diff --git a/src/ImageProcessorCore/Quantizers/Options/Quantization.cs b/src/ImageProcessorCore/Quantizers/Options/Quantization.cs
new file mode 100644
index 0000000000..3dd0c87997
--- /dev/null
+++ b/src/ImageProcessorCore/Quantizers/Options/Quantization.cs
@@ -0,0 +1,28 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageProcessorCore
+{
+ ///
+ /// Provides enumeration over how an image should be quantized.
+ ///
+ public enum Quantization
+ {
+ ///
+ /// An adaptive Octree quantizer. Fast with good quality.
+ ///
+ Octree,
+
+ ///
+ /// Xiaolin Wu's Color Quantizer which generates high quality output.
+ ///
+ Wu,
+
+ ///
+ /// Palette based, Uses the collection of web-safe colors by default.
+ ///
+ Palette
+ }
+}
diff --git a/src/ImageProcessorCore/Quantizers/Palette/PaletteQuantizer.cs b/src/ImageProcessorCore/Quantizers/Palette/PaletteQuantizer.cs
index 6fafad5f09..9de4a4dc4a 100644
--- a/src/ImageProcessorCore/Quantizers/Palette/PaletteQuantizer.cs
+++ b/src/ImageProcessorCore/Quantizers/Palette/PaletteQuantizer.cs
@@ -101,7 +101,7 @@ namespace ImageProcessorCore.Quantizers
int leastDistance = int.MaxValue;
int red = bytes[0];
int green = bytes[1];
- int blue = bytes[3];
+ int blue = bytes[2];
// Loop through the entire palette, looking for the closest color match
for (int index = 0; index < this.colors.Length; index++)
diff --git a/src/ImageProcessorCore/Quantizers/Quantize.cs b/src/ImageProcessorCore/Quantizers/Quantize.cs
new file mode 100644
index 0000000000..2db63a76b8
--- /dev/null
+++ b/src/ImageProcessorCore/Quantizers/Quantize.cs
@@ -0,0 +1,65 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+using ImageProcessorCore.Quantizers;
+
+namespace ImageProcessorCore
+{
+ ///
+ /// Extension methods for the type.
+ ///
+ public static partial class ImageExtensions
+ {
+ ///
+ /// Applies quantization to the image.
+ ///
+ /// The pixel format.
+ /// The packed format. long, float.
+ /// The image this method extends.
+ /// The quantization mode to apply to perform the operation.
+ /// The maximum number of colors to return. Defaults to 256.
+ /// The .
+ public static Image Quantize(this Image source, Quantization mode = Quantization.Octree, int maxColors = 256)
+ where T : IPackedVector
+ where TP : struct
+ {
+ IQuantizer quantizer;
+ switch (mode)
+ {
+ case Quantization.Wu:
+ quantizer = new WuQuantizer();
+ break;
+
+ case Quantization.Palette:
+ quantizer = new PaletteQuantizer();
+ break;
+
+ default:
+ quantizer = new OctreeQuantizer();
+ break;
+ }
+
+ return Quantize(source, quantizer, maxColors);
+ }
+
+ ///
+ /// Applies quantization to the image.
+ ///
+ /// The pixel format.
+ /// The packed format. long, float.
+ /// The image this method extends.
+ /// The quantizer to apply to perform the operation.
+ /// The maximum number of colors to return.
+ /// The .
+ public static Image Quantize(this Image source, IQuantizer quantizer, int maxColors)
+ where T : IPackedVector
+ where TP : struct
+ {
+ QuantizedImage quantizedImage = quantizer.Quantize(source, maxColors);
+ source.SetPixels(source.Width, source.Height, quantizedImage.ToImage().Pixels);
+ return source;
+ }
+ }
+}
diff --git a/src/ImageProcessorCore/Quantizers/QuantizedImage.cs b/src/ImageProcessorCore/Quantizers/QuantizedImage.cs
index 123efe6533..680f8b7ab0 100644
--- a/src/ImageProcessorCore/Quantizers/QuantizedImage.cs
+++ b/src/ImageProcessorCore/Quantizers/QuantizedImage.cs
@@ -90,9 +90,8 @@ namespace ImageProcessorCore.Quantizers
Bootstrapper.Instance.ParallelOptions,
i =>
{
- int offset = i * 4;
T color = this.Palette[Math.Min(palletCount, this.Pixels[i])];
- pixels[offset] = color;
+ pixels[i] = color;
});
image.SetPixels(this.Width, this.Height, pixels);
diff --git a/src/ImageProcessorCore/Samplers/Crop.cs b/src/ImageProcessorCore/Samplers/Crop.cs
index 04c96dbd1a..84d343d6d6 100644
--- a/src/ImageProcessorCore/Samplers/Crop.cs
+++ b/src/ImageProcessorCore/Samplers/Crop.cs
@@ -1,7 +1,7 @@
//
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
-// -------------------------------------------------------------------------------------------------------------------
+//
namespace ImageProcessorCore
{
diff --git a/src/ImageProcessorCore/Samplers/EntropyCrop.cs b/src/ImageProcessorCore/Samplers/EntropyCrop.cs
index 631662d800..cbc9eedee8 100644
--- a/src/ImageProcessorCore/Samplers/EntropyCrop.cs
+++ b/src/ImageProcessorCore/Samplers/EntropyCrop.cs
@@ -1,7 +1,7 @@
//
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
-// -------------------------------------------------------------------------------------------------------------------
+//
namespace ImageProcessorCore
{
diff --git a/src/ImageProcessorCore/Samplers/Pad.cs b/src/ImageProcessorCore/Samplers/Pad.cs
index 0a7277144e..6be9f654c4 100644
--- a/src/ImageProcessorCore/Samplers/Pad.cs
+++ b/src/ImageProcessorCore/Samplers/Pad.cs
@@ -1,7 +1,7 @@
//
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
-// -------------------------------------------------------------------------------------------------------------------
+//
namespace ImageProcessorCore
{
diff --git a/src/ImageProcessorCore/Samplers/Resize.cs b/src/ImageProcessorCore/Samplers/Resize.cs
index e9d3093f4d..40b07469ec 100644
--- a/src/ImageProcessorCore/Samplers/Resize.cs
+++ b/src/ImageProcessorCore/Samplers/Resize.cs
@@ -1,7 +1,7 @@
//
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
-// -------------------------------------------------------------------------------------------------------------------
+//
namespace ImageProcessorCore
{
diff --git a/tests/ImageProcessorCore.Tests/Formats/Bmp/BitmapTests.cs b/tests/ImageProcessorCore.Tests/Formats/Bmp/BitmapTests.cs
new file mode 100644
index 0000000000..23884e9ed6
--- /dev/null
+++ b/tests/ImageProcessorCore.Tests/Formats/Bmp/BitmapTests.cs
@@ -0,0 +1,47 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageProcessorCore.Tests
+{
+ using System.IO;
+
+ using Formats;
+
+ using Xunit;
+
+ public class BitmapTests : FileTestBase
+ {
+ [Fact]
+ public void BitmapCanEncodeDifferentBitRates()
+ {
+ const string path = "TestOutput/Bmp";
+ if (!Directory.Exists(path))
+ {
+ Directory.CreateDirectory(path);
+ }
+
+ foreach (string file in Files)
+ {
+ using (FileStream stream = File.OpenRead(file))
+ {
+ Image image = new Image(stream);
+ string encodeFilename = path + "/24-" + Path.GetFileNameWithoutExtension(file) + ".bmp";
+
+ using (FileStream output = File.OpenWrite(encodeFilename))
+ {
+ image.Save(output, new BmpEncoder { BitsPerPixel = BmpBitsPerPixel.Pixel24 });
+ }
+
+ encodeFilename = path + "/32-" + Path.GetFileNameWithoutExtension(file) + ".bmp";
+
+ using (FileStream output = File.OpenWrite(encodeFilename))
+ {
+ image.Save(output, new BmpEncoder { BitsPerPixel = BmpBitsPerPixel.Pixel32 });
+ }
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/ImageProcessorCore.Tests/Formats/GeneralFormatTests.cs b/tests/ImageProcessorCore.Tests/Formats/GeneralFormatTests.cs
new file mode 100644
index 0000000000..462a39cebc
--- /dev/null
+++ b/tests/ImageProcessorCore.Tests/Formats/GeneralFormatTests.cs
@@ -0,0 +1,199 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageProcessorCore.Tests
+{
+ using System;
+ using System.IO;
+
+ using Xunit;
+
+ public class GeneralFormatTests : FileTestBase
+ {
+ [Fact]
+ public void ResolutionShouldChange()
+ {
+ const string path = "TestOutput/Resolution";
+ if (!Directory.Exists(path))
+ {
+ Directory.CreateDirectory(path);
+ }
+
+ foreach (string file in Files)
+ {
+ using (FileStream stream = File.OpenRead(file))
+ {
+ string filename = Path.GetFileName(file);
+
+ Image image = new Image(stream);
+ using (FileStream output = File.OpenWrite($"{path}/{filename}"))
+ {
+ image.VerticalResolution = 150;
+ image.HorizontalResolution = 150;
+ image.Save(output);
+ }
+ }
+ }
+ }
+
+ [Fact]
+ public void ImageCanEncodeToString()
+ {
+ const string path = "TestOutput/ToString";
+ if (!Directory.Exists(path))
+ {
+ Directory.CreateDirectory(path);
+ }
+
+ foreach (string file in Files)
+ {
+ using (FileStream stream = File.OpenRead(file))
+ {
+ Image image = new Image(stream);
+ string filename = path + "/" + Path.GetFileNameWithoutExtension(file) + ".txt";
+ File.WriteAllText(filename, image.ToString());
+ }
+ }
+ }
+
+ [Fact]
+ public void DecodeThenEncodeImageFromStreamShouldSucceed()
+ {
+ const string path = "TestOutput/Encode";
+ if (!Directory.Exists(path))
+ {
+ Directory.CreateDirectory(path);
+ }
+
+ foreach (string file in Files)
+ {
+ using (FileStream stream = File.OpenRead(file))
+ {
+ Image image = new Image(stream);
+ string encodeFilename = path + "/" + Path.GetFileName(file);
+
+ using (FileStream output = File.OpenWrite(encodeFilename))
+ {
+ image.Save(output);
+ }
+ }
+ }
+ }
+
+ [Fact]
+ public void QuantizeImageShouldPreserveMaximumColorPrecision()
+ {
+ const string path = "TestOutput/Quantize";
+ if (!Directory.Exists(path))
+ {
+ Directory.CreateDirectory(path);
+ }
+
+ foreach (string file in Files)
+ {
+ using (FileStream stream = File.OpenRead(file))
+ {
+ Image image = new Image(stream);
+
+ // Copy the original pixels to save decoding time.
+ Color[] pixels = new Color[image.Width * image.Height];
+ Array.Copy(image.Pixels, pixels, image.Pixels.Length);
+
+ using (FileStream output = File.OpenWrite($"{path}/Octree-{Path.GetFileName(file)}"))
+ {
+ image.Quantize(Quantization.Octree)
+ .Save(output, image.CurrentImageFormat);
+
+ }
+
+ image.SetPixels(image.Width, image.Height, pixels);
+ using (FileStream output = File.OpenWrite($"{path}/Wu-{Path.GetFileName(file)}"))
+ {
+ image.Quantize(Quantization.Wu)
+ .Save(output, image.CurrentImageFormat);
+ }
+
+ image.SetPixels(image.Width, image.Height, pixels);
+ using (FileStream output = File.OpenWrite($"{path}/Palette-{Path.GetFileName(file)}"))
+ {
+ image.Quantize(Quantization.Palette)
+ .Save(output, image.CurrentImageFormat);
+ }
+ }
+ }
+ }
+
+ [Fact]
+ public void ImageCanConvertFormat()
+ {
+ const string path = "TestOutput/Format";
+ if (!Directory.Exists(path))
+ {
+ Directory.CreateDirectory(path);
+ }
+
+ foreach (string file in Files)
+ {
+ using (FileStream stream = File.OpenRead(file))
+ {
+ Image image = new Image(stream);
+ using (FileStream output = File.OpenWrite($"{path}/{Path.GetFileNameWithoutExtension(file)}.gif"))
+ {
+ image.SaveAsGif(output);
+ }
+
+ using (FileStream output = File.OpenWrite($"{path}/{Path.GetFileNameWithoutExtension(file)}.bmp"))
+ {
+ image.SaveAsBmp(output);
+ }
+
+ using (FileStream output = File.OpenWrite($"{path}/{Path.GetFileNameWithoutExtension(file)}.jpg"))
+ {
+ image.SaveAsJpeg(output);
+ }
+
+ using (FileStream output = File.OpenWrite($"{path}/{Path.GetFileNameWithoutExtension(file)}.png"))
+ {
+ image.SaveAsPng(output);
+ }
+ }
+ }
+ }
+
+ [Fact]
+ public void ImageShouldPreservePixelByteOrderWhenSerialized()
+ {
+ const string path = "TestOutput/Serialized";
+ if (!Directory.Exists(path))
+ {
+ Directory.CreateDirectory(path);
+ }
+
+ foreach (string file in Files)
+ {
+ using (FileStream stream = File.OpenRead(file))
+ {
+ Image image = new Image(stream);
+ byte[] serialized;
+ using (MemoryStream memoryStream = new MemoryStream())
+ {
+ image.Save(memoryStream);
+ memoryStream.Flush();
+ serialized = memoryStream.ToArray();
+ }
+
+ using (MemoryStream memoryStream = new MemoryStream(serialized))
+ {
+ Image image2 = new Image(memoryStream);
+ using (FileStream output = File.OpenWrite($"{path}/{Path.GetFileName(file)}"))
+ {
+ image2.Save(output);
+ }
+ }
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/ImageProcessorCore.Tests/Formats/Jpg/GeneralFormatTests.cs b/tests/ImageProcessorCore.Tests/Formats/Png/PngTests.cs
similarity index 52%
rename from tests/ImageProcessorCore.Tests/Formats/Jpg/GeneralFormatTests.cs
rename to tests/ImageProcessorCore.Tests/Formats/Png/PngTests.cs
index 69e2ef0f10..c86238cbac 100644
--- a/tests/ImageProcessorCore.Tests/Formats/Jpg/GeneralFormatTests.cs
+++ b/tests/ImageProcessorCore.Tests/Formats/Png/PngTests.cs
@@ -1,4 +1,4 @@
-//
+//
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
//
@@ -7,30 +7,30 @@ namespace ImageProcessorCore.Tests
{
using System.IO;
+ using Formats;
+
using Xunit;
- public class GeneralFormatTests : FileTestBase
+ public class PngTests : FileTestBase
{
[Fact]
- public void ResolutionShouldChange()
+ public void ImageCanSaveIndexedPng()
{
- if (!Directory.Exists("TestOutput/Resolution"))
+ const string path = "TestOutput/Png";
+ if (!Directory.Exists(path))
{
- Directory.CreateDirectory("TestOutput/Resolution");
+ Directory.CreateDirectory(path);
}
foreach (string file in Files)
{
using (FileStream stream = File.OpenRead(file))
{
- string filename = Path.GetFileName(file);
-
Image image = new Image(stream);
- using (FileStream output = File.OpenWrite($"TestOutput/Resolution/{filename}"))
+ using (FileStream output = File.OpenWrite($"{path}/{Path.GetFileNameWithoutExtension(file)}.png"))
{
- image.VerticalResolution = 150;
- image.HorizontalResolution = 150;
- image.Save(output);
+ image.Quality = 256;
+ image.Save(output, new PngFormat());
}
}
}