X-Git-Url: https://git.sesse.net/?p=movit;a=blobdiff_plain;f=dither_effect.h;fp=dither_effect.h;h=d0f2423c26831c1705475f49052db0eeb7d496d0;hp=0000000000000000000000000000000000000000;hb=ff9e68a3f5abb179bd7bf9fb84df48327f148583;hpb=3a1e190a989a29edffdfa79bad7994149fc5d27c diff --git a/dither_effect.h b/dither_effect.h new file mode 100644 index 0000000..d0f2423 --- /dev/null +++ b/dither_effect.h @@ -0,0 +1,67 @@ +#ifndef _DITHER_EFFECT_H +#define _DITHER_EFFECT_H 1 + +// Implements simple rectangular-PDF dither. +// +// Although all of our processing internally is in floating-point (a mix of 16- +// and 32-bit), eventually most pipelines will end up downconverting to a fixed-point +// format, typically 8-bits unsigned integer (GL_RGBA8). +// +// The hardware will typically do proper rounding for us, so that we minimize +// quantization noise, but for some applications, if you look closely, you can still +// see some banding; 8 bits is not really all that much (and if we didn't have the +// perceptual gamma curve, it would be a lot worse). +// +// The standard solution to this is dithering; in short, to add a small random component +// to each pixel before quantization. This increases the overall noise floor slightly, +// but allows us to represent frequency components with an amplitude lower than 1/256. +// +// My standard reference on dither is: +// +// Cameron Nicklaus Christou: “Optimal Dither and Noise Shaping in Image Processing” +// http://uwspace.uwaterloo.ca/bitstream/10012/3867/1/thesis.pdf +// +// However, we need to make two significant deviations from the recommendations it makes. +// First of all, it recommends using a triangular-PDF (TPDF) dither (which can be synthesized +// effectively by adding two uniformly distributed random numbers) instead of rectangular-PDF +// (RPDF; using one uniformly distributed random number), in order to make the second moment +// of the error signal independent from the original image. However, since the recommended +// TPDF must be twice as wide as the RPDF, it means it can go to +/- 1, which means that +// some of the time, it will add enough noise to change a pixel just by itself. Given that +// a very common use case for us is converting 8-bit -> 8-bit (ie., no bit reduction at all), +// it would seem like a more important goal to have no noise in that situation than to +// improve the dither further. +// +// Second, the thesis recommends noise shaping (also known as error diffusion in the image +// processing world). This is, however, very hard to implement properly on a GPU, since it +// almost by definition feeds the value of output pixels into the neighboring input pixels. +// Maybe one could make a version that implemented the noise shapers by way of FIR filters +// instead of IIR like this, but it would seem a lot of work for very subtle gain. +// +// We keep the dither noise fixed as long as the output resolution doesn't change; +// this ensures we don't upset video codecs too much. (One could also dither in time, +// like many LCD monitors do, but it starts to get very hairy, again, for limited gains.) +// The dither is also deterministic across runs. + +#include "effect.h" + +class DitherEffect : public Effect { +public: + DitherEffect(); + ~DitherEffect(); + virtual std::string effect_type_id() const { return "DitherEffect"; } + std::string output_fragment_shader(); + + void set_gl_state(GLuint glsl_program_num, const std::string &prefix, unsigned *sampler_num); + +private: + void update_texture(GLuint glsl_program_num, const std::string &prefix, unsigned *sampler_num); + + int width, height, num_bits; + int last_width, last_height, last_num_bits; + + GLuint texnum; + bool need_texture_update; +}; + +#endif // !defined(_DITHER_EFFECT_H)