图像原理
对计算机中的一张图片,我们可以将其看成一个宽w*高h的二维数组,即一张图片=int [w][h],在这个二维数组中每个位置对应着一个int值,它们就是这张图片中每个点的像素值,那么我们要对这张图像做处理,实际上就是对这张图片的二维数组进行操作。下面我们首先来实现将原图显示在界面上吧。
实现原图显示
import java.awt.Color;
import java.awt.Graphics;
import java.awt.image.BufferedImage;
import java.io.File;
import javax.imageio.ImageIO;
import javax.swing.JFrame;
public class Image {
public static void main(String[] args) {
Image image=new Image();
image.UI();
}
public void UI() {
JFrame frame=new JFrame("图像处理");
frame.setSize(1000,1000);
frame.setLocationRelativeTo(null);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
//获取界面的画笔,要放在界面可视之后
Graphics g=frame.getGraphics();
showImage(g);
}
public void showImage(Graphics g) {
//这里我写的是我放在同包下的一张图片的路径
//小伙伴们可以根据实际修改
File file=new File("src/img/1.jpg");
BufferedImage bi=null;
try {
bi=ImageIO.read(file);
} catch (Exception e) {
e.printStackTrace();
}
int w=bi.getWidth();
int h=bi.getHeight();
int [][]data=new int[w][h];
for(int i=0;i<data.length;i++) {
for(int j=0;j<data[i].length;j++) {
int v=bi.getRGB(i, j);
data[i][j]=v;
Color color=new Color(v);
g.setColor(color);
g.drawLine(i, j, i, j);
}
}
}
}
图像基本效果
下面的图像效果介绍,我们尝试去修改上面showImage方法中的循环就可以实现不同的效果啦。
灰度1
for (int i = 0; i < data.length; i++) {
for (int j = 0; j < data[i].length; j++) {
Color c = new Color(data[i][j]);
int r = c.getRed();
int gr = c.getGreen();
int b = c.getBlue();
int change = (r + gr + b) / 3;
c = new Color(change, change, change);
g.setColor(c);
g.drawLine(i, j, i, j);
}
}
灰度2
for (int i = 0; i < data.length; i++) {
for (int j = 0; j < data[i].length; j++) {
Color c = new Color(data[i][j]);
int r = c.getRed();
int gr = c.getGreen();
int b = c.getBlue();
int change = (Math.max(r, Math.max(gr, b)) + Math.min(r, Math.min(gr, b))) / 2;
c = new Color(change, change, change);
bg.setColor(c);
bg.drawLine(i, j, i, j);
}
}
珠纹化
for (int i = 0; i < data.length; i += 5) {
for (int j = 0; j < data[i].length; j += 5) {
Color c = new Color(data[i][j]);
g.setColor(c);
g.fillOval(i, j, 4, 4);
}
}
油画
for (int i = 0; i < data.length; i++) {
for (int j = 0; j < data[i].length; j++) {
Color c = new Color(data[i][j]);
g.setColor(c);
Random rand = new Random();
int k = rand.nextInt(10) + 1;
g.fillOval(i, j, k, k);
}
}
熔铸滤镜
for (int i = 0; i < data.length; i++) {
for (int j = 0; j < data[i].length; j++) {
Color c = new Color(data[i][j]);
int r = c.getRed();
int gr = c.getGreen();
int b = c.getBlue();
r = r * 128 / (gr + b + 1);
gr = gr * 128 / (r + b + 1);
b = b * 128 / (r + gr + 1);
if (r > 255)
r = c.getRed();
if (gr > 255)
gr = c.getGreen();
if (b > 255)
b = c.getBlue();
c = new Color(r, gr, b);
g.setColor(c);
g.drawLine(i, j, i, j);
}
}
去色滤镜
这个效果还不是很理想,如果要做得强大的话,还是要花费时间去做的。
for (int i = 0; i < data.length; i++) {
for (int j = 0; j < data[i].length; j++) {
Color c = new Color(data[i][j]);
int gr = c.getGreen();
if (gr < 52) {
g.setColor(c);
g.drawLine(i, j, i, j);
}
}
}
马赛克滤镜
for (int i = 0; i < data.length; i += 5) {
for (int j = 0; j < data[i].length; j += 5) {
Color c = new Color(data[i][j]);
g.setColor(c);
g.fillRect(i, j, 8, 8);
}
}
连环画滤镜
for (int i = 0; i < data.length; i++) {
for (int j = 0; j < data[i].length; j++) {
Color c = new Color(data[i][j]);
int r = c.getRed();
int gr = c.getGreen();
int b = c.getBlue();
r = Math.abs(gr - b + gr + r) * r / 256;
gr = Math.abs(b - gr + b + r) * r / 256;
b = Math.abs(b - gr + b + r) * gr / 256;
if (r < 0 || r > 255)
r = c.getRed();
if (gr < 0 || gr > 255)
gr = c.getGreen();
if (b < 0 || b > 255)
b = c.getBlue();
c = new Color(r, gr, b);
g.setColor(c);
g.drawLine(i, j, i, j);
}
}
怀旧滤镜
for (int i = 0; i < data.length; i++) {
for (int j = 0; j < data[i].length; j++) {
Color c = new Color(data[i][j]);
int r = c.getRed();
int gr = c.getGreen();
int b = c.getBlue();
// 处理溢出
r = (int) (0.393 * r + 0.769 * gr + 0.189 * b);
gr = (int) (0.349 * r + 0.686 * gr + 0.168 * b);
b = (int) (0.272 * r + 0.534 * gr + 0.131 * b);
if (r > 255)
r = c.getRed();
if (gr > 255)
gr = c.getGreen();
if (b > 255)
b = c.getBlue();
c = new Color(r, gr, b);
g.setColor(c);
g.drawLine(i, j, i, j);
}
}
黑白滤镜
for (int i = 0; i < data.length; i++) {
for (int j = 0; j < data[i].length; j++) {
Color c = new Color(data[i][j]);
int r = c.getRed();
int gr = c.getGreen();
int b = c.getBlue();
int val = (r + gr + b) / 3;
if (val > 80) {
g.setColor(Color.white);
} else {
g.setColor(Color.black);
}
g.drawLine(i, j, i, j);
}
}
褐色滤镜
for (int i = 0; i < data.length; i++) {
for (int j = 0; j < data[i].length; j++) {
Color c = new Color(data[i][j]);
int r = c.getRed();
int gr = c.getGreen();
int b = c.getBlue();
r = (int) (r * 0.393 + gr * 0.769 + b * 0.189);
gr = (int) (r * 0.349 + gr * 0.686 + b * 0.168);
b = (int) (r * 0.272 + gr * 0.534 + b * 0.131);
if (r < 0 || r > 255)
r = c.getRed();
if (gr < 0 || gr > 255)
gr = c.getGreen();
if (b < 0 || b > 255)
b = c.getBlue();
c = new Color(r, gr, b);
g.setColor(c);
g.drawLine(i, j, i, j);
}
}
图片合并
public void showImage(Graphics g) {
// 选取两张照片
File file1 = new File("src/img/1.jpg");
File file2 = new File("src/img/2.jpg");
BufferedImage bi = null;
BufferedImage bii = null;
try {
bi = ImageIO.read(file1);
bii = ImageIO.read(file2);
} catch (Exception e) {
e.printStackTrace();
}
// 取其宽高的最小值,防止数组越界
int w = Math.min(bi.getWidth(), bii.getWidth());
int h = Math.min(bi.getHeight(), bii.getHeight());
// 创建一块与打开的图片一样大小的缓冲区
bi_img = new BufferedImage(w, h, BufferedImage.TYPE_INT_RGB);
// 获取缓冲区的画笔
bg = bi_img.getGraphics();
for (int i = 0; i < w; i++) {
for (int j = 0; j < h; j++) {
int v1 = bi.getRGB(i, j);
int v2 = bii.getRGB(i, j);
Color color1 = new Color(v1);
Color color2=new Color(v2);
// 在缓冲区中绘制图像的每个像素点
int r=(int)(color1.getRed()*0.7+color2.getRed()*0.3);
int gr=(int)(color1.getGreen()*0.3+color2.getGreen()*0.7);
int b=(int)(color1.getBlue()*0.3+color2.getBlue()*0.7);
Color color=new Color(r,gr,b);
bg.setColor(color);
bg.drawLine(i, j, i, j);
}
}
// 缓冲区绘制完成后,用界面的画笔一次性将图像画出来
g.drawImage(bi_img, 0, 0, bi_img.getWidth(), bi_img.getHeight(), null);
}
负片
for (int i = 0; i < data.length; i++) {
for (int j = 0; j < data[i].length; j++) {
Color c = new Color(-data[i][j]);
g.setColor(c);
g.drawLine(i, j, i, j);
}
}
反向滤镜
for (int i = 0; i < data.length; i++) {
for (int j = 0; j < data[i].length; j++) {
Color c = new Color(data[i][j]);
int r = c.getRed();
int gr = c.getGreen();
int b = c.getBlue();
r = 255 - r;
gr = 255 - gr;
b = 255 - b;
c = new Color(r, gr, b);
g.setColor(c);
g.drawLine(i, j, i, j);
}
}
冰冻滤镜
for (int i = 0; i < data.length; i++) {
for (int j = 0; j < data[i].length; j++) {
Color c = new Color(data[i][j]);
int r = c.getRed();
int gr = c.getGreen();
int b = c.getBlue();
r = (r - gr - b) * 3 / 2;
gr = (gr - r - b) * 3 / 2;
b = (b - r - gr) * 3 / 2;
if (r < 0 || r > 255)
r = c.getRed();
if (gr < 0 || gr > 255)
gr = c.getGreen();
if (b < 0 || b > 255)
b = c.getBlue();
c = new Color(r, gr, b);
g.setColor(c);
g.drawLine(i, j, i, j);
}
}
单色滤镜
红色
for (int i = 0; i < data.length; i++) {
for (int j = 0; j < data[i].length; j++) {
Color color = new Color(data[i][j]);
int r = color.getRed();
color = new Color(r, 0, 0);
g.setColor(color);
g.drawLine(i, j, i, j);
}
}
绿色
for (int i = 0; i < data.length; i++) {
for (int j = 0; j < data[i].length; j++) {
Color color = new Color(data[i][j]);
int gr = color.getGreen();
color = new Color(0, gr, 0);
g.setColor(color);
g.drawLine(i, j, i, j);
}
}
蓝色
for (int i = 0; i < data.length; i++) {
for (int j = 0; j < data[i].length; j++) {
Color color = new Color(data[i][j]);
int b = color.getBlue();
color = new Color(0, 0, b);
g.setColor(color);
g.drawLine(i, j, i, j);
}
}
优化图像显示
图像缓冲区
计算机当中设备的速度大体上可以分为CPU、内存、IO设备三类(其实CPU与内存之间还有cache,但在这里并不影响解释这个问题),它们的速度由快到慢,也就是说IO设备在计算机中属于低速设备,与IO设备进行交互的话将会花费大量时间。而我们上面的写法都是将图像中的每个像素点依次去画在界面上,当图像过大的时候,显示在界面上就会需要等待一定的响应时间。但是现在我们首先在缓冲区中将图像绘制下来,在缓冲区中图像绘制完成以后,再一次性将图像在界面上画出来就好了,那么原来需要与IO设备交互w*h次,现在就只需要交互一次就够了,性能上就会有显著提升。我们还是以显示原图为例来演示缓冲区的使用。
使用缓冲区显示原图
public void showImage(Graphics g) {
//这里我写的是我放在同包下的一张图片的路径
//小伙伴们可以根据实际修改
File file=new File("src/img/1.jpg");
BufferedImage bi=null;
try {
bi=ImageIO.read(file);
} catch (Exception e) {
e.printStackTrace();
}
int w=bi.getWidth();
int h=bi.getHeight();
//创建一块与打开的图片一样大小的缓冲区
BufferedImage bi_img=new BufferedImage(w, h, BufferedImage.TYPE_INT_RGB);
//获取缓冲区的画笔
Graphics bg=bi_img.getGraphics();
int [][]data=new int[w][h];
for(int i=0;i<data.length;i++) {
for(int j=0;j<data[i].length;j++) {
int v=bi.getRGB(i, j);
data[i][j]=v;
Color color=new Color(v);
//在缓冲区中绘制图像的每个像素点
bg.setColor(color);
bg.drawLine(i, j, i, j);
}
}
//缓冲区绘制完成后,用界面的画笔一次性将图像画出来
g.drawImage(bi_img, 0, 0, bi_img.getWidth(), bi_img.getHeight(), null);
}
文件选择器
我们的代码写的图片路径都是直接固定的,那在不同的电脑上可能不会存在这样的路径或者图片,那么我们这时候可以提供一个对话框来供用户选择图片,然后对选择的图片做处理这样子。
文件选择器代码示例
我们通过下面的测试openFile方法已经可以成功返回我们在电脑上选择图片的绝对路径,那么我们只需要对上面的showImage方法的参数添加一个String类的参数,然后将new File的路径改成这个String类的参数就可以了。
public class Image {
public static void main(String[] args) {
Image image=new Image();
System.out.println(image.openFile());
}
public String openFile() {
JFileChooser chooser=new JFileChooser();
// 文件选择器添加过滤
FileNameExtensionFilter filter=new FileNameExtensionFilter("Image files", "jpg");
chooser.setFileFilter(filter);
// 显示(打开文件)的选择器
int returnVal=chooser.showOpenDialog(null);
// 判断返回值
if(returnVal==JFileChooser.APPROVE_OPTION) {
// 获取选择的文件
File file=chooser.getSelectedFile();
// 返回文件的绝对路径
return file.getAbsolutePath();
}
// 没有选择文件的情况
return "";
}
图像重绘
最后的一个小优化就是,当我们拖动界面或者将窗体改变大小时,界面上的图像就会消失,那么这时候我们就要继承JFrame并且重写paint方法,让窗体发生改变时,我们的界面重新绘制图像。paint方法并不需要我们定义何时调用,我们只需要在里面写代码规定窗体改变时(会自动调用paint方法)需要重绘的内容即可。所以我们的UI方法也有些许改变,而且为了方便我的重绘操作,我将缓冲区与缓冲区的画笔设置成了全局的变量。所以,我这里就再贴了一次有点相似的代码。
图像重绘代码示例
import java.awt.Color;
import java.awt.Graphics;
import java.awt.image.BufferedImage;
import java.io.File;
import javax.imageio.ImageIO;
import javax.swing.JFrame;
public class Image extends JFrame{
BufferedImage bi_img=null;
Graphics bg=null;
public static void main(String[] args) {
Image image=new Image();
image.UI();
}
public void UI() {
this.setTitle("图像处理");
this.setSize(1000,1000);
this.setLocationRelativeTo(null);
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
this.setVisible(true);
//获取界面的画笔,要放在界面可视之后
Graphics g=this.getGraphics();
showImage(g);
}
public void showImage(Graphics g) {
//这里我写的是我放在同包下的一张图片的路径
//小伙伴们可以根据实际修改
File file=new File("src/img/1.jpg");
BufferedImage bi=null;
try {
bi=ImageIO.read(file);
} catch (Exception e) {
e.printStackTrace();
}
int w=bi.getWidth();
int h=bi.getHeight();
//创建一块与打开的图片一样大小的缓冲区
bi_img=new BufferedImage(w, h, BufferedImage.TYPE_INT_RGB);
//获取缓冲区的画笔
bg=bi_img.getGraphics();
int [][]data=new int[w][h];
for(int i=0;i<data.length;i++) {
for(int j=0;j<data[i].length;j++) {
int v=bi.getRGB(i, j);
data[i][j]=v;
Color color=new Color(v);
//在缓冲区中绘制图像的每个像素点
bg.setColor(color);
bg.drawLine(i, j, i, j);
}
}
//缓冲区绘制完成后,用界面的画笔一次性将图像画出来
g.drawImage(bi_img, 0, 0, bi_img.getWidth(), bi_img.getHeight(), null);
}
public void paint(Graphics g) {
super.paint(g);
if(bi_img!=null)g.drawImage(bi_img, 0, 0, bi_img.getWidth(), bi_img.getHeight(), null);
}
}