Tile glass effect on the original image

Original image.

original photo by Pixabay.

Photo by Pixabay.

The tile glass (or pixelated glass?) effect creative coding.

It's an image manipulation code that adds a tile glass effect on the original image. This code was written in the Processing programming language.

I used the Worley noise method in this code and I calculated the pixel color with the distance from the nearest node like this.

float r = constrain(minDist / _range, 0.0, 1.0);
Tile glass effect on the original image

You can change this calculation formula and you'll get an interesting result.

float r = abs(cos(constrain(minDist / _range, 0.0, 1.0) * PI));
Tile glass effect on the original image


Reference.

Inspired by these cats.
Low Resolution Cats Behind Pixelated Glass Doors.

Worley Noise.
Worley Noise - Coding in the Cabana Challenge #4 · The Coding Train

A similar work using the Worley noise.
Break Up to Make Up : Adding a Cubism taste to the original image.
Looking Through A Stained Glass Darkly.


An example code of Processing.

This code does not display any images on the screen but generates image files in frames directory.
Please feel free to use this example code. To see other works based on my code is my pleasure. And my honor.



/**
 * These Eyes.
 * Adds a tile glass effect on the original image.
 * ref. https://www.sadanduseless.com/pixel-cat-windows-gallery/
 *
 * @author @deconbatch
 * @version 0.1
 * @license GPL Version 3 http://www.gnu.org/licenses/
 * Processing 3.5.3
 * 2020.12.23
 */

void setup() {
  size(1080, 1080);
  colorMode(HSB, 360.0, 100.0, 100.0, 100.0);
  smooth();
  noLoop();
}

void draw() {

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

  PImage img = loadImage("your_photo.jpg");
  float rateSize = baseCanvas * 1.0 / max(img.width, img.height);
  img.resize(floor(img.width * rateSize), floor(img.height * rateSize));

  translate((width - img.width) / 2, (height - img.height) / 2);

  // squre mesh
  background(0.0, 0.0, 90.0, 100.0);
  drawWorley(img, getMesh(img, 40, 0.0), 50.0);
  casing(caseWidth, img.width, img.height);
  saveFrame("frames/te0001.png");

  // with little randomness
  background(0.0, 0.0, 90.0, 100.0);
  drawWorley(img, getMesh(img, 40, 0.5), 80.0);
  casing(caseWidth, img.width, img.height);
  saveFrame("frames/te0002.png");

  exit();
  
}

/**
 * getMesh : locate Nodes on mesh with some randomness.
 * @param _img  : original image to get color.
 * @param _step : mesh step.
 * @param _rate : lack rate, 0.0 : no lack, 1.0 totally lack.
 * @return ArrayList<Node> : holds nodes.
 */
public ArrayList<Node> getMesh(PImage _img, int _step, float _rate) {

  int   start = floor(_step * 0.5);
  ArrayList<Node> nodes = new ArrayList<Node>();
  _img.loadPixels();

  for (int fX = start; fX < _img.width; fX += _step) {
    for (int fY = start; fY < _img.height; fY += _step) {
      if (random(1.0) >= _rate) {
        // add node
        int pixIndex = floor(fY * _img.width + fX);
        nodes.add(new Node(
                           fX,
                           fY,
                           hue(_img.pixels[pixIndex]),
                           saturation(_img.pixels[pixIndex]),
                           brightness(_img.pixels[pixIndex])
                           ));
      }
    }
  }
  return nodes;
}

/**
 * drawWorley : draw 
 * @param _img   : original image.
 * @param _nodes : point's location and color.
 * @param _range : distance range, lower value will make strong result.
 */
public void drawWorley(PImage _img, ArrayList<Node> _nodes, float _range) {

  noFill();
  strokeWeight(1.0);
  
  float heighValue = _img.width + _img.height;
  for (int iX = 0; iX < _img.width; iX++) {
    for (int iY = 0; iY < _img.height; iY++) {
      // get nearest node
      int   minIndx = 0;
      float minDist = heighValue;
      for (int i = 0; i < _nodes.size(); i++) {
        float distance = dist(iX, iY, _nodes.get(i).x, _nodes.get(i).y);
        if (minDist > distance) {
          minIndx = i;
          minDist = distance;
        }
      }

      Node  n = _nodes.get(minIndx);
      float t = atan2(iY - n.y, iX - n.x);
      float r = constrain(minDist / _range, 0.0, 1.0);

      int   nX = round(n.x + minDist * r * cos(t));
      int   nY = round(n.y + minDist * r * sin(t));
      int   nI = floor(nY * _img.width + nX);

      float sHue = hue(_img.pixels[nI]);
      float sSat = saturation(_img.pixels[nI]) * (1.0 + r) * 0.8;
      float sBri = brightness(_img.pixels[nI]) * (1.2 - r * 0.3);
      stroke(sHue, sSat, sBri, 100.0);
      point(iX, iY);
    }
  }
}

/**
 * casing : draw fancy casing
 */
public 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);
}

/**
 * Node : draw and hold location and color.
 */
public class Node {

  public  int   x, y;   // location of point
  // did not use this time.
  private float hueVal; // hue value of point
  private float satVal; // saturation value of point
  private float briVal; // brightness value of point

  Node(int _x, int _y, float _c, float _s, float _b) {
    x = _x;
    y = _y;
    hueVal = _c;
    satVal = _s;
    briVal = _b;
  }

}


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