mirror of
https://github.com/penpot/penpot.git
synced 2025-06-28 17:07:02 +02:00
Refactor thumbnails generation.
- Start use temporal file instead of pipe because im4java seems does not work properly with it (it constantly generates `-` file on he *cwd*) - Move many impl of ratpack file types from old storage vendor package to catacumba code. - Add spec for thumbnails configuration.
This commit is contained in:
parent
618ce12fd8
commit
e6602ac68b
3 changed files with 90 additions and 68 deletions
|
@ -27,7 +27,8 @@
|
||||||
|
|
||||||
(def +thumbnail-options+ {:src :path
|
(def +thumbnail-options+ {:src :path
|
||||||
:dst :thumbnail
|
:dst :thumbnail
|
||||||
:size [300 110]
|
:width 300
|
||||||
|
:height 100
|
||||||
:quality 92
|
:quality 92
|
||||||
:format "webp"})
|
:format "webp"})
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
;;
|
;;
|
||||||
;; Copyright (c) 2016 Andrey Antukh <niwi@niwi.nz>
|
;; Copyright (c) 2016-2017 Andrey Antukh <niwi@niwi.nz>
|
||||||
|
|
||||||
(ns uxbox.images
|
(ns uxbox.images
|
||||||
"Image postprocessing."
|
"Image postprocessing."
|
||||||
|
@ -13,41 +13,81 @@
|
||||||
[datoteka.proto :as pt]
|
[datoteka.proto :as pt]
|
||||||
[uxbox.util.spec :as us]
|
[uxbox.util.spec :as us]
|
||||||
[uxbox.media :as media]
|
[uxbox.media :as media]
|
||||||
[uxbox.util.images :as images]
|
[uxbox.util.data :refer (dissoc-in)])
|
||||||
[uxbox.util.data :refer (dissoc-in)]))
|
(:import java.io.InputStream
|
||||||
|
java.io.ByteArrayInputStream
|
||||||
|
ratpack.form.UploadedFile
|
||||||
|
ratpack.http.TypedData
|
||||||
|
org.im4java.core.IMOperation
|
||||||
|
org.im4java.core.ConvertCmd))
|
||||||
|
|
||||||
;; FIXME: add spec for thumbnail config
|
;; --- Thumbnails Generation
|
||||||
|
|
||||||
|
(s/def ::width integer?)
|
||||||
|
(s/def ::height integer?)
|
||||||
|
(s/def ::quality #(< 0 % 101))
|
||||||
|
(s/def ::format #{"jpg" "webp"})
|
||||||
|
(s/def ::thumbnail-opts
|
||||||
|
(s/keys :opt-un [::format ::quality ::width ::height]))
|
||||||
|
|
||||||
|
;; Related info on how thumbnails generation
|
||||||
|
;; http://www.imagemagick.org/Usage/thumbnails/
|
||||||
|
|
||||||
|
(defn generate-thumbnail
|
||||||
|
([input] (generate-thumbnail input nil))
|
||||||
|
([input {:keys [size quality format width height]
|
||||||
|
:or {format "jpg"
|
||||||
|
quality 92
|
||||||
|
width 200
|
||||||
|
height 200}
|
||||||
|
:as opts}]
|
||||||
|
{:pre [(us/valid? ::thumbnail-opts opts)
|
||||||
|
(fs/path? input)]}
|
||||||
|
(let [tmp (fs/create-tempfile :suffix (str "." format))
|
||||||
|
opr (doto (IMOperation.)
|
||||||
|
(.addImage)
|
||||||
|
(.autoOrient)
|
||||||
|
(.resize (int width) (int height) "^")
|
||||||
|
(.quality (double quality))
|
||||||
|
(.addImage))]
|
||||||
|
(doto (ConvertCmd.)
|
||||||
|
(.run opr (into-array (map str [input tmp]))))
|
||||||
|
(let [thumbnail-data (fs/slurp-bytes tmp)]
|
||||||
|
(fs/delete tmp)
|
||||||
|
(ByteArrayInputStream. thumbnail-data)))))
|
||||||
|
|
||||||
(defn make-thumbnail
|
(defn make-thumbnail
|
||||||
[path {:keys [size format quality] :as cfg}]
|
[input {:keys [width height format quality] :as opts}]
|
||||||
(let [parent (fs/parent path)
|
{:pre [(us/valid? ::thumbnail-opts opts)
|
||||||
[filename ext] (fs/split-ext path)
|
(or (string? input)
|
||||||
|
(fs/path input))]}
|
||||||
suffix-parts [(nth size 0) (nth size 1) quality format]
|
(let [parent (fs/parent input)
|
||||||
final-name (apply str filename "-" (interpose "." suffix-parts))
|
[filename ext] (fs/split-ext input)
|
||||||
final-path (fs/path parent final-name)
|
|
||||||
|
|
||||||
|
suffix (->> [width height quality format]
|
||||||
|
(interpose ".")
|
||||||
|
(apply str))
|
||||||
|
thumbnail-path (fs/path parent (str filename "-" suffix))
|
||||||
images-storage media/images-storage
|
images-storage media/images-storage
|
||||||
thumbs-storage media/thumbnails-storage]
|
thumbs-storage media/thumbnails-storage]
|
||||||
(if @(st/exists? thumbs-storage final-path)
|
(if @(st/exists? thumbs-storage thumbnail-path)
|
||||||
(str (st/public-url thumbs-storage final-path))
|
(str (st/public-url thumbs-storage thumbnail-path))
|
||||||
(if @(st/exists? images-storage path)
|
(if @(st/exists? images-storage input)
|
||||||
(let [datapath @(st/lookup images-storage path)
|
(let [datapath @(st/lookup images-storage input)
|
||||||
content (images/thumbnail datapath cfg)
|
thumbnail (generate-thumbnail datapath opts)
|
||||||
path @(st/save thumbs-storage final-path content)]
|
path @(st/save thumbs-storage thumbnail-path thumbnail)]
|
||||||
(str (st/public-url thumbs-storage path)))
|
(str (st/public-url thumbs-storage path)))
|
||||||
nil))))
|
nil))))
|
||||||
|
|
||||||
(defn populate-thumbnail
|
(defn populate-thumbnail
|
||||||
[entry {:keys [src dst] :as cfg}]
|
[entry {:keys [src dst] :as opts}]
|
||||||
(assert (map? entry) "`entry` should be map")
|
{:pre [(map? entry)]}
|
||||||
|
|
||||||
(let [src (if (vector? src) src [src])
|
(let [src (if (vector? src) src [src])
|
||||||
dst (if (vector? dst) dst [dst])
|
dst (if (vector? dst) dst [dst])
|
||||||
src (get-in entry src)]
|
src (get-in entry src)]
|
||||||
(if (empty? src)
|
(if (empty? src)
|
||||||
entry
|
entry
|
||||||
(assoc-in entry dst (make-thumbnail src cfg)))))
|
(assoc-in entry dst (make-thumbnail src opts)))))
|
||||||
|
|
||||||
(defn populate-thumbnails
|
(defn populate-thumbnails
|
||||||
[entry & settings]
|
[entry & settings]
|
||||||
|
@ -55,8 +95,8 @@
|
||||||
|
|
||||||
(defn populate-urls
|
(defn populate-urls
|
||||||
[entry storage src dst]
|
[entry storage src dst]
|
||||||
(assert (map? entry) "`entry` should be map")
|
{:pre [(map? entry)
|
||||||
(assert (st/storage? storage) "`storage` should be a valid storage instance.")
|
(st/storage? storage)]}
|
||||||
(let [src (if (vector? src) src [src])
|
(let [src (if (vector? src) src [src])
|
||||||
dst (if (vector? dst) dst [dst])
|
dst (if (vector? dst) dst [dst])
|
||||||
value (get-in entry src)]
|
value (get-in entry src)]
|
||||||
|
@ -66,3 +106,28 @@
|
||||||
(-> entry
|
(-> entry
|
||||||
(dissoc-in src)
|
(dissoc-in src)
|
||||||
(assoc-in dst url))))))
|
(assoc-in dst url))))))
|
||||||
|
|
||||||
|
;; --- Impl
|
||||||
|
|
||||||
|
(extend-type UploadedFile
|
||||||
|
pt/IPath
|
||||||
|
(-path [this]
|
||||||
|
(pt/-path (.getFileName ^UploadedFile this))))
|
||||||
|
|
||||||
|
(extend-type TypedData
|
||||||
|
pt/IContent
|
||||||
|
(-input-stream [this]
|
||||||
|
(.getInputStream this))
|
||||||
|
|
||||||
|
io/IOFactory
|
||||||
|
(make-reader [td opts]
|
||||||
|
(let [^InputStream is (.getInputStream td)]
|
||||||
|
(io/make-reader is opts)))
|
||||||
|
(make-writer [path opts]
|
||||||
|
(throw (UnsupportedOperationException. "read only object")))
|
||||||
|
(make-input-stream [td opts]
|
||||||
|
(let [^InputStream is (.getInputStream td)]
|
||||||
|
(io/make-input-stream is opts)))
|
||||||
|
(make-output-stream [path opts]
|
||||||
|
(throw (UnsupportedOperationException. "read only object"))))
|
||||||
|
|
||||||
|
|
|
@ -1,44 +0,0 @@
|
||||||
;; This Source Code Form is subject to the terms of the Mozilla Public
|
|
||||||
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
||||||
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
||||||
;;
|
|
||||||
;; Copyright (c) 2016 Andrey Antukh <niwi@niwi.nz>
|
|
||||||
|
|
||||||
(ns uxbox.util.images
|
|
||||||
"Images transformation utils."
|
|
||||||
(:require [clojure.java.io :as io])
|
|
||||||
(:import org.im4java.core.IMOperation
|
|
||||||
org.im4java.core.ConvertCmd
|
|
||||||
org.im4java.process.Pipe
|
|
||||||
java.io.ByteArrayInputStream
|
|
||||||
java.io.ByteArrayOutputStream))
|
|
||||||
|
|
||||||
;; Related info on how thumbnails generation
|
|
||||||
;; http://www.imagemagick.org/Usage/thumbnails/
|
|
||||||
|
|
||||||
(defn thumbnail
|
|
||||||
([input] (thumbnail input nil))
|
|
||||||
([input {:keys [size quality format]
|
|
||||||
:or {format "jpg"
|
|
||||||
quality 92
|
|
||||||
size [200 200]}
|
|
||||||
:as opts}]
|
|
||||||
{:pre [(vector? size)]}
|
|
||||||
(with-open [out (ByteArrayOutputStream.)
|
|
||||||
in (io/input-stream input)]
|
|
||||||
(let [[width height] size
|
|
||||||
pipe (Pipe. in out)
|
|
||||||
op (doto (IMOperation.)
|
|
||||||
(.addRawArgs ^java.util.List ["-"])
|
|
||||||
(.autoOrient)
|
|
||||||
;; (.thumbnail (int width) (int height) "^")
|
|
||||||
;; (.gravity "center")
|
|
||||||
;; (.extent (int width) (int height))
|
|
||||||
(.resize (int width) (int height) "^")
|
|
||||||
(.quality (double quality))
|
|
||||||
(.addRawArgs ^java.util.List [(str format ":-")]))
|
|
||||||
cmd (doto (ConvertCmd.)
|
|
||||||
(.setInputProvider pipe)
|
|
||||||
(.setOutputConsumer pipe))]
|
|
||||||
(.run cmd op (make-array Object 0))
|
|
||||||
(ByteArrayInputStream. (.toByteArray out))))))
|
|
Loading…
Add table
Add a link
Reference in a new issue