大型画布的canvas.toDataURL()

10

我在使用.toDataURL()对于大画布时遇到了问题。我想用base64编码并在php文件中解码,但如果我的画布很大,则strDataURI变量为空。

我的代码:

var strDataURI = canvas.toDataURL();
strDataURI = strDataURI.substr(22, strDataURI.length);
$.post("save.php",
{ 
   str: strDataURI
};

是否有替代.toDataURL()或改变大小限制的方法?

谢谢。


哪个浏览器?还是所有浏览器都有这个问题?IE对数据URL的大小有限制。 - RichieHindle
我的浏览器是Google Chrome,但我已经在其他浏览器中进行了测试。我不知道我的画布尺寸,但它的尺寸是20000x20000像素。谢谢。 - ptCoder
4
一张20000x20000像素的图片将生成一个大小约为2GB的数据URL。编辑 等等,因为JavaScript字符串使用UTF-16,所以实际上更接近4GB。 - Pointy
1
在这种情况下,您可能会更幸运地使用_Blob_,而不是数据URI; HTMLCanvasElement.toBlob - Paul S.
我认为这个问题与<canvas>大小限制有关,就像这个链接所示https://dev59.com/0m025IYBdhLWcg3wUEOP - ptCoder
显示剩余3条评论
4个回答

18

我不确定canvas尺寸是否有限制,但数据URL的大小有浏览器限制:Data URL size limitations

你可以尝试使用Node.js + node-canvas(服务器端)重新创建画布。我一直在使用它们从canvas元素创建可打印的图像,到目前为止没有任何问题/限制使用toDataURL。

你是否在使用fabric.js库?我注意到你也在他们的论坛上发布了帖子。Fabric.js可以在Node.js中使用,并且有一个toDataURLWithMultiplier方法,它可以缩放canvas/context,允许您更改dataurl图像的大小。您可以查看该方法源代码以了解其工作原理。

编辑:

由于您正在使用fabric.js,我建议使用Node.js在服务器端处理画布到图像处理。您可以在此处找到有关如何在Node.js上使用fabric.js的更多信息:here

这是一个简单的使用Node.js和express的服务器:

var express = require('express'),
    fs = require('fs'),
    fabric = require('fabric').fabric,
    app = express(),
    port = 3000;

var allowCrossDomain = function (req, res, next) {
    res.header('Access-Control-Allow-Origin', '*');
    res.header('Access-Control-Allow-Methods', 'POST, OPTIONS');
    res.header('Access-Control-Allow-Headers', 'Content-Type');
    next();
}

app.configure(function() {
    app.use(express.bodyParser());
    app.use(allowCrossDomain);
});

app.options('/', function(req, res) {
    res.send(200);
});

app.post('/', function(req, res) {
    var canvas = fabric.createCanvasForNode(req.body.width, req.body.height);
    
    console.log('> Loading JSON ...');
    canvas.loadFromJSON(req.body.json, function() {
        canvas.renderAll();
        
        console.log('> Getting PNG data ... (this can take a while)');
        var dataUrl = canvas.toDataURLWithMultiplier('png', req.body.multiplier),
            data = dataUrl.replace(/^data:image\/png;base64,/, '');
        
        console.log('> Saving PNG to file ...');
        var filePath = __dirname + '/test.png';
        fs.writeFile(filePath, data, 'base64', function(err) {
            if (err) {
                console.log('! Error saving PNG: ' + err);
                res.json(200, { error: 'Error saving PNG: ' + err });
            } else {
                console.log('> PNG file saved to: ' + filePath);
                res.json(200, { success: 'PNG file saved to: ' + filePath });
            }
        });
    });
});

app.listen(port);
console.log('> Server listening on port ' + port);

服务器运行时,您可以向其发送数据(postData)。 服务器期望 jsonwidthheight 来重新创建画布,并使用 multiplier 缩放数据URL图像。客户端代码可能如下所示:

当服务器正在运行时,您可以将数据发送给它 (postData)。 服务器期望 jsonwidthheight 来重新创建画布,并使用 multiplier 缩放 data url 图像。客户端代码应该类似于以下内容:

var postData = {
    json: canvas.toJSON(),
    width: canvas.getWidth(),
    height: canvas.getHeight(),
    multiplier: 2
};

$.ajax({
    url: 'http://localhost:3000',
    type: 'POST',
    contentType: 'application/json; charset=utf-8',
    data: JSON.stringify(postData),
    dataType: 'json',
    success: function(data) {
        console.log(data);
    },
    error: function(err) {
        console.log(err);
    }
});

谢谢您的回复。 Node.js 只能在 Unix 服务器上运行,对吗? - ptCoder
1
您可以在Node.js下载页面上找到适用于Windows和MacOSX的安装程序。 - sn3p
是的。我的项目涉及将非常大的画布导出为jpg或png图像。但我认为画布不支持大尺寸。你能推荐我任何选项吗?我正在使用小画布,比如1000x1000(例如),然后我需要通过Imagick将画布对象缩放以转换为300 DPI。谢谢。 - ptCoder
我已经更新了我的答案,并附上了一个服务器和客户端的示例。希望能对你有所帮助。 - sn3p

4
你应该首先考虑这一点:上传的大小是有限制的。限制取决于浏览器、操作系统和服务器环境。你可以查看这篇文章:http://www.motobit.com/help/scptutl/pa98.htm 通常情况下,你可以尝试以下方法:首先我们需要一个将dataURI转换为blob的函数:
function convertDataURItoBlob(dataURI) {
        'use strict'

        var byteString,
            mimestring

        if(dataURI.split(',')[0].indexOf('base64') !== -1 ) {
            byteString = atob(dataURI.split(',')[1])
        } else {
            byteString = decodeURI(dataURI.split(',')[1])
        }

        mimestring = dataURI.split(',')[0].split(':')[1].split(';')[0]


        var content = new Array();
        for (var i = 0; i < byteString.length; i++) {
            content[i] = byteString.charCodeAt(i)
        }
        var rawContent = new Uint8Array(content),
            returnBlob = new Blob([rawContent], {type: mimestring})

        return returnBlob;

}

接下来是使用XMLHttpRequest2上传文件的函数:

function upload(blob) {
  var xhr = new XMLHttpRequest();
  xhr.open('POST', '/yourServerEndPoint', true);
  xhr.onload = function(e) { ... };

  xhr.send(blob);
}

现在,您可以将strDataURI传递给第一个函数,然后使用第二个函数上传文件。
您可以在此处深入了解XMLHTTPRequest2:http://www.html5rocks.com/en/tutorials/file/xhr2/,有关blob构造函数的信息请参见:https://developer.mozilla.org/en-US/docs/DOM/Blob

我不需要上传,我需要将我的画布转换为PNG格式。我的问题是导出画布。例如:我有一个100x100厘米、72 DPI分辨率的画布,但我需要调整大小到300 dpi。最终画布大约为30000x30000像素(100厘米* 300约为30000像素),而toDataURL函数无法工作。我认为这个问题与<canvas>大小限制有关,就像这个链接中所述https://dev59.com/0m025IYBdhLWcg3wUEOP 谢谢您的回复。 - ptCoder

2
您可以将图像分成较小的部分并单独保存,这可能是一个不错的想法。基本上,您需要编写一个类似于以下内容的函数:
var largeCanvas = document.getElementById('yourGiantCanvas').getContext('2d'),
    slice = document.createElement('canvas').getContext('2d');

slice.canvas.width = 1000;
slice.canvas.height = 1000;

for (var y=0; y < canvas.height; y+=1000){
  for (var x=0; x < canvas.width; x+=1000){
    slice.clearRect(0, 0, slice.canvas.width, slice.canvas.height);
    slice.drawImage(largeCanvas.canvas, x, y, 1000, 1000, 0, 0, 1000, 1000);

    var imagePiece = slice.canvas.toDataURL();

    //Now just save the imagePiece however you normally were planning to
    //and you can build the image again using these slices. You can create 
    //a much better user experience this way too. 
  }
}

你遇到了什么问题?这不是一个完整的工作函数,而只是一个类似于这样做的模板。 - hobberwickey
抱歉,可能这个可以工作。但我需要你的帮助。如何解析imagePiece变量或数组以创建像这样的一张图片: $.post("createImage.php", { str: imagePiece; };如何将所有的imagePieces连接起来制作一张图片,就像这个脚本: $urlUploadImages = 'images/'; $nameImage = 'test.png'; $data = base64_decode($_POST["str"]); $img = imagecreatefromstring($data); $width = imagesx($img); ... imagedestroy($img);非常感谢。 - ptCoder
我不会在php中费力地合并所有图像(除非你必须这样做,如果是的话,请查看此链接http://stackoverflow.com/questions/1719909/joining-multiple-images-into-one-with-a-php-script)。我只会以某种命名格式保存所有图像,例如ImagePiece001.png,ImagePiece002.png等...然后当你在前端加载它们时,只需创建一个大画布并将碎片拼回去即可。 - hobberwickey

1
我已更新代码,将画布分成较小的画布对象。效果很好,并添加了跟踪器:
这允许跟踪上传过程,总体而言,我认为这对用户更好。我使用PHP在稍后重新连接。
它避免了画布/浏览器大小等问题。
这是我的第一篇帖子,希望能有所帮助!
//传递文件名类型
function sliceCanvas(type, canvasId){
var largeCanvas = document.getElementById(canvasId).getContext('2d');
var slice = document.createElement('canvas').getContext('2d');

var baseSize = 500;

fileH = largeCanvas.canvas.height / baseSize;
fileW = largeCanvas.canvas.width / baseSize;

slice.canvas.width = baseSize;
slice.canvas.height = baseSize; 
count = 1;

numFiles = Math.ceil(fileH) * Math.ceil(fileW);

for (var y=0; y < largeCanvas.canvas.height; y+=baseSize){
  for (var x=0; x < largeCanvas.canvas.width; x+=baseSize){
    slice.clearRect(0, 0, slice.canvas.width, slice.canvas.height);
    slice.drawImage(largeCanvas.canvas, x, y, baseSize, baseSize, 0, 0, baseSize, baseSize);

    var imagePiece = slice.canvas.toDataURL();

    typeFinal = type + count;

    exportSlice(typeFinal, imagePiece, numFiles);
    count++;


  } 
}
}

Ajax上传:

function exportSlice(type, dataURL, fileNum){

percent = 0;
percentComplete = 0;

    $.ajax({
         type: "POST",
          url: YourServerSideFiletoSave,
          data: {image: dataURL, type: type}
        })
        .done(function( response ) {
            console.log(response);
            percent++;
            percentComplete = Math.ceil(Number(percent/fileNum*100));
           return true;
         })
          .fail(function(response) {
            console.log("Image FAILED");

            console.log(response);

            return false;

        })
        .always(function(response) {
          console.log( "Always");
        });

  }

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接