Image optimization on upload

Krzysztof Walentkowski
Member since: 2017
 

Hello, I'm facing the challenge of images optimization on our website. We have already manually optimized all blobs on a server using apps suggested by Google PageSpeed Insights, BUT we would like to optimize new images during upload process (set quality and define max dimentions). I have already went through several topics on this forum and I found:

https://hacksbyme.net/2018/05/12/optimize-your-images-with-imageprocessor/ and
https://www.frederikvig.com/2012/05/faster-episerver-sites-image-optimization/

but none of those seems to fit our needs.

Geta ImagesOptimization looks promising but it has one huge disadvantage: it needs images to be available from public domain. We've got thousands of images uploaded and we cannot take risk of experimenting on production environment..

ImageProcessor on the other hand needs to have new HTMLhelper implemented. We have dozens of views already created and such approach is a no-go. 

If you heard of any way to optimize single images during upload into episerver I'd reeeeally appreciate your help.

#192281 Edited, May 14, 2018 14:17
  • Krzysztof Walentkowski
    Member since: 2017
     

    Sven Tegelmo, the biggest issue on our site is not image size, it's quality. Our editors upload images which sometimes can be reduced by 70% without visible quality loss...

    #192286 May 14, 2018 15:09
  •  

    You can add on your own funktionality to handle size, quality, scale, ratio etc.

    For instance do we use it like this in child class of ImageData:

    [ScaffoldColumn(false)]
    [ImageWidthDescriptor(Width = 1200)]
    [ImageQualityDescriptor(JpegQuality = 85)]
    public virtual Blob Large { get; set; }

    By adding this do we keep the original upload image (good to have to be enable to reapply image optimization) as well as out optimized one (we actually have several with different size and quality depending on where its used).

    Then hook on to the PublishingContent event on IConenteEvents and you can change the image in what ever way you like (in our case based on the atteributes set).

    #192287 Edited, May 14, 2018 15:15
  • Krzysztof Walentkowski
    Member since: 2017
     

    Sven Tegelmo Is it possible for you to show more detailed code? I'm not episerver ninja yet :D

    #192320 May 15, 2018 12:08
  •  

    Heres the handler for the event.
    Not the prettiest code due to updates through episerver updates. And currenlty it's using an ugky swap of imageservice :-S The code has however worked fine for a long while so it's been untouched.
    It need some refactoring and it actually uses an internal classes from Episerver for the image and blob generation. I think I'll replace them with better ones if they change... and do some refactoring.

    public void contentEvents_PublishingContent(object sender, ContentEventArgs e)
    {
      var content = e.Content;
      if (!(content is ImageData))
        return;

      var image = content as ImageData;
      Dimensions imageDimensions = ImageBlobUtility.GetDimensions(image.BinaryData);

      PropertyInfo[] properties = image.GetType().GetProperties();
      int requiredWidth;
      int requiredJpegQuality;

      foreach (var propertyInfo in properties)
      {
         if (propertyInfo.PropertyType != typeof(Blob))
           continue;

         // Returnera direkt om episerver original blob eller tumme
         if (propertyInfo.Name.Equals("Thumbnail") || propertyInfo.Name.Equals("BinaryData"))
           continue;

         // Defaults
         requiredWidth = imageDimensions.Width;
         requiredJpegQuality = 90; // Default

         //get attribute name
         var imageWidthAttribute = (ImageWidthDescriptorAttribute)Attribute.GetCustomAttribute(propertyInfo, typeof(ImageWidthDescriptorAttribute));
         var imageScaleAttribute = (ImageScaleDescriptorAttribute)Attribute.GetCustomAttribute(propertyInfo, typeof(ImageScaleDescriptorAttribute));
         var imageQualityAttribute = (ImageQualityDescriptorAttribute)Attribute.GetCustomAttribute(propertyInfo, typeof(ImageQualityDescriptorAttribute));

         if (imageWidthAttribute != null)
        {
           requiredWidth = imageWidthAttribute.Width;
        }
        else if (imageScaleAttribute != null)
        {
           requiredWidth = (int)(imageDimensions.Width * imageScaleAttribute.DimensionMultiplier);
        }
        if (imageQualityAttribute != null)
        {
           requiredJpegQuality = imageQualityAttribute.JpegQuality;
        }


        var calculatedDimensions = ImageResizeUtility.ResizeWidthMaintainAspectRatio(imageDimensions, requiredWidth);
        var imageDescriptor = new ImageDescriptorAttribute(calculatedDimensions.Height, calculatedDimensions.Width);

        var thumbnailManager = ServiceLocator.Current.GetInstance<ThumbnailManager>();
        var orgImageService = thumbnailManager.ImageService;

        try
        {
           // Använd min egen ImageService (Passa dock in original servicen dit då vi använder Epis under ytan)
           thumbnailManager.ImageService = new SFImageService(orgImageService, 1.0f, (int)requiredJpegQuality);

           // Gör om bild
          Blob resizedBlob = thumbnailManager.CreateImageBlob(image.BinaryData, propertyInfo.Name, imageDescriptor);

          // Använder ovanstående from Epi 10
          //Blob resizedBlob = CreateImageBlob(thumbnailManager, image.BinaryData, propertyInfo.Name, imageDescriptor, requiredJpegQuality);

          // Stoppa in blob på egenskapen
          propertyInfo.SetValue(image, resizedBlob, null);
        }
        finally
       {
          // Sätt tillbaka originalservicen. Vid fel eller inte.
          thumbnailManager.ImageService = orgImageService;
       }
     }
    }

    The SFImageService:

    public class SFImageService : IImageService
    {
    IImageService _imageService;
    float _zoomFactor;
    int _jpegQuality;
    public SFImageService(IImageService imageService, float zoomFactor, int jpegQuality)
    {
    _imageService = imageService;
    _zoomFactor = zoomFactor;
    _jpegQuality = jpegQuality;
    }

    public byte[] RenderImage(byte[] imageBuffer, IEnumerable<ImageOperation> operations, string mimeType, float zoomFactor, int jpegQuality)
    {
       // Använd EpiServers egen...
       return _imageService.RenderImage(imageBuffer, operations, "image/jpeg", _zoomFactor, _jpegQuality);
    }

    #192327 Edited, May 15, 2018 12:28
  • Krzysztof Walentkowski
    Member since: 2017
     

    great, thanks ;)

    #192328 May 15, 2018 12:37
  •  

    No problem. As i said it uses Internal Episerver classes so it might break. So you should really use another.... and I as well. I have also seen that the episerver one is not so good when it comes to image quality. If you stumble upon a good free one please let me know and I might swap as well :)

    By the way: As described earlier do I add a new property to my image class. And therefore is the original image stored as well as the modified/optimized one. (Actually do I have three more in different sizes to be efficient on mobiles etc). A good thing is that if you change how the image is generated/optimzed can they be regenerated (or actually cleared) easily i the Episerver admin GUI. The "Clear Thumbnail Properties" schedule job does that for you.

    #192329 May 15, 2018 12:52
  • Vincent Baaij
    Member since: 2016
     

    Hi,

    Maybe an option is to use my Episerver ImageProcessor add-on (blog, package) in combination with the ImageProcessor Post Processor plugin (http://imageprocessor.org/imageprocessor-web/plugins/postprocessor/)?

    #192332 May 15, 2018 13:19
  • Krzysztof Walentkowski
    Member since: 2017
     

    Hi, in the meantime I found other solution which is most suitable for us. 
    https://www.david-tec.com/2017/02/using-tinypng-to-automatically-compress-images-in-episerver/

    https://tinypng.com/

    Works great, there's completely no difference between original and compressed images from visual perspective. And it's really cheap. 

    Thanks everyone for help!

    #192333 May 15, 2018 13:22