diff --git a/backend/src/uxbox/frontend/images.clj b/backend/src/uxbox/frontend/images.clj index 26d573915..605fcda73 100644 --- a/backend/src/uxbox/frontend/images.clj +++ b/backend/src/uxbox/frontend/images.clj @@ -27,7 +27,8 @@ (def +thumbnail-options+ {:src :path :dst :thumbnail - :size [300 110] + :width 300 + :height 100 :quality 92 :format "webp"}) diff --git a/backend/src/uxbox/images.clj b/backend/src/uxbox/images.clj index f3686b7a4..7889d29d4 100644 --- a/backend/src/uxbox/images.clj +++ b/backend/src/uxbox/images.clj @@ -2,7 +2,7 @@ ;; 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 +;; Copyright (c) 2016-2017 Andrey Antukh (ns uxbox.images "Image postprocessing." @@ -13,41 +13,81 @@ [datoteka.proto :as pt] [uxbox.util.spec :as us] [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 - [path {:keys [size format quality] :as cfg}] - (let [parent (fs/parent path) - [filename ext] (fs/split-ext path) - - suffix-parts [(nth size 0) (nth size 1) quality format] - final-name (apply str filename "-" (interpose "." suffix-parts)) - final-path (fs/path parent final-name) + [input {:keys [width height format quality] :as opts}] + {:pre [(us/valid? ::thumbnail-opts opts) + (or (string? input) + (fs/path input))]} + (let [parent (fs/parent input) + [filename ext] (fs/split-ext input) + suffix (->> [width height quality format] + (interpose ".") + (apply str)) + thumbnail-path (fs/path parent (str filename "-" suffix)) images-storage media/images-storage thumbs-storage media/thumbnails-storage] - (if @(st/exists? thumbs-storage final-path) - (str (st/public-url thumbs-storage final-path)) - (if @(st/exists? images-storage path) - (let [datapath @(st/lookup images-storage path) - content (images/thumbnail datapath cfg) - path @(st/save thumbs-storage final-path content)] + (if @(st/exists? thumbs-storage thumbnail-path) + (str (st/public-url thumbs-storage thumbnail-path)) + (if @(st/exists? images-storage input) + (let [datapath @(st/lookup images-storage input) + thumbnail (generate-thumbnail datapath opts) + path @(st/save thumbs-storage thumbnail-path thumbnail)] (str (st/public-url thumbs-storage path))) nil)))) (defn populate-thumbnail - [entry {:keys [src dst] :as cfg}] - (assert (map? entry) "`entry` should be map") - + [entry {:keys [src dst] :as opts}] + {:pre [(map? entry)]} (let [src (if (vector? src) src [src]) dst (if (vector? dst) dst [dst]) src (get-in entry src)] (if (empty? src) entry - (assoc-in entry dst (make-thumbnail src cfg))))) + (assoc-in entry dst (make-thumbnail src opts))))) (defn populate-thumbnails [entry & settings] @@ -55,8 +95,8 @@ (defn populate-urls [entry storage src dst] - (assert (map? entry) "`entry` should be map") - (assert (st/storage? storage) "`storage` should be a valid storage instance.") + {:pre [(map? entry) + (st/storage? storage)]} (let [src (if (vector? src) src [src]) dst (if (vector? dst) dst [dst]) value (get-in entry src)] @@ -66,3 +106,28 @@ (-> entry (dissoc-in src) (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")))) + diff --git a/backend/src/uxbox/util/images.clj b/backend/src/uxbox/util/images.clj deleted file mode 100644 index dffc0a9be..000000000 --- a/backend/src/uxbox/util/images.clj +++ /dev/null @@ -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 - -(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))))))