3

Given a typed array such as:

const myBuffer = new Uint8Array([255,0,0,255])

How could I obtain a base64 encoded an image, to be put in the DOM?

<img src={ whatToDoHere(myBuffer) }/>

I'd like to see a 1px x 1px red image.

I'm reading a WebGL render target using gl.readPixels(). My mind melted from reading about a dozen different questions regarding this, and none of them solved my issue.

If I render directly to the canvas, I can use toDataURL on the DOM element (canvas) and get what I need. I'd like to do it from a target though, not the drawing buffer.

tanguy_k
  • 11,307
  • 6
  • 54
  • 58
pailhead
  • 5,162
  • 2
  • 25
  • 46
  • You can use [data URIs](https://css-tricks.com/data-uris/) in your `img` tag - your hard part is converting your typed array into a base-64 string that represents a character encoding such as base-64 or ASCII or UTF-8. See [the MDN documentation](https://developer.mozilla.org/en-US/docs/Web/API/WindowBase64/Base64_encoding_and_decoding#Solution_2_–_rewrite_the_DOMs_atob()_and_btoa()_using_JavaScript's_TypedArrays_and_UTF-8) on how to do this conversion. – Akshat Mahajan Jul 17 '17 at 22:20
  • Why does it have to be an image? That data array would fit perfect for a 2d canvas via ImageData. – Stephan Jul 17 '17 at 22:21
  • You need to show us what you don't understand about those questions. otherwise, this looks an awful lot like https://stackoverflow.com/q/12710001/215552 combined with https://stackoverflow.com/q/1207190/215552. – Heretic Monkey Jul 17 '17 at 22:22
  • @AkshatMahajan do you have any tips on the hard part? I cant even track down all the SE questions that i encountered, but i've tried using random custom functions, `btoa`, `TextDecoder` etc etc. – pailhead Jul 17 '17 at 22:22
  • 2
    @AkshatMahajan simply converting the encoding isn't enough. One has to add a bitmap file header and make additional adjustments such that it becomes a valid file. – Stephan Jul 17 '17 at 22:24
  • @Stephan i'm trying to format many many of these images on a page to be printed. So basically have a long scrollable page with N images. Can this be done with many canvases? – pailhead Jul 17 '17 at 22:24
  • @MikeMcCaughan I believe that this is exactly the problem (what stephan described). What i didnt understand in those other questions is how do you designate those 4 bytes to be a jpg or a png? https://stackoverflow.com/questions/12710001/how-to-convert-uint8-array-to-base64-encoded-string – pailhead Jul 17 '17 at 22:25
  • You would have to write a PNG encoder… or just put them on a canvas. Why don’t you want to do that, again? – Ry- Jul 17 '17 at 22:26
  • Can i have say, 200 different canvas elements on a page? Or is there something im not understanding with canvas? – pailhead Jul 17 '17 at 22:26
  • Yes, you can. But you don’t have to keep them around after converting them to data URIs. – Ry- Jul 17 '17 at 22:26
  • @pailhead you could create as many different canvases and format them as you like. Depending on the browser a downside could be that up- and downscaling images using CSS has a better quality than with canvases using CSS, but you could try that out. – Stephan Jul 17 '17 at 22:27
  • @Ryan I see. Well, i don't have to go to the canvas at all in that case. I could just use my webgl context to do the exact same thing. `toDataURL` works. I can call it after each render. I specifically wanted to use it on a render target though (not something that is drawn to the canvas, but is "offscreen"). The result of that is an Uint8Array. Hence the question. – pailhead Jul 17 '17 at 22:28
  • As far as I know the canvas doesn't have to be visible to generate the data URL. – Stephan Jul 17 '17 at 22:31
  • Hmm, that part i dont understand. Not being visible here means that i can push it off screen using css? Or is there another way of generating something resembling a render target in webgl? – pailhead Jul 17 '17 at 22:31
  • At this point, your question has little resemblance to the question you're asking in the comments, especially the title. Please [edit] one or both. – Heretic Monkey Jul 17 '17 at 22:33
  • Please advise on how to edit? It's not obvious, at least to me. I tried to ask the question in the most simple way i can think of. **I have A, i want B** what we're discussing here are origins of A, which i feel are irrelevant to the question? – pailhead Jul 17 '17 at 22:33
  • You can create a canvas element in JavaScript. By default it isn't visible (as long as you don't explicitly attach it to the DOM). – Stephan Jul 17 '17 at 22:34
  • Could the answer here be "not without using canvas, or writing your own png encoder"? – pailhead Jul 17 '17 at 22:35

2 Answers2

3

A solution would be to directly generate a data URL from webgl's canvas using canvas.toDataURL, which also supports image compression.

Stephan
  • 2,028
  • 16
  • 19
  • 1
    I'd like to use gl.readPixels() on a render target (not the drawing buffer). The result of `gl.readPixels()` is an `Uint8Array`. Can this be converted to an image somehow without it going back to the canvas? – pailhead Jul 17 '17 at 22:31
  • 1
    Directly to an image would be technically possible but probably unnecessarily complicate. In case of need you could still use a second canvas on which you paint/copy the image data or the render target and use it to generate the data url. – Stephan Jul 17 '17 at 22:36
  • Thanks, this actually does seem like the most straight forward solution. – pailhead Jul 17 '17 at 22:39
  • 1
    Are you using three.js? Isn't `gl` already the webgl context of an (offscreen) canvas? – Stephan Jul 17 '17 at 22:42
  • Yes but it's not offscreen, usually you add to the dom. In this case I probably don't want to do that right? I can use the canvas that three creates, just not attach it. – pailhead Jul 17 '17 at 22:56
  • Depending on your situation an option would be to use a WebGLRenderer (which automatically creates an associated canvas) instead of a WebGLRenderTarget. – Stephan Jul 17 '17 at 23:34
  • I think this question has been answered :) but out of curiousity, if the browser is capable of encoding the image why is it limited to only this method on the canvas? – pailhead Jul 17 '17 at 23:37
  • I think transforming a canvas to data url is mainly needed for 2d content (simple drawings). 3d rendering has other use cases where it is faster to display everything in one single (large) canvas. That you can convert a 3d rendering to a data url maybe just a historic relict because 2d drawings and webgl use the same DOM element as root. – Stephan Jul 17 '17 at 23:48
  • How you put the Uint8Array into the canvas?, I don't wuite understand this solution – Mariano Argañaraz Apr 04 '19 at 15:27
  • I didn’t, I just rendered the webgl and called the method from the answer. I didn’t attach the canvas to the dom. – pailhead Jan 08 '20 at 07:27
3

This is a bit off topic. Here a solution without WebGL or the Canvas. This is very simple using a Blob:

// Small red dot image
const content = new Uint8Array([137, 80, 78, 71, 13, 10, 26, 10, 0, 0, 0, 13, 73, 72, 68, 82, 0, 0, 0, 5, 0, 0, 0, 5, 8, 6, 0, 0, 0, 141, 111, 38, 229, 0, 0, 0, 28, 73, 68, 65, 84, 8, 215, 99, 248, 255, 255, 63, 195, 127, 6, 32, 5, 195, 32, 18, 132, 208, 49, 241, 130, 88, 205, 4, 0, 14, 245, 53, 203, 209, 142, 14, 31, 0, 0, 0, 0, 73, 69, 78, 68, 174, 66, 96, 130]);

document.getElementById('my-img').src = URL.createObjectURL(
  new Blob([content.buffer], { type: 'image/png' } /* (1) */)
);
Should display a small red dot: <img id="my-img">

(1) It also works without specifying the MIME type.

tanguy_k
  • 11,307
  • 6
  • 54
  • 58