Changing Image colour through Javascript

18,799

Solution 1

OK, has taken some time but through a combination of the suggestions given and extra searching have found a solution for my needs.

Each of the suggestions got me so far but there were some issues for the specific functionality I needed, so am posting this in case it helps others.

I am using the < svg > element:

I required a main image, and them some smaller images on top that would change colour if the image was clicked on, or if a button outside the main image is clicked.

<div style="position: absolute; top: 0px; left: 0px;"><svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="0px" height="0px"><defs><filter id="desaturate"> <feColorMatrix 
  type="matrix"
  values="0.5 0 0 0 0
          0.2 0 0 0 0
          0.9 0 0 0 0
          0 0 0 1 0" /></filter></defs></svg></div>

The values set the colour change. It is trial and error atm as I haven't found a good guide to the colours yet but there are enough around to get by.

I had a problem with random spaces and positioning caused by using the < svg > element. So I have put it inside a dive that is absolutely positioned and set the size of the element to '0' '0' so it takes up no space and doesn't affect anything else on the page.

The ID specifies this particular colour. You can then apply this colour change to as many images as you like as follows:

<div style="position: relative; top: 000px; left: 000px; z-index: 1; display: inline;">
<svg class="svgClass" version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="450px" height="900px">
<image xlink:href="main_image.png" width="450" height="900" />
<image xlink:href="small_image1.png" class="ChangeImageColour ChangeImageColourClick" width="79" height="198" x="110px" y="100px" />
<image xlink:href="small_image2.png" class="ChangeImageColour ChangeImageColourClick" width="79" height="198" x="150px" y="130px" /></svg></div>

Again, to avoid position and spacing issues with SVG, I have set the size of the < svg > element for the images to the same size of the main image, and placed them all within a < div > that can then be positioned in the right place on the page. If you want images overlapping in this way, you will need to have all the images within the same < svg > element or you may get display position and spacing issues in some browsers.

You can the position each image individually over the top of the main image using 'x' and 'y'. You must also specify the height and width of the image.

Once you have your < svg > to specify the colour, and your < svg > element for the images, you now just need to apply that colour to the images you want using class and ID attributes and the following code:

<script>
  var $shape = $( '.ChangeImageColour' );
$( '.ChangeImageColourClick' ).click(function() {
          if ( $shape.attr( 'filter' ) )
     $shape.removeAttr( 'filter' );
  else
     $shape.attr( 'filter', 'url(#desaturate)' );
});
</script>

I want the colour change to happen after a click event ether on an image or a button. Just create a button with the matching 'click' calss, in this case 'ChangeImageColourClick'. So you can create anything to be clicked on and will change the colour. This click adds the ID of the set colour to change (#desaturate). If any matching element already has the ID, it is removed.

This creates the effect of toggling the colour change. It will change the colour of any matching image of the same class.

You can of course not use a click event if not required, or start with the colour change 'on' and click to remove etc.

Note: You can have multiple < svg > elements with images in and apply the same colour change. Just make sure you set the < svg > element dimensions to the exact ones you want, even if it is 0. If you leave it blank then you'll get some spacing issues.

I need to have the images in the same < svg > as they are png images that are occupying the same space on the page. If your images are not overlapping you should use separate < svg > elements or each image or image set, and set the dimensions of the < svg > element to the size of that image and you shouldn't have any issues with odd spacing and positioning in some browsers.

I think that about explains it all.

It works pretty well for what I need, and is a fairly concise bit of coding, especially as you only have to set the colour once and apply to multiple images.

Just be careful of some of the notes I've put to make sure it displays as you need.

Thanks for all the help, will update if there is anything relevant to add.

If you are having any problems then post on here. I'm not an expert but have looked at this a fair bit now and have probably tried and tested most pitfalls

Solution 2

I don't know how custom of a job you are trying, but a simple fix would be to make a second class with a filter, then add that class at the click event.

<img src='mug.png' id='mug' class='unfiltered-pic'  onclick="changeColor(this)>
<style>
.unfiltered-pic {
     width: 25%
     height: 25%
}

.filtered-pic {
    filter: hue-rotate(/*degree on color wheel*/deg)
}
</style>
<script>
function changeColor(element) {
     element.setAttribute('class', 'filtered-pic');
}
</script>

You can choose various filters through css-filters here. Use hue-rotate to change the hue(color). Use a color wheel to determine where you want to go.

If you would like to switch to using class to change all of them at once, you can do this instead:

<img src='mug.png' id='mug' class='unfiltered-pic'  onclick="changeColor()>
<style>
.unfiltered-pic {
     width: 25%
     height: 25%
}

.filtered-pic {
    filter: filter function /*See Link*/
}
</style>
<script>
var pics = document.getElementByClassName('unfiltered-pic')
function changeColor() {
     for(var i=0; i < pics.length; i++) {
         pics[i].setAttribute('class', 'filtered-pic');
     }
}
</script>

Let me know if you need any clarification or additional help.

Solution 3

I think it might have something to do with browser support because document.getElementByClassName("mug"); should return an array of elements (older browsers might not support it). At which point it is easy to iterate over each mug and change the colour like so

<img src="mug.png" id="mug" width="25%" height="25%" onload="getPixels(this)" />
<input type="text" id="color" value="#6491ee" />
<input type="button" value="change color" onclick="changeMugsColor()">

<script type="text/javascript">
    var mug = document.getElementsByClassName("mug");
    var canvas = document.createElement("canvas");
    var ctx = canvas.getContext("2d");
    var originalPixels = null;
    var currentPixels = null;

    function HexToRGB(Hex)
    {
        var Long = parseInt(Hex.replace(/^#/, ""), 16);
        return {
            R: (Long >>> 16) & 0xff,
            G: (Long >>> 8) & 0xff,
            B: Long & 0xff
        };
    }

    function changeMugsColor() {
        for (var ii = 0; ii < mug.length; ii++) {
            changeColor(mug[ii]);
        }
    }

    function changeColor(amug)
    {
        if(!originalPixels) return; // Check if image has loaded
        var newColor = HexToRGB(document.getElementById("color").value);

        for(var I = 0, L = originalPixels.data.length; I < L; I += 4)
        {
            if(currentPixels.data[I + 3] > 0)
            {
                currentPixels.data[I] = originalPixels.data[I] / 255 * newColor.R;
                currentPixels.data[I + 1] = originalPixels.data[I + 1] / 255 * newColor.G;
                currentPixels.data[I + 2] = originalPixels.data[I + 2] / 255 * newColor.B;
            }
        }

        ctx.putImageData(currentPixels, 0, 0);
        amug.src = canvas.toDataURL("image/png");
    }

    function getPixels(img)
    {
        canvas.width = img.width;
        canvas.height = img.height;

        ctx.drawImage(img, 0, 0, img.naturalWidth, img.naturalHeight, 0, 0, img.width, img.height);
        originalPixels = ctx.getImageData(0, 0, img.width, img.height);
        currentPixels = ctx.getImageData(0, 0, img.width, img.height);

        img.onload = null;
    }
</script>

I haven't ran the code but it should work

Share:
18,799
Richard Harris
Author by

Richard Harris

Updated on June 20, 2022

Comments

  • Richard Harris
    Richard Harris almost 2 years

    I have been looking to change the colour of an image when a click event is used.

    I Came across this post, which the first and main response with the Mug works really well.

    However, I need to use class, rather than ID, as I need to change the colour of more than one image. When I change the code to getElementsByClassName, instead of byID, it no longer works.

    I of course change the ID=mug to class=mug.

    I can't see anywhere else in the code that would cause a problem, so any help would be appreciated.

    I can't post on the original so adding here. Original link is: How to change color of an image using jquery

    This is the code:

    <img src="mug.png" id="mug" width="25%" height="25%" onload="getPixels(this)" />
    <input type="text" id="color" value="#6491ee" />
    <input type="button" value="change color" onclick="changeColor()">
    
    <script type="text/javascript">
        var mug = document.getElementById("mug");
        var canvas = document.createElement("canvas");
        var ctx = canvas.getContext("2d");
        var originalPixels = null;
        var currentPixels = null;
    
        function HexToRGB(Hex)
        {
            var Long = parseInt(Hex.replace(/^#/, ""), 16);
            return {
                R: (Long >>> 16) & 0xff,
                G: (Long >>> 8) & 0xff,
                B: Long & 0xff
            };
        }
    
        function changeColor()
        {
            if(!originalPixels) return; // Check if image has loaded
            var newColor = HexToRGB(document.getElementById("color").value);
    
            for(var I = 0, L = originalPixels.data.length; I < L; I += 4)
            {
                if(currentPixels.data[I + 3] > 0)
                {
                    currentPixels.data[I] = originalPixels.data[I] / 255 * newColor.R;
                    currentPixels.data[I + 1] = originalPixels.data[I + 1] / 255 * newColor.G;
                    currentPixels.data[I + 2] = originalPixels.data[I + 2] / 255 * newColor.B;
                }
            }
    
            ctx.putImageData(currentPixels, 0, 0);
            mug.src = canvas.toDataURL("image/png");
        }
    
        function getPixels(img)
        {
            canvas.width = img.width;
            canvas.height = img.height;
    
            ctx.drawImage(img, 0, 0, img.naturalWidth, img.naturalHeight, 0, 0, img.width, img.height);
            originalPixels = ctx.getImageData(0, 0, img.width, img.height);
            currentPixels = ctx.getImageData(0, 0, img.width, img.height);
    
            img.onload = null;
        }
    </script>
    

    Thanks

  • Richard Harris
    Richard Harris over 9 years
    Thanks, will look at this solution more later, been working on the first option from above.
  • Richard Harris
    Richard Harris over 9 years
    Had a quick go with that, but isn't working as yet. From what I understand having looked at getelementsbyclassname, I need to run something like var i; for (i = 0; i < y.length; i++) to affect more than one instance of a specified class.
  • Richard Harris
    Richard Harris over 9 years
    OK, had a look at this and I did look at using filters before, but unless I am missing something it doesn't give the ability to change to a specified colour, just a filter effect? The second bit of: for(var i=0..... is what I need to implement to get it to apply to all images (not just one if [0] is specified) However I'm not sure how to implement this to my original code above so any help there would be appreciated.
  • kahjav
    kahjav over 9 years
    I added an explanation of hue-rotate, which will get you a change to a specific color. Is there one in particular you are trying to get? I would stray away from deconstructing the picture, changing the pixels, and putting it back together. It's a work intensive process and their are other tools that can get you there, like filters.
  • Richard Harris
    Richard Harris over 9 years
    Thanks, sorry I didn't see the hue addition. You're right, doing it through filter will probably be easier for my needs, though the method I've used so far does give some really good results that look differently than just putting a filter over the top. Would be good I think to complete the other method even if I end up using the filter option as it might prove useful in the future. It also loads and works really quickly so far, though may not be the case once it is extended to all the images I need. I'll give the filter option a go and post back tomorrow. Thanks
  • Richard Harris
    Richard Harris over 9 years
    Had a look but it doesn't work in IE apparently? If not then not really going to be able to use it atm, which is a shame as it seems a good solution.
  • kahjav
    kahjav over 9 years
    msdn.microsoft.com/en-us/library/ie/ms530752(v=vs.85).aspx for ie < 9 karlhorky.com/2012/06/… for ie 10. But the ie 10 solution, using an svg, works for everything. Have you also thought about just having two pictures, one with the filter (can be added using any photo editing software) swapping them out on click?