From 44fe5aef693b90d5c023d35a833c02932d402c21 Mon Sep 17 00:00:00 2001 From: erikn69 Date: Tue, 7 Oct 2025 09:43:05 -0500 Subject: [PATCH] GD fallback when no Imagick ext --- src/Parser/Command/PrintRasterBitImageCmd.php | 45 +++++++++ src/Parser/Command/SelectBitImageModeCmd.php | 95 ++++++++++++++++++- ...sterFmtDataToPrintBufferGraphicsSubCmd.php | 45 +++++++++ 3 files changed, 181 insertions(+), 4 deletions(-) diff --git a/src/Parser/Command/PrintRasterBitImageCmd.php b/src/Parser/Command/PrintRasterBitImageCmd.php index d2788f4..407fc3a 100644 --- a/src/Parser/Command/PrintRasterBitImageCmd.php +++ b/src/Parser/Command/PrintRasterBitImageCmd.php @@ -69,10 +69,55 @@ public function asPng() { // Just a format conversion PBM -> PNG $pbmBlob = $this -> asPbm(); + if (!class_exists(Imagick::class)) { + return $this->asPngUsingGD($pbmBlob); + } + $im = new Imagick(); $im -> readImageBlob($pbmBlob, 'pbm'); $im->setResourceLimit(6, 1); // Prevent libgomp1 segfaults, grumble grumble. $im -> setFormat('png'); return $im -> getImageBlob(); } + + private function asPngUsingGD($pbmBlob) { + $fp = fopen("php://memory", "r+"); + fwrite($fp, $pbmBlob); + rewind($fp); + + $header = trim(fgets($fp)); + do { + $pos = ftell($fp); + $line = trim(fgets($fp)); + } while ($line !== false && str_starts_with($line, "#")); + fseek($fp, $pos); + + [$width, $height] = array_map("intval", preg_split('/\s+/', trim(fgets($fp)))); + $img = imagecreatetruecolor($width, $height); + imagefilledrectangle($img, 0, 0, $width, $height, imagecolorallocate($img, 255, 255, 255)); + + $black = imagecolorallocate($img, 0, 0, 0); + $rowBytes = (int)ceil($width / 8); + for ($y = 0; $y < $height; $y++) { + $row = fread($fp, $rowBytes); + $bits = unpack("C*", $row); + $x = 0; + foreach ($bits as $byte) { + for ($bit = 7; $bit >= 0; $bit--) { + if ($x >= $width) break; + $val = ($byte >> $bit) & 1; + if ($val === 1) { + imagesetpixel($img, $x, $y, $black); + } + $x++; + } + } + } + + fclose($fp); + ob_start(); + imagepng($img); + imagedestroy($img); + return ob_get_clean(); + } } diff --git a/src/Parser/Command/SelectBitImageModeCmd.php b/src/Parser/Command/SelectBitImageModeCmd.php index 5865029..322bf6b 100644 --- a/src/Parser/Command/SelectBitImageModeCmd.php +++ b/src/Parser/Command/SelectBitImageModeCmd.php @@ -54,7 +54,7 @@ public function getWidth() { return $this -> width; } - + protected function asReflectedPbm() { // Gemerate a PBM image from the source data. If we add a PBM header to the column @@ -62,9 +62,13 @@ protected function asReflectedPbm() // the image reflected diagonally compared with the original. return "P4\n" . $this -> getHeight() . " " . $this -> getWidth() . "\n" . $this -> data; } - + public function asPbm() { + if (!class_exists(Imagick::class)) { + return $this->asPbmUsingGD($this -> asReflectedPbm()); + } + // Reflect image diagonally from internally generated PBM $pbmBlob = $this -> asReflectedPbm(); $im = new Imagick(); @@ -73,15 +77,98 @@ public function asPbm() $im -> flopImage(); return $im -> getImageBlob(); } - + public function asPng() { + if (!class_exists(Imagick::class)) { + return $this->asPngUsingGD($this -> asReflectedPbm()); + } + // Just a format conversion PBM -> PNG $pbmBlob = $this -> asPbm(); $im = new Imagick(); $im -> readImageBlob($pbmBlob, 'pbm'); - $im->setResourceLimit(6, 1); // Prevent libgomp1 segfaults, grumble grumble. + $im->setResourceLimit(6, 1); // Prevent libgomp1 segfaults, grumble grumble. $im -> setFormat('png'); return $im -> getImageBlob(); } + + private function asPbmUsingGD($pbmBlob) + { + $image = $this->createImageFromPBMUsingGd($pbmBlob); + if (!$image) { + throw new \Exception("No se pudo crear la imagen GD desde PBM"); + } + + $image = imagerotate($image, -90, 0); + $width = imagesx($image); + $height = imagesy($image); + $flipped = imagecreatetruecolor($width, $height); + imagecopyresampled($flipped, $image, 0, 0, $width - 1, 0, $width, $height, -$width, $height); + imagedestroy($image); + + ob_start(); + imagegd2($flipped); + $pngData = ob_get_clean(); + imagedestroy($flipped); + + return $pngData; + } + + private function asPngUsingGD($pbmBlob) + { + $image = $this->createImageFromPBMUsingGd($pbmBlob); + if (!$image) { + throw new \Exception("No se pudo crear la imagen GD desde PBM"); + } + + $image = imagerotate($image, -90, 0); + $width = imagesx($image); + $height = imagesy($image); + $flipped = imagecreatetruecolor($width, $height); + imagecopyresampled($flipped, $image, 0, 0, $width - 1, 0, $width, $height, -$width, $height); + imagedestroy($image); + + ob_start(); + imagepng($flipped); + $pngData = ob_get_clean(); + imagedestroy($flipped); + + return $pngData; + } + + private function createImageFromPBMUsingGd(string $pbmBlob) + { + $lines = preg_split('/\s+/', trim($pbmBlob)); + if (count($lines) < 3 || $lines[0] !== 'P4') { + return null; + } + + $width = (int) $lines[1]; + $height = (int) $lines[2]; + $headerEnd = strpos($pbmBlob, "\n", strpos($pbmBlob, $height) + strlen($height)) + 1; + $bitmapData = substr($pbmBlob, $headerEnd); + + $img = imagecreatetruecolor($width, $height); + $white = imagecolorallocate($img, 255, 255, 255); + $black = imagecolorallocate($img, 0, 0, 0); + imagefill($img, 0, 0, $white); + + $rowBytes = (int) ceil($width / 8); + $offset = 0; + + for ($y = 0; $y < $height; $y++) { + $row = substr($bitmapData, $offset, $rowBytes); + $offset += $rowBytes; + + for ($x = 0; $x < $width; $x++) { + $byteIndex = intdiv($x, 8); + $bitIndex = 7 - ($x % 8); + $bit = (ord($row[$byteIndex]) >> $bitIndex) & 1; + imagesetpixel($img, $x, $y, $bit ? $black : $white); + } + } + + return $img; + } } diff --git a/src/Parser/Command/StoreRasterFmtDataToPrintBufferGraphicsSubCmd.php b/src/Parser/Command/StoreRasterFmtDataToPrintBufferGraphicsSubCmd.php index 6c6e378..b3e2092 100644 --- a/src/Parser/Command/StoreRasterFmtDataToPrintBufferGraphicsSubCmd.php +++ b/src/Parser/Command/StoreRasterFmtDataToPrintBufferGraphicsSubCmd.php @@ -74,10 +74,55 @@ public function asPbm() public function asPng() { $pbmBlob = $this -> asPbm(); + if (!class_exists(Imagick::class)) { + return $this->asPngUsingGD($pbmBlob); + } + $im = new Imagick(); $im -> readImageBlob($pbmBlob, 'pbm'); $im->setResourceLimit(6, 1); // Prevent libgomp1 segfaults, grumble grumble. $im -> setFormat('png'); return $im -> getImageBlob(); } + + private function asPngUsingGD($pbmBlob) { + $fp = fopen("php://memory", "r+"); + fwrite($fp, $pbmBlob); + rewind($fp); + + $header = trim(fgets($fp)); + do { + $pos = ftell($fp); + $line = trim(fgets($fp)); + } while ($line !== false && str_starts_with($line, "#")); + fseek($fp, $pos); + + [$width, $height] = array_map("intval", preg_split('/\s+/', trim(fgets($fp)))); + $img = imagecreatetruecolor($width, $height); + imagefilledrectangle($img, 0, 0, $width, $height, imagecolorallocate($img, 255, 255, 255)); + + $black = imagecolorallocate($img, 0, 0, 0); + $rowBytes = (int)ceil($width / 8); + for ($y = 0; $y < $height; $y++) { + $row = fread($fp, $rowBytes); + $bits = unpack("C*", $row); + $x = 0; + foreach ($bits as $byte) { + for ($bit = 7; $bit >= 0; $bit--) { + if ($x >= $width) break; + $val = ($byte >> $bit) & 1; + if ($val === 1) { + imagesetpixel($img, $x, $y, $black); + } + $x++; + } + } + } + + fclose($fp); + ob_start(); + imagepng($img); + imagedestroy($img); + return ob_get_clean(); + } }