原文作者:小魚周凌宇
原文地址:https://juejin.im/post/5cd17612f265da037a3d0183
很多 APP 都在敏感頁面有水印,主要為了應(yīng)對輿情時(shí)可以追蹤圖片來源货葬,一般在水印上都會有員工或用戶 ID 和昵稱煎饼。
水印的用途總結(jié)有亮點(diǎn):
- 追蹤來源
- 威懾作用
威懾作用是指當(dāng)用戶看到水印時(shí),會自覺避免違法傳輿行為。
但是月趟,當(dāng)不需要威懾作用時(shí)莉测,例如,為了保持應(yīng)用或者圖片的美觀柔袁,顯形的水印似乎不是那么必要呆躲,這時(shí)候可以考慮使用隱形水印。
最近在同事在知乎上看到一種水印捶索。
如下圖插掂,表面似乎沒有什么水印
但通過 PS 的混色模式處理后,隱形水印就顯示出來了
具體處理方式是
- 在原圖上圖層添加全黑圖層
- 全黑圖層選擇『顏色加深』
到此為止腥例,我對 PS 的算法產(chǎn)生了好奇辅甥,混色模式是常用工具,但是以前沒有注意過公式燎竖。
顏色加深混色模式
PS 的混色模式璃弄,其實(shí)是底圖和混色層的每個(gè)像素點(diǎn),經(jīng)過一系列計(jì)算后得到的結(jié)果層构回。
翻閱了一系列資料后我發(fā)現(xiàn)夏块,現(xiàn)有的公式都是不正確的,有些熱門文章里也不對纤掸。而 PS 官方文檔只對幾種混色模式進(jìn)行了介紹脐供,而并沒有給出公式。
查看每個(gè)通道中的顏色信息借跪,并通過增加二者之間的對比度使基色變暗以反映出混合色政己。與白色混合后不產(chǎn)生變化。
比較多的是這套公式(是有問題的):
結(jié)果色 = 基色-[(255-基色)×(255-混合色)]/混合色
公式中(255-基色)和(255-混合色)分別是基色和混合色的反相掏愁。
- 若混合色為0(黑色)歇由,(基色×混合色)為0,得到的數(shù)值為一相個(gè)負(fù)值托猩,歸為0印蓖,所以不論基色為何值均為0。
- 當(dāng)混合色的色階值是255(白色)時(shí)京腥,混合色同基色赦肃。
基本查到的算法公式都有一個(gè)致命問題,公式都標(biāo)明了,任何顏色和黑色混色結(jié)果為黑色他宛,這顯然與上文中 PS 處理結(jié)果不符合船侧。如果按照這套理論,整個(gè)圖片都應(yīng)該黑了厅各。
最后我試出來一個(gè)接近的方案是:
- 結(jié)果色 = 基色 —(基色反相×混合色反相)/ 混合色
- 如混色層為黑色镜撩,則認(rèn)為 RGB 為 (255, 255, 255),即非常深的灰色
這個(gè)公式可以基本實(shí)現(xiàn) PS 中的顏色加深效果队塘。可以將淺色變深袁梗,越淺越深。
隱形水印的實(shí)現(xiàn)
添加水印
首先介紹 iOS 中的基本圖像處理方式:
- 獲取圖片的所有像素點(diǎn)
- 改變指針指向的像素信息
+ (UIImage *)addWatermark:(UIImage *)image
text:(NSString *)text {
UIFont *font = [UIFont systemFontOfSize:32];
NSDictionary *attributes = @{NSFontAttributeName: font,
NSForegroundColorAttributeName: [UIColor colorWithRed:0
green:0
blue:0
alpha:0.01]};
UIImage *newImage = [image copy];
CGFloat x = 0.0;
CGFloat y = 0.0;
CGFloat idx0 = 0;
CGFloat idx1 = 0;
CGSize textSize = [text sizeWithAttributes:attributes];
while (y < image.size.height) {
y = (textSize.height * 2) * idx1;
while (x < image.size.width) {
@autoreleasepool {
x = (textSize.width * 2) * idx0;
newImage = [self addWatermark:newImage
text:text
textPoint:CGPointMake(x, y)
attributedString:attributes];
}
idx0 ++;
}
x = 0;
idx0 = 0;
idx1 ++;
}
return newImage;
}
+ (UIImage *)addWatermark:(UIImage *)image
text:(NSString *)text
textPoint:(CGPoint)point
attributedString:(NSDictionary *)attributes {
UIGraphicsBeginImageContext(image.size);
[image drawInRect:CGRectMake(0,0, image.size.width, image.size.height)];
CGSize textSize = [text sizeWithAttributes:attributes];
[text drawInRect:CGRectMake(point.x, point.y, textSize.width, textSize.height) withAttributes:attributes];
UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return newImage;
}
顯示水印
通過上文提到的公式憔古,可以讓水印顯示遮怜。
+ (UIImage *)visibleWatermark:(UIImage *)image {
// 1\. Get the raw pixels of the image
// 定義 32位整形指針 *inputPixels
UInt32 * inputPixels;
//轉(zhuǎn)換圖片為CGImageRef,獲取參數(shù):長寬高,每個(gè)像素的字節(jié)數(shù)(4)鸿市,每個(gè)R的比特?cái)?shù)
CGImageRef inputCGImage = [image CGImage];
NSUInteger inputWidth = CGImageGetWidth(inputCGImage);
NSUInteger inputHeight = CGImageGetHeight(inputCGImage);
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
NSUInteger bytesPerPixel = 4;
NSUInteger bitsPerComponent = 8;
// 每行字節(jié)數(shù)
NSUInteger inputBytesPerRow = bytesPerPixel * inputWidth;
// 開辟內(nèi)存區(qū)域,指向首像素地址
inputPixels = (UInt32 *)calloc(inputHeight * inputWidth, sizeof(UInt32));
// 根據(jù)指針锯梁,前面的參數(shù),創(chuàng)建像素層
CGContextRef context = CGBitmapContextCreate(inputPixels, inputWidth, inputHeight,
bitsPerComponent, inputBytesPerRow, colorSpace,
kCGImageAlphaPremultipliedLast | kCGBitmapByteOrder32Big);
//根據(jù)目前像素在界面繪制圖像
CGContextDrawImage(context, CGRectMake(0, 0, inputWidth, inputHeight), inputCGImage);
// 像素處理
for (int j = 0; j < inputHeight; j++) {
for (int i = 0; i < inputWidth; i++) {
@autoreleasepool {
UInt32 *currentPixel = inputPixels + (j * inputWidth) + i;
UInt32 color = *currentPixel;
UInt32 thisR,thisG,thisB,thisA;
// 這里直接移位獲得RBGA的值,以及輸出寫的非常好焰情!
thisR = R(color);
thisG = G(color);
thisB = B(color);
thisA = A(color);
UInt32 newR,newG,newB;
newR = [self mixedCalculation:thisR];
newG = [self mixedCalculation:thisG];
newB = [self mixedCalculation:thisB];
*currentPixel = RGBAMake(newR,
newG,
newB,
thisA);
}
}
}
//創(chuàng)建新圖
// 4\. Create a new UIImage
CGImageRef newCGImage = CGBitmapContextCreateImage(context);
UIImage * processedImage = [UIImage imageWithCGImage:newCGImage];
//釋放
// 5\. Cleanup!
CGColorSpaceRelease(colorSpace);
CGContextRelease(context);
free(inputPixels);
return processedImage;
}
+ (int)mixedCalculation:(int)originValue {
// 結(jié)果色 = 基色 —(基色反相×混合色反相)/ 混合色
int mixValue = 1;
int resultValue = 0;
if (mixValue == 0) {
resultValue = 0;
} else {
resultValue = originValue - (255 - originValue) * (255 - mixValue) / mixValue;
}
if (resultValue < 0) {
resultValue = 0;
}
return resultValue;
}
代碼和開源庫
為了方便使用陌凳,寫了一個(gè)開源庫,封裝的很實(shí)用内舟,附帶 DEMO
<figcaption></figcaption>
</article>