SaaS CMS has officially launched! Learn more now.

Image optimization on upload

Vote:
 

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
Vote:
 

Check this one out:
https://www.markeverard.com/2014/02/13/image-resizing-in-episerver-7-5-cms/

#192284
Edited, May 14, 2018 15:06
Vote:
 

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
Vote:
 

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
Vote:
 

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
Vote:
 

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
Vote:
 

great, thanks ;)

#192328
May 15, 2018 12:37
Vote:
 

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
Vote:
 

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
Vote:
 

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
Vote:
 

Hi Sven Tegelmo
Thanks for sharing the handler for event. Insightful and helpful. I appreciate the detailed explanation and code. Understand it's not prettiest. Due to updates and refactoring needs.

I have a couple of questions. Ensure I understand everything correctly.
How do you handle large batches of images? Without impacting on performance?
Have you encountered any issues with SFImageService? How did you address them?
Do you have any recommendations for alternative image services that could be more efficient or reliable?

Best regards,
Neha

#323154
Edited, Jun 05, 2024 14:37
Vote:
 

Hello Neha,

  • To answer your questions as good as I can:
    - We don't really have large numbe of images. Depends on what numbers we are talking about. Normally there are no problmes for us to throw in 50-100 images in a pages (very rare). Since episerver triggers the generations under the houd async does it not affect the Editors.
  • - No, no issues. I might think that the png-compression is not the greatest, but since mostly used for illustrations, diagrams etc do they not get to large. Sometimes do editors use png for "normal" photos though and thats not great. I have added a warning featuer based om the file size to remind them.
  • No, unfortunatly not. since I'm a solo developer in my company do i not have that much time. Combined with that normally is the third party integrations and maintemance that takes time do I like to keep it as simple as possible. Well, this is not the prettiest code, but I can easily maintain it, I have also confirmed no problem with .net core/CMS 12.

Kind regards,

Sven

#323503
Jun 13, 2024 7:01
Vote:
 

Hi Sven,

Appreciate your prompt detailed response. Handling large image batches asynchronously indeed works well. This management doesn't impact performance. It is very helpful. Thanks again. 
I have been using jpeg compressor for my needs. It provides a bit good result. However, the process is all done manually then i upload it.

Thanks once more for your Kind Help!

Best regards

Neha.

#323515
Jun 13, 2024 17:32
* You are NOT allowed to include any hyperlinks in the post because your account hasn't associated to your company. User profile should be updated.