在 Java 中编辑 BufferedImage 的边框

Editing the Border of a BufferedImage in Java

提问人:KajSech 提问时间:11/7/2023 最后编辑:KajSech 更新时间:11/7/2023 访问量:96

问:

我正在尝试创建一个方法,该方法将向 BufferedImage 添加边框,其中边框粗细和边框颜色都可以通过该方法的参数进行配置。

边框粗细将是一个非负整数(0、1、2 等),对应于图像周长内所有四个边的边框应有多少像素厚。

边框颜色是一个长度为 3 的整数数组,对应于边框应为 RGB 颜色值。

这是原始图像:原始图像

这是图像的外观,边框为 10,RGB 值为 235、64、52:生成的图像

这是我拥有的代码,但我一直遇到越界错误。错误

我将不胜感激任何帮助。我还意识到我可以使用 Java 的图形工具在图像顶部进行编辑,但我正在尝试编辑图像本身而不是创建任何类型的叠加层。

public static void applyBorder(BufferedImage img, int borderThickness, int[] borderColor) {
    int width = img.getWidth();
    int height = img.getHeight();
    int borderRgb = (borderColor[0] << 16) | (borderColor[1] << 8) | borderColor[2];

    for (int x = 0; x < img.getWidth(); x++) {
      for (int y = 0; y < borderThickness; y++) {
        img.setRGB(x, y, borderRgb); // Top border
        img.setRGB(x, img.getHeight() - 1 - y, borderRgb); // Bottom border
      }
    }

    for (int y = borderThickness; y < img.getHeight() - borderThickness; y++) {
      for (int x = 0; x < borderThickness; x++) {
        img.setRGB(x, y, borderRgb); // Left border
        img.setRGB(img.getWidth() - 1 - x, y, borderRgb); // Right border
      }
    }

    for (int x = 0; x < width; x++) {
      for (int y = 0; y < height; y++) {
        img.setRGB(x + borderThickness, y + borderThickness, img.getRGB(x, y));
      }
    }
  }

这是我在主类中调用方法的方式:

int borderThickness = 10;
int[] borderColor = {255, 0, 0};
applyBorder(img, borderThickness, borderColor);
File fileBorder = new File("dog_border.png");
ImageIO.write(img, "png", fileBorder);
Java 方法 bufferedimage

评论

1赞 dan1st 11/7/2023
您当前的代码有什么问题?
0赞 user85421 11/7/2023
提示:1)有setRGB()方法,无需获取像素;2)它还有一个getGraphics(),它返回一个用于图像上绘制,这是编辑图像的首选方式BufferedImageGraphics
0赞 KajSech 11/7/2023
@dan1st 从技术上讲什么都没有。就像素的编辑而言,我只是不知道下一步该怎么做
2赞 Hovercraft Full Of Eels 11/7/2023
您可以更简单地使用 BufferedImage 中的 Graphics 对象来绘制边框
0赞 user85421 11/7/2023
我想知道最后一个嵌套循环的用途是什么?此问题中包含的示例图像显示边框只是在图像上绘制,而不是在图像周围绘制(图像大小未更改)for

答:

1赞 Hovercraft Full Of Eels 11/7/2023 #1

您可以简单地使用来自 的 / 对象在图像周围创建边框。例如:GraphicsGraphics2DBufferedImage

public BufferedImage addBorder(BufferedImage img, int borderThickness, Color borderColor) {
    if (img == null) {
        throw new IllegalArgumentException("img cannot be null");
    }
    int width = img.getWidth();
    int height = img.getHeight();

    // create a new image, the same size as the old
    BufferedImage newImg = new BufferedImage(width, height, img.getType());

    // extract the Graphics2D object
    Graphics2D g = newImg.createGraphics();

    // draw the original image with it
    g.drawImage(img, 0, 0, null);

    // then draw the border 
    g.setColor(borderColor);
    g.setStroke(new BasicStroke(borderThickness));
    g.drawRect(0, 0, width, height);

    // conserve resources
    g.dispose();

    return newImg;
}

或者使用 int 数组:

public BufferedImage addBorder(BufferedImage img, int borderThickness, int[] borderColor) {
    if (img == null) {
        throw new IllegalArgumentException("img cannot be null");
    }
    int width = img.getWidth();
    int height = img.getHeight();

    // create a new image, the same size as the old
    BufferedImage newImg = new BufferedImage(width, height, img.getType());

    // extract the Graphics2D object
    Graphics2D g = newImg.createGraphics();

    // draw the original image with it
    g.drawImage(img, 0, 0, null);

    // then draw the border
    g.setColor(new Color(borderColor[0], borderColor[1], borderColor[2]));
    g.setStroke(new BasicStroke(borderThickness));
    g.drawRect(0, 0, width, height);

    // conserve resources
    g.dispose();

    return newImg;
}

代码可以这样测试:

public static void main(String[] args) {
    String urlPath = "https://i.stack.imgur.com/kdvs7.png";
    URL url = null;
    BufferedImage img = null;
    try {
        url = new URL(urlPath);
        img = ImageIO.read(url);
    } catch (IOException e) {
        e.printStackTrace();
        System.exit(-1);
    }

    AddBorder addBorder = new AddBorder();
    BufferedImage newImg = addBorder.addBorder(img, 20, Color.BLUE);

    // display original image
    ImageIcon icon1 = new ImageIcon(img);
    JOptionPane.showMessageDialog(null, icon1);

    // display the image
    ImageIcon icon2 = new ImageIcon(newImg);
    JOptionPane.showMessageDialog(null, icon2);
}

评论

0赞 KajSech 11/7/2023
谢谢你的帮助!虽然我试图避免使用 Graphics2D
1赞 MadProgrammer 11/7/2023
@KajSech 为什么(避免)?它提供了一个易于使用的抽象层,这比尝试修改栅格数据要简单得多,而且通常更快Graphics2D
0赞 user85421 11/7/2023
此代码仅绘制图像内部厚度的一半,最终在右侧和底部缺少一个像素 (try ) - 必须调整坐标/大小(或将描边加倍)。thickness=1drawRect
0赞 KajSech 11/7/2023
@MadProgrammer 我只想学习如何编辑阵列像素的 RGB 值而不绘制它们
0赞 MadProgrammer 11/7/2023
@KajSech“编辑阵列像素的 RGB 值而不绘制它们”——好吧,就是在像素上“绘制”......或者至少更换它。老实说,我认为你错过了一个学习 API 强大功能的机会,因为它适用于许多其他领域,而不仅仅是图像。如果你对“更高级”的技术感兴趣,我会推荐 JH Labs,这是一本相当有趣的读物setRGBGraphics
1赞 MadProgrammer 11/7/2023 #2

有几种方法可以做到这一点,就我个人而言,我可能会考虑使用 ,因为它允许您(或)一起塑造形状,例如......Areasubtractadd

enter image description here

import java.awt.Color;
import java.awt.EventQueue;
import java.awt.Graphics2D;
import java.awt.GridLayout;
import java.awt.Rectangle;
import java.awt.geom.Area;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.imageio.ImageIO;
import javax.swing.ImageIcon;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;

public class Main {
    public static void main(String[] args) {
        new Main();
    }

    public Main() {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                try {
                    JFrame frame = new JFrame();
                    frame.add(new TestPane());
                    frame.pack();
                    frame.setLocationRelativeTo(null);
                    frame.setVisible(true);
                } catch (IOException ex) {
                    Logger.getLogger(Main.class.getName()).log(Level.SEVERE, null, ex);
                }
            }
        });
    }

    public static BufferedImage applyBorder(BufferedImage img, int borderThickness, Color borderColor) {
        if (borderThickness * 2 > img.getWidth() || borderThickness * 2 > img.getHeight()) {
            return null;
        }
        Area border = new Area(new Rectangle(0, 0, img.getWidth(), img.getHeight()));
        border.subtract(new Area(new Rectangle(
                borderThickness, 
                borderThickness, 
                img.getWidth() - (borderThickness * 2), 
                img.getHeight() - (borderThickness * 2)
        )));
        BufferedImage targetImg = new BufferedImage(img.getWidth(), img.getHeight(), img.getType());
        Graphics2D g2d = targetImg.createGraphics();
        g2d.drawImage(img, 0, 0, null);
        g2d.setColor(borderColor);
        g2d.fill(border);
        g2d.dispose();
        return targetImg;
    }

    public class TestPane extends JPanel {

        public TestPane() throws IOException {
            BufferedImage master = ImageIO.read(getClass().getResource("/images/MegaTokyo.png"));
            BufferedImage bordered = applyBorder(master, 10, Color.RED);
            setLayout(new GridLayout(0, 2));

            add(new JLabel(new ImageIcon(master)));
            add(new JLabel(new ImageIcon(bordered)));
        }
    }
}

您可能正在挠头,试图弄清楚为什么这会有所帮助。

想一想,如果你想让边框有一个圆润的内边缘怎么办!

嗯,这很容易实现,只需修改方法以减去不同的形状,例如......applyBorder

border.subtract(new Area(new RoundRectangle2D.Double(
        borderThickness, 
        borderThickness, 
        img.getWidth() - (borderThickness * 2), 
        img.getHeight() - (borderThickness * 2),
        32,
        32
)));

这会产生...

enter image description here

如果您真的愿意,也可以使用现有的 API。Border

“问题”......

你得到一个的原因是,我认为,出于某种原因我不理解,代码用图像的像素填充图像......我假设您正在尝试将图像“插入”到边框中,而不是将边框绘制在图像的顶部???java.lang.ArrayIndexOutOfBoundsException

无论如何,你的问题就在这里......

    for (int x = 0; x < width; x++) {
        for (int y = 0; y < height; y++) {
            targetImg.setRGB(x + borderThickness, y + borderThickness, img.getRGB(x, y));
        }
    }

When 是 ,然后您正在向其添加,这会尝试将像素放置在图像范围之外。位置也是如此。xwidth - 1borderThicknessy

假设您想在图像周围添加边框,就像在图像上添加边框一样,您需要修改代码以创建一个足够大的新图像以包含原始图像和边框,例如...

enter image description here

public static BufferedImage applySlowBorder(BufferedImage img, int borderThickness, Color borderColor) {
    int width = img.getWidth();
    int height = img.getHeight();
    int borderRgb = borderColor.getRGB();//(borderColor[0] << 16) | (borderColor[1] << 8) | borderColor[2];
    
    BufferedImage targetImg = new BufferedImage(
            img.getWidth() + (borderThickness * 2), 
            img.getHeight() + (borderThickness * 2), 
            img.getType()
    );

    for (int x = 0; x < targetImg.getWidth(); x++) {
        for (int y = 0; y < borderThickness; y++) {
            targetImg.setRGB(x, y, borderRgb); // Top border
            targetImg.setRGB(x, targetImg.getHeight() - 1 - y, borderRgb); // Bottom border
        }
    }

    for (int y = borderThickness; y < targetImg.getHeight() - borderThickness; y++) {
        for (int x = 0; x < borderThickness; x++) {
            targetImg.setRGB(x, y, borderRgb); // Left border
            targetImg.setRGB(targetImg.getWidth() - 1 - x, y, borderRgb); // Right border
        }
    }

    for (int x = 0; x < width; x++) {
        for (int y = 0; y < height; y++) {
            targetImg.setRGB(x + borderThickness, y + borderThickness, img.getRGB(x, y));
        }
    }
    return targetImg;
}

但是,如果您想在图像上绘制边框,则需要先绘制原始图像,例如......

enter image description here

public static BufferedImage applySlowBorder(BufferedImage img, int borderThickness, Color borderColor) {
    int width = img.getWidth();
    int height = img.getHeight();
    int borderRgb = borderColor.getRGB();//(borderColor[0] << 16) | (borderColor[1] << 8) | borderColor[2];
    
    BufferedImage targetImg = new BufferedImage(
            img.getWidth(),
            img.getHeight(),
            img.getType()
    );

    for (int x = 0; x < width; x++) {
        for (int y = 0; y < height; y++) {
            targetImg.setRGB(x, y, img.getRGB(x, y));
        }
    }

    for (int x = 0; x < targetImg.getWidth(); x++) {
        for (int y = 0; y < borderThickness; y++) {
            targetImg.setRGB(x, y, borderRgb); // Top border
            targetImg.setRGB(x, targetImg.getHeight() - 1 - y, borderRgb); // Bottom border
        }
    }

    for (int y = borderThickness; y < targetImg.getHeight() - borderThickness; y++) {
        for (int x = 0; x < borderThickness; x++) {
            targetImg.setRGB(x, y, borderRgb); // Left border
            targetImg.setRGB(targetImg.getWidth() - 1 - x, y, borderRgb); // Right border
        }
    }
    return targetImg;
}

仅供参考:使用我的超级准确、科学的测量工具(插入讽刺🤣),Graphics2D 方法比 get/setRGB 方法快约 10 毫秒 - 虽然差异不大,但图像的大小将对这些结果产生越来越大的影响 - 例如,使用 1920x1080 源图像 Graphics2D 方法大约需要 9 毫秒,而您的代码大约需要 203 毫秒 - 同样,这些是“观察”差异,但众所周知,get/setRGB 速度很慢

评论

0赞 Hovercraft Full Of Eels 11/7/2023
FancyRoundedBorders先生。1+
0赞 MadProgrammer 11/7/2023
@HovercraftFullOfEels 养猫,会剥皮 😜
0赞 user85421 11/7/2023
in 是什么意思?不应该是吗?rows: -1new GridLayout(-1, 2)0
0赞 MadProgrammer 11/7/2023
@user85421同样的事情:P
1赞 user85421 11/7/2023
但不是有据可查的......和 even 的非公共 javadoc 声明:“这应该是一个非负整数”(但有点像后门,因为它只抛出一个 IllegalArgument [Java 22])rowsif ((rows == 0) && (cols == 0))