-
Notifications
You must be signed in to change notification settings - Fork 57
Expand file tree
/
Copy pathTypes.hs
More file actions
2643 lines (2238 loc) · 102 KB
/
Types.hs
File metadata and controls
2643 lines (2238 loc) · 102 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
-- | Module provides basic types for image manipulation in the library.
{-# LANGUAGE BangPatterns #-}
{-# LANGUAGE CPP #-}
{-# LANGUAGE DeriveDataTypeable #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE FunctionalDependencies #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE Rank2Types #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE TypeSynonymInstances #-}
{-# LANGUAGE UndecidableInstances #-}
-- Defined types are used to store all of those __Juicy Pixels__
module Codec.Picture.Types( -- * Types
-- ** Image types
Image( .. )
, MutableImage( .. )
, DynamicImage( .. )
, PalettedImage( .. )
, Palette
, Palette'( .. )
, paletteSize
-- ** Image functions
, createMutableImage
, newMutableImage
, freezeImage
, unsafeFreezeImage
, thawImage
, unsafeThawImage
-- ** Image Lenses
, Traversal
, imagePixels
, imageIPixels
-- ** Pixel types
, Pixel8
, Pixel16
, Pixel32
, PixelF
, PixelYA8( .. )
, PixelYA16( .. )
, PixelRGB8( .. )
, PixelRGB16( .. )
, PixelRGBF( .. )
, PixelRGBA8( .. )
, PixelRGBA16( .. )
, PixelCMYK8( .. )
, PixelCMYK16( .. )
, PixelYCbCr8( .. )
, PixelYCbCrK8( .. )
-- * Type classes
, ColorConvertible( .. )
, Pixel(..)
-- $graph
, ColorSpaceConvertible( .. )
, LumaPlaneExtractable( .. )
, TransparentPixel( .. )
-- * Helper functions
, pixelMap
, pixelMapXY
, pixelFold
, pixelFoldM
, pixelFoldMap
, dynamicMap
, dynamicPixelMap
, palettedToTrueColor
, palettedAsImage
, dropAlphaLayer
, withImage
, zipPixelComponent3
, generateImage
, generateFoldImage
, gammaCorrection
, toneMapping
-- * Color plane extraction
, ColorPlane ( )
, PlaneRed( .. )
, PlaneGreen( .. )
, PlaneBlue( .. )
, PlaneAlpha( .. )
, PlaneLuma( .. )
, PlaneCr( .. )
, PlaneCb( .. )
, PlaneCyan( .. )
, PlaneMagenta( .. )
, PlaneYellow( .. )
, PlaneBlack( .. )
, extractComponent
, unsafeExtractComponent
-- * Packeable writing (unsafe but faster)
, PackeablePixel( .. )
, fillImageWith
, readPackedPixelAt
, writePackedPixelAt
, unsafeWritePixelBetweenAt
) where
#if !MIN_VERSION_base(4,8,0)
import Data.Monoid( Monoid, mempty )
import Control.Applicative( Applicative, pure, (<*>), (<$>) )
#endif
#if !MIN_VERSION_base(4,11,0)
import Data.Monoid( (<>) )
#endif
import Control.Monad( foldM, liftM, ap )
import Control.DeepSeq( NFData( .. ) )
import Control.Monad.ST( ST, runST )
import Control.Monad.Primitive ( PrimMonad, PrimState )
import Foreign.ForeignPtr( castForeignPtr )
import Foreign.Storable ( Storable )
import Data.Bits( unsafeShiftL, unsafeShiftR, (.|.), (.&.) )
import Data.Typeable ( Typeable )
import Data.Word( Word8, Word16, Word32, Word64 )
import Data.Vector.Storable ( (!) )
import qualified Data.Vector.Storable as V
import qualified Data.Vector.Storable.Mutable as M
#include "ConvGraph.hs"
-- | The main type of this package, one that most
-- functions work on, is Image.
--
-- Parameterized by the underlying pixel format it
-- forms a rigid type. If you wish to store images
-- of different or unknown pixel formats use 'DynamicImage'.
--
-- Image is essentially a rectangular pixel buffer
-- of specified width and height. The coordinates are
-- assumed to start from the upper-left corner
-- of the image, with the horizontal position first
-- and vertical second.
data Image a = Image
{ -- | Width of the image in pixels
imageWidth :: {-# UNPACK #-} !Int
-- | Height of the image in pixels.
, imageHeight :: {-# UNPACK #-} !Int
-- | Image pixel data. To extract pixels at a given position
-- you should use the helper functions.
--
-- Internally pixel data is stored as consecutively packed
-- lines from top to bottom, scanned from left to right
-- within individual lines, from first to last color
-- component within each pixel.
, imageData :: V.Vector (PixelBaseComponent a)
}
deriving (Typeable)
instance (Eq (PixelBaseComponent a), Storable (PixelBaseComponent a))
=> Eq (Image a) where
a == b = imageWidth a == imageWidth b &&
imageHeight a == imageHeight b &&
imageData a == imageData b
-- | Type for the palette used in Gif & PNG files.
type Palette = Image PixelRGB8
-- | Class used to describle plane present in the pixel
-- type. If a pixel has a plane description associated,
-- you can use the plane name to extract planes independently.
class ColorPlane pixel planeToken where
-- | Retrieve the index of the component in the
-- given pixel type.
toComponentIndex :: pixel -> planeToken -> Int
-- | Define the plane for the red color component
data PlaneRed = PlaneRed
deriving (Typeable)
-- | Define the plane for the green color component
data PlaneGreen = PlaneGreen
deriving (Typeable)
-- | Define the plane for the blue color component
data PlaneBlue = PlaneBlue
deriving (Typeable)
-- | Define the plane for the alpha (transparency) component
data PlaneAlpha = PlaneAlpha
deriving (Typeable)
-- | Define the plane for the luma component
data PlaneLuma = PlaneLuma
deriving (Typeable)
-- | Define the plane for the Cr component
data PlaneCr = PlaneCr
deriving (Typeable)
-- | Define the plane for the Cb component
data PlaneCb = PlaneCb
deriving (Typeable)
-- | Define plane for the cyan component of the
-- CMYK color space.
data PlaneCyan = PlaneCyan
deriving (Typeable)
-- | Define plane for the magenta component of the
-- CMYK color space.
data PlaneMagenta = PlaneMagenta
deriving (Typeable)
-- | Define plane for the yellow component of the
-- CMYK color space.
data PlaneYellow = PlaneYellow
deriving (Typeable)
-- | Define plane for the black component of
-- the CMYK color space.
data PlaneBlack = PlaneBlack
deriving (Typeable)
-- | Extract a color plane from an image given a present plane in the image
-- examples:
--
-- @
-- extractRedPlane :: Image PixelRGB8 -> Image Pixel8
-- extractRedPlane = extractComponent PlaneRed
-- @
--
extractComponent :: forall px plane. ( Pixel px
, Pixel (PixelBaseComponent px)
, PixelBaseComponent (PixelBaseComponent px)
~ PixelBaseComponent px
, ColorPlane px plane )
=> plane -> Image px -> Image (PixelBaseComponent px)
extractComponent plane = unsafeExtractComponent idx
where idx = toComponentIndex (undefined :: px) plane
-- | Extract a plane of an image. Returns the requested color
-- component as a greyscale image.
--
-- If you ask for a component out of bound, the `error` function will
-- be called.
unsafeExtractComponent :: forall a
. ( Pixel a
, Pixel (PixelBaseComponent a)
, PixelBaseComponent (PixelBaseComponent a)
~ PixelBaseComponent a)
=> Int -- ^ The component index, beginning at 0 ending at (componentCount - 1)
-> Image a -- ^ Source image
-> Image (PixelBaseComponent a)
unsafeExtractComponent comp img@(Image { imageWidth = w, imageHeight = h })
| comp >= padd = error $ "extractComponent : invalid component index ("
++ show comp ++ ", max:" ++ show padd ++ ")"
| otherwise = Image { imageWidth = w, imageHeight = h, imageData = plane }
where plane = stride img padd comp
padd = componentCount (undefined :: a)
-- | For any image with an alpha component (transparency),
-- drop it, returning a pure opaque image.
dropAlphaLayer :: (TransparentPixel a b) => Image a -> Image b
dropAlphaLayer = pixelMap dropTransparency
-- | Class modeling transparent pixel, should provide a method
-- to combine transparent pixels
class (Pixel a, Pixel b) => TransparentPixel a b | a -> b where
-- | Just return the opaque pixel value
dropTransparency :: a -> b
-- | access the transparency (alpha layer) of a given
-- transparent pixel type.
getTransparency :: a -> PixelBaseComponent a
{-# DEPRECATED getTransparency "please use 'pixelOpacity' instead" #-}
instance TransparentPixel PixelRGBA8 PixelRGB8 where
{-# INLINE dropTransparency #-}
dropTransparency (PixelRGBA8 r g b _) = PixelRGB8 r g b
{-# INLINE getTransparency #-}
getTransparency (PixelRGBA8 _ _ _ a) = a
lineFold :: (Monad m) => a -> Int -> (a -> Int -> m a) -> m a
{-# INLINE lineFold #-}
lineFold initial count f = go 0 initial
where go n acc | n >= count = return acc
go n acc = f acc n >>= go (n + 1)
stride :: (Storable (PixelBaseComponent a))
=> Image a -> Int -> Int -> V.Vector (PixelBaseComponent a)
stride Image { imageWidth = w, imageHeight = h, imageData = array }
padd firstComponent = runST $ do
let cell_count = w * h
outArray <- M.new cell_count
let go writeIndex _ | writeIndex >= cell_count = return ()
go writeIndex readIndex = do
(outArray `M.unsafeWrite` writeIndex) $ array `V.unsafeIndex` readIndex
go (writeIndex + 1) $ readIndex + padd
go 0 firstComponent
V.unsafeFreeze outArray
instance NFData (Image a) where
rnf (Image width height dat) = width `seq`
height `seq`
dat `seq`
()
-- | Image or pixel buffer, the coordinates are assumed to start
-- from the upper-left corner of the image, with the horizontal
-- position first, then the vertical one. The image can be transformed in place.
data MutableImage s a = MutableImage
{ -- | Width of the image in pixels
mutableImageWidth :: {-# UNPACK #-} !Int
-- | Height of the image in pixels.
, mutableImageHeight :: {-# UNPACK #-} !Int
-- | The real image, to extract pixels at some position
-- you should use the helpers functions.
, mutableImageData :: M.STVector s (PixelBaseComponent a)
}
deriving (Typeable)
-- | `O(n)` Yield an immutable copy of an image by making a copy of it
freezeImage :: (Storable (PixelBaseComponent px), PrimMonad m)
=> MutableImage (PrimState m) px -> m (Image px)
freezeImage (MutableImage w h d) = Image w h `liftM` V.freeze d
-- | `O(n)` Yield a mutable copy of an image by making a copy of it.
thawImage :: (Storable (PixelBaseComponent px), PrimMonad m)
=> Image px -> m (MutableImage (PrimState m) px)
thawImage (Image w h d) = MutableImage w h `liftM` V.thaw d
-- | `O(1)` Unsafe convert an imutable image to an mutable one without copying.
-- The source image shouldn't be used after this operation.
unsafeThawImage :: (Storable (PixelBaseComponent px), PrimMonad m)
=> Image px -> m (MutableImage (PrimState m) px)
{-# NOINLINE unsafeThawImage #-}
unsafeThawImage (Image w h d) = MutableImage w h `liftM` V.unsafeThaw d
-- | `O(1)` Unsafe convert a mutable image to an immutable one without copying.
-- The mutable image may not be used after this operation.
unsafeFreezeImage :: (Storable (PixelBaseComponent a), PrimMonad m)
=> MutableImage (PrimState m) a -> m (Image a)
unsafeFreezeImage (MutableImage w h d) = Image w h `liftM` V.unsafeFreeze d
-- | Create a mutable image, filled with the given background color.
createMutableImage :: (Pixel px, PrimMonad m)
=> Int -- ^ Width
-> Int -- ^ Height
-> px -- ^ Background color
-> m (MutableImage (PrimState m) px)
createMutableImage width height background =
generateMutableImage (\_ _ -> background) width height
-- | Create a mutable image with garbage as content. All data
-- is uninitialized.
newMutableImage :: forall px m. (Pixel px, PrimMonad m)
=> Int -- ^ Width
-> Int -- ^ Height
-> m (MutableImage (PrimState m) px)
newMutableImage w h = MutableImage w h `liftM` M.new (w * h * compCount)
where compCount = componentCount (undefined :: px)
instance NFData (MutableImage s a) where
rnf (MutableImage width height dat) = width `seq`
height `seq`
dat `seq`
()
-- | Image type enumerating all predefined pixel types.
-- It enables loading and use of images of different
-- pixel types.
data DynamicImage =
-- | A greyscale image.
ImageY8 (Image Pixel8)
-- | A greyscale image with 16bit components
| ImageY16 (Image Pixel16)
-- | A greyscale image with 32bit components
| ImageY32 (Image Pixel32)
-- | A greyscale HDR image
| ImageYF (Image PixelF)
-- | An image in greyscale with an alpha channel.
| ImageYA8 (Image PixelYA8)
-- | An image in greyscale with alpha channel on 16 bits.
| ImageYA16 (Image PixelYA16)
-- | An image in true color.
| ImageRGB8 (Image PixelRGB8)
-- | An image in true color with 16bit depth.
| ImageRGB16 (Image PixelRGB16)
-- | An image with HDR pixels
| ImageRGBF (Image PixelRGBF)
-- | An image in true color and an alpha channel.
| ImageRGBA8 (Image PixelRGBA8)
-- | A true color image with alpha on 16 bits.
| ImageRGBA16 (Image PixelRGBA16)
-- | An image in the colorspace used by Jpeg images.
| ImageYCbCr8 (Image PixelYCbCr8)
-- | An image in the colorspace CMYK
| ImageCMYK8 (Image PixelCMYK8)
-- | An image in the colorspace CMYK and 16 bits precision
| ImageCMYK16 (Image PixelCMYK16)
deriving (Eq, Typeable)
-- | Type used to expose a palette extracted during reading.
-- Use palettedAsImage to convert it to a palette usable for
-- writing.
newtype Palette' px = Palette'
{ -- | Real data used by the palette.
_paletteData :: V.Vector (PixelBaseComponent px)
}
deriving Typeable
-- | Size of pallete in pixels
paletteSize :: forall px . Pixel px => Palette' px -> Int
paletteSize = (`div` componentCount (undefined :: px)) . V.length . _paletteData
-- | Convert a palette to an image. Used mainly for
-- backward compatibility.
palettedAsImage :: Pixel px => Palette' px -> Image px
palettedAsImage p = Image (paletteSize p) 1 $ _paletteData p
-- | Describe an image and it's potential associated
-- palette. If no palette is present, fallback to a
-- DynamicImage
data PalettedImage
= TrueColorImage DynamicImage -- ^ Fallback
| PalettedY8 (Image Pixel8) (Palette' Pixel8)
| PalettedRGB8 (Image Pixel8) (Palette' PixelRGB8)
| PalettedRGBA8 (Image Pixel8) (Palette' PixelRGBA8)
| PalettedRGB16 (Image Pixel8) (Palette' PixelRGB16)
deriving (Typeable)
-- | Flatten a PalettedImage to a DynamicImage
palettedToTrueColor :: PalettedImage -> DynamicImage
palettedToTrueColor img = case img of
TrueColorImage d -> d
PalettedY8 i p -> ImageY8 $ toTrueColor 1 (_paletteData p) i
PalettedRGB8 i p -> ImageRGB8 $ toTrueColor 3 (_paletteData p) i
PalettedRGBA8 i p -> ImageRGBA8 $ toTrueColor 4 (_paletteData p) i
PalettedRGB16 i p -> ImageRGB16 $ toTrueColor 3 (_paletteData p) i
where
toTrueColor c vec = pixelMap (unsafePixelAt vec . (c *) . fromIntegral)
-- | Helper function to help extract information from dynamic
-- image. To get the width of a dynamic image, you can use
-- the following snippet:
--
-- > dynWidth :: DynamicImage -> Int
-- > dynWidth img = dynamicMap imageWidth img
--
dynamicMap :: (forall pixel . (Pixel pixel) => Image pixel -> a)
-> DynamicImage -> a
dynamicMap f (ImageY8 i) = f i
dynamicMap f (ImageY16 i) = f i
dynamicMap f (ImageY32 i) = f i
dynamicMap f (ImageYF i) = f i
dynamicMap f (ImageYA8 i) = f i
dynamicMap f (ImageYA16 i) = f i
dynamicMap f (ImageRGB8 i) = f i
dynamicMap f (ImageRGB16 i) = f i
dynamicMap f (ImageRGBF i) = f i
dynamicMap f (ImageRGBA8 i) = f i
dynamicMap f (ImageRGBA16 i) = f i
dynamicMap f (ImageYCbCr8 i) = f i
dynamicMap f (ImageCMYK8 i) = f i
dynamicMap f (ImageCMYK16 i) = f i
-- | Equivalent of the `pixelMap` function for the dynamic images.
-- You can perform pixel colorspace independant operations with this
-- function.
--
-- For instance, if you want to extract a square crop of any image,
-- without caring about colorspace, you can use the following snippet.
--
-- > dynSquare :: DynamicImage -> DynamicImage
-- > dynSquare = dynamicPixelMap squareImage
-- >
-- > squareImage :: Pixel a => Image a -> Image a
-- > squareImage img = generateImage (\x y -> pixelAt img x y) edge edge
-- > where edge = min (imageWidth img) (imageHeight img)
--
dynamicPixelMap :: (forall pixel . (Pixel pixel) => Image pixel -> Image pixel)
-> DynamicImage -> DynamicImage
dynamicPixelMap f = aux
where
aux (ImageY8 i) = ImageY8 (f i)
aux (ImageY16 i) = ImageY16 (f i)
aux (ImageY32 i) = ImageY32 (f i)
aux (ImageYF i) = ImageYF (f i)
aux (ImageYA8 i) = ImageYA8 (f i)
aux (ImageYA16 i) = ImageYA16 (f i)
aux (ImageRGB8 i) = ImageRGB8 (f i)
aux (ImageRGB16 i) = ImageRGB16 (f i)
aux (ImageRGBF i) = ImageRGBF (f i)
aux (ImageRGBA8 i) = ImageRGBA8 (f i)
aux (ImageRGBA16 i) = ImageRGBA16 (f i)
aux (ImageYCbCr8 i) = ImageYCbCr8 (f i)
aux (ImageCMYK8 i) = ImageCMYK8 (f i)
aux (ImageCMYK16 i) = ImageCMYK16 (f i)
instance NFData DynamicImage where
rnf (ImageY8 img) = rnf img
rnf (ImageY16 img) = rnf img
rnf (ImageY32 img) = rnf img
rnf (ImageYF img) = rnf img
rnf (ImageYA8 img) = rnf img
rnf (ImageYA16 img) = rnf img
rnf (ImageRGB8 img) = rnf img
rnf (ImageRGB16 img) = rnf img
rnf (ImageRGBF img) = rnf img
rnf (ImageRGBA8 img) = rnf img
rnf (ImageRGBA16 img) = rnf img
rnf (ImageYCbCr8 img) = rnf img
rnf (ImageCMYK8 img) = rnf img
rnf (ImageCMYK16 img) = rnf img
-- | Type alias for 8bit greyscale pixels. For simplicity,
-- greyscale pixels use plain numbers instead of a separate type.
type Pixel8 = Word8
-- | Type alias for 16bit greyscale pixels.
type Pixel16 = Word16
-- | Type alias for 32bit greyscale pixels.
type Pixel32 = Word32
-- | Type alias for 32bit floating point greyscale pixels. The standard
-- bounded value range is mapped to the closed interval [0,1] i.e.
--
-- > map promotePixel [0, 1 .. 255 :: Pixel8] == [0/255, 1/255 .. 1.0 :: PixelF]
type PixelF = Float
-- | Pixel type storing 8bit Luminance (Y) and alpha (A) information.
-- Values are stored in the following order:
--
-- * Luminance
--
-- * Alpha
--
data PixelYA8 = PixelYA8 {-# UNPACK #-} !Pixel8 -- Luminance
{-# UNPACK #-} !Pixel8 -- Alpha value
deriving (Eq, Ord, Show, Typeable)
-- | Pixel type storing 16bit Luminance (Y) and alpha (A) information.
-- Values are stored in the following order:
--
-- * Luminance
--
-- * Alpha
--
data PixelYA16 = PixelYA16 {-# UNPACK #-} !Pixel16 -- Luminance
{-# UNPACK #-} !Pixel16 -- Alpha value
deriving (Eq, Ord, Show, Typeable)
-- | Classic pixel type storing 8bit red, green and blue (RGB) information.
-- Values are stored in the following order:
--
-- * Red
--
-- * Green
--
-- * Blue
--
data PixelRGB8 = PixelRGB8 {-# UNPACK #-} !Pixel8 -- Red
{-# UNPACK #-} !Pixel8 -- Green
{-# UNPACK #-} !Pixel8 -- Blue
deriving (Eq, Ord, Show, Typeable)
-- | Pixel type storing value for the YCCK color space:
--
-- * Y (Luminance)
--
-- * Cb
--
-- * Cr
--
-- * Black
--
data PixelYCbCrK8 = PixelYCbCrK8 {-# UNPACK #-} !Pixel8
{-# UNPACK #-} !Pixel8
{-# UNPACK #-} !Pixel8
{-# UNPACK #-} !Pixel8
deriving (Eq, Ord, Show, Typeable)
-- | Pixel type storing 16bit red, green and blue (RGB) information.
-- Values are stored in the following order:
--
-- * Red
--
-- * Green
--
-- * Blue
--
data PixelRGB16 = PixelRGB16 {-# UNPACK #-} !Pixel16 -- Red
{-# UNPACK #-} !Pixel16 -- Green
{-# UNPACK #-} !Pixel16 -- Blue
deriving (Eq, Ord, Show, Typeable)
-- | HDR pixel type storing floating point 32bit red, green and blue (RGB) information.
-- Same value range and comments apply as for 'PixelF'.
-- Values are stored in the following order:
--
-- * Red
--
-- * Green
--
-- * Blue
--
data PixelRGBF = PixelRGBF {-# UNPACK #-} !PixelF -- Red
{-# UNPACK #-} !PixelF -- Green
{-# UNPACK #-} !PixelF -- Blue
deriving (Eq, Ord, Show, Typeable)
-- | Pixel type storing 8bit luminance, blue difference and red difference (YCbCr) information.
-- Values are stored in the following order:
--
-- * Y (luminance)
--
-- * Cb
--
-- * Cr
--
data PixelYCbCr8 = PixelYCbCr8 {-# UNPACK #-} !Pixel8 -- Y luminance
{-# UNPACK #-} !Pixel8 -- Cb blue difference
{-# UNPACK #-} !Pixel8 -- Cr red difference
deriving (Eq, Ord, Show, Typeable)
-- | Pixel type storing 8bit cyan, magenta, yellow and black (CMYK) information.
-- Values are stored in the following order:
--
-- * Cyan
--
-- * Magenta
--
-- * Yellow
--
-- * Black
--
data PixelCMYK8 = PixelCMYK8 {-# UNPACK #-} !Pixel8 -- Cyan
{-# UNPACK #-} !Pixel8 -- Magenta
{-# UNPACK #-} !Pixel8 -- Yellow
{-# UNPACK #-} !Pixel8 -- Black
deriving (Eq, Ord, Show, Typeable)
-- | Pixel type storing 16bit cyan, magenta, yellow and black (CMYK) information.
-- Values are stored in the following order:
--
-- * Cyan
--
-- * Magenta
--
-- * Yellow
--
-- * Black
--
data PixelCMYK16 = PixelCMYK16 {-# UNPACK #-} !Pixel16 -- Cyan
{-# UNPACK #-} !Pixel16 -- Magenta
{-# UNPACK #-} !Pixel16 -- Yellow
{-# UNPACK #-} !Pixel16 -- Black
deriving (Eq, Ord, Show, Typeable)
-- | Classical pixel type storing 8bit red, green, blue and alpha (RGBA) information.
-- Values are stored in the following order:
--
-- * Red
--
-- * Green
--
-- * Blue
--
-- * Alpha
--
data PixelRGBA8 = PixelRGBA8 {-# UNPACK #-} !Pixel8 -- Red
{-# UNPACK #-} !Pixel8 -- Green
{-# UNPACK #-} !Pixel8 -- Blue
{-# UNPACK #-} !Pixel8 -- Alpha
deriving (Eq, Ord, Show, Typeable)
-- | Pixel type storing 16bit red, green, blue and alpha (RGBA) information.
-- Values are stored in the following order:
--
-- * Red
--
-- * Green
--
-- * Blue
--
-- * Alpha
--
data PixelRGBA16 = PixelRGBA16 {-# UNPACK #-} !Pixel16 -- Red
{-# UNPACK #-} !Pixel16 -- Green
{-# UNPACK #-} !Pixel16 -- Blue
{-# UNPACK #-} !Pixel16 -- Alpha
deriving (Eq, Ord, Show, Typeable)
-- | Definition of pixels used in images. Each pixel has a color space, and a representative
-- component (Word8 or Float).
class ( Storable (PixelBaseComponent a)
, Num (PixelBaseComponent a), Eq a ) => Pixel a where
-- | Type of the pixel component, "classical" images
-- would have Word8 type as their PixelBaseComponent,
-- HDR image would have Float for instance
type PixelBaseComponent a :: *
-- | Call the function for every component of the pixels.
-- For example for RGB pixels mixWith is declared like this:
--
-- > mixWith f (PixelRGB8 ra ga ba) (PixelRGB8 rb gb bb) =
-- > PixelRGB8 (f 0 ra rb) (f 1 ga gb) (f 2 ba bb)
--
mixWith :: (Int -> PixelBaseComponent a -> PixelBaseComponent a -> PixelBaseComponent a)
-> a -> a -> a
-- | Extension of the `mixWith` which separate the treatment
-- of the color components of the alpha value (transparency component).
-- For pixel without alpha components, it is equivalent to mixWith.
--
-- > mixWithAlpha f fa (PixelRGBA8 ra ga ba aa) (PixelRGB8 rb gb bb ab) =
-- > PixelRGBA8 (f 0 ra rb) (f 1 ga gb) (f 2 ba bb) (fa aa ab)
--
mixWithAlpha :: (Int -> PixelBaseComponent a -> PixelBaseComponent a
-> PixelBaseComponent a) -- ^ Function for color component
-> (PixelBaseComponent a -> PixelBaseComponent a
-> PixelBaseComponent a) -- ^ Function for alpha component
-> a -> a -> a
{-# INLINE mixWithAlpha #-}
mixWithAlpha f _ = mixWith f
-- | Return the opacity of a pixel, if the pixel has an
-- alpha layer, return the alpha value. If the pixel
-- doesn't have an alpha value, return a value
-- representing the opaqueness.
pixelOpacity :: a -> PixelBaseComponent a
-- | Return the number of components of the pixel
componentCount :: a -> Int
-- | Apply a function to each component of a pixel.
-- If the color type possess an alpha (transparency channel),
-- it is treated like the other color components.
colorMap :: (PixelBaseComponent a -> PixelBaseComponent a) -> a -> a
-- | Calculate the index for the begining of the pixel
pixelBaseIndex :: Image a -> Int -> Int -> Int
pixelBaseIndex (Image { imageWidth = w }) x y =
(x + y * w) * componentCount (undefined :: a)
-- | Calculate theindex for the begining of the pixel at position x y
mutablePixelBaseIndex :: MutableImage s a -> Int -> Int -> Int
mutablePixelBaseIndex (MutableImage { mutableImageWidth = w }) x y =
(x + y * w) * componentCount (undefined :: a)
-- | Extract a pixel at a given position, (x, y), the origin
-- is assumed to be at the corner top left, positive y to the
-- bottom of the image
pixelAt :: Image a -> Int -> Int -> a
-- | Same as pixelAt but for mutable images.
readPixel :: PrimMonad m => MutableImage (PrimState m) a -> Int -> Int -> m a
-- | Write a pixel in a mutable image at position x y
writePixel :: PrimMonad m => MutableImage (PrimState m) a -> Int -> Int -> a -> m ()
-- | Unsafe version of pixelAt, read a pixel at the given
-- index without bound checking (if possible).
-- The index is expressed in number (PixelBaseComponent a)
unsafePixelAt :: V.Vector (PixelBaseComponent a) -> Int -> a
-- | Unsafe version of readPixel, read a pixel at the given
-- position without bound checking (if possible). The index
-- is expressed in number (PixelBaseComponent a)
unsafeReadPixel :: PrimMonad m => M.STVector (PrimState m) (PixelBaseComponent a) -> Int -> m a
-- | Unsafe version of writePixel, write a pixel at the
-- given position without bound checking. This can be _really_ unsafe.
-- The index is expressed in number (PixelBaseComponent a)
unsafeWritePixel :: PrimMonad m => M.STVector (PrimState m) (PixelBaseComponent a) -> Int -> a -> m ()
-- | Implement upcasting for pixel types.
-- Minimal declaration of `promotePixel`.
-- It is strongly recommended to overload promoteImage to keep
-- performance acceptable
class (Pixel a, Pixel b) => ColorConvertible a b where
-- | Convert a pixel type to another pixel type. This
-- operation should never lose any data.
promotePixel :: a -> b
-- | Change the underlying pixel type of an image by performing a full copy
-- of it.
promoteImage :: Image a -> Image b
promoteImage = pixelMap promotePixel
-- | This class abstract colorspace conversion. This
-- conversion can be lossy, which ColorConvertible cannot
class (Pixel a, Pixel b) => ColorSpaceConvertible a b where
-- | Pass a pixel from a colorspace (say RGB) to the second one
-- (say YCbCr)
convertPixel :: a -> b
-- | Helper function to convert a whole image by taking a
-- copy it.
convertImage :: Image a -> Image b
convertImage = pixelMap convertPixel
generateMutableImage :: forall m px. (Pixel px, PrimMonad m)
=> (Int -> Int -> px) -- ^ Generating function, with `x` and `y` params.
-> Int -- ^ Width in pixels
-> Int -- ^ Height in pixels
-> m (MutableImage (PrimState m) px)
{-# INLINE generateMutableImage #-}
generateMutableImage f w h = MutableImage w h `liftM` generated where
compCount = componentCount (undefined :: px)
generated = do
arr <- M.new (w * h * compCount)
let lineGenerator _ !y | y >= h = return ()
lineGenerator !lineIdx y = column lineIdx 0
where column !idx !x | x >= w = lineGenerator idx $ y + 1
column idx x = do
unsafeWritePixel arr idx $ f x y
column (idx + compCount) $ x + 1
lineGenerator 0 0
return arr
-- | Create an image given a function to generate pixels.
-- The function will receive values from 0 to width-1 for the x parameter
-- and 0 to height-1 for the y parameter. The coordinates 0,0 are the upper
-- left corner of the image, and (width-1, height-1) the lower right corner.
--
-- for example, to create a small gradient image:
--
-- > imageCreator :: String -> IO ()
-- > imageCreator path = writePng path $ generateImage pixelRenderer 250 300
-- > where pixelRenderer x y = PixelRGB8 (fromIntegral x) (fromIntegral y) 128
--
generateImage :: forall px. (Pixel px)
=> (Int -> Int -> px) -- ^ Generating function, with `x` and `y` params.
-> Int -- ^ Width in pixels
-> Int -- ^ Height in pixels
-> Image px
{-# INLINE generateImage #-}
generateImage f w h = runST img where
img :: ST s (Image px)
img = generateMutableImage f w h >>= unsafeFreezeImage
-- | Create an image using a monadic initializer function.
-- The function will receive values from 0 to width-1 for the x parameter
-- and 0 to height-1 for the y parameter. The coordinates 0,0 are the upper
-- left corner of the image, and (width-1, height-1) the lower right corner.
--
-- The function is called for each pixel in the line from left to right (0 to width - 1)
-- and for each line (0 to height - 1).
withImage :: forall m pixel. (Pixel pixel, PrimMonad m)
=> Int -- ^ Image width
-> Int -- ^ Image height
-> (Int -> Int -> m pixel) -- ^ Generating functions
-> m (Image pixel)
withImage width height pixelGenerator = do
let pixelComponentCount = componentCount (undefined :: pixel)
arr <- M.new (width * height * pixelComponentCount)
let mutImage = MutableImage
{ mutableImageWidth = width
, mutableImageHeight = height
, mutableImageData = arr
}
let pixelPositions = [(x, y) | y <- [0 .. height-1], x <- [0..width-1]]
sequence_ [pixelGenerator x y >>= unsafeWritePixel arr idx
| ((x,y), idx) <- zip pixelPositions [0, pixelComponentCount ..]]
unsafeFreezeImage mutImage
-- | Create an image given a function to generate pixels.
-- The function will receive values from 0 to width-1 for the x parameter
-- and 0 to height-1 for the y parameter. The coordinates 0,0 are the upper
-- left corner of the image, and (width-1, height-1) the lower right corner.
--
-- the acc parameter is a user defined one.
--
-- The function is called for each pixel in the line from left to right (0 to width - 1)
-- and for each line (0 to height - 1).
generateFoldImage :: forall a acc. (Pixel a)
=> (acc -> Int -> Int -> (acc, a)) -- ^ Function taking the state, x and y
-> acc -- ^ Initial state
-> Int -- ^ Width in pixels
-> Int -- ^ Height in pixels
-> (acc, Image a)
generateFoldImage f intialAcc w h =
(finalState, Image { imageWidth = w, imageHeight = h, imageData = generated })
where compCount = componentCount (undefined :: a)
(finalState, generated) = runST $ do
arr <- M.new (w * h * compCount)
let mutImage = MutableImage {
mutableImageWidth = w,
mutableImageHeight = h,
mutableImageData = arr }
foldResult <- foldM (\acc (x,y) -> do
let (acc', px) = f acc x y
writePixel mutImage x y px
return acc') intialAcc [(x,y) | y <- [0 .. h-1], x <- [0 .. w-1]]
frozen <- V.unsafeFreeze arr
return (foldResult, frozen)
-- | Fold over the pixel of an image with a raster scan order:
-- from top to bottom, left to right
{-# INLINE pixelFold #-}
pixelFold :: forall acc pixel. (Pixel pixel)
=> (acc -> Int -> Int -> pixel -> acc) -> acc -> Image pixel -> acc
pixelFold f initialAccumulator img@(Image { imageWidth = w, imageHeight = h }) =
columnFold 0 initialAccumulator 0
where
!compCount = componentCount (undefined :: pixel)
!vec = imageData img
lfold !y acc !x !idx
| x >= w = columnFold (y + 1) acc idx
| otherwise =
lfold y (f acc x y $ unsafePixelAt vec idx) (x + 1) (idx + compCount)
columnFold !y lineAcc !readIdx
| y >= h = lineAcc
| otherwise = lfold y lineAcc 0 readIdx
-- | Fold over the pixel of an image with a raster scan order:
-- from top to bottom, left to right, carrying out a state
pixelFoldM :: (Pixel pixel, Monad m)
=> (acc -> Int -> Int -> pixel -> m acc) -- ^ monadic mapping function
-> acc -- ^ Initial state
-> Image pixel -- ^ Image to fold over
-> m acc
{-# INLINE pixelFoldM #-}
pixelFoldM action initialAccumulator img@(Image { imageWidth = w, imageHeight = h }) =
lineFold initialAccumulator h columnFold
where
pixelFolder y acc x = action acc x y $ pixelAt img x y
columnFold lineAcc y = lineFold lineAcc w (pixelFolder y)
-- | Fold over the pixel of an image with a raster scan order:
-- from top to bottom, left to right. This functions is analog
-- to the foldMap from the 'Foldable' typeclass, but due to the
-- Pixel constraint, Image cannot be made an instance of it.
pixelFoldMap :: forall m px. (Pixel px, Monoid m) => (px -> m) -> Image px -> m
pixelFoldMap f Image { imageWidth = w, imageHeight = h, imageData = vec } = folder 0
where
compCount = componentCount (undefined :: px)
maxi = w * h * compCount
folder idx | idx >= maxi = mempty
folder idx = f (unsafePixelAt vec idx) <> folder (idx + compCount)
-- | `map` equivalent for an image, working at the pixel level.
-- Little example : a brightness function for an rgb image
--
-- > brightnessRGB8 :: Int -> Image PixelRGB8 -> Image PixelRGB8
-- > brightnessRGB8 add = pixelMap brightFunction
-- > where up v = fromIntegral (fromIntegral v + add)
-- > brightFunction (PixelRGB8 r g b) =
-- > PixelRGB8 (up r) (up g) (up b)
--
pixelMap :: forall a b. (Pixel a, Pixel b)
=> (a -> b) -> Image a -> Image b
{-# SPECIALIZE INLINE pixelMap :: (PixelYCbCr8 -> PixelRGB8) -> Image PixelYCbCr8 -> Image PixelRGB8 #-}
{-# SPECIALIZE INLINE pixelMap :: (PixelRGB8 -> PixelYCbCr8) -> Image PixelRGB8 -> Image PixelYCbCr8 #-}
{-# SPECIALIZE INLINE pixelMap :: (PixelRGB8 -> PixelRGB8) -> Image PixelRGB8 -> Image PixelRGB8 #-}
{-# SPECIALIZE INLINE pixelMap :: (PixelRGB8 -> PixelRGBA8) -> Image PixelRGB8 -> Image PixelRGBA8 #-}
{-# SPECIALIZE INLINE pixelMap :: (PixelRGBA8 -> PixelRGBA8) -> Image PixelRGBA8 -> Image PixelRGBA8 #-}
{-# SPECIALIZE INLINE pixelMap :: (Pixel8 -> PixelRGB8) -> Image Pixel8 -> Image PixelRGB8 #-}
{-# SPECIALIZE INLINE pixelMap :: (Pixel8 -> Pixel8) -> Image Pixel8 -> Image Pixel8 #-}
pixelMap f Image { imageWidth = w, imageHeight = h, imageData = vec } =
Image w h pixels
where sourceComponentCount = componentCount (undefined :: a)
destComponentCount = componentCount (undefined :: b)
pixels = runST $ do
newArr <- M.new (w * h * destComponentCount)
let lineMapper _ _ y | y >= h = return ()
lineMapper readIdxLine writeIdxLine y = colMapper readIdxLine writeIdxLine 0
where colMapper readIdx writeIdx x
| x >= w = lineMapper readIdx writeIdx $ y + 1
| otherwise = do
unsafeWritePixel newArr writeIdx . f $ unsafePixelAt vec readIdx
colMapper (readIdx + sourceComponentCount)
(writeIdx + destComponentCount)
(x + 1)
lineMapper 0 0 0
-- unsafeFreeze avoids making a second copy and it will be
-- safe because newArray can't be referenced as a mutable array
-- outside of this where block
V.unsafeFreeze newArr
-- | Helpers to embed a rankNTypes inside an Applicative