xref: /reactos/win32ss/gdi/ntgdi/xformobj.c (revision 845faec4)
1 /*
2  * PROJECT:         ReactOS win32 kernel mode subsystem
3  * LICENSE:         GPL - See COPYING in the top level directory
4  * FILE:            win32ss/gdi/ntgdi/xformobj.c
5  * PURPOSE:         XFORMOBJ API
6  * PROGRAMMER:      Timo Kreuzer
7  */
8 
9 /** Includes ******************************************************************/
10 
11 #include <win32k.h>
12 #define NDEBUG
13 #include <debug.h>
14 
15 #define DOES_VALUE_OVERFLOW_LONG(x) \
16     (((__int64)((long)(x))) != (x))
17 
18 /** Inline helper functions ***************************************************/
19 
20 /*
21  * Inline helper to calculate pfo1 * pfo2 + pfo3 * pfo4
22  */
23 FORCEINLINE
24 VOID
25 MulAdd(
26     PFLOATOBJ pfoDest,
27     PFLOATOBJ pfo1,
28     PFLOATOBJ pfo2,
29     PFLOATOBJ pfo3,
30     PFLOATOBJ pfo4)
31 {
32     FLOATOBJ foTmp;
33 
34     *pfoDest = *pfo1;
35     FLOATOBJ_Mul(pfoDest, pfo2);
36     foTmp = *pfo3;
37     FLOATOBJ_Mul(&foTmp, pfo4);
38     FLOATOBJ_Add(pfoDest, &foTmp);
39 }
40 
41 /*
42  * Inline helper to calculate pfo1 * l2 + pfo3 * l4
43  */
44 FORCEINLINE
45 VOID
46 MulAddLong(
47     PFLOATOBJ pfoDest,
48     PFLOATOBJ pfo1,
49     LONG l2,
50     PFLOATOBJ pfo3,
51     LONG l4)
52 {
53     FLOATOBJ foTmp;
54 
55     *pfoDest = *pfo1;
56     FLOATOBJ_MulLong(pfoDest, l2);
57     foTmp = *pfo3;
58     FLOATOBJ_MulLong(&foTmp, l4);
59     FLOATOBJ_Add(pfoDest, &foTmp);
60 }
61 
62 /*
63  * Inline helper to calculate pfo1 * pfo2 - pfo3 * pfo4
64  */
65 FORCEINLINE
66 VOID
67 MulSub(
68     PFLOATOBJ pfoDest,
69     PFLOATOBJ pfo1,
70     PFLOATOBJ pfo2,
71     PFLOATOBJ pfo3,
72     PFLOATOBJ pfo4)
73 {
74     FLOATOBJ foTmp;
75 
76     *pfoDest = *pfo1;
77     FLOATOBJ_Mul(pfoDest, pfo2);
78     foTmp = *pfo3;
79     FLOATOBJ_Mul(&foTmp, pfo4);
80     FLOATOBJ_Sub(pfoDest, &foTmp);
81 }
82 
83 /*
84  * Inline helper to get the complexity hint from flAccel
85  */
86 FORCEINLINE
87 ULONG
88 HintFromAccel(ULONG flAccel)
89 {
90     switch (flAccel & (XFORM_SCALE|XFORM_UNITY|XFORM_NO_TRANSLATION))
91     {
92         case (XFORM_SCALE|XFORM_UNITY|XFORM_NO_TRANSLATION):
93             return GX_IDENTITY;
94         case (XFORM_SCALE|XFORM_UNITY):
95             return GX_OFFSET;
96         case XFORM_SCALE:
97             return GX_SCALE;
98         default:
99             return GX_GENERAL;
100     }
101 }
102 
103 /** Internal functions ********************************************************/
104 
105 ULONG
106 NTAPI
107 XFORMOBJ_UpdateAccel(
108     IN XFORMOBJ *pxo)
109 {
110     PMATRIX pmx = XFORMOBJ_pmx(pxo);
111 
112     /* Copy Dx and Dy to FIX format */
113     pmx->fxDx = FLOATOBJ_GetFix(&pmx->efDx);
114     pmx->fxDy = FLOATOBJ_GetFix(&pmx->efDy);
115 
116     pmx->flAccel = 0;
117 
118     if (FLOATOBJ_Equal0(&pmx->efDx) &&
119         FLOATOBJ_Equal0(&pmx->efDy))
120     {
121         pmx->flAccel |= XFORM_NO_TRANSLATION;
122     }
123 
124     if (FLOATOBJ_Equal0(&pmx->efM12) &&
125         FLOATOBJ_Equal0(&pmx->efM21))
126     {
127         pmx->flAccel |= XFORM_SCALE;
128     }
129 
130     if (FLOATOBJ_Equal1(&pmx->efM11) &&
131         FLOATOBJ_Equal1(&pmx->efM22))
132     {
133         pmx->flAccel |= XFORM_UNITY;
134     }
135 
136     if (FLOATOBJ_IsLong(&pmx->efM11) && FLOATOBJ_IsLong(&pmx->efM12) &&
137         FLOATOBJ_IsLong(&pmx->efM21) && FLOATOBJ_IsLong(&pmx->efM22))
138     {
139         pmx->flAccel |= XFORM_INTEGER;
140     }
141 
142     return HintFromAccel(pmx->flAccel);
143 }
144 
145 
146 ULONG
147 NTAPI
148 XFORMOBJ_iSetXform(
149     OUT XFORMOBJ *pxo,
150     IN const XFORML *pxform)
151 {
152     PMATRIX pmx = XFORMOBJ_pmx(pxo);
153 
154     /* Check parameters */
155     if (!pxo || !pxform) return DDI_ERROR;
156 
157     /* Check if the xform is valid */
158     if ((pxform->eM11 == 0) || (pxform->eM22 == 0)) return DDI_ERROR;
159 
160     /* Copy members */
161     FLOATOBJ_SetFloat(&pmx->efM11, pxform->eM11);
162     FLOATOBJ_SetFloat(&pmx->efM12, pxform->eM12);
163     FLOATOBJ_SetFloat(&pmx->efM21, pxform->eM21);
164     FLOATOBJ_SetFloat(&pmx->efM22, pxform->eM22);
165     FLOATOBJ_SetFloat(&pmx->efDx, pxform->eDx);
166     FLOATOBJ_SetFloat(&pmx->efDy, pxform->eDy);
167 
168     /* Update accelerators and return complexity */
169     return XFORMOBJ_UpdateAccel(pxo);
170 }
171 
172 
173 /*
174  * Multiplies pxo1 with pxo2 and stores the result in pxo.
175  * returns complexity hint
176  * | efM11 efM12 0 |
177  * | efM21 efM22 0 |
178  * | efDx  efDy  1 |
179  */
180 ULONG
181 NTAPI
182 XFORMOBJ_iCombine(
183     IN XFORMOBJ *pxo,
184     IN XFORMOBJ *pxo1,
185     IN XFORMOBJ *pxo2)
186 {
187     MATRIX mx;
188     PMATRIX pmx, pmx1, pmx2;
189 
190     /* Get the source matrices */
191     pmx1 = XFORMOBJ_pmx(pxo1);
192     pmx2 = XFORMOBJ_pmx(pxo2);
193 
194     /* Do a 3 x 3 matrix multiplication with mx as destinantion */
195     MulAdd(&mx.efM11, &pmx1->efM11, &pmx2->efM11, &pmx1->efM12, &pmx2->efM21);
196     MulAdd(&mx.efM12, &pmx1->efM11, &pmx2->efM12, &pmx1->efM12, &pmx2->efM22);
197     MulAdd(&mx.efM21, &pmx1->efM21, &pmx2->efM11, &pmx1->efM22, &pmx2->efM21);
198     MulAdd(&mx.efM22, &pmx1->efM21, &pmx2->efM12, &pmx1->efM22, &pmx2->efM22);
199     MulAdd(&mx.efDx, &pmx1->efDx, &pmx2->efM11, &pmx1->efDy, &pmx2->efM21);
200     FLOATOBJ_Add(&mx.efDx, &pmx2->efDx);
201     MulAdd(&mx.efDy, &pmx1->efDx, &pmx2->efM12, &pmx1->efDy, &pmx2->efM22);
202     FLOATOBJ_Add(&mx.efDy, &pmx2->efDy);
203 
204     /* Copy back */
205     pmx = XFORMOBJ_pmx(pxo);
206     *pmx = mx;
207 
208     /* Update accelerators and return complexity */
209     return XFORMOBJ_UpdateAccel(pxo);
210 }
211 
212 
213 ULONG
214 NTAPI
215 XFORMOBJ_iCombineXform(
216     IN XFORMOBJ *pxo,
217     IN XFORMOBJ *pxo1,
218     IN XFORML *pxform,
219     IN BOOL bLeftMultiply)
220 {
221     MATRIX mx;
222     XFORMOBJ xo2;
223 
224     XFORMOBJ_vInit(&xo2, &mx);
225     XFORMOBJ_iSetXform(&xo2, pxform);
226 
227     if (bLeftMultiply)
228     {
229         return XFORMOBJ_iCombine(pxo, &xo2, pxo1);
230     }
231     else
232     {
233         return XFORMOBJ_iCombine(pxo, pxo1, &xo2);
234     }
235 }
236 
237 /*
238  * A^-1 = adj(A) / det(AT)
239  * A^-1 = 1/(a*d - b*c) * (a22,-a12,a21,-a11)
240  */
241 ULONG
242 NTAPI
243 XFORMOBJ_iInverse(
244     OUT XFORMOBJ *pxoDst,
245     IN XFORMOBJ *pxoSrc)
246 {
247     PMATRIX pmxDst, pmxSrc;
248     FLOATOBJ foDet;
249     XFORM xformSrc;
250 
251     pmxDst = XFORMOBJ_pmx(pxoDst);
252     pmxSrc = XFORMOBJ_pmx(pxoSrc);
253 
254     XFORMOBJ_iGetXform(pxoSrc, (XFORML*)&xformSrc);
255 
256     /* det = M11 * M22 - M12 * M21 */
257     MulSub(&foDet, &pmxSrc->efM11, &pmxSrc->efM22, &pmxSrc->efM12, &pmxSrc->efM21);
258 
259     if (FLOATOBJ_Equal0(&foDet))
260     {
261         /* Determinant is 0! */
262         return DDI_ERROR;
263     }
264 
265     /* Calculate adj(A) / det(A) */
266     pmxDst->efM11 = pmxSrc->efM22;
267     FLOATOBJ_Div(&pmxDst->efM11, &foDet);
268     pmxDst->efM22 = pmxSrc->efM11;
269     FLOATOBJ_Div(&pmxDst->efM22, &foDet);
270 
271     /* The other 2 are negative, negate foDet for that */
272     FLOATOBJ_Neg(&foDet);
273     pmxDst->efM12 = pmxSrc->efM12;
274     FLOATOBJ_Div(&pmxDst->efM12, &foDet);
275     pmxDst->efM21 = pmxSrc->efM21;
276     FLOATOBJ_Div(&pmxDst->efM21, &foDet);
277 
278     /* Calculate the inverted x shift: Dx' = -Dx * M11' - Dy * M21' */
279     pmxDst->efDx = pmxSrc->efDx;
280     FLOATOBJ_Neg(&pmxDst->efDx);
281     MulSub(&pmxDst->efDx, &pmxDst->efDx, &pmxDst->efM11, &pmxSrc->efDy, &pmxDst->efM21);
282 
283     /* Calculate the inverted y shift: Dy' = -Dy * M22' - Dx * M12' */
284     pmxDst->efDy = pmxSrc->efDy;
285     FLOATOBJ_Neg(&pmxDst->efDy);
286     MulSub(&pmxDst->efDy, &pmxDst->efDy, &pmxDst->efM22, &pmxSrc->efDx, &pmxDst->efM12);
287 
288     /* Update accelerators and return complexity */
289     return XFORMOBJ_UpdateAccel(pxoDst);
290 }
291 
292 
293 /*!
294  * \brief Transforms fix-point coordinates in an array of POINTL structures using
295  *        the transformation matrix from the XFORMOBJ.
296  *
297  * \param pxo - Pointer to the XFORMOBJ
298  *
299  * \param cPoints - Number of coordinates to transform
300  *
301  * \param pptIn - Pointer to an array of POINTL structures containing the
302  *        source coordinates.
303  *
304  * \param pptOut - Pointer to an array of POINTL structures, receiving the
305  *        transformed coordinates. Can be the same as pptIn.
306  *
307  * \return TRUE if the operation was successful, FALSE if any of the calculations
308  *         caused an integer overflow.
309  *
310  * \note If the function returns FALSE, it might still have written to the
311  *       output buffer. If pptIn and pptOut are equal, the source coordinates
312  *       might have been partly overwritten!
313  */
314 static
315 BOOL
316 NTAPI
317 XFORMOBJ_bXformFixPoints(
318     _In_ XFORMOBJ *pxo,
319     _In_ ULONG cPoints,
320     _In_reads_(cPoints) PPOINTL pptIn,
321     _Out_writes_(cPoints) PPOINTL pptOut)
322 {
323     PMATRIX pmx;
324     INT i;
325     FLOATOBJ fo1, fo2;
326     FLONG flAccel;
327     LONG lM11, lM12, lM21, lM22, lTemp;
328     register LONGLONG llx, lly;
329 
330     pmx = XFORMOBJ_pmx(pxo);
331     flAccel = pmx->flAccel;
332 
333     if ((flAccel & (XFORM_SCALE|XFORM_UNITY)) == (XFORM_SCALE|XFORM_UNITY))
334     {
335         /* Identity transformation */
336         RtlCopyMemory(pptOut, pptIn, cPoints * sizeof(POINTL));
337     }
338     else if (flAccel & XFORM_INTEGER)
339     {
340         if (flAccel & XFORM_UNITY)
341         {
342             /* 1-scale integer transform, get the off-diagonal elements */
343             if (!FLOATOBJ_bConvertToLong(&pmx->efM12, &lM12) ||
344                 !FLOATOBJ_bConvertToLong(&pmx->efM21, &lM21))
345             {
346                 NT_ASSERT(FALSE);
347                 return FALSE;
348             }
349 
350             i = cPoints - 1;
351             do
352             {
353                 /* Calculate x in 64 bit and check for overflow */
354                 llx = Int32x32To64(pptIn[i].y, lM21) + pptIn[i].x;
355                 if (DOES_VALUE_OVERFLOW_LONG(llx))
356                 {
357                     return FALSE;
358                 }
359 
360                 /* Calculate y in 64 bit and check for overflow */
361                 lly = Int32x32To64(pptIn[i].x, lM12) + pptIn[i].y;
362                 if (DOES_VALUE_OVERFLOW_LONG(lly))
363                 {
364                     return FALSE;
365                 }
366 
367                 /* Write back the results */
368                 pptOut[i].x = (LONG)llx;
369                 pptOut[i].y = (LONG)lly;
370             }
371             while (--i >= 0);
372         }
373         else if (flAccel & XFORM_SCALE)
374         {
375             /* Diagonal integer transform, get the diagonal elements */
376             if (!FLOATOBJ_bConvertToLong(&pmx->efM11, &lM11) ||
377                 !FLOATOBJ_bConvertToLong(&pmx->efM22, &lM22))
378             {
379                 NT_ASSERT(FALSE);
380                 return FALSE;
381             }
382 
383             i = cPoints - 1;
384             do
385             {
386                 /* Calculate x in 64 bit and check for overflow */
387                 llx = Int32x32To64(pptIn[i].x, lM11);
388                 if (DOES_VALUE_OVERFLOW_LONG(llx))
389                 {
390                     return FALSE;
391                 }
392 
393                 /* Calculate y in 64 bit and check for overflow */
394                 lly = Int32x32To64(pptIn[i].y, lM22);
395                 if (DOES_VALUE_OVERFLOW_LONG(lly))
396                 {
397                     return FALSE;
398                 }
399 
400                 /* Write back the results */
401                 pptOut[i].x = (LONG)llx;
402                 pptOut[i].y = (LONG)lly;
403             }
404             while (--i >= 0);
405         }
406         else
407         {
408             /* Full integer transform */
409             if (!FLOATOBJ_bConvertToLong(&pmx->efM11, &lM11) ||
410                 !FLOATOBJ_bConvertToLong(&pmx->efM12, &lM12) ||
411                 !FLOATOBJ_bConvertToLong(&pmx->efM21, &lM21) ||
412                 !FLOATOBJ_bConvertToLong(&pmx->efM22, &lM22))
413             {
414                 NT_ASSERT(FALSE);
415                 return FALSE;
416             }
417 
418             i = cPoints - 1;
419             do
420             {
421                 /* Calculate x in 64 bit and check for overflow */
422                 llx  = Int32x32To64(pptIn[i].x, lM11);
423                 llx += Int32x32To64(pptIn[i].y, lM21);
424                 if (DOES_VALUE_OVERFLOW_LONG(llx))
425                 {
426                     return FALSE;
427                 }
428 
429                 /* Calculate y in 64 bit and check for overflow */
430                 lly  = Int32x32To64(pptIn[i].y, lM22);
431                 lly += Int32x32To64(pptIn[i].x, lM12);
432                 if (DOES_VALUE_OVERFLOW_LONG(lly))
433                 {
434                     return FALSE;
435                 }
436 
437                 /* Write back the results */
438                 pptOut[i].x = (LONG)llx;
439                 pptOut[i].y = (LONG)lly;
440             }
441             while (--i >= 0);
442         }
443     }
444     else if (flAccel & XFORM_UNITY)
445     {
446         /* 1-scale transform */
447         i = cPoints - 1;
448         do
449         {
450             /* Calculate x in 64 bit and check for overflow */
451             fo1 = pmx->efM21;
452             FLOATOBJ_MulLong(&fo1, pptIn[i].y);
453             if (!FLOATOBJ_bConvertToLong(&fo1, &lTemp))
454             {
455                 return FALSE;
456             }
457             llx = (LONGLONG)pptIn[i].x + lTemp;
458             if (DOES_VALUE_OVERFLOW_LONG(llx))
459             {
460                 return FALSE;
461             }
462 
463             /* Calculate y in 64 bit and check for overflow */
464             fo2 = pmx->efM12;
465             FLOATOBJ_MulLong(&fo2, pptIn[i].x);
466             if (!FLOATOBJ_bConvertToLong(&fo2, &lTemp))
467             {
468                 return FALSE;
469             }
470             lly = (LONGLONG)pptIn[i].y + lTemp;
471             if (DOES_VALUE_OVERFLOW_LONG(lly))
472             {
473                 return FALSE;
474             }
475 
476             /* Write back the results */
477             pptOut[i].x = (LONG)llx;
478             pptOut[i].y = (LONG)lly;
479         }
480         while (--i >= 0);
481     }
482     else if (flAccel & XFORM_SCALE)
483     {
484         /* Diagonal float transform */
485         i = cPoints - 1;
486         do
487         {
488             fo1 = pmx->efM11;
489             FLOATOBJ_MulLong(&fo1, pptIn[i].x);
490             if (!FLOATOBJ_bConvertToLong(&fo1, &pptOut[i].x))
491             {
492                 return FALSE;
493             }
494 
495             fo2 = pmx->efM22;
496             FLOATOBJ_MulLong(&fo2, pptIn[i].y);
497             if (!FLOATOBJ_bConvertToLong(&fo2, &pptOut[i].y))
498             {
499                 return FALSE;
500             }
501         }
502         while (--i >= 0);
503     }
504     else
505     {
506         /* Full float transform */
507         i = cPoints - 1;
508         do
509         {
510             /* Calculate x as FLOATOBJ */
511             MulAddLong(&fo1, &pmx->efM11, pptIn[i].x, &pmx->efM21, pptIn[i].y);
512 
513             /* Calculate y as FLOATOBJ */
514             MulAddLong(&fo2, &pmx->efM12, pptIn[i].x, &pmx->efM22, pptIn[i].y);
515 
516             if (!FLOATOBJ_bConvertToLong(&fo1, &pptOut[i].x))
517             {
518                 return FALSE;
519             }
520 
521             if (!FLOATOBJ_bConvertToLong(&fo2, &pptOut[i].y))
522             {
523                 return FALSE;
524             }
525         }
526         while (--i >= 0);
527     }
528 
529     if (!(pmx->flAccel & XFORM_NO_TRANSLATION))
530     {
531         /* Translate points */
532         i = cPoints - 1;
533         do
534         {
535             llx = (LONGLONG)pptOut[i].x + pmx->fxDx;
536             if (DOES_VALUE_OVERFLOW_LONG(llx))
537             {
538                 return FALSE;
539             }
540             pptOut[i].x = (LONG)llx;
541 
542             lly = (LONGLONG)pptOut[i].y + pmx->fxDy;
543             if (DOES_VALUE_OVERFLOW_LONG(lly))
544             {
545                 return FALSE;
546             }
547             pptOut[i].y = (LONG)lly;
548         }
549         while (--i >= 0);
550     }
551 
552     return TRUE;
553 }
554 
555 /** Public functions **********************************************************/
556 
557 // www.osr.com/ddk/graphics/gdifncs_0s2v.htm
558 ULONG
559 APIENTRY
560 XFORMOBJ_iGetXform(
561     IN XFORMOBJ *pxo,
562     OUT XFORML *pxform)
563 {
564     PMATRIX pmx = XFORMOBJ_pmx(pxo);
565 
566     /* Check parameters */
567     if (!pxo || !pxform)
568     {
569         return DDI_ERROR;
570     }
571 
572     /* Copy members */
573     pxform->eM11 = FLOATOBJ_GetFloat(&pmx->efM11);
574     pxform->eM12 = FLOATOBJ_GetFloat(&pmx->efM12);
575     pxform->eM21 = FLOATOBJ_GetFloat(&pmx->efM21);
576     pxform->eM22 = FLOATOBJ_GetFloat(&pmx->efM22);
577     pxform->eDx = FLOATOBJ_GetFloat(&pmx->efDx);
578     pxform->eDy = FLOATOBJ_GetFloat(&pmx->efDy);
579 
580     /* Return complexity hint */
581     return HintFromAccel(pmx->flAccel);
582 }
583 
584 
585 // www.osr.com/ddk/graphics/gdifncs_5ig7.htm
586 ULONG
587 APIENTRY
588 XFORMOBJ_iGetFloatObjXform(
589     IN XFORMOBJ *pxo,
590     OUT FLOATOBJ_XFORM *pxfo)
591 {
592     PMATRIX pmx = XFORMOBJ_pmx(pxo);
593 
594     /* Check parameters */
595     if (!pxo || !pxfo)
596     {
597         return DDI_ERROR;
598     }
599 
600     /* Copy members */
601     pxfo->eM11 = pmx->efM11;
602     pxfo->eM12 = pmx->efM12;
603     pxfo->eM21 = pmx->efM21;
604     pxfo->eM22 = pmx->efM22;
605     pxfo->eDx = pmx->efDx;
606     pxfo->eDy = pmx->efDy;
607 
608     /* Return complexity hint */
609     return HintFromAccel(pmx->flAccel);
610 }
611 
612 
613 // www.osr.com/ddk/graphics/gdifncs_027b.htm
614 BOOL
615 APIENTRY
616 XFORMOBJ_bApplyXform(
617     IN XFORMOBJ *pxo,
618     IN ULONG iMode,
619     IN ULONG cPoints,
620     IN PVOID pvIn,
621     OUT PVOID pvOut)
622 {
623     MATRIX mx;
624     XFORMOBJ xoInv;
625     PPOINTL pptlIn, pptlOut;
626     INT i;
627 
628     /* Check parameters */
629     if (!pxo || !pvIn || !pvOut || cPoints < 1)
630     {
631         return FALSE;
632     }
633 
634     /* Use inverse xform? */
635     if (iMode == XF_INV_FXTOL || iMode == XF_INV_LTOL)
636     {
637         XFORMOBJ_vInit(&xoInv, &mx);
638         if (XFORMOBJ_iInverse(&xoInv, pxo) == DDI_ERROR)
639         {
640             return FALSE;
641         }
642         pxo = &xoInv;
643     }
644 
645     /* Convert POINTL to POINTFIX? */
646     if (iMode == XF_LTOFX || iMode == XF_LTOL || iMode == XF_INV_LTOL)
647     {
648         pptlIn = pvIn;
649         pptlOut = pvOut;
650         for (i = cPoints - 1; i >= 0; i--)
651         {
652             pptlOut[i].x = LONG2FIX(pptlIn[i].x);
653             pptlOut[i].y = LONG2FIX(pptlIn[i].y);
654         }
655 
656         /* The input is in the out buffer now! */
657         pvIn = pvOut;
658     }
659 
660     /* Do the actual fixpoint transformation */
661     if (!XFORMOBJ_bXformFixPoints(pxo, cPoints, pvIn, pvOut))
662     {
663         return FALSE;
664     }
665 
666     /* Convert POINTFIX to POINTL? */
667     if (iMode == XF_INV_FXTOL || iMode == XF_INV_LTOL || iMode == XF_LTOL)
668     {
669         pptlOut = pvOut;
670         for (i = cPoints - 1; i >= 0; i--)
671         {
672             pptlOut[i].x = FIX2LONG(pptlOut[i].x);
673             pptlOut[i].y = FIX2LONG(pptlOut[i].y);
674         }
675     }
676 
677     return TRUE;
678 }
679 
680 /* EOF */
681