Pinta icon indicating copy to clipboard operation
Pinta copied to clipboard

Add Color Quantization effect

Open Matthieu-LAURENT39 opened this issue 2 months ago • 9 comments

This adds an effect to quantize the colors in an image, which can be used as either a compression method or as a stylistic choice. I also tried my best to make an icon for it.

This was originally going to be an addon, hence why i didn't make an issue first, but in the end i figured it's probably useful enough to warrant being upstream. I will likely add more quantization methods in the future when i have more time, but i wanted to make this PR first to make sure the effect is fit for inclusion in Pinta.

Matthieu-LAURENT39 avatar Oct 17 '25 10:10 Matthieu-LAURENT39

I'll try to review this soon - I'm still catching up on reviewing some PRs, sorry!

cameronwhite avatar Nov 11 '25 05:11 cameronwhite

Just some initial thoughts about how this should be integrated:

This was originally going to be an addon, hence why i didn't make an issue first, but in the end i figured it's probably useful enough to warrant being upstream

I would also normally suggest making an add-in for new effects :) , but I think this has a very good argument for being upstream due to its overlap with the Dithering effect. There was a brief discussion about this back in https://github.com/PintaProject/Pinta/pull/457#issuecomment-1691289383 , but the focus at the time was on being able to choose specific palettes

My initial thinking is that this could perhaps be merged with the Dithering effect, as a new option for automatically choosing a palette with N colors. Then, you'd also gain the ability to apply one of the error diffusion methods too (but if we like the look of not having any error diffusion, as in the current version of this Quantize effect, that's a trivially easy to option to add)

I'd also be interested to hear @Lehonti 's thoughts on this, who implemented the dithering effect

cameronwhite avatar Nov 18 '25 04:11 cameronwhite

@Matthieu-LAURENT39, @cameronwhite I haven't been able to look in much detail, but I see that an optimized palette is generated based on the colors of the image (not all of them, just a sample, presumably for speed).

To fit this into the dithering effect, I think we would need to add the following options:

  • An 'Optimized Palette (Median Cut)' option to 'Palette Source'
  • A 'No Diffusion' option to 'Error Diffusion Method'

Both of the algorithms would benefit from each other. The dithering effect would be able to use the optimized palette that is used here, and also, we would not only be able to quantize a color, but also to spread the resulting inaccuracies to neighboring pixels. Let me know what you guys think :)

Lehonti avatar Nov 18 '25 21:11 Lehonti

Merging the 2 does seem like a good idea, those 2 effects are likely to be used together and it would definitely allow for better-looking results.

My only concerns are

  • merging the quantization effect into the dithering effect would make color quantization hard to find (although this could be fixed by renaming the effect to something like "Quantize & Dither")
  • dynamic palettes (currently only median cut, but in the future having more like k-means and octree as well would be nice as they have a very different look) are going to be in the middle of hardcoded palettes. I wonder if Gtk supports separators/categories in dropdowns to make it clearer?

@Matthieu-LAURENT39, @cameronwhite I haven't been able to look in much detail, but I see that an optimized palette is generated based on the colors of the image (not all of them, just a sample, presumably for speed).

To fit this into the dithering effect, I think we would need to add the following options:

* An 'Optimized Palette (Median Cut)' option to 'Palette Source'

* A 'No Diffusion' option to 'Error Diffusion Method'

Both of the algorithms would benefit from each other. The dithering effect would be able to use the optimized palette that is used here, and also, we would not only be able to quantize a color, but also to spread the resulting inaccuracies to neighboring pixels. Let me know what you guys think :)

Fully agreed, this seems like the best approach.

Matthieu-LAURENT39 avatar Nov 19 '25 11:11 Matthieu-LAURENT39

@Matthieu-LAURENT39 I have nothing against changing the effect names as the implementations evolve (but we have to make sure to document it in the change log). I think my only concern would be if it broke the API somehow (which in this case it doesn't). But let's see what @cameronwhite thinks.

As for the separators: to my knowledge, separators can indeed be added to Gtk combo boxes. But you should note that, for most effects, the configuration dialog and its child controls are being generated dynamically based on the properties of the EffectData in question. You can see how it's done by looking at SimpleEffectDialog. To generate a separator, this would need to be modified a bit, perhaps by creating Attributes to annotate the properties and the class with more metadata, and modifying the SimpleEffectDialog to take the metadata into account. The enum could end up looking something like (and I'm just thinking out loud, I think the mechanism could probably be made more elegant):

[CategoryConfig("Dynamic", Order = 1)]
[CategoryConfig("Catalog", Order = 2)]
[CategoryConfig("Application", Order = 3)]
enum PaletteSource
{
    [Category("Dynamic")]
    MedianCut,
    
    [Category("Catalog")]
    PresetPalettes,
    
    [Category("Application")]
    CurrentPalette,
    
    [Category("Application")]
    RecentlyUsedColors,
}

Note: a Category attribute exists in System.ComponentModel, but I think nothing like CategoryConfig (to specify the order) exists. Perhaps it's not even needed; I don't that level of control is needed in Pinta.

Lehonti avatar Nov 19 '25 12:11 Lehonti

@Matthieu-LAURENT39 I have nothing against changing the effect names as the implementations evolve (but we have to make sure to document it in the change log). I think my only concern would be if it broke the API somehow (which in this case it doesn't). But let's see what @cameronwhite thinks.

As for the separators: to my knowledge, separators can indeed be added to Gtk combo boxes. But you should note that, for most effects, the configuration dialog and its child controls are being generated dynamically based on the properties of the EffectData in question. You can see how it's done by looking at SimpleEffectDialog. To generate a separator, this would need to be modified a bit, perhaps by creating Attributes to annotate the properties and the class with more metadata, and modifying the SimpleEffectDialog to take the metadata into account. The enum could end up looking something like (and I'm just thinking out loud, I think the mechanism could probably be made more elegant):

[CategoryConfig("Dynamic", Order = 1)]
[CategoryConfig("Catalog", Order = 2)]
[CategoryConfig("Application", Order = 3)]
enum PaletteSource
{
    [Category("Dynamic")]
    MedianCut,
    
    [Category("Catalog")]
    PresetPalettes,
    
    [Category("Application")]
    CurrentPalette,
    
    [Category("Application")]
    RecentlyUsedColors,
}

Good point for the EffectData, annotations look like an elegant solution.

Another thing that is related is using dynamic palettes requires an extra config field (the number of colors for the palette), but that field should probably not be shown when using static palettes. I'm not sure if this is currently doable, and in fact I'm not sure if this could be implemented without some effect-specific code for EffectData. The most elegant way I can think of would be with an option to add callback functions on EffectData, but even that doesn't really seem like a good solution. To be honest, I feel like for this effect specifically it might be best to just create the config dialogue ourselves.

Matthieu-LAURENT39 avatar Nov 19 '25 12:11 Matthieu-LAURENT39

@Matthieu-LAURENT39 currently, there is no support in SimpleEffectDialog for hiding/disabling controls based on the state of a property. It would be nice to have something like it, because a user could get confused when they change a property and nothing appears to change in the effect. This is especially evident in effects that use gradients, like the Mandelbrot effect: the "Color Scheme" setting only means something if "Color Scheme Source" is set to "Preset Gradient".

And as for creating a custom config dialog, it's perfectly possible (whether that's advisable is a different matter, though). Look at CurvesEffect for an example.

Lehonti avatar Nov 19 '25 12:11 Lehonti

@Lehonti a simple way to toggle an option's visibility based on other config would indeed be nice to have, although it should probably it's own PR, especially given it would affect other effects.

I'll wait from feedback from @cameronwhite to see if it's better to go with a custom dialogue or with extra attributes. In my opinion, the former would be better, as it would be more flexible.

Matthieu-LAURENT39 avatar Nov 19 '25 12:11 Matthieu-LAURENT39

  • Renaming the effect would be fine by me - there isn't any impact on API so changing the name to something more discoverable is ok. We'd just need to mention it in the changelog

  • I think it would be nice to have a way to hide / disable controls for effects using SimpleEffectDialog 👍 . Writing a custom dialog is more maintenance in the long run as GTK versions evolve, so I'd only suggest it for something that needs custom widgets or a very different UI. And there are some other effects that would benefit from it, like the Mandlebrot effect that was mentioned

cameronwhite avatar Nov 22 '25 21:11 cameronwhite