Image manipulation with the Vector Field method

A generative art with cat photo that uses Vector Field method.
Close-Up Photography of Tabby Cat · Free Stock Photo

A generative art with cat photo that uses Vector Field method.
Close Up of Brown Tabby Cat · Free Stock Photo

 

Image manipulation using the Vector Field method.

It's an image manipulation type creative coding example made with the 'Processing'.

This is another implementation of SteaMew. I use custom noise and draw the Vector Field with nice parameters.

This code does not display any images on the screen but generates image file.

 







The 'Processing' code example.

Please feel free to use this example code under the terms of the GPL. To see other works based on my code is my pleasure. And my honor.


/**
 * PussWay.
 * Draw narrow line with start point color and path way saturation/brightness.
 * run : processing-java --force --sketch=/path/to/PussWay/ --run "image path"
 * 
 * @author @deconbatch
 * @version 0.1
 * Processing 3.2.1
 * 2019.03.16
 */

/**
 * main
 */
void setup() {

  size(1080, 1080);
  colorMode(HSB, 360.0, 100.0, 100.0, 100.0);
  smooth();
  noLoop();
  noStroke();

}

void draw() {

  int caseWidth  = 30;
  int baseCanvas = width - caseWidth * 2;

  // I brought vector field parameters in class.
  VectorParams bp = new BackgroundParams();
  VectorParams ep = new EdgeParams();
  VectorParams dp = new DetailEdgeParams();

  ImageLoader imgLoader = new ImageLoader(baseCanvas);
  PImage img = imgLoader.load();
    
  int edgeAry[][] = detectEdge(img);
    
  translate((width - img.width) / 2, (height - img.height) / 2);
  background(0.0, 0.0, 90.0, 100.0);

  // draw vector field pattern with nice parameters.
  drawVector(bp, img, edgeAry);
  drawVector(ep, img, edgeAry);
  drawVector(dp, img, edgeAry);

  casing(caseWidth, img.width, img.height);
  saveFrame("frames/oioi0001.png");

  exit();

}

/**
 * drawVector : draw vector field.
 * @param  _vp       : vector field parameters class.
 * @param  _img      : origimal photo image.
 * @param  _edge     : detented edge information.
 */
private void drawVector(VectorParams _vp, PImage _img, int[][] _edge) {

  int   plotCntMax = _vp.plotCntMax();
  int   initDiv    = _vp.initDiv();
  float baseSiz    = _vp.baseSiz();

  // draw vector field with nice parameters
  float noiseDiv = 0.001;
  float xFrom = random(900.0, 1000.0);
  float yFrom = random(700.0, 800.0);
  
  for (int xInit = 0; xInit < _img.width; xInit += initDiv) {
    for (int yInit = 0; yInit < _img.height; yInit += initDiv) {
      if (_vp.isTarget(_edge, xInit, yInit)) {
        
        color original = _img.pixels[yInit * _img.width + xInit];

        // need to start like this to get nice calculation
        float xOrigin  = xFrom + xInit * 0.05;
        float yOrigin  = yFrom + yInit * 0.05;
        float xCurrent = xOrigin;
        float yCurrent = yOrigin;

        for (int plotCnt = 0; plotCnt < plotCntMax; ++plotCnt) {

          float pRatio = map(plotCnt, 0, plotCntMax, 0.0, 1.0);

          float xPrev = xCurrent;
          float yPrev = yCurrent;
          xCurrent += cos(TWO_PI * customNoise(yPrev * noiseDiv, yPrev * noiseDiv * 10.0) * 8.0);
          yCurrent += cos(TWO_PI * customNoise(xPrev * noiseDiv, xPrev * noiseDiv * 10.0) * 8.0);

          int xCol = floor(constrain(xCurrent - xOrigin + xInit, 0, _img.width - 1));
          int yCol = floor(constrain(yCurrent - yOrigin + yInit, 0, _img.height - 1));
          color pathway = _img.pixels[yCol * _img.width + xCol];

          float eHue  = hue(original);
          float eSat  = saturation(pathway);
          float eBri  = brightness(pathway);
          float eAlp  = 100.0 * (1.0 - pRatio);
          float eSiz  = pow(baseSiz * sin(PI * pRatio), 2);

          fill(eHue % 360.0, eSat, eBri, eAlp);
          ellipse(xCol, yCol, eSiz, eSiz);

        }
      }
    }
  }
}
  
/**
 * customNoise : returns -1.0 .. 1.0 almost random but interesting value
 */
private float customNoise(float _x, float _y) {
  return pow(sin(_x), 3) * cos(pow(_y, 2));
}

/**
 * casing : draw fancy casing
 */
private void casing(int _casing, float _w, float _h) {
  fill(0.0, 0.0, 0.0, 0.0);
  strokeWeight(_casing + 4.0);
  stroke(0.0, 0.0, 30.0, 100.0);
  rect(-_casing * 0.5, -_casing * 0.5, _w + _casing, _h + _casing);
  strokeWeight(_casing);
  stroke(0.0, 0.0, 100.0, 100.0);
  rect(-_casing * 0.5, -_casing * 0.5, _w + _casing, _h + _casing);
  noStroke();
  noFill();
}

/**
 * detectEdge : detect edge of photo image.
 * @param  _img      : detect edge of thid image.
 * @return int[x][y] : 2 dimmension array. it holds 0 or 1, 1 = edge
 */
private int[][] detectEdge(PImage _img) {

  int edgeAry[][] = new int[_img.width][_img.height];
  for (int idxW = 0; idxW < _img.width; ++idxW) {  
    for (int idxH = 0; idxH < _img.height; ++idxH) {
      edgeAry[idxW][idxH] = 0;
    }
  }
    
  _img.loadPixels();
  for (int idxW = 1; idxW < _img.width - 1; ++idxW) {  
    for (int idxH = 1; idxH < _img.height - 1; ++idxH) {

      int pixIndex = idxH * _img.width + idxW;

      // saturation difference
      float satCenter = saturation(_img.pixels[pixIndex]);
      float satNorth  = saturation(_img.pixels[pixIndex - _img.width]);
      float satWest   = saturation(_img.pixels[pixIndex - 1]);
      float satEast   = saturation(_img.pixels[pixIndex + 1]);
      float satSouth  = saturation(_img.pixels[pixIndex + _img.width]);
      float lapSat = pow(
                         - satCenter * 4.0
                         + satNorth
                         + satWest
                         + satSouth
                         + satEast
                         , 2);

      // brightness difference
      float briCenter = brightness(_img.pixels[pixIndex]);
      float briNorth  = brightness(_img.pixels[pixIndex - _img.width]);
      float briWest   = brightness(_img.pixels[pixIndex - 1]);
      float briEast   = brightness(_img.pixels[pixIndex + 1]);
      float briSouth  = brightness(_img.pixels[pixIndex + _img.width]);
      float lapBri = pow(
                         - briCenter * 4.0
                         + briNorth
                         + briWest
                         + briSouth
                         + briEast
                         , 2);

      // hue difference
      float hueCenter = hue(_img.pixels[pixIndex]);
      float hueNorth  = hue(_img.pixels[pixIndex - _img.width]);
      float hueWest   = hue(_img.pixels[pixIndex - 1]);
      float hueEast   = hue(_img.pixels[pixIndex + 1]);
      float hueSouth  = hue(_img.pixels[pixIndex + _img.width]);
      float lapHue = pow(
                         - hueCenter * 4.0
                         + hueNorth
                         + hueWest
                         + hueSouth
                         + hueEast
                         , 2);

      // bright and saturation difference
      if (
          brightness(_img.pixels[pixIndex]) > 30.0
          && lapSat > 20.0
          ) edgeAry[idxW][idxH] = 1;

      // bright and some saturation and hue difference
      if (
          brightness(_img.pixels[pixIndex]) > 30.0
          && saturation(_img.pixels[pixIndex]) > 10.0
          && lapHue > 100.0
          ) edgeAry[idxW][idxH] = 1;

      // just brightness difference
      if (lapBri > 100.0) edgeAry[idxW][idxH] = 1;

    }
  }

  return edgeAry;
}

/**
 * ImageLoader : load and resize image
 */
public class ImageLoader {

  PImage imgInit;
  String imgPass;

  ImageLoader(int baseCanvas) {

    if (args == null) {
      // you can use your photo in ./data/your_image.jpg
      imgPass = "your_image.jpg";
    } else {
      // args[0] must be image path
      imgPass = args[0];
    }    
    imgInit = loadImage(imgPass);
      
    float rateSize = baseCanvas * 1.0 / max(imgInit.width, imgInit.height);
    imgInit.resize(floor(imgInit.width * rateSize), floor(imgInit.height * rateSize));

    println(int(imgInit.width)); // Image width
    println(int(imgInit.height)); // Image height

  }

  /**
   * load : return loaded image
   */
  public PImage load() {
    return imgInit;
  }

}

/**
 * VectorParams : holding vector field parameters.
 */
interface VectorParams {

  /**
   * isTarget : is this point(x, y) drawing target?
   * @return true : draw target
   */
  Boolean isTarget(int _points[][], int _x, int _y);

  /**
   * just returns parameter value
   */
  int   plotCntMax();
  int   initDiv();
  float baseSiz();

}

public class BackgroundParams implements VectorParams {
  
  public Boolean isTarget(int _points[][], int _x, int _y) {
    // every point
    return true;
  }
  
  public int plotCntMax() {
    return 3000;
  }
  public int initDiv() {
    return 50;
  }
  public float baseSiz() {
    return 1.0;
  }

}

public class EdgeParams implements VectorParams {

  public Boolean isTarget(int _points[][], int _x, int _y) {
    // only edge is target
    if (_points[_x][_y] == 1) {
      return true;
    }
    return false;
  }

  public int plotCntMax() {
    return 500;
  }
  public int initDiv() {
    return 10;
  }
  public float baseSiz() {
    return 1.1;
  }
  
}

public class DetailEdgeParams extends EdgeParams {

  public int plotCntMax() {
    return 300;
  }
  public int initDiv() {
    return 5;
  }
  public float baseSiz() {
    return 1.2;
  }
  
}

/*
Copyright (C) 2019- deconbatch

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>
*/


 

Next Post Previous Post
No Comment
Add Comment
comment url