如何从 XMLHttpRequest 响应设置 HTML5 canvas ImageData?
How can I set an HTML5 canvas ImageData from an XMLHttpRequest response?
我想使用 XMLHttpRequest 加载一个 png 文件,然后使用响应在 canvas 对象中设置图像数据,从而完全不需要 Image 对象并可以直接访问图像数据。到目前为止,我的代码如下所示:
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 吗?这可能吗?
通过 XMLHttpRequest 加载图像
图像文件不是像素阵列
您获得的数据不是像素数组,而是图像数据。您可以直接读取数据并对其进行解码,但工作量很大,png 有许多不同的内部格式和压缩方法。当所有的代码都已经在浏览器中可用时,为什么还要费心。
通常我会把它留给浏览器来完成所有的获取但是因为图像上没有进度事件并且游戏可能需要大量图像数据我创建这个来处理有意义的加载问题进度显示。它与您尝试做的一样。
加载数据后,您需要让浏览器为您解码。为此,您需要将您拥有的数据转换为 DataURL。我在函数 arrayToImage 中执行此操作,该函数将类型化数组转换为具有适当图像 header 的数据 url。
然后只需创建图像并将源设置为数据 URL。它相当丑陋,因为它需要您创建数据缓冲区,然后是 url 字符串,然后浏览器再制作一份副本以最终获取图像。 (使用太多内存)如果你想把它作为一个 imageData 数组,你需要将图像渲染到 canvas 并从那里获取数据。
使用(实际)进度事件加载示例图像
下面是代码,如果图像不允许跨站点访问,它会失败,它唯一的好处是你会得到进度事件,它包含在代码片段中。
// 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!
})
我想使用 XMLHttpRequest 加载一个 png 文件,然后使用响应在 canvas 对象中设置图像数据,从而完全不需要 Image 对象并可以直接访问图像数据。到目前为止,我的代码如下所示:
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 吗?这可能吗?
通过 XMLHttpRequest 加载图像
图像文件不是像素阵列
您获得的数据不是像素数组,而是图像数据。您可以直接读取数据并对其进行解码,但工作量很大,png 有许多不同的内部格式和压缩方法。当所有的代码都已经在浏览器中可用时,为什么还要费心。
通常我会把它留给浏览器来完成所有的获取但是因为图像上没有进度事件并且游戏可能需要大量图像数据我创建这个来处理有意义的加载问题进度显示。它与您尝试做的一样。
加载数据后,您需要让浏览器为您解码。为此,您需要将您拥有的数据转换为 DataURL。我在函数 arrayToImage 中执行此操作,该函数将类型化数组转换为具有适当图像 header 的数据 url。
然后只需创建图像并将源设置为数据 URL。它相当丑陋,因为它需要您创建数据缓冲区,然后是 url 字符串,然后浏览器再制作一份副本以最终获取图像。 (使用太多内存)如果你想把它作为一个 imageData 数组,你需要将图像渲染到 canvas 并从那里获取数据。
使用(实际)进度事件加载示例图像
下面是代码,如果图像不允许跨站点访问,它会失败,它唯一的好处是你会得到进度事件,它包含在代码片段中。
// 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!
})