Øyvind Kolås' Spachrotyzer   donate Patreon  
returntoggle demo
zsave image
1set dot pattern 1
2set dot pattern 2
3set dot pattern 3
4set dot pattern 4
7white on black
8black on white
9RGB color
10CMYK color
q/aperiod
w/scolor turbulence
g,b,h,n,j,mcolor angle

Spachrotyzer is a client side only webcam newsprint and other trippy halftoning effects filter renderer (contains no code communicating back with servers) - that allows playing with, tuning parameters and store images distorted with various forms of digital halftoning, scroll down on this page for further explanation / code for shader devised for these effects. Tested and reported to work with firefox and chrome, on OSX, win32, android and linux. Sorry, mot working with Safari on neither OSX nor iOS.

Spatio Chromathic Texture Synthesizer

This implementation of variation of AM-halftoning started as an experiment, trying to create a filter that did engraving style lines depending on the source hue/saturation for angle and frequency, the core structure of the filter thus developed was suited also for the core of a glsl shader.

When used with the GEGL tool in GIMP-2.9, it can often be advantagous to first apply a gaussian blur to the input image, this produces nicer smoother isoline/countour line like patterns.

The following code is the core of spachromtyzer, both for the GEGL operation for use with GIMP-2.9 and beyond, and the glsl webgl shader. The code is written for compatibility both with C and webgl's variation of glsl. It takes the coordinates and input colors of one pixel and returns the grayscale level of a corresponding halftoning mask for that pixel, multiple masks are combined to create the composite RGB/CMYK results.

/*
 * Copyright (c) 2017 Øyvind Kolås <pippin@gimp.org>
 *
 * Permission to use, copy, modify, and/or distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */
float spachrotyze (
    float x,
    float y,
    float part_white,
    float offset,
    float hue,
    int   pattern,
    float period,
    float turbulence,
    float blocksize,
    float angleboost,
    float twist)
{
  const float aa  = 2.0;  /* this gives 2x2 MSAA -
                             increase value for better antialiasing*/
  float acc   = 0.0;
  float angle = (1.0-(hue * angleboost) + twist);
  float width = (period * (1.0 - turbulence) +
                (period * offset) * turbulence);

  float vec0 = cosf (-angle * 3.14151 / 2.0);
  float vec1 = sinf (-angle * 3.14151 / 2.0);
  float aa_sq = aa * aa;

  for (float xi = 0.0; xi < aa; xi += 1.0)
  {
    float u = fmodf (x + xi/aa + 0.5 * width, blocksize * width);
    for (float yi = 0.0; yi < aa; yi += 1.0)
    {
      float v = fmodf (y + yi/aa + 0.5 * width, blocksize * width);
      float w = vec0 * u + vec1 * v;
      float q = vec1 * u - vec0 * v;

      float wperiod = fmodf (w, width);
      float wphase  = (wperiod / width) * 2.0 - 1.0;

      float qperiod = fmodf (q, width);
      float qphase  = (qperiod / width) * 2.0 - 1.0;

      if (pattern == 0) {      /* line */
        if (fabsf (wphase) < part_white)
          acc += 1.0 / aa_sq;
      }
      else if (pattern == 1) { /* dot */
        if (qphase * qphase + wphase * wphase <
          part_white * part_white * 2.0)
          acc += 1.0 / aa_sq;
      }
      else if (pattern == 2) { /* diamond */
        if (fabsf(wphase) + fabsf(qphase) < (part_white*2.0) )
          acc += 1.0 / aa_sq;
      }
      else if (pattern == 3) { /* dot-to-diamond-to-dot */
          float ax = fabsf (wphase ) ;
          float ay = fabsf (qphase ) ;
          float v = 0.0;

          if  (ax + ay > 1.0)
          {
            v = 2.0 - sqrtf((1.0-ay) * (1.0-ay) + (1.0-ax) * (1.0-ax));
          }
          else
          {
            v = sqrtf (ay * ay + ax * ax);
          }
          v/=2.0;
          if (v < part_white)
            acc = acc + 1.0 / aa_sq;
      }
    }
  }
  return acc;
}