MagickSlicer/MagickSlicer.sh

464 lines
14 KiB
Bash

#!/bin/bash
version="0.001"
date="24/07/2015"
if [ $# -le 3 ]; then
echo "Usage: imageSlicer.sh \"<source file>\" <tile_w> <tile_h> <step>"
echo
echo " Map tiles generator. License: MIT. $date"
echo " Version: $version"
echo " Date: $date"
echo
echo " - \"step\" - zoom step"
echo " 200 -> 200%, or 2x"
echo " 110 -> 110%, or 1.1x"
echo " 100 -> 100%, or 1x (no resize) - infinity loop. Don't use it."
echo
echo " result:"
echo " ./sliceResult/<zoom_Level>/<horizontal tiles line (x) (folder)>/<vertical tiles line (y) (file)>"
echo
echo " More options inside (TODO: add all options into command line)"
echo
exit 0
fi
# ####### Options ####### #
resultFormat='png'
resizeFilter='' # http://www.imagemagick.org/Usage/filter/
resultDir='./sliceResult'
# Selector fo slicer: A or B
scaleFromImage=true # Type of scaling: if true - scale calculates from image size to donw (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
# Options omly for slicerB
upScale=false # 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.
zoomReverse=false # false: maxZoom=100%; true: minZoom=100%
# Example:
# Scale from image (scaleFromImage=true). We have Image of some size:
# Image example
# x
# y ___________
# | |
# | |
# | |
# | |
# |___________|
#
# Step 1a: resize
# max zoom level - no need to resize
# ___________
# | |
# | |
# | |
# | |
# |___________|
#
# Step 1b: slicing
# image is slicing to tiles. Some tiles can be cropped.
# And here we have 2 options: 'extent' and 'gravity'
#
# ____/ gravity='NorthWest' /
# V
# Tile# ____ ____ _ ...
# 0-> | | | | . <- Not full tiles, set extent=true
# |____|____|_|... to get all tiles of full size
# 1-> | | | | .
# |____|____|_|...
# 2-> |____|____|_| .
# ................
# ^ ^ ^__/ Dir# 2 /
# | |
# | |__/ Dir# 1 /
# |
# |__/ Dir# 0 /
#
# Step 2a:
# Image resized to smaller size.
# ______
# | |
# | |
# |______|
#
# Next image size formula - it calculates in percents:
# (1) scale = scale * 100 / step
# If step=200:
# 100% * 100 / 200 = 50%
# If step=150:
# 100% * 100 / 175 = 57.(142857)%
# If step=300:
# 100% * 100 / 175 = 33.(3)%
# And here we have another question:
# What happens, when we have very big image and large number of zoom levels?
# Is there some loss in accuracy of calculations? (bash works with int only).
# Yes, here exist some loss. [Right now is used pixeldirect calculations]
# TODO: add oercent calculation in percents
#
# Step = 200 (200%)
#
# Bits CPU
# 64 1 0000000 000000000
# 32 1 0000000
#
# Step# Scale 32 loss 64 loss
# 1 1.0
# 10 0.0009765 625
# 15 0.0000305 17578125
# 20 0.0000009 536743164 0625
# 25 0.0000000 298023223 876953125
# 30 0.0000000 009313225 74615478515625
# 40 0.0000000 000018189 89403545856475830078125
# 50 0.0000000 000000177 63568394002504646778106689453
# 60 0.0000000 000000001 7347234759768070944119244813919
#
# So, as you can see - after step 20 and step 60 (for 32 and 64) we can't resize image.
#
# Example:
# image size is '128x128' and tile size '32' and step '200' (2.0)
# Example: (scaleFromImage=true); then zoom steps is next:
# (1) 128/1=128 -> 128/(1*2.0)=64 -> 128/(2*2.0)=32 # 3 zoom steps
# Example 2: (scaleFromImage=false)
# (2) 32*1=32 -> 32*(1*2.0)=64 -> 32*(2*2.0)=128 # 3 zoom steps
# Same result? Yes, because image have perfect size.
# But! What happens if image size various? lets take '145x145':
# (1a) 145/1=145 -> 145/2=72 -> 145/4=36 -> 145/8 # Oops! Wrong. Need slice tiles to parts or resize image to perfect size (size/tiles=x[int]).
# (2a) 32*1=32 -> 32*2=64 -> 32*4=128 # result the same, but zoom not perfect - some image data was lost on resizing
# So, the real result of (2a) be next:
# (1b) 145/1=145, 145 -> 145/32=4, 32*4=128 (4x4 tiles), 17x32 (right), 32x17(bottom) and one 17x17(right bottom) pixels tiles. Now we have 4x4 full tiles and 4+4+1 cutted tiles.
# 145/2.0=72.5, 72.5/32=2, 32*2=64 (2x2 tiles), 8x32, 32x8, 8x8
# Next options are need if scaleFromImage=false
# if extent=true:
# Vertical:
#
# |<-image->|
# ___ ___ _ _ _ _
# | | | | | ^
# |___|___|_|_| _v_Tile
# | | | | |
# |___|___|_|_|
# ^-- Transparent color (extent=true) or cropped (extent=false)
# ^--- Not full tile
# |< image >|
#
# Horizontal:
# ___ ___ ___ _ _
# | | | | ^
# |___|___|___| Image
# |___|___|___| _v_
# |___|___|___| <-- Transparent color or cropped
# ####### Options end ####### #
# ———————————————————————————————————————————————————————————————————————————————————
# ####### Variables ####### #
# Getting the data
imageSource=${1}
tileW=${2}
tileH=${3}
step=${4}
imageW=''
imageH=''
# ####### Functions ####### #
getImgW(){ # image_file
echo `identify -format "%[fx:w]" $1`
}
getImgH(){ # image_file
echo `identify -format "%[fx:h]" $1`
}
# ———————————————————————————————————————————————————————————————————————————————————
# ######################## #
# ####### 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
while [ "$imgLen" -gt "$tileLen" ]
do
r[$cnt]=$imgLen
let "cnt+=1"
let "imgLen = imgLen * 100 / zoomStep"
done
r[$cnt]=$imgLen
r[0]=$cnt
echo ${r[*]}
}
# getZooms(){ # -> zoom levels for this image (it calculate zoom levels for image and tile each side)
# local zw=`getZoomLevels $imageW $tileW $step`
# local zh=`getZoomLevels $imageH $tileH $step`
# echo $(( zw > zh ? zw : zh ))
# }
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}.${resultFormat}"
# 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}.${resultFormat}"
# 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 tilesFormat="%[fx:page.x/${tileW}]/%[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].png"
local ext=''
# local file="${resultDir}/${zoom}/%d.png"
# 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
# extent option
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(){
echo "Slicer A is running..."
local scalesW=( `getZoomLevels $imageW $tileW $step` )
local scalesH=( `getZoomLevels $imageH $tileH $step` )
local zw=${scalesW[0]}
local zh=${scalesH[0]}
local scales=()
local zoomMax=0
local zoom=0
local hMod=''
local s=1
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" -le "$zoomMax" ]
do
if $zoomReverse
then
let "zoom = s"
else
let "zoom = zoomMax - s + 1"
fi
file=`zoomImage $s "${hMod}${scales[$zoom]}"`
echo " Converted file: $file"
sliceImage $s $file
echo " Sliced file: $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
echo "Sclicer A complete"
# s=`nextScale $scaleStart`
# s=`nextScale $s`
# scaleToPercents $s
}
# ———————————————————————————————————————————————————————————————————————————————————
# ##########################
# ####### 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}.${resultFormat}"
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}.${resultFormat}"
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 $upScale
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
}
init(){
if [ "$step" -le 100 ]
then
echo "You get infinity loop. Minimum step value = 101% (101)"
exit 1
fi
rm -rf $resultDir # removing old results
mkdir -p $resultDir # creating new results folder
# Getting image sizes
imageW=`getImgW $imageSource`
imageH=`getImgH $imageSource`
setScale # Set scale
}
# ———————————————————————————————————————————————————————————————————————————————————
# ###################### #
# ### Programm start ### #
init
mainScale
# ### Programm end ##### #
# ###################### #
# ———————————————————————————————————————————————————————————————————————————————————