Image processing with polka-dots

A cat photo in polka-dot pattern with this generative art code.
Close-Up Photography of Tabby Cat · Free Stock Photo

This generative art is a cat photo in polka‐dot pattern.
Close Up of Brown Tabby Cat · Free Stock Photo

 

About this image manipulation.

It's an image manipulation type generative art made with the 'Processing'.

I tried to draw a cat's photo in a polka‐dot pattern.
Oops, typo! To be exact in a 'purrka-dot'. ;-)

I used edge detection in this code to make the result more clearly.







 

 

The 'Processing' example codes (Java).

You can use your own photo.

You can hard coding the path of your photo image file or you can give command line parameter like this.

/your_processing_installed_path/processing-java --force --sketch=/your_sketch_path/ --run /your_photo_file_path

 

Current version code.

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.


/**
 * Purrka Dots.
 * Draw image with color dot.
 * run : processing-java --force --sketch=/path/to/PurrkaDots/ --run "image path"
 * 
 * @author @deconbatch
 * @version 0.4
 * created 0.1 2017.10.22
 * updated 0.2 2017.10.28 Many micro dots + little giant dots, no middle dots
 * updated 0.3 2018.12.23
 * updated 0.4 2019.03.21 rewrote whole codes with same concept, less dots, use edge detection
 * 
 * Processing 3.2.1
 * 2019.03.21
 */

import java.util.Random;

void setup() {

  size(1080, 1080);
  colorMode(HSB, 360, 100, 100, 100);
  smooth();
  noStroke();
  noLoop();

}

void draw() {

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

  // I brought dots pattern parameters in class.
  DotsParams bp = new BackgroundParams();
  DotsParams sp = new SpotParams();
  DotsParams ep = new EdgeParams();
  DotsParams dp = new DetailParams();
  
  ImageLoader imgLoader = new ImageLoader(baseCanvas);
  PImage img = imgLoader.load();

  // edge detection
  int edgeAry[][] = detectEdge(img);

  background(0.0, 0.0, 90.0, 100.0);
  translate((width - img.width) / 2, (height - img.height) / 2);

  // draw dots pattern
  putDots(bp, img, edgeAry);
  putDots(sp, img, edgeAry);
  putDots(ep, img, edgeAry);
  putDots(dp, img, edgeAry);
 
  saveFrame("frames/0001.png");
  exit();

}

/**
 * putDots : draw dots.
 * @param  _dp       : dots pattern parameters class.
 * @param  _img      : origimal photo image.
 * @param  _edge     : detented edge information.
 */
private void putDots(DotsParams _dp, PImage _img, int[][] _edge) {

  int   drawCntMax = _dp.drawCntMax();
  int   idxDiv     = _dp.initDiv();
  float baseSiz    = _dp.baseSize();

  Utils ut = new Utils();

  for (int drawCnt = 1; drawCnt < drawCntMax; ++drawCnt) {

    float prmSat = map(drawCnt, 1, drawCntMax, 1.0, 0.4);
    float prmAlp = map(drawCnt, 1, drawCntMax, 1.0, 0.0);
    
    for (int idxH = 0; idxH < _img.height; idxH += idxDiv) {
      for (float idxW = 0; idxW < _img.width; idxW += idxDiv) {
      
        float brushAlp = ut.gaussdist(50.0, 30.0, 20.0) * prmAlp;
        float brushSiz = baseSiz * (0.5 + ut.gaussdist(0.5, 0.5, 0.2));

        int pointW = constrain(round(idxW + ut.gaussdist(0.0, idxDiv * 0.6, idxDiv * 0.3)), 0, _img.width - 1);
        int pointH = constrain(round(idxH + ut.gaussdist(0.0, idxDiv * 0.6, idxDiv * 0.3)), 0, _img.height - 1);

        if (_dp.isTarget(_edge, pointW, pointH)) {
          color cPoint = _img.pixels[pointH * _img.width + pointW];
          fill(hue(cPoint), saturation(cPoint), brightness(cPoint), brushAlp);
          ellipse(pointW, pointH, brushSiz, brushSiz);
        }   

      }
    }
  }
}

/* ---------------------------------------------------------------------- */
/**
 * Utils : utility methods
 */
private class Utils {

  Random rnd;

  Utils() {
    rnd = new Random();
  }

  /**
   * gaussdist : returns Gaussian distributed random.
   * @param  _mean      : mean value of Gaussian distribution
   * @param  _limit     : max value of abs(deviation)
   * @param  _deviation : standard deviation of Gaussian distribution
   * @return float      : _mean - _limit < Gaussian distributed random < _mean + _limit
   */
  private float gaussdist(float _mean, float _limit, float _deviation) {
    if (_limit == 0) {
      return _mean;
    }

    float gauss = (float) rnd.nextGaussian() * _deviation;
    // not good idea
    if (abs(gauss) > _limit) {
      gauss = pow(_limit, 2) / gauss;
    }
    return _mean + gauss;

  }
}

/**
 * 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;
}

/**
 * DotsParams : holding dots pattern parameters.
 */
interface DotsParams {

  /**
   * 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   drawCntMax();
  int   initDiv();
  float baseSize();

}

public class BackgroundParams implements DotsParams {
  
  public Boolean isTarget(int _points[][], int _x, int _y) {
    // every point
    return true;
  }

  public int drawCntMax() {
    return 3;
  }
  public int initDiv() {
    return 500;
  }
  public float baseSize() {
    return 150;
  }

}

public class EdgeParams implements DotsParams {

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

  public int drawCntMax() {
    return 10;
  }
  public int initDiv() {
    return 20;
  }
  public float baseSize() {
    return 30;
  }
  
}

public class SpotParams extends EdgeParams {

  public int drawCntMax() {
    return 5;
  }
  public int initDiv() {
    return 100;
  }
  public float baseSize() {
    return 60;
  }
  
}

public class DetailParams extends EdgeParams {

  public int drawCntMax() {
    return 20;
  }
  public int initDiv() {
    return 4;
  }
  public float baseSize() {
    return 2;
  }
  
}

/**
 * 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;
  }

}

/*
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/>
*/


 

Older version result images.


It makes a cat photo into polka-dot pattern.

Image manipulation with the image from
Cat and Kitten looking out Door
http://www.photos-public-domain.com/2010/08/28/cat-and-kitten-looking-out-door/


It makes a cat photo into polka-dot pattern.

Image manipulation with the image from
Orange And White Cat Closeup
http://www.photos-public-domain.com/2011/01/07/orange-and-white-cat-closeup/ 


It makes a cat photo into polka-dot pattern.

Image manipulation with the image with
Fat Cat with Greens in Sunbeam
http://www.photos-public-domain.com/2010/08/28/fat-cat-with-greens-in-sunbeam/






 

Older version code.



// Purrka Dots.
// @author @deconbatch
// @version 0.3
// Processing 3.2.1
// 0.1 2017.10.22
// 0.2 2017.10.28 Many micro dots + little giant dots, no middle dots
// 0.3 2018.12.23

import java.util.Random;

/* ---------------------------------------------------------------------- */
class Utils {

  Random obj_random;

  Utils() {
    obj_random = new Random();
  }

  float gaussdist(float pmean, float plimit, float pdevi) {
    /**
       Gaussian distribution
       1.parameters.
       pmean  : mean value
       plimit : max value of abs(deviation)
       ex. plimit >= 0
       pmean = 0.5, plimit = 0.5 -> return value = from 0.0 to 1.0
       pdevi  : standard deviation value
       ex. good value? -> pdevi = plimit / 2
       2.return.
       gaussian distribution
    **/

    if (plimit == 0) {
      return pmean;
    }

    float gauss = (float) obj_random.nextGaussian() * pdevi;
    // not good idea
    if (abs(gauss) > plimit) {
      gauss = pow(plimit, 2) / gauss;
    }

    return pmean + gauss;
    
  }

}

/* ---------------------------------------------------------------------- */
abstract class PshapeElement {

  PShape anElement;
  float elementColor, elementSaturation, elementBright, elementAlpha;
  
  PshapeElement() {
    anElement = pscreateElement();
    elementColor = 0;
    elementSaturation = 0;
    elementBright = 0;
    elementAlpha = 0;
  }

  abstract PShape pscreateElement();

  void setElementFill(float pcolor, float psaturation, float pbright, float palpha) {
    elementColor = pcolor;
    elementSaturation = psaturation;
    elementBright = pbright;
    elementAlpha = palpha;
    resetColor();
  }

  void resetColor() {
    anElement.setFill(color(elementColor, elementSaturation, elementBright, elementAlpha));
  }

  void changeColor(float scolor) {
    elementColor = scolor;
    resetColor();
  }

  void changeBright(float sbright) {
    elementBright = sbright;
    resetColor();
  }

  void resetSize() {
    anElement.resetMatrix();
  }

  void changeSize(float scaleX, float scaleY) {
    anElement.scale(scaleX, scaleY);
  }

  void rotate(float radX) {
    anElement.rotate(radX);
  }

  void show() {
    shape(anElement);
  }

}

/* ---------------------------------------------------------------------- */
class Brush extends PshapeElement {
  
  Brush() {
    super();
  }

  PShape pscreateElement() {

    noStroke();
    //    strokeWeight(0.02);
    //    stroke(0.0, 0.0, 90.0, 5.0);
    fill(0.0, 0.0, 0.0, 0.0);
    PShape psDp = createShape(ELLIPSE, 0.0, 0.0, 1.0, 1.0);
    return psDp;

  }

}

/* ---------------------------------------------------------------------- */
void drawCircles() {

  int idxSetMax = 5000;
  int idxSetDiv = 1;
  
  float marginW = (width - img.width) / 2.0;
  float marginH = (height - img.height) / 2.0;

  float idxHStart = marginH + img.height / 6.0;
  float idxHEnd = 60 + height - (marginH + img.height / 6.0);
  float idxHDiv = img.height / 3.0;
  float idxWStart = marginW;
  float idxWEnd = width - marginW;

  float nsHueStart = random(10.0);
  float nsSatStart = random(10.0);
  float nsAlpStart = random(10.0);

  for (int idxSet = 1; idxSet <= idxSetMax; idxSet += idxSetDiv) {

    // many micro dots
        if (idxSet > 1200 & idxSet % 10 == 0) {
          ++idxSetDiv;
        }
    
    float nsSat = nsSatStart;
    float prmSat = map(idxSet, 1, idxSetMax, 1.0, 0.8);
    float prmAlp = map(idxSet, 1, idxSetMax, 1.0, 0.1);
    float idxWDiv = pow(map(idxSet, 1, idxSetMax, 1.0, 8.0), 3); // little giant dots
    

    for (float idxH = idxHStart; idxH < idxHEnd; idxH += idxHDiv) {

      float nsHue = nsHueStart;
      float nsAlp = nsAlpStart;

      for (float idxW = idxWStart; idxW < idxWEnd; idxW += idxWDiv * 0.5) {

        float brushSiz = (0.1 + abs(ut.gaussdist(0.0, 10.0, 0.1))) * idxWDiv;
        float brushHue = ut.gaussdist(map(noise(nsHue), 0.0, 1.0, 190.0, 420.0), 60.0, 20.0) % 360.0; // avoid green
        float brushSat = map(noise(nsSat), 0.0, 1.0, 40.0, 60.0) * prmSat;
        float brushBri = 90;
        float brushAlp = map(noise(nsAlp), 0.0, 1.0, 20.0, 100.0) * prmAlp;
      
        float pointW = idxW + ut.gaussdist(0.0, brushSiz * 3.0, brushSiz);
        float pointH = idxH + ut.gaussdist(0.0, img.height / 2.5, img.height / 8.0);

        // photo area
        if (pointW >= marginW & pointW < img.width + marginW & pointH >= marginH & pointH < img.height + marginH) {

          color cPoint = img.pixels[floor(pointH - marginH) * img.width + floor(pointW - marginW)];

          // avoid green
          float zeroOrigin = (brushHue + hue(cPoint) + 170) % 360; // 190 = 0, 420 = 230
          float zeroTo230 = (zeroOrigin + 230) % 230; // 0-360 -> 0-230
          brushHue = (zeroTo230 + 190) % 360; // 0-230 -> 0-60, 190-360
          brushSat *= map(saturation(cPoint), 0.0, 100.0, 0.8, 1.1) * map(brightness(cPoint), 0.0, 100.0, 1.2, 0.5);
          brushBri *= map(brightness(cPoint), 0.0, 100.0, 0.8, 1.1);
          brushAlp *= map(brightness(cPoint), 0.0, 100.0, 1.5, 0.1);

        }
      
        pushMatrix();
        translate(pointW, pointH);
        brush.resetSize();
        brush.changeSize(brushSiz, brushSiz);
        brush.setElementFill(brushHue, brushSat, brushBri, brushAlp);
        brush.show();
        popMatrix();
        
        nsHue += 0.0015 * idxWDiv / 3.0;
        nsSat += 0.012 * idxWDiv;
        nsAlp += 0.016 * idxWDiv;

      }
    }
  }
}

/* ---------------------------------------------------------------------- */
Utils ut;
PImage img;
PshapeElement brush;

void setup() {
  size(1080, 1080);
  float baseWH = 980;
  colorMode(HSB, 360, 100, 100, 100);
  smooth(8);
  noLoop();

  ut = new Utils();  
  brush = new Brush();

  if (args == null) {
    // you can use your photo in ./data/your_image.jpg
    img = loadImage("your_image.jpg");
  } else {
    // args[0] must be image path
    img = loadImage(args[0]);
  }
  
  // fit to canvas size
  float rateSize = baseWH / max(img.width, img.height);
  float canvasW = img.width * rateSize;
  float canvasH = img.height * rateSize;
  img.resize(int(canvasW), int(canvasH));
  img.loadPixels();

}

void draw() {

  background(0.0, 0.0, 90.0, 100.0);
  translate(0.0, 0.0);
  
  drawCircles();

  saveFrame("frames/####.png");
  exit();

}

/*
Copyright (C) 2017- 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