Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: create custom srcsets #58

Merged
merged 3 commits into from
May 29, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
92 changes: 62 additions & 30 deletions src/Imgix/UrlBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,17 +9,21 @@ 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,
3038, 3524, 4088, 4742, 5500, 6380, 7400, 8192);

// 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;
Expand Down Expand Up @@ -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);
}
}

Expand All @@ -98,60 +110,80 @@ 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";
}

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;
Expand Down
106 changes: 82 additions & 24 deletions tests/Imgix/Tests/UrlBuilderTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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);
}
Expand All @@ -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));
Expand All @@ -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';
Expand All @@ -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,
Expand All @@ -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();

Expand Down Expand Up @@ -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,
Expand All @@ -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);
}

Expand Down