Euphoria
rgb_blend.cc
Go to the documentation of this file.
1 #include "core/rgb_blend.h"
2 
3 #include <cstdint>
4 
5 #include "assert/assert.h"
6 #include "base/cint.h"
7 
8 // reference: https://stackoverflow.com/questions/5919663/how-does-photoshop-blend-two-images-together
9 
10 namespace eu::core
11 {
12  namespace
13  {
14  // todo(Gustav): replace uint8 with float
15  using Uint8 = std::uint8_t;
16 
17 
18  Uint8
19  min(Uint8 lhs, Uint8 rhs)
20  {
21  return (lhs < rhs) ? lhs : rhs;
22  }
23 
24 
25  Uint8
26  max(Uint8 lhs, Uint8 rhs)
27  {
28  return (lhs > rhs) ? lhs : rhs;
29  }
30 
31 
32  Uint8
33  abs(Uint8 lhs)
34  {
35  return lhs;
36  }
37 
38 
39  Uint8 c_u8(int i)
40  {
41  if(i < 0) { return 0; }
42  else if(i > 255) { return 255; }
43  else { return static_cast<Uint8>(i); }
44  }
45 
46 
47  Uint8 c_u8(float f)
48  {
49  const auto i = c_float_to_int(f);
50  return c_u8(i);
51  }
52 
53 
54  Uint8 channel_blend_normal(Uint8 top, Uint8)
55  {
56  return top;
57  }
58 
59 
60  Uint8 channel_blend_lighten(Uint8 top, Uint8 bottom)
61  {
62  return (bottom > top) ? bottom : top;
63  }
64 
65 
66  Uint8 channel_blend_darken(Uint8 top, Uint8 bottom)
67  {
68  return (bottom > top) ? top : bottom;
69  }
70 
71 
72  Uint8 channel_blend_multiply(Uint8 top, Uint8 bottom)
73  {
74  return c_u8(static_cast<float>(static_cast<int>(top) * static_cast<int>(bottom)) / 255.0f);
75  }
76 
77 
78  Uint8 channel_blend_average(Uint8 top, Uint8 bottom)
79  {
80  return c_u8(static_cast<float>(static_cast<int>(top) + static_cast<int>(bottom)) / 2.0f);
81  }
82 
83 
84  Uint8 channel_blend_add(Uint8 top, Uint8 bottom)
85  {
86  const auto sum = static_cast<int>(top) + static_cast<int>(bottom);
87  return c_u8(sum);
88  }
89 
90 
91  Uint8 channel_blend_subtract(Uint8 top, Uint8 bottom)
92  {
93  return c_u8(static_cast<int>(top) + static_cast<int>(bottom) - 255);
94  }
95 
96 
97  Uint8 channel_blend_difference(Uint8 top, Uint8 bottom)
98  {
99  return c_u8(static_cast<int>(top) - static_cast<int>(bottom));
100  }
101 
102 
103  Uint8 channel_blend_negation(Uint8 top, Uint8 bottom)
104  {
105  return 255 - abs(255 - top - bottom);
106  }
107 
108 
109  Uint8 channel_blend_screen(Uint8 top, Uint8 bottom)
110  {
111  return 255 - (((255 - top) * (255 - bottom)) >> 8);
112  }
113 
114 
115  Uint8 channel_blend_exclusion(Uint8 top, Uint8 bottom)
116  {
117  return top + bottom - 2 * top * bottom / 255;
118  }
119 
120 
121  Uint8 channel_blend_overlay(Uint8 top, Uint8 bottom)
122  {
123  if(bottom < 128)
124  {
125  return 2 * top * bottom / 255;
126  }
127  else
128  {
129  return 255 - 2 * (255 - top) * (255 - bottom) / 255;
130  }
131  }
132 
133 
134  Uint8 channel_blend_soft_light(Uint8 top, Uint8 bottom)
135  {
136  const int magic = (top>>1)+64;
137 
138  if (bottom < 128)
139  {
140  const auto r = 2.0f * c_int_to_float(magic) * static_cast<float>(bottom) / 255.0f;
141  return c_u8(r);
142  }
143  else
144  {
145  const auto r = 2.0f * (255.0f-c_int_to_float(magic)) * static_cast<float>(255-bottom) / 255.0f;
146  return c_u8(255.0f - r);
147  }
148  }
149 
150 
151  Uint8 channel_blend_hard_light(Uint8 top, Uint8 bottom)
152  {
153  const auto swapped_top = bottom;
154  const auto swapped_bottom = top;
155  return channel_blend_overlay(swapped_top, swapped_bottom);
156  }
157 
158 
159  Uint8 channel_blend_color_dodge(Uint8 top, Uint8 bottom)
160  {
161  if(bottom == 255)
162  {
163  return bottom;
164  }
165  else
166  {
167  return min(255, ((top << 8 ) / (255 - bottom)));
168  }
169  }
170 
171 
172  Uint8 channel_blend_color_burn(Uint8 top, Uint8 bottom)
173  {
174  if(bottom == 0)
175  {
176  return bottom;
177  }
178  else
179  {
180  return max(0, (255 - ((255 - top) << 8 ) / bottom));
181  }
182  }
183 
184 
185  Uint8 channel_blend_linear_dodge(Uint8 top, Uint8 bottom)
186  {
187  return channel_blend_add(top, bottom);
188  }
189 
190 
191  Uint8 channel_blend_linear_burn(Uint8 top, Uint8 bottom)
192  {
193  return channel_blend_subtract(top, bottom);
194  }
195 
196 
197  Uint8 channel_blend_linear_light(Uint8 top, Uint8 bottom)
198  {
199  if(bottom < 128)
200  {
201  return channel_blend_linear_burn(top, (2 * bottom));
202  }
203  else
204  {
205  return channel_blend_linear_dodge(top, (2 * (bottom - 128)));
206  }
207  }
208 
209 
210  Uint8 channel_blend_vivid_light(Uint8 top, Uint8 bottom)
211  {
212  if(bottom < 128)
213  {
214  return channel_blend_color_burn(top, (2 * bottom));
215  }
216  else
217  {
218  return channel_blend_color_dodge(top, (2 * (bottom - 128)));
219  }
220  }
221 
222 
223  Uint8 channel_blend_pin_light(Uint8 top, Uint8 bottom)
224  {
225  if(bottom < 128)
226  {
227  return channel_blend_darken(top, (2 * bottom));
228  }
229  else
230  {
231  return channel_blend_lighten(top, (2 * (bottom - 128)));
232  }
233  }
234 
235 
236  Uint8 channel_blend_hard_mix(Uint8 top, Uint8 bottom)
237  {
238  if(channel_blend_vivid_light(top, bottom) < 128)
239  {
240  return 0;
241  }
242  else
243  {
244  return 255;
245  }
246  }
247 
248 
249  Uint8 channel_blend_reflect(Uint8 top, Uint8 bottom)
250  {
251  if(bottom == 255)
252  {
253  return bottom;
254  }
255  else
256  {
257  return min(255, (top * top / (255 - bottom)));
258  }
259  }
260 
261 
262  Uint8 channel_blend_glow(Uint8 top, Uint8 bottom)
263  {
264  const auto swapped_top = bottom;
265  const auto swapped_bottom = top;
266  return channel_blend_reflect(swapped_top, swapped_bottom);
267  }
268 
269 
270  Uint8 channel_blend_phoenix(Uint8 top, Uint8 bottom)
271  {
272  return min(top,bottom) - max(top,bottom) + 255;
273  }
274 
275 
276 
277  // 1 = top
278  // 0 = bottom
279  Uint8
280  channel_blend_alpha(Uint8 top, Uint8 bottom, float factor)
281  {
282  return c_u8(factor * static_cast<float>(top) + (1.0f - factor) * static_cast<float>(bottom));
283  }
284 
285 
286  template<typename TF>
287  Uint8
288  channel_blend_alpha_f(Uint8 top, Uint8 bottom, float factor, TF f)
289  {
290  return channel_blend_alpha(f(top, bottom), bottom, factor);
291  }
292 
293 
294  template<typename TBlendFunction>
295  Rgbi
296  blend(const Rgbi& top, const Rgbi& bottom, TBlendFunction blend_function)
297  {
298  return
299  {
300  blend_function(top.r, bottom.r),
301  blend_function(top.g, bottom.g),
302  blend_function(top.b, bottom.b),
303  };
304  }
305 
306 
307  template<typename TBlendFunction>
308  Rgb
309  blend(const Rgb& top, const Rgb& bottom, TBlendFunction blend_function)
310  {
311  return to_rgb(blend(to_rgbi(top), to_rgbi(bottom), blend_function));
312  }
313 
314 
315  template<typename T>
316  T
317  alpha_blend(T lhs, T rhs, float a)
318  {
319  return static_cast<T>(lhs * (1-a) + rhs * a);
320  }
321 
322 
323  template<typename TBlendFunction>
324  Rgbai
325  blend
326  (
327  const Rgbai& top,
328  const Rgbai& bottom,
329  TBlendFunction blend_function
330  )
331  {
332  const auto factor = static_cast<float>(top.a)/255.0f;
333  const auto ff = [&](Uint8 a_top, Uint8 a_bottom)
334  {
335  return channel_blend_alpha_f
336  (
337  a_top,
338  a_bottom,
339  factor,
340  blend_function
341  );
342  };
343  return
344  {
345  {
346  ff(top.r, bottom.r),
347  ff(top.g, bottom.g),
348  ff(top.b, bottom.b)
349  },
350  alpha_blend(top.a, bottom.a, 1-factor)
351  };
352  }
353 
354 
355  template<typename TBlendFunction>
356  Rgba
357  blend(const Rgba& top, const Rgba& bottom, TBlendFunction blend_function)
358  {
359  return to_rgba(blend(to_rgbai(top), to_rgbai(bottom), blend_function));
360  }
361 
362 
363  template<typename C>
364  C
365  color_blend(BlendMode mode, const C& top, const C& bottom)
366  {
367  switch(mode)
368  {
369  case BlendMode::normal : return blend(top, bottom, channel_blend_normal );
370  case BlendMode::lighten : return blend(top, bottom, channel_blend_lighten );
371  case BlendMode::darken : return blend(top, bottom, channel_blend_darken );
372  case BlendMode::multiply : return blend(top, bottom, channel_blend_multiply );
373  case BlendMode::average : return blend(top, bottom, channel_blend_average );
374  case BlendMode::add : return blend(top, bottom, channel_blend_add );
375  case BlendMode::subtract : return blend(top, bottom, channel_blend_subtract );
376  case BlendMode::difference : return blend(top, bottom, channel_blend_difference );
377  case BlendMode::negation : return blend(top, bottom, channel_blend_negation );
378  case BlendMode::screen : return blend(top, bottom, channel_blend_screen );
379  case BlendMode::exclusion : return blend(top, bottom, channel_blend_exclusion );
380  case BlendMode::overlay : return blend(top, bottom, channel_blend_overlay );
381  case BlendMode::soft_light : return blend(top, bottom, channel_blend_soft_light );
382  case BlendMode::hard_light : return blend(top, bottom, channel_blend_hard_light );
383  case BlendMode::color_dodge : return blend(top, bottom, channel_blend_color_dodge );
384  case BlendMode::color_burn : return blend(top, bottom, channel_blend_color_burn );
385  case BlendMode::linear_dodge: return blend(top, bottom, channel_blend_linear_dodge);
386  case BlendMode::linear_burn : return blend(top, bottom, channel_blend_linear_burn );
387  case BlendMode::linear_light: return blend(top, bottom, channel_blend_linear_light);
388  case BlendMode::vivid_light : return blend(top, bottom, channel_blend_vivid_light );
389  case BlendMode::pin_light : return blend(top, bottom, channel_blend_pin_light );
390  case BlendMode::hard_mix : return blend(top, bottom, channel_blend_hard_mix );
391  case BlendMode::reflect : return blend(top, bottom, channel_blend_reflect );
392  case BlendMode::glow : return blend(top, bottom, channel_blend_glow );
393  case BlendMode::phoenix : return blend(top, bottom, channel_blend_phoenix );
394  default:
395  DIE("unhandled blend case");
396  return top;
397  }
398  }
399  }
400 
401 
402  Rgb blend(const Rgb& top, const Rgb& bottom, BlendMode mode) { return color_blend(mode, top, bottom); }
403  Rgba blend(const Rgba& top, const Rgba& bottom, BlendMode mode) { return color_blend(mode, top, bottom); }
404  Rgbi blend(const Rgbi& top, const Rgbi& bottom, BlendMode mode) { return color_blend(mode, top, bottom); }
405  Rgbai blend(const Rgbai& top, const Rgbai& bottom, BlendMode mode) { return color_blend(mode, top, bottom); }
406 }
#define DIE(message)
Definition: assert.h:67
Rgb blend(const Rgb &top, const Rgb &bottom, BlendMode mode)
Definition: rgb_blend.cc:402
Rgbai to_rgbai(const Rgba &c)
Definition: rgb.cc:395
Rgbi to_rgbi(const Rgb &c)
Definition: rgb.cc:352
constexpr int c_float_to_int(float f)
Definition: cint.h:36
Rgb to_rgb(const Rgbi &c)
Definition: rgb.cc:229
constexpr float abs(float r)
Definition: numeric.h:8
constexpr float c_int_to_float(int i)
Definition: cint.h:50
size2f min(const size2f lhs, const size2f rhs)
Definition: size2.cc:140
Rgba to_rgba(const Rgbai &c)
Definition: rgb.cc:381
size2f max(const size2f lhs, const size2f rhs)
Definition: size2.cc:149
Definition: rgb.h:62
Definition: rgb.h:143
Definition: rgb.h:45
Definition: rgb.h:26