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