diff --git a/src/ImageSharp/Formats/Tga/TgaDecoderCore.cs b/src/ImageSharp/Formats/Tga/TgaDecoderCore.cs
index cca34c9b0..a4e9bdcd7 100644
--- a/src/ImageSharp/Formats/Tga/TgaDecoderCore.cs
+++ b/src/ImageSharp/Formats/Tga/TgaDecoderCore.cs
@@ -17,6 +17,11 @@ namespace SixLabors.ImageSharp.Formats.Tga
///
internal sealed class TgaDecoderCore
{
+ ///
+ /// A scratch buffer to reduce allocations.
+ ///
+ private readonly byte[] scratchBuffer = new byte[4];
+
///
/// The metadata.
///
@@ -88,7 +93,7 @@ namespace SixLabors.ImageSharp.Formats.Tga
{
try
{
- bool inverted = this.ReadFileHeader(stream);
+ TgaImageOrigin origin = this.ReadFileHeader(stream);
this.currentStream.Skip(this.fileHeader.IdLength);
// Parse the color map, if present.
@@ -131,7 +136,7 @@ namespace SixLabors.ImageSharp.Formats.Tga
pixels,
palette.Array,
colorMapPixelSizeInBytes,
- inverted);
+ origin);
}
else
{
@@ -141,7 +146,7 @@ namespace SixLabors.ImageSharp.Formats.Tga
pixels,
palette.Array,
colorMapPixelSizeInBytes,
- inverted);
+ origin);
}
}
@@ -160,11 +165,11 @@ namespace SixLabors.ImageSharp.Formats.Tga
case 8:
if (this.fileHeader.ImageType.IsRunLengthEncoded())
{
- this.ReadRle(this.fileHeader.Width, this.fileHeader.Height, pixels, 1, inverted);
+ this.ReadRle(this.fileHeader.Width, this.fileHeader.Height, pixels, 1, origin);
}
else
{
- this.ReadMonoChrome(this.fileHeader.Width, this.fileHeader.Height, pixels, inverted);
+ this.ReadMonoChrome(this.fileHeader.Width, this.fileHeader.Height, pixels, origin);
}
break;
@@ -173,11 +178,11 @@ namespace SixLabors.ImageSharp.Formats.Tga
case 16:
if (this.fileHeader.ImageType.IsRunLengthEncoded())
{
- this.ReadRle(this.fileHeader.Width, this.fileHeader.Height, pixels, 2, inverted);
+ this.ReadRle(this.fileHeader.Width, this.fileHeader.Height, pixels, 2, origin);
}
else
{
- this.ReadBgra16(this.fileHeader.Width, this.fileHeader.Height, pixels, inverted);
+ this.ReadBgra16(this.fileHeader.Width, this.fileHeader.Height, pixels, origin);
}
break;
@@ -185,11 +190,11 @@ namespace SixLabors.ImageSharp.Formats.Tga
case 24:
if (this.fileHeader.ImageType.IsRunLengthEncoded())
{
- this.ReadRle(this.fileHeader.Width, this.fileHeader.Height, pixels, 3, inverted);
+ this.ReadRle(this.fileHeader.Width, this.fileHeader.Height, pixels, 3, origin);
}
else
{
- this.ReadBgr24(this.fileHeader.Width, this.fileHeader.Height, pixels, inverted);
+ this.ReadBgr24(this.fileHeader.Width, this.fileHeader.Height, pixels, origin);
}
break;
@@ -197,11 +202,11 @@ namespace SixLabors.ImageSharp.Formats.Tga
case 32:
if (this.fileHeader.ImageType.IsRunLengthEncoded())
{
- this.ReadRle(this.fileHeader.Width, this.fileHeader.Height, pixels, 4, inverted);
+ this.ReadRle(this.fileHeader.Width, this.fileHeader.Height, pixels, 4, origin);
}
else
{
- this.ReadBgra32(this.fileHeader.Width, this.fileHeader.Height, pixels, inverted);
+ this.ReadBgra32(this.fileHeader.Width, this.fileHeader.Height, pixels, origin);
}
break;
@@ -228,60 +233,73 @@ namespace SixLabors.ImageSharp.Formats.Tga
/// The to assign the palette to.
/// The color palette.
/// Color map size of one entry in bytes.
- /// Indicates, if the origin of the image is top left rather the bottom left (the default).
- private void ReadPaletted(int width, int height, Buffer2D pixels, byte[] palette, int colorMapPixelSizeInBytes, bool inverted)
+ /// The image origin.
+ private void ReadPaletted(int width, int height, Buffer2D pixels, byte[] palette, int colorMapPixelSizeInBytes, TgaImageOrigin origin)
where TPixel : unmanaged, IPixel
{
- using (IManagedByteBuffer row = this.memoryAllocator.AllocateManagedByteBuffer(width, AllocationOptions.Clean))
+ TPixel color = default;
+ bool invertX = InvertX(origin);
+
+ for (int y = 0; y < height; y++)
{
- TPixel color = default;
- Span rowSpan = row.GetSpan();
+ int newY = InvertY(y, height, origin);
+ Span pixelRow = pixels.GetRowSpan(newY);
- for (int y = 0; y < height; y++)
+ switch (colorMapPixelSizeInBytes)
{
- this.currentStream.Read(row);
- int newY = Invert(y, height, inverted);
- Span pixelRow = pixels.GetRowSpan(newY);
- switch (colorMapPixelSizeInBytes)
- {
- case 2:
+ case 2:
+ if (invertX)
+ {
+ for (int x = width - 1; x >= 0; x--)
+ {
+ this.ReadPalettedBgr16Pixel(palette, colorMapPixelSizeInBytes, x, color, pixelRow);
+ }
+ }
+ else
+ {
for (int x = 0; x < width; x++)
{
- int colorIndex = rowSpan[x];
-
- Bgra5551 bgra = Unsafe.As(ref palette[colorIndex * colorMapPixelSizeInBytes]);
- if (!this.hasAlpha)
- {
- // Set alpha value to 1, to treat it as opaque for Bgra5551.
- bgra.PackedValue = (ushort)(bgra.PackedValue | 0x8000);
- }
-
- color.FromBgra5551(bgra);
- pixelRow[x] = color;
+ this.ReadPalettedBgr16Pixel(palette, colorMapPixelSizeInBytes, x, color, pixelRow);
}
+ }
- break;
+ break;
- case 3:
+ case 3:
+ if (invertX)
+ {
+ for (int x = width - 1; x >= 0; x--)
+ {
+ this.ReadPalettedBgr24Pixel(palette, colorMapPixelSizeInBytes, x, color, pixelRow);
+ }
+ }
+ else
+ {
for (int x = 0; x < width; x++)
{
- int colorIndex = rowSpan[x];
- color.FromBgr24(Unsafe.As(ref palette[colorIndex * colorMapPixelSizeInBytes]));
- pixelRow[x] = color;
+ this.ReadPalettedBgr24Pixel(palette, colorMapPixelSizeInBytes, x, color, pixelRow);
}
+ }
- break;
+ break;
- case 4:
+ case 4:
+ if (invertX)
+ {
+ for (int x = width - 1; x >= 0; x--)
+ {
+ this.ReadPalettedBgra32Pixel(palette, colorMapPixelSizeInBytes, x, color, pixelRow);
+ }
+ }
+ else
+ {
for (int x = 0; x < width; x++)
{
- int colorIndex = rowSpan[x];
- color.FromBgra32(Unsafe.As(ref palette[colorIndex * colorMapPixelSizeInBytes]));
- pixelRow[x] = color;
+ this.ReadPalettedBgra32Pixel(palette, colorMapPixelSizeInBytes, x, color, pixelRow);
}
+ }
- break;
- }
+ break;
}
}
}
@@ -295,8 +313,8 @@ namespace SixLabors.ImageSharp.Formats.Tga
/// The to assign the palette to.
/// The color palette.
/// Color map size of one entry in bytes.
- /// Indicates, if the origin of the image is top left rather the bottom left (the default).
- private void ReadPalettedRle(int width, int height, Buffer2D pixels, byte[] palette, int colorMapPixelSizeInBytes, bool inverted)
+ /// The image origin.
+ private void ReadPalettedRle(int width, int height, Buffer2D pixels, byte[] palette, int colorMapPixelSizeInBytes, TgaImageOrigin origin)
where TPixel : unmanaged, IPixel
{
int bytesPerPixel = 1;
@@ -309,7 +327,7 @@ namespace SixLabors.ImageSharp.Formats.Tga
for (int y = 0; y < height; y++)
{
- int newY = Invert(y, height, inverted);
+ int newY = InvertY(y, height, origin);
Span pixelRow = pixels.GetRowSpan(newY);
int rowStartIdx = y * width * bytesPerPixel;
for (int x = 0; x < width; x++)
@@ -348,7 +366,8 @@ namespace SixLabors.ImageSharp.Formats.Tga
break;
}
- pixelRow[x] = color;
+ int newX = InvertX(x, width, origin);
+ pixelRow[newX] = color;
}
}
}
@@ -361,18 +380,43 @@ namespace SixLabors.ImageSharp.Formats.Tga
/// The width of the image.
/// The height of the image.
/// The to assign the palette to.
- /// Indicates, if the origin of the image is top left rather the bottom left (the default).
- private void ReadMonoChrome(int width, int height, Buffer2D pixels, bool inverted)
+ /// the image origin.
+ private void ReadMonoChrome(int width, int height, Buffer2D pixels, TgaImageOrigin origin)
where TPixel : unmanaged, IPixel
{
- using (IManagedByteBuffer row = this.memoryAllocator.AllocatePaddedPixelRowBuffer(width, 1, 0))
+ bool invertX = InvertX(origin);
+ if (invertX)
{
+ TPixel color = default;
for (int y = 0; y < height; y++)
{
- this.currentStream.Read(row);
- int newY = Invert(y, height, inverted);
+ int newY = InvertY(y, height, origin);
Span pixelSpan = pixels.GetRowSpan(newY);
- PixelOperations.Instance.FromL8Bytes(this.configuration, row.GetSpan(), pixelSpan, width);
+ for (int x = width - 1; x >= 0; x--)
+ {
+ this.ReadL8Pixel(color, x, pixelSpan);
+ }
+ }
+
+ return;
+ }
+
+ using (IManagedByteBuffer row = this.memoryAllocator.AllocatePaddedPixelRowBuffer(width, 1, 0))
+ {
+ bool invertY = InvertY(origin);
+ if (invertY)
+ {
+ for (int y = height - 1; y >= 0; y--)
+ {
+ this.ReadL8Row(width, pixels, row, y);
+ }
+ }
+ else
+ {
+ for (int y = 0; y < height; y++)
+ {
+ this.ReadL8Row(width, pixels, row, y);
+ }
}
}
}
@@ -384,29 +428,64 @@ namespace SixLabors.ImageSharp.Formats.Tga
/// The width of the image.
/// The height of the image.
/// The to assign the palette to.
- /// Indicates, if the origin of the image is top left rather the bottom left (the default).
- private void ReadBgra16(int width, int height, Buffer2D pixels, bool inverted)
+ /// The image origin.
+ private void ReadBgra16(int width, int height, Buffer2D pixels, TgaImageOrigin origin)
where TPixel : unmanaged, IPixel
{
+ TPixel color = default;
+ bool invertX = InvertX(origin);
using (IManagedByteBuffer row = this.memoryAllocator.AllocatePaddedPixelRowBuffer(width, 2, 0))
{
for (int y = 0; y < height; y++)
{
- this.currentStream.Read(row);
- Span rowSpan = row.GetSpan();
+ int newY = InvertY(y, height, origin);
+ Span pixelSpan = pixels.GetRowSpan(newY);
- if (!this.hasAlpha)
+ if (invertX)
{
- // We need to set the alpha component value to fully opaque.
- for (int x = 1; x < rowSpan.Length; x += 2)
+ for (int x = width - 1; x >= 0; x--)
{
- rowSpan[x] = (byte)(rowSpan[x] | (1 << 7));
+ this.currentStream.Read(this.scratchBuffer, 0, 2);
+ if (!this.hasAlpha)
+ {
+ this.scratchBuffer[1] |= 1 << 7;
+ }
+
+ if (this.fileHeader.ImageType == TgaImageType.BlackAndWhite)
+ {
+ color.FromLa16(Unsafe.As(ref this.scratchBuffer[0]));
+ }
+ else
+ {
+ color.FromBgra5551(Unsafe.As(ref this.scratchBuffer[0]));
+ }
+
+ pixelSpan[x] = color;
}
}
+ else
+ {
+ this.currentStream.Read(row);
+ Span rowSpan = row.GetSpan();
- int newY = Invert(y, height, inverted);
- Span pixelSpan = pixels.GetRowSpan(newY);
- PixelOperations.Instance.FromBgra5551Bytes(this.configuration, rowSpan, pixelSpan, width);
+ if (!this.hasAlpha)
+ {
+ // We need to set the alpha component value to fully opaque.
+ for (int x = 1; x < rowSpan.Length; x += 2)
+ {
+ rowSpan[x] |= 1 << 7;
+ }
+ }
+
+ if (this.fileHeader.ImageType == TgaImageType.BlackAndWhite)
+ {
+ PixelOperations.Instance.FromLa16Bytes(this.configuration, rowSpan, pixelSpan, width);
+ }
+ else
+ {
+ PixelOperations.Instance.FromBgra5551Bytes(this.configuration, rowSpan, pixelSpan, width);
+ }
+ }
}
}
}
@@ -418,18 +497,44 @@ namespace SixLabors.ImageSharp.Formats.Tga
/// The width of the image.
/// The height of the image.
/// The to assign the palette to.
- /// Indicates, if the origin of the image is top left rather the bottom left (the default).
- private void ReadBgr24(int width, int height, Buffer2D pixels, bool inverted)
+ /// The image origin.
+ private void ReadBgr24(int width, int height, Buffer2D pixels, TgaImageOrigin origin)
where TPixel : unmanaged, IPixel
{
- using (IManagedByteBuffer row = this.memoryAllocator.AllocatePaddedPixelRowBuffer(width, 3, 0))
+ bool invertX = InvertX(origin);
+ if (invertX)
{
+ TPixel color = default;
for (int y = 0; y < height; y++)
{
- this.currentStream.Read(row);
- int newY = Invert(y, height, inverted);
+ int newY = InvertY(y, height, origin);
Span pixelSpan = pixels.GetRowSpan(newY);
- PixelOperations.Instance.FromBgr24Bytes(this.configuration, row.GetSpan(), pixelSpan, width);
+ for (int x = width - 1; x >= 0; x--)
+ {
+ this.ReadBgr24Pixel(color, x, pixelSpan);
+ }
+ }
+
+ return;
+ }
+
+ using (IManagedByteBuffer row = this.memoryAllocator.AllocatePaddedPixelRowBuffer(width, 3, 0))
+ {
+ bool invertY = InvertY(origin);
+
+ if (invertY)
+ {
+ for (int y = height - 1; y >= 0; y--)
+ {
+ this.ReadBgr24Row(width, pixels, row, y);
+ }
+ }
+ else
+ {
+ for (int y = 0; y < height; y++)
+ {
+ this.ReadBgr24Row(width, pixels, row, y);
+ }
}
}
}
@@ -441,44 +546,51 @@ namespace SixLabors.ImageSharp.Formats.Tga
/// The width of the image.
/// The height of the image.
/// The to assign the palette to.
- /// Indicates, if the origin of the image is top left rather the bottom left (the default).
- private void ReadBgra32(int width, int height, Buffer2D pixels, bool inverted)
+ /// The image origin.
+ private void ReadBgra32(int width, int height, Buffer2D pixels, TgaImageOrigin origin)
where TPixel : unmanaged, IPixel
{
- if (this.tgaMetadata.AlphaChannelBits == 8)
+ TPixel color = default;
+ bool invertX = InvertX(origin);
+ if (this.tgaMetadata.AlphaChannelBits == 8 && !invertX)
{
using (IManagedByteBuffer row = this.memoryAllocator.AllocatePaddedPixelRowBuffer(width, 4, 0))
{
- for (int y = 0; y < height; y++)
+ if (InvertY(origin))
{
- this.currentStream.Read(row);
- int newY = Invert(y, height, inverted);
- Span pixelSpan = pixels.GetRowSpan(newY);
-
- PixelOperations.Instance.FromBgra32Bytes(this.configuration, row.GetSpan(), pixelSpan, width);
+ for (int y = height - 1; y >= 0; y--)
+ {
+ this.ReadBgra32Row(width, pixels, row, y);
+ }
+ }
+ else
+ {
+ for (int y = 0; y < height; y++)
+ {
+ this.ReadBgra32Row(width, pixels, row, y);
+ }
}
}
return;
}
- TPixel color = default;
- var alphaBits = this.tgaMetadata.AlphaChannelBits;
- using (IManagedByteBuffer row = this.memoryAllocator.AllocatePaddedPixelRowBuffer(width, 4, 0))
+ for (int y = 0; y < height; y++)
{
- for (int y = 0; y < height; y++)
+ int newY = InvertY(y, height, origin);
+ Span pixelRow = pixels.GetRowSpan(newY);
+ if (invertX)
+ {
+ for (int x = width - 1; x >= 0; x--)
+ {
+ this.ReadBgra32Pixel(x, color, pixelRow);
+ }
+ }
+ else
{
- this.currentStream.Read(row);
- int newY = Invert(y, height, inverted);
- Span pixelRow = pixels.GetRowSpan(newY);
- Span rowSpan = row.GetSpan();
-
for (int x = 0; x < width; x++)
{
- int idx = x * 4;
- var alpha = alphaBits == 0 ? byte.MaxValue : rowSpan[idx + 3];
- color.FromBgra32(new Bgra32(rowSpan[idx + 2], rowSpan[idx + 1], rowSpan[idx], (byte)alpha));
- pixelRow[x] = color;
+ this.ReadBgra32Pixel(x, color, pixelRow);
}
}
}
@@ -492,8 +604,8 @@ namespace SixLabors.ImageSharp.Formats.Tga
/// The height of the image.
/// The to assign the palette to.
/// The bytes per pixel.
- /// Indicates, if the origin of the image is top left rather the bottom left (the default).
- private void ReadRle(int width, int height, Buffer2D pixels, int bytesPerPixel, bool inverted)
+ /// The image origin.
+ private void ReadRle(int width, int height, Buffer2D pixels, int bytesPerPixel, TgaImageOrigin origin)
where TPixel : unmanaged, IPixel
{
TPixel color = default;
@@ -504,7 +616,7 @@ namespace SixLabors.ImageSharp.Formats.Tga
this.UncompressRle(width, height, bufferSpan, bytesPerPixel);
for (int y = 0; y < height; y++)
{
- int newY = Invert(y, height, inverted);
+ int newY = InvertY(y, height, origin);
Span pixelRow = pixels.GetRowSpan(newY);
int rowStartIdx = y * width * bytesPerPixel;
for (int x = 0; x < width; x++)
@@ -522,7 +634,15 @@ namespace SixLabors.ImageSharp.Formats.Tga
bufferSpan[idx + 1] = (byte)(bufferSpan[idx + 1] | 128);
}
- color.FromBgra5551(Unsafe.As(ref bufferSpan[idx]));
+ if (this.fileHeader.ImageType == TgaImageType.RleBlackAndWhite)
+ {
+ color.FromLa16(Unsafe.As(ref bufferSpan[idx]));
+ }
+ else
+ {
+ color.FromBgra5551(Unsafe.As(ref bufferSpan[idx]));
+ }
+
break;
case 3:
color.FromBgr24(Unsafe.As(ref bufferSpan[idx]));
@@ -541,7 +661,8 @@ namespace SixLabors.ImageSharp.Formats.Tga
break;
}
- pixelRow[x] = color;
+ int newX = InvertX(x, width, origin);
+ pixelRow[newX] = color;
}
}
}
@@ -561,6 +682,95 @@ namespace SixLabors.ImageSharp.Formats.Tga
this.metadata);
}
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private void ReadL8Row(int width, Buffer2D pixels, IManagedByteBuffer row, int y)
+ where TPixel : unmanaged, IPixel
+ {
+ this.currentStream.Read(row);
+ Span pixelSpan = pixels.GetRowSpan(y);
+ PixelOperations.Instance.FromL8Bytes(this.configuration, row.GetSpan(), pixelSpan, width);
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private void ReadL8Pixel(TPixel color, int x, Span pixelSpan)
+ where TPixel : unmanaged, IPixel
+ {
+ var pixelValue = (byte)this.currentStream.ReadByte();
+ color.FromL8(Unsafe.As(ref pixelValue));
+ pixelSpan[x] = color;
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private void ReadBgr24Pixel(TPixel color, int x, Span pixelSpan)
+ where TPixel : unmanaged, IPixel
+ {
+ this.currentStream.Read(this.scratchBuffer, 0, 3);
+ color.FromBgr24(Unsafe.As(ref this.scratchBuffer[0]));
+ pixelSpan[x] = color;
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private void ReadBgr24Row(int width, Buffer2D pixels, IManagedByteBuffer row, int y)
+ where TPixel : unmanaged, IPixel
+ {
+ this.currentStream.Read(row);
+ Span pixelSpan = pixels.GetRowSpan(y);
+ PixelOperations.Instance.FromBgr24Bytes(this.configuration, row.GetSpan(), pixelSpan, width);
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private void ReadBgra32Pixel(int x, TPixel color, Span pixelRow)
+ where TPixel : unmanaged, IPixel
+ {
+ this.currentStream.Read(this.scratchBuffer, 0, 4);
+ var alpha = this.tgaMetadata.AlphaChannelBits == 0 ? byte.MaxValue : this.scratchBuffer[3];
+ color.FromBgra32(new Bgra32(this.scratchBuffer[2], this.scratchBuffer[1], this.scratchBuffer[0], alpha));
+ pixelRow[x] = color;
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private void ReadBgra32Row(int width, Buffer2D pixels, IManagedByteBuffer row, int y)
+ where TPixel : unmanaged, IPixel
+ {
+ this.currentStream.Read(row);
+ Span pixelSpan = pixels.GetRowSpan(y);
+ PixelOperations.Instance.FromBgra32Bytes(this.configuration, row.GetSpan(), pixelSpan, width);
+ }
+
+ private void ReadPalettedBgr16Pixel(byte[] palette, int colorMapPixelSizeInBytes, int x, TPixel color, Span pixelRow)
+ where TPixel : unmanaged, IPixel
+ {
+ int colorIndex = this.currentStream.ReadByte();
+ Bgra5551 bgra = default;
+ bgra.FromBgra5551(Unsafe.As(ref palette[colorIndex * colorMapPixelSizeInBytes]));
+ if (!this.hasAlpha)
+ {
+ // Set alpha value to 1, to treat it as opaque for Bgra5551.
+ bgra.PackedValue = (ushort)(bgra.PackedValue | 0x8000);
+ }
+
+ color.FromBgra5551(bgra);
+ pixelRow[x] = color;
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private void ReadPalettedBgr24Pixel(byte[] palette, int colorMapPixelSizeInBytes, int x, TPixel color, Span pixelRow)
+ where TPixel : unmanaged, IPixel
+ {
+ int colorIndex = this.currentStream.ReadByte();
+ color.FromBgr24(Unsafe.As(ref palette[colorIndex * colorMapPixelSizeInBytes]));
+ pixelRow[x] = color;
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private void ReadPalettedBgra32Pixel(byte[] palette, int colorMapPixelSizeInBytes, int x, TPixel color, Span pixelRow)
+ where TPixel : unmanaged, IPixel
+ {
+ int colorIndex = this.currentStream.ReadByte();
+ color.FromBgra32(Unsafe.As(ref palette[colorIndex * colorMapPixelSizeInBytes]));
+ pixelRow[x] = color;
+ }
+
///
/// Produce uncompressed tga data from a run length encoded stream.
///
@@ -609,18 +819,80 @@ namespace SixLabors.ImageSharp.Formats.Tga
/// Returns the y- value based on the given height.
///
/// The y- value representing the current row.
- /// The height of the bitmap.
- /// Whether the bitmap is inverted.
+ /// The height of the image.
+ /// The image origin.
+ /// The representing the inverted value.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static int InvertY(int y, int height, TgaImageOrigin origin)
+ {
+ if (InvertY(origin))
+ {
+ return height - y - 1;
+ }
+
+ return y;
+ }
+
+ ///
+ /// Indicates whether the y coordinates needs to be inverted, to keep a top left origin.
+ ///
+ /// The image origin.
+ /// True, if y coordinate needs to be inverted.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static bool InvertY(TgaImageOrigin origin)
+ {
+ switch (origin)
+ {
+ case TgaImageOrigin.BottomLeft:
+ case TgaImageOrigin.BottomRight:
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ ///
+ /// Returns the x- value based on the given width.
+ ///
+ /// The x- value representing the current column.
+ /// The width of the image.
+ /// The image origin.
/// The representing the inverted value.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- private static int Invert(int y, int height, bool inverted) => (!inverted) ? height - y - 1 : y;
+ private static int InvertX(int x, int width, TgaImageOrigin origin)
+ {
+ if (InvertX(origin))
+ {
+ return width - x - 1;
+ }
+
+ return x;
+ }
+
+ ///
+ /// Indicates whether the x coordinates needs to be inverted, to keep a top left origin.
+ ///
+ /// The image origin.
+ /// True, if x coordinate needs to be inverted.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static bool InvertX(TgaImageOrigin origin)
+ {
+ switch (origin)
+ {
+ case TgaImageOrigin.TopRight:
+ case TgaImageOrigin.BottomRight:
+ return true;
+ default:
+ return false;
+ }
+ }
///
/// Reads the tga file header from the stream.
///
/// The containing image data.
- /// true, if the image origin is top left.
- private bool ReadFileHeader(Stream stream)
+ /// The image origin.
+ private TgaImageOrigin ReadFileHeader(Stream stream)
{
this.currentStream = stream;
@@ -641,15 +913,9 @@ namespace SixLabors.ImageSharp.Formats.Tga
this.tgaMetadata.AlphaChannelBits = (byte)alphaBits;
this.hasAlpha = alphaBits > 0;
- // TODO: bits 4 and 5 describe the image origin. See spec page 9. bit 4 is currently ignored.
- // Theoretically the origin could also be top right and bottom right.
- // Bit at position 5 of the descriptor indicates, that the origin is top left instead of bottom left.
- if ((this.fileHeader.ImageDescriptor & (1 << 5)) != 0)
- {
- return true;
- }
-
- return false;
+ // Bits 4 and 5 describe the image origin.
+ var origin = (TgaImageOrigin)((this.fileHeader.ImageDescriptor & 0x30) >> 4);
+ return origin;
}
}
}
diff --git a/src/ImageSharp/Formats/Tga/TgaImageOrigin.cs b/src/ImageSharp/Formats/Tga/TgaImageOrigin.cs
new file mode 100644
index 000000000..06d7b5945
--- /dev/null
+++ b/src/ImageSharp/Formats/Tga/TgaImageOrigin.cs
@@ -0,0 +1,28 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+namespace SixLabors.ImageSharp.Formats.Tga
+{
+ internal enum TgaImageOrigin
+ {
+ ///
+ /// Bottom left origin.
+ ///
+ BottomLeft = 0,
+
+ ///
+ /// Bottom right origin.
+ ///
+ BottomRight = 1,
+
+ ///
+ /// Top left origin.
+ ///
+ TopLeft = 2,
+
+ ///
+ /// Top right origin.
+ ///
+ TopRight = 3,
+ }
+}
diff --git a/tests/Directory.Build.targets b/tests/Directory.Build.targets
index 3c3b3b5ec..df153c08b 100644
--- a/tests/Directory.Build.targets
+++ b/tests/Directory.Build.targets
@@ -29,7 +29,7 @@
-
+
diff --git a/tests/ImageSharp.Benchmarks/Codecs/DecodeTga.cs b/tests/ImageSharp.Benchmarks/Codecs/DecodeTga.cs
index 3c8f45edb..072bd53ed 100644
--- a/tests/ImageSharp.Benchmarks/Codecs/DecodeTga.cs
+++ b/tests/ImageSharp.Benchmarks/Codecs/DecodeTga.cs
@@ -23,7 +23,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs
private byte[] data;
- [Params(TestImages.Tga.Bit24)]
+ [Params(TestImages.Tga.Bit24BottomLeft)]
public string TestImage { get; set; }
[GlobalSetup]
diff --git a/tests/ImageSharp.Benchmarks/Codecs/EncodeTga.cs b/tests/ImageSharp.Benchmarks/Codecs/EncodeTga.cs
index 7100ca6b7..f10eacb28 100644
--- a/tests/ImageSharp.Benchmarks/Codecs/EncodeTga.cs
+++ b/tests/ImageSharp.Benchmarks/Codecs/EncodeTga.cs
@@ -20,7 +20,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs
private string TestImageFullPath => Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, this.TestImage);
- [Params(TestImages.Tga.Bit24)]
+ [Params(TestImages.Tga.Bit24BottomLeft)]
public string TestImage { get; set; }
[GlobalSetup]
diff --git a/tests/ImageSharp.Tests/Formats/Tga/TgaDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Tga/TgaDecoderTests.cs
index 767b3b954..f932f994d 100644
--- a/tests/ImageSharp.Tests/Formats/Tga/TgaDecoderTests.cs
+++ b/tests/ImageSharp.Tests/Formats/Tga/TgaDecoderTests.cs
@@ -20,8 +20,8 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tga
private static TgaDecoder TgaDecoder => new TgaDecoder();
[Theory]
- [WithFile(Grey, PixelTypes.Rgba32)]
- public void TgaDecoder_CanDecode_Uncompressed_MonoChrome(TestImageProvider provider)
+ [WithFile(Gray8BitTopLeft, PixelTypes.Rgba32)]
+ public void TgaDecoder_CanDecode_Gray_WithTopLeftOrigin_8Bit(TestImageProvider provider)
where TPixel : unmanaged, IPixel
{
using (Image image = provider.GetImage(TgaDecoder))
@@ -31,9 +31,213 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tga
}
}
+ [Theory]
+ [WithFile(Gray8BitBottomLeft, PixelTypes.Rgba32)]
+ public void TgaDecoder_CanDecode_Gray_WithBottomLeftOrigin_8Bit(TestImageProvider provider)
+ where TPixel : unmanaged, IPixel
+ {
+ using (Image image = provider.GetImage(TgaDecoder))
+ {
+ image.DebugSave(provider);
+ TgaTestUtils.CompareWithReferenceDecoder(provider, image);
+ }
+ }
+
+ [Theory]
+ [WithFile(Gray8BitTopRight, PixelTypes.Rgba32)]
+ public void TgaDecoder_CanDecode_Gray_WithTopRightOrigin_8Bit(TestImageProvider provider)
+ where TPixel : unmanaged, IPixel
+ {
+ using (Image image = provider.GetImage(TgaDecoder))
+ {
+ image.DebugSave(provider);
+ TgaTestUtils.CompareWithReferenceDecoder(provider, image);
+ }
+ }
+
+ [Theory]
+ [WithFile(Gray8BitBottomRight, PixelTypes.Rgba32)]
+ public void TgaDecoder_CanDecode_Gray_WithBottomRightOrigin_8Bit(TestImageProvider provider)
+ where TPixel : unmanaged, IPixel
+ {
+ using (Image image = provider.GetImage(TgaDecoder))
+ {
+ image.DebugSave(provider);
+ TgaTestUtils.CompareWithReferenceDecoder(provider, image);
+ }
+ }
+
+ [Theory]
+ [WithFile(Gray8BitRleTopLeft, PixelTypes.Rgba32)]
+ public void TgaDecoder_CanDecode_RunLengthEncoded_Gray_WithTopLeftOrigin_8Bit(TestImageProvider provider)
+ where TPixel : unmanaged, IPixel
+ {
+ using (Image image = provider.GetImage(TgaDecoder))
+ {
+ image.DebugSave(provider);
+ TgaTestUtils.CompareWithReferenceDecoder(provider, image);
+ }
+ }
+
+ [Theory]
+ [WithFile(Gray8BitRleTopRight, PixelTypes.Rgba32)]
+ public void TgaDecoder_CanDecode_RunLengthEncoded_Gray_WithTopRightOrigin_8Bit(TestImageProvider provider)
+ where TPixel : unmanaged, IPixel
+ {
+ using (Image image = provider.GetImage(TgaDecoder))
+ {
+ image.DebugSave(provider);
+ TgaTestUtils.CompareWithReferenceDecoder(provider, image);
+ }
+ }
+
+ [Theory]
+ [WithFile(Gray8BitRleBottomLeft, PixelTypes.Rgba32)]
+ public void TgaDecoder_CanDecode_RunLengthEncoded_Gray_WithBottomLeftOrigin_8Bit(TestImageProvider provider)
+ where TPixel : unmanaged, IPixel
+ {
+ using (Image image = provider.GetImage(TgaDecoder))
+ {
+ image.DebugSave(provider);
+ TgaTestUtils.CompareWithReferenceDecoder(provider, image);
+ }
+ }
+
+ [Theory]
+ [WithFile(Gray8BitRleBottomRight, PixelTypes.Rgba32)]
+ public void TgaDecoder_CanDecode_RunLengthEncoded_Gray_WithBottomRightOrigin_8Bit(TestImageProvider provider)
+ where TPixel : unmanaged, IPixel
+ {
+ using (Image image = provider.GetImage(TgaDecoder))
+ {
+ image.DebugSave(provider);
+ TgaTestUtils.CompareWithReferenceDecoder(provider, image);
+ }
+ }
+
+ [Theory]
+ [WithFile(Gray16BitTopLeft, PixelTypes.Rgba32)]
+ public void TgaDecoder_CanDecode_Gray_16Bit(TestImageProvider provider)
+ where TPixel : unmanaged, IPixel
+ {
+ using (Image image = provider.GetImage(TgaDecoder))
+ {
+ image.DebugSave(provider);
+
+ // Using here the reference output instead of the the reference decoder,
+ // because the reference decoder output seems not to be correct for 16bit gray images.
+ image.CompareToReferenceOutput(ImageComparer.Exact, provider);
+ }
+ }
+
+ [Theory]
+ [WithFile(Gray16BitBottomLeft, PixelTypes.Rgba32)]
+ public void TgaDecoder_CanDecode_Gray_WithBottomLeftOrigin_16Bit(TestImageProvider provider)
+ where TPixel : unmanaged, IPixel
+ {
+ using (Image image = provider.GetImage(TgaDecoder))
+ {
+ image.DebugSave(provider);
+
+ // Using here the reference output instead of the the reference decoder,
+ // because the reference decoder output seems not to be correct for 16bit gray images.
+ image.CompareToReferenceOutput(ImageComparer.Exact, provider);
+ }
+ }
+
+ [Theory]
+ [WithFile(Gray16BitBottomRight, PixelTypes.Rgba32)]
+ public void TgaDecoder_CanDecode_Gray_WithBottomRightOrigin_16Bit(TestImageProvider provider)
+ where TPixel : unmanaged, IPixel
+ {
+ using (Image image = provider.GetImage(TgaDecoder))
+ {
+ image.DebugSave(provider);
+
+ // Using here the reference output instead of the the reference decoder,
+ // because the reference decoder output seems not to be correct for 16bit gray images.
+ image.CompareToReferenceOutput(ImageComparer.Exact, provider);
+ }
+ }
+
+ [Theory]
+ [WithFile(Gray16BitTopRight, PixelTypes.Rgba32)]
+ public void TgaDecoder_CanDecode_Gray_WithTopRightOrigin_16Bit(TestImageProvider provider)
+ where TPixel : unmanaged, IPixel
+ {
+ using (Image image = provider.GetImage(TgaDecoder))
+ {
+ image.DebugSave(provider);
+
+ // Using here the reference output instead of the the reference decoder,
+ // because the reference decoder output seems not to be correct for 16bit gray images.
+ image.CompareToReferenceOutput(ImageComparer.Exact, provider);
+ }
+ }
+
+ [Theory]
+ [WithFile(Gray16BitRleTopLeft, PixelTypes.Rgba32)]
+ public void TgaDecoder_CanDecode_RunLengthEncoded_Gray_16Bit(TestImageProvider provider)
+ where TPixel : unmanaged, IPixel
+ {
+ using (Image image = provider.GetImage(TgaDecoder))
+ {
+ image.DebugSave(provider);
+
+ // Using here the reference output instead of the the reference decoder,
+ // because the reference decoder output seems not to be correct for 16bit gray images.
+ image.CompareToReferenceOutput(ImageComparer.Exact, provider);
+ }
+ }
+
+ [Theory]
+ [WithFile(Gray16BitRleBottomLeft, PixelTypes.Rgba32)]
+ public void TgaDecoder_CanDecode_RunLengthEncoded_Gray_WithBottomLeftOrigin_16Bit(TestImageProvider provider)
+ where TPixel : unmanaged, IPixel
+ {
+ using (Image image = provider.GetImage(TgaDecoder))
+ {
+ image.DebugSave(provider);
+
+ // Using here the reference output instead of the the reference decoder,
+ // because the reference decoder output seems not to be correct for 16bit gray images.
+ image.CompareToReferenceOutput(ImageComparer.Exact, provider);
+ }
+ }
+
+ [Theory]
+ [WithFile(Gray16BitRleBottomRight, PixelTypes.Rgba32)]
+ public void TgaDecoder_CanDecode_RunLengthEncoded_Gray_WithBottomRightOrigin_16Bit(TestImageProvider provider)
+ where TPixel : unmanaged, IPixel
+ {
+ using (Image image = provider.GetImage(TgaDecoder))
+ {
+ image.DebugSave(provider);
+
+ // Using here the reference output instead of the the reference decoder,
+ // because the reference decoder output seems not to be correct for 16bit gray images.
+ image.CompareToReferenceOutput(ImageComparer.Exact, provider);
+ }
+ }
+
+ [Theory]
+ [WithFile(Gray16BitRleTopRight, PixelTypes.Rgba32)]
+ public void TgaDecoder_CanDecode_RunLengthEncoded_Gray_WithTopRightOrigin_16Bit(TestImageProvider provider)
+ where TPixel : unmanaged, IPixel
+ {
+ using (Image image = provider.GetImage(TgaDecoder))
+ {
+ image.DebugSave(provider);
+
+ // Using here the reference output instead of the the reference decoder,
+ // because the reference decoder output seems not to be correct for 16bit gray images.
+ image.CompareToReferenceOutput(ImageComparer.Exact, provider);
+ }
+ }
+
[Theory]
[WithFile(Bit15, PixelTypes.Rgba32)]
- public void TgaDecoder_CanDecode_Uncompressed_15Bit(TestImageProvider provider)
+ public void TgaDecoder_CanDecode_15Bit(TestImageProvider provider)
where TPixel : unmanaged, IPixel
{
using (Image image = provider.GetImage(TgaDecoder))
@@ -56,8 +260,8 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tga
}
[Theory]
- [WithFile(Bit16, PixelTypes.Rgba32)]
- public void TgaDecoder_CanDecode_Uncompressed_16Bit(TestImageProvider provider)
+ [WithFile(Bit16BottomLeft, PixelTypes.Rgba32)]
+ public void TgaDecoder_CanDecode_WithBottomLeftOrigin_16Bit(TestImageProvider provider)
where TPixel : unmanaged, IPixel
{
using (Image image = provider.GetImage(TgaDecoder))
@@ -80,8 +284,44 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tga
}
[Theory]
- [WithFile(Bit24, PixelTypes.Rgba32)]
- public void TgaDecoder_CanDecode_Uncompressed_24Bit(TestImageProvider provider)
+ [WithFile(Bit24TopLeft, PixelTypes.Rgba32)]
+ public void TgaDecoder_CanDecode_WithTopLeftOrigin_24Bit(TestImageProvider provider)
+ where TPixel : unmanaged, IPixel
+ {
+ using (Image image = provider.GetImage(TgaDecoder))
+ {
+ image.DebugSave(provider);
+ TgaTestUtils.CompareWithReferenceDecoder(provider, image);
+ }
+ }
+
+ [Theory]
+ [WithFile(Bit24BottomLeft, PixelTypes.Rgba32)]
+ public void TgaDecoder_CanDecode_WithBottomLeftOrigin_24Bit(TestImageProvider provider)
+ where TPixel : unmanaged, IPixel
+ {
+ using (Image image = provider.GetImage(TgaDecoder))
+ {
+ image.DebugSave(provider);
+ TgaTestUtils.CompareWithReferenceDecoder(provider, image);
+ }
+ }
+
+ [Theory]
+ [WithFile(Bit24TopRight, PixelTypes.Rgba32)]
+ public void TgaDecoder_CanDecode_WithTopRightOrigin_24Bit(TestImageProvider provider)
+ where TPixel : unmanaged, IPixel
+ {
+ using (Image image = provider.GetImage(TgaDecoder))
+ {
+ image.DebugSave(provider);
+ TgaTestUtils.CompareWithReferenceDecoder(provider, image);
+ }
+ }
+
+ [Theory]
+ [WithFile(Bit24BottomRight, PixelTypes.Rgba32)]
+ public void TgaDecoder_CanDecode_WithBottomRightOrigin_24Bit(TestImageProvider provider)
where TPixel : unmanaged, IPixel
{
using (Image image = provider.GetImage(TgaDecoder))
@@ -103,6 +343,30 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tga
}
}
+ [Theory]
+ [WithFile(Bit24RleTopRight, PixelTypes.Rgba32)]
+ public void TgaDecoder_CanDecode_RunLengthEncoded_WithTopRightOrigin_24Bit(TestImageProvider provider)
+ where TPixel : unmanaged, IPixel
+ {
+ using (Image image = provider.GetImage(TgaDecoder))
+ {
+ image.DebugSave(provider);
+ TgaTestUtils.CompareWithReferenceDecoder(provider, image);
+ }
+ }
+
+ [Theory]
+ [WithFile(Bit24RleBottomRight, PixelTypes.Rgba32)]
+ public void TgaDecoder_CanDecode_RunLengthEncoded_WithBottomRightOrigin_24Bit(TestImageProvider provider)
+ where TPixel : unmanaged, IPixel
+ {
+ using (Image image = provider.GetImage(TgaDecoder))
+ {
+ image.DebugSave(provider);
+ TgaTestUtils.CompareWithReferenceDecoder(provider, image);
+ }
+ }
+
[Theory]
[WithFile(Bit24TopLeft, PixelTypes.Rgba32)]
public void TgaDecoder_CanDecode_Palette_WithTopLeftOrigin_24Bit(TestImageProvider provider)
@@ -116,8 +380,152 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tga
}
[Theory]
- [WithFile(Bit32, PixelTypes.Rgba32)]
- public void TgaDecoder_CanDecode_Uncompressed_32Bit(TestImageProvider provider)
+ [WithFile(Bit32TopLeft, PixelTypes.Rgba32)]
+ public void TgaDecoder_CanDecode_WithTopLeftOrigin_32Bit(TestImageProvider provider)
+ where TPixel : unmanaged, IPixel
+ {
+ using (Image image = provider.GetImage(TgaDecoder))
+ {
+ image.DebugSave(provider);
+ TgaTestUtils.CompareWithReferenceDecoder(provider, image);
+ }
+ }
+
+ [Theory]
+ [WithFile(Bit32TopRight, PixelTypes.Rgba32)]
+ public void TgaDecoder_CanDecode_WithTopRightOrigin_32Bit(TestImageProvider provider)
+ where TPixel : unmanaged, IPixel
+ {
+ using (Image image = provider.GetImage(TgaDecoder))
+ {
+ image.DebugSave(provider);
+ TgaTestUtils.CompareWithReferenceDecoder(provider, image);
+ }
+ }
+
+ [Theory]
+ [WithFile(Bit32BottomLeft, PixelTypes.Rgba32)]
+ public void TgaDecoder_CanDecode_WithBottomLeftOrigin_32Bit(TestImageProvider provider)
+ where TPixel : unmanaged, IPixel
+ {
+ using (Image image = provider.GetImage(TgaDecoder))
+ {
+ image.DebugSave(provider);
+ TgaTestUtils.CompareWithReferenceDecoder(provider, image);
+ }
+ }
+
+ [Theory]
+ [WithFile(Bit32BottomRight, PixelTypes.Rgba32)]
+ public void TgaDecoder_CanDecode_WithBottomRightOrigin_32Bit(TestImageProvider provider)
+ where TPixel : unmanaged, IPixel
+ {
+ using (Image image = provider.GetImage(TgaDecoder))
+ {
+ image.DebugSave(provider);
+ TgaTestUtils.CompareWithReferenceDecoder(provider, image);
+ }
+ }
+
+ [Theory]
+ [WithFile(Bit16RleBottomLeft, PixelTypes.Rgba32)]
+ public void TgaDecoder_CanDecode_RunLengthEncoded_WithBottomLeftOrigin_16Bit(TestImageProvider provider)
+ where TPixel : unmanaged, IPixel
+ {
+ using (Image image = provider.GetImage(TgaDecoder))
+ {
+ image.DebugSave(provider);
+ TgaTestUtils.CompareWithReferenceDecoder(provider, image);
+ }
+ }
+
+ [Theory]
+ [WithFile(Bit24RleBottomLeft, PixelTypes.Rgba32)]
+ public void TgaDecoder_CanDecode_RunLengthEncoded_WithBottomLeftOrigin_24Bit(TestImageProvider provider)
+ where TPixel : unmanaged, IPixel
+ {
+ using (Image image = provider.GetImage(TgaDecoder))
+ {
+ image.DebugSave(provider);
+ TgaTestUtils.CompareWithReferenceDecoder(provider, image);
+ }
+ }
+
+ [Theory]
+ [WithFile(Bit32RleTopLeft, PixelTypes.Rgba32)]
+ public void TgaDecoder_CanDecode_RunLengthEncoded_WithTopLeftOrigin_32Bit(TestImageProvider provider)
+ where TPixel : unmanaged, IPixel
+ {
+ using (Image image = provider.GetImage(TgaDecoder))
+ {
+ image.DebugSave(provider);
+ TgaTestUtils.CompareWithReferenceDecoder(provider, image);
+ }
+ }
+
+ [Theory]
+ [WithFile(Bit32RleBottomLeft, PixelTypes.Rgba32)]
+ public void TgaDecoder_CanDecode_RunLengthEncoded_WithBottomLeftOrigin_32Bit(TestImageProvider provider)
+ where TPixel : unmanaged, IPixel
+ {
+ using (Image image = provider.GetImage(TgaDecoder))
+ {
+ image.DebugSave(provider);
+ TgaTestUtils.CompareWithReferenceDecoder(provider, image);
+ }
+ }
+
+ [Theory]
+ [WithFile(Bit32RleTopRight, PixelTypes.Rgba32)]
+ public void TgaDecoder_CanDecode_RunLengthEncoded_WithTopRightOrigin_32Bit(TestImageProvider provider)
+ where TPixel : unmanaged, IPixel
+ {
+ using (Image image = provider.GetImage(TgaDecoder))
+ {
+ image.DebugSave(provider);
+ TgaTestUtils.CompareWithReferenceDecoder(provider, image);
+ }
+ }
+
+ [Theory]
+ [WithFile(Bit32RleBottomRight, PixelTypes.Rgba32)]
+ public void TgaDecoder_CanDecode_RunLengthEncoded_WithBottomRightOrigin_32Bit(TestImageProvider provider)
+ where TPixel : unmanaged, IPixel
+ {
+ using (Image image = provider.GetImage(TgaDecoder))
+ {
+ image.DebugSave(provider);
+ TgaTestUtils.CompareWithReferenceDecoder(provider, image);
+ }
+ }
+
+ [Theory]
+ [WithFile(Bit16PalBottomLeft, PixelTypes.Rgba32)]
+ public void TgaDecoder_CanDecode_WithPaletteBottomLeftOrigin_16Bit(TestImageProvider provider)
+ where TPixel : unmanaged, IPixel
+ {
+ using (Image