Skip to content

Commit

Permalink
Merge branch 'dev'
Browse files Browse the repository at this point in the history
  • Loading branch information
stevemonaco committed Apr 5, 2020
2 parents db5195f + eced039 commit 5e24fec
Show file tree
Hide file tree
Showing 51 changed files with 863 additions and 180 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

namespace ImageMagitek.UnitTests
{
public class StreamExtensionTestCases
public class StreamReadExtensionTestCases
{
public static byte[] singleArray = new byte[] { 0b10011101 };
public static byte[] largeArray = new byte[] { 0b10110011, 0b11111111, 0b01010101, 0b11001100, 0b10000001 };
Expand Down Expand Up @@ -42,6 +42,7 @@ public static IEnumerable<TestCaseData> ReadShiftedCases

yield return new TestCaseData(largeArray, new FileBitAddress(4, 0), 8, new byte[] { 0b10000001 });
yield return new TestCaseData(largeArray, new FileBitAddress(4, 1), 7, new byte[] { 0b00000010 });
yield return new TestCaseData(largeArray, new FileBitAddress(3, 1), 8, new byte[] { 0b10011001 });
yield return new TestCaseData(largeArray, new FileBitAddress(4, 7), 1, new byte[] { 0b10000000 });

yield return new TestCaseData(largeArray, new FileBitAddress(2, 1), 14, new byte[] { 0b10101011, 0b10011000 });
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@
namespace ImageMagitek.UnitTests
{
[TestFixture]
public class StreamExtensionTests
public class StreamReadExtensionTests
{
[TestCaseSource(typeof(StreamExtensionTestCases), "ReadUnshiftedCases")]
[TestCaseSource(typeof(StreamReadExtensionTestCases), "ReadUnshiftedCases")]
public void ReadUnshifted_AsExpected(byte[] data, FileBitAddress offset, int numBits, byte[] expected)
{
var stream = new MemoryStream(data);
Expand All @@ -17,7 +17,7 @@ public void ReadUnshifted_AsExpected(byte[] data, FileBitAddress offset, int num
CollectionAssert.AreEqual(expected, actual);
}

[TestCaseSource(typeof(StreamExtensionTestCases), "ReadShiftedCases")]
[TestCaseSource(typeof(StreamReadExtensionTestCases), "ReadShiftedCases")]
public void ReadShifted_AsExpected(byte[] data, FileBitAddress offset, int numBits, byte[] expected)
{
var stream = new MemoryStream(data);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
using NUnit.Framework;
using System;
using System.Collections.Generic;
using System.Text;

namespace ImageMagitek.UnitTests.ExtensionMethodTests
{
public class StreamWriteExtensionTestCases
{
public static byte[] singleArray = new byte[] { 0b10011101 };
public static byte[] largeArray = new byte[] { 0b10110011, 0b11111111, 0b01010101, 0b11001100, 0b10000001 };

public static IEnumerable<TestCaseData> WriteUnshiftedCases
{
get
{
yield return new TestCaseData(singleArray.Clone(), new FileBitAddress(0, 0), 1,
new byte[] { 0b00000000 }, new byte[] { 0b00011101 });
yield return new TestCaseData(singleArray.Clone(), new FileBitAddress(0, 2), 2,
new byte[] { 0b00110000 }, new byte[] { 0b10111101 });
yield return new TestCaseData(singleArray.Clone(), new FileBitAddress(0, 2), 6,
new byte[] { 0b00111110 }, new byte[] { 0b10111110 });

yield return new TestCaseData(largeArray.Clone(), new FileBitAddress(1, 3), 14,
new byte[] { 0b00010101, 0b11010100, 0b00000000 }, new byte[] { 0b10110011, 0b11110101, 0b11010100, 0b01001100, 0b10000001 });

yield return new TestCaseData(largeArray.Clone(), new FileBitAddress(0, 1), 38,
new byte[] { 0b00010101, 0b11010100, 0b01111111, 0b01010100, 0b01101010 }, new byte[] { 0b10010101, 0b11010100, 0b01111111, 0b01010100, 0b01101011 });

yield return new TestCaseData(largeArray.Clone(), new FileBitAddress(0, 0), 40,
new byte[] { 0b00010101, 0b11010100, 0b01111111, 0b01010100, 0b01101010 }, new byte[] { 0b00010101, 0b11010100, 0b01111111, 0b01010100, 0b01101010 });

// Cases with unmasked bits outside of writing range
yield return new TestCaseData(singleArray.Clone(), new FileBitAddress(0, 0), 1,
new byte[] { 0b00000010 }, new byte[] { 0b00011101 });

yield return new TestCaseData(largeArray.Clone(), new FileBitAddress(1, 3), 14,
new byte[] { 0b00010101, 0b11010100, 0b01111111 }, new byte[] { 0b10110011, 0b11110101, 0b11010100, 0b01001100, 0b10000001 });
}
}

//public static byte[] singleArray = new byte[] { 0b10011101 };
//public static byte[] largeArray = new byte[] { 0b10110011, 0b11111111, 0b01010101, 0b11001100, 0b10000001 };

public static IEnumerable<TestCaseData> WriteShiftedCases
{
get
{
yield return new TestCaseData(singleArray.Clone(), new FileBitAddress(0, 0), 1,
new byte[] { 0b00000000 }, new byte[] { 0b00011101 });
yield return new TestCaseData(singleArray.Clone(), new FileBitAddress(0, 2), 2,
new byte[] { 0b11000000 }, new byte[] { 0b10111101 });
yield return new TestCaseData(singleArray.Clone(), new FileBitAddress(0, 2), 6,
new byte[] { 0b11111000 }, new byte[] { 0b10111110 });

yield return new TestCaseData(largeArray.Clone(), new FileBitAddress(1, 3), 14,
new byte[] { 0b10101110, 0b10100000, 0b00000000 }, new byte[] { 0b10110011, 0b11110101, 0b11010100, 0b01001100, 0b10000001 });

yield return new TestCaseData(largeArray.Clone(), new FileBitAddress(0, 1), 38,
new byte[] { 0b00101011, 0b10101000, 0b11111110, 0b10101000, 0b11010100 }, new byte[] { 0b10010101, 0b11010100, 0b01111111, 0b01010100, 0b01101011 });

yield return new TestCaseData(largeArray.Clone(), new FileBitAddress(0, 0), 40,
new byte[] { 0b00010101, 0b11010100, 0b01111111, 0b01010100, 0b01101010 }, new byte[] { 0b00010101, 0b11010100, 0b01111111, 0b01010100, 0b01101010 });

// Cases with unmasked bits outside of writing range
yield return new TestCaseData(singleArray.Clone(), new FileBitAddress(0, 0), 1,
new byte[] { 0b00000010 }, new byte[] { 0b00011101 });

yield return new TestCaseData(largeArray.Clone(), new FileBitAddress(1, 3), 14,
new byte[] { 0b10101110, 0b10100011, 0b11111000 }, new byte[] { 0b10110011, 0b11110101, 0b11010100, 0b01001100, 0b10000001 });
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
using NUnit.Framework;
using System.IO;
using ImageMagitek.ExtensionMethods;

namespace ImageMagitek.UnitTests.ExtensionMethodTests
{
[TestFixture]
public class StreamWriteExtensionTests
{
[TestCaseSource(typeof(StreamWriteExtensionTestCases), "WriteUnshiftedCases")]
public void WriteUnshifted_AsExpected(byte[] data, FileBitAddress offset, int numBits, byte[] writeData, byte[] expected)
{
using var stream = new MemoryStream(data);
stream.WriteUnshifted(offset, numBits, writeData);

stream.Seek(0, SeekOrigin.Begin);
var actual = new byte[expected.Length];
stream.Read(actual);

CollectionAssert.AreEqual(expected, actual);
}

[TestCaseSource(typeof(StreamWriteExtensionTestCases), "WriteShiftedCases")]
public void WriteShifted_AsExpected(byte[] data, FileBitAddress offset, int numBits, byte[] writeData, byte[] expected)
{
using var stream = new MemoryStream(data);
stream.WriteShifted(offset, numBits, writeData);

stream.Seek(0, SeekOrigin.Begin);
var actual = new byte[expected.Length];
stream.Read(actual);

CollectionAssert.AreEqual(expected, actual);
}
}
}
8 changes: 3 additions & 5 deletions ImageMagitek/Codec/CodecFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,6 @@ namespace ImageMagitek.Codec
public class CodecFactory : ICodecFactory
{
private readonly Dictionary<string, GraphicsFormat> _formats;
private const int _defaultWidth = 8;
private const int _defaultHeight = 8;

public Palette DefaultPalette { get; set; }

Expand All @@ -25,11 +23,11 @@ public IGraphicsCodec GetCodec(string codecName)
//case "NES 1bpp":
// return new Nes1bppCodec(_defaultWidth, _defaultHeight);
case "SNES 3bpp":
return new Snes3bppCodec(_defaultWidth, _defaultHeight);
return new Snes3bppCodec();
case "PSX 4bpp":
return new Psx4bppCodec(_defaultWidth, _defaultHeight);
return new Psx4bppCodec();
case "PSX 8bpp":
return new Psx8bppCodec(_defaultWidth, _defaultHeight);
return new Psx8bppCodec();
//case "PSX 16bpp":
// return new Psx16bppCodec(width, height);
//case "PSX 24bpp":
Expand Down
21 changes: 19 additions & 2 deletions ImageMagitek/Codec/DirectCodec.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,20 @@ public abstract class DirectCodec : IDirectCodec
public PixelColorType ColorType => PixelColorType.Direct;
public abstract int ColorDepth { get; }
public abstract int StorageSize { get; }
public int RowStride { get; }
public int ElementStride { get; }
public abstract int RowStride { get; }
public abstract int ElementStride { get; }

public virtual ReadOnlySpan<byte> ForeignBuffer => _foreignBuffer;
protected byte[] _foreignBuffer;

public virtual ColorRgba32[,] NativeBuffer => _nativeBuffer;

public abstract bool CanResize { get; }
public abstract int WidthResizeIncrement { get; }
public abstract int HeightResizeIncrement { get; }
public abstract int DefaultWidth { get; }
public abstract int DefaultHeight { get; }

protected ColorRgba32[,] _nativeBuffer;

public abstract ColorRgba32[,] DecodeElement(ArrangerElement el, ReadOnlySpan<byte> encodedBuffer);
Expand Down Expand Up @@ -59,5 +66,15 @@ public virtual void WriteElement(ArrangerElement el, ReadOnlySpan<byte> encodedB
fs.Seek(el.FileAddress.FileOffset, SeekOrigin.Begin);
fs.Write(encodedBuffer);
}

public int GetPreferredWidth(int width)
{
throw new NotImplementedException();
}

public int GetPreferredHeight(int height)
{
throw new NotImplementedException();
}
}
}
4 changes: 2 additions & 2 deletions ImageMagitek/Codec/Generalized/ImageProperty.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,13 @@ public class ImageProperty
/// <summary>
/// Placement pattern of pixels within a row
/// </summary>
public BroadcastList<int> RowPixelPattern { get; private set; }
public RepeatList RowPixelPattern { get; private set; }

public ImageProperty(int colorDepth, bool rowInterlace, IEnumerable<int> rowPixelPattern)
{
ColorDepth = colorDepth;
RowInterlace = rowInterlace;
RowPixelPattern = new BroadcastList<int>(rowPixelPattern);
RowPixelPattern = new RepeatList(rowPixelPattern);
}
}
}
76 changes: 55 additions & 21 deletions ImageMagitek/Codec/Generalized/IndexedGraphicsCodec.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using ImageMagitek.Colors;
using ImageMagitek.ExtensionMethods;

Expand All @@ -24,6 +25,13 @@ public class IndexedGraphicsCodec : IIndexedCodec
protected byte[] _foreignBuffer;

public virtual byte[,] NativeBuffer => _nativeBuffer;

public int DefaultWidth => Format.DefaultWidth;
public int DefaultHeight => Format.DefaultHeight;
public bool CanResize => !Format.FixedSize;
public int WidthResizeIncrement { get; }
public int HeightResizeIncrement => 1;

protected byte[,] _nativeBuffer;

/// <summary>
Expand All @@ -44,6 +52,10 @@ public IndexedGraphicsCodec(GraphicsFormat format, Palette defaultPalette)
Name = format.Name;
DefaultPalette = defaultPalette;
AllocateBuffers();

// Consider implementing resize increment with more accurate LCM approach
// https://stackoverflow.com/questions/147515/least-common-multiple-for-3-or-more-numbers
WidthResizeIncrement = format.ImageProperties.Max(x => x.RowPixelPattern.Count);
}

private void AllocateBuffers()
Expand Down Expand Up @@ -72,50 +84,62 @@ private void AllocateBuffers()
_bitStream.SeekAbsolute(0);

int plane = 0;
int pos;
int scanlinePosition;

// Deinterlace into separate bitplanes
foreach (ImageProperty ip in Format.ImageProperties)
{
pos = 0;
if (ip.RowInterlace)
{
for (int y = 0; y < el.Height; y++)
{
for (int curPlane = plane; curPlane < plane + ip.ColorDepth; curPlane++)
{
pos = y * el.Height;
scanlinePosition = y * el.Width;
for (int x = 0; x < el.Width; x++)
ElementData[Format.MergePlanePriority[curPlane]][pos + ip.RowPixelPattern[x]] = (byte)_bitStream.ReadBit();
{
var mergePlane = Format.MergePlanePriority[curPlane];
var pixelPosition = scanlinePosition + ip.RowPixelPattern[x];
ElementData[mergePlane][pixelPosition] = (byte)_bitStream.ReadBit();
}
}
}
}
else // Non-interlaced
{
for (int y = 0; y < el.Height; y++, pos += el.Width)
for (int y = 0; y < el.Height; y++)
{
for (int x = 0; x < el.Width; x++)
{
scanlinePosition = y * el.Width;
for (int curPlane = plane; curPlane < plane + ip.ColorDepth; curPlane++)
ElementData[Format.MergePlanePriority[curPlane]][pos + ip.RowPixelPattern[x]] = (byte)_bitStream.ReadBit();
{
var mergePlane = Format.MergePlanePriority[curPlane];
int pixelPosition = scanlinePosition + ip.RowPixelPattern[x];
ElementData[mergePlane][pixelPosition] = (byte)_bitStream.ReadBit();
}
}
}
}

plane += ip.ColorDepth;
}

// Merge into foreign pixel data
// Merge into foreign pixel data
byte foreignPixelData;

for (pos = 0; pos < MergedData.Length; pos++)
for (scanlinePosition = 0; scanlinePosition < MergedData.Length; scanlinePosition++)
{
foreignPixelData = 0;
for (int i = 0; i < Format.ColorDepth; i++)
foreignPixelData |= (byte)(ElementData[i][pos] << i); // Works for SNES image data and palettes, may need customization later
MergedData[pos] = foreignPixelData;
foreignPixelData |= (byte)(ElementData[i][scanlinePosition] << i); // Works for SNES image data and palettes, may need customization later
MergedData[scanlinePosition] = foreignPixelData;
}

pos = 0;
scanlinePosition = 0;
for (int y = 0; y < Height; y++)
for (int x = 0; x < Width; x++, pos++)
_nativeBuffer[x, y] = MergedData[pos];
for (int x = 0; x < Width; x++, scanlinePosition++)
_nativeBuffer[x, y] = MergedData[scanlinePosition];

return NativeBuffer;
}
Expand Down Expand Up @@ -310,16 +334,12 @@ public ReadOnlySpan<byte> EncodeElement(ArrangerElement el, byte[,] imageBuffer)
public virtual ReadOnlySpan<byte> ReadElement(ArrangerElement el)
{
var buffer = new byte[(StorageSize + 7) / 8];
var bitStream = BitStream.OpenRead(buffer, StorageSize);

var fs = el.DataFile.Stream;

// TODO: Add bit granularity to seek and read
if (el.FileAddress + StorageSize > fs.Length * 8)
return null;

bitStream.SeekAbsolute(0);
fs.ReadUnshifted(el.FileAddress, StorageSize, buffer);
fs.ReadShifted(el.FileAddress, StorageSize, buffer);

return buffer;
}
Expand All @@ -329,10 +349,24 @@ public virtual ReadOnlySpan<byte> ReadElement(ArrangerElement el)
/// </summary>
public virtual void WriteElement(ArrangerElement el, ReadOnlySpan<byte> encodedBuffer)
{
// TODO: Add bit granularity to seek and read
var fs = el.DataFile.Stream;
fs.Seek(el.FileAddress.FileOffset, SeekOrigin.Begin);
fs.Write(encodedBuffer);
fs.WriteShifted(el.FileAddress, StorageSize, encodedBuffer);
}

public int GetPreferredWidth(int width)
{
if (!CanResize)
return DefaultWidth;

return Math.Clamp(width - width % WidthResizeIncrement, WidthResizeIncrement, int.MaxValue);
}

public int GetPreferredHeight(int height)
{
if (!CanResize)
return DefaultHeight;

return Math.Clamp(height - height % HeightResizeIncrement, HeightResizeIncrement, int.MaxValue);
}
}
}
8 changes: 8 additions & 0 deletions ImageMagitek/Codec/IGraphicsCodec.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,5 +18,13 @@ public interface IGraphicsCodec
int StorageSize { get; }
int RowStride { get; }
int ElementStride { get; }

int DefaultWidth { get; }
int DefaultHeight { get; }
bool CanResize { get; }
int WidthResizeIncrement { get; }
int HeightResizeIncrement { get; }
int GetPreferredWidth(int width);
int GetPreferredHeight(int height);
}
}
Loading

0 comments on commit 5e24fec

Please sign in to comment.