Skip to content

Commit

Permalink
fixes & enhancements
Browse files Browse the repository at this point in the history
fixed palletized save variants, added more test images and unit tests, simplified interface, readme update with example
  • Loading branch information
VasilijP committed Oct 17, 2024
1 parent cd79b33 commit 2309a06
Show file tree
Hide file tree
Showing 13 changed files with 224 additions and 149 deletions.
31 changes: 27 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,33 @@ Following features are support:
## Example
----------
```C#
using (var fs = new System.IO.FileStream("test.tga", FileMode.Open, FileAccess.Read, FileShare.Read))
using (var reader = new System.IO.BinaryReader(fs))
// Load the original TGA image
TgaImage actualImage = new("test_8bit.tga");

// Save the image using the specified TgaMode
using FileStream fs = File.Create("test03.tga");
TgaFileFormat.CommonSave(TgaMode.Pal8Rle, fs, actualImage);

// Reload the saved image
TgaImage actualImage2 = new("test03.tga");

// Compare dimensions
int width = actualImage.Width;
int height = actualImage.Height;

Assert.That(width, Is.EqualTo(actualImage2.Width));
Assert.That(height, Is.EqualTo(actualImage2.Height));

// Compare pixel data
for (int x = 0; x < width; x++)
{
var tga = new TgaLib.TgaImage(reader);
System.Windows.Media.Imaging.BitmapSource source = tga.GetBitmap();
for (int y = 0; y < height; y++)
{
actualImage.GetPixelRgba(x, y, out int r1, out int g1, out int b1, out int a1);
actualImage2.GetPixelRgba(x, y, out int r2, out int g2, out int b2, out int a2);
Assert.That(r1, Is.EqualTo(r2), $"Red component mismatch at ({x}, {y})");
Assert.That(g1, Is.EqualTo(g2), $"Green component mismatch at ({x}, {y})");
Assert.That(b1, Is.EqualTo(b2), $"Blue component mismatch at ({x}, {y})");
}
}
```
1 change: 1 addition & 0 deletions tga-info/tga-info.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
<RootNamespace>tga_info</RootNamespace>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<IsPackable>false</IsPackable>
</PropertyGroup>

<ItemGroup>
Expand Down
102 changes: 76 additions & 26 deletions tgalib-core.Tests/TgaFileFormatTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,9 @@ public class TgaFileFormatTests
[TestCase("resources/CBW8.TGA")]
public void Test01_LoadSaveRgb24Rle(string filename)
{
using FileStream ts = new(filename, FileMode.Open, FileAccess.Read, FileShare.Read);
using BinaryReader r = new(ts);
TgaImage tga = new(r);
Image actualImage = tga.GetImage();

TgaFileFormat tff = new();
TgaImage actualImage = new(filename);
using FileStream fs = File.OpenWrite("test01.tga");
tff.CommonSave(TgaMode.Rgb24Rle, fs, actualImage);
fs.Close();
// Assert.Pass();
TgaFileFormat.CommonSave(TgaMode.Rgb24Rle, fs, actualImage);
}

[Test]
Expand All @@ -35,22 +28,17 @@ public void Test01_LoadSaveRgb24Rle(string filename)
[TestCase("resources/CTC24.TGA", false)]
[TestCase("resources/CTC32.TGA", false)]
[TestCase("resources/rgb32rle.tga", true)]
[TestCase("resources/test_24bit.tga", true)]
[TestCase("resources/test_8bit.tga", false)]
[TestCase("resources/test_24bitRLE.tga", false)]
public void Test02_RoundTripRgb24Rle(string filename, bool forceAlpha)
{
using FileStream ts = new(filename, FileMode.Open, FileAccess.Read, FileShare.Read);
using BinaryReader br = new(ts);
TgaImage tga = new(br, forceAlpha);
Image actualImage = tga.GetImage();

TgaFileFormat tff = new();
using FileStream fs = File.OpenWrite("test02.tga");
tff.CommonSave(TgaMode.Rgb24Rle, fs, actualImage);
fs.Close();

using FileStream ts2 = new("test02.tga", FileMode.Open, FileAccess.Read, FileShare.Read);
using BinaryReader br2 = new(ts2);
TgaImage tga2 = new(br2, forceAlpha);
Image actualImage2 = tga2.GetImage();
TgaImage actualImage = new(filename, forceAlpha);

using FileStream fs = File.Create("test02.tga");
TgaFileFormat.CommonSave(TgaMode.Rgb24Rle, fs, actualImage);

TgaImage actualImage2 = new("test02.tga", forceAlpha);

int width = actualImage.Width;
int height = actualImage.Height;
Expand All @@ -64,9 +52,71 @@ public void Test02_RoundTripRgb24Rle(string filename, bool forceAlpha)
{
actualImage.GetPixelRgba(x, y, out int r1, out int g1, out int b1, out int a1);
actualImage2.GetPixelRgba(x, y, out int r2, out int g2, out int b2, out int a2);
Assert.That(r1, Is.EqualTo(r2));
Assert.That(g1, Is.EqualTo(g2));
Assert.That(b1, Is.EqualTo(b2));
Assert.That(r1, Is.EqualTo(r2), $"Red component mismatch at ({x}, {y})");
Assert.That(g1, Is.EqualTo(g2), $"Green component mismatch at ({x}, {y})");
Assert.That(b1, Is.EqualTo(b2), $"Blue component mismatch at ({x}, {y})");
}
}
}

[Test]
[TestCase("resources/UBW8.TGA", TgaMode.Pal8Unc, false)]
[TestCase("resources/UCM8.TGA", TgaMode.Pal8Unc, false)]
[TestCase("resources/UTC16.TGA", TgaMode.Pal8Unc, false)]
[TestCase("resources/UTC24.TGA", TgaMode.Pal8Unc, false)]
[TestCase("resources/UTC32.TGA", TgaMode.Pal8Unc, false)]
[TestCase("resources/UBW8.TGA", TgaMode.Pal8Unc, true)]
[TestCase("resources/CBW8.TGA", TgaMode.Pal8Unc, false)]
[TestCase("resources/CCM8.TGA", TgaMode.Pal8Unc, false)]
[TestCase("resources/CTC16.TGA", TgaMode.Pal8Unc, false)]
[TestCase("resources/CTC24.TGA", TgaMode.Pal8Unc, false)]
[TestCase("resources/CTC32.TGA", TgaMode.Pal8Unc, false)]
[TestCase("resources/test_8bit.tga", TgaMode.Pal8Unc, false)]
[TestCase("resources/test_24bitRLE.tga", TgaMode.Pal8Unc, false)]
[TestCase("resources/test_8bitRLE.tga", TgaMode.Pal8Unc, false)]
[TestCase("resources/UBW8.TGA", TgaMode.Pal8Rle, false)]
[TestCase("resources/UCM8.TGA", TgaMode.Pal8Rle, false)]
[TestCase("resources/UTC16.TGA", TgaMode.Pal8Rle, false)]
[TestCase("resources/UTC24.TGA", TgaMode.Pal8Rle, false)]
[TestCase("resources/UTC32.TGA", TgaMode.Pal8Rle, false)]
[TestCase("resources/UBW8.TGA", TgaMode.Pal8Rle, true)]
[TestCase("resources/CBW8.TGA", TgaMode.Pal8Rle, false)]
[TestCase("resources/CCM8.TGA", TgaMode.Pal8Rle, false)]
[TestCase("resources/CTC16.TGA", TgaMode.Pal8Rle, false)]
[TestCase("resources/CTC24.TGA", TgaMode.Pal8Rle, false)]
[TestCase("resources/CTC32.TGA", TgaMode.Pal8Rle, false)]
[TestCase("resources/test_8bit.tga", TgaMode.Pal8Rle, false)]
[TestCase("resources/test_24bitRLE.tga", TgaMode.Pal8Rle, false)]
[TestCase("resources/test_8bitRLE.tga", TgaMode.Pal8Rle, false)]
public void Test03_RoundTripPal8(string filename, TgaMode mode, bool forceAlpha)
{
// Load the original TGA image
TgaImage actualImage = new(filename, forceAlpha);

// Save the image using the specified TgaMode
using FileStream fs = File.Create("test03.tga");
TgaFileFormat.CommonSave(mode, fs, actualImage);

// Reload the saved image
TgaImage actualImage2 = new("test03.tga", forceAlpha);

// Compare dimensions
int width = actualImage.Width;
int height = actualImage.Height;

Assert.That(width, Is.EqualTo(actualImage2.Width));
Assert.That(height, Is.EqualTo(actualImage2.Height));

// Compare pixel data
for (int x = 0; x < width; x++)
{
for (int y = 0; y < height; y++)
{
actualImage.GetPixelRgba(x, y, out int r1, out int g1, out int b1, out int a1);
actualImage2.GetPixelRgba(x, y, out int r2, out int g2, out int b2, out int a2);
Assert.That(r1, Is.EqualTo(r2), $"Red component mismatch at ({x}, {y})");
Assert.That(g1, Is.EqualTo(g2), $"Green component mismatch at ({x}, {y})");
Assert.That(b1, Is.EqualTo(b2), $"Blue component mismatch at ({x}, {y})");
}
}
}
Expand Down
Binary file added tgalib-core.Tests/resources/test_24bit.bmp
Binary file not shown.
Binary file added tgalib-core.Tests/resources/test_24bit.tga
Binary file not shown.
Binary file added tgalib-core.Tests/resources/test_24bitRLE.tga
Binary file not shown.
Binary file added tgalib-core.Tests/resources/test_8bit.tga
Binary file not shown.
Binary file added tgalib-core.Tests/resources/test_8bitRLE.tga
Binary file not shown.
16 changes: 15 additions & 1 deletion tgalib-core.Tests/tgalib-core.Tests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
<PropertyGroup>
<TargetFrameworks>net8.0</TargetFrameworks>
<RootNamespace>tgalib_core.Tests</RootNamespace>

<IsPackable>false</IsPackable>
</PropertyGroup>

Expand Down Expand Up @@ -60,6 +59,21 @@
<None Update="resources\UTC32.TGA">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="resources\test_8bit.tga">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="resources\test_24bit.bmp">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="resources\test_24bit.tga">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="resources\test_24bitRLE.tga">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="resources\test_8bitRLE.tga">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
</ItemGroup>

</Project>
68 changes: 0 additions & 68 deletions tgalib-core/Image.cs

This file was deleted.

40 changes: 20 additions & 20 deletions tgalib-core/TgaFileFormat.cs
Original file line number Diff line number Diff line change
Expand Up @@ -41,10 +41,10 @@
* header[17] = 0; 0 bits for Alpha Channel, non-interleaved, Origin in lower left-hand corner
*
* @author Peter Truchly */
public sealed class TgaFileFormat
public static class TgaFileFormat
{
// Common save routine.
public void CommonSave(TgaMode curTgaMode, Stream stream, Image g)
public static void CommonSave(TgaMode curTgaMode, Stream stream, TgaImage g)
{
// ReSharper disable once SwitchStatementHandlesSomeKnownEnumValuesWithDefault
switch (curTgaMode)
Expand All @@ -58,10 +58,10 @@ public void CommonSave(TgaMode curTgaMode, Stream stream, Image g)
}

// TGA RGB 24 RLE save code.
private static void TgaRgb24RleSave(Stream tgaFile, Image image)
private static void TgaRgb24RleSave(Stream tgaFile, TgaImage image)
{
int width = image.Width;
int height = image.Height;
int width = image.TgaHeader.Width;
int height = image.TgaHeader.Height;

// 1| 2| 3| 4| 5| 6| 7| 8| 9|10|11|12| 13| 14| 15| 16|17|18
byte[] header = [ 0, 0,10, 0, 0, 0, 0, 0, 0, 0, 0, 0,(byte)(width&255),(byte)((width>>8)&255),(byte)(height&255),(byte)((height>>8)&255),24, 0];
Expand All @@ -84,10 +84,10 @@ private static void TgaRgb24RleSave(Stream tgaFile, Image image)
// Stops when entire image is searched or palette is full (max 256 colors)
// Colors which are not in palette are written as color 0 (there should be no more than 256 colors in image)
// Performs RLE compression
private static void TgaPal8RleSave(Stream tgaFile, Image image)
private static void TgaPal8RleSave(Stream tgaFile, TgaImage image)
{
int width = image.Width;
int height = image.Height;
int width = image.TgaHeader.Width;
int height = image.TgaHeader.Height;
const int maxColors = 256;

//palette (BGR)
Expand All @@ -109,8 +109,8 @@ private static void TgaPal8RleSave(Stream tgaFile, Image image)
byte[] header = [ 0, 1, 9, 0, 0, (byte)(pal.Count&255),(byte)((pal.Count>>8)&255),24, 0, 0, 0, 0, (byte)(width&255),(byte)((width>>8)&255),(byte)(height&255),(byte)((height>>8)&255), 8, 0 ];
tgaFile.Write(header);

// write palette to file
for (int rpc = 0; rpc < maxColors; rpc++) { tgaFile.Write(pal[rpc]); }
// write palette to file & init hashmap
foreach (byte[] color in pal) { tgaFile.Write(color); }

// run through image and code pixels as indexes using dictionary
RleCompressor rlec = new(tgaFile);
Expand All @@ -130,12 +130,12 @@ private static void TgaPal8RleSave(Stream tgaFile, Image image)
// Seraches image for unique colors.
// Stops when entire image is searched or palette is full (max 256 colors)
// Colors which are not in palette are written as color 0 (there should be no more than 256 colors in image)
private void TgaPal8UncSave(Stream tgaFile, Image image)
private static void TgaPal8UncSave(Stream tgaFile, TgaImage image)
{
//header
int width = image.Width;
int height = image.Height;
const int pallen = 256; //change also header!
int width = image.TgaHeader.Width;
int height = image.TgaHeader.Height;
const int maxColors = 256;

//palette ( colors are stored as BGR in file )
List<byte[]> pal = [];
Expand All @@ -147,13 +147,13 @@ private void TgaPal8UncSave(Stream tgaFile, Image image)
{
image.GetPixelRgba(x, y, out int r, out int g, out int b, out int a);
string key = $"{r & 255}|{g & 255}|{b & 255}";
if (hm.ContainsKey(key) || hm.Count >= pallen) continue;
if (hm.ContainsKey(key) || hm.Count >= maxColors) continue;
hm.TryAdd(key, pal.Count); // index of a color in palette
pal.Add([(byte)(b & 255), (byte)(g & 255), (byte)(r & 255)]); //palette entry
}

// 1| 2| 3| 4| 5| 6| 7| 8| 9|10|11|12| 13| 14| 15| 16|17|18
byte[] header = [ 0, 1, 1, 0, 0, (byte)(pallen&255),(byte)((pallen>>8)&255),24, 0, 0, 0, 0, (byte)(width&255),(byte)((width>>8)&255),(byte)(height&255),(byte)((height>>8)&255), 8, 0 ];
// 1| 2| 3| 4| 5| 6| 7| 8| 9|10|11|12| 13| 14| 15| 16|17|18
byte[] header = [ 0, 1, 1, 0, 0, (byte)(pal.Count & 255),(byte)((pal.Count >> 8)&255),24, 0, 0, 0, 0, (byte)(width&255),(byte)((width>>8)&255),(byte)(height&255),(byte)((height>>8)&255), 8, 0 ];
tgaFile.Write(header);

// write palette to file & init hashmap
Expand All @@ -172,10 +172,10 @@ private void TgaPal8UncSave(Stream tgaFile, Image image)
}

// TGA RGB 24 UNC save code.
private static void TgaRgb24UncSave(Stream tgaFile, Image image)
private static void TgaRgb24UncSave(Stream tgaFile, TgaImage image)
{
int width = image.Width;
int height = image.Height;
int width = image.TgaHeader.Width;
int height = image.TgaHeader.Height;

// 1| 2| 3| 4| 5| 6| 7| 8| 9|10|11|12| 13| 14| 15| 16|17|18
byte[] header = [0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0,(byte)(width&255),(byte)((width>>8)&255),(byte)(height&255),(byte)((height>>8)&255),24, 0];
Expand Down
Loading

0 comments on commit 2309a06

Please sign in to comment.