[ 'bg_color' => [245, 245, 245], 'text_color' => [50, 50, 50], 'border_color' => [200, 200, 200], 'font_size' => 24, // Increased for higher resolution 'line_height' => 30, 'font' => __DIR__ . '/fonts/NotoSans-Regular.ttf', ], 'modern' => [ 'bg_color' => [66, 133, 244], 'text_color' => [255, 255, 255], 'border_color' => [59, 120, 220], 'font_size' => 24, 'line_height' => 30, 'font' => __DIR__ . '/fonts/NotoSans-Regular.ttf', ], 'retro' => [ 'bg_color' => [255, 204, 102], 'text_color' => [51, 51, 51], 'border_color' => [204, 153, 0], 'font_size' => 24, 'line_height' => 30, 'font' => __DIR__ . '/fonts/NotoSans-Regular.ttf', ], 'minimal' => [ 'bg_color' => [255, 255, 255], 'text_color' => [0, 0, 0], 'border_color' => [220, 220, 220], 'font_size' => 24, 'line_height' => 30, 'font' => __DIR__ . '/fonts/NotoSans-Regular.ttf', ], ]; // Set default styling $config = [ 'bg_color' => [245, 245, 245], 'text_color' => [50, 50, 50], 'border_color' => [200, 200, 200], 'font_size' => 24, 'line_height' => 30, 'font' => __DIR__ . '/fonts/NotoSans-Regular.ttf', ]; // Ensure required directories exist $dirs = [ __DIR__ . '/characters/Example', __DIR__ . '/fonts' ]; foreach ($dirs as $dir) { if (!is_dir($dir)) { mkdir($dir, 0755, true); } } // Fallback to a built-in font if the specified font file doesn't exist if (!file_exists($config['font'])) { // Try to find any TTF font in the fonts directory $fontFiles = glob(__DIR__ . '/fonts/*.ttf'); if (!empty($fontFiles)) { $config['font'] = $fontFiles[0]; } else { $config['use_builtin_font'] = true; } } // Create the canvas $canvas = imagecreatetruecolor($canvasWidth, $canvasHeight); // Set default white background $whiteColor = imagecolorallocate($canvas, 255, 255, 255); imagefill($canvas, 0, 0, $whiteColor); // Set text color $textColor = imagecolorallocate($canvas, $config['text_color'][0], $config['text_color'][1], $config['text_color'][2]); // Function to get image path with fallback to Example function getAssetPath($assetType, $character) { // Primary path in character's directory $primaryPath = __DIR__ . '/characters/' . $character . '/' . $assetType . '.png'; // Fallback to Example character $fallbackPath = __DIR__ . '/characters/Example/' . $assetType . '.png'; return file_exists($primaryPath) ? $primaryPath : $fallbackPath; } // Check for and load the background image $backgroundPath = getAssetPath('background', $character); $backgroundLoaded = false; if (file_exists($backgroundPath)) { $backgroundImage = imagecreatefrompng($backgroundPath); if ($backgroundImage) { // Resize if needed $bgWidth = imagesx($backgroundImage); $bgHeight = imagesy($backgroundImage); // Copy background to canvas, resizing if necessary imagecopyresampled($canvas, $backgroundImage, 0, 0, 0, 0, $canvasWidth, $canvasHeight, $bgWidth, $bgHeight); imagedestroy($backgroundImage); $backgroundLoaded = true; } } // If no background was loaded, use a solid color background if (!$backgroundLoaded) { // Use legacy style background color if it exists if (isset($styles[$style])) { $bgColor = imagecolorallocate($canvas, $styles[$style]['bg_color'][0], $styles[$style]['bg_color'][1], $styles[$style]['bg_color'][2] ); } else { $bgColor = imagecolorallocate($canvas, 245, 245, 245); // Default light gray } imagefilledrectangle($canvas, 0, 0, $canvasWidth-1, $canvasHeight-1, $bgColor); } // Check for and load the character image $characterPath = getAssetPath('character', $character); $characterLoaded = false; if (file_exists($characterPath)) { $characterImage = imagecreatefrompng($characterPath); if ($characterImage) { // Enable alpha blending imagesavealpha($characterImage, true); // Calculate position (center vertically, align left) $charWidth = imagesx($characterImage); $charHeight = imagesy($characterImage); $charX = $bubbleMargin; $charY = ($canvasHeight - $charHeight) / 2; // Copy character to canvas imagecopy($canvas, $characterImage, $charX, $charY, 0, 0, $charWidth, $charHeight); imagedestroy($characterImage); $characterLoaded = true; } } // Check for and load the bubble image $bubblePath = getAssetPath($bubble_type, $character); $bubbleLoaded = false; if (file_exists($bubblePath)) { $bubbleImage = imagecreatefrompng($bubblePath); if ($bubbleImage) { // Enable alpha blending imagesavealpha($bubbleImage, true); // Calculate position (center vertically, align right of character) $bubbleWidth = imagesx($bubbleImage); $bubbleHeight = imagesy($bubbleImage); $bubbleX = $charWidth + $bubbleMargin * 2; $bubbleY = ($canvasHeight - $bubbleHeight) / 2; // Copy bubble to canvas imagecopy($canvas, $bubbleImage, $bubbleX, $bubbleY, 0, 0, $bubbleWidth, $bubbleHeight); imagedestroy($bubbleImage); $bubbleLoaded = true; } } // If no bubble was loaded, draw a simple bubble if (!$bubbleLoaded) { // Calculate bubble dimensions and position $bubbleX = $charWidth + $bubbleMargin * 2; $bubbleY = $canvasHeight * 0.1; $bubbleWidth = $canvasWidth - $bubbleX - $bubbleMargin; $bubbleHeight = $canvasHeight * 0.8; // Determine bubble style based on legacy styles if available if (isset($styles[$style])) { $bubbleBgColor = imagecolorallocate($canvas, $styles[$style]['bg_color'][0], $styles[$style]['bg_color'][1], $styles[$style]['bg_color'][2] ); $bubbleBorderColor = imagecolorallocate($canvas, $styles[$style]['border_color'][0], $styles[$style]['border_color'][1], $styles[$style]['border_color'][2] ); } else { $bubbleBgColor = imagecolorallocate($canvas, 245, 245, 245); // Default light gray $bubbleBorderColor = imagecolorallocate($canvas, 200, 200, 200); // Default gray border } // Draw bubble based on type if ($bubble_type == 'thought') { // Draw a thought bubble (rounded rectangle with smaller circles) $radius = 40; // Main bubble imagefilledrectangle($canvas, $bubbleX + $radius, $bubbleY, $bubbleX + $bubbleWidth - $radius, $bubbleY + $bubbleHeight, $bubbleBgColor); imagefilledrectangle($canvas, $bubbleX, $bubbleY + $radius, $bubbleX + $bubbleWidth, $bubbleY + $bubbleHeight - $radius, $bubbleBgColor); // Corners imagefilledarc($canvas, $bubbleX + $radius, $bubbleY + $radius, $radius * 2, $radius * 2, 180, 270, $bubbleBgColor, IMG_ARC_PIE); imagefilledarc($canvas, $bubbleX + $bubbleWidth - $radius, $bubbleY + $radius, $radius * 2, $radius * 2, 270, 360, $bubbleBgColor, IMG_ARC_PIE); imagefilledarc($canvas, $bubbleX + $radius, $bubbleY + $bubbleHeight - $radius, $radius * 2, $radius * 2, 90, 180, $bubbleBgColor, IMG_ARC_PIE); imagefilledarc($canvas, $bubbleX + $bubbleWidth - $radius, $bubbleY + $bubbleHeight - $radius, $radius * 2, $radius * 2, 0, 90, $bubbleBgColor, IMG_ARC_PIE); // Draw small circles leading to character $circleCount = 3; $startX = $bubbleX - 20; $startY = $bubbleY + $bubbleHeight/2 + 20; for ($i = 0; $i < $circleCount; $i++) { $circleRadius = 15 - ($i * 4); $circleX = $startX - ($i * 30); $circleY = $startY + ($i * 20); imagefilledellipse($canvas, $circleX, $circleY, $circleRadius*2, $circleRadius*2, $bubbleBgColor); imageellipse($canvas, $circleX, $circleY, $circleRadius*2, $circleRadius*2, $bubbleBorderColor); } // Border imagerectangle($canvas, $bubbleX + $radius, $bubbleY, $bubbleX + $bubbleWidth - $radius, $bubbleY + $bubbleHeight, $bubbleBorderColor); imagerectangle($canvas, $bubbleX, $bubbleY + $radius, $bubbleX + $bubbleWidth, $bubbleY + $bubbleHeight - $radius, $bubbleBorderColor); imagearc($canvas, $bubbleX + $radius, $bubbleY + $radius, $radius * 2, $radius * 2, 180, 270, $bubbleBorderColor); imagearc($canvas, $bubbleX + $bubbleWidth - $radius, $bubbleY + $radius, $radius * 2, $radius * 2, 270, 360, $bubbleBorderColor); imagearc($canvas, $bubbleX + $radius, $bubbleY + $bubbleHeight - $radius, $radius * 2, $radius * 2, 90, 180, $bubbleBorderColor); imagearc($canvas, $bubbleX + $bubbleWidth - $radius, $bubbleY + $bubbleHeight - $radius, $radius * 2, $radius * 2, 0, 90, $bubbleBorderColor); } else { // Draw a speech bubble (rounded rectangle with a pointer) $radius = 40; // Main bubble imagefilledrectangle($canvas, $bubbleX + $radius, $bubbleY, $bubbleX + $bubbleWidth - $radius, $bubbleY + $bubbleHeight, $bubbleBgColor); imagefilledrectangle($canvas, $bubbleX, $bubbleY + $radius, $bubbleX + $bubbleWidth, $bubbleY + $bubbleHeight - $radius, $bubbleBgColor); // Corners imagefilledarc($canvas, $bubbleX + $radius, $bubbleY + $radius, $radius * 2, $radius * 2, 180, 270, $bubbleBgColor, IMG_ARC_PIE); imagefilledarc($canvas, $bubbleX + $bubbleWidth - $radius, $bubbleY + $radius, $radius * 2, $radius * 2, 270, 360, $bubbleBgColor, IMG_ARC_PIE); imagefilledarc($canvas, $bubbleX + $radius, $bubbleY + $bubbleHeight - $radius, $radius * 2, $radius * 2, 90, 180, $bubbleBgColor, IMG_ARC_PIE); imagefilledarc($canvas, $bubbleX + $bubbleWidth - $radius, $bubbleY + $bubbleHeight - $radius, $radius * 2, $radius * 2, 0, 90, $bubbleBgColor, IMG_ARC_PIE); // Draw pointer $pointerX = [$bubbleX, $bubbleX - 40, $bubbleX]; $pointerY = [$bubbleY + $bubbleHeight/2 - 40, $bubbleY + $bubbleHeight/2, $bubbleY + $bubbleHeight/2 + 40]; imagefilledpolygon($canvas, $pointerX, $pointerY, 3, $bubbleBgColor); // Border imagerectangle($canvas, $bubbleX + $radius, $bubbleY, $bubbleX + $bubbleWidth - $radius, $bubbleY + $bubbleHeight, $bubbleBorderColor); imagerectangle($canvas, $bubbleX, $bubbleY + $radius, $bubbleX + $bubbleWidth, $bubbleY + $bubbleHeight - $radius, $bubbleBorderColor); imagearc($canvas, $bubbleX + $radius, $bubbleY + $radius, $radius * 2, $radius * 2, 180, 270, $bubbleBorderColor); imagearc($canvas, $bubbleX + $bubbleWidth - $radius, $bubbleY + $radius, $radius * 2, $radius * 2, 270, 360, $bubbleBorderColor); imagearc($canvas, $bubbleX + $radius, $bubbleY + $bubbleHeight - $radius, $radius * 2, $radius * 2, 90, 180, $bubbleBorderColor); imagearc($canvas, $bubbleX + $bubbleWidth - $radius, $bubbleY + $bubbleHeight - $radius, $radius * 2, $radius * 2, 0, 90, $bubbleBorderColor); // Pointer border imagepolygon($canvas, $pointerX, $pointerY, 3, $bubbleBorderColor); } } // Set text area dimensions $textAreaX = $bubbleX + $bubblePadding; $textAreaY = $bubbleY + $bubblePadding; $textAreaWidth = $bubbleWidth - ($bubblePadding * 2); $textAreaHeight = $bubbleHeight - ($bubblePadding * 2); // Word wrap the text $words = explode(' ', $text); $lines = []; $currentLine = ''; $maxWidth = $textAreaWidth; foreach ($words as $word) { $testLine = $currentLine . ' ' . $word; $testLine = ltrim($testLine); // Remove leading space // Calculate width using built-in or TTF font if (isset($config['use_builtin_font'])) { $textWidth = imagefontwidth(5) * strlen($testLine); } else { $bbox = imagettfbbox($config['font_size'], 0, $config['font'], $testLine); $textWidth = $bbox[2] - $bbox[0]; } if ($textWidth > $maxWidth && $currentLine !== '') { $lines[] = $currentLine; $currentLine = $word; } else { $currentLine = $testLine; } } if ($currentLine !== '') { $lines[] = $currentLine; } // Center text vertically in the bubble $lineCount = count($lines); if (isset($config['use_builtin_font'])) { $totalTextHeight = $lineCount * imagefontheight(5); } else { $totalTextHeight = $lineCount * $config['line_height']; } // Calculate vertical starting position for centered text $startY = $textAreaY + ($textAreaHeight - $totalTextHeight) / 2; if ($startY < $textAreaY) $startY = $textAreaY; // Ensure text doesn't overflow top // Draw text $y = $startY + $config['font_size']; // Start position for centered text foreach ($lines as $line) { if (isset($config['use_builtin_font'])) { // Calculate width for centering horizontally $lineWidth = imagefontwidth(5) * strlen($line); $x = $textAreaX + ($textAreaWidth - $lineWidth) / 2; // Use built-in font (less nice but always available) imagestring($canvas, 5, $x, $y, $line, $textColor); $y += imagefontheight(5); } else { // Calculate width for centering horizontally $bbox = imagettfbbox($config['font_size'], 0, $config['font'], $line); $lineWidth = $bbox[2] - $bbox[0]; $x = $textAreaX + ($textAreaWidth - $lineWidth) / 2; // Use TrueType font (nicer but requires font file) imagettftext($canvas, $config['font_size'], 0, $x, $y, $textColor, $config['font'], $line); $y += $config['line_height']; } } // Output the image imagepng($canvas); // Clean up imagedestroy($canvas);