簡體   English   中英

我在從另一個canvas / image(png)中復制canvas putImageData的透明像素時遇到麻煩

[英]I'm having troubles with copying transparent pixels with canvas putImageData from another canvas/image(png)

我正在嘗試復制在stackoverflow上描述的方法。 但是我遇到了一些我不知道如何解決的問題。

我設置了jsfiddle來演示所有內容。 這是第二個jsfiddle ,僅移動和繪制了粒子。

我的問題在於繪圖,分析器顯示大約10000個粒子的drawImage占用了整個循環時間的40%。 如果不直接繪制而僅進行計算,則沒有什么會妨礙代碼執行,因此問題就出在繪制上。


有沒有辦法在沒有這些副作用的情況下使用這種技術? 目前,我向您展示了如何用圓弧創建圓形區域,但我還將png文件用於其他一些對象,它們表現出的行為完全相同。

問題:黑色重疊區域而不是透明區域,通過上方的圓圈可以看到底部圓的邊緣

(問題:黑色重疊區域而不是透明區域,可以通過上方的圓圈看到底部圓的邊緣)

我希望我能盡可能清楚地表達自己(上面的圖片非常清楚地顯示了我的問題),並感謝您的幫助。

繪制功能-最終繪制到可見的畫布。

Game.prototype.draw2 = function(interpolation, canvas, ctx, group)
{
    var canvasData = ctx.createImageData(canvas.width, canvas.height),
        cData = canvasData.data;

    for (var i = 0; i < group.length; i++)
    {
        var obj = group[i];

        if(!obj.draw)
        {
            continue;
        }

        var imagePixelData = obj.imagePixelData;

        var x = obj.previous.x + (obj.x - obj.previous.x) * interpolation;
        var y = obj.previous.y + (obj.y - obj.previous.y) * interpolation;

        for (var w = 0; w < obj.width; w++)
        {
            for (var h = 0; h < obj.height; h++)
            {
                if (x + w < canvas.width && obj.x + w > 0 &&
                    y + h > 0 && y + h < canvas.height)
                {
                    var iData = (h * obj.width + w) * 4;
                    var pData = (~~ (x + w) + ~~ (y + h) * canvas.width) * 4;

                    cData[pData] = imagePixelData[iData];
                    cData[pData + 1] = imagePixelData[iData + 1];
                    cData[pData + 2] = imagePixelData[iData + 2];
                    if (cData[pData + 3] < 100)
                    {
                        cData[pData + 3] = imagePixelData[iData + 3];
                    }

                }
            }
        }    
    }
    ctx.putImageData(canvasData, 0, 0);
};

這就是我在其他隱形畫布上准備粉紅色圓形區域的方法。

Game.prototype.constructors.Attractor.prototype.getImageData = function(context)
{
    this.separateScene = new context.constructors.Graphics(this.width, this.height, false);
    this.image = this.separateScene.canvas;
    this.separateScene.ctx.beginPath();
    this.separateScene.ctx.arc(this.radius, this.radius, this.radius, 0, 2 * Math.PI, false);
    this.separateScene.ctx.fillStyle = '#ff9b9b';
    this.separateScene.ctx.fill();
    this.separateScene.ctx.beginPath();
    this.separateScene.ctx.arc(this.radius, this.radius, this.radiusCut, 0, 2 * Math.PI, false);
    this.separateScene.ctx.fillStyle = 'rgba(255, 255, 255, 0.27)';
    this.separateScene.ctx.fill();
    this.separateScene.ctx.beginPath();
    this.separateScene.ctx.arc(this.radius, this.radius, this.coreRadius, 0, 2 * Math.PI, false);
    this.separateScene.ctx.fillStyle = '#ff64b2';
    this.separateScene.ctx.fill();
    this.imageData = this.separateScene.ctx.getImageData(0, 0, this.width, this.height);
    this.imagePixelData = this.imageData.data;
};

尋找黑色像素


@Loktar的絕佳答案是針對僅由黑色和透明像素組成的特定圖像。

在imageData中,這兩種類型的像素非常相似,因為只有它們的alpha值不同。 因此,他的代碼僅對alpha值(每個循環中的第四個)進行了應繪制檢查。

 cData[pData] = imagePixData[iData]; cData[pData + 1] = imagePixData[iData + 1]; cData[pData + 2] = imagePixData[iData + 2]; // only checking for the alpha value... if(cData[pData + 3] < 100){ cData[pData + 3] = imagePixData[iData + 3]; } 

另一方面,您正在處理彩色圖像。 因此,當對透明像素執行此部分時,並且您在該位置已經有一個彩色像素,則前三行將把現有像素轉換為透明像素的rgb值( 0,0,0 ),但保留alpha值現有像素的像素數(在您的情況下為255 )。

然后,您將得到一個黑色像素,而不是之前的彩色像素。

要解決此問題,可以將整個塊包裝在檢查當前imagePixData的不透明度的條件下,而不是檢查已經繪制的塊。

if (imagePixelData[iData+3]>150){
    cData[pData] = imagePixelData[iData];
    cData[pData + 1] = imagePixelData[iData + 1];
    cData[pData + 2] = imagePixelData[iData + 2];
    cData[pData + 3] = imagePixelData[iData + 3];
}

與白人戰斗


那些白色像素在這里是因為抗鋸齒 @Loktar的原始示例中已經存在該文件,由於其圖像的大小,其可見性幾乎沒有。

當您處理imageData時,這些偽像會變得很糟糕,因為我們只能修改每個像素,而不能在子像素上設置值。 換句話說,我們無法利用抗鋸齒功能。

這就是原始檢查中<100或我上面的解決方案中>150的目的。

在此檢查中,與alpha值相對應的最小范圍將使工件更少。 但另一方面,您的邊界將更加粗糙。

您必須自己找到合適的值,但是圓形是最差的,因為幾乎每個邊框像素都將被消除鋸齒。

改善超贊效果(又名我可以為您提供10000張彩色圖像)


您的實際實現使我考慮了可以對@Loktar解決方案進行的一些改進。

除了保留原始圖像的數據,我們還可以對每個像素進行第一個循環,並存儲一個新的imageData數組,該數組由六個插槽組成: [x, y, r, g, b ,a]

這樣,我們可以避免存儲所有我們不想要的透明像素,從而減少每次調用的迭代次數,並且還可以避免在每個循環中進行任何alpha檢查。 最后,我們甚至不需要“從圖像畫布中獲取位置像素”,因為我們為每個像素存儲了它。

這是一個帶注釋的代碼示例,作為概念證明。

 var parseImageData = function(ctx) { var pixelArr = ctx.getImageData(0, 0, ctx.canvas.width, ctx.canvas.height).data; // the width of our image var w = ctx.canvas.width; // first store our image's dimension var filtered = []; // loop through all our image's pixels for (var i = 0; i < pixelArr.length; i += 4) { // we don't want traparent or almost transparent pixels if (pixelArr[i + 3] < 250) { continue; } // get the actual xy position of our pixel var f = (i / 4) / w; var y = Math.floor(f); var x = Math.round((f - y) * w); // add the pixel to our array, with its xy positions filtered.push(x, y, pixelArr[i], pixelArr[i + 1], pixelArr[i + 2], pixelArr[i + 3]); } return filtered; }; // here we will store all our pixel arrays var images = []; // here we will store our entities var objects = []; var draw = function() { // create a new empty imageData of our main canvas var imageData = mainCtx.createImageData(mainCanvas.width, mainCanvas.height); // get the array we'll write onto var pixels = imageData.data; var width = mainCanvas.width; var pixelArray, deg = Math.PI / 180; // micro-optimizaion doesn't hurt for (var n = 0; n < objects.length; n++) { var entity = objects[n], // HERE update your objects // some fancy things by OP velY = Math.cos(entity.angle * deg) * entity.speed, velX = Math.sin(entity.angle * deg) * entity.speed; entity.x += velX; entity.y -= velY; entity.angle++; // END update // retrieve the pixel array we created before pixelArray = images[entity.image]; // loop through our pixel Array for (var p = 0; p < pixelArray.length; p += 6) { // retrieve the x and positions of our pixel, relative to its original image var x = pixelArray[p]; var y = pixelArray[p + 1]; // get the position of our ( pixel + object ) relative to the canvas size var pData = (~~(entity.x + x) + ~~(entity.y + y) * width) * 4 // draw our pixel pixels[pData] = pixelArray[p + 2]; pixels[pData + 1] = pixelArray[p + 3]; pixels[pData + 2] = pixelArray[p + 4]; pixels[pData + 3] = pixelArray[p + 5]; } } // everything is here, put the image data mainCtx.putImageData(imageData, 0, 0); }; var mainCanvas = document.createElement('canvas'); var mainCtx = mainCanvas.getContext('2d'); mainCanvas.width = 800; mainCanvas.height = 600; document.body.appendChild(mainCanvas); // just for the demo var colors = ['lightblue', 'orange', 'lightgreen', 'pink']; // the canvas that will be used to draw all our images and get their dataImage var imageCtx = document.createElement('canvas').getContext('2d'); // draw a random image var randomEgg = function() { if (Math.random() < .8) { var radius = Math.random() * 25 + 1; var c = Math.floor(Math.random() * colors.length); var c1 = (c + Math.ceil(Math.random() * (colors.length - 1))) % (colors.length); imageCtx.canvas.width = imageCtx.canvas.height = radius * 2 + 3; imageCtx.beginPath(); imageCtx.fillStyle = colors[c]; imageCtx.arc(radius, radius, radius, 0, Math.PI * 2); imageCtx.fill(); imageCtx.beginPath(); imageCtx.fillStyle = colors[c1]; imageCtx.arc(radius, radius, radius / 2, 0, Math.PI * 2); imageCtx.fill(); } else { var img = Math.floor(Math.random() * loadedImage.length); imageCtx.canvas.width = loadedImage[img].width; imageCtx.canvas.height = loadedImage[img].height; imageCtx.drawImage(loadedImage[img], 0, 0); } return parseImageData(imageCtx); }; // init our objects and shapes var init = function() { var i; for (i = 0; i < 30; i++) { images.push(randomEgg()); } for (i = 0; i < 10000; i++) { objects.push({ angle: Math.random() * 360, x: 100 + (Math.random() * mainCanvas.width / 2), y: 100 + (Math.random() * mainCanvas.height / 2), speed: 1 + Math.random() * 20, image: Math.floor(Math.random() * (images.length)) }); } loop(); }; var loop = function() { draw(); requestAnimationFrame(loop); }; // were our offsite images will be stored var loadedImage = []; (function preloadImages() { var toLoad = ['https://dl.dropboxusercontent.com/s/4e90e48s5vtmfbd/aaa.png', 'https://dl.dropboxusercontent.com/s/rumlhyme6s5f8pt/ABC.png' ]; for (var i = 0; i < toLoad.length; i++) { var img = new Image(); img.crossOrigin = 'anonymous'; img.onload = function() { loadedImage.push(this); if (loadedImage.length === toLoad.length) { init(); } }; img.src = toLoad[i]; } })(); 

請注意,要繪制的圖像越大,繪制的速度也越慢。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM