From 8df5b8af6dd7b2c614f1fbd6f5a1eede4912e7be Mon Sep 17 00:00:00 2001 From: gered Date: Sun, 30 Mar 2014 09:46:58 -0400 Subject: [PATCH] attempts to refactor / speed up image -> ascii conversion. improve image route --- src/toascii/models/image.clj | 110 ++++++++++++++++++++----------- src/toascii/routes/api/image.clj | 13 +++- 2 files changed, 81 insertions(+), 42 deletions(-) diff --git a/src/toascii/models/image.clj b/src/toascii/models/image.clj index 74bbcc2..e9c4994 100644 --- a/src/toascii/models/image.clj +++ b/src/toascii/models/image.clj @@ -11,52 +11,82 @@ (:use hiccup.core)) (def ascii-chars [\# \A \@ \% \$ \+ \= \* \: \, \. \space]) +(def num-ascii-chars (count ascii-chars)) -(defmacro get-properties [obj & properties] - (let [target (gensym)] - `(let [~target ~obj] - (vector ~@(for [property properties] - `(~property ~target)))))) +(defn get-image-by-url + (^BufferedImage [^String url] + (try + (let [java-url (query-param-url->java-url url)] + (ImageIO/read java-url)) + (catch Exception ex)))) + +(defn get-image-by-file + (^BufferedImage [^File file] + (try + (ImageIO/read file) + (catch Exception ex)))) (defn scale-image "takes a source image specified by the uri (a filename or a URL) and scales it proportionally using the new width, returning the newly scaled image." - [url new-width] - (let [^Image image (ImageIO/read url) - new-height (* (/ new-width (.getWidth image)) - (.getHeight image)) - scaled-image (BufferedImage. new-width new-height BufferedImage/TYPE_INT_RGB) - gfx2d (doto (.createGraphics scaled-image) - (.setRenderingHint RenderingHints/KEY_INTERPOLATION - RenderingHints/VALUE_INTERPOLATION_BILINEAR) - (.drawImage image 0 0 new-width new-height nil) - (.dispose))] - scaled-image)) + (^BufferedImage [url new-width] + (let [^Image image (ImageIO/read url) + new-height (* (/ new-width (.getWidth image)) + (.getHeight image)) + scaled-image (BufferedImage. new-width new-height BufferedImage/TYPE_INT_RGB) + gfx2d (doto (.createGraphics scaled-image) + (.setRenderingHint RenderingHints/KEY_INTERPOLATION + RenderingHints/VALUE_INTERPOLATION_BILINEAR) + (.drawImage image 0 0 new-width new-height nil) + (.dispose))] + scaled-image))) -(defn ascii [^BufferedImage img x y color?] - (let [[red green blue] (get-properties (Color. (.getRGB img x y)) - .getRed .getGreen .getBlue) - peak (apply max [red green blue]) - idx (if (zero? peak) - (dec (count ascii-chars)) - (dec (int (+ 1/2 (* (count ascii-chars) (/ peak 255)))))) - output (nth ascii-chars (if (pos? idx) idx 0)) ] +(defn- get-css-color-attr [r g b] + (format "color: #%02x%02x%02x;" r g b)) + +(defn- get-pixel [^BufferedImage image x y] + (let [argb (.getRGB image x y)] + [(bit-shift-right (bit-and 0xff000000 argb) 24) + (bit-shift-right (bit-and 0x00ff0000 argb) 16) + (bit-shift-right (bit-and 0x0000ff00 argb) 8) + (bit-and 0x000000ff argb)])) + +(defn get-ascii-pixel + "" + [^BufferedImage image x y color?] + (let [[a r g b] (get-pixel image x y) + peak (apply max [r g b]) + char-index (if (zero? peak) + (dec num-ascii-chars) + (dec (int (+ 0.5 (* num-ascii-chars (/ peak 255)))))) + pixel-char (nth ascii-chars (if (pos? char-index) char-index 0))] (if color? - (html [:span {:style (format "color: rgb(%s,%s,%s);" red green blue)} output]) + [:span {:style (get-css-color-attr r g b)} pixel-char] + pixel-char))) + +(defn- pixels->ascii [^BufferedImage image color?] + (let [width (.getWidth image) + ascii-image (for [y (range (.getHeight image)) + x (range (.getWidth image))] + (get-ascii-pixel image x y color?)) + output (->> ascii-image + (partition width) + (map #(conj % (if color? [:br] \newline))) + (apply concat))] + (if color? + (html + [:pre + {:style "font-size:5pt; letter-spacing:1px; line-height:4pt; font-weight:bold;"} + output]) output))) -(defn convert-image [url w color?] - (let [java-url (query-param-url->java-url url) - ^Image raw-image (scale-image java-url w) - ascii-image (->> (for [y (range (.getHeight raw-image)) - x (range (.getWidth raw-image))] - (ascii raw-image x y color?)) - (partition w)) - output (->> ascii-image - (interpose (if color? "
" \newline)) - flatten)] - (if color? - (html [:pre {:style "font-size:5pt; letter-spacing:1px; - line-height:4pt; font-weight:bold;"} - output]) - (println output)))) \ No newline at end of file +(defn convert-image + ([^BufferedImage image color?] + (convert-image image nil color?)) + ([^BufferedImage image scale-width color?] + (let [current-width (.getWidth image) + new-width (or scale-width current-width) + final-image (if-not (= new-width current-width) + (scale-image image new-width) + image)] + (pixels->ascii final-image color?)))) \ No newline at end of file diff --git a/src/toascii/routes/api/image.clj b/src/toascii/routes/api/image.clj index 556b6df..0575a9d 100644 --- a/src/toascii/routes/api/image.clj +++ b/src/toascii/routes/api/image.clj @@ -1,9 +1,10 @@ (ns toascii.routes.api.image + (:import (java.awt.image BufferedImage)) (:require [clojure.string :as str] [liberator.core :refer [defresource]] [compojure.core :refer [ANY]] [toascii.route-utils :refer [register-routes]] - [toascii.models.image :refer [convert-image]])) + [toascii.models.image :refer [convert-image get-image-by-url]])) (defresource render-image [{:keys [url width color format] :as params}] :media-type-available? @@ -17,13 +18,21 @@ (fn [_] (cond (str/blank? url) {:error "Missing image url"})) + :exists? + (fn [_] + (if-let [image (get-image-by-url url)] + {:image image} + [false {:error "Image could not be loaded."}])) :handle-ok (fn [ctx] - (let [rendered (convert-image url 64 true)] + (let [rendered (convert-image (:image ctx) true)] (if (= "text/html" (get-in ctx [:representation :media-type])) rendered rendered))) :handle-malformed + (fn [ctx] + (:error ctx)) + :handle-not-found (fn [ctx] (:error ctx)))