问题
Why is the imageData that I get from WebGLRenderingContext.readPixels() upside down?
I try to do the folowing:
var gl = renderer.domElement.getContext("webgl")
var pixels = new Uint8Array(gl.drawingBufferWidth * gl.drawingBufferHeight * 4);
gl.readPixels(0, 0, gl.drawingBufferWidth, gl.drawingBufferHeight, gl.RGBA, gl.UNSIGNED_BYTE, pixels);
var imageData = new ImageData(Uint8ClampedArray.from(pixels), gl.drawingBufferWidth, gl.drawingBufferHeight);
ctx.putImageData(imageData, 0, 0);
but the result is an image that is mirrored along the x-axis (i.e.: flipped upside down).
I've also tried to use scale after ctx.putImageData like this:
ctx.scale(1, -1);
But no results. Reversing the pixels also doesn't work.
By now I understand that putImageData() uses coordinates that start from top left, and readPixels() starts from bottom left.
Does anyone have suggestions on how to flip the image or avoid the problem altogether?
回答1:
If you're going to copy to a 2d canvas to flip you might as well skip the readPixels. Just use drawImage
var dstX = 0;
var dstY = 0;
var dstWidth = ctx.canvas.width;
var dstHeight = ctx.canvas.height;
ctx.drawImage(gl.canvas, dstX, dstY, dstWidth, dstHeight);
The browser will do the right thing and the result will be right side up.
Example:
var gl = document.querySelector("#webgl").getContext("webgl");
var ctx = document.querySelector("#two_d").getContext("2d");
// fill webgl canvas with red on top and blue on bottom
gl.enable(gl.SCISSOR_TEST);
for (var y = 0; y < 15; ++y) {
var v = y / 14;
gl.scissor(0, y * 10, 300, 10);
gl.clearColor(v, 0, 1 - v, 1);
gl.clear(gl.COLOR_BUFFER_BIT);
}
// copy it to 2d canvas
var dstX = 0;
var dstY = 0;
var dstWidth = ctx.canvas.width;
var dstHeight = ctx.canvas.height;
ctx.drawImage(gl.canvas, dstX, dstY, dstWidth, dstHeight);
canvas {
margin: 1em;
border: 1px solid black;
}
<canvas id="webgl"></canvas>
<canvas id="two_d"></canvas>
If you really did want to call gl.readPixels for some reason (you had no intent of every putting them in a 2d canvas, then you can just flip the bytes
var width = gl.drawingBufferWidth;
var height = gl.drawingBufferHeight
var pixels = new Uint8Array(width * height * 4);
gl.readPixels(0, 0, width, height, gl.RGBA, gl.UNSIGNED_BYTE, pixels);
var halfHeight = height / 2 | 0; // the | 0 keeps the result an int
var bytesPerRow = width * 4;
// make a temp buffer to hold one row
var temp = new Uint8Array(width * 4);
for (var y = 0; y < halfHeight; ++y) {
var topOffset = y * bytesPerRow;
var bottomOffset = (height - y - 1) * bytesPerRow;
// make copy of a row on the top half
temp.set(pixels.subarray(topOffset, topOffset + bytesPerRow));
// copy a row from the bottom half to the top
pixels.copyWithin(topOffset, bottomOffset, bottomOffset + bytesPerRow);
// copy the copy of the top half row to the bottom half
pixels.set(temp, bottomOffset);
}
Example:
var gl = document.querySelector("#webgl").getContext("webgl");
var ctx = document.querySelector("#two_d").getContext("2d");
// fill webgl canvas with red on top and blue on bottom
gl.enable(gl.SCISSOR_TEST);
for (var y = 0; y < 15; ++y) {
var v = y / 14;
gl.scissor(0, y * 10, 300, 10);
gl.clearColor(v, 0, 1 - v, 1);
gl.clear(gl.COLOR_BUFFER_BIT);
}
var width = gl.drawingBufferWidth;
var height = gl.drawingBufferHeight
var pixels = new Uint8Array(width * height * 4);
gl.readPixels(0, 0, width, height, gl.RGBA, gl.UNSIGNED_BYTE, pixels);
var halfHeight = height / 2 | 0; // the | 0 keeps the result an int
var bytesPerRow = width * 4;
// make a temp buffer to hold one row
var temp = new Uint8Array(width * 4);
for (var y = 0; y < halfHeight; ++y) {
var topOffset = y * bytesPerRow;
var bottomOffset = (height - y - 1) * bytesPerRow;
// make copy of a row on the top half
temp.set(pixels.subarray(topOffset, topOffset + bytesPerRow));
// copy a row from the bottom half to the top
pixels.copyWithin(topOffset, bottomOffset, bottomOffset + bytesPerRow);
// copy the copy of the top half row to the bottom half
pixels.set(temp, bottomOffset);
}
// This part is not part of the answer. It's only here
// to show the code above worked
// copy the pixels in a 2d canvas to show it worked
var imgdata = new ImageData(with, height);
imgdata.data.set(pixels);
ctx.putImageData(imgdata, 0, 0);
canvas {
margin: 1em;
border: 1px solid black;
}
<canvas id="webgl"></canvas>
<canvas id="two_d"></canvas>
回答2:
You can flip pixels of ImadaData directly like this.
const imageData = new ImageData(Uint8ClampedArray.from(pixels),gl.drawingBufferWidth,gl.drawingBufferHeight);
const w = imageData.width, h = imageData.height;
const data = imageData.data;
Array.from({length: h}, (val, i) => data.slice(i * w * 4, (i + 1) * w * 4))
.forEach((val, i) => data.set(val, (h - i - 1) * w * 4));
回答3:
I don't know a webgl way to flip readPixels, and I suspect there is indeed a way to "avoid the problem altogether", but since you seem to draw it on a 2DContext anyway, here is a way to flip your putImageData.
Since putImageData is not affected by context's transforms, simply doing ctx.scale(1,-1); ctx.putImageData() won't work.
You'll need to putImageData, then flip its transforms, before drawing the canvas on itself.
Use globalCompositeOperation = 'copy' if you have transparency.
function flip(imageData){
var ctx = flipped.getContext('2d');
flipped.width = imageData.width;
flipped.height = imageData.height;
// first put the imageData
ctx.putImageData(imageData, 0,0);
// because we've got transparency
ctx.globalCompositeOperation = 'copy';
ctx.scale(1,-1); // Y flip
ctx.translate(0, -imageData.height); // so we can draw at 0,0
ctx.drawImage(flipped, 0,0);
// now we can restore the context to defaults if needed
ctx.setTransform(1,0,0,1,0,0);
ctx.globalCompositeOperation = 'source-over';
}
/* remaining is just for the demo */
var ctx = normal.getContext('2d');
var img = new Image();
img.crossOrigin = 'anonymous';
img.onload = getImageData;
img.src = "https://dl.dropboxusercontent.com/s/4e90e48s5vtmfbd/aaa.png";
function getImageData(){
normal.width = this.width;
normal.height = this.height;
ctx.drawImage(this, 0,0);
imageData = ctx.getImageData(0,0,this.width, this.height);
flip(imageData);
}
canvas{border: 1px solid}
body{background: ivory;}
<canvas id="normal"></canvas>
<canvas id="flipped"></canvas>
来源:https://stackoverflow.com/questions/41969562/how-can-i-flip-the-result-of-webglrenderingcontext-readpixels