2024-05-14 12:44:26 +00:00
|
|
|
import 'dart:io';
|
2024-05-17 09:05:35 +00:00
|
|
|
import 'dart:typed_data';
|
2024-05-14 12:44:26 +00:00
|
|
|
|
2024-05-17 09:05:35 +00:00
|
|
|
import 'package:args/args.dart';
|
2024-05-14 12:44:26 +00:00
|
|
|
import 'package:image/image.dart';
|
2024-05-17 09:05:35 +00:00
|
|
|
import 'package:logging/logging.dart';
|
2024-05-14 12:44:26 +00:00
|
|
|
|
|
|
|
import 'models/screenshot_args.dart';
|
|
|
|
import 'models/screenshot_device.dart';
|
|
|
|
import 'models/screenshot_layout.dart';
|
|
|
|
import 'utils/parse_configs.dart';
|
|
|
|
|
|
|
|
final _configs = parseScreenshotConfigs();
|
|
|
|
|
2024-05-17 09:05:35 +00:00
|
|
|
Future<int> main(List<String> args) async {
|
|
|
|
final parser = ArgParser()
|
2024-05-21 10:56:01 +00:00
|
|
|
..addFlag('verbose', abbr: 'v', help: 'Verbose output')
|
|
|
|
..addOption('platform', abbr: 'p', help: 'Device platform', mandatory: true)
|
2024-05-17 09:05:35 +00:00
|
|
|
..addOption('device', abbr: 'd', help: 'device_snake_name', mandatory: true)
|
2024-05-21 10:56:01 +00:00
|
|
|
..addOption('layout', abbr: 'l', help: 'Device layout', mandatory: true);
|
2024-05-17 09:05:35 +00:00
|
|
|
final ArgResults argResults = parser.parse(args);
|
|
|
|
|
|
|
|
if (argResults['verbose'] as bool) {
|
|
|
|
Logger.root.level = Level.ALL;
|
|
|
|
} else {
|
|
|
|
Logger.root.level = Level.INFO;
|
|
|
|
}
|
|
|
|
|
|
|
|
final platform = argResults["platform"] as String;
|
|
|
|
final device = argResults["device"] as String;
|
|
|
|
final layout = ScreenshotLayout.values.firstWhere((e) => e.name == argResults["layout"] as String);
|
|
|
|
|
|
|
|
Directory('screenshots/generated/raw/$platform/$device').listSync().forEach((filePath) async {
|
|
|
|
final screenshotName = filePath.path.split('/').last.replaceAll('.png', '');
|
|
|
|
final screenshotBytes = File(filePath.path).readAsBytesSync();
|
|
|
|
final screenshot = decodePng(Uint8List.fromList(screenshotBytes))!;
|
|
|
|
|
|
|
|
final screenshotArgs = ScreenshotArgs.fromRawName(
|
|
|
|
name: screenshotName,
|
|
|
|
deviceName: device,
|
|
|
|
platformFolder: platform,
|
|
|
|
);
|
|
|
|
|
|
|
|
final file = await File(screenshotArgs.toPath(layout.name)).create(recursive: true);
|
|
|
|
file.writeAsBytesSync(
|
|
|
|
encodePng(
|
|
|
|
screenshot.convertToStoreScreenshot(
|
|
|
|
args: screenshotArgs,
|
|
|
|
layout: layout,
|
|
|
|
),
|
|
|
|
),
|
|
|
|
);
|
|
|
|
});
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2024-05-14 12:44:26 +00:00
|
|
|
extension ScreenshotImage on Image {
|
|
|
|
Image convertToStoreScreenshot({
|
|
|
|
required ScreenshotArgs args,
|
|
|
|
required ScreenshotLayout layout,
|
|
|
|
}) {
|
2024-05-15 19:47:03 +00:00
|
|
|
if (_configs[args.nameWithTheme] == null) {
|
2024-05-14 12:44:26 +00:00
|
|
|
return this;
|
|
|
|
}
|
|
|
|
return _addSystemOverlay(
|
|
|
|
screenshotDevices[args.deviceName]!,
|
|
|
|
isDark: args.isDark,
|
|
|
|
)
|
|
|
|
._addDeviceFrame(
|
|
|
|
screenshotDevices[args.deviceName]!,
|
2024-05-15 19:47:03 +00:00
|
|
|
args.backgroundColor,
|
2024-05-14 12:44:26 +00:00
|
|
|
)
|
2024-05-14 14:15:19 +00:00
|
|
|
._applyLayout(
|
2024-05-14 12:44:26 +00:00
|
|
|
layout,
|
2024-05-15 19:47:03 +00:00
|
|
|
_configs[args.nameWithTheme]!.title,
|
|
|
|
_configs[args.nameWithTheme]!.subtitle,
|
2024-05-14 12:52:58 +00:00
|
|
|
isDark: args.isDark,
|
2024-05-14 12:44:26 +00:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
Image _addSystemOverlay(ScreenshotDevice device, {required bool isDark}) {
|
|
|
|
final path = isDark ? device.systemOverlayPathDark : device.systemOverlayPathLight;
|
|
|
|
final statusBar = copyResize(
|
|
|
|
decodePng(File(path).readAsBytesSync())!,
|
|
|
|
width: width,
|
|
|
|
);
|
|
|
|
return compositeImage(this, statusBar);
|
|
|
|
}
|
|
|
|
|
2024-05-15 19:47:03 +00:00
|
|
|
Image _addDeviceFrame(ScreenshotDevice device, String color) {
|
|
|
|
final backgroundColor = ColorRgba8(
|
|
|
|
int.parse(color.substring(2, 4), radix: 16),
|
|
|
|
int.parse(color.substring(4, 6), radix: 16),
|
|
|
|
int.parse(color.substring(6, 8), radix: 16),
|
|
|
|
int.parse(color.substring(0, 2), radix: 16),
|
|
|
|
);
|
2024-05-14 12:44:26 +00:00
|
|
|
final screenshotRounded = copyCrop(
|
|
|
|
this,
|
|
|
|
x: 0,
|
|
|
|
y: 0,
|
|
|
|
width: width,
|
|
|
|
height: height,
|
|
|
|
);
|
|
|
|
|
|
|
|
final frame = decodePng(File(device.deviceFramePath).readAsBytesSync())!;
|
|
|
|
final expandedScreenshot = copyExpandCanvas(
|
|
|
|
copyExpandCanvas(
|
|
|
|
screenshotRounded,
|
|
|
|
newWidth: screenshotRounded.width + device.screenshotFrameOffset.dx,
|
|
|
|
newHeight: screenshotRounded.height + device.screenshotFrameOffset.dy,
|
|
|
|
position: ExpandCanvasPosition.bottomRight,
|
|
|
|
backgroundColor: backgroundColor,
|
|
|
|
),
|
|
|
|
newWidth: frame.width,
|
|
|
|
newHeight: frame.height,
|
|
|
|
position: ExpandCanvasPosition.topLeft,
|
|
|
|
backgroundColor: backgroundColor,
|
|
|
|
);
|
|
|
|
|
|
|
|
return compositeImage(expandedScreenshot, frame);
|
|
|
|
}
|
|
|
|
|
2024-05-14 14:15:19 +00:00
|
|
|
Image _applyLayout(ScreenshotLayout layout, String title, String subtitle, {required bool isDark}) {
|
|
|
|
final textImage = _drawTitles(layout, title, subtitle, isDark: isDark);
|
|
|
|
final maxFrameHeight =
|
|
|
|
layout.size.height - (layout.contentPadding.top + textImage.height + 84 + layout.contentPadding.bottom);
|
|
|
|
int maxFrameWidth = layout.size.width - (layout.contentPadding.left + layout.contentPadding.right);
|
|
|
|
if (maxFrameWidth * height / width > maxFrameHeight) {
|
|
|
|
maxFrameWidth = maxFrameHeight * width ~/ height;
|
|
|
|
}
|
|
|
|
final scaledScreenshot = copyResize(this, width: maxFrameWidth);
|
2024-05-14 12:44:26 +00:00
|
|
|
|
2024-05-14 14:15:19 +00:00
|
|
|
final draft = copyExpandCanvas(
|
2024-05-14 12:44:26 +00:00
|
|
|
copyExpandCanvas(
|
|
|
|
scaledScreenshot,
|
2024-05-14 14:15:19 +00:00
|
|
|
newWidth: scaledScreenshot.width + (layout.size.width - scaledScreenshot.width) ~/ 2,
|
2024-05-14 12:44:26 +00:00
|
|
|
newHeight: scaledScreenshot.height + layout.contentPadding.bottom,
|
|
|
|
position: ExpandCanvasPosition.topLeft,
|
|
|
|
backgroundColor: getPixel(0, 0),
|
|
|
|
),
|
|
|
|
newWidth: layout.size.width,
|
|
|
|
newHeight: layout.size.height,
|
|
|
|
position: ExpandCanvasPosition.bottomRight,
|
|
|
|
backgroundColor: getPixel(0, 0),
|
|
|
|
);
|
2024-05-14 14:15:19 +00:00
|
|
|
|
|
|
|
return compositeImage(
|
|
|
|
draft,
|
|
|
|
textImage,
|
|
|
|
dstX: layout.contentPadding.left,
|
|
|
|
dstY: layout.contentPadding.top,
|
|
|
|
);
|
2024-05-14 12:44:26 +00:00
|
|
|
}
|
|
|
|
|
2024-05-14 14:15:19 +00:00
|
|
|
Image _drawTitles(ScreenshotLayout layout, String title, String subtitle, {required bool isDark}) {
|
2024-05-14 12:52:58 +00:00
|
|
|
final titleFont =
|
|
|
|
BitmapFont.fromZip(File(isDark ? layout.titleFontDarkPath : layout.titleFontPath).readAsBytesSync());
|
|
|
|
final subtitleFont =
|
|
|
|
BitmapFont.fromZip(File(isDark ? layout.subtitleFontDarkPath : layout.subtitleFontPath).readAsBytesSync());
|
2024-05-14 12:44:26 +00:00
|
|
|
final textImage = fill(
|
|
|
|
Image(
|
|
|
|
height: titleFont.lineHeight + 36 + subtitleFont.lineHeight * 2,
|
|
|
|
width: layout.size.width - (layout.contentPadding.left + layout.contentPadding.right),
|
|
|
|
),
|
|
|
|
color: getPixel(0, 0),
|
|
|
|
);
|
|
|
|
|
|
|
|
drawString(
|
|
|
|
textImage,
|
|
|
|
title,
|
|
|
|
font: titleFont,
|
|
|
|
y: 0,
|
|
|
|
);
|
|
|
|
|
|
|
|
int subtitleDy = titleFont.lineHeight + 36;
|
|
|
|
subtitle.split('\n').forEach((line) {
|
|
|
|
drawString(
|
|
|
|
textImage,
|
|
|
|
line,
|
|
|
|
font: subtitleFont,
|
|
|
|
y: subtitleDy,
|
|
|
|
);
|
|
|
|
subtitleDy += subtitleFont.lineHeight;
|
|
|
|
});
|
|
|
|
|
2024-05-14 14:15:19 +00:00
|
|
|
return textImage;
|
2024-05-14 12:44:26 +00:00
|
|
|
}
|
|
|
|
}
|