Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Resizing via SKImage is blurry. Resizing via SKBitmap is great. #520

Closed
dsyno opened this issue May 15, 2018 · 10 comments

Comments

Projects
3 participants
@dsyno
Copy link

commented May 15, 2018

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

boat.jpg
boat

blurry.jpg (SKImage resize)
blurry

crisp.jpg (SKBitmap resize)
crisp

@mattleibow mattleibow added this to New in Triage via automation May 18, 2018

@mattleibow mattleibow added this to To do in v1.68.0 via automation Nov 29, 2018

@mattleibow mattleibow removed this from Needs Triage in Triage Nov 29, 2018

@mattleibow mattleibow added this to the v1.68.0 milestone Nov 29, 2018

@mattleibow

This comment has been minimized.

Copy link
Contributor

commented Nov 29, 2018

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:

  • ScalePixels scales the current pixels and puts the result into the provided container - typically a SKPixmap
  • Resize scales the current and returns them in the container that resized them - right now, just for SKBitmap

@mattleibow mattleibow closed this Nov 29, 2018

v1.68.0 automation moved this from To do to Done Nov 29, 2018

@AndersMad

This comment has been minimized.

Copy link

commented Dec 6, 2018

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

New SKFilterQuality.High:
h-q

Old SKBitmapResizeMethod.Lanczos3:
lanczos3

UPDATE!: I applied a small sharpening filter (SKImageFilter) and now down-scaled images is actually better than before - maybe in a "cheat" way(?)..

New with sharpen filter:
h-q-f

@dsyno

This comment has been minimized.

Copy link
Author

commented Dec 6, 2018

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.

@AndersMad

This comment has been minimized.

Copy link

commented Dec 6, 2018

@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
			);
@dsyno

This comment has been minimized.

Copy link
Author

commented Dec 6, 2018

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.

@AndersMad

This comment has been minimized.

Copy link

commented Dec 7, 2018

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.

@mattleibow

This comment has been minimized.

Copy link
Contributor

commented Dec 7, 2018

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.

@mattleibow

This comment has been minimized.

Copy link
Contributor

commented Dec 7, 2018

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.

https://github.com/mono/SkiaSharp.Extended

@mattleibow

This comment has been minimized.

Copy link
Contributor

commented Dec 7, 2018

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.

@AndersMad

This comment has been minimized.

Copy link

commented Dec 10, 2018

This might be related: "Anti-aliasing change between m65 and m69" https://groups.google.com/forum/#!topic/skia-discuss/QrJadRQR5A4 - only for CPU

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.