简体   繁体   English

如何在NodeJS中使用fabricjs从画布渲染GIF?

[英]How to render a GIF from canvas using fabricjs in NodeJS?

I'm using fabric.js to design an Image editor.我正在使用fabric.js 来设计一个图像编辑器。 I'm able to convert the canvas to JSON object and send it to the backend.我能够将画布转换为 JSON 对象并将其发送到后端。

In the backend I'm able to render the Image from NodeJS server to view it as a static image.在后端,我可以从 NodeJS 服务器渲染图像以将其作为静态图像查看。

Here is my front end code:这是我的前端代码:

var json = canvas.toJSON();

The json object is sent to server. json 对象被发送到服务器。

Code in my backend server to render the image via URL.在我的后端服务器中编写代码以通过 URL 呈现图像。

app.get('/image', function(req, res) {

let data = {"version":"4.6.0","objects":[{"type":"rect","version":"4.6.0","originX":"left","originY":"top","left":87,"top":24,"width":20,"height":20,"fill":"blue","stroke":null,"strokeWidth":1,"strokeDashArray":null,"strokeLineCap":"butt","strokeDashOffset":0,"strokeLineJoin":"miter","strokeUniform":false,"strokeMiterLimit":4,"scaleX":16.48,"scaleY":16.48,"angle":0,"flipX":false,"flipY":false,"opacity":1,"shadow":null,"visible":true,"backgroundColor":"","fillRule":"nonzero","paintFirst":"fill","globalCompositeOperation":"source-over","skewX":0,"skewY":0,"rx":0,"ry":0},{"type":"textbox","version":"4.6.0","originX":"left","originY":"top","left":148,"top":134,"width":237,"height":45.2,"fill":"rgb(0,0,0)","stroke":null,"strokeWidth":1,"strokeDashArray":null,"strokeLineCap":"butt","strokeDashOffset":0,"strokeLineJoin":"miter","strokeUniform":false,"strokeMiterLimit":4,"scaleX":1,"scaleY":1,"angle":0,"flipX":false,"flipY":false,"opacity":1,"shadow":null,"visible":true,"backgroundColor":"","fillRule":"nonzero","paintFirst":"fill","globalCompositeOperation":"source-over","skewX":0,"skewY":0,"fontFamily":"Fontdiner Swanky","fontWeight":"normal","fontSize":40,"text":"Demo Text","underline":false,"overline":false,"linethrough":false,"textAlign":"left","fontStyle":"normal","lineHeight":1.16,"textBackgroundColor":"","charSpacing":0,"styles":{},"direction":"ltr","path":null,"pathStartOffset":0,"pathSide":"left","minWidth":20,"splitByGrapheme":false}]}

res.writeHead(200, { 'Content-Type': 'image/png' });

var canvas = new fabric.StaticCanvas(null, { width: 800, height: 400 });

canvas.loadFromJSON(data, function() {
    canvas.renderAll();

    var stream = canvas.createPNGStream();
    stream.on('data', function(chunk) {
        res.write(chunk);
    });
    stream.on('end', function() {
        res.end();
    });
  });


});

Here is how the image renders这是图像的渲染方式

在此处输入图片说明

I'm little confused on how to send data to backend when it comes to GIFs.当涉及到 GIF 时,我对如何将数据发送到后端有点困惑。

I was able to add GIF on to the canvas using gifuct-js package.我能够使用gifuct-js包将 GIF 添加到画布上。 When I convert the canvas to JSON object, the size is more than 10mb.当我将画布转换为 JSON 对象时,大小超过 10mb。 I felt this was not the right way to pass information to backend.我觉得这不是将信息传递给后端的正确方式。 Also, how do I render it from backend?另外,我如何从后端渲染它?

You may avoid this entire problem (and save some bandwidth) by doing the rendering on the client side.您可以通过在客户端进行渲染来避免整个问题(并节省一些带宽)。 The function below is just enough for that:下面的函数就足够了:

function render(canvas, mimeType = 'image/png') {
    const target = document.createElement('canvas');
    target.width = canvas.getWidth();
    target.height = canvas.getHeight();
    
    var wasInteractive = canvas.interactive;
    try {
        // kludge to disable rendering interactive elements
        canvas.interactive = false;
        canvas.renderCanvas(target.getContext('2d'), canvas.getObjects());
    } finally {
        canvas.interactive = wasInteractive;
    }

    return new Promise((accept, reject) => {
        try {
            target.toBlob(blob => accept(blob), mimeType);
        } catch (e) {
            reject(e);
        }
    });
}

The above will return a promise resolving to a Blob , which you can then for example offer for download .以上将返回一个解析为Blob的承诺,然后您可以例如提供下载 Be aware, however, that a privacy-conscious browser (like Firefox) may refuse to convert canvas contents into a Blob in order to thwart fingerprinting attempts: unlocking the functionality may require interacting with the page first and/or answering a browser prompt.但是请注意,注重隐私的浏览器(如 Firefox)可能会拒绝将画布内容转换为Blob以阻止指纹尝试:解锁功能可能需要首先与页面交互和/或回答浏览器提示。 Browsers may also refuse to render canvas contents if any of the images are not cross-origin clean .如果任何图像不是cross-origin clean ,浏览器也可能拒绝呈现画布内容。


If you insist on rendering the canvas server-side though, you should have noticed the canvas data only contains URIs of the images, so the issue becomes whether the server will be able to resolve them.如果你坚持在服务器端渲染画布,你应该已经注意到画布数据只包含图像的 URI,所以问题就变成了服务器是否能够解决它们。 If all the URIs point to resources downloadable from the public Internet, this should not be a problem: the server can just download the images itself.如果所有 URI 都指向可从公共 Internet 下载的资源,这应该不是问题:服务器可以自己下载图像。 Server-side fabric.js relies on JSDOM as the DOM implementation: this library defines a class, ResourceLoader , which is responsible for resolving URIs into resources.服务器端 fabric.js 依赖JSDOM作为 DOM 实现:这个库定义了一个类ResourceLoader ,它负责将 URI 解析为资源。 By default, the DOM environment used by fabric.js is configured to allow fetching external images.默认情况下,fabric.js 使用的 DOM 环境配置为允许获取外部图像。

On the other hand, if some of the images are file uploads from the local computer, their contents will have to be bundled with the uploaded canvas data.另一方面,如果某些图像是从本地计算机上传的文件,则它们的内容必须与上传的画布数据捆绑在一起。

You may for example try something like:例如,您可以尝试以下操作:

function* walkObjects(data) {
    for (const obj of data.objects) {
        yield obj;
        if (obj.type === 'group')
            yield* walkObjects(obj);
    }
}

function resolveBlob(blobURI) {
    if (!/^blob:/.test(blobURI))
        return null;

    /* look up in a private cache of blob URIs */
}

const formData = new FormData;
const data = canvas.toObject();
formData.append('canvas', JSON.stringify(data));

let counter = 0;
for (const obj of walkObjects(data)) {
    if (obj.type !== 'image')
        continue;
    const blob = resolveBlob(obj.src);
    if (!blob)
        continue;
    formData.append('bundle' + counter, blob);
    obj.src = 'example:attached/bundle' + counter;
    counter++;
}

const response = await fetch('/image', {
    method: 'post',
    data: formData,
});

/* e.g. convert into a Blob URI and attach it to <img> */

On the server side, you can supply your own ResourceLoader , so that the substituted URLs resolve correctly, and tell fabric.js to use it .在服务器端,您可以提供自己的ResourceLoader ,以便替换的 URL 正确解析,并告诉 fabric.js 使用它

What you're trying to achieve sounds reasonable, but you went down the wrong path.你想要达到的目标听起来很合理,但你走错了路。

You should load the elements/images/gifs/whatevs all by themselves and process them afterwards in the canvas.您应该自己加载元素/图像/gifs/whatevs,然后在画布中处理它们。

And that already is the answer to your question: don't save the canvas but the images and when needed load the images onto the canvas.这已经是您问题的答案:不要保存画布,而是保存图像,并在需要时将图像加载到画布上。

If you need to create new elements for the canvas save each element on its own and load it appropriately, don't save the full canvas.如果您需要为画布创建新元素,请单独保存每个元素并适当加载,不要保存完整的画布。

Bonus: you get layers for free on your image editor奖励:您可以在图像编辑器上免费获得图层

You need to split the GIF into individual frames and then pass it on to fabric to render/animate.您需要将 GIF 拆分为单独的帧,然后将其传递给织物以进行渲染/动画处理。

Code found here might be useful.此处找到的代码可能有用。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM