如何翻转WebGLRenderingContext.readPixels()的结果?

时间:2017-01-31 23:43:21

标签: canvas webgl

为什么我从WebGLRenderingContext.readPixels()获取的imageData是颠倒的?

我尝试做下面的事情:

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);

但结果是沿x轴镜像的图像(即:倒置翻转)。

我还试图在ctx.putImageData之后使用比例:

ctx.scale(1, -1);

但没有结果。反转像素也不起作用。

到目前为止,我知道putImageData()使用从左上角开始的坐标,readPixels()从左下角开始。

有没有人有关于如何翻转图像或完全避免问题的建议?

3 个答案:

答案 0 :(得分:4)

如果您要复制到2d画布进行翻转,您可以跳过readPixels。只需使用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);

浏览器会做正确的事情,结果将是正确的。

示例:



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);
&#13;
canvas { 
  margin: 1em;
  border: 1px solid black;
}
&#13;
<canvas id="webgl"></canvas>
<canvas id="two_d"></canvas>
&#13;
&#13;
&#13;

如果你确实想要出于某种原因想要调用gl.readPixels(你没有意图将它们放在2d画布中,那么你可以只翻转字节

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);
}

示例:

&#13;
&#13;
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);
&#13;
canvas { 
  margin: 1em;
  border: 1px solid black;
}
&#13;
<canvas id="webgl"></canvas>
<canvas id="two_d"></canvas>
&#13;
&#13;
&#13;

答案 1 :(得分:2)

你可以像这样直接翻转ImadaData的像素。

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));

答案 2 :(得分:0)

我不知道翻转readPixels的webgl方式,我怀疑确实有办法“完全避免这个问题”,但是因为你似乎无论如何都是在2DContext上绘制它,这里是一个翻转putImageData

的方法

由于putImageData不受上下文转换的影响,因此只需执行ctx.scale(1,-1); ctx.putImageData()即可。

在绘制画布之前,您需要先放置ImageData,然后翻转其变换。

如果您有透明度,请使用globalCompositeOperation = 'copy'

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>