* @copyright 2011-2026 Nicola Asuni - Tecnick.com LTD * @license https://www.gnu.org/copyleft/lesser.html GNU-LGPL v3 (see LICENSE) * @link https://github.com/tecnickcom/tc-lib-pdf-image * * This file is part of tc-lib-pdf-image software library. */ namespace Test; /** * Jpeg class test * * @since 2026-04-19 * @category Library * @package PdfImage * @author Nicola Asuni * @copyright 2011-2026 Nicola Asuni - Tecnick.com LTD * @license https://www.gnu.org/copyleft/lesser.html GNU-LGPL v3 (see LICENSE) * @link https://github.com/tecnickcom/tc-lib-pdf-image * * @phpstan-import-type ImageBaseData from \Com\Tecnick\Pdf\Image\Import */ class JpegTest extends TestUtil { /** * @throws \RangeException */ public function testGetDataRgbJpeg(): void { $import = new \Com\Tecnick\Pdf\Image\Import\Jpeg(); $file = \file_get_contents(__DIR__ . '/images/200x100_RGB.jpg'); $this->assertIsString($file); $data = [ 'bits' => 8, 'channels' => 3, 'colspace' => 'DeviceRGB', 'data' => '', 'exturl' => false, 'file' => __DIR__ . '/images/200x100_RGB.jpg', 'filter' => '', 'height' => 100, 'icc' => '', 'ismask' => false, 'key' => 'test', 'mapto' => IMAGETYPE_JPEG, 'native' => true, 'obj' => 0, 'obj_alt' => 0, 'obj_icc' => 0, 'obj_pal' => 0, 'pal' => '', 'parms' => '', 'raw' => $file, 'recode' => false, 'recoded' => false, 'splitalpha' => false, 'trns' => [], 'type' => IMAGETYPE_JPEG, 'width' => 200, ]; $result = $import->getData($data); $this->assertEquals('DCTDecode', $result['filter']); $this->assertNotEmpty($result['data']); $this->assertEquals($file, $result['data']); } /** * @throws \RangeException */ public function testGetDataGrayJpeg(): void { $import = new \Com\Tecnick\Pdf\Image\Import\Jpeg(); $file = \file_get_contents(__DIR__ . '/images/200x100_GRAY.jpg'); $this->assertIsString($file); $data = [ 'bits' => 8, 'channels' => 1, 'colspace' => 'DeviceGray', 'data' => '', 'exturl' => false, 'file' => __DIR__ . '/images/200x100_GRAY.jpg', 'filter' => '', 'height' => 100, 'icc' => '', 'ismask' => false, 'key' => 'test', 'mapto' => IMAGETYPE_JPEG, 'native' => true, 'obj' => 0, 'obj_alt' => 0, 'obj_icc' => 0, 'obj_pal' => 0, 'pal' => '', 'parms' => '', 'raw' => $file, 'recode' => false, 'recoded' => false, 'splitalpha' => false, 'trns' => [], 'type' => IMAGETYPE_JPEG, 'width' => 200, ]; $result = $import->getData($data); $this->assertEquals('DCTDecode', $result['filter']); $this->assertEquals(1, $result['channels']); } /** * @throws \RangeException */ public function testGetDataCmykJpeg(): void { $import = new \Com\Tecnick\Pdf\Image\Import\Jpeg(); $file = \file_get_contents(__DIR__ . '/images/200x100_CMYK.jpg'); $this->assertIsString($file); $data = [ 'bits' => 8, 'channels' => 4, 'colspace' => 'DeviceCMYK', 'data' => '', 'exturl' => false, 'file' => __DIR__ . '/images/200x100_CMYK.jpg', 'filter' => '', 'height' => 100, 'icc' => '', 'ismask' => false, 'key' => 'test', 'mapto' => IMAGETYPE_JPEG, 'native' => true, 'obj' => 0, 'obj_alt' => 0, 'obj_icc' => 0, 'obj_pal' => 0, 'pal' => '', 'parms' => '', 'raw' => $file, 'recode' => false, 'recoded' => false, 'splitalpha' => false, 'trns' => [], 'type' => IMAGETYPE_JPEG, 'width' => 200, ]; $result = $import->getData($data); $this->assertEquals('DCTDecode', $result['filter']); $this->assertEquals(4, $result['channels']); $this->assertEquals('DeviceCMYK', $result['colspace']); } /** * @throws \RangeException */ public function testGetDataJpegWithIcc(): void { $import = new \Com\Tecnick\Pdf\Image\Import\Jpeg(); $file = \file_get_contents(__DIR__ . '/images/200x100_RGBICC.jpg'); $this->assertIsString($file); $data = [ 'bits' => 8, 'channels' => 3, 'colspace' => 'DeviceRGB', 'data' => '', 'exturl' => false, 'file' => __DIR__ . '/images/200x100_RGBICC.jpg', 'filter' => '', 'height' => 100, 'icc' => '', 'ismask' => false, 'key' => 'test', 'mapto' => IMAGETYPE_JPEG, 'native' => true, 'obj' => 0, 'obj_alt' => 0, 'obj_icc' => 0, 'obj_pal' => 0, 'pal' => '', 'parms' => '', 'raw' => $file, 'recode' => false, 'recoded' => false, 'splitalpha' => false, 'trns' => [], 'type' => IMAGETYPE_JPEG, 'width' => 200, ]; $result = $import->getData($data); $this->assertEquals('DCTDecode', $result['filter']); $this->assertNotEmpty($result['icc']); } /** * @throws \RangeException */ public function testGetDataJpegWithoutIcc(): void { $import = new \Com\Tecnick\Pdf\Image\Import\Jpeg(); $file = \file_get_contents(__DIR__ . '/images/200x100_RGB.jpg'); $this->assertIsString($file); $data = [ 'bits' => 8, 'channels' => 3, 'colspace' => 'DeviceRGB', 'data' => '', 'exturl' => false, 'file' => __DIR__ . '/images/200x100_RGB.jpg', 'filter' => '', 'height' => 100, 'icc' => '', 'ismask' => false, 'key' => 'test', 'mapto' => IMAGETYPE_JPEG, 'native' => true, 'obj' => 0, 'obj_alt' => 0, 'obj_icc' => 0, 'obj_pal' => 0, 'pal' => '', 'parms' => '', 'raw' => $file, 'recode' => false, 'recoded' => false, 'splitalpha' => false, 'trns' => [], 'type' => IMAGETYPE_JPEG, 'width' => 200, ]; $result = $import->getData($data); $this->assertEquals('DCTDecode', $result['filter']); $this->assertEmpty($result['icc']); } /** * @throws \RangeException */ public function testGetDataPreservesRawData(): void { $import = new \Com\Tecnick\Pdf\Image\Import\Jpeg(); $file = \file_get_contents(__DIR__ . '/images/200x100_RGB.jpg'); $this->assertIsString($file); $data = [ 'bits' => 8, 'channels' => 3, 'colspace' => 'DeviceRGB', 'data' => '', 'exturl' => false, 'file' => __DIR__ . '/images/200x100_RGB.jpg', 'filter' => '', 'height' => 100, 'icc' => '', 'ismask' => false, 'key' => 'test', 'mapto' => IMAGETYPE_JPEG, 'native' => true, 'obj' => 0, 'obj_alt' => 0, 'obj_icc' => 0, 'obj_pal' => 0, 'pal' => '', 'parms' => '', 'raw' => $file, 'recode' => false, 'recoded' => false, 'splitalpha' => false, 'trns' => [], 'type' => IMAGETYPE_JPEG, 'width' => 200, ]; $result = $import->getData($data); $this->assertEquals($file, $result['raw']); $this->assertEquals($file, $result['data']); } /** * @throws \RangeException */ public function testGetDataWithMissingIcc(): void { $import = new \Com\Tecnick\Pdf\Image\Import\Jpeg(); $file = \file_get_contents(__DIR__ . '/images/200x100_RGB.jpg'); $this->assertIsString($file); $data = [ 'bits' => 8, 'channels' => 3, 'colspace' => 'DeviceRGB', 'data' => '', 'exturl' => false, 'file' => __DIR__ . '/images/200x100_RGB.jpg', 'filter' => '', 'height' => 100, 'icc' => '', 'ismask' => false, 'key' => 'test', 'mapto' => IMAGETYPE_JPEG, 'native' => true, 'obj' => 0, 'obj_alt' => 0, 'obj_icc' => 0, 'obj_pal' => 0, 'pal' => '', 'parms' => '', 'raw' => $file, 'recode' => false, 'recoded' => false, 'splitalpha' => false, 'trns' => [], 'type' => IMAGETYPE_JPEG, 'width' => 200, ]; $result = $import->getData($data); $this->assertEquals('DCTDecode', $result['filter']); $this->assertEquals('DeviceRGB', $result['colspace']); } /** * @throws \RangeException */ public function testGetDataPreservesChannels(): void { $import = new \Com\Tecnick\Pdf\Image\Import\Jpeg(); $file = \file_get_contents(__DIR__ . '/images/200x100_GRAY.jpg'); $this->assertIsString($file); $data = [ 'bits' => 8, 'channels' => 1, 'colspace' => 'DeviceGray', 'data' => '', 'exturl' => false, 'file' => __DIR__ . '/images/200x100_GRAY.jpg', 'filter' => '', 'height' => 100, 'icc' => '', 'ismask' => false, 'key' => 'test', 'mapto' => IMAGETYPE_JPEG, 'native' => true, 'obj' => 0, 'obj_alt' => 0, 'obj_icc' => 0, 'obj_pal' => 0, 'pal' => '', 'parms' => '', 'raw' => $file, 'recode' => false, 'recoded' => false, 'splitalpha' => false, 'trns' => [], 'type' => IMAGETYPE_JPEG, 'width' => 200, ]; $result = $import->getData($data); $this->assertEquals(1, $result['channels']); $this->assertEquals($file, $result['data']); } /** * A crafted "ICC_PROFILE" marker preceded by a 2-byte length of 0 yields a * negative segment length. The scanner must still move forward instead of * re-finding the same marker forever (DoS regression). * * @throws \RangeException */ public function testGetDataDoesNotHangOnMalformedIccMarker(): void { $import = new \Com\Tecnick\Pdf\Image\Import\Jpeg(); $raw = 'XX' . "\x00\x00" . 'ICC_PROFILE' . "\x00\x01\x01" . 'PAYLOAD'; $data = $this->makeJpegData($raw); $result = $import->getData($data); $this->assertSame('DCTDecode', $result['filter']); $this->assertSame('', $result['icc']); $this->assertSame($raw, $result['data']); } /** * A marker at the very start of the stream has too few bytes before it to * carry a length: it must be skipped without throwing or looping. * * @throws \RangeException */ public function testGetDataIgnoresIccMarkerWithoutLengthPrefix(): void { $import = new \Com\Tecnick\Pdf\Image\Import\Jpeg(); $raw = 'ICC_PROFILE' . "\x00" . 'rest'; $data = $this->makeJpegData($raw); $result = $import->getData($data); $this->assertSame('', $result['icc']); } /** * Build a minimal JPEG base-data array wrapping the given raw bytes. * * @return ImageBaseData */ private function makeJpegData(string $raw): array { return [ 'bits' => 8, 'channels' => 3, 'colspace' => 'DeviceRGB', 'data' => '', 'exturl' => false, 'file' => '', 'filter' => '', 'height' => 100, 'icc' => '', 'ismask' => false, 'key' => 'test', 'mapto' => IMAGETYPE_JPEG, 'native' => true, 'obj' => 0, 'obj_alt' => 0, 'obj_icc' => 0, 'obj_pal' => 0, 'pal' => '', 'parms' => '', 'raw' => $raw, 'recode' => false, 'recoded' => false, 'splitalpha' => false, 'trns' => [], 'type' => IMAGETYPE_JPEG, 'width' => 200, ]; } }