import processing.core.*; 
import processing.data.*; 
import processing.event.*; 
import processing.opengl.*; 

import java.util.LinkedHashSet; 
import processing.pdf.*; 
import controlP5.*; 
import java.awt.image.BufferedImage; 
import java.util.Iterator; 

import java.util.HashMap; 
import java.util.ArrayList; 
import java.io.File; 
import java.io.BufferedReader; 
import java.io.PrintWriter; 
import java.io.InputStream; 
import java.io.OutputStream; 
import java.io.IOException; 

public class TriangulateImage7_2_3_2_1 extends PApplet {

//Pan Zoom original idea
//https://forum.processing.org/two/discussion/20813/zooming-and-panning-headache

/*TODO: 
fixed: Look at mouse press action , mapped mouse coordinates are not correct
critical: hashSets based on pixel integer don't properly store float mouse coords or negative and out of range coords, need to find a different way to compare
Transparency slider for triangles alpha
interpolate edge colour


*/








public int blur_val = 0;
public String displayType = "mesh";
public boolean deleteMode = false;
public boolean panMode = false;
boolean refreshBuffer = true;
boolean refreshBufferOnce = false;

public SmartBlurFilter smartFilter = new SmartBlurFilter();
boolean insideFrame;

//Graphic UI varialbles
int maxEraserSize = 80;
int minEraserSize = 1;
boolean displayBlurMessage = false;
float eraserSize = 5.0f;
PImage img, img_b, img_c, delCursor, processingTextImg;

//Control varialbles
private ControlP5 cp5;
ControlFrame cf;
RadioButton r;
Toggle e; 
Textarea ta;
Slider blurSlider, thSlider, nCPSlider, cPSlider, cWSlider, cThSlider, TransSlider;
Button chooseButton, blurButton, sPButton, lPButton;
int controlFrameWidth = 360;
int initWindowLocationX = 100;
int initWindowLocationY = 100;


//Pan Zoom variables
float mx,my,ratio,xpt,ypt,xzt,yzt,swt,zoom;
float mappedMouseX,mappedMouseY;

//text file writer
PrintWriter output;
PGraphics pdf;

//Triangulate points and objects 
ArrayList<PVector> contourPointsList = new ArrayList<PVector>();
ArrayList<IntList> contourImgPoints;
ArrayList triangles = new ArrayList();

LinkedHashSet<PVector> userPointsHash = new LinkedHashSet<PVector>();
LinkedHashSet<PVector> points = new LinkedHashSet<PVector>();
LinkedHashSet<PVector> pointsDisplay = new LinkedHashSet<PVector>(); 

IntList contourPoints = new IntList();
IntList nonContourPoints = new IntList();



public void setup()
{
	
  zoom=1.0f;
	img = loadImage("Instructions.png");
  processingTextImg = loadImage("processing.png");
	//delCursor = loadImage("delcursor.png");
	img_b = img.get();
	img_c = countourImage(img_b, 1,80);

	
	//println("insets w are: " + widthInsets);
	surface.setSize(img.width, img.height);
  surface.setResizable(true);
	surface.setTitle("Traingulate 7");
  surface.setLocation(initWindowLocationX+controlFrameWidth,initWindowLocationY);

	//standard corner points
	points.add(new PVector(0, 0, 0));
	points.add(new PVector(img.width-1, 0, 0));
	points.add(new PVector(img.width-1, img.height-1, 0));
	points.add(new PVector(0, img.height-1, 0));
	points.add(new PVector(img.width/2, img.height/2, 0));
	
  userPointsHash.addAll(points);
  pointsDisplay.addAll(points); 	
	
  contourImgPoints = getThresholdPixels (img_c, true);
	nonContourPoints = contourImgPoints.get(0);
	contourPoints = contourImgPoints.get(1);

	noStroke();
	frameRate(60);
	cursor(CROSS);
	

	cp5 = new ControlP5(this);
	cf = new ControlFrame(this, 340, 690, "Tools");
	//noLoop();
}//end setup

public void draw()
{
  scale(zoom);
  translate(xpt-xzt,ypt-yzt);
  background(128);
  
    switch(displayType)
    {
      case "image":
      {
        image(img, 0, 0);
        noStroke();
        fill(0, 0, 255);
        
        for (PVector temp : pointsDisplay)
        {
          ellipse(temp.x, temp.y, 2, 2);
        }
        
        //for (int i=0; i< points.size(); i++)
        //{
        //  ellipse((points.get(i)).x, (points.get(i)).y, 2, 2);
        //}
        
        if (displayBlurMessage == true)  {drawBlurMessage();}
        
        break;
      }//end if displayImage true
    
      case "blurred":
      {
        image(img_b, 0, 0);
        noStroke();
        fill(0, 0, 255);
        for (PVector temp : pointsDisplay)
        {
          ellipse(temp.x, temp.y, 2, 2);
        }
        
        if (displayBlurMessage == true)  {drawBlurMessage();}
  
        
        break;
      }//end if displayImage true
      
      case "contour":
      {
        image(img_c, 0, 0);
        noStroke();
        fill(255, 0, 0);
        for (PVector temp : pointsDisplay)
        {
          ellipse(temp.x, temp.y, 2, 2);
        }
        
        if (displayBlurMessage == true)  {drawBlurMessage();}
        
        break;
      }//end if displayImage true
      
      case "mesh":
      {
        image(img, 0, 0);
        
        if (refreshBuffer == true){triangles = Triangulate.triangulate(pointsDisplay);}
    
        noFill();
    
        // draw the mesh of triangles
        beginShape(TRIANGLES);
        strokeJoin(BEVEL);
        strokeWeight(0.7f/zoom);
        stroke(0, 0, 255);
        for (int i = 0; i < triangles.size(); i++) 
        {
          Triangle t = (Triangle)triangles.get(i);
    
          vertex(t.p1.x, t.p1.y);
          vertex(t.p2.x, t.p2.y);
          vertex(t.p3.x, t.p3.y);
        }
        endShape();
        if (refreshBufferOnce == true){refreshBuffer = false;}
        if (displayBlurMessage == true)  {drawBlurMessage();}
        
        break;
      }//end if displayResult true
  
    	case "result":
    	{
    		image(img_b, 0, 0);
    		noStroke();
    
        //LinkedHashSet pointsDisplay = new LinkedHashSet(); 
        //pointsDisplay = (LinkedHashSet)points.clone(); 
        if (refreshBuffer == true){triangles = Triangulate.triangulate(pointsDisplay);}
    		
    		beginShape(TRIANGLES);
    		
    		for (int i = 0; i < triangles.size(); i++) 
    		{
    			Triangle t = (Triangle)triangles.get(i);
    		  
    			int ave_x = PApplet.parseInt((t.p1.x + t.p2.x + t.p3.x)/3);  
    			int ave_y = PApplet.parseInt((t.p1.y + t.p2.y + t.p3.y)/3);
          
          if (notInsideImage(ave_x,ave_y))
          {
            PVector imgEdgeIntersection = lineIntersectionBox(new PVector (ave_x,ave_y), new PVector (img.width/2,img.height/2), new PVector (1.0f, 1.0f), new PVector (img.width-1,img.height-1));
            fill(img_b.get(floor(imgEdgeIntersection.x), floor(imgEdgeIntersection.y)), 255);
          }
          else
          {
            fill(img_b.get(ave_x, ave_y), 255);
          }
    			vertex(t.p1.x, t.p1.y);
    			vertex(t.p2.x, t.p2.y);
    			vertex(t.p3.x, t.p3.y);
          
          //testing image intersection
          //if (notInsideImage(ave_x,ave_y))
          //{
          //  fill(255,0,0);
          //  stroke(255,0,0);
          //  ellipse(ave_x,ave_y,5,5);
          //  text( (str(ave_x) +" " + str(ave_y)) ,ave_x,ave_y);
          //  line(ave_x,ave_y,img.width/2,img.height/2);
          //  PVector imgEdgeIntersection = lineIntersectionBox(new PVector (ave_x,ave_y), new PVector (img.width/2,img.height/2), new PVector (0.0, 0.0), new PVector (img.width,img.height));
          //  ellipse(imgEdgeIntersection.x,imgEdgeIntersection.y,5,5);
          //}
    		}
    		endShape();
        if (refreshBufferOnce == true){refreshBuffer = false;}       
        if (displayBlurMessage == true)  {drawBlurMessage();}
        break;
    	}//end if displayResult true
    
      default:
      {
        image(img, 0, 0);
        noStroke();
        
        if (displayBlurMessage == true)  {drawBlurMessage();}
        
        break;
      }
    }//end switch

  
  if (deleteMode == true) 
  {
    drawEraserCursor();
    refreshBuffer = false;
  }
  
}//end draw


/////////////////////////////////////////
//        OTHER FUNCTIONS              //
/////////////////////////////////////////


//create a contour image from img , using weight v, and threshold
public PImage countourImage (PImage img, int v, int threshold)
{
	int[] AllImgPixels;

	int w = v*(-8);
	int[][] kernel = { { v, v, v },
					   { v, w, v },
					   { v, v, v } };
		
	PImage edgeImg = createImage(img.width, img.height, RGB);
	//long timer = System.currentTimeMillis();
	// Loop through every pixel in the image.
	for (int y = 1; y < img.height-1; y++) 
	{ // Skip top and bottom edges
		for (int x = 1; x < img.width-1; x++) 
		{ // Skip left and right edges
			float sum = 0; // Kernel sum for this pixel
			for (int ky = -1; ky <= 1; ky++) 
			{
				for (int kx = -1; kx <= 1; kx++) 
				{
					// Calculate the adjacent pixel for this kernel point
					int pos = (y + ky)*img.width + (x + kx);
					// Image is grayscale, red/green/blue are identical
					float val = red(img.pixels[pos]);
					// Multiply adjacent pixels based on the kernel values
					sum += kernel[ky+1][kx+1] * val;
				}
			}
			// For this pixel in the new image, set the gray value
			// based on the sum from the kernel
			if (sum >= threshold) {sum = 255;}
			else {sum = 0;}
			//println(sum);
			edgeImg.pixels[y*img.width + x] = color(sum);

		}
	}
	// State that there are changes to edgeImg.pixels[]
	edgeImg.updatePixels();

	return edgeImg;
}

//return an arraylist of intlist (0:contourPoints, 1:non-contourPoints)
public ArrayList<IntList> getThresholdPixels (PImage inImg, boolean shuffled)
{
	ArrayList<IntList> result = new ArrayList<IntList>();
	IntList cP = new IntList();
	IntList nCP = new IntList();
	//img = inImg.get();
	for (int y = 0; y < inImg.height; y++) 
	{ // Skip top and bottom edges
		for (int x = 0; x < inImg.width; x++) 
		{
			int argb = inImg.get(x,y);
			int value = (argb >> 16) & 0xFF;
			//println(value);
			if ( value > 254 || x == 0 || y == 0 || x == inImg.width-1 || y == inImg.height-1)
			{
				int i = y*inImg.width + x;
				cP.append(i);
			}
			else
			{
				int i = y*inImg.width + x;
				nCP.append(i);
			}
		
		}
	}
	if (shuffled == true)
	{
		cP.shuffle();
		nCP.shuffle();
	}
	result.add(nCP);
	result.add(cP);
		
	return result;
}

public int[] shuffle (int[] array) 
{
  int m = array.length, t, i;

  // While there remain elements to shuffle
  while (m < 0) {

    // Pick a remaining element
    i = (int)Math.floor(Math.random() * m--);

    // And swap it with the current element.
    t = array[m];
    array[m] = array[i];
    array[i] = t;
  }
  return array;
}
//create a hashset of cartesian coords from intlist of pixel coordinates
public LinkedHashSet<PVector> sublistIntList (IntList inList, int start, int end)
{
	LinkedHashSet<PVector> result = new LinkedHashSet<PVector>();
	for(int i = start; i < end; i++)
	{
		result.add((intToCoords(inList.get(i))));
	}
	return result;
}

//converts image pixel coordinate to cartesian coordinate
public PVector intToCoords (int tempPoint)
{
	int tempX = tempPoint % img.width;
	int tempY = (int)(tempPoint/img.width);
	PVector result = new PVector(tempX, tempY, 0);
	return result;
}

public boolean notInsideImage (float _x, float _y)
{
  return (_x <=0 || _x >= img.width || _y<=0 || _y >= img.height); 
}

// from: https://forum.processing.org/one/topic/urgent-intersection-between-one-line-and-set-of-lines.html
public PVector lineIntersection(PVector p1, PVector p2, PVector p3, PVector p4) 
{
  PVector b = PVector.sub(p2, p1);
  PVector d = PVector.sub(p4, p3);
 
  float b_dot_d_perp = b.x * d.y - b.y * d.x;
  if (b_dot_d_perp == 0) { return null; }
 
  PVector c = PVector.sub(p3, p1);
  float t = (c.x * d.y - c.y * d.x) / b_dot_d_perp;
  if (t < 0 || t > 1) { return null; }
  float u = (c.x * b.y - c.y * b.x) / b_dot_d_perp;
  if (u < 0 || u > 1) { return null; }
 
  return new PVector(p1.x+t*b.x, p1.y+t*b.y);
}

//assumes only one and first intersection - that's all we need for this
public PVector lineIntersectionBox (PVector p1, PVector p2, PVector boxP1, PVector boxP2)
{
  PVector result = null;
  PVector[] boxVectors = 
  {
   new PVector (boxP1.x,boxP1.y),
   new PVector (boxP1.x,boxP2.y),
   
   new PVector (boxP1.x,boxP2.y),
   new PVector (boxP2.x,boxP2.y),
   
   new PVector (boxP2.x,boxP2.y),
   new PVector (boxP2.x,boxP1.y),
   
   new PVector (boxP2.x,boxP1.y),
   new PVector (boxP1.x,boxP1.y)
  };
  
  for (int i = 0; i < boxVectors.length; i=i+2)
  {
     PVector is = lineIntersection(p1, p2, boxVectors[i], boxVectors[i+1]);
     if (is!=null) { result = is;break;}
  }
  
  return result;
}
// the ControlFrame class extends PApplet, so we 
// are creating a new processing applet inside a
// new frame with a controlP5 object loaded

public class ControlFrame extends PApplet 
{
  int w, h;
  String name;
  PApplet parent;
  ControlP5 cp5;
  
  public ControlFrame(PApplet _parent, int _w, int _h, String _name) 
  {
    super();   
    parent = _parent;
    w=_w;
    h=_h;
    name = _name;
  
    PApplet.runSketch(new String[]{name}, this);
  }
  
  public void settings() 
  {
    size(w, h);
  }
  
  public void setup() 
  {
    surface.setTitle(name);
    surface.setLocation(initWindowLocationX,initWindowLocationY);
    frameRate(25);
    cp5 = new ControlP5(this);
    
    //UI sizes
    int marginX = 10;
    int groupInsetX = 10;
    int groupWidth = controlFrameWidth - (4*marginX);
    int[] largeButtonSize = {(groupWidth - 2*groupInsetX), 20};
    int sliderWidth = 90 + 90 + 25;
    
    chooseButton = 
    cp5.addButton("choose")
    .setPosition(marginX,20)
    .setSize(groupWidth,largeButtonSize[1]+10)
    .plugTo(parent,"choose")
    .setLabel("Choose an Image...")
    .linebreak()
    ;

    Group g1 = 
    cp5.addGroup("g1")
    .setPosition(marginX,chooseButton.getPosition()[1] + chooseButton.getHeight() + 30)
    .setLabel("Blur Controls")
    .setBackgroundColor(color(0,60))
    .setSize(groupWidth,100)
    .disableCollapse()
    ;
    
    blurSlider =          
    cp5.addSlider("blur")
    .setPosition(groupInsetX,10)
    .setSize(sliderWidth,largeButtonSize[1])
    .setRange(0,20)
    .setValue(5)
    .setLabel("Blur Radius")
    .plugTo(parent,"blur")
    .setGroup(g1)
    ;
    
    thSlider=
    cp5.addSlider("threshold")
    .setPosition(groupInsetX,40)
    .setSize(sliderWidth,largeButtonSize[1])
    .setRange(0,255)
    .setValue(10)
    .setLabel("Threshold")
    .plugTo(parent,"threshold")
    .setGroup(g1)
    ;
    
    blurButton = 
    cp5.addButton("blurIt")
    .setPosition(groupInsetX,70)
    .setSize(sliderWidth,largeButtonSize[1])
    .plugTo(parent,"blurIt")
    .setLabel("Blur")
    .setGroup(g1)
    ;
  
    Group g2 = 
    cp5.addGroup("g2")
    .setPosition(marginX,g1.getPosition()[1] + g1.getBackgroundHeight() + 30)
    .setLabel("Point Generation")
    .setBackgroundColor(color(0,60))
    .setSize(groupWidth,130)
    .disableCollapse()
    ;
    
    cWSlider = 
    cp5.addSlider("contWeight")
    .setPosition(groupInsetX,10)
    .setSize(sliderWidth,largeButtonSize[1])
    .setRange(0,25)
    .setValue(1)
    .setLabel("Edge Weight")
    .plugTo(parent,"contWeight")
    .setGroup(g2)
    ;
    
    cThSlider = 
    cp5.addSlider("contThreshold")
    .setPosition(groupInsetX,40)
    .setSize(sliderWidth,largeButtonSize[1])
    .setRange(0,254)
    .setValue(80)
    .setLabel("Edge Threshold")
    .plugTo(parent,"contThreshold")
    .setGroup(g2)
    ;
    
    cPSlider = 
    cp5.addSlider("contourPointsN")
    .setPosition(groupInsetX,70)
    .setSize(sliderWidth,largeButtonSize[1])
    .setRange(0,500)
    .setValue(0)
    .setLabel("# of Edge Points")
    .plugTo(parent,"contourPointsN")
    .setGroup(g2)
    ;
  
    nCPSlider = 
    cp5.addSlider("randomPointsN")
    .setPosition(groupInsetX,100)
    .setSize(sliderWidth,largeButtonSize[1])
    .setRange(0,500)
    .setValue(0)
    .setLabel("# of Random Points")
    .plugTo(parent,"randomPointsN")
    .setGroup(g2)
    ;
  
    Group g3 = 
    cp5.addGroup("g3")
    .setPosition(marginX,g2.getPosition()[1] + g2.getBackgroundHeight() + 30)
    .setLabel("Point Control")
    .setBackgroundColor(color(0,60))
    .setSize(groupWidth,60)
    .disableCollapse()
    ;
    
    sPButton = 
    cp5.addButton("sPoints")
    .setPosition(groupInsetX,groupInsetX)
    .setSize(90,largeButtonSize[1])
    .plugTo(parent,"sPoints")
    .setLabel("Save Points")
    .setGroup(g3)
    ;
    
    lPButton =  
    cp5.addButton("lPoints")
    .setPosition(sPButton.getPosition()[0] + sPButton.getWidth() + 15,groupInsetX)
    .setSize(90,largeButtonSize[1])
    .plugTo(parent,"lPoints")
    .setLabel("Load Points")
    .setGroup(g3)
    ;
  
    e = cp5.addToggle("eraser")
    .plugTo(parent,"eraser")
    .setPosition(lPButton.getPosition()[0] + lPButton.getWidth() + 15,groupInsetX)
    .setSize(90,largeButtonSize[1])
    .setLabel("On/Off Eraser (e)")
    .setGroup(g3)
    ;
  
    Group g4 = 
    cp5.addGroup("g4")
    .setPosition(marginX,g3.getPosition()[1] + g3.getBackgroundHeight() + 30)
    .setLabel("Display Options")
    .setBackgroundColor(color(0,60))
    .setSize(groupWidth,70)
    .disableCollapse()
    ;
   
    r = cp5.addRadioButton("passChooser")
    .setPosition(groupInsetX,10)
    .setSize(20,largeButtonSize[1])
    .setItemsPerRow(3)
    .setSpacingColumn(90)
    .setSpacingRow(10)
    .addItem("original (o)",1)
    .addItem("blurred (b)",2)
    .addItem("mesh (m)",3)
    .addItem("contour (c)",5)
    .addItem("result (r)",4)
    .activate(2)
    .plugTo(parent,"passChooser")
    .setGroup(g4)
    ;

    //Needs work
    /*TransSlider = 
    cp5.addSlider("randomPointsN")
    .setPosition(groupInsetX,100)
    .setSize(sliderWidth,largeButtonSize[1])
    .setRange(0,255)
    .setValue(255)
    .setLabel("Triangle Transparency")
    .plugTo(parent,"randomPointsN")
    .setGroup(g4)
    ;*/ 

  
    Button sP = 
    cp5.addButton("savePDF")
    .setPosition(marginX,g4.getPosition()[1] + g4.getBackgroundHeight() + 10)
    .setSize(groupWidth,largeButtonSize[1])
    .setLabel("Write to PDF")
    .plugTo(parent,"savePDF")
    ;
       
    Button sO = 
    cp5.addButton("saveOBJ")
    .setPosition(marginX,sP.getPosition()[1] + sP.getHeight() + 10)
    .setSize(groupWidth,largeButtonSize[1])
    .setLabel("Write to OBJ")
    .plugTo(parent,"saveOBJ")
    ;
       
     // cp5.addSlider("abc").setRange(0, 255).setPosition(10,10);
     // cp5.addSlider("def").plugTo(parent,"def").setRange(0, 255).setPosition(10,30);
     
    ta = 
    cp5.addTextarea("txt")
    .setPosition(marginX,sO.getPosition()[1] + 30)
    .setSize(groupWidth,60)
    .setLineHeight(14)
    .setColor(color(255))
    .setColorBackground(color(2,52,77))
    .setBorderColor(color(2,52,77))
    .setText("Message Window")  
    ;
  }

  public void draw() 
  {
      background(100);
  }
  

  public ControlP5 control() 
  {
    return cp5;
  }

  public void keyPressed() 
  {
    if (key == 's' || key == 'S') 
    {
      PGraphics pdf = createGraphics(img.width, img.height, PDF, "output.pdf");
      pdf.beginDraw();
      pdf.noStroke();   
      pdf.image(img, 0, 0);
      LinkedHashSet<PVector> pointsDisplay = new LinkedHashSet<PVector>();  
      pointsDisplay = (LinkedHashSet)points.clone(); 
      triangles = Triangulate.triangulate(pointsDisplay);
      for (int i = 0; i < triangles.size(); i++) 
      {
        Triangle t = (Triangle)triangles.get(i);
  
        int ave_x = PApplet.parseInt((t.p1.x + t.p2.x + t.p3.x)/3);  
        int ave_y = PApplet.parseInt((t.p1.y + t.p2.y + t.p3.y)/3);
  
        pdf.fill( img_b.get(ave_x, ave_y));
  
        pdf.triangle(t.p1.x, t.p1.y, t.p2.x, t.p2.y, t.p3.x, t.p3.y);
      }
      pdf.dispose();
      pdf.endDraw();
      save("output.png");
    }
  
    if (key == 'o' || key == 'O') 
    {
      displayType = "image";
      r.activate(0);
    }
  
    if (key == 'r' || key == 'R') 
    {
      displayType = "result";
      r.activate(4);
    }
  
    if (key == 'b' || key == 'B') 
    {
      displayType = "blurred";
      r.activate(1);
    }
  
    if (key == 'm' || key == 'M') 
    {
      displayType = "mesh";
      r.activate(2);
    }
   
    if (key == 'c' || key == 'C') 
    {
      displayType = "contour";
      r.activate(3);
    }
  
    if (key == 'e' || key == 'E') 
    {
      if (deleteMode == true) 
      {
        e.setState(false);
      }
      else if (deleteMode == false) 
      {
        e.setState(true);
      }
    }
     if ((key == '}' || key == ']') && (eraserSize != maxEraserSize))
    {
      eraserSize = eraserSize+1;  
    }
    
    if ((key == '{' || key == '[') && (eraserSize != minEraserSize))
    {
      eraserSize = eraserSize-1;
    }
  }
}
/////////////////////////////////////////
//        BUTTON FUNCTIONS             //
/////////////////////////////////////////

public void eraser(boolean theFlag) 
{
  //println("delete flag set");
  if (theFlag==true) 
  {
    deleteMode = true;
    refreshBufferOnce = true;
    pointsDisplay = (LinkedHashSet)points.clone();  
  } 
  else 
  {
    cursor(CROSS);
    deleteMode = false;
    refreshBuffer = true;
    refreshBufferOnce = false;
  }
}

public void choose() 
{
  refreshBuffer = true;
  selectInput("Select a file to process:", "imageFileSelect");
} //end choose

public void lPoints() 
{
  refreshBuffer = true;
  selectInput("Select a points text file:", "pointsFileSelect");
} //end lPoints

public void sPoints() 
{
  selectOutput("Save points text file:", "pointsFileSave");
} //end sPoints

public void savePDF()
{
  selectOutput("Save as a pdf:", "pdfFileSave");
}

public void saveOBJ()
{
  selectOutput("Save as a obj:", "objFileSave");
}

public void contWeight (int _value) 
{
  if (deleteMode == true){refreshBuffer = true;}
  img_c = countourImage(img_b, _value, (int)cThSlider.getValue());
  contourImgPoints = getThresholdPixels (img_c, true);
  nonContourPoints = contourImgPoints.get(0);
  contourPoints = contourImgPoints.get(1);
  displayType = "contour";
  r.activate(3);
}

public void contThreshold (int _value) 
{
  if (deleteMode == true){refreshBuffer = true;}
  img_c = countourImage(img_b, (int)cWSlider.getValue(),_value);
  contourImgPoints = getThresholdPixels (img_c, true);
  nonContourPoints = contourImgPoints.get(0);
  contourPoints = contourImgPoints.get(1);
  displayType = "contour";  
  r.activate(3);
}

public void blur (int blur_value) 
{
  smartFilter.setRadius(PApplet.parseInt(blur_value)); 
  //println("a numberbox event. setting background to "+ blur_value);
}

public void threshold (float threshold_value) 
{
  smartFilter.setThreshold((threshold_value)); 
  //println("a numberbox event. setting background to "+ threshold_value);
}

public void blurIt () 
{
  if ( smartFilter.getRadius() != 0 && smartFilter.getThreshold() != 0)
  {
    noLoop();
    displayBlurMessage = true;
    redraw();
    
    BufferedImage filtered = smartFilter.filter((BufferedImage) img.getNative());
    img_b = new PImage(filtered);
    loop();
    displayBlurMessage = false;
  }
  else
  {
    img_b = img.get();
  }
  displayType = "blurred";
  r.activate(1);
}

public void passChooser(int a) 
{ 
  if (a == 1)
  {
    displayType = "image";
    r.activate(0);
  }  

  if (a==2)
  {
    displayType = "blurred";
    r.activate(1);
  }

  if (a==3)
  {
    refreshBuffer = true;
    displayType = "mesh";
    r.activate(2);
  }

  if (a==4)
  {
    refreshBuffer = true;
    displayType = "result";
    r.activate(4);
  }
  if (a==5)
  {
    displayType = "contour";
    r.activate(3);
  }
}

//need to resolve this - currently uses the old pvector list need to convert everything to hashset
public void randomPointsN(int pointsNumber)
{
  //noLoop();
  if (deleteMode == true){refreshBuffer = true;}
  LinkedHashSet<PVector> contourPointsHash = new LinkedHashSet<PVector>(sublistIntList(contourPoints, 0, (int)cPSlider.getValue()));
  LinkedHashSet<PVector> nonContourPointsHash = new LinkedHashSet<PVector>(sublistIntList(nonContourPoints, 0, pointsNumber));   
  points = new LinkedHashSet<PVector>();
  pointsDisplay = new LinkedHashSet<PVector>();
  points.addAll(userPointsHash);
  points.addAll(contourPointsHash);
  points.addAll(nonContourPointsHash);
  
  pointsDisplay.addAll(points);

  //println(userPointsHash);
  //loop();
}

public void contourPointsN(int pointsNumber)
{
  //noLoop();
  if (deleteMode == true){refreshBuffer = true;}
  LinkedHashSet<PVector> contourPointsHash = new LinkedHashSet<PVector>(sublistIntList(contourPoints, 0, pointsNumber));
  LinkedHashSet<PVector> nonContourPointsHash = new LinkedHashSet<PVector>(sublistIntList(nonContourPoints, 0, (int)nCPSlider.getValue()));   
  points = new LinkedHashSet<PVector>();
  pointsDisplay = new LinkedHashSet<PVector>();
  points.addAll(userPointsHash);
  points.addAll(contourPointsHash);
  points.addAll(nonContourPointsHash);
  
  pointsDisplay.addAll(points);
  //loop();
}
public void drawEraserCursor ()
{
   noFill();
   stroke(0);
   strokeWeight(2.0f/zoom);
   ellipse(mappedMouseX, mappedMouseY, eraserSize*2.0f,eraserSize*2.0f);
   //ellipse(mouseX, mouseY, eraserSize*2.0,eraserSize*2.0);
}

public void drawBlurMessage()
{
   int img_w = 297;
   int img_h = 40;
   int img_x = (img.width - img_w)/2;
   int img_y = (img.height - img_h)/2;
   fill(0,0,255);
   rect(0,img.height/2 - 30, img.width, 60);
   textAlign(CENTER,CENTER);
   textSize(40);
   fill(255);
   image(processingTextImg,img_x,img_y, img_w, img_h);
   //text("PROCESSING",img.width/2,img.height/2 - 5);
}
/////////////////////////////////////////
//        KEY FUNCTIONS                //
/////////////////////////////////////////


public void keyPressed() 
{
  if (key == 's' || key == 'S') 
  {
    PGraphics pdf = createGraphics(img.width, img.height, PDF, "output.pdf");
    pdf.beginDraw();
    pdf.noStroke();   
    pdf.image(img, 0, 0);
    
    LinkedHashSet<PVector> pointsDisplay = new LinkedHashSet<PVector>();   
    pointsDisplay = (LinkedHashSet)points.clone(); 
    triangles = Triangulate.triangulate(pointsDisplay);
    
    for (int i = 0; i < triangles.size(); i++) 
    {
      Triangle t = (Triangle)triangles.get(i);

      int ave_x = PApplet.parseInt((t.p1.x + t.p2.x + t.p3.x)/3);  
      int ave_y = PApplet.parseInt((t.p1.y + t.p2.y + t.p3.y)/3);

      pdf.fill( img_b.get(ave_x, ave_y));

      pdf.triangle(t.p1.x, t.p1.y, t.p2.x, t.p2.y, t.p3.x, t.p3.y);
    }
    pdf.dispose();
    pdf.endDraw();
    save("output.png");
  }

  if (key == 'o' || key == 'O') 
  {
    displayType = "image";
    r.activate(0);
  }

  if (key == 'r' || key == 'R') 
  {
    displayType = "result";
    r.activate(4);
  }

  if (key == 'b' || key == 'B') 
  {
    displayType = "blurred";
    r.activate(1);
  }

  if (key == 'm' || key == 'M') 
  {
    displayType = "mesh";
    r.activate(2);
  }
 
  if (key == 'c' || key == 'C') 
  {
    displayType = "contour";
    r.activate(3);
  }

  if (key == 'e' || key == 'E') 
  {
    if (deleteMode == true) 
    {
      e.setState(false);
    }
    else if (deleteMode == false) 
    {  
      e.setState(true);
    }
  }
   if ((key == '}' || key == ']') && (eraserSize != maxEraserSize))
  {
    eraserSize = eraserSize+1;  
  }
  
  if ((key == '{' || key == '[') && (eraserSize != minEraserSize))
  {
    eraserSize = eraserSize-1;
  }
  
  //println(keyCode);
  //println(key);
}
public void pdfFileSave(File selection)
{
  if (selection == null) 
  {
    ta.setColor(color(255));
    ta.setText("Status." + "\n" + "Nothing selected, no file saved.");
    //JOptionPane.showMessageDialog(this, "No file saved.", "Error!", JOptionPane.INFORMATION_MESSAGE);
  } 
  else
  {
    if (selection.getName().endsWith(".pdf"))
    {
      pdf = createGraphics(img.width, img.height, PDF, selection.getAbsolutePath());
    }// end if txt
    else
    {
      pdf = createGraphics(img.width, img.height, PDF, selection.getAbsolutePath()+".pdf");
    }// end else end with

    pdf.beginDraw();
    pdf.noStroke();   
    pdf.image(img, 0, 0);
    
    LinkedHashSet<PVector> pointsDisplay = new LinkedHashSet<PVector>();  
    pointsDisplay = (LinkedHashSet)points.clone(); 
    triangles = Triangulate.triangulate(pointsDisplay);
    
    for (int i = 0; i < triangles.size(); i++) 
    {
      Triangle t = (Triangle)triangles.get(i);

      int ave_x = PApplet.parseInt((t.p1.x + t.p2.x + t.p3.x)/3);  
      int ave_y = PApplet.parseInt((t.p1.y + t.p2.y + t.p3.y)/3);
      pdf.fill( img_b.get(ave_x, ave_y));
      pdf.triangle(t.p1.x, t.p1.y, t.p2.x, t.p2.y, t.p3.x, t.p3.y);
    }
    pdf.dispose();
    pdf.endDraw();

    ta.setColor(color(0, 255, 0));
    ta.setText("Success." + "\n" + "The File was Saved Successfully!");

    //JOptionPane.showMessageDialog(this, "The File was Saved Successfully!", "Success!", JOptionPane.INFORMATION_MESSAGE);
  }//end else null
}// pdfFileSave

public void objFileSave(File selection)
{
  if (selection == null) 
  {
    ta.setColor(color(255));
    ta.setText("Status." + "\n" + "Nothing selected, no file saved.");
  }
  else
  {
    PrintWriter outputOBJ, outputMTL;
    String mtlFileName;
    if (selection.getName().endsWith(".obj"))
    {
      outputOBJ = createWriter(selection.getAbsolutePath());
      outputMTL = createWriter((selection.getAbsolutePath()).replace( ".obj", ".mtl" ));
      mtlFileName = (selection.getName()).replace( ".obj", ".mtl" );
    }// end if txt
    else
    {
      outputOBJ = createWriter(selection.getAbsolutePath()+".obj");
      outputMTL = createWriter(selection.getAbsolutePath()+".mtl");
      mtlFileName = (selection.getName()) + ".mtl";
    }// end else end with

    outputOBJ.println("mtllib " + mtlFileName +"\n");
    
    LinkedHashSet<PVector> pointsDisplay = new LinkedHashSet<PVector>();   
    pointsDisplay = (LinkedHashSet)points.clone(); 
    triangles = Triangulate.triangulate(pointsDisplay);
    
    LinkedHashSet<Integer> colorHash = new LinkedHashSet<Integer>();
    for (int i = 0; i < triangles.size(); i++) 
    {
      Triangle t = (Triangle)triangles.get(i);

      int ave_x = PApplet.parseInt((t.p1.x + t.p2.x + t.p3.x)/3);  
      int ave_y = PApplet.parseInt((t.p1.y + t.p2.y + t.p3.y)/3);
      int img_bColor =  img_b.get(ave_x, ave_y);
      
      String colorRGBName = str((img_bColor >> 16) & 0xFF) + "_" + str((img_bColor >> 8) & 0xFF) + "_" + str(img_bColor & 0xFF);
      
      boolean hasColor = colorHash.contains(img_bColor);
      if (hasColor == false)
      {
        colorHash.add(img_bColor);
        
                    
        String img_b_r = str(((img_bColor >> 16) & 0xFF)/255.0f);
        String img_b_g = str(((img_bColor >> 8) & 0xFF)/255.0f);
        String img_b_b = str((img_bColor & 0xFF)/255.0f);
        
        outputMTL.println("newmtl Colour_" + colorRGBName);
        outputMTL.println("\tNs 32");
        outputMTL.println("\td 1");
        outputMTL.println("\tTr 0");
        outputMTL.println("\tTf 1 1 1");
        outputMTL.println("\tillum 2");
        outputMTL.println("\tKa " + img_b_r + " " + img_b_g + " " + img_b_b);
        outputMTL.println("\tKd " + img_b_r + " " + img_b_g + " " + img_b_b);
        outputMTL.println("\tKs 0.349999994 0.349999994 0.349999994\n");
      }
      
      outputOBJ.println("v " + str(t.p1.x) + " " + str(t.p1.y) + " 0");
      outputOBJ.println("v " + str(t.p2.x) + " " + str(t.p2.y) + " 0");
      outputOBJ.println("v " + str(t.p3.x) + " " + str(t.p3.y) + " 0");
      
      outputOBJ.println("vn 0 1 0");
      outputOBJ.println("g triangle_" + str(i));
      outputOBJ.println("usemtl Colour_" + colorRGBName);
      outputOBJ.println("s 1");
      outputOBJ.println("f " + str(i*3 + 1) + "//" + str(i+1) + " " + str(i*3 + 2) + "//" + str(i+1) + " " + str(i*3 + 3)+ "//" + str(i+1) +"\n");
    }
    
    outputOBJ.flush();  // Writes the remaining data to the file
    outputOBJ.close();  // Finishes the file
    outputMTL.flush();  // Writes the remaining data to the file
    outputMTL.close();  // Finishes the file
    
    ta.setColor(color(0, 255, 0));
    ta.setText("Success." + "\n" + "The File was Saved Successfully!");
  }//end else null
}// end objFileSave

public void pointsFileSelect(File selection)
{
  if (selection == null) 
  {
    //println("Window was closed or the user hit cancel.");
  } 
  else
  {
    if (selection.getName().endsWith("txt"))
    {
      // load the image using the given file path
      String lines[] = loadStrings(selection.getPath());     
      String[] width_height = split(lines[0], " ");
      if (parseFloat(width_height[0]) == img.width && parseFloat(width_height[1]) == img.height)
      {
        zoom = 1.0f;
        xpt=0.0f;
        ypt=0.0f;
        xzt=0.0f;
        yzt=0.0f;

        noLoop();
        
        points = new LinkedHashSet<PVector>();
        //chosenPointsHash = new LinkedHashSet<PVector>();
        userPointsHash = new LinkedHashSet<PVector>();
        
        for (int i = 1; i < lines.length; i++)
        {
          String[] coords = split(lines[i], ", ");
          String[] coords_x = split(coords[0], "[ ");
          //println (coords);
          float x_ = parseFloat(coords_x[1]);
          float y_ = parseFloat(coords[1]);
          //println(lines[i]);
          points.add(new PVector(x_, y_, 0));
          //int pixelInteger = int(y_*img.width + x_);
          //chosenPointsHash.add(new PVector(x_, y_, 0));
          userPointsHash.add(new PVector(x_, y_, 0));
        }
        loop();
        ta.setColor(color(0, 255, 0));
        ta.setText("Success." + "\n" + "Points are loaded.");
        nCPSlider.setValue(0);
        cPSlider.setValue(0);
      }
      else {
        ta.setColor(color(255, 0, 0));
        ta.setText("Error." + "\n" + "Points file does not match the loaded image.");
        //JOptionPane.showMessageDialog(this, "Points file does not match the loaded image.", "Error!", JOptionPane.INFORMATION_MESSAGE);
      }
    }// end if txt
    else
    {
      ta.setColor(color(255, 0, 0));
      ta.setText("Error." + "\n" + "Please choose a txt file.");
      //JOptionPane.showMessageDialog(this, "Please choose a txt file.", "Error!", JOptionPane.INFORMATION_MESSAGE);
    }
  }//end else null
}//end pointsfileselction


public void imageFileSelect(File selection) 
{
  if (selection == null) 
  {
    ta.setColor(color(255));
    ta.setText("Status." + "\n" + "Nothing selected, selection was cancelled.");
  } 
  else 
  {
    if (selection.getName().endsWith("jpg") || selection.getName().endsWith("JPEG")|| selection.getName().endsWith("JPG")|| selection.getName().endsWith("jpeg") || selection.getName().endsWith("png")|| selection.getName().endsWith("PNG")|| selection.getName().endsWith("GIF") || selection.getName().endsWith("gif") || selection.getName().endsWith("tga")|| selection.getName().endsWith("TGA")|| selection.getName().endsWith("tiff")|| selection.getName().endsWith("TIFF")|| selection.getName().endsWith("tif")|| selection.getName().endsWith("TIF"))
    {

      PImage checkImg = loadImage(selection.getAbsolutePath()); 
      // Check if loaded image is valid if invalid should return null or width/height -1
      if (checkImg != null && checkImg.width > 0 && checkImg.height > 0)  
      { 
        img = checkImg.get();

        String Scaled = ""; 
        String extension = "";

        int q = selection.getAbsolutePath().lastIndexOf('.');
        int p = Math.max(selection.getAbsolutePath().lastIndexOf('/'), selection.getAbsolutePath().lastIndexOf('\\'));

        if (q > p) {
          extension = selection.getAbsolutePath().substring(q+1);
        }


        //check image is 60px less than the display
        if (img.width + 60 > displayWidth || img.height + 60 > displayHeight)
        {
          float ratio = PApplet.parseFloat(img.width)/PApplet.parseFloat(img.height);
          //println(ratio);
          int targetHeight = 0;
          int targetWidth = 0;

          if (img.width + 60 > displayWidth)
          {
            targetHeight = PApplet.parseInt((displayWidth- 60.0f)/ratio);  
            targetWidth = displayWidth - 60;
          }
          if (img.height + 60 > displayHeight)
          {
            targetWidth = PApplet.parseInt((displayHeight - 60.0f) * ratio);  
            targetHeight = displayHeight - 60;
          }

          PGraphics scaledImage = createGraphics(targetWidth, targetHeight);

          scaledImage.beginDraw();
          scaledImage.background(0, 0, 0, 0);
          scaledImage.image(img, 0, 0, targetWidth, targetHeight);
          scaledImage.endDraw();

          //println(extension);
          //println(selection.getAbsolutePath().substring(0, q)+"_scaled." + extension);     

          //scaledImage.save(selection.getAbsolutePath().substring(0, q)+"_scaled." + extension);
          //img = loadImage(selection.getAbsolutePath().substring(0, q)+"_scaled." + extension);
          Scaled = (" Original image was too large for your display - scaled to fit");
          img = scaledImage.get(0,0,targetWidth,targetHeight);
        }// end if img is bigger than display
        
        //println(selection.getAbsolutePath());
        img_b = img.get();
        img_c = countourImage(img_b, 1, 80);
        // size the window and show the image 

        surface.setSize(img.width, img.height);
        zoom = 1.0f;
        xpt=0.0f;
        ypt=0.0f;
        xzt=0.0f;
        yzt=0.0f;

        //chosenPointsHash = new LinkedHashSet<PVector>();
        userPointsHash = new LinkedHashSet<PVector>();
        points = new LinkedHashSet<PVector>();
        pointsDisplay = new LinkedHashSet<PVector>();
        
        //standard corner points
        points.add(new PVector(0, 0, 0));
        points.add(new PVector(img.width-1, 0, 0));
        points.add(new PVector(img.width-1, img.height-1, 0));
        points.add(new PVector(0, img.height-1, 0));
        points.add(new PVector(img.width/2, img.height/2, 0));
    
        //chosenPointsHash.addAll(points);
        userPointsHash.addAll(points);
        pointsDisplay.addAll(points);
        
        contourImgPoints = getThresholdPixels (img_c, true);
        nonContourPoints = contourImgPoints.get(0);
        contourPoints = contourImgPoints.get(1);
        
        cWSlider.setValue(1);
        cThSlider.setValue(80);
        displayType = "mesh";
        r.activate(2);
        nCPSlider.setValue(0);
        cPSlider.setValue(0);

        ta.setColor(color(0, 255, 0));
        ta.setText("Success." + "\n" + "Image file is loaded." + Scaled);
      } 
      else {
        ta.setColor(color(255, 0, 0));
        ta.setText("Error." + "\n" + "File chosen is not a valid image file.");
      }
    }  
    else
    {
      ta.setColor(color(255, 0, 0));
      ta.setText("Error." + "\n" + "Please choose an image file. (JPEG, JPG, PNG, TGA or GIF).");
      //JOptionPane.showMessageDialog(this, "Please choose an image file. (JPEG, JPG, PNG or GIF)", "Error!", JOptionPane.INFORMATION_MESSAGE);
    }
  }//end else selection
}// end image file selection

public void pointsFileSave(File selection)
{
  if (selection == null) 
  {
    ta.setColor(color(255));
    ta.setText("Status." + "\n" + "Nothing selected, no file saved.");
    ///JOptionPane.showMessageDialog(this, "No file saved.", "Error!", JOptionPane.INFORMATION_MESSAGE);
  } 
  else
  {
    if (selection.getName().endsWith(".txt"))
    {
      output = createWriter(selection.getAbsolutePath());
    }// end if txt
    else
    {
      output = createWriter(selection.getAbsolutePath()+".txt");
    }// end else end with

    output.println((img.width) + " " + (img.height));
    
    for (PVector temp : points)
    {
      output.println(temp);
    }
    
    output.flush();  // Writes the remaining data to the file
    output.close();  // Finishes the file
    ta.setColor(color(0, 255, 0));
    ta.setText("Success." + "\n" + "The File was Saved Successfully!");
    //JOptionPane.showMessageDialog(this, "The File was Saved Successfully!", "Success!", JOptionPane.INFORMATION_MESSAGE);
  }//end else null
}// end pointsfilesave
/////////////////////////////////////////
//        MOUSE FUNCTIONS              //
/////////////////////////////////////////


public void mouseMoved()
{
  //println(frameRate);
  
  loop();
  mappedMouseX = (mouseX/zoom - (xpt-xzt));
  mappedMouseY = (mouseY/zoom - (ypt-yzt));
  
  if (deleteMode == false && panMode == false)
  {
    pointsDisplay = (LinkedHashSet)points.clone();   
    pointsDisplay.add(new PVector(mappedMouseX, mappedMouseY, 0));
  }
  else if (deleteMode == true && panMode == false)
  {
    
  }
}
public void mouseDragged()
{
  if (mouseButton == CENTER)
  {
    panMode = true;
    xpt-=(pmouseX-mouseX)/zoom;
    ypt-=(pmouseY-mouseY)/zoom;
  }
}

public void mouseWheel(MouseEvent event) {
  swt-=event.getCount();
  
  if (swt==0) 
  {
    zoom=1.0f;
  } else if (swt>=1 && swt<=10) 
  {
    zoom=pow(2, swt);
  } else if (swt<=-1 && swt>=-10) 
  {
    zoom=1/pow(2, abs(swt));
  }
  
  xzt=((zoom-1)*width/2)/zoom;
  yzt=((zoom-1)*height/2)/zoom;
  
  if(event.getCount()<=-1)
  {
    xpt-=(mouseX-width/2)/zoom;
    ypt-=(mouseY-height/2)/zoom;
  } else {
    xpt+=(mouseX-width/2)/(pow(2, swt+1));
    ypt+=(mouseY-height/2)/(pow(2, swt+1));
  }
}

public void mouseEntered()
{
  //println("mouse entered");
}

public void mouseExited()
{
  pointsDisplay = (LinkedHashSet)points.clone(); 
  //println("mouse exited");
}


public void mousePressed() 
{ 
  if (mouseButton != CENTER) {panMode = false;}
  
  if (deleteMode == false && mouseEvent.getClickCount()< 2 && mouseButton == LEFT)
  {
    //println("mouse pressed");
    //long timer = System.currentTimeMillis();
    noLoop();  

    points.add(new PVector(mappedMouseX, mappedMouseY, 0));
    //pointsDisplay.add(new PVector(mappedMouseX, mappedMouseY, 0));
    userPointsHash.add(new PVector(mappedMouseX, mappedMouseY, 0));
    
    //redraw();
    //loop();
  }
  else if (deleteMode == true && mouseButton == LEFT)
  {
    //for (PVector temp : points)
    //{
    //  float d = dist(mappedMouseX, mappedMouseY, temp.x, temp.y);
    //  if ( d < eraserSize)
    //  {
    //    userPointsHash.remove(temp);
    //    points.remove(temp);
    //  }
    //}
    
    //LinkedHashSet<PVector> points = new LinkedHashSet<PVector>(); 
    refreshBuffer = true;
    refreshBufferOnce = true;
    Iterator<PVector> it = pointsDisplay.iterator();
    while(it.hasNext())
    {
      PVector p = it.next();
      float d = dist(mappedMouseX, mappedMouseY, p.x, p.y);
      if ( d < eraserSize)
      {
       userPointsHash.remove(p);
       it.remove();
      }
    }
    points = (LinkedHashSet)pointsDisplay.clone(); 
  }
} //end mousepressed

public void mouseReleased() 
{
  panMode = false;
  frameRate(60);
}//end released
  static public void main(String[] passedArgs) {
    String[] appletArgs = new String[] { "TriangulateImage7_2_3_2_1" };
    if (passedArgs != null) {
      PApplet.main(concat(appletArgs, passedArgs));
    } else {
      PApplet.main(appletArgs);
    }
  }
}
