diff --git a/docker/frontend/src/engine/exportEngine.ts b/docker/frontend/src/engine/exportEngine.ts index d6145e7..98e76b4 100644 --- a/docker/frontend/src/engine/exportEngine.ts +++ b/docker/frontend/src/engine/exportEngine.ts @@ -185,6 +185,23 @@ function screenSharpenImage(image: any, amount = 0.5): any { return convolveImage(image, amount); } +// The colour matrix, applied on its own into a fresh image. A SkPaint runs its +// shader before its colourFilter, so the graded pixels have to exist as an +// image before the tone/cinema pass can read them (see renderPhoto step 3). +function gradeThrough(image: any, w: number, h: number, colorFilter: any): any { + const surf = createSurface(w, h); + if (!surf) return null; + try { + const p = Skia.Paint(); + p.setColorFilter(colorFilter); + surf.getCanvas().drawImage(image, 0, 0, p); + p.dispose(); + return surf.makeImageSnapshot() ?? null; + } finally { + surf.dispose(); + } +} + // Snapshot → clear → blur redraw: the blur must not sit on top of the sharp // pixels it is meant to replace. function drawBlurred(canvas: any, surface: any, w: number, h: number, sigma: number): void { @@ -305,47 +322,60 @@ export async function renderPhoto(input: RenderInput): Promise v === (i % 6 === 0 ? 1 : 0)); // 3b. Tone shader. const tone = getToneUniforms(adjustments, recipe.baseFilter); let toneShader: any = null; + // 3c. Cinema seasonal grade (cinema → tone → image). + const cinema = getCinemaUniforms(recipe.cinema); + const cinemaOn = !!cinema && cinemaIsActive(cinema.flat); + let paintShader: any = null; + let gradeImage = skImage; + const graded = (toneIsActive(tone) || cinemaOn) && !matrixIsIdentity; + if (graded) { + const snapshot = gradeThrough(skImage, width, height, colorFilter); + if (snapshot) { + own(snapshot); + gradeImage = snapshot; + } + } + const imageShaderOf = () => + own( + gradeImage.makeShaderOptions( + Skia.TileMode.Clamp, Skia.TileMode.Clamp, Skia.FilterMode.Linear, Skia.MipmapMode.None + ) + ); if (toneIsActive(tone)) { const { toneEffect: effect } = effects(); if (effect) { - const imageShader = own( - skImage.makeShaderOptions( - Skia.TileMode.Clamp, Skia.TileMode.Clamp, Skia.FilterMode.Linear, Skia.MipmapMode.None - ) - ); - toneShader = own(effect.makeShaderWithChildren(toneUniformArray(tone), [imageShader])); - if (toneShader) paint.setShader(toneShader); + toneShader = own(effect.makeShaderWithChildren(toneUniformArray(tone), [imageShaderOf()])); } } - - // 3c. Cinema seasonal grade (cinema → tone → image). - const cinema = getCinemaUniforms(recipe.cinema); - let paintShader = toneShader; - if (cinema && cinemaIsActive(cinema.flat)) { + if (cinemaOn) { const { cinemaEffect: effect } = effects(); if (effect) { - const child = - toneShader ?? - own(skImage.makeShaderOptions( - Skia.TileMode.Clamp, Skia.TileMode.Clamp, Skia.FilterMode.Linear, Skia.MipmapMode.None - )); - const cs = own(effect.makeShaderWithChildren(cinema.flat, [child])); - if (cs) { - paintShader = cs; - paint.setShader(paintShader); - } + const cs = own(effect.makeShaderWithChildren(cinema.flat, [toneShader ?? imageShaderOf()])); + if (cs) paintShader = cs; } } + if (!paintShader && toneShader) paintShader = toneShader; + // Only one of the two may tint the pixels: the graded image already carries + // the matrix, an ungraded draw still needs it. + if (paintShader) paint.setShader(paintShader); + else if (!graded) paint.setColorFilter(colorFilter); // 4. Denoise (blur only — CLARITY moved to its own pass below, see header). if (adjustments.denoise > 0) { @@ -382,24 +412,22 @@ export async function renderPhoto(input: RenderInput): Promise + graded ? undefined : own(Skia.ImageFilter.MakeColorFilter(inner ?? colorFilter, null)); + const bloomBlur = () => own(Skia.ImageFilter.MakeBlur(sigma, sigma, Skia.TileMode.Clamp, null)); if (glowShader != null) { glowPaint.setShader(glowShader); - glowPaint.setImageFilter( - own(Skia.ImageFilter.MakeCompose( - own(Skia.ImageFilter.MakeBlur(sigma, sigma, Skia.TileMode.Clamp, null)), - own(Skia.ImageFilter.MakeColorFilter(colorFilter, null)) - )) - ); + const grade = bloomGrade(); + const blur = bloomBlur(); + glowPaint.setImageFilter(grade ? own(Skia.ImageFilter.MakeCompose(blur, grade)) : blur); canvas.drawRect(Skia.XYWHRect(0, 0, width, height), glowPaint); } else { const hiMatrix = [ @@ -408,23 +436,16 @@ export async function renderPhoto(input: RenderInput): Promise