透明度
最后修改于 2023 年 7 月 17 日
在 Java 2D 的这一部分,我们讨论透明度。我们提供了一些基本定义和几种有趣的透明度效果。
透明度解释
透明度是能够看穿物质的性质。理解透明度最简单的方法是想象一块玻璃或水。从技术上讲,光线可以穿过玻璃,这样我们就能看到玻璃后面的物体。
在计算机图形学中,我们可以使用*alpha 混合*来实现透明度效果。Alpha 混合是结合图像和背景以创建部分透明外观的过程。混合过程使用*alpha 通道*。Alpha 通道是图形文件格式中一个 8 位图层,用于表达半透明性(透明度)。每像素的额外八位作为掩码,代表 256 级半透明性。
AlphaComposite 类用于处理 Java 2D 中的透明度。它实现了基本的 alpha 混合规则,用于组合源像素和目标像素,以实现图形和图像的混合和透明度效果。要创建 AlphaComposite,我们需要提供两个值:规则指示符和 alpha 值。规则指定我们如何组合源像素和目标像素。最常见的是 AlphaComposite.SRC_OVER。alpha 值可以从 0.0f(完全透明)到 1.0f(完全不透明)。
透明矩形
第一个示例绘制了十个具有不同透明度级别的矩形。
package com.zetcode;
import java.awt.AlphaComposite;
import java.awt.Color;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import javax.swing.JFrame;
import javax.swing.JPanel;
class Surface extends JPanel {
private void doDrawing(Graphics g) {
Graphics2D g2d = (Graphics2D) g.create();
g2d.setPaint(Color.blue);
for (int i = 1; i <= 10; i++) {
float alpha = i * 0.1f;
AlphaComposite alcom = AlphaComposite.getInstance(
AlphaComposite.SRC_OVER, alpha);
g2d.setComposite(alcom);
g2d.fillRect(50 * i, 20, 40, 40);
}
g2d.dispose();
}
@Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
doDrawing(g);
}
}
public class TransparentRectanglesEx extends JFrame {
public TransparentRectanglesEx() {
initUI();
}
private void initUI() {
add(new Surface());
setTitle("Transparent rectangles");
setSize(590, 120);
setLocationRelativeTo(null);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
@Override
public void run() {
TransparentRectanglesEx ex = new TransparentRectanglesEx();
ex.setVisible(true);
}
});
}
}
在我们的示例中,我们绘制了 10 个应用了各种透明度级别的蓝色矩形。
float alpha = i * 0.1f;
alpha 值在 for 循环中动态变化。
AlphaComposite alcom = AlphaComposite.getInstance(
AlphaComposite.SRC_OVER, alpha);
AlphaComposite.getInstance 方法创建一个 AlphaComposite 对象,该对象具有指定的规则和要乘以源 alpha 的常量 alpha。
g2d.setComposite(alcom);
setComposite 方法为 Graphics2D 对象设置 composite 属性。
淡出演示
在下一个示例中,我们将淡出一个图像。图像将逐渐变得更透明,直到完全不可见。
package com.zetcode;
import java.awt.AlphaComposite;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.ImageIcon;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.Timer;
class Surface extends JPanel
implements ActionListener {
private Image img;
private Timer timer;
private float alpha = 1f;
private final int DELAY = 40;
private final int INITIAL_DELAY = 500;
public Surface() {
loadImage();
setSurfaceSize();
initTimer();
}
private void loadImage() {
img = new ImageIcon("mushrooms.jpg").getImage();
}
private void setSurfaceSize() {
int h = img.getHeight(this);
int w = img.getWidth(this);
setPreferredSize(new Dimension(w, h));
}
private void initTimer() {
timer = new Timer(DELAY, this);
timer.setInitialDelay(INITIAL_DELAY);
timer.start();
}
private void doDrawing(Graphics g) {
Graphics2D g2d = (Graphics2D) g.create();
AlphaComposite acomp = AlphaComposite.getInstance(
AlphaComposite.SRC_OVER, alpha);
g2d.setComposite(acomp);
g2d.drawImage(img, 0, 0, null);
g2d.dispose();
}
@Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
doDrawing(g);
}
private void step() {
alpha += -0.01f;
if (alpha <= 0) {
alpha = 0;
timer.stop();
}
}
@Override
public void actionPerformed(ActionEvent e) {
step();
repaint();
}
}
public class FadeOutEx extends JFrame {
public FadeOutEx() {
initUI();
}
private void initUI() {
add(new Surface());
pack();
setTitle("Fade out");
setLocationRelativeTo(null);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
@Override
public void run() {
FadeOutEx ex = new FadeOutEx();
ex.setVisible(true);
}
});
}
}
使用 AlphaComposite,我们逐渐淡出一个面板上的图像。
private void setSurfaceSize() {
int h = img.getHeight(this);
int w = img.getWidth(this);
setPreferredSize(new Dimension(w, h));
}
setSurfaceSize 方法确定图像的大小并将首选大小设置为面板。首选大小与 pack 方法结合使用,将窗口显示得足够大以显示整个图像。
private void initTimer() {
timer = new Timer(DELAY, this);
timer.setInitialDelay(INITIAL_DELAY);
timer.start();
}
initTimer 方法启动一个计时器。计时器在指定的初始延迟后触发操作事件。连续的操作事件在事件之间延迟生成。作为对操作事件的响应,我们将更改 alpha 值并重绘面板。
AlphaComposite acomp = AlphaComposite.getInstance(
AlphaComposite.SRC_OVER, alpha);
g2d.setComposite(acomp);
g2d.drawImage(img, 0, 0, null);
此代码在面板上绘制一个透明度级别不断增加的图像。
private void step() {
alpha += -0.01f;
if (alpha <= 0) {
alpha = 0;
timer.stop();
}
}
step 方法代表一个淡出周期。alpha 值逐渐减小。请注意,alpha 值不能为负数。当它达到零时,计时器停止。
repaint();
repaint 方法重绘组件。它调用面板组件的 paint 方法,该方法又调用 paintComponent 方法。
等待演示
在此示例中,我们使用透明度效果创建了一个等待演示。我们绘制了 8 条逐渐淡出的线条,营造出一条线条正在移动的错觉。此类效果通常用于通知用户后台正在进行一项耗时任务。例如,在互联网上流式传输视频时。
package com.zetcode;
import java.awt.AlphaComposite;
import java.awt.BasicStroke;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.Timer;
class Surface extends JPanel
implements ActionListener {
private Timer timer;
private int count;
private final int INITIAL_DELAY = 200;
private final int DELAY = 80;
private final int NUMBER_OF_LINES = 8;
private final int STROKE_WIDTH = 3;
private final double[][] trs = {
{0.0, 0.15, 0.30, 0.5, 0.65, 0.80, 0.9, 1.0},
{1.0, 0.0, 0.15, 0.30, 0.5, 0.65, 0.8, 0.9},
{0.9, 1.0, 0.0, 0.15, 0.3, 0.5, 0.65, 0.8},
{0.8, 0.9, 1.0, 0.0, 0.15, 0.3, 0.5, 0.65},
{0.65, 0.8, 0.9, 1.0, 0.0, 0.15, 0.3, 0.5},
{0.5, 0.65, 0.8, 0.9, 1.0, 0.0, 0.15, 0.3},
{0.3, 0.5, 0.65, 0.8, 0.9, 1.0, 0.0, 0.15},
{0.15, 0.3, 0.5, 0.65, 0.8, 0.9, 1.0, 0.0}
};
public Surface() {
initTimer();
}
private void initTimer() {
timer = new Timer(DELAY, this);
timer.setInitialDelay(INITIAL_DELAY);
timer.start();
}
private void doDrawing(Graphics g) {
Graphics2D g2d = (Graphics2D) g.create();
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
g2d.setRenderingHint(RenderingHints.KEY_RENDERING,
RenderingHints.VALUE_RENDER_QUALITY);
int width = getWidth();
int height = getHeight();
g2d.setStroke(new BasicStroke(STROKE_WIDTH, BasicStroke.CAP_ROUND,
BasicStroke.JOIN_ROUND));
g2d.translate(width / 2, height / 2);
for (int i = 0; i < NUMBER_OF_LINES; i++) {
float alpha = (float) trs[count % NUMBER_OF_LINES][i];
AlphaComposite acomp = AlphaComposite.getInstance(
AlphaComposite.SRC_OVER, alpha);
g2d.setComposite(acomp);
g2d.rotate(Math.PI / 4f);
g2d.drawLine(0, -10, 0, -40);
}
g2d.dispose();
}
@Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
doDrawing(g);
}
@Override
public void actionPerformed(ActionEvent e) {
repaint();
count++;
}
}
public class WaitingEx extends JFrame {
public WaitingEx() {
initUI();
}
private void initUI() {
add(new Surface());
setTitle("Waiting");
setSize(300, 200);
setLocationRelativeTo(null);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
@Override
public void run() {
WaitingEx ex = new WaitingEx();
ex.setVisible(true);
}
});
}
}
我们绘制八条线,每条线有八个不同的 alpha 值。
private final double[][] trs = {
...
};
这是一个用于此演示的二维透明度值数组。有 8 行,每行代表一种状态。这 8 条线将连续使用这些值。
g2d.setStroke(new BasicStroke(STROKE_WIDTH, BasicStroke.CAP_ROUND,
BasicStroke.JOIN_ROUND));
我们将线条画得稍粗一些,以便更好地显示。我们用圆角绘制线条。
g2d.rotate(Math.PI/4f); g2d.drawLine(0, -10, 0, -40);
此代码绘制了八条线中的每一条。rotate 方法用于沿圆周旋转线条。
在 Java 2D 教程的这一部分,我们讨论了透明度。