Skip to content

Commit cbb10e5

Browse files
committed
feat: Implement export cover art wall
1 parent 60995a6 commit cbb10e5

File tree

4 files changed

+99
-4
lines changed

4 files changed

+99
-4
lines changed

assets/watermark.png

3.59 KB
Loading

lib/utils/dialogs/export_cover_wall/export_cover_wall_dialog.dart

+24-2
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,18 @@ import '../mix/utils/select_input_controller.dart';
1616
import '../mix/widgets/select_buttons_section.dart';
1717
import '../mix/widgets/select_input_section.dart';
1818

19+
Size parseSize(String input) {
20+
List<String> parts = input.split(' ');
21+
22+
int widthRatio = int.parse(parts[0]);
23+
int heightRatio = int.parse(parts[1]);
24+
25+
int width = 1920;
26+
int height = (1920 * heightRatio / widthRatio).round();
27+
28+
return Size(width.toDouble(), height.toDouble());
29+
}
30+
1931
List<SelectItem> sizeItems(BuildContext context) => [
2032
SelectItem(
2133
value: '16 9',
@@ -153,8 +165,18 @@ class ExportCoverWallDialogState extends State<ExportCoverWallDialog> {
153165
isLoading = true;
154166
});
155167

156-
final image =
157-
await renderCoverWall(widget.type, widget.id);
168+
final image = await renderCoverWall(
169+
widget.type,
170+
widget.id,
171+
parseSize(ratioController.selectedValue ?? '16 9'),
172+
backgroundController.selectedValue == 'light'
173+
? Colors.white
174+
: Colors.black,
175+
frameController.selectedValue == 'enable',
176+
backgroundController.selectedValue == 'light'
177+
? Colors.black
178+
: Colors.white,
179+
);
158180

159181
final FileSaveLocation? result = await getSaveLocation(
160182
suggestedName: 'cover_wall.png',

lib/utils/dialogs/export_cover_wall/utils/render_cover_wall.dart

+74-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import 'dart:ui' as ui;
22

3+
import 'package:flutter/services.dart';
34
import 'package:fluent_ui/fluent_ui.dart';
45
import 'package:rune/utils/query_list.dart';
56

@@ -13,16 +14,30 @@ import '../../../build_query.dart';
1314
import '../../../load_and_resize_image.dart';
1415
import '../../../api/query_mix_tracks.dart';
1516

17+
Future<ui.Image> loadImageFromAsset(String assetPath) async {
18+
final ByteData data = await rootBundle.load(assetPath);
19+
final Uint8List bytes = data.buffer.asUint8List();
20+
final codec = await ui.instantiateImageCodec(bytes);
21+
final frame = await codec.getNextFrame();
22+
return frame.image;
23+
}
24+
1625
Future<ui.Image> renderCoverWall(
1726
CollectionType type,
1827
int id,
28+
Size size,
29+
Color background,
30+
bool frame,
31+
Color watermarkColor,
1932
) async {
20-
const sizeDefinition = BoxConstraints(maxWidth: 1920, maxHeight: 1080);
33+
final sizeDefinition =
34+
BoxConstraints(maxWidth: size.width, maxHeight: size.height);
2135
final gridSize = calculateCoverWallGridSize(sizeDefinition).ceil();
2236
const gap = 4;
2337

2438
ui.PictureRecorder recorder = ui.PictureRecorder();
2539
ui.Canvas canvas = ui.Canvas(recorder);
40+
2641
final queries = await buildQuery(type, id);
2742
final newItems = await queryMixTracks(
2843
QueryList([...queries, ('filter::with_cover_art', 'true')]),
@@ -48,16 +63,73 @@ Future<ui.Image> renderCoverWall(
4863
),
4964
);
5065

66+
final backgroundPaint = Paint()
67+
..color = background
68+
..style = PaintingStyle.fill;
69+
70+
canvas.drawRect(
71+
Rect.fromLTWH(0, 0, size.width, size.height),
72+
backgroundPaint,
73+
);
74+
5175
final painter = CoverWallBackgroundPainter(
5276
grid: grid,
5377
gridSize: gridSize,
5478
gap: gap,
5579
images: images,
5680
);
5781

58-
final size = ui.Size(1920, 1080);
5982
painter.paint(canvas, size);
6083

84+
if (frame) {
85+
const strokeSize = 16.0;
86+
const bottomSize = 100.0;
87+
88+
final borderPaint = Paint()
89+
..color = background
90+
..style = PaintingStyle.stroke;
91+
92+
borderPaint.strokeWidth = strokeSize;
93+
94+
final path = Path();
95+
96+
path.moveTo(0, strokeSize / 2);
97+
path.lineTo(size.width, strokeSize / 2);
98+
canvas.drawPath(path, borderPaint);
99+
100+
path.reset();
101+
path.moveTo(size.width - strokeSize / 2, strokeSize);
102+
path.lineTo(size.width - strokeSize / 2, size.height - bottomSize / 2);
103+
canvas.drawPath(path, borderPaint);
104+
105+
path.reset();
106+
path.moveTo(strokeSize / 2, strokeSize);
107+
path.lineTo(strokeSize / 2, size.height - bottomSize / 2);
108+
canvas.drawPath(path, borderPaint);
109+
110+
borderPaint.strokeWidth = bottomSize;
111+
112+
path.reset();
113+
path.moveTo(0, size.height - bottomSize / 2);
114+
path.lineTo(size.width, size.height - bottomSize / 2);
115+
canvas.drawPath(path, borderPaint);
116+
117+
final watermarkPaint = Paint()
118+
..colorFilter = ColorFilter.mode(
119+
watermarkColor,
120+
BlendMode.srcATop,
121+
);
122+
123+
final position = Offset(
124+
0,
125+
(size.height - 100),
126+
);
127+
128+
final watermark = await loadImageFromAsset('assets/watermark.png');
129+
130+
canvas.drawImage(watermark, position, watermarkPaint);
131+
}
132+
61133
return recorder
62134
.endRecording()
63135
.toImage(size.width.floor(), size.height.floor());

pubspec.yaml

+1
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,7 @@ flutter:
121121
- assets/mac-tray.svg
122122
- assets/tray_icon_light.ico
123123
- assets/tray_icon_dark.ico
124+
- assets/watermark.png
124125
# An image asset can refer to one or more resolution-specific "variants", see
125126
# https://flutter.dev/assets-and-images/#resolution-aware
126127
# For details regarding adding assets from package dependencies, see

0 commit comments

Comments
 (0)