Automattic / Automattic/node-canvas

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

Open
#2,535 0 comments 0 reactions 0 assignees View on GitHub
Bug
Dominant language
JavaScript
Stars
10.7k
Forks
1.2k
Avg merge
4d 8h
Merged PRs (30d)
1

Description

# 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:
```javascript
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
```javascript
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
```javascript
// 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

Contributor guide

No contributing guide indexed for this repository

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.