40

In my project i have to implement one different color image on the other same size and pattern image using canvas and images are not in round or rectangle shapes. That all are in waves shape and it will apply on a single main background image for showing multiple graphics on every onclick function.

Overlapped image should be change in another selected color. My question Is there any way with using canvas from that we can change the image color which is draw by canvas or we need to use different images always and apply with CSS/jQuery.

I read about canvas image masking and overlapping. But cant understand with my images because that are not in square or circle shape then first thing is how i draw multiple wave shapes on a single image. I have no idea about that i searched but fail to search perfect solution.

My need is just draw one wave image on canvas and change its color from on click function and also set an another div with background-image and also more then two canvas will overlapped. Is this possible?

(That means : This functionality is for create or set multiple graphics on a car, for that each graphic image need to set in a canvas and for another graphic need to overlapped on div and first canvas)

Community
  • 1
  • 1
Anup
  • 3,283
  • 1
  • 28
  • 37
  • It's a little bit unclear what you are asking for - you want to draw images inside a wave shape - that the wave shape clips the images? You can simply define the shape on canvas and call clip(). The next thing drawn will be clipped inside the shape you first defined. –  Aug 22 '13 at 16:58
  • Sir, I didn't use canvas ever? Also i add some more text for more clear my functionality. – Anup Aug 23 '13 at 05:01

3 Answers3

67

The question as it stands is a bit unclear IMO. To give a more general answer that you can apply to a scenario where you need clipping you can use (at least) two approaches:

Method 1 - Using composite mode to clip

Composite mode is the simplest way but also the least flexible as you need to pre-define the clipping mask as an image with a transparent background (usually PNG).

You can either use the solid parts of the image to clip the next drawn thing, or use the transparent areas to fill in.

Here is an approach where we use the solid parts to clip the next drawn shape/image:

/// draw the shape we want to use for clipping
ctx1.drawImage(imgClip, 0, 0);

/// change composite mode to use that shape
ctx1.globalCompositeOperation = 'source-in';

/// draw the image to be clipped
ctx1.drawImage(img, 0, 0);

Here the globalCompositeOperation is changed to source-in which means that the source image (the one we are gonna draw next to destination) will be drawn inside the existing solid data. Nothing will be drawn to transparent areas.

If our clipping mask looks like this (random fair-use from the net):

Clip mask

And our image like this:

Main image

The result will be this:

Composited image

Method 2 - Using a Path to clip

You can also define a Path for the clipping. This is very flexible as you can adjust the path or animate it if you desire.

Note: Just have in mind that clipping using Path is currently a bit "fragile" in the browsers so you should consider using save() and restore() before and after setting and using a clip path as the browsers are unable to reset clip at the moment (restore will restore the default clip = full canvas);

Lets define a simple zig-zag path (this will be your waves in your case):

/// use save when using clip Path
ctx2.save();

ctx2.beginPath();
ctx2.moveTo(0, 20);
ctx2.lineTo(50,0);
/// ... more here - see demo
ctx2.lineTo(400, 20);
ctx2.lineTo(400, 100);
ctx2.lineTo(0, 100);
ctx2.closePath();

/// define this Path as clipping mask
ctx2.clip();

/// draw the image
ctx2.drawImage(img, 0, 0);

/// reset clip to default
ctx2.restore();

Now that we have set the clipping mask using clip anything drawn to the canvas next will be clipped to fit inside that shape (note that we make sure the shape can end where it begun):

Path clipped image

lewis
  • 2,936
  • 2
  • 37
  • 72
  • My need is just draw one wave image on canvas and change its color from on click function and also more then two canvas will overlapped, Is it possible? – Anup Aug 23 '13 at 04:53
  • @Anrup why not just use two images in that case? –  Aug 23 '13 at 05:26
  • I am very new for canvas, I dont know any thing in that also i have to change only colors of an draw image not to replace. if i can replace an image then i have another method also with css3 and jquery i can replace very easily but client want it only with html5/canvas.. – Anup Aug 23 '13 at 05:33
54

You can use context compositing to replace part of an image.

For example, if you have this blue logo already as an image:

enter image description here

Any you want the top part of the logo to be colored purple:

enter image description here

You can use compositing to recolor the top part of the image.

First, use your favorite image editor to crop away any part you don’t want recolored.

What’s left is called an overlay.

This overlay part of the image is what we will programmatically recolor.

enter image description here

This overlay can be programatically recolored to any color.

enter image description hereenter image description here

How the overlay was programatically recolored:

  1. Draw the overlay on an empty canvas.
  2. Set the compositing mode to “source-in”.
  3. The Effect: Only existing pixels are replaced—transparent pixels remain transparent
  4. Now draw a rectangle of any color the covering the canvas
  5. (remember, only the existing overlay will be replaced with the new color)

How to complete the logo with the changed overlay color

  1. Set the compositing mode to “destination-atop”
  2. The Effect: Only transparent pixels are replaced—existing pixels remain unchanged
  3. Now draw the original logo
  4. (remember, the existing colored overlay will not be replaced)

This "destination-atop" compositing effect is sometimes called “drawing under”.

This overlay can even be replace with textures!

enter image description here

Here is code and a Fiddle: http://jsfiddle.net/m1erickson/bfUPr/

<!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; padding:20px; }
    #canvas{border:1px solid red;}
</style>

<script>
$(function(){

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

    var truck,logo,overlay;
    var newColor="red";

    var imageURLs=[];
    var imagesOK=0;
    var imgs=[];
    imageURLs.push("https://dl.dropboxusercontent.com/u/139992952/stackoverflow/boxTruck.png");
    imageURLs.push("https://dl.dropboxusercontent.com/u/139992952/stackoverflow/TVlogoSmall.png");
    imageURLs.push("https://dl.dropboxusercontent.com/u/139992952/stackoverflow/TVlogoSmallOverlay.png");
    loadAllImages();

    function loadAllImages(){
        for (var i = 0; i < imageURLs.length; i++) {
          var img = new Image();
          imgs.push(img);
          img.onload = function(){ imagesOK++; imagesAllLoaded(); };
          img.src = imageURLs[i];
        }      
    }

    var imagesAllLoaded = function() {
      if (imagesOK==imageURLs.length ) {
         // all images are fully loaded an ready to use
         truck=imgs[0];
         logo=imgs[1];
         overlay=imgs[2];
         start();
      }
    };


    function start(){

        // save the context state
        ctx.save();

        // draw the overlay
        ctx.drawImage(overlay,150,35);

        // change composite mode to source-in
        // any new drawing will only overwrite existing pixels
        ctx.globalCompositeOperation="source-in";

        // draw a purple rectangle the size of the canvas
        // Only the overlay will become purple
        ctx.fillStyle=newColor;
        ctx.fillRect(0,0,canvas.width,canvas.height);

        // change the composite mode to destination-atop
        // any new drawing will not overwrite any existing pixels
        ctx.globalCompositeOperation="destination-atop";

        // draw the full logo
        // This will NOT overwrite any existing purple overlay pixels
        ctx.drawImage(logo,150,35);

        // draw the truck
        // This will NOT replace any existing pixels
        // The purple overlay will not be overwritten
        // The blue logo will not be overwritten
        ctx.drawImage(truck,0,0);

        // restore the context to it's original state
        ctx.restore();

    }


}); // end $(function(){});
</script>

</head>

<body>
    <canvas id="canvas" width=500 height=253></canvas>
</body>
</html>
markE
  • 102,905
  • 11
  • 164
  • 176
  • My need is just draw one wave image on canvas and change its color from on click function and also more then two canvas will overlapped, Is it possible? – Anup Aug 23 '13 at 04:53
  • Yes it's possible and you don't even need 2 canvases: http://jsfiddle.net/m1erickson/bfUPr/ – markE Aug 23 '13 at 06:19
  • Thanks again brother, but in your example full image color is changing every time but if i want to change only upper portion then? your example is nearby my need but not an exact answer please once try again – Anup Aug 23 '13 at 06:37
  • Modified the example once again... http://jsfiddle.net/m1erickson/7G6xn/ ...Now only the upper portion changes color on clicking the canvas. :) – markE Aug 23 '13 at 16:12
  • hi mark i need to hide the overlapping area of the image.http://stackoverflow.com/questions/29943533/how-to-hide-the-overflow-of-photos-in-canvas . This is the question i have posted. Please give me a solution. I have made it in fabric js. – Rahi Apr 29 '15 at 13:34
  • 1
    Great answer! Helped me out a lot making circular images with transparency around them, in combination with [this](http://stackoverflow.com/a/5476576/945863) :). – Daniel Buckmaster Oct 07 '15 at 00:05
0

Maybe this would be helpful for somebody. Based on answer above I wanted just to mask part of it and I couldn't figure out why save() and restore() on canvas context doesnt work as I draw previously many images, therefore I did simple solution:

function maskPhoto(img) {
    var imagecanvas = document.createElement('canvas'),
        imagecontext = imagecanvas.getContext('2d'),
        width  = img.naturalWidth,
        height = img.naturalHeight,
        mask = document.querySelector('your-img-mask-selector');

    imagecanvas.width  = width;
    imagecanvas.height = height;

    imagecontext.drawImage(mask, 0, 0, width, height);
    imagecontext.globalCompositeOperation = 'source-in';
    imagecontext.drawImage(img, 0, 0);

    return imagecanvas;

}

And in your canvas use like that:

 contextCanvas.drawImage(
        maskPhoto(document.querySelector('your-img-selector')),
        x, y
 );
fearis
  • 424
  • 3
  • 15