On first thought, yeah, usually images encoded as webp are supposed to be smaller.
Looking at your image though, I see it has very few different colours and if I remember correctly, png is already quite optimised if there are few different colours. I'd say: try the same thing with a photograph of the same dimensions or try it with a jpeg version of the same image and see if webp is still larger than the original image.
I think in general you're losing performance with this as I think your logo is a bit of a special case. The WebP optimization for other png images should still be (very) helpful.
In your code you're removing format - so you're "reverting" developer intent (I assume you're appending the ?format= to every image src?)
How about doing this in another way. You can implement for example a middleware:
public class WebPMiddleware
{
private readonly RequestDelegate _next;
public WebPMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task Invoke(HttpContext httpContext)
{
if (IsImageRequest(httpContext) &&
httpContext.Request.GetTypedHeaders()?.Accept != null
&& httpContext.Request.GetTypedHeaders().Accept.Any(aValue => aValue.MediaType.Value == "image/webp"))
{
var updatedQueryString = GetUpdatedQueryString(httpContext);
var qb1 = new QueryBuilder(updatedQueryString);
httpContext.Request.QueryString = qb1.ToQueryString();
}
await _next(httpContext);
}
private bool IsImageRequest(HttpContext httpContext)
{
var path = httpContext.Request.Path.ToString();
return path.EndsWith("png") || path.EndsWith("jpg") || path.EndsWith("jpeg");
}
private List<KeyValuePair<string, string>> GetUpdatedQueryString(HttpContext httpContext)
{
var queryitems = httpContext.Request.Query.SelectMany(x => x.Value, (col, value) => new KeyValuePair<string, string>(col.Key, value)).ToList();
var queryParameters = new List<KeyValuePair<string, string>>();
foreach (var item in queryitems)
{
var value = item.Value;
if (item.Key == "format")
{
value = "webp";
}
var newQueryParameter = new KeyValuePair<string, string>(item.Key, value);
queryParameters.Add(newQueryParameter);
}
if (!queryParameters.Any())
{
queryParameters.Add(new KeyValuePair<string, string>("format", "webp"));
}
return queryParameters;
}
}
public static class WebPMiddlewareExtensions
{
public static IApplicationBuilder UseWebP(this IApplicationBuilder builder)
{
return builder.UseMiddleware<WebPMiddleware>();
}
}
use it:
app.UseWebP();
app.UseImageSharp();
and then transform your code to something else:
services.AddImageSharp(options =>
{
options.OnParseCommandsAsync = c =>
{
bool doesntWantFormat = c.Commands.TryGetValue("noformat", out string value);
if (doesntWantFormat)
{
c.Commands.Remove("format");
}
return Task.CompletedTask;
};
});
and then WebP works by default for all images except the ones you want to have it disabled, like this:
Hey, this solution works great. Thanks for sharing. One thing I've found however with both solutions is that neither works when you enable CDN URL with the Azure Blob Storage. Any ideas on how to get the WEBP functionality working when utilizing the CDN URL as well?
I'm not utilising CDN in my project so unfortunately I can't play with it without doing some more setting up for which I don't have time today but I have an idea.
The first thing that came into my mind is that you might need to implement custom image provider - more in the links:
Thanks for the reply and thanks to all of you for your tips.
I've tried and tried different approaches to get to a good solution that works for me.
After some digging in the encoder i found "Image.PixelType.BitsPerPixel" and found a correlation between png and jpegs.
The jpges are 24px and pngs are 32px, in that way i could sepparate them.
And depending on that use different compression types. Which made it all work. Haha, 🙌
So now i'm getting the images in correct size.
Idon't auctually know what i'm doing but it works... haha
I believe the 32 bits per pixel has to do with transparency: an additional 8 bits for the alpha channel. So I guess it's a good way to determine if a PNG contains transparency. However, I'm not sure if using Lossless WebP encoding for 32 bpp images will always result in a smaller image than the original PNG. It may depend on the image, see the following discussion:
Hey there, just curious, how do you enable webp encoding for images in v10? Just adding the ?format=webp still shows encoding as the original format in the network tab for me.
I've tried your code in V10, however it doesn't seem to work.
Nothing inside of /Media hits your middleware, I presume this is due to the way Umbraco is now registering the static assets in program.cs rather than startup.cs?
I seem to have the same problem in my Umbraco 10 project. Some png images works just fine to convert to webp but some become huge when converted. I have one png image that is about 50kb but when i add format=webp it becomes 1.1 mb.
I'm guessing that Image Sharp uses lossless format for this image for some reason.
I have tried some of the solutions suggested here but no luck. I have disabled UseStaticFiles but that din't give any effect.
I would like to configure Image Sharp to always use lossy compression but I can't seem to figure out how to do that.
I tried aerksn solution but it doesn't work for me. The code never runs in my solution.
Edit:
aerksns solution does work in my project. I just forgot that I needed to delete the image cache files on disk.
In my case it seems that there is something wrong with two of my png images. I tried to save them as a new png image and used that image instead and they compress just fine as webp.
So far, .NET is really lousy in generating PNG files. In Umbraco 8 this was also a problem. When creating a new crop from a png, the resulting PNG (although smaller in number of pixels) would be way larger in file size. My assumption is that PNG's are always rendered in 32bit mode, even though it's not needed.
In my opinion, PNG's should be used as little as possible. For images, use JPG and WEBP and for graphics and icons, use svg. Only use PNG if you need the transparency.
I was forcing to Lossy, not to Lossless and not testing the bit depth - i just assumed Lossy would always be smaller than Lossless, only clicked when I was testing after your reply! :)
Did you try this solution? This code still needs some tweaking though, because I noticed it also caused excessive compression for JPEG images, resulting in low quality WEBP images.
The following code used on a fresh install of Umbraco 11.3.0 converted this 19.0 kB PNG image to an 11.5 kB WEBP image. Without the OnBeforeSaveAsync logic that same image is converted to a 128 kB WEBP.
Don't forget the clear the /umbraco/Data/TEMP/MediaCache folder after changing the code, otherwise you'll just see a previously cached version of the image.
services.AddImageSharp(options =>
{
// Enable Lossless compression for transparent PNG
options.OnBeforeSaveAsync = c =>
{
if (c.Encoder.GetType().Name == "WebpEncoder" && c.Image.PixelType.BitsPerPixel == 32)
{
c.Encoder = new WebpEncoder()
{
FileFormat = WebpFileFormatType.Lossless,
};
}
return Task.CompletedTask;
};
options.OnParseCommandsAsync = c =>
{
if (c.Context != null)
{
var path = c.Context.Request.Path.ToString();
// Don't convert when the noformat query string is set
if (c.Context.Request.QueryString.Value?.Contains("noformat") == true)
c.Commands.Add("noformat", "1");
// Exclude /umbraco/assets and don't convert if WebP is not supported
if (path.Contains("/umbraco/assets/") == false &&
c.Context.Request.GetTypedHeaders().Accept.Any(x => x.MediaType.Value == "image/webp"))
{
// Only convert PNG and JP(E)G to WebP
if (c.Commands.Contains("webp") == false &&
c.Commands.Contains("noformat") == false &&
(path.EndsWith("png") || path.EndsWith("jpg") || path.EndsWith("jpeg")))
{
c.Commands.Remove("format");
c.Commands.Add("format", "webp");
if (c.Commands.Contains("quality") == false)
c.Commands.Add("quality", "92");
c.Context.Response.Headers.Add("Vary", "Accept");
}
}
}
if (c.Commands.Count > 0)
{
// Check width and height to provide very basic security
var width = c.Parser.ParseValue<uint>(
c.Commands.GetValueOrDefault(ResizeWebProcessor.Width),
c.Culture);
var height = c.Parser.ParseValue<uint>(
c.Commands.GetValueOrDefault(ResizeWebProcessor.Height),
c.Culture);
// If width exceeds limit, remove it from request
if (width > 2400)
c.Commands.Remove(ResizeWebProcessor.Width);
// If height exceeds limit, remove it from request
if (height > 2400)
c.Commands.Remove(ResizeWebProcessor.Height);
// Remove format command if noformat command has been set
if (c.Commands.TryGetValue("noformat", out string value))
{
c.Commands.Remove("format");
}
}
return Task.CompletedTask;
};
});
And with JPEG's I'm saving roughly 20% in file size when converted to WEBP. Lowering the quality command value will reduce these file sizes even further, eventually with visible loss of image quality. 91 is the minimum quality required to force the encoder to not use 4:2:0 subsampling (source).
===
EDIT: I also tested your PNG. Without the OnBeforeSaveAsync logic it converted to a 261 kB WEBP. When forcing Lossless compression it converted to a 9.2 kB WEBP.
Which company logo are you referring to? The GymPartner logo? In that case I have no idea why you're getting a completely white image. In my test project the image worked fine with both the Url() and GetCropUrl() methods:
Umbraco 10 WebP file much larger than png
Just updated to version 10 and tried the webp encoding Isn't webp suppose to be smaller, or not atleast 8-10x larger ?
https://appnitecloud.azurewebsites.net/media/c1cn1kpg/gympartnervit.png?format=webp
webp = 125KB
https://appnitecloud.azurewebsites.net/media/c1cn1kpg/gympartnervit.png?format=png
png = 19KB
Hi aerksn,
On first thought, yeah, usually images encoded as webp are supposed to be smaller. Looking at your image though, I see it has very few different colours and if I remember correctly, png is already quite optimised if there are few different colours. I'd say: try the same thing with a photograph of the same dimensions or try it with a jpeg version of the same image and see if webp is still larger than the original image.
kind regards, Dennis
jpeg and all other formats works fine, except all pngs. Don't know if i ever had issue with that before.
Here the exact same image on .framework (umbraco 8) https://www.gympartner.nu/media/nw3dmqcc/gympartnervit.png?format=webp
I did a workaround for now on it, (thanks @Kamil for the tip)
aerksn,
I think in general you're losing performance with this as I think your logo is a bit of a special case. The WebP optimization for other png images should still be (very) helpful.
In your code you're removing format - so you're "reverting" developer intent (I assume you're appending the ?format= to every image src?)
How about doing this in another way. You can implement for example a middleware:
use it:
and then transform your code to something else:
and then WebP works by default for all images except the ones you want to have it disabled, like this:
I tested the above code and it works - just a random saturday idea :)
Hey, this solution works great. Thanks for sharing. One thing I've found however with both solutions is that neither works when you enable CDN URL with the Azure Blob Storage. Any ideas on how to get the WEBP functionality working when utilizing the CDN URL as well?
I'm glad you like it!
I'm not utilising CDN in my project so unfortunately I can't play with it without doing some more setting up for which I don't have time today but I have an idea.
The first thing that came into my mind is that you might need to implement custom image provider - more in the links:
https://our.umbraco.com/forum/using-umbraco-and-getting-started/108123-umbraco9-local-and-remote-images
https://github.com/SixLabors/ImageSharp.Web/discussions/167
Let us know how you get on!
Thanks for the reply and thanks to all of you for your tips. I've tried and tried different approaches to get to a good solution that works for me.
After some digging in the encoder i found "Image.PixelType.BitsPerPixel" and found a correlation between png and jpegs.
The jpges are 24px and pngs are 32px, in that way i could sepparate them. And depending on that use different compression types. Which made it all work. Haha, 🙌
So now i'm getting the images in correct size.
Idon't auctually know what i'm doing but it works... haha
(btw you can edit all properties in the encoder object as well if you want to tweak it even more)
I believe the 32 bits per pixel has to do with transparency: an additional 8 bits for the alpha channel. So I guess it's a good way to determine if a PNG contains transparency. However, I'm not sure if using Lossless WebP encoding for 32 bpp images will always result in a smaller image than the original PNG. It may depend on the image, see the following discussion:
https://github.com/SixLabors/ImageSharp/discussions/1957
Have you experienced any issues with your code? Or did all the JPG and PNG images that you run through this code have a smaller WebP encoded image?
Hey there, just curious, how do you enable webp encoding for images in v10? Just adding the ?format=webp still shows encoding as the original format in the network tab for me.
Matthew, I came across same problem despite hearing that it should happen OOTB.
I've enabled the ImageSharp2 manually, in Startup.cs: in ConfigureServices:
in Configure:
PS If these methods aren't found there's a nuget SixLabors.ImageSharp in v2.1.2
PS2 Remember to add the above line before UseStaticFiles!
Thank you for the tip, it looks like that worked!
Hey Kamil!
I've tried your code in V10, however it doesn't seem to work.
Nothing inside of /Media hits your middleware, I presume this is due to the way Umbraco is now registering the static assets in program.cs rather than startup.cs?
I seem to have the same problem in my Umbraco 10 project. Some png images works just fine to convert to webp but some become huge when converted. I have one png image that is about 50kb but when i add format=webp it becomes 1.1 mb. I'm guessing that Image Sharp uses lossless format for this image for some reason. I have tried some of the solutions suggested here but no luck. I have disabled UseStaticFiles but that din't give any effect. I would like to configure Image Sharp to always use lossy compression but I can't seem to figure out how to do that. I tried aerksn solution but it doesn't work for me. The code never runs in my solution.
Edit: aerksns solution does work in my project. I just forgot that I needed to delete the image cache files on disk. In my case it seems that there is something wrong with two of my png images. I tried to save them as a new png image and used that image instead and they compress just fine as webp.
Could you share the problematic PNG image? I'd like to do some testing.
So far, .NET is really lousy in generating PNG files. In Umbraco 8 this was also a problem. When creating a new crop from a png, the resulting PNG (although smaller in number of pixels) would be way larger in file size. My assumption is that PNG's are always rendered in 32bit mode, even though it's not needed.
In my opinion, PNG's should be used as little as possible. For images, use JPG and WEBP and for graphics and icons, use svg. Only use PNG if you need the transparency.
WEBP also offers transparency, so you could avoid using PNG altogether.
I am also experiencing this issue, trying with multiple transparent PNG's - all tests have resulted in around 8-10x larger webp, even if forced lossy.
If I convert to webp via cloudconvert, they come back 10x smaller.
I downloaded a publicly available example PNG with transparency. available here
The original is 50kb. After running through ImageSharp, the resulting file is 958kb!! almost 20x larger.
The same file run through cloudconvert returns a 28k
Has anyone seen this work as expected with transparent PNGs?
The hack mentioned earlier gave me a good result with that sample file:
content-length: 14698
Thanks Arjan,
I was forcing to Lossy, not to Lossless and not testing the bit depth - i just assumed Lossy would always be smaller than Lossless, only clicked when I was testing after your reply! :)
Hi. I've kind of successfully implemented the webp configurations described in this post (and summarized in https://umbhost.net/blog/2022/06/dynamic-webp-images-in-umbraco-v10), but I still have a hard time getting my transparent pngs in a reasonable size.
Served as pngs they are around 12kb but as webp they get about 200kb.
Has anyone found a solution for this?
Did you try this solution? This code still needs some tweaking though, because I noticed it also caused excessive compression for JPEG images, resulting in low quality WEBP images.
Hi. Yeah I added the fix but didn't notice any difference at all.
Could you share one of the PNG's that becomes very big when converted to WEBP?
Here's one. I hope Pasteboard hasn't done anything with it while uploading. I dont think so, because it has the same bytesize.
https://pasteboard.co/S2cWsEbhSJAg.png
I am pretty sure we just need Umbraco to update Imagesharp to v3. This issue is listed in the things v3 has fixed. https://sixlabors.com/posts/announcing-imagesharp-300/
The following code used on a fresh install of Umbraco 11.3.0 converted this 19.0 kB PNG image to an 11.5 kB WEBP image. Without the
OnBeforeSaveAsync
logic that same image is converted to a 128 kB WEBP.Don't forget the clear the
/umbraco/Data/TEMP/MediaCache
folder after changing the code, otherwise you'll just see a previously cached version of the image.And with JPEG's I'm saving roughly 20% in file size when converted to WEBP. Lowering the
quality
command value will reduce these file sizes even further, eventually with visible loss of image quality. 91 is the minimum quality required to force the encoder to not use 4:2:0 subsampling (source).===
EDIT: I also tested your PNG. Without the
OnBeforeSaveAsync
logic it converted to a 261 kB WEBP. When forcing Lossless compression it converted to a 9.2 kB WEBP.Thanks Arjan! N00b me hadn't cleared the mediacache folder, so the image got smaller for me to!
I noticed though that the company logo that is a transparent png gets converted to a completely white image. Any idea what could be going on there?
Which company logo are you referring to? The GymPartner logo? In that case I have no idea why you're getting a completely white image. In my test project the image worked fine with both the
Url()
andGetCropUrl()
methods:is working on a reply...