Source code

"Invisible color" and "color deepening" algorithm

Many apps have watermarks on sensitive pages, mainly to track the source of images in response to public opinion. Generally, there will be employee or user ID and nickname on the watermark.


The use of watermark has bright points

  1. Tracking sources

  2. Deterrence

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

However, when there is no need for deterrence, for example, in order to maintain the beauty of the application or image, explicit watermark seems not necessary, so invisible watermark can be considered.

Recently, I saw a kind of watermark on Zhihu.

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


After the invisible watermark is processed, the watermark will be displayed

 image.png The specific treatment method is

  1. Add all black layers to the original image

  2. Select "color deepening" for all black layer

So far, I'm curious about PS algorithm. Color mixing mode is a common tool, but I haven't paid attention to the formula before.

Color deepen blend mode

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

After reading a series of materials, I found that the existing formulas are not correct, and some popular articles are also wrong. However, there are only a few official color mixing models.

View the color information in each channel and darken the base color to reflect the blend color by increasing the contrast between the two. It does not change when mixed with white.…

There are many problems with this formula

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

In the formula (255 primary color) and (255 mixed color) are the inverse phases of primary color and mixed color respectively.

  1. If the mixed color is 0 (black) and (base color × mixed color) is 0, the value obtained is a negative value of one phase, which is classified as 0, so the value is 0 regardless of the primary color.

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

They all have a fatal formula, Any color mixed with black results in black, which is obviously inconsistent with the PS treatment results mentioned above According to this theory, the whole picture should be black.

I tried to get close to the last one

  1. Result color = base color - (base color reversed phase × mixed color inverse phase) / mixed color

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

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

Implementation of invisible watermark

Add watermark

Firstly, the basic image processing methods in IOS are introduced

  1. Get all pixels of the picture

  2. Change the pixel information that the pointer points to

 + (UIImage *)addWatermark:(UIImage *)image
                     text:(NSString *)text {
    UIFont *font = [UIFont systemFontOfSize:32];
    NSDictionary *attributes = @{NSFontAttributeName: font, 
                                 NSForegroundColorAttributeName: [UIColor colorWithRed:0
    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
                                    textPoint:CGPointMake(x,  y)
            idx0 ++;
        x = 0;
        idx0 = 0;
        idx1 ++;
    return newImage; 
+ (UIImage *)addWatermark:(UIImage *)image
                     text:(NSString *)text
         attributedString:(NSDictionary *)attributes {
    [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();
    return newImage; 

Show watermark

Through the formula mentioned above, the watermark can be displayed.

 +(uiimage *) visiblewatermark: (uiimage *) image {
 / / 1. Get the raw pixels of the image / / define a 32-bit integer pointer * inputpixels 
 uint32 * inputpixels; 
 / / convert the image to cgimageref, Get parameters: length, width and height, bytes per pixel (4), bits per R 
 cgimageref inputgimage = [image cgimage]; 
 nsinteger inputwidth = cgimagegetwidth (inputcgimage); 
 nsinteger inputheight = cgimagegetheight (inputcgimage); 
 cgcolorspaceref colorspace = cgcolorspacecreatedevicergb(); nsinteger bytesperpixel = 4; 
 nsuinteger bitspercomponent = 8; 
 / / number of bytes per line 
 / inputbytesperrow = bytesperpixel * inputwidth; 
 / / open the memory area, pointing to the first pixel address 
 inputpixels = (uint32 *) calloc (inputheight * inputwidth, Sizeof ); 
 / / according to the pointer and previous parameters, create a pixel layer 
 / / according to the pointer and previous parameters, 
 cgcontextref context = cgbitmapcontextcreate (inputpixels, inputwidth, inputheight, 
 bitspercomponent, inputbytesperrow, colorspace, At present, the image is drawn according to the bitmap context; / / 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; 
 thisr, thisg, thisb, thisa; 
 / / the RBGA value is obtained by direct shift here, 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, 
                                         thisA); Create a new uiimage cgimageref newcgimage = cgbitmapcontextcreateimage (context); 
 uiimage * processedimage = [uiimage imagewithcgimage: newcgimage]; / / release 
 / / 5. Cleanup! 
 cgcolorspacerelease (colorspace); 
 cgcontextrelease (context); 
 free (inputpixels); 
 return processedamage; 
 + (int) mixedcalculation: (int) originvalue {/ / result color = base color - (base color inverse × mixed color inverse) / 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 the use of an open source library, encapsulation is very practical, with demo


Author: Zhou Lingyu


fabulous ( zero )

This paper is written by Contributors Creation, article address:
use Knowledge sharing signature 4.0 International license agreement. Except for the reprint / source, they are all original or translated by our website. Please sign before reprinting. Last time: May 23, 2019

Hot comments on articles




Please wait three seconds after submitting to avoid unsuccessful submission and repetition