先上预览图
上工具类代码
import javax.swing.*;
import java.awt.*;
import java.awt.image.BufferedImage;
/**
* 图像处理工具类
*/
public class ImageUtils {
/**
* 对ImageIcon图标进行缩放,不改变原对象,
* 该方法使用 getScaledInstance() 获取新图像,要缩放图像建议使用 getFasterScaledInstance()
*
* @param image 要缩放的图标对象
* @param i 缩放倍数
* @return 缩放后的图标对象
*/
public static ImageIcon imageIconScale(ImageIcon image, double i) {
int width = (int) (image.getIconWidth() * i);
int height = (int) (image.getIconHeight() * i);
Image img = image.getImage().getScaledInstance(width, height, Image.SCALE_DEFAULT);
return new ImageIcon(img);
}
/**
* 图像缩放<br>
* 缩放原理:对于缩小倍率较大的图像,使用一次drawImage()缩放并且当缩小比例大于 50% 时,图像质量会发生严重的问题,
* 因此这里采用多次缩小的方法,每次最多缩小50%,在效率和质量上均衡。该方法要优于getScaledImage() (时间上优于)
*
* @param img 要缩放的源图像
* @param targetWidth 目标宽度,以像素为单位
* @param targetHeight 目标高度,以像素为单位
* @param hint 对应下面一种渲染提示
* RenderingHints.KEY_INTERPOLATION (e.g.
* RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR,
* RenderingHints.VALUE_INTERPOLATION_BILINEAR,
* RenderingHints.VALUE_INTERPOLATION_BICUBIC)
* @param progressiveBilinear 如果为真,此方法将使用渐进双线性过滤,该技术提供比通常的一步缩放技术更高的质量
* (仅在缩小比例的情况下有用,其中 targetWidth 或 targetHeight 小于原始尺寸),
* 为假的话就调用默认方法一次缩放
* @return 缩放的图像
*/
public static BufferedImage getFasterScaledInstance(BufferedImage img, int targetWidth, int targetHeight, Object hint, boolean progressiveBilinear) {
int type = (img.getTransparency() == Transparency.OPAQUE) ? BufferedImage.TYPE_INT_RGB : BufferedImage.TYPE_INT_ARGB;
BufferedImage ret = img;
BufferedImage scratchImage = null;
Graphics2D g2 = null;
int w, h;
int prevW = ret.getWidth();
int prevH = ret.getHeight();
if (prevW == targetWidth && prevH == targetHeight) return img; // 已经是这个大小就直接返回
boolean isTranslucent = img.getTransparency() != Transparency.OPAQUE;
if (progressiveBilinear) { //多部缩小,调用多次drawImage()
w = img.getWidth();
h = img.getHeight();
} else { //一次缩放,调用一次drawImage()
w = targetWidth;
h = targetHeight;
}
if (w < targetWidth) {
w = targetWidth;
}
if (h < targetHeight) {
h = targetHeight;
}
do {
if (progressiveBilinear && w > targetWidth) {
w /= 2;
if (w < targetWidth) {
w = targetWidth;
}
}
if (progressiveBilinear && h > targetHeight) {
h /= 2;
if (h < targetHeight) {
h = targetHeight;
}
}
if (scratchImage == null || isTranslucent) {
// 对所有迭代使用单个暂存缓冲区,然后在返回之前复制到最终大小正确的图像
scratchImage = new BufferedImage(w, h, type);
g2 = scratchImage.createGraphics();
}
g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION, hint);
g2.drawImage(ret, 0, 0, w, h, 0, 0, prevW, prevH, null);
prevW = w;
prevH = h;
ret = scratchImage;
} while (w != targetWidth || h != targetHeight);
g2.dispose();
// 如果我们使用的暂存缓冲区大于我们的目标大小,则创建一个大小合适的图像并将结果复制到其中
if (targetWidth != ret.getWidth() || targetHeight != ret.getHeight()) {
scratchImage = new BufferedImage(targetWidth, targetHeight, type);
g2 = scratchImage.createGraphics();
g2.drawImage(ret, 0, 0, null);
g2.dispose();
ret = scratchImage;
}
return ret;
}
/**
* 返回一个具有给定图像反射效果的新图像
*
* @param image 源图像
* @param reflectHeightRatio 反射图像的高度相对于源图像的比例,不知道填什么就试试 0.5f 吧
* @return 高度为源图像两倍的具有反射效果的新图像
*/
public static BufferedImage createReflection(BufferedImage image, float reflectHeightRatio) {
int height = image.getHeight();
if (reflectHeightRatio > 1) reflectHeightRatio = 1;
if (reflectHeightRatio < 0) reflectHeightRatio = 0;
// 创建一个空的,半透明的,高度为原始图像两倍的BufferedImage,以便这个反射可以放到原始图像下方
BufferedImage result = new BufferedImage(image.getWidth(), image.getHeight() * 2, BufferedImage.TYPE_INT_ARGB);
Graphics2D g2 = (Graphics2D) result.getGraphics();
// 绘制原图像
g2.drawImage(image, 0, 0, null);
// 绘制镜像图像
g2.scale(1.0, -1.0); //使用一个负的缩放,设置图像反转
g2.drawImage(image, 0, -height - height, null); //找到对称位置
g2.scale(1.0, -1.0); //设置回正常
// 为了接下来操作容易,把原点设置为副本位置
g2.translate(0, height);
// 利用DstIn的合成规则,这里只需要一个alpha值逐渐减小的渐变即可,源颜色无所谓
GradientPaint mask = new GradientPaint(0, 0, new Color(1f, 1f, 1f, .5f), 0, height * reflectHeightRatio, new Color(1f, 1f, 1f, 0f));
g2.setPaint(mask);
g2.setComposite(AlphaComposite.DstIn);
// 与镜像图像合成为一个只有一半且逐渐透明的静态图像
g2.fillRect(0, 0, image.getWidth(), height);
g2.dispose();
return result;
}
/**
* 绘制圆角图像
*
* @param srcImage 源图像
* @param hint 渲染暗示,从以下选择一个(绘制效果递增):
* VALUE_INTERPOLATION_NEAREST_NEIGHBOR,
* RenderingHints.VALUE_INTERPOLATION_BILINEAR,
* VALUE_INTERPOLATION_BICUBIC,
* 如果为null,默认选择 BILINEAR
* @param angle 圆角大小
* @return 设置圆角并且缩放后的图像
*/
public static BufferedImage createRoundImage(BufferedImage srcImage, Object hint, int angle) {
BufferedImage result = new BufferedImage(srcImage.getWidth(), srcImage.getHeight(), BufferedImage.TYPE_INT_ARGB);
Graphics2D g2 = (Graphics2D) result.getGraphics();
if (hint == null) hint = RenderingHints.VALUE_INTERPOLATION_BILINEAR;
// 绘制圆角图像
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2.fillRoundRect(0, 0, srcImage.getWidth(), srcImage.getHeight(), angle, angle);
g2.setComposite(AlphaComposite.SrcIn);
g2.drawImage(srcImage, 0, 0, null);
g2.dispose();
return result;
}
/**
* 创建具有一定偏移的半透明图像为阴影的圆角图像
*
* @param srcImage 源图像
* @param offset 图像阴影的偏移大小
* @return 具有图像阴影的圆角图像
*/
public static BufferedImage createLayerImage(BufferedImage srcImage, int offset) {
int width = srcImage.getWidth();
int height = srcImage.getHeight();
// 绘制图像与阴影
BufferedImage result = new BufferedImage(width + offset, height + offset, BufferedImage.TYPE_INT_ARGB);
Graphics2D g2 = (Graphics2D) result.getGraphics();
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, .5f));
g2.drawImage(srcImage, offset, offset, width, height, null);
g2.setComposite(((AlphaComposite) g2.getComposite()).derive(1f));
g2.drawImage(srcImage, 0, 0, width, height, null);
g2.dispose();
return result;
}
/**
* 将两张图像合成为新图像,其中上面的图像具体指定不透明度
*
* @param frontImage 前景图
* @param behindImage 背景图
* @param hint 渲染暗示,从以下选择一个(绘制效果递增):
* VALUE_INTERPOLATION_NEAREST_NEIGHBOR,
* RenderingHints.VALUE_INTERPOLATION_BILINEAR,
* VALUE_INTERPOLATION_BICUBIC,
* 如果为null,默认选择 BILINEAR
* @param offsetX 前景图的水平偏移量
* @param offsetY 前景图的垂直偏移量
* @param opacity 前景图的不透明度
* @return 合成后的新图像
*/
public static BufferedImage createLayerImage(BufferedImage frontImage, BufferedImage behindImage, Object hint, int offsetX, int offsetY, float opacity) {
if (hint == null) hint = RenderingHints.VALUE_INTERPOLATION_BILINEAR;
BufferedImage result = new BufferedImage(Math.max(frontImage.getWidth(), behindImage.getWidth()), Math.max(frontImage.getHeight(), behindImage.getHeight()),
BufferedImage.TYPE_INT_ARGB);
Graphics2D g2 = (Graphics2D) result.getGraphics();
g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION, hint);
g2.drawImage(behindImage, 0, 0, null);
g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, opacity));
g2.drawImage(frontImage, offsetX, offsetY, null);
g2.dispose();
return result;
}
}
关于绘制图像的简单方法
使用JLabel显示图像
private static int ICON_WIDTH = 200;
private static int ICON_HEIGHT = 200;
public static void main(String[] args) throws IOException {
// 1.使用ImageIcon
ImageIcon icon = new ImageIcon("images/4.jpg");
icon = ImageUtils.imageIconScale(icon, ICON_WIDTH, ICON_HEIGHT);
JLabel label = new JLabel(icon);
// 2.使用BufferedImage,这里使用的是多步缩放,缩放效果要优于上面的一步缩放
BufferedImage image = ImageIO.read(new File("images/4.jpg"));
image = ImageUtils.getFasterScaledInstance(image, ICON_WIDTH, ICON_HEIGHT, RenderingHints.VALUE_INTERPOLATION_BICUBIC, true);
//JLabel需要ImageIcon对象,还需要创建该对象
JLabel label2 = new JLabel(new ImageIcon(image));
// 开启窗口,把组件添加进去查看效果即可,下面是我创建的工具类,用于测试组件的,不用管
SwingTestUtils.test(label, label2);
}
使用JButton显示图像
跟上面差不多,也是接收一个ImageIcon对象,就使用上面创建好的ImageIcon对象和BufferedImage对象了
JButton button = new JButton(icon);
// 设置外边距
button.setMargin(new Insets(0,0,0,0));
// 移除边框
// button.setBorder(null);
JButton button2 = new JButton(new ImageIcon(image));
// 测试组件
SwingTestUtils.test(button, button2);
效果如下,右边是缩放后的图像,可以看到按钮默认有外边距的,左边是设置外边距后的样子,但外面还有一层边框
使用自定义组件显示图像
这里只演示一个返回具体反射效果的方法
class MyComponent extends JComponent {
BufferedImage image; //要显示的图像
public MyComponent(String url, int width, int height) throws IOException {
// 设置组件大小,要为镜像图像留一些高度,可以根据createReflection第二个参数计算所需最小高度
setPreferredSize(new Dimension(width, height * 2));
// 读取一个图像
image = ImageIO.read(new File(url));
// 先将图像进行缩放,第三个参数可以参考该方法注释
image = ImageUtils.getFasterScaledInstance(image, width, height, RenderingHints.VALUE_INTERPOLATION_BICUBIC, true);
image = ImageUtils.createReflection(image, 0.7f);
}
@Override
protected void paintComponent(Graphics g) {
// 简单绘制图像
g.drawImage(image, 0, 0, null);
}
public static void main(String[] args) throws IOException {
// 普通创建窗口添加该组件即可,不必管这个工具类
SwingTestUtils.test(new MyComponent("images/1.jpg", 200, 200));
}
}
效果预览:
放上综合测试
可能有点难理解,看不懂的话用上面的三个方法就够了
import javax.imageio.ImageIO;
import javax.swing.*;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
public class Test extends JFrame {
private static BufferedImage image;
static {
try {
image = ImageUtils.getFasterScaledInstance(ImageIO.read(new File("images/4.jpg")), 200, 200, RenderingHints.VALUE_INTERPOLATION_BICUBIC, true);
} catch (IOException e) {
e.printStackTrace();
}
}
public Test() throws HeadlessException {
setSize(800, 500);
setLocationRelativeTo(null);
setDefaultCloseOperation(EXIT_ON_CLOSE);
setLayout(new FlowLayout());
add(new MyComponent("默认缩放") {
@Override
protected void paintComponent(Graphics g) {
try {
g.drawImage(ImageIO.read(new File("images/4.jpg")), 0, 0, 200, 200, null);
} catch (IOException e) {
e.printStackTrace();
}
super.paintComponent(g);
}
});
add(new MyComponent("更高效的图片缩放方法") {
@Override
protected void paintComponent(Graphics g) {
Graphics2D g2 = (Graphics2D) g;
g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC);
g.drawImage(image, 0, 0, null);
super.paintComponent(g);
}
});
add(new MyComponent("图像反射") {
@Override
protected void paintComponent(Graphics g) {
try {
Graphics2D g2 = (Graphics2D) g;
BufferedImage image = ImageUtils.createReflection(ImageIO.read(new File("images/按钮.png")), .7f);
g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC);
g.drawImage(image, 0, 0, null);
} catch (IOException e) {
e.printStackTrace();
}
super.paintComponent(g);
}
});
add(new MyComponent("绘制圆角图像") {
@Override
protected void paintComponent(Graphics g) {
Graphics2D g2 = (Graphics2D) g;
BufferedImage re = ImageUtils.createRoundImage(image, null, 20);
g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC);
g.drawImage(re, 0, 0, null);
super.paintComponent(g);
}
});
add(new MyComponent("绘制图像阴影") {
@Override
protected void paintComponent(Graphics g) {
Graphics2D g2 = (Graphics2D) g;
try {
BufferedImage sr = ImageUtils.createLayerImage(ImageUtils.getFasterScaledInstance(ImageIO.read(new File("images/4.jpg")), 180, 180, RenderingHints.VALUE_INTERPOLATION_BICUBIC, true), 15);
g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC);
g.drawImage(sr, 0, 0, null);
} catch (IOException e) {
e.printStackTrace();
}
super.paintComponent(g);
}
});
add(new MyComponent("合成两张图像") {
@Override
protected void paintComponent(Graphics g) {
Graphics2D g2 = (Graphics2D) g;
try {
BufferedImage sr = ImageUtils.createLayerImage(ImageIO.read(new File("images/按钮.png")), ImageUtils.getFasterScaledInstance(ImageIO.read(new File("images/4.jpg")), 200, 200, RenderingHints.VALUE_INTERPOLATION_BICUBIC, true), null, 10, 10, .5f);
g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC);
g.drawImage(sr, 0, 0, null);
} catch (IOException e) {
e.printStackTrace();
}
super.paintComponent(g);
}
});
}
public static void main(String[] args) {
EventQueue.invokeLater(() -> new Test().setVisible(true));
}
private static class MyComponent extends JComponent {
private String title;
public static int WIDTH = 200;
public static int HEIGHT = 200;
public MyComponent(String title) {
this.title = title;
setPreferredSize(new Dimension(WIDTH, HEIGHT + 20));
}
@Override
protected void paintComponent(Graphics g) {
Graphics2D g2 = (Graphics2D) g;
g2.setFont(new Font("宋体", Font.BOLD, 17));
g2.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
g2.drawString(title, 0, HEIGHT + 18);
g2.drawRect(0, 0, getWidth(), getHeight());
}
}
}
效果就是第一张图里的那样
用于测试组件工具类SwingTestUtils
把之前用的测试类也放在这里
import javax.swing.*;
import java.awt.*;
import java.util.List;
/**
* 组件测试的工具类,用于测试组件功能
*/
public class SwingTestUtils {
public static int WIDTH = 500;
public static int HEIGHT = 400;
public static void test(JComponent component) {
test(List.of(component));
}
/**
* 测试重写后的paint()相关的方法,为传入组件设置好大小
*/
public static void testPaintMethod(JComponent component) {
component.setPreferredSize(new Dimension(WIDTH, HEIGHT));
test(List.of(component));
}
public static void test(JComponent component1, JComponent component2) {
test(List.of(component1, component2));
}
public static void test(JComponent component1, JComponent component2, JComponent component3) {
test(List.of(component1, component2, component3));
}
public static void test(JComponent[] components) {
test(List.of(components));
}
public static void test(List<JComponent> components) {
// 这里加了个检测,检测当前线程是否是事件分发线程
if (SwingUtilities.isEventDispatchThread()) {
invoke(components);
} else {
EventQueue.invokeLater(() -> invoke(components));
}
}
private static void invoke(List<JComponent> components) {
JFrame frame = new JFrame("测试组件");
// 设置窗口居中
Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
frame.setBounds((screenSize.width - WIDTH) / 2, (screenSize.height - HEIGHT) / 2, WIDTH + 8 + 8, HEIGHT + 32 + 8);
frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
frame.setLayout(new FlowLayout());
components.forEach(frame::add);
frame.setVisible(true);
}
}
方法思路来源
《java动画、图形和极富 客户端效果开发》
好几年前的书了,但技术尚未过时,能重新颠覆你对 Swing 的看法,看了之后跟你说用Swing仿造一个PS你都会相信