mirror of
https://github.com/reactos/reactos.git
synced 2025-02-20 15:35:04 +00:00
[GDIPLUS] Sync with Wine Staging 2.16. CORE-13762
6bf1b63 gdiplus: Account for gdi32 transform in SOFTWARE_GdipDrawThinPath. e127101 gdiplus: Send paths to gdi32 in device coordinates. 93e8507 gdiplus: Account for gdi32 transform in GdipDrawImage. be95252 gdiplus: Use SOFTWARE_GdipDrawPath with alpha hdc's. 0914f62 gdiplus: Account for gdi transform in brush_fill_pixels. 399fd55 gdiplus: Account for gdi transform in SOFTWARE_GdipFillRegion. 016dc76 gdiplus: Transform clipping region to gdi device coordinates. cfa4f08 gdiplus: Replace DPtoLP with an internal coordinate space constant. 5c12ced gdiplus: Check for invalid coordinate space in GdipTransformPoints. 8c593bd gdiplus: Set correct color space flags for grayscale images. 7860d11 gdiplus: Don't call PlayEnhMetaFileRecord for records handled by gdiplus. 5870431 gdiplus: Force conversion of 8 bpp grayscale PNG images to 32 bpp BGRA. 42e5d27 gdiplus: Use defined constants for wrap modes. 79ebd3f gdiplus: Fix copy-paste typo. a4ab858 gdiplus: GdipCreateMetafileFromWmfFile will also load EMFs. aac33da gdiplus: Implement transform matrix for line gradient brushes. 14bb8df gdiplus: Support GdipSetClipRegion in metafiles. 4a02870 gdiplus: Add write_region_data helper and use it in GdipGetRegionData. 595959c gdiplus: Add more accurate algorithm for inverting scaling and translation matrices in GdipInvertMatrix. 1744277 gdiplus: Implement stub for GdipGraphicsSetAbort. 331a7af gdiplus: Fix a possible floating point exception in path gradients. 400cfb0 gdiplus: Avoid division by zero in SOFTWARE_GdipDrawThinPath. 2176348 gdiplus: Return success saving path to metafile. 70afb4e gdiplus: Fix saving pen dashed line cap style to metafile. a172cc6 gdiplus: Free dash_pattern_scaled (Coverity). 58eb74c gdiplus: Use write_path_data helper in GdipGetRegionData. a892b68 gdiplus: Add write_path_data helper to create EMF+ path object. 5545332 gdiplus: Store newer gdi+ version in created GdipRegions. cfe2b3f gdiplus: Don't require specific gdi+ versions in metafile structures. a8b5fdd gdiplus: Use VERSION_MAGIC2 constant in metafiles functions. 8498aa3 gdiplus: Add support for creating image object containing metafile. 9f22041 gdiplus: Fix leak in widen_dashed_figure. f9b881e gdiplus: Fix GdipGetVisibleClipBounds behavior on metafiles. de37ced gdiplus: Add partial support for GdipFillPath on metafiles. e79c4ca gdiplus: Add partial support for GdipDrawPath on metafiles. 7d6896e gdiplus: Add helper for saving pens to metafile. e502a8d gdiplus: Add helper for saving path objects in metafile. 8608bf5 gdiplus: Add DrawPath stub for metafiles. 29968cf gdiplus: Support GdipSetInterpolationMode in metafiles. f248374 gdiplus: Support GdipSetCompositingQuality in metafiles. 1cecd47 gdiplus: Support GdipSetCompositingMode in metafiles. 910975a gdiplus: Support GdipSetSmoothingMode in metafiles. f716029 gdiplus: Support GdipSetPixelOffsetMode in metafiles. 683315d gdiplus: Support GdipSetTextRenderingHint in metafiles. 689268d gdiplus: Add support for ImageAttributes when drawing image to metafile. ac231b1 gdiplus: Add function for managing metafile objects id. e1e4dd2 gdiplus: Add partial support for GdipDrawImagePointsRect on metafile. 1a75f76 gdiplus: Remove unused clsid parameter from encode_image_func helpers. 01c9fb9 gdiplus: Remove a duplicate word in a comment. 6ec3cd9 gdiplus: Set flatness more appropriately in GdipDrawPath. 7e1522c gdiplus: Scale widened dashes to the pen width. c95877d gdiplus: Write API documentation for GdipAddPathArc and GdipAddPathArcI. f1123f3 gdiplus: Write API for GdipClonePath. f96e319 gdiplus: Write API for GdipAddPathLine and GdipAddPathLineI. 260cbd0 gdiplus: Implement triangular line caps in widened paths. a4b7fe6 gdiplus: Initialize containers list in GdipCloneImage. svn path=/trunk/; revision=75872
This commit is contained in:
parent
2a10a08909
commit
c6f32e4a5f
10 changed files with 1654 additions and 332 deletions
|
@ -1,5 +1,6 @@
|
|||
/*
|
||||
* Copyright (C) 2007 Google (Evan Stade)
|
||||
* Copyright (C) 2003-2004,2007 Novell, Inc. http://www.novell.com (Ravindra (rkumar@novell.com))
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
|
@ -162,6 +163,8 @@ GpStatus WINGDIPAPI GdipCloneBrush(GpBrush *brush, GpBrush **clone)
|
|||
return OutOfMemory;
|
||||
}
|
||||
|
||||
dest->transform = src->transform;
|
||||
|
||||
memcpy(dest->blendfac, src->blendfac, count * sizeof(REAL));
|
||||
memcpy(dest->blendpos, src->blendpos, count * sizeof(REAL));
|
||||
|
||||
|
@ -272,6 +275,41 @@ GpStatus WINGDIPAPI GdipCreateHatchBrush(HatchStyle hatchstyle, ARGB forecol, AR
|
|||
return Ok;
|
||||
}
|
||||
|
||||
static void linegradient_init_transform(GpLineGradient *line)
|
||||
{
|
||||
float trans_x = line->rect.X + (line->rect.Width / 2.f);
|
||||
float trans_y = line->rect.Y + (line->rect.Height / 2.f);
|
||||
float dx = line->endpoint.X - line->startpoint.X;
|
||||
float dy = line->endpoint.Y - line->startpoint.Y;
|
||||
float t_cos, t_sin, w_ratio, h_ratio;
|
||||
float h;
|
||||
GpMatrix rot;
|
||||
|
||||
h = sqrtf(dx * dx + dy * dy);
|
||||
|
||||
t_cos = dx / h;
|
||||
t_sin = dy / h;
|
||||
|
||||
w_ratio = (fabs(t_cos) * line->rect.Width + fabs(t_sin) * line->rect.Height) / line->rect.Width;
|
||||
h_ratio = (fabs(t_sin) * line->rect.Width + fabs(t_cos) * line->rect.Height) / line->rect.Height;
|
||||
|
||||
GdipSetMatrixElements(&line->transform, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0);
|
||||
|
||||
GdipSetMatrixElements(&rot, t_cos, t_sin, -1.f * t_sin, t_cos, 0, 0);
|
||||
|
||||
/* center about the origin */
|
||||
GdipTranslateMatrix(&line->transform, -trans_x, -trans_y, MatrixOrderAppend);
|
||||
|
||||
/* scale to normalize gradient along gradient line (?) */
|
||||
GdipScaleMatrix(&line->transform, w_ratio, h_ratio, MatrixOrderAppend);
|
||||
|
||||
/* rotate so the gradient is horizontal */
|
||||
GdipMultiplyMatrix(&line->transform, &rot, MatrixOrderAppend);
|
||||
|
||||
/* restore original offset in new coords */
|
||||
GdipTranslateMatrix(&line->transform, trans_x, trans_y, MatrixOrderAppend);
|
||||
}
|
||||
|
||||
/******************************************************************************
|
||||
* GdipCreateLineBrush [GDIPLUS.@]
|
||||
*/
|
||||
|
@ -338,6 +376,8 @@ GpStatus WINGDIPAPI GdipCreateLineBrush(GDIPCONST GpPointF* startpoint,
|
|||
(*line)->pblendpos = NULL;
|
||||
(*line)->pblendcount = 0;
|
||||
|
||||
linegradient_init_transform(*line);
|
||||
|
||||
TRACE("<-- %p\n", *line);
|
||||
|
||||
return Ok;
|
||||
|
@ -370,6 +410,7 @@ GpStatus WINGDIPAPI GdipCreateLineBrushFromRect(GDIPCONST GpRectF* rect,
|
|||
{
|
||||
GpPointF start, end;
|
||||
GpStatus stat;
|
||||
float far_x, far_y;
|
||||
|
||||
TRACE("(%p, %x, %x, %d, %d, %p)\n", rect, startcolor, endcolor, mode,
|
||||
wrap, line);
|
||||
|
@ -377,31 +418,34 @@ GpStatus WINGDIPAPI GdipCreateLineBrushFromRect(GDIPCONST GpRectF* rect,
|
|||
if(!line || !rect)
|
||||
return InvalidParameter;
|
||||
|
||||
far_x = rect->X + rect->Width;
|
||||
far_y = rect->Y + rect->Height;
|
||||
|
||||
switch (mode)
|
||||
{
|
||||
case LinearGradientModeHorizontal:
|
||||
start.X = rect->X;
|
||||
start.X = min(rect->X, far_x);
|
||||
start.Y = rect->Y;
|
||||
end.X = rect->X + rect->Width;
|
||||
end.X = max(rect->X, far_x);
|
||||
end.Y = rect->Y;
|
||||
break;
|
||||
case LinearGradientModeVertical:
|
||||
start.X = rect->X;
|
||||
start.Y = rect->Y;
|
||||
start.Y = min(rect->Y, far_y);
|
||||
end.X = rect->X;
|
||||
end.Y = rect->Y + rect->Height;
|
||||
end.Y = max(rect->Y, far_y);
|
||||
break;
|
||||
case LinearGradientModeForwardDiagonal:
|
||||
start.X = rect->X;
|
||||
start.Y = rect->Y;
|
||||
end.X = rect->X + rect->Width;
|
||||
end.Y = rect->Y + rect->Height;
|
||||
start.X = min(rect->X, far_x);
|
||||
start.Y = min(rect->Y, far_y);
|
||||
end.X = max(rect->X, far_x);
|
||||
end.Y = max(rect->Y, far_y);
|
||||
break;
|
||||
case LinearGradientModeBackwardDiagonal:
|
||||
start.X = rect->X + rect->Width;
|
||||
start.Y = rect->Y;
|
||||
end.X = rect->X;
|
||||
end.Y = rect->Y + rect->Height;
|
||||
start.X = max(rect->X, far_x);
|
||||
start.Y = min(rect->Y, far_y);
|
||||
end.X = min(rect->X, far_x);
|
||||
end.Y = max(rect->Y, far_y);
|
||||
break;
|
||||
default:
|
||||
return InvalidParameter;
|
||||
|
@ -510,6 +554,8 @@ GpStatus WINGDIPAPI GdipCreateLineBrushFromRectWithAngle(GDIPCONST GpRectF* rect
|
|||
(*line)->startpoint.X = rect->X + exofs;
|
||||
(*line)->startpoint.Y = rect->Y + eyofs;
|
||||
}
|
||||
|
||||
linegradient_init_transform(*line);
|
||||
}
|
||||
|
||||
return stat;
|
||||
|
@ -2018,78 +2064,73 @@ GpStatus WINGDIPAPI GdipGetLinePresetBlendCount(GpLineGradient *brush,
|
|||
|
||||
GpStatus WINGDIPAPI GdipResetLineTransform(GpLineGradient *brush)
|
||||
{
|
||||
static int calls;
|
||||
|
||||
TRACE("(%p)\n", brush);
|
||||
|
||||
if(!(calls++))
|
||||
FIXME("not implemented\n");
|
||||
if(!brush)
|
||||
return InvalidParameter;
|
||||
|
||||
return NotImplemented;
|
||||
return GdipSetMatrixElements(&brush->transform, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0);
|
||||
}
|
||||
|
||||
GpStatus WINGDIPAPI GdipSetLineTransform(GpLineGradient *brush,
|
||||
GDIPCONST GpMatrix *matrix)
|
||||
{
|
||||
static int calls;
|
||||
|
||||
TRACE("(%p,%p)\n", brush, matrix);
|
||||
|
||||
if(!(calls++))
|
||||
FIXME("not implemented\n");
|
||||
if(!brush || !matrix)
|
||||
return InvalidParameter;
|
||||
|
||||
return NotImplemented;
|
||||
brush->transform = *matrix;
|
||||
|
||||
return Ok;
|
||||
}
|
||||
|
||||
GpStatus WINGDIPAPI GdipGetLineTransform(GpLineGradient *brush, GpMatrix *matrix)
|
||||
{
|
||||
static int calls;
|
||||
|
||||
TRACE("(%p,%p)\n", brush, matrix);
|
||||
|
||||
if(!(calls++))
|
||||
FIXME("not implemented\n");
|
||||
if(!brush || !matrix)
|
||||
return InvalidParameter;
|
||||
|
||||
return NotImplemented;
|
||||
*matrix = brush->transform;
|
||||
|
||||
return Ok;
|
||||
}
|
||||
|
||||
GpStatus WINGDIPAPI GdipScaleLineTransform(GpLineGradient *brush, REAL sx, REAL sy,
|
||||
GpMatrixOrder order)
|
||||
{
|
||||
static int calls;
|
||||
|
||||
TRACE("(%p,%0.2f,%0.2f,%u)\n", brush, sx, sy, order);
|
||||
|
||||
if(!(calls++))
|
||||
FIXME("not implemented\n");
|
||||
if(!brush)
|
||||
return InvalidParameter;
|
||||
|
||||
return NotImplemented;
|
||||
return GdipScaleMatrix(&brush->transform, sx, sy, order);
|
||||
}
|
||||
|
||||
GpStatus WINGDIPAPI GdipMultiplyLineTransform(GpLineGradient *brush,
|
||||
GDIPCONST GpMatrix *matrix, GpMatrixOrder order)
|
||||
{
|
||||
static int calls;
|
||||
|
||||
TRACE("(%p,%p,%u)\n", brush, matrix, order);
|
||||
|
||||
if(!(calls++))
|
||||
FIXME("not implemented\n");
|
||||
if(!brush)
|
||||
return InvalidParameter;
|
||||
|
||||
return NotImplemented;
|
||||
if(!matrix)
|
||||
return Ok;
|
||||
|
||||
return GdipMultiplyMatrix(&brush->transform, matrix, order);
|
||||
}
|
||||
|
||||
GpStatus WINGDIPAPI GdipTranslateLineTransform(GpLineGradient* brush,
|
||||
GpStatus WINGDIPAPI GdipTranslateLineTransform(GpLineGradient *brush,
|
||||
REAL dx, REAL dy, GpMatrixOrder order)
|
||||
{
|
||||
static int calls;
|
||||
|
||||
TRACE("(%p,%f,%f,%d)\n", brush, dx, dy, order);
|
||||
|
||||
if(!(calls++))
|
||||
FIXME("not implemented\n");
|
||||
if(!brush)
|
||||
return InvalidParameter;
|
||||
|
||||
return Ok;
|
||||
return GdipTranslateMatrix(&brush->transform, dx, dy, order);
|
||||
}
|
||||
|
||||
/******************************************************************************
|
||||
|
|
|
@ -622,7 +622,7 @@
|
|||
622 stdcall GdipBitmapGetHistogramSize(long ptr)
|
||||
623 stdcall GdipBitmapConvertFormat(ptr long long long ptr float)
|
||||
624 stdcall GdipImageSetAbort(ptr ptr)
|
||||
625 stub GdipGraphicsSetAbort
|
||||
625 stdcall GdipGraphicsSetAbort(ptr ptr)
|
||||
626 stub GdipDrawImageFX
|
||||
627 stdcall GdipConvertToEmfPlus(ptr ptr ptr long ptr ptr)
|
||||
628 stdcall GdipConvertToEmfPlusToFile(ptr ptr ptr ptr long ptr ptr)
|
||||
|
|
|
@ -49,6 +49,7 @@ WINE_DEFAULT_DEBUG_CHANNEL(gdiplus);
|
|||
|
||||
#define VERSION_MAGIC 0xdbc01001
|
||||
#define VERSION_MAGIC2 0xdbc01002
|
||||
#define VALID_MAGIC(x) (((x) & 0xfffff000) == 0xdbc01000)
|
||||
#define TENSION_CONST (0.3)
|
||||
|
||||
#define GIF_DISPOSE_UNSPECIFIED 0
|
||||
|
@ -86,10 +87,17 @@ extern REAL units_to_pixels(REAL units, GpUnit unit, REAL dpi) DECLSPEC_HIDDEN;
|
|||
extern REAL pixels_to_units(REAL pixels, GpUnit unit, REAL dpi) DECLSPEC_HIDDEN;
|
||||
extern REAL units_scale(GpUnit from, GpUnit to, REAL dpi) DECLSPEC_HIDDEN;
|
||||
|
||||
#define WineCoordinateSpaceGdiDevice ((GpCoordinateSpace)4)
|
||||
|
||||
extern GpStatus gdi_transform_acquire(GpGraphics *graphics);
|
||||
extern GpStatus gdi_transform_release(GpGraphics *graphics);
|
||||
extern GpStatus get_graphics_transform(GpGraphics *graphics, GpCoordinateSpace dst_space,
|
||||
GpCoordinateSpace src_space, GpMatrix *matrix) DECLSPEC_HIDDEN;
|
||||
extern GpStatus gdip_transform_points(GpGraphics *graphics, GpCoordinateSpace dst_space,
|
||||
GpCoordinateSpace src_space, GpPointF *points, INT count) DECLSPEC_HIDDEN;
|
||||
|
||||
extern GpStatus graphics_from_image(GpImage *image, GpGraphics **graphics) DECLSPEC_HIDDEN;
|
||||
extern GpStatus encode_image_png(GpImage *image, IStream* stream, GDIPCONST EncoderParameters* params) DECLSPEC_HIDDEN;
|
||||
|
||||
extern GpStatus METAFILE_GetGraphicsContext(GpMetafile* metafile, GpGraphics **result) DECLSPEC_HIDDEN;
|
||||
extern GpStatus METAFILE_GetDC(GpMetafile* metafile, HDC *hdc) DECLSPEC_HIDDEN;
|
||||
|
@ -99,6 +107,7 @@ extern GpStatus METAFILE_FillRectangles(GpMetafile* metafile, GpBrush* brush,
|
|||
GDIPCONST GpRectF* rects, INT count) DECLSPEC_HIDDEN;
|
||||
extern GpStatus METAFILE_SetClipRect(GpMetafile* metafile,
|
||||
REAL x, REAL y, REAL width, REAL height, CombineMode mode) DECLSPEC_HIDDEN;
|
||||
extern GpStatus METAFILE_SetClipRegion(GpMetafile* metafile, GpRegion* region, CombineMode mode) DECLSPEC_HIDDEN;
|
||||
extern GpStatus METAFILE_SetPageTransform(GpMetafile* metafile, GpUnit unit, REAL scale) DECLSPEC_HIDDEN;
|
||||
extern GpStatus METAFILE_SetWorldTransform(GpMetafile* metafile, GDIPCONST GpMatrix* transform) DECLSPEC_HIDDEN;
|
||||
extern GpStatus METAFILE_ScaleWorldTransform(GpMetafile* metafile, REAL sx, REAL sy, MatrixOrder order) DECLSPEC_HIDDEN;
|
||||
|
@ -113,6 +122,13 @@ extern GpStatus METAFILE_EndContainer(GpMetafile* metafile, DWORD StackIndex) DE
|
|||
extern GpStatus METAFILE_SaveGraphics(GpMetafile* metafile, DWORD StackIndex) DECLSPEC_HIDDEN;
|
||||
extern GpStatus METAFILE_RestoreGraphics(GpMetafile* metafile, DWORD StackIndex) DECLSPEC_HIDDEN;
|
||||
extern GpStatus METAFILE_GraphicsDeleted(GpMetafile* metafile) DECLSPEC_HIDDEN;
|
||||
extern GpStatus METAFILE_DrawImagePointsRect(GpMetafile* metafile, GpImage *image,
|
||||
GDIPCONST GpPointF *points, INT count, REAL srcx, REAL srcy, REAL srcwidth,
|
||||
REAL srcheight, GpUnit srcUnit, GDIPCONST GpImageAttributes* imageAttributes,
|
||||
DrawImageAbort callback, VOID *callbackData) DECLSPEC_HIDDEN;
|
||||
extern GpStatus METAFILE_AddSimpleProperty(GpMetafile *metafile, SHORT prop, SHORT val) DECLSPEC_HIDDEN;
|
||||
extern GpStatus METAFILE_DrawPath(GpMetafile *metafile, GpPen *pen, GpPath *path) DECLSPEC_HIDDEN;
|
||||
extern GpStatus METAFILE_FillPath(GpMetafile *metafile, GpBrush *brush, GpPath *path) DECLSPEC_HIDDEN;
|
||||
|
||||
extern void calc_curve_bezier(const GpPointF *pts, REAL tension, REAL *x1,
|
||||
REAL *y1, REAL *x2, REAL *y2) DECLSPEC_HIDDEN;
|
||||
|
@ -123,6 +139,9 @@ extern void free_installed_fonts(void) DECLSPEC_HIDDEN;
|
|||
|
||||
extern BOOL lengthen_path(GpPath *path, INT len) DECLSPEC_HIDDEN;
|
||||
|
||||
extern DWORD write_region_data(const GpRegion *region, void *data) DECLSPEC_HIDDEN;
|
||||
extern DWORD write_path_data(GpPath *path, void *data) DECLSPEC_HIDDEN;
|
||||
|
||||
extern GpStatus trace_path(GpGraphics *graphics, GpPath *path) DECLSPEC_HIDDEN;
|
||||
|
||||
typedef struct region_element region_element;
|
||||
|
@ -249,6 +268,8 @@ struct GpGraphics{
|
|||
struct list containers;
|
||||
GraphicsContainer contid; /* last-issued container ID */
|
||||
INT origin_x, origin_y;
|
||||
INT gdi_transform_acquire_count, gdi_transform_save;
|
||||
GpMatrix gdi_transform;
|
||||
/* For giving the caller an HDC when we technically can't: */
|
||||
HBITMAP temp_hbitmap;
|
||||
int temp_hbitmap_width;
|
||||
|
@ -307,6 +328,7 @@ struct GpLineGradient{
|
|||
ARGB* pblendcolor; /* preset blend colors */
|
||||
REAL* pblendpos; /* preset blend positions */
|
||||
INT pblendcount;
|
||||
GpMatrix transform;
|
||||
};
|
||||
|
||||
struct GpTexture{
|
||||
|
@ -373,6 +395,7 @@ struct GpMetafile{
|
|||
IStream *record_stream;
|
||||
BOOL auto_frame; /* If true, determine the frame automatically */
|
||||
GpPointF auto_frame_min, auto_frame_max;
|
||||
DWORD next_object_id;
|
||||
|
||||
/* playback */
|
||||
GpGraphics *playback_graphics;
|
||||
|
|
|
@ -38,6 +38,10 @@ static volatile LONG g_priv_contid = GDIP_CONTID_STEP;
|
|||
#define GDIP_GET_NEW_CONTID_FOR(pGpGraphics) \
|
||||
(UINT)(InterlockedExchangeAdd(&g_priv_contid,GDIP_CONTID_STEP))
|
||||
|
||||
|
||||
/* ReactOS FIXME: Inspect */
|
||||
#define fmax max
|
||||
|
||||
/* looks-right constants */
|
||||
#define ANCHOR_WIDTH (2.0)
|
||||
#define MAX_ITERS (50)
|
||||
|
@ -245,6 +249,14 @@ static INT prepare_dc(GpGraphics *graphics, GpPen *pen)
|
|||
|
||||
width *= units_to_pixels(pen->width, pen->unit == UnitWorld ? graphics->unit : pen->unit, graphics->xres);
|
||||
width *= graphics->scale;
|
||||
|
||||
pt[0].X = 0.0;
|
||||
pt[0].Y = 0.0;
|
||||
pt[1].X = 1.0;
|
||||
pt[1].Y = 1.0;
|
||||
gdip_transform_points(graphics, WineCoordinateSpaceGdiDevice, CoordinateSpaceDevice, pt, 2);
|
||||
width *= sqrt((pt[1].X - pt[0].X) * (pt[1].X - pt[0].X) +
|
||||
(pt[1].Y - pt[0].Y) * (pt[1].Y - pt[0].Y)) / sqrt(2.0);
|
||||
}
|
||||
|
||||
if(pen->dash == DashStyleCustom){
|
||||
|
@ -280,43 +292,23 @@ static void restore_dc(GpGraphics *graphics, INT state)
|
|||
RestoreDC(graphics->hdc, state);
|
||||
}
|
||||
|
||||
/* This helper applies all the changes that the points listed in ptf need in
|
||||
* order to be drawn on the device context. In the end, this should include at
|
||||
* least:
|
||||
* -scaling by page unit
|
||||
* -applying world transformation
|
||||
* -converting from float to int
|
||||
* Native gdiplus uses gdi32 to do all this (via SetMapMode, SetViewportExtEx,
|
||||
* SetWindowExtEx, SetWorldTransform, etc.) but we cannot because we are using
|
||||
* gdi to draw, and these functions would irreparably mess with line widths.
|
||||
*/
|
||||
static void transform_and_round_points(GpGraphics *graphics, POINT *pti,
|
||||
GpPointF *ptf, INT count)
|
||||
static void round_points(POINT *pti, GpPointF *ptf, INT count)
|
||||
{
|
||||
REAL scale_x, scale_y;
|
||||
GpMatrix matrix;
|
||||
int i;
|
||||
|
||||
scale_x = units_to_pixels(1.0, graphics->unit, graphics->xres);
|
||||
scale_y = units_to_pixels(1.0, graphics->unit, graphics->yres);
|
||||
|
||||
/* apply page scale */
|
||||
if(graphics->unit != UnitDisplay)
|
||||
{
|
||||
scale_x *= graphics->scale;
|
||||
scale_y *= graphics->scale;
|
||||
}
|
||||
|
||||
matrix = graphics->worldtrans;
|
||||
GdipScaleMatrix(&matrix, scale_x, scale_y, MatrixOrderAppend);
|
||||
GdipTransformMatrixPoints(&matrix, ptf, count);
|
||||
|
||||
for(i = 0; i < count; i++){
|
||||
pti[i].x = gdip_round(ptf[i].X);
|
||||
pti[i].y = gdip_round(ptf[i].Y);
|
||||
}
|
||||
}
|
||||
|
||||
static void transform_and_round_points(GpGraphics *graphics, POINT *pti,
|
||||
GpPointF *ptf, INT count)
|
||||
{
|
||||
gdip_transform_points(graphics, CoordinateSpaceDevice, CoordinateSpaceWorld, ptf, count);
|
||||
round_points(pti, ptf, count);
|
||||
}
|
||||
|
||||
static void gdi_alpha_blend(GpGraphics *graphics, INT dst_x, INT dst_y, INT dst_width, INT dst_height,
|
||||
HDC hdc, INT src_x, INT src_y, INT src_width, INT src_height)
|
||||
{
|
||||
|
@ -344,8 +336,26 @@ static void gdi_alpha_blend(GpGraphics *graphics, INT dst_x, INT dst_y, INT dst_
|
|||
|
||||
static GpStatus get_clip_hrgn(GpGraphics *graphics, HRGN *hrgn)
|
||||
{
|
||||
/* clipping region is in device coords */
|
||||
return GdipGetRegionHRgn(graphics->clip, NULL, hrgn);
|
||||
GpRegion *rgn;
|
||||
GpMatrix transform;
|
||||
GpStatus stat;
|
||||
|
||||
stat = get_graphics_transform(graphics, WineCoordinateSpaceGdiDevice, CoordinateSpaceDevice, &transform);
|
||||
|
||||
if (stat == Ok)
|
||||
stat = GdipCloneRegion(graphics->clip, &rgn);
|
||||
|
||||
if (stat == Ok)
|
||||
{
|
||||
stat = GdipTransformRegion(rgn, &transform);
|
||||
|
||||
if (stat == Ok)
|
||||
stat = GdipGetRegionHRgn(rgn, NULL, hrgn);
|
||||
|
||||
GdipDeleteRegion(rgn);
|
||||
}
|
||||
|
||||
return stat;
|
||||
}
|
||||
|
||||
/* Draw ARGB data to the given graphics object */
|
||||
|
@ -555,6 +565,7 @@ static ARGB blend_line_gradient(GpLineGradient* brush, REAL position)
|
|||
REAL blendfac;
|
||||
|
||||
/* clamp to between 0.0 and 1.0, using the wrap mode */
|
||||
position = (position - brush->rect.X) / brush->rect.Width;
|
||||
if (brush->wrap == WrapModeTile)
|
||||
{
|
||||
position = fmodf(position, 1.0f);
|
||||
|
@ -905,9 +916,8 @@ static ARGB sample_bitmap_pixel(GDIPCONST GpRect *src_rect, LPBYTE bits, UINT wi
|
|||
if (y < 0)
|
||||
y = height*2 + y % (height * 2);
|
||||
|
||||
if ((attributes->wrap & 1) == 1)
|
||||
if (attributes->wrap & WrapModeTileFlipX)
|
||||
{
|
||||
/* Flip X */
|
||||
if ((x / width) % 2 == 0)
|
||||
x = x % width;
|
||||
else
|
||||
|
@ -916,9 +926,8 @@ static ARGB sample_bitmap_pixel(GDIPCONST GpRect *src_rect, LPBYTE bits, UINT wi
|
|||
else
|
||||
x = x % width;
|
||||
|
||||
if ((attributes->wrap & 2) == 2)
|
||||
if (attributes->wrap & WrapModeTileFlipY)
|
||||
{
|
||||
/* Flip Y */
|
||||
if ((y / height) % 2 == 0)
|
||||
y = y % height;
|
||||
else
|
||||
|
@ -1220,10 +1229,8 @@ static GpStatus brush_fill_pixels(GpGraphics *graphics, GpBrush *brush,
|
|||
case BrushTypeLinearGradient:
|
||||
{
|
||||
GpLineGradient *fill = (GpLineGradient*)brush;
|
||||
GpPointF draw_points[3], line_points[3];
|
||||
GpPointF draw_points[3];
|
||||
GpStatus stat;
|
||||
static const GpRectF box_1 = { 0.0, 0.0, 1.0, 1.0 };
|
||||
GpMatrix *world_to_gradient; /* FIXME: Store this in the brush? */
|
||||
int x, y;
|
||||
|
||||
draw_points[0].X = fill_area->X;
|
||||
|
@ -1236,27 +1243,16 @@ static GpStatus brush_fill_pixels(GpGraphics *graphics, GpBrush *brush,
|
|||
/* Transform the points to a co-ordinate space where X is the point's
|
||||
* position in the gradient, 0.0 being the start point and 1.0 the
|
||||
* end point. */
|
||||
stat = GdipTransformPoints(graphics, CoordinateSpaceWorld,
|
||||
CoordinateSpaceDevice, draw_points, 3);
|
||||
stat = gdip_transform_points(graphics, CoordinateSpaceWorld,
|
||||
WineCoordinateSpaceGdiDevice, draw_points, 3);
|
||||
|
||||
if (stat == Ok)
|
||||
{
|
||||
line_points[0] = fill->startpoint;
|
||||
line_points[1] = fill->endpoint;
|
||||
line_points[2].X = fill->startpoint.X + (fill->startpoint.Y - fill->endpoint.Y);
|
||||
line_points[2].Y = fill->startpoint.Y + (fill->endpoint.X - fill->startpoint.X);
|
||||
|
||||
stat = GdipCreateMatrix3(&box_1, line_points, &world_to_gradient);
|
||||
}
|
||||
|
||||
if (stat == Ok)
|
||||
{
|
||||
stat = GdipInvertMatrix(world_to_gradient);
|
||||
GpMatrix world_to_gradient = fill->transform;
|
||||
|
||||
stat = GdipInvertMatrix(&world_to_gradient);
|
||||
if (stat == Ok)
|
||||
stat = GdipTransformMatrixPoints(world_to_gradient, draw_points, 3);
|
||||
|
||||
GdipDeleteMatrix(world_to_gradient);
|
||||
stat = GdipTransformMatrixPoints(&world_to_gradient, draw_points, 3);
|
||||
}
|
||||
|
||||
if (stat == Ok)
|
||||
|
@ -1308,8 +1304,8 @@ static GpStatus brush_fill_pixels(GpGraphics *graphics, GpBrush *brush,
|
|||
draw_points[2].Y = fill_area->Y+1;
|
||||
|
||||
/* Transform the points to the co-ordinate space of the bitmap. */
|
||||
stat = GdipTransformPoints(graphics, CoordinateSpaceWorld,
|
||||
CoordinateSpaceDevice, draw_points, 3);
|
||||
stat = gdip_transform_points(graphics, CoordinateSpaceWorld,
|
||||
WineCoordinateSpaceGdiDevice, draw_points, 3);
|
||||
|
||||
if (stat == Ok)
|
||||
{
|
||||
|
@ -1439,7 +1435,7 @@ static GpStatus brush_fill_pixels(GpGraphics *graphics, GpBrush *brush,
|
|||
if (stat != Ok)
|
||||
return stat;
|
||||
|
||||
stat = get_graphics_transform(graphics, CoordinateSpaceDevice,
|
||||
stat = get_graphics_transform(graphics, WineCoordinateSpaceGdiDevice,
|
||||
CoordinateSpaceWorld, &world_to_device);
|
||||
if (stat == Ok)
|
||||
{
|
||||
|
@ -1575,8 +1571,17 @@ static GpStatus brush_fill_pixels(GpGraphics *graphics, GpBrush *brush,
|
|||
REAL blend_amount, pdy, pdx;
|
||||
pdy = yf - center_point.Y;
|
||||
pdx = xf - center_point.X;
|
||||
blend_amount = ( (center_point.Y - start_point.Y) * pdx + (start_point.X - center_point.X) * pdy ) / ( dy * pdx - dx * pdy );
|
||||
outer_color = blend_colors(start_color, end_color, blend_amount);
|
||||
|
||||
if (fabs(pdx) <= 0.001 && fabs(pdy) <= 0.001)
|
||||
{
|
||||
/* Too close to center point, don't try to calculate outer color */
|
||||
outer_color = start_color;
|
||||
}
|
||||
else
|
||||
{
|
||||
blend_amount = ( (center_point.Y - start_point.Y) * pdx + (start_point.X - center_point.X) * pdy ) / ( dy * pdx - dx * pdy );
|
||||
outer_color = blend_colors(start_color, end_color, blend_amount);
|
||||
}
|
||||
}
|
||||
|
||||
distance = (end_point.Y - start_point.Y) * (start_point.X - xf) +
|
||||
|
@ -1662,7 +1667,10 @@ static void draw_cap(GpGraphics *graphics, COLORREF color, GpLineCap cap, REAL s
|
|||
ptf[3].X = x2 - dbig;
|
||||
ptf[2].X = x2 + dsmall;
|
||||
|
||||
transform_and_round_points(graphics, pt, ptf, 4);
|
||||
gdip_transform_points(graphics, WineCoordinateSpaceGdiDevice, CoordinateSpaceWorld, ptf, 3);
|
||||
|
||||
round_points(pt, ptf, 3);
|
||||
|
||||
Polygon(graphics->hdc, pt, 4);
|
||||
|
||||
break;
|
||||
|
@ -1684,7 +1692,10 @@ static void draw_cap(GpGraphics *graphics, COLORREF color, GpLineCap cap, REAL s
|
|||
ptf[2].X = x2;
|
||||
ptf[2].Y = y2;
|
||||
|
||||
transform_and_round_points(graphics, pt, ptf, 3);
|
||||
gdip_transform_points(graphics, WineCoordinateSpaceGdiDevice, CoordinateSpaceWorld, ptf, 3);
|
||||
|
||||
round_points(pt, ptf, 3);
|
||||
|
||||
Polygon(graphics->hdc, pt, 3);
|
||||
|
||||
break;
|
||||
|
@ -1696,7 +1707,10 @@ static void draw_cap(GpGraphics *graphics, COLORREF color, GpLineCap cap, REAL s
|
|||
ptf[1].X = x2 + dx;
|
||||
ptf[1].Y = y2 + dy;
|
||||
|
||||
transform_and_round_points(graphics, pt, ptf, 2);
|
||||
gdip_transform_points(graphics, WineCoordinateSpaceGdiDevice, CoordinateSpaceWorld, ptf, 3);
|
||||
|
||||
round_points(pt, ptf, 3);
|
||||
|
||||
Ellipse(graphics->hdc, pt[0].x, pt[0].y, pt[1].x, pt[1].y);
|
||||
|
||||
break;
|
||||
|
@ -1716,7 +1730,10 @@ static void draw_cap(GpGraphics *graphics, COLORREF color, GpLineCap cap, REAL s
|
|||
ptf[2].X = x2 + dx;
|
||||
ptf[2].Y = y2 + dy;
|
||||
|
||||
transform_and_round_points(graphics, pt, ptf, 3);
|
||||
gdip_transform_points(graphics, WineCoordinateSpaceGdiDevice, CoordinateSpaceWorld, ptf, 3);
|
||||
|
||||
round_points(pt, ptf, 3);
|
||||
|
||||
Polygon(graphics->hdc, pt, 3);
|
||||
|
||||
break;
|
||||
|
@ -1736,7 +1753,10 @@ static void draw_cap(GpGraphics *graphics, COLORREF color, GpLineCap cap, REAL s
|
|||
ptf[3].X = x2 + dx;
|
||||
ptf[3].Y = y2 + dy;
|
||||
|
||||
transform_and_round_points(graphics, pt, ptf, 4);
|
||||
gdip_transform_points(graphics, WineCoordinateSpaceGdiDevice, CoordinateSpaceWorld, ptf, 3);
|
||||
|
||||
round_points(pt, ptf, 3);
|
||||
|
||||
Pie(graphics->hdc, pt[0].x, pt[0].y, pt[1].x, pt[1].y, pt[2].x,
|
||||
pt[2].y, pt[3].x, pt[3].y);
|
||||
|
||||
|
@ -1762,7 +1782,9 @@ static void draw_cap(GpGraphics *graphics, COLORREF color, GpLineCap cap, REAL s
|
|||
GdipTranslateMatrix(&matrix, x2, y2, MatrixOrderAppend);
|
||||
GdipTransformMatrixPoints(&matrix, custptf, count);
|
||||
|
||||
transform_and_round_points(graphics, custpt, custptf, count);
|
||||
gdip_transform_points(graphics, WineCoordinateSpaceGdiDevice, CoordinateSpaceWorld, ptf, 3);
|
||||
|
||||
round_points(pt, ptf, 3);
|
||||
|
||||
for(i = 0; i < count; i++)
|
||||
tp[i] = convert_path_point_type(custom->pathdata.Types[i]);
|
||||
|
@ -1985,7 +2007,9 @@ static GpStatus draw_poly(GpGraphics *graphics, GpPen *pen, GDIPCONST GpPointF *
|
|||
}
|
||||
}
|
||||
|
||||
transform_and_round_points(graphics, pti, ptcopy, count);
|
||||
gdip_transform_points(graphics, WineCoordinateSpaceGdiDevice, CoordinateSpaceWorld, ptcopy, count);
|
||||
|
||||
round_points(pti, ptcopy, count);
|
||||
|
||||
for(i = 0; i < count; i++){
|
||||
tp[i] = convert_path_point_type(types[i]);
|
||||
|
@ -2108,7 +2132,7 @@ static GpStatus restore_container(GpGraphics* graphics,
|
|||
return Ok;
|
||||
}
|
||||
|
||||
static GpStatus get_graphics_bounds(GpGraphics* graphics, GpRectF* rect)
|
||||
static GpStatus get_graphics_device_bounds(GpGraphics* graphics, GpRectF* rect)
|
||||
{
|
||||
RECT wnd_rect;
|
||||
GpStatus stat=Ok;
|
||||
|
@ -2152,21 +2176,39 @@ static GpStatus get_graphics_bounds(GpGraphics* graphics, GpRectF* rect)
|
|||
rect->Height = GetDeviceCaps(graphics->hdc, VERTRES);
|
||||
}
|
||||
|
||||
if (graphics->hdc)
|
||||
return stat;
|
||||
}
|
||||
|
||||
static GpStatus get_graphics_bounds(GpGraphics* graphics, GpRectF* rect)
|
||||
{
|
||||
GpStatus stat = get_graphics_device_bounds(graphics, rect);
|
||||
|
||||
if (stat == Ok && graphics->hdc)
|
||||
{
|
||||
POINT points[2];
|
||||
GpPointF points[4], min_point, max_point;
|
||||
int i;
|
||||
|
||||
points[0].x = rect->X;
|
||||
points[0].y = rect->Y;
|
||||
points[1].x = rect->X + rect->Width;
|
||||
points[1].y = rect->Y + rect->Height;
|
||||
points[0].X = points[2].X = rect->X;
|
||||
points[0].Y = points[1].Y = rect->Y;
|
||||
points[1].X = points[3].X = rect->X + rect->Width;
|
||||
points[2].Y = points[3].Y = rect->Y + rect->Height;
|
||||
|
||||
DPtoLP(graphics->hdc, points, sizeof(points)/sizeof(points[0]));
|
||||
gdip_transform_points(graphics, CoordinateSpaceDevice, WineCoordinateSpaceGdiDevice, points, 4);
|
||||
|
||||
rect->X = min(points[0].x, points[1].x);
|
||||
rect->Y = min(points[0].y, points[1].y);
|
||||
rect->Width = abs(points[1].x - points[0].x);
|
||||
rect->Height = abs(points[1].y - points[0].y);
|
||||
min_point = max_point = points[0];
|
||||
|
||||
for (i=1; i<4; i++)
|
||||
{
|
||||
if (points[i].X < min_point.X) min_point.X = points[i].X;
|
||||
if (points[i].Y < min_point.Y) min_point.Y = points[i].Y;
|
||||
if (points[i].X > max_point.X) max_point.X = points[i].X;
|
||||
if (points[i].Y > max_point.Y) max_point.Y = points[i].Y;
|
||||
}
|
||||
|
||||
rect->X = min_point.X;
|
||||
rect->Y = min_point.Y;
|
||||
rect->Width = max_point.X - min_point.X;
|
||||
rect->Height = max_point.Y - min_point.Y;
|
||||
}
|
||||
|
||||
return stat;
|
||||
|
@ -2180,6 +2222,10 @@ static GpStatus get_visible_clip_region(GpGraphics *graphics, GpRegion *rgn)
|
|||
GpRectF rectf;
|
||||
GpRegion* tmp;
|
||||
|
||||
/* Ignore graphics image bounds for metafiles */
|
||||
if (graphics->image && graphics->image_type == ImageTypeMetafile)
|
||||
return GdipCombineRegionRegion(rgn, graphics->clip, CombineModeReplace);
|
||||
|
||||
if((stat = get_graphics_bounds(graphics, &rectf)) != Ok)
|
||||
return stat;
|
||||
|
||||
|
@ -2954,6 +3000,13 @@ GpStatus WINGDIPAPI GdipDrawImagePointsRect(GpGraphics *graphics, GpImage *image
|
|||
TRACE("%s %s %s\n", debugstr_pointf(&points[0]), debugstr_pointf(&points[1]),
|
||||
debugstr_pointf(&points[2]));
|
||||
|
||||
if (graphics->image && graphics->image->type == ImageTypeMetafile)
|
||||
{
|
||||
return METAFILE_DrawImagePointsRect((GpMetafile*)graphics->image,
|
||||
image, points, count, srcx, srcy, srcwidth, srcheight,
|
||||
srcUnit, imageAttributes, callback, callbackData);
|
||||
}
|
||||
|
||||
memcpy(ptf, points, 3 * sizeof(GpPointF));
|
||||
|
||||
/* Ensure source width/height is positive */
|
||||
|
@ -2983,7 +3036,8 @@ GpStatus WINGDIPAPI GdipDrawImagePointsRect(GpGraphics *graphics, GpImage *image
|
|||
ptf[3].Y = ptf[2].Y + ptf[1].Y - ptf[0].Y;
|
||||
if (!srcwidth || !srcheight || (ptf[3].X == ptf[0].X && ptf[3].Y == ptf[0].Y))
|
||||
return Ok;
|
||||
transform_and_round_points(graphics, pti, ptf, 4);
|
||||
gdip_transform_points(graphics, WineCoordinateSpaceGdiDevice, CoordinateSpaceWorld, ptf, 4);
|
||||
round_points(pti, ptf, 4);
|
||||
|
||||
TRACE("%s %s %s %s\n", wine_dbgstr_point(&pti[0]), wine_dbgstr_point(&pti[1]),
|
||||
wine_dbgstr_point(&pti[2]), wine_dbgstr_point(&pti[3]));
|
||||
|
@ -3045,7 +3099,7 @@ GpStatus WINGDIPAPI GdipDrawImagePointsRect(GpGraphics *graphics, GpImage *image
|
|||
if (dst_area.bottom < pti[i].y) dst_area.bottom = pti[i].y;
|
||||
}
|
||||
|
||||
stat = get_graphics_bounds(graphics, &graphics_bounds);
|
||||
stat = get_graphics_device_bounds(graphics, &graphics_bounds);
|
||||
if (stat != Ok) return stat;
|
||||
|
||||
if (graphics_bounds.X > dst_area.left) dst_area.left = floorf(graphics_bounds.X);
|
||||
|
@ -3183,10 +3237,14 @@ GpStatus WINGDIPAPI GdipDrawImagePointsRect(GpGraphics *graphics, GpImage *image
|
|||
dst_stride = src_stride;
|
||||
}
|
||||
|
||||
gdi_transform_acquire(graphics);
|
||||
|
||||
stat = alpha_blend_pixels(graphics, dst_area.left, dst_area.top,
|
||||
dst_data, dst_area.right - dst_area.left, dst_area.bottom - dst_area.top, dst_stride,
|
||||
lockeddata.PixelFormat);
|
||||
|
||||
gdi_transform_release(graphics);
|
||||
|
||||
heap_free(src_data);
|
||||
|
||||
heap_free(dst_dyn_data);
|
||||
|
@ -3270,6 +3328,8 @@ GpStatus WINGDIPAPI GdipDrawImagePointsRect(GpGraphics *graphics, GpImage *image
|
|||
DeleteObject(hrgn);
|
||||
}
|
||||
|
||||
gdi_transform_acquire(graphics);
|
||||
|
||||
if (bitmap->format & (PixelFormatAlpha|PixelFormatPAlpha))
|
||||
{
|
||||
gdi_alpha_blend(graphics, pti[0].x, pti[0].y, pti[1].x - pti[0].x, pti[2].y - pti[0].y,
|
||||
|
@ -3281,6 +3341,8 @@ GpStatus WINGDIPAPI GdipDrawImagePointsRect(GpGraphics *graphics, GpImage *image
|
|||
hdc, srcx, srcy, srcwidth, srcheight, SRCCOPY);
|
||||
}
|
||||
|
||||
gdi_transform_release(graphics);
|
||||
|
||||
RestoreDC(graphics->hdc, save_state);
|
||||
|
||||
if (temp_hdc)
|
||||
|
@ -3506,9 +3568,13 @@ static GpStatus GDI32_GdipDrawPath(GpGraphics *graphics, GpPen *pen, GpPath *pat
|
|||
if (hrgn)
|
||||
ExtSelectClipRgn(graphics->hdc, hrgn, RGN_AND);
|
||||
|
||||
gdi_transform_acquire(graphics);
|
||||
|
||||
retval = draw_poly(graphics, pen, path->pathdata.Points,
|
||||
path->pathdata.Types, path->pathdata.Count, TRUE);
|
||||
|
||||
gdi_transform_release(graphics);
|
||||
|
||||
end:
|
||||
restore_dc(graphics, save_state);
|
||||
DeleteObject(hrgn);
|
||||
|
@ -3541,7 +3607,7 @@ static GpStatus SOFTWARE_GdipDrawThinPath(GpGraphics *graphics, GpPen *pen, GpPa
|
|||
|
||||
if (stat == Ok)
|
||||
{
|
||||
stat = get_graphics_transform(graphics, CoordinateSpaceDevice,
|
||||
stat = get_graphics_transform(graphics, WineCoordinateSpaceGdiDevice,
|
||||
CoordinateSpaceWorld, transform);
|
||||
|
||||
if (stat == Ok)
|
||||
|
@ -3570,7 +3636,7 @@ static GpStatus SOFTWARE_GdipDrawThinPath(GpGraphics *graphics, GpPen *pen, GpPa
|
|||
if (ceilf(y) > output_area.bottom) output_area.bottom = ceilf(y);
|
||||
}
|
||||
|
||||
stat = get_graphics_bounds(graphics, &gp_bound_rect);
|
||||
stat = get_graphics_device_bounds(graphics, &gp_bound_rect);
|
||||
}
|
||||
|
||||
if (stat == Ok)
|
||||
|
@ -3708,6 +3774,9 @@ static GpStatus SOFTWARE_GdipDrawThinPath(GpGraphics *graphics, GpPen *pen, GpPa
|
|||
end_pointi.X = floorf(end_point.X);
|
||||
end_pointi.Y = floorf(end_point.Y);
|
||||
|
||||
if(start_pointi.X == end_pointi.X && start_pointi.Y == end_pointi.Y)
|
||||
continue;
|
||||
|
||||
/* draw line segment */
|
||||
if (abs(start_pointi.Y - end_pointi.Y) > abs(start_pointi.X - end_pointi.X))
|
||||
{
|
||||
|
@ -3799,9 +3868,13 @@ static GpStatus SOFTWARE_GdipDrawThinPath(GpGraphics *graphics, GpPen *pen, GpPa
|
|||
/* draw output image */
|
||||
if (stat == Ok)
|
||||
{
|
||||
gdi_transform_acquire(graphics);
|
||||
|
||||
stat = alpha_blend_pixels(graphics, output_area.left, output_area.top,
|
||||
(BYTE*)output_bits, output_width, output_height, output_width * 4,
|
||||
PixelFormat32bppARGB);
|
||||
|
||||
gdi_transform_release(graphics);
|
||||
}
|
||||
|
||||
heap_free(brush_bits);
|
||||
|
@ -3819,6 +3892,7 @@ static GpStatus SOFTWARE_GdipDrawPath(GpGraphics *graphics, GpPen *pen, GpPath *
|
|||
GpStatus stat;
|
||||
GpPath *wide_path;
|
||||
GpMatrix *transform=NULL;
|
||||
REAL flatness=1.0;
|
||||
|
||||
/* Check if the final pen thickness in pixels is too thin. */
|
||||
if (pen->unit == UnitPixel)
|
||||
|
@ -3860,9 +3934,24 @@ static GpStatus SOFTWARE_GdipDrawPath(GpGraphics *graphics, GpPen *pen, GpPath *
|
|||
stat = get_graphics_transform(graphics, CoordinateSpaceDevice,
|
||||
CoordinateSpaceWorld, transform);
|
||||
}
|
||||
else
|
||||
{
|
||||
/* Set flatness based on the final coordinate space */
|
||||
GpMatrix t;
|
||||
|
||||
stat = get_graphics_transform(graphics, CoordinateSpaceDevice,
|
||||
CoordinateSpaceWorld, &t);
|
||||
|
||||
if (stat != Ok)
|
||||
return stat;
|
||||
|
||||
flatness = 1.0/sqrt(fmax(
|
||||
t.matrix[0] * t.matrix[0] + t.matrix[1] * t.matrix[1],
|
||||
t.matrix[2] * t.matrix[2] + t.matrix[3] * t.matrix[3]));
|
||||
}
|
||||
|
||||
if (stat == Ok)
|
||||
stat = GdipWidenPath(wide_path, pen, transform, 1.0);
|
||||
stat = GdipWidenPath(wide_path, pen, transform, flatness);
|
||||
|
||||
if (pen->unit == UnitPixel)
|
||||
{
|
||||
|
@ -3900,7 +3989,9 @@ GpStatus WINGDIPAPI GdipDrawPath(GpGraphics *graphics, GpPen *pen, GpPath *path)
|
|||
if (path->pathdata.Count == 0)
|
||||
return Ok;
|
||||
|
||||
if (!graphics->hdc || !brush_can_fill_path(pen->brush, FALSE))
|
||||
if (graphics->image && graphics->image->type == ImageTypeMetafile)
|
||||
retval = METAFILE_DrawPath((GpMetafile*)graphics->image, pen, path);
|
||||
else if (!graphics->hdc || graphics->alpha_hdc || !brush_can_fill_path(pen->brush, FALSE))
|
||||
retval = SOFTWARE_GdipDrawPath(graphics, pen, path);
|
||||
else
|
||||
retval = GDI32_GdipDrawPath(graphics, pen, path);
|
||||
|
@ -4166,17 +4257,19 @@ static GpStatus GDI32_GdipFillPath(GpGraphics *graphics, GpBrush *brush, GpPath
|
|||
if (hrgn)
|
||||
ExtSelectClipRgn(graphics->hdc, hrgn, RGN_AND);
|
||||
|
||||
gdi_transform_acquire(graphics);
|
||||
|
||||
BeginPath(graphics->hdc);
|
||||
retval = draw_poly(graphics, NULL, path->pathdata.Points,
|
||||
path->pathdata.Types, path->pathdata.Count, FALSE);
|
||||
|
||||
if(retval != Ok)
|
||||
goto end;
|
||||
if(retval == Ok)
|
||||
{
|
||||
EndPath(graphics->hdc);
|
||||
brush_fill_path(graphics, brush);
|
||||
}
|
||||
|
||||
EndPath(graphics->hdc);
|
||||
brush_fill_path(graphics, brush);
|
||||
|
||||
retval = Ok;
|
||||
gdi_transform_release(graphics);
|
||||
|
||||
end:
|
||||
RestoreDC(graphics->hdc, save_state);
|
||||
|
@ -4219,6 +4312,9 @@ GpStatus WINGDIPAPI GdipFillPath(GpGraphics *graphics, GpBrush *brush, GpPath *p
|
|||
if(graphics->busy)
|
||||
return ObjectBusy;
|
||||
|
||||
if (graphics->image && graphics->image->type == ImageTypeMetafile)
|
||||
return METAFILE_FillPath((GpMetafile*)graphics->image, brush, path);
|
||||
|
||||
if (!graphics->image && !graphics->alpha_hdc)
|
||||
stat = GDI32_GdipFillPath(graphics, brush, path);
|
||||
|
||||
|
@ -4425,7 +4521,7 @@ GpStatus WINGDIPAPI GdipFillRectanglesI(GpGraphics *graphics, GpBrush *brush, GD
|
|||
for(i = 0; i < count; i++){
|
||||
rectsF[i].X = (REAL)rects[i].X;
|
||||
rectsF[i].Y = (REAL)rects[i].Y;
|
||||
rectsF[i].X = (REAL)rects[i].Width;
|
||||
rectsF[i].Width = (REAL)rects[i].Width;
|
||||
rectsF[i].Height = (REAL)rects[i].Height;
|
||||
}
|
||||
|
||||
|
@ -4502,14 +4598,17 @@ static GpStatus SOFTWARE_GdipFillRegion(GpGraphics *graphics, GpBrush *brush,
|
|||
if (!brush_can_fill_pixels(brush))
|
||||
return NotImplemented;
|
||||
|
||||
stat = get_graphics_bounds(graphics, &graphics_bounds);
|
||||
stat = gdi_transform_acquire(graphics);
|
||||
|
||||
if (stat == Ok)
|
||||
stat = get_graphics_device_bounds(graphics, &graphics_bounds);
|
||||
|
||||
if (stat == Ok)
|
||||
stat = GdipCloneRegion(region, &temp_region);
|
||||
|
||||
if (stat == Ok)
|
||||
{
|
||||
stat = get_graphics_transform(graphics, CoordinateSpaceDevice,
|
||||
stat = get_graphics_transform(graphics, WineCoordinateSpaceGdiDevice,
|
||||
CoordinateSpaceWorld, &world_to_device);
|
||||
|
||||
if (stat == Ok)
|
||||
|
@ -4527,6 +4626,7 @@ static GpStatus SOFTWARE_GdipFillRegion(GpGraphics *graphics, GpBrush *brush,
|
|||
if (stat == Ok && GetRgnBox(hregion, &bound_rect) == NULLREGION)
|
||||
{
|
||||
DeleteObject(hregion);
|
||||
gdi_transform_release(graphics);
|
||||
return Ok;
|
||||
}
|
||||
|
||||
|
@ -4558,6 +4658,8 @@ static GpStatus SOFTWARE_GdipFillRegion(GpGraphics *graphics, GpBrush *brush,
|
|||
DeleteObject(hregion);
|
||||
}
|
||||
|
||||
gdi_transform_release(graphics);
|
||||
|
||||
return stat;
|
||||
}
|
||||
|
||||
|
@ -5889,6 +5991,19 @@ GpStatus WINGDIPAPI GdipSetCompositingMode(GpGraphics *graphics,
|
|||
if(graphics->busy)
|
||||
return ObjectBusy;
|
||||
|
||||
if(graphics->compmode == mode)
|
||||
return Ok;
|
||||
|
||||
if(graphics->image && graphics->image->type == ImageTypeMetafile)
|
||||
{
|
||||
GpStatus stat;
|
||||
|
||||
stat = METAFILE_AddSimpleProperty((GpMetafile*)graphics->image,
|
||||
EmfPlusRecordTypeSetCompositingMode, mode);
|
||||
if(stat != Ok)
|
||||
return stat;
|
||||
}
|
||||
|
||||
graphics->compmode = mode;
|
||||
|
||||
return Ok;
|
||||
|
@ -5905,6 +6020,19 @@ GpStatus WINGDIPAPI GdipSetCompositingQuality(GpGraphics *graphics,
|
|||
if(graphics->busy)
|
||||
return ObjectBusy;
|
||||
|
||||
if(graphics->compqual == quality)
|
||||
return Ok;
|
||||
|
||||
if(graphics->image && graphics->image->type == ImageTypeMetafile)
|
||||
{
|
||||
GpStatus stat;
|
||||
|
||||
stat = METAFILE_AddSimpleProperty((GpMetafile*)graphics->image,
|
||||
EmfPlusRecordTypeSetCompositingQuality, quality);
|
||||
if(stat != Ok)
|
||||
return stat;
|
||||
}
|
||||
|
||||
graphics->compqual = quality;
|
||||
|
||||
return Ok;
|
||||
|
@ -5927,6 +6055,19 @@ GpStatus WINGDIPAPI GdipSetInterpolationMode(GpGraphics *graphics,
|
|||
if (mode == InterpolationModeHighQuality)
|
||||
mode = InterpolationModeHighQualityBicubic;
|
||||
|
||||
if (mode == graphics->interpolation)
|
||||
return Ok;
|
||||
|
||||
if (graphics->image && graphics->image->type == ImageTypeMetafile)
|
||||
{
|
||||
GpStatus stat;
|
||||
|
||||
stat = METAFILE_AddSimpleProperty((GpMetafile*)graphics->image,
|
||||
EmfPlusRecordTypeSetInterpolationMode, mode);
|
||||
if (stat != Ok)
|
||||
return stat;
|
||||
}
|
||||
|
||||
graphics->interpolation = mode;
|
||||
|
||||
return Ok;
|
||||
|
@ -5994,6 +6135,19 @@ GpStatus WINGDIPAPI GdipSetPixelOffsetMode(GpGraphics *graphics, PixelOffsetMode
|
|||
if(graphics->busy)
|
||||
return ObjectBusy;
|
||||
|
||||
if(graphics->pixeloffset == mode)
|
||||
return Ok;
|
||||
|
||||
if(graphics->image && graphics->image->type == ImageTypeMetafile)
|
||||
{
|
||||
GpStatus stat;
|
||||
|
||||
stat = METAFILE_AddSimpleProperty((GpMetafile*)graphics->image,
|
||||
EmfPlusRecordTypeSetPixelOffsetMode, mode);
|
||||
if(stat != Ok)
|
||||
return stat;
|
||||
}
|
||||
|
||||
graphics->pixeloffset = mode;
|
||||
|
||||
return Ok;
|
||||
|
@ -6040,6 +6194,20 @@ GpStatus WINGDIPAPI GdipSetSmoothingMode(GpGraphics *graphics, SmoothingMode mod
|
|||
if(graphics->busy)
|
||||
return ObjectBusy;
|
||||
|
||||
if(graphics->smoothing == mode)
|
||||
return Ok;
|
||||
|
||||
if(graphics->image && graphics->image->type == ImageTypeMetafile) {
|
||||
GpStatus stat;
|
||||
BOOL antialias = (mode != SmoothingModeDefault &&
|
||||
mode != SmoothingModeNone && mode != SmoothingModeHighSpeed);
|
||||
|
||||
stat = METAFILE_AddSimpleProperty((GpMetafile*)graphics->image,
|
||||
EmfPlusRecordTypeSetAntiAliasMode, (mode << 1) + antialias);
|
||||
if(stat != Ok)
|
||||
return stat;
|
||||
}
|
||||
|
||||
graphics->smoothing = mode;
|
||||
|
||||
return Ok;
|
||||
|
@ -6068,6 +6236,18 @@ GpStatus WINGDIPAPI GdipSetTextRenderingHint(GpGraphics *graphics,
|
|||
if(graphics->busy)
|
||||
return ObjectBusy;
|
||||
|
||||
if(graphics->texthint == hint)
|
||||
return Ok;
|
||||
|
||||
if(graphics->image && graphics->image->type == ImageTypeMetafile) {
|
||||
GpStatus stat;
|
||||
|
||||
stat = METAFILE_AddSimpleProperty((GpMetafile*)graphics->image,
|
||||
EmfPlusRecordTypeSetTextRenderingHint, hint);
|
||||
if(stat != Ok)
|
||||
return stat;
|
||||
}
|
||||
|
||||
graphics->texthint = hint;
|
||||
|
||||
return Ok;
|
||||
|
@ -6251,6 +6431,13 @@ GpStatus WINGDIPAPI GdipSetClipRegion(GpGraphics *graphics, GpRegion *region,
|
|||
if(graphics->busy)
|
||||
return ObjectBusy;
|
||||
|
||||
if (graphics->image && graphics->image->type == ImageTypeMetafile)
|
||||
{
|
||||
status = METAFILE_SetClipRegion((GpMetafile*)graphics->image, region, mode);
|
||||
if (status != Ok)
|
||||
return status;
|
||||
}
|
||||
|
||||
status = GdipCloneRegion(region, &clip);
|
||||
if (status == Ok)
|
||||
{
|
||||
|
@ -6536,6 +6723,56 @@ GpStatus WINGDIPAPI GdipGetClip(GpGraphics *graphics, GpRegion *region)
|
|||
return Ok;
|
||||
}
|
||||
|
||||
static void get_gdi_transform(GpGraphics *graphics, GpMatrix *matrix)
|
||||
{
|
||||
XFORM xform;
|
||||
|
||||
if (graphics->hdc == NULL)
|
||||
{
|
||||
GdipSetMatrixElements(matrix, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0);
|
||||
return;
|
||||
}
|
||||
|
||||
if (graphics->gdi_transform_acquire_count)
|
||||
{
|
||||
*matrix = graphics->gdi_transform;
|
||||
return;
|
||||
}
|
||||
|
||||
GetTransform(graphics->hdc, 0x204, &xform);
|
||||
GdipSetMatrixElements(matrix, xform.eM11, xform.eM12, xform.eM21, xform.eM22, xform.eDx, xform.eDy);
|
||||
}
|
||||
|
||||
GpStatus gdi_transform_acquire(GpGraphics *graphics)
|
||||
{
|
||||
if (graphics->gdi_transform_acquire_count == 0 && graphics->hdc)
|
||||
{
|
||||
get_gdi_transform(graphics, &graphics->gdi_transform);
|
||||
graphics->gdi_transform_save = SaveDC(graphics->hdc);
|
||||
SetGraphicsMode(graphics->hdc, GM_COMPATIBLE);
|
||||
SetMapMode(graphics->hdc, MM_TEXT);
|
||||
SetWindowOrgEx(graphics->hdc, 0, 0, NULL);
|
||||
SetViewportOrgEx(graphics->hdc, 0, 0, NULL);
|
||||
}
|
||||
graphics->gdi_transform_acquire_count++;
|
||||
return Ok;
|
||||
}
|
||||
|
||||
GpStatus gdi_transform_release(GpGraphics *graphics)
|
||||
{
|
||||
if (graphics->gdi_transform_acquire_count <= 0)
|
||||
{
|
||||
ERR("called without matching gdi_transform_acquire");
|
||||
return GenericError;
|
||||
}
|
||||
if (graphics->gdi_transform_acquire_count == 1 && graphics->hdc)
|
||||
{
|
||||
RestoreDC(graphics->hdc, graphics->gdi_transform_save);
|
||||
}
|
||||
graphics->gdi_transform_acquire_count--;
|
||||
return Ok;
|
||||
}
|
||||
|
||||
GpStatus get_graphics_transform(GpGraphics *graphics, GpCoordinateSpace dst_space,
|
||||
GpCoordinateSpace src_space, GpMatrix *matrix)
|
||||
{
|
||||
|
@ -6555,23 +6792,29 @@ GpStatus get_graphics_transform(GpGraphics *graphics, GpCoordinateSpace dst_spac
|
|||
scale_y *= graphics->scale;
|
||||
}
|
||||
|
||||
/* transform from src_space to CoordinateSpacePage */
|
||||
switch (src_space)
|
||||
if (dst_space < src_space)
|
||||
{
|
||||
case CoordinateSpaceWorld:
|
||||
GdipMultiplyMatrix(matrix, &graphics->worldtrans, MatrixOrderAppend);
|
||||
break;
|
||||
case CoordinateSpacePage:
|
||||
break;
|
||||
case CoordinateSpaceDevice:
|
||||
GdipScaleMatrix(matrix, 1.0/scale_x, 1.0/scale_y, MatrixOrderAppend);
|
||||
break;
|
||||
}
|
||||
|
||||
/* transform from CoordinateSpacePage to dst_space */
|
||||
switch (dst_space)
|
||||
{
|
||||
case CoordinateSpaceWorld:
|
||||
/* transform towards world space */
|
||||
switch ((int)src_space)
|
||||
{
|
||||
case WineCoordinateSpaceGdiDevice:
|
||||
{
|
||||
GpMatrix gdixform;
|
||||
get_gdi_transform(graphics, &gdixform);
|
||||
stat = GdipInvertMatrix(&gdixform);
|
||||
if (stat != Ok)
|
||||
break;
|
||||
GdipMultiplyMatrix(matrix, &gdixform, MatrixOrderAppend);
|
||||
if (dst_space == CoordinateSpaceDevice)
|
||||
break;
|
||||
/* else fall-through */
|
||||
}
|
||||
case CoordinateSpaceDevice:
|
||||
GdipScaleMatrix(matrix, 1.0/scale_x, 1.0/scale_y, MatrixOrderAppend);
|
||||
if (dst_space == CoordinateSpacePage)
|
||||
break;
|
||||
/* else fall-through */
|
||||
case CoordinateSpacePage:
|
||||
{
|
||||
GpMatrix inverted_transform = graphics->worldtrans;
|
||||
stat = GdipInvertMatrix(&inverted_transform);
|
||||
|
@ -6579,23 +6822,54 @@ GpStatus get_graphics_transform(GpGraphics *graphics, GpCoordinateSpace dst_spac
|
|||
GdipMultiplyMatrix(matrix, &inverted_transform, MatrixOrderAppend);
|
||||
break;
|
||||
}
|
||||
case CoordinateSpacePage:
|
||||
break;
|
||||
case CoordinateSpaceDevice:
|
||||
GdipScaleMatrix(matrix, scale_x, scale_y, MatrixOrderAppend);
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
/* transform towards device space */
|
||||
switch ((int)src_space)
|
||||
{
|
||||
case CoordinateSpaceWorld:
|
||||
GdipMultiplyMatrix(matrix, &graphics->worldtrans, MatrixOrderAppend);
|
||||
if (dst_space == CoordinateSpacePage)
|
||||
break;
|
||||
/* else fall-through */
|
||||
case CoordinateSpacePage:
|
||||
GdipScaleMatrix(matrix, scale_x, scale_y, MatrixOrderAppend);
|
||||
if (dst_space == CoordinateSpaceDevice)
|
||||
break;
|
||||
/* else fall-through */
|
||||
case CoordinateSpaceDevice:
|
||||
{
|
||||
GpMatrix gdixform;
|
||||
get_gdi_transform(graphics, &gdixform);
|
||||
GdipMultiplyMatrix(matrix, &gdixform, MatrixOrderAppend);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return stat;
|
||||
}
|
||||
|
||||
GpStatus WINGDIPAPI GdipTransformPoints(GpGraphics *graphics, GpCoordinateSpace dst_space,
|
||||
GpCoordinateSpace src_space, GpPointF *points, INT count)
|
||||
GpStatus gdip_transform_points(GpGraphics *graphics, GpCoordinateSpace dst_space,
|
||||
GpCoordinateSpace src_space, GpPointF *points, INT count)
|
||||
{
|
||||
GpMatrix matrix;
|
||||
GpStatus stat;
|
||||
|
||||
if(!graphics || !points || count <= 0)
|
||||
stat = get_graphics_transform(graphics, dst_space, src_space, &matrix);
|
||||
if (stat != Ok) return stat;
|
||||
|
||||
return GdipTransformMatrixPoints(&matrix, points, count);
|
||||
}
|
||||
|
||||
GpStatus WINGDIPAPI GdipTransformPoints(GpGraphics *graphics, GpCoordinateSpace dst_space,
|
||||
GpCoordinateSpace src_space, GpPointF *points, INT count)
|
||||
{
|
||||
if(!graphics || !points || count <= 0 ||
|
||||
dst_space < 0 || dst_space > CoordinateSpaceDevice ||
|
||||
src_space < 0 || src_space > CoordinateSpaceDevice)
|
||||
return InvalidParameter;
|
||||
|
||||
if(graphics->busy)
|
||||
|
@ -6605,10 +6879,7 @@ GpStatus WINGDIPAPI GdipTransformPoints(GpGraphics *graphics, GpCoordinateSpace
|
|||
|
||||
if (src_space == dst_space) return Ok;
|
||||
|
||||
stat = get_graphics_transform(graphics, dst_space, src_space, &matrix);
|
||||
if (stat != Ok) return stat;
|
||||
|
||||
return GdipTransformMatrixPoints(&matrix, points, count);
|
||||
return gdip_transform_points(graphics, dst_space, src_space, points, count);
|
||||
}
|
||||
|
||||
GpStatus WINGDIPAPI GdipTransformPointsI(GpGraphics *graphics, GpCoordinateSpace dst_space,
|
||||
|
@ -7129,3 +7400,16 @@ GpStatus WINGDIPAPI GdipResetPageTransform(GpGraphics *graphics)
|
|||
|
||||
return NotImplemented;
|
||||
}
|
||||
|
||||
GpStatus WINGDIPAPI GdipGraphicsSetAbort(GpGraphics *graphics, GdiplusAbort *pabort)
|
||||
{
|
||||
TRACE("(%p, %p)\n", graphics, pabort);
|
||||
|
||||
if (!graphics)
|
||||
return InvalidParameter;
|
||||
|
||||
if (pabort)
|
||||
FIXME("Abort callback is not supported.\n");
|
||||
|
||||
return Ok;
|
||||
}
|
||||
|
|
|
@ -152,6 +152,32 @@ static BOOL flatten_bezier(path_list_node_t *start, REAL x2, REAL y2, REAL x3, R
|
|||
return TRUE;
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
* GdipAddPathArc [GDIPLUS.1]
|
||||
*
|
||||
* Add an elliptical arc to the given path.
|
||||
*
|
||||
* PARAMS
|
||||
* path [I/O] Path that the arc is appended to
|
||||
* x1 [I] X coordinate of the boundary box
|
||||
* y1 [I] Y coordinate of the boundary box
|
||||
* x2 [I] Width of the boundary box
|
||||
* y2 [I] Height of the boundary box
|
||||
* startAngle [I] Starting angle of the arc, clockwise
|
||||
* sweepAngle [I] Angle of the arc, clockwise
|
||||
*
|
||||
* RETURNS
|
||||
* InvalidParameter If the given path is invalid
|
||||
* OutOfMemory If memory allocation fails, i.e. the path cannot be lengthened
|
||||
* Ok If everything works out as expected
|
||||
*
|
||||
* NOTES
|
||||
* This functions takes the newfigure value of the given path into account,
|
||||
* i.e. the arc is connected to the end of the given path if it was set to
|
||||
* FALSE, otherwise the arc's first point gets the PathPointTypeStart value.
|
||||
* In both cases, the value of newfigure of the given path is FALSE
|
||||
* afterwards.
|
||||
*/
|
||||
GpStatus WINGDIPAPI GdipAddPathArc(GpPath *path, REAL x1, REAL y1, REAL x2,
|
||||
REAL y2, REAL startAngle, REAL sweepAngle)
|
||||
{
|
||||
|
@ -186,6 +212,11 @@ GpStatus WINGDIPAPI GdipAddPathArc(GpPath *path, REAL x1, REAL y1, REAL x2,
|
|||
return Ok;
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
* GdipAddPathArcI [GDUPLUS.2]
|
||||
*
|
||||
* See GdipAddPathArc
|
||||
*/
|
||||
GpStatus WINGDIPAPI GdipAddPathArcI(GpPath *path, INT x1, INT y1, INT x2,
|
||||
INT y2, REAL startAngle, REAL sweepAngle)
|
||||
{
|
||||
|
@ -632,6 +663,30 @@ GpStatus WINGDIPAPI GdipAddPathLine2I(GpPath *path, GDIPCONST GpPoint *points, I
|
|||
return stat;
|
||||
}
|
||||
|
||||
/*************************************************************************
|
||||
* GdipAddPathLine [GDIPLUS.21]
|
||||
*
|
||||
* Add two points to the given path.
|
||||
*
|
||||
* PARAMS
|
||||
* path [I/O] Path that the line is appended to
|
||||
* x1 [I] X coordinate of the first point of the line
|
||||
* y1 [I] Y coordinate of the first point of the line
|
||||
* x2 [I] X coordinate of the second point of the line
|
||||
* y2 [I] Y coordinate of the second point of the line
|
||||
*
|
||||
* RETURNS
|
||||
* InvalidParameter If the first parameter is not a valid path
|
||||
* OutOfMemory If the path cannot be lengthened, i.e. memory allocation fails
|
||||
* Ok If everything works out as expected
|
||||
*
|
||||
* NOTES
|
||||
* This functions takes the newfigure value of the given path into account,
|
||||
* i.e. the two new points are connected to the end of the given path if it
|
||||
* was set to FALSE, otherwise the first point is given the PathPointTypeStart
|
||||
* value. In both cases, the value of newfigure of the given path is FALSE
|
||||
* afterwards.
|
||||
*/
|
||||
GpStatus WINGDIPAPI GdipAddPathLine(GpPath *path, REAL x1, REAL y1, REAL x2, REAL y2)
|
||||
{
|
||||
INT old_count;
|
||||
|
@ -661,6 +716,11 @@ GpStatus WINGDIPAPI GdipAddPathLine(GpPath *path, REAL x1, REAL y1, REAL x2, REA
|
|||
return Ok;
|
||||
}
|
||||
|
||||
/*************************************************************************
|
||||
* GdipAddPathLineI [GDIPLUS.21]
|
||||
*
|
||||
* See GdipAddPathLine
|
||||
*/
|
||||
GpStatus WINGDIPAPI GdipAddPathLineI(GpPath *path, INT x1, INT y1, INT x2, INT y2)
|
||||
{
|
||||
TRACE("(%p, %d, %d, %d, %d)\n", path, x1, y1, x2, y2);
|
||||
|
@ -1044,6 +1104,20 @@ GpStatus WINGDIPAPI GdipAddPathStringI(GpPath* path, GDIPCONST WCHAR* string, IN
|
|||
return InvalidParameter;
|
||||
}
|
||||
|
||||
/*************************************************************************
|
||||
* GdipClonePath [GDIPLUS.53]
|
||||
*
|
||||
* Duplicate the given path in memory.
|
||||
*
|
||||
* PARAMS
|
||||
* path [I] The path to be duplicated
|
||||
* clone [O] Pointer to the new path
|
||||
*
|
||||
* RETURNS
|
||||
* InvalidParameter If the input path is invalid
|
||||
* OutOfMemory If allocation of needed memory fails
|
||||
* Ok If everything works out as expected
|
||||
*/
|
||||
GpStatus WINGDIPAPI GdipClonePath(GpPath* path, GpPath **clone)
|
||||
{
|
||||
TRACE("(%p, %p)\n", path, clone);
|
||||
|
@ -1869,6 +1943,27 @@ static void widen_cap(const GpPointF *endpoint, const GpPointF *nextpoint,
|
|||
}
|
||||
break;
|
||||
}
|
||||
case LineCapTriangle:
|
||||
{
|
||||
REAL segment_dy = nextpoint->Y-endpoint->Y;
|
||||
REAL segment_dx = nextpoint->X-endpoint->X;
|
||||
REAL segment_length = sqrtf(segment_dy*segment_dy + segment_dx*segment_dx);
|
||||
REAL distance = pen->width/2.0;
|
||||
REAL dx, dy;
|
||||
|
||||
dx = distance * segment_dx / segment_length;
|
||||
dy = distance * segment_dy / segment_length;
|
||||
|
||||
if (add_first_points) {
|
||||
add_bevel_point(endpoint, nextpoint, pen, 1, last_point);
|
||||
|
||||
*last_point = add_path_list_node(*last_point, endpoint->X - dx,
|
||||
endpoint->Y - dy, PathPointTypeLine);
|
||||
}
|
||||
if (add_last_point)
|
||||
add_bevel_point(endpoint, nextpoint, pen, 0, last_point);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1954,6 +2049,7 @@ static void widen_dashed_figure(GpPath *path, GpPen *pen, int start, int end,
|
|||
REAL dash_pos=0.0;
|
||||
int dash_index=0;
|
||||
const REAL *dash_pattern;
|
||||
REAL *dash_pattern_scaled;
|
||||
int dash_count;
|
||||
GpPointF *tmp_points;
|
||||
REAL segment_dy;
|
||||
|
@ -1992,8 +2088,17 @@ static void widen_dashed_figure(GpPath *path, GpPen *pen, int start, int end,
|
|||
break;
|
||||
}
|
||||
|
||||
dash_pattern_scaled = heap_alloc(dash_count * sizeof(REAL));
|
||||
if (!dash_pattern_scaled) return;
|
||||
|
||||
for (i = 0; i < dash_count; i++)
|
||||
dash_pattern_scaled[i] = pen->width * dash_pattern[i];
|
||||
|
||||
tmp_points = heap_alloc_zero((end - start + 2) * sizeof(GpPoint));
|
||||
if (!tmp_points) return; /* FIXME */
|
||||
if (!tmp_points) {
|
||||
heap_free(dash_pattern_scaled);
|
||||
return; /* FIXME */
|
||||
}
|
||||
|
||||
if (!closed)
|
||||
draw_start_cap = 1;
|
||||
|
@ -2040,7 +2145,7 @@ static void widen_dashed_figure(GpPath *path, GpPen *pen, int start, int end,
|
|||
}
|
||||
}
|
||||
|
||||
if (dash_pattern[dash_index] - dash_pos > segment_length - segment_pos)
|
||||
if (dash_pattern_scaled[dash_index] - dash_pos > segment_length - segment_pos)
|
||||
{
|
||||
/* advance to next segment */
|
||||
if ((dash_index % 2) == 0)
|
||||
|
@ -2054,7 +2159,7 @@ static void widen_dashed_figure(GpPath *path, GpPen *pen, int start, int end,
|
|||
else
|
||||
{
|
||||
/* advance to next dash in pattern */
|
||||
segment_pos += dash_pattern[dash_index] - dash_pos;
|
||||
segment_pos += dash_pattern_scaled[dash_index] - dash_pos;
|
||||
dash_pos = 0.0;
|
||||
if (++dash_index == dash_count)
|
||||
dash_index = 0;
|
||||
|
@ -2066,12 +2171,12 @@ static void widen_dashed_figure(GpPath *path, GpPen *pen, int start, int end,
|
|||
if (dash_index % 2 == 0 && num_tmp_points != 0)
|
||||
{
|
||||
/* last dash overflows last segment */
|
||||
tmp_points[num_tmp_points] = path->pathdata.Points[end];
|
||||
widen_open_figure(tmp_points, pen, 0, num_tmp_points,
|
||||
widen_open_figure(tmp_points, pen, 0, num_tmp_points-1,
|
||||
draw_start_cap ? pen->startcap : LineCapFlat, pen->customstart,
|
||||
closed ? LineCapFlat : pen->endcap, pen->customend, last_point);
|
||||
}
|
||||
|
||||
heap_free(dash_pattern_scaled);
|
||||
heap_free(tmp_points);
|
||||
}
|
||||
|
||||
|
@ -2104,10 +2209,10 @@ GpStatus WINGDIPAPI GdipWidenPath(GpPath *path, GpPen *pen, GpMatrix *matrix,
|
|||
{
|
||||
last_point = points;
|
||||
|
||||
if (pen->endcap > LineCapRound)
|
||||
if (pen->endcap > LineCapTriangle)
|
||||
FIXME("unimplemented end cap %x\n", pen->endcap);
|
||||
|
||||
if (pen->startcap > LineCapRound)
|
||||
if (pen->startcap > LineCapTriangle)
|
||||
FIXME("unimplemented start cap %x\n", pen->startcap);
|
||||
|
||||
if (pen->dashcap != DashCapFlat)
|
||||
|
@ -2338,3 +2443,77 @@ GpStatus WINGDIPAPI GdipWindingModeOutline(GpPath *path, GpMatrix *matrix, REAL
|
|||
FIXME("stub: %p, %p, %.2f\n", path, matrix, flatness);
|
||||
return NotImplemented;
|
||||
}
|
||||
|
||||
#define FLAGS_INTPATH 0x4000
|
||||
|
||||
struct path_header
|
||||
{
|
||||
DWORD version;
|
||||
DWORD count;
|
||||
DWORD flags;
|
||||
};
|
||||
|
||||
/* Test to see if the path could be stored as an array of shorts */
|
||||
static BOOL is_integer_path(const GpPath *path)
|
||||
{
|
||||
int i;
|
||||
|
||||
if (!path->pathdata.Count) return FALSE;
|
||||
|
||||
for (i = 0; i < path->pathdata.Count; i++)
|
||||
{
|
||||
short x, y;
|
||||
x = gdip_round(path->pathdata.Points[i].X);
|
||||
y = gdip_round(path->pathdata.Points[i].Y);
|
||||
if (path->pathdata.Points[i].X != (REAL)x || path->pathdata.Points[i].Y != (REAL)y)
|
||||
return FALSE;
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
DWORD write_path_data(GpPath *path, void *data)
|
||||
{
|
||||
struct path_header *header = data;
|
||||
BOOL integer_path = is_integer_path(path);
|
||||
DWORD i, size;
|
||||
BYTE *types;
|
||||
|
||||
size = sizeof(struct path_header) + path->pathdata.Count;
|
||||
if (integer_path)
|
||||
size += sizeof(short[2]) * path->pathdata.Count;
|
||||
else
|
||||
size += sizeof(float[2]) * path->pathdata.Count;
|
||||
size = (size + 3) & ~3;
|
||||
|
||||
if (!data) return size;
|
||||
|
||||
header->version = VERSION_MAGIC2;
|
||||
header->count = path->pathdata.Count;
|
||||
header->flags = integer_path ? FLAGS_INTPATH : 0;
|
||||
|
||||
if (integer_path)
|
||||
{
|
||||
short *points = (short*)(header + 1);
|
||||
for (i = 0; i < path->pathdata.Count; i++)
|
||||
{
|
||||
points[2*i] = path->pathdata.Points[i].X;
|
||||
points[2*i + 1] = path->pathdata.Points[i].Y;
|
||||
}
|
||||
types = (BYTE*)(points + 2*i);
|
||||
}
|
||||
else
|
||||
{
|
||||
float *points = (float*)(header + 1);
|
||||
for (i = 0; i < path->pathdata.Count; i++)
|
||||
{
|
||||
points[2*i] = path->pathdata.Points[i].X;
|
||||
points[2*i + 1] = path->pathdata.Points[i].Y;
|
||||
}
|
||||
types = (BYTE*)(points + 2*i);
|
||||
}
|
||||
|
||||
for (i=0; i<path->pathdata.Count; i++)
|
||||
types[i] = path->pathdata.Types[i];
|
||||
memset(types + i, 0, ((path->pathdata.Count + 3) & ~3) - path->pathdata.Count);
|
||||
return size;
|
||||
}
|
||||
|
|
|
@ -1322,6 +1322,7 @@ GpStatus WINGDIPAPI GdipCloneImage(GpImage *image, GpImage **cloneImage)
|
|||
result->unit = metafile->unit;
|
||||
result->metafile_type = metafile->metafile_type;
|
||||
result->hemf = CopyEnhMetaFileW(metafile->hemf, NULL);
|
||||
list_init(&result->containers);
|
||||
|
||||
if (!result->hemf)
|
||||
{
|
||||
|
@ -4165,7 +4166,7 @@ static GpStatus decode_image_emf(IStream *stream, GpImage **image)
|
|||
}
|
||||
|
||||
typedef GpStatus (*encode_image_func)(GpImage *image, IStream* stream,
|
||||
GDIPCONST CLSID* clsid, GDIPCONST EncoderParameters* params);
|
||||
GDIPCONST EncoderParameters* params);
|
||||
|
||||
typedef GpStatus (*decode_image_func)(IStream *stream, GpImage **image);
|
||||
|
||||
|
@ -4541,31 +4542,31 @@ static GpStatus encode_image_wic(GpImage *image, IStream* stream,
|
|||
}
|
||||
|
||||
static GpStatus encode_image_BMP(GpImage *image, IStream* stream,
|
||||
GDIPCONST CLSID* clsid, GDIPCONST EncoderParameters* params)
|
||||
GDIPCONST EncoderParameters* params)
|
||||
{
|
||||
return encode_image_wic(image, stream, &GUID_ContainerFormatBmp, params);
|
||||
}
|
||||
|
||||
static GpStatus encode_image_tiff(GpImage *image, IStream* stream,
|
||||
GDIPCONST CLSID* clsid, GDIPCONST EncoderParameters* params)
|
||||
GDIPCONST EncoderParameters* params)
|
||||
{
|
||||
return encode_image_wic(image, stream, &GUID_ContainerFormatTiff, params);
|
||||
}
|
||||
|
||||
static GpStatus encode_image_png(GpImage *image, IStream* stream,
|
||||
GDIPCONST CLSID* clsid, GDIPCONST EncoderParameters* params)
|
||||
GpStatus encode_image_png(GpImage *image, IStream* stream,
|
||||
GDIPCONST EncoderParameters* params)
|
||||
{
|
||||
return encode_image_wic(image, stream, &GUID_ContainerFormatPng, params);
|
||||
}
|
||||
|
||||
static GpStatus encode_image_jpeg(GpImage *image, IStream* stream,
|
||||
GDIPCONST CLSID* clsid, GDIPCONST EncoderParameters* params)
|
||||
GDIPCONST EncoderParameters* params)
|
||||
{
|
||||
return encode_image_wic(image, stream, &GUID_ContainerFormatJpeg, params);
|
||||
}
|
||||
|
||||
static GpStatus encode_image_gif(GpImage *image, IStream* stream,
|
||||
GDIPCONST CLSID* clsid, GDIPCONST EncoderParameters* params)
|
||||
GDIPCONST EncoderParameters* params)
|
||||
{
|
||||
return encode_image_wic(image, stream, &GUID_ContainerFormatGif, params);
|
||||
}
|
||||
|
@ -4595,7 +4596,7 @@ GpStatus WINGDIPAPI GdipSaveImageToStream(GpImage *image, IStream* stream,
|
|||
if (encode_image == NULL)
|
||||
return UnknownImageFormat;
|
||||
|
||||
stat = encode_image(image, stream, clsid, params);
|
||||
stat = encode_image(image, stream, params);
|
||||
|
||||
return stat;
|
||||
}
|
||||
|
|
|
@ -184,6 +184,17 @@ GpStatus WINGDIPAPI GdipInvertMatrix(GpMatrix *matrix)
|
|||
if(!invertible)
|
||||
return InvalidParameter;
|
||||
|
||||
/* optimize inverting simple scaling and translation matrices */
|
||||
if(matrix->matrix[1] == 0 && matrix->matrix[2] == 0)
|
||||
{
|
||||
matrix->matrix[4] = -matrix->matrix[4] / matrix->matrix[0];
|
||||
matrix->matrix[5] = -matrix->matrix[5] / matrix->matrix[3];
|
||||
matrix->matrix[0] = 1 / matrix->matrix[0];
|
||||
matrix->matrix[3] = 1 / matrix->matrix[3];
|
||||
|
||||
return Ok;
|
||||
}
|
||||
|
||||
det = matrix_det(matrix);
|
||||
|
||||
copy = *matrix;
|
||||
|
@ -205,7 +216,10 @@ GpStatus WINGDIPAPI GdipIsMatrixInvertible(GDIPCONST GpMatrix *matrix, BOOL *res
|
|||
if(!matrix || !result)
|
||||
return InvalidParameter;
|
||||
|
||||
*result = (fabs(matrix_det(matrix)) >= 1e-5);
|
||||
if(matrix->matrix[1] == 0 && matrix->matrix[2] == 0)
|
||||
*result = matrix->matrix[0] != 0 && matrix->matrix[3] != 0;
|
||||
else
|
||||
*result = (fabs(matrix_det(matrix)) >= 1e-5);
|
||||
|
||||
return Ok;
|
||||
}
|
||||
|
|
|
@ -18,6 +18,17 @@
|
|||
|
||||
#include "gdiplus_private.h"
|
||||
|
||||
#include <assert.h>
|
||||
#include <ole2.h>
|
||||
|
||||
typedef struct EmfPlusARGB
|
||||
{
|
||||
BYTE Blue;
|
||||
BYTE Green;
|
||||
BYTE Red;
|
||||
BYTE Alpha;
|
||||
} EmfPlusARGB;
|
||||
|
||||
typedef struct EmfPlusRecordHeader
|
||||
{
|
||||
WORD Type;
|
||||
|
@ -132,6 +143,216 @@ typedef struct container
|
|||
GpRegion *clip;
|
||||
} container;
|
||||
|
||||
enum PenDataFlags
|
||||
{
|
||||
PenDataTransform = 0x0001,
|
||||
PenDataStartCap = 0x0002,
|
||||
PenDataEndCap = 0x0004,
|
||||
PenDataJoin = 0x0008,
|
||||
PenDataMiterLimit = 0x0010,
|
||||
PenDataLineStyle = 0x0020,
|
||||
PenDataDashedLineCap = 0x0040,
|
||||
PenDataDashedLineOffset = 0x0080,
|
||||
PenDataDashedLine = 0x0100,
|
||||
PenDataNonCenter = 0x0200,
|
||||
PenDataCompoundLine = 0x0400,
|
||||
PenDataCustomStartCap = 0x0800,
|
||||
PenDataCustomEndCap = 0x1000
|
||||
};
|
||||
|
||||
typedef struct EmfPlusTransformMatrix
|
||||
{
|
||||
REAL TransformMatrix[6];
|
||||
} EmfPlusTransformMatrix;
|
||||
|
||||
enum LineStyle
|
||||
{
|
||||
LineStyleSolid,
|
||||
LineStyleDash,
|
||||
LineStyleDot,
|
||||
LineStyleDashDot,
|
||||
LineStyleDashDotDot,
|
||||
LineStyleCustom
|
||||
};
|
||||
|
||||
typedef struct EmfPlusPenData
|
||||
{
|
||||
DWORD PenDataFlags;
|
||||
DWORD PenUnit;
|
||||
REAL PenWidth;
|
||||
BYTE OptionalData[1];
|
||||
} EmfPlusPenData;
|
||||
|
||||
typedef struct EmfPlusSolidBrushData
|
||||
{
|
||||
EmfPlusARGB SolidColor;
|
||||
} EmfPlusSolidBrushData;
|
||||
|
||||
typedef struct EmfPlusBrush
|
||||
{
|
||||
DWORD Version;
|
||||
DWORD Type;
|
||||
union {
|
||||
EmfPlusSolidBrushData solid;
|
||||
} BrushData;
|
||||
} EmfPlusBrush;
|
||||
|
||||
typedef struct EmfPlusPen
|
||||
{
|
||||
DWORD Version;
|
||||
DWORD Type;
|
||||
/* EmfPlusPenData */
|
||||
/* EmfPlusBrush */
|
||||
BYTE data[1];
|
||||
} EmfPlusPen;
|
||||
|
||||
typedef struct EmfPlusPath
|
||||
{
|
||||
DWORD Version;
|
||||
DWORD PathPointCount;
|
||||
DWORD PathPointFlags;
|
||||
/* PathPoints[] */
|
||||
/* PathPointTypes[] */
|
||||
/* AlignmentPadding */
|
||||
BYTE data[1];
|
||||
} EmfPlusPath;
|
||||
|
||||
typedef struct EmfPlusRegion
|
||||
{
|
||||
DWORD Version;
|
||||
DWORD RegionNodeCount;
|
||||
BYTE RegionNode[1];
|
||||
} EmfPlusRegion;
|
||||
|
||||
typedef enum
|
||||
{
|
||||
BitmapDataTypePixel,
|
||||
BitmapDataTypeCompressed,
|
||||
} BitmapDataType;
|
||||
|
||||
typedef struct EmfPlusBitmap
|
||||
{
|
||||
DWORD Width;
|
||||
DWORD Height;
|
||||
DWORD Stride;
|
||||
DWORD PixelFormat;
|
||||
DWORD Type;
|
||||
BYTE BitmapData[1];
|
||||
} EmfPlusBitmap;
|
||||
|
||||
typedef struct EmfPlusMetafile
|
||||
{
|
||||
DWORD Type;
|
||||
DWORD MetafileDataSize;
|
||||
BYTE MetafileData[1];
|
||||
} EmfPlusMetafile;
|
||||
|
||||
typedef enum ImageDataType
|
||||
{
|
||||
ImageDataTypeUnknown,
|
||||
ImageDataTypeBitmap,
|
||||
ImageDataTypeMetafile,
|
||||
} ImageDataType;
|
||||
|
||||
typedef struct EmfPlusImage
|
||||
{
|
||||
DWORD Version;
|
||||
ImageDataType Type;
|
||||
union
|
||||
{
|
||||
EmfPlusBitmap bitmap;
|
||||
EmfPlusMetafile metafile;
|
||||
} ImageData;
|
||||
} EmfPlusImage;
|
||||
|
||||
typedef struct EmfPlusImageAttributes
|
||||
{
|
||||
DWORD Version;
|
||||
DWORD Reserved1;
|
||||
DWORD WrapMode;
|
||||
EmfPlusARGB ClampColor;
|
||||
DWORD ObjectClamp;
|
||||
DWORD Reserved2;
|
||||
} EmfPlusImageAttributes;
|
||||
|
||||
typedef enum ObjectType
|
||||
{
|
||||
ObjectTypeInvalid,
|
||||
ObjectTypeBrush,
|
||||
ObjectTypePen,
|
||||
ObjectTypePath,
|
||||
ObjectTypeRegion,
|
||||
ObjectTypeImage,
|
||||
ObjectTypeFont,
|
||||
ObjectTypeStringFormat,
|
||||
ObjectTypeImageAttributes,
|
||||
ObjectTypeCustomLineCap,
|
||||
} ObjectType;
|
||||
|
||||
typedef struct EmfPlusObject
|
||||
{
|
||||
EmfPlusRecordHeader Header;
|
||||
union
|
||||
{
|
||||
EmfPlusBrush brush;
|
||||
EmfPlusPen pen;
|
||||
EmfPlusPath path;
|
||||
EmfPlusRegion region;
|
||||
EmfPlusImage image;
|
||||
EmfPlusImageAttributes image_attributes;
|
||||
} ObjectData;
|
||||
} EmfPlusObject;
|
||||
|
||||
typedef struct EmfPlusRectF
|
||||
{
|
||||
float X;
|
||||
float Y;
|
||||
float Width;
|
||||
float Height;
|
||||
} EmfPlusRectF;
|
||||
|
||||
typedef struct EmfPlusPointF
|
||||
{
|
||||
float X;
|
||||
float Y;
|
||||
} EmfPlusPointF;
|
||||
|
||||
typedef struct EmfPlusDrawImagePoints
|
||||
{
|
||||
EmfPlusRecordHeader Header;
|
||||
DWORD ImageAttributesID;
|
||||
DWORD SrcUnit;
|
||||
EmfPlusRectF SrcRect;
|
||||
DWORD count;
|
||||
union
|
||||
{
|
||||
/*EmfPlusPointR pointR;
|
||||
EmfPlusPoint point;*/
|
||||
EmfPlusPointF pointF;
|
||||
} PointData[3];
|
||||
} EmfPlusDrawImagePoints;
|
||||
|
||||
typedef struct EmfPlusDrawPath
|
||||
{
|
||||
EmfPlusRecordHeader Header;
|
||||
DWORD PenId;
|
||||
} EmfPlusDrawPath;
|
||||
|
||||
typedef struct EmfPlusFillPath
|
||||
{
|
||||
EmfPlusRecordHeader Header;
|
||||
union
|
||||
{
|
||||
DWORD BrushId;
|
||||
EmfPlusARGB Color;
|
||||
} data;
|
||||
} EmfPlusFillPath;
|
||||
|
||||
static DWORD METAFILE_AddObjectId(GpMetafile *metafile)
|
||||
{
|
||||
return (metafile->next_object_id++) % 64;
|
||||
}
|
||||
|
||||
static GpStatus METAFILE_AllocateRecord(GpMetafile *metafile, DWORD size, void **result)
|
||||
{
|
||||
DWORD size_needed;
|
||||
|
@ -178,6 +399,12 @@ static GpStatus METAFILE_AllocateRecord(GpMetafile *metafile, DWORD size, void *
|
|||
return Ok;
|
||||
}
|
||||
|
||||
static void METAFILE_RemoveLastRecord(GpMetafile *metafile, EmfPlusRecordHeader *record)
|
||||
{
|
||||
assert(metafile->comment_data + metafile->comment_data_length == (BYTE*)record + record->Size);
|
||||
metafile->comment_data_length -= record->Size;
|
||||
}
|
||||
|
||||
static void METAFILE_WriteRecords(GpMetafile *metafile)
|
||||
{
|
||||
if (metafile->comment_data_length > 4)
|
||||
|
@ -206,7 +433,7 @@ static GpStatus METAFILE_WriteHeader(GpMetafile *metafile, HDC hdc)
|
|||
else
|
||||
header->Header.Flags = 0;
|
||||
|
||||
header->Version = 0xDBC01002;
|
||||
header->Version = VERSION_MAGIC2;
|
||||
|
||||
if (GetDeviceCaps(hdc, TECHNOLOGY) == DT_RASDISPLAY)
|
||||
header->EmfPlusFlags = 1;
|
||||
|
@ -606,6 +833,53 @@ GpStatus METAFILE_SetClipRect(GpMetafile* metafile, REAL x, REAL y, REAL width,
|
|||
return Ok;
|
||||
}
|
||||
|
||||
static GpStatus METAFILE_AddRegionObject(GpMetafile *metafile, GpRegion *region, DWORD *id)
|
||||
{
|
||||
EmfPlusObject *object_record;
|
||||
DWORD size;
|
||||
GpStatus stat;
|
||||
|
||||
*id = -1;
|
||||
if (metafile->metafile_type != MetafileTypeEmfPlusOnly && metafile->metafile_type != MetafileTypeEmfPlusDual)
|
||||
return Ok;
|
||||
|
||||
size = write_region_data(region, NULL);
|
||||
stat = METAFILE_AllocateRecord(metafile,
|
||||
FIELD_OFFSET(EmfPlusObject, ObjectData.region) + size, (void**)&object_record);
|
||||
if (stat != Ok) return stat;
|
||||
|
||||
*id = METAFILE_AddObjectId(metafile);
|
||||
object_record->Header.Type = EmfPlusRecordTypeObject;
|
||||
object_record->Header.Flags = *id | ObjectTypeRegion << 8;
|
||||
write_region_data(region, &object_record->ObjectData.region);
|
||||
return Ok;
|
||||
}
|
||||
|
||||
GpStatus METAFILE_SetClipRegion(GpMetafile* metafile, GpRegion* region, CombineMode mode)
|
||||
{
|
||||
EmfPlusRecordHeader *record;
|
||||
DWORD region_id;
|
||||
GpStatus stat;
|
||||
|
||||
if (metafile->metafile_type == MetafileTypeEmf)
|
||||
{
|
||||
FIXME("stub!\n");
|
||||
return NotImplemented;
|
||||
}
|
||||
|
||||
stat = METAFILE_AddRegionObject(metafile, region, ®ion_id);
|
||||
if (stat != Ok) return stat;
|
||||
|
||||
stat = METAFILE_AllocateRecord(metafile, sizeof(*record), (void**)&record);
|
||||
if (stat != Ok) return stat;
|
||||
|
||||
record->Type = EmfPlusRecordTypeSetClipRegion;
|
||||
record->Flags = region_id | mode << 8;
|
||||
|
||||
METAFILE_WriteRecords(metafile);
|
||||
return Ok;
|
||||
}
|
||||
|
||||
GpStatus METAFILE_SetPageTransform(GpMetafile* metafile, GpUnit unit, REAL scale)
|
||||
{
|
||||
if (metafile->metafile_type == MetafileTypeEmfPlusOnly || metafile->metafile_type == MetafileTypeEmfPlusDual)
|
||||
|
@ -1127,8 +1401,6 @@ GpStatus WINGDIPAPI GdipPlayMetafileRecord(GDIPCONST GpMetafile *metafile,
|
|||
/* regular EMF record */
|
||||
if (metafile->playback_dc)
|
||||
{
|
||||
ENHMETARECORD *record;
|
||||
|
||||
switch (recordType)
|
||||
{
|
||||
case EMR_SETMAPMODE:
|
||||
|
@ -1173,24 +1445,27 @@ GpStatus WINGDIPAPI GdipPlayMetafileRecord(GDIPCONST GpMetafile *metafile,
|
|||
return Ok;
|
||||
}
|
||||
default:
|
||||
{
|
||||
ENHMETARECORD *record = heap_alloc_zero(dataSize + 8);
|
||||
|
||||
if (record)
|
||||
{
|
||||
record->iType = recordType;
|
||||
record->nSize = dataSize + 8;
|
||||
memcpy(record->dParm, data, dataSize);
|
||||
|
||||
if(PlayEnhMetaFileRecord(metafile->playback_dc, metafile->handle_table,
|
||||
record, metafile->handle_count) == 0)
|
||||
ERR("PlayEnhMetaFileRecord failed\n");
|
||||
|
||||
heap_free(record);
|
||||
}
|
||||
else
|
||||
return OutOfMemory;
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
record = heap_alloc_zero(dataSize + 8);
|
||||
|
||||
if (record)
|
||||
{
|
||||
record->iType = recordType;
|
||||
record->nSize = dataSize + 8;
|
||||
memcpy(record->dParm, data, dataSize);
|
||||
|
||||
PlayEnhMetaFileRecord(metafile->playback_dc, metafile->handle_table,
|
||||
record, metafile->handle_count);
|
||||
|
||||
heap_free(record);
|
||||
}
|
||||
else
|
||||
return OutOfMemory;
|
||||
}
|
||||
}
|
||||
else
|
||||
|
@ -1816,7 +2091,7 @@ GpStatus WINGDIPAPI GdipGetMetafileHeaderFromMetafile(GpMetafile * metafile,
|
|||
else
|
||||
{
|
||||
memset(header, 0, sizeof(*header));
|
||||
header->Version = 0xdbc01002;
|
||||
header->Version = VERSION_MAGIC2;
|
||||
}
|
||||
|
||||
header->Type = metafile->metafile_type;
|
||||
|
@ -2065,13 +2340,20 @@ GpStatus WINGDIPAPI GdipCreateMetafileFromWmf(HMETAFILE hwmf, BOOL delete,
|
|||
GpStatus WINGDIPAPI GdipCreateMetafileFromWmfFile(GDIPCONST WCHAR *file,
|
||||
GDIPCONST WmfPlaceableFileHeader * placeable, GpMetafile **metafile)
|
||||
{
|
||||
HMETAFILE hmf = GetMetaFileW(file);
|
||||
HMETAFILE hmf;
|
||||
HENHMETAFILE emf;
|
||||
|
||||
TRACE("(%s, %p, %p)\n", debugstr_w(file), placeable, metafile);
|
||||
|
||||
if(!hmf) return InvalidParameter;
|
||||
hmf = GetMetaFileW(file);
|
||||
if(hmf)
|
||||
return GdipCreateMetafileFromWmf(hmf, TRUE, placeable, metafile);
|
||||
|
||||
return GdipCreateMetafileFromWmf(hmf, TRUE, placeable, metafile);
|
||||
emf = GetEnhMetaFileW(file);
|
||||
if(emf)
|
||||
return GdipCreateMetafileFromEmf(emf, TRUE, metafile);
|
||||
|
||||
return GenericError;
|
||||
}
|
||||
|
||||
GpStatus WINGDIPAPI GdipCreateMetafileFromFile(GDIPCONST WCHAR *file,
|
||||
|
@ -2185,3 +2467,585 @@ GpStatus WINGDIPAPI GdipConvertToEmfPlusToFile(const GpGraphics* refGraphics,
|
|||
FIXME("stub: %p, %p, %p, %p, %u, %p, %p\n", refGraphics, metafile, conversionSuccess, filename, emfType, description, out_metafile);
|
||||
return NotImplemented;
|
||||
}
|
||||
|
||||
static GpStatus METAFILE_CreateCompressedImageStream(GpImage *image, IStream **stream, DWORD *size)
|
||||
{
|
||||
LARGE_INTEGER zero;
|
||||
STATSTG statstg;
|
||||
GpStatus stat;
|
||||
HRESULT hr;
|
||||
|
||||
*size = 0;
|
||||
|
||||
hr = CreateStreamOnHGlobal(NULL, TRUE, stream);
|
||||
if (FAILED(hr)) return hresult_to_status(hr);
|
||||
|
||||
stat = encode_image_png(image, *stream, NULL);
|
||||
if (stat != Ok)
|
||||
{
|
||||
IStream_Release(*stream);
|
||||
return stat;
|
||||
}
|
||||
|
||||
hr = IStream_Stat(*stream, &statstg, 1);
|
||||
if (FAILED(hr))
|
||||
{
|
||||
IStream_Release(*stream);
|
||||
return hresult_to_status(hr);
|
||||
}
|
||||
*size = statstg.cbSize.u.LowPart;
|
||||
|
||||
zero.QuadPart = 0;
|
||||
hr = IStream_Seek(*stream, zero, STREAM_SEEK_SET, NULL);
|
||||
if (FAILED(hr))
|
||||
{
|
||||
IStream_Release(*stream);
|
||||
return hresult_to_status(hr);
|
||||
}
|
||||
|
||||
return Ok;
|
||||
}
|
||||
|
||||
static GpStatus METAFILE_FillEmfPlusBitmap(EmfPlusBitmap *record, IStream *stream, DWORD size)
|
||||
{
|
||||
HRESULT hr;
|
||||
|
||||
record->Width = 0;
|
||||
record->Height = 0;
|
||||
record->Stride = 0;
|
||||
record->PixelFormat = 0;
|
||||
record->Type = BitmapDataTypeCompressed;
|
||||
|
||||
hr = IStream_Read(stream, record->BitmapData, size, NULL);
|
||||
if (FAILED(hr)) return hresult_to_status(hr);
|
||||
return Ok;
|
||||
}
|
||||
|
||||
static GpStatus METAFILE_AddImageObject(GpMetafile *metafile, GpImage *image, DWORD *id)
|
||||
{
|
||||
EmfPlusObject *object_record;
|
||||
GpStatus stat;
|
||||
DWORD size;
|
||||
|
||||
*id = -1;
|
||||
|
||||
if (metafile->metafile_type != MetafileTypeEmfPlusOnly && metafile->metafile_type != MetafileTypeEmfPlusDual)
|
||||
return Ok;
|
||||
|
||||
if (image->type == ImageTypeBitmap)
|
||||
{
|
||||
IStream *stream;
|
||||
DWORD aligned_size;
|
||||
|
||||
stat = METAFILE_CreateCompressedImageStream(image, &stream, &size);
|
||||
if (stat != Ok) return stat;
|
||||
aligned_size = (size + 3) & ~3;
|
||||
|
||||
stat = METAFILE_AllocateRecord(metafile,
|
||||
FIELD_OFFSET(EmfPlusObject, ObjectData.image.ImageData.bitmap.BitmapData[aligned_size]),
|
||||
(void**)&object_record);
|
||||
if (stat != Ok)
|
||||
{
|
||||
IStream_Release(stream);
|
||||
return stat;
|
||||
}
|
||||
memset(object_record->ObjectData.image.ImageData.bitmap.BitmapData + size, 0, aligned_size - size);
|
||||
|
||||
*id = METAFILE_AddObjectId(metafile);
|
||||
object_record->Header.Type = EmfPlusRecordTypeObject;
|
||||
object_record->Header.Flags = *id | ObjectTypeImage << 8;
|
||||
object_record->ObjectData.image.Version = VERSION_MAGIC2;
|
||||
object_record->ObjectData.image.Type = ImageDataTypeBitmap;
|
||||
|
||||
stat = METAFILE_FillEmfPlusBitmap(&object_record->ObjectData.image.ImageData.bitmap, stream, size);
|
||||
IStream_Release(stream);
|
||||
if (stat != Ok) METAFILE_RemoveLastRecord(metafile, &object_record->Header);
|
||||
return stat;
|
||||
}
|
||||
else if (image->type == ImageTypeMetafile)
|
||||
{
|
||||
HENHMETAFILE hemf = ((GpMetafile*)image)->hemf;
|
||||
EmfPlusMetafile *metafile_record;
|
||||
|
||||
if (!hemf) return InvalidParameter;
|
||||
|
||||
size = GetEnhMetaFileBits(hemf, 0, NULL);
|
||||
if (!size) return GenericError;
|
||||
|
||||
stat = METAFILE_AllocateRecord(metafile,
|
||||
FIELD_OFFSET(EmfPlusObject, ObjectData.image.ImageData.metafile.MetafileData[size]),
|
||||
(void**)&object_record);
|
||||
if (stat != Ok) return stat;
|
||||
|
||||
*id = METAFILE_AddObjectId(metafile);
|
||||
object_record->Header.Type = EmfPlusRecordTypeObject;
|
||||
object_record->Header.Flags = *id | ObjectTypeImage << 8;
|
||||
object_record->ObjectData.image.Version = VERSION_MAGIC2;
|
||||
object_record->ObjectData.image.Type = ImageDataTypeMetafile;
|
||||
metafile_record = &object_record->ObjectData.image.ImageData.metafile;
|
||||
metafile_record->Type = ((GpMetafile*)image)->metafile_type;
|
||||
metafile_record->MetafileDataSize = size;
|
||||
if (GetEnhMetaFileBits(hemf, size, metafile_record->MetafileData) != size)
|
||||
{
|
||||
METAFILE_RemoveLastRecord(metafile, &object_record->Header);
|
||||
return GenericError;
|
||||
}
|
||||
return Ok;
|
||||
}
|
||||
else
|
||||
{
|
||||
FIXME("not supported image type (%d)\n", image->type);
|
||||
return NotImplemented;
|
||||
}
|
||||
}
|
||||
|
||||
static GpStatus METAFILE_AddImageAttributesObject(GpMetafile *metafile, const GpImageAttributes *attrs, DWORD *id)
|
||||
{
|
||||
EmfPlusObject *object_record;
|
||||
EmfPlusImageAttributes *attrs_record;
|
||||
GpStatus stat;
|
||||
|
||||
*id = -1;
|
||||
|
||||
if (metafile->metafile_type != MetafileTypeEmfPlusOnly && metafile->metafile_type != MetafileTypeEmfPlusDual)
|
||||
return Ok;
|
||||
|
||||
if (!attrs)
|
||||
return Ok;
|
||||
|
||||
stat = METAFILE_AllocateRecord(metafile,
|
||||
FIELD_OFFSET(EmfPlusObject, ObjectData.image_attributes) + sizeof(EmfPlusImageAttributes),
|
||||
(void**)&object_record);
|
||||
if (stat != Ok) return stat;
|
||||
|
||||
*id = METAFILE_AddObjectId(metafile);
|
||||
object_record->Header.Type = EmfPlusRecordTypeObject;
|
||||
object_record->Header.Flags = *id | (ObjectTypeImageAttributes << 8);
|
||||
attrs_record = &object_record->ObjectData.image_attributes;
|
||||
attrs_record->Version = VERSION_MAGIC2;
|
||||
attrs_record->Reserved1 = 0;
|
||||
attrs_record->WrapMode = attrs->wrap;
|
||||
attrs_record->ClampColor.Blue = attrs->outside_color & 0xff;
|
||||
attrs_record->ClampColor.Green = (attrs->outside_color >> 8) & 0xff;
|
||||
attrs_record->ClampColor.Red = (attrs->outside_color >> 16) & 0xff;
|
||||
attrs_record->ClampColor.Alpha = attrs->outside_color >> 24;
|
||||
attrs_record->ObjectClamp = attrs->clamp;
|
||||
attrs_record->Reserved2 = 0;
|
||||
return Ok;
|
||||
}
|
||||
|
||||
GpStatus METAFILE_DrawImagePointsRect(GpMetafile *metafile, GpImage *image,
|
||||
GDIPCONST GpPointF *points, INT count, REAL srcx, REAL srcy, REAL srcwidth,
|
||||
REAL srcheight, GpUnit srcUnit, GDIPCONST GpImageAttributes* imageAttributes,
|
||||
DrawImageAbort callback, VOID *callbackData)
|
||||
{
|
||||
EmfPlusDrawImagePoints *draw_image_record;
|
||||
DWORD image_id, attributes_id;
|
||||
GpStatus stat;
|
||||
|
||||
if (count != 3) return InvalidParameter;
|
||||
|
||||
if (metafile->metafile_type == MetafileTypeEmf)
|
||||
{
|
||||
FIXME("MetafileTypeEmf metafiles not supported\n");
|
||||
return NotImplemented;
|
||||
}
|
||||
else
|
||||
FIXME("semi-stub\n");
|
||||
|
||||
if (!imageAttributes)
|
||||
{
|
||||
stat = METAFILE_AddImageObject(metafile, image, &image_id);
|
||||
}
|
||||
else if (image->type == ImageTypeBitmap)
|
||||
{
|
||||
INT width = ((GpBitmap*)image)->width;
|
||||
INT height = ((GpBitmap*)image)->height;
|
||||
GpGraphics *graphics;
|
||||
GpBitmap *bitmap;
|
||||
|
||||
stat = GdipCreateBitmapFromScan0(width, height,
|
||||
0, PixelFormat32bppARGB, NULL, &bitmap);
|
||||
if (stat != Ok) return stat;
|
||||
|
||||
stat = GdipGetImageGraphicsContext((GpImage*)bitmap, &graphics);
|
||||
if (stat != Ok)
|
||||
{
|
||||
GdipDisposeImage((GpImage*)bitmap);
|
||||
return stat;
|
||||
}
|
||||
|
||||
stat = GdipDrawImageRectRectI(graphics, image, 0, 0, width, height,
|
||||
0, 0, width, height, UnitPixel, imageAttributes, NULL, NULL);
|
||||
GdipDeleteGraphics(graphics);
|
||||
if (stat != Ok)
|
||||
{
|
||||
GdipDisposeImage((GpImage*)bitmap);
|
||||
return stat;
|
||||
}
|
||||
|
||||
stat = METAFILE_AddImageObject(metafile, (GpImage*)bitmap, &image_id);
|
||||
GdipDisposeImage((GpImage*)bitmap);
|
||||
}
|
||||
else
|
||||
{
|
||||
FIXME("imageAttributes not supported (image type %d)\n", image->type);
|
||||
return NotImplemented;
|
||||
}
|
||||
if (stat != Ok) return stat;
|
||||
|
||||
stat = METAFILE_AddImageAttributesObject(metafile, imageAttributes, &attributes_id);
|
||||
if (stat != Ok) return stat;
|
||||
|
||||
stat = METAFILE_AllocateRecord(metafile, sizeof(EmfPlusDrawImagePoints), (void**)&draw_image_record);
|
||||
if (stat != Ok) return stat;
|
||||
draw_image_record->Header.Type = EmfPlusRecordTypeDrawImagePoints;
|
||||
draw_image_record->Header.Flags = image_id;
|
||||
draw_image_record->ImageAttributesID = attributes_id;
|
||||
draw_image_record->SrcUnit = UnitPixel;
|
||||
draw_image_record->SrcRect.X = units_to_pixels(srcx, srcUnit, metafile->image.xres);
|
||||
draw_image_record->SrcRect.Y = units_to_pixels(srcy, srcUnit, metafile->image.yres);
|
||||
draw_image_record->SrcRect.Width = units_to_pixels(srcwidth, srcUnit, metafile->image.xres);
|
||||
draw_image_record->SrcRect.Height = units_to_pixels(srcheight, srcUnit, metafile->image.yres);
|
||||
draw_image_record->count = 3;
|
||||
draw_image_record->PointData[0].pointF.X = points[0].X;
|
||||
draw_image_record->PointData[0].pointF.Y = points[0].Y;
|
||||
draw_image_record->PointData[1].pointF.X = points[1].X;
|
||||
draw_image_record->PointData[1].pointF.Y = points[1].Y;
|
||||
draw_image_record->PointData[2].pointF.X = points[2].X;
|
||||
draw_image_record->PointData[2].pointF.Y = points[2].Y;
|
||||
METAFILE_WriteRecords(metafile);
|
||||
return Ok;
|
||||
}
|
||||
|
||||
GpStatus METAFILE_AddSimpleProperty(GpMetafile *metafile, SHORT prop, SHORT val)
|
||||
{
|
||||
EmfPlusRecordHeader *record;
|
||||
GpStatus stat;
|
||||
|
||||
if (metafile->metafile_type != MetafileTypeEmfPlusOnly && metafile->metafile_type != MetafileTypeEmfPlusDual)
|
||||
return Ok;
|
||||
|
||||
stat = METAFILE_AllocateRecord(metafile, sizeof(*record), (void**)&record);
|
||||
if (stat != Ok) return stat;
|
||||
|
||||
record->Type = prop;
|
||||
record->Flags = val;
|
||||
|
||||
METAFILE_WriteRecords(metafile);
|
||||
return Ok;
|
||||
}
|
||||
|
||||
static GpStatus METAFILE_AddPathObject(GpMetafile *metafile, GpPath *path, DWORD *id)
|
||||
{
|
||||
EmfPlusObject *object_record;
|
||||
GpStatus stat;
|
||||
DWORD size;
|
||||
|
||||
*id = -1;
|
||||
if (metafile->metafile_type != MetafileTypeEmfPlusOnly && metafile->metafile_type != MetafileTypeEmfPlusDual)
|
||||
return Ok;
|
||||
|
||||
size = write_path_data(path, NULL);
|
||||
stat = METAFILE_AllocateRecord(metafile,
|
||||
FIELD_OFFSET(EmfPlusObject, ObjectData.path) + size,
|
||||
(void**)&object_record);
|
||||
if (stat != Ok) return stat;
|
||||
|
||||
*id = METAFILE_AddObjectId(metafile);
|
||||
object_record->Header.Type = EmfPlusRecordTypeObject;
|
||||
object_record->Header.Flags = *id | ObjectTypePath << 8;
|
||||
write_path_data(path, &object_record->ObjectData.path);
|
||||
return Ok;
|
||||
}
|
||||
|
||||
static GpStatus METAFILE_PrepareBrushData(GpBrush *brush, DWORD *size)
|
||||
{
|
||||
if (brush->bt == BrushTypeSolidColor)
|
||||
{
|
||||
*size = FIELD_OFFSET(EmfPlusBrush, BrushData.solid) + sizeof(EmfPlusSolidBrushData);
|
||||
return Ok;
|
||||
}
|
||||
|
||||
FIXME("unsupported brush type: %d\n", brush->bt);
|
||||
return NotImplemented;
|
||||
}
|
||||
|
||||
static void METAFILE_FillBrushData(GpBrush *brush, EmfPlusBrush *data)
|
||||
{
|
||||
if (brush->bt == BrushTypeSolidColor)
|
||||
{
|
||||
GpSolidFill *solid = (GpSolidFill*)brush;
|
||||
|
||||
data->Version = VERSION_MAGIC2;
|
||||
data->Type = solid->brush.bt;
|
||||
data->BrushData.solid.SolidColor.Blue = solid->color & 0xff;
|
||||
data->BrushData.solid.SolidColor.Green = (solid->color >> 8) & 0xff;
|
||||
data->BrushData.solid.SolidColor.Red = (solid->color >> 16) & 0xff;
|
||||
data->BrushData.solid.SolidColor.Alpha = solid->color >> 24;
|
||||
}
|
||||
}
|
||||
|
||||
static GpStatus METAFILE_AddPenObject(GpMetafile *metafile, GpPen *pen, DWORD *id)
|
||||
{
|
||||
DWORD i, data_flags, pen_data_size, brush_size;
|
||||
EmfPlusObject *object_record;
|
||||
EmfPlusPenData *pen_data;
|
||||
GpStatus stat;
|
||||
BOOL result;
|
||||
|
||||
*id = -1;
|
||||
if (metafile->metafile_type != MetafileTypeEmfPlusOnly && metafile->metafile_type != MetafileTypeEmfPlusDual)
|
||||
return Ok;
|
||||
|
||||
data_flags = 0;
|
||||
pen_data_size = FIELD_OFFSET(EmfPlusPenData, OptionalData);
|
||||
|
||||
GdipIsMatrixIdentity(&pen->transform, &result);
|
||||
if (!result)
|
||||
{
|
||||
data_flags |= PenDataTransform;
|
||||
pen_data_size += sizeof(EmfPlusTransformMatrix);
|
||||
}
|
||||
if (pen->startcap != LineCapFlat)
|
||||
{
|
||||
data_flags |= PenDataStartCap;
|
||||
pen_data_size += sizeof(DWORD);
|
||||
}
|
||||
if (pen->endcap != LineCapFlat)
|
||||
{
|
||||
data_flags |= PenDataEndCap;
|
||||
pen_data_size += sizeof(DWORD);
|
||||
}
|
||||
if (pen->join != LineJoinMiter)
|
||||
{
|
||||
data_flags |= PenDataJoin;
|
||||
pen_data_size += sizeof(DWORD);
|
||||
}
|
||||
if (pen->miterlimit != 10.0)
|
||||
{
|
||||
data_flags |= PenDataMiterLimit;
|
||||
pen_data_size += sizeof(REAL);
|
||||
}
|
||||
if (pen->style != GP_DEFAULT_PENSTYLE)
|
||||
{
|
||||
data_flags |= PenDataLineStyle;
|
||||
pen_data_size += sizeof(DWORD);
|
||||
}
|
||||
if (pen->dashcap != DashCapFlat)
|
||||
{
|
||||
data_flags |= PenDataDashedLineCap;
|
||||
pen_data_size += sizeof(DWORD);
|
||||
}
|
||||
data_flags |= PenDataDashedLineOffset;
|
||||
pen_data_size += sizeof(REAL);
|
||||
if (pen->numdashes)
|
||||
{
|
||||
data_flags |= PenDataDashedLine;
|
||||
pen_data_size += sizeof(DWORD) + pen->numdashes*sizeof(REAL);
|
||||
}
|
||||
if (pen->align != PenAlignmentCenter)
|
||||
{
|
||||
data_flags |= PenDataNonCenter;
|
||||
pen_data_size += sizeof(DWORD);
|
||||
}
|
||||
/* TODO: Add support for PenDataCompoundLine */
|
||||
if (pen->customstart)
|
||||
{
|
||||
FIXME("ignoring custom start cup\n");
|
||||
}
|
||||
if (pen->customend)
|
||||
{
|
||||
FIXME("ignoring custom end cup\n");
|
||||
}
|
||||
|
||||
stat = METAFILE_PrepareBrushData(pen->brush, &brush_size);
|
||||
if (stat != Ok) return stat;
|
||||
|
||||
stat = METAFILE_AllocateRecord(metafile,
|
||||
FIELD_OFFSET(EmfPlusObject, ObjectData.pen.data) + pen_data_size + brush_size,
|
||||
(void**)&object_record);
|
||||
if (stat != Ok) return stat;
|
||||
|
||||
*id = METAFILE_AddObjectId(metafile);
|
||||
object_record->Header.Type = EmfPlusRecordTypeObject;
|
||||
object_record->Header.Flags = *id | ObjectTypePen << 8;
|
||||
object_record->ObjectData.pen.Version = VERSION_MAGIC2;
|
||||
object_record->ObjectData.pen.Type = 0;
|
||||
|
||||
pen_data = (EmfPlusPenData*)object_record->ObjectData.pen.data;
|
||||
pen_data->PenDataFlags = data_flags;
|
||||
pen_data->PenUnit = pen->unit;
|
||||
pen_data->PenWidth = pen->width;
|
||||
|
||||
i = 0;
|
||||
if (data_flags & PenDataTransform)
|
||||
{
|
||||
EmfPlusTransformMatrix *m = (EmfPlusTransformMatrix*)(pen_data->OptionalData + i);
|
||||
memcpy(m, &pen->transform, sizeof(*m));
|
||||
i += sizeof(EmfPlusTransformMatrix);
|
||||
}
|
||||
if (data_flags & PenDataStartCap)
|
||||
{
|
||||
*(DWORD*)(pen_data->OptionalData + i) = pen->startcap;
|
||||
i += sizeof(DWORD);
|
||||
}
|
||||
if (data_flags & PenDataEndCap)
|
||||
{
|
||||
*(DWORD*)(pen_data->OptionalData + i) = pen->endcap;
|
||||
i += sizeof(DWORD);
|
||||
}
|
||||
if (data_flags & PenDataJoin)
|
||||
{
|
||||
*(DWORD*)(pen_data->OptionalData + i) = pen->join;
|
||||
i += sizeof(DWORD);
|
||||
}
|
||||
if (data_flags & PenDataMiterLimit)
|
||||
{
|
||||
*(REAL*)(pen_data->OptionalData + i) = pen->miterlimit;
|
||||
i += sizeof(REAL);
|
||||
}
|
||||
if (data_flags & PenDataLineStyle)
|
||||
{
|
||||
switch (pen->style & PS_STYLE_MASK)
|
||||
{
|
||||
case PS_SOLID: *(DWORD*)(pen_data->OptionalData + i) = LineStyleSolid; break;
|
||||
case PS_DASH: *(DWORD*)(pen_data->OptionalData + i) = LineStyleDash; break;
|
||||
case PS_DOT: *(DWORD*)(pen_data->OptionalData + i) = LineStyleDot; break;
|
||||
case PS_DASHDOT: *(DWORD*)(pen_data->OptionalData + i) = LineStyleDashDot; break;
|
||||
case PS_DASHDOTDOT: *(DWORD*)(pen_data->OptionalData + i) = LineStyleDashDotDot; break;
|
||||
default: *(DWORD*)(pen_data->OptionalData + i) = LineStyleCustom; break;
|
||||
}
|
||||
i += sizeof(DWORD);
|
||||
}
|
||||
if (data_flags & PenDataDashedLineCap)
|
||||
{
|
||||
*(DWORD*)(pen_data->OptionalData + i) = pen->dashcap;
|
||||
i += sizeof(DWORD);
|
||||
}
|
||||
if (data_flags & PenDataDashedLineOffset)
|
||||
{
|
||||
*(REAL*)(pen_data->OptionalData + i) = pen->offset;
|
||||
i += sizeof(REAL);
|
||||
}
|
||||
if (data_flags & PenDataDashedLine)
|
||||
{
|
||||
int j;
|
||||
|
||||
*(DWORD*)(pen_data->OptionalData + i) = pen->numdashes;
|
||||
i += sizeof(DWORD);
|
||||
|
||||
for (j=0; j<pen->numdashes; j++)
|
||||
{
|
||||
*(REAL*)(pen_data->OptionalData + i) = pen->dashes[j];
|
||||
i += sizeof(REAL);
|
||||
}
|
||||
}
|
||||
if (data_flags & PenDataNonCenter)
|
||||
{
|
||||
*(REAL*)(pen_data->OptionalData + i) = pen->align;
|
||||
i += sizeof(DWORD);
|
||||
}
|
||||
|
||||
METAFILE_FillBrushData(pen->brush,
|
||||
(EmfPlusBrush*)(object_record->ObjectData.pen.data + pen_data_size));
|
||||
return Ok;
|
||||
}
|
||||
|
||||
GpStatus METAFILE_DrawPath(GpMetafile *metafile, GpPen *pen, GpPath *path)
|
||||
{
|
||||
EmfPlusDrawPath *draw_path_record;
|
||||
DWORD path_id;
|
||||
DWORD pen_id;
|
||||
GpStatus stat;
|
||||
|
||||
if (metafile->metafile_type == MetafileTypeEmf)
|
||||
{
|
||||
FIXME("stub!\n");
|
||||
return NotImplemented;
|
||||
}
|
||||
|
||||
stat = METAFILE_AddPenObject(metafile, pen, &pen_id);
|
||||
if (stat != Ok) return stat;
|
||||
|
||||
stat = METAFILE_AddPathObject(metafile, path, &path_id);
|
||||
if (stat != Ok) return stat;
|
||||
|
||||
stat = METAFILE_AllocateRecord(metafile, sizeof(EmfPlusDrawPath), (void**)&draw_path_record);
|
||||
if (stat != Ok) return stat;
|
||||
draw_path_record->Header.Type = EmfPlusRecordTypeDrawPath;
|
||||
draw_path_record->Header.Flags = path_id;
|
||||
draw_path_record->PenId = pen_id;
|
||||
|
||||
METAFILE_WriteRecords(metafile);
|
||||
return Ok;
|
||||
}
|
||||
|
||||
static GpStatus METAFILE_AddBrushObject(GpMetafile *metafile, GpBrush *brush, DWORD *id)
|
||||
{
|
||||
EmfPlusObject *object_record;
|
||||
GpStatus stat;
|
||||
DWORD size;
|
||||
|
||||
*id = -1;
|
||||
if (metafile->metafile_type != MetafileTypeEmfPlusOnly && metafile->metafile_type != MetafileTypeEmfPlusDual)
|
||||
return Ok;
|
||||
|
||||
stat = METAFILE_PrepareBrushData(brush, &size);
|
||||
if (stat != Ok) return stat;
|
||||
|
||||
stat = METAFILE_AllocateRecord(metafile,
|
||||
FIELD_OFFSET(EmfPlusObject, ObjectData) + size, (void**)&object_record);
|
||||
if (stat != Ok) return stat;
|
||||
|
||||
*id = METAFILE_AddObjectId(metafile);
|
||||
object_record->Header.Type = EmfPlusRecordTypeObject;
|
||||
object_record->Header.Flags = *id | ObjectTypeBrush << 8;
|
||||
METAFILE_FillBrushData(brush, &object_record->ObjectData.brush);
|
||||
return Ok;
|
||||
}
|
||||
|
||||
GpStatus METAFILE_FillPath(GpMetafile *metafile, GpBrush *brush, GpPath *path)
|
||||
{
|
||||
EmfPlusFillPath *fill_path_record;
|
||||
DWORD brush_id = -1, path_id;
|
||||
BOOL inline_color;
|
||||
GpStatus stat;
|
||||
|
||||
if (metafile->metafile_type == MetafileTypeEmf)
|
||||
{
|
||||
FIXME("stub!\n");
|
||||
return NotImplemented;
|
||||
}
|
||||
|
||||
inline_color = brush->bt == BrushTypeSolidColor;
|
||||
if (!inline_color)
|
||||
{
|
||||
stat = METAFILE_AddBrushObject(metafile, brush, &brush_id);
|
||||
if (stat != Ok) return stat;
|
||||
}
|
||||
|
||||
stat = METAFILE_AddPathObject(metafile, path, &path_id);
|
||||
if (stat != Ok) return stat;
|
||||
|
||||
stat = METAFILE_AllocateRecord(metafile,
|
||||
sizeof(EmfPlusFillPath), (void**)&fill_path_record);
|
||||
if (stat != Ok) return stat;
|
||||
fill_path_record->Header.Type = EmfPlusRecordTypeFillPath;
|
||||
if (inline_color)
|
||||
{
|
||||
fill_path_record->Header.Flags = 0x8000 | path_id;
|
||||
fill_path_record->data.Color.Blue = ((GpSolidFill*)brush)->color & 0xff;
|
||||
fill_path_record->data.Color.Green = (((GpSolidFill*)brush)->color >> 8) & 0xff;
|
||||
fill_path_record->data.Color.Red = (((GpSolidFill*)brush)->color >> 16) & 0xff;
|
||||
fill_path_record->data.Color.Alpha = ((GpSolidFill*)brush)->color >> 24;
|
||||
}
|
||||
else
|
||||
{
|
||||
fill_path_record->Header.Flags = path_id;
|
||||
fill_path_record->data.BrushId = brush_id;
|
||||
}
|
||||
|
||||
METAFILE_WriteRecords(metafile);
|
||||
return Ok;
|
||||
}
|
||||
|
|
|
@ -62,7 +62,6 @@
|
|||
*
|
||||
*/
|
||||
|
||||
#define FLAGS_NOFLAGS 0x0
|
||||
#define FLAGS_INTPATH 0x4000
|
||||
|
||||
struct memory_buffer
|
||||
|
@ -73,12 +72,17 @@ struct memory_buffer
|
|||
|
||||
struct region_header
|
||||
{
|
||||
DWORD size;
|
||||
DWORD checksum;
|
||||
DWORD magic;
|
||||
DWORD num_children;
|
||||
};
|
||||
|
||||
struct region_data_header
|
||||
{
|
||||
DWORD size;
|
||||
DWORD checksum;
|
||||
struct region_header header;
|
||||
};
|
||||
|
||||
struct path_header
|
||||
{
|
||||
DWORD size;
|
||||
|
@ -87,46 +91,12 @@ struct path_header
|
|||
DWORD flags;
|
||||
};
|
||||
|
||||
/* Header size as far as header->size is concerned. This doesn't include
|
||||
* header->size or header->checksum
|
||||
*/
|
||||
static const INT sizeheader_size = sizeof(DWORD) * 2;
|
||||
|
||||
typedef struct packed_point
|
||||
{
|
||||
short X;
|
||||
short Y;
|
||||
} packed_point;
|
||||
|
||||
/* Test to see if the path could be stored as an array of shorts */
|
||||
static BOOL is_integer_path(const GpPath *path)
|
||||
{
|
||||
int i;
|
||||
|
||||
if (!path->pathdata.Count) return FALSE;
|
||||
|
||||
for (i = 0; i < path->pathdata.Count; i++)
|
||||
{
|
||||
short x, y;
|
||||
x = gdip_round(path->pathdata.Points[i].X);
|
||||
y = gdip_round(path->pathdata.Points[i].Y);
|
||||
if (path->pathdata.Points[i].X != (REAL)x || path->pathdata.Points[i].Y != (REAL)y)
|
||||
return FALSE;
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/* Everything is measured in DWORDS; round up if there's a remainder */
|
||||
static inline INT get_pathtypes_size(const GpPath* path)
|
||||
{
|
||||
INT needed = path->pathdata.Count / sizeof(DWORD);
|
||||
|
||||
if (path->pathdata.Count % sizeof(DWORD) > 0)
|
||||
needed++;
|
||||
|
||||
return needed * sizeof(DWORD);
|
||||
}
|
||||
|
||||
static inline INT get_element_size(const region_element* element)
|
||||
{
|
||||
INT needed = sizeof(DWORD); /* DWORD for the type */
|
||||
|
@ -136,17 +106,8 @@ static inline INT get_element_size(const region_element* element)
|
|||
return needed + sizeof(GpRect);
|
||||
case RegionDataPath:
|
||||
{
|
||||
const GpPath *path = element->elementdata.path;
|
||||
DWORD flags = is_integer_path(path) ? FLAGS_INTPATH : FLAGS_NOFLAGS;
|
||||
/* 3 for headers, once again size doesn't count itself */
|
||||
needed += sizeof(DWORD) * 3;
|
||||
if (flags & FLAGS_INTPATH)
|
||||
needed += 2 * sizeof(SHORT) * path->pathdata.Count;
|
||||
else
|
||||
needed += 2 * sizeof(FLOAT) * path->pathdata.Count;
|
||||
|
||||
needed += get_pathtypes_size(path);
|
||||
needed += sizeof(DWORD); /* Extra DWORD for pathheader.size */
|
||||
needed += write_path_data(element->elementdata.path, NULL);
|
||||
needed += sizeof(DWORD); /* Extra DWORD for path size */
|
||||
return needed;
|
||||
}
|
||||
case RegionDataEmptyRect:
|
||||
|
@ -692,29 +653,6 @@ static inline void write_float(DWORD* location, INT* offset, const FLOAT write)
|
|||
(*offset)++;
|
||||
}
|
||||
|
||||
static inline void write_packed_point(DWORD* location, INT* offset,
|
||||
const GpPointF* write)
|
||||
{
|
||||
packed_point *point = (packed_point *)(location + *offset);
|
||||
point->X = gdip_round(write->X);
|
||||
point->Y = gdip_round(write->Y);
|
||||
(*offset)++;
|
||||
}
|
||||
|
||||
static inline void write_path_types(DWORD* location, INT* offset,
|
||||
const GpPath* path)
|
||||
{
|
||||
INT rounded_size = get_pathtypes_size(path);
|
||||
|
||||
memcpy(location + *offset, path->pathdata.Types, path->pathdata.Count);
|
||||
|
||||
/* The unwritten parts of the DWORD (if any) must be cleared */
|
||||
if (rounded_size - path->pathdata.Count)
|
||||
ZeroMemory(((BYTE*)location) + (*offset * sizeof(DWORD)) +
|
||||
path->pathdata.Count, rounded_size - path->pathdata.Count);
|
||||
*offset += rounded_size / sizeof(DWORD);
|
||||
}
|
||||
|
||||
static void write_element(const region_element* element, DWORD *buffer,
|
||||
INT* filled)
|
||||
{
|
||||
|
@ -738,43 +676,9 @@ static void write_element(const region_element* element, DWORD *buffer,
|
|||
break;
|
||||
case RegionDataPath:
|
||||
{
|
||||
INT i;
|
||||
const GpPath* path = element->elementdata.path;
|
||||
struct path_header *pathheader;
|
||||
|
||||
pathheader = (struct path_header *)(buffer + *filled);
|
||||
|
||||
pathheader->flags = is_integer_path(path) ? FLAGS_INTPATH : FLAGS_NOFLAGS;
|
||||
/* 3 for headers, once again size doesn't count itself */
|
||||
pathheader->size = sizeof(DWORD) * 3;
|
||||
if (pathheader->flags & FLAGS_INTPATH)
|
||||
pathheader->size += 2 * sizeof(SHORT) * path->pathdata.Count;
|
||||
else
|
||||
pathheader->size += 2 * sizeof(FLOAT) * path->pathdata.Count;
|
||||
pathheader->size += get_pathtypes_size(path);
|
||||
pathheader->magic = VERSION_MAGIC;
|
||||
pathheader->count = path->pathdata.Count;
|
||||
|
||||
*filled += 4;
|
||||
|
||||
switch (pathheader->flags & FLAGS_INTPATH)
|
||||
{
|
||||
case FLAGS_NOFLAGS:
|
||||
for (i = 0; i < path->pathdata.Count; i++)
|
||||
{
|
||||
write_float(buffer, filled, path->pathdata.Points[i].X);
|
||||
write_float(buffer, filled, path->pathdata.Points[i].Y);
|
||||
}
|
||||
break;
|
||||
case FLAGS_INTPATH:
|
||||
for (i = 0; i < path->pathdata.Count; i++)
|
||||
{
|
||||
write_packed_point(buffer, filled,
|
||||
&path->pathdata.Points[i]);
|
||||
}
|
||||
break;
|
||||
}
|
||||
write_path_types(buffer, filled, path);
|
||||
DWORD size = write_path_data(element->elementdata.path, buffer + *filled + 1);
|
||||
write_dword(buffer, filled, size);
|
||||
*filled += size / sizeof(DWORD);
|
||||
break;
|
||||
}
|
||||
case RegionDataEmptyRect:
|
||||
|
@ -783,6 +687,24 @@ static void write_element(const region_element* element, DWORD *buffer,
|
|||
}
|
||||
}
|
||||
|
||||
DWORD write_region_data(const GpRegion *region, void *data)
|
||||
{
|
||||
struct region_header *header = data;
|
||||
INT filled = 0;
|
||||
DWORD size;
|
||||
|
||||
size = sizeof(struct region_header) + get_element_size(®ion->node);
|
||||
if (!data) return size;
|
||||
|
||||
header->magic = VERSION_MAGIC2;
|
||||
header->num_children = region->num_children;
|
||||
filled += 2;
|
||||
/* With few exceptions, everything written is DWORD aligned,
|
||||
* so use that as our base */
|
||||
write_element(®ion->node, (DWORD*)data, &filled);
|
||||
return size;
|
||||
}
|
||||
|
||||
/*****************************************************************************
|
||||
* GdipGetRegionData [GDIPLUS.@]
|
||||
*
|
||||
|
@ -819,36 +741,27 @@ static void write_element(const region_element* element, DWORD *buffer,
|
|||
GpStatus WINGDIPAPI GdipGetRegionData(GpRegion *region, BYTE *buffer, UINT size,
|
||||
UINT *needed)
|
||||
{
|
||||
struct region_header *region_header;
|
||||
INT filled = 0;
|
||||
struct region_data_header *region_data_header;
|
||||
UINT required;
|
||||
GpStatus status;
|
||||
|
||||
TRACE("%p, %p, %d, %p\n", region, buffer, size, needed);
|
||||
|
||||
if (!region || !buffer || !size)
|
||||
return InvalidParameter;
|
||||
|
||||
status = GdipGetRegionDataSize(region, &required);
|
||||
if (status != Ok) return status;
|
||||
required = FIELD_OFFSET(struct region_data_header, header) + write_region_data(region, NULL);
|
||||
if (size < required)
|
||||
{
|
||||
if (needed) *needed = size;
|
||||
return InsufficientBuffer;
|
||||
}
|
||||
|
||||
region_header = (struct region_header *)buffer;
|
||||
region_header->size = sizeheader_size + get_element_size(®ion->node);
|
||||
region_header->checksum = 0;
|
||||
region_header->magic = VERSION_MAGIC;
|
||||
region_header->num_children = region->num_children;
|
||||
filled += 4;
|
||||
/* With few exceptions, everything written is DWORD aligned,
|
||||
* so use that as our base */
|
||||
write_element(®ion->node, (DWORD*)buffer, &filled);
|
||||
region_data_header = (struct region_data_header *)buffer;
|
||||
region_data_header->size = write_region_data(region, ®ion_data_header->header);
|
||||
region_data_header->checksum = 0;
|
||||
|
||||
if (needed)
|
||||
*needed = filled * sizeof(DWORD);
|
||||
*needed = required;
|
||||
|
||||
return Ok;
|
||||
}
|
||||
|
@ -949,7 +862,7 @@ static GpStatus read_element(struct memory_buffer *mbuf, GpRegion *region, regio
|
|||
ERR("failed to read path header\n");
|
||||
return InvalidParameter;
|
||||
}
|
||||
if (path_header->magic != VERSION_MAGIC)
|
||||
if (!VALID_MAGIC(path_header->magic))
|
||||
{
|
||||
ERR("invalid path header magic %#x\n", path_header->magic);
|
||||
return InvalidParameter;
|
||||
|
@ -1044,7 +957,7 @@ static GpStatus read_element(struct memory_buffer *mbuf, GpRegion *region, regio
|
|||
*/
|
||||
GpStatus WINGDIPAPI GdipCreateRegionRgnData(GDIPCONST BYTE *data, INT size, GpRegion **region)
|
||||
{
|
||||
const struct region_header *region_header;
|
||||
const struct region_data_header *region_data_header;
|
||||
struct memory_buffer mbuf;
|
||||
GpStatus status;
|
||||
INT count;
|
||||
|
@ -1056,9 +969,8 @@ GpStatus WINGDIPAPI GdipCreateRegionRgnData(GDIPCONST BYTE *data, INT size, GpRe
|
|||
|
||||
init_memory_buffer(&mbuf, data, size);
|
||||
|
||||
region_header = buffer_read(&mbuf, sizeof(*region_header));
|
||||
if (!region_header || (region_header->magic != VERSION_MAGIC &&
|
||||
region_header->magic != VERSION_MAGIC2))
|
||||
region_data_header = buffer_read(&mbuf, sizeof(*region_data_header));
|
||||
if (!region_data_header || !VALID_MAGIC(region_data_header->header.magic))
|
||||
return InvalidParameter;
|
||||
|
||||
status = GdipCreateRegion(region);
|
||||
|
@ -1090,7 +1002,7 @@ GpStatus WINGDIPAPI GdipGetRegionDataSize(GpRegion *region, UINT *needed)
|
|||
return InvalidParameter;
|
||||
|
||||
/* header.size doesn't count header.size and header.checksum */
|
||||
*needed = sizeof(DWORD) * 2 + sizeheader_size + get_element_size(®ion->node);
|
||||
*needed = FIELD_OFFSET(struct region_data_header, header) + write_region_data(region, NULL);
|
||||
|
||||
return Ok;
|
||||
}
|
||||
|
@ -1135,6 +1047,8 @@ static GpStatus get_path_hrgn(GpPath *path, GpGraphics *graphics, HRGN *hrgn)
|
|||
SetPolyFillMode(graphics->hdc, (path->fill == FillModeAlternate ? ALTERNATE
|
||||
: WINDING));
|
||||
|
||||
gdi_transform_acquire(graphics);
|
||||
|
||||
stat = trace_path(graphics, path);
|
||||
if (stat == Ok)
|
||||
{
|
||||
|
@ -1142,6 +1056,8 @@ static GpStatus get_path_hrgn(GpPath *path, GpGraphics *graphics, HRGN *hrgn)
|
|||
stat = *hrgn ? Ok : OutOfMemory;
|
||||
}
|
||||
|
||||
gdi_transform_release(graphics);
|
||||
|
||||
RestoreDC(graphics->hdc, save_state);
|
||||
if (new_hdc)
|
||||
{
|
||||
|
|
|
@ -68,7 +68,7 @@ reactos/dll/win32/dciman32 # Synced to WineStaging-2.9
|
|||
reactos/dll/win32/faultrep # Synced to WineStaging-2.9
|
||||
reactos/dll/win32/fontsub # Synced to WineStaging-2.9
|
||||
reactos/dll/win32/fusion # Synced to WineStaging-2.16
|
||||
reactos/dll/win32/gdiplus # Synced to WineStaging-2.9
|
||||
reactos/dll/win32/gdiplus # Synced to WineStaging-2.16
|
||||
reactos/dll/win32/hhctrl.ocx # Synced to WineStaging-2.9
|
||||
reactos/dll/win32/hlink # Synced to WineStaging-2.9
|
||||
reactos/dll/win32/hnetcfg # Synced to WineStaging-2.9
|
||||
|
|
Loading…
Reference in a new issue