【问题标题】:How can I set an HTML5 canvas ImageData from an XMLHttpRequest response?如何从 XMLHttpRequest 响应中设置 HTML5 画布 ImageData?
【发布时间】:2016-07-13 14:52:10
【问题描述】:

我想使用 XMLHttpRequest 加载 png 文件,然后使用响应在画布对象中设置图像数据,从而完全消除对 Image 对象的需要并直接访问 ImageData。到目前为止,我的代码如下所示:

var xhr = new XMLHttpRequest();
var context = document.createElement("canvas").getContext("2d"); // the context for the image we are loading
var display = document.getElementById("display").getContext("2d"); // the context of the canvas element in the html document

function load(event) {
  var imagedata = context.createImageData(64, 64); // 64 = w, h of image
  imagedata.data.set(new Uint8ClampedArray(this.response)); // the response of the load event
  context.putImageData(imagedata,0,0); // put the image data at the top left corner of the canvas

  display.drawImage(context.canvas, 0, 0, 64, 64, 0, 0, 64, 64); // draws a bunch of jumbled up pixels from my image in the top of my display canvas
}

xhr.addEventListener("load", load);
xhr.open("GET", "myimage.png");
xhr.responseType = "arraybuffer";
xhr.send(null);

我在这里做错了什么?将响应中的 ArrayBuffer 转换为 Uint8ClampedArray 是否有问题?我应该使用不同的数组类型吗?是 XMLHttpRequest 吗?这可能吗?

【问题讨论】:

    标签: javascript html xmlhttprequest html5-canvas putimagedata


    【解决方案1】:

    通过 XMLHttpRequest 加载图片

    图像文件不是像素数组

    你得到的数据不是像素数组,而是图像数据。您可以直接读取数据并对其进行解码,但这是一项繁重的工作,png 有许多不同的内部格式和压缩方法。当所有的代码都已经在浏览器中可用时,为什么还要麻烦呢。

    通常我会把它留给浏览器来完成所有的获取,但是因为图像上没有进度事件,并且游戏可能需要大量的图像数据,所以我创建了这个来处理加载问题并显示有意义的进度显示.它的作用与您尝试做的相同。

    加载数据后,您需要让浏览器为您解码。为此,您需要将拥有的数据转换为 DataURL。我在函数 arrayToImage 中执行此操作,该函数将类型化数组转换为具有适当图像标题的数据 url。

    然后只需创建图像并将源设置为数据 URL。它相当难看,因为它需要您创建数据缓冲区,然后是 url 字符串,然后浏览器制作另一个副本以最终获取图像。 (使用了太多内存)如果您希望它作为 imageData 数组,您需要将图像渲染到画布并从那里获取数据。

    带有(真实)进度事件的图像加载示例

    下面是代码,如果图片不允许跨站访问就会失败,唯一的好处就是可以获取进度事件,包含在sn-p中。

    // creates an image from a binary array
    // buf   : is the image as an arrayBuffer
    // type  : is the mime image type "png", "jpg", etc...
    // returns a promise that has the image
    function arrayToImage(buf, type) {
        // define variables
        var url, chars, bWord, i, data, len, count, stream, wordMask, imagePromise;
        // define functions
        imagePromise = function (resolve, reject) { // function promises to return an image
            var image = new Image(); // create an image
            image.onload = function () { // it has loaded
                resolve(image); // fore fill the promise
            }
            image.onerror = function () { // something rotten has happened
                reject(image); // crossing the fingers
            }
            image.src = url; // use the created data64URL to ceate the image
        }
    
        wordMask = 0b111111; // mask for word base 64 word
        stream = 0; // to hold incoming bits;
        count = 0; // number of bits in stream;
        // 64 characters used to encode the 64 values of the base64 word
        chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
    
        data = new Uint8Array(buf); // convert to byte array
        len = data.byteLength; // get the length;
        url = 'data:image/' + type.toLowerCase() + ';base64,'; // String to hold the image URL
    
        // get each byte and put it on the bit stream
        for (i = 0; i < len; i++) {
            stream |= data[i]; // add byte to bit stream
            count += 8; // add the number of bits added to stream
            if (count === 12) { // if there are two 6bit words on the stream
                url += chars[(stream >> 6) & wordMask] + chars[stream & wordMask]; // encode both words and add to base 64 string
                stream = 0; // stream is empty now so just zero
                count = 0; // no bits on the stream
            } else {
                url += chars[(stream >> (count - 6)) & wordMask]; // encode top 6 bits and add to base64 string
                count -= 6; //decrease the bit count by the 6 removed bits
                stream <<= 8; // make room for next 8 bits
            }
        }
        if (count > 0) { // there could be 2 or 4 remaining bits
            url += chars[(stream >> (count + 2)) & wordMask]; // shift them  back to B64 word size and encode
        }
        // data url constructed for image so lets promise to create it
        return new Promise(imagePromise); // return the promise
    }
    // loads an image via ajax providing progress data
    // WARNING cross domain images will fail if they have a CORS header prohibiting your domain from access
    // filename : url of the image file
    // progress : progress call back. This is called on progress events
    // returns a promise of an image
    var loadImage = function(filename,progress){
        // declare variables
        var imagePromise;
        // declare functions
        imagePromise = function(resolve, reject){  // promise an image
            // decalare vars;
            var ajax, image, load, failed;
            // decalare functions
            failed = function (reason) { reject("Shit happens"); } // pass on the bad news
            load = function (e) {  // handle load event
                // declare vars
                var type, loaded;
                // decalare functions
                loaded = function (image) { resolve(image);} // resolve the promise of an image
    
                if(e.currentTarget.status !== 200){ // anything but OK reject the promise and say sorry
                    reject("Bummer dude! Web says '"+e.currentTarget.status+"'");
                }else{
                    type = filename.split(".").pop(); // ok we have the image as a binary get the type
                    // now convert it to an image
                    arrayToImage(e.currentTarget.response,type)  // return a promise 
                        .then(loaded)   // all good resolve the promise we made
                        .catch(failed); // failed could be a bug in the soup.
                }
            };
            
            ajax = new XMLHttpRequest();  // create the thingy that does the thing
            ajax.overrideMimeType('text/plain; charset=x-user-defined'); // no not an image. 
            ajax.responseType = 'arraybuffer';  // we want it as an arraybuffer to save space and time
            ajax.onload = load;  // set the load function
            ajax.onerror = failed; // on error
            ajax.onprogress = progress; // set the progress callback
            ajax.open('GET', filename, true);  // point to the image url
            ajax.send();  // command the broswer to wrangle this image from the server gods
        }
        return new Promise(imagePromise);
    }
    
    
    // the progress display. Something that looks profesional but still hates the status quo.
    var displayProgress = function(event){ // event is the progress event 
        // decalre vars
        var w,h,x,y,p,str;
        
        w = ctx.canvas.width;  // get the canvas size
        h = ctx.canvas.height;
        x = w/2-w/4;          // locate the progress bar
        w /= 2;              // make it in the center
        y = h/2-10;
        
        if(event.lengthComputable){   // does the progress know whats coming
            p = event.loaded/event.total;   // yes so get the fraction found
            str = Math.floor(p*100)+"%";    // make it text for the blind
        }else{
            p = event.loaded/1024;   // dont know how much is comine so get number killobytes
            str = Math.floor(p) + "k"; // for the gods
            p /= 50;   // show it in blocks of 50k
        }
    
        ctx.strokeStyle = "white";  // draw the prgress bar in black and white
        ctx.fillStyle = "black"; 
        ctx.lineWidth = 2; // give it go fast lines
        ctx.beginPath();
        ctx.rect(x,y,w,20);   // set up the draw
        ctx.fill();  // fill 
        ctx.stroke(); // then stroke
    
        ctx.fillStyle = "white";  // draw text in white 
        ctx.font = "16px verdana"; // set the font
        ctx.textAlign = "center";  // centre it
        ctx.textBaseline = "middle";  // in the middle please
        ctx.fillText(str,x+w/2,y+10);  // draw the text in the center
    
        ctx.globalCompositeOperation = "difference"; // so the text is inverted when bar ontop
        ctx.beginPath();  
        ctx.fillRect(x+3,y+3,(p*(w-6))%w,14);  // draw the bar, make sure it cycles if we dont know what coming
    
        ctx.globalCompositeOperation = "source-over"; // resore the comp state
    }
    var canvas = document.createElement("canvas");
    canvas.width = window.innerWidth;
    canvas.height = window.innerHeight;
    document.body.appendChild(canvas);
    ctx = canvas.getContext("2d");
    
        // The image name. 
        var imageName = "https://upload.wikimedia.org/wikipedia/commons/thumb/c/cb/Broadway_tower_edit.jpg/800px-Broadway_tower_edit.jpg";
        
        // lets load the image and see if all this actualy works.
        loadImage(imageName, displayProgress)
         .then(function (image) {  // well what do you know it works
             ctx.drawImage(image, 0, 0, ctx.canvas.width, ctx.canvas.height); // draw the image on the canvas to prove it
         })
        .catch(function (reason) {
            console.log(reason);  // did not load, that sucks!
        })

    【讨论】:

    • 与其使用 arraybuffer responseType 并在将其转换为 dataURL 之前自己进行解析,您可以将图像加载为 blob,然后使用 URL.createObjectURL,而无需忘记在加载后撤销它。
    • 我后来发现,即使你确实使用 XMLHttpRequest 获取了 base64 字符串,你仍然需要设置图像的 src 并且它仍然不同步。最好只设置 src 并添加一个加载事件侦听器。不过,谢谢你放纵我的好奇心。很抱歉很晚才接受。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2013-04-20
    • 2012-05-22
    • 2011-03-27
    • 1970-01-01
    • 2018-07-18
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多