import 'dart:ui' as ui; import 'package:flutter/material.dart'; import 'package:google_fonts/google_fonts.dart'; import 'package:aesthetica_wallpaper/models/recipe.dart'; // ------------------------------------- // --- 3. 画布绘制器 (WallpaperPainter) --- // ------------------------------------- class WallpaperPainter extends CustomPainter { final ui.Image image; final Recipe recipe; final Color timeOverlay; final double batterySaturation; // 1.0 = 正常, 0.0 = 黑白 WallpaperPainter({ required this.image, required this.recipe, required this.timeOverlay, required this.batterySaturation, }); // 辅助函数,用于根据 亮度、对比度、饱和度 生成 5x5 颜色矩阵 ColorFilter _buildColorMatrix() { // 亮度 (-1 to 1, default 0) final double brightness = recipe.brightness; // 对比度 (0 to 4, default 1) final double contrast = recipe.contrast; // 饱和度 (0 to 4, default 1) final double saturation = recipe.saturation * batterySaturation; // 应用电量效果 // 矩阵从单位矩阵开始 List matrix = [ 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, ]; // 1. 应用饱和度 if (saturation != 1.0) { final sat = saturation; const lumR = 0.3086; const lumG = 0.6094; const lumB = 0.0820; matrix = [ lumR * (1 - sat) + sat, lumG * (1 - sat), lumB * (1 - sat), 0, 0, lumR * (1 - sat), lumG * (1 - sat) + sat, lumB * (1 - sat), 0, 0, lumR * (1 - sat), lumG * (1 - sat), lumB * (1 - sat) + sat, 0, 0, 0, 0, 0, 1, 0, ]; } // 2. 应用对比度 if (contrast != 1.0) { final translate = (1.0 - contrast) * 128; // 注意:这里需要矩阵乘法,为了简单起见,我们假设在饱和度*之后*应用 matrix[0] *= contrast; matrix[5] *= contrast; matrix[10] *= contrast; matrix[4] += translate; matrix[9] += translate; matrix[14] += translate; } // 3. 应用亮度 if (brightness != 0.0) { final b = brightness * 255; matrix[4] += b; matrix[9] += b; matrix[14] += b; } return ColorFilter.matrix(matrix); } // 获取字体样式 TextStyle _getFont() { final style = TextStyle(fontSize: 32, color: Color(recipe.textColor)); try { if (recipe.fontFamily == 'Roboto') { return GoogleFonts.roboto( fontSize: 32, color: Color(recipe.textColor), fontWeight: FontWeight.bold, ); } // 默认 return GoogleFonts.lato(fontSize: 32, color: Color(recipe.textColor)); } catch (e) { // 字体加载失败时的回退 return style; } } @override void paint(Canvas canvas, Size size) { // 裁剪画布,防止绘制到边界之外 canvas.clipRect(Offset.zero & size); // --- 1. 准备画笔 --- final Paint paint = Paint() ..filterQuality = FilterQuality.low; // 默认使用低质量(更快) // 定义源矩形 (整张图片) final Rect srcRect = Rect.fromLTWH( 0, 0, image.width.toDouble(), image.height.toDouble(), ); // 定义目标矩形 (填满画布) final Rect dstRect = Rect.fromLTWH(0, 0, size.width, size.height); // --- 2. 应用滤镜 --- // 应用颜色矩阵 (B/C/S + Battery) paint.colorFilter = _buildColorMatrix(); // 应用模糊 if (recipe.blur > 0.0) { paint.imageFilter = ui.ImageFilter.blur( sigmaX: recipe.blur, sigmaY: recipe.blur, ); } // --- 3. 绘制图片 --- // `drawImageRect` 会使用 `paint` 中定义的滤镜来绘制 canvas.drawImageRect(image, srcRect, dstRect, paint); // --- 4. 绘制动态蒙版 --- if (timeOverlay.a > 0) { canvas.drawRect(dstRect, Paint()..color = timeOverlay); } // --- 5. 绘制文字 (使用 TextPainter) --- if (recipe.overlayText.isNotEmpty) { final textPainter = TextPainter( text: TextSpan(text: recipe.overlayText, style: _getFont()), textDirection: ui.TextDirection.ltr, textAlign: TextAlign.center, ); // 布局文字,限制最大宽度 textPainter.layout(maxWidth: size.width - 40); // 左右各留 20 padding // 计算居中位置 final offset = Offset( (size.width - textPainter.width) / 2, (size.height - textPainter.height) / 2, ); // 绘制文字 textPainter.paint(canvas, offset); } } // 优化 shouldRepaint // 仅当绘制所需的数据发生变化时才重绘 @override bool shouldRepaint(covariant WallpaperPainter oldDelegate) { // 简单的比较 (因为 Recipe 是可变的,这可能不总是触发) // return oldDelegate.image != image || // oldDelegate.recipe != recipe || // oldDelegate.timeOverlay != timeOverlay || // oldDelegate.batterySaturation != batterySaturation; // 优化:比较所有字段 final oldRecipe = oldDelegate.recipe; final newRecipe = recipe; return oldDelegate.image != image || oldRecipe.baseImagePath != newRecipe.baseImagePath || oldRecipe.brightness != newRecipe.brightness || oldRecipe.contrast != newRecipe.contrast || oldRecipe.saturation != newRecipe.saturation || oldRecipe.blur != newRecipe.blur || oldRecipe.pixelate != newRecipe.pixelate || oldRecipe.overlayText != newRecipe.overlayText || oldRecipe.fontFamily != newRecipe.fontFamily || oldRecipe.textColor != newRecipe.textColor || oldDelegate.timeOverlay != timeOverlay || oldDelegate.batterySaturation != batterySaturation; } }