gdiplus/tests: Added GdipAddPathLineI test.
[wine] / dlls / gdiplus / graphicspath.c
1 /*
2  * Copyright (C) 2007 Google (Evan Stade)
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Lesser General Public
6  * License as published by the Free Software Foundation; either
7  * version 2.1 of the License, or (at your option) any later version.
8  *
9  * This library 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 GNU
12  * Lesser General Public License for more details.
13  *
14  * You should have received a copy of the GNU Lesser General Public
15  * License along with this library; if not, write to the Free Software
16  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
17  *
18  */
19
20 #include <stdarg.h>
21 #include <math.h>
22
23 #include "windef.h"
24 #include "winbase.h"
25 #include "winuser.h"
26 #include "wingdi.h"
27
28 #include "objbase.h"
29
30 #include "gdiplus.h"
31 #include "gdiplus_private.h"
32 #include "wine/debug.h"
33
34 WINE_DEFAULT_DEBUG_CHANNEL(gdiplus);
35
36 /* make sure path has enough space for len more points */
37 static BOOL lengthen_path(GpPath *path, INT len)
38 {
39     /* initial allocation */
40     if(path->datalen == 0){
41         path->datalen = len * 2;
42
43         path->pathdata.Points = GdipAlloc(path->datalen * sizeof(PointF));
44         if(!path->pathdata.Points)   return FALSE;
45
46         path->pathdata.Types = GdipAlloc(path->datalen);
47         if(!path->pathdata.Types){
48             GdipFree(path->pathdata.Points);
49             return FALSE;
50         }
51     }
52     /* reallocation, double size of arrays */
53     else if(path->datalen - path->pathdata.Count < len){
54         while(path->datalen - path->pathdata.Count < len)
55             path->datalen *= 2;
56
57         path->pathdata.Points = HeapReAlloc(GetProcessHeap(), 0,
58             path->pathdata.Points, path->datalen * sizeof(PointF));
59         if(!path->pathdata.Points)  return FALSE;
60
61         path->pathdata.Types = HeapReAlloc(GetProcessHeap(), 0,
62             path->pathdata.Types, path->datalen);
63         if(!path->pathdata.Types)   return FALSE;
64     }
65
66     return TRUE;
67 }
68
69 GpStatus WINGDIPAPI GdipAddPathArc(GpPath *path, REAL x1, REAL y1, REAL x2,
70     REAL y2, REAL startAngle, REAL sweepAngle)
71 {
72     INT count, old_count, i;
73
74     if(!path)
75         return InvalidParameter;
76
77     count = arc2polybezier(NULL, x1, y1, x2, y2, startAngle, sweepAngle);
78
79     if(count == 0)
80         return Ok;
81     if(!lengthen_path(path, count))
82         return OutOfMemory;
83
84     old_count = path->pathdata.Count;
85     arc2polybezier(&path->pathdata.Points[old_count], x1, y1, x2, y2,
86                    startAngle, sweepAngle);
87
88     for(i = 0; i < count; i++){
89         path->pathdata.Types[old_count + i] = PathPointTypeBezier;
90     }
91
92     path->pathdata.Types[old_count] =
93         (path->newfigure ? PathPointTypeStart : PathPointTypeLine);
94     path->newfigure = FALSE;
95     path->pathdata.Count += count;
96
97     return Ok;
98 }
99
100 GpStatus WINGDIPAPI GdipAddPathBezierI(GpPath *path, INT x1, INT y1, INT x2,
101     INT y2, INT x3, INT y3, INT x4, INT y4)
102 {
103     INT old_count;
104
105     if(!path)
106         return InvalidParameter;
107
108     if(!lengthen_path(path, 4))
109         return OutOfMemory;
110
111     old_count = path->pathdata.Count;
112
113     path->pathdata.Points[old_count].X = (REAL) x1;
114     path->pathdata.Points[old_count].Y = (REAL) y1;
115     path->pathdata.Points[old_count + 1].X = (REAL) x2;
116     path->pathdata.Points[old_count + 1].Y = (REAL) y2;
117     path->pathdata.Points[old_count + 2].X = (REAL) x3;
118     path->pathdata.Points[old_count + 2].Y = (REAL) y3;
119     path->pathdata.Points[old_count + 3].X = (REAL) x4;
120     path->pathdata.Points[old_count + 3].Y = (REAL) y4;
121
122     path->pathdata.Types[old_count] =
123         (path->newfigure ? PathPointTypeStart : PathPointTypeLine);
124     path->pathdata.Types[old_count + 1] = PathPointTypeBezier;
125     path->pathdata.Types[old_count + 2] = PathPointTypeBezier;
126     path->pathdata.Types[old_count + 3] = PathPointTypeBezier;
127
128     path->newfigure = FALSE;
129     path->pathdata.Count += 4;
130
131     return Ok;
132 }
133
134 GpStatus WINGDIPAPI GdipAddPathBeziers(GpPath *path, GDIPCONST GpPointF *points,
135     INT count)
136 {
137     INT i, old_count;
138
139     if(!path || !points || ((count - 1) % 3))
140         return InvalidParameter;
141
142     if(!lengthen_path(path, count))
143         return OutOfMemory;
144
145     old_count = path->pathdata.Count;
146
147     for(i = 0; i < count; i++){
148         path->pathdata.Points[old_count + i].X = points[i].X;
149         path->pathdata.Points[old_count + i].Y = points[i].Y;
150         path->pathdata.Types[old_count + i] = PathPointTypeBezier;
151     }
152
153     path->pathdata.Types[old_count] =
154         (path->newfigure ? PathPointTypeStart : PathPointTypeLine);
155     path->newfigure = FALSE;
156     path->pathdata.Count += count;
157
158     return Ok;
159 }
160
161 GpStatus WINGDIPAPI GdipAddPathEllipse(GpPath *path, REAL x, REAL y, REAL width,
162     REAL height)
163 {
164     INT old_count, numpts;
165
166     if(!path)
167         return InvalidParameter;
168
169     if(!lengthen_path(path, MAX_ARC_PTS))
170         return OutOfMemory;
171
172     old_count = path->pathdata.Count;
173     if((numpts = arc2polybezier(&path->pathdata.Points[old_count],  x, y, width,
174                                height, 0.0, 360.0)) != MAX_ARC_PTS){
175         ERR("expected %d points but got %d\n", MAX_ARC_PTS, numpts);
176         return GenericError;
177     }
178
179     memset(&path->pathdata.Types[old_count + 1], PathPointTypeBezier,
180            MAX_ARC_PTS - 1);
181
182     /* An ellipse is an instrinsic figure (always its own subpath). */
183     path->pathdata.Types[old_count] = PathPointTypeStart;
184     path->pathdata.Types[old_count + MAX_ARC_PTS - 1] |= PathPointTypeCloseSubpath;
185     path->newfigure = TRUE;
186     path->pathdata.Count += MAX_ARC_PTS;
187
188     return Ok;
189 }
190
191 GpStatus WINGDIPAPI GdipAddPathLine2(GpPath *path, GDIPCONST GpPointF *points,
192     INT count)
193 {
194     INT i, old_count;
195
196     if(!path || !points)
197         return InvalidParameter;
198
199     if(!lengthen_path(path, count))
200         return OutOfMemory;
201
202     old_count = path->pathdata.Count;
203
204     for(i = 0; i < count; i++){
205         path->pathdata.Points[old_count + i].X = points[i].X;
206         path->pathdata.Points[old_count + i].Y = points[i].Y;
207         path->pathdata.Types[old_count + i] = PathPointTypeLine;
208     }
209
210     if(path->newfigure){
211         path->pathdata.Types[old_count] = PathPointTypeStart;
212         path->newfigure = FALSE;
213     }
214
215     path->pathdata.Count += count;
216
217     return Ok;
218 }
219
220 GpStatus WINGDIPAPI GdipAddPathLineI(GpPath *path, INT x1, INT y1, INT x2, INT y2)
221 {
222     INT old_count;
223
224     if(!path)
225         return InvalidParameter;
226
227     if(!lengthen_path(path, 2))
228         return OutOfMemory;
229
230     old_count = path->pathdata.Count;
231
232     path->pathdata.Points[old_count].X = (REAL) x1;
233     path->pathdata.Points[old_count].Y = (REAL) y1;
234     path->pathdata.Points[old_count + 1].X = (REAL) x2;
235     path->pathdata.Points[old_count + 1].Y = (REAL) y2;
236
237     path->pathdata.Types[old_count] =
238         (path->newfigure ? PathPointTypeStart : PathPointTypeLine);
239     path->pathdata.Types[old_count + 1] = PathPointTypeLine;
240
241     path->newfigure = FALSE;
242     path->pathdata.Count += 2;
243
244     return Ok;
245 }
246
247 GpStatus WINGDIPAPI GdipAddPathPath(GpPath *path, GDIPCONST GpPath* addingPath,
248     BOOL connect)
249 {
250     INT old_count, count;
251
252     if(!path || !addingPath)
253         return InvalidParameter;
254
255     old_count = path->pathdata.Count;
256     count = addingPath->pathdata.Count;
257
258     if(!lengthen_path(path, count))
259         return OutOfMemory;
260
261     memcpy(&path->pathdata.Points[old_count], addingPath->pathdata.Points,
262            count * sizeof(GpPointF));
263     memcpy(&path->pathdata.Types[old_count], addingPath->pathdata.Types, count);
264
265     if(path->newfigure || !connect)
266         path->pathdata.Types[old_count] = PathPointTypeStart;
267     else
268         path->pathdata.Types[old_count] = PathPointTypeLine;
269
270     path->newfigure = FALSE;
271     path->pathdata.Count += count;
272
273     return Ok;
274 }
275
276 GpStatus WINGDIPAPI GdipClonePath(GpPath* path, GpPath **clone)
277 {
278     if(!path || !clone)
279         return InvalidParameter;
280
281     *clone = GdipAlloc(sizeof(GpPath));
282     if(!*clone) return OutOfMemory;
283
284     memcpy(*clone, path, sizeof(GpPath));
285
286     (*clone)->pathdata.Points = GdipAlloc(path->datalen * sizeof(PointF));
287     (*clone)->pathdata.Types = GdipAlloc(path->datalen);
288     if(!(*clone)->pathdata.Points || !(*clone)->pathdata.Types){
289         GdipFree(*clone);
290         GdipFree((*clone)->pathdata.Points);
291         GdipFree((*clone)->pathdata.Types);
292         return OutOfMemory;
293     }
294
295     memcpy((*clone)->pathdata.Points, path->pathdata.Points,
296            path->datalen * sizeof(PointF));
297     memcpy((*clone)->pathdata.Types, path->pathdata.Types, path->datalen);
298
299     return Ok;
300 }
301
302 GpStatus WINGDIPAPI GdipClosePathFigure(GpPath* path)
303 {
304     if(!path)
305         return InvalidParameter;
306
307     if(path->pathdata.Count > 0){
308         path->pathdata.Types[path->pathdata.Count - 1] |= PathPointTypeCloseSubpath;
309         path->newfigure = TRUE;
310     }
311
312     return Ok;
313 }
314
315 GpStatus WINGDIPAPI GdipClosePathFigures(GpPath* path)
316 {
317     INT i;
318
319     if(!path)
320         return InvalidParameter;
321
322     for(i = 1; i < path->pathdata.Count; i++){
323         if(path->pathdata.Types[i] == PathPointTypeStart)
324             path->pathdata.Types[i-1] |= PathPointTypeCloseSubpath;
325     }
326
327     path->newfigure = TRUE;
328
329     return Ok;
330 }
331
332 GpStatus WINGDIPAPI GdipCreatePath(GpFillMode fill, GpPath **path)
333 {
334     if(!path)
335         return InvalidParameter;
336
337     *path = GdipAlloc(sizeof(GpPath));
338     if(!*path)  return OutOfMemory;
339
340     (*path)->fill = fill;
341     (*path)->newfigure = TRUE;
342
343     return Ok;
344 }
345
346 GpStatus WINGDIPAPI GdipCreatePath2(GDIPCONST GpPointF* points,
347     GDIPCONST BYTE* types, INT count, GpFillMode fill, GpPath **path)
348 {
349     if(!path)
350         return InvalidParameter;
351
352     *path = GdipAlloc(sizeof(GpPath));
353     if(!*path)  return OutOfMemory;
354
355     (*path)->pathdata.Points = GdipAlloc(count * sizeof(PointF));
356     (*path)->pathdata.Types = GdipAlloc(count);
357
358     if(!(*path)->pathdata.Points || !(*path)->pathdata.Types){
359         GdipFree((*path)->pathdata.Points);
360         GdipFree((*path)->pathdata.Types);
361         GdipFree(*path);
362         return OutOfMemory;
363     }
364
365     memcpy((*path)->pathdata.Points, points, count * sizeof(PointF));
366     memcpy((*path)->pathdata.Types, types, count);
367     (*path)->pathdata.Count = count;
368     (*path)->datalen = count;
369
370     (*path)->fill = fill;
371     (*path)->newfigure = TRUE;
372
373     return Ok;
374 }
375
376 GpStatus WINGDIPAPI GdipDeletePath(GpPath *path)
377 {
378     if(!path)
379         return InvalidParameter;
380
381     GdipFree(path->pathdata.Points);
382     GdipFree(path->pathdata.Types);
383     GdipFree(path);
384
385     return Ok;
386 }
387
388 GpStatus WINGDIPAPI GdipGetPathFillMode(GpPath *path, GpFillMode *fillmode)
389 {
390     if(!path || !fillmode)
391         return InvalidParameter;
392
393     *fillmode = path->fill;
394
395     return Ok;
396 }
397
398 GpStatus WINGDIPAPI GdipGetPathPoints(GpPath *path, GpPointF* points, INT count)
399 {
400     if(!path)
401         return InvalidParameter;
402
403     if(count < path->pathdata.Count)
404         return InsufficientBuffer;
405
406     memcpy(points, path->pathdata.Points, path->pathdata.Count * sizeof(GpPointF));
407
408     return Ok;
409 }
410
411 GpStatus WINGDIPAPI GdipGetPathTypes(GpPath *path, BYTE* types, INT count)
412 {
413     if(!path)
414         return InvalidParameter;
415
416     if(count < path->pathdata.Count)
417         return InsufficientBuffer;
418
419     memcpy(types, path->pathdata.Types, path->pathdata.Count);
420
421     return Ok;
422 }
423
424 /* Windows expands the bounding box to the maximum possible bounding box
425  * for a given pen.  For example, if a line join can extend past the point
426  * it's joining by x units, the bounding box is extended by x units in every
427  * direction (even though this is too conservative for most cases). */
428 GpStatus WINGDIPAPI GdipGetPathWorldBounds(GpPath* path, GpRectF* bounds,
429     GDIPCONST GpMatrix *matrix, GDIPCONST GpPen *pen)
430 {
431     GpPointF * points, temp_pts[4];
432     INT count, i;
433     REAL path_width = 1.0, width, height, temp, low_x, low_y, high_x, high_y;
434
435     /* Matrix and pen can be null. */
436     if(!path || !bounds)
437         return InvalidParameter;
438
439     /* If path is empty just return. */
440     count = path->pathdata.Count;
441     if(count == 0){
442         bounds->X = bounds->Y = bounds->Width = bounds->Height = 0.0;
443         return Ok;
444     }
445
446     points = path->pathdata.Points;
447
448     low_x = high_x = points[0].X;
449     low_y = high_y = points[0].Y;
450
451     for(i = 1; i < count; i++){
452         low_x = min(low_x, points[i].X);
453         low_y = min(low_y, points[i].Y);
454         high_x = max(high_x, points[i].X);
455         high_y = max(high_y, points[i].Y);
456     }
457
458     width = high_x - low_x;
459     height = high_y - low_y;
460
461     /* This looks unusual but it's the only way I can imitate windows. */
462     if(matrix){
463         temp_pts[0].X = low_x;
464         temp_pts[0].Y = low_y;
465         temp_pts[1].X = low_x;
466         temp_pts[1].Y = high_y;
467         temp_pts[2].X = high_x;
468         temp_pts[2].Y = high_y;
469         temp_pts[3].X = high_x;
470         temp_pts[3].Y = low_y;
471
472         GdipTransformMatrixPoints((GpMatrix*)matrix, temp_pts, 4);
473         low_x = temp_pts[0].X;
474         low_y = temp_pts[0].Y;
475
476         for(i = 1; i < 4; i++){
477             low_x = min(low_x, temp_pts[i].X);
478             low_y = min(low_y, temp_pts[i].Y);
479         }
480
481         temp = width;
482         width = height * fabs(matrix->matrix[2]) + width * fabs(matrix->matrix[0]);
483         height = height * fabs(matrix->matrix[3]) + temp * fabs(matrix->matrix[1]);
484     }
485
486     if(pen){
487         path_width = pen->width / 2.0;
488
489         if(count > 2)
490             path_width = max(path_width,  pen->width * pen->miterlimit / 2.0);
491         /* FIXME: this should probably also check for the startcap */
492         if(pen->endcap & LineCapNoAnchor)
493             path_width = max(path_width,  pen->width * 2.2);
494
495         low_x -= path_width;
496         low_y -= path_width;
497         width += 2.0 * path_width;
498         height += 2.0 * path_width;
499     }
500
501     bounds->X = low_x;
502     bounds->Y = low_y;
503     bounds->Width = width;
504     bounds->Height = height;
505
506     return Ok;
507 }
508
509 GpStatus WINGDIPAPI GdipGetPointCount(GpPath *path, INT *count)
510 {
511     if(!path)
512         return InvalidParameter;
513
514     *count = path->pathdata.Count;
515
516     return Ok;
517 }
518
519 GpStatus WINGDIPAPI GdipIsOutlineVisiblePathPointI(GpPath* path, INT x, INT y,
520     GpPen *pen, GpGraphics *graphics, BOOL *result)
521 {
522     static int calls;
523
524     if(!path || !pen)
525         return InvalidParameter;
526
527     if(!(calls++))
528         FIXME("not implemented\n");
529
530     return NotImplemented;
531 }
532
533 GpStatus WINGDIPAPI GdipStartPathFigure(GpPath *path)
534 {
535     if(!path)
536         return InvalidParameter;
537
538     path->newfigure = TRUE;
539
540     return Ok;
541 }
542
543 GpStatus WINGDIPAPI GdipResetPath(GpPath *path)
544 {
545     if(!path)
546         return InvalidParameter;
547
548     path->pathdata.Count = 0;
549     path->newfigure = TRUE;
550     path->fill = FillModeAlternate;
551
552     return Ok;
553 }
554
555 GpStatus WINGDIPAPI GdipSetPathFillMode(GpPath *path, GpFillMode fill)
556 {
557     if(!path)
558         return InvalidParameter;
559
560     path->fill = fill;
561
562     return Ok;
563 }
564
565 GpStatus WINGDIPAPI GdipTransformPath(GpPath *path, GpMatrix *matrix)
566 {
567     if(!path)
568         return InvalidParameter;
569
570     if(path->pathdata.Count == 0)
571         return Ok;
572
573     return GdipTransformMatrixPoints(matrix, (GpPointF*) path->pathdata.Points,
574                                      path->pathdata.Count);
575 }