0

I am working on creating a webpage that will allow users to add color filters to images they upload. In order to do this I wish to keep an original copy of the image stored in a canvas. Then whenever a user changes one of the color values, using a range slider I want to copy the original image into a new canvas edit it and then plot the new image.

The reason I don't want to apply changes directly to the original image is that they can be difficult to reverse. For example if the user set the blue channel of an image to 0 then unless I have the original image the user would not be able to undo that effect.

My current solution was to create a closure holding a canvas element. This closure is initialized when the user uploads their photo. The code for my closure is below

var scaleCanvas = (function() {
  var scaledCvs = document.createElement('canvas'); //Creates a canvas object
  var scaledCtx = scaledCvs.getContext("2d"); //Gets canvas context
  // All the functions in this section can access scaledCanvas
  return {
    init: function(cvs) {
      scaledCvs = cvs;
      console.log("Image Stored")
    }, //close init
    getImg: function() {
      return (scaledCvs);
    } //Close getImg
  }; //close return
})();

Once the canvas is initialized it can be accessed using the scaleCanvas.getImg() function. An example of this would be during my function adjustFilters() which is called by a range slider element as below

<div class="slidecontainer">
    <label>Blue Channel</label>
    <input type="range" min="0" max="150" value="100" class="slider" id="blue" onchange="adjustFilters();"/>
</div>

Once the blue slider is changed I load the canvas that I want to use to display the image. Then I load my original image using scaleCanvas.getImg(). Then I apply the effects I am interested in. Then I draw the image. What is strange though is that it appears the scaledCvs variable in my closure is being changed. I say this because if I select a value for "blue" as 0 then it is impossible for me to recover the original blue channel. This should not happen unless the scaledCvs in my closure is being altered somehow.

function adjustFilters() {
  var canvas = document.getElementById("dispImg"); //gets the canvas element
  canvas.style.display = "block"; //Makes the canvas visible
  var context = canvas.getContext("2d"); //Using 2D context

  // look up the size the canvas is being displayed at
  const width = canvas.clientWidth;
  const height = canvas.clientHeight;

  var imgCV = scaleCanvas.getImg(); //Gets the original canvas image
  var imgCtx = imgCV.getContext('2d');

  var blue = document.getElementById("blue").value / 100; //Gets value of the blue slider

  // Adjusts blue channel
  adjustColor(imgCtx, imgCV.height, imgCV.width, "blue", blue)

  // Draws the Image
  context.clearRect(0, 0, canvas.width, canvas.height);
  context.drawImage(imgCV, 0, 0);
}

I am looking for suggestions on how to get my code to operate as expected. i.e I need the variable scaledCvs in my closure to not be adjusted except for when the init function is called.

Thank You

Barmar
  • 741,623
  • 53
  • 500
  • 612
  • `scaledCvs=cvs;` doesn't make a copy of the canvas. It just saves a reference to it in a variable. – Barmar Apr 29 '21 at 05:37
  • I see that now. So in order to clone the canvas I would need to follow the method specified here: https://stackoverflow.com/questions/3318565/any-way-to-clone-html5-canvas-element-with-its-content. I imagine I would probably need to do the same when I access the original image for editing – Cole Harlow Apr 29 '21 at 05:51

1 Answers1

0

The reason for the observed behavior is that canvas elements are passed by reference therefore

init: function(cvs) {
  scaledCvs = cvs;
  console.log("Image Stored")
}, //close init

Does not create a copy of cvs it just creates a reference to it. In order to create a copy of the actual canvas you would need to follow the protocol below

init: function(cvs) {
                //Create a Copy of the provided canvas (cvs)
                scaledCvs.width=cvs.width;
                scaledCvs.height=cvs.height;
                scaledCtx.drawImage(cvs,0,0);
                }, //close init

For the entire code to work properly you would also need to modify getImage() so that it is not passed a reference of scaledCvs. The final closure definition would be

var scaleCanvas = (function() {
        var scaledCvs=document.createElement('canvas'); //Creates a canvas object
        var scaledCtx=scaledCvs.getContext("2d"); //Gets canvas context
        // All the functions in this section can access scaledCanvas
        return {
            init: function(cvs) {
                //Create a Copy of the provided canvas (cvs)
                scaledCvs.width=cvs.width;
                scaledCvs.height=cvs.height;
                scaledCtx.drawImage(cvs,0,0);
                }, //close init
            getImg: function() {
                //Since canvas are passed by reference in order to protect the saved canvas I have to create a copy before passing it out
                var cpCanvas = document.createElement('canvas');
                var context = cpCanvas.getContext("2d");
                cpCanvas.height = scaledCvs.height;
                cpCanvas.width = scaledCvs.width;
                context.drawImage(scaledCvs,0,0);
                return(cpCanvas);
                } //Close getImg
            }; //close return
    })();

All other code can remain the same.