2
0
forked from Wavyzz/dolibarr
Files
dolibarr-fork/htdocs/includes/mike42/escpos-php/src/EscposImage.php
Laurent Destailleur 9f20779136 Revert "Update escpos-php"
This reverts commit 2f42a226d0.
2016-05-18 13:18:55 +02:00

406 lines
14 KiB
PHP

<?php
/**
* escpos-php, a Thermal receipt printer library, for use with
* ESC/POS compatible printers.
*
* Copyright (c) 2014-2015 Michael Billington <michael.billington@gmail.com>,
* incorporating modifications by:
* - Roni Saha <roni.cse@gmail.com>
* - Gergely Radics <gerifield@ustream.tv>
* - Warren Doyle <w.doyle@fuelled.co>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
* This class deals with images in raster formats, and converts them into formats
* which are suitable for use on thermal receipt printers. Currently, only PNG
* images (in) and ESC/POS raster format (out) are implemeted.
*
* Input formats:
* - Currently, only PNG is supported.
* - Other easily read raster formats (jpg, gif) will be added at a later date, as this is not complex.
* - The BMP format can be directly read by some commands, but this has not yet been implemented.
*
* Output formats:
* - Currently, only ESC/POS raster format is supported
* - ESC/POS 'column format' support is partially implemented, but is not yet used by Escpos.php library.
* - Output as multiple rows of column format image is not yet in the works.
*
* Libraries:
* - Currently, php-gd is used to read the input. Support for imagemagick where gd is not installed is
* also not complex to add, and is a likely future feature.
* - Support for native use of the BMP format is a goal, for maximum compatibility with target environments.
*/
class EscposImage {
/**
* @var string The image's bitmap data (if it is a Windows BMP).
*/
protected $imgBmpData;
/**
* @var string image data in rows: 1 for black, 0 for white.
*/
protected $imgData;
/**
* @var string cached raster format data to avoid re-computation
*/
protected $imgRasterData;
/**
* @var int height of the image
*/
protected $imgHeight;
/**
* @var int width of the image
*/
protected $imgWidth;
/**
* Load up an image from a filename
*
* @param string $imgPath The path to the image to load, or null to skip
* loading the image (some other functions are available for
* populating the data). Supported graphics types depend on your PHP configuration.
*/
public function __construct($imgPath = null) {
/* Can't use bitmaps yet */
$this -> imgBmpData = null;
$this -> imgRasterData = null;
if($imgPath === null) {
// Blank image
$this -> imgHeight = 0;
$this -> imgWidth = 0;
$this -> imgData = "";
return;
}
/* Load up using GD */
if(!file_exists($imgPath)) {
throw new Exception("File '$imgPath' does not exist.");
}
$ext = pathinfo($imgPath, PATHINFO_EXTENSION);
if($ext == "bmp") {
// The plan is to implement BMP handling directly in
// PHP, as some printers understand this format themselves.
// TODO implement PHP bitmap handling
throw new Exception("Native bitmaps not yet supported. Please convert the file to a supported raster format.");
}
if($this -> isGdSupported()) {
// Prefer to use gd. It is installed by default, so
// most systems will have it, giving a consistent UX.
switch($ext) {
case "png":
$im = @imagecreatefrompng($imgPath);
$this -> readImageFromGdResource($im);
return;
case "jpg":
$im = @imagecreatefromjpeg($imgPath);
$this -> readImageFromGdResource($im);
return;
case "gif":
$im = @imagecreatefromgif($imgPath);
$this -> readImageFromGdResource($im);
return;
}
}
if($this -> isImagickSupported()) {
$im = new Imagick();
try {
// Throws an ImagickException if the format is not supported or file is not found
$im -> readImage($imgPath);
} catch(ImagickException $e) {
// Wrap in normal exception, so that classes which call this do not themselves require imagick as a dependency.
throw new Exception($e);
}
/* Flatten by doing a composite over white, in case of transparency */
$flat = new Imagick();
$flat -> newImage($im -> getimagewidth(), $im -> getimageheight(), "white");
$flat -> compositeimage($im, Imagick::COMPOSITE_OVER, 0, 0);
$this -> readImageFromImagick($flat);
return;
}
throw new Exception("Images are not supported on your PHP. Please install either the gd or imagick extension.");
}
/**
* @return int height of the image in pixels
*/
public function getHeight() {
return $this -> imgHeight;
}
/**
* @return int Number of bytes to represent a row of this image
*/
public function getHeightBytes() {
return (int)(($this -> imgHeight + 7) / 8);
}
/**
* @return int Width of the image
*/
public function getWidth() {
return $this -> imgWidth;
}
/**
* @return int Number of bytes to represent a row of this image
*/
public function getWidthBytes() {
return (int)(($this -> imgWidth + 7) / 8);
}
/**
* @return string binary data of the original file, for function which accept bitmaps.
*/
public function getWindowsBMPData() {
return $this -> imgBmpData;
}
/**
* @return boolean True if the image was a windows bitmap, false otherwise
*/
public function isWindowsBMP() {
return $this -> imgBmpData != null;
}
/**
* Load actual image pixels from GD resource.
*
* @param resouce $im GD resource to use
* @throws Exception Where the image can't be read.
*/
public function readImageFromGdResource($im) {
if(!is_resource($im)) {
throw new Exception("Failed to load image.");
} else if(!$this -> isGdSupported()) {
throw new Exception(__FUNCTION__ . " requires 'gd' extension.");
}
/* Make a string of 1's and 0's */
$this -> imgHeight = imagesy($im);
$this -> imgWidth = imagesx($im);
$this -> imgData = str_repeat("\0", $this -> imgHeight * $this -> imgWidth);
for($y = 0; $y < $this -> imgHeight; $y++) {
for($x = 0; $x < $this -> imgWidth; $x++) {
/* Faster to average channels, blend alpha and negate the image here than via filters (tested!) */
$cols = imagecolorsforindex($im, imagecolorat($im, $x, $y));
$greyness = (int)(($cols['red'] + $cols['green'] + $cols['blue']) / 3) >> 7; // 1 for white, 0 for black
$black = (1 - $greyness) >> ($cols['alpha'] >> 6); // 1 for black, 0 for white, taking into account transparency
$this -> imgData[$y * $this -> imgWidth + $x] = $black;
}
}
}
/**
* Load actual image pixels from Imagick object
*
* @param Imagick $im Image to load from
*/
public function readImageFromImagick(Imagick $im) {
/* Threshold */
$im -> setImageType(Imagick::IMGTYPE_TRUECOLOR); // Remove transparency (good for PDF's)
$max = $im->getQuantumRange();
$max = $max["quantumRangeLong"];
$im -> thresholdImage(0.5 * $max);
/* Make a string of 1's and 0's */
$geometry = $im -> getimagegeometry();
$this -> imgHeight = $im -> getimageheight();
$this -> imgWidth = $im -> getimagewidth();
$this -> imgData = str_repeat("\0", $this -> imgHeight * $this -> imgWidth);
for($y = 0; $y < $this -> imgHeight; $y++) {
for($x = 0; $x < $this -> imgWidth; $x++) {
/* Faster to average channels, blend alpha and negate the image here than via filters (tested!) */
$cols = $im -> getImagePixelColor($x, $y);
$cols = $cols -> getcolor();
$greyness = (int)(($cols['r'] + $cols['g'] + $cols['b']) / 3) >> 7; // 1 for white, 0 for black
$this -> imgData[$y * $this -> imgWidth + $x] = (1 - $greyness); // 1 for black, 0 for white
}
}
}
/**
* Output the image in raster (row) format. This can result in padding on the right of the image, if its width is not divisible by 8.
*
* @throws Exception Where the generated data is unsuitable for the printer (indicates a bug or oversized image).
* @return string The image in raster format.
*/
public function toRasterFormat() {
if($this -> imgRasterData != null) {
/* Use previous calculation */
return $this -> imgRasterData;
}
/* Loop through and convert format */
$widthPixels = $this -> getWidth();
$heightPixels = $this -> getHeight();
$widthBytes = $this -> getWidthBytes();
$heightBytes = $this -> getHeightBytes();
$x = $y = $bit = $byte = $byteVal = 0;
$data = str_repeat("\0", $widthBytes * $heightPixels);
if(strlen($data) == 0) {
return $data;
}
do {
$byteVal |= (int)$this -> imgData[$y * $widthPixels + $x] << (7 - $bit);
$x++;
$bit++;
if($x >= $widthPixels) {
$x = 0;
$y++;
$bit = 8;
if($y >= $heightPixels) {
$data[$byte] = chr($byteVal);
break;
}
}
if($bit >= 8) {
$data[$byte] = chr($byteVal);
$byteVal = 0;
$bit = 0;
$byte++;
}
} while(true);
if(strlen($data) != ($this -> getWidthBytes() * $this -> getHeight())) {
throw new Exception("Bug in " . __FUNCTION__ . ", wrong number of bytes.");
}
$this -> imgRasterData = $data;
return $this -> imgRasterData;
}
/**
* Output image in column format. This format results in padding at the base and right of the image, if its height and width are not divisible by 8.
*/
private function toColumnFormat() {
/* Note: This function is marked private, as it is not yet used/tested and may be buggy. */
$widthPixels = $this -> getWidth();
$heightPixels = $this -> getHeight();
$widthBytes = $this -> getWidthBytes();
$heightBytes = $this -> getHeightBytes();
$x = $y = $bit = $byte = $byteVal = 0;
$data = str_repeat("\0", $widthBytes * $heightBytes * 8);
do {
$byteVal |= (int)$this -> imgData[$y * $widthPixels + $x] << (8 - $bit);
$y++;
$bit++;
if($y >= $heightPixels) {
$y = 0;
$x++;
$bit = 8;
if($x >= $widthPixels) {
$data[$byte] = chr($byteVal);
break;
}
}
if($bit >= 8) {
$data[$byte] = chr($byteVal);
$byteVal = 0;
$bit = 0;
$byte++;
}
} while(true);
if(strlen($data) != ($widthBytes * $heightBytes * 8)) {
throw new Exception("Bug in " . __FUNCTION__ . ", wrong number of bytes. Should be " . ($widthBytes * $heightBytes * 8) . " but was " . strlen($data));
}
return $data;
}
/**
* @return boolean True if GD is supported, false otherwise (a wrapper for the static version, for mocking in tests)
*/
protected function isGdSupported() {
return self::isGdLoaded();
}
/**
* @return boolean True if Imagick is supported, false otherwise (a wrapper for the static version, for mocking in tests)
*/
protected function isImagickSupported() {
return self::isImagickLoaded();
}
/**
* @return boolean True if GD is loaded, false otherwise
*/
public static function isGdLoaded() {
return extension_loaded('gd');
}
/**
* @return boolean True if Imagick is loaded, false otherwise
*/
public static function isImagickLoaded() {
return extension_loaded('imagick');
}
/**
* Load a PDF for use on the printer
*
* @param string $pdfFile The file to load
* @param string $pageWidth The width, in pixels, of the printer's output. The first page of the PDF will be scaled to approximately fit in this area.
* @param array $range array indicating the first and last page (starting from 0) to load. If not set, the entire document is loaded.
* @throws Exception Where Imagick is not loaded, or where a missing file or invalid page number is requested.
* @return multitype:EscposImage Array of images, retrieved from the PDF file.
*/
public static function loadPdf($pdfFile, $pageWidth = 550, array $range = null) {
if(!extension_loaded('imagick')) {
throw new Exception(__FUNCTION__ . " requires imagick extension.");
}
/*
* Load first page at very low density (resolution), to figure out what
* density to use to achieve $pageWidth
*/
try {
$image = new Imagick();
$testRes = 2; // Test resolution
$image -> setresolution($testRes, $testRes);
$image -> readimage($pdfFile."[0]");
$geo = $image -> getimagegeometry();
$image -> destroy();
$width = $geo['width'];
$newRes = $pageWidth / $width * $testRes;
/* Load actual document (can be very slow!) */
$rangeStr = ""; // Set to [0] [0-1] page range if $range is set
if($range != null) {
if(count($range) != 2 || !isset($range[0]) || !is_integer($range[0]) || !isset($range[1]) || !is_integer($range[1]) || $range[0] > $range[1]) {
throw new Exception("Invalid range. Must be two numbers in the array: The start and finish page indexes, starting from 0.");
}
$rangeStr = "[" . ($range[0] == $range[1] ? $range[0] : implode($range, "-")) . "]";
}
$image -> setresolution($newRes, $newRes);
$image -> readImage($pdfFile."$rangeStr");
$pages = $image -> getNumberImages();
/* Convert images to Escpos objects */
$ret = array();
for($i = 0;$i < $pages; $i++) {
$image -> setIteratorIndex($i);
$ep = new EscposImage();
$ep -> readImageFromImagick($image);
$ret[] = $ep;
}
return $ret;
} catch(ImagickException $e) {
// Wrap in normal exception, so that classes which call this do not themselves require imagick as a dependency.
throw new Exception($e);
}
}
}