Check this one out:
https://www.markeverard.com/2014/02/13/image-resizing-in-episerver-7-5-cms/
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...
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).
Sven Tegelmo Is it possible for you to show more detailed code? I'm not episerver ninja yet :D
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);
}
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.
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/)?
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!
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
Hello Neha,
Kind regards,
Sven
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.
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?
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.
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...
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.
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.
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.