Repository: ricardofiorani/php-legofy Branch: master Commit: 6c07ce6ace85 Files: 11 Total size: 19.3 KB Directory structure: gitextract_8uvu_bst/ ├── .gitignore ├── .travis.yml ├── README.md ├── composer.json ├── docs/ │ └── EXAMPLES.md ├── phpunit.xml ├── src/ │ ├── Legofy.php │ └── Pallete/ │ ├── ColorPalette.php │ └── LegoPaletteInterface.php └── tests/ ├── Integration/ │ └── MainIntegrationTest.php └── Unit/ └── Palette/ └── ColorPaletteTest.php ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ .idea composer.lock vendor temp index.php build .env .phpunit.result.cache ================================================ FILE: .travis.yml ================================================ dist: xenial language: php php: - 7.1 - 7.2 - 7.3 - 7.4 - 8.0 # This triggers builds to run on the new TravisCI infrastructure. # See: http://docs.travis-ci.com/user/workers/container-based-infrastructure/ sudo: false ## Cache composer cache: directories: - $HOME/.composer/cache before_script: - travis_retry composer update ${COMPOSER_FLAGS} --no-interaction --prefer-dist script: - echo "xdebug.mode=coverage" >> ~/.phpenv/versions/$(phpenv version-name)/etc/php.ini - vendor/bin/phpcs --standard=psr2 src/ - vendor/bin/phpunit --coverage-text --coverage-clover=coverage.clover after_script: - | if [[ "$TRAVIS_PHP_VERSION" != 'hhvm' && "$TRAVIS_PHP_VERSION" != '7.0' ]]; then wget https://scrutinizer-ci.com/ocular.phar php ocular.phar code-coverage:upload --format=php-clover coverage.clover fi ================================================ FILE: README.md ================================================ # PHP Legofy [![Build Status](https://api.travis-ci.org/ricardofiorani/php-legofy.svg?branch=master)](http://travis-ci.org/ricardofiorani/php-legofy) [![Minimum PHP Version](https://img.shields.io/packagist/php-v/ricardofiorani/php-legofy.svg)](https://php.net/) [![License](https://poser.pugx.org/ricardofiorani/php-legofy/license.png)](https://packagist.org/packages/ricardofiorani/php-legofy) [![Total Downloads](https://poser.pugx.org/ricardofiorani/php-legofy/d/total.png)](https://packagist.org/packages/ricardofiorani/php-legofy) [![Coding Standards](https://img.shields.io/badge/cs-PSR--4-yellow.svg)](https://github.com/php-fig-rectified/fig-rectified-standards) ### What is this ? PHP 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. It was inspired by the original Legofy made in Python https://github.com/JuanPotato/Legofy Basically it transforms this: ![Image of a beer][beer] Into this: ![Image of a legofied beer][lego-beer] [beer]: ./assets/examples/beer.jpg [lego-beer]: ./assets/examples/lego-beer.jpeg ### Requirements * PHP ^7.1 || ^8.0 * GD or ImageMagick > I tested it with GD only but I'm trusting intervention/image that this will work on ImageMagick as well. ### Quickstart Via composer: ```bash $ composer require ricardofiorani/php-legofy ``` Via source: ```bash $ git clone git@github.com:ricardofiorani/php-legofy.git $ cd php-legofy $ composer install ``` ### Usage: ```php convertToLego($source, $resolutionMultiplier, $useLegoPalette); // Please see http://image.intervention.io/use/basics and http://image.intervention.io/use/http echo $output->response(); ``` For more examples of usage please see [the usage examples on documentation](https://github.com/ricardofiorani/php-legofy/blob/master/docs/EXAMPLES.md) ================================================ FILE: composer.json ================================================ { "name": "ricardofiorani/php-legofy", "description": "A PHP Port of Legofy", "keywords": ["lego", "legofy", "gd", "imagick", "image", "effect"], "require": { "php": "^7.1 || ^8.0", "intervention/image": "^2.4" }, "require-dev": { "phpunit/phpunit": "^7.2 || ^9.0", "spryker/code-sniffer": "^0.15.1" }, "autoload": { "psr-4": { "RicardoFiorani\\Legofy\\": "src" } }, "autoload-dev": { "psr-4": { "Tests\\RicardoFiorani\\": "tests" } }, "license": "MIT", "authors": [ { "name": "Ricardo Fiorani", "email": "ricardo.fiorani@gmail.com" } ], "scripts": { "test": "phpunit" }, "suggest": { "ext-gd": "to use GD library based image processing.", "ext-imagick": "to use Imagick based image processing." } } ================================================ FILE: docs/EXAMPLES.md ================================================ # Examples ### Input Image: ![Image of a beer][beer] ### Example 1 - Default: ```php convertToLego($source, $resolutionMultiplier, $useLegoPalette); // Please see http://image.intervention.io/use/basics and http://image.intervention.io/use/http echo $output->response(); ``` Output: ![Image of a legofied beer][lego-beer] ### Example 2 - Resolution multiplier > Please be advised that using higher multiplers will consume more memory and CPU usage on the legofying process ! The resolution multiplier multiplies the resolution of the original source image. The default value is 1. Please note that this value is a float, so you can set it as 1.5 for example: ![Image of a legofied beer with more res][1-5-lego-beer] You can also set it as an 0.5 value for example: ![Image of a legofied beer with less res][0-5-lego-beer] ### Example 3 - Using the original lego color palette On 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.) ```php convertToLego($source, $resolutionMultiplier, true); ``` Output: ![Image of a legofied beer][real-color-lego-beer] > 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. [beer]: ../assets/examples/beer.jpg [lego-beer]: ../assets/examples/lego-beer.jpeg [1-5-lego-beer]: ../assets/examples/res-1-5-lego-beer.jpeg [0-5-lego-beer]: ../assets/examples/res-0-5-lego-beer.jpeg [real-color-lego-beer]: ../assets/examples/real-color-lego-beer.jpeg ================================================ FILE: phpunit.xml ================================================ tests/Integration tests/Unit src/ ================================================ FILE: src/Legofy.php ================================================ setBrick( ImageManagerStatic::make($brickResource ?? __DIR__ . '/../assets/brick.png') ); $this->setPalette($palette ?? new ColorPalette()); } public function setBrick(Image $brick): self { $this->brick = $brick; $this->brickWidth = $brick->getWidth(); $this->brickHeight = $brick->getHeight(); return $this; } public function setPalette(LegoPaletteInterface $palette): self { $this->palette = $palette; return $this; } public function getBrick(): Image { return $this->brick; } public function getPalette(): LegoPaletteInterface { return $this->palette; } public function convertToLego($resource, float $resolutionMultipler = 1, bool $legoColorsOnly = false): Image { $image = ImageManagerStatic::make($resource); // Calculate how many bricks fit in the image $amountOfBricksX = (int) round($image->getWidth() * $resolutionMultipler / $this->brickWidth); $amountOfBricksY = (int) round($image->getHeight() * $resolutionMultipler / $this->brickHeight); // Resize to the rounded value relative to the brick size $image->resize($amountOfBricksX, $amountOfBricksY); $canvas = ImageManagerStatic::canvas( $amountOfBricksX * $this->brickWidth, $amountOfBricksY * $this->brickHeight ); for ($x = 0; $x < $amountOfBricksX; ++$x) { for ($y = 0; $y < $amountOfBricksY; ++$y) { /** @var AbstractColor $color */ $color = $image->pickColor($x, $y, "object"); if ($legoColorsOnly) { $color = $this->palette->pickClosestColor($color); } $colorizedBrick = $this->colorizeBrick($color); $canvas->insert( $colorizedBrick, '', $x * $this->brickWidth, $y * $this->brickHeight ); } } return $canvas; } private function getAverageBrickColor(): AbstractColor { if (null !== $this->brickAverageColor) { return $this->brickAverageColor; } return $this->brickAverageColor = (clone $this->getBrick()) ->pixelate($this->brickWidth) ->blur(50) ->pickColor($this->brickWidth / 2, $this->brickHeight / 2, 'obj'); } private function colorizeBrick(AbstractColor $color): Image { $brickColor = $this->getAverageBrickColor(); $colorRgba = $color->getArray(); $brickColorRgba = $brickColor->getArray(); return ImageManagerStatic::canvas( $this->brickWidth, $this->brickHeight, $color->getHex('') )->insert( (clone $this->getBrick())->colorize( // Picked color subtracted by the average brick color to avoid the image getting brighter ($colorRgba[0] - $brickColorRgba[1]) / 2.55, ($colorRgba[1] - $brickColorRgba[1]) / 2.55, ($colorRgba[2] - $brickColorRgba[2]) / 2.55 ) ); } } ================================================ FILE: src/Pallete/ColorPalette.php ================================================ [ 254, 196, 1, ], '106' => [ 231, 100, 25, ], '021' => [ 222, 1, 14, ], '221' => [ 222, 56, 139, ], '023' => [ 1, 88, 168, ], '028' => [ 1, 124, 41, ], '119' => [ 149, 185, 12, ], '192' => [ 92, 29, 13, ], '018' => [ 214, 115, 65, ], '001' => [ 244, 244, 244, ], '026' => [ 2, 2, 2, ], '226' => [ 255, 255, 153, ], '222' => [ 238, 157, 195, ], '212' => [ 135, 192, 234, ], '037' => [ 1, 150, 37, ], '005' => [ 217, 187, 124, ], '283' => [ 245, 193, 137, ], '208' => [ 228, 228, 218, ], '191' => [ 244, 155, 1, ], '124' => [ 156, 1, 198, ], '102' => [ 72, 140, 198, ], '135' => [ 95, 117, 140, ], '151' => [ 96, 130, 102, ], '138' => [ 141, 117, 83, ], '038' => [ 168, 62, 22, ], '194' => [ 156, 146, 145, ], '154' => [ 128, 9, 28, ], '268' => [ 45, 22, 120, ], '140' => [ 1, 38, 66, ], '141' => [ 1, 53, 23, ], '312' => [ 170, 126, 86, ], '199' => [ 77, 94, 87, ], '308' => [ 49, 16, 7, ], '044' => [ 249, 239, 105, ], '182' => [ 236, 118, 14, ], '047' => [ 231, 102, 72, ], '041' => [ 224, 42, 41, ], '113' => [ 238, 157, 195, ], '126' => [ 156, 149, 199, ], '042' => [ 182, 224, 234, ], '043' => [ 80, 177, 232, ], '143' => [ 206, 227, 246, ], '048' => [ 99, 178, 110, ], '311' => [ 153, 255, 102, ], '049' => [ 241, 237, 91, ], '111' => [ 166, 145, 130, ], '040' => [ 238, 238, 238, ], '131' => [ 141, 148, 150, ], '297' => [ 170, 127, 46, ], '148' => [ 73, 63, 59, ], '294' => [ 254, 252, 213, ], ]; /** * @link https://stackoverflow.com/questions/4485229/rgb-to-closest-predefined-color */ public function pickClosestColor(AbstractColor $color): AbstractColor { $distances = []; $colorArray = $color->getArray(); foreach (self::PALETTE as $colorIdentifier => $colorSchema) { $rDistance = $colorSchema[0] - $colorArray[0]; $gDistance = $colorSchema[1] - $colorArray[1]; $bDistance = $colorSchema[2] - $colorArray[2]; $distance = ($rDistance * .299) ** 2 + ($gDistance * .587) ** 2 + ($bDistance * .114) ** 2; $distances[$colorIdentifier] = $distance; } asort($distances); $colorIdentifier = array_keys($distances)[0]; $color->initFromRgb(...self::PALETTE[$colorIdentifier]); return $color; } } ================================================ FILE: src/Pallete/LegoPaletteInterface.php ================================================ getBrick()); TestCase::assertInstanceOf(LegoPaletteInterface::class, $legofier->getPalette()); $originalSource = __DIR__ . '/../../assets/examples/beer.jpg'; $result = $legofier->convertToLego($originalSource); TestCase::assertInstanceOf(Image::class, $result); TestCase::assertNotEmpty($result->psrResponse()->getBody()->getContents()); } public function testWorksOnLegoColorOnly() { $legofier = new Legofy(); TestCase::assertInstanceOf(Image::class, $legofier->getBrick()); TestCase::assertInstanceOf(LegoPaletteInterface::class, $legofier->getPalette()); $originalSource = __DIR__ . '/../../assets/examples/beer.jpg'; $result = $legofier->convertToLego($originalSource, 1, true); TestCase::assertInstanceOf(Image::class, $result); TestCase::assertNotEmpty($result->psrResponse()->getBody()->getContents()); } } ================================================ FILE: tests/Unit/Palette/ColorPaletteTest.php ================================================ pickClosestColor(new Color($input)); $outputColorArray = $outputColor->getArray(); $this->assertEquals($expected[0], $outputColorArray[0]); $this->assertEquals($expected[1], $outputColorArray[1]); $this->assertEquals($expected[2], $outputColorArray[2]); } public function colorProvider() { return [ //Whole set [ //Palette [ '024' => [254, 196, 1,], '106' => [231, 100, 25,], '021' => [222, 1, 14,], ], //input color [253, 194, 2], //expected color [254, 196, 1,] ], //Whole set [ //Palette [ '024' => [254, 196, 1,], '106' => [231, 100, 25,], '021' => [222, 1, 14,], ], //input color [224, 5, 22], //expected color [222, 1, 14,] ], ]; } }