| 1 | (in-package :quickhoney.imageproc) |
|---|
| 2 | |
|---|
| 3 | (defparameter *button-size* 208) |
|---|
| 4 | (defparameter *big-button-size* 318) |
|---|
| 5 | |
|---|
| 6 | (cl-interpol:enable-interpol-syntax) |
|---|
| 7 | |
|---|
| 8 | (defun corner-cutout-coords (image-width image-height radius) |
|---|
| 9 | "Return a list of coordinates that need to be made transparent or |
|---|
| 10 | colored in background color to get a rounded corner effect. |
|---|
| 11 | IMAGE-WIDTH and IMAGE-HEIGHT are the dimensions of the image, RADIUS |
|---|
| 12 | is the desired corner rounding radius. The list of coordinates that |
|---|
| 13 | is returned is ordered by row and column so that DO-ROWS and |
|---|
| 14 | DO-PIXELS-IN-ROW can be used to iterate over the image and pop |
|---|
| 15 | coordinate pairs off the front of the list at the same time." |
|---|
| 16 | (let ((radius (floor radius)) |
|---|
| 17 | (diameter (+ 1 radius radius)) |
|---|
| 18 | coords) |
|---|
| 19 | (assert (and (>= image-width diameter) |
|---|
| 20 | (>= image-height diameter))) |
|---|
| 21 | (with-image (circle diameter diameter) |
|---|
| 22 | (let ((white (allocate-color 255 255 255 :image circle)) |
|---|
| 23 | (black (allocate-color 0 0 0 :image circle))) |
|---|
| 24 | (fill-image 0 0 :color white :image circle) |
|---|
| 25 | (draw-filled-circle radius radius radius :color black :image circle) |
|---|
| 26 | (do-rows (y circle) |
|---|
| 27 | (do-pixels-in-row (x) |
|---|
| 28 | (when (eql (raw-pixel) white) |
|---|
| 29 | (push (list (if (< x radius) x (+ (- image-width diameter) x)) |
|---|
| 30 | (if (< y radius) y (+ (- image-height diameter) y))) |
|---|
| 31 | coords)))))) |
|---|
| 32 | (nreverse coords))) |
|---|
| 33 | |
|---|
| 34 | (defun corner-image (&key (image *default-image*) |
|---|
| 35 | (radius (/ (max (image-width image) (image-height image)) 40)) |
|---|
| 36 | corner-color) |
|---|
| 37 | (with-default-image (image) |
|---|
| 38 | (unless corner-color |
|---|
| 39 | (setf (save-alpha-p) t |
|---|
| 40 | corner-color (if (true-color-p) #x7f000000 |
|---|
| 41 | (or (transparent-color) |
|---|
| 42 | (allocate-color 255 255 255 :alpha 127) |
|---|
| 43 | (error "can't allocate transparent color for button"))) |
|---|
| 44 | (transparent-color) corner-color)) |
|---|
| 45 | (let ((coords (corner-cutout-coords (image-width) (image-height) radius))) |
|---|
| 46 | (destructuring-bind (x-tx y-tx) (car coords) |
|---|
| 47 | (do-rows (y) |
|---|
| 48 | (do-pixels-in-row (x) |
|---|
| 49 | (when (and (eql x x-tx) |
|---|
| 50 | (eql y y-tx)) |
|---|
| 51 | (setf (raw-pixel) corner-color) |
|---|
| 52 | (when (cdr coords) |
|---|
| 53 | (setf coords (cdr coords) |
|---|
| 54 | x-tx (caar coords) |
|---|
| 55 | y-tx (cadar coords)))))))))) |
|---|
| 56 | |
|---|
| 57 | (define-imageproc-handler cutout-button (input-image |
|---|
| 58 | &optional keyword |
|---|
| 59 | (background-color "ffffff") |
|---|
| 60 | (button-width "208") |
|---|
| 61 | (button-height "208") |
|---|
| 62 | (radius "8") |
|---|
| 63 | category) |
|---|
| 64 | (let* ((button-width (parse-integer button-width)) |
|---|
| 65 | (button-height (parse-integer button-height)) |
|---|
| 66 | (button-image (create-image button-width button-height t)) |
|---|
| 67 | (category (bknr.utils:make-keyword-from-string category)) |
|---|
| 68 | (scale-factor (funcall (if (eq :pixel category) |
|---|
| 69 | #'ceiling #'identity) |
|---|
| 70 | (if (> (/ (image-width input-image) (image-height input-image)) |
|---|
| 71 | (/ button-width button-height)) |
|---|
| 72 | (/ button-height (image-height input-image)) |
|---|
| 73 | (/ button-width (image-width input-image))))) |
|---|
| 74 | (cutout-width (floor (/ button-width scale-factor))) |
|---|
| 75 | (cutout-height (floor (/ button-height scale-factor))) |
|---|
| 76 | (radius (parse-integer radius))) |
|---|
| 77 | (copy-image input-image button-image |
|---|
| 78 | (floor (- (image-width input-image) cutout-width) 2) |
|---|
| 79 | (if (eq :pixel category) |
|---|
| 80 | (floor (- (image-height input-image) cutout-height) 2) |
|---|
| 81 | 0) |
|---|
| 82 | 0 0 |
|---|
| 83 | cutout-width cutout-height |
|---|
| 84 | :resize t :resample t |
|---|
| 85 | :dest-width button-width :dest-height button-height) |
|---|
| 86 | (when keyword |
|---|
| 87 | (let ((type-store-image (store-image-with-name (format nil "type-~(~A~)" keyword)))) |
|---|
| 88 | (unless type-store-image |
|---|
| 89 | (error "can't find type image for keyword ~A" keyword)) |
|---|
| 90 | (with-store-image (type-image type-store-image) |
|---|
| 91 | (copy-image type-image button-image |
|---|
| 92 | 0 0 |
|---|
| 93 | 0 0 |
|---|
| 94 | (image-width type-image) (image-height type-image))))) |
|---|
| 95 | (unless (zerop radius) |
|---|
| 96 | (corner-image :image button-image |
|---|
| 97 | :radius radius |
|---|
| 98 | :corner-color (parse-color background-color :image button-image))) |
|---|
| 99 | button-image)) |
|---|
| 100 | |
|---|
| 101 | (define-imageproc-handler center-thumbnail (input-image width height) |
|---|
| 102 | (setq width (parse-integer width) |
|---|
| 103 | height (parse-integer height)) |
|---|
| 104 | (unless (or (> width (image-width input-image)) |
|---|
| 105 | (> height (image-width input-image))) |
|---|
| 106 | (let ((thumbnail-image (create-image width height t))) |
|---|
| 107 | (copy-image input-image thumbnail-image |
|---|
| 108 | (round (/ (- (image-width input-image) width) 2)) |
|---|
| 109 | (round (/ (- (image-height input-image) height) 2)) |
|---|
| 110 | 0 0 |
|---|
| 111 | width height) |
|---|
| 112 | thumbnail-image))) |
|---|
| 113 | |
|---|
| 114 | (defparameter +news-image-width+ 486 |
|---|
| 115 | "Width of news images") |
|---|
| 116 | (defparameter +news-image-corner-radius+ 8 |
|---|
| 117 | "Corner radius for news images") |
|---|
| 118 | |
|---|
| 119 | (define-imageproc-handler news-article-cutout (input-image) |
|---|
| 120 | (let* ((image-height (floor (* +news-image-width+ |
|---|
| 121 | (/ (image-height input-image) (image-width input-image))))) |
|---|
| 122 | (output-image (create-image +news-image-width+ image-height t))) |
|---|
| 123 | (copy-image input-image output-image |
|---|
| 124 | 0 0 |
|---|
| 125 | 0 0 |
|---|
| 126 | +news-image-width+ image-height |
|---|
| 127 | :resize t :resample t |
|---|
| 128 | :dest-width +news-image-width+ :dest-height image-height) |
|---|
| 129 | (corner-image :image output-image |
|---|
| 130 | :radius +news-image-corner-radius+ |
|---|
| 131 | :corner-color (allocate-color 255 255 255 :image output-image)) |
|---|
| 132 | output-image)) |
|---|
| 133 | |
|---|
| 134 | (define-imageproc-handler download (input-image filename) |
|---|
| 135 | (setf (tbnl:header-out :content-disposition) #?"attachment; filename=$(filename)") |
|---|
| 136 | input-image) |
|---|