aboutsummaryrefslogtreecommitdiff
path: root/compiler/src/Processors.hs
diff options
context:
space:
mode:
Diffstat (limited to 'compiler/src/Processors.hs')
-rw-r--r--compiler/src/Processors.hs201
1 files changed, 201 insertions, 0 deletions
diff --git a/compiler/src/Processors.hs b/compiler/src/Processors.hs
new file mode 100644
index 0000000..159a425
--- /dev/null
+++ b/compiler/src/Processors.hs
@@ -0,0 +1,201 @@
1-- ldgallery - A static generator which turns a collection of tagged
2-- pictures into a searchable web gallery.
3--
4-- Copyright (C) 2019-2020 Pacien TRAN-GIRARD
5--
6-- This program is free software: you can redistribute it and/or modify
7-- it under the terms of the GNU Affero General Public License as
8-- published by the Free Software Foundation, either version 3 of the
9-- License, or (at your option) any later version.
10--
11-- This program is distributed in the hope that it will be useful,
12-- but WITHOUT ANY WARRANTY; without even the implied warranty of
13-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14-- GNU Affero General Public License for more details.
15--
16-- You should have received a copy of the GNU Affero General Public License
17-- along with this program. If not, see <https://www.gnu.org/licenses/>.
18
19module Processors
20 ( Resolution(..)
21 , ItemFileProcessor, itemFileProcessor
22 , ThumbnailFileProcessor, thumbnailFileProcessor
23 , skipCached, withCached
24 ) where
25
26
27import Control.Exception (Exception, throwIO)
28import Data.Function ((&))
29import Data.Ratio ((%))
30import Data.Char (toLower)
31
32import System.Directory hiding (copyFile)
33import qualified System.Directory
34import System.FilePath
35
36import Codec.Picture
37import Codec.Picture.Extra -- TODO: compare DCT and bilinear (and Lanczos, but it's not implemented)
38
39import Resource
40 ( ItemProcessor, ThumbnailProcessor
41 , GalleryItemProps(..), Resolution(..) )
42
43import Files
44
45
46data ProcessingException = ProcessingException FilePath String deriving Show
47instance Exception ProcessingException
48
49
50data PictureFileFormat = Bmp | Jpg | Png | Tiff | Hdr | Gif
51
52-- TODO: handle video, music, text...
53data Format = PictureFormat PictureFileFormat | Unknown
54
55formatFromPath :: Path -> Format
56formatFromPath =
57 maybe Unknown fromExt
58 . fmap (map toLower)
59 . fmap takeExtension
60 . fileName
61 where
62 fromExt :: String -> Format
63 fromExt ".bmp" = PictureFormat Bmp
64 fromExt ".jpg" = PictureFormat Jpg
65 fromExt ".jpeg" = PictureFormat Jpg
66 fromExt ".png" = PictureFormat Png
67 fromExt ".tiff" = PictureFormat Tiff
68 fromExt ".hdr" = PictureFormat Hdr
69 fromExt ".gif" = PictureFormat Gif
70 fromExt _ = Unknown
71
72
73type FileProcessor =
74 FileName -- ^ Input path
75 -> FileName -- ^ Output path
76 -> IO ()
77
78copyFileProcessor :: FileProcessor
79copyFileProcessor inputPath outputPath =
80 (putStrLn $ "Copying:\t" ++ outputPath)
81 >> ensureParentDir (flip System.Directory.copyFile) outputPath inputPath
82
83resizeStaticImageUpTo :: PictureFileFormat -> Resolution -> FileProcessor
84resizeStaticImageUpTo Bmp = resizeStaticGeneric readBitmap saveBmpImage
85-- TODO: parameterise export quality for jpg
86resizeStaticImageUpTo Jpg = resizeStaticGeneric readJpeg (saveJpgImage 80)
87resizeStaticImageUpTo Png = resizeStaticGeneric readPng savePngImage
88resizeStaticImageUpTo Tiff = resizeStaticGeneric readTiff saveTiffImage
89resizeStaticImageUpTo Hdr = resizeStaticGeneric readHDR saveRadianceImage
90resizeStaticImageUpTo Gif = resizeStaticGeneric readGif saveGifImage'
91 where
92 saveGifImage' :: StaticImageWriter
93 saveGifImage' outputPath image =
94 saveGifImage outputPath image
95 & either (throwIO . ProcessingException outputPath) id
96
97
98type StaticImageReader = FilePath -> IO (Either String DynamicImage)
99type StaticImageWriter = FilePath -> DynamicImage -> IO ()
100
101resizeStaticGeneric :: StaticImageReader -> StaticImageWriter -> Resolution -> FileProcessor
102resizeStaticGeneric reader writer maxRes inputPath outputPath =
103 (putStrLn $ "Generating:\t" ++ outputPath)
104 >> reader inputPath
105 >>= either (throwIO . ProcessingException inputPath) return
106 >>= return . (fitDynamicImage maxRes)
107 >>= ensureParentDir writer outputPath
108
109fitDynamicImage :: Resolution -> DynamicImage -> DynamicImage
110fitDynamicImage (Resolution boxWidth boxHeight) image =
111 convertRGBA8 image
112 & scaleBilinear targetWidth targetHeight
113 & ImageRGBA8
114 where
115 picWidth = dynamicMap imageWidth image
116 picHeight = dynamicMap imageHeight image
117 resizeRatio = min (boxWidth % picWidth) (boxHeight % picHeight)
118 targetWidth = floor $ resizeRatio * (picWidth % 1)
119 targetHeight = floor $ resizeRatio * (picHeight % 1)
120
121
122type Cache = FileProcessor -> FileProcessor
123
124skipCached :: Cache
125skipCached processor inputPath outputPath =
126 removePathForcibly outputPath
127 >> processor inputPath outputPath
128
129withCached :: Cache
130withCached processor inputPath outputPath =
131 do
132 isDir <- doesDirectoryExist outputPath
133 if isDir then removePathForcibly outputPath else noop
134
135 fileExists <- doesFileExist outputPath
136 if fileExists then
137 do
138 needUpdate <- isOutdated True inputPath outputPath
139 if needUpdate then update else skip
140 else
141 update
142
143 where
144 noop = return ()
145 update = processor inputPath outputPath
146 skip = putStrLn $ "Skipping:\t" ++ outputPath
147
148
149type ItemFileProcessor =
150 FileName -- ^ Input base path
151 -> FileName -- ^ Output base path
152 -> FileName -- ^ Output class (subdir)
153 -> ItemProcessor
154
155itemFileProcessor :: Maybe Resolution -> Cache -> ItemFileProcessor
156itemFileProcessor maxResolution cached inputBase outputBase resClass inputRes =
157 cached processor inPath outPath
158 >> return (props relOutPath)
159 where
160 relOutPath = resClass /> inputRes
161 inPath = localPath $ inputBase /> inputRes
162 outPath = localPath $ outputBase /> relOutPath
163 (processor, props) = processorFor maxResolution $ formatFromPath inputRes
164
165 processorFor :: Maybe Resolution -> Format -> (FileProcessor, Path -> GalleryItemProps)
166 processorFor Nothing _ =
167 (copyFileProcessor, Other)
168 processorFor _ (PictureFormat Gif) =
169 (copyFileProcessor, Picture) -- TODO: handle animated gif resizing
170 processorFor (Just maxRes) (PictureFormat picFormat) =
171 (resizeStaticImageUpTo picFormat maxRes, Picture)
172 processorFor _ Unknown =
173 (copyFileProcessor, Other) -- TODO: handle video reencoding and others?
174
175
176type ThumbnailFileProcessor =
177 FileName -- ^ Input base path
178 -> FileName -- ^ Output base path
179 -> FileName -- ^ Output class (subdir)
180 -> ThumbnailProcessor
181
182thumbnailFileProcessor :: Resolution -> Cache -> ThumbnailFileProcessor
183thumbnailFileProcessor maxRes cached inputBase outputBase resClass inputRes =
184 cached <$> processorFor (formatFromPath inputRes)
185 & process
186 where
187 relOutPath = resClass /> inputRes
188 inPath = localPath $ inputBase /> inputRes
189 outPath = localPath $ outputBase /> relOutPath
190
191 process :: Maybe FileProcessor -> IO (Maybe Path)
192 process Nothing = return Nothing
193 process (Just proc) =
194 proc inPath outPath
195 >> return (Just relOutPath)
196
197 processorFor :: Format -> Maybe FileProcessor
198 processorFor (PictureFormat picFormat) =
199 Just $ resizeStaticImageUpTo picFormat maxRes
200 processorFor _ =
201 Nothing