// Copyright (c) 2012 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_platform_device_emf_win.h" #include #include "base/logging.h" #include "base/strings/string16.h" #include "skia/ext/bitmap_platform_device.h" #include "skia/ext/skia_utils_win.h" #include "third_party/skia/include/core/SkFontHost.h" #include "third_party/skia/include/core/SkPathEffect.h" #include "third_party/skia/include/core/SkTemplates.h" #include "third_party/skia/include/core/SkUtils.h" #include "third_party/skia/include/ports/SkTypeface_win.h" namespace skia { #define CHECK_FOR_NODRAW_ANNOTATION(paint) \ do { if (paint.isNoDrawAnnotation()) { return; } } while (0) // static SkDevice* VectorPlatformDeviceEmf::CreateDevice( int width, int height, bool is_opaque, HANDLE shared_section) { if (!is_opaque) { // TODO(maruel): http://crbug.com/18382 When restoring a semi-transparent // layer, i.e. merging it, we need to rasterize it because GDI doesn't // support transparency except for AlphaBlend(). Right now, a // BitmapPlatformDevice is created when VectorCanvas think a saveLayers() // call is being done. The way to save a layer would be to create an // EMF-based VectorDevice and have this device registers the drawing. When // playing back the device into a bitmap, do it at the printer's dpi instead // of the layout's dpi (which is much lower). return BitmapPlatformDevice::Create(width, height, is_opaque, shared_section); } // TODO(maruel): http://crbug.com/18383 Look if it would be worth to // increase the resolution by ~10x (any worthy factor) to increase the // rendering precision (think about printing) while using a relatively // low dpi. This happens because we receive float as input but the GDI // functions works with integers. The idea is to premultiply the matrix // with this factor and multiply each SkScalar that are passed to // SkScalarRound(value) as SkScalarRound(value * 10). Safari is already // doing the same for text rendering. SkASSERT(shared_section); SkDevice* device = VectorPlatformDeviceEmf::create( reinterpret_cast(shared_section), width, height); return device; } static void FillBitmapInfoHeader(int width, int height, BITMAPINFOHEADER* hdr) { hdr->biSize = sizeof(BITMAPINFOHEADER); hdr->biWidth = width; hdr->biHeight = -height; // Minus means top-down bitmap. hdr->biPlanes = 1; hdr->biBitCount = 32; hdr->biCompression = BI_RGB; // no compression hdr->biSizeImage = 0; hdr->biXPelsPerMeter = 1; hdr->biYPelsPerMeter = 1; hdr->biClrUsed = 0; hdr->biClrImportant = 0; } SkDevice* VectorPlatformDeviceEmf::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, // VectorPlatformDeviceEmf 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 VectorPlatformDeviceEmf(dc, bitmap); } VectorPlatformDeviceEmf::VectorPlatformDeviceEmf(HDC dc, const SkBitmap& bitmap) : SkDevice(bitmap), hdc_(dc), previous_brush_(NULL), previous_pen_(NULL) { transform_.reset(); SetPlatformDevice(this, this); } VectorPlatformDeviceEmf::~VectorPlatformDeviceEmf() { SkASSERT(previous_brush_ == NULL); SkASSERT(previous_pen_ == NULL); } HDC VectorPlatformDeviceEmf::BeginPlatformPaint() { return hdc_; } uint32_t VectorPlatformDeviceEmf::getDeviceCapabilities() { return SkDevice::getDeviceCapabilities() | kVector_Capability; } void VectorPlatformDeviceEmf::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 VectorPlatformDeviceEmf::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 VectorPlatformDeviceEmf::drawRect(const SkDraw& draw, const SkRect& rect, const SkPaint& paint) { CHECK_FOR_NODRAW_ANNOTATION(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); // Draw the calculated path. drawPath(draw, path_modified, paint_no_effet); return; } if (!ApplyPaint(paint)) { return; } HDC dc = BeginPlatformPaint(); if (!Rectangle(dc, SkScalarRound(rect.fLeft), SkScalarRound(rect.fTop), SkScalarRound(rect.fRight), SkScalarRound(rect.fBottom))) { SkASSERT(false); } EndPlatformPaint(); Cleanup(); } void VectorPlatformDeviceEmf::drawPath(const SkDraw& draw, const SkPath& path, const SkPaint& paint, const SkMatrix* prePathMatrix, bool pathIsMutable) { CHECK_FOR_NODRAW_ANNOTATION(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); // Draw the calculated path. drawPath(draw, path_modified, paint_no_effet); return; } if (!ApplyPaint(paint)) { return; } HDC dc = BeginPlatformPaint(); if (PlatformDevice::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; } } EndPlatformPaint(); Cleanup(); } void VectorPlatformDeviceEmf::drawBitmapRect(const SkDraw& draw, const SkBitmap& bitmap, const SkRect* src, const SkRect& dst, const SkPaint& paint) { SkMatrix matrix; SkRect bitmapBounds, tmpSrc, tmpDst; SkBitmap tmpBitmap; bitmapBounds.isetWH(bitmap.width(), bitmap.height()); // Compute matrix from the two rectangles if (src) { tmpSrc = *src; } else { tmpSrc = bitmapBounds; } matrix.setRectToRect(tmpSrc, dst, SkMatrix::kFill_ScaleToFit); const SkBitmap* bitmapPtr = &bitmap; // clip the tmpSrc to the bounds of the bitmap, and recompute dstRect if // needed (if the src was clipped). No check needed if src==null. if (src) { if (!bitmapBounds.contains(*src)) { if (!tmpSrc.intersect(bitmapBounds)) { return; // nothing to draw } // recompute dst, based on the smaller tmpSrc matrix.mapRect(&tmpDst, tmpSrc); } // since we may need to clamp to the borders of the src rect within // the bitmap, we extract a subset. // TODO: make sure this is handled in drawrect and remove it from here. SkIRect srcIR; tmpSrc.roundOut(&srcIR); if (!bitmap.extractSubset(&tmpBitmap, srcIR)) { return; } bitmapPtr = &tmpBitmap; // Since we did an extract, we need to adjust the matrix accordingly SkScalar dx = 0, dy = 0; if (srcIR.fLeft > 0) { dx = SkIntToScalar(srcIR.fLeft); } if (srcIR.fTop > 0) { dy = SkIntToScalar(srcIR.fTop); } if (dx || dy) { matrix.preTranslate(dx, dy); } } this->drawBitmap(draw, *bitmapPtr, matrix, paint); } void VectorPlatformDeviceEmf::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 VectorPlatformDeviceEmf::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_); } ///////////////////////////////////////////////////////////////////////// static bool gdiCanHandleText(const SkPaint& paint) { return !paint.getShader() && !paint.getPathEffect() && (SkPaint::kFill_Style == paint.getStyle()) && (255 == paint.getAlpha()); } class SkGDIFontSetup { public: SkGDIFontSetup() : fHDC(NULL), fNewFont(NULL), fSavedFont(NULL), fSavedTextColor(0), fUseGDI(false) { SkDEBUGCODE(fUseGDIHasBeenCalled = false;) } ~SkGDIFontSetup(); // can only be called once bool useGDI(HDC hdc, const SkPaint&); private: HDC fHDC; HFONT fNewFont; HFONT fSavedFont; COLORREF fSavedTextColor; bool fUseGDI; SkDEBUGCODE(bool fUseGDIHasBeenCalled;) }; bool SkGDIFontSetup::useGDI(HDC hdc, const SkPaint& paint) { SkASSERT(!fUseGDIHasBeenCalled); SkDEBUGCODE(fUseGDIHasBeenCalled = true;) fUseGDI = gdiCanHandleText(paint); if (fUseGDI) { fSavedTextColor = GetTextColor(hdc); SetTextColor(hdc, skia::SkColorToCOLORREF(paint.getColor())); LOGFONT lf; SkLOGFONTFromTypeface(paint.getTypeface(), &lf); lf.lfHeight = -SkScalarRound(paint.getTextSize()); fNewFont = CreateFontIndirect(&lf); fSavedFont = (HFONT)::SelectObject(hdc, fNewFont); fHDC = hdc; } return fUseGDI; } SkGDIFontSetup::~SkGDIFontSetup() { if (fUseGDI) { ::SelectObject(fHDC, fSavedFont); ::DeleteObject(fNewFont); SetTextColor(fHDC, fSavedTextColor); } } static SkScalar getAscent(const SkPaint& paint) { SkPaint::FontMetrics fm; paint.getFontMetrics(&fm); return fm.fAscent; } // return the options int for ExtTextOut. Only valid if the paint's text // encoding is not UTF8 (in which case ExtTextOut can't be used). static UINT getTextOutOptions(const SkPaint& paint) { if (SkPaint::kGlyphID_TextEncoding == paint.getTextEncoding()) { return ETO_GLYPH_INDEX; } else { SkASSERT(SkPaint::kUTF16_TextEncoding == paint.getTextEncoding()); return 0; } } static SkiaEnsureTypefaceCharactersAccessible g_skia_ensure_typeface_characters_accessible = NULL; SK_API void SetSkiaEnsureTypefaceCharactersAccessible( SkiaEnsureTypefaceCharactersAccessible func) { // This function is supposed to be called once in process life time. SkASSERT(g_skia_ensure_typeface_characters_accessible == NULL); g_skia_ensure_typeface_characters_accessible = func; } void EnsureTypefaceCharactersAccessible( const SkTypeface& typeface, const wchar_t* text, unsigned int text_length) { LOGFONT lf; SkLOGFONTFromTypeface(&typeface, &lf); g_skia_ensure_typeface_characters_accessible(lf, text, text_length); } bool EnsureExtTextOut(HDC hdc, int x, int y, UINT options, const RECT * lprect, LPCWSTR text, unsigned int characters, const int * lpDx, SkTypeface* const typeface) { bool success = ExtTextOut(hdc, x, y, options, lprect, text, characters, lpDx); if (!success) { if (typeface) { EnsureTypefaceCharactersAccessible(*typeface, text, characters); success = ExtTextOut(hdc, x, y, options, lprect, text, characters, lpDx); if (!success) { LOGFONT lf; SkLOGFONTFromTypeface(typeface, &lf); VLOG(1) << "SkFontHost::EnsureTypefaceCharactersAccessible FAILED for " << " FaceName = " << lf.lfFaceName << " and characters: " << string16(text, characters); } } else { VLOG(1) << "ExtTextOut FAILED for default FaceName " << " and characters: " << string16(text, characters); } } return success; } void VectorPlatformDeviceEmf::drawText(const SkDraw& draw, const void* text, size_t byteLength, SkScalar x, SkScalar y, const SkPaint& paint) { SkGDIFontSetup setup; bool useDrawPath = true; if (SkPaint::kUTF8_TextEncoding != paint.getTextEncoding() && setup.useGDI(hdc_, paint)) { UINT options = getTextOutOptions(paint); UINT count = byteLength >> 1; useDrawPath = !EnsureExtTextOut(hdc_, SkScalarRound(x), SkScalarRound(y + getAscent(paint)), options, 0, reinterpret_cast(text), count, NULL, paint.getTypeface()); } if (useDrawPath) { SkPath path; paint.getTextPath(text, byteLength, x, y, &path); drawPath(draw, path, paint); } } static size_t size_utf8(const char* text) { return SkUTF8_CountUTF8Bytes(text); } static size_t size_utf16(const char* text) { uint16_t c = *reinterpret_cast(text); return SkUTF16_IsHighSurrogate(c) ? 4 : 2; } static size_t size_glyphid(const char* text) { return 2; } void VectorPlatformDeviceEmf::drawPosText(const SkDraw& draw, const void* text, size_t len, const SkScalar pos[], SkScalar constY, int scalarsPerPos, const SkPaint& paint) { SkGDIFontSetup setup; bool useDrawText = true; if (2 == scalarsPerPos && SkPaint::kUTF8_TextEncoding != paint.getTextEncoding() && setup.useGDI(hdc_, paint)) { int startX = SkScalarRound(pos[0]); int startY = SkScalarRound(pos[1] + getAscent(paint)); const int count = len >> 1; SkAutoSTMalloc<64, INT> storage(count); INT* advances = storage.get(); for (int i = 0; i < count - 1; ++i) { advances[i] = SkScalarRound(pos[2] - pos[0]); pos += 2; } useDrawText = !EnsureExtTextOut(hdc_, startX, startY, getTextOutOptions(paint), 0, reinterpret_cast(text), count, advances, paint.getTypeface()); } if (useDrawText) { size_t (*bytesPerCodePoint)(const char*); switch (paint.getTextEncoding()) { case SkPaint::kUTF8_TextEncoding: bytesPerCodePoint = size_utf8; break; case SkPaint::kUTF16_TextEncoding: bytesPerCodePoint = size_utf16; break; default: SkASSERT(SkPaint::kGlyphID_TextEncoding == paint.getTextEncoding()); bytesPerCodePoint = size_glyphid; break; } const char* curr = reinterpret_cast(text); const char* stop = curr + len; while (curr < stop) { SkScalar y = (1 == scalarsPerPos) ? constY : pos[1]; size_t bytes = bytesPerCodePoint(curr); drawText(draw, curr, bytes, pos[0], y, paint); curr += bytes; pos += scalarsPerPos; } } } void VectorPlatformDeviceEmf::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 VectorPlatformDeviceEmf::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 VectorPlatformDeviceEmf::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 VectorPlatformDeviceEmf::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::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()); // This isn't used in the code. Verify this assumption. SkASSERT(!paint.getRasterizer()); // Reuse code to load Win32 Fonts. return true; } void VectorPlatformDeviceEmf::setMatrixClip(const SkMatrix& transform, const SkRegion& region, const SkClipStack&) { transform_ = transform; LoadTransformToDC(hdc_, transform_); clip_region_ = region; if (!clip_region_.isEmpty()) LoadClipRegion(); } void VectorPlatformDeviceEmf::DrawToNativeContext(HDC dc, int x, int y, const RECT* src_rect) { SkASSERT(false); } void VectorPlatformDeviceEmf::LoadClipRegion() { SkMatrix t; t.reset(); LoadClippingRegionToDC(hdc_, clip_region_, t); } SkDevice* VectorPlatformDeviceEmf::onCreateCompatibleDevice( SkBitmap::Config config, int width, int height, bool isOpaque, Usage /*usage*/) { SkASSERT(config == SkBitmap::kARGB_8888_Config); return VectorPlatformDeviceEmf::CreateDevice(width, height, isOpaque, NULL); } bool VectorPlatformDeviceEmf::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 VectorPlatformDeviceEmf::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 VectorPlatformDeviceEmf::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 VectorPlatformDeviceEmf::SelectObject(HGDIOBJ object) { HGDIOBJ result = ::SelectObject(hdc_, object); SkASSERT(result != HGDI_ERROR); if (result == HGDI_ERROR) return NULL; return result; } bool VectorPlatformDeviceEmf::CreateBrush(bool use_brush, const SkPaint& paint) { // Make sure that for transparent color, no brush is used. if (paint.getAlpha() == 0) { use_brush = false; } return CreateBrush(use_brush, SkColorToCOLORREF(paint.getColor())); } bool VectorPlatformDeviceEmf::CreatePen(bool use_pen, const SkPaint& paint) { // Make sure that for transparent color, no pen is used. if (paint.getAlpha() == 0) { 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 VectorPlatformDeviceEmf::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. We use the shared "V3" // fillter to fill the stardard items, then add in the "V4" stuff we want. BITMAPV4HEADER bitmap_header; memset(&bitmap_header, 0, sizeof(BITMAPV4HEADER)); FillBitmapInfoHeader(src_size_x, src_size_y, reinterpret_cast(&bitmap_header)); bitmap_header.bV4Size = sizeof(BITMAPV4HEADER); bitmap_header.bV4RedMask = 0x00ff0000; bitmap_header.bV4GreenMask = 0x0000ff00; bitmap_header.bV4BlueMask = 0x000000ff; bitmap_header.bV4AlphaMask = 0xff000000; SkAutoLockPixels lock(bitmap); SkASSERT(bitmap.config() == SkBitmap::kARGB_8888_Config); const uint32_t* pixels = static_cast(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; } } } } HDC dc = BeginPlatformPaint(); BITMAPINFOHEADER hdr; FillBitmapInfoHeader(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(&hdr), DIB_RGB_COLORS, &bits, NULL, 0); // static cast to a char so we can do byte ptr arithmatic to // get the offset. unsigned char* dest_buffer = static_cast(bits); // We will copy row by row to avoid having to worry about // the row strides being different. const int dest_row_size = hdr.biBitCount / 8 * hdr.biWidth; for (int row = 0; row < bitmap.height(); ++row) { int dest_offset = row * dest_row_size; // pixels_offset in terms of pixel count. int src_offset = row * bitmap.rowBytesAsPixels(); memcpy(dest_buffer + dest_offset, pixels + src_offset, dest_row_size); } 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(old_bitmap)); DeleteObject(hbitmap); DeleteDC(bitmap_dc); } else { int nCopied = 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(&hdr), DIB_RGB_COLORS, SRCCOPY); } EndPlatformPaint(); Cleanup(); } } // namespace skia