/*

 ASDFPIXELSORT
 
 */

class ASDFPIXELSORT extends Shader {
  ASDFPIXELSORT() {
    name = "fxASDFPixelSort";
    params.add(new Param("black", INTVAL, -17000000, -2000000, new int[]{SINE, SAWTOOTH, RAMPUPDOWN, TAN, TANINVERSE, TRIANG}));
    params.add(new Param("target", INTVAL, 0, 2, new int[]{RANDOM}));
  }
  int previousMode;

  void apply() {
    if (previousMode != int(params.get(1).value)) {
      if (params.get(1).value == 0) changeParam(0, new Param("black", INTVAL, -17000000, -2000000, new int[]{SINE, SAWTOOTH, RAMPUPDOWN, TAN, TANINVERSE, TRIANG}));
      if (params.get(1).value == 1) changeParam(0, new Param("brightness", INTVAL, 0, 200, new int[]{SINE, SAWTOOTH, RAMPUPDOWN, TAN, TANINVERSE, TRIANG}));
      if (params.get(1).value == 2) changeParam(0, new Param("white", INTVAL, -15000000, -700000, new int[]{SINE, SAWTOOTH, RAMPUPDOWN, TAN, TANINVERSE, TRIANG}));
    }
    previousMode = int(params.get(1).value);
    row = 0;
    column = 0;
    renderer.beginDraw();
    renderer.colorMode(RGB);
    colorMode(RGB);
    while (column < renderer.width-1) {
      renderer.loadPixels();
      sortColumn();
      column++;
      renderer.updatePixels();
    }

    while (row < renderer.height-1) {
      renderer.loadPixels();
      sortRow();
      row++;
      renderer.updatePixels();
    }

    renderer.endDraw();
  }

  int row = 0;
  int column = 0;
  void sortRow() {
    int x = 0;
    int y = row;
    int xend = 0;
    while (xend < renderer.width-1) {
      switch((int)params.get(1).value) {
      case 0:
        x = getFirstNotBlackX(x, y);
        xend = getNextBlackX(x, y);
        break;
      case 1:
        x = getFirstBrightX(x, y);
        xend = getNextDarkX(x, y);
        break;
      case 2:
        x = getFirstNotWhiteX(x, y);
        xend = getNextWhiteX(x, y);
        break;
      default:
        break;
      }

      if (x < 0) break;

      int sortLength = xend-x;

      color[] unsorted = new color[sortLength];
      color[] sorted = new color[sortLength];

      for (int i=0; i<sortLength; i++) {
        unsorted[i] = renderer.pixels[x + i + y * renderer.width];
      }

      sorted = sort(unsorted);

      for (int i=0; i<sortLength; i++) {
        renderer.pixels[x + i + y * renderer.width] = sorted[i];
      }

      x = xend+1;
    }
  }


  void sortColumn() {
    int x = column;
    int y = 0;
    int yend = 0;

    while (yend < renderer.height-1) {
      switch((int)params.get(1).value) {
      case 0:
        y = getFirstNotBlackY(x, y);
        yend = getNextBlackY(x, y);
        break;
      case 1:
        y = getFirstBrightY(x, y);
        yend = getNextDarkY(x, y);
        break;
      case 2:
        y = getFirstNotWhiteY(x, y);
        yend = getNextWhiteY(x, y);
        break;
      default:
        break;
      }

      if (y < 0) break;

      int sortLength = yend-y;
      color[] unsorted = new color[sortLength];
      color[] sorted = new color[sortLength];
      for (int i=0; i<sortLength; i++) {
        unsorted[i] = renderer.pixels[x + (y+i) * renderer.width];
      }

      sorted = sort(unsorted);


      for (int i=0; i<sortLength; i++) {
        renderer.pixels[x + (y+i) * renderer.width] = sorted[i];
      }

      y = yend+1;
    }
  }


  //BLACK
  int getFirstNotBlackX(int _x, int _y) {
    int x = _x;
    int y = _y;
    color c;
    while ( (c = renderer.pixels[x + y * renderer.width]) < params.get(0).value) {
      x++;
      if (x >= renderer.width) return -1;
    }
    return x;
  }

  int getNextBlackX(int _x, int _y) {
    int x = _x+1;
    int y = _y;
    color c;
    while ( (c = renderer.pixels[x + y * renderer.width]) > params.get(0).value) {
      x++;
      if (x >= renderer.width) return renderer.width-1;
    }
    return x-1;
  }

  //BRIGHTNESS
  int getFirstBrightX(int _x, int _y) {
    int x = _x;
    int y = _y;
    color c;
    while (brightness (c = renderer.pixels[x + y * renderer.width]) < params.get(0).value) {
      x++;
      if (x >= renderer.width) return -1;
    }
    return x;
  }

  int getNextDarkX(int _x, int _y) {
    int x = _x+1;
    int y = _y;
    color c;
    while (brightness (c = renderer.pixels[x + y * renderer.width]) > params.get(0).value) {
      x++;
      if (x >= renderer.width) return renderer.width-1;
    }
    return x-1;
  }

  //WHITE
  int getFirstNotWhiteX(int _x, int _y) {
    int x = _x;
    int y = _y;
    color c;
    while ( (c = renderer.pixels[x + y * renderer.width]) > params.get(0).value) {
      x++;
      if (x >= renderer.width) return -1;
    }
    return x;
  }

  int getNextWhiteX(int _x, int _y) {
    int x = _x+1;
    int y = _y;
    color c;
    while ( (c = renderer.pixels[x + y * renderer.width]) < params.get(0).value) {
      x++;
      if (x >= renderer.width) return renderer.width-1;
    }
    return x-1;
  }


  //BLACK
  int getFirstNotBlackY(int _x, int _y) {
    int x = _x;
    int y = _y;
    color c;
    if (y < renderer.height) {
      while ( (c = renderer.pixels[x + y * renderer.width]) < params.get(0).value) {
        y++;
        if (y >= renderer.height) return -1;
      }
    }
    return y;
  }

  int getNextBlackY(int _x, int _y) {
    int x = _x;
    int y = _y+1;
    color c;
    if (y < renderer.height) {
      while ( (c = renderer.pixels[x + y * renderer.width]) > params.get(0).value) {
        y++;
        if (y >= renderer.height) return renderer.height-1;
      }
    }
    return y-1;
  }

  //BRIGHTNESS
  int getFirstBrightY(int _x, int _y) {
    int x = _x;
    int y = _y;
    color c;
    if (y < renderer.height) {
      while (brightness (c = renderer.pixels[x + y * renderer.width]) < params.get(0).value) {
        y++;
        if (y >= renderer.height) return -1;
      }
    }
    return y;
  }

  int getNextDarkY(int _x, int _y) {
    int x = _x;
    int y = _y+1;
    color c;
    if (y < renderer.height) {
      while (brightness (c = renderer.pixels[x + y * renderer.width]) > params.get(0).value) {
        y++;
        if (y >= renderer.height) return renderer.height-1;
      }
    }
    return y-1;
  }

  //WHITE
  int getFirstNotWhiteY(int _x, int _y) {
    int x = _x;
    int y = _y;
    color c;
    if (y < renderer.height) {
      while ( (c = renderer.pixels[x + y * renderer.width]) > params.get(0).value) {
        y++;
        if (y >= renderer.height) return -1;
      }
    }
    return y;
  }

  int getNextWhiteY(int _x, int _y) {
    int x = _x;
    int y = _y+1;
    color c;
    if (y < renderer.height) {
      while ( (c = renderer.pixels[x + y * renderer.width]) < params.get(0).value) {
        y++;
        if (y >= renderer.height) return renderer.height-1;
      }
    }
    return y-1;
  }
}


/*

 DISTORTER
 
 */

class DISTORTER extends Shader {
  boolean do_blend = false; // blend image after process
  int blend_mode = OVERLAY; // blend type
  int channel = BRIGHTNESS; // channel used in processing (R,G,B) or (H,S,B)
  float scalex = 0.05; // from 0.01 to 1
  float scaley = 0.1; // from 0.01 to 1
  boolean shift_hue = true;
  float shift_amt = 0.1; // from 0 to 1

  PImage buffer;

  final static int distortionMatrixSize = 512; //doesnt really make a difference, it's more or less "noise variety".. only kinda different if real low, like 2 or so

  int[][] distort = new int[2][distortionMatrixSize];
  final static float tick = 1.0/distortionMatrixSize;

  int mode = 0;
  int initBufferW, initBufferH;

  DISTORTER() {

    buffer = createImage(renderer.width, renderer.height, ARGB);
    initBufferW = buffer.width;
    initBufferH = buffer.height;

    name = "fxDistorter";
    params.add(new Param("width", FLOATVAL, 2, buffer.width/4-1, new int[]{SINE, SAWTOOTH, RAMPUPDOWN, TAN, TANINVERSE, TRIANG}));
    params.add(new Param("height", FLOATVAL, 2, buffer.height/4-1, new int[]{SINE, SAWTOOTH, RAMPUPDOWN, TAN, TANINVERSE, TRIANG}));
    params.add(new Param("do blend", INTVAL, 0, 1, new int[]{RANDOM, SQUAR}));
    params.add(new Param("shift hue amount", FLOATVAL, 0, 1, new int[]{SAWTOOTH, SAWTOOTHINVERSE, TAN, TANINVERSE, RAMP, RAMPINVERSE}));
    params.add(new Param("scale x", FLOATVAL, 0.01, 1, new int[]{SINE, SAWTOOTH, RAMPUPDOWN, TAN, TANINVERSE, TRIANG}));
    params.add(new Param("scale y", FLOATVAL, 0.01, 1, new int[]{SINE, SAWTOOTH, RAMPUPDOWN, TAN, TANINVERSE, TRIANG}));
    params.add(new Param("blend mode", INTVAL, 0, blends.length-1, new int[]{SINE, SAWTOOTH, RAMPUPDOWN, TAN, TANINVERSE, TRIANG}));
    params.add(new Param("channel", INTVAL, 0, 12, new int[]{SINE, SAWTOOTH, RAMPUPDOWN, TAN, TANINVERSE, TRIANG}));

    // channel, blend_mode

    //params.add(new Param("height", FLOATVAL, 1, buffer.height/4-1, new int[]{SINE, SAWTOOTH, RAMPUPDOWN, TAN, TANINVERSE, TRIANG}));


    // prepare distortion pattern

    for (int i=0; i<distortionMatrixSize; i++) {
      distort[0][i] = (int)random(-128, 128);
      distort[1][i] = (int)random(-128, 128);
    }
  }



  // ALL Channels, Nxxx stand for negative (255-value)
  // channels to work with
  final static int RED = 0;
  final static int GREEN = 1;
  final static int BLUE = 2;
  final static int HUE = 3;
  final static int SATURATION = 4;
  final static int BRIGHTNESS = 5;
  final static int NRED = 6;
  final static int NGREEN = 7;
  final static int NBLUE = 8;
  final static int NHUE = 9;
  final static int NSATURATION = 10;
  final static int NBRIGHTNESS = 11;

  void apply() {
    //println(buffer.height);

    buffer = renderer.get();
    buffer.resize(renderer.width, renderer.height);

    float neww = map(params.get(0).value, 2, initBufferW-2, 2, buffer.width/4-2);
    float newh = map(params.get(1).value, 2, initBufferH-2, 2, buffer.height/4-2);



    do_blend = boolean(int(params.get(2).value));
    shift_amt = params.get(3).value;
    scalex = params.get(4).value;
    scaley = params.get(5).value;
    blend_mode = blends[(int)params.get(6).value];
    channel = (int)params.get(7).value;


    float totalnum = neww+newh;
    float times = (totalnum/floor(totalnum/neww));
    float offx = (totalnum%neww)/times;
    float ratiox = neww/buffer.width;
    //println(ratiox);

    renderer.beginDraw();
    renderer.noStroke();

    for (int y=0; y<buffer.height; y++) {
      float yy = y/(float)buffer.height;
      for (int x=0; x<buffer.width; x++) {
        float xx = x/(float)buffer.width;

        float offy = floor(newh*yy);
        float fx = xx*ratiox+offx*offy;

        float shift = fx%1.0;
        float st = shift/tick;
        int no1 = floor(st)%distortionMatrixSize;
        int no2 = ceil(st)%distortionMatrixSize  ;
        float l = st-(float)no1;

        float cx = lerp(distort[0][no1], distort[0][no2], l);
        float cy = lerp(distort[1][no1], distort[1][no2], l);

        float rx =getChannel(buffer.get(x, y), channel);
        int sx = (int)((buffer.width+x+cx*rx*scalex*0.1)%buffer.width);
        int sy = (int)((buffer.height+y+cy*scaley)%buffer.height);

        color c=buffer.get(sx, sy);

        if (shift_hue) {
          colorMode(HSB, 255);
          c = color((hue(c)+shift_amt*255*noise(newh+y))%255.0, constrain(saturation(c)*1.2, 0, 255), constrain(brightness(c), 0, 255));
          colorMode(RGB, 255);
        }
        //  buffer.fill(lerpColor(c,img.get(x,y),0.2));
        renderer.fill(c); //wärs nich effizienter die pixelmatrix zu ändern ?
        renderer.rect(x, y, 1, 1);
      }
    }

    if (do_blend)
      renderer.blend(buffer, 0, 0, buffer.width, buffer.height, 0, 0, renderer.width, renderer.height, blend_mode);

    renderer.endDraw();
  }



  float getChannel(color c, int channel) {
    int ch = channel>5?channel-6:channel;
    float cc;

    switch(ch) {
    case RED:
      cc = red(c);
      break;
    case GREEN:
      cc = green(c);
      break;
    case BLUE:
      cc = blue(c);
      break;
    case HUE:
      cc = hue(c);
      break;
    case SATURATION:
      cc = saturation(c);
      break;
    default:
      cc= brightness(c);
      break;
    }

    return channel>5?255-cc:cc;
  }
}

/*

 FM
 
 */


class FM extends Shader {
  // configuration
  int colorspace = RGB;
  int quantval = 30; // 0 - off, less - more glitch, more - more precision
  boolean do_blend = true; // blend image after process
  int blend_mode = OVERLAY; // blend type



  //unused parameters (giers):
  final static boolean first_channel_only = false; // for L.. or Y.. colorspaces set true to modulate only luma;
  final static boolean lowpass1_on = true; // on/off of first low pass filter
  final static boolean lowpass2_on = true; // on/off of second low pass filter
  final static boolean lowpass3_on = true; // on/off of third low pass filter
  // better don't touch it, lowpass filters are run in cascade
  float lowpass1_cutoff = 0.25; // percentage of rate
  float lowpass2_cutoff = 0.1;
  float lowpass3_cutoff = 0.05;



  // working buffer

  PGraphics buffer;

  // local variables
  float min_omega, max_omega;
  float min_phase_mult=0.05;
  float max_phase_mult=50.0;
  LowpassFilter lpf1, lpf2, lpf3;
  int[][] pxls;
  boolean negate = false;

  FM() {
    name = "fxFM";
    params.add(new Param("do blend", INTVAL, 0, 1, new int[]{RANDOM, SQUAR}));
    params.add(new Param ("blend_mode", INTVAL, 0, blends.length-1, new int[]{RANDOM}));
    params.add(new Param ("omega", FLOATVAL, 0, 1, new int[]{SINE, SAWTOOTH, TRIANG}));
    params.add(new Param ("phase", FLOATVAL, 0, 1, new int[]{SAWTOOTH, TRIANG, SINE, TAN, TANINVERSE, RAMPUPDOWN, RAMP, RAMPINVERSE}));
    params.add(new Param ("colorspace", INTVAL, 0, 16, new int[]{RANDOM}));
    params.add(new Param ("quant", INTVAL, 0, 40, new int[]{SAWTOOTH, TRIANG, SINE, TAN, TANINVERSE, RAMPUPDOWN, RAMP, RAMPINVERSE}));

    buffer = createGraphics(renderer.width, renderer.height);
    buffer.beginDraw();
    buffer.noStroke();
    //buffer.smooth(8);
    //buffer.background(0);
    // buffer.image(renderer, 0, 0);
    buffer.endDraw();

    //img.loadPixels();

    min_omega = TWO_PI/(0.05*renderer.width);
    max_omega = TWO_PI/(300.0*renderer.width);

    float rate = 100000.0;

    lpf1 = new LowpassFilter(rate, lowpass1_cutoff*rate);
    lpf2 = new LowpassFilter(rate, lowpass2_cutoff*rate);
    lpf3 = new LowpassFilter(rate, lowpass3_cutoff*rate);

    rw = renderer.width;
    prepareData();
  }
  void prepareData() {
    pxls = new int[3][renderer.pixels.length];
    for (int i=0; i<renderer.pixels.length; i++) {
      int cl = toColorspace(renderer.pixels[i], colorspace);
      pxls[0][i] = (cl >> 16) & 0xff;
      pxls[1][i] = (cl >> 8) & 0xff;
      pxls[2][i] = (cl) & 0xff;
    }
  }

  float omega, min_phase, max_phase;


  int rw, rh;
  void apply() {
    do_blend = boolean(int(params.get(0).value));
    blend_mode = blends[(int)params.get(1).value];
    omega = map(sqrt(params.get(2).value), 0, 1, min_omega, max_omega);
    float phase = map(sq(params.get(3).value), 0, 1, min_phase_mult, max_phase_mult);
    colorspace = (int)params.get(4).value;
    quantval = (int) params.get(5).value;


    if (rw != renderer.width || rh != renderer.height) {
      rw = renderer.width;
      rh = renderer.height;
      min_omega = TWO_PI/(0.05*renderer.width);
      max_omega = TWO_PI/(300.0*renderer.width);
    }
    prepareData();

    buffer.setSize(renderer.width, renderer.height);
    //buffer = renderer.get(0, 0, renderer.width, renderer.height);


    max_phase = phase * omega;
    min_phase = -max_phase;

    processImage();
  }

  void processImage() {
    buffer.beginDraw();
    buffer.loadPixels();

    int [][] dest_pxls = new int[3][renderer.pixels.length];

    if (first_channel_only) {
      arrayCopy(pxls[1], dest_pxls[1]);
      arrayCopy(pxls[2], dest_pxls[2]);
    }

    for (int i=0; i< (first_channel_only?1:3); i++) {
      for (int y=0; y<renderer.height; y++) {
        int off = y * renderer.width;

        //reset filters each line
        lpf1.resetFilter(map(pxls[i][off], 0, 255, min_phase, max_phase));
        lpf2.resetFilter(map(pxls[i][off], 0, 255, min_phase, max_phase));
        lpf3.resetFilter(map(pxls[i][off], 0, 255, min_phase, max_phase));

        float sig_int = 0; // integral of the signal
        float pre_m = 0; // previous value of modulated signal

        for (int x=0; x<renderer.width; x++) {

          /////////////////////////
          // FM part starts here
          /////////////////////////

          float sig = map(pxls[i][x+off], 0, 255, min_phase, max_phase); // current signal value
          sig_int += sig; // current value of signal integral

          float m = cos(omega * x + sig_int); // modulate signal

          if ( quantval > 0) {
            m = map((int)map(m, -1, 1, 0, quantval), 0, quantval, -1, 1); // quantize
          }

          float dem = abs(m-pre_m); // demodulate signal, derivative
          pre_m = m; // remember current value

          // lowpass filter chain
          if (lowpass1_on) dem = lpf1.lowpass(dem);
          if (lowpass2_on) dem = lpf2.lowpass(dem);
          if (lowpass3_on) dem = lpf3.lowpass(dem);

          // remap signal back to channel value
          int v = constrain( (int)map(2*(dem-omega), min_phase, max_phase, 0, 255), 0, 255);

          //////////////////////
          // FM part ends here
          //////////////////////

          dest_pxls[i][x+off] = negate?255-v:v;
        }
      }
    }

    for (int i=0; i<buffer.pixels.length; i++) {
      buffer.pixels[i] = fromColorspace(0xff000000 | (dest_pxls[0][i] << 16) |  (dest_pxls[1][i] << 8) |  (dest_pxls[2][i]), colorspace);
    }

    buffer.updatePixels();

    if (do_blend)
      buffer.blend(renderer, 0, 0, renderer.width, renderer.height, 0, 0, buffer.width, buffer.height, blend_mode);

    buffer.endDraw();
    renderer.beginDraw();
    renderer.image(buffer, 0, 0, renderer.width, renderer.height);
    renderer.endDraw();
  }


  class LowpassFilter {
    float alpha;
    float prev;

    public LowpassFilter(float rate, float hz) {
      alpha = 0.0;
      prev = 0.0;
      setFilter(rate, hz);
    }

    void setFilter(float rate, float hz) {
      float timeInterval = 1.0/rate;
      float tau = 1.0 / (hz * TWO_PI);
      alpha = timeInterval / (tau + timeInterval);
    }

    void resetFilter(float val) {
      prev = val;
    }

    void resetFilter() {
      resetFilter(0);
    }

    float lowpass(float sample) {
      float stage1 = sample * alpha;
      float stage2 = prev - (prev * alpha);
      prev = (stage1 + stage2);
      return prev;
    }

    float highpass(float sample) {
      return sample - lowpass(sample);
    }
  }
}


/*

 WZIP
 
 */


class WZIP extends Shader {


  final float sqrt05 = sqrt(0.5);

  float[] raw, raw1, raw2, raw3;
  float[] in, w, out;
  float[] in1, in2, in3, out1, out2, out3;
  int n, n2, s;
  float scalingfactorin, scalingfactorout;

  PImage img;
  String sessionid;



  WZIP() {

    name = "fxWZIP";
    params.add(new Param ("scale", FLOATVAL, 0.1, 1000, new int[]{SAWTOOTH, TRIANG, SINE, TAN, TANINVERSE, RAMPUPDOWN, RAMP, RAMPINVERSE}));
    params.add(new Param ("factor in", FLOATVAL, 0.01, 1, new int[]{SAWTOOTH, TRIANG, SINE, TAN, TANINVERSE, RAMPUPDOWN, RAMP, RAMPINVERSE}));
    params.add(new Param ("factor out", FLOATVAL, 0.01, 1, new int[]{SAWTOOTH, TRIANG, SINE, TAN, TANINVERSE, RAMPUPDOWN, RAMP, RAMPINVERSE}));
    params.add(new Param("hsb/rgb", INTVAL, 0, 1, new int[]{RANDOM, SQUAR}));
    params.add(new Param("mode", INTVAL, 0, 1, new int[]{RANDOM, SQUAR}));


    sessionid = hex((int)random(0xffff), 4);
    img = createImage(renderer.width, renderer.height, ARGB);
    img = renderer.get(0, 0, renderer.width, renderer.height);
  }

  void apply() {
    //    img = createImage(renderer.width, renderer.height, ARGB);
    img.resize(renderer.width, renderer.height);
    img = renderer.get(0, 0, renderer.width, renderer.height);

    s = img.width*img.height;
    raw = new float[s*3];
    raw1 = new float[s];
    raw2 = new float[s];
    raw3 = new float[s];
    renderer.beginDraw();


    renderer.background(0);
    renderer.noStroke();


    if (boolean((int)params.get(3).value)) { /////////////////////
      renderer.colorMode(HSB, 255);
      colorMode(HSB, 255);
    } else {
      renderer.colorMode(RGB, 255);
      colorMode(RGB, 255);
    }

    scalingfactorin = map(params.get(1).value, 0, 1, 0, params.get(0).value); /////////////////////
    scalingfactorout = map(params.get(2).value, 0, 1, 0, params.get(0).value); /////////////////////



    int iter=0;
    int iter2 = 0;
    for (int y=0; y<img.height; y++) {
      for (int x=0; x<img.width; x++) {
        color c = img.get(x, y);
        float r, g, b;
        if (boolean((int)params.get(3).value)) { /////////////////////
          r = hue(c)>127?hue(c)-256:hue(c);
          g = saturation(c)>127?saturation(c)-256:saturation(c);
          b = brightness(c)>127?brightness(c)-256:brightness(c);
        } else {
          r = red(c)>127?red(c)-256:red(c);
          g = green(c)>127?green(c)-256:green(c);
          b = blue(c)>127?blue(c)-256:blue(c);
        }
        raw[iter++] = r;
        raw[iter++] = g;
        raw[iter++] = b;
        raw1[iter2] = r;
        raw2[iter2] = g;
        raw3[iter2] = b;
        iter2++;
      }
    }

    n = (int)pow(2, ceil(log(s*3)/log(2)));
    n2 = (int)pow(2, ceil(log(s)/log(2)));

    in = new float[n];
    w = new float[n];
    out = new float[n];
    out1 = new float[n2];
    out2 = new float[n2];
    out3 = new float[n2];
    in1 = new float[n2];
    in2 = new float[n2];
    in3 = new float[n2];

    arrayCopy(raw, 0, in, 0, raw.length);
    for (int i=raw.length; i<n; i++) in[i] = raw[raw.length-1];

    arrayCopy(raw1, 0, in1, 0, s);
    arrayCopy(raw2, 0, in2, 0, s);
    arrayCopy(raw3, 0, in3, 0, s);

    for (int i=s; i<n2; i++) {
      in1[i] = raw1[s-1];
      in2[i] = raw2[s-1];
      in3[i] = raw3[s-1];
    }


    if (boolean((int)params.get(4).value)) option1(); /////////////////////
    else option2();


    renderer.colorMode(RGB);
    colorMode(RGB);


    renderer.endDraw();
  }



  //  void printOption() {
  //    String str1, str2;
  //    if (do_hsb) {
  //      str1 = "HSBHSBHSB...";
  //      str2 = "HHH...SSS...BBB...";
  //    } else {
  //      str1 = "RGBRGBRGB...";
  //      str2 = "RRR...GGG...BBB...";
  //    }
  //    if (option1) println("channels combined: " + str1);
  //    else println("channels separated: " + str2);
  //  }
  //
  //  void printScale() {
  //    println("Scale: 0.."+sc);
  //  }

  float clamp(float c) {
    return(abs(c<0?256+c:c)%255.0);
  }
  void option2() {
    wtrafo(in1, n2);
    wbtrafo(out1, n2);

    wtrafo(in2, n2);
    wbtrafo(out2, n2);

    wtrafo(in3, n2);
    wbtrafo(out3, n2);

    for (int i=0; i<s; i++) {
      float r = clamp(out1[i]);
      float g = clamp(out2[i]);
      float b = clamp(out3[i]);
      renderer.fill(r, g, b);
      renderer.rect(i%img.width, i/img.width, 1, 1);
    }
  }
  void option1() {
    wtrafo(in, n);
    wbtrafo(out, n);

    float r=0, g=0, b=0;
    int state = 0;

    for (int i=0; i<raw.length; i++) {
      float c = clamp(out[i]);
      switch(state) {
      case 0:
        r = c;
        break;
      case 1:
        g = c;
        break;
      case 2:
        b = c;
        break;
      default:
        {
          r = c;
          renderer.fill(r, g, b);
          renderer.rect(floor(i/3.0)%img.width, floor(i/3.0)/img.width, 1, 1);
          state = 0;
        }
      }
      state++;
    }
  }




  void wbtrafo(float[] y, int n) {
    float[] d = new float[n];
    d[n-2] = w[n-1];
    int b1 = n-4;
    int b2 = n-2;
    int a=1;
    while (a<n/2) {
      for (int i=0; i<a; i++) {
        d[2*i+b1]=(d[i+b2]+w[i+b2])*sqrt05;
        d[2*i+1+b1]=(d[i+b2]-w[i+b2])*sqrt05;
      }
      b2=b1;
      b1=b1-4*a;
      a*=2;
    }

    for (int i=0; i<a; i++) {
      y[2*i]=(d[i]+w[i])*sqrt05;
      y[2*i+1]=(d[i]-w[i])*sqrt05;
    }

    for (int i=0; i<n; i++) y[i] *= scalingfactorout;
  }

  void wtrafo(float[] y, int n) {
    float[] d = new float[n];
    int a = n/2;
    for (int i=0; i<a; i++) {
      w[i] = (y[2*i]-y[2*i+1])*sqrt05;
      d[i] = (y[2*i]+y[2*i+1])*sqrt05;
    }
    int b1 = 0;
    int b2 = a;
    a/=2;
    while (a>0) {
      for (int i=0; i<a; i++) {
        w[i+b2]=(d[2*i+b1]-d[2*i+1+b1])*sqrt05;
        d[i+b2]=(d[2*i+b1]+d[2*i+1+b1])*sqrt05;
      }
      b1=b2;
      b2=b2+a;
      a/=2;
    }
    w[b2] = d[b1];

    for (int i=0; i<n-1; i++) w[i] = (int)(w[i]/scalingfactorin);
    if (w[n-1]>0) w[n-1] = (int)(w[n-1]/scalingfactorin+0.5);
    else w[n-1] = (int)(w[n-1]/scalingfactorin-0.5);
  }
}


/*

 AUECHO
 
 */


class AUECHO extends Shader {
  final int[] blends = {BLEND, ADD, SUBTRACT, DARKEST, LIGHTEST, DIFFERENCE, EXCLUSION, MULTIPLY, SCREEN, OVERLAY, HARD_LIGHT, SOFT_LIGHT, DODGE, BURN};
  AUECHO() {
    name = "fxAUecho";
    params.add(new Param("mode", INTVAL, 0, 1, new int[]{RANDOM, SQUAR}));
    params.add(new Param ("echo", FLOATVAL, 0.001, 1, new int[]{TRIANG, SINE, RAMPUPDOWN, }));
    params.add(new Param ("decay", FLOATVAL, 0.001, 1, new int[]{TRIANG, SINE, RAMPUPDOWN }));
    params.add(new Param ("blend mode", INTVAL, 0, this.blends.length-1, new int[]{RANDOM }));
  }
  void apply() {
    renderer.beginDraw();
    if (boolean((int)params.get(0).value)) {
      renderer.colorMode(HSB);
      colorMode(HSB);
    } else {
      renderer.colorMode(RGB);
      colorMode(RGB);
    }
    renderer.loadPixels();

    float _delay = params.get(1).value;
    float decay = params.get(2).value;

    int delay = (int)(renderer.pixels.length * _delay);
    color[] history = new color[renderer.pixels.length];
    int blendMode =this.blends[(int)params.get(3).value];
    for ( int i = 0, l = renderer.pixels.length; i<l; i++) {
      history[i] = renderer.pixels[i];
    }
    for ( int i = 0, l = renderer.pixels.length; i<l; i++) {
      int fromPos = i-delay < 0 ? l-abs(i-delay) : i-delay;
      color fromColor = history[fromPos];
      float r = red(fromColor) * decay;
      float g = green(fromColor) * decay;
      float b = blue(fromColor) * decay;
      color origColor = history[i];
      color toColor = color(
        r = r + red(origColor) > 255 ? r + red(origColor) - 255 : r + red(origColor), // simulate overflow ;)
        g = g + green(origColor) > 255 ? g + green(origColor) - 255 : g + green(origColor),
        b = b + blue(origColor) > 255 ? b + blue(origColor) - 255 : b + blue(origColor)  );

      //renderer.pixels[i] = history[i] = toColor;
      renderer.pixels[i] = history[i] = blendColor(origColor, toColor, blendMode);
    }
    renderer.updatePixels();
    if (boolean((int)params.get(0).value)) {
      renderer.colorMode(RGB);
      colorMode(RGB);
    }
    renderer.endDraw();
  }
}


/*

 SLITSCAN
 
 */


class SLITSCAN extends Shader {
  int[] fx;
  int[] fy;
  float[] phx;
  float[] phy;
  int[] sx, sy;
  boolean[] skipfx;
  boolean[] skipfy;
  boolean dox, doy;
  PImage buffer;
  float[][] ft = new float[2][32];
  int depth; // number of octaves
  int  fxnum;
  int  fynum;
  SLITSCAN() {
    name = "fxSlitSscan";
    buffer = createImage(renderer.width, renderer.height, ARGB);
    for (int i=0; i<32; i++) {
      ft[0][i] = pow(2.0, i);
      ft[1][i] = 0.5*1.0/ft[0][i];
    }
  }

  void apply() {
    renderer.beginDraw();
    renderer.colorMode(RGB);
    renderer.noStroke();
    colorMode(RGB);
    renderer.fill(255);
    buffer.resize(renderer.width, renderer.height);
    buffer = renderer.get(0, 0, renderer.width, renderer.height);

    //int s = buffer.width>buffer.height?buffer.height:buffer.width;
    int s = min(buffer.width, buffer.height);

    depth = (int)(log(s)/log(2));

    fxnum = (int)random(depth); ////
    fynum = (int)random(depth); ////

    fx = new int[fxnum+1];
    fy = new int[fynum+1];
    sx = new int[fxnum+1];
    sy = new int[fynum+1];
    phx = new float[fxnum+1];
    phy = new float[fynum+1];
    skipfx = new boolean[fxnum+1];
    skipfy = new boolean[fynum+1];
    for (int i=0; i<fxnum; i++) {

      fx[i]=(int)random(6);
      phx[i] = random(1);
      skipfx[i] = random(1)<0.2;
      sx[i] = random(1)<0.2?-1:1;
    }
    for (int i=0; i<fynum; i++) {

      fy[i]=(int)random(6);
      phy[i] = random(1);
      skipfy[i] = random(1)<0.2;
      sy[i] = random(1)<0.2?-1:1;
    }

    dox = random(1)<0.8;
    doy = dox?random(1)<0.8:true;



    float v=0;
    for (int y=0; y<buffer.height; y++)
      for (int x=0; x<buffer.width; x++) {
        float iy = map(y, 0, buffer.height, 0, 1);

        v=0;
        if (doy) for (int i=0; i<fy.length; i++)
          if (!skipfy[i]) v+=sy[i]*getValue(fy[i], iy, i, phy[i]);

        float ry = 2*iy+v;
        float y2 = (3*buffer.height+ry * buffer.height/2)%buffer.height;

        float ix = map(x, 0, buffer.width, 0, 1);
        v=0;
        if (dox) for (int i=0; i<fx.length; i++)
          if (!skipfx[i]) v+=sx[i]*getValue(fx[i], ix, i, phx[i]);


        float rx = 2*ix+v;
        float x2 = (3*buffer.width+rx * buffer.width/2)%buffer.width;

        renderer.fill(buffer.get((int)x2, (int)y2));
        renderer.rect(x, y, 1, 1);
      }
    renderer.endDraw();
  }

  float getValue(int fun, float idx, int freq, float phase) {
    switch(fun) {
    case 0:
      return getSin(idx, freq, phase);
    case 1:
      return getSaw(idx, freq, phase);
    case 2:
      return getTriangle(idx, freq, phase);
    case 3:
      return getCutTriangle(idx, freq, phase);
    case 4:
      return getSquare(idx, freq, phase);
    case 5:
      return getNoise(idx, freq, phase);
    default:
      return getSin(idx, freq, phase);
    }
  }

  float getNoise(float idx, int freq, float phase) {
    return 2*ft[1][freq]*(noise((idx+phase)*ft[0][freq])-0.5);
  }

  float getSin(float idx, int freq, float phase) {
    float p = ft[0][freq];
    return ft[1][freq] * sin(idx*TWO_PI*p+phase*TWO_PI);
  }

  float getSaw(float idx, int freq, float phase) {
    float p = ft[0][freq];
    float rp = 2.0*ft[1][freq];
    float p2 = p*((idx+phase+ft[1][freq])%1.0);
    return rp*(p2-floor(p2)-0.5);
  }

  float getSquare(float idx, int freq, float phase) {
    float p = ft[0][freq];
    float rp = ft[1][freq];
    return (((idx*p)+phase)%1.0)<0.5?rp:-rp;
  }

  float getTriangle(float idx, int freq, float phase) {
    return 2*abs(getSaw(idx, freq, phase+0.5*ft[1][freq]))-ft[1][freq];
  }

  float getCutTriangle(float idx, int freq, float phase) {
    return constrain(getTriangle(idx, freq, phase), -ft[1][freq+1], ft[1][freq+1]);
  }
}

/*

 WAHWAH
 
 */


class WAHWAH extends Shader {
  float sequence, lfoskip, xn1, xn2, yn1, yn2, b0, b1, b2, a0, a1, a2, freqofs, freq, freqoff, startsequence, res, depth;
  float mCurRate = 0.4, skipcount = 0;
  int lfoskipsamples = 0;
  float frequency, omega, sn, cs, alpha;
  float in, out;
  float val;
  WAHWAH() {
    name = "fxWahWah";
    //params.add(new Param("mode", INTVAL, 0, 1, new int[]{RANDOM, SQUAR}));
    params.add(new Param ("resolution", FLOATVAL, 1, 100, new int[]{TRIANG, SINE, RAMPUPDOWN, }));
    params.add(new Param ("depth", FLOATVAL, 0.0001, 1, new int[]{TRIANG, SINE, RAMPUPDOWN }));
    params.add(new Param ("frequency offset", FLOATVAL, 0, 1, new int[]{TRIANG, SINE, RAMPUPDOWN }));
    params.add(new Param ("mCurRate", FLOATVAL, 0, 1, new int[]{TRIANG, SINE, RAMPUPDOWN }));
    // params.add(new Param ("blend mode", INTVAL, 0, this.blends.length-1, new int[]{RANDOM }));
  }
  void apply() {
    res = params.get(0).value;
    depth = params.get(1).value;
    freqofs = params.get(2).value;
    //res = 12.5
    //depth = 0.8;
    //freqofs = 0.9;

    freq = 1.5;
    startsequence = 0.2;
    lfoskip = freq * 2 * PI / mCurRate;
    skipcount = xn1 = xn2 = yn1 = yn2 = b0 = b1 = b2 = a0 = a1 = a2 = 0;
    sequence = startsequence;

    renderer.beginDraw();
    renderer.colorMode(RGB);
    renderer.loadPixels();
    float[] rgb = new float[3];
    for ( int i = 0, len = renderer.pixels.length; i < len; i++) {
      rgb[0] = red(renderer.pixels[i]);
      rgb[1] = green(renderer.pixels[i]);
      rgb[2] = blue(renderer.pixels[i]);
      for ( int ri = 0; ri < 3; ri++ ) {
        in = map(rgb[ri], 0, 255, 0, 1);
        frequency = (1+cos(skipcount * lfoskip + sequence ))/2;
        frequency = frequency * depth * (1-freqofs) + freqofs;
        frequency = exp((frequency - 1) * 6 );
        omega = PI * frequency;
        sn = sin(omega);
        cs = cos(omega);
        alpha = sn/(2*res);
        b0 = (1-cs) /2;
        b1 = 1 - cs;
        b2 = (1-cs)/2;
        a0 = 1 + alpha;
        a1 = -2 * cs;
        a2 = 1 - alpha;
        out = ( b0 * in + b1 * xn1 + b2 * xn2 - a1 * yn1 - a2 * yn2 ) / a0;
        xn2 = xn1;
        xn1 = in;
        yn2 = yn1;
        yn1 = out;
        rgb[ri] = map(out, 0, 1, 0, 255);
      }
      renderer.pixels[i] = color(rgb[0], rgb[1], rgb[2]);
    }

    renderer.updatePixels();
    renderer.endDraw();
  }
}


/*

 PHASER
 
 */


class PHASER extends Shader {
  //float samplerate = 92230.0; // try setting this to 44100.0 or 2048.5 for kicks
  float samplerate = 44100; // try setting this to 44100.0 or 2048.5 for kicks
  int mode;
  PHASER() {
    name ="fxPhaser";
    params.add(new Param("mode", INTVAL, 0, 1, new int[]{RANDOM, SQUAR}));
    params.add(new Param ("frequency", FLOATVAL, 0.1, 40.0, new int[]{TRIANG, SINE, RAMPUPDOWN }));
    params.add(new Param ("depth", INTVAL, 1, 255, new int[]{TRIANG, SINE, RAMPUPDOWN }));
    params.add(new Param ("feedback", INTVAL, -100, 100, new int[]{TRIANG, SINE, RAMPUPDOWN }));
    params.add(new Param ("phase", FLOATVAL, 0, TWO_PI, new int[]{TRIANG, SINE, RAMPUPDOWN }));
    params.add(new Param ("stages", INTVAL, 1, 24, new int[]{TRIANG, SINE, RAMPUPDOWN }));
    params.add(new Param ("sample rate", FLOATVAL, 512, 92230, new int[]{TRIANG, SINE, RAMPUPDOWN }));


    //params.add(new Param ("frequency offset", FLOATVAL, 0, 1, new int[]{TRIANG, SINE, RAMPUPDOWN }));
  }
  /*
    mDepth = (int) map(knobZero, 0, 255, 255, 0);
   mFeedback = (int) map(knobOne, 0, 255, -100, 100);
   // enable these for some more fun :)
   if (mode == 1) {
   mSampleRate = map(knobTwo, 0, 255, 1, 512);
   mStages = (int) ( 2*map(knobThree, 0, 255, 1, 12));
   }
   */

  void apply() {
    mode = (int)params.get(0).value;
    float mFreq = params.get(1).value;

    int mDryWet = 255;


    int mDepth = (int)params.get(2).value;
    int mFeedback = (int)params.get(3).value;
    float mPhase = params.get(4).value;

    //these two are only changed if mode = 1
    int mStages = 2;
    float mSampleRate = samplerate;

    renderer.beginDraw();
    renderer.colorMode(RGB);
    renderer.loadPixels();


    //constants
    float phaserlfoshape = 4.0;
    int lfoskipsamples = 20; //how many samples are processed before recomputing lfo
    int numStages = 24;


    //getParams
    /*
    Phaser Parameters
     mFreq       - Phaser's LFO frequency
     mPhase      - Phaser's LFO startsequence (radians), needed for stereo Phasers
     mDepth      - Phaser depth (0 - no depth, 255 - max depth)
     mStages     - Phaser stages (recomanded from 2 to 16-24, and EVEN NUMBER)
     mDryWet     - Dry/wet mix, (0 - dry, 128 - dry=wet, 255 - wet)
     mFeedback   - Phaser FeedBack (0 - no feedback, 100 = 100% Feedback,
     -100 = -100% FeedBack)
     */
    // enable these for some more fun :)
    if (mode == 1) {
      mStages = (int)params.get(5).value;
      mSampleRate = params.get(6).value;
    }
    //init
    float gain = 0, fbout = 0;
    float lfoskip = mFreq * 2 * PI / mSampleRate;
    float sequence = mPhase * PI / 180;

    float[] old = new float[mStages];
    for ( int j = 0; j < mStages; j++) {
      old[j] = 0.0;
    }

    /* EffectPhaser::ProcessBlock */
    int skipcount = 0;

    float[] rgb = new float[3];
    for ( int i = 0, l = renderer.pixels.length; i<l; i++ ) {
      color c = renderer.pixels[i];
      rgb[0] = map(red(c), 0, 255, 0, 1);
      rgb[1] = map(green(c), 0, 255, 0, 1);
      rgb[2] = map(blue(c), 0, 255, 0, 1);
      for ( int ci = 0; ci < 3; ci++) {
        float in = rgb[ci];
        float m = in + fbout * mFeedback / 100;
        if ( (( skipcount++) % lfoskipsamples ) == 0 ) { //recomopute lfo
          gain = (1.0 + cos(skipcount * lfoskip + sequence)) / 2.0; //compute sine between 0 and 1
          gain = exp(gain * phaserlfoshape) / exp(phaserlfoshape); // change lfo shape
          gain = 1.0 - gain / 255.0 * mDepth; // attenuate the lfo
        }
        //phasing routine
        for ( int j = 0; j<mStages; j++) {
          float tmp = old[j];
          old[j] = gain * tmp + m;
          m = tmp - gain * old[j];
        }
        fbout = m;
        rgb[ci] = (float) (( m * mDryWet + in * (255-mDryWet)) / 255);
      }
      color rc = color(
        map(rgb[0], 0, 1, 0, 255),
        map(rgb[1], 0, 1, 0, 255),
        map(rgb[2], 0, 1, 0, 255));
      renderer.pixels[i] = rc;
    }
    renderer.updatePixels();
    renderer.endDraw();
  }
}


/*

 ECHO
 
 */


class ECHO extends Shader {
  int mode = 0;
  PImage result;
  ECHO() {
    name = "fxEcho";
    params.add(new Param("mode", INTVAL, 0, 1, new int[]{RANDOM, SQUAR}));
    params.add(new Param ("xp", INTVAL, 0, 100, new int[]{TRIANG, SINE, RAMPUPDOWN }));
    params.add(new Param ("yp", INTVAL, 0, 100, new int[]{TRIANG, SINE, RAMPUPDOWN }));
    result = createImage(renderer.width, renderer.height, RGB);
  }
  void apply() {
    mode = (int)params.get(0).value;

    int xp = (int)params.get(1).value;
    int yp = (int)params.get(2).value;

    renderer.beginDraw();
    renderer.colorMode(RGB);
    colorMode(RGB);
    if (mode == 0) {
      renderer.image(auEcho(renderer, xp, yp), 0, 0);
    } else if (mode == 1) {
      renderer.image(auEchoWTF(renderer, xp, yp), 0, 0);
    }
    renderer.endDraw();
  }


  PImage auEcho(PImage img, int xp, int yp) {
    result.resize(img.width, img.height);
    float _delay = map(xp, 0, 100, 0.001, 1.0);
    float decay = map(yp, 0, 100, 0.0, 1.0);
    int delay = (int)(img.pixels.length * _delay);
    color[] history = new color[img.pixels.length];
    int blendMode = BLEND;
    img.loadPixels();
    result.loadPixels();
    for ( int i = 0, l = img.pixels.length; i<l; i++) {
      history[i] = img.pixels[i];
    }
    for ( int i = 0, l = img.pixels.length; i<l; i++) {
      int fromPos = i-delay < 0 ? l-abs(i-delay) : i-delay;
      color fromColor = history[fromPos];
      float r = red(fromColor) * decay;
      float g = green(fromColor) * decay;
      float b = blue(fromColor) * decay;
      color origColor = history[i];
      color toColor = color(
        r + red(origColor),
        g + green(origColor),
        b + blue(origColor) );

      result.pixels[i] = history[i] = blendColor(origColor, toColor, blendMode);
    }
    return result;
  }

  PImage auEchoWTF(PImage img, int xp, int yp) {
    result.resize(img.width, img.height);

    float delay = map(xp, 0, 100, 0.001, 5);
    float decay = map(yp, 0, 100, 0.0, 5.0);
    int histPos = 0;
    int histLen = img.pixels.length*3;
    float[] history = new float[histLen*3];

    img.loadPixels();
    result.loadPixels();
    float ibuf = 0.0, obuf = 0.0;
    float[] rgb = new float[3];
    for ( int i = 0, l = img.pixels.length; i<l; i++, histPos++) {
      color c = img.pixels[i];
      rgb[0] = map(red(c), 0, 255, 0, 1);
      rgb[1] = map(green(c), 0, 255, 0, 1);
      rgb[2] = map(blue(c), 0, 255, 0, 1);
      history[i] = rgb[0];
      history[i+1] = rgb[1];
      history[i+2] = rgb[2];
    }
    for ( int i = 0, l = img.pixels.length; i<l; i++, histPos++) {
      color c = img.pixels[i];
      rgb[0] = map(red(c), 0, 255, 0, 1);
      rgb[1] = map(green(c), 0, 255, 0, 1);
      rgb[2] = map(blue(c), 0, 255, 0, 1);

      if ( histPos == histLen ) histPos = 0;
      for ( int ri = 0; ri < 3; ri++ ) {
        history[histPos+ri] = rgb[ri] = rgb[ri] + history[histPos+ri] * decay;
      }
      color out = color(
        (int)map(rgb[0], 0, 1, 0, 255),
        (int)map(rgb[1], 0, 1, 0, 255),
        (int)map(rgb[2], 0, 1, 0, 255));
      result.pixels[i] = out;
    }

    return result;
  }
}

/*

 DARKER
 
 */

class DARKER extends Shader {
  float thresh = 127;
  float darken = 150;
  int mode = 1;
  int bangCount = 0;
  DARKER() {
    name = "fxDarker";
    params.add(new Param("count", INTVAL, 1, 4, new int[]{TRIANG, SINE, RAMPUPDOWN, TAN, TANINVERSE}));
    params.add(new Param("threshold", INTVAL, 60, 180, new int[]{SAWTOOTH, TRIANG, SINE, TAN, TANINVERSE, RAMPUPDOWN, RAMP, RAMPINVERSE}));
    params.add(new Param("darken", INTVAL, 140, 220, new int[]{TRIANG, SINE, RAMPUPDOWN }));
    params.add(new Param("mode", INTVAL, 0, 1, new int[]{SQUAR, RANDOM}));


    //thresh = int(random(60, 180));
    //darken = int(random(140, 220));
  }

  void apply() {
    // bright = knobZero;
    int count = int(params.get(0).value);
    darken = int(params.get(1).value);
    thresh = int(params.get(2).value);
    mode = int(params.get(3).value);

    renderer.beginDraw();
    renderer.colorMode(HSB);
    colorMode(HSB);
    renderer.loadPixels();

    if (mode == 0) {
      for (int h = 1; h < count+1; h++) {
        for (int i = 0; i < renderer.width*renderer.height; i++) {
          float hue = hue(renderer.pixels[i]);
          float sat = saturation(renderer.pixels[i]);
          float bright = brightness(renderer.pixels[i]);

          if (bright > thresh/h) {
            bright -= darken/h;
            constrain(bright, 0, 255);
          }
          color c = color(hue, sat, bright);
          renderer.pixels[i] = c;
        }
      }
    } else if (mode == 1) {
      for (int h = 1; h < count+1; h++) {
        for (int i = 0; i < renderer.width*renderer.height; i++) {
          float hue = hue(renderer.pixels[i]);
          float sat = saturation(renderer.pixels[i]);
          float bright = brightness(renderer.pixels[i]);

          if (bright < thresh/h) {
            bright -= darken/h;
            constrain(bright, 0, 255);
          }
          color c = color(hue, sat, bright);
          renderer.pixels[i] = c;
        }
      }
    }
    renderer.updatePixels();
    renderer.endDraw();
    colorMode(RGB);
  }
}


/*

 BRIGHTER
 
 */

class BRIGHTER extends Shader {
  float thresh = 120;
  float thresh2 = 150;
  float brighten = 180;
  float speed;
  BRIGHTER() {
    name = "fxBrighter";
    params.add(new Param("threshold", INTVAL, 0, 255, new int[]{SAWTOOTH, TRIANG, SINE, TAN, TANINVERSE, RAMPUPDOWN, RAMP, RAMPINVERSE}));
    params.add(new Param("threshold 2", INTVAL, 0, 255, new int[]{SAWTOOTH, TRIANG, SINE, TAN, TANINVERSE, RAMPUPDOWN, RAMP, RAMPINVERSE}));
    params.add(new Param("brighten", INTVAL, 0, 255, new int[]{TRIANG, SINE, RAMPUPDOWN }));
  }

  void apply() {
    brighten = int(params.get(2).value);
    thresh2 = int(params.get(1).value);
    thresh = int(params.get(0).value);

    renderer.beginDraw();
    renderer.colorMode(HSB);
    colorMode(HSB);
    renderer.loadPixels();
    for (int i = 0; i < renderer.width*renderer.height; i++) {
      float hue = hue(renderer.pixels[i]);
      float sat = saturation(renderer.pixels[i]);
      float bright = brightness(renderer.pixels[i]);
      if (bright < thresh && bright > thresh2) {
        bright += brighten;
        constrain(bright, 0, 255);
      }
      color c = color(hue, sat, bright);
      renderer.pixels[i] = c;
    }
    renderer.colorMode(RGB);
    renderer.updatePixels();
    renderer.endDraw();
    colorMode(RGB);
  }
}

/*

 AMPLIFY
 
 */


//13
class AMPLIFY extends Shader {
  int spawnT;
  float h, s, b;
  AMPLIFY() {
    name = "fxAmplify";
    params.add(new Param("hue / red", FLOATVAL, -255, 255, new int[]{SAWTOOTH, TRIANG, SINE, TAN, TANINVERSE, RAMPUPDOWN, RAMP, RAMPINVERSE}));
    params.add(new Param("saturation / green", FLOATVAL, -255, 255, new int[]{SAWTOOTH, TRIANG, SINE, TAN, TANINVERSE, RAMPUPDOWN, RAMP, RAMPINVERSE}));
    params.add(new Param("brightness / blue", FLOATVAL, -255, 255, new int[]{SAWTOOTH, TRIANG, SINE, TAN, TANINVERSE, RAMPUPDOWN, RAMP, RAMPINVERSE}));
    params.add(new Param("RGB / HSB", INTVAL, 0, 1, new int[]{SQUAR, RANDOM}));
  }
  void apply() {
    renderer.beginDraw();
    if (params.get(3).value > 0) colorMode(HSB);
    else {
      colorMode(RGB);
    }
    renderer.loadPixels();
    for (int i = 0; i < renderer.width*renderer.height; i++) {
      float h = hue(renderer.pixels[i]);
      float s = saturation(renderer.pixels[i]);
      float b = brightness(renderer.pixels[i]);
      renderer.pixels[i] = color(h+params.get(0).value, s+params.get(1).value, b+params.get(2).value);
    }
    renderer.updatePixels();
    renderer.endDraw();
    colorMode(RGB);
  }
}

/*

 BROKENCOLORROT
 
 */


class BROKENCOLORROT extends Shader {
  int spawnT;
  float h, s, b;
  BROKENCOLORROT() {
    name = "fxBrokenColorRot";

    params.add(new Param("hue", INTVAL, 0, 255, new int[]{SAWTOOTH, TRIANG, SINE, TAN, TANINVERSE, RAMPUPDOWN, RAMP, RAMPINVERSE}));
    params.add(new Param("saturation", INTVAL, 0, 255, new int[]{SAWTOOTH, TRIANG, SINE, TAN, TANINVERSE, RAMPUPDOWN, RAMP, RAMPINVERSE}));
    params.add(new Param("brightness", INTVAL, 0, 255, new int[]{SAWTOOTH, TRIANG, SINE, TAN, TANINVERSE, RAMPUPDOWN, RAMP, RAMPINVERSE}));
    params.add(new Param("mode", INTVAL, 0, 1, new int[]{SQUAR, RANDOM}));
  }
  void apply() {
    renderer.beginDraw();
    if (params.get(3).value > 0) colorMode(HSB);
    else {
      colorMode(RGB);
    }
    renderer.loadPixels();
    float h = params.get(0).value;
    float s = params.get(1).value;
    float b = params.get(2).value;
    for (int i = 0; i < renderer.width*renderer.height; i++) {
      h = hue(renderer.pixels[i])+h;
      if (h > 255) h -= 255;
      s = saturation(renderer.pixels[i])+s;
      if (s > 255) s -= 255;
      b = brightness(renderer.pixels[i])+b;
      if (b > 255) b -= 255;
      renderer.pixels[i] = color(h, s, b);
    }
    renderer.updatePixels();
    renderer.endDraw();
    colorMode(RGB);
  }
}



/*

 POSTERIZE
 
 */

class POSTER extends Shader {
  POSTER() {
    name = "fxPosterize";
    params.add(new Param("levels", INTVAL, 2, 10, new int[]{SAWTOOTH, TRIANG, SINE, TAN, TANINVERSE, RAMPUPDOWN, RAMP, RAMPINVERSE}));
  }
  void apply() {
    renderer.beginDraw();
    renderer.filter(POSTERIZE, (int)params.get(0).value);
    renderer.endDraw();
  }
}

/*

 DUAL
 
 */

class DUAL extends Shader {
  PImage buffer;
  int dualColor;
  int dirx = 1;
  int diry = 1;
  DUAL() {
    name = "fxDual";
    params.add(new Param("dual color", INTVAL, 2000000, 15000000, new int[]{SAWTOOTH, TRIANG, SINE, TAN, TANINVERSE, RAMPUPDOWN, RAMP, RAMPINVERSE}));
    params.add(new Param("flip direction", INTVAL, 0, 3, new int[]{RANDOM}));
    params.add(new Param("mode", INTVAL, 0, 1, new int[]{SQUAR, RANDOM}));
    buffer = createImage(renderer.width, renderer.height, ARGB);
  }
  void apply() {
    switch((int)params.get(1).value) {
      case(0):
      dirx = 1;
      diry = 1;
      break;
      case(1):
      dirx = -1;
      diry = 1;
      break;
      case(2):
      dirx = 1;
      diry = -1;
      break;
      case(3):
      dirx = -1;
      diry = -1;
      break;
    }
    dualColor = (int)params.get(0).value;
    buffer.resize(renderer.width, renderer.height);
    renderer.beginDraw();
    renderer.loadPixels();
    buffer.loadPixels();
    if ((int)params.get(2).value > 0) {
      for (int i = 0; i < renderer.width*renderer.height; i++) {
        buffer.pixels[i] = renderer.pixels[i]+dualColor;
      }
    } else {
      for (int i = 0; i < renderer.width*renderer.height; i++) {
        buffer.pixels[i] = renderer.pixels[i]+dualColor;
        buffer.pixels[i] = buffer.pixels[i]+i/10;
      }
    }
    buffer.updatePixels();
    renderer.updatePixels();
    renderer.pushMatrix();
    renderer.scale(dirx, diry);
    renderer.image(buffer, 0, 0, dirx * renderer.width, diry * renderer.height);
    renderer.popMatrix();
    renderer.endDraw();
  }
}

/*

 GRAUZONE
 
 */

class GRAUZONE extends Shader {
  int nFrames = 20;
  int iWrite = 0, iRead = 1;
  PImage[] buffer;
  PGraphics grauz;
  GRAUZONE() {
    name = "fxGrauzone";
    params.add(new Param("delay (in frames)", INTVAL, 3, 100, new int[]{RANDOM}));
    nFrames = (int)params.get(0).value;
    buffer = new PImage[nFrames];
  }
  int nFramesPrev;
  void apply() {
    nFramesPrev = nFrames;
    if (nFramesPrev != (int)params.get(0).value) {
      iWrite = 0;
      iRead = 1;
      int nFramesOld = nFrames;
      nFrames = (int)params.get(0).value;
      if (nFrames > nFramesOld) {
        buffer = new PImage[nFrames];
      }
    }

    buffer[iWrite] = renderer.get();
    grauz = createGraphics(renderer.width, renderer.height);
    grauz.beginDraw();
    //  grauz.resize(renderer.width, renderer.height);
    buffer[iWrite] = renderer.get();
    if (buffer[iRead] != null) {
      grauz.tint(255, 127);
      buffer[iRead].filter(INVERT);
      grauz.image(buffer[iRead], 0, 0, renderer.width, renderer.height);
      grauz.tint(255, 255);
    }
    grauz.endDraw();
    renderer.beginDraw();
    renderer.image(grauz, 0, 0, renderer.width, renderer.height);
    renderer.endDraw();


    iWrite++;
    iRead++;
    if (iRead >= nFrames-1) {
      iRead = 0;
    }
    if (iWrite >= nFrames-1) {
      iWrite = 0;
    }
  }
}

/*

 COPYZOOM
 
 */

class COPYZOOM extends Shader {
  int coprx, copry, coprw, coprh;
  PImage buffer;
  COPYZOOM() {
    name = "fxCopyZoom";
    params.add(new Param("w", FLOATVAL, 0, 1, new int[]{SAWTOOTH, TRIANG, SINE, TAN, TANINVERSE, RAMPUPDOWN, RAMP, RAMPINVERSE}));
    params.add(new Param("h", FLOATVAL, 0, 1, new int[]{SAWTOOTH, TRIANG, SINE, TAN, TANINVERSE, RAMPUPDOWN, RAMP, RAMPINVERSE}));
    params.add(new Param("x", FLOATVAL, 0, 1, new int[]{SAWTOOTH, TRIANG, SINE, TAN, TANINVERSE, RAMPUPDOWN, RAMP, RAMPINVERSE}));
    params.add(new Param("y", FLOATVAL, 0, 1, new int[]{SAWTOOTH, TRIANG, SINE, TAN, TANINVERSE, RAMPUPDOWN, RAMP, RAMPINVERSE}));
    buffer = createImage(renderer.width, renderer.height, ARGB);

    coprw = int(random(0, renderer.width));
    coprh = int(random(0, renderer.height));
    coprx = int(random(0, renderer.width-coprw));
    copry = int(random(0, renderer.height-coprh));
  }
  void apply() {
    if (buffer.width != renderer.width || buffer.height != renderer.height) buffer.resize(renderer.width, renderer.height);
    buffer = renderer.get();
    coprw = int(map(params.get(0).value, 0, 1, 1, renderer.width));
    coprh = int(map(params.get(1).value, 0, 1, 1, renderer.height));
    coprx = int(map(params.get(2).value, 0, 1, 0, renderer.width-coprw));
    copry = int(map(params.get(3).value, 0, 1, 0, renderer.height-coprh));
    renderer.beginDraw();
    buffer.copy(coprx, copry, coprw, coprh, 0, 0, renderer.width, renderer.height);
    renderer.image(buffer, 0, 0, renderer.width, renderer.height);
    renderer.endDraw();
  }
}

/*

 BUFFER
 
 */

class BUFFER extends Shader {
  BUFFER() {
    name = "BUFFER";
  }
}

























// name
String getColorspaceName(int cs) {
  switch(cs) {
  case OHTA:
    return "OHTA";
  case CMY:
    return "CMY";
  case XYZ:
    return "XYZ";
  case YXY:
    return "YXY";
  case HCL:
    return "HCL";
  case LUV:
    return "LUV";
  case LAB:
    return "LAB";
  case HWB:
    return "HWB";
  case HSB:
    return "HSB";
  case RGGBG:
    return "R-GGB-G";
  case YPbPr:
    return "YPbPr";
  case YCbCr:
    return "YCbCr";
  case YDbDr:
    return "YDbDr";
  case GS:
    return "Greyscale";
  case YUV:
    return "YUV";
  default:
    return "RGB";
  }
}

// colorspace converters
color fromColorspace(color c, int cs) {
  switch(cs) {
  case OHTA:
    return fromOHTA(c);
  case CMY:
    return fromCMY(c);
  case XYZ:
    return fromXYZ(c);
  case YXY:
    return fromYXY(c);
  case HCL:
    return fromHCL(c);
  case LUV:
    return fromLUV(c);
  case LAB:
    return fromLAB(c);
  case HWB:
    return fromHWB(c);
  case HSB:
    return fromHSB(c);
  case RGGBG:
    return fromRGGBG(c);
  case YPbPr:
    return fromYPbPr(c);
  case YCbCr:
    return fromYCbCr(c);
  case YDbDr:
    return fromYDbDr(c);
  case GS:
    return tofromGS(c);
  case YUV:
    return fromYUV(c);
  default:
    return c;
  }
}

color toColorspace(color c, int cs) {
  switch(cs) {
  case OHTA:
    return toOHTA(c);
  case CMY:
    return toCMY(c);
  case XYZ:
    return toXYZ(c);
  case YXY:
    return toYXY(c);
  case HCL:
    return toHCL(c);
  case LUV:
    return toLUV(c);
  case LAB:
    return toLAB(c);
  case HWB:
    return toHWB(c);
  case HSB:
    return toHSB(c);
  case RGGBG:
    return toRGGBG(c);
  case YPbPr:
    return toYPbPr(c);
  case YCbCr:
    return toYCbCr(c);
  case YDbDr:
    return toYDbDr(c);
  case YUV:
    return toYUV(c);
  case GS:
    return tofromGS(c);
  default:
    return c;
  }
}

// Colorspace converters

final int getR(color c) {
  return (c & 0xff0000) >> 16;
}
final int getG(color c) {
  return (c & 0xff00) >> 8;
}
final int getB(color c) {
  return c & 0xff;
}

final int getLuma(color c) {
  return constrain((int)(0.2126*getR(c)+0.7152*getG(c)+0.0722*getB(c)), 0, 255);
}

int getChannel(color c, int ch) {
  switch(ch) {
  case 0 :
    return getR(c);
  case 1 :
    return getG(c);
  case 2 :
    return getB(c);
  default:
    return 0;
  }
}

// normalized versions
final float getNR(color c) {
  return r255[(c & 0xff0000) >> 16];
}
final float getNG(color c) {
  return r255[(c & 0xff00) >> 8];
}
final float getNB(color c) {
  return r255[c & 0xff];
}
final float getNLuma(color c) {
  return r255[getLuma(c)];
}

color blendRGB(color c, int r, int g, int b) {
  return (c & 0xff000000) | (constrain(r, 0, 255) << 16) |  (constrain(g, 0, 255) << 8 ) | constrain(b, 0, 255);
}

color blendRGB(color c, float r, float g, float b) {
  return blendRGB(c, (int)(r*255), (int)(g*255), (int)(b*255));
}

/**************
 * Greyscale
 **************/

color tofromGS(color c) {
  int l = getLuma(c);
  return blendRGB(c, l, l, l);
}

/**************
 * YUV
 **************/

final static float Umax = 0.436 * 255.0;
final static float Vmax = 0.615 * 255.0;

color toYUV(color c) {
  int R = getR(c);
  int G = getG(c);
  int B = getB(c);

  int Y =  (int)(  0.299*R+0.587*G+0.114*B);
  int U = (int)map(-0.14713*R-0.28886*G+0.436*B, -Umax, Umax, 0, 255);
  int V = (int)map(0.615*R-0.51499*G-0.10001*B, -Vmax, Vmax, 0, 255);

  return blendRGB(c, Y, U, V);
}

color fromYUV(color c) {
  int Y = getR(c);
  float U = map(getG(c), 0, 255, -Umax, Umax);
  float V = map(getB(c), 0, 255, -Vmax, Vmax);

  int R = (int)(Y + 1.13983*V);
  int G = (int)(Y - 0.39465*U - 0.58060*V);
  int B = (int)(Y + 2.03211*U);

  return blendRGB(c, R, G, B);
}

/**************
 * YDbDr
 **************/

color toYDbDr(color c) {
  int R = getR(c);
  int G = getG(c);
  int B = getB(c);

  int Y =  (int)(  0.299*R+0.587*G+0.114*B);
  int Db = (int)(127.5+(-0.450*R-0.883*G+1.333*B)/2.666);
  int Dr = (int)(127.5+(-1.333*R+1.116*G+0.217*B)/2.666);

  return blendRGB(c, Y, Db, Dr);
}

color fromYDbDr(color c) {
  int Y = getR(c);
  float Db = (getG(c)-127.5)*2.666;
  float Dr = (getB(c)-127.5)*2.666;

  int R = (int)(Y + 9.2303716147657e-05*Db-0.52591263066186533*Dr);
  int G = (int)(Y - 0.12913289889050927*Db+0.26789932820759876*Dr);
  int B = (int)(Y + 0.66467905997895482*Db-7.9202543533108e-05*Dr);

  return blendRGB(c, R, G, B);
}

/**************
 * YCbCr
 **************/

color toYCbCr(color c) {
  int R = getR(c);
  int G = getG(c);
  int B = getB(c);

  int Y =  (int)( 0.2988390*R+0.5868110*G+0.1143500*B);
  int Cb = (int)(-0.168736*R-0.3312640*G+0.5000000*B+127.5);
  int Cr = (int)( 0.5000000*R-0.4186880*G-0.0813120*B+127.5);

  return blendRGB(c, Y, Cb, Cr);
}

color fromYCbCr(color c) {
  int Y = getR(c);
  float Cb = getG(c) - 127.5;
  float Cr = getB(c) - 127.5;

  int R = (int)(Y + 1.402*Cr)+1; // some fix
  int G = (int)(Y-0.344136*Cb-0.714136*Cr);
  int B = (int)(Y+1.772000*Cb)+1; // some fix

  return blendRGB(c, R, G, B);
}

/**************
 * YPbPr
 **************/

color toYPbPr(color c) {
  int R = getR(c);
  int B = getB(c);

  int Y = getLuma(c);
  int Pb = B - Y;
  int Pr = R - Y;
  if (Pb<0) Pb+=256;
  if (Pr<0) Pr+=256;
  return blendRGB(c, Y, Pb, Pr);
}

color fromYPbPr(color c) {
  int Y = getR(c);
  int B = getG(c) + Y;
  int R = getB(c) + Y;
  if (R>255) R-=256;
  if (B>255) B-=256;

  int G = (int)((Y-0.2126*R-0.0722*B)/0.7152);

  return blendRGB(c, R, G, B);
}


/**************
 * R-G,G,B-G
 **************/

color toRGGBG(color c) {
  int G = getG(c);
  int R = getR(c)-G;
  int B = getB(c)-G;
  if (R<0) R+=256;
  if (B<0) B+=256;
  return blendRGB(c, R, G, B);
}

color fromRGGBG(color c) {
  int G = getG(c);
  int R = getR(c)+G;
  int B = getB(c)+G;
  if (R>255) R-=256;
  if (B>255) B-=256;
  return blendRGB(c, R, G, B);
}

/**************
 * HWB
 **************/

color toHSB(color c) {
  int R = getR(c);
  int G = getG(c);
  int B = getB(c);

  int _min = min(R, G, B);
  int _max = max(R, G, B);
  float delta = _max-_min;
  float saturation = delta/_max;
  float brightness = r255[_max];
  if (delta == 0.0) return blendRGB(c, 0.0, saturation, brightness);
  float hue = 0;
  if (R == _max) hue = (G-B)/delta;
  else if (G == _max) hue = 2.0 + (B-R)/delta;
  else hue = 4.0 + (R-G)/delta;
  hue /= 6.0;
  if (hue < 0.0) hue += 1.0;
  return blendRGB(c, hue, saturation, brightness);
}

color fromHSB(color c) {
  float S = getNG(c);
  float B = getNB(c);
  if (S == 0.0) return blendRGB(c, B, B, B);

  float h = 6.0 * getNR(c);
  float f = h-floor(h);
  float p = B*(1.0-S);
  float q = B*(1.0-S*f);
  float t = B*(1.0-(S*(1.0-f)));

  float r, g, b;
  switch((int)h) {
  case 1:
    r=q;
    g=B;
    b=p;
    break;
  case 2:
    r=p;
    g=B;
    b=t;
    break;
  case 3:
    r=p;
    g=q;
    b=B;
    break;
  case 4:
    r=t;
    g=p;
    b=B;
    break;
  case 5:
    r=B;
    g=p;
    b=q;
    break;
  default:
    r=B;
    g=t;
    b=p;
    break;
  }
  return blendRGB(c, r, g, b);
}



/**************
 * HWB
 **************/

color toHWB(color c) {
  int R = getR(c);
  int G = getG(c);
  int B = getB(c);

  int w = min(R, G, B);
  int v = max(R, G, B);

  int hue;
  if (v == w) hue = 255;
  else {
    float f = ((R == w) ? G-B : ((G == w) ? B-R : R-G));
    float p = (R == w) ? 3.0 : ((G == w) ? 5.0 : 1.0);
    hue = (int)map((p-f/(v-w))/6.0, 0, 1, 0, 254);
  }
  return blendRGB(c, hue, w, 255-v);
}

color fromHWB(color c) {
  int H = getR(c);
  int B = 255-getB(c);
  if (H == 255) return blendRGB(c, B, B, B);
  else {
    float hue = map(H, 0, 254, 0, 6);
    float v = r255[B];
    float whiteness = getNG(c);
    int i = (int)floor(hue);
    float f = hue-i;
    if ((i&0x01)!= 0) f=1.0-f;
    float n = whiteness+f*(v-whiteness);
    float r, g, b;
    switch(i) {
    case 1:
      r=n;
      g=v;
      b=whiteness;
      break;
    case 2:
      r=whiteness;
      g=v;
      b=n;
      break;
    case 3:
      r=whiteness;
      g=n;
      b=v;
      break;
    case 4:
      r=n;
      g=whiteness;
      b=v;
      break;
    case 5:
      r=v;
      g=whiteness;
      b=n;
      break;
    default:
      r=v;
      g=n;
      b=whiteness;
      break;
    }
    return blendRGB(c, r, g, b);
  }
}

/**************
 * Lab
 **************/

final static float D65X=0.950456;
final static float D65Y=1.0;
final static float D65Z=1.088754;
final static float CIEEpsilon=(216.0/24389.0);
final static float CIEK=(24389.0/27.0);
final static float CIEK2epsilon = CIEK * CIEEpsilon;
final static float D65FX_4 = 4.0*D65X/(D65X+15.0*D65Y+3.0*D65Z);
final static float D65FY_9 = 9.0*D65Y/(D65X+15.0*D65Y+3.0*D65Z);
final static float RANGE_X = 100.0 * (0.4124+0.3576+0.1805);
final static float RANGE_Y = 100.0;
final static float RANGE_Z = 100.0 * (0.0193+0.1192+0.9505);
final static float mepsilon = 1.0e-10;
final static float corrratio = 1.0/2.4;
final static float One_Third = 1.0/3.0;
final static float one_hsixteen = 1.0/116.0;

color toLAB(color c) {
  PVector xyz = _toXYZ(getNR(c), getNG(c), getNB(c));
  xyz.div(100.0);
  xyz.x /= D65X;
  xyz.y /= D65Y;
  xyz.z /= D65Z;
  float x, y, z;

  if (xyz.x > CIEEpsilon) {
    x = pow(xyz.x, One_Third);
  } else {
    x= (CIEK*xyz.x+16.0)*one_hsixteen;
  }

  if (xyz.y > CIEEpsilon) {
    y = pow(xyz.y, One_Third);
  } else {
    y = (CIEK*xyz.y+16.0)*one_hsixteen;
  }

  if (xyz.z > CIEEpsilon) {
    z = pow(xyz.z, One_Third);
  } else {
    z = (CIEK*xyz.z+16.0)*one_hsixteen;
  }

  float L = 255.0*(((116.0*y)-16.0)*0.01);
  float a = 255.0*(0.5*(x-y)+0.5);
  float b = 255.0*(0.5*(y-z)+0.5);

  return blendRGB(c, round(L), round(a), round(b));
}

color fromLAB(color c) {
  float L = 100*getNR(c);
  float a = getNG(c)-0.5;
  float b = getNB(c)-0.5;

  float y = (L+16.0)*one_hsixteen;
  float x = y+a;
  float z = y-b;

  float xxx=x*x*x;
  if (xxx>CIEEpsilon) {
    x = xxx;
  } else {
    x = (116.0*x-16.0)/CIEK;
  }

  float yyy=y*y*y;
  if (yyy>CIEEpsilon) {
    y = yyy;
  } else {
    y = L/CIEK;
  }

  float zzz=z*z*z;
  if (zzz>CIEEpsilon) {
    z = zzz;
  } else {
    z = (116.0*z-16.0)/CIEK;
  }

  return _fromXYZ(c, RANGE_X*x, RANGE_Y*y, RANGE_Z*z);
}

/**************
 * Luv
 **************/

final float PerceptibleReciprocal(float x) {
  float sgn = x < 0.0 ? -1.0 : 1.0;
  if ((sgn * x) >= mepsilon) return (1.0 / x);
  return (sgn/mepsilon);
}

color toLUV(color c) {
  PVector xyz = _toXYZ(getNR(c), getNG(c), getNB(c));
  xyz.div(100.0);
  float d = xyz.y; // / D65Y;
  float L;
  if (d > CIEEpsilon) L = 116.0*pow(d, One_Third)-16.0;
  else L = CIEK * d;
  float alpha = PerceptibleReciprocal(xyz.x + 15.0 * xyz.y + 3.0 * xyz.z);
  float L13 = 13.0 * L;
  float u = L13 * ((4.0 * alpha * xyz.x)-D65FX_4);
  float v = L13 * ((9.0 * alpha * xyz.y)-D65FY_9);
  L /= 100.0;
  u=(u+134.0)/354.0;
  v=(v+140.0)/262.0;
  return blendRGB(c, round(L*255), round(u*255), round(v*255));
}

color fromLUV(color c) {
  float L = 100.0*getNR(c);
  float u = 354.0*getNG(c)-134.0;
  float v = 262.0*getNB(c)-140.0;
  float X, Y, Z;
  if (L > CIEK2epsilon) Y = pow((L+16.0)*one_hsixteen, 3.0);
  else Y = L/CIEK;
  float L13 = 13.0*L;
  float L52 = 52.0*L;
  float Y5 = 5.0*Y;
  float L13u = L52/(u+L13*D65FX_4);
  X=((Y*((39.0*L/(v+L13*D65FY_9))-5.0))+Y5)/((((L13u)-1.0)/3.0)+One_Third);
  Z=(X*(((L13u)-1.0)/3.0))-Y5;
  return _fromXYZ(c, 100*X, 100*Y, 100*Z);
}

/**************
 * HCL
 **************/

color toHCL(color c) {
  float r = getNR(c);
  float g = getNG(c);
  float b = getNB(c);
  float max = max(r, max(g, b));
  float chr = max - min(r, min(g, b));
  float h = 0.0;
  if ( chr != 0) {
    if (r == max) {
      h = ((g-b)/chr+6.0) % 6.0;
    } else if (g == max) {
      h = (b-r)/chr + 2.0;
    } else {
      h = (r-g)/chr + 4.0;
    }
  }
  return blendRGB(c, round((h/6.0)*255), round(chr*255), round(255*(0.298839*r+0.586811*g+0.114350*b)));
}

color fromHCL(color c) {
  float h = 6.0*getNR(c);
  float chr = getNG(c);
  float l = getNB(c);
  float x = chr*(1.0-abs((h%2.0)-1.0));
  float r = 0.0;
  float g = 0.0;
  float b = 0.0;
  if ((0.0 <= h) && (h < 1.0)) {
    r=chr;
    g=x;
  } else if ((1.0 <= h) && (h < 2.0)) {
    r=x;
    g=chr;
  } else if ((2.0 <= h) && (h < 3.0)) {
    g=chr;
    b=x;
  } else if ((3.0 <= h) && (h < 4.0)) {
    g=x;
    b=chr;
  } else if ((4.0 <= h) && (h < 5.0)) {
    r=x;
    b=chr;
  } else {//if ((5.0 <= h) && (h < 6.0)) {
    r=chr;
    b=x;
  }
  float m = l - (0.298839*r+0.586811*g+0.114350*b);
  return blendRGB(c, round(255*(r+m)), round(255*(g+m)), round(255*(b+m)));
}

/**************
 * Yxy
 **************/

color toYXY(color c) {
  PVector xyz = _toXYZ(getNR(c), getNG(c), getNB(c));
  float sum = xyz.x + xyz.y + xyz.z;
  float x = xyz.x > 0 ? xyz.x / sum : 0.0;
  float y = xyz.y > 0 ? xyz.y / sum : 0.0;
  return blendRGB(c,
    (int)map(xyz.y, 0, RANGE_Y, 0, 255),
    (int)map(x, 0.0, 1.0, 0, 255),
    (int)map(y, 0.0, 1.0, 0, 255));
}

color fromYXY(color c) {
  float Y = map(getR(c), 0, 255, 0, RANGE_Y);
  float x = map(getG(c), 0, 255, 0, 1.0);
  float y = map(getB(c), 0, 255, 0, 1.0);
  float divy = Y / (y>0 ? y : 1.0e-6);

  return _fromXYZ(c, x * divy, Y, (1-x-y)*divy);
}

/**************
 * XYZ
 **************/

// FIXME: range from 0 to 1
float correctionxyz(float n) {
  return (n > 0.04045 ? pow((n + 0.055) / 1.055, 2.4) : n / 12.92) * 100.0;
}

PVector _toXYZ(float rr, float gg, float bb) {
  float r = correctionxyz(rr);
  float g = correctionxyz(gg);
  float b = correctionxyz(bb);
  return new PVector(r * 0.4124 + g * 0.3576 + b * 0.1805,
    r * 0.2126 + g * 0.7152 + b * 0.0722,
    r * 0.0193 + g * 0.1192 + b * 0.9505);
}

color toXYZ(color c) {
  PVector xyz = _toXYZ(getNR(c), getNG(c), getNB(c));
  return blendRGB(c,
    (int)map(xyz.x, 0, RANGE_X, 0, 255),
    (int)map(xyz.y, 0, RANGE_Y, 0, 255),
    (int)map(xyz.z, 0, RANGE_Z, 0, 255));
}

float recorrectionxyz(float n) {
  return n > 0.0031308 ? 1.055 * pow(n, corrratio) - 0.055 : 12.92 * n;
}

// FIXME: range from 0 to 1
color _fromXYZ(color c, float xx, float yy, float zz) {
  float x = xx/100.0;
  float y = yy/100.0;
  float z = zz/100.0;

  int r = round(255.0*recorrectionxyz(x * 3.2406 + y * -1.5372 + z * -0.4986));
  int g = round(255.0*recorrectionxyz(x * -0.9689 + y * 1.8758 + z * 0.0415));
  int b = round(255.0*recorrectionxyz(x * 0.0557 + y * -0.2040 + z * 1.0570));

  return blendRGB(c, r, g, b);
}

color fromXYZ(color c) {
  float x = map(getR(c), 0, 255, 0, RANGE_X);
  float y = map(getG(c), 0, 255, 0, RANGE_Y);
  float z = map(getB(c), 0, 255, 0, RANGE_Z);

  return _fromXYZ(c, x, y, z);
}

/**************
 * CMY
 **************/

color toCMY(color c) {
  return blendRGB(c, 255-getR(c), 255-getG(c), 255-getB(c));
}

color fromCMY(color c) {
  return toCMY(c);
}

/**************
 * OHTA
 **************/

color fromOHTA(color c) {
  int I1 = getR(c);
  float I2 = map(getG(c), 0, 255, -127.5, 127.5);
  float I3 = map(getB(c), 0, 255, -127.5, 127.5);

  int R = (int)(I1+1.00000*I2-0.66668*I3);
  int G = (int)(I1+1.33333*I3);
  int B = (int)(I1-1.00000*I2-0.66668*I3);

  return blendRGB(c, R, G, B);
}

color toOHTA(color c) {
  int R = getR(c);
  int G = getG(c);
  int B = getB(c);

  int I1 = (int)(0.33333*R+0.33334*G+0.33333*B);
  int I2 = (int)map(0.5*(R-B), -127.5, 127.5, 0, 255);
  int I3 = (int)map(-0.25000*R+0.50000*G-0.25000*B, -127.5, 127.5, 0, 255);

  return blendRGB(c, I1, I2, I3);
}

////
// 1/n table for n=0..255 - to speed up color conversions things
final static float[] r255 = {
  0.0, 0.003921569, 0.007843138, 0.011764706, 0.015686275, 0.019607844, 0.023529412, 0.02745098, 0.03137255, 0.03529412, 0.039215688,
  0.043137256, 0.047058824, 0.050980393, 0.05490196, 0.05882353, 0.0627451, 0.06666667, 0.07058824, 0.07450981, 0.078431375, 0.08235294,
  0.08627451, 0.09019608, 0.09411765, 0.09803922, 0.101960786, 0.105882354, 0.10980392, 0.11372549, 0.11764706, 0.12156863, 0.1254902,
  0.12941177, 0.13333334, 0.13725491, 0.14117648, 0.14509805, 0.14901961, 0.15294118, 0.15686275, 0.16078432, 0.16470589, 0.16862746,
  0.17254902, 0.1764706, 0.18039216, 0.18431373, 0.1882353, 0.19215687, 0.19607843, 0.2, 0.20392157, 0.20784314, 0.21176471, 0.21568628,
  0.21960784, 0.22352941, 0.22745098, 0.23137255, 0.23529412, 0.23921569, 0.24313726, 0.24705882, 0.2509804, 0.25490198, 0.25882354,
  0.2627451, 0.26666668, 0.27058825, 0.27450982, 0.2784314, 0.28235295, 0.28627452, 0.2901961, 0.29411766, 0.29803923, 0.3019608, 0.30588236,
  0.30980393, 0.3137255, 0.31764707, 0.32156864, 0.3254902, 0.32941177, 0.33333334, 0.3372549, 0.34117648, 0.34509805, 0.34901962, 0.3529412,
  0.35686275, 0.36078432, 0.3647059, 0.36862746, 0.37254903, 0.3764706, 0.38039216, 0.38431373, 0.3882353, 0.39215687, 0.39607844, 0.4,
  0.40392157, 0.40784314, 0.4117647, 0.41568628, 0.41960785, 0.42352942, 0.42745098, 0.43137255, 0.43529412, 0.4392157, 0.44313726,
  0.44705883, 0.4509804, 0.45490196, 0.45882353, 0.4627451, 0.46666667, 0.47058824, 0.4745098, 0.47843137, 0.48235294, 0.4862745, 0.49019608,
  0.49411765, 0.49803922, 0.5019608, 0.5058824, 0.50980395, 0.5137255, 0.5176471, 0.52156866, 0.5254902, 0.5294118, 0.53333336, 0.5372549,
  0.5411765, 0.54509807, 0.54901963, 0.5529412, 0.5568628, 0.56078434, 0.5647059, 0.5686275, 0.57254905, 0.5764706, 0.5803922, 0.58431375,
  0.5882353, 0.5921569, 0.59607846, 0.6, 0.6039216, 0.60784316, 0.6117647, 0.6156863, 0.61960787, 0.62352943, 0.627451, 0.6313726, 0.63529414,
  0.6392157, 0.6431373, 0.64705884, 0.6509804, 0.654902, 0.65882355, 0.6627451, 0.6666667, 0.67058825, 0.6745098, 0.6784314, 0.68235296,
  0.6862745, 0.6901961, 0.69411767, 0.69803923, 0.7019608, 0.7058824, 0.70980394, 0.7137255, 0.7176471, 0.72156864, 0.7254902, 0.7294118,
  0.73333335, 0.7372549, 0.7411765, 0.74509805, 0.7490196, 0.7529412, 0.75686276, 0.7607843, 0.7647059, 0.76862746, 0.77254903, 0.7764706,
  0.78039217, 0.78431374, 0.7882353, 0.7921569, 0.79607844, 0.8, 0.8039216, 0.80784315, 0.8117647, 0.8156863, 0.81960785, 0.8235294, 0.827451,
  0.83137256, 0.8352941, 0.8392157, 0.84313726, 0.84705883, 0.8509804, 0.85490197, 0.85882354, 0.8627451, 0.8666667, 0.87058824, 0.8745098,
  0.8784314, 0.88235295, 0.8862745, 0.8901961, 0.89411765, 0.8980392, 0.9019608, 0.90588236, 0.9098039, 0.9137255, 0.91764706, 0.92156863,
  0.9254902, 0.92941177, 0.93333334, 0.9372549, 0.9411765, 0.94509804, 0.9490196, 0.9529412, 0.95686275, 0.9607843, 0.9647059, 0.96862745,
  0.972549, 0.9764706, 0.98039216, 0.9843137, 0.9882353, 0.99215686, 0.99607843, 1.0
};
