diff --git a/README.md b/README.md
index bada7219f..528015329 100644
--- a/README.md
+++ b/README.md
@@ -2,7 +2,7 @@
**This branch contains the new cross platform version of ImageProcessor**.
-This is a complete rewrite from the ground up to allow the processing of images without the use of `System.Drawing` using a portable class library (PCL). It's still in early stages but progress has been pretty quick.
+This is a complete rewrite from the ground up to allow the processing of images without the use of `System.Drawing` using a cross-platform class library. It's still in early stages but progress has been pretty quick.
###Why am I writing this?
diff --git a/src/ImageProcessor/Colors/Color.cs b/src/ImageProcessor/Colors/Color.cs
index 799ae7e3f..4f6ca7eff 100644
--- a/src/ImageProcessor/Colors/Color.cs
+++ b/src/ImageProcessor/Colors/Color.cs
@@ -10,7 +10,8 @@ namespace ImageProcessor
using System.Numerics;
///
- /// Represents a four-component color using red, green, blue, and alpha data.
+ /// Represents a four-component color using red, green, blue, and alpha data.
+ /// Each component is stored in premultiplied format multiplied by the alpha component.
///
///
/// This struct is fully mutable. This is done (against the guidelines) for the sake of performance,
@@ -470,7 +471,8 @@ namespace ImageProcessor
{
amount = amount.Clamp(0f, 1f);
- return (from * (1 - amount)) + (to * amount);
+ //return (from * (1 - amount)) + (to * amount);
+ return (from * (1 - amount)) + to ;
}
///
@@ -478,7 +480,7 @@ namespace ImageProcessor
///
///
///
- /// The whos signal to compress.
+ /// The whose signal to compress.
/// The .
public static Color Compand(Color linear)
{
@@ -495,7 +497,7 @@ namespace ImageProcessor
///
///
///
- /// The whos signal to expand.
+ /// The whose signal to expand.
/// The .
public static Color InverseCompand(Color gamma)
{
@@ -511,33 +513,29 @@ namespace ImageProcessor
/// Converts a non-premultipled alpha to a
/// that contains premultiplied alpha.
///
- /// The red component of this .
- /// The green component of this .
- /// The blue component of this .
- /// The alpha component of this .
+ /// The to convert.
/// The .
- public static Color FromNonPremultiplied(float r, float g, float b, float a)
+ public static Color FromNonPremultiplied(Color color)
{
- return new Color(r * a, g * a, b * a, a);
+ float a = color.A;
+ return new Color(color.R * a, color.G * a, color.B * a, a);
}
///
/// Converts a premultipled alpha to a
/// that contains non-premultiplied alpha.
///
- /// The red component of this .
- /// The green component of this .
- /// The blue component of this .
- /// The alpha component of this .
+ /// The to convert.
/// The .
- public static Color ToNonPremultiplied(float r, float g, float b, float a)
+ public static Color ToNonPremultiplied(Color color)
{
+ float a = color.A;
if (Math.Abs(a) < Epsilon)
{
- return new Color(r, g, b, a);
+ return new Color(color.R, color.G, color.B, a);
}
- return new Color(r / a, g / a, b / a, a);
+ return new Color(color.R / a, color.G / a, color.B / a, a);
}
///
diff --git a/src/ImageProcessor/Filters/BackgroundColor.cs b/src/ImageProcessor/Filters/BackgroundColor.cs
new file mode 100644
index 000000000..0f844f7a5
--- /dev/null
+++ b/src/ImageProcessor/Filters/BackgroundColor.cs
@@ -0,0 +1,61 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageProcessor.Filters
+{
+ using System.Threading.Tasks;
+
+ ///
+ /// Sets the background color of the image.
+ ///
+ public class BackgroundColor : ParallelImageProcessor
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The to set the background color to.
+ public BackgroundColor(Color color)
+ {
+ this.Value = Color.FromNonPremultiplied(color);
+ }
+
+ ///
+ /// Gets the background color value.
+ ///
+ public Color Value { get; }
+
+ ///
+ protected override void Apply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle, int startY, int endY)
+ {
+ int sourceY = sourceRectangle.Y;
+ int sourceBottom = sourceRectangle.Bottom;
+ int startX = sourceRectangle.X;
+ int endX = sourceRectangle.Right;
+ Color backgroundColor = this.Value;
+
+ Parallel.For(
+ startY,
+ endY,
+ y =>
+ {
+ if (y >= sourceY && y < sourceBottom)
+ {
+ for (int x = startX; x < endX; x++)
+ {
+ Color color = source[x, y];
+
+ // TODO: Fix this nonesense.
+ if (color.A < .9)
+ {
+ color = Color.Lerp(color, backgroundColor, .5f);
+ }
+
+ target[x, y] = color;
+ }
+ }
+ });
+ }
+ }
+}
diff --git a/src/ImageProcessor/Filters/ImageFilterExtensions.cs b/src/ImageProcessor/Filters/ImageFilterExtensions.cs
index a322105b5..f435829b2 100644
--- a/src/ImageProcessor/Filters/ImageFilterExtensions.cs
+++ b/src/ImageProcessor/Filters/ImageFilterExtensions.cs
@@ -35,6 +35,18 @@ namespace ImageProcessor.Filters
return source.Process(rectangle, new Alpha(percent));
}
+ ///
+ /// Combines the given image together with the current one by blending their pixels.
+ ///
+ /// The image this method extends.
+ /// The image to blend with the currently processing image.
+ /// The opacity of the image image to blend. Must be between 0 and 100.
+ /// The .
+ public static Image BackgroundColor(this Image source, Color color)
+ {
+ return source.Process(source.Bounds, new BackgroundColor(color));
+ }
+
///
/// Combines the given image together with the current one by blending their pixels.
///
diff --git a/src/ImageProcessor/Formats/Gif/GifDecoderCore.cs b/src/ImageProcessor/Formats/Gif/GifDecoderCore.cs
index 077f28483..ee69a854b 100644
--- a/src/ImageProcessor/Formats/Gif/GifDecoderCore.cs
+++ b/src/ImageProcessor/Formats/Gif/GifDecoderCore.cs
@@ -354,6 +354,8 @@ 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.
+ // Gifs don't store alpha transparency so we don't need to convert to
+ // premultiplied.
int indexOffset = index * 3;
this.currentFrame[offset + 0] = colorTable[indexOffset] / 255f; // r
this.currentFrame[offset + 1] = colorTable[indexOffset + 1] / 255f; // g
diff --git a/src/ImageProcessor/Formats/Gif/Quantizer/Quantizer.cs b/src/ImageProcessor/Formats/Gif/Quantizer/Quantizer.cs
index c601b2e12..039f9f82c 100644
--- a/src/ImageProcessor/Formats/Gif/Quantizer/Quantizer.cs
+++ b/src/ImageProcessor/Formats/Gif/Quantizer/Quantizer.cs
@@ -79,7 +79,7 @@ namespace ImageProcessor.Formats
for (int x = 0; x < width; x++)
{
// Now I have the pixel, call the FirstPassQuantize function...
- this.InitialQuantizePixel(source[x, y]);
+ this.InitialQuantizePixel(Color.ToNonPremultiplied(source[x, y]));
}
}
}
@@ -107,7 +107,7 @@ namespace ImageProcessor.Formats
for (int x = 0; x < width; x++)
{
// Implicit cast here from Color.
- Bgra32 sourcePixel = source[x, y];
+ Bgra32 sourcePixel = Color.ToNonPremultiplied(source[x, y]);
// Check if this is the same as the last pixel. If so use that value
// rather than calculating it again. This is an inexpensive optimization.
diff --git a/src/ImageProcessor/Formats/Jpg/JpegEncoder.cs b/src/ImageProcessor/Formats/Jpg/JpegEncoder.cs
index 2da1fe11f..ed0a69a64 100644
--- a/src/ImageProcessor/Formats/Jpg/JpegEncoder.cs
+++ b/src/ImageProcessor/Formats/Jpg/JpegEncoder.cs
@@ -105,9 +105,17 @@ namespace ImageProcessor.Formats
int start = x * 3;
int source = ((y * pixelWidth) + x) * 4;
- samples[start] = (byte)(sourcePixels[source].Clamp(0, 1) * 255);
- samples[start + 1] = (byte)(sourcePixels[source + 1].Clamp(0, 1) * 255);
- samples[start + 2] = (byte)(sourcePixels[source + 2].Clamp(0, 1) * 255);
+ // Convert to non-premultiplied color.
+ float r = sourcePixels[source];
+ float g = sourcePixels[source + 1];
+ float b = sourcePixels[source + 2];
+ float a = sourcePixels[source + 3];
+
+ Bgra32 color = Color.ToNonPremultiplied(new Color(r, g, b, a));
+
+ samples[start] = color.R;
+ samples[start + 1] = color.G;
+ samples[start + 2] = color.B;
}
rows[y] = new SampleRow(samples, pixelWidth, 8, 3);
diff --git a/src/ImageProcessor/Formats/Png/GrayscaleReader.cs b/src/ImageProcessor/Formats/Png/GrayscaleReader.cs
index 0392458a0..4dfc69d9a 100644
--- a/src/ImageProcessor/Formats/Png/GrayscaleReader.cs
+++ b/src/ImageProcessor/Formats/Png/GrayscaleReader.cs
@@ -47,10 +47,18 @@ namespace ImageProcessor.Formats
{
offset = ((this.row * header.Width) + x) * 4;
- pixels[offset] = newScanline[x * 2] / 255f;
- pixels[offset + 1] = newScanline[x * 2] / 255f;
- pixels[offset + 2] = newScanline[x * 2] / 255f;
- pixels[offset + 3] = newScanline[(x * 2) + 1] / 255f;
+ // We want to convert to premultiplied alpha here.
+ float r = newScanline[x * 2] / 255f;
+ float g = newScanline[x * 2] / 255f;
+ float b = newScanline[x * 2] / 255f;
+ float a = newScanline[(x * 2) + 1] / 255f;
+
+ Color premultiplied = Color.FromNonPremultiplied(new Color(r, g, b, a));
+
+ pixels[offset] = premultiplied.R;
+ pixels[offset + 1] = premultiplied.G;
+ pixels[offset + 2] = premultiplied.B;
+ pixels[offset + 3] = premultiplied.A;
}
}
else
diff --git a/src/ImageProcessor/Formats/Png/PaletteIndexReader.cs b/src/ImageProcessor/Formats/Png/PaletteIndexReader.cs
index 90b2053ac..af087701b 100644
--- a/src/ImageProcessor/Formats/Png/PaletteIndexReader.cs
+++ b/src/ImageProcessor/Formats/Png/PaletteIndexReader.cs
@@ -55,13 +55,25 @@ namespace ImageProcessor.Formats
offset = ((this.row * header.Width) + i) * 4;
int pixelOffset = index * 3;
+
+ float r = newScanline[pixelOffset] / 255f;
+ float g = newScanline[pixelOffset + 1] / 255f;
+ float b = newScanline[pixelOffset + 2] / 255f;
+ float a = this.paletteAlpha.Length > index
+ ? this.paletteAlpha[index] / 255f
+ : 1;
- pixels[offset] = this.palette[pixelOffset] / 255f;
- pixels[offset + 1] = this.palette[pixelOffset + 1] / 255f;
- pixels[offset + 2] = this.palette[pixelOffset + 2] / 255f;
- pixels[offset + 3] = this.paletteAlpha.Length > index
- ? this.paletteAlpha[index] / 255f
- : 1;
+ Color color = new Color(r, g, b, a);
+ if (color.A < 1)
+ {
+ // We want to convert to premultiplied alpha here.
+ color = Color.FromNonPremultiplied(color);
+ }
+
+ pixels[offset] = color.R;
+ pixels[offset + 1] = color.G;
+ pixels[offset + 2] = color.B;
+ pixels[offset + 3] = color.A;
}
}
else
diff --git a/src/ImageProcessor/Formats/Png/PngEncoder.cs b/src/ImageProcessor/Formats/Png/PngEncoder.cs
index 6ac2b0f71..e4501a956 100644
--- a/src/ImageProcessor/Formats/Png/PngEncoder.cs
+++ b/src/ImageProcessor/Formats/Png/PngEncoder.cs
@@ -46,7 +46,9 @@ namespace ImageProcessor.Formats
/// true if the image should be written uncompressed to
/// the stream; otherwise, false.
///
- public bool IsWritingUncompressed { get; set; }
+ // TODO: We can't quickly return a color to non-premultiplied with this method.
+ // Should we remove?
+ //public bool IsWritingUncompressed { get; set; }
///
/// Gets or sets a value indicating whether this instance is writing
@@ -113,14 +115,14 @@ namespace ImageProcessor.Formats
this.WritePhysicalChunk(stream, image);
this.WriteGammaChunk(stream);
- if (this.IsWritingUncompressed)
- {
- this.WriteDataChunksFast(stream, image);
- }
- else
- {
- this.WriteDataChunks(stream, image);
- }
+ //if (this.IsWritingUncompressed)
+ //{
+ // this.WriteDataChunksFast(stream, image);
+ //}
+ //else
+ //{
+ this.WriteDataChunks(stream, image);
+ //}
this.WriteEndChunk(stream);
stream.Flush();
@@ -318,19 +320,34 @@ namespace ImageProcessor.Formats
// Calculate the offset for the original pixel array.
int pixelOffset = ((y * imageBase.Width) + x) * 4;
- data[dataOffset] = (byte)(pixels[pixelOffset].Clamp(0, 1) * 255);
- data[dataOffset + 1] = (byte)(pixels[pixelOffset + 1].Clamp(0, 1) * 255);
- data[dataOffset + 2] = (byte)(pixels[pixelOffset + 2].Clamp(0, 1) * 255);
- data[dataOffset + 3] = (byte)(pixels[pixelOffset + 3].Clamp(0, 1) * 255);
+ // Convert to non-premultiplied color.
+ float r = pixels[pixelOffset];
+ float g = pixels[pixelOffset + 1];
+ float b = pixels[pixelOffset + 2];
+ float a = pixels[pixelOffset + 3];
+
+ Bgra32 color = Color.ToNonPremultiplied(new Color(r, g, b, a));
+
+ data[dataOffset] = color.R;
+ data[dataOffset + 1] = color.G;
+ data[dataOffset + 2] = color.B;
+ data[dataOffset + 3] = color.A;
if (y > 0)
{
int lastOffset = (((y - 1) * imageBase.Width) + x) * 4;
- data[dataOffset] -= (byte)(pixels[lastOffset].Clamp(0, 1) * 255);
- data[dataOffset + 1] -= (byte)(pixels[lastOffset + 1].Clamp(0, 1) * 255);
- data[dataOffset + 2] -= (byte)(pixels[lastOffset + 2].Clamp(0, 1) * 255);
- data[dataOffset + 3] -= (byte)(pixels[lastOffset + 3].Clamp(0, 1) * 255);
+ r = pixels[lastOffset];
+ g = pixels[lastOffset + 1];
+ b = pixels[lastOffset + 2];
+ a = pixels[lastOffset + 3];
+
+ color = Color.ToNonPremultiplied(new Color(r, g, b, a));
+
+ data[dataOffset] -= color.R;
+ data[dataOffset + 1] -= color.G;
+ data[dataOffset + 2] -= color.B;
+ data[dataOffset + 3] -= color.A;
}
}
}
diff --git a/src/ImageProcessor/Formats/Png/TrueColorReader.cs b/src/ImageProcessor/Formats/Png/TrueColorReader.cs
index 2a9f46198..c292fcd56 100644
--- a/src/ImageProcessor/Formats/Png/TrueColorReader.cs
+++ b/src/ImageProcessor/Formats/Png/TrueColorReader.cs
@@ -44,10 +44,18 @@ namespace ImageProcessor.Formats
{
offset = ((this.row * header.Width) + (x >> 2)) * 4;
- pixels[offset + 0] = newScanline[x] / 255f;
- pixels[offset + 1] = newScanline[x + 1] / 255f;
- pixels[offset + 2] = newScanline[x + 2] / 255f;
- pixels[offset + 3] = newScanline[x + 3] / 255f;
+ // We want to convert to premultiplied alpha here.
+ float r = newScanline[x] / 255f;
+ float g = newScanline[x + 1] / 255f;
+ float b = newScanline[x + 2] / 255f;
+ float a = newScanline[x + 3] / 255f;
+
+ Color premultiplied = Color.FromNonPremultiplied(new Color(r, g, b, a));
+
+ pixels[offset] = premultiplied.R;
+ pixels[offset + 1] = premultiplied.G;
+ pixels[offset + 2] = premultiplied.B;
+ pixels[offset + 3] = premultiplied.A;
}
}
else
@@ -57,7 +65,7 @@ namespace ImageProcessor.Formats
offset = ((this.row * header.Width) + x) * 4;
int pixelOffset = x * 3;
- pixels[offset + 0] = newScanline[pixelOffset] / 255f;
+ pixels[offset] = newScanline[pixelOffset] / 255f;
pixels[offset + 1] = newScanline[pixelOffset + 1] / 255f;
pixels[offset + 2] = newScanline[pixelOffset + 2] / 255f;
pixels[offset + 3] = 1;
diff --git a/tests/ImageProcessor.Tests/Processors/Filters/FilterTests.cs b/tests/ImageProcessor.Tests/Processors/Filters/FilterTests.cs
index 35029e8d9..584b62a5e 100644
--- a/tests/ImageProcessor.Tests/Processors/Filters/FilterTests.cs
+++ b/tests/ImageProcessor.Tests/Processors/Filters/FilterTests.cs
@@ -46,9 +46,9 @@ namespace ImageProcessor.Tests
[MemberData("Filters")]
public void FilterImage(string name, IImageProcessor processor)
{
- if (!Directory.Exists("Filtered"))
+ if (!Directory.Exists("TestOutput/Filtered"))
{
- Directory.CreateDirectory("Filtered");
+ Directory.CreateDirectory("TestOutput/Filtered");
}
foreach (string file in Files)
@@ -58,7 +58,7 @@ namespace ImageProcessor.Tests
Stopwatch watch = Stopwatch.StartNew();
Image image = new Image(stream);
string filename = Path.GetFileNameWithoutExtension(file) + "-" + name + Path.GetExtension(file);
- using (FileStream output = File.OpenWrite($"Filtered/{ Path.GetFileName(filename) }"))
+ using (FileStream output = File.OpenWrite($"TestOutput/Filtered/{ Path.GetFileName(filename) }"))
{
image.Process(processor).Save(output);
}
diff --git a/tests/ImageProcessor.Tests/Processors/ProcessorTestBase.cs b/tests/ImageProcessor.Tests/Processors/ProcessorTestBase.cs
index 1bc117e54..8d274ae95 100644
--- a/tests/ImageProcessor.Tests/Processors/ProcessorTestBase.cs
+++ b/tests/ImageProcessor.Tests/Processors/ProcessorTestBase.cs
@@ -30,6 +30,7 @@ namespace ImageProcessor.Tests
//"TestImages/Formats/Jpg/greyscale.jpg",
//"TestImages/Formats/Bmp/Car.bmp",
"TestImages/Formats/Png/cballs.png",
+ "TestImages/Formats/Png/blur.png",
//"TestImages/Formats/Png/cmyk.png",
//"TestImages/Formats/Png/gamma-1.0-or-2.2.png",
"TestImages/Formats/Png/splash.png",
diff --git a/tests/ImageProcessor.Tests/Processors/Samplers/SamplerTests.cs b/tests/ImageProcessor.Tests/Processors/Samplers/SamplerTests.cs
index e171341f8..88d5b7a38 100644
--- a/tests/ImageProcessor.Tests/Processors/Samplers/SamplerTests.cs
+++ b/tests/ImageProcessor.Tests/Processors/Samplers/SamplerTests.cs
@@ -4,6 +4,7 @@ namespace ImageProcessor.Tests
using System.Diagnostics;
using System.IO;
+ using ImageProcessor.Filters;
using ImageProcessor.Samplers;
using Xunit;
diff --git a/tests/ImageProcessor.Tests/TestImages/Formats/Png/blur.png.REMOVED.git-id b/tests/ImageProcessor.Tests/TestImages/Formats/Png/blur.png.REMOVED.git-id
new file mode 100644
index 000000000..7bcc08db2
--- /dev/null
+++ b/tests/ImageProcessor.Tests/TestImages/Formats/Png/blur.png.REMOVED.git-id
@@ -0,0 +1 @@
+8b5342317c64603069b6b7227edeb96f6acf6c29
\ No newline at end of file