不少 APP 都在敏感頁面有水印,主要爲了應對輿情時能夠追蹤圖片來源,通常在水印上都會有員工或用戶 ID 和暱稱。html
水印的用途總結有亮點:git
威懾做用是指當用戶看到水印時,會自覺避免違法傳輿行爲。github
可是,當不須要威懾做用時,例如,爲了保持應用或者圖片的美觀,顯形的水印彷佛不是那麼必要,這時候能夠考慮使用隱形水印。算法
最近在同事在知乎上看到一種水印。工具
以下圖,表面彷佛沒有什麼水印ui
但經過 PS 的混色模式處理後,隱形水印就顯示出來了this
具體處理方式是spa
到此爲止,我對 PS 的算法產生了好奇,混色模式是經常使用工具,可是之前沒有注意過公式。指針
PS 的混色模式,實際上是底圖和混色層的每一個像素點,通過一系列計算後獲得的結果層。code
翻閱了一系列資料後我發現,現有的公式都是不正確的,有些熱門文章裏也不對。而 PS 官方文檔只對幾種混色模式進行了介紹,而並無給出公式。
查看每一個通道中的顏色信息,並經過增長兩者之間的對比度使基色變暗以反映出混合色。與白色混合後不產生變化。
比較多的是這套公式(是有問題的):
結果色 = 基色-[(255-基色)×(255-混合色)]/混合色
公式中(255-基色)和(255-混合色)分別是基色和混合色的反相。
- 若混合色爲0(黑色),(基色×混合色)爲0,獲得的數值爲一相個負值,歸爲0,因此不論基色爲什麼值均爲0。
- 當混合色的色階值是255(白色)時,混合色同基色。
基本查到的算法公式都有一個致命問題,公式都標明瞭,任何顏色和黑色混色結果爲黑色,這顯然與上文中 PS 處理結果不符合。若是按照這套理論,整個圖片都應該黑了。
最後我試出來一個接近的方案是:
這個公式能夠基本實現 PS 中的顏色加深效果。能夠將淺色變深,越淺越深。
首先介紹 iOS 中的基本圖像處理方式:
+ (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;
//轉換圖片爲CGImageRef,獲取參數:長寬高,每一個像素的字節數(4),每一個R的比特數
CGImageRef inputCGImage = [image CGImage];
NSUInteger inputWidth = CGImageGetWidth(inputCGImage);
NSUInteger inputHeight = CGImageGetHeight(inputCGImage);
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
NSUInteger bytesPerPixel = 4;
NSUInteger bitsPerComponent = 8;
// 每行字節數
NSUInteger inputBytesPerRow = bytesPerPixel * inputWidth;
// 開闢內存區域,指向首像素地址
inputPixels = (UInt32 *)calloc(inputHeight * inputWidth, sizeof(UInt32));
// 根據指針,前面的參數,建立像素層
CGContextRef context = CGBitmapContextCreate(inputPixels, inputWidth, inputHeight,
bitsPerComponent, inputBytesPerRow, colorSpace,
kCGImageAlphaPremultipliedLast | kCGBitmapByteOrder32Big);
//根據目前像素在界面繪製圖像
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);
}
}
}
//建立新圖
// 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 {
// 結果色 = 基色 —(基色反相×混合色反相)/ 混合色
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;
}
複製代碼
爲了方便使用,寫了一個開源庫,封裝的很實用,附帶 DEMO