{-# LANGUAGE GeneralisedNewtypeDeriving #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE ScopedTypeVariables #-}

-- |
-- Module      : Hakyll.Images.CompressJpg
-- Description : Hakyll compiler to compress Jpeg images
-- Copyright   : (c) Laurent P René de Cotret, 2019 - present
-- License     : BSD3
-- Maintainer  : laurent.decotret@outlook.com
-- Stability   : unstable
-- Portability : portable
--
-- This module defines a Hakyll compiler, 'compressJpgCompiler', which can be used to
-- re-encode Jpeg images at a lower quality during website compilation. Original images are
-- left unchanged, but compressed images can be up to 10x smaller.
--
-- The @compressJpgCompiler@ is expected to be used like this:
--
-- @
--     import Hakyll
--     import Hakyll.Images        ( loadImage
--                                 , compressJpgCompiler
--                                 )
--
--     hakyll $ do
--
--         -- Compress all source Jpegs to a Jpeg quality of 50
--         match "images/**.jpg" $ do
--             route idRoute
--             compile $ loadImage
--                 >>= compressJpgCompiler 50
--
--         (... omitted ...)
-- @
module Hakyll.Images.CompressJpg
  ( JpgQuality,
    compressJpgCompiler,
  )
where

import Codec.Picture.Saving.WithMetadata (imageToJpgWithMetadata)
import Data.ByteString.Lazy (toStrict)
import Hakyll.Core.Compiler (Compiler)
import Hakyll.Core.Item (Item (..))
import Hakyll.Images.Common
  ( Image (..),
    ImageContent,
    ImageFormat (..),
    WithMetadata (..),
    withImageContent,
  )
import Numeric.Natural (Natural)

-- | Jpeg encoding quality, from 0 (lower quality) to 100 (best quality).
-- @since 1.2.0
newtype JpgQuality = JpgQuality Natural
  deriving (Integer -> JpgQuality
JpgQuality -> JpgQuality
JpgQuality -> JpgQuality -> JpgQuality
(JpgQuality -> JpgQuality -> JpgQuality)
-> (JpgQuality -> JpgQuality -> JpgQuality)
-> (JpgQuality -> JpgQuality -> JpgQuality)
-> (JpgQuality -> JpgQuality)
-> (JpgQuality -> JpgQuality)
-> (JpgQuality -> JpgQuality)
-> (Integer -> JpgQuality)
-> Num JpgQuality
forall a.
(a -> a -> a)
-> (a -> a -> a)
-> (a -> a -> a)
-> (a -> a)
-> (a -> a)
-> (a -> a)
-> (Integer -> a)
-> Num a
$c+ :: JpgQuality -> JpgQuality -> JpgQuality
+ :: JpgQuality -> JpgQuality -> JpgQuality
$c- :: JpgQuality -> JpgQuality -> JpgQuality
- :: JpgQuality -> JpgQuality -> JpgQuality
$c* :: JpgQuality -> JpgQuality -> JpgQuality
* :: JpgQuality -> JpgQuality -> JpgQuality
$cnegate :: JpgQuality -> JpgQuality
negate :: JpgQuality -> JpgQuality
$cabs :: JpgQuality -> JpgQuality
abs :: JpgQuality -> JpgQuality
$csignum :: JpgQuality -> JpgQuality
signum :: JpgQuality -> JpgQuality
$cfromInteger :: Integer -> JpgQuality
fromInteger :: Integer -> JpgQuality
Num, JpgQuality -> JpgQuality -> Bool
(JpgQuality -> JpgQuality -> Bool)
-> (JpgQuality -> JpgQuality -> Bool) -> Eq JpgQuality
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: JpgQuality -> JpgQuality -> Bool
== :: JpgQuality -> JpgQuality -> Bool
$c/= :: JpgQuality -> JpgQuality -> Bool
/= :: JpgQuality -> JpgQuality -> Bool
Eq, Int -> JpgQuality
JpgQuality -> Int
JpgQuality -> [JpgQuality]
JpgQuality -> JpgQuality
JpgQuality -> JpgQuality -> [JpgQuality]
JpgQuality -> JpgQuality -> JpgQuality -> [JpgQuality]
(JpgQuality -> JpgQuality)
-> (JpgQuality -> JpgQuality)
-> (Int -> JpgQuality)
-> (JpgQuality -> Int)
-> (JpgQuality -> [JpgQuality])
-> (JpgQuality -> JpgQuality -> [JpgQuality])
-> (JpgQuality -> JpgQuality -> [JpgQuality])
-> (JpgQuality -> JpgQuality -> JpgQuality -> [JpgQuality])
-> Enum JpgQuality
forall a.
(a -> a)
-> (a -> a)
-> (Int -> a)
-> (a -> Int)
-> (a -> [a])
-> (a -> a -> [a])
-> (a -> a -> [a])
-> (a -> a -> a -> [a])
-> Enum a
$csucc :: JpgQuality -> JpgQuality
succ :: JpgQuality -> JpgQuality
$cpred :: JpgQuality -> JpgQuality
pred :: JpgQuality -> JpgQuality
$ctoEnum :: Int -> JpgQuality
toEnum :: Int -> JpgQuality
$cfromEnum :: JpgQuality -> Int
fromEnum :: JpgQuality -> Int
$cenumFrom :: JpgQuality -> [JpgQuality]
enumFrom :: JpgQuality -> [JpgQuality]
$cenumFromThen :: JpgQuality -> JpgQuality -> [JpgQuality]
enumFromThen :: JpgQuality -> JpgQuality -> [JpgQuality]
$cenumFromTo :: JpgQuality -> JpgQuality -> [JpgQuality]
enumFromTo :: JpgQuality -> JpgQuality -> [JpgQuality]
$cenumFromThenTo :: JpgQuality -> JpgQuality -> JpgQuality -> [JpgQuality]
enumFromThenTo :: JpgQuality -> JpgQuality -> JpgQuality -> [JpgQuality]
Enum, Eq JpgQuality
Eq JpgQuality =>
(JpgQuality -> JpgQuality -> Ordering)
-> (JpgQuality -> JpgQuality -> Bool)
-> (JpgQuality -> JpgQuality -> Bool)
-> (JpgQuality -> JpgQuality -> Bool)
-> (JpgQuality -> JpgQuality -> Bool)
-> (JpgQuality -> JpgQuality -> JpgQuality)
-> (JpgQuality -> JpgQuality -> JpgQuality)
-> Ord JpgQuality
JpgQuality -> JpgQuality -> Bool
JpgQuality -> JpgQuality -> Ordering
JpgQuality -> JpgQuality -> JpgQuality
forall a.
Eq a =>
(a -> a -> Ordering)
-> (a -> a -> Bool)
-> (a -> a -> Bool)
-> (a -> a -> Bool)
-> (a -> a -> Bool)
-> (a -> a -> a)
-> (a -> a -> a)
-> Ord a
$ccompare :: JpgQuality -> JpgQuality -> Ordering
compare :: JpgQuality -> JpgQuality -> Ordering
$c< :: JpgQuality -> JpgQuality -> Bool
< :: JpgQuality -> JpgQuality -> Bool
$c<= :: JpgQuality -> JpgQuality -> Bool
<= :: JpgQuality -> JpgQuality -> Bool
$c> :: JpgQuality -> JpgQuality -> Bool
> :: JpgQuality -> JpgQuality -> Bool
$c>= :: JpgQuality -> JpgQuality -> Bool
>= :: JpgQuality -> JpgQuality -> Bool
$cmax :: JpgQuality -> JpgQuality -> JpgQuality
max :: JpgQuality -> JpgQuality -> JpgQuality
$cmin :: JpgQuality -> JpgQuality -> JpgQuality
min :: JpgQuality -> JpgQuality -> JpgQuality
Ord, Num JpgQuality
Ord JpgQuality
(Num JpgQuality, Ord JpgQuality) =>
(JpgQuality -> Rational) -> Real JpgQuality
JpgQuality -> Rational
forall a. (Num a, Ord a) => (a -> Rational) -> Real a
$ctoRational :: JpgQuality -> Rational
toRational :: JpgQuality -> Rational
Real, Enum JpgQuality
Real JpgQuality
(Real JpgQuality, Enum JpgQuality) =>
(JpgQuality -> JpgQuality -> JpgQuality)
-> (JpgQuality -> JpgQuality -> JpgQuality)
-> (JpgQuality -> JpgQuality -> JpgQuality)
-> (JpgQuality -> JpgQuality -> JpgQuality)
-> (JpgQuality -> JpgQuality -> (JpgQuality, JpgQuality))
-> (JpgQuality -> JpgQuality -> (JpgQuality, JpgQuality))
-> (JpgQuality -> Integer)
-> Integral JpgQuality
JpgQuality -> Integer
JpgQuality -> JpgQuality -> (JpgQuality, JpgQuality)
JpgQuality -> JpgQuality -> JpgQuality
forall a.
(Real a, Enum a) =>
(a -> a -> a)
-> (a -> a -> a)
-> (a -> a -> a)
-> (a -> a -> a)
-> (a -> a -> (a, a))
-> (a -> a -> (a, a))
-> (a -> Integer)
-> Integral a
$cquot :: JpgQuality -> JpgQuality -> JpgQuality
quot :: JpgQuality -> JpgQuality -> JpgQuality
$crem :: JpgQuality -> JpgQuality -> JpgQuality
rem :: JpgQuality -> JpgQuality -> JpgQuality
$cdiv :: JpgQuality -> JpgQuality -> JpgQuality
div :: JpgQuality -> JpgQuality -> JpgQuality
$cmod :: JpgQuality -> JpgQuality -> JpgQuality
mod :: JpgQuality -> JpgQuality -> JpgQuality
$cquotRem :: JpgQuality -> JpgQuality -> (JpgQuality, JpgQuality)
quotRem :: JpgQuality -> JpgQuality -> (JpgQuality, JpgQuality)
$cdivMod :: JpgQuality -> JpgQuality -> (JpgQuality, JpgQuality)
divMod :: JpgQuality -> JpgQuality -> (JpgQuality, JpgQuality)
$ctoInteger :: JpgQuality -> Integer
toInteger :: JpgQuality -> Integer
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 :: forall a. Integral a => a -> JpgQuality
mkJpgQuality a
q
  | a
q a -> a -> Bool
forall a. Ord a => a -> a -> Bool
< a
0 = Natural -> JpgQuality
JpgQuality Natural
0
  | a
q a -> a -> Bool
forall a. Ord a => a -> a -> Bool
> a
100 = Natural -> JpgQuality
JpgQuality Natural
100
  | Bool
otherwise = Natural -> JpgQuality
JpgQuality (a -> Natural
forall a b. (Integral a, Num b) => a -> b
fromIntegral a
q)

-- | 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.
--
-- @
-- match "*.jpg" $ do
--    route idRoute
--    compile $ loadImage >>= compressJpgCompiler 50
-- @
compressJpgCompiler :: (Integral a) => a -> Item Image -> Compiler (Item Image)
compressJpgCompiler :: forall a. Integral a => a -> Item Image -> Compiler (Item Image)
compressJpgCompiler a
quality =
  Item Image -> Compiler (Item Image)
forall a. a -> Compiler a
forall (m :: * -> *) a. Monad m => a -> m a
return (Item Image -> Compiler (Item Image))
-> (Item Image -> Item Image)
-> Item Image
-> Compiler (Item Image)
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (Image -> Image) -> Item Image -> Item Image
forall a b. (a -> b) -> Item a -> Item b
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap ((ImageContent -> ImageContent)
-> (ImageFormat -> ImageContent -> Image) -> Image -> Image
withImageContent ImageContent -> ImageContent
forall a. a -> a
id ImageFormat -> ImageContent -> Image
encoder) -- JPG compression isn't a transformation of the image, but rather a re-encoding
  where
    validatedQuality :: Int
    validatedQuality :: Int
validatedQuality = JpgQuality -> Int
forall a b. (Integral a, Num b) => a -> b
fromIntegral (JpgQuality -> Int) -> JpgQuality -> Int
forall a b. (a -> b) -> a -> b
$ a -> JpgQuality
forall a. Integral a => a -> JpgQuality
mkJpgQuality a
quality

    encoder :: ImageFormat -> ImageContent -> Image
    encoder :: ImageFormat -> ImageContent -> Image
encoder ImageFormat
_ (MkWithMetadata DynamicImage
d Metadatas
md) =
      ImageFormat -> ByteString -> Image
Image ImageFormat
Jpeg (ByteString -> ByteString
toStrict (ByteString -> ByteString) -> ByteString -> ByteString
forall a b. (a -> b) -> a -> b
$ Int -> Metadatas -> DynamicImage -> ByteString
imageToJpgWithMetadata Int
validatedQuality Metadatas
md DynamicImage
d)