Browse Source

Base logic for debugging. Encoder does not work. 😢

Former-commit-id: 3a4712523a7dd856388f33240956748af6e55fad
Former-commit-id: ebf7949a65bacd1c289c4c2da49195f3f76db672
Former-commit-id: 7e44492e3bceea442348ddd88dd0af75df3279af
pull/17/head
James Jackson-South 11 years ago
parent
commit
d603101d60
  1. 5
      src/ImageProcessor/Formats/Gif/GifDecoderCore.cs
  2. 146
      src/ImageProcessor/Formats/Gif/GifEncoder.cs
  3. 2
      src/ImageProcessor/Formats/Gif/Quantizer/QuantizedImage.cs
  4. 3
      src/ImageProcessor/Image.cs
  5. 5
      src/ImageProcessor/ImageBase.cs
  6. 31
      tests/ImageProcessor.Tests/Formats/EncoderDecoderTests.cs
  7. BIN
      tests/ImageProcessor.Tests/TestImages/Formats/Gif/a.gif

5
src/ImageProcessor/Formats/Gif/GifDecoderCore.cs

@ -26,8 +26,11 @@
private byte[] currentFrame; private byte[] currentFrame;
internal GifLogicalScreenDescriptor LogicalScreenDescriptor { get; set; } internal GifLogicalScreenDescriptor LogicalScreenDescriptor { get; set; }
internal GifGraphicsControlExtension GraphicsControlExtension { get; set; } internal GifGraphicsControlExtension GraphicsControlExtension { get; set; }
internal byte Quality { get; set; }
public void Decode(Image image, Stream stream) public void Decode(Image image, Stream stream)
{ {
this.image = image; this.image = image;
@ -324,6 +327,7 @@
{ {
currentImage = this.image; currentImage = this.image;
currentImage.SetPixels(imageWidth, imageHeight, pixels); currentImage.SetPixels(imageWidth, imageHeight, pixels);
currentImage.Quality = colorTable.Length / 3;
if (this.GraphicsControlExtension != null && this.GraphicsControlExtension.DelayTime > 0) if (this.GraphicsControlExtension != null && this.GraphicsControlExtension.DelayTime > 0)
{ {
@ -336,6 +340,7 @@
currentImage = frame; currentImage = frame;
currentImage.SetPixels(imageWidth, imageHeight, pixels); currentImage.SetPixels(imageWidth, imageHeight, pixels);
currentImage.Quality = colorTable.Length / 3;
if (this.GraphicsControlExtension != null && this.GraphicsControlExtension.DelayTime > 0) if (this.GraphicsControlExtension != null && this.GraphicsControlExtension.DelayTime > 0)
{ {

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

@ -14,31 +14,20 @@ namespace ImageProcessor.Formats
public class GifEncoder : IImageEncoder public class GifEncoder : IImageEncoder
{ {
/// <summary> /// <summary>
/// The quality. /// The gif decoder if any used to decode the original image.
/// </summary> /// </summary>
private int quality = 256; private GifDecoder gifDecoder;
/// <summary> /// <summary>
/// The gif decoder if any used to decode the original image. /// The currently processed image.
/// </summary> /// </summary>
private GifDecoder gifDecoder; private ImageBase currentImage;
/// <summary> /// <summary>
/// Gets or sets the quality of output for images. /// Gets or sets the quality of output for images.
/// </summary> /// </summary>
/// <remarks>For gifs the value ranges from 1 to 256.</remarks> /// <remarks>For gifs the value ranges from 1 to 256.</remarks>
public int Quality public int Quality { get; set; }
{
get
{
return this.quality;
}
set
{
this.quality = value.Clamp(1, 256);
}
}
/// <summary> /// <summary>
/// Gets the default file extension for this encoder. /// Gets the default file extension for this encoder.
@ -85,34 +74,46 @@ namespace ImageProcessor.Formats
this.WriteString(stream, GifConstants.FileType); this.WriteString(stream, GifConstants.FileType);
this.WriteString(stream, GifConstants.FileVersion); this.WriteString(stream, GifConstants.FileVersion);
int bitdepth = this.GetBitsNeededForColorDepth(this.Quality) - 1; // Calculate the quality.
int quality = this.Quality > 0 ? this.Quality : imageBase.Quality;
quality = quality > 0 ? quality.Clamp(1, 256) : 256;
// Get the number of bits.
int bitdepth = this.GetBitsNeededForColorDepth(quality);
// Write the LSD and check to see if we need a global color table. // Write the LSD and check to see if we need a global color table.
// Always true just now.
bool globalColor = this.WriteGlobalLogicalScreenDescriptor(image, stream, bitdepth); bool globalColor = this.WriteGlobalLogicalScreenDescriptor(image, stream, bitdepth);
QuantizedImage quantized;
// Should always be true.
if (globalColor) if (globalColor)
{ {
this.WriteColorTable(imageBase, stream, bitdepth); quantized = this.WriteColorTable(imageBase, stream, quality, bitdepth);
}
else
{
// Quantize the image returning a pallete.
IQuantizer quantizer = new OctreeQuantizer(quality.Clamp(1, 255), bitdepth + 1);
quantized = quantizer.Quantize(image);
} }
this.WriteGraphicalControlExtension(imageBase, stream); this.WriteGraphicalControlExtension(imageBase, stream);
// TODO: Write Comments // TODO: Write Comments
this.WriteApplicationExtension(stream, image.RepeatCount); this.WriteApplicationExtension(stream, image.RepeatCount, image.Frames.Count);
this.WriteImageDescriptor(quantized.ToImage(), quality, stream, true);
// TODO: Write Image Info // TODO: Write Image Info
foreach (ImageFrame frame in image.Frames) foreach (ImageFrame frame in image.Frames)
{ {
this.WriteColorTable(frame, stream, bitdepth);
this.WriteGraphicalControlExtension(frame, stream); this.WriteGraphicalControlExtension(frame, stream);
// TODO: Write Image Info this.WriteImageDescriptor(imageBase, quality, stream, false);
} }
throw new System.NotImplementedException();
// Cleanup // Cleanup
this.Quality = 256;
this.gifDecoder = null; this.gifDecoder = null;
} }
@ -127,7 +128,8 @@ namespace ImageProcessor.Formats
descriptor = this.gifDecoder.CoreDecoder.LogicalScreenDescriptor; descriptor = this.gifDecoder.CoreDecoder.LogicalScreenDescriptor;
descriptor.Width = (short)image.Width; descriptor.Width = (short)image.Width;
descriptor.Height = (short)image.Height; descriptor.Height = (short)image.Height;
descriptor.GlobalColorTableSize = this.Quality; descriptor.GlobalColorTableFlag = true;
descriptor.GlobalColorTableSize = bitDepth;
} }
else else
{ {
@ -136,29 +138,29 @@ namespace ImageProcessor.Formats
Width = (short)image.Width, Width = (short)image.Width,
Height = (short)image.Height, Height = (short)image.Height,
GlobalColorTableFlag = true, GlobalColorTableFlag = true,
GlobalColorTableSize = this.Quality GlobalColorTableSize = bitDepth
}; };
} }
this.WriteShort(stream, descriptor.Width); this.WriteShort(stream, descriptor.Width);
this.WriteShort(stream, descriptor.Width); this.WriteShort(stream, descriptor.Height);
int packed = 0x80 | // 1 : Global color table flag = 1 (GCT used) int packed = 0x80 | // 1 : Global color table flag = 1 (GCT used)
0x70 | // 2-4 : color resolution bitDepth - 1 | // 2-4 : color resolution
0x00 | // 5 : GCT sort flag = 0 0x00 | // 5 : GCT sort flag = 0
bitDepth; // 6-8 : GCT size assume 1:1 bitDepth - 1; // 6-8 : GCT size TODO: Check this.
this.WriteByte(stream, packed); this.WriteByte(stream, packed);
this.WriteByte(stream, descriptor.BackgroundColorIndex); // Background Color Index this.WriteByte(stream, descriptor.BackgroundColorIndex); // Background Color Index
this.WriteByte(stream, descriptor.PixelAspectRatio); // Pixel aspect ratio this.WriteByte(stream, descriptor.PixelAspectRatio); // Pixel aspect ratio. Assume 1:1
return descriptor.GlobalColorTableFlag; return descriptor.GlobalColorTableFlag;
} }
private void WriteColorTable(ImageBase image, Stream stream, 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(Math.Max(1, this.quality - 1), bitDepth); 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.
@ -169,34 +171,40 @@ namespace ImageProcessor.Formats
for (int i = 0; i < pixelCount; i++) for (int i = 0; i < pixelCount; i++)
{ {
int offset = i * 4; int offset = i * 3;
Bgra color = pallete[i]; Bgra color = pallete[i];
colorTable[offset + 0] = color.B; colorTable[offset + 2] = color.B;
colorTable[offset + 1] = color.G; colorTable[offset + 1] = color.G;
colorTable[offset + 2] = color.R; colorTable[offset + 0] = color.R;
} }
stream.Write(colorTable, 0, colorTableLength); stream.Write(colorTable, 0, colorTableLength);
return quantizedImage;
} }
private void WriteGraphicalControlExtension(ImageBase image, Stream stream) private void WriteGraphicalControlExtension(ImageBase image, Stream stream)
{ {
GifGraphicsControlExtension extension; GifGraphicsControlExtension extension;
// Calculate the quality.
int quality = this.Quality > 0 ? this.Quality : image.Quality;
quality = quality > 0 ? quality.Clamp(1, 256) : 256;
// Try and grab an existing descriptor. // Try and grab an existing descriptor.
// TODO: Check whether we need to. // TODO: Check whether we need to.
if (this.gifDecoder != null) if (this.gifDecoder != null)
{ {
// Ensure the dimensions etc are up to date. // Ensure the dimensions etc are up to date.
extension = this.gifDecoder.CoreDecoder.GraphicsControlExtension; extension = this.gifDecoder.CoreDecoder.GraphicsControlExtension;
extension.TransparencyFlag = this.Quality > 1; extension.TransparencyFlag = quality > 1;
extension.TransparencyIndex = this.Quality - 1; // Quantizer set last as transparent. extension.TransparencyIndex = quality - 1; // Quantizer set last as transparent.
extension.DelayTime = image.FrameDelay; extension.DelayTime = image.FrameDelay;
} }
else else
{ {
// TODO: Check transparency logic. // TODO: Check transparency logic.
bool hasTransparent = this.Quality > 1; bool hasTransparent = quality > 1;
DisposalMethod disposalMethod = hasTransparent DisposalMethod disposalMethod = hasTransparent
? DisposalMethod.RestoreToBackground ? DisposalMethod.RestoreToBackground
: DisposalMethod.Unspecified; : DisposalMethod.Unspecified;
@ -205,7 +213,7 @@ namespace ImageProcessor.Formats
{ {
DisposalMethod = disposalMethod, DisposalMethod = disposalMethod,
TransparencyFlag = hasTransparent, TransparencyFlag = hasTransparent,
TransparencyIndex = this.Quality - 1, TransparencyIndex = quality - 1,
DelayTime = image.FrameDelay DelayTime = image.FrameDelay
}; };
} }
@ -215,19 +223,20 @@ namespace ImageProcessor.Formats
this.WriteByte(stream, 4); // Size this.WriteByte(stream, 4); // Size
int packed = 0 | // 1-3 : Reserved int packed = 0 | // 1-3 : Reserved
(int)extension.DisposalMethod | // 4-6 : Disposal (int)extension.DisposalMethod << 2 | // 4-6 : Disposal
0 | // 7 : User input - 0 = none 0 | // 7 : User input - 0 = none
extension.TransparencyIndex; (extension.TransparencyFlag ? 1 : 0); // 8: Has transparent.
this.WriteByte(stream, packed); this.WriteByte(stream, packed);
this.WriteShort(stream, extension.DelayTime); this.WriteShort(stream, extension.DelayTime);
this.WriteByte(stream, extension.TransparencyIndex);
this.WriteByte(stream, GifConstants.Terminator); this.WriteByte(stream, GifConstants.Terminator);
} }
private void WriteApplicationExtension(Stream stream, ushort repeatCount) private void WriteApplicationExtension(Stream stream, ushort repeatCount, int frames)
{ {
// Application Extension Header // Application Extension Header
if (repeatCount != 1) if (repeatCount != 1 && frames > 0)
{ {
// 0 means loop indefinitely. count is set as play n + 1 times. // 0 means loop indefinitely. count is set as play n + 1 times.
// TODO: Check this as the correct value might be pulled from the decoder. // TODO: Check this as the correct value might be pulled from the decoder.
@ -245,6 +254,57 @@ namespace ImageProcessor.Formats
} }
} }
private void WriteImageDescriptor(ImageBase image, int quality, Stream stream, bool first)
{
this.WriteByte(stream, GifConstants.ImageDescriptorLabel); // 2c
// TODO: Can we capture this?
this.WriteShort(stream, 0); // Left position
this.WriteShort(stream, 0); // Top position
this.WriteShort(stream, image.Width);
this.WriteShort(stream, image.Height);
if (first)
{
// Calculate the quality.
int bitdepth = this.GetBitsNeededForColorDepth(quality);
// No LCT use GCT.
this.WriteByte(stream, 0);
// Write the image data. Pixels have already been quantized.
this.WriteImageData(image, stream, bitdepth);
}
else
{
// Calculate the quality.
quality = this.Quality > 0 ? this.Quality : image.Quality;
quality = quality > 0 ? quality.Clamp(1, 256) : 256;
int bitdepth = this.GetBitsNeededForColorDepth(quality);
int packed = 0x80 | // 1: Local color table flag = 1 (LCT used)
0x00 | // 2: Interlace flag 0
0x00 | // 3: Sort flag 0
0 | // 4-5: Reserved
bitdepth - 1;
this.WriteByte(stream, packed);
// Now immediately follow with the color table.
QuantizedImage quantized = this.WriteColorTable(image, stream, quality, bitdepth);
this.WriteImageData(quantized.ToImage(), stream, bitdepth);
}
this.WriteByte(stream, GifConstants.EndIntroducer);
}
private void WriteImageData(ImageBase image, Stream stream, int bitDepth)
{
LzwEncoder encoder = new LzwEncoder(image.Pixels, (byte)bitDepth);
encoder.Encode(stream);
this.WriteByte(stream, GifConstants.Terminator);
}
/// <summary> /// <summary>
/// Writes a short to the given stream. /// Writes a short to the given stream.
/// </summary> /// </summary>

2
src/ImageProcessor/Formats/Gif/Quantizer/QuantizedImage.cs

@ -17,7 +17,6 @@ namespace ImageProcessor.Formats
/// </summary> /// </summary>
public class QuantizedImage public class QuantizedImage
{ {
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="QuantizedImage"/> class. /// Initializes a new instance of the <see cref="QuantizedImage"/> class.
/// </summary> /// </summary>
@ -73,6 +72,7 @@ namespace ImageProcessor.Formats
public Image ToImage() public Image ToImage()
{ {
Image image = new Image(); Image image = new Image();
int pixelCount = this.Pixels.Length; int pixelCount = this.Pixels.Length;
byte[] bgraPixels = new byte[pixelCount * 4]; byte[] bgraPixels = new byte[pixelCount * 4];

3
src/ImageProcessor/Image.cs

@ -63,7 +63,8 @@ namespace ImageProcessor
{ {
new BmpEncoder(), new BmpEncoder(),
new JpegEncoder(), new JpegEncoder(),
new PngEncoder() new PngEncoder(),
new GifEncoder(),
}); });
/// <summary> /// <summary>

5
src/ImageProcessor/ImageBase.cs

@ -110,6 +110,11 @@ namespace ImageProcessor
/// </summary> /// </summary>
public Rectangle Bounds => new Rectangle(0, 0, this.Width, this.Height); public Rectangle Bounds => new Rectangle(0, 0, this.Width, this.Height);
/// <summary>
/// Gets or sets th quality of the image. This affects the output quality of lossy image formats.
/// </summary>
public int Quality { get; set; }
/// <summary> /// <summary>
/// Gets or sets the frame delay for animated images. /// Gets or sets the frame delay for animated images.
/// If not 0, this field specifies the number of hundredths (1/100) of a second to /// If not 0, this field specifies the number of hundredths (1/100) of a second to

31
tests/ImageProcessor.Tests/Formats/EncoderDecoderTests.cs

@ -13,8 +13,9 @@
[Theory] [Theory]
//[InlineData("TestImages/Car.bmp")] //[InlineData("TestImages/Car.bmp")]
//[InlineData("TestImages/Portrait.png")] //[InlineData("TestImages/Portrait.png")]
[InlineData("../../TestImages/Formats/Jpg/Backdrop.jpg")] //[InlineData("../../TestImages/Formats/Jpg/Backdrop.jpg")]
[InlineData("../../TestImages/Formats/Gif/leaf.gif")] [InlineData("../../TestImages/Formats/Gif/a.gif")]
//[InlineData("../../TestImages/Formats/Gif/leaf.gif")]
//[InlineData("TestImages/Windmill.gif")] //[InlineData("TestImages/Windmill.gif")]
//[InlineData("../../TestImages/Formats/Bmp/Car.bmp")] //[InlineData("../../TestImages/Formats/Bmp/Car.bmp")]
//[InlineData("../../TestImages/Formats/Png/cmyk.png")] //[InlineData("../../TestImages/Formats/Png/cmyk.png")]
@ -29,35 +30,13 @@
Stopwatch watch = Stopwatch.StartNew(); Stopwatch watch = Stopwatch.StartNew();
Image image = new Image(stream); Image image = new Image(stream);
OctreeQuantizer quantizer = new OctreeQuantizer(); string encodedFilename = "Encoded/" + Path.GetFileName(filename);
var o = quantizer.Quantize(image);
using (MemoryStream s2 = new MemoryStream())
{
LzwEncoder2 enc2 = new LzwEncoder2(image.Width, image.Height, o.Pixels, 8);
enc2.Encode(s2);
using (MemoryStream s = new MemoryStream())
{
LzwEncoder enc = new LzwEncoder(o.Pixels, 8);
enc.Encode(s);
var x = s.ToArray();
var y = s2.ToArray();
var a = x.Skip(1080);
var b = y.Skip(1080);
Assert.Equal(s.ToArray(), s2.ToArray());
}
}
string encodedFilename = "Encoded/" + Path.GetFileNameWithoutExtension(filename) + ".jpg";
//if (!image.IsAnimated) //if (!image.IsAnimated)
//{ //{
using (FileStream output = File.OpenWrite(encodedFilename)) using (FileStream output = File.OpenWrite(encodedFilename))
{ {
IImageEncoder encoder = Image.Encoders.First(e => e.IsSupportedFileExtension(".jpg")); IImageEncoder encoder = Image.Encoders.First(e => e.IsSupportedFileExtension(Path.GetExtension(filename)));
encoder.Encode(image, output); encoder.Encode(image, output);
} }
//} //}

BIN
tests/ImageProcessor.Tests/TestImages/Formats/Gif/a.gif

Binary file not shown.

After

Width:  |  Height:  |  Size: 69 B

Loading…
Cancel
Save