Browse Source

Better transparency handling

Former-commit-id: a1d0f5d52b21aa76531f86ee63f75973311e1837
Former-commit-id: b3f3963a089a4c99ae8ceda8b4ad019b19e32ec2
Former-commit-id: eb5b4e013f56035cd961b03e213cea128ee5e88b
pull/17/head
James Jackson-South 10 years ago
parent
commit
09176be63f
  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);
if (Math.Abs(from.A - 1) < Epsilon || Math.Abs(from.A) < Epsilon)
{
return from + (to - from) * amount;
}
// Premultiplied.
return from + (to - from) * amount;
//return (from * (1 - amount)) + to;
return from * (1 - amount) + to;
}
/// <summary>

13
src/ImageProcessor/Filters/BackgroundColor.cs

@ -5,6 +5,7 @@
namespace ImageProcessor.Filters
{
using System;
using System.Threading.Tasks;
/// <summary>
@ -12,6 +13,11 @@ namespace ImageProcessor.Filters
/// </summary>
public class BackgroundColor : ParallelImageProcessor
{
/// <summary>
/// The epsilon for comparing floating point numbers.
/// </summary>
private const float Epsilon = 0.001f;
/// <summary>
/// Initializes a new instance of the <see cref="BackgroundColor"/> class.
/// </summary>
@ -46,11 +52,16 @@ namespace ImageProcessor.Filters
{
Color color = source[x, y];
if (color.A < 1)
if (color.A < 1 && color.A > 0)
{
color = Color.Lerp(color, backgroundColor, .5f);
}
if (Math.Abs(color.A) < Epsilon)
{
color = backgroundColor;
}
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)
{
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 endX = sourceRectangle.Right;
Color color = this.Color;
Color glowColor = this.Color;
Vector2 centre = Rectangle.Center(targetRectangle).ToVector2();
float rX = this.RadiusX > 0 ? this.RadiusX : targetRectangle.Width / 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));
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>
/// Gets or sets the vignette color to apply.
/// </summary>
public Color Color { get; set; } = Color.Black;
public Color Color { get; set; } = new Color(0, 0, 0, 1);
/// <summary>
/// Gets or sets the the x-radius.
@ -34,7 +34,7 @@ namespace ImageProcessor.Filters
{
int startX = sourceRectangle.X;
int endX = sourceRectangle.Right;
Color color = this.Color;
Color vignetteColor = this.Color;
Vector2 centre = Rectangle.Center(targetRectangle).ToVector2();
float rX = this.RadiusX > 0 ? this.RadiusX : targetRectangle.Width / 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));
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>
public class BmpEncoder : IImageEncoder
{
/// <summary>
/// The the transparency threshold.
/// </summary>
private int threshold = 128;
/// <summary>
/// Gets or sets the quality of output for images.
/// </summary>
@ -31,15 +26,6 @@ namespace ImageProcessor.Formats
/// <inheritdoc/>
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/>
public bool IsSupportedFileExtension(string extension)
{
@ -132,11 +118,6 @@ namespace ImageProcessor.Formats
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.G);
writer.Write(color.R);

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

@ -15,11 +15,6 @@ namespace ImageProcessor.Formats
/// </summary>
public class GifEncoder : IImageEncoder
{
/// <summary>
/// The the transparency threshold.
/// </summary>
private int threshold = 128;
/// <summary>
/// Gets or sets the quality of output for images.
/// </summary>
@ -32,15 +27,6 @@ namespace ImageProcessor.Formats
/// <inheritdoc/>
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/>
public bool IsSupportedFileExtension(string extension)
{
@ -138,7 +124,7 @@ namespace ImageProcessor.Formats
private QuantizedImage WriteColorTable(ImageBase image, Stream stream, int quality, int bitDepth)
{
// 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);
// 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>
private readonly int maxColors;
/// <summary>
/// The the transparency threshold.
/// </summary>
private int threshold = 128;
/// <summary>
/// Initializes a new instance of the <see cref="OctreeQuantizer"/> class.
/// </summary>
@ -65,15 +60,6 @@ namespace ImageProcessor.Formats
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>
/// Process the pixel in the first pass of the algorithm
/// </summary>
@ -105,7 +91,7 @@ namespace ImageProcessor.Formats
byte paletteIndex = (byte)this.maxColors;
// 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);
}

5
src/ImageProcessor/Formats/IImageEncoder.cs

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

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

@ -21,11 +21,6 @@ namespace ImageProcessor.Formats
/// </summary>
private int quality = 100;
/// <summary>
/// The the transparency threshold.
/// </summary>
private int threshold = 128;
/// <summary>
/// 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).
@ -37,15 +32,6 @@ namespace ImageProcessor.Formats
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/>
public string MimeType => "image/jpeg";
@ -127,11 +113,6 @@ namespace ImageProcessor.Formats
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 + 1] = color.G;
samples[start + 2] = color.B;

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

@ -18,11 +18,6 @@ namespace ImageProcessor.Formats
/// </summary>
private const int MaxBlockSize = 0xFFFF;
/// <summary>
/// The the transparency threshold.
/// </summary>
private int threshold;
/// <summary>
/// Initializes a new instance of the <see cref="PngEncoder"/> class.
/// </summary>
@ -37,15 +32,6 @@ namespace ImageProcessor.Formats
/// <remarks>Png is a lossless format so this is not used in this encoder.</remarks>
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/>
public string MimeType => "image/png";
@ -342,11 +328,6 @@ namespace ImageProcessor.Formats
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 + 1] = color.G;
data[dataOffset + 2] = color.B;

2
src/ImageProcessor/Samplers/ImageSampleExtensions.cs

@ -121,7 +121,7 @@ namespace ImageProcessor.Samplers
/// <returns>The <see cref="Image"/></returns>
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>

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>
{
{ "Brightness-50", new Brightness(50) },
{ "Brightness--50", new Brightness(-50) },
{ "Contrast-50", new Contrast(50) },
{ "Contrast--50", new Contrast(-50) },
{ "BackgroundColor", new BackgroundColor(new Color(243 / 255f, 87 / 255f, 161 / 255f))},
{ "Blend", new Blend(new Image(File.OpenRead("TestImages/Formats/Bmp/Car.bmp")),50)},
{ "Saturation-50", new Saturation(50) },
{ "Saturation--50", new Saturation(-50) },
//{ "Brightness-50", new Brightness(50) },
//{ "Brightness--50", new Brightness(-50) },
//{ "Contrast-50", new Contrast(50) },
//{ "Contrast--50", new Contrast(-50) },
//{ "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)},
//{ "Saturation-50", new Saturation(50) },
//{ "Saturation--50", new Saturation(-50) },
{ "Alpha--50", new Alpha(50) },
{ "Invert", new Invert() },
{ "Sepia", new Sepia() },
{ "BlackWhite", new BlackWhite() },
{ "Lomograph", new Lomograph() },
//{ "Invert", new Invert() },
//{ "Sepia", new Sepia() },
//{ "BlackWhite", new BlackWhite() },
//{ "Lomograph", new Lomograph() },
{ "Polaroid", new Polaroid() },
{ "Kodachrome", new Kodachrome() },
{ "GreyscaleBt709", new GreyscaleBt709() },
{ "GreyscaleBt601", new GreyscaleBt601() },
{ "Kayyali", new Kayyali() },
{ "Kirsch", new Kirsch() },
{ "Laplacian3X3", new Laplacian3X3() },
{ "Laplacian5X5", new Laplacian5X5() },
{ "LaplacianOfGaussian", new LaplacianOfGaussian() },
{ "Prewitt", new Prewitt() },
{ "RobertsCross", new RobertsCross() },
{ "Scharr", new Scharr() },
{ "Sobel", new Sobel {Greyscale = true} },
{ "Pixelate", new Pixelate(8) },
{ "GuassianBlur", new GuassianBlur(10) },
{ "GuassianSharpen", new GuassianSharpen(10) },
{ "Hue-180", new Hue(180) },
{ "Hue--180", new Hue(-180) },
{ "BoxBlur", new BoxBlur(10) },
{"Vignette", new Vignette()}
//{ "Kodachrome", new Kodachrome() },
//{ "GreyscaleBt709", new GreyscaleBt709() },
//{ "GreyscaleBt601", new GreyscaleBt601() },
//{ "Kayyali", new Kayyali() },
//{ "Kirsch", new Kirsch() },
//{ "Laplacian3X3", new Laplacian3X3() },
//{ "Laplacian5X5", new Laplacian5X5() },
//{ "LaplacianOfGaussian", new LaplacianOfGaussian() },
//{ "Prewitt", new Prewitt() },
//{ "RobertsCross", new RobertsCross() },
//{ "Scharr", new Scharr() },
//{ "Sobel", new Sobel {Greyscale = true} },
//{ "Pixelate", new Pixelate(8) },
//{ "GuassianBlur", new GuassianBlur(10) },
//{ "GuassianSharpen", new GuassianSharpen(10) },
//{ "Hue-180", new Hue(180) },
//{ "Hue--180", new Hue(-180) },
//{ "BoxBlur", new BoxBlur(10) },
//{"Vignette", new Vignette()}
};
[Theory]

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

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

Loading…
Cancel
Save