Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Changing Image colour through Javascript

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

like image 438
Richard Harris Avatar asked Feb 03 '15 14:02

Richard Harris


3 Answers

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

like image 135
DWB Avatar answered Oct 23 '22 04:10

DWB


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.

like image 36
kahjav Avatar answered Oct 23 '22 03:10

kahjav


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

like image 2
Richard Harris Avatar answered Oct 23 '22 04:10

Richard Harris