Skip to content

Commit

Permalink
Merge pull request #399 from thephpleague/feat/progressive-interlaced
Browse files Browse the repository at this point in the history
Feat/progressive interlaced
  • Loading branch information
ADmad authored Oct 27, 2024
2 parents 9afc803 + 4f2cf24 commit 8d51c57
Show file tree
Hide file tree
Showing 11 changed files with 426 additions and 316 deletions.
2 changes: 1 addition & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
],
"require": {
"php": "^8.1",
"intervention/image": "^3.3",
"intervention/image": "^3.6",
"league/flysystem": "^3.0",
"psr/http-message": "^1.0|^2.0"
},
Expand Down
26 changes: 26 additions & 0 deletions docs/3.0/api/progressive-interlaced.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
---
layout: default
title: Progressive & Interlaced
---

## Progressive & Interlaced Images

## Interlace `interlace`

The `interlace` parameter controls whether an image is rendered in a progressive or interlaced format. This feature enhances the loading experience of images, making them appear gradually as they are downloaded, which can improve the user experience on slower connections.

> Caution: For GIF/PNG, it can generate a slightly larger file size.
### Supported Formats

- **JPG**: The `onterlace` parameter applies a progressive scan to JPG images.
- **PNG** and **GIF**: The `interlace` parameter enables interlacing for GIF/PNG images.

> Note: When `ext` is set to `.pjpg`, it will automatically generate a progressive JPG image, regardless of the `interlace` parameter.
~~~ html
<img src="kayaks.jpg?interlace=1">
<img src="logo.png?interlace=1">
~~~

[![© Photo Joel Reynolds](https://glide.herokuapp.com/1.0/kayaks.jpg?interlace=1)](https://glide.herokuapp.com/1.0/kayaks.jpg?interlace=1)
1 change: 0 additions & 1 deletion docs/3.0/config/setup.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,6 @@ $manipulators = [
new League\Glide\Manipulators\Watermark($watermarks),
new League\Glide\Manipulators\Background(),
new League\Glide\Manipulators\Border(),
new League\Glide\Manipulators\Encode(),
];

// Set API
Expand Down
1 change: 1 addition & 0 deletions docs/_data/menu.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
Background: '/3.0/api/background/'
Border: '/3.0/api/border/'
Encode: '/3.0/api/encode/'
Progressive & Interlaced: '/3.0/api/progressive-interlaced/'
'2.0':
Getting Started:
Introduction: '/'
Expand Down
20 changes: 18 additions & 2 deletions src/Api/Api.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
namespace League\Glide\Api;

use Intervention\Image\ImageManager;
use Intervention\Image\Interfaces\ImageInterface;
use League\Glide\Manipulators\ManipulatorInterface;

class Api implements ApiInterface
Expand Down Expand Up @@ -91,10 +92,25 @@ public function run(string $source, array $params): string

foreach ($this->manipulators as $manipulator) {
$manipulator->setParams($params);

$image = $manipulator->run($image);
}

return $image->encodeByMediaType()->toString();
return $this->encode($image, $params);
}

/**
* Perform image encoding to a given format.
*
* @param ImageInterface $image Image object
* @param array $params the manipulator params
*
* @return string Manipulated image binary data
*/
public function encode(ImageInterface $image, array $params): string
{
$encoder = new Encoder($params);
$encoded = $encoder->run($image);

return $encoded->toString();
}
}
151 changes: 151 additions & 0 deletions src/Api/Encoder.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
<?php

namespace League\Glide\Api;

use Intervention\Image\Interfaces\EncodedImageInterface;
use Intervention\Image\Interfaces\ImageInterface;

/**
* Encoder Api class to convert a given image to a specific format.
*/
class Encoder
{
/**
* The manipulation params.
*/
protected array $params;

/**
* Class constructor.
*
* @param array $params the manipulator params
*/
public function __construct(array $params = [])
{
$this->params = $params;
}

/**
* Set the manipulation params.
*
* @param array $params The manipulation params.
*
* @return $this
*/
public function setParams(array $params)
{
$this->params = $params;

return $this;
}

/**
* Get a specific manipulation param.
*/
public function getParam(string $name): mixed
{
return array_key_exists($name, $this->params)
? $this->params[$name]
: null;
}

/**
* Perform output image manipulation.
*
* @param ImageInterface $image The source image.
*
* @return EncodedImageInterface The encoded image.
*/
public function run(ImageInterface $image): EncodedImageInterface
{
$format = $this->getFormat($image);
$quality = $this->getQuality();
$shouldInterlace = filter_var($this->getParam('interlace'), FILTER_VALIDATE_BOOLEAN);

if ('pjpg' === $format) {
$shouldInterlace = true;
$format = 'jpg';
}

$encoderOptions = ['extension' => $format];
switch ($format) {
case 'avif':
case 'heic':
case 'tiff':
case 'webp':
$encoderOptions['quality'] = $quality;
break;
case 'jpg':
$encoderOptions['quality'] = $quality;
$encoderOptions['progressive'] = $shouldInterlace;
break;
case 'gif':
case 'png':
$encoderOptions['interlaced'] = $shouldInterlace;
break;
default:
throw new \Exception("Invalid format provided: {$format}");
}

return $image->encodeByExtension(...$encoderOptions);
}

/**
* Resolve format.
*
* @param ImageInterface $image The source image.
*
* @return string The resolved format.
*/
public function getFormat(ImageInterface $image): string
{
$fm = (string) $this->getParam('fm');

if ($fm && array_key_exists($fm, static::supportedFormats())) {
return $fm;
}

/** @psalm-suppress RiskyTruthyFalsyComparison */
return array_search($image->origin()->mediaType(), static::supportedFormats(), true) ?: 'jpg';
}

/**
* Get a list of supported image formats and MIME types.
*
* @return array<string,string>
*/
public static function supportedFormats(): array
{
return [
'avif' => 'image/avif',
'gif' => 'image/gif',
'jpg' => 'image/jpeg',
'pjpg' => 'image/jpeg',
'png' => 'image/png',
'webp' => 'image/webp',
'tiff' => 'image/tiff',
'heic' => 'image/heic',
];
}

/**
* Resolve quality.
*
* @return int The resolved quality.
*/
public function getQuality(): int
{
$default = 90;
$q = $this->getParam('q');

if (
!is_numeric($q)
|| $q < 0
|| $q > 100
) {
return $default;
}

return (int) $q;
}
}
114 changes: 0 additions & 114 deletions src/Manipulators/Encode.php

This file was deleted.

2 changes: 0 additions & 2 deletions src/ServerFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@
use League\Glide\Manipulators\Brightness;
use League\Glide\Manipulators\Contrast;
use League\Glide\Manipulators\Crop;
use League\Glide\Manipulators\Encode;
use League\Glide\Manipulators\Filter;
use League\Glide\Manipulators\Flip;
use League\Glide\Manipulators\Gamma;
Expand Down Expand Up @@ -255,7 +254,6 @@ public function getManipulators(): array
new Watermark($this->getWatermarks(), $this->getWatermarksPathPrefix() ?? ''),
new Background(),
new Border(),
new Encode(),
];
}

Expand Down
6 changes: 5 additions & 1 deletion tests/Api/ApiTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,11 @@ public function testGetManipulators()
public function testRun()
{
$image = \Mockery::mock(ImageInterface::class, function ($mock) {
$mock->shouldReceive('encodeByMediaType')->andReturn(\Mockery::mock(EncodedImageInterface::class, function ($mock) {
$mock->shouldReceive('origin')->andReturn(\Mockery::mock('\Intervention\Image\Origin', function ($mock) {
$mock->shouldReceive('mediaType')->andReturn('image/png');
}));

$mock->shouldReceive('encodeByExtension')->with('png')->andReturn(\Mockery::mock(EncodedImageInterface::class, function ($mock) {
$mock->shouldReceive('toString')->andReturn('encoded');
}));
});
Expand Down
Loading

0 comments on commit 8d51c57

Please sign in to comment.