From 025896fc59ed8ba0eab0ce8cc15f963b2fa1dd7f Mon Sep 17 00:00:00 2001 From: dirk Date: Fri, 11 Nov 2016 22:12:56 +0100 Subject: [PATCH] Optimized the GIF decoder. --- src/ImageSharp/Formats/Gif/GifDecoderCore.cs | 215 +++++++++++-------- 1 file changed, 122 insertions(+), 93 deletions(-) diff --git a/src/ImageSharp/Formats/Gif/GifDecoderCore.cs b/src/ImageSharp/Formats/Gif/GifDecoderCore.cs index 44bb5553b..784d05da2 100644 --- a/src/ImageSharp/Formats/Gif/GifDecoderCore.cs +++ b/src/ImageSharp/Formats/Gif/GifDecoderCore.cs @@ -33,9 +33,14 @@ namespace ImageSharp.Formats private byte[] globalColorTable; /// - /// The current frame. + /// The next frame. /// - private TColor[] currentFrame; + private ImageBase nextFrame; + + /// + /// The area to restore. + /// + private Rectangle? restoreArea; /// /// The logical screen descriptor. @@ -285,141 +290,165 @@ namespace ImageSharp.Formats /// The indexed pixels. /// The color table containing the available colors. /// The - private void ReadFrameColors(byte[] indices, byte[] colorTable, GifImageDescriptor descriptor) + private unsafe void ReadFrameColors(byte[] indices, byte[] colorTable, GifImageDescriptor descriptor) { int imageWidth = this.logicalScreenDescriptor.Width; int imageHeight = this.logicalScreenDescriptor.Height; - if (this.currentFrame == null) + ImageBase previousFrame = null; + + ImageBase currentFrame; + + if (this.nextFrame == null) { - this.currentFrame = new TColor[imageWidth * imageHeight]; - } + currentFrame = this.decodedImage; - TColor[] lastFrame = null; + currentFrame.Quality = colorTable.Length / 3; - if (this.graphicsControlExtension != null && - this.graphicsControlExtension.DisposalMethod == DisposalMethod.RestoreToPrevious) + // This initializes the image to become fully transparent because the alpha channel is zero. + currentFrame.InitPixels(imageWidth, imageHeight); + } + else { - lastFrame = new TColor[imageWidth * imageHeight]; + if (this.graphicsControlExtension != null && + this.graphicsControlExtension.DisposalMethod == DisposalMethod.RestoreToPrevious) + { + previousFrame = this.nextFrame; + } + + ImageFrame frame = this.nextFrame.ToFrame(); + + currentFrame = frame; + + RestoreToBackground(currentFrame); - Array.Copy(this.currentFrame, lastFrame, lastFrame.Length); + this.decodedImage.Frames.Add(frame); + } + + if (this.graphicsControlExtension != null && this.graphicsControlExtension.DelayTime > 0) + { + currentFrame.FrameDelay = this.graphicsControlExtension.DelayTime; } - int offset, i = 0; + int i = 0; int interlacePass = 0; // The interlace pass int interlaceIncrement = 8; // The interlacing line increment int interlaceY = 0; // The current interlaced line - for (int y = descriptor.Top; y < descriptor.Top + descriptor.Height; y++) + using (PixelAccessor pixelAccessor = currentFrame.Lock()) { - // Check if this image is interlaced. - int writeY; // the target y offset to write to - if (descriptor.InterlaceFlag) + using (PixelRow pixelRow = new PixelRow(imageWidth, ComponentOrder.XYZW)) { - // 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) + for (int y = descriptor.Top; y < descriptor.Top + descriptor.Height; y++) { - interlacePass++; - switch (interlacePass) + // Check if this image is interlaced. + int writeY; // the target y offset to write to + if (descriptor.InterlaceFlag) { - case 1: - interlaceY = 4; - break; - case 2: - interlaceY = 2; - interlaceIncrement = 4; - break; - case 3: - interlaceY = 1; - interlaceIncrement = 2; - break; + // 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) + { + case 1: + interlaceY = 4; + break; + case 2: + interlaceY = 2; + interlaceIncrement = 4; + break; + case 3: + interlaceY = 1; + interlaceIncrement = 2; + break; + } + } + + writeY = interlaceY + descriptor.Top; + + interlaceY += interlaceIncrement; + } + else + { + writeY = y; } - } - - writeY = interlaceY + descriptor.Top; - interlaceY += interlaceIncrement; - } - else - { - writeY = y; - } + pixelRow.Reset(); - for (int x = descriptor.Left; x < descriptor.Left + descriptor.Width; x++) - { - offset = (writeY * imageWidth) + x; - int index = indices[i]; + byte* pixelBase = pixelRow.PixelBase; + for (int x = 0; x < descriptor.Width; x++) + { + int index = indices[i]; + + if (this.graphicsControlExtension == null || + this.graphicsControlExtension.TransparencyFlag == false || + this.graphicsControlExtension.TransparencyIndex != index) + { + int indexOffset = index * 3; + *(pixelBase + 0) = colorTable[indexOffset]; + *(pixelBase + 1) = colorTable[indexOffset + 1]; + *(pixelBase + 2) = colorTable[indexOffset + 2]; + *(pixelBase + 3) = 255; + } + + i++; + pixelBase += 4; + } - if (this.graphicsControlExtension == null || - this.graphicsControlExtension.TransparencyFlag == false || - this.graphicsControlExtension.TransparencyIndex != index) - { - // Stored in r-> g-> b-> a order. - int indexOffset = index * 3; - TColor pixel = default(TColor); - pixel.PackFromVector4(new Color(colorTable[indexOffset], colorTable[indexOffset + 1], colorTable[indexOffset + 2]).ToVector4()); - this.currentFrame[offset] = pixel; + pixelAccessor.CopyFrom(pixelRow, writeY, descriptor.Left); } - - i++; } } - TColor[] pixels = new TColor[imageWidth * imageHeight]; - - Array.Copy(this.currentFrame, pixels, pixels.Length); + if (previousFrame != null) + { + this.nextFrame = previousFrame; + return; + } - ImageBase currentImage; + this.nextFrame = currentFrame; - if (this.decodedImage.Pixels == null) + if (this.graphicsControlExtension != null && + this.graphicsControlExtension.DisposalMethod == DisposalMethod.RestoreToBackground) { - currentImage = this.decodedImage; - currentImage.SetPixels(imageWidth, imageHeight, pixels); - currentImage.Quality = colorTable.Length / 3; - - if (this.graphicsControlExtension != null && this.graphicsControlExtension.DelayTime > 0) - { - this.decodedImage.FrameDelay = this.graphicsControlExtension.DelayTime; - } + this.restoreArea = new Rectangle(descriptor.Left, descriptor.Top, descriptor.Width, descriptor.Height); } - else - { - ImageFrame frame = new ImageFrame(); + } - currentImage = frame; - currentImage.SetPixels(imageWidth, imageHeight, pixels); - currentImage.Quality = colorTable.Length / 3; + private void RestoreToBackground(ImageBase frame) + { + if (this.restoreArea == null) + { + return; + } - if (this.graphicsControlExtension != null && this.graphicsControlExtension.DelayTime > 0) + // Optimization for when the size of the frame is the same as the image size. + if (this.restoreArea.Value.Width == this.decodedImage.Width && + this.restoreArea.Value.Height == this.decodedImage.Height) + { + using (PixelAccessor pixelAccessor = frame.Lock()) { - currentImage.FrameDelay = this.graphicsControlExtension.DelayTime; + pixelAccessor.Reset(); } - - this.decodedImage.Frames.Add(frame); } - - if (this.graphicsControlExtension != null) + else { - if (this.graphicsControlExtension.DisposalMethod == DisposalMethod.RestoreToBackground) + using (PixelRow emptyRow = new PixelRow(this.restoreArea.Value.Width, ComponentOrder.XYZW)) { - for (int y = descriptor.Top; y < descriptor.Top + descriptor.Height; y++) + using (PixelAccessor pixelAccessor = frame.Lock()) { - for (int x = descriptor.Left; x < descriptor.Left + descriptor.Width; x++) + for (int y = this.restoreArea.Value.Top; y < this.restoreArea.Value.Top + this.restoreArea.Value.Height; y++) { - offset = (y * imageWidth) + x; - - // Stored in r-> g-> b-> a order. - this.currentFrame[offset] = default(TColor); + pixelAccessor.CopyFrom(emptyRow, y, this.restoreArea.Value.Left); } } } - else if (this.graphicsControlExtension.DisposalMethod == DisposalMethod.RestoreToPrevious) - { - this.currentFrame = lastFrame; - } } + + this.restoreArea = null; } } }