All Downloads are FREE. Search and download functionalities are using the official Maven repository.
Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
com.github.jlangch.venice.images.venice Maven / Gradle / Ivy
;;;; __ __ _
;;;; \ \ / /__ _ __ (_) ___ ___
;;;; \ \/ / _ \ '_ \| |/ __/ _ \
;;;; \ / __/ | | | | (_| __/
;;;; \/ \___|_| |_|_|\___\___|
;;;;
;;;;
;;;; Copyright 2017-2024 Venice
;;;;
;;;; Licensed under the Apache License, Version 2.0 (the "License");
;;;; you may not use this file except in compliance with the License.
;;;; You may obtain a copy of the License at
;;;;
;;;; http://www.apache.org/licenses/LICENSE-2.0
;;;;
;;;; Unless required by applicable law or agreed to in writing, software
;;;; distributed under the License is distributed on an "AS IS" BASIS,
;;;; WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
;;;; See the License for the specific language governing permissions and
;;;; limitations under the License.
;;;; Image functions
;;;; Thanks to Riyad Kalla and his 'imgscalr' Java project (Apache-2.0 license).
;;;; https://github.com/rkalla/imgscalr/
;;;;
;;;; This is a Venice rewrite of the 'imgscalr'.
(ns images)
(import :java.awt.Color
:java.awt.Graphics
:java.awt.Graphics2D
:java.awt.Image
:java.awt.RenderingHints
:java.awt.Transparency
:java.awt.color.ColorSpace
:java.awt.geom.AffineTransform
:java.awt.geom.Rectangle2D
:java.awt.image.AreaAveragingScaleFilter
:java.awt.image.BufferedImage
:java.awt.image.BufferedImageOp
:java.awt.image.ColorConvertOp
:java.awt.image.ColorModel
:java.awt.image.ConvolveOp
:java.awt.image.ImagingOpException
:java.awt.image.IndexColorModel
:java.awt.image.Kernel
:java.awt.image.RasterFormatException
:java.awt.image.RescaleOp)
(import :javax.imageio.ImageIO
:javax.imageio.stream.FileImageInputStream
:javax.imageio.spi.IIORegistry
:javax.imageio.spi.ImageWriterSpi)
;; -----------------------------------------------------------------------------
;; Constants
;; -----------------------------------------------------------------------------
(defonce TYPE_INT_RGB (. :BufferedImage :TYPE_INT_RGB))
(defonce TYPE_INT_ARGB (. :BufferedImage :TYPE_INT_ARGB))
(defonce resize-modes #{ :resize-auto
:resize-performance
:resize-balanced
:resize-quality
:resize-high-quality })
(defonce fit-modes #{ :fit-best
:fit-exact
:fit-width
:fit-height })
;; -----------------------------------------------------------------------------
;; Imaging operators
;; -----------------------------------------------------------------------------
(def convolve-op-antialias (. :ConvolveOp
:new
(. :Kernel :new 3 3 [ 0.00F, 0.08F, 0.00F,
0.08F, 0.68F, 0.08F,
0.00F, 0.08F, 0.00F ])
(. :ConvolveOp :EDGE_NO_OP)
nil))
(def rescale-op-darker (. :RescaleOp :new 0.9F 0 nil))
(def rescale-op-brighter (. :RescaleOp :new 1.1F 0 nil))
(def color-convert-op-grayscale (as-> (. :ColorSpace :CS_GRAY) cs
(. :ColorSpace :getInstance cs)
(. :ColorConvertOp :new cs nil)))
;; -----------------------------------------------------------------------------
;; Load / Save
;; -----------------------------------------------------------------------------
(defn
^{ :arglists '(
"(load file)")
:doc """
Loads an image from a `:java.io.File`, a `:java.io.InputStream`, or a
`:java.net.URL`.
"""
:examples '(
"""
(do
(load-module :images)
(images/load (io/file "/Users/foo/Desktop/Pink-Forest.jpg")))
""")
:see-also '(
"images/save" ) }
load [f]
(assert (or (io/file? f)
(instance-of? :java.io.InputStream f)
(instance-of? :java.net.URL f)))
(try
(. :ImageIO :read f)
(catch :Exception e
(throw (ex :VncException "Faled to load image" e)))))
(defn
^{ :arglists '(
"(save img format-name f)")
:doc """
Saves an image to 'java.io.File' or an ':java.io.OutputStream'.
Supported formats: "png", "jpg", "jpeg", "gif", "bmp", ...
"""
:examples '(
"""
(do
(load-module :images)
(-> (images/load (io/file "/Users/foo/Desktop/Pink-Forest.jpg"))
(images/save img :png (io/file "/Users/foo/Desktop/Pink-Forest.png"))))
""")
:see-also '(
"images/load"
"images/format-names" ) }
save [img format-name f]
(assert (instance-of? :Image img))
(assert (or (string? format-name) (keyword? format-name)))
(assert (or (io/file? f) (instance-of? :java.io.OutputStream f)))
(when-not (try
(. :ImageIO :write img (name format-name) f)
(catch :Exception e
(throw (ex :VncException "Failed to write image" e))))
(throw (ex :VncException
"""
Failed to write image. No appropriate writer found for format \
'~{format-name}'!
"""))))
;; -----------------------------------------------------------------------------
;; Image properties
;; -----------------------------------------------------------------------------
(defn
^{ :arglists '(
"(dimension f)")
:doc """
Returns the images dimensions as a vector [width height]. 'f' may
be a `:java.io.File` or a `:java.awt.Image`.
Note: Do not load a file first to get the dimension, passing a
`:java.io.File` is much faster!
"""
:examples '(
"""
(do
(load-module :images)
(images/dimension (io/file "/Users/foo/Desktop/Pink-Forest.jpg")))
""" ) }
dimension [f]
(assert (or (io/file? f) (instance-of? :Image f)))
(if (instance-of? :Image f)
[(long (. f :getWidth)) (long (. f :getHeight))] ;; return dimensions
(let [suffix (io/file-ext f)
iterators (. :ImageIO :getImageReadersBySuffix suffix)]
(let [dim (loop [readers (java-iterator-to-list iterators)]
(if-let [reader (first readers)]
(if-let [d (read-dimension reader f)]
d ;; return dimensions
(recur (rest readers)))))] ;; try with next reader
(if dim
dim
(throw (ex :VncException
(str "Cannot get image dimensions for unknown image "
f))))))))
(defn- read-dimension [img-reader file]
(try-with [is (. :FileImageInputStream :new file)]
(. img-reader :setInput is)
;; return dimensions
[(long (. img-reader :getWidth (. img-reader :getMinIndex)))
(long (. img-reader :getHeight (. img-reader :getMinIndex)))]
(catch :Exception e nil) ;; this reader does not match the image type
(finally (. img-reader :dispose))))
;; -----------------------------------------------------------------------------
;; Rotate / Flip
;; -----------------------------------------------------------------------------
(defn
^{ :arglists '(
"(rotate img angle)")
:doc """
Rotates an image by 0°, 90°, 180°, or 270° clockwise.
Returns a new image.
"""
:examples '(
"""
(do
(load-module :images)
(-> (images/load (io/file "/Users/foo/Desktop/Pink-Forest.jpg"))
(images/rotate 90)
(images/save "jpg" (io/file "/Users/foo/Desktop/Pink-Forest-1.jpg"))))
""" ) }
rotate [img angle]
(assert (instance-of? :Image img))
(assert (and (long? angle) (contains? #{0, 90, 180, 270} angle)))
(let [[width height] (dimension img)
tx (. :AffineTransform :new)]
(case angle
90 (let [new-width height
new-height width]
(. tx :translate new-width 0)
(. tx :quadrantRotate 1)
(transform img tx new-width new-height))
180 (let [new-width width
new-height height]
(. tx :translate new-width new-height)
(. tx :quadrantRotate 2)
(transform img tx new-width new-height))
270 (let [new-width height
new-height width]
(. tx :translate 0 new-height)
(. tx :quadrantRotate 3)
(transform img tx new-width new-height))
img)))
(defn
^{ :arglists '(
"(flip img mode)")
:doc """
Flips an image vertically or horizontally. Returns a new image.
Mode is either :vertical or :horizontal.
"""
:examples '(
"""
(do
(load-module :images)
(-> (images/load (io/file "/Users/foo/Desktop/Pink-Forest.jpg"))
(images/flip :vertical)
(images/save "jpg" (io/file "/Users/foo/Desktop/Pink-Forest-1.jpg"))))
""" ) }
flip [img mode]
(assert (instance-of? :Image img))
(assert (and (keyword? mode) (contains? #{:vertical :horizontal} mode)))
(let [[width height] (dimension img)
tx (. :AffineTransform :new)]
(case mode
:horizontal (do (. tx :translate width 0)
(. tx :scale -1.0 1.0)
(transform img tx width height))
:vertical (do (. tx :translate 0 height)
(. tx :scale 1.0 -1.0)
(transform img tx width height))
img)))
(defn- transform [img tx width height]
(assert (instance-of? :Image img))
(assert (and (long? width) (pos? width)))
(assert (and (long? height) (pos? height)))
(let [result (create-derived-image img width height)
g2d (. result :createGraphics)]
(. g2d :drawImage img tx nil)
(. g2d :dispose)
result))
;; -----------------------------------------------------------------------------
;; Crop / Pad / Resize
;; -----------------------------------------------------------------------------
(defn
^{ :arglists '(
"(crop img x y width height)")
:doc """
Crops an image. Returns a new image.
"""
:examples '(
"""
(do
(load-module :images)
(-> (images/load (io/file "/Users/foo/Desktop/Pink-Forest.jpg"))
(images/crop 1000 1000 400 200)
(images/save "jpg" (io/file "/Users/foo/Desktop/Pink-Forest-1.jpg"))))
""" ) }
crop [img x y width height]
(assert (instance-of? :Image img))
(assert (and (long? x) (long? y) (long? width) (long? height)))
(assert (and (>= x 0) (>= y 0) (>= width 0) (>= height 0)))
(let [[img-width img-height] (dimension img)]
(when (> (+ x width) img-width)
(throw (ex :VncException
"Invalid crop bounds: x[~{x}] + width[~{width}] < img-width[~{img-width}]")))
(when (> (+ y height) img-height)
(throw (ex :VncException
"Invalid crop bounds: y[~{y}]+ height[~{height}] < img-height[~{img-height}]")))
(let [dest (create-derived-image img width height)
g (. dest :getGraphics)]
(. g :drawImage img
0 0 width height
x y (+ x width) (+ y height)
nil)
(. g :dispose)
dest)))
(defn
^{ :arglists '(
"(pad img padding color)")
:doc """
Pads an image. Returns a new image.
"""
:examples '(
"""
(do
(load-module :images)
(import :java.awt.Color)
(-> (images/load (io/file "/Users/foo/Desktop/Pink-Forest.jpg"))
(images/pad 200 (. :Color :WHITE))
(images/save "jpg" (io/file "/Users/foo/Desktop/Pink-Forest-1.jpg"))))
""" ) }
pad [img padding color]
(assert (instance-of? :Image img))
(assert (and (long? padding) (>= padding 0)))
(assert (instance-of? :Color color))
(let [[img-width img-height] (dimension img)
img-alpha? (not (opaque? img))
width (+ img-width (* padding 2))
height (+ img-height (* padding 2))
type (if (or (color-alpha? color) img-alpha?) TYPE_INT_ARGB TYPE_INT_RGB)
dest (. :BufferedImage :new width height type)
g (. dest :getGraphics)]
;; draw the padding border
(. g :setColor color)
(. g :fillRect 0 0 width padding)
(. g :fillRect 0 padding padding height)
(. g :fillRect padding (- height padding) width height)
(. g :fillRect (- width padding) padding width (- height padding))
; draw the centered image
(. g :drawImage img padding padding nil)
(. g :dispose)
dest))
(defn
^{ :arglists '(
"(resize-fit img size fit-mode)"
"(resize img size fit-mode resize-mode)" )
:doc """
Resizes an image to a new size, a square of width and height the
image should fit within the size.
Resize modes:
| [![width: 15%]] | [![width: 85%]] |
| `:fit-best` | (default), fit within a square box of size 'size', \
keeps width / height ratio |
| `:fit-exact` | fit exactly to a square of size 'size', \
causes distortions |
| `:fit-width` | fit to width, keeps width / height ratio |
| `:fit-height` | fit to height, keeps width / height ratio |
Resize modes:
| [![width: 15%]] | [![width: 85%]] |
| `:resize-auto` | (default) chooses best mode |
| `:resize-performance` | resize for best performance |
| `:resize-balanced` | balance between performance and quality |
| `:resize-quality` | resize for quality |
| `:resize-high-quality` | resize for high quality |
Returns a new image.
"""
:examples '(
"""
(do
(load-module :images)
(let [src (io/file "/Users/foo/Desktop/Pink-Forest.jpg")
dst (io/file "/Users/foo/Desktop/Pink-Forest-1.jpg")]
(-> (images/load src)
(images/resize-fit 500 :fit-best :resize-balanced)
(images/save "jpg" dst))
(println (io/file-name src) ":" (images/dimension src))
(println (io/file-name dst) ":" (images/dimension dst))))
""" )
:see-also '(
"images/resize" ) }
resize-fit
([img size fit-mode]
(resize-fit img size fit-mode :resize-auto))
([img size fit-mode resize-mode]
(assert (instance-of? :Image img))
(assert (and (long? size) (> size 0)))
(assert (and (keyword? fit-mode) (contains? fit-modes fit-mode)))
(assert (and (keyword? resize-mode) (contains? resize-modes resize-mode)))
(let [[img-width img-height] (dimension img)]
(case fit-mode
:fit-exact
(resize img size size resize-mode)
:fit-width
(let [factor (/ (double size) (double img-width))]
(resize img size (scale img-height factor) resize-mode))
:fit-height
(let [factor (/ (double size) (double img-height))]
(resize img (scale img-width factor) size resize-mode))
:fit-best
(let [factor-w (/ (double size) (double img-width))
factor-h (/ (double size) (double img-height))
factor (cond
(and (>= factor-w 1.0) (>= factor-h 1.0))
(min factor-w factor-h) ;; upscale
(and (>= factor-w 1.0) (< factor-h 1.0))
factor-h ;; downscale
(and (< factor-w 1.0) (>= factor-h 1.0))
factor-w ;; downscale
(and (< factor-w 1.0) (< factor-h 1.0))
(max factor-w factor-h))] ;; downscale
:else 1.0
(resize img
(scale img-width factor)
(scale img-height factor)
resize-mode))
;; else
(throw (ex :VncException
"Internal error: unhandled fit-mode '~{fit-mode}'!"))))))
(defn
^{ :arglists '(
"(resize img width height)"
"(resize img width height resize-mode)" )
:doc """
Resizes an image to a new width and height.
Resize modes:
| [![width: 15%]] | [![width: 85%]]. |
| `:resize-auto` | (default) chooses best mode |
| `:resize-performance` | resize for best performance |
| `:resize-balanced` | balance between performance and quality |
| `:resize-quality` | resize for quality |
| `:resize-high-quality` | resize for high quality |
Returns a new image.
"""
:examples '(
"""
(do
(load-module :images)
(let [src (io/file "/Users/foo/Desktop/Pink-Forest.jpg")
dst (io/file "/Users/foo/Desktop/Pink-Forest-1.jpg")]
(-> (images/load src)
(images/resize 500 200 :resize-balanced)
(images/save "jpg" dst))
(println (io/file-name src) ":" (images/dimension src))
(println (io/file-name dst) ":" (images/dimension dst))))
""" )
:see-also '(
"images/resize-fit" ) }
resize
([img width height]
(resize img width height :auto))
([img width height resize-mode]
(assert (instance-of? :Image img))
(assert (and (long? width) (> width 0)))
(assert (and (long? height) (> height 0)))
(assert (and (keyword? resize-mode) (contains? resize-modes resize-mode)))
(let [[img-width img-height] (dimension img)
img-ratio (/ (double img-height) (double img-width))
img-landscape? (<= img-ratio 1.0)
resize-mode (if (= resize-mode :resize-auto)
(scale-method width height img-landscape?)
resize-mode)]
(if (and (== img-width width) (== img-height height))
img ;; no change in size return original image
(do
;; resize the image
(case resize-mode
:resize-performance
(scale-image img width height
(. :RenderingHints :VALUE_INTERPOLATION_NEAREST_NEIGHBOR))
:resize-balanced
(scale-image img width height
(. :RenderingHints :VALUE_INTERPOLATION_BILINEAR))
:resize-quality
(if (or (> width img-width) (> height img-height))
;; scale up
(scale-image img width height
(. :RenderingHints :VALUE_INTERPOLATION_BICUBIC))
;; scale down
(scale-image img width height
(. :RenderingHints :VALUE_INTERPOLATION_BICUBIC)))
:resize-high-quality
(if (or (> width img-width) (> height img-height))
;; scale up
(scale-image img width height
(. :RenderingHints :VALUE_INTERPOLATION_BICUBIC))
;; scale down
(scale-image img width height
(. :RenderingHints :VALUE_INTERPOLATION_BICUBIC)))
;; else
(throw (ex :VncException
"Internal error: unhandled resize-mode '~{resize-mode}'!"))))))))
(defn- scale-image [img width height interpolation-hint]
(let [dest (create-derived-image img width height)
g2d (cast :java.awt.Graphics2D (. dest :getGraphics))
key (. :RenderingHints :KEY_INTERPOLATION) ]
(. g2d :setRenderingHint key interpolation-hint)
(. g2d :drawImage img 0 0 width height nil)
(. g2d :dispose)
dest))
(defn- scale-method [width height img-landscape?]
(let [length (if img-landscape? width height)]
(cond
(<= length 800) :quality
(<= length 1600) :balanced
:else :performance)))
(defn scale [size factor]
(long (* (double size) (double factor))))
;; -----------------------------------------------------------------------------
;; Apply image OPs
;; -----------------------------------------------------------------------------
(defn
^{ :arglists '(
"(apply-ops img ops)")
:doc """
Applies one or multiple image operators (:java.awt.image.BufferedImageOp)
to the image.
Returns a new image.
Examples for image operators:
```
(import :java.awt.color.ColorSpace
:java.awt.image.ColorConvertOp
:java.awt.image.ConvolveOp
:java.awt.image.Kernel
:java.awt.image.RescaleOp)
(def convolve-op-antialias (. :ConvolveOp
:new
(. :Kernel :new 3 3
[ 0.00F, 0.08F, 0.00F,
0.08F, 0.68F, 0.08F,
0.00F, 0.08F, 0.00F ])
(. :ConvolveOp :EDGE_NO_OP)
nil))
(def rescale-op-darker (. :RescaleOp :new 0.9F 0 nil))
(def rescale-op-brighter (. :RescaleOp :new 1.1F 0 nil))
(def color-convert-op-grayscale (as-> (. :ColorSpace :CS_GRAY) cs
(. :ColorSpace :getInstance cs)
(. :ColorConvertOp :new cs nil)))
```
"""
:examples '(
"""
;; make the image brighter
(do
(load-module :images)
(import :java.awt.image.RescaleOp)
(let [op-brighter (. :RescaleOp :new 1.3F 0 nil)]
(-> (images/load (io/file "/Users/foo/Desktop/Pink-Forest.jpg"))
(images/apply-ops [op-brighter])
(images/save "jpg" (io/file "/Users/foo/Desktop/Pink-Forest-1.jpg")))))
""",
"""
;; convert the image to grayscale
(do
(load-module :images)
(import :java.awt.color.ColorSpace)
(import :java.awt.image.ColorConvertOp)
(let [op-grayscale (as-> (. :ColorSpace :CS_GRAY) cs
(. :ColorSpace :getInstance cs)
(. :ColorConvertOp :new cs nil))]
(-> (images/load (io/file "/Users/foo/Desktop/Pink-Forest.jpg"))
(images/apply-ops [op-grayscale])
(images/save "jpg" (io/file "/Users/foo/Desktop/Pink-Forest-1.jpg")))))
""",
) }
apply-ops [img ops]
(assert (instance-of? :Image img))
(assert (sequential? ops))
(assert (not-empty? ops))
(assert (every? #(instance-of? :BufferedImageOp %) ops))
(let [src (if (rgb-or-argb-image? img) img (copy-to-derived-image img))]
(loop [src src
ops (filter some? ops)
reassigned false]
(let [op (first ops)
result-bounds (. op :getBounds2D src)]
(when (nil? result-bounds)
(throw (ex :VncException (str "BufferedImageOp[" op "] get bounds returned nil."))))
(let [w (long (. result-bounds :getWidth))
h (long (. result-bounds :getHeight))
dest (create-derived-image src w h)
result (. op :filter src dest)]
(when reassigned (. src :flush))
(if (<= (count ops) 1)
result
(recur result (rest ops) true)))))))
;; -----------------------------------------------------------------------------
;; Utils
;; -----------------------------------------------------------------------------
(defn
^{ :arglists '(
"(format-names)")
:doc """
Returns a list of format that the image writer supports.
"""
:examples '(
"""
(do
(load-module :images)
(images/format-names))
""" ) }
format-names []
(. :ImageIO :getWriterFormatNames))
;; -----------------------------------------------------------------------------
;; Internals
;; -----------------------------------------------------------------------------
(defn- create-derived-image
([src]
(create-derived-image (long (. img :getWidth)) (long (. img :getHeight))))
([src width height]
(assert (instance-of? :Image src))
(assert (and (long? width) (pos? width)))
(assert (and (long? height) (pos? height)))
(let [type (if (opaque? src) TYPE_INT_RGB TYPE_INT_ARGB)]
(. :BufferedImage :new width height type))))
(defn- copy-to-derived-image [img]
(let [width (long (. img :getWidth))
height (long (. img :getHeight))
type (if (opaque? img) TYPE_INT_RGB TYPE_INT_ARGB)
dest (. :BufferedImage :new width height type)
gd (. dest :getGraphics)]
(. gd :drawImage img 0 0 nil)
(. gd :dispose)
dest))
(defn- rgb-or-argb-image? [img]
(contains? #{TYPE_INT_RGB, TYPE_INT_ARGB} (. img :getType)))
(defn- opaque? [img]
(== (. img :getTransparency) (. :Transparency :OPAQUE)))
(defn- color-alpha? [color]
(not= (long (. color :getAlpha)) 255))