fix(image-pipeline): align detail-adjust grain seed parity

This commit is contained in:
Matthias
2026-04-04 22:42:51 +02:00
parent 65e96cbdf1
commit 46b7aeb26e
4 changed files with 58 additions and 3 deletions

View File

@@ -539,6 +539,7 @@ function runLightAdjustShader(
function runDetailAdjustShader(
input: Uint8Array,
width: number,
uniforms: Map<string, number | [number, number, number]>,
): Uint8Array {
const output = new Uint8Array(input.length);
@@ -549,6 +550,7 @@ function runDetailAdjustShader(
const denoiseColor = Number(uniforms.get("uDenoiseColor") ?? 0);
const grainAmount = Number(uniforms.get("uGrainAmount") ?? 0);
const grainScale = Math.max(0.5, Number(uniforms.get("uGrainScale") ?? 1));
const imageWidth = Math.max(1, Number(uniforms.get("uImageWidth") ?? width));
for (let index = 0; index < input.length; index += 4) {
let red = input[index] ?? 0;
@@ -580,7 +582,11 @@ function runDetailAdjustShader(
}
if (grainAmount > 0) {
const grain = (pseudoNoise((index + 1) / grainScale) - 0.5) * grainAmount * 40;
const pixel = index / 4;
const x = pixel % imageWidth;
const y = Math.floor(pixel / imageWidth);
const pixelIndex = (y * imageWidth + x) * 4;
const grain = (pseudoNoise((pixelIndex + 1) / grainScale) - 0.5) * grainAmount * 40;
red += grain;
green += grain;
blue += grain;
@@ -798,7 +804,11 @@ function createParityWebglContext(): WebGLRenderingContext {
}
if (currentProgram.kind === "detail-adjust") {
currentFramebuffer.attachment.data = runDetailAdjustShader(sourceTexture.data, currentProgram.uniforms);
currentFramebuffer.attachment.data = runDetailAdjustShader(
sourceTexture.data,
drawWidth,
currentProgram.uniforms,
);
return;
}

View File

@@ -65,6 +65,19 @@ function createUnsupportedStep(): PipelineStep {
};
}
function createDetailAdjustStep(): PipelineStep {
return {
nodeId: "detail-1",
type: "detail-adjust",
params: {
sharpen: { amount: 20, radius: 1.4, detail: 10, masking: 0 },
clarity: 15,
denoise: { luminance: 12, color: 18, detail: 50 },
grain: { amount: 35, size: 2.5, roughness: 50 },
},
};
}
describe("webgl backend poc", () => {
beforeEach(() => {
vi.resetModules();
@@ -425,6 +438,29 @@ describe("webgl backend poc", () => {
expect(getContextSpy).toHaveBeenCalledWith("webgl2", expect.any(Object));
});
it("passes image width uniform for detail-adjust grain parity", async () => {
const fakeGl = createFakeWebglContext({
readbackPixels: new Uint8Array([11, 22, 33, 255]),
});
vi.spyOn(HTMLCanvasElement.prototype, "getContext").mockImplementation((contextId) => {
if (contextId === "webgl") {
return fakeGl;
}
return null;
});
const { createWebglPreviewBackend } = await import("@/lib/image-pipeline/backend/webgl/webgl-backend");
const backend = createWebglPreviewBackend();
backend.runPreviewStep({
pixels: new Uint8ClampedArray(7 * 3 * 4),
step: createDetailAdjustStep(),
width: 7,
height: 3,
});
expect(fakeGl.uniform1f).toHaveBeenCalledWith(expect.anything(), 7);
});
it("downgrades compile/link failures to cpu with runtime_error reason", async () => {
const { createBackendRouter } = await import("@/lib/image-pipeline/backend/backend-router");
const { createWebglPreviewBackend } = await import("@/lib/image-pipeline/backend/webgl/webgl-backend");