]> git.sesse.net Git - movit/blob - dither_effect.h
Add a notice to render_to_screen() that it might be suboptimal.
[movit] / dither_effect.h
1 #ifndef _MOVIT_DITHER_EFFECT_H
2 #define _MOVIT_DITHER_EFFECT_H 1
3
4 // Implements simple rectangular-PDF dither.
5 //
6 // Although all of our processing internally is in floating-point (a mix of 16-
7 // and 32-bit), eventually most pipelines will end up downconverting to a fixed-point
8 // format, typically 8-bits unsigned integer (GL_RGBA8).
9 //
10 // The hardware will typically do proper rounding for us, so that we minimize
11 // quantization noise, but for some applications, if you look closely, you can still
12 // see some banding; 8 bits is not really all that much (and if we didn't have the
13 // perceptual gamma curve, it would be a lot worse).
14 //
15 // The standard solution to this is dithering; in short, to add a small random component
16 // to each pixel before quantization. This increases the overall noise floor slightly,
17 // but allows us to represent frequency components with an amplitude lower than 1/256.
18 // 
19 // My standard reference on dither is:
20 //
21 //   Cameron Nicklaus Christou: “Optimal Dither and Noise Shaping in Image Processing”
22 //   http://uwspace.uwaterloo.ca/bitstream/10012/3867/1/thesis.pdf
23 //
24 // However, we need to make two significant deviations from the recommendations it makes.
25 // First of all, it recommends using a triangular-PDF (TPDF) dither (which can be synthesized
26 // effectively by adding two uniformly distributed random numbers) instead of rectangular-PDF
27 // (RPDF; using one uniformly distributed random number), in order to make the second moment
28 // of the error signal independent from the original image. However, since the recommended
29 // TPDF must be twice as wide as the RPDF, it means it can go to +/- 1, which means that
30 // some of the time, it will add enough noise to change a pixel just by itself. Given that
31 // a very common use case for us is converting 8-bit -> 8-bit (ie., no bit reduction at all),
32 // it would seem like a more important goal to have no noise in that situation than to
33 // improve the dither further.
34 //
35 // Second, the thesis recommends noise shaping (also known as error diffusion in the image
36 // processing world). This is, however, very hard to implement properly on a GPU, since it
37 // almost by definition feeds the value of output pixels into the neighboring input pixels.
38 // Maybe one could make a version that implemented the noise shapers by way of FIR filters
39 // instead of IIR like this, but it would seem a lot of work for very subtle gain.
40 //
41 // We keep the dither noise fixed as long as the output resolution doesn't change;
42 // this ensures we don't upset video codecs too much. (One could also dither in time,
43 // like many LCD monitors do, but it starts to get very hairy, again, for limited gains.)
44 // The dither is also deterministic across runs.
45
46 #include <epoxy/gl.h>
47 #include <string>
48
49 #include "effect.h"
50
51 namespace movit {
52
53 class DitherEffect : public Effect {
54 private:
55         // Should not be instantiated by end users;
56         // call EffectChain::set_dither_bits() instead.
57         DitherEffect();
58         friend class EffectChain;
59
60 public:
61         ~DitherEffect();
62         virtual std::string effect_type_id() const { return "DitherEffect"; }
63         std::string output_fragment_shader();
64
65         // Note that if we did error diffusion, we'd actually want to diffuse the
66         // premultiplied error. However, we need to do dithering in the same
67         // space as quantization, whether that be pre- or postmultiply.
68         virtual AlphaHandling alpha_handling() const { return DONT_CARE_ALPHA_TYPE; }
69         virtual bool one_to_one_sampling() const { return true; }
70
71         void set_gl_state(GLuint glsl_program_num, const std::string &prefix, unsigned *sampler_num);
72
73 private:
74         void update_texture(GLuint glsl_program_num, const std::string &prefix, unsigned *sampler_num);
75
76         int width, height, num_bits;
77         int last_width, last_height, last_num_bits;
78         int texture_width, texture_height;
79
80         GLuint texnum;
81         float uniform_round_fac, uniform_inv_round_fac;
82         float uniform_tc_scale[2];
83         GLint uniform_dither_tex;
84 };
85
86 }  // namespace movit
87
88 #endif // !defined(_MOVIT_DITHER_EFFECT_H)