X-Git-Url: http://git.rot13.org/?a=blobdiff_plain;f=BookReaderIA%2Fdatanode%2FBookReaderImages.php;h=5d3c99d503a8aa42eea31217a9a2696c5145948b;hb=b7835fd00ce70fd75937b3c81769cfebfa0e5700;hp=9128489cfa2d9ef5ec78fecf87fc5ccaed1f04b4;hpb=d1d89a11bd8975840475a0516657fa2920b48843;p=bookreader.git diff --git a/BookReaderIA/datanode/BookReaderImages.php b/BookReaderIA/datanode/BookReaderImages.php index 9128489..5d3c99d 100644 --- a/BookReaderIA/datanode/BookReaderImages.php +++ b/BookReaderIA/datanode/BookReaderImages.php @@ -1,9 +1,10 @@ . */ -$MIMES = array('gif' => 'image/gif', - 'jp2' => 'image/jp2', - 'jpg' => 'image/jpeg', - 'jpeg' => 'image/jpeg', - 'png' => 'image/png', - 'tif' => 'image/tiff', - 'tiff' => 'image/tiff'); - -$EXTENSIONS = array('gif' => 'gif', - 'jp2' => 'jp2', - 'jpeg' => 'jpeg', - 'jpg' => 'jpeg', - 'png' => 'png', - 'tif' => 'tiff', - 'tiff' => 'tiff'); - -$exiftool = '/petabox/sw/books/exiftool/exiftool'; - -// Process some of the request parameters -$zipPath = $_REQUEST['zip']; -$file = $_REQUEST['file']; -if (isset($_REQUEST['ext'])) { - $ext = $_REQUEST['ext']; -} else { - // Default to jpg - $ext = 'jpg'; -} -if (isset($_REQUEST['callback'])) { - // XXX sanitize - $callback = $_REQUEST['callback']; -} else { - $callback = null; -} - -/* - * Approach: - * - * Get info about requested image (input) - * Get info about requested output format - * Determine processing parameters - * Process image - * Return image data - * Clean up temporary files - */ - -function getUnarchiveCommand($archivePath, $file) +class BookReaderImages { - $lowerPath = strtolower($archivePath); - if (preg_match('/\.([^\.]+)$/', $lowerPath, $matches)) { - $suffix = $matches[1]; - - if ($suffix == 'zip') { - return 'unzip -p ' - . escapeshellarg($archivePath) - . ' ' . escapeshellarg($file); - } else if ($suffix == 'tar') { - return '7z e -so ' - . escapeshellarg($archivePath) - . ' ' . escapeshellarg($file); + public $MIMES = array('gif' => 'image/gif', + 'jp2' => 'image/jp2', + 'jpg' => 'image/jpeg', + 'jpeg' => 'image/jpeg', + 'png' => 'image/png', + 'tif' => 'image/tiff', + 'tiff' => 'image/tiff'); + + public $EXTENSIONS = array('gif' => 'gif', + 'jp2' => 'jp2', + 'jpeg' => 'jpeg', + 'jpg' => 'jpeg', + 'png' => 'png', + 'tif' => 'tiff', + 'tiff' => 'tiff'); + + // Paths to command-line tools + var $exiftool = '/petabox/sw/books/exiftool/exiftool'; + var $kduExpand = '/petabox/sw/bin/kdu_expand'; + + /* + * Approach: + * + * Get info about requested image (input) + * Get info about requested output format + * Determine processing parameters + * Process image + * Return image data + * Clean up temporary files + */ + + function serveRequest($requestEnv) { + // Process some of the request parameters + $zipPath = $requestEnv['zip']; + $file = $requestEnv['file']; + if (! $ext) { + $ext = $requestEnv['ext']; } else { - BRfatal('Incompatible archive format'); + // Default to jpg + $ext = 'jpeg'; } - - } else { - BRfatal('Bad image stack path'); - } - - BRfatal('Bad image stack path or archive format'); - -} - -/* - * Returns the image type associated with the file extension. - */ -function imageExtensionToType($extension) -{ - global $EXTENSIONS; - - if (array_key_exists($extension, $EXTENSIONS)) { - return $EXTENSIONS[$extension]; - } else { - BRfatal('Unknown image extension'); - } -} - -/* - * Get the image width, height and depth from a jp2 file in zip. - */ -function getImageInfo($zipPath, $file) -{ - global $exiftool; + if (isset($requestEnv['callback'])) { + // validate callback is valid JS identifier (only) + $callback = $requestEnv['callback']; + $identifierPatt = '/^[[:alpha:]$_]([[:alnum:]$_])*$/'; + if (! preg_match($identifierPatt, $callback)) { + $this->BRfatal('Invalid callback'); + } + } else { + $callback = null; + } + + // Make sure the image stack is readable - return 403 if not + $this->checkPrivs($zipPath); + + + // 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 + $this->outputJSON($imageInfo, $callback); // $$$ move to BookReaderRequest + exit; + } + + // Unfortunately kakadu requires us to know a priori if the + // output file should be .ppm or .pgm. By decompressing to + // .bmp kakadu will write a file we can consistently turn into + // .pnm. Really kakadu should support .pnm as the file output + // extension and automatically write ppm or pgm format as + // appropriate. + $this->decompressToBmp = true; // $$$ shouldn't be necessary if we use file info to determine output format + if ($this->decompressToBmp) { + $stdoutLink = '/tmp/stdout.bmp'; + } else { + $stdoutLink = '/tmp/stdout.ppm'; + } + + $fileExt = strtolower(pathinfo($file, PATHINFO_EXTENSION)); + + // Rotate is currently only supported for jp2 since it does not add server load + $allowedRotations = array("0", "90", "180", "270"); + $rotate = $requestEnv['rotate']; + if ( !in_array($rotate, $allowedRotations) ) { + $rotate = "0"; + } + + // Image conversion options + $pngOptions = ''; + $jpegOptions = '-quality 75'; + + // 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; + } + + } 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; + } else { + // $$$ Leaving this in as default though I'm not sure why it is... + $powReduce = 3; + } + $scale = pow(2, $powReduce); + } + + // 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 + if (1 == $imageInfo['bits']) { + if ($scale > 1) { + $scale /= 2; + $powReduce -= 1; + + // Hard limit so there are some black pixels to use! + if ($scale > 4) { + $scale = 4; + $powReduce = 2; + } + } + } + + if (!file_exists($stdoutLink)) + { + system('ln -s /dev/stdout ' . $stdoutLink); + } + + + putenv('LD_LIBRARY_PATH=/petabox/sw/lib/kakadu'); + + $unzipCmd = $this->getUnarchiveCommand($zipPath, $file); + + $decompressCmd = $this->getDecompressCmd($imageInfo['type'], $powReduce, $rotate, $scale, $stdoutLink); + + // Non-integer scaling is currently disabled on the cluster + // if (isset($_REQUEST['height'])) { + // $cmd .= " | pnmscale -height {$_REQUEST['height']} "; + // } + + switch ($ext) { + case 'png': + $compressCmd = ' | pnmtopng ' . $pngOptions; + break; + + case 'jpeg': + case 'jpg': + default: + $compressCmd = ' | pnmtojpeg ' . $jpegOptions; + $ext = 'jpeg'; // for matching below + break; + + } + + if (($ext == $fileExt) && ($scale == 1) && ($rotate === "0")) { + // Just pass through original data if same format and size + $cmd = $unzipCmd; + } else { + $cmd = $unzipCmd . $decompressCmd . $compressCmd; + } + + // print $cmd; + + $headers = array('Content-type: '. $MIMES[$ext], + 'Cache-Control: max-age=15552000'); + + $errorMessage = ''; + if (! $this->passthruIfSuccessful($headers, $cmd, $errorMessage)) { // $$$ move to BookReaderRequest + // $$$ automated reporting + trigger_error('BookReader Processing Error: ' . $cmd . ' -- ' . $errorMessage, E_USER_WARNING); + + // Try some content-specific recovery + $recovered = false; + if ($imageInfo['type'] == 'jp2') { + $records = $this->getJp2Records($zipPath, $file); + if ($powReduce > intval($records['Clevels'])) { + $powReduce = $records['Clevels']; + $reduce = pow(2, $powReduce); + } else { + $reduce = 1; + $powReduce = 0; + } + + $cmd = $unzipCmd . $this->getDecompressCmd($imageInfo['type'], $powReduce, $rotate, $scale, $stdoutLink) . $compressCmd; + if ($this->passthruIfSuccessful($headers, $cmd, $errorMessage)) { // $$$ move to BookReaderRequest + $recovered = true; + } else { + trigger_error('BookReader fallback image processing also failed: ' . $errorMessage, E_USER_WARNING); + } + } + + if (! $recovered) { + $this->BRfatal('Problem processing image - command failed'); + } + } + + if (isset($tempFile)) { + unlink($tempFile); + } + } - $fileExt = strtolower(pathinfo($file, PATHINFO_EXTENSION)); - $type = imageExtensionToType($fileExt); - - // We look for all the possible tags of interest then act on the - // ones presumed present based on the file type - $tagsToGet = ' -ImageWidth -ImageHeight -FileType' // all formats - . ' -BitsPerComponent -ColorSpace' // jp2 - . ' -BitDepth' // png - . ' -BitsPerSample'; // tiff - - $cmd = getUnarchiveCommand($zipPath, $file) - . ' | '. $exiftool . ' -S -fast' . $tagsToGet . ' -'; - exec($cmd, $output); + function getUnarchiveCommand($archivePath, $file) + { + $lowerPath = strtolower($archivePath); + if (preg_match('/\.([^\.]+)$/', $lowerPath, $matches)) { + $suffix = $matches[1]; + + if ($suffix == 'zip') { + return 'unzip -p ' + . escapeshellarg($archivePath) + . ' ' . escapeshellarg($file); + } else if ($suffix == 'tar') { + return ' ( 7z e -so ' + . escapeshellarg($archivePath) + . ' ' . escapeshellarg($file) . ' 2>/dev/null ) '; + } else { + $this->BRfatal('Incompatible archive format'); + } - $tags = Array(); - foreach ($output as $line) { - $keyValue = explode(": ", $line); - $tags[$keyValue[0]] = $keyValue[1]; + } else { + $this->BRfatal('Bad image stack path'); + } + + $this->BRfatal('Bad image stack path or archive format'); + } - $width = intval($tags["ImageWidth"]); - $height = intval($tags["ImageHeight"]); - $type = strtolower($tags["FileType"]); - - switch ($type) { - case "jp2": - $bits = intval($tags["BitsPerComponent"]); - break; - case "tiff": - $bits = intval($tags["BitsPerSample"]); - break; - case "jpeg": - $bits = 8; - break; - case "png": - $bits = intval($tags["BitDepth"]); - break; - default: - BRfatal("Unsupported image type"); - break; + /* + * Returns the image type associated with the file extension. + */ + function imageExtensionToType($extension) + { + + if (array_key_exists($extension, $this->EXTENSIONS)) { + return $this->EXTENSIONS[$extension]; + } else { + $this->BRfatal('Unknown image extension'); + } } - - - $retval = Array('width' => $width, 'height' => $height, - 'bits' => $bits, 'type' => $type); - return $retval; -} - -/* - * Output JSON given the imageInfo associative array - */ -function outputJSON($imageInfo, $callback) -{ - header('Content-type: text/plain'); - $jsonOutput = json_encode($imageInfo); - if ($callback) { - $jsonOutput = $callback . '(' . $jsonOutput . ');'; + /* + * Get the image information. The returned associative array fields will + * vary depending on the image type. The basic keys are width, height, type + * and bits. + */ + function getImageInfo($zipPath, $file) + { + return $this->getImageInfoFromExif($zipPath, $file); // this is fast + + /* + $fileExt = strtolower(pathinfo($file, PATHINFO_EXTENSION)); + $type = imageExtensionToType($fileExt); + + switch ($type) { + case "jp2": + return getImageInfoFromJp2($zipPath, $file); + + default: + return getImageInfoFromExif($zipPath, $file); + } + */ } - echo $jsonOutput; -} - -// Get the image size and depth -$imageInfo = getImageInfo($zipPath, $file); - -// Output json if requested -if ('json' == $ext) { - // $$$ we should determine the output size first based on requested scale - outputJSON($imageInfo, $callback); - exit; -} - -// Unfortunately kakadu requires us to know a priori if the -// output file should be .ppm or .pgm. By decompressing to -// .bmp kakadu will write a file we can consistently turn into -// .pnm. Really kakadu should support .pnm as the file output -// extension and automatically write ppm or pgm format as -// appropriate. -$decompressToBmp = true; -if ($decompressToBmp) { - $stdoutLink = '/tmp/stdout.bmp'; -} else { - $stdoutLink = '/tmp/stdout.ppm'; -} - -$fileExt = strtolower(pathinfo($file, PATHINFO_EXTENSION)); - -// Rotate is currently only supported for jp2 since it does not add server load -$allowedRotations = array("0", "90", "180", "270"); -$rotate = $_REQUEST['rotate']; -if ( !in_array($rotate, $allowedRotations) ) { - $rotate = "0"; -} - -// Image conversion options -$pngOptions = ''; -$jpegOptions = '-quality 75'; - -// 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($_REQUEST['height'])) { - $ratio = floatval($_REQUEST['origHeight']) / floatval($_REQUEST['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; + + // Get the records of of JP2 as returned by kdu_expand + function getJp2Records($zipPath, $file) + { + + $cmd = $this->getUnarchiveCommand($zipPath, $file) + . ' | ' . $this->kduExpand + . ' -no_seek -quiet -i /dev/stdin -record /dev/stdout'; + exec($cmd, $output); + + $records = Array(); + foreach ($output as $line) { + $elems = explode("=", $line, 2); + if (1 == count($elems)) { + // delimiter not found + continue; + } + $records[$elems[0]] = $elems[1]; + } + + return $records; } - -} else { - $scale = $_REQUEST['scale']; - if (1 >= $scale) { - $scale = 1; - $powReduce = 0; - } else if (2 == $scale) { - $powReduce = 1; - } else if (4 == $scale) { - $powReduce = 2; - } else if (8 == $scale) { - $powReduce = 3; - } else if (16 == $scale) { - $powReduce = 4; - } else if (32 == $scale) { - $powReduce = 5; - } else { - // $$$ Leaving this in as default though I'm not sure why it is... - $scale = 8; - $powReduce = 3; + + /* + * Get the image width, height and depth using the EXIF information. + */ + function getImageInfoFromExif($zipPath, $file) + { + + // We look for all the possible tags of interest then act on the + // ones presumed present based on the file type + $tagsToGet = ' -ImageWidth -ImageHeight -FileType' // all formats + . ' -BitsPerComponent -ColorSpace' // jp2 + . ' -BitDepth' // png + . ' -BitsPerSample'; // tiff + + $cmd = $this->getUnarchiveCommand($zipPath, $file) + . ' | '. $this->exiftool . ' -S -fast' . $tagsToGet . ' -'; + exec($cmd, $output); + + $tags = Array(); + foreach ($output as $line) { + $keyValue = explode(": ", $line); + $tags[$keyValue[0]] = $keyValue[1]; + } + + $width = intval($tags["ImageWidth"]); + $height = intval($tags["ImageHeight"]); + $type = strtolower($tags["FileType"]); + + switch ($type) { + case "jp2": + $bits = intval($tags["BitsPerComponent"]); + break; + case "tiff": + $bits = intval($tags["BitsPerSample"]); + break; + case "jpeg": + $bits = 8; + break; + case "png": + $bits = intval($tags["BitDepth"]); + break; + default: + $this->BRfatal("Unsupported image type"); + break; + } + + + $retval = Array('width' => $width, 'height' => $height, + 'bits' => $bits, 'type' => $type); + + return $retval; } -} - -// 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 -if (1 == $imageInfo['bits']) { - if ($scale > 1) { - $scale /= 2; - $powReduce -= 1; - - // Hard limit so there are some black pixels to use! - if ($scale > 4) { - $scale = 4; - $powReduce = 2; + + /* + * Output JSON given the imageInfo associative array + */ + function outputJSON($imageInfo, $callback) + { + header('Content-type: text/plain'); + $jsonOutput = json_encode($imageInfo); + if ($callback) { + $jsonOutput = $callback . '(' . $jsonOutput . ');'; } + echo $jsonOutput; } -} - -if (!file_exists($stdoutLink)) -{ - system('ln -s /dev/stdout ' . $stdoutLink); -} - - -putenv('LD_LIBRARY_PATH=/petabox/sw/lib/kakadu'); - -$unzipCmd = getUnarchiveCommand($zipPath, $file); - -switch ($imageInfo['type']) { - case 'jp2': - $decompressCmd = - " | /petabox/sw/bin/kdu_expand -no_seek -quiet -reduce $powReduce -rotate $rotate -i /dev/stdin -o " . $stdoutLink; - if ($decompressToBmp) { - $decompressCmd .= ' | bmptopnm '; + + function getDecompressCmd($imageType, $powReduce, $rotate, $scale, $stdoutLink) { + + switch ($imageType) { + case 'jp2': + $decompressCmd = + " | " . $this->kduExpand . " -no_seek -quiet -reduce $powReduce -rotate $rotate -i /dev/stdin -o " . $stdoutLink; + if ($this->decompressToBmp) { + // We suppress output since bmptopnm always outputs on stderr + $decompressCmd .= ' | (bmptopnm 2>/dev/null)'; + } + break; + + case 'tiff': + // We need to create a temporary file for tifftopnm since it cannot + // work on a pipe (the file must be seekable). + // We use the BookReaderTiff prefix to give a hint in case things don't + // get cleaned up. + $tempFile = tempnam("/tmp", "BookReaderTiff"); + + // $$$ look at bit depth when reducing + $decompressCmd = + ' > ' . $tempFile . ' ; tifftopnm ' . $tempFile . ' 2>/dev/null' . $this->reduceCommand($scale); + break; + + case 'jpeg': + $decompressCmd = ' | ( jpegtopnm 2>/dev/null ) ' . $this->reduceCommand($scale); + break; + + case 'png': + $decompressCmd = ' | ( pngtopnm 2>/dev/null ) ' . $this->reduceCommand($scale); + break; + + default: + $this->BRfatal('Unknown image type: ' . $imageType); + 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). - // We use the BookReaderTiff prefix to give a hint in case things don't - // get cleaned up. - $tempFile = tempnam("/tmp", "BookReaderTiff"); + return $decompressCmd; + } - // $$$ look at bit depth when reducing - $decompressCmd = - ' > ' . $tempFile . ' ; tifftopnm ' . $tempFile . ' 2>/dev/null' . reduceCommand($scale); - break; - - case 'jpeg': - $decompressCmd = ' | jpegtopnm ' . reduceCommand($scale); - break; - - case 'png': - $decompressCmd = ' | pngtopnm ' . reduceCommand($scale); - break; + // 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. + // + // Returns: + // true - if command emits stdout and has zero exit code + // false - command has initial output on stderr or non-zero exit code + // &$errorMessage - error string if there was an error + // + // $$$ Tested with our command-line image processing. May be deadlocks for + // other cases. + function passthruIfSuccessful($headers, $cmd, &$errorMessage) + { + $retVal = false; + $errorMessage = ''; - default: - BRfatal('Unknown source file extension: ' . $fileExt); - break; -} - -// Non-integer scaling is currently disabled on the cluster -// if (isset($_REQUEST['height'])) { -// $cmd .= " | pnmscale -height {$_REQUEST['height']} "; -// } - -switch ($ext) { - case 'png': - $compressCmd = ' | pnmtopng ' . $pngOptions; - break; - - case 'jpeg': - case 'jpg': - default: - $compressCmd = ' | pnmtojpeg ' . $jpegOptions; - $ext = 'jpeg'; // for matching below - break; - -} - -if (($ext == $fileExt) && ($scale == 1) && ($rotate === "0")) { - // Just pass through original data if same format and size - $cmd = $unzipCmd; -} else { - $cmd = $unzipCmd . $decompressCmd . $compressCmd; -} - -# print $cmd; - - -// $$$ investigate how to flush cache when this file is changed -header('Content-type: ' . $MIMES[$ext]); -header('Cache-Control: max-age=15552000'); -passthru ($cmd); # cmd returns image data - -if (isset($tempFile)) { - unlink($tempFile); -} - -function BRFatal($string) { - echo "alert('$string');\n"; - die(-1); -} - -// Returns true if using a power node -function onPowerNode() { - exec("lspci | fgrep -c Realtek", $output, $return); - if ("0" != $output[0]) { - return true; - } else { - exec("egrep -q AMD /proc/cpuinfo", $output, $return); - if ($return == 0) { + $descriptorspec = array( + 0 => array("pipe", "r"), // stdin is a pipe that the child will read from + 1 => array("pipe", "w"), // stdout is a pipe that the child will write to + 2 => array("pipe", "w"), // stderr is a pipe to write to + ); + + $cwd = NULL; + $env = NULL; + + $process = proc_open($cmd, $descriptorspec, $pipes, $cwd, $env); + + if (is_resource($process)) { + // $pipes now looks like this: + // 0 => writeable handle connected to child stdin + // 1 => readable handle connected to child stdout + // 2 => readable handle connected to child stderr + + $stdin = $pipes[0]; + $stdout = $pipes[1]; + $stderr = $pipes[2]; + + // check whether we get input first on stdout or stderr + $read = array($stdout, $stderr); + $write = NULL; + $except = NULL; + $numChanged = stream_select($read, $write, $except, NULL); // $$$ no timeout + if (false === $numChanged) { + // select failed + $errorMessage = 'Select failed'; + $retVal = false; + } + if ($read[0] == $stdout && (1 == $numChanged)) { + // Got output first on stdout (only) + // $$$ make sure we get all stdout + $output = fopen('php://output', 'w'); + foreach($headers as $header) { + header($header); + } + stream_copy_to_stream($pipes[1], $output); + fclose($output); // okay since tied to special php://output + $retVal = true; + } else { + // Got output on stderr + // $$$ make sure we get all stderr + $errorMessage = stream_get_contents($stderr); + $retVal = false; + } + + fclose($stderr); + fclose($stdout); + fclose($stdin); + + + // It is important that you close any pipes before calling + // proc_close in order to avoid a deadlock + $cmdRet = proc_close($process); + if (0 != $cmdRet) { + $retVal = false; + $errorMessage .= "Command failed with result code " . $cmdRet; + } + } + return $retVal; + } + + function BRfatal($string) { + echo "alert('$string');\n"; + die(-1); + } + + // Returns true if using a power node + function onPowerNode() { + exec("lspci | fgrep -c Realtek", $output, $return); + if ("0" != $output[0]) { return true; + } else { + exec("egrep -q AMD /proc/cpuinfo", $output, $return); + if ($return == 0) { + return true; + } } + return false; } - return false; -} - -function reduceCommand($scale) { - if (1 != $scale) { - if (onPowerNode()) { - return ' | pnmscale -reduce ' . $scale; + + function reduceCommand($scale) { + if (1 != $scale) { + if ($this->onPowerNode()) { + return ' | pnmscale -reduce ' . $scale . ' 2>/dev/null '; + } else { + return ' | pnmscale -nomix -reduce ' . $scale . ' 2>/dev/null '; + } } else { - return ' | pnmscale -nomix -reduce ' . $scale; + return ''; + } + } + + function checkPrivs($filename) { + if (!is_readable($filename)) { + header('HTTP/1.1 403 Forbidden'); + exit(0); } - } else { - return ''; } } +$bri = new BookReaderImages(); +$bri->serveRequest($_REQUEST); ?>