<h1>Saturation functions</h1>
  <div class="container">
    <div class="innercont inputcont">
      <label for="color">Enter color:</label>

      <input type="color" name="color" id="color">
    </div>

    <div class="innercont buttoncont">
      <button id="increase-sat">Increase saturation
      </button>
      <button id="decrease-sat">Decrease saturation</button>
    </div>

    <div class="innercont outputcont">
      <label for="output" id="output-label"> Resulting color: </label>
      <span id="hash-output">#000000</span>
      <span id="rgb-output">rgb(0,0,0)</span>
    </div>
  </div>
* {
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica,
    Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
  margin: 0px;
  padding: 0px;
  box-sizing: border-box;
}
body {
  display: flex;
  flex-direction: column;
  align-items: center;
}
.container {
  margin: 3em;
  display: flex;
  align-items: flex-start;
  max-width: 800px;
}
@media only screen and (max-width: 800px) {
  .container {
    display: flex;
    flex-direction: column;
    height: 400px;
    justify-content: space-around;
  }
}
label {
  font-weight: bold;
}
button {
  padding: 1vw;
  margin: 1vw 0vw;
  border-radius: 1.5vw;
  font-weight: bold;
  width: 15vw;
  min-width: 100px;
}
.inputcont {
  order: 2;
  display: flex;
  flex-direction: column;
  align-items: center;
}
.buttoncont {
  order: 1;
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
}
.outputcont {
  order: 3;
  display: flex;
  flex-direction: column;
  align-items: center;
}

.innercont {
  height: 20vw;
  max-height: 250px;
  width: 50vw;
  margin: 0vw 1vw;
}

#color {
  display: block;
  background-color: inherit;
  appearance: none;
  -webkit-appearance: none;
  width: 100%;
  flex-grow: 1;
  box-sizing: inherit;
  border-radius: 1.5vw;
}
#color::-webkit-color-swatch {
  border-radius: 1.5vw;
}
#color::-moz-color-swatch {
  border-radius: 1.5vw;
}
//Grab dom nodes

const colorInput = document.getElementById("color");
const hashOutput = document.getElementById("hash-output");
const rgbOutput = document.getElementById("rgb-output");
const increase = document.getElementById("increase-sat");
const decrease = document.getElementById("decrease-sat");

//Add event listeners

colorInput.addEventListener("input", onColorInput);
increase.addEventListener("click", onIncreaseClick);
decrease.addEventListener("click", onDecreaseClick);

//Set output to match input on change

function onColorInput(e) {
  hashOutput.textContent = e.target.value;
  rgbOutput.textContent = hexToRgb(e.target.value);
}

//functionality for saturation buttons

function onIncreaseClick(e) {
  result = rgbToHex(saturateByTenth(hexToRgb(colorInput.value)));
  hashOutput.textContent = result;
  rgbOutput.textContent = hexToRgb(result);
  colorInput.value = result;
}
function onDecreaseClick(e) {
  result = rgbToHex(desaturateByTenth(hexToRgb(colorInput.value)));
  hashOutput.textContent = result;
  rgbOutput.textContent = hexToRgb(result);
  colorInput.value = result;
}

//manipulation functions

function hexToRgb(h) {
  var r = parseInt(cutHex(h).substring(0, 2), 16),
    g = parseInt(cutHex(h).substring(2, 4), 16),
    b = parseInt(cutHex(h).substring(4, 6), 16);
  return "rgb(" + r + "," + g + "," + b + ")";
}
function cutHex(h) {
  return h.charAt(0) == "#" ? h.substring(1, 7) : h;
}

function rgbToHex(rgb) {
  const [red, green, blue] = rgb
    .replace(/ /g, "")
    .slice(4, -1)
    .split(",")
    .map((e) => parseInt(e));
  return "#" + toHex(red) + toHex(green) + toHex(blue);
}

function toHex(n) {
  n = parseInt(n, 10);
  if (isNaN(n)) return "00";
  n = Math.max(0, Math.min(n, 255));
  return (
    "0123456789ABCDEF".charAt((n - (n % 16)) / 16) +
    "0123456789ABCDEF".charAt(n % 16)
  );
}

function getLightnessOfRGB(rgbString) {
  // First convert to an array of integers by removing the whitespace, taking the 3rd char to the 2nd last then splitting by ','
  const rgbIntArray = rgbString
    .replace(/ /g, "")
    .slice(4, -1)
    .split(",")
    .map((e) => parseInt(e));

  // Get the highest and lowest out of red green and blue
  const highest = Math.max(...rgbIntArray);
  const lowest = Math.min(...rgbIntArray);
  // Return the average divided by 255
  return (highest + lowest) / 2 / 255;
}

function saturateByTenth(rgb) {
  const rgbIntArray = rgb
    .replace(/ /g, "")
    .slice(4, -1)
    .split(",")
    .map((e) => parseInt(e));
  const greyVal = getLightnessOfRGB(rgb) * 255;
  const [lowest, middle, highest] = getLowestMiddleHighest(rgbIntArray);

  if (lowest.val === highest.val) {
    return rgb;
  }

  const saturationRange = Math.round(Math.min(255 - greyVal, greyVal));
  const maxChange = Math.min(255 - highest.val, lowest.val);
  const changeAmount = Math.min(saturationRange / 10, maxChange);
  const middleValueRatio = (greyVal - middle.val) / (greyVal - highest.val);

  const returnArray = [];
  returnArray[highest.index] = Math.round(highest.val + changeAmount);
  returnArray[lowest.index] = Math.round(lowest.val - changeAmount);
  returnArray[middle.index] = Math.round(
    greyVal + (returnArray[highest.index] - greyVal) * middleValueRatio
  );
  return `rgb(${[returnArray].join()})`;
}

function desaturateByTenth(rgb) {
  const rgbIntArray = rgb
    .replace(/ /g, "")
    .slice(4, -1)
    .split(",")
    .map((e) => parseInt(e));
  const [lowest, middle, highest] = getLowestMiddleHighest(rgbIntArray);
  const greyVal = getLightnessOfRGB(rgb) * 255;

  //if colour is just grey, return grey
  if (lowest.val === highest.val) {
    return rgb;
  }

  const saturationRange = Math.round(Math.min(255 - greyVal, greyVal));
  const maxChange = greyVal - lowest.val;
  const changeAmount = Math.min(saturationRange / 10, maxChange);

  const middleValueRatio = (greyVal - middle.val) / (greyVal - highest.val);

  const returnArray = [];
  returnArray[highest.index] = Math.round(highest.val - changeAmount);
  returnArray[lowest.index] = Math.round(lowest.val + changeAmount);
  returnArray[middle.index] = Math.round(
    greyVal + (returnArray[highest.index] - greyVal) * middleValueRatio
  );
  return `rgb(${[returnArray].join()})`;
}

function getLowestMiddleHighest(rgbIntArray) {
  let highest = { val: 0, index: -1 };
  let lowest = { val: Infinity, index: -1 };

  rgbIntArray.map((val, index) => {
    if (val > highest.val) {
      highest = { val: val, index: index };
    }
    if (val < lowest.val) {
      lowest = { val: val, index: index };
    }
  });

  let middle = { index: 3 - highest.index - lowest.index };
  middle.val = rgbIntArray[middle.index];
  return [lowest, middle, highest];
}

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

This Pen doesn't use any external JavaScript resources.