Team Ai
Modelpublic

Felipe97/llama-cpp-compiled

sourceHugging Faceupdated 21d agoView on Hugging Face
0likes1.2kdownloads
make-icons-circular.js140 linesDownload Raw Back to scripts
1#!/usr/bin/env node2 3/**4 * Apply circular mask to pwa-*.png icons.5 * Uses the maskable icon as source (white bg, full logo) to avoid6 * the small-colormap pwa icons looking bad when cropped to a circle.7 *8 * Usage: node scripts/make-icons-circular.js [--padding-pct <0-50>] [--scale-pct <50-100>]9 *10 * - padding-pct: percentage of icon size kept as padding around the circle (default: 25)11 * - scale-pct: scale down the source image before cropping (default: 85)12 *13 * maskable-icon and apple-touch-icon are left untouched.14 */15 16import fs from 'fs';17import path from 'path';18import sharp from 'sharp';19import { fileURLToPath } from 'url';20 21const __filename = fileURLToPath(import.meta.url);22const __dirname = path.dirname(__filename);23const STATIC_DIR = path.resolve(__dirname, '..', 'static');24const paddingPct = process.argv.reduce((acc, arg, i, args) => {25	if (arg === '--padding-pct' && args[i + 1]) return parseFloat(args[i + 1]);26 27	return acc;28}, 0);29// Scale down the source image before cropping to circle30const scalePct = process.argv.reduce((acc, arg, i, args) => {31	if (arg === '--scale-pct' && args[i + 1]) return parseFloat(args[i + 1]);32 33	return acc;34}, 85); // default 85% - icon fills 85% of the circular area35// Source for circular icons: the maskable icon (white bg, full logo)36const sourceIcon = 'maskable-icon-512x512.png';37const targetIcons = ['pwa-64x64.png', 'pwa-192x192.png', 'pwa-512x512.png'];38// maskable-icon and apple-touch-icon stay square39const untouchedIcons = ['maskable-icon-512x512.png', 'apple-touch-icon-180x180.png'];40 41async function makeCircle(targetFilename) {42	const targetPath = path.join(STATIC_DIR, targetFilename);43	const sourcePath = path.join(STATIC_DIR, sourceIcon);44 45	if (!fs.existsSync(sourcePath)) {46		console.log(`⏭️  ${sourceIcon} not found, skipping`);47 48		return;49	}50 51	if (!fs.existsSync(targetPath)) {52		console.log(`⏭️  ${targetFilename} not found, skipping`);53 54		return;55	}56 57	const metadata = await sharp(targetPath).metadata();58	const size = Math.max(metadata.width, metadata.height);59	const radius = Math.floor((size * (1 - paddingPct / 100)) / 2);60	const center = Math.floor(size / 2);61	// Build circular mask as RGBA buffer: white opaque circle on transparent bg62	const maskBuf = Buffer.alloc(size * size * 4, 0);63 64	for (let y = 0; y < size; y++) {65		for (let x = 0; x < size; x++) {66			const dx = x - center;67			const dy = y - center;68			const dist = Math.sqrt(dx * dx + dy * dy);69 70			if (dist < radius) {71				const i = (y * size + x) * 4;72 73				maskBuf[i] = 255;74				maskBuf[i + 1] = 255;75				maskBuf[i + 2] = 255;76				maskBuf[i + 3] = 255;77			}78		}79	}80 81	const tmpMask = path.join(STATIC_DIR, '.mask-tmp.png');82 83	await sharp(maskBuf, {84		raw: { channels: 4, height: size, width: size }85	})86		.png()87		.toFile(tmpMask);88 89	// Step 1: Scale source relative to circle diameter (not full icon), composite centered onto white canvas of full size90	const circleDiameter = Math.floor(size * (1 - paddingPct / 100));91	const scaledSize = Math.floor((circleDiameter * scalePct) / 100);92	const offset = Math.floor((size - scaledSize) / 2);93	const scaledBuf = await sharp(sourcePath)94		.resize(scaledSize, scaledSize, {95			background: { alpha: 1, b: 255, g: 255, r: 255 },96			fit: 'cover'97		})98		.ensureAlpha()99		.png()100		.toBuffer();101	// Step 2: Composite scaled image onto white background, then apply circular mask102	const output = await sharp({103		create: {104			background: { alpha: 1, b: 255, g: 255, r: 255 },105			channels: 4,106			height: size,107			width: size108		}109	})110		.composite([111			{ input: scaledBuf, left: offset, top: offset },112			{ blend: 'dest-in', input: tmpMask, left: 0, top: 0 }113		])114		.png()115		.toBuffer();116 117	fs.writeFileSync(targetPath, output);118	fs.unlinkSync(tmpMask);119 120	console.log(121		`✓ ${targetFilename} → circle from ${sourceIcon}, ${paddingPct}% padding (size=${size}, r=${radius}, scale=${scalePct}%, circleDiameter=${circleDiameter})`122	);123}124 125async function main() {126	console.log(`Circular mask: ${paddingPct}% padding, ${scalePct}% scale, source=${sourceIcon}\n`);127	for (const icon of targetIcons) {128		await makeCircle(icon);129	}130 131	console.log('\nUnchanged:');132	for (const icon of untouchedIcons) {133		const fp = path.join(STATIC_DIR, icon);134 135		console.log(`  ${icon} (${fs.existsSync(fp) ? fs.statSync(fp).size + ' bytes' : 'missing'})`);136	}137}138 139main();140