3

I'm aware that the question of how to actually apply CSS filters to an image has been asked several times. Some answers are given that do not actually use CSS filters, but would still appear to get the job done. Unfortunately, I'm very new to JavaScript, and I do not understand these answers.

So, my question is in two parts:

  1. Since some of these answers are old, has a way been developed of saving a canvas image with CSS filters applied?

  2. If not, how do I implement the solutions described in one of these stackoverflow answers? I essentially need a code sample of actually applying multiple filters to the image or pixels, which was not contained in the original answers.

Thank you

Answers:

In the first answer, I'm lost on the "..." in the loop. In the second answer, there are no details on step 4. And if I'm correct, both of these use CSS3 filters, which is my preferred choice: Capture/save/export an image with CSS filter effects applied

I'm lost on step 3 of this one: is anyone solve about saving filtered canvas as an image?

Again, I'm lost on the application of the non-CSS filters: How to save image from canvas with css filters

JavaScript:

  // I know I eventually need to move these globals so they're passed as parameters.        
  var image; 
  var c; 
  var context; 
  var file; 

  // Begin File Open Code. 
  function fileOpen()
  {
    var fileInput = document.getElementById('fileInput');
    var fileDisplayArea = document.getElementById('iDisplay');

    fileInput.addEventListener('change', function(e)
    {
      file = fileInput.files[0];
      var imageType = /image.*/;
      if (file.type.match(imageType)) 
      {
        var reader = new FileReader();
        reader.onload = function(e)
        {                 
          iDisplay.innerHTML = "";
          image = new Image();

          image.src = reader.result;
          image.id = "I";               // Add an id attribute so the image editor code can access the image. // Image has since been moved to canvas.   
          iDisplay.appendChild(image);
          image.onload = function()
          {
            c = document.getElementById("C"); 
            context = c.getContext("2d");
            c.width = image.width; 
            c.height = image.height; 
            context.drawImage(image,0,0);  
          }                
        }
        reader.readAsDataURL(file); 
            }
      else
      {
        iDisplay.innerHTML = "File type not supported."
            }
        }); 
    setValues(); // Call setValues() as function exits to set Image Editing Code Hue Slider to 0.  
  }
  // End File Open Code 

  // Begin Image Editing Code. 
  var degrees = 0;
  var percentB = 100; 
  var percentC = 100;

  // Set initial values of sliders 
  function setValues()
  {      
    form1.brightness.value = 100;
    form1.contrast.value = 100;
    form1.hue.value = 0;      
  }

  // Get slider settings and apply changes to image
  function apply()
  { 
    degrees = form1.hue.value;
    percentC = form1.contrast.value;
    percentB = form1.brightness.value;
    // This is the crux of my question: How do I apply this properly, using the stackoverflow answers, so that the edited image may be saved?   
    if (document.getElementById("C") != null) document.getElementById("C").style.filter = "brightness(" + parseInt(percentB) + "%)" + " contrast(" + parseInt(percentC) + "%)" + " hue-rotate(" + parseInt(degrees) + "deg)";   
    if (document.getElementById("C") != null) document.getElementById("C").style.WebkitFilter = "brightness(" + parseInt(percentB) + "%)" + " contrast(" + parseInt(percentC) + "%)" + " hue-rotate(" + parseInt(degrees) + "deg)";   
  }
  // End Image Editing Code 

  // Canvas was not cooperating, it needed c.width not c.style.width, can probably remove these and replace with small default values.    
  function setCanvasWidth()
  {
    c.width = image.width;  
  }

  function setCanvasHeight()
  {
    c.height = image.height;  
  }

  // Begin File Save Code (or user can right click and save if filters get applied.  
  function save()
  {
    alert("Place file save code here.")
  }

HTML

 <!doctype html>
 <html lang="en">
 <head>
   <title>HTML5 CSS3 Javascript Image Editor</title>
   <meta charset="utf-8"/>
   <link rel="stylesheet" type="text/css" href="default.css"/>
   <script src="nav.js"></script>
   <script type="text/javascript">
   // JavaScript is in here. 
   </script>  

<!-- style elements specific to this page are placed in the style tags. -->  
<style>

  image  
  {
    max-width: 100%;
    display: block;
    margin: auto; 
    margin-left: auto; 
    margin-right: auto;  
  }

  #C 
  {
    max-width: 100%;
    display: block;
    margin: auto;     
    left: 0;
    right: 0;
    margin-left: auto; 
    margin-right: auto;  
  }

  #iDisplay 
  {
    margin-top: 2em;
    max-width: 100%;
    overflow-x: auto;
    margin-left: auto; 
    margin-right: auto;  
    display: none; 
  }

</style> 
</head>
  <body onload="setValues()">
<header>
  <a href="index.html"><img src="logoglow.png" alt="Logo Image" width="215" height="135" /></a>
  <a href="index.html"><img src="ac.png" alt="Header Image" width="800" height="135" /></a>
</header>
<main>
  <h3>An Ongoing Experiment in HTML5 / CSS3 / JavaScript Image Editing</h3>
  <div>
  <!-- Nav is floating left and Main is floating right, clear float styles (IF NEEDED) so they dont interfere with new image. -->     
    <p style="float:left;">  
    <input type="file" id="fileInput" onclick="fileOpen()"></p>
    <br style="clear:both">
  </div>
  <div id="iDisplay"></div>  
  <br style="clear:both"><br style="clear:both">
  <!-- <script>document.write('<img src="' + file + '"/>');</script><br><br> --> 
  <!-- <div style="overflow:scroll;">  -->
  <canvas style="color:#FFFFFF;" id="C" width="javascript:setCanvasWidth()" height="javascript:setCanvasHeight()">Your browser is too old to support this feature. <br>Please consider updating to a modern browser.</canvas>
  <!-- </div> --> 
  <!-- use javascript:function() to get width and height?  -->
  <div id="afterCanvas">  
  <br><br> 
  <form name="form1" id="form1id" action="javascript:save();" style="font-size:90%; text-align:center;">
    Brightness: <input type="range" name="brightness" min="0" max="200" step="5" onmousemove="apply()" ontouchmove="apply()" style="vertical-align:-7px;" />&nbsp; &nbsp;
    Contrast: <input type="range" name="contrast" min="0" max="200" step="5" onmousemove="apply()" ontouchmove="apply()" style="vertical-align:-7px;"/>&nbsp; &nbsp;
    Hue: <input type="range" name="hue" min="0" max="360" step="5" onmousemove="apply()" ontouchmove="apply()" style="vertical-align:-7px;"/>
    <br><br>  
    <input type="submit" style="float:left;" value="Save Changes" /> <!-- chnage to type="submit" if form action is needed --> 
  </form>
  </div>
</main>
</body>
</html>  
Community
  • 1
  • 1
TonyLuigiC
  • 185
  • 1
  • 2
  • 13
  • I probably should have mentioned that after 5 days, I have everything working. Except for the issue in my question. – TonyLuigiC May 11 '17 at 08:27

1 Answers1

3

Let's look at this this part from your question, because if you're only supporting browsers where this works, it's all you need:

Again, I'm lost on the application of the non-CSS filters: How to save image from canvas with css filters

The context's filter property works just like CSS filters – it takes the same functions – but the important thing to know is that, unlike for CSS, applying a filter to the context does not change the canvas' current image. It only applies to subsequent drawing operations.

Here is an example. When you run the code snippet below, you'll see two canvases. The first one has a CSS filter applied, the second one hasn't (as evidenced by the fact the border of the first canvas is blurry and pink, while the second is crisp and green).

When you then click the "Copy image" button, the code

  1. Looks at what CSS filters currently apply to the first canvas,
  2. applies this filter value to the second context,
  3. draws the first canvas onto the second one.

Because of 2), the drawing operation in 3) will have the filter applied.

The second canvas now looks like the first one, but the second canvas now truly contains the hue-rotated and blurred pixels, while the first one still has red and clear pixels – you just don't see that because of the CSS filter.

At this point, you can save the second canvas as an image, and it will look as expected.

window.onload = function () {
  var cnv1 = document.getElementById("canvas1");
  var ctx1 = cnv1.getContext("2d");
  
  var cnv2 = document.getElementById("canvas2");
  var ctx2 = cnv2.getContext("2d");
  
  ctx1.font = "30px sans-serif";
  ctx1.fillStyle = "red";
  ctx1.fillText("Hello world!", 30, 80);

  var button = document.getElementById("button");
  button.onclick = function () {
  
    // take whatever CSS filter is applied to the first canvas
    var cssFilter = getComputedStyle(cnv1).filter;
    
    // use that filter as the second canvas' context's filter
    // all subsequent drawing operations will have this filter applied
    ctx2.filter = cssFilter;
    
    // take whatever is in canvas 1 and draw it onto canvas 2
    // the text on cnv1 is red and un-blurred (it's only the css
    // that makes it look otherwise), but the above filter will
    // cause blurring and hue-shifting to be applied to the drawImage
    // operation
    ctx2.drawImage(cnv1, 0, 0);
  };
}
* {
  vertical-align: top;
}

canvas {
  border: 2px solid green;
}
#canvas1 {
  filter: blur(2px) hue-rotate(180deg);
}
<canvas id="canvas1" width="200" height="200"></canvas>

<canvas id="canvas2" width="200" height="200"></canvas>

<button id="button">Copy image</button>
Community
  • 1
  • 1
ij6
  • 436
  • 2
  • 5
  • I've already taken a sleeping pill and am not able to implement and test this right now, but **thank you very much** for taking the time. One follow up question from my sedated mind: how will I add the slider (range) values as variables in #canvas1? I'm sure I do this with JavaScript, but I'm unsure of the syntax for multiple filters. Thank you! – TonyLuigiC May 11 '17 at 10:26
  • You should be able to re-use the code you already have. While the user changes the values, apply the values as CSS `style` like you're doing right now. Only at the moment that you want to save the image, you create a second canvas, and apply to its context whatever CSS filters you currently have on the first canvas. That's what `var cssFilter = getComputedStyle(cnv1).filter; ctx2.filter = cssFilter;` in my example does. And then you copy the image (`ctx2.drawImage(cnv1, 0, 0);`). – ij6 May 11 '17 at 10:37
  • Canvas `ctx.filter` allows multiple filter functions just like CSS `filter` does (note that my example uses both `blur` and `hue-rotate`). So you don't need to do anything special to support that. – ij6 May 11 '17 at 10:37
  • I'm in the process of merging this with my code, and I can already see that it works. I just need to clean things up, such as centering, moving controls, etc... All routine stuff. **I can't thank you enough for this.** There are so many answers on here that say it can't be done with pure HTML5 / CSS3 / JavaScript, but your answer resolves the issue. – TonyLuigiC May 12 '17 at 05:13
  • You're very welcome. FWIW, the context `filter` property is new, so this wasn't possible for a long time (and still isn't if you need to support older browsers). I could imagine (but haven't tried it out) that it might also be possible by converting CSS filters to SVG filters and then drawing an SVG onto a new canvas (SVG filters have more browser support than canvas filter). That would be much messier though. Anyway, good luck :) – ij6 May 12 '17 at 08:15