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

Processing CMYK skews colors #523

Closed
dsyno opened this issue May 17, 2018 · 6 comments
Closed

Processing CMYK skews colors #523

dsyno opened this issue May 17, 2018 · 6 comments

Comments

@dsyno
Copy link

dsyno commented May 17, 2018

Description

Processing a CMYK image (in this case, resizing it) drastically skews the colors. Most likely occurring when it's converted to RGB.

Is there a way to resize a CMYK image while keeping the colors as similar as possible?

Code

SKBitmap srcBitmap = ...
// Use an CMYK image
int resizedWidth = 510, resizedHeight = 720; // random dimensions

// resize and save
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. "After-resize.jpg"
}

Expected Behavior

Conversion from CMYK to RGB should convert to fairly similar colors.

Actual Behavior

Drastic color difference when CMYK gets converted to RGB.

Basic Information

  • Version with issue: 1.60.0
  • IDE: Visual Studio
  • Platform Target Frameworks: .NET Core 2.0 (AWS Lambda)

Screenshots

compare

@mattleibow
Copy link
Contributor

This may be a result of you loosing the color space info. The SKImageInfo type has a ColorSpace property. Maybe copy the value from srcBitmap.ColorSpace.

In fact, it may be better to just take the srcBitmap.Info value and change the dimensions. This way you can keep any future properties as well as the color type and alpha type. I do know that some color types are read-only, so that may fail - in that case just copy the color space.

@dsyno
Copy link
Author

dsyno commented May 18, 2018

Unfortunately, that doesn't work.

In fact, simply creating a SKBitmap from the stream, then saving the bitmap, causes the colors to be skewed.

What am I missing?

Code

SKBitmap srcBitmap = ...
// use the attached sample CMYK image

// save
using (SKImage image = SKImage.FromBitmap(srcBitmap))
using (SKData data = image.Encode(SKEncodedImageFormat.Jpeg, 100))
using (Stream imgStream = data.AsStream())
{
	// save the stream and look at the image.
}

Sample CMYK image (use this with the code above)

(Note that the colors of this CMYK image will look different depending on which browser you are using. e.g. Chrome, Firefox, etc; I recommend using Chrome or IE.)
channel_digital_image_cmyk_color

Result

samplecmyk_afterskia

@dsyno
Copy link
Author

dsyno commented May 19, 2018

The issue has been resolved. As you mentioned, it was a ColorSpace issue.

The key piece of info for anyone else reading this: ColorSpace must be set on the source object AND the destination object (SKBitmap, SKImage, SKSurface, etc). This is so Skia can know how to convert the colors between the source and destination. If the ColorSpace is not set on either of those, or if either ColorSpace gets lost along the way (which easily happens when you're creating new objects), Skia will use default settings which can skew the color conversion.

Example of maintaining ColorSpace and converting CMYK to sRGB:

using (SKData origData = SKData.Create(imgStream)) // convert the stream into SKData
using (SKImage srcImg = SKImage.FromEncodedData(origData))
	// srcImg now contains the original `ColorSpace` (e.g. CMYK)
{
	SKImageInfo info = new SKImageInfo(resizeWidth, resizeHeight,
		SKImageInfo.PlatformColorType, SKAlphaType.Premul, SKColorSpace.CreateSrgb());
	// this is the important part. set the destination `ColorSpace` as
	// `SKColorSpace.CreateSrgb()`. Skia will then automatically convert the original CMYK
	// colorspace, to this new sRGB colorspace. (Though the conversion is extremely slow!
	// More on this in at the end of this post.)

	using (SKImage newImg = SKImage.Create(info)) // new image. `ColorSpace` set via `info`
	{
		srcImg.ScalePixels(newImg.PeekPixels(), SKFilterQuality.None);
		// now when doing this resize, Skia knows the original `ColorSpace`, and the
		// destination `ColorSpace`, and converts the colors from CMYK to sRGB.
	}
}

Another way to do the CMYK to sRGB conversion:

using (SKCodec codec = SKCodec.Create(imgStream)) // create a codec with the imgStream
{
	SKImageInfo info = new SKImageInfo(codec.Info.Width, codec.Info.Height,
		SKImageInfo.PlatformColorType, SKAlphaType.Premul, SKColorSpace.CreateSrgb());
	// set the destination `ColorSpace` via SKColorSpace.CreateSrgb()

	SKBitmap srcImg = SKBitmap.Decode(codec, info);
	// Skia creates a new bitmap, converting the codec `ColorSpace` (e.g. CMYK) to the
	// destination ColorSpace (sRGB)
}

@mattleibow, feel free to correct me, or clarify any of this to make it easier for others to understand. Thank you!

On a related note, the operation to convert from CMYK to RGB is EXTREMELY slow. When I simply added SKColorSpace.CreateSrgb() to my code (so Skia then converted CMYK to RGB), the processing time went from 3 seconds to 30 seconds!... for one (4958x6990px) CMYK image.

Do you want me to open a new ticket for this CMYK-to-RGB slowness? Or do you already know the answer, and whether anything can be done to speed it up?

@mattleibow
Copy link
Contributor

I haven't done much work with the color space area, but what happens if you use the color space from the codec?

I know in SKBitmap.Decode(...) i explicitly set the color space to null for backwards compatibility. But if this is really breaking things, I probably should not do this.

@dsyno
Copy link
Author

dsyno commented May 20, 2018

I haven't tried using the ColorSpace from the codec, because I actually want to convert the CMYK to RGB. I imagine using the codec ColorSpace to apply it to the destination, would keep it all in the CMYK ColorSpace. (On a related note, you had mentioned that "some color types are read-only". If that's the case, it seems you could still copy all the image info, while replacing the ColorType with your preference: SKImageInfo newInfo = originalInfo.WithColorType(SKImageInfo.PlatformColorType);)

Regarding your other comment, if you're setting the SKBitmap.Decode(...) ColorSpace to null, I imagine that'll definitely cause color issues, especially if you're discarding a previously set ColorSpace. (Are you saying that's what the SkiaSharp source code is doing? If so, that may explain why the ColorSpace has been getting lost.)

Here is where I learned this: https://skia.org/user/sample/color

Please read the whole page (it's short and packed with great information that you may find very useful), and the last part was especially helpful:

Opting In To Color Correct Skia

By itself, adding a color space tag to a source will not change draw behavior. In fact, tagging sources with color spaces is always a best practice, regardless of whether or not we want Skia’s color correct behavior.

Adding a color space tag to the destination is the trigger that turns on Skia color correct behavior.

Drawing a source without a color space to a destination with a color space is undefined. Skia cannot know how to draw without knowing the color space of the source.

colorspace

@mattleibow
Copy link
Contributor

Closing this as it has been resolved by setting a colorspace.

@ghost ghost locked as resolved and limited conversation to collaborators Aug 19, 2022
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants