DEV Community

Cover image for Building a Realtime Photoshop 🎨
Chris Bongers
Chris Bongers

Posted on • Edited on • Originally published at daily-dev-tips.com

Building a Realtime Photoshop 🎨

We're going to do something amazing this weekend - we'll be re-creating Photoshop!

Well, not entirely, but we'll be re-creating the image filters in Photoshop using variable CSS Filters.

I choose to build a playground so people can understand what each filter's effect is! I do hope you find it helpful.

Things we will address in this article are:

  • CSS Variables
  • CSS Filters
  • JavaScript Setting CSS Variables

Please have a play around with this Codepen, because actually trying it will show you how it works.

HTML Structure

Our application is going to have one image on the left hand side, and then our slider controls on the right, so let's start by creating this in HTML:

<div class="container">
  <img
    src="https://images.unsplash.com/photo-1508671323699-6df22ecaec2a?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=250&q=80"
    id="main-image"
  />
  <div class="toolbox">
    <label>
      Blur
      <input id="blur" max="20" min="0" step="1" type="range" value="0" />
    </label>
    <label>
      Brightness
      <input id="brightness" max="1" min="0" step="0.1" type="range" value="1" />
    </label>
    <label>
      Contrast
      <input id="contrast" max="200" min="0" step="1" type="range" value="100" />
    </label>
    <label>
      Grayscale
      <input id="grayscale" max="100" min="0" step="1" type="range" value="0" />
    </label>
    <label>
      Hue
      <input id="hue" max="360" min="0" step="1" type="range" value="0" />
    </label>
    <label>
      Invert
      <input id="invert" max="100" min="0" step="1" type="range" value="0" />
    </label>
    <label>
      Opacity
      <input id="opacity" max="100" min="0" step="1" type="range" value="100" />
    </label>
    <label>
      Saturate
      <input id="saturate" max="200" min="0" step="1" type="range" value="100" />
    </label>
    <label>
      Sepia
      <input id="sepia" max="100" min="0" step="1" type="range" value="0" />
    </label>
  </div>
</div>
Enter fullscreen mode Exit fullscreen mode

There we go, as you can see we are using the HTML range type sliders and give them default values, which are the normal values for each filter.

Adding our General Styling

Let's start by adding our general styling:

.container {
  display: flex;
  align-items: center;
  justify-content: center;
  height: 100vh;
  background: #eee;
}
.toolbox {
  margin-left: 50px;
  display: flex;
  justify-content: center;
  flex-direction: column;
}
label {
  margin-bottom: 5px;
}
Enter fullscreen mode Exit fullscreen mode

Nothing fancy, we are using Flex to center everything and add some primary margin to our elements.

Adding our CSS Variables

In today's topic we are addressing CSS Variables, these are super useful because they can easily be changed by JavaScript!

:root {
  --blur: 0;
  --brightness: 1;
  --contrast: 100;
  --grayscale: 0;
  --hue: 0;
  --invert: 0;
  --opacity: 100;
  --saturate: 100;
  --sepia: 0;
}
Enter fullscreen mode Exit fullscreen mode

Above, you see all the variables we are using. They correspond with the HTML inputs we created!

So we set them all to have our basic starting point values, but how do they do something?

CSS Filters

As we saw in our article about our CSS Disco Text, CSS Filters are really cool.

The filters we can use are as follows:

  • url (We won't use this today)
  • blur
  • brightness
  • contrast
  • drop-shadow (Also, won't be used)
  • grayscale
  • hue-rotate
  • invert
  • opacity
  • saturate
  • sepia

So let's address this and add the filters to our Image.

#main-image {
  transition: all 300ms ease-in-out;
  filter: blur(calc(1px * var(--blur))) brightness(var(--brightness)) contrast(
      calc(1% * var(--contrast))
    ) grayscale(calc(1% * var(--grayscale))) hue-rotate(calc(1deg * var(--hue))) invert(
      calc(1% * var(--invert))
    )
    opacity(calc(1% * var(--opacity))) saturate(calc(1% * var(--saturate))) sepia(calc(1% *
          var(--sepia)));
  border: 5px solid #fff;
}
Enter fullscreen mode Exit fullscreen mode

Wow! Massive code-block, but basically, you can only have one filter element, so we "chain" the filters after each other!

Ok, cool, we now set all the filters on our image, but nothing changed?

Correct! These are all the default values of each filter, so let's add some JavaScript to make the sliders affect our variables.

JavaScript Setting CSS Variables

First let's start and get our image object.

var img = document.getElementById('main-image');
Enter fullscreen mode Exit fullscreen mode

Ok, now we need to find all our sliders

var inputs = document.querySelectorAll('input');
Enter fullscreen mode Exit fullscreen mode

Next, we want to loop over each input and attach a event listener.

[].forEach.call(inputs, function(input) {
  input.addEventListener('input', e => {
    img.style.setProperty('--' + input.id, input.value);
  });
});
Enter fullscreen mode Exit fullscreen mode

We add an event listener to our input event, so every time the slider changes, we execute the function.
The function then gets our image variable and uses the setProperty function to set the variables (e.g. --blur) to the input value!

Browser Support

As mentioned before, CSS Filters are cool, but not widely supported yet 😩.

There is a polyfill, but also limited.

CSS Filter support

Thank you for reading, and let's connect!

Thank you for reading my blog. Feel free to subscribe to my email newsletter and connect on Facebook or Twitter

Top comments (11)

Collapse
 
saberglow profile image
Wali Ullah • Edited

Check out my pen (Clrd.), I didn't use any CSS filters, but actually programmed those filters myself in JS (so the processing is really slow, but hey, it works).

codepen.io/saberglow/pen/OJNypmg

Collapse
 
dailydevtips1 profile image
Chris Bongers

Hey Wali,

That's proper amazing man!
Can't believe you made these in JS from scratch 🀟

Collapse
 
dailydevtips1 profile image
Chris Bongers

Thanks! Happy to hear you enjoyed it.

Collapse
 
amindotb profile image
Amin Bagheri

Do you have any idea to export the edited image as it rendered in browser?

Collapse
 
saberglow profile image
Wali Ullah

right click > save as....? seems to be working fine for me.

Collapse
 
waylonwalker profile image
Waylon Walker

That doesn't capture your edits.

Thread Thread
 
saberglow profile image
Wali Ullah

yeap, just noticed that....

Collapse
 
dailydevtips1 profile image
Chris Bongers

Hmm I have a unverified idea, that Canvas might be an option, but going to do some research on this topic.

Collapse
 
dailydevtips1 profile image
Chris Bongers

Glad you like it 🀩

Collapse
 
gwutama profile image
Galuh Utama

Great job!

I’m wondering if we can load RAW images and howβ€˜s the performance when working with let’s say 20 MP jpeg image.

Collapse
 
dailydevtips1 profile image
Chris Bongers

Hi Galuh,

First, thank you so much for the respect here.

As too RAW, unfortunately not as "image" we could convert the RAW image to a base64 encode, but that removes all meta stuff you actually want in a RAW image.

As for big images the following uses a 54MB picture!

codepen.io/rebelchris/full/gOrrGyz