]> git.sesse.net Git - movit/blobdiff - dither_effect.h
Add an implementation of RPDF dither on the final output.
[movit] / dither_effect.h
diff --git a/dither_effect.h b/dither_effect.h
new file mode 100644 (file)
index 0000000..d0f2423
--- /dev/null
@@ -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)