roof-raster-quality.mjs 1.4 KB

123456789101112131415161718192021222324
  1. // Bounded image processing; never adds pixels beyond source-derived sampling density.
  2. import assert from 'node:assert/strict'
  3. export function roofRasterSize(span, {maxDimension=256, metresPerPixel=null}={}) {
  4. assert.ok(span.length===2&&span.every(x=>Number.isFinite(x)&&x>0))
  5. assert.ok(Number.isInteger(maxDimension)&&maxDimension>=16&&maxDimension<=512)
  6. assert.ok(metresPerPixel===null||(Number.isFinite(metresPerPixel)&&metresPerPixel>0))
  7. const longest=Math.max(...span)
  8. const dimension=metresPerPixel===null?maxDimension:Math.max(16,Math.min(maxDimension,Math.ceil(longest/metresPerPixel)))
  9. return span.map(x=>Math.max(16,Math.min(dimension,Math.ceil(x/longest*dimension))))
  10. }
  11. export function sharpenRoofRgba(input,width,height,amount=0.25) {
  12. assert.equal(input.length,width*height*4)
  13. assert.ok(Number.isFinite(amount)&&amount>=0&&amount<=0.5)
  14. const result=Buffer.from(input)
  15. if(amount===0)return result
  16. // Restrained cross-kernel unsharp mask, per-channel delta bounded to avoid bright halos.
  17. for(let y=1;y<height-1;y++)for(let x=1;x<width-1;x++)for(let c=0;c<3;c++){
  18. const i=(y*width+x)*4+c
  19. const mean=(input[i-4]+input[i+4]+input[i-width*4]+input[i+width*4])/4
  20. const delta=Math.max(-8,Math.min(8,(input[i]-mean)*amount))
  21. result[i]=Math.max(0,Math.min(255,Math.round(input[i]+delta)))
  22. }
  23. return result
  24. }