JuicyPixels-extra 0.4.1 → 0.5.0
raw patch · 8 files changed
+295/−235 lines, 8 filesdep ~basePVP ok
version bump matches the API change (PVP)
Dependency ranges changed: base
API changes (from Hackage documentation)
Files
- CHANGELOG.md +8/−0
- Codec/Picture/Extra.hs +95/−91
- JuicyPixels-extra.cabal +71/−62
- LICENSE.md +1/−1
- README.md +10/−4
- bench/Main.hs +59/−39
- data-examples/lenna-scaled-up.png binary
- tests/Codec/Picture/ExtraSpec.hs +51/−38
CHANGELOG.md view
@@ -1,3 +1,11 @@+## Juicy Pixels Extra 0.5.0++* Changed how edge pixels are calculated when scaling via bilinear+ interpolation is used. [See PR+ 21](https://github.com/mrkkrp/JuicyPixels-extra/pull/21).++* Dropped support for GHC 8.2 and older.+ ## Juicy Pixels Extra 0.4.1 * Fixed left rotation so that `rotateLeft90 . rotateRight90` is now identity
Codec/Picture/Extra.hs view
@@ -1,138 +1,148 @@+{-# LANGUAGE CPP #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE RankNTypes #-}+{-# LANGUAGE RecordWildCards #-}+{-# LANGUAGE ScopedTypeVariables #-}+ -- | -- Module : Codec.Picture.Extra--- Copyright : © 2016–2019 Mark Karpov+-- Copyright : © 2016–present Mark Karpov -- License : BSD 3 clause -- -- Maintainer : Mark Karpov <markkarpov92@gmail.com> -- Stability : experimental -- Portability : portable ----- Utilities for image transformation with JuicyPixels.--{-# LANGUAGE CPP #-}-{-# LANGUAGE FlexibleContexts #-}-{-# LANGUAGE RankNTypes #-}-{-# LANGUAGE RecordWildCards #-}-{-# LANGUAGE ScopedTypeVariables #-}-+-- A collection of functions to scale, crop, flip images with JuicyPixels. module Codec.Picture.Extra ( -- * Scaling- scaleBilinear+ scaleBilinear,+ -- * Cropping- , crop+ crop,+ -- * Rotation- , flipHorizontally- , flipVertically- , rotateLeft90- , rotateRight90- , rotate180+ flipHorizontally,+ flipVertically,+ rotateLeft90,+ rotateRight90,+ rotate180,+ -- * Other- , beside- , below )+ beside,+ below,+ ) where import Codec.Picture+import qualified Codec.Picture.Types as M import Control.Monad.ST import Data.List (foldl1')-import qualified Codec.Picture.Types as M -- | Scale an image using bi-linear interpolation.--scaleBilinear- :: ( Pixel a- , Bounded (PixelBaseComponent a)- , Integral (PixelBaseComponent a)- )- => Int -- ^ Desired width- -> Int -- ^ Desired height- -> Image a -- ^ Original image- -> Image a -- ^ Scaled image+scaleBilinear ::+ ( Pixel a,+ Bounded (PixelBaseComponent a),+ Integral (PixelBaseComponent a)+ ) =>+ -- | Desired width+ Int ->+ -- | Desired height+ Int ->+ -- | Original image+ Image a ->+ -- | Scaled image+ Image a scaleBilinear width height img@Image {..} = runST $ do mimg <- M.newMutableImage width height let sx, sy :: Float- sx = fromIntegral imageWidth / fromIntegral width+ sx = fromIntegral imageWidth / fromIntegral width sy = fromIntegral imageHeight / fromIntegral height go x' y' | x' >= width = go 0 (y' + 1) | y' >= height = M.unsafeFreezeImage mimg | otherwise = do- let xf = fromIntegral x' * sx- yf = fromIntegral y' * sy- x, y :: Int- x = floor xf- y = floor yf- δx = xf - fromIntegral x- δy = yf - fromIntegral y- pixelAt' i j =- if i >= imageWidth || j >= imageHeight- then toBlack (pixelAt img 0 0)- else pixelAt img i j- writePixel mimg x' y' $- mulp (pixelAt' x y) ((1 - δx) * (1 - δy)) `addp`- mulp (pixelAt' (x + 1) y) (δx * (1 - δy)) `addp`- mulp (pixelAt' x (y + 1)) ((1 - δx) * δy) `addp`- mulp (pixelAt' (x + 1) (y + 1)) (δx * δy)- go (x' + 1) y'+ let xf = fromIntegral x' * sx+ yf = fromIntegral y' * sy+ x, y :: Int+ x = floor xf+ y = floor yf+ δx = xf - fromIntegral x+ δy = yf - fromIntegral y+ pixelAt' i j =+ pixelAt img (min (pred imageWidth) i) (min (pred imageHeight) j)+ writePixel mimg x' y' $+ mulp (pixelAt' x y) ((1 - δx) * (1 - δy))+ `addp` mulp (pixelAt' (x + 1) y) (δx * (1 - δy))+ `addp` mulp (pixelAt' x (y + 1)) ((1 - δx) * δy)+ `addp` mulp (pixelAt' (x + 1) (y + 1)) (δx * δy)+ go (x' + 1) y' go 0 0 #define scaleBilinear_spec(pixel) \ {-# SPECIALIZE scaleBilinear :: Int -> Int -> Image pixel -> Image pixel #-} -scaleBilinear_spec(M.PixelRGBA16)-scaleBilinear_spec(M.PixelRGBA8)-scaleBilinear_spec(M.PixelCMYK16)-scaleBilinear_spec(M.PixelCMYK8)-scaleBilinear_spec(M.PixelYCbCr8)-scaleBilinear_spec(M.PixelRGB16)-scaleBilinear_spec(M.PixelYCbCrK8)-scaleBilinear_spec(M.PixelRGB8)-scaleBilinear_spec(M.PixelYA16)-scaleBilinear_spec(M.PixelYA8)-scaleBilinear_spec(M.Pixel32)-scaleBilinear_spec(M.Pixel16)-scaleBilinear_spec(M.Pixel8)--toBlack :: Pixel a => a -> a-toBlack = colorMap (const 0)-{-# INLINE toBlack #-}+scaleBilinear_spec (M.PixelRGBA16)+scaleBilinear_spec (M.PixelRGBA8)+scaleBilinear_spec (M.PixelCMYK16)+scaleBilinear_spec (M.PixelCMYK8)+scaleBilinear_spec (M.PixelYCbCr8)+scaleBilinear_spec (M.PixelRGB16)+scaleBilinear_spec (M.PixelYCbCrK8)+scaleBilinear_spec (M.PixelRGB8)+scaleBilinear_spec (M.PixelYA16)+scaleBilinear_spec (M.PixelYA8)+scaleBilinear_spec (M.Pixel32)+scaleBilinear_spec (M.Pixel16)+scaleBilinear_spec (M.Pixel8) mulp :: (Pixel a, Integral (PixelBaseComponent a)) => a -> Float -> a mulp pixel x = colorMap (floor . (* x) . fromIntegral) pixel {-# INLINE mulp #-} -addp- :: forall a. ( Pixel a- , Bounded (PixelBaseComponent a)- , Integral (PixelBaseComponent a)- ) => a -> a -> a+addp ::+ forall a.+ ( Pixel a,+ Bounded (PixelBaseComponent a),+ Integral (PixelBaseComponent a)+ ) =>+ a ->+ a ->+ a addp = mixWith (const f) where- f x y = fromIntegral $- (maxBound :: PixelBaseComponent a) `min` (fromIntegral x + fromIntegral y)+ f x y =+ fromIntegral $+ (maxBound :: PixelBaseComponent a) `min` (fromIntegral x + fromIntegral y) {-# INLINE addp #-} --- | Crop a given image. If supplied coordinates are greater than size of--- original image, image boundaries are used instead.--crop :: Pixel a- => Int -- ^ Index (X axis) of first pixel to include- -> Int -- ^ Index (Y axis) of first pixel to include- -> Int -- ^ Width of resulting image- -> Int -- ^ Height of resulting image- -> Image a -- ^ Original image- -> Image a -- ^ Cropped image+-- | Crop an image. If the supplied coordinates are greater than the size of+-- the image, the image boundaries are used instead.+crop ::+ Pixel a =>+ -- | Index (X axis) of first pixel to include+ Int ->+ -- | Index (Y axis) of first pixel to include+ Int ->+ -- | Width of resulting image+ Int ->+ -- | Height of resulting image+ Int ->+ -- | Original image+ Image a ->+ -- | Cropped image+ Image a crop x' y' w' h' img@Image {..} = generateImage gen w h where gen i j = pixelAt img (x + i) (y + j)- x = min (imageWidth - 1) x'+ x = min (imageWidth - 1) x' y = min (imageHeight - 1) y'- w = min (imageWidth - x) w'- h = min (imageHeight - y) h'+ w = min (imageWidth - x) w'+ h = min (imageHeight - y) h' {-# INLINEABLE crop #-} -- | Flip an image horizontally.- flipHorizontally :: Pixel a => Image a -> Image a flipHorizontally img@Image {..} = generateImage gen imageWidth imageHeight@@ -141,7 +151,6 @@ {-# INLINEABLE flipHorizontally #-} -- | Flip an image vertically.- flipVertically :: Pixel a => Image a -> Image a flipVertically img@Image {..} = generateImage gen imageWidth imageHeight@@ -150,7 +159,6 @@ {-# INLINEABLE flipVertically #-} -- | Rotate an image to the left by 90°.- rotateLeft90 :: Pixel a => Image a -> Image a rotateLeft90 img@Image {..} = generateImage gen imageHeight imageWidth@@ -159,7 +167,6 @@ {-# INLINEABLE rotateLeft90 #-} -- | Rotate an image to the right by 90°.- rotateRight90 :: Pixel a => Image a -> Image a rotateRight90 img@Image {..} = generateImage gen imageHeight imageWidth@@ -170,7 +177,6 @@ -- | Rotate an image by 180°, i.e flip both vertically and horizontally. -- -- @since 0.2.0- rotate180 :: Pixel a => Image a -> Image a rotate180 img@(Image w h _) = generateImage g w h where@@ -181,7 +187,6 @@ -- are of differnet heights the smallest height is used. -- -- @since 0.2.0- beside :: Pixel a => [Image a] -> Image a beside = foldl1' go where@@ -199,7 +204,6 @@ -- images are of differnet widths the smallest width is used. -- -- @since 0.2.0- below :: Pixel a => [Image a] -> Image a below = foldl1' go where
JuicyPixels-extra.cabal view
@@ -1,72 +1,81 @@-name: JuicyPixels-extra-version: 0.4.1-cabal-version: 1.18-tested-with: GHC==8.0.2, GHC==8.2.2, GHC==8.4.4, GHC==8.6.5-license: BSD3-license-file: LICENSE.md-author: Mark Karpov <markkarpov92@gmail.com>-maintainer: Mark Karpov <markkarpov92@gmail.com>-homepage: https://github.com/mrkkrp/JuicyPixels-extra-bug-reports: https://github.com/mrkkrp/JuicyPixels-extra/issues-category: Graphics, Image-synopsis: Efficiently scale, crop, flip images with JuicyPixels-build-type: Simple-description: Efficiently scale, crop, flip images with JuicyPixels.-extra-doc-files: CHANGELOG.md- , README.md-data-files: data-examples/*.png+cabal-version: 1.18+name: JuicyPixels-extra+version: 0.5.0+license: BSD3+license-file: LICENSE.md+maintainer: Mark Karpov <markkarpov92@gmail.com>+author: Mark Karpov <markkarpov92@gmail.com>+tested-with: ghc ==8.8.4 ghc ==8.10.4 ghc ==9.0.1+homepage: https://github.com/mrkkrp/JuicyPixels-extra+bug-reports: https://github.com/mrkkrp/JuicyPixels-extra/issues+synopsis: Efficiently scale, crop, flip images with JuicyPixels+description: Efficiently scale, crop, flip images with JuicyPixels.+category: Graphics, Image+build-type: Simple+data-files: data-examples/*.png+extra-doc-files:+ CHANGELOG.md+ README.md source-repository head- type: git- location: https://github.com/mrkkrp/JuicyPixels-extra.git+ type: git+ location: https://github.com/mrkkrp/JuicyPixels-extra.git flag dev- description: Turn on development settings.- manual: True- default: False+ description: Turn on development settings.+ default: False+ manual: True library- build-depends: base >= 4.9 && < 5- , JuicyPixels >= 3.2.6.4 && < 3.4- exposed-modules: Codec.Picture.Extra- if flag(dev)- ghc-options: -Wall -Werror- else- ghc-options: -O2 -Wall- if flag(dev)- ghc-options: -Wcompat- -Wincomplete-record-updates- -Wincomplete-uni-patterns- -Wnoncanonical-monad-instances- -Wnoncanonical-monadfail-instances- default-language: Haskell2010+ exposed-modules: Codec.Picture.Extra+ default-language: Haskell2010+ build-depends:+ base >=4.13 && <5,+ JuicyPixels >=3.2.6.4 && <3.4 + if flag(dev)+ ghc-options: -Wall -Werror++ else+ ghc-options: -O2 -Wall++ if flag(dev)+ ghc-options:+ -Wcompat -Wincomplete-record-updates -Wincomplete-uni-patterns+ -Wnoncanonical-monad-instances+ test-suite tests- main-is: Spec.hs- hs-source-dirs: tests- type: exitcode-stdio-1.0- build-depends: base >= 4.9 && < 5- , JuicyPixels >= 3.2.6.4 && < 3.4- , JuicyPixels-extra- , hspec >= 2.0- build-tools: hspec-discover >= 2.0 && < 3.0- other-modules: Codec.Picture.ExtraSpec- if flag(dev)- ghc-options: -Wall -Werror- else- ghc-options: -O2 -Wall- default-language: Haskell2010+ type: exitcode-stdio-1.0+ main-is: Spec.hs+ build-tools: hspec-discover >=2.0 && <3.0+ hs-source-dirs: tests+ other-modules: Codec.Picture.ExtraSpec+ default-language: Haskell2010+ build-depends:+ base >=4.13 && <5,+ JuicyPixels >=3.2.6.4 && <3.4,+ JuicyPixels-extra,+ hspec >=2.0 + if flag(dev)+ ghc-options: -Wall -Werror++ else+ ghc-options: -O2 -Wall+ benchmark bench- main-is: Main.hs- hs-source-dirs: bench- type: exitcode-stdio-1.0- build-depends: base >= 4.9 && < 5- , JuicyPixels >= 3.2.6.4 && < 3.4- , JuicyPixels-extra- , criterion >= 0.6.2.1 && < 1.6- if flag(dev)- ghc-options: -O2 -Wall -Werror- else- ghc-options: -O2 -Wall- default-language: Haskell2010+ type: exitcode-stdio-1.0+ main-is: Main.hs+ hs-source-dirs: bench+ default-language: Haskell2010+ build-depends:+ base >=4.13 && <5,+ JuicyPixels >=3.2.6.4 && <3.4,+ JuicyPixels-extra,+ criterion >=0.6.2.1 && <1.6++ if flag(dev)+ ghc-options: -O2 -Wall -Werror++ else+ ghc-options: -O2 -Wall
LICENSE.md view
@@ -1,4 +1,4 @@-Copyright © 2016–2018 Mark Karpov+Copyright © 2016–present Mark Karpov All rights reserved.
README.md view
@@ -4,13 +4,19 @@ [](https://hackage.haskell.org/package/JuicyPixels-extra) [](http://stackage.org/nightly/package/JuicyPixels-extra) [](http://stackage.org/lts/package/JuicyPixels-extra)-[](https://travis-ci.org/mrkkrp/JuicyPixels-extra)+ -Missing primitives you shouldn't need to re-implement yourself. I think the-Haddocks are pretty self-explanatory, so head straight to them.+A collection of functions to scale, crop, flip images with JuicyPixels. +## Contribution++Issues, bugs, and questions may be reported in [the GitHub issue tracker for+this project](https://github.com/mrkkrp/JuicyPixels-extra/issues).++Pull requests are also welcome.+ ## License -Copyright © 2016–2019 Mark Karpov+Copyright © 2016–present Mark Karpov Distributed under BSD 3 clause license.
bench/Main.hs view
@@ -5,49 +5,69 @@ import Criterion.Main main :: IO ()-main = defaultMain- [ bgroup "scaleBilinear"- [ baction "512 × 512" (scaleBilinear 512 512) "data-examples/lenna.png"- , baction "256 × 256" (scaleBilinear 256 256) "data-examples/lenna.png"- , baction "1024 × 1024" (scaleBilinear 1024 1024) "data-examples/lenna.png" ]- , bgroup "crop"- [ baction "on 256 horizontally" (crop 256 0 256 512) "data-examples/lenna.png"- , baction "on 265 vertically" (crop 0 256 512 256) "data-examples/lenna.png" ]- , bgroup "flipHorizontally"- [ baction "512 × 512" flipHorizontally "data-examples/lenna.png"- , baction "100 × 100" flipHorizontally "data-examples/lenna-scaled-down.png"+main =+ defaultMain+ [ bgroup+ "scaleBilinear"+ [ baction "512 × 512" (scaleBilinear 512 512) "data-examples/lenna.png",+ baction "256 × 256" (scaleBilinear 256 256) "data-examples/lenna.png",+ baction "1024 × 1024" (scaleBilinear 1024 1024) "data-examples/lenna.png"+ ],+ bgroup+ "crop"+ [ baction "on 256 horizontally" (crop 256 0 256 512) "data-examples/lenna.png",+ baction "on 265 vertically" (crop 0 256 512 256) "data-examples/lenna.png"+ ],+ bgroup+ "flipHorizontally"+ [ baction "512 × 512" flipHorizontally "data-examples/lenna.png",+ baction "100 × 100" flipHorizontally "data-examples/lenna-scaled-down.png"+ ],+ bgroup+ "flipVertically"+ [ baction "512 × 512" flipVertically "data-examples/lenna.png",+ baction "100 × 100" flipVertically "data-examples/lenna-scaled-down.png"+ ],+ bgroup+ "rotateLeft90"+ [ baction "512 × 512" rotateLeft90 "data-examples/lenna.png",+ baction "100 × 100" rotateLeft90 "data-examples/lenna-scaled-down.png"+ ],+ bgroup+ "rotateRight90"+ [ baction "512 × 512" rotateRight90 "data-examples/lenna.png",+ baction "100 × 100" rotateRight90 "data-examples/lenna-scaled-down.png"+ ],+ bgroup+ "rotate180"+ [ baction "512 × 512" rotate180 "data-examples/lenna.png",+ baction "100 × 100" rotate180 "data-examples/lenna-scaled-down.png"+ ],+ bgroup+ "beside"+ [ baction "1024 × 512" beside' "data-examples/lenna.png",+ baction "200 × 100" beside' "data-examples/lenna-scaled-down.png"+ ],+ bgroup+ "below"+ [ baction "512 × 1024" below' "data-examples/lenna.png",+ baction "100 × 200" below' "data-examples/lenna-scaled-down.png"+ ] ]- , bgroup "flipVertically"- [ baction "512 × 512" flipVertically "data-examples/lenna.png"- , baction "100 × 100" flipVertically "data-examples/lenna-scaled-down.png" ]- , bgroup "rotateLeft90"- [ baction "512 × 512" rotateLeft90 "data-examples/lenna.png"- , baction "100 × 100" rotateLeft90 "data-examples/lenna-scaled-down.png" ]- , bgroup "rotateRight90"- [ baction "512 × 512" rotateRight90 "data-examples/lenna.png"- , baction "100 × 100" rotateRight90 "data-examples/lenna-scaled-down.png" ]- , bgroup "rotate180"- [ baction "512 × 512" rotate180 "data-examples/lenna.png"- , baction "100 × 100" rotate180 "data-examples/lenna-scaled-down.png" ]- , bgroup "beside"- [ baction "1024 × 512" beside' "data-examples/lenna.png"- , baction "200 × 100" beside' "data-examples/lenna-scaled-down.png" ]- , bgroup "below"- [ baction "512 × 1024" below' "data-examples/lenna.png"- , baction "100 × 200" below' "data-examples/lenna-scaled-down.png" ]- ] where beside' img = beside [img, img]- below' img = below [img, img]---- | Run a benchmark given function to benchmark and where to get image to--- pass as an input.+ below' img = below [img, img] -baction- :: String -- ^ Name of the benchmark- -> (Image PixelRGB8 -> Image PixelRGB8) -- ^ Transformation to perform- -> FilePath -- ^ Where to get image to start with- -> Benchmark+-- | Run a benchmark given a function to benchmark and where to get image to+-- pass as the input.+baction ::+ -- | Name of the benchmark+ String ->+ -- | The transformation to perform+ (Image PixelRGB8 -> Image PixelRGB8) ->+ -- | Where to get the image to start with+ FilePath ->+ Benchmark baction name f path = env getImg (bench name . nf f) where getImg = do
data-examples/lenna-scaled-up.png view
binary file changed (985586 → 985068 bytes)
tests/Codec/Picture/ExtraSpec.hs view
@@ -1,6 +1,7 @@ module Codec.Picture.ExtraSpec- ( main- , spec )+ ( main,+ spec,+ ) where import Codec.Picture@@ -14,31 +15,34 @@ spec :: Spec spec = do- describe "scaleBilinear" scaleBilinearSpec- describe "crop" cropSpec- describe "flipHorizontally" flipHorizontallySpec- describe "flipVertically" flipVerticallySpec- describe "rotateLeft90Spec" rotateLeft90Spec+ describe "scaleBilinear" scaleBilinearSpec+ describe "crop" cropSpec+ describe "flipHorizontally" flipHorizontallySpec+ describe "flipVertically" flipVerticallySpec+ describe "rotateLeft90Spec" rotateLeft90Spec describe "rotateRight90Spec" rotateRight90Spec- describe "rotate180" rotate180Spec- describe "beside" besideSpec- describe "below" belowSpec+ describe "rotate180" rotate180Spec+ describe "beside" besideSpec+ describe "below" belowSpec scaleBilinearSpec :: Spec scaleBilinearSpec = do context "when called with orginial dimensions" $ it "produces the same image" $- checkWithFiles (scaleBilinear 512 512)+ checkWithFiles+ (scaleBilinear 512 512) "data-examples/lenna.png" "data-examples/lenna.png" context "when we scale down" $ it "produces correct image" $- checkWithFiles (scaleBilinear 100 100)+ checkWithFiles+ (scaleBilinear 100 100) "data-examples/lenna.png" "data-examples/lenna-scaled-down.png" context "when we scale up" $ it "produces correct image" $- checkWithFiles (scaleBilinear 600 600)+ checkWithFiles+ (scaleBilinear 600 600) "data-examples/lenna.png" "data-examples/lenna-scaled-up.png" @@ -46,12 +50,14 @@ cropSpec = do context "when we pass arguments within image size (square)" $ it "produces correct image" $- checkWithFiles (crop 211 210 178 191)+ checkWithFiles+ (crop 211 210 178 191) "data-examples/lenna.png" "data-examples/lenna-cropped.png" context "when we pass arguments within image size (vertical)" $ it "produces correct image" $- checkWithFiles (crop 0 512 512 512)+ checkWithFiles+ (crop 0 512 512 512) "data-examples/lenna-below.png" "data-examples/lenna.png" @@ -59,7 +65,8 @@ flipHorizontallySpec = context "when we flip horizontally" $ it "produces correct image" $- checkWithFiles flipHorizontally+ checkWithFiles+ flipHorizontally "data-examples/lenna.png" "data-examples/lenna-horizontal-flip.png" @@ -67,7 +74,8 @@ flipVerticallySpec = context "when we flip vertically" $ it "produces correct image" $- checkWithFiles flipVertically+ checkWithFiles+ flipVertically "data-examples/lenna.png" "data-examples/lenna-vertical-flip.png" @@ -75,7 +83,8 @@ rotateLeft90Spec = context "when we rotate to the left by 90°" $ it "produces correct image" $- checkWithFiles rotateLeft90+ checkWithFiles+ rotateLeft90 "data-examples/lenna.png" "data-examples/lenna-left-rotated.png" @@ -83,7 +92,8 @@ rotateRight90Spec = context "when we rotate to the right by 90°" $ it "produces correct image" $- checkWithFiles rotateRight90+ checkWithFiles+ rotateRight90 "data-examples/lenna.png" "data-examples/lenna-right-rotated.png" @@ -91,7 +101,8 @@ rotate180Spec = context "when we rotate by 180°" $ it "produces correct image" $- checkWithFiles rotate180+ checkWithFiles+ rotate180 "data-examples/lenna.png" "data-examples/lenna-180-rotated.png" @@ -99,7 +110,8 @@ besideSpec = context "when we place images beside each other" $ it "produces correct image" $- checkWithFiles (\x -> beside [x,x])+ checkWithFiles+ (\x -> beside [x, x]) "data-examples/lenna.png" "data-examples/lenna-beside.png" @@ -107,34 +119,35 @@ belowSpec = context "when we place images below each other" $ it "produces correct image" $- checkWithFiles (\x -> below [x,x])+ checkWithFiles+ (\x -> below [x, x]) "data-examples/lenna.png" "data-examples/lenna-below.png" --- | Run given transforming function on image loaded from one file and--- compare resulting image with contents of another file.--checkWithFiles- :: (Image PixelRGB8 -> Image PixelRGB8) -- ^ Transformation to test- -> FilePath -- ^ Where to get the original image- -> FilePath -- ^ Where to get image to compare with- -> Expectation+-- | Run a transforming on the image loaded from a file and compare the+-- resulting image with the contents of another file.+checkWithFiles ::+ -- | Transformation to test+ (Image PixelRGB8 -> Image PixelRGB8) ->+ -- | Where to get the original image+ FilePath ->+ -- | Where to get the image to compare with+ FilePath ->+ Expectation checkWithFiles f opath fpath = do (Right (ImageRGB8 original)) <- readImage opath- (Right (ImageRGB8 result)) <- readImage fpath+ (Right (ImageRGB8 result)) <- readImage fpath f original `blindlySatisfy` sameImage result --- | The same as 'shouldSatisfy', but doesn't care if its argument is+-- | The same as 'shouldSatisfy', but doesn't care if its argument is an -- instance of 'Show' or not.- blindlySatisfy :: a -> (a -> Bool) -> Expectation v `blindlySatisfy` p = unless (p v) (expectationFailure "predicate failed") --- | Equality test for images.-+-- | The equality test for images. sameImage :: Image PixelRGB8 -> Image PixelRGB8 -> Bool sameImage a b =- ((==) `on` imageWidth) a b &&- ((==) `on` imageHeight) a b &&- ((==) `on` imageData) a b+ ((==) `on` imageWidth) a b+ && ((==) `on` imageHeight) a b+ && ((==) `on` imageData) a b