| Server IP : 217.154.3.148 / Your IP : 216.73.216.90 Web Server : nginx/1.30.3 System : Linux blissful-wright.217-154-3-148.plesk.page 6.8.0-124-generic #124-Ubuntu SMP PREEMPT_DYNAMIC Tue May 26 13:00:45 UTC 2026 x86_64 User : gracious-boyd_hfhh1h8vo9n ( 10000) PHP Version : 8.3.31 Disable Function : opcache_get_status MySQL : OFF | cURL : ON | WGET : OFF | Perl : OFF | Python : OFF | Sudo : OFF | Pkexec : OFF Directory : /var/www/vhosts/gracious-boyd.217-154-3-148.plesk.page/nextjs/lib/ |
Upload File : |
import sharp from 'sharp'
function dataUrlToBuf(d: string){ return Buffer.from(d.replace(/^data:image\/png;base64,/, ''), 'base64') }
function grayFromRGBA(r:number,g:number,b:number){ return 0.299*r + 0.587*g + 0.114*b }
export async function estimateLightShadow(
imgBuf: Buffer,
opts: { fgMask?: string; blur?: number; sigma?: number; eps?: number } = {}
){
const eps = opts.eps ?? 1e-3
const meta = await sharp(imgBuf).metadata()
const W = meta.width||0, H = meta.height||0
if (!W || !H) throw new Error('bad image size')
const longest = Math.max(W, H)
const adaptiveBlur = Math.max(12, Math.round(longest / 28))
const blur = opts.blur ?? adaptiveBlur // starker Gaussian für Low-Freq Lichtfeld
// 1) RGB(A) Rohdaten
const src = await sharp(imgBuf).ensureAlpha().raw().toBuffer()
// 2) Luminanz + weiches Lichtfeld (Gaussian blur)
const L = new Float32Array(W*H) // original luminance 0..255
for (let i=0, j=0; i<W*H; i++, j+=4) {
L[i] = grayFromRGBA(src[j], src[j+1], src[j+2])
}
const blurred = await sharp(src, { raw:{width:W, height:H, channels:4} })
.blur(blur)
.raw()
.toBuffer()
const LF = new Float32Array(W*H) // low-frequency lighting
for (let i=0, j=0; i<W*H; i++, j+=4) {
LF[i] = grayFromRGBA(blurred[j], blurred[j+1], blurred[j+2]) + 1e-6
}
// 3) Area-Normalisierung nur auf Hintergrund (inverse FG)
// → median des Hintergrundes ≈ 1.0
let maskInv:number[]|null=null
if (opts.fgMask){
const mbuf = dataUrlToBuf(opts.fgMask)
const m = await sharp(mbuf).ensureAlpha().resize(W,H,{fit:'fill'}).raw().toBuffer()
maskInv = new Array(W*H)
for (let i=0, j=3; i<W*H; i++, j+=4) maskInv[i] = (m[j] < 128) ? 1 : 0
}
const vals:number[]=[]
for (let i=0;i<W*H;i++){
if (!maskInv || maskInv[i]===1){
vals.push(L[i]/LF[i])
}
}
vals.sort((a,b)=>a-b)
const med = vals.length ? (vals.length%2? vals[(vals.length-1)/2] : 0.5*(vals[vals.length/2-1]+vals[vals.length/2])) : 1
const norm = Math.max(med, 1e-3)
// 4) Relighting-Map R = L / (LF*norm)
const R = new Float32Array(W*H)
for (let i=0;i<W*H;i++) R[i] = Math.max(0, Math.min(2.5, (L[i]/(LF[i]+eps)) / norm)) // clamp 0..2.5
// 5) Aufsplitten: Schatten (<1) & Licht (>1)
const shadow = Buffer.alloc(W*H*4)
const light = Buffer.alloc(W*H*4)
for (let i=0;i<W*H;i++){
const r = R[i]
// shadow: map 0..1 → 0..1 (1 = keine Abdunklung)
const s = Math.min(1, r) // 0..1
const l = Math.max(0, Math.min(2, r)) // 0..2
// Ausgabestrategie:
// - shadow RGBA: Grauwert = 255, Alpha = (1 - s) → mit 'multiply' anwenden
// - light RGBA: Grauwert = 255, Alpha = (l-1) → mit 'screen' anwenden
const sA = Math.max(0, Math.min(1, 1 - s))
const lA = Math.max(0, Math.min(1, l - 1))
const j=i*4
shadow[j]=255; shadow[j+1]=255; shadow[j+2]=255; shadow[j+3]=Math.round(sA*255)
light[j] =255; light[j+1] =255; light[j+2] =255; light[j+3] =Math.round(lA*255)
// optional: FG-Bereiche ausmaskieren → nur Hintergrundlicht
if (maskInv && maskInv[i]===0){ shadow[j+3]=0; light[j+3]=0 }
}
const shadowPng = await sharp(shadow, {raw:{width:W,height:H,channels:4}}).png().toBuffer()
const lightPng = await sharp(light , {raw:{width:W,height:H,channels:4}}).png().toBuffer()
return {
shadow_mask: 'data:image/png;base64,'+shadowPng.toString('base64'),
light_mask : 'data:image/png;base64,'+lightPng.toString('base64'),
width: W, height: H
}
}