Source code

IOS - Implementation of invisible watermark and "color deepening" algorithm

Many apps have watermarks on sensitive pages, mainly to track the image source when responding to public opinion. Generally, there are employee or user IDs and nicknames on the watermarks.

 image.png

Highlights of the use of watermarks:

  1. Tracking source

  2. Deterrent effect

Deterrence means that when users see the watermark, they will consciously avoid illegal public opinion dissemination.

However, when deterrence is not required, for example, in order to maintain the beauty of applications or images, explicit watermarks do not seem to be necessary. At this time, invisible watermarks can be considered.

Recently, my colleague saw a watermark on Zhihu.

As shown in the figure below, there seems to be no watermark on the surface

 image.png

But after processing in PS's color mixing mode, the invisible watermark will appear

 image.png The specific processing method is

  1. Add an all black layer to the original image

  2. Select "Color deepening" for all black layer

So far, I am curious about the algorithm of PS. Color mixing mode is a common tool, but I have not paid attention to the formula before.

Color deepening and mixing mode


The color mixing mode of PS is actually the result layer of each pixel of the base map and color mixing layer after a series of calculations.

After reading a series of materials, I found that the existing formulas are incorrect, and some popular articles are also incorrect. The PS official document only introduces several color mixing modes, but does not give formulas.

View the color information in each channel, and darken the base color to reflect the mixed color by increasing the contrast between them. No change after mixing with white.

helpx.adobe.com/cn/photosho…

This formula is more common (problematic):

Result color=primary color - [(255 primary color) × (255 mixed color)]/mixed color

In the formula, (255 primary color) and (255 mixed color) are the opposite of the primary color and the mixed color, respectively.

  1. If the mixed color is 0 (black) and (primary color × mixed color) is 0, the value obtained is a negative value of one phase, which is 0, so no matter what the primary color is, it is 0.

  2. When the color scale value of the mixed color is 255 (white), the mixed color is the same as the base color.

Basically, all the algorithms and formulas found have a fatal problem, which is indicated in the formulas, Any color and black mixing result is black, which is obviously inconsistent with the PS processing result above According to this theory, the whole picture should be black.

At last, I tried out a close plan:

  1. Result color=primary color - (primary color inversion × mixed color inversion)/mixed color

  2. If the color mixing layer is black, RGB is considered as (255, 255, 255), that is, very dark gray

This formula can basically realize the color deepening effect in PS. You can darken the light color, the lighter the darker.

Implementation of invisible watermark


Add Watermark

First, introduce the basic image processing methods in iOS:

  1. Get all pixels of the picture

  2. Change the pixel information pointed by the pointer

 + (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 = ;     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; }

Show Watermark

The watermark can be displayed through the formula mentioned above.

 + (UIImage *)visibleWatermark:(UIImage *)image {     // 1. Get the raw pixels of the image //Define 32-bit integer pointer * inputPixels     UInt32 * inputPixels;      //Convert the picture to CGImageRef, and obtain parameters: length, width, height, bytes per pixel (4), and bits per R     CGImageRef inputCGImage = [image CGImage];     NSUInteger inputWidth = CGImageGetWidth(inputCGImage);     NSUInteger inputHeight = CGImageGetHeight(inputCGImage);     CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();          NSUInteger bytesPerPixel = 4;     NSUInteger bitsPerComponent = 8;      //Bytes per line     NSUInteger inputBytesPerRow = bytesPerPixel * inputWidth;      //Open memory area and point to the first pixel address     inputPixels = (UInt32 *)calloc(inputHeight * inputWidth, sizeof(UInt32));      //Create pixel layer according to pointer and previous parameters     CGContextRef context = CGBitmapContextCreate(inputPixels, inputWidth, inputHeight,                                                  bitsPerComponent, inputBytesPerRow, colorSpace,                                                  kCGImageAlphaPremultipliedLast | kCGBitmapByteOrder32Big);      //Draw an image on the interface according to the current pixel     CGContextDrawImage(context, CGRectMake(0, 0, inputWidth, inputHeight), inputCGImage); //Pixel processing     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; //Here, the RBGA value is obtained by direct shift, and the output is written very well!                 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);             }         }     } //Create New Diagram     // 4. Create a new UIImage     CGImageRef newCGImage = CGBitmapContextCreateImage(context);     UIImage * processedImage = [UIImage imageWithCGImage:newCGImage]; //Release     // 5. Cleanup!     CGColorSpaceRelease(colorSpace);     CGContextRelease(context);     free(inputPixels);          return processedImage; } + (int)mixedCalculation:(int)originValue { //Result color=primary color - (primary color inversion × mixed color inversion)/mixed color     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; }

Code and open source libraries


In order to facilitate use, we have written an open source library, which is very practical and packaged with DEMO

ZLYInvisibleWatermark

Author: Xiaoyu Zhou Lingyu

Link: https://juejin.im/post/5cd17612f265da037a3d0183

fabulous ( zero )

This article is written by Contributors Author, article address: https://blog.isoyu.com/archives/iosyinxingshuiyindeshixianheyansejiashensuanfa.html
use Knowledge Sharing Attribution 4.0 International License Agreement. Unless the reprint/source is indicated, they are all original or translated by this website. Please sign your name before reprinting. Last editing time: May 23, 2019 at 06:13 PM

Popular articles

Post reply

[Required]

I am a human?

Please wait three seconds after submission to avoid unsubmission and repetition