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