snibgo's ImageMagick pages

Watermarks

Adding and removing them.

A watermark is a visible distortion to an image often denoting authorship or ownership. As it is visible, a person wishing to "steal" the image for use elsewhere can easily remove the mark, but it may discourage casual theft.

There are many possible ways of watermarking an image. A simple way is by compositing the watermark over the image.

Make a watermark

We use "snibgo" in light letters with a dark shadow at 25% opacity, on a transparent background. This image has no opaque pixels.

 %IM%convert ^
  -size 200x100 ^
  -background None -fill #123 label:snibgo ^
  ( +clone -channel RGB -negate +channel -repage -2-2 ) ^
  -layers merge ^
  -channel A -evaluate Multiply 0.25 +channel ^
  -trim ^
  wm_mark.png
wm_mark.png

Apply the watermark

We simply composite the watermark over an image:

%IM%convert ^
  toes.png ^
  wm_mark.png ^
  -gravity Center ^
  -compose Over -composite ^
  wm_toes_wm.png
wm_toes_wm.pngjpg

De-watermark

This watermarking process above is lossless; it hasn't removed information from the original image, so we can recover it from the watermarked image.

(This isn't strictly true. We are working in Q16 integer. If both images have 16 bits of information/channel/pixel, we can't combine them in a 16-bit format while retaining all the information. Higher opacities of the watermark lose more information from the input, and we can't get it back. But an opacity up to 99.99% gives a visually successful dewatermark.)

Thus, if we have an opaque watermarked image and the non-opaque watermark, we can reconstruct the opaque original with the process that is the inverse of "-compose Over -composite". (See the Inverse composites page, equation D = (R - S.Sa) / (1-Sa).) This removes the watermark from the watermarked image.

%IM%convert ^
  wm_toes_wm.png ^
  ( wm_mark.png ^
    ( +clone -alpha extract +write mpr:WEX ) ^
    -alpha off ^
    -compose Multiply -composite -negate ^
  ) ^
  -gravity center ^
  -compose ModulusAdd -composite ^
  ( mpr:WEX -negate ) ^
  -alpha off ^
  -compose DivideSrc -composite ^
  wm_dewm.png
wm_dewm.pngjpg

How close is the de-watermarked image to the original image?

%IM%compare -metric RMSE toes.png wm_dewm.png NULL: 
0.179151 (2.73367e-006)
%IM%compare -metric PAE toes.png wm_dewm.png NULL: 
1 (1.5259e-005)

This is very close. The peak error is one in 2^16 as we are using Q16. We are using integer IM so the watermarking "-compose Over -composite" has been rounded to the nearest integer, and the same for each de-watermarking operation.

Suppose someone took my watermarked image and removed the watermark. Could I show that they had done so? We find the difference between the original and de-watermarked version, auto-levelled.

%IM%convert ^
  toes.png ^
  wm_dewm.png ^
  -compose Difference -composite ^
  +write wm_dewm_r.png ^
  -auto-level ^
  wm_dewm_r_a.png
wm_dewm_r.pngjpg wm_dewm_r_a.pngjpg

A dishonest dewatermarker could conceal their action by adding a little noise to the de-watermarked image. A more tamper-proof watermark might be noisy (so the watermarking image could not be easily re-constructed), or be lossy so that mathematical reconstruction is not possible, or both. But a dewatermarker could simply remove pixels and use a hole-filling technique.

Deriving a semi-transparent watermark

If we have the opaque input image, and the result from the image with a semi-transparent watermark composited over it, can we derive the watermark?

Image: toes.png

toes.pngjpg

Watermarked image: wm_toes_wm.png

wm_toes_wm.pngjpg

As shown in Inverse composites: Opaque inputs, if we don't know the colours or opacity of a watermark, any one of a virtually infinite number of watermarks might have been used.

However, if we guess the opacity of each pixel, we can calculate its colour. The script findWater.bat assumes that where a pixel is unchanged, the watermark there is fully transparent. Otherwise, it takes a user-supplied value for opacity (0.0 = transparent, 1.0 = opaque). The script returns the derived watermark. It also composites this over the input image, compares that to the user-specified watermarked image, and returns the RMSE score (0.0 is perfect match, 1.0 is totally different).

So we can run the script with a number of values for opacity.

call %PICTBAT%findWater ^
  toes.png wm_toes_wm.png 0.0 ^
  wm_deriv_00.png

echo %FW_COMP% 
0.0216775 
wm_deriv_00.png
call %PICTBAT%findWater ^
  toes.png wm_toes_wm.png 0.25 ^
  wm_deriv_025.png

echo %FW_COMP% 
1.11974e-006 
wm_deriv_025.png
call %PICTBAT%findWater ^
  toes.png wm_toes_wm.png 0.5 ^
  wm_deriv_05.png

echo %FW_COMP% 
1.64537e-006 
wm_deriv_05.png
call %PICTBAT%findWater ^
  toes.png wm_toes_wm.png 0.75 ^
  wm_deriv_075.png

echo %FW_COMP% 
2.01577e-006 
wm_deriv_075.png
call %PICTBAT%findWater ^
  toes.png wm_toes_wm.png 1.0 ^
  wm_deriv_1.png

echo %FW_COMP% 
0 
wm_deriv_1.png

The best score, 0, occurs when we guess the watermark opacity is 1.0, ie fully opaque. But we can see edges of the toes showing through.

The worst score occurs when we guess the watermark is entirely transparent.

We get a good score from the opacity that was actually used to make the watermark: 0.25. We can't expect a perfect score because the watermark was antialiased, giving a variable opacity at the edges of the characters.

Watermarking images with transparency

When an image contains transparency, it can be watermarked in exactly the same way.

We will watermark ic_tr_src.png.

It contains full transparency,
full opacity, and levels in between.

ic_tr_src.png

Apply the watermark:

%IM%convert ^
  ic_tr_src.png ^
  wm_mark.png ^
  -gravity Center ^
  -compose Over -composite ^
  wm_tr_wm.png
wm_tr_wm.png

Provided we know the watermark and it contains no opaque pixels, we can de-watermark it to reconstruct the original. We use algebra from Inverse composites, in particular these equations ...

Da = (Ra - Sa) / (1-Sa)

D  = (R*Ra - S*Sa) / (Ra - Sa)

... that give us the alpha and colour channels, which we combine with "-compose CopyOpacity -composite", to reconstruct the original.

De-watermark:

 %IM%convert ^
  ( wm_tr_wm.png +write mpr:R -alpha extract ^
    +write mpr:Ra +delete ^
  ) ^
  ( wm_mark.png +write mpr:S -alpha extract ^
    +write mpr:Sa ^
  ) ^
  mpr:S ^
  -alpha off ^
  -gravity Center ^
  -compose Multiply -composite ^
  ( mpr:R mpr:Ra -alpha off -compose Multiply -composite ) ^
  +swap ^
  -alpha off ^
  -compose MinusSrc -composite ^
  ( mpr:Ra ^
    mpr:Sa ^
    -alpha off ^
    -compose MinusSrc -composite ^
    +write mpr:RamSa ^
  ) ^
  -compose DivideSrc -composite ^
  ( mpr:RamSa  ^
    ( mpr:Sa -negate ) ^
    -compose DivideSrc -composite ^
  ) ^
  -alpha Off ^
  -compose CopyOpacity -composite ^
  wm_tr_dewm.png
wm_tr_dewm.png

How close is this de-watermarked image to the original image?

%IM%compare -metric RMSE ic_tr_src.png wm_tr_dewm.png NULL: 
0.176102 (2.68715e-006)
%IM%compare -metric PAE ic_tr_src.png wm_tr_dewm.png NULL: 
2.32422 (3.54653e-005)

Again, this is very close. We have more operations, so rounding has lost more precision.

Scripts

For convenience, .bat scripts are also available in a single zip file. See Zipped BAT files.

findWater.bat

rem Given an opaque input image %1,
rem and same-sized image %2 which is %1 composited with a watermark,
rem where watermark pixels are either fully transparent or opacity %3 (0.0 to 1.0),
rem finds the watermark image, and writes it to %4.
rem
rem Also tests the watermark by compositing over %1, compares this to %2,
rem and returns result in FW_COMP,
rem where 0.0 is perfect match, 1.0 is totally different.


set DST=%1
set RES=%2

set ALPHA=%3

set OUTPUT=%4

if "%OUTPUT%"=="" exit /B 1

rem Set v.png to the opacity of the watermark image.
%IM%convert ^
  %RES% %DST% ^
  -compose ChangeMask -composite ^
  -alpha extract ^
  -channel RGB -evaluate Multiply %ALPHA% +channel ^
  v.png


rem Implement S = (R - D(1-Sa)) / Sa

rem Calculate S, w.png, the assumed watermark.
%IMG7%magick ^
  %RES% ^
  ( %DST% -evaluate Multiply %%[fx:1-%ALPHA%] ) ^
  -compose MinusSrc -composite ^
  -evaluate Divide %ALPHA% ^
  +write d0.png ^
  v.png ^
  -compose CopyOpacity -composite ^
  %OUTPUT%


rem Test the calculated watermark.
for /F "usebackq" %%L in (`%IM%convert ^
  %DST% ^
  %OUTPUT% ^
  -compose Over -composite ^
  %RES% ^
  -metric RMSE ^
  -format "FW_COMP=%%[distortion]" ^
  -compare ^
  info:`) do set %%L


All images on this page were created by the commands shown, using:

%IM%identify -version
Version: ImageMagick 6.9.5-3 Q16 x86 2016-07-22 http://www.imagemagick.org
Copyright: Copyright (C) 1999-2015 ImageMagick Studio LLC
License: http://www.imagemagick.org/script/license.php
Visual C++: 180040629
Features: Cipher DPC Modules OpenMP 
Delegates (built-in): bzlib cairo flif freetype jng jp2 jpeg lcms lqr openexr pangocairo png ps rsvg tiff webp xml zlib

To improve internet download speeds, some images may have been automatically converted (by ImageMagick, of course) from PNG to JPG.

Source file for this web page is watermark.h1. To re-create this web page, run "procH1 watermark".


This page, including the images, is my copyright. Anyone is permitted to use or adapt any of the code, scripts or images for any purpose, including commercial use.

Anyone is permitted to re-publish this page, but only for non-commercial use.

Anyone is permitted to link to this page, including for commercial use.


Page version v1.0 26-February-2017.

Page created 03-Apr-2017 23:51:16.

Copyright © 2017 Alan Gibson.