package hairdetection;


import org.opencv.core.Core;
import org.opencv.core.CvType;
import org.opencv.core.Mat;
import org.opencv.imgcodecs.Imgcodecs;
import org.opencv.core.Size;
import org.opencv.imgproc.Imgproc;
import org.opencv.core.Rect;
import org.opencv.core.Scalar;
import java.util.List;
import java.util.ArrayList;
import org.opencv.core.MatOfFloat;
import org.opencv.core.MatOfInt;
//import org.opencv.core.MatOfInt;
import org.opencv.core.MatOfPoint;
import org.opencv.core.Point;

public class OpenCVoperation 
{
    //Matrices invovled in algorithm
    private Mat sourceImage;
    private Mat matrix2_grabcut;
    private Mat matrix3_skindetection;
    private Mat orgMask;
    private Mat mask_rgb;
    private Mat newMask;
    private Mat matrix5_grabcut_quantized;
    private Mat matrix6_skin_quantized;
    private Mat matrix7_output;
    private Mat matrix8_max_contour;
    private Mat erosion_dilutionMatrix;
    private Mat matrix9_finalOutput;
    
    //String path names invovled in code
    private final String resultDirectory;
    private final String fileName;
    private final String grabcutOutput;
    private final String skinDetectionOutput;
    private final String grabcut_QuantizedOutput;
    private final String skin_QuantizedOutput;
    private final String finalImage_Output;
    private final String maskOutput;
    private final String maskRgbOutput;
    private final String contourMaskOutput;
    private final String erosionOutput;
    private final String dilationOutput;
    private final String morphingOutput;
    private final String FinalImageWithDilutionOutput;
    public OpenCVoperation(String rootDirectory,String sourceFileName,String[] OutputFileNames)
    {
         resultDirectory = rootDirectory;
         fileName =sourceFileName;
         grabcutOutput = "face2_grabcut.png";
         skinDetectionOutput = "face3_skindetection.png";
         /*--existing file names
         grabcut_QuantizedOutput = "face5_grabcut_quantized.png";
         skin_QuantizedOutput = "face6_skin_quantized.png";
         finalImage_Output = "face7_output.png";
         maskOutput ="skin_mask.png";
         maskRgbOutput = "skin_mask_rgb.png";
         contourMaskOutput="face8_contour_image.png";
         erosionOutput ="erosion_image.png";
         dilationOutput ="dilation_image.png";
         */
         
         dilationOutput ="dilation_image.png";
         maskOutput ="skin_mask.png";
         maskRgbOutput = "skin_mask_rgb.png";
         grabcut_QuantizedOutput = OutputFileNames[2]+".png";
         skin_QuantizedOutput = OutputFileNames[3]+".png";
         morphingOutput = OutputFileNames[4]+".png";
         finalImage_Output = OutputFileNames[5]+".png";
         erosionOutput =OutputFileNames[6]+".png";
         contourMaskOutput=OutputFileNames[7]+".png";
         FinalImageWithDilutionOutput =OutputFileNames[8]+".png";
         
        System.loadLibrary(Core.NATIVE_LIBRARY_NAME);
    }
    
    public void testGrabCut()
    {
        sourceImage = Imgcodecs.imread(resultDirectory+fileName,Imgcodecs.CV_LOAD_IMAGE_COLOR);
        System.out.println("result Directory is: "+ resultDirectory+fileName);
        
        Mat result = new Mat(sourceImage.size(),sourceImage.type());
        Mat bgModel = new Mat();    //background model
        Mat fgModel = new Mat();    //foreground model
        
        //draw a rectangle 
        Rect rectangle = new Rect(1,1,sourceImage.cols()-1,sourceImage.rows()-1);
        
        Imgproc.grabCut(sourceImage, result,rectangle, bgModel,fgModel,10,Imgproc.GC_INIT_WITH_RECT);  
        Core.compare(result,new Scalar(3,3,3),result,Core.CMP_EQ);
        matrix2_grabcut = new Mat(sourceImage.size(),CvType.CV_8UC3,new Scalar(255,255,255));
        sourceImage.copyTo(matrix2_grabcut, result);
        
        Imgcodecs.imwrite(resultDirectory+grabcutOutput,matrix2_grabcut);
        
       // displayPixels(matrix2_grabcut);
    }  
    
    //skin detection algorithm 1
    public void skinSegmentation_WithThreshold()
    { 
        //-----code for skin detection--using hsv color method
        
        orgMask= new Mat(matrix2_grabcut.size(),CvType.CV_8UC3);
        orgMask.setTo(new Scalar(0,0,0));
        
        Mat hsvImage = new Mat();
        Imgproc.cvtColor(matrix2_grabcut, hsvImage, Imgproc.COLOR_BGR2HSV);
        Core.inRange(hsvImage, new Scalar(3,30,50),new Scalar(33,255,255),orgMask);
        
        Imgcodecs.imwrite(resultDirectory + maskOutput, orgMask);
        
        newMask = Imgcodecs.imread(resultDirectory + maskOutput);
        //code for getting rgb skin mask from hsv skin mask
        mask_rgb = new Mat(newMask.size(),CvType.CV_8SC3);
        Imgproc.cvtColor(newMask, mask_rgb, Imgproc.COLOR_HSV2RGB);    
        Imgcodecs.imwrite(resultDirectory+maskRgbOutput, mask_rgb);
        
        //getting only skin image with red background
        matrix3_skindetection= new Mat(sourceImage.size(), sourceImage.type());
        matrix3_skindetection.setTo(new Scalar(0,0,255));
        sourceImage.copyTo(matrix3_skindetection,orgMask);
        
        Imgcodecs.imwrite(resultDirectory+skinDetectionOutput,matrix3_skindetection);
    }
    
    
    // -- skin detection algo 2
    public void skinSegmentation()
    {
        matrix3_skindetection = new Mat(matrix2_grabcut.size(),matrix2_grabcut.type()); 
        matrix3_skindetection.setTo(new Scalar(0,0,255));
        Mat skinMask = new Mat();
        Mat hsvMatrix = new Mat();
        
        Scalar lower = new Scalar(0,48,80);
        Scalar upper = new Scalar(20,255,255);
        
        Imgproc.cvtColor(matrix2_grabcut, hsvMatrix, Imgproc.COLOR_BGR2HSV);
        Core.inRange(hsvMatrix, lower, upper, skinMask);
        
        Mat kernel =Imgproc.getStructuringElement(Imgproc.MORPH_ELLIPSE,new Size(11,11));
        Imgproc.erode(skinMask, skinMask, kernel);
        Imgproc.dilate(skinMask, skinMask, kernel);  
        
        Imgproc.GaussianBlur(skinMask,skinMask, new Size(3,3), 0);
        
        Core.bitwise_and(matrix2_grabcut, matrix2_grabcut, matrix3_skindetection,skinMask);
        Imgcodecs.imwrite(resultDirectory+skinDetectionOutput , matrix3_skindetection);
    }
    
    
      //new skin detection algorithm-- skin detection Algorithm 3
    public void skinDetection2()
    {
        matrix3_skindetection = new Mat(matrix2_grabcut.size(),matrix2_grabcut.type()); 
        matrix3_skindetection.setTo(new Scalar(0,0,255));
        
        Mat  src_YCrCb = new Mat(matrix2_grabcut.size(),CvType.CV_8SC3);
        Mat src_hsv = new Mat(matrix2_grabcut.size(),CvType.CV_8SC3);
        
        Imgproc.cvtColor(matrix2_grabcut, src_YCrCb, Imgproc.COLOR_BGR2YCrCb);  
        matrix2_grabcut.convertTo(src_hsv, CvType.CV_32FC3);
        Imgproc.cvtColor(src_hsv, src_hsv, Imgproc.COLOR_BGR2HSV);
        Core.normalize(src_hsv, src_hsv, 0.00, 255.00, Core.NORM_MINMAX,CvType.CV_32FC3);
        
        for(int r = 0 ; r< matrix2_grabcut.rows(); r++)
        {
            for(int c = 0 ; c< matrix2_grabcut.cols(); c++)
            {
                double[] Pixel_val_rgb = matrix2_grabcut.get(r, c);
                int B= (int)Pixel_val_rgb[0];
                int G= (int)Pixel_val_rgb[1];
                int R= (int)Pixel_val_rgb[2];
                boolean a1= R1(R,G,B);
                
                double[] Pixel_val_YCrCb = src_YCrCb.get(r, c);
                int Y =(int)Pixel_val_YCrCb[0];
                int Cr =(int)Pixel_val_YCrCb[1];
                int Cb =(int)Pixel_val_YCrCb[2];
                boolean a2= R2(Y,Cr,Cb);
                
                double[] Pixel_val_hsv =src_hsv.get(r,c);
                float H = (float)Pixel_val_hsv[0];
                float S = (float)Pixel_val_hsv[1];
                float V = (float)Pixel_val_hsv[2];
                boolean a3= R3(H,S,V);
                
                if(!(a1 && a2 && a3))
                   matrix3_skindetection.put(r, c, new double[]{0,0,255});
                else
                   matrix3_skindetection.put(r,c,sourceImage.get(r, c));
             }
        }
        Imgcodecs.imwrite(resultDirectory+skinDetectionOutput , matrix3_skindetection);
    }
    
    public boolean R1(int R,int G,int B)
    {
       boolean e1 = (R>95) && (G>40) && (B>20) && ((Math.max(R,Math.max(G,B)) - Math.min(R, Math.min(G,B)))>15) && (Math.abs(R-G)>15) && (R>G) && (R>B);
       boolean e2 = (R>220) && (G>210) && (B>170) && (Math.abs(R-G)<=15) && (R>B) && (G>B);
       return (e1||e2);
    }
    
    public boolean R2(float Y, float Cr, float Cb)
    {
        boolean e3 = Cr <= 1.5862*Cb+20;
        boolean e4 = Cr >= 0.3448*Cb+76.2069;
        boolean e5 = Cr >= -4.5652*Cb+234.5652;
        boolean e6 = Cr <= -1.15*Cb+301.75;
        boolean e7 = Cr <= -2.2857*Cb+432.85;
        return e3 && e4 && e5 && e6 && e7;
    }
    public boolean R3(float H, float S, float V) 
    {
        return (H<25) || (H > 230);
    }
    
    
    void setQuantizedImages()
    {
        matrix5_grabcut_quantized = this.quantizeImage(matrix2_grabcut, grabcut_QuantizedOutput);
        matrix6_skin_quantized = this.quantizeImage(matrix3_skindetection,skin_QuantizedOutput);         
    }
      
    public void findContours() 
    {
           
        //Mat orgImage = Imgcodecs.imread(imageFilePath); //load image
        Mat grayImage = new Mat();
        Mat cannyImage= new Mat();
        List<MatOfPoint> contours = new ArrayList<>();  
        Imgproc.cvtColor(this.erosion_dilutionMatrix, grayImage, Imgproc.COLOR_BGR2GRAY);      //bgr to gray scale image conversion
        Imgproc.Canny(grayImage, cannyImage, 100, 200);     //get edges of image
        
        
        //morph edge detected image to improve egde connectivity
        Mat element= Imgproc.getStructuringElement(Imgproc.CV_SHAPE_RECT, new Size(5,5));
        Imgproc.morphologyEx(cannyImage, cannyImage, Imgproc.MORPH_CLOSE, element);
        String morphedImagePath = resultDirectory+ morphingOutput;
        Imgcodecs.imwrite(morphedImagePath,cannyImage);
        
        
        Mat hierarchy = new Mat();
        Imgproc.findContours(cannyImage, contours, hierarchy, Imgproc.RETR_EXTERNAL, Imgproc.CHAIN_APPROX_NONE);    //find all contours
        
        matrix8_max_contour = Mat.zeros(cannyImage.size(),CvType.CV_8UC3);
        matrix8_max_contour.setTo(new Scalar(255,255,255));
        double maxArea =Imgproc.contourArea(contours.get(0));
       // double maxArea = 0;
        int maxAreaIndex =0;
        MatOfPoint temp_contour;
        for(int i=1; i<contours.size(); i++)
        {   
            temp_contour = contours.get(i);
            double curr_cont_area = Imgproc.contourArea(temp_contour); 
            if(maxArea < curr_cont_area)
            {
                maxArea=curr_cont_area;
                maxAreaIndex = i;
            }
        }
         //Imgproc.drawContours(matrix8_max_contour, contours, maxAreaIndex, new Scalar(0,0,0),Core.FILLED);
         //   Imgproc.drawContours(matrix8_max_contour, contours, maxAreaIndex, new Scalar(0,0,0),1);
            Imgproc.drawContours(matrix8_max_contour, contours, maxAreaIndex, new Scalar(0,0,0),-1);
            //Imgproc.watershed();
            Imgcodecs.imwrite(resultDirectory+contourMaskOutput,matrix8_max_contour);
         
         
          matrix9_finalOutput = new Mat(sourceImage.size(),sourceImage.type());
         matrix9_finalOutput.setTo(new Scalar(255,255,255));
        
          for( int r =0 ;r < matrix8_max_contour.rows() ; r++)
        {
            for( int c=0; c < matrix8_max_contour.cols() ; c++)
            {
                double[] pixel_val = matrix8_max_contour.get(r, c);
                if(pixel_val[0] == 0 && pixel_val[1] == 0 && pixel_val[2] == 0)
                {
                    double[] orginal_pixel_val = sourceImage.get(r, c);
                    matrix9_finalOutput.put(r,c,orginal_pixel_val);
                }
            }      
        }
          
        //dilution on final image
        int erosion_size=2;
        Mat element2 = Imgproc.getStructuringElement(Imgproc.MORPH_DILATE,  new Size(2*erosion_size + 1, 2*erosion_size+1));
        Imgproc.dilate(matrix9_finalOutput,matrix9_finalOutput, element2);  
        Imgcodecs.imwrite(resultDirectory+FinalImageWithDilutionOutput, matrix9_finalOutput);
         
        // Imgproc.drawContours(mask, contours, maxAreaIndex, new Scalar(255,255,255));
        
        
        /* 
        //--------------code for copying original image with mask--------------------------
        //Mat croppedImage = new Mat(orgImage.size(),CvType.CV_8UC3);
        Mat croppedImage = new Mat(orgImage.size(),orgImage.type());
        //Mat croppedImage = Mat.ones(orgImage.size(), orgImage.type());
        croppedImage.setTo(new Scalar(0,0,0));      
        String destinationPath = "/home/sujit25/Pictures/croppedImage.png";
        orgImage.copyTo(croppedImage, mask);              // copy original image with mask 
        Imgcodecs.imwrite(destinationPath, croppedImage);
        return destinationPath;
        */
        /* //normalize and save mask
        //Core.normalize(mask, mask,0,255,Core.NORM_MINMAX,CvType.CV_8UC3);
         // write mask 
        String destinationPath2 = "/home/sujit25/Pictures/maskImage.png";
        Imgcodecs.imwrite(destinationPath2,mask);
        return destinationPath2;
        */
        
         /*
        //---------------code for grabcut with final mask------------------------------/
        Mat bgModel = new Mat();        //background model
        Mat fgModel = new Mat();        //foreground model
        Rect rectangle = Imgproc.boundingRect(contours.get(maxAreaIndex));  //draw a rectangle around maximum area contour
        Mat result = new Mat();
        
        Imgproc.grabCut(orgImage, result, rectangle, bgModel, fgModel,1,Imgproc.GC_INIT_WITH_RECT);
        Core.compare(result,new Scalar(3,3,3),result,Core.CMP_EQ);
        Mat foreground = new Mat(orgImage.size(),CvType.CV_8UC3,new Scalar(0,0,255));
        orgImage.copyTo(foreground, result);
        String destination = "/home/sujit25/Pictures/Results/face4_contourImage.png";
        Imgcodecs.imwrite(destination, foreground);
        return destination;
        */
      //  return this.skinSegmentation_WithThreshold(destination);
    }
    
    
    //step5
    public void findImageDifference()
    {
        matrix7_output = new Mat(sourceImage.size(),sourceImage.type());
        matrix7_output.setTo(new Scalar(255,255,255));      //white colored image 
        int rows = sourceImage.rows();
        int cols = sourceImage.cols();
        
        for(int r=0;r <rows ; r++)
        {
            for(int c =0; c < cols; c++)
            {
              //  double grabcut_pixel_val[] =matrix2_grabcut.get(r, c);
              //  double skin_pixel_val[] = newMask.get(r, c);                  
                 double grabcut_pixel_val[] =matrix5_grabcut_quantized.get(r,c);
                 double skin_pixel_val[] =  matrix6_skin_quantized.get(r,c);      
                    //extract those pixels which are non blue in 1st image and red in 2nd image
                  if(  ( (grabcut_pixel_val[0] != 255 ) && (grabcut_pixel_val[1]!=255 ) && (grabcut_pixel_val[2] !=255) )  && ( (skin_pixel_val[0]== 0) && (skin_pixel_val[1]== 0) &&(skin_pixel_val[2]== 255) ) )              
                  {
                       double orgImage_pixel_val[] = sourceImage.get(r, c);
                      //double orgImage_pixel_val[] = new double[]{0,0,0};
                      //double pixel_val[] = new double[3];
                      //pixel_val[0]=pixel_val[1]=pixel_val[2]=0;
                       matrix7_output.put(r, c, orgImage_pixel_val);
                  }
            }
        }       
        Imgcodecs.imwrite(resultDirectory +finalImage_Output, matrix7_output);
        
    }
    public  Mat quantizeImage(Mat image,String destinationFileName)
    {
        //Mat image  = testGrabCut(imageFilePath);
        int rows = image.rows();
        int cols = image.cols();
        Mat newImage = new Mat(image.size(),image.type());
        for(int r = 0 ; r < rows ; r++)
        {
            for(int c =0; c< cols; c++)
            {
                double [] pixel_val = image.get(r, c);
                double [] pixel_data = new double[3];
                pixel_data[0] = reduceVal(pixel_val[0]);
                pixel_data[1] = reduceVal(pixel_val[1]);
                pixel_data[2] = reduceVal(pixel_val[2]);
             //   System.out.print("(" +pixel_data[0]+","+pixel_data[1]+","+pixel_data[2]+") *");
                newImage.put(r, c, pixel_data);
            }
          //  System.out.println();
        }
        /*
        MatOfInt params= new MatOfInt();
        int arr[] = new int[2];
        arr[0]= Imgcodecs.CV_IMWRITE_JPEG_QUALITY;
        arr[1]= 100;
        params.fromArray(arr);
        */
        Imgcodecs.imwrite(resultDirectory+destinationFileName, newImage);
        return newImage;
    }
    
    public double reduceVal(double val)
    {   
        if(val >=0.00 && val <64.00) return 0.00;
        else if(val>=64.00 && val <128.00) return 64.00;
        else if (val>= 128.00 && val < 192.00) return 128.00;
        else return 255.00;
        
    }
    public String getFinalPath()
    {
        return finalImage_Output;
    }
    public  void displayPixels(Mat image)
    {
        for( int r =0 ;r < image.rows() ; r++)
        {
            for( int c=0; c < image.cols() ; c++)
            {
                double[] pixel_val = image.get(r, c);
                System.out.print("(" +pixel_val[0]+","+pixel_val[1]+","+pixel_val[2]+") *");
            }
            System.out.println();
        }
    }
    
    public void performErosion_Dilution()
    {
            erosion_dilutionMatrix = new Mat(this.matrix7_output.size(),this.matrix7_output.type());
            int erosion_size=2;
           
            //erosion
            Mat element1 = Imgproc.getStructuringElement(Imgproc.MORPH_ERODE,  new Size(2*erosion_size + 1, 2*erosion_size+1));
            Imgproc.erode(matrix7_output, erosion_dilutionMatrix, element1);
            Imgcodecs.imwrite(resultDirectory+erosionOutput,erosion_dilutionMatrix);
            
            /*
            //dilation
            Mat element2 = Imgproc.getStructuringElement(Imgproc.MORPH_DILATE,  new Size(2*erosion_size + 1, 2*erosion_size+1));
            Imgproc.dilate(erosion_dilutionMatrix, erosion_dilutionMatrix, element2);
            Imgcodecs.imwrite(resultDirectory+this.dilationOutput,erosion_dilutionMatrix);
            */
    }
    
    public void predict_hair_color()
    {
        Mat hsv_input = matrix9_finalOutput.clone();
        List<Mat> channels= new ArrayList<>();
        Mat hsv_histogram = new Mat();
        MatOfFloat  ranges = new MatOfFloat(0,180);
        MatOfInt histSize = new MatOfInt(255);
        Imgproc.cvtColor(hsv_input, hsv_input, Imgproc.COLOR_BGR2HSV);
        Core.split(hsv_input, channels);
        Imgproc.calcHist(channels.subList(0,1), new MatOfInt(0), new Mat(), hsv_histogram, histSize, ranges);
        int hist_w =256;
        int hist_h = 150;
        
        int bin_w =(int)Math.round(hist_w/histSize.get(0,0)[0]);
        Mat histImage= new Mat(hist_h,hist_w,CvType.CV_8UC3,new Scalar(0,0,0));
        
        for(int i=1;i < histSize.get(0,0)[0];i++)
        {
            Imgproc.line(histImage, new Point(bin_w * (i - 1), hist_h - Math.round(hsv_histogram.get(i - 1, 0)[0])), new Point(bin_w * (i), hist_h - Math.round(hsv_histogram.get(i, 0)[0])), new Scalar(255,0,0),2);			    
        }
        Imgcodecs.imwrite(resultDirectory+"histogram_image.png",histImage);
    }
    
    /*
    public static void main(String[] args)
    {
        OpenCVoperation obj = new OpenCVoperation();
        
        obj.testGrabCut(resultDirectory+fileName);
        obj.skinSegmentation();
        //obj.skinDetection2();
        obj.setQuantizedImages();
        obj.findImageDifference(); 
        obj.performErosion_Dilution();
        obj.findContours();      
    }
    */
}