From 4cef65b78233ff4c0fd0453b13af49ca50426f07 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Thu, 25 May 2017 00:19:37 +1000 Subject: [PATCH] Read spec, fix gifs --- src/ImageSharp/Formats/Gif/GifDecoderCore.cs | 92 +++++++++---------- src/ImageSharp/Formats/Gif/GifEncoderCore.cs | 43 ++++----- .../Sections/GifGraphicsControlExtension.cs | 2 +- .../Quantizers/OctreeQuantizer{TPixel}.cs | 12 ++- 4 files changed, 71 insertions(+), 78 deletions(-) diff --git a/src/ImageSharp/Formats/Gif/GifDecoderCore.cs b/src/ImageSharp/Formats/Gif/GifDecoderCore.cs index 272e4d0750..618d268f70 100644 --- a/src/ImageSharp/Formats/Gif/GifDecoderCore.cs +++ b/src/ImageSharp/Formats/Gif/GifDecoderCore.cs @@ -191,7 +191,7 @@ namespace ImageSharp.Formats byte packed = this.buffer[8]; - GifImageDescriptor imageDescriptor = new GifImageDescriptor + var imageDescriptor = new GifImageDescriptor { Left = BitConverter.ToInt16(this.buffer, 0), Top = BitConverter.ToInt16(this.buffer, 2), @@ -337,7 +337,7 @@ namespace ImageSharp.Formats private void ReadFrameIndices(GifImageDescriptor imageDescriptor, byte[] indices) { int dataSize = this.currentStream.ReadByte(); - using (LzwDecoder lzwDecoder = new LzwDecoder(this.currentStream)) + using (var lzwDecoder = new LzwDecoder(this.currentStream)) { lzwDecoder.DecodePixels(imageDescriptor.Width, imageDescriptor.Height, dataSize, indices); } @@ -396,62 +396,60 @@ namespace ImageSharp.Formats int interlaceIncrement = 8; // The interlacing line increment int interlaceY = 0; // The current interlaced line - using (PixelAccessor pixelAccessor = image.Lock()) + for (int y = descriptor.Top; y < descriptor.Top + descriptor.Height; y++) { - for (int y = descriptor.Top; y < descriptor.Top + descriptor.Height; y++) + // Check if this image is interlaced. + int writeY; // the target y offset to write to + if (descriptor.InterlaceFlag) { - // Check if this image is interlaced. - int writeY; // the target y offset to write to - if (descriptor.InterlaceFlag) + // If so then we read lines at predetermined offsets. + // When an entire image height worth of offset lines has been read we consider this a pass. + // With each pass the number of offset lines changes and the starting line changes. + if (interlaceY >= descriptor.Height) { - // If so then we read lines at predetermined offsets. - // When an entire image height worth of offset lines has been read we consider this a pass. - // With each pass the number of offset lines changes and the starting line changes. - if (interlaceY >= descriptor.Height) + interlacePass++; + switch (interlacePass) { - interlacePass++; - switch (interlacePass) - { - case 1: - interlaceY = 4; - break; - case 2: - interlaceY = 2; - interlaceIncrement = 4; - break; - case 3: - interlaceY = 1; - interlaceIncrement = 2; - break; - } + case 1: + interlaceY = 4; + break; + case 2: + interlaceY = 2; + interlaceIncrement = 4; + break; + case 3: + interlaceY = 1; + interlaceIncrement = 2; + break; } + } - writeY = interlaceY + descriptor.Top; + writeY = interlaceY + descriptor.Top; - interlaceY += interlaceIncrement; - } - else - { - writeY = y; - } + interlaceY += interlaceIncrement; + } + else + { + writeY = y; + } - for (int x = descriptor.Left; x < descriptor.Left + descriptor.Width; x++) - { - int index = indices[i]; + Span rowSpan = image.GetRowSpan(writeY); - if (this.graphicsControlExtension == null || - this.graphicsControlExtension.TransparencyFlag == false || - this.graphicsControlExtension.TransparencyIndex != index) - { - int indexOffset = index * 3; + for (int x = descriptor.Left; x < descriptor.Left + descriptor.Width; x++) + { + int index = indices[i]; - TPixel pixel = default(TPixel); - pixel.PackFromBytes(colorTable[indexOffset], colorTable[indexOffset + 1], colorTable[indexOffset + 2], 255); - pixelAccessor[x, writeY] = pixel; - } + if (this.graphicsControlExtension == null || + this.graphicsControlExtension.TransparencyFlag == false || + this.graphicsControlExtension.TransparencyIndex != index) + { + int indexOffset = index * 3; - i++; + ref TPixel pixel = ref rowSpan[x]; + pixel.PackFromBytes(colorTable[indexOffset], colorTable[indexOffset + 1], colorTable[indexOffset + 2], 255); } + + i++; } } @@ -492,7 +490,7 @@ namespace ImageSharp.Formats } else { - using (PixelArea emptyRow = new PixelArea(this.restoreArea.Value.Width, ComponentOrder.Xyzw)) + using (var emptyRow = new PixelArea(this.restoreArea.Value.Width, ComponentOrder.Xyzw)) { using (PixelAccessor pixelAccessor = frame.Lock()) { diff --git a/src/ImageSharp/Formats/Gif/GifEncoderCore.cs b/src/ImageSharp/Formats/Gif/GifEncoderCore.cs index ad0ecc88fb..5ef7ca1658 100644 --- a/src/ImageSharp/Formats/Gif/GifEncoderCore.cs +++ b/src/ImageSharp/Formats/Gif/GifEncoderCore.cs @@ -137,31 +137,20 @@ namespace ImageSharp.Formats private int GetTransparentIndex(QuantizedImage quantized) where TPixel : struct, IPixel { - // Find the lowest alpha value and make it the transparent index. - int index = 255; - byte alpha = 255; - bool hasEmpty = false; - - // Some images may have more than one quantized pixel returned with an alpha value of zero - // so we should always ignore if we have empty pixels present. - for (int i = 0; i < quantized.Palette.Length; i++) + // Transparent pixels are much more likely to be found at the end of a palette + int index = -1; + for (int i = quantized.Palette.Length - 1; i >= 0; i--) { quantized.Palette[i].ToXyzwBytes(this.buffer, 0); - if (!hasEmpty) + if (this.buffer[3] > 0) { - if (this.buffer[0] == 0 && this.buffer[1] == 0 && this.buffer[2] == 0 && this.buffer[3] == 0) - { - alpha = this.buffer[3]; - index = i; - hasEmpty = true; - } - - if (this.buffer[3] < alpha) - { - alpha = this.buffer[3]; - index = i; - } + continue; + } + else + { + index = i; + break; } } @@ -183,8 +172,8 @@ namespace ImageSharp.Formats /// The pixel format. /// The image to encode. /// The writer to write to the stream with. - /// The transparency index to set the default background index to. - private void WriteLogicalScreenDescriptor(Image image, EndianBinaryWriter writer, int tranparencyIndex) + /// The transparency index to set the default background index to. + private void WriteLogicalScreenDescriptor(Image image, EndianBinaryWriter writer, int transparencyIndex) where TPixel : struct, IPixel { var descriptor = new GifLogicalScreenDescriptor @@ -193,7 +182,7 @@ namespace ImageSharp.Formats Height = (short)image.Height, GlobalColorTableFlag = false, // TODO: Always false for now. GlobalColorTableSize = this.bitDepth - 1, - BackgroundColorIndex = (byte)tranparencyIndex + BackgroundColorIndex = unchecked((byte)transparencyIndex) }; writer.Write((ushort)descriptor.Width); @@ -286,8 +275,8 @@ namespace ImageSharp.Formats var extension = new GifGraphicsControlExtension { DisposalMethod = metaData.DisposalMethod, - TransparencyFlag = true, // TODO: The spec here is unclear. Can we get away with this? - TransparencyIndex = transparencyIndex, + TransparencyFlag = transparencyIndex > -1, + TransparencyIndex = unchecked((byte)transparencyIndex), DelayTime = metaData.FrameDelay }; @@ -306,7 +295,7 @@ namespace ImageSharp.Formats writer.Write(field.Byte); writer.Write((ushort)extension.DelayTime); - writer.Write((byte)extension.TransparencyIndex); + writer.Write(extension.TransparencyIndex); writer.Write(GifConstants.Terminator); } diff --git a/src/ImageSharp/Formats/Gif/Sections/GifGraphicsControlExtension.cs b/src/ImageSharp/Formats/Gif/Sections/GifGraphicsControlExtension.cs index 79d98f5fbf..503bd4fdf7 100644 --- a/src/ImageSharp/Formats/Gif/Sections/GifGraphicsControlExtension.cs +++ b/src/ImageSharp/Formats/Gif/Sections/GifGraphicsControlExtension.cs @@ -29,7 +29,7 @@ namespace ImageSharp.Formats /// The Transparency Index is such that when encountered, the corresponding pixel /// of the display device is not modified and processing goes on to the next pixel. /// - public int TransparencyIndex { get; set; } + public byte TransparencyIndex { get; set; } /// /// Gets or sets the delay time. diff --git a/src/ImageSharp/Quantizers/OctreeQuantizer{TPixel}.cs b/src/ImageSharp/Quantizers/OctreeQuantizer{TPixel}.cs index bd963c4527..e19df4cfaa 100644 --- a/src/ImageSharp/Quantizers/OctreeQuantizer{TPixel}.cs +++ b/src/ImageSharp/Quantizers/OctreeQuantizer{TPixel}.cs @@ -36,7 +36,7 @@ namespace ImageSharp.Quantizers /// /// Maximum allowed color depth /// - private int colors; + private byte colors; /// /// The reduced image palette @@ -58,7 +58,7 @@ namespace ImageSharp.Quantizers /// public override QuantizedImage Quantize(ImageBase image, int maxColors) { - this.colors = maxColors.Clamp(1, 255); + this.colors = (byte)maxColors.Clamp(1, 255); this.octree = new Octree(this.GetBitsNeededForColorDepth(this.colors)); this.palette = null; @@ -123,7 +123,7 @@ namespace ImageSharp.Quantizers /// protected override TPixel[] GetPalette() { - return this.palette ?? (this.palette = this.octree.Palletize(Math.Max(this.colors, 1))); + return this.palette ?? (this.palette = this.octree.Palletize(Math.Max(this.colors, (byte)1))); } /// @@ -143,6 +143,12 @@ namespace ImageSharp.Quantizers return this.GetClosestPixel(pixel, this.palette, this.colorMap); } + pixel.ToXyzwBytes(this.pixelBuffer, 0); + if (this.pixelBuffer[3] == 0) + { + return this.colors; + } + return (byte)this.octree.GetPaletteIndex(pixel, this.pixelBuffer); }