- Add branding management feature: API routes, payload handling, and OpenAPI schema updates. - Implement department branding logic: CRUD operations, validation, and permissions. - Add order deletion confirmation support with conflict handling and OpenAPI schema updates. - Enhance tests and API methods for improved order handling and branding workflows.
711 lines
28 KiB
PHP
711 lines
28 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';
|
|
const BUTTON_RESET = 'reset';
|
|
const BUTTON_START = 'start';
|
|
// 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)
|
|
// e.g., 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 = 0; // No "click counter" completed yet = 0
|
|
// Config
|
|
public bool $only_generate_current_step = false; // If true, only generate the current step button highlights
|
|
|
|
|
|
/**
|
|
* @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();
|
|
$deferredStartButtons = $this->drawHighlightedButtonSequence();
|
|
$this->drawAsset($this->getAsset(self::IMAGE_POWER_BUTTON), 0, 0);
|
|
$this->drawDeferredHighlightedButtons($deferredStartButtons);
|
|
$this->drawCropOutLine(true);
|
|
}
|
|
|
|
public function drawCropOutLine(bool $crop = false): void
|
|
{
|
|
$line_template = [
|
|
'x' => [
|
|
'from' => 0,
|
|
'to' => 6000
|
|
],
|
|
'y' => [
|
|
'from' => 0,
|
|
'to' => 0
|
|
],
|
|
];
|
|
// Define top crop line
|
|
$top_line = [
|
|
'x' => [
|
|
'from' => 0,
|
|
'to' => 6000
|
|
],
|
|
'y' => [
|
|
'from' => 262,
|
|
'to' => 262
|
|
],
|
|
];
|
|
// Define the left crop line
|
|
$left_line = [
|
|
'x' => [
|
|
'from' => 194,
|
|
'to' => 194
|
|
],
|
|
'y' => [
|
|
'from' => 0,
|
|
'to' => 4500
|
|
],
|
|
];
|
|
// Define the right crop line
|
|
$right_line = [
|
|
'x' => [
|
|
'from' => 5765,
|
|
'to' => 5765
|
|
],
|
|
'y' => [
|
|
'from' => 0,
|
|
'to' => 4500
|
|
],
|
|
];
|
|
// Define the bottom crop line
|
|
$bottom_line = [
|
|
'x' => [
|
|
'from' => 0,
|
|
'to' => 6000
|
|
],
|
|
'y' => [
|
|
'from' => 4160,
|
|
'to' => 4160
|
|
],
|
|
];
|
|
$lines = [
|
|
$top_line,
|
|
$left_line,
|
|
$right_line,
|
|
$bottom_line
|
|
];
|
|
|
|
// If the crop parameter is false, just draw the outline
|
|
if (!$crop) {
|
|
// Draw the crop outline
|
|
foreach ( $lines as $line ) {
|
|
$x1 = (int)$line['x']['from'];;
|
|
$y1 = (int)$line['y']['from'];;
|
|
$x2 = (int)$line['x']['to'];;
|
|
$y2 = (int)$line['y']['to'];;
|
|
$this->drawLine($x1, $y1, $x2, $y2, 'rgba(255,0,0,0.75)', 1);
|
|
}
|
|
}
|
|
// If the crop parameter is true, apply the crop
|
|
else {
|
|
$cropX = (int)$left_line['x']['from'];
|
|
$cropY = (int)$top_line['y']['from'];
|
|
$cropWidth = (int)6000 - $cropX - (6000 - (int)$right_line['x']['from']);
|
|
$cropHeight = (int)4500 - $cropY - (4500 - (int)$bottom_line['y']['from']);
|
|
$this->crop($cropWidth, $cropHeight, $cropX, $cropY);
|
|
}
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
private function isHighlightedButton(int|string $button): bool
|
|
{
|
|
foreach ($this->highlighted_buttons as $highlightedButton) {
|
|
if (is_int($button)) {
|
|
if (is_numeric($highlightedButton) && (int)$highlightedButton === $button) {
|
|
return true;
|
|
}
|
|
continue;
|
|
}
|
|
|
|
if (is_string($highlightedButton) && strtolower(trim($highlightedButton)) === $button) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
private function normalizeHighlightedButtonToken(mixed $button): int|string|null
|
|
{
|
|
if (is_string($button)) {
|
|
$trimmed = trim($button);
|
|
$specialButton = strtolower($trimmed);
|
|
if ($specialButton === self::BUTTON_RESET || $specialButton === self::BUTTON_START) {
|
|
return $specialButton;
|
|
}
|
|
|
|
if ($trimmed !== '' && ctype_digit($trimmed)) {
|
|
$button = (int)$trimmed;
|
|
}
|
|
}
|
|
|
|
if (is_int($button)) {
|
|
return $button >= 0 && $button < ($this->button_rows * $this->button_columns) ? $button : null;
|
|
}
|
|
|
|
if (is_numeric($button) && (int)$button == $button) {
|
|
$button = (int)$button;
|
|
return $button >= 0 && $button < ($this->button_rows * $this->button_columns) ? $button : null;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
private function getOrderedHighlightedButtonTokens(): array
|
|
{
|
|
$tokens = [];
|
|
foreach ($this->highlighted_buttons as $button) {
|
|
$token = $this->normalizeHighlightedButtonToken($button);
|
|
if ($token !== null) {
|
|
$tokens[] = $token;
|
|
}
|
|
}
|
|
|
|
return $tokens;
|
|
}
|
|
|
|
private function getRegularButtonCoordinates(int $buttonIndex): ?array
|
|
{
|
|
if ($buttonIndex < 0 || $buttonIndex >= ($this->button_rows * $this->button_columns)) {
|
|
return null;
|
|
}
|
|
|
|
$row = intdiv($buttonIndex, $this->button_columns);
|
|
$col = $buttonIndex % $this->button_columns;
|
|
$x = 2815 + ($col * ($this->button_highlight_size + $this->button_columns_spacing));
|
|
$y = 1265 + ($row * ($this->button_highlight_size + $this->button_rows_spacing));
|
|
if ($row === $this->button_rows - 1) {
|
|
$y += $this->button_last_row_spacing_buffer;
|
|
}
|
|
|
|
return [
|
|
'x' => $x,
|
|
'y' => $y,
|
|
'size' => $this->button_highlight_size,
|
|
];
|
|
}
|
|
|
|
private function getHighlightedButtonCoordinates(int|string $button): ?array
|
|
{
|
|
if ($button === self::BUTTON_RESET) {
|
|
return [
|
|
'x' => 1736,
|
|
'y' => 599,
|
|
'size' => $this->reset_button_highlight_size,
|
|
];
|
|
}
|
|
|
|
if ($button === self::BUTTON_START) {
|
|
return [
|
|
'x' => 4965,
|
|
'y' => 1980,
|
|
'size' => $this->start_button_highlight_size,
|
|
];
|
|
}
|
|
|
|
return is_int($button) ? $this->getRegularButtonCoordinates($button) : null;
|
|
}
|
|
|
|
/**
|
|
* @throws \Exception
|
|
*/
|
|
private function buildHighlightedButtonDraw(int|string $button): ?array
|
|
{
|
|
$coordinates = $this->getHighlightedButtonCoordinates($button);
|
|
if ($coordinates === null) {
|
|
return null;
|
|
}
|
|
|
|
$tmp = new dynamicimages_asset($this->getAssetPath(self::getHighlightedAssetName()));
|
|
$tmp->resize($coordinates['size'], $coordinates['size']);
|
|
$tmp = $this->drawStepCounterOnButton($tmp);
|
|
if ($this->only_generate_current_step && $this->button_counter != $this->current_step + 1) {
|
|
if (method_exists($tmp, 'clearMemoryImage')) {
|
|
$tmp->clearMemoryImage();
|
|
}
|
|
return null;
|
|
}
|
|
|
|
return [
|
|
'asset' => $tmp,
|
|
'x' => $coordinates['x'],
|
|
'y' => $coordinates['y'],
|
|
];
|
|
}
|
|
|
|
/**
|
|
* @throws \Exception
|
|
*/
|
|
private function drawHighlightedButtonDraw(array $draw): void
|
|
{
|
|
$tmp = $draw['asset'];
|
|
$this->drawAsset($tmp, (int)$draw['x'], (int)$draw['y']);
|
|
if (method_exists($tmp, 'clearMemoryImage')) {
|
|
$tmp->clearMemoryImage();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @return array<int,array{asset: dynamicimages_asset, x: int, y: int}>
|
|
* @throws \Exception
|
|
*/
|
|
public function drawHighlightedButtonSequence(): array
|
|
{
|
|
$deferredStartButtons = [];
|
|
foreach ($this->getOrderedHighlightedButtonTokens() as $button) {
|
|
$draw = $this->buildHighlightedButtonDraw($button);
|
|
if ($draw === null) {
|
|
continue;
|
|
}
|
|
|
|
if ($button === self::BUTTON_START) {
|
|
$deferredStartButtons[] = $draw;
|
|
continue;
|
|
}
|
|
|
|
$this->drawHighlightedButtonDraw($draw);
|
|
}
|
|
|
|
return $deferredStartButtons;
|
|
}
|
|
|
|
/**
|
|
* @param array<int,array{asset: dynamicimages_asset, x: int, y: int}> $deferredStartButtons
|
|
* @throws \Exception
|
|
*/
|
|
public function drawDeferredHighlightedButtons(array $deferredStartButtons): void
|
|
{
|
|
foreach ($deferredStartButtons as $draw) {
|
|
$this->drawHighlightedButtonDraw($draw);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @throws \Exception
|
|
*/
|
|
public function drawHighlightedStartButton(): void
|
|
{
|
|
if (!$this->isHighlightedButton(self::BUTTON_START)) {
|
|
return;
|
|
}
|
|
|
|
$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);
|
|
// If only generating current step, skip if not matching
|
|
if ($this->only_generate_current_step && $this->button_counter != $this->current_step +1) {
|
|
return;
|
|
}
|
|
$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
|
|
{
|
|
if (!$this->isHighlightedButton(self::BUTTON_RESET)) {
|
|
return;
|
|
}
|
|
|
|
$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);
|
|
if ($this->only_generate_current_step && $this->button_counter != $this->current_step +1) {
|
|
return;
|
|
}
|
|
$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
|
|
{
|
|
// Draw a centered step counter number on the button
|
|
$font = $this->getFontPath('AlfaSlabOne-Regular.ttf');
|
|
$imgW = $buttonAsset->getWidth();
|
|
$imgH = $buttonAsset->getHeight();
|
|
// Virtual badge used only to size text well (no circle is drawn)
|
|
$badgeDiameter = 0.40 * min($imgW, $imgH);
|
|
$badgeRadius = $badgeDiameter / 2.0;
|
|
|
|
// Badge center equals image center
|
|
$cx = $imgW / 2.0;
|
|
$cy = $imgH / 2.0;
|
|
// With center/center alignment, offsets are deltas from image center
|
|
$xOffset = (int)round($cx - ($imgW / 2.0));
|
|
$yOffset = (int)round($cy - ($imgH / 2.0));
|
|
|
|
// Dynamic font sizing to fit up to 2 digits inside the virtual badge
|
|
$targetFrac = 0.72; // 72% of diameter
|
|
$maxBox = $badgeDiameter * $targetFrac;
|
|
$fontSize = (int)max(1, round($badgeDiameter * 0.6));
|
|
try {
|
|
$im = $buttonAsset->getMemoryImage();
|
|
if ($im instanceof \Imagick) {
|
|
$draw = new \ImagickDraw();
|
|
$draw->setFont($font);
|
|
for ($i = 0; $i < 6; $i++) {
|
|
$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.9));
|
|
} else {
|
|
$next = (int)ceil($fontSize * 1.05);
|
|
if ($next === $fontSize) {
|
|
break;
|
|
}
|
|
$fontSize = $next;
|
|
}
|
|
}
|
|
// Safety: ensure we don't 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 to initial estimate
|
|
}
|
|
|
|
$angle = 0;
|
|
$color = [255, 255, 255];
|
|
$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',
|
|
]);
|
|
|
|
return $buttonAsset;
|
|
}
|
|
|
|
public function drawStepCounterOnButtonTopRight(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 (!$this->isHighlightedButton($buttonIndex)) {
|
|
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);
|
|
if ($this->only_generate_current_step && $this->button_counter != $this->current_step +1) {
|
|
continue;
|
|
}
|
|
$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::IMAGE_BUTTON_HIGHLIGHTED_BLUE));
|
|
$tmp->resize($stepSize, $stepSize);
|
|
$this->drawAsset($tmp, $stepX, $stepY);
|
|
// Free memory used by temporary asset image, if any
|
|
if (method_exists($tmp, 'clearMemoryImage')) {
|
|
$tmp->clearMemoryImage();
|
|
}
|
|
}
|
|
}
|