Browse Source

Various fixes

- Fix alpha clamping on Resize to prevent bleed.
- Finish Color premultiplication updates


Former-commit-id: e0cc8e3c08b0626fe1a1a7ce4c2f1246c1b912ca
Former-commit-id: 503f9fa8137a2ff561d697bb2a4cdd2071f1728d
Former-commit-id: 51ee649dbfec8fc8cc4db412ac6b743ba2ce991c
pull/17/head
James Jackson-South 10 years ago
parent
commit
56584d9656
  1. 33
      README.md
  2. 33
      src/ImageProcessor/Colors/Color.cs
  3. 2
      src/ImageProcessor/Colors/Formats/Bgra32.cs
  4. 2
      src/ImageProcessor/Colors/Formats/Hsv.cs
  5. 2
      src/ImageProcessor/Colors/Formats/YCbCr.cs
  6. 3
      src/ImageProcessor/Filters/Alpha.cs
  7. 3
      src/ImageProcessor/Filters/BackgroundColor.cs
  8. 15
      src/ImageProcessor/Formats/Bmp/BmpEncoder.cs
  9. 7
      src/ImageProcessor/Formats/Gif/Quantizer/Quantizer.cs
  10. 3
      src/ImageProcessor/Formats/Jpg/JpegEncoder.cs
  11. 5
      src/ImageProcessor/Formats/Png/PngEncoder.cs
  12. 4
      src/ImageProcessor/Samplers/Resize.cs
  13. 4
      src/ImageProcessor/project.json
  14. 55
      tests/ImageProcessor.Tests/Processors/Filters/FilterTests.cs
  15. 6
      tests/ImageProcessor.Tests/Processors/Formats/EncoderDecoderTests.cs
  16. 10
      tests/ImageProcessor.Tests/Processors/ProcessorTestBase.cs
  17. 3
      tests/ImageProcessor.Tests/Processors/Samplers/SamplerTests.cs
  18. 2
      tests/ImageProcessor.Tests/project.json

33
README.md

@ -4,14 +4,40 @@
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.
[![Build status](https://ci.appveyor.com/api/projects/status/8ypr7527dnao04yr/branch/V3?svg=true)](https://ci.appveyor.com/project/JamesSouth/imageprocessor/branch/V3)
[![Gitter](https://badges.gitter.im/Join Chat.svg)](https://gitter.im/JimBobSquarePants/ImageProcessor?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
###Why am I writing this?
With NETCore there is currently no version of `System.Drawing` to allow continued progress of the existing ImageProcessor library. Progress developing a crossplatform update are restricted to the [CoreFXLab repo](https://github.com/dotnet/corefxlab/tree/master/src/System.Drawing.Graphics) where progress seems to be very slow.
With NETCore there is currently no version of `System.Drawing` to allow continued progress of the existing ImageProcessor library. Progress developing a cross-platform update are restricted to the [CoreFXLab repo](https://github.com/dotnet/corefxlab/tree/master/src/System.Drawing.Graphics) where progress seems to be very slow.
###Is this wise?
Honestly... I don't know. I could be writing code that may be suddenly obsolete. There has been little [feedback](https://github.com/dotnet/corefxlab/issues/86#issuecomment-139930600) on questions I've asked but it's a nice learning process if anything and I will definitely be releasing the code for consumption.
### Installation
At present the code is pre-release but when ready it will be available on [Nuget](http://www.nuget.org).
**Pre-release downloads**
We already have a [MyGet package repository](https://www.myget.org/F/imageprocessor/api/v3/index.json) - for bleeding-edge / development NuGet releases.
### Manual build
If you prefer, you can compile ImageProcessor yourself (please do and help!), you'll need:
- Visual Studio 2015 (or above)
- The [Windows 10 development tools](https://dev.windows.com/en-us/downloads) - Click `Get Visual Studio Community`.
- Dnvm and Dnx installed
To install the last two please see the instructions at the [DotNet documentation](http://dotnet.readthedocs.org/en/latest/getting-started/installing-core-windows.html)
To clone it locally click the "Clone in Windows" button above or run the following git commands.
```bash
git clone https://github.com/JimBobSquarePants/ImageProcessor
```
###What works so far/ What is planned?
- Encoding/decoding of image formats (plugable)
@ -19,8 +45,8 @@ Honestly... I don't know. I could be writing code that may be suddenly obsolete.
- [x] bmp (More bmp format saving support required, 24bit just now)
- [x] png (Need updating for saving indexed support)
- [x] gif
- Basic color structs with implicit operators. Vector backed. Need help investigating premultiplication.
- [x] Color - Float based, No limit to r, g, b, a values allowing for a fuller color range.
- Basic color structs with implicit operators. Vector backed.
- [x] Color - Float based, premultiplied alpha, No limit to r, g, b, a values allowing for a fuller color range.
- [x] BGRA32
- [ ] CIE Lab
- [ ] CIE XYZ
@ -81,6 +107,7 @@ Honestly... I don't know. I could be writing code that may be suddenly obsolete.
- [x] Alpha
- [x] Contrast
- [x] Invert
- [x] BackgroundColor
- [x] Brightness
- [x] Saturation
- [ ] Hue

33
src/ImageProcessor/Colors/Color.cs

@ -81,10 +81,13 @@ namespace ImageProcessor
if (hex.Length == 8)
{
this.R = Convert.ToByte(hex.Substring(2, 2), 16) / 255f;
this.G = Convert.ToByte(hex.Substring(4, 2), 16) / 255f;
this.B = Convert.ToByte(hex.Substring(6, 2), 16) / 255f;
this.A = Convert.ToByte(hex.Substring(0, 2), 16) / 255f;
float r = Convert.ToByte(hex.Substring(2, 2), 16) / 255f;
float g = Convert.ToByte(hex.Substring(4, 2), 16) / 255f;
float b = Convert.ToByte(hex.Substring(6, 2), 16) / 255f;
float a = Convert.ToByte(hex.Substring(0, 2), 16) / 255f;
this.backingVector = Color.FromNonPremultiplied(new Color(r, g, b, a)).ToVector4();
}
else if (hex.Length == 6)
{
@ -95,13 +98,13 @@ namespace ImageProcessor
}
else
{
string r = char.ToString(hex[0]);
string g = char.ToString(hex[1]);
string b = char.ToString(hex[2]);
string rh = char.ToString(hex[0]);
string gh = char.ToString(hex[1]);
string bh = char.ToString(hex[2]);
this.B = Convert.ToByte(b + b, 16) / 255f;
this.G = Convert.ToByte(g + g, 16) / 255f;
this.R = Convert.ToByte(r + r, 16) / 255f;
this.B = Convert.ToByte(bh + bh, 16) / 255f;
this.G = Convert.ToByte(gh + gh, 16) / 255f;
this.R = Convert.ToByte(rh + rh, 16) / 255f;
this.A = 1;
}
}
@ -235,7 +238,7 @@ namespace ImageProcessor
/// </returns>
public static implicit operator Color(Bgra32 color)
{
return new Color(color.R / 255f, color.G / 255f, color.B / 255f, color.A / 255f);
return Color.FromNonPremultiplied(new Color(color.R / 255f, color.G / 255f, color.B / 255f, color.A / 255f));
}
/// <summary>
@ -471,8 +474,12 @@ namespace ImageProcessor
{
amount = amount.Clamp(0f, 1f);
//return (from * (1 - amount)) + (to * amount);
return (from * (1 - amount)) + to ;
if (to.A < 1)
{
return (from * (1 - amount)) + to;
}
return (from * (1 - amount)) + (to * amount);
}
/// <summary>

2
src/ImageProcessor/Colors/Formats/Bgra32.cs

@ -94,7 +94,7 @@ namespace ImageProcessor
/// </returns>
public static implicit operator Bgra32(Color color)
{
color = color.Limited;
color = Color.ToNonPremultiplied(color.Limited);
return new Bgra32((255f * color.B).ToByte(), (255f * color.G).ToByte(), (255f * color.R).ToByte(), (255f * color.A).ToByte());
}

2
src/ImageProcessor/Colors/Formats/Hsv.cs

@ -76,7 +76,7 @@ namespace ImageProcessor
/// </returns>
public static implicit operator Hsv(Color color)
{
color = color.Limited;
color = Color.ToNonPremultiplied(color.Limited);
float r = color.R;
float g = color.G;
float b = color.B;

2
src/ImageProcessor/Colors/Formats/YCbCr.cs

@ -76,7 +76,7 @@ namespace ImageProcessor
/// </returns>
public static implicit operator YCbCr(Color color)
{
color = color.Limited;
color = Color.ToNonPremultiplied(color.Limited);
float r = color.R * 255f;
float g = color.G * 255f;
float b = color.B * 255f;

3
src/ImageProcessor/Filters/Alpha.cs

@ -49,8 +49,9 @@ namespace ImageProcessor.Filters
{
for (int x = startX; x < endX; x++)
{
Color color = source[x, y];
Color color = Color.ToNonPremultiplied(source[x, y]);
color.A = color.A * alpha;
color = Color.FromNonPremultiplied(color);
target[x, y] = color;
}
}

3
src/ImageProcessor/Filters/BackgroundColor.cs

@ -46,8 +46,7 @@ namespace ImageProcessor.Filters
{
Color color = source[x, y];
// TODO: Fix this nonesense.
if (color.A < .9)
if (color.A < 1)
{
color = Color.Lerp(color, backgroundColor, .5f);
}

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

@ -110,9 +110,18 @@ namespace ImageProcessor.Formats
// Limit the output range and multiply out from our floating point.
// Convert back to b-> g-> r-> a order.
writer.Write((byte)(data[offset + 2].Clamp(0, 1) * 255));
writer.Write((byte)(data[offset + 1].Clamp(0, 1) * 255));
writer.Write((byte)(data[offset].Clamp(0, 1) * 255));
// Convert to non-premultiplied color.
float r = data[offset];
float g = data[offset + 1];
float b = data[offset + 2];
float a = data[offset + 3];
// Implicit cast to Bgra32 handles premultiplication conversion.
Bgra32 color = new Color(r, g, b, a);
writer.Write(color.B);
writer.Write(color.G);
writer.Write(color.R);
}
for (int i = 0; i < amount; i++)

7
src/ImageProcessor/Formats/Gif/Quantizer/Quantizer.cs

@ -79,7 +79,8 @@ namespace ImageProcessor.Formats
for (int x = 0; x < width; x++)
{
// Now I have the pixel, call the FirstPassQuantize function...
this.InitialQuantizePixel(Color.ToNonPremultiplied(source[x, y]));
// Implicit cast to Bgra32 handles premultiplication conversion.
this.InitialQuantizePixel(source[x, y]);
}
}
}
@ -106,8 +107,8 @@ namespace ImageProcessor.Formats
{
for (int x = 0; x < width; x++)
{
// Implicit cast here from Color.
Bgra32 sourcePixel = Color.ToNonPremultiplied(source[x, y]);
// Implicit cast to Bgra32 handles premultiplication conversion.
Bgra32 sourcePixel = 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.

3
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));
// Implicit cast to Bgra32 handles premultiplication conversion.
Bgra32 color = new Color(r, g, b, a);
samples[start] = color.R;
samples[start + 1] = color.G;

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

@ -326,7 +326,8 @@ namespace ImageProcessor.Formats
float b = pixels[pixelOffset + 2];
float a = pixels[pixelOffset + 3];
Bgra32 color = Color.ToNonPremultiplied(new Color(r, g, b, a));
// Implicit cast to Bgra32 handles premultiplication conversion.
Bgra32 color = new Color(r, g, b, a);
data[dataOffset] = color.R;
data[dataOffset + 1] = color.G;
@ -342,7 +343,7 @@ namespace ImageProcessor.Formats
b = pixels[lastOffset + 2];
a = pixels[lastOffset + 3];
color = Color.ToNonPremultiplied(new Color(r, g, b, a));
color = new Color(r, g, b, a);
data[dataOffset] -= color.R;
data[dataOffset + 1] -= color.G;

4
src/ImageProcessor/Samplers/Resize.cs

@ -17,7 +17,7 @@ namespace ImageProcessor.Samplers
/// <summary>
/// The epsilon for comparing floating point numbers.
/// </summary>
private const float Epsilon = 0.00001f;
private const float Epsilon = 0.01f;
/// <summary>
/// The horizontal weights.
@ -112,6 +112,8 @@ namespace ImageProcessor.Samplers
}
}
// Ensure are alpha values only reflect possible values to prevent bleed.
destination.A = (float)Math.Round(destination.A, 2);
target[x, y] = Color.Compand(destination);
}
}

4
src/ImageProcessor/project.json

@ -2,7 +2,7 @@
"version": "3.0.0-*",
"description": "ImageProcessor",
"authors": [
"James Jackson-South"
"James Jackson-South and contributors"
],
"tags": [
"Image Resize Crop Quality Gif Jpg Jpeg Bitmap Png Fluent Animated"
@ -19,7 +19,7 @@
"System.Runtime.Extensions": "4.0.10",
"System.Reflection": "4.0.10",
"System.IO": "4.0.10",
"StyleCop.Analyzers": "1.0.0-beta015",
"StyleCop.Analyzers": "1.0.0-beta016",
"Microsoft.NETCore": "5.0.1-beta-23409",
"Microsoft.NETCore.Platforms": "1.0.1-beta-23409"
},

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

@ -13,33 +13,34 @@ 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) },
{ "Blend", new Blend(new Image(File.OpenRead("TestImages/Formats/Bmp/Car.bmp")),15)},
{ "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() },
{ "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} },
{ "GuassianBlur", new GuassianBlur(10) },
{ "GuassianSharpen", new GuassianSharpen(10) }
//{ "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) },
//{ "Alpha--50", new Alpha(50) },
//{ "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} },
//{ "GuassianBlur", new GuassianBlur(10) },
//{ "GuassianSharpen", new GuassianSharpen(10) }
};
[Theory]

6
tests/ImageProcessor.Tests/Processors/Formats/EncoderDecoderTests.cs

@ -44,9 +44,9 @@
[Fact]
public void QuantizedImageShouldPreserveMaximumColorPrecision()
{
if (!Directory.Exists("Quantized"))
if (!Directory.Exists("TestOutput/Quantized"))
{
Directory.CreateDirectory("Quantized");
Directory.CreateDirectory("TestOutput/Quantized");
}
foreach (string file in Files)
@ -57,7 +57,7 @@
IQuantizer quantizer = new OctreeQuantizer();
QuantizedImage quantizedImage = quantizer.Quantize(image);
using (FileStream output = File.OpenWrite($"Quantized/{Path.GetFileName(file)}"))
using (FileStream output = File.OpenWrite($"TestOutput/Quantized/{Path.GetFileName(file)}"))
{
quantizedImage.ToImage().Save(output);
}

10
tests/ImageProcessor.Tests/Processors/ProcessorTestBase.cs

@ -20,20 +20,20 @@ namespace ImageProcessor.Tests
public static readonly List<string> Files = new List<string>
{
//"TestImages/Formats/Jpg/Backdrop.jpg",
//"TestImages/Formats/Jpg/Calliphora.jpg",
"TestImages/Formats/Jpg/china.jpg",
"TestImages/Formats/Jpg/Calliphora.jpg",
//"TestImages/Formats/Jpg/china.jpg",
//"TestImages/Formats/Jpg/ant.jpg",
//"TestImages/Formats/Jpg/parachute.jpg",
//"TestImages/Formats/Jpg/lomo.jpg",
//"TestImages/Formats/Jpg/shaftesbury.jpg",
//"TestImages/Formats/Jpg/gamma_dalai_lama_gray.jpg",
//"TestImages/Formats/Jpg/greyscale.jpg",
//"TestImages/Formats/Bmp/Car.bmp",
"TestImages/Formats/Bmp/Car.bmp",
"TestImages/Formats/Png/cballs.png",
"TestImages/Formats/Png/blur.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",
//"TestImages/Formats/Png/splash.png",
"TestImages/Formats/Gif/leaf.gif",
//"TestImages/Formats/Gif/ben2.gif",
//"TestImages/Formats/Gif/rings.gif",

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

@ -4,7 +4,6 @@ namespace ImageProcessor.Tests
using System.Diagnostics;
using System.IO;
using ImageProcessor.Filters;
using ImageProcessor.Samplers;
using Xunit;
@ -44,7 +43,7 @@ namespace ImageProcessor.Tests
{
Stopwatch watch = Stopwatch.StartNew();
Image image = new Image(stream);
string filename = Path.GetFileNameWithoutExtension(file) + "-" + name + Path.GetExtension(file);
string filename = Path.GetFileNameWithoutExtension(file) + "-" + name + ".jpg";
using (FileStream output = File.OpenWrite($"TestOutput/Resized/{filename}"))
{
image.Resize(image.Width / 2, image.Height / 2, sampler).Save(output);

2
tests/ImageProcessor.Tests/project.json

@ -1,7 +1,7 @@
{
"version": "1.0.0-*",
"description": "ImageProcessor.Tests Class Library",
"authors": [ "jeavon" ],
"authors": [ "James Jackson-South and contributors" ],
"tags": [ "" ],
"projectUrl": "",
"licenseUrl": "",

Loading…
Cancel
Save