; indexed-lock-palette.scm
; By Pedro Gimeno Fortea, 28-7-2004.
; Donated to the public domain.

; Create a palette with locked colors using the following method:
;
; [1] Identify the pixels whose color exactly matches a color in the palette.
;     Create a selection out of it.
; [2] Clear the pixels that do not exactly match. Convert the resulting
;     image to indexed using the palette, keeping the generated colormap for
;     the final palette and taking note of the count.
; [3] Clear the pixels that do exactly match. Convert the resulting image to
;     indexed, generating an optimal palette with N-M colors (N being the
;     desired final number of colors and M the number of colors used from
;     the palette as noted in step 2). Add the result to the palette.
; Steps [4] and [5] have to be performed manually since there's no
; means in the PDB for importing the palette or creating/editing one.
; [4] Import the palette.
; [5] Use the palette to index the resulting image.

; This is how it's accomplished in detail:
;
; [1] Create a duplicate of the image. These operations will be performed on it:
;     [1a] Select all and copy to the clipboard (the clipboard now holds the original image).
;     [1b] Convert it to indexed with no dither, using the palette.
;     [1c] Convert it back to RGB and paste the original image as a new layer.
;     [1d] Set layer mode of original image to "Difference".
;     [1e] Merge down both layers.
;     [1f] Use the Threshold tool to isolate the pixels with a RGB value of (0,0,0)
;          which were the ones matching the colors in the palette.
;     [1g] Copy the resulting layer and dispose of the image.
;
;     After step [1] we have in the clipboard an image in which every black pixel
;     represents a pixel in the original image which matches a pixel in the palette.
;
; [2] Create another duplicate of the image. These operations will be performed on it:
;     [2a] Create a channel with the same size as the image and paste the clipboard into it.
;          Load it into the selection and delete it.
;     [2b] Add an alpha channel if there's none.
;     [2c] Duplicate the image for step [3].
;     [2d] Clear the selection contents.
;     [2e] Convert the result to indexed.
;     [2f] Store the colormap and set k to the number of colors.
;     [2g] Dispose of the image.
;
; [3] With the duplicate coming from step [2c] do:
;     [3a] Invert the selection and clear its contents.
;     [3b] Convert the result to indexed with N-k colors.
;     [3c] Store the colormap.
;     [3d] Dispose of the image.

;(define (script-fu-indexed-palette img drawable palette-name do-dither dither-trans rem-unused ncolors)
(define (script-fu-indexed-palette img drawable palette-name ncolors)
  (let* (
      (palette-img (car (gimp-image-new 256 1 RGB)))
      (palette-layer (car (gimp-layer-new palette-img 256 1 RGB-IMAGE "Palette" 100 NORMAL-MODE)))
      (dupimg1 (car (gimp-image-duplicate img)))
      (dupimg2 0)
      (aux-layer 0)
      (float-sel 0)
      (k 0)
      (i 0)
      (rem-unused TRUE)
      (temp-palette 0)

      ; local functions:

      (cmap2rgb (lambda (cmap rindex gindex bindex)
        (let* ((x (hexstr->bytes "000000")))
          (aset x 0 (aref cmap rindex))
          (aset x 1 (aref cmap gindex))
          (aset x 2 (aref cmap bindex))
          x
        )
      ))
      (copy-palette (lambda (source-img start)
        (let*
          (
            (palette (gimp-image-get-cmap source-img))
            (x start)
            (i 0)
            (ncol (/ (car palette) 3))
          )
          (while (< i ncol)
            (gimp-drawable-set-pixel palette-layer x 0 3 (cmap2rgb (cadr palette) (* i 3) (+ (* i 3) 1) (+ (* i 3) 2)))
            (set! i (+ i 1))
            (set! x (+ x 1))
          )
          x
        )
      ))
    )

    (gimp-image-undo-group-start palette-img)

    (gimp-image-add-layer palette-img palette-layer -1)
; [1]
; [1a]
    (gimp-selection-all dupimg1)
    (gimp-edit-copy (aref (cadr (gimp-image-get-layers dupimg1)) 0))

; [1b]
    (gimp-image-convert-indexed dupimg1 NO-DITHER CUSTOM-PALETTE 256 FALSE rem-unused palette-name)

; [1c]
    (gimp-image-convert-rgb dupimg1)
    (set! float-sel (car (gimp-edit-paste (aref (cadr (gimp-image-get-layers dupimg1)) 0) FALSE)))
    (gimp-floating-sel-to-layer float-sel)
    (set! aux-layer float-sel)

; [1d]
    (gimp-layer-set-mode aux-layer DIFFERENCE-MODE)

; [1e]
    (set! aux-layer (car (gimp-image-merge-down dupimg1 aux-layer EXPAND-AS-NECESSARY)))

; [1f]
    (gimp-threshold aux-layer 1 255)

; [1g]
    (gimp-edit-copy aux-layer)
    (gimp-image-delete dupimg1)

; [2]
    (set! dupimg1 (car (gimp-image-duplicate img)))

; [2a]
    (set! aux-layer (car (gimp-channel-new dupimg1 (car (gimp-image-width dupimg1)) (car (gimp-image-height dupimg1)) "Mask" 0.0 '(255 0 0))))
    (gimp-image-add-channel dupimg1 aux-layer 0)
    (gimp-selection-none dupimg1)
    (set! float-sel (car (gimp-edit-paste aux-layer FALSE)))
    (gimp-floating-sel-anchor float-sel)
    (gimp-selection-load aux-layer)
    (gimp-image-remove-channel dupimg1 aux-layer)

; [2b]
    (set! aux-layer (aref (cadr (gimp-image-get-layers dupimg1)) 0))
    (if (= (car (gimp-drawable-has-alpha aux-layer)) FALSE)
      (gimp-layer-add-alpha aux-layer))

; [2c]
    (set! dupimg2 (car (gimp-image-duplicate dupimg1)))

; [2d]
    (gimp-edit-clear aux-layer)

; [2e]
    (gimp-image-convert-indexed dupimg1 NO-DITHER CUSTOM-PALETTE 256 FALSE rem-unused palette-name)

; [2f]
    (set! k (copy-palette dupimg1 0))

; [2g]
    (gimp-image-delete dupimg1)

; [3]
; [3a]
    (gimp-selection-invert dupimg2)
    (set! aux-layer (aref (cadr (gimp-image-get-layers dupimg2)) 0))
    (gimp-edit-clear aux-layer)

; [3b]
    (gimp-image-convert-indexed dupimg2 NO-DITHER MAKE-PALETTE (- ncolors k) FALSE TRUE "")
; [3c]
    (set! k (copy-palette dupimg2 k))

; [3d]
    (gimp-image-delete dupimg2)

    (gimp-image-crop palette-img k 1 0 0)
    (gimp-layer-resize-to-image-size palette-layer)
    (gimp-image-convert-indexed palette-img NO-DITHER MAKE-PALETTE ncolors FALSE TRUE "")

    (gimp-image-undo-group-end palette-img)

    (gimp-display-new palette-img)
  )
)


(script-fu-register "script-fu-indexed-palette"
                    "<Image>/Script-Fu/Indexed/_Lock Palette..."
                    "Convert the given image to indexed preserving the palette."
                    "Pedro Gimeno Fortea"
                    _"Public Domain"
                    "2004-07-28"
                    "RGB*"
                    SF-IMAGE    "Image" 0
                    SF-DRAWABLE "Drawable" 0
                    SF-STRING   "Palette" "Web"
;                    SF-OPTION   "Dither type" '("No dither" "FS Dither" "FS Low Bleed" "Positioned")
;                    SF-TOGGLE   "Dither Transparency" FALSE
;                    SF-TOGGLE   "Remove unused colors" TRUE
                    SF-ADJUSTMENT "Max. colors" '(256 1 256 1 8 0 1))
