'tile' => 'tile',
'w' => 'width',
'h' => 'height',
- 'rotate' => 'rotate'
+ 'x' => 'x',
+ 'y' => 'y',
+ 'rot' => 'rotate'
);
// Paths to command-line tools
$basePage = $pageInfo['type'];
$leaf = null;
+ $region = null;
switch ($basePage) {
case 'title':
// Leaf explicitly specified
$leaf = $pageInfo['value'];
break;
-
+
default:
// Shouldn't be possible
$this->BRfatal("Unrecognized page type requested");
// Get the image size and depth
$imageInfo = $this->getImageInfo($zipPath, $file);
-
+
// Output json if requested
if ('json' == $ext) {
// $$$ we should determine the output size first based on requested scale
// image processing load on our cluster. The client should then scale to their final
// needed size.
- // Set scale from height or width if set
- if (isset($requestEnv['height'])) {
+ // Set scale from height or width if set and no x or y specified
+ if ( isset($requestEnv['height']) && !isset($requestEnv['x']) && !isset($requestEnv['y']) ) {
+ // No x or y specified, use height for scaling
$powReduce = $this->nearestPow2Reduce($requestEnv['height'], $imageInfo['height']);
$scale = pow(2, $powReduce);
- } else if (isset($requestEnv['width'])) {
+ } else if ( isset($requestEnv['width']) && !isset($requestEnv['x']) && !isset($requestEnv['y']) ) {
+ // No x or y specified, use width for scaling
$powReduce = $this->nearestPow2Reduce($requestEnv['width'], $imageInfo['width']);
$scale = pow(2, $powReduce);
}
}
+ // Only extract a specific region if x or y were set
+ $region = array();
+ if (isset($reqeuestEnv['x']) || isset($requestEnv['y'])) {
+ foreach (array('x', 'y', 'width', 'height') as $key) {
+ if (array_key_exists($key, $requestEnv)) {
+ $region[$key] = $requestEnv[$key];
+ }
+ }
+ }
+ $regionDimensions = $this->getRegionDimensions($imageInfo, $region);
+
+ /*
+ print('imageInfo');
+ print_r($imageInfo);
+ print('region');
+ print_r($region);
+ print('regionDimensions');
+ print_r($regionDimensions);
+ print('asFloat');
+ print_r($this->getRegionDimensionsAsFloat($imageInfo, $region));
+ die(-1);
+ */
+
+
// Override depending on source image format
// $$$ consider doing a 302 here instead, to make better use of the browser cache
// Limit scaling for 1-bit images. See https://bugs.edge.launchpad.net/bookreader/+bug/486011
$unzipCmd = $this->getUnarchiveCommand($zipPath, $file);
- $decompressCmd = $this->getDecompressCmd($imageInfo['type'], $powReduce, $rotate, $scale, $stdoutLink);
-
+ $decompressCmd = $this->getDecompressCmd($imageInfo, $powReduce, $rotate, $scale, $region, $stdoutLink);
+
// Non-integer scaling is currently disabled on the cluster
// if (isset($_REQUEST['height'])) {
// $cmd .= " | pnmscale -height {$_REQUEST['height']} ";
$powReduce = min($powReduce, $maxReduce);
$reduce = pow(2, $powReduce);
- $cmd = $unzipCmd . $this->getDecompressCmd($imageInfo['type'], $powReduce, $rotate, $scale, $stdoutLink) . $compressCmd;
+ $cmd = $unzipCmd . $this->getDecompressCmd($imageInfo, $powReduce, $rotate, $scale, $region, $stdoutLink) . $compressCmd;
trigger_error('BookReader rerunning with new cmd: ' . $cmd, E_USER_WARNING);
if ($this->passthruIfSuccessful($headers, $cmd, $errorMessage)) { // $$$ move to BookReaderRequest
$recovered = true;
echo $jsonOutput;
}
- function getDecompressCmd($imageType, $powReduce, $rotate, $scale, $stdoutLink) {
+ function getDecompressCmd($srcInfo, $powReduce, $rotate, $scale, $region, $stdoutLink) {
- switch ($imageType) {
+ switch ($srcInfo['type']) {
case 'jp2':
+ $regionAsFloat = $this->getRegionDimensionsAsFloat($srcInfo, $region);
+ $regionString = sprintf("{%f,%f},{%f,%f}", $regionAsFloat['y'], $regionAsFloat['x'], $regionAsFloat['h'], $regionAsFloat['w']);
$decompressCmd =
- " | " . $this->kduExpand . " -no_seek -quiet -reduce $powReduce -rotate $rotate -i /dev/stdin -o " . $stdoutLink;
+ " | " . $this->kduExpand . " -no_seek -quiet -reduce $powReduce -rotate $rotate -region $regionString -i /dev/stdin -o " . $stdoutLink;
if ($this->decompressToBmp) {
// We suppress output since bmptopnm always outputs on stderr
$decompressCmd .= ' | (bmptopnm 2>/dev/null)';
}
- break;
-
+ break;
+/*
case 'tiff':
// We need to create a temporary file for tifftopnm since it cannot
// work on a pipe (the file must be seekable).
case 'png':
$decompressCmd = ' | ( pngtopnm 2>/dev/null ) ' . $this->reduceCommand($scale);
break;
+*/
+
+ // Formats handled by ImageMagick
+ case 'tiff':
+ case 'jpeg':
+ case 'png':
+ $region = $this->getRegionDimensions($srcInfo, $region);
+ $regionString = sprintf('[%dx%d+%d+%d]', $region['w'], $region['h'], $region['x'], $region['y']);
+
+ // The argument to ImageMagick's scale command is a "geometry". We pass in the new width/height
+ $scaleString = ' -scale ' . sprintf("%dx%d", $region['w'] / $scale, $region['h'] / $scale);
+
+ $rotateString = '';
+ if ($rotate && $rotate != '0') {
+ $rotateString = ' -rotate ' . $rotate; // was previously checked to be a known value
+ }
+
+ $decompressCmd = ' | convert -' . $regionString . $scaleString . $rotateString . ' pnm:-';
+ break;
default:
$this->BRfatal('Unknown image type: ' . $imageType);
return $decompressCmd;
}
+
// If the command has its initial output on stdout the headers will be emitted followed
// by the stdout output. If initial output is on stderr an error message will be
// returned.
return $pageInfo;
}
+ function getRegionDimensions($sourceDimensions, $regionDimensions) {
+ // Return region dimensions as { 'x' => xOffset, 'y' => yOffset, 'w' => width, 'h' => height }
+ // in terms of full resolution image.
+ // Note: this will clip the returned dimensions to fit within the source image
+
+ $sourceX = 0;
+ if (array_key_exists('x', $regionDimensions)) {
+ $sourceX = $this->intAmount($regionDimensions['x'], $sourceDimensions['width']);
+ }
+ $sourceX = $this->clamp(0, $sourceDimensions['width'] - 2, $sourceX); // Allow at least one pixel
+
+ $sourceY = 0;
+ if (array_key_exists('y', $regionDimensions)) {
+ $sourceY = $this->intAmount($regionDimensions['y'], $sourceDimensions['height']);
+ }
+ $sourceY = $this->clamp(0, $sourceDimensions['height'] - 2, $sourceY); // Allow at least one pixel
+
+ $sourceWidth = $sourceDimensions['width'] - $sourceX;
+ if (array_key_exists('width', $regionDimensions)) {
+ $sourceWidth = $this->intAmount($regionDimensions['width'], $sourceDimensions['width']);
+ }
+ $sourceWidth = $this->clamp(1, max(1, $sourceDimensions['width'] - $sourceX), $sourceWidth);
+
+ $sourceHeight = $sourceDimensions['height'] - $sourceY;
+ if (array_key_exists('height', $regionDimensions)) {
+ $sourceHeight = $this->intAmount($regionDimensions['height'], $sourceDimensions['height']);
+ }
+ $sourceHeight = $this->clamp(1, max(1, $sourceDimensions['height'] - $sourceY), $sourceHeight);
+
+ return array('x' => $sourceX, 'y' => $sourceY, 'w' => $sourceWidth, 'h' => $sourceHeight);
+ }
+
+ function getRegionDimensionsAsFloat($sourceDimensions, $regionDimensions) {
+ // Return region dimensions as { 'x' => xOffset, 'y' => yOffset, 'w' => width, 'h' => height }
+ // in terms of full resolution image.
+ // Note: this will clip the returned dimensions to fit within the source image
+
+ $sourceX = 0;
+ if (array_key_exists('x', $regionDimensions)) {
+ $sourceX = $this->floatAmount($regionDimensions['x'], $sourceDimensions['width']);
+ }
+ $sourceX = $this->clamp(0.0, 1.0, $sourceX);
+
+ $sourceY = 0;
+ if (array_key_exists('y', $regionDimensions)) {
+ $sourceY = $this->floatAmount($regionDimensions['y'], $sourceDimensions['height']);
+ }
+ $sourceY = $this->clamp(0.0, 1.0, $sourceY);
+
+ $sourceWidth = 1 - $sourceX;
+ if (array_key_exists('width', $regionDimensions)) {
+ $sourceWidth = $this->floatAmount($regionDimensions['width'], $sourceDimensions['width']);
+ }
+ $sourceWidth = $this->clamp(0.0, 1.0, $sourceWidth);
+
+ $sourceHeight = 1 - $sourceY;
+ if (array_key_exists('height', $regionDimensions)) {
+ $sourceHeight = $this->floatAmount($regionDimensions['height'], $sourceDimensions['height']);
+ }
+ $sourceHeight = $this->clamp(0.0, 1.0, $sourceHeight);
+
+ return array('x' => $sourceX, 'y' => $sourceY, 'w' => $sourceWidth, 'h' => $sourceHeight);
+ }
+
+ function intAmount($stringValue, $maximum) {
+ // Returns integer amount for string like "5" (5 units) or "0.5" (50%)
+ if (strpos($stringValue, '.') === false) {
+ // No decimal, assume int
+ return intval($stringValue);
+ }
+
+ return floatval($stringValue) * $maximum + 0.5;
+ }
+
+ function floatAmount($stringValue, $maximum) {
+ // Returns float amount (0.0 to 1.0) for string like "0.4" (40%) or "4" (40% if max is 10)
+ if (strpos($stringValue, ".") === false) {
+ // No decimal, assume int value out of maximum
+ return floatval($stringValue) / $maximum;
+ }
+
+ // Given float - just pass through
+ return floatval($stringValue);
+ }
+
+ function clamp($minValue, $maxValue, $observedValue) {
+ if ($observedValue < $minValue) {
+ return $minValue;
+ }
+
+ if ($observedValue > $maxValue) {
+ return $maxValue;
+ }
+
+ return $observedValue;
+ }
+
// Clean up temporary files and resources
function cleanup() {
foreach($this->tempFiles as $tempFile) {
var img = new Image();
$(img).bind( 'load error', function(eventObj) {
equals(eventObj.type, 'load', 'Load image (' + pageURI + '). Event handler called');
- equals(this.width, 702, 'Image width');
+ equals(this.width, 701, 'Image width');
start();
})
.attr('src', pageURI);
var img = new Image();
$(img).bind( 'load error', function(eventObj) {
equals(eventObj.type, 'load', 'Load image (' + pageURI + '). Event handler called');
- equals(this.width, 244, 'Image width');
+ equals(this.width, 243, 'Image width');
start();
})
.attr('src', pageURI);
});
+
+asyncTest('Load image region using /download URL - populationsc18400378unit/page/n800_x1544_y4144_w1192_h848_s4.jpg', function() {
+ expect(3);
+ var pageURI = testHost() + '/download/populationsc18400378unit/page/n800_x1544_y4144_w1192_h848_s4.jpg';
+
+ var img = new Image();
+ $(img).bind( 'load error', function(eventObj) {
+ equals(eventObj.type, 'load', 'Load image (' + pageURI + '). Event handler called');
+ equals(this.width, 299, 'Image width');
+ equals(this.height, 212, 'Image height');
+ start();
+ })
+ .attr('src', pageURI);
+
+});
+
+
+asyncTest('Load image region using /download URL with decimal coordinates - populationsc18400378unit/page/n800_x0.75_y0.75_w0.25_h0.25_s4.jpg', function() {
+ expect(3);
+ var pageURI = testHost() + '/download/populationsc18400378unit/page/n800_x0.75_y0.75_w0.25_h0.25_s4.jpg';
+
+ var img = new Image();
+ $(img).bind( 'load error', function(eventObj) {
+ equals(eventObj.type, 'load', 'Load image (' + pageURI + '). Event handler called');
+ equals(this.width, 337, 'Image width');
+ equals(this.height, 342, 'Image height');
+ start();
+ })
+ .attr('src', pageURI);
+
+});
+
+
+asyncTest('Load image region - tomslademotorcyc00fitz/page/page3_x256_y96_w1720_h152_s4.jpg', function() {
+ expect(3);
+ var pageURI = testHost() + '/download/tomslademotorcyc00fitz/page/page3_x256_y96_w1720_h152_s4.jpg';
+
+ var img = new Image();
+ $(img).bind( 'load error', function(eventObj) {
+ equals(eventObj.type, 'load', 'Load image (' + pageURI + '). Event handler called');
+ equals(this.width, 430, 'Image width');
+ equals(this.height, 38, 'Image height');
+ start();
+ })
+ .attr('src', pageURI);
+
+});
+
+asyncTest('Load image region from tiff, via br.getRegionURI - fightingflyingc00rickgoog - n17_x1944_y1708_w668_h584', function() {
+
+ $.getScript( jsLocateURL('fightingflyingc00rickgoog'), function() {
+
+ expect(3);
+ var pageURI = br.getRegionURI(17, undefined, undefined, 1944, 1708, 668, 584);
+
+ var img = new Image();
+ $(img).bind( 'load error', function(eventObj) {
+ equals(eventObj.type, 'load', 'Load image (' + pageURI + '). Event handler called');
+ equals(this.width, 668, 'Image width');
+ equals(this.height, 584, 'Image height');
+ start();
+ })
+ .attr('src', pageURI);
+
+ });
+});
+
+asyncTest('Same image rotated 90 degrees, br.getRegionURI - fightingflyingc00rickgoog - n17_x1944_y1708_w668_h584_rot90', function() {
+
+ $.getScript( jsLocateURL('fightingflyingc00rickgoog'), function() {
+
+ expect(3);
+ var pageURI = br.getRegionURI(17, undefined, 90, 1944, 1708, 668, 584);
+
+ var img = new Image();
+ $(img).bind( 'load error', function(eventObj) {
+ equals(eventObj.type, 'load', 'Load image (' + pageURI + '). Event handler called');
+ equals(this.width, 584, 'Image width');
+ equals(this.height, 668, 'Image height');
+ start();
+ })
+ .attr('src', pageURI);
+
+ });
+});
+