packages feed

hakyll-images 1.1.1 → 1.2.0

raw patch · 11 files changed

+74/−77 lines, 11 filesPVP ok

version bump matches the API change (PVP)

API changes (from Hackage documentation)

- Hakyll.Images: type JpgQuality = Int
- Hakyll.Images.CompressJpg: type JpgQuality = Int
+ Hakyll.Images: data JpgQuality
+ Hakyll.Images.CompressJpg: data JpgQuality
+ Hakyll.Images.CompressJpg: instance GHC.Classes.Eq Hakyll.Images.CompressJpg.JpgQuality
+ Hakyll.Images.CompressJpg: instance GHC.Classes.Ord Hakyll.Images.CompressJpg.JpgQuality
+ Hakyll.Images.CompressJpg: instance GHC.Enum.Enum Hakyll.Images.CompressJpg.JpgQuality
+ Hakyll.Images.CompressJpg: instance GHC.Num.Num Hakyll.Images.CompressJpg.JpgQuality
+ Hakyll.Images.CompressJpg: instance GHC.Real.Integral Hakyll.Images.CompressJpg.JpgQuality
+ Hakyll.Images.CompressJpg: instance GHC.Real.Real Hakyll.Images.CompressJpg.JpgQuality
- Hakyll.Images: compressJpg :: JpgQuality -> Image -> Image
+ Hakyll.Images: compressJpg :: Integral a => a -> Image -> Image
- Hakyll.Images: compressJpgCompiler :: JpgQuality -> Item Image -> Compiler (Item Image)
+ Hakyll.Images: compressJpgCompiler :: Integral a => a -> Item Image -> Compiler (Item Image)
- Hakyll.Images.CompressJpg: compressJpg :: JpgQuality -> Image -> Image
+ Hakyll.Images.CompressJpg: compressJpg :: Integral a => a -> Image -> Image
- Hakyll.Images.CompressJpg: compressJpgCompiler :: JpgQuality -> Item Image -> Compiler (Item Image)
+ Hakyll.Images.CompressJpg: compressJpgCompiler :: Integral a => a -> Item Image -> Compiler (Item Image)

Files

CHANGELOG.md view
@@ -1,5 +1,9 @@ # Change log
 
+## Release 1.2.0
+
+* Made `JpgQuality` an opaque constructor, so that we can normalize the input to the range [0, 100].
+
 ## Release 1.1.1
 
 * Updated the test suite to support `Hakyll.Core.Runtime.RunMode` (added in hakyll 4.15) (#10).
hakyll-images.cabal view
@@ -1,7 +1,7 @@ cabal-version: 2.2
 
 name:           hakyll-images
-version:        1.1.1
+version:        1.2.0
 synopsis:       Hakyll utilities to work with images
 description:    hakyll-images is an add-on to the hakyll package. It adds utilities to work with images, including JPEG compression.
 category:       Web
library/Hakyll/Images.hs view
@@ -1,7 +1,7 @@ -- |
 -- Module      : Hakyll.Images
 -- Description : Hakyll utilities for image files
--- Copyright   : (c) Laurent P René de Cotret, 2019
+-- Copyright   : (c) Laurent P René de Cotret, 2019 - present
 -- License     : BSD3
 -- Maintainer  : laurent.decotret@outlook.com
 -- Stability   : unstable
library/Hakyll/Images/Common.hs view
@@ -1,12 +1,10 @@ {-# LANGUAGE DeriveDataTypeable #-}
 {-# LANGUAGE DeriveGeneric #-}
 {-# LANGUAGE FlexibleInstances #-}
-{-# LANGUAGE TypeSynonymInstances #-}
-
 -- |
 -- Module      : Hakyll.Images.Common
 -- Description : Types and utilities for Hakyll.Images
--- Copyright   : (c) Laurent P René de Cotret, 2019
+-- Copyright   : (c) Laurent P René de Cotret, 2019 - present
 -- License     : BSD3
 -- Maintainer  : laurent.decotret@outlook.com
 -- Stability   : unstable
@@ -25,6 +23,7 @@ import Data.ByteString (ByteString)
 import Data.ByteString.Lazy (toStrict)
 import Data.Char (toLower)
+import Data.Either (fromRight)
 import Data.Typeable (Typeable)
 import GHC.Generics (Generic)
 import Hakyll.Core.Compiler (Compiler, getResourceLBS, getUnderlyingExtension)
@@ -74,7 +73,7 @@ loadImage = do
   content <- fmap toStrict <$> getResourceLBS
   fmt <- fromExt <$> getUnderlyingExtension
-  return $ (Image fmt) <$> content
+  return $ Image fmt <$> content
 
 -- | Translation between file extensions and image formats.
 -- It is important to keep track of image formats because Hakyll
@@ -99,4 +98,4 @@ encode Png im = Image Png $ (toStrict . imageToPng) im
 encode Bitmap im = Image Bitmap $ (toStrict . imageToBitmap) im
 encode Tiff im = Image Tiff $ (toStrict . imageToTiff) im
-encode Gif im = Image Gif $ (toStrict . either (const $ error "Could not parse gif") id . imageToGif) im
+encode Gif im = Image Gif $ (toStrict . fromRight (error "Could not parse gif") . imageToGif) im
library/Hakyll/Images/CompressJpg.hs view
@@ -1,7 +1,8 @@+{-# LANGUAGE GeneralisedNewtypeDeriving #-}
 -- |
 -- Module      : Hakyll.Images.CompressJpg
 -- Description : Hakyll compiler to compress Jpeg images
--- Copyright   : (c) Laurent P René de Cotret, 2019
+-- Copyright   : (c) Laurent P René de Cotret, 2019 - present
 -- License     : BSD3
 -- Maintainer  : laurent.decotret@outlook.com
 -- Stability   : unstable
@@ -47,27 +48,41 @@     format,
     image,
   )
+import Numeric.Natural (Natural)
 
+
 -- | Jpeg encoding quality, from 0 (lower quality) to 100 (best quality).
-type JpgQuality = Int
+-- @since 1.2.0
+newtype JpgQuality = JpgQuality Natural
+  deriving (Num, Eq, Enum, Ord, Real, Integral)
 
+
+-- | @JpgQuality@ smart constructor. Ensures that @JpgQuality@ is always
+-- in the interval [0, 100]. Numbers outside this range will result in either
+-- a quality of 0 or 100.
+--
+-- @since 1.2.0
+mkJpgQuality :: Integral a => a -> JpgQuality
+mkJpgQuality q | q < 0     = JpgQuality 0
+               | q > 100   = JpgQuality 100
+               | otherwise = JpgQuality (fromIntegral q)
+
+
 -- | Compress a JPG bytestring to a certain quality setting.
 -- The quality should be between 0 (lowest quality) and 100 (best quality).
--- An error is raised if the image cannot be decoded, or if the
--- encoding quality is out-of-bounds
-compressJpg :: JpgQuality -> Image -> Image
-compressJpg quality src =
-  if (format src) /= Jpeg
-    then error $ "Image is not a JPEG."
+-- An error is raised if the image cannot be decoded.
+compressJpg :: Integral a => a -> Image -> Image
+compressJpg quality' src =
+  if format src /= Jpeg
+    then error "Image is not a JPEG."
     else case decodeJpeg $ image src of
-      Left _ -> error $ "Loading the image failed."
-      Right dynImage ->
-        if (quality < 0 || quality > 100)
-          then error $ "JPEG encoding quality should be between 0 and 100."
-          else Image Jpeg $ (toStrict $ imageToJpg quality dynImage)
+      Left _ -> error "Loading the image failed."
+      Right dynImage -> Image Jpeg $ toStrict (imageToJpg (fromIntegral quality) dynImage)
+  where quality = mkJpgQuality quality'
 
 -- | Compiler that compresses a JPG image to a certain quality setting.
 -- The quality should be between 0 (lowest quality) and 100 (best quality).
+-- Values outside of this range will be normalized to the interval [0, 100].
 -- An error is raised if the image cannot be decoded.
 --
 -- @
@@ -76,5 +91,5 @@ --     compile $ loadImage
 --         >>= compressJpgCompiler 50
 -- @
-compressJpgCompiler :: JpgQuality -> Item Image -> Compiler (Item Image)
+compressJpgCompiler :: Integral a => a -> Item Image -> Compiler (Item Image)
 compressJpgCompiler quality = return . fmap (compressJpg quality)
library/Hakyll/Images/Metadata.hs view
@@ -1,7 +1,7 @@ -- |
 -- Module      : Hakyll.Images.Metadata
 -- Description : Handling image metadata
--- Copyright   : (c) Laurent P René de Cotret, 2019
+-- Copyright   : (c) Laurent P René de Cotret, 2019 - present
 -- License     : BSD3
 -- Maintainer  : laurent.decotret@outlook.com
 -- Stability   : unstable
library/Hakyll/Images/Resize.hs view
@@ -1,7 +1,7 @@ -- |
 -- Module      : Hakyll.Images.Resize
 -- Description : Hakyll compiler to resize images
--- Copyright   : (c) Laurent P René de Cotret, 2019
+-- Copyright   : (c) Laurent P René de Cotret, 2019 - present
 -- License     : BSD3
 -- Maintainer  : laurent.decotret@outlook.com
 -- Stability   : unstable
@@ -72,7 +72,7 @@ -- In the process, an image is converted to RGBA8. Therefore, some information
 -- loss may occur.
 resize :: Width -> Height -> DynamicImage -> DynamicImage
-resize w h = ImageRGBA8 . (scaleBilinear w h) . convertRGBA8
+resize w h = ImageRGBA8 . scaleBilinear w h . convertRGBA8
 
 -- | Scale an image to a size that will fit in the specified width and height,
 -- while preserving aspect ratio. Images might be scaled up as well.
@@ -139,7 +139,7 @@ resizeImageCompiler :: Width -> Height -> Item Image -> Compiler (Item Image)
 resizeImageCompiler w h item =
   let fmt = (format . itemBody) item
-   in return $ (encode fmt . resize w h . decodeImage' . image) <$> item
+   in return $ encode fmt . resize w h . decodeImage' . image <$> item
 
 -- | Compiler that rescales images to fit within dimensions. Aspect ratio
 -- will be preserved. Images might be scaled up as well.
@@ -156,7 +156,7 @@ scaleImageCompiler :: Width -> Height -> Item Image -> Compiler (Item Image)
 scaleImageCompiler w h item =
   let fmt = (format . itemBody) item
-   in return $ (encode fmt . scale w h . decodeImage' . image) <$> item
+   in return $ encode fmt . scale w h . decodeImage' . image <$> item
 
 -- | Compiler that ensures images will fit within dimensions. Images might
 -- be scaled down, but never up.  Aspect ratio will be preserved.
@@ -173,4 +173,4 @@ ensureFitCompiler :: Width -> Height -> Item Image -> Compiler (Item Image)
 ensureFitCompiler w h item =
   let fmt = (format . itemBody) item
-   in return $ (encode fmt . ensureFit w h . decodeImage' . image) <$> item
+   in return $ encode fmt . ensureFit w h . decodeImage' . image <$> item
tests/Hakyll/Images/Common/Tests.hs view
@@ -64,18 +64,15 @@       route idRoute
       compile $
         loadImage
-          >>= compressJpgCompiler 50
+          >>= compressJpgCompiler (50::Int)
 
   _ <-
     assertBool "Image was not written"
-      <$> (doesFileExist $ destinationDirectory testConfiguration </> "piccolo.jpg")
+      <$> doesFileExist ( destinationDirectory testConfiguration </> "piccolo.jpg")
 
   cleanTestEnv
 
 --------------------------------------------------------------------------------
 tests :: TestTree
 tests =
-  testGroup "Hakyll.Images.Common.Tests" $
-    concat
-      [ fromAssertions "run" [case1]
-      ]
+  testGroup "Hakyll.Images.Common.Tests" $ fromAssertions "run" [case1]
tests/Hakyll/Images/CompressJpg/Tests.hs view
@@ -8,16 +8,15 @@ 
 --------------------------------------------------------------------------------
 
-import Control.Exception (ErrorCall, catch, evaluate)
 import qualified Data.ByteString as B
-import Hakyll.Images.Common
-import Hakyll.Images.CompressJpg
+import Hakyll.Images.Common ( Image(Image, image), ImageFormat(Jpeg) )
+import Hakyll.Images.CompressJpg ( compressJpg )
 import Test.Tasty (TestTree, testGroup)
-import Test.Tasty.HUnit (Assertion, assertBool, assertFailure, testCase)
+import Test.Tasty.HUnit (Assertion, assertBool, testCase)
 import Text.Printf (printf)
 
 testJpg :: IO Image
-testJpg = Image Jpeg <$> (B.readFile "tests/data/piccolo.jpg")
+testJpg = Image Jpeg <$> B.readFile "tests/data/piccolo.jpg"
 
 fromAssertions ::
   -- | Name
@@ -35,51 +34,37 @@ testCompressionFromImage = do
   im <- testJpg
   let initialSize = (B.length . image) im
-      finalSize = (B.length . image . compressJpg 25) im
+      finalSize = (B.length . image . compressJpg (25::Int)) im
 
   assertBool "Image was not compressed" (initialSize > finalSize)
 
--- Test that specifying a JPG encoding below 0 will fail
+-- Test that specifying a JPG encoding below 0 will not fail
 testJpgEncodingOutOfLowerBound :: Assertion
 testJpgEncodingOutOfLowerBound = do
   im <- testJpg
-  -- Catching exceptions is an idea from here:
-  -- https://stackoverflow.com/questions/46330592/is-it-possible-to-assert-an-error-case-in-hunit
-  -- Since compressJpg is a "pure" function, we need to evaluate it in an IO context
-  -- to catch errors.
-  errored <- catch (evaluate (compressJpg (-10) im) >> pure False) handler
-  if errored
-    then pure ()
-    else assertFailure "did not catch expected error"
-  where
-    handler :: ErrorCall -> IO Bool
-    handler _ = pure True
+  
+  let compressedSize = (B.length . image . compressJpg (-10 :: Int)) im
+      expectedSize = (B.length . image . compressJpg (0 :: Int)) im
 
+  assertBool "Out-of-boumds JpgQuality was not handled properly" (expectedSize == compressedSize)
+
 -- Test that specifying a JPG encoding above 100 will fail
 testJpgEncodingOutOfUpperBound :: Assertion
 testJpgEncodingOutOfUpperBound = do
   im <- testJpg
-  -- Catching exceptions is an idea from here:
-  -- https://stackoverflow.com/questions/46330592/is-it-possible-to-assert-an-error-case-in-hunit
-  -- Since compressJpg is a "pure" function, we need to evaluate it in an IO context
-  -- to catch errors.
-  errored <- catch (evaluate (compressJpg 111 im) >> pure False) handler
-  if errored
-    then pure ()
-    else assertFailure "did not catch expected error"
-  where
-    handler :: ErrorCall -> IO Bool
-    handler _ = pure True
+  
+  let compressedSize = (B.length . image . compressJpg (150 :: Int)) im
+      expectedSize = (B.length . image . compressJpg (100 :: Int)) im
 
+  assertBool "Out-of-boumds JpgQuality was not handled properly" (expectedSize == compressedSize)
+
 --------------------------------------------------------------------------------
 tests :: TestTree
 tests =
   testGroup "Hakyll.Images.CompressJpg.Tests" $
-    concat
-      [ fromAssertions
-          "compressJpg"
-          [ testCompressionFromImage,
-            testJpgEncodingOutOfLowerBound,
-            testJpgEncodingOutOfUpperBound
-          ]
-      ]
+    fromAssertions
+        "compressJpg"
+        [ testCompressionFromImage,
+          testJpgEncodingOutOfLowerBound,
+          testJpgEncodingOutOfUpperBound
+        ]
tests/Hakyll/Images/Metadata/Tests.hs view
@@ -29,7 +29,7 @@   zipWith testCase [printf "[%2d] %s" n name | n <- [1 :: Int ..]]
 
 testJpg :: IO Image
-testJpg = Image Jpeg <$> (B.readFile "tests/data/piccolo.jpg")
+testJpg = Image Jpeg <$> B.readFile "tests/data/piccolo.jpg"
 
 testMetadata :: Assertion
 testMetadata = do
@@ -45,7 +45,4 @@ 
 tests :: TestTree
 tests =
-  testGroup "Hakyll.Images.Metadata.Tests" $
-    concat
-      [ fromAssertions "metadata" [testMetadata]
-      ]
+  testGroup "Hakyll.Images.Metadata.Tests" $ fromAssertions "metadata" [testMetadata]
tests/Hakyll/Images/Resize/Tests.hs view
@@ -101,9 +101,9 @@ testScalePreservesAspectRatio = do
   image <- testJpg
 
-  let initialAspectRatio = (imageWidth $ convertRGBA8 image) % (imageHeight $ convertRGBA8 image)
+  let initialAspectRatio = imageWidth (convertRGBA8 image) % imageHeight (convertRGBA8 image)
       scaledImage = scale 600 400 image
-      finalAspectRatio = (imageWidth $ convertRGBA8 scaledImage) % (imageHeight $ convertRGBA8 scaledImage)
+      finalAspectRatio = imageWidth (convertRGBA8 scaledImage) % imageHeight (convertRGBA8 scaledImage)
 
   assertApproxEqual "Aspect ratio was not preserved" 0.02 initialAspectRatio finalAspectRatio