Sweden-Number/dlls/win32u/mapping.c

471 lines
15 KiB
C
Raw Normal View History

/*
* GDI mapping mode functions
*
* Copyright 1993 Alexandre Julliard
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
*/
#if 0
#pragma makedep unix
#endif
2006-11-17 14:52:07 +01:00
#include <stdarg.h>
#include "windef.h"
#include "winbase.h"
#include "wingdi.h"
#include "ntgdi_private.h"
#include "wine/debug.h"
WINE_DEFAULT_DEBUG_CHANNEL(dc);
/* copied from kernelbase */
int muldiv( int a, int b, int c )
{
LONGLONG ret;
if (!c) return -1;
/* We want to deal with a positive divisor to simplify the logic. */
if (c < 0)
{
a = -a;
c = -c;
}
/* If the result is positive, we "add" to round. else, we subtract to round. */
if ((a < 0 && b < 0) || (a >= 0 && b >= 0))
ret = (((LONGLONG)a * b) + (c / 2)) / c;
else
ret = (((LONGLONG)a * b) - (c / 2)) / c;
if (ret > 2147483647 || ret < -2147483647) return -1;
return ret;
}
static SIZE get_dc_virtual_size( DC *dc )
{
SIZE ret = dc->attr->virtual_size;
if (!ret.cx)
{
ret.cx = NtGdiGetDeviceCaps( dc->hSelf, HORZSIZE );
ret.cy = NtGdiGetDeviceCaps( dc->hSelf, VERTSIZE );
}
return ret;
}
static SIZE get_dc_virtual_res( DC *dc )
{
SIZE ret = dc->attr->virtual_res;
if (!ret.cx)
{
ret.cx = NtGdiGetDeviceCaps( dc->hSelf, HORZRES );
ret.cy = NtGdiGetDeviceCaps( dc->hSelf, VERTRES );
}
return ret;
}
/***********************************************************************
* MAPPING_FixIsotropic
*
* Fix viewport extensions for isotropic mode.
*/
2007-01-08 21:19:46 +01:00
static void MAPPING_FixIsotropic( DC * dc )
{
SIZE virtual_size = get_dc_virtual_size( dc );
SIZE virtual_res = get_dc_virtual_res( dc );
double xdim = fabs((double)dc->attr->vport_ext.cx * virtual_size.cx /
(virtual_res.cx * dc->attr->wnd_ext.cx));
double ydim = fabs((double)dc->attr->vport_ext.cy * virtual_size.cy /
(virtual_res.cy * dc->attr->wnd_ext.cy));
if (xdim > ydim)
{
INT mincx = (dc->attr->vport_ext.cx >= 0) ? 1 : -1;
dc->attr->vport_ext.cx = GDI_ROUND( dc->attr->vport_ext.cx * ydim / xdim );
if (!dc->attr->vport_ext.cx) dc->attr->vport_ext.cx = mincx;
}
else
{
INT mincy = (dc->attr->vport_ext.cy >= 0) ? 1 : -1;
dc->attr->vport_ext.cy = GDI_ROUND( dc->attr->vport_ext.cy * xdim / ydim );
if (!dc->attr->vport_ext.cy) dc->attr->vport_ext.cy = mincy;
}
}
BOOL set_map_mode( DC *dc, int mode )
{
SIZE virtual_size, virtual_res;
if (mode == dc->attr->map_mode && (mode == MM_ISOTROPIC || mode == MM_ANISOTROPIC))
return TRUE;
switch (mode)
{
case MM_TEXT:
dc->attr->wnd_ext.cx = 1;
dc->attr->wnd_ext.cy = 1;
dc->attr->vport_ext.cx = 1;
dc->attr->vport_ext.cy = 1;
break;
case MM_LOMETRIC:
case MM_ISOTROPIC:
virtual_size = get_dc_virtual_size( dc );
virtual_res = get_dc_virtual_res( dc );
dc->attr->wnd_ext.cx = virtual_size.cx * 10;
dc->attr->wnd_ext.cy = virtual_size.cy * 10;
dc->attr->vport_ext.cx = virtual_res.cx;
dc->attr->vport_ext.cy = -virtual_res.cy;
break;
case MM_HIMETRIC:
virtual_size = get_dc_virtual_size( dc );
virtual_res = get_dc_virtual_res( dc );
dc->attr->wnd_ext.cx = virtual_size.cx * 100;
dc->attr->wnd_ext.cy = virtual_size.cy * 100;
dc->attr->vport_ext.cx = virtual_res.cx;
dc->attr->vport_ext.cy = -virtual_res.cy;
break;
case MM_LOENGLISH:
virtual_size = get_dc_virtual_size( dc );
virtual_res = get_dc_virtual_res( dc );
dc->attr->wnd_ext.cx = muldiv(1000, virtual_size.cx, 254);
dc->attr->wnd_ext.cy = muldiv(1000, virtual_size.cy, 254);
dc->attr->vport_ext.cx = virtual_res.cx;
dc->attr->vport_ext.cy = -virtual_res.cy;
break;
case MM_HIENGLISH:
virtual_size = get_dc_virtual_size( dc );
virtual_res = get_dc_virtual_res( dc );
dc->attr->wnd_ext.cx = muldiv(10000, virtual_size.cx, 254);
dc->attr->wnd_ext.cy = muldiv(10000, virtual_size.cy, 254);
dc->attr->vport_ext.cx = virtual_res.cx;
dc->attr->vport_ext.cy = -virtual_res.cy;
break;
case MM_TWIPS:
virtual_size = get_dc_virtual_size( dc );
virtual_res = get_dc_virtual_res( dc );
dc->attr->wnd_ext.cx = muldiv(14400, virtual_size.cx, 254);
dc->attr->wnd_ext.cy = muldiv(14400, virtual_size.cy, 254);
dc->attr->vport_ext.cx = virtual_res.cx;
dc->attr->vport_ext.cy = -virtual_res.cy;
break;
case MM_ANISOTROPIC:
break;
default:
return FALSE;
}
/* RTL layout is always MM_ANISOTROPIC */
if (!(dc->attr->layout & LAYOUT_RTL)) dc->attr->map_mode = mode;
Release 980517 Sun May 17 16:23:56 1998 Uwe Bonnes <bon@elektron.ikp.physik.tu-darmstadt.de> * [file/profile.c] Fix the return value of PROFILE_GetSection * [misc/crtdll.c] Do _getdrive, fix _chdrive. * [misc/commdlg.c] First cut at ChooseColor[WA]. * [misc/network.c] Do something sensible for WNetGetDirectoryType16. Sun May 17 10:21:35 1998 Andreas Mohr <100.30936@germany.net> * [controls/menu.c] Fixed disabled sub menus with MF_BYPOSITION that were not disabled. * [misc/crtdll.c] [relay32/crtdll.spec] [include/winerror.h] Implemented fscanf, fsetpos, _access, _fpreset (thanks to Uwe Bonnes), and _ltoa. * [loader/task.c] MakeProcInstance: must use CURRENT_DS if hInst == NULL. * [misc/shell.c] SHELL_GetResourceTable, InternalExtractIcon: fixed broken .ICO handling * [windows/winpos.c] DeferWindowPos: removed "same parent" requirement. Which doc states that this is required ? Sat May 16 20:08:11 1998 Alexandre Julliard <julliard@lrc.epfl.ch> * [loader/module.c] [loader/ne/module.c] More NE module cleanups. * [loader/task.c] Fixed SwitchStackBack(). Fri May 15 10:04:27 1998 Marcus Meissner <marcus@jet.franken.de> * [configure.in][inlcude/acconfig.h] Fixed broken OSS check, added check for working sigaltstack, fixed broken statfs checks on some linux systems. * [files/directory.c][loader/pe_image.c][relay32/builtin.c] [loader/module.c] Added handling of win32 module pathnames. * [relay32/wnaspi32.spec] New file. * [misc/lzexpand.c] LZCopy auto-decompresses LZ compressed files, even if they are not specially flagged. Fixes some InstallShield problems. * [misc/registry.c] Some fixes for RegQueryInfoKey (reference program monkey.exe from Win32 SDK works now better). Probably still has faults. Fri May 15 08:58:58 1998 Martin Boehme <boehme@informatik.mu-luebeck.de> * [graphics/mapping.c] [include/dc.h] [include/gdi.h] [objects/dc.c] Reworked the way world transformations and mapping modes are handled so that both of these transformations can be computed in a single step. * [graphics/painting.c] [graphics/path.c] [include/path.h] More GDI path support. * [graphics/x11drv/graphics.c] Fixed the return value of GRAPH_DrawArc for the zero height / zero width case to reflect Windows' behaviour. * [include/windows.h] [relay32/gdi32.spec] [objects/dc.c] Implemented ModifyWorldTransform and CombineTransform. Tue May 14 18:03:46 1998 Eric Kohl <ekohl@abo.rhein-zeitung.de> * [controls/commctrl.c][relay32/comctl32.spec] [controls/*.c][include/*.h] Implemented InitCommonControlsEx (dll version 4.72 compatible). InitCommonControls calls ImageCommonControlsEx. Registering code of the common controls had to be changed (see XXXX_Register functions). * [controls/status.c][include/commctrl.h][include/status.h] Implemented most new features and fixed the look and feel. * [contols/commctrl.c][include/commctrl.h][relay32/comctl32.spec] Implemented MenuHelp (incomplete). * [controls/status.c][controls/progress.c] Changed allocation strategy for control specific memory. * [controls/header.c][include/header.h][include/commctrl.h] First implementation of header control. * [windows/defwnd.c][windows/syscolors.c] Fixed default control colors for Win95 look. * [windows/nonclient.c] Fixed off by one error for Win95 look. Top border of child windows should be visible. * [misc/imagelist.h] Improved documentation and fixed some bugs. Thu May 14 15:42:21 1998 Robert Wilhelm <robert@physiol.med.tu-muenchen.de> * [relay32/crtdll.spec] Added hypot,j0,j1,jn and ceil. Wed May 13 19:10:10 1998 Pascal Cuoq <pcuoq@ens-lyon.fr> * [controls/listbox.c] Item height is now exactly font height. Wine listboxes now behave like Windows' when they are created without WS_VSCROLL but the program subsequently calls ShowScrollBar or SetScrollInfo. Wed May 13 18:33:01 1998 Ulrich Weigand <weigand@informatik.uni-erlangen.de> * [relay32/relay386.c] Restore ES also in the non-debug case. * [windows/event.c] Bugfix: Blocking TSXNextEvent could deadlock Wine. * [win32/process.c] [windows/message.c] Silly stubs for MsgWaitForMultipleObjects / PostThreadMessage that make some programs run better. * [windows/winproc.c] WINPROC_MapMsg32Ato16/16To32A: added WM_NOTIFY. * [win32/kernel32.c] Added 16->32 thunking and improved 32->16 thunking functions. * [tools/build.c] Added new variant of CallFrom16 stub for use with Win95 thunks. * [if1632/kernel.spec] [if1632/builtin.c] [win32/kernel32.c] Added a few undocumented KERNEL functions. * [loader/ne/module.c] [loader/ne/segment.c] Call DllEntryPoint for 16-bit DLLs with subsystem >= 4.0. * [win32/kernel32.spec] [win32/wow32.spec] [win32/ordinals.c] Use names from the Oct 94 beta release for undoc. functions. Wed May 13 14:18:26 1998 Matthew Becker <mbecker@glasscity.net> * [misc/registry.c] Code cleanup. * [misc/cpu.c] Commented out the registry puts temporarily. * [programs/regtest/*] New registry testing program. Tue May 12 22:54:03 1998 Michael Mess <michael@kawo2.rwth-aachen.de> * [multimedia/audio.c] ioctl's do not commute in /dev/dsp initialization. Tue May 12 20:11:42 1998 Karl Garrison <karlos@eznet.net> * [win32/console.c] Implemented SetConsoleTextAttribute, FillConsoleOutputCharacter. Improved cursor positioning. This allows for text colors in an xterm, rxvt, or console. Tue May 12 17:57:52 1998 Petter Reinholdtsen <pere@td.org.uit.no> * [Makefile.in] Create prefix/{bin|lib} directories if missing during install. Sun May 10 19:37:51 1998 Jan Willamowius <jan@janhh.shnet.org> * [multimedia/mmio.c] Have mmioSetBuffer return success (0), so Corel Draw 4 keeps working. (IO is still unbuffered) Wed May 6 16:57:55 1998 James Juran <jrj120@psu.edu> * [Makefile.in] [Make.rules.in] Changed "make clean" to remove `textedit` backup files (*%) * [controls/menu.c][graphics/x11drv/xfont.c][include/libres.h] [loader/main.c][loader/ne/module.c][scheduler/synchro.c] [win32/time.c][windows/winpos.c][include/windows.h] Fixed miscellaneous compilation warnings. * [misc/main.c][miscemu/main.c][include/main.h] Moved prototypes to new include file main.h, various cleanups. Tue May 5 21:05:06 1998 Morten Welinder <terra@diku.dk> * [misc/winsock.c] Don't refer to __FreeBSD__ when HAVE_STRERROR is meant. * [misc/debugstr.c] For debug_dumpstrSend, send strings to stderr. Tue May 5 21:47:40 1998 Huw D M Davies <h.davies1@physics.oxford.ac.uk> * [objects/region.c] Fix for REGION_RegionOp() if newReg is one of the source regions. Tue May 5 18:27:32 1998 Jim Peterson <jspeter@roanoke.infi.net> * [misc/main.c] Add '-h/-help' option and print WINE_RELEASE_INFO with usage message. * [misc/spy.c] Realign trace messages. Tue May 5 15:46:47 1998 Donnie V. Savage <dsavage@cisco.com> * [graphics/ddraw.c] Fixed compile warnings * [misc/winsock.c] Warnings should not be errors. Tue May 5 13:40:42 1998 Jim Peterson <jspeter@roanoke.infi.net> * [*/*] Remove many warnings through explicit casts, added #include's, and corrected printf formats. Tue May 5 05:18:12 1998 Insomnia (Stea Greene) <insomnia@core.binghamton.edu> * [graphics/ddraw.c] Kept unchanged portion of old palette when changing only a few palette entries. Really should only deallocate the changed cells. This make StarCraft work almost perfectly (sound overflows still cause static). Mon May 4 15:04:57 1998 Alexander V. Lukyanov <lav@long.yar.ru> * [misc/lstr.c] FormatMessage: terminate string on %0, undo linefeed strip.
1998-05-17 19:13:43 +02:00
DC_UpdateXforms( dc );
return TRUE;
}
/***********************************************************************
* dp_to_lp
*
* Internal version of DPtoLP that takes a DC *.
*/
BOOL dp_to_lp( DC *dc, POINT *points, INT count )
{
if (dc->vport2WorldValid)
{
while (count--)
{
double x = points->x;
double y = points->y;
points->x = GDI_ROUND( x * dc->xformVport2World.eM11 +
y * dc->xformVport2World.eM21 +
dc->xformVport2World.eDx );
points->y = GDI_ROUND( x * dc->xformVport2World.eM12 +
y * dc->xformVport2World.eM22 +
dc->xformVport2World.eDy );
points++;
}
}
return (count < 0);
}
/***********************************************************************
* NtGdiTransformPoints (win32u.@)
*/
BOOL WINAPI NtGdiTransformPoints( HDC hdc, const POINT *points_in, POINT *points_out,
INT count, UINT mode )
{
DC *dc = get_dc_ptr( hdc );
int i = 0;
BOOL ret = FALSE;
if (!dc) return FALSE;
switch (mode)
{
case NtGdiLPtoDP:
for (i = 0; i < count; i++)
{
double x = points_in[i].x;
double y = points_in[i].y;
points_out[i].x = GDI_ROUND( x * dc->xformWorld2Vport.eM11 +
y * dc->xformWorld2Vport.eM21 +
dc->xformWorld2Vport.eDx );
points_out[i].y = GDI_ROUND( x * dc->xformWorld2Vport.eM12 +
y * dc->xformWorld2Vport.eM22 +
dc->xformWorld2Vport.eDy );
}
ret = TRUE;
break;
case NtGdiDPtoLP:
if (!dc->vport2WorldValid) break;
for (i = 0; i < count; i++)
{
double x = points_in[i].x;
double y = points_in[i].y;
points_out[i].x = GDI_ROUND( x * dc->xformVport2World.eM11 +
y * dc->xformVport2World.eM21 +
dc->xformVport2World.eDx );
points_out[i].y = GDI_ROUND( x * dc->xformVport2World.eM12 +
y * dc->xformVport2World.eM22 +
dc->xformVport2World.eDy );
}
ret = TRUE;
break;
default:
WARN( "invalid mode %x\n", mode );
break;
}
release_dc_ptr( dc );
return ret;
}
/***********************************************************************
* lp_to_dp
*
* Internal version of LPtoDP that takes a DC *.
*/
void lp_to_dp( DC *dc, POINT *points, INT count )
{
while (count--)
{
double x = points->x;
double y = points->y;
points->x = GDI_ROUND( x * dc->xformWorld2Vport.eM11 +
y * dc->xformWorld2Vport.eM21 +
dc->xformWorld2Vport.eDx );
points->y = GDI_ROUND( x * dc->xformWorld2Vport.eM12 +
y * dc->xformWorld2Vport.eM22 +
dc->xformWorld2Vport.eDy );
points++;
}
}
/***********************************************************************
* NtGdiComputeXformCoefficients (win32u.@)
*/
BOOL WINAPI NtGdiComputeXformCoefficients( HDC hdc )
{
DC *dc;
if (!(dc = get_dc_ptr( hdc ))) return FALSE;
if (dc->attr->map_mode == MM_ISOTROPIC) MAPPING_FixIsotropic( dc );
DC_UpdateXforms( dc );
release_dc_ptr( dc );
return TRUE;
}
/***********************************************************************
* NtGdiScaleViewportExtEx (win32u.@)
*/
BOOL WINAPI NtGdiScaleViewportExtEx( HDC hdc, INT x_num, INT x_denom,
INT y_num, INT y_denom, SIZE *size )
{
DC *dc;
if ((!(dc = get_dc_ptr( hdc )))) return FALSE;
if (size) *size = dc->attr->vport_ext;
if (dc->attr->map_mode == MM_ISOTROPIC || dc->attr->map_mode == MM_ANISOTROPIC)
{
if (!x_num || !x_denom || !y_num || !y_denom)
{
release_dc_ptr( dc );
return FALSE;
}
dc->attr->vport_ext.cx = (dc->attr->vport_ext.cx * x_num) / x_denom;
dc->attr->vport_ext.cy = (dc->attr->vport_ext.cy * y_num) / y_denom;
if (dc->attr->vport_ext.cx == 0) dc->attr->vport_ext.cx = 1;
if (dc->attr->vport_ext.cy == 0) dc->attr->vport_ext.cy = 1;
if (dc->attr->map_mode == MM_ISOTROPIC) MAPPING_FixIsotropic( dc );
DC_UpdateXforms( dc );
}
release_dc_ptr( dc );
return TRUE;
}
/***********************************************************************
* NtGdiScaleWindowExtEx (win32u.@)
*/
BOOL WINAPI NtGdiScaleWindowExtEx( HDC hdc, INT x_num, INT x_denom,
INT y_num, INT y_denom, SIZE *size )
{
DC *dc;
if ((!(dc = get_dc_ptr( hdc )))) return FALSE;
if (size) *size = dc->attr->wnd_ext;
if (dc->attr->map_mode == MM_ISOTROPIC || dc->attr->map_mode == MM_ANISOTROPIC)
{
if (!x_num || !x_denom || !y_num || !y_denom)
{
release_dc_ptr( dc );
return FALSE;
}
dc->attr->wnd_ext.cx = (dc->attr->wnd_ext.cx * x_num) / x_denom;
dc->attr->wnd_ext.cy = (dc->attr->wnd_ext.cy * y_num) / y_denom;
if (dc->attr->wnd_ext.cx == 0) dc->attr->wnd_ext.cx = 1;
if (dc->attr->wnd_ext.cy == 0) dc->attr->wnd_ext.cy = 1;
if (dc->attr->map_mode == MM_ISOTROPIC) MAPPING_FixIsotropic( dc );
DC_UpdateXforms( dc );
}
release_dc_ptr( dc );
return TRUE;
}
2009-06-23 15:55:32 +02:00
/****************************************************************************
* NtGdiModifyWorldTransform (win32u.@)
*/
BOOL WINAPI NtGdiModifyWorldTransform( HDC hdc, const XFORM *xform, DWORD mode )
{
BOOL ret = FALSE;
DC *dc;
if (!xform && mode != MWT_IDENTITY) return FALSE;
if ((dc = get_dc_ptr( hdc )))
{
switch (mode)
{
case MWT_IDENTITY:
dc->xformWorld2Wnd.eM11 = 1.0f;
dc->xformWorld2Wnd.eM12 = 0.0f;
dc->xformWorld2Wnd.eM21 = 0.0f;
dc->xformWorld2Wnd.eM22 = 1.0f;
dc->xformWorld2Wnd.eDx = 0.0f;
dc->xformWorld2Wnd.eDy = 0.0f;
ret = TRUE;
break;
case MWT_LEFTMULTIPLY:
combine_transform( &dc->xformWorld2Wnd, xform, &dc->xformWorld2Wnd );
ret = TRUE;
break;
case MWT_RIGHTMULTIPLY:
combine_transform( &dc->xformWorld2Wnd, &dc->xformWorld2Wnd, xform );
ret = TRUE;
break;
case MWT_SET:
ret = dc->attr->graphics_mode == GM_ADVANCED &&
xform->eM11 * xform->eM22 != xform->eM12 * xform->eM21;
if (ret) dc->xformWorld2Wnd = *xform;
break;
}
if (ret) DC_UpdateXforms( dc );
release_dc_ptr( dc );
}
return ret;
}
2009-06-23 15:55:32 +02:00
/***********************************************************************
* NtGdiSetVirtualResolution (win32u.@)
2009-06-23 15:55:32 +02:00
*
* Undocumented on msdn.
*
* Changes the values of screen size in pixels and millimeters used by
* the mapping mode functions.
*
* PARAMS
* hdc [I] Device context
* horz_res [I] Width in pixels (equivalent to HORZRES device cap).
* vert_res [I] Height in pixels (equivalent to VERTRES device cap).
* horz_size [I] Width in mm (equivalent to HORZSIZE device cap).
* vert_size [I] Height in mm (equivalent to VERTSIZE device cap).
*
* RETURNS
* TRUE if successful.
* FALSE if any (but not all) of the last four params are zero.
*
* NOTES
* This doesn't change the values returned by NtGdiGetDeviceCaps, just the
2009-06-23 15:55:32 +02:00
* scaling of the mapping modes.
*
* Calling with the last four params equal to zero sets the values
* back to their defaults obtained by calls to NtGdiGetDeviceCaps.
2009-06-23 15:55:32 +02:00
*/
BOOL WINAPI NtGdiSetVirtualResolution( HDC hdc, DWORD horz_res, DWORD vert_res,
DWORD horz_size, DWORD vert_size )
2009-06-23 15:55:32 +02:00
{
DC * dc;
TRACE("(%p %d %d %d %d)\n", hdc, horz_res, vert_res, horz_size, vert_size);
if (!horz_res || !vert_res || !horz_size || !vert_size)
2009-06-23 15:55:32 +02:00
{
/* they must be all zero */
if (horz_res || vert_res || horz_size || vert_size) return FALSE;
2009-06-23 15:55:32 +02:00
}
dc = get_dc_ptr( hdc );
if (!dc) return FALSE;
dc->attr->virtual_res.cx = horz_res;
dc->attr->virtual_res.cy = vert_res;
dc->attr->virtual_size.cx = horz_size;
dc->attr->virtual_size.cy = vert_size;
2009-06-23 15:55:32 +02:00
release_dc_ptr( dc );
return TRUE;
}
void combine_transform( XFORM *result, const XFORM *xform1, const XFORM *xform2 )
{
XFORM r;
/* Create the result in a temporary XFORM, since result may be
* equal to xform1 or xform2 */
r.eM11 = xform1->eM11 * xform2->eM11 + xform1->eM12 * xform2->eM21;
r.eM12 = xform1->eM11 * xform2->eM12 + xform1->eM12 * xform2->eM22;
r.eM21 = xform1->eM21 * xform2->eM11 + xform1->eM22 * xform2->eM21;
r.eM22 = xform1->eM21 * xform2->eM12 + xform1->eM22 * xform2->eM22;
r.eDx = xform1->eDx * xform2->eM11 + xform1->eDy * xform2->eM21 + xform2->eDx;
r.eDy = xform1->eDx * xform2->eM12 + xform1->eDy * xform2->eM22 + xform2->eDy;
*result = r;
}