imagemagick-0.0.1: examples/landscape_3d.hs
{-# LANGUAGE OverloadedStrings #-}
-- | Example taken from: http://members.shaw.ca/el.supremo/MagickWand/landscape_3d.htm
-- This is derived from a PHP script at:
-- http://eclecticdjs.com/mike/tutorials/php/imagemagick/imagickpixeliterator/3D_landscape.php
import Control.Applicative ((<$>))
import Control.Monad
import Control.Monad.IO.Class (liftIO)
import Data.IORef
import Graphics.ImageMagick.MagickWand
main :: IO ()
main = withMagickWandGenesis $ do
-- input image
-- The input image is at: "http://eclecticdjs.com/mike/temp/ball/fract6.jpg"
let url = "fract6.jpg";
-- output image
let file = "3d_fractal.jpg";
(_,image) <- magickWand
pw <- pixelWand
readImage image url
-- scale it down
-- w <- getImageWidth image
-- h <- getImageHeight image
pw `setColor` "transparent"
shearImage image pw 45 0
-- MessageBox(NULL,"B - Shear failed","",MB_OK);
w0 <- getImageWidth image
h0 <- getImageHeight image
-- scale it to make it look like it is laying down
scaleImage image w0 (h0 `div` 2)
-- MessageBox(NULL,"C - Scale failed","",MB_OK);
-- Get image stats
w <- getImageWidth image
h <- getImageHeight image
-- Make a blank canvas to draw on
(_,canvas) <- magickWand
-- Use a colour from the input image
getImagePixelColor image 0 0 pw
newImage canvas w (h*2) pw
let offset = h
-- The original drawing method was to go along each row from top to bottom so that
-- a line in the "front" (which is one lower down the picture) will be drawn over
-- one behind it.
-- The problem with this method is that every line is drawn even if it will be covered
-- up by a line "in front" of it later on.
-- The method used here goes up each column from left to right and only draws a line if
-- it is longer than everything drawn so far in this column and will therefore be visible.
-- With the new drawing method this takes 13 secs - the previous method took 59 secs
-- loop through all points in image
forM_ [0..(w-1)] $ \x -> localGenesis $ do
-- The PHP version created, used and destroyed the drawingwand inside
-- the inner loop but it is about 25% faster to do only the DrawLine
-- inside the loop
(_,line) <- drawingWand
line_height <- liftIO $ newIORef 0 -- let line_height = 0
forM_ [(h-1),(h-2)..0] $ \y -> do
-- get (r,g,b) and grey value
getImagePixelColor image x y pw
-- 255* adjusts the rgb values to Q8 even if the IM being used is Q16
r <- round <$> (255*) <$> getRed pw
g <- round <$> (255*) <$> getGreen pw
b <- round <$> (255*) <$> getBlue pw
-- Calculate grayscale - a divisor of 10-25 seems to work well.
-- grey = (r+g+b)/25;
let grey = (r + g + b) `div` 15
-- grey = (r+g+b)/10;
-- Only draw a line if it will show "above" what's already been done
current <- liftIO $ readIORef line_height
when (current == 0 || current < grey) $ do
line `setFillColor` pw
line `setStrokeColor` pw
-- Draw the part of the line that is visible
let
start_y = y + offset - current
end_y = y - grey + offset
drawLine line (fromIntegral x) (fromIntegral start_y) (fromIntegral x) (fromIntegral end_y)
liftIO $ writeIORef line_height grey
liftIO $ modifyIORef line_height (\n->n-1)
-- Draw the lines on the image
drawImage canvas line
scaleImage canvas (w-h) (h*2)
-- write canvas
writeImage canvas (Just file)