Search code examples
imagehaskelltypespixelsignature

How can I edit pixels of DynamicImage using JuicyPixels (Haskell)?


How can I do similar thing but with DynamicImage (the result of example <- readImage "/filepath.png"):

negative :: Image PixelRGBA8 -> Image PixelRGBA8
negative = pixelMap $ \(PixelRGBA8 r g b a) -> PixelRGBA8 (255 - r) (255 - g) (255 - b) a

And how it's working without any arguments despite signature? I guess that's what I need, because there's no writeJpg function provided that does work with Image (not Dynamic) type. I know about dynamicPixelMap function provided by JuicyPixels, but I do not actually understand how to work with it. It will be good if someone can explain it.

UPD: I've found simple solution:

negative :: Image PixelRGBA8 -> Image PixelRGBA8
dynamicImage <- readImage filepath
let image = convertRGBA8 <$> dynamicImage
let modified = negative <$> image
case modified of 
        Left err -> print err
        Right image -> saveJpgImage 100 outputFile $ ImageRGBA8 image

It's an example, my code is more complicated.


Solution

  • Let's start by looking at why DynamicImage exists. Just looking at the definition is sufficient to make that clear: There are lot of different formats for color data in images. The input routines JuicyPixels provides preserve the color data the way it was stored in the file. But the Image type puts the color data format into the type. This is a problem in Haskell - if you make a function polymorphic, the caller gets to choose the concrete type, not the function itself. There's no way to say "this returns some Image type, depending on the input". So DynamicImage exists - it has a different constructor for each supported color data format. Matching on the constructor tells you the type of Image you're working with, so you can't get the data without also knowing what type it is.

    Since the constructors are public, you can match on them to get an Image and use pixelMap on it... But I wouldn't. JPEG files are sometimes in CMYK space, not RGB. Logic in that space is entirely different - it's a subtractive space, rather than additive. And JuicyPixels provides tools to help you out here. The convertRGBA8 function takes a DynamicImage and does whatever colorspace conversion is necessary to give you an Image PixelRBGA8. The conversion will lose details in some cases, but it will get you the format in the example you provided.