MagickSlicer/magick-slicer.sh

954 lines
26 KiB
Bash
Executable File
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/bin/bash
version="0.004"
date="05/08/2015"
# ####### Options ####### #
# resultExt='png'
resultExt=''
resizeFilter='' # http://www.imagemagick.org/Usage/filter/
# resultDir='./sliceResult'
resultDir=''
# Selector fo slicer: A or B
scaleFromImage=true # Type of scaling: if true - scale calculates from image size to down (slicer A), if false - image scale starts from tile size and grow up (slicer B)
gravity='NorthWest' # Image positioning (from this option depends, which tiles sides can be cropped, if it not full size). Choices include: 'NorthWest', 'North', 'NorthEast', 'West', 'Center', 'East', 'SouthWest', 'South', 'SouthEast'. Use -list gravity to get a complete list of -gravity settings available in your ImageMagick installation.
extent=false # Extent option (false - tiles be cropped, true - will be added transparent color to get all tiles of full size)
scale64=false
zoomReverse=false # false: maxZoom=100%; true: minZoom=100%
# Options only for slicerB
scaleover=true # Maximum zoom: bigger or less then image. False - will not create upscaled image for maximum zoom; true - last zoom be equal or grater, then image.
horizontal=true # Type of positioning of image: horizontal or vertical.
# ####### Options end ####### #
# ———————————————————————————————————————————————————————————————————————————————————
# ####### Variables ####### #
imageSource=''
tileW=256
tileH=256
step=200
imageW=''
imageH=''
imOptions=''
dziFormat=true
verboseLevel=0
overlap=0 # TODO: Overlap handling.
# ———————————————————————————————————————————————————————————————————————————————————
# ####### Verbose functions ####### #
# (0=none, 1=warnings, 2=warnings+info, 3=warning+info+debug)
warnMsg(){
if [ $verboseLevel -ge 1 ]
then
echo $1
fi
}
infoMsg(){
if [ $verboseLevel -ge 2 ]
then
echo $1
fi
}
debugMsg(){
if [ $verboseLevel -ge 3 ]
then
echo $1
fi
}
# ———————————————————————————————————————————————————————————————————————————————————
# ####### CLI ####### #
qHelp(){
echo " -?, --help [option]"
if [ "$1" = true ]
then
echo " Show basic help or show help for selected option."
echo
echo " Type: str"
echo
fi
}
mHelp(){
echo " -m, --man"
if [ "$1" = true ]
then
echo " Show full help for all options."
echo
echo " Type: str"
echo
fi
}
iHelp(){
echo " [ -i, --in <file_path> ]"
if [ "$1" = true ]
then
echo " Input file to slice."
echo
echo " Type: str"
echo
fi
}
oHelp(){
echo " [ -o, --out <directory_path> ]"
if [ "$1" = true ]
then
echo " Output directory for result."
echo
echo " Default: same as source"
echo " Type: str"
echo
fi
# sliceResult/<zoom_Level>/<horizontal tiles line (x) (folder)>/<vertical tiles line (y) (file)>"
}
eHelp(){
echo " [ -e, --extension <file_extesion> ]"
if [ "$1" = true ]
then
echo " Set result files extension."
echo
echo " Default: same as source"
echo " Type: str"
echo
fi
}
wHelp(){
echo " [ -w, --width <tile_width> ]"
if [ "$1" = true ]
then
echo " Set tile width."
echo
echo " Default: 256 pixels or same as height, if height is present."
echo " Type: int"
echo
fi
}
hHelp(){
echo " [ -h, --height <tile_height> ]"
if [ "$1" = true ]
then
echo " Set tile height"
echo
echo " Default: 256 pixels or same as width, if width is present."
echo " Type: int"
echo
fi
}
sHelp(){
echo " [ -s, --step <zoom_step_value> ]"
if [ "$1" = true ]
then
echo " Zoom step value. Formula:"
echo " (1) image_size[i+1] = image_size[i] * 100 / step"
echo " 200 -> 200% or 2x -> 100% * 100 / 200 = 50%"
echo " 175 -> 175% or 1.75x -> 100% * 100 / 175 = 57.(142857)%"
echo " 120 -> 120% or 1.2x -> 100% * 100 / 120 = 83.(3)%"
echo " 300 -> 300% or 3x -> 100% * 100 / 300 = 33.(3)%"
echo " 100 -> 100% or 1x (no resize) -> infinity loop. Don't use it."
echo
echo " Default: 200"
echo " Type: int"
echo
fi
}
pHelp(){
echo " [ -p, --options 'imagemagick options string']"
if [ "$1" = true ]
then
echo " Specifies additional imagemagick options for 'convert'."
echo
echo " Type: str"
echo
fi
}
gHelp(){
echo " [ -g, --gravity <type> ]"
if [ "$1" = true ]
then
echo " Types: NorthWest North NorthEast West Center East SouthWest South SouthEast"
echo " The direction you choose specifies where to position of cuts start on image. Use -list gravity to get a complete list of -gravity settings available in your ImageMagick installation."
echo " http://www.imagemagick.org/script/command-line-options.php#gravity"
echo
echo " Default: NorthWest"
echo " Type: str"
echo
fi
}
xHelp(){
echo " [ -x, --extent ]"
if [ "$1" = true ]
then
echo " Specifies the edge tiles size: cropped or extent them to full size and fill transparent color."
echo
echo " Default: false"
echo " Type: logic switch"
echo
fi
}
dHelp(){
echo " [ -d, --dzi ]"
if [ "$1" = true ]
then
echo " Specifies output format."
echo
echo " Default: true"
echo " Type: logic switch"
echo
fi
}
aHelp(){
echo " [ -a, --slicea ]"
if [ "$1" = true ]
then
echo " Type of slicing - slice A. Image scale starts from image size to down. Inverts option '--sliceb'."
echo
echo " Default: true"
echo " Type: logic switch"
echo
fi
}
bHelp(){
echo " [ -b, --sliceb ]"
if [ "$1" = true ]
then
echo " Type of slicing - slice B. Image scale starts from tile size and grow up. Inverts option '--slicea'."
echo
echo " Default: false"
echo " Type: logic switch"
echo
fi
}
cHelp(){
echo " [ -c, --scaleover ] "
if [ "$1" = true ]
then
echo " Create upscaled image for maximum zoom (last zoom be equal or grater, then image). zoom[i-1]_size < source_image_size < zoom[i]_size"
echo " Work only in slice B mode. In other cases ignored."
echo
echo " Default: false"
echo " Type: logic switch"
echo
fi
}
rHelp(){
echo " [ -r, --horizontal ] "
if [ "$1" = true ]
then
echo " Tiles divide image on horizontal side without remains. On this side tiles will not be croped."
echo " Work only in slice B mode. In other cases ignored."
echo
echo " ___ ___ ___ _ _"
echo " | | | | ^"
echo " |___|___|___| Image"
echo " |___|___|___| _v_ <- Not full tiles."
echo " |_._|_._|_._| <-- Transparent color (extent=true) or cropped (extent=false)"
echo
echo " Default: true"
echo " Type: logic switch"
echo
fi
}
tHelp(){
echo " [ -t, --vertical ] "
if [ "$1" = true ]
then
echo " Tiles divide image on vertical side without remains. On this side tiles will not be croped."
echo " Work only in slice B mode. In other cases ignored."
echo
echo " |<-image->|"
echo " ___ ___ _ _ _ _"
echo " | | | |.| ^"
echo " |___|___|_|_| _v_Tile"
echo " | | | |.|"
echo " |___|___|_|_|"
echo " ^-- Transparent color (extent=true) or cropped (extent=false)"
echo " ^--- Not full tiles"
echo
echo " Default: false"
echo " Type: logic switch"
echo
fi
}
vHelp(){
echo " [ -v, --verbose <level> ]"
if [ "$1" = true ]
then
echo " User-selected verbosity levels:"
echo " - 0 = none"
echo " - 1 = warnings"
echo " - 2 = warnings + info"
echo " - 3 = warnings + info + debug"
echo
fi
echo " [ -v0, -v1, -v2, -v3 ]"
if [ "$1" = true ]
then
echo " Short commands for each verbosity level."
echo
echo " Default: 0"
echo " Type: logic switch"
echo
fi
}
lHelp(){
echo " [ -l, --overlap <pixels> ] "
if [ "$1" = true ]
then
echo " Tiles overlap in pixels."
echo
echo " Default: 1"
echo " Type: int"
echo
fi
}
uHelp(){
echo " Usage:"
echo " magick-slicer.sh -u|--usage"
echo " magick-slicer.sh -?|--help|-m|--man [option_name]"
echo " magick-slicer.sh [options] [-i] /source/image [[-o] result/dir]"
echo " magick-slicer.sh /source/image [options] [result/dir]"
echo " magick-slicer.sh /source/image [result/dir] [options]"
echo
echo " Use quotes for path or options with spaces. First unknown string interpreting as source image, if it not defined. Second unknown string is interpreting as result path, if it not defined. Also, for source and result you can use options '-i' and '-o'."
echo
}
cliHelp(){
echo " Map tiles generator. License: MIT."
echo " Version: $version"
echo " Date: $date"
echo
case $1 in
-i|--in) iHelp true ;;
-o|--out) oHelp true ;;
-e|--extension) eHelp true ;;
-w|--width) wHelp true ;;
-h|--height) hHelp true ;;
-s|--step) sHelp true ;;
# -l|--overlap) lHelp true ;;
-p|--options) pHelp true ;;
-g|--gravity) gHelp true ;;
-x|--extent) xHelp true ;;
-d|--dzi) dHelp true ;;
-a|--slicea) aHelp true ;;
-b|--sliceb) bHelp true ;;
-c|--scaleover) cHelp true ;;
-r|--horizontal) rHelp true ;;
-t|--vertical) tHelp true ;;
-v|--verbose) vHelp true ;;
-u|--usage) uHelp true ;;
-\?|--help) qHelp true ;;
-m|--man) mHelp true ;;
"")
uHelp $2
echo " Options list: "
qHelp $2
mHelp $2
vHelp $2
iHelp $2
oHelp $2
eHelp $2
wHelp $2
hHelp $2
sHelp $2
# lHelp $2
pHelp $2
gHelp $2
xHelp $2
dHelp $2
aHelp $2
bHelp $2
cHelp $2
rHelp $2
tHelp $2
;;
*)
echo " Unknown option: $1"
echo
;;
esac
echo
exit 0
}
# ### CLI parsing ###
debugMsg "CLI parsing"
# Test number of arguments
if [ $# -eq 0 ]
then
cliHelp
fi
# Temp variables for parser
WnotDefined=true
HnotDefined=true
SourceNotDefined=true
ResDirNotDefined=true
ExtNotDefined=true
# Arguments parsing
while [[ $# > 0 ]] # cmd tools
do
debugMsg "Parsing key: $1"
key="$1" # Get current key
case $key in # Do key work
-i|--in)
imageSource="$2"
SourceNotDefined=false
shift # past argument
;;
-o|--out)
resultDir="$2"
ResDirNotDefined=false
shift # past argument
;;
-e|--extension)
resultExt="$2"
ExtNotDefined=false
shift # past argument
;;
-w|--width)
tileW="$2"
if $HnotDefined
then
tileH="$2" # Set h=w by default, if it not defined yet
fi
WnotDefined=false
shift # past argument
;;
-h|--height)
tileH="$2"
if $WnotDefined
then
tileW="$2" # Set w=h by default, if it not defined yet
fi
HnotDefined=false
shift # past argument
;;
-s|--step)
step="$2"
shift # past argument
;;
-l|--overlap)
overlap="$2"
shift # past argument
;;
-p|--options)
imOptions="$2"
shift # past argument
;;
-g|--gravity)
gravity="$2"
shift # past argument
;;
-x|--extent)
extent=true
;;
-d|--dzi)
dziFormat=true
;;
-a|--slicea)
scaleFromImage=true
;;
-b|--sliceb)
scaleFromImage=false
;;
-c|--scaleover)
scaleover=false
;;
-r|--horizontal)
horizontal=true
;;
-t|--vertical)
horizontal=false
;;
-v|--verbose)
verboseLevel="$2"
shift # past argument
;;
-v0|--verbose0)
verboseLevel=0
;;
-v1|--verbose1)
verboseLevel=1
;;
-v2|--verbose1)
verboseLevel=2
;;
-v3|--verbose1)
verboseLevel=3
;;
-u|--usage)
uHelp
exit 0
;;
-\?|--help)
cliHelp "$2"
shift # past argument
;;
-m|--man)
cliHelp "" true
;;
# --default)
# DEFAULT=YES
# ;;
*)
if $SourceNotDefined # Interpreting first unknown command as source
then
imageSource="$1"
SourceNotDefined=false
else
if $ResDirNotDefined # Interpreting second unknown command as source
then
resultDir="$1"
ResDirNotDefined=false
else # Interpreting third unknown command as unknown command
echo "Unknown option: $1"
fi
fi
;;
esac
shift # Get next key
done
# Cheking for installed applications
command -v convert >/dev/null 2>&1 || { echo >&2 "I require ImageMagick tool 'convert', but it's not installed. Aborting."; exit 1; }
command -v identify >/dev/null 2>&1 || { echo >&2 "I require ImageMagick tool 'identify', but it's not installed. Aborting."; exit 1; }
# Checking if file was defined
if $SourceNotDefined
then
echo "No source file present. Canceled."
exit 1
fi
# Cheking if source file not exists
if [ ! -f "$imageSource" ]
then
echo "Error! Input file 'images source' not found! File path: $imageSource"
exit 1
fi
# Set extension
fullName=$(basename "$imageSource")
fileBase="${fullName%.*}"
fileExt="${fullName##*.}"
if $ExtNotDefined
then
resultExt="$fileExt"
fi
# Set out name
if $ResDirNotDefined
then
resultDir="$fileBase"
fi
# ———————————————————————————————————————————————————————————————————————————————————
# ####### Functions ####### #
debugMsg "Section: Functions"
getImgW(){ # image_file
echo `identify -format "%[fx:w]" $1`
}
getImgH(){ # image_file
echo `identify -format "%[fx:h]" $1`
}
# ———————————————————————————————————————————————————————————————————————————————————
# ######################## #
# ####### Slicer A ####### #
debugMsg "Section: Slicer A"
# Constants
scaleBase=100 # Scale in percents - 100% (TODO: add option to use image sizes)
scaleMult=100000 # Scale multiplier (bash works only with int)
scaleMult64=100000000000000 # Scale multiplier for x64 bash and x64 convert (if you have very many zoom level and need more accuracy)
scaleStart=0
# declare -a scaledW
# declare -a scaledH
# scaledW=()
# scaledH=()
setScale(){
if $scale64
then
local arch=`uname -m`
if [ "${arch}" == "x86_64" ]
then
scaleMult=$scaleMult64
else
echo "Your system (${arch}) isn't x86_64"
exit 1
fi
fi
scaleStart=$(( $scaleBase * $scaleMult ))
}
getZoomLevels(){ # imgLen(pixels) tileLen(pixels) step(int) # Calculate zoom levels for current step
local imgLen=$1
local tileLen=$2
local zoomStep=$3
local r=(0)
local cnt=1
# Drop zooms less tile size
# while [ "$imgLen" -gt "$tileLen" ]
# do
# r[$cnt]=$imgLen
# let "cnt+=1"
# let "imgLen = imgLen * 100 / zoomStep"
# done
# Do all zooms down to 1x1 px
while [ "$imgLen" -ge 1 ]
do
r[$cnt]=$imgLen
let "cnt+=1"
let "imgLen = imgLen * 100 / zoomStep"
done
r[$cnt]=$imgLen
r[0]=$cnt
echo ${r[*]}
}
nextScale(){ # oldScale -> newScale
# Calculate image zoom in percents - it need for imagemagick for image resize
echo $(( $1 * 100 / $step ))
}
scaleToPercents(){ # scale
local s=$1
local sInt=0
local sFloat=0
let "sInt = s / $scaleMult"
let "sFloat = s - sInt * $scaleMult"
echo "${sInt}.${sFloat}%"
}
# scaleImage(){ # zoom scale -> file_path
# local zoom=$1
# local s=$2
# local dir="${resultDir}/${zoom}"
# local file="${dir}.${resultExt}"
# local size=`scaleToPercents $s`
# mkdir -p $dir # Imagemagick can't create directories
# convert $imageSource $resizeFilter -resize $size $file
# echo $file
# }
zoomImage(){ # zoom size -> file_path
local zoom=$1
local size=$2
local dir="${resultDir}/${zoom}"
local file="${dir}.${resultExt}"
# local file="${dir}.png"
# local size=`scaleToPercents $s`
mkdir -p $dir # Imagemagick can't create directories
convert $imageSource $resizeFilter -resize $size $file
echo $file
}
sliceImage(){ # zoom image
local zoom=$1
local src=$2
local wxh="${tileW}x${tileH}"
local xyDelim='/'
if $dziFormat
then
xyDelim='_'
fi
local tilesFormat="%[fx:page.x/${tileW}]${xyDelim}%[fx:page.y/${tileH}]" # This very important magic! It allow imagemagick to generate tile names with it position and place it in corect folders (but folders need to generate manually)
local file="${resultDir}/${zoom}/%[filename:tile].${resultExt}"
if [ ! $dziFormat ]
then
# Creating subdirectories for tiles (one vertical line of tiles is in one folder)
local srcSize=`getImgW $src` # Getting image width
local dirNum=$(( $srcSize / $tileW )) # Calculating number of tiles in line
local i=0
for(( i=0; i<=$dirNum; i++ ))
do
mkdir -p "${resultDir}/${zoom}/$i" # Imagemagick can't create directories
done
sync
fi
# extent option
local ext=''
if $extent
then
ext="-background none -extent ${wxh}"
fi
# Slice image to tiles
# convert $src -crop $wxh -set filename:tile $tilesFormat +repage +adjoin -background none -gravity $gravity $ext $file
convert $src -gravity $gravity -crop $wxh -set filename:tile $tilesFormat +repage +adjoin -gravity $gravity $ext $file
}
sliceA(){
infoMsg " Slicer A is running..."
local scalesW=( `getZoomLevels $imageW $tileW $step` ) # Get width for each zoom level
local scalesH=( `getZoomLevels $imageH $tileH $step` ) # Get height for each zoom level
local zw=${scalesW[0]} # Get zoom level for width
local zh=${scalesH[0]} # Get zoom level for height
local scales=() # Creating empty array
local zoomMax=0
local zoom=0
local hMod=''
local s=0
local file=''
if [ "$zw" -ge "$zh" ]
then
zoomMax=$zw
scales=( ${scalesW[*]} )
hMod=''
else
zoomMax=$zh
scales=( ${scalesH[*]} )
hMod='x'
fi
# local scale=$scaleStart
# local scalep=''
while [ "$s" -lt "$zoomMax" ]
do
if $zoomReverse
then
let "zoom = s"
else
let "zoom = zoomMax - s"
fi
infoMsg "    Resizing next file..."
debugMsg "    zoomMax=$zoomMax, zoomLevel=$s, wxhInex=$zoom, wxh=${hMod}${scales[$zoom]}"
file=`zoomImage $s "${hMod}${scales[$zoom]}"`
infoMsg "    File resized: ${file}"
infoMsg "    Slicing file..."
sliceImage $s $file
rm -rf $file
# scalep=`scaleToPercents $scale`
# s=${scales[zoom-1]}
# echo $zoom "$s"
# file=`scaleImage $zoom "${hMod}${scales[$zoom]}"`
# echo "zoom, scalep, scale: $zoom $scalep $scale $file"
# echo ${scaledW[$i]}
# echo ${scaledH[$i]}
# scale=`nextScale $scale`
let "s+=1"
done
infoMsg " Slicer A complete"
# s=`nextScale $scaleStart`
# s=`nextScale $s`
# scaleToPercents $s
}
# ———————————————————————————————————————————————————————————————————————————————————
# ##########################
# ####### Slicer B ####### #
debugMsg "Section: Slicer B"
zoomPixels(){ # zoom tileSize
local zoom=$1
local pixels=$2
if [ "zoom" -ne 0 ]
then
let "pixels = pixels * 100"
for(( i=0; i<zoom; i++ ))
do
let "pixels = pixels * $step / 100"
done
let "pixels = pixels / 100"
fi
echo $pixels
}
resizeImageH(){ # zoom -> file_path
local zoom=$1
local dir="${resultDir}/${zoom}"
local file="${dir}.${resultExt}"
local size=`zoomPixels $zoom $tileW`
mkdir -p $dir # Imagemagick can't create directories
convert $imageSource $resizeFilter -resize $size $file
echo $file
}
resizeImageV(){ # zoom -> file_path
local zoom=$1
local dir="${resultDir}/${zoom}"
local file="${dir}.${resultExt}"
local size=`zoomPixels $zoom $tileH`
mkdir -p $dir # Imagemagick can't create directories
convert $imageSource $resizeFilter -resize "x${size}" $file
echo $file
}
resizeImage(){ # zoom -> file_path
if $horizontal
then
echo `resizeImageH $1`
else
echo `resizeImageV $1`
fi
}
sliceB(){
echo "Slicer B is running..."
local size=0
local sizeMax=0
local zoom=0
if $horizontal
then
let "size = $tileW"
let "sizeMax = $imageW"
else
let "size = $tileH"
let "sizeMax = $imageH"
fi
if $scaleover
then
let "sizeMax += $size"
fi
local px=$size
while [ "$px" -lt "$sizeMax" ]
do
echo " Slicing zoom level \"${zoom}\"; image main size is \"${px}\""
sliceImage $zoom `resizeImage $zoom`
let "zoom++"
px=`zoomPixels $zoom $size`
done
echo "Slicer B complete"
}
mainScale(){ # min zoom = tile width
if $scaleFromImage
then
sliceA
else
sliceB
fi
echo
}
setDziFormat(){
local dziFileName="${resultDir}.dzi"
resultDir="${resultDir}_files"
tileH=$tileW
echo '<?xml version="1.0"?>' > "$dziFileName"
echo "<Image TileSize=\"${tileW}\" Overlap=\"0\" Format=\"${resultExt}\" xmlns=\"http://schemas.microsoft.com/deepzoom/2008\">" >> "$dziFileName"
echo "<Size Width=\"${imageW}\" Height=\"${imageH}\"/>" >> "$dziFileName"
echo '</Image>' >> "$dziFileName"
# <?xml version="1.0"?>
#
#
# </Image>
}
setFormat(){
if $dziFormat
then
setDziFormat
fi
}
init(){
if [ "$step" -le 100 ]
then
echo "You get infinity loop. Minimum step value = 101% (101)"
exit 1
fi
# Getting image sizes
imageW=`getImgW $imageSource`
imageH=`getImgH $imageSource`
# Set options for selected format
setFormat
# Set scale
setScale
rm -rf $resultDir # removing old results
mkdir -p $resultDir # creating new results folder
}
# ———————————————————————————————————————————————————————————————————————————————————
# ###################### #
# ### Programm start ### #
debugMsg "Section: Programm"
init
mainScale
# ### Programm end ##### #
# ###################### #
# ———————————————————————————————————————————————————————————————————————————————————