{-# 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")