Coverage Report

Created: 2026-02-26 06:54

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/FreeRDP/libfreerdp/gdi/region.c
Line
Count
Source
1
/**
2
 * FreeRDP: A Remote Desktop Protocol Implementation
3
 * GDI Region Functions
4
 *
5
 * Copyright 2010-2011 Marc-Andre Moreau <marcandre.moreau@gmail.com>
6
 * Copyright 2016 Armin Novak <armin.novak@thincast.com>
7
 * Copyright 2016 Thincast Technologies GmbH
8
 *
9
 * Licensed under the Apache License, Version 2.0 (the "License");
10
 * you may not use this file except in compliance with the License.
11
 * You may obtain a copy of the License at
12
 *
13
 *     http://www.apache.org/licenses/LICENSE-2.0
14
 *
15
 * Unless required by applicable law or agreed to in writing, software
16
 * distributed under the License is distributed on an "AS IS" BASIS,
17
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
18
 * See the License for the specific language governing permissions and
19
 * limitations under the License.
20
 */
21
22
#include <freerdp/config.h>
23
24
#include <stdio.h>
25
#include <string.h>
26
#include <stdlib.h>
27
28
#include <winpr/wtypes.h>
29
30
#include <freerdp/api.h>
31
#include <freerdp/freerdp.h>
32
#include <freerdp/gdi/gdi.h>
33
34
#include <freerdp/gdi/region.h>
35
36
#include <freerdp/log.h>
37
38
#define TAG FREERDP_TAG("gdi.region")
39
40
static char* gdi_rect_str(char* buffer, size_t size, const GDI_RECT* rect)
41
0
{
42
0
  if (!buffer || (size < 1) || !rect)
43
0
    return NULL;
44
45
0
  (void)_snprintf(buffer, size - 1,
46
0
                  "[top/left=%" PRId32 "x%" PRId32 "-bottom/right%" PRId32 "x%" PRId32 "]",
47
0
                  rect->top, rect->left, rect->bottom, rect->right);
48
0
  buffer[size - 1] = '\0';
49
50
0
  return buffer;
51
0
}
52
53
static char* gdi_regn_str(char* buffer, size_t size, const GDI_RGN* rgn)
54
0
{
55
0
  if (!buffer || (size < 1) || !rgn)
56
0
    return NULL;
57
58
0
  (void)_snprintf(buffer, size - 1, "[%" PRId32 "x%" PRId32 "-%" PRId32 "x%" PRId32 "]", rgn->x,
59
0
                  rgn->y, rgn->w, rgn->h);
60
0
  buffer[size - 1] = '\0';
61
62
0
  return buffer;
63
0
}
64
65
/**
66
 * Create a region from rectangular coordinates.
67
 * msdn{dd183514}
68
 *
69
 * @param nLeftRect x1
70
 * @param nTopRect y1
71
 * @param nRightRect x2
72
 * @param nBottomRect y2
73
 *
74
 * @return new region
75
 */
76
77
GDI_RGN* gdi_CreateRectRgn(INT32 nLeftRect, INT32 nTopRect, INT32 nRightRect, INT32 nBottomRect)
78
0
{
79
0
  INT64 w = 0;
80
0
  INT64 h = 0;
81
0
  GDI_RGN* hRgn = NULL;
82
83
0
  w = nRightRect - nLeftRect + 1ll;
84
0
  h = nBottomRect - nTopRect + 1ll;
85
0
  if ((w < 0) || (h < 0) || (w > INT32_MAX) || (h > INT32_MAX))
86
0
  {
87
0
    WLog_ERR(TAG,
88
0
             "Can not create region top/left=%" PRId32 "x%" PRId32 "-bottom/right=%" PRId32
89
0
             "x%" PRId32,
90
0
             nTopRect, nLeftRect, nBottomRect, nRightRect);
91
0
    return NULL;
92
0
  }
93
0
  hRgn = (GDI_RGN*)calloc(1, sizeof(GDI_RGN));
94
95
0
  if (!hRgn)
96
0
    return NULL;
97
98
0
  hRgn->objectType = GDIOBJECT_REGION;
99
0
  hRgn->x = nLeftRect;
100
0
  hRgn->y = nTopRect;
101
0
  hRgn->w = (INT32)w;
102
0
  hRgn->h = (INT32)h;
103
0
  hRgn->null = FALSE;
104
0
  return hRgn;
105
0
}
106
107
/**
108
 * Create a new rectangle.
109
 * @param xLeft x1
110
 * @param yTop y1
111
 * @param xRight x2
112
 * @param yBottom y2
113
 * @return new rectangle
114
 */
115
116
GDI_RECT* gdi_CreateRect(INT32 xLeft, INT32 yTop, INT32 xRight, INT32 yBottom)
117
0
{
118
0
  GDI_RECT* hRect = NULL;
119
120
0
  if (xLeft > xRight)
121
0
    return NULL;
122
0
  if (yTop > yBottom)
123
0
    return NULL;
124
125
0
  hRect = (GDI_RECT*)calloc(1, sizeof(GDI_RECT));
126
127
0
  if (!hRect)
128
0
    return NULL;
129
130
0
  hRect->objectType = GDIOBJECT_RECT;
131
0
  hRect->left = xLeft;
132
0
  hRect->top = yTop;
133
0
  hRect->right = xRight;
134
0
  hRect->bottom = yBottom;
135
0
  return hRect;
136
0
}
137
138
/**
139
 * Convert a rectangle to a region.
140
 * @param rect source rectangle
141
 * @param rgn destination region
142
 */
143
144
BOOL gdi_RectToRgn(const GDI_RECT* rect, GDI_RGN* rgn)
145
0
{
146
0
  WINPR_ASSERT(rect);
147
0
  WINPR_ASSERT(rgn);
148
149
0
  BOOL rc = TRUE;
150
0
  INT64 w = rect->right - rect->left + 1ll;
151
0
  INT64 h = rect->bottom - rect->top + 1ll;
152
153
0
  if ((w < 0) || (h < 0) || (w > INT32_MAX) || (h > INT32_MAX))
154
0
  {
155
0
    WLog_ERR(TAG,
156
0
             "Can not create region top/left=%" PRId32 "x%" PRId32 "-bottom/right=%" PRId32
157
0
             "x%" PRId32,
158
0
             rect->top, rect->left, rect->bottom, rect->right);
159
0
    w = 0;
160
0
    h = 0;
161
0
    rc = FALSE;
162
0
  }
163
164
0
  rgn->x = rect->left;
165
0
  rgn->y = rect->top;
166
0
  rgn->w = (INT32)w;
167
0
  rgn->h = (INT32)h;
168
169
0
  return rc;
170
0
}
171
172
/**
173
 * Convert rectangular coordinates to a region.
174
 * @param left x1
175
 * @param top y1
176
 * @param right x2
177
 * @param bottom y2
178
 * @param rgn destination region
179
 */
180
181
BOOL gdi_CRectToRgn(INT32 left, INT32 top, INT32 right, INT32 bottom, GDI_RGN* rgn)
182
0
{
183
0
  BOOL rc = TRUE;
184
0
  INT64 w = 0;
185
0
  INT64 h = 0;
186
0
  w = right - left + 1ll;
187
0
  h = bottom - top + 1ll;
188
189
0
  if (!rgn)
190
0
    return FALSE;
191
192
0
  if ((w < 0) || (h < 0) || (w > INT32_MAX) || (h > INT32_MAX))
193
0
  {
194
0
    WLog_ERR(TAG,
195
0
             "Can not create region top/left=%" PRId32 "x%" PRId32 "-bottom/right=%" PRId32
196
0
             "x%" PRId32,
197
0
             top, left, bottom, right);
198
0
    w = 0;
199
0
    h = 0;
200
0
    rc = FALSE;
201
0
  }
202
203
0
  rgn->x = left;
204
0
  rgn->y = top;
205
0
  rgn->w = (INT32)w;
206
0
  rgn->h = (INT32)h;
207
0
  return rc;
208
0
}
209
210
/**
211
 * Convert a rectangle to region coordinates.
212
 * @param rect source rectangle
213
 * @param x x1
214
 * @param y y1
215
 * @param w width
216
 * @param h height
217
 */
218
219
BOOL gdi_RectToCRgn(const GDI_RECT* rect, INT32* x, INT32* y, INT32* w, INT32* h)
220
0
{
221
0
  BOOL rc = TRUE;
222
0
  *x = rect->left;
223
0
  *y = rect->top;
224
0
  INT64 tmp = rect->right - rect->left + 1;
225
0
  if ((tmp < 0) || (tmp > INT32_MAX))
226
0
  {
227
0
    char buffer[256];
228
0
    WLog_ERR(TAG, "rectangle invalid %s", gdi_rect_str(buffer, sizeof(buffer), rect));
229
0
    *w = 0;
230
0
    rc = FALSE;
231
0
  }
232
0
  else
233
0
    *w = (INT32)tmp;
234
0
  tmp = rect->bottom - rect->top + 1;
235
0
  if ((tmp < 0) || (tmp > INT32_MAX))
236
0
  {
237
0
    char buffer[256];
238
0
    WLog_ERR(TAG, "rectangle invalid %s", gdi_rect_str(buffer, sizeof(buffer), rect));
239
0
    *h = 0;
240
0
    rc = FALSE;
241
0
  }
242
0
  else
243
0
    *h = (INT32)tmp;
244
0
  return rc;
245
0
}
246
247
/**
248
 * Convert rectangular coordinates to region coordinates.
249
 * @param left x1
250
 * @param top y1
251
 * @param right x2
252
 * @param bottom y2
253
 * @param x x1
254
 * @param y y1
255
 * @param w width
256
 * @param h height
257
 */
258
259
BOOL gdi_CRectToCRgn(INT32 left, INT32 top, INT32 right, INT32 bottom, INT32* x, INT32* y, INT32* w,
260
                     INT32* h)
261
0
{
262
0
  INT64 wl = 0;
263
0
  INT64 hl = 0;
264
0
  BOOL rc = TRUE;
265
0
  wl = right - left + 1ll;
266
0
  hl = bottom - top + 1ll;
267
268
0
  if ((left > right) || (top > bottom) || (wl <= 0) || (hl <= 0) || (wl > INT32_MAX) ||
269
0
      (hl > INT32_MAX))
270
0
  {
271
0
    WLog_ERR(TAG,
272
0
             "Can not create region top/left=%" PRId32 "x%" PRId32 "-bottom/right=%" PRId32
273
0
             "x%" PRId32,
274
0
             top, left, bottom, right);
275
0
    wl = 0;
276
0
    hl = 0;
277
0
    rc = FALSE;
278
0
  }
279
280
0
  *x = left;
281
0
  *y = top;
282
0
  *w = (INT32)wl;
283
0
  *h = (INT32)hl;
284
0
  return rc;
285
0
}
286
287
/**
288
 * Convert a region to a rectangle.
289
 * @param rgn source region
290
 * @param rect destination rectangle
291
 */
292
293
BOOL gdi_RgnToRect(const GDI_RGN* rgn, GDI_RECT* rect)
294
0
{
295
0
  INT64 r = 0;
296
0
  INT64 b = 0;
297
0
  BOOL rc = TRUE;
298
0
  r = rgn->x + rgn->w - 1ll;
299
0
  b = rgn->y + rgn->h - 1ll;
300
301
0
  if ((r < INT32_MIN) || (r > INT32_MAX) || (b < INT32_MIN) || (b > INT32_MAX))
302
0
  {
303
0
    char buffer[256];
304
0
    WLog_ERR(TAG, "Can not create region %s", gdi_regn_str(buffer, sizeof(buffer), rgn));
305
0
    r = rgn->x;
306
0
    b = rgn->y;
307
0
    rc = FALSE;
308
0
  }
309
0
  rect->left = rgn->x;
310
0
  rect->top = rgn->y;
311
0
  rect->right = (INT32)r;
312
0
  rect->bottom = (INT32)b;
313
314
0
  return rc;
315
0
}
316
317
/**
318
 * Convert region coordinates to a rectangle.
319
 * @param x x1
320
 * @param y y1
321
 * @param w width
322
 * @param h height
323
 * @param rect destination rectangle
324
 */
325
326
BOOL gdi_CRgnToRect(INT64 x, INT64 y, INT32 w, INT32 h, GDI_RECT* rect)
327
0
{
328
0
  BOOL invalid = FALSE;
329
0
  const INT64 r = x + w - 1;
330
0
  const INT64 b = y + h - 1;
331
0
  WINPR_ASSERT(x <= INT32_MAX);
332
0
  WINPR_ASSERT(y <= INT32_MAX);
333
0
  WINPR_ASSERT(r <= INT32_MAX);
334
0
  WINPR_ASSERT(b <= INT32_MAX);
335
0
  rect->left = (x > 0) ? (INT32)x : 0;
336
0
  rect->top = (y > 0) ? (INT32)y : 0;
337
0
  rect->right = rect->left;
338
0
  rect->bottom = rect->top;
339
340
0
  if ((w <= 0) || (h <= 0))
341
0
    invalid = TRUE;
342
343
0
  if (r > 0)
344
0
    rect->right = (INT32)r;
345
0
  else
346
0
    invalid = TRUE;
347
348
0
  if (b > 0)
349
0
    rect->bottom = (INT32)b;
350
0
  else
351
0
    invalid = TRUE;
352
353
0
  if (invalid)
354
0
  {
355
0
    WLog_DBG(TAG, "Invisible rectangle %" PRId64 "x%" PRId64 "-%" PRId64 "x%" PRId64, x, y, r,
356
0
             b);
357
0
    return FALSE;
358
0
  }
359
360
0
  return TRUE;
361
0
}
362
363
/**
364
 * Convert a region to rectangular coordinates.
365
 * @param rgn source region
366
 * @param left x1
367
 * @param top y1
368
 * @param right x2
369
 * @param bottom y2
370
 */
371
372
BOOL gdi_RgnToCRect(const GDI_RGN* rgn, INT32* left, INT32* top, INT32* right, INT32* bottom)
373
0
{
374
0
  BOOL rc = TRUE;
375
376
0
  WINPR_ASSERT(rgn);
377
0
  if ((rgn->w < 0) || (rgn->h < 0))
378
0
  {
379
0
    char buffer[256];
380
0
    WLog_ERR(TAG, "Can not create region %s", gdi_regn_str(buffer, sizeof(buffer), rgn));
381
0
    rc = FALSE;
382
0
  }
383
384
0
  *left = rgn->x;
385
0
  *top = rgn->y;
386
0
  *right = rgn->x + rgn->w - 1;
387
0
  *bottom = rgn->y + rgn->h - 1;
388
389
0
  return rc;
390
0
}
391
392
/**
393
 * Convert region coordinates to rectangular coordinates.
394
 * @param x x1
395
 * @param y y1
396
 * @param w width
397
 * @param h height
398
 * @param left x1
399
 * @param top y1
400
 * @param right x2
401
 * @param bottom y2
402
 */
403
404
BOOL gdi_CRgnToCRect(INT32 x, INT32 y, INT32 w, INT32 h, INT32* left, INT32* top, INT32* right,
405
                     INT32* bottom)
406
0
{
407
0
  BOOL rc = TRUE;
408
0
  *left = x;
409
0
  *top = y;
410
0
  *right = 0;
411
412
0
  if (w > 0)
413
0
    *right = x + w - 1;
414
0
  else
415
0
  {
416
0
    WLog_ERR(TAG, "Invalid width");
417
0
    rc = FALSE;
418
0
  }
419
420
0
  *bottom = 0;
421
422
0
  if (h > 0)
423
0
    *bottom = y + h - 1;
424
0
  else
425
0
  {
426
0
    WLog_ERR(TAG, "Invalid height");
427
0
    rc = FALSE;
428
0
  }
429
430
0
  return rc;
431
0
}
432
433
/**
434
 * Check if copying would involve overlapping regions
435
 * @param x x1
436
 * @param y y1
437
 * @param width width
438
 * @param height height
439
 * @param srcx source x1
440
 * @param srcy source y1
441
 * @return nonzero if there is an overlap, 0 otherwise
442
 */
443
444
inline BOOL gdi_CopyOverlap(INT32 x, INT32 y, INT32 width, INT32 height, INT32 srcx, INT32 srcy)
445
0
{
446
0
  GDI_RECT dst;
447
0
  GDI_RECT src;
448
0
  if (!gdi_CRgnToRect(x, y, width, height, &dst))
449
0
    return FALSE;
450
0
  if (!gdi_CRgnToRect(srcx, srcy, width, height, &src))
451
0
    return FALSE;
452
453
0
  if (dst.right < src.left)
454
0
    return FALSE;
455
0
  if (dst.left > src.right)
456
0
    return FALSE;
457
0
  if (dst.bottom < src.top)
458
0
    return FALSE;
459
0
  if (dst.top > src.bottom)
460
0
    return FALSE;
461
0
  return TRUE;
462
0
}
463
464
/**
465
 * Set the coordinates of a given rectangle.
466
 * msdn{dd145085}
467
 *
468
 * @param rc rectangle
469
 * @param xLeft x1
470
 * @param yTop y1
471
 * @param xRight x2
472
 * @param yBottom y2
473
 *
474
 * @return nonzero if successful, 0 otherwise
475
 */
476
477
inline BOOL gdi_SetRect(GDI_RECT* rc, INT32 xLeft, INT32 yTop, INT32 xRight, INT32 yBottom)
478
0
{
479
0
  if (!rc)
480
0
    return FALSE;
481
0
  if (xLeft > xRight)
482
0
    return FALSE;
483
0
  if (yTop > yBottom)
484
0
    return FALSE;
485
486
0
  rc->left = xLeft;
487
0
  rc->top = yTop;
488
0
  rc->right = xRight;
489
0
  rc->bottom = yBottom;
490
0
  return TRUE;
491
0
}
492
493
/**
494
 * Set the coordinates of a given region.
495
 * @param hRgn region
496
 * @param nXLeft x1
497
 * @param nYLeft y1
498
 * @param nWidth width
499
 * @param nHeight height
500
 * @return nonzero if successful, 0 otherwise
501
 */
502
503
inline BOOL gdi_SetRgn(GDI_RGN* hRgn, INT32 nXLeft, INT32 nYLeft, INT32 nWidth, INT32 nHeight)
504
0
{
505
0
  if (!hRgn)
506
0
    return FALSE;
507
508
0
  if ((nWidth < 0) || (nHeight < 0))
509
0
    return FALSE;
510
511
0
  hRgn->x = nXLeft;
512
0
  hRgn->y = nYLeft;
513
0
  hRgn->w = nWidth;
514
0
  hRgn->h = nHeight;
515
0
  hRgn->null = FALSE;
516
0
  return TRUE;
517
0
}
518
519
/**
520
 * Convert rectangular coordinates to a region
521
 * @param hRgn destination region
522
 * @param nLeftRect x1
523
 * @param nTopRect y1
524
 * @param nRightRect x2
525
 * @param nBottomRect y2
526
 * @return nonzero if successful, 0 otherwise
527
 */
528
529
inline BOOL gdi_SetRectRgn(GDI_RGN* hRgn, INT32 nLeftRect, INT32 nTopRect, INT32 nRightRect,
530
                           INT32 nBottomRect)
531
0
{
532
0
  if (!gdi_CRectToRgn(nLeftRect, nTopRect, nRightRect, nBottomRect, hRgn))
533
0
    return FALSE;
534
0
  hRgn->null = FALSE;
535
0
  return TRUE;
536
0
}
537
538
/**
539
 * @brief Compare two regions for equality.
540
 * msdn{dd162700}
541
 *
542
 * @param hSrcRgn1 first region
543
 * @param hSrcRgn2 second region
544
 * @return nonzero if both regions are equal, 0 otherwise
545
 */
546
547
inline BOOL gdi_EqualRgn(const GDI_RGN* hSrcRgn1, const GDI_RGN* hSrcRgn2)
548
0
{
549
0
  WINPR_ASSERT(hSrcRgn1);
550
0
  WINPR_ASSERT(hSrcRgn2);
551
0
  if ((hSrcRgn1->x == hSrcRgn2->x) && (hSrcRgn1->y == hSrcRgn2->y) &&
552
0
      (hSrcRgn1->w == hSrcRgn2->w) && (hSrcRgn1->h == hSrcRgn2->h))
553
0
  {
554
0
    return TRUE;
555
0
  }
556
557
0
  return FALSE;
558
0
}
559
560
/**
561
 * @brief Copy coordinates from a rectangle to another rectangle
562
 * msdn{dd183481}
563
 *
564
 * @param dst destination rectangle
565
 * @param src source rectangle
566
 * @return nonzero if successful, 0 otherwise
567
 */
568
569
inline BOOL gdi_CopyRect(GDI_RECT* dst, const GDI_RECT* src)
570
0
{
571
0
  if (!dst || !src)
572
0
    return FALSE;
573
574
0
  dst->left = src->left;
575
0
  dst->top = src->top;
576
0
  dst->right = src->right;
577
0
  dst->bottom = src->bottom;
578
0
  return TRUE;
579
0
}
580
581
/**
582
 * Check if a point is inside a rectangle.
583
 * msdn{dd162882}
584
 * @param rc rectangle
585
 * @param x point x position
586
 * @param y point y position
587
 * @return nonzero if the point is inside, 0 otherwise
588
 */
589
590
inline BOOL gdi_PtInRect(const GDI_RECT* rc, INT32 x, INT32 y)
591
0
{
592
  /*
593
   * points on the left and top sides are considered in,
594
   * while points on the right and bottom sides are considered out
595
   */
596
0
  if ((x >= rc->left) && (x <= rc->right))
597
0
  {
598
0
    if ((y >= rc->top) && (y <= rc->bottom))
599
0
    {
600
0
      return TRUE;
601
0
    }
602
0
  }
603
604
0
  return FALSE;
605
0
}
606
607
/**
608
 * Invalidate a given region, such that it is redrawn on the next region update.
609
 * msdn{dd145003}
610
 * @param hdc device context
611
 * @param x x1
612
 * @param y y1
613
 * @param w width
614
 * @param h height
615
 * @return nonzero on success, 0 otherwise
616
 */
617
618
inline BOOL gdi_InvalidateRegion(HGDI_DC hdc, INT32 x, INT32 y, INT32 w, INT32 h)
619
0
{
620
0
  GDI_RECT inv;
621
0
  GDI_RECT rgn;
622
0
  GDI_RGN* invalid = NULL;
623
0
  GDI_RGN* cinvalid = NULL;
624
625
0
  if (!hdc->hwnd)
626
0
    return TRUE;
627
628
0
  if (!hdc->hwnd->invalid)
629
0
    return TRUE;
630
631
0
  if (w == 0 || h == 0)
632
0
    return TRUE;
633
634
0
  cinvalid = hdc->hwnd->cinvalid;
635
636
0
  if ((hdc->hwnd->ninvalid + 1) > (INT64)hdc->hwnd->count)
637
0
  {
638
0
    GDI_RGN* new_rgn = NULL;
639
0
    size_t new_cnt = 2ULL * hdc->hwnd->count;
640
0
    if (new_cnt > UINT32_MAX)
641
0
      return FALSE;
642
643
0
    new_rgn = (GDI_RGN*)realloc(cinvalid, sizeof(GDI_RGN) * new_cnt);
644
645
0
    if (!new_rgn)
646
0
      return FALSE;
647
648
0
    hdc->hwnd->count = (UINT32)new_cnt;
649
0
    cinvalid = new_rgn;
650
0
  }
651
652
0
  hdc->hwnd->cinvalid = cinvalid;
653
0
  invalid = hdc->hwnd->invalid;
654
655
0
  if (!gdi_SetRgn(&cinvalid[hdc->hwnd->ninvalid++], x, y, w, h))
656
0
    return FALSE;
657
658
0
  if (!gdi_CRgnToRect(x, y, w, h, &rgn))
659
0
  {
660
0
    invalid->x = 0;
661
0
    invalid->y = 0;
662
0
    invalid->w = 0;
663
0
    invalid->h = 0;
664
0
    invalid->null = TRUE;
665
0
    return TRUE;
666
0
  }
667
668
0
  if (invalid->null)
669
0
  {
670
0
    invalid->x = x;
671
0
    invalid->y = y;
672
0
    invalid->w = w;
673
0
    invalid->h = h;
674
0
    invalid->null = FALSE;
675
0
    return TRUE;
676
0
  }
677
678
0
  if (!gdi_RgnToRect(invalid, &inv))
679
0
    return FALSE;
680
681
0
  if (rgn.left < inv.left)
682
0
    inv.left = rgn.left;
683
684
0
  if (rgn.top < inv.top)
685
0
    inv.top = rgn.top;
686
687
0
  if (rgn.right > inv.right)
688
0
    inv.right = rgn.right;
689
690
0
  if (rgn.bottom > inv.bottom)
691
0
    inv.bottom = rgn.bottom;
692
693
0
  return gdi_RectToRgn(&inv, invalid);
694
0
}