Sync with trunk revision 63128.
[reactos.git] / dll / win32 / gdiplus / region.c
1 /*
2 * Copyright (C) 2008 Google (Lei Zhang)
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 #include "gdiplus_private.h"
20
21 /**********************************************************
22 *
23 * Data returned by GdipGetRegionData looks something like this:
24 *
25 * struct region_data_header
26 * {
27 * DWORD size; size in bytes of the data - 8.
28 * DWORD magic1; probably a checksum.
29 * DWORD magic2; always seems to be 0xdbc01001 - version?
30 * DWORD num_ops; number of combining ops * 2
31 * };
32 *
33 * Then follows a sequence of combining ops and region elements.
34 *
35 * A region element is either a RECTF or some path data.
36 *
37 * Combining ops are just stored as their CombineMode value.
38 *
39 * Each RECTF is preceded by the DWORD 0x10000000. An empty rect is
40 * stored as 0x10000002 (with no following RECTF) and an infinite rect
41 * is stored as 0x10000003 (again with no following RECTF).
42 *
43 * Path data is preceded by the DWORD 0x10000001. Then follows a
44 * DWORD size and then size bytes of data.
45 *
46 * The combining ops are stored in the reverse order to the region
47 * elements and in the reverse order to which the region was
48 * constructed.
49 *
50 * When two or more complex regions (ie those with more than one
51 * element) are combined, the combining op for the two regions comes
52 * first, then the combining ops for the region elements in region 1,
53 * followed by the region elements for region 1, then follows the
54 * combining ops for region 2 and finally region 2's region elements.
55 * Presumably you're supposed to use the 0x1000000x header to find the
56 * end of the op list (the count of the elements in each region is not
57 * stored).
58 *
59 * When a simple region (1 element) is combined, it's treated as if a
60 * single rect/path is being combined.
61 *
62 */
63
64 #define FLAGS_NOFLAGS 0x0
65 #define FLAGS_INTPATH 0x4000
66
67 /* Header size as far as header->size is concerned. This doesn't include
68 * header->size or header->checksum
69 */
70 static const INT sizeheader_size = sizeof(DWORD) * 2;
71
72 typedef struct packed_point
73 {
74 short X;
75 short Y;
76 } packed_point;
77
78 /* Test to see if the path could be stored as an array of shorts */
79 static BOOL is_integer_path(const GpPath *path)
80 {
81 int i;
82
83 if (!path->pathdata.Count) return FALSE;
84
85 for (i = 0; i < path->pathdata.Count; i++)
86 {
87 short x, y;
88 x = gdip_round(path->pathdata.Points[i].X);
89 y = gdip_round(path->pathdata.Points[i].Y);
90 if (path->pathdata.Points[i].X != (REAL)x || path->pathdata.Points[i].Y != (REAL)y)
91 return FALSE;
92 }
93 return TRUE;
94 }
95
96 /* Everything is measured in DWORDS; round up if there's a remainder */
97 static inline INT get_pathtypes_size(const GpPath* path)
98 {
99 INT needed = path->pathdata.Count / sizeof(DWORD);
100
101 if (path->pathdata.Count % sizeof(DWORD) > 0)
102 needed++;
103
104 return needed * sizeof(DWORD);
105 }
106
107 static inline INT get_element_size(const region_element* element)
108 {
109 INT needed = sizeof(DWORD); /* DWORD for the type */
110 switch(element->type)
111 {
112 case RegionDataRect:
113 return needed + sizeof(GpRect);
114 case RegionDataPath:
115 {
116 const GpPath *path = element->elementdata.path;
117 DWORD flags = is_integer_path(path) ? FLAGS_INTPATH : FLAGS_NOFLAGS;
118 /* 3 for headers, once again size doesn't count itself */
119 needed += sizeof(DWORD) * 3;
120 if (flags & FLAGS_INTPATH)
121 needed += 2 * sizeof(SHORT) * path->pathdata.Count;
122 else
123 needed += 2 * sizeof(FLOAT) * path->pathdata.Count;
124
125 needed += get_pathtypes_size(path);
126 needed += sizeof(DWORD); /* Extra DWORD for pathheader.size */
127 return needed;
128 }
129 case RegionDataEmptyRect:
130 case RegionDataInfiniteRect:
131 return needed;
132 default:
133 needed += get_element_size(element->elementdata.combine.left);
134 needed += get_element_size(element->elementdata.combine.right);
135 return needed;
136 }
137
138 return 0;
139 }
140
141 /* Does not check parameters, caller must do that */
142 static inline GpStatus init_region(GpRegion* region, const RegionType type)
143 {
144 region->node.type = type;
145 region->num_children = 0;
146
147 return Ok;
148 }
149
150 static inline GpStatus clone_element(const region_element* element,
151 region_element** element2)
152 {
153 GpStatus stat;
154
155 /* root node is allocated with GpRegion */
156 if(!*element2){
157 *element2 = GdipAlloc(sizeof(region_element));
158 if (!*element2)
159 return OutOfMemory;
160 }
161
162 (*element2)->type = element->type;
163
164 switch (element->type)
165 {
166 case RegionDataRect:
167 (*element2)->elementdata.rect = element->elementdata.rect;
168 return Ok;
169 case RegionDataEmptyRect:
170 case RegionDataInfiniteRect:
171 return Ok;
172 case RegionDataPath:
173 stat = GdipClonePath(element->elementdata.path, &(*element2)->elementdata.path);
174 if (stat == Ok) return Ok;
175 break;
176 default:
177 (*element2)->elementdata.combine.left = NULL;
178 (*element2)->elementdata.combine.right = NULL;
179
180 stat = clone_element(element->elementdata.combine.left,
181 &(*element2)->elementdata.combine.left);
182 if (stat == Ok)
183 {
184 stat = clone_element(element->elementdata.combine.right,
185 &(*element2)->elementdata.combine.right);
186 if (stat == Ok) return Ok;
187 }
188 break;
189 }
190
191 delete_element(*element2);
192 *element2 = NULL;
193 return stat;
194 }
195
196 /* Common code for CombineRegion*
197 * All the caller has to do is get its format into an element
198 */
199 static inline void fuse_region(GpRegion* region, region_element* left,
200 region_element* right, const CombineMode mode)
201 {
202 region->node.type = mode;
203 region->node.elementdata.combine.left = left;
204 region->node.elementdata.combine.right = right;
205 region->num_children += 2;
206 }
207
208 /*****************************************************************************
209 * GdipCloneRegion [GDIPLUS.@]
210 *
211 * Creates a deep copy of the region
212 *
213 * PARAMS
214 * region [I] source region
215 * clone [O] resulting clone
216 *
217 * RETURNS
218 * SUCCESS: Ok
219 * FAILURE: InvalidParameter or OutOfMemory
220 */
221 GpStatus WINGDIPAPI GdipCloneRegion(GpRegion *region, GpRegion **clone)
222 {
223 region_element *element;
224
225 TRACE("%p %p\n", region, clone);
226
227 if (!(region && clone))
228 return InvalidParameter;
229
230 *clone = GdipAlloc(sizeof(GpRegion));
231 if (!*clone)
232 return OutOfMemory;
233 element = &(*clone)->node;
234
235 (*clone)->num_children = region->num_children;
236 return clone_element(&region->node, &element);
237 }
238
239 /*****************************************************************************
240 * GdipCombineRegionPath [GDIPLUS.@]
241 */
242 GpStatus WINGDIPAPI GdipCombineRegionPath(GpRegion *region, GpPath *path, CombineMode mode)
243 {
244 GpRegion *path_region;
245 region_element *left, *right = NULL;
246 GpStatus stat;
247
248 TRACE("%p %p %d\n", region, path, mode);
249
250 if (!(region && path))
251 return InvalidParameter;
252
253 stat = GdipCreateRegionPath(path, &path_region);
254 if (stat != Ok)
255 return stat;
256
257 /* simply replace region data */
258 if(mode == CombineModeReplace){
259 delete_element(&region->node);
260 memcpy(region, path_region, sizeof(GpRegion));
261 GdipFree(path_region);
262 return Ok;
263 }
264
265 left = GdipAlloc(sizeof(region_element));
266 if (left)
267 {
268 *left = region->node;
269 stat = clone_element(&path_region->node, &right);
270 if (stat == Ok)
271 {
272 fuse_region(region, left, right, mode);
273 GdipDeleteRegion(path_region);
274 return Ok;
275 }
276 }
277 else
278 stat = OutOfMemory;
279
280 GdipFree(left);
281 GdipDeleteRegion(path_region);
282 return stat;
283 }
284
285 /*****************************************************************************
286 * GdipCombineRegionRect [GDIPLUS.@]
287 */
288 GpStatus WINGDIPAPI GdipCombineRegionRect(GpRegion *region,
289 GDIPCONST GpRectF *rect, CombineMode mode)
290 {
291 GpRegion *rect_region;
292 region_element *left, *right = NULL;
293 GpStatus stat;
294
295 TRACE("%p %s %d\n", region, debugstr_rectf(rect), mode);
296
297 if (!(region && rect))
298 return InvalidParameter;
299
300 stat = GdipCreateRegionRect(rect, &rect_region);
301 if (stat != Ok)
302 return stat;
303
304 /* simply replace region data */
305 if(mode == CombineModeReplace){
306 delete_element(&region->node);
307 memcpy(region, rect_region, sizeof(GpRegion));
308 GdipFree(rect_region);
309 return Ok;
310 }
311
312 left = GdipAlloc(sizeof(region_element));
313 if (left)
314 {
315 memcpy(left, &region->node, sizeof(region_element));
316 stat = clone_element(&rect_region->node, &right);
317 if (stat == Ok)
318 {
319 fuse_region(region, left, right, mode);
320 GdipDeleteRegion(rect_region);
321 return Ok;
322 }
323 }
324 else
325 stat = OutOfMemory;
326
327 GdipFree(left);
328 GdipDeleteRegion(rect_region);
329 return stat;
330 }
331
332 /*****************************************************************************
333 * GdipCombineRegionRectI [GDIPLUS.@]
334 */
335 GpStatus WINGDIPAPI GdipCombineRegionRectI(GpRegion *region,
336 GDIPCONST GpRect *rect, CombineMode mode)
337 {
338 GpRectF rectf;
339
340 TRACE("%p %p %d\n", region, rect, mode);
341
342 if (!rect)
343 return InvalidParameter;
344
345 rectf.X = (REAL)rect->X;
346 rectf.Y = (REAL)rect->Y;
347 rectf.Height = (REAL)rect->Height;
348 rectf.Width = (REAL)rect->Width;
349
350 return GdipCombineRegionRect(region, &rectf, mode);
351 }
352
353 /*****************************************************************************
354 * GdipCombineRegionRegion [GDIPLUS.@]
355 */
356 GpStatus WINGDIPAPI GdipCombineRegionRegion(GpRegion *region1,
357 GpRegion *region2, CombineMode mode)
358 {
359 region_element *left, *right = NULL;
360 GpStatus stat;
361 GpRegion *reg2copy;
362
363 TRACE("%p %p %d\n", region1, region2, mode);
364
365 if(!(region1 && region2))
366 return InvalidParameter;
367
368 /* simply replace region data */
369 if(mode == CombineModeReplace){
370 stat = GdipCloneRegion(region2, &reg2copy);
371 if(stat != Ok) return stat;
372
373 delete_element(&region1->node);
374 memcpy(region1, reg2copy, sizeof(GpRegion));
375 GdipFree(reg2copy);
376 return Ok;
377 }
378
379 left = GdipAlloc(sizeof(region_element));
380 if (!left)
381 return OutOfMemory;
382
383 *left = region1->node;
384 stat = clone_element(&region2->node, &right);
385 if (stat != Ok)
386 {
387 GdipFree(left);
388 return OutOfMemory;
389 }
390
391 fuse_region(region1, left, right, mode);
392 region1->num_children += region2->num_children;
393
394 return Ok;
395 }
396
397 /*****************************************************************************
398 * GdipCreateRegion [GDIPLUS.@]
399 */
400 GpStatus WINGDIPAPI GdipCreateRegion(GpRegion **region)
401 {
402 TRACE("%p\n", region);
403
404 if(!region)
405 return InvalidParameter;
406
407 *region = GdipAlloc(sizeof(GpRegion));
408 if(!*region)
409 return OutOfMemory;
410
411 TRACE("=> %p\n", *region);
412
413 return init_region(*region, RegionDataInfiniteRect);
414 }
415
416 /*****************************************************************************
417 * GdipCreateRegionPath [GDIPLUS.@]
418 *
419 * Creates a GpRegion from a GpPath
420 *
421 * PARAMS
422 * path [I] path to base the region on
423 * region [O] pointer to the newly allocated region
424 *
425 * RETURNS
426 * SUCCESS: Ok
427 * FAILURE: InvalidParameter
428 *
429 * NOTES
430 * If a path has no floating point points, its points will be stored as shorts
431 * (INTPATH)
432 *
433 * If a path is empty, it is considered to be an INTPATH
434 */
435 GpStatus WINGDIPAPI GdipCreateRegionPath(GpPath *path, GpRegion **region)
436 {
437 region_element* element;
438 GpStatus stat;
439
440 TRACE("%p, %p\n", path, region);
441
442 if (!(path && region))
443 return InvalidParameter;
444
445 *region = GdipAlloc(sizeof(GpRegion));
446 if(!*region)
447 return OutOfMemory;
448 stat = init_region(*region, RegionDataPath);
449 if (stat != Ok)
450 {
451 GdipDeleteRegion(*region);
452 return stat;
453 }
454 element = &(*region)->node;
455
456 stat = GdipClonePath(path, &element->elementdata.path);
457 if (stat != Ok)
458 {
459 GdipDeleteRegion(*region);
460 return stat;
461 }
462
463 return Ok;
464 }
465
466 /*****************************************************************************
467 * GdipCreateRegionRect [GDIPLUS.@]
468 */
469 GpStatus WINGDIPAPI GdipCreateRegionRect(GDIPCONST GpRectF *rect,
470 GpRegion **region)
471 {
472 GpStatus stat;
473
474 TRACE("%p, %p\n", rect, region);
475
476 if (!(rect && region))
477 return InvalidParameter;
478
479 *region = GdipAlloc(sizeof(GpRegion));
480 stat = init_region(*region, RegionDataRect);
481 if(stat != Ok)
482 {
483 GdipDeleteRegion(*region);
484 return stat;
485 }
486
487 (*region)->node.elementdata.rect.X = rect->X;
488 (*region)->node.elementdata.rect.Y = rect->Y;
489 (*region)->node.elementdata.rect.Width = rect->Width;
490 (*region)->node.elementdata.rect.Height = rect->Height;
491
492 return Ok;
493 }
494
495 /*****************************************************************************
496 * GdipCreateRegionRectI [GDIPLUS.@]
497 */
498 GpStatus WINGDIPAPI GdipCreateRegionRectI(GDIPCONST GpRect *rect,
499 GpRegion **region)
500 {
501 GpRectF rectf;
502
503 TRACE("%p, %p\n", rect, region);
504
505 rectf.X = (REAL)rect->X;
506 rectf.Y = (REAL)rect->Y;
507 rectf.Width = (REAL)rect->Width;
508 rectf.Height = (REAL)rect->Height;
509
510 return GdipCreateRegionRect(&rectf, region);
511 }
512
513 GpStatus WINGDIPAPI GdipCreateRegionRgnData(GDIPCONST BYTE *data, INT size, GpRegion **region)
514 {
515 FIXME("(%p, %d, %p): stub\n", data, size, region);
516
517 *region = NULL;
518 return NotImplemented;
519 }
520
521
522 /******************************************************************************
523 * GdipCreateRegionHrgn [GDIPLUS.@]
524 */
525 GpStatus WINGDIPAPI GdipCreateRegionHrgn(HRGN hrgn, GpRegion **region)
526 {
527 DWORD size;
528 LPRGNDATA buf;
529 LPRECT rect;
530 GpStatus stat;
531 GpPath* path;
532 GpRegion* local;
533 DWORD i;
534
535 TRACE("(%p, %p)\n", hrgn, region);
536
537 if(!region || !(size = GetRegionData(hrgn, 0, NULL)))
538 return InvalidParameter;
539
540 buf = GdipAlloc(size);
541 if(!buf)
542 return OutOfMemory;
543
544 if(!GetRegionData(hrgn, size, buf)){
545 GdipFree(buf);
546 return GenericError;
547 }
548
549 if(buf->rdh.nCount == 0){
550 if((stat = GdipCreateRegion(&local)) != Ok){
551 GdipFree(buf);
552 return stat;
553 }
554 if((stat = GdipSetEmpty(local)) != Ok){
555 GdipFree(buf);
556 GdipDeleteRegion(local);
557 return stat;
558 }
559 *region = local;
560 GdipFree(buf);
561 return Ok;
562 }
563
564 if((stat = GdipCreatePath(FillModeAlternate, &path)) != Ok){
565 GdipFree(buf);
566 return stat;
567 }
568
569 rect = (LPRECT)buf->Buffer;
570 for(i = 0; i < buf->rdh.nCount; i++){
571 if((stat = GdipAddPathRectangle(path, (REAL)rect->left, (REAL)rect->top,
572 (REAL)(rect->right - rect->left), (REAL)(rect->bottom - rect->top))) != Ok){
573 GdipFree(buf);
574 GdipDeletePath(path);
575 return stat;
576 }
577 rect++;
578 }
579
580 stat = GdipCreateRegionPath(path, region);
581
582 GdipFree(buf);
583 GdipDeletePath(path);
584 return stat;
585 }
586
587 /*****************************************************************************
588 * GdipDeleteRegion [GDIPLUS.@]
589 */
590 GpStatus WINGDIPAPI GdipDeleteRegion(GpRegion *region)
591 {
592 TRACE("%p\n", region);
593
594 if (!region)
595 return InvalidParameter;
596
597 delete_element(&region->node);
598 GdipFree(region);
599
600 return Ok;
601 }
602
603 /*****************************************************************************
604 * GdipGetRegionBounds [GDIPLUS.@]
605 */
606 GpStatus WINGDIPAPI GdipGetRegionBounds(GpRegion *region, GpGraphics *graphics, GpRectF *rect)
607 {
608 HRGN hrgn;
609 RECT r;
610 GpStatus status;
611
612 TRACE("(%p, %p, %p)\n", region, graphics, rect);
613
614 if(!region || !graphics || !rect)
615 return InvalidParameter;
616
617 /* Contrary to MSDN, native ignores the graphics transform. */
618 status = GdipGetRegionHRgn(region, NULL, &hrgn);
619 if(status != Ok)
620 return status;
621
622 /* infinite */
623 if(!hrgn){
624 rect->X = rect->Y = -(REAL)(1 << 22);
625 rect->Width = rect->Height = (REAL)(1 << 23);
626 TRACE("%p => infinite\n", region);
627 return Ok;
628 }
629
630 if(GetRgnBox(hrgn, &r)){
631 rect->X = r.left;
632 rect->Y = r.top;
633 rect->Width = r.right - r.left;
634 rect->Height = r.bottom - r.top;
635 TRACE("%p => %s\n", region, debugstr_rectf(rect));
636 }
637 else
638 status = GenericError;
639
640 DeleteObject(hrgn);
641
642 return status;
643 }
644
645 /*****************************************************************************
646 * GdipGetRegionBoundsI [GDIPLUS.@]
647 */
648 GpStatus WINGDIPAPI GdipGetRegionBoundsI(GpRegion *region, GpGraphics *graphics, GpRect *rect)
649 {
650 GpRectF rectf;
651 GpStatus status;
652
653 TRACE("(%p, %p, %p)\n", region, graphics, rect);
654
655 if(!rect)
656 return InvalidParameter;
657
658 status = GdipGetRegionBounds(region, graphics, &rectf);
659 if(status == Ok){
660 rect->X = gdip_round(rectf.X);
661 rect->Y = gdip_round(rectf.Y);
662 rect->Width = gdip_round(rectf.Width);
663 rect->Height = gdip_round(rectf.Height);
664 }
665
666 return status;
667 }
668
669 static inline void write_dword(DWORD* location, INT* offset, const DWORD write)
670 {
671 location[*offset] = write;
672 (*offset)++;
673 }
674
675 static inline void write_float(DWORD* location, INT* offset, const FLOAT write)
676 {
677 ((FLOAT*)location)[*offset] = write;
678 (*offset)++;
679 }
680
681 static inline void write_packed_point(DWORD* location, INT* offset,
682 const GpPointF* write)
683 {
684 packed_point *point = (packed_point *)(location + *offset);
685 point->X = gdip_round(write->X);
686 point->Y = gdip_round(write->Y);
687 (*offset)++;
688 }
689
690 static inline void write_path_types(DWORD* location, INT* offset,
691 const GpPath* path)
692 {
693 INT rounded_size = get_pathtypes_size(path);
694
695 memcpy(location + *offset, path->pathdata.Types, path->pathdata.Count);
696
697 /* The unwritten parts of the DWORD (if any) must be cleared */
698 if (rounded_size - path->pathdata.Count)
699 ZeroMemory(((BYTE*)location) + (*offset * sizeof(DWORD)) +
700 path->pathdata.Count, rounded_size - path->pathdata.Count);
701 *offset += rounded_size / sizeof(DWORD);
702 }
703
704 static void write_element(const region_element* element, DWORD *buffer,
705 INT* filled)
706 {
707 write_dword(buffer, filled, element->type);
708 switch (element->type)
709 {
710 case CombineModeReplace:
711 case CombineModeIntersect:
712 case CombineModeUnion:
713 case CombineModeXor:
714 case CombineModeExclude:
715 case CombineModeComplement:
716 write_element(element->elementdata.combine.left, buffer, filled);
717 write_element(element->elementdata.combine.right, buffer, filled);
718 break;
719 case RegionDataRect:
720 write_float(buffer, filled, element->elementdata.rect.X);
721 write_float(buffer, filled, element->elementdata.rect.Y);
722 write_float(buffer, filled, element->elementdata.rect.Width);
723 write_float(buffer, filled, element->elementdata.rect.Height);
724 break;
725 case RegionDataPath:
726 {
727 INT i;
728 const GpPath* path = element->elementdata.path;
729 struct _pathheader
730 {
731 DWORD size;
732 DWORD magic;
733 DWORD count;
734 DWORD flags;
735 } *pathheader;
736
737 pathheader = (struct _pathheader *)(buffer + *filled);
738
739 pathheader->flags = is_integer_path(path) ? FLAGS_INTPATH : FLAGS_NOFLAGS;
740 /* 3 for headers, once again size doesn't count itself */
741 pathheader->size = sizeof(DWORD) * 3;
742 if (pathheader->flags & FLAGS_INTPATH)
743 pathheader->size += 2 * sizeof(SHORT) * path->pathdata.Count;
744 else
745 pathheader->size += 2 * sizeof(FLOAT) * path->pathdata.Count;
746 pathheader->size += get_pathtypes_size(path);
747 pathheader->magic = VERSION_MAGIC;
748 pathheader->count = path->pathdata.Count;
749
750 *filled += 4;
751
752 switch (pathheader->flags & FLAGS_INTPATH)
753 {
754 case FLAGS_NOFLAGS:
755 for (i = 0; i < path->pathdata.Count; i++)
756 {
757 write_float(buffer, filled, path->pathdata.Points[i].X);
758 write_float(buffer, filled, path->pathdata.Points[i].Y);
759 }
760 break;
761 case FLAGS_INTPATH:
762 for (i = 0; i < path->pathdata.Count; i++)
763 {
764 write_packed_point(buffer, filled,
765 &path->pathdata.Points[i]);
766 }
767 break;
768 }
769 write_path_types(buffer, filled, path);
770 break;
771 }
772 case RegionDataEmptyRect:
773 case RegionDataInfiniteRect:
774 break;
775 }
776 }
777
778 /*****************************************************************************
779 * GdipGetRegionData [GDIPLUS.@]
780 *
781 * Returns the header, followed by combining ops and region elements.
782 *
783 * PARAMS
784 * region [I] region to retrieve from
785 * buffer [O] buffer to hold the resulting data
786 * size [I] size of the buffer
787 * needed [O] (optional) how much data was written
788 *
789 * RETURNS
790 * SUCCESS: Ok
791 * FAILURE: InvalidParameter
792 *
793 * NOTES
794 * The header contains the size, a checksum, a version string, and the number
795 * of children. The size does not count itself or the checksum.
796 * Version is always something like 0xdbc01001 or 0xdbc01002
797 *
798 * An element is a RECT, or PATH; Combining ops are stored as their
799 * CombineMode value. Special regions (infinite, empty) emit just their
800 * op-code; GpRectFs emit their code followed by their points; GpPaths emit
801 * their code followed by a second header for the path followed by the actual
802 * path data. Followed by the flags for each point. The pathheader contains
803 * the size of the data to follow, a version number again, followed by a count
804 * of how many points, and any special flags which may apply. 0x4000 means its
805 * a path of shorts instead of FLOAT.
806 *
807 * Combining Ops are stored in reverse order from when they were constructed;
808 * the output is a tree where the left side combining area is always taken
809 * first.
810 */
811 GpStatus WINGDIPAPI GdipGetRegionData(GpRegion *region, BYTE *buffer, UINT size,
812 UINT *needed)
813 {
814 struct _region_header
815 {
816 DWORD size;
817 DWORD checksum;
818 DWORD magic;
819 DWORD num_children;
820 } *region_header;
821 INT filled = 0;
822 UINT required;
823 GpStatus status;
824
825 TRACE("%p, %p, %d, %p\n", region, buffer, size, needed);
826
827 if (!region || !buffer || !size)
828 return InvalidParameter;
829
830 status = GdipGetRegionDataSize(region, &required);
831 if (status != Ok) return status;
832 if (size < required)
833 {
834 if (needed) *needed = size;
835 return InsufficientBuffer;
836 }
837
838 region_header = (struct _region_header *)buffer;
839 region_header->size = sizeheader_size + get_element_size(&region->node);
840 region_header->checksum = 0;
841 region_header->magic = VERSION_MAGIC;
842 region_header->num_children = region->num_children;
843 filled += 4;
844 /* With few exceptions, everything written is DWORD aligned,
845 * so use that as our base */
846 write_element(&region->node, (DWORD*)buffer, &filled);
847
848 if (needed)
849 *needed = filled * sizeof(DWORD);
850
851 return Ok;
852 }
853
854 /*****************************************************************************
855 * GdipGetRegionDataSize [GDIPLUS.@]
856 */
857 GpStatus WINGDIPAPI GdipGetRegionDataSize(GpRegion *region, UINT *needed)
858 {
859 TRACE("%p, %p\n", region, needed);
860
861 if (!(region && needed))
862 return InvalidParameter;
863
864 /* header.size doesn't count header.size and header.checksum */
865 *needed = sizeof(DWORD) * 2 + sizeheader_size + get_element_size(&region->node);
866
867 return Ok;
868 }
869
870 static GpStatus get_path_hrgn(GpPath *path, GpGraphics *graphics, HRGN *hrgn)
871 {
872 HDC new_hdc=NULL;
873 GpGraphics *new_graphics=NULL;
874 GpStatus stat;
875 INT save_state;
876
877 if (!graphics)
878 {
879 new_hdc = CreateCompatibleDC(0);
880 if (!new_hdc)
881 return OutOfMemory;
882
883 stat = GdipCreateFromHDC(new_hdc, &new_graphics);
884 graphics = new_graphics;
885 if (stat != Ok)
886 {
887 DeleteDC(new_hdc);
888 return stat;
889 }
890 }
891 else if (!graphics->hdc)
892 {
893 graphics->hdc = new_hdc = CreateCompatibleDC(0);
894 if (!new_hdc)
895 return OutOfMemory;
896 }
897
898 save_state = SaveDC(graphics->hdc);
899 EndPath(graphics->hdc);
900
901 SetPolyFillMode(graphics->hdc, (path->fill == FillModeAlternate ? ALTERNATE
902 : WINDING));
903
904 stat = trace_path(graphics, path);
905 if (stat == Ok)
906 {
907 *hrgn = PathToRegion(graphics->hdc);
908 stat = *hrgn ? Ok : OutOfMemory;
909 }
910
911 RestoreDC(graphics->hdc, save_state);
912 if (new_hdc)
913 {
914 DeleteDC(new_hdc);
915 if (new_graphics)
916 GdipDeleteGraphics(new_graphics);
917 else
918 graphics->hdc = NULL;
919 }
920
921 return stat;
922 }
923
924 static GpStatus get_region_hrgn(struct region_element *element, GpGraphics *graphics, HRGN *hrgn)
925 {
926 switch (element->type)
927 {
928 case RegionDataInfiniteRect:
929 *hrgn = NULL;
930 return Ok;
931 case RegionDataEmptyRect:
932 *hrgn = CreateRectRgn(0, 0, 0, 0);
933 return *hrgn ? Ok : OutOfMemory;
934 case RegionDataPath:
935 return get_path_hrgn(element->elementdata.path, graphics, hrgn);
936 case RegionDataRect:
937 {
938 GpPath* path;
939 GpStatus stat;
940 GpRectF* rc = &element->elementdata.rect;
941
942 stat = GdipCreatePath(FillModeAlternate, &path);
943 if (stat != Ok)
944 return stat;
945 stat = GdipAddPathRectangle(path, rc->X, rc->Y, rc->Width, rc->Height);
946
947 if (stat == Ok)
948 stat = get_path_hrgn(path, graphics, hrgn);
949
950 GdipDeletePath(path);
951
952 return stat;
953 }
954 case CombineModeIntersect:
955 case CombineModeUnion:
956 case CombineModeXor:
957 case CombineModeExclude:
958 case CombineModeComplement:
959 {
960 HRGN left, right;
961 GpStatus stat;
962 int ret;
963
964 stat = get_region_hrgn(element->elementdata.combine.left, graphics, &left);
965 if (stat != Ok)
966 {
967 *hrgn = NULL;
968 return stat;
969 }
970
971 if (left == NULL)
972 {
973 /* existing region is infinite */
974 switch (element->type)
975 {
976 case CombineModeIntersect:
977 return get_region_hrgn(element->elementdata.combine.right, graphics, hrgn);
978 case CombineModeXor: case CombineModeExclude:
979 left = CreateRectRgn(-4194304, -4194304, 4194304, 4194304);
980 break;
981 case CombineModeUnion: case CombineModeComplement:
982 *hrgn = NULL;
983 return Ok;
984 }
985 }
986
987 stat = get_region_hrgn(element->elementdata.combine.right, graphics, &right);
988 if (stat != Ok)
989 {
990 DeleteObject(left);
991 *hrgn = NULL;
992 return stat;
993 }
994
995 if (right == NULL)
996 {
997 /* new region is infinite */
998 switch (element->type)
999 {
1000 case CombineModeIntersect:
1001 *hrgn = left;
1002 return Ok;
1003 case CombineModeXor: case CombineModeComplement:
1004 right = CreateRectRgn(-4194304, -4194304, 4194304, 4194304);
1005 break;
1006 case CombineModeUnion: case CombineModeExclude:
1007 DeleteObject(left);
1008 *hrgn = NULL;
1009 return Ok;
1010 }
1011 }
1012
1013 switch (element->type)
1014 {
1015 case CombineModeIntersect:
1016 ret = CombineRgn(left, left, right, RGN_AND);
1017 break;
1018 case CombineModeUnion:
1019 ret = CombineRgn(left, left, right, RGN_OR);
1020 break;
1021 case CombineModeXor:
1022 ret = CombineRgn(left, left, right, RGN_XOR);
1023 break;
1024 case CombineModeExclude:
1025 ret = CombineRgn(left, left, right, RGN_DIFF);
1026 break;
1027 case CombineModeComplement:
1028 ret = CombineRgn(left, right, left, RGN_DIFF);
1029 break;
1030 default:
1031 ret = ERROR;
1032 }
1033
1034 DeleteObject(right);
1035
1036 if (ret == ERROR)
1037 {
1038 DeleteObject(left);
1039 *hrgn = NULL;
1040 return GenericError;
1041 }
1042
1043 *hrgn = left;
1044 return Ok;
1045 }
1046 default:
1047 FIXME("GdipGetRegionHRgn unimplemented for region type=%x\n", element->type);
1048 *hrgn = NULL;
1049 return NotImplemented;
1050 }
1051 }
1052
1053 /*****************************************************************************
1054 * GdipGetRegionHRgn [GDIPLUS.@]
1055 */
1056 GpStatus WINGDIPAPI GdipGetRegionHRgn(GpRegion *region, GpGraphics *graphics, HRGN *hrgn)
1057 {
1058 TRACE("(%p, %p, %p)\n", region, graphics, hrgn);
1059
1060 if (!region || !hrgn)
1061 return InvalidParameter;
1062
1063 return get_region_hrgn(&region->node, graphics, hrgn);
1064 }
1065
1066 GpStatus WINGDIPAPI GdipIsEmptyRegion(GpRegion *region, GpGraphics *graphics, BOOL *res)
1067 {
1068 GpStatus status;
1069 GpRectF rect;
1070
1071 TRACE("(%p, %p, %p)\n", region, graphics, res);
1072
1073 if(!region || !graphics || !res)
1074 return InvalidParameter;
1075
1076 status = GdipGetRegionBounds(region, graphics, &rect);
1077 if (status != Ok) return status;
1078
1079 *res = rect.Width == 0.0 && rect.Height == 0.0;
1080 TRACE("=> %d\n", *res);
1081
1082 return Ok;
1083 }
1084
1085 /*****************************************************************************
1086 * GdipIsEqualRegion [GDIPLUS.@]
1087 */
1088 GpStatus WINGDIPAPI GdipIsEqualRegion(GpRegion *region, GpRegion *region2, GpGraphics *graphics,
1089 BOOL *res)
1090 {
1091 HRGN hrgn1, hrgn2;
1092 GpStatus stat;
1093
1094 TRACE("(%p, %p, %p, %p)\n", region, region2, graphics, res);
1095
1096 if(!region || !region2 || !graphics || !res)
1097 return InvalidParameter;
1098
1099 stat = GdipGetRegionHRgn(region, graphics, &hrgn1);
1100 if(stat != Ok)
1101 return stat;
1102 stat = GdipGetRegionHRgn(region2, graphics, &hrgn2);
1103 if(stat != Ok){
1104 DeleteObject(hrgn1);
1105 return stat;
1106 }
1107
1108 *res = EqualRgn(hrgn1, hrgn2);
1109
1110 /* one of GpRegions is infinite */
1111 if(*res == ERROR)
1112 *res = (!hrgn1 && !hrgn2);
1113
1114 DeleteObject(hrgn1);
1115 DeleteObject(hrgn2);
1116
1117 return Ok;
1118 }
1119
1120 /*****************************************************************************
1121 * GdipIsInfiniteRegion [GDIPLUS.@]
1122 */
1123 GpStatus WINGDIPAPI GdipIsInfiniteRegion(GpRegion *region, GpGraphics *graphics, BOOL *res)
1124 {
1125 /* I think graphics is ignored here */
1126 TRACE("(%p, %p, %p)\n", region, graphics, res);
1127
1128 if(!region || !graphics || !res)
1129 return InvalidParameter;
1130
1131 *res = (region->node.type == RegionDataInfiniteRect);
1132
1133 return Ok;
1134 }
1135
1136 /*****************************************************************************
1137 * GdipIsVisibleRegionRect [GDIPLUS.@]
1138 */
1139 GpStatus WINGDIPAPI GdipIsVisibleRegionRect(GpRegion* region, REAL x, REAL y, REAL w, REAL h, GpGraphics *graphics, BOOL *res)
1140 {
1141 HRGN hrgn;
1142 GpStatus stat;
1143 RECT rect;
1144
1145 TRACE("(%p, %.2f, %.2f, %.2f, %.2f, %p, %p)\n", region, x, y, w, h, graphics, res);
1146
1147 if(!region || !res)
1148 return InvalidParameter;
1149
1150 if((stat = GdipGetRegionHRgn(region, NULL, &hrgn)) != Ok)
1151 return stat;
1152
1153 /* infinite */
1154 if(!hrgn){
1155 *res = TRUE;
1156 return Ok;
1157 }
1158
1159 rect.left = ceilr(x);
1160 rect.top = ceilr(y);
1161 rect.right = ceilr(x + w);
1162 rect.bottom = ceilr(y + h);
1163
1164 *res = RectInRegion(hrgn, &rect);
1165
1166 DeleteObject(hrgn);
1167
1168 return Ok;
1169 }
1170
1171 /*****************************************************************************
1172 * GdipIsVisibleRegionRectI [GDIPLUS.@]
1173 */
1174 GpStatus WINGDIPAPI GdipIsVisibleRegionRectI(GpRegion* region, INT x, INT y, INT w, INT h, GpGraphics *graphics, BOOL *res)
1175 {
1176 TRACE("(%p, %d, %d, %d, %d, %p, %p)\n", region, x, y, w, h, graphics, res);
1177 if(!region || !res)
1178 return InvalidParameter;
1179
1180 return GdipIsVisibleRegionRect(region, (REAL)x, (REAL)y, (REAL)w, (REAL)h, graphics, res);
1181 }
1182
1183 /*****************************************************************************
1184 * GdipIsVisibleRegionPoint [GDIPLUS.@]
1185 */
1186 GpStatus WINGDIPAPI GdipIsVisibleRegionPoint(GpRegion* region, REAL x, REAL y, GpGraphics *graphics, BOOL *res)
1187 {
1188 HRGN hrgn;
1189 GpStatus stat;
1190
1191 TRACE("(%p, %.2f, %.2f, %p, %p)\n", region, x, y, graphics, res);
1192
1193 if(!region || !res)
1194 return InvalidParameter;
1195
1196 if((stat = GdipGetRegionHRgn(region, NULL, &hrgn)) != Ok)
1197 return stat;
1198
1199 /* infinite */
1200 if(!hrgn){
1201 *res = TRUE;
1202 return Ok;
1203 }
1204
1205 *res = PtInRegion(hrgn, gdip_round(x), gdip_round(y));
1206
1207 DeleteObject(hrgn);
1208
1209 return Ok;
1210 }
1211
1212 /*****************************************************************************
1213 * GdipIsVisibleRegionPointI [GDIPLUS.@]
1214 */
1215 GpStatus WINGDIPAPI GdipIsVisibleRegionPointI(GpRegion* region, INT x, INT y, GpGraphics *graphics, BOOL *res)
1216 {
1217 TRACE("(%p, %d, %d, %p, %p)\n", region, x, y, graphics, res);
1218
1219 return GdipIsVisibleRegionPoint(region, (REAL)x, (REAL)y, graphics, res);
1220 }
1221
1222 /*****************************************************************************
1223 * GdipSetEmpty [GDIPLUS.@]
1224 */
1225 GpStatus WINGDIPAPI GdipSetEmpty(GpRegion *region)
1226 {
1227 GpStatus stat;
1228
1229 TRACE("%p\n", region);
1230
1231 if (!region)
1232 return InvalidParameter;
1233
1234 delete_element(&region->node);
1235 stat = init_region(region, RegionDataEmptyRect);
1236
1237 return stat;
1238 }
1239
1240 GpStatus WINGDIPAPI GdipSetInfinite(GpRegion *region)
1241 {
1242 GpStatus stat;
1243
1244 TRACE("%p\n", region);
1245
1246 if (!region)
1247 return InvalidParameter;
1248
1249 delete_element(&region->node);
1250 stat = init_region(region, RegionDataInfiniteRect);
1251
1252 return stat;
1253 }
1254
1255 /* Transforms GpRegion elements with given matrix */
1256 static GpStatus transform_region_element(region_element* element, GpMatrix *matrix)
1257 {
1258 GpStatus stat;
1259
1260 switch(element->type)
1261 {
1262 case RegionDataEmptyRect:
1263 case RegionDataInfiniteRect:
1264 return Ok;
1265 case RegionDataRect:
1266 {
1267 /* We can't transform a rectangle, so convert it to a path. */
1268 GpRegion *new_region;
1269 GpPath *path;
1270
1271 stat = GdipCreatePath(FillModeAlternate, &path);
1272 if (stat == Ok)
1273 {
1274 stat = GdipAddPathRectangle(path,
1275 element->elementdata.rect.X, element->elementdata.rect.Y,
1276 element->elementdata.rect.Width, element->elementdata.rect.Height);
1277
1278 if (stat == Ok)
1279 stat = GdipCreateRegionPath(path, &new_region);
1280
1281 GdipDeletePath(path);
1282 }
1283
1284 if (stat == Ok)
1285 {
1286 /* Steal the element from the created region. */
1287 memcpy(element, &new_region->node, sizeof(region_element));
1288 GdipFree(new_region);
1289 }
1290 else
1291 return stat;
1292 }
1293 /* Fall-through to do the actual conversion. */
1294 case RegionDataPath:
1295 if (!element->elementdata.path->pathdata.Count)
1296 return Ok;
1297
1298 stat = GdipTransformMatrixPoints(matrix,
1299 element->elementdata.path->pathdata.Points,
1300 element->elementdata.path->pathdata.Count);
1301 return stat;
1302 default:
1303 stat = transform_region_element(element->elementdata.combine.left, matrix);
1304 if (stat == Ok)
1305 stat = transform_region_element(element->elementdata.combine.right, matrix);
1306 return stat;
1307 }
1308 }
1309
1310 GpStatus WINGDIPAPI GdipTransformRegion(GpRegion *region, GpMatrix *matrix)
1311 {
1312 TRACE("(%p, %p)\n", region, matrix);
1313
1314 if (!region || !matrix)
1315 return InvalidParameter;
1316
1317 return transform_region_element(&region->node, matrix);
1318 }
1319
1320 /* Translates GpRegion elements with specified offsets */
1321 static void translate_region_element(region_element* element, REAL dx, REAL dy)
1322 {
1323 INT i;
1324
1325 switch(element->type)
1326 {
1327 case RegionDataEmptyRect:
1328 case RegionDataInfiniteRect:
1329 return;
1330 case RegionDataRect:
1331 element->elementdata.rect.X += dx;
1332 element->elementdata.rect.Y += dy;
1333 return;
1334 case RegionDataPath:
1335 for(i = 0; i < element->elementdata.path->pathdata.Count; i++){
1336 element->elementdata.path->pathdata.Points[i].X += dx;
1337 element->elementdata.path->pathdata.Points[i].Y += dy;
1338 }
1339 return;
1340 default:
1341 translate_region_element(element->elementdata.combine.left, dx, dy);
1342 translate_region_element(element->elementdata.combine.right, dx, dy);
1343 return;
1344 }
1345 }
1346
1347 /*****************************************************************************
1348 * GdipTranslateRegion [GDIPLUS.@]
1349 */
1350 GpStatus WINGDIPAPI GdipTranslateRegion(GpRegion *region, REAL dx, REAL dy)
1351 {
1352 TRACE("(%p, %f, %f)\n", region, dx, dy);
1353
1354 if(!region)
1355 return InvalidParameter;
1356
1357 translate_region_element(&region->node, dx, dy);
1358
1359 return Ok;
1360 }
1361
1362 /*****************************************************************************
1363 * GdipTranslateRegionI [GDIPLUS.@]
1364 */
1365 GpStatus WINGDIPAPI GdipTranslateRegionI(GpRegion *region, INT dx, INT dy)
1366 {
1367 TRACE("(%p, %d, %d)\n", region, dx, dy);
1368
1369 return GdipTranslateRegion(region, (REAL)dx, (REAL)dy);
1370 }
1371
1372 static GpStatus get_region_scans_data(GpRegion *region, GpMatrix *matrix, LPRGNDATA *data)
1373 {
1374 GpRegion *region_copy;
1375 GpStatus stat;
1376 HRGN hrgn;
1377 DWORD data_size;
1378
1379 stat = GdipCloneRegion(region, &region_copy);
1380
1381 if (stat == Ok)
1382 {
1383 stat = GdipTransformRegion(region_copy, matrix);
1384
1385 if (stat == Ok)
1386 stat = GdipGetRegionHRgn(region_copy, NULL, &hrgn);
1387
1388 if (stat == Ok)
1389 {
1390 if (hrgn)
1391 {
1392 data_size = GetRegionData(hrgn, 0, NULL);
1393
1394 *data = GdipAlloc(data_size);
1395
1396 if (*data)
1397 GetRegionData(hrgn, data_size, *data);
1398 else
1399 stat = OutOfMemory;
1400
1401 DeleteObject(hrgn);
1402 }
1403 else
1404 {
1405 data_size = sizeof(RGNDATAHEADER) + sizeof(RECT);
1406
1407 *data = GdipAlloc(data_size);
1408
1409 if (*data)
1410 {
1411 (*data)->rdh.dwSize = sizeof(RGNDATAHEADER);
1412 (*data)->rdh.iType = RDH_RECTANGLES;
1413 (*data)->rdh.nCount = 1;
1414 (*data)->rdh.nRgnSize = sizeof(RECT);
1415 (*data)->rdh.rcBound.left = (*data)->rdh.rcBound.top = -0x400000;
1416 (*data)->rdh.rcBound.right = (*data)->rdh.rcBound.bottom = 0x400000;
1417
1418 memcpy((*data)->Buffer, &(*data)->rdh.rcBound, sizeof(RECT));
1419 }
1420 else
1421 stat = OutOfMemory;
1422 }
1423 }
1424
1425 GdipDeleteRegion(region_copy);
1426 }
1427
1428 return stat;
1429 }
1430
1431 GpStatus WINGDIPAPI GdipGetRegionScansCount(GpRegion *region, UINT *count, GpMatrix *matrix)
1432 {
1433 GpStatus stat;
1434 LPRGNDATA data;
1435
1436 TRACE("(%p, %p, %p)\n", region, count, matrix);
1437
1438 if (!region || !count || !matrix)
1439 return InvalidParameter;
1440
1441 stat = get_region_scans_data(region, matrix, &data);
1442
1443 if (stat == Ok)
1444 {
1445 *count = data->rdh.nCount;
1446 GdipFree(data);
1447 }
1448
1449 return stat;
1450 }
1451
1452 GpStatus WINGDIPAPI GdipGetRegionScansI(GpRegion *region, GpRect *scans, INT *count, GpMatrix *matrix)
1453 {
1454 GpStatus stat;
1455 DWORD i;
1456 LPRGNDATA data;
1457 RECT *rects;
1458
1459 if (!region || !count || !matrix)
1460 return InvalidParameter;
1461
1462 stat = get_region_scans_data(region, matrix, &data);
1463
1464 if (stat == Ok)
1465 {
1466 *count = data->rdh.nCount;
1467 rects = (RECT*)data->Buffer;
1468
1469 if (scans)
1470 {
1471 for (i=0; i<data->rdh.nCount; i++)
1472 {
1473 scans[i].X = rects[i].left;
1474 scans[i].Y = rects[i].top;
1475 scans[i].Width = rects[i].right - rects[i].left;
1476 scans[i].Height = rects[i].bottom - rects[i].top;
1477 }
1478 }
1479
1480 GdipFree(data);
1481 }
1482
1483 return Ok;
1484 }
1485
1486 GpStatus WINGDIPAPI GdipGetRegionScans(GpRegion *region, GpRectF *scans, INT *count, GpMatrix *matrix)
1487 {
1488 GpStatus stat;
1489 DWORD i;
1490 LPRGNDATA data;
1491 RECT *rects;
1492
1493 if (!region || !count || !matrix)
1494 return InvalidParameter;
1495
1496 stat = get_region_scans_data(region, matrix, &data);
1497
1498 if (stat == Ok)
1499 {
1500 *count = data->rdh.nCount;
1501 rects = (RECT*)data->Buffer;
1502
1503 if (scans)
1504 {
1505 for (i=0; i<data->rdh.nCount; i++)
1506 {
1507 scans[i].X = rects[i].left;
1508 scans[i].Y = rects[i].top;
1509 scans[i].Width = rects[i].right - rects[i].left;
1510 scans[i].Height = rects[i].bottom - rects[i].top;
1511 }
1512 }
1513
1514 GdipFree(data);
1515 }
1516
1517 return Ok;
1518 }