15

从最初的问题(如下),我现在提供以下赏​​金:

AlphaComposite圆角的基于解决方案。

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

希望有人能快速解决这个问题,这似乎很容易。

如果有一个很好解释的理由说明为什么这永远不能完成,并且其他人同意,我也会奖励赏金。

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


原始问题

我一直在试图找出一种使用合成来做圆角的方法,非常类似于How to make a rounded corner image in Javahttp://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);
    }
}
4

2 回答 2

7

关于您的性能问题,Java 2D Trickery 文章包含一个链接,指向 Chet Haase 对Intermediate Images使用的一个很好的解释。

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

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 对象。

于 2012-02-24T14:35:36.540 回答
4

I have looked into this issue and cannot see how to do this in a single call to system classes.

Graphics2D is an abstract instance, implemented as SunGraphics2D. The source code is available at for example docjar, and so we could potentially just 'do the same, but different' by copy some code. However, methods to paint an image depends on some 'pipe' classes which are not available. Although you do stuff with classloading, you will probably hit some native, optimized class which cannot be manipulated to do the theoretically optimal approach; all you get is image painting as squares.

However we can make an approach in which our own, non-native (read:slow?), code runs as little as possible, and not depending on the image size but rather the (relatively) low area in the round rect. Also, without copying the images in memory, consuming a lot of memory. But if you have a lot of memory, then obviously, a cached image is faster after the instance has been created.

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

}

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

}

Hope this helps, this is somewhat of a second-best solution but that's life (this is when some graphics-guru comes and proves me wrong (??)..) ;-)

于 2012-02-24T12:19:00.690 回答