Skip to content

Commit

Permalink
refactor: Use byte array instead of bit planes in colored frame.
Browse files Browse the repository at this point in the history
  • Loading branch information
freezy committed Jun 11, 2023
1 parent aed88ed commit 24d4a05
Show file tree
Hide file tree
Showing 30 changed files with 458 additions and 401 deletions.
3 changes: 2 additions & 1 deletion LibDmd.Test/Frame/FrameTests.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Collections.Generic;
using System;
using System.Collections.Generic;
using System.Reactive.Linq;
using System.Threading.Tasks;
using FluentAssertions;
Expand Down
2 changes: 1 addition & 1 deletion LibDmd.Test/RenderGraph/Gray6ConverterSourceTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,7 @@ 00 00 00 00 00 00 00 00 00 00 00 00 22 22 22 22 22 22 22 22 22 22 22 22
00000000");

var coloredFrame = FrameGenerator.FromString(@"
3F 2E 1D 0C 1B 2A 39 08
3F 2E 1D 0C 1B 2A 39 28
0A 0A 0A 0A 30 30 30 30
00 00 00 00 22 22 22 22
00 11 22 33 04 15 26 37",
Expand Down
6 changes: 2 additions & 4 deletions LibDmd.Test/TestBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -104,10 +104,8 @@ protected static async Task AssertFrame<TFrame>(ITestSource<TFrame> source, ITes

Print(expectedFrame, "Expected: ");

receivedFrame.Planes.Length.Should().Be(expectedFrame.Planes.Length);
for (var i = 0; i < receivedFrame.Planes.Length; i++) {
receivedFrame.Planes[i].Should().BeEquivalentTo(expectedFrame.Planes[i]);
}
receivedFrame.Data.Length.Should().Be(expectedFrame.Data.Length);
receivedFrame.Data.Should().BeEquivalentTo(expectedFrame.Data);
receivedFrame.Palette.Should().BeEquivalentTo(expectedFrame.Palette);
receivedFrame.Dimensions.Should().Be(expectedFrame.Dimensions);
}
Expand Down
117 changes: 113 additions & 4 deletions LibDmd/AlphaNumericFrame.cs
Original file line number Diff line number Diff line change
@@ -1,24 +1,32 @@
using System;
using System.Collections.Generic;
using LibDmd.DmdDevice;

namespace LibDmd
{
public class AlphaNumericFrame : ICloneable
public class AlphaNumericFrame : ICloneable, IEqualityComparer<AlphaNumericFrame>
{
/// <summary>
/// The segment data
/// </summary>
public ushort[] SegmentData { get; }
public ushort[] SegmentData { get; private set; }

/// <summary>
/// The extended segment data
/// </summary>
public ushort[] SegmentDataExtended { get; }
public ushort[] SegmentDataExtended { get; private set; }

/// <summary>
/// The segment type
/// </summary>
public NumericalLayout SegmentLayout { get; }
public NumericalLayout SegmentLayout { get; private set; }

public static bool operator == (AlphaNumericFrame x, AlphaNumericFrame y) => Equals(x, y);
public static bool operator != (AlphaNumericFrame x, AlphaNumericFrame y) => !Equals(x, y);

public AlphaNumericFrame()
{
}

public AlphaNumericFrame(NumericalLayout layout, ushort[] segData)
{
Expand All @@ -33,6 +41,107 @@ public AlphaNumericFrame(NumericalLayout layout, ushort[] segData, ushort[] segD
SegmentLayout = layout;
}


public void Update(AlphaNumericFrame frame)
{
SegmentData = frame.SegmentData;
SegmentDataExtended = frame.SegmentDataExtended;
SegmentLayout = frame.SegmentLayout;
}

public object Clone() => new AlphaNumericFrame(SegmentLayout, SegmentData, SegmentDataExtended);

#region Equality

public override bool Equals(object obj)
{
if (ReferenceEquals(null, obj)) {
return false;
}

if (ReferenceEquals(this, obj)) {
return true;
}

if (obj.GetType() != this.GetType()) {
return false;
}

return Equals(this, (AlphaNumericFrame)obj);
}

protected bool Equals(AlphaNumericFrame other) => Equals(this, other);

public override int GetHashCode()
{
unchecked
{
var hashCode = (SegmentData != null ? SegmentData.GetHashCode() : 0);
hashCode = (hashCode * 397) ^ (SegmentDataExtended != null ? SegmentDataExtended.GetHashCode() : 0);
hashCode = (hashCode * 397) ^ (int)SegmentLayout;
return hashCode;
}
}

public static bool Equals(AlphaNumericFrame x, AlphaNumericFrame y)
{
if (ReferenceEquals(x, y)) {
return true;
}

if (ReferenceEquals(x, null)) {
return false;
}

if (ReferenceEquals(y, null)) {
return false;
}

if (x.GetType() != y.GetType()) {
return false;
}

return Compare(x.SegmentData, y.SegmentData)
&& Compare(x.SegmentDataExtended, y.SegmentDataExtended)
&& x.SegmentLayout == y.SegmentLayout;
}

bool IEqualityComparer<AlphaNumericFrame>.Equals(AlphaNumericFrame x, AlphaNumericFrame y) => Equals(x, y);

private static bool Compare(IReadOnlyList<ushort> a, IReadOnlyList<ushort> b)
{
if (a == null && b == null) {
return true;
}

if (a == null || b == null) {
return false;
}

if (a.Count != b.Count) {
return false;
}

for (var i = 0; i < a.Count; i++) {
if (a[i] != b[i]) {
return false;
}
}

return true;
}

public int GetHashCode(AlphaNumericFrame obj)
{
unchecked
{
var hashCode = (obj.SegmentData != null ? obj.SegmentData.GetHashCode() : 0);
hashCode = (hashCode * 397) ^ (obj.SegmentDataExtended != null ? obj.SegmentDataExtended.GetHashCode() : 0);
hashCode = (hashCode * 397) ^ (int)obj.SegmentLayout;
return hashCode;
}
}

#endregion
}
}
82 changes: 28 additions & 54 deletions LibDmd/Common/FrameUtil.cs
Original file line number Diff line number Diff line change
@@ -1,35 +1,38 @@
using System;
using System.Collections;
using System.Linq;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Text;
using LibDmd.Frame;
using NLog;
using Color = System.Windows.Media.Color;

namespace LibDmd.Common
{
/// <summary>
/// Wärchziig zum hin- und härkonvertiärä vo Biud-Datä.
/// Tools for dealing with frame data.
/// </summary>
public class FrameUtil
public static class FrameUtil
{
private static readonly Logger Logger = LogManager.GetCurrentClassLogger();

/// <summary>
/// Tuät es Biud i sini Bitahteiu uifteilä.
/// Splits a pixel array into separate bit planes.
/// </summary>
///
/// <remarks>
/// Mr chas so gseh dass für äs Biud mit viar Graiteen zwe Ebänä fir
/// jedes Bit uisächemid
/// A bit plane is a byte array with the same dimensions as the original frame,
/// but since it's bits, a pixel can be either one or zero, so they are packed
/// into bytes.
///
/// This makes it more efficient to transfer than one byte per pixel, where only
/// 2 or 4 bits are used.
/// </remarks>
///
/// <param name="dim">Dimensionä vom Biud</param>
/// <param name="bitlen">Mit wefu Bits pro Pixu s Biud konstruiärt isch</param>
/// <param name="frame">D datä vom Biud</param>
/// <param name="destPlanes">Bruich das bim zruggäh wenn definiärt.</param>
/// <returns>Än Ebini fir jedes Bit</returns>
/// <param name="dim">Frame dimensions</param>
/// <param name="bitlen">How many bits per pixel, i.e. how many bit planes</param>
/// <param name="frame">Frame data, from top left to bottom right</param>
/// <param name="destPlanes">If set, write the bit planes into this.</param>
/// <returns>Array of bit plans</returns>
public static byte[][] Split(Dimensions dim, int bitlen, byte[] frame, byte[][] destPlanes = null)
{
using (Profiler.Start("FrameUtil.Split")) {
Expand Down Expand Up @@ -89,16 +92,16 @@ public static byte[][] Split(Dimensions dim, int bitlen, byte[] frame, byte[][]
}

/// <summary>
/// Tuät mehreri Bit-Ebänä widr zämäfiägä.
/// Joins an array of bit planes back into one single byte array where one byte represents one pixel.
/// </summary>
/// <param name="dim">Dimensionä vom Biud</param>
/// <param name="bitPlanes">Ä Lischtä vo Ebänä zum zämäfiägä</param>
/// <returns>Äs Graistuifäbiud mit sefu Bittiäfi wiä Ebänä gä wordä sind</returns>
/// <param name="dim">Frame dimensions</param>
/// <param name="bitPlanes">Array of bit planes</param>
/// <returns>Byte array from top left to bottom right</returns>
public static byte[] Join(Dimensions dim, byte[][] bitPlanes)
{
using (Profiler.Start("FrameUtil.Join")) {
var frame = new byte[dim.Surface];

var frame = new byte[dim.Surface];
if (bitPlanes.Length == 2) {
unsafe
{
Expand Down Expand Up @@ -135,7 +138,7 @@ public static byte[] Join(Dimensions dim, byte[][] bitPlanes)
{
var pfEnd = pFrame + frame.Length;

System.Diagnostics.Debug.Assert(bitPlanes.Length == 4);
Debug.Assert(bitPlanes.Length == 4);
fixed (byte* plane0 = &bitPlanes[0][0], plane1 = &bitPlanes[1][0],
plane2 = &bitPlanes[2][0], plane3 = &bitPlanes[3][0])
{
Expand Down Expand Up @@ -238,7 +241,7 @@ public static void SplitIntoRgbPlanes(char[] rgb565, int width, int numLogicalRo
| (r1 & 1) << 2
| (g1 & 1) << 1
| (b1 & 1) << 0;
var indexWithinSubframe = mapAdafruitIndex(x, y, width, height, numLogicalRows);
var indexWithinSubframe = MapAdafruitIndex(x, y, width, height, numLogicalRows);
var indexWithinOutput = subframe * subframeSize + indexWithinSubframe;
dest[indexWithinOutput] = (byte)dotPair;
r0 >>= 1;
Expand All @@ -252,7 +255,7 @@ public static void SplitIntoRgbPlanes(char[] rgb565, int width, int numLogicalRo
}
}

private static int mapAdafruitIndex(int x, int y, int width, int height, int numLogicalRows)
private static int MapAdafruitIndex(int x, int y, int width, int height, int numLogicalRows)
{
var pairOffset = 16;
var logicalRowLengthPerMatrix = 32 * 32 / 2 / numLogicalRows;
Expand All @@ -269,24 +272,6 @@ private static int mapAdafruitIndex(int x, int y, int width, int height, int num
return index;
}

/// <summary>
/// Merges an array of bit planes into one single array.
/// </summary>
/// <param name="planes">Source planes</param>
/// <param name="frame">Destination array</param>
/// <param name="offset">Where to start copying at destination</param>
/// <returns>True if destination array changed, false otherwise.</returns>
public static bool Copy(byte[][] planes, byte[] frame, int offset)
{
var identical = true;
foreach (var plane in planes) {
identical = identical && CompareBuffers(plane, 0, frame, offset, plane.Length);
Buffer.BlockCopy(plane, 0, frame, offset, plane.Length);
offset += plane.Length;
}
return !identical;
}

/// <summary>
/// Copies a byte array to another byte array.
/// </summary>
Expand All @@ -302,7 +287,6 @@ public static bool Copy(byte[] frame, byte[] dest, int offset)
}

//Scale planes by doubling the pixels in each byte

public static readonly ushort[] doublePixel = {
0x0000,0x0003,0x000C,0x000F,0x0030,0x0033,0x003C,0x003F,0x00C0,0x00C3,0x00CC,0x00CF,0x00F0,0x00F3,0x00FC,0x00FF,
0x0300,0x0303,0x030C,0x030F,0x0330,0x0333,0x033C,0x033F,0x03C0,0x03C3,0x03CC,0x03CF,0x03F0,0x03F3,0x03FC,0x03FF,
Expand Down Expand Up @@ -410,7 +394,7 @@ public static byte[] ScaleDoubleRgb(Dimensions dim, byte[] frame)
/// <param name="data"></param>
/// <returns>scaled frame planes</returns>
[Obsolete("Use Scale2x which uses more obvious parameters.")]
public static byte[] Scale2xUgh(Dimensions dim, byte[] data)
public static byte[] Scale2xObsolete(Dimensions dim, byte[] data)
{
byte[] scaledData = new byte[dim.Surface];

Expand Down Expand Up @@ -542,10 +526,11 @@ public static byte[] Scale2XRgb(Dimensions dim, byte[] data)
/// <param name="dim"></param>
/// <param name="data"></param>
/// <returns></returns>
public static byte[][] Scale2x(Dimensions dim, byte[][] data)
[Obsolete]
public static byte[][] Scale2xObsolete(Dimensions dim, byte[][] data)
{
var joinData = Join(dim, data);
var frameData = Scale2xUgh(dim, joinData);
var frameData = Scale2xObsolete(dim, joinData);
return Split(dim, data.Length, frameData);
}

Expand Down Expand Up @@ -603,12 +588,6 @@ public static byte[] ConvertGrayToGray(byte[] srcFrame, params byte[] mapping)
}
}

public static DmdFrame ConvertToRgb24(Dimensions dim, byte[][] planes, Color[] palette)
{
var frame = Join(dim, planes);
return ColorUtil.ColorizeObsolete(dim, frame, palette);
}

public static byte[] NewPlane(Dimensions dim)
{
var count = dim.Width / 8 * dim.Height;
Expand Down Expand Up @@ -661,7 +640,7 @@ public static void OrPlane(byte[] plane, byte[] target)
public static byte[] CombinePlaneWithMask(byte[] planeA, byte[] planeB, byte[] mask)
{
var length = planeA.Length;
System.Diagnostics.Debug.Assert(length == planeB.Length && length == mask.Length);
Debug.Assert(length == planeB.Length && length == mask.Length);
byte[] outPlane = new byte[length];

unchecked
Expand Down Expand Up @@ -881,11 +860,6 @@ public static unsafe bool CompareBuffers(byte[] buffer1, int offset1, byte[] buf
}
}

public static bool CompareBuffersSlow(byte[] a, byte[] b)
{
return a != null && CompareBuffers(a, 0, b, 0, a.Length);
}

/// <summary>
/// Fast byte array comparison, courtesy to https://stackoverflow.com/questions/43289/comparing-two-byte-arrays-in-net/8808245#8808245
///
Expand Down
2 changes: 1 addition & 1 deletion LibDmd/Converter/Pin2Color/Animation.cs
Original file line number Diff line number Diff line change
Expand Up @@ -194,7 +194,7 @@ private byte[][] RenderLCM(byte[][] vpmFrame)
}
else
{
vpmFrame = FrameUtil.Scale2x(Size, vpmFrame);
vpmFrame = FrameUtil.Scale2xObsolete(Size, vpmFrame);
}
}

Expand Down
Loading

0 comments on commit 24d4a05

Please sign in to comment.