图像原理

对计算机中的一张图片,我们可以将其看成一个宽w*高h的二维数组,即一张图片=int [w][h],在这个二维数组中每个位置对应着一个int值,它们就是这张图片中每个点的像素值,那么我们要对这张图像做处理,实际上就是对这张图片的二维数组进行操作。下面我们首先来实现将原图显示在界面上吧。

实现原图显示

Java 镜像图片 java的照片_Image

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

Java 镜像图片 java的照片_Java 镜像图片_02

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

Java 镜像图片 java的照片_java_03

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);
			}
		}

珠纹化

Java 镜像图片 java的照片_滤镜_04

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);
			}
		}

油画

Java 镜像图片 java的照片_i++_05

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);
			}
		}

熔铸滤镜

Java 镜像图片 java的照片_滤镜_06

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);
			}
		}

去色滤镜

这个效果还不是很理想,如果要做得强大的话,还是要花费时间去做的。

Java 镜像图片 java的照片_Java 镜像图片_07

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);
				}
			}
		}

马赛克滤镜

Java 镜像图片 java的照片_滤镜_08

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);
			}
		}

连环画滤镜

Java 镜像图片 java的照片_滤镜_09

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);
			}
		}

怀旧滤镜

Java 镜像图片 java的照片_Java 镜像图片_10

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);
			}
		}

黑白滤镜

Java 镜像图片 java的照片_Java 镜像图片_11

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);
			}
		}

褐色滤镜

Java 镜像图片 java的照片_java_12

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);
			}
		}

图片合并

Java 镜像图片 java的照片_i++_13

Java 镜像图片 java的照片_java_14

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);
	}

负片

Java 镜像图片 java的照片_i++_15

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);
			}
		}

反向滤镜

Java 镜像图片 java的照片_java_16

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);
			}
		}

冰冻滤镜

Java 镜像图片 java的照片_Java 镜像图片_17

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);
			}
		}

单色滤镜

Java 镜像图片 java的照片_Image_18

红色
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);
			}
		}
绿色

Java 镜像图片 java的照片_Image_19

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);
			}
		}
蓝色

Java 镜像图片 java的照片_滤镜_20

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);
	}

}