Browse Source

Better transparency handling

Former-commit-id: a1d0f5d52b21aa76531f86ee63f75973311e1837
Former-commit-id: b3f3963a089a4c99ae8ceda8b4ad019b19e32ec2
Former-commit-id: eb5b4e013f56035cd961b03e213cea128ee5e88b
af/merge-core
James Jackson-South 10 years ago
parent
commit
e8f6157023
  1. 8
      src/ImageProcessor/Colors/Color.cs
  2. 13
      src/ImageProcessor/Filters/BackgroundColor.cs
  3. 2
      src/ImageProcessor/Filters/ColorMatrix/Polaroid.cs
  4. 4
      src/ImageProcessor/Filters/Glow.cs
  5. 13
      src/ImageProcessor/Filters/Vignette.cs
  6. 19
      src/ImageProcessor/Formats/Bmp/BmpEncoder.cs
  7. 16
      src/ImageProcessor/Formats/Gif/GifEncoder.cs
  8. 16
      src/ImageProcessor/Formats/Gif/Quantizer/OctreeQuantizer.cs
  9. 5
      src/ImageProcessor/Formats/IImageEncoder.cs
  10. 19
      src/ImageProcessor/Formats/Jpg/JpegEncoder.cs
  11. 19
      src/ImageProcessor/Formats/Png/PngEncoder.cs
  12. 2
      src/ImageProcessor/Samplers/ImageSampleExtensions.cs
  13. 62
      tests/ImageProcessor.Tests/Processors/Filters/FilterTests.cs
  14. 27
      tests/ImageProcessor.Tests/Processors/Samplers/SamplerTests.cs

8
src/ImageProcessor/Colors/Color.cs

@ -350,9 +350,13 @@ namespace ImageProcessor
{ {
amount = amount.Clamp(0f, 1f); amount = amount.Clamp(0f, 1f);
if (Math.Abs(from.A - 1) < Epsilon || Math.Abs(from.A) < Epsilon)
{
return from + (to - from) * amount;
}
// Premultiplied. // Premultiplied.
return from + (to - from) * amount; return from * (1 - amount) + to;
//return (from * (1 - amount)) + to;
} }
/// <summary> /// <summary>

13
src/ImageProcessor/Filters/BackgroundColor.cs

@ -5,6 +5,7 @@
namespace ImageProcessor.Filters namespace ImageProcessor.Filters
{ {
using System;
using System.Threading.Tasks; using System.Threading.Tasks;
/// <summary> /// <summary>
@ -12,6 +13,11 @@ namespace ImageProcessor.Filters
/// </summary> /// </summary>
public class BackgroundColor : ParallelImageProcessor public class BackgroundColor : ParallelImageProcessor
{ {
/// <summary>
/// The epsilon for comparing floating point numbers.
/// </summary>
private const float Epsilon = 0.001f;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="BackgroundColor"/> class. /// Initializes a new instance of the <see cref="BackgroundColor"/> class.
/// </summary> /// </summary>
@ -46,11 +52,16 @@ namespace ImageProcessor.Filters
{ {
Color color = source[x, y]; Color color = source[x, y];
if (color.A < 1) if (color.A < 1 && color.A > 0)
{ {
color = Color.Lerp(color, backgroundColor, .5f); color = Color.Lerp(color, backgroundColor, .5f);
} }
if (Math.Abs(color.A) < Epsilon)
{
color = backgroundColor;
}
target[x, y] = color; target[x, y] = color;
} }
} }

2
src/ImageProcessor/Filters/ColorMatrix/Polaroid.cs

@ -33,7 +33,7 @@ namespace ImageProcessor.Filters
protected override void AfterApply(ImageBase source, ImageBase target, Rectangle targetRectangle, Rectangle sourceRectangle) protected override void AfterApply(ImageBase source, ImageBase target, Rectangle targetRectangle, Rectangle sourceRectangle)
{ {
new Vignette { Color = new Color(102 / 255f, 34 / 255f, 0) }.Apply(target, target, targetRectangle); new Vignette { Color = new Color(102 / 255f, 34 / 255f, 0) }.Apply(target, target, targetRectangle);
new Glow { Color = new Color(1, 153 / 255f, 102 / 255f) }.Apply(target, target, targetRectangle); new Glow { Color = new Color(1, 153 / 255f, 102 / 255f, .7f) }.Apply(target, target, targetRectangle);
} }
} }
} }

4
src/ImageProcessor/Filters/Glow.cs

@ -34,7 +34,7 @@ namespace ImageProcessor.Filters
{ {
int startX = sourceRectangle.X; int startX = sourceRectangle.X;
int endX = sourceRectangle.Right; int endX = sourceRectangle.Right;
Color color = this.Color; Color glowColor = this.Color;
Vector2 centre = Rectangle.Center(targetRectangle).ToVector2(); Vector2 centre = Rectangle.Center(targetRectangle).ToVector2();
float rX = this.RadiusX > 0 ? this.RadiusX : targetRectangle.Width / 2f; float rX = this.RadiusX > 0 ? this.RadiusX : targetRectangle.Width / 2f;
float rY = this.RadiusY > 0 ? this.RadiusY : targetRectangle.Height / 2f; float rY = this.RadiusY > 0 ? this.RadiusY : targetRectangle.Height / 2f;
@ -49,7 +49,7 @@ namespace ImageProcessor.Filters
{ {
float distance = Vector2.Distance(centre, new Vector2(x, y)); float distance = Vector2.Distance(centre, new Vector2(x, y));
Color sourceColor = target[x, y]; Color sourceColor = target[x, y];
target[x, y] = Color.Lerp(color, sourceColor, 4f * distance / maxDistance); target[x, y] = Color.Lerp(sourceColor, glowColor, .334f * (1 - distance / maxDistance));
} }
}); });
} }

13
src/ImageProcessor/Filters/Vignette.cs

@ -17,7 +17,7 @@ namespace ImageProcessor.Filters
/// <summary> /// <summary>
/// Gets or sets the vignette color to apply. /// Gets or sets the vignette color to apply.
/// </summary> /// </summary>
public Color Color { get; set; } = Color.Black; public Color Color { get; set; } = new Color(0, 0, 0, 1);
/// <summary> /// <summary>
/// Gets or sets the the x-radius. /// Gets or sets the the x-radius.
@ -34,7 +34,7 @@ namespace ImageProcessor.Filters
{ {
int startX = sourceRectangle.X; int startX = sourceRectangle.X;
int endX = sourceRectangle.Right; int endX = sourceRectangle.Right;
Color color = this.Color; Color vignetteColor = this.Color;
Vector2 centre = Rectangle.Center(targetRectangle).ToVector2(); Vector2 centre = Rectangle.Center(targetRectangle).ToVector2();
float rX = this.RadiusX > 0 ? this.RadiusX : targetRectangle.Width / 2f; float rX = this.RadiusX > 0 ? this.RadiusX : targetRectangle.Width / 2f;
float rY = this.RadiusY > 0 ? this.RadiusY : targetRectangle.Height / 2f; float rY = this.RadiusY > 0 ? this.RadiusY : targetRectangle.Height / 2f;
@ -49,7 +49,14 @@ namespace ImageProcessor.Filters
{ {
float distance = Vector2.Distance(centre, new Vector2(x, y)); float distance = Vector2.Distance(centre, new Vector2(x, y));
Color sourceColor = target[x, y]; Color sourceColor = target[x, y];
target[x, y] = Color.Lerp(sourceColor, color, .9f * distance / maxDistance); //if (sourceColor.A > 0)
//{
target[x, y] = Color.Lerp(sourceColor, vignetteColor, .9f * distance / maxDistance);
//}
//else
//{
// target[x, y] = Color.Lerp(sourceColor, color, distance / maxDistance);
//}
} }
}); });
} }

19
src/ImageProcessor/Formats/Bmp/BmpEncoder.cs

@ -14,11 +14,6 @@ namespace ImageProcessor.Formats
/// <remarks>The encoder can currently only write 24-bit rgb images to streams.</remarks> /// <remarks>The encoder can currently only write 24-bit rgb images to streams.</remarks>
public class BmpEncoder : IImageEncoder public class BmpEncoder : IImageEncoder
{ {
/// <summary>
/// The the transparency threshold.
/// </summary>
private int threshold = 128;
/// <summary> /// <summary>
/// Gets or sets the quality of output for images. /// Gets or sets the quality of output for images.
/// </summary> /// </summary>
@ -31,15 +26,6 @@ namespace ImageProcessor.Formats
/// <inheritdoc/> /// <inheritdoc/>
public string Extension => "bmp"; public string Extension => "bmp";
/// <summary>
/// Gets or sets the transparency threshold.
/// </summary>
public int Threshold
{
get { return this.threshold; }
set { this.threshold = value.Clamp(0, 255); }
}
/// <inheritdoc/> /// <inheritdoc/>
public bool IsSupportedFileExtension(string extension) public bool IsSupportedFileExtension(string extension)
{ {
@ -132,11 +118,6 @@ namespace ImageProcessor.Formats
Bgra32 color = Color.ToNonPremultiplied(new Color(r, g, b, a)); Bgra32 color = Color.ToNonPremultiplied(new Color(r, g, b, a));
if (color.A < this.Threshold)
{
color = new Bgra32(0, 0, 0, 0);
}
writer.Write(color.B); writer.Write(color.B);
writer.Write(color.G); writer.Write(color.G);
writer.Write(color.R); writer.Write(color.R);

16
src/ImageProcessor/Formats/Gif/GifEncoder.cs

@ -15,11 +15,6 @@ namespace ImageProcessor.Formats
/// </summary> /// </summary>
public class GifEncoder : IImageEncoder public class GifEncoder : IImageEncoder
{ {
/// <summary>
/// The the transparency threshold.
/// </summary>
private int threshold = 128;
/// <summary> /// <summary>
/// Gets or sets the quality of output for images. /// Gets or sets the quality of output for images.
/// </summary> /// </summary>
@ -32,15 +27,6 @@ namespace ImageProcessor.Formats
/// <inheritdoc/> /// <inheritdoc/>
public string MimeType => "image/gif"; public string MimeType => "image/gif";
/// <summary>
/// Gets or sets the transparency threshold.
/// </summary>
public int Threshold
{
get { return this.threshold; }
set { this.threshold = value.Clamp(0, 255); }
}
/// <inheritdoc/> /// <inheritdoc/>
public bool IsSupportedFileExtension(string extension) public bool IsSupportedFileExtension(string extension)
{ {
@ -138,7 +124,7 @@ namespace ImageProcessor.Formats
private QuantizedImage WriteColorTable(ImageBase image, Stream stream, int quality, int bitDepth) private QuantizedImage WriteColorTable(ImageBase image, Stream stream, int quality, int bitDepth)
{ {
// Quantize the image returning a pallete. // Quantize the image returning a pallete.
IQuantizer quantizer = new OctreeQuantizer(quality.Clamp(1, 255), bitDepth) { Threshold = this.threshold }; IQuantizer quantizer = new OctreeQuantizer(quality.Clamp(1, 255), bitDepth);
QuantizedImage quantizedImage = quantizer.Quantize(image); QuantizedImage quantizedImage = quantizer.Quantize(image);
// Grab the pallete and write it to the stream. // Grab the pallete and write it to the stream.

16
src/ImageProcessor/Formats/Gif/Quantizer/OctreeQuantizer.cs

@ -24,11 +24,6 @@ namespace ImageProcessor.Formats
/// </summary> /// </summary>
private readonly int maxColors; private readonly int maxColors;
/// <summary>
/// The the transparency threshold.
/// </summary>
private int threshold = 128;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="OctreeQuantizer"/> class. /// Initializes a new instance of the <see cref="OctreeQuantizer"/> class.
/// </summary> /// </summary>
@ -65,15 +60,6 @@ namespace ImageProcessor.Formats
this.maxColors = maxColors; this.maxColors = maxColors;
} }
/// <summary>
/// Gets or sets the transparency threshold.
/// </summary>
public int Threshold
{
get { return this.threshold; }
set { this.threshold = value.Clamp(0, 255); }
}
/// <summary> /// <summary>
/// Process the pixel in the first pass of the algorithm /// Process the pixel in the first pass of the algorithm
/// </summary> /// </summary>
@ -105,7 +91,7 @@ namespace ImageProcessor.Formats
byte paletteIndex = (byte)this.maxColors; byte paletteIndex = (byte)this.maxColors;
// Get the palette index if it's transparency meets criterea. // Get the palette index if it's transparency meets criterea.
if (pixel.A >= this.Threshold) if (pixel.A > 0)
{ {
paletteIndex = (byte)this.octree.GetPaletteIndex(pixel); paletteIndex = (byte)this.octree.GetPaletteIndex(pixel);
} }

5
src/ImageProcessor/Formats/IImageEncoder.cs

@ -22,11 +22,6 @@ namespace ImageProcessor.Formats
/// </summary> /// </summary>
int Quality { get; set; } int Quality { get; set; }
/// <summary>
/// Gets or sets the transparency threshold.
/// </summary>
int Threshold { get; set; }
/// <summary> /// <summary>
/// Gets the standard identifier used on the Internet to indicate the type of data that a file contains. /// Gets the standard identifier used on the Internet to indicate the type of data that a file contains.
/// </summary> /// </summary>

19
src/ImageProcessor/Formats/Jpg/JpegEncoder.cs

@ -21,11 +21,6 @@ namespace ImageProcessor.Formats
/// </summary> /// </summary>
private int quality = 100; private int quality = 100;
/// <summary>
/// The the transparency threshold.
/// </summary>
private int threshold = 128;
/// <summary> /// <summary>
/// Gets or sets the quality, that will be used to encode the image. Quality /// Gets or sets the quality, that will be used to encode the image. Quality
/// index must be between 0 and 100 (compression from max to min). /// index must be between 0 and 100 (compression from max to min).
@ -37,15 +32,6 @@ namespace ImageProcessor.Formats
set { this.quality = value.Clamp(1, 100); } set { this.quality = value.Clamp(1, 100); }
} }
/// <summary>
/// Gets or sets the transparency threshold.
/// </summary>
public int Threshold
{
get { return this.threshold; }
set { this.threshold = value.Clamp(0, 255); }
}
/// <inheritdoc/> /// <inheritdoc/>
public string MimeType => "image/jpeg"; public string MimeType => "image/jpeg";
@ -127,11 +113,6 @@ namespace ImageProcessor.Formats
Bgra32 color = Color.ToNonPremultiplied(new Color(r, g, b, a)); Bgra32 color = Color.ToNonPremultiplied(new Color(r, g, b, a));
if (color.A < this.Threshold)
{
color = new Bgra32(0, 0, 0, 0);
}
samples[start] = color.R; samples[start] = color.R;
samples[start + 1] = color.G; samples[start + 1] = color.G;
samples[start + 2] = color.B; samples[start + 2] = color.B;

19
src/ImageProcessor/Formats/Png/PngEncoder.cs

@ -18,11 +18,6 @@ namespace ImageProcessor.Formats
/// </summary> /// </summary>
private const int MaxBlockSize = 0xFFFF; private const int MaxBlockSize = 0xFFFF;
/// <summary>
/// The the transparency threshold.
/// </summary>
private int threshold;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="PngEncoder"/> class. /// Initializes a new instance of the <see cref="PngEncoder"/> class.
/// </summary> /// </summary>
@ -37,15 +32,6 @@ namespace ImageProcessor.Formats
/// <remarks>Png is a lossless format so this is not used in this encoder.</remarks> /// <remarks>Png is a lossless format so this is not used in this encoder.</remarks>
public int Quality { get; set; } public int Quality { get; set; }
/// <summary>
/// Gets or sets the transparency threshold.
/// </summary>
public int Threshold
{
get { return this.threshold; }
set { this.threshold = value.Clamp(0, 255); }
}
/// <inheritdoc/> /// <inheritdoc/>
public string MimeType => "image/png"; public string MimeType => "image/png";
@ -342,11 +328,6 @@ namespace ImageProcessor.Formats
Bgra32 color = Color.ToNonPremultiplied(new Color(r, g, b, a)); Bgra32 color = Color.ToNonPremultiplied(new Color(r, g, b, a));
if (color.A < this.Threshold)
{
color = new Bgra32(0, 0, 0, 0);
}
data[dataOffset] = color.R; data[dataOffset] = color.R;
data[dataOffset + 1] = color.G; data[dataOffset + 1] = color.G;
data[dataOffset + 2] = color.B; data[dataOffset + 2] = color.B;

2
src/ImageProcessor/Samplers/ImageSampleExtensions.cs

@ -121,7 +121,7 @@ namespace ImageProcessor.Samplers
/// <returns>The <see cref="Image"/></returns> /// <returns>The <see cref="Image"/></returns>
public static Image Rotate(this Image source, float degrees) public static Image Rotate(this Image source, float degrees)
{ {
return source.Process(source.Width, source.Height, source.Bounds, source.Bounds, new Resampler(new RobidouxResampler()) { Angle = degrees }); return source.Process(source.Width, source.Height, source.Bounds, source.Bounds, new Resampler(new BicubicResampler()) { Angle = degrees });
} }
/// <summary> /// <summary>

62
tests/ImageProcessor.Tests/Processors/Filters/FilterTests.cs

@ -13,39 +13,39 @@ namespace ImageProcessor.Tests
{ {
public static readonly TheoryData<string, IImageProcessor> Filters = new TheoryData<string, IImageProcessor> public static readonly TheoryData<string, IImageProcessor> Filters = new TheoryData<string, IImageProcessor>
{ {
{ "Brightness-50", new Brightness(50) }, //{ "Brightness-50", new Brightness(50) },
{ "Brightness--50", new Brightness(-50) }, //{ "Brightness--50", new Brightness(-50) },
{ "Contrast-50", new Contrast(50) }, //{ "Contrast-50", new Contrast(50) },
{ "Contrast--50", new Contrast(-50) }, //{ "Contrast--50", new Contrast(-50) },
{ "BackgroundColor", new BackgroundColor(new Color(243 / 255f, 87 / 255f, 161 / 255f))}, //{ "BackgroundColor", new BackgroundColor(new Color(243 / 255f, 87 / 255f, 161 / 255f,.5f))},
{ "Blend", new Blend(new Image(File.OpenRead("TestImages/Formats/Bmp/Car.bmp")),50)}, //{ "Blend", new Blend(new Image(File.OpenRead("TestImages/Formats/Bmp/Car.bmp")),50)},
{ "Saturation-50", new Saturation(50) }, //{ "Saturation-50", new Saturation(50) },
{ "Saturation--50", new Saturation(-50) }, //{ "Saturation--50", new Saturation(-50) },
{ "Alpha--50", new Alpha(50) }, { "Alpha--50", new Alpha(50) },
{ "Invert", new Invert() }, //{ "Invert", new Invert() },
{ "Sepia", new Sepia() }, //{ "Sepia", new Sepia() },
{ "BlackWhite", new BlackWhite() }, //{ "BlackWhite", new BlackWhite() },
{ "Lomograph", new Lomograph() }, //{ "Lomograph", new Lomograph() },
{ "Polaroid", new Polaroid() }, { "Polaroid", new Polaroid() },
{ "Kodachrome", new Kodachrome() }, //{ "Kodachrome", new Kodachrome() },
{ "GreyscaleBt709", new GreyscaleBt709() }, //{ "GreyscaleBt709", new GreyscaleBt709() },
{ "GreyscaleBt601", new GreyscaleBt601() }, //{ "GreyscaleBt601", new GreyscaleBt601() },
{ "Kayyali", new Kayyali() }, //{ "Kayyali", new Kayyali() },
{ "Kirsch", new Kirsch() }, //{ "Kirsch", new Kirsch() },
{ "Laplacian3X3", new Laplacian3X3() }, //{ "Laplacian3X3", new Laplacian3X3() },
{ "Laplacian5X5", new Laplacian5X5() }, //{ "Laplacian5X5", new Laplacian5X5() },
{ "LaplacianOfGaussian", new LaplacianOfGaussian() }, //{ "LaplacianOfGaussian", new LaplacianOfGaussian() },
{ "Prewitt", new Prewitt() }, //{ "Prewitt", new Prewitt() },
{ "RobertsCross", new RobertsCross() }, //{ "RobertsCross", new RobertsCross() },
{ "Scharr", new Scharr() }, //{ "Scharr", new Scharr() },
{ "Sobel", new Sobel {Greyscale = true} }, //{ "Sobel", new Sobel {Greyscale = true} },
{ "Pixelate", new Pixelate(8) }, //{ "Pixelate", new Pixelate(8) },
{ "GuassianBlur", new GuassianBlur(10) }, //{ "GuassianBlur", new GuassianBlur(10) },
{ "GuassianSharpen", new GuassianSharpen(10) }, //{ "GuassianSharpen", new GuassianSharpen(10) },
{ "Hue-180", new Hue(180) }, //{ "Hue-180", new Hue(180) },
{ "Hue--180", new Hue(-180) }, //{ "Hue--180", new Hue(-180) },
{ "BoxBlur", new BoxBlur(10) }, //{ "BoxBlur", new BoxBlur(10) },
{"Vignette", new Vignette()} //{"Vignette", new Vignette()}
}; };
[Theory] [Theory]

27
tests/ImageProcessor.Tests/Processors/Samplers/SamplerTests.cs

@ -13,20 +13,20 @@
public static readonly TheoryData<string, IResampler> Samplers = public static readonly TheoryData<string, IResampler> Samplers =
new TheoryData<string, IResampler> new TheoryData<string, IResampler>
{ {
//{ "Bicubic", new BicubicResampler() }, { "Bicubic", new BicubicResampler() },
//{ "Triangle", new TriangleResampler() }, { "Triangle", new TriangleResampler() },
//{ "Box", new BoxResampler() }, { "Box", new BoxResampler() },
//{ "Lanczos3", new Lanczos3Resampler() }, { "Lanczos3", new Lanczos3Resampler() },
//{ "Lanczos5", new Lanczos5Resampler() }, { "Lanczos5", new Lanczos5Resampler() },
//{ "Lanczos8", new Lanczos8Resampler() }, { "Lanczos8", new Lanczos8Resampler() },
//{ "MitchellNetravali", new MitchellNetravaliResampler() }, { "MitchellNetravali", new MitchellNetravaliResampler() },
{ "NearestNeighbor", new NearestNeighborResampler() }, { "NearestNeighbor", new NearestNeighborResampler() },
//{ "Hermite", new HermiteResampler() }, { "Hermite", new HermiteResampler() },
//{ "Spline", new SplineResampler() }, { "Spline", new SplineResampler() },
//{ "Robidoux", new RobidouxResampler() }, { "Robidoux", new RobidouxResampler() },
//{ "RobidouxSharp", new RobidouxSharpResampler() }, { "RobidouxSharp", new RobidouxSharpResampler() },
//{ "RobidouxSoft", new RobidouxSoftResampler() }, { "RobidouxSoft", new RobidouxSoftResampler() },
//{ "Welch", new WelchResampler() } { "Welch", new WelchResampler() }
}; };
public static readonly TheoryData<RotateType, FlipType> RotateFlips = new TheoryData<RotateType, FlipType> public static readonly TheoryData<RotateType, FlipType> RotateFlips = new TheoryData<RotateType, FlipType>
@ -167,6 +167,7 @@
using (FileStream output = File.OpenWrite($"TestOutput/Rotate/{filename}")) using (FileStream output = File.OpenWrite($"TestOutput/Rotate/{filename}"))
{ {
image.Rotate(45, sampler) image.Rotate(45, sampler)
.BackgroundColor(Color.Aqua)
.Save(output); .Save(output);
} }

Loading…
Cancel
Save