底层像素矩阵到位运算

一、图片的结构

    1. 像素值组成的矩阵
    1. 像素值:颜色的数值表达
    • 计算机的颜色:RGB(三个通道)ARGB(四个通道)
      • 三个通道:
        • red : 16-23位
        • green : 8-15位
        • blue : 0-7位
      • 四个通道:
        • Alpha : 24-31
        • red 16-23位
        • green 8-15位
        • blue 0-7位
      • 一个int:4byte
        • 会将四个通道之中的值按照顺序排进一个int数值中。

二、 拆解像素:位运算

  • 四个颜色通道被塞进了一个int里,在做图像处理时,需要单独拆解出来。这里就需要用到执行效率高的位运算。
    • 注意,这里是按位与运算(&),是对二进制位进行逐位计算的运算符,不要和判断布尔条件的逻辑与(&&)混淆!
      • 向右位移:>>将二进制数向右边移动,高位缺失补0,低位溢出的位数丢失
      • 向左位移:<< 将二进制数向左边移动,低位缺失补0,高位溢出的位数丢失
    • 位运算:
      • 与运算:相同保留源码,不同为0,两个都是1就是1,其他都是0;
      • 或运算:有一个1就是1,都为0才是0.
      • 异或运算:相同为0,不同为1.
    • 拆数据:
      • red = num>>16 & 0xff;
      • green = num>>8 & 0xff;
      • blue = num & 0xff;
      • 将num向右移位,低位的数据直接抛弃,只将所需要的数据留在最右边。此时比如需要八位数据,八位已经位移到最后八位,如果使用0xff(0000 0000 1111 1111)。前面0的部分和位移后数据与运算之后,得到的就是0。最后8位的数据都是1,和位移后的数据进行与运算,就能提取出来所需要的几位数据。
    • 合数据:
      • rgb = red<<16 | green<<8 | blue;
      • 此时red,green等数据都是最右边是八位是有效数据的一串数字,如果在此时将red向左移动16位,那么这个有效数据就回到了它原本应该在的部位,其他几个同理。
      • 将这几个数据或运算,只要有1,结果就是1,那么这几个内容就会完美的拼在一起,并且保证数据正确。

三、不同的绘画

1.基础图画还原

  • 直接给出代码,代码之中包含注释
package picture01;

import javax.imageio.ImageIO;
import javax.swing.*;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;

public class ImagePro extends JFrame {

    // 核心数据结构:保存图片每一个像素点的二维数组
    int w, h;
    int[][] imgArr;

    public ImagePro() {
        setTitle("Java 图像处理 - 基础原图还原");
        setSize(800, 900);
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        // 在窗口初始化时,提前将图片数据加载进内存
        imgArr = getImage("D:\\study\\java\\新建文件夹\\mihu.jpg");
        setVisible(true);
    }
//将所画图纸变成实物
    public static void main(String[] args) {
        new ImagePro();
    }

    // 重绘方法:核心渲染逻辑
    @Override
    public void paint(Graphics g) {
        //清理画板,JFram窗口在每次重绘时,如果不调用这几代码,画面上可可能会残留之前拖动窗口留下的
        //重影或花屏
        super.paint(g);
        //防御性编程,如果读取图片错误或者未读到,立即返回防止空指针异常
        if (imgArr == null) return;

        // 遍历二维数组,逐个像素画出来
        for (int i = 0; i < w; i++) {
            for (int j = 0; j < h; j++) {
                int pix = imgArr[i][j];

                // 拆解出干净的 RGB 通道
                int red = (pix >> 16) & 0xFF;
                int green = (pix >> 8) & 0xFF;
                int blue = pix & 0xFF;

                // 设置画笔颜色
                g.setColor(new Color(red, green, blue));

                // 画出这个像素点(加上 50 的偏移量避免被窗口的标题栏遮挡)
                // 使用 1x1 的矩形代表一个真实的像素点
                g.fillRect(i + 50, j + 50, 1, 1);
            }
        }
    }

    // 数据提取:将物理图片转化为内存中的二维数组
    /*try { ... } catch (IOException e) { ... }作用:涉及到硬盘文件io操作,,文件可能不存在
    ,路径写错,或者图片格式损坏,这是一道安全防线,如果读取失败,会打印错误信息并返回null,
    防止整个程序直接崩溃*/
    public int[][] getImage(String path) {
        try {
            //根据文件路径,将硬盘里的文件加载到内存之中,并且转换成BufferedImage对象。
            //可以理解为图片已经脱离了物理文件的形态,变成了Java可以直接调用的内存对象
            BufferedImage img = ImageIO.read(new File(path));
            w = img.getWidth();
            h = img.getHeight();
            int[][] arr = new int[w][h];

            for (int i = 0; i < w; i++) {
                for (int j = 0; j < h; j++) {
                    //把该像素点的颜色提取出来
                    arr[i][j] = img.getRGB(i, j);
                }
            }
            //经过循环之后,图片已经变成了一个装满int数字的数字矩阵。最后将这个二位数组返回给调用者(paint)
            return arr;
        } catch (IOException e) {
            /*catch的意思是捕获,IOException 的全称是 Input/Output Exception(输入/输出异常)。
            之前try之中的是我们希望正常执行的代码,如果在读取过程之中发生了任何io错误,程序不会立即闪退
            二十生成一个IOException类型的错误对象
            */
            //打印黑匣子记录
            e.printStackTrace();
            return null;
        }
    }
}

2.底片/反色

// 设置画笔颜色
                g.setColor(new Color(255-red, 255-green, 255-blue));

-这是最简单的滤镜,视觉冲击力极强。原理是用最大值 255 减去当前的颜色值,得到它的互补色。

3.黑白/二值化滤镜

  • 先计算灰度值,使得图像非黑即白,大于灰度值的一个颜色,反之另一个颜色
int Y = (int)(0.299*red+0.587*green+0.114*blue);
                int threshold = 127;
                if(Y>threshold){
                g.setColor(Color.WHITE);
                }else{
                    g.setColor(Color.BLACK);
                }

4.复古滤镜

  • 1.利用公式变换颜色
  • 2.注意颜色溢出(最高到255)
//4.复古老照片滤镜
                int R = (int)(0.393*red + 0.769*green + 0.189*blue);
                int G = (int)(0.349*red + 0.686*green + 0.168*blue);
                int B = (int)(0.272*red + 0.534*green + 0.131*blue);

                int RED = Math.min(R, 255);
                int GREEN = Math.min(G, 255);
                int BLUE = Math.min(B, 255);

                g.setColor(new Color(RED, GREEN, BLUE));

高对比度/色彩增强滤镜

  • 1.色彩增强,利用公式来得到。
  • 2.运用线性映射,将每个颜色通道归一化到【0,1】区间,进行缩放后再还原到[0,255]
  • 3.contrast
    • 大于1,对比度增强
    • 小于1,对比度降低
  • 4.需要设置边界,无论是大还是小。
//高对比度增强
                double contrast = 1;
                int R = (int)((red-128)*contrast+128);
                int G = (int)((green-128)*contrast+128);
                int B = (int)((blue-128)*contrast+128);

                int RED = Math.max(0, Math.min(R, 255));
                int GREEN = Math.max(0, Math.min(G, 255));
                int BLUE = Math.max(0, Math.min(B, 255));

                g.setColor(new Color(RED, GREEN, BLUE));

  • 颜色对比
    • (1)contrast = 1:原图
    • (2)contrast = 2>1:饱和度增强
    • (3)contrast = 0.5<1:饱和度降低

更多推荐