generated from dellevin/template
加入新图标,优化通知栏,统计界面加入标签词云
This commit is contained in:
206
tool/generate_icon.dart
Normal file
206
tool/generate_icon.dart
Normal file
@@ -0,0 +1,206 @@
|
||||
import 'dart:io';
|
||||
import 'dart:typed_data';
|
||||
import 'dart:math' as math;
|
||||
|
||||
void main() {
|
||||
final outputDir = Directory('assets/icon');
|
||||
if (!outputDir.existsSync()) outputDir.createSync(recursive: true);
|
||||
|
||||
final bytes1024 = generateIcon(1024);
|
||||
File('${outputDir.path}/app_icon_m.png').writeAsBytesSync(bytes1024);
|
||||
print('Generated app_icon_m.png (1024×1024)');
|
||||
|
||||
final androidMipmapDir = 'android/app/src/main/res';
|
||||
final densities = {
|
||||
'mipmap-mdpi': 48, 'mipmap-hdpi': 72, 'mipmap-xhdpi': 96,
|
||||
'mipmap-xxhdpi': 144, 'mipmap-xxxhdpi': 192,
|
||||
};
|
||||
for (final entry in densities.entries) {
|
||||
final dir = Directory('$androidMipmapDir/${entry.key}');
|
||||
if (!dir.existsSync()) dir.createSync(recursive: true);
|
||||
File('${dir.path}/ic_launcher_m.png').writeAsBytesSync(generateIcon(entry.value));
|
||||
print('Generated ${entry.key}/ic_launcher_m.png');
|
||||
}
|
||||
print('Done!');
|
||||
}
|
||||
|
||||
Uint8List generateIcon(int size) {
|
||||
final bm = _Bitmap(size, size);
|
||||
|
||||
// 透明背景
|
||||
for (int y = 0; y < size; y++) {
|
||||
for (int x = 0; x < size; x++) {
|
||||
bm.setPixel(x, y, 0, 0, 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
// 圆角黑底
|
||||
final cr = (size * 0.22).round();
|
||||
_fillRoundedRect(bm, 0, 0, size, size, cr, 0, 0, 0, 255);
|
||||
|
||||
// 绘制标准 M
|
||||
_drawStandardM(bm);
|
||||
|
||||
return _encodePng(bm);
|
||||
}
|
||||
|
||||
/// 用扫描线 + 区间运算绘制标准 M
|
||||
void _drawStandardM(_Bitmap bm) {
|
||||
const int r = 255, g = 255, b = 255, a = 255;
|
||||
final size = bm.width;
|
||||
|
||||
final letterW = size * 0.36;
|
||||
final letterH = size * 0.38;
|
||||
final stroke = letterW * 0.24;
|
||||
|
||||
final cx = size / 2.0;
|
||||
final cy = size / 2.0;
|
||||
final hw = letterW / 2.0;
|
||||
final hh = letterH / 2.0;
|
||||
final hs = stroke / 2.0;
|
||||
|
||||
final top = cy - hh;
|
||||
final bot = cy + hh;
|
||||
final leftOuter = cx - hw - hs;
|
||||
final leftInner = cx - hw + hs;
|
||||
final rightInner = cx + hw - hs;
|
||||
final rightOuter = cx + hw + hs;
|
||||
final botC = bot; // 对角线与竖笔底边齐平
|
||||
|
||||
// V 形切口的底端 y
|
||||
final vBotY = cy - hh * 0.15;
|
||||
// V 形在 vBotY 处的半宽
|
||||
final vBotHW = hw * 0.30;
|
||||
|
||||
final minY = top.floor().clamp(0, size - 1);
|
||||
final maxY = botC.ceil().clamp(0, size - 1);
|
||||
|
||||
for (int y = minY; y <= maxY; y++) {
|
||||
// 该行的像素掩码
|
||||
final row = List.filled(size, false);
|
||||
|
||||
// ── 填充各部件 ──
|
||||
|
||||
// 左竖笔
|
||||
if (y >= top && y <= bot) {
|
||||
_setRange(row, leftOuter, leftInner);
|
||||
}
|
||||
// 右竖笔
|
||||
if (y >= top && y <= bot) {
|
||||
_setRange(row, rightInner, rightOuter);
|
||||
}
|
||||
// 左对角线(左边缘从 bar 外缘开始,保持连续无断裂)
|
||||
if (y >= top && y <= botC) {
|
||||
final t = (y - top) / (botC - top);
|
||||
final l = leftOuter + (cx - hs - leftOuter) * t;
|
||||
final r = leftInner + (cx + hs - leftInner) * t;
|
||||
_setRange(row, l, r);
|
||||
}
|
||||
// 右对角线(右边缘从 bar 外缘开始,保持连续无断裂)
|
||||
if (y >= top && y <= botC) {
|
||||
final t = (y - top) / (botC - top);
|
||||
final l = rightInner + (cx - hs - rightInner) * t;
|
||||
final r = rightOuter + (cx + hs - rightOuter) * t;
|
||||
_setRange(row, l, r);
|
||||
}
|
||||
// V 形切口(清除中心区域)
|
||||
if (y >= top && y <= vBotY) {
|
||||
final vt = (y - top) / (vBotY - top);
|
||||
final vw = vBotHW * vt; // V 宽度随 y 增大而增大
|
||||
_clearRange(row, cx - vw, cx + vw);
|
||||
}
|
||||
|
||||
// ── 输出 ──
|
||||
_fillRowFromMask(bm, row, y, r, g, b, a);
|
||||
}
|
||||
}
|
||||
|
||||
void _setRange(List<bool> row, double from, double to) {
|
||||
final a = from.round().clamp(0, row.length - 1);
|
||||
final b = to.round().clamp(0, row.length - 1);
|
||||
final start = math.min(a, b);
|
||||
final end = math.max(a, b);
|
||||
for (int i = start; i <= end; i++) {
|
||||
row[i] = true;
|
||||
}
|
||||
}
|
||||
|
||||
void _clearRange(List<bool> row, double from, double to) {
|
||||
final a = from.round().clamp(0, row.length - 1);
|
||||
final b = to.round().clamp(0, row.length - 1);
|
||||
final start = math.min(a, b);
|
||||
final end = math.max(a, b);
|
||||
for (int i = start; i <= end; i++) {
|
||||
row[i] = false;
|
||||
}
|
||||
}
|
||||
|
||||
void _fillRowFromMask(_Bitmap bm, List<bool> row, int y, int r, int g, int b, int a) {
|
||||
for (int x = 0; x < row.length; x++) {
|
||||
if (row[x]) bm.setPixel(x, y, r, g, b, a);
|
||||
}
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════
|
||||
|
||||
void _fillRoundedRect(_Bitmap bm, int x, int y, int w, int h, int rr, int r, int g, int b, int a) {
|
||||
final radius = rr.clamp(0, math.min(w, h) ~/ 2);
|
||||
for (int dy = 0; dy < h; dy++) {
|
||||
for (int dx = 0; dx < w; dx++) {
|
||||
bool inside = true;
|
||||
if (dx < radius && dy < radius) {
|
||||
inside = math.sqrt((radius - dx) * (radius - dx) + (radius - dy) * (radius - dy)) <= radius;
|
||||
} else if (dx >= w - radius && dy < radius) {
|
||||
inside = math.sqrt((dx - (w - radius - 1)) * (dx - (w - radius - 1)) + (radius - dy) * (radius - dy)) <= radius;
|
||||
} else if (dx < radius && dy >= h - radius) {
|
||||
inside = math.sqrt((radius - dx) * (radius - dx) + (dy - (h - radius - 1)) * (dy - (h - radius - 1))) <= radius;
|
||||
} else if (dx >= w - radius && dy >= h - radius) {
|
||||
inside = math.sqrt((dx - (w - radius - 1)) * (dx - (w - radius - 1)) + (dy - (h - radius - 1)) * (dy - (h - radius - 1))) <= radius;
|
||||
}
|
||||
if (inside) bm.setPixel(x + dx, y + dy, r, g, b, a);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ── Bitmap ────────────────────────────────────────────────────────
|
||||
|
||||
class _Bitmap {
|
||||
final int width, height;
|
||||
final Uint8List data;
|
||||
_Bitmap(this.width, this.height) : data = Uint8List(width * height * 4);
|
||||
void setPixel(int x, int y, int r, int g, int b, int a) {
|
||||
if (x < 0 || x >= width || y < 0 || y >= height) return;
|
||||
final o = (y * width + x) * 4;
|
||||
data[o] = r; data[o + 1] = g; data[o + 2] = b; data[o + 3] = a;
|
||||
}
|
||||
}
|
||||
|
||||
// ── PNG Encoder ───────────────────────────────────────────────────
|
||||
|
||||
Uint8List _encodePng(_Bitmap bm) {
|
||||
final w = bm.width, h = bm.height;
|
||||
final raw = <int>[];
|
||||
for (int y = 0; y < h; y++) {
|
||||
raw.add(0);
|
||||
raw.addAll(bm.data.sublist(y * w * 4, (y + 1) * w * 4));
|
||||
}
|
||||
final deflated = ZLibEncoder().convert(raw);
|
||||
final buf = BytesBuilder();
|
||||
buf.add(_chunk('IHDR', [..._be32(w), ..._be32(h), 8, 6, 0, 0, 0]));
|
||||
buf.add(_chunk('IDAT', deflated));
|
||||
buf.add(_chunk('IEND', []));
|
||||
return Uint8List.fromList([137, 80, 78, 71, 13, 10, 26, 10, ...buf.toBytes()]);
|
||||
}
|
||||
|
||||
List<int> _chunk(String type, List<int> data) {
|
||||
final body = [...type.codeUnits, ...data];
|
||||
return [..._be32(data.length), ...body, ..._be32(_crc32(body))];
|
||||
}
|
||||
|
||||
List<int> _be32(int v) => [(v >> 24) & 0xFF, (v >> 16) & 0xFF, (v >> 8) & 0xFF, v & 0xFF];
|
||||
|
||||
int _crc32(List<int> data) {
|
||||
int crc = 0xFFFFFFFF;
|
||||
for (final b in data) { crc ^= b; for (int i = 0; i < 8; i++) crc = (crc & 1) != 0 ? (crc >> 1) ^ 0xEDB88320 : (crc >> 1); }
|
||||
return crc ^ 0xFFFFFFFF;
|
||||
}
|
||||
Reference in New Issue
Block a user