imagemagick-0.0.1: examples/affine.hs
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE OverloadedStrings #-}
-- | Example taken from: http://members.shaw.ca/el.supremo/MagickWand/affine.htm
{-
Originally inspired by:
http://www.imagemagick.org/discourse-server/viewtopic.php?f=2&t=12530
The idea for these specific examples came from reading this:
http://www.csl.mtu.edu/cs4611/www/HillLectureNotes/CS4611%202D%20Affine%20Transformation.htm
When reading that (and other web pages about affine) keep in mind
that IM's ordering of the affine matrix as described at:
http://imagemagick.org/script/command-line-options.php#affine
orders the affine values and their multiplication like this:
[x y 1] |sx rx 0|
|ry sy 0|
|tx ty 1|
Whereas the CS4611 web page uses this (which, if nothing else, is tidier):
|sx ry tx| |x|
|rx sy ty| |y|
|0 0 1 | |1|
My multiplication routine is written to conform to the way IM
specifies things.
ALSO, I think there are a couple of errors on the CS4611 page.
1. In the example of rotation about a point, it says that first
translate by V, then rotate, then translate by -V.
But the matrix representation of this does -V,rotate,V.
2. Reflection across the x-axis is not correct as shown.
When a point (x,y) is reflected across the x-axis its
new coordinate is (x,-y) - the matrix shown in the example
actually reflects across the y-axis - i.e. it produces (-x,y).
-}
import Data.Fixed (mod')
import Graphics.ImageMagick.MagickWand
-- typesafe angle logic could be imported form AC-Angle package
-- | Convert from degrees to radians.
radians :: Double -> Double
radians x = x / 180 * pi
-- | Convert from radians to degrees.
degrees :: Double -> Double
degrees x = x * 180 / pi
-- Set the affine array to translate by (x,y)
-- Set the affine array to scale the image by sx,sy
translate_affine :: (Floating x) => x -> x -> [x]
translate_affine x y =
[ 1, 0, 0,
1, x, y ]
-- Set the affine array to scale the image by sx,sy
scale_affine :: (Floating x) => x -> x -> [x]
scale_affine sx sy =
[ sx, 0, 0,
sy, 0, 0 ]
-- get the affine array to rotate image by 'degrees' clockwise
rotate_affine :: Double -> [Double]
rotate_affine angle =
[ cos (radians (angle `mod'` 360)), sin (radians (angle `mod'` 360)), -sin (radians (angle `mod'` 360)),
cos (radians (angle `mod'` 360)), 0, 0 ]
-- Multiply two affine arrays and return the result.
affine_multiply :: (Floating x) => [x] -> [x] -> [x]
affine_multiply [a0,a1,a2,a3,a4,a5] [b0,b1,b2,b3,b4,b5] =
[ a0*b0 + a1*b2, a0*b1 + a1*b3,
a2*b0 + a3*b2, a2*b1 + a3*b3,
a4*b0 + a5*b2 + b4, a4*b1 + a5*b3 + b5 ]
affine_multiply _ _ = error "incorrect list sizes"
main :: IO ()
main = withMagickWandGenesis $ do
-- Remember that these operations are done with respect to the
-- origin of the image which is the TOP LEFT CORNER.
localGenesis $ do
-- Example 1.
-- Rotate logo: by 90 degrees (about the origin), scale by 50 percent and
-- then move the image 240 in the x direction
-- TODO: fix problem with 'leaky' pixel
(_,mw) <- magickWand
readImage mw "logo:"
-- Set up the affine matrices
-- rotate 90 degrees clockwise
let
r = rotate_affine 90
-- scale by .5 in x and y
s = scale_affine 0.5 0.5
-- translate to (240,0)
t = translate_affine 240 0
-- now multiply them - note the order in
-- which they are specified - in particular beware that
-- temp = r*s is NOT necessarily the same as temp = s*r
--first do the rotation and scaling
-- temp = r*s
temp = r `affine_multiply` s
-- now the translation
-- result = temp*t;
result = temp `affine_multiply` t
-- and then apply the result to the image
distortImage mw affineProjectionDistortion result False
writeImage mw (Just "logo_affine_1.jpg")
localGenesis $ do
-- Example 2
-- Rotate logo: 30 degrees around the point (300,100)
-- Since rotation is done around the origin, we must translate
-- the point (300,100) up to the origin, do the rotation, and
-- then translate back again
(_,mw) <- magickWand
readImage mw "logo:"
let
-- Initialize the required affines
-- translate (300,100) to origin
t1 = translate_affine (-300) (-100)
-- rotate clockwise by 30 degrees
r = rotate_affine 30
-- translate back again
t2 = translate_affine 300 100
-- Now multiply the affine sequence
-- temp = t1*r
temp = t1 `affine_multiply` r
-- result = temp*t2;
result = temp `affine_multiply` t2
distortImage mw affineProjectionDistortion result False
writeImage mw (Just "logo_affine_2.jpg")
localGenesis $ do
-- Example 3
-- Reflect the image about a line passing through the origin.
-- If the line makes an angle of D degrees with the horizontal
-- then this can be done by rotating the image by -D degrees so
-- that the line is now (in effect) the x axis, reflect the image
-- across the x axis, and then rotate everything back again.
-- In this example, rather than just picking an arbitrary angle,
-- let's say that we want the "logo:" image to be reflected across
-- it's own major diagonal. Although we know the logo: image is
-- 640x480 let's also generalize the code slightly so that it
-- will still work if the name of the input image is changed.
-- If the image has a width "w" and height "h", then the angle between
-- the x-axis and the major diagonal is atan(h/w) (NOTE that this
-- result is in RADIANS!)
-- For this example I will also retain the original dimensions of the
-- image so that anything that is reflected outside the borders of the
-- input image is lost
(_,mw) <- magickWand
readImage mw "logo:"
w <- getImageWidth mw
h <- getImageHeight mw
let
-- Just convert the radians to degrees. This way I don't have
-- to write a function which sets up an affine rotation for an
-- argument specified in radians rather than degrees.
-- You can always change this.
angle_degrees = degrees(atan(realToFrac(h) / realToFrac(w)))
-- Initialize the required affines
-- Rotate diagonal to the x axis
r1 = rotate_affine (-angle_degrees)
-- Reflection affine (about x-axis)
-- In this case there isn't a specific function to set the
-- affine array (like there is for rotation and scaling)
-- so use the function which sets an arbitrary affine
reflect = [ 1, 0, 0,
-1, 0, 0 ]
-- rotate image back again
r2 = rotate_affine angle_degrees
-- temp = r1*reflect
temp = r1 `affine_multiply` reflect
-- result = temp*r2;
result = temp `affine_multiply` r2
distortImage mw affineProjectionDistortion result False
writeImage mw (Just "logo_affine_3.jpg")
localGenesis $ do
-- Example 4
-- Create a rotated gradient
-- See: http:--www.imagemagick.org/discourse-server/viewtopic.php?f=1&t=12707
-- The affine in this one is essentially the same as the one in Example 2 but
-- this example has a different use for the result
let
-- Dimensions of the final rectangle
w = 600 :: Int
h = 100 :: Int
-- angle of clockwise rotation
theta = 15 -- degrees
-- Convert theta to radians
rad_theta = radians theta
-- Compute the dimensions of the rectangular gradient
-- Don't let the rotation make the gradient rectangle any smaller
-- than the required output (using `max`)
gw = max w $ round (fromIntegral w * cos rad_theta + fromIntegral h * sin rad_theta + 0.5)
gh = max h $ round (fromIntegral w * sin rad_theta + fromIntegral h * cos rad_theta + 0.5)
(_,mw) <- magickWand
setSize mw gw gh
readImage mw "gradient:white-black"
let
-- Initialize the required affines
-- translate centre of gradient to origin
t1 = translate_affine (- fromIntegral gw / 2) (- fromIntegral gh / 2)
-- rotate clockwise by theta degrees
r = rotate_affine(theta)
-- translate back again
t2 = translate_affine (fromIntegral gw / 2) (fromIntegral gh / 2)
-- Now multiply the affine sequences
-- temp = t1*r
temp = t1 `affine_multiply` r
-- result = temp*t2;
result = temp `affine_multiply` t2
distortImage mw affineProjectionDistortion result False
-- Get the size of the distorted image and crop out the middle
nw <- getImageWidth mw
nh <- getImageHeight mw
cropImage mw w h ((nw - w) `div` 2) ((nh - h) `div` 2)
writeImage mw (Just "rotgrad_2.png")