along with BookReader. If not, see <http://www.gnu.org/licenses/>.
*/
+require_once("BookReaderMeta.inc.php");
+
class BookReaderImages
{
public $MIMES = array('gif' => 'image/gif',
'png' => 'png',
'tif' => 'tiff',
'tiff' => 'tiff');
-
+
+ // Width when generating thumbnails
+ public $imageSizes = array(
+ 'thumb' => 100,
+ 'small' => 240,
+ 'medium' => 500,
+ 'large' => 1024,
+ );
+
// Paths to command-line tools
var $exiftool = '/petabox/sw/books/exiftool/exiftool';
var $kduExpand = '/petabox/sw/bin/kdu_expand';
/*
+ * Serve an image request that requires looking up the book metadata
+ *
+ * Code path:
+ * - Get book metadata
+ * - Parse the requested page (e.g. cover_t.jpg, n5_r4.jpg) to determine which page type,
+ * size and format (etc) is being requested
+ * - Determine the leaf number corresponding to the page
+ * - Determine scaling values
+ * - Serve image request now that all information has been gathered
+ */
+
+ function serveLookupRequest($requestEnv) {
+ $brm = new BookReaderMeta();
+ try {
+ $metadata = $brm->buildMetadata($_REQUEST['id'], $_REQUEST['itemPath'], $_REQUEST['subPrefix'], $_REQUEST['server']);
+ } catch (Exception $e) {
+ $this->BRfatal($e->getMessage);
+ }
+
+ $page = $_REQUEST['page'];
+
+ // Index of image to return
+ $imageIndex = null;
+
+ // deal with subPrefix
+ if ($_REQUEST['subPrefix']) {
+ $parts = split('/', $_REQUEST['subPrefix']);
+ $bookId = $parts[count($parts) - 1 ];
+ } else {
+ $bookId = $_REQUEST['id'];
+ }
+
+ $pageInfo = $this->parsePageRequest($page, $bookId);
+
+ $basePage = $pageInfo['type'];
+
+ switch ($basePage) {
+ case 'title':
+ if (! array_key_exists('titleIndex', $metadata)) {
+ $this->BRfatal("No title page asserted in book");
+ }
+ $imageIndex = $metadata['titleIndex'];
+ break;
+
+ case 'cover':
+ if (! array_key_exists('coverIndices', $metadata)) {
+ $this->BRfatal("No cover asserted in book");
+ }
+ $imageIndex = $metadata['coverIndices'][0]; // $$$ TODO add support for other covers
+ break;
+
+ case 'preview':
+ // Preference is:
+ // Cover page if book was published >= 1950
+ // Title page
+ // Cover page
+ // Page 0
+
+ if ( array_key_exists('date', $metadata) && array_key_exists('coverIndices', $metadata) ) {
+ if ($brm->parseYear($metadata['date']) >= 1950) {
+ $imageIndex = $metadata['coverIndices'][0];
+ break;
+ }
+ }
+ if (array_key_exists('titleIndex', $metadata)) {
+ $imageIndex = $metadata['titleIndex'];
+ break;
+ }
+ if (array_key_exists('coverIndices', $metadata)) {
+ $imageIndex = $metadata['coverIndices'][0];
+ break;
+ }
+
+ // First page
+ $imageIndex = 0;
+ break;
+
+ case 'n':
+ // Accessible index page
+ $imageIndex = intval($pageInfo['value']);
+ break;
+
+ case 'page':
+ // Named page
+ $index = array_search($pageInfo['value'], $metadata['pageNums']);
+ if ($index === FALSE) {
+ // Not found
+ $this->BRfatal("Page not found");
+ break;
+ }
+
+ $imageIndex = $index;
+ break;
+
+ default:
+ // Shouldn't be possible
+ $this->BRfatal("Unrecognized page type requested");
+ break;
+
+ }
+
+ $leaf = $brm->leafForIndex($imageIndex, $metadata['leafNums']);
+
+ $requestEnv = array(
+ 'zip' => $metadata['zip'],
+ 'file' => $brm->imageFilePath($leaf, $metadata['subPrefix'], $metadata['imageFormat']),
+ 'ext' => 'jpg',
+ );
+
+ // Return image data - will check privs
+ $this->serveRequest($requestEnv);
+
+ }
+
+ /*
+ * Returns a page image when all parameters such as the image stack location are
+ * passed in.
+ *
* Approach:
*
* Get info about requested image (input)
* Return image data
* Clean up temporary files
*/
-
function serveRequest($requestEnv) {
// Process some of the request parameters
$zipPath = $requestEnv['zip'];
} else {
$callback = null;
}
-
+
+ if ( !file_exists($zipPath) ) {
+ $this->BRfatal('Image stack does not exist at ' . $zipPath);
+ }
// Make sure the image stack is readable - return 403 if not
$this->checkPrivs($zipPath);
// The pbmreduce reduction factor produces an image with dimension 1/n
// The kakadu reduction factor produceds an image with dimension 1/(2^n)
- // $$$ handle continuous values for scale
if (isset($requestEnv['height'])) {
- $ratio = floatval($requestEnv['origHeight']) / floatval($requestEnv['height']);
- if ($ratio <= 2) {
- $scale = 2;
- $powReduce = 1;
- } else if ($ratio <= 4) {
- $scale = 4;
- $powReduce = 2;
- } else {
- //$powReduce = 3; //too blurry!
- $scale = 2;
- $powReduce = 1;
- }
-
+ $powReduce = $this->nearestPow2Reduce($requestEnv['height'], $imageInfo['height']);
+ $scale = pow(2, $powReduce);
+ } else if (isset($requestEnv['width'])) {
+ $powReduce = $this->nearestPow2Reduce($requestEnv['width'], $imageInfo['width']);
+ $scale = pow(2, $powReduce);
+
} else {
// $$$ could be cleaner
// Provide next smaller power of two reduction
- $scale = intval($requestEnv['scale']);
- if (1 >= $scale) {
- $powReduce = 0;
- } else if (2 > $scale) {
- $powReduce = 0;
- } else if (4 > $scale) {
- $powReduce = 1;
- } else if (8 > $scale) {
- $powReduce = 2;
- } else if (16 > $scale) {
- $powReduce = 3;
- } else if (32 > $scale) {
- $powReduce = 4;
- } else if (64 > $scale) {
- $powReduce = 5;
+ $scale = $requestEnv['scale'];
+ if (!$scale) {
+ $scale = 1;
+ }
+ if (array_key_exists($scale, $this->imageSizes)) {
+ $srcRatio = floatval($imageInfo['width']) / floatval($imageInfo['height']);
+ if ($srcRatio > 1) {
+ // wide
+ $dimension = 'width';
+ } else {
+ $dimension = 'height';
+ }
+ $powReduce = $this->nearestPow2Reduce($this->imageSizes[$scale], $imageInfo[$dimension]);
} else {
- // $$$ Leaving this in as default though I'm not sure why it is...
- $powReduce = 3;
+ $powReduce = $this->nearestPow2ForScale($scale);
}
$scale = pow(2, $powReduce);
}
system('ln -s /dev/stdout ' . $stdoutLink);
}
-
putenv('LD_LIBRARY_PATH=/petabox/sw/lib/kakadu');
$unzipCmd = $this->getUnarchiveCommand($zipPath, $file);
$filenameForClient = $this->filenameForClient($file, $ext);
- $headers = array('Content-type: '. $MIMES[$ext],
+ $headers = array('Content-type: '. $MIMES[$ext], // XXX is nginx swallowing this?
'Cache-Control: max-age=15552000',
'Content-disposition: inline; filename=' . $filenameForClient);
$bits = intval($tags["BitDepth"]);
break;
default:
- $this->BRfatal("Unsupported image type");
+ $this->BRfatal("Unsupported image type $type for file $file in $zipPath");
break;
}
}
function BRfatal($string) {
- echo "alert('$string');\n";
- die(-1);
+ throw new Exception("Image error: $string");
}
// Returns true if using a power node
}
return $pathParts['filename'] . '.' . $ext;
}
+
+ // Returns the nearest power of 2 reduction factor that results in a larger image
+ function nearestPow2Reduce($desiredDimension, $sourceDimension) {
+ $ratio = floatval($sourceDimension) / floatval($desiredDimension);
+ return $this->nearestPow2ForScale($ratio);
+ }
+
+ // Returns nearest power of 2 reduction factor that results in a larger image
+ function nearestPow2ForScale($scale) {
+ $scale = intval($scale);
+ if ($scale <= 1) {
+ return 0;
+ }
+ $binStr = decbin($scale); // convert to binary string. e.g. 5 -> '101'
+ return strlen($binStr) - 1;
+ }
+
+ /*
+ * Parses a page request like "page5_r2.jpg" or "cover_t.jpg" to corresponding
+ * page type, size, reduce, and format
+ */
+ function parsePageRequest($pageRequest, $bookPrefix) {
+
+ $pageInfo = array();
+
+ // Pull off extension
+ if (preg_match('#(.*)\.([^.]+)$#', $pageRequest, $matches) === 1) {
+ $pageRequest = $matches[1];
+ $extension = $matches[2];
+ if ($extension == 'jpeg') {
+ $extension = 'jpg';
+ }
+ } else {
+ $extension = 'jpg';
+ }
+ $pageInfo['extension'] = $extension;
+
+ // Split parts out
+ $parts = explode('_', $pageRequest);
+
+ // Remove book prefix if it was included (historical)
+ if ($parts[0] == $bookPrefix) {
+ array_shift($parts);
+ }
+
+ if (count($parts) === 0) {
+ $this->BRfatal('No page type specified');
+ }
+ $page = array_shift($parts);
+
+ $pageTypes = array(
+ 'page' => 'str',
+ 'n' => 'num',
+ 'cover' => 'single',
+ 'preview' => 'single',
+ 'title' => 'single'
+ );
+
+ // Look for known page types
+ foreach ( $pageTypes as $pageName => $kind ) {
+ if ( preg_match('#^(' . $pageName . ')(.*)#', $page, $matches) === 1 ) {
+ $pageInfo['type'] = $matches[1];
+ switch ($kind) {
+ case 'str':
+ $pageInfo['value'] = $matches[2];
+ break;
+ case 'num':
+ $pageInfo['value'] = intval($matches[2]);
+ break;
+ case 'single':
+ break;
+ }
+ }
+ }
+
+ if ( !array_key_exists('type', $pageInfo) ) {
+ $this->BRfatal('Unrecognized page type');
+ }
+
+ // Look for other known parts
+ foreach ($parts as $part) {
+ $start = substr($part, 0, 1);
+
+ switch ($start) {
+ case 't':
+ $pageInfo['size'] = $start;
+ break;
+ case 'r':
+ $pageInfo['reduce'] = substr($part, 0);
+ break;
+ default:
+ // Unrecognized... just let it pass
+ break;
+ }
+ }
+
+ return $pageInfo;
+ }
+
}
?>
\ No newline at end of file