Color Correction

Demonstration of Color Correction Implementation in PIXI WebGL. Also known as Gradient Mapping

by adil_invideo

HTML

<script src="https://cdnjs.cloudflare.com/ajax/libs/pixi.js/5.3.3/pixi.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/pixi-filters.js"></script>
<div class="header">
  <h2>Color Correction</h2>
  <div class="checkbox-wrapper">
  
  <label class="switch">
  <input type="checkbox" id="toggle" checked>
  <span class="slider round"></span>
</label>
<span>Gradient map</span>
  </div>
</div>
<div class="content">
  <div class="row">
    <div class="cell"><span class="title">Duotone</span></div>
    <div class="cell"><span class="title">Duotone2</span></div>
  </div>

  <div class="row">
    <div class="cell"><span class="title">Tritone</span></div>
    <div class="cell"><span class="title">Tritone3</span></div>
  </div>
</div>

SCSS

* {
  box-sizing: border-box;
}

body {
  font-family: sans-serif;
  display: flex;
  flex-direction: column;
  margin: 0;
}

#canvas {
  border: 1px solid black;
  width: 200px;
  height: 300px;
}

.header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 0 10px;

  .checkbox-wrapper {
    display: flex;
    align-items: center;

    span {
      font-weight: 500;
      font-size: 20px;
    }
  }
}

.content {
  display: flex;
  flex-direction: column;
  width: 100vw;
  height: calc(100vh - 100px);
  border-top: 1px solid black;

  .row {
    display: flex;
    flex: 1;

    .cell {
      border-right: 1px solid black;
      border-bottom: 1px solid black;
      flex: 1;
      position: relative;

      .title {
        position: absolute;
        left: 8px;
        bottom: 8px;
        font-size: 35px;
        font-weight: 600;
        color: #000;
        z-index: 1;
      }

      .canvas {
        top: 50%;
        left: 50%;
        position: absolute;
        transform: translate(-50%, -50%);
        background: #F0F0F0;
        border-radius: 20px;
      }
    }
  }
}

.switch {
  position: relative;
  display: inline-block;
  width: 60px;
  height: 34px;
  margin-right: 10px;
}

.switch input {
  opacity: 0;
  width: 0;
  height: 0;
}

.slider {
  position: absolute;
  cursor: pointer;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  background-color: #ccc;
  -webkit-transition: .4s;
  transition: .4s;
}

.slider:before {
  position: absolute;
  content: "";
  height: 26px;
  width: 26px;
  left: 4px;
  bottom: 4px;
  background-color: white;
  -webkit-transition: .4s;
  transition: .4s;
}

input:checked+.slider {
  background-color: #2196F3;
}

input:focus+.slider {
  box-shadow: 0 0 1px #2196F3;
}

input:checked+.slider:before {
  -webkit-transform: translateX(26px);
  -ms-transform: translateX(26px);
  transform: translateX(26px);
}

/* Rounded sliders */
.slider.round {
  border-radius:...

JavaScript

/* 
References: 
	https://forum.openframeworks.cc/t/gradient-map-on-image/16312/4
	https://forum.openframeworks.cc/t/gradient-map-on-image/16312/4
  https://halisavakis.com/my-take-on-shaders-gradient-mapper/
*/

const cells = document.getElementsByClassName("cell");
const placeholder = [];

/* ****************** DUOTONE***************************** */
const duotoneFrag = `
varying highp vec2 vTextureCoord;
uniform sampler2D uSampler;
uniform lowp vec4 color1;
uniform lowp vec4 color2;
const mat4 greyScaleMatrix = mat4( 
            0.2126, 0.7152, 0.0722, 0,
            0.2126, 0.7152, 0.0722, 0,
            0.2126, 0.7152, 0.0722, 0,
            0, 0, 0, 1
);
void main()
{
     lowp vec4 textureColor = texture2D(uSampler, vTextureCoord);
     lowp vec4 greyScaleTexture = textureColor * greyScaleMatrix; // grey scale
     float grayscaleValue = dot(textureColor.rgb, vec3(0.2126, 0.7152, 0.0722));
     lowp vec4 mixColor = mix(
       color1,
      color2, 
      grayscaleValue);
    
     gl_FragColor = mixColor * greyScaleTexture;
}
`;
// 5, 21, 39
// 248, 228, 120
class DuotoneFilter extends PIXI.Filter {

  constructor(color1, color2) {
  const uniforms = {
      color1: [color1[0] / 255.0, color1[1] / 255.0, color1[2] / 255.0, 1.0],
      color2: [color2[0] / 255.0, color2[1] / 255.0, color2[2] / 255.0, 1.0],
    };
    super(null, duotoneFrag, uniforms);
  }
}
/* ****************** TRITONE****************************** */
const tritoneFrag = `

varying highp vec2 vTextureCoord;
uniform sampler2D uSampler;

uniform vec3 color1;
uniform vec3 color2;
uniform vec3 color3;


void main()
{
    lowp vec4 textureColor = texture2D(uSampler, vTextureCoord);
    textureColor.rgb /= textureColor.a;
   
    float grayscaleValue = dot(textureColor.rgb, vec3(0.299, 0.587, 0.114));
   
    vec3 map = vec3(0);
    
    if(grayscaleValue < 0.5) {
    	float t = grayscaleValue / 0.5;
    	map.rgb = mix(color1, color2, t);
    }else{
    	float t = (grayscaleValue - 0.5) /...