Files
IKAR/tool/make_ikar_icons.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)}%',
);
}