packages feed

JuicyPixels-extra-0.6.0: tests/Codec/Picture/ExtraSpec.hs

{-# LANGUAGE FlexibleContexts #-}

module Codec.Picture.ExtraSpec
  ( main,
    spec,
  )
where

import Codec.Picture
import Codec.Picture.Extra
import Control.Monad
import Data.Function (on)
import Foreign (Storable)
import Test.Hspec

main :: IO ()
main = hspec spec

spec :: Spec
spec = do
  describe "scaleBilinear" scaleBilinearSpec
  describe "crop" cropSpec
  describe "trim" trimSpec
  describe "flipHorizontally" flipHorizontallySpec
  describe "flipVertically" flipVerticallySpec
  describe "rotateLeft90Spec" rotateLeft90Spec
  describe "rotateRight90Spec" rotateRight90Spec
  describe "rotate180" rotate180Spec
  describe "beside" besideSpec
  describe "below" belowSpec
  describe "square" squareSpec

scaleBilinearSpec :: Spec
scaleBilinearSpec = do
  context "when called with orginial dimensions" $
    it "produces the same image" $
      checkWithFiles
        (scaleBilinear 512 512)
        "data-examples/macaque.png"
        "data-examples/macaque.png"
  context "when we scale down" $
    it "produces correct image" $
      checkWithFiles
        (scaleBilinear 100 100)
        "data-examples/macaque.png"
        "data-examples/macaque-scaled-down.png"
  context "when we scale up" $
    it "produces correct image" $
      checkWithFiles
        (scaleBilinear 600 600)
        "data-examples/macaque.png"
        "data-examples/macaque-scaled-up.png"
  context "when we scale to 1 width" $
    it "produces correct image" $
      checkWithFiles
        (scaleBilinear 1 512)
        "data-examples/macaque.png"
        "data-examples/macaque-width-1.png"
  context "when we scale to 1 height" $
    it "produces correct image" $
      checkWithFiles
        (scaleBilinear 512 1)
        "data-examples/macaque.png"
        "data-examples/macaque-height-1.png"
  context "when we scale to 0x0" $
    it "produces the empty image" $
      checkWithFiles
        (scaleBilinear 0 0)
        "data-examples/macaque.png"
        "data-examples/empty.png"

cropSpec :: Spec
cropSpec = do
  context "when we pass arguments within image size (square)" $
    it "produces correct image" $
      checkWithFiles
        (crop 211 210 178 191)
        "data-examples/macaque.png"
        "data-examples/macaque-cropped.png"
  context "when we pass arguments within image size (vertical)" $
    it "produces correct image" $
      checkWithFiles
        (crop 0 512 512 512)
        "data-examples/macaque-below.png"
        "data-examples/macaque.png"

trimSpec :: Spec
trimSpec = do
  context "when we pass an image without transparency" $
    it "does nothing" $
      checkWithFiles
        trim
        "data-examples/macaque.png"
        "data-examples/macaque.png"
  context "when passing an image with transparency" $ do
    it "does nothing if there are no fully transparent edges" $
      checkWithFilesAlpha
        trim
        "data-examples/macaque-transparent-cropped.png"
        "data-examples/macaque-transparent-cropped.png"
    it "removed the transparent edges" $
      checkWithFilesAlpha
        trim
        "data-examples/macaque-transparent.png"
        "data-examples/macaque-transparent-cropped.png"
  context "when passing a fully transparent image" $
    it "returns an empty image" $ do
      (Right (ImageRGBA8 original)) <- readImage "data-examples/fully-transparent.png"
      let trimmed = trim original
      imageWidth trimmed `shouldBe` 1
      imageHeight trimmed `shouldBe` 1

flipHorizontallySpec :: Spec
flipHorizontallySpec =
  context "when we flip horizontally" $
    it "produces correct image" $
      checkWithFiles
        flipHorizontally
        "data-examples/macaque.png"
        "data-examples/macaque-horizontal-flip.png"

flipVerticallySpec :: Spec
flipVerticallySpec =
  context "when we flip vertically" $
    it "produces correct image" $
      checkWithFiles
        flipVertically
        "data-examples/macaque.png"
        "data-examples/macaque-vertical-flip.png"

rotateLeft90Spec :: Spec
rotateLeft90Spec =
  context "when we rotate to the left by 90°" $
    it "produces correct image" $
      checkWithFiles
        rotateLeft90
        "data-examples/macaque.png"
        "data-examples/macaque-left-rotated.png"

rotateRight90Spec :: Spec
rotateRight90Spec =
  context "when we rotate to the right by 90°" $
    it "produces correct image" $
      checkWithFiles
        rotateRight90
        "data-examples/macaque.png"
        "data-examples/macaque-right-rotated.png"

rotate180Spec :: Spec
rotate180Spec =
  context "when we rotate by 180°" $
    it "produces correct image" $
      checkWithFiles
        rotate180
        "data-examples/macaque.png"
        "data-examples/macaque-180-rotated.png"

besideSpec :: Spec
besideSpec =
  context "when we place images beside each other" $
    it "produces correct image" $
      checkWithFiles
        (\x -> beside [x, x])
        "data-examples/macaque.png"
        "data-examples/macaque-beside.png"

belowSpec :: Spec
belowSpec =
  context "when we place images below each other" $
    it "produces correct image" $
      checkWithFiles
        (\x -> below [x, x])
        "data-examples/macaque.png"
        "data-examples/macaque-below.png"

squareSpec :: Spec
squareSpec = do
  context "when we pass an already square image" $ do
    it "does nothing" $
      checkWithFiles
        (square $ PixelRGB8 0 0 0)
        "data-examples/macaque.png"
        "data-examples/macaque.png"
    it "does nothing (alpha)" $
      checkWithFilesAlpha
        (square $ PixelRGBA8 0 0 0 0)
        "data-examples/macaque-transparent.png"
        "data-examples/macaque-transparent.png"
  context "when we pass a non-square image" $ do
    it "adds the required filler to wider than heigh image" $ do
      (Right (ImageRGB8 original)) <- readImage "data-examples/macaque-beside.png"
      let squared = square (PixelRGB8 0 0 0) original
      imageWidth squared `shouldBe` 1024
      imageHeight squared `shouldBe` 1024
    it "adds the required filler to higher than wide image" $ do
      (Right (ImageRGB8 original)) <- readImage "data-examples/macaque-below.png"
      let squared = square (PixelRGB8 0 0 0) original
      imageWidth squared `shouldBe` 1024
      imageHeight squared `shouldBe` 1024

-- | 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
  f original `blindlySatisfy` sameImage result

-- | `checkWithFiles` for images with an alpha channel.
checkWithFilesAlpha ::
  -- | Transformation to test
  (Image PixelRGBA8 -> Image PixelRGBA8) ->
  -- | Where to get the original image
  FilePath ->
  -- | Where to get the image to compare with
  FilePath ->
  Expectation
checkWithFilesAlpha f opath fpath = do
  (Right (ImageRGBA8 original)) <- readImage opath
  (Right (ImageRGBA8 result)) <- readImage fpath
  f original `blindlySatisfy` sameImage result

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

-- | The equality test for images.
sameImage :: (Eq a, Eq (PixelBaseComponent a), Storable (PixelBaseComponent a)) => Image a -> Image a -> Bool
sameImage a b =
  ((==) `on` imageWidth) a b
    && ((==) `on` imageHeight) a b
    && ((==) `on` imageData) a b