85 lines
2.7 KiB
Dart
85 lines
2.7 KiB
Dart
import 'dart:io';
|
|
import 'dart:math' as math;
|
|
import 'package:image/image.dart' as img;
|
|
|
|
const _size = 1024;
|
|
const _coverage = 0.90;
|
|
|
|
void main() {
|
|
final source = img.decodePng(File('assets/ikar_mark.png').readAsBytesSync())!;
|
|
var left = source.width;
|
|
var top = source.height;
|
|
var right = 0;
|
|
var bottom = 0;
|
|
for (final pixel in source) {
|
|
if (pixel.a < 128) continue;
|
|
left = math.min(left, pixel.x);
|
|
top = math.min(top, pixel.y);
|
|
right = math.max(right, pixel.x);
|
|
bottom = math.max(bottom, pixel.y);
|
|
}
|
|
final artwork = img.copyCrop(
|
|
source,
|
|
x: left,
|
|
y: top,
|
|
width: right - left + 1,
|
|
height: bottom - top + 1,
|
|
);
|
|
var radius = 0.0;
|
|
for (final pixel in artwork) {
|
|
if (pixel.a < 128) continue;
|
|
final dx = pixel.x + 0.5 - artwork.width / 2;
|
|
final dy = pixel.y + 0.5 - artwork.height / 2;
|
|
radius = math.max(radius, math.sqrt(dx * dx + dy * dy));
|
|
}
|
|
|
|
img.Image render(double viewportFraction, {required bool opaque}) {
|
|
final targetRadius = _size * viewportFraction * _coverage / 2 - 1;
|
|
final scale = targetRadius / radius;
|
|
final scaled = img.copyResize(
|
|
artwork,
|
|
width: (artwork.width * scale).round(),
|
|
height: (artwork.height * scale).round(),
|
|
interpolation: img.Interpolation.cubic,
|
|
);
|
|
final canvas = img.Image(width: _size, height: _size, numChannels: 4);
|
|
if (opaque) img.fill(canvas, color: img.ColorRgba8(255, 255, 255, 255));
|
|
img.compositeImage(
|
|
canvas,
|
|
scaled,
|
|
dstX: (_size - scaled.width) ~/ 2,
|
|
dstY: (_size - scaled.height) ~/ 2,
|
|
);
|
|
return canvas;
|
|
}
|
|
|
|
final icon = render(1, opaque: true);
|
|
final foreground = render(72 / 108, opaque: false);
|
|
File('assets/ikar_icon.png').writeAsBytesSync(img.encodePng(icon));
|
|
File('assets/ikar.png').writeAsBytesSync(img.encodePng(foreground));
|
|
Directory('artifacts').createSync(recursive: true);
|
|
final preview = img.copyResize(icon, width: 512, height: 512);
|
|
for (final pixel in preview) {
|
|
final dx = pixel.x + 0.5 - 256;
|
|
final dy = pixel.y + 0.5 - 256;
|
|
if (dx * dx + dy * dy > 256 * 256) pixel.a = 0;
|
|
}
|
|
File(
|
|
'artifacts/ikar-icon-circle.png',
|
|
).writeAsBytesSync(img.encodePng(preview));
|
|
var actualRadius = 0.0;
|
|
for (final pixel in foreground) {
|
|
if (pixel.a < 128) continue;
|
|
final dx = pixel.x + 0.5 - _size / 2;
|
|
final dy = pixel.y + 0.5 - _size / 2;
|
|
actualRadius = math.max(actualRadius, math.sqrt(dx * dx + dy * dy));
|
|
}
|
|
final actualCoverage = actualRadius / (_size * (72 / 108) / 2);
|
|
if ((actualCoverage - _coverage).abs() > 0.01) {
|
|
throw StateError('Unexpected circular coverage: $actualCoverage');
|
|
}
|
|
stdout.writeln(
|
|
'Circular artwork coverage: ${(actualCoverage * 100).toStringAsFixed(1)}%',
|
|
);
|
|
}
|