#!/bin/bash
# Batch Picture Watermarking Tool based on ImageMagick
# Copyright (c) 2009 - Luis León Cárdenas Graide < lcardena [@] nic [.] cl >
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. There is NO WARRANTY,
# to the extent permitted by law. See the GNU General Public License for more
# details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
#
#
# If you think this is useful, you can donate to the author:
# https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=1290721
# Thank you, very much.
self=$( basename "$0" .sh )
base="$HOME"/."$self"
opt_overwrite=''
opt_recurse=''
opt_repository="$base"
opt_parent='.'
opt_geometry='auto'
opt_threshold='70%,70%,70%'
opt_dissolve='20'
opt_watermark_file='watermark.png'
opt_watermark_path="$opt_repository"/"$opt_watermark_file"
opt_format='watermarked-%s'
opt_config="$base"/wm.conf
opt_font_config='30'
Title() {
echo 'Batch Picture Watermarking Tool based on ImageMagick
version 1.0
Copyright (c) 2009 Luis León Cárdenas Graide < lcardena [@] nic [.] cl >
License GPLv3+: GNU GPL version 3 or later '
}
License() {
Title
echo '
LICENSE
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. There is NO WARRANTY,
to the extent permitted by law. See the GNU General Public License for more
details.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
If you think this is useful, you can donate to the author:
https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=1290721
Thank you, very much.'
}
Help() {
echo HELP
echo
echo Prepare Watermark: "$self" '[ opts ] [ -[wW] destination_watermark.ext ]' pw 'base_watermark.ext'
echo Create Text Watermark: "$self" '[ opts ] [ -[wW] destination_watermark.ext ]' tw 'Text Message'
echo Watermark Pictures: "$self" '[ opts ] [ -[wW] generated_watermark.ext ]' wm 'photo.ext ...'
echo '
GLOBAL OPTIONS:
-h: Displays this help.
-L: Prints License.
-o: Overwrites existing files instead of renaming from "file.ext" to "file.ext~".
-c CONFIG_FILE: Configuration file. Defaults to '"$opt_config"'
-R REPOSITORY_DIR: Watermarks repository location. Defaults to "'"$opt_repository"/'".
-w WATERMARK_FILE: Watermark'\'' filename relative to watermarks repository. Defaults to "watermark.png". Excludes -W.
-W WATERMARK_PATH: Absolute watermark'\''s path. Defaults to "-R"/"-w". Excludes -w.
CMD COMMANDS:
* Prepare Watermark: CMD = pw
Loads a picture ("base_watermark.ext") and generates a watermark based on it, storing the watermark file in your watermarks repository.
-g GEOMETRY: Watermark destination dimensions ("HHHxVVV" pixels). Defaults to "'"$opt_geometry"'". If GEOMETRY is "auto", GEOMETRY will be automatically identified based on "base_watermark.ext"'\''s geometry.
-t THRESHOLD: Monochrome RGB threshold. Defaults to "'"$opt_threshold"'"
* Create Text Watermark: CMD = tw
Creates a watermark based on specified "Text Message". Useful for watermarking pictures with your name or your website domain name.
-F FONT_CONFIG: If present, defines font size, font family or font name, and rotation angle. Defaults to "'"$opt_font_config"'".
The format of FONT_CONFIG must be: "POINT_SIZE TYPE FONT / BASE_ANGLE r APERTURE", without blanks, where:
POINT_SIZE: Integer font point size. Optative. Defaults to ImageMagick default.
FONT: Font Name or Font Family. Optative. Defaults to ImageMagick default.
TYPE: One between ":" (meaning FONT is Font Family) or "=" (meaning FONT is Font Name). If FONT is present, TYPE MUST be present.
BASE_ANGLE: Text rotation (in degrees). Optative. Defaults to "0".
APERTURE: Random range whose half will be added/substracted from BASE_ANGLE. Optative. Defaults to 0. If present, "r" MUST be present.
If BASE_ANGLE or APERTURE are present, "/" MUST be present.
Options about CMD "pw" will be applied after text render in order to create the watermark.
* Watermark Pictures: CMD = wm
Watermark pictures based on specified watermark.
-r: Recurse subdirectories.
-f FORMAT: sprintf FORMAT for destination watermarked file based on original file name. Defaults to "'"$opt_format"'".
-p DESTINATION_DIR: Parent directory where to place watermarked files. Defaults to "'"$opt_parent"'".
-d DISSOLVE: Stamp dissolve percentage. Defaults to "'"$opt_dissolve"'".
CONFIGURATION FILE
Configuration file will be read before command line options. By default, "'"$opt_config"'" will be read. If specified with "-c", it will be loaded while parsing options, replacing previous
parsed options if present. It is recommended to be the first option.
Configuration file is a new-line separated list of PARAMETER=VALUE pairs. Parameters are aliases to command line arguments. Aliases are:
Option | Alias
-------+-------------------
-o | opt_overwrite
-R | opt_repository
-w | opt_watermark_file
-W | opt_watermark_path
-g | opt_geometry
-t | opt_threshold
-r | opt_recurse
-f | opt_format
-p | opt_parent
-d | opt_dissolve
-F | opt_font_config
On/Off unary boolean parameters might be set as "" for off/false or "y" for on/true. Special characters must be shell-quoted. Unknown options will be silently ignored. Blank lines are ignored. Comment lines begin with "#".
SECURITY WARNING: Configuration file will be "source"d verbatim by Bash, which can lead to arbitrary command execution under running user privileges. Set up that file with responsibility.
EXAMPLE CONFIGURATION FILE
"'"$base"/wm.conf'": (Identical to default values)
'
for opt in opt_overwrite opt_recurse opt_repository opt_parent opt_geometry opt_threshold opt_dissolve opt_watermark_file opt_watermark_path opt_format opt_config opt_font_config
do
printf '%s=%q\n' "$opt" "${!opt}"
done
echo '
EXAMPLE USAGE:
% '"$self"' -w my_site pw icon.jpg
Based on "icon.jpg", converts it to "my_site.png" watermark and imports it into your default watermarks repository.
% '"$self"' -w my_name -F 20=Courier-New-Normal/45r20 tw '\''Tux r00lz'\''
Creates watermark "my_name.png" into your default watermarks repository containing text "Tux r00lz", with point font size "20" and using font name "Courier-New-Normal". Text orientation will be contained between 35 (45-20/2) and 55 (45+20/2) degrees. Be aware of special shell metacharacters, which must be quoted.
% '"$self"' -w my_name -r -p my_watermarked_pics wm my_pics
Recursively watermarks pictures under "my_pics/" with watermark "my_name.png" from your default watermarks repository, locating watermarked pictures under "my_watermarked_pics/".
AUTHOR
Copyright (c) 2009 Luis León Cárdenas Graide < l cardena [at] nic [dot] cl >
CREDITS
Commands "pw" and "wm" based on web tutorial by Arto Selonen:
http://www.selonen.org/arto/netbsd/watermarks.html
'
License
}
usage() {
local x="$1"
local msg="$2"
if [ ! -z "$msg" ]
then
echo "$msg"
fi
echo
Title
echo
echo Usage: "$self" '[ opts ]' CMD '[ args ... ]'
echo Help: "$self" -h
echo License: "$self" -L
exit "$x"
}
testx() {
local program="$1"
local msg="$2"
echo -n Testing executable \""$program"\":' '
if ! which "$program"
then
if [ ! -z "$msg" ]
then
echo "$msg"
fi
usage 2 'Executable Not found: '"$program"
fi
true
}
test_opt() {
local param="$1"
local value="$2"
if [ -z "$value" ]
then
usage 3 'Expected '"$param"
fi
}
source_config() {
local file="$1"
if [ ! -z "$file" ]
then
if [ -r "$file" ]
then
echo Loading CONFIG FILE: \""$file"\"
source "$file"
else
echo CONFIG FILE not found: \""$file"\"
false
fi
fi
}
backup() {
local file="$1"
if [ ! "$opt_overwrite" -a -e "$file" ]
then
mv -vf "$file" "$file"\~
fi
}
get_geometry() {
local image="$1"
if [ "$opt_geometry" == 'auto' ]
then
opt_geometry=$( identify -format '%wx%h' "$image" ) ||\
usage 4 'Can'\''t determine geometry of: "'"$image"'"'
fi
echo GEOMETRY: "$opt_geometry"
[[ "$opt_geometry" =~ [0-9]+x[0-9]+ ]]
}
prepare_watermark() {
local watermark="$1"
local stamp="$2"
local watermark_dir=$( dirname "$watermark" )
mkdir -vp "$watermark_dir" ||\
usage 4 'Can'\''t create watermark'\''s base dir: '"$watermark_dir"/
mkdir -vp "$base" ||\
usage 3 'Cant'\''t create base dir: '"$base"/
local tmp=$( mktemp -dt "$self"."$$".XXXXXX ) ||\
usage 3 'Cant'\'' create temp dir'
echo TMP: "$tmp"/
# How to make watermarks using ImageMagick
#
# Here are some quick and dirty instructions for creating a "stamp", and then using it for adding "watermarks" to existing images. I'll use ImageMagick 5.5.7, and use PNG files for making the stamp, and JPEG files for original and watermarked images.
# 1. Scanned stamp
#
# I started with a simple hand-made figure (my initials). I wanted to get a fairly small stamp, so the origial drawing is about 2cm x 2cm in size (or a bit less). My goal was to have a ~250x250 pixel stamp that has some white space around the drawing. So, I made a drawing, and then set the scanner to grayscale, 300DPI, and 200x200 pixels size selection. After scanning, I converted the TIF to PNG using:
#
# % convert -quality 9 scan.tif scan.png
convert -quality 9 "$stamp" "$tmp"/stamp.png || usage 4
# Checking the image size with identify gives:
#
# % identify scan.png
# scan.png PNG 200x200+0+0 PseudoClass 256c 8-bit 15.4kb 0.030u 0:01
get_geometry "$stamp" || usage 3 'Ilegal Geometry: "'"$opt_geometry"'"'
# Therefore, I needed to add some whitespace around it to make it, say 256x256. I chose to create a proper sized, white image, and then place my scan in the center:
#
# % convert -size 256x256 xc:white base.png
convert -size "$opt_geometry" xc:white "$tmp"/base.png || usage 4
# % composite -gravity center scan.png base.png mark1.png
composite -gravity center "$tmp"/stamp.png "$tmp"/base.png "$tmp"/mark1.png || usage 4
# 2. Make it a simple stamp
#
# Our scanned image has some noise in it, and a simple stamp would be monochrome: only the figure as black, and all surrounding areas as transparent. I chose to do the following:
#
# % convert -monochrome mark1.png mark2.png
# or
# % convert -monochrome -threshold 70%,70%,70% mark1.png mark2.png
convert -monochrome -threshold "$opt_threshold" "$tmp"/mark1.png "$tmp"/mark2.png || usage 4
# One could also try segmenting the image:
#
# % convert -segment 1x1 mark1.png mark3.png
# % convert -monochrome mark3.png mark2.png
#
# The monochrome image is then converted to stamp with transparent background:
#
# % convert -transparent white mark2.png mark3.png
convert -transparent white "$tmp"/mark2.png "$tmp"/mark3.png || usage 4
# Note how the edge of the drawing is not well defined, and the drawing has some "holes" in it, as well. This only makes the stamp more unique. One could clean it up in some image editing program, but we'll leave it as it is for now.
# 3. Grayscale stamp with noise
#
# Our simple stamp is now ready to be used, but it has a problem: what if the original image is black, or almost black? Our stamp will not be visible. No matter what color our stamp would be, it would always disappear in an image with mostly the same color.
#
# To combat this problem, we'll create a grayscale stamp with noise in it. So, the stamp will have all shades of gray, making any subset of those stamp pixels visible (white pixels will show up on black images, etc).
#
# First, we need to create an image with proper noise in it. Since adding noise will also add color, I'll start by creating a simple grayscale image that can be used as a color map:
#
# % convert -size 256x256 -colors 256 gradient:black-white map.png
convert -size "$opt_geometry" -colors 256 gradient:black-white "$tmp"/map.png || usage 4
# Then I'll create another image, now with noise (you can use any of the following as noise type: Uniform, Gaussian, Impulse, Laplacian):
#
# % convert -size 256x256 xc:white base.png
convert -size "$opt_geometry" xc:white "$tmp"/base.png || usage 4
# % convert base.png +noise Impulse -normalize -map map.png noise.png
convert "$tmp"/base.png +noise Uniform -normalize -map "$tmp"/map.png "$tmp"/noise.png || usage 4
# Finally, I'll combine the noise with my stamp:
#
# % composite -dissolve 100% noise.png mark3.png miff:- | composite -compose CopyOpacity mark3.png - watermark.png
backup "$watermark"
composite -dissolve '100%' "$tmp"/noise.png "$tmp"/mark3.png miff:- |\
composite -compose CopyOpacity "$tmp"/mark3.png - "$watermark" || usage 4
# And make sure the image has the whole grayscale in use:
#
# % convert watermark.png -normalize watermark.png
convert "$watermark" -normalize "$watermark" || usage 4
rm -rfv "$tmp"
}
cmd_prepare_watermark() {
local watermark="$1"
local stamp="$2"
if [ -z "$stamp" ]
then
usage 3 'Missing Stamp file'
fi
if [ ! -r "$stamp" ]
then
usage 3 'Can'\''t read watermark stamp file: "'"$stamp"'"'
fi
if [ "$opt_geometry" == 'auto' ]
then
testx identify 'Do you have ImageMagick package installed?'
fi
prepare_watermark "$opt_watermark_path" "$stamp"
}
watermark() {
local watermark="$1"
local target="$2"
if [ -d "$target" ]
then
if [ "$opt_recurse" ]
then
for file in "$target"/*
do
if identify "$file" 2>/dev/null
then
watermark "$watermark" "$file"
else
echo SKIP UNKNOWN: "$file"
fi
done
else
echo SKIP DIR: "$target"
fi
else
local target_dir=$( dirname "$target" )
local target_base=$( basename "$target" )
local watermarking_target=''
printf -v watermarking_target "$opt_format" "$target_base"
if [ -z "$watermarking_target" ]
then
echo WARNING: Empty watermarking target for "$target"
return 1
fi
watermarking_target="$target_dir"/"$watermarking_target"
echo WATERMARKING TARGET: "$target"
local watermarked_target="$opt_parent"/"$watermarking_target"
echo WATERMARKED TARGET: "$watermarked_target"
local watermarked_target_dir=$( dirname "$watermarked_target" )
mkdir -vp "$watermarked_target_dir" ||\
usage 3 'Cant'\'' create Target Watermarked DIR: "'"$watermarked_target_dir"'".'
backup "$watermarked_target"
# 4. Using the watermark
#
# We can use the watermark quite simply:
#
# % composite -dissolve 15 -tile watermark.png src.jpg dst.jpg
composite -monitor -dissolve "$opt_dissolve" -tile "$watermark" "$target" "$watermarked_target" || usage 4
fi
}
cmd_watermark() {
local watermark="$1"
shift
if [ "$opt_recurse" ]
then
testx identify 'Do you have ImageMagick package installed?'
fi
if [ ! -r "$watermark" ]
then
usage 3 'Cant'\''t read watermark in "'"$watermark"'".'
fi
for file in "$@"
do
watermark "$watermark" "$file"
done
}
create_text_watermark() {
local text="$1"
local pointsize="$2"
local font_type="$3"
local font_id="$4"
local angle="$5"
local tmp_watermark=$( mktemp -t "$self"."$$".XXXXXX ) ||\
usage 4 'Cant'\'' create temp watermark'
tmp_watermark="$tmp_watermark".png
local -a cmd=( convert -monitor -quality 100 -antialias -dither -gravity center )
if [ ! -z "$pointsize" ]
then
echo FONT POINTSIZE: "$pointsize"
cmd=( "${cmd[@]}" -pointsize "$pointsize" )
fi
if [ ! -z "$font_type" ]
then
cmd=( "${cmd[@]}" "$font_type" "$font_id" )
fi
cmd=( "${cmd[@]}" label:"$text" )
if [ ! -z "$angle" ]
then
cmd=( "${cmd[@]}" -rotate "$angle" )
fi
"${cmd[@]}" "$tmp_watermark"
cmd_prepare_watermark "$opt_watermark_path" "$tmp_watermark"
rm -vf "$tmp_watermark"
}
cmd_create_text_watermark() {
local text="$1"
local font_config="$2"
if [ -z "$text" ]
then
usage 2 'Missing Text'
fi
local font_pointsize=''
local font_type=''
local font_id=''
local angle='0'
if [ ! -z "$font_config" ]
then
if [[ "$font_config" =~ ^([0-9]+)?(([=:])([^/]+))?(/(.*))?$ ]]
then
font_pointsize="${BASH_REMATCH[1]}"
font_type="${BASH_REMATCH[3]}"
font_id="${BASH_REMATCH[4]}"
local angle_spec="${BASH_REMATCH[6]}"
if [[ ! "$font_pointsize" =~ ^[0-9]*$ ]]
then
usage 3 'Ilegal Font Point-Size: "'"$font_pointsize"'"'
fi
case "$font_type" in
:) echo FONT FAMILY: "$font_id"; font_type='-family';;
=) echo FONT NAME: "$font_id"; font_type='-font';;
'') echo DEFAULT FONT;;
*)usage 3 'Ilegal Font Config: ":" or "=" in "'"$font_config"'"';;
esac
if [ ! -z "$angle_spec" ]
then
if [[ "$angle_spec" =~ ^([+-]?[0-9]+)?(r([+-]?[0-9]+))?$ ]]
then
local angle_base="${BASH_REMATCH[1]}"
local angle_aperture="${BASH_REMATCH[3]}"
if [ -z "$angle_base" ]
then
angle_base='0'
fi
if [ -z "$angle_aperture" ]
then
angle="$angle_base"
else
echo ANGLE BASE: "$angle_base"
echo ANGLE APERTURE: "$angle_aperture"
angle=$(( $angle_base + $angle_aperture * ( $RANDOM - ( 32767 / 2 ) ) / ( 32767 / 2 ) ))
fi
echo ANGLE: "$angle"
else
usage 3 'Ilegal Font Angle: "'"$angle_spec"'"'
fi
fi
else
usage 3 'Ilegal Font Config: "'"$font_config"'"'
fi
fi
create_text_watermark "$text" "$font_pointsize" "$font_type" "$font_id" "$angle"
}
source_config "$opt_config"
OPTIND=''
while getopts 'hLorF:c:R:f:p:g:t:d:w:W:' name
do
case "$name" in
h) Help; usage 0;;
L) License; usage 0;;
c) test_opt CONFIG_FILE "$OPTARG"; opt_config="$OPTARG"
source_config "$opt_config" || usage 3 'Error loading CONFIG FILE';;
o) opt_overwrite='y';;
r) opt_recurse='y';;
F) test_opt FONT_CONFIG "$OPTARG"; opt_font_config="$OPTARG";;
f) test_opt FORMAT "$OPTARG"; opt_format="$OPTARG";;
p) test_opt DESTINATION_DIR "$OPTARG"; opt_parent="$OPTARG";;
g) test_opt GEOMETRY "$OPTARG"; opt_geometry="$OPTARG";;
t) test_opt THRESHOLD "$OPTARG"; opt_threshold="$OPTARG";;
d) test_opt DISSOLVE "$OPTARG"; opt_dissolve="$OPTARG";;
W) test_opt WATERMARK_PATH "$OPTARG"; opt_watermark_path="$OPTARG";;
w) test_opt WATERMARK_FILE "$OPTARG"; opt_watermark_file="$OPTARG"
opt_watermark_path="$opt_repository"/"$opt_watermark_file";;
R) test_opt REPOSITORY_DIR "$OPTARG"; opt_repository="$OPTARG"
opt_watermark_path="$opt_repository"/"$opt_watermark_file";;
\?)
usage 2 'Unrecognized option'
;;
esac
done
if [ "$OPTIND" ]
then
shift $(( $OPTIND - 1 ))
fi
cmd="$1"
if [ -z "$cmd" ]
then
usage 2 'Missing CMD'
fi
shift
for Cmd in convert composite
do
testx "$Cmd" 'Do you have ImageMagick package installed?'
done
if [[ ! "$opt_watermark_path" =~ \.(png|PNG)$ ]]
then
opt_watermark_path="$opt_watermark_path".png
fi
echo WATERMARK_PATH: "$opt_watermark_path"
case "$cmd" in
pw) stamp="$1"; cmd_prepare_watermark "$opt_watermark_path" "$stamp";;
wm) cmd_watermark "$opt_watermark_path" "$@";;
tw) text="$1"; cmd_create_text_watermark "$text" "$opt_font_config";;
*) usage 3 'Unknown CMD '"$cmd";;
esac