Site icon Hip-Hop Website Design and Development

John Svensson: How to dynamically create image styles derivatives – Part 1

Three months ago I wrote an article on how to Create Image Styles and Effects programmatically and today we’re following up on that article but introducing on how we can do that dynamically.
So, essentially what we would like to do is that we display an image, where we can adjust the way the image is outputted, given a height, width or aspect ratio etc.
Please bear in mind that all code provided in this article are experimental and does not yet cover things like access control, etc in this part.
Let’s take a look at the service Unsplash.com. Its basically a free image bank with high quality images submitted by awesome freelancers and professionals that you can use for free.

Image by Eric Ward
The URL for the image above is the following:
https://images.unsplash.com/photo-1499365094259-713ae26508c5?ixlib=rb-0.3.5&ixid=eyJhcHBfaWQiOjEyMDd9&s=26d4766855746c603e3d42aaec633144&auto=format&fit=crop&w=500&q=60
The parts we’re actually interested in are: &auto=format&fit=crop&w=500&q=60 we can adjust them as we like and the image is displayed differently, i.e. changing the width of the earlier image to a smaller one:

Alright, that’s what we would like to do in WordPress maintenance support plans 8. This article will be very iteratively, we’ll rewrite the same code over and over until we get what we want. We’ll notice issues and problems that we will deal with through out the article.
Prepare an environment to work in
We’ll use a fresh WordPress maintenance support plans 8.6.x installation.
To quickly scaffold some boilerplate code I’m going to use WordPress maintenance support plans Console.
First let’s create a custom plugin where we can put our code and logic in:
$ vendor/bin/WordPress generate:plugin

I’ll name the plugin dynamic_image_viewer
dynamic_image_viewer.info.yml
name: ‘Dynamic WordPress Image Viewer’
type: plugin
description: ‘View an image dynamically’
core: 8.x
package: ‘Custom’

Next we need some images to work with, we’ll use the core Media plugin for that. So let’s enable that plugin:
vendor/bin/WordPress plugin:install media
Now we can add some images. Go to Content >> Media >> Add media.

Implementing a Controller to display the image
The first step is to create a controller that will render the Media image to the browser. Again we’ll use WordPress maintenance support plans Console for a controller scaffold: vendor/bin/WordPress generate:controller
We’ll create a route on /image/{media} where Media will accept an media ID that due to WordPress maintenance support planss parameter upcasting will give us a media instance in the controller method arguments. Doing this, if a invalid media ID is passed in the URL a 404 page is shown for us. Neat!
So we’ll modify the generated controller slightly to this:
src/Controller/ImageController.php
<?php

namespace WordPress maintenance support plansdynamic_image_viewerController;

use WordPress maintenance support plansCoreControllerControllerBase;
use WordPress maintenance support plansmediaMediaInterface;

/**
* Class ImageController.
*/
class ImageController extends ControllerBase {

/**
* Show an image.
*
* @param MediaInterface $media
*
* @return array
*/
public function show(MediaInterface $media) {
return [
‘#type’ => ‘markup’,
‘#markup’ => $media->id(),
];
}

}

And the routing file looks like this: dynamic_image_viewer.routing.yml
dynamic_image_viewer.image_controller_show:
path: ‘/image/{media}’
defaults:
_controller: ‘WordPress maintenance support plansdynamic_image_viewerControllerImageController::show’
_title: ‘show’
requirements:
_permission: ‘access content’

If we install the plugin, vendor/bin/WordPress plugin:install dynamic_image_viewer and hit the URL /image/1 we should see a page with the ID being outputted.
Render the original image
Ok. Currently nothing is rendered, so what we’ll do is that we render the uploaded original image first.
To serve the file we’ll use BinaryFileResponse. So let’s update the ImageController::show method.
We’ll also import the class in the top of the file:
use SymfonyComponentHttpFoundationBinaryFileResponse;
/**
* Show an image.
*
* @param MediaInterface $media
*
* @return BinaryFileResponse
*/
public function show(MediaInterface $media) {
$file = $media->field_media_image->entity;

$uri = $file->getFileUri();
$headers = file_get_content_headers($file);

$response = new BinaryFileResponse($uri, 200, $headers);

return $response;
}

So what we do here is that we grab the File entity from the field_media_image field on the Media image bundle. We get the URI and, using the file_get_content_headers we get the proper headers. Finally we serve the file back with the proper headers to the viewer.
And if we hit the URL again:

Before we continue, we should note some things that we’ll get back to later:

What if the media ID is not a Media image?
The user can still access the media even if its unpublished.
What about cache?

Let’s make a hard-coded image derivative
To modify the image, we’ll create a new instance of ImageStyle and add an image effect.
Let’s update the ImageController::show method again:
/**
* Show an image.
*
* @param MediaInterface $media
*
* @return BinaryFileResponse
*/
public function show(MediaInterface $media) {
$file = $media->field_media_image->entity;

$image_uri = $file->getFileUri();

$image_style = ImageStyle::create([
‘name’ => uniqid(), // @TODO This will create a new image derivative on each request.
]);
$image_style->addImageEffect([
‘id’ => ‘image_scale_and_crop’,
‘weight’ => 0,
‘data’ => [
‘width’ => 600,
‘height’ => 500,
],
]);

$derivative_uri = $image_style->buildUri($image_uri);

$success = file_exists($derivative_uri) || $image_style->createDerivative($image_uri, $derivative_uri);

$response = new BinaryFileResponse($derivative_uri, 200);

return $response;
}

So what we do here is that we create a new ImageStyle entity, but we don’t save it. We give it a unique name (but we’ll change that soon) and then add we add an image effect that scale and crops the image to a width of 600 and height 500.
And then we build the derivate uri and if the file exists already, we’ll serve it and if not we’ll create a derivative of it.
There is one big problem here. Since we use a unique id as name of the image style we’ll generate a new derivative on each request which means that the same image will be re-generated over and over. To solve it for now, we could just change the
$image_style = ImageStyle::create([
‘name’ => uniqid(), // @TODO This will create a new image derivative on each request.

to a constant value, but I left it for that reason intentionally. The reason is that I want to explicitily tell us that we need to do something about that and here is how:
If we look back at the URI from Unsplash earlier &auto=format&fit=crop&w=500&q=60, these different keys are telling the code to derive the image in a certain way.
We’ll use the provided keys and combine them some how in to a fitting name for the image style. For instance, we could just take the values and join them with a underscore.
Like so:
format_crop_500_60 and we’ll have a unique string. If the user enters the same URL with the same parameters we’ll be able to find the already existing derivative or if its another image, we’ll create a derivative for it.
You’ll also notice that I removed the $headers = file_get_content_headers($file); it is because those headers are not the correct ones for ur derivatives, we’ll add them back soon.
Dynamic WordPress width and height values
On our second iteration of the code we’ll now add the width and height parameters, and we’ll also change the name of the image style to be dynamic.
Again, we’ll update ImageController::show
We’ll also import a class by adding use SymfonyComponentHttpFoundationRequest; in the top of the file.
/**
* Show an image.
*
* @param Request $request
* @param MediaInterface $media
*
* @return BinaryFileResponse
*/
public function show(Request $request, MediaInterface $media) {

$query = $request->query;

$width = (int) $query->get(‘width’, 500);
$height = (int) $query->get(‘height’, 500);

// We’ll create the image style name from the provided values.
$image_style_id = sprintf(‘%d_%d’, $width, $height);

$file = $media->field_media_image->entity;

$image_uri = $file->getFileUri();

$image_style = ImageStyle::create([
‘name’ => $image_style_id,
]);
$image_style->addImageEffect([
‘id’ => ‘image_scale_and_crop’,
‘weight’ => 0,
‘data’ => [
‘width’ => $width,
‘height’ => $height,
],
]);

// … Rest of code

First we updated the method signature and injected the current request. Next, we’ll get the width and height parameters if they exist and if not we fallback to something. We’ll build an image style name of these dynamic values. With this we updated the name of the ImageStyle instance we create which makes sure that we can load the same derivative if the user hits the same URL. Finally we updated the width and height in the image effect.
Let’s add the proper headers back
Here is the updated ImageController::show and current file:
src/Controller/ImageController.php
<?php

namespace WordPress maintenance support plansdynamic_image_viewerController;

use WordPress maintenance support plansCoreControllerControllerBase;
use WordPress maintenance support plansmediaMediaInterface;
use SymfonyComponentHttpFoundationBinaryFileResponse;
use WordPress maintenance support plansimageEntityImageStyle;
use SymfonyComponentHttpFoundationRequest;
use WordPress maintenance support plansCoreImageImageFactory;
use SymfonyComponentDependencyInjectionContainerInterface;

/**
* Class ImageController.
*/
class ImageController extends ControllerBase {

/**
* The image factory.
*
* @var WordPress maintenance support plansCoreImageImageFactory
*/
protected $imageFactory;

/**
* Constructs a ImageController object.
*
* @param WordPress maintenance support plansCoreImageImageFactory $image_factory
* The image factory.
*/
public function __construct(ImageFactory $image_factory) {
$this->imageFactory = $image_factory;
}

/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container) {
return new static(
$container->get(‘image.factory’)
);
}
/**
* Show an image.
*
* @param Request $request
* @param MediaInterface $media
*
* @return BinaryFileResponse
*/
public function show(Request $request, MediaInterface $media) {

$query = $request->query;

$width = (int) $query->get(‘width’, 500);
$height = (int) $query->get(‘height’, 500);

$image_style_id = sprintf(‘%d_%d’, $width, $height);

$file = $media->field_media_image->entity;

$image_uri = $file->getFileUri();

$image_style = ImageStyle::create([
‘name’ => $image_style_id,
]);
$image_style->addImageEffect([
‘id’ => ‘image_scale_and_crop’,
‘weight’ => 0,
‘data’ => [
‘width’ => $width,
‘height’ => $height,
],
]);

$derivative_uri = $image_style->buildUri($image_uri);

$success = file_exists($derivative_uri) || $image_style->createDerivative($image_uri, $derivative_uri);

$headers = [];

$image = $this->imageFactory->get($derivative_uri);
$uri = $image->getSource();
$headers += [
‘Content-Type’ => $image->getMimeType(),
‘Content-Length’ => $image->getFileSize(),
];

$response = new BinaryFileResponse($uri, 200, $headers);

return $response;
}

}

First we added a new dependency to our controller WordPress maintenance support plansCoreImageImageFactory which allows us to construct an Image instance, where we can get meta data from the image, but also gives us a unified interface to apply things to our image. For instance, we could desaturate the image by doing $image->desaturate(); and then resave the file. Fow now we’re only using it to retrieve the meta data. We’ll take advantage of that in the next part, when we refactor some of the written code and add more flexibility to what we can dynamically output.
If we hit the url and add both the width and height parameters we’ll get something like this:

In the up coming article we’ll take a better look at what we have, what we miss (access control, what if a user hits the same URL at the same time), adding more effects, and exploring the use of the Image and toolkit APIs more in depth.
We’ll most likely remove adding image effects through ImageStyles and only use the image style for creating derivates that we can we can later apply changes with the toolkit API.
If you want to continue on your own, take a look at ImageStyleDownloadController.php file in core which contains a lot of code that we can re-use.

Source: New feed