| /* |
| * 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/debug.h" |
| |
| WINE_DEFAULT_DEBUG_CHANNEL(dib); |
| |
| static RECT get_device_rect( HDC hdc, 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 && GetLayout( hdc ) & 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--; |
| } |
| LPtoDP( hdc, (POINT *)&rect, 2 ); |
| if (rect.left > rect.right) |
| { |
| int tmp = rect.left; |
| rect.left = rect.right; |
| rect.right = tmp; |
| } |
| if (rect.top > rect.bottom) |
| { |
| int tmp = rect.top; |
| rect.top = rect.bottom; |
| rect.bottom = tmp; |
| } |
| return rect; |
| } |
| |
| /* 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 ); |
| } |
| } |
| |
| void update_aa_ranges( dibdrv_physdev *pdev ) |
| { |
| COLORREF text = pdev->dib.funcs->pixel_to_colorref( &pdev->dib, pdev->text_color ); |
| get_aa_ranges( text, pdev->glyph_intensities ); |
| } |
| |
| /********************************************************************** |
| * get_text_bkgnd_masks |
| * |
| * See the comment above get_pen_bkgnd_masks |
| */ |
| static inline void get_text_bkgnd_masks( const dibdrv_physdev *pdev, rop_mask *mask ) |
| { |
| mask->and = 0; |
| |
| if (pdev->dib.bit_count != 1) |
| mask->xor = pdev->bkgnd_color; |
| else |
| { |
| mask->xor = ~pdev->text_color; |
| if (GetTextColor( pdev->dev.hdc ) == GetBkColor( pdev->dev.hdc )) |
| mask->xor = pdev->text_color; |
| } |
| } |
| |
| static void draw_glyph( dibdrv_physdev *pdev, const POINT *origin, const GLYPHMETRICS *metrics, |
| const struct gdi_image_bits *image ) |
| { |
| const WINEREGION *clip = get_wine_region( pdev->clip ); |
| int i; |
| RECT rect, clipped_rect; |
| POINT src_origin; |
| dib_info glyph_dib; |
| |
| glyph_dib.bit_count = 8; |
| glyph_dib.width = metrics->gmBlackBoxX; |
| glyph_dib.height = metrics->gmBlackBoxY; |
| glyph_dib.stride = get_dib_stride( metrics->gmBlackBoxX, 8 ); |
| glyph_dib.bits = *image; |
| |
| rect.left = origin->x + metrics->gmptGlyphOrigin.x; |
| rect.top = origin->y - metrics->gmptGlyphOrigin.y; |
| rect.right = rect.left + metrics->gmBlackBoxX; |
| rect.bottom = rect.top + metrics->gmBlackBoxY; |
| |
| for (i = 0; i < clip->numRects; i++) |
| { |
| if (intersect_rect( &clipped_rect, &rect, clip->rects + i )) |
| { |
| src_origin.x = clipped_rect.left - rect.left; |
| src_origin.y = clipped_rect.top - rect.top; |
| |
| pdev->dib.funcs->draw_glyph( &pdev->dib, &clipped_rect, &glyph_dib, &src_origin, |
| pdev->text_color, pdev->glyph_intensities ); |
| } |
| } |
| |
| release_wine_region( pdev->clip ); |
| } |
| |
| static const BYTE masks[8] = {0x80, 0x40, 0x20, 0x10, 0x08, 0x04, 0x02, 0x01}; |
| static const int padding[4] = {0, 3, 2, 1}; |
| |
| /*********************************************************************** |
| * get_glyph_bitmap |
| * |
| * Retrieve a 17-level bitmap for the appropiate glyph. |
| * |
| * For non-antialiased bitmaps convert them to the 17-level format |
| * using only values 0 or 16. |
| */ |
| static DWORD get_glyph_bitmap( HDC hdc, UINT index, UINT aa_flags, GLYPHMETRICS *metrics, |
| struct gdi_image_bits *image ) |
| { |
| UINT ggo_flags = aa_flags | GGO_GLYPH_INDEX; |
| 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 *buf, *dst, *src; |
| int pad, stride; |
| |
| image->ptr = NULL; |
| image->is_copy = FALSE; |
| image->free = free_heap_bits; |
| image->param = NULL; |
| |
| indices[0] = index; |
| |
| for (i = 0; i < sizeof(indices) / sizeof(indices[0]); i++) |
| { |
| index = indices[i]; |
| ret = GetGlyphOutlineW( hdc, index, ggo_flags, metrics, 0, NULL, &identity ); |
| if (ret != GDI_ERROR) break; |
| } |
| |
| if (ret == GDI_ERROR) return ERROR_NOT_FOUND; |
| if (!ret) return ERROR_SUCCESS; /* empty glyph */ |
| |
| /* We'll convert non-antialiased 1-bpp bitmaps to 8-bpp, so these sizes relate to 8-bpp */ |
| pad = padding[ metrics->gmBlackBoxX % 4 ]; |
| stride = get_dib_stride( metrics->gmBlackBoxX, 8 ); |
| size = metrics->gmBlackBoxY * stride; |
| |
| buf = HeapAlloc( GetProcessHeap(), 0, size ); |
| if (!buf) return ERROR_OUTOFMEMORY; |
| |
| ret = GetGlyphOutlineW( hdc, index, ggo_flags, metrics, size, buf, &identity ); |
| if (ret == GDI_ERROR) |
| { |
| HeapFree( GetProcessHeap(), 0, buf ); |
| return ERROR_NOT_FOUND; |
| } |
| |
| if (aa_flags == GGO_BITMAP) |
| { |
| for (y = metrics->gmBlackBoxY - 1; y >= 0; y--) |
| { |
| src = buf + y * get_dib_stride( metrics->gmBlackBoxX, 1 ); |
| dst = buf + 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 = buf; y < metrics->gmBlackBoxY; y++, dst += stride) |
| memset( dst + metrics->gmBlackBoxX, 0, pad ); |
| } |
| |
| image->ptr = buf; |
| return ERROR_SUCCESS; |
| } |
| |
| BOOL render_aa_text_bitmapinfo( HDC hdc, 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; |
| UINT i; |
| DWORD err; |
| BOOL got_pixel; |
| COLORREF fg, bg; |
| DWORD fg_pixel, bg_pixel; |
| struct intensity_range glyph_intensities[17]; |
| |
| assert( info->bmiHeader.biBitCount > 8 ); /* mono and indexed formats don't support anti-aliasing */ |
| |
| if (!init_dib_info_from_bitmapinfo( &dib, info, bits->ptr, 0 )) return FALSE; |
| |
| fg = make_rgb_colorref( hdc, &dib, GetTextColor( hdc ), &got_pixel, &fg_pixel); |
| if (!got_pixel) fg_pixel = dib.funcs->colorref_to_pixel( &dib, fg ); |
| |
| get_aa_ranges( fg, glyph_intensities ); |
| |
| if (flags & ETO_OPAQUE) |
| { |
| rop_mask bkgnd_color; |
| |
| bg = make_rgb_colorref( hdc, &dib, GetBkColor( hdc ), &got_pixel, &bg_pixel); |
| if (!got_pixel) bg_pixel = dib.funcs->colorref_to_pixel( &dib, bg ); |
| |
| bkgnd_color.and = 0; |
| bkgnd_color.xor = bg_pixel; |
| solid_rects( &dib, 1, &src->visrect, &bkgnd_color, 0 ); |
| } |
| |
| for (i = 0; i < count; i++) |
| { |
| GLYPHMETRICS metrics; |
| struct gdi_image_bits image; |
| |
| err = get_glyph_bitmap( hdc, (UINT)str[i], aa_flags, &metrics, &image ); |
| if (err) continue; |
| |
| if (image.ptr) |
| { |
| RECT rect, clipped_rect; |
| POINT src_origin; |
| dib_info glyph_dib; |
| |
| glyph_dib.bit_count = 8; |
| glyph_dib.width = metrics.gmBlackBoxX; |
| glyph_dib.height = metrics.gmBlackBoxY; |
| glyph_dib.stride = get_dib_stride( metrics.gmBlackBoxX, 8 ); |
| glyph_dib.bits = image; |
| |
| 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 (intersect_rect( &clipped_rect, &rect, &src->visrect )) |
| { |
| src_origin.x = clipped_rect.left - rect.left; |
| src_origin.y = clipped_rect.top - rect.top; |
| |
| dib.funcs->draw_glyph( &dib, &clipped_rect, &glyph_dib, &src_origin, |
| fg_pixel, glyph_intensities ); |
| } |
| } |
| if (image.free) image.free( &image ); |
| |
| if (dx) |
| { |
| if (flags & ETO_PDY) |
| { |
| x += dx[ i * 2 ]; |
| y += dx[ i * 2 + 1]; |
| } |
| else |
| x += dx[ i ]; |
| } |
| else |
| { |
| x += metrics.gmCellIncX; |
| y += metrics.gmCellIncY; |
| } |
| } |
| free_dib_info( &dib ); |
| 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); |
| UINT aa_flags, i; |
| POINT origin; |
| DWORD err; |
| HRGN saved_clip = NULL; |
| |
| if (flags & ETO_OPAQUE) |
| { |
| rop_mask bkgnd_color; |
| get_text_bkgnd_masks( pdev, &bkgnd_color ); |
| solid_rects( &pdev->dib, 1, rect, &bkgnd_color, pdev->clip ); |
| } |
| |
| if (count == 0) return TRUE; |
| |
| if (flags & ETO_CLIPPED) |
| { |
| HRGN clip = CreateRectRgnIndirect( rect ); |
| saved_clip = add_extra_clipping_region( pdev, clip ); |
| DeleteObject( clip ); |
| } |
| |
| aa_flags = get_font_aa_flags( dev->hdc ); |
| origin.x = x; |
| origin.y = y; |
| for (i = 0; i < count; i++) |
| { |
| GLYPHMETRICS metrics; |
| struct gdi_image_bits image; |
| |
| err = get_glyph_bitmap( dev->hdc, (UINT)str[i], aa_flags, &metrics, &image ); |
| if (err) continue; |
| |
| if (image.ptr) draw_glyph( pdev, &origin, &metrics, &image ); |
| if (image.free) image.free( &image ); |
| |
| if (dx) |
| { |
| if (flags & ETO_PDY) |
| { |
| origin.x += dx[ i * 2 ]; |
| origin.y += dx[ i * 2 + 1]; |
| } |
| else |
| origin.x += dx[ i ]; |
| } |
| else |
| { |
| origin.x += metrics.gmCellIncX; |
| origin.y += metrics.gmCellIncY; |
| } |
| } |
| |
| restore_clipping_region( pdev, saved_clip ); |
| return TRUE; |
| } |
| |
| /*********************************************************************** |
| * dibdrv_GetPixel |
| */ |
| COLORREF dibdrv_GetPixel( PHYSDEV dev, INT x, INT y ) |
| { |
| dibdrv_physdev *pdev = get_dibdrv_pdev( dev ); |
| POINT pt; |
| DWORD pixel; |
| |
| TRACE( "(%p, %d, %d)\n", dev, x, y ); |
| |
| pt.x = x; |
| pt.y = y; |
| LPtoDP( dev->hdc, &pt, 1 ); |
| |
| if (pt.x < 0 || pt.x >= pdev->dib.width || |
| pt.y < 0 || pt.y >= pdev->dib.height) |
| return CLR_INVALID; |
| |
| pixel = pdev->dib.funcs->get_pixel( &pdev->dib, &pt ); |
| return pdev->dib.funcs->pixel_to_colorref( &pdev->dib, pixel ); |
| } |
| |
| /*********************************************************************** |
| * dibdrv_LineTo |
| */ |
| BOOL dibdrv_LineTo( PHYSDEV dev, INT x, INT y ) |
| { |
| PHYSDEV next = GET_NEXT_PHYSDEV( dev, pLineTo ); |
| dibdrv_physdev *pdev = get_dibdrv_pdev(dev); |
| POINT pts[2]; |
| |
| GetCurrentPositionEx(dev->hdc, pts); |
| pts[1].x = x; |
| pts[1].y = y; |
| |
| LPtoDP(dev->hdc, pts, 2); |
| |
| reset_dash_origin(pdev); |
| |
| if(defer_pen(pdev) || !pdev->pen_lines(pdev, 2, pts, FALSE)) |
| return next->funcs->pLineTo( next, x, y ); |
| |
| return TRUE; |
| } |
| |
| /*********************************************************************** |
| * 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 ) |
| { |
| PHYSDEV next = GET_NEXT_PHYSDEV( dev, pPatBlt ); |
| dibdrv_physdev *pdev = get_dibdrv_pdev(dev); |
| INT rop2 = get_rop2_from_rop(rop); |
| BOOL done; |
| |
| TRACE("(%p, %d, %d, %d, %d, %06x)\n", dev, dst->x, dst->y, dst->width, dst->height, rop); |
| |
| if(defer_brush(pdev)) |
| return next->funcs->pPatBlt( next, dst, rop ); |
| |
| update_brush_rop( pdev, rop2 ); |
| |
| done = brush_rects( pdev, 1, &dst->visrect ); |
| |
| update_brush_rop( pdev, GetROP2(dev->hdc) ); |
| |
| if(!done) |
| return next->funcs->pPatBlt( next, dst, rop ); |
| |
| return TRUE; |
| } |
| |
| /*********************************************************************** |
| * dibdrv_PaintRgn |
| */ |
| BOOL dibdrv_PaintRgn( PHYSDEV dev, HRGN rgn ) |
| { |
| PHYSDEV next = GET_NEXT_PHYSDEV( dev, pPaintRgn ); |
| dibdrv_physdev *pdev = get_dibdrv_pdev(dev); |
| const WINEREGION *region; |
| int i; |
| RECT rect; |
| |
| TRACE("%p, %p\n", dev, rgn); |
| |
| if(defer_brush(pdev)) return next->funcs->pPaintRgn( next, rgn ); |
| |
| region = get_wine_region( rgn ); |
| if(!region) return FALSE; |
| |
| for(i = 0; i < region->numRects; i++) |
| { |
| rect = get_device_rect( dev->hdc, region->rects[i].left, region->rects[i].top, |
| region->rects[i].right, region->rects[i].bottom, FALSE ); |
| brush_rects( pdev, 1, &rect ); |
| } |
| |
| release_wine_region( rgn ); |
| return TRUE; |
| } |
| |
| /*********************************************************************** |
| * dibdrv_PolyPolyline |
| */ |
| BOOL dibdrv_PolyPolyline( PHYSDEV dev, const POINT* pt, const DWORD* counts, DWORD polylines ) |
| { |
| dibdrv_physdev *pdev = get_dibdrv_pdev(dev); |
| PHYSDEV next = GET_NEXT_PHYSDEV( dev, pPolyPolyline ); |
| DWORD max_points = 0, i; |
| POINT *points; |
| |
| if (defer_pen( pdev )) return next->funcs->pPolyPolyline( next, pt, counts, polylines ); |
| |
| for (i = 0; i < polylines; i++) max_points = max( counts[i], max_points ); |
| |
| points = HeapAlloc( GetProcessHeap(), 0, max_points * sizeof(*pt) ); |
| if (!points) return FALSE; |
| |
| for (i = 0; i < polylines; i++) |
| { |
| memcpy( points, pt, counts[i] * sizeof(*pt) ); |
| pt += counts[i]; |
| LPtoDP( dev->hdc, points, counts[i] ); |
| |
| reset_dash_origin( pdev ); |
| pdev->pen_lines( pdev, counts[i], points, FALSE ); |
| } |
| |
| HeapFree( GetProcessHeap(), 0, points ); |
| return TRUE; |
| } |
| |
| /*********************************************************************** |
| * dibdrv_Polyline |
| */ |
| BOOL dibdrv_Polyline( PHYSDEV dev, const POINT* pt, INT count ) |
| { |
| dibdrv_physdev *pdev = get_dibdrv_pdev(dev); |
| PHYSDEV next = GET_NEXT_PHYSDEV( dev, pPolyline ); |
| POINT *points; |
| |
| if (defer_pen( pdev )) return next->funcs->pPolyline( next, pt, count ); |
| |
| points = HeapAlloc( GetProcessHeap(), 0, count * sizeof(*pt) ); |
| if (!points) return FALSE; |
| |
| memcpy( points, pt, count * sizeof(*pt) ); |
| LPtoDP( dev->hdc, points, count ); |
| |
| reset_dash_origin( pdev ); |
| pdev->pen_lines( pdev, count, points, FALSE ); |
| |
| HeapFree( GetProcessHeap(), 0, points ); |
| return TRUE; |
| } |
| |
| /*********************************************************************** |
| * dibdrv_Rectangle |
| */ |
| BOOL dibdrv_Rectangle( PHYSDEV dev, INT left, INT top, INT right, INT bottom ) |
| { |
| PHYSDEV next = GET_NEXT_PHYSDEV( dev, pRectangle ); |
| dibdrv_physdev *pdev = get_dibdrv_pdev(dev); |
| RECT rect = get_device_rect( dev->hdc, left, top, right, bottom, TRUE ); |
| POINT pts[4]; |
| |
| TRACE("(%p, %d, %d, %d, %d)\n", dev, left, top, right, bottom); |
| |
| if(rect.left == rect.right || rect.top == rect.bottom) return TRUE; |
| |
| if(defer_pen(pdev) || defer_brush(pdev)) |
| return next->funcs->pRectangle( next, left, top, right, bottom ); |
| |
| reset_dash_origin(pdev); |
| |
| /* 4 pts going anti-clockwise starting from top-right */ |
| pts[0].x = pts[3].x = rect.right - 1; |
| pts[0].y = pts[1].y = rect.top; |
| pts[1].x = pts[2].x = rect.left; |
| pts[2].y = pts[3].y = rect.bottom - 1; |
| |
| pdev->pen_lines(pdev, 4, pts, TRUE); |
| |
| /* FIXME: Will need updating when we support wide pens */ |
| |
| rect.left += 1; |
| rect.top += 1; |
| rect.right -= 1; |
| rect.bottom -= 1; |
| |
| brush_rects(pdev, 1, &rect); |
| |
| return TRUE; |
| } |
| |
| /*********************************************************************** |
| * dibdrv_SetPixel |
| */ |
| COLORREF dibdrv_SetPixel( PHYSDEV dev, INT x, INT y, COLORREF color ) |
| { |
| dibdrv_physdev *pdev = get_dibdrv_pdev( dev ); |
| int i; |
| POINT pt; |
| DWORD pixel; |
| const WINEREGION *clip = get_wine_region( pdev->clip ); |
| |
| TRACE( "(%p, %d, %d, %08x)\n", dev, x, y, color ); |
| |
| pt.x = x; |
| pt.y = y; |
| LPtoDP( dev->hdc, &pt, 1 ); |
| |
| /* SetPixel doesn't do the 1bpp massaging like other fg colors */ |
| pixel = get_pixel_color( pdev, color, FALSE ); |
| color = pdev->dib.funcs->pixel_to_colorref( &pdev->dib, pixel ); |
| |
| for (i = 0; i < clip->numRects; i++) |
| { |
| if (pt_in_rect( clip->rects + i, pt )) |
| { |
| RECT rect; |
| rect.left = pt.x; |
| rect.top = pt.y; |
| rect.right = rect.left + 1; |
| rect.bottom = rect.top + 1; |
| |
| pdev->dib.funcs->solid_rects( &pdev->dib, 1, &rect, 0, pixel ); |
| break; |
| } |
| } |
| |
| release_wine_region( pdev->clip ); |
| return color; |
| } |