Java像素图像处理
在学会了之前的画板功能之后我们就更进一步进入图像处理部分下面来学习一下纯像素级图像处理项目不使用任何高级滤镜工具全部通过遍历图片每一个像素、拆分 RGB、重新计算颜色值的方式实现图片特效。让我们来看一下具体的内容实现功能包括原图还原图片灰度化图片二值化黑白效果图片马赛克模糊蓝调滤镜黄昏暖色滤镜反片颜色反转。以上所有代码都应写在监听器中通过点击按钮来实现对应功能以下我们只看监听器内代码。先实现监听器接口并定义变量:Public class ImageListener implements ActionListener(){ Graphics g; int w; int h; int[][] data;接下来我们写对应的方法一原图还原先了解一下几个基础概念IO Input输入 Output输出也就是程序和外部设备读写数据的操作读文件、写文件、读取图片、网络传输、读取键盘输入都属于 IO 操作。IOException全称 Input/Output Exception中文输入输出异常。当程序读写文件、图片、网络等外部资源失败时Java 就会抛出这个异常。接着在进行原图还原之前我们首先要新建一个方法把原图每个点的像素数据存到data数组里面我们要认识一个新的类BufferedImage类BufferedImage img ImageIO.read(new File(path));拆解一下这行代码① new File(path)File 是 java.io 自带类代表磁盘上的文件 / 文件夹路径path Image/1.png就是项目里图片的相对路径作用根据路径定位到硬盘里的 1.png 图片文件生成文件对象。② ImageIO.read(文件对象)ImageIOJava AWT 内置图像工具类专门读写图片png/jpg/bmp 等.read()静态方法接收一个文件从磁盘读取图片加载到内存读取完成后返回一个 BufferedImage 图像对象重点这个方法读取文件会抛出 IOException所以必须放在 try{} 里。③ BufferedImage imgBufferedImage内存图像缓冲区就是存放在内存里的图片它封装了整张图片的所有信息宽度、高度、每一个像素的 ARGB 颜色img 是变量名用来接收加载好的内存图片后续写的 img.getWidth()、img.getRGB(i,j) 全是调用这个内存图片对象的方法。总结这行代码就是根据路径找到磁盘上的图片文件把整张图片读取到内存生成内存图像对象用变量 img 接收这个图片。接着我们来了解一下try-catch用法Java 程序运行时可能出现异常错误比如文件找不到、读取失败、空指针。如果不处理异常程序会直接崩溃退出try-catch 用来捕获、处理异常保证程序不会直接卡死还能自定义报错逻辑。、1. try 代码块把有风险、会抛出受检异常的代码放这里比如上面的 ImageIO.read(new File(path))读取文件时可能出现文件不存在图片损坏无法解析路径写错没有文件读取权限这类 IO 操作会抛出 IOException必须放进 try否则编译器直接报错。2. catch 捕获块IOException e指定要捕获的异常类型e 是异常对象存储错误详情一旦 try 内部代码报错程序立刻跳转到对应 catch 块不会终止程序代码格式如下try { // 【可能出错、会抛出异常的代码】 } catch (异常类型 变量名) { // 【捕获到异常后执行的补救/报错代码】 } // 可选拓展finally无论是否报错都会执行 finally { // 释放资源、关闭文件等操作 }我们直接看catch部分catch (IOException e) { throw new RuntimeException(e); }IOException是专门用来封装程序读写外部资源失败的错误信息的类这两行代码作用为一旦读取图片失败程序跳到这个 catch 块把捕获到的 IO 异常包装成运行时异常重新抛出以上了解完之后我们就可以用双层for循环把每一个像素点RGB存入数组int w img.getWidth(); int h img.getHeight(); int[][] data new int[w][h]; for(int i 0;iW;i){ for(int j 0;jh;j){ data[w][h] img.getRGB(i,j);再加上try-catch完整代码如下public int[][] getImageData() { String path Image/1.png; try { BufferedImage img ImageIO.read(new File(path)); w img.getWidth(); h img.getHeight(); int[][] data new int[w][h]; for (int i 0; i w; i) { for (int j 0; j h; j) { data[i][j] img.getRGB(i, j); } } return data; } catch (IOException e) { throw new RuntimeException(e); } }data数组建立完成之后我们就可以通过遍历data数组来进行原图还原了public void draw() { for (int i 0; i w; i) { for (int j 0; j h; j) { int pix data[i][j]; Color c1 new Color(pix); g.setColor(c1); g.drawRect(i 50, j 100, 1, 1); } } }效果二灰度化在进行灰度化之前先来认识一下标准 ARGB32 位像素getRGB()返回的 int总长度32 位二进制整数4 个通道各占 8 bit1 字节分段分配从高位到低位A 透明度第 31~24 位 → 8 位R 红色第 23~16 位 → 8 位G 绿色第 15~8 位 → 8 位B 蓝色第 7~0 位 → 8 位数值范围每个通道 8 位二进制取值范围0 ~ 255R0 无红色R255 红色拉满G0 无绿色G255 绿色拉满B0 无蓝色B255 蓝色拉满A0 完全透明A255 完全不透明暗→亮0→255最大数字11111111十进制为255灰度化的本质就是取平均三色数值完全相等画面无彩色只有黑白灰代码如下public void draw1() { for (int i 0; i w; i) { for (int j 0; j h; j) { int pix data[i][j]; Color c2 new Color(pix); int red c2.getRed(); int green c2.getGreen(); int blue c2.getBlue(); int ave (red green blue) / 3; Color c3 new Color(ave, ave, ave); g.setColor(c3); g.drawRect(i 400, j 100, 1, 1); } } }效果三二值化图像里只有两种颜色纯黑 (0)、纯白 (255)不存在中间灰色只有 0 和 255 两个数值所以叫 “二值”完整两步流程第一步彩色图 → 灰度图先计算 RGB 平均值得到gray0 最黑255 最亮把彩色转成单通道亮度。必须先进行灰度化彩色有 3 个通道无法直接判断明暗必须合并成单通道亮度第二步阈值分割设置一个阈值分界线这里阈值 1270~255 正中间规则 1像素灰度 127 → 归为白色规则 2像素灰度 ≤ 127 → 归为黑色所有中间灰度全部被强制抹除整张图片只剩下黑白两种色块。代码如下public void draw2() { for (int i 0; i w; i) { for (int j 0; j h; j) { int pix data[i][j]; Color c2 new Color(pix); int red c2.getRed(); int green c2.getGreen(); int blue c2.getBlue(); int gray (red green blue) / 3; if (gray 127) { Color c3 new Color(255, 255, 255); g.setColor(c3); g.drawRect(i 400, j 100, 1, 1); } else { Color c3 new Color(0, 0, 0); g.setColor(c3); g.drawRect(i 400, j 100, 1, 1); } } } }效果:四马赛克步骤如下分块把原图切割成无数个大小相等的正方形小块以 5×5 像素一块为例取代表色只拿每一块左上角的像素颜色作为这个方块唯一颜色整块同色填充把方块里所有位置全部画成这一个颜色代码如下public void draw3() { int m 5; for (int i 0; i w; i i m) { for (int j 0; j h; j j m) { int pix data[i][j]; Color c new Color(pix); for (int k 0; k m; k) { for (int l 0; l m; l) { g.setColor(c); g.drawRect(i k 400, j l 100, 1, 1); } } } } }效果五蓝调增加蓝色效果削弱红绿原理与上述基本相同代码如下public void draw4() { for (int i 0; i w; i) { for (int j 0; j h; j) { int pix data[i][j]; Color c1 new Color(pix); int red c1.getRed(); int green c1.getGreen(); int blue c1.getBlue(); red red / 2; green green / 2; blue blue 60; if (blue 255) { blue 255; } Color c2 new Color(red, green, blue); g.setColor(c2); g.fillRect(i 400, j 100, 1, 1); } } }效果六黄昏增强红绿色效果削弱蓝色与蓝调类似代码如下public void draw5() { for (int i 0; i w; i) { for (int j 0; j h; j) { int pix data[i][j]; Color c new Color(pix); int red c.getRed(); int green c.getGreen(); int blue c.getBlue(); red red 60; if (red 255) { red 255; } green green 30; if (green 255) { green 255; } blue blue / 3; Color c2 new Color(red, green, blue); g.setColor(c2); g.fillRect(i 400, j 100, 1, 1); } } }效果六反片用通道最大值 255 减去原始亮度得到互补色例如原始红色 0无红→ 255 - 0 255纯红原始红色 255纯红→ 255 - 255 0无红亮变暗暗变亮每个颜色通道全部翻转。代码如下public void draw6() { for (int i 0; i w; i) { for (int j 0; j h; j) { int pix data[i][j]; Color c new Color(pix); int red c.getRed(); int green c.getGreen(); int blue c.getBlue(); red 255 - red; green 255 - green; blue 255 - blue; Color c2 new Color(red, green, blue); g.setColor(c2); g.fillRect(i 400, j 100, 1, 1); } } }效果最后重写actionPerform方法public void actionPerformed(ActionEvent e) { String key e.getActionCommand(); System.out.println(key); if (key.equals(原图)) { data getImageData(); draw(); } else if (key.equals(灰度化)) { draw1(); } else if (key.equals(二值化)) { draw2(); } else if (key.equals(马赛克)) { draw3(); } else if (key.equals(蓝调)) { draw4(); } else if (key.equals(黄昏)) { draw5(); } else if (key.equals(反片)) { draw6(); } }注意有返回值要加上datagetImageData把 getImageData() 读取到的全新原图像素数组赋值给全局变量 data覆盖旧数据从硬盘加载原始图片像素将返回的像素数组存入全局 datadraw() 读取更新后的 data才能画出原图。