#!/bin/bash version="0.001" date="24/07/2015" if [ $# -le 3 ]; then echo "Usage: magick-slicer.sh \"\" " 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///" 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 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 ##### # # ###################### # # ———————————————————————————————————————————————————————————————————————————————————