29923

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

Question:

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?

Answer1:

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:

<pre class="snippet-code-js lang-js prettyprint-override">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); <pre class="snippet-code-css lang-css prettyprint-override">canvas { margin: 1em; border: 1px solid black; } <pre class="snippet-code-html lang-html prettyprint-override"><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:

<pre class="snippet-code-js lang-js prettyprint-override">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); <pre class="snippet-code-css lang-css prettyprint-override">canvas { margin: 1em; border: 1px solid black; } <pre class="snippet-code-html lang-html prettyprint-override"><canvas id="webgl"></canvas> <canvas id="two_d"></canvas>

Answer2:

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

Answer3:

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.

<pre class="snippet-code-js lang-js prettyprint-override">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); } <pre class="snippet-code-css lang-css prettyprint-override">canvas{border: 1px solid} body{background: ivory;} <pre class="snippet-code-html lang-html prettyprint-override"><canvas id="normal"></canvas> <canvas id="flipped"></canvas>

Recommend

  • How to increase performance using WebGL?
  • FireFox : image base64 data using canvas object not working
  • Android SurfaceView: Show Video after Images
  • Get byte[] from
  • IndexSizeError on drawImage on IE and Edge
  • Office365 authentication without login redirection
  • Using Delphi + Jedi, losing USB data when device sends it “too fast”
  • how to avoid the dependencies hell with unit test in angular 2+
  • Grouping by blank nodes
  • Reading from Windows registry in Perl [duplicate]
  • Find tangent points on a curve from a user-given point outside the curve
  • C++ Armadillo Access Triangular Matrix Elements
  • Cosmos DB succeeds and fails on randomly on the same query, saying they are cross partition when the
  • What version of Java should I use with Cassandra 2.0?
  • How to eliminate warning for passing multidimensional array as const multidimensional array?
  • change color of jstree node
  • How to add closing tag for canvas in three js rendered Canvas?
  • redirect_to root_url and return unless @user.activated
  • Validate jQuery plugin, field not required
  • Why does it draw lines in the wrong place?
  • what makes a request a new request in asp.net C#
  • Can't delete or rename original file after resizing
  • Ember.js model to be organised as a tree structure
  • D3 get axis values on zoom event
  • how to avoid repetitive constructor in children
  • System.InvalidCastException: Specified cast is not valid
  • How to get Eclipse Oxygen to run on Java 9
  • Jackson Parser: ignore deserializing for type mismatch
  • OpenGL 3.3 on Mac OSX El Capitan with LWJGL
  • OpenGL ES texture problem, 4 duplicate columns and horizontal lines (Android)
  • Read a local file using javascript
  • Control modification in presentation layer
  • Apache 2.4 and php-fpm does not trigger apache http basic auth for php pages
  • How to set/get protobuf's extension field in Go?
  • How to show dropdown in excel using jrxml (jasper api)?
  • How can I estimate amount of memory left with calling System.gc()?
  • NSLayoutConstraint that would pin a view to the bottom edge of a superview
  • How get height of the a view with gone visibility and height defined as wrap_content in xml?
  • python draw pie shapes with colour filled
  • jQuery Masonry / Isotope and fluid images: Momentary overlap on window resize