private static int InvertX(int x, int width, TgaImageOrigin origin) { if (InvertX(origin)) { return(width - x - 1); } return(x); }
private static int InvertY(int y, int height, TgaImageOrigin origin) { if (InvertY(origin)) { return(height - y - 1); } return(y); }
private static bool InvertX(TgaImageOrigin origin) { switch (origin) { case TgaImageOrigin.TopRight: case TgaImageOrigin.BottomRight: return(true); default: return(false); } }
/// <summary> /// Decodes the image from the specified stream. /// </summary> /// <typeparam name="TPixel">The pixel format.</typeparam> /// <param name="stream">The stream, where the image should be decoded from. Cannot be null.</param> /// <exception cref="System.ArgumentNullException"> /// <para><paramref name="stream"/> is null.</para> /// </exception> /// <returns>The decoded image.</returns> public Image <TPixel> Decode <TPixel>(Stream stream) where TPixel : unmanaged, IPixel <TPixel> { try { TgaImageOrigin origin = this.ReadFileHeader(stream); this.currentStream.Skip(this.fileHeader.IdLength); // Parse the color map, if present. if (this.fileHeader.ColorMapType != 0 && this.fileHeader.ColorMapType != 1) { TgaThrowHelper.ThrowNotSupportedException($"Unknown tga colormap type {this.fileHeader.ColorMapType} found"); } if (this.fileHeader.Width == 0 || this.fileHeader.Height == 0) { throw new UnknownImageFormatException("Width or height cannot be 0"); } var image = Image.CreateUninitialized <TPixel>(this.configuration, this.fileHeader.Width, this.fileHeader.Height, this.metadata); Buffer2D <TPixel> pixels = image.GetRootFramePixelBuffer(); if (this.fileHeader.ColorMapType == 1) { if (this.fileHeader.CMapLength <= 0) { TgaThrowHelper.ThrowImageFormatException("Missing tga color map length"); } if (this.fileHeader.CMapDepth <= 0) { TgaThrowHelper.ThrowImageFormatException("Missing tga color map depth"); } int colorMapPixelSizeInBytes = this.fileHeader.CMapDepth / 8; int colorMapSizeInBytes = this.fileHeader.CMapLength * colorMapPixelSizeInBytes; using (IManagedByteBuffer palette = this.memoryAllocator.AllocateManagedByteBuffer(colorMapSizeInBytes, AllocationOptions.Clean)) { this.currentStream.Read(palette.Array, this.fileHeader.CMapStart, colorMapSizeInBytes); if (this.fileHeader.ImageType == TgaImageType.RleColorMapped) { this.ReadPalettedRle( this.fileHeader.Width, this.fileHeader.Height, pixels, palette.Array, colorMapPixelSizeInBytes, origin); } else { this.ReadPaletted( this.fileHeader.Width, this.fileHeader.Height, pixels, palette.Array, colorMapPixelSizeInBytes, origin); } } return(image); } // Even if the image type indicates it is not a paletted image, it can still contain a palette. Skip those bytes. if (this.fileHeader.CMapLength > 0) { int colorMapPixelSizeInBytes = this.fileHeader.CMapDepth / 8; this.currentStream.Skip(this.fileHeader.CMapLength * colorMapPixelSizeInBytes); } switch (this.fileHeader.PixelDepth) { case 8: if (this.fileHeader.ImageType.IsRunLengthEncoded()) { this.ReadRle(this.fileHeader.Width, this.fileHeader.Height, pixels, 1, origin); } else { this.ReadMonoChrome(this.fileHeader.Width, this.fileHeader.Height, pixels, origin); } break; case 15: case 16: if (this.fileHeader.ImageType.IsRunLengthEncoded()) { this.ReadRle(this.fileHeader.Width, this.fileHeader.Height, pixels, 2, origin); } else { this.ReadBgra16(this.fileHeader.Width, this.fileHeader.Height, pixels, origin); } break; case 24: if (this.fileHeader.ImageType.IsRunLengthEncoded()) { this.ReadRle(this.fileHeader.Width, this.fileHeader.Height, pixels, 3, origin); } else { this.ReadBgr24(this.fileHeader.Width, this.fileHeader.Height, pixels, origin); } break; case 32: if (this.fileHeader.ImageType.IsRunLengthEncoded()) { this.ReadRle(this.fileHeader.Width, this.fileHeader.Height, pixels, 4, origin); } else { this.ReadBgra32(this.fileHeader.Width, this.fileHeader.Height, pixels, origin); } break; default: TgaThrowHelper.ThrowNotSupportedException("ImageSharp does not support this kind of tga files."); break; } return(image); } catch (IndexOutOfRangeException e) { throw new ImageFormatException("TGA image does not have a valid format.", e); } }
/// <summary> /// Reads a run length encoded TGA image. /// </summary> /// <typeparam name="TPixel">The pixel type.</typeparam> /// <param name="width">The width of the image.</param> /// <param name="height">The height of the image.</param> /// <param name="pixels">The <see cref="Buffer2D{TPixel}"/> to assign the palette to.</param> /// <param name="bytesPerPixel">The bytes per pixel.</param> /// <param name="origin">The image origin.</param> private void ReadRle <TPixel>(int width, int height, Buffer2D <TPixel> pixels, int bytesPerPixel, TgaImageOrigin origin) where TPixel : unmanaged, IPixel <TPixel> { TPixel color = default; var alphaBits = this.tgaMetadata.AlphaChannelBits; using (IMemoryOwner <byte> buffer = this.memoryAllocator.Allocate <byte>(width * height * bytesPerPixel, AllocationOptions.Clean)) { Span <byte> bufferSpan = buffer.GetSpan(); this.UncompressRle(width, height, bufferSpan, bytesPerPixel); for (int y = 0; y < height; y++) { int newY = InvertY(y, height, origin); Span <TPixel> pixelRow = pixels.GetRowSpan(newY); int rowStartIdx = y * width * bytesPerPixel; for (int x = 0; x < width; x++) { int idx = rowStartIdx + (x * bytesPerPixel); switch (bytesPerPixel) { case 1: color.FromL8(Unsafe.As <byte, L8>(ref bufferSpan[idx])); break; case 2: if (!this.hasAlpha) { // Set alpha value to 1, to treat it as opaque for Bgra5551. bufferSpan[idx + 1] = (byte)(bufferSpan[idx + 1] | 128); } if (this.fileHeader.ImageType == TgaImageType.RleBlackAndWhite) { color.FromLa16(Unsafe.As <byte, La16>(ref bufferSpan[idx])); } else { color.FromBgra5551(Unsafe.As <byte, Bgra5551>(ref bufferSpan[idx])); } break; case 3: color.FromBgr24(Unsafe.As <byte, Bgr24>(ref bufferSpan[idx])); break; case 4: if (this.hasAlpha) { color.FromBgra32(Unsafe.As <byte, Bgra32>(ref bufferSpan[idx])); } else { var alpha = alphaBits == 0 ? byte.MaxValue : bufferSpan[idx + 3]; color.FromBgra32(new Bgra32(bufferSpan[idx + 2], bufferSpan[idx + 1], bufferSpan[idx], (byte)alpha)); } break; } int newX = InvertX(x, width, origin); pixelRow[newX] = color; } } } }
/// <summary> /// Reads a uncompressed TGA image where each pixels has 24 bit. /// </summary> /// <typeparam name="TPixel">The pixel type.</typeparam> /// <param name="width">The width of the image.</param> /// <param name="height">The height of the image.</param> /// <param name="pixels">The <see cref="Buffer2D{TPixel}"/> to assign the palette to.</param> /// <param name="origin">The image origin.</param> private void ReadBgr24 <TPixel>(int width, int height, Buffer2D <TPixel> pixels, TgaImageOrigin origin) where TPixel : unmanaged, IPixel <TPixel> { bool invertX = InvertX(origin); if (invertX) { TPixel color = default; for (int y = 0; y < height; y++) { int newY = InvertY(y, height, origin); Span <TPixel> pixelSpan = pixels.GetRowSpan(newY); for (int x = width - 1; x >= 0; x--) { this.ReadBgr24Pixel(color, x, pixelSpan); } } return; } using (IManagedByteBuffer row = this.memoryAllocator.AllocatePaddedPixelRowBuffer(width, 3, 0)) { bool invertY = InvertY(origin); if (invertY) { for (int y = height - 1; y >= 0; y--) { this.ReadBgr24Row(width, pixels, row, y); } } else { for (int y = 0; y < height; y++) { this.ReadBgr24Row(width, pixels, row, y); } } } }
/// <summary> /// Reads a uncompressed TGA image where each pixels has 32 bit. /// </summary> /// <typeparam name="TPixel">The pixel type.</typeparam> /// <param name="width">The width of the image.</param> /// <param name="height">The height of the image.</param> /// <param name="pixels">The <see cref="Buffer2D{TPixel}"/> to assign the palette to.</param> /// <param name="origin">The image origin.</param> private void ReadBgra32 <TPixel>(int width, int height, Buffer2D <TPixel> pixels, TgaImageOrigin origin) where TPixel : unmanaged, IPixel <TPixel> { TPixel color = default; bool invertX = InvertX(origin); if (this.tgaMetadata.AlphaChannelBits == 8 && !invertX) { using (IManagedByteBuffer row = this.memoryAllocator.AllocatePaddedPixelRowBuffer(width, 4, 0)) { if (InvertY(origin)) { for (int y = height - 1; y >= 0; y--) { this.ReadBgra32Row(width, pixels, row, y); } } else { for (int y = 0; y < height; y++) { this.ReadBgra32Row(width, pixels, row, y); } } } return; } for (int y = 0; y < height; y++) { int newY = InvertY(y, height, origin); Span <TPixel> pixelRow = pixels.GetRowSpan(newY); if (invertX) { for (int x = width - 1; x >= 0; x--) { this.ReadBgra32Pixel(x, color, pixelRow); } } else { for (int x = 0; x < width; x++) { this.ReadBgra32Pixel(x, color, pixelRow); } } } }
/// <summary> /// Reads a uncompressed TGA image where each pixels has 16 bit. /// </summary> /// <typeparam name="TPixel">The pixel type.</typeparam> /// <param name="width">The width of the image.</param> /// <param name="height">The height of the image.</param> /// <param name="pixels">The <see cref="Buffer2D{TPixel}"/> to assign the palette to.</param> /// <param name="origin">The image origin.</param> private void ReadBgra16 <TPixel>(int width, int height, Buffer2D <TPixel> pixels, TgaImageOrigin origin) where TPixel : unmanaged, IPixel <TPixel> { TPixel color = default; bool invertX = InvertX(origin); using (IManagedByteBuffer row = this.memoryAllocator.AllocatePaddedPixelRowBuffer(width, 2, 0)) { for (int y = 0; y < height; y++) { int newY = InvertY(y, height, origin); Span <TPixel> pixelSpan = pixels.GetRowSpan(newY); if (invertX) { for (int x = width - 1; x >= 0; x--) { this.currentStream.Read(this.scratchBuffer, 0, 2); if (!this.hasAlpha) { this.scratchBuffer[1] |= 1 << 7; } if (this.fileHeader.ImageType == TgaImageType.BlackAndWhite) { color.FromLa16(Unsafe.As <byte, La16>(ref this.scratchBuffer[0])); } else { color.FromBgra5551(Unsafe.As <byte, Bgra5551>(ref this.scratchBuffer[0])); } pixelSpan[x] = color; } } else { this.currentStream.Read(row); Span <byte> rowSpan = row.GetSpan(); 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; } } if (this.fileHeader.ImageType == TgaImageType.BlackAndWhite) { PixelOperations <TPixel> .Instance.FromLa16Bytes(this.configuration, rowSpan, pixelSpan, width); } else { PixelOperations <TPixel> .Instance.FromBgra5551Bytes(this.configuration, rowSpan, pixelSpan, width); } } } } }
/// <summary> /// Reads a run length encoded TGA image with a palette. /// </summary> /// <typeparam name="TPixel">The pixel type.</typeparam> /// <param name="width">The width of the image.</param> /// <param name="height">The height of the image.</param> /// <param name="pixels">The <see cref="Buffer2D{TPixel}"/> to assign the palette to.</param> /// <param name="palette">The color palette.</param> /// <param name="colorMapPixelSizeInBytes">Color map size of one entry in bytes.</param> /// <param name="origin">The image origin.</param> private void ReadPalettedRle <TPixel>(int width, int height, Buffer2D <TPixel> pixels, byte[] palette, int colorMapPixelSizeInBytes, TgaImageOrigin origin) where TPixel : unmanaged, IPixel <TPixel> { int bytesPerPixel = 1; using (IMemoryOwner <byte> buffer = this.memoryAllocator.Allocate <byte>(width * height * bytesPerPixel, AllocationOptions.Clean)) { TPixel color = default; Span <byte> bufferSpan = buffer.GetSpan(); this.UncompressRle(width, height, bufferSpan, bytesPerPixel: 1); for (int y = 0; y < height; y++) { int newY = InvertY(y, height, origin); Span <TPixel> pixelRow = pixels.GetRowSpan(newY); int rowStartIdx = y * width * bytesPerPixel; for (int x = 0; x < width; x++) { int idx = rowStartIdx + x; switch (colorMapPixelSizeInBytes) { case 1: color.FromL8(Unsafe.As <byte, L8>(ref palette[bufferSpan[idx] * colorMapPixelSizeInBytes])); break; case 2: this.ReadPalettedBgra16Pixel(palette, bufferSpan[idx], colorMapPixelSizeInBytes, ref color); break; case 3: color.FromBgr24(Unsafe.As <byte, Bgr24>(ref palette[bufferSpan[idx] * colorMapPixelSizeInBytes])); break; case 4: color.FromBgra32(Unsafe.As <byte, Bgra32>(ref palette[bufferSpan[idx] * colorMapPixelSizeInBytes])); break; } int newX = InvertX(x, width, origin); pixelRow[newX] = color; } } } }
/// <summary> /// Reads a uncompressed TGA image with a palette. /// </summary> /// <typeparam name="TPixel">The pixel type.</typeparam> /// <param name="width">The width of the image.</param> /// <param name="height">The height of the image.</param> /// <param name="pixels">The <see cref="Buffer2D{TPixel}"/> to assign the palette to.</param> /// <param name="palette">The color palette.</param> /// <param name="colorMapPixelSizeInBytes">Color map size of one entry in bytes.</param> /// <param name="origin">The image origin.</param> private void ReadPaletted <TPixel>(int width, int height, Buffer2D <TPixel> pixels, byte[] palette, int colorMapPixelSizeInBytes, TgaImageOrigin origin) where TPixel : unmanaged, IPixel <TPixel> { TPixel color = default; bool invertX = InvertX(origin); for (int y = 0; y < height; y++) { int newY = InvertY(y, height, origin); Span <TPixel> pixelRow = pixels.GetRowSpan(newY); switch (colorMapPixelSizeInBytes) { case 2: if (invertX) { for (int x = width - 1; x >= 0; x--) { this.ReadPalettedBgra16Pixel(palette, colorMapPixelSizeInBytes, x, color, pixelRow); } } else { for (int x = 0; x < width; x++) { this.ReadPalettedBgra16Pixel(palette, colorMapPixelSizeInBytes, x, color, pixelRow); } } break; case 3: if (invertX) { for (int x = width - 1; x >= 0; x--) { this.ReadPalettedBgr24Pixel(palette, colorMapPixelSizeInBytes, x, color, pixelRow); } } else { for (int x = 0; x < width; x++) { this.ReadPalettedBgr24Pixel(palette, colorMapPixelSizeInBytes, x, color, pixelRow); } } break; case 4: if (invertX) { for (int x = width - 1; x >= 0; x--) { this.ReadPalettedBgra32Pixel(palette, colorMapPixelSizeInBytes, x, color, pixelRow); } } else { for (int x = 0; x < width; x++) { this.ReadPalettedBgra32Pixel(palette, colorMapPixelSizeInBytes, x, color, pixelRow); } } break; } } }
/// <summary> /// Reads a uncompressed monochrome TGA image. /// </summary> /// <typeparam name="TPixel">The pixel type.</typeparam> /// <param name="width">The width of the image.</param> /// <param name="height">The height of the image.</param> /// <param name="pixels">The <see cref="Buffer2D{TPixel}"/> to assign the palette to.</param> /// <param name="origin">the image origin.</param> private void ReadMonoChrome <TPixel>(int width, int height, Buffer2D <TPixel> pixels, TgaImageOrigin origin) where TPixel : unmanaged, IPixel <TPixel> { bool invertX = InvertX(origin); if (invertX) { TPixel color = default; for (int y = 0; y < height; y++) { int newY = InvertY(y, height, origin); Span <TPixel> pixelSpan = pixels.GetRowSpan(newY); for (int x = width - 1; x >= 0; x--) { this.ReadL8Pixel(color, x, pixelSpan); } } return; } using IMemoryOwner <byte> row = this.memoryAllocator.AllocatePaddedPixelRowBuffer(width, 1, 0); Span <byte> rowSpan = row.GetSpan(); bool invertY = InvertY(origin); if (invertY) { for (int y = height - 1; y >= 0; y--) { this.ReadL8Row(width, pixels, rowSpan, y); } } else { for (int y = 0; y < height; y++) { this.ReadL8Row(width, pixels, rowSpan, y); } } }
/// <summary> /// Reads a run length encoded TGA image with a palette. /// </summary> /// <typeparam name="TPixel">The pixel type.</typeparam> /// <param name="width">The width of the image.</param> /// <param name="height">The height of the image.</param> /// <param name="pixels">The <see cref="Buffer2D{TPixel}"/> to assign the palette to.</param> /// <param name="palette">The color palette.</param> /// <param name="colorMapPixelSizeInBytes">Color map size of one entry in bytes.</param> /// <param name="origin">The image origin.</param> private void ReadPalettedRle <TPixel>(int width, int height, Buffer2D <TPixel> pixels, byte[] palette, int colorMapPixelSizeInBytes, TgaImageOrigin origin) where TPixel : unmanaged, IPixel <TPixel> { int bytesPerPixel = 1; using (IMemoryOwner <byte> buffer = this.memoryAllocator.Allocate <byte>(width * height * bytesPerPixel, AllocationOptions.Clean)) { TPixel color = default; var alphaBits = this.tgaMetadata.AlphaChannelBits; Span <byte> bufferSpan = buffer.GetSpan(); this.UncompressRle(width, height, bufferSpan, bytesPerPixel: 1); for (int y = 0; y < height; y++) { int newY = InvertY(y, height, origin); Span <TPixel> pixelRow = pixels.GetRowSpan(newY); int rowStartIdx = y * width * bytesPerPixel; for (int x = 0; x < width; x++) { int idx = rowStartIdx + x; switch (colorMapPixelSizeInBytes) { case 1: color.FromL8(Unsafe.As <byte, L8>(ref palette[bufferSpan[idx] * colorMapPixelSizeInBytes])); break; case 2: Bgra5551 bgra = Unsafe.As <byte, Bgra5551>(ref palette[bufferSpan[idx] * colorMapPixelSizeInBytes]); if (!this.hasAlpha) { // Set alpha value to 1, to treat it as opaque for Bgra5551. bgra.PackedValue = (ushort)(bgra.PackedValue | 0x8000); } color.FromBgra5551(bgra); break; case 3: color.FromBgr24(Unsafe.As <byte, Bgr24>(ref palette[bufferSpan[idx] * colorMapPixelSizeInBytes])); break; case 4: if (this.hasAlpha) { color.FromBgra32(Unsafe.As <byte, Bgra32>(ref palette[bufferSpan[idx] * colorMapPixelSizeInBytes])); } else { var alpha = alphaBits == 0 ? byte.MaxValue : bufferSpan[idx + 3]; color.FromBgra32(new Bgra32(bufferSpan[idx + 2], bufferSpan[idx + 1], bufferSpan[idx], (byte)alpha)); } break; } int newX = InvertX(x, width, origin); pixelRow[newX] = color; } } } }