From 2c67fdfe86a1e7cb153b7c03fd1bdb234a300ea4 Mon Sep 17 00:00:00 2001 From: Gruven Date: Tue, 25 Aug 2026 13:08:42 +0500 Subject: [PATCH] Restore PHP 8.1 randomizer compatibility --- composer.json | 1 + src/Analyzers/DominantPaletteAnalyzer.php | 34 +++++- src/Colors/ColorExtractor.php | 2 + src/Random/GammaSection.php | 58 ++++++++++ tests/Unit/GammaSectionTest.php | 125 ++++++++++++++++++++++ 5 files changed, 219 insertions(+), 1 deletion(-) create mode 100644 src/Random/GammaSection.php create mode 100644 tests/Unit/GammaSectionTest.php diff --git a/composer.json b/composer.json index 45063beba..100db5bba 100644 --- a/composer.json +++ b/composer.json @@ -25,6 +25,7 @@ "require": { "php": "^8.1", "ext-mbstring": "*", + "arokettu/random-polyfill": "^1.0.6", "openregion/gif": "^5.0" }, "replace": { diff --git a/src/Analyzers/DominantPaletteAnalyzer.php b/src/Analyzers/DominantPaletteAnalyzer.php index d71a1919a..238c30304 100644 --- a/src/Analyzers/DominantPaletteAnalyzer.php +++ b/src/Analyzers/DominantPaletteAnalyzer.php @@ -14,7 +14,9 @@ use Intervention\Image\Interfaces\ImageInterface; use Intervention\Image\Interfaces\PaletteInterface; use Intervention\Image\Interfaces\SizeInterface; +use Intervention\Image\Random\GammaSection; use Random\Engine\Mt19937; +use Random\RandomException; use Random\Randomizer; class DominantPaletteAnalyzer extends AbstractPaletteAnalyzer @@ -48,6 +50,7 @@ class DominantPaletteAnalyzer extends AbstractPaletteAnalyzer * Create new instance. * * @throws InvalidArgumentException + * @throws RandomException */ public function __construct(protected int $limit = 8, protected ?SizeInterface $region = null) { @@ -63,6 +66,7 @@ public function __construct(protected int $limit = 8, protected ?SizeInterface $ * * @throws InvalidArgumentException * @throws AnalyzerException + * @throws RandomException */ public function analyze(ImageInterface $image): PaletteInterface { @@ -213,7 +217,7 @@ private function initializeCentroids(array $points, int $k): array } // choose next centroid with weighted probability (deterministic with seed) - $target = $this->rng->getFloat(0, $sumDistances); + $target = $this->randomFloat($sumDistances); $cumulative = 0.0; $chosenIndex = 0; @@ -334,8 +338,36 @@ private function squaredDistance(array $point1, array $point2): float return $dl * $dl + $da * $da + $db * $db; } + /** + * Generate a random float in the right-open interval from zero to the given maximum. + */ + private function randomFloat(float $max): float + { + $native = self::nativeFloat($this->rng, $max); + + return $native ?? GammaSection::closedOpen($this->rng, $max); + } + + /** + * Use the native method when it is available in the current PHP runtime. + * + * The generic object type bridges the different Randomizer APIs exposed by PHP 8.1-8.3. + */ + private static function nativeFloat(object $randomizer, float $max): ?float + { + if (!method_exists($randomizer, 'getFloat')) { + return null; + } + + $result = $randomizer->getFloat(0, $max); + + return is_float($result) ? $result : null; + } + /** * Re-seed local RNG. + * + * @throws RandomException */ private function randomize(): void { diff --git a/src/Colors/ColorExtractor.php b/src/Colors/ColorExtractor.php index 4e5a3c222..14606dccb 100644 --- a/src/Colors/ColorExtractor.php +++ b/src/Colors/ColorExtractor.php @@ -12,6 +12,7 @@ use Intervention\Image\Interfaces\SizeInterface; use Intervention\Image\Interfaces\ThemeDefinitionInterface; use Intervention\Image\Interfaces\ThemeInterface; +use Random\RandomException; class ColorExtractor { @@ -37,6 +38,7 @@ public function popular(int $limit = 256, ?SizeInterface $region = null): Palett * Extract the visually dominant colors in the image, starting with the most dominant ones. * * @throws InvalidArgumentException + * @throws RandomException */ public function dominant(int $limit = 8, ?SizeInterface $region = null): PaletteInterface { diff --git a/src/Random/GammaSection.php b/src/Random/GammaSection.php new file mode 100644 index 000000000..a64612da6 --- /dev/null +++ b/src/Random/GammaSection.php @@ -0,0 +1,58 @@ +getInt(0, $steps - 1); + + if ($step === $steps) { + return 0.0; + } + + $stepHigh = intdiv($step, 4); + $stepLow = $step & 0x3; + + return 4.0 * ($max * 0.25 - $stepHigh * $gamma) - $stepLow * $gamma; + } + + /** + * Return the preceding representable floating-point value. + */ + private static function previousFloat(float $value): float + { + $bytes = pack('E', $value); + + for ($index = strlen($bytes) - 1; $index >= 0; $index--) { + $byte = ord($bytes[$index]); + + if ($byte > 0) { + $bytes[$index] = chr($byte - 1); + + break; + } + + $bytes[$index] = "\xff"; + } + + return unpack('Evalue', $bytes)['value']; + } +} diff --git a/tests/Unit/GammaSectionTest.php b/tests/Unit/GammaSectionTest.php new file mode 100644 index 000000000..e2899d698 --- /dev/null +++ b/tests/Unit/GammaSectionTest.php @@ -0,0 +1,125 @@ + $expected + */ + #[DataProvider('nativeSequenceProvider')] + public function testClosedOpenMatchesNativeSequence(float $max, array $expected): void + { + $randomizer = new Randomizer(new Mt19937(1024)); + $actual = []; + + for ($index = 0; $index < count($expected); $index++) { + $actual[] = GammaSection::closedOpen($randomizer, $max); + } + + $this->assertSame($expected, $actual); + } + + /** + * Values captured from Randomizer::getFloat() on PHP 8.3.30. + * + * @return Generator}> + */ + public static function nativeSequenceProvider(): Generator + { + yield 'binary fraction' => [ + 0.125, + [ + 0.081488511103914141, + 0.072114169781590992, + 0.0077766466854058136, + 0.0045041645698113697, + 0.0071696878037766087, + 0.10223849099473176, + 0.0086961307378339153, + 0.076955139391593805, + ], + ]; + + yield 'fraction' => [ + 0.123456789, + [ + 0.095376409103911791, + 0.072112531781587108, + 0.12197316868540342, + 0.023021331569808165, + 0.10439161680377591, + 0.10069391499472852, + 0.044187799737828798, + 0.044547708391593749, + ], + ]; + + yield 'integer' => [ + 1.0, + [ + 0.65190808883131313, + 0.57691335825272794, + 0.062213173483246509, + 0.036033316558490958, + 0.05735750243021287, + 0.81790792795785405, + 0.069569045902671323, + 0.61564111513275044, + ], + ]; + + yield 'large fraction' => [ + 1234.56789, + [ + 243.14092592657835, + 209.63987770166796, + 414.2108792937388, + 383.10742231185645, + 941.95719497709047, + 51.744426457752752, + 271.6272360087778, + 228.14126379187405, + ], + ]; + + yield 'minimum normal' => [ + PHP_FLOAT_MIN, + [ + 6.760134347086892e-309, + 3.422758056362881e-309, + 2.7685781194469043e-309, + 1.6035358141922581e-309, + 2.5524935849345986e-309, + 1.4147372398224234e-308, + 3.0959253079864298e-309, + 5.1462004450100907e-309, + ], + ]; + + yield 'maximum' => [ + PHP_FLOAT_MAX, + [ + 1.1719306958530906e+308, + 1.0371131835410054e+308, + 1.1184019486865369e+307, + 6.47768458032827e+306, + 1.0311118835159098e+307, + 1.4703474670390562e+308, + 1.2506379621777495e+307, + 1.1067338062131219e+308, + ], + ]; + } +}