純文字介面雖然高效,但缺乏直觀吸引力。Java 提供豐富的多媒體 API,讓開發者能輕鬆整合影像、音效、字型與色彩,打造生動的使用者體驗。本章將系統介紹 java.awt、javax.swing、java.applet 等套件的多媒體處理能力,並提供實用的最佳實踐模式。
| 格式 | 副檔名 | 特色 |
|---|---|---|
| JPEG | .jpg, .jpeg | 有損壓縮、適合照片、不支援透明 |
| PNG | .png | 無損壓縮、支援 Alpha 透明、適合圖示/介面 |
| GIF | .gif | 256 色、支援動畫、單色透明 |
| BMP | .bmp | 無壓縮、檔案大、相容性佳 |
| WBMP | .wbmp | 無線位圖、行動裝置專用 |
核心類別:
java.awt.Image — 抽象影像類別,代表像素資料javax.swing.ImageIcon — Swing 元件專用影像包裝,可直接套用於 JLabel、JButton 等java.awt.Toolkit — getImage(url) 非同步載入javax.imageio.ImageIO — 推薦,同步讀寫、格式轉換、元資料處理
// 1. 載入影像 (推薦使用 ImageIO 同步載入,確保完整讀取)
Image rawImage = ImageIO.read(new File("photo.jpg"));
// 或從 classpath 載入(打包成 JAR 時適用)
// Image rawImage = ImageIO.read(getClass().getResource("/images/photo.jpg"));
// 2. 包裝為 ImageIcon
ImageIcon icon = new ImageIcon(rawImage);
// 3. 套用至元件
JLabel label = new JLabel(icon);
JButton btn = new JButton("帶圖示按鈕", icon);
btn.setHorizontalTextPosition(SwingConstants.CENTER);
btn.setVerticalTextPosition(SwingConstants.BOTTOM); // 文字在圖示下方
JButton toggleBtn = new JButton();
toggleBtn.setIcon(new ImageIcon(ImageIO.read(new File("off.png")))); // 正常
toggleBtn.setRolloverIcon(new ImageIcon(ImageIO.read(new File("hover.png")))); // 滑鼠懸停
toggleBtn.setPressedIcon(new ImageIcon(ImageIO.read(new File("pressed.png")))); // 按下
toggleBtn.setSelectedIcon(new ImageIcon(ImageIO.read(new File("on.png")))); // 選中狀態
toggleBtn.setDisabledIcon(new ImageIcon(ImageIO.read(new File("disabled.png")))); // 停用
public class ImageViewer extends JFrame {
private JLabel imageLabel = new JLabel();
private JFileChooser fileChooser = new JFileChooser();
private FileNameExtensionFilter imgFilter =
new FileNameExtensionFilter("影像檔案", "jpg", "jpeg", "png", "gif", "bmp");
public ImageViewer() {
setTitle("影像檢視器");
setDefaultCloseOperation(EXIT_ON_CLOSE);
setLayout(new BorderLayout());
// 中央顯示區,支援捲動
JScrollPane scrollPane = new JScrollPane(imageLabel);
scrollPane.getViewport().setBackground(Color.DARK_GRAY);
add(scrollPane, BorderLayout.CENTER);
// 底部工具列
JPanel toolbar = new JPanel(new FlowLayout(FlowLayout.LEFT));
JButton openBtn = new JButton("開啟影像");
JButton zoomInBtn = new JButton("放大 (+)");
JButton zoomOutBtn = new JButton("縮小 (-)");
JButton resetBtn = new JButton("原始大小");
toolbar.add(openBtn); toolbar.add(zoomInBtn); toolbar.add(zoomOutBtn); toolbar.add(resetBtn);
add(toolbar, BorderLayout.SOUTH);
// 事件處理
openBtn.addActionListener(e -> openImage());
zoomInBtn.addActionListener(e -> scaleImage(1.25));
zoomOutBtn.addActionListener(e -> scaleImage(0.8));
resetBtn.addActionListener(e -> resetImage());
fileChooser.setFileFilter(imgFilter);
fileChooser.setCurrentDirectory(new File(System.getProperty("user.home"), "Pictures"));
pack();
setSize(800, 600);
setLocationRelativeTo(null);
setVisible(true);
}
private Image originalImage;
private double scale = 1.0;
private void openImage() {
if (fileChooser.showOpenDialog(this) == JFileChooser.APPROVE_OPTION) {
try {
originalImage = ImageIO.read(fileChooser.getSelectedFile());
scale = 1.0;
updateDisplay();
} catch (IOException ex) {
JOptionPane.showMessageDialog(this, "無法讀取檔案: " + ex.getMessage(), "錯誤", JOptionPane.ERROR_MESSAGE);
}
}
}
private void scaleImage(double factor) {
if (originalImage != null) {
scale *= factor;
updateDisplay();
}
}
private void resetImage() {
scale = 1.0;
updateDisplay();
}
private void updateDisplay() {
if (originalImage == null) return;
int w = (int)(originalImage.getWidth(null) * scale);
int h = (int)(originalImage.getHeight(null) * scale);
Image scaled = originalImage.getScaledInstance(w, h, Image.SCALE_SMOOTH);
imageLabel.setIcon(new ImageIcon(scaled));
imageLabel.setText(null); // 清除文字
revalidate(); // 更新捲動區域
}
public static void main(String[] args) {
SwingUtilities.invokeLater(ImageViewer::new);
}
}
現代應用較少使用 Applet,桌面程式推薦:
new File("path/to/image.png") 或 Paths.get(...)getClass().getResource("/images/logo.png")(打包 JAR 內資源)new URL("https://example.com/image.jpg") 配合 ImageIO.read(url)| 格式 | 副檔名 | 適用場景 |
|---|---|---|
| WAV | .wav | 無損、短音效、遊戲音效、系統提示音 |
| AIFF | .aif, .aiff | Mac 標準、無損 |
| AU | .au | Sun/Unix 標準、早期 Java 預設 |
| MIDI | .mid, .midi | 樂器數據、檔案極小、背景音樂 |
| 建立方式 | 語法範例 | 說明 |
|---|---|---|
| 預定義常數 | Color.RED, Color.BLUE, Color.WHITE... | 13 種基本顏色 + 淺/深灰階 |
| RGB 整數 | new Color(255, 128, 0) | 紅綠藍各 0-255,共 1677 萬色 |
| RGBA 整數 | new Color(255, 0, 0, 128) | 含 Alpha 透明度 (0 透明 ~ 255 不透明) |
| RGB 浮點數 | new Color(1.0f, 0.5f, 0.0f) | 各分量 0.0~1.0 |
| HSB 色相飽和度亮度 | Color.getHSBColor(0.5f, 1.0f, 1.0f) | H: 0-1 (色相環), S: 0-1, B: 0-1 |
| 十六進位 | Color.decode("#FF8000") | Web 標準格式 |
Color c = new Color(100, 150, 200);
int r = c.getRed(); // 0-255
int g = c.getGreen();
int b = c.getBlue();
int alpha = c.getAlpha(); // 0-255
int rgb = c.getRGB(); // 0xRRGGBB (不含 alpha)
int argb = c.getRGB(); // 0xAARRGGBB (含 alpha)
// 亮度計算 (sRGB 亮度公式)
float luminance = 0.2126f * r/255 + 0.7152f * g/255 + 0.0722f * b/255;
// 顏色轉換
float[] hsb = Color.RGBtoHSB(r, g, b, null); // 回傳 [h, s, b]
Color brighter = c.brighter(); // 變亮
Color darker = c.darker(); // 變暗
// 透明度處理
Color semiTransparent = new Color(r, g, b, 128); // 50% 透明
// 或
Color translucent = new Color(c.getRGB() & 0x00FFFFFF, true); // 保留 RGB,設 alpha=0
public class ColorPickerTool extends JFrame {
private JPanel previewPanel = new JPanel();
private JSlider rSlider = new JSlider(0, 255, 128);
private JSlider gSlider = new JSlider(0, 255, 128);
private JSlider bSlider = new JSlider(0, 255, 128);
private JSlider aSlider = new JSlider(0, 255, 255);
private JTextField hexField = new JTextField(8);
private JTextField rgbField = new JTextField(20);
public ColorPickerTool() {
setTitle("RGB 調色盤");
setDefaultCloseOperation(EXIT_ON_CLOSE);
setLayout(new BorderLayout(10, 10));
// 預覽區
previewPanel.setPreferredSize(new Dimension(300, 200));
previewPanel.setBorder(BorderFactory.createTitledBorder("預覽"));
previewPanel.setBackground(new Color(128, 128, 128));
add(previewPanel, BorderLayout.NORTH);
// 滑桿控制區
JPanel sliderPanel = new JPanel(new GridLayout(4, 2, 5, 5));
sliderPanel.setBorder(BorderFactory.createTitledBorder("RGBA 控制"));
addSliderRow(sliderPanel, "紅 (R):", rSlider);
addSliderRow(sliderPanel, "綠 (G):", gSlider);
addSliderRow(sliderPanel, "藍 (B):", bSlider);
addSliderRow(sliderPanel, "透明度 (A):", aSlider);
add(sliderPanel, BorderLayout.CENTER);
// 數值輸出區
JPanel outputPanel = new JPanel(new GridLayout(2, 2, 5, 5));
outputPanel.setBorder(BorderFactory.createTitledBorder("色碼輸出"));
outputPanel.add(new JLabel("HEX:")); outputPanel.add(hexField);
outputPanel.add(new JLabel("RGB:")); outputPanel.add(rgbField);
hexField.setEditable(false); rgbField.setEditable(false);
add(outputPanel, BorderLayout.SOUTH);
// 事件綁定
ChangeListener listener = e -> updateColor();
rSlider.addChangeListener(listener);
gSlider.addChangeListener(listener);
bSlider.addChangeListener(listener);
aSlider.addChangeListener(listener);
updateColor();
pack();
setLocationRelativeTo(null);
setVisible(true);
}
private void addSliderRow(JPanel panel, String label, JSlider slider) {
panel.add(new JLabel(label));
slider.setMajorTickSpacing(51);
slider.setMinorTickSpacing(17);
slider.setPaintTicks(true);
slider.setPaintLabels(true);
panel.add(slider);
}
private void updateColor() {
Color c = new Color(rSlider.getValue(), gSlider.getValue(),
bSlider.getValue(), aSlider.getValue());
previewPanel.setBackground(c);
// HEX 格式 #RRGGBB 或 #AARRGGBB
String hex = String.format("#%02X%02X%02X%02X",
c.getRed(), c.getGreen(), c.getBlue(), c.getAlpha());
hexField.setText(hex);
// RGB 格式
String rgb = String.format("rgb(%d, %d, %d)%s",
c.getRed(), c.getGreen(), c.getBlue(),
c.getAlpha() < 255 ? String.format(" / alpha=%d", c.getAlpha()) : "");
rgbField.setText(rgb);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(ColorPickerTool::new);
}
}
public class SettingsDialog extends JDialog {
public SettingsDialog(Frame owner) {
super(owner, "系統設定", true);
setLayout(new BorderLayout(10, 10));
setDefaultCloseOperation(DISPOSE_ON_CLOSE);
// 標題區:Logo + 標題
JPanel header = new JPanel(new BorderLayout());
header.setBackground(new Color(0x2C3E50));
header.setBorder(BorderFactory.createEmptyBorder(20, 20, 20, 20));
try {
Image logoImg = ImageIO.read(getClass().getResource("/images/logo.png"));
JLabel logoLabel = new JLabel(new ImageIcon(logoImg.getScaledInstance(64, 64, Image.SCALE_SMOOTH)));
header.add(logoLabel, BorderLayout.WEST);
} catch (Exception e) {
header.add(new JLabel("⚙"), BorderLayout.WEST);
}
JLabel titleLabel = new JLabel("系統設定");
titleLabel.setFont(new Font("微軟正黑體", Font.BOLD, 24));
titleLabel.setForeground(Color.WHITE);
header.add(titleLabel, BorderLayout.CENTER);
add(header, BorderLayout.NORTH);
// 內容區:分頁面板
JTabbedPane tabs = new JTabbedPane();
tabs.addTab("外觀", createAppearancePanel());
tabs.addTab("音效", createSoundPanel());
tabs.addTab("字型", createFontPanel());
tabs.addTab("色彩", createColorPanel());
add(tabs, BorderLayout.CENTER);
// 底部按鈕
JPanel btnPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT));
JButton okBtn = new JButton("確定");
JButton cancelBtn = new JButton("取消");
JButton applyBtn = new JButton("套用");
okBtn.addActionListener(e -> dispose());
cancelBtn.addActionListener(e -> dispose());
applyBtn.addActionListener(e -> applySettings());
btnPanel.add(applyBtn); btnPanel.add(okBtn); btnPanel.add(cancelBtn);
add(btnPanel, BorderLayout.SOUTH);
pack();
setSize(600, 500);
setLocationRelativeTo(owner);
setVisible(true);
}
private JPanel createAppearancePanel() {
JPanel p = new JPanel(new GridLayout(0, 1, 10, 10));
p.setBorder(BorderFactory.createEmptyBorder(20, 20, 20, 20));
p.add(new JCheckBox("深色模式"));
p.add(new JCheckBox("顯示工具列圖示"));
p.add(new JCheckBox("啟用動畫效果"));
return p;
}
private JPanel createSoundPanel() {
JPanel p = new JPanel(new GridLayout(0, 1, 10, 10));
p.setBorder(BorderFactory.createEmptyBorder(20, 20, 20, 20));
p.add(new JCheckBox("啟用系統音效"));
JSlider vol = new JSlider(0, 100, 70);
vol.setMajorTickSpacing(25);
vol.setPaintTicks(true);
vol.setPaintLabels(true);
p.add(new JLabel("主音量:"));
p.add(vol);
return p;
}
private JPanel createFontPanel() {
JPanel p = new JPanel(new GridBagLayout());
p.setBorder(BorderFactory.createEmptyBorder(20, 20, 20, 20));
GridBagConstraints gbc = new GridBagConstraints();
gbc.insets = new Insets(5, 5, 5, 5);
gbc.anchor = GridBagConstraints.WEST;
String[] fonts = GraphicsEnvironment.getLocalGraphicsEnvironment().getAvailableFontFamilyNames();
JComboBox fontBox = new JComboBox<>(fonts);
JSpinner sizeSpinner = new JSpinner(new SpinnerNumberModel(14, 8, 72, 1));
gbc.gridx = 0; gbc.gridy = 0; p.add(new JLabel("字型:"), gbc);
gbc.gridx = 1; gbc.fill = GridBagConstraints.HORIZONTAL; gbc.weightx = 1.0; p.add(fontBox, gbc);
gbc.gridx = 0; gbc.gridy = 1; gbc.fill = GridBagConstraints.NONE; gbc.weightx = 0; p.add(new JLabel("大小:"), gbc);
gbc.gridx = 1; gbc.fill = GridBagConstraints.HORIZONTAL; p.add(sizeSpinner, gbc);
// 預覽
JLabel preview = new JLabel("預覽文字: The quick brown fox jumps over the lazy dog");
preview.setFont(new Font("微軟正黑體", Font.PLAIN, 14));
gbc.gridx = 0; gbc.gridy = 2; gbc.gridwidth = 2; gbc.fill = GridBagConstraints.HORIZONTAL;
p.add(preview, gbc);
ChangeListener updater = e -> preview.setFont(new Font(
(String)fontBox.getSelectedItem(), Font.PLAIN, (Integer)sizeSpinner.getValue()));
fontBox.addActionListener(updater);
sizeSpinner.addChangeListener(updater);
return p;
}
private JPanel createColorPanel() {
JPanel p = new JPanel(new GridLayout(0, 2, 10, 10));
p.setBorder(BorderFactory.createEmptyBorder(20, 20, 20, 20));
String[] colorRoles = {"主色調", "次要色", "背景色", "文字色", "錯誤色", "成功色"};
for (String role : colorRoles) {
p.add(new JLabel(role + ":"));
JButton colorBtn = new JButton("選擇顏色");
colorBtn.addActionListener(e -> {
Color c = JColorChooser.showDialog(this, "選擇 " + role, Color.WHITE);
if (c != null) colorBtn.setBackground(c);
});
p.add(colorBtn);
}
return p;
}
private void applySettings() {
// 實際儲存設定至 Preferences 或設定檔
System.out.println("設定已套用");
}
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> new SettingsDialog(null));
}
}
MapSwingWorker 背景載入,避免阻塞 EDTsrc/main/resources,透過 getClass().getResource() 存取,打包 JAR 後仍可運作Image.flush() 釋放原生資源,避免 OOM本章完整涵蓋 Java 多媒體程式設計核心技術:影像載入顯示與縮放、音效播放控制、字型排版渲染、色彩模型與調色工具。掌握這些技能後,可進階學習 Java 2D 繪圖 API、JavaFX Media 媒體框架、OpenGL 整合 (JOGL/LWJGL) 等高階圖形多媒體開發。