diff --git a/deploy/bin/64bit/obs64.exe b/deploy/bin/64bit/obs64.exe index 74bcc21..eac8c80 100644 Binary files a/deploy/bin/64bit/obs64.exe and b/deploy/bin/64bit/obs64.exe differ diff --git a/docs/icon_preview.png b/docs/icon_preview.png index def164b..be31c99 100644 Binary files a/docs/icon_preview.png and b/docs/icon_preview.png differ diff --git a/obs-studio/cmake/bundle/windows/obs-studio.ico b/obs-studio/cmake/bundle/windows/obs-studio.ico index 7127372..4499d08 100644 Binary files a/obs-studio/cmake/bundle/windows/obs-studio.ico and b/obs-studio/cmake/bundle/windows/obs-studio.ico differ diff --git a/obs-studio/frontend/cmake/windows/obs-studio.ico b/obs-studio/frontend/cmake/windows/obs-studio.ico index bbb75dc..4499d08 100644 Binary files a/obs-studio/frontend/cmake/windows/obs-studio.ico and b/obs-studio/frontend/cmake/windows/obs-studio.ico differ diff --git a/obs-studio/frontend/forms/OBSAbout.ui b/obs-studio/frontend/forms/OBSAbout.ui index 9cbb4c0..1c7861b 100644 --- a/obs-studio/frontend/forms/OBSAbout.ui +++ b/obs-studio/frontend/forms/OBSAbout.ui @@ -7,7 +7,7 @@ 0 0 520 - 300 + 340 @@ -73,6 +73,22 @@ + + + + Based on <a href="https://obsproject.com">OBS Studio</a> (GPLv2)<br/>Secondary development by CAIIC + + + true + + + Qt::TextBrowserInteraction + + + true + + + diff --git a/obs-studio/frontend/forms/images/obs.png b/obs-studio/frontend/forms/images/obs.png index def164b..be31c99 100644 Binary files a/obs-studio/frontend/forms/images/obs.png and b/obs-studio/frontend/forms/images/obs.png differ diff --git a/tools/dome-icon-gen.js b/tools/dome-icon-gen.js new file mode 100644 index 0000000..09da2a8 --- /dev/null +++ b/tools/dome-icon-gen.js @@ -0,0 +1,206 @@ +// DOME-style icon generator for the CAIIC OBS fork. +// Renders a blue observatory/planetarium dome with a white three-blade +// aperture (nod to the OBS logo) wrapped by three CAIIC-colored arcs +// (orange top, green lower-left, yellow lower-right). +// Usage: node dome-icon-gen.js + +const fs = require('fs'); +const path = require('path'); +const zlib = require('zlib'); + +const DEG = Math.PI / 180; + +// ---------- CRC32 / PNG ---------- +const crcTable = (() => { + const t = new Uint32Array(256); + for (let n = 0; n < 256; n++) { + let c = n; + for (let k = 0; k < 8; k++) c = c & 1 ? 0xedb88320 ^ (c >>> 1) : c >>> 1; + t[n] = c >>> 0; + } + return t; +})(); +function crc32(buf) { + let c = 0xffffffff; + for (let i = 0; i < buf.length; i++) c = crcTable[(c ^ buf[i]) & 0xff] ^ (c >>> 8); + return (c ^ 0xffffffff) >>> 0; +} +function chunk(type, data) { + const out = Buffer.alloc(8 + data.length + 4); + out.writeUInt32BE(data.length, 0); + out.write(type, 4, 'ascii'); + data.copy(out, 8); + out.writeUInt32BE(crc32(out.subarray(4, 8 + data.length)), 8 + data.length); + return out; +} +function pngEncode(width, height, rgba) { + const stride = width * 4; + const raw = Buffer.alloc((stride + 1) * height); + for (let y = 0; y < height; y++) { + raw[y * (stride + 1)] = 0; + rgba.copy(raw, y * (stride + 1) + 1, y * stride, (y + 1) * stride); + } + const ihdr = Buffer.alloc(13); + ihdr.writeUInt32BE(width, 0); + ihdr.writeUInt32BE(height, 4); + ihdr[8] = 8; // bit depth + ihdr[9] = 6; // RGBA + return Buffer.concat([ + Buffer.from([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]), + chunk('IHDR', ihdr), + chunk('IDAT', zlib.deflateSync(raw, { level: 9 })), + chunk('IEND', Buffer.alloc(0)), + ]); +} +function icoEncode(images) { + // images: [{size, png}] + const header = Buffer.alloc(6); + header.writeUInt16LE(1, 2); + header.writeUInt16LE(images.length, 4); + let offset = 6 + images.length * 16; + const entries = []; + for (const img of images) { + const e = Buffer.alloc(16); + e[0] = img.size >= 256 ? 0 : img.size; + e[1] = img.size >= 256 ? 0 : img.size; + e[4] = 1; // planes + e.writeUInt16LE(32, 6); + e.writeUInt32LE(img.png.length, 8); + e.writeUInt32LE(offset, 12); + offset += img.png.length; + entries.push(e); + } + return Buffer.concat([header, ...entries, ...images.map((i) => i.png)]); +} + +// ---------- geometry ---------- +function clamp(x, a, b) { return Math.min(Math.max(x, a), b); } +function mix(a, b, t) { return a + (b - a) * t; } +function mix3(c1, c2, t) { return [mix(c1[0], c2[0], t), mix(c1[1], c2[1], t), mix(c1[2], c2[2], t)]; } + +// signed distance to a circular arc stroke (rounded caps), angles in radians +function arcSDF(px, py, cx, cy, r, a0, a1, w) { + const dx = px - cx, dy = py - cy; + const d = Math.hypot(dx, dy); + let ang = Math.atan2(dy, dx); + // normalize ang into [a0, a0 + 2PI) + const TWO_PI = Math.PI * 2; + while (ang < a0) ang += TWO_PI; + while (ang >= a0 + TWO_PI) ang -= TWO_PI; + let dist; + if (ang <= a1) { + dist = Math.abs(d - r); + } else { + const e0x = cx + r * Math.cos(a0), e0y = cy + r * Math.sin(a0); + const e1x = cx + r * Math.cos(a1), e1y = cy + r * Math.sin(a1); + dist = Math.min(Math.hypot(px - e0x, py - e0y), Math.hypot(px - e1x, py - e1y)); + } + return dist - w / 2; +} +// rounded rect sdf +function rrSDF(px, py, cx, cy, hw, hh, r) { + const qx = Math.abs(px - cx) - (hw - r); + const qy = Math.abs(py - cy) - (hh - r); + const ax = Math.max(qx, 0), ay = Math.max(qy, 0); + return Math.hypot(ax, ay) + Math.min(Math.max(qx, qy), 0) - r; +} + +// ---------- design ---------- +const DOME_C = [0.5, 0.56]; // dome circle center +const DOME_R = 0.295; // dome radius +const BLADE_C = [0.5, 0.475]; // aperture center +const BLADE_R = 0.155; +const BLADE_W = 0.048; +const ARC_C = [0.5, 0.53]; +const ARC_R = 0.4; +const ARC_W = 0.036; + +const NAVY = [10, 52, 128]; +const BLUE_HI = [110, 196, 250]; +const BLUE_LO = [9, 68, 152]; +const WHITE = [248, 252, 255]; +const ORANGE = [240, 128, 60]; +const GREEN = [110, 190, 74]; +const YELLOW = [240, 204, 60]; + +function domeColor(px, py) { + const t = Math.pow(clamp(Math.hypot(px - 0.34, py - 0.36) / 0.5, 0, 1), 0.8); + let c = mix3(BLUE_HI, BLUE_LO, t); + // darken slightly toward the base for depth + const base = clamp((py - 0.42) / 0.16, 0, 1) * 0.18; + return [c[0] * (1 - base), c[1] * (1 - base), c[2] * (1 - base)]; +} + +function bladeSDF(px, py) { + let best = 1e9; + const span = 100 * DEG; + for (let i = 0; i < 3; i++) { + const a0 = (-90 + i * 120) * DEG; + best = Math.min(best, arcSDF(px, py, BLADE_C[0], BLADE_C[1], BLADE_R, a0, a0 + span, BLADE_W)); + } + return best; +} +function arcsSDF(px, py) { + return [ + { sdf: arcSDF(px, py, ARC_C[0], ARC_C[1], ARC_R, -160 * DEG, -20 * DEG, ARC_W), color: ORANGE }, + { sdf: arcSDF(px, py, ARC_C[0], ARC_C[1], ARC_R, 105 * DEG, 170 * DEG, ARC_W), color: GREEN }, + { sdf: arcSDF(px, py, ARC_C[0], ARC_C[1], ARC_R, 10 * DEG, 75 * DEG, ARC_W), color: YELLOW }, + ]; +} + +function render(size) { + const SS = 4; + const R = size * SS; + const out = Buffer.alloc(size * size * 4); + const px = 1 / R; + + for (let y = 0; y < size; y++) { + 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 nx = (x * SS + sx + 0.5) / R; + const ny = (y * SS + sy + 0.5) / R; + const aa = (sdf) => clamp(0.5 - sdf / px, 0, 1); + + let cr = 0, cg = 0, cb = 0, ca = 0; + const over = (sdf, col, opacity) => { + const cov = aa(sdf) * opacity; + cr = cr * (1 - cov) + col[0] * cov; + cg = cg * (1 - cov) + col[1] * cov; + cb = cb * (1 - cov) + col[2] * cov; + ca = ca * (1 - cov) + cov; + }; + + // base bar + over(rrSDF(nx, ny, 0.5, DOME_C[1] + 0.022, DOME_R + 0.045, 0.028, 0.026), NAVY, 1); + // dome (upper half of circle) + const dd = Math.max(Math.hypot(nx - DOME_C[0], ny - DOME_C[1]) - DOME_R, ny - DOME_C[1]); + over(dd, domeColor(nx, ny), 1); + // aperture blades + over(bladeSDF(nx, ny), WHITE, 0.95); + // colored arcs + for (const arc of arcsSDF(nx, ny)) over(arc.sdf, arc.color, 1); + + r += cr; g += cg; b += cb; a += ca; + } + } + const n = SS * SS; + const o = (y * size + x) * 4; + out[o] = Math.round(clamp(r / n, 0, 255)); + out[o + 1] = Math.round(clamp(g / n, 0, 255)); + out[o + 2] = Math.round(clamp(b / n, 0, 255)); + out[o + 3] = Math.round(clamp(a / n, 0, 1) * 255); + } + } + return out; +} + +// ---------- main ---------- +const outDir = process.argv[2] || '.'; +const sizes = [16, 24, 32, 48, 64, 128, 256]; +const images = sizes.map((s) => ({ size: s, png: pngEncode(s, s, render(s)) })); + +fs.writeFileSync(path.join(outDir, 'obs.png'), images[images.length - 1].png); +fs.writeFileSync(path.join(outDir, 'obs-studio.ico'), icoEncode(images)); +console.log('wrote obs.png (256x256) and obs-studio.ico (' + sizes.join(',') + ') to ' + outDir);