PHP Classes

File: src/Contracts/Customs/Drawers/AbstractDrawer.php

Recommend this page to a friend!
  Packages of Amirreza Ebrahimi   HeroQR Powerful PHP QR Code Library to generate PNG, SVG, PDF, Logos Ready to Use with Laravel   src/Contracts/Customs/Drawers/AbstractDrawer.php   Download  
File: src/Contracts/Customs/Drawers/AbstractDrawer.php
Role: Class source
Content type: text/plain
Description: Class source
Class: HeroQR Powerful PHP QR Code Library to generate PNG, SVG, PDF, Logos Ready to Use with Laravel
Generate QR code images in several formats
Author: By
Last change:
Date: 23 days ago
Size: 13,069 bytes
 

Contents

Class file image Download
<?php namespace HeroQR\Contracts\Customs\Drawers; use Endroid\QrCode\{Color\ColorInterface, Logo\LogoInterface, Matrix\MatrixInterface, QrCodeInterface,}; use HeroQR\Customs\ImageOverlay; /** * Base abstract drawer for rendering QR codes (PNG, SVG, etc.) * including matrix drawing, color allocation, logo handling, and finder logic. */ abstract class AbstractDrawer { protected const FINDER_PATTERN_SIZE = 7; /** * Draws the QR code on a base image. * * @param MatrixInterface $matrix The QR code matrix containing the data modules. * @param QrCodeInterface $qrCode The QR code object providing configuration such as colors and size. * @param LogoInterface|null $logo Optional logo to embed in the center of the QR code. * @param ImageOverlay $imageOverlay Optional overlay to apply on top of the QR code. * @param string $blockShape The shape of the QR code blocks (e.g., 'square', 'circle'). * @param mixed ...$args Additional arguments for custom rendering behavior. * * @return object */ abstract public static function drawQrCode( MatrixInterface $matrix, QrCodeInterface $qrCode, ?LogoInterface $logo, ImageOverlay $imageOverlay, string $blockShape, mixed ...$args ): object; /** * Creates the base image for the QR code. * * @param MatrixInterface $matrix The QR code matrix containing the data modules. * @param array $options Optional configuration array for base image creation (size, colors, etc.). * * @return object Returns the base image object (e.g., \GdImage for PNG, \SimpleXMLElement for SVG). */ abstract protected static function createBaseImage( MatrixInterface $matrix, array $options = [] ): object; /** * Draws the QR code matrix onto the base image. * * @param mixed $baseImage The base image for the QR code. * @param MatrixInterface $matrix The QR code matrix. * @param int $baseBlockSize The pixel size of each QR matrix block. * @param int $foregroundColor The fill color for the blocks. * @param mixed $resizedCorner Resized corner image for rounded blocks. * @param string $blockShape The shape of the block in the QR code * @param LogoInterface|null $logo The logo to embed in the QR code (optional). * @param string $data The data encoded in the QR code (optional). * @return void */ abstract protected static function drawMatrix( mixed $baseImage, MatrixInterface $matrix, int $baseBlockSize, int $foregroundColor, mixed $resizedCorner, string $blockShape, string $data, ?LogoInterface $logo = null, ): void; /** * Allocates the foreground color for the base image (PNG or SVG). * * @param object $baseImage The base image to apply the foreground color to. Can be \GdImage or \SimpleXMLElement (SVG). * @param ColorInterface $color The color to allocate. * @return int|\SimpleXMLElement Returns a GD color resource for PNG or modifies the SVG element. * * @throws \InvalidArgumentException If the base image type is not supported. */ protected static function allocateColor( object $baseImage, ColorInterface $color ): int|\SimpleXMLElement { if ($baseImage instanceof \GdImage) { return self::allocateGdColor($baseImage, $color); } elseif ($baseImage instanceof \SimpleXMLElement) { return self::allocateSvgColor($baseImage, $color); } throw new \InvalidArgumentException('Unsupported image type for allocateForegroundColor'); } /** * Allocates the foreground color for a GD image. * * @param \GdImage $baseImage The GD image to apply the foreground color to. * @param ColorInterface $color The color to allocate. * @return int Returns a GD color resource. */ protected static function allocateGdColor( \GdImage $baseImage, ColorInterface $color ): int { $alpha = $color->getAlpha(); return imagecolorallocatealpha( $baseImage, $color->getRed(), $color->getGreen(), $color->getBlue(), min(127, max(0, $alpha)) ); } /** * Allocates the foreground color for an SVG image. * * @param \SimpleXMLElement $baseImage The SVG image to apply the foreground color to. * @param ColorInterface $color The color to allocate. * @return \SimpleXMLElement Returns the modified SVG element. */ protected static function allocateSvgColor( \SimpleXMLElement $baseImage, ColorInterface $color ): \SimpleXMLElement { $alpha = 1 - ($color->getAlpha() / 127); $baseImage->addAttribute('fill', $color->getHex()); $baseImage->addAttribute('fill-opacity', min(1, max(0, $alpha))); return $baseImage; } /** * Prepares the logo dimensions and calculates the necessary padding based on the data length. * * @param LogoInterface|null $logo The logo to be used in the QR code. * @param string|null $data The data for the QR code. * @param int $blockCount The number of blocks in the QR code. * @param int $baseBlockSize The base size of the QR code blocks. * @param string $drawer Fully-qualified class name of the drawer (e.g., SvgDrawer, PngDrawer). * @return array{int,int,int} The width, height, and padding for the logo. */ protected static function prepareLogoDimensions( ?LogoInterface $logo, ?string $data, int $blockCount, int $baseBlockSize, string $drawer ): array { if ($logo === null) { return [0, 0, 0]; } [$logoWidth, $logoHeight] = self::calculateLogoDimensions($logo, $baseBlockSize); $logoRatio = ($logoWidth * $logoHeight) / ($blockCount * $blockCount); $length = strlen($data ?? ''); $padding = self::calculateLogoPadding($blockCount, $logoRatio, $length, $drawer); return [$logoWidth, $logoHeight, $padding]; } /** * Calculates the logo padding based on drawer type. * * @param int $blockCount The number of blocks in the QR code. * @param float $logoRatio The ratio of the logo size to the QR code size. * @param int $length The length of the QR code data. * @param string $drawer Fully-qualified class name of the drawer (e.g., SvgDrawer, PngDrawer). * @return int The calculated logo padding. */ private static function calculateLogoPadding(int $blockCount, float $logoRatio, int $length, string $drawer): int { if ($drawer === 'HeroQR\Contracts\Customs\Drawers\SvgDrawer') { return max( 1, (int)ceil($blockCount * 0.02 + $logoRatio * $blockCount * 0.3) ); } else { return max( 2, (int)ceil($blockCount * 0.035 + $logoRatio * $blockCount * 0.6 + log10($length ?: 1)) ); } } /** * Calculates the dimensions of the logo based on the base block size. * * @param LogoInterface|null $logo The logo to be resized. * @param int $baseBlockSize The pixel size of each QR matrix block. * @return array{int,int} The resized width and height of the logo in blocks. */ private static function calculateLogoDimensions( ?LogoInterface $logo, int $baseBlockSize ): array { return $logo === null ? [0, 0] : [ (int)ceil($logo->getResizeToWidth() / $baseBlockSize), (int)ceil($logo->getResizeToHeight() / $baseBlockSize) ]; } /** * Calculates the bounds for the logo's placement within the QR code. * * @param array $logo Contains logo dimensions, block count, and padding information. * @return array The start and end row and column indices for logo placement. */ protected static function calculateLogoBounds(array $logo): array { $logoStartRow = max(0, ($logo['blockCount'] - $logo['height']) / 2 - $logo['padding']); $logoEndRow = min($logo['blockCount'], ($logo['blockCount'] + $logo['height']) / 2 + $logo['padding']); $logoStartCol = max(0, ($logo['blockCount'] - $logo['width']) / 2 - $logo['padding']); $logoEndCol = min($logo['blockCount'], ($logo['blockCount'] + $logo['width']) / 2 + $logo['padding']); return [$logoStartRow, $logoEndRow, $logoStartCol, $logoEndCol]; } /** * Formats a number to a string with specific decimal precision. * * @param float $number The number to format. * @param int $precision The number of decimal places. * @return string The formatted number string. */ protected static function formatNumber(float $number, int $precision = 2): string { $string = number_format($number, $precision, '.', ''); $string = rtrim($string, '0'); return rtrim($string, '.'); } /** * Checks if the corner image needs to be rotated based on the matrix position. * * @param int $rowIndex The current row index in the matrix. * @param int $columnIndex The current column index in the matrix. * @param int $blackCount * @return bool True if the corner image needs to be rotated, false otherwise. */ protected static function isFinderStart( int $rowIndex, int $columnIndex, int $blackCount ): bool { return ($rowIndex === 0 && $columnIndex === 0) || ($rowIndex === 0 && $columnIndex === $blackCount - self::FINDER_PATTERN_SIZE) || ($rowIndex === $blackCount - self::FINDER_PATTERN_SIZE && $columnIndex === 0); } /** * Resamples and copies a portion of the source image to the destination image * * @param mixed $dstImage Destination image resource * @param mixed $srcImage Source image resource * @param array $dst_X_Y_W_H Destination coordinates and dimensions (X, Y, Width, Height) * @param array $src_X_Y_W_H Source coordinates and dimensions (X, Y, Width, Height) * @return bool True on success, false on failure */ public static function copyResampledImage( mixed $dstImage, mixed $srcImage, array $dst_X_Y_W_H, array $src_X_Y_W_H ): bool { return imagecopyresampled( $dstImage, $srcImage, $dst_X_Y_W_H['X'], $dst_X_Y_W_H['Y'], $src_X_Y_W_H['X'], $src_X_Y_W_H['Y'], $dst_X_Y_W_H['Width'], $dst_X_Y_W_H['Height'], $src_X_Y_W_H['Width'], $src_X_Y_W_H['Height'], ); } /** * Checks if the given row and column are within the calculated logo bounds. * * @param array $columns Contains row and column indices, and logo bounds. * @return bool True if the position is within the logo bounds, false otherwise. */ protected static function isWithinLogoBounds( array $columns ): bool { return $columns['rowIndex'] >= $columns['logoStartRow'] && $columns['rowIndex'] < $columns['logoEndRow'] && $columns['columnIndex'] >= $columns['logoStartCol'] && $columns['columnIndex'] < $columns['logoEndCol']; } /** * Determines whether the given row and column belong to a finder pattern area * * @param int $rowIndex The row index of the current block * @param int $colIndex The column index of the current block * @param int $blockCount Total number of blocks per side of the QR matrix * * @return bool True if the block is part of a finder pattern; otherwise false */ protected static function isFinderPattern( int $rowIndex, int $colIndex, int $blockCount ): bool { return ($rowIndex < self::FINDER_PATTERN_SIZE && $colIndex < self::FINDER_PATTERN_SIZE) || ($rowIndex < self::FINDER_PATTERN_SIZE && $colIndex >= $blockCount - self::FINDER_PATTERN_SIZE) || ($rowIndex >= $blockCount - self::FINDER_PATTERN_SIZE && $colIndex < self::FINDER_PATTERN_SIZE); } /** * Returns the rotation angle (0, 90, or 270 degrees) for a finder pattern based on its position * * @param int $rowIndex The row index of the finder * @param int $colIndex The column index of the finder * @return int Rotation angle in degrees (0, 90, or 270) */ protected static function getFinderRotation(int $rowIndex, int $colIndex): int { if ($rowIndex === 0 && $colIndex > 0) return 90; if ($rowIndex > 0 && $colIndex === 0) return 270; return 0; } }