/// <summary> /// Reads the tga file header from the stream. /// </summary> /// <param name="stream">The <see cref="Stream"/> containing image data.</param> /// <returns>The image origin.</returns> private TgaImageOrigin ReadFileHeader(Stream stream) { this.currentStream = stream; Span <byte> buffer = stackalloc byte[TgaFileHeader.Size]; this.currentStream.Read(buffer, 0, TgaFileHeader.Size); this.fileHeader = TgaFileHeader.Parse(buffer); this.metadata = new ImageMetadata(); this.tgaMetadata = this.metadata.GetTgaMetadata(); this.tgaMetadata.BitsPerPixel = (TgaBitsPerPixel)this.fileHeader.PixelDepth; var alphaBits = this.fileHeader.ImageDescriptor & 0xf; if (alphaBits != 0 && alphaBits != 1 && alphaBits != 8) { TgaThrowHelper.ThrowImageFormatException("Invalid alpha channel bits"); } this.tgaMetadata.AlphaChannelBits = (byte)alphaBits; this.hasAlpha = alphaBits > 0; // Bits 4 and 5 describe the image origin. var origin = (TgaImageOrigin)((this.fileHeader.ImageDescriptor & 0x30) >> 4); return(origin); }
/// <summary> /// Reads the tga file header from the stream. /// </summary> /// <param name="stream">The <see cref="Stream"/> containing image data.</param> /// <returns>true, if the image origin is top left.</returns> private bool ReadFileHeader(Stream stream) { this.currentStream = stream; Span <byte> buffer = stackalloc byte[TgaFileHeader.Size]; this.currentStream.Read(buffer, 0, TgaFileHeader.Size); this.fileHeader = TgaFileHeader.Parse(buffer); this.metadata = new ImageMetadata(); this.tgaMetadata = this.metadata.GetTgaMetadata(); this.tgaMetadata.BitsPerPixel = (TgaBitsPerPixel)this.fileHeader.PixelDepth; var alphaBits = this.fileHeader.ImageDescriptor & 0xf; if (alphaBits != 0 && alphaBits != 1 && alphaBits != 8) { TgaThrowHelper.ThrowImageFormatException("Invalid alpha channel bits"); } 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); }
/// <inheritdoc/> public Image <TPixel> Decode <TPixel>(Configuration configuration, Stream stream) where TPixel : unmanaged, IPixel <TPixel> { Guard.NotNull(stream, nameof(stream)); var decoder = new TgaDecoderCore(configuration, this); try { return(decoder.Decode <TPixel>(stream)); } catch (InvalidMemoryOperationException ex) { Size dims = decoder.Dimensions; TgaThrowHelper.ThrowInvalidImageContentException($"Can not decode image. Failed to allocate buffers for possibly degenerate dimensions: {dims.Width}x{dims.Height}.", ex); // Not reachable, as the previous statement will throw a exception. return(null); } }
/// <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); } }