From b45718af6dc2806ca6591bc9ee085086e7c3d07e Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Mon, 18 Jan 2016 23:10:16 +1100 Subject: [PATCH] Color is now linear by default. Former-commit-id: 3869ebe4d426df2fba1e2996c2fed464a0a55357 Former-commit-id: 549f0ac778eb8b1a12d7b7e38280f360aa71ad59 Former-commit-id: 144be0509d7ce2e2702b13b1ea60f708083e4912 --- src/ImageProcessor/Colors/Color.cs | 2 +- src/ImageProcessor/Colors/ColorDefinitions.cs | 2 +- src/ImageProcessor/Colors/ColorTransforms.cs | 2 +- .../Colors/ColorspaceTransforms.cs | 6 +-- .../Colors/Colorspaces/CieLab.cs | 2 - .../Colors/Colorspaces/CieXyz.cs | 2 - src/ImageProcessor/Filters/Brightness.cs | 4 +- .../Filters/ColorMatrix/ColorMatrixFilter.cs | 6 +-- src/ImageProcessor/Filters/ColorMatrix/Hue.cs | 2 +- .../Filters/ColorMatrix/Sepia.cs | 2 +- src/ImageProcessor/Filters/Contrast.cs | 4 +- .../Formats/Bmp/BmpDecoderCore.cs | 50 +++++++++++++------ src/ImageProcessor/Formats/Bmp/BmpEncoder.cs | 4 +- src/ImageProcessor/Formats/Jpg/JpegDecoder.cs | 25 +++++++--- src/ImageProcessor/Formats/Jpg/JpegEncoder.cs | 3 +- src/ImageProcessor/Samplers/Resampler.cs | 6 +-- 16 files changed, 72 insertions(+), 50 deletions(-) diff --git a/src/ImageProcessor/Colors/Color.cs b/src/ImageProcessor/Colors/Color.cs index 44ed036e3..3d04a0a41 100644 --- a/src/ImageProcessor/Colors/Color.cs +++ b/src/ImageProcessor/Colors/Color.cs @@ -11,7 +11,7 @@ namespace ImageProcessor /// /// Represents a four-component color using red, green, blue, and alpha data. - /// Each component is stored in premultiplied format multiplied by the alpha component. + /// Each component is stored in a linear premultiplied format multiplied by the alpha component. /// /// /// This struct is fully mutable. This is done (against the guidelines) for the sake of performance, diff --git a/src/ImageProcessor/Colors/ColorDefinitions.cs b/src/ImageProcessor/Colors/ColorDefinitions.cs index 061b509de..78ea3fbca 100644 --- a/src/ImageProcessor/Colors/ColorDefinitions.cs +++ b/src/ImageProcessor/Colors/ColorDefinitions.cs @@ -7,7 +7,7 @@ namespace ImageProcessor { /// /// Represents a four-component color using red, green, blue, and alpha data. - /// Each component is stored in premultiplied format multiplied by the alpha component. + /// Each component is stored in a linear premultiplied format multiplied by the alpha component. /// /// /// This struct is fully mutable. This is done (against the guidelines) for the sake of performance, diff --git a/src/ImageProcessor/Colors/ColorTransforms.cs b/src/ImageProcessor/Colors/ColorTransforms.cs index 43bc03251..9551222fb 100644 --- a/src/ImageProcessor/Colors/ColorTransforms.cs +++ b/src/ImageProcessor/Colors/ColorTransforms.cs @@ -9,7 +9,7 @@ namespace ImageProcessor /// /// Represents a four-component color using red, green, blue, and alpha data. - /// Each component is stored in premultiplied format multiplied by the alpha component. + /// Each component is stored in a linear premultiplied format multiplied by the alpha component. /// /// /// This struct is fully mutable. This is done (against the guidelines) for the sake of performance, diff --git a/src/ImageProcessor/Colors/ColorspaceTransforms.cs b/src/ImageProcessor/Colors/ColorspaceTransforms.cs index 334c967fb..ea08973fe 100644 --- a/src/ImageProcessor/Colors/ColorspaceTransforms.cs +++ b/src/ImageProcessor/Colors/ColorspaceTransforms.cs @@ -9,7 +9,7 @@ namespace ImageProcessor /// /// Represents a four-component color using red, green, blue, and alpha data. - /// Each component is stored in premultiplied format multiplied by the alpha component. + /// Each component is stored in a linear premultiplied format multiplied by the alpha component. /// /// /// This struct is fully mutable. This is done (against the guidelines) for the sake of performance, @@ -86,7 +86,7 @@ namespace ImageProcessor float g = (x * -0.9689F) + (y * 1.8758F) + (z * 0.0415F); float b = (x * 0.0557F) + (y * -0.2040F) + (z * 1.0570F); - return Color.Compress(new Color(r, g, b)); + return new Color(r, g, b); } /// @@ -226,7 +226,7 @@ namespace ImageProcessor float g = (x * -0.9689F) + (y * 1.8758F) + (z * 0.0415F); float b = (x * 0.0557F) + (y * -0.2040F) + (z * 1.0570F); - return Color.Compress(new Color(r, g, b)); + return new Color(r, g, b); } /// diff --git a/src/ImageProcessor/Colors/Colorspaces/CieLab.cs b/src/ImageProcessor/Colors/Colorspaces/CieLab.cs index d5875da07..c1b64134d 100644 --- a/src/ImageProcessor/Colors/Colorspaces/CieLab.cs +++ b/src/ImageProcessor/Colors/Colorspaces/CieLab.cs @@ -79,8 +79,6 @@ namespace ImageProcessor public static implicit operator CieLab(Color color) { // First convert to CIE XYZ - color = Color.Expand(color); - float x = (color.R * 0.4124F) + (color.G * 0.3576F) + (color.B * 0.1805F); float y = (color.R * 0.2126F) + (color.G * 0.7152F) + (color.B * 0.0722F); float z = (color.R * 0.0193F) + (color.G * 0.1192F) + (color.B * 0.9505F); diff --git a/src/ImageProcessor/Colors/Colorspaces/CieXyz.cs b/src/ImageProcessor/Colors/Colorspaces/CieXyz.cs index ab63c868d..8c609dc0d 100644 --- a/src/ImageProcessor/Colors/Colorspaces/CieXyz.cs +++ b/src/ImageProcessor/Colors/Colorspaces/CieXyz.cs @@ -79,8 +79,6 @@ namespace ImageProcessor /// public static implicit operator CieXyz(Color color) { - color = Color.Expand(color); - float x = (color.R * 0.4124F) + (color.G * 0.3576F) + (color.B * 0.1805F); float y = (color.R * 0.2126F) + (color.G * 0.7152F) + (color.B * 0.0722F); float z = (color.R * 0.0193F) + (color.G * 0.1192F) + (color.B * 0.9505F); diff --git a/src/ImageProcessor/Filters/Brightness.cs b/src/ImageProcessor/Filters/Brightness.cs index 6a5ae3985..08165037c 100644 --- a/src/ImageProcessor/Filters/Brightness.cs +++ b/src/ImageProcessor/Filters/Brightness.cs @@ -50,12 +50,12 @@ namespace ImageProcessor.Filters { for (int x = startX; x < endX; x++) { - Color color = Color.Expand(source[x, y]); + Color color = source[x, y]; Vector3 vector3 = color.ToVector3(); vector3 += new Vector3(brightness); - target[x, y] = Color.Compress(new Color(vector3, color.A)); + target[x, y] = new Color(vector3, color.A); } } }); diff --git a/src/ImageProcessor/Filters/ColorMatrix/ColorMatrixFilter.cs b/src/ImageProcessor/Filters/ColorMatrix/ColorMatrixFilter.cs index d63732751..396d5cda1 100644 --- a/src/ImageProcessor/Filters/ColorMatrix/ColorMatrixFilter.cs +++ b/src/ImageProcessor/Filters/ColorMatrix/ColorMatrixFilter.cs @@ -17,7 +17,7 @@ namespace ImageProcessor.Filters public abstract Matrix4x4 Matrix { get; } /// - public virtual bool Compand => true; + public virtual bool Compand => false; /// protected override void Apply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle, int startY, int endY) @@ -57,11 +57,11 @@ namespace ImageProcessor.Filters if (compand) { - color = Color.Expand(color); + color = Color.Compress(color); } Vector3 transformed = Vector3.Transform(color.ToVector3(), matrix); - return compand ? Color.Compress(new Color(transformed, color.A)) : new Color(transformed, color.A); + return compand ? Color.Expand(new Color(transformed, color.A)) : new Color(transformed, color.A); } } } diff --git a/src/ImageProcessor/Filters/ColorMatrix/Hue.cs b/src/ImageProcessor/Filters/ColorMatrix/Hue.cs index a77f6ff09..9acf9e3bb 100644 --- a/src/ImageProcessor/Filters/ColorMatrix/Hue.cs +++ b/src/ImageProcessor/Filters/ColorMatrix/Hue.cs @@ -37,7 +37,7 @@ public override Matrix4x4 Matrix => this.matrix; /// - public override bool Compand => false; + public override bool Compand => true; /// protected override void OnApply(ImageBase source, ImageBase target, Rectangle targetRectangle, Rectangle sourceRectangle) diff --git a/src/ImageProcessor/Filters/ColorMatrix/Sepia.cs b/src/ImageProcessor/Filters/ColorMatrix/Sepia.cs index cab932b25..db5020271 100644 --- a/src/ImageProcessor/Filters/ColorMatrix/Sepia.cs +++ b/src/ImageProcessor/Filters/ColorMatrix/Sepia.cs @@ -28,6 +28,6 @@ namespace ImageProcessor.Filters }; /// - public override bool Compand => false; + public override bool Compand => true; } } diff --git a/src/ImageProcessor/Filters/Contrast.cs b/src/ImageProcessor/Filters/Contrast.cs index da55507ff..97216f4cc 100644 --- a/src/ImageProcessor/Filters/Contrast.cs +++ b/src/ImageProcessor/Filters/Contrast.cs @@ -51,11 +51,11 @@ namespace ImageProcessor.Filters { for (int x = startX; x < endX; x++) { - Vector4 color = Color.Expand(source[x, y]).ToVector4(); + Vector4 color = source[x, y].ToVector4(); color -= shiftVector; color *= contrastVector; color += shiftVector; - target[x, y] = Color.Compress(new Color(color)); + target[x, y] = new Color(color); } } }); diff --git a/src/ImageProcessor/Formats/Bmp/BmpDecoderCore.cs b/src/ImageProcessor/Formats/Bmp/BmpDecoderCore.cs index c11f2d7c7..e058d48f0 100644 --- a/src/ImageProcessor/Formats/Bmp/BmpDecoderCore.cs +++ b/src/ImageProcessor/Formats/Bmp/BmpDecoderCore.cs @@ -7,6 +7,7 @@ namespace ImageProcessor.Formats { using System; using System.IO; + using System.Numerics; using System.Threading.Tasks; /// @@ -223,11 +224,17 @@ namespace ImageProcessor.Formats int arrayOffset = ((row * width) + (colOffset + shift)) * 4; // We divide by 255 as we will store the colors in our floating point format. + // Default colorspace is sRGB TODO: Check if we can detect this. // Stored in r-> g-> b-> a order. - imageData[arrayOffset] = colors[colorIndex + 2] / 255f; // r - imageData[arrayOffset + 1] = colors[colorIndex + 1] / 255f; // g - imageData[arrayOffset + 2] = colors[colorIndex] / 255f; // b - imageData[arrayOffset + 3] = 1; // a + // Expand from sRGB to linear RGB + Color color = + Color.Expand( + new Color(new Vector3(colors[colorIndex + 2], colors[colorIndex + 1], colors[colorIndex]) / 255f)); + + imageData[arrayOffset] = color.R; // r + imageData[arrayOffset + 1] = color.G; // g + imageData[arrayOffset + 2] = color.B; // b + imageData[arrayOffset + 3] = color.A; // a } } }); @@ -270,11 +277,14 @@ namespace ImageProcessor.Formats int arrayOffset = ((row * width) + x) * 4; + // Expand from sRGB to linear RGB + Color color = Color.Expand(new Color(r, g, b, 1)); + // Stored in r-> g-> b-> a order. - imageData[arrayOffset] = r; - imageData[arrayOffset + 1] = g; - imageData[arrayOffset + 2] = b; - imageData[arrayOffset + 3] = 1; + imageData[arrayOffset] = color.R; + imageData[arrayOffset + 1] = color.G; + imageData[arrayOffset + 2] = color.B; + imageData[arrayOffset + 3] = color.A; } }); } @@ -305,12 +315,15 @@ namespace ImageProcessor.Formats int offset = rowOffset + (x * 3); int arrayOffset = ((row * width) + x) * 4; + // Expand from sRGB to linear RGB + Color color = Color.Expand(new Color(new Vector3(data[offset + 2], data[offset + 1], data[offset]) / 255f, 1)); + // We divide by 255 as we will store the colors in our floating point format. // Stored in r-> g-> b-> a order. - imageData[arrayOffset] = data[offset + 2] / 255f; - imageData[arrayOffset + 1] = data[offset + 1] / 255f; - imageData[arrayOffset + 2] = data[offset] / 255f; - imageData[arrayOffset + 3] = 1; + imageData[arrayOffset] = color.R; + imageData[arrayOffset + 1] = color.G; + imageData[arrayOffset + 2] = color.B; + imageData[arrayOffset + 3] = color.A; } }); } @@ -343,10 +356,15 @@ namespace ImageProcessor.Formats // We divide by 255 as we will store the colors in our floating point format. // Stored in r-> g-> b-> a order. - imageData[arrayOffset] = data[offset + 2] / 255f; - imageData[arrayOffset + 1] = data[offset + 1] / 255f; - imageData[arrayOffset + 2] = data[offset] / 255f; - imageData[arrayOffset + 3] = 1; // TODO: Can we use our real alpha here? + + // Expand from sRGB to linear RGB + // TODO: Can we use our real alpha here? + Color color = Color.Expand(new Color(new Vector3(data[offset + 2], data[offset + 1], data[offset]) / 255f, 1)); + + imageData[arrayOffset] = color.R; + imageData[arrayOffset + 1] = color.G; + imageData[arrayOffset + 2] = color.B; + imageData[arrayOffset + 3] = color.A; } }); } diff --git a/src/ImageProcessor/Formats/Bmp/BmpEncoder.cs b/src/ImageProcessor/Formats/Bmp/BmpEncoder.cs index ad80bbc76..cd955612c 100644 --- a/src/ImageProcessor/Formats/Bmp/BmpEncoder.cs +++ b/src/ImageProcessor/Formats/Bmp/BmpEncoder.cs @@ -110,13 +110,13 @@ namespace ImageProcessor.Formats // Limit the output range and multiply out from our floating point. // Convert back to b-> g-> r-> a order. - // Convert to non-premultiplied color. + // Convert to non-premultiplied sRGB color. float r = data[offset]; float g = data[offset + 1]; float b = data[offset + 2]; float a = data[offset + 3]; - Bgra32 color = Color.ToNonPremultiplied(new Color(r, g, b, a)); + Bgra32 color = Color.ToNonPremultiplied(Color.Compress(new Color(r, g, b, a))); writer.Write(color.B); writer.Write(color.G); diff --git a/src/ImageProcessor/Formats/Jpg/JpegDecoder.cs b/src/ImageProcessor/Formats/Jpg/JpegDecoder.cs index 2f456dfc0..6ffcafe06 100644 --- a/src/ImageProcessor/Formats/Jpg/JpegDecoder.cs +++ b/src/ImageProcessor/Formats/Jpg/JpegDecoder.cs @@ -7,6 +7,7 @@ namespace ImageProcessor.Formats { using System; using System.IO; + using System.Numerics; using System.Threading.Tasks; using BitMiracle.LibJpeg; @@ -117,10 +118,14 @@ namespace ImageProcessor.Formats int offset = ((y * pixelWidth) + x) * 4; - pixels[offset + 0] = sample[0] / 255f; - pixels[offset + 1] = sample[1] / 255f; - pixels[offset + 2] = sample[2] / 255f; - pixels[offset + 3] = 1; + // Expand from sRGB to linear RGB + Color color = + Color.Expand(new Color(new Vector3(sample[0], sample[1], sample[2]) / 255f)); + + pixels[offset + 0] = color.R; + pixels[offset + 1] = color.G; + pixels[offset + 2] = color.B; + pixels[offset + 3] = color.A; } }); } @@ -139,10 +144,14 @@ namespace ImageProcessor.Formats int offset = ((y * pixelWidth) + x) * 4; - pixels[offset + 0] = sample[0] / 255f; - pixels[offset + 1] = sample[0] / 255f; - pixels[offset + 2] = sample[0] / 255f; - pixels[offset + 3] = 1; + // Expand from sRGB to linear RGB + Color color = + Color.Expand(new Color(new Vector3(sample[0], sample[0], sample[0]) / 255f)); + + pixels[offset + 0] = color.R; + pixels[offset + 1] = color.G; + pixels[offset + 2] = color.B; + pixels[offset + 3] = color.A; } }); } diff --git a/src/ImageProcessor/Formats/Jpg/JpegEncoder.cs b/src/ImageProcessor/Formats/Jpg/JpegEncoder.cs index 0517b422c..9958d6e63 100644 --- a/src/ImageProcessor/Formats/Jpg/JpegEncoder.cs +++ b/src/ImageProcessor/Formats/Jpg/JpegEncoder.cs @@ -111,7 +111,8 @@ namespace ImageProcessor.Formats float b = sourcePixels[source + 2]; float a = sourcePixels[source + 3]; - Bgra32 color = Color.ToNonPremultiplied(new Color(r, g, b, a)); + // Compress back to sRGB + Bgra32 color = Color.ToNonPremultiplied(Color.Compress(new Color(r, g, b, a))); samples[start] = color.R; samples[start + 1] = color.G; diff --git a/src/ImageProcessor/Samplers/Resampler.cs b/src/ImageProcessor/Samplers/Resampler.cs index 4fd105f16..22bc7b57c 100644 --- a/src/ImageProcessor/Samplers/Resampler.cs +++ b/src/ImageProcessor/Samplers/Resampler.cs @@ -193,12 +193,11 @@ namespace ImageProcessor.Samplers foreach (Weight xw in horizontalValues) { int originX = xw.Index; - Color sourceColor = Color.Expand(source[originX, originY]); + Color sourceColor = source[originX, originY]; destination += sourceColor * yw.Value * xw.Value; } } - destination = Color.Compress(destination); target[x, y] = destination; } } @@ -302,13 +301,12 @@ namespace ImageProcessor.Samplers if (sourceRectangle.Contains(rotated.X, rotated.Y)) { - Color sourceColor = Color.Expand(source[rotated.X, rotated.Y]); + Color sourceColor = source[rotated.X, rotated.Y]; destination += sourceColor * yw.Value * xw.Value; } } } - destination = Color.Compress(destination); target[x, y] = destination; } }