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.
We use "snibgo" in light antialiased letters with a dark shadow at 25% opacity, on a transparent background. This image has no opaque pixels.
%IMG7%magick ^ -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 |
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:
%IMG7%magick ^ wm_mark.png ^ -alpha extract -unique-colors ^ -format %%w\n ^ info:
372
We simply composite the watermark over a source image:
%IMG7%magick ^ toes.png ^ wm_mark.png ^ -gravity Center ^ -compose Over -composite ^ wm_toes_wm.png |
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.
%IMG7%magick ^ wm_toes_wm.png ^ ( wm_mark.png ^ ( +clone -alpha extract +write mpr:WEX ) ^ -alpha off ^ -compose Multiply -composite ^ ) ^ -gravity center ^ -compose MinusSrc -composite ^ ( mpr:WEX -negate ) ^ -alpha off ^ -compose DivideSrc -composite ^ wm_dewm.png |
How close is the de-watermarked image to the original image?
%IMG7%magick compare -metric RMSE toes.png wm_dewm.png NULL:
0.126276 (1.92685e-06)
%IMG7%magick compare -metric PAE toes.png wm_dewm.png NULL:
1 (1.5259e-05)
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:
%IMG7%magick ^ toes.png ^ wm_dewm.png ^ -compose Difference -composite ^ +write wm_dewm_r.png ^ -auto-level ^ wm_dewm_r_a.png |
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.
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 |
|
Watermarked image: wm_toes_wm.png |
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.02156181224797551 |
|
call %PICTBAT%findWater ^ toes.png wm_toes_wm.png 0.1 ^ wm_deriv_01.png echo %FW_COMP% 0.009716789188342283 |
|
call %PICTBAT%findWater ^ toes.png wm_toes_wm.png 0.25 ^ wm_deriv_025.png echo %FW_COMP% 3.290983852175886e-07 |
|
call %PICTBAT%findWater ^ toes.png wm_toes_wm.png 0.5 ^ wm_deriv_05.png echo %FW_COMP% 3.290983852175886e-07 |
|
call %PICTBAT%findWater ^ toes.png wm_toes_wm.png 0.75 ^ wm_deriv_075.png echo %FW_COMP% 8.621969907565209e-07 |
|
call %PICTBAT%findWater ^ toes.png wm_toes_wm.png 1.0 ^ wm_deriv_1.png echo %FW_COMP% 0 |
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.
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" |
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% 3.16411e-07 |
How many different values are in the alpha channel?
%IMG7%magick ^ wm_aa_025.png ^ -alpha extract -unique-colors ^ -format %%w\n ^ info:
23
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,
|
|
Apply the watermark: %IMG7%magick ^ ic_tr_src.png ^ wm_mark.png ^ -gravity Center ^ -compose Over -composite ^ 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)
... where all the variables are in the range 0.0 to 1.0. This calculates Da and D, the alpha and colour channels, which we combine with "-compose CopyOpacity -composite", to reconstruct the original.
De-watermark: %IMG7%magick ^ ( 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 |
How close is this de-watermarked image to the original image?
%IMG7%magick compare -metric RMSE ic_tr_src.png wm_tr_dewm.png NULL:
0.0954275 (1.45613e-06)
%IMG7%magick compare -metric PAE ic_tr_src.png wm_tr_dewm.png NULL:
1.71052 (2.61009e-05)
Again, this is very close. We have more operations, so rounding has lost more precision.
Above, we calculate a watermark from an un-watermarked image and a watermarked image. This generally has an infinite number of possible solutions. However, if we have two un-watermarked images and the corresponding two watermarked images, we can calculate the unique solution.
Suppose we have two known opaque images ("dest") without a watermark, and a version of each those two images with a watermark ("result"). These four images are the same size. We assume the same unknown watermark, which may have transparency, was used for both, with "-compose Over -composite". The watermark is in the same position in both images.
For a given pixel, and a given channel, let the input ("dest") colour values be D1 and D2, and the result watermarked image colour values be R1 and R2. Let the watermark ("source") colour value be S, and the watermark alpha be Sa.
Then Inverse composites: Opaque inputs gives equations for S and Sa:
S = (R1-D1)/Sa + D1 = (R2-D2)/Sa + D2 Sa = (R1-D1) / (S-D1) = (R2-D2) / (S-D2) (=Sa)
Solve the first equation for Sa:
(R1-D1) + D1*Sa = (R2-D2) + D2*Sa Sa*(D1-D2) = (R2-D2)-(R1-D1) = R2 - R1 + D1 - D2 Sa = (R2 - R1 + D1 - D2) / (D1-D2)
Solve the second equation for S:
(R1-D1) * (S-D2) = (R2-D2) * (S-D1) S*R1 - S*D1 - D2*R1 + D1*D2 = S*R2 - S*D2 - D1*R2 + D1*D2 S*R1 - S*D1 - D2*R1 = S*R2 - S*D2 - D1*R2 S*R1 - S*D1 - S*R2 + S*D2 = D2*R1 - D1*R2 S*(R1 - D1 - R2 + D2) = D2*R1 - D1*R2 S = (D2*R1 - D1*R2) / (R1 - D1 - R2 + D2)
These two equations are used by the script findWater2img.bat to calculate the watermark: S gives the pixel colour values, and Sa gives the pixel alphas. If any values S or Sa are outside the range [0..QuantumRange] then no watermark can be calculated.
For an example, we make two different images for watermarking. They should be the same size, but do not need to be related.
%IMG7%magick ^ toes.png ^ -evaluate Pow 1.4 ^ wm_pow1.png |
%IMG7%magick ^ toes.png ^ -evaluate Pow 0.6 ^ wm_pow2.png |
We apply a watermark to each image:
%IMG7%magick ^ wm_pow1.png ^ wm_mark.png ^ -gravity Center ^ -compose Over ^ -composite ^ wm_pow1_w.png |
%IMG7%magick ^ wm_pow2.png ^ wm_mark.png ^ -gravity Center ^ -compose Over ^ -composite ^ wm_pow2_w.png |
From these four images, we reconstruct the watermark with the script findWater2img.bat:
call %PICTBAT%findWater2img ^ wm_pow1.png wm_pow2.png ^ wm_pow1_w.png wm_pow2_w.png ^ wm_pow_calcwm.png |
We can invert the order of images 1 and 2, to get the same calculated watermark:
call %PICTBAT%findWater2img ^ wm_pow2.png wm_pow1.png ^ wm_pow2_w.png wm_pow1_w.png ^ wm_pow_calcwm21.png |
|
Verify that it is the same: %IMG7%magick compare ^ -metric RMSE ^ wm_pow_calcwm21.png wm_pow_calcwm21.png ^ NULL: 0 (0) |
[No image] |
How close is this to the actual watermark used?
%IMG7%magick compare -metric RMSE wm_mark.png wm_pow_calcwm.png -trim NULL:
0.75482 (1.15178e-05)
The round-trip is accurate (1/65536 is 1.526e-5), and the calculated watermark is very close to the actual watermark.
But we wouldn't normally have access to the actual watermark. We can also check the result by applying the calculated watermark to the original two inputs, and comparing the results to the watermarked inputs.
%IMG7%magick ^ wm_pow1.png ^ wm_pow_calcwm.png ^ -gravity Center ^ -compose Over -composite ^ wm_pow1_w.png ^ -metric RMSE ^ -format %%[distortion]\n ^ -compare ^ info:
3.11911e-06
%IMG7%magick ^ wm_pow2.png ^ wm_pow_calcwm.png ^ -gravity Center ^ -compose Over -composite ^ wm_pow2_w.png ^ -metric RMSE ^ -format %%[distortion]\n ^ -compare ^ info:
3.01222e-06
The calculated watermark accurately re-creates the watermarked images.
Some special cases are of interest.
In this case, R1==R2, and the equations simplify to:
Sa = (R2 - R1 + D1 - D2) / (D1-D2) = (D1-D2) / (D1-D2) = 1 hence fully opaque. S = (D2*R1 - D1*R2) / (R1 - D1 - R2 + D2) = R1*(D2-D1) / (D2-D1) = R1, so the watermark colour is either result.
However, when the values in the input images are also equal, then D1==D1, so S and Sa are indeterminate.
For example, we create two images (mirror images of each other), and watermarked versions of them:
%IMG7%magick ^ -size 150x301 ^ gradient:#f84-#4f8 ^ -rotate 90 ^ +write wm_grad_mark1.png ^ +write mpr:GRAD ^ -gravity Center ^ wm_mark.png ^ -compose Over -composite ^ -write wm_grad_mark1_w.png ^ +delete ^ mpr:GRAD -flop ^ +write wm_grad_mark2.png ^ wm_mark.png ^ -compose Over -composite ^ wm_grad_mark2_w.png |
From these four images, we reconstruct the watermark with the script findWater2img.bat:
call %PICTBAT%findWater2img ^ wm_grad_mark1.png wm_grad_mark2.png ^ wm_grad_mark1_w.png wm_grad_mark2_w.png ^ wm_grad_calcwm.png echo Return code is: %ERRORLEVEL% Return code is: 0 |
Pixels in the central column of the calculated watermark are indeterminate, with values outside [0..QuantumRange], and are transparent black in the written PNG file wm_grad_calcwm.png. A further process could identify those pixels and use a hole-filling (aka inpainting) method to correct them.
Special case when R1==D1:
Sa = (R2 - D2) / (D1-D2) S = (D2*D1 - D1*R2) / (- R2 + D2) = D1*(D2-R2)/(D2-R2) = D1 = R1
If, in addition, R2==D2:
Sa = 0 / (D1-D2) = 0
In other words, where the watermark doesn't change pixels, the watermark must be transparent.
Special case when values of one input image are 0 (black) and values of the other are 1 (white), so D1 is 0 and D2 is 1:
S = R1 / (R1 - R2 + 1) Sa = R1 - R2 + 1 = R1 / S
When the inputs are entirely black and white, this simplifies the IM command. See the script findWaterBW.bat. For example, we make watermarked black and white images:
%IMG7%magick ^ -size 300x150 ^ xc:Black ^ -gravity Center ^ wm_mark.png ^ -compose Over -composite ^ -write wm_bk_mark.png ^ +delete ^ xc:White ^ wm_mark.png ^ -compose Over -composite ^ wm_wh_mark.png |
From these two images, we reconstruct the watermark with the script findWaterBW.bat:
call %PICTBAT%findWaterBW ^ wm_bk_mark.png wm_wh_mark.png ^ wm_pow_calcbw.png |
How close is this to the actual watermark?
%IMG7%magick compare -metric RMSE wm_mark.png wm_pow_calcbw.png -trim NULL:
0.521761 (7.96156e-06)
The round-trip is accurate, and the calculated watermark is very close to the actual watermark.
For convenience, .bat scripts are also available in a single zip file. See Zipped BAT files.
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%
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%
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. @rem @rem Updated: @rem 6-August-2022 for IM v7 @rem @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 (`%IMG7%magick ^ %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%
rem Given %1 and %2 are same-sized opaque input images without watermarks (D1 and D2), rem %3 and %4 are those images composited with watermark (R1 and R2), rem writes watermark to %5. rem S = (D1*R2 + D2*R1) / (R1 - D1 - R2 + D2) set IS_OK=0 %IMG7%magick ^ -define "compose:clamp=off" ^ +depth ^ ^( %1 -set colorspace sRGB +write mpr:D1 ^) ^ ^( %2 -set colorspace sRGB +write mpr:D2 ^) ^ ^( %3 -set colorspace sRGB +write mpr:R1 ^) ^ ^( %4 -set colorspace sRGB +write mpr:R2 ^) ^ -delete 0--1 ^ ^( mpr:D2 mpr:R1 -compose Multiply -composite ^) ^ ^( mpr:D1 mpr:R2 -compose Multiply -composite ^) ^ +depth ^ -compose MinusSrc -composite ^ ^( mpr:R1 ^ mpr:D1 -compose MinusSrc -composite ^ mpr:R2 -compose MinusSrc -composite ^ mpr:D2 -compose Add -composite ^ ^) ^ -compose DivideSrc -composite ^ ^( ^ ^( mpr:D1 ^ mpr:D2 ^ -compose MinusSrc -composite ^ +write mpr:D1mD2 ^ ^) ^ mpr:R2 -compose Plus -composite ^ mpr:R1 -compose MinusSrc -composite ^ mpr:D1mD2 -compose DivideSrc -composite ^ ^) ^ -alpha off -compose CopyOpacity -composite ^ +depth ^ -verbose info: for /F "usebackq" %%L in (`%IMG7%magick ^ -define "compose:clamp=off" ^ +depth ^ ^( %1 -set colorspace sRGB +write mpr:D1 ^) ^ ^( %2 -set colorspace sRGB +write mpr:D2 ^) ^ ^( %3 -set colorspace sRGB +write mpr:R1 ^) ^ ^( %4 -set colorspace sRGB +write mpr:R2 ^) ^ -delete 0--1 ^ ^( mpr:D2 mpr:R1 -compose Multiply -composite ^) ^ ^( mpr:D1 mpr:R2 -compose Multiply -composite ^) ^ +depth ^ -compose MinusSrc -composite ^ ^( mpr:R1 ^ mpr:D1 -compose MinusSrc -composite ^ mpr:R2 -compose MinusSrc -composite ^ mpr:D2 -compose Add -composite ^ ^) ^ -compose DivideSrc -composite ^ ^( ^ ^( mpr:D1 ^ mpr:D2 ^ -compose MinusSrc -composite ^ +write mpr:D1mD2 ^ ^) ^ mpr:R2 -compose Plus -composite ^ mpr:R1 -compose MinusSrc -composite ^ mpr:D1mD2 -compose DivideSrc -composite ^ ^) ^ -alpha off -compose CopyOpacity -composite ^ +depth ^ -format "IS_OK=%%[fx:minima>=0.0 && maxima<=1.0?1:0]\n" ^ +write info: ^ %~5 `) do set %%L if %IS_OK%==0 ( echo %0: failed rem exit /B 1 ) exit /B 0
rem From %1 black watermarked image rem %2 white watermarked image rem writes watermark to %3. %IMG7%magick ^ -define compose:clamp=off ^ ( %1 +write mpr:R1 ^ %2 ^ -compose MinusSrc -composite ^ -evaluate Add 100%% +write mpr:Sa ^ ) ^ ( mpr:R1 mpr:Sa ^ -compose DivideSrc -composite ^ ) ^ +swap ^ -alpha off ^ -compose CopyAlpha -composite ^ +depth ^ %~3
All images on this page were created by the commands shown, using:
%IMG7%magick -version
Version: ImageMagick 7.1.0-42 Q16-HDRI x64 396d87c:20220709 https://imagemagick.org Copyright: (C) 1999 ImageMagick Studio LLC License: https://imagemagick.org/script/license.php Features: Cipher DPC HDRI OpenCL Delegates (built-in): bzlib cairo freetype gslib heic jng jp2 jpeg jxl lcms lqr lzma openexr pangocairo png ps raqm raw rsvg tiff webp xml zip zlib Compiler: Visual Studio 2022 (193231332)
To improve internet download speeds, some images may have been automatically converted (by ImageMagick, of course) from PNG or TIFF or MIFF 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 06-Aug-2022 23:14:43.
Copyright © 2022 Alan Gibson.