初学java,想做一个简易的美颜相机,对图像处理的一些方法进行了相应的学习。主要包括图像的原理、图像的数据处理、位运算、滤镜效果、图片读写等。
每一张图像,其实都是由一个个的带有颜色的点组成的矩阵,也就是我们常说的像素点矩阵,同时横纵坐标像素点的个数乘积就是图像的分辨率。
每一个像素点的颜色,都是有相应的编号的,一般的存储格式是RGB和ARGB格式。RGB格式:RGB其实就是三原色,Red、Green、Blue,每一种原色的编号范围是0-255,也就是存储在一个字节(8位2进制)中。Red从0到255颜色会越来越“红”,红的程度会加深。
举个例子来说,对于RGB: 123, 200,13 来说,这个颜色是由Red123,Green200,Blue13这三种颜色合成的。
所以我们很容易知道,RGB是占三个字节,可以存储在整型变量(四个字节)中,并且存储在低24位,那么就会出现高8位没有使用的情况。ARGB格式就是把高8位也利用起来,用来存其他的信息例如照片拍摄日期等等。
初始化生成一个窗体,然后通过随机数随机生成一张RGB格式的图片。通过位移运算计算RGB格式里red、green、blue的值(位移运算在2.3会讲解)。对于滤镜等操作,就可以通过调节red、green、blue的值来实现,比如说red=red/2,就能实现图片红色降度,也就是照片的红色不会太鲜艳。
我们常常也会听说灰度图片,就是照片会变成灰色,这种照片的每一个像素点的RGB值均满足red=green=blue,我们可以调整三原色的占比来确定灰阶(详见代码)。
二维码:有了灰阶,也就是对每一个像素值都有唯一标准了。我们可以确定一个分界值a,当算出的灰度值大于a,就用白色来表示;若小于a,就用黑色来表示,并且使用fiilRect方法的时候,把像素点的宽和高调大一点,我们就可以得到这张图片的二维码了,也就是说,这个二维码储存了我这张图片的数据。
import javax.swing.*;
import java.awt.*;
import java.util.Random;
import javax.swing.JFrame;
public class ImageUI extends JFrame {
//初始化一个窗体
public void initUI(){
setTitle("图像编程");
setSize(1000,1000);
setVisible(true);
setDefaultCloseOperation(EXIT_ON_CLOSE);
}
/*
* 窗体出现在屏幕上时调用
* 绘制窗体本身
* 在窗体上绘制像素点
* @param g :图形对象 可以来绘制图形
* Graphics 图形
* g.fillRect(x,y,w,h);
*/
public void paint(Graphics g){
super.paint(g);
//g.fillRect(100,100,10,10);
int[][] imgarr=new int[300][300];
// 随机创建颜色矩阵
Random random = new Random();
for (int i = 0; i < 300; i++) {
for (int j = 0; j < 300; j++) {
int colorValue = random.nextInt(256*256*256);
imgarr[i][j]=colorValue;
Color color = new Color(colorValue);
g.setColor(color);
g.fillRect(50+i,50+j,1,1);
}
}
for (int i = 0; i <imgarr.length; i+=10) {
for (int j = 0; j < imgarr[i].length; j+=10) {
int value = imgarr[i][j];
int red =(value>>16)&0xFF;
int green =(value>>8)&0xFF;
int blue =(value>>0)&0xFF;
// 灰度 r=g=b
// 有了灰阶 一张照片所有的像素值都具有同样的标准了
int gray =(int)(red*0.36+green*0.29+blue*0.35);
if(gray>128){
g.setColor(Color.white);
}
else{
g.setColor(Color.black);
}
g.fillRect(400+i,50+j,10,10);
}
}
}
// 启动程序
public static void main(String[] args) {
ImageUI img = new ImageUI();
img.initUI();
}
}
这里主要讲如何通过存储RGB的整型int得到red、green、blue的值;以及red、green、blue如何合成为一个int。
我们了解了RGB的存储原理,很容易知道:17-24位存red,9-16位存green,1-8位存blue,用除法和减法可以提取出red、green、blue,但是这样运算速度会慢一些,可以用位移运算,运算速度会快一些。
假设RGB的值存储在int value中,通过位移运算有:
int red =(value>>16)&0xFF;
int green =(value>>8)&0xFF;
int blue =(value>>0)&0xFF;
为什么要位移之后与0xFF求与运算呢? 仔细分析后,我们知道,与0xFF做与运算后,可以将除低8位以外的所有位的数字变为0。
所以我们以此类推,我们发现把red、green、blue合成一个RGB时,可以通过位移加上或运算来实现:
int rgb= (red<<16)|(green<<8)|blue;
图片的输入,我写了一个输入图片的方法,加上想要读取的图片路径,路径是相对路径和绝对路径都可以,我的图片和代码是放在一个文件夹里的,所以用的相对路径。
使用ImageIO.read()方法的时候,需要创建BufferedImage类的对象
int[][] imgarr = getImagePixArr("picture4.jpeg");
//这里imgarr是创建的二维数组,用来存储图片的像素点矩阵。
public int[][] getImagePixArr(String path){
// 声明一个文件对象
File file = new File(path);
BufferedImage buffimg=null;
try {// 处理文件IO异常
// 读取为 buffimg 对象
buffimg = ImageIO.read(file);
} catch (IOException e) {
e.printStackTrace();
}
// 宽 高 图片类型 ARGB
int w = buffimg.getWidth();
int h = buffimg.getHeight();
// 根据 图片对象的宽 高 生成一个二维数组 -- 线性结构
int[][] imgarr = new int[w][h];
// 遍历存储像素值
for (int i = 0; i < w; i++) {
for (int j = 0; j < h; j++) {
imgarr[i][j]= buffimg.getRGB(i,j);
}
}
return imgarr;
}
图片输出的话也是一样,也需要创建BufferedImage类的对象,代码如下:
public void outImagePixArr(int[][] imgarr ,String path){
// 声明一个文件对象
File file = new File(path);
BufferedImage buffimg = new BufferedImage(imgarr.length ,imgarr[0].length ,BufferedImage.TYPE_INT_ARGB);
for (int i = 0; i < imgarr.length; i++){
for (int j = 0; j < imgarr[i].length; j++){
buffimg.setRGB(i, j, imgarr[i][j]);
}
}
try {// 处理文件IO异常
// 读取为 buffimg 对象
ImageIO.write(buffimg, "PNG", file);
} catch (IOException e) {
e.printStackTrace();
//return;
}
}
// 原图
for (int i = 0; i < imgarr.length; i++) {
for (int j = 0; j < imgarr[i].length; j++) {
Color color = new Color(imgarr[i][j]);
g.setColor(color);
g.fillRect(i,j,1,1);
}
}
马赛克就是模糊处理,把一个像素点的长宽变大即可。
// 马赛克
for (int i = 0; i < imgarr.length; i+=10) {
for (int j = 0; j < imgarr[i].length; j+=10) {
Color color = new Color(imgarr[i][j]);
g.setColor(color);
g.fillRect(750+i,j,10,10);
}
}
在前面已经讲过,灰度图片的red=green=blue。
// 灰度
for (int i = 0; i < imgarr.length; i++) {
for (int j = 0; j < imgarr[i].length; j++) {
int value = imgarr[i][j];
int red=(value>>16)&0xFF;
int green=(value>>8)&0xFF;
int blue=(value>>0)&0xFF;
int gray = (red+green+blue)/3;
Color color = new Color(gray,gray,gray);
g.setColor(color);
g.fillRect(i,400+j,1,1);
}
}
这个滤镜是我自己调的,可能不是很好看,red变为原来的0.8,green变成原来的0.5,blue变成原来的0.6。
//滤镜
for (int i = 0; i < imgarr.length; i++){
for (int j = 0; j < imgarr[i].length; j++){
int value = imgarr[i][j];
int red=(value>>16)&0xFF;
int green=(value>>8)&0xFF;
int blue=(value>>0)&0xFF;
red = red*4/5;
green = green/2;
blue = blue*3/5;
//imgarr1 = (red<<16)|(green<<8)|blue;
Color color = new Color(red,green,blue);
g.setColor(color);
g.fillRect(i,400+j,1,1);
}
}
//轮廓
for (int i = 0; i < imgarr.length-2; i++) {
for (int j = 0; j < imgarr[i].length-2; j++) {
int value = imgarr[i][j];
int red=(value>>16)&0xFF;
int green=(value>>8)&0xFF;
int blue=(value>>0)&0xFF;
int gray = (red+green+blue)/3;
int nvalue = imgarr[i+2][j+2];
int nred=(nvalue>>16)&0xFF;
int ngreen=(nvalue>>8)&0xFF;
int nblue=(nvalue>>0)&0xFF;
int ngray = (nred+ngreen+nblue)/3;
if(Math.abs(gray-ngray)>10){
g.setColor(Color.white);
}else{
g.setColor(Color.black);
}
// Color color = new Color(ngray,ngray,ngray);
g.fillRect(750+i,400+j,1,1);
}
}
创建一个UI类,可以生成带按钮的窗体。那个布局方法是自动把按钮布局。
import javax.swing.*;
import java.awt.*;
import javax.swing.JFrame;
public class UI extends JFrame{
// 构造方法
// 创建对象时调用
String[] strs={"原图","灰度","怀旧","马赛克","二值化","融合","轮廓"};
public void initUI() {
setTitle("图像窗体");
setSize(2200, 1000);
setVisible(true);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
//布局
FlowLayout fl =new FlowLayout();
setLayout(fl);
for (int i = 0; i < strs.length; i++) {
JButton btn = new JButton(strs[i]);
add(btn);
}
setVisible(true);
}
public static void main(String[] args) {
UI img = new UI();
img.initUI();
}
}
运行结果
但是我们在窗体上点击按钮,是没有反应的,于是我们需要一个Listener类,也就是监听器,来监听按钮是否被点击,如果被点击就会进行相应操作。
ActionListener——动作监听器,作用是监听按钮是否被点击了,如果被点击了就调用一个预设好的方法(方法的内容需要自己来写 )。实现接口interface时,用implements关键字,可以变相实现多继承。
import java.awt.event.ActionListener;
// 1:首先创建一个类 使用 implements ActionListener
public class ImageListener implements ActionListener{
// 2:重写ActionListener 中的方法
public void actionPerformed(ActionEvent e){
// 3: 点什么按钮就绘制什么图像
System.out.println("点击了按钮 ");
}
}
运行结果:点一下按钮就会输出“点击了按钮 ”
前文讲了很多细节的处理以及方法,现在我们就可以创建一个窗体,一个有按钮的窗体,点击按钮能得到相应“滤镜”的图片,不再是4.1那样没有反应的按钮;也不再是4.2只输出一句话,我们重写方法后,可以把图片输出在窗体上。
创建这个工具类目的有两个:第一,读取某个路径下的图片,把他存在我们的像素点矩阵imgarr(二维数组)中,这个我在3.1中有介绍;第二,就是能把处理后的像素点矩阵“画出来”,也就是图片。
import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
public class ImageUtils {
public int[][] getImagePixArr(String path){
// 声明一个文件对象
File file = new File(path);
BufferedImage buffimg=null;
try {// 处理文件IO异常
// 读取为 buffimg 对象
buffimg = ImageIO.read(file);
} catch (IOException e) {
e.printStackTrace();
}
// 宽 高 图片类型 ARGB
int w = buffimg.getWidth();
int h = buffimg.getHeight();
// 根据 图片对象的宽 高 生成一个二维数组 -- 线性结构
int[][] imgarr = new int[w][h];
// 遍历存储像素值
for (int i = 0; i < w; i++) {
for (int j = 0; j < h; j++) {
imgarr[i][j]= buffimg.getRGB(i,j);
}
}
return imgarr;
}
public void drawImage0(int[][] imgarr,Graphics g){
for (int i = 0; i < imgarr.length; i++) {
for (int j = 0; j < imgarr[i].length; j++) {
Color color = new Color(imgarr[i][j]);
g.setColor(color);
g.fillRect(100+i,100+j,1,1);
}
}
}
}
创建这个动作监听器类,作用是监听按钮是否被点击了,如果点击了就调用相应的方法。我们在4.2中重写了方法,点击就会输出“点击了按钮 ”;现在,我们再次重写方法,点击相应的按钮,就输出对应按钮的图片。(这里我只重写了输出原图的方法,输出其他“滤镜”图片的方法只需要把3.2中的代码粘贴修改即可)
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
public class ImageListener implements ActionListener{
Graphics gr;
ImageUtils imageUtils = new ImageUtils();
int[][] imgarr=null;
{ // 初始化的代码块 创建对象时执行
imgarr = imageUtils.getImagePixArr("picture4.jpeg");
}
// 2:重写ActionListener 中的方法
public void actionPerformed(ActionEvent e){
// 3: 点什么按钮就绘制什么图像
// 如何区分 按钮
//获取按钮的字符串
String btnstr = e.getActionCommand();
//System.out.println("点击了 按钮 "+btnstr);
if(btnstr.equals("原图")){
// 绘制原图
// gr.drawString(btnstr,200,200);
imageUtils.drawImage0(imgarr,gr);
}
}
}
创建这个类,其实就是输出带有按钮的窗体,参考4.1,代码如下:
import javax.swing.*;
import java.awt.*;
public class ImageUI extends JFrame {
// 构造方法
// 创建对象时调用
String[] strs={"原图","灰度","怀旧","马赛克","二值化","融合","轮廓"};
public ImageUI(String title) {
setTitle(title);
setSize(1000, 1000);
setVisible(true);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
//布局
FlowLayout fl =new FlowLayout();
setLayout(fl);
// 监听器对象
ImageListener imageL = new ImageListener();
for (int i = 0; i < strs.length; i++) {
JButton btn = new JButton(strs[i]);
btn.setBackground(Color.white);
btn.addActionListener(imageL);// 所有的按钮都注册了监听器
add(btn);
}
setVisible(true);
// 获取画笔
Graphics g = this.getGraphics();
imageL.gr=g;
}
public static void main(String[] args) {
new ImageUI("图像处理编程");
}
}
本文到这里就结束了,后面博客会补充更“厉害”的图像处理方法,如PS等。一介java小白,欢迎大家批评指正。