Jump to content

Saving PIXI content to Image


voidmen
 Share

Recommended Posts

Guys,

We recently have new requirement that we need to save the content we rendered with pixi.js to image file like PNG.

The canvas's toDataURL method worked well at first, however  we need more than that.

 

The content (a diagram) could be zoomed and the canvas only shows part of that diagram at the time we save the image.

Also, if we zoom out and try to capture the whole diagram, the output diagram seems not so crisp, or sharp. Hope I've made my self clear.

So, my question is whether their is a way to export the whole content image with desired resolution?

 

BR,

Yeling

 

Link to comment
Share on other sites

function download_sprite_as_png(renderer, sprite, fileName) {
	renderer.extract.canvas(sprite).toBlob(function(b){
		var a = document.createElement('a');
		document.body.append(a);
		a.download = fileName;
		a.href = URL.createObjectURL(b);
		a.click();
		a.remove();
	}, 'image/png');
}

Assuming you have instance of PIXI.Application in variable app, you can call the function using app.renderer as the first argument.

Tested in google-chrome on linux.

HTH

Link to comment
Share on other sites

9 hours ago, voidmen said:

Guys,

We recently have new requirement that we need to save the content we rendered with pixi.js to image file like PNG.

The canvas's toDataURL method worked well at first, however  we need more than that.

 

The content (a diagram) could be zoomed and the canvas only shows part of that diagram at the time we save the image.

Also, if we zoom out and try to capture the whole diagram, the output diagram seems not so crisp, or sharp. Hope I've made my self clear.

So, my question is whether their is a way to export the whole content image with desired resolution?

 

BR,

Yeling

 

yes, you can but it might be issue with performance, maybe worth try to use worker instead. you gotta know you cant save the picture directly to your local file system

Link to comment
Share on other sites

9 hours ago, Vitalije said:

function download_sprite_as_png(renderer, sprite, fileName) {
	renderer.extract.canvas(sprite).toBlob(function(b){
		var a = document.createElement('a');
		document.body.append(a);
		a.download = fileName;
		a.href = URL.createObjectURL(b);
		a.click();
		a.remove();
	}, 'image/png');
}

Assuming you have instance of PIXI.Application in variable app, you can call the function using app.renderer as the first argument.

Tested in google-chrome on linux.

HTH

you won't believe the code I wrote to achieve this... so what exactly is the "sprite" we pass here? The container/sprite we wish to save?

Link to comment
Share on other sites

14 hours ago, Jammy said:

you won't believe the code I wrote to achieve this... so what exactly is the "sprite" we pass here? The container/sprite we wish to save?

Can you elaborate a little bit more about how you did this?

I'm trying out the above solution.

Link to comment
Share on other sites

23 hours ago, Vitalije said:

function download_sprite_as_png(renderer, sprite, fileName) {
	renderer.extract.canvas(sprite).toBlob(function(b){
		var a = document.createElement('a');
		document.body.append(a);
		a.download = fileName;
		a.href = URL.createObjectURL(b);
		a.click();
		a.remove();
	}, 'image/png');
}

Assuming you have instance of PIXI.Application in variable app, you can call the function using app.renderer as the first argument.

Tested in google-chrome on linux.

HTH

Thanks Vitalije, 

But the above method doesn't work. It ONLY exports content currently visible on canvas as image.

I've tried using my content root or null as argument for the sprite parameter, both the same. 

Did I get it wrong?

 

 

Link to comment
Share on other sites

Quote

 

I've tried using my content root or null as argument for the sprite parameter, both the same. 

Did I get it wrong?

 

You should pass any sprite that you wish to save as image as second parameter to the above function. The sprite can be of any size you want. I have tested with the sprite image of the RPGMaker map which was made of 40 by 41 squared tiles 48px each. Total dimension of map was 1920x1968.

If you pass null or stage it will save only what is visible on the screen. Instead you should pass sprite or container that you want to save. I am not sure but maybe the root container (app.stage) has defined mask by default to exclude all pixels out of visible part of canvas. I have also noticed that canvas.toDataURL has some limits on the size of generated url.  But if you first convert it to Blob and then use URL.createObjectURL(blob) it allows generating large images.

HTH Vitalije

Link to comment
Share on other sites

21 hours ago, Vitalije said:

You should pass any sprite that you wish to save as image as second parameter to the above function. The sprite can be of any size you want. I have tested with the sprite image of the RPGMaker map which was made of 40 by 41 squared tiles 48px each. Total dimension of map was 1920x1968.

If you pass null or stage it will save only what is visible on the screen. Instead you should pass sprite or container that you want to save. I am not sure but maybe the root container (app.stage) has defined mask by default to exclude all pixels out of visible part of canvas. I have also noticed that canvas.toDataURL has some limits on the size of generated url.  But if you first convert it to Blob and then use URL.createObjectURL(blob) it allows generating large images.

HTH Vitalije

Wow. This really worked! I'm able to save the whole diagram as image with this solution.

Two remaining issues:

The root container that holds all the diagram elements are transparent, the background color is set on the renderer. So the exported images have transparent background. which is weird.

Also, the image is a little blurry.

Not sure how I can fix them. Their seems no background color property on the Container class.

Link to comment
Share on other sites

Regarding the blurriness of the exported image I can't say anything without looking in your actual sprite. Maybe you have some blur filter, or maybe you have scaled the original sprite content with some anti-aliasing flag set. Try to export your sprite at its normal size without scaling. (PIXI.Sprite will scale itself if you set width or height) 

Link to comment
Share on other sites

1 hour ago, Vitalije said:

Regarding the blurriness of the exported image I can't say anything without looking in your actual sprite. Maybe you have some blur filter, or maybe you have scaled the original sprite content with some anti-aliasing flag set. Try to export your sprite at its normal size without scaling. (PIXI.Sprite will scale itself if you set width or height) 

Will try it out

Link to comment
Share on other sites

1 hour ago, Vitalije said:

Just add colored rectangle as the first child of your container to be exported, and later add the rest  of your sprites.

Well, the root container doesn't actually have a size. As we move the diagram elements around its bounding rectangle changes and the output image size changes.

It will be quite verbose if we want to keep a rectangle as the background when diagram shape changes.

 

My thoughts is to hack the WebGLExtract.canvas method to accept a background color parameter and set it as the internal canvas color. Sounds viable?

Link to comment
Share on other sites

Finally, I hacked the canvas methods and used double buffer to draw a container onto a canvas with background.

The below code did the trick:

 

const canvasBuffer = new PIXI.CanvasRenderTarget(width, height);
            const background = new PIXI.CanvasRenderTarget(width, height);

            if (textureBuffer) {
                // bind the buffer
                renderer.bindRenderTarget(textureBuffer);

                // set up an array of pixels
                const webglPixels = new Uint8Array(BYTES_PER_PIXEL * width * height);

                // read pixels to the array
                const gl = myRenderer.gl;

                gl.readPixels(
                    frame.x * resolution,
                    frame.y * resolution,
                    width,
                    height,
                    gl.RGBA,
                    gl.UNSIGNED_BYTE,
                    webglPixels,
                );

                // canvasBuffer.context.fillStyle = 'blue';
                background.context.fillStyle = '#' + visConfig.backgroundColor.toString(16);
                background.context.fillRect(0, 0, width, height);

                // add the pixels to the canvas
                const canvasData = canvasBuffer.context.getImageData(0, 0, width, height);

                canvasData.data.set(webglPixels);
                // canvasBuffer.context.drawImage(canvasData.data, 0, 0);
                canvasBuffer.context.putImageData(canvasData, 0, 0);
                background.context.drawImage(canvasBuffer.canvas, 0, 0);
                // pulling pixels
                if (flipY) {
                    background.context.scale(1, -1);
                    background.context.drawImage(background.canvas, 0, -height);
                }
            }

            // send the canvas back..
            return background.canvas;

 

Link to comment
Share on other sites

  • 1 year later...
On 6/21/2017 at 8:30 AM, Vitalije said:

function download_sprite_as_png(renderer, sprite, fileName) {
	renderer.extract.canvas(sprite).toBlob(function(b){
		var a = document.createElement('a');
		document.body.append(a);
		a.download = fileName;
		a.href = URL.createObjectURL(b);
		a.click();
		a.remove();
	}, 'image/png');
}

Assuming you have instance of PIXI.Application in variable app, you can call the function using app.renderer as the first argument.

Tested in google-chrome on linux.

HTH

This works, but crashes on larger Containers. (Uncaught TypeError: Failed to execute 'createObjectURL' on 'URL': No function was found that matched the signature provided.)

I will try to use a webworker and report back later. Any other advice is welcome, of course!

Link to comment
Share on other sites

  • 5 years later...
On 6/25/2017 at 7:26 PM, voidmen said:

Can you give some advice?

Maybe this other alternative is better in terms of performance:
 

import { base64StringToBlob } from 'blob-util';

const SHARE_EXTENSION = 'png';
const SHARE_FORMAT = `image/${SHARE_EXTENSION}`;

const saveImg = (title, imgBlob) => {
  const createEl = document.createElement('a');
  const fileUrl = URL.createObjectURL(imgBlob);
  createEl.href = fileUrl;
  createEl.download = title;
  createEl.click();
  createEl.remove();
  URL.revokeObjectURL(fileUrl);
};

const onDownload = async () => {
  const base64 = await app.renderer.extract.base64(app.stage, SHARE_FORMAT);
  const blob = base64StringToBlob(
    base64.replace(`data:${SHARE_FORMAT};base64,`, ''),
    SHARE_FORMAT
  );
  saveImg(`image.${SHARE_EXTENSION}`, blob);
};

Happy Coding! <3

Link to comment
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

Loading...
 Share

  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...