1628 lines
50 KiB
C
1628 lines
50 KiB
C
/*
|
|
* DIB driver graphics operations.
|
|
*
|
|
* Copyright 2011 Huw Davies
|
|
*
|
|
* This library is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Lesser General Public
|
|
* License as published by the Free Software Foundation; either
|
|
* version 2.1 of the License, or (at your option) any later version.
|
|
*
|
|
* This library is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* Lesser General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public
|
|
* License along with this library; if not, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
|
|
*/
|
|
|
|
#include <assert.h>
|
|
#include "gdi_private.h"
|
|
#include "dibdrv.h"
|
|
|
|
#include "wine/unicode.h"
|
|
#include "wine/debug.h"
|
|
|
|
WINE_DEFAULT_DEBUG_CHANNEL(dib);
|
|
|
|
struct cached_glyph
|
|
{
|
|
GLYPHMETRICS metrics;
|
|
BYTE bits[1];
|
|
};
|
|
|
|
enum glyph_type
|
|
{
|
|
GLYPH_INDEX,
|
|
GLYPH_WCHAR,
|
|
GLYPH_NBTYPES
|
|
};
|
|
|
|
#define GLYPH_CACHE_PAGE_SIZE 0x100
|
|
#define GLYPH_CACHE_PAGES (0x10000 / GLYPH_CACHE_PAGE_SIZE)
|
|
|
|
struct cached_font
|
|
{
|
|
struct list entry;
|
|
LONG ref;
|
|
DWORD hash;
|
|
LOGFONTW lf;
|
|
XFORM xform;
|
|
UINT aa_flags;
|
|
struct cached_glyph **glyphs[GLYPH_NBTYPES][GLYPH_CACHE_PAGES];
|
|
};
|
|
|
|
static struct list font_cache = LIST_INIT( font_cache );
|
|
|
|
static CRITICAL_SECTION font_cache_cs;
|
|
static CRITICAL_SECTION_DEBUG critsect_debug =
|
|
{
|
|
0, 0, &font_cache_cs,
|
|
{ &critsect_debug.ProcessLocksList, &critsect_debug.ProcessLocksList },
|
|
0, 0, { (DWORD_PTR)(__FILE__ ": font_cache_cs") }
|
|
};
|
|
static CRITICAL_SECTION font_cache_cs = { &critsect_debug, -1, 0, 0, 0, 0 };
|
|
|
|
|
|
static BOOL brush_rect( dibdrv_physdev *pdev, dib_brush *brush, const RECT *rect, HRGN clip )
|
|
{
|
|
DC *dc = get_physdev_dc( &pdev->dev );
|
|
struct clipped_rects clipped_rects;
|
|
BOOL ret;
|
|
|
|
if (!get_clipped_rects( &pdev->dib, rect, clip, &clipped_rects )) return TRUE;
|
|
ret = brush->rects( pdev, brush, &pdev->dib, clipped_rects.count, clipped_rects.rects,
|
|
dc->ROPmode );
|
|
free_clipped_rects( &clipped_rects );
|
|
return ret;
|
|
}
|
|
|
|
/* paint a region with the brush (note: the region can be modified) */
|
|
static BOOL brush_region( dibdrv_physdev *pdev, HRGN region )
|
|
{
|
|
if (pdev->clip) CombineRgn( region, region, pdev->clip, RGN_AND );
|
|
return brush_rect( pdev, &pdev->brush, NULL, region );
|
|
}
|
|
|
|
/* paint a region with the pen (note: the region can be modified) */
|
|
static BOOL pen_region( dibdrv_physdev *pdev, HRGN region )
|
|
{
|
|
if (pdev->clip) CombineRgn( region, region, pdev->clip, RGN_AND );
|
|
return brush_rect( pdev, &pdev->pen_brush, NULL, region );
|
|
}
|
|
|
|
static RECT get_device_rect( DC *dc, int left, int top, int right, int bottom, BOOL rtl_correction )
|
|
{
|
|
RECT rect;
|
|
|
|
rect.left = left;
|
|
rect.top = top;
|
|
rect.right = right;
|
|
rect.bottom = bottom;
|
|
if (rtl_correction && dc->layout & LAYOUT_RTL)
|
|
{
|
|
/* shift the rectangle so that the right border is included after mirroring */
|
|
/* it would be more correct to do this after LPtoDP but that's not what Windows does */
|
|
rect.left--;
|
|
rect.right--;
|
|
}
|
|
lp_to_dp( dc, (POINT *)&rect, 2 );
|
|
order_rect( &rect );
|
|
return rect;
|
|
}
|
|
|
|
static BOOL get_pen_device_rect( DC *dc, dibdrv_physdev *dev, RECT *rect,
|
|
int left, int top, int right, int bottom )
|
|
{
|
|
*rect = get_device_rect( dc, left, top, right, bottom, TRUE );
|
|
if (rect->left == rect->right || rect->top == rect->bottom) return FALSE;
|
|
|
|
if (dev->pen_style == PS_INSIDEFRAME)
|
|
{
|
|
rect->left += dev->pen_width / 2;
|
|
rect->top += dev->pen_width / 2;
|
|
rect->right -= (dev->pen_width - 1) / 2;
|
|
rect->bottom -= (dev->pen_width - 1) / 2;
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
static void add_pen_lines_bounds( dibdrv_physdev *dev, int count, const POINT *points, HRGN rgn )
|
|
{
|
|
const WINEREGION *region;
|
|
RECT bounds, rect;
|
|
int width = 0;
|
|
|
|
if (!dev->bounds) return;
|
|
reset_bounds( &bounds );
|
|
|
|
if (dev->pen_uses_region)
|
|
{
|
|
/* Windows uses some heuristics to estimate the distance from the point that will be painted */
|
|
width = dev->pen_width + 2;
|
|
if (dev->pen_join == PS_JOIN_MITER)
|
|
{
|
|
width *= 5;
|
|
if (dev->pen_endcap == PS_ENDCAP_SQUARE) width = (width * 3 + 1) / 2;
|
|
}
|
|
else
|
|
{
|
|
if (dev->pen_endcap == PS_ENDCAP_SQUARE) width -= width / 4;
|
|
else width = (width + 1) / 2;
|
|
}
|
|
|
|
/* in case the heuristics are wrong, add the actual region too */
|
|
if ((region = get_wine_region( rgn )))
|
|
{
|
|
add_bounds_rect( &bounds, ®ion->extents );
|
|
release_wine_region( rgn );
|
|
}
|
|
}
|
|
|
|
while (count-- > 0)
|
|
{
|
|
rect.left = points->x - width;
|
|
rect.top = points->y - width;
|
|
rect.right = points->x + width + 1;
|
|
rect.bottom = points->y + width + 1;
|
|
add_bounds_rect( &bounds, &rect );
|
|
points++;
|
|
}
|
|
|
|
add_clipped_bounds( dev, &bounds, dev->clip );
|
|
}
|
|
|
|
/* compute the points for the first quadrant of an ellipse, counterclockwise from the x axis */
|
|
/* 'data' must contain enough space, (width+height)/2 is a reasonable upper bound */
|
|
static int ellipse_first_quadrant( int width, int height, POINT *data )
|
|
{
|
|
const int a = width - 1;
|
|
const int b = height - 1;
|
|
const INT64 asq = (INT64)8 * a * a;
|
|
const INT64 bsq = (INT64)8 * b * b;
|
|
INT64 dx = (INT64)4 * b * b * (1 - a);
|
|
INT64 dy = (INT64)4 * a * a * (1 + (b % 2));
|
|
INT64 err = dx + dy + a * a * (b % 2);
|
|
int pos = 0;
|
|
POINT pt;
|
|
|
|
pt.x = a;
|
|
pt.y = height / 2;
|
|
|
|
/* based on an algorithm by Alois Zingl */
|
|
|
|
while (pt.x >= width / 2)
|
|
{
|
|
INT64 e2 = 2 * err;
|
|
data[pos++] = pt;
|
|
if (e2 >= dx)
|
|
{
|
|
pt.x--;
|
|
err += dx += bsq;
|
|
}
|
|
if (e2 <= dy)
|
|
{
|
|
pt.y++;
|
|
err += dy += asq;
|
|
}
|
|
}
|
|
return pos;
|
|
}
|
|
|
|
static int find_intersection( const POINT *points, int x, int y, int count )
|
|
{
|
|
int i;
|
|
|
|
if (y >= 0)
|
|
{
|
|
if (x >= 0) /* first quadrant */
|
|
{
|
|
for (i = 0; i < count; i++) if (points[i].x * y <= points[i].y * x) break;
|
|
return i;
|
|
}
|
|
/* second quadrant */
|
|
for (i = 0; i < count; i++) if (points[i].x * y < points[i].y * -x) break;
|
|
return 2 * count - i;
|
|
}
|
|
if (x >= 0) /* fourth quadrant */
|
|
{
|
|
for (i = 0; i < count; i++) if (points[i].x * -y <= points[i].y * x) break;
|
|
return 4 * count - i;
|
|
}
|
|
/* third quadrant */
|
|
for (i = 0; i < count; i++) if (points[i].x * -y < points[i].y * -x) break;
|
|
return 2 * count + i;
|
|
}
|
|
|
|
static int get_arc_points( int arc_dir, const RECT *rect, POINT start, POINT end, POINT *points )
|
|
{
|
|
int i, pos, count, start_pos, end_pos;
|
|
int width = rect->right - rect->left;
|
|
int height = rect->bottom - rect->top;
|
|
|
|
count = ellipse_first_quadrant( width, height, points );
|
|
for (i = 0; i < count; i++)
|
|
{
|
|
points[i].x -= width / 2;
|
|
points[i].y -= height / 2;
|
|
}
|
|
if (arc_dir != AD_CLOCKWISE)
|
|
{
|
|
start.y = -start.y;
|
|
end.y = -end.y;
|
|
}
|
|
start_pos = find_intersection( points, start.x, start.y, count );
|
|
end_pos = find_intersection( points, end.x, end.y, count );
|
|
if (end_pos <= start_pos) end_pos += 4 * count;
|
|
|
|
pos = count;
|
|
if (arc_dir == AD_CLOCKWISE)
|
|
{
|
|
for (i = start_pos; i < end_pos; i++, pos++)
|
|
{
|
|
switch ((i / count) % 4)
|
|
{
|
|
case 0:
|
|
points[pos].x = rect->left + width/2 + points[i % count].x;
|
|
points[pos].y = rect->top + height/2 + points[i % count].y;
|
|
break;
|
|
case 1:
|
|
points[pos].x = rect->right-1 - width/2 - points[count - 1 - i % count].x;
|
|
points[pos].y = rect->top + height/2 + points[count - 1 - i % count].y;
|
|
break;
|
|
case 2:
|
|
points[pos].x = rect->right-1 - width/2 - points[i % count].x;
|
|
points[pos].y = rect->bottom-1 - height/2 - points[i % count].y;
|
|
break;
|
|
case 3:
|
|
points[pos].x = rect->left + width/2 + points[count - 1 - i % count].x;
|
|
points[pos].y = rect->bottom-1 - height/2 - points[count - 1 - i % count].y;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
for (i = start_pos; i < end_pos; i++, pos++)
|
|
{
|
|
switch ((i / count) % 4)
|
|
{
|
|
case 0:
|
|
points[pos].x = rect->left + width/2 + points[i % count].x;
|
|
points[pos].y = rect->bottom-1 - height/2 - points[i % count].y;
|
|
break;
|
|
case 1:
|
|
points[pos].x = rect->right-1 - width/2 - points[count - 1 - i % count].x;
|
|
points[pos].y = rect->bottom-1 - height/2 - points[count - 1 - i % count].y;
|
|
break;
|
|
case 2:
|
|
points[pos].x = rect->right-1 - width/2 - points[i % count].x;
|
|
points[pos].y = rect->top + height/2 + points[i % count].y;
|
|
break;
|
|
case 3:
|
|
points[pos].x = rect->left + width/2 + points[count - 1 - i % count].x;
|
|
points[pos].y = rect->top + height/2 + points[count - 1 - i % count].y;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
memmove( points, points + count, (pos - count) * sizeof(POINT) );
|
|
return pos - count;
|
|
}
|
|
|
|
/* backend for arc functions; extra_lines is -1 for ArcTo, 0 for Arc, 1 for Chord, 2 for Pie */
|
|
static BOOL draw_arc( PHYSDEV dev, INT left, INT top, INT right, INT bottom,
|
|
INT start_x, INT start_y, INT end_x, INT end_y, INT extra_lines )
|
|
{
|
|
dibdrv_physdev *pdev = get_dibdrv_pdev( dev );
|
|
DC *dc = get_physdev_dc( dev );
|
|
RECT rect;
|
|
POINT pt[2], *points;
|
|
int width, height, count;
|
|
BOOL ret = TRUE;
|
|
HRGN outline = 0, interior = 0;
|
|
|
|
if (!get_pen_device_rect( dc, pdev, &rect, left, top, right, bottom )) return TRUE;
|
|
|
|
width = rect.right - rect.left;
|
|
height = rect.bottom - rect.top;
|
|
|
|
pt[0].x = start_x;
|
|
pt[0].y = start_y;
|
|
pt[1].x = end_x;
|
|
pt[1].y = end_y;
|
|
lp_to_dp( dc, pt, 2 );
|
|
/* make them relative to the ellipse center */
|
|
pt[0].x -= rect.left + width / 2;
|
|
pt[0].y -= rect.top + height / 2;
|
|
pt[1].x -= rect.left + width / 2;
|
|
pt[1].y -= rect.top + height / 2;
|
|
|
|
points = HeapAlloc( GetProcessHeap(), 0, (width + height) * 3 * sizeof(*points) );
|
|
if (!points) return FALSE;
|
|
|
|
if (extra_lines == -1)
|
|
{
|
|
points[0] = dc->cur_pos;
|
|
lp_to_dp( dc, points, 1 );
|
|
count = 1 + get_arc_points( dc->ArcDirection, &rect, pt[0], pt[1], points + 1 );
|
|
}
|
|
else count = get_arc_points( dc->ArcDirection, &rect, pt[0], pt[1], points );
|
|
|
|
if (extra_lines == 2)
|
|
{
|
|
points[count].x = rect.left + width / 2;
|
|
points[count].y = rect.top + height / 2;
|
|
count++;
|
|
}
|
|
if (count < 2)
|
|
{
|
|
HeapFree( GetProcessHeap(), 0, points );
|
|
return TRUE;
|
|
}
|
|
|
|
if (pdev->pen_uses_region && !(outline = CreateRectRgn( 0, 0, 0, 0 )))
|
|
{
|
|
HeapFree( GetProcessHeap(), 0, points );
|
|
return FALSE;
|
|
}
|
|
|
|
if (pdev->brush.style != BS_NULL && extra_lines > 0 &&
|
|
!(interior = CreatePolygonRgn( points, count, WINDING )))
|
|
{
|
|
HeapFree( GetProcessHeap(), 0, points );
|
|
if (outline) DeleteObject( outline );
|
|
return FALSE;
|
|
}
|
|
|
|
/* if not using a region, paint the interior first so the outline can overlap it */
|
|
if (interior && !outline)
|
|
{
|
|
ret = brush_region( pdev, interior );
|
|
DeleteObject( interior );
|
|
interior = 0;
|
|
}
|
|
|
|
reset_dash_origin( pdev );
|
|
pdev->pen_lines( pdev, count, points, extra_lines > 0, outline );
|
|
add_pen_lines_bounds( pdev, count, points, outline );
|
|
|
|
if (interior)
|
|
{
|
|
CombineRgn( interior, interior, outline, RGN_DIFF );
|
|
ret = brush_region( pdev, interior );
|
|
DeleteObject( interior );
|
|
}
|
|
if (outline)
|
|
{
|
|
if (ret) ret = pen_region( pdev, outline );
|
|
DeleteObject( outline );
|
|
}
|
|
HeapFree( GetProcessHeap(), 0, points );
|
|
return ret;
|
|
}
|
|
|
|
/* helper for path stroking and filling functions */
|
|
static BOOL stroke_and_fill_path( dibdrv_physdev *dev, BOOL stroke, BOOL fill )
|
|
{
|
|
DC *dc = get_physdev_dc( &dev->dev );
|
|
struct gdi_path *path;
|
|
POINT *points;
|
|
BYTE *types;
|
|
BOOL ret = TRUE;
|
|
HRGN outline = 0, interior = 0;
|
|
int i, pos, total;
|
|
|
|
if (dev->brush.style == BS_NULL) fill = FALSE;
|
|
|
|
if (!(path = get_gdi_flat_path( dc, fill ? &interior : NULL ))) return FALSE;
|
|
if (!(total = get_gdi_path_data( path, &points, &types ))) goto done;
|
|
|
|
if (stroke && dev->pen_uses_region) outline = CreateRectRgn( 0, 0, 0, 0 );
|
|
|
|
/* if not using a region, paint the interior first so the outline can overlap it */
|
|
if (interior && !outline)
|
|
{
|
|
ret = brush_region( dev, interior );
|
|
DeleteObject( interior );
|
|
interior = 0;
|
|
}
|
|
|
|
if (stroke)
|
|
{
|
|
pos = 0;
|
|
for (i = 1; i < total; i++)
|
|
{
|
|
if (types[i] != PT_MOVETO) continue;
|
|
if (i > pos + 1)
|
|
{
|
|
reset_dash_origin( dev );
|
|
dev->pen_lines( dev, i - pos, points + pos,
|
|
fill || types[i - 1] & PT_CLOSEFIGURE, outline );
|
|
}
|
|
pos = i;
|
|
}
|
|
if (i > pos + 1)
|
|
{
|
|
reset_dash_origin( dev );
|
|
dev->pen_lines( dev, i - pos, points + pos,
|
|
fill || types[i - 1] & PT_CLOSEFIGURE, outline );
|
|
}
|
|
}
|
|
|
|
add_pen_lines_bounds( dev, total, points, outline );
|
|
|
|
if (interior)
|
|
{
|
|
CombineRgn( interior, interior, outline, RGN_DIFF );
|
|
ret = brush_region( dev, interior );
|
|
DeleteObject( interior );
|
|
}
|
|
if (outline)
|
|
{
|
|
if (ret) ret = pen_region( dev, outline );
|
|
DeleteObject( outline );
|
|
}
|
|
|
|
done:
|
|
free_gdi_path( path );
|
|
return ret;
|
|
}
|
|
|
|
/* Intensities of the 17 glyph levels when drawn with text component of 0xff on a
|
|
black bkgnd. [A log-log plot of these data gives: y = 77.05 * x^0.4315]. */
|
|
static const BYTE ramp[17] =
|
|
{
|
|
0, 0x4d, 0x68, 0x7c,
|
|
0x8c, 0x9a, 0xa7, 0xb2,
|
|
0xbd, 0xc7, 0xd0, 0xd9,
|
|
0xe1, 0xe9, 0xf0, 0xf8,
|
|
0xff
|
|
};
|
|
|
|
/* For a give text-color component and a glyph level, calculate the
|
|
range of dst intensities, the min/max corresponding to 0/0xff bkgnd
|
|
components respectively.
|
|
|
|
The minimum is a linear interpolation between 0 and the value in
|
|
the ramp table.
|
|
|
|
The maximum is a linear interpolation between the value from the
|
|
ramp table read in reverse and 0xff.
|
|
|
|
To find the resulting pixel intensity, we note that if the text and
|
|
bkgnd intensities are the same then the result must be that
|
|
intensity. Otherwise we linearly interpolate between either the
|
|
min or the max value and this intermediate value depending on which
|
|
side of the inequality we lie.
|
|
*/
|
|
|
|
static inline void get_range( BYTE aa, DWORD text_comp, BYTE *min_comp, BYTE *max_comp )
|
|
{
|
|
*min_comp = (ramp[aa] * text_comp) / 0xff;
|
|
*max_comp = ramp[16 - aa] + ((0xff - ramp[16 - aa]) * text_comp) / 0xff;
|
|
}
|
|
|
|
static inline void get_aa_ranges( COLORREF col, struct intensity_range intensities[17] )
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < 17; i++)
|
|
{
|
|
get_range( i, GetRValue(col), &intensities[i].r_min, &intensities[i].r_max );
|
|
get_range( i, GetGValue(col), &intensities[i].g_min, &intensities[i].g_max );
|
|
get_range( i, GetBValue(col), &intensities[i].b_min, &intensities[i].b_max );
|
|
}
|
|
}
|
|
|
|
static DWORD font_cache_hash( struct cached_font *font )
|
|
{
|
|
DWORD hash = 0, *ptr, two_chars;
|
|
WORD *pwc;
|
|
int i;
|
|
|
|
hash ^= font->aa_flags;
|
|
for(i = 0, ptr = (DWORD*)&font->xform; i < sizeof(XFORM)/sizeof(DWORD); i++, ptr++)
|
|
hash ^= *ptr;
|
|
for(i = 0, ptr = (DWORD*)&font->lf; i < 7; i++, ptr++)
|
|
hash ^= *ptr;
|
|
for(i = 0, ptr = (DWORD*)font->lf.lfFaceName; i < LF_FACESIZE/2; i++, ptr++) {
|
|
two_chars = *ptr;
|
|
pwc = (WCHAR *)&two_chars;
|
|
if (!*pwc) break;
|
|
*pwc = toupperW(*pwc);
|
|
pwc++;
|
|
*pwc = toupperW(*pwc);
|
|
hash ^= two_chars;
|
|
if (!*pwc) break;
|
|
}
|
|
return hash;
|
|
}
|
|
|
|
static int font_cache_cmp( const struct cached_font *p1, const struct cached_font *p2 )
|
|
{
|
|
int ret = p1->hash - p2->hash;
|
|
if (!ret) ret = p1->aa_flags - p2->aa_flags;
|
|
if (!ret) ret = memcmp( &p1->xform, &p2->xform, sizeof(p1->xform) );
|
|
if (!ret) ret = memcmp( &p1->lf, &p2->lf, FIELD_OFFSET( LOGFONTW, lfFaceName ));
|
|
if (!ret) ret = strcmpiW( p1->lf.lfFaceName, p2->lf.lfFaceName );
|
|
return ret;
|
|
}
|
|
|
|
static struct cached_font *add_cached_font( DC *dc, HFONT hfont, UINT aa_flags )
|
|
{
|
|
struct cached_font font, *ptr, *last_unused = NULL;
|
|
UINT i = 0, j, k;
|
|
|
|
GetObjectW( hfont, sizeof(font.lf), &font.lf );
|
|
font.xform = dc->xformWorld2Vport;
|
|
font.xform.eDx = font.xform.eDy = 0; /* unused, would break hashing */
|
|
if (dc->GraphicsMode == GM_COMPATIBLE)
|
|
{
|
|
font.lf.lfOrientation = font.lf.lfEscapement;
|
|
if (font.xform.eM11 * font.xform.eM22 < 0)
|
|
font.lf.lfOrientation = -font.lf.lfOrientation;
|
|
}
|
|
font.lf.lfWidth = abs( font.lf.lfWidth );
|
|
font.aa_flags = aa_flags;
|
|
font.hash = font_cache_hash( &font );
|
|
|
|
EnterCriticalSection( &font_cache_cs );
|
|
LIST_FOR_EACH_ENTRY( ptr, &font_cache, struct cached_font, entry )
|
|
{
|
|
if (!font_cache_cmp( &font, ptr ))
|
|
{
|
|
InterlockedIncrement( &ptr->ref );
|
|
list_remove( &ptr->entry );
|
|
goto done;
|
|
}
|
|
if (!ptr->ref)
|
|
{
|
|
i++;
|
|
last_unused = ptr;
|
|
}
|
|
}
|
|
|
|
if (i > 5) /* keep at least 5 of the most-recently used fonts around */
|
|
{
|
|
ptr = last_unused;
|
|
for (i = 0; i < GLYPH_NBTYPES; i++)
|
|
{
|
|
for (j = 0; j < GLYPH_CACHE_PAGES; j++)
|
|
{
|
|
if (!ptr->glyphs[i][j]) continue;
|
|
for (k = 0; k < GLYPH_CACHE_PAGE_SIZE; k++)
|
|
HeapFree( GetProcessHeap(), 0, ptr->glyphs[i][j][k] );
|
|
HeapFree( GetProcessHeap(), 0, ptr->glyphs[i][j] );
|
|
}
|
|
}
|
|
list_remove( &ptr->entry );
|
|
}
|
|
else if (!(ptr = HeapAlloc( GetProcessHeap(), 0, sizeof(*ptr) )))
|
|
{
|
|
LeaveCriticalSection( &font_cache_cs );
|
|
return NULL;
|
|
}
|
|
|
|
*ptr = font;
|
|
ptr->ref = 1;
|
|
memset( ptr->glyphs, 0, sizeof(ptr->glyphs) );
|
|
done:
|
|
list_add_head( &font_cache, &ptr->entry );
|
|
LeaveCriticalSection( &font_cache_cs );
|
|
TRACE( "%d %s -> %p\n", ptr->lf.lfHeight, debugstr_w(ptr->lf.lfFaceName), ptr );
|
|
return ptr;
|
|
}
|
|
|
|
void release_cached_font( struct cached_font *font )
|
|
{
|
|
if (font) InterlockedDecrement( &font->ref );
|
|
}
|
|
|
|
static struct cached_glyph *add_cached_glyph( struct cached_font *font, UINT index, UINT flags,
|
|
struct cached_glyph *glyph )
|
|
{
|
|
struct cached_glyph *ret;
|
|
enum glyph_type type = (flags & ETO_GLYPH_INDEX) ? GLYPH_INDEX : GLYPH_WCHAR;
|
|
UINT page = index / GLYPH_CACHE_PAGE_SIZE;
|
|
UINT entry = index % GLYPH_CACHE_PAGE_SIZE;
|
|
|
|
if (!font->glyphs[type][page])
|
|
{
|
|
struct cached_glyph **ptr;
|
|
|
|
ptr = HeapAlloc( GetProcessHeap(), HEAP_ZERO_MEMORY, GLYPH_CACHE_PAGE_SIZE * sizeof(*ptr) );
|
|
if (!ptr)
|
|
{
|
|
HeapFree( GetProcessHeap(), 0, glyph );
|
|
return NULL;
|
|
}
|
|
if (InterlockedCompareExchangePointer( (void **)&font->glyphs[type][page], ptr, NULL ))
|
|
HeapFree( GetProcessHeap(), 0, ptr );
|
|
}
|
|
ret = InterlockedCompareExchangePointer( (void **)&font->glyphs[type][page][entry], glyph, NULL );
|
|
if (!ret) ret = glyph;
|
|
else HeapFree( GetProcessHeap(), 0, glyph );
|
|
return ret;
|
|
}
|
|
|
|
static struct cached_glyph *get_cached_glyph( struct cached_font *font, UINT index, UINT flags )
|
|
{
|
|
enum glyph_type type = (flags & ETO_GLYPH_INDEX) ? GLYPH_INDEX : GLYPH_WCHAR;
|
|
UINT page = index / GLYPH_CACHE_PAGE_SIZE;
|
|
|
|
if (!font->glyphs[type][page]) return NULL;
|
|
return font->glyphs[type][page][index % GLYPH_CACHE_PAGE_SIZE];
|
|
}
|
|
|
|
/**********************************************************************
|
|
* get_text_bkgnd_masks
|
|
*
|
|
* See the comment above get_pen_bkgnd_masks
|
|
*/
|
|
static inline void get_text_bkgnd_masks( DC *dc, const dib_info *dib, rop_mask *mask )
|
|
{
|
|
COLORREF bg = dc->backgroundColor;
|
|
|
|
mask->and = 0;
|
|
|
|
if (dib->bit_count != 1)
|
|
mask->xor = get_pixel_color( dc, dib, bg, FALSE );
|
|
else
|
|
{
|
|
COLORREF fg = dc->textColor;
|
|
mask->xor = get_pixel_color( dc, dib, fg, TRUE );
|
|
if (fg != bg) mask->xor = ~mask->xor;
|
|
}
|
|
}
|
|
|
|
static void draw_glyph( dib_info *dib, int x, int y, const GLYPHMETRICS *metrics,
|
|
const dib_info *glyph_dib, DWORD text_color,
|
|
const struct intensity_range *ranges, const struct clipped_rects *clipped_rects,
|
|
RECT *bounds )
|
|
{
|
|
int i;
|
|
RECT rect, clipped_rect;
|
|
POINT src_origin;
|
|
|
|
rect.left = x + metrics->gmptGlyphOrigin.x;
|
|
rect.top = y - metrics->gmptGlyphOrigin.y;
|
|
rect.right = rect.left + metrics->gmBlackBoxX;
|
|
rect.bottom = rect.top + metrics->gmBlackBoxY;
|
|
if (bounds) add_bounds_rect( bounds, &rect );
|
|
|
|
for (i = 0; i < clipped_rects->count; i++)
|
|
{
|
|
if (intersect_rect( &clipped_rect, &rect, clipped_rects->rects + i ))
|
|
{
|
|
src_origin.x = clipped_rect.left - rect.left;
|
|
src_origin.y = clipped_rect.top - rect.top;
|
|
|
|
if (glyph_dib->bit_count == 32)
|
|
dib->funcs->draw_subpixel_glyph( dib, &clipped_rect, glyph_dib, &src_origin,
|
|
text_color );
|
|
else
|
|
dib->funcs->draw_glyph( dib, &clipped_rect, glyph_dib, &src_origin,
|
|
text_color, ranges );
|
|
}
|
|
}
|
|
}
|
|
|
|
static int get_glyph_depth( UINT aa_flags )
|
|
{
|
|
switch (aa_flags)
|
|
{
|
|
case GGO_BITMAP: /* we'll convert non-antialiased 1-bpp bitmaps to 8-bpp */
|
|
case GGO_GRAY2_BITMAP:
|
|
case GGO_GRAY4_BITMAP:
|
|
case GGO_GRAY8_BITMAP:
|
|
case WINE_GGO_GRAY16_BITMAP: return 8;
|
|
|
|
case WINE_GGO_HRGB_BITMAP:
|
|
case WINE_GGO_HBGR_BITMAP:
|
|
case WINE_GGO_VRGB_BITMAP:
|
|
case WINE_GGO_VBGR_BITMAP: return 32;
|
|
|
|
default:
|
|
ERR("Unexpected flags %08x\n", aa_flags);
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
static const BYTE masks[8] = {0x80, 0x40, 0x20, 0x10, 0x08, 0x04, 0x02, 0x01};
|
|
static const int padding[4] = {0, 3, 2, 1};
|
|
|
|
/***********************************************************************
|
|
* cache_glyph_bitmap
|
|
*
|
|
* Retrieve a 17-level bitmap for the appropriate glyph.
|
|
*
|
|
* For non-antialiased bitmaps convert them to the 17-level format
|
|
* using only values 0 or 16.
|
|
*/
|
|
static struct cached_glyph *cache_glyph_bitmap( DC *dc, struct cached_font *font, UINT index, UINT flags )
|
|
{
|
|
UINT ggo_flags = font->aa_flags;
|
|
static const MAT2 identity = { {0,1}, {0,0}, {0,0}, {0,1} };
|
|
UINT indices[3] = {0, 0, 0x20};
|
|
int i, x, y;
|
|
DWORD ret, size;
|
|
BYTE *dst, *src;
|
|
int pad = 0, stride, bit_count;
|
|
GLYPHMETRICS metrics;
|
|
struct cached_glyph *glyph;
|
|
|
|
if (flags & ETO_GLYPH_INDEX) ggo_flags |= GGO_GLYPH_INDEX;
|
|
indices[0] = index;
|
|
for (i = 0; i < sizeof(indices) / sizeof(indices[0]); i++)
|
|
{
|
|
index = indices[i];
|
|
ret = GetGlyphOutlineW( dc->hSelf, index, ggo_flags, &metrics, 0, NULL, &identity );
|
|
if (ret != GDI_ERROR) break;
|
|
}
|
|
if (ret == GDI_ERROR) return NULL;
|
|
if (!ret) metrics.gmBlackBoxX = metrics.gmBlackBoxY = 0; /* empty glyph */
|
|
|
|
bit_count = get_glyph_depth( font->aa_flags );
|
|
stride = get_dib_stride( metrics.gmBlackBoxX, bit_count );
|
|
size = metrics.gmBlackBoxY * stride;
|
|
glyph = HeapAlloc( GetProcessHeap(), 0, FIELD_OFFSET( struct cached_glyph, bits[size] ));
|
|
if (!glyph) return NULL;
|
|
if (!size) goto done; /* empty glyph */
|
|
|
|
if (bit_count == 8) pad = padding[ metrics.gmBlackBoxX % 4 ];
|
|
|
|
ret = GetGlyphOutlineW( dc->hSelf, index, ggo_flags, &metrics, size, glyph->bits, &identity );
|
|
if (ret == GDI_ERROR)
|
|
{
|
|
HeapFree( GetProcessHeap(), 0, glyph );
|
|
return NULL;
|
|
}
|
|
assert( ret <= size );
|
|
if (font->aa_flags == GGO_BITMAP)
|
|
{
|
|
for (y = metrics.gmBlackBoxY - 1; y >= 0; y--)
|
|
{
|
|
src = glyph->bits + y * get_dib_stride( metrics.gmBlackBoxX, 1 );
|
|
dst = glyph->bits + y * stride;
|
|
|
|
if (pad) memset( dst + metrics.gmBlackBoxX, 0, pad );
|
|
|
|
for (x = metrics.gmBlackBoxX - 1; x >= 0; x--)
|
|
dst[x] = (src[x / 8] & masks[x % 8]) ? 0x10 : 0;
|
|
}
|
|
}
|
|
else if (pad)
|
|
{
|
|
for (y = 0, dst = glyph->bits; y < metrics.gmBlackBoxY; y++, dst += stride)
|
|
memset( dst + metrics.gmBlackBoxX, 0, pad );
|
|
}
|
|
|
|
done:
|
|
glyph->metrics = metrics;
|
|
return add_cached_glyph( font, index, flags, glyph );
|
|
}
|
|
|
|
static void render_string( DC *dc, dib_info *dib, struct cached_font *font, INT x, INT y,
|
|
UINT flags, const WCHAR *str, UINT count, const INT *dx,
|
|
const struct clipped_rects *clipped_rects, RECT *bounds )
|
|
{
|
|
UINT i;
|
|
struct cached_glyph *glyph;
|
|
dib_info glyph_dib;
|
|
DWORD text_color;
|
|
struct intensity_range ranges[17];
|
|
|
|
glyph_dib.bit_count = get_glyph_depth( font->aa_flags );
|
|
glyph_dib.rect.left = 0;
|
|
glyph_dib.rect.top = 0;
|
|
glyph_dib.bits.is_copy = FALSE;
|
|
glyph_dib.bits.free = NULL;
|
|
|
|
text_color = get_pixel_color( dc, dib, dc->textColor, TRUE );
|
|
|
|
if (glyph_dib.bit_count == 8)
|
|
get_aa_ranges( dib->funcs->pixel_to_colorref( dib, text_color ), ranges );
|
|
|
|
for (i = 0; i < count; i++)
|
|
{
|
|
if (!(glyph = get_cached_glyph( font, str[i], flags )) &&
|
|
!(glyph = cache_glyph_bitmap( dc, font, str[i], flags ))) continue;
|
|
|
|
glyph_dib.width = glyph->metrics.gmBlackBoxX;
|
|
glyph_dib.height = glyph->metrics.gmBlackBoxY;
|
|
glyph_dib.rect.right = glyph->metrics.gmBlackBoxX;
|
|
glyph_dib.rect.bottom = glyph->metrics.gmBlackBoxY;
|
|
glyph_dib.stride = get_dib_stride( glyph->metrics.gmBlackBoxX, glyph_dib.bit_count );
|
|
glyph_dib.bits.ptr = glyph->bits;
|
|
|
|
draw_glyph( dib, x, y, &glyph->metrics, &glyph_dib, text_color, ranges, clipped_rects, bounds );
|
|
|
|
if (dx)
|
|
{
|
|
if (flags & ETO_PDY)
|
|
{
|
|
x += dx[ i * 2 ];
|
|
y += dx[ i * 2 + 1];
|
|
}
|
|
else
|
|
x += dx[ i ];
|
|
}
|
|
else
|
|
{
|
|
x += glyph->metrics.gmCellIncX;
|
|
y += glyph->metrics.gmCellIncY;
|
|
}
|
|
}
|
|
}
|
|
|
|
BOOL render_aa_text_bitmapinfo( DC *dc, BITMAPINFO *info, struct gdi_image_bits *bits,
|
|
struct bitblt_coords *src, INT x, INT y, UINT flags,
|
|
UINT aa_flags, LPCWSTR str, UINT count, const INT *dx )
|
|
{
|
|
dib_info dib;
|
|
struct clipped_rects visrect;
|
|
struct cached_font *font;
|
|
|
|
assert( info->bmiHeader.biBitCount > 8 ); /* mono and indexed formats don't support anti-aliasing */
|
|
|
|
init_dib_info_from_bitmapinfo( &dib, info, bits->ptr );
|
|
|
|
visrect.count = 1;
|
|
visrect.rects = &src->visrect;
|
|
|
|
if (flags & ETO_OPAQUE)
|
|
{
|
|
rop_mask bkgnd_color;
|
|
get_text_bkgnd_masks( dc, &dib, &bkgnd_color );
|
|
dib.funcs->solid_rects( &dib, 1, &src->visrect, bkgnd_color.and, bkgnd_color.xor );
|
|
}
|
|
|
|
if (!(font = add_cached_font( dc, dc->hFont, aa_flags ))) return FALSE;
|
|
|
|
render_string( dc, &dib, font, x, y, flags, str, count, dx, &visrect, NULL );
|
|
release_cached_font( font );
|
|
return TRUE;
|
|
}
|
|
|
|
/***********************************************************************
|
|
* dibdrv_ExtTextOut
|
|
*/
|
|
BOOL dibdrv_ExtTextOut( PHYSDEV dev, INT x, INT y, UINT flags,
|
|
const RECT *rect, LPCWSTR str, UINT count, const INT *dx )
|
|
{
|
|
dibdrv_physdev *pdev = get_dibdrv_pdev(dev);
|
|
DC *dc = get_physdev_dc( dev );
|
|
struct clipped_rects clipped_rects;
|
|
RECT bounds;
|
|
|
|
if (!pdev->font) return FALSE;
|
|
|
|
init_clipped_rects( &clipped_rects );
|
|
reset_bounds( &bounds );
|
|
|
|
if (flags & ETO_OPAQUE)
|
|
{
|
|
rop_mask bkgnd_color;
|
|
get_text_bkgnd_masks( dc, &pdev->dib, &bkgnd_color );
|
|
add_bounds_rect( &bounds, rect );
|
|
get_clipped_rects( &pdev->dib, rect, pdev->clip, &clipped_rects );
|
|
pdev->dib.funcs->solid_rects( &pdev->dib, clipped_rects.count, clipped_rects.rects,
|
|
bkgnd_color.and, bkgnd_color.xor );
|
|
}
|
|
|
|
if (count == 0) goto done;
|
|
|
|
if (flags & ETO_CLIPPED)
|
|
{
|
|
if (!(flags & ETO_OPAQUE)) /* otherwise we have done it already */
|
|
get_clipped_rects( &pdev->dib, rect, pdev->clip, &clipped_rects );
|
|
}
|
|
else
|
|
{
|
|
free_clipped_rects( &clipped_rects );
|
|
get_clipped_rects( &pdev->dib, NULL, pdev->clip, &clipped_rects );
|
|
}
|
|
if (!clipped_rects.count) goto done;
|
|
|
|
render_string( dc, &pdev->dib, pdev->font, x, y, flags, str, count, dx,
|
|
&clipped_rects, &bounds );
|
|
|
|
done:
|
|
add_clipped_bounds( pdev, &bounds, pdev->clip );
|
|
free_clipped_rects( &clipped_rects );
|
|
return TRUE;
|
|
}
|
|
|
|
/***********************************************************************
|
|
* dibdrv_SelectFont
|
|
*/
|
|
HFONT dibdrv_SelectFont( PHYSDEV dev, HFONT font, UINT *aa_flags )
|
|
{
|
|
dibdrv_physdev *pdev = get_dibdrv_pdev(dev);
|
|
DC *dc = get_physdev_dc( dev );
|
|
HFONT ret;
|
|
|
|
if (pdev->dib.bit_count <= 8) *aa_flags = GGO_BITMAP; /* no anti-aliasing on <= 8bpp */
|
|
|
|
dev = GET_NEXT_PHYSDEV( dev, pSelectFont );
|
|
ret = dev->funcs->pSelectFont( dev, font, aa_flags );
|
|
if (ret)
|
|
{
|
|
struct cached_font *prev = pdev->font;
|
|
pdev->font = add_cached_font( dc, font, *aa_flags ? *aa_flags : GGO_BITMAP );
|
|
release_cached_font( prev );
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
/***********************************************************************
|
|
* dibdrv_Arc
|
|
*/
|
|
BOOL dibdrv_Arc( PHYSDEV dev, INT left, INT top, INT right, INT bottom,
|
|
INT start_x, INT start_y, INT end_x, INT end_y )
|
|
{
|
|
return draw_arc( dev, left, top, right, bottom, start_x, start_y, end_x, end_y, 0 );
|
|
}
|
|
|
|
/***********************************************************************
|
|
* dibdrv_ArcTo
|
|
*/
|
|
BOOL dibdrv_ArcTo( PHYSDEV dev, INT left, INT top, INT right, INT bottom,
|
|
INT start_x, INT start_y, INT end_x, INT end_y )
|
|
{
|
|
return draw_arc( dev, left, top, right, bottom, start_x, start_y, end_x, end_y, -1 );
|
|
}
|
|
|
|
/***********************************************************************
|
|
* dibdrv_Chord
|
|
*/
|
|
BOOL dibdrv_Chord( PHYSDEV dev, INT left, INT top, INT right, INT bottom,
|
|
INT start_x, INT start_y, INT end_x, INT end_y )
|
|
{
|
|
return draw_arc( dev, left, top, right, bottom, start_x, start_y, end_x, end_y, 1 );
|
|
}
|
|
|
|
/***********************************************************************
|
|
* dibdrv_Ellipse
|
|
*/
|
|
BOOL dibdrv_Ellipse( PHYSDEV dev, INT left, INT top, INT right, INT bottom )
|
|
{
|
|
return dibdrv_RoundRect( dev, left, top, right, bottom, right - left, bottom - top );
|
|
}
|
|
|
|
static inline BOOL is_interior( dib_info *dib, HRGN clip, int x, int y, DWORD pixel, UINT type)
|
|
{
|
|
/* the clip rgn stops the flooding */
|
|
if (clip && !PtInRegion( clip, x, y )) return FALSE;
|
|
|
|
if (type == FLOODFILLBORDER)
|
|
return dib->funcs->get_pixel( dib, x, y ) != pixel;
|
|
else
|
|
return dib->funcs->get_pixel( dib, x, y ) == pixel;
|
|
}
|
|
|
|
static void fill_row( dib_info *dib, HRGN clip, RECT *row, DWORD pixel, UINT type, HRGN rgn );
|
|
|
|
static inline void do_next_row( dib_info *dib, HRGN clip, const RECT *row, int offset, DWORD pixel, UINT type, HRGN rgn )
|
|
{
|
|
RECT next;
|
|
|
|
next.top = row->top + offset;
|
|
next.bottom = next.top + 1;
|
|
next.left = next.right = row->left;
|
|
while (next.right < row->right)
|
|
{
|
|
if (is_interior( dib, clip, next.right, next.top, pixel, type)) next.right++;
|
|
else
|
|
{
|
|
if (next.left != next.right && !PtInRegion( rgn, next.left, next.top ))
|
|
fill_row( dib, clip, &next, pixel, type, rgn );
|
|
next.left = ++next.right;
|
|
}
|
|
}
|
|
if (next.left != next.right && !PtInRegion( rgn, next.left, next.top ))
|
|
fill_row( dib, clip, &next, pixel, type, rgn );
|
|
}
|
|
|
|
static void fill_row( dib_info *dib, HRGN clip, RECT *row, DWORD pixel, UINT type, HRGN rgn )
|
|
{
|
|
while (row->left > 0 && is_interior( dib, clip, row->left - 1, row->top, pixel, type)) row->left--;
|
|
while (row->right < dib->rect.right - dib->rect.left &&
|
|
is_interior( dib, clip, row->right, row->top, pixel, type))
|
|
row->right++;
|
|
|
|
add_rect_to_region( rgn, row );
|
|
|
|
if (row->top > 0) do_next_row( dib, clip, row, -1, pixel, type, rgn );
|
|
if (row->top < dib->rect.bottom - dib->rect.top - 1)
|
|
do_next_row( dib, clip, row, 1, pixel, type, rgn );
|
|
}
|
|
|
|
/***********************************************************************
|
|
* dibdrv_ExtFloodFill
|
|
*/
|
|
BOOL dibdrv_ExtFloodFill( PHYSDEV dev, INT x, INT y, COLORREF color, UINT type )
|
|
{
|
|
dibdrv_physdev *pdev = get_dibdrv_pdev( dev );
|
|
DC *dc = get_physdev_dc( dev );
|
|
DWORD pixel = get_pixel_color( dc, &pdev->dib, color, FALSE );
|
|
RECT row;
|
|
HRGN rgn;
|
|
|
|
TRACE( "(%p, %d, %d, %08x, %d)\n", pdev, x, y, color, type );
|
|
|
|
if (x < 0 || x >= pdev->dib.rect.right - pdev->dib.rect.left ||
|
|
y < 0 || y >= pdev->dib.rect.bottom - pdev->dib.rect.top) return FALSE;
|
|
|
|
if (!is_interior( &pdev->dib, pdev->clip, x, y, pixel, type )) return FALSE;
|
|
|
|
if (!(rgn = CreateRectRgn( 0, 0, 0, 0 ))) return FALSE;
|
|
row.left = x;
|
|
row.right = x + 1;
|
|
row.top = y;
|
|
row.bottom = y + 1;
|
|
|
|
fill_row( &pdev->dib, pdev->clip, &row, pixel, type, rgn );
|
|
|
|
add_clipped_bounds( pdev, NULL, rgn );
|
|
brush_region( pdev, rgn );
|
|
|
|
DeleteObject( rgn );
|
|
return TRUE;
|
|
}
|
|
|
|
/***********************************************************************
|
|
* dibdrv_FillPath
|
|
*/
|
|
BOOL dibdrv_FillPath( PHYSDEV dev )
|
|
{
|
|
dibdrv_physdev *pdev = get_dibdrv_pdev( dev );
|
|
|
|
return stroke_and_fill_path( pdev, FALSE, TRUE );
|
|
}
|
|
|
|
/***********************************************************************
|
|
* dibdrv_GetNearestColor
|
|
*/
|
|
COLORREF dibdrv_GetNearestColor( PHYSDEV dev, COLORREF color )
|
|
{
|
|
dibdrv_physdev *pdev = get_dibdrv_pdev( dev );
|
|
DC *dc = get_physdev_dc( dev );
|
|
DWORD pixel;
|
|
|
|
TRACE( "(%p, %08x)\n", dev, color );
|
|
|
|
pixel = get_pixel_color( dc, &pdev->dib, color, FALSE );
|
|
return pdev->dib.funcs->pixel_to_colorref( &pdev->dib, pixel );
|
|
}
|
|
|
|
/***********************************************************************
|
|
* dibdrv_GetPixel
|
|
*/
|
|
COLORREF dibdrv_GetPixel( PHYSDEV dev, INT x, INT y )
|
|
{
|
|
dibdrv_physdev *pdev = get_dibdrv_pdev( dev );
|
|
DC *dc = get_physdev_dc( dev );
|
|
POINT pt;
|
|
DWORD pixel;
|
|
|
|
TRACE( "(%p, %d, %d)\n", dev, x, y );
|
|
|
|
pt.x = x;
|
|
pt.y = y;
|
|
lp_to_dp( dc, &pt, 1 );
|
|
|
|
if (pt.x < 0 || pt.x >= pdev->dib.rect.right - pdev->dib.rect.left ||
|
|
pt.y < 0 || pt.y >= pdev->dib.rect.bottom - pdev->dib.rect.top)
|
|
return CLR_INVALID;
|
|
|
|
pixel = pdev->dib.funcs->get_pixel( &pdev->dib, pt.x, pt.y );
|
|
return pdev->dib.funcs->pixel_to_colorref( &pdev->dib, pixel );
|
|
}
|
|
|
|
/***********************************************************************
|
|
* dibdrv_LineTo
|
|
*/
|
|
BOOL dibdrv_LineTo( PHYSDEV dev, INT x, INT y )
|
|
{
|
|
dibdrv_physdev *pdev = get_dibdrv_pdev(dev);
|
|
DC *dc = get_physdev_dc( dev );
|
|
POINT pts[2];
|
|
HRGN region = 0;
|
|
BOOL ret;
|
|
|
|
pts[0] = dc->cur_pos;
|
|
pts[1].x = x;
|
|
pts[1].y = y;
|
|
|
|
lp_to_dp(dc, pts, 2);
|
|
|
|
if (pdev->pen_uses_region && !(region = CreateRectRgn( 0, 0, 0, 0 ))) return FALSE;
|
|
|
|
reset_dash_origin(pdev);
|
|
|
|
ret = pdev->pen_lines(pdev, 2, pts, FALSE, region);
|
|
add_pen_lines_bounds( pdev, 2, pts, region );
|
|
|
|
if (region)
|
|
{
|
|
ret = pen_region( pdev, region );
|
|
DeleteObject( region );
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
/***********************************************************************
|
|
* get_rop2_from_rop
|
|
*
|
|
* Returns the binary rop that is equivalent to the provided ternary rop
|
|
* if the src bits are ignored.
|
|
*/
|
|
static inline INT get_rop2_from_rop(INT rop)
|
|
{
|
|
return (((rop >> 18) & 0x0c) | ((rop >> 16) & 0x03)) + 1;
|
|
}
|
|
|
|
/***********************************************************************
|
|
* dibdrv_PatBlt
|
|
*/
|
|
BOOL dibdrv_PatBlt( PHYSDEV dev, struct bitblt_coords *dst, DWORD rop )
|
|
{
|
|
dibdrv_physdev *pdev = get_dibdrv_pdev(dev);
|
|
dib_brush *brush = &pdev->brush;
|
|
int rop2 = get_rop2_from_rop( rop );
|
|
struct clipped_rects clipped_rects;
|
|
DWORD and = 0, xor = 0;
|
|
BOOL ret = TRUE;
|
|
|
|
TRACE("(%p, %d, %d, %d, %d, %06x)\n", dev, dst->x, dst->y, dst->width, dst->height, rop);
|
|
|
|
add_clipped_bounds( pdev, &dst->visrect, 0 );
|
|
if (!get_clipped_rects( &pdev->dib, &dst->visrect, pdev->clip, &clipped_rects )) return TRUE;
|
|
|
|
switch (rop2) /* shortcuts for rops that don't involve the brush */
|
|
{
|
|
case R2_NOT: and = ~0u;
|
|
/* fall through */
|
|
case R2_WHITE: xor = ~0u;
|
|
/* fall through */
|
|
case R2_BLACK:
|
|
pdev->dib.funcs->solid_rects( &pdev->dib, clipped_rects.count, clipped_rects.rects, and, xor );
|
|
/* fall through */
|
|
case R2_NOP:
|
|
break;
|
|
default:
|
|
ret = brush->rects( pdev, brush, &pdev->dib, clipped_rects.count, clipped_rects.rects, rop2 );
|
|
break;
|
|
}
|
|
free_clipped_rects( &clipped_rects );
|
|
return ret;
|
|
}
|
|
|
|
/***********************************************************************
|
|
* dibdrv_PaintRgn
|
|
*/
|
|
BOOL dibdrv_PaintRgn( PHYSDEV dev, HRGN rgn )
|
|
{
|
|
dibdrv_physdev *pdev = get_dibdrv_pdev(dev);
|
|
const WINEREGION *region;
|
|
int i;
|
|
RECT rect, bounds;
|
|
DC *dc = get_physdev_dc( dev );
|
|
|
|
TRACE("%p, %p\n", dev, rgn);
|
|
|
|
reset_bounds( &bounds );
|
|
|
|
region = get_wine_region( rgn );
|
|
if(!region) return FALSE;
|
|
|
|
for(i = 0; i < region->numRects; i++)
|
|
{
|
|
rect = get_device_rect( dc, region->rects[i].left, region->rects[i].top,
|
|
region->rects[i].right, region->rects[i].bottom, FALSE );
|
|
add_bounds_rect( &bounds, &rect );
|
|
brush_rect( pdev, &pdev->brush, &rect, pdev->clip );
|
|
}
|
|
|
|
release_wine_region( rgn );
|
|
add_clipped_bounds( pdev, &bounds, pdev->clip );
|
|
return TRUE;
|
|
}
|
|
|
|
/***********************************************************************
|
|
* dibdrv_PolyPolygon
|
|
*/
|
|
BOOL dibdrv_PolyPolygon( PHYSDEV dev, const POINT *pt, const INT *counts, DWORD polygons )
|
|
{
|
|
dibdrv_physdev *pdev = get_dibdrv_pdev(dev);
|
|
DC *dc = get_physdev_dc( dev );
|
|
DWORD total, i, pos;
|
|
BOOL ret = TRUE;
|
|
POINT pt_buf[32];
|
|
POINT *points = pt_buf;
|
|
HRGN outline = 0, interior = 0;
|
|
|
|
for (i = total = 0; i < polygons; i++)
|
|
{
|
|
if (counts[i] < 2) return FALSE;
|
|
total += counts[i];
|
|
}
|
|
|
|
if (total > sizeof(pt_buf) / sizeof(pt_buf[0]))
|
|
{
|
|
points = HeapAlloc( GetProcessHeap(), 0, total * sizeof(*pt) );
|
|
if (!points) return FALSE;
|
|
}
|
|
memcpy( points, pt, total * sizeof(*pt) );
|
|
lp_to_dp( dc, points, total );
|
|
|
|
if (pdev->brush.style != BS_NULL &&
|
|
!(interior = CreatePolyPolygonRgn( points, counts, polygons, dc->polyFillMode )))
|
|
{
|
|
ret = FALSE;
|
|
goto done;
|
|
}
|
|
|
|
if (pdev->pen_uses_region) outline = CreateRectRgn( 0, 0, 0, 0 );
|
|
|
|
/* if not using a region, paint the interior first so the outline can overlap it */
|
|
if (interior && !outline)
|
|
{
|
|
ret = brush_region( pdev, interior );
|
|
DeleteObject( interior );
|
|
interior = 0;
|
|
}
|
|
|
|
for (i = pos = 0; i < polygons; i++)
|
|
{
|
|
reset_dash_origin( pdev );
|
|
pdev->pen_lines( pdev, counts[i], points + pos, TRUE, outline );
|
|
pos += counts[i];
|
|
}
|
|
add_pen_lines_bounds( pdev, total, points, outline );
|
|
|
|
if (interior)
|
|
{
|
|
CombineRgn( interior, interior, outline, RGN_DIFF );
|
|
ret = brush_region( pdev, interior );
|
|
DeleteObject( interior );
|
|
}
|
|
if (outline)
|
|
{
|
|
if (ret) ret = pen_region( pdev, outline );
|
|
DeleteObject( outline );
|
|
}
|
|
|
|
done:
|
|
if (points != pt_buf) HeapFree( GetProcessHeap(), 0, points );
|
|
return ret;
|
|
}
|
|
|
|
/***********************************************************************
|
|
* dibdrv_PolyPolyline
|
|
*/
|
|
BOOL dibdrv_PolyPolyline( PHYSDEV dev, const POINT* pt, const DWORD* counts, DWORD polylines )
|
|
{
|
|
dibdrv_physdev *pdev = get_dibdrv_pdev(dev);
|
|
DC *dc = get_physdev_dc( dev );
|
|
DWORD total, pos, i;
|
|
POINT pt_buf[32];
|
|
POINT *points = pt_buf;
|
|
BOOL ret = TRUE;
|
|
HRGN outline = 0;
|
|
|
|
for (i = total = 0; i < polylines; i++)
|
|
{
|
|
if (counts[i] < 2) return FALSE;
|
|
total += counts[i];
|
|
}
|
|
|
|
if (total > sizeof(pt_buf) / sizeof(pt_buf[0]))
|
|
{
|
|
points = HeapAlloc( GetProcessHeap(), 0, total * sizeof(*pt) );
|
|
if (!points) return FALSE;
|
|
}
|
|
memcpy( points, pt, total * sizeof(*pt) );
|
|
lp_to_dp( dc, points, total );
|
|
|
|
if (pdev->pen_uses_region && !(outline = CreateRectRgn( 0, 0, 0, 0 )))
|
|
{
|
|
ret = FALSE;
|
|
goto done;
|
|
}
|
|
|
|
for (i = pos = 0; i < polylines; i++)
|
|
{
|
|
reset_dash_origin( pdev );
|
|
pdev->pen_lines( pdev, counts[i], points + pos, FALSE, outline );
|
|
pos += counts[i];
|
|
}
|
|
add_pen_lines_bounds( pdev, total, points, outline );
|
|
|
|
if (outline)
|
|
{
|
|
ret = pen_region( pdev, outline );
|
|
DeleteObject( outline );
|
|
}
|
|
|
|
done:
|
|
if (points != pt_buf) HeapFree( GetProcessHeap(), 0, points );
|
|
return ret;
|
|
}
|
|
|
|
/***********************************************************************
|
|
* dibdrv_Polygon
|
|
*/
|
|
BOOL dibdrv_Polygon( PHYSDEV dev, const POINT *pt, INT count )
|
|
{
|
|
INT counts[1] = { count };
|
|
|
|
return dibdrv_PolyPolygon( dev, pt, counts, 1 );
|
|
}
|
|
|
|
/***********************************************************************
|
|
* dibdrv_Polyline
|
|
*/
|
|
BOOL dibdrv_Polyline( PHYSDEV dev, const POINT* pt, INT count )
|
|
{
|
|
DWORD counts[1] = { count };
|
|
|
|
if (count < 0) return FALSE;
|
|
return dibdrv_PolyPolyline( dev, pt, counts, 1 );
|
|
}
|
|
|
|
/***********************************************************************
|
|
* dibdrv_Rectangle
|
|
*/
|
|
BOOL dibdrv_Rectangle( PHYSDEV dev, INT left, INT top, INT right, INT bottom )
|
|
{
|
|
dibdrv_physdev *pdev = get_dibdrv_pdev(dev);
|
|
DC *dc = get_physdev_dc( dev );
|
|
RECT rect;
|
|
POINT pts[4];
|
|
BOOL ret;
|
|
HRGN outline = 0;
|
|
|
|
TRACE("(%p, %d, %d, %d, %d)\n", dev, left, top, right, bottom);
|
|
|
|
if (dc->GraphicsMode == GM_ADVANCED)
|
|
{
|
|
pts[0].x = pts[3].x = left;
|
|
pts[0].y = pts[1].y = top;
|
|
pts[1].x = pts[2].x = right;
|
|
pts[2].y = pts[3].y = bottom;
|
|
return dibdrv_Polygon( dev, pts, 4 );
|
|
}
|
|
|
|
if (!get_pen_device_rect( dc, pdev, &rect, left, top, right, bottom )) return TRUE;
|
|
|
|
if (pdev->pen_uses_region && !(outline = CreateRectRgn( 0, 0, 0, 0 ))) return FALSE;
|
|
|
|
rect.right--;
|
|
rect.bottom--;
|
|
reset_dash_origin(pdev);
|
|
|
|
if (dc->ArcDirection == AD_CLOCKWISE)
|
|
{
|
|
/* 4 pts going clockwise starting from bottom-right */
|
|
pts[0].x = pts[3].x = rect.right;
|
|
pts[0].y = pts[1].y = rect.bottom;
|
|
pts[1].x = pts[2].x = rect.left;
|
|
pts[2].y = pts[3].y = rect.top;
|
|
}
|
|
else
|
|
{
|
|
/* 4 pts going anti-clockwise starting from top-right */
|
|
pts[0].x = pts[3].x = rect.right;
|
|
pts[0].y = pts[1].y = rect.top;
|
|
pts[1].x = pts[2].x = rect.left;
|
|
pts[2].y = pts[3].y = rect.bottom;
|
|
}
|
|
|
|
pdev->pen_lines(pdev, 4, pts, TRUE, outline);
|
|
add_pen_lines_bounds( pdev, 4, pts, outline );
|
|
|
|
if (outline)
|
|
{
|
|
if (pdev->brush.style != BS_NULL)
|
|
{
|
|
HRGN interior = CreateRectRgnIndirect( &rect );
|
|
|
|
CombineRgn( interior, interior, outline, RGN_DIFF );
|
|
brush_region( pdev, interior );
|
|
DeleteObject( interior );
|
|
}
|
|
ret = pen_region( pdev, outline );
|
|
DeleteObject( outline );
|
|
}
|
|
else
|
|
{
|
|
rect.left += (pdev->pen_width + 1) / 2;
|
|
rect.top += (pdev->pen_width + 1) / 2;
|
|
rect.right -= pdev->pen_width / 2;
|
|
rect.bottom -= pdev->pen_width / 2;
|
|
ret = brush_rect( pdev, &pdev->brush, &rect, pdev->clip );
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
/***********************************************************************
|
|
* dibdrv_RoundRect
|
|
*/
|
|
BOOL dibdrv_RoundRect( PHYSDEV dev, INT left, INT top, INT right, INT bottom,
|
|
INT ellipse_width, INT ellipse_height )
|
|
{
|
|
dibdrv_physdev *pdev = get_dibdrv_pdev( dev );
|
|
DC *dc = get_physdev_dc( dev );
|
|
RECT rect;
|
|
POINT pt[2], *points;
|
|
int i, end, count;
|
|
BOOL ret = TRUE;
|
|
HRGN outline = 0, interior = 0;
|
|
|
|
if (!get_pen_device_rect( dc, pdev, &rect, left, top, right, bottom )) return TRUE;
|
|
|
|
pt[0].x = pt[0].y = 0;
|
|
pt[1].x = ellipse_width;
|
|
pt[1].y = ellipse_height;
|
|
lp_to_dp( dc, pt, 2 );
|
|
ellipse_width = min( rect.right - rect.left, abs( pt[1].x - pt[0].x ));
|
|
ellipse_height = min( rect.bottom - rect.top, abs( pt[1].y - pt[0].y ));
|
|
if (ellipse_width <= 2|| ellipse_height <= 2)
|
|
return dibdrv_Rectangle( dev, left, top, right, bottom );
|
|
|
|
points = HeapAlloc( GetProcessHeap(), 0, (ellipse_width + ellipse_height) * 2 * sizeof(*points) );
|
|
if (!points) return FALSE;
|
|
|
|
if (pdev->pen_uses_region && !(outline = CreateRectRgn( 0, 0, 0, 0 )))
|
|
{
|
|
HeapFree( GetProcessHeap(), 0, points );
|
|
return FALSE;
|
|
}
|
|
|
|
if (pdev->brush.style != BS_NULL &&
|
|
!(interior = CreateRoundRectRgn( rect.left, rect.top, rect.right + 1, rect.bottom + 1,
|
|
ellipse_width, ellipse_height )))
|
|
{
|
|
HeapFree( GetProcessHeap(), 0, points );
|
|
if (outline) DeleteObject( outline );
|
|
return FALSE;
|
|
}
|
|
|
|
/* if not using a region, paint the interior first so the outline can overlap it */
|
|
if (interior && !outline)
|
|
{
|
|
ret = brush_region( pdev, interior );
|
|
DeleteObject( interior );
|
|
interior = 0;
|
|
}
|
|
|
|
count = ellipse_first_quadrant( ellipse_width, ellipse_height, points );
|
|
|
|
if (dc->ArcDirection == AD_CLOCKWISE)
|
|
{
|
|
for (i = 0; i < count; i++)
|
|
{
|
|
points[i].x = rect.right - ellipse_width + points[i].x;
|
|
points[i].y = rect.bottom - ellipse_height + points[i].y;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
for (i = 0; i < count; i++)
|
|
{
|
|
points[i].x = rect.right - ellipse_width + points[i].x;
|
|
points[i].y = rect.top + ellipse_height - 1 - points[i].y;
|
|
}
|
|
}
|
|
|
|
/* horizontal symmetry */
|
|
|
|
end = 2 * count - 1;
|
|
/* avoid duplicating the midpoint */
|
|
if (ellipse_width % 2 && ellipse_width == rect.right - rect.left) end--;
|
|
for (i = 0; i < count; i++)
|
|
{
|
|
points[end - i].x = rect.left + rect.right - 1 - points[i].x;
|
|
points[end - i].y = points[i].y;
|
|
}
|
|
count = end + 1;
|
|
|
|
/* vertical symmetry */
|
|
|
|
end = 2 * count - 1;
|
|
/* avoid duplicating the midpoint */
|
|
if (ellipse_height % 2 && ellipse_height == rect.bottom - rect.top) end--;
|
|
for (i = 0; i < count; i++)
|
|
{
|
|
points[end - i].x = points[i].x;
|
|
points[end - i].y = rect.top + rect.bottom - 1 - points[i].y;
|
|
}
|
|
count = end + 1;
|
|
|
|
reset_dash_origin( pdev );
|
|
pdev->pen_lines( pdev, count, points, TRUE, outline );
|
|
add_pen_lines_bounds( pdev, count, points, outline );
|
|
|
|
if (interior)
|
|
{
|
|
CombineRgn( interior, interior, outline, RGN_DIFF );
|
|
ret = brush_region( pdev, interior );
|
|
DeleteObject( interior );
|
|
}
|
|
if (outline)
|
|
{
|
|
if (ret) ret = pen_region( pdev, outline );
|
|
DeleteObject( outline );
|
|
}
|
|
HeapFree( GetProcessHeap(), 0, points );
|
|
return ret;
|
|
}
|
|
|
|
/***********************************************************************
|
|
* dibdrv_Pie
|
|
*/
|
|
BOOL dibdrv_Pie( PHYSDEV dev, INT left, INT top, INT right, INT bottom,
|
|
INT start_x, INT start_y, INT end_x, INT end_y )
|
|
{
|
|
return draw_arc( dev, left, top, right, bottom, start_x, start_y, end_x, end_y, 2 );
|
|
}
|
|
|
|
/***********************************************************************
|
|
* dibdrv_SetPixel
|
|
*/
|
|
COLORREF dibdrv_SetPixel( PHYSDEV dev, INT x, INT y, COLORREF color )
|
|
{
|
|
dibdrv_physdev *pdev = get_dibdrv_pdev( dev );
|
|
DC *dc = get_physdev_dc( dev );
|
|
struct clipped_rects clipped_rects;
|
|
RECT rect;
|
|
POINT pt;
|
|
DWORD pixel;
|
|
|
|
TRACE( "(%p, %d, %d, %08x)\n", dev, x, y, color );
|
|
|
|
pt.x = x;
|
|
pt.y = y;
|
|
lp_to_dp( dc, &pt, 1 );
|
|
rect.left = pt.x;
|
|
rect.top = pt.y;
|
|
rect.right = rect.left + 1;
|
|
rect.bottom = rect.top + 1;
|
|
add_clipped_bounds( pdev, &rect, pdev->clip );
|
|
|
|
/* SetPixel doesn't do the 1bpp massaging like other fg colors */
|
|
pixel = get_pixel_color( dc, &pdev->dib, color, FALSE );
|
|
color = pdev->dib.funcs->pixel_to_colorref( &pdev->dib, pixel );
|
|
|
|
if (!get_clipped_rects( &pdev->dib, &rect, pdev->clip, &clipped_rects )) return color;
|
|
pdev->dib.funcs->solid_rects( &pdev->dib, clipped_rects.count, clipped_rects.rects, 0, pixel );
|
|
free_clipped_rects( &clipped_rects );
|
|
return color;
|
|
}
|
|
|
|
/***********************************************************************
|
|
* dibdrv_StrokeAndFillPath
|
|
*/
|
|
BOOL dibdrv_StrokeAndFillPath( PHYSDEV dev )
|
|
{
|
|
dibdrv_physdev *pdev = get_dibdrv_pdev( dev );
|
|
|
|
return stroke_and_fill_path( pdev, TRUE, TRUE );
|
|
}
|
|
|
|
/***********************************************************************
|
|
* dibdrv_StrokePath
|
|
*/
|
|
BOOL dibdrv_StrokePath( PHYSDEV dev )
|
|
{
|
|
dibdrv_physdev *pdev = get_dibdrv_pdev( dev );
|
|
|
|
return stroke_and_fill_path( pdev, TRUE, FALSE );
|
|
}
|