Files
api/services/nginx/app/modules/dynamicimages/images/machine_1.php
T
Jeppe Bundgaard 7247da5e21 Add start button rendering and update highlighted button logic in machine_1
- Introduce `drawHighlightedStartButton` method for dynamic rendering of the start button with step indicators.
- Add new constants for `IMAGE_BUTTON_HIGHLIGHTED_GREEN`, `IMAGE_BUTTON_HIGHLIGHTED_GREY`, and `IMAGE_BUTTON_HIGHLIGHTED_COMPLETED`.
- Refactor `getHighlightedAssetName` to provide conditional asset selection based on step completion.
- Implement `drawStepThumb` for rendering step indicators on the washing thumb.
- Adjust highlighted button logic to dynamically incorporate step counts and completion states.
- Add new assets for highlighted button states (`completed`, `green`, `grey`).
2026-01-28 18:11:52 +01:00

353 lines
16 KiB
PHP

<?php
namespace dynamicimages\images;
use dynamicimages\classes\dynamicimages_asset;
use dynamicimages\classes\dynamicimages_image;
class machine_1 extends dynamicimages_image
{
const IMAGE_POWER_BUTTON = 'machine_1_button_start_stop.png';
const IMAGE_PANEL_BACKGROUND = 'machine_1_panel_background.png';
const IMAGE_WASH_PROGRAMS_THUMB = 'machine_1_wash_programs_thumb_standalone_transparent.png';
const IMAGE_WASH_PROGRAMS = 'machine_1_programs_wheel_transparent.png';
const IMAGE_BUTTON_HIGHLIGHTED = 'machine_1_button_highlighted_blue.png'; // 'machine_1_button_highlighted_transparent.png';
const IMAGE_BUTTON_HIGHLIGHTED_BLUE = 'machine_1_button_highlighted_blue.png';
const IMAGE_BUTTON_HIGHLIGHTED_GREY = 'machine_1_button_highlighted_grey.png';
const IMAGE_BUTTON_HIGHLIGHTED_COMPLETED = 'machine_1_button_highlighted_completed_grey.png';
const IMAGE_BUTTON_HIGHLIGHTED_GREEN = 'machine_1_button_highlighted_green.png';
// Thumb
public int $thumb_position = 1; // 0-11 (default: 0 = up = 270 degrees)
public int $thumb_size = 1550; // height and width of the thumb
// Buttons
public int $button_highlight_size = 295; // size of the highlighted button
public int $button_rows = 4; // Number of rows (on top of each other)
public int $button_rows_spacing = 179; // Spacing between rows of buttons
public int $button_last_row_spacing_buffer = -23; // Extra spacing buffer for the last row (The last row has a smaller distance)
public int $button_columns = 3; // Number of columns (side by side)
public int $button_columns_spacing = 442; // Spacing between columns of buttons
public array $highlighted_buttons = [
// Array of button positions to highlight (0 indexed, left to right, top to bottom)
0, 2, 4, 7
];
// Reset button
public int $reset_button_highlight_size = 380; // size of the highlighted reset button
// Start button
public int $start_button_highlight_size = 300; // size of the highlighted start button
// Button counts
public int $button_counter = 0; // Is incremented every time a button is drawn
// Current step
public int $current_step = 3; // No "click counter" completed yet = 0
/**
* @throws \Exception
*/
public function __construct()
{
$this->setAssetPath(self::asset_path . str_replace(__NAMESPACE__ . '\\', '', __CLASS__) . '/');
parent::__construct([
new dynamicimages_asset(self::getAssetPath(self::IMAGE_POWER_BUTTON)),
new dynamicimages_asset(self::getAssetPath(self::IMAGE_PANEL_BACKGROUND)),
new dynamicimages_asset(self::getAssetPath(self::IMAGE_WASH_PROGRAMS_THUMB)),
new dynamicimages_asset(self::getAssetPath(self::IMAGE_WASH_PROGRAMS)),
new dynamicimages_asset(self::getAssetPath(self::IMAGE_BUTTON_HIGHLIGHTED)),
new dynamicimages_asset(self::getAssetPath(self::IMAGE_BUTTON_HIGHLIGHTED_BLUE)),
new dynamicimages_asset(self::getAssetPath(self::IMAGE_BUTTON_HIGHLIGHTED_COMPLETED)),
new dynamicimages_asset(self::getAssetPath(self::IMAGE_BUTTON_HIGHLIGHTED_GREEN)),
]);
}
/**
* @throws \Exception
*/
public function setup(): void
{
$this->initImageCanvas(6000, 4500);
// By default, the background should be shown with the programs on top and then on top of that the thumb
$this->drawAsset($this->getAsset(self::IMAGE_PANEL_BACKGROUND), 0, 0);
$this->drawProgramWheel();
$this->drawThumb();
$this->drawStepThumb();
$this->drawHighlightedResetButton();
$this->drawHighlightedButtons();
$this->drawAsset($this->getAsset(self::IMAGE_POWER_BUTTON), 0, 0);
$this->drawHighlightedStartButton();
}
public function getHighlightedAssetName(): string
{
if ($this->button_counter == $this->current_step) {
return self::IMAGE_BUTTON_HIGHLIGHTED_BLUE;
}
if ($this->button_counter < $this->current_step) {
return self::IMAGE_BUTTON_HIGHLIGHTED_COMPLETED;
}
return self::IMAGE_BUTTON_HIGHLIGHTED_GREY;
}
/**
* @throws \Exception
*/
public function drawHighlightedStartButton(): void
{
$x = 4965; // X position for the start button
$y = 1980; // Y position for the start button
$tmp = new dynamicimages_asset($this->getAssetPath(self::getHighlightedAssetName()));
$tmp->resize($this->start_button_highlight_size, $this->start_button_highlight_size);
$tmp = $this->drawStepCounterOnButton($tmp);
$this->drawAsset($tmp, $x, $y);
// Free memory used by temporary asset image, if any
if (method_exists($tmp, 'clearMemoryImage')) {
$tmp->clearMemoryImage();
}
}
/**
* @throws \Exception
*/
public function drawHighlightedResetButton(): void
{
$x = 1736; // X position for the reset button
$y = 599; // Y position for the reset button
$tmp = new dynamicimages_asset($this->getAssetPath(self::getHighlightedAssetName()));
$tmp->resize($this->reset_button_highlight_size, $this->reset_button_highlight_size);
$tmp = $this->drawStepCounterOnButton($tmp);
$this->drawAsset($tmp, $x, $y);
// Free memory used by temporary asset image, if any
if (method_exists($tmp, 'clearMemoryImage')) {
$tmp->clearMemoryImage();
}
}
public function drawStepCounterOnButton(dynamicimages_asset $buttonAsset): dynamicimages_asset
{
$font = $this->getFontPath('AlfaSlabOne-Regular.ttf');
// The blue badge circle occupies 25% of the button width/height (diameter)
$imgW = $buttonAsset->getWidth();
$imgH = $buttonAsset->getHeight();
$badgeDiameter = 0.25 * min($imgW, $imgH);
$badgeRadius = $badgeDiameter / 2.0;
// Center of the badge is near the top-right corner; apply inner inset to match actual asset layout
// Final calibrated insets: 50% of badge diameter inward from right and downward from top
$centerXNudgeIn = 0.50 * $badgeDiameter; // inward from right edge (~50% of diameter)
$centerYNudgeIn = 0.50 * $badgeDiameter; // downward from top edge (~50% of diameter)
$cx = $imgW - $badgeRadius - $centerXNudgeIn; // adjusted circle center X
$cy = $badgeRadius + $centerYNudgeIn; // adjusted circle center Y
// We use center/center alignment and offset from the image center to the badge center
$baseCenterXOffset = (int)round($cx - ($imgW / 2.0));
$baseCenterYOffset = (int)round($cy - ($imgH / 2.0));
$xOffset = $baseCenterXOffset;
$yOffset = $baseCenterYOffset;
// Temporarily disable optical nudge while calibrating true badge center
$xNudge = 0.00 * $badgeDiameter;
$yNudge = 0.00 * $badgeDiameter;
$xOffset -= (int)round($xNudge);
$yOffset += (int)round($yNudge);
// Optical nudge remains disabled; visual centering confirmed with calibrated center.
// Compute a dynamic font size that fits up to 2 digits comfortably inside the badge
// Target occupied size inside the circle (padding from edges)
$targetFrac = 0.72; // 72% of diameter for both width and height
$maxBox = $badgeDiameter * $targetFrac;
// Start with an optimistic font size close to the target box
$fontSize = (int)max(1, round($badgeDiameter * 0.6));
try {
// Try to refine using Imagick metrics if available
$im = $buttonAsset->getMemoryImage();
if ($im instanceof \Imagick) {
$draw = new \ImagickDraw();
$draw->setFont($font);
// Iterate a few times to converge
for ($i = 0; $i < 6; $i++) {
$draw->setFontSize($fontSize);
$metrics = $im->queryFontMetrics($draw, (string)$this->getNextButtonStepCount(), false);
// We queried getNextButtonStepCount() for metrics; revert increment afterwards
$this->button_counter--; // undo increment from metrics call
$textW = (int)ceil($metrics['textWidth'] ?? 0);
$textH = (int)ceil(($metrics['ascender'] ?? 0) - ($metrics['descender'] ?? 0));
// If it exceeds maxBox in either dimension, reduce; otherwise increase slightly
if ($textW > $maxBox || $textH > $maxBox) {
$fontSize = (int)max(1, floor($fontSize * 0.9));
} else {
// Try to expand a bit to approach the limit
$next = (int)ceil($fontSize * 1.05);
if ($next === $fontSize) {
break;
}
$fontSize = $next;
}
}
// Final safety cap not to exceed the box
$draw->setFontSize($fontSize);
$metrics = $im->queryFontMetrics($draw, (string)$this->getNextButtonStepCount(), false);
$this->button_counter--; // undo increment from metrics call
$textW = (int)ceil($metrics['textWidth'] ?? 0);
$textH = (int)ceil(($metrics['ascender'] ?? 0) - ($metrics['descender'] ?? 0));
if ($textW > $maxBox || $textH > $maxBox) {
$fontSize = (int)max(1, floor($fontSize * 0.95));
}
}
} catch (\Throwable $e) {
// Fallback: keep the initial estimate on any error
}
$angle = 0;
$color = [255, 255, 255]; // White color for the text
$text = (string)$this->getNextButtonStepCount();
// If the step has been completed, do not draw the number
if ($this->button_counter <= $this->current_step) {
$text = '';
}
$buttonAsset->addTextOverlay($text, [
'font_size' => $fontSize,
'font_color' => $color,
'font_path' => $font,
'x_offset' => $xOffset,
'y_offset' => $yOffset,
'angle' => $angle,
'align' => 'center',
'valign' => 'center',
]);
// Debug guides removed after calibration
return $buttonAsset;
}
/**
* @throws \Exception
*/
public function drawHighlightedButtons(): void
{
$startX = 2815; // Starting X position for the first button
$startY = 1265; // Starting Y position for the first button
for ($row = 0; $row < $this->button_rows; $row++) {
for ($col = 0; $col < $this->button_columns; $col++) {
// If the current button position is not in the highlighted buttons array, skip it
$buttonIndex = $row * $this->button_columns + $col;
if (!in_array($buttonIndex, $this->highlighted_buttons, true)) {
continue;
}
// Calculate the position for the current button
$x = $startX + ($col * ($this->button_highlight_size + $this->button_columns_spacing));
$y = $startY + ($row * ($this->button_highlight_size + $this->button_rows_spacing));
// Last row adjustment
if ($row === $this->button_rows - 1) {
$y += $this->button_last_row_spacing_buffer;
}
$tmp = new dynamicimages_asset($this->getAssetPath(self::getHighlightedAssetName()));
$tmp->resize($this->button_highlight_size, $this->button_highlight_size);
$tmp = $this->drawStepCounterOnButton($tmp);
$this->drawAsset($tmp, $x, $y);
// Free memory used by temporary asset image, if any
if (method_exists($tmp, 'clearMemoryImage')) {
$tmp->clearMemoryImage();
}
}
}
}
/**
* @return int
*/
public function getNextButtonStepCount(): int
{
$this->button_counter++;
return $this->button_counter;
}
/**
* @throws \Exception
*/
public function drawProgramWheel(): void
{
// Match the wheel center to the thumb's visual center
$thumbX = 650;
$thumbY = 1290;
$buffer = 600; // Pixels to increase the wheel size beyond the thumb edges
$thumbX -= $buffer / 2;
$thumbY -= $buffer / 2;
$thumbWidth = $this->calculateThumbWidth($this->thumb_size) + $buffer;
$thumbHeight = $this->thumb_size + $buffer;
$tmp = new dynamicimages_asset($this->getAssetPath(self::IMAGE_WASH_PROGRAMS));
$tmp->resize($thumbWidth, $thumbHeight);
$this->drawAsset($tmp, $thumbX, $thumbY);
// Free memory used by temporary asset image, if any
if (method_exists($tmp, 'clearMemoryImage')) {
$tmp->clearMemoryImage();
}
}
public function getRotationThumbPosition(): float
{
// Each position represents a 30 degree increment (12 positions in total for 360 degrees)
$degrees = -90;
return ($this->thumb_position) * 30 + $degrees;
}
public function calculateThumbWidth(int $height): int
{
$originalWidth = $this->getAsset(self::IMAGE_WASH_PROGRAMS_THUMB)->getWidth();
$originalHeight = $this->getAsset(self::IMAGE_WASH_PROGRAMS_THUMB)->getHeight();
return (int)round(($originalWidth / $originalHeight) * $height);
}
/**
* @return void
* @throws \Exception
*/
public function drawThumb(): void
{
// Thumb parameters
$thumbX = 650;
$thumbY = 1290;
// Base resized thumb to compute the visual pivot (center coordinates)
$baseThumb = $this->getAsset(self::IMAGE_WASH_PROGRAMS_THUMB)->resize($this->calculateThumbWidth($this->thumb_size), $this->thumb_size);
$centerX = $thumbX + (int)floor($baseThumb->getWidth() / 2);
$centerY = $thumbY + (int)floor($baseThumb->getHeight() / 2);
$tmp = new dynamicimages_asset($this->getAssetPath(self::IMAGE_WASH_PROGRAMS_THUMB));
$tmp->resize($this->calculateThumbWidth($this->thumb_size), $this->thumb_size)
->rotate($this->getRotationThumbPosition());
$this->drawAssetCentered($tmp, $centerX, $centerY);
// Free memory used by temporary asset image, if any
if (method_exists($tmp, 'clearMemoryImage')) {
$tmp->clearMemoryImage();
}
}
/**
* @throws \Exception
*/
public function drawStepThumb(): void
{
// Draw the blue step indicator on the thumb
$thumbX = 650;
$thumbY = 1290;
$stepSize = 350; // Size of the step indicator
// Base resized thumb to compute the visual pivot (center coordinates)
$baseThumb = $this->getAsset(self::IMAGE_WASH_PROGRAMS_THUMB)->resize($this->calculateThumbWidth($this->thumb_size), $this->thumb_size);
$centerX = $thumbX + (int)floor($baseThumb->getWidth() / 2);
$centerY = $thumbY + (int)floor($baseThumb->getHeight() / 2);
$angle = $this->getRotationThumbPosition();
// Calculate position to draw the step indicator
$radius = ($this->thumb_size / 2) - ($stepSize / 2) - 150; // Radius from center to position the step indicator
$rad = deg2rad($angle);
$stepX = $centerX + (int)round($radius * cos($rad)) - (int)floor($stepSize / 2);
$stepY = $centerY + (int)round($radius * sin($rad)) - (int)floor($stepSize / 2);
$tmp = new dynamicimages_asset($this->getAssetPath(self::getHighlightedAssetName()));
$tmp->resize($stepSize, $stepSize);
$tmp = $this->drawStepCounterOnButton($tmp);
$this->drawAsset($tmp, $stepX, $stepY);
// Free memory used by temporary asset image, if any
if (method_exists($tmp, 'clearMemoryImage')) {
$tmp->clearMemoryImage();
}
}
}