diff --git a/src/ImageSharp/Formats/Tga/TgaDecoderCore.cs b/src/ImageSharp/Formats/Tga/TgaDecoderCore.cs index cca34c9b0..270dfb22b 100644 --- a/src/ImageSharp/Formats/Tga/TgaDecoderCore.cs +++ b/src/ImageSharp/Formats/Tga/TgaDecoderCore.cs @@ -17,6 +17,11 @@ namespace SixLabors.ImageSharp.Formats.Tga /// internal sealed class TgaDecoderCore { + /// + /// A scratch buffer to reduce allocations. + /// + private readonly byte[] buffer = new byte[4]; + /// /// The metadata. /// @@ -88,7 +93,7 @@ namespace SixLabors.ImageSharp.Formats.Tga { try { - bool inverted = this.ReadFileHeader(stream); + TgaImageOrigin origin = this.ReadFileHeader(stream); this.currentStream.Skip(this.fileHeader.IdLength); // Parse the color map, if present. @@ -131,7 +136,7 @@ namespace SixLabors.ImageSharp.Formats.Tga pixels, palette.Array, colorMapPixelSizeInBytes, - inverted); + origin); } else { @@ -141,7 +146,7 @@ namespace SixLabors.ImageSharp.Formats.Tga pixels, palette.Array, colorMapPixelSizeInBytes, - inverted); + origin); } } @@ -160,11 +165,11 @@ namespace SixLabors.ImageSharp.Formats.Tga case 8: if (this.fileHeader.ImageType.IsRunLengthEncoded()) { - this.ReadRle(this.fileHeader.Width, this.fileHeader.Height, pixels, 1, inverted); + this.ReadRle(this.fileHeader.Width, this.fileHeader.Height, pixels, 1, origin); } else { - this.ReadMonoChrome(this.fileHeader.Width, this.fileHeader.Height, pixels, inverted); + this.ReadMonoChrome(this.fileHeader.Width, this.fileHeader.Height, pixels, origin); } break; @@ -173,11 +178,11 @@ namespace SixLabors.ImageSharp.Formats.Tga case 16: if (this.fileHeader.ImageType.IsRunLengthEncoded()) { - this.ReadRle(this.fileHeader.Width, this.fileHeader.Height, pixels, 2, inverted); + this.ReadRle(this.fileHeader.Width, this.fileHeader.Height, pixels, 2, origin); } else { - this.ReadBgra16(this.fileHeader.Width, this.fileHeader.Height, pixels, inverted); + this.ReadBgra16(this.fileHeader.Width, this.fileHeader.Height, pixels, origin); } break; @@ -185,11 +190,11 @@ namespace SixLabors.ImageSharp.Formats.Tga case 24: if (this.fileHeader.ImageType.IsRunLengthEncoded()) { - this.ReadRle(this.fileHeader.Width, this.fileHeader.Height, pixels, 3, inverted); + this.ReadRle(this.fileHeader.Width, this.fileHeader.Height, pixels, 3, origin); } else { - this.ReadBgr24(this.fileHeader.Width, this.fileHeader.Height, pixels, inverted); + this.ReadBgr24(this.fileHeader.Width, this.fileHeader.Height, pixels, origin); } break; @@ -197,11 +202,11 @@ namespace SixLabors.ImageSharp.Formats.Tga case 32: if (this.fileHeader.ImageType.IsRunLengthEncoded()) { - this.ReadRle(this.fileHeader.Width, this.fileHeader.Height, pixels, 4, inverted); + this.ReadRle(this.fileHeader.Width, this.fileHeader.Height, pixels, 4, origin); } else { - this.ReadBgra32(this.fileHeader.Width, this.fileHeader.Height, pixels, inverted); + this.ReadBgra32(this.fileHeader.Width, this.fileHeader.Height, pixels, origin); } break; @@ -228,8 +233,8 @@ namespace SixLabors.ImageSharp.Formats.Tga /// The to assign the palette to. /// The color palette. /// Color map size of one entry in bytes. - /// Indicates, if the origin of the image is top left rather the bottom left (the default). - private void ReadPaletted(int width, int height, Buffer2D pixels, byte[] palette, int colorMapPixelSizeInBytes, bool inverted) + /// The image origin. + private void ReadPaletted(int width, int height, Buffer2D pixels, byte[] palette, int colorMapPixelSizeInBytes, TgaImageOrigin origin) where TPixel : unmanaged, IPixel { using (IManagedByteBuffer row = this.memoryAllocator.AllocateManagedByteBuffer(width, AllocationOptions.Clean)) @@ -240,7 +245,7 @@ namespace SixLabors.ImageSharp.Formats.Tga for (int y = 0; y < height; y++) { this.currentStream.Read(row); - int newY = Invert(y, height, inverted); + int newY = InvertY(y, height, origin); Span pixelRow = pixels.GetRowSpan(newY); switch (colorMapPixelSizeInBytes) { @@ -257,7 +262,8 @@ namespace SixLabors.ImageSharp.Formats.Tga } color.FromBgra5551(bgra); - pixelRow[x] = color; + int newX = InvertX(x, width, origin); + pixelRow[newX] = color; } break; @@ -267,7 +273,8 @@ namespace SixLabors.ImageSharp.Formats.Tga { int colorIndex = rowSpan[x]; color.FromBgr24(Unsafe.As(ref palette[colorIndex * colorMapPixelSizeInBytes])); - pixelRow[x] = color; + int newX = InvertX(x, width, origin); + pixelRow[newX] = color; } break; @@ -277,7 +284,8 @@ namespace SixLabors.ImageSharp.Formats.Tga { int colorIndex = rowSpan[x]; color.FromBgra32(Unsafe.As(ref palette[colorIndex * colorMapPixelSizeInBytes])); - pixelRow[x] = color; + int newX = InvertX(x, width, origin); + pixelRow[newX] = color; } break; @@ -295,8 +303,8 @@ namespace SixLabors.ImageSharp.Formats.Tga /// The to assign the palette to. /// The color palette. /// Color map size of one entry in bytes. - /// Indicates, if the origin of the image is top left rather the bottom left (the default). - private void ReadPalettedRle(int width, int height, Buffer2D pixels, byte[] palette, int colorMapPixelSizeInBytes, bool inverted) + /// The image origin. + private void ReadPalettedRle(int width, int height, Buffer2D pixels, byte[] palette, int colorMapPixelSizeInBytes, TgaImageOrigin origin) where TPixel : unmanaged, IPixel { int bytesPerPixel = 1; @@ -309,7 +317,7 @@ namespace SixLabors.ImageSharp.Formats.Tga for (int y = 0; y < height; y++) { - int newY = Invert(y, height, inverted); + int newY = InvertY(y, height, origin); Span pixelRow = pixels.GetRowSpan(newY); int rowStartIdx = y * width * bytesPerPixel; for (int x = 0; x < width; x++) @@ -348,7 +356,8 @@ namespace SixLabors.ImageSharp.Formats.Tga break; } - pixelRow[x] = color; + int newX = InvertX(x, width, origin); + pixelRow[newX] = color; } } } @@ -361,16 +370,36 @@ namespace SixLabors.ImageSharp.Formats.Tga /// The width of the image. /// The height of the image. /// The to assign the palette to. - /// Indicates, if the origin of the image is top left rather the bottom left (the default). - private void ReadMonoChrome(int width, int height, Buffer2D pixels, bool inverted) + /// the image origin. + private void ReadMonoChrome(int width, int height, Buffer2D pixels, TgaImageOrigin origin) where TPixel : unmanaged, IPixel { + bool isXInverted = origin == TgaImageOrigin.BottomRight || origin == TgaImageOrigin.TopRight; + if (isXInverted) + { + TPixel color = default; + for (int y = 0; y < height; y++) + { + int newY = InvertY(y, height, origin); + Span pixelSpan = pixels.GetRowSpan(newY); + for (int x = 0; x < width; x++) + { + var pixelValue = (byte)this.currentStream.ReadByte(); + color.FromL8(Unsafe.As(ref pixelValue)); + int newX = InvertX(x, width, origin); + pixelSpan[newX] = color; + } + } + + return; + } + using (IManagedByteBuffer row = this.memoryAllocator.AllocatePaddedPixelRowBuffer(width, 1, 0)) { for (int y = 0; y < height; y++) { this.currentStream.Read(row); - int newY = Invert(y, height, inverted); + int newY = InvertY(y, height, origin); Span pixelSpan = pixels.GetRowSpan(newY); PixelOperations.Instance.FromL8Bytes(this.configuration, row.GetSpan(), pixelSpan, width); } @@ -384,29 +413,50 @@ namespace SixLabors.ImageSharp.Formats.Tga /// The width of the image. /// The height of the image. /// The to assign the palette to. - /// Indicates, if the origin of the image is top left rather the bottom left (the default). - private void ReadBgra16(int width, int height, Buffer2D pixels, bool inverted) + /// The image origin. + private void ReadBgra16(int width, int height, Buffer2D pixels, TgaImageOrigin origin) where TPixel : unmanaged, IPixel { + TPixel color = default; + bool isXInverted = origin == TgaImageOrigin.BottomRight || origin == TgaImageOrigin.TopRight; using (IManagedByteBuffer row = this.memoryAllocator.AllocatePaddedPixelRowBuffer(width, 2, 0)) { for (int y = 0; y < height; y++) { - this.currentStream.Read(row); - Span rowSpan = row.GetSpan(); + int newY = InvertY(y, height, origin); + Span pixelSpan = pixels.GetRowSpan(newY); - if (!this.hasAlpha) + if (isXInverted) { - // We need to set the alpha component value to fully opaque. - for (int x = 1; x < rowSpan.Length; x += 2) + for (int x = 0; x < width; x++) { - rowSpan[x] = (byte)(rowSpan[x] | (1 << 7)); + this.currentStream.Read(this.buffer, 0, 2); + if (!this.hasAlpha) + { + this.buffer[1] |= 1 << 7; + } + + color.FromBgra5551(Unsafe.As(ref this.buffer[0])); + int newX = InvertX(x, width, origin); + pixelSpan[newX] = color; } } + else + { + this.currentStream.Read(row); + Span rowSpan = row.GetSpan(); - int newY = Invert(y, height, inverted); - Span pixelSpan = pixels.GetRowSpan(newY); - PixelOperations.Instance.FromBgra5551Bytes(this.configuration, rowSpan, pixelSpan, width); + if (!this.hasAlpha) + { + // We need to set the alpha component value to fully opaque. + for (int x = 1; x < rowSpan.Length; x += 2) + { + rowSpan[x] |= (1 << 7); + } + } + + PixelOperations.Instance.FromBgra5551Bytes(this.configuration, rowSpan, pixelSpan, width); + } } } } @@ -418,16 +468,36 @@ namespace SixLabors.ImageSharp.Formats.Tga /// The width of the image. /// The height of the image. /// The to assign the palette to. - /// Indicates, if the origin of the image is top left rather the bottom left (the default). - private void ReadBgr24(int width, int height, Buffer2D pixels, bool inverted) + /// The image origin. + private void ReadBgr24(int width, int height, Buffer2D pixels, TgaImageOrigin origin) where TPixel : unmanaged, IPixel { + bool isXInverted = origin == TgaImageOrigin.BottomRight || origin == TgaImageOrigin.TopRight; + if (isXInverted) + { + TPixel color = default; + for (int y = 0; y < height; y++) + { + int newY = InvertY(y, height, origin); + Span pixelSpan = pixels.GetRowSpan(newY); + for (int x = 0; x < width; x++) + { + this.currentStream.Read(this.buffer, 0, 3); + color.FromBgr24(Unsafe.As(ref this.buffer[0])); + int newX = InvertX(x, width, origin); + pixelSpan[newX] = color; + } + } + + return; + } + using (IManagedByteBuffer row = this.memoryAllocator.AllocatePaddedPixelRowBuffer(width, 3, 0)) { for (int y = 0; y < height; y++) { this.currentStream.Read(row); - int newY = Invert(y, height, inverted); + int newY = InvertY(y, height, origin); Span pixelSpan = pixels.GetRowSpan(newY); PixelOperations.Instance.FromBgr24Bytes(this.configuration, row.GetSpan(), pixelSpan, width); } @@ -441,18 +511,38 @@ namespace SixLabors.ImageSharp.Formats.Tga /// The width of the image. /// The height of the image. /// The to assign the palette to. - /// Indicates, if the origin of the image is top left rather the bottom left (the default). - private void ReadBgra32(int width, int height, Buffer2D pixels, bool inverted) + /// The image origin. + private void ReadBgra32(int width, int height, Buffer2D pixels, TgaImageOrigin origin) where TPixel : unmanaged, IPixel { + TPixel color = default; if (this.tgaMetadata.AlphaChannelBits == 8) { + bool isXInverted = origin == TgaImageOrigin.BottomRight || origin == TgaImageOrigin.TopRight; + if (isXInverted) + { + for (int y = 0; y < height; y++) + { + int newY = InvertY(y, height, origin); + Span pixelSpan = pixels.GetRowSpan(newY); + for (int x = 0; x < width; x++) + { + this.currentStream.Read(this.buffer, 0, 4); + color.FromBgra32(Unsafe.As(ref this.buffer[0])); + int newX = InvertX(x, width, origin); + pixelSpan[newX] = color; + } + } + + return; + } + using (IManagedByteBuffer row = this.memoryAllocator.AllocatePaddedPixelRowBuffer(width, 4, 0)) { for (int y = 0; y < height; y++) { this.currentStream.Read(row); - int newY = Invert(y, height, inverted); + int newY = InvertY(y, height, origin); Span pixelSpan = pixels.GetRowSpan(newY); PixelOperations.Instance.FromBgra32Bytes(this.configuration, row.GetSpan(), pixelSpan, width); @@ -462,14 +552,13 @@ namespace SixLabors.ImageSharp.Formats.Tga return; } - TPixel color = default; var alphaBits = this.tgaMetadata.AlphaChannelBits; using (IManagedByteBuffer row = this.memoryAllocator.AllocatePaddedPixelRowBuffer(width, 4, 0)) { for (int y = 0; y < height; y++) { this.currentStream.Read(row); - int newY = Invert(y, height, inverted); + int newY = InvertY(y, height, origin); Span pixelRow = pixels.GetRowSpan(newY); Span rowSpan = row.GetSpan(); @@ -478,7 +567,8 @@ namespace SixLabors.ImageSharp.Formats.Tga int idx = x * 4; var alpha = alphaBits == 0 ? byte.MaxValue : rowSpan[idx + 3]; color.FromBgra32(new Bgra32(rowSpan[idx + 2], rowSpan[idx + 1], rowSpan[idx], (byte)alpha)); - pixelRow[x] = color; + int newX = InvertX(x, width, origin); + pixelRow[newX] = color; } } } @@ -492,8 +582,8 @@ namespace SixLabors.ImageSharp.Formats.Tga /// The height of the image. /// The to assign the palette to. /// The bytes per pixel. - /// Indicates, if the origin of the image is top left rather the bottom left (the default). - private void ReadRle(int width, int height, Buffer2D pixels, int bytesPerPixel, bool inverted) + /// The image origin. + private void ReadRle(int width, int height, Buffer2D pixels, int bytesPerPixel, TgaImageOrigin origin) where TPixel : unmanaged, IPixel { TPixel color = default; @@ -504,7 +594,7 @@ namespace SixLabors.ImageSharp.Formats.Tga this.UncompressRle(width, height, bufferSpan, bytesPerPixel); for (int y = 0; y < height; y++) { - int newY = Invert(y, height, inverted); + int newY = InvertY(y, height, origin); Span pixelRow = pixels.GetRowSpan(newY); int rowStartIdx = y * width * bytesPerPixel; for (int x = 0; x < width; x++) @@ -541,7 +631,8 @@ namespace SixLabors.ImageSharp.Formats.Tga break; } - pixelRow[x] = color; + int newX = InvertX(x, width, origin); + pixelRow[newX] = color; } } } @@ -609,18 +700,48 @@ namespace SixLabors.ImageSharp.Formats.Tga /// Returns the y- value based on the given height. /// /// The y- value representing the current row. - /// The height of the bitmap. - /// Whether the bitmap is inverted. + /// The height of the image. + /// The image origin. + /// The representing the inverted value. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static int InvertY(int y, int height, TgaImageOrigin origin) + { + switch (origin) + { + case TgaImageOrigin.BottomLeft: + case TgaImageOrigin.BottomRight: + return height - y - 1; + default: + return y; + } + } + + /// + /// Returns the x- value based on the given width. + /// + /// The x- value representing the current column. + /// The width of the image. + /// The image origin. /// The representing the inverted value. [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static int Invert(int y, int height, bool inverted) => (!inverted) ? height - y - 1 : y; + private static int InvertX(int x, int width, TgaImageOrigin origin) + { + switch (origin) + { + case TgaImageOrigin.TopRight: + case TgaImageOrigin.BottomRight: + return width - x - 1; + default: + return x; + } + } /// /// Reads the tga file header from the stream. /// /// The containing image data. - /// true, if the image origin is top left. - private bool ReadFileHeader(Stream stream) + /// The image origin. + private TgaImageOrigin ReadFileHeader(Stream stream) { this.currentStream = stream; @@ -641,15 +762,9 @@ namespace SixLabors.ImageSharp.Formats.Tga this.tgaMetadata.AlphaChannelBits = (byte)alphaBits; this.hasAlpha = alphaBits > 0; - // TODO: bits 4 and 5 describe the image origin. See spec page 9. bit 4 is currently ignored. - // Theoretically the origin could also be top right and bottom right. - // Bit at position 5 of the descriptor indicates, that the origin is top left instead of bottom left. - if ((this.fileHeader.ImageDescriptor & (1 << 5)) != 0) - { - return true; - } - - return false; + // Bits 4 and 5 describe the image origin. + var origin = (TgaImageOrigin)((this.fileHeader.ImageDescriptor & 0x30) >> 4); + return origin; } } } diff --git a/src/ImageSharp/Formats/Tga/TgaImageOrigin.cs b/src/ImageSharp/Formats/Tga/TgaImageOrigin.cs new file mode 100644 index 000000000..06d7b5945 --- /dev/null +++ b/src/ImageSharp/Formats/Tga/TgaImageOrigin.cs @@ -0,0 +1,28 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Formats.Tga +{ + internal enum TgaImageOrigin + { + /// + /// Bottom left origin. + /// + BottomLeft = 0, + + /// + /// Bottom right origin. + /// + BottomRight = 1, + + /// + /// Top left origin. + /// + TopLeft = 2, + + /// + /// Top right origin. + /// + TopRight = 3, + } +}