Tuesday, May 26, 2015

Generating tiles for Google Maps API

I use Google Maps API to render the maps on my Zelda Parallel Worlds walkthrough, and as a result I needed to generate the necessary tiles for the Maps API to use.  My source image was a 4,096x4,096 image, and I needed to generate 256x256 tiles at various zoom levels, starting at fully zoomed out, where the entire map was contained in a single tile, up to however large I could reasonable render (which ended up being a whopping 16,384x16,384).  GIMP's script-fu functionality was perfect for the task, but I couldn't find a script that quite did what I wanted, including scaling the map to the various zoom levels, so I made my own.  I used the tiles-to-files plugin as my starting point and went from there.  The end result gives the following options


Tile size is adjustable (though I've only tested powers of 2, such as 64, 128, and 256)
Max zoom level determines how many zoom levels should be generated.  The lowest zoom level is 0, which is a single tile in size.

Output file type is selectable between PNG and JPEG

Interpolation mode can be chosen separately for shrinking and growing.  In my case, I didn't want any interpolation when growing, since I was growing a pixel-perfect image by a factor of 2 each zoom level, so I wanted to retain the pixel-perfect aspect and just create "big pixels".

Here's the script:

; Google Maps Tiles, V1.0
;
; Based on the tiles-to-files script by theilr
; http://registry.gimp.org/node/20868
;
; http://www.qwertymodo.com
;
(define (script-fu-google-maps-tiles inImage inDrawable inTileSize inMaxZoom
                                     inFileType inShrinkMethod inGrowMethod outDir)
  (gimp-image-undo-group-start inImage)
  (let* (
          (fullWidth  (car (gimp-image-width  inImage)))
          (fullHeight (car (gimp-image-height inImage)))
          (tileWidth  inTileSize)
          (tileHeight inTileSize)
          (zoomWidth  tileWidth)
          (zoomHeight tileHeight)
          (newImage (car (gimp-image-new tileWidth tileHeight RGB)))
          (tmpImage)
          (tmpLayer)
          (selLayer)
          (outfname)
          (hcnt 0)
          (vcnt 0)
          (zcnt 0)
    )
   
    (set! zcnt 0)
    (while (<= zcnt inMaxZoom)
      (set! zoomWidth (* tileWidth (expt 2 zcnt)))
      (set! zoomHeight (* tileHeight (expt 2 zcnt)))
     
      (gimp-rect-select inImage
                        0
                        0
                        fullWidth
                        fullHeight
                        CHANNEL-OP-ADD FALSE 0)
                       
      (gimp-edit-copy-visible inImage)
      (gimp-selection-none inImage)
     
      (set! tmpImage
        (car (gimp-image-new zoomWidth zoomHeight RGB)))
         
      (set! tmpLayer
        (car (gimp-layer-new tmpImage fullWidth fullHeight
                     RGB-IMAGE "Background" 100
                     NORMAL-MODE)))                    
      (gimp-image-add-layer tmpImage tmpLayer -1)
      (set! selLayer
        (car (gimp-edit-paste tmpLayer FALSE)))
      (gimp-floating-sel-anchor selLayer)
     
      (if (< zoomWidth fullWidth)
        (begin
          (gimp-context-set-interpolation inShrinkMethod)
          (gimp-layer-scale tmpLayer zoomWidth zoomHeight FALSE)
          (gimp-image-resize-to-layers tmpImage)
        )
      )
     
      (if (> zoomWidth fullWidth)
        (begin
          (gimp-context-set-interpolation inGrowMethod)
          (gimp-layer-scale tmpLayer zoomWidth zoomHeight FALSE)
          (gimp-image-resize-to-layers tmpImage)
        )
      )
   
      (set! hcnt 0)
      (while (< (* hcnt tileWidth) zoomWidth)
        (set! vcnt 0)
        (while (< (* vcnt tileHeight) zoomHeight)
          (gimp-rect-select tmpImage
                            (* tileWidth hcnt)
                            (* tileHeight vcnt)
                            tileWidth
                            tileHeight
                            CHANNEL-OP-ADD FALSE 0)
          (gimp-edit-copy-visible tmpImage)
          (gimp-selection-none tmpImage)
         
          (set! tmpLayer
            (car (gimp-layer-new newImage tileWidth tileHeight
                         RGB-IMAGE "Background" 100
                         NORMAL-MODE)))
          (gimp-image-add-layer newImage tmpLayer -1)
          (set! selLayer
            (car (gimp-edit-paste tmpLayer FALSE)))
          (gimp-floating-sel-anchor selLayer)
         
          (if (= inFileType 0)
            (begin
              (set! outfname (string-append outDir
                                            "/"
                                            (number->string zcnt)
                                            "-"
                                            (number->string hcnt)
                                            "-"
                                            (number->string vcnt)
                                            ".png"))
         
              (file-png-save  RUN-NONINTERACTIVE
                              newImage
                              tmpLayer
                              outfname
                              outfname
                              0 9 1 0 0 1 1 )
              )
            )
         
          (if (= inFileType 1)
            (begin
              (set! outfname (string-append outDir
                                            "/"
                                            (number->string zcnt)
                                            "-"
                                            (number->string hcnt)
                                            "-"
                                            (number->string vcnt)
                                            ".jpg"))
         
              (file-jpeg-save RUN-NONINTERACTIVE
                              newImage
                              tmpLayer
                              outfname
                              outfname
                              0.95 ; JPEG compression level
                              0    ; Smoothing
                              1    ; Optimize
                              1    ; Progressive
                              ""   ; Comment
                              0    ; Subsampling (0-4)
                              1    ; Baseline
                              0    ; Restart
                              0    ; DCT
                              )
              )
            )
     
          (set! vcnt (+ vcnt 1))
          )
        (set! hcnt (+ hcnt 1))
        )
       
      (gimp-image-delete tmpImage)
     
      (set! zcnt (+ zcnt 1))
      )
     
    (gimp-image-delete newImage)
    (gimp-image-undo-group-end inImage)
    (gimp-displays-flush)
  )
)
(script-fu-register
  "script-fu-google-maps-tiles"            ; function name
  "<Image>/Filters/Tiles/_Google Maps"     ; menu label
  "Split an image into tiles suitable\     ; description
   for use with Google Maps API"
  "qwertymodo"                             ; author
  "(c) 2015, qwertymodo"                   ; copyright notice
  "25 May 2015"                            ; date created
  "RGB*"                                   ; image type
  SF-IMAGE      "Image"   0
  SF-DRAWABLE   "Drawable" 0
  SF-ADJUSTMENT "Tile Size (px)"           '(128 8 1024 1 8 0 SF-SPINNER)
  SF-ADJUSTMENT "Max Zoom Level"           '(4 0 10 1 2 0 SF-SPINNER)
  SF-OPTION     "Output File Type"         '("png" "jpg")
  SF-ENUM       "Interpolation (Shrink)"   '("InterpolationType" "cubic")
  SF-ENUM       "Interpolation (Grow)"     '("InterpolationType" "cubic")
  SF-DIRNAME    "Output Folder"            "tiles"
)