Skip to content

Canvas toBuffer() ignores pixelFormat option and returns RGBA instead of RGB16_565 #2535

@capolan

Description

@capolan

Canvas toBuffer() ignores pixelFormat option and returns RGBA instead of RGB16_565

Issue Summary

When creating a canvas with pixelFormat: 'RGB16_565' and calling toBuffer('raw', { pixelFormat: 'RGB16_565' }), the method returns a 32-bit RGBA buffer instead of the expected 16-bit RGB565 buffer.

Environment

  • Node.js version: [Your Node.js version]
  • Canvas library version: 2.11.2
  • Operating System: Linux (Raspberry Pi) Debian GNU/Linux 12 (bookworm)
  • Platform: ARM64

Expected Behavior

When using:

const canvas = createCanvas(1920, 1080, 'image', { pixelFormat: 'RGB16_565' });
const ctx = canvas.getContext('2d', { pixelFormat: 'RGB16_565', alpha: false });
const buffer = canvas.toBuffer('raw', { pixelFormat: 'RGB16_565' });

The buffer should contain RGB565 data with:

  • Expected size: 1920 × 1080 × 2 = 4,147,200 bytes (16-bit per pixel)
  • Expected format: RGB565 (5-bit red, 6-bit green, 5-bit blue)

Actual Behavior

The buffer contains RGBA data with:

  • Actual size: 1920 × 1080 × 4 = 8,294,400 bytes (32-bit per pixel)
  • Actual format: RGBA (8-bit red, 8-bit green, 8-bit blue, 8-bit alpha)

Reproduction Code

const { createCanvas } = require('canvas');

// Create canvas with RGB16_565 format
const canvas = createCanvas(1920, 1080, 'image', { pixelFormat: 'RGB16_565' });
const ctx = canvas.getContext('2d', { 
  pixelFormat: 'RGB16_565',
  alpha: false 
});

// Draw something
ctx.fillStyle = '#FF0000';
ctx.fillRect(0, 0, 100, 100);

// Try to get RGB565 buffer
const buffer = canvas.toBuffer('raw', { pixelFormat: 'RGB16_565' });

// Check buffer size
const expectedSize = 1920 * 1080 * 2; // RGB565 = 2 bytes per pixel
const actualSize = buffer.length;
const bitsPerPixel = (actualSize / (1920 * 1080)) * 8;

console.log(`Expected size (RGB565): ${expectedSize} bytes`);
console.log(`Actual size: ${actualSize} bytes`);
console.log(`Bits per pixel: ${bitsPerPixel}`);
console.log(`Format detected: ${bitsPerPixel === 16 ? 'RGB565' : 'RGBA'}`);

Output

Expected size (RGB565): 4147200 bytes
Actual size: 8294400 bytes
Bits per pixel: 32
Format detected: RGBA

Impact

This issue prevents direct framebuffer rendering on embedded systems (like Raspberry Pi) that expect RGB565 format. Currently, we need to manually convert RGBA to RGB565, which:

  1. Doubles memory usage (8MB instead of 4MB for 1920×1080)
  2. Increases CPU usage due to manual conversion
  3. Reduces performance significantly on ARM devices
  4. Defeats the purpose of specifying pixelFormat: 'RGB16_565'

Current Workaround

// Manual RGBA to RGB565 conversion
const imageData = ctx.getImageData(0, 0, width, height).data;
const bufferSize = width * height * 2;
const buffer = Buffer.alloc(bufferSize);

let bufferIndex = 0;
for (let i = 0; i < imageData.length; i += 4) {
  const r = imageData[i];
  const g = imageData[i + 1];
  const b = imageData[i + 2];
  
  const r5 = Math.round(r * 31 / 255) & 0x1F;
  const g6 = Math.round(g * 63 / 255) & 0x3F;
  const b5 = Math.round(b * 31 / 255) & 0x1F;
  
  const rgb565 = (r5 << 11) | (g6 << 5) | b5;
  
  buffer[bufferIndex++] = rgb565 & 0xFF;
  buffer[bufferIndex++] = (rgb565 >> 8) & 0xFF;
}

Expected Fix

The toBuffer('raw', { pixelFormat: 'RGB16_565' }) method should:

  1. Respect the pixelFormat option
  2. Return a buffer with the correct size (width × height × 2 bytes)
  3. Contain actual RGB565-encoded pixel data
  4. Work consistently with the canvas pixel format configuration

Additional Context

This issue is critical for embedded applications that need to write directly to framebuffers in RGB565 format. The current behavior makes the pixelFormat option essentially useless for toBuffer() operations.

Related

  • Canvas configuration works correctly for rendering
  • getImageData() returns RGBA as expected (since it's the standard)
  • Only toBuffer() with pixelFormat option is affected

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions