Module: Everywhere::Icon

Defined in:
lib/everywhere/icon.rb

Overview

Turns one source PNG into platform-native app icons — no Tauri, no sips, no iconutil, no ImageMagick. Pure Ruby, so it runs the same locally and on the cloud build runners.

The important part is macОS shaping. macOS renders an .icns literally: it does not round the corners or add margins the way iOS does. A full-bleed square PNG therefore shows up as a hard square, out of place next to every native app. So we bake the platform's look into the artwork ourselves — inset the icon to the Big Sur grid (~824px inside a 1024px canvas) and clip it to a superellipse ("squircle") with transparent corners.

Windows (.ico) and Linux (hicolor PNGs) don't get the squircle — those desktops present square icons — so they're generated straight from the full-bleed source.

Constant Summary collapse

CANVAS =

Big Sur icon-grid geometry, all as fractions of the 1024px canvas so it scales to any master size.

1024
INSET =

icon body is 824/1024 of the canvas

0.8047
CORNER_RATIO =

corner radius as a fraction of the body's side

0.2237
EXPONENT =

superellipse power — the iOS/macOS "squircle" corner

5.0
ICNS_TYPES =

.icns type codes -> pixel size. Mirrors what iconutil emits from a standard .iconset (retina variants included), so the result is indistinguishable from an Apple-toolchain icns.

[
  ["icp4", 16], ["icp5", 32], ["ic07", 128], ["ic08", 256], ["ic09", 512],
  ["ic11", 32], ["ic12", 64], ["ic13", 256], ["ic14", 512], ["ic10", 1024]
].freeze
ICO_SIZES =
[16, 32, 48, 64, 128, 256].freeze
LINUX_SIZES =
[16, 32, 48, 64, 128, 256, 512].freeze

Class Method Summary collapse

Class Method Details

.macos_master(source, dest, canvas: CANVAS) ⇒ Object

Write a macOS-shaped 1024px RGBA master PNG (padding + squircle) from a source PNG. Returns true, or false if the source PNG can't be read.



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
70
# File 'lib/everywhere/icon.rb', line 44

def macos_master(source, dest, canvas: CANVAS)
  raster = Raster.decode_png(source) or return false

  body = (canvas * INSET).round
  offset = (canvas - body) / 2

  # Fit the artwork inside the body square preserving aspect ratio (square
  # sources fill it exactly; non-square ones are centred, not stretched).
  fit = [body.to_f / raster.width, body.to_f / raster.height].min
  sw = (raster.width * fit).round.clamp(1, body)
  sh = (raster.height * fit).round.clamp(1, body)
  scaled = raster.resample(sw, sh)

  out = Raster.transparent(canvas, canvas)
  out.paste!(scaled, offset + (body - sw) / 2, offset + (body - sh) / 2)

  centre = canvas / 2.0
  half = body / 2.0
  radius = [body * CORNER_RATIO, half].min
  straight = half - radius # half-extent of the straight edges
  out.mask_region!(offset, offset, offset + body, offset + body) do |x, y|
    squircle_coverage(x, y, centre, half, straight, radius, EXPONENT)
  end

  out.write_png(dest)
  true
end

.sized_png(master, size) ⇒ Object

PNG bytes of master at sizexsize (no-op resample when already sized).



121
122
123
# File 'lib/everywhere/icon.rb', line 121

def sized_png(master, size)
  (size == master.width && size == master.height ? master : master.resample(size, size)).encode_png
end

.squircle_coverage(x, y, centre, half, straight, radius, exponent) ⇒ Object

Coverage (0.0..1.0) of pixel (x, y) by the squircle: a rounded rectangle with continuous (superelliptical) corners. Interior pixels return 1.0, exterior 0.0, and boundary pixels are antialiased by 4x4 supersampling.



128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
# File 'lib/everywhere/icon.rb', line 128

def squircle_coverage(x, y, centre, half, straight, radius, exponent)
  c0 = squircle_inside?(x,     y,     centre, half, straight, radius, exponent)
  c1 = squircle_inside?(x + 1, y,     centre, half, straight, radius, exponent)
  c2 = squircle_inside?(x,     y + 1, centre, half, straight, radius, exponent)
  c3 = squircle_inside?(x + 1, y + 1, centre, half, straight, radius, exponent)
  return 1.0 if c0 && c1 && c2 && c3
  return 0.0 unless c0 || c1 || c2 || c3

  hit = 0
  4.times do |i|
    4.times do |j|
      hit += 1 if squircle_inside?(x + (i + 0.5) / 4.0, y + (j + 0.5) / 4.0,
                                   centre, half, straight, radius, exponent)
    end
  end
  hit / 16.0
end

.squircle_inside?(sx, sy, centre, half, straight, radius, exponent) ⇒ Boolean

Is the continuous point (sx, sy) inside the squircle? Straight edges up to straight from centre, superelliptical corners of radius beyond that.

Returns:

  • (Boolean)


148
149
150
151
152
153
154
155
156
157
# File 'lib/everywhere/icon.rb', line 148

def squircle_inside?(sx, sy, centre, half, straight, radius, exponent)
  u = (sx - centre).abs
  v = (sy - centre).abs
  return false if u > half || v > half
  return true if u <= straight || v <= straight

  du = (u - straight) / radius
  dv = (v - straight) / radius
  (du**exponent) + (dv**exponent) <= 1.0
end

.write_icns(master_png, dest) ⇒ Object

Build a macOS .icns from an already-shaped master PNG (ideally 1024px). Returns true on success.



74
75
76
77
78
79
80
81
82
83
84
# File 'lib/everywhere/icon.rb', line 74

def write_icns(master_png, dest)
  master = Raster.decode_png(master_png) or return false

  body = ICNS_TYPES.map do |type, size|
    png = sized_png(master, size)
    type.b + [png.bytesize + 8].pack("N") + png
  end.join

  File.binwrite(dest, "icns".b + [body.bytesize + 8].pack("N") + body)
  true
end

.write_ico(source, dest, sizes: ICO_SIZES) ⇒ Object

Build a Windows .ico (PNG-compressed entries, Vista+) from a full-bleed source PNG. Returns true on success.



88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
# File 'lib/everywhere/icon.rb', line 88

def write_ico(source, dest, sizes: ICO_SIZES)
  master = Raster.decode_png(source) or return false

  images = sizes.map { |s| [s, sized_png(master, s)] }
  offset = 6 + images.size * 16
  entries = +"".b
  blob = +"".b
  images.each do |size, png|
    dim = size >= 256 ? 0 : size # 0 means 256 in the ICONDIRENTRY
    entries << [dim, dim, 0, 0, 1, 32, png.bytesize, offset].pack("CCCCvvVV")
    blob << png
    offset += png.bytesize
  end

  header = [0, 1, images.size].pack("vvv") # reserved, type=icon, count
  File.binwrite(dest, header + entries + blob)
  true
end

.write_png_set(source, dest_dir, sizes: LINUX_SIZES) ⇒ Object

Write a set of square PNGs (Linux/desktop use) named "x.png" into dest_dir from a full-bleed source. Returns the written paths.



109
110
111
112
113
114
115
116
117
118
# File 'lib/everywhere/icon.rb', line 109

def write_png_set(source, dest_dir, sizes: LINUX_SIZES)
  master = Raster.decode_png(source) or return []

  FileUtils.mkdir_p(dest_dir)
  sizes.map do |size|
    path = File.join(dest_dir, "#{size}x#{size}.png")
    File.binwrite(path, sized_png(master, size))
    path
  end
end