perf(app): Phase 2 前端性能优化 5 项 — 8b-D01/D02/D03/M02/N01
- 8b-D01: Isar 添加 authorId+dateEpoch 复合索引和 dateEpoch 单独索引 - 8b-D02: getJournals 分页改为 DB 层 .offset().limit() 替代 Dart 层 sublist - 8b-D03: home_bloc monthCount 改用日期范围独立查询(不受分页限制) - 8b-M02: 笔画光栅化改为 BBox 裁剪 — 短笔画不再创建全画布尺寸图像 - _CacheEntry 增加 offset 字段记录 BBox 偏移 - _rasterizeStroke 计算包围盒 + 4px padding - _compositeIncremental 使用 offset 定位 - 8b-N01: SyncEngine enqueue 合并同一资源的操作 - create+update → create(最新数据) - update+update → update(最新数据) - update+delete → delete - create+delete → 取消(不发送) - 注意: Isar .g.dart 需运行 build_runner 重新生成
This commit is contained in:
@@ -20,8 +20,10 @@ import 'stroke_renderer.dart';
|
||||
class _CacheEntry {
|
||||
final ui.Image image;
|
||||
final Stroke stroke;
|
||||
/// BBox 偏移量 — 光栅化时裁剪的起点,合成时用于定位
|
||||
final Offset offset;
|
||||
|
||||
const _CacheEntry({required this.image, required this.stroke});
|
||||
const _CacheEntry({required this.image, required this.stroke, required this.offset});
|
||||
}
|
||||
|
||||
// ===== 光栅化缓存 =====
|
||||
@@ -76,18 +78,22 @@ class StrokeRasterCache {
|
||||
|
||||
/// 添加一条已完成笔画到缓存
|
||||
///
|
||||
/// 光栅化该笔画为 ui.Image,然后增量合成到 compositeImage。
|
||||
/// 光栅化该笔画为 ui.Image(仅 BBox 区域),然后增量合成到 compositeImage。
|
||||
Future<void> addStroke(Stroke stroke) async {
|
||||
if (_canvasSize == Size.zero) return;
|
||||
|
||||
// 光栅化单笔画
|
||||
final image = await _rasterizeStroke(stroke);
|
||||
if (image == null) return;
|
||||
// 光栅化单笔画(BBox 裁剪)
|
||||
final result = await _rasterizeStroke(stroke);
|
||||
if (result == null) return;
|
||||
|
||||
_cache[stroke.id] = _CacheEntry(image: image, stroke: stroke);
|
||||
_cache[stroke.id] = _CacheEntry(
|
||||
image: result.image,
|
||||
stroke: stroke,
|
||||
offset: result.offset,
|
||||
);
|
||||
|
||||
// 增量合成:将新笔画绘制到现有 compositeImage 之上
|
||||
await _compositeIncremental(stroke, image);
|
||||
await _compositeIncremental(stroke, result.image, result.offset);
|
||||
}
|
||||
|
||||
/// 同步笔画列表(用于撤销/重做后与 BLoC 状态对齐)
|
||||
@@ -108,9 +114,13 @@ class StrokeRasterCache {
|
||||
final toAdd = currentIds.difference(cachedIds);
|
||||
for (final stroke in strokes) {
|
||||
if (toAdd.contains(stroke.id)) {
|
||||
final image = await _rasterizeStroke(stroke);
|
||||
if (image != null) {
|
||||
_cache[stroke.id] = _CacheEntry(image: image, stroke: stroke);
|
||||
final result = await _rasterizeStroke(stroke);
|
||||
if (result != null) {
|
||||
_cache[stroke.id] = _CacheEntry(
|
||||
image: result.image,
|
||||
stroke: stroke,
|
||||
offset: result.offset,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -155,8 +165,12 @@ class StrokeRasterCache {
|
||||
|
||||
// ===== 光栅化 =====
|
||||
|
||||
/// 将单条笔画光栅化为 ui.Image
|
||||
Future<ui.Image?> _rasterizeStroke(Stroke stroke) async {
|
||||
/// 将单条笔画光栅化为 ui.Image — 仅光栅化 BBox 区域(性能优化 8b-M02)
|
||||
///
|
||||
/// 计算笔画的包围盒 (bounding box),仅对该区域光栅化,
|
||||
/// 大幅减少 GPU 内存占用(短笔画从全画布 4096×4096 降到实际尺寸)。
|
||||
/// 返回 null 表示笔画无有效点。
|
||||
Future<({ui.Image image, Offset offset})?> _rasterizeStroke(Stroke stroke) async {
|
||||
final outlinePoints = pointsToOutline(
|
||||
stroke.points,
|
||||
stroke.brushType,
|
||||
@@ -165,16 +179,38 @@ class StrokeRasterCache {
|
||||
);
|
||||
if (outlinePoints.isEmpty) return null;
|
||||
|
||||
// 计算笔画包围盒
|
||||
double minX = double.infinity, minY = double.infinity;
|
||||
double maxX = double.negativeInfinity, maxY = double.negativeInfinity;
|
||||
for (final p in outlinePoints) {
|
||||
if (p.dx < minX) minX = p.dx;
|
||||
if (p.dy < minY) minY = p.dy;
|
||||
if (p.dx > maxX) maxX = p.dx;
|
||||
if (p.dy > maxY) maxY = p.dy;
|
||||
}
|
||||
|
||||
// 添加边距(抗锯齿 + 笔触溢出)
|
||||
const padding = 4.0;
|
||||
final bboxLeft = (minX - padding).clamp(0.0, _canvasSize.width);
|
||||
final bboxTop = (minY - padding).clamp(0.0, _canvasSize.height);
|
||||
final bboxRight = (maxX + padding).clamp(0.0, _canvasSize.width);
|
||||
final bboxBottom = (maxY + padding).clamp(0.0, _canvasSize.height);
|
||||
final bboxWidth = (bboxRight - bboxLeft).clamp(1.0, 4096.0);
|
||||
final bboxHeight = (bboxBottom - bboxTop).clamp(1.0, 4096.0);
|
||||
|
||||
final path = buildStrokePath(outlinePoints);
|
||||
final paint = createPaintForStroke(stroke);
|
||||
|
||||
final recorder = ui.PictureRecorder();
|
||||
final canvas = Canvas(recorder);
|
||||
|
||||
// 平移坐标系,使 BBox 左上角对齐 (0, 0)
|
||||
canvas.translate(-bboxLeft, -bboxTop);
|
||||
|
||||
// 橡皮擦需要 saveLayer 保护,避免穿透
|
||||
if (stroke.brushType == BrushType.eraser) {
|
||||
canvas.saveLayer(
|
||||
Rect.fromLTWH(0, 0, _canvasSize.width, _canvasSize.height),
|
||||
Rect.fromLTWH(0, 0, bboxWidth, bboxHeight),
|
||||
Paint(),
|
||||
);
|
||||
}
|
||||
@@ -186,16 +222,20 @@ class StrokeRasterCache {
|
||||
}
|
||||
|
||||
final picture = recorder.endRecording();
|
||||
return picture.toImage(
|
||||
_canvasSize.width.toInt().clamp(1, 4096),
|
||||
_canvasSize.height.toInt().clamp(1, 4096),
|
||||
final image = await picture.toImage(
|
||||
bboxWidth.toInt().clamp(1, 4096),
|
||||
bboxHeight.toInt().clamp(1, 4096),
|
||||
);
|
||||
|
||||
return (image: image, offset: Offset(bboxLeft, bboxTop));
|
||||
}
|
||||
|
||||
// ===== 合成 =====
|
||||
|
||||
/// 增量合成:将新笔画图像绘制到现有 compositeImage 上
|
||||
Future<void> _compositeIncremental(Stroke stroke, ui.Image strokeImage) async {
|
||||
///
|
||||
/// strokeImage 是 BBox 裁剪后的图像,offset 是其原始位置偏移。
|
||||
Future<void> _compositeIncremental(Stroke stroke, ui.Image strokeImage, Offset offset) async {
|
||||
final recorder = ui.PictureRecorder();
|
||||
final canvas = Canvas(recorder);
|
||||
|
||||
@@ -210,15 +250,15 @@ class StrokeRasterCache {
|
||||
canvas.drawImage(_compositeImage!, Offset.zero, Paint());
|
||||
}
|
||||
|
||||
// 再绘制新笔画(橡皮擦用 dstOut)
|
||||
// 再绘制新笔画(橡皮擦用 dstOut),使用 BBox offset 定位
|
||||
if (stroke.brushType == BrushType.eraser) {
|
||||
canvas.drawImage(
|
||||
strokeImage,
|
||||
Offset.zero,
|
||||
offset,
|
||||
Paint()..blendMode = BlendMode.dstOut,
|
||||
);
|
||||
} else {
|
||||
canvas.drawImage(strokeImage, Offset.zero, Paint());
|
||||
canvas.drawImage(strokeImage, offset, Paint());
|
||||
}
|
||||
|
||||
canvas.restore();
|
||||
|
||||
Reference in New Issue
Block a user