| // Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "skia/ext/vector_device.h" |
| |
| #include "base/gfx/gdi_util.h" |
| #include "skia/ext/skia_utils_win.h" |
| |
| #include "SkUtils.h" |
| |
| namespace skia { |
| |
| VectorDevice* VectorDevice::create(HDC dc, int width, int height) { |
| InitializeDC(dc); |
| |
| // Link the SkBitmap to the current selected bitmap in the device context. |
| SkBitmap bitmap; |
| HGDIOBJ selected_bitmap = GetCurrentObject(dc, OBJ_BITMAP); |
| bool succeeded = false; |
| if (selected_bitmap != NULL) { |
| BITMAP bitmap_data; |
| if (GetObject(selected_bitmap, sizeof(BITMAP), &bitmap_data) == |
| sizeof(BITMAP)) { |
| // The context has a bitmap attached. Attach our SkBitmap to it. |
| // Warning: If the bitmap gets unselected from the HDC, VectorDevice has |
| // no way to detect this, so the HBITMAP could be released while SkBitmap |
| // still has a reference to it. Be cautious. |
| if (width == bitmap_data.bmWidth && |
| height == bitmap_data.bmHeight) { |
| bitmap.setConfig(SkBitmap::kARGB_8888_Config, |
| bitmap_data.bmWidth, |
| bitmap_data.bmHeight, |
| bitmap_data.bmWidthBytes); |
| bitmap.setPixels(bitmap_data.bmBits); |
| succeeded = true; |
| } |
| } |
| } |
| |
| if (!succeeded) |
| bitmap.setConfig(SkBitmap::kARGB_8888_Config, width, height); |
| |
| return new VectorDevice(dc, bitmap); |
| } |
| |
| VectorDevice::VectorDevice(HDC dc, const SkBitmap& bitmap) |
| : PlatformDeviceWin(bitmap), |
| hdc_(dc), |
| previous_brush_(NULL), |
| previous_pen_(NULL) { |
| transform_.reset(); |
| } |
| |
| VectorDevice::~VectorDevice() { |
| SkASSERT(previous_brush_ == NULL); |
| SkASSERT(previous_pen_ == NULL); |
| } |
| |
| |
| void VectorDevice::drawPaint(const SkDraw& draw, const SkPaint& paint) { |
| // TODO(maruel): Bypass the current transformation matrix. |
| SkRect rect; |
| rect.fLeft = 0; |
| rect.fTop = 0; |
| rect.fRight = SkIntToScalar(width() + 1); |
| rect.fBottom = SkIntToScalar(height() + 1); |
| drawRect(draw, rect, paint); |
| } |
| |
| void VectorDevice::drawPoints(const SkDraw& draw, SkCanvas::PointMode mode, |
| size_t count, const SkPoint pts[], |
| const SkPaint& paint) { |
| if (!count) |
| return; |
| |
| if (mode == SkCanvas::kPoints_PointMode) { |
| SkASSERT(false); |
| return; |
| } |
| |
| SkPaint tmp_paint(paint); |
| tmp_paint.setStyle(SkPaint::kStroke_Style); |
| |
| // Draw a path instead. |
| SkPath path; |
| switch (mode) { |
| case SkCanvas::kLines_PointMode: |
| if (count % 2) { |
| SkASSERT(false); |
| return; |
| } |
| for (size_t i = 0; i < count / 2; ++i) { |
| path.moveTo(pts[2 * i]); |
| path.lineTo(pts[2 * i + 1]); |
| } |
| break; |
| case SkCanvas::kPolygon_PointMode: |
| path.moveTo(pts[0]); |
| for (size_t i = 1; i < count; ++i) { |
| path.lineTo(pts[i]); |
| } |
| break; |
| default: |
| SkASSERT(false); |
| return; |
| } |
| // Draw the calculated path. |
| drawPath(draw, path, tmp_paint); |
| } |
| |
| void VectorDevice::drawRect(const SkDraw& draw, const SkRect& rect, |
| const SkPaint& paint) { |
| if (paint.getPathEffect()) { |
| // Draw a path instead. |
| SkPath path_orginal; |
| path_orginal.addRect(rect); |
| |
| // Apply the path effect to the rect. |
| SkPath path_modified; |
| paint.getFillPath(path_orginal, &path_modified); |
| |
| // Removes the path effect from the temporary SkPaint object. |
| SkPaint paint_no_effet(paint); |
| paint_no_effet.setPathEffect(NULL)->safeUnref(); |
| |
| // Draw the calculated path. |
| drawPath(draw, path_modified, paint_no_effet); |
| return; |
| } |
| |
| if (!ApplyPaint(paint)) { |
| return; |
| } |
| HDC dc = getBitmapDC(); |
| if (!Rectangle(dc, SkScalarRound(rect.fLeft), |
| SkScalarRound(rect.fTop), |
| SkScalarRound(rect.fRight), |
| SkScalarRound(rect.fBottom))) { |
| SkASSERT(false); |
| } |
| Cleanup(); |
| } |
| |
| void VectorDevice::drawPath(const SkDraw& draw, const SkPath& path, |
| const SkPaint& paint) { |
| if (paint.getPathEffect()) { |
| // Apply the path effect forehand. |
| SkPath path_modified; |
| paint.getFillPath(path, &path_modified); |
| |
| // Removes the path effect from the temporary SkPaint object. |
| SkPaint paint_no_effet(paint); |
| paint_no_effet.setPathEffect(NULL)->safeUnref(); |
| |
| // Draw the calculated path. |
| drawPath(draw, path_modified, paint_no_effet); |
| return; |
| } |
| |
| if (!ApplyPaint(paint)) { |
| return; |
| } |
| HDC dc = getBitmapDC(); |
| PlatformDeviceWin::LoadPathToDC(dc, path); |
| switch (paint.getStyle()) { |
| case SkPaint::kFill_Style: { |
| BOOL res = StrokeAndFillPath(dc); |
| SkASSERT(res != 0); |
| break; |
| } |
| case SkPaint::kStroke_Style: { |
| BOOL res = StrokePath(dc); |
| SkASSERT(res != 0); |
| break; |
| } |
| case SkPaint::kStrokeAndFill_Style: { |
| BOOL res = StrokeAndFillPath(dc); |
| SkASSERT(res != 0); |
| break; |
| } |
| default: |
| SkASSERT(false); |
| break; |
| } |
| Cleanup(); |
| } |
| |
| void VectorDevice::drawBitmap(const SkDraw& draw, const SkBitmap& bitmap, |
| const SkMatrix& matrix, const SkPaint& paint) { |
| // Load the temporary matrix. This is what will translate, rotate and resize |
| // the bitmap. |
| SkMatrix actual_transform(transform_); |
| actual_transform.preConcat(matrix); |
| LoadTransformToDC(hdc_, actual_transform); |
| |
| InternalDrawBitmap(bitmap, 0, 0, paint); |
| |
| // Restore the original matrix. |
| LoadTransformToDC(hdc_, transform_); |
| } |
| |
| void VectorDevice::drawSprite(const SkDraw& draw, const SkBitmap& bitmap, |
| int x, int y, const SkPaint& paint) { |
| SkMatrix identity; |
| identity.reset(); |
| LoadTransformToDC(hdc_, identity); |
| |
| InternalDrawBitmap(bitmap, x, y, paint); |
| |
| // Restore the original matrix. |
| LoadTransformToDC(hdc_, transform_); |
| } |
| |
| void VectorDevice::drawText(const SkDraw& draw, const void* text, size_t byteLength, |
| SkScalar x, SkScalar y, const SkPaint& paint) { |
| // This function isn't used in the code. Verify this assumption. |
| SkASSERT(false); |
| } |
| |
| void VectorDevice::drawPosText(const SkDraw& draw, const void* text, size_t len, |
| const SkScalar pos[], SkScalar constY, |
| int scalarsPerPos, const SkPaint& paint) { |
| // This function isn't used in the code. Verify this assumption. |
| SkASSERT(false); |
| } |
| |
| void VectorDevice::drawTextOnPath(const SkDraw& draw, const void* text, |
| size_t len, |
| const SkPath& path, const SkMatrix* matrix, |
| const SkPaint& paint) { |
| // This function isn't used in the code. Verify this assumption. |
| SkASSERT(false); |
| } |
| |
| void VectorDevice::drawVertices(const SkDraw& draw, SkCanvas::VertexMode vmode, |
| int vertexCount, |
| const SkPoint vertices[], const SkPoint texs[], |
| const SkColor colors[], SkXfermode* xmode, |
| const uint16_t indices[], int indexCount, |
| const SkPaint& paint) { |
| // This function isn't used in the code. Verify this assumption. |
| SkASSERT(false); |
| } |
| |
| void VectorDevice::drawDevice(const SkDraw& draw, SkDevice* device, int x, |
| int y, const SkPaint& paint) { |
| // TODO(maruel): http://b/1183870 Playback the EMF buffer at printer's dpi if |
| // it is a vectorial device. |
| drawSprite(draw, device->accessBitmap(false), x, y, paint); |
| } |
| |
| bool VectorDevice::ApplyPaint(const SkPaint& paint) { |
| // Note: The goal here is to transfert the SkPaint's state to the HDC's state. |
| // This function does not execute the SkPaint drawing commands. These should |
| // be executed in drawPaint(). |
| |
| SkPaint::Style style = paint.getStyle(); |
| if (!paint.getAlpha()) |
| style = SkPaint::kStyleCount; |
| |
| switch (style) { |
| case SkPaint::kFill_Style: |
| if (!CreateBrush(true, paint) || |
| !CreatePen(false, paint)) |
| return false; |
| break; |
| case SkPaint::kStroke_Style: |
| if (!CreateBrush(false, paint) || |
| !CreatePen(true, paint)) |
| return false; |
| break; |
| case SkPaint::kStrokeAndFill_Style: |
| if (!CreateBrush(true, paint) || |
| !CreatePen(true, paint)) |
| return false; |
| break; |
| default: |
| if (!CreateBrush(false, paint) || |
| !CreatePen(false, paint)) |
| return false; |
| break; |
| } |
| |
| /* |
| getFlags(); |
| isAntiAlias(); |
| isDither() |
| isLinearText() |
| isSubpixelText() |
| isUnderlineText() |
| isStrikeThruText() |
| isFakeBoldText() |
| isDevKernText() |
| isFilterBitmap() |
| |
| // Skia's text is not used. This should be fixed. |
| getTextAlign() |
| getTextScaleX() |
| getTextSkewX() |
| getTextEncoding() |
| getFontMetrics() |
| getFontSpacing() |
| */ |
| |
| // BUG 1094907: Implement shaders. Shaders currently in use: |
| // SkShader::CreateBitmapShader |
| // SkGradientShader::CreateRadial |
| // SkGradientShader::CreateLinear |
| // SkASSERT(!paint.getShader()); |
| |
| // http://b/1106647 Implement loopers and mask filter. Looper currently in |
| // use: |
| // SkBlurDrawLooper is used for shadows. |
| // SkASSERT(!paint.getLooper()); |
| // SkASSERT(!paint.getMaskFilter()); |
| |
| // http://b/1165900 Implement xfermode. |
| // SkASSERT(!paint.getXfermode()); |
| |
| // The path effect should be processed before arriving here. |
| SkASSERT(!paint.getPathEffect()); |
| |
| // These aren't used in the code. Verify this assumption. |
| SkASSERT(!paint.getColorFilter()); |
| SkASSERT(!paint.getRasterizer()); |
| // Reuse code to load Win32 Fonts. |
| SkASSERT(!paint.getTypeface()); |
| return true; |
| } |
| |
| void VectorDevice::setMatrixClip(const SkMatrix& transform, |
| const SkRegion& region) { |
| transform_ = transform; |
| LoadTransformToDC(hdc_, transform_); |
| clip_region_ = region; |
| if (!clip_region_.isEmpty()) |
| LoadClipRegion(); |
| } |
| |
| void VectorDevice::drawToHDC(HDC dc, int x, int y, const RECT* src_rect) { |
| SkASSERT(false); |
| } |
| |
| void VectorDevice::LoadClipRegion() { |
| SkMatrix t; |
| t.reset(); |
| LoadClippingRegionToDC(hdc_, clip_region_, t); |
| } |
| |
| bool VectorDevice::CreateBrush(bool use_brush, COLORREF color) { |
| SkASSERT(previous_brush_ == NULL); |
| // We can't use SetDCBrushColor() or DC_BRUSH when drawing to a EMF buffer. |
| // SetDCBrushColor() calls are not recorded at all and DC_BRUSH will use |
| // WHITE_BRUSH instead. |
| |
| if (!use_brush) { |
| // Set the transparency. |
| if (0 == SetBkMode(hdc_, TRANSPARENT)) { |
| SkASSERT(false); |
| return false; |
| } |
| |
| // Select the NULL brush. |
| previous_brush_ = SelectObject(GetStockObject(NULL_BRUSH)); |
| return previous_brush_ != NULL; |
| } |
| |
| // Set the opacity. |
| if (0 == SetBkMode(hdc_, OPAQUE)) { |
| SkASSERT(false); |
| return false; |
| } |
| |
| // Create and select the brush. |
| previous_brush_ = SelectObject(CreateSolidBrush(color)); |
| return previous_brush_ != NULL; |
| } |
| |
| bool VectorDevice::CreatePen(bool use_pen, COLORREF color, int stroke_width, |
| float stroke_miter, DWORD pen_style) { |
| SkASSERT(previous_pen_ == NULL); |
| // We can't use SetDCPenColor() or DC_PEN when drawing to a EMF buffer. |
| // SetDCPenColor() calls are not recorded at all and DC_PEN will use BLACK_PEN |
| // instead. |
| |
| // No pen case |
| if (!use_pen) { |
| previous_pen_ = SelectObject(GetStockObject(NULL_PEN)); |
| return previous_pen_ != NULL; |
| } |
| |
| // Use the stock pen if the stroke width is 0. |
| if (stroke_width == 0) { |
| // Create a pen with the right color. |
| previous_pen_ = SelectObject(::CreatePen(PS_SOLID, 0, color)); |
| return previous_pen_ != NULL; |
| } |
| |
| // Load a custom pen. |
| LOGBRUSH brush; |
| brush.lbStyle = BS_SOLID; |
| brush.lbColor = color; |
| brush.lbHatch = 0; |
| HPEN pen = ExtCreatePen(pen_style, stroke_width, &brush, 0, NULL); |
| SkASSERT(pen != NULL); |
| previous_pen_ = SelectObject(pen); |
| if (previous_pen_ == NULL) |
| return false; |
| |
| if (!SetMiterLimit(hdc_, stroke_miter, NULL)) { |
| SkASSERT(false); |
| return false; |
| } |
| return true; |
| } |
| |
| void VectorDevice::Cleanup() { |
| if (previous_brush_) { |
| HGDIOBJ result = SelectObject(previous_brush_); |
| previous_brush_ = NULL; |
| if (result) { |
| BOOL res = DeleteObject(result); |
| SkASSERT(res != 0); |
| } |
| } |
| if (previous_pen_) { |
| HGDIOBJ result = SelectObject(previous_pen_); |
| previous_pen_ = NULL; |
| if (result) { |
| BOOL res = DeleteObject(result); |
| SkASSERT(res != 0); |
| } |
| } |
| // Remove any loaded path from the context. |
| AbortPath(hdc_); |
| } |
| |
| HGDIOBJ VectorDevice::SelectObject(HGDIOBJ object) { |
| HGDIOBJ result = ::SelectObject(hdc_, object); |
| SkASSERT(result != HGDI_ERROR); |
| if (result == HGDI_ERROR) |
| return NULL; |
| return result; |
| } |
| |
| bool VectorDevice::CreateBrush(bool use_brush, const SkPaint& paint) { |
| // Make sure that for transparent color, no brush is used. |
| if (paint.getAlpha() == 0) { |
| // Test if it ever happen. |
| SkASSERT(false); |
| use_brush = false; |
| } |
| |
| return CreateBrush(use_brush, SkColorToCOLORREF(paint.getColor())); |
| } |
| |
| bool VectorDevice::CreatePen(bool use_pen, const SkPaint& paint) { |
| // Make sure that for transparent color, no pen is used. |
| if (paint.getAlpha() == 0) { |
| // Test if it ever happen. |
| SkASSERT(false); |
| use_pen = false; |
| } |
| |
| DWORD pen_style = PS_GEOMETRIC | PS_SOLID; |
| switch (paint.getStrokeJoin()) { |
| case SkPaint::kMiter_Join: |
| // Connects path segments with a sharp join. |
| pen_style |= PS_JOIN_MITER; |
| break; |
| case SkPaint::kRound_Join: |
| // Connects path segments with a round join. |
| pen_style |= PS_JOIN_ROUND; |
| break; |
| case SkPaint::kBevel_Join: |
| // Connects path segments with a flat bevel join. |
| pen_style |= PS_JOIN_BEVEL; |
| break; |
| default: |
| SkASSERT(false); |
| break; |
| } |
| switch (paint.getStrokeCap()) { |
| case SkPaint::kButt_Cap: |
| // Begin/end contours with no extension. |
| pen_style |= PS_ENDCAP_FLAT; |
| break; |
| case SkPaint::kRound_Cap: |
| // Begin/end contours with a semi-circle extension. |
| pen_style |= PS_ENDCAP_ROUND; |
| break; |
| case SkPaint::kSquare_Cap: |
| // Begin/end contours with a half square extension. |
| pen_style |= PS_ENDCAP_SQUARE; |
| break; |
| default: |
| SkASSERT(false); |
| break; |
| } |
| |
| return CreatePen(use_pen, |
| SkColorToCOLORREF(paint.getColor()), |
| SkScalarRound(paint.getStrokeWidth()), |
| paint.getStrokeMiter(), |
| pen_style); |
| } |
| |
| void VectorDevice::InternalDrawBitmap(const SkBitmap& bitmap, int x, int y, |
| const SkPaint& paint) { |
| unsigned char alpha = paint.getAlpha(); |
| if (alpha == 0) |
| return; |
| |
| bool is_translucent; |
| if (alpha != 255) { |
| // ApplyPaint expect an opaque color. |
| SkPaint tmp_paint(paint); |
| tmp_paint.setAlpha(255); |
| if (!ApplyPaint(tmp_paint)) |
| return; |
| is_translucent = true; |
| } else { |
| if (!ApplyPaint(paint)) |
| return; |
| is_translucent = false; |
| } |
| int src_size_x = bitmap.width(); |
| int src_size_y = bitmap.height(); |
| if (!src_size_x || !src_size_y) |
| return; |
| |
| // Create a BMP v4 header that we can serialize. |
| BITMAPV4HEADER bitmap_header; |
| gfx::CreateBitmapV4Header(src_size_x, src_size_y, &bitmap_header); |
| HDC dc = getBitmapDC(); |
| SkAutoLockPixels lock(bitmap); |
| SkASSERT(bitmap.getConfig() == SkBitmap::kARGB_8888_Config); |
| const uint32_t* pixels = static_cast<const uint32_t*>(bitmap.getPixels()); |
| if (pixels == NULL) { |
| SkASSERT(false); |
| return; |
| } |
| |
| if (!is_translucent) { |
| int row_length = bitmap.rowBytesAsPixels(); |
| // There is no quick way to determine if an image is opaque. |
| for (int y2 = 0; y2 < src_size_y; ++y2) { |
| for (int x2 = 0; x2 < src_size_x; ++x2) { |
| if (SkColorGetA(pixels[(y2 * row_length) + x2]) != 255) { |
| is_translucent = true; |
| y2 = src_size_y; |
| break; |
| } |
| } |
| } |
| } |
| |
| BITMAPINFOHEADER hdr; |
| gfx::CreateBitmapHeader(src_size_x, src_size_y, &hdr); |
| if (is_translucent) { |
| // The image must be loaded as a bitmap inside a device context. |
| HDC bitmap_dc = ::CreateCompatibleDC(dc); |
| void* bits = NULL; |
| HBITMAP hbitmap = ::CreateDIBSection( |
| bitmap_dc, reinterpret_cast<const BITMAPINFO*>(&hdr), |
| DIB_RGB_COLORS, &bits, NULL, 0); |
| memcpy(bits, pixels, bitmap.getSize()); |
| SkASSERT(hbitmap); |
| HGDIOBJ old_bitmap = ::SelectObject(bitmap_dc, hbitmap); |
| |
| // After some analysis of IE7's behavior, this is the thing to do. I was |
| // sure IE7 was doing so kind of bitmasking due to the way translucent image |
| // where renderered but after some windbg tracing, it is being done by the |
| // printer driver after all (mostly HP printers). IE7 always use AlphaBlend |
| // for bitmasked images. The trick seems to switch the stretching mode in |
| // what the driver expects. |
| DWORD previous_mode = GetStretchBltMode(dc); |
| BOOL result = SetStretchBltMode(dc, COLORONCOLOR); |
| SkASSERT(result); |
| // Note that this function expect premultiplied colors (!) |
| BLENDFUNCTION blend_function = {AC_SRC_OVER, 0, alpha, AC_SRC_ALPHA}; |
| result = GdiAlphaBlend(dc, |
| x, y, // Destination origin. |
| src_size_x, src_size_y, // Destination size. |
| bitmap_dc, |
| 0, 0, // Source origin. |
| src_size_x, src_size_y, // Source size. |
| blend_function); |
| SkASSERT(result); |
| result = SetStretchBltMode(dc, previous_mode); |
| SkASSERT(result); |
| |
| ::SelectObject(bitmap_dc, static_cast<HBITMAP>(old_bitmap)); |
| DeleteObject(hbitmap); |
| DeleteDC(bitmap_dc); |
| } else { |
| BOOL result = StretchDIBits(dc, |
| x, y, // Destination origin. |
| src_size_x, src_size_y, |
| 0, 0, // Source origin. |
| src_size_x, src_size_y, // Source size. |
| pixels, |
| reinterpret_cast<const BITMAPINFO*>(&hdr), |
| DIB_RGB_COLORS, |
| SRCCOPY); |
| SkASSERT(result); |
| } |
| Cleanup(); |
| } |
| |
| } // namespace skia |
| |