diff --git a/MagickSlicer.sh b/MagickSlicer.sh new file mode 100644 index 0000000..b5c8c37 --- /dev/null +++ b/MagickSlicer.sh @@ -0,0 +1,463 @@ +#!/bin/bash +version="0.001" +date="24/07/2015" +if [ $# -le 3 ]; then + echo "Usage: imageSlicer.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 ##### # +# ###################### # +# ———————————————————————————————————————————————————————————————————————————————————