iOS——隱形水印的實(shí)現(xiàn)和『顏色加深』算法

原文作者:小魚周凌宇
原文地址:https://juejin.im/post/5cd17612f265da037a3d0183

很多 APP 都在敏感頁面有水印,主要為了應(yīng)對輿情時(shí)可以追蹤圖片來源货葬,一般在水印上都會有員工或用戶 ID 和昵稱煎饼。

水印的用途總結(jié)有亮點(diǎn):

  1. 追蹤來源
  2. 威懾作用

威懾作用是指當(dāng)用戶看到水印時(shí),會自覺避免違法傳輿行為。

但是月趟,當(dāng)不需要威懾作用時(shí)莉测,例如,為了保持應(yīng)用或者圖片的美觀柔袁,顯形的水印似乎不是那么必要呆躲,這時(shí)候可以考慮使用隱形水印。

最近在同事在知乎上看到一種水印捶索。

如下圖插掂,表面似乎沒有什么水印

但通過 PS 的混色模式處理后,隱形水印就顯示出來了

具體處理方式是

  1. 在原圖上圖層添加全黑圖層
  2. 全黑圖層選擇『顏色加深』

到此為止腥例,我對 PS 的算法產(chǎn)生了好奇辅甥,混色模式是常用工具,但是以前沒有注意過公式燎竖。

顏色加深混色模式

PS 的混色模式璃弄,其實(shí)是底圖和混色層的每個(gè)像素點(diǎn),經(jīng)過一系列計(jì)算后得到的結(jié)果層构回。

翻閱了一系列資料后我發(fā)現(xiàn)夏块,現(xiàn)有的公式都是不正確的,有些熱門文章里也不對纤掸。而 PS 官方文檔只對幾種混色模式進(jìn)行了介紹脐供,而并沒有給出公式。

查看每個(gè)通道中的顏色信息借跪,并通過增加二者之間的對比度使基色變暗以反映出混合色政己。與白色混合后不產(chǎn)生變化。

helpx.adobe.com/cn/photosho…

比較多的是這套公式(是有問題的):

結(jié)果色 = 基色-[(255-基色)×(255-混合色)]/混合色

公式中(255-基色)和(255-混合色)分別是基色和混合色的反相掏愁。

  1. 若混合色為0(黑色)歇由,(基色×混合色)為0,得到的數(shù)值為一相個(gè)負(fù)值托猩,歸為0印蓖,所以不論基色為何值均為0。
  2. 當(dāng)混合色的色階值是255(白色)時(shí)京腥,混合色同基色赦肃。

基本查到的算法公式都有一個(gè)致命問題,公式都標(biāo)明了,任何顏色和黑色混色結(jié)果為黑色他宛,這顯然與上文中 PS 處理結(jié)果不符合船侧。如果按照這套理論,整個(gè)圖片都應(yīng)該黑了厅各。

最后我試出來一個(gè)接近的方案是:

  1. 結(jié)果色 = 基色 —(基色反相×混合色反相)/ 混合色
  2. 如混色層為黑色镜撩,則認(rèn)為 RGB 為 (255, 255, 255),即非常深的灰色

這個(gè)公式可以基本實(shí)現(xiàn) PS 中的顏色加深效果队塘。可以將淺色變深袁梗,越淺越深

隱形水印的實(shí)現(xiàn)

添加水印

首先介紹 iOS 中的基本圖像處理方式:

  1. 獲取圖片的所有像素點(diǎn)
  2. 改變指針指向的像素信息
+ (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

image

<figcaption></figcaption>

ZLYInvisibleWatermark

</article>

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末合敦,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子验游,更是在濱河造成了極大的恐慌蛤肌,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,194評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件批狱,死亡現(xiàn)場離奇詭異,居然都是意外死亡展东,警方通過查閱死者的電腦和手機(jī)赔硫,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,058評論 2 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來盐肃,“玉大人爪膊,你說我怎么就攤上這事≡彝酰” “怎么了推盛?”我有些...
    開封第一講書人閱讀 156,780評論 0 346
  • 文/不壞的土叔 我叫張陵,是天一觀的道長谦铃。 經(jīng)常有香客問我耘成,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,388評論 1 283
  • 正文 為了忘掉前任瘪菌,我火速辦了婚禮撒会,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘师妙。我一直安慰自己诵肛,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,430評論 5 384
  • 文/花漫 我一把揭開白布默穴。 她就那樣靜靜地躺著怔檩,像睡著了一般。 火紅的嫁衣襯著肌膚如雪蓄诽。 梳的紋絲不亂的頭發(fā)上薛训,一...
    開封第一講書人閱讀 49,764評論 1 290
  • 那天,我揣著相機(jī)與錄音若专,去河邊找鬼许蓖。 笑死,一個(gè)胖子當(dāng)著我的面吹牛调衰,可吹牛的內(nèi)容都是我干的膊爪。 我是一名探鬼主播,決...
    沈念sama閱讀 38,907評論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼嚎莉,長吁一口氣:“原來是場噩夢啊……” “哼米酬!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起趋箩,我...
    開封第一講書人閱讀 37,679評論 0 266
  • 序言:老撾萬榮一對情侶失蹤赃额,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后叫确,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體跳芳,經(jīng)...
    沈念sama閱讀 44,122評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,459評論 2 325
  • 正文 我和宋清朗相戀三年竹勉,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了飞盆。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,605評論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡次乓,死狀恐怖吓歇,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情票腰,我是刑警寧澤城看,帶...
    沈念sama閱讀 34,270評論 4 329
  • 正文 年R本政府宣布,位于F島的核電站杏慰,受9級特大地震影響测柠,放射性物質(zhì)發(fā)生泄漏炼鞠。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,867評論 3 312
  • 文/蒙蒙 一鹃愤、第九天 我趴在偏房一處隱蔽的房頂上張望簇搅。 院中可真熱鬧,春花似錦软吐、人聲如沸瘩将。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,734評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽姿现。三九已至,卻和暖如春肖抱,著一層夾襖步出監(jiān)牢的瞬間备典,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,961評論 1 265
  • 我被黑心中介騙來泰國打工意述, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留提佣,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,297評論 2 360
  • 正文 我出身青樓荤崇,卻偏偏與公主長得像拌屏,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子术荤,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,472評論 2 348

推薦閱讀更多精彩內(nèi)容