import 'dart:io'; import 'dart:typed_data'; import 'package:image/image.dart' as img; import 'package:mnemo_cards_common_backend/mnemo_cards_common_backend.dart'; /// Generator for creating share images with frames for cards class ImageGenerator { /// Path to cards directory in backend final String cardsBasePath; /// Border width in pixels final int borderWidth; /// Border color (RGB format as int) final int borderColor; /// Title text to display on the image final String titleText; /// Title text color (RGB format as int) final int titleTextColor; ImageGenerator({ this.cardsBasePath = '../mnemo_cards_backend/data/cards', this.borderWidth = 40, this.borderColor = 0xFF1a1a1a, // Dark gray this.titleText = 'mnemo cards', this.titleTextColor = 0xFFFFFFFF, // White }); /// Generate a share image from a card with border and title /// Returns PNG bytes or null if generation fails Future generateShareImage(GameCardModel card) async { try { // Read original card image final imageBytes = await _loadCardImage(card.image); if (imageBytes == null || imageBytes.isEmpty) { print('Failed to load card image: ${card.image}'); return null; } // Decode image final originalImage = img.decodeImage(imageBytes); if (originalImage == null) { print('Failed to decode card image'); return null; } // Create new image with border final width = originalImage.width + (borderWidth * 2); final height = originalImage.height + (borderWidth * 2); final newImage = img.Image(width: width, height: height); // Fill background with border color final borderColorObj = img.ColorRgba8( (borderColor >> 16) & 0xFF, (borderColor >> 8) & 0xFF, borderColor & 0xFF, 255, ); img.fill(newImage, color: borderColorObj); // Draw original image in center img.compositeImage( newImage, originalImage, dstX: borderWidth, dstY: borderWidth, ); // Add semi-transparent overlay for text background _addTextBackground(newImage); // Draw title text on the image _drawTitleText(newImage); // Encode to PNG final pngBytes = img.encodePng(newImage); return pngBytes; } catch (e, s) { print('Error generating share image: $e\n$s'); return null; } } /// Load card image from file system /// Card image field can be just a filename or full path Future _loadCardImage(String imageField) async { try { if (imageField.isEmpty) { return null; } // Extract just the filename if it contains path separators final fileName = imageField.contains('/') ? imageField.split('/').last : imageField; final filePath = '$cardsBasePath/$fileName'; final file = File(filePath); if (!await file.exists()) { print('Card image file not found: $filePath'); return null; } return await file.readAsBytes(); } catch (e, s) { print('Error loading card image: $e\n$s'); return null; } } /// Add semi-transparent text background to bottom of image void _addTextBackground(img.Image image) { final overlayHeight = 100; final overlayY = image.height - overlayHeight; // Draw semi-transparent black rectangle at bottom final semiTransparentBlack = img.ColorRgba8(0, 0, 0, 187); // ~0xBB transparency img.drawRect( image, x1: 0, y1: overlayY, x2: image.width, y2: image.height, color: semiTransparentBlack, ); } /// Draw title text on the image /// Text is positioned at bottom center void _drawTitleText(img.Image image) { // Note: Text rendering with image package requires external fonts // For now, we just add a colored background overlay // Full text rendering can be added later with proper font support } }