Skip to content

Commit

Permalink
Resize session images to 500x500
Browse files Browse the repository at this point in the history
  • Loading branch information
plexus committed Sep 15, 2024
1 parent f08c023 commit 9286d4e
Show file tree
Hide file tree
Showing 10 changed files with 167 additions and 9 deletions.
3 changes: 2 additions & 1 deletion deps.edn
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,8 @@
potemkin/potemkin {:mvn/version "0.4.7"} ; def-map-type

;; Markdown
markdown-to-hiccup/markdown-to-hiccup {:mvn/version "0.6.2"}}
markdown-to-hiccup/markdown-to-hiccup {:mvn/version "0.6.2"}
com.drewnoakes/metadata-extractor {:mvn/version "2.19.0"}}

:aliases
{:dev
Expand Down
1 change: 1 addition & 0 deletions ops/nginx.conf
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ server {
listen 8000;
listen [::]:8000;
server_name compass.heartofclojure.eu;
client_max_body_size 20M;

location /uploads/ {
alias /home/compass/uploads/;
Expand Down
1 change: 1 addition & 0 deletions src/co/gaiwan/compass/db/schema.clj
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@
[:session/duration :string "Duration of the session in ISO interval notation"]
[:session/location :ref "Where does the session take place"]
[:session/image :string "Image URL, either absolute, or relative to compass root"]
[:session/thumbnail :string "Downsized image thumbnail"]
[:session/capacity :long "Number of people that are able to join this session"]
[:session/ticket-required? :boolean "If this session requires a ticket"]
[:session/published? :boolean "If this session is published/visible?"]
Expand Down
1 change: 0 additions & 1 deletion src/co/gaiwan/compass/html/profiles.clj
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,6 @@
:border-radius t/--size-2}
[#{:textarea "input[type='text']"} {:background-color t/--surface-3}]]
([user]
(def user user)
[:<>
[:h2 "Edit Profile"]
[:form {:method "POST" :action "/profile/save" :enctype "multipart/form-data"}
Expand Down
98 changes: 98 additions & 0 deletions src/co/gaiwan/compass/images.clj
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
(ns co.gaiwan.compass.images
(:require
[clojure.java.io :as io])
(:import
(java.awt.geom AffineTransform)
(java.awt.image AffineTransformOp BufferedImage DataBufferByte Raster)
(java.io ByteArrayInputStream ByteArrayOutputStream)
(java.lang ArrayIndexOutOfBoundsException)
(javax.imageio ImageIO)))

(set! *warn-on-reflection* true)

(def ^"[F" float4 (make-array Float/TYPE 4))

(defn image-raster ^Raster [^BufferedImage img]
(.. img getRaster))

(defn raster-data [img]
(.getData ^DataBufferByte (.getDataBuffer (image-raster img))))

(defn read-img [path]
(ImageIO/read (io/file path)))

(defn bytes->img [bytes]
(ImageIO/read (ByteArrayInputStream. bytes)))

(defn write-png [path img]
(ImageIO/write
^java.awt.image.RenderedImage img
"png"
(io/file path)))

(defn slice [img [x1 y1 x2 y2]]
(let [src-raster (image-raster img)
width (int (Math/ceil (inc (- x2 x1))))
height (int (Math/ceil (inc (- y2 y1))))
dest (BufferedImage. width height BufferedImage/TYPE_INT_ARGB)
^java.awt.image.WritableRaster dest-raster (image-raster dest)]
(doseq [x (range width)
y (range height)
px (.getPixel src-raster (int (+ x1 x)) (int (+ y1 y)) float4)]
(try
(.setPixel dest-raster (int x) (int y) float4)
(catch ArrayIndexOutOfBoundsException e
(println "out of bounds:" [x1 y1 x2 y2] [x y] :width width :height height)
(throw e))))
dest))

(defn width [^BufferedImage img]
(.getWidth img))

(defn height [^BufferedImage img]
(.getHeight img))

(defn new-img ^BufferedImage [w h]
(BufferedImage. w h BufferedImage/TYPE_INT_ARGB))

(defn apply-transform [^BufferedImage in ^BufferedImage out ^AffineTransform at]
(let [^AffineTransformOp at-op (AffineTransformOp. at AffineTransformOp/TYPE_BILINEAR)]
(.filter at-op in out)))

(defn scale-img [^BufferedImage img scale]
(apply-transform
img
(new-img (* (width img) scale)
(* (height img) scale))
(AffineTransform/getScaleInstance scale scale)))

(defn rotate-180 [^BufferedImage img]
(apply-transform
img
(new-img (width img) (height img))
(AffineTransform/getRotateInstance Math/PI (/ (width img) 2) (/ (height img) 2))))

(defn rotate-clockwise [^BufferedImage img]
(let [h (height img) w (width img)]
(apply-transform
img
(new-img h w)
(doto (AffineTransform.)
(.translate (/ h 2) (/ w 2))
(.rotate (/ Math/PI 2))
(.translate (- (/ w 2)) (- (/ h 2)))))))

(defn rotate-counterclockwise [^BufferedImage img]
(let [h (height img) w (width img)]
(apply-transform
img
(new-img h w)
(doto (AffineTransform.)
(.translate (/ h 2) (/ w 2))
(.rotate (* 3/2 Math/PI))
(.translate (- (/ w 2)) (- (/ h 2)))))))

(defn image-png-bytes [^BufferedImage img]
(let [baos (ByteArrayOutputStream.)]
(ImageIO/write img "png" baos)
(.toByteArray baos)))
56 changes: 54 additions & 2 deletions src/co/gaiwan/compass/model/assets.clj
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,14 @@
[clojure.java.io :as io]
[clojure.string :as str]
[co.gaiwan.compass.config :as config]
[co.gaiwan.compass.images :as images]
[hato.client :as hato])
(:import
(java.security MessageDigest)
(java.util Formatter)))
(java.util Formatter)
(javax.imageio ImageIO)))

(set! *warn-on-reflection* true)

(defn file-extension [mime]
(let [[mime] (str/split mime #"\s*;\s*")]
Expand All @@ -16,6 +20,14 @@
"image/webp" "webp"
"image/svg+xml" "svg"} mime)))

(defn ext->mime [path]
(let [ext (str/lower-case (last (str/split (str path) #"\.")))]
({"png" "image/png"
"jpeg" "image/jpeg"
"gif" "image/gif"
"webp" "image/webp"
"svg" "image/svg+xml"} ext)))

(defn sha256 [bytes]
(.digest (MessageDigest/getInstance "SHA-256") bytes))

Expand Down Expand Up @@ -49,7 +61,9 @@
(bytes? source)
source
(instance? java.io.File source)
(java.nio.file.Files/readAllBytes (.toPath source))
(java.nio.file.Files/readAllBytes (.toPath ^java.io.File source))
(instance? java.awt.image.BufferedImage source)
(images/image-png-bytes source)
:else
(throw (ex-info {:source (class source)} "Unsupported data source")))
filename (str (sha256-hex bytes) "." ext)
Expand All @@ -64,7 +78,45 @@
(let [{:keys [^bytes body headers]} (hato/get url {:as :byte-array})]
(add-to-content-addressed-storage (get headers "content-type") body)))

(defn jpeg-orientation [f]
;; 1: Normal (0° rotation)
;; 3: Upside-down (180° rotation)
;; 6: Rotated 90° counterclockwise (270° clockwise)
;; 8: Rotated 90° clockwise (270° counterclockwise)
(case
(.getInt
(.getFirstDirectoryOfType
(com.drew.imaging.ImageMetadataReader/readMetadata (io/file f))
com.drew.metadata.exif.ExifIFD0Directory)
com.drew.metadata.exif.ExifIFD0Directory/TAG_ORIENTATION)
3 :upside-down
6 :counterclockwise
8 :clockwise))

(defn resize-image
([path max-width max-height]
(resize-image (ext->mime path) path max-width max-height))
([mime-type path max-width max-height]
(let [source (images/read-img path)
width (images/width source)
height (images/height source)
aspect (/ width height)
scale (min (/ max-width width)
(/ max-height height))
rotation (when (= "image/jpeg" mime-type)
(jpeg-orientation (io/file path)))
img (images/scale-img source scale)]
(case rotation
nil img
:upside-down (images/rotate-180 img)
:counterclockwise (images/rotate-clockwise img)
:clockwise (images/rotate-counterclockwise img)))))

(comment
(def path "/home/arne/Downloads/IMG_20240914_212508.jpg")
(images/write-png "/tmp/resized.png"
(resize-image path 500 500))
(add-to-content-addressed-storage "image/png" (resize-image path 500 500))
(def r (hato/get "https://cdn.discordapp.com/avatars/758588684177768469/8b32119c1ae262544e2952ea60aaf9a7.png" {:as :byte-array}))

(download-image "https://cdn.discordapp.com/avatars/758588684177768469/8b32119c1ae262544e2952ea60aaf9a7.png"))
3 changes: 2 additions & 1 deletion src/co/gaiwan/compass/model/session.clj
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,8 @@
(merge default-filters filters)))

(defn session-image-css-value [session]
(str "url(" (assets/image-url (:session/image session)) ")"))
(str "url(" (assets/image-url (or (:session/thumbnail session)
(:session/image session))) ")"))

(defn subtitle
"Returns the subtitle if there is one, or 'organized by <person>' otherwise."
Expand Down
4 changes: 4 additions & 0 deletions src/co/gaiwan/compass/repl.clj
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@
(db/db)
name-or-email)))

(:db/id
(user "Arne"))

{:user/uuid #uuid "ee944d53-0c49-486c-9b4e-a178491673ba", :public-profile/name "Arne", :public-profile/avatar-url "66c1ebd5cfd87056a7fd591c773efe4cfe022304554e3f49988fb7a240010c19.png", :discord/id "758588684177768469", :discord/access-token "2cYNs1YwseOCwjmlkid2r7QiiVIl01", :discord/expires-at #time/zdt "2024-09-21T09:37:58.756+02:00[Europe/Brussels]", :discord/refresh-token "YauvlnohxgWk7XREbfRD02cXsV61xj", :discord/email "arne.brasseur@gmail.com"}
(defn sessions []
(map db/entity (db/q '[:find [?e ...]
:where
Expand Down
6 changes: 3 additions & 3 deletions src/co/gaiwan/compass/routes/profiles.clj
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@
(defn GET-profile [{:keys [params] :as req}]
{:html/body
[h/profile-detail
(if-let [profile-id (:profile-id params)]
(db/entity profile-id)
(if-let [user-uuid (:user-uuid params)]
(db/entity [:user/uuid user-uuid])
(:identity req))]})

(defn GET-profile-form [req]
Expand Down Expand Up @@ -191,7 +191,7 @@
["/uploads/:filename"
{:middleware [[response/wrap-requires-auth]]
:get {:handler file-handler}}]
["/user/:profile-id"
["/user/:user-uuid"
{:name :profile/show
:middleware [[response/wrap-requires-auth]]
:get {:handler GET-profile}}]
Expand Down
3 changes: 2 additions & 1 deletion src/co/gaiwan/compass/routes/sessions.clj
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,8 @@
(= published? "on")
(assoc :session/published? true)
image
(assoc :session/image (assets/add-to-content-addressed-storage (:content-type image) (:tempfile image))))))
(assoc :session/image (assets/add-to-content-addressed-storage (:content-type image) (:tempfile image))
:session/thumbnail (assets/add-to-content-addressed-storage "image/png" (assets/resize-image (:content-type image) (:tempfile image) 500 500))))))

(defn POST-create-session
"Create new session, save to Datomic
Expand Down

0 comments on commit 9286d4e

Please sign in to comment.