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

下一篇將實現Android中的黑白化。其原理一樣一樣的,只是安卓中使用的是Bitmap:

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

安卓和pc端比較,安卓完敗。同樣的圖片,同樣的4096*4096像素,類似的實現過程,安卓卻用了20秒!噴血了。

——end

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章