mirror of https://github.com/SixLabors/ImageSharp
57 changed files with 2150 additions and 29 deletions
@ -0,0 +1,6 @@ |
|||
# Encoder/Decoder for true vision targa files |
|||
|
|||
Useful links for reference: |
|||
|
|||
- [FileFront](https://www.fileformat.info/format/tga/egff.htm) |
|||
- [Tga Specification](http://www.dca.fee.unicamp.br/~martino/disciplinas/ea978/tgaffs.pdf) |
|||
@ -0,0 +1,12 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Tga |
|||
{ |
|||
/// <summary>
|
|||
/// The options for decoding tga images. Currently empty, but this may change in the future.
|
|||
/// </summary>
|
|||
internal interface ITgaDecoderOptions |
|||
{ |
|||
} |
|||
} |
|||
@ -0,0 +1,21 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Tga |
|||
{ |
|||
/// <summary>
|
|||
/// Configuration options for use during tga encoding.
|
|||
/// </summary>
|
|||
internal interface ITgaEncoderOptions |
|||
{ |
|||
/// <summary>
|
|||
/// Gets the number of bits per pixel.
|
|||
/// </summary>
|
|||
TgaBitsPerPixel? BitsPerPixel { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets a value indicating whether run length compression should be used.
|
|||
/// </summary>
|
|||
TgaCompression Compression { get; } |
|||
} |
|||
} |
|||
Binary file not shown.
@ -0,0 +1,31 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Tga |
|||
{ |
|||
/// <summary>
|
|||
/// Enumerates the available bits per pixel the tga encoder supports.
|
|||
/// </summary>
|
|||
public enum TgaBitsPerPixel : byte |
|||
{ |
|||
/// <summary>
|
|||
/// 8 bits per pixel. Each pixel consists of 1 byte.
|
|||
/// </summary>
|
|||
Pixel8 = 8, |
|||
|
|||
/// <summary>
|
|||
/// 16 bits per pixel. Each pixel consists of 2 bytes.
|
|||
/// </summary>
|
|||
Pixel16 = 16, |
|||
|
|||
/// <summary>
|
|||
/// 24 bits per pixel. Each pixel consists of 3 bytes.
|
|||
/// </summary>
|
|||
Pixel24 = 24, |
|||
|
|||
/// <summary>
|
|||
/// 32 bits per pixel. Each pixel consists of 4 bytes.
|
|||
/// </summary>
|
|||
Pixel32 = 32 |
|||
} |
|||
} |
|||
@ -0,0 +1,21 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Tga |
|||
{ |
|||
/// <summary>
|
|||
/// Indicates if compression is used.
|
|||
/// </summary>
|
|||
public enum TgaCompression |
|||
{ |
|||
/// <summary>
|
|||
/// No compression is used.
|
|||
/// </summary>
|
|||
None, |
|||
|
|||
/// <summary>
|
|||
/// Run length encoding is used.
|
|||
/// </summary>
|
|||
RunLength, |
|||
} |
|||
} |
|||
@ -0,0 +1,19 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Tga |
|||
{ |
|||
/// <summary>
|
|||
/// Registers the image encoders, decoders and mime type detectors for the tga format.
|
|||
/// </summary>
|
|||
public sealed class TgaConfigurationModule : IConfigurationModule |
|||
{ |
|||
/// <inheritdoc/>
|
|||
public void Configure(Configuration configuration) |
|||
{ |
|||
configuration.ImageFormatsManager.SetEncoder(TgaFormat.Instance, new TgaEncoder()); |
|||
configuration.ImageFormatsManager.SetDecoder(TgaFormat.Instance, new TgaDecoder()); |
|||
configuration.ImageFormatsManager.AddImageFormatDetector(new TgaImageFormatDetector()); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,25 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System.Collections.Generic; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Tga |
|||
{ |
|||
internal static class TgaConstants |
|||
{ |
|||
/// <summary>
|
|||
/// The list of mimetypes that equate to a targa file.
|
|||
/// </summary>
|
|||
public static readonly IEnumerable<string> MimeTypes = new[] { "image/x-tga", "image/x-targa" }; |
|||
|
|||
/// <summary>
|
|||
/// The list of file extensions that equate to a targa file.
|
|||
/// </summary>
|
|||
public static readonly IEnumerable<string> FileExtensions = new[] { "tga", "vda", "icb", "vst" }; |
|||
|
|||
/// <summary>
|
|||
/// The file header length of a tga image in bytes.
|
|||
/// </summary>
|
|||
public const int FileHeaderLength = 18; |
|||
} |
|||
} |
|||
@ -0,0 +1,34 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System.IO; |
|||
using SixLabors.ImageSharp.PixelFormats; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Tga |
|||
{ |
|||
/// <summary>
|
|||
/// Image decoder for Truevision TGA images.
|
|||
/// </summary>
|
|||
public sealed class TgaDecoder : IImageDecoder, ITgaDecoderOptions, IImageInfoDetector |
|||
{ |
|||
/// <inheritdoc/>
|
|||
public Image<TPixel> Decode<TPixel>(Configuration configuration, Stream stream) |
|||
where TPixel : struct, IPixel<TPixel> |
|||
{ |
|||
Guard.NotNull(stream, nameof(stream)); |
|||
|
|||
return new TgaDecoderCore(configuration, this).Decode<TPixel>(stream); |
|||
} |
|||
|
|||
/// <inheritdoc />
|
|||
public Image Decode(Configuration configuration, Stream stream) => this.Decode<Rgba32>(configuration, stream); |
|||
|
|||
/// <inheritdoc/>
|
|||
public IImageInfo Identify(Configuration configuration, Stream stream) |
|||
{ |
|||
Guard.NotNull(stream, nameof(stream)); |
|||
|
|||
return new TgaDecoderCore(configuration, this).Identify(stream); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,588 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System; |
|||
using System.Buffers; |
|||
using System.IO; |
|||
using System.Runtime.CompilerServices; |
|||
|
|||
using SixLabors.ImageSharp.Memory; |
|||
using SixLabors.ImageSharp.Metadata; |
|||
using SixLabors.ImageSharp.PixelFormats; |
|||
using SixLabors.Memory; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Tga |
|||
{ |
|||
internal sealed class TgaDecoderCore |
|||
{ |
|||
/// <summary>
|
|||
/// The metadata.
|
|||
/// </summary>
|
|||
private ImageMetadata metadata; |
|||
|
|||
/// <summary>
|
|||
/// The tga specific metadata.
|
|||
/// </summary>
|
|||
private TgaMetadata tgaMetadata; |
|||
|
|||
/// <summary>
|
|||
/// The file header containing general information about the image.
|
|||
/// </summary>
|
|||
private TgaFileHeader fileHeader; |
|||
|
|||
/// <summary>
|
|||
/// The global configuration.
|
|||
/// </summary>
|
|||
private readonly Configuration configuration; |
|||
|
|||
/// <summary>
|
|||
/// Used for allocating memory during processing operations.
|
|||
/// </summary>
|
|||
private readonly MemoryAllocator memoryAllocator; |
|||
|
|||
/// <summary>
|
|||
/// The stream to decode from.
|
|||
/// </summary>
|
|||
private Stream currentStream; |
|||
|
|||
/// <summary>
|
|||
/// The bitmap decoder options.
|
|||
/// </summary>
|
|||
private readonly ITgaDecoderOptions options; |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="TgaDecoderCore"/> class.
|
|||
/// </summary>
|
|||
/// <param name="configuration">The configuration.</param>
|
|||
/// <param name="options">The options.</param>
|
|||
public TgaDecoderCore(Configuration configuration, ITgaDecoderOptions options) |
|||
{ |
|||
this.configuration = configuration; |
|||
this.memoryAllocator = configuration.MemoryAllocator; |
|||
this.options = options; |
|||
} |
|||
|
|||
/// <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 : struct, IPixel<TPixel> |
|||
{ |
|||
try |
|||
{ |
|||
bool inverted = 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 = new Image<TPixel>(this.configuration, this.fileHeader.Width, this.fileHeader.Height, this.metadata); |
|||
Buffer2D<TPixel> pixels = image.GetRootFramePixelBuffer(); |
|||
|
|||
if (this.fileHeader.ColorMapType is 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 is TgaImageType.RleColorMapped) |
|||
{ |
|||
this.ReadPalettedRle( |
|||
this.fileHeader.Width, |
|||
this.fileHeader.Height, |
|||
pixels, |
|||
palette.Array, |
|||
colorMapPixelSizeInBytes, |
|||
inverted); |
|||
} |
|||
else |
|||
{ |
|||
this.ReadPaletted( |
|||
this.fileHeader.Width, |
|||
this.fileHeader.Height, |
|||
pixels, |
|||
palette.Array, |
|||
colorMapPixelSizeInBytes, |
|||
inverted); |
|||
} |
|||
} |
|||
|
|||
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, inverted); |
|||
} |
|||
else |
|||
{ |
|||
this.ReadMonoChrome(this.fileHeader.Width, this.fileHeader.Height, pixels, inverted); |
|||
} |
|||
|
|||
break; |
|||
|
|||
case 15: |
|||
case 16: |
|||
if (this.fileHeader.ImageType.IsRunLengthEncoded()) |
|||
{ |
|||
this.ReadRle(this.fileHeader.Width, this.fileHeader.Height, pixels, 2, inverted); |
|||
} |
|||
else |
|||
{ |
|||
this.ReadBgra16(this.fileHeader.Width, this.fileHeader.Height, pixels, inverted); |
|||
} |
|||
|
|||
break; |
|||
|
|||
case 24: |
|||
if (this.fileHeader.ImageType.IsRunLengthEncoded()) |
|||
{ |
|||
this.ReadRle(this.fileHeader.Width, this.fileHeader.Height, pixels, 3, inverted); |
|||
} |
|||
else |
|||
{ |
|||
this.ReadBgr24(this.fileHeader.Width, this.fileHeader.Height, pixels, inverted); |
|||
} |
|||
|
|||
break; |
|||
|
|||
case 32: |
|||
if (this.fileHeader.ImageType.IsRunLengthEncoded()) |
|||
{ |
|||
this.ReadRle(this.fileHeader.Width, this.fileHeader.Height, pixels, 4, inverted); |
|||
} |
|||
else |
|||
{ |
|||
this.ReadBgra32(this.fileHeader.Width, this.fileHeader.Height, pixels, inverted); |
|||
} |
|||
|
|||
break; |
|||
|
|||
default: |
|||
TgaThrowHelper.ThrowNotSupportedException("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 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="inverted">Indicates, if the origin of the image is top left rather the bottom left (the default).</param>
|
|||
private void ReadPaletted<TPixel>(int width, int height, Buffer2D<TPixel> pixels, byte[] palette, int colorMapPixelSizeInBytes, bool inverted) |
|||
where TPixel : struct, IPixel<TPixel> |
|||
{ |
|||
using (IManagedByteBuffer row = this.memoryAllocator.AllocateManagedByteBuffer(width, AllocationOptions.Clean)) |
|||
{ |
|||
TPixel color = default; |
|||
Span<byte> rowSpan = row.GetSpan(); |
|||
|
|||
for (int y = 0; y < height; y++) |
|||
{ |
|||
this.currentStream.Read(row); |
|||
int newY = Invert(y, height, inverted); |
|||
Span<TPixel> pixelRow = pixels.GetRowSpan(newY); |
|||
switch (colorMapPixelSizeInBytes) |
|||
{ |
|||
case 2: |
|||
for (int x = 0; x < width; x++) |
|||
{ |
|||
int colorIndex = rowSpan[x]; |
|||
|
|||
// Set alpha value to 1, to treat it as opaque for Bgra5551.
|
|||
Bgra5551 bgra = Unsafe.As<byte, Bgra5551>(ref palette[colorIndex * colorMapPixelSizeInBytes]); |
|||
bgra.PackedValue = (ushort)(bgra.PackedValue | 0x8000); |
|||
color.FromBgra5551(bgra); |
|||
pixelRow[x] = color; |
|||
} |
|||
|
|||
break; |
|||
|
|||
case 3: |
|||
for (int x = 0; x < width; x++) |
|||
{ |
|||
int colorIndex = rowSpan[x]; |
|||
color.FromBgr24(Unsafe.As<byte, Bgr24>(ref palette[colorIndex * colorMapPixelSizeInBytes])); |
|||
pixelRow[x] = color; |
|||
} |
|||
|
|||
break; |
|||
|
|||
case 4: |
|||
for (int x = 0; x < width; x++) |
|||
{ |
|||
int colorIndex = rowSpan[x]; |
|||
color.FromBgra32(Unsafe.As<byte, Bgra32>(ref palette[colorIndex * colorMapPixelSizeInBytes])); |
|||
pixelRow[x] = color; |
|||
} |
|||
|
|||
break; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
/// <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="inverted">Indicates, if the origin of the image is top left rather the bottom left (the default).</param>
|
|||
private void ReadPalettedRle<TPixel>(int width, int height, Buffer2D<TPixel> pixels, byte[] palette, int colorMapPixelSizeInBytes, bool inverted) |
|||
where TPixel : struct, 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 = Invert(y, height, inverted); |
|||
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.FromGray8(Unsafe.As<byte, Gray8>(ref palette[bufferSpan[idx] * colorMapPixelSizeInBytes])); |
|||
break; |
|||
case 2: |
|||
// Set alpha value to 1, to treat it as opaque for Bgra5551.
|
|||
Bgra5551 bgra = Unsafe.As<byte, Bgra5551>(ref palette[bufferSpan[idx] * colorMapPixelSizeInBytes]); |
|||
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: |
|||
color.FromBgra32(Unsafe.As<byte, Bgra32>(ref palette[bufferSpan[idx] * colorMapPixelSizeInBytes])); |
|||
break; |
|||
} |
|||
|
|||
pixelRow[x] = color; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
/// <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="inverted">Indicates, if the origin of the image is top left rather the bottom left (the default).</param>
|
|||
private void ReadMonoChrome<TPixel>(int width, int height, Buffer2D<TPixel> pixels, bool inverted) |
|||
where TPixel : struct, IPixel<TPixel> |
|||
{ |
|||
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); |
|||
Span<TPixel> pixelSpan = pixels.GetRowSpan(newY); |
|||
PixelOperations<TPixel>.Instance.FromGray8Bytes( |
|||
this.configuration, |
|||
row.GetSpan(), |
|||
pixelSpan, |
|||
width); |
|||
} |
|||
} |
|||
} |
|||
|
|||
/// <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="inverted">Indicates, if the origin of the image is top left rather the bottom left (the default).</param>
|
|||
private void ReadBgra16<TPixel>(int width, int height, Buffer2D<TPixel> pixels, bool inverted) |
|||
where TPixel : struct, IPixel<TPixel> |
|||
{ |
|||
using (IManagedByteBuffer row = this.memoryAllocator.AllocatePaddedPixelRowBuffer(width, 2, 0)) |
|||
{ |
|||
for (int y = 0; y < height; y++) |
|||
{ |
|||
this.currentStream.Read(row); |
|||
Span<byte> rowSpan = row.GetSpan(); |
|||
|
|||
// We need to set each alpha component value to fully opaque.
|
|||
for (int x = 1; x < rowSpan.Length; x += 2) |
|||
{ |
|||
rowSpan[x] = (byte)(rowSpan[x] | (1 << 7)); |
|||
} |
|||
|
|||
int newY = Invert(y, height, inverted); |
|||
Span<TPixel> pixelSpan = pixels.GetRowSpan(newY); |
|||
PixelOperations<TPixel>.Instance.FromBgra5551Bytes( |
|||
this.configuration, |
|||
rowSpan, |
|||
pixelSpan, |
|||
width); |
|||
} |
|||
} |
|||
} |
|||
|
|||
/// <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="inverted">Indicates, if the origin of the image is top left rather the bottom left (the default).</param>
|
|||
private void ReadBgr24<TPixel>(int width, int height, Buffer2D<TPixel> pixels, bool inverted) |
|||
where TPixel : struct, IPixel<TPixel> |
|||
{ |
|||
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); |
|||
Span<TPixel> pixelSpan = pixels.GetRowSpan(newY); |
|||
PixelOperations<TPixel>.Instance.FromBgr24Bytes( |
|||
this.configuration, |
|||
row.GetSpan(), |
|||
pixelSpan, |
|||
width); |
|||
} |
|||
} |
|||
} |
|||
|
|||
/// <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="inverted">Indicates, if the origin of the image is top left rather the bottom left (the default).</param>
|
|||
private void ReadBgra32<TPixel>(int width, int height, Buffer2D<TPixel> pixels, bool inverted) |
|||
where TPixel : struct, IPixel<TPixel> |
|||
{ |
|||
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); |
|||
Span<TPixel> pixelSpan = pixels.GetRowSpan(newY); |
|||
PixelOperations<TPixel>.Instance.FromBgra32Bytes( |
|||
this.configuration, |
|||
row.GetSpan(), |
|||
pixelSpan, |
|||
width); |
|||
} |
|||
} |
|||
} |
|||
|
|||
/// <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="inverted">Indicates, if the origin of the image is top left rather the bottom left (the default).</param>
|
|||
private void ReadRle<TPixel>(int width, int height, Buffer2D<TPixel> pixels, int bytesPerPixel, bool inverted) |
|||
where TPixel : struct, IPixel<TPixel> |
|||
{ |
|||
TPixel color = default; |
|||
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 = Invert(y, height, inverted); |
|||
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.FromGray8(Unsafe.As<byte, Gray8>(ref bufferSpan[idx])); |
|||
break; |
|||
case 2: |
|||
// Set alpha value to 1, to treat it as opaque for Bgra5551.
|
|||
bufferSpan[idx + 1] = (byte)(bufferSpan[idx + 1] | 128); |
|||
color.FromBgra5551(Unsafe.As<byte, Bgra5551>(ref bufferSpan[idx])); |
|||
break; |
|||
case 3: |
|||
color.FromBgr24(Unsafe.As<byte, Bgr24>(ref bufferSpan[idx])); |
|||
break; |
|||
case 4: |
|||
color.FromBgra32(Unsafe.As<byte, Bgra32>(ref bufferSpan[idx])); |
|||
break; |
|||
} |
|||
|
|||
pixelRow[x] = color; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Reads the raw image information from the specified stream.
|
|||
/// </summary>
|
|||
/// <param name="stream">The <see cref="Stream"/> containing image data.</param>
|
|||
public IImageInfo Identify(Stream stream) |
|||
{ |
|||
this.ReadFileHeader(stream); |
|||
return new ImageInfo( |
|||
new PixelTypeInfo(this.fileHeader.PixelDepth), |
|||
this.fileHeader.Width, |
|||
this.fileHeader.Height, |
|||
this.metadata); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Produce uncompressed tga data from a run length encoded stream.
|
|||
/// </summary>
|
|||
/// <param name="width">The width of the image.</param>
|
|||
/// <param name="height">The height of the image.</param>
|
|||
/// <param name="buffer">Buffer for uncompressed data.</param>
|
|||
/// <param name="bytesPerPixel">The bytes used per pixel.</param>
|
|||
private void UncompressRle(int width, int height, Span<byte> buffer, int bytesPerPixel) |
|||
{ |
|||
int uncompressedPixels = 0; |
|||
var pixel = new byte[bytesPerPixel]; |
|||
int totalPixels = width * height; |
|||
while (uncompressedPixels < totalPixels) |
|||
{ |
|||
byte runLengthByte = (byte)this.currentStream.ReadByte(); |
|||
|
|||
// The high bit of a run length packet is set to 1.
|
|||
int highBit = runLengthByte >> 7; |
|||
if (highBit == 1) |
|||
{ |
|||
int runLength = runLengthByte & 127; |
|||
this.currentStream.Read(pixel, 0, bytesPerPixel); |
|||
int bufferIdx = uncompressedPixels * bytesPerPixel; |
|||
for (int i = 0; i < runLength + 1; i++, uncompressedPixels++) |
|||
{ |
|||
pixel.AsSpan().CopyTo(buffer.Slice(bufferIdx)); |
|||
bufferIdx += bytesPerPixel; |
|||
} |
|||
} |
|||
else |
|||
{ |
|||
// Non-run-length encoded packet.
|
|||
int runLength = runLengthByte; |
|||
int bufferIdx = uncompressedPixels * bytesPerPixel; |
|||
for (int i = 0; i < runLength + 1; i++, uncompressedPixels++) |
|||
{ |
|||
this.currentStream.Read(pixel, 0, bytesPerPixel); |
|||
pixel.AsSpan().CopyTo(buffer.Slice(bufferIdx)); |
|||
bufferIdx += bytesPerPixel; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Returns the y- value based on the given height.
|
|||
/// </summary>
|
|||
/// <param name="y">The y- value representing the current row.</param>
|
|||
/// <param name="height">The height of the bitmap.</param>
|
|||
/// <param name="inverted">Whether the bitmap is inverted.</param>
|
|||
/// <returns>The <see cref="int"/> representing the inverted value.</returns>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
private static int Invert(int y, int height, bool inverted) => (!inverted) ? height - y - 1 : y; |
|||
|
|||
/// <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; |
|||
|
|||
#if NETCOREAPP2_1
|
|||
Span<byte> buffer = stackalloc byte[TgaFileHeader.Size]; |
|||
#else
|
|||
var buffer = new byte[TgaFileHeader.Size]; |
|||
#endif
|
|||
this.currentStream.Read(buffer, 0, TgaFileHeader.Size); |
|||
this.fileHeader = TgaFileHeader.Parse(buffer); |
|||
this.metadata = new ImageMetadata(); |
|||
this.tgaMetadata = this.metadata.GetFormatMetadata(TgaFormat.Instance); |
|||
this.tgaMetadata.BitsPerPixel = (TgaBitsPerPixel)this.fileHeader.PixelDepth; |
|||
|
|||
// Bit at position 5 of the descriptor indicates, that the origin is top left instead of bottom right.
|
|||
if ((this.fileHeader.ImageDescriptor & (1 << 5)) != 0) |
|||
{ |
|||
return true; |
|||
} |
|||
|
|||
return false; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,34 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System.IO; |
|||
|
|||
using SixLabors.ImageSharp.Advanced; |
|||
using SixLabors.ImageSharp.PixelFormats; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Tga |
|||
{ |
|||
/// <summary>
|
|||
/// Image encoder for writing an image to a stream as a targa truevision image.
|
|||
/// </summary>
|
|||
public sealed class TgaEncoder : IImageEncoder, ITgaEncoderOptions |
|||
{ |
|||
/// <summary>
|
|||
/// Gets or sets the number of bits per pixel.
|
|||
/// </summary>
|
|||
public TgaBitsPerPixel? BitsPerPixel { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets a value indicating whether no compression or run length compression should be used.
|
|||
/// </summary>
|
|||
public TgaCompression Compression { get; set; } = TgaCompression.RunLength; |
|||
|
|||
/// <inheritdoc/>
|
|||
public void Encode<TPixel>(Image<TPixel> image, Stream stream) |
|||
where TPixel : struct, IPixel<TPixel> |
|||
{ |
|||
var encoder = new TgaEncoderCore(this, image.GetMemoryAllocator()); |
|||
encoder.Encode(image, stream); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,348 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System; |
|||
using System.Buffers.Binary; |
|||
using System.IO; |
|||
using System.Numerics; |
|||
using System.Runtime.CompilerServices; |
|||
|
|||
using SixLabors.ImageSharp.Advanced; |
|||
using SixLabors.ImageSharp.Memory; |
|||
using SixLabors.ImageSharp.Metadata; |
|||
using SixLabors.ImageSharp.PixelFormats; |
|||
using SixLabors.Memory; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Tga |
|||
{ |
|||
/// <summary>
|
|||
/// Image encoder for writing an image to a stream as a truevision targa image.
|
|||
/// </summary>
|
|||
internal sealed class TgaEncoderCore |
|||
{ |
|||
/// <summary>
|
|||
/// Used for allocating memory during processing operations.
|
|||
/// </summary>
|
|||
private readonly MemoryAllocator memoryAllocator; |
|||
|
|||
/// <summary>
|
|||
/// The global configuration.
|
|||
/// </summary>
|
|||
private Configuration configuration; |
|||
|
|||
/// <summary>
|
|||
/// Reusable buffer for writing data.
|
|||
/// </summary>
|
|||
private readonly byte[] buffer = new byte[2]; |
|||
|
|||
/// <summary>
|
|||
/// The color depth, in number of bits per pixel.
|
|||
/// </summary>
|
|||
private TgaBitsPerPixel? bitsPerPixel; |
|||
|
|||
/// <summary>
|
|||
/// Indicates if run length compression should be used.
|
|||
/// </summary>
|
|||
private readonly TgaCompression compression; |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="TgaEncoderCore"/> class.
|
|||
/// </summary>
|
|||
/// <param name="options">The encoder options.</param>
|
|||
/// <param name="memoryAllocator">The memory manager.</param>
|
|||
public TgaEncoderCore(ITgaEncoderOptions options, MemoryAllocator memoryAllocator) |
|||
{ |
|||
this.memoryAllocator = memoryAllocator; |
|||
this.bitsPerPixel = options.BitsPerPixel; |
|||
this.compression = options.Compression; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Encodes the image to the specified stream from the <see cref="ImageFrame{TPixel}"/>.
|
|||
/// </summary>
|
|||
/// <typeparam name="TPixel">The pixel format.</typeparam>
|
|||
/// <param name="image">The <see cref="ImageFrame{TPixel}"/> to encode from.</param>
|
|||
/// <param name="stream">The <see cref="Stream"/> to encode the image data to.</param>
|
|||
public void Encode<TPixel>(Image<TPixel> image, Stream stream) |
|||
where TPixel : struct, IPixel<TPixel> |
|||
{ |
|||
Guard.NotNull(image, nameof(image)); |
|||
Guard.NotNull(stream, nameof(stream)); |
|||
|
|||
this.configuration = image.GetConfiguration(); |
|||
ImageMetadata metadata = image.Metadata; |
|||
TgaMetadata tgaMetadata = metadata.GetFormatMetadata(TgaFormat.Instance); |
|||
this.bitsPerPixel = this.bitsPerPixel ?? tgaMetadata.BitsPerPixel; |
|||
|
|||
TgaImageType imageType = this.compression is TgaCompression.RunLength ? TgaImageType.RleTrueColor : TgaImageType.TrueColor; |
|||
if (this.bitsPerPixel == TgaBitsPerPixel.Pixel8) |
|||
{ |
|||
imageType = this.compression is TgaCompression.RunLength ? TgaImageType.RleBlackAndWhite : TgaImageType.BlackAndWhite; |
|||
} |
|||
|
|||
// If compression is used, set bit 5 of the image descriptor to indicate an left top origin.
|
|||
byte imageDescriptor = (byte)(this.compression is TgaCompression.RunLength ? 32 : 0); |
|||
|
|||
var fileHeader = new TgaFileHeader( |
|||
idLength: 0, |
|||
colorMapType: 0, |
|||
imageType: imageType, |
|||
cMapStart: 0, |
|||
cMapLength: 0, |
|||
cMapDepth: 0, |
|||
xOffset: 0, |
|||
yOffset: this.compression is TgaCompression.RunLength ? (short)image.Height : (short)0, // When run length encoding is used, the origin should be top left instead of the default bottom left.
|
|||
width: (short)image.Width, |
|||
height: (short)image.Height, |
|||
pixelDepth: (byte)this.bitsPerPixel.Value, |
|||
imageDescriptor: imageDescriptor); |
|||
|
|||
#if NETCOREAPP2_1
|
|||
Span<byte> buffer = stackalloc byte[TgaFileHeader.Size]; |
|||
#else
|
|||
byte[] buffer = new byte[TgaFileHeader.Size]; |
|||
#endif
|
|||
fileHeader.WriteTo(buffer); |
|||
|
|||
stream.Write(buffer, 0, TgaFileHeader.Size); |
|||
|
|||
if (this.compression is TgaCompression.RunLength) |
|||
{ |
|||
this.WriteRunLengthEndcodedImage(stream, image.Frames.RootFrame); |
|||
} |
|||
else |
|||
{ |
|||
this.WriteImage(stream, image.Frames.RootFrame); |
|||
} |
|||
|
|||
stream.Flush(); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Writes the pixel data to the binary stream.
|
|||
/// </summary>
|
|||
/// <typeparam name="TPixel">The pixel format.</typeparam>
|
|||
/// <param name="stream">The <see cref="Stream"/> to write to.</param>
|
|||
/// <param name="image">
|
|||
/// The <see cref="ImageFrame{TPixel}"/> containing pixel data.
|
|||
/// </param>
|
|||
private void WriteImage<TPixel>(Stream stream, ImageFrame<TPixel> image) |
|||
where TPixel : struct, IPixel<TPixel> |
|||
{ |
|||
Buffer2D<TPixel> pixels = image.PixelBuffer; |
|||
switch (this.bitsPerPixel) |
|||
{ |
|||
case TgaBitsPerPixel.Pixel8: |
|||
this.Write8Bit(stream, pixels); |
|||
break; |
|||
|
|||
case TgaBitsPerPixel.Pixel16: |
|||
this.Write16Bit(stream, pixels); |
|||
break; |
|||
|
|||
case TgaBitsPerPixel.Pixel24: |
|||
this.Write24Bit(stream, pixels); |
|||
break; |
|||
|
|||
case TgaBitsPerPixel.Pixel32: |
|||
this.Write32Bit(stream, pixels); |
|||
break; |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Writes a run length encoded tga image to the stream.
|
|||
/// </summary>
|
|||
/// <typeparam name="TPixel">The pixel type.</typeparam>
|
|||
/// <param name="stream">The stream to write the image to.</param>
|
|||
/// <param name="image">The image to encode.</param>
|
|||
private void WriteRunLengthEndcodedImage<TPixel>(Stream stream, ImageFrame<TPixel> image) |
|||
where TPixel : struct, IPixel<TPixel> |
|||
{ |
|||
Rgba32 color = default; |
|||
Buffer2D<TPixel> pixels = image.PixelBuffer; |
|||
Span<TPixel> pixelSpan = pixels.GetSpan(); |
|||
int totalPixels = image.Width * image.Height; |
|||
int encodedPixels = 0; |
|||
while (encodedPixels < totalPixels) |
|||
{ |
|||
TPixel currentPixel = pixelSpan[encodedPixels]; |
|||
currentPixel.ToRgba32(ref color); |
|||
byte equalPixelCount = this.FindEqualPixels(pixelSpan.Slice(encodedPixels)); |
|||
|
|||
// Write the number of equal pixels, with the high bit set, indicating ist a compressed pixel run.
|
|||
stream.WriteByte((byte)(equalPixelCount | 128)); |
|||
switch (this.bitsPerPixel) |
|||
{ |
|||
case TgaBitsPerPixel.Pixel8: |
|||
int luminance = GetLuminance(currentPixel); |
|||
stream.WriteByte((byte)luminance); |
|||
break; |
|||
|
|||
case TgaBitsPerPixel.Pixel16: |
|||
var bgra5551 = new Bgra5551(color.ToVector4()); |
|||
BinaryPrimitives.TryWriteInt16LittleEndian(this.buffer, (short)bgra5551.PackedValue); |
|||
stream.WriteByte(this.buffer[0]); |
|||
stream.WriteByte(this.buffer[1]); |
|||
|
|||
break; |
|||
|
|||
case TgaBitsPerPixel.Pixel24: |
|||
stream.WriteByte(color.B); |
|||
stream.WriteByte(color.G); |
|||
stream.WriteByte(color.R); |
|||
break; |
|||
|
|||
case TgaBitsPerPixel.Pixel32: |
|||
stream.WriteByte(color.B); |
|||
stream.WriteByte(color.G); |
|||
stream.WriteByte(color.R); |
|||
stream.WriteByte(color.A); |
|||
break; |
|||
} |
|||
|
|||
encodedPixels += equalPixelCount + 1; |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Finds consecutive pixels, which have the same value starting from the pixel span offset 0.
|
|||
/// </summary>
|
|||
/// <typeparam name="TPixel">The pixel type.</typeparam>
|
|||
/// <param name="pixelSpan">The pixel span to search in.</param>
|
|||
/// <returns>The number of equal pixels.</returns>
|
|||
private byte FindEqualPixels<TPixel>(Span<TPixel> pixelSpan) |
|||
where TPixel : struct, IPixel<TPixel> |
|||
{ |
|||
int idx = 0; |
|||
byte equalPixelCount = 0; |
|||
while (equalPixelCount < 127 && idx < pixelSpan.Length - 1) |
|||
{ |
|||
TPixel currentPixel = pixelSpan[idx]; |
|||
TPixel nextPixel = pixelSpan[idx + 1]; |
|||
if (currentPixel.Equals(nextPixel)) |
|||
{ |
|||
equalPixelCount++; |
|||
} |
|||
else |
|||
{ |
|||
return equalPixelCount; |
|||
} |
|||
|
|||
idx++; |
|||
} |
|||
|
|||
return equalPixelCount; |
|||
} |
|||
|
|||
private IManagedByteBuffer AllocateRow(int width, int bytesPerPixel) => this.memoryAllocator.AllocatePaddedPixelRowBuffer(width, bytesPerPixel, 0); |
|||
|
|||
/// <summary>
|
|||
/// Writes the 8bit pixels uncompressed to the stream.
|
|||
/// </summary>
|
|||
/// <typeparam name="TPixel">The pixel format.</typeparam>
|
|||
/// <param name="stream">The <see cref="Stream"/> to write to.</param>
|
|||
/// <param name="pixels">The <see cref="Buffer2D{TPixel}"/> containing pixel data.</param>
|
|||
private void Write8Bit<TPixel>(Stream stream, Buffer2D<TPixel> pixels) |
|||
where TPixel : struct, IPixel<TPixel> |
|||
{ |
|||
using (IManagedByteBuffer row = this.AllocateRow(pixels.Width, 1)) |
|||
{ |
|||
for (int y = pixels.Height - 1; y >= 0; y--) |
|||
{ |
|||
Span<TPixel> pixelSpan = pixels.GetRowSpan(y); |
|||
PixelOperations<TPixel>.Instance.ToGray8Bytes( |
|||
this.configuration, |
|||
pixelSpan, |
|||
row.GetSpan(), |
|||
pixelSpan.Length); |
|||
stream.Write(row.Array, 0, row.Length()); |
|||
} |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Writes the 16bit pixels uncompressed to the stream.
|
|||
/// </summary>
|
|||
/// <typeparam name="TPixel">The pixel format.</typeparam>
|
|||
/// <param name="stream">The <see cref="Stream"/> to write to.</param>
|
|||
/// <param name="pixels">The <see cref="Buffer2D{TPixel}"/> containing pixel data.</param>
|
|||
private void Write16Bit<TPixel>(Stream stream, Buffer2D<TPixel> pixels) |
|||
where TPixel : struct, IPixel<TPixel> |
|||
{ |
|||
using (IManagedByteBuffer row = this.AllocateRow(pixels.Width, 2)) |
|||
{ |
|||
for (int y = pixels.Height - 1; y >= 0; y--) |
|||
{ |
|||
Span<TPixel> pixelSpan = pixels.GetRowSpan(y); |
|||
PixelOperations<TPixel>.Instance.ToBgra5551Bytes( |
|||
this.configuration, |
|||
pixelSpan, |
|||
row.GetSpan(), |
|||
pixelSpan.Length); |
|||
stream.Write(row.Array, 0, row.Length()); |
|||
} |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Writes the 24bit pixels uncompressed to the stream.
|
|||
/// </summary>
|
|||
/// <typeparam name="TPixel">The pixel format.</typeparam>
|
|||
/// <param name="stream">The <see cref="Stream"/> to write to.</param>
|
|||
/// <param name="pixels">The <see cref="Buffer2D{TPixel}"/> containing pixel data.</param>
|
|||
private void Write24Bit<TPixel>(Stream stream, Buffer2D<TPixel> pixels) |
|||
where TPixel : struct, IPixel<TPixel> |
|||
{ |
|||
using (IManagedByteBuffer row = this.AllocateRow(pixels.Width, 3)) |
|||
{ |
|||
for (int y = pixels.Height - 1; y >= 0; y--) |
|||
{ |
|||
Span<TPixel> pixelSpan = pixels.GetRowSpan(y); |
|||
PixelOperations<TPixel>.Instance.ToBgr24Bytes( |
|||
this.configuration, |
|||
pixelSpan, |
|||
row.GetSpan(), |
|||
pixelSpan.Length); |
|||
stream.Write(row.Array, 0, row.Length()); |
|||
} |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Writes the 32bit pixels uncompressed to the stream.
|
|||
/// </summary>
|
|||
/// <typeparam name="TPixel">The pixel format.</typeparam>
|
|||
/// <param name="stream">The <see cref="Stream"/> to write to.</param>
|
|||
/// <param name="pixels">The <see cref="Buffer2D{TPixel}"/> containing pixel data.</param>
|
|||
private void Write32Bit<TPixel>(Stream stream, Buffer2D<TPixel> pixels) |
|||
where TPixel : struct, IPixel<TPixel> |
|||
{ |
|||
using (IManagedByteBuffer row = this.AllocateRow(pixels.Width, 4)) |
|||
{ |
|||
for (int y = pixels.Height - 1; y >= 0; y--) |
|||
{ |
|||
Span<TPixel> pixelSpan = pixels.GetRowSpan(y); |
|||
PixelOperations<TPixel>.Instance.ToBgra32Bytes( |
|||
this.configuration, |
|||
pixelSpan, |
|||
row.GetSpan(), |
|||
pixelSpan.Length); |
|||
stream.Write(row.Array, 0, row.Length()); |
|||
} |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Convert the pixel values to grayscale using ITU-R Recommendation BT.709.
|
|||
/// </summary>
|
|||
/// <param name="sourcePixel">The pixel to get the luminance from.</param>
|
|||
[MethodImpl(InliningOptions.ShortMethod)] |
|||
public static int GetLuminance<TPixel>(TPixel sourcePixel) |
|||
where TPixel : struct, IPixel<TPixel> |
|||
{ |
|||
var vector = sourcePixel.ToVector4(); |
|||
return ImageMaths.GetBT709Luminance(ref vector, 256); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,147 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System; |
|||
using System.Runtime.CompilerServices; |
|||
using System.Runtime.InteropServices; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Tga |
|||
{ |
|||
/// <summary>
|
|||
/// This block of bytes tells the application detailed information about the targa image.
|
|||
/// <see href="https://www.fileformat.info/format/tga/egff.htm"/>
|
|||
/// </summary>
|
|||
[StructLayout(LayoutKind.Sequential, Pack = 1)] |
|||
internal readonly struct TgaFileHeader |
|||
{ |
|||
/// <summary>
|
|||
/// Defines the size of the data structure in the targa file.
|
|||
/// </summary>
|
|||
public const int Size = TgaConstants.FileHeaderLength; |
|||
|
|||
public TgaFileHeader( |
|||
byte idLength, |
|||
byte colorMapType, |
|||
TgaImageType imageType, |
|||
short cMapStart, |
|||
short cMapLength, |
|||
byte cMapDepth, |
|||
short xOffset, |
|||
short yOffset, |
|||
short width, |
|||
short height, |
|||
byte pixelDepth, |
|||
byte imageDescriptor) |
|||
{ |
|||
this.IdLength = idLength; |
|||
this.ColorMapType = colorMapType; |
|||
this.ImageType = imageType; |
|||
this.CMapStart = cMapStart; |
|||
this.CMapLength = cMapLength; |
|||
this.CMapDepth = cMapDepth; |
|||
this.XOffset = xOffset; |
|||
this.YOffset = yOffset; |
|||
this.Width = width; |
|||
this.Height = height; |
|||
this.PixelDepth = pixelDepth; |
|||
this.ImageDescriptor = imageDescriptor; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the id length.
|
|||
/// This field identifies the number of bytes contained in Field 6, the Image ID Field. The maximum number
|
|||
/// of characters is 255. A value of zero indicates that no Image ID field is included with the image.
|
|||
/// </summary>
|
|||
public byte IdLength { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the color map type.
|
|||
/// This field indicates the type of color map (if any) included with the image. There are currently 2 defined
|
|||
/// values for this field:
|
|||
/// 0 - indicates that no color-map data is included with this image.
|
|||
/// 1 - indicates that a color-map is included with this image.
|
|||
/// </summary>
|
|||
public byte ColorMapType { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the image type.
|
|||
/// The TGA File Format can be used to store Pseudo-Color, True-Color and Direct-Color images of various
|
|||
/// pixel depths.
|
|||
/// </summary>
|
|||
public TgaImageType ImageType { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the start of the color map.
|
|||
/// This field and its sub-fields describe the color map (if any) used for the image. If the Color Map Type field
|
|||
/// is set to zero, indicating that no color map exists, then these 5 bytes should be set to zero.
|
|||
/// </summary>
|
|||
public short CMapStart { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the total number of color map entries included.
|
|||
/// </summary>
|
|||
public short CMapLength { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the number of bits per entry. Typically 15, 16, 24 or 32-bit values are used.
|
|||
/// </summary>
|
|||
public byte CMapDepth { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the XOffset.
|
|||
/// These bytes specify the absolute horizontal coordinate for the lower left
|
|||
/// corner of the image as it is positioned on a display device having an
|
|||
/// origin at the lower left of the screen.
|
|||
/// </summary>
|
|||
public short XOffset { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the YOffset.
|
|||
/// These bytes specify the absolute vertical coordinate for the lower left
|
|||
/// corner of the image as it is positioned on a display device having an
|
|||
/// origin at the lower left of the screen.
|
|||
/// </summary>
|
|||
public short YOffset { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the width of the image in pixels.
|
|||
/// </summary>
|
|||
public short Width { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the height of the image in pixels.
|
|||
/// </summary>
|
|||
public short Height { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the number of bits per pixel. This number includes
|
|||
/// the Attribute or Alpha channel bits. Common values are 8, 16, 24 and
|
|||
/// 32 but other pixel depths could be used.
|
|||
/// </summary>
|
|||
public byte PixelDepth { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the ImageDescriptor.
|
|||
/// ImageDescriptor contains two pieces of information.
|
|||
/// Bits 0 through 3 contain the number of attribute bits per pixel.
|
|||
/// Attribute bits are found only in pixels for the 16- and 32-bit flavors of the TGA format and are called alpha channel,
|
|||
/// overlay, or interrupt bits. Bits 4 and 5 contain the image origin location (coordinate 0,0) of the image.
|
|||
/// This position may be any of the four corners of the display screen.
|
|||
/// When both of these bits are set to zero, the image origin is the lower-left corner of the screen.
|
|||
/// Bits 6 and 7 of the ImageDescriptor field are unused and should be set to 0.
|
|||
/// </summary>
|
|||
public byte ImageDescriptor { get; } |
|||
|
|||
public static TgaFileHeader Parse(Span<byte> data) |
|||
{ |
|||
return MemoryMarshal.Cast<byte, TgaFileHeader>(data)[0]; |
|||
} |
|||
|
|||
public void WriteTo(Span<byte> buffer) |
|||
{ |
|||
ref TgaFileHeader dest = ref Unsafe.As<byte, TgaFileHeader>(ref MemoryMarshal.GetReference(buffer)); |
|||
|
|||
dest = this; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,33 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System.Collections.Generic; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Tga |
|||
{ |
|||
/// <summary>
|
|||
/// Registers the image encoders, decoders and mime type detectors for the tga format.
|
|||
/// </summary>
|
|||
public sealed class TgaFormat : IImageFormat<TgaMetadata> |
|||
{ |
|||
/// <summary>
|
|||
/// Gets the current instance.
|
|||
/// </summary>
|
|||
public static TgaFormat Instance { get; } = new TgaFormat(); |
|||
|
|||
/// <inheritdoc/>
|
|||
public string Name => "TGA"; |
|||
|
|||
/// <inheritdoc/>
|
|||
public string DefaultMimeType => "image/tga"; |
|||
|
|||
/// <inheritdoc/>
|
|||
public IEnumerable<string> MimeTypes => TgaConstants.MimeTypes; |
|||
|
|||
/// <inheritdoc/>
|
|||
public IEnumerable<string> FileExtensions => TgaConstants.FileExtensions; |
|||
|
|||
/// <inheritdoc/>
|
|||
public TgaMetadata CreateDefaultFormatMetadata() => new TgaMetadata(); |
|||
} |
|||
} |
|||
@ -0,0 +1,40 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Tga |
|||
{ |
|||
/// <summary>
|
|||
/// Detects tga file headers.
|
|||
/// </summary>
|
|||
public sealed class TgaImageFormatDetector : IImageFormatDetector |
|||
{ |
|||
/// <inheritdoc/>
|
|||
public int HeaderSize => TgaConstants.FileHeaderLength; |
|||
|
|||
/// <inheritdoc/>
|
|||
public IImageFormat DetectFormat(ReadOnlySpan<byte> header) |
|||
{ |
|||
return this.IsSupportedFileFormat(header) ? TgaFormat.Instance : null; |
|||
} |
|||
|
|||
private bool IsSupportedFileFormat(ReadOnlySpan<byte> header) |
|||
{ |
|||
if (header.Length >= this.HeaderSize) |
|||
{ |
|||
// There is no magick bytes in a tga file, so at least the image type
|
|||
// and the colormap type in the header will be checked for a valid value.
|
|||
if (header[1] != 0 && header[1] != 1) |
|||
{ |
|||
return false; |
|||
} |
|||
|
|||
var imageType = (TgaImageType)header[2]; |
|||
return imageType.IsValid(); |
|||
} |
|||
|
|||
return true; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,48 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
namespace SixLabors. |
|||
ImageSharp.Formats.Tga |
|||
{ |
|||
/// <summary>
|
|||
/// Defines the tga image type. The TGA File Format can be used to store Pseudo-Color,
|
|||
/// True-Color and Direct-Color images of various pixel depths.
|
|||
/// </summary>
|
|||
public enum TgaImageType : byte |
|||
{ |
|||
/// <summary>
|
|||
/// No image data included.
|
|||
/// </summary>
|
|||
NoImageData = 0, |
|||
|
|||
/// <summary>
|
|||
/// Uncompressed, color mapped image.
|
|||
/// </summary>
|
|||
ColorMapped = 1, |
|||
|
|||
/// <summary>
|
|||
/// Uncompressed true color image.
|
|||
/// </summary>
|
|||
TrueColor = 2, |
|||
|
|||
/// <summary>
|
|||
/// Uncompressed Black and white (grayscale) image.
|
|||
/// </summary>
|
|||
BlackAndWhite = 3, |
|||
|
|||
/// <summary>
|
|||
/// Run length encoded, color mapped image.
|
|||
/// </summary>
|
|||
RleColorMapped = 9, |
|||
|
|||
/// <summary>
|
|||
/// Run length encoded, true color image.
|
|||
/// </summary>
|
|||
RleTrueColor = 10, |
|||
|
|||
/// <summary>
|
|||
/// Run length encoded, black and white (grayscale) image.
|
|||
/// </summary>
|
|||
RleBlackAndWhite = 11, |
|||
} |
|||
} |
|||
@ -0,0 +1,49 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Tga |
|||
{ |
|||
/// <summary>
|
|||
/// Extension methods for TgaImageType enum.
|
|||
/// </summary>
|
|||
public static class TgaImageTypeExtensions |
|||
{ |
|||
/// <summary>
|
|||
/// Checks if this tga image type is run length encoded.
|
|||
/// </summary>
|
|||
/// <param name="imageType">The tga image type.</param>
|
|||
/// <returns>True, if this image type is run length encoded, otherwise false.</returns>
|
|||
public static bool IsRunLengthEncoded(this TgaImageType imageType) |
|||
{ |
|||
if (imageType is TgaImageType.RleColorMapped || imageType is TgaImageType.RleBlackAndWhite || imageType is TgaImageType.RleTrueColor) |
|||
{ |
|||
return true; |
|||
} |
|||
|
|||
return false; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Checks, if the image type has valid value.
|
|||
/// </summary>
|
|||
/// <param name="imageType">The image type.</param>
|
|||
/// <returns>true, if its a valid tga image type.</returns>
|
|||
public static bool IsValid(this TgaImageType imageType) |
|||
{ |
|||
switch (imageType) |
|||
{ |
|||
case TgaImageType.NoImageData: |
|||
case TgaImageType.ColorMapped: |
|||
case TgaImageType.TrueColor: |
|||
case TgaImageType.BlackAndWhite: |
|||
case TgaImageType.RleColorMapped: |
|||
case TgaImageType.RleTrueColor: |
|||
case TgaImageType.RleBlackAndWhite: |
|||
return true; |
|||
|
|||
default: |
|||
return false; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,35 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Tga |
|||
{ |
|||
/// <summary>
|
|||
/// Provides TGA specific metadata information for the image.
|
|||
/// </summary>
|
|||
public class TgaMetadata : IDeepCloneable |
|||
{ |
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="TgaMetadata"/> class.
|
|||
/// </summary>
|
|||
public TgaMetadata() |
|||
{ |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="TgaMetadata"/> class.
|
|||
/// </summary>
|
|||
/// <param name="other">The metadata to create an instance from.</param>
|
|||
private TgaMetadata(TgaMetadata other) |
|||
{ |
|||
this.BitsPerPixel = other.BitsPerPixel; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the number of bits per pixel.
|
|||
/// </summary>
|
|||
public TgaBitsPerPixel BitsPerPixel { get; set; } = TgaBitsPerPixel.Pixel24; |
|||
|
|||
/// <inheritdoc/>
|
|||
public IDeepCloneable DeepClone() => new TgaMetadata(this); |
|||
} |
|||
} |
|||
@ -0,0 +1,31 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System; |
|||
using System.Runtime.CompilerServices; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Tga |
|||
{ |
|||
internal static class TgaThrowHelper |
|||
{ |
|||
/// <summary>
|
|||
/// Cold path optimization for throwing <see cref="ImageFormatException"/>-s
|
|||
/// </summary>
|
|||
/// <param name="errorMessage">The error message for the exception.</param>
|
|||
[MethodImpl(MethodImplOptions.NoInlining)] |
|||
public static void ThrowImageFormatException(string errorMessage) |
|||
{ |
|||
throw new ImageFormatException(errorMessage); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Cold path optimization for throwing <see cref="NotSupportedException"/>-s
|
|||
/// </summary>
|
|||
/// <param name="errorMessage">The error message for the exception.</param>
|
|||
[MethodImpl(MethodImplOptions.NoInlining)] |
|||
public static void ThrowNotSupportedException(string errorMessage) |
|||
{ |
|||
throw new NotSupportedException(errorMessage); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,42 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System.IO; |
|||
|
|||
using BenchmarkDotNet.Attributes; |
|||
|
|||
using ImageMagick; |
|||
|
|||
using SixLabors.ImageSharp.PixelFormats; |
|||
using SixLabors.ImageSharp.Tests; |
|||
using SixLabors.Primitives; |
|||
|
|||
namespace SixLabors.ImageSharp.Benchmarks.Codecs |
|||
{ |
|||
[Config(typeof(Config.ShortClr))] |
|||
public class DecodeTga : BenchmarkBase |
|||
{ |
|||
private string TestImageFullPath => Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, this.TestImage); |
|||
|
|||
[Params(TestImages.Tga.Bit24)] |
|||
public string TestImage { get; set; } |
|||
|
|||
[Benchmark(Baseline = true, Description = "ImageMagick Tga")] |
|||
public Size TgaImageMagick() |
|||
{ |
|||
using (var magickImage = new MagickImage(this.TestImageFullPath)) |
|||
{ |
|||
return new Size(magickImage.Width, magickImage.Height); |
|||
} |
|||
} |
|||
|
|||
[Benchmark(Description = "ImageSharp Tga")] |
|||
public Size TgaCore() |
|||
{ |
|||
using (var image = Image.Load<Rgba32>(this.TestImageFullPath)) |
|||
{ |
|||
return new Size(image.Width, image.Height); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,54 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System.IO; |
|||
|
|||
using BenchmarkDotNet.Attributes; |
|||
|
|||
using ImageMagick; |
|||
|
|||
using SixLabors.ImageSharp.PixelFormats; |
|||
using SixLabors.ImageSharp.Tests; |
|||
|
|||
namespace SixLabors.ImageSharp.Benchmarks.Codecs |
|||
{ |
|||
[Config(typeof(Config.ShortClr))] |
|||
public class EncodeTga : BenchmarkBase |
|||
{ |
|||
private MagickImage tgaMagick; |
|||
private Image<Rgba32> tgaCore; |
|||
|
|||
private string TestImageFullPath => Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, this.TestImage); |
|||
|
|||
[Params(TestImages.Tga.Bit24)] |
|||
public string TestImage { get; set; } |
|||
|
|||
[GlobalSetup] |
|||
public void ReadImages() |
|||
{ |
|||
if (this.tgaCore == null) |
|||
{ |
|||
this.tgaCore = Image.Load<Rgba32>(TestImageFullPath); |
|||
this.tgaMagick = new MagickImage(this.TestImageFullPath); |
|||
} |
|||
} |
|||
|
|||
[Benchmark(Baseline = true, Description = "Magick Tga")] |
|||
public void BmpSystemDrawing() |
|||
{ |
|||
using (var memoryStream = new MemoryStream()) |
|||
{ |
|||
this.tgaMagick.Write(memoryStream, MagickFormat.Tga); |
|||
} |
|||
} |
|||
|
|||
[Benchmark(Description = "ImageSharp Tga")] |
|||
public void BmpCore() |
|||
{ |
|||
using (var memoryStream = new MemoryStream()) |
|||
{ |
|||
this.tgaCore.SaveAsBmp(memoryStream); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,197 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
// ReSharper disable InconsistentNaming
|
|||
|
|||
using SixLabors.ImageSharp.Formats.Tga; |
|||
using SixLabors.ImageSharp.PixelFormats; |
|||
|
|||
using Xunit; |
|||
|
|||
namespace SixLabors.ImageSharp.Tests.Formats.Tga |
|||
{ |
|||
using static TestImages.Tga; |
|||
|
|||
public class TgaDecoderTests |
|||
{ |
|||
[Theory] |
|||
[WithFile(Grey, PixelTypes.Rgba32)] |
|||
public void TgaDecoder_CanDecode_Uncompressed_MonoChrome<TPixel>(TestImageProvider<TPixel> provider) |
|||
where TPixel : struct, IPixel<TPixel> |
|||
{ |
|||
using (Image<TPixel> image = provider.GetImage(new TgaDecoder())) |
|||
{ |
|||
image.DebugSave(provider); |
|||
TgaTestUtils.CompareWithReferenceDecoder(provider, image); |
|||
} |
|||
} |
|||
|
|||
[Theory] |
|||
[WithFile(Bit15, PixelTypes.Rgba32)] |
|||
public void TgaDecoder_CanDecode_Uncompressed_15Bit<TPixel>(TestImageProvider<TPixel> provider) |
|||
where TPixel : struct, IPixel<TPixel> |
|||
{ |
|||
using (Image<TPixel> image = provider.GetImage(new TgaDecoder())) |
|||
{ |
|||
image.DebugSave(provider); |
|||
TgaTestUtils.CompareWithReferenceDecoder(provider, image); |
|||
} |
|||
} |
|||
|
|||
[Theory] |
|||
[WithFile(Bit15Rle, PixelTypes.Rgba32)] |
|||
public void TgaDecoder_CanDecode_RunLengthEncoded_15Bit<TPixel>(TestImageProvider<TPixel> provider) |
|||
where TPixel : struct, IPixel<TPixel> |
|||
{ |
|||
using (Image<TPixel> image = provider.GetImage(new TgaDecoder())) |
|||
{ |
|||
image.DebugSave(provider); |
|||
TgaTestUtils.CompareWithReferenceDecoder(provider, image); |
|||
} |
|||
} |
|||
|
|||
[Theory] |
|||
[WithFile(Bit16, PixelTypes.Rgba32)] |
|||
public void TgaDecoder_CanDecode_Uncompressed_16Bit<TPixel>(TestImageProvider<TPixel> provider) |
|||
where TPixel : struct, IPixel<TPixel> |
|||
{ |
|||
using (Image<TPixel> image = provider.GetImage(new TgaDecoder())) |
|||
{ |
|||
image.DebugSave(provider); |
|||
TgaTestUtils.CompareWithReferenceDecoder(provider, image); |
|||
} |
|||
} |
|||
|
|||
[Theory] |
|||
[WithFile(Bit16PalRle, PixelTypes.Rgba32)] |
|||
public void TgaDecoder_CanDecode_RunLengthEncoded_WithPalette_16Bit<TPixel>(TestImageProvider<TPixel> provider) |
|||
where TPixel : struct, IPixel<TPixel> |
|||
{ |
|||
using (Image<TPixel> image = provider.GetImage(new TgaDecoder())) |
|||
{ |
|||
image.DebugSave(provider); |
|||
TgaTestUtils.CompareWithReferenceDecoder(provider, image); |
|||
} |
|||
} |
|||
|
|||
[Theory] |
|||
[WithFile(Bit24, PixelTypes.Rgba32)] |
|||
public void TgaDecoder_CanDecode_Uncompressed_24Bit<TPixel>(TestImageProvider<TPixel> provider) |
|||
where TPixel : struct, IPixel<TPixel> |
|||
{ |
|||
using (Image<TPixel> image = provider.GetImage(new TgaDecoder())) |
|||
{ |
|||
image.DebugSave(provider); |
|||
TgaTestUtils.CompareWithReferenceDecoder(provider, image); |
|||
} |
|||
} |
|||
|
|||
[Theory] |
|||
[WithFile(Bit24RleTopLeft, PixelTypes.Rgba32)] |
|||
public void TgaDecoder_CanDecode_RunLengthEncoded_WithTopLeftOrigin_24Bit<TPixel>(TestImageProvider<TPixel> provider) |
|||
where TPixel : struct, IPixel<TPixel> |
|||
{ |
|||
using (Image<TPixel> image = provider.GetImage(new TgaDecoder())) |
|||
{ |
|||
image.DebugSave(provider); |
|||
TgaTestUtils.CompareWithReferenceDecoder(provider, image); |
|||
} |
|||
} |
|||
|
|||
[Theory] |
|||
[WithFile(Bit24TopLeft, PixelTypes.Rgba32)] |
|||
public void TgaDecoder_CanDecode_Palette_WithTopLeftOrigin_24Bit<TPixel>(TestImageProvider<TPixel> provider) |
|||
where TPixel : struct, IPixel<TPixel> |
|||
{ |
|||
using (Image<TPixel> image = provider.GetImage(new TgaDecoder())) |
|||
{ |
|||
image.DebugSave(provider); |
|||
TgaTestUtils.CompareWithReferenceDecoder(provider, image); |
|||
} |
|||
} |
|||
|
|||
[Theory] |
|||
[WithFile(Bit32, PixelTypes.Rgba32)] |
|||
public void TgaDecoder_CanDecode_Uncompressed_32Bit<TPixel>(TestImageProvider<TPixel> provider) |
|||
where TPixel : struct, IPixel<TPixel> |
|||
{ |
|||
using (Image<TPixel> image = provider.GetImage(new TgaDecoder())) |
|||
{ |
|||
image.DebugSave(provider); |
|||
TgaTestUtils.CompareWithReferenceDecoder(provider, image); |
|||
} |
|||
} |
|||
|
|||
[Theory] |
|||
[WithFile(GreyRle, PixelTypes.Rgba32)] |
|||
public void TgaDecoder_CanDecode_RunLengthEncoded_MonoChrome<TPixel>(TestImageProvider<TPixel> provider) |
|||
where TPixel : struct, IPixel<TPixel> |
|||
{ |
|||
using (Image<TPixel> image = provider.GetImage(new TgaDecoder())) |
|||
{ |
|||
image.DebugSave(provider); |
|||
TgaTestUtils.CompareWithReferenceDecoder(provider, image); |
|||
} |
|||
} |
|||
|
|||
[Theory] |
|||
[WithFile(Bit16Rle, PixelTypes.Rgba32)] |
|||
public void TgaDecoder_CanDecode_RunLengthEncoded_16Bit<TPixel>(TestImageProvider<TPixel> provider) |
|||
where TPixel : struct, IPixel<TPixel> |
|||
{ |
|||
using (Image<TPixel> image = provider.GetImage(new TgaDecoder())) |
|||
{ |
|||
image.DebugSave(provider); |
|||
TgaTestUtils.CompareWithReferenceDecoder(provider, image); |
|||
} |
|||
} |
|||
|
|||
[Theory] |
|||
[WithFile(Bit24Rle, PixelTypes.Rgba32)] |
|||
public void TgaDecoder_CanDecode_RunLengthEncoded_24Bit<TPixel>(TestImageProvider<TPixel> provider) |
|||
where TPixel : struct, IPixel<TPixel> |
|||
{ |
|||
using (Image<TPixel> image = provider.GetImage(new TgaDecoder())) |
|||
{ |
|||
image.DebugSave(provider); |
|||
TgaTestUtils.CompareWithReferenceDecoder(provider, image); |
|||
} |
|||
} |
|||
|
|||
[Theory] |
|||
[WithFile(Bit32Rle, PixelTypes.Rgba32)] |
|||
public void TgaDecoder_CanDecode_RunLengthEncoded_32Bit<TPixel>(TestImageProvider<TPixel> provider) |
|||
where TPixel : struct, IPixel<TPixel> |
|||
{ |
|||
using (Image<TPixel> image = provider.GetImage(new TgaDecoder())) |
|||
{ |
|||
image.DebugSave(provider); |
|||
TgaTestUtils.CompareWithReferenceDecoder(provider, image); |
|||
} |
|||
} |
|||
|
|||
[Theory] |
|||
[WithFile(Bit16Pal, PixelTypes.Rgba32)] |
|||
public void TgaDecoder_CanDecode_WithPalette_16Bit<TPixel>(TestImageProvider<TPixel> provider) |
|||
where TPixel : struct, IPixel<TPixel> |
|||
{ |
|||
using (Image<TPixel> image = provider.GetImage(new TgaDecoder())) |
|||
{ |
|||
image.DebugSave(provider); |
|||
TgaTestUtils.CompareWithReferenceDecoder(provider, image); |
|||
} |
|||
} |
|||
|
|||
[Theory] |
|||
[WithFile(Bit24Pal, PixelTypes.Rgba32)] |
|||
public void TgaDecoder_CanDecode_WithPalette_24Bit<TPixel>(TestImageProvider<TPixel> provider) |
|||
where TPixel : struct, IPixel<TPixel> |
|||
{ |
|||
using (Image<TPixel> image = provider.GetImage(new TgaDecoder())) |
|||
{ |
|||
image.DebugSave(provider); |
|||
TgaTestUtils.CompareWithReferenceDecoder(provider, image); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,148 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
// ReSharper disable InconsistentNaming
|
|||
|
|||
using System.IO; |
|||
|
|||
using SixLabors.ImageSharp.Formats.Tga; |
|||
using SixLabors.ImageSharp.PixelFormats; |
|||
|
|||
using Xunit; |
|||
|
|||
namespace SixLabors.ImageSharp.Tests.Formats.Tga |
|||
{ |
|||
using static TestImages.Tga; |
|||
|
|||
public class TgaEncoderTests |
|||
{ |
|||
public static readonly TheoryData<TgaBitsPerPixel> BitsPerPixel = |
|||
new TheoryData<TgaBitsPerPixel> |
|||
{ |
|||
TgaBitsPerPixel.Pixel24, |
|||
TgaBitsPerPixel.Pixel32 |
|||
}; |
|||
|
|||
public static readonly TheoryData<string, TgaBitsPerPixel> TgaBitsPerPixelFiles = |
|||
new TheoryData<string, TgaBitsPerPixel> |
|||
{ |
|||
{ Grey, TgaBitsPerPixel.Pixel8 }, |
|||
{ Bit32, TgaBitsPerPixel.Pixel32 }, |
|||
{ Bit24, TgaBitsPerPixel.Pixel24 }, |
|||
{ Bit16, TgaBitsPerPixel.Pixel16 }, |
|||
}; |
|||
|
|||
[Theory] |
|||
[MemberData(nameof(TgaBitsPerPixelFiles))] |
|||
public void Encode_PreserveBitsPerPixel(string imagePath, TgaBitsPerPixel bmpBitsPerPixel) |
|||
{ |
|||
var options = new TgaEncoder(); |
|||
|
|||
TestFile testFile = TestFile.Create(imagePath); |
|||
using (Image<Rgba32> input = testFile.CreateRgba32Image()) |
|||
{ |
|||
using (var memStream = new MemoryStream()) |
|||
{ |
|||
input.Save(memStream, options); |
|||
memStream.Position = 0; |
|||
using (Image<Rgba32> output = Image.Load<Rgba32>(memStream)) |
|||
{ |
|||
TgaMetadata meta = output.Metadata.GetFormatMetadata(TgaFormat.Instance); |
|||
Assert.Equal(bmpBitsPerPixel, meta.BitsPerPixel); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
[Theory] |
|||
[MemberData(nameof(TgaBitsPerPixelFiles))] |
|||
public void Encode_WithCompression_PreserveBitsPerPixel(string imagePath, TgaBitsPerPixel bmpBitsPerPixel) |
|||
{ |
|||
var options = new TgaEncoder() |
|||
{ |
|||
Compression = TgaCompression.RunLength |
|||
}; |
|||
|
|||
TestFile testFile = TestFile.Create(imagePath); |
|||
using (Image<Rgba32> input = testFile.CreateRgba32Image()) |
|||
{ |
|||
using (var memStream = new MemoryStream()) |
|||
{ |
|||
input.Save(memStream, options); |
|||
memStream.Position = 0; |
|||
using (Image<Rgba32> output = Image.Load<Rgba32>(memStream)) |
|||
{ |
|||
TgaMetadata meta = output.Metadata.GetFormatMetadata(TgaFormat.Instance); |
|||
Assert.Equal(bmpBitsPerPixel, meta.BitsPerPixel); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
[Theory] |
|||
[WithFile(Bit32, PixelTypes.Rgba32)] |
|||
public void Encode_Bit8_Works<TPixel>(TestImageProvider<TPixel> provider, TgaBitsPerPixel bitsPerPixel = TgaBitsPerPixel.Pixel8) |
|||
// using tolerant comparer here. The results from magick differ slightly. Maybe a different ToGrey method is used. The image looks otherwise ok.
|
|||
where TPixel : struct, IPixel<TPixel> => TestTgaEncoderCore(provider, bitsPerPixel, TgaCompression.None, useExactComparer: false, compareTolerance: 0.03f); |
|||
|
|||
[Theory] |
|||
[WithFile(Bit32, PixelTypes.Rgba32)] |
|||
public void Encode_Bit16_Works<TPixel>(TestImageProvider<TPixel> provider, TgaBitsPerPixel bitsPerPixel = TgaBitsPerPixel.Pixel16) |
|||
where TPixel : struct, IPixel<TPixel> => TestTgaEncoderCore(provider, bitsPerPixel, TgaCompression.None, useExactComparer: false); |
|||
|
|||
[Theory] |
|||
[WithFile(Bit32, PixelTypes.Rgba32)] |
|||
public void Encode_Bit24_Works<TPixel>(TestImageProvider<TPixel> provider, TgaBitsPerPixel bitsPerPixel = TgaBitsPerPixel.Pixel24) |
|||
where TPixel : struct, IPixel<TPixel> => TestTgaEncoderCore(provider, bitsPerPixel, TgaCompression.None); |
|||
|
|||
[Theory] |
|||
[WithFile(Bit32, PixelTypes.Rgba32)] |
|||
public void Encode_Bit32_Works<TPixel>(TestImageProvider<TPixel> provider, TgaBitsPerPixel bitsPerPixel = TgaBitsPerPixel.Pixel32) |
|||
where TPixel : struct, IPixel<TPixel> => TestTgaEncoderCore(provider, bitsPerPixel, TgaCompression.None); |
|||
|
|||
[Theory] |
|||
[WithFile(Bit32, PixelTypes.Rgba32)] |
|||
public void Encode_Bit8_WithRunLengthEncoding_Works<TPixel>(TestImageProvider<TPixel> provider, TgaBitsPerPixel bitsPerPixel = TgaBitsPerPixel.Pixel8) |
|||
// using tolerant comparer here. The results from magick differ slightly. Maybe a different ToGrey method is used. The image looks otherwise ok.
|
|||
where TPixel : struct, IPixel<TPixel> => TestTgaEncoderCore(provider, bitsPerPixel, TgaCompression.RunLength, useExactComparer: false, compareTolerance: 0.03f); |
|||
|
|||
[Theory] |
|||
[WithFile(Bit32, PixelTypes.Rgba32)] |
|||
public void Encode_Bit16_WithRunLengthEncoding_Works<TPixel>(TestImageProvider<TPixel> provider, TgaBitsPerPixel bitsPerPixel = TgaBitsPerPixel.Pixel16) |
|||
where TPixel : struct, IPixel<TPixel> => TestTgaEncoderCore(provider, bitsPerPixel, TgaCompression.RunLength, useExactComparer: false); |
|||
|
|||
[Theory] |
|||
[WithFile(Bit32, PixelTypes.Rgba32)] |
|||
public void Encode_Bit24_WithRunLengthEncoding_Works<TPixel>(TestImageProvider<TPixel> provider, TgaBitsPerPixel bitsPerPixel = TgaBitsPerPixel.Pixel24) |
|||
where TPixel : struct, IPixel<TPixel> => TestTgaEncoderCore(provider, bitsPerPixel, TgaCompression.RunLength); |
|||
|
|||
[Theory] |
|||
[WithFile(Bit32, PixelTypes.Rgba32)] |
|||
public void Encode_Bit32_WithRunLengthEncoding_Works<TPixel>(TestImageProvider<TPixel> provider, TgaBitsPerPixel bitsPerPixel = TgaBitsPerPixel.Pixel32) |
|||
where TPixel : struct, IPixel<TPixel> => TestTgaEncoderCore(provider, bitsPerPixel, TgaCompression.RunLength); |
|||
|
|||
private static void TestTgaEncoderCore<TPixel>( |
|||
TestImageProvider<TPixel> provider, |
|||
TgaBitsPerPixel bitsPerPixel, |
|||
TgaCompression compression = TgaCompression.None, |
|||
bool useExactComparer = true, |
|||
float compareTolerance = 0.01f) |
|||
where TPixel : struct, IPixel<TPixel> |
|||
{ |
|||
using (Image<TPixel> image = provider.GetImage()) |
|||
{ |
|||
var encoder = new TgaEncoder { BitsPerPixel = bitsPerPixel, Compression = compression}; |
|||
|
|||
using (var memStream = new MemoryStream()) |
|||
{ |
|||
image.Save(memStream, encoder); |
|||
memStream.Position = 0; |
|||
using (var encodedImage = (Image<TPixel>)Image.Load(memStream)) |
|||
{ |
|||
TgaTestUtils.CompareWithReferenceDecoder(provider, encodedImage, useExactComparer, compareTolerance); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,33 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System.IO; |
|||
|
|||
using SixLabors.ImageSharp.Formats; |
|||
|
|||
using Xunit; |
|||
|
|||
namespace SixLabors.ImageSharp.Tests.Formats.Tga |
|||
{ |
|||
public class TgaFileHeaderTests |
|||
{ |
|||
private static readonly byte[] Data = { |
|||
0, |
|||
0, |
|||
15 // invalid tga image type
|
|||
}; |
|||
|
|||
private MemoryStream Stream { get; } = new MemoryStream(Data); |
|||
|
|||
[Fact] |
|||
public void ImageLoad_WithInvalidImageType_Throws_UnknownImageFormatException() |
|||
{ |
|||
Assert.Throws<UnknownImageFormatException>(() => |
|||
{ |
|||
using (Image.Load(Configuration.Default, this.Stream, out IImageFormat _)) |
|||
{ |
|||
} |
|||
}); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,61 @@ |
|||
using System; |
|||
using System.IO; |
|||
|
|||
using ImageMagick; |
|||
|
|||
using SixLabors.ImageSharp.Advanced; |
|||
using SixLabors.ImageSharp.PixelFormats; |
|||
using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; |
|||
|
|||
namespace SixLabors.ImageSharp.Tests.Formats.Tga |
|||
{ |
|||
public static class TgaTestUtils |
|||
{ |
|||
public static void CompareWithReferenceDecoder<TPixel>(TestImageProvider<TPixel> provider, |
|||
Image<TPixel> image, |
|||
bool useExactComparer = true, |
|||
float compareTolerance = 0.01f) |
|||
where TPixel : struct, IPixel<TPixel> |
|||
{ |
|||
string path = TestImageProvider<TPixel>.GetFilePathOrNull(provider); |
|||
if (path == null) |
|||
{ |
|||
throw new InvalidOperationException("CompareToOriginal() works only with file providers!"); |
|||
} |
|||
|
|||
TestFile testFile = TestFile.Create(path); |
|||
Image<Rgba32> magickImage = DecodeWithMagick<Rgba32>(Configuration.Default, new FileInfo(testFile.FullPath)); |
|||
if (useExactComparer) |
|||
{ |
|||
ImageComparer.Exact.VerifySimilarity(magickImage, image); |
|||
} |
|||
else |
|||
{ |
|||
ImageComparer.Tolerant(compareTolerance).VerifySimilarity(magickImage, image); |
|||
} |
|||
} |
|||
|
|||
public static Image<TPixel> DecodeWithMagick<TPixel>(Configuration configuration, FileInfo fileInfo) |
|||
where TPixel : struct, IPixel<TPixel> |
|||
{ |
|||
using (var magickImage = new MagickImage(fileInfo)) |
|||
{ |
|||
var result = new Image<TPixel>(configuration, magickImage.Width, magickImage.Height); |
|||
Span<TPixel> resultPixels = result.GetPixelSpan(); |
|||
|
|||
using (IPixelCollection pixels = magickImage.GetPixelsUnsafe()) |
|||
{ |
|||
byte[] data = pixels.ToByteArray(PixelMapping.RGBA); |
|||
|
|||
PixelOperations<TPixel>.Instance.FromRgba32Bytes( |
|||
configuration, |
|||
data, |
|||
resultPixels, |
|||
resultPixels.Length); |
|||
} |
|||
|
|||
return result; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -1 +1 @@ |
|||
Subproject commit f0c4033667bd23ad9dde82ccb625c232d402ee05 |
|||
Subproject commit ca4cf8318fe4d09f0fc825686dcd477ebfb5e3e5 |
|||
Binary file not shown.
|
After Width: | Height: | Size: 79 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 104 KiB |
|
After Width: | Height: | Size: 69 KiB |
Binary file not shown.
Binary file not shown.
|
After Width: | Height: | Size: 103 KiB |
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
After Width: | Height: | Size: 138 KiB |
Binary file not shown.
Binary file not shown.
Binary file not shown.
Loading…
Reference in new issue