← back to CelebritySignatures
public/assets/wallpaper-pattern.js
49 lines
export const DEFAULTS = Object.freeze({version:1,qid:'Q91',stripeWidth:4,gapWidth:1.5,rowGap:1.5,scale:100,repeat:'straight',stripeColor:'#e0e6dc',gapColor:'#faf7ef',inkColor:'#244335',zoom:80});
const number = (value, fallback, min, max) => value !== null && value !== '' && Number.isFinite(Number(value)) ? Math.min(max,Math.max(min,Number(value))) : fallback;
const color = (value, fallback) => /^#[a-f\d]{6}$/i.test(String(value)) ? value.toLowerCase() : fallback;
export function normalize(input={}) {
if (!input || typeof input !== 'object') input={};
return {version:1,qid:/^Q\d+$/.test(input.qid || '') ? input.qid : DEFAULTS.qid,
stripeWidth:number(input.stripeWidth,DEFAULTS.stripeWidth,1,10),gapWidth:number(input.gapWidth,DEFAULTS.gapWidth,0,6),
rowGap:number(input.rowGap,DEFAULTS.rowGap,0.25,6),scale:number(input.scale,DEFAULTS.scale,25,150),
repeat:input.repeat==='half-drop'?'half-drop':'straight',stripeColor:color(input.stripeColor,DEFAULTS.stripeColor),
gapColor:color(input.gapColor,DEFAULTS.gapColor),inkColor:color(input.inkColor,DEFAULTS.inkColor),zoom:number(input.zoom,DEFAULTS.zoom,40,150)};
}
export function geometry(input, aspect, ppi=96) {
const s=normalize(input);
const stripe=Math.round(s.stripeWidth*ppi), gap=Math.round(s.gapWidth*ppi);
const columns=s.repeat==='half-drop'?2:1;
const ratio=number(aspect,4,0.2,30);
let motifWidth=Math.round(stripe*0.6*s.scale/100);
let motifHeight=Math.max(1,Math.round(motifWidth/ratio));
// Keep unusually tall marks proportional and the export canvas bounded.
if(motifHeight>5*ppi){motifHeight=5*ppi;motifWidth=Math.max(1,Math.round(motifHeight*ratio));}
const height=motifHeight+Math.round(s.rowGap*ppi);
return {stripe,gap,columns,columnWidth:stripe+gap,width:(stripe+gap)*columns,height,motifWidth,motifHeight,ppi};
}
export function placements(g,repeat) {
const output=[];
for(let col=0;col<g.columns;col++) {
const x=col*g.columnWidth+(g.stripe-g.motifWidth)/2;
const drop=repeat==='half-drop' && col%2 ? g.height/2 : 0;
for(let row=-1;row<=1;row++) {
const y=(g.height-g.motifHeight)/2+drop+row*g.height;
if(y<g.height && y+g.motifHeight>0) output.push({x,y,width:g.motifWidth,height:g.motifHeight,column:col});
}
}
return output;
}
export function drawTile(canvas,mask,input,ppi=96) {
const s=normalize(input),g=geometry(s,mask.width/mask.height,ppi);
canvas.width=g.width;canvas.height=g.height;
const ctx=canvas.getContext('2d');
ctx.fillStyle=s.gapColor;ctx.fillRect(0,0,g.width,g.height);
ctx.fillStyle=s.stripeColor;
for(let col=0;col<g.columns;col++) ctx.fillRect(col*g.columnWidth,0,g.stripe,g.height);
const ink=document.createElement('canvas');ink.width=mask.width;ink.height=mask.height;
const brush=ink.getContext('2d');brush.drawImage(mask,0,0);brush.globalCompositeOperation='source-in';
brush.fillStyle=s.inkColor;brush.fillRect(0,0,ink.width,ink.height);
for(const p of placements(g,s.repeat)) ctx.drawImage(ink,p.x,p.y,p.width,p.height);
return g;
}