I work with webcodecs and have found that when I manually convert an NV12 format to an RGB format, I get slightly different values than if I simply write the VideoFrame to a canvas and read the imageData.
The ImageData from the Canvas [87, 87, 39, 255, 86, 86, 39, 255, 85, 85, 39, 255, 83, 84, 39, 255, 81, 83, 39, 255, 79, 82, 38, 255, 77, 81, 37, 255, 76, 80, 36, 255, 79, 83, ... ]
The ImageData from manuel converting the NV12-Format to the RGB-Format: [94, 101, 62, 255, 94, 101, 62, 255, 95, 102, 63, 255, 97, 103, 64, 255, 97, 103, 64, 255, 98, 104, 66, 255, 100, 106, 68, 255, 101, 108, 69, 25, ...]
Can anyone tell me how these differences come about or how the Canvas converts a YUV format to an RGB format?
Here is the code for the two different options:
- draw the image on a canvas and get the ImageData from the Canvas
this.context.drawImage(frame, 0,0, this.canvas.width, this.canvas.height);
let imageData = this.context!.getImageData(0,0,this.canvas.width,this.canvas.height);
- Write the content of a VideoFrame into a Array and convert the Array to an RGB-Format.
...
let imageData = convertToImageData(videoFrame);
...
public async convertToImageData(frame) {
let buffer : Uint8ClampedArray = new Uint8ClampedArray(frame.allocationSize());
await frame.copyTo(buffer)
let imageData = await this.convertNV12ToRGB(buffer)
return imageData;
}
private convertNV12ToRGB(buffer : Uint8ClampedArray) {
// Y should be from 0-921599 (1280x720)
// Cr & Cb should be from 921600 - 1382399 interleaved
// NOTE: Solution is slow: 1280 * 720; 1 Minute -> 44 Seconds full convert.
let imageData : ImageData = this.context!.getImageData(0, 0, 1280, 720);
let pixels : Uint8ClampedArray = imageData.data;
let row : number = 0;
let col : number = 0;
const plane2Start = 1280 * 720; //Hier starten die Chroma-Werte
for (row = 0; row < 720; row++) {
const p2row = (row % 2 === 0 ? row / 2 : row / 2 - 0.5) * 1280; //Hälfte der Row
let cr = 0;
let cb = 0;
const rowOffset : number = row * 1280;
for (col = 0; col < 1280; col++) {
const indexY = rowOffset + col;
let y = buffer[indexY];
if (col % 2 === 0) {
const indexCr = plane2Start + p2row + (col);
const indexCb = indexCr + 1;
cr = buffer[indexCr];
cb = buffer[indexCb];
}
this.yuvToRgb(y, cr, cb, row, col, pixels);
}
}
return imageData;
}
private yuvToRgb(y : number, u : number, v : number, row : number, col : number, pixels : Uint8ClampedArray) {
y -= 16;
u -= 128;
v -= 128;
let r = 1.164 * y + 1.596 * v;
let g = 1.164 * y - 0.392 * u - 0.813 * v;
let b = 1.164 * y + 2.017 * u;
const index = (row * 1280 + col) * 4;
pixels[index] = r;
pixels[index + 1] = g;
pixels[index + 2] = b;
pixels[index + 3] = 255;
} ```