1 /* GIMP - The GNU Image Manipulation Program
2 * Copyright (C) 1995-1999 Spencer Kimball and Peter Mattis
3 *
4 * This program is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; either version 3 of the License, or
7 * (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program. If not, see <https://www.gnu.org/licenses/>.
16 */
17
18 #include "config.h"
19
20 #include <string.h>
21
22 #include <gdk-pixbuf/gdk-pixbuf.h>
23 #include <gegl.h>
24
25 #include <cairo.h>
26
27 #include "libgimpbase/gimpbase.h"
28 #include "libgimpmath/gimpmath.h"
29
30 #include "core-types.h"
31
32 #include "gimpboundary.h"
33 #include "gimpbezierdesc.h"
34 #include "gimpscanconvert.h"
35
36
37 struct _GimpScanConvert
38 {
39 gdouble ratio_xy;
40
41 gboolean clip;
42 gint clip_x;
43 gint clip_y;
44 gint clip_w;
45 gint clip_h;
46
47 /* stroking options */
48 gboolean do_stroke;
49 gdouble width;
50 GimpJoinStyle join;
51 GimpCapStyle cap;
52 gdouble miter;
53 gdouble dash_offset;
54 GArray *dash_info;
55
56 GArray *path_data;
57 };
58
59
60 /* public functions */
61
62 /**
63 * gimp_scan_convert_new:
64 *
65 * Create a new scan conversion context.
66 *
67 * Return value: a newly allocated #GimpScanConvert context.
68 */
69 GimpScanConvert *
gimp_scan_convert_new(void)70 gimp_scan_convert_new (void)
71 {
72 GimpScanConvert *sc = g_slice_new0 (GimpScanConvert);
73
74 sc->path_data = g_array_new (FALSE, FALSE, sizeof (cairo_path_data_t));
75 sc->ratio_xy = 1.0;
76
77 return sc;
78 }
79
80 GimpScanConvert *
gimp_scan_convert_new_from_boundary(const GimpBoundSeg * bound_segs,gint n_bound_segs,gint offset_x,gint offset_y)81 gimp_scan_convert_new_from_boundary (const GimpBoundSeg *bound_segs,
82 gint n_bound_segs,
83 gint offset_x,
84 gint offset_y)
85 {
86 g_return_val_if_fail (bound_segs == NULL || n_bound_segs != 0, NULL);
87
88 if (bound_segs)
89 {
90 GimpBoundSeg *stroke_segs;
91 gint n_stroke_segs;
92
93 stroke_segs = gimp_boundary_sort (bound_segs, n_bound_segs,
94 &n_stroke_segs);
95
96 if (stroke_segs)
97 {
98 GimpBezierDesc *bezier;
99
100 bezier = gimp_bezier_desc_new_from_bound_segs (stroke_segs,
101 n_bound_segs,
102 n_stroke_segs);
103
104 g_free (stroke_segs);
105
106 if (bezier)
107 {
108 GimpScanConvert *scan_convert;
109
110 scan_convert = gimp_scan_convert_new ();
111
112 gimp_bezier_desc_translate (bezier, offset_x, offset_y);
113 gimp_scan_convert_add_bezier (scan_convert, bezier);
114
115 gimp_bezier_desc_free (bezier);
116
117 return scan_convert;
118 }
119 }
120 }
121
122 return NULL;
123 }
124
125 /**
126 * gimp_scan_convert_free:
127 * @sc: a #GimpScanConvert context
128 *
129 * Frees the resources allocated for @sc.
130 */
131 void
gimp_scan_convert_free(GimpScanConvert * sc)132 gimp_scan_convert_free (GimpScanConvert *sc)
133 {
134 g_return_if_fail (sc != NULL);
135
136 if (sc->path_data)
137 g_array_free (sc->path_data, TRUE);
138
139 if (sc->dash_info)
140 g_array_free (sc->dash_info, TRUE);
141
142 g_slice_free (GimpScanConvert, sc);
143 }
144
145 /**
146 * gimp_scan_convert_set_pixel_ratio:
147 * @sc: a #GimpScanConvert context
148 * @ratio_xy: the aspect ratio of the major coordinate axes
149 *
150 * Sets the pixel aspect ratio.
151 */
152 void
gimp_scan_convert_set_pixel_ratio(GimpScanConvert * sc,gdouble ratio_xy)153 gimp_scan_convert_set_pixel_ratio (GimpScanConvert *sc,
154 gdouble ratio_xy)
155 {
156 g_return_if_fail (sc != NULL);
157
158 /* we only need the relative resolution */
159 sc->ratio_xy = ratio_xy;
160 }
161
162 /**
163 * gimp_scan_convert_set_clip_rectangle
164 * @sc: a #GimpScanConvert context
165 * @x: horizontal offset of clip rectangle
166 * @y: vertical offset of clip rectangle
167 * @width: width of clip rectangle
168 * @height: height of clip rectangle
169 *
170 * Sets a clip rectangle on @sc. Subsequent render operations will be
171 * restricted to this area.
172 */
173 void
gimp_scan_convert_set_clip_rectangle(GimpScanConvert * sc,gint x,gint y,gint width,gint height)174 gimp_scan_convert_set_clip_rectangle (GimpScanConvert *sc,
175 gint x,
176 gint y,
177 gint width,
178 gint height)
179 {
180 g_return_if_fail (sc != NULL);
181
182 sc->clip = TRUE;
183 sc->clip_x = x;
184 sc->clip_y = y;
185 sc->clip_w = width;
186 sc->clip_h = height;
187 }
188
189 /**
190 * gimp_scan_convert_add_polyline:
191 * @sc: a #GimpScanConvert context
192 * @n_points: number of points to add
193 * @points: array of points to add
194 * @closed: whether to close the polyline and make it a polygon
195 *
196 * Add a polyline with @n_points @points that may be open or closed.
197 *
198 * Please note that you should use gimp_scan_convert_stroke() if you
199 * specify open polygons.
200 */
201 void
gimp_scan_convert_add_polyline(GimpScanConvert * sc,guint n_points,const GimpVector2 * points,gboolean closed)202 gimp_scan_convert_add_polyline (GimpScanConvert *sc,
203 guint n_points,
204 const GimpVector2 *points,
205 gboolean closed)
206 {
207 GimpVector2 prev = { 0.0, 0.0, };
208 cairo_path_data_t pd;
209 gint i;
210
211 g_return_if_fail (sc != NULL);
212 g_return_if_fail (points != NULL);
213 g_return_if_fail (n_points > 0);
214
215 for (i = 0; i < n_points; i++)
216 {
217 /* compress multiple identical coordinates */
218 if (i == 0 ||
219 prev.x != points[i].x ||
220 prev.y != points[i].y)
221 {
222 pd.header.type = (i == 0) ? CAIRO_PATH_MOVE_TO : CAIRO_PATH_LINE_TO;
223 pd.header.length = 2;
224 sc->path_data = g_array_append_val (sc->path_data, pd);
225
226 pd.point.x = points[i].x;
227 pd.point.y = points[i].y;
228 sc->path_data = g_array_append_val (sc->path_data, pd);
229 prev = points[i];
230 }
231 }
232
233 /* close the polyline when needed */
234 if (closed)
235 {
236 pd.header.type = CAIRO_PATH_CLOSE_PATH;
237 pd.header.length = 1;
238 sc->path_data = g_array_append_val (sc->path_data, pd);
239 }
240 }
241
242 /**
243 * gimp_scan_convert_add_polyline:
244 * @sc: a #GimpScanConvert context
245 * @bezier: a #GimpBezierDesc
246 *
247 * Adds a @bezier path to @sc.
248 *
249 * Please note that you should use gimp_scan_convert_stroke() if you
250 * specify open paths.
251 **/
252 void
gimp_scan_convert_add_bezier(GimpScanConvert * sc,const GimpBezierDesc * bezier)253 gimp_scan_convert_add_bezier (GimpScanConvert *sc,
254 const GimpBezierDesc *bezier)
255 {
256 g_return_if_fail (sc != NULL);
257 g_return_if_fail (bezier != NULL);
258
259 sc->path_data = g_array_append_vals (sc->path_data,
260 bezier->data, bezier->num_data);
261 }
262
263 /**
264 * gimp_scan_convert_stroke:
265 * @sc: a #GimpScanConvert context
266 * @width: line width in pixels
267 * @join: how lines should be joined
268 * @cap: how to render the end of lines
269 * @miter: convert a mitered join to a bevelled join if the miter would
270 * extend to a distance of more than @miter times @width from
271 * the actual join point
272 * @dash_offset: offset to apply on the dash pattern
273 * @dash_info: dash pattern or %NULL for a solid line
274 *
275 * Stroke the content of a GimpScanConvert. The next
276 * gimp_scan_convert_render() will result in the outline of the
277 * polygon defined with the commands above.
278 *
279 * You cannot add additional polygons after this command.
280 *
281 * Note that if you have nonstandard resolution, "width" gives the
282 * width (in pixels) for a vertical stroke, i.e. use the X resolution
283 * to calculate the width of a stroke when operating with real world
284 * units.
285 */
286 void
gimp_scan_convert_stroke(GimpScanConvert * sc,gdouble width,GimpJoinStyle join,GimpCapStyle cap,gdouble miter,gdouble dash_offset,GArray * dash_info)287 gimp_scan_convert_stroke (GimpScanConvert *sc,
288 gdouble width,
289 GimpJoinStyle join,
290 GimpCapStyle cap,
291 gdouble miter,
292 gdouble dash_offset,
293 GArray *dash_info)
294 {
295 sc->do_stroke = TRUE;
296 sc->width = width;
297 sc->join = join;
298 sc->cap = cap;
299 sc->miter = miter;
300
301 if (sc->dash_info)
302 {
303 g_array_free (sc->dash_info, TRUE);
304 sc->dash_info = NULL;
305 }
306
307 if (dash_info && dash_info->len >= 2)
308 {
309 gint n_dashes;
310 gdouble *dashes;
311 gint i;
312
313 dash_offset = dash_offset * MAX (width, 1.0);
314
315 n_dashes = dash_info->len;
316 dashes = g_new (gdouble, dash_info->len);
317
318 for (i = 0; i < dash_info->len ; i++)
319 dashes[i] = MAX (width, 1.0) * g_array_index (dash_info, gdouble, i);
320
321 /* correct 0.0 in the first element (starts with a gap) */
322
323 if (dashes[0] == 0.0)
324 {
325 gdouble first;
326
327 first = dashes[1];
328
329 /* shift the pattern to really starts with a dash and
330 * use the offset to skip into it.
331 */
332 for (i = 0; i < dash_info->len - 2; i++)
333 {
334 dashes[i] = dashes[i+2];
335 dash_offset += dashes[i];
336 }
337
338 if (dash_info->len % 2 == 1)
339 {
340 dashes[dash_info->len - 2] = first;
341 n_dashes --;
342 }
343 else if (dash_info->len > 2)
344 {
345 dashes [dash_info->len - 3] += first;
346 n_dashes -= 2;
347 }
348 }
349
350 /* correct odd number of dash specifiers */
351
352 if (n_dashes % 2 == 1)
353 {
354 gdouble last;
355
356 last = dashes[n_dashes - 1];
357 dashes[0] += last;
358 dash_offset += last;
359 n_dashes --;
360 }
361
362 if (n_dashes >= 2)
363 {
364 sc->dash_info = g_array_sized_new (FALSE, FALSE,
365 sizeof (gdouble), n_dashes);
366 sc->dash_info = g_array_append_vals (sc->dash_info, dashes, n_dashes);
367 sc->dash_offset = dash_offset;
368 }
369
370 g_free (dashes);
371 }
372 }
373
374
375 /**
376 * gimp_scan_convert_render:
377 * @sc: a #GimpScanConvert context
378 * @buffer: the #GeglBuffer to render to
379 * @off_x: horizontal offset into the @buffer
380 * @off_y: vertical offset into the @buffer
381 * @antialias: whether to apply antialiasiing
382 *
383 * This is a wrapper around gimp_scan_convert_render_full() that replaces the
384 * content of the @buffer with a rendered form of the path passed in.
385 *
386 * You cannot add additional polygons after this command.
387 */
388 void
gimp_scan_convert_render(GimpScanConvert * sc,GeglBuffer * buffer,gint off_x,gint off_y,gboolean antialias)389 gimp_scan_convert_render (GimpScanConvert *sc,
390 GeglBuffer *buffer,
391 gint off_x,
392 gint off_y,
393 gboolean antialias)
394 {
395 gimp_scan_convert_render_full (sc, buffer, off_x, off_y,
396 TRUE, antialias, 1.0);
397 }
398
399 /**
400 * gimp_scan_convert_render_value:
401 * @sc: a #GimpScanConvert context
402 * @buffer: the #GeglBuffer to render to
403 * @off_x: horizontal offset into the @buffer
404 * @off_y: vertical offset into the @buffer
405 * @value: value to use for covered pixels
406 *
407 * This is a wrapper around gimp_scan_convert_render_full() that
408 * doesn't do antialiasing but gives control over the value that
409 * should be used for pixels covered by the scan conversion. Uncovered
410 * pixels are set to zero.
411 *
412 * You cannot add additional polygons after this command.
413 */
414 void
gimp_scan_convert_render_value(GimpScanConvert * sc,GeglBuffer * buffer,gint off_x,gint off_y,gdouble value)415 gimp_scan_convert_render_value (GimpScanConvert *sc,
416 GeglBuffer *buffer,
417 gint off_x,
418 gint off_y,
419 gdouble value)
420 {
421 gimp_scan_convert_render_full (sc, buffer, off_x, off_y,
422 TRUE, FALSE, value);
423 }
424
425 /**
426 * gimp_scan_convert_compose:
427 * @sc: a #GimpScanConvert context
428 * @buffer: the #GeglBuffer to render to
429 * @off_x: horizontal offset into the @buffer
430 * @off_y: vertical offset into the @buffer
431 *
432 * This is a wrapper around of gimp_scan_convert_render_full() that composes
433 * the (aliased) scan conversion on top of the content of the @buffer.
434 *
435 * You cannot add additional polygons after this command.
436 */
437 void
gimp_scan_convert_compose(GimpScanConvert * sc,GeglBuffer * buffer,gint off_x,gint off_y)438 gimp_scan_convert_compose (GimpScanConvert *sc,
439 GeglBuffer *buffer,
440 gint off_x,
441 gint off_y)
442 {
443 gimp_scan_convert_render_full (sc, buffer, off_x, off_y,
444 FALSE, FALSE, 1.0);
445 }
446
447 /**
448 * gimp_scan_convert_compose_value:
449 * @sc: a #GimpScanConvert context
450 * @buffer: the #GeglBuffer to render to
451 * @off_x: horizontal offset into the @buffer
452 * @off_y: vertical offset into the @buffer
453 * @value: value to use for covered pixels
454 *
455 * This is a wrapper around gimp_scan_convert_render_full() that
456 * composes the (aliased) scan conversion with value @value on top of the
457 * content of the @buffer.
458 *
459 * You cannot add additional polygons after this command.
460 */
461 void
gimp_scan_convert_compose_value(GimpScanConvert * sc,GeglBuffer * buffer,gint off_x,gint off_y,gdouble value)462 gimp_scan_convert_compose_value (GimpScanConvert *sc,
463 GeglBuffer *buffer,
464 gint off_x,
465 gint off_y,
466 gdouble value)
467 {
468 gimp_scan_convert_render_full (sc, buffer, off_x, off_y,
469 FALSE, FALSE, value);
470 }
471
472 /**
473 * gimp_scan_convert_render_full:
474 * @sc: a #GimpScanConvert context
475 * @buffer: the #GeglBuffer to render to
476 * @off_x: horizontal offset into the @buffer
477 * @off_y: vertical offset into the @buffer
478 * @replace: if true the original content of the @buffer gets estroyed
479 * @antialias: if true the rendering happens antialiased
480 * @value: value to use for covered pixels
481 *
482 * This function renders the area described by the path to the
483 * @buffer, taking the offset @off_x and @off_y in the buffer into
484 * account. The rendering can happen antialiased and be rendered on
485 * top of existing content or replacing it completely. The @value
486 * specifies the opacity value to be used for the objects in the @sc.
487 *
488 * You cannot add additional polygons after this command.
489 */
490 void
gimp_scan_convert_render_full(GimpScanConvert * sc,GeglBuffer * buffer,gint off_x,gint off_y,gboolean replace,gboolean antialias,gdouble value)491 gimp_scan_convert_render_full (GimpScanConvert *sc,
492 GeglBuffer *buffer,
493 gint off_x,
494 gint off_y,
495 gboolean replace,
496 gboolean antialias,
497 gdouble value)
498 {
499 const Babl *format;
500 guchar *shared_buf = NULL;
501 gsize shared_buf_size = 0;
502 GeglBufferIterator *iter;
503 GeglRectangle *roi;
504 cairo_t *cr;
505 cairo_surface_t *surface;
506 cairo_path_t path;
507 gint bpp;
508 gint x, y;
509 gint width, height;
510
511 g_return_if_fail (sc != NULL);
512 g_return_if_fail (GEGL_IS_BUFFER (buffer));
513
514 x = gegl_buffer_get_x (buffer);
515 y = gegl_buffer_get_y (buffer);
516 width = gegl_buffer_get_width (buffer);
517 height = gegl_buffer_get_height (buffer);
518
519 if (sc->clip && ! gimp_rectangle_intersect (x, y, width, height,
520 sc->clip_x, sc->clip_y,
521 sc->clip_w, sc->clip_h,
522 &x, &y, &width, &height))
523 return;
524
525 path.status = CAIRO_STATUS_SUCCESS;
526 path.data = (cairo_path_data_t *) sc->path_data->data;
527 path.num_data = sc->path_data->len;
528
529 format = babl_format ("Y u8");
530 bpp = babl_format_get_bytes_per_pixel (format);
531
532 iter = gegl_buffer_iterator_new (buffer, NULL, 0, format,
533 GEGL_ACCESS_READWRITE, GEGL_ABYSS_NONE, 1);
534 roi = &iter->items[0].roi;
535
536 while (gegl_buffer_iterator_next (iter))
537 {
538 guchar *data = iter->items[0].data;
539 guchar *tmp_buf = NULL;
540 const gint stride = cairo_format_stride_for_width (CAIRO_FORMAT_A8,
541 roi->width);
542
543 /* cairo rowstrides are always multiples of 4, whereas
544 * maskPR.rowstride can be anything, so to be able to create an
545 * image surface, we maybe have to create our own temporary
546 * buffer
547 */
548 if (roi->width * bpp != stride)
549 {
550 if (shared_buf_size < stride * roi->height)
551 {
552 shared_buf_size = stride * roi->height;
553 g_free (shared_buf);
554 shared_buf = g_malloc (shared_buf_size);
555 }
556 tmp_buf = shared_buf;
557
558 if (! replace)
559 {
560 const guchar *src = data;
561 guchar *dest = tmp_buf;
562 gint i;
563
564 for (i = 0; i < roi->height; i++)
565 {
566 memcpy (dest, src, roi->width * bpp);
567
568 src += roi->width * bpp;
569 dest += stride;
570 }
571 }
572 }
573
574 surface = cairo_image_surface_create_for_data (tmp_buf ?
575 tmp_buf : data,
576 CAIRO_FORMAT_A8,
577 roi->width, roi->height,
578 stride);
579
580 cairo_surface_set_device_offset (surface,
581 -off_x - roi->x,
582 -off_y - roi->y);
583 cr = cairo_create (surface);
584 cairo_set_operator (cr, CAIRO_OPERATOR_SOURCE);
585
586 if (replace)
587 {
588 cairo_set_source_rgba (cr, 0, 0, 0, 0);
589 cairo_paint (cr);
590 }
591
592 cairo_set_source_rgba (cr, 0, 0, 0, value);
593 cairo_append_path (cr, &path);
594
595 cairo_set_antialias (cr, antialias ?
596 CAIRO_ANTIALIAS_GRAY : CAIRO_ANTIALIAS_NONE);
597 cairo_set_miter_limit (cr, sc->miter);
598
599 if (sc->do_stroke)
600 {
601 cairo_set_line_cap (cr,
602 sc->cap == GIMP_CAP_BUTT ? CAIRO_LINE_CAP_BUTT :
603 sc->cap == GIMP_CAP_ROUND ? CAIRO_LINE_CAP_ROUND :
604 CAIRO_LINE_CAP_SQUARE);
605 cairo_set_line_join (cr,
606 sc->join == GIMP_JOIN_MITER ? CAIRO_LINE_JOIN_MITER :
607 sc->join == GIMP_JOIN_ROUND ? CAIRO_LINE_JOIN_ROUND :
608 CAIRO_LINE_JOIN_BEVEL);
609
610 cairo_set_line_width (cr, sc->width);
611
612 if (sc->dash_info)
613 cairo_set_dash (cr,
614 (double *) sc->dash_info->data,
615 sc->dash_info->len,
616 sc->dash_offset);
617
618 cairo_scale (cr, 1.0, sc->ratio_xy);
619 cairo_stroke (cr);
620 }
621 else
622 {
623 cairo_set_fill_rule (cr, CAIRO_FILL_RULE_EVEN_ODD);
624 cairo_fill (cr);
625 }
626
627 cairo_destroy (cr);
628 cairo_surface_destroy (surface);
629
630 if (tmp_buf)
631 {
632 const guchar *src = tmp_buf;
633 guchar *dest = data;
634 gint i;
635
636 for (i = 0; i < roi->height; i++)
637 {
638 memcpy (dest, src, roi->width * bpp);
639
640 src += stride;
641 dest += roi->width * bpp;
642 }
643 }
644 }
645
646 g_free (shared_buf);
647 }
648