forked from mypaint/libmypaint
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathbrushmodes.c
301 lines (256 loc) · 10.3 KB
/
brushmodes.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
/* libmypaint - The MyPaint Brush Library
* Copyright (C) 2007-2014 Martin Renold <[email protected]> et. al
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#include <config.h>
#include <stdint.h>
#include <assert.h>
#include "helpers.h"
// parameters to those methods:
//
// rgba: A pointer to 16bit rgba data with premultiplied alpha.
// The range of each components is limited from 0 to 2^15.
//
// mask: Contains the dab shape, that is, the intensity of the dab at
// each pixel. Usually rendering is done for one tile at a
// time. The mask is LRE encoded to jump quickly over regions
// that are not affected by the dab.
//
// opacity: overall strenght of the blending mode. Has the same
// influence on the dab as the values inside the mask.
// We are manipulating pixels with premultiplied alpha directly.
// This is an "over" operation (opa = topAlpha).
// In the formula below, topColor is assumed to be premultiplied.
//
// opa_a < opa_b >
// resultAlpha = topAlpha + (1.0 - topAlpha) * bottomAlpha
// resultColor = topColor + (1.0 - topAlpha) * bottomColor
//
void draw_dab_pixels_BlendMode_Normal (uint16_t * mask,
uint16_t * rgba,
uint16_t color_r,
uint16_t color_g,
uint16_t color_b,
uint16_t opacity) {
while (1) {
for (; mask[0]; mask++, rgba+=4) {
uint32_t opa_a = mask[0]*(uint32_t)opacity/(1<<15); // topAlpha
uint32_t opa_b = (1<<15)-opa_a; // bottomAlpha
rgba[3] = opa_a + opa_b * rgba[3] / (1<<15);
rgba[0] = (opa_a*color_r + opa_b*rgba[0])/(1<<15);
rgba[1] = (opa_a*color_g + opa_b*rgba[1])/(1<<15);
rgba[2] = (opa_a*color_b + opa_b*rgba[2])/(1<<15);
}
if (!mask[1]) break;
rgba += mask[1];
mask += 2;
}
};
// Colorize: apply the source hue and saturation, retaining the target
// brightness. Same thing as in the PDF spec addendum, and upcoming SVG
// compositing drafts. Colorize should be used at either 1.0 or 0.0, values in
// between probably aren't very useful. This blend mode retains the target
// alpha, and any pure whites and blacks in the target layer.
#define MAX3(a, b, c) ((a)>(b)?MAX((a),(c)):MAX((b),(c)))
#define MIN3(a, b, c) ((a)<(b)?MIN((a),(c)):MIN((b),(c)))
// For consistency, these are the values used by MyPaint's Color and
// Luminosity layer blend modes, which in turn are defined by
// http://dvcs.w3.org/hg/FXTF/rawfile/tip/compositing/index.html.
// Same as ITU Rec. BT.601 (SDTV) rounded to 2 decimal places.
static const float LUMA_RED_COEFF = 0.3 * (1<<15);
static const float LUMA_GREEN_COEFF = 0.59 * (1<<15);
static const float LUMA_BLUE_COEFF = 0.11 * (1<<15);
// See also http://en.wikipedia.org/wiki/YCbCr
/* Returns the sRGB luminance of an RGB triple, expressed as scaled ints. */
#define LUMA(r,g,b) \
((r)*LUMA_RED_COEFF + (g)*LUMA_GREEN_COEFF + (b)*LUMA_BLUE_COEFF)
/*
* Sets the output RGB triple's luminance to that of the input, retaining its
* colour. Inputs and outputs are scaled ints having factor 2**-15, and must
* not store premultiplied alpha.
*/
inline static void
set_rgb16_lum_from_rgb16(const uint16_t topr,
const uint16_t topg,
const uint16_t topb,
uint16_t *botr,
uint16_t *botg,
uint16_t *botb)
{
// Spec: SetLum()
// Colours potentially can go out of band to both sides, hence the
// temporary representation inflation.
const uint16_t botlum = LUMA(*botr, *botg, *botb) / (1<<15);
const uint16_t toplum = LUMA(topr, topg, topb) / (1<<15);
const int16_t diff = botlum - toplum;
int32_t r = topr + diff;
int32_t g = topg + diff;
int32_t b = topb + diff;
// Spec: ClipColor()
// Clip out of band values
int32_t lum = LUMA(r, g, b) / (1<<15);
int32_t cmin = MIN3(r, g, b);
int32_t cmax = MAX3(r, g, b);
if (cmin < 0) {
r = lum + (((r - lum) * lum) / (lum - cmin));
g = lum + (((g - lum) * lum) / (lum - cmin));
b = lum + (((b - lum) * lum) / (lum - cmin));
}
if (cmax > (1<<15)) {
r = lum + (((r - lum) * ((1<<15)-lum)) / (cmax - lum));
g = lum + (((g - lum) * ((1<<15)-lum)) / (cmax - lum));
b = lum + (((b - lum) * ((1<<15)-lum)) / (cmax - lum));
}
#ifdef HEAVY_DEBUG
assert((0 <= r) && (r <= (1<<15)));
assert((0 <= g) && (g <= (1<<15)));
assert((0 <= b) && (b <= (1<<15)));
#endif
*botr = r;
*botg = g;
*botb = b;
}
// The method is an implementation of that described in the official Adobe "PDF
// Blend Modes: Addendum" document, dated January 23, 2006; specifically it's
// the "Color" nonseparable blend mode. We do however use different
// coefficients for the Luma value.
void
draw_dab_pixels_BlendMode_Color (uint16_t *mask,
uint16_t *rgba, // b=bottom, premult
uint16_t color_r, // }
uint16_t color_g, // }-- a=top, !premult
uint16_t color_b, // }
uint16_t opacity)
{
while (1) {
for (; mask[0]; mask++, rgba+=4) {
// De-premult
uint16_t r, g, b;
const uint16_t a = rgba[3];
r = g = b = 0;
if (rgba[3] != 0) {
r = ((1<<15)*((uint32_t)rgba[0])) / a;
g = ((1<<15)*((uint32_t)rgba[1])) / a;
b = ((1<<15)*((uint32_t)rgba[2])) / a;
}
// Apply luminance
set_rgb16_lum_from_rgb16(color_r, color_g, color_b, &r, &g, &b);
// Re-premult
r = ((uint32_t) r) * a / (1<<15);
g = ((uint32_t) g) * a / (1<<15);
b = ((uint32_t) b) * a / (1<<15);
// And combine as normal.
uint32_t opa_a = mask[0] * opacity / (1<<15); // topAlpha
uint32_t opa_b = (1<<15) - opa_a; // bottomAlpha
rgba[0] = (opa_a*r + opa_b*rgba[0])/(1<<15);
rgba[1] = (opa_a*g + opa_b*rgba[1])/(1<<15);
rgba[2] = (opa_a*b + opa_b*rgba[2])/(1<<15);
}
if (!mask[1]) break;
rgba += mask[1];
mask += 2;
}
};
// This blend mode is used for smudging and erasing. Smudging
// allows to "drag" around transparency as if it was a color. When
// smuding over a region that is 60% opaque the result will stay 60%
// opaque (color_a=0.6). For normal erasing color_a is set to 0.0
// and color_r/g/b will be ignored. This function can also do normal
// blending (color_a=1.0).
//
void draw_dab_pixels_BlendMode_Normal_and_Eraser (uint16_t * mask,
uint16_t * rgba,
uint16_t color_r,
uint16_t color_g,
uint16_t color_b,
uint16_t color_a,
uint16_t opacity) {
while (1) {
for (; mask[0]; mask++, rgba+=4) {
uint32_t opa_a = mask[0]*(uint32_t)opacity/(1<<15); // topAlpha
uint32_t opa_b = (1<<15)-opa_a; // bottomAlpha
opa_a = opa_a * color_a / (1<<15);
rgba[3] = opa_a + opa_b * rgba[3] / (1<<15);
rgba[0] = (opa_a*color_r + opa_b*rgba[0])/(1<<15);
rgba[1] = (opa_a*color_g + opa_b*rgba[1])/(1<<15);
rgba[2] = (opa_a*color_b + opa_b*rgba[2])/(1<<15);
}
if (!mask[1]) break;
rgba += mask[1];
mask += 2;
}
};
// This is BlendMode_Normal with locked alpha channel.
//
void draw_dab_pixels_BlendMode_LockAlpha (uint16_t * mask,
uint16_t * rgba,
uint16_t color_r,
uint16_t color_g,
uint16_t color_b,
uint16_t opacity) {
while (1) {
for (; mask[0]; mask++, rgba+=4) {
uint32_t opa_a = mask[0]*(uint32_t)opacity/(1<<15); // topAlpha
uint32_t opa_b = (1<<15)-opa_a; // bottomAlpha
opa_a *= rgba[3];
opa_a /= (1<<15);
rgba[0] = (opa_a*color_r + opa_b*rgba[0])/(1<<15);
rgba[1] = (opa_a*color_g + opa_b*rgba[1])/(1<<15);
rgba[2] = (opa_a*color_b + opa_b*rgba[2])/(1<<15);
}
if (!mask[1]) break;
rgba += mask[1];
mask += 2;
}
};
// Sum up the color/alpha components inside the masked region.
// Called by get_color().
//
void get_color_pixels_accumulate (uint16_t * mask,
uint16_t * rgba,
float * sum_weight,
float * sum_r,
float * sum_g,
float * sum_b,
float * sum_a
) {
// The sum of a 64x64 tile fits into a 32 bit integer, but the sum
// of an arbitrary number of tiles may not fit. We assume that we
// are processing a single tile at a time, so we can use integers.
// But for the result we need floats.
uint32_t weight = 0;
uint32_t r = 0;
uint32_t g = 0;
uint32_t b = 0;
uint32_t a = 0;
while (1) {
for (; mask[0]; mask++, rgba+=4) {
uint32_t opa = mask[0];
weight += opa;
r += opa*rgba[0]/(1<<15);
g += opa*rgba[1]/(1<<15);
b += opa*rgba[2]/(1<<15);
a += opa*rgba[3]/(1<<15);
}
if (!mask[1]) break;
rgba += mask[1];
mask += 2;
}
// convert integer to float outside the performance critical loop
*sum_weight += weight;
*sum_r += r;
*sum_g += g;
*sum_b += b;
*sum_a += a;
};