在贴吧上经常有吧友发一些图片,点开之后就变成另一张图片,当时觉得很神奇,又不是gif,怎么会变呢,有一日逛贴吧,看到了这个帖子http://tieba.baidu.com/p/5306081495,里面介绍了这种图片的原理,和PS上的制作方法,不过我们身为程序员,这么复杂的事情怎么能手动来完成呢,于是就有了这篇文章。
帖子上面讲到
① 线性减淡(添加):
Img输出 = Img上+ Img下
我们代码里面怎么做呢,直接贴代码吧:
/**
* 图层特效 效果等于PhotoShop中图层特效的线性减淡(增加)
*
* @param src 作用特效的bitmap
* @param target 目标bitmap
* @return 作用特效后的bitmap
*/
public static Bitmap 线性减淡(Bitmap src, Bitmap target) {
final int width = src.getWidth(), height = src.getHeight();
int[] srcPixels = new int[width * height];
final Bitmap result = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
getBitmapPixelColor(src, (x, y, a, r, g, b) -> {
int dstR, dstG, dstB, dstPixelColor, resultA, resultR, resultG, resultB;
dstPixelColor = target.getPixel(x, y);
dstR = Color.red(dstPixelColor);
dstG = Color.green(dstPixelColor);
dstB = Color.blue(dstPixelColor);
resultA = 255;
resultR = r + dstR;
resultG = g + dstG;
resultB = b + dstB;
// 防止色值溢出
if (resultR > 255)
resultR = 255;
if (resultG > 255)
resultG = 255;
if (resultB > 255)
resultB = 255;
srcPixels[x + y * width] = Color.argb(resultA, resultR, resultG, resultB);
});
result.setPixels(srcPixels, 0, width, 0, 0, width, height);
return result;
}
这里贴上getBitmapPixelColor方法
private static void getBitmapPixelColor(Bitmap target, PixelColorHandler handler) {
if (checkBitmapCanUse(target) && handler != null) {
int a, r, g, b, pixelColor;
for (int y = 0; y < target.getHeight(); y++) {
for (int x = 0; x < target.getWidth(); x++) {
pixelColor = target.getPixel(x, y);
a = Color.alpha(pixelColor);
r = Color.red(pixelColor);
g = Color.green(pixelColor);
b = Color.blue(pixelColor);
handler.onHandle(x, y, a, r, g, b);
}
}
}
}
PixeColorHandler接口:
private interface PixelColorHandler {
void onHandle(int x, int y, int a, int r, int g, int b);
}
帖子上面的
② 划分:
Img输出 = Img下/ Img上;
这个我们代码实现的时候,直接跟楼主的 Img下/ Img上 是不行的,经过我反复测试对比,
用楼主的话应该是: 1 / (Img上 / Img下)
对应代码:
/**
* 图层特效 效果等于PhotoShop中图层特效的划分
*
* @param src 作用特效的bitmap
* @param target 目标bitmap
* @return 作用特效后的bitmap
*/
public static Bitmap 划分(Bitmap src, Bitmap target) {
final int width = src.getWidth(), height = src.getHeight();
int[] srcPixels = new int[width * height];
final Bitmap result = Bitmap.createBitmap(src.getWidth(), src.getHeight(), Bitmap.Config.ARGB_8888);
getBitmapPixelColor(src, (x, y, a, r, g, b) -> {
int dstR, dstG, dstB, dstPixelColor, resultA, resultR, resultG, resultB;
dstPixelColor = target.getPixel(x, y);
dstR = Color.red(dstPixelColor);
dstG = Color.green(dstPixelColor);
dstB = Color.blue(dstPixelColor);
resultA = 255;
resultR = (int) (255 / (((float) r / (float) dstR)));
resultG = (int) (255 / (((float) g / (float) dstG)));
resultB = (int) (255 / (((float) b / (float) dstB)));
srcPixels[x + y * width] = Color.argb(resultA, resultR, resultG, resultB);
});
result.setPixels(srcPixels, 0, width, 0, 0, width, height);
return result;
}
③ 反相:
Img输出 = 1 – Img ;
这个比较容易 直接用255 - 原色值:
public static void 反相(Bitmap target) {
final int width = target.getWidth(), height = target.getHeight();
int[] targetPixels = new int[width * height];
getBitmapPixelColor(target, (x, y, a, r, g, b) -> {
int max = 255;
targetPixels[x + y * width] = Color.argb(a, max - r, max - g, max - b);
});
target.setPixels(targetPixels, 0, width, 0, 0, width, height);
}
我们来看看整个流程是怎么样的:
图层1、图层2去色;
图层1调亮后反相、图层2调暗;
图层1相对于图层2作 线性减淡(添加)特效;
取 作用特效后的图层(1、2) 的红色通道;
最后,将红色通道作为图层2的蒙板;
蒙板后的图层就是我们想要的 隐藏图片了,保存为png即可;
贴出上面所须的代码:
去色:
/**
* 将目标bitmap去色 (变成黑白图片)
*
* @param target 目标bitmap
*/
public static void 去色(Bitmap target) {
final int width = target.getWidth(), height = target.getHeight();
int[] targetPixels = new int[width * height];
getBitmapPixelColor(target, (x, y, a, r, g, b) -> {
int gray = (r + g + b) / 3;
targetPixels[x + y * width] = Color.argb(a, gray, gray, gray);
});
target.setPixels(targetPixels, 0, width, 0, 0, width, height);
}
取红色通道:
/**
* 效果类似PhotoShop中的取图层红色通道中的选区
* (因为我们等下的蒙板只是取目标bitmap的透明度, 而反相和调整色阶都只是更改rgb色值,
* 并没有影响到透明度, 所以我们为了执行效率, 直接把red色值作为透明度后返回,
* 当然, 如需和PhotoShop上的 红色通道 的选区效果一样, 可以把下面的3行注释取消, 其实不会影响到最终生成图片的效果的)
*
* @param target 目标bitmap
* @return 目标bitmap的红色通道
*/
public static Bitmap 红色通道(Bitmap target) {
final int width = target.getWidth(), height = target.getHeight();
int[] targetPixels = new int[width * height];
final Bitmap result = Bitmap.createBitmap(target.getWidth(), target.getHeight(), Bitmap.Config.ARGB_8888);
getBitmapPixelColor(target, (x, y, a, r, g, b) -> targetPixels[x + y * width] = Color.argb(r, r, g, b));
result.setPixels(targetPixels, 0, width, 0, 0, width, height);
// 反相(result);
// for (int i = 0; i < 5; i++)
// 调暗色阶(result);
return result;
}
蒙板效果, 其实也就是将图片的透明度换成蒙板的透明度:
/**
* 将bitmap的透明度换成目标bitmap的透明度
*
* @param src 作用蒙板的bitmap
* @param target 目标bitmap
* @return 添加蒙板后的bitmap
*/
public static Bitmap 蒙版(Bitmap src, Bitmap target) {
final int width = src.getWidth(), height = src.getHeight();
int[] srcPixels = new int[width * height];
final Bitmap result = Bitmap.createBitmap(src.getWidth(), src.getHeight(), Bitmap.Config.ARGB_8888);
getBitmapPixelColor(src, (x, y, a, r, g, b) -> {
int dstA, dstPixelColor;
dstPixelColor = target.getPixel(x, y);
dstA = Color.alpha(dstPixelColor);
srcPixels[x + y * width] = Color.argb(dstA, r, g, b);
});
result.setPixels(srcPixels, 0, width, 0, 0, width, height);
return result;
}
我们将以上的一堆东西封装起来:
@SuppressWarnings({"unused", "WeakerAccess"})
/**
* 不提倡用中文写代码,本工具使用中文只是为了更容易理解
*/
public class BitmapPixelUtil {
/**
* 亮, 暗色阶映射表
*/
private static int[] mLightColorTable, mDarkColorTable;
/**
* 生成隐藏图片
*
* @param outerBitmap 要在外面才显示的bitmap
* @param interBitmap 要在里面才显示的bitmap
* @return 隐藏图片
*/
public static Bitmap makeHideImage(Bitmap outerBitmap, Bitmap interBitmap) {
return makeHideImage(outerBitmap, interBitmap, null);
}
/**
* 生成隐藏图片
*
* @param outerBitmap 要在外面才显示的bitmap
* @param interBitmap 要在里面才显示的bitmap
* @param listener 进度监听
* @return 隐藏图片
*/
public static Bitmap makeHideImage(Bitmap outerBitmap, Bitmap interBitmap, OnProgressUpdateListener listener) {
if (checkBitmapCanUse(outerBitmap) && checkBitmapCanUse(interBitmap)) {
updateListener(listener, 0.1F);
去色(outerBitmap);
updateListener(listener, 0.2F);
调亮色阶(outerBitmap);
updateListener(listener, 0.3F);
反相(outerBitmap);
updateListener(listener, 0.4F);
去色(interBitmap);
updateListener(listener, 0.5F);
调暗色阶(interBitmap);
updateListener(listener, 0.6F);
outerBitmap = 线性减淡(outerBitmap, interBitmap);
updateListener(listener, 0.7F);
Bitmap temp = 红色通道(outerBitmap);
updateListener(listener, 0.8F);
outerBitmap = 划分(outerBitmap, interBitmap);
updateListener(listener, 0.9F);
Bitmap result = 蒙版(outerBitmap, temp);
updateListener(listener, 1);
return result;
}
return null;
}
private static void updateListener(OnProgressUpdateListener listener, float progress) {
if (listener != null)
listener.onUpdate(progress);
}
/**
* 将目标bitmap去色 (变成黑白图片)
*
* @param target 目标bitmap
*/
public static void 去色(Bitmap target) {
final int width = target.getWidth(), height = target.getHeight();
int[] targetPixels = new int[width * height];
getBitmapPixelColor(target, (x, y, a, r, g, b) -> {
int gray = (r + g + b) / 3;
targetPixels[x + y * width] = Color.argb(a, gray, gray, gray);
});
target.setPixels(targetPixels, 0, width, 0, 0, width, height);
}
public static void 调亮色阶(Bitmap target) {
changeColorLevel(target, true);
}
public static void 调暗色阶(Bitmap target) {
changeColorLevel(target, false);
}
public static void 反相(Bitmap target) {
final int width = target.getWidth(), height = target.getHeight();
int[] targetPixels = new int[width * height];
getBitmapPixelColor(target, (x, y, a, r, g, b) -> {
int max = 255;
targetPixels[x + y * width] = Color.argb(a, max - r, max - g, max - b);
});
target.setPixels(targetPixels, 0, width, 0, 0, width, height);
}
/**
* 图层特效 效果等于PhotoShop中图层特效的线性减淡(增加)
*
* @param src 作用特效的bitmap
* @param target 目标bitmap
* @return 作用特效后的bitmap
*/
public static Bitmap 线性减淡(Bitmap src, Bitmap target) {
final int width = src.getWidth(), height = src.getHeight();
int[] srcPixels = new int[width * height];
final Bitmap result = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
getBitmapPixelColor(src, (x, y, a, r, g, b) -> {
int dstR, dstG, dstB, dstPixelColor, resultA, resultR, resultG, resultB;
dstPixelColor = target.getPixel(x, y);
dstR = Color.red(dstPixelColor);
dstG = Color.green(dstPixelColor);
dstB = Color.blue(dstPixelColor);
resultA = 255;
resultR = r + dstR;
resultG = g + dstG;
resultB = b + dstB;
if (resultR > 255)
resultR = 255;
if (resultG > 255)
resultG = 255;
if (resultB > 255)
resultB = 255;
// result.setPixel(x, y, Color.argb(resultA, resultR, resultG, resultB));
srcPixels[x + y * width] = Color.argb(resultA, resultR, resultG, resultB);
});
result.setPixels(srcPixels, 0, width, 0, 0, width, height);
return result;
}
/**
* 效果类似PhotoShop中的取图层红色通道中的选区
* (因为我们等下的蒙板只是取目标bitmap的透明度, 而反相和调整色阶都只是更改rgb色值,
* 并没有影响到透明度, 所以我们为了执行效率, 直接把red色值作为透明度后返回,
* 当然, 如需和PhotoShop上的 红色通道 的选区效果一样, 可以把下面的3行注释取消, 其实不会影响到最终生成图片的效果的)
*
* @param target 目标bitmap
* @return 目标bitmap的红色通道
*/
public static Bitmap 红色通道(Bitmap target) {
final int width = target.getWidth(), height = target.getHeight();
int[] targetPixels = new int[width * height];
final Bitmap result = Bitmap.createBitmap(target.getWidth(), target.getHeight(), Bitmap.Config.ARGB_8888);
getBitmapPixelColor(target, (x, y, a, r, g, b) -> targetPixels[x + y * width] = Color.argb(r, r, g, b));
result.setPixels(targetPixels, 0, width, 0, 0, width, height);
// 反相(result);
// for (int i = 0; i < 5; i++)
// 调暗色阶(result);
return result;
}
/**
* 图层特效 效果等于PhotoShop中图层特效的划分
*
* @param src 作用特效的bitmap
* @param target 目标bitmap
* @return 作用特效后的bitmap
*/
public static Bitmap 划分(Bitmap src, Bitmap target) {
final int width = src.getWidth(), height = src.getHeight();
int[] srcPixels = new int[width * height];
final Bitmap result = Bitmap.createBitmap(src.getWidth(), src.getHeight(), Bitmap.Config.ARGB_8888);
getBitmapPixelColor(src, (x, y, a, r, g, b) -> {
int dstR, dstG, dstB, dstPixelColor, resultA, resultR, resultG, resultB;
dstPixelColor = target.getPixel(x, y);
dstR = Color.red(dstPixelColor);
dstG = Color.green(dstPixelColor);
dstB = Color.blue(dstPixelColor);
resultA = 255;
resultR = (int) (255 / (((float) r / (float) dstR)));
resultG = (int) (255 / (((float) g / (float) dstG)));
resultB = (int) (255 / (((float) b / (float) dstB)));
// result.setPixel(x, y, Color.argb(resultA, resultR, resultG, resultB));
srcPixels[x + y * width] = Color.argb(resultA, resultR, resultG, resultB);
});
result.setPixels(srcPixels, 0, width, 0, 0, width, height);
return result;
}
/**
* 将bitmap的透明度换成目标bitmap的透明度
*
* @param src 作用蒙板的bitmap
* @param target 目标bitmap
* @return 添加蒙板后的bitmap
*/
public static Bitmap 蒙版(Bitmap src, Bitmap target) {
final int width = src.getWidth(), height = src.getHeight();
int[] srcPixels = new int[width * height];
final Bitmap result = Bitmap.createBitmap(src.getWidth(), src.getHeight(), Bitmap.Config.ARGB_8888);
getBitmapPixelColor(src, (x, y, a, r, g, b) -> {
int dstA, dstPixelColor;
dstPixelColor = target.getPixel(x, y);
dstA = Color.alpha(dstPixelColor);
srcPixels[x + y * width] = Color.argb(dstA, r, g, b);
});
result.setPixels(srcPixels, 0, width, 0, 0, width, height);
return result;
}
public static Bitmap scaleBitmap(Bitmap target, int w, int h) {
if (target == null || target.isRecycled()) return target;
int width = target.getWidth(), height = target.getHeight();
Matrix matrix = new Matrix();
matrix.postScale(((float) w / width), ((float) h / height));
return Bitmap.createBitmap(target, 0, 0, width, height, matrix, true);
}
private static void changeColorLevel(Bitmap target, boolean isToLight) {
final int width = target.getWidth(), height = target.getHeight();
int[] targetPixels = new int[width * height];
int[] table = isToLight ? getLightColorTable() : getDarkColorTable();
getBitmapPixelColor(target, (x, y, a, r, g, b) -> targetPixels[x + y * width] = Color.argb(a, table[r], table[g], table[b]));
target.setPixels(targetPixels, 0, width, 0, 0, width, height);
}
private static boolean checkBitmapCanUse(Bitmap target) {
return target != null && !target.isRecycled() && target.isMutable();
}
private static void getBitmapPixelColor(Bitmap target, PixelColorHandler handler) {
if (checkBitmapCanUse(target) && handler != null) {
int a, r, g, b, pixelColor;
for (int y = 0; y < target.getHeight(); y++) {
for (int x = 0; x < target.getWidth(); x++) {
pixelColor = target.getPixel(x, y);
a = Color.alpha(pixelColor);
r = Color.red(pixelColor);
g = Color.green(pixelColor);
b = Color.blue(pixelColor);
handler.onHandle(x, y, a, r, g, b);
}
}
}
}
private static int[] getLightColorTable() {
if (mLightColorTable == null)
initLightColorTable();
return mLightColorTable;
}
private static int[] getDarkColorTable() {
if (mDarkColorTable == null)
initDarkColorTable();
return mDarkColorTable;
}
private static void initLightColorTable() {
// 输出色阶 120 ~ 255 的映射表
// 由 getColorLevelTable(120, 255); 得来
mLightColorTable = new int[]{
120, 120, 121, 121, 122, 122, 123, 123, 124, 124, 125, 125, 126, 126, 127, 127, 128, 128,
129, 129, 130, 130, 131, 132, 132, 133, 133, 134, 134, 135, 135, 136, 136, 137, 137, 138,
138, 139, 139, 140, 140, 141, 142, 142, 143, 143, 144, 144, 145, 145, 146, 146, 147, 147,
148, 148, 149, 149, 150, 150, 151, 152, 152, 153, 153, 154, 154, 155, 155, 156, 156, 157,
157, 158, 158, 159, 159, 160, 161, 161, 162, 162, 163, 163, 164, 164, 165, 165, 166, 166,
167, 167, 168, 168, 169, 170, 170, 171, 171, 172, 172, 173, 173, 174, 174, 175, 175, 176,
176, 177, 177, 178, 179, 179, 180, 180, 181, 181, 182, 182, 183, 183, 184, 184, 185, 185,
186, 186, 187, 188, 188, 189, 189, 190, 190, 191, 191, 192, 192, 193, 193, 194, 194, 195,
195, 196, 197, 197, 198, 198, 199, 199, 200, 200, 201, 201, 202, 202, 203, 203, 204, 205,
205, 206, 206, 207, 207, 208, 208, 209, 209, 210, 210, 211, 211, 212, 212, 213, 214, 214,
215, 215, 216, 216, 217, 217, 218, 218, 219, 219, 220, 220, 221, 222, 222, 223, 223, 224,
224, 225, 225, 226, 226, 227, 227, 228, 228, 229, 229, 230, 231, 231, 232, 232, 233, 233,
234, 234, 235, 235, 236, 236, 237, 237, 238, 239, 239, 240, 240, 241, 241, 242, 242, 243,
243, 244, 244, 245, 245, 246, 247, 247, 248, 248, 249, 249, 250, 250, 251, 251, 252, 252,
253, 253, 254, 255,
};
}
private static void initDarkColorTable() {
// 输出色阶 0 ~ 135 的映射表
// 由 getColorLevelTable(0, 135); 得来
mDarkColorTable = new int[]{0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9, 10,
10, 11, 12, 12, 13, 13, 14, 14, 15, 15, 16, 16, 17, 17, 18, 18, 19, 19, 20, 20, 21,
22, 22, 23, 23, 24, 24, 25, 25, 26, 26, 27, 27, 28, 28, 29, 29, 30, 30, 31, 32, 32,
33, 33, 34, 34, 35, 35, 36, 36, 37, 37, 38, 38, 39, 39, 40, 41, 41, 42, 42, 43, 43,
44, 44, 45, 45, 46, 46, 47, 47, 48, 48, 49, 50, 50, 51, 51, 52, 52, 53, 53, 54, 54,
55, 55, 56, 56, 57, 57, 58, 59, 59, 60, 60, 61, 61, 62, 62, 63, 63, 64, 64, 65, 65,
66, 66, 67, 68, 68, 69, 69, 70, 70, 71, 71, 72, 72, 73, 73, 74, 74, 75, 75, 76, 77,
77, 78, 78, 79, 79, 80, 80, 81, 81, 82, 82, 83, 83, 84, 85, 85, 86, 86, 87, 87, 88,
88, 89, 89, 90, 90, 91, 91, 92, 92, 93, 94, 94, 95, 95, 96, 96, 97, 97, 98, 98, 99,
99, 100, 100, 101, 102, 102, 103, 103, 104, 104, 105, 105, 106, 106, 107, 107, 108,
108, 109, 109, 110, 111, 111, 112, 112, 113, 113, 114, 114, 115, 115, 116, 116, 117,
117, 118, 119, 119, 120, 120, 121, 121, 122, 122, 123, 123, 124, 124, 125, 125, 126,
127, 127, 128, 128, 129, 129, 130, 130, 131, 131, 132, 132, 133, 133, 134, 135,
};
}
/**
* @param outputMin 输出色阶
* @param outputMax 输出色阶
* @return 色值映射表
*/
private static int[] getColorLevelTable(int outputMin, int outputMax) {
int[] data = new int[256];
int inputMin = 0, inputMiddle = 128, inputMax = 255;
if (outputMin < 0) outputMin = 0;
if (outputMin > 255) outputMin = 255;
if (outputMax < 0) outputMax = 0;
if (outputMax > 255) outputMax = 255;
for (int index = 0; index <= 255; index++) {
double temp = index - inputMin;
if (temp < 0) {
temp = outputMin;
} else if (temp + inputMin > inputMax) {
temp = outputMax;
} else {
double gamma = Math.log(0.5) / Math.log((double) (inputMiddle - inputMin) / (inputMax - inputMin));
temp = outputMin + (outputMax - outputMin) * Math.pow((temp / (inputMax - inputMin)), gamma);
}
if (temp > 255)
temp = 255;
else if (temp < 0)
temp = 0;
data[index] = (int) temp;
}
return data;
}
private interface PixelColorHandler {
void onHandle(int x, int y, int a, int r, int g, int b);
}
public interface OnProgressUpdateListener {
void onUpdate(float progress);
}
}
其实这个工具类还有优化的地方,就是把target.getPixel这个获取单个像素换成getPixels方法一次换取全部像素存在数组里面,再来遍历:
int width = target.getWidth(), height = target.getHeight();
int[] targetPixels = new int[width * height];
//获取bitmap所有像素点
target.getPixels(targetPixels, 0, width, 0, 0, width, height);
int pixelColor;
int a, r, g, b;
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
pixelColor = targetPixels[index];
.........
}
}
好, 现在我们来看看效果:
哈哈,完成了。
获取色阶映射表那个方法 参考自http://www.pythontip.com/acm/post/203
完整Demo见 https://github.com/wuyr/HideImageMaker
有错误的地方请指出,谢谢大家!