Java - закругленная угловая панель с композицией в paintComponent

Из исходного вопроса (ниже) я теперь предлагаю щедрость для следующего:

Решение AlphaComposite для закругленных углов.

  • Пожалуйста, продемонстрируйте с помощью JPanel.
  • Уголки должны быть полностью прозрачными.
  • Должна быть способна поддерживать JPG-роспись, но все же иметь закругленные углы.
  • Нельзя использовать setClip (или любое обрезание)
  • Должна иметь достойную производительность

Надеюсь, кто-то быстро подберет это, это кажется легким.

Я также награжу награду, если есть хорошо объясненная причина, почему это невозможно сделать, с которым другие согласны.

Вот пример изображения того, что я имею в виду (но используя AlphaComposite) enter image description here


Оригинальный вопрос

Я пытался найти способ сделать закругленные углы, используя композицию, очень похожую на Как сделать округленное угловое изображение в 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);
    }
}

Ответы

Ответ 1

Что касается вашей производительности, то статья Java 2D Trickery содержит ссылку на очень хорошее объяснение Чет Хаазе по использованию Промежуточные изображения.

Я думаю, что следующая выдержка из O'Reilly Java Foundation Classes в двух словах может быть полезна для вас, чтобы понять поведение AlphaComposite и почему промежуточные изображения могут быть необходимой методикой для использования,

Правила AlphaComposite Compositing

Правило составления SRC_OVER рисует возможно полупрозрачный источник цвет над цветом назначения. Это то, что мы обычно хотим когда мы выполняем графическую операцию. Но AlphaComposite объект фактически позволяет комбинировать цвета в соответствии с семью другими также правила.

Прежде чем мы подробно рассмотрим правила компоновки, важный момент, который вам нужно понять. Цвета, отображаемые на экране никогда не имеют альфа-канала. Если вы видите цвет, это непрозрачный цвет. Точное значение цвета может быть выбрано на основе прозрачности, но, как только этот цвет выбран, цвет где-то находится в памяти видеокарты и не имеет связанное с ним значение альфа. Другими словами, с помощью экрана рисунок, пиксели назначения всегда имеют альфа-значения 1.0.

Ситуация другая, когда вы рисуете в заставке изображение, однако. Как вы увидите, когда мы рассматриваем Java 2D BufferedImage далее в этой главе, вы можете указать желаемый когда вы создаете изображение за пределами экрана. По умолчанию, объект BufferedImage представляет изображение как массив цветов RGB, но вы также можете создать изображение, представляющее собой массив цветов ARGB. такие изображение имеет связанные с ним альфа-значения, и когда вы рисуете изображения, альфа-значения остаются связанными с пикселями, которые вы рисовать.

Это различие между экранным и внеэкранным рисунком важно потому что некоторые из композиционных правил выполняют композицию на основе альфа-значений целевых пикселей, а не альфа-значений исходных пикселей. С помощью экранного чертежа целевые пиксели всегда непрозрачны (с альфа-значениями 1,0), но с выключенным экраном рисунок, это не обязательно. Таким образом, некоторые композиции правила применимы только тогда, когда вы рисуете внеэкранные изображения, которые имеют альфа-канал.

Чтобы переразложить немного, мы можем сказать, что когда вы рисуете на экране, вы обычно придерживаетесь компоновки SRC_OVER по умолчанию правило, используйте непрозрачные цвета и изменяйте значение альфы, используемое Объект AlphaComposite. При работе с изображениями вне экрана, которые имеют альфа-каналы, однако вы можете использовать другие правила компоновки. В этом случае вы обычно используете полупрозрачные цвета и полупрозрачные изображения и объект AlphaComposite с альфа-значением 1.0.

Ответ 2

Я рассмотрел эту проблему и не вижу, как это сделать в одном вызове системных классов.

Graphics2D - абстрактный экземпляр, реализованный как SunGraphics2D. Исходный код доступен, например, docjar, и поэтому мы могли бы просто "сделать то же, но другое", скопировав некоторый код. Однако методы рисования изображения зависят от некоторых "трубных" классов, которые недоступны. Хотя вы делаете материал с загрузкой классов, вы, вероятно, попадете в какой-то собственный, оптимизированный класс, который нельзя манипулировать, чтобы сделать теоретически оптимальный подход; все, что у вас есть, - это рисование изображения как квадраты.

Однако мы можем сделать подход, в котором наш собственный, не-native (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]);
    }
}

}

Надеюсь, что это поможет, это несколько лучшее решение, но эта жизнь (это когда появляется какой-то графический гуру и доказывает, что я ошибаюсь (??)..); -)