Juicy.Pixels icon indicating copy to clipboard operation
Juicy.Pixels copied to clipboard

Removing Pixel instance repetition

Open KholdStare opened this issue 10 years ago • 1 comments

Hi! First of all, thank you for the library, it's a great help.

I've been using your library, and at one point I needed a Pixel type that held 3 Doubles, since I wanted to keep the precision consistent with the rest of my program. I found that I could create my own custom instances for PixelD (aka Double), as well as PixelRGBD, but I was basically copy-pasting code from inside Codec.Picture.Types . I feel that a lot of this repetition can be removed from within the library, and automatically generalize to a lot of other types, like Double without the user having to do anything.

The suggestion comes down to "subclassing" instances. To give an intuition:

instance (Eq a, Storable a, Num a) => Pixel a where
    type PixelBaseComponent a = a

    -- etc...

There's only one instance required for all existing single-value pixels, both for types used in the library, and those that may be needed by users. Of course, the above code is questionable, as it leads to UndecidableInstances. One solution is a newtype wrapper:

-- a no-cost wrapper to 'tag' types as pixels
newtype Pix a = Pix a
    deriving (Eq)

instance (Eq a, Storable a, Num a) => Pixel (Pix a) where
    type PixelBaseComponent (Pix a) = a

    -- etc... but with more wrapping/unwrapping

This is ideal from an implementation standpoint, but is probably bad from the user's standpoint, as all their single-value pixels come wrapped in a newtype. Backwards compatibility goes out the window.

For multi-value pixels (e.g. RGBF), one can use a fixed-size vector library, that provides the vector size statically. Instances can then be generically created for many of the existing pixel formats. I don't know if there is a de-facto fixed-vector library that everyone goes to, because it seems many libraries recreate this functionality constantly.

A side-effect of the above approach is that vectors of size 1 effectively serve the purpose of a newtype wrapper too. So there are many approaches.

In summary:

  • Generic Pixel instance for (Eq a, Storable a, Num a) types.
    • Pros: Very generic, works with current types
    • Cons: UndecidableInstances
  • Above but with a newtype wrapper to keep the typesystem happy.
    • Pros: Guaranteed to play nice with the typesystem
    • Cons: Annoying wrapper around single-value pixels
  • Generic Pixel instance for fixed-size vectors of single pixels.
    • Pros: More genericity/extensibility
    • Cons: Still have to differentiate pixels that have the same number of components but mean different things. (e.g. PixelRGB8 vs PixelYCbCr8). This could be dealt with newtype wrappers, or introduce the concept of color spaces.

You may have considered these possibilities already, and ruled them out, but I just wanted to pitch in a few suggestions. If you feel this is worthy of an investigation, I could try and experiment with these ideas on a separate branch.

KholdStare avatar Dec 29 '13 20:12 KholdStare

I didn't thought anybody wanted to add pixel instances as they're directly mapped to image format, so generalizing pixel instances didn't make much sense to me (especially the Double instance :-]). Generalizing the "basic" pixel type might not be worth the hassle.

That being said, there is clearly a problem with the pixel types of more than one component, and I'm not proud of the situation today. I've tried a design with a fixed vector size and a phantom type but never got to something that was:

  • not a typery clusterfuck.
  • with unpacked constructor fields.

That was more than a year ago, so maybe it would be possible to have a second look at this with -funbox-small-strict-fields (which should come with GHC 7.8) in mind

Twinside avatar Dec 29 '13 22:12 Twinside