如何在Java中制作圆角图片

20

我想制作一个带圆角的图片。输入一张图片后,我会将其变成带圆角的图片并保存。我使用纯Java。我应该如何做?我需要像这样的函数:

public void makeRoundedCorner(Image image, File outputFile){
.....
}

Schema

编辑:添加了一张图片以提供信息。


你需要更具体地说明圆角:是透明的还是填充了背景颜色(前者可能会强制使用PNG作为输出格式)?如果是变半径,相对于什么固定或可变? - Philipp Reichart
我猜这个问题已经在这里得到了回答:http://stackoverflow.com/questions/1826665/round-corners-on-images-using-java-and-jai - Kris
这个问题缺少很多必要的细节,因此很难回答。 - Hovercraft Full Of Eels
一个大致的想法围绕着 new BufferedImagegetGraphics()setClip(new RoundedRectangle.Float(...)) 和调用 drawImage() - Philipp Reichart
@Philipp Reichart 这会向图像添加一个形状,我想要切圆角。 - Ali Davut
1
你误解了 setClip(),让我写了一个实际的答案,见下文 :) - Philipp Reichart
3个回答

37
我建议使用这种方法,它可以接收一张图片并生成一张新的图片,并且将图像输入输出保持在外部。 编辑:最终我成功地让Java2D对图形进行了软剪裁,并得到了Java 2D Trickery: Soft Clipping by Chris Campbell的帮助。可悲的是,这不是Java2D默认支持的功能,需要某些RenderingHint
public static BufferedImage makeRoundedCorner(BufferedImage image, int cornerRadius) {
    int w = image.getWidth();
    int h = image.getHeight();
    BufferedImage output = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB);

    Graphics2D g2 = output.createGraphics();
    
    // This is what we want, but it only does hard-clipping, i.e. aliasing
    // g2.setClip(new RoundRectangle2D ...)

    // so instead fake soft-clipping by first drawing the desired clip shape
    // in fully opaque white with antialiasing enabled...
    g2.setComposite(AlphaComposite.Src);
    g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
    g2.setColor(Color.WHITE);
    g2.fill(new RoundRectangle2D.Float(0, 0, w, h, cornerRadius, cornerRadius));
    
    // ... then compositing the image on top,
    // using the white shape from above as alpha source
    g2.setComposite(AlphaComposite.SrcAtop);
    g2.drawImage(image, 0, 0, null);
    
    g2.dispose();
    
    return output;
}

这是一个测试驱动程序:

public static void main(String[] args) throws IOException {
    BufferedImage icon = ImageIO.read(new File("icon.png"));
    BufferedImage rounded = makeRoundedCorner(icon, 20);
    ImageIO.write(rounded, "png", new File("icon.rounded.png"));
}

以下是上述方法的输入/输出:

输入:

输入图像

使用 setClip() 产生的丑陋、不平滑输出:

setclip 不平滑输出

使用复合技巧产生的漂亮、平滑输出:

复合技巧 平滑输出

在灰色背景下角落的近距离照片(左侧为 setClip(),右侧为复合):

灰色背景下的角落近距离照片


尽管角落被别名化了,但是没有明显的“RenderingHint”可以做到我想要的效果...如果我找到了让角落变得平滑的方法,我会更新我的代码。 - Philipp Reichart
你需要创建四个线条和圆形,并将它们放在一起,放置EmptyBorders并在角落内留出空间重新计算每个像素并删除它,但为什么要重新发明轮子呢?官方API已经存在。 - mKorbel
可以了,谢谢。我现在可以使用你的答案,但是我需要圆滑的角落。如果你找到了方法,请添加。谢谢Philipp Reichart。 - Ali Davut
也许值得添加一个额外的渲染提示:RenderingHints.KEY_STROKE_CONTROL,RenderingHints.VALUE_STROKE_PURE,以确保输出对称。您会注意到在您的示例中,右下角与左上角有些不同。 - Gary
我在一个答案中引用了这个答案,它解决了在japer报告中添加圆角边框的问题,感谢提供这样一个优秀的解决方案。 - Petter Friberg
显示剩余2条评论

3
我在回答Philipp Reichart的问题时进行了跟进。为了去掉白色背景(图片中看起来是黑色),请将 g2.setComposite(AlphaComposite.SrcAtop); 改为 g2.setComposite(AlphaComposite.SrcIn);
这对我来说是个大问题,因为我有不想失去透明度的不同图像。
我的原始图片:
enter image description here 如果我使用 g2.setComposite(AlphaComposite.SrcAtop);
enter image description here 当我使用 g2.setComposite(AlphaComposite.SrcIn); 时,背景就是透明的。

0

我找到了另一种使用TexturePaint的方法:

                ImageObserver obs = ...;
                int w = img.getWidth(obs);
                int h = img.getHeight(obs);

                // any shape can be used
                Shape clipShape = new RoundRectangle2D.Double(0, 0, w, h, 20, 20);

                // create a BufferedImage with transparency
                BufferedImage bi = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB);
                Graphics2D bg = bi.createGraphics();

                // make BufferedImage fully transparent
                bg.setComposite(AlphaComposite.Clear);
                bg.fillRect(0, 0, w, h);
                bg.setComposite(AlphaComposite.SrcOver);

                // copy/paint the actual image into the BufferedImage
                bg.drawImage(img, 0, 0, w, h, obs);

                // set the image to be used as TexturePaint on the target Graphics
                g.setPaint(new TexturePaint(bi, new Rectangle2D.Float(0, 0, w, h)));

                // activate AntiAliasing
                g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);

                // translate the origin to where you want to paint the image
                g.translate(x, y);

                // draw the Image
                g.fill(clipShape);

                // reset paint
                g.setPaint(null);

如果您有一个非动画图像,可以通过仅创建BufferedImage一次并将其保留每个paint来简化此代码。
但是,如果您的图像是动画的,则必须在每次paint时重新创建BufferedImage。(或者至少我还没有找到更好的解决方案。)

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接