首先来看最终效果图
美颜相机主要由多种功能组成:基本窗体,菜单栏,拉杆,灰度,二值,放大一倍,缩小一倍,以及三原色滤镜和透明度调节组成。
万丈高楼平地起,咱们先从基础款开始搞起
第一期链接:: 从零开始编写美颜相机程序第一讲
第二期链接: 从零开始编写美颜相机程序第二讲
OK我们回归正题
这一期,我们将把所有内容完成
先来做imagepan面板的内容
- 首先,我们先将想要添加的功能通过之前编写的一维数组添加进去
String[] name = {"灰度", "显示图片", "二值", "放大一倍", "缩小一倍", "滤镜", "透明度"};
for (int i = 0; i < name.length; i++) {
JButton jbu = new JButton(name[i]);
this.add(jbu);
jbu.addActionListener(dl);
}
接着编写菜单栏,利用JMenu组件制作菜单栏
- 首先创建JMenuBar对象,然后用JMenu创建三个对象并添加菜单项
- 之后将菜单栏添加到菜单界面
JMenuBar mb = new JMenuBar();
//创建菜单
JMenu meFile = new JMenu("文件");
JMenu meEdit = new JMenu("Edit");
JMenu meAbout = new JMenu("About");
//给某个菜单加菜单项:
JMenuItem miOpen = new JMenuItem("打开");
//给菜单项加上监听器
// miOpen.addActionListener();
JMenuItem miSave = new JMenuItem("保存");
JMenuItem miExit = new JMenuItem("退出");
meFile.add(miOpen);
meFile.add(miSave);
meFile.add(miExit);
mb.add(meFile);
mb.add(meEdit);
mb.add(meAbout);
return mb;//返回菜单条
}
然后添加一个滑块
JSlider js = new JSlider(1, 255);
接着我们来写最关键的代码逻辑——drawlis部分
-
为了达成各种颜色的滤镜不会互相影响原本图片的逻辑,我们需要创建两个数组
- 其中一个数组保存原始图片数据,另一个数组保存改变后的图片数据
private int[][] pixelArray;
private int[][] toArray;
pixelArray = new int[image.getWidth()][image.getHeight()];
// 遍历图像的每个像素,并将其RGB值存储在像素数组中
for (int i = 0; i < image.getWidth(); i++) {
for (int j = 0; j < image.getHeight(); j++) {
pixelArray[i][j] = image.getRGB(i, j);
}
}
toArray = new int[image.getWidth()][image.getHeight()];
// 遍历图像的每个像素,并将其RGB值存储在像素数组中
for (int i = 0; i < image.getWidth(); i++) {
for (int j = 0; j < image.getHeight(); j++) {
toArray[i][j] = image.getRGB(i, j);
}
}
- 有了这两个数组之后,我们可以先来设计滤镜的代码
- 我们想要的效果是根据滑块的数值来改变图像rgb三种的其中一种颜色从而达到滤镜效果
- 因此,我们可以直接进行位运算来操纵图像像素点的颜色。
- 通常,图片的像素点由透明度,红色,绿色,蓝色组成32位的数列。
- 因此我们要改变其中一种颜色,只需要改变对应的八位数字就行了。
- 所以我们可以记录滑块的值来替换掉其中一种颜色的位运算数值从而达到滤镜效果
以下以蓝色滤镜为例
if ("蓝色滤镜".equals(name)) {
// 遍历像素数组,并根据JSlider的值修改每个像素的蓝色通道
for (int i = 0; i < pixelArray.length; i++) {
for (int j = 0; j < pixelArray[i].length; j++) {
int v = pixelArray[i][j];
// 使用位运算提取红透明度的值
int alpha = (v >> 24) & 0xFF;
// 使用位运算提取红色、绿色和蓝色通道的值
int red = (v >> 16) & 0xFF;
int green = (v >> 8) & 0xFF;
// 蓝色通道不需要提取,因为我们将用scal替换它
int blue = v & 0xFF;
//创建一个新的像素值,其中红色和绿色通道保持不变,蓝色通道设置为JSlider的值
int newPixel = (alpha << 24) | (red << 16) | (green << 8) | scal;
toArray[i][j] = newPixel; // 将新的像素值存储回像素数组
}
}
//双缓冲
BufferedImage modifiedImage = new BufferedImage(2000, 1600, BufferedImage.TYPE_INT_ARGB);
// 遍历像素数组,并将每个像素的RGB值设置到新的BufferedImage对象中
for (int i = 0; i < image.getWidth(); i++) {
for (int j = 0; j < image.getHeight(); j++) {
modifiedImage.setRGB(i / 2, j / 2, toArray[i][j]);
}
}
g.drawImage(modifiedImage, 150, 150, null);
}
可以仔细看一下这段代码
-
首先,JSlider为代码中的scal变量,我们滑动滑块就能进行像素颜色的改变。
-
其次为了防止每次滑动绘制图层导致卡顿,我们可以使用双缓冲技术。
-
上述代码大家可以看到一个细节,接受像素数据时使用的是pixelArray数组,而保存改变的数据的是 toArray数组,这就能保证你在修改图像数据时不会影响原始图像的数据,从而防止改变多种颜色通道使图片变成一个纯色块。
透明度的代码逻辑与滤镜类似
if ("透明度".equals(name)) {// 遍历像素数组,并根据JSlider的值修改每个像素的蓝色通道
for (int i = 0; i < pixelArray.length; i++) {
for (int j = 0; j < pixelArray[i].length; j++) {
int v = toArray[i][j];
// 使用位运算提取红透明度的值
int alpha = (v >> 24) & 0xFF;
// 使用位运算提取红色、绿色和蓝色通道的值
int red = (v >> 16) & 0xFF;
int green = (v >> 8) & 0xFF;
// 蓝色通道不需要提取,因为我们将用scal替换它
int blue = v & 0xFF;
//创建一个新的像素值,其中红色和绿色通道保持不变,蓝色通道设置为JSlider的值
int newPixel = (scal << 24) | (red << 16) | (green << 8) | blue;
toArray[i][j] = newPixel; // 将新的像素值存储回像素数组
}
}
// 创建一个新的BufferedImage对象,用于存储修改后的图像
//带缓冲区图像类
//将挪动后的图像放入缓冲区,不展现在图片上而是放到标签上
BufferedImage modifiedImage = new BufferedImage(2000, 1600, BufferedImage.TYPE_INT_ARGB);
// 遍历像素数组,并将每个像素的RGB值设置到新的BufferedImage对象中
for (int i = 0; i < image.getWidth(); i++) {
for (int j = 0; j < image.getHeight(); j++) {
modifiedImage.setRGB(i / 2, j / 2, toArray[i][j]);
}
}
g.drawImage(modifiedImage, 150, 150, null);
}
}
由于绘制卡顿的问题,我们可以在所有绘制逻辑中都加上双缓冲绘画
以显示图像为例
//显示图片的类
private void draw3() {
BufferedImage bi = new BufferedImage(image.getWidth() / 2, image.getHeight() / 2, BufferedImage.TYPE_INT_RGB);
//取得内存中一张画布,要画的东西,都画到这个画布上
Graphics big = bi.getGraphics();
int[][] ia = toArray;
for (int i = 0; i < ia.length; i++) {
for (int j = 0; j < ia[i].length; j++) {
int v = ia[i][j];
Color cc = new Color(v);
double ra = cc.getRed();
double rb = cc.getGreen();
double rc = cc.getBlue();
Color c = new Color((int) ra, (int) rb, (int) rc, 50);
double re = (ra + rb + rc) / 3;
big.setColor(c);
//画文件
big.fillOval(i / 2, j / 2, 2, 2);
}
}
g.drawImage(bi, 150, 150, null);
}
缩小一倍与放大一倍的逻辑基本相似,只需要改变绘制时的像素点大小即可
就是绘制时 big.fillOval(i / 2, j / 2, 2, 2);这个代码的i和j的大小(由于我的图片过大,所以我默认为二分之一大小)
/缩小一倍的类
private void drawImage1() {
BufferedImage bi = new BufferedImage(image.getWidth() / 4, image.getHeight() / 4, BufferedImage.TYPE_INT_RGB);
//取得内存中一张画布,要画的东西,都画到这个画布上
Graphics big = bi.getGraphics();
int[][] ia = toArray;
for (int i = 0; i < ia.length; i++) {
for (int j = 0; j < ia[i].length; j++) {
int v = ia[i][j];
Color cc = new Color(v);
double ra = cc.getRed();
double rb = cc.getGreen();
double rc = cc.getBlue();
Color c = new Color((int) ra, (int) rb, (int) rc, 50);
big.setColor(c);
big.fillOval(i / 4, j / 4, 2, 2);
}
}
g.drawImage(bi, 150, 150, null);
}
以上为代码中难以理解的逻辑,如有疏漏还请见谅
以上的内容可能有些过于干燥,不过这都是本人的基本思路,大伙可以参考一下
以下附上所有代码
注意:并没有附上功能包
public class imagepan extends JFrame {
public void intiUI() throws IOException {
this.setTitle("美颜相机");
this.setSize(1600, 1600);
//添加布局管理器
FlowLayout fl = new FlowLayout();
this.setLayout(fl);
JSlider js = new JSlider(1, 255);
this.add(js);
//出现在中心
this.setLocationRelativeTo(null);
//关闭窗口自动关闭
this.setDefaultCloseOperation(3);
//创建监听器对象
//可视化
JMenuBar mb = createMenu();
this.setJMenuBar(mb);
this.setVisible(true);
JLabel imageLabel = new JLabel();
Graphics g = this.getGraphics();
drawlis dl = new drawlis(g, js, imageLabel);
js.addChangeListener(dl);
//添加滑动条
//添加按钮
String[] name = {"灰度", "显示图片", "二值", "放大一倍", "缩小一倍", "蓝色滤镜", "绿色滤镜", "红色滤镜", "透明度"};
for (int i = 0; i < name.length; i++) {
JButton jbu = new JButton(name[i]);
this.add(jbu);
jbu.addActionListener(dl);
}
this.setVisible(true);
}
public JMenuBar createMenu() {
JMenuBar mb = new JMenuBar();
//创建菜单
JMenu meFile = new JMenu("文件");
JMenu meEdit = new JMenu("Edit");
JMenu meAbout = new JMenu("About");
//给某个菜单加菜单项:
JMenuItem miOpen = new JMenuItem("打开");
//给菜单项加上监听器
// miOpen.addActionListener();
JMenuItem miSave = new JMenuItem("保存");
JMenuItem miExit = new JMenuItem("退出");
meFile.add(miOpen);
meFile.add(miSave);
meFile.add(miExit);
mb.add(meFile);
mb.add(meEdit);
mb.add(meAbout);
return mb;//返回菜单条
}
public static void main(String args[]) throws IOException {
imagepan ip = new imagepan();
ip.intiUI();
}
}
public class drawlis implements ActionListener, ChangeListener {
private BufferedImage image;
private Graphics g;
private JSlider js;
public String name;
private int[][] pixelArray;
private int[][] toArray;
private JLabel imageLabel;
public drawlis(Graphics g, JSlider js, JLabel imageLabel) throws IOException {
this.js = js;
this.g = g;
this.imageLabel = imageLabel;
image = ImageIO.read(new File("mansui.png"));
pixelArray = new int[image.getWidth()][image.getHeight()];
// 遍历图像的每个像素,并将其RGB值存储在像素数组中
for (int i = 0; i < image.getWidth(); i++) {
for (int j = 0; j < image.getHeight(); j++) {
pixelArray[i][j] = image.getRGB(i, j);
}
}
toArray = new int[image.getWidth()][image.getHeight()];
// 遍历图像的每个像素,并将其RGB值存储在像素数组中
for (int i = 0; i < image.getWidth(); i++) {
for (int j = 0; j < image.getHeight(); j++) {
toArray[i][j] = image.getRGB(i, j);
}
}
}
//滑块监听器的方法
public void stateChanged(ChangeEvent e) {
int scal = js.getValue();
g.setColor(Color.white);
g.clearRect(130, 130, 1600, 1600);
if ("蓝色滤镜".equals(name)) {
drawarr(scal);
} else if ("透明度".equals(name)) {
drawar(scal);
} else if ("绿色滤镜".equals(name)) {
drawarr(scal);
} else if ("红色滤镜".equals(name)) {
drawarr(scal);
}
}
//画图
private void drawar(int scal) {
if ("透明度".equals(name)) {// 遍历像素数组,并根据JSlider的值修改每个像素的蓝色通道
for (int i = 0; i < pixelArray.length; i++) {
for (int j = 0; j < pixelArray[i].length; j++) {
int v = toArray[i][j];
// 使用位运算提取红透明度的值
int alpha = (v >> 24) & 0xFF;
// 使用位运算提取红色、绿色和蓝色通道的值
int red = (v >> 16) & 0xFF;
int green = (v >> 8) & 0xFF;
// 蓝色通道不需要提取,因为我们将用scal替换它
int blue = v & 0xFF;
//创建一个新的像素值,其中红色和绿色通道保持不变,蓝色通道设置为JSlider的值
int newPixel = (scal << 24) | (red << 16) | (green << 8) | blue;
toArray[i][j] = newPixel; // 将新的像素值存储回像素数组
}
}
// 创建一个新的BufferedImage对象,用于存储修改后的图像
//带缓冲区图像类
//将挪动后的图像放入缓冲区,不展现在图片上而是放到标签上
BufferedImage modifiedImage = new BufferedImage(2000, 1600, BufferedImage.TYPE_INT_ARGB);
// 遍历像素数组,并将每个像素的RGB值设置到新的BufferedImage对象中
for (int i = 0; i < image.getWidth(); i++) {
for (int j = 0; j < image.getHeight(); j++) {
modifiedImage.setRGB(i / 2, j / 2, toArray[i][j]);
}
}
g.drawImage(modifiedImage, 150, 150, null);
}
}
private void drawarr(int scal) {
if ("蓝色滤镜".equals(name)) {
// 遍历像素数组,并根据JSlider的值修改每个像素的蓝色通道
for (int i = 0; i < pixelArray.length; i++) {
for (int j = 0; j < pixelArray[i].length; j++) {
int v = pixelArray[i][j];
// 使用位运算提取红透明度的值
int alpha = (v >> 24) & 0xFF;
// 使用位运算提取红色、绿色和蓝色通道的值
int red = (v >> 16) & 0xFF;
int green = (v >> 8) & 0xFF;
// 蓝色通道不需要提取,因为我们将用scal替换它
int blue = v & 0xFF;
//创建一个新的像素值,其中红色和绿色通道保持不变,蓝色通道设置为JSlider的值
int newPixel = (alpha << 24) | (red << 16) | (green << 8) | scal;
toArray[i][j] = newPixel; // 将新的像素值存储回像素数组
}
}
//双缓冲
BufferedImage modifiedImage = new BufferedImage(2000, 1600, BufferedImage.TYPE_INT_ARGB);
// 遍历像素数组,并将每个像素的RGB值设置到新的BufferedImage对象中
for (int i = 0; i < image.getWidth(); i++) {
for (int j = 0; j < image.getHeight(); j++) {
modifiedImage.setRGB(i / 2, j / 2, toArray[i][j]);
}
}
g.drawImage(modifiedImage, 150, 150, null);
}
if ("绿色滤镜".equals(name)) {
// 遍历像素数组,并根据JSlider的值修改每个像素的蓝色通道
for (int i = 0; i < pixelArray.length; i++) {
for (int j = 0; j < pixelArray[i].length; j++) {
int v = pixelArray[i][j];
// 使用位运算提取红透明度的值
int alpha = (v >> 24) & 0xFF;
// 使用位运算提取红色、绿色和蓝色通道的值
int red = (v >> 16) & 0xFF;
int green = (v >> 8) & 0xFF;
// 绿色通道不需要提取,因为我们将用scal替换它
int blue = v & 0xFF;
//创建一个新的像素值,其中红色和绿色通道保持不变,蓝色通道设置为JSlider的值
int newPixel = (alpha << 24) | (red << 16) | (scal << 8) | blue;
toArray[i][j] = newPixel; // 将新的像素值存储回像素数组
}
}
BufferedImage modifiedImage = new BufferedImage(2000, 1600, BufferedImage.TYPE_INT_ARGB);
// 遍历像素数组,并将每个像素的RGB值设置到新的BufferedImage对象中
for (int i = 0; i < image.getWidth(); i++) {
for (int j = 0; j < image.getHeight(); j++) {
modifiedImage.setRGB(i / 2, j / 2, toArray[i][j]);
}
}
g.drawImage(modifiedImage, 150, 150, null);
}
if ("红色滤镜".equals(name)) {
// 遍历像素数组,并根据JSlider的值修改每个像素的蓝色通道
for (int i = 0; i < pixelArray.length; i++) {
for (int j = 0; j < pixelArray[i].length; j++) {
int v = pixelArray[i][j];
// 使用位运算提取红透明度的值
int alpha = (v >> 24) & 0xFF;
// 使用位运算提取红色、绿色和蓝色通道的值
int red = (v >> 16) & 0xFF;
int green = (v >> 8) & 0xFF;
// 蓝色通道不需要提取,因为我们将用scal替换它
int blue = v & 0xFF;
//创建一个新的像素值,其中红色和绿色通道保持不变,蓝色通道设置为JSlider的值
int newPixel = (alpha << 24) | (scal << 16) | (green << 8) | blue;
toArray[i][j] = newPixel; // 将新的像素值存储回像素数组
}
}
BufferedImage modifiedImage = new BufferedImage(2000, 1600, BufferedImage.TYPE_INT_ARGB);
// 遍历像素数组,并将每个像素的RGB值设置到新的BufferedImage对象中
for (int i = 0; i < image.getWidth(); i++) {
for (int j = 0; j < image.getHeight(); j++) {
modifiedImage.setRGB(i / 2, j / 2, toArray[i][j]);
}
}
g.drawImage(modifiedImage, 150, 150, null);
}
}
//放大1一倍
private void drawImage() {
BufferedImage bi = new BufferedImage(image.getWidth(), image.getHeight(), BufferedImage.TYPE_INT_RGB);
//取得内存中一张画布,要画的东西,都画到这个画布上
Graphics big = bi.getGraphics();
int[][] ia = toArray;
//不能动的代码
for (int i = 0; i < ia.length; i++) {
for (int j = 0; j < ia[i].length; j++) {
int v = ia[i][j];
Color cc = new Color(v);
double ra = cc.getRed();
double rb = cc.getGreen();
double rc = cc.getBlue();
Color c = new Color((int) ra, (int) rb, (int) rc, 50);
big.setColor(c);
big.fillOval(i, j, 4, 4);
}
}
BufferedImage modifiedImage = new BufferedImage(2000, 1600, BufferedImage.TYPE_INT_ARGB);
// 遍历像素数组,并将每个像素的RGB值设置到新的BufferedImage对象中
for (int i = 0; i < image.getWidth(); i++) {
for (int j = 0; j < image.getHeight(); j++) {
modifiedImage.setRGB(i / 2, j / 2, toArray[i][j]);
}
}
g.drawImage(bi, 150, 150, null);
}
//灰度的类
private void draw1() {
BufferedImage bi = new BufferedImage(image.getWidth() / 2, image.getHeight() / 2, BufferedImage.TYPE_INT_RGB);
//取得内存中一张画布,要画的东西,都画到这个画布上
Graphics big = bi.getGraphics();
int[][] ia = toArray;
for (int i = 0; i < ia.length; i++) {
for (int j = 0; j < ia[i].length; j++) {
int v = ia[i][j];
Color cc = new Color(v);
double ra = cc.getRed();
double rb = cc.getGreen();
double rc = cc.getBlue();
double re = (ra + rb + rc) / 3;
Color c = new Color((int) re, (int) re, (int) re, 50);
int newPixel = ((int)re << 16) | ((int)re << 8) |(int)re;
toArray[i][j] = newPixel;
big.setColor(c);
big.fillOval(i / 2, j / 2, 2, 2);
}
}
BufferedImage modifiedImage = new BufferedImage(2000, 1600, BufferedImage.TYPE_INT_ARGB);
// 遍历像素数组,并将每个像素的RGB值设置到新的BufferedImage对象中
for (int i = 0; i < image.getWidth(); i++) {
for (int j = 0; j < image.getHeight(); j++) {
modifiedImage.setRGB(i / 2, j / 2, toArray[i][j]);
}
}
g.drawImage(bi, 150, 150, null);
}
//二值的类
private void draw2() {
BufferedImage bi = new BufferedImage(image.getWidth() / 2, image.getHeight() / 2, BufferedImage.TYPE_INT_RGB);
//取得内存中一张画布,要画的东西,都画到这个画布上
Graphics big = bi.getGraphics();
int[][] ia = toArray;
for (int i = 0; i < ia.length; i++) {
for (int j = 0; j < ia[i].length; j++) {
int v = ia[i][j];
Color cc = new Color(v);
double ra = cc.getRed();
double rb = cc.getGreen();
double rc = cc.getBlue();
Color c = new Color((int) ra, (int) rb, (int) rc, 50);
double re = (ra + rb + rc) / 3;
big.setColor(c);
if (re > 127) {
big.setColor(Color.black);
int newPixel = (50) | (0) |(0)|(0);
toArray[i][j] = newPixel;
} else {
int newPixel = (50) | (255) |(255)|(255);
toArray[i][j] = newPixel;
big.setColor(Color.white);
}
big.fillOval(i / 2, j / 2, 2, 2);
}
}
BufferedImage modifiedImage = new BufferedImage(2000, 1600, BufferedImage.TYPE_INT_ARGB);
// 遍历像素数组,并将每个像素的RGB值设置到新的BufferedImage对象中
for (int i = 0; i < image.getWidth(); i++) {
for (int j = 0; j < image.getHeight(); j++) {
modifiedImage.setRGB(i / 2, j / 2, toArray[i][j]);
}
}
g.drawImage(bi, 150, 150, null);
}
//显示图片的类
private void draw3() {
BufferedImage bi = new BufferedImage(image.getWidth() / 2, image.getHeight() / 2, BufferedImage.TYPE_INT_RGB);
//取得内存中一张画布,要画的东西,都画到这个画布上
Graphics big = bi.getGraphics();
int[][] ia = toArray;
for (int i = 0; i < ia.length; i++) {
for (int j = 0; j < ia[i].length; j++) {
int v = ia[i][j];
Color cc = new Color(v);
double ra = cc.getRed();
double rb = cc.getGreen();
double rc = cc.getBlue();
Color c = new Color((int) ra, (int) rb, (int) rc, 50);
double re = (ra + rb + rc) / 3;
big.setColor(c);
//画文件
big.fillOval(i / 2, j / 2, 2, 2);
}
}
g.drawImage(bi, 150, 150, null);
}
//缩小一倍的类
private void drawImage1() {
BufferedImage bi = new BufferedImage(image.getWidth() / 4, image.getHeight() / 4, BufferedImage.TYPE_INT_RGB);
//取得内存中一张画布,要画的东西,都画到这个画布上
Graphics big = bi.getGraphics();
int[][] ia = toArray;
for (int i = 0; i < ia.length; i++) {
for (int j = 0; j < ia[i].length; j++) {
int v = ia[i][j];
Color cc = new Color(v);
double ra = cc.getRed();
double rb = cc.getGreen();
double rc = cc.getBlue();
Color c = new Color((int) ra, (int) rb, (int) rc, 50);
big.setColor(c);
big.fillOval(i / 4, j / 4, 2, 2);
}
}
g.drawImage(bi, 150, 150, null);
}
public void actionPerformed(ActionEvent e) {
g.setColor(Color.white);
g.clearRect(150, 150, 1800, 1800);
name = e.getActionCommand();
if ("缩小一倍".
equals(name)) {
drawImage1();
}
if ("放大一倍".
equals(name)) {
drawImage();
}
if ("灰度".equals(name)) {
draw1();
}
// 遍历像素数组,并将每个像素的RGB值设置到新的BufferedImage对象中
if ("二值".
equals(name)) {
draw2();
}
if ("显示图片".
equals(name)) {
draw3();
}
}
}
如果有任何疑问都可以留言提问