diff --git a/src/Imgix/UrlBuilder.php b/src/Imgix/UrlBuilder.php index e99f9d8..09bcf36 100644 --- a/src/Imgix/UrlBuilder.php +++ b/src/Imgix/UrlBuilder.php @@ -9,7 +9,7 @@ class UrlBuilder { private $useHttps; private $signKey; - const TARGETWIDTHS = array( + const TARGET_WIDTHS = array( 100, 116, 134, 156, 182, 210, 244, 282, 328, 380, 442, 512, 594, 688, 798, 926, 1074, 1246, 1446, 1678, 1946, 2258, 2618, @@ -17,9 +17,13 @@ class UrlBuilder { // define class constants // should be private; but visibility modifiers are not supported php version <7.1 - const TARGETRATIOS = array(1, 2, 3, 4, 5); + const TARGET_RATIOS = array(1, 2, 3, 4, 5); - const DPRQUALITIES = array(1 => 75, 2 => 50, 3 => 35, 4 => 23, 5 => 20); + const DPR_QUALITIES = array(1 => 75, 2 => 50, 3 => 35, 4 => 23, 5 => 20); + + const MIN_WIDTH = 100; + const MAX_WIDTH = 8192; + const SRCSET_WIDTH_TOLERANCE = 8; // constants cannot be dynamically assigned; keeping as a class variable instead private $targetWidths; @@ -74,20 +78,28 @@ public function createURL($path, $params=array()) { return $uh->getURL(); } - public function createSrcSet($path, $params=array(), $start=100, $stop=8192, $tol=8, $disableVariableQuality=false) { - $width = array_key_exists('w', $params) ? $params['w'] : NULL; - $height = array_key_exists('h', $params) ? $params['h'] : NULL; - $aspectRatio = array_key_exists('ar', $params) ? $params['ar'] : NULL; + public function createSrcSet($path, $params=array(), $options=array()) { + $widthsArray = isset($options['widths']) ? $options['widths'] : NULL; + + if (isset($widthsArray)) { + return $this->createSrcSetPairs($path, $params=$params, $widthsArray); + } + + $_start = isset($options['start']) ? $options['start'] : self::MIN_WIDTH; + $_stop = isset($options['stop']) ? $options['stop'] : self::MAX_WIDTH; + $_tol = isset($options['tol']) ? $options['tol'] : self::SRCSET_WIDTH_TOLERANCE; + $_disableVariableQuality = isset($options['disableVariableQuality']) + ? $options['disableVariableQuality'] : false; - if (($width) || ($height && $aspectRatio)) { + if ($this->isDpr($params)) { return $this->createDPRSrcSet( $path=$path, $params=$params, - $disableVariableQuality=$disableVariableQuality); + $disableVariableQuality=$_disableVariableQuality); } else { - $targets = $this->targetWidths($start=$start, $stop=$stop, $tol=$tol); - return $this->createSrcSetPairs($path, $params, $targets=$targets); + $targets = $this->targetWidths($start=$_start, $stop=$_stop, $tol=$_tol); + return $this->createSrcSetPairs($path, $params=$params, $targets=$targets); } } @@ -98,52 +110,72 @@ public function createSrcSet($path, $params=array(), $start=100, $stop=8192, $to * width-describe image candidate strings (URLs) within a * srcset attribute. * - * This function returns an array of even integer values that - * denote image target widths. This array begins with `$start` + * This function returns an array of integer values that denote + * image target widths. This array begins with `$start` * and ends with `$stop`. The `$tol` or tolerance value dictates - * how many values lie between `$start` and `$stop`. + * the amount of tolerable width variation between each width + * in the range of values that lie between `$start` and `$stop`. * * @param int $start Starting minimum width value. * @param int $stop Stopping maximum width value. * @param int $tol Tolerable amount of width variation. - * @return int[] $resolutions An array of even integer values. + * @return int[] $resolutions An array of integer values. */ - public function targetWidths($start=100, $stop=8192, $tol=8) { + public function targetWidths( + $start=self::MIN_WIDTH, + $stop=self::MAX_WIDTH, + $tol=self::SRCSET_WIDTH_TOLERANCE) { + if ($start == $stop) { - return array($start); + return array((int) $start); } $resolutions = array(); - while ($start < $stop && $start < 8192) { - array_push($resolutions, round($start)); + while ($start < $stop && $start < self::MAX_WIDTH) { + array_push($resolutions, (int) round($start)); $start *= 1 + ($tol / 100.0) * 2; } + // The most recently appended value may, or may not, be + // the `stop` value. In order to be inclusive of the + // stop value, check for this case and add it, if necessary. if (end($resolutions) < $stop) { - array_push($resolutions, $stop); + array_push($resolutions, (int) $stop); } return $resolutions; } - private function createDPRSrcSet( - $path, - $params, - $targets=self::TARGETRATIOS, - $disableVariableQuality=false) { - + private function isDpr($params) { + if (empty($params)) { + // If the params array is empty, then + // it is _not_ dpr based. + return false; + } + + $hasWidth = array_key_exists('w', $params) ? $params['w'] : NULL; + $hasHeight = array_key_exists('h', $params) ? $params['h'] : NULL; + $hasAspectRatio = array_key_exists('ar', $params) ? $params['ar'] : NULL; + + // If `params` have a width param or _both_ height and aspect + // ratio parameters then the srcset to be constructed with + // these params _is dpr based + return $hasWidth || ($hasHeight && $hasAspectRatio); + } + + private function createDPRSrcSet($path, $params, $disableVariableQuality=false) { $srcset = ""; - $size = count(self::TARGETRATIOS); + $size = count(self::TARGET_RATIOS); for ($i = 0; $i < $size; $i++) { $currentParams = $params; $currentParams['dpr'] = $i+1; - $currentRatio = self::TARGETRATIOS[$i]; + $currentRatio = self::TARGET_RATIOS[$i]; // If variable quality output has been disabled _and_ // the `q` param _has not_ been passed: if (!$disableVariableQuality && !isset($params["q"])) { - $currentParams["q"] = self::DPRQUALITIES[$i + 1]; + $currentParams["q"] = self::DPR_QUALITIES[$i + 1]; } $srcset .= $this->createURL($path, $currentParams) . " " . $currentRatio . "x,\n"; } @@ -151,7 +183,7 @@ private function createDPRSrcSet( return substr($srcset, 0, strlen($srcset) - 2); } - private function createSrcSetPairs($path, $params, $targets=self::TARGETWIDTHS) { + private function createSrcSetPairs($path, $params, $targets=self::TARGET_WIDTHS) { $srcset = ""; $currentWidth = NULL; $currentParams = NULL; diff --git a/tests/Imgix/Tests/UrlBuilderTest.php b/tests/Imgix/Tests/UrlBuilderTest.php index edba595..61e97bc 100644 --- a/tests/Imgix/Tests/UrlBuilderTest.php +++ b/tests/Imgix/Tests/UrlBuilderTest.php @@ -4,7 +4,8 @@ class UrlBuilderTest extends \PHPUnit\Framework\TestCase { - const TARGETWIDTHS = array( + + const TARGET_WIDTHS = array( 100, 116, 135, 156, 181, 210, 244, 283, 328, 380, 441, 512, 594, 689, 799, 927, 1075, 1247, 1446, 1678, 1946, 2257, 2619, @@ -169,10 +170,10 @@ private function parseWidth($width) { return (int)substr($width, 0, strlen($width)-1); } - public function testSrcsetCustomTargetWidths100to7400() { + public function testSrcsetCustomTargetWidths100to7401() { $builder = new UrlBuilder("demos.imgix.net", true, "my-key", false); $expected = $builder->targetWidths($start=100, $stop=7401); - $actual = array_slice(self::TARGETWIDTHS, 0, -1); + $actual = array_slice(self::TARGET_WIDTHS, 0, -1); $this->assertEquals(count($expected), count($actual)); $this->assertEquals($expected, $actual); } @@ -187,7 +188,7 @@ public function testSrcsetCustomTargetWidths328to4088() { $idx_328 = 8; $idx_4087 = 18; $expected = $builder->targetWidths($start=$start, $stop=$stop); - $actual = array_slice(self::TARGETWIDTHS, $idx_328, $idx_4087); + $actual = array_slice(self::TARGET_WIDTHS, $idx_328, $idx_4087); $this->assertEquals(count($actual), count($expected)); $this->assertEquals($start, $actual[0]); $this->assertEquals($stop, end($actual)); @@ -209,17 +210,23 @@ public function testNoParametersGeneratesSrcsetPairs() { public function testCustomSrcsetPairs() { // Test custom srcset pairs within ranges. $builder = new UrlBuilder("demos.imgix.net", true, false); - $actual = $builder->createSrcSet($path="image.jpg", $array=array(), $start=328, $stop=328); + $opts = array('start' => 328, 'stop' => 328); + $actual = $builder->createSrcSet($path="image.jpg", $params=array(), $options=$opts); $expected = 'https://demos.imgix.net/image.jpg?ixlib=php-3.2.0&w=328 328w'; $this->assertEquals($expected, $actual); $builder = new UrlBuilder("demos.imgix.net", true, false); - $actual = $builder->createSrcSet($path="image.jpg", $array=array(), $start=720, $stop=720); + $actual = $builder->createSrcSet( + $path="image.jpg", + $params=array(), + $options=array('start' => 720, 'stop' => 720)); + $expected = 'https://demos.imgix.net/image.jpg?ixlib=php-3.2.0&w=720 720w'; $this->assertEquals($expected, $actual); $builder = new UrlBuilder("demos.imgix.net", true, false); - $actual = $builder->createSrcSet($path="image.jpg", $array=array(), $start=640, $stop=720); + $opts = array('start' => 640, 'stop' => 720); + $actual = $builder->createSrcSet($path="image.jpg", $params=array(), $options=$opts); $expected = // Raw string literal 'https://demos.imgix.net/image.jpg?ixlib=php-3.2.0&w=640 640w, https://demos.imgix.net/image.jpg?ixlib=php-3.2.0&w=720 720w'; @@ -228,7 +235,8 @@ public function testCustomSrcsetPairs() { // Now test custom tolerances (also within a range). $builder = new UrlBuilder("demos.imgix.net", true, false); - $actual = $builder->createSrcSet($path="image.jpg", $array=array(), $start=100, $stop=108, $tol=1); + $opts = array('start' => 100, 'stop' => 108, 'tol' => 1); + $actual = $builder->createSrcSet($path="image.jpg", $params=array(), $options=$opts); $expected = // Raw string literal 'https://demos.imgix.net/image.jpg?ixlib=php-3.2.0&w=100 100w, https://demos.imgix.net/image.jpg?ixlib=php-3.2.0&w=102 102w, @@ -243,7 +251,7 @@ public function testSrcsetPairValues() { $srcset = $this->srcsetBuilder(); $index = 0; // array of expected resolutions generated by srcset - $resolutions = self::TARGETWIDTHS; + $resolutions = self::TARGET_WIDTHS; $srclist = explode(",", $srcset); $matches = array(); @@ -277,7 +285,7 @@ public function testGivenWidthSrcsetIsDPR() { public function testDprSrcsetWithQ100() { $builder = new UrlBuilder("demos.imgix.net", true, false); $actual = $builder->createSrcSet( - $path="image.jpg", $array=array("w" => 640, "q" => 100)); + $path="image.jpg", $params=array("w" => 640, "q" => 100)); $expected = 'https://demos.imgix.net/image.jpg?dpr=1&ixlib=php-3.2.0&q=100&w=640 1x, https://demos.imgix.net/image.jpg?dpr=2&ixlib=php-3.2.0&q=100&w=640 2x, @@ -287,32 +295,82 @@ public function testDprSrcsetWithQ100() { $this->assertEquals($expected, $actual); } + // Test that variable quality is enabled by default. public function testDprSrcsetWithDefaultQuality() { $builder = new UrlBuilder("demos.imgix.net", true, false); - $actual = $builder->createSrcSet($path="image.jpg", $array=array("w" => 640)); + $actual = $builder->createSrcSet($path="image.jpg", $params=array("w" => 740)); + + $expected = +'https://demos.imgix.net/image.jpg?dpr=1&ixlib=php-3.2.0&q=75&w=740 1x, +https://demos.imgix.net/image.jpg?dpr=2&ixlib=php-3.2.0&q=50&w=740 2x, +https://demos.imgix.net/image.jpg?dpr=3&ixlib=php-3.2.0&q=35&w=740 3x, +https://demos.imgix.net/image.jpg?dpr=4&ixlib=php-3.2.0&q=23&w=740 4x, +https://demos.imgix.net/image.jpg?dpr=5&ixlib=php-3.2.0&q=20&w=740 5x'; + $this->assertEquals($expected, $actual); + } + + // Test that `disableVariableQuality = true` disables `q` params. + public function testDprVariableQualityDisabled() { + $builder = new UrlBuilder("demos.imgix.net", true, false); + $params = array("w" => 640); + $opts = array('disableVariableQuality' => true); + $actual = $builder->createSrcSet($path="image.jpg", $params=$params, $opts=$opts); + + $expected = +'https://demos.imgix.net/image.jpg?dpr=1&ixlib=php-3.2.0&w=640 1x, +https://demos.imgix.net/image.jpg?dpr=2&ixlib=php-3.2.0&w=640 2x, +https://demos.imgix.net/image.jpg?dpr=3&ixlib=php-3.2.0&w=640 3x, +https://demos.imgix.net/image.jpg?dpr=4&ixlib=php-3.2.0&w=640 4x, +https://demos.imgix.net/image.jpg?dpr=5&ixlib=php-3.2.0&w=640 5x'; + $this->assertEquals($expected, $actual); + } + + // Test that `q` overrides the default qualities when + // `disableVariableQuality = false`. + public function testDprSrcsetQOverridesEnabledVariableQuality() { + $builder = new UrlBuilder("demos.imgix.net", true, false); + $params = array("w" => 540, "q" => 75); + $opts = array('disableVariableQuality' => false); // Enabled. + $actual = $builder->createSrcSet($path="image.jpg", $params, $opts); $expected = -'https://demos.imgix.net/image.jpg?dpr=1&ixlib=php-3.2.0&q=75&w=640 1x, -https://demos.imgix.net/image.jpg?dpr=2&ixlib=php-3.2.0&q=50&w=640 2x, -https://demos.imgix.net/image.jpg?dpr=3&ixlib=php-3.2.0&q=35&w=640 3x, -https://demos.imgix.net/image.jpg?dpr=4&ixlib=php-3.2.0&q=23&w=640 4x, -https://demos.imgix.net/image.jpg?dpr=5&ixlib=php-3.2.0&q=20&w=640 5x'; +'https://demos.imgix.net/image.jpg?dpr=1&ixlib=php-3.2.0&q=75&w=540 1x, +https://demos.imgix.net/image.jpg?dpr=2&ixlib=php-3.2.0&q=75&w=540 2x, +https://demos.imgix.net/image.jpg?dpr=3&ixlib=php-3.2.0&q=75&w=540 3x, +https://demos.imgix.net/image.jpg?dpr=4&ixlib=php-3.2.0&q=75&w=540 4x, +https://demos.imgix.net/image.jpg?dpr=5&ixlib=php-3.2.0&q=75&w=540 5x'; $this->assertEquals($expected, $actual); } - public function testDprSrcsetWithDisabledVariableQuality() { + // Test that `q` overrides `disableVariableQuality = true`. + public function testDprSrcsetQOverridesDisabledVariableQuality() { $builder = new UrlBuilder("demos.imgix.net", true, false); + $opts = array('disableVariableQuality' => true); // Disabled. $actual = $builder->createSrcSet( $path="image.jpg", - $array=array("w" => 640, "q" => 99), - $disableVariableQuality=true); + $params=array("w" => 440, "q" => 99), + $options=$opts); $expected = -'https://demos.imgix.net/image.jpg?dpr=1&ixlib=php-3.2.0&q=99&w=640 1x, -https://demos.imgix.net/image.jpg?dpr=2&ixlib=php-3.2.0&q=99&w=640 2x, -https://demos.imgix.net/image.jpg?dpr=3&ixlib=php-3.2.0&q=99&w=640 3x, -https://demos.imgix.net/image.jpg?dpr=4&ixlib=php-3.2.0&q=99&w=640 4x, -https://demos.imgix.net/image.jpg?dpr=5&ixlib=php-3.2.0&q=99&w=640 5x'; +'https://demos.imgix.net/image.jpg?dpr=1&ixlib=php-3.2.0&q=99&w=440 1x, +https://demos.imgix.net/image.jpg?dpr=2&ixlib=php-3.2.0&q=99&w=440 2x, +https://demos.imgix.net/image.jpg?dpr=3&ixlib=php-3.2.0&q=99&w=440 3x, +https://demos.imgix.net/image.jpg?dpr=4&ixlib=php-3.2.0&q=99&w=440 4x, +https://demos.imgix.net/image.jpg?dpr=5&ixlib=php-3.2.0&q=99&w=440 5x'; + $this->assertEquals($expected, $actual); + } + + public function testCreateSrcSetFromWidthsArray() { + $builder = new UrlBuilder("demos.imgix.net", true, false); + $opts = array('widths' => array(100, 200, 303, 404, 535)); + $actual = $builder->createSrcSet($path="image.jpg", $params=array(), $options=$opts); + $expected = +'https://demos.imgix.net/image.jpg?ixlib=php-3.2.0&w=100 100w, +https://demos.imgix.net/image.jpg?ixlib=php-3.2.0&w=200 200w, +https://demos.imgix.net/image.jpg?ixlib=php-3.2.0&w=303 303w, +https://demos.imgix.net/image.jpg?ixlib=php-3.2.0&w=404 404w, +https://demos.imgix.net/image.jpg?ixlib=php-3.2.0&w=535 535w'; + $this->assertEquals($expected, $actual); }