ps图片黑白调整算法——java实现及性能优化

参考:

http://blog.csdn.net/matrix_space/article/details/22992833
http://blog.csdn.net/majinlei121/article/details/46372887


引文

Photoshop 的图像黑白调整功能,是通过对红、黄、绿、青、蓝和洋红等6种颜色的比例调节来完成的。这个是一直都知道,还经常使用的。但是不清楚它是怎么计算的。

后来,从博文http://blog.csdn.net/matrix_space/article/details/22992833
中找到Photoshop 图像黑白调整功能的计算公式:

gray= (max - mid) * ratio_max + (mid - min) * ratio_max_mid + min

但是,用不了啊用不了,不是java版的,而我最终要用到Android中。当时为了赶时间,直接用了平均值算法,那效果实在不给力,于是又换了系统自带的过滤器,效果也一般。现在闲暇了,于是,将这个算法用java实现一下。


正文

黑白调节计算公式解说

ps通过对红、黄、绿、青、蓝和洋红等6种颜色的比例来黑白化图片:

gray= (max - mid) * ratio_max + (mid - min) * ratio_max_mid + min

gray :像素的灰度值,不是真的灰色哈。
max : 像素点R、G、B三色中的最大者
mid:像素点R、G、B三色中的中间者
min:像素点R、G、B三色中的最小者
ratio_max:最大的颜色所占比率
ratio_max_mid:最大的颜色和中间颜色所占的比率

这六种颜色在ps中默认比例为:

redRadio = 40%;
yellowRadio = 60%;
greenRadio = 40%;
cyanRadio = 60%;
blueRadio = 20%;
magentaRadio =80%;

为了验证这一点,我打开ps:

这里写图片描述

可以看到,的确如此

并且这个数值是可以调整的,在ps中可以通过调节这几个数值,达到理想的去色效果。

java中的实现效果

奋战了许久,我用java来实现了这个算法,其效果如下。

原图:

这里写图片描述

java黑白化后:

这里写图片描述

Photoshop黑白化后:

这里写图片描述

很像,简直一模一样了,连大小都一样了。不过,大小自然也可能不一样,因为ps直接保存会存储一些图片元数据,同时底层的处理可能也有所不同。

实现代码

废话不多说,以下是初次的实现代码:

封装工具类

package date1114.图片;

import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;

import javax.imageio.ImageIO;

public class ImageUtil {

    //这个方法就拿来保存,测试效果一下
    public static void save(BufferedImage image,String path) {
        try {
            SimpleDateFormat simpleDateFormat = new SimpleDateFormat("YYYYMMddHHmmssSSS");
            String imageFormat;

            int type = image.getType();
            switch (type) {
            case BufferedImage.TYPE_4BYTE_ABGR:
            case BufferedImage.TYPE_4BYTE_ABGR_PRE:
                 imageFormat = "png";
                break;
            case BufferedImage.TYPE_INT_ARGB:
            case BufferedImage.TYPE_INT_ARGB_PRE:
                imageFormat = "bmp";
                break;
            default:
                imageFormat = "jpg";
                break;
            }
            String date = simpleDateFormat.format(new Date());
            ImageIO.write(image,imageFormat , new File(path+"_"+date+"."+imageFormat));
        } catch (IOException e) {
            e.printStackTrace();
        }

    }


    /**
     * Photoshop 黑白算法,默认效果
     * @param image 
     * @return 新的黑白化图片
     */
    public static BufferedImage createBlackWhiteImage(BufferedImage image) {
        return createBlackWhiteImage(image, null);
    }

    /**
     * Photoshop 黑白算法,默认效果
     * @param image
     * @radios 颜色通道配置,依次为红、黄、 绿、 青、 蓝、紫六个通道
     * @return 新的黑白化图片
     */
    public static BufferedImage createBlackWhiteImage(BufferedImage image,float[] radios) {
        int width = image.getWidth();   //获取位图的宽
        int height = image.getHeight();  //获取位图的高

        BufferedImage result = new BufferedImage(image.getWidth(), image.getHeight(), image.getType());
        int alpha = 0xff000000;
        int r = 0;
        int g = 0;
        int b = 0;
        int max = 0;
        int min = 0;
        int mid = 0;
        int gray = 0;

        float radioMax = 0;
        float radioMaxMid = 0;

        if (radios == null) {
            //                    红        黄         绿         青         蓝        紫
            radios = new float[]{0.4f,0.6f,0.4f,0.6f,0.2f,0.8f};
        }   
        for (int i = 0; i < width; i++) {//一列列扫描
            for (int j = 0; j < height; j++) {

                gray = image.getRGB(i, j);

                alpha = gray >>> 24;
                r = (gray>>16) & 0x000000ff;
                g = (gray >> 8) & 0x000000ff;
                b = gray & 0x000000ff;

                if (r >= g && r>=b) {
                    max = r;
                    radioMax = radios[0];
                }
                if (g>= r && g>=b) {
                    max = g;
                    radioMax = radios[2]; 
                }
                if (b >= r && b>=g) {
                    max = b;
                    radioMax = radios[4];
                }


                if (r<=g && r<=b) { // g+ b = cyan 青色
                    min = r;
                    radioMaxMid = radios[3];
                }

                if (b <= r && b<=g) {//r+g = yellow 黄色
                    min = b;
                    radioMaxMid = radios[1];
                }
                if (g <= r && g<=b) {//r+b = m 洋红
                    min = g;
                    radioMaxMid = radios[5];
                }

                mid = r + g + b-max -min;

//              公式:gray= (max - mid) * ratio_max + (mid - min) * ratio_max_mid + min

                gray = (int) ((max - mid) * radioMax + (mid - min) * radioMaxMid + min);

                gray = (alpha << 24) | (gray << 16) | (gray << 8) | gray;

                result.setRGB(i, j, gray);
            }

        }

        return result;
    }

}

调用和测试:

package date1114.图片;

import java.awt.image.BufferedImage;
import java.io.IOException;
import java.net.URL;

import javax.imageio.ImageIO;

public class Test {

public static void main(String[] args) {
    String url = "https://img-blog.csdn.net/20171114193050621";
    try {
        BufferedImage colorImage = ImageIO.read(new URL(url));
        ImageUtil.save(colorImage, "color");


        BufferedImage grayImage = ImageUtil.createBlackWhiteImage(colorImage);
        ImageUtil.save(grayImage, "gray");

    } catch (IOException e) {

        e.printStackTrace();
    }
}   
}

运行,效果就出来了。这里,我使用网络图片:https://img-blog.csdn.net/20171114193050621 进行处理,这个http地址其实就是本文彩虹色环的url地址。

性能优化

隔了两天,发现上述的代码似乎有优化的空间。

我在本地磁盘选取了一张4096*4096的大图进行黑白化,多次进行测试,处理时间在2800毫秒上下浮动。

于是考虑是否因为代码在循环体中使用bufferImage进行了颜色的修改从而导致性能降低,也就是怀疑循环体中的这段代码:

for(){
for(){
//...
result.setRGB(i, j, gray);//BufferImage设置像素点颜色
//...

那么,转换为像素数组来进行计算,效率会不会更高一些?

从流中读取所有像素点:

int[] pixels = image.getRGB(0, 0, width,height, null, 0, width);

在这时候还是挺忐忑的,因为看这个读取像素的方法,其实现可能用了循环(印象中操作流时,总是循环的),搞不好性能又低下了。

得到像素数组后,逐行或逐列扫描将每一个像素点变灰。于是将result.setRGB(i, j, gray);//BufferImage替换为:

  //result.setRGB(i, j, gray);
  pixels[j*width+i] = gray;

最后,循环结束,将像素数组转换为BufferImage流:

result.setRGB(0, 0, width, height, pixels, 0, width);

运行,咦,耗时降低了,在2600毫秒上下。提升不明显啊,或许从流中读取像素点也耗了一些时间?(为什么这样想?在多次循环中,优化性能可以从不做复杂的操作,避免创建多个对象,声明多个引用等方面来考虑,于是对象有了克隆,循环体中的变量可以在循环外部声明等。而这里,多次从流这个复杂对象读取东西,感觉不妙,不知道它底层是怎么读的,或许就有循环,耗时操作。况且,都拿到像素数组,直接从数组中读取色彩就o了)

改:


//              gray = image.getRGB(i, j);
                gray = pixels[j*width+i]; 

一运行,咦,这回居然只要1600毫秒,比未优化前少了整整1.2秒钟。

几乎算大功告成了吧。感觉是的。

以下是优化后的所有代码:

package date1114.Photoshop图片黑白化算法;

import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;

import javax.imageio.ImageIO;

public class ImageUtil {

    //这个方法就拿来保存,测试效果一下
    public static void save(BufferedImage image,String path) {
        try {
            SimpleDateFormat simpleDateFormat = new SimpleDateFormat("YYYYMMddHHmmssSSS");
            String imageFormat;

            int type = image.getType();
            switch (type) {
            case BufferedImage.TYPE_4BYTE_ABGR:
            case BufferedImage.TYPE_4BYTE_ABGR_PRE:
                 imageFormat = "png";
                break;
            case BufferedImage.TYPE_INT_ARGB:
            case BufferedImage.TYPE_INT_ARGB_PRE:
                imageFormat = "bmp";
                break;
            default:
                imageFormat = "jpg";
                break;
            }
            String date = simpleDateFormat.format(new Date());
            ImageIO.write(image,imageFormat , new File(path+"_"+date+"."+imageFormat));
        } catch (IOException e) {
            e.printStackTrace();
        }

    }




    /**
     * Photoshop 黑白算法,默认效果
     * @param image 
     * @return 新的黑白化图片
     */
    public static BufferedImage createBlackWhiteImage(BufferedImage image) {
        return createBlackWhiteImage(image, null);
    }

    /**
     * Photoshop 黑白算法,默认效果
     * @param image
     * @radios 颜色通道配置,依次为红、黄、 绿、 青、 蓝、紫六个通道
     * @return 新的黑白化图片
     */
    public static BufferedImage createBlackWhiteImage(BufferedImage image,float[] radios) {
        int width = image.getWidth();   //获取位图的宽
        int height = image.getHeight();  //获取位图的高

        int alpha = 0;
        int r = 0;
        int g = 0;
        int b = 0;
        int max = 0;
        int min = 0;
        int mid = 0;
        int gray = 0;

        float radioMax = 0;
        float radioMaxMid = 0;

        if (radios == null) {
            //                    红        黄         绿         青         蓝        紫
            radios = new float[]{0.4f,0.6f,0.4f,0.6f,0.2f,0.8f};
        }

        //int[] pixels = new int[width*height];
        int[] pixels = image.getRGB(0, 0, width,height, null, 0, width);

//      BufferedImage result = new BufferedImage(width, height, image.getType());
        for (int i = 0; i < width; i++) {//一列列扫描
            for (int j = 0; j < height; j++) {

//              gray = image.getRGB(i, j);
                gray = pixels[j*width+i]; 


                alpha = gray >>> 24;
                r = (gray>>16) & 0x000000ff;
                g = (gray >> 8) & 0x000000ff;
                b = gray & 0x000000ff;

                if (r >= g && r>=b) {
                    max = r;
                    radioMax = radios[0];
                }
                if (g>= r && g>=b) {
                    max = g;
                    radioMax = radios[2]; 
                }
                if (b >= r && b>=g) {
                    max = b;
                    radioMax = radios[4];
                }


                if (r<=g && r<=b) { // g+ b = cyan 青色
                    min = r;
                    radioMaxMid = radios[3];
                }

                if (b <= r && b<=g) {//r+g = yellow 黄色
                    min = b;
                    radioMaxMid = radios[1];
                }
                if (g <= r && g<=b) {//r+b = m 洋红
                    min = g;
                    radioMaxMid = radios[5];
                }

                mid = r + g + b-max -min;

//              公式:gray= (max - mid) * ratio_max + (mid - min) * ratio_max_mid + min

                gray = (int) ((max - mid) * radioMax + (mid - min) * radioMaxMid + min);
                gray = (alpha << 24) | (gray << 16) | (gray << 8) | gray;


//                result.setRGB(i, j, gray);
                pixels[j*width+i] = gray;

            }

        }
        BufferedImage result = new BufferedImage(width, height, image.getType());
        result.setRGB(0, 0, width, height, pixels, 0, width);
        return result;
    }

}

测试代码:


import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;

import javax.imageio.ImageIO;

public class Test {

public static void main(String[] args) {
    String url = "D:/abc/a.jpg";
    try {
        BufferedImage colorImage = ImageIO.read(new File(url));
        ImageUtil.save(colorImage, "color");

        long time = System.currentTimeMillis();
        BufferedImage grayImage = ImageUtil.createBlackWhiteImage(colorImage);
        System.out.println(System.currentTimeMillis()-time);
        ImageUtil.save(grayImage, "gray");

    } catch (IOException e) {

        e.printStackTrace();
    }
}   
}

ps:

虽然优化完成,但是鉴于安卓bitmap.getPixel() 与 bitmap.setPixel() 导致特别耗时的现象,我有些些怀疑bufferedImage.getRGB()与bufferedImage.getRGB() 也可能存在一定程度上的过度耗时现象,或许不如直接使用像素数组来的快,这个就留待以后研究了。


下一篇将实现Android中的黑白化。其原理一样一样的,只是安卓中使用的是Bitmap:

http://blog.csdn.net/Mingyueyixi/article/details/78638495

安卓和pc端比较,安卓完败。同样的图片,同样的4096*4096像素,类似的实现过程,安卓却用了20秒!喷血了——这是在未优化之前的结果。

——end

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值