putImageData(), how to keep old pixels if new pixels are transparent?

烈酒焚心 提交于 2019-11-27 06:13:21

问题


In html5, when you draw to a canvas using putImageData(), if some of the pixels you are drawing are transparent (or semi-transparent), how do you keep old pixels in the canvas unaffected?

example:

var imgData = context.createImageData(30,30);
for(var i=0; i<imgData.data.length; i+=4)
{
imgData.data[i]=255;
imgData.data[i+1]=0;
imgData.data[i+2]=0;
imgData.data[i+3]=255;
if((i/4)%30 > 15)imgData.data[i+3] = 0;
}
context.putImageData(imgData,0,0);

The right half of the 30x30 rect is transparent. If this is drawn over something on the canvas, pixels behind the right half are removed (or become thransparent). How do I keep them?


回答1:


You can use getImageData to create a semi-transparent overlay:

  • create a temporary offscreen canvas
  • getImageData to get the pixel data from the offscreen canvas
  • modify the pixels as you desire
  • putImageData the pixels back on the offscreen canvas
  • use drawImage to draw the offscreen canvas to the onscreen canvas

Here's example code and a Demo: http://jsfiddle.net/m1erickson/CM7uY/

<!doctype html>
<html>
<head>
<link rel="stylesheet" type="text/css" media="all" href="css/reset.css" /> <!-- reset css -->
<script type="text/javascript" src="http://code.jquery.com/jquery.min.js"></script>
<style>
    body{ background-color: ivory; }
    canvas{border:1px solid red;}
</style>
<script>
$(function(){

    var canvas=document.getElementById("canvas");
    var context=canvas.getContext("2d");

    // draw an image on the canvas
    var img=new Image();
    img.onload=start;
    img.src="https://dl.dropboxusercontent.com/u/139992952/stack1/landscape1.jpg";
    function start(){
        canvas.width=img.width;
        canvas.height=img.height;
        context.drawImage(img,0,0);

        // overlay a red gradient 
        drawSemiTransparentOverlay(canvas.width/2,canvas.height)

    }

    function drawSemiTransparentOverlay(w,h){

        // create a temporary canvas to hold the gradient overlay
        var canvas2=document.createElement("canvas");
        canvas2.width=w;
        canvas2.height=h
        var ctx2=canvas2.getContext("2d");

        // make gradient using ImageData
        var imgData = ctx2.getImageData(0,0,w,h);
        var data=imgData.data;
        for(var y=0; y<h; y++) {
            for(var x=0; x<w; x++) {
                var n=((w*y)+x)*4;
                data[n]=255;
                data[n+1]=0;
                data[n+2]=0;
                data[n+3]=255;
                if(x>w/2){
                    data[n+3]=255*(1-((x-w/2)/(w/2)));
                }
            }
        }

        // put the modified pixels on the temporary canvas
        ctx2.putImageData(imgData,0,0);

        // draw the temporary gradient canvas on the visible canvas
        context.drawImage(canvas2,0,0);

    }


}); // end $(function(){});
</script>
</head>
<body>
    <canvas id="canvas" width=200 height=200></canvas>
</body>
</html>

Alternatively, you might check out using a linear gradient to do your effect more directly.

http://jsfiddle.net/m1erickson/j6wLR/




回答2:


Problem

As you know, your statement

if((i/4)%30 > 15)imgData.data[i+3] = 0;

will make pixels on the right half of the image be transparent, so that any other object on the page behind the canvas can be seen through the canvas at that pixel position. However, you are still overwriting the pixel of the canvas itself with context.putImageData, which replaces all of its previous pixels. The transparency that you add will not cause the previous pixels of to show through, because the result of putImageData is not a second set of pixels on top of the previous pixels in the canvas, but rather the replacement of existing pixels.

Solution

I suggest that you begin your code not with createImageData which will begin with a blank set of data, but rather with getImageData which will give you a copy of the existing data to work with. You can then use your conditional statement to avoid overwriting the portion of the image that you wish to preserve. This will also make your function more efficient.

var imgData = context.getImageData(30,30);
for(var i=0; i<imgData.data.length; i+=4)
{
  if((i/4)%30 > 15) continue;
  imgData.data[i]=255;
  imgData.data[i+1]=0;
  imgData.data[i+2]=0;
  imgData.data[i+3]=255;
}
context.putImageData(imgData,0,0);



回答3:


Something that tripped me up that may be of use... I had problems with this because I assumed that putImageData() and drawImage() would work in the same way but it seems they don't. putImageData() will overwrite existing pixels with its own transparent data while drawImage() will leave them untouched.

When looking into this I just glanced at the docs for CanvasRenderingContext2D.globalCompositeOperation (should have read more closely), saw that source-over is the default and didn't realise this would not apply to putImageData()

Drawing into a temporary canvas then and using drawImage() to add the temp canvas to the main context was the solution I needed so cheers for that.




回答4:


I wanted to copy a CRISP, un modified version of the canvas on top of itself. I eventually came up with this solution, which applies.

https://jsfiddle.net/4Le454ak/1/

The copy portion is in this code:

var imageData = canvas.toDataURL(0, 0, w, h);
var tmp = document.createElement('img');
tmp.style.display = 'none'
tmp.src = imageData;
document.body.appendChild(tmp);
ctx.drawImage(tmp, 30, 30);

What's happening:

  • copy image data from canvas
  • set image data to a non-displayed <img> (<img> has to be in dom though)
  • draw that image back onto the canvas
  • you can delete or reuse the <img> at this point


来源:https://stackoverflow.com/questions/22194267/putimagedata-how-to-keep-old-pixels-if-new-pixels-are-transparent

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!