1 // -*- mode: c++; c-basic-offset: 4; indent-tabs-mode: nil; -*-
2 // (c) 2016-2021 Henner Zeller <h.zeller@acm.org>
3 //
4 // This program is free software; you can redistribute it and/or modify
5 // it under the terms of the GNU General Public License as published by
6 // the Free Software Foundation version 2.
7 //
8 // This program is distributed in the hope that it will be useful,
9 // but WITHOUT ANY WARRANTY; without even the implied warranty of
10 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11 // GNU General Public License for more details.
12 //
13 // You should have received a copy of the GNU General Public License
14 // along with this program.  If not, see <http://gnu.org/licenses/gpl-2.0.txt>
15 
16 #ifndef TIMG_FRAMEBUFFER_H
17 #define TIMG_FRAMEBUFFER_H
18 
19 #include <math.h>
20 #include <stdint.h>
21 #include <string.h>
22 
23 #include <functional>
24 #include <initializer_list>
25 
26 namespace timg {
27 struct rgba_t {
28     uint8_t r, g, b;  // Color components, gamma corrected (non-linear)
29     uint8_t a;        // Alpha channel. Linear.
30 
31     inline bool operator==(const rgba_t &that) const {
32         // Using memcmp() slower, so force uint-compare with type-punning.
33         return *((uint32_t*)this) == *((uint32_t*)&that);
34     }
35     inline bool operator!=(const rgba_t &o) const { return !(*this == o); }
36 
37     // Rough mapping to the 256 color modes, a 6x6x6 cube.
As256TermColorrgba_t38     inline uint8_t As256TermColor() const {
39         auto v2cube = [](uint8_t v) {  // middle of cut-off points for cube.
40             return v < 0x5f/2 ? 0
41                 : v < (0x5f + 0x87)/2 ? 1
42                 : v < (0x87 + 0xaf)/2 ? 2
43                 : v < (0xaf + 0xd7)/2 ? 3
44                 : v < (0xd7 + 0xff)/2 ? 4
45                 : 5;
46         };
47         return 16 + 36 * v2cube(r) + 6 * v2cube(g) + v2cube(b);
48     }
49 
50     // Parse a color given as string. Supported are numeric formats are
51     // "#rrggbb" and "rgb(r, g, b)", and also common textual X11/HTML names
52     // such as 'red' or 'MediumAquaMarine'.
53     // Returned alpha channel is solid 0xff unless color could not be
54     // decoded, in which case it is all-transparent 0x00
55     static rgba_t ParseColor(const char *color);
56 };
57 static_assert(sizeof(rgba_t) == 4, "Unexpected size for rgba_t struct");
58 
59 // Very simple framebuffer, storing widht*height pixels in RGBA format.
60 class Framebuffer {
61 public:
62     typedef rgba_t * iterator;
63     typedef const rgba_t * const_iterator;
64     class rgb_iterator;
65 
66     Framebuffer(int width, int height);
67     Framebuffer() = delete;
68     Framebuffer(const Framebuffer &other) = delete;
69     ~Framebuffer();
70 
71     // Set a pixel at position X/Y with rgba_t color value. use to_rgba() to
72     // convert a value.
73     void SetPixel(int x, int y, rgba_t value);
74 
75     // Get pixel data at given position.
76     rgba_t at(int x, int y) const;
77 
78     // Clear to fully transparent black pixels.
79     void Clear();
80 
width()81     inline int width() const { return width_; }
height()82     inline int height() const { return height_; }
83 
84     // Blend all transparent pixels with a background color and an optional
85     // alternative pattern color to make them a solid (alpha=0xff) color.
86     // The Background color is queried using the provided callback and only
87     // requested when needed, i.e. if there any transparent pixels to be
88     // blended.
89     //
90     // If the alpha value of "pattern" is set (alpha=0xff), then every other
91     // pixel will be the pattern color. That creates a 'checkerboard-pattern'
92     // sometimes used to display transparent pictures.
93     //
94     // This Alpha compositing merges in the linearized colors domain.
95     using bgcolor_query = std::function<rgba_t()>;
96     void AlphaComposeBackground(bgcolor_query get_bg, rgba_t pattern,
97                                 int pattern_width, int pattern_height);
98 
99     // The raw internal buffer containing width()*height() pixels organized
100     // from top left to bottom right.
begin()101     const_iterator begin() const { return pixels_; }
begin()102     iterator begin() { return pixels_; }
end()103     const_iterator end() const { return end_; }
end()104     iterator end() { return end_; }
105 
106     /* the following two methods are useful with line-oriented sws_scale()
107      * to allow it to directly write into our frame-buffer
108      */
109 
110     // Return an array containing the amount of bytes for each line.
111     // This is returned as an array.
stride()112     const int* stride() const { return strides_; }
113 
114     // Return an array containing pointers to the data for each line.
115     uint8_t** row_data();
116 
117 private:
118     const int width_;
119     const int height_;
120     rgba_t *const pixels_;
121     rgba_t *const end_;
122     int strides_[2];
123     uint8_t **row_data_ = nullptr;  // Only allocated if requested.
124 };
125 
126 // Unpacked rgba_t into linear color space, useful to do any blending ops on.
127 class LinearColor {
128 public:
LinearColor()129     LinearColor() : r(0), g(0), b(0), a(0) {}
130     // We approximate x^2.2 with x^2
LinearColor(rgba_t c)131     LinearColor(rgba_t c) : r(c.r*c.r), g(c.g*c.g), b(c.b*c.b), a(c.a) {}
132 
dist(const LinearColor & other)133     inline float dist(const LinearColor &other) const {
134         // quadratic distance. Not bothering sqrt()ing them.
135         return sq(other.r - r) + sq(other.g - g) + sq(other.b - b);
136     }
137 
repack()138     inline rgba_t repack() const {
139         return { gamma(r), gamma(g), gamma(b), (uint8_t)a };
140     }
141 
142     // If this color is transparent, blend in the background according to alpha
AlphaBlend(const LinearColor & background)143     LinearColor &AlphaBlend(const LinearColor &background) {
144         r = (r * a + background.r * (0xff - a)) / 0xff;
145         g = (g * a + background.g * (0xff - a)) / 0xff;
146         b = (b * a + background.b * (0xff - a)) / 0xff;
147         a = 0xff;  // We're a fully solid color now.
148         return *this;
149     }
150 
151     float r;
152     float g;
153     float b;
154     float a;
155 
156 private:
gamma(float v)157     static uint8_t gamma(float v) {
158         const float vg = sqrtf(v);
159         return (vg > 255) ? 255 : vg;
160     }
sq(float x)161     static float sq(float x) { return x * x; }
162 };
163 
164 // Average "values" into "res" and return sum of distance of all values to avg
avd(LinearColor * res,std::initializer_list<LinearColor> values)165 inline float avd(LinearColor *res, std::initializer_list<LinearColor> values) {
166     for (const LinearColor &c : values) {
167         res->r += c.r; res->g += c.g; res->b += c.b; res->a += c.a;
168     }
169     const size_t n = values.size();
170     res->r /= n; res->g /= n; res->b /= n; res->a /= n;
171     float sum = 0;
172     for (const LinearColor &c : values) {
173         sum += res->dist(c);
174     }
175     return sum;
176 }
177 
linear_average(std::initializer_list<LinearColor> values)178 inline LinearColor linear_average(std::initializer_list<LinearColor> values) {
179     LinearColor result;
180     avd(&result, values);
181     return result;
182 }
183 
184 }  // namespace timg
185 
186 #endif  // TIMG_FRAMEBUFFER_H
187