#11110·cvat

Mask drawing/editing renders a blank white canvas on HiDPI displays with large images (no console errors)

Author: YzYhhhstudyCreated Aug 30, 2026Updated Aug 30, 2026
Labelsbug

Actions before raising this issue

  • I searched the existing issues and did not find anything similar.
  • I read/searched the docs

Steps to Reproduce

  1. Open CVAT in Chrome on a HiDPI display where window.devicePixelRatio === 2.

    The issue was reproduced on a MacBook Retina display at 100% browser zoom.

  2. Create or open a task containing a large image. The reproduced image is 12600 × 9000 pixels (113.4 MP).

  3. Open the job. The background image displays normally before entering mask drawing or editing mode.

  4. Select the brush tool, or choose Edit on an existing mask shape.

  5. The complete canvas area becomes opaque white:

    • the background image disappears;
    • the mask being edited disappears;
    • brush strokes are not visible;
    • no relevant exception appears in the Chrome console.
  6. Leave mask drawing/editing mode. The background image becomes visible again.

The same machine and job work when window.devicePixelRatio === 1, for example on a non-HiDPI display or when Chrome is launched with a forced device scale factor of 1.

Expected Behavior

The mask drawing layer should render normally above the image on HiDPI displays.

The background image and existing mask should remain visible, and brush/edit operations should work for an image that is otherwise within the browser canvas limits.

Possible Solution

MasksHandlerImpl creates its Fabric canvas without overriding Fabric's default Retina scaling behavior:

typescript
this.canvas = new fabric.Canvas(canvas, {
    containerClass: 'cvat_masks_canvas_wrapper',
    fireRightClick: true,
    selection: false,
    defaultCursor: 'inherit',
});

Fabric therefore scales the backing canvas dimensions by window.devicePixelRatio.

For a 12600 × 9000 image at devicePixelRatio = 2, both the lower and upper Fabric canvases attempt to use 25200 × 18000 backing stores, or 453,600,000 pixels each.

This exceeds Chromium's practical maximum 2D canvas area of 268,435,456 pixels. Chrome fails the allocation silently, so subsequent drawing operations become no-ops while the failed Fabric canvases remain above the background image.

Passing enableRetinaScaling: false when the mask editing canvas is created prevents the backing dimensions from being multiplied by DPR:

typescript
this.canvas = new fabric.Canvas(canvas, {
    containerClass: 'cvat_masks_canvas_wrapper',
    fireRightClick: true,
    selection: false,
    defaultCursor: 'inherit',
    enableRetinaScaling: false,
});

With this change, the mask canvas retains the image-coordinate dimensions while the browser continues to report window.devicePixelRatio === 2.

Context

The root-cause chain was verified in a self-hosted CVAT environment:

  1. Fabric enables Retina scaling by default.
  2. At DPR 2, the 12600 × 9000 mask canvas becomes 25200 × 18000.
  3. That backing-store area is 453,600,000 pixels, above Chromium's maximum canvas area.
  4. Canvas allocation and drawing fail silently.
  5. The failed Fabric lower and upper canvases remain above the background canvas, producing an opaque white editing area.
  6. Exiting mask editing removes the failed overlay and makes the background image visible again.

A minimal Chrome console probe demonstrates the silent allocation failure:

javascript
const canvas = document.createElement('canvas');
canvas.width = 25200;
canvas.height = 18000;

const context = canvas.getContext('2d');
context.fillStyle = 'red';
context.fillRect(0, 0, 10, 10);

console.log(context.getImageData(2, 2, 1, 1).data);
// Returns [0, 0, 0, 0] after the oversized allocation fails silently.

After applying enableRetinaScaling: false, the issue was retested on the original HiDPI client:

  • browser zoom: 100%;
  • window.devicePixelRatio: 2;
  • image size: 12600 × 9000;
  • lower Fabric canvas backing size: 12600 × 9000;
  • upper Fabric canvas backing size: 12600 × 9000;
  • entering brush/edit mode no longer produces a white canvas;
  • mask drawing and editing work;
  • annotations can be saved successfully.

The following canvas values were observed after the patch:

javascript
[
  {
    className: 'lower-canvas',
    backingWidth: 12600,
    backingHeight: 9000,
    cssWidth: 12600,
    cssHeight: 9000
  },
  {
    className: 'upper-canvas',
    backingWidth: 12600,
    backingHeight: 9000,
    cssWidth: 12600,
    cssHeight: 9000
  }
]

This is distinct from issues where the source image itself exceeds the browser canvas limit. In this case, the source image displays normally; only the DPR-scaled mask editing canvases exceed the limit.

Environment

markdown
- CVAT: v2.71.0, self-hosted with Docker Compose
- Server OS: Ubuntu 22.04
- Client browser: Chrome 151
- Client OS: macOS
- Browser zoom: 100%
- `window.devicePixelRatio`: 2
- Reproduced image size: `12600 × 9000`
- Remote access: ngrok
  - This is not relevant to the root cause because the failure occurs in the
    client-side Fabric canvas.