Skip to content

[p5.js 2.0+ Bug Report]: p5.Image pixelDensity() compounds division on repeated calls and resize() desynchronizes canvas backing store #9152

Description

@Pcmhacker-piro

Most appropriate sub-area of p5.js?

  • Accessibility
  • Color
  • Core/Environment/Rendering
  • Data
  • DOM
  • Events
  • Image
  • IO
  • Math
  • Typography
  • Utilities
  • WebGL
  • WebGPU
  • p5.strands
  • Build process
  • Unit testing
  • Internationalization
  • Friendly errors
  • Other (specify if possible)

p5.js version

2.x (main branch)

Web browser and version

All browsers (Chromium / Firefox / Safari)

Operating system

macOS / All

Steps to reproduce this

Steps:

  1. Create a p5.Image (e.g. const img = createImage(100, 100)).
  2. Set img.pixelDensity(2)img.width becomes 50.
  3. Call img.pixelDensity(2) again — img.width becomes 25 instead of remaining 50.
  4. Call img.pixelDensity(1)img.width remains 25 instead of restoring to 100.
  5. Call img.resize(50, 50) on an image with pixelDensity(2)img.canvas.width is set to 50 instead of 100 (50 * 2), causing img.get(30, 30) to return [0, 0, 0, 0] as out of bounds, and img.set(x, y) to silently drop writes.

Snippet:

function setup() {
  createCanvas(200, 200);

  // --- Bug 1: pixelDensity setter is not idempotent & cannot be reset ---
  const img1 = createImage(100, 100);
  img1.pixelDensity(2);
  console.log('After pixelDensity(2):', img1.width); // 50 (correct)

  img1.pixelDensity(2);
  console.log('After 2nd pixelDensity(2):', img1.width); // 25 (BUG: expected 50, halved again)

  img1.pixelDensity(1);
  console.log('After resetting pixelDensity(1):', img1.width); // 25 (BUG: expected 100, not restored)

  // --- Bug 2: resize() desynchronizes canvas backing store at density > 1 ---
  const img2 = createImage(100, 100);
  img2.pixelDensity(2);
  img2.resize(50, 50);

  console.log('Logical width:', img2.width); // 50
  console.log('Backing canvas.width:', img2.canvas.width); // 50 (BUG: expected 100)

  // Because canvas.width is 50, get() checks x * 2 >= canvas.width.
  // Coordinate (30, 30) is inside the 50x50 logical image, but 30 * 2 = 60 >= 50.
  const col = img2.get(30, 30);
  console.log('img2.get(30, 30):', col); // [0, 0, 0, 0] (BUG: treated as out of bounds)
}

Expected behavior:

  1. img.pixelDensity(density) should be idempotent: calling img.pixelDensity(2) repeatedly should preserve logical dimensions at canvas.width / density (50), and calling img.pixelDensity(1) should restore logical dimensions to canvas.width / 1 (100).
  2. img.resize(width, height) should maintain high-DPI resolution by resizing the backing canvas to Math.floor(width * this._pixelDensity) and Math.floor(height * this._pixelDensity). Pixel operations (get(), set(), loadPixels()) should work accurately across the full logical dimensions of the resized image.

Actual behavior:

  1. In src/image/p5.Image.js:
// Adjust canvas dimensions based on pixel density
this.width /= density;
this.height /= density;

this.width and this.height are mutated via relative division (/= density). Each call compounds the division, and resetting to 1 divides by 1 without restoring the original dimensions.

  1. In src/image/p5.Image.js:
// Resize the original canvas, which will clear its contents
this.canvas.width = this.width = width;
this.canvas.height = this.height = height;

resize() sets this.canvas.width and this.canvas.height directly to the logical target size width and height, discarding physical pixel density and desynchronizing the backing canvas from this._pixelDensity. As a result, subsequent get(x, y) calls fail bounds checking for any coordinates where x * pd >= canvas.width, returning transparent black [0, 0, 0, 0], and set(x, y) silently drops pixel writes.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions