Color palette support in byos_laravel

This commit is contained in:
Dan van Kley 2025-11-17 20:48:06 -05:00
parent c157dcf3b6
commit 9ee7bc1aac
7 changed files with 236 additions and 4 deletions

View file

@ -9,6 +9,7 @@ enum ImageFormat: string
case BMP3_1BIT_SRGB = 'bmp3_1bit_srgb';
case PNG_8BIT_256C = 'png_8bit_256c';
case PNG_2BIT_4C = 'png_2bit_4c';
case PNG_INDEXED = 'png_indexed';
public function label(): string
{
@ -18,6 +19,7 @@ enum ImageFormat: string
self::BMP3_1BIT_SRGB => 'BMP3 1-bit sRGB 2c',
self::PNG_8BIT_256C => 'PNG 8-bit Grayscale Gray 256c',
self::PNG_2BIT_4C => 'PNG 2-bit Grayscale 4c',
self::PNG_INDEXED => 'PNG indexed',
};
}
}

31
app/Enums/PaletteName.php Normal file
View file

@ -0,0 +1,31 @@
<?php
namespace App\Enums;
use Bnussbau\TrmnlPipeline\Data\RgbColor;
use InvalidArgumentException;
enum PaletteName: string
{
case SPECTRA_6 = 'spectra_6';
/**
* @return RgbColor[]
*/
public static function getPalette(PaletteName $palette): array
{
switch ($palette) {
case PaletteName::SPECTRA_6:
return [
RgbColor::fromComponents(0, 0, 0),
RgbColor::fromComponents(255, 255, 255),
RgbColor::fromComponents(255, 255, 0),
RgbColor::fromComponents(255, 0, 0),
RgbColor::fromComponents(0, 255, 0),
RgbColor::fromComponents(0, 0, 255),
];
default:
throw new InvalidArgumentException('Invalid palette name');
}
}
}

View file

@ -6,6 +6,7 @@ namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
final class DeviceModel extends Model
{
@ -25,6 +26,11 @@ final class DeviceModel extends Model
'published_at' => 'datetime',
];
public function palette(): BelongsTo
{
return $this->belongsTo(Palette::class);
}
public function getColorDepthAttribute(): ?string
{
if (! $this->bit_depth) {

32
app/Models/Palette.php Normal file
View file

@ -0,0 +1,32 @@
<?php
declare(strict_types=1);
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\HasMany;
/**
* Palette model representing color palettes used by device models.
*
* @property int $id
* @property string $name
* @property string $description
* @property string $palette JSON single dimension array where each entry is a 24-bit number
* representing the RGB color code.
*/
final class Palette extends Model
{
use HasFactory;
public $timestamps = false;
protected $guarded = ['id'];
public function deviceModels(): HasMany
{
return $this->hasMany(DeviceModel::class);
}
}

View file

@ -6,6 +6,8 @@ use App\Enums\ImageFormat;
use App\Models\Device;
use App\Models\DeviceModel;
use App\Models\Plugin;
use Bnussbau\TrmnlPipeline\Data\ColorType;
use Bnussbau\TrmnlPipeline\Data\RgbColor;
use Bnussbau\TrmnlPipeline\Stages\BrowserStage;
use Bnussbau\TrmnlPipeline\Stages\ImageStage;
use Bnussbau\TrmnlPipeline\TrmnlPipeline;
@ -66,16 +68,21 @@ class ImageGenerationService
->width($imageSettings['width'])
->height($imageSettings['height'])
->colors($imageSettings['colors'])
->colorType($imageSettings['color_type'])
->bitDepth($imageSettings['bit_depth'])
->rotation($imageSettings['rotation'])
->offsetX($imageSettings['offset_x'])
->offsetY($imageSettings['offset_y'])
->outputPath($outputPath);
// Apply dithering if requested by markup
$shouldDither = self::markupContainsDitherImage($markup);
if ($shouldDither) {
$imageStage->dither();
// TODO: actually resolve this merge conflict
// // Apply dithering if requested by markup
// $shouldDither = self::markupContainsDitherImage($markup);
// if ($shouldDither) {
// $imageStage->dither();
// }
if ($imageSettings['palette']) {
$imageStage->palette($imageSettings['palette']);
}
(new TrmnlPipeline())->pipe($browserStage)
@ -110,11 +117,20 @@ class ImageGenerationService
if ($device->deviceModel) {
/** @var DeviceModel $model */
$model = $device->deviceModel;
$paletteRecord = $model->palette()->firstOr();
$palette = null;
if ($paletteRecord) {
$palette = array_map(
fn ($colorCode) => new RgbColor($colorCode),
json_decode($paletteRecord->palette)
);
}
return [
'width' => $model->width,
'height' => $model->height,
'colors' => $model->colors,
'color_type' => ColorType::fromString($model->color_type),
'bit_depth' => $model->bit_depth,
'scale_factor' => $model->scale_factor,
'rotation' => $model->rotation,
@ -123,6 +139,7 @@ class ImageGenerationService
'offset_y' => $model->offset_y,
'image_format' => self::determineImageFormatFromModel($model),
'use_model_settings' => true,
'palette' => $palette,
];
}
@ -153,6 +170,9 @@ class ImageGenerationService
private static function determineImageFormatFromModel(DeviceModel $model): string
{
// Map DeviceModel settings to ImageFormat
if ($model->mime_type === 'image/png' && $model->palette()) {
return ImageFormat::PNG_INDEXED->value;
}
if ($model->mime_type === 'image/bmp' && $model->bit_depth === 1) {
return ImageFormat::BMP3_1BIT_SRGB->value;
}