/**
 * author:SiZheng.Chen
 * update date:20210120
**/

#include "wrinkle.h"

WrinkleData detectWrinkle(Mat image){
    WrinkleData data;
    if(image.empty()||image.type()!=CV_8UC3){
        cout<<"image is empty or type error."<<endl;
        data.wrinkleImg=image;
        return data;
    }
    Scalar colorL=Scalar(0,255,0);
    int THRESHOLD_LENTH=20;
    int THRESHOLD_AREA = 0;
    Mat lMat=wrinkleL(image);

    vector<vector<cv::Point> > contours;
    vector<Vec4i> hirearchy;
    findContours(lMat,contours,hirearchy,RETR_EXTERNAL,CHAIN_APPROX_SIMPLE);
    int countL=0;
    double areaL=0;
    for(int i=0;i<contours.size();++i){
        double area = contourArea(contours[i]);
        RotatedRect box=minAreaRect(contours[i]);

        float width=box.size.width>box.size.height?box.size.width:box.size.height;
        float height=box.size.width<box.size.height?box.size.width:box.size.height;

        if((height<THRESHOLD_LENTH&&width<THRESHOLD_LENTH)||area<THRESHOLD_AREA){
            drawContours(lMat,contours,i,Scalar(0),FILLED);
        }else{
            countL++;
            areaL+=area;
            drawContours(image,contours,i,colorL,2);
        }
    }
    data.wrinkleLenth=areaL;
    data.wrinkleCount=countL;
    data.wrinkleImg=image;
    return data;
}

Mat wrinkleL(Mat image){
    Mat mHairMask=hairMask(image);
    Mat grayImg;
    cvtColor(image,grayImg,COLOR_BGR2GRAY);

    cv::Ptr<CLAHE> clahe=createCLAHE();
    clahe->setClipLimit(3.5);
    clahe->setTilesGridSize(cv::Size(10,10));
    clahe->apply(grayImg,grayImg);

    GaussianBlur(grayImg,grayImg,cv::Size(15,15),0);
    Mat bilaterMat;
    bilateralFilter(grayImg,bilaterMat,0,120,10);
    grayImg = bilaterMat;

    Mat binImg,maskImg;
    adaptiveThreshold(grayImg,binImg,255,ADAPTIVE_THRESH_GAUSSIAN_C,THRESH_BINARY_INV,265,10);
    add(binImg,Mat(grayImg.size(),CV_8UC1,Scalar(0)),maskImg,mHairMask);

    // Mat kernel = getStructuringElement(MORPH_RECT,Size(15,15));
    // dilate(binImg,binImg,kernel);
    // erode(binImg,binImg,kernel);
    // imshow("win",maskImg);
    // waitKey();

    vector<vector<cv::Point> > contours;
    vector<Vec4i> hirearchy;
    binImg = Mat(grayImg.size(),CV_8UC1,Scalar(0));
    findContours(maskImg,contours,hirearchy,RETR_LIST,CHAIN_APPROX_NONE);
    for(int i=0;i<contours.size();++i){
        double area = contourArea(contours[i]);
        if(area>=150){
            RotatedRect box = minAreaRect(contours[i]);
            float width=box.size.width>box.size.height?box.size.width:box.size.height;
            float height=box.size.width<box.size.height?box.size.width:box.size.height;

            float eccentricity=(width+height)!=0?((width-height)/(width+height)):(width-height);
            if(eccentricity>0.0){
                drawContours(binImg,contours,i,Scalar(255),FILLED);
            }
        }
    }

    thinning(binImg,binImg,THINNING_ZHANGSUEN);
    Mat structKernel = getStructuringElement(MORPH_RECT,cv::Size(15,15));
    dilate(binImg,binImg,structKernel);

    contours.clear();
    hirearchy.clear();
    findContours(binImg,contours,hirearchy,RETR_LIST,CHAIN_APPROX_SIMPLE);
    for(int i=0;i<contours.size();++i){
        double area = contourArea(contours[i]);
        RotatedRect box = minAreaRect(contours[i]);
        float max_len=box.size.width>box.size.height?box.size.width:box.size.height;
        if(area>2000&&max_len>280){
        }else{
            drawContours(binImg,contours,i,Scalar(0),FILLED);
        }
    }

    thinning(binImg,binImg,THINNING_ZHANGSUEN);
    Mat viewKernel = getStructuringElement(MORPH_RECT,cv::Size(3,3));
    dilate(binImg,binImg,viewKernel);

    // imshow("win",binImg);
    // waitKey();

    return binImg;
}

Mat wrinlLeS(Mat image){
    return image;
}

Mat hairMask(Mat image){
    Mat grayImg;
    cvtColor(image,grayImg,COLOR_BGR2GRAY);

    Mat colorMat;
    applyColorMap(grayImg,colorMat,2);
    cvtColor(colorMat,grayImg,COLOR_BGR2GRAY);

    Mat binImg;
    adaptiveThreshold(grayImg,binImg,255,ADAPTIVE_THRESH_GAUSSIAN_C,THRESH_BINARY_INV,155,7);

    vector<vector<cv::Point> > contours;
    vector<Vec4i> hirearchy;
    Mat hairMat=Mat(grayImg.size(),CV_8UC1,Scalar(0));
    findContours(binImg,contours,hirearchy,RETR_LIST,CHAIN_APPROX_NONE);
    for(int i=0;i<contours.size();++i){
        double area = contourArea(contours[i]);
        if(130<=area){
            RotatedRect box = minAreaRect(contours[i]);
            float width=box.size.width>box.size.height?box.size.width:box.size.height;
            float height=box.size.width<box.size.height?box.size.width:box.size.height;
            float eccentricity=(width+height)!=0?((width-height)/(width+height)):(width-height);
            if(eccentricity>0.45){
                drawContours(hairMat,contours,i,Scalar(255),FILLED);
            }
        }
    }

    Mat structKernel=getStructuringElement(MORPH_RECT,cv::Size(9,9));
    dilate(hairMat,hairMat,structKernel);

    Mat lightMat=Mat(grayImg.size(),CV_8UC1,Scalar(255));
    hairMat = lightMat-hairMat;

    // imshow("win",hairMat);
    // waitKey();

    return hairMat;
}

// Applies a thinning iteration to a binary image
static void thinningIteration(Mat img, int iter, int thinningType){
    Mat marker = Mat::zeros(img.size(), CV_8UC1);

    if(thinningType == THINNING_ZHANGSUEN){
        for (int i = 1; i < img.rows-1; i++)
        {
            for (int j = 1; j < img.cols-1; j++)
            {
                uchar p2 = img.at<uchar>(i-1, j);
                uchar p3 = img.at<uchar>(i-1, j+1);
                uchar p4 = img.at<uchar>(i, j+1);
                uchar p5 = img.at<uchar>(i+1, j+1);
                uchar p6 = img.at<uchar>(i+1, j);
                uchar p7 = img.at<uchar>(i+1, j-1);
                uchar p8 = img.at<uchar>(i, j-1);
                uchar p9 = img.at<uchar>(i-1, j-1);

                int A  = (p2 == 0 && p3 == 1) + (p3 == 0 && p4 == 1) +
                         (p4 == 0 && p5 == 1) + (p5 == 0 && p6 == 1) +
                         (p6 == 0 && p7 == 1) + (p7 == 0 && p8 == 1) +
                         (p8 == 0 && p9 == 1) + (p9 == 0 && p2 == 1);
                int B  = p2 + p3 + p4 + p5 + p6 + p7 + p8 + p9;
                int m1 = iter == 0 ? (p2 * p4 * p6) : (p2 * p4 * p8);
                int m2 = iter == 0 ? (p4 * p6 * p8) : (p2 * p6 * p8);

                if (A == 1 && (B >= 2 && B <= 6) && m1 == 0 && m2 == 0)
                    marker.at<uchar>(i,j) = 1;
            }
        }
    }
    if(thinningType == THINNING_GUOHALL){
        for (int i = 1; i < img.rows-1; i++)
        {
            for (int j = 1; j < img.cols-1; j++)
            {
                uchar p2 = img.at<uchar>(i-1, j);
                uchar p3 = img.at<uchar>(i-1, j+1);
                uchar p4 = img.at<uchar>(i, j+1);
                uchar p5 = img.at<uchar>(i+1, j+1);
                uchar p6 = img.at<uchar>(i+1, j);
                uchar p7 = img.at<uchar>(i+1, j-1);
                uchar p8 = img.at<uchar>(i, j-1);
                uchar p9 = img.at<uchar>(i-1, j-1);

                int C  = ((!p2) & (p3 | p4)) + ((!p4) & (p5 | p6)) +
                         ((!p6) & (p7 | p8)) + ((!p8) & (p9 | p2));
                int N1 = (p9 | p2) + (p3 | p4) + (p5 | p6) + (p7 | p8);
                int N2 = (p2 | p3) + (p4 | p5) + (p6 | p7) + (p8 | p9);
                int N  = N1 < N2 ? N1 : N2;
                int m  = iter == 0 ? ((p6 | p7 | (!p9)) & p8) : ((p2 | p3 | (!p5)) & p4);

                if ((C == 1) && ((N >= 2) && ((N <= 3)) & (m == 0)))
                    marker.at<uchar>(i,j) = 1;
            }
        }
    }

    img &= ~marker;
}

// Apply the thinning procedure to a given image
void thinning(InputArray input, OutputArray output, int thinningType){
    Mat processed = input.getMat().clone();
    // Enforce the range of the input image to be in between 0 - 255
    processed /= 255;

    Mat prev = Mat::zeros(processed.size(), CV_8UC1);
    Mat diff;

    do {
        thinningIteration(processed, 0, thinningType);
        thinningIteration(processed, 1, thinningType);
        absdiff(processed, prev, diff);
        processed.copyTo(prev);
    }
    while (countNonZero(diff) > 0);

    processed *= 255;

    output.assign(processed);
}
