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