/// <inheritdoc /> public Image <TPixel> Decode <TPixel>(BufferedReadStream 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.ThrowInvalidImageContentException("Missing tga color map length"); } if (this.fileHeader.CMapDepth <= 0) { TgaThrowHelper.ThrowInvalidImageContentException("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> /// Attempts to convert a byte array to a new array where each value in the original array is represented by the /// specified number of bits. /// </summary> /// <param name="source">The bytes to convert from. Cannot be empty.</param> /// <param name="bytesPerScanline">The number of bytes per scanline</param> /// <param name="bits">The number of bits per value.</param> /// <param name="buffer">The new array.</param> /// <returns>The resulting <see cref="ReadOnlySpan{Byte}"/> array.</returns> private bool TryScaleUpTo8BitArray(ReadOnlySpan <byte> source, int bytesPerScanline, int bits, out IManagedByteBuffer buffer) { if (bits >= 8) { buffer = null; return(false); } buffer = this.memoryAllocator.AllocateManagedByteBuffer(bytesPerScanline * 8 / bits, AllocationOptions.Clean); ref byte sourceRef = ref MemoryMarshal.GetReference(source);
/// <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); } } } } }
public static void Read(this Stream stream, IManagedByteBuffer buffer) => stream.Read(buffer.Array, 0, buffer.Length());
public static void Write(this Stream stream, IManagedByteBuffer buffer) { stream.Write(buffer.Array, 0, buffer.Length()); }
/// <summary> /// Writes the pixel information to the stream. /// </summary> /// <typeparam name="TPixel">The pixel format.</typeparam> /// <param name="pixels">The image.</param> /// <param name="stream">The stream.</param> private void WriteDataChunks <TPixel>(ImageFrame <TPixel> pixels, Stream stream) where TPixel : struct, IPixel <TPixel> { this.bytesPerScanline = this.width * this.bytesPerPixel; int resultLength = this.bytesPerScanline + 1; this.previousScanline = this.memoryManager.AllocateCleanManagedByteBuffer(this.bytesPerScanline); this.rawScanline = this.memoryManager.AllocateCleanManagedByteBuffer(this.bytesPerScanline); this.result = this.memoryManager.AllocateCleanManagedByteBuffer(resultLength); if (this.pngColorType != PngColorType.Palette) { this.sub = this.memoryManager.AllocateCleanManagedByteBuffer(resultLength); this.up = this.memoryManager.AllocateCleanManagedByteBuffer(resultLength); this.average = this.memoryManager.AllocateCleanManagedByteBuffer(resultLength); this.paeth = this.memoryManager.AllocateCleanManagedByteBuffer(resultLength); } byte[] buffer; int bufferLength; MemoryStream memoryStream = null; try { memoryStream = new MemoryStream(); using (var deflateStream = new ZlibDeflateStream(memoryStream, this.compressionLevel)) { for (int y = 0; y < this.height; y++) { IManagedByteBuffer r = this.EncodePixelRow(pixels.GetPixelRowSpan(y), y); deflateStream.Write(r.Array, 0, resultLength); IManagedByteBuffer temp = this.rawScanline; this.rawScanline = this.previousScanline; this.previousScanline = temp; } } buffer = memoryStream.ToArray(); bufferLength = buffer.Length; } finally { memoryStream?.Dispose(); } // Store the chunks in repeated 64k blocks. // This reduces the memory load for decoding the image for many decoders. int numChunks = bufferLength / MaxBlockSize; if (bufferLength % MaxBlockSize != 0) { numChunks++; } for (int i = 0; i < numChunks; i++) { int length = bufferLength - (i * MaxBlockSize); if (length > MaxBlockSize) { length = MaxBlockSize; } this.WriteChunk(stream, PngChunkTypes.Data, buffer, i * MaxBlockSize, length); } }
/// <summary> /// Writes the palette chunk to the stream. /// </summary> /// <typeparam name="TPixel">The pixel format.</typeparam> /// <param name="stream">The <see cref="Stream"/> containing image data.</param> /// <param name="header">The <see cref="PngHeader"/>.</param> /// <param name="image">The image to encode.</param> /// <returns>The <see cref="QuantizedFrame{TPixel}"/></returns> private QuantizedFrame <TPixel> WritePaletteChunk <TPixel>(Stream stream, PngHeader header, ImageFrame <TPixel> image) where TPixel : struct, IPixel <TPixel> { if (this.paletteSize > 256) { return(null); } if (this.quantizer == null) { this.quantizer = new WuQuantizer <TPixel>(); } // Quantize the image returning a palette. This boxing is icky. QuantizedFrame <TPixel> quantized = ((IQuantizer <TPixel>) this.quantizer).Quantize(image, this.paletteSize); // Grab the palette and write it to the stream. TPixel[] palette = quantized.Palette; byte pixelCount = palette.Length.ToByte(); // Get max colors for bit depth. int colorTableLength = (int)Math.Pow(2, header.BitDepth) * 3; var rgba = default(Rgba32); bool anyAlpha = false; using (IManagedByteBuffer colorTable = this.memoryManager.AllocateManagedByteBuffer(colorTableLength)) using (IManagedByteBuffer alphaTable = this.memoryManager.AllocateManagedByteBuffer(pixelCount)) { Span <byte> colorTableSpan = colorTable.Span; Span <byte> alphaTableSpan = alphaTable.Span; for (byte i = 0; i < pixelCount; i++) { if (quantized.Pixels.Contains(i)) { int offset = i * 3; palette[i].ToRgba32(ref rgba); byte alpha = rgba.A; colorTableSpan[offset] = rgba.R; colorTableSpan[offset + 1] = rgba.G; colorTableSpan[offset + 2] = rgba.B; if (alpha > this.threshold) { alpha = 255; } anyAlpha = anyAlpha || alpha < 255; alphaTableSpan[i] = alpha; } } this.WriteChunk(stream, PngChunkTypes.Palette, colorTable.Array, 0, colorTableLength); // Write the transparency data if (anyAlpha) { this.WriteChunk(stream, PngChunkTypes.PaletteAlpha, alphaTable.Array, 0, pixelCount); } } return(quantized); }
public static void Write(this Stream stream, IManagedByteBuffer buffer) => stream.Write(buffer.Array, 0, buffer.Memory.Length);
public PngChunk(int length, PngChunkType type, IManagedByteBuffer data = null) { this.Length = length; this.Type = type; this.Data = data; }
/// <summary> /// Reads the 32 bit color palette from the stream, checking the alpha component of each pixel. /// This is a special case only used for 32bpp WinBMPv3 files, which could be in either BGR0 or BGRA format. /// </summary> /// <typeparam name="TPixel">The pixel format.</typeparam> /// <param name="pixels">The <see cref="Buffer2D{TPixel}"/> to assign the palette to.</param> /// <param name="width">The width of the bitmap.</param> /// <param name="height">The height of the bitmap.</param> /// <param name="inverted">Whether the bitmap is inverted.</param> private void ReadRgb32Slow <TPixel>(Buffer2D <TPixel> pixels, int width, int height, bool inverted) where TPixel : struct, IPixel <TPixel> { int padding = CalculatePadding(width, 4); using (IManagedByteBuffer row = this.memoryAllocator.AllocatePaddedPixelRowBuffer(width, 4, padding)) using (IMemoryOwner <Bgra32> bgraRow = this.memoryAllocator.Allocate <Bgra32>(width)) { Span <Bgra32> bgraRowSpan = bgraRow.GetSpan(); long currentPosition = this.stream.Position; bool hasAlpha = false; // Loop though the rows checking each pixel. We start by assuming it's // an BGR0 image. If we hit a non-zero alpha value, then we know it's // actually a BGRA image, and change tactics accordingly. for (int y = 0; y < height; y++) { this.stream.Read(row); PixelOperations <Bgra32> .Instance.FromBgra32Bytes( this.configuration, row.GetSpan(), bgraRowSpan, width); // Check each pixel in the row to see if it has an alpha value. for (int x = 0; x < width; x++) { Bgra32 bgra = bgraRowSpan[x]; if (bgra.A > 0) { hasAlpha = true; break; } } if (hasAlpha) { break; } } // Reset our stream for a second pass. this.stream.Position = currentPosition; // Process the pixels in bulk taking the raw alpha component value. if (hasAlpha) { for (int y = 0; y < height; y++) { this.stream.Read(row); int newY = Invert(y, height, inverted); Span <TPixel> pixelSpan = pixels.GetRowSpan(newY); PixelOperations <TPixel> .Instance.FromBgra32Bytes( this.configuration, row.GetSpan(), pixelSpan, width); } return; } // Slow path. We need to set each alpha component value to fully opaque. for (int y = 0; y < height; y++) { this.stream.Read(row); PixelOperations <Bgra32> .Instance.FromBgra32Bytes( this.configuration, row.GetSpan(), bgraRowSpan, width); int newY = Invert(y, height, inverted); Span <TPixel> pixelSpan = pixels.GetRowSpan(newY); for (int x = 0; x < width; x++) { Bgra32 bgra = bgraRowSpan[x]; bgra.A = byte.MaxValue; ref TPixel pixel = ref pixelSpan[x]; pixel.FromBgra32(bgra); } } }