<?php
/**
* SeekQuarry/Yioop --
* Open Source Pure PHP Search Engine, Crawler, and Indexer
*
* Copyright (C) 2009 - 2026 Chris Pollett chris@pollett.org
*
* LICENSE:
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* END LICENSE
*
* @author Chris Pollett chris@pollett.org
* @license https://www.gnu.org/licenses/ GPL3
* @link https://www.seekquarry.com/
* @copyright 2009 - 2026
* @filesource
*/
namespace seekquarry\yioop\tests;
use seekquarry\yioop\library\UnitTest;
use seekquarry\yioop\library\processors\JpgProcessor;
/**
* Tests the reading of a picture written in the four inks a press uses.
* The drawing library reads such a picture without complaint and drops
* its black ink, which leaves a magazine cover pale and low in contrast,
* so those are read here instead. This checks how such a picture says
* which way its planes are stored and that the arithmetic that turns
* them into light holds at its ends.
*
* @author Chris Pollett
*/
class JpgProcessorTest extends UnitTest
{
/**
* setUp does nothing, since every case here works on values it
* makes for itself rather than on a file.
*/
public function setUp()
{
}
/**
* tearDown does nothing, since no case here writes a file.
*/
public function tearDown()
{
}
/**
* pictureSaysHowItsPlanesAreStoredTestCase checks the note a
* printing program leaves in a picture, which says whether its first
* three planes hold the first three inks or hold one of brightness
* and two of color. Read as inks, a khaki figure came out green.
*/
public function pictureSaysHowItsPlanesAreStoredTestCase()
{
$note = "\xFF\xD8\xFF\xEE\x00\x0EAdobe\x00\x64\x00\x00\x00\x00" .
"\x02";
$this->assertTrue(JpgProcessor::storedAsBrightnessAndColor(
$note), "a note carrying a two says brightness and color");
$plain = str_replace("\x02", "\x00", $note);
$this->assertTrue(!JpgProcessor::storedAsBrightnessAndColor(
$plain), "a note carrying a zero says the inks outright");
$this->assertTrue(!JpgProcessor::storedAsBrightnessAndColor(
"\xFF\xD8\xFF\xE0nothing of the kind"),
"a picture with no such note says the inks outright");
}
/**
* brightnessAndColorBecomeInksTestCase checks that the first three
* planes of a picture stored the way a photograph is stored are
* turned into the first three inks. An ink is the light it takes
* away, so a plane holding full brightness and no color comes out as
* no ink at all.
*/
public function brightnessAndColorBecomeInksTestCase()
{
/* white: full brightness, no color either way */
$planes = [[255], [128], [128], [0]];
JpgProcessor::inksFromBrightnessAndColor($planes);
$this->assertEqual(0, $planes[0][0],
"white lays down none of the first ink");
$this->assertEqual(0, $planes[1][0],
"and none of the second");
$this->assertEqual(0, $planes[2][0],
"and none of the third");
/* black: no brightness, no color either way */
$planes = [[0], [128], [128], [0]];
JpgProcessor::inksFromBrightnessAndColor($planes);
$this->assertEqual(255, $planes[0][0],
"no light at all lays down the first ink at full");
}
/**
* valueIsHeldWithinWhatAPlaneCanHoldTestCase checks that the
* arithmetic turning brightness and color into light cannot leave a
* value outside what a plane holds, since it lands a little outside
* at the ends of the range.
*/
public function valueIsHeldWithinWhatAPlaneCanHoldTestCase()
{
$this->assertEqual(0, JpgProcessor::heldInRange(-40),
"a value below the bottom is held at the bottom");
$this->assertEqual(255, JpgProcessor::heldInRange(400),
"a value above the top is held at the top");
$this->assertEqual(128, JpgProcessor::heldInRange(127.6),
"a value between them is rounded to the nearest");
}
/**
* ordinaryPictureIsLeftToTheDrawingLibraryTestCase checks that a
* picture in the three lights a screen uses is handed back by the
* drawing library rather than read here, since only a picture in the
* four inks needs the reading done here.
*/
public function ordinaryPictureIsLeftToTheDrawingLibraryTestCase()
{
$this->assertTrue(function_exists("imagecreatetruecolor"),
"this case needs the GD drawing library loaded in PHP");
if (!function_exists("imagecreatetruecolor")) {
return;
}
$picture = imagecreatetruecolor(8, 8);
imagefill($picture, 0, 0,
imagecolorallocate($picture, 200, 100, 50));
ob_start();
imagejpeg($picture);
$bytes = ob_get_clean();
$read = JpgProcessor::toImage($bytes);
$this->assertTrue($read !== false, "an ordinary picture is read");
$this->assertEqual(8, imagesx($read), "at the width it was made");
$this->assertTrue(!JpgProcessor::storedAsBrightnessAndColor(
$bytes), "and it carries no note about brightness and color");
}
/**
* Checks a thumbnail is never larger than the picture it stands
* for. A 240 by 60 picture was scaled up to a 414 by 256 thumbnail,
* which cost seven times the work of one its own size, and a
* synthetic crawl spent most of a fetcher's time on that.
*/
public function thumbIsNoLargerThanItsPictureTestCase()
{
$picture = imagecreatetruecolor(240, 60);
$ink = imagecolorallocate($picture, 20, 40, 60);
imagefilledrectangle($picture, 0, 0, 120, 60, $ink);
$thumb = JpgProcessor::createThumb($picture, 414, 256);
$this->assertTrue($thumb !== "", "a thumbnail is made");
$made = imagecreatefromstring($thumb);
$this->assertTrue(imagesx($made) <= 240,
"the thumbnail is no wider than the picture");
$this->assertTrue(imagesy($made) <= 60,
"the thumbnail is no taller than the picture");
}
/**
* Checks a thumbnail keeps the picture's proportions and fills what
* is left with the picture's average color. A four-to-one picture
* used to be stretched into the golden-ratio thumb.
*/
public function thumbKeepsProportionsOnAverageColorTestCase()
{
$picture = imagecreatetruecolor(800, 200);
$red = imagecolorallocate($picture, 200, 0, 0);
$blue = imagecolorallocate($picture, 0, 0, 200);
imagefilledrectangle($picture, 0, 0, 399, 199, $red);
imagefilledrectangle($picture, 400, 0, 799, 199, $blue);
$thumb = JpgProcessor::createThumb($picture, 320, 200);
$made = imagecreatefromstring($thumb);
$this->assertEqual(320, imagesx($made), "the thumb is the width asked");
$this->assertEqual(200, imagesy($made), "and the height asked");
/* a four-to-one picture fitted into 320 wide is 80 tall, so the
rows well above and below it show the fill */
$corner = imagecolorsforindex($made, imagecolorat($made, 5, 5));
$this->assertTrue(abs($corner["red"] - 100) < 30 &&
abs($corner["blue"] - 100) < 30 && $corner["green"] < 30,
"the border is the average of the red and blue halves");
$middle_left = imagecolorsforindex($made,
imagecolorat($made, 40, 100));
$middle_right = imagecolorsforindex($made,
imagecolorat($made, 280, 100));
$this->assertTrue($middle_left["red"] > 150 &&
$middle_left["blue"] < 50,
"the left of the picture is still red");
$this->assertTrue($middle_right["blue"] > 150 &&
$middle_right["red"] < 50,
"the right of the picture is still blue");
}
/**
* jpeg2000PictureIsReadExactlyTestCase checks a JPEG 2000 (a later
* picture format a PDF may hold) picture comes back with each point
* exact when it was stored without loss.
* The cover photo of a Rolling Stone PDF was stored this way and its
* thumbnail showed nothing where the photo was.
*/
public function jpeg2000PictureIsReadExactlyTestCase()
{
$bytes = base64_decode("/0//UQAvAAAAAAAQAAAACAAAAAAAAAAAAAAAEAAA" .
"AAgAAAAAAAAAAAADBwEBBwEBBwEB/1IADAAAAAEAAwQEAAH/XAANQEBISFBISF" .
"BISFD/ZAAlAAFDcmVhdGVkIGJ5IE9wZW5KUEVHIHZlcnNpb24gMi41LjT/kAAK" .
"AAAAAABKAAH/k9+AIAcT/3/H1AQIc4DH2gQAC5Gn4AYCZVOAw+oDABWGiaPtBQ" .
"ALQW+sB4DB84QANp3/f6H1AgADhKkPgP/Z");
$read = JpgProcessor::fromJpeg2000($bytes);
$this->assertTrue($read !== false, "the picture is read");
if ($read === false) {
return;
}
$this->assertEqual(16, imagesx($read), "at the width it was made");
$this->assertEqual(8, imagesy($read), "and the height");
$wrong = 0;
for ($across = 0; $across < 16; $across++) {
for ($down = 0; $down < 8; $down++) {
$color = imagecolorsforindex($read,
imagecolorat($read, $across, $down));
if ($color["red"] != $across * 16 ||
$color["green"] != $down * 32 || $color["blue"] != 128) {
$wrong++;
}
}
}
$this->assertEqual(0, $wrong, "every point has the color it had");
}
}