Closed Bug 1613369 Opened 6 years ago Closed 18 hours ago

Premultiplied alpha rounding error with canvas 2D

Categories

(Core :: Graphics: Canvas2D, defect, P3)

72 Branch
defect

Tracking

()

RESOLVED FIXED
160 Branch
Tracking Status
firefox160 --- fixed

People

(Reporter: info, Assigned: python273)

Details

(Keywords: parity-chrome, parity-edge, testcase)

Attachments

(2 files)

User Agent: Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:72.0) Gecko/20100101 Firefox/72.0

Steps to reproduce:

I have created a 1x1 px PNG with the values [12, 187, 146, 62], put it into a canvas and read back the values. Here is the code with the base64 encoded PNG (see also https://stackoverflow.com/q/60074569/1796523):

let image = document.createElement('img');
image.addEventListener('load', () => {
let canvas = document.createElement('canvas');
let ctx = canvas.getContext("2d");
canvas.width = image.width;
canvas.height = image.height;
ctx.drawImage(image, 0, 0);
let data = ctx.getImageData(0, 0, canvas.width, canvas.height).data;
document.body.innerHTML = data;
});

image.src = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR4nGPg2T3JDgADyAGYmiSbAQAAAABJRU5ErkJggg==";

Actual results:

The code returned [12, 189, 148, 62].

Expected results:

It should return [12, 185, 144, 62] which is based on the premultiplied alpha value of [3, 45, 35, 255]. Firefox seems to calculate [3, 46, 36, 255] (see also https://stackoverflow.com/a/60076433/1796523). This is working correctly in Chrome.

Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:74.0) Gecko/20100101 Firefox/74.0
20200205055433

Status: UNCONFIRMED → NEW
Has STR: --- → yes
Component: Untriaged → Canvas: 2D
Ever confirmed: true
Product: Firefox → Core
Attached file testcase.html —

Lee what do you think?

Flags: needinfo?(lsalzman)
Priority: -- → P3

Andrew, anything silly going on in the PNG decoder here?

Flags: needinfo?(lsalzman)
Severity: normal → S3

Still reproducible in Firefox 157.0 on macOS. A canvas → PNG → canvas round trip changes [127,0,0,8] to [159,0,0,8], while a direct putImageData()/getImageData() round trip preserves it.

On a larger image, repeated round trips caused visible progressive brightening/banding after 5–10 cycles, with alpha unchanged. Chromium 151 and WebKit 26.5 remained stable through 50 cycles. This affects PNG-based storage in image editing applications, especially after repeated round trips.

Minimal reproduction:

const canvas = document.createElement("canvas");
canvas.width = canvas.height = 1;
const ctx = canvas.getContext("2d");
ctx.putImageData(
  new ImageData(new Uint8ClampedArray([127, 0, 0, 8]), 1, 1), 0, 0
);
console.log("before:", Array.from(ctx.getImageData(0, 0, 1, 1).data));

const img = new Image();
img.src = canvas.toDataURL("image/png");
await img.decode();
ctx.clearRect(0, 0, 1, 1);
ctx.drawImage(img, 0, 0);
console.log("after:", Array.from(ctx.getImageData(0, 0, 1, 1).data));

Possible code pointers: nsPNGEncoder::ConvertHostARGBRow() unpremultiplies using nearest rounding:

(channel * 255 + alpha / 2) / alpha

However, UnpremultiplyChunkFallback() in Swizzle.cpp uses a fixed-point reciprocal with truncation. PremultiplyChunkFallback() in the same file uses an upward-biased divide-by-255 approximation.

This rounding mismatch appears to explain the reproduction: premultiplied red 4 at alpha 8 reads back as 127, exports as 128, then becomes premultiplied red 5 on import and reads back as 159.

Encoding the saved getImageData() RGBA bytes directly into PNG with Python Pillow, then importing through the same Firefox path, avoided the drift in the tested image.

Assignee: nobody → bugzilla
Status: NEW → ASSIGNED
Pushed by lsalzman@mozilla.com: https://github.com/mozilla-firefox/firefox/commit/234b96c3dcc5 https://hg.mozilla.org/integration/autoland/rev/e4ef043f66bc Use paired nearest rounding for 8-bit alpha conversions. r=gfx-reviewers,lsalzman
Pushed by smolnar@mozilla.com: https://github.com/mozilla-firefox/firefox/commit/02bde4f3c9b0 https://hg.mozilla.org/integration/autoland/rev/a2ab2783e63a Revert "Bug 1613369 - Use paired nearest rounding for 8-bit alpha conversions. r=gfx-reviewers,lsalzman" for causing mochitest gpu failures
Attachment #9654104 - Attachment description: Bug 1613369 - Use paired nearest rounding for 8-bit alpha conversions. r=#gfx-reviewers → Bug 1613369 - Use paired nearest alpha rounding and correct conversion expectations. r?lsalzman
  • Corrected the gradient expectations to pixel-center sampling; stored/rendered
    pixels are unchanged, and tolerance remains 3.
  • Regenerated four JPEG/WebP references and both affected favicon PNG variants
    after verifying the intended nearest-rounding pixel changes.
  • Removed obsolete FAIL expectations for 18 compositing WPTs (including the
    offscreen/worker variants). These now genuinely pass; references and fuzzy
    limits are unchanged.

The original C++ fix is unchanged. Optimized local checks pass all 27
image/favicons files, the legacy canvas test, all 267 PNG round-trip assertions
on software/verified NVIDIA acceleration, and 212 graphics/image gtests.
Additional opaque-PNG and compositing mismatches found during local C-zlib (USE_LIBZ_RS= test_imgtools.js) and
forced-GPU testing also reproduce on matched prepatch builds, so they appear
unrelated to this change.

Flags: needinfo?(bugzilla)
Pushed by lsalzman@mozilla.com: https://github.com/mozilla-firefox/firefox/commit/f84faf54643e https://hg.mozilla.org/integration/autoland/rev/123afceb75bd Use paired nearest alpha rounding and correct conversion expectations. r=gfx-reviewers,lsalzman,places-reviewers,mak
Pushed by agoloman@mozilla.com: https://github.com/mozilla-firefox/firefox/commit/5d3b9c9c0e96 https://hg.mozilla.org/integration/autoland/rev/f2a7d839022b Revert "Bug 1613369 - Use paired nearest alpha rounding and correct conversion expectations. r=gfx-reviewers,lsalzman,places-reviewers,mak" for causing reftest failures.

Backed out for causing reftest failures.

Flags: needinfo?(bugzilla)

Updated D331332 with the GTK snapshot reference fix

Flags: needinfo?(bugzilla)
Pushed by lsalzman@mozilla.com: https://github.com/mozilla-firefox/firefox/commit/20c63b43b9ba https://hg.mozilla.org/integration/autoland/rev/6cf47244e4e3 Use paired nearest alpha rounding and correct conversion expectations. r=gfx-reviewers,lsalzman,places-reviewers,mak
Pushed by abutkovits@mozilla.com: https://github.com/mozilla-firefox/firefox/commit/bc58d78989b9 https://hg.mozilla.org/integration/autoland/rev/ee203168e091 Revert "Bug 1613369 - Use paired nearest alpha rounding and correct conversion expectations. r=gfx-reviewers,lsalzman,places-reviewers,mak" for causing failures at conformance-alpha-premultiplied-reftest.html.
Pushed by abutkovits@mozilla.com: https://github.com/mozilla-firefox/firefox/commit/7af81fe47ac8 https://hg.mozilla.org/mozilla-central/rev/22080c83e0d3 Revert "Bug 1613369 - Use paired nearest alpha rounding and correct conversion expectations. r=gfx-reviewers,lsalzman,places-reviewers,mak" for causing failures at conformance-alpha-premultiplied-reftest.html.
Attachment #9654104 - Attachment description: Bug 1613369 - Use paired nearest alpha rounding and correct conversion expectations. r?lsalzman → Bug 1613369 - Use paired nearest alpha rounding and correct conversion expectations. r=gfx-reviewers,lsalzman,places-reviewers,mak

Updated the JPEG XL reftest’s pixel-count tolerance to 36000, retaining the original libjxl conformance PNG

Flags: needinfo?(bugzilla)
Pushed by lsalzman@mozilla.com: https://github.com/mozilla-firefox/firefox/commit/6b9d9e663b9f https://hg.mozilla.org/integration/autoland/rev/dc7e8879cfdf Use paired nearest alpha rounding and correct conversion expectations. r=gfx-reviewers,lsalzman,places-reviewers,mak
Status: ASSIGNED → RESOLVED
Closed: 18 hours ago
Resolution: --- → FIXED
Target Milestone: --- → 160 Branch

Created web-platform-tests PR https://github.com/web-platform-tests/wpt/pull/63538 for changes under testing/web-platform/tests

You need to log in before you can comment on or make changes to this bug.

Attachment

General

Created:
Updated:
Size: