When implementing a responsive design the size of images is almost always an issue. You want the images to be resized server-side so they fit the screen. Primarily for performance but also to please Google Page Speed.
Sitecore enables us to set the width and height dynamically by sending query parameters to the media handler.
My colleague Brian wrote this nice post where he explains the basic out-of-the-box Sitecore image parameters.
But what if it was possible to also let say set the JPEG compression level using query parameters? Then it would be possible to set the compression level low for small screens and high on desktops. And what about center-cropping images to make them fit in those designs where the image proportions differ between breakpoints.
Wait no longer. This post will show how to set the JPEG compression level using a query parameter and my next post in this mini-series will show how to crop images using a similar technique.
What about performance then? It can take up a lot of resources on the server when working with images.
Well when changing the media in the right pipeline Sitecore will do its magic and store the image in the media cache.
Setting JPEG compression level via the query string
To do this we implement a service class which changes the compression level of a Stream. Plain and simple .NET
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
public class ChangeJpegCompressionLevelService { public static Stream Change(MediaStream mediaStream, int jpegQuality) { var jpegEncoder = GetEncoder(ImageFormat.Jpeg); var qualityEncoder = Encoder.Quality; var encoderParameters = new EncoderParameters(1); var bitmap = new Bitmap(mediaStream.Stream); encoderParameters.Param[0] = new EncoderParameter(qualityEncoder, jpegQuality); var stream = new MemoryStream(); bitmap.Save(stream, jpegEncoder, encoderParameters); return stream; } private static ImageCodecInfo GetEncoder(ImageFormat format) { var codecs = ImageCodecInfo.GetImageDecoders(); return codecs.FirstOrDefault(codec => codec.FormatID == format.Guid); } } |
Next thing we create a new processor for the getMediaStream pipeline.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
namespace PT.LaubPlusCo.ImageParameters.Infrastructure { public class SetJpegCompressionProcessor { private string JpegCompressionLevelQueryKey { get { return Settings.GetSetting("LaubPlusCo.JpegCompressionLevelQueryKey", "jq"); } } public void Process(GetMediaStreamPipelineArgs args) { Assert.ArgumentNotNull(args, "args"); if (args.Options.Thumbnail || !IsJpegImageRequest(args.MediaData.MimeType)) return; if (args.OutputStream == null || !args.OutputStream.AllowMemoryLoading) return; var jpegQualityQuery = WebUtil.GetQueryString(JpegCompressionLevelQueryKey); if (string.IsNullOrEmpty(jpegQualityQuery)) return; int jpegQuality; if (!int.TryParse(jpegQualityQuery, out jpegQuality) || jpegQuality <= 0 || jpegQuality > 100) return; var compressedStream = ChangeJpegCompressionLevelService.Change(args.OutputStream, jpegQuality); args.OutputStream = new MediaStream(compressedStream, args.MediaData.Extension, args.OutputStream.MediaItem); } protected bool IsJpegImageRequest(string mimeType) { return mimeType.Equals("image/jpeg", StringComparison.InvariantCultureIgnoreCase) || mimeType.Equals("image/pjpeg", StringComparison.InvariantCultureIgnoreCase); } } } |
We first check if it is a jpeg image which is being requested and if the query key is present and contain a valid compression level between 1 and 100.
Finally we call the service class with the image stream and replace the media stream in the pipeline args with the new one.
The processor should run after Sitecore have performed resizing etc. so we place it in the end of the getMediaStream pipeline using the following include config.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/"> <sitecore> <settings> <setting name="LaubPlusCo.JpegCompressionLevelQueryKey" value="jq" /> </settings> <pipelines> <getMediaStream> <processor patch:after="processor[@type='Sitecore.Resources.Media.GrayscaleProcessor, Sitecore.Kernel']" type="PT.LaubPlusCo.ImageParameters.Infrastructure.SetJpegCompressionProcessor, PT.LaubPlusCo.ImageParameters" /> </getMediaStream> </pipelines> </sitecore> </configuration> |
That is it. Now we can set the JPEG compression level by adding the query jq=<quality> and even mix this with the other Sitecore image parameters, for example:
http://MYWEBSITE/~/media/my-image.jpg?w=100&h=100&jq=60
I have used the well-known Windows Koala as a show case
Low compression
Medium compression
High compression
What about caching then? Well Sitecore has already taken care of this all by itself.
Stay tuned for my next post which shows how to center-crop images using query string values.
Anders Laub Christoffersen
Anders has been working with Sitecore for over a decade and has in this time been the lead developer and architect on several large scale enterprise solutions all around the world. Anders was appointed the title of Sitecore Technical MVP in 2014 and has been re-appointed the title every year since then.
- Web |
- More Posts
Great article, thanks for sharing – I implement basically the same thing on my site. However, I have a question and wondered what your stance is…
Doesn’t this compress the image twice, and therefore is quite inefficient (double processing) and also produces a poorer quality image (re-compressing a lossy-compressed image)?
I imagine a better way would be to re-implement the default Sitecore resize processor but add the quality querystring param into that. What are your thoughts?
Thanks again
You are right about the image being processed twice if it is also resized in the same go, but Sitecore caches the image automatically so it only happens when the image is not already present in the media cache.
You could replace the default Sitecore resize processor to optimize but I am not sure I would say it is time well-spent and it could potentially cause problems on Sitecore updates.