import 'dart:io'; import 'dart:typed_data'; import 'dart:math'; /// Generates a simple tree icon PNG void main() async { const size = 1024; final pixels = Uint8List(size * size * 4); // Fill with white background for (var i = 0; i < pixels.length; i += 4) { pixels[i] = 255; // R pixels[i + 1] = 255; // G pixels[i + 2] = 255; // B pixels[i + 3] = 255; // A } // Tree colors (muted teal from our theme) const treeR = 74; // 0x4A const treeG = 144; // 0x90 const treeB = 164; // 0xA4 // Trunk color (darker) const trunkR = 90; const trunkG = 70; const trunkB = 55; // Draw tree trunk (rectangle) final trunkLeft = (size * 0.44).round(); final trunkRight = (size * 0.56).round(); final trunkTop = (size * 0.65).round(); final trunkBottom = (size * 0.85).round(); for (var y = trunkTop; y < trunkBottom; y++) { for (var x = trunkLeft; x < trunkRight; x++) { final i = (y * size + x) * 4; pixels[i] = trunkR; pixels[i + 1] = trunkG; pixels[i + 2] = trunkB; pixels[i + 3] = 255; } } // Draw tree canopy (three triangles stacked) void drawTriangle(int centerX, int topY, int height, int baseWidth) { for (var y = topY; y < topY + height; y++) { final progress = (y - topY) / height; final halfWidth = (baseWidth * progress / 2).round(); for (var x = centerX - halfWidth; x <= centerX + halfWidth; x++) { if (x >= 0 && x < size && y >= 0 && y < size) { final i = (y * size + x) * 4; pixels[i] = treeR; pixels[i + 1] = treeG; pixels[i + 2] = treeB; pixels[i + 3] = 255; } } } } final centerX = size ~/ 2; // Top triangle (smallest) drawTriangle(centerX, (size * 0.15).round(), (size * 0.20).round(), (size * 0.35).round()); // Middle triangle drawTriangle(centerX, (size * 0.28).round(), (size * 0.22).round(), (size * 0.48).round()); // Bottom triangle (largest) drawTriangle(centerX, (size * 0.42).round(), (size * 0.26).round(), (size * 0.58).round()); // Encode as PNG final png = encodePng(size, size, pixels); // Write to file final file = File('assets/icon/app_icon.png'); await file.writeAsBytes(png); print('Generated app_icon.png'); // Also create foreground version (same but smaller for adaptive icons) final foregroundFile = File('assets/icon/app_icon_foreground.png'); await foregroundFile.writeAsBytes(png); print('Generated app_icon_foreground.png'); } /// Simple PNG encoder (no compression for simplicity) Uint8List encodePng(int width, int height, Uint8List rgba) { final output = BytesBuilder(); // PNG signature output.add([0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A]); // IHDR chunk final ihdr = BytesBuilder(); ihdr.add(_int32be(width)); ihdr.add(_int32be(height)); ihdr.addByte(8); // bit depth ihdr.addByte(6); // color type (RGBA) ihdr.addByte(0); // compression ihdr.addByte(0); // filter ihdr.addByte(0); // interlace _writeChunk(output, 'IHDR', ihdr.toBytes()); // IDAT chunk (image data with zlib compression) // For simplicity, we'll use store (no compression) final rawData = BytesBuilder(); for (var y = 0; y < height; y++) { rawData.addByte(0); // filter type: None for (var x = 0; x < width; x++) { final i = (y * width + x) * 4; rawData.addByte(rgba[i]); // R rawData.addByte(rgba[i + 1]); // G rawData.addByte(rgba[i + 2]); // B rawData.addByte(rgba[i + 3]); // A } } final compressed = _deflateStore(rawData.toBytes()); _writeChunk(output, 'IDAT', compressed); // IEND chunk _writeChunk(output, 'IEND', Uint8List(0)); return output.toBytes(); } Uint8List _int32be(int value) { return Uint8List.fromList([ (value >> 24) & 0xFF, (value >> 16) & 0xFF, (value >> 8) & 0xFF, value & 0xFF, ]); } void _writeChunk(BytesBuilder output, String type, Uint8List data) { output.add(_int32be(data.length)); final typeBytes = type.codeUnits; output.add(typeBytes); output.add(data); // CRC32 of type + data final crcData = Uint8List(typeBytes.length + data.length); crcData.setAll(0, typeBytes); crcData.setAll(typeBytes.length, data); output.add(_int32be(_crc32(crcData))); } /// Simple deflate with store (no compression) Uint8List _deflateStore(Uint8List data) { final output = BytesBuilder(); // zlib header output.addByte(0x78); // CMF output.addByte(0x01); // FLG (no dict, fastest) // Split into blocks of max 65535 bytes const maxBlock = 65535; var offset = 0; while (offset < data.length) { final remaining = data.length - offset; final blockSize = remaining > maxBlock ? maxBlock : remaining; final isLast = offset + blockSize >= data.length; output.addByte(isLast ? 0x01 : 0x00); // BFINAL + BTYPE (store) output.addByte(blockSize & 0xFF); output.addByte((blockSize >> 8) & 0xFF); output.addByte((~blockSize) & 0xFF); output.addByte(((~blockSize) >> 8) & 0xFF); output.add(data.sublist(offset, offset + blockSize)); offset += blockSize; } // Adler-32 checksum var s1 = 1; var s2 = 0; for (var i = 0; i < data.length; i++) { s1 = (s1 + data[i]) % 65521; s2 = (s2 + s1) % 65521; } final adler = (s2 << 16) | s1; output.add(_int32be(adler)); return output.toBytes(); } int _crc32(Uint8List data) { var crc = 0xFFFFFFFF; for (var byte in data) { crc ^= byte; for (var i = 0; i < 8; i++) { crc = (crc & 1) != 0 ? (crc >> 1) ^ 0xEDB88320 : crc >> 1; } } return crc ^ 0xFFFFFFFF; }