文字图片重新排版

因为腾讯的文字识别还是会有错别字,需要手动校验,时间和精力都不允许,所以就萌发了直接展示原文字图片给用户, 为了适配不同宽度的终端, 增加了文字图片重新排版的功能, 大部分情况表现良好, 还是会有一些bug, 后续有空再调整吧.

引入jar包

<dependency>
    <groupId>net.coobird</groupId>
    <artifactId>thumbnailator</artifactId>
    <version>0.4.12</version>
</dependency>

代码

使用说明:

  • 1.通过binaryPic()方法, 将手机拍摄的文字图片进行二值化
  • 2.将上面的二值化图片作为modifyPicWidth()方法的参数, 并设置新生成图片的宽度, 执行就可以了
  • 3.重新排版的原理就是先切割成单个文字图片, 再重新拼接, 这个过程中需要记录换行的位置.
  • 4.存在的问题, 对于标点符号, 会出现忽上忽下的问题, 暂时没有什么好办法解决
package com.example.springboot2.picture;

import net.coobird.thumbnailator.Thumbnails;
import org.junit.jupiter.api.Test;

import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.*;

public class Test0315 {

    // 横向切分块数
    private static final int widthSplitNum = 12;

    // 纵向切分块数
    private static final int heightSplitNum = 14;

    // 面积小于50像素的字符,舍弃掉
    private static final int ignoreArea = 30;

    // 首行缩进宽度, 超过该值就换行
    private static final int textIndent = 80;

    // 行尾空白宽度, 超过该值就换行
    private static final int rowBlankWidth = 75;


    /**
     * 二值化图片
     * @throws IOException
     */
    @Test
    public void binaryPic() throws IOException {
        long start = System.currentTimeMillis();

        // 黑白图片
        String fromPic = "d:\\IMG_20221001_064715.jpg";
        File file = new File(fromPic);

        BufferedImage bufferedImage = ImageIO.read(file);

        // 压缩图片 275ms
        BufferedImage compactImage = Thumbnails.of(bufferedImage).size(2000, 2000).asBufferedImage();

        // 切割图片,分别处理,防止出现大片黑斑 1ms
        Map<Integer, BufferedImage> map = splitImage2(compactImage);

        long startTime = System.currentTimeMillis();
        // 二值化
        for (Integer key : map.keySet()) {
            BufferedImage binaryImage = binaryImage(map.get(key));
            map.put(key, binaryImage);
        }

        System.out.println("1:" + (System.currentTimeMillis() - startTime));

        // 合并图片
        BufferedImage destImage = mergeImage(map);

        System.out.println("2:" + (System.currentTimeMillis() - startTime));


        //System.out.println("耗时-1:" + (System.currentTimeMillis()- start) + "ms");

        File newFile2 = new File("d:\\dd3.png");
        ImageIO.write(destImage, "png", newFile2);
        //System.out.println("耗时0:" + (System.currentTimeMillis()- start) + "ms");
    }

    /**
     * 调整文字图片宽度
     */
    @Test
    public void modifyPicWidth() throws IOException {
        long start = System.currentTimeMillis();

        // 页面宽度
        int maxWidth = 1000;
        // 字符与字符之间的间隔
        int colSpace = 8;
        // 行与行之间的间隔
        int rowSpace = 15;

        // 黑白图片
        String fromPic = "d:\\dd3.png";
        //String fromPic = "E:\\documents\\DCIM\\unfinished\\邗江县资料本\\IMG_20230215_213145.png";
        File file = new File(fromPic);

        BufferedImage bufferedImage = ImageIO.read(file);

        // 去除图片左右两边的污点
        BufferedImage cutLRImage = cutLR(bufferedImage);

        // 将字符行染黑
        BufferedImage expandWhite = expandWhite2(cutLRImage);

        // 根据上面确定的字符行位置,提取出字符行
        List<BufferedImage> list = getCharRow4(expandWhite, cutLRImage);

        List<BufferedImage> list2 = new ArrayList<>();

        int i = 1;
        // 切割字符
        for (BufferedImage item : list) {
            List<BufferedImage> charImages = splitChar3(item);
            list2.addAll(charImages);
        }

        BufferedImage bufferedImage2 = mergeImage5(list2, maxWidth, colSpace, rowSpace);
        File newFile = new File("d:\\ccc.png");
        ImageIO.write(bufferedImage2, "png", newFile);

        System.out.println("耗时2:" + (System.currentTimeMillis()- start) + "ms");
    }

    /**
     * 同一水平线上的两个黑色像素点,如果他们之家的间距小于设定的阈值,那么将他们之间的所有白色像素点变成黑色像素点
     * @param bufferedImage
     * @return
     */
    private BufferedImage expandWhite2(BufferedImage bufferedImage) {
        BufferedImage grayImage = new BufferedImage(bufferedImage.getWidth(), bufferedImage.getHeight(), bufferedImage.getType());

        //long start1 = System.currentTimeMillis();
        grayImage.setData(bufferedImage.getData());
        //System.out.println("expandWhite2耗时1:" + (System.currentTimeMillis()- start1) + "ms");



        int width = bufferedImage.getWidth();
        int height = bufferedImage.getHeight();

        map3 = new HashMap<>();
        for (int col=0; col<height; col++) {
            for (int row = 0; row < width; row++) {
                int color = bufferedImage.getRGB(row, col);
                if ((color & 0xff) == 0) {
                    map3.put(col, 1);
                    break;
                }
            }
        }

        // 抹黑字符,确定字符行的位置,便于后续的调整行间距
        // 从左往右扫描,遇到黑色像素,就将他左边的像素抹黑
        int threshold = 120;

        // 第一个黑色像素点下标
        int start = -1;
        int end = -1;
        // 两个黑色像素点之间的白色像素点个数
        int whiteCount = 0;
        for (int j=0; j<height; j++) {
            if (!map3.containsKey(j)) {
                continue;
            }

            for (int i=0; i<width; i++) {

                int color = grayImage.getRGB(i, j);
                int red = color & 0xFF;

                // 黑色像素点
                if (red == 0) {
                    if (start == -1) {
                        start = i;
                    } else {
                        if (whiteCount == 0) {
                            start = i;
                        } else {
                            if (whiteCount < threshold) {
                                for (int k=start+1; k<i; k++) {
                                    grayImage.setRGB(k, j, 0);
                                }
                            }
                            start = i;
                            whiteCount = 0;
                        }
                    }
                } else if (red == 255 && start != -1) {
                    // 白色像素点
                    whiteCount++;
                }
            }

            start = -1;
            whiteCount = 0;
        }

        //System.out.println("expandWhite2耗时2" + (System.currentTimeMillis()- start1) + "ms");
        return grayImage;
    }

    /**
     * 处理整个图片
     * 截图,去除左右边框
     * @param bufferedImage
     * @return
     */
    private BufferedImage cutLR(BufferedImage bufferedImage) {
        //long start = System.currentTimeMillis();

        // 横坐标上的映射图片,如果首行连续空白像素的个数超过阈值,就认为连续空白像素前面的都是污点,需要清除掉
        int threshold = 10;

        int ignoreHeight = 5;

        int width = bufferedImage.getWidth();
        int height = bufferedImage.getHeight();

        // 存储黑色行号,key为x轴坐标,value为对应x坐标上的黑色像素个数
        Map<Integer, Integer> map = new HashMap<>();

        // 统计
        for (int i = 0; i < width; i++) {
            int blackCount = 0;
            for (int j = 0; j < height; j++) {
                // getRGB()方法,根据手册,其返回的int型数据(32位)为ARGB格式,其中ARGB各占8bit
                int color = bufferedImage.getRGB(i, j);
                int b = color & 0xff;
                if (b == 0) {
                    blackCount++;
                    if (blackCount > ignoreHeight) {
                        break;
                    }
                }
            }

            map.put(i, blackCount);
        }
        //System.out.println("耗时-4:" + (System.currentTimeMillis()- start) + "ms");

        int whiteCount = 0;
        int startIndex = -1;
        int endIndex = width-1;

        // 找出起始像素点
        for (int i = 0; i < width/2; i++) {
            int value = map.get(i);

            // x轴上像素个数小于3的都认为是空白
            if (value < ignoreHeight) {
                whiteCount++;
                if (whiteCount >= threshold) {
                    startIndex = i;
                }
            } else {
                whiteCount = 0;
            }
        }

        // 找出结束像素点
        whiteCount = 0;
        for (int i = width-1; i > width/2; i--) {
            int value = map.get(i);

            if (value < ignoreHeight) {
                whiteCount++;
                if (whiteCount >= threshold) {
                    endIndex = i;
                }
            } else {
                whiteCount = 0;
            }
        }


        // 截掉图片左右两个空白部分
        BufferedImage grayImage = bufferedImage.getSubimage(startIndex, 0, endIndex-startIndex, height);

        return grayImage;
    }

    /**
     * 去除单个字符上下边框
     * @param bufferedImage
     * @return
     */
    private BufferedImage cutUD(BufferedImage bufferedImage) {
        int startIndex = 0;

        // 找到起始像素点
        out:
        for (int j = 0; j < bufferedImage.getHeight(); j++) {
            for (int i = 0; i < bufferedImage.getWidth(); i++) {
                // getRGB()方法,根据手册,其返回的int型数据(32位)为ARGB格式,其中ARGB各占8bit
                int color = bufferedImage.getRGB(i, j);
                int b = color & 0xff;
                if (b == 0) {
                    startIndex = j;
                    break out;
                }
            }
        }

        // 找到结束像素点
        int endIndex = bufferedImage.getWidth()-1;
        out:
        for (int j = bufferedImage.getHeight()-1; j > -1; j--) {
            for (int i = 0; i < bufferedImage.getWidth(); i++) {
                int color = bufferedImage.getRGB(i, j);
                int b = color & 0xff;
                if (b == 0) {
                    endIndex = j;
                    break out;
                }
            }
        }

        // 截掉图片上下两个空白部分
        // 不清除上下边界
        if (bufferedImage.getWidth() < 30) {
            return bufferedImage;
        } else {
            return bufferedImage.getSubimage(0, startIndex, bufferedImage.getWidth(), endIndex-startIndex+1);
        }
    }

    /**
     * 去除单个字符左右边框
     * @param bufferedImage
     * @return
     */
    private BufferedImage cutLR2(BufferedImage bufferedImage) {
        int startIndex = 0;

        // 找到起始像素点
        out:
        for (int i = 0; i < bufferedImage.getWidth(); i++) {
            for (int j = 0; j < bufferedImage.getHeight(); j++) {
                // getRGB()方法,根据手册,其返回的int型数据(32位)为ARGB格式,其中ARGB各占8bit
                int color = bufferedImage.getRGB(i, j);
                int b = color & 0xff;
                if (b == 0) {
                    startIndex = i;
                    break out;
                }
            }
        }

        // 找到结束像素点
        int endIndex = bufferedImage.getWidth()-1;
        out:
        for (int i = bufferedImage.getWidth()-1; i > -1; i--) {
            for (int j = 0; j < bufferedImage.getHeight(); j++) {
                int color = bufferedImage.getRGB(i, j);
                int b = color & 0xff;
                if (b == 0) {
                    endIndex = i;
                    break out;
                }
            }
        }

        return bufferedImage.getSubimage(startIndex, 0, endIndex-startIndex+1, bufferedImage.getHeight());
    }

    /**
     * 去除单个字符左边空白部分
     * @param bufferedImage
     * @return
     */
    private BufferedImage cutL(BufferedImage bufferedImage) {
        int startIndex = 0;

        // 找到起始像素点
        out:
        for (int i = 0; i < bufferedImage.getWidth(); i++) {
            for (int j = 0; j < bufferedImage.getHeight(); j++) {
                // getRGB()方法,根据手册,其返回的int型数据(32位)为ARGB格式,其中ARGB各占8bit
                int color = bufferedImage.getRGB(i, j);
                int b = color & 0xff;
                if (b == 0) {
                    startIndex = i;
                    break out;
                }
            }
        }

        return bufferedImage.getSubimage(startIndex, 0, bufferedImage.getWidth()-startIndex, bufferedImage.getHeight());
    }

    /**
     * 去除单个字符右边空白部分
     * @param bufferedImage
     * @return
     */
    private BufferedImage cutR(BufferedImage bufferedImage) {
        // 找到结束像素点
        int endIndex = bufferedImage.getWidth()-1;
        out:
        for (int i = bufferedImage.getWidth()-1; i > -1; i--) {
            for (int j = 0; j < bufferedImage.getHeight(); j++) {
                int color = bufferedImage.getRGB(i, j);
                int b = color & 0xff;
                if (b == 0) {
                    endIndex = i;
                    break out;
                }
            }
        }

        return bufferedImage.getSubimage(0, 0, endIndex+1, bufferedImage.getHeight());
    }

    /**
     * 对一行字符进行分割
     * 紧贴分割,且不会造成将一个正常字符分割成两个部分的情况
     * 如果过度分割,那么就进行合并
     * 合并的原理就是只要两个相邻的字符合并后不超过最大字符宽度,就认为是一个字符,否则就是两个字符
     * @param bufferedImage
     * @return
     */
    private List<BufferedImage> splitChar3(BufferedImage bufferedImage) {
        List<BufferedImage> charImages = new ArrayList<>();

        int width = bufferedImage.getWidth();
        int height = bufferedImage.getHeight();
        // 横坐标上的映射图片,如果x坐标上的像素个数小于这个阈值,就认为是噪点,舍弃掉,值越大,达到分割的间距越小,分的越细
        //int threshold = 2;
        // 字符间距,值越小,达到分割的间距越小,分的越细
        //int interval = 1;

        // 横坐标上的映射图片,如果x坐标上的像素个数小于这个阈值,就认为是噪点,舍弃掉,值越大,达到分割的间距越小,分的越细
        int threshold = 1;
        // 字符间距,值越小,达到分割的间距越小,分的越细
        int interval = 2;

        // 存储黑色像素在x轴上的映射,key为x轴坐标,value为对应x坐标上的黑色像素个数
        Map<Integer, Integer> map = new HashMap<>();

        // 存储字符起始和结束下标,key为起始下标,value为结束下标后一位,二者差就是字符实际宽度
        Map<Integer, Integer> charsMap = new LinkedHashMap<>();
        // 统计
        for (int i = 0; i < width; i++) {
            int blackCount = 0;
            for (int j = 0; j < height; j++) {
                // getRGB()方法,根据手册,其返回的int型数据(32位)为ARGB格式,其中ARGB各占8bit
                int color = bufferedImage.getRGB(i, j);
                int b = color & 0xff;
                if (b == 0) {
                    blackCount++;
                    if (blackCount > threshold) {
                        break;
                    }
                }
            }

            map.put(i, blackCount);
        }

        // 从左到右遍历时,记录白色像素点的宽度,超过这个值,就表示当前处于字符之间的空白处
        int whiteCount = 0;
        int startIndex = -1;

        // 单个字符的最小宽度,用于判断当前字符是否需要合并
        // 存在“出”字被分成两个部分,左边宽度大于bufferedImage.getHeight()*2/3,右边宽度为1像素,导致不能合并,需要增大minWidth
        //int minWidth = bufferedImage.getHeight()*2/3;
        // 对于字符行是弯曲的情况,直接使用图片高度不适用
        //int minWidth = bufferedImage.getHeight();

        // 对于“小”字这种宽度大于高度的,如果他所在行的有效高度正好等于他的高度,就会造成”小“被分割成两个部分,所以需要额外增加1/20和一个像素
        int maxHeight = getMaxHeight(bufferedImage);
        int minWidth = maxHeight + maxHeight/20 + 1;

        // 单个字符的最大宽度
        // 存在“呀,”合并的情况,因为它们的宽度小于bufferedImage.getHeight()*5/4,需要缩小他的值
        //int maxWidth = bufferedImage.getHeight()*5/4;
        // 对于字符行是弯曲的情况,直接使用图片高度不适用
        //int maxWidth = bufferedImage.getHeight();

        int maxWidth = minWidth;
        int lastKey = -1;
        int lastValue = -1;
        // 找出起始像素点
        for (int i = 0; i < width; i++) {
            int value = map.get(i);

            // 字符起始下标
            if (startIndex == -1) {
                // 对于高度小于设定阈值的坐标,直接舍弃
                if (value >= threshold) {
                    startIndex = i;
                }
            } else {
                // x轴上像素个数小于3的都认为是空白
                if (value < threshold) {
                    whiteCount++;
                } else {
                    // 对于一个字符中间存在空白的情况,需要排除掉
                    whiteCount = 0;
                }

                if (whiteCount > interval) {
                    int charWidth = i-interval-startIndex;
                    // 防止一个字符被分割成两个部分
                    if (charWidth < minWidth) {
                        // 如果是类似“儿”这样的左右分开的字符
                        if (lastKey == -1) {
                            lastKey = startIndex;
                            lastValue = i-interval;
                        } else {
                            if (i-interval-lastKey > maxWidth) {
                                charsMap.put(lastKey, lastValue);
                                lastKey = startIndex;
                                lastValue = i-interval;
                            } else {
                                /*charsMap.put(lastKey, i-interval);
                                lastKey = -1;
                                lastValue = -1;*/

                                // 存在一个字符被分割成多个情况,这时需要多次合并
                                lastValue = i-interval;
                            }
                        }
                    } else {
                        if (lastKey != -1) {
                            charsMap.put(lastKey, lastValue);
                            lastKey = -1;
                            lastValue = -1;
                        }
                        charsMap.put(startIndex, i-interval);
                    }

                    startIndex = -1;
                    whiteCount = 0;
                }
            }
        }

        // 处理行尾的字符
        if (lastKey != -1 && startIndex != -1) {
            int i = width;

            // 后一个字符的宽度
            int charWidth = i-interval-startIndex;
            // 防止一个字符被分割成两个部分
            if (charWidth < minWidth) {
                // 如果是类似“儿”这样的左右分开的字符
                if (i-interval-lastKey > maxWidth) {
                    charsMap.put(lastKey, lastValue);
                    charsMap.put(startIndex, i-interval);
                } else {
                    // 存在一个字符被分割成多个情况,这时需要多次合并
                    charsMap.put(lastKey, i-interval);
                }
            } else {
                charsMap.put(lastKey, lastValue);
                charsMap.put(startIndex, i-interval);
            }
            lastKey = -1;
            lastValue = -1;
            startIndex = -1;
        }


        if (lastKey != -1 && startIndex == -1) {
            // 过滤噪点,暂时不考虑与最后一个字符合并
            if (lastValue - lastKey > 1) {
                charsMap.put(lastKey, lastValue);
                lastKey = -1;
                lastValue = -1;
            }
        }

        if (startIndex != -1 && lastKey == -1) {
            charsMap.put(startIndex, width);
            startIndex = -1;
        }

        // 清除无效字符
        Iterator<Map.Entry<Integer, Integer>> itr = charsMap.entrySet().iterator();
        while (itr.hasNext()) {
            Map.Entry<Integer, Integer> entry = itr.next();
            Integer key = entry.getKey();
            int value = entry.getValue();
            if (value <= key) {
                itr.remove();
                continue;
            }

            BufferedImage grayImage = bufferedImage.getSubimage(key,0,value-key,height);
            if (isInvalidChar(grayImage)) {
                itr.remove();
            }
        }

        // 是否是第一个有效字符
        boolean flag = true;
        // 第几个元素
        int num = 1;
        // 根据上面已经标注好的字符下标,提取字符图片
        for (Integer key : charsMap.keySet()) {
            int value = charsMap.get(key);

            BufferedImage grayImage = null;

            try {
                grayImage = bufferedImage.getSubimage(key,0,value-key,height);
            } catch (Exception e) {
                e.printStackTrace();
            }

            // 首行缩进
            if (flag && key > textIndent) {
                grayImage = bufferedImage.getSubimage(key-textIndent,0,value-key+textIndent,height);
                // 将缩进部分设置为纯白色
                for (int row=0; row<textIndent; row++) {
                    for (int col=0; col<height; col++) {
                        grayImage.setRGB(row, col, 0xFFFFFF);
                    }
                }
            }

            // 如果最后一个字符后面有空白, 就认为下一行需要换行
            if ((num++) == charsMap.size() && bufferedImage.getWidth() - value > rowBlankWidth) {
                flag = true;
                grayImage = bufferedImage.getSubimage(key,0,value-key+rowBlankWidth,height);
                // 将行末尾空白部分设置为纯白色
                for (int row=value-key; row<value-key+rowBlankWidth; row++) {
                    for (int col=0; col<height; col++) {
                        grayImage.setRGB(row, col, 0xFFFFFF);
                    }
                }
            }

            // 清除字符上下空白部分
            BufferedImage cutUDImage = cutUD(grayImage);

            // 再次进行分割
            //if (cutUDImage.getWidth() > bufferedImage.getHeight() && cutUDImage.getWidth() > cutUDImage.getHeight()*2) {
            // 案例:“这样”字符宽80像素,高39像素,maxWidth=42,不满足cutUDImage.getWidth() > maxWidth*1.92,没有进行分割
            //if (cutUDImage.getWidth() > maxHeight*1.92) {
            if (!flag && cutUDImage.getWidth() > maxHeight*splitThreshold) {
                List<BufferedImage> list2 = splitChar4(cutUDImage);
                charImages.addAll(list2);
            } else {
                charImages.add(cutUDImage);
            }

            flag = false;
        }

        // “个儿”这两个字符会分成三个部分,需要将后面两个部分合在一起
        for (int i=0; i<charImages.size()-1; i++) {
            BufferedImage bufferedImage1 = charImages.get(i);

            if (bufferedImage1.getWidth() * 2 < bufferedImage1.getHeight()
                    && bufferedImage1.getWidth() * 3 > bufferedImage1.getHeight()) {
                BufferedImage bufferedImage2 = charImages.get(i+1);

                // 两个字符的宽度和高度都差不多,且高度都大致等于宽度的两倍
                if (bufferedImage2.getWidth() * 1.8 < bufferedImage2.getHeight()
                        && bufferedImage2.getWidth() * 3 > bufferedImage2.getHeight()
                        && Math.abs(bufferedImage1.getHeight()-bufferedImage2.getHeight()) < 5) {

                    // 两个字符合并后中间的空白宽度
                    int blankWidth = 4;

                    int newWidth = bufferedImage1.getWidth() + blankWidth + bufferedImage2.getWidth();
                    int newHeight = Math.max(bufferedImage1.getHeight(), bufferedImage2.getHeight());
                    // 进行合并
                    BufferedImage grayImage2 = new BufferedImage(newWidth, newHeight, bufferedImage.getType());

                    // 设置白色背景
                    for (int row=0; row<newWidth; row++) {
                        for (int col=0; col<newHeight; col++) {
                            grayImage2.setRGB(row, col, 0xFFFFFF);
                        }
                    }

                    for (int row=0; row<bufferedImage1.getWidth(); row++) {
                        for (int col=0; col<bufferedImage1.getHeight(); col++) {
                            grayImage2.setRGB(row, col, bufferedImage1.getRGB(row, col));
                        }
                    }

                    for (int row=0; row<bufferedImage2.getWidth(); row++) {
                        for (int col=0; col<bufferedImage2.getHeight(); col++) {
                            grayImage2.setRGB(row+bufferedImage1.getWidth()+blankWidth, col, bufferedImage2.getRGB(row, col));
                        }
                    }

                    charImages.set(i, grayImage2);
                    charImages.remove(i+1);

                    i++;
                }
            }
        }

        return charImages;
    }

    private double splitThreshold = 1.4;

    /**
     * 调小分割的间隙,再进行分割
     * @param bufferedImage
     * @return
     */
    private List<BufferedImage> splitChar4(BufferedImage bufferedImage) {
        List<BufferedImage> charImages = new ArrayList<>();

        int width = bufferedImage.getWidth();
        int height = bufferedImage.getHeight();
        // 横坐标上的映射图片,如果x坐标上的像素个数小于这个阈值,就认为是噪点,舍弃掉,值越大,达到分割的间距越小,分的越细
        //int threshold = 2;
        // 字符间距,值越小,达到分割的间距越小,分的越细
        //int interval = 1;

        // 横坐标上的映射图片,如果x坐标上的像素个数小于这个阈值,就认为是噪点,舍弃掉,值越大,达到分割的间距越小,分的越细
        int threshold = 2;
        // 字符间距,值越小,达到分割的间距越小,分的越细
        int interval = 1;

        // 存储黑色像素在x轴上的映射,key为x轴坐标,value为对应x坐标上的黑色像素个数
        Map<Integer, Integer> map = new HashMap<>();

        // 存储字符起始和结束下标,key为起始下标,value为结束下标
        Map<Integer, Integer> charsMap = new LinkedHashMap<>();
        // 统计
        for (int i = 0; i < width; i++) {
            int blackCount = 0;
            for (int j = 0; j < height; j++) {
                // getRGB()方法,根据手册,其返回的int型数据(32位)为ARGB格式,其中ARGB各占8bit
                int color = bufferedImage.getRGB(i, j);
                int b = color & 0xff;
                if (b == 0) {
                    blackCount++;
                }
            }

            map.put(i, blackCount);
        }

        // 从左到右遍历时,记录白色像素点的宽度,超过这个值,就表示当前处于字符之间的空白处
        int whiteCount = 0;
        int startIndex = -1;

        // 单个字符的最小宽度,用于判断当前字符是否需要合并
        // 存在“出”字被分成两个部分,左边宽度大于bufferedImage.getHeight()*2/3,右边宽度为1像素,导致不能合并,需要增大minWidth
        //int minWidth = bufferedImage.getHeight()*2/3;
        int minWidth = bufferedImage.getHeight();

        // 单个字符的最大宽度
        // 存在“呀,”合并的情况,因为它们的宽度小于bufferedImage.getHeight()*5/4,需要缩小他的值
        //int maxWidth = bufferedImage.getHeight()*5/4;
        int maxWidth = height;
        int lastKey = -1;
        int lastValue = -1;

        // 下标从左到右顺序:lastKey lastValue startIndex i
        // 字符切割逻辑,从左往右依次扫描,碰到第一个黑色像素,标记为startIndex,继续扫描,碰到第一个白色像素,标记为i,
        // 这时认为i-startIndex为第一个字符的宽度,如果此宽度大于设定的参数,就认为他是一个完整的字符,添加进去,重置startIndex变量
        // 如果小于设定的参数,就认为他不是一个完整的字符,将startIndex赋值给lastKey,i赋值给lastValue,继续扫描,
        // 当扫描到第二个字符的起始黑色像素点时,标记为startIndex,继续扫描,碰到第一个白色像素,标记为i,
        // 如果第二个字符是完整的字符,即宽度超过设定值,那么第一个不完整的字符肯定是完整的字符,因为没有找到他的剩余部分
        // 如果第二个字符也不是完整的字符,那么计算第二个字符的宽度加上之前第一个不完整字符的宽度即i-lastKey,
        // 如果超过了设定的参数,就认为他们两个可以组成完整的字符,否则继续扫描剩余的部分
        // 总结起来就是,我从左往右扫描,当扫描到的黑色像素块超过我设定的阈值,那么我就认为这些黑色像素块是一个字符,以此类推
        // 找出起始像素点
        for (int i = 0; i < width; i++) {
            int value = map.get(i);

            // 字符起始下标
            if (startIndex == -1) {
                // 对于高度小于设定阈值的坐标,直接舍弃
                if (value >= threshold) {
                    startIndex = i;
                }
            } else {
                // x轴上像素个数小于3的都认为是空白
                if (value < threshold) {
                    whiteCount++;
                } else {
                    // 对于一个字符中间存在空白的情况,需要排除掉
                    whiteCount = 0;
                }

                if (whiteCount > interval) {
                    int charWidth = i-interval-startIndex;
                    // 防止一个字符被分割成两个部分
                    if (charWidth < minWidth) {
                        // 如果是类似“儿”这样的左右分开的字符
                        if (lastKey == -1) {
                            lastKey = startIndex;
                            lastValue = i-interval;
                        } else {
                            if (i-interval-lastKey > maxWidth) {
                                charsMap.put(lastKey, lastValue);
                                lastKey = startIndex;
                                lastValue = i-interval;
                            } else {
                                /*charsMap.put(lastKey, i-interval);
                                lastKey = -1;
                                lastValue = -1;*/

                                // 存在一个字符被分割成多个情况,这时需要多次合并
                                lastValue = i-interval;
                            }
                        }
                    } else {
                        if (lastKey != -1) {
                            charsMap.put(lastKey, lastValue);
                            lastKey = -1;
                            lastValue = -1;
                        }
                        charsMap.put(startIndex, i-interval);
                    }

                    startIndex = -1;
                    whiteCount = 0;
                }
            }
        }

        // 处理行尾的字符
        if (lastKey != -1 && startIndex != -1) {
            int i = width;

            // 后一个字符的宽度
            int charWidth = i-interval-startIndex;
            // 防止一个字符被分割成两个部分
            if (charWidth < minWidth) {
                // 如果是类似“儿”这样的左右分开的字符
                if (i-interval-lastKey > maxWidth) {
                    charsMap.put(lastKey, lastValue);
                    charsMap.put(startIndex, i-interval);
                } else {
                    // 存在一个字符被分割成多个情况,这时需要多次合并
                    charsMap.put(lastKey, i-interval);
                }
            } else {
                charsMap.put(lastKey, lastValue);
                charsMap.put(startIndex, i-interval);
            }
            lastKey = -1;
            lastValue = -1;
            startIndex = -1;
        }


        if (lastKey != -1 && startIndex == -1) {
            // 过滤噪点,暂时不考虑与最后一个字符合并
            if (lastValue - lastKey > 1) {
                charsMap.put(lastKey, lastValue);
                lastKey = -1;
                lastValue = -1;
            }
        }

        if (startIndex != -1 && lastKey == -1) {
            charsMap.put(startIndex, width);
            startIndex = -1;
        }

        // 根据上面已经标注好的字符下标,提取字符图片
        for (Integer key : charsMap.keySet()) {
            int value = charsMap.get(key);

            if (value-key == 0) {
                continue;
            }
            BufferedImage grayImage = bufferedImage.getSubimage(key,0,value-key,height);


            /*BufferedImage grayImage = new BufferedImage(value-key, bufferedImage.getHeight(), bufferedImage.getType());
            for (int i = 0; i < value-key; i++) {
                for (int j = 0; j < bufferedImage.getHeight(); j++) {
                    int color = bufferedImage.getRGB(key+i, j);
                    grayImage.setRGB(i, j, color);
                }
            }*/


            // 清除无效字符
            if (isInvalidChar(grayImage)) {
                continue;
            }

            // 清除字符上下空白部分
            BufferedImage cutUDImage = cutUD(grayImage);


            int maxHeight = getMaxHeight(bufferedImage);
            if (cutUDImage.getWidth() > maxHeight*splitThreshold) {
                // 如果两个字符相互重合不能使用一条直线分割,使用连通量进行分割
                List<BufferedImage> list2 = splitChar5(cutUDImage);
                charImages.addAll(list2);
            } else {
                charImages.add(cutUDImage);
            }

        }

        return charImages;
    }

    // 用于处理连在一起的字符串
    private int[] array2;

    /**
     * 根据连通分量来分割字符
     * 只处理含有两个连通分量的图片
     * @param image
     * @return
     */
    private List<BufferedImage> splitChar5(BufferedImage image) {
        int width = image.getWidth();
        int height = image.getHeight();
        int size = width * height;

        // 最小连通分量的面积,小于阈值的过滤掉
        int threshold = 20;

        // 用于存储每个单元格所属的集合
        array2 = new int[size];
        // 初始化各个单元格所属的集合
        for (int j = 0; j < size; j++) {
            array2[j] = -1;
        }

        //long startTime = System.currentTimeMillis();

        // 先合并左右单元格中间的竖着的墙
        for (int col=0; col<height; col++) {
            for (int row=0; row<width-1; row++) {
                int firstColor = image.getRGB(row, col);

                int secondColor = image.getRGB(row+1, col);

                int firstIndex = col*width+row;
                if ((firstColor & 0xff) == 0 && firstColor == secondColor) {
                    // 将两个单元格连通
                    array2[firstIndex] = firstIndex+1;
                }
            }
        }

        // 再拆除上下单元格之间的横着的墙,只拆除上下两行最右边的两个单元格就行了,能保证两行可以合并到同一个联通分量里面就行
        for (int col=height-1; col>0; col--) {
            for (int row=width-1; row>-1; row--) {
                int firstColor = image.getRGB(row, col);
                int secondColor = image.getRGB(row, col-1);

                // 上下两个单元格的右边如果都是黑色,就不合并
                if ((firstColor & 0xff) == 0 && firstColor == secondColor) {
                    // 第一个像素在第二个像素的下面
                    int firstIndex = col*width+row;
                    int secondIndex = firstIndex - width;

                    union2(firstIndex, secondIndex);
                }
            }
        }

        // 统计所有连通分量的大小
        Map<Integer, Integer> map = new HashMap<>();

        // 将联通分量里面的元素全部设置为统一编号,便于统计,用倒序比正序快得多
        for (int i=size-1; i>-1; i--) {
            int row = array2[i];
            if (row == -1) {
                continue;
            }

            int result1 = find2(i);

            Integer key = map.get(result1);
            if (key == null) {
                map.put(result1, 1);
            } else if (key < threshold) {
                map.put(result1, key+1);
            }

            array2[i] = result1;
        }

        // 存储连通量的上下两个极值在数组中的位置,key为连通量编号,也是连通量的上极值,value为连通量的下极值
        Map<Integer, Integer> map2 = new LinkedHashMap<>();
        for (int minY=0; minY<size; minY++) {
            int maxY = array2[minY];
            if (maxY == -1) {
                continue;
            }

            // 过滤掉面积小于1500像素的联通分量
            if (!map2.containsKey(maxY) && map.get(maxY) == threshold) {
                map2.put(maxY, minY);
            }
        }

        List<BufferedImage> list = new ArrayList<>();
        if (map2.size() != 2) {
            list.add(image);
            return list;
        }

        // 根据联通分量来抠图
        Map<Integer, BufferedImage> map5 = new LinkedHashMap<>();
        for (int row = 0; row < width; row++) {
            for (int col=0; col<height; col++) {
                int index = col * width + row;
                int maxY = array2[index];
                if (maxY == -1 || map2.get(maxY) == null) {
                    continue;
                }


                int minY = map2.get(maxY);

                if (map2.containsKey(maxY)) {
                    int newHeight = maxY/width - minY/width+1;

                    int blankHeight = minY/width;

                    if (map5.containsKey(maxY)) {
                        BufferedImage grayImage = map5.get(maxY);
                        int color = image.getRGB(row, col);

                        grayImage.setRGB(row, col-blankHeight, color);
                    } else {
                        // 找出能放下字符行的四边形的四个角
                        BufferedImage grayImage = new BufferedImage(width, newHeight, image.getType());
                        // 设置白色背景
                        for (int i=0; i<width; i++) {
                            for (int j=0; j<newHeight; j++) {
                                grayImage.setRGB(i, j, 0xFFFFFF);
                            }
                        }

                        int color = image.getRGB(row, col);

                        grayImage.setRGB(row, col-blankHeight, color);

                        map5.put(maxY, grayImage);
                    }


                }

            }
        }

        for (Integer key : map5.keySet()) {
            // 清除字符上下空白部分
            BufferedImage cutLR2 = cutLR2(map5.get(key));

            list.add(cutLR2);
        }

        return list;
    }

    private boolean isInvalidChar(BufferedImage bufferedImage) {
        int blackCount = 0;
        // 统计
        for (int i = 0; i < bufferedImage.getWidth(); i++) {
            for (int j = 0; j < bufferedImage.getHeight(); j++) {
                // getRGB()方法,根据手册,其返回的int型数据(32位)为ARGB格式,其中ARGB各占8bit
                int color = bufferedImage.getRGB(i, j);
                int b = color & 0xff;
                if (b == 0) {
                    blackCount++;
                }
            }
        }

        return blackCount < ignoreArea;
    }

    /**
     * 获取一行文字的最大有效高度
     * 用于处理倒S形的文字行
     * 从左到右扫描列,通过记录每一列的第一个和最后一个黑色像素,从而得到该列的有效高度,最后取所有列最大的有效高度,也即是字符的最大高度
     * @param bufferedImage
     * @return
     */
    private static int getMaxHeight(BufferedImage bufferedImage) {
        int maxHeight = 0;
        // 以图片左上角点为坐标原点
        for (int i = 0; i < bufferedImage.getWidth(); i++) {

            int start = bufferedImage.getHeight()-1;
            int end = 0;

            for (int j = 0; j < bufferedImage.getHeight(); j++) {
                int color = bufferedImage.getRGB(i, j);
                int red = color & 0xFF;
                if (red == 0) {
                    start = j;
                    break;
                }
            }

            for (int j = bufferedImage.getHeight()-1; j > -1; j--) {
                int color = bufferedImage.getRGB(i, j);
                int red = color & 0xFF;
                if (red == 0) {
                    end = j;
                    break;
                }
            }

            if ((end - start) > maxHeight) {
                maxHeight = end - start;
            }

        }

        return maxHeight + 1;
    }

    /**
     * 分割图片
     * 在原图上进行分割,不重新创建BufferedImage对象
     * @param bufferedImage
     * @return
     */
    private static Map<Integer, BufferedImage> splitImage2(BufferedImage bufferedImage) {
        Map<Integer, BufferedImage> map = new HashMap<>();

        int w = bufferedImage.getWidth();
        int h = bufferedImage.getHeight();

        int blockWidth = w / widthSplitNum;
        int blockHeight = h / heightSplitNum;

        for (int i = 0; i < widthSplitNum; i++) {
            for (int j = 0; j < heightSplitNum; j++) {
                int mStart = i * blockWidth;
                int nStart = j * blockHeight;

                BufferedImage grayImage2 = bufferedImage.getSubimage(mStart, nStart, blockWidth, blockHeight);

                map.put(i * heightSplitNum + j, grayImage2);
            }
        }

        return map;
    }

    public BufferedImage mergeImage(Map<Integer, BufferedImage> map) {
        if (map == null || map.get(0) == null) {
            return null;
        }

        int blockWidth = map.get(0).getWidth();
        int blockHeight = map.get(0).getHeight();

        BufferedImage destImage = new BufferedImage(blockWidth * widthSplitNum, blockHeight * heightSplitNum, BufferedImage.TYPE_BYTE_BINARY);


        for (int i = 0; i < widthSplitNum; i++) {
            for (int j = 0; j < heightSplitNum; j++) {
                BufferedImage grayImage = map.get(i * heightSplitNum + j);

                for (int m = 0; m < grayImage.getWidth(); m++) {
                    for (int n = 0; n < grayImage.getHeight(); n++) {
                        destImage.setRGB(i * blockWidth + m, j * blockHeight + n, grayImage.getRGB(m, n));
                    }
                }
            }
        }

        return destImage;
    }

    /**
     * 将单文字图片组合成指定宽度的文字图片
     * 每一行文字, 按中间对齐, 均匀分布在一行中
     * @param list 单文字图片列表
     * @param width 组合图片宽度
     * @param colSpace 列间距
     * @param rowSpace 行间距
     * @return
     */
    public BufferedImage mergeImage5(List<BufferedImage> list, int width, int colSpace, int rowSpace) {
        if (list == null || list.isEmpty()) {
            return null;
        }

        // 当前行单文字组合的宽度
        int currentWidth = 0;

        // 存放每一行的单文字图片
        Map<Integer, List<BufferedImage>> picMap = new HashMap<>();

        // 记录每一行文字距离顶部的高度
        Map<Integer, Integer> rowsHeight = new HashMap<>();
        rowsHeight.put(0, rowSpace);

        // 行下标
        int rowIndex = 0;
        // 单行文字最高高度
        int maxHeight = 0;
        // 组合文字图片高度
        int totalHeight = rowSpace;

        // 计算文字与文字之间的间距, 对应text-align: justify; 向两侧对齐,最后一行不适用
        Map<Integer, Integer> charBlankMap = new HashMap<>();
        // 每行文字图片的宽度和
        int picsWidth = 0;

        List<BufferedImage> picList = new ArrayList<>();
        for (BufferedImage image : list) {

            // 首行缩进换行
            BufferedImage temp = cutL(image);
            if (image.getWidth() - textIndent == temp.getWidth()) {
                charBlankMap.put(rowIndex, colSpace);
                picsWidth = 0;

                picMap.put(rowIndex++, picList);
                totalHeight += rowSpace + maxHeight;
                rowsHeight.put(rowIndex, totalHeight);
                currentWidth = 0;
                maxHeight = 0;
                picList = new ArrayList<>();

                currentWidth += image.getWidth() + colSpace;
                maxHeight = image.getHeight();
                picList.add(image);
                picsWidth += image.getWidth();
                continue;
            }

            int charWidth = image.getWidth() + colSpace;

            // 超出行长度, 自动换行
            if (currentWidth + charWidth > width) {
                charBlankMap.put(rowIndex, (width-picsWidth)/(picList.size()-1));
                //charBlankMap.put(rowIndex, colSpace);

                picsWidth = 0;

                picMap.put(rowIndex++, picList);
                totalHeight += rowSpace + maxHeight;
                rowsHeight.put(rowIndex, totalHeight);
                currentWidth = 0;
                maxHeight = 0;
                picList = new ArrayList<>();
            }

            picsWidth += image.getWidth();
            currentWidth += charWidth;
            picList.add(image);

            if (image.getHeight() > maxHeight) {
                maxHeight = image.getHeight();
            }


            // 行末尾空白换行
            BufferedImage temp2 = cutR(image);
            if (image.getWidth() - rowBlankWidth == temp2.getWidth()) {
                charBlankMap.put(rowIndex, colSpace);
                picsWidth = 0;

                picMap.put(rowIndex++, picList);
                totalHeight += rowSpace + maxHeight;
                rowsHeight.put(rowIndex, totalHeight);
                currentWidth = 0;
                maxHeight = 0;
                picList = new ArrayList<>();
            }
        }

        // 最后一行
        if (currentWidth > 0) {
            charBlankMap.put(rowIndex, colSpace);
            picsWidth = 0;

            picMap.put(rowIndex++, picList);
            totalHeight += rowSpace + maxHeight;
            rowsHeight.put(rowIndex, totalHeight);
        }

        // colSpace*10 图片左右两边空白边界
        BufferedImage destImage = new BufferedImage(width + colSpace*10, totalHeight + rowSpace*2, BufferedImage.TYPE_BYTE_BINARY);
        // 设置白色背景
        for (int i=0; i<destImage.getWidth(); i++) {
            for (int j=0; j<destImage.getHeight(); j++) {
                destImage.setRGB(i, j, 0xFFFFFF);
            }
        }

        for (Integer index: picMap.keySet()) {
            // 拼接行时, 记录行宽度下标
            int tempWidthIndex = 0;
            for (BufferedImage grayImage : picMap.get(index)) {

                // 对于"一"字,需要放在中间
                int tempHeight = 0;
                //if (grayImage.getHeight() < 11) {
                tempHeight = (rowsHeight.get(index+1)-rowsHeight.get(index) - rowSpace)/2 - grayImage.getHeight()/2;
                //}

                for (int m = 0; m < grayImage.getWidth(); m++) {
                    for (int n = 0; n < grayImage.getHeight(); n++) {
                        int rgb = grayImage.getRGB(m, n);

                        // colSpace*5 图片左边空白界
                        try {
                            destImage.setRGB(tempWidthIndex + m + colSpace * 5, rowsHeight.get(index) + n + tempHeight, rgb);
                        } catch (Exception e) {
                            e.printStackTrace();
                        }
                    }
                }
                tempWidthIndex += charBlankMap.get(index) + grayImage.getWidth();
            }
        }

        return destImage;
    }

    /**
     * 二值化图片
     *
     * @param bufferedImage 原图片
     * @return 二值化后的图片
     */
    private static BufferedImage binaryImage(BufferedImage bufferedImage) {
        BufferedImage grayImage = new BufferedImage(bufferedImage.getWidth(), bufferedImage.getHeight(), bufferedImage.getType());

        int threshold = getMeanThreshold(bufferedImage);

        for (int i = 0; i < bufferedImage.getWidth(); i++) {
            for (int j = 0; j < bufferedImage.getHeight(); j++) {
                // getRGB()方法,根据手册,其返回的int型数据(32位)为ARGB格式,其中ARGB各占8bit
                int color = bufferedImage.getRGB(i, j);
                int r = (color >> 16) & 0xff;
                int g = (color >> 8) & 0xff;
                int b = color & 0xff;
                int gray = (int) (0.2126 * r + 0.7152 * g + 0.0722 * b);
                if (gray > threshold) {
                    // 白色
                    grayImage.setRGB(i, j, 0xFFFFFF);
                } else {
                    // 黑色
                    grayImage.setRGB(i, j, 0);
                }
            }
        }

        return grayImage;
    }

    /**
     * 获取图片的阀值,采用基于灰度平均值的阈值
     *
     * @param bufferedImage 原图片
     * @return 二值化的阈值
     */
    private static int getMeanThreshold(BufferedImage bufferedImage) {
        double aa = 0.83;
        int w = bufferedImage.getWidth();
        int h = bufferedImage.getHeight();
        int num = 0;
        long sum = 0;
        for (int i = 0; i < w; i++) {
            for (int j = 0; j < h; j++) {
                int color = bufferedImage.getRGB(i, j);
                int r = (color >> 16) & 0xff;
                int g = (color >> 8) & 0xff;
                int b = color & 0xff;
                int gray = (int) (0.2126 * r + 0.7152 * g + 0.0722 * b);
                sum += gray;
                num += 1;
            }
        }

        // 测试表明,阀值取平均值的1.2倍效果最好。
        // 越大越黑
        int threshold = (int) (sum / num);
        if (threshold * aa < 255) {
            threshold = (int) (aa * sum / num);
        }

        return threshold;
    }

    // 用于存储每个单元格所属的集合
    private int[] array;

    // 映射到右边边界上,用于判断哪些行有黑色像素,对于没有黑色像素的行直接跳过
    private Map<Integer, Integer> map3;

    /**
     * 减少union的时间
     * @param image
     * @return
     * @throws IOException
     */
    private List<BufferedImage> getCharRow4(BufferedImage image, BufferedImage bufferedImage) {
        int width = image.getWidth();
        int height = image.getHeight();
        int size = width * height;

        // 最小连通分量的面积,小于阈值的过滤掉
        int threshold = 800;

        // 用于存储每个单元格所属的集合
        array = new int[size];
        // 初始化各个单元格所属的集合
        for (int j = 0; j < size; j++) {
            array[j] = -1;
        }

        //long startTime = System.currentTimeMillis();

        // 先合并左右单元格中间的竖着的墙
        for (int col=0; col<height; col++) {
            if (!map3.containsKey(col)) {
                continue;
            }

            for (int row=0; row<width-1; row++) {
                int firstColor = image.getRGB(row, col);

                int secondColor = image.getRGB(row+1, col);

                int firstIndex = col*width+row;
                if ((firstColor & 0xff) == 0 && firstColor == secondColor) {
                    // 将两个单元格连通
                    array[firstIndex] = firstIndex+1;
                }
            }
        }


        boolean flag = false;
        // 再拆除上下单元格之间的横着的墙,只拆除上下两行最右边的两个单元格就行了,能保证两行可以合并到同一个联通分量里面就行
        for (int col=height-1; col>0; col--) {
            if (!map3.containsKey(col)) {
                continue;
            }
            // 每次换行都要执行一次合并,防止因为两行的最右边是边界,没有白色像素
            flag = true;
            for (int row=width-1; row>-1; row--) {
                int firstColor = image.getRGB(row, col);
                int secondColor = image.getRGB(row, col-1);

                // 如果两个黑色像素前面是白色像素,那么就表示他们是各自行的末尾,那么就进行合并操作
                if (!flag && (firstColor == -1 || secondColor == -1)) {
                    flag = true;
                }

                // 上下两个单元格的右边如果都是黑色,就不合并
                if (flag && ((firstColor & 0xff) == 0 && firstColor == secondColor)) {
                    // 第一个像素在第二个像素的下面
                    int firstIndex = col*width+row;
                    int secondIndex = firstIndex - width;

                    union(firstIndex, secondIndex);
                    flag = false;
                }
            }
        }


        //System.out.println("startTime3:" + (System.currentTimeMillis()- startTime) + "ms");

        // 统计所有连通分量的大小
        Map<Integer, Integer> map = new HashMap<>();

        // 将联通分量里面的元素全部设置为统一编号,便于统计,用倒序比正序快得多
        for (int i=size-1; i>-1; i--) {
            int row = array[i];
            if (row == -1) {
                continue;
            }

            int result1 = find(i);

            Integer key = map.get(result1);
            if (key == null) {
                map.put(result1, 1);
            } else if (key < threshold) {
                map.put(result1, key+1);
            }

            array[i] = result1;
        }

        // 存储连通量的上下两个极值在数组中的位置,key为连通量编号,也是连通量的上极值,value为连通量的下极值
        Map<Integer, Integer> map2 = new LinkedHashMap<>();
        for (int minY=0; minY<size; minY++) {
            int maxY = array[minY];
            if (maxY == -1) {
                continue;
            }

            // 过滤掉面积小于1500像素的联通分量
            if (!map2.containsKey(maxY) && map.get(maxY) == threshold) {
                map2.put(maxY, minY);
            }
        }

        //System.out.println("startTime6:" + (System.currentTimeMillis()- startTime) + "ms");


        // 根据联通分量来抠图
        List<BufferedImage> list = new ArrayList<>();
        Map<Integer, BufferedImage> map5 = new LinkedHashMap<>();

        for (int col=0; col<height; col++) {
            if (!map3.containsKey(col)) {
                continue;
            }

            for (int row = 0; row < width; row++) {
                int index = col * width + row;
                int maxY = array[index];
                if (maxY == -1 || map2.get(maxY) == null) {
                    continue;
                }


                int minY = map2.get(maxY);

                if (map2.containsKey(maxY)) {
                    int newHeight = maxY/width - minY/width+1;

                    int blankHeight = minY/width;

                    if (map5.containsKey(maxY)) {
                        BufferedImage grayImage = map5.get(maxY);
                        int color = bufferedImage.getRGB(row, col);

                        grayImage.setRGB(row, col-blankHeight, color);
                    } else {
                        // 找出能放下字符行的四边形的四个角
                        BufferedImage grayImage = new BufferedImage(width, newHeight, image.getType());
                        // 设置白色背景
                        for (int i=0; i<width; i++) {
                            for (int j=0; j<newHeight; j++) {
                                grayImage.setRGB(i, j, 0xFFFFFF);
                            }
                        }

                        int color = bufferedImage.getRGB(row, col);

                        grayImage.setRGB(row, col-blankHeight, color);

                        map5.put(maxY, grayImage);
                    }


                }

            }
        }

        for (Integer key : map5.keySet()) {
            list.add(map5.get(key));
        }

        return list;
    }

    public String fillBlank(String str, int length) {
        int strLength = str.length();
        if (strLength < length) {
            for (int i=0; i<length-strLength; i++) {
                str += " ";
            }
        }
        return str;
    }

    /**
     * 返回 i 所在集合的最大值
     *
     * @param i 单元格编号
     * @return
     */
    public int find(int i) {
        int result = i;
        while (array[result] != -1) {
            result = array[result];
        }
        return result;
    }

    /**
     * 将 i 和 j 所在集合进行合并
     *
     * @param i 单元格编号
     * @param j 单元格编号
     */
    public void union(int i, int j) {
        int result1 = find(i);
        int result2 = find(j);
        if (result1 == result2){
            return;
        }
        if(result1 > result2) {
            array[result2] = result1;
        }
        else {
            array[result1] = result2;
        }
    }


    public int find2(int i) {
        int result = i;
        while (array2[result] != -1) {
            result = array2[result];
        }
        return result;
    }

    public void union2(int i, int j) {
        int result1 = find2(i);
        int result2 = find2(j);
        if (result1 == result2){
            return;
        }
        if(result1 > result2) {
            array2[result2] = result1;
        }
        else {
            array2[result1] = result2;
        }
    }

}

效果展示

在这里插入图片描述

重新排版在这里插入图片描述
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值