generated from mirzaev/pot-php
blanks, papers, pages, fit system, render 100% precision
This commit is contained in:
@@ -8,7 +8,9 @@ namespace svoboda\pechatalka\controllers;
|
||||
use svoboda\pechatalka\controllers\core,
|
||||
svoboda\pechatalka\models\paper,
|
||||
svoboda\pechatalka\models\product,
|
||||
svoboda\pechatalka\models\enumerations\products\type as product_type;
|
||||
/* svoboda\pechatalka\models\enumerations\paper\type as paper_type, */
|
||||
svoboda\pechatalka\models\enumerations\paper\format as paper_format,
|
||||
svoboda\pechatalka\models\enumerations\product\type as product_type;
|
||||
|
||||
// Framework for PHP
|
||||
use mirzaev\minimal\http\enumerations\content,
|
||||
@@ -194,7 +196,14 @@ final class generator extends core
|
||||
if ($type === product_type::pin_37) {
|
||||
// Pin 37mm
|
||||
|
||||
$biba = paper::a5(type: $type, products: $products, canvas: $canvas);
|
||||
$biba = paper::generate(
|
||||
format: paper_format::a6,
|
||||
type: $type,
|
||||
products: $products,
|
||||
canvas: $canvas,
|
||||
dpi: 80,
|
||||
fit: true
|
||||
);
|
||||
}
|
||||
|
||||
if (str_contains($this->request->headers['accept'], content::any->value)) {
|
||||
|
||||
@@ -0,0 +1,91 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace svoboda\pechatalka\models\enumerations\paper;
|
||||
|
||||
// Files of the project
|
||||
use svoboda\pechatalka\models\enumerations\product\type as product_type;
|
||||
|
||||
/**
|
||||
* Types of products
|
||||
*
|
||||
* @package svoboda\pechatalka\models\enumerations\paper
|
||||
*
|
||||
* @license http://www.wtfpl.net/ Do What The Fuck You Want To Public License
|
||||
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
|
||||
*/
|
||||
enum format
|
||||
{
|
||||
case a6;
|
||||
|
||||
/**
|
||||
* Dimensions
|
||||
*
|
||||
* @return array The paper format dimensions in centimeters (cm) ['width', 'height']
|
||||
*/
|
||||
public function dimensions(): array
|
||||
{
|
||||
return match ($this) {
|
||||
format::a6 => [
|
||||
'width' => 10.5,
|
||||
'height' => 14.8
|
||||
]
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Places
|
||||
*
|
||||
* @param product_type $type The product type
|
||||
* @param int|float $padding The paper padding (print borders)
|
||||
*
|
||||
* @return array The paper places by the product type in centimeters (cm)
|
||||
*/
|
||||
public function places(product_type $type, int|float $padding = 0): array|false
|
||||
{
|
||||
if ($this === format::a6) {
|
||||
// A6
|
||||
|
||||
// Initializing the paper dimensions
|
||||
$paper = $this->dimensions();
|
||||
|
||||
if ($type === product_type::pin_37) {
|
||||
// Pin 37mm
|
||||
|
||||
// Initializing the product dimensions
|
||||
$blank = $type->areas()['blank'];
|
||||
|
||||
// Calculating the general X coordinate
|
||||
$x = $paper['width'] / 2 - $blank['width'] / 2 - $padding;
|
||||
|
||||
// Initializing the amount of products
|
||||
$amount = 2;
|
||||
|
||||
// Calculating the summary products height
|
||||
$height = $blank['height'] * $amount;
|
||||
|
||||
// Calculating the free space on the page
|
||||
$free = $paper['height'] - $padding * 2 - $height;
|
||||
|
||||
// Calculating the offset between products and from products to the paper borders
|
||||
$offset = $free / ($amount + 1);
|
||||
|
||||
// Exit (success)
|
||||
return [
|
||||
[
|
||||
'x' => $x,
|
||||
'y' => $offset
|
||||
],
|
||||
[
|
||||
'x' => $x,
|
||||
'y' => $offset + $blank['height'] + $offset
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
// Exit (fail)
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -2,12 +2,12 @@
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace svoboda\pechatalka\models\enumerations\products;
|
||||
namespace svoboda\pechatalka\models\enumerations\product;
|
||||
|
||||
/**
|
||||
* Types of products
|
||||
*
|
||||
* @package svoboda\pechatalka\models\enumerations\products
|
||||
* @package svoboda\pechatalka\models\enumerations\product
|
||||
*
|
||||
* @license http://www.wtfpl.net/ Do What The Fuck You Want To Public License
|
||||
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
|
||||
@@ -19,19 +19,19 @@ enum type
|
||||
/**
|
||||
* Areas
|
||||
*
|
||||
* @return array ['visible' => ['width', 'height'], 'blank' => ['width', 'height']]
|
||||
* @return array The product type areas in centimeters (cm) ['visible' => ['width', 'height'], 'blank' => ['width', 'height']]
|
||||
*/
|
||||
public function areas(): array
|
||||
{
|
||||
return match ($this) {
|
||||
type::pin_37 => [
|
||||
'visible' => [
|
||||
'width' => 37,
|
||||
'height' => 37
|
||||
'width' => 3.7,
|
||||
'height' => 3.7
|
||||
],
|
||||
'blank' => [
|
||||
'width' => 48.5,
|
||||
'height' => 48.5
|
||||
'width' => 4.85,
|
||||
'height' => 4.85
|
||||
]
|
||||
]
|
||||
};
|
||||
@@ -6,7 +6,9 @@ namespace svoboda\pechatalka\models;
|
||||
|
||||
// Files of the project
|
||||
use svoboda\pechatalka\models\core,
|
||||
svoboda\pechatalka\models\enumerations\products\type as product_type;
|
||||
/* svoboda\pechatalka\models\enumerations\paper\type as paper_type, */
|
||||
svoboda\pechatalka\models\enumerations\paper\format as paper_format,
|
||||
svoboda\pechatalka\models\enumerations\product\type as product_type;
|
||||
|
||||
// Svoboda time
|
||||
use svoboda\time\statement as svoboda;
|
||||
@@ -23,6 +25,7 @@ use Zanzara\Telegram\Type\User as telegram;
|
||||
|
||||
// Built-in libraries
|
||||
use Imagick as imagick,
|
||||
ImagickKernel as imagick_kernel,
|
||||
ImagickPixel as imagick_pixel,
|
||||
ImagickDraw as imagick_draw,
|
||||
Exception as exception,
|
||||
@@ -39,120 +42,167 @@ use Imagick as imagick,
|
||||
final class paper extends core
|
||||
{
|
||||
/**
|
||||
* A5
|
||||
*
|
||||
* Generate
|
||||
*
|
||||
* @param paper_format $format
|
||||
* @param product_type $type
|
||||
* @param array $products
|
||||
* @param array $canvas
|
||||
* @param int|float $dpi Dots (pixels) per Inch (used with centimeters)
|
||||
* @param bool $fit Fit the maximum number of blanks on paper? (duplication)
|
||||
*
|
||||
* @return record The account record from the database
|
||||
*/
|
||||
public static function a5(product_type $type, array $products, array $canvas = []): mixed
|
||||
public static function generate(paper_format $format, product_type $type, array $products, array $canvas = [], int|float $dpi = 300, bool $fit = true): mixed
|
||||
{
|
||||
// Initializing the print DPI
|
||||
define('dpi', 80);
|
||||
/* define('dpi', 300); */
|
||||
|
||||
// Initializing the A5 paper dimensions
|
||||
$a5 = [
|
||||
'width' => (int) round(10.5 * dpi),
|
||||
'height' => (int) round(14.8 * dpi)
|
||||
];
|
||||
|
||||
// Initializing the paper
|
||||
$paper = new imagick();
|
||||
$paper->setResolution(dpi, dpi);
|
||||
$paper->newImage($a5['width'], $a5['height'], new imagick_pixel("white"));
|
||||
$paper->setImageUnits(imagick::RESOLUTION_PIXELSPERCENTIMETER);
|
||||
$paper->setImageFormat("jpg");
|
||||
|
||||
foreach ($products as $product) {
|
||||
// Iterating over products
|
||||
|
||||
foreach ($product as $layer) {
|
||||
// Iterating over the product layers
|
||||
|
||||
// Filtering
|
||||
empty($layer['scale']) || $layer['scale'] == 0 and $layer['scale'] = 1;
|
||||
// Initializing the paper dimensions
|
||||
$dimensions = $format->dimensions();
|
||||
$width = (int) round($dimensions['width'] * $dpi);
|
||||
$height = (int) round($dimensions['height'] * $dpi);
|
||||
unset($dimensions);
|
||||
|
||||
// Initializing the product areas
|
||||
$areas = $type->areas();
|
||||
|
||||
// Calculating the product blank dimensions by the print DPI
|
||||
// Calculating the product blank dimensions by the print $dpi
|
||||
$blank = [
|
||||
'width' => (int) round($areas['blank']['width'] / 10 * dpi),
|
||||
'height' => (int) round($areas['blank']['height'] / 10 * dpi)
|
||||
'width' => (int) round($areas['blank']['width'] * $dpi),
|
||||
'height' => (int) round($areas['blank']['height'] * $dpi)
|
||||
];
|
||||
|
||||
// Calculating the DPI ratio of the pechatalka interface to the blank dimensions
|
||||
// Calculating the $dpi ratio of the pechatalka interface to the blank dimensions
|
||||
$ratio = [
|
||||
'width' => $blank['width'] / $canvas['width'],
|
||||
'height' => $blank['height'] / $canvas['height']
|
||||
];
|
||||
|
||||
// Initializing the circle mask
|
||||
$circle = new imagick();
|
||||
$circle->setResolution(dpi, dpi);
|
||||
$circle->newImage($blank['width'], $blank['height'], '#0008');
|
||||
$circle->setImageUnits(imagick::RESOLUTION_PIXELSPERCENTIMETER);
|
||||
$circle->setimageformat('png');
|
||||
$circle->setimagematte(true);
|
||||
$mask = new imagick();
|
||||
$mask->setResolution($dpi, $dpi);
|
||||
$mask->newImage($blank['width'], $blank['height'], '#0000');
|
||||
$mask->setImageUnits(imagick::RESOLUTION_PIXELSPERCENTIMETER);
|
||||
$mask->setimageformat('png');
|
||||
$mask->setimagematte(true);
|
||||
$draw = new imagick_draw();
|
||||
$draw->setfillcolor('#fff');
|
||||
$draw->circle($blank['width'] / 2, $blank['height'] / 2, $blank['width'] / 2, $blank['height']);
|
||||
$circle->drawimage($draw);
|
||||
$mask->drawimage($draw);
|
||||
|
||||
// Initializing the paper places
|
||||
$places = $format->places(type: $type);
|
||||
|
||||
// Calculating the amount of products
|
||||
$have = count($products);
|
||||
|
||||
// Calculating the amount of places per paper
|
||||
$limit = count($places);
|
||||
|
||||
// Calculating the amount of missing products on the paper
|
||||
$need = $limit - $have;
|
||||
|
||||
if ($fit && $have < $limit) {
|
||||
// Received less products then can be fit on the page
|
||||
|
||||
for (; $need++ < $limit;) {
|
||||
// Iterating over free places
|
||||
|
||||
// Dublicating the product
|
||||
$products[] = $products[rand(0, $have - 1)];
|
||||
}
|
||||
}
|
||||
|
||||
// Calculating the amount of papers
|
||||
$amount = $have % $limit;
|
||||
|
||||
for ($page = 0; $page < $amount; $page++) {
|
||||
// Iterating over pages
|
||||
|
||||
// Initializing the paper
|
||||
$paper = new imagick();
|
||||
$paper->setResolution($dpi, $dpi);
|
||||
$paper->newImage($width, $height, new imagick_pixel("#fff"));
|
||||
$paper->setImageUnits(imagick::RESOLUTION_PIXELSPERCENTIMETER);
|
||||
$paper->setImageFormat("jpg");
|
||||
$paper->setimagematte(true);
|
||||
|
||||
// Calculating the products pages offset
|
||||
$offset = $page === 0 ? 0 : $page * $amount;
|
||||
|
||||
foreach ($places as $index => $place) {
|
||||
// Iterating over places
|
||||
|
||||
// Recalculating the place coordinates with DPI
|
||||
$place['x'] *= $dpi;
|
||||
$place['y'] *= $dpi;
|
||||
|
||||
// Initializing the product
|
||||
$product = $products[$offset + $index];
|
||||
|
||||
foreach ($product as $layer) {
|
||||
// Iterating over the product layers
|
||||
|
||||
// Filtering and normalizing the layer parameters
|
||||
empty($layer['scale']) || $layer['scale'] == 0 and $layer['scale'] = 1;
|
||||
|
||||
// Initializing the layer image
|
||||
$image = new imagick();
|
||||
$image->setResolution(dpi, dpi);
|
||||
$image->setResolution($dpi, $dpi);
|
||||
$image->readImage($layer['image']);
|
||||
$image->setImageUnits(imagick::RESOLUTION_PIXELSPERCENTIMETER);
|
||||
$image->setimagematte(true);
|
||||
|
||||
$width = (int) round($blank['width'] * $layer['scale']);
|
||||
$image->adaptiveResizeImage($width, 0);
|
||||
/* $image->roundCornersImage(50, 50); */
|
||||
// Initializing the layer image height before resizing
|
||||
$before = $image->getImageHeight();
|
||||
|
||||
$offset = [
|
||||
'x' => ($blank['width'] - $image->getImageWidth()) / 2,
|
||||
'y' => ($blank['height'] - $image->getImageHeight()) / 2
|
||||
];
|
||||
// Resizing the layer image
|
||||
$image->adaptiveResizeImage((int) round($blank['width'] * $layer['scale']), 0);
|
||||
$image->roundCornersImage($layer['corners'], $layer['corners']);
|
||||
|
||||
$vertical = $blank['height'] - $image->getImageHeight();
|
||||
|
||||
|
||||
|
||||
// Добавить нормальные комментарии после глубокго тестирования
|
||||
|
||||
|
||||
|
||||
|
||||
// Reinitializing the product layer offsets
|
||||
$layer['x'] = (int) round($layer['x'] * $ratio['width']);
|
||||
$layer['y'] = (int) round($layer['y'] * $ratio['height']);
|
||||
// Calculating the layer image coordinates by the layer image mask
|
||||
$vertical = $blank['height'] - $before;
|
||||
$layer['x'] = (int) round($layer['x'] * $ratio['width'] + ($blank['width'] - $image->getImageWidth()) / 2);
|
||||
$layer['y'] = (int) round($layer['y'] * $ratio['height'] + ($blank['height'] - $image->getImageHeight() + ($vertical > 0 ? $vertical : 0)) / 2);
|
||||
unset($before, $vertical);
|
||||
|
||||
// Compositing the layer image mask with the layer image
|
||||
$image->compositeImage(
|
||||
$circle,
|
||||
$mask,
|
||||
imagick::COMPOSITE_DSTIN,
|
||||
(int) round(-$layer['x'] - $offset['x']),
|
||||
/* (int) round(-$layer['y'] - $offset['y'] - $vertical / 2) */
|
||||
(int) round(-$layer['y'] - $offset['y'])
|
||||
(int) round(-$layer['x']),
|
||||
(int) round(-$layer['y'])
|
||||
);
|
||||
|
||||
// Compositing the layer with the paper
|
||||
$paper->setImageVirtualPixelMethod(imagick::VIRTUALPIXELMETHOD_TRANSPARENT);
|
||||
$paper->setImageArtifact('compose:args', "1,0,-0.5,0.5");
|
||||
// Drawing the cutting line
|
||||
$draw = new imagick_draw();
|
||||
$draw->setfillcolor($canvas['background'] ?? '#fff');
|
||||
$stroke = 1;
|
||||
$draw->setStrokeOpacity($stroke);
|
||||
$draw->setStrokeColor('#000');
|
||||
$draw->setStrokeWidth(2);
|
||||
$draw->circle(
|
||||
$place['x'] + $blank['width'] / 2,
|
||||
$place['y'] + $blank['height'] / 2,
|
||||
round($place['x'] + $blank['width'] / 2 - $stroke),
|
||||
round($place['y'] + $blank['height'])
|
||||
);
|
||||
$paper->drawimage($draw);
|
||||
|
||||
// Compositing the layer image with the paper
|
||||
$paper->compositeImage(
|
||||
$image,
|
||||
imagick::COMPOSITE_MATHEMATICS,
|
||||
(int) round($a5['width'] / 2 - $blank['width'] / 2 + $layer['x'] + $offset['x']),
|
||||
/* (int) round($a5['height'] / 2 - $blank['height'] / 2 + $layer['y'] + $offset['y'] + $vertical / 2) */
|
||||
(int) round($a5['height'] / 2 - $blank['height'] / 2 + $layer['y'] + $offset['y'])
|
||||
$image->getImageCompose(),
|
||||
(int) round($place['x'] + $layer['x']),
|
||||
(int) round($place['y'] + $layer['y'])
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Writing the paper file
|
||||
$paper->writeImage(INDEX . DIRECTORY_SEPARATOR . 'test.jpg');
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
// Exit (success)
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ namespace svoboda\pechatalka\models;
|
||||
|
||||
// Files of the project
|
||||
use svoboda\pechatalka\models\core,
|
||||
svoboda\pechatalka\models\enumerations\products\type as product_type;
|
||||
svoboda\pechatalka\models\enumerations\product\type as product_type;
|
||||
|
||||
// Svoboda time
|
||||
use svoboda\time\statement as svoboda;
|
||||
|
||||
BIN
svoboda/pechatalka/system/public/test.jpg
Normal file
BIN
svoboda/pechatalka/system/public/test.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 117 KiB |
@@ -155,7 +155,8 @@ div#pechatalka {
|
||||
width: calc(var(--diameter-cut, var(--width, 100%)) + var(--width-zoom, 0px));
|
||||
height: calc(var(--diameter-cut, var(--width, 100%)) + var(--width-zoom, 0px));
|
||||
cursor: grab;
|
||||
align-content: center;
|
||||
display: flex;
|
||||
place-items: center;
|
||||
|
||||
&:active {
|
||||
cursor: grabbing;
|
||||
|
||||
Reference in New Issue
Block a user