// This program generates videos which demonstrate how various image
// processing algorithms work.
// Set the 'transformation' variable in order to determine which 
// video you want to see.

char transformation = 's';
// 'm' - mirror image
// 'r' - rotate image
// 's' - shrink image
// 'e' - expand image
// 'c' - rotate color components
// 'd' - edge detection

// Coordinates of the virtual screen
int[] screenCorner = new int[2];
int[] screenSize = new int[2];
int charSize = 20;

// Each image is represented by an array of rectangles.  Each
// rectangle represents one image.
int[][] img1 = new int[10][10];
int[][] img2 = new int[10][10];
// The top left corner of the first img are the same as the screen.
// The top left corner of the second img is below
int[] img2Corner = new int[2];

// Set up the graphics
void setup() {
  size(1000, 500);
  background(200);
  // Figure out where the screen is drawn
  screenCorner[0] = width/10;
  screenCorner[1] = height/10;
  screenSize[0] = width - 2 * screenCorner[0];
  screenSize[1] = height - 2 * screenCorner[1];

  // Draw it
  drawScreen();

  // The frame rate is the same as the animation rate.
  frameRate(1);
}

// These pixels will also be highlighted in the video
int[] extraspecial1 = {
};
int[] extraspecial2 = {
};

// Each frame, figure out what the next image in the video
// should be.  This sets particular points on each image as
// 'special' which will be highlighted in the animation.
// Then draw img1 and img2.
void draw() {
  int[] special = animate();

  drawPixels(img1, screenCorner[0], screenCorner[1], 
  screenSize[0]/2, screenSize[1], special[0], special[1], extraspecial1);

  drawPixels(img2, screenCorner[0]+screenSize[0]/2, 
  screenCorner[1], screenSize[0]/2, screenSize[1], 
  special[2], special[3], extraspecial2);
}


// Draw the pixels corresponding to a particular image
void drawPixels(int[][] img, int x, int y, int w, int h, int sx, int sy, int[] extra) {
  // Fill in each of the pixel colors
  int pixelw = w/img.length;
  int pixelh = h/img[0].length;
  stroke(0);
  for (int i = 0; i < img.length; i++) {
    for (int j = 0; j < img.length; j++) {
      // Fill with the appropriate color, then draw a rectangle corresponding
      // to that pixel
      fill(img[i][j]);
      rect(x + pixelw * i, y + pixelh * j, pixelw, pixelh);
    }
  }

  // erase any text I might have previously written
  noStroke();
  fill(200);
  rect(x, screenCorner[1] + screenSize[1] + 1, 100, 100);

  // If necessary, write special x and y
  if ((sx >= 0) && (sy >= 0)) {
    // Highlight the special pixel
    stroke(255, 0, 0);
    strokeWeight(3);
    fill(0, 0);
    rect(x + pixelw * sx+3, y + pixelh * sy+3, pixelw-6, pixelh-6);

    // Write the special pixel's location below
    strokeWeight(1);
    fill(0);
    text("x: " + sx + "; y: " + sy, x, screenCorner[1] + screenSize[1] + 2 * charSize);
    stroke(0);
  }

  // If necessary, write extra special x and y
  for (int i = 0; i < extra.length; i += 2) {
    // Highlight the extra special pixels
    stroke(250, 128, 5);
    strokeWeight(3);
    fill(0, 0);
    rect(x + pixelw * extra[i]+3, y + pixelh * extra[i+1] +3, pixelw-6, pixelh-6);
    strokeWeight(1);
  }
}


// This draws the basic screen with widths and heights
void drawScreen() {
  fill(0);
  int textY = screenCorner[1] - 2 * charSize;
  text("<----", screenCorner[0], textY);
  text("width", width/2-charSize, textY);
  text("---->", width-(2*screenCorner[1])-40, textY);

  textY += charSize;
  text("<----", screenCorner[0], textY);
  text("img1.width", width/4-charSize, textY);
  text("---->", (width-(2*screenCorner[1]))/2, textY);

  text("<----", (width-(2*screenCorner[1]))/2 + 3*charSize, textY);
  text("img2.width", width/4+screenSize[0]/2, textY);
  text("---->", width-(2*screenCorner[1])-40, textY);

  int textX = screenCorner[0] - charSize; 
  text("^", textX, screenCorner[1]+charSize);
  text("|", textX, screenCorner[1]+2*charSize);
  text("|", textX, screenCorner[1]+3*charSize);
  text("height", textX - charSize, height/2);
  text("|", textX, height-screenCorner[1]-2*charSize);
  text("|", textX, height-screenCorner[1]-charSize);
  text("v", textX, height-screenCorner[1]);

  // make 'screen'
  fill(255);
  rect(100, 50, width-200, height-100);
}

// This runs the actual animation.  Depending on the frame,
// it does different things.  Essentially, it sets the colors
// of each image and notes if anything needs to be highlighted.
int[] animate() {
  int[] special = new int[4];
  if (frameCount == 1) {
    // make both images be pure white
    for (int i = 0; i < img1.length; i++) {
      for (int j = 0; j < img1[0].length; j++) {
        img1[i][j] = 255;
      }
    }
    for (int i = 0; i < img2.length; i++) {
      for (int j = 0; j < img2[0].length; j++) {
        img2[i][j] = 255;
      }
    }
  }
  else if (frameCount == 2) {
    // "load" img1
        colorMode(HSB);
    for (int i = 0; i < img1.length; i++) {
      int j;
      for (j = 0; j < i; j++) {
        img1[i][j] = color(100 + round(155*(i*1.0)/(img1.length-1)), 
        255-round(180*(j*1.0)/(img2.length-1)), 
        255);
      }
      for(; j < img1[0].length; j++) {
        img1[i][j] = color(100 + round(155*(i*1.0)/(img1.length-1)), 
        255-round(180*(j*1.0)/(img2.length-1)), 
        0+round(255*(j*1.0)/(img2.length-1)));
      }
    }
        colorMode(RGB);
  }
  else if (frameCount < 3 + img1.length*img1[0].length) {
    return transform(frameCount-3);
  }

  // if nothing has returned yet, then there is no special point
  special[0] = -1;
  special[1] = -1;
  special[2] = -1;
  special[3] = -1;
  extraspecial1 = new int[0];
  extraspecial2 = new int[0];
  return special;
}

int[] transform(int pix) {
  int[] special = new int[4];

  if (transformation == 'm') {
    special[0] = pix%img1[0].length;
    special[1] = pix/img1[0].length;
    special[2] = img1.length-special[0]-1;
    special[3] = special[1];
    img2[special[2]][special[3]] = img1[special[0]][special[1]];
  }
  else if (transformation == 'r') {
    special[0] = pix%img1[0].length;
    special[1] = pix/img1[0].length;
    special[2] = img1[0].length-special[1]-1;
    special[3] = special[0];
    img2[special[2]][special[3]] = img1[special[0]][special[1]];
  }
  else if (transformation == 's') {
    special[2] = (pix/2)%(img1[0].length/2);
    special[3] = (pix/2)/(img1[0].length/2);
    special[0] = special[2]*2;
    special[1] = special[3]*2;

    img2[special[2]][special[3]] = img1[special[0]][special[1]];
  }
  else if (transformation == 'e') {
    int basex = (pix/4)%(img1.length/2);
    int basey = (pix/4)/(img1[0].length/2);
    special[0] = basex;
    special[1] = basey;

    special[2] = basex*2;
    special[3] = basey*2;
    if ((pix%4 == 1) || (pix%4 == 3)) {
      special[2]++;
    }
    if ((pix%4 == 2) || (pix%4 == 3)) {
      special[3]++;
    }
    img2[special[2]][special[3]] = img1[special[0]][special[1]];
  }
  else if (transformation == 'c') {
    special[0] = pix%img1[0].length;
    special[1] = pix/img1[0].length;
    special[2] = special[0];
    special[3] = special[1];

    color old = img1[special[0]][special[1]];
    img2[special[2]][special[3]] = color(blue(old), red(old), green(old));
  }
  else if (transformation == 'd') {
    special[2] = special[0] = pix%img1[0].length;
    special[3] = special[1] = pix/img1[0].length;
    if (special[0] == (img1.length-1)) {
      special[0]--;
      special[2]--;
    }
    else if (special[1] == (img1[0].length - 1)) {
      special[2] = special[0] = img1.length-1;
      special[3] = special[1] = special[1] - 1;
    }
    else {
      // highlight the points that are used to make the edge detection decision
      extraspecial1 = new int[4];
      extraspecial1[0] = special[0] + 1;
      extraspecial1[1] = special[1];
      extraspecial1[2] = special[0];
      extraspecial1[3] = special[1] + 1;
      img2[special[2]][special[3]] = edge_detection(img1[special[0]][special[1]],  
      img1[extraspecial1[2]][extraspecial1[3]],
      img1[extraspecial1[0]][extraspecial1[1]]);
    }
  }
  return special;
}

int lineThreshold = 2;

// The algorithm used in this function is from
// "Introduction to Computing and Programming in
// Python: A Multimedia Approach", by Mark Guzdial
// and Barbara Ericson.
color edge_detection(color c, color below, color next) {
  int lineThreshold = round(map(mouseX, 0, width, 1, 50));
  // convert all colors to grayscale
  c = round(brightness(c));
  below = round(brightness(below));
  next = round(brightness(next));
  // see if C differs greatly from its two neighbors
  if ((abs(c-below) > lineThreshold) &&
    (abs(c-next) > lineThreshold)) {
    // if yes, make it black
     return color(0);
  }
  else {
    // otherwise, make it white
    return color(255);
  }
}