Resizing via SKImage is blurry. Resizing via SKBitmap is great.
dsyno opened this issue ยท 12 comments
Description
As @AndersMad discovered in #319 SKImage resizes very blurry, whereas resizing a SKBitmap works great.
Code
SKBitmap srcBitmap = ...
// Use attached "boat.jpg". A large image like this provides a more dramatic resize comparison
// (e.g. 5400x3615px). The more dramatic the resize, the more dramatic the resize quality between
// SKImage and SKBitmap.
int resizedWidth = 160, resizedHeight = 107;
// SKImage resizes blurry
using (var surface = SKSurface.Create(resizedWidth, resizedHeight, SKImageInfo.PlatformColorType,
SKAlphaType.Premul))
using (var paint = new SKPaint())
{
// high quality with antialiasing
paint.IsAntialias = true;
paint.FilterQuality = SKFilterQuality.High;
// draw the bitmap to fill the surface
surface.Canvas.DrawBitmap(srcBitmap, new SKRectI(0, 0, resizedWidth, resizedHeight),
paint);
surface.Canvas.Flush();
// save
using (var newImg = surface.Snapshot())
using (SKData data = newImg.Encode(SKEncodedImageFormat.Jpeg, 100))
using (Stream imgStream = data.AsStream())
{
// save the stream and look at the image. e.g. "blurry.jpg"
}
}
// SKBitmap resizes crisp.
SKImageInfo resizeInfo = new SKImageInfo(resizedWidth, resizedHeight);
using (SKBitmap resizedSKBitmap = srcBitmap.Resize(resizeInfo, SKBitmapResizeMethod.Lanczos3))
using (SKImage newImg = SKImage.FromPixels(resizedSKBitmap.PeekPixels()))
using (SKData data = newImg.Encode(SKEncodedImageFormat.Jpeg, jpegQuality))
using (Stream imgStream = data.AsStream())
{
// save the stream and look at the image. e.g. "crisp.jpg"
}
Expected Behavior
SKImage should be able to resize with the same good quality as SKBitmap
Actual Behavior
SKImage resizes very blurry.
Basic Information
- Version with issue: 1.60.0
- Last known good version:
- IDE: Visual Studio
- Platform Target Frameworks: .NET Core 2.0 (AWS Lambda)
Attachments
As of v1.68, this is not a problem anymore. Google has improved the resize APIs and consolidated them into the SKPixmap.ScalePixels.
Then, we added more members to the 3 imaging types (SKPixmap, SKImage, SKBitmap) to be consistent:
ScalePixelsscales the current pixels and puts the result into the provided container - typically aSKPixmapResizescales the current and returns them in the container that resized them - right now, just forSKBitmap
Q & FYI for others who scale:
The ScalePixels takes SKFilterQuality - but the old SKPixmap.Resize could take SKBitmapResizeMethod.Lanczos3. In the new version Lanczos3 is converted to SKFilterQuality.Medium.
Lanczos3 is better imho, even compared to SKFilterQuality.High for smaller images / preserves sharpness (did a blind test with a few colleagues)..
Is there an alternative way than SKFilterQuality.High to bring back the better and sharper scaling of images like Lanczos3? Or maybe this is just how Skia rolls now .oO( making Chrome images scale with lower quality in the future ).
Look at face and dress (click on image to see org. to compare):
Old SKBitmapResizeMethod.Lanczos3:

UPDATE!: I applied a small sharpening filter (SKImageFilter) and now down-scaled images is actually better than before - maybe in a "cheat" way(?)..
Thanks @AndersMad for testing this. So with your updated comment, what combination are you using now?... SKFilterQuality.High plus the sharpening filter?
P.S. Please include the code you're using for the "sharpening filter". Perhaps it only became available in v1.68 because I don't see it available anywhere.
@dsyno before i used ScalePixels with some mem overhead when the image needed to scale and crop.. But now I create a canvas and do a DrawBitmap on that - think this has less overhead when dealing with offsets and cropping etc. First mode was crisp before update - and the last mode was not usable as it was very blurry. After update they where both semi blurry :/ win some lose some.. Overall the performance is noticeable better though.
Now I i do a SKImageFilter.CreateMatrixConvolution with a tiny bit of sharpening before the draw - and it does seem to work nicely when scaling big to small.
So for now I have this (hope this "hacky cheat mode" helps):
using (var surface = SKSurface.Create(info)) {
using (var canvas = surface.Canvas) {
using (var paint = new SKPaint()) {
paint.FilterQuality = SKFilterQuality.High;
paint.IsAntialias = false;
paint.IsDither = false;
if (codec.EncodedOrigin == SKEncodedOrigin.RightTop) {
canvas.RotateDegrees(90);
canvas.Translate(0, -w);
}
var kernel = new float[9] {
0, -.1f, 0,
-.1f, 1.4f, -.1f,
0, -.1f, 0,
};
var kernelSize = new SKSizeI(3, 3);
var kernelOffset = new SKPointI(1, 1);
paint.ImageFilter = SKImageFilter.CreateMatrixConvolution(
kernelSize, kernel, 1f, 0f, kernelOffset,
SKMatrixConvolutionTileMode.Clamp, false);
canvas.DrawBitmap(bmp,
SKRect.Create(0, 0, w, h),
SKRect.Create(-thumbInfo.OffsetX, -thumbInfo.OffsetY, thumbInfo.NewWidth, thumbInfo.NewHeight),
paint
);Wow. Bummer all that has to be done @AndersMad, instead of Skia resize just working properly.
It doesn't seem this was fixed properly afterall, @mattleibow. It appears it still resizes blurry and hoops have to be jumped through to get a good resize.
NOTE: It is less blurry than before - it's just missing some "crispyness" - and my cheat way actually scored higher in the blind test.
I believe its 100% the Skia and has nothing to do with SkiaSharp. My guess is - that if you use GPU/OpenGL it will be crispy - its the software scaling that's off. As Skia is primarily used for Chrome canvas - GPU rendering is the focus as almost all browsers/devices has that - and software part just a bloater (again, just me guessing). Not using Lanczos3 might be the thing that makes it faster.
I have been following along and I believe that @AndersMad is probably using the resize idea as it was actually designed. SkiaSharp (and skia) is primarily a drawing API, not an image processor, and as such, they have split concepts into separate steps.
If you are using an image processing library, this is typically the case where things are batched or single-run. And, you often provide some image, perform a task (such as resize), and then output the best quality.
With SkiaSharp, you are actually going to be working with a different idea. You start with nothing and then draw what you want as fast as possible, with good-enough quality. Since this is meant for drawing a UI, chances are the user won't notice any minor flaws (as can be seen in the general improvement for the basic bitmap resize).
However!
SkiaSharp does not leave you there and provides a series of filters, shaders and effects to help provide an even better image. And this is where I think @AndersMad comes in. The base resize will always be faster that the previous version because that is what it is designed to do - be fast. But, the user code will pull together the various features and make it the best as possible.
I don't think that SkiaSharp will ever provide all the little operations to get the best image, as this is more for additional libraries or frameworks. Think of SkiaSharp as the core drawing engine - it does the steps as fast as possible, but leaves the actual big chunks to libraries.
With regards to an additional library, I have been trying to collect together a few useful APIs that provide an additional layer of usefulness to SkiaSharp. Right now, there is just a few extra geometry and a basic path interpolation: https://github.com/mono/SkiaSharp.Extended/tree/master/SkiaSharp.Extended
But, I hope to slowly add features here to make things useful. One example would be a set of extension methods that could take the basic Resize method and turn it into a real, high-quality process. Or even a new type that can be much more useful.
If this really is an issue that needs solving within the library, please open an issue there and share code and PRs. As that is a satellite library, it is much, much easier to work with as it just depends on a NuGet.
If you create an issue in the extended repo, just link back to this so we have some code to start with and can initiate a discussion.
This might be related: "Anti-aliasing change between m65 and m69" https://groups.google.com/forum/#!topic/skia-discuss/QrJadRQR5A4 - only for CPU
@AndersMad please include the c# code you used... aka in ur update u say you applied SKImageFilter could you include code sample so i can see what u did
@AndersMad could u please had the full code! so we can see how this is done.




