Java - 圆角面板,在油漆中合成组件

从最初的问题(下面)来看,我现在为以下方面提供赏金:

基于圆角的解决方案。AlphaComposite

  • 请用 .JPanel
  • 角必须完全透明。
  • 必须能够支持JPG绘画,但仍然具有圆角
  • 不得使用设置剪辑(或任何剪辑)
  • 必须有不错的性能

希望有人能快速掌握这一点,这似乎很容易。

如果有一个解释清楚的原因,为什么这永远不能做,其他人也同意,我也会颁发赏金。

以下是我的想法的示例图像(但使用AlphaComposite)enter image description here


原始问题

我一直在尝试找出一种使用合成进行圆角的方法,这与如何在Java或 http://weblogs.java.net/blog/campbell/archive/2006/07/java_2d_tricker.html 中制作圆角图像非常相似。

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

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

1)有没有办法做到这一点?

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

SSCCE:

测试面板

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);
    }
}

答案 1

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

我认为以下摘自O'Reilly的Java Foundation Classes in a Nutshell可能对你有所帮助,以便理解AlphaComposite的行为,以及为什么中间图像可能是必要的技术。

阿尔法复合合成规则

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

在我们详细考虑合成规则之前,您需要了解一个重要点。屏幕上显示的颜色永远不会有 Alpha 通道。如果可以看到一种颜色,则它是不透明的颜色。精确的颜色值可能是根据透明度计算选择的,但是,一旦选择了该颜色,该颜色就会驻留在视频卡的内存中的某个位置,并且没有与之关联的 Alpha 值。换句话说,在屏幕绘图中,目标像素的 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 对象。


答案 2

我已经研究了这个问题,但无法看到如何在对系统类的单个调用中执行此操作。

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

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

备选案文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]);
    }
}

}

希望这有帮助,这在某种程度上是一个第二好的解决方案,但这就是生活(这是当一些图形大师来证明我错了(??));-)


推荐