Browse Source

Merge branch 'master' into js/managed-zlib

pull/1054/head
James Jackson-South 6 years ago
parent
commit
ccedaba841
  1. 1
      .gitattributes
  2. 2
      Directory.Build.targets
  3. 17
      src/ImageSharp/Common/Helpers/ImageMaths.cs
  4. 5
      src/ImageSharp/Configuration.cs
  5. 6
      src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs
  6. 6
      src/ImageSharp/Formats/README.md
  7. 12
      src/ImageSharp/Formats/Tga/ITgaDecoderOptions.cs
  8. 21
      src/ImageSharp/Formats/Tga/ITgaEncoderOptions.cs
  9. BIN
      src/ImageSharp/Formats/Tga/TGA_Specification.pdf
  10. 31
      src/ImageSharp/Formats/Tga/TgaBitsPerPixel.cs
  11. 21
      src/ImageSharp/Formats/Tga/TgaCompression.cs
  12. 19
      src/ImageSharp/Formats/Tga/TgaConfigurationModule.cs
  13. 25
      src/ImageSharp/Formats/Tga/TgaConstants.cs
  14. 34
      src/ImageSharp/Formats/Tga/TgaDecoder.cs
  15. 588
      src/ImageSharp/Formats/Tga/TgaDecoderCore.cs
  16. 34
      src/ImageSharp/Formats/Tga/TgaEncoder.cs
  17. 348
      src/ImageSharp/Formats/Tga/TgaEncoderCore.cs
  18. 147
      src/ImageSharp/Formats/Tga/TgaFileHeader.cs
  19. 33
      src/ImageSharp/Formats/Tga/TgaFormat.cs
  20. 40
      src/ImageSharp/Formats/Tga/TgaImageFormatDetector.cs
  21. 48
      src/ImageSharp/Formats/Tga/TgaImageType.cs
  22. 49
      src/ImageSharp/Formats/Tga/TgaImageTypeExtensions.cs
  23. 35
      src/ImageSharp/Formats/Tga/TgaMetadata.cs
  24. 31
      src/ImageSharp/Formats/Tga/TgaThrowHelper.cs
  25. 4
      src/ImageSharp/PixelFormats/PixelOperations{TPixel}.Generated.tt
  26. 4
      src/ImageSharp/Processing/Processors/Normalization/AdaptiveHistogramEqualizationSlidingWindowProcessor{TPixel}.cs
  27. 11
      src/ImageSharp/Processing/Processors/Normalization/HistogramEqualizationProcessor{TPixel}.cs
  28. 42
      tests/ImageSharp.Benchmarks/Codecs/DecodeTga.cs
  29. 54
      tests/ImageSharp.Benchmarks/Codecs/EncodeTga.cs
  30. 1
      tests/ImageSharp.Benchmarks/ImageSharp.Benchmarks.csproj
  31. 11
      tests/ImageSharp.Tests/ConfigurationTests.cs
  32. 7
      tests/ImageSharp.Tests/Formats/Bmp/BmpFileHeaderTests.cs
  33. 2
      tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs
  34. 197
      tests/ImageSharp.Tests/Formats/Tga/TgaDecoderTests.cs
  35. 148
      tests/ImageSharp.Tests/Formats/Tga/TgaEncoderTests.cs
  36. 33
      tests/ImageSharp.Tests/Formats/Tga/TgaFileHeaderTests.cs
  37. 61
      tests/ImageSharp.Tests/Formats/Tga/TgaTestUtils.cs
  38. 24
      tests/ImageSharp.Tests/Helpers/ImageMathsTests.cs
  39. 19
      tests/ImageSharp.Tests/TestImages.cs
  40. 6
      tests/ImageSharp.Tests/TestUtilities/TestEnvironment.Formats.cs
  41. 2
      tests/Images/External
  42. BIN
      tests/Images/Input/Tga/ccm8.tga
  43. BIN
      tests/Images/Input/Tga/rgb15.tga
  44. BIN
      tests/Images/Input/Tga/rgb15rle.tga
  45. BIN
      tests/Images/Input/Tga/targa.png
  46. BIN
      tests/Images/Input/Tga/targa_16bit.tga
  47. BIN
      tests/Images/Input/Tga/targa_16bit_pal.tga
  48. BIN
      tests/Images/Input/Tga/targa_16bit_rle.tga
  49. BIN
      tests/Images/Input/Tga/targa_24bit.tga
  50. BIN
      tests/Images/Input/Tga/targa_24bit_pal.tga
  51. BIN
      tests/Images/Input/Tga/targa_24bit_pal_origin_topleft.tga
  52. BIN
      tests/Images/Input/Tga/targa_24bit_rle.tga
  53. BIN
      tests/Images/Input/Tga/targa_24bit_rle_origin_topleft.tga
  54. BIN
      tests/Images/Input/Tga/targa_32bit.tga
  55. BIN
      tests/Images/Input/Tga/targa_32bit_rle.tga
  56. BIN
      tests/Images/Input/Tga/targa_8bit.tga
  57. BIN
      tests/Images/Input/Tga/targa_8bit_rle.tga

1
.gitattributes

@ -93,6 +93,7 @@
*.gif binary
*.jpg binary
*.png binary
*.tga binary
*.ttf binary
*.snk binary

2
Directory.Build.targets

@ -24,7 +24,7 @@
<ItemGroup>
<PackageReference Update="BenchmarkDotNet" Version="0.11.5" />
<PackageReference Update="Colourful" Version="2.0.2" />
<PackageReference Update="Magick.NET-Q16-AnyCPU" Version="7.12.0" />
<PackageReference Update="Magick.NET-Q16-AnyCPU" Version="7.14.4" />
<PackageReference Update="Microsoft.Net.Compilers.Toolset" Version="3.3.1" />
<PackageReference Update="Microsoft.NET.Test.Sdk" Version="15.9.0" />
<PackageReference Update="Moq" Version="4.10.0" />

17
src/ImageSharp/Common/Helpers/ImageMaths.cs

@ -1,7 +1,8 @@
// Copyright (c) Six Labors and contributors.
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Numerics;
using System.Runtime.CompilerServices;
using SixLabors.ImageSharp.PixelFormats;
@ -14,6 +15,20 @@ namespace SixLabors.ImageSharp
/// </summary>
internal static class ImageMaths
{
/// <summary>
/// Vector for converting pixel to gray value as specified by ITU-R Recommendation BT.709.
/// </summary>
private static readonly Vector4 Bt709 = new Vector4(.2126f, .7152f, .0722f, 0.0f);
/// <summary>
/// Convert a pixel value to grayscale using ITU-R Recommendation BT.709.
/// </summary>
/// <param name="vector">The vector to get the luminance from.</param>
/// <param name="luminanceLevels">The number of luminance levels (256 for 8 bit, 65536 for 16 bit grayscale images)</param>
[MethodImpl(InliningOptions.ShortMethod)]
public static int GetBT709Luminance(ref Vector4 vector, int luminanceLevels)
=> (int)MathF.Round(Vector4.Dot(vector, Bt709) * (luminanceLevels - 1));
/// <summary>
/// Gets the luminance from the rgb components using the formula as specified by ITU-R Recommendation BT.709.
/// </summary>

5
src/ImageSharp/Configuration.cs

@ -8,6 +8,7 @@ using SixLabors.ImageSharp.Formats.Bmp;
using SixLabors.ImageSharp.Formats.Gif;
using SixLabors.ImageSharp.Formats.Jpeg;
using SixLabors.ImageSharp.Formats.Png;
using SixLabors.ImageSharp.Formats.Tga;
using SixLabors.ImageSharp.IO;
using SixLabors.ImageSharp.Processing;
using SixLabors.Memory;
@ -150,6 +151,7 @@ namespace SixLabors.ImageSharp
/// <see cref="JpegConfigurationModule"/>
/// <see cref="GifConfigurationModule"/>
/// <see cref="BmpConfigurationModule"/>.
/// <see cref="TgaConfigurationModule"/>.
/// </summary>
/// <returns>The default configuration of <see cref="Configuration"/>.</returns>
internal static Configuration CreateDefaultInstance()
@ -158,7 +160,8 @@ namespace SixLabors.ImageSharp
new PngConfigurationModule(),
new JpegConfigurationModule(),
new GifConfigurationModule(),
new BmpConfigurationModule());
new BmpConfigurationModule(),
new TgaConfigurationModule());
}
}
}

6
src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs

@ -387,9 +387,10 @@ namespace SixLabors.ImageSharp.Formats.Bmp
if (rowHasUndefinedPixels)
{
// Slow path with undefined pixels.
int rowStartIdx = y * width * 3;
for (int x = 0; x < width; x++)
{
int idx = (y * width * 3) + (x * 3);
int idx = rowStartIdx + (x * 3);
if (undefinedPixels[x, y])
{
switch (this.options.RleSkippedPixelHandling)
@ -418,9 +419,10 @@ namespace SixLabors.ImageSharp.Formats.Bmp
else
{
// Fast path without any undefined pixels.
int rowStartIdx = y * width * 3;
for (int x = 0; x < width; x++)
{
int idx = (y * width * 3) + (x * 3);
int idx = rowStartIdx + (x * 3);
color.FromBgr24(Unsafe.As<byte, Bgr24>(ref bufferSpan[idx]));
pixelRow[x] = color;
}

6
src/ImageSharp/Formats/README.md

@ -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)

12
src/ImageSharp/Formats/Tga/ITgaDecoderOptions.cs

@ -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
{
}
}

21
src/ImageSharp/Formats/Tga/ITgaEncoderOptions.cs

@ -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; }
}
}

BIN
src/ImageSharp/Formats/Tga/TGA_Specification.pdf

Binary file not shown.

31
src/ImageSharp/Formats/Tga/TgaBitsPerPixel.cs

@ -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
}
}

21
src/ImageSharp/Formats/Tga/TgaCompression.cs

@ -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,
}
}

19
src/ImageSharp/Formats/Tga/TgaConfigurationModule.cs

@ -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());
}
}
}

25
src/ImageSharp/Formats/Tga/TgaConstants.cs

@ -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;
}
}

34
src/ImageSharp/Formats/Tga/TgaDecoder.cs

@ -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);
}
}
}

588
src/ImageSharp/Formats/Tga/TgaDecoderCore.cs

@ -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;
}
}
}

34
src/ImageSharp/Formats/Tga/TgaEncoder.cs

@ -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);
}
}
}

348
src/ImageSharp/Formats/Tga/TgaEncoderCore.cs

@ -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);
}
}
}

147
src/ImageSharp/Formats/Tga/TgaFileHeader.cs

@ -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;
}
}
}

33
src/ImageSharp/Formats/Tga/TgaFormat.cs

@ -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();
}
}

40
src/ImageSharp/Formats/Tga/TgaImageFormatDetector.cs

@ -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;
}
}
}

48
src/ImageSharp/Formats/Tga/TgaImageType.cs

@ -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,
}
}

49
src/ImageSharp/Formats/Tga/TgaImageTypeExtensions.cs

@ -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;
}
}
}
}

35
src/ImageSharp/Formats/Tga/TgaMetadata.cs

@ -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);
}
}

31
src/ImageSharp/Formats/Tga/TgaThrowHelper.cs

@ -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);
}
}
}

4
src/ImageSharp/PixelFormats/PixelOperations{TPixel}.Generated.tt

@ -18,7 +18,7 @@
/// <summary>
/// Converts all pixels in 'source` span of <see cref="<#=pixelType#>"/> into a span of <typeparamref name="TPixel"/>-s.
/// </summary>
/// <param name="configuration">A <see cref="Configuration"/> to configure internal operations</param>
/// <param name="configuration">A <see cref="Configuration"/> to configure internal operations.</param>
/// <param name="source">The source <see cref="Span{T}"/> of <see cref="<#=pixelType#>"/> data.</param>
/// <param name="destPixels">The <see cref="Span{T}"/> to the destination pixels.</param>
internal virtual void From<#=pixelType#>(Configuration configuration, ReadOnlySpan<<#=pixelType#>> source, Span<TPixel> destPixels)
@ -41,7 +41,7 @@
/// A helper for <see cref="From<#=pixelType#>(Configuration, ReadOnlySpan{<#=pixelType#>}, Span{TPixel})"/> that expects a byte span.
/// The layout of the data in 'sourceBytes' must be compatible with <see cref="<#=pixelType#>"/> layout.
/// </summary>
/// <param name="configuration">A <see cref="Configuration"/> to configure internal operations</param>
/// <param name="configuration">A <see cref="Configuration"/> to configure internal operations.</param>
/// <param name="sourceBytes">The <see cref="ReadOnlySpan{T}"/> to the source bytes.</param>
/// <param name="destPixels">The <see cref="Span{T}"/> to the destination pixels.</param>
/// <param name="count">The number of pixels to convert.</param>

4
src/ImageSharp/Processing/Processors/Normalization/AdaptiveHistogramEqualizationSlidingWindowProcessor{TPixel}.cs

@ -349,7 +349,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization
{
for (int idx = 0; idx < length; idx++)
{
int luminance = GetLuminance(ref Unsafe.Add(ref greyValuesBase, idx), luminanceLevels);
int luminance = ImageMaths.GetBT709Luminance(ref Unsafe.Add(ref greyValuesBase, idx), luminanceLevels);
Unsafe.Add(ref histogramBase, luminance)++;
}
}
@ -366,7 +366,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization
{
for (int idx = 0; idx < length; idx++)
{
int luminance = GetLuminance(ref Unsafe.Add(ref greyValuesBase, idx), luminanceLevels);
int luminance = ImageMaths.GetBT709Luminance(ref Unsafe.Add(ref greyValuesBase, idx), luminanceLevels);
Unsafe.Add(ref histogramBase, luminance)--;
}
}

11
src/ImageSharp/Processing/Processors/Normalization/HistogramEqualizationProcessor{TPixel}.cs

@ -143,16 +143,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization
public static int GetLuminance(TPixel sourcePixel, int luminanceLevels)
{
var vector = sourcePixel.ToVector4();
return GetLuminance(ref vector, luminanceLevels);
return ImageMaths.GetBT709Luminance(ref vector, luminanceLevels);
}
/// <summary>
/// Convert the pixel values to grayscale using ITU-R Recommendation BT.709.
/// </summary>
/// <param name="vector">The vector to get the luminance from</param>
/// <param name="luminanceLevels">The number of luminance levels (256 for 8 bit, 65536 for 16 bit grayscale images)</param>
[MethodImpl(InliningOptions.ShortMethod)]
public static int GetLuminance(ref Vector4 vector, int luminanceLevels)
=> (int)MathF.Round(((.2126F * vector.X) + (.7152F * vector.Y) + (.0722F * vector.Y)) * (luminanceLevels - 1));
}
}

42
tests/ImageSharp.Benchmarks/Codecs/DecodeTga.cs

@ -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);
}
}
}
}

54
tests/ImageSharp.Benchmarks/Codecs/EncodeTga.cs

@ -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);
}
}
}
}

1
tests/ImageSharp.Benchmarks/ImageSharp.Benchmarks.csproj

@ -16,6 +16,7 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="Magick.NET-Q16-AnyCPU" />
<PackageReference Include="BenchmarkDotNet" />
<PackageReference Include="Colourful" />
<PackageReference Include="SixLabors.Shapes.Text" />

11
tests/ImageSharp.Tests/ConfigurationTests.cs

@ -1,4 +1,4 @@
// Copyright (c) Six Labors and contributors.
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System;
@ -20,6 +20,8 @@ namespace SixLabors.ImageSharp.Tests
public Configuration ConfigurationEmpty { get; }
public Configuration DefaultConfiguration { get; }
private readonly int expectedDefaultConfigurationCount = 5;
public ConfigurationTests()
{
// the shallow copy of configuration should behave exactly like the default configuration,
@ -108,14 +110,13 @@ namespace SixLabors.ImageSharp.Tests
[Fact]
public void ConfigurationCannotAddDuplicates()
{
const int count = 4;
Configuration config = this.DefaultConfiguration;
Assert.Equal(count, config.ImageFormats.Count());
Assert.Equal(expectedDefaultConfigurationCount, config.ImageFormats.Count());
config.ImageFormatsManager.AddImageFormat(BmpFormat.Instance);
Assert.Equal(count, config.ImageFormats.Count());
Assert.Equal(expectedDefaultConfigurationCount, config.ImageFormats.Count());
}
[Fact]
@ -123,7 +124,7 @@ namespace SixLabors.ImageSharp.Tests
{
Configuration config = Configuration.CreateDefaultInstance();
Assert.Equal(4, config.ImageFormats.Count());
Assert.Equal(expectedDefaultConfigurationCount, config.ImageFormats.Count());
}
[Fact]

7
tests/ImageSharp.Tests/Formats/Bmp/BmpFileHeaderTests.cs

@ -1,4 +1,11 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.IO;
using System.Linq;
using SixLabors.ImageSharp.Formats;
using SixLabors.ImageSharp.Formats.Bmp;
using Xunit;

2
tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs

@ -218,7 +218,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png
using (Image<TPixel> image = provider.GetImage(new PngDecoder()))
{
image.DebugSave(provider);
// TODO: compare to expected output
image.CompareToOriginal(provider, ImageComparer.Exact);
}
});
Assert.Null(ex);

197
tests/ImageSharp.Tests/Formats/Tga/TgaDecoderTests.cs

@ -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);
}
}
}
}

148
tests/ImageSharp.Tests/Formats/Tga/TgaEncoderTests.cs

@ -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);
}
}
}
}
}
}

33
tests/ImageSharp.Tests/Formats/Tga/TgaFileHeaderTests.cs

@ -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 _))
{
}
});
}
}
}

61
tests/ImageSharp.Tests/Formats/Tga/TgaTestUtils.cs

@ -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;
}
}
}
}

24
tests/ImageSharp.Tests/Helpers/ImageMathsTests.cs

@ -2,6 +2,11 @@
// Licensed under the Apache License, Version 2.0.
using System;
using System.Numerics;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.Memory;
using Xunit;
namespace SixLabors.ImageSharp.Tests.Helpers
@ -131,6 +136,23 @@ namespace SixLabors.ImageSharp.Tests.Helpers
Assert.Equal(expected, actual);
}
[Theory]
[InlineData(0.2f, 0.7f, 0.1f, 256, 140)]
[InlineData(0.5f, 0.5f, 0.5f, 256, 128)]
[InlineData(0.5f, 0.5f, 0.5f, 65536, 32768)]
[InlineData(0.2f, 0.7f, 0.1f, 65536, 36069)]
public void GetBT709Luminance_WithVector4(float x, float y, float z, int luminanceLevels, int expected)
{
// arrange
var vector = new Vector4(x, y, z, 0.0f);
// act
int actual = ImageMaths.GetBT709Luminance(ref vector, luminanceLevels);
// assert
Assert.Equal(expected, actual);
}
// TODO: We need to test all ImageMaths methods!
}
}
}

19
tests/ImageSharp.Tests/TestImages.cs

@ -365,5 +365,24 @@ namespace SixLabors.ImageSharp.Tests
public static readonly string[] All = { Rings, Giphy, Cheers, Trans, Kumin, Leo, Ratio4x1, Ratio1x4 };
}
public static class Tga
{
public const string Bit15 = "Tga/rgb15.tga";
public const string Bit15Rle = "Tga/rgb15rle.tga";
public const string Bit16 = "Tga/targa_16bit.tga";
public const string Bit16PalRle = "Tga/ccm8.tga";
public const string Bit24 = "Tga/targa_24bit.tga";
public const string Bit24TopLeft = "Tga/targa_24bit_pal_origin_topleft.tga";
public const string Bit24RleTopLeft = "Tga/targa_24bit_rle_origin_topleft.tga";
public const string Bit32 = "Tga/targa_32bit.tga";
public const string Grey = "Tga/targa_8bit.tga";
public const string GreyRle = "Tga/targa_8bit_rle.tga";
public const string Bit16Rle = "Tga/targa_16bit_rle.tga";
public const string Bit24Rle = "Tga/targa_24bit_rle.tga";
public const string Bit32Rle = "Tga/targa_32bit_rle.tga";
public const string Bit16Pal = "Tga/targa_16bit_pal.tga";
public const string Bit24Pal = "Tga/targa_24bit_pal.tga";
}
}
}

6
tests/ImageSharp.Tests/TestUtilities/TestEnvironment.Formats.cs

@ -8,6 +8,7 @@ using SixLabors.ImageSharp.Formats.Bmp;
using SixLabors.ImageSharp.Formats.Gif;
using SixLabors.ImageSharp.Formats.Jpeg;
using SixLabors.ImageSharp.Formats.Png;
using SixLabors.ImageSharp.Formats.Tga;
using SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs;
namespace SixLabors.ImageSharp.Tests
@ -53,7 +54,8 @@ namespace SixLabors.ImageSharp.Tests
{
var cfg = new Configuration(
new JpegConfigurationModule(),
new GifConfigurationModule()
new GifConfigurationModule(),
new TgaConfigurationModule()
);
// Magick codecs should work on all platforms
@ -75,4 +77,4 @@ namespace SixLabors.ImageSharp.Tests
return cfg;
}
}
}
}

2
tests/Images/External

@ -1 +1 @@
Subproject commit f0c4033667bd23ad9dde82ccb625c232d402ee05
Subproject commit ca4cf8318fe4d09f0fc825686dcd477ebfb5e3e5

BIN
tests/Images/Input/Tga/ccm8.tga

Binary file not shown.

BIN
tests/Images/Input/Tga/rgb15.tga

Binary file not shown.

After

Width:  |  Height:  |  Size: 79 KiB

BIN
tests/Images/Input/Tga/rgb15rle.tga

Binary file not shown.

BIN
tests/Images/Input/Tga/targa.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 104 KiB

BIN
tests/Images/Input/Tga/targa_16bit.tga

Binary file not shown.

After

Width:  |  Height:  |  Size: 69 KiB

BIN
tests/Images/Input/Tga/targa_16bit_pal.tga

Binary file not shown.

BIN
tests/Images/Input/Tga/targa_16bit_rle.tga

Binary file not shown.

BIN
tests/Images/Input/Tga/targa_24bit.tga

Binary file not shown.

After

Width:  |  Height:  |  Size: 103 KiB

BIN
tests/Images/Input/Tga/targa_24bit_pal.tga

Binary file not shown.

BIN
tests/Images/Input/Tga/targa_24bit_pal_origin_topleft.tga

Binary file not shown.

BIN
tests/Images/Input/Tga/targa_24bit_rle.tga

Binary file not shown.

BIN
tests/Images/Input/Tga/targa_24bit_rle_origin_topleft.tga

Binary file not shown.

BIN
tests/Images/Input/Tga/targa_32bit.tga

Binary file not shown.

After

Width:  |  Height:  |  Size: 138 KiB

BIN
tests/Images/Input/Tga/targa_32bit_rle.tga

Binary file not shown.

BIN
tests/Images/Input/Tga/targa_8bit.tga

Binary file not shown.

BIN
tests/Images/Input/Tga/targa_8bit_rle.tga

Binary file not shown.
Loading…
Cancel
Save