[
  {
    "path": ".gitignore",
    "content": ".idea\ncomposer.lock\nvendor\ntemp\nindex.php\nbuild\n.env\n.phpunit.result.cache"
  },
  {
    "path": ".travis.yml",
    "content": "dist: xenial\nlanguage: php\n\nphp:\n  - 7.1\n  - 7.2\n  - 7.3\n  - 7.4\n  - 8.0\n  \n# This triggers builds to run on the new TravisCI infrastructure.\n# See: http://docs.travis-ci.com/user/workers/container-based-infrastructure/\nsudo: false\n\n## Cache composer\ncache:\n  directories:\n    - $HOME/.composer/cache\n\nbefore_script:\n  - travis_retry composer update ${COMPOSER_FLAGS} --no-interaction --prefer-dist\n\nscript:\n  - echo \"xdebug.mode=coverage\" >> ~/.phpenv/versions/$(phpenv version-name)/etc/php.ini\n  - vendor/bin/phpcs --standard=psr2 src/\n  - vendor/bin/phpunit --coverage-text --coverage-clover=coverage.clover\n\nafter_script:\n  - |\n    if [[ \"$TRAVIS_PHP_VERSION\" != 'hhvm' && \"$TRAVIS_PHP_VERSION\" != '7.0' ]]; then\n      wget https://scrutinizer-ci.com/ocular.phar\n      php ocular.phar code-coverage:upload --format=php-clover coverage.clover\n    fi\n"
  },
  {
    "path": "README.md",
    "content": "# PHP Legofy\n[![Build Status](https://api.travis-ci.org/ricardofiorani/php-legofy.svg?branch=master)](http://travis-ci.org/ricardofiorani/php-legofy)\n[![Minimum PHP Version](https://img.shields.io/packagist/php-v/ricardofiorani/php-legofy.svg)](https://php.net/)\n[![License](https://poser.pugx.org/ricardofiorani/php-legofy/license.png)](https://packagist.org/packages/ricardofiorani/php-legofy)\n[![Total Downloads](https://poser.pugx.org/ricardofiorani/php-legofy/d/total.png)](https://packagist.org/packages/ricardofiorani/php-legofy)\n[![Coding Standards](https://img.shields.io/badge/cs-PSR--4-yellow.svg)](https://github.com/php-fig-rectified/fig-rectified-standards)\n\n### What is this ?\nPHP Legofy is a PHP package that takes a static image and makes it so that it looks as if it was built out of LEGO.  \nIt was inspired by the original Legofy made in Python https://github.com/JuanPotato/Legofy\n\nBasically it transforms this:\n![Image of a beer][beer]\nInto this:\n![Image of a legofied beer][lego-beer]\n\n[beer]: ./assets/examples/beer.jpg\n[lego-beer]: ./assets/examples/lego-beer.jpeg\n\n### Requirements\n* PHP ^7.1 || ^8.0\n* GD or ImageMagick\n> I tested it with GD only but I'm trusting intervention/image that this will work on ImageMagick as well.\n\n### Quickstart\nVia composer:\n```bash\n$ composer require ricardofiorani/php-legofy\n```\n\nVia source:\n```bash\n$ git clone git@github.com:ricardofiorani/php-legofy.git\n$ cd php-legofy\n$ composer install\n```\n\n### Usage:\n```php\n<?php\nrequire 'vendor/autoload.php';\n\n// The multiplier for the amount of legos on your image, or \"legolution\" :)\n$resolutionMultiplier = 1;\n\n// When set to true it will only use lego colors that exists in real world.\n$useLegoPalette = false;\n\n$legofy = new \\RicardoFiorani\\Legofy\\Legofy();\n\n// $source can be any acceptable parameter for intervention/image\n// Please see http://image.intervention.io/api/make\n$source = 'my/imagem/path/image.jpg';\n\n/**\n *@var Intervention\\Image\\Image \n */\n$output = $legofy->convertToLego($source, $resolutionMultiplier, $useLegoPalette);\n\n// Please see http://image.intervention.io/use/basics and http://image.intervention.io/use/http\necho $output->response();\n```\n\nFor more examples of usage please see [the usage examples on documentation](https://github.com/ricardofiorani/php-legofy/blob/master/docs/EXAMPLES.md)"
  },
  {
    "path": "composer.json",
    "content": "{\n    \"name\": \"ricardofiorani/php-legofy\",\n    \"description\": \"A PHP Port of Legofy\",\n    \"keywords\": [\"lego\", \"legofy\", \"gd\", \"imagick\", \"image\", \"effect\"],\n    \"require\": {\n        \"php\": \"^7.1 || ^8.0\",\n        \"intervention/image\": \"^2.4\"\n    },\n    \"require-dev\": {\n        \"phpunit/phpunit\": \"^7.2 || ^9.0\",\n        \"spryker/code-sniffer\": \"^0.15.1\"\n    },\n    \"autoload\": {\n        \"psr-4\": {\n            \"RicardoFiorani\\\\Legofy\\\\\": \"src\"\n        }\n    },\n    \"autoload-dev\": {\n        \"psr-4\": {\n            \"Tests\\\\RicardoFiorani\\\\\": \"tests\"\n        }\n    },\n    \"license\": \"MIT\",\n    \"authors\": [\n        {\n            \"name\": \"Ricardo Fiorani\",\n            \"email\": \"ricardo.fiorani@gmail.com\"\n        }\n    ],\n    \"scripts\": {\n        \"test\": \"phpunit\"\n    },\n    \"suggest\": {\n        \"ext-gd\": \"to use GD library based image processing.\",\n        \"ext-imagick\": \"to use Imagick based image processing.\"\n    }\n}\n"
  },
  {
    "path": "docs/EXAMPLES.md",
    "content": "# Examples\n\n### Input Image:\n![Image of a beer][beer]\n\n### Example 1 - Default:\n```php\n<?php\nrequire 'vendor/autoload.php';\n\n// The multiplier for the amount of legos on your image, or \"legolution\" :)\n$resolutionMultiplier = 1;\n\n// When set to true it will only use lego colors that exists in real world.\n$useLegoPalette = false;\n\n$legofy = new \\RicardoFiorani\\Legofy\\Legofy();\n\n// $source can be any acceptable parameter for intervention/image\n// Please see http://image.intervention.io/api/make\n$source = 'my/imagem/path/image.jpg';\n\n/**\n *@var Intervention\\Image\\Image \n */\n$output = $legofy->convertToLego($source, $resolutionMultiplier, $useLegoPalette);\n\n// Please see http://image.intervention.io/use/basics and http://image.intervention.io/use/http\necho $output->response();\n```\nOutput:\n![Image of a legofied beer][lego-beer]\n\n### Example 2 - Resolution multiplier\n> Please be advised that using higher multiplers will consume more memory and CPU usage on the legofying process !  \n\nThe resolution multiplier multiplies the resolution of the original source image. The default value is 1.\nPlease note that this value is a float, so you can set it as 1.5 for example:\n![Image of a legofied beer with more res][1-5-lego-beer]\nYou can also set it as an 0.5 value for example:\n![Image of a legofied beer with less res][0-5-lego-beer]\n\n### Example 3 - Using the original lego color palette\nOn this package I also added the feature to use the [original LEGO color palette](https://github.com/JuanPotato/Legofy/blob/master/2010-LEGO-color-palette.pdf). (The same that [https://github.com/JuanPotato/Legofy](https://github.com/JuanPotato/Legofy) uses.)\n```php\n<?php\n\n$legofy->convertToLego($source, $resolutionMultiplier, true);\n\n```\nOutput:\n![Image of a legofied beer][real-color-lego-beer]\n\n> Please note that this implementation of color distance uses the [Euclidean method](https://en.wikipedia.org/wiki/Color_difference#Euclidean) so it might not be 100% precise for scientific purposes.\n\n\n[beer]: ../assets/examples/beer.jpg\n[lego-beer]: ../assets/examples/lego-beer.jpeg\n[1-5-lego-beer]: ../assets/examples/res-1-5-lego-beer.jpeg\n[0-5-lego-beer]: ../assets/examples/res-0-5-lego-beer.jpeg\n[real-color-lego-beer]: ../assets/examples/real-color-lego-beer.jpeg"
  },
  {
    "path": "phpunit.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<phpunit bootstrap=\"vendor/autoload.php\"\n         backupGlobals=\"false\"\n         backupStaticAttributes=\"false\"\n         colors=\"true\"\n         verbose=\"true\"\n         convertErrorsToExceptions=\"true\"\n         convertNoticesToExceptions=\"true\"\n         convertWarningsToExceptions=\"true\"\n         processIsolation=\"false\"\n         stopOnFailure=\"false\">\n    <testsuites>\n        <testsuite name=\"PHP Legofy Integration Test Suite\">\n            <directory>tests/Integration</directory>\n        </testsuite>\n        <testsuite name=\"PHP Legofy Unit Test Suite\">\n            <directory>tests/Unit</directory>\n        </testsuite>\n    </testsuites>\n    <filter>\n        <whitelist>\n            <directory suffix=\".php\">src/</directory>\n        </whitelist>\n    </filter>\n    <logging>\n        <log type=\"coverage-html\" target=\"build/coverage\"/>\n        <log type=\"coverage-text\" target=\"build/coverage.txt\"/>\n        <log type=\"coverage-clover\" target=\"build/logs/clover.xml\"/>\n    </logging>\n</phpunit>"
  },
  {
    "path": "src/Legofy.php",
    "content": "<?php declare(strict_types=1);\n\nnamespace RicardoFiorani\\Legofy;\n\nuse Intervention\\Image\\AbstractColor;\nuse Intervention\\Image\\Image;\nuse Intervention\\Image\\ImageManagerStatic;\nuse RicardoFiorani\\Legofy\\Pallete\\ColorPalette;\nuse RicardoFiorani\\Legofy\\Pallete\\LegoPaletteInterface;\n\nclass Legofy\n{\n    /**\n     * @var Image\n     */\n    private $brick;\n\n    /**\n     * @var AbstractColor\n     */\n    private $brickAverageColor;\n\n    /**\n     * @var int\n     */\n    private $brickWidth;\n\n    /**\n     * @var int\n     */\n    private $brickHeight;\n\n    /**\n     * @var LegoPaletteInterface\n     */\n    private $palette;\n\n    public function __construct($brickResource = null, LegoPaletteInterface $palette = null)\n    {\n        $this->setBrick(\n            ImageManagerStatic::make($brickResource ?? __DIR__ . '/../assets/brick.png')\n        );\n\n        $this->setPalette($palette ?? new ColorPalette());\n    }\n\n    public function setBrick(Image $brick): self\n    {\n        $this->brick       = $brick;\n        $this->brickWidth  = $brick->getWidth();\n        $this->brickHeight = $brick->getHeight();\n\n        return $this;\n    }\n\n    public function setPalette(LegoPaletteInterface $palette): self\n    {\n        $this->palette = $palette;\n\n        return $this;\n    }\n\n    public function getBrick(): Image\n    {\n        return $this->brick;\n    }\n\n    public function getPalette(): LegoPaletteInterface\n    {\n        return $this->palette;\n    }\n\n    public function convertToLego($resource, float $resolutionMultipler = 1, bool $legoColorsOnly = false): Image\n    {\n        $image = ImageManagerStatic::make($resource);\n\n        // Calculate how many bricks fit in the image\n        $amountOfBricksX = (int) round($image->getWidth() * $resolutionMultipler / $this->brickWidth);\n        $amountOfBricksY = (int) round($image->getHeight() * $resolutionMultipler / $this->brickHeight);\n\n        // Resize to the rounded value relative to the brick size\n        $image->resize($amountOfBricksX, $amountOfBricksY);\n\n        $canvas = ImageManagerStatic::canvas(\n            $amountOfBricksX * $this->brickWidth,\n            $amountOfBricksY * $this->brickHeight\n        );\n\n        for ($x = 0; $x < $amountOfBricksX; ++$x) {\n            for ($y = 0; $y < $amountOfBricksY; ++$y) {\n\n                /** @var AbstractColor $color */\n                $color = $image->pickColor($x, $y, \"object\");\n\n                if ($legoColorsOnly) {\n                    $color = $this->palette->pickClosestColor($color);\n                }\n\n                $colorizedBrick = $this->colorizeBrick($color);\n\n                $canvas->insert(\n                    $colorizedBrick,\n                    '',\n                    $x * $this->brickWidth,\n                    $y * $this->brickHeight\n                );\n            }\n        }\n\n        return $canvas;\n    }\n\n    private function getAverageBrickColor(): AbstractColor\n    {\n        if (null !== $this->brickAverageColor) {\n            return $this->brickAverageColor;\n        }\n\n        return $this->brickAverageColor = (clone $this->getBrick())\n            ->pixelate($this->brickWidth)\n            ->blur(50)\n            ->pickColor($this->brickWidth / 2, $this->brickHeight / 2, 'obj');\n    }\n\n    private function colorizeBrick(AbstractColor $color): Image\n    {\n        $brickColor = $this->getAverageBrickColor();\n\n        $colorRgba = $color->getArray();\n        $brickColorRgba = $brickColor->getArray();\n\n        return ImageManagerStatic::canvas(\n            $this->brickWidth,\n            $this->brickHeight,\n            $color->getHex('')\n        )->insert(\n            (clone $this->getBrick())->colorize(\n                // Picked color subtracted by the average brick color to avoid the image getting brighter\n                ($colorRgba[0] - $brickColorRgba[1]) / 2.55,\n                ($colorRgba[1] - $brickColorRgba[1]) / 2.55,\n                ($colorRgba[2] - $brickColorRgba[2]) / 2.55\n            )\n        );\n    }\n}\n"
  },
  {
    "path": "src/Pallete/ColorPalette.php",
    "content": "<?php declare(strict_types=1);\n\nnamespace RicardoFiorani\\Legofy\\Pallete;\n\nuse Intervention\\Image\\AbstractColor;\n\nclass ColorPalette implements LegoPaletteInterface\n{\n    private const PALETTE = [\n        '024' => [\n            254,\n            196,\n            1,\n        ],\n        '106' => [\n            231,\n            100,\n            25,\n        ],\n        '021' => [\n            222,\n            1,\n            14,\n        ],\n        '221' => [\n            222,\n            56,\n            139,\n        ],\n        '023' => [\n            1,\n            88,\n            168,\n        ],\n        '028' => [\n            1,\n            124,\n            41,\n        ],\n        '119' => [\n            149,\n            185,\n            12,\n        ],\n        '192' => [\n            92,\n            29,\n            13,\n        ],\n        '018' => [\n            214,\n            115,\n            65,\n        ],\n        '001' => [\n            244,\n            244,\n            244,\n        ],\n        '026' => [\n            2,\n            2,\n            2,\n        ],\n        '226' => [\n            255,\n            255,\n            153,\n        ],\n        '222' => [\n            238,\n            157,\n            195,\n        ],\n        '212' => [\n            135,\n            192,\n            234,\n        ],\n        '037' => [\n            1,\n            150,\n            37,\n        ],\n        '005' => [\n            217,\n            187,\n            124,\n        ],\n        '283' => [\n            245,\n            193,\n            137,\n        ],\n        '208' => [\n            228,\n            228,\n            218,\n        ],\n        '191' => [\n            244,\n            155,\n            1,\n        ],\n        '124' => [\n            156,\n            1,\n            198,\n        ],\n        '102' => [\n            72,\n            140,\n            198,\n        ],\n        '135' => [\n            95,\n            117,\n            140,\n        ],\n        '151' => [\n            96,\n            130,\n            102,\n        ],\n        '138' => [\n            141,\n            117,\n            83,\n        ],\n        '038' => [\n            168,\n            62,\n            22,\n        ],\n        '194' => [\n            156,\n            146,\n            145,\n        ],\n        '154' => [\n            128,\n            9,\n            28,\n        ],\n        '268' => [\n            45,\n            22,\n            120,\n        ],\n        '140' => [\n            1,\n            38,\n            66,\n        ],\n        '141' => [\n            1,\n            53,\n            23,\n        ],\n        '312' => [\n            170,\n            126,\n            86,\n        ],\n        '199' => [\n            77,\n            94,\n            87,\n        ],\n        '308' => [\n            49,\n            16,\n            7,\n        ],\n        '044' => [\n            249,\n            239,\n            105,\n        ],\n        '182' => [\n            236,\n            118,\n            14,\n        ],\n        '047' => [\n            231,\n            102,\n            72,\n        ],\n        '041' => [\n            224,\n            42,\n            41,\n        ],\n        '113' => [\n            238,\n            157,\n            195,\n        ],\n        '126' => [\n            156,\n            149,\n            199,\n        ],\n        '042' => [\n            182,\n            224,\n            234,\n        ],\n        '043' => [\n            80,\n            177,\n            232,\n        ],\n        '143' => [\n            206,\n            227,\n            246,\n        ],\n        '048' => [\n            99,\n            178,\n            110,\n        ],\n        '311' => [\n            153,\n            255,\n            102,\n        ],\n        '049' => [\n            241,\n            237,\n            91,\n        ],\n        '111' => [\n            166,\n            145,\n            130,\n        ],\n        '040' => [\n            238,\n            238,\n            238,\n        ],\n        '131' => [\n            141,\n            148,\n            150,\n        ],\n        '297' => [\n            170,\n            127,\n            46,\n        ],\n        '148' => [\n            73,\n            63,\n            59,\n        ],\n        '294' => [\n            254,\n            252,\n            213,\n        ],\n    ];\n\n    /**\n     * @link https://stackoverflow.com/questions/4485229/rgb-to-closest-predefined-color\n     */\n    public function pickClosestColor(AbstractColor $color): AbstractColor\n    {\n        $distances = [];\n\n        $colorArray = $color->getArray();\n\n        foreach (self::PALETTE as $colorIdentifier => $colorSchema) {\n            $rDistance = $colorSchema[0] - $colorArray[0];\n            $gDistance = $colorSchema[1] - $colorArray[1];\n            $bDistance = $colorSchema[2] - $colorArray[2];\n\n            $distance = ($rDistance * .299) ** 2 + ($gDistance * .587) ** 2 + ($bDistance * .114) ** 2;\n\n            $distances[$colorIdentifier] = $distance;\n        }\n\n        asort($distances);\n\n        $colorIdentifier = array_keys($distances)[0];\n\n        $color->initFromRgb(...self::PALETTE[$colorIdentifier]);\n\n        return $color;\n    }\n}\n"
  },
  {
    "path": "src/Pallete/LegoPaletteInterface.php",
    "content": "<?php declare(strict_types=1);\n\nnamespace RicardoFiorani\\Legofy\\Pallete;\n\nuse Intervention\\Image\\AbstractColor;\n\ninterface LegoPaletteInterface\n{\n    public function pickClosestColor(AbstractColor $color): AbstractColor;\n}\n"
  },
  {
    "path": "tests/Integration/MainIntegrationTest.php",
    "content": "<?php declare(strict_types=1);\n\nnamespace Tests\\RicardoFiorani\\Integration;\n\nuse Intervention\\Image\\Image;\nuse PHPUnit\\Framework\\TestCase;\nuse RicardoFiorani\\Legofy\\Legofy;\nuse RicardoFiorani\\Legofy\\Pallete\\LegoPaletteInterface;\n\nclass MainIntegrationTest extends TestCase\n{\n    public function testMainFunctionality()\n    {\n        $legofier = new Legofy();\n\n        TestCase::assertInstanceOf(Image::class, $legofier->getBrick());\n        TestCase::assertInstanceOf(LegoPaletteInterface::class, $legofier->getPalette());\n\n        $originalSource = __DIR__ . '/../../assets/examples/beer.jpg';\n        $result = $legofier->convertToLego($originalSource);\n\n        TestCase::assertInstanceOf(Image::class, $result);\n        TestCase::assertNotEmpty($result->psrResponse()->getBody()->getContents());\n    }\n\n    public function testWorksOnLegoColorOnly()\n    {\n        $legofier = new Legofy();\n\n        TestCase::assertInstanceOf(Image::class, $legofier->getBrick());\n        TestCase::assertInstanceOf(LegoPaletteInterface::class, $legofier->getPalette());\n\n        $originalSource = __DIR__ . '/../../assets/examples/beer.jpg';\n        $result = $legofier->convertToLego($originalSource, 1, true);\n\n        TestCase::assertInstanceOf(Image::class, $result);\n        TestCase::assertNotEmpty($result->psrResponse()->getBody()->getContents());\n    }\n}\n"
  },
  {
    "path": "tests/Unit/Palette/ColorPaletteTest.php",
    "content": "<?php declare(strict_types=1);\n\nnamespace Tests\\RicardoFiorani\\Unit\\Palette;\n\nuse Intervention\\Image\\Gd\\Color;\nuse PHPUnit\\Framework\\TestCase;\nuse RicardoFiorani\\Legofy\\Pallete\\ColorPalette;\n\nclass ColorPaletteTest extends TestCase\n{\n    /**\n     * @dataProvider colorProvider\n     */\n    public function testGetsRightColor(array $colors, array $input, $expected)\n    {\n        $colorPalette = new ColorPalette($colors);\n\n        $outputColor = $colorPalette->pickClosestColor(new Color($input));\n        $outputColorArray = $outputColor->getArray();\n\n        $this->assertEquals($expected[0], $outputColorArray[0]);\n        $this->assertEquals($expected[1], $outputColorArray[1]);\n        $this->assertEquals($expected[2], $outputColorArray[2]);\n    }\n\n    public function colorProvider()\n    {\n        return [\n            //Whole set\n            [\n                //Palette\n                [\n                    '024' => [254, 196, 1,],\n                    '106' => [231, 100, 25,],\n                    '021' => [222, 1, 14,],\n                ],\n                //input color\n                [253, 194, 2],\n                //expected color\n                [254, 196, 1,]\n            ],\n            //Whole set\n            [\n                //Palette\n                [\n                    '024' => [254, 196, 1,],\n                    '106' => [231, 100, 25,],\n                    '021' => [222, 1, 14,],\n                ],\n                //input color\n                [224, 5, 22],\n                //expected color\n                [222, 1, 14,]\n            ],\n        ];\n    }\n}"
  }
]