diff options
author | Minteck <contact@minteck.org> | 2023-02-23 19:34:56 +0100 |
---|---|---|
committer | Minteck <contact@minteck.org> | 2023-02-23 19:34:56 +0100 |
commit | 3d1cd02f27518f1a04374c7c8320cd5d82ede6e9 (patch) | |
tree | 75be5fba4368472fb11c8015aee026b2b9a71888 /includes/composer/vendor/ksubileau | |
parent | 8cc1f13c17fa2fb5a4410542d39e650e02945634 (diff) | |
download | pluralconnect-3d1cd02f27518f1a04374c7c8320cd5d82ede6e9.tar.gz pluralconnect-3d1cd02f27518f1a04374c7c8320cd5d82ede6e9.tar.bz2 pluralconnect-3d1cd02f27518f1a04374c7c8320cd5d82ede6e9.zip |
Updated 40 files, added 37 files, deleted 1103 files and renamed 3905 files (automated)
Diffstat (limited to 'includes/composer/vendor/ksubileau')
17 files changed, 1891 insertions, 0 deletions
diff --git a/includes/composer/vendor/ksubileau/color-thief-php/LICENSE b/includes/composer/vendor/ksubileau/color-thief-php/LICENSE new file mode 100644 index 0000000..3e13937 --- /dev/null +++ b/includes/composer/vendor/ksubileau/color-thief-php/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2020 Kevin Subileau + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/includes/composer/vendor/ksubileau/color-thief-php/composer.json b/includes/composer/vendor/ksubileau/color-thief-php/composer.json new file mode 100644 index 0000000..3b6d81a --- /dev/null +++ b/includes/composer/vendor/ksubileau/color-thief-php/composer.json @@ -0,0 +1,45 @@ +{ + "name": "ksubileau/color-thief-php", + "type": "library", + "homepage" : "http://www.kevinsubileau.fr/projets/color-thief-php", + "description": "Grabs the dominant color or a representative color palette from an image.", + "keywords": ["color", "thief", "php", "dominant", "palette"], + "license": "MIT", + + "authors": [ + { + "name": "Kevin Subileau", + "homepage": "http://www.kevinsubileau.fr" + } + ], + + "require": { + "php": "^7.2|^8.0", + "ext-fileinfo": "*" + }, + + "require-dev": { + "phpunit/phpunit": "^8.5", + "friendsofphp/php-cs-fixer": "^3.4", + "phpstan/phpstan": "^1.2.0", + "phpstan/phpstan-phpunit": "^1.0.0" + }, + + "suggest": { + "ext-gd": "to use the GD image adapter.", + "ext-imagick": "to use the Imagick image adapter.", + "ext-gmagick": "to use the Gmagick image adapter." + }, + + "autoload": { + "psr-4": { + "ColorThief\\": "src/ColorThief" + } + }, + + "autoload-dev": { + "psr-4": { + "ColorThief\\Tests\\": "tests/" + } + } +} diff --git a/includes/composer/vendor/ksubileau/color-thief-php/src/ColorThief/Color.php b/includes/composer/vendor/ksubileau/color-thief-php/src/ColorThief/Color.php new file mode 100644 index 0000000..f0c7ef0 --- /dev/null +++ b/includes/composer/vendor/ksubileau/color-thief-php/src/ColorThief/Color.php @@ -0,0 +1,145 @@ +<?php + +/* + * This file is part of the Color Thief PHP project. + * + * (c) Kevin Subileau + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace ColorThief; + +use ColorThief\Exception\NotSupportedException; + +class Color +{ + /** + * RGB Red value of current color instance. + * + * @var int + */ + private $red; + + /** + * RGB Green value of current color instance. + * + * @var int + */ + private $green; + + /** + * RGB Blue value of current color instance. + * + * @var int + */ + private $blue; + + /** + * Creates new instance. + */ + public function __construct(int $red = 0, int $green = 0, int $blue = 0) + { + $this->red = $red; + $this->green = $green; + $this->blue = $blue; + } + + /** + * Get red value. + */ + public function getRed(): int + { + return $this->red; + } + + /** + * Get green value. + */ + public function getGreen(): int + { + return $this->green; + } + + /** + * Get blue value. + */ + public function getBlue(): int + { + return $this->blue; + } + + /** + * Calculates integer value of current color instance. + */ + public function getInt(): int + { + return ($this->red << 16) + ($this->green << 8) + $this->blue; + } + + /** + * Calculates hexadecimal value of current color instance. + */ + public function getHex(string $prefix = ''): string + { + return sprintf('%s%02x%02x%02x', $prefix, $this->red, $this->green, $this->blue); + } + + /** + * Calculates RGB in array format of current color instance. + * + * @phpstan-return ColorRGB + */ + public function getArray(): array + { + return [$this->red, $this->green, $this->blue]; + } + + /** + * Calculates RGB in string format of current color instance. + */ + public function getRgb(): string + { + return sprintf('rgb(%d, %d, %d)', $this->red, $this->green, $this->blue); + } + + /** + * Formats current color instance into given format. + * + * @return string|int|array|self + * @phpstan-return ColorRGB|string|int|self + */ + public function format(string $type) + { + switch (strtolower($type)) { + case 'rgb': + return $this->getRgb(); + + case 'hex': + return $this->getHex('#'); + + case 'int': + return $this->getInt(); + + case 'array': + return $this->getArray(); + + case 'obj': + return $this; + + default: + throw new NotSupportedException("Color format ({$type}) is not supported."); + } + } + + /** + * Get color as string. + */ + public function __toString(): string + { + return $this->getHex('#'); + } +} diff --git a/includes/composer/vendor/ksubileau/color-thief-php/src/ColorThief/ColorThief.php b/includes/composer/vendor/ksubileau/color-thief-php/src/ColorThief/ColorThief.php new file mode 100644 index 0000000..2cd658b --- /dev/null +++ b/includes/composer/vendor/ksubileau/color-thief-php/src/ColorThief/ColorThief.php @@ -0,0 +1,539 @@ +<?php + +/* + * This file is part of the Color Thief PHP project. + * + * (c) Kevin Subileau + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +/* + * Color Thief PHP + * + * Grabs the dominant color or a representative color palette from an image. + * + * This class requires the GD library to be installed on the server. + * + * It's a PHP port of the Color Thief Javascript library + * (http://github.com/lokesh/color-thief), using the MMCQ + * (modified median cut quantization) algorithm from + * the Leptonica library (http://www.leptonica.com/). + * + * by Kevin Subileau - http://www.kevinsubileau.fr + * Based on the work done by Lokesh Dhakar - http://www.lokeshdhakar.com + * and Nick Rabinowitz + * + * Thanks + * ------ + * Lokesh Dhakar - For creating the original project. + * Nick Rabinowitz - For creating quantize.js. + * + */ + +namespace ColorThief; + +use ColorThief\Exception\InvalidArgumentException; +use ColorThief\Exception\NotSupportedException; +use ColorThief\Exception\RuntimeException; +use ColorThief\Image\Adapter\AdapterInterface; +use ColorThief\Image\ImageLoader; +use SplFixedArray; + +class ColorThief +{ + public const SIGBITS = 5; + public const RSHIFT = 3; + public const MAX_ITERATIONS = 1000; + public const FRACT_BY_POPULATIONS = 0.75; + public const THRESHOLD_ALPHA = 62; + public const THRESHOLD_WHITE = 250; + + /** + * Get combined color index (3 colors as one integer) from RGB values (0-255) or RGB Histogram Buckets (0-31). + */ + public static function getColorIndex(int $red, int $green, int $blue, int $sigBits = self::SIGBITS): int + { + return (($red >> (8 - $sigBits)) << (2 * $sigBits)) | (($green >> (8 - $sigBits)) << $sigBits) | ($blue >> (8 - $sigBits)); + } + + /** + * Get RGB values (0-255) or RGB Histogram Buckets from a combined color index (3 colors as one integer). + * + * @phpstan-return ColorRGB + */ + public static function getColorsFromIndex(int $index, int $sigBits = 8): array + { + $mask = (1 << $sigBits) - 1; + + $red = ($index >> (2 * $sigBits)) & $mask; + $green = ($index >> $sigBits) & $mask; + $blue = $index & $mask; + + return [$red, $green, $blue]; + } + + /** + * Gets the dominant color from the image using the median cut algorithm to cluster similar colors. + * + * @param mixed $sourceImage Path/URL to the image, GD resource, Imagick/Gmagick instance, or image as binary string + * @param int $quality 1 is the highest quality. There is a trade-off between quality and speed. + * It determines how many pixels are skipped before the next one is sampled. + * We rarely need to sample every single pixel in the image to get good results. + * The bigger the number, the faster the palette generation but the greater the + * likelihood that colors will be missed. + * @param array|null $area It allows you to specify a rectangular area in the image in order to get + * colors only for this area. It needs to be an associative array with the + * following keys: + * $area['x']: The x-coordinate of the top left corner of the area. Default to 0. + * $area['y']: The y-coordinate of the top left corner of the area. Default to 0. + * $area['w']: The width of the area. Default to image width minus x-coordinate. + * $area['h']: The height of the area. Default to image height minus y-coordinate. + * @param string $outputFormat By default, color is returned as an array of three integers representing + * red, green, and blue values. + * You can choose another output format by passing one of the following values: + * 'rgb' : RGB string notation (ex: rgb(253, 42, 152)). + * 'hex' : String of the hexadecimal representation (ex: #fd2a98). + * 'int' : Integer color value (ex: 16591512). + * 'array' : Default format (ex: [253, 42, 152]). + * 'obj' : Instance of ColorThief\Color, for custom processing. + * @param AdapterInterface|string|null $adapter Optional argument to choose a preferred image adapter to use for loading the image. + * By default, the adapter is automatically chosen depending on the available extensions + * and the type of $sourceImage (for example Imagick is used if $sourceImage is an Imagick instance). + * You can pass one of the 'Imagick', 'Gmagick' or 'Gd' string to use the corresponding + * underlying image extension, or you can pass an instance of any class implementing + * the AdapterInterface interface to use a custom image loader. + * @phpstan-param ?RectangularArea $area + * + * @phpstan-return ColorRGB|Color|int|string|null + */ + public static function getColor($sourceImage, int $quality = 10, ?array $area = null, string $outputFormat = 'array', $adapter = null) + { + $palette = self::getPalette($sourceImage, 5, $quality, $area, $outputFormat, $adapter); + + return $palette ? $palette[0] : null; + } + + /** + * Gets a palette of dominant colors from the image using the median cut algorithm to cluster similar colors. + * + * @param mixed $sourceImage Path/URL to the image, GD resource, Imagick/Gmagick instance, or image as binary string + * @param int $colorCount it determines the size of the palette; the number of colors returned + * @param int $quality 1 is the highest quality. There is a trade-off between quality and speed. + * It determines how many pixels are skipped before the next one is sampled. + * We rarely need to sample every single pixel in the image to get good results. + * The bigger the number, the faster the palette generation but the greater the + * likelihood that colors will be missed. + * @param array|null $area It allows you to specify a rectangular area in the image in order to get + * colors only for this area. It needs to be an associative array with the + * following keys: + * $area['x']: The x-coordinate of the top left corner of the area. Default to 0. + * $area['y']: The y-coordinate of the top left corner of the area. Default to 0. + * $area['w']: The width of the area. Default to image width minus x-coordinate. + * $area['h']: The height of the area. Default to image height minus y-coordinate. + * @param string $outputFormat By default, colors are returned as an array of three integers representing + * red, green, and blue values. + * You can choose another output format by passing one of the following values: + * 'rgb' : RGB string notation (ex: rgb(253, 42, 152)). + * 'hex' : String of the hexadecimal representation (ex: #fd2a98). + * 'int' : Integer color value (ex: 16591512). + * 'array' : Default format (ex: [253, 42, 152]). + * 'obj' : Instance of ColorThief\Color, for custom processing. + * @param AdapterInterface|string|null $adapter Optional argument to choose a preferred image adapter to use for loading the image. + * By default, the adapter is automatically chosen depending on the available extensions + * and the type of $sourceImage (e.g. Imagick is used if $sourceImage is an Imagick instance). + * You can pass one of the 'Imagick', 'Gmagick' or 'Gd' string to use the corresponding + * underlying image extension, or you can pass an instance of any class implementing + * the AdapterInterface interface to use a custom image loader. + * @phpstan-param ?RectangularArea $area + * + * @return array + * @phpstan-return ColorRGB[]|Color[]|int[]|string[]|null + */ + public static function getPalette( + $sourceImage, + int $colorCount = 10, + int $quality = 10, + ?array $area = null, + string $outputFormat = 'array', + $adapter = null + ): ?array { + if ($colorCount < 2 || $colorCount > 256) { + throw new InvalidArgumentException('The number of palette colors must be between 2 and 256 inclusive.'); + } + + if ($quality < 1) { + throw new InvalidArgumentException('The quality argument must be an integer greater than one.'); + } + + $histo = []; + $numPixelsAnalyzed = self::loadImage($sourceImage, $quality, $histo, $area, $adapter); + if (0 === $numPixelsAnalyzed) { + throw new NotSupportedException('Unable to compute the color palette of a blank or transparent image.'); + } + + // Send histogram to quantize function which clusters values + // using median cut algorithm + $palette = self::quantize($numPixelsAnalyzed, $colorCount, $histo); + + return array_map(function (Color $color) use ($outputFormat) { + return $color->format($outputFormat); + }, $palette); + } + + /** + * @param mixed $sourceImage Path/URL to the image, GD resource, Imagick instance, or image as binary string + * @param int $quality Analyze every $quality pixels + * @param array<int, int> $histo Histogram + * @param AdapterInterface|string|null $adapter Image adapter to use for loading the image + * @phpstan-param ?RectangularArea $area + */ + private static function loadImage($sourceImage, int $quality, array &$histo, array $area = null, $adapter = null): int + { + $loader = new ImageLoader(); + if (null !== $adapter) { + $loader->setPreferredAdapter($adapter); + } + $image = $loader->load($sourceImage); + $startX = 0; + $startY = 0; + $width = $image->getWidth(); + $height = $image->getHeight(); + + if ($area) { + $startX = $area['x'] ?? 0; + $startY = $area['y'] ?? 0; + $width = $area['w'] ?? ($width - $startX); + $height = $area['h'] ?? ($height - $startY); + + if ((($startX + $width) > $image->getWidth()) || (($startY + $height) > $image->getHeight())) { + throw new InvalidArgumentException('Area is out of image bounds.'); + } + } + + // Fill a SplArray with zeroes to initialize the 5-bit buckets and avoid having to check isset in the pixel loop. + // There are 32768 buckets because each color is 5 bits (15 bits total for RGB values). + $totalBuckets = (1 << (3 * self::SIGBITS)); + $histoSpl = new SplFixedArray($totalBuckets); + for ($i = 0; $i < $totalBuckets; ++$i) { + $histoSpl[$i] = 0; + } + + $numUsefulPixels = 0; + $pixelCount = $width * $height; + + for ($i = 0; $i < $pixelCount; $i += $quality) { + $x = $startX + ($i % $width); + $y = (int) ($startY + $i / $width); + $color = $image->getPixelColor($x, $y); + + // Pixel is too transparent. Its alpha value is larger (more transparent) than THRESHOLD_ALPHA. + // PHP's transparency range (0-127 opaque-transparent) is reverse that of Javascript (0-255 tranparent-opaque). + if ($color->alpha > self::THRESHOLD_ALPHA) { + continue; + } + + // Pixel is too white to be useful. Its RGB values all exceed THRESHOLD_WHITE + if ($color->red > self::THRESHOLD_WHITE && $color->green > self::THRESHOLD_WHITE && $color->blue > self::THRESHOLD_WHITE) { + continue; + } + + // Count this pixel in its histogram bucket. + ++$numUsefulPixels; + $bucketIndex = self::getColorIndex($color->red, $color->green, $color->blue); + $histoSpl[$bucketIndex] = $histoSpl[$bucketIndex] + 1; + } + + // Copy the histogram buckets that had pixels back to a normal array. + $histo = []; + foreach ($histoSpl as $bucketInt => $numPixels) { + if ($numPixels > 0) { + $histo[$bucketInt] = $numPixels; + } + } + + // Don't destroy a resource passed by the user ! + // TODO Add a method in ImageLoader to know if the image should be destroy + // (or to know the detected image source type) + if (\is_string($sourceImage)) { + $image->destroy(); + } + + return $numUsefulPixels; + } + + /** + * @param array<int, int> $histo + */ + private static function vboxFromHistogram(array $histo): VBox + { + $rgbMin = [\PHP_INT_MAX, \PHP_INT_MAX, \PHP_INT_MAX]; + $rgbMax = [-\PHP_INT_MAX, -\PHP_INT_MAX, -\PHP_INT_MAX]; + + // find min/max + foreach ($histo as $bucketIndex => $count) { + $rgb = self::getColorsFromIndex($bucketIndex, self::SIGBITS); + + // For each color components + for ($i = 0; $i < 3; ++$i) { + if ($rgb[$i] < $rgbMin[$i]) { + $rgbMin[$i] = $rgb[$i]; + } + if ($rgb[$i] > $rgbMax[$i]) { + $rgbMax[$i] = $rgb[$i]; + } + } + } + + return new VBox($rgbMin[0], $rgbMax[0], $rgbMin[1], $rgbMax[1], $rgbMin[2], $rgbMax[2], $histo); + } + + /** + * @param int[] $partialSum + * + * @return array{VBox, VBox}|null + */ + private static function doCut(string $color, VBox $vBox, array $partialSum, int $total): ?array + { + $dim1 = $color.'1'; + $dim2 = $color.'2'; + + for ($i = $vBox->$dim1; $i <= $vBox->$dim2; ++$i) { + if ($partialSum[$i] > $total / 2) { + $vBox1 = $vBox->copy(); + $vBox2 = $vBox->copy(); + $left = $i - $vBox->$dim1; + $right = $vBox->$dim2 - $i; + + // Choose the cut plane within the greater of the (left, right) sides + // of the bin in which the median pixel resides + if ($left <= $right) { + $d2 = min($vBox->$dim2 - 1, (int) ($i + $right / 2)); + } else { /* left > right */ + $d2 = max($vBox->$dim1, (int) ($i - 1 - $left / 2)); + } + + while (empty($partialSum[$d2])) { + ++$d2; + } + // Avoid 0-count boxes + while ($partialSum[$d2] >= $total && !empty($partialSum[$d2 - 1])) { + --$d2; + } + + // set dimensions + $vBox1->$dim2 = $d2; + $vBox2->$dim1 = $d2 + 1; + + return [$vBox1, $vBox2]; + } + } + + return null; + } + + /** + * @param array<int, int> $histo + * + * @return VBox[]|null + */ + private static function medianCutApply(array $histo, VBox $vBox): ?array + { + if (!$vBox->count()) { + return null; + } + + // If the vbox occupies just one element in color space, it can't be split + if (1 == $vBox->count()) { + return [ + $vBox->copy(), + ]; + } + + // Select the longest axis for splitting + $cutColor = $vBox->longestAxis(); + + // Find the partial sum arrays along the selected axis. + [$total, $partialSum] = self::sumColors($cutColor, $histo, $vBox); + + return self::doCut($cutColor, $vBox, $partialSum, $total); + } + + /** + * Find the partial sum arrays along the selected axis. + * + * @param string $axis r|g|b + * @phpstan-param 'r'|'g'|'b' $axis + * + * @param array<int, int> $histo + * + * @return array{int, array<int, int>} [$total, $partialSum] + */ + private static function sumColors(string $axis, array $histo, VBox $vBox): array + { + $total = 0; + $partialSum = []; + + // The selected axis should be the first range + $colorIterateOrder = array_diff(['r', 'g', 'b'], [$axis]); + array_unshift($colorIterateOrder, $axis); + + // Retrieves iteration ranges + [$firstRange, $secondRange, $thirdRange] = self::getVBoxColorRanges($vBox, $colorIterateOrder); + + foreach ($firstRange as $firstColor) { + $sum = 0; + foreach ($secondRange as $secondColor) { + foreach ($thirdRange as $thirdColor) { + // Rearrange color components + $bucket = [ + $colorIterateOrder[0] => $firstColor, + $colorIterateOrder[1] => $secondColor, + $colorIterateOrder[2] => $thirdColor, + ]; + + // The getColorIndex function takes RGB values instead of buckets. The left shift converts our bucket into its RGB value. + $bucketIndex = self::getColorIndex( + $bucket['r'] << self::RSHIFT, + $bucket['g'] << self::RSHIFT, + $bucket['b'] << self::RSHIFT, + self::SIGBITS + ); + + if (isset($histo[$bucketIndex])) { + $sum += $histo[$bucketIndex]; + } + } + } + $total += $sum; + $partialSum[$firstColor] = $total; + } + + return [$total, $partialSum]; + } + + /** + * @phpstan-param array<'r'|'g'|'b'> $order + * + * @return int[][] + * @phpstan-return array{int[], int[], int[]} + */ + private static function getVBoxColorRanges(VBox $vBox, array $order): array + { + $ranges = [ + 'r' => range($vBox->r1, $vBox->r2), + 'g' => range($vBox->g1, $vBox->g2), + 'b' => range($vBox->b1, $vBox->b2), + ]; + + return [ + $ranges[$order[0]], + $ranges[$order[1]], + $ranges[$order[2]], + ]; + } + + /** + * Inner function to do the iteration. + * + * @param PQueue<VBox> $priorityQueue + * @param array<int, int> $histo + */ + private static function quantizeIter(PQueue &$priorityQueue, float $target, array $histo): void + { + $nColors = $priorityQueue->size(); + $nIterations = 0; + + while ($nIterations < self::MAX_ITERATIONS) { + if ($nColors >= $target) { + return; + } + + if ($nIterations++ > self::MAX_ITERATIONS) { + // echo "infinite loop; perhaps too few pixels!"."\n"; + return; + } + + $vBox = $priorityQueue->pop(); + if (null === $vBox) { + // Logic error: should not happen! + throw new RuntimeException('Failed to pop VBox from an empty queue.'); + } + + if (!$vBox->count()) { /* just put it back */ + $priorityQueue->push($vBox); + ++$nIterations; + continue; + } + // do the cut + $vBoxes = self::medianCutApply($histo, $vBox); + + if (!(\is_array($vBoxes) && isset($vBoxes[0]))) { + // Expect an array of VBox + throw new RuntimeException('Unexpected result from the medianCutApply function.'); + } + + $priorityQueue->push($vBoxes[0]); + + if (isset($vBoxes[1])) { /* vbox2 can be null */ + $priorityQueue->push($vBoxes[1]); + ++$nColors; + } + } + } + + /** + * @param int $numPixels Number of image pixels analyzed + * @param array<int, int> $histo Histogram + * + * @return Color[] + */ + private static function quantize(int $numPixels, int $maxColors, array &$histo): array + { + // Short-Circuits + if (0 === $numPixels) { + throw new InvalidArgumentException('Zero usable pixels found in image.'); + } + if ($maxColors < 2 || $maxColors > 256) { + throw new InvalidArgumentException('The maxColors parameter must be between 2 and 256 inclusive.'); + } + if (0 === \count($histo)) { + throw new InvalidArgumentException('Image produced an empty histogram.'); + } + + // check that we aren't below maxcolors already + //if (count($histo) <= $maxcolors) { + // XXX: generate the new colors from the histo and return + //} + + $vBox = self::vboxFromHistogram($histo); + + /** @var PQueue<VBox> $priorityQueue */ + $priorityQueue = new PQueue(function (VBox $a, VBox $b) { + return $a->count() <=> $b->count(); + }); + $priorityQueue->push($vBox); + + // first set of colors, sorted by population + self::quantizeIter($priorityQueue, self::FRACT_BY_POPULATIONS * $maxColors, $histo); + + // Re-sort by the product of pixel occupancy times the size in color space. + $priorityQueue->setComparator(function (VBox $a, VBox $b) { + return ($a->count() * $a->volume()) <=> ($b->count() * $b->volume()); + }); + + // next set - generate the median cuts using the (npix * vol) sorting. + self::quantizeIter($priorityQueue, $maxColors, $histo); + + // calculate the actual colors + $colors = $priorityQueue->map(function (VBox $vbox) { + return new Color(...$vbox->avg()); + }); + $colors = array_reverse($colors); + + return $colors; + } +} diff --git a/includes/composer/vendor/ksubileau/color-thief-php/src/ColorThief/Exception/Exception.php b/includes/composer/vendor/ksubileau/color-thief-php/src/ColorThief/Exception/Exception.php new file mode 100644 index 0000000..d843f61 --- /dev/null +++ b/includes/composer/vendor/ksubileau/color-thief-php/src/ColorThief/Exception/Exception.php @@ -0,0 +1,21 @@ +<?php + +/* + * This file is part of the Color Thief PHP project. + * + * (c) Kevin Subileau + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace ColorThief\Exception; + +/** + * Base exception marker interface for ColorThief. + */ +interface Exception extends \Throwable +{ +} diff --git a/includes/composer/vendor/ksubileau/color-thief-php/src/ColorThief/Exception/InvalidArgumentException.php b/includes/composer/vendor/ksubileau/color-thief-php/src/ColorThief/Exception/InvalidArgumentException.php new file mode 100644 index 0000000..609daa6 --- /dev/null +++ b/includes/composer/vendor/ksubileau/color-thief-php/src/ColorThief/Exception/InvalidArgumentException.php @@ -0,0 +1,22 @@ +<?php + +/* + * This file is part of the Color Thief PHP project. + * + * (c) Kevin Subileau + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace ColorThief\Exception; + +/** + * Exception thrown if an argument is not of the expected type. + */ +class InvalidArgumentException extends \InvalidArgumentException implements Exception +{ + // nothing to override +} diff --git a/includes/composer/vendor/ksubileau/color-thief-php/src/ColorThief/Exception/NotReadableException.php b/includes/composer/vendor/ksubileau/color-thief-php/src/ColorThief/Exception/NotReadableException.php new file mode 100644 index 0000000..39f1051 --- /dev/null +++ b/includes/composer/vendor/ksubileau/color-thief-php/src/ColorThief/Exception/NotReadableException.php @@ -0,0 +1,22 @@ +<?php + +/* + * This file is part of the Color Thief PHP project. + * + * (c) Kevin Subileau + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace ColorThief\Exception; + +/** + * Exception thrown if an image source is not readable. + */ +class NotReadableException extends \RuntimeException implements Exception +{ + // nothing to override +} diff --git a/includes/composer/vendor/ksubileau/color-thief-php/src/ColorThief/Exception/NotSupportedException.php b/includes/composer/vendor/ksubileau/color-thief-php/src/ColorThief/Exception/NotSupportedException.php new file mode 100644 index 0000000..b25e64c --- /dev/null +++ b/includes/composer/vendor/ksubileau/color-thief-php/src/ColorThief/Exception/NotSupportedException.php @@ -0,0 +1,22 @@ +<?php + +/* + * This file is part of the Color Thief PHP project. + * + * (c) Kevin Subileau + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace ColorThief\Exception; + +/** + * Exception thrown when an operation is not supported. + */ +class NotSupportedException extends \RuntimeException implements Exception +{ + // nothing to override +} diff --git a/includes/composer/vendor/ksubileau/color-thief-php/src/ColorThief/Exception/RuntimeException.php b/includes/composer/vendor/ksubileau/color-thief-php/src/ColorThief/Exception/RuntimeException.php new file mode 100644 index 0000000..ef9427c --- /dev/null +++ b/includes/composer/vendor/ksubileau/color-thief-php/src/ColorThief/Exception/RuntimeException.php @@ -0,0 +1,19 @@ +<?php + +/* + * This file is part of the Color Thief PHP project. + * + * (c) Kevin Subileau + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace ColorThief\Exception; + +class RuntimeException extends \RuntimeException implements Exception +{ + // nothing to override +} diff --git a/includes/composer/vendor/ksubileau/color-thief-php/src/ColorThief/Image/Adapter/AbstractAdapter.php b/includes/composer/vendor/ksubileau/color-thief-php/src/ColorThief/Image/Adapter/AbstractAdapter.php new file mode 100644 index 0000000..17378c9 --- /dev/null +++ b/includes/composer/vendor/ksubileau/color-thief-php/src/ColorThief/Image/Adapter/AbstractAdapter.php @@ -0,0 +1,78 @@ +<?php + +/* + * This file is part of the Color Thief PHP project. + * + * (c) Kevin Subileau + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace ColorThief\Image\Adapter; + +use ColorThief\Exception\NotReadableException; +use ColorThief\Exception\NotSupportedException; + +/** + * Base adapter implementation to handle image manipulation. + */ +abstract class AbstractAdapter implements AdapterInterface +{ + /** + * @var object|resource|null Image resource/object of current image adapter + */ + protected $resource; + + /** + * Creates new instance of the image adapter. + */ + public function __construct() + { + if (!$this->isAvailable()) { + throw new NotSupportedException('Image adapter is not available with this PHP installation. Required extension may be missing.'); + } + } + + public function load($resource): AdapterInterface + { + $this->resource = $resource; + + return $this; + } + + public function loadFromUrl(string $url): AdapterInterface + { + $options = [ + 'http' => [ + 'method' => 'GET', + 'protocol_version' => 1.1, // force use HTTP 1.1 for service mesh environment with envoy + 'header' => [ + 'Accept-language: en', + 'User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:95.0) Gecko/20100101 Firefox/95.0', + ], + ], + ]; + + $context = stream_context_create($options); + + $data = @file_get_contents($url, false, $context); + if (false === $data) { + throw new NotReadableException("Unable to load image from url ({$url})."); + } + + return $this->loadFromBinary($data); + } + + public function destroy(): void + { + $this->resource = null; + } + + public function getResource() + { + return $this->resource; + } +} diff --git a/includes/composer/vendor/ksubileau/color-thief-php/src/ColorThief/Image/Adapter/AdapterInterface.php b/includes/composer/vendor/ksubileau/color-thief-php/src/ColorThief/Image/Adapter/AdapterInterface.php new file mode 100644 index 0000000..7c08229 --- /dev/null +++ b/includes/composer/vendor/ksubileau/color-thief-php/src/ColorThief/Image/Adapter/AdapterInterface.php @@ -0,0 +1,74 @@ +<?php + +/* + * This file is part of the Color Thief PHP project. + * + * (c) Kevin Subileau + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace ColorThief\Image\Adapter; + +/** + * Basic interface for all image adapters. + */ +interface AdapterInterface +{ + /** + * Checks if the image adapter is available. + */ + public static function isAvailable(): bool; + + /** + * Loads an image from path in filesystem. + */ + public function loadFromPath(string $file): self; + + /** + * Loads an image from given URL. + */ + public function loadFromUrl(string $url): self; + + /** + * Loads an image from a binary string representation. + */ + public function loadFromBinary(string $data): self; + + /** + * Loads an image resource. + * + * @param resource|object $resource + */ + public function load($resource): self; + + /** + * Destroys the image. + */ + public function destroy(): void; + + /** + * Returns image height. + */ + public function getHeight(): int; + + /** + * Returns image width. + */ + public function getWidth(): int; + + /** + * Returns the color of the specified pixel. + */ + public function getPixelColor(int $x, int $y): \stdClass; + + /** + * Get the raw resource. + * + * @return resource|object|null + */ + public function getResource(); +} diff --git a/includes/composer/vendor/ksubileau/color-thief-php/src/ColorThief/Image/Adapter/GdAdapter.php b/includes/composer/vendor/ksubileau/color-thief-php/src/ColorThief/Image/Adapter/GdAdapter.php new file mode 100644 index 0000000..e3845d9 --- /dev/null +++ b/includes/composer/vendor/ksubileau/color-thief-php/src/ColorThief/Image/Adapter/GdAdapter.php @@ -0,0 +1,119 @@ +<?php + +/* + * This file is part of the Color Thief PHP project. + * + * (c) Kevin Subileau + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace ColorThief\Image\Adapter; + +use ColorThief\Exception\InvalidArgumentException; +use ColorThief\Exception\NotReadableException; + +/** + * @property resource|\GdImage|null $resource + */ +class GdAdapter extends AbstractAdapter +{ + public static function isAvailable(): bool + { + return extension_loaded('gd') && function_exists('gd_info'); + } + + public function load($resource): AdapterInterface + { + if (version_compare(\PHP_VERSION, '8.0.0') >= 0) { + if (!($resource instanceof \GdImage)) { + throw new InvalidArgumentException('Argument is not an instance of GdImage.'); + } + } else { + if (!\is_resource($resource) || 'gd' != get_resource_type($resource)) { + throw new InvalidArgumentException('Argument is not a valid GD resource.'); + } + } + + return parent::load($resource); + } + + public function loadFromBinary(string $data): AdapterInterface + { + $resource = @imagecreatefromstring($data); + if (false === $resource) { + throw new NotReadableException('Unable to read image from binary data.'); + } + + return parent::load($resource); + } + + public function loadFromPath(string $file): AdapterInterface + { + if (!is_readable($file)) { + throw new NotReadableException("Unable to read image from path ({$file})."); + } + + [, , $type] = @getimagesize($file); + + switch ($type) { + case \IMAGETYPE_GIF: + $resource = @imagecreatefromgif($file); + break; + + case \IMAGETYPE_JPEG: + $resource = @imagecreatefromjpeg($file); + break; + + case \IMAGETYPE_PNG: + $resource = @imagecreatefrompng($file); + break; + + case \IMAGETYPE_WEBP: + if (!function_exists('imagecreatefromwebp')) { + throw new NotReadableException('Unsupported image type. GD/PHP installation does not support WebP format.'); + } + $resource = @imagecreatefromwebp($file); + break; + + default: + throw new NotReadableException('Unsupported image type for GD image adapter.'); + } + + if (false === $resource) { + throw new NotReadableException("Unable to decode image from file ({$file})."); + } + + return parent::load($resource); + } + + public function destroy(): void + { + if ($this->resource) { + // For PHP 7.x only, noop starting from PHP 8.0 + imagedestroy($this->resource); + } + parent::destroy(); + } + + public function getHeight(): int + { + return imagesy($this->resource); + } + + public function getWidth(): int + { + return imagesx($this->resource); + } + + public function getPixelColor(int $x, int $y): \stdClass + { + $rgba = imagecolorat($this->resource, $x, $y); + $color = imagecolorsforindex($this->resource, $rgba); + + return (object) $color; + } +} diff --git a/includes/composer/vendor/ksubileau/color-thief-php/src/ColorThief/Image/Adapter/GmagickAdapter.php b/includes/composer/vendor/ksubileau/color-thief-php/src/ColorThief/Image/Adapter/GmagickAdapter.php new file mode 100644 index 0000000..ca7063e --- /dev/null +++ b/includes/composer/vendor/ksubileau/color-thief-php/src/ColorThief/Image/Adapter/GmagickAdapter.php @@ -0,0 +1,105 @@ +<?php + +/* + * This file is part of the Color Thief PHP project. + * + * (c) Kevin Subileau + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace ColorThief\Image\Adapter; + +use ColorThief\Exception\InvalidArgumentException; +use ColorThief\Exception\NotReadableException; +use Gmagick; + +/** + * @property ?Gmagick $resource + */ +class GmagickAdapter extends AbstractAdapter +{ + public static function isAvailable(): bool + { + return extension_loaded('gmagick') && class_exists('Gmagick'); + } + + public function load($resource): AdapterInterface + { + if (!($resource instanceof Gmagick)) { + throw new InvalidArgumentException('Argument is not an instance of Gmagick.'); + } + + if (Gmagick::COLORSPACE_CMYK == $resource->getImageColorSpace()) { + // Leave original object unmodified + $resource = clone $resource; + $resource->setImageColorspace(Gmagick::COLORSPACE_RGB); + } + + return parent::load($resource); + } + + public function loadFromBinary(string $data): AdapterInterface + { + $resource = new Gmagick(); + try { + $resource->readImageBlob($data); + } catch (\GmagickException $e) { + throw new NotReadableException('Unable to read image from binary data.', 0, $e); + } + + return $this->load($resource); + } + + public function loadFromPath(string $file): AdapterInterface + { + $resource = null; + try { + $resource = new Gmagick($file); + } catch (\GmagickException $e) { + throw new NotReadableException("Unable to read image from path ({$file}).", 0, $e); + } + + return $this->load($resource); + } + + public function destroy(): void + { + if ($this->resource) { + $this->resource->clear(); + $this->resource->destroy(); + } + parent::destroy(); + } + + public function getHeight(): int + { + return $this->resource->getimageheight(); + } + + public function getWidth(): int + { + return $this->resource->getimagewidth(); + } + + public function getPixelColor(int $x, int $y): \stdClass + { + $cropped = clone $this->resource; // No need to modify the original object. + $histogram = $cropped->cropImage(1, 1, $x, $y)->getImageHistogram(); + $pixel = array_shift($histogram); + + // Un-normalized values don't give a full range 0-1 alpha channel + // So we ask for normalized values, and then we un-normalize it ourselves. + $colorArray = $pixel->getColor(true, true); + $color = new \stdClass(); + $color->red = (int) round($colorArray['r'] * 255); + $color->green = (int) round($colorArray['g'] * 255); + $color->blue = (int) round($colorArray['b'] * 255); + $color->alpha = (int) round($pixel->getcolorvalue(\Gmagick::COLOR_OPACITY) * 127); + + return $color; + } +} diff --git a/includes/composer/vendor/ksubileau/color-thief-php/src/ColorThief/Image/Adapter/ImagickAdapter.php b/includes/composer/vendor/ksubileau/color-thief-php/src/ColorThief/Image/Adapter/ImagickAdapter.php new file mode 100644 index 0000000..2ca15a9 --- /dev/null +++ b/includes/composer/vendor/ksubileau/color-thief-php/src/ColorThief/Image/Adapter/ImagickAdapter.php @@ -0,0 +1,116 @@ +<?php + +/* + * This file is part of the Color Thief PHP project. + * + * (c) Kevin Subileau + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace ColorThief\Image\Adapter; + +use ColorThief\Exception\InvalidArgumentException; +use ColorThief\Exception\NotReadableException; +use ColorThief\Exception\NotSupportedException; +use Imagick; + +/** + * @property ?Imagick $resource + */ +class ImagickAdapter extends AbstractAdapter +{ + public static function isAvailable(): bool + { + return extension_loaded('imagick') && class_exists('Imagick'); + } + + public function load($resource): AdapterInterface + { + if (!($resource instanceof Imagick)) { + throw new InvalidArgumentException('Argument is not an instance of Imagick.'); + } + + if (Imagick::COLORSPACE_CMYK == $resource->getImageColorspace()) { + // Leave original object unmodified + $resource = clone $resource; + + $imagickVersion = phpversion('imagick'); + if ($imagickVersion && version_compare($imagickVersion, '3.0.0', '<')) { + throw new NotSupportedException('Imagick extension version 3.0.0 or later is required for sampling CMYK images.'); + } + + // With ImageMagick version 6.7.7, CMYK images converted to RGB color space work as expected, + // but for later versions (6.9.7 and 7.0.8 have been tested), conversion to SRGB seems to be required + $imageMagickVersion = $resource->getVersion(); + if ($imageMagickVersion['versionNumber'] > 1655) { + $resource->transformImageColorspace(Imagick::COLORSPACE_SRGB); + } else { + $resource->transformImageColorspace(Imagick::COLORSPACE_RGB); + } + } + + return parent::load($resource); + } + + public function loadFromBinary(string $data): AdapterInterface + { + $resource = new Imagick(); + try { + $resource->readImageBlob($data); + } catch (\ImagickException $e) { + throw new NotReadableException('Unable to read image from binary data.', 0, $e); + } + + return $this->load($resource); + } + + public function loadFromPath(string $file): AdapterInterface + { + try { + $resource = new Imagick($file); + } catch (\ImagickException $e) { + throw new NotReadableException("Unable to read image from path ({$file}).", 0, $e); + } + + return $this->load($resource); + } + + public function destroy(): void + { + if ($this->resource) { + $this->resource->clear(); + } + parent::destroy(); + } + + public function getHeight(): int + { + return $this->resource->getImageHeight(); + } + + public function getWidth(): int + { + return $this->resource->getImageWidth(); + } + + public function getPixelColor(int $x, int $y): \stdClass + { + /** @var \ImagickPixel $pixel */ + $pixel = $this->resource->getImagePixelColor($x, $y); + + // Un-normalized values don't give a full range 0-1 alpha channel + // So we ask for normalized values, and then we un-normalize it ourselves. + $colorArray = $pixel->getColor(1); + $color = new \stdClass(); + $color->red = (int) round($colorArray['r'] * 255); + $color->green = (int) round($colorArray['g'] * 255); + $color->blue = (int) round($colorArray['b'] * 255); + $color->alpha = (int) (127 - round($colorArray['a'] * 127)); + + return $color; + } +} diff --git a/includes/composer/vendor/ksubileau/color-thief-php/src/ColorThief/Image/ImageLoader.php b/includes/composer/vendor/ksubileau/color-thief-php/src/ColorThief/Image/ImageLoader.php new file mode 100644 index 0000000..9e82784 --- /dev/null +++ b/includes/composer/vendor/ksubileau/color-thief-php/src/ColorThief/Image/ImageLoader.php @@ -0,0 +1,195 @@ +<?php + +/* + * This file is part of the Color Thief PHP project. + * + * (c) Kevin Subileau + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace ColorThief\Image; + +use ColorThief\Exception\NotReadableException; +use ColorThief\Exception\NotSupportedException; +use ColorThief\Image\Adapter\AdapterInterface; + +class ImageLoader +{ + /** + * @var AdapterInterface|string|null + */ + private $preferredAdapter = null; + + /** + * Configure the preferred adapter to use to load images. + * + * @param string|AdapterInterface|null $adapter Name of the preferred adapter or adapter instance. + * If null, the adapter is automatically chosen according to the + * available extensions. + */ + public function setPreferredAdapter($adapter): self + { + $this->preferredAdapter = $adapter; + + return $this; + } + + /** + * @param mixed $source Path/URL to the image, GD resource, Imagick/Gmagick instance, or image as binary string + */ + public function load($source): AdapterInterface + { + $image = null; + + $preferredAdapter = $this->preferredAdapter; + // Select appropriate adapter depending on source type if no preference given + if (null === $preferredAdapter) { + if ($this->isGdImage($source)) { + $preferredAdapter = 'Gd'; + } elseif ($this->isImagick($source)) { + $preferredAdapter = 'Imagick'; + } elseif ($this->isGmagick($source)) { + $preferredAdapter = 'Gmagick'; + } + } + + $image = $this->createAdapter($preferredAdapter); + + switch (true) { + case $this->isGdImage($source): + case $this->isImagick($source): + case $this->isGmagick($source): + return $image->load($source); + case $this->isBinary($source): + return $image->loadFromBinary($source); + case $this->isUrl($source): + return $image->loadFromUrl($source); + case $this->isFilePath($source): + return $image->loadFromPath($source); + default: + throw new NotReadableException('Image source does not exists or is not readable.'); + } + } + + /** + * Creates an adapter instance according to config settings. + * + * @param string|AdapterInterface|null $preferredAdapter + */ + public function createAdapter($preferredAdapter = null): AdapterInterface + { + if (null === $preferredAdapter) { + // Select first available adapter + if (\ColorThief\Image\Adapter\ImagickAdapter::isAvailable()) { + $preferredAdapter = 'Imagick'; + } elseif (\ColorThief\Image\Adapter\GmagickAdapter::isAvailable()) { + $preferredAdapter = 'Gmagick'; + } elseif (\ColorThief\Image\Adapter\GdAdapter::isAvailable()) { + $preferredAdapter = 'Gd'; + } else { + throw new NotSupportedException('At least one of GD, Imagick or Gmagick extension must be installed. None of them was found.'); + } + } + + if (is_string($preferredAdapter)) { + $adapterName = ucfirst($preferredAdapter); + $adapterClass = sprintf('\\ColorThief\\Image\\Adapter\\%sAdapter', $adapterName); + + if (!class_exists($adapterClass)) { + throw new NotSupportedException("Image adapter ({$adapterName}) could not be instantiated."); + } + + return new $adapterClass(); + } + + if ($preferredAdapter instanceof AdapterInterface) { + return $preferredAdapter; + } + + throw new NotSupportedException('Unknown image adapter type.'); + } + + /** + * Determines if given source data is a GD image. + * + * @param mixed $data + */ + public function isGdImage($data): bool + { + if (version_compare(\PHP_VERSION, '8.0.0') >= 0) { + return $data instanceof \GdImage; + } + + return \is_resource($data) && 'gd' == get_resource_type($data); + } + + /** + * Determines if given source data is an Imagick object. + * + * @param mixed $data + */ + public function isImagick($data): bool + { + return is_a($data, 'Imagick'); + } + + /** + * Determines if given source data is a Gmagick object. + * + * @param mixed $data + */ + public function isGmagick($data): bool + { + return is_a($data, 'Gmagick'); + } + + /** + * Determines if given source data is file path. + * + * @param mixed $data + */ + public function isFilePath($data): bool + { + if (is_string($data)) { + try { + return is_file($data); + } catch (\Exception $e) { + return false; + } + } + + return false; + } + + /** + * Determines if given source data is url. + * + * @param mixed $data + */ + public function isUrl($data): bool + { + return (bool) filter_var($data, FILTER_VALIDATE_URL); + } + + /** + * Determines if given source data is binary data. + * + * @param mixed $data + */ + public function isBinary($data): bool + { + if (is_string($data)) { + $finfo = finfo_open(FILEINFO_MIME_TYPE); + $mime = finfo_buffer($finfo, $data); + finfo_close($finfo); + + return 'text' != substr($mime, 0, 4) && 'application/x-empty' != $mime; + } + + return false; + } +} diff --git a/includes/composer/vendor/ksubileau/color-thief-php/src/ColorThief/PQueue.php b/includes/composer/vendor/ksubileau/color-thief-php/src/ColorThief/PQueue.php new file mode 100644 index 0000000..21981a6 --- /dev/null +++ b/includes/composer/vendor/ksubileau/color-thief-php/src/ColorThief/PQueue.php @@ -0,0 +1,129 @@ +<?php + +/* + * This file is part of the Color Thief PHP project. + * + * (c) Kevin Subileau + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace ColorThief; + +/** + * Simple priority queue. + * + * @phpstan-template T + */ +class PQueue +{ + /** + * @var array + * @phpstan-var array<T> + */ + private $contents = []; + + /** @var bool */ + private $sorted = false; + + /** + * @var callable + * @phpstan-var callable(T, T): int + */ + private $comparator = null; + + public function __construct(callable $comparator) + { + $this->setComparator($comparator); + } + + private function sort(): void + { + usort($this->contents, $this->comparator); + $this->sorted = true; + } + + /** + * @param mixed $object + * @phpstan-param T $object + */ + public function push($object): void + { + $this->contents[] = $object; + $this->sorted = false; + } + + /** + * @return mixed + * @phpstan-return T + */ + public function peek(?int $index = null) + { + if (!$this->sorted) { + $this->sort(); + } + + if (null === $index) { + $index = $this->size() - 1; + } + + return $this->contents[$index]; + } + + /** + * @return mixed|null + * @phpstan-return T|null + */ + public function pop() + { + if (!$this->sorted) { + $this->sort(); + } + + return array_pop($this->contents); + } + + public function size(): int + { + return \count($this->contents); + } + + /** + * @phpstan-template R + * @phpstan-param callable(T): R $function + * @phpstan-return array<R> + */ + public function map(callable $function, bool $sorted = true): array + { + if ($sorted && !$this->sorted) { + $this->sort(); + } + + return array_map($function, $this->contents); + } + + /** + * @phpstan-param callable(T, T): int $function + */ + public function setComparator(callable $function): void + { + $this->comparator = $function; + $this->sorted = false; + } + + /** + * @return array<T> + * @phpstan-return array<T> + */ + public function getContent(bool $sorted = true) + { + if ($sorted && !$this->sorted) { + $this->sort(); + } + + return $this->contents; + } +} diff --git a/includes/composer/vendor/ksubileau/color-thief-php/src/ColorThief/VBox.php b/includes/composer/vendor/ksubileau/color-thief-php/src/ColorThief/VBox.php new file mode 100644 index 0000000..d8746ee --- /dev/null +++ b/includes/composer/vendor/ksubileau/color-thief-php/src/ColorThief/VBox.php @@ -0,0 +1,219 @@ +<?php + +/* + * This file is part of the Color Thief PHP project. + * + * (c) Kevin Subileau + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace ColorThief; + +class VBox +{ + /** @var int */ + public $r1; + /** @var int */ + public $r2; + /** @var int */ + public $g1; + /** @var int */ + public $g2; + /** @var int */ + public $b1; + /** @var int */ + public $b2; + + /** @var array<int, int> */ + public $histo; + + /** @var int */ + private $volume; + /** @var bool */ + private $volume_set = false; + + /** @var int */ + private $count; + /** @var bool */ + private $count_set = false; + + /** + * @var array + * @phpstan-var ColorRGB + */ + private $avg; + /** @var bool */ + private $avg_set = false; + + /** + * VBox constructor. + * + * @param array<int, int> $histo + */ + public function __construct(int $r1, int $r2, int $g1, int $g2, int $b1, int $b2, array $histo) + { + $this->r1 = $r1; + $this->r2 = $r2; + $this->g1 = $g1; + $this->g2 = $g2; + $this->b1 = $b1; + $this->b2 = $b2; + $this->histo = $histo; + } + + public function volume(bool $force = false): int + { + if (true !== $this->volume_set || $force) { + $this->volume = (($this->r2 - $this->r1 + 1) * ($this->g2 - $this->g1 + 1) * ($this->b2 - $this->b1 + 1)); + $this->volume_set = true; + } + + return $this->volume; + } + + public function count(bool $force = false): int + { + if (true !== $this->count_set || $force) { + $npix = 0; + + // Select the fastest way (i.e. with the fewest iterations) to count + // the number of pixels contained in this vbox. + if ($this->volume() > \count($this->histo)) { + // Iterate over the histogram if the size of this histogram is lower than the vbox volume + foreach ($this->histo as $bucketIndex => $count) { + $rgbBuckets = ColorThief::getColorsFromIndex($bucketIndex, ColorThief::SIGBITS); + if ($this->contains($rgbBuckets, 0)) { + $npix += $count; + } + } + } else { + // Or iterate over points of the vbox if the size of the histogram is greater than the vbox volume + for ($redBucket = $this->r1; $redBucket <= $this->r2; ++$redBucket) { + for ($greenBucket = $this->g1; $greenBucket <= $this->g2; ++$greenBucket) { + for ($blueBucket = $this->b1; $blueBucket <= $this->b2; ++$blueBucket) { + // The getColorIndex function takes RGB values instead of buckets. The left shift converts our bucket into its RGB value. + $bucketIndex = ColorThief::getColorIndex( + $redBucket << ColorThief::RSHIFT, + $greenBucket << ColorThief::RSHIFT, + $blueBucket << ColorThief::RSHIFT, + ColorThief::SIGBITS + ); + if (isset($this->histo[$bucketIndex])) { + $npix += $this->histo[$bucketIndex]; + } + } + } + } + } + $this->count = $npix; + $this->count_set = true; + } + + return $this->count; + } + + public function copy(): self + { + return new self($this->r1, $this->r2, $this->g1, $this->g2, $this->b1, $this->b2, $this->histo); + } + + /** + * Calculates the average color represented by this VBox. + * + * @phpstan-return ColorRGB + */ + public function avg(bool $force = false): array + { + if (true !== $this->avg_set || $force) { + $ntot = 0; + $mult = 1 << ColorThief::RSHIFT; + $rsum = 0; + $gsum = 0; + $bsum = 0; + + for ($redBucket = $this->r1; $redBucket <= $this->r2; ++$redBucket) { + for ($greenBucket = $this->g1; $greenBucket <= $this->g2; ++$greenBucket) { + for ($blueBucket = $this->b1; $blueBucket <= $this->b2; ++$blueBucket) { + // getColorIndex takes RGB values instead of buckets, so left shift so we get a bucketIndex + $bucketIndex = ColorThief::getColorIndex( + $redBucket << ColorThief::RSHIFT, + $greenBucket << ColorThief::RSHIFT, + $blueBucket << ColorThief::RSHIFT, + ColorThief::SIGBITS + ); + + // The bucket values need to be multiplied by $mult to get the RGB values. + // Can't use a left shift here, as we're working with a floating point number to put the value at the bucket's midpoint. + $hval = $this->histo[$bucketIndex] ?? 0; + $ntot += $hval; + $rsum += ($hval * ($redBucket + 0.5) * $mult); + $gsum += ($hval * ($greenBucket + 0.5) * $mult); + $bsum += ($hval * ($blueBucket + 0.5) * $mult); + } + } + } + + if ($ntot) { + $this->avg = [ + (int) ($rsum / $ntot), + (int) ($gsum / $ntot), + (int) ($bsum / $ntot), + ]; + } else { + // echo 'empty box'."\n"; + $this->avg = [ + (int) ($mult * ($this->r1 + $this->r2 + 1) / 2), + (int) ($mult * ($this->g1 + $this->g2 + 1) / 2), + (int) ($mult * ($this->b1 + $this->b2 + 1) / 2), + ]; + + // Ensure all channel values are leather or equal 255 (Issue #24) + $this->avg = array_map(function ($val) { + return min($val, 255); + }, $this->avg); + } + + $this->avg_set = true; + } + + return $this->avg; + } + + /** + * @phpstan-param ColorRGB $rgbValue + */ + public function contains(array $rgbValue, int $rshift = ColorThief::RSHIFT): bool + { + // Get the buckets from the RGB values. + $redBucket = $rgbValue[0] >> $rshift; + $greenBucket = $rgbValue[1] >> $rshift; + $blueBucket = $rgbValue[2] >> $rshift; + + return + $redBucket >= $this->r1 && + $redBucket <= $this->r2 && + $greenBucket >= $this->g1 && + $greenBucket <= $this->g2 && + $blueBucket >= $this->b1 && + $blueBucket <= $this->b2; + } + + /** + * Determines the longest axis. + * + * @phpstan-return 'r'|'g'|'b' + */ + public function longestAxis(): string + { + // Color-Width for RGB + $red = $this->r2 - $this->r1; + $green = $this->g2 - $this->g1; + $blue = $this->b2 - $this->b1; + + return $red >= $green && $red >= $blue ? 'r' : ($green >= $red && $green >= $blue ? 'g' : 'b'); + } +} |