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/progressive interlaced #399

Merged
merged 21 commits into from
Oct 27, 2024
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
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
Loading