Java – 在paintComponent中使用合成的圆角面板

从最初的问题(下面),我现在提供以下奖励:

基于AlphaComposite的圆角解决方案。

  • 请用JPanel演示。
  • 角落必须完全透明。
  • 必须能够支持JPG绘画,但仍然有圆角
  • 不得使用setClip(或任何剪辑)
  • 必须有不错的表现

希望有人快速选择这个,这似乎很容易。

如果有一个充分解释的原因,为什么永远不会这样做,其他人同意,我也会奖励赏金。

这是我想到的一个示例图像(但使用AlphaComposite在此处输入图像描述


原始问题

我一直试图找到一种使用合成来做圆角的方法,非常类似于如何在Java中制作圆角图像或http://weblogs.java.net/blog/campbell/archive/2006/07/ java_2d_tricker.html 。

但是,我没有中间BufferedImage的尝试不起作用 – 圆形目标合成显然不会影响源。 我尝试了不同的东西,但没有任何作用。 应该得到一个圆形的红色矩形,而不是我得到一个正方形。

所以,我有两个问题,真的:

1)有没有办法使这项工作?

2)中间图像是否会实际产生更好的性能?

SSCCE:

测试面板TPanel

 import java.awt.AlphaComposite; import java.awt.Color; import java.awt.Dimension; import java.awt.Graphics; import java.awt.Graphics2D; import javax.swing.JLabel; public class TPanel extends JLabel { int w = 300; int h = 200; public TPanel() { setOpaque(false); setPreferredSize(new Dimension(w, h)); setMaximumSize(new Dimension(w, h)); setMinimumSize(new Dimension(w, h)); } @Override public void paintComponent(Graphics g) { super.paintComponent(g); Graphics2D g2d = (Graphics2D) g.create(); // Yellow is the clipped area. g2d.setColor(Color.yellow); g2d.fillRoundRect(0, 0, w, h, 20, 20); g2d.setComposite(AlphaComposite.Src); // Red simulates the image. g2d.setColor(Color.red); g2d.setComposite(AlphaComposite.SrcAtop); g2d.fillRect(0, 0, w, h); } } 

和它的沙盒

 import java.awt.Dimension; import java.awt.FlowLayout; import javax.swing.JFrame; public class Sandbox { public static void main(String[] args) { JFrame f = new JFrame(); f.setMinimumSize(new Dimension(800, 600)); f.setLocationRelativeTo(null); f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); f.setLayout(new FlowLayout()); TPanel pnl = new TPanel(); f.getContentPane().add(pnl); f.setVisible(true); } } 

关于您的性能问题,Java 2D Trickery文章包含了Chet Haase关于中间图像使用的非常好的解释的链接。

我认为以下摘自O’Reilly的Java基础类的Nutshell可能对您有所帮助,以便了解AlphaComposite行为以及为什么中间图像可能是必要的技术。

AlphaComposite合成规则

SRC_OVER合成规则在目标颜色上绘制可能半透明的源颜色。 这是我们在执行图形操作时通常希望发生的事情。 但是AlphaComposite对象实际上也允许根据其他七个规则组合颜色。

在我们详细考虑合成规则之前,您需要了解一个重点。 屏幕上显示的颜色从不具有Alpha通道。 如果你能看到一种颜色,它就是一种不透明的颜色。 可以基于透明度计算来选择精确的颜色值,但是,一旦选择了该颜色,颜色就存在于某处的video卡的存储器中并且不具有与其相关联的α值。 换句话说,使用屏幕绘制时,目标像素的alpha值始终为1.0。

但是,当您绘制屏幕外图像时,情况会有所不同。 正如您将在本章后面考虑Java 2D BufferedImage类时所看到的那样,您可以在创建屏幕外图像时指定所需的颜色表示。 默认情况下,BufferedImage对象将图像表示为RGB颜色数组,但您也可以创建一个ARGB颜色数组的图像。 这样的图像具有与之关联的alpha值,当您绘制图像时,alpha值仍与您绘制的像素相关联。

屏幕上和屏幕外绘图之间的这种区别很重要,因为一些合成规则基于目标像素的alpha值而不是源像素的alpha值执行合成。 使用屏幕绘制时,目标像素始终是不透明的(alpha值为1.0),但是对于屏幕外绘图,情况并非如此。 因此,某些合成规则仅在您绘制具有Alpha通道的屏幕外图像时才有用。

为了过度概括,我们可以说当你在屏幕上绘图时,你通常会坚持使用默认的SRC_OVER合成规则,使用不透明的颜色,并改变AlphaComposite对象使用的alpha值。 但是,在处理具有Alpha通道的屏幕外图像时,您可以使用其他合成规则。 在这种情况下,您通常使用半透明颜色和半透明图像以及Alpha值为1.0的AlphaComposite对象。

我已经研究过这个问题,并且无法在一次调用系统类中看到如何执行此操作。

Graphics2D是一个抽象实例,实现为SunGraphics2D。 源代码可以在例如docjar中获得 ,因此我们可能通过复制一些代码来“做同样但不同的”。 但是,绘制图像的方法取决于一些不可用的“管道”类。 虽然你做了类加载的东西,但你可能会遇到一些原生的优化类,这些类无法被操作来做理论上的最优方法; 所有你得到的是作为正方形的图像绘画。

但是,我们可以采用一种方法,使我们自己的非本机(读取:慢?)代码尽可能少地运行,而不是取决于图像大小,而是取决于圆形矩形中的(相对)低区域。 此外,无需在内存中复制图像,消耗大量内存。 但是如果你有很多内存,那么很明显,在创建实例后,缓存的图像会更快。

备选方案1:

 import java.awt.Composite; import java.awt.CompositeContext; import java.awt.Dimension; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.RenderingHints; import java.awt.image.BufferedImage; import java.awt.image.ColorModel; import java.awt.image.Raster; import java.awt.image.WritableRaster; import javax.swing.JLabel; public class TPanel2 extends JLabel implements Composite, CompositeContext { private int w = 300; private int h = 200; private int cornerRadius = 20; private int[] roundRect; // first quadrant private BufferedImage image; private int[][] first = new int[cornerRadius][]; private int[][] second = new int[cornerRadius][]; private int[][] third = new int[cornerRadius][]; private int[][] forth = new int[cornerRadius][]; public TPanel2() { setOpaque(false); setPreferredSize(new Dimension(w, h)); setMaximumSize(new Dimension(w, h)); setMinimumSize(new Dimension(w, h)); // calculate round rect roundRect = new int[cornerRadius]; for(int i = 0; i < roundRect.length; i++) { roundRect[i] = (int)(Math.cos(Math.asin(1 - ((double)i)/20))*20); // x for y } image = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB); // all black } @Override public void paintComponent(Graphics g) { // discussion: // We have to work with the passed Graphics object. if(g instanceof Graphics2D) { Graphics2D g2d = (Graphics2D) g; // draw the whole image and save the corners g2d.setComposite(this); g2d.drawImage(image, 0, 0, null); } else { super.paintComponent(g); } } @Override public CompositeContext createContext(ColorModel srcColorModel, ColorModel dstColorModel, RenderingHints hints) { return this; } @Override public void dispose() { } @Override public void compose(Raster src, Raster dstIn, WritableRaster dstOut) { // lets assume image pixels >> round rect pixels // lets also assume bulk operations are optimized // copy current pixels for(int i = 0; i < cornerRadius; i++) { // quadrants // from top to buttom // first first[i] = (int[]) dstOut.getDataElements(src.getWidth() - (cornerRadius - roundRect[i]), i, cornerRadius - roundRect[i], 1, first[i]); // second second[i] = (int[]) dstOut.getDataElements(0, i, cornerRadius - roundRect[i], 1, second[i]); // from buttom to top // third third[i] = (int[]) dstOut.getDataElements(0, src.getHeight() - i - 1, cornerRadius - roundRect[i], 1, third[i]); // forth forth[i] = (int[]) dstOut.getDataElements(src.getWidth() - cornerRadius + roundRect[i], src.getHeight() - i - 1, cornerRadius - roundRect[i], 1, forth[i]); } // overwrite entire image as a square dstOut.setRect(src); // copy previous pixels back in corners for(int i = 0; i < cornerRadius; i++) { // first dstOut.setDataElements(src.getWidth() - cornerRadius + roundRect[i], i, first[i].length, 1, second[i]); // second dstOut.setDataElements(0, i, second[i].length, 1, second[i]); // third dstOut.setDataElements(0, src.getHeight() - i - 1, third[i].length, 1, third[i]); // forth dstOut.setDataElements(src.getWidth() - cornerRadius + roundRect[i], src.getHeight() - i - 1, forth[i].length, 1, forth[i]); } } } 

备选方案2:

 import java.awt.AlphaComposite; import java.awt.Composite; import java.awt.CompositeContext; import java.awt.Dimension; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.RenderingHints; import java.awt.image.BufferedImage; import java.awt.image.ColorModel; import java.awt.image.Raster; import java.awt.image.WritableRaster; import javax.swing.JLabel; public class TPanel extends JLabel implements Composite, CompositeContext { private int w = 300; private int h = 200; private int cornerRadius = 20; private int[] roundRect; // first quadrant private BufferedImage image; private boolean initialized = false; private int[][] first = new int[cornerRadius][]; private int[][] second = new int[cornerRadius][]; private int[][] third = new int[cornerRadius][]; private int[][] forth = new int[cornerRadius][]; public TPanel() { setOpaque(false); setPreferredSize(new Dimension(w, h)); setMaximumSize(new Dimension(w, h)); setMinimumSize(new Dimension(w, h)); // calculate round rect roundRect = new int[cornerRadius]; for(int i = 0; i < roundRect.length; i++) { roundRect[i] = (int)(Math.cos(Math.asin(1 - ((double)i)/20))*20); // x for y } image = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB); // all black } @Override public void paintComponent(Graphics g) { if(g instanceof Graphics2D) { Graphics2D g2d = (Graphics2D) g; // draw 1 + 2 rectangles and copy pixels from image. could also be 1 rectangle + 4 edges g2d.setComposite(AlphaComposite.Src); g2d.drawImage(image, cornerRadius, 0, image.getWidth() - cornerRadius - cornerRadius, image.getHeight(), null); g2d.drawImage(image, 0, cornerRadius, cornerRadius, image.getHeight() - cornerRadius - cornerRadius, null); g2d.drawImage(image, image.getWidth() - cornerRadius, cornerRadius, image.getWidth(), image.getHeight() - cornerRadius, image.getWidth() - cornerRadius, cornerRadius, image.getWidth(), image.getHeight() - cornerRadius, null); // draw the corners using our own logic g2d.setComposite(this); g2d.drawImage(image, 0, 0, null); } else { super.paintComponent(g); } } @Override public CompositeContext createContext(ColorModel srcColorModel, ColorModel dstColorModel, RenderingHints hints) { return this; } @Override public void dispose() { } @Override public void compose(Raster src, Raster dstIn, WritableRaster dstOut) { // assume only corners need painting if(!initialized) { // copy image pixels for(int i = 0; i < cornerRadius; i++) { // quadrants // from top to buttom // first first[i] = (int[]) src.getDataElements(src.getWidth() - cornerRadius, i, roundRect[i], 1, first[i]); // second second[i] = (int[]) src.getDataElements(cornerRadius - roundRect[i], i, roundRect[i], 1, second[i]); // from buttom to top // third third[i] = (int[]) src.getDataElements(cornerRadius - roundRect[i], src.getHeight() - i - 1, roundRect[i], 1, third[i]); // forth forth[i] = (int[]) src.getDataElements(src.getWidth() - cornerRadius, src.getHeight() - i - 1, roundRect[i], 1, forth[i]); } initialized = true; } // copy image pixels into corners for(int i = 0; i < cornerRadius; i++) { // first dstOut.setDataElements(src.getWidth() - cornerRadius, i, first[i].length, 1, second[i]); // second dstOut.setDataElements(cornerRadius - roundRect[i], i, second[i].length, 1, second[i]); // third dstOut.setDataElements(cornerRadius - roundRect[i], src.getHeight() - i - 1, third[i].length, 1, third[i]); // forth dstOut.setDataElements(src.getWidth() - cornerRadius, src.getHeight() - i - 1, forth[i].length, 1, forth[i]); } } } 

希望这有所帮助,这有点是第二个最好的解决方案,但这就是生活(这是一些图形大师出现并certificate我错了(??);-)