The issue with these images was that they are massive in size and cannot be loaded into the web site without some sort of re-sizing and compression. Unfortunately the image hosting site, does not have any functionality that would allow us to resize the image at source - i.e. it is not a DAM.
We wanted to leverage Sitecore to do this for two reasons:
- Sitecore has a very robust re-sizing mechanism that is easy to invoke and is familiar to all Sitecore developers.
- We wanted the images to be cached on the server - which Sitecore does for all media items by default.
In our case we have an image in the media library called CustomContent.
We have also designed our processor so that if there is no external image, the CustomContent image will be shown instead.
Just to give you an idea of what we are talking about here is an image URL:
/~/media/images/CustomContent.ashx?externalImage=https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiZ9hdJMNqLxS9LO-wqL5S2la1_D9naH77vul12s10qI8PTzCb10F0qypgWMTyU_xGXI_3jV31vziVwLPGcKmb5vajxeWXhXVmfmqpxIalraqVnpilsJHp4_V-DOzAoNVhXz6xSLUo7VuF1/,27da3a5fe74dc9dc/IMG_0030.JPG&h=180&w=180&id=3f36195e-8dbe-45c5-8ee0-18c2ab130eef&hash=7FC9C5E5044978E99335A9BF4FD3E8B56237E17B
Important points to note:
- Extra Parameters including externalImage and id
- The image URL can point to any external image
In your URL helper / resolver do something like:
HashingUtils.ProtectAssetUrl( string.Format("/~/media/images/CustomContent.ashx?externalImage={0}&h={1}&w={2}&id={3}", url, source.Height, source.Width, source.Item.ID.ToGuid().ToString("D")));
The HashingUtils class can be found by using Sitecore.Resources.Media.
You will need to add your parameters to the protectedMediaQueryParameters section of the Sitecore.Media.RequestProtection.config file, as follows:
So now for the code:
First off the processor has an entry method called Process, in here we accept a single argument parameter of GetMediaStreamPipelineArgs, which we can then examine for our custom parameters:
public void Process(GetMediaStreamPipelineArgs args) { if (args.Options.CustomOptions["externalImage"] != null || args.Options.CustomOptions["id"] != null) { GetExternalImage(args); } }
If we find the custom parameters, we can call the main piece of functionality which will get the image re-size and/or compress it and render it through the media request pipeline.
private void GetExternalImage(GetMediaStreamPipelineArgs args) { try { var client = new WebClient(); var result = client.OpenRead(new Uri(args.Options.CustomOptions["externalImage"])); if (result == null) { args.AbortPipeline(); return; } var bm = CreateBitMapFromStream(result); var quantizer = new WuQuantizer(); if (args.Options.Width != 0 && args.Options.Height != 0) { using (var newImage = ScaleImage(bm, args.Options.Width, args.Options.Height)) { using (var quantized = quantizer.QuantizeImage(new Bitmap(newImage))) { var stream = new MemoryStream(); quantized.Save(stream, ImageFormat.Png); args.OutputStream = new MediaStream(stream, "png", args.MediaData.MediaItem); } } } else { using (var quantized = quantizer.QuantizeImage(bm)) { var stream = new MemoryStream(); quantized.Save(stream, ImageFormat.Png); args.OutputStream = new MediaStream(stream, "png", args.MediaData.MediaItem); } } } catch (Exception ex) { Log.Error(ex.Message, ex, this); args.AbortPipeline(); } }
We use WuQuantizer to do the png compression which does a better job than .net's native compression.
https://www.nuget.org/packages/nQuant/
Just for completeness here are the two private methods referenced in the above:
private static Bitmap CreateBitMapFromStream(Stream result) { byte[] imageBytes; using (var ms = new MemoryStream()) { var count = 0; do { var buf = new byte[1024]; count = result.Read(buf, 0, 1024); ms.Write(buf, 0, count); } while (result.CanRead && count > 0); imageBytes = ms.ToArray(); } // Now you can use the returned stream to set the image source too var image = new MemoryStream(imageBytes); var bm = (Bitmap) Image.FromStream(image); return bm; } public static Image ScaleImage(Image image, int maxWidth, int maxHeight) { var ratioX = (double) maxWidth/image.Width; var ratioY = (double) maxHeight/image.Height; var ratio = Math.Min(ratioX, ratioY); var newWidth = (int) (image.Width*ratio); var newHeight = (int) (image.Height*ratio); var newImage = new Bitmap(newWidth, newHeight); using (var graphics = Graphics.FromImage(newImage)) graphics.DrawImage(image, 0, 0, newWidth, newHeight); return newImage; }
Finally implementing the processor, we need to add our processor to the getMediaStream pipeline, and we do that we creating the following config file:
<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/"> <sitecore> <pipelines> <getMediaStream> <processor patch:after="processor[@type='Sitecore.Resources.Media.GrayscaleProcessor, Sitecore.Kernel']" type="MyProject.Library.Processors.CustomImageProcessor, MyProject.Library" /> </getMediaStream> </pipelines> </sitecore> </configuration>
@Sitecore.Resources.Media.HashingUtils.ProtectAssetUrl("/~/media/XXX_Intranet/Profile/photo.ashx?userName={{contact.imageUrl}}")"
ReplyDeleteHashingUtils() is called on server side and {{contact.imageUrl}} is getting asssigned on client side. this is giving same hash values for all the images. how can i get rid of this.
Try creating the entire hashed url server side, and then pass that to your front end.
Delete