SeekquarrySeekquarry - Yioop Repo - Seekquarry

/ tests / JpgProcessorTest.php⤓
<?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");
    }
}