feat: support for additional devices

This commit is contained in:
Benjamin Nussbaum 2025-05-13 16:14:01 +02:00
parent aa1e02d184
commit 75012ba5b1
4 changed files with 96 additions and 16 deletions

21
app/Enums/ImageFormat.php Normal file
View file

@ -0,0 +1,21 @@
<?php
namespace App\Enums;
enum ImageFormat: string
{
case AUTO = 'auto';
case PNG_8BIT_GRAYSCALE = 'png_8bit_grayscale';
case BMP3_1BIT_SRGB = 'bmp3_1bit_srgb';
case PNG_8BIT_256C = 'png_8bit_256c';
public function label(): string
{
return match ($this) {
self::AUTO => 'Auto',
self::PNG_8BIT_GRAYSCALE => 'PNG 8-bit Grayscale Gray 2c',
self::BMP3_1BIT_SRGB => 'BMP3 1-bit sRGB 2c',
self::PNG_8BIT_256C => 'PNG 8-bit Grayscale Gray 256c',
};
}
}

View file

@ -2,6 +2,7 @@
namespace App\Services; namespace App\Services;
use App\Enums\ImageFormat;
use App\Models\Device; use App\Models\Device;
use App\Models\Plugin; use App\Models\Plugin;
use Illuminate\Support\Facades\Storage; use Illuminate\Support\Facades\Storage;
@ -38,21 +39,40 @@ class ImageGenerationService
throw new \RuntimeException('Failed to generate PNG: '.$e->getMessage(), 0, $e); throw new \RuntimeException('Failed to generate PNG: '.$e->getMessage(), 0, $e);
} }
} }
switch ($device->image_format) {
if (isset($device->last_firmware_version) case ImageFormat::BMP3_1BIT_SRGB->value:
&& version_compare($device->last_firmware_version, '1.5.2', '<')) { try {
try { ImageGenerationService::convertToBmpImageMagick($pngPath, $bmpPath);
ImageGenerationService::convertToBmpImageMagick($pngPath, $bmpPath); } catch (\ImagickException $e) {
} catch (\ImagickException $e) { throw new \RuntimeException('Failed to convert image to BMP: '.$e->getMessage(), 0, $e);
throw new \RuntimeException('Failed to convert image to BMP: '.$e->getMessage(), 0, $e); }
} break;
} else { case ImageFormat::PNG_8BIT_GRAYSCALE->value:
try { case ImageFormat::PNG_8BIT_256C->value:
ImageGenerationService::convertToPngImageMagick($pngPath, $device->width, $device->height, $device->rotate); try {
} catch (\ImagickException $e) { ImageGenerationService::convertToPngImageMagick($pngPath, $device->width, $device->height, $device->rotate, quantize: $device->image_format === ImageFormat::PNG_8BIT_GRAYSCALE);
throw new \RuntimeException('Failed to convert image to PNG: '.$e->getMessage(), 0, $e); } catch (\ImagickException $e) {
} throw new \RuntimeException('Failed to convert image to PNG: '.$e->getMessage(), 0, $e);
}
break;
case ImageFormat::AUTO->value:
default:
if (isset($device->last_firmware_version)
&& version_compare($device->last_firmware_version, '1.5.2', '<')) {
try {
ImageGenerationService::convertToBmpImageMagick($pngPath, $bmpPath);
} catch (\ImagickException $e) {
throw new \RuntimeException('Failed to convert image to BMP: '.$e->getMessage(), 0, $e);
}
} else {
try {
ImageGenerationService::convertToPngImageMagick($pngPath, $device->width, $device->height, $device->rotate);
} catch (\ImagickException $e) {
throw new \RuntimeException('Failed to convert image to PNG: '.$e->getMessage(), 0, $e);
}
}
} }
$device->update(['current_screen_image' => $uuid]); $device->update(['current_screen_image' => $uuid]);
\Log::info("Device $device->id: updated with new image: $uuid"); \Log::info("Device $device->id: updated with new image: $uuid");
@ -77,7 +97,7 @@ class ImageGenerationService
/** /**
* @throws \ImagickException * @throws \ImagickException
*/ */
private static function convertToPngImageMagick(string $pngPath, ?int $width, ?int $height, ?int $rotate): void private static function convertToPngImageMagick(string $pngPath, ?int $width, ?int $height, ?int $rotate, $quantize = true): void
{ {
$imagick = new \Imagick($pngPath); $imagick = new \Imagick($pngPath);
if ($width !== 800 || $height !== 480) { if ($width !== 800 || $height !== 480) {
@ -87,7 +107,9 @@ class ImageGenerationService
$imagick->rotateImage(new ImagickPixel('black'), $rotate); $imagick->rotateImage(new ImagickPixel('black'), $rotate);
} }
$imagick->setImageType(\Imagick::IMGTYPE_GRAYSCALE); $imagick->setImageType(\Imagick::IMGTYPE_GRAYSCALE);
$imagick->quantizeImage(2, \Imagick::COLORSPACE_GRAY, 0, true, false); if ($quantize) {
$imagick->quantizeImage(2, \Imagick::COLORSPACE_GRAY, 0, true, false);
}
$imagick->setImageDepth(8); $imagick->setImageDepth(8);
$imagick->stripImage(); $imagick->stripImage();

View file

@ -0,0 +1,28 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::table('devices', function (Blueprint $table) {
$table->string('image_format')->default('auto')->after('rotate');
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('devices', function (Blueprint $table) {
$table->dropColumn('image_format');
});
}
};

View file

@ -16,6 +16,7 @@ new class extends Component {
public $width; public $width;
public $height; public $height;
public $rotate; public $rotate;
public $image_format;
// Playlist properties // Playlist properties
public $playlists; public $playlists;
@ -41,6 +42,7 @@ new class extends Component {
$this->width = $device->width; $this->width = $device->width;
$this->height = $device->height; $this->height = $device->height;
$this->rotate = $device->rotate; $this->rotate = $device->rotate;
$this->image_format = $device->image_format;
$this->playlists = $device->playlists()->with('items.plugin')->orderBy('created_at')->get(); $this->playlists = $device->playlists()->with('items.plugin')->orderBy('created_at')->get();
return view('livewire.devices.configure', [ return view('livewire.devices.configure', [
@ -68,6 +70,7 @@ new class extends Component {
'width' => 'required|integer|min:1', 'width' => 'required|integer|min:1',
'height' => 'required|integer|min:1', 'height' => 'required|integer|min:1',
'rotate' => 'required|integer|min:0|max:359', 'rotate' => 'required|integer|min:0|max:359',
'image_format' => 'required|string',
]); ]);
$this->device->update([ $this->device->update([
@ -78,6 +81,7 @@ new class extends Component {
'width' => $this->width, 'width' => $this->width,
'height' => $this->height, 'height' => $this->height,
'rotate' => $this->rotate, 'rotate' => $this->rotate,
'image_format' => $this->image_format,
]); ]);
Flux::modal('edit-device')->close(); Flux::modal('edit-device')->close();
@ -288,6 +292,11 @@ new class extends Component {
<flux:input label="Height (px)" wire:model="height" type="number"/> <flux:input label="Height (px)" wire:model="height" type="number"/>
<flux:input label="Rotate °" wire:model="rotate" type="number"/> <flux:input label="Rotate °" wire:model="rotate" type="number"/>
</div> </div>
<flux:select label="Image Format" wire:model="image_format">
@foreach(\App\Enums\ImageFormat::cases() as $format)
<flux:select.option value="{{ $format->value }}">{{$format->label()}}</flux:select.option>
@endforeach
</flux:select>
<flux:input label="Default Refresh Interval (seconds)" wire:model="default_refresh_interval" <flux:input label="Default Refresh Interval (seconds)" wire:model="default_refresh_interval"
type="number"/> type="number"/>