/src/leptonica/src/blend.c
Line | Count | Source (jump to first uncovered line) |
1 | | /*====================================================================* |
2 | | - Copyright (C) 2001 Leptonica. All rights reserved. |
3 | | - |
4 | | - Redistribution and use in source and binary forms, with or without |
5 | | - modification, are permitted provided that the following conditions |
6 | | - are met: |
7 | | - 1. Redistributions of source code must retain the above copyright |
8 | | - notice, this list of conditions and the following disclaimer. |
9 | | - 2. Redistributions in binary form must reproduce the above |
10 | | - copyright notice, this list of conditions and the following |
11 | | - disclaimer in the documentation and/or other materials |
12 | | - provided with the distribution. |
13 | | - |
14 | | - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
15 | | - ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
16 | | - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
17 | | - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL ANY |
18 | | - CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, |
19 | | - EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, |
20 | | - PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR |
21 | | - PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY |
22 | | - OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING |
23 | | - NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS |
24 | | - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
25 | | *====================================================================*/ |
26 | | |
27 | | /*! |
28 | | * \file blend.c |
29 | | * <pre> |
30 | | * |
31 | | * Blending two images that are not colormapped |
32 | | * PIX *pixBlend() |
33 | | * PIX *pixBlendMask() |
34 | | * PIX *pixBlendGray() |
35 | | * PIX *pixBlendGrayInverse() |
36 | | * PIX *pixBlendColor() |
37 | | * PIX *pixBlendColorByChannel() |
38 | | * PIX *pixBlendGrayAdapt() |
39 | | * static l_int32 blendComponents() |
40 | | * PIX *pixFadeWithGray() |
41 | | * PIX *pixBlendHardLight() |
42 | | * static l_int32 blendHardLightComponents() |
43 | | * |
44 | | * Blending two colormapped images |
45 | | * l_int32 pixBlendCmap() |
46 | | * |
47 | | * Blending two images using a third (alpha mask) |
48 | | * PIX *pixBlendWithGrayMask() |
49 | | * |
50 | | * Blending background to a specific color |
51 | | * PIX *pixBlendBackgroundToColor() |
52 | | * |
53 | | * Multiplying by a specific color |
54 | | * PIX *pixMultiplyByColor() |
55 | | * |
56 | | * Rendering with alpha blending over a uniform background |
57 | | * PIX *pixAlphaBlendUniform() |
58 | | * |
59 | | * Adding an alpha layer for blending |
60 | | * PIX *pixAddAlphaToBlend() |
61 | | * |
62 | | * Setting a transparent alpha component over a white background |
63 | | * PIX *pixSetAlphaOverWhite() |
64 | | * |
65 | | * Fading from the edge |
66 | | * l_int32 pixLinearEdgeFade() |
67 | | * |
68 | | * In blending operations a new pix is produced where typically |
69 | | * a subset of pixels in src1 are changed by the set of pixels |
70 | | * in src2, when src2 is located in a given position relative |
71 | | * to src1. This is similar to rasterop, except that the |
72 | | * blending operations we allow are more complex, and typically |
73 | | * result in dest pixels that are a linear combination of two |
74 | | * pixels, such as src1 and its inverse. I find it convenient |
75 | | * to think of src2 as the "blender" (the one that takes the action) |
76 | | * and src1 as the "blendee" (the one that changes). |
77 | | * |
78 | | * Blending works best when src1 is 8 or 32 bpp. We also allow |
79 | | * src1 to be colormapped, but the colormap is removed before blending, |
80 | | * so if src1 is colormapped, we can't allow in-place blending. |
81 | | * |
82 | | * Because src2 is typically smaller than src1, we can implement by |
83 | | * clipping src2 to src1 and then transforming some of the dest |
84 | | * pixels that are under the support of src2. In practice, we |
85 | | * do the clipping in the inner pixel loop. For grayscale and |
86 | | * color src2, we also allow a simple form of transparency, where |
87 | | * pixels of a particular value in src2 are transparent; for those pixels, |
88 | | * no blending is done. |
89 | | * |
90 | | * The blending functions are categorized by the depth of src2, |
91 | | * the blender, and not that of src1, the blendee. |
92 | | * |
93 | | * ~ If src2 is 1 bpp, we can do one of three things: |
94 | | * (1) L_BLEND_WITH_INVERSE: Blend a given fraction of src1 with its |
95 | | * inverse color for those pixels in src2 that are fg (ON), |
96 | | * and leave the dest pixels unchanged for pixels in src2 that |
97 | | * are bg (OFF). |
98 | | * (2) L_BLEND_TO_WHITE: Fade the src1 pixels toward white by a |
99 | | * given fraction for those pixels in src2 that are fg (ON), |
100 | | * and leave the dest pixels unchanged for pixels in src2 that |
101 | | * are bg (OFF). |
102 | | * (3) L_BLEND_TO_BLACK: Fade the src1 pixels toward black by a |
103 | | * given fraction for those pixels in src2 that are fg (ON), |
104 | | * and leave the dest pixels unchanged for pixels in src2 that |
105 | | * are bg (OFF). |
106 | | * The blending function is pixBlendMask(). |
107 | | * |
108 | | * ~ If src2 is 8 bpp grayscale, we can do one of two things |
109 | | * (but see pixFadeWithGray() below): |
110 | | * (1) L_BLEND_GRAY: If src1 is 8 bpp, mix the two values, using |
111 | | * a fraction of src2 and (1 - fraction) of src1. |
112 | | * If src1 is 32 bpp (rgb), mix the fraction of src2 with |
113 | | * each of the color components in src1. |
114 | | * (2) L_BLEND_GRAY_WITH_INVERSE: Use the grayscale value in src2 |
115 | | * to determine how much of the inverse of a src1 pixel is |
116 | | * to be combined with the pixel value. The input fraction |
117 | | * further acts to scale the change in the src1 pixel. |
118 | | * The blending function is pixBlendGray(). |
119 | | * |
120 | | * ~ If src2 is color, we blend a given fraction of src2 with |
121 | | * src1. If src1 is 8 bpp, the resulting image is 32 bpp. |
122 | | * The blending function is pixBlendColor(). |
123 | | * |
124 | | * ~ For all three blending functions -- pixBlendMask(), pixBlendGray() |
125 | | * and pixBlendColor() -- you can apply the blender to the blendee |
126 | | * either in-place or generating a new pix. For the in-place |
127 | | * operation, this requires that the depth of the resulting pix |
128 | | * must equal that of the input pixs1. |
129 | | * |
130 | | * ~ We remove colormaps from src1 and src2 before blending. |
131 | | * Any quantization would have to be done after blending. |
132 | | * |
133 | | * We include another function, pixFadeWithGray(), that blends |
134 | | * a gray or color src1 with a gray src2. It does one of these things: |
135 | | * (1) L_BLEND_TO_WHITE: Fade the src1 pixels toward white by |
136 | | * a number times the value in src2. |
137 | | * (2) L_BLEND_TO_BLACK: Fade the src1 pixels toward black by |
138 | | * a number times the value in src2. |
139 | | * |
140 | | * Also included is a generalization of the so-called "hard light" |
141 | | * blending: pixBlendHardLight(). We generalize by allowing a fraction < 1.0 |
142 | | * of the blender to be admixed with the blendee. The standard function |
143 | | * does full mixing. |
144 | | * </pre> |
145 | | */ |
146 | | |
147 | | #ifdef HAVE_CONFIG_H |
148 | | #include <config_auto.h> |
149 | | #endif /* HAVE_CONFIG_H */ |
150 | | |
151 | | #include "allheaders.h" |
152 | | |
153 | | static l_int32 blendComponents(l_int32 a, l_int32 b, l_float32 fract); |
154 | | static l_int32 blendHardLightComponents(l_int32 a, l_int32 b, l_float32 fract); |
155 | | |
156 | | /*-------------------------------------------------------------* |
157 | | * Blending two images that are not colormapped * |
158 | | *-------------------------------------------------------------*/ |
159 | | /*! |
160 | | * \brief pixBlend() |
161 | | * |
162 | | * \param[in] pixs1 blendee |
163 | | * \param[in] pixs2 blender; typ. smaller |
164 | | * \param[in] x,y origin [UL corner] of pixs2 relative to |
165 | | * the origin of pixs1; can be < 0 |
166 | | * \param[in] fract blending fraction |
167 | | * \return pixd blended image, or null on error |
168 | | * |
169 | | * <pre> |
170 | | * Notes: |
171 | | * (1) This is a simple top-level interface. For more flexibility, |
172 | | * call directly into pixBlendMask(), etc. |
173 | | * </pre> |
174 | | */ |
175 | | PIX * |
176 | | pixBlend(PIX *pixs1, |
177 | | PIX *pixs2, |
178 | | l_int32 x, |
179 | | l_int32 y, |
180 | | l_float32 fract) |
181 | 0 | { |
182 | 0 | l_int32 w1, h1, d1, d2; |
183 | 0 | BOX *box; |
184 | 0 | PIX *pixc, *pixt, *pixd; |
185 | |
|
186 | 0 | if (!pixs1) |
187 | 0 | return (PIX *)ERROR_PTR("pixs1 not defined", __func__, NULL); |
188 | 0 | if (!pixs2) |
189 | 0 | return (PIX *)ERROR_PTR("pixs2 not defined", __func__, NULL); |
190 | | |
191 | | /* check relative depths */ |
192 | 0 | d1 = pixGetDepth(pixs1); |
193 | 0 | d2 = pixGetDepth(pixs2); |
194 | 0 | if (d1 == 1 && d2 > 1) |
195 | 0 | return (PIX *)ERROR_PTR("mixing gray or color with 1 bpp", |
196 | 0 | __func__, NULL); |
197 | | |
198 | | /* remove colormap from pixs2 if necessary */ |
199 | 0 | pixt = pixRemoveColormap(pixs2, REMOVE_CMAP_BASED_ON_SRC); |
200 | 0 | d2 = pixGetDepth(pixt); |
201 | | |
202 | | /* Check if pixs2 is clipped by its position with respect |
203 | | * to pixs1; if so, clip it and redefine x and y if necessary. |
204 | | * This actually isn't necessary, as the specific blending |
205 | | * functions do the clipping directly in the pixel loop |
206 | | * over pixs2, but it's included here to show how it can |
207 | | * easily be done on pixs2 first. */ |
208 | 0 | pixGetDimensions(pixs1, &w1, &h1, NULL); |
209 | 0 | box = boxCreate(-x, -y, w1, h1); /* box of pixs1 relative to pixs2 */ |
210 | 0 | pixc = pixClipRectangle(pixt, box, NULL); |
211 | 0 | boxDestroy(&box); |
212 | 0 | if (!pixc) { |
213 | 0 | L_WARNING("box doesn't overlap pix\n", __func__); |
214 | 0 | pixDestroy(&pixt); |
215 | 0 | return NULL; |
216 | 0 | } |
217 | 0 | x = L_MAX(0, x); |
218 | 0 | y = L_MAX(0, y); |
219 | |
|
220 | 0 | if (d2 == 1) { |
221 | 0 | pixd = pixBlendMask(NULL, pixs1, pixc, x, y, fract, |
222 | 0 | L_BLEND_WITH_INVERSE); |
223 | 0 | } else if (d2 == 8) { |
224 | 0 | pixd = pixBlendGray(NULL, pixs1, pixc, x, y, fract, |
225 | 0 | L_BLEND_GRAY, 0, 0); |
226 | 0 | } else { /* d2 == 32 */ |
227 | 0 | pixd = pixBlendColor(NULL, pixs1, pixc, x, y, fract, 0, 0); |
228 | 0 | } |
229 | |
|
230 | 0 | pixDestroy(&pixc); |
231 | 0 | pixDestroy(&pixt); |
232 | 0 | return pixd; |
233 | 0 | } |
234 | | |
235 | | |
236 | | /*! |
237 | | * \brief pixBlendMask() |
238 | | * |
239 | | * \param[in] pixd [optional]; either NULL or equal to pixs1 for in-place |
240 | | * \param[in] pixs1 blendee, depth > 1 |
241 | | * \param[in] pixs2 blender, 1 bpp; typ. smaller in size than pixs1 |
242 | | * \param[in] x,y origin [UL corner] of pixs2 relative to |
243 | | * the origin of pixs1; can be < 0 |
244 | | * \param[in] fract blending fraction |
245 | | * \param[in] type L_BLEND_WITH_INVERSE, L_BLEND_TO_WHITE, |
246 | | * L_BLEND_TO_BLACK |
247 | | * \return pixd if OK; null on error |
248 | | * |
249 | | * <pre> |
250 | | * Notes: |
251 | | * (1) Clipping of pixs2 to pixs1 is done in the inner pixel loop. |
252 | | * (2) If pixs1 has a colormap, it is removed. |
253 | | * (3) For inplace operation (pixs1 not cmapped), call it this way: |
254 | | * pixBlendMask(pixs1, pixs1, pixs2, ...) |
255 | | * (4) For generating a new pixd: |
256 | | * pixd = pixBlendMask(NULL, pixs1, pixs2, ...) |
257 | | * (5) Only call in-place if pixs1 does not have a colormap. |
258 | | * (6) Invalid %fract defaults to 0.5 with a warning. |
259 | | * Invalid %type defaults to L_BLEND_WITH_INVERSE with a warning. |
260 | | * </pre> |
261 | | */ |
262 | | PIX * |
263 | | pixBlendMask(PIX *pixd, |
264 | | PIX *pixs1, |
265 | | PIX *pixs2, |
266 | | l_int32 x, |
267 | | l_int32 y, |
268 | | l_float32 fract, |
269 | | l_int32 type) |
270 | 0 | { |
271 | 0 | l_int32 i, j, d, wc, hc, w, h, wplc; |
272 | 0 | l_int32 val, rval, gval, bval; |
273 | 0 | l_uint32 pixval; |
274 | 0 | l_uint32 *linec, *datac; |
275 | 0 | PIX *pixc, *pix1, *pix2; |
276 | |
|
277 | 0 | if (!pixs1) |
278 | 0 | return (PIX *)ERROR_PTR("pixs1 not defined", __func__, NULL); |
279 | 0 | if (!pixs2) |
280 | 0 | return (PIX *)ERROR_PTR("pixs2 not defined", __func__, NULL); |
281 | 0 | if (pixGetDepth(pixs1) == 1) |
282 | 0 | return (PIX *)ERROR_PTR("pixs1 is 1 bpp", __func__, NULL); |
283 | 0 | if (pixGetDepth(pixs2) != 1) |
284 | 0 | return (PIX *)ERROR_PTR("pixs2 not 1 bpp", __func__, NULL); |
285 | 0 | if (pixd == pixs1 && pixGetColormap(pixs1)) |
286 | 0 | return (PIX *)ERROR_PTR("inplace; pixs1 has colormap", __func__, NULL); |
287 | 0 | if (pixd && (pixd != pixs1)) |
288 | 0 | return (PIX *)ERROR_PTR("pixd must be NULL or pixs1", __func__, NULL); |
289 | 0 | if (fract < 0.0 || fract > 1.0) { |
290 | 0 | L_WARNING("fract must be in [0.0, 1.0]; setting to 0.5\n", __func__); |
291 | 0 | fract = 0.5; |
292 | 0 | } |
293 | 0 | if (type != L_BLEND_WITH_INVERSE && type != L_BLEND_TO_WHITE && |
294 | 0 | type != L_BLEND_TO_BLACK) { |
295 | 0 | L_WARNING("invalid blend type; setting to L_BLEND_WITH_INVERSE\n", |
296 | 0 | __func__); |
297 | 0 | type = L_BLEND_WITH_INVERSE; |
298 | 0 | } |
299 | | |
300 | | /* If pixd != NULL, we know that it is equal to pixs1 and |
301 | | * that pixs1 does not have a colormap, so that an in-place operation |
302 | | * can be done. Otherwise, remove colormap from pixs1 if |
303 | | * it exists and unpack to at least 8 bpp if necessary, |
304 | | * to do the blending on a new pix. */ |
305 | 0 | if (!pixd) { |
306 | 0 | pix1 = pixRemoveColormap(pixs1, REMOVE_CMAP_BASED_ON_SRC); |
307 | 0 | if (pixGetDepth(pix1) < 8) |
308 | 0 | pix2 = pixConvertTo8(pix1, FALSE); |
309 | 0 | else |
310 | 0 | pix2 = pixClone(pix1); |
311 | 0 | pixd = pixCopy(NULL, pix2); |
312 | 0 | pixDestroy(&pix1); |
313 | 0 | pixDestroy(&pix2); |
314 | 0 | } |
315 | |
|
316 | 0 | pixGetDimensions(pixd, &w, &h, &d); /* d must be either 8 or 32 bpp */ |
317 | 0 | pixc = pixClone(pixs2); |
318 | 0 | wc = pixGetWidth(pixc); |
319 | 0 | hc = pixGetHeight(pixc); |
320 | 0 | datac = pixGetData(pixc); |
321 | 0 | wplc = pixGetWpl(pixc); |
322 | | |
323 | | /* Check limits for src1, in case clipping was not done. */ |
324 | 0 | switch (type) |
325 | 0 | { |
326 | 0 | case L_BLEND_WITH_INVERSE: |
327 | | /* |
328 | | * The basic logic for this blending is: |
329 | | * p --> (1 - f) * p + f * (1 - p) |
330 | | * where p is a normalized value: p = pixval / 255. |
331 | | * Thus, |
332 | | * p --> p + f * (1 - 2 * p) |
333 | | */ |
334 | 0 | for (i = 0; i < hc; i++) { |
335 | 0 | if (i + y < 0 || i + y >= h) continue; |
336 | 0 | linec = datac + i * wplc; |
337 | 0 | for (j = 0; j < wc; j++) { |
338 | 0 | if (j + x < 0 || j + x >= w) continue; |
339 | 0 | bval = GET_DATA_BIT(linec, j); |
340 | 0 | if (bval) { |
341 | 0 | switch (d) |
342 | 0 | { |
343 | 0 | case 8: |
344 | 0 | pixGetPixel(pixd, x + j, y + i, &pixval); |
345 | 0 | val = (l_int32)(pixval + fract * (255 - 2 * pixval)); |
346 | 0 | pixSetPixel(pixd, x + j, y + i, val); |
347 | 0 | break; |
348 | 0 | case 32: |
349 | 0 | pixGetPixel(pixd, x + j, y + i, &pixval); |
350 | 0 | extractRGBValues(pixval, &rval, &gval, &bval); |
351 | 0 | rval = (l_int32)(rval + fract * (255 - 2 * rval)); |
352 | 0 | gval = (l_int32)(gval + fract * (255 - 2 * gval)); |
353 | 0 | bval = (l_int32)(bval + fract * (255 - 2 * bval)); |
354 | 0 | composeRGBPixel(rval, gval, bval, &pixval); |
355 | 0 | pixSetPixel(pixd, x + j, y + i, pixval); |
356 | 0 | break; |
357 | 0 | default: |
358 | 0 | L_WARNING("d neither 8 nor 32 bpp; no blend\n", |
359 | 0 | __func__); |
360 | 0 | } |
361 | 0 | } |
362 | 0 | } |
363 | 0 | } |
364 | 0 | break; |
365 | 0 | case L_BLEND_TO_WHITE: |
366 | | /* |
367 | | * The basic logic for this blending is: |
368 | | * p --> p + f * (1 - p) (p normalized to [0...1]) |
369 | | */ |
370 | 0 | for (i = 0; i < hc; i++) { |
371 | 0 | if (i + y < 0 || i + y >= h) continue; |
372 | 0 | linec = datac + i * wplc; |
373 | 0 | for (j = 0; j < wc; j++) { |
374 | 0 | if (j + x < 0 || j + x >= w) continue; |
375 | 0 | bval = GET_DATA_BIT(linec, j); |
376 | 0 | if (bval) { |
377 | 0 | switch (d) |
378 | 0 | { |
379 | 0 | case 8: |
380 | 0 | pixGetPixel(pixd, x + j, y + i, &pixval); |
381 | 0 | val = (l_int32)(pixval + fract * (255 - pixval)); |
382 | 0 | pixSetPixel(pixd, x + j, y + i, val); |
383 | 0 | break; |
384 | 0 | case 32: |
385 | 0 | pixGetPixel(pixd, x + j, y + i, &pixval); |
386 | 0 | extractRGBValues(pixval, &rval, &gval, &bval); |
387 | 0 | rval = (l_int32)(rval + fract * (255 - rval)); |
388 | 0 | gval = (l_int32)(gval + fract * (255 - gval)); |
389 | 0 | bval = (l_int32)(bval + fract * (255 - bval)); |
390 | 0 | composeRGBPixel(rval, gval, bval, &pixval); |
391 | 0 | pixSetPixel(pixd, x + j, y + i, pixval); |
392 | 0 | break; |
393 | 0 | default: |
394 | 0 | L_WARNING("d neither 8 nor 32 bpp; no blend\n", |
395 | 0 | __func__); |
396 | 0 | } |
397 | 0 | } |
398 | 0 | } |
399 | 0 | } |
400 | 0 | break; |
401 | 0 | case L_BLEND_TO_BLACK: |
402 | | /* |
403 | | * The basic logic for this blending is: |
404 | | * p --> (1 - f) * p (p normalized to [0...1]) |
405 | | */ |
406 | 0 | for (i = 0; i < hc; i++) { |
407 | 0 | if (i + y < 0 || i + y >= h) continue; |
408 | 0 | linec = datac + i * wplc; |
409 | 0 | for (j = 0; j < wc; j++) { |
410 | 0 | if (j + x < 0 || j + x >= w) continue; |
411 | 0 | bval = GET_DATA_BIT(linec, j); |
412 | 0 | if (bval) { |
413 | 0 | switch (d) |
414 | 0 | { |
415 | 0 | case 8: |
416 | 0 | pixGetPixel(pixd, x + j, y + i, &pixval); |
417 | 0 | val = (l_int32)((1. - fract) * pixval); |
418 | 0 | pixSetPixel(pixd, x + j, y + i, val); |
419 | 0 | break; |
420 | 0 | case 32: |
421 | 0 | pixGetPixel(pixd, x + j, y + i, &pixval); |
422 | 0 | extractRGBValues(pixval, &rval, &gval, &bval); |
423 | 0 | rval = (l_int32)((1. - fract) * rval); |
424 | 0 | gval = (l_int32)((1. - fract) * gval); |
425 | 0 | bval = (l_int32)((1. - fract) * bval); |
426 | 0 | composeRGBPixel(rval, gval, bval, &pixval); |
427 | 0 | pixSetPixel(pixd, x + j, y + i, pixval); |
428 | 0 | break; |
429 | 0 | default: |
430 | 0 | L_WARNING("d neither 8 nor 32 bpp; no blend\n", |
431 | 0 | __func__); |
432 | 0 | } |
433 | 0 | } |
434 | 0 | } |
435 | 0 | } |
436 | 0 | break; |
437 | 0 | default: |
438 | 0 | L_WARNING("invalid binary mask blend type\n", __func__); |
439 | 0 | break; |
440 | 0 | } |
441 | | |
442 | 0 | pixDestroy(&pixc); |
443 | 0 | return pixd; |
444 | 0 | } |
445 | | |
446 | | |
447 | | /*! |
448 | | * \brief pixBlendGray() |
449 | | * |
450 | | * \param[in] pixd [optional] either equal to pixs1 for in-place, |
451 | | * or NULL |
452 | | * \param[in] pixs1 blendee, depth > 1 |
453 | | * \param[in] pixs2 blender, any depth; typically, the area of |
454 | | * pixs2 is smaller than pixs1 |
455 | | * \param[in] x,y origin [UL corner] of pixs2 relative to |
456 | | * the origin of pixs1; can be < 0 |
457 | | * \param[in] fract blending fraction |
458 | | * \param[in] type L_BLEND_GRAY, L_BLEND_GRAY_WITH_INVERSE |
459 | | * \param[in] transparent 1 to use transparency; 0 otherwise |
460 | | * \param[in] transpix pixel grayval in pixs2 that is to be transparent |
461 | | * \return pixd if OK; pixs1 on error |
462 | | * |
463 | | * <pre> |
464 | | * Notes: |
465 | | * (1) For inplace operation (pixs1 not cmapped), call it this way: |
466 | | * pixBlendGray(pixs1, pixs1, pixs2, ...) |
467 | | * (2) For generating a new pixd: |
468 | | * pixd = pixBlendGray(NULL, pixs1, pixs2, ...) |
469 | | * (3) Clipping of pixs2 to pixs1 is done in the inner pixel loop. |
470 | | * (4) If pixs1 has a colormap, it is removed; otherwise, if pixs1 |
471 | | * has depth < 8, it is unpacked to generate a 8 bpp pix. |
472 | | * (5) If transparent = 0, the blending fraction (fract) is |
473 | | * applied equally to all pixels. |
474 | | * (6) If transparent = 1, all pixels of value transpix (typically |
475 | | * either 0 or 0xff) in pixs2 are transparent in the blend. |
476 | | * (7) After processing pixs1, it is either 8 bpp or 32 bpp: |
477 | | * ~ if 8 bpp, the fraction of pixs2 is mixed with pixs1. |
478 | | * ~ if 32 bpp, each component of pixs1 is mixed with |
479 | | * the same fraction of pixs2. |
480 | | * (8) For L_BLEND_GRAY_WITH_INVERSE, the white values of the blendee |
481 | | * (cval == 255 in the code below) result in a delta of 0. |
482 | | * Thus, these pixels are intrinsically transparent! |
483 | | * The "pivot" value of the src, at which no blending occurs, is |
484 | | * 128. Compare with the adaptive pivot in pixBlendGrayAdapt(). |
485 | | * (9) Invalid %fract defaults to 0.5 with a warning. |
486 | | * Invalid %type defaults to L_BLEND_GRAY with a warning. |
487 | | * </pre> |
488 | | */ |
489 | | PIX * |
490 | | pixBlendGray(PIX *pixd, |
491 | | PIX *pixs1, |
492 | | PIX *pixs2, |
493 | | l_int32 x, |
494 | | l_int32 y, |
495 | | l_float32 fract, |
496 | | l_int32 type, |
497 | | l_int32 transparent, |
498 | | l_uint32 transpix) |
499 | 0 | { |
500 | 0 | l_int32 i, j, d, wc, hc, w, h, wplc, wpld, delta; |
501 | 0 | l_int32 ival, irval, igval, ibval, cval, dval; |
502 | 0 | l_uint32 val32; |
503 | 0 | l_uint32 *linec, *lined, *datac, *datad; |
504 | 0 | PIX *pixc, *pix1, *pix2; |
505 | |
|
506 | 0 | if (!pixs1) |
507 | 0 | return (PIX *)ERROR_PTR("pixs1 not defined", __func__, pixd); |
508 | 0 | if (!pixs2) |
509 | 0 | return (PIX *)ERROR_PTR("pixs2 not defined", __func__, pixd); |
510 | 0 | if (pixGetDepth(pixs1) == 1) |
511 | 0 | return (PIX *)ERROR_PTR("pixs1 is 1 bpp", __func__, pixd); |
512 | 0 | if (pixd == pixs1 && pixGetColormap(pixs1)) |
513 | 0 | return (PIX *)ERROR_PTR("can't do in-place with cmap", __func__, pixd); |
514 | 0 | if (pixd && (pixd != pixs1)) |
515 | 0 | return (PIX *)ERROR_PTR("pixd must be NULL or pixs1", __func__, pixd); |
516 | 0 | if (fract < 0.0 || fract > 1.0) { |
517 | 0 | L_WARNING("fract must be in [0.0, 1.0]; setting to 0.5\n", __func__); |
518 | 0 | fract = 0.5; |
519 | 0 | } |
520 | 0 | if (type != L_BLEND_GRAY && type != L_BLEND_GRAY_WITH_INVERSE) { |
521 | 0 | L_WARNING("invalid blend type; setting to L_BLEND_GRAY\n", __func__); |
522 | 0 | type = L_BLEND_GRAY; |
523 | 0 | } |
524 | | |
525 | | /* If pixd != NULL, we know that it is equal to pixs1 and |
526 | | * that pixs1 does not have a colormap, so that an in-place operation |
527 | | * can be done. Otherwise, remove colormap from pixs1 if |
528 | | * it exists and unpack to at least 8 bpp if necessary, |
529 | | * to do the blending on a new pix. */ |
530 | 0 | if (!pixd) { |
531 | 0 | pix1 = pixRemoveColormap(pixs1, REMOVE_CMAP_BASED_ON_SRC); |
532 | 0 | if (pixGetDepth(pix1) < 8) |
533 | 0 | pix2 = pixConvertTo8(pix1, FALSE); |
534 | 0 | else |
535 | 0 | pix2 = pixClone(pix1); |
536 | 0 | pixd = pixCopy(NULL, pix2); |
537 | 0 | pixDestroy(&pix1); |
538 | 0 | pixDestroy(&pix2); |
539 | 0 | } |
540 | |
|
541 | 0 | pixGetDimensions(pixd, &w, &h, &d); /* 8 or 32 bpp */ |
542 | 0 | wpld = pixGetWpl(pixd); |
543 | 0 | datad = pixGetData(pixd); |
544 | 0 | pixc = pixConvertTo8(pixs2, 0); |
545 | 0 | pixGetDimensions(pixc, &wc, &hc, NULL); |
546 | 0 | datac = pixGetData(pixc); |
547 | 0 | wplc = pixGetWpl(pixc); |
548 | | |
549 | | /* Check limits for src1, in case clipping was not done */ |
550 | 0 | if (type == L_BLEND_GRAY) { |
551 | | /* |
552 | | * The basic logic for this blending is: |
553 | | * p --> (1 - f) * p + f * c |
554 | | * where c is the 8 bpp blender. All values are normalized to [0...1]. |
555 | | */ |
556 | 0 | for (i = 0; i < hc; i++) { |
557 | 0 | if (i + y < 0 || i + y >= h) continue; |
558 | 0 | linec = datac + i * wplc; |
559 | 0 | lined = datad + (i + y) * wpld; |
560 | 0 | switch (d) |
561 | 0 | { |
562 | 0 | case 8: |
563 | 0 | for (j = 0; j < wc; j++) { |
564 | 0 | if (j + x < 0 || j + x >= w) continue; |
565 | 0 | cval = GET_DATA_BYTE(linec, j); |
566 | 0 | if (transparent == 0 || cval != transpix) { |
567 | 0 | dval = GET_DATA_BYTE(lined, j + x); |
568 | 0 | ival = (l_int32)((1. - fract) * dval + fract * cval); |
569 | 0 | SET_DATA_BYTE(lined, j + x, ival); |
570 | 0 | } |
571 | 0 | } |
572 | 0 | break; |
573 | 0 | case 32: |
574 | 0 | for (j = 0; j < wc; j++) { |
575 | 0 | if (j + x < 0 || j + x >= w) continue; |
576 | 0 | cval = GET_DATA_BYTE(linec, j); |
577 | 0 | if (transparent == 0 || cval != transpix) { |
578 | 0 | val32 = *(lined + j + x); |
579 | 0 | extractRGBValues(val32, &irval, &igval, &ibval); |
580 | 0 | irval = (l_int32)((1. - fract) * irval + fract * cval); |
581 | 0 | igval = (l_int32)((1. - fract) * igval + fract * cval); |
582 | 0 | ibval = (l_int32)((1. - fract) * ibval + fract * cval); |
583 | 0 | composeRGBPixel(irval, igval, ibval, &val32); |
584 | 0 | *(lined + j + x) = val32; |
585 | 0 | } |
586 | 0 | } |
587 | 0 | break; |
588 | 0 | default: |
589 | 0 | break; /* shouldn't happen */ |
590 | 0 | } |
591 | 0 | } |
592 | 0 | } else { /* L_BLEND_GRAY_WITH_INVERSE */ |
593 | 0 | for (i = 0; i < hc; i++) { |
594 | 0 | if (i + y < 0 || i + y >= h) continue; |
595 | 0 | linec = datac + i * wplc; |
596 | 0 | lined = datad + (i + y) * wpld; |
597 | 0 | switch (d) |
598 | 0 | { |
599 | 0 | case 8: |
600 | | /* |
601 | | * For 8 bpp, the dest pix is shifted by a signed amount |
602 | | * proportional to the distance from 128 (the pivot value), |
603 | | * and to the darkness of src2. If the dest is darker |
604 | | * than 128, it becomes lighter, and v.v. |
605 | | * The basic logic is: |
606 | | * d --> d + f * (0.5 - d) * (1 - c) |
607 | | * where d and c are normalized pixel values for src1 and |
608 | | * src2, respectively, with 8 bit normalization to [0...1]. |
609 | | */ |
610 | 0 | for (j = 0; j < wc; j++) { |
611 | 0 | if (j + x < 0 || j + x >= w) continue; |
612 | 0 | cval = GET_DATA_BYTE(linec, j); |
613 | 0 | if (transparent == 0 || cval != transpix) { |
614 | 0 | ival = GET_DATA_BYTE(lined, j + x); |
615 | 0 | delta = (128 - ival) * (255 - cval) / 256; |
616 | 0 | ival += (l_int32)(fract * delta + 0.5); |
617 | 0 | SET_DATA_BYTE(lined, j + x, ival); |
618 | 0 | } |
619 | 0 | } |
620 | 0 | break; |
621 | 0 | case 32: |
622 | | /* Each component is shifted by the same formula for 8 bpp */ |
623 | 0 | for (j = 0; j < wc; j++) { |
624 | 0 | if (j + x < 0 || j + x >= w) continue; |
625 | 0 | cval = GET_DATA_BYTE(linec, j); |
626 | 0 | if (transparent == 0 || cval != transpix) { |
627 | 0 | val32 = *(lined + j + x); |
628 | 0 | extractRGBValues(val32, &irval, &igval, &ibval); |
629 | 0 | delta = (128 - irval) * (255 - cval) / 256; |
630 | 0 | irval += (l_int32)(fract * delta + 0.5); |
631 | 0 | delta = (128 - igval) * (255 - cval) / 256; |
632 | 0 | igval += (l_int32)(fract * delta + 0.5); |
633 | 0 | delta = (128 - ibval) * (255 - cval) / 256; |
634 | 0 | ibval += (l_int32)(fract * delta + 0.5); |
635 | 0 | composeRGBPixel(irval, igval, ibval, &val32); |
636 | 0 | *(lined + j + x) = val32; |
637 | 0 | } |
638 | 0 | } |
639 | 0 | break; |
640 | 0 | default: |
641 | 0 | break; /* shouldn't happen */ |
642 | 0 | } |
643 | 0 | } |
644 | 0 | } |
645 | | |
646 | 0 | pixDestroy(&pixc); |
647 | 0 | return pixd; |
648 | 0 | } |
649 | | |
650 | | |
651 | | /*! |
652 | | * \brief pixBlendGrayInverse() |
653 | | * |
654 | | * \param[in] pixd [optional] either equal to pixs1 for in-place, or NULL |
655 | | * \param[in] pixd [optional] either NULL or equal to pixs1 for in-place |
656 | | * \param[in] pixs1 blendee, depth > 1 |
657 | | * \param[in] pixs2 blender, any depth; typ. smaller in size than pixs1 |
658 | | * \param[in] x,y origin [UL corner] of pixs2 relative to |
659 | | * the origin of pixs1; can be < 0 |
660 | | * \param[in] fract blending fraction |
661 | | * \return pixd if OK; pixs1 on error |
662 | | * |
663 | | * <pre> |
664 | | * Notes: |
665 | | * (1) For inplace operation (pixs1 not cmapped), call it this way: |
666 | | * pixBlendGrayInverse(pixs1, pixs1, pixs2, ...) |
667 | | * (2) For generating a new pixd: |
668 | | * pixd = pixBlendGrayInverse(NULL, pixs1, pixs2, ...) |
669 | | * (3) Clipping of pixs2 to pixs1 is done in the inner pixel loop. |
670 | | * (4) If pixs1 has a colormap, it is removed; otherwise if pixs1 |
671 | | * has depth < 8, it is unpacked to generate a 8 bpp pix. |
672 | | * (5) This is a no-nonsense blender. It changes the src1 pixel except |
673 | | * when the src1 pixel is midlevel gray. Use fract == 1 for the most |
674 | | * aggressive blending, where, if the gray pixel in pixs2 is 0, |
675 | | * we get a complete inversion of the color of the src pixel in pixs1. |
676 | | * (6) The basic logic is that each component transforms by: |
677 | | d --> c * d + (1 - c ) * (f * (1 - d) + d * (1 - f)) |
678 | | * where c is the blender pixel from pixs2, |
679 | | * f is %fract, |
680 | | * c and d are normalized to [0...1] |
681 | | * This has the property that for f == 0 (no blend) or c == 1 (white): |
682 | | * d --> d |
683 | | * For c == 0 (black) we get maximum inversion: |
684 | | * d --> f * (1 - d) + d * (1 - f) [inversion by fraction f] |
685 | | * </pre> |
686 | | */ |
687 | | PIX * |
688 | | pixBlendGrayInverse(PIX *pixd, |
689 | | PIX *pixs1, |
690 | | PIX *pixs2, |
691 | | l_int32 x, |
692 | | l_int32 y, |
693 | | l_float32 fract) |
694 | 0 | { |
695 | 0 | l_int32 i, j, d, wc, hc, w, h, wplc, wpld; |
696 | 0 | l_int32 irval, igval, ibval, cval, dval; |
697 | 0 | l_float32 a; |
698 | 0 | l_uint32 val32; |
699 | 0 | l_uint32 *linec, *lined, *datac, *datad; |
700 | 0 | PIX *pixc, *pix1, *pix2; |
701 | |
|
702 | 0 | if (!pixs1) |
703 | 0 | return (PIX *)ERROR_PTR("pixs1 not defined", __func__, pixd); |
704 | 0 | if (!pixs2) |
705 | 0 | return (PIX *)ERROR_PTR("pixs2 not defined", __func__, pixd); |
706 | 0 | if (pixGetDepth(pixs1) == 1) |
707 | 0 | return (PIX *)ERROR_PTR("pixs1 is 1 bpp", __func__, pixd); |
708 | 0 | if (pixd == pixs1 && pixGetColormap(pixs1)) |
709 | 0 | return (PIX *)ERROR_PTR("can't do in-place with cmap", __func__, pixd); |
710 | 0 | if (pixd && (pixd != pixs1)) |
711 | 0 | return (PIX *)ERROR_PTR("pixd must be NULL or pixs1", __func__, pixd); |
712 | 0 | if (fract < 0.0 || fract > 1.0) { |
713 | 0 | L_WARNING("fract must be in [0.0, 1.0]; setting to 0.5\n", __func__); |
714 | 0 | fract = 0.5; |
715 | 0 | } |
716 | | |
717 | | /* If pixd != NULL, we know that it is equal to pixs1 and |
718 | | * that pixs1 does not have a colormap, so that an in-place operation |
719 | | * can be done. Otherwise, remove colormap from pixs1 if |
720 | | * it exists and unpack to at least 8 bpp if necessary, |
721 | | * to do the blending on a new pix. */ |
722 | 0 | if (!pixd) { |
723 | 0 | pix1 = pixRemoveColormap(pixs1, REMOVE_CMAP_BASED_ON_SRC); |
724 | 0 | if (pixGetDepth(pix1) < 8) |
725 | 0 | pix2 = pixConvertTo8(pix1, FALSE); |
726 | 0 | else |
727 | 0 | pix2 = pixClone(pix1); |
728 | 0 | pixd = pixCopy(NULL, pix2); |
729 | 0 | pixDestroy(&pix1); |
730 | 0 | pixDestroy(&pix2); |
731 | 0 | } |
732 | |
|
733 | 0 | pixGetDimensions(pixd, &w, &h, &d); /* 8 or 32 bpp */ |
734 | 0 | wpld = pixGetWpl(pixd); |
735 | 0 | datad = pixGetData(pixd); |
736 | 0 | pixc = pixConvertTo8(pixs2, 0); |
737 | 0 | pixGetDimensions(pixc, &wc, &hc, NULL); |
738 | 0 | datac = pixGetData(pixc); |
739 | 0 | wplc = pixGetWpl(pixc); |
740 | | |
741 | | /* Check limits for src1, in case clipping was not done */ |
742 | 0 | for (i = 0; i < hc; i++) { |
743 | 0 | if (i + y < 0 || i + y >= h) continue; |
744 | 0 | linec = datac + i * wplc; |
745 | 0 | lined = datad + (i + y) * wpld; |
746 | 0 | switch (d) |
747 | 0 | { |
748 | 0 | case 8: |
749 | 0 | for (j = 0; j < wc; j++) { |
750 | 0 | if (j + x < 0 || j + x >= w) continue; |
751 | 0 | cval = GET_DATA_BYTE(linec, j); |
752 | 0 | dval = GET_DATA_BYTE(lined, j + x); |
753 | 0 | a = (1.0 - fract) * dval + fract * (255.0 - dval); |
754 | 0 | dval = (l_int32)(cval * dval / 255.0 + |
755 | 0 | a * (255.0 - cval) / 255.0); |
756 | 0 | SET_DATA_BYTE(lined, j + x, dval); |
757 | 0 | } |
758 | 0 | break; |
759 | 0 | case 32: |
760 | 0 | for (j = 0; j < wc; j++) { |
761 | 0 | if (j + x < 0 || j + x >= w) continue; |
762 | 0 | cval = GET_DATA_BYTE(linec, j); |
763 | 0 | val32 = *(lined + j + x); |
764 | 0 | extractRGBValues(val32, &irval, &igval, &ibval); |
765 | 0 | a = (1.0 - fract) * irval + fract * (255.0 - irval); |
766 | 0 | irval = (l_int32)(cval * irval / 255.0 + |
767 | 0 | a * (255.0 - cval) / 255.0); |
768 | 0 | a = (1.0 - fract) * igval + fract * (255.0 - igval); |
769 | 0 | igval = (l_int32)(cval * igval / 255.0 + |
770 | 0 | a * (255.0 - cval) / 255.0); |
771 | 0 | a = (1.0 - fract) * ibval + fract * (255.0 - ibval); |
772 | 0 | ibval = (l_int32)(cval * ibval / 255.0 + |
773 | 0 | a * (255.0 - cval) / 255.0); |
774 | 0 | composeRGBPixel(irval, igval, ibval, &val32); |
775 | 0 | *(lined + j + x) = val32; |
776 | 0 | } |
777 | 0 | break; |
778 | 0 | default: |
779 | 0 | break; /* shouldn't happen */ |
780 | 0 | } |
781 | 0 | } |
782 | | |
783 | 0 | pixDestroy(&pixc); |
784 | 0 | return pixd; |
785 | 0 | } |
786 | | |
787 | | |
788 | | /*! |
789 | | * \brief pixBlendColor() |
790 | | * |
791 | | * \param[in] pixd [optional] either equal to pixs1 for in-place, |
792 | | * or NULL |
793 | | * \param[in] pixs1 blendee; depth > 1 |
794 | | * \param[in] pixs2 blender, any depth; typically, the area of |
795 | | * pixs2 is smaller than pixs1 |
796 | | * \param[in] x,y origin [UL corner] of pixs2 relative to |
797 | | * the origin of pixs1 |
798 | | * \param[in] fract blending fraction |
799 | | * \param[in] transparent 1 to use transparency; 0 otherwise |
800 | | * \param[in] transpix pixel color in pixs2 that is to be transparent |
801 | | * \return pixd, or null on error |
802 | | * |
803 | | * <pre> |
804 | | * Notes: |
805 | | * (1) For inplace operation (pixs1 must be 32 bpp), call it this way: |
806 | | * pixBlendColor(pixs1, pixs1, pixs2, ...) |
807 | | * (2) For generating a new pixd: |
808 | | * pixd = pixBlendColor(NULL, pixs1, pixs2, ...) |
809 | | * (3) If pixs2 is not 32 bpp rgb, it is converted. |
810 | | * (4) Clipping of pixs2 to pixs1 is done in the inner pixel loop. |
811 | | * (5) If pixs1 has a colormap, it is removed to generate a 32 bpp pix. |
812 | | * (6) If pixs1 has depth < 32, it is unpacked to generate a 32 bpp pix. |
813 | | * (7) If transparent = 0, the blending fraction (fract) is |
814 | | * applied equally to all pixels. |
815 | | * (8) If transparent = 1, all pixels of value transpix (typically |
816 | | * either 0 or 0xffffff00) in pixs2 are transparent in the blend. |
817 | | * </pre> |
818 | | */ |
819 | | PIX * |
820 | | pixBlendColor(PIX *pixd, |
821 | | PIX *pixs1, |
822 | | PIX *pixs2, |
823 | | l_int32 x, |
824 | | l_int32 y, |
825 | | l_float32 fract, |
826 | | l_int32 transparent, |
827 | | l_uint32 transpix) |
828 | 0 | { |
829 | 0 | l_int32 i, j, wc, hc, w, h, wplc, wpld; |
830 | 0 | l_int32 rval, gval, bval, rcval, gcval, bcval; |
831 | 0 | l_uint32 cval32, val32; |
832 | 0 | l_uint32 *linec, *lined, *datac, *datad; |
833 | 0 | PIX *pixc; |
834 | |
|
835 | 0 | if (!pixs1) |
836 | 0 | return (PIX *)ERROR_PTR("pixs1 not defined", __func__, NULL); |
837 | 0 | if (!pixs2) |
838 | 0 | return (PIX *)ERROR_PTR("pixs2 not defined", __func__, NULL); |
839 | 0 | if (pixGetDepth(pixs1) == 1) |
840 | 0 | return (PIX *)ERROR_PTR("pixs1 is 1 bpp", __func__, NULL); |
841 | 0 | if (pixd == pixs1 && pixGetDepth(pixs1) != 32) |
842 | 0 | return (PIX *)ERROR_PTR("inplace; pixs1 not 32 bpp", __func__, NULL); |
843 | 0 | if (pixd && (pixd != pixs1)) |
844 | 0 | return (PIX *)ERROR_PTR("pixd must be NULL or pixs1", __func__, NULL); |
845 | 0 | if (fract < 0.0 || fract > 1.0) { |
846 | 0 | L_WARNING("fract must be in [0.0, 1.0]; setting to 0.5\n", __func__); |
847 | 0 | fract = 0.5; |
848 | 0 | } |
849 | | |
850 | | /* If pixd != null, we know that it is equal to pixs1 and |
851 | | * that pixs1 is 32 bpp rgb, so that an in-place operation |
852 | | * can be done. Otherwise, pixConvertTo32() will remove a |
853 | | * colormap from pixs1 if it exists and unpack to 32 bpp |
854 | | * (if necessary) to do the blending on a new 32 bpp Pix. */ |
855 | 0 | if (!pixd) |
856 | 0 | pixd = pixConvertTo32(pixs1); |
857 | 0 | pixGetDimensions(pixd, &w, &h, NULL); |
858 | 0 | wpld = pixGetWpl(pixd); |
859 | 0 | datad = pixGetData(pixd); |
860 | 0 | pixc = pixConvertTo32(pixs2); /* blend with 32 bpp rgb */ |
861 | 0 | pixGetDimensions(pixc, &wc, &hc, NULL); |
862 | 0 | datac = pixGetData(pixc); |
863 | 0 | wplc = pixGetWpl(pixc); |
864 | | |
865 | | /* Check limits for src1, in case clipping was not done */ |
866 | 0 | for (i = 0; i < hc; i++) { |
867 | | /* |
868 | | * The basic logic for this blending is: |
869 | | * p --> (1 - f) * p + f * c |
870 | | * for each color channel. c is a color component of the blender. |
871 | | * All values are normalized to [0...1]. |
872 | | */ |
873 | 0 | if (i + y < 0 || i + y >= h) continue; |
874 | 0 | linec = datac + i * wplc; |
875 | 0 | lined = datad + (i + y) * wpld; |
876 | 0 | for (j = 0; j < wc; j++) { |
877 | 0 | if (j + x < 0 || j + x >= w) continue; |
878 | 0 | cval32 = *(linec + j); |
879 | 0 | if (transparent == 0 || |
880 | 0 | ((cval32 & 0xffffff00) != (transpix & 0xffffff00))) { |
881 | 0 | val32 = *(lined + j + x); |
882 | 0 | extractRGBValues(cval32, &rcval, &gcval, &bcval); |
883 | 0 | extractRGBValues(val32, &rval, &gval, &bval); |
884 | 0 | rval = (l_int32)((1. - fract) * rval + fract * rcval); |
885 | 0 | gval = (l_int32)((1. - fract) * gval + fract * gcval); |
886 | 0 | bval = (l_int32)((1. - fract) * bval + fract * bcval); |
887 | 0 | composeRGBPixel(rval, gval, bval, &val32); |
888 | 0 | *(lined + j + x) = val32; |
889 | 0 | } |
890 | 0 | } |
891 | 0 | } |
892 | |
|
893 | 0 | pixDestroy(&pixc); |
894 | 0 | return pixd; |
895 | 0 | } |
896 | | |
897 | | |
898 | | /* |
899 | | * \brief pixBlendColorByChannel() |
900 | | * |
901 | | * \param[in] pixd [optional] either equal to pixs1 for in-place, |
902 | | * or NULL |
903 | | * \param[in] pixs1 blendee; depth > 1 |
904 | | * \param[in] pixs2 blender, any depth; typically, the area of |
905 | | * pixs2 is smaller than pixs1 |
906 | | * \param[in] x,y origin [UL corner] of pixs2 relative to |
907 | | * the origin of pixs1 |
908 | | * \param[in] rfract blending fraction in red channel |
909 | | * \param[in] gfract blending fraction in green channel |
910 | | * \param[in] bfract blending fraction in blue channel |
911 | | * \param[in] transparent 1 to use transparency; 0 otherwise |
912 | | * \param[in] transpix pixel color in pixs2 that is to be transparent |
913 | | * \return pixd if OK; pixd on error |
914 | | * |
915 | | * <pre> |
916 | | * Notes: |
917 | | * (1) This generalizes pixBlendColor() in two ways: |
918 | | * (a) The mixing fraction is specified per channel. |
919 | | * (b) The mixing fraction may be < 0 or > 1, in which case, |
920 | | * the min or max of two images are taken, respectively. |
921 | | * (2) Specifically, |
922 | | * for p = pixs1[i], c = pixs2[i], f = fract[i], i = 1, 2, 3: |
923 | | * f < 0.0: p --> min(p, c) |
924 | | * 0.0 <= f <= 1.0: p --> (1 - f) * p + f * c |
925 | | * f > 1.0: p --> max(a, c) |
926 | | * Special cases: |
927 | | * f = 0: p --> p |
928 | | * f = 1: p --> c |
929 | | * (3) See usage notes in pixBlendColor() |
930 | | * (4) pixBlendColor() would be equivalent to |
931 | | * pixBlendColorChannel(..., fract, fract, fract, ...); |
932 | | * at a small cost of efficiency. |
933 | | * </pre> |
934 | | */ |
935 | | PIX * |
936 | | pixBlendColorByChannel(PIX *pixd, |
937 | | PIX *pixs1, |
938 | | PIX *pixs2, |
939 | | l_int32 x, |
940 | | l_int32 y, |
941 | | l_float32 rfract, |
942 | | l_float32 gfract, |
943 | | l_float32 bfract, |
944 | | l_int32 transparent, |
945 | | l_uint32 transpix) |
946 | 0 | { |
947 | 0 | l_int32 i, j, wc, hc, w, h, wplc, wpld; |
948 | 0 | l_int32 rval, gval, bval, rcval, gcval, bcval; |
949 | 0 | l_uint32 cval32, val32; |
950 | 0 | l_uint32 *linec, *lined, *datac, *datad; |
951 | 0 | PIX *pixc; |
952 | |
|
953 | 0 | if (!pixs1) |
954 | 0 | return (PIX *)ERROR_PTR("pixs1 not defined", __func__, pixd); |
955 | 0 | if (!pixs2) |
956 | 0 | return (PIX *)ERROR_PTR("pixs2 not defined", __func__, pixd); |
957 | 0 | if (pixGetDepth(pixs1) == 1) |
958 | 0 | return (PIX *)ERROR_PTR("pixs1 is 1 bpp", __func__, pixd); |
959 | 0 | if (pixd == pixs1 && pixGetDepth(pixs1) != 32) |
960 | 0 | return (PIX *)ERROR_PTR("inplace; pixs1 not 32 bpp", __func__, pixd); |
961 | 0 | if (pixd && (pixd != pixs1)) |
962 | 0 | return (PIX *)ERROR_PTR("pixd must be NULL or pixs1", __func__, pixd); |
963 | | |
964 | | /* If pixd != NULL, we know that it is equal to pixs1 and |
965 | | * that pixs1 is 32 bpp rgb, so that an in-place operation |
966 | | * can be done. Otherwise, pixConvertTo32() will remove a |
967 | | * colormap from pixs1 if it exists and unpack to 32 bpp |
968 | | * (if necessary) to do the blending on a new 32 bpp Pix. */ |
969 | 0 | if (!pixd) |
970 | 0 | pixd = pixConvertTo32(pixs1); |
971 | 0 | pixGetDimensions(pixd, &w, &h, NULL); |
972 | 0 | wpld = pixGetWpl(pixd); |
973 | 0 | datad = pixGetData(pixd); |
974 | 0 | pixc = pixConvertTo32(pixs2); |
975 | 0 | pixGetDimensions(pixc, &wc, &hc, NULL); |
976 | 0 | datac = pixGetData(pixc); |
977 | 0 | wplc = pixGetWpl(pixc); |
978 | | |
979 | | /* Check limits for src1, in case clipping was not done */ |
980 | 0 | for (i = 0; i < hc; i++) { |
981 | 0 | if (i + y < 0 || i + y >= h) continue; |
982 | 0 | linec = datac + i * wplc; |
983 | 0 | lined = datad + (i + y) * wpld; |
984 | 0 | for (j = 0; j < wc; j++) { |
985 | 0 | if (j + x < 0 || j + x >= w) continue; |
986 | 0 | cval32 = *(linec + j); |
987 | 0 | if (transparent == 0 || |
988 | 0 | ((cval32 & 0xffffff00) != (transpix & 0xffffff00))) { |
989 | 0 | val32 = *(lined + j + x); |
990 | 0 | extractRGBValues(cval32, &rcval, &gcval, &bcval); |
991 | 0 | extractRGBValues(val32, &rval, &gval, &bval); |
992 | 0 | rval = blendComponents(rval, rcval, rfract); |
993 | 0 | gval = blendComponents(gval, gcval, gfract); |
994 | 0 | bval = blendComponents(bval, bcval, bfract); |
995 | 0 | composeRGBPixel(rval, gval, bval, &val32); |
996 | 0 | *(lined + j + x) = val32; |
997 | 0 | } |
998 | 0 | } |
999 | 0 | } |
1000 | |
|
1001 | 0 | pixDestroy(&pixc); |
1002 | 0 | return pixd; |
1003 | 0 | } |
1004 | | |
1005 | | |
1006 | | static l_int32 |
1007 | | blendComponents(l_int32 a, |
1008 | | l_int32 b, |
1009 | | l_float32 fract) |
1010 | 0 | { |
1011 | 0 | if (fract < 0.) |
1012 | 0 | return ((a < b) ? a : b); |
1013 | 0 | if (fract > 1.) |
1014 | 0 | return ((a > b) ? a : b); |
1015 | 0 | return (l_int32)((1. - fract) * a + fract * b); |
1016 | 0 | } |
1017 | | |
1018 | | |
1019 | | /*! |
1020 | | * \brief pixBlendGrayAdapt() |
1021 | | * |
1022 | | * \param[in] pixd [optional] either equal to pixs1 for in-place, or NULL |
1023 | | * \param[in] pixs1 blendee; depth > 1 |
1024 | | * \param[in] pixs2 blender, any depth; typically, the area of |
1025 | | * pixs2 is smaller than pixs1 |
1026 | | * \param[in] x,y origin [UL corner] of pixs2 relative to |
1027 | | * the origin of pixs1; can be < 0 |
1028 | | * \param[in] fract blending fraction |
1029 | | * \param[in] shift >= 0 but <= 128: shift of zero blend value from |
1030 | | * median source; use -1 for default value; |
1031 | | * \return pixd if OK; pixs1 on error |
1032 | | * |
1033 | | * <pre> |
1034 | | * Notes: |
1035 | | * (1) For inplace operation (pixs1 not cmapped), call it this way: |
1036 | | * pixBlendGrayAdapt(pixs1, pixs1, pixs2, ...) |
1037 | | * For generating a new pixd: |
1038 | | * pixd = pixBlendGrayAdapt(NULL, pixs1, pixs2, ...) |
1039 | | * (2) Clipping of pixs2 to pixs1 is done in the inner pixel loop. |
1040 | | * (3) If pixs1 has a colormap, it is removed. |
1041 | | * (4) If pixs1 has depth < 8, it is unpacked to generate a 8 bpp pix. |
1042 | | * (5) This does a blend with inverse. Whereas in pixGlendGray(), the |
1043 | | * zero blend point is where the blendee pixel is 128, here |
1044 | | * the zero blend point is found adaptively, with respect to the |
1045 | | * median of the blendee region. If the median is < 128, |
1046 | | * the zero blend point is found from |
1047 | | * median + shift. |
1048 | | * Otherwise, if the median >= 128, the zero blend point is |
1049 | | * median - shift. |
1050 | | * The purpose of shifting the zero blend point away from the |
1051 | | * median is to prevent a situation in pixBlendGray() where |
1052 | | * the median is 128 and the blender is not visible. |
1053 | | * The default value of shift is 64. |
1054 | | * (6) After processing pixs1, it is either 8 bpp or 32 bpp: |
1055 | | * ~ if 8 bpp, the fraction of pixs2 is mixed with pixs1. |
1056 | | * ~ if 32 bpp, each component of pixs1 is mixed with |
1057 | | * the same fraction of pixs2. |
1058 | | * (7) The darker the blender, the more it mixes with the blendee. |
1059 | | * A blender value of 0 has maximum mixing; a value of 255 |
1060 | | * has no mixing and hence is transparent. |
1061 | | * </pre> |
1062 | | */ |
1063 | | PIX * |
1064 | | pixBlendGrayAdapt(PIX *pixd, |
1065 | | PIX *pixs1, |
1066 | | PIX *pixs2, |
1067 | | l_int32 x, |
1068 | | l_int32 y, |
1069 | | l_float32 fract, |
1070 | | l_int32 shift) |
1071 | 0 | { |
1072 | 0 | l_int32 i, j, d, wc, hc, w, h, wplc, wpld, delta, overlap; |
1073 | 0 | l_int32 rval, gval, bval, cval, dval, mval, median, pivot; |
1074 | 0 | l_uint32 val32; |
1075 | 0 | l_uint32 *linec, *lined, *datac, *datad; |
1076 | 0 | l_float32 fmedian, factor; |
1077 | 0 | BOX *box, *boxt; |
1078 | 0 | PIX *pixc, *pix1, *pix2; |
1079 | |
|
1080 | 0 | if (!pixs1) |
1081 | 0 | return (PIX *)ERROR_PTR("pixs1 not defined", __func__, pixd); |
1082 | 0 | if (!pixs2) |
1083 | 0 | return (PIX *)ERROR_PTR("pixs2 not defined", __func__, pixd); |
1084 | 0 | if (pixGetDepth(pixs1) == 1) |
1085 | 0 | return (PIX *)ERROR_PTR("pixs1 is 1 bpp", __func__, pixd); |
1086 | 0 | if (pixd == pixs1 && pixGetColormap(pixs1)) |
1087 | 0 | return (PIX *)ERROR_PTR("can't do in-place with cmap", __func__, pixd); |
1088 | 0 | if (pixd && (pixd != pixs1)) |
1089 | 0 | return (PIX *)ERROR_PTR("pixd must be NULL or pixs1", __func__, pixd); |
1090 | 0 | if (fract < 0.0 || fract > 1.0) { |
1091 | 0 | L_WARNING("fract must be in [0.0, 1.0]; setting to 0.5\n", __func__); |
1092 | 0 | fract = 0.5; |
1093 | 0 | } |
1094 | 0 | if (shift == -1) shift = 64; /* default value */ |
1095 | 0 | if (shift < 0 || shift > 127) { |
1096 | 0 | L_WARNING("invalid shift; setting to 64\n", __func__); |
1097 | 0 | shift = 64; |
1098 | 0 | } |
1099 | | |
1100 | | /* Test for overlap */ |
1101 | 0 | pixGetDimensions(pixs1, &w, &h, NULL); |
1102 | 0 | pixGetDimensions(pixs2, &wc, &hc, NULL); |
1103 | 0 | box = boxCreate(x, y, wc, hc); |
1104 | 0 | boxt = boxCreate(0, 0, w, h); |
1105 | 0 | boxIntersects(box, boxt, &overlap); |
1106 | 0 | boxDestroy(&boxt); |
1107 | 0 | if (!overlap) { |
1108 | 0 | boxDestroy(&box); |
1109 | 0 | return (PIX *)ERROR_PTR("no image overlap", __func__, pixd); |
1110 | 0 | } |
1111 | | |
1112 | | /* If pixd != NULL, we know that it is equal to pixs1 and |
1113 | | * that pixs1 does not have a colormap, so that an in-place operation |
1114 | | * can be done. Otherwise, remove colormap from pixs1 if |
1115 | | * it exists and unpack to at least 8 bpp if necessary, |
1116 | | * to do the blending on a new pix. */ |
1117 | 0 | if (!pixd) { |
1118 | 0 | pix1 = pixRemoveColormap(pixs1, REMOVE_CMAP_BASED_ON_SRC); |
1119 | 0 | if (pixGetDepth(pix1) < 8) |
1120 | 0 | pix2 = pixConvertTo8(pix1, FALSE); |
1121 | 0 | else |
1122 | 0 | pix2 = pixClone(pix1); |
1123 | 0 | pixd = pixCopy(NULL, pix2); |
1124 | 0 | pixDestroy(&pix1); |
1125 | 0 | pixDestroy(&pix2); |
1126 | 0 | } |
1127 | | |
1128 | | /* Get the median value in the region of blending */ |
1129 | 0 | pix1 = pixClipRectangle(pixd, box, NULL); |
1130 | 0 | pix2 = pixConvertTo8(pix1, 0); |
1131 | 0 | pixGetRankValueMasked(pix2, NULL, 0, 0, 1, 0.5, &fmedian, NULL); |
1132 | 0 | median = (l_int32)(fmedian + 0.5); |
1133 | 0 | if (median < 128) |
1134 | 0 | pivot = median + shift; |
1135 | 0 | else |
1136 | 0 | pivot = median - shift; |
1137 | 0 | pixDestroy(&pix1); |
1138 | 0 | pixDestroy(&pix2); |
1139 | 0 | boxDestroy(&box); |
1140 | | |
1141 | | /* Process over src2; clip to src1. */ |
1142 | 0 | d = pixGetDepth(pixd); |
1143 | 0 | wpld = pixGetWpl(pixd); |
1144 | 0 | datad = pixGetData(pixd); |
1145 | 0 | pixc = pixConvertTo8(pixs2, 0); |
1146 | 0 | datac = pixGetData(pixc); |
1147 | 0 | wplc = pixGetWpl(pixc); |
1148 | 0 | for (i = 0; i < hc; i++) { |
1149 | 0 | if (i + y < 0 || i + y >= h) continue; |
1150 | 0 | linec = datac + i * wplc; |
1151 | 0 | lined = datad + (i + y) * wpld; |
1152 | 0 | switch (d) |
1153 | 0 | { |
1154 | 0 | case 8: |
1155 | | /* |
1156 | | * For 8 bpp, the dest pix is shifted by an amount |
1157 | | * proportional to the distance from the pivot value, |
1158 | | * and to the darkness of src2. In no situation will it |
1159 | | * pass the pivot value in intensity. |
1160 | | * The basic logic is: |
1161 | | * d --> d + f * (np - d) * (1 - c) |
1162 | | * where np, d and c are normalized pixel values for |
1163 | | * the pivot, src1 and src2, respectively, with normalization |
1164 | | * to 255. |
1165 | | */ |
1166 | 0 | for (j = 0; j < wc; j++) { |
1167 | 0 | if (j + x < 0 || j + x >= w) continue; |
1168 | 0 | dval = GET_DATA_BYTE(lined, j + x); |
1169 | 0 | cval = GET_DATA_BYTE(linec, j); |
1170 | 0 | delta = (pivot - dval) * (255 - cval) / 256; |
1171 | 0 | dval += (l_int32)(fract * delta + 0.5); |
1172 | 0 | SET_DATA_BYTE(lined, j + x, dval); |
1173 | 0 | } |
1174 | 0 | break; |
1175 | 0 | case 32: |
1176 | | /* |
1177 | | * For 32 bpp, the dest pix is shifted by an amount |
1178 | | * proportional to the max component distance from the |
1179 | | * pivot value, and to the darkness of src2. Each component |
1180 | | * is shifted by the same fraction, either up or down, |
1181 | | * depending on the shift direction (which is toward the |
1182 | | * pivot). The basic logic for the red component is: |
1183 | | * r --> r + f * (np - m) * (1 - c) * (r / m) |
1184 | | * where np, r, m and c are normalized pixel values for |
1185 | | * the pivot, the r component of src1, the max component |
1186 | | * of src1, and src2, respectively, again with normalization |
1187 | | * to 255. Likewise for the green and blue components. |
1188 | | */ |
1189 | 0 | for (j = 0; j < wc; j++) { |
1190 | 0 | if (j + x < 0 || j + x >= w) continue; |
1191 | 0 | cval = GET_DATA_BYTE(linec, j); |
1192 | 0 | val32 = *(lined + j + x); |
1193 | 0 | extractRGBValues(val32, &rval, &gval, &bval); |
1194 | 0 | mval = L_MAX(rval, gval); |
1195 | 0 | mval = L_MAX(mval, bval); |
1196 | 0 | mval = L_MAX(mval, 1); |
1197 | 0 | delta = (pivot - mval) * (255 - cval) / 256; |
1198 | 0 | factor = fract * delta / mval; |
1199 | 0 | rval += (l_int32)(factor * rval + 0.5); |
1200 | 0 | gval += (l_int32)(factor * gval + 0.5); |
1201 | 0 | bval += (l_int32)(factor * bval + 0.5); |
1202 | 0 | composeRGBPixel(rval, gval, bval, &val32); |
1203 | 0 | *(lined + j + x) = val32; |
1204 | 0 | } |
1205 | 0 | break; |
1206 | 0 | default: |
1207 | 0 | break; /* shouldn't happen */ |
1208 | 0 | } |
1209 | 0 | } |
1210 | | |
1211 | 0 | pixDestroy(&pixc); |
1212 | 0 | return pixd; |
1213 | 0 | } |
1214 | | |
1215 | | |
1216 | | /*! |
1217 | | * \brief pixFadeWithGray() |
1218 | | * |
1219 | | * \param[in] pixs colormapped or 8 bpp or 32 bpp |
1220 | | * \param[in] pixb 8 bpp blender |
1221 | | * \param[in] factor multiplicative factor to apply to blender value |
1222 | | * \param[in] type L_BLEND_TO_WHITE, L_BLEND_TO_BLACK |
1223 | | * \return pixd, or null on error |
1224 | | * |
1225 | | * <pre> |
1226 | | * Notes: |
1227 | | * (1) This function combines two pix aligned to the UL corner; they |
1228 | | * need not be the same size. |
1229 | | * (2) Each pixel in pixb is multiplied by 'factor' divided by 255, and |
1230 | | * clipped to the range [0 ... 1]. This gives the fade fraction |
1231 | | * to be applied to pixs. Fade either to white (L_BLEND_TO_WHITE) |
1232 | | * or to black (L_BLEND_TO_BLACK). |
1233 | | * </pre> |
1234 | | */ |
1235 | | PIX * |
1236 | | pixFadeWithGray(PIX *pixs, |
1237 | | PIX *pixb, |
1238 | | l_float32 factor, |
1239 | | l_int32 type) |
1240 | 0 | { |
1241 | 0 | l_int32 i, j, w, h, d, wb, hb, db, wd, hd, wplb, wpld; |
1242 | 0 | l_int32 valb, vald, nvald, rval, gval, bval, nrval, ngval, nbval; |
1243 | 0 | l_float32 nfactor, fract; |
1244 | 0 | l_uint32 val32, nval32; |
1245 | 0 | l_uint32 *lined, *datad, *lineb, *datab; |
1246 | 0 | PIX *pixd; |
1247 | |
|
1248 | 0 | if (!pixs) |
1249 | 0 | return (PIX *)ERROR_PTR("pixs not defined", __func__, NULL); |
1250 | 0 | if (!pixb) |
1251 | 0 | return (PIX *)ERROR_PTR("pixb not defined", __func__, NULL); |
1252 | 0 | if (pixGetDepth(pixs) == 1) |
1253 | 0 | return (PIX *)ERROR_PTR("pixs is 1 bpp", __func__, NULL); |
1254 | 0 | pixGetDimensions(pixb, &wb, &hb, &db); |
1255 | 0 | if (db != 8) |
1256 | 0 | return (PIX *)ERROR_PTR("pixb not 8 bpp", __func__, NULL); |
1257 | 0 | if (factor < 0.0 || factor > 255.0) |
1258 | 0 | return (PIX *)ERROR_PTR("factor not in [0.0...255.0]", __func__, NULL); |
1259 | 0 | if (type != L_BLEND_TO_WHITE && type != L_BLEND_TO_BLACK) |
1260 | 0 | return (PIX *)ERROR_PTR("invalid fade type", __func__, NULL); |
1261 | | |
1262 | | /* Remove colormap if it exists; otherwise copy */ |
1263 | 0 | pixd = pixRemoveColormapGeneral(pixs, REMOVE_CMAP_BASED_ON_SRC, L_COPY); |
1264 | 0 | pixGetDimensions(pixd, &wd, &hd, &d); |
1265 | 0 | w = L_MIN(wb, wd); |
1266 | 0 | h = L_MIN(hb, hd); |
1267 | 0 | datad = pixGetData(pixd); |
1268 | 0 | wpld = pixGetWpl(pixd); |
1269 | 0 | datab = pixGetData(pixb); |
1270 | 0 | wplb = pixGetWpl(pixb); |
1271 | | |
1272 | | /* The basic logic for this blending is, for each component p of pixs: |
1273 | | * fade-to-white: p --> p + (f * c) * (1 - p) |
1274 | | * fade-to-black: p --> p - (f * c) * p |
1275 | | * with c being the 8 bpp blender pixel of pixb, and with both |
1276 | | * p and c normalized to [0...1]. */ |
1277 | 0 | nfactor = factor / 255.; |
1278 | 0 | for (i = 0; i < h; i++) { |
1279 | 0 | lineb = datab + i * wplb; |
1280 | 0 | lined = datad + i * wpld; |
1281 | 0 | for (j = 0; j < w; j++) { |
1282 | 0 | valb = GET_DATA_BYTE(lineb, j); |
1283 | 0 | fract = nfactor * (l_float32)valb; |
1284 | 0 | fract = L_MIN(fract, 1.0); |
1285 | 0 | if (d == 8) { |
1286 | 0 | vald = GET_DATA_BYTE(lined, j); |
1287 | 0 | if (type == L_BLEND_TO_WHITE) |
1288 | 0 | nvald = vald + (l_int32)(fract * (255. - (l_float32)vald)); |
1289 | 0 | else /* L_BLEND_TO_BLACK */ |
1290 | 0 | nvald = vald - (l_int32)(fract * (l_float32)vald); |
1291 | 0 | SET_DATA_BYTE(lined, j, nvald); |
1292 | 0 | } else { /* d == 32 */ |
1293 | 0 | val32 = lined[j]; |
1294 | 0 | extractRGBValues(val32, &rval, &gval, &bval); |
1295 | 0 | if (type == L_BLEND_TO_WHITE) { |
1296 | 0 | nrval = rval + (l_int32)(fract * (255. - (l_float32)rval)); |
1297 | 0 | ngval = gval + (l_int32)(fract * (255. - (l_float32)gval)); |
1298 | 0 | nbval = bval + (l_int32)(fract * (255. - (l_float32)bval)); |
1299 | 0 | } else { |
1300 | 0 | nrval = rval - (l_int32)(fract * (l_float32)rval); |
1301 | 0 | ngval = gval - (l_int32)(fract * (l_float32)gval); |
1302 | 0 | nbval = bval - (l_int32)(fract * (l_float32)bval); |
1303 | 0 | } |
1304 | 0 | composeRGBPixel(nrval, ngval, nbval, &nval32); |
1305 | 0 | lined[j] = nval32; |
1306 | 0 | } |
1307 | 0 | } |
1308 | 0 | } |
1309 | |
|
1310 | 0 | return pixd; |
1311 | 0 | } |
1312 | | |
1313 | | |
1314 | | /* |
1315 | | * \brief pixBlendHardLight() |
1316 | | * |
1317 | | * \param[in] pixd either NULL or equal to pixs1 for in-place |
1318 | | * \param[in] pixs1 blendee; depth > 1, may be cmapped |
1319 | | * \param[in] pixs2 blender, 8 or 32 bpp; may be colormapped; |
1320 | | * typ. smaller in size than pixs1 |
1321 | | * \param[in] x,y origin [UL corner] of pixs2 relative to |
1322 | | * the origin of pixs1 |
1323 | | * \param[in] fract blending fraction, or 'opacity factor' |
1324 | | * \return pixd if OK; pixs1 on error |
1325 | | * |
1326 | | * <pre> |
1327 | | * Notes: |
1328 | | * (1) pixs2 must be 8 or 32 bpp; either may have a colormap. |
1329 | | * (2) Clipping of pixs2 to pixs1 is done in the inner pixel loop. |
1330 | | * (3) Only call in-place if pixs1 is not colormapped. |
1331 | | * (4) If pixs1 has a colormap, it is removed to generate either an |
1332 | | * 8 or 32 bpp pix, depending on the colormap. |
1333 | | * (5) For inplace operation, call it this way: |
1334 | | * pixBlendHardLight(pixs1, pixs1, pixs2, ...) |
1335 | | * (6) For generating a new pixd: |
1336 | | * pixd = pixBlendHardLight(NULL, pixs1, pixs2, ...) |
1337 | | * (7) This is a generalization of the usual hard light blending, |
1338 | | * where fract == 1.0. |
1339 | | * (8) "Overlay" blending is the same as hard light blending, with |
1340 | | * fract == 1.0, except that the components are switched |
1341 | | * in the test. (Note that the result is symmetric in the |
1342 | | * two components.) |
1343 | | * (9) See, e.g.: |
1344 | | * http://www.pegtop.net/delphi/articles/blendmodes/hardlight.htm |
1345 | | * http://www.digitalartform.com/imageArithmetic.htm |
1346 | | * (10) This function was built by Paco Galanes. |
1347 | | * </pre> |
1348 | | */ |
1349 | | PIX * |
1350 | | pixBlendHardLight(PIX *pixd, |
1351 | | PIX *pixs1, |
1352 | | PIX *pixs2, |
1353 | | l_int32 x, |
1354 | | l_int32 y, |
1355 | | l_float32 fract) |
1356 | 0 | { |
1357 | 0 | l_int32 i, j, w, h, d, wc, hc, dc, wplc, wpld; |
1358 | 0 | l_int32 cval, dval, rcval, gcval, bcval, rdval, gdval, bdval; |
1359 | 0 | l_uint32 cval32, dval32; |
1360 | 0 | l_uint32 *linec, *lined, *datac, *datad; |
1361 | 0 | PIX *pixc, *pixt; |
1362 | |
|
1363 | 0 | if (!pixs1) |
1364 | 0 | return (PIX *)ERROR_PTR("pixs1 not defined", __func__, pixd); |
1365 | 0 | if (!pixs2) |
1366 | 0 | return (PIX *)ERROR_PTR("pixs2 not defined", __func__, pixd); |
1367 | 0 | pixGetDimensions(pixs1, &w, &h, &d); |
1368 | 0 | pixGetDimensions(pixs2, &wc, &hc, &dc); |
1369 | 0 | if (d == 1) |
1370 | 0 | return (PIX *)ERROR_PTR("pixs1 is 1 bpp", __func__, pixd); |
1371 | 0 | if (dc != 8 && dc != 32) |
1372 | 0 | return (PIX *)ERROR_PTR("pixs2 not 8 or 32 bpp", __func__, pixd); |
1373 | 0 | if (pixd && (pixd != pixs1)) |
1374 | 0 | return (PIX *)ERROR_PTR("inplace and pixd != pixs1", __func__, pixd); |
1375 | 0 | if (pixd == pixs1 && pixGetColormap(pixs1)) |
1376 | 0 | return (PIX *)ERROR_PTR("inplace and pixs1 cmapped", __func__, pixd); |
1377 | 0 | if (pixd && d != 8 && d != 32) |
1378 | 0 | return (PIX *)ERROR_PTR("inplace and not 8 or 32 bpp", __func__, pixd); |
1379 | | |
1380 | 0 | if (fract < 0.0 || fract > 1.0) { |
1381 | 0 | L_WARNING("fract must be in [0.0, 1.0]; setting to 0.5\n", __func__); |
1382 | 0 | fract = 0.5; |
1383 | 0 | } |
1384 | | |
1385 | | /* If pixs2 has a colormap, remove it */ |
1386 | 0 | pixc = pixRemoveColormap(pixs2, REMOVE_CMAP_BASED_ON_SRC); /* clone ok */ |
1387 | 0 | dc = pixGetDepth(pixc); |
1388 | | |
1389 | | /* There are 4 cases: |
1390 | | * * pixs1 has or doesn't have a colormap |
1391 | | * * pixc is either 8 or 32 bpp |
1392 | | * In all situations, if pixs has a colormap it must be removed, |
1393 | | * and pixd must have a depth that is equal to or greater than pixc. */ |
1394 | 0 | if (dc == 32) { |
1395 | 0 | if (pixGetColormap(pixs1)) { /* pixd == NULL */ |
1396 | 0 | pixd = pixRemoveColormap(pixs1, REMOVE_CMAP_TO_FULL_COLOR); |
1397 | 0 | } else { |
1398 | 0 | if (!pixd) { |
1399 | 0 | pixd = pixConvertTo32(pixs1); |
1400 | 0 | } else { |
1401 | 0 | pixt = pixConvertTo32(pixs1); |
1402 | 0 | pixCopy(pixd, pixt); |
1403 | 0 | pixDestroy(&pixt); |
1404 | 0 | } |
1405 | 0 | } |
1406 | 0 | d = 32; |
1407 | 0 | } else { /* dc == 8 */ |
1408 | 0 | if (pixGetColormap(pixs1)) /* pixd == NULL */ |
1409 | 0 | pixd = pixRemoveColormap(pixs1, REMOVE_CMAP_BASED_ON_SRC); |
1410 | 0 | else |
1411 | 0 | pixd = pixCopy(pixd, pixs1); |
1412 | 0 | d = pixGetDepth(pixd); |
1413 | 0 | } |
1414 | |
|
1415 | 0 | if (!(d == 8 && dc == 8) && /* 3 cases only */ |
1416 | 0 | !(d == 32 && dc == 8) && |
1417 | 0 | !(d == 32 && dc == 32)) { |
1418 | 0 | pixDestroy(&pixc); |
1419 | 0 | return (PIX *)ERROR_PTR("bad! -- invalid depth combo!", __func__, pixd); |
1420 | 0 | } |
1421 | | |
1422 | 0 | wpld = pixGetWpl(pixd); |
1423 | 0 | datad = pixGetData(pixd); |
1424 | 0 | datac = pixGetData(pixc); |
1425 | 0 | wplc = pixGetWpl(pixc); |
1426 | 0 | for (i = 0; i < hc; i++) { |
1427 | 0 | if (i + y < 0 || i + y >= h) continue; |
1428 | 0 | linec = datac + i * wplc; |
1429 | 0 | lined = datad + (i + y) * wpld; |
1430 | 0 | for (j = 0; j < wc; j++) { |
1431 | 0 | if (j + x < 0 || j + x >= w) continue; |
1432 | 0 | if (d == 8 && dc == 8) { |
1433 | 0 | dval = GET_DATA_BYTE(lined, x + j); |
1434 | 0 | cval = GET_DATA_BYTE(linec, j); |
1435 | 0 | dval = blendHardLightComponents(dval, cval, fract); |
1436 | 0 | SET_DATA_BYTE(lined, x + j, dval); |
1437 | 0 | } else if (d == 32 && dc == 8) { |
1438 | 0 | dval32 = *(lined + x + j); |
1439 | 0 | extractRGBValues(dval32, &rdval, &gdval, &bdval); |
1440 | 0 | cval = GET_DATA_BYTE(linec, j); |
1441 | 0 | rdval = blendHardLightComponents(rdval, cval, fract); |
1442 | 0 | gdval = blendHardLightComponents(gdval, cval, fract); |
1443 | 0 | bdval = blendHardLightComponents(bdval, cval, fract); |
1444 | 0 | composeRGBPixel(rdval, gdval, bdval, &dval32); |
1445 | 0 | *(lined + x + j) = dval32; |
1446 | 0 | } else if (d == 32 && dc == 32) { |
1447 | 0 | dval32 = *(lined + x + j); |
1448 | 0 | extractRGBValues(dval32, &rdval, &gdval, &bdval); |
1449 | 0 | cval32 = *(linec + j); |
1450 | 0 | extractRGBValues(cval32, &rcval, &gcval, &bcval); |
1451 | 0 | rdval = blendHardLightComponents(rdval, rcval, fract); |
1452 | 0 | gdval = blendHardLightComponents(gdval, gcval, fract); |
1453 | 0 | bdval = blendHardLightComponents(bdval, bcval, fract); |
1454 | 0 | composeRGBPixel(rdval, gdval, bdval, &dval32); |
1455 | 0 | *(lined + x + j) = dval32; |
1456 | 0 | } |
1457 | 0 | } |
1458 | 0 | } |
1459 | |
|
1460 | 0 | pixDestroy(&pixc); |
1461 | 0 | return pixd; |
1462 | 0 | } |
1463 | | |
1464 | | |
1465 | | /* |
1466 | | * \brief blendHardLightComponents() |
1467 | | * |
1468 | | * \param[in] a 8 bpp blendee component |
1469 | | * \param[in] b 8 bpp blender component |
1470 | | * \param[in] fract fraction of blending; use 1.0 for usual definition |
1471 | | * \return blended 8 bpp component |
1472 | | * |
1473 | | * <pre> |
1474 | | * Notes: |
1475 | | * |
1476 | | * The basic logic for this blending is: |
1477 | | * b < 0.5: |
1478 | | * a --> 2 * a * (0.5 - f * (0.5 - b)) |
1479 | | * b >= 0.5: |
1480 | | * a --> 1 - 2 * (1 - a) * (1 - (0.5 - f * (0.5 - b))) |
1481 | | * |
1482 | | * In the limit that f == 1 (standard hardlight blending): |
1483 | | * b < 0.5: a --> 2 * a * b |
1484 | | * or |
1485 | | * a --> a - a * (1 - 2 * b) |
1486 | | * b >= 0.5: a --> 1 - 2 * (1 - a) * (1 - b) |
1487 | | * or |
1488 | | * a --> a + (1 - a) * (2 * b - 1) |
1489 | | * |
1490 | | * You can see that for standard hardlight blending: |
1491 | | * b < 0.5: a is pushed linearly with b down to 0 |
1492 | | * b >= 0.5: a is pushed linearly with b up to 1 |
1493 | | * a is unchanged if b = 0.5 |
1494 | | * |
1495 | | * Our opacity factor f reduces the deviation of b from 0.5: |
1496 | | * f == 0: b --> 0.5, so no blending occurs |
1497 | | * f == 1: b --> b, so we get full conventional blending |
1498 | | * |
1499 | | * There is a variant of hardlight blending called "softlight" blending: |
1500 | | * (e.g., http://jswidget.com/blog/tag/hard-light/) |
1501 | | * b < 0.5: |
1502 | | * a --> a - a * (0.5 - b) * (1 - Abs(2 * a - 1)) |
1503 | | * b >= 0.5: |
1504 | | * a --> a + (1 - a) * (b - 0.5) * (1 - Abs(2 * a - 1)) |
1505 | | * which limits the amount that 'a' can be moved to a maximum of |
1506 | | * halfway toward 0 or 1, and further reduces it as 'a' moves |
1507 | | * away from 0.5. |
1508 | | * As you can see, there are a nearly infinite number of different |
1509 | | * blending formulas that can be conjured up. |
1510 | | * </pre> |
1511 | | */ |
1512 | | static l_int32 blendHardLightComponents(l_int32 a, |
1513 | | l_int32 b, |
1514 | | l_float32 fract) |
1515 | 0 | { |
1516 | 0 | if (b < 0x80) { |
1517 | 0 | b = 0x80 - (l_int32)(fract * (0x80 - b)); |
1518 | 0 | return (a * b) >> 7; |
1519 | 0 | } else { |
1520 | 0 | b = 0x80 + (l_int32)(fract * (b - 0x80)); |
1521 | 0 | return 0xff - (((0xff - b) * (0xff - a)) >> 7); |
1522 | 0 | } |
1523 | 0 | } |
1524 | | |
1525 | | |
1526 | | /*-------------------------------------------------------------* |
1527 | | * Blending two colormapped images * |
1528 | | *-------------------------------------------------------------*/ |
1529 | | /*! |
1530 | | * \brief pixBlendCmap() |
1531 | | * |
1532 | | * \param[in] pixs 2, 4 or 8 bpp, with colormap |
1533 | | * \param[in] pixb colormapped blender |
1534 | | * \param[in] x, y UL corner of blender relative to pixs |
1535 | | * \param[in] sindex colormap index of pixels in pixs to be changed |
1536 | | * \return 0 if OK, 1 on error |
1537 | | * |
1538 | | * <pre> |
1539 | | * Notes: |
1540 | | * (1) This function combines two colormaps, and replaces the pixels |
1541 | | * in pixs that have a specified color value with those in pixb. |
1542 | | * (2) sindex must be in the existing colormap; otherwise an |
1543 | | * error is returned. In use, sindex will typically be the index |
1544 | | * for white (255, 255, 255). |
1545 | | * (3) Blender colors that already exist in the colormap are used; |
1546 | | * others are added. If any blender colors cannot be |
1547 | | * stored in the colormap, an error is returned. |
1548 | | * (4) In the implementation, a mapping is generated from each |
1549 | | * original blender colormap index to the corresponding index |
1550 | | * in the expanded colormap for pixs. Then for each pixel in |
1551 | | * pixs with value sindex, and which is covered by a blender pixel, |
1552 | | * the new index corresponding to the blender pixel is substituted |
1553 | | * for sindex. |
1554 | | * </pre> |
1555 | | */ |
1556 | | l_ok |
1557 | | pixBlendCmap(PIX *pixs, |
1558 | | PIX *pixb, |
1559 | | l_int32 x, |
1560 | | l_int32 y, |
1561 | | l_int32 sindex) |
1562 | 0 | { |
1563 | 0 | l_int32 rval, gval, bval; |
1564 | 0 | l_int32 i, j, w, h, d, ncb, wb, hb, wpls; |
1565 | 0 | l_int32 index, val, nadded; |
1566 | 0 | l_int32 lut[256]; |
1567 | 0 | l_uint32 pval; |
1568 | 0 | l_uint32 *lines, *datas; |
1569 | 0 | PIXCMAP *cmaps, *cmapb, *cmapsc; |
1570 | |
|
1571 | 0 | if (!pixs) |
1572 | 0 | return ERROR_INT("pixs not defined", __func__, 1); |
1573 | 0 | if (!pixb) |
1574 | 0 | return ERROR_INT("pixb not defined", __func__, 1); |
1575 | 0 | if ((cmaps = pixGetColormap(pixs)) == NULL) |
1576 | 0 | return ERROR_INT("no colormap in pixs", __func__, 1); |
1577 | 0 | if ((cmapb = pixGetColormap(pixb)) == NULL) |
1578 | 0 | return ERROR_INT("no colormap in pixb", __func__, 1); |
1579 | 0 | ncb = pixcmapGetCount(cmapb); |
1580 | |
|
1581 | 0 | pixGetDimensions(pixs, &w, &h, &d); |
1582 | 0 | if (d != 2 && d != 4 && d != 8) |
1583 | 0 | return ERROR_INT("depth not in {2,4,8}", __func__, 1); |
1584 | | |
1585 | | /* Make a copy of cmaps; we'll add to this if necessary |
1586 | | * and substitute at the end if we found there was enough room |
1587 | | * to hold all the new colors. */ |
1588 | 0 | cmapsc = pixcmapCopy(cmaps); |
1589 | | |
1590 | | /* Add new colors if necessary; get mapping array between |
1591 | | * cmaps and cmapb. */ |
1592 | 0 | for (i = 0, nadded = 0; i < ncb; i++) { |
1593 | 0 | pixcmapGetColor(cmapb, i, &rval, &gval, &bval); |
1594 | 0 | if (pixcmapGetIndex(cmapsc, rval, gval, bval, &index)) { /* not found */ |
1595 | 0 | if (pixcmapAddColor(cmapsc, rval, gval, bval)) { |
1596 | 0 | pixcmapDestroy(&cmapsc); |
1597 | 0 | return ERROR_INT("not enough room in cmaps", __func__, 1); |
1598 | 0 | } |
1599 | 0 | lut[i] = pixcmapGetCount(cmapsc) - 1; |
1600 | 0 | nadded++; |
1601 | 0 | } else { |
1602 | 0 | lut[i] = index; |
1603 | 0 | } |
1604 | 0 | } |
1605 | | |
1606 | | /* Replace cmaps if colors have been added. */ |
1607 | 0 | if (nadded == 0) |
1608 | 0 | pixcmapDestroy(&cmapsc); |
1609 | 0 | else |
1610 | 0 | pixSetColormap(pixs, cmapsc); |
1611 | | |
1612 | | /* Replace each pixel value sindex by mapped colormap index when |
1613 | | * a blender pixel in pixbc overlays it. */ |
1614 | 0 | datas = pixGetData(pixs); |
1615 | 0 | wpls = pixGetWpl(pixs); |
1616 | 0 | pixGetDimensions(pixb, &wb, &hb, NULL); |
1617 | 0 | for (i = 0; i < hb; i++) { |
1618 | 0 | if (i + y < 0 || i + y >= h) continue; |
1619 | 0 | lines = datas + (y + i) * wpls; |
1620 | 0 | for (j = 0; j < wb; j++) { |
1621 | 0 | if (j + x < 0 || j + x >= w) continue; |
1622 | 0 | switch (d) { |
1623 | 0 | case 2: |
1624 | 0 | val = GET_DATA_DIBIT(lines, x + j); |
1625 | 0 | if (val == sindex) { |
1626 | 0 | pixGetPixel(pixb, j, i, &pval); |
1627 | 0 | SET_DATA_DIBIT(lines, x + j, lut[pval]); |
1628 | 0 | } |
1629 | 0 | break; |
1630 | 0 | case 4: |
1631 | 0 | val = GET_DATA_QBIT(lines, x + j); |
1632 | 0 | if (val == sindex) { |
1633 | 0 | pixGetPixel(pixb, j, i, &pval); |
1634 | 0 | SET_DATA_QBIT(lines, x + j, lut[pval]); |
1635 | 0 | } |
1636 | 0 | break; |
1637 | 0 | case 8: |
1638 | 0 | val = GET_DATA_BYTE(lines, x + j); |
1639 | 0 | if (val == sindex) { |
1640 | 0 | pixGetPixel(pixb, j, i, &pval); |
1641 | 0 | SET_DATA_BYTE(lines, x + j, lut[pval]); |
1642 | 0 | } |
1643 | 0 | break; |
1644 | 0 | default: |
1645 | 0 | return ERROR_INT("depth not in {2,4,8}", __func__, 1); |
1646 | 0 | } |
1647 | 0 | } |
1648 | 0 | } |
1649 | | |
1650 | 0 | return 0; |
1651 | 0 | } |
1652 | | |
1653 | | |
1654 | | /*---------------------------------------------------------------------* |
1655 | | * Blending two images using a third * |
1656 | | *---------------------------------------------------------------------*/ |
1657 | | /*! |
1658 | | * \brief pixBlendWithGrayMask() |
1659 | | * |
1660 | | * \param[in] pixs1 8 bpp gray, rgb, rgba or colormapped |
1661 | | * \param[in] pixs2 8 bpp gray, rgb, rgba or colormapped |
1662 | | * \param[in] pixg [optional] 8 bpp gray, for transparency of pixs2; |
1663 | | * can be null |
1664 | | * \param[in] x, y UL corner of pixs2 and pixg with respect to pixs1 |
1665 | | * \return pixd blended image, or null on error |
1666 | | * |
1667 | | * <pre> |
1668 | | * Notes: |
1669 | | * (1) The result is 8 bpp grayscale if both pixs1 and pixs2 are |
1670 | | * 8 bpp gray. Otherwise, the result is 32 bpp rgb. |
1671 | | * (2) pixg is an 8 bpp transparency image, where 0 is transparent |
1672 | | * and 255 is opaque. It determines the transparency of pixs2 |
1673 | | * when applied over pixs1. It can be null if pixs2 is rgba, |
1674 | | * in which case we use the alpha component of pixs2. |
1675 | | * (3) If pixg exists, it need not be the same size as pixs2. |
1676 | | * However, we assume their UL corners are aligned with each other, |
1677 | | * and placed at the location (x, y) in pixs1. |
1678 | | * (4) The pixels in pixd are a combination of those in pixs1 |
1679 | | * and pixs2, where the amount from pixs2 is proportional to |
1680 | | * the value of the pixel (p) in pixg, and the amount from pixs1 |
1681 | | * is proportional to (255 - p). Thus pixg is a transparency |
1682 | | * image (usually called an alpha blender) where each pixel |
1683 | | * can be associated with a pixel in pixs2, and determines |
1684 | | * the amount of the pixs2 pixel in the final result. |
1685 | | * For example, if pixg is all 0, pixs2 is transparent and |
1686 | | * the result in pixd is simply pixs1. |
1687 | | * (5) A typical use is for the pixs2/pixg combination to be |
1688 | | * a small watermark that is applied to pixs1. |
1689 | | * </pre> |
1690 | | */ |
1691 | | PIX * |
1692 | | pixBlendWithGrayMask(PIX *pixs1, |
1693 | | PIX *pixs2, |
1694 | | PIX *pixg, |
1695 | | l_int32 x, |
1696 | | l_int32 y) |
1697 | 0 | { |
1698 | 0 | l_int32 w1, h1, d1, w2, h2, d2, spp, wg, hg, wmin, hmin, wpld, wpls, wplg; |
1699 | 0 | l_int32 i, j, val, dval, sval; |
1700 | 0 | l_int32 drval, dgval, dbval, srval, sgval, sbval; |
1701 | 0 | l_uint32 dval32, sval32; |
1702 | 0 | l_uint32 *datad, *datas, *datag, *lined, *lines, *lineg; |
1703 | 0 | l_float32 fract; |
1704 | 0 | PIX *pixr1, *pixr2, *pix1, *pix2, *pixg2, *pixd; |
1705 | |
|
1706 | 0 | if (!pixs1) |
1707 | 0 | return (PIX *)ERROR_PTR("pixs1 not defined", __func__, NULL); |
1708 | 0 | if (!pixs2) |
1709 | 0 | return (PIX *)ERROR_PTR("pixs2 not defined", __func__, NULL); |
1710 | 0 | pixGetDimensions(pixs1, &w1, &h1, &d1); |
1711 | 0 | pixGetDimensions(pixs2, &w2, &h2, &d2); |
1712 | 0 | if (d1 == 1 || d2 == 1) |
1713 | 0 | return (PIX *)ERROR_PTR("pixs1 or pixs2 is 1 bpp", __func__, NULL); |
1714 | 0 | if (pixg) { |
1715 | 0 | if (pixGetDepth(pixg) != 8) |
1716 | 0 | return (PIX *)ERROR_PTR("pixg not 8 bpp", __func__, NULL); |
1717 | 0 | pixGetDimensions(pixg, &wg, &hg, NULL); |
1718 | 0 | wmin = L_MIN(w2, wg); |
1719 | 0 | hmin = L_MIN(h2, hg); |
1720 | 0 | pixg2 = pixClone(pixg); |
1721 | 0 | } else { /* use the alpha component of pixs2 */ |
1722 | 0 | spp = pixGetSpp(pixs2); |
1723 | 0 | if (d2 != 32 || spp != 4) |
1724 | 0 | return (PIX *)ERROR_PTR("no alpha; pixs2 not rgba", __func__, NULL); |
1725 | 0 | wmin = w2; |
1726 | 0 | hmin = h2; |
1727 | 0 | pixg2 = pixGetRGBComponent(pixs2, L_ALPHA_CHANNEL); |
1728 | 0 | } |
1729 | | |
1730 | | /* Remove colormaps if they exist; clones are OK */ |
1731 | 0 | pixr1 = pixRemoveColormap(pixs1, REMOVE_CMAP_BASED_ON_SRC); |
1732 | 0 | pixr2 = pixRemoveColormap(pixs2, REMOVE_CMAP_BASED_ON_SRC); |
1733 | | |
1734 | | /* Regularize to the same depth if necessary */ |
1735 | 0 | d1 = pixGetDepth(pixr1); |
1736 | 0 | d2 = pixGetDepth(pixr2); |
1737 | 0 | if (d1 == 32) { /* convert d2 to rgb if necessary */ |
1738 | 0 | pix1 = pixClone(pixr1); |
1739 | 0 | if (d2 != 32) |
1740 | 0 | pix2 = pixConvertTo32(pixr2); |
1741 | 0 | else |
1742 | 0 | pix2 = pixClone(pixr2); |
1743 | 0 | } else if (d2 == 32) { /* and d1 != 32; convert to 32 */ |
1744 | 0 | pix2 = pixClone(pixr2); |
1745 | 0 | pix1 = pixConvertTo32(pixr1); |
1746 | 0 | } else { /* both are 8 bpp or less */ |
1747 | 0 | pix1 = pixConvertTo8(pixr1, FALSE); |
1748 | 0 | pix2 = pixConvertTo8(pixr2, FALSE); |
1749 | 0 | } |
1750 | 0 | pixDestroy(&pixr1); |
1751 | 0 | pixDestroy(&pixr2); |
1752 | | |
1753 | | /* Output a copy of pix1 to avoid side-effecting input pixs1 */ |
1754 | 0 | pixd = pixCopy(NULL, pix1); |
1755 | 0 | pixDestroy(&pix1); |
1756 | | |
1757 | | /* Sanity check: both either 8 or 32 bpp */ |
1758 | 0 | d1 = pixGetDepth(pixd); |
1759 | 0 | d2 = pixGetDepth(pix2); |
1760 | 0 | if (!pixd || d1 != d2 || (d1 != 8 && d1 != 32)) { |
1761 | 0 | pixDestroy(&pixd); |
1762 | 0 | pixDestroy(&pix2); |
1763 | 0 | pixDestroy(&pixg2); |
1764 | 0 | return (PIX *)ERROR_PTR("depths not regularized! bad!", __func__, NULL); |
1765 | 0 | } |
1766 | | |
1767 | | /* Blend pix2 onto pixd, using pixg2. |
1768 | | * Let the normalized pixel value of pixg2 be f = pixval / 255, |
1769 | | * and the pixel values of pixd and pix2 be p1 and p2, rsp. |
1770 | | * Then the blended value is: |
1771 | | * p = (1.0 - f) * p1 + f * p2 |
1772 | | * Blending is done component-wise if rgb. |
1773 | | * Scan over pix2 and pixg2, clipping to pixd where necessary. */ |
1774 | 0 | datad = pixGetData(pixd); |
1775 | 0 | datas = pixGetData(pix2); |
1776 | 0 | datag = pixGetData(pixg2); |
1777 | 0 | wpld = pixGetWpl(pixd); |
1778 | 0 | wpls = pixGetWpl(pix2); |
1779 | 0 | wplg = pixGetWpl(pixg2); |
1780 | 0 | for (i = 0; i < hmin; i++) { |
1781 | 0 | if (i + y < 0 || i + y >= h1) continue; |
1782 | 0 | lined = datad + (i + y) * wpld; |
1783 | 0 | lines = datas + i * wpls; |
1784 | 0 | lineg = datag + i * wplg; |
1785 | 0 | for (j = 0; j < wmin; j++) { |
1786 | 0 | if (j + x < 0 || j + x >= w1) continue; |
1787 | 0 | val = GET_DATA_BYTE(lineg, j); |
1788 | 0 | if (val == 0) continue; /* pix2 is transparent */ |
1789 | 0 | fract = (l_float32)val / 255.; |
1790 | 0 | if (d1 == 8) { |
1791 | 0 | dval = GET_DATA_BYTE(lined, j + x); |
1792 | 0 | sval = GET_DATA_BYTE(lines, j); |
1793 | 0 | dval = (l_int32)((1.0 - fract) * dval + fract * sval); |
1794 | 0 | SET_DATA_BYTE(lined, j + x, dval); |
1795 | 0 | } else { /* 32 */ |
1796 | 0 | dval32 = *(lined + j + x); |
1797 | 0 | sval32 = *(lines + j); |
1798 | 0 | extractRGBValues(dval32, &drval, &dgval, &dbval); |
1799 | 0 | extractRGBValues(sval32, &srval, &sgval, &sbval); |
1800 | 0 | drval = (l_int32)((1.0 - fract) * drval + fract * srval); |
1801 | 0 | dgval = (l_int32)((1.0 - fract) * dgval + fract * sgval); |
1802 | 0 | dbval = (l_int32)((1.0 - fract) * dbval + fract * sbval); |
1803 | 0 | composeRGBPixel(drval, dgval, dbval, &dval32); |
1804 | 0 | *(lined + j + x) = dval32; |
1805 | 0 | } |
1806 | 0 | } |
1807 | 0 | } |
1808 | |
|
1809 | 0 | pixDestroy(&pixg2); |
1810 | 0 | pixDestroy(&pix2); |
1811 | 0 | return pixd; |
1812 | 0 | } |
1813 | | |
1814 | | |
1815 | | /*---------------------------------------------------------------------* |
1816 | | * Blending background to a specific color * |
1817 | | *---------------------------------------------------------------------*/ |
1818 | | /*! |
1819 | | * \brief pixBlendBackgroundToColor() |
1820 | | * |
1821 | | * \param[in] pixd can be NULL or pixs |
1822 | | * \param[in] pixs 32 bpp rgb |
1823 | | * \param[in] box region for blending; can be NULL) |
1824 | | * \param[in] color 32 bit color in 0xrrggbb00 format |
1825 | | * \param[in] gamma, minval, maxval args for grayscale TRC mapping |
1826 | | * \return pixd always |
1827 | | * |
1828 | | * <pre> |
1829 | | * Notes: |
1830 | | * (1) This in effect replaces light background pixels in pixs |
1831 | | * by the input color. It does it by alpha blending so that |
1832 | | * there are no visible artifacts from hard cutoffs. |
1833 | | * (2) If pixd == pixs, this is done in-place. |
1834 | | * (3) If box == NULL, this is performed on all of pixs. |
1835 | | * (4) The alpha component for blending is derived from pixs, |
1836 | | * by converting to grayscale and enhancing with a TRC. |
1837 | | * (5) The last three arguments specify the TRC operation. |
1838 | | * Suggested values are: %gamma = 0.3, %minval = 50, %maxval = 200. |
1839 | | * To skip the TRC, use %gamma == 1, %minval = 0, %maxval = 255. |
1840 | | * See pixGammaTRC() for details. |
1841 | | * </pre> |
1842 | | */ |
1843 | | PIX * |
1844 | | pixBlendBackgroundToColor(PIX *pixd, |
1845 | | PIX *pixs, |
1846 | | BOX *box, |
1847 | | l_uint32 color, |
1848 | | l_float32 gamma, |
1849 | | l_int32 minval, |
1850 | | l_int32 maxval) |
1851 | 0 | { |
1852 | 0 | l_int32 x, y, w, h; |
1853 | 0 | BOX *boxt; |
1854 | 0 | PIX *pixt, *pixc, *pixr, *pixg; |
1855 | |
|
1856 | 0 | if (!pixs) |
1857 | 0 | return (PIX *)ERROR_PTR("pixs not defined", __func__, pixd); |
1858 | 0 | if (pixGetDepth(pixs) != 32) |
1859 | 0 | return (PIX *)ERROR_PTR("pixs not 32 bpp", __func__, pixd); |
1860 | 0 | if (pixd && (pixd != pixs)) |
1861 | 0 | return (PIX *)ERROR_PTR("pixd neither null nor pixs", __func__, pixd); |
1862 | | |
1863 | | /* Extract the (optionally cropped) region, pixr, and generate |
1864 | | * an identically sized pixc with the uniform color. */ |
1865 | 0 | if (!pixd) |
1866 | 0 | pixd = pixCopy(NULL, pixs); |
1867 | 0 | if (box) { |
1868 | 0 | pixr = pixClipRectangle(pixd, box, &boxt); |
1869 | 0 | boxGetGeometry(boxt, &x, &y, &w, &h); |
1870 | 0 | pixc = pixCreate(w, h, 32); |
1871 | 0 | boxDestroy(&boxt); |
1872 | 0 | } else { |
1873 | 0 | pixc = pixCreateTemplate(pixs); |
1874 | 0 | pixr = pixClone(pixd); |
1875 | 0 | } |
1876 | 0 | pixSetAllArbitrary(pixc, color); |
1877 | | |
1878 | | /* Set up the alpha channel */ |
1879 | 0 | pixg = pixConvertTo8(pixr, 0); |
1880 | 0 | pixGammaTRC(pixg, pixg, gamma, minval, maxval); |
1881 | 0 | pixSetRGBComponent(pixc, pixg, L_ALPHA_CHANNEL); |
1882 | | |
1883 | | /* Blend and replace in pixd */ |
1884 | 0 | pixt = pixBlendWithGrayMask(pixr, pixc, NULL, 0, 0); |
1885 | 0 | if (box) { |
1886 | 0 | pixRasterop(pixd, x, y, w, h, PIX_SRC, pixt, 0, 0); |
1887 | 0 | pixDestroy(&pixt); |
1888 | 0 | } else { |
1889 | 0 | pixTransferAllData(pixd, &pixt, 0, 0); |
1890 | 0 | } |
1891 | |
|
1892 | 0 | pixDestroy(&pixc); |
1893 | 0 | pixDestroy(&pixr); |
1894 | 0 | pixDestroy(&pixg); |
1895 | 0 | return pixd; |
1896 | 0 | } |
1897 | | |
1898 | | |
1899 | | /*---------------------------------------------------------------------* |
1900 | | * Multiplying by a specific color * |
1901 | | *---------------------------------------------------------------------*/ |
1902 | | /*! |
1903 | | * \brief pixMultiplyByColor() |
1904 | | * |
1905 | | * \param[in] pixd can be NULL or pixs |
1906 | | * \param[in] pixs 32 bpp rgb |
1907 | | * \param[in] box region for filtering; can be NULL) |
1908 | | * \param[in] color 32 bit color in 0xrrggbb00 format |
1909 | | * \return pixd always |
1910 | | * |
1911 | | * <pre> |
1912 | | * Notes: |
1913 | | * (1) This filters all pixels in the specified region by |
1914 | | * multiplying each component by the input color. |
1915 | | * This leaves black invariant and transforms white to the |
1916 | | * input color. |
1917 | | * (2) If pixd == pixs, this is done in-place. |
1918 | | * (3) If box == NULL, this is performed on all of pixs. |
1919 | | * </pre> |
1920 | | */ |
1921 | | PIX * |
1922 | | pixMultiplyByColor(PIX *pixd, |
1923 | | PIX *pixs, |
1924 | | BOX *box, |
1925 | | l_uint32 color) |
1926 | 0 | { |
1927 | 0 | l_int32 i, j, bx, by, w, h, wpl; |
1928 | 0 | l_int32 red, green, blue, rval, gval, bval, nrval, ngval, nbval; |
1929 | 0 | l_float32 frval, fgval, fbval; |
1930 | 0 | l_uint32 *data, *line; |
1931 | 0 | PIX *pixt; |
1932 | |
|
1933 | 0 | if (!pixs) |
1934 | 0 | return (PIX *)ERROR_PTR("pixs not defined", __func__, pixd); |
1935 | 0 | if (pixGetDepth(pixs) != 32) |
1936 | 0 | return (PIX *)ERROR_PTR("pixs not 32 bpp", __func__, pixd); |
1937 | 0 | if (pixd && (pixd != pixs)) |
1938 | 0 | return (PIX *)ERROR_PTR("pixd neither null nor pixs", __func__, pixd); |
1939 | | |
1940 | 0 | if (!pixd) |
1941 | 0 | pixd = pixCopy(NULL, pixs); |
1942 | 0 | if (box) { |
1943 | 0 | boxGetGeometry(box, &bx, &by, NULL, NULL); |
1944 | 0 | pixt = pixClipRectangle(pixd, box, NULL); |
1945 | 0 | } else { |
1946 | 0 | pixt = pixClone(pixd); |
1947 | 0 | } |
1948 | | |
1949 | | /* Multiply each pixel in pixt by the color */ |
1950 | 0 | extractRGBValues(color, &red, &green, &blue); |
1951 | 0 | frval = (1. / 255.) * red; |
1952 | 0 | fgval = (1. / 255.) * green; |
1953 | 0 | fbval = (1. / 255.) * blue; |
1954 | 0 | data = pixGetData(pixt); |
1955 | 0 | wpl = pixGetWpl(pixt); |
1956 | 0 | pixGetDimensions(pixt, &w, &h, NULL); |
1957 | 0 | for (i = 0; i < h; i++) { |
1958 | 0 | line = data + i * wpl; |
1959 | 0 | for (j = 0; j < w; j++) { |
1960 | 0 | extractRGBValues(line[j], &rval, &gval, &bval); |
1961 | 0 | nrval = (l_int32)(frval * rval + 0.5); |
1962 | 0 | ngval = (l_int32)(fgval * gval + 0.5); |
1963 | 0 | nbval = (l_int32)(fbval * bval + 0.5); |
1964 | 0 | composeRGBPixel(nrval, ngval, nbval, line + j); |
1965 | 0 | } |
1966 | 0 | } |
1967 | | |
1968 | | /* Replace */ |
1969 | 0 | if (box) |
1970 | 0 | pixRasterop(pixd, bx, by, w, h, PIX_SRC, pixt, 0, 0); |
1971 | 0 | pixDestroy(&pixt); |
1972 | 0 | return pixd; |
1973 | 0 | } |
1974 | | |
1975 | | |
1976 | | /*---------------------------------------------------------------------* |
1977 | | * Rendering with alpha blending over a uniform background * |
1978 | | *---------------------------------------------------------------------*/ |
1979 | | /*! |
1980 | | * \brief pixAlphaBlendUniform() |
1981 | | * |
1982 | | * \param[in] pixs 32 bpp rgba, with alpha |
1983 | | * \param[in] color 32 bit color in 0xrrggbb00 format |
1984 | | * \return pixd 32 bpp rgb: pixs blended over uniform color %color, |
1985 | | * a clone of pixs if no alpha, and null on error |
1986 | | * |
1987 | | * <pre> |
1988 | | * Notes: |
1989 | | * (1) This is a convenience function that renders 32 bpp RGBA images |
1990 | | * (with an alpha channel) over a uniform background of |
1991 | | * value %color. To render over a white background, |
1992 | | * use %color = 0xffffff00. The result is an RGB image. |
1993 | | * (2) If pixs does not have an alpha channel, it returns a clone |
1994 | | * of pixs. |
1995 | | * </pre> |
1996 | | */ |
1997 | | PIX * |
1998 | | pixAlphaBlendUniform(PIX *pixs, |
1999 | | l_uint32 color) |
2000 | 0 | { |
2001 | 0 | PIX *pixt, *pixd; |
2002 | |
|
2003 | 0 | if (!pixs) |
2004 | 0 | return (PIX *)ERROR_PTR("pixs not defined", __func__, NULL); |
2005 | 0 | if (pixGetDepth(pixs) != 32) |
2006 | 0 | return (PIX *)ERROR_PTR("pixs not 32 bpp", __func__, NULL); |
2007 | 0 | if (pixGetSpp(pixs) != 4) { |
2008 | 0 | L_WARNING("no alpha channel; returning clone\n", __func__); |
2009 | 0 | return pixClone(pixs); |
2010 | 0 | } |
2011 | | |
2012 | 0 | pixt = pixCreateTemplate(pixs); |
2013 | 0 | pixSetAllArbitrary(pixt, color); |
2014 | 0 | pixSetSpp(pixt, 3); /* not required */ |
2015 | 0 | pixd = pixBlendWithGrayMask(pixt, pixs, NULL, 0, 0); |
2016 | |
|
2017 | 0 | pixDestroy(&pixt); |
2018 | 0 | return pixd; |
2019 | 0 | } |
2020 | | |
2021 | | |
2022 | | /*---------------------------------------------------------------------* |
2023 | | * Adding an alpha layer for blending * |
2024 | | *---------------------------------------------------------------------*/ |
2025 | | /*! |
2026 | | * \brief pixAddAlphaToBlend() |
2027 | | * |
2028 | | * \param[in] pixs any depth |
2029 | | * \param[in] fract fade fraction in the alpha component |
2030 | | * \param[in] invert 1 to photometrically invert pixs |
2031 | | * \return pixd 32 bpp with alpha, or null on error |
2032 | | * |
2033 | | * <pre> |
2034 | | * Notes: |
2035 | | * (1) This is a simple alpha layer generator, where typically white has |
2036 | | * maximum transparency and black has minimum. |
2037 | | * (2) If %invert == 1, generate the same alpha layer but invert |
2038 | | * the input image photometrically. This is useful for blending |
2039 | | * over dark images, where you want dark regions in pixs, such |
2040 | | * as text, to be lighter in the blended image. |
2041 | | * (3) The fade %fract gives the minimum transparency (i.e., |
2042 | | * maximum opacity). A small fraction is useful for adding |
2043 | | * a watermark to an image. |
2044 | | * (4) If pixs has a colormap, it is removed to rgb. |
2045 | | * (5) If pixs already has an alpha layer, it is overwritten. |
2046 | | * </pre> |
2047 | | */ |
2048 | | PIX * |
2049 | | pixAddAlphaToBlend(PIX *pixs, |
2050 | | l_float32 fract, |
2051 | | l_int32 invert) |
2052 | 0 | { |
2053 | 0 | PIX *pixd, *pix1, *pix2; |
2054 | |
|
2055 | 0 | if (!pixs) |
2056 | 0 | return (PIX *)ERROR_PTR("pixs not defined", __func__, NULL); |
2057 | 0 | if (fract < 0.0 || fract > 1.0) |
2058 | 0 | return (PIX *)ERROR_PTR("invalid fract", __func__, NULL); |
2059 | | |
2060 | | /* Convert to 32 bpp */ |
2061 | 0 | if (pixGetColormap(pixs)) |
2062 | 0 | pix1 = pixRemoveColormap(pixs, REMOVE_CMAP_TO_FULL_COLOR); |
2063 | 0 | else |
2064 | 0 | pix1 = pixClone(pixs); |
2065 | 0 | pixd = pixConvertTo32(pix1); /* new */ |
2066 | | |
2067 | | /* Use an inverted image if this will be blended with a dark image */ |
2068 | 0 | if (invert) pixInvert(pixd, pixd); |
2069 | | |
2070 | | /* Generate alpha layer */ |
2071 | 0 | pix2 = pixConvertTo8(pix1, 0); /* new */ |
2072 | 0 | pixInvert(pix2, pix2); |
2073 | 0 | pixMultConstantGray(pix2, fract); |
2074 | 0 | pixSetRGBComponent(pixd, pix2, L_ALPHA_CHANNEL); |
2075 | |
|
2076 | 0 | pixDestroy(&pix1); |
2077 | 0 | pixDestroy(&pix2); |
2078 | 0 | return pixd; |
2079 | 0 | } |
2080 | | |
2081 | | |
2082 | | |
2083 | | /*---------------------------------------------------------------------* |
2084 | | * Setting a transparent alpha component over a white background * |
2085 | | *---------------------------------------------------------------------*/ |
2086 | | /*! |
2087 | | * \brief pixSetAlphaOverWhite() |
2088 | | * |
2089 | | * \param[in] pixs colormapped or 32 bpp rgb; no alpha |
2090 | | * \return pixd new pix with meaningful alpha component, |
2091 | | * or null on error |
2092 | | * |
2093 | | * <pre> |
2094 | | * Notes: |
2095 | | * (1) The generated alpha component is transparent over white |
2096 | | * (background) pixels in pixs, and quickly grades to opaque |
2097 | | * away from the transparent parts. This is a cheap and |
2098 | | * dirty alpha generator. The 2 pixel gradation is useful |
2099 | | * to blur the boundary between the transparent region |
2100 | | * (that will render entirely from a backing image) and |
2101 | | * the remainder which renders from pixs. |
2102 | | * (2) All alpha component bits in pixs are overwritten. |
2103 | | * </pre> |
2104 | | */ |
2105 | | PIX * |
2106 | | pixSetAlphaOverWhite(PIX *pixs) |
2107 | 0 | { |
2108 | 0 | PIX *pixd, *pix1, *pix2, *pix3, *pix4; |
2109 | |
|
2110 | 0 | if (!pixs) |
2111 | 0 | return (PIX *)ERROR_PTR("pixs not defined", __func__, NULL); |
2112 | 0 | if (!(pixGetDepth(pixs) == 32 || pixGetColormap(pixs))) |
2113 | 0 | return (PIX *)ERROR_PTR("pixs not 32 bpp or cmapped", __func__, NULL); |
2114 | | |
2115 | | /* Remove colormap if it exists; otherwise copy */ |
2116 | 0 | pixd = pixRemoveColormapGeneral(pixs, REMOVE_CMAP_TO_FULL_COLOR, L_COPY); |
2117 | | |
2118 | | /* Generate a 1 bpp image where a white pixel in pixd is 0. |
2119 | | * In the comments below, a "white" pixel refers to pixd. |
2120 | | * pix1 is rgb, pix2 is 8 bpp gray, pix3 is 1 bpp. */ |
2121 | 0 | pix1 = pixInvert(NULL, pixd); /* send white (255) to 0 for each sample */ |
2122 | 0 | pix2 = pixConvertRGBToGrayMinMax(pix1, L_CHOOSE_MAX); /* 0 if white */ |
2123 | 0 | pix3 = pixThresholdToBinary(pix2, 1); /* sets white pixels to 1 */ |
2124 | 0 | pixInvert(pix3, pix3); /* sets white pixels to 0 */ |
2125 | | |
2126 | | /* Generate the alpha component using the distance transform, |
2127 | | * which measures the distance to the nearest bg (0) pixel in pix3. |
2128 | | * After multiplying by 128, its value is 0 (transparent) |
2129 | | * over white pixels, and goes to opaque (255) two pixels away |
2130 | | * from the nearest white pixel. */ |
2131 | 0 | pix4 = pixDistanceFunction(pix3, 8, 8, L_BOUNDARY_FG); |
2132 | 0 | pixMultConstantGray(pix4, 128.0); |
2133 | 0 | pixSetRGBComponent(pixd, pix4, L_ALPHA_CHANNEL); |
2134 | |
|
2135 | 0 | pixDestroy(&pix1); |
2136 | 0 | pixDestroy(&pix2); |
2137 | 0 | pixDestroy(&pix3); |
2138 | 0 | pixDestroy(&pix4); |
2139 | 0 | return pixd; |
2140 | 0 | } |
2141 | | |
2142 | | |
2143 | | /*---------------------------------------------------------------------* |
2144 | | * Fading from the edge * |
2145 | | *---------------------------------------------------------------------*/ |
2146 | | /*! |
2147 | | * \brief pixLinearEdgeFade() |
2148 | | * |
2149 | | * \param[in] pixs 8 or 32 bpp; no colormap |
2150 | | * \param[in] dir L_FROM_LEFT, L_FROM_RIGHT, L_FROM_TOP, L_FROM_BOT |
2151 | | * \param[in] fadeto L_BLEND_TO_WHITE, L_BLEND_TO_BLACK |
2152 | | * \param[in] distfract fraction of width or height over which fading occurs |
2153 | | * \param[in] maxfade fraction of fading at the edge, <= 1.0 |
2154 | | * \return 0 if OK, 1 on error |
2155 | | * |
2156 | | * <pre> |
2157 | | * Notes: |
2158 | | * (1) In-place operation. |
2159 | | * (2) Maximum fading fraction %maxfade occurs at the edge of the image, |
2160 | | * and the fraction goes to 0 at the fractional distance %distfract |
2161 | | * from the edge. %maxfade must be in [0, 1]. |
2162 | | * (3) %distrfact must be in [0, 1], and typically it would be <= 0.5. |
2163 | | * </pre> |
2164 | | */ |
2165 | | l_ok |
2166 | | pixLinearEdgeFade(PIX *pixs, |
2167 | | l_int32 dir, |
2168 | | l_int32 fadeto, |
2169 | | l_float32 distfract, |
2170 | | l_float32 maxfade) |
2171 | 0 | { |
2172 | 0 | l_int32 i, j, w, h, d, wpl, xmin, ymin, range, val, rval, gval, bval; |
2173 | 0 | l_float32 slope, limit, del; |
2174 | 0 | l_uint32 *data, *line; |
2175 | |
|
2176 | 0 | if (!pixs) |
2177 | 0 | return ERROR_INT("pixs not defined", __func__, 1); |
2178 | 0 | if (pixGetColormap(pixs) != NULL) |
2179 | 0 | return ERROR_INT("pixs has a colormap", __func__, 1); |
2180 | 0 | pixGetDimensions(pixs, &w, &h, &d); |
2181 | 0 | if (d != 8 && d != 32) |
2182 | 0 | return ERROR_INT("pixs not 8 or 32 bpp", __func__, 1); |
2183 | 0 | if (dir != L_FROM_LEFT && dir != L_FROM_RIGHT && |
2184 | 0 | dir != L_FROM_TOP && dir != L_FROM_BOT) |
2185 | 0 | return ERROR_INT("invalid fade direction from edge", __func__, 1); |
2186 | 0 | if (fadeto != L_BLEND_TO_WHITE && fadeto != L_BLEND_TO_BLACK) |
2187 | 0 | return ERROR_INT("invalid fadeto photometry", __func__, 1); |
2188 | 0 | if (maxfade <= 0) return 0; |
2189 | 0 | if (maxfade > 1.0) |
2190 | 0 | return ERROR_INT("invalid maxfade", __func__, 1); |
2191 | 0 | if (distfract <= 0 || distfract * L_MIN(w, h) < 1.0) { |
2192 | 0 | L_INFO("distfract is too small\n", __func__); |
2193 | 0 | return 0; |
2194 | 0 | } |
2195 | 0 | if (distfract > 1.0) |
2196 | 0 | return ERROR_INT("invalid distfract", __func__, 1); |
2197 | | |
2198 | | /* Set up parameters */ |
2199 | 0 | if (dir == L_FROM_LEFT) { |
2200 | 0 | range = (l_int32)(distfract * w); |
2201 | 0 | xmin = 0; |
2202 | 0 | slope = maxfade / (l_float32)range; |
2203 | 0 | } else if (dir == L_FROM_RIGHT) { |
2204 | 0 | range = (l_int32)(distfract * w); |
2205 | 0 | xmin = w - range; |
2206 | 0 | slope = maxfade / (l_float32)range; |
2207 | 0 | } else if (dir == L_FROM_TOP) { |
2208 | 0 | range = (l_int32)(distfract * h); |
2209 | 0 | ymin = 0; |
2210 | 0 | slope = maxfade / (l_float32)range; |
2211 | 0 | } else { /* dir == L_FROM_BOT */ |
2212 | 0 | range = (l_int32)(distfract * h); |
2213 | 0 | ymin = h - range; |
2214 | 0 | slope = maxfade / (l_float32)range; |
2215 | 0 | } |
2216 | |
|
2217 | 0 | limit = (fadeto == L_BLEND_TO_WHITE) ? 255.0 : 0.0; |
2218 | 0 | data = pixGetData(pixs); |
2219 | 0 | wpl = pixGetWpl(pixs); |
2220 | 0 | if (dir == L_FROM_LEFT || dir == L_FROM_RIGHT) { |
2221 | 0 | for (j = 0; j < range; j++) { |
2222 | 0 | del = (dir == L_FROM_LEFT) ? maxfade - slope * j |
2223 | 0 | : maxfade - slope * (range - j); |
2224 | 0 | for (i = 0; i < h; i++) { |
2225 | 0 | line = data + i * wpl; |
2226 | 0 | if (d == 8) { |
2227 | 0 | val = GET_DATA_BYTE(line, xmin + j); |
2228 | 0 | val += (limit - val) * del + 0.5; |
2229 | 0 | SET_DATA_BYTE(line, xmin + j, val); |
2230 | 0 | } else { /* rgb */ |
2231 | 0 | extractRGBValues(*(line + xmin + j), &rval, &gval, &bval); |
2232 | 0 | rval += (limit - rval) * del + 0.5; |
2233 | 0 | gval += (limit - gval) * del + 0.5; |
2234 | 0 | bval += (limit - bval) * del + 0.5; |
2235 | 0 | composeRGBPixel(rval, gval, bval, line + xmin + j); |
2236 | 0 | } |
2237 | 0 | } |
2238 | 0 | } |
2239 | 0 | } else { /* dir == L_FROM_TOP || L_FROM_BOT */ |
2240 | 0 | for (i = 0; i < range; i++) { |
2241 | 0 | del = (dir == L_FROM_TOP) ? maxfade - slope * i |
2242 | 0 | : maxfade - slope * (range - i); |
2243 | 0 | line = data + (ymin + i) * wpl; |
2244 | 0 | for (j = 0; j < w; j++) { |
2245 | 0 | if (d == 8) { |
2246 | 0 | val = GET_DATA_BYTE(line, j); |
2247 | 0 | val += (limit - val) * del + 0.5; |
2248 | 0 | SET_DATA_BYTE(line, j, val); |
2249 | 0 | } else { /* rgb */ |
2250 | 0 | extractRGBValues(*(line + j), &rval, &gval, &bval); |
2251 | 0 | rval += (limit - rval) * del + 0.5; |
2252 | 0 | gval += (limit - gval) * del + 0.5; |
2253 | 0 | bval += (limit - bval) * del + 0.5; |
2254 | 0 | composeRGBPixel(rval, gval, bval, line + j); |
2255 | 0 | } |
2256 | 0 | } |
2257 | 0 | } |
2258 | 0 | } |
2259 | |
|
2260 | 0 | return 0; |
2261 | 0 | } |