3.1 PaletteFactory
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -96,7 +96,7 @@ Each type lives in `src/FrameProcessor/Domain/`. Tests in `tests/FrameProcessor.
|
|||||||
|
|
||||||
## Phase 3 — Image pipeline
|
## Phase 3 — Image pipeline
|
||||||
|
|
||||||
### [ ] 3.1 `PaletteFactory`
|
### [x] 3.1 `PaletteFactory`
|
||||||
- `static ReadOnlyMemory<Color> BuildDisplay(IReadOnlyList<PaletteEntry>)` and `BuildDevice(...)`.
|
- `static ReadOnlyMemory<Color> BuildDisplay(IReadOnlyList<PaletteEntry>)` and `BuildDevice(...)`.
|
||||||
- **Tests:** `PaletteFactoryTests` — count, ordering preserved.
|
- **Tests:** `PaletteFactoryTests` — count, ordering preserved.
|
||||||
|
|
||||||
|
|||||||
38
src/FrameProcessor/ImagePipeline/PaletteFactory.cs
Normal file
38
src/FrameProcessor/ImagePipeline/PaletteFactory.cs
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
using FrameProcessor.Domain;
|
||||||
|
using SixLabors.ImageSharp;
|
||||||
|
|
||||||
|
namespace FrameProcessor.ImagePipeline;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Builds ImageSharp <see cref="Color"/> palettes from a frame's <see cref="PaletteEntry"/> list.
|
||||||
|
/// The display palette is what we dither *against* (what the eye sees on the panel); the device
|
||||||
|
/// palette is the set of RGB values firmware expects in the input PNG. Order is preserved in both.
|
||||||
|
/// </summary>
|
||||||
|
public static class PaletteFactory
|
||||||
|
{
|
||||||
|
public static ReadOnlyMemory<Color> BuildDisplay(IReadOnlyList<PaletteEntry> entries)
|
||||||
|
{
|
||||||
|
ArgumentNullException.ThrowIfNull(entries);
|
||||||
|
|
||||||
|
var colors = new Color[entries.Count];
|
||||||
|
for (var i = 0; i < entries.Count; i++)
|
||||||
|
{
|
||||||
|
colors[i] = entries[i].DisplayColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
return colors;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ReadOnlyMemory<Color> BuildDevice(IReadOnlyList<PaletteEntry> entries)
|
||||||
|
{
|
||||||
|
ArgumentNullException.ThrowIfNull(entries);
|
||||||
|
|
||||||
|
var colors = new Color[entries.Count];
|
||||||
|
for (var i = 0; i < entries.Count; i++)
|
||||||
|
{
|
||||||
|
colors[i] = entries[i].DeviceColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
return colors;
|
||||||
|
}
|
||||||
|
}
|
||||||
66
tests/FrameProcessor.Tests/PaletteFactoryTests.cs
Normal file
66
tests/FrameProcessor.Tests/PaletteFactoryTests.cs
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
using FrameProcessor.Domain;
|
||||||
|
using FrameProcessor.ImagePipeline;
|
||||||
|
using SixLabors.ImageSharp;
|
||||||
|
using SixLabors.ImageSharp.PixelFormats;
|
||||||
|
|
||||||
|
namespace FrameProcessor.Tests;
|
||||||
|
|
||||||
|
public class PaletteFactoryTests
|
||||||
|
{
|
||||||
|
private static readonly IReadOnlyList<PaletteEntry> Spectra6 = new[]
|
||||||
|
{
|
||||||
|
new PaletteEntry("black", Color.FromPixel(new Rgba32(0x1F, 0x22, 0x26, 0xFF)), Color.FromPixel(new Rgba32(0x00, 0x00, 0x00, 0xFF))),
|
||||||
|
new PaletteEntry("white", Color.FromPixel(new Rgba32(0xB9, 0xC7, 0xC9, 0xFF)), Color.FromPixel(new Rgba32(0xFF, 0xFF, 0xFF, 0xFF))),
|
||||||
|
new PaletteEntry("blue", Color.FromPixel(new Rgba32(0x23, 0x3F, 0x8E, 0xFF)), Color.FromPixel(new Rgba32(0x00, 0x00, 0xFF, 0xFF))),
|
||||||
|
new PaletteEntry("green", Color.FromPixel(new Rgba32(0x35, 0x56, 0x3A, 0xFF)), Color.FromPixel(new Rgba32(0x00, 0xFF, 0x00, 0xFF))),
|
||||||
|
new PaletteEntry("red", Color.FromPixel(new Rgba32(0x62, 0x20, 0x1E, 0xFF)), Color.FromPixel(new Rgba32(0xFF, 0x00, 0x00, 0xFF))),
|
||||||
|
new PaletteEntry("yellow", Color.FromPixel(new Rgba32(0xC1, 0xBB, 0x1E, 0xFF)), Color.FromPixel(new Rgba32(0xFF, 0xFF, 0x00, 0xFF))),
|
||||||
|
};
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void BuildDisplay_PreservesOrderAndCount()
|
||||||
|
{
|
||||||
|
var palette = PaletteFactory.BuildDisplay(Spectra6).ToArray();
|
||||||
|
|
||||||
|
Assert.Equal(Spectra6.Count, palette.Length);
|
||||||
|
for (var i = 0; i < Spectra6.Count; i++)
|
||||||
|
{
|
||||||
|
Assert.Equal(Spectra6[i].DisplayColor, palette[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void BuildDevice_PreservesOrderAndCount()
|
||||||
|
{
|
||||||
|
var palette = PaletteFactory.BuildDevice(Spectra6).ToArray();
|
||||||
|
|
||||||
|
Assert.Equal(Spectra6.Count, palette.Length);
|
||||||
|
for (var i = 0; i < Spectra6.Count; i++)
|
||||||
|
{
|
||||||
|
Assert.Equal(Spectra6[i].DeviceColor, palette[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void BuildDisplay_AndBuildDevice_DiffWhenColorsDiffer()
|
||||||
|
{
|
||||||
|
var display = PaletteFactory.BuildDisplay(Spectra6).ToArray();
|
||||||
|
var device = PaletteFactory.BuildDevice(Spectra6).ToArray();
|
||||||
|
|
||||||
|
Assert.NotEqual(display, device);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Build_OnEmptyList_ReturnsEmptyPalette()
|
||||||
|
{
|
||||||
|
Assert.Equal(0, PaletteFactory.BuildDisplay(Array.Empty<PaletteEntry>()).Length);
|
||||||
|
Assert.Equal(0, PaletteFactory.BuildDevice(Array.Empty<PaletteEntry>()).Length);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Build_NullEntries_Throws()
|
||||||
|
{
|
||||||
|
Assert.Throws<ArgumentNullException>(() => PaletteFactory.BuildDisplay(null!));
|
||||||
|
Assert.Throws<ArgumentNullException>(() => PaletteFactory.BuildDevice(null!));
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user