All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.github.cosycode.bdmp.BdmpRecognizer Maven / Gradle / Ivy

package com.github.cosycode.bdmp;

import com.github.cosycode.common.ext.bean.DoubleBean;
import com.github.cosycode.common.lang.WrongBranchException;
import lombok.Getter;
import lombok.NonNull;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.Validate;

import java.awt.*;
import java.awt.image.BufferedImage;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

/**
 * Description :  解析二进制图片工具类
 *
 * @author CPF
 * Date: 2020/5/19 18:15
 */
@Slf4j
public class BdmpRecognizer {

    /**
     * 图片
     */
    @Setter
    private BufferedImage image;
    @Getter
    private PixelPngReader pixelReader;
    /**
     * 边界半径
     */
    private int radixBorder;

    /**
     * @param image 待识别的图片
     * @return 识别后的识别器对象
     */
    public static BdmpRecInfo resolver(BufferedImage image) {
        BdmpRecognizer recognizer = new BdmpRecognizer();
        recognizer.setImage(image);
        final boolean distinguish = recognizer.distinguish();
        if (!distinguish) {
            // 未识别出区域
            return null;
        }
        recognizer.pixelReader.readFileInfo();
        // 封装结果返回
        final PixelPngReader pixelReader = recognizer.getPixelReader();
        final BdmpRecInfo picRecInfo = new BdmpRecInfo();
        picRecInfo.setBdmpHeader(pixelReader.getBdmpHeader());
        picRecInfo.setByteModal(pixelReader.getByteModal());
        picRecInfo.setXArr(pixelReader.getXArr());
        picRecInfo.setYArr(pixelReader.getYArr());
        picRecInfo.setFileContent(pixelReader.getFileContent());
        picRecInfo.setBitCnt(pixelReader.getBitCnt());
        picRecInfo.setContentLength(pixelReader.getContentLength());
        return picRecInfo;
    }

    /**
     * 判断定位区像素是否是白色或黑色
     *
     * @param rgb rgb 值
     */
    public static boolean isBorderVal(int rgb) {
        return isBlack(rgb) || isWhite(rgb);
    }

    /**
     * 判断定位区像素是否是黑色
     *
     * @param rgb rgb 值
     */
    public static boolean isBlack(int rgb) {
        int n = 0;
        n += (rgb >>> 16 & 0xFF);
        n += (rgb >>> 8 & 0xFF);
        n += (rgb & 0xFF);
        return n < 60;
    }

    /**
     * 判断定位区像素是否是黑色
     *
     * @param rgb rgb 值
     */
    public static boolean isGray(int rgb) {
        int n = (rgb >>> 16 & 0xFF);
        if (0x60 > n || n > 0xa0) {
            return false;
        }
        n = (rgb >>> 8 & 0xFF);
        if (0x60 > n || n > 0xa0) {
            return false;
        }
        n = (rgb & 0xFF);
        return 0x60 <= n && n <= 0xa0;
    }

    /**
     * 判断定位区像素是否是白色
     *
     * @param rgb rgb 值
     */
    public static boolean isWhite(int rgb) {
        int n = 0;
        n += (rgb >>> 16 & 0xFF);
        n += (rgb >>> 8 & 0xFF);
        n += (rgb & 0xFF);
        return n > 705;
    }

    /**
     * 判断两个像素是否相近
     *
     * @param rgb1 整型rgb像素值1
     * @param rgb2 整型rgb像素值2
     * @return 两个像素是否相近
     */
    public static boolean isNearColor(int rgb1, int rgb2) {
        int rgb = rgb1 ^ rgb2;
        final int cnt = 0x10;
        if ((rgb >>> 16 & 0xFF) > cnt) {
            return false;
        }
        if ((rgb >>> 8 & 0xFF) > cnt) {
            return false;
        }
        return (rgb & 0xFF) <= cnt;
    }

    /**
     * 识别图片定位区
     */
    @SuppressWarnings({"java:S3776", "java:S135", "java:S1199", "java:S3518"})
    public boolean distinguish() {
        int maxR = Math.min(image.getHeight(), image.getWidth());
        /* 寻找四边, 斜线切入, 直到寻找到黑白色的像素 */
        // 斜边切入边距, 黑白框外层需要有一层灰色边, 因此 r 需要 >= 1
        for (int r = Math.max(1, radixBorder); r < maxR; r++) {
            int rgb = image.getRGB(r, r);
            // 如果(r, r) 为黑白框像素, 同时(r - 1, r - 1)为灰色像素
            if (isBorderVal(rgb) && isGray(image.getRGB(r - 1, r - 1))) {
                radixBorder = r;
                final Point leftTopPoint = findLeftTopPointFromSidePixel(r);
                if (leftTopPoint == null) {
                    continue;
                }
                final BdmpRecCngInfo info = recognizeDrawArea(leftTopPoint);
                if (info == null) {
                    continue;
                }
                final DoubleBean xyArr = findXYArr(info);
                if (xyArr == null) {
                    continue;
                }
                info.setXArr(xyArr.getO1());
                info.setXArr(xyArr.getO2());
                pixelReader = new PixelPngReader(image, xyArr.getO1(), xyArr.getO2());
                return true;
            }
        }
        return false;
    }

    /**
     * 通过斜边切入点找到该绘制区域(可能是绘制区域)的左上方的点
     * (r, r) 是当前切入的点
     *
     * @param r 斜边切入的边长
     * @return 该绘制区域(可能是绘制区域)的左上方的点
     */
    private Point findLeftTopPointFromSidePixel(int r) {
        // 通过斜边切入到当前点的上一个点, 在此应该是刚从边缘切向黑白边, 因此应该是灰色, 与灰色相近的点, 不应该和黑色和白色相近
        int rgbMinusOne = image.getRGB(r - 1, r - 1);
        // 判断(r,r)是否是横轴点还是纵轴点
        if (isNearColor(rgbMinusOne, image.getRGB(r, r - 1)) && isBorderVal(image.getRGB(r - 1, r))) {
            // 如果左边是 黑白框 点,
            int x = r - 2;
            while (isBorderVal(image.getRGB(x, r))) x--;
            return new Point(++x, r);
        } else if (isNearColor(rgbMinusOne, image.getRGB(r - 1, r)) && isBorderVal(image.getRGB(r, r - 1))) {
            int y = r - 2;
            while (isBorderVal(image.getRGB(r, y))) y--;
            return new Point(r, ++y);
        } else if (isNearColor(rgbMinusOne, image.getRGB(r - 1, r)) && isNearColor(rgbMinusOne, image.getRGB(r, r - 1))) {
            return new Point(r, r);
        }
        return null;
    }

    /**
     * 识别绘制区域
     * 

* 从 leftTopPoint 出发, 向右, 向下, 找到黑白框边界 * 判断边界是否围了一周, * 判断边界外一圈是否全是和rgb_1相近的像素 * * @return 识别的绘制区域, 如果识别不出则返回null */ @SuppressWarnings("java:S1659") private BdmpRecCngInfo recognizeDrawArea(@NonNull Point leftTopPoint) { // 图片高度 int height = image.getHeight(); // 图片宽度 int width = image.getWidth(); // 绘制左上角像素应该是黑色 if (!isBlack(image.getRGB(leftTopPoint.x, leftTopPoint.y))) { return null; } int x, y, len; // → for (x = leftTopPoint.x + 1, y = leftTopPoint.y, len = width - 1; x < len && isBorderVal(image.getRGB(x, y)); x++); Point rightTop = new Point(x - 1, y); // →↓ for (x = rightTop.x, y = rightTop.y, len = height - 1; y < len && isBorderVal(image.getRGB(x, y)); y++); Point rightBottom1 = new Point(x, y - 1); // ↓ for (x = leftTopPoint.x, y = leftTopPoint.y + 1, len = height - 1; y < len && isBorderVal(image.getRGB(x, y)); y++); Point leftBottom = new Point(x, y - 1); // ↓→ for (x = leftBottom.x, y = leftBottom.y, len = width - 1; x < len && isBorderVal(image.getRGB(x, y)); x++); Point rightBottom2 = new Point(x - 1, y); // 判断黑白像素从左上角向下, 向右延申查询有效区域, 是否能够在右下角合并一起 if (!rightBottom1.equals(rightBottom2)) { return null; } /* 判断周围边缘全部是灰色 */ final int borderWidth = rightTop.x - leftTopPoint.x + 2; final int borderHeight = leftBottom.y - leftTopPoint.y + 2; // → : 上边缘 int[] rgb = image.getRGB(leftTopPoint.x - 1, leftTopPoint.y - 1, borderWidth, 1, null, 0, borderWidth); if (!Arrays.stream(rgb).allMatch(BdmpRecognizer::isGray)) { return null; } // →↓ : 右边缘 rgb = image.getRGB(rightTop.x + 1, rightTop.y - 1, 1, borderHeight, null, 0, 1); if (!Arrays.stream(rgb).allMatch(BdmpRecognizer::isGray)) { return null; } // ↓ : 左边缘 rgb = image.getRGB(leftTopPoint.x - 1, leftTopPoint.y, 1, borderHeight, null, 0, 1); if (!Arrays.stream(rgb).allMatch(BdmpRecognizer::isGray)) { return null; } // ↓→ : 下边缘 rgb = image.getRGB(leftBottom.x, leftBottom.y + 1, borderWidth, 1, null, 0, borderWidth); if (!Arrays.stream(rgb).allMatch(BdmpRecognizer::isGray)) { return null; } // 确定变量 BdmpRecCngInfo info = new BdmpRecCngInfo(); info.setLeftTopPoint(leftTopPoint); info.setLeftBottomPoint(leftBottom); info.setRightTopPoint(rightTop); info.setRightBottomPoint(rightBottom1); return info; } /** * 寻找 XArr, YArr * XArr: x轴每一个像素点阵中的平均值 * yArr: y轴每一个像素点阵中的平均值 * * @param info 识别的像素信息 * @return DoubleBean */ @SuppressWarnings({"java:S3776", "java:S3518"}) public DoubleBean findXYArr(BdmpRecCngInfo info) { Point leftTopPoint = info.getLeftTopPoint(); // 图片高度 int height = image.getHeight(); // 图片宽度 int width = image.getWidth(); // 找到xy 有效像素列表 boolean isBlack = true; int cnt = 0; int sum = 0; List xList = new ArrayList<>(); for (int x = leftTopPoint.x, y = leftTopPoint.y; x < width; x++) { int rgb = image.getRGB(x, y); if (!isBorderVal(rgb)) { // 到此说明找到了黑白框外面 if (cnt == 0) { throw new WrongBranchException(""); } xList.add(sum / cnt); Validate.isTrue(info.getRightTopPoint().equals(new Point(x - 1, y)), "两次识别的右上点不相同"); break; } if ((isBlack && isBlack(rgb)) || (!isBlack && isWhite(rgb))) { // 在此说明黑色白色像素x宽度不为1, 需要通过取平均值来作为x像素坐标位置 cnt++; sum += x; } else { // 在此说明跳到了不同颜色点阵 xList.add(sum / cnt); cnt = 1; sum = x; isBlack = !isBlack; } } // check if (xList.size() < 3) { return null; } // 移除x首尾坐标点 xList.remove(xList.size() - 1); xList.remove(0); int[] xArr = xList.stream().mapToInt(it -> it).toArray(); xList.clear(); isBlack = true; cnt = 0; sum = 0; for (int x = leftTopPoint.x, y = leftTopPoint.y; y < height; y++) { int rgb = image.getRGB(x, y); if (!isBorderVal(rgb)) { xList.add(sum / cnt); Validate.isTrue(info.getLeftBottomPoint().equals(new Point(x, y - 1)), "两次识别的右上点不相同"); break; } if ((isBlack && isBlack(rgb)) || (!isBlack && isWhite(rgb))) { cnt++; sum += y; } else { xList.add(sum / cnt); cnt = 1; sum = y; isBlack = !isBlack; } } // check if (xList.size() < 3) { return null; } xList.remove(xList.size() - 1); xList.remove(0); int[] yArr = xList.stream().mapToInt(it -> it).toArray(); return DoubleBean.of(xArr, yArr); } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy