处理BufferedImage的工具类(Swing用)

先上预览图

在这里插入图片描述

上工具类代码

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你都会相信

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值