How can I flip the result of WebGLRenderingContext.readPixels()?

怎甘沉沦 提交于 2019-12-10 22:13:18

问题


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

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!