File size: 2,886 Bytes
b56c709
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
// Draws the app icons (a waveform on terracotta) as PNGs with no dependencies.
import { deflateSync } from 'node:zlib';
import { writeFileSync, mkdirSync } from 'node:fs';
import { join } from 'node:path';

const crcTable = Array.from({ length: 256 }, (_, n) => {
  let c = n;
  for (let k = 0; k < 8; k++) c = c & 1 ? 0xedb88320 ^ (c >>> 1) : c >>> 1;
  return c >>> 0;
});
const crc = (buf) => { let c = ~0; for (const b of buf) c = crcTable[(c ^ b) & 255] ^ (c >>> 8); return ~c >>> 0; };
const chunk = (type, data) => {
  const len = Buffer.alloc(4); len.writeUInt32BE(data.length);
  const td = Buffer.concat([Buffer.from(type), data]);
  const c = Buffer.alloc(4); c.writeUInt32BE(crc(td));
  return Buffer.concat([len, td, c]);
};

function draw(size, maskable) {
  const ss = 3, S = size * ss; // supersample for smooth edges
  const BG = [180, 83, 42], FG = [244, 239, 230];
  const heights = [0.18, 0.36, 0.56, 0.36, 0.24, 0.46, 0.2];
  const scale = maskable ? 0.62 : 0.8;
  const n = heights.length, w = (S * scale) / (n * 1.7);
  const total = n * w + (n - 1) * w * 0.7;
  const x0 = (S - total) / 2, R = S * 0.22;
  const inRound = (x, y, x1, y1, x2, y2, r) => {
    if (x < x1 || x > x2 || y < y1 || y > y2) return false;
    const cx = Math.min(Math.max(x, x1 + r), x2 - r), cy = Math.min(Math.max(y, y1 + r), y2 - r);
    return (x - cx) ** 2 + (y - cy) ** 2 <= r * r;
  };
  const px = (x, y) => {
    if (!maskable && !inRound(x, y, 0, 0, S, S, R)) return null;
    for (let i = 0; i < n; i++) {
      const bx = x0 + i * w * 1.7, hh = S * heights[i] * scale;
      if (inRound(x, y, bx, S / 2 - hh / 2, bx + w, S / 2 + hh / 2, w / 2)) return FG;
    }
    return BG;
  };
  const raw = Buffer.alloc(size * (size * 4 + 1));
  for (let y = 0; y < size; y++) {
    raw[y * (size * 4 + 1)] = 0;
    for (let x = 0; x < size; x++) {
      let r = 0, g = 0, b = 0, a = 0;
      for (let sy = 0; sy < ss; sy++) for (let sx = 0; sx < ss; sx++) {
        const c = px(x * ss + sx + 0.5, y * ss + sy + 0.5);
        if (c) { r += c[0]; g += c[1]; b += c[2]; a++; }
      }
      const o = y * (size * 4 + 1) + 1 + x * 4;
      if (a) { raw[o] = r / a; raw[o + 1] = g / a; raw[o + 2] = b / a; }
      raw[o + 3] = Math.round((a / (ss * ss)) * 255);
    }
  }
  const ihdr = Buffer.alloc(13);
  ihdr.writeUInt32BE(size, 0); ihdr.writeUInt32BE(size, 4);
  ihdr[8] = 8; ihdr[9] = 6; ihdr[10] = 0; ihdr[11] = 0; ihdr[12] = 0;
  return Buffer.concat([
    Buffer.from([137, 80, 78, 71, 13, 10, 26, 10]),
    chunk('IHDR', ihdr), chunk('IDAT', deflateSync(raw, { level: 9 })), chunk('IEND', Buffer.alloc(0)),
  ]);
}

export function writeIcons(dir) {
  mkdirSync(dir, { recursive: true });
  writeFileSync(join(dir, 'icon-192.png'), draw(192, false));
  writeFileSync(join(dir, 'icon-512.png'), draw(512, false));
  writeFileSync(join(dir, 'maskable-512.png'), draw(512, true));
}