;;; hsv-graph.scm -*-scheme-*-
;;; Author: Shuji Narazaki <narazaki@InetQ.or.jp>
;;; Time-stamp: <1998/01/18 05:25:03 narazaki@InetQ.or.jp>
;;; Version: 1.2
; ************************************************************************
; Changed on Feb 4, 1999 by Piet van Oostrum <piet@cs.uu.nl>
; For use with GIMP 1.1.
; All calls to gimp-text-* have been converted to use the *-fontname form.
; The corresponding parameters have been replaced by an SF-FONT parameter.
; ************************************************************************
;;; Code:

(if (not (symbol-bound? 'script-fu-hsv-graph-scale (the-environment)))
    (define script-fu-hsv-graph-scale 1))
(if (not (symbol-bound? 'script-fu-hsv-graph-opacity (the-environment)))
    (define script-fu-hsv-graph-opacity 100))
(if (not (symbol-bound? 'script-fu-hsv-graph-bounds? (the-environment)))
    (define script-fu-hsv-graph-bounds? TRUE))
(if (not (symbol-bound? 'script-fu-hsv-graph-left2right? (the-environment)))
    (define script-fu-hsv-graph-left2right? FALSE))
(if (not (symbol-bound? 'script-fu-hsv-graph-beg-x (the-environment)))
    (define script-fu-hsv-graph-beg-x 0))
(if (not (symbol-bound? 'script-fu-hsv-graph-beg-y (the-environment)))
    (define script-fu-hsv-graph-beg-y 0))
(if (not (symbol-bound? 'script-fu-hsv-graph-end-x (the-environment)))
    (define script-fu-hsv-graph-end-x 1))
(if (not (symbol-bound? 'script-fu-hsv-graph-end-y (the-environment)))
    (define script-fu-hsv-graph-end-y 1))

(define (script-fu-hsv-graph img drawable scale opacity bounds?
			     left2right? beg-x beg-y end-x end-y)
  (define (floor x) (- x (fmod x 1)))
  (define *pos* #f)
  (define (set-point! fvec index x y)
    (aset fvec (* 2 index) x)
    (aset fvec (+ (* 2 index) 1) y)
    fvec)

  (define (plot-dot img drawable x y)
    (gimp-pencil drawable 1 (set-point! *pos* 0 x y)))

  (define (rgb-to-hsv rgb hsv)
    (let* ((red (floor (nth 0 rgb)))
	   (green (floor (nth 1 rgb)))
	   (blue (floor (nth 2 rgb)))
	   (h 0.0)
	   (s 0.0)
	   (minv (min red (min green blue)))
	   (maxv (max red (max green blue)))
	   (v maxv)
	   (delta 0))
      (if (not (= 0 maxv))
	  (set! s (/ (* (- maxv minv) 255.0) maxv))
	  (set! s 0.0))
      (if (= 0.0 s)
	  (set! h 0.0)
	  (begin
	    (set! delta (- maxv minv))
	    (cond ((= maxv red)
		   (set! h (/ (- green blue) delta)))
		  ((= maxv green)
		   (set! h (+ 2.0 (/ (- blue red) delta))))
		  ((= maxv blue)
		   (set! h (+ 4.0 (/ (- red green) delta)))))
	    (set! h (* 42.5 h))
	    (if (< h 0.0)
		(set! h (+ h 255.0)))
	    (if (< 255 h)
		(set! h (- h 255.0)))))
      (set-car! hsv (floor h))
      (set-car! (cdr hsv) (floor s))
      (set-car! (cddr hsv) (floor v))))

  ;; segment is
  ;;   filled-index (integer)
  ;;   size as number of points (integer)
  ;;   vector (which size is 2 * size)
  (define (make-segment length x y)
    (if (< 64 length)
	(set! length 64))
    (if (< length 5)
	(set! length 5))
    (let ((vec (cons-array (* 2 length) 'double)))
      (aset vec 0 x)
      (aset vec 1 y)
      (list 1 length vec)))

  ;; accessors
  (define (segment-filled-size segment) (car segment))
  (define (segment-max-size segment) (cadr segment))
  (define (segment-strokes segment) (caddr segment))

  (define (fill-segment! segment new-x new-y)
    (define (shift-segment! segment)
      (let ((base 0)
	    (size (cadr segment))
	    (vec (caddr segment))
	    (offset 2))
	(while (< base offset)
	       (aset vec (* 2 base)
		     (aref vec (* 2 (- size (- offset base)))))
	       (aset vec (+ (* 2 base) 1)
		     (aref vec (+ (* 2 (- size (- offset base))) 1)))
	       (set! base (+ base 1)))
	(set-car! segment base)))
    (let ((base (car segment))
	  (size (cadr segment))
	  (vec (caddr segment)))
      (if (= base 0)
	  (begin
	    (shift-segment! segment)
	    (set! base (segment-filled-size segment))))
      (if (and (= new-x (aref vec (* 2 (- base 1))))
	       (= new-y (aref vec (+ (* 2 (- base 1)) 1))))
	  #f
	  (begin
	    (aset vec (* 2 base) new-x)
	    (aset vec (+ (* 2 base) 1) new-y)
	    (set! base (+ base 1))
	    (if (= base size)
		(begin
		  (set-car! segment 0)
		  #t)
		(begin
		  (set-car! segment base)
		  #f))))))

  (define (draw-segment img drawable segment limit rgb)
    (gimp-context-set-foreground rgb)
    (gimp-airbrush drawable 100 (* 2 limit) (segment-strokes segment)))

  (define red-color '(255 10 10))
  (define green-color '(10 255 10))
  (define blue-color '(10 10 255))
  (define hue-segment #f)
  (define saturation-segment #f)
  (define value-segment #f)
  (define red-segment #f)
  (define green-segment #f)
  (define blue-segment #f)
  (define border-size 10)

  (define (fill-dot img drawable x y segment color)
    (if (fill-segment! segment x y)
	(begin
	  (gimp-context-set-foreground color)
	  (draw-segment img drawable segment (segment-max-size segment) color)
	  #t)
	#f))

  (define (fill-color-band img drawable x scale x-base y-base color)
    (gimp-context-set-foreground color)
    (gimp-rect-select img (+ x-base (* scale x)) 0 scale y-base CHANNEL-OP-REPLACE FALSE 0)
    (gimp-edit-bucket-fill drawable FG-BUCKET-FILL NORMAL-MODE 100 0 FALSE 0 0)
    (gimp-selection-none img))

  (define (plot-hsv img drawable x scale x-base y-base hsv)
    (let ((real-x (* scale x))
	  (h (car hsv))
	  (s (cadr hsv))
	  (v (caddr hsv)))
      (fill-dot img drawable (+ x-base real-x) (- y-base h)
		hue-segment red-color)
      (fill-dot img drawable (+ x-base real-x) (- y-base s)
		saturation-segment green-color)
      (if (fill-dot img drawable (+ x-base real-x) (- y-base v)
		    value-segment blue-color)
	  (gimp-displays-flush))))

  (define (plot-rgb img drawable x scale x-base y-base hsv)
    (let ((real-x (* scale x))
	  (h (car hsv))
	  (s (cadr hsv))
	  (v (caddr hsv)))
      (fill-dot img drawable (+ x-base real-x) (- y-base h)
		red-segment red-color)
      (fill-dot img drawable (+ x-base real-x) (- y-base s)
		green-segment green-color)
      (if (fill-dot img drawable (+ x-base real-x) (- y-base v)
		    blue-segment blue-color)
	  (gimp-displays-flush))))

  (define (clamp-value x minv maxv)
    (if (< x minv)
	(set! x minv))
    (if (< maxv x)
	(set! x maxv))
    x)

  ;; start of script-fu-hsv-graph
  (if (= TRUE bounds?)
      (if (= TRUE (car (gimp-selection-bounds img)))
	  (let ((results (gimp-selection-bounds img)))
	    (set! beg-x (nth (if (= TRUE left2right?) 1 3) results))
	    (set! beg-y (nth 2 results))
	    (set! end-x (nth (if (= TRUE left2right?) 3 1) results))
	    (set! end-y (nth 4 results)))
	  (let ((offsets (gimp-drawable-offsets drawable)))
	    (set! beg-x (if (= TRUE left2right?)
			    (nth 0 offsets)
			    (- (+ (nth 0 offsets)
				  (car (gimp-drawable-width drawable)))
			       1)))
	    (set! beg-y (nth 1 offsets))
	    (set! end-x (if (= TRUE left2right?)
			    (- (+ (nth 0 offsets)
				  (car (gimp-drawable-width drawable)))
			       1)
			    (nth 0 offsets)))
	    (set! end-y (- (+ (nth 1 offsets)
			      (car (gimp-drawable-height drawable)))
			   1))))
      (let ((offsets (gimp-drawable-offsets drawable)))
	(set! beg-x (clamp-value beg-x 0
				 (+ (nth 0 offsets)
				    (car (gimp-drawable-width drawable)))))
	(set! end-x (clamp-value end-x 0
				 (+ (nth 0 offsets)
				    (car (gimp-drawable-width drawable)))))
	(set! beg-y (clamp-value beg-y 0
				 (+ (nth 1 offsets)
				    (car (gimp-drawable-height drawable)))))
	(set! end-y (clamp-value end-y 0
				 (+ (nth 1 offsets)
				    (car (gimp-drawable-height drawable)))))))
  (set! opacity (clamp-value opacity 0 100))
  (let* ((x-len (- end-x beg-x))
	 (y-len (- end-y beg-y))
	 (limit (pow (+ (pow x-len 2) (pow y-len 2)) 0.5))
	 (gimg-width (* limit scale))
	 (gimg-height 256)
	 (gimg (car (gimp-image-new (+ (* 2 border-size) gimg-width)
				    (+ (* 2 border-size) gimg-height) RGB)))
	 (bglayer (car (gimp-layer-new gimg
				       (+ (* 2 border-size) gimg-width)
				       (+ (* 2 border-size) gimg-height)
				       1 "Background" 100 NORMAL-MODE)))
	 (hsv-layer (car (gimp-layer-new gimg
				       (+ (* 2 border-size) gimg-width)
				       (+ (* 2 border-size) gimg-height)
				      RGBA-IMAGE "HSV Graph" 100 NORMAL-MODE)))
	 (rgb-layer (car (gimp-layer-new gimg
				       (+ (* 2 border-size) gimg-width)
				       (+ (* 2 border-size) gimg-height)
				      RGBA-IMAGE "RGB Graph" 100 NORMAL-MODE)))
	 (clayer (car (gimp-layer-new gimg gimg-width 40 RGBA-IMAGE
				       "Color Sampled" opacity NORMAL-MODE)))
	 (rgb '(255 255 255))
	 (hsv '(254 255 255))
	 (x-base border-size)
	 (y-base (+ gimg-height border-size))
	 (index 0))

    (gimp-context-push)

    (gimp-image-undo-disable gimg)
    (gimp-image-add-layer gimg bglayer -1)
    (gimp-selection-all gimg)
    (gimp-context-set-background '(255 255 255))
    (gimp-edit-fill bglayer BACKGROUND-FILL)
    (gimp-image-add-layer gimg hsv-layer -1)
    (gimp-edit-clear hsv-layer)
    (gimp-image-add-layer gimg rgb-layer -1)
    (gimp-drawable-set-visible rgb-layer FALSE)
    (gimp-edit-clear rgb-layer)
    (gimp-image-add-layer gimg clayer -1)
    (gimp-edit-clear clayer)
    (gimp-layer-translate clayer border-size 0)
    (gimp-selection-none gimg)
    (set! red-segment (make-segment 64 x-base y-base))
    (set! green-segment (make-segment 64 x-base y-base))
    (set! blue-segment (make-segment 64 x-base y-base))
    (set! hue-segment (make-segment 64 x-base y-base))
    (set! saturation-segment (make-segment 64 x-base y-base))
    (set! value-segment (make-segment 64 x-base y-base))
    (gimp-context-set-brush "Circle (01)")
    (gimp-context-set-paint-mode NORMAL-MODE)
    (gimp-context-set-opacity 70)
    (gimp-display-new gimg)
    (while (< index limit)
      (set! rgb (car (gimp-image-pick-color img drawable
					    (+ beg-x (* x-len (/ index limit)))
					    (+ beg-y (* y-len (/ index limit)))
					    TRUE FALSE 0)))
      (fill-color-band gimg clayer index scale x-base 40 rgb)
      (rgb-to-hsv rgb hsv)
      (plot-hsv gimg hsv-layer index scale x-base y-base hsv)
      (plot-rgb gimg rgb-layer index scale x-base y-base rgb)
      (set! index (+ index 1)))
    (mapcar
     (lambda (segment color)
       (if (< 1 (segment-filled-size segment))
	(begin
	  (gimp-context-set-foreground color)
	  (draw-segment gimg hsv-layer segment (segment-filled-size segment)
			color))))
     (list hue-segment saturation-segment value-segment)
     (list red-color green-color blue-color))
    (mapcar
     (lambda (segment color)
       (if (< 1 (segment-filled-size segment))
	(begin
	  (gimp-context-set-foreground color)
	  (draw-segment gimg rgb-layer segment (segment-filled-size segment)
			color))))
     (list red-segment green-segment blue-segment)
     (list red-color green-color blue-color))
    (gimp-context-set-foreground '(255 255 255))
    (let ((text-layer (car (gimp-text-fontname gimg -1 0 0
				      "Red: Hue, Green: Sat, Blue: Val"
				      1 1 12 PIXELS
				      "Sans")))
	  (offset-y (- y-base (car (gimp-drawable-height clayer)))))
      (gimp-layer-set-mode text-layer DIFFERENCE-MODE)
      (gimp-layer-translate clayer 0 offset-y)
      (gimp-layer-translate text-layer border-size (+ offset-y 15)))
    (gimp-image-set-active-layer gimg bglayer)
    (gimp-image-clean-all gimg)
    (gimp-image-undo-enable gimg)

    (set! script-fu-hsv-graph-scale scale)
    (set! script-fu-hsv-graph-opacity opacity)
    (set! script-fu-hsv-graph-bounds? bounds?)
    (set! script-fu-hsv-graph-left2right? left2right?)
    (set! script-fu-hsv-graph-beg-x beg-x)
    (set! script-fu-hsv-graph-beg-y beg-y)
    (set! script-fu-hsv-graph-end-x end-x)
    (set! script-fu-hsv-graph-end-y end-y)
    (gimp-displays-flush)

    (gimp-context-pop)))

(script-fu-register "script-fu-hsv-graph"
		    _"Draw _HSV Graph..."
		    "Draph the graph of H/S/V values on the drawable"
		    "Shuji Narazaki <narazaki@InetQ.or.jp>"
		    "Shuji Narazaki"
		    "1997"
		    "RGB*"
		    SF-IMAGE       "Image to analyze"    0
		    SF-DRAWABLE    "Drawable to analyze" 0
		    SF-ADJUSTMENT _"Graph scale" (cons script-fu-hsv-graph-scale '(0.1 5 0.1 1 1 1))
		    SF-ADJUSTMENT _"BG opacity"  (cons script-fu-hsv-graph-opacity '(0 100 1 10 0 1))
		    SF-TOGGLE     _"Use selection bounds instead of belows" script-fu-hsv-graph-bounds?
		    SF-TOGGLE     _"From top-left to bottom-right" script-fu-hsv-graph-left2right?
		    SF-ADJUSTMENT _"Start X" (cons script-fu-hsv-graph-beg-x '(0 5000 1 10 0 1))
		    SF-ADJUSTMENT _"Start Y" (cons script-fu-hsv-graph-beg-y '(0 5000 1 10 0 1))
		    SF-ADJUSTMENT _"End X" (cons script-fu-hsv-graph-end-x '(0 5000 1 10 0 1))
		    SF-ADJUSTMENT _"End Y" (cons script-fu-hsv-graph-end-y '(0 5000 1 10 0 1)))

(script-fu-menu-register "script-fu-hsv-graph"
			 _"<Image>/Script-Fu/Utils")
