1 | #include "mupdf/fitz.h" |
2 | #include "draw-imp.h" |
3 | |
4 | #include <assert.h> |
5 | #include <math.h> |
6 | |
7 | enum { MAXN = 2 + FZ_MAX_COLORS }; |
8 | |
9 | static void paint_scan(fz_pixmap *FZ_RESTRICT pix, int y, int fx0, int fx1, int cx0, int cx1, const int *FZ_RESTRICT v0, const int *FZ_RESTRICT v1, int n) |
10 | { |
11 | unsigned char *p; |
12 | int c[MAXN], dc[MAXN]; |
13 | int k, w; |
14 | float div, mul; |
15 | int x0, x1, pa; |
16 | |
17 | /* Ensure that fx0 is left edge, and fx1 is right */ |
18 | if (fx0 > fx1) |
19 | { |
20 | const int *v; |
21 | int t = fx0; fx0 = fx1; fx1 = t; |
22 | v = v0; v0 = v1; v1 = v; |
23 | } |
24 | else if (fx0 == fx1) |
25 | return; |
26 | |
27 | /* Clip fx0, fx1 to range */ |
28 | if (fx0 >= cx1) |
29 | return; |
30 | if (fx1 <= cx0) |
31 | return; |
32 | x0 = (fx0 > cx0 ? fx0 : cx0); |
33 | x1 = (fx1 < cx1 ? fx1 : cx1); |
34 | |
35 | w = x1 - x0; |
36 | if (w == 0) |
37 | return; |
38 | |
39 | div = 1.0f / (fx1 - fx0); |
40 | mul = (x0 - fx0); |
41 | for (k = 0; k < n; k++) |
42 | { |
43 | dc[k] = (v1[k] - v0[k]) * div; |
44 | c[k] = v0[k] + dc[k] * mul; |
45 | } |
46 | |
47 | p = pix->samples + ((x0 - pix->x) * pix->n) + ((y - pix->y) * pix->stride); |
48 | pa = pix->alpha; |
49 | do |
50 | { |
51 | for (k = 0; k < n; k++) |
52 | { |
53 | *p++ = c[k]>>16; |
54 | c[k] += dc[k]; |
55 | } |
56 | if (pa) |
57 | *p++ = 255; |
58 | } |
59 | while (--w); |
60 | } |
61 | |
62 | typedef struct edge_data_s edge_data; |
63 | |
64 | struct edge_data_s |
65 | { |
66 | float x; |
67 | float dx; |
68 | int v[2*MAXN]; |
69 | }; |
70 | |
71 | static inline void prepare_edge(const float *FZ_RESTRICT vtop, const float *FZ_RESTRICT vbot, edge_data *FZ_RESTRICT edge, float y, int n) |
72 | { |
73 | float r = 1.0f / (vbot[1] - vtop[1]); |
74 | float t = (y - vtop[1]) * r; |
75 | float diff = vbot[0] - vtop[0]; |
76 | int i; |
77 | |
78 | edge->x = vtop[0] + diff * t; |
79 | edge->dx = diff * r; |
80 | |
81 | for (i = 0; i < n; i++) |
82 | { |
83 | diff = vbot[i+2] - vtop[i+2]; |
84 | edge->v[i] = (int)(65536.0f * (vtop[i+2] + diff * t)); |
85 | edge->v[i+MAXN] = (int)(65536.0f * diff * r); |
86 | } |
87 | } |
88 | |
89 | static inline void step_edge(edge_data *edge, int n) |
90 | { |
91 | int i; |
92 | |
93 | edge->x += edge->dx; |
94 | |
95 | for (i = 0; i < n; i++) |
96 | { |
97 | edge->v[i] += edge->v[i + MAXN]; |
98 | } |
99 | } |
100 | |
101 | static void |
102 | fz_paint_triangle(fz_pixmap *pix, float *v[3], int n, fz_irect bbox) |
103 | { |
104 | edge_data e0, e1; |
105 | int top, mid, bot; |
106 | float y, y1; |
107 | int minx, maxx; |
108 | |
109 | top = bot = 0; |
110 | if (v[1][1] < v[0][1]) top = 1; else bot = 1; |
111 | if (v[2][1] < v[top][1]) top = 2; |
112 | else if (v[2][1] > v[bot][1]) bot = 2; |
113 | if (v[top][1] == v[bot][1]) return; |
114 | |
115 | /* Test if the triangle is completely outside the scissor rect */ |
116 | if (v[bot][1] < bbox.y0) return; |
117 | if (v[top][1] > bbox.y1) return; |
118 | |
119 | /* Magic! Ensure that mid/top/bot are all different */ |
120 | mid = 3^top^bot; |
121 | |
122 | assert(top != bot && top != mid && mid != bot); |
123 | |
124 | minx = fz_maxi(bbox.x0, pix->x); |
125 | maxx = fz_mini(bbox.x1, pix->x + pix->w); |
126 | |
127 | y = ceilf(fz_max(bbox.y0, v[top][1])); |
128 | y1 = ceilf(fz_min(bbox.y1, v[mid][1])); |
129 | |
130 | n -= 2; |
131 | prepare_edge(v[top], v[bot], &e0, y, n); |
132 | if (y < y1) |
133 | { |
134 | prepare_edge(v[top], v[mid], &e1, y, n); |
135 | |
136 | do |
137 | { |
138 | paint_scan(pix, y, (int)e0.x, (int)e1.x, minx, maxx, &e0.v[0], &e1.v[0], n); |
139 | step_edge(&e0, n); |
140 | step_edge(&e1, n); |
141 | y ++; |
142 | } |
143 | while (y < y1); |
144 | } |
145 | |
146 | y1 = ceilf(fz_min(bbox.y1, v[bot][1])); |
147 | if (y < y1) |
148 | { |
149 | prepare_edge(v[mid], v[bot], &e1, y, n); |
150 | |
151 | do |
152 | { |
153 | paint_scan(pix, y, (int)e0.x, (int)e1.x, minx, maxx, &e0.v[0], &e1.v[0], n); |
154 | y ++; |
155 | if (y >= y1) |
156 | break; |
157 | step_edge(&e0, n); |
158 | step_edge(&e1, n); |
159 | } |
160 | while (1); |
161 | } |
162 | } |
163 | |
164 | struct paint_tri_data |
165 | { |
166 | const fz_shade *shade; |
167 | fz_pixmap *dest; |
168 | fz_irect bbox; |
169 | fz_color_converter cc; |
170 | }; |
171 | |
172 | static void |
173 | prepare_mesh_vertex(fz_context *ctx, void *arg, fz_vertex *v, const float *input) |
174 | { |
175 | struct paint_tri_data *ptd = (struct paint_tri_data *)arg; |
176 | const fz_shade *shade = ptd->shade; |
177 | fz_pixmap *dest = ptd->dest; |
178 | float *output = v->c; |
179 | int i; |
180 | |
181 | if (shade->use_function) |
182 | output[0] = input[0] * 255; |
183 | else |
184 | { |
185 | int n = fz_colorspace_n(ctx, dest->colorspace); |
186 | int a = dest->alpha; |
187 | int m = dest->n - a; |
188 | if (ptd->cc.convert) |
189 | ptd->cc.convert(ctx, &ptd->cc, input, output); |
190 | for (i = 0; i < n; i++) |
191 | output[i] *= 255; |
192 | for (; i < m; i++) |
193 | output[i] = 0; |
194 | if (a) |
195 | output[i] = 255; |
196 | } |
197 | } |
198 | |
199 | static void |
200 | do_paint_tri(fz_context *ctx, void *arg, fz_vertex *av, fz_vertex *bv, fz_vertex *cv) |
201 | { |
202 | struct paint_tri_data *ptd = (struct paint_tri_data *)arg; |
203 | float *vertices[3]; |
204 | fz_pixmap *dest; |
205 | |
206 | vertices[0] = (float *)av; |
207 | vertices[1] = (float *)bv; |
208 | vertices[2] = (float *)cv; |
209 | |
210 | dest = ptd->dest; |
211 | fz_paint_triangle(dest, vertices, 2 + dest->n - dest->alpha, ptd->bbox); |
212 | } |
213 | |
214 | /* |
215 | Render a shade to a given pixmap. |
216 | |
217 | shade: The shade to paint. |
218 | |
219 | override_cs: NULL, or colorspace to override the shades |
220 | inbuilt colorspace. |
221 | |
222 | ctm: The transform to apply. |
223 | |
224 | dest: The pixmap to render into. |
225 | |
226 | color_params: The color rendering settings |
227 | |
228 | bbox: Pointer to a bounding box to limit the rendering |
229 | of the shade. |
230 | |
231 | op: NULL, or pointer to overprint bitmap. |
232 | */ |
233 | void |
234 | fz_paint_shade(fz_context *ctx, fz_shade *shade, fz_colorspace *colorspace, fz_matrix ctm, fz_pixmap *dest, fz_color_params color_params, fz_irect bbox, const fz_overprint *eop) |
235 | { |
236 | unsigned char clut[256][FZ_MAX_COLORS]; |
237 | fz_pixmap *temp = NULL; |
238 | fz_pixmap *conv = NULL; |
239 | fz_color_converter cc = { 0 }; |
240 | float color[FZ_MAX_COLORS]; |
241 | struct paint_tri_data ptd = { 0 }; |
242 | int i, k; |
243 | fz_matrix local_ctm; |
244 | |
245 | fz_var(temp); |
246 | fz_var(conv); |
247 | |
248 | if (colorspace == NULL) |
249 | colorspace = shade->colorspace; |
250 | |
251 | fz_try(ctx) |
252 | { |
253 | local_ctm = fz_concat(shade->matrix, ctm); |
254 | |
255 | if (shade->use_function) |
256 | { |
257 | /* We need to use alpha = 1 here, because the shade might not fill the bbox. */ |
258 | temp = fz_new_pixmap_with_bbox(ctx, fz_device_gray(ctx), bbox, NULL, 1); |
259 | fz_clear_pixmap(ctx, temp); |
260 | } |
261 | else |
262 | { |
263 | temp = dest; |
264 | } |
265 | |
266 | ptd.dest = temp; |
267 | ptd.shade = shade; |
268 | ptd.bbox = bbox; |
269 | |
270 | if (temp->colorspace) |
271 | fz_init_cached_color_converter(ctx, &ptd.cc, colorspace, temp->colorspace, NULL, color_params); |
272 | |
273 | fz_process_shade(ctx, shade, local_ctm, fz_rect_from_irect(bbox), prepare_mesh_vertex, &do_paint_tri, &ptd); |
274 | |
275 | if (shade->use_function) |
276 | { |
277 | /* If the shade is defined in a deviceN (or separation, |
278 | * which is the same internally to MuPDF) space, then |
279 | * we need to render it in deviceN before painting it |
280 | * to the destination. If not, we are free to render it |
281 | * direct to the target. */ |
282 | if (fz_colorspace_is_device_n(ctx, colorspace)) |
283 | { |
284 | /* We've drawn it as greyscale, with the values being |
285 | * the input to the function. Now make DevN version |
286 | * by mapping that greyscale through the function. |
287 | * This seems inefficient, but it's actually required, |
288 | * because we need to apply the function lookup POST |
289 | * interpolation in the do_paint_tri routines, not |
290 | * before it to avoid problems with some test files |
291 | * (tests/GhentV3.0/061_Shading_x1a.pdf for example). |
292 | */ |
293 | unsigned char *s = temp->samples; |
294 | unsigned char *d; |
295 | int hh = temp->h; |
296 | int n = fz_colorspace_n(ctx, colorspace); |
297 | |
298 | /* alpha = 1 here for the same reason as earlier */ |
299 | conv = fz_new_pixmap_with_bbox(ctx, colorspace, bbox, NULL, 1); |
300 | d = conv->samples; |
301 | while (hh--) |
302 | { |
303 | int len = temp->w; |
304 | while (len--) |
305 | { |
306 | int v = *s++; |
307 | int a = *s++; |
308 | const float *f = shade->function[v]; |
309 | for (k = 0; k < n; k++) |
310 | *d++ = fz_clampi(255 * f[k], 0, 255); |
311 | *d++ = a; |
312 | } |
313 | d += conv->stride - conv->w * conv->n; |
314 | s += temp->stride - temp->w * temp->n; |
315 | } |
316 | fz_drop_pixmap(ctx, temp); |
317 | temp = conv; |
318 | conv = NULL; |
319 | |
320 | /* Now Change from our device_n colorspace into the target colorspace/spots. */ |
321 | conv = fz_clone_pixmap_area_with_different_seps(ctx, temp, NULL, dest->colorspace, dest->seps, color_params, NULL); |
322 | } |
323 | else |
324 | { |
325 | unsigned char *s = temp->samples; |
326 | unsigned char *d; |
327 | int da; |
328 | int sa = temp->alpha; |
329 | int hh = temp->h; |
330 | int cn = fz_colorspace_n(ctx, colorspace); |
331 | int m = dest->n - dest->alpha; |
332 | int n = fz_colorspace_n(ctx, dest->colorspace); |
333 | |
334 | if (dest->colorspace) |
335 | { |
336 | fz_find_color_converter(ctx, &cc, colorspace, dest->colorspace, NULL, color_params); |
337 | for (i = 0; i < 256; i++) |
338 | { |
339 | cc.convert(ctx, &cc, shade->function[i], color); |
340 | for (k = 0; k < n; k++) |
341 | clut[i][k] = color[k] * 255; |
342 | for (; k < m; k++) |
343 | clut[i][k] = 0; |
344 | clut[i][k] = shade->function[i][cn] * 255; |
345 | } |
346 | fz_drop_color_converter(ctx, &cc); |
347 | } |
348 | else |
349 | { |
350 | for (i = 0; i < 256; i++) |
351 | { |
352 | for (k = 0; k < m; k++) |
353 | clut[i][k] = 0; |
354 | clut[i][k] = shade->function[i][cn] * 255; |
355 | } |
356 | } |
357 | |
358 | conv = fz_new_pixmap_with_bbox(ctx, dest->colorspace, bbox, dest->seps, 1); |
359 | d = conv->samples; |
360 | da = conv->alpha; |
361 | while (hh--) |
362 | { |
363 | int len = temp->w; |
364 | while (len--) |
365 | { |
366 | int v = *s++; |
367 | int a = (da ? clut[v][conv->n - 1] : 255); |
368 | if (sa) |
369 | a = fz_mul255(*s++, a); |
370 | for (k = 0; k < conv->n - da; k++) |
371 | *d++ = fz_mul255(clut[v][k], a); |
372 | if (da) |
373 | *d++ = a; |
374 | } |
375 | d += conv->stride - conv->w * conv->n; |
376 | s += temp->stride - temp->w * temp->n; |
377 | } |
378 | } |
379 | fz_paint_pixmap_with_overprint(dest, conv, eop); |
380 | } |
381 | } |
382 | fz_always(ctx) |
383 | { |
384 | if (shade->use_function) |
385 | { |
386 | fz_drop_color_converter(ctx, &cc); |
387 | fz_drop_pixmap(ctx, temp); |
388 | fz_drop_pixmap(ctx, conv); |
389 | } |
390 | fz_fin_cached_color_converter(ctx, &ptd.cc); |
391 | } |
392 | fz_catch(ctx) |
393 | fz_rethrow(ctx); |
394 | } |
395 | |