;;;; pixbuf.lisp (in-package #:iip-project) (defun clamp (x min max) (cond ((< x min) min) ((> x max) max) (t x))) (defstruct pixel-str (length 0 :type integer) (height 0 :type integer) (width 0 :type integer) (stride 0 :type integer) (data)) ; data is a foreign cffi pointer (cffi:define-foreign-library libgdk-pixbuf (t (:default "libgdk_pixbuf-2.0"))) (cffi:use-foreign-library libgdk-pixbuf) (cffi:defcfun ("gdk_pixbuf_get_pixels_with_length" gdk-pixbuf-get-pixels-with-length) :pointer (pixbuf :pointer) (len :pointer)) (defun pixbuf->pixel-str (pixbuf) (let* ((height (gdk-pixbuf:pixbuf-height pixbuf)) (width (gdk-pixbuf:pixbuf-width pixbuf)) (stride (gdk-pixbuf:pixbuf-rowstride pixbuf))) (cffi:with-foreign-object (len-loc :int) (let ((data (gdk-pixbuf-get-pixels-with-length (gobj::object-pointer pixbuf) len-loc))) (make-pixel-str :length (cffi:mem-aref len-loc :int) :height height :width width :stride stride :data data))))) (cffi:defcstruct c-rgba (red :unsigned-char) (green :unsigned-char) (blue :unsigned-char) (alpha :unsigned-char)) (defun luminance (rgb-vec) (let ((r (svref rgb-vec 0)) (g (svref rgb-vec 1)) (b (svref rgb-vec 2))) (+ (* 0.2126 r) (* 0.7152 g) (* 0.0722 b)))) ; h, s and l are floats [0,1] ; r, g and b are ints [0,255] (defun hsl->rgb (hsl-vec) (flet ((hue->rgb (p q h) (when (< h 0) (incf h)) (when (> h 1) (decf h)) (round (* 255 (cond ((< h 1/6) (+ p (* (- q p) 6 h))) ((< h 1/2) q) ((< h 2/3) (+ p (* (- q p) (- 2/3 h) 6))) (h p)))))) (let ((h (svref hsl-vec 0)) (s (svref hsl-vec 1)) (l (svref hsl-vec 2))) (if (= s 0) (vector (round (* 255 l)) (round (* 255 l)) (round (* 255 l))) (let* ((q (if (< l 0.5) (* l (+ s 1)) (+ l s (- (* l s))))) (p (- (* 2 l) q))) (vector (hue->rgb p q (+ h 1/3)) (hue->rgb p q h) (hue->rgb p q (- h 1/3)))))))) (defun rgb->hsl (rgb-vec) (let* ((rgb-vec-norm (map 'simple-vector (lambda (x) (/ x 255.0)) rgb-vec)) (r (svref rgb-vec-norm 0)) (g (svref rgb-vec-norm 1)) (b (svref rgb-vec-norm 2)) (vmax (max r g b)) (vmin (min r g b)) (l (/ (+ vmax vmin) 2))) (if (= vmax vmin) (vector 0.0 0.0 l) (let* ((d (- vmax vmin)) (s (if (> l 0.5) (/ d (- 2 vmax vmin)) (/ d (+ vmax vmin)))) (h (/ (cond ((= vmax r) (+ (/ (- g b) d) (if (< g b) 6 0))) ((= vmax g) (+ (/ (- b r) d) 2)) ((= vmax b) (+ (/ (- r g) d) 4))) 6))) (vector h s l))))) (defmacro image-proc-lambda (arglist &body body) (labels ((double-float-p (x) (typep x 'double-float)) (real-and-3-doubles (lst) (and (eq (car lst) 'real) (= 4 (length lst)) (every #'double-float-p (cdr lst)))) (proper-arglist (lst) (and (listp lst) (every #'listp lst) (every (lambda (arg) (and (symbolp (car arg)) (listp (cadr arg)) (eq 'real (caadr arg)) (= 3 (length (cdadr arg))) (every #'double-float-p (cdadr arg)))) lst))) (argspec (arg) (cadr arg)) (arg-min (arg) (second (argspec arg))) (arg-max (arg) (third (argspec arg))) (arg-step (arg) (fourth (argspec arg))) (get-argnames (arglist) (mapcar #'car arglist)) (get-label-names (arglist) (mapcar (lambda (arg) (gensym (concatenate 'string "label-" (string (car arg))))) arglist)) (get-widget-names (arglist) (mapcar (lambda (arg) (gensym (concatenate 'string (cond ((real-and-3-doubles (argspec arg)) "scale-")) (string (car arg))))) arglist)) (label-def (label-name arg) `(,label-name (gtk:make-label :str ,(concatenate 'string (string (car arg)) ": ")))) (widget-def (widget-name arg) `(,widget-name (,(cond ((real-and-3-doubles (argspec arg)) 'gtk:make-scale)) :min ,(arg-min arg) :max ,(arg-max arg) :step ,(arg-step arg) :orientation gtk:+orientation-horizontal+))) (widget-set (widget-name arg) (cond ((real-and-3-doubles (argspec arg)) `((setf (gtk:scale-draw-value-p ,widget-name) t) (setf (gtk:scale-has-origin-p ,widget-name) t) (setf (gtk:scale-digits ,widget-name) ,(let ((digits (round (- (log (arg-step arg) 10))))) (if (< digits 0) 0 digits))) (setf (gtk:scale-value-pos ,widget-name) gtk:+position-type-left+) (setf (gtk:range-value ,widget-name) ,(/ (+ (arg-min arg) (arg-max arg)) 2.0d0)) (setf (gtk:widget-hexpand-p ,widget-name) t))))) (widgets-labels-grid (widget-names label-names) (let ((numlist (loop for x from 0 below (length widget-names) collect x))) (append (apply #'append (mapcar (lambda (w-name l-name num) `((gtk:grid-attach grid ,l-name 0 ,num 1 1) (gtk:grid-attach grid ,w-name 1 ,num 1 1))) widget-names label-names numlist)) `((gtk:grid-attach grid submit 0 ,(length widget-names) 1 1))))) (bind-arg (arg widget-name) (cond ((real-and-3-doubles (argspec arg)) `(,(car arg) (gtk:range-value ,widget-name)))))) (let ((argnames (get-argnames arglist))) (cond ((not (proper-arglist arglist)) (error "~A is not a proper argument list for macro image-proc-lambda." arglist)) ((not (equal argnames (remove-duplicates argnames))) (error "Arglist ~A has duplicates" arglist)) (t (let* ((label-names (get-label-names arglist)) (label-defs (mapcar #'label-def label-names arglist)) (widget-names (get-widget-names arglist)) (widget-defs (mapcar #'widget-def widget-names arglist)) (widget-settings (apply #'append (mapcar #'widget-set widget-names arglist))) (arg-bindings (mapcar #'bind-arg arglist widget-names))) `(lambda (button) (unless (null *pixbuf-original*) (let ((dialog-window (gtk:make-window)) (grid (gtk:make-grid)) ,@ label-defs ,@ widget-defs (submit (gtk:make-button :label "Submit"))) (setf (gtk:window-child dialog-window) grid) ,@ widget-settings ,@ (widgets-labels-grid widget-names label-names) (gtk:connect submit "clicked" (lambda (submit) (let* ((ps (pixbuf->pixel-str *pixbuf-new*)) (length (pixel-str-length ps)) (height (pixel-str-height ps)) (width (pixel-str-width ps)) (stride (pixel-str-stride ps)) (data (pixel-str-data ps)) ,@ arg-bindings) ,@ body (setf (gtk:image-from-pixbuf *img-new*) *pixbuf-new*)) (gtk:window-destroy dialog-window))) (gtk:window-present dialog-window)))))))))) (defparameter *function-alist* `(("rotation" . ,(image-proc-lambda ((degree (real -60.0d0 +60.0d0 0.1d0))) (let* ((angle (* degree pi 1/180)) (rescale (sqrt (/ (min (* (expt height 2) (+ 1 (expt (tan (- (/ pi 2) (abs angle) (atan (/ height width)))) 2))) (* (expt width 2) (+ 1 (expt (tan (- (/ pi 2) (abs angle) (atan (/ width height)))) 2)))) (+ (expt height 2) (expt width 2))))) (mod-pixbuf (gdk-pixbuf:pixbuf-copy *pixbuf-new*)) (m-ps (pixbuf->pixel-str mod-pixbuf)) (m-data (pixel-str-data m-ps)) (zero-struct (cffi:convert-to-foreign '(red 0 green 0 blue 0 alpha 0) '(:struct c-rgba)))) (loop for x from 0 below width do (loop for y from 0 below height do (let* ((shx (- x (floor width 2))) (shy (- y (floor height 2))) (newx (/ (- (* (cos (- angle)) shx) (* (sin (- angle)) shy)) rescale)) (newy (/ (+ (* (sin (- angle)) shx) (* (cos (- angle)) shy)) rescale)) (newx-cen (+ newx (floor width 2))) (newy-cen (+ newy (floor height 2))) (newx-nn (round newx-cen)) (newy-nn (round newy-cen))) (setf (cffi:mem-aref m-data '(:struct c-rgba) (+ (* y width) x)) (if (or (not (< -1 newx-nn width)) (not (< -1 newy-nn height))) (cffi:mem-aref zero-struct '(:struct c-rgba)) (cffi:mem-aref data '(:struct c-rgba) (+ (* newy-nn width) newx-nn))))))) (setf *pixbuf-new* mod-pixbuf)))) ("brightness" . ,(image-proc-lambda ((brightness (real -1.0d0 +1.0d0 0.01d0)) (contrast (real -1.0d0 +1.0d0 0.01d0))) (loop for i from 0 below (/ length 4) do (loop for ch from 0 below 3 do (let* ((val (cffi:mem-aref data :unsigned-char (+ (* 4 i) ch)))) (setf (cffi:mem-aref data :unsigned-char (+ (* 4 i) ch)) (let* ((brightness (/ brightness 2.0)) (slant (tan (* (+ contrast 1) (/ pi 4)))) (val1 (if (< brightness 0.0) (* val (+ 1.0 brightness)) (+ val (* (- 255 val) brightness)))) (val2 (+ (* (- val1 127) slant) 127)) (val3 (ldb (byte 8 0) (floor (clamp val2 0 255))))) val3))))))) ("warmth" . ,(image-proc-lambda ((temperature (real 1000.0d0 12000.0d0 100.0d0))) (flet ((addv (v1 v2) (map 'simple-vector (lambda (x1 x2) (+ x1 x2)) v1 v2)) (mulsv (s v) (map 'simple-vector (lambda (x) (* x s)) v))) (multiple-value-bind (quot remainder) (truncate temperature 100.0d0) (let* ((lower (* quot 100.0d0)) (lower-index (- quot 10)) (line-portion (/ remainder 100.0d0)) (low-factor (cdr (svref +color-temp-table+ lower-index))) (rgb-factor (map 'simple-vector #'round (if (= lower temperature) low-factor (addv (mulsv (- 1 line-portion) low-factor) (mulsv line-portion (cdr (svref +color-temp-table+ (1+ lower-index))))))))) (loop for i from 0 below (/ length 4) do (loop for ch from 0 below 3 do (let* ((val (cffi:mem-aref data :unsigned-char (+ (* 4 i) ch)))) (setf (cffi:mem-aref data :unsigned-char (+ (* 4 i) ch)) (round (* val (svref rgb-factor ch) 1/255))))))))))) ("saturation" . ,(image-proc-lambda ((saturation (real 0.0d0 2.0d0 0.01d0))) (loop for i from 0 below (/ length 4) do (let* ((rgb (vector (cffi:mem-aref data :unsigned-char (+ (* 4 i) 0)) (cffi:mem-aref data :unsigned-char (+ (* 4 i) 1)) (cffi:mem-aref data :unsigned-char (+ (* 4 i) 2)))) (hsl (rgb->hsl rgb)) (s (svref hsl 1))) (setf s (clamp (* s saturation) 0 1)) (setf (svref hsl 1) s) (let ((new-rgb (hsl->rgb hsl))) (setf (cffi:mem-aref data :unsigned-char (+ (* 4 i) 0)) (svref new-rgb 0)) (setf (cffi:mem-aref data :unsigned-char (+ (* 4 i) 1)) (svref new-rgb 1)) (setf (cffi:mem-aref data :unsigned-char (+ (* 4 i) 2)) (svref new-rgb 2))))))) ("fade" . ,(image-proc-lambda ((fade (real 0.0d0 1.0d0 0.01d0))) (loop for i from 0 below (/ length 4) do (let* ((rgb (vector (cffi:mem-aref data :unsigned-char (+ (* 4 i) 0)) (cffi:mem-aref data :unsigned-char (+ (* 4 i) 1)) (cffi:mem-aref data :unsigned-char (+ (* 4 i) 2)))) (hsl (rgb->hsl rgb)) (l (svref hsl 2))) (setf l (- 1 (* (- 1 l) (- 1 fade)))) (setf (svref hsl 2) l) (let ((new-rgb (hsl->rgb hsl))) (setf (cffi:mem-aref data :unsigned-char (+ (* 4 i) 0)) (svref new-rgb 0)) (setf (cffi:mem-aref data :unsigned-char (+ (* 4 i) 1)) (svref new-rgb 1)) (setf (cffi:mem-aref data :unsigned-char (+ (* 4 i) 2)) (svref new-rgb 2))))))) ("zoom" . ,(image-proc-lambda ((zoom (real 1.0d0 +10.0d0 0.1d0))) (let* ((mod-pixbuf (gdk-pixbuf:pixbuf-copy *pixbuf-new*)) (m-ps (pixbuf->pixel-str mod-pixbuf)) (m-data (pixel-str-data m-ps)) (zero-struct (cffi:convert-to-foreign '(red 0 green 0 blue 0 alpha 0) '(:struct c-rgba)))) (loop for x from 0 below width do (loop for y from 0 below height do (let* ((shx (- x (floor width 2))) (shy (- y (floor height 2))) (newx (/ shx zoom)) (newy (/ shy zoom)) (newx-cen (+ newx (floor width 2))) (newy-cen (+ newy (floor height 2))) (newx-nn (round newx-cen)) (newy-nn (round newy-cen))) (setf (cffi:mem-aref m-data '(:struct c-rgba) (+ (* y width) x)) (if (or (not (< -1 newx-nn width)) (not (< -1 newy-nn height))) (cffi:mem-aref zero-struct '(:struct c-rgba)) (cffi:mem-aref data '(:struct c-rgba) (+ (* newy-nn width) newx-nn))))))) (setf *pixbuf-new* mod-pixbuf)))))) (defparameter *pixbuf-original* ()) (defparameter *pixbuf-new* ()) ; FIXME: temporary workaround (defparameter *img-new* ())