Skip to content

Commit

Permalink
Merge pull request #1096 from WildernessLabs/2bpp_epaper
Browse files Browse the repository at this point in the history
Add 2bpp indexed buffer +minor cleanup on 4bpp indexed buffer
  • Loading branch information
ctacke authored Dec 10, 2024
2 parents b9150f4 + 6b1e5c3 commit cd36478
Show file tree
Hide file tree
Showing 2 changed files with 232 additions and 15 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,218 @@
using Meadow.Peripherals.Displays;
using System;
using System.Linq;

namespace Meadow.Foundation.Graphics.Buffers
{
/// <summary>
/// Represents a 2bpp pixel buffer with indexed colors
/// Each pixel is represented by 2 bits, allowing for 4 distinct colors
/// </summary>
public class BufferIndexed2 : PixelBufferBase
{
/// <summary>
/// Color mode of the buffer
/// </summary>
public override ColorMode ColorMode => ColorMode.Format2bppIndexed;

/// <summary>
/// The indexed colors as a 4 element array of Color values
/// </summary>
public readonly Color[] IndexedColors = new Color[4];

/// <summary>
/// Create a new BufferIndexed2 object
/// </summary>
/// <param name="width">The width in pixels</param>
/// <param name="height">The height in pixels</param>
/// <param name="buffer">The backing buffer</param>
public BufferIndexed2(int width, int height, byte[] buffer) : base(width, height, buffer) { }

/// <summary>
/// Create a new BufferIndexed2 object
/// </summary>
/// <param name="width">The width in pixels</param>
/// <param name="height">The height in pixels</param>
public BufferIndexed2(int width, int height) : base(width, height) { }

/// <summary>
/// Create a new BufferIndexed2 object
/// </summary>
public BufferIndexed2() : base() { }

/// <summary>
/// Fill buffer with a color
/// </summary>
/// <param name="color">The fill color</param>
public override void Fill(Color color)
{
byte colorValue = (byte)GetIndexForColor(color);
Buffer[0] = (byte)(colorValue << 2 | colorValue << 4 | colorValue << 6);

int arrayMidPoint = Buffer.Length / 2;
int copyLength;

for (copyLength = 1; copyLength < arrayMidPoint; copyLength <<= 1)
{
Array.Copy(Buffer, 0, Buffer, copyLength, copyLength);
}

Array.Copy(Buffer, 0, Buffer, copyLength, Buffer.Length - copyLength);
}

/// <summary>
/// Fill a rectangular area with a color
/// </summary>
/// <param name="x">X start position in pixels</param>
/// <param name="y">Y start position in pixels</param>
/// <param name="width">Width in pixels</param>
/// <param name="height">Height in pixels</param>
/// <param name="color">The fill color</param>
public override void Fill(int x, int y, int width, int height, Color color)
{
if (x < 0 || x + width > Width ||
y < 0 || y + height > Height)
{
throw new ArgumentOutOfRangeException();
}

//TODO optimize
var index = GetIndexForColor(color);

for (int i = 0; i < width; i++)
{
for (int j = 0; j < height; j++)
{
SetPixel(x + i, y + j, index);
}
}
}

/// <summary>
/// Get the pixel color at a given coordinate
/// </summary>
/// <param name="x">The X pixel position</param>
/// <param name="y">The Y pixel position</param>
/// <returns>The pixel color</returns>
public override Color GetPixel(int x, int y)
{
var index = GetColorIndexForPixel(x, y);
return IndexedColors[index];
}

/// <summary>
/// Set the pixel color
/// </summary>
/// <param name="x">X pixel position</param>
/// <param name="y">Y pixel position</param>
/// <param name="color">The pixel color</param>
public override void SetPixel(int x, int y, Color color)
{
var index = GetIndexForColor(color);
SetPixel(x, y, index);
}

/// <summary>
/// Set the pixel using a color index
/// </summary>
/// <param name="x">X pixel position</param>
/// <param name="y">Y pixel position</param>
/// <param name="colorIndex">The color index (0-3)</param>
public void SetPixel(int x, int y, int colorIndex)
{
int byteIndex = (y * Width + x) >> 2; // divide by 4 to find the byte
int pixelOffset = (x & 0x03) << 1; // (x % 4)*2 bits offset

// Clear current 2 bits
Buffer[byteIndex] &= (byte)~(0x03 << pixelOffset);
// Set new bits
Buffer[byteIndex] |= (byte)((colorIndex & 0x03) << pixelOffset);
}

/// <summary>
/// Invert the pixel color
/// </summary>
/// <param name="x">x position of pixel</param>
/// <param name="y">y position of pixel</param>
public override void InvertPixel(int x, int y)
{
throw new NotImplementedException("InvertPixel not supported for indexed buffers");
}

/// <summary>
/// Write a buffer into this buffer at a specified location
/// </summary>
/// <param name="x">x origin</param>
/// <param name="y">y origin</param>
/// <param name="buffer">buffer to write</param>
public override void WriteBuffer(int x, int y, IPixelBuffer buffer)
{
if (buffer.ColorMode == ColorMode &&
x % 4 == 0 &&
buffer.Width % 4 == 0)
{
// We can do a direct block copy row by row
int sourceIndex, destinationIndex;
int length = buffer.Width / 2;

for (int i = 0; i < buffer.Height; i++)
{
sourceIndex = length * i;
destinationIndex = (Width * (y + i) + x) >> 2;

Array.Copy(buffer.Buffer, sourceIndex, Buffer, destinationIndex, length);
}
}
else
{ // fall back to a slow write
base.WriteBuffer(x, y, buffer);
}
}

/// <summary>
/// Get the pixel's color index (0-3) at a given coordinate
/// </summary>
/// <param name="x">The X pixel position</param>
/// <param name="y">The Y pixel position</param>
/// <returns>The 2-bit color index</returns>
public byte GetColorIndexForPixel(int x, int y)
{
int byteIndex = (y * Width + x) >> 2;
int pixelOffset = (x & 0x03) << 1;

byte value = (byte)((Buffer[byteIndex] >> pixelOffset) & 0x03);
return value;
}

Color GetClosestColor(Color color)
{
return IndexedColors[GetIndexForColor(color)];
}

int GetIndexForColor(Color color)
{
if (IndexedColors == null || IndexedColors.All(x => x == null))
{
throw new NullReferenceException("No indexed colors assigned");
}

int closestIndex = -1;
double shortestDistance = double.MaxValue;

for (int i = 0; i < IndexedColors.Length; i++)
{
if (IndexedColors[i] != null)
{
double distance = GetColorDistance(color, IndexedColors[i]);
if (distance < shortestDistance)
{
shortestDistance = distance;
closestIndex = i;
if (distance == 0) { break; } // perfect match
}
}
}
return closestIndex;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ namespace Meadow.Foundation.Graphics.Buffers
{
/// <summary>
/// Represents a 4bpp pixel buffer with indexed colors
/// Each pixel is represented by 4 bits, allowing for 8 distinct colors
/// </summary>
public class BufferIndexed4 : PixelBufferBase
{
Expand Down Expand Up @@ -60,7 +61,7 @@ public override void Fill(Color color)
}

/// <summary>
/// Fill with a color
/// Fill a rectangular area with a color
/// </summary>
/// <param name="x">X start position in pixels</param>
/// <param name="y">Y start position in pixels</param>
Expand Down Expand Up @@ -88,15 +89,14 @@ public override void Fill(int x, int y, int width, int height, Color color)
}

/// <summary>
/// Get the pixel color
/// Get the pixel color at a given coordinate
/// </summary>
/// <param name="x">The X pixel position</param>
/// <param name="y">The Y pixel position</param>
/// <returns>The pixel color</returns>
public override Color GetPixel(int x, int y)
{ //comes back as a 4bit value
{
var index = GetColorIndexForPixel(x, y);

return IndexedColors[index];
}

Expand All @@ -113,11 +113,11 @@ public override void SetPixel(int x, int y, Color color)
}

/// <summary>
/// Set the pixel to a shade of gray
/// Set the pixel using a color index
/// </summary>
/// <param name="x">X pixel position</param>
/// <param name="y">Y pixel position</param>
/// <param name="colorIndex">The color index</param>
/// <param name="colorIndex">The color index (0-7)</param>
public void SetPixel(int x, int y, int colorIndex)
{
int index = y * Width / 2 + x / 2;
Expand All @@ -133,7 +133,7 @@ public void SetPixel(int x, int y, int colorIndex)
}

/// <summary>
/// Invert the pixel
/// Invert the pixel color
/// </summary>
/// <param name="x">x position of pixel</param>
/// <param name="y">y position of pixel</param>
Expand All @@ -143,7 +143,7 @@ public override void InvertPixel(int x, int y)
}

/// <summary>
/// Write a buffer to specific location to the current buffer
/// Write a buffer into this buffer at a specified location
/// </summary>
/// <param name="x">x origin</param>
/// <param name="y">y origin</param>
Expand All @@ -154,14 +154,14 @@ public override void WriteBuffer(int x, int y, IPixelBuffer buffer)
x % 2 == 0 &&
buffer.Width % 2 == 0)
{
//we have a happy path
// We can do a direct block copy row by row
int sourceIndex, destinationIndex;
int length = buffer.Width / 2;

for (int i = 0; i < buffer.Height; i++)
{
sourceIndex = length * i;
destinationIndex = (Width * (y + i) + x) >> 2; //divide by 2
destinationIndex = (Width * (y + i) + x) >> 2;

Array.Copy(buffer.Buffer, sourceIndex, Buffer, destinationIndex, length);
}
Expand All @@ -173,11 +173,11 @@ public override void WriteBuffer(int x, int y, IPixelBuffer buffer)
}

/// <summary>
/// Get the pixel color index
/// Get the pixel's color index (0-7) at a given coordinate
/// </summary>
/// <param name="x">The X pixel position</param>
/// <param name="y">The Y pixel position</param>
/// <returns>The pixel color as a 4bpp gray value</returns>
/// <returns>The 4-bit color index</returns>
public byte GetColorIndexForPixel(int x, int y)
{
int index = y * Width / 2 + x / 2;
Expand Down Expand Up @@ -211,15 +211,14 @@ int GetIndexForColor(Color color)

for (int i = 0; i < IndexedColors.Length; i++)
{
double distance;
if (IndexedColors[i] != null)
{
distance = GetColorDistance(color, IndexedColors[i]);
double distance = GetColorDistance(color, IndexedColors[i]);
if (distance < shortestDistance)
{
shortestDistance = distance;
closestIndex = i;
if (distance == 0) { break; } //perfect match
if (distance == 0) { break; } // perfect match
}
}
}
Expand Down

0 comments on commit cd36478

Please sign in to comment.