char-fork/llama.cpp
git clone https://git.t4t.associates/char-fork/llama.cpp
92d1bb0c9
char/aliceai-t5-moe
1#!/usr/bin/env node 2 3/** 4* Apply circular mask to pwa-*.png icons. 5* Uses the maskable icon as source (white bg, full logo) to avoid 6* 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 ) => { 25if ( arg === '--padding-pct' && args [ i + 1 ]) return parseFloat ( args [ i + 1 ]); 26 27return acc ; 28}, 0 ); 29// Scale down the source image before cropping to circle 30const scalePct = process . argv . reduce (( acc , arg , i , args ) => { 31if ( arg === '--scale-pct' && args [ i + 1 ]) return parseFloat ( args [ i + 1 ]); 32 33return acc ; 34}, 85 ); // default 85% - icon fills 85% of the circular area 35// 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 square 39const untouchedIcons = [ 'maskable-icon-512x512.png' , 'apple-touch-icon-180x180.png' ]; 40 41async function makeCircle ( targetFilename ) { 42const targetPath = path . join ( STATIC_DIR , targetFilename ); 43const sourcePath = path . join ( STATIC_DIR , sourceIcon ); 44 45if ( ! fs . existsSync ( sourcePath )) { 46console . log ( `⏭️ ${ sourceIcon } not found, skipping` ); 47 48return ; 49} 50 51if ( ! fs . existsSync ( targetPath )) { 52console . log ( `⏭️ ${ targetFilename } not found, skipping` ); 53 54return ; 55} 56 57const metadata = await sharp ( targetPath ). metadata (); 58const size = Math . max ( metadata . width , metadata . height ); 59const radius = Math . floor (( size * ( 1 - paddingPct / 100 )) / 2 ); 60const center = Math . floor ( size / 2 ); 61// Build circular mask as RGBA buffer: white opaque circle on transparent bg 62const maskBuf = Buffer . alloc ( size * size * 4 , 0 ); 63 64for ( let y = 0 ; y < size ; y ++ ) { 65for ( let x = 0 ; x < size ; x ++ ) { 66const dx = x - center ; 67const dy = y - center ; 68const dist = Math . sqrt ( dx * dx + dy * dy ); 69 70if ( dist < radius ) { 71const i = ( y * size + x ) * 4 ; 72 73maskBuf [ i ] = 255 ; 74maskBuf [ i + 1 ] = 255 ; 75maskBuf [ i + 2 ] = 255 ; 76maskBuf [ i + 3 ] = 255 ; 77} 78} 79} 80 81const tmpMask = path . join ( STATIC_DIR , '.mask-tmp.png' ); 82 83await sharp ( maskBuf , { 84raw :{ 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 size 90const circleDiameter = Math . floor ( size * ( 1 - paddingPct / 100 )); 91const scaledSize = Math . floor (( circleDiameter * scalePct ) / 100 ); 92const offset = Math . floor (( size - scaledSize ) / 2 ); 93const scaledBuf = await sharp ( sourcePath ) 94. resize ( scaledSize , scaledSize , { 95background :{ alpha :1 , b :255 , g :255 , r :255 }, 96fit :'cover' 97}) 98. ensureAlpha () 99. png () 100. toBuffer (); 101// Step 2: Composite scaled image onto white background, then apply circular mask 102const output = await sharp ({ 103create :{ 104background :{ alpha :1 , b :255 , g :255 , r :255 }, 105channels :4 , 106height :size , 107width :size 108} 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 117fs . writeFileSync ( targetPath , output ); 118fs . unlinkSync ( tmpMask ); 119 120console . log ( 121`✓ ${ targetFilename } → circle from ${ sourceIcon } , ${ paddingPct } % padding (size= ${ size } , r= ${ radius } , scale= ${ scalePct } %, circleDiameter= ${ circleDiameter } )` 122); 123} 124 125async function main () { 126console . log ( `Circular mask: ${ paddingPct } % padding, ${ scalePct } % scale, source= ${ sourceIcon } \n` ); 127for ( const icon of targetIcons ) { 128await makeCircle ( icon ); 129} 130 131console . log ( '\nUnchanged:' ); 132for ( const icon of untouchedIcons ) { 133const fp = path . join ( STATIC_DIR , icon ); 134 135console . log ( ` ${ icon } ( ${ fs . existsSync ( fp ) ? fs . statSync ( fp ). size + ' bytes' : 'missing' } )` ); 136} 137} 138 139main ();