November Happy Hour will be moved to Thursday December 5th.

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

Not a direct answer, but an observation: Typically you'd want editors to upload original images in the maximum resolution, and optimize images just before sending it to the client's device. That way, you retain an original (uploaded one time) that can be resized, cropped and compressed any way you like for any device/screen size. 

What is the reason you want to compress the original on upload? Performance issues for editors?

#327695
Aug 19, 2024 11:18
Vote:
 

In this scenario is orignal image untouched (any resolution the editor wants). Copies in different sizes are created for it when uploaded. So no need to does this on per request (no delays for user). Actual loaded (and procesed) image is based on device/screen size.

#327696
Aug 19, 2024 11:22
Vote:
 

So you're basically pre-compressing image variants, to avoid having to do it on the fly? I can imagine this having a minor benefit on pages with LOTS of heavy images, but in most cases the on-the-fly compression is pretty quick with these tools (plus, the images will be cached after first compression/load). The downside to your approach is that you flood your database with all possible variants of all your images, without knowing whether they will be needed. But hey, disk space is cheap...

#327697
Aug 19, 2024 11:29
Vote:
 

Yes, plus and minus as always. I prefer performance for user, less CPU/memory usage instead om DB/file-size, but of course depending on your specific needs/nyumber of images/speed requirements etc. If using caching does the cache need to be populated (and re-initialized if memorybased on restart). Unless disc-based caching, but that makes it both slower for user on first load and require file/DB-space.

#327698
Aug 19, 2024 11:43
Vote:
 

Bearing in mind that you're going through Cloudflare when you're on DXP.  You'll benefit from excellent caching of resized images within CloudFlare, especially if you apply good cache control headers.  If your image is a PNG or JPG, CloudFlare will also convert that to a WebP after the first request and that will have a different "quality" applied to it.  You could even end up in a situation where you have 2 rounds of image compression being applied for each variant; One during your own optimization and one during Cloudflare optimization. At least with On Demand resizing, you'll only be caching images sizes being used within cloudflare rather than storing all variants in an "on upload" scenario.

#328328
Aug 29, 2024 10:49
* 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.