mnemo_cards/mnemo_cards_telegram_bot/lib/image_generator.dart

142 lines
3.9 KiB
Dart
Raw Normal View History

2025-11-10 23:55:41 +00:00
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<Uint8List?> 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<Uint8List?> _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
}
}