Home > Net >  Image filter gets stronger every loop - p5.js
Image filter gets stronger every loop - p5.js

Time:03-10

I have made my own image filters sepia and blur. When the mouse is clicked on the original (left) image, it is meant to create a radial blur effect on the processed (right) image. However, after testing it I have found that it only works if I have my mouse over the picture as it's loading. Then, if I click my mouse, the original picture is processed and it looks like the filter gets stronger on the already processed image to the right like it gets another filter applied on top. This continues the more I click. Would anyone have an idea of what I've done wrong? I'm not sure where the issue with my code is.

var imgIn;

function preload() {
  imgIn = loadImage("https://media.makeameme.org/created/doesnt-know-why-gxsyb3.jpg");
}

function setup() {
  createCanvas((imgIn.width * 2), imgIn.height);
}

function draw() {
  background(125);
  image(imgIn, 0, 0);
  image(earlyBirdFilter(imgIn), imgIn.width, 0);
  noLoop();
}

function mousePressed() {
  loop();
}

function earlyBirdFilter(img) {
  var resultImg = createImage(img.width, img.height);

  resultImg = sepiaFilter(img);
  resultImg = radialBlurFilter(resultImg);

  resultImg.updatePixels();
  return resultImg;
}

function sepiaFilter(input) {
  input.loadPixels();

  for (var x = 0; x < input.width; x  ) {
    for (var y = 0; y < input.height; y  ) {
      var index = (y * input.width   x) * 4;

      var oldRed = input.pixels[index   0];
      var oldGreen = input.pixels[index   1];
      var oldBlue = input.pixels[index   2];

      var newRed = (oldRed * .393)   (oldGreen * .769)   (oldBlue * .189)
      var newGreen = (oldRed * .349)   (oldGreen * .686)   (oldBlue * .168)
      var newBlue = (oldRed * .272)   (oldGreen * .534)   (oldBlue * .131)

      input.pixels[index   0] = newRed;
      input.pixels[index   1] = newGreen;
      input.pixels[index   2] = newBlue;
      input.pixels[index   3] = 255;

    }
  }
  input.updatePixels();
  return input;

}

function radialBlurFilter(input) {
  var matrixSize = matrix.length;

  input.loadPixels();

  for (var x = 0; x < input.width; x  ) {
    for (var y = 0; y < input.height; y  ) {

      var index = (x   y * input.width) * 4;
      var c = convolution(x, y, matrix, matrixSize, input);
      var d = dist(mouseX, mouseY, x, y);
      var m = map(d, 100, 300, 0, 1);
      var dynBlur = constrain(m, 0, 1);
      var r = input.pixels[index   0];
      var g = input.pixels[index   1];
      var b = input.pixels[index   2];

      input.pixels[index   0] = c[0] * dynBlur   r * (1 - dynBlur);
      input.pixels[index   1] = c[1] * dynBlur   g * (1 - dynBlur);
      input.pixels[index   2] = c[2] * dynBlur   b * (1 - dynBlur);
      input.pixels[index   3] = 255;
    }
  }
  input.updatePixels();
  return input;
}

function convolution(x, y, matrix, matrixSize, input) {
  var totalRed = 0.0;
  var totalGreen = 0.0;
  var totalBlue = 0.0;
  var offset = floor(matrixSize / 2);

  // convolution matrix loop
  for (var i = 0; i < matrixSize; i  ) {
    for (var j = 0; j < matrixSize; j  ) {
      // Get pixel loc within convolution matrix
      var xloc = x   i - offset;
      var yloc = y   j - offset;
      var index = (xloc   input.width * yloc) * 4;

      index = constrain(index, 0, input.pixels.length - 1);

      // multiply all values with the mask and sum up
      totalRed  = input.pixels[index   0] * matrix[i][j];
      totalGreen  = input.pixels[index   1] * matrix[i][j];
      totalBlue  = input.pixels[index   2] * matrix[i][j];
    }
  }
  // return the new color
  return [totalRed, totalGreen, totalBlue];
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.4.0/p5.js"></script>

CodePudding user response:

As Sam mentioned in the comment, you're re-applying the filters to the same pixels each time: hence the cumulative effect.

You could simply make a copy of the input p5.Image using get() and apply the filters to that instead of the original image (which after the first filter is altered):

var imgIn;
var matrix = [[1,1,1],
              [1,3,1],
              [1,1,1]];

function preload() {
  imgIn = loadImage("https://media.makeameme.org/created/doesnt-know-why-gxsyb3.jpg");
}

function setup() {
  createCanvas((imgIn.width * 2), imgIn.height);
}

function draw() {
  background(125);
  image(imgIn, 0, 0);
  image(earlyBirdFilter(imgIn), imgIn.width, 0);
  noLoop();
}

function mousePressed() {
  loop();
}

function earlyBirdFilter(img) {
  var resultImg = createImage(img.width, img.height);

  resultImg = sepiaFilter(img.get());
  resultImg = radialBlurFilter(resultImg);

  resultImg.updatePixels();
  return resultImg;
}

function sepiaFilter(input) {
  input.loadPixels();

  for (var x = 0; x < input.width; x  ) {
    for (var y = 0; y < input.height; y  ) {
      var index = (y * input.width   x) * 4;

      var oldRed = input.pixels[index   0];
      var oldGreen = input.pixels[index   1];
      var oldBlue = input.pixels[index   2];

      var newRed = (oldRed * .393)   (oldGreen * .769)   (oldBlue * .189)
      var newGreen = (oldRed * .349)   (oldGreen * .686)   (oldBlue * .168)
      var newBlue = (oldRed * .272)   (oldGreen * .534)   (oldBlue * .131)

      input.pixels[index   0] = newRed;
      input.pixels[index   1] = newGreen;
      input.pixels[index   2] = newBlue;
      input.pixels[index   3] = 255;

    }
  }
  input.updatePixels();
  return input;

}

function radialBlurFilter(input) {
  var matrixSize = matrix.length;

  input.loadPixels();

  for (var x = 0; x < input.width; x  ) {
    for (var y = 0; y < input.height; y  ) {

      var index = (x   y * input.width) * 4;
      var c = convolution(x, y, matrix, matrixSize, input);
      var d = dist(mouseX, mouseY, x, y);
      var m = map(d, 100, 300, 0, 1);
      var dynBlur = constrain(m, 0, 1);
      var r = input.pixels[index   0];
      var g = input.pixels[index   1];
      var b = input.pixels[index   2];

      input.pixels[index   0] = c[0] * dynBlur   r * (1 - dynBlur);
      input.pixels[index   1] = c[1] * dynBlur   g * (1 - dynBlur);
      input.pixels[index   2] = c[2] * dynBlur   b * (1 - dynBlur);
      input.pixels[index   3] = 255;
    }
  }
  input.updatePixels();
  return input;
}

function convolution(x, y, matrix, matrixSize, input) {
  var totalRed = 0.0;
  var totalGreen = 0.0;
  var totalBlue = 0.0;
  var offset = floor(matrixSize / 2);

  // convolution matrix loop
  for (var i = 0; i < matrixSize; i  ) {
    for (var j = 0; j < matrixSize; j  ) {
      // Get pixel loc within convolution matrix
      var xloc = x   i - offset;
      var yloc = y   j - offset;
      var index = (xloc   input.width * yloc) * 4;

      index = constrain(index, 0, input.pixels.length - 1);

      // multiply all values with the mask and sum up
      totalRed  = input.pixels[index   0] * matrix[i][j];
      totalGreen  = input.pixels[index   1] * matrix[i][j];
      totalBlue  = input.pixels[index   2] * matrix[i][j];
    }
  }
  // return the new color
  return [totalRed, totalGreen, totalBlue];
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.4.1/p5.min.js"></script>

The confusion might come from this section ?

function earlyBirdFilter(img) {
  // you are creating a new image here
  var resultImg = createImage(img.width, img.height);
  // however, here, you're overriding the blank image above with the filtered `img`
  resultImg = sepiaFilter(img);
  resultImg = radialBlurFilter(resultImg);

  resultImg.updatePixels();
  return resultImg;
}

You've got multiple options, but they would all do roughly about the same thing.

One option, as mentioned above, you could simply clone the input image with get() first:

function earlyBirdFilter(img) {
  var resultImg = createImage(img.width, img.height);

  resultImg = sepiaFilter(img.get());
  resultImg = radialBlurFilter(resultImg);
  // this call to `updatePixels()` is a bit redundant since sepiaFilter and radialBlurFilter both call `updatePixels()` on the reference image passed as an argument
  resultImg.updatePixels();
  return resultImg;
}

Another option is to have your filter functions take an image as an input but also return a separate image as an output, for example:

function sepiaFilter(input) {
  // copy input
  let output = input.get();
  input.loadPixels();
  output.loadPixels();

  let numPixels = input.pixels.length;
  for(let index = 0 ; index < numPixels; index  = 4){

    var oldRed   = input.pixels[index   0];
    var oldGreen = input.pixels[index   1];
    var oldBlue  = input.pixels[index   2];

    var newRed   = (oldRed * .393)   (oldGreen * .769)   (oldBlue * .189);
    var newGreen = (oldRed * .349)   (oldGreen * .686)   (oldBlue * .168);
    var newBlue  = (oldRed * .272)   (oldGreen * .534)   (oldBlue * .131);

    output.pixels[index   0] = newRed;
    output.pixels[index   1] = newGreen;
    output.pixels[index   2] = newBlue;
    // copy alpha channel
    output.pixels[index   3] = input.pixels[index   3];

  }

  output.updatePixels();
  return output;
}

(Also notice you call loop over the pixels[] array with a single for loop)

  • Related