2

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);

ここで何が間違っていますか?Uint8ClampedArray への応答で ArrayBuffer を変換する際の問題ですか? 異なる配列タイプを使用する必要がありますか? それは XMLHttpRequest ですか? これは可能ですか?

4

1 に答える 1

2

XMLHttpRequest による画像のロード

画像ファイルはピクセル配列ではありません

取得するデータはピクセル配列ではなく、画像データです。データを直接読み取ってデコードすることはできますが、それは大変な作業です。png にはさまざまな内部形式と圧縮方法があります。そして、それを行うためのすべてのコードがブラウザー内で既に利用可能であるのに、なぜ気にする必要がありますか。

通常、私はすべてのフェッチをブラウザに任せますが、画像には進行状況イベントがなく、ゲームには大量の画像データが必要になる可能性があるため、意味のある進行状況表示でロードの問題を処理するためにこれを作成しました。あなたがやろうとしているのと同じことをします。

データをロードしたら、ブラウザでデコードする必要があります。そのためには、持っているデータを DataURL に変換する必要があります。型指定された配列を適​​切な画像ヘッダーを持つデータ URL に変換する関数 arrayToImage でそれを行います。

あとは、画像を作成し、ソースをデータ URL に設定するだけです。データ バッファを作成し、次に URL 文字列を作成し、ブラウザが別のコピーを作成して最終的に画像を取得する必要があるため、かなり醜いです。(メモリの使用量が多すぎます) imageData 配列として使用する場合は、イメージをキャンバスにレンダリングし、そこからデータを取得する必要があります。

(実際の) 進行状況イベントを使用した画像の読み込みの例

以下はコードです。イメージがクロス サイト アクセスを許可しない場合は失敗します。唯一の利点は、スニペットに含まれる進行状況イベントを取得できることです。

// 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!
    })

于 2016-07-13T20:13:00.163 に答える