SATURN  RING  STATION

At a distant space view post, we chat about codes.

Java(2D Graphics API) large image splits into chunks of squares and then rejoin back to original.

There are some examples online that split image into set number of chunks (e.g. 3×3, 5×4). Be careful that some of them, in order to get a evenly divided images, may sacrifice the remainder pixels. However, the one I gonna share is splitting large image into a fixed size square chunks of images keeping the remainders as smaller chunks of images, then rejoin them back to the original. Cropping into squares is simple, handling remainders need a little bit extra care.

Have a look at a map to see what remainders I’m talking about.

 

Let say the map is the image, grid lines are the squares we gonna crop. You can see remainders(not perfect squares) in both most right column and bottom row of the image.

The logic is:

 

  • Do regular squares cropping.
  • if (width / lenght of square has a remainder) we have right column sub rectangle chunks.
  • if (width / lenght of square has a remainder) we have bottom row sub rectangle chunks.
  • if (both have remainders) we have an extra little chunk at the very end of right and bottom(rectangle starting at 26  x 43 on map).

 

In this case, we crop them into squares with 500 x 500 pixels each.

 

        ...

        BufferedImage originalImg = getImage();
        int squareLength = 500;

        int originalWidth = originalImg.getWidth();
        int originalHeight = originalImg.getHeight();

        int colsOfSquare = originalWidth / squareLength;
        int widthOutOfSquaresRemainder = originalWidth % squareLength;
        boolean hasWidthRemainder = widthOutOfSquaresRemainder != 0;
        totalCols = hasWidthRemainder ? colsOfSquare + 1 : colsOfSquare;

        int rowsOfSquare = originalHeight / squareLength;
        int heightOutOfSquaresRemainder = originalHeight % squareLength;
        boolean hasHeightRemainder = heightOutOfSquaresRemainder != 0;
        totalRows = hasHeightRemainder ? rowsOfSquare + 1 : rowsOfSquare;

        int squaresCount = colsOfSquare * rowsOfSquare;

        /**
         *  Regular square image chunks crop and output.
         */
        BufferedImage imgs[] = new BufferedImage[squaresCount];
        int count = 0;
        for (int x = 0; x < colsOfSquare; x++) {
            for (int y = 0; y < rowsOfSquare; y++) {
                //Initialize the image array with image chunks
                imgs[count] = new BufferedImage(squareLength, squareLength, originalImg.getType());

                // draws the image chunk
                Graphics2D gr = imgs[count].createGraphics();
                gr.drawImage(originalImg, 0, 0, squareLength, squareLength, squareLength * x, squareLength * y, squareLength * x + squareLength, squareLength * y + squareLength, null);
                gr.dispose();

                // output chunk
                writeToFile(imgs[count], x, y);
                count++;
            }
        }
        /**
         *  Remainder chunks crop and output.
         */
        // Right end column width remainder image chunks crop and output.
        if (hasWidthRemainder) {
            BufferedImage rightRemainderChunks[] = new BufferedImage[rowsOfSquare];
            for (int i = 0; i < rowsOfSquare; i++) {
                rightRemainderChunks[i] = new BufferedImage(widthOutOfSquaresRemainder, squareLength, originalImg.getType());

                Graphics2D gr = rightRemainderChunks[i].createGraphics();
                gr.drawImage(originalImg, 0, 0, widthOutOfSquaresRemainder, squareLength, squareLength * colsOfSquare, squareLength * i, squareLength * colsOfSquare + widthOutOfSquaresRemainder, squareLength * i + squareLength, null);
                gr.dispose();

                writeToFile(rightRemainderChunks[i], totalCols - 1, i);
            }
        }

        //  Bottom row height remainder image chunks crop and output.
        if (hasHeightRemainder) {
            BufferedImage bottomRemainderChunks[] = new BufferedImage[colsOfSquare];
            for (int i = 0; i < colsOfSquare; i++) {
                bottomRemainderChunks[i] = new BufferedImage(squareLength, heightOutOfSquaresRemainder, originalImg.getType());

                Graphics2D gr = bottomRemainderChunks[i].createGraphics();
                gr.drawImage(originalImg, 0, 0, squareLength, heightOutOfSquaresRemainder, squareLength * i, squareLength * rowsOfSquare, squareLength * i + squareLength, squareLength * rowsOfSquare + heightOutOfSquaresRemainder, null);
                gr.dispose();

                writeToFile(bottomRemainderChunks[i], i, totalRows - 1);
            }
        }

        //  Bottom right corner remainder image chunk crops and outputs.
        if (hasWidthRemainder && hasHeightRemainder) {
            BufferedImage cornerRemainderImage = new BufferedImage(widthOutOfSquaresRemainder, heightOutOfSquaresRemainder, originalImg.getType());

            Graphics2D gr = cornerRemainderImage.createGraphics();
            gr.drawImage(originalImg, 0, 0, widthOutOfSquaresRemainder, heightOutOfSquaresRemainder, squareLength * colsOfSquare, squareLength * rowsOfSquare, originalWidth, originalHeight, null);
            gr.dispose();

            writeToFile(cornerRemainderImage, totalCols - 1, totalRows - 1);
        }
    }

    ...

    private void writeToFile(BufferedImage image, int colNum, int rowNum) throws IOException {

        image = doSomethingService.doSomething(image);

        File file = new File("./output/img_" + colNum + "_" + (totalCols - 1) + "_" + rowNum + "_" + (totalRows - 1) + ".bmp");
        ImageIO.write(image, "bmp", file);
    }

 

Take a look at the image chunks output name. For rejoining image chunks back to the original in correct order, I marked down its column number, total columns count, row number and total rows count.

Here is the rejoin code.

 

        int totalWidth = 0;
        int totalHeight = 0;
        int cols;   // 0 base
        int rows;   // 0 base
        int type;

        /**
         *  Load images from output folder.
         */
        File folder = new File("./output");
        File[] listOfFiles = folder.listFiles();
        if (listOfFiles.length == 0) {
            throw new IOException("No files are found in the output folder.");
        }

        /**
         *  Get data from first file.
         */
        String[] firstFileName = listOfFiles[0].getName().split("_");
        cols = Integer.parseInt(firstFileName[2]);
        rows = Integer.parseInt(firstFileName[4].split("\\.")[0]); // remove .bmp
        BufferedImage firstImage = ImageIO.read(listOfFiles[0]);
        type = firstImage.getType();

        /**
         *  Set up 2d array holding image chunks.
         */
        BufferedImage[][] imageChunks = new BufferedImage[cols + 1][rows + 1];
        for (File file : listOfFiles) {
            if (file.isFile()) {
                String[] name = file.getName().split("_");
                int colNum = Integer.parseInt(name[1]);
                int rowNum = Integer.parseInt(name[3]);

                BufferedImage image = ImageIO.read(file);

                if (colNum == 0) {
                    totalHeight += image.getHeight();
                }
                if (rowNum == 0) {
                    totalWidth += image.getWidth();
                }

                image = anotherDoSomethingService.doSomething(image);

                imageChunks[colNum][rowNum] = image;
            }
        }

        /**
         *  Assign image chunks from 2d array to original one image.
         */
        BufferedImage combineImage = new BufferedImage(totalWidth, totalHeight, type);
        int stackWidth = 0;
        int stackHeight = 0;
        for (int i = 0; i <= cols; i++) {
            for (int j = 0; j <= rows; j++) {
                combineImage.createGraphics().drawImage(imageChunks[i][j], stackWidth, stackHeight, null);
                stackHeight += imageChunks[i][j].getHeight();
            }
            stackWidth += imageChunks[i][0].getWidth();
            stackHeight = 0;
        }

        ImageIO.write(combineImage, "bmp", new File("./RejoinImage.bmp"));
        System.out.println("Image rejoin done.");
        return combineImage;
    }

 

I’m using addition instead of multiplication for setting width and height in drawImage method because of the remainders’ width and height irregularity. Also, it’s a little bit faster.

You can tell from the code that square size can be easily changes, and it’s not limiting into cropping squares. We can do rectangle too, just be careful changing the values in Graphics2D API.

Leave a Comment

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.

Java(2D Graphics API) large image splits into chunks of squares and then rejoin back to original.