參考:
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