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 antialiased 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 +repage ^
  wm_mark.png
wm_mark.png

Most of the pixels in this image are either fully transparent or 25% opaque, but there is antialiasing betwen the regions. We can count the number of different values of alpha:

%IM%convert ^
  wm_mark.png ^
  -alpha extract -unique-colors ^
  -format %%w\n ^
  info: 
373

Apply the watermark

We simply composite the watermark over a source 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 source image, so if we know the watermark and the the watermarked image, we can recover the source.

(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 used this process to remove the watermark. Could I show that they had done so? We find the difference between the original source and the 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

So, although the de-watermarked image looks like the original source, we can compare it to the original and detect the remnants of a watermark.

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.02167751485610768 
wm_deriv_00.png
call %PICTBAT%findWater ^
  toes.png wm_toes_wm.png 0.1 ^
  wm_deriv_01.png

echo %FW_COMP% 
0.009710735008938307 
wm_deriv_01.png
call %PICTBAT%findWater ^
  toes.png wm_toes_wm.png 0.25 ^
  wm_deriv_025.png

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

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

echo %FW_COMP% 
8.75594411788342e-007 
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. We can also see a ghostly version of the image for watermarks at 0.75 and 0.5 opacity.

The worst score occurs when we guess the watermark is entirely transparent, which is the same as having no watermark.

We get a good score from the opacity that was actually used to make the watermark: 0.25.

Finding the antialias

The solutions found above have watermarks that have exactly two opacities: entirely transparent, and some other opacity. However, the ground truth watermark has not only full transparency and 25% opacity but also antialiasing between the two. There is no way to determine the anti-aliasing because there is no unique solution for the opacity at any pixel. However, we can guess at the anti-aliasing, which supplies the value for the opacity at every pixel, so we can determine the colour at every pixel.

The script findWaterOpac.bat models the antialiasing by creating an image that is black where there is no watermark, and white where there is a watermark. Then it applies a small blur, and levels from 50% to 100%. This gives a graduation that occurs entirely within the watermark. The resulting image is just the opacity of the watermark.

call %PICTBAT%findWaterOpac ^
  toes.png wm_toes_wm.png 0.25 ^
  wm_opac_025.png ^
  "-blur 0x0.4"
wm_opac_025.png

From this opacity map, the script findWater2.bat calculates the watermap colours.

call %PICTBAT%findWater2 ^
  toes.png wm_toes_wm.png wm_opac_025.png ^
  wm_aa_025.png

echo %FW_COMP% 
2.3665e-006 
wm_aa_025.png

How many different values are in the alpha channel?

%IM%convert ^
  wm_aa_025.png ^
  -alpha extract -unique-colors ^
  -format %%w\n ^
  info: 
19

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.


@if "%2"=="" findstr /B "rem @rem" %~f0 & exit /B 1

@setlocal enabledelayedexpansion

rem @call echoOffSave

call %PICTBAT%setInOut %1 fw

set DST=%1
set RES=%2

set ALPHA=%3
if "%ALPHA%"=="." set ALPHA=
if "%ALPHA%"=="" set ALPHA=0.5

if not "%4"=="" if not "%4"=="." set OUTFILE=%4

set TMPDIR=\temp\
set OPAC=%TMPDIR%fw_opac.png

rem Set OPAC to the opacity of the watermark image.
%IMG7%magick ^
  %RES% %DST% ^
  -compose ChangeMask -composite ^
  -alpha extract ^
  -channel RGB -evaluate Multiply %ALPHA% +channel ^
  %OPAC%


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

rem Calculate S, the assumed watermark.
%IMG7%magick ^
  %RES% ^
  ( %DST% -evaluate Multiply %%[fx:1-%ALPHA%] ) ^
  -compose MinusSrc -composite ^
  -evaluate Divide %ALPHA% ^
  %OPAC% ^
  -compose CopyOpacity -composite ^
  %OUTFILE%


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

echo %0: FW_COMP=%FW_COMP%

call echoRestore

@endlocal & set fwOUTFILE=%OUTFILE%& set FW_COMP=%FW_COMP%

findWaterOpac.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 opacity for the watermark image, and writes it to %4.
rem %5 is optional processing, eg "-blur 0x1 -level 50,100%"

@if "%2"=="" findstr /B "rem @rem" %~f0 & exit /B 1

@setlocal enabledelayedexpansion

rem @call echoOffSave

call %PICTBAT%setInOut %1 fwo

set RES=%2

set ALPHA=%3
if "%ALPHA%"=="." set ALPHA=
if "%ALPHA%"=="" set ALPHA=0.5

if not "%4"=="" if not "%4"=="." set OUTFILE=%4

set PROC=%~5
if "%PROC%"=="." set PROC=

echo %0: PROC is [%PROC%]


%IMG7%magick ^
  %RES% %INFILE% ^
  -compose ChangeMask -composite ^
  -alpha extract ^
  %PROC% ^
  -channel RGB -evaluate Multiply %ALPHA% +channel ^
  %OUTFILE%


call echoRestore

@endlocal & set fwoOUTFILE=%OUTFILE%

findWater2.bat

rem Given an opaque input image %1,
rem and same-sized image %2 which is %1 composited with a watermark,
rem where %3 is same-sized image of opacity for watermark (0=transparent),
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.


@if "%3"=="" findstr /B "rem @rem" %~f0 & exit /B 1

@setlocal enabledelayedexpansion

rem @call echoOffSave

call %PICTBAT%setInOut %1 fw

set DST=%1
set RES=%2

set ALPHA_IMG=%3

if not "%4"=="" if not "%4"=="." set OUTFILE=%4


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

rem Calculate S, the assumed watermark.
%IMG7%magick ^
  %RES% ^
  ( %DST% ^
    ( %ALPHA_IMG% -negate ) ^
    -compose Multiply -composite ^
  ) ^
  -compose MinusSrc -composite ^
  %ALPHA_IMG% ^
  -compose DivideSrc -composite ^
  %ALPHA_IMG% ^
  -alpha off ^
  -compose CopyOpacity -composite ^
  %OUTFILE%


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

echo %0: FW_COMP=%FW_COMP%

call echoRestore

@endlocal & set fwOUTFILE=%OUTFILE%& set FW_COMP=%FW_COMP%

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 09-Jun-2018 12:52:35.

Copyright © 2018 Alan Gibson.