142 lines
3.9 KiB
Dart
142 lines
3.9 KiB
Dart
|
|
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
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|