0

I have the original image:

enter image description here

I rotate the image with the following Java code:

BufferedImage bi = ImageHelper.rotateImage(bi, -imageSkewAngle);

ImageIO.write(bi, "PNG", new File("out.png"));

as the result I have the following image:

enter image description here

How to remove black bound around the image and make it a proper white rectangle and to not spent much space.. use only the required size for the transformation... equal to original or larger if needed?

alexanoid
  • 24,051
  • 54
  • 210
  • 410
  • Making the backround white is trivial, and will likely be answered soon, regardless of the point that prevents an appropriate answer: It is not necessarily possible to make the image "the same size" as the original. It will be wider than the original (although this may only be due to a "white border", that *could* probably be omitted - but figuring out how wide this border is may be a bit fiddly...) – Marco13 Feb 02 '18 at 17:13
  • Thanks for your answer. Yes, you are right - the size can be changed. So - how to make the same size or the size a little bit larger than the original one in order to satisfy the rotation requirements? – alexanoid Feb 02 '18 at 17:21
  • It is not entirely clear what the `ImageHelper` does exactly, but it seems to create a new image that has a size that is sufficiently large to contain the original image, after the original image has been rotated about its center. From the example image, it *might* be OK to just draw the rotated image at the center of a new image that has the same size as the original one. If you think that this could solve your problem, drop me a note, then I'll convert these comments into an answer. – Marco13 Feb 02 '18 at 18:03
  • Yes, you are right about ImageHelper. This is a helper class from Tess4j/Tesseract net.sourceforge.tess4j.util.ImageHelper. Regarding the image size - it is true for this particular image, but I'm going to rotate hundreds of different images and their side and proportions can differ.. so it will be really nice to have some common approach in order to remove black bound and to not spent much space.. use only the required size for the transformation... equal to original or larger if needed. Thanks! – alexanoid Feb 02 '18 at 18:28
  • For better help sooner, post a [MCVE] or [Short, Self Contained, Correct Example](http://www.sscce.org/). – Andrew Thompson Feb 02 '18 at 18:36
  • Instead of `ImageHelper.rotateImage` from Tess4j/Tesseract you can use the code given in [this answer](https://stackoverflow.com/questions/44086310/how-to-rotate-a-buffered-image-without-cropping-it-is-there-any-way-to-rotate-a/44087430#44087430). – Thomas Fritsch Feb 02 '18 at 19:53

1 Answers1

4

The following program contains a method rotateImage that should be equivalent to the rotateImage method that was used in the question: It computes the bounds of the rotated image, creates a new image with the required size, and paints the original image into the center of the new one.

The method additionally receives a Color backgroundColor that determines the background color. In the example, this is set to Color.RED, to illustrate the effect.

The example also contains a method rotateImageInPlace. This method will always create an image that has the same size as the input image, and will also paint the (rotated) original image into the center of this image.

The program creates two panels, the left one showing the result of rotateImage and the right one the result of rotateImageInPlace, together with a slider that allows changing the rotation angle. So the output of this program is shown here:

rotateImageInPlace

(Again, Color.RED is just used for illustration. Change it to Color.WHITE for your application)

As discussed in the comments, the goal of not changing the image size may not always be achievable, depending on the contents of the image and the angle of rotation. So for certain angles, the rotated image may not fit into the resulting image. But for the use case of the question, this should be OK: The use case is that the original image already contains a rotated rectangular "region of interest". So the parts that do not appear in the output should usually be the parts of the input image that do not contain relevant information anyhow.

(Otherwise, it would be necessary to either provide more information about the structure of the input image, regarding the border size or the angle of rotation, or one would have to manually figure out the required size by examining the image, pixel by pixel, to see which pixels are black and which ones are white)

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GridLayout;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.geom.AffineTransform;
import java.awt.image.BufferedImage;
import java.net.URL;

import javax.imageio.ImageIO;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JSlider;

public class RotateImageWithoutBorder
{
    public static void main(String[] args) throws Exception
    {
        BufferedImage image = 
            ImageIO.read(new URL("https://i.stack.imgur.com/tMtFh.png"));


        JFrame f = new JFrame();
        f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        ImagePanel imagePanel0 = new ImagePanel();
        imagePanel0.setBackground(Color.BLUE);

        ImagePanel imagePanel1 = new ImagePanel();
        imagePanel1.setBackground(Color.BLUE);

        JSlider slider = new JSlider(0, 100, 1);
        slider.addChangeListener(e -> 
        {
            double alpha = slider.getValue() / 100.0;
            double angleRad = alpha * Math.PI * 2;

            BufferedImage rotatedImage = rotateImage(
                image, angleRad, Color.RED);
            imagePanel0.setImage(rotatedImage);

            BufferedImage rotatedImageInPlace = rotateImageInPlace(
                image, angleRad, Color.RED);
            imagePanel1.setImage(rotatedImageInPlace);

            f.repaint();
        });
        slider.setValue(0);
        f.getContentPane().add(slider, BorderLayout.SOUTH);

        JPanel imagePanels = new JPanel(new GridLayout(1,2));
        imagePanels.add(imagePanel0);
        imagePanels.add(imagePanel1);
        f.getContentPane().add(imagePanels, BorderLayout.CENTER);

        f.setSize(800,500);
        f.setLocationRelativeTo(null);
        f.setVisible(true);

    }

    private static BufferedImage rotateImage(
        BufferedImage image, double angleRad, Color backgroundColor)
    {
        int w = image.getWidth();
        int h = image.getHeight();
        AffineTransform at = AffineTransform.getRotateInstance(
            angleRad, w * 0.5, h * 0.5);
        Rectangle rotatedBounds = at.createTransformedShape(
            new Rectangle(0, 0, w, h)).getBounds();
        BufferedImage result = new BufferedImage(
            rotatedBounds.width, rotatedBounds.height, 
            BufferedImage.TYPE_INT_ARGB);
        Graphics2D g = result.createGraphics();
        g.setColor(backgroundColor);
        g.fillRect(0, 0, rotatedBounds.width, rotatedBounds.height);
        at.preConcatenate(AffineTransform.getTranslateInstance(
            -rotatedBounds.x, -rotatedBounds.y));
        g.transform(at);
        g.setRenderingHint(
            RenderingHints.KEY_INTERPOLATION, 
            RenderingHints.VALUE_INTERPOLATION_BILINEAR);
        g.drawImage(image, 0, 0, null);
        g.dispose();
        return result;        
    }

    private static BufferedImage rotateImageInPlace(
        BufferedImage image, double angleRad, Color backgroundColor)
    {
        int w = image.getWidth();
        int h = image.getHeight();
        AffineTransform at = AffineTransform.getRotateInstance(
            angleRad, w * 0.5, h * 0.5);
        Rectangle rotatedBounds = at.createTransformedShape(
            new Rectangle(0, 0, w, h)).getBounds();
        BufferedImage result = new BufferedImage(
            w, h, 
            BufferedImage.TYPE_INT_ARGB);
        Graphics2D g = result.createGraphics();
        g.setColor(backgroundColor);
        g.fillRect(0, 0, w, h);
        at.preConcatenate(AffineTransform.getTranslateInstance(
            -rotatedBounds.x - (rotatedBounds.width - w) * 0.5, 
            -rotatedBounds.y - (rotatedBounds.height - h) * 0.5));
        g.transform(at);
        g.setRenderingHint(
            RenderingHints.KEY_INTERPOLATION, 
            RenderingHints.VALUE_INTERPOLATION_BILINEAR);
        g.drawImage(image, 0, 0, null);
        g.dispose();
        return result;        
    }


    static class ImagePanel extends JPanel
    {
        private BufferedImage image;

        public void setImage(BufferedImage image)
        {
            this.image = image;
            repaint();
        }

        @Override
        protected void paintComponent(Graphics g)
        {
            super.paintComponent(g);
            if (image != null)
            {
                g.drawImage(image, 0, 0, null);
            }
        }
    }
}
Marco13
  • 53,703
  • 9
  • 80
  • 159