diff --git a/svoboda/pechatalka/system/controllers/generator.php b/svoboda/pechatalka/system/controllers/generator.php index bc042b3..c1b6267 100755 --- a/svoboda/pechatalka/system/controllers/generator.php +++ b/svoboda/pechatalka/system/controllers/generator.php @@ -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)) { diff --git a/svoboda/pechatalka/system/models/enumerations/paper/format.php b/svoboda/pechatalka/system/models/enumerations/paper/format.php new file mode 100644 index 0000000..d28e08d --- /dev/null +++ b/svoboda/pechatalka/system/models/enumerations/paper/format.php @@ -0,0 +1,91 @@ + + */ +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; + } +} diff --git a/svoboda/pechatalka/system/models/enumerations/products/type.php b/svoboda/pechatalka/system/models/enumerations/product/type.php similarity index 55% rename from svoboda/pechatalka/system/models/enumerations/products/type.php rename to svoboda/pechatalka/system/models/enumerations/product/type.php index d302882..c42af59 100644 --- a/svoboda/pechatalka/system/models/enumerations/products/type.php +++ b/svoboda/pechatalka/system/models/enumerations/product/type.php @@ -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 @@ -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 ] ] }; diff --git a/svoboda/pechatalka/system/models/paper.php b/svoboda/pechatalka/system/models/paper.php index 36cd9f6..3e5a36a 100644 --- a/svoboda/pechatalka/system/models/paper.php +++ b/svoboda/pechatalka/system/models/paper.php @@ -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 paper dimensions + $dimensions = $format->dimensions(); + $width = (int) round($dimensions['width'] * $dpi); + $height = (int) round($dimensions['height'] * $dpi); + unset($dimensions); - // Initializing the A5 paper dimensions - $a5 = [ - 'width' => (int) round(10.5 * dpi), - 'height' => (int) round(14.8 * dpi) + // Initializing the product areas + $areas = $type->areas(); + + // Calculating the product blank dimensions by the print $dpi + $blank = [ + 'width' => (int) round($areas['blank']['width'] * $dpi), + 'height' => (int) round($areas['blank']['height'] * $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"); + // Calculating the $dpi ratio of the pechatalka interface to the blank dimensions + $ratio = [ + 'width' => $blank['width'] / $canvas['width'], + 'height' => $blank['height'] / $canvas['height'] + ]; - foreach ($products as $product) { - // Iterating over products + // Initializing the circle mask + $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']); + $mask->drawimage($draw); - foreach ($product as $layer) { - // Iterating over the product layers + // Initializing the paper places + $places = $format->places(type: $type); - // Filtering - empty($layer['scale']) || $layer['scale'] == 0 and $layer['scale'] = 1; + // Calculating the amount of products + $have = count($products); - // Initializing the product areas - $areas = $type->areas(); + // Calculating the amount of places per paper + $limit = count($places); - // 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) - ]; + // Calculating the amount of missing products on the paper + $need = $limit - $have; - // Calculating the DPI ratio of the pechatalka interface to the blank dimensions - $ratio = [ - 'width' => $blank['width'] / $canvas['width'], - 'height' => $blank['height'] / $canvas['height'] - ]; + if ($fit && $have < $limit) { + // Received less products then can be fit on the page - // 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); - $draw = new imagick_draw(); - $draw->setfillcolor('#fff'); - $draw->circle($blank['width'] / 2, $blank['height'] / 2, $blank['width'] / 2, $blank['height']); - $circle->drawimage($draw); + for (; $need++ < $limit;) { + // Iterating over free places - // Initializing the layer image - $image = new imagick(); - $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); */ - - $offset = [ - 'x' => ($blank['width'] - $image->getImageWidth()) / 2, - 'y' => ($blank['height'] - $image->getImageHeight()) / 2 - ]; - - $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']); - - $image->compositeImage( - $circle, - imagick::COMPOSITE_DSTIN, - (int) round(-$layer['x'] - $offset['x']), - /* (int) round(-$layer['y'] - $offset['y'] - $vertical / 2) */ - (int) round(-$layer['y'] - $offset['y']) - ); - - // Compositing the layer with the paper - $paper->setImageVirtualPixelMethod(imagick::VIRTUALPIXELMETHOD_TRANSPARENT); - $paper->setImageArtifact('compose:args', "1,0,-0.5,0.5"); - $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']) - ); - - // Writing the paper file - $paper->writeImage(INDEX . DIRECTORY_SEPARATOR . 'test.jpg'); + // Dublicating the product + $products[] = $products[rand(0, $have - 1)]; } } - return true; + // 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->readImage($layer['image']); + $image->setImageUnits(imagick::RESOLUTION_PIXELSPERCENTIMETER); + $image->setimagematte(true); + + // Initializing the layer image height before resizing + $before = $image->getImageHeight(); + + // Resizing the layer image + $image->adaptiveResizeImage((int) round($blank['width'] * $layer['scale']), 0); + $image->roundCornersImage($layer['corners'], $layer['corners']); + + // 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( + $mask, + imagick::COMPOSITE_DSTIN, + (int) round(-$layer['x']), + (int) round(-$layer['y']) + ); + + // 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, + $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'); + } + + + // Exit (success) + return []; } } diff --git a/svoboda/pechatalka/system/models/product.php b/svoboda/pechatalka/system/models/product.php index 5d854eb..2e895cc 100644 --- a/svoboda/pechatalka/system/models/product.php +++ b/svoboda/pechatalka/system/models/product.php @@ -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; diff --git a/svoboda/pechatalka/system/public/test.jpg b/svoboda/pechatalka/system/public/test.jpg new file mode 100644 index 0000000..04ee739 Binary files /dev/null and b/svoboda/pechatalka/system/public/test.jpg differ diff --git a/svoboda/pechatalka/system/public/themes/default/css/pages/generator/pin.css b/svoboda/pechatalka/system/public/themes/default/css/pages/generator/pin.css index f33fd23..5602ad9 100644 --- a/svoboda/pechatalka/system/public/themes/default/css/pages/generator/pin.css +++ b/svoboda/pechatalka/system/public/themes/default/css/pages/generator/pin.css @@ -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;