Files
HDR/scripts/generate-hdr-wordmark.mjs

139 lines
4.4 KiB
JavaScript

import { mkdir, mkdtemp, rm, writeFile } from 'node:fs/promises';
import { spawn } from 'node:child_process';
import { once } from 'node:events';
import { tmpdir } from 'node:os';
import { dirname, join, resolve } from 'node:path';
const root = resolve(import.meta.dirname, '..');
const outputPath = resolve(root, 'media/hdr-wordmark.png');
const width = 2200;
const height = 500;
function run(command, args, options = {}) {
const child = spawn(command, args, options);
return once(child, 'close').then(([code]) => {
if (code !== 0) throw new Error(`${command} exited with code ${code}`);
});
}
function capture(command, args) {
return new Promise((resolveBuffer, reject) => {
const child = spawn(command, args, { stdio: ['ignore', 'pipe', 'inherit'] });
const chunks = [];
child.stdout.on('data', (chunk) => chunks.push(chunk));
child.on('error', reject);
child.on('close', (code) => {
if (code !== 0) {
reject(new Error(`${command} exited with code ${code}`));
return;
}
resolveBuffer(Buffer.concat(chunks));
});
});
}
const tempDir = await mkdtemp(join(tmpdir(), 'hdr-wordmark-'));
const svgPath = join(tempDir, 'wordmark.svg');
const maskPath = join(tempDir, 'wordmark-mask.png');
const svg = `<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" width="${width}" height="${height}" viewBox="0 0 ${width} ${height}">
<defs>
<filter id="blur-strong" x="-20%" y="-70%" width="140%" height="240%">
<feGaussianBlur stdDeviation="38"/>
</filter>
<filter id="blur-soft" x="-20%" y="-70%" width="140%" height="240%">
<feGaussianBlur stdDeviation="16"/>
</filter>
</defs>
<g font-family="Arial Black, Helvetica, Arial, sans-serif" font-size="220" font-weight="900" letter-spacing="-9" text-anchor="middle">
<text x="1100" y="326" fill="#00faff" stroke="#00faff" stroke-width="48" opacity="0.96" filter="url(#blur-strong)">HDR Web Plugin</text>
<text x="1114" y="336" fill="#ff31ef" stroke="#ff31ef" stroke-width="30" opacity="0.74" filter="url(#blur-strong)">HDR Web Plugin</text>
<text x="1086" y="316" fill="#2f76ff" stroke="#2f76ff" stroke-width="22" opacity="0.62" filter="url(#blur-soft)">HDR Web Plugin</text>
<text x="1100" y="326" fill="#ffffff" stroke="#ffffff" stroke-width="18" opacity="0.72" filter="url(#blur-soft)">HDR Web Plugin</text>
<text x="1094" y="322" fill="#eaffff" stroke="#00faff" stroke-width="12" opacity="1" filter="url(#blur-soft)">HDR Web Plugin</text>
<text x="1100" y="326" fill="#ffffff" stroke="#ffffff" stroke-width="7">HDR Web Plugin</text>
</g>
</svg>`;
await mkdir(dirname(outputPath), { recursive: true });
await writeFile(svgPath, svg, 'utf8');
await run('/usr/bin/sips', [
'-s',
'format',
'png',
svgPath,
'--out',
maskPath
], { stdio: ['ignore', 'inherit', 'inherit'] });
const rgba = await capture('ffmpeg', [
'-hide_banner',
'-loglevel',
'error',
'-i',
maskPath,
'-f',
'rawvideo',
'-pix_fmt',
'rgba',
'pipe:1'
]);
const frame = Buffer.alloc(width * height * 8);
for (let index = 0; index < width * height; index += 1) {
const src = index * 4;
const dst = index * 8;
const alpha = rgba[src + 3] / 255;
const red = alpha === 0 ? 0 : rgba[src] / 255;
const green = alpha === 0 ? 0 : rgba[src + 1] / 255;
const blue = alpha === 0 ? 0 : rgba[src + 2] / 255;
const channels = [red, green, blue];
for (let channel = 0; channel < 3; channel += 1) {
const lifted = Math.pow(channels[channel], 0.42);
const value = Math.min(65535, Math.round(lifted * 65535));
frame.writeUInt16LE(value, dst + channel * 2);
}
const alphaValue = Math.min(65535, Math.round(Math.pow(alpha, 0.42) * 65535));
frame.writeUInt16LE(alphaValue, dst + 6);
}
const encode = spawn('ffmpeg', [
'-y',
'-f',
'rawvideo',
'-pix_fmt',
'rgba64le',
'-s:v',
`${width}x${height}`,
'-i',
'pipe:0',
'-vf',
'setparams=range=full:color_primaries=bt2020:color_trc=smpte2084:colorspace=gbr',
'-frames:v',
'1',
'-update',
'1',
'-c:v',
'png',
'-pix_fmt',
'rgba64be',
'-pred',
'mixed',
'-color_primaries',
'bt2020',
'-color_trc',
'smpte2084',
outputPath
], { stdio: ['pipe', 'inherit', 'inherit'] });
encode.stdin.end(frame);
const [encodeCode] = await once(encode, 'close');
await rm(tempDir, { recursive: true, force: true });
if (encodeCode !== 0) throw new Error(`ffmpeg png exited with code ${encodeCode}`);
console.log(`Wrote ${outputPath}`);