package com.xhkjedu.smarking.utils.opencv;

import org.opencv.core.*;
import org.opencv.imgcodecs.Imgcodecs;
import org.opencv.imgproc.Imgproc;

import java.util.Base64;
import java.util.List;

public class CvCommonUtils {
    /**
     * 逆时针旋转90度
     *
     * @param source 原Mat
     * @return Mat
     */
    public static Mat rotate90Counter(Mat source) {
        Mat dst = new Mat();
        Core.rotate(
                source,
                dst,
                Core.ROTATE_90_COUNTERCLOCKWISE
        );
        return dst;
    }

    /**
     * 顺时针旋转90度
     *
     * @param source 原Mat
     * @return Mat
     */
    public static Mat rotate90(Mat source) {
        Mat dst = new Mat();
        Core.rotate(
                source,
                dst,
                Core.ROTATE_90_CLOCKWISE
        );
        return dst;
    }

    /**
     * 旋转180度
     *
     * @param source 原Mat
     * @return Mat
     */
    public static Mat rotate180(Mat source) {
        Mat dst = new Mat();
        Core.rotate(
                source,
                dst,
                Core.ROTATE_180
        );
        return dst;
    }

    /**
     * 旋转任意角度
     *
     * @param source 原Mat
     * @param angle  角度
     * @return Mat
     */
    public static Mat rotate(Mat source, int angle) {
        angle = -angle;
        // 获取图像的尺寸
        Size size = source.size();
        int width = (int) size.width;
        int height = (int) size.height;
        // 创建一个新的Mat对象来存储旋转后的图像
        Mat dst;
        int tempAngle = Math.abs(angle) % 180;
        if (tempAngle > 45) {
            dst = new Mat(
                    width,
                    height,
                    source.type()
            );
        } else {
            dst = new Mat(
                    height,
                    width,
                    source.type()
            );
        }
        // 获取图像的中心点
        Point center = new Point((double) width / 2, (double) height / 2);
        // 计算旋转矩阵
        Mat rotMat = Imgproc.getRotationMatrix2D(
                center,
                angle,
                1.0
        );
        // 调整旋转矩阵的平移部分以保持图像在目标矩阵中心
        rotMat.put(
                0,
                2,
                rotMat.get(
                        0,
                        2
                )[0] + (dst.size().width / 2.0 - center.x)
        );
        rotMat.put(
                1,
                2,
                rotMat.get(1, 2)[0] + (dst.size().height / 2.0 - center.y)
        );
        // 执行仿射变换（旋转）
        Imgproc.warpAffine(
                source,
                dst,
                rotMat,
                dst.size()
        );
        return dst;
    }

    /**
     * 缩放
     *
     * @param source 原Mat
     * @param width  目标宽度
     * @param height 目标高度
     * @return Mat
     */
    public static Mat resize(Mat source, int width, int height) {
        // 检查输入图像是否有效
        if (source.empty()) {
            return new Mat();
        }
        // 获取源图像的宽高
        int sWidth = source.cols();
        int sHeight = source.rows();
        // 若目标宽度和高度同时为0，直接返回源图像
        if (width == 0 && height == 0) {
            return source.clone(); // 深拷贝源图像
        }
        // 计算目标宽高
        double aspectRatio = (double) sWidth / sHeight; // 原始宽高比
        int targetWidth = (width > 0) ? width : (int) (height * aspectRatio);
        int targetHeight = (height > 0) ? height : (int) (width / aspectRatio);
        // 创建结果Mat并进行缩放
        Mat resultMat = new Mat();
        Imgproc.resize(source, resultMat, new Size(targetWidth, targetHeight));
        return resultMat;
    }

    /**
     * 以灰度化读取图片
     *
     * @param imgPath 图片路径
     * @return Mat
     */
    public static Mat gray(String imgPath) {
        return Imgcodecs.imread(
                imgPath,
                Imgcodecs.IMREAD_GRAYSCALE
        );
    }

    // 灰度化
    public static Mat gray(Mat inputImage) {
        Mat grayImage = new Mat();
        Imgproc.cvtColor(
                inputImage,
                grayImage,
                Imgproc.COLOR_BGR2GRAY
        );
        return grayImage;
    }

    /**
     * 二值化 输入源必须是灰度化的图片
     *
     * @param grayImage 灰度化Mat
     * @return Mat
     */
    public static Mat binary(Mat grayImage) {
        Mat binaryImage = new Mat();
        Imgproc.threshold(
                grayImage,
                binaryImage,
                0,
                255,
                Imgproc.THRESH_BINARY | Imgproc.THRESH_OTSU
        );
        return binaryImage;
    }

    /**
     * 腐蚀（黑色区域变大）
     *
     * @param source 原Mat
     * @return Mat
     */
    public static Mat eroding(Mat source) {
        return eroding(
                source,
                1
        );
    }

    /**
     * 腐蚀（黑色区域变大）
     *
     * @param source       原Mat
     * @param erosion_size 腐蚀大小
     * @return Mat
     */
    public static Mat eroding(Mat source, double erosion_size) {
        Mat resultMat = new Mat(
                source.rows(),
                source.cols(),
                source.type()
        );
        Mat element = Imgproc.getStructuringElement(
                Imgproc.MORPH_RECT,
                new Size(
                        erosion_size + 1,
                        erosion_size + 1
                )
        );
        Imgproc.erode(
                source,
                resultMat,
                element
        );
        return resultMat;
    }

    /**
     * 膨胀（白色区域变大）
     *
     * @param source 原Mat
     * @return Mat
     */
    public static Mat dilation(Mat source) {
        return dilation(
                source,
                1
        );
    }

    /**
     * 膨胀（白色区域变大）
     *
     * @param source        原Mat
     * @param dilation_size 膨胀因子2*x+1 里的x
     * @return Mat
     */
    public static Mat dilation(Mat source, double dilation_size) {
        Mat resultMat = new Mat(
                source.rows(),
                source.cols(),
                source.type()
        );
        Mat element = Imgproc.getStructuringElement(
                Imgproc.MORPH_RECT,
                new Size(
                        2 * dilation_size + 1,
                        2 * dilation_size + 1
                )
        );
        Imgproc.dilate(
                source,
                resultMat,
                element
        );
        return resultMat;
    }

    /**
     * 获取区域Mat
     *
     * @param source 原Mat
     * @param rect   区域
     * @return Mat
     */
    public static Mat getRectMat(Mat source, Rect rect) {
        return new Mat(
                source,
                rect
        );
    }

    /**
     * 获取填涂率
     *
     * @param source 原Mat
     * @param rect   区域
     * @return Mat
     */
    public static double getSmearRate(Mat source, Rect rect) {
        // 创建一个新的Mat对象，它表示源图像中的指定区域
        Mat matTemp = new Mat(
                source,
                rect
        );
        // 计算非零像素的数量
        int count = Core.countNonZero(matTemp);
        // 计算总像素数
        int total = matTemp.cols() * matTemp.rows();
        // 计算零像素的比率
        double rate = (double) (total - count) / total;
        // 释放临时Mat对象的资源
        matTemp.release();
        // 返回比率是否大于最大值
        return rate;
    }

    /**
     * 是否涂卡
     *
     * @param source 图片
     * @param rect   区域
     * @param min    最小的填涂率
     * @return 是否填涂
     */
    public static boolean isSmearCard(Mat source, Rect rect, double min) {
        // 计算零像素的比率
        double rate = getSmearRate(
                source,
                rect
        );
        // 返回比率是否大于最大值
        return rate > min;
    }

    /**
     * 颜色反转
     *
     * @param source 原Mat
     * @return Mat
     */
    public static Mat bitwiseNot(Mat source) {
        Mat resultMat = new Mat();
        Core.bitwise_not(
                source,
                resultMat
        );
        return resultMat;
    }

    /**
     * 垂直合并多个Mat
     *
     * @param matList 图片列表
     * @return Mat
     */
    public static Mat jointMatV(List<Mat> matList) {
        if (matList.isEmpty()) {
            return new Mat();
        }
        int rows = 0;
        int cols = 0;
        for (Mat imgTemp : matList) {
            rows += imgTemp.rows();
            cols = Math.max(cols, imgTemp.cols());
        }
        // 创建一个白色背景的Mat对象
        Mat result = new Mat(rows, cols, matList.get(0).type(), new Scalar(255, 255, 255));
        int tempRows = 0;
        for (Mat itemMat : matList) {
            // 定义复制的区域
            Mat roi = result.submat(new Rect(0, tempRows, itemMat.cols(), itemMat.rows()));
            // 将itemMat的内容复制到ROI中
            itemMat.copyTo(roi);
            tempRows += itemMat.rows();
        }
        return result;
    }

    /**
     * 水平合并多个Mat
     *
     * @param matList 图片列表
     * @return Mat
     */
    public static Mat jointMatH(List<Mat> matList) {
        if (matList.isEmpty()) {
            return new Mat();
        }
        int rows = 0;
        int cols = 0;
        for (Mat imgTemp : matList) {
            rows = Math.max(rows, imgTemp.rows());
            cols += imgTemp.cols();
        }
        // 创建一个白色背景的Mat对象
        Mat result = new Mat(rows, cols, matList.get(0).type(), new Scalar(255, 255, 255));
        int tempCols = 0;
        for (Mat itemMat : matList) {
            // 定义复制的区域
            Mat roi = result.submat(new Rect(tempCols, 0, itemMat.cols(), itemMat.rows()));
            // 将itemMat的内容复制到ROI中
            itemMat.copyTo(roi);
            tempCols += itemMat.cols();
        }
        return result;
    }

    /**
     * Mat转Base64字符串
     *
     * @param mat mat
     * @return Base64字符串
     */
    public static String matToBase64(Mat mat) {
        return matToBase64(mat, true);
    }

    /**
     * Mat转Base64字符串
     *
     * @param mat       mat
     * @param addPrefix 是否添加前缀
     * @return Base64字符串
     */
    public static String matToBase64(Mat mat, boolean addPrefix) {
        // 将Mat对象转换为PNG格式的字节数组
        MatOfByte matOfByte = new MatOfByte();
        Imgcodecs.imencode(".jpeg", mat, matOfByte);
        // 将字节数组转换为Base64字符串
        byte[] byteArray = matOfByte.toArray();
        String imgBase64Str = Base64.getEncoder().encodeToString(byteArray);
        if (addPrefix) {
            String prefix = "data:image/jpeg;base64,";
            return prefix + imgBase64Str;
        }
        return imgBase64Str;
    }

    /**
     * 保存图片
     *
     * @param img  图片Mat
     * @param path 保存路径
     */
    public static void saveImg(Mat img, String path) {
        Imgcodecs.imwrite(
                path,
                img
        );
    }
}
