Java - закругленная угловая панель с композитингом в paintComponent - PullRequest
14 голосов
/ 15 февраля 2012

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

Решение на основе 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);
    }
}

Ответы [ 2 ]

7 голосов
/ 24 февраля 2012

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

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

Правила компоновки AlphaComposite

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

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

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

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

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

4 голосов
/ 24 февраля 2012

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

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

}

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

...