/// <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);
            }
        }
Beispiel #2
0
        /// <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());
Beispiel #5
0
 public static void Write(this Stream stream, IManagedByteBuffer buffer)
 {
     stream.Write(buffer.Array, 0, buffer.Length());
 }
Beispiel #6
0
        /// <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);
            }
        }
Beispiel #7
0
        /// <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);
        }
Beispiel #8
0
 public static void Write(this Stream stream, IManagedByteBuffer buffer) => stream.Write(buffer.Array, 0, buffer.Memory.Length);
Beispiel #9
0
 public PngChunk(int length, PngChunkType type, IManagedByteBuffer data = null)
 {
     this.Length = length;
     this.Type   = type;
     this.Data   = data;
 }
Beispiel #10
0
        /// <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);
                        }
                    }
                }