summaryrefslogtreecommitdiffstats
path: root/skia
diff options
context:
space:
mode:
authorbrettw@google.com <brettw@google.com@0039d316-1c4b-4281-b951-d872f2087c98>2008-11-21 23:03:56 +0000
committerbrettw@google.com <brettw@google.com@0039d316-1c4b-4281-b951-d872f2087c98>2008-11-21 23:03:56 +0000
commit156f2c0e187c0283f026fd356a4b682d4b4eaaa1 (patch)
tree209d254abb43d9ff2cb9a904dd16eed00a85bfb4 /skia
parentbd2e63eed24e8c097be6ea3fb46aa354c638e88e (diff)
downloadchromium_src-156f2c0e187c0283f026fd356a4b682d4b4eaaa1.zip
chromium_src-156f2c0e187c0283f026fd356a4b682d4b4eaaa1.tar.gz
chromium_src-156f2c0e187c0283f026fd356a4b682d4b4eaaa1.tar.bz2
Move skia extensions from the port to skia/ext for Windows only. Fixed the
include guards of the moved files, but nothing else. Review URL: http://codereview.chromium.org/11568 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@5861 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'skia')
-rw-r--r--skia/SConscript6
-rw-r--r--skia/ext/bitmap_platform_device_win.cc446
-rw-r--r--skia/ext/bitmap_platform_device_win.h111
-rw-r--r--skia/ext/platform_canvas_unittest.cc296
-rw-r--r--skia/ext/platform_canvas_win.cc117
-rw-r--r--skia/ext/platform_canvas_win.h202
-rw-r--r--skia/ext/platform_device.h27
-rw-r--r--skia/ext/platform_device_win.cc229
-rw-r--r--skia/ext/platform_device_win.h98
-rw-r--r--skia/ext/vector_canvas.cc90
-rw-r--r--skia/ext/vector_canvas.h46
-rw-r--r--skia/ext/vector_canvas_unittest.cc1009
-rw-r--r--skia/ext/vector_device.cc612
-rw-r--r--skia/ext/vector_device.h119
-rw-r--r--skia/skia.vcproj56
15 files changed, 3464 insertions, 0 deletions
diff --git a/skia/SConscript b/skia/SConscript
index 95fa1b1..ad1f194 100644
--- a/skia/SConscript
+++ b/skia/SConscript
@@ -173,6 +173,12 @@ if env['PLATFORM'] == 'darwin':
input_files.append('ext/platform_device_mac.cc')
if env['PLATFORM'] == 'win32':
+ input_files.append('ext/bitmap_platform_device_win.cc')
+ input_files.append('ext/platform_canvas_win.cc')
+ input_files.append('ext/platform_device_win.cc')
+ input_files.append('ext/vector_canvas.cc')
+ input_files.append('ext/vector_device.cc')
+
input_files.append('ports/SkThread_win.cpp')
env_p = env.Clone(
diff --git a/skia/ext/bitmap_platform_device_win.cc b/skia/ext/bitmap_platform_device_win.cc
new file mode 100644
index 0000000..63efc8f
--- /dev/null
+++ b/skia/ext/bitmap_platform_device_win.cc
@@ -0,0 +1,446 @@
+// 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/bitmap_platform_device_win.h"
+
+#include "base/gfx/gdi_util.h"
+#include "base/logging.h"
+#include "SkMatrix.h"
+#include "SkRegion.h"
+#include "SkUtils.h"
+
+namespace gfx {
+
+// When Windows draws text, is sets the fourth byte (which Skia uses for alpha)
+// to zero. This means that if we try compositing with text that Windows has
+// drawn, we get invalid color values (if the alpha is 0, the other channels
+// should be 0 since Skia uses premultiplied colors) and strange results.
+//
+// HTML rendering only requires one bit of transparency. When you ask for a
+// semitransparent div, the div itself is drawn in another layer as completely
+// opaque, and then composited onto the lower layer with a transfer function.
+// The only place an alpha channel is needed is to track what has been drawn
+// and what has not been drawn.
+//
+// Therefore, when we allocate a new device, we fill it with this special
+// color. Because Skia uses premultiplied colors, any color where the alpha
+// channel is smaller than any component is impossible, so we know that no
+// legitimate drawing will produce this color. We use 1 as the alpha value
+// because 0 is produced when Windows draws text (even though it should be
+// opaque).
+//
+// When a layer is done and we want to render it to a lower layer, we use
+// fixupAlphaBeforeCompositing. This replaces all 0 alpha channels with
+// opaque (to fix the text problem), and replaces this magic color value
+// with transparency. The result is something that can be correctly
+// composited. However, once this has been done, no more can be drawn to
+// the layer because fixing the alphas *again* will result in incorrect
+// values.
+static const uint32_t kMagicTransparencyColor = 0x01FFFEFD;
+
+namespace {
+
+// Constrains position and size to fit within available_size. If |size| is -1,
+// all the available_size is used. Returns false if the position is out of
+// available_size.
+bool Constrain(int available_size, int* position, int *size) {
+ if (*size < -2)
+ return false;
+
+ if (*position < 0) {
+ if (*size != -1)
+ *size += *position;
+ *position = 0;
+ }
+ if (*size == 0 || *position >= available_size)
+ return false;
+
+ if (*size > 0) {
+ int overflow = (*position + *size) - available_size;
+ if (overflow > 0) {
+ *size -= overflow;
+ }
+ } else {
+ // Fill up available size.
+ *size = available_size - *position;
+ }
+ return true;
+}
+
+// If the pixel value is 0, it gets set to kMagicTransparencyColor.
+void PrepareAlphaForGDI(uint32_t* pixel) {
+ if (*pixel == 0) {
+ *pixel = kMagicTransparencyColor;
+ }
+}
+
+// If the pixel value is kMagicTransparencyColor, it gets set to 0. Otherwise
+// if the alpha is 0, the alpha is set to 255.
+void PostProcessAlphaForGDI(uint32_t* pixel) {
+ if (*pixel == kMagicTransparencyColor) {
+ *pixel = 0;
+ } else if ((*pixel & 0xFF000000) == 0) {
+ *pixel |= 0xFF000000;
+ }
+}
+
+// Sets the opacity of the specified value to 0xFF.
+void MakeOpaqueAlphaAdjuster(uint32_t* pixel) {
+ *pixel |= 0xFF000000;
+}
+
+// See the declaration of kMagicTransparencyColor at the top of the file.
+void FixupAlphaBeforeCompositing(uint32_t* pixel) {
+ if (*pixel == kMagicTransparencyColor)
+ *pixel = 0;
+ else
+ *pixel |= 0xFF000000;
+}
+
+} // namespace
+
+class BitmapPlatformDeviceWin::BitmapPlatformDeviceWinData
+ : public base::RefCounted<BitmapPlatformDeviceWinData> {
+ public:
+ explicit BitmapPlatformDeviceWinData(HBITMAP hbitmap);
+
+ // Create/destroy hdc_, which is the memory DC for our bitmap data.
+ HDC GetBitmapDC();
+ void ReleaseBitmapDC();
+ bool IsBitmapDCCreated() const;
+
+ // Sets the transform and clip operations. This will not update the DC,
+ // but will mark the config as dirty. The next call of LoadConfig will
+ // pick up these changes.
+ void SetMatrixClip(const SkMatrix& transform, const SkRegion& region);
+
+ const SkMatrix& transform() const {
+ return transform_;
+ }
+
+ protected:
+ // Loads the current transform and clip into the DC. Can be called even when
+ // the DC is NULL (will be a NOP).
+ void LoadConfig();
+
+ // Windows bitmap corresponding to our surface.
+ HBITMAP hbitmap_;
+
+ // Lazily-created DC used to draw into the bitmap, see getBitmapDC.
+ HDC hdc_;
+
+ // True when there is a transform or clip that has not been set to the DC.
+ // The DC is retrieved for every text operation, and the transform and clip
+ // do not change as much. We can save time by not loading the clip and
+ // transform for every one.
+ bool config_dirty_;
+
+ // Translation assigned to the DC: we need to keep track of this separately
+ // so it can be updated even if the DC isn't created yet.
+ SkMatrix transform_;
+
+ // The current clipping
+ SkRegion clip_region_;
+
+ private:
+ friend class base::RefCounted<BitmapPlatformDeviceWinData>;
+ ~BitmapPlatformDeviceWinData();
+
+ DISALLOW_EVIL_CONSTRUCTORS(BitmapPlatformDeviceWinData);
+};
+
+BitmapPlatformDeviceWin::BitmapPlatformDeviceWinData::BitmapPlatformDeviceWinData(
+ HBITMAP hbitmap)
+ : hbitmap_(hbitmap),
+ hdc_(NULL),
+ config_dirty_(true) { // Want to load the config next time.
+ // Initialize the clip region to the entire bitmap.
+ BITMAP bitmap_data;
+ if (GetObject(hbitmap_, sizeof(BITMAP), &bitmap_data)) {
+ SkIRect rect;
+ rect.set(0, 0, bitmap_data.bmWidth, bitmap_data.bmHeight);
+ clip_region_ = SkRegion(rect);
+ }
+
+ transform_.reset();
+}
+
+BitmapPlatformDeviceWin::BitmapPlatformDeviceWinData::~BitmapPlatformDeviceWinData() {
+ if (hdc_)
+ ReleaseBitmapDC();
+
+ // this will free the bitmap data as well as the bitmap handle
+ DeleteObject(hbitmap_);
+}
+
+HDC BitmapPlatformDeviceWin::BitmapPlatformDeviceWinData::GetBitmapDC() {
+ if (!hdc_) {
+ hdc_ = CreateCompatibleDC(NULL);
+ InitializeDC(hdc_);
+ HGDIOBJ old_bitmap = SelectObject(hdc_, hbitmap_);
+ // When the memory DC is created, its display surface is exactly one
+ // monochrome pixel wide and one monochrome pixel high. Since we select our
+ // own bitmap, we must delete the previous one.
+ DeleteObject(old_bitmap);
+ }
+
+ LoadConfig();
+ return hdc_;
+}
+
+void BitmapPlatformDeviceWin::BitmapPlatformDeviceWinData::ReleaseBitmapDC() {
+ DCHECK(hdc_);
+ DeleteDC(hdc_);
+ hdc_ = NULL;
+}
+
+bool BitmapPlatformDeviceWin::BitmapPlatformDeviceWinData::IsBitmapDCCreated()
+ const {
+ return hdc_ != NULL;
+}
+
+
+void BitmapPlatformDeviceWin::BitmapPlatformDeviceWinData::SetMatrixClip(
+ const SkMatrix& transform,
+ const SkRegion& region) {
+ transform_ = transform;
+ clip_region_ = region;
+ config_dirty_ = true;
+}
+
+void BitmapPlatformDeviceWin::BitmapPlatformDeviceWinData::LoadConfig() {
+ if (!config_dirty_ || !hdc_)
+ return; // Nothing to do.
+ config_dirty_ = false;
+
+ // Transform.
+ SkMatrix t(transform_);
+ LoadTransformToDC(hdc_, t);
+ // We don't use transform_ for the clipping region since the translation is
+ // already applied to offset_x_ and offset_y_.
+ t.reset();
+ LoadClippingRegionToDC(hdc_, clip_region_, t);
+}
+
+// We use this static factory function instead of the regular constructor so
+// that we can create the pixel data before calling the constructor. This is
+// required so that we can call the base class' constructor with the pixel
+// data.
+BitmapPlatformDeviceWin* BitmapPlatformDeviceWin::create(HDC screen_dc,
+ int width,
+ int height,
+ bool is_opaque,
+ HANDLE shared_section) {
+ SkBitmap bitmap;
+
+ // CreateDIBSection appears to get unhappy if we create an empty bitmap, so
+ // just create a minimal bitmap
+ if ((width == 0) || (height == 0)) {
+ width = 1;
+ height = 1;
+ }
+
+ BITMAPINFOHEADER hdr = {0};
+ CreateBitmapHeader(width, height, &hdr);
+
+ void* data = NULL;
+ HBITMAP hbitmap = CreateDIBSection(screen_dc,
+ reinterpret_cast<BITMAPINFO*>(&hdr), 0,
+ &data,
+ shared_section, 0);
+
+ // If we run out of GDI objects or some other error occurs, we won't get a
+ // bitmap here. This will cause us to crash later because the data pointer is
+ // NULL. To make sure that we can assign blame for those crashes to this code,
+ // we deliberately crash here, even in release mode.
+ if (!hbitmap) {
+ DWORD error = GetLastError();
+ LOG(ERROR) << "CreateDIBSection Failed. Error: " << error << "\n";
+ return NULL;
+ }
+
+ bitmap.setConfig(SkBitmap::kARGB_8888_Config, width, height);
+ bitmap.setPixels(data);
+ bitmap.setIsOpaque(is_opaque);
+
+ if (is_opaque) {
+#ifndef NDEBUG
+ // To aid in finding bugs, we set the background color to something
+ // obviously wrong so it will be noticable when it is not cleared
+ bitmap.eraseARGB(255, 0, 255, 128); // bright bluish green
+#endif
+ } else {
+ // A transparent layer is requested: fill with our magic "transparent"
+ // color, see the declaration of kMagicTransparencyColor above
+ sk_memset32(static_cast<uint32_t*>(data), kMagicTransparencyColor,
+ width * height);
+ }
+
+ // The device object will take ownership of the HBITMAP.
+ return new BitmapPlatformDeviceWin(new BitmapPlatformDeviceWinData(hbitmap),
+ bitmap);
+}
+
+// The device will own the HBITMAP, which corresponds to also owning the pixel
+// data. Therefore, we do not transfer ownership to the SkDevice's bitmap.
+BitmapPlatformDeviceWin::BitmapPlatformDeviceWin(
+ BitmapPlatformDeviceWinData* data,
+ const SkBitmap& bitmap) : PlatformDeviceWin(bitmap), data_(data) {
+}
+
+// The copy constructor just adds another reference to the underlying data.
+// We use a const cast since the default Skia definitions don't define the
+// proper constedness that we expect (accessBitmap should really be const).
+BitmapPlatformDeviceWin::BitmapPlatformDeviceWin(
+ const BitmapPlatformDeviceWin& other)
+ : PlatformDeviceWin(
+ const_cast<BitmapPlatformDeviceWin&>(other).accessBitmap(true)),
+ data_(other.data_) {
+}
+
+BitmapPlatformDeviceWin::~BitmapPlatformDeviceWin() {
+}
+
+BitmapPlatformDeviceWin& BitmapPlatformDeviceWin::operator=(
+ const BitmapPlatformDeviceWin& other) {
+ data_ = other.data_;
+ return *this;
+}
+
+HDC BitmapPlatformDeviceWin::getBitmapDC() {
+ return data_->GetBitmapDC();
+}
+
+void BitmapPlatformDeviceWin::setMatrixClip(const SkMatrix& transform,
+ const SkRegion& region) {
+ data_->SetMatrixClip(transform, region);
+}
+
+void BitmapPlatformDeviceWin::drawToHDC(HDC dc, int x, int y,
+ const RECT* src_rect) {
+ bool created_dc = !data_->IsBitmapDCCreated();
+ HDC source_dc = getBitmapDC();
+
+ RECT temp_rect;
+ if (!src_rect) {
+ temp_rect.left = 0;
+ temp_rect.right = width();
+ temp_rect.top = 0;
+ temp_rect.bottom = height();
+ src_rect = &temp_rect;
+ }
+
+ int copy_width = src_rect->right - src_rect->left;
+ int copy_height = src_rect->bottom - src_rect->top;
+
+ // We need to reset the translation for our bitmap or (0,0) won't be in the
+ // upper left anymore
+ SkMatrix identity;
+ identity.reset();
+
+ LoadTransformToDC(source_dc, identity);
+ if (isOpaque()) {
+ BitBlt(dc,
+ x,
+ y,
+ copy_width,
+ copy_height,
+ source_dc,
+ src_rect->left,
+ src_rect->top,
+ SRCCOPY);
+ } else {
+ DCHECK(copy_width != 0 && copy_height != 0);
+ BLENDFUNCTION blend_function = {AC_SRC_OVER, 0, 255, AC_SRC_ALPHA};
+ GdiAlphaBlend(dc,
+ x,
+ y,
+ copy_width,
+ copy_height,
+ source_dc,
+ src_rect->left,
+ src_rect->top,
+ copy_width,
+ copy_height,
+ blend_function);
+ }
+ LoadTransformToDC(source_dc, data_->transform());
+
+ if (created_dc)
+ data_->ReleaseBitmapDC();
+}
+
+void BitmapPlatformDeviceWin::prepareForGDI(int x, int y, int width,
+ int height) {
+ processPixels<PrepareAlphaForGDI>(x, y, width, height);
+}
+
+void BitmapPlatformDeviceWin::postProcessGDI(int x, int y, int width,
+ int height) {
+ processPixels<PostProcessAlphaForGDI>(x, y, width, height);
+}
+
+void BitmapPlatformDeviceWin::makeOpaque(int x, int y, int width, int height) {
+ processPixels<MakeOpaqueAlphaAdjuster>(x, y, width, height);
+}
+
+void BitmapPlatformDeviceWin::fixupAlphaBeforeCompositing() {
+ const SkBitmap& bitmap = accessBitmap(true);
+ SkAutoLockPixels lock(bitmap);
+ uint32_t* data = bitmap.getAddr32(0, 0);
+
+ size_t words = bitmap.rowBytes() / sizeof(uint32_t) * bitmap.height();
+ for (size_t i = 0; i < words; i++) {
+ if (data[i] == kMagicTransparencyColor)
+ data[i] = 0;
+ else
+ data[i] |= 0xFF000000;
+ }
+}
+
+// Returns the color value at the specified location.
+SkColor BitmapPlatformDeviceWin::getColorAt(int x, int y) {
+ const SkBitmap& bitmap = accessBitmap(false);
+ SkAutoLockPixels lock(bitmap);
+ uint32_t* data = bitmap.getAddr32(0, 0);
+ return static_cast<SkColor>(data[x + y * width()]);
+}
+
+void BitmapPlatformDeviceWin::onAccessBitmap(SkBitmap* bitmap) {
+ // FIXME(brettw) OPTIMIZATION: We should only flush if we know a GDI
+ // operation has occurred on our DC.
+ if (data_->IsBitmapDCCreated())
+ GdiFlush();
+}
+
+template<BitmapPlatformDeviceWin::adjustAlpha adjustor>
+void BitmapPlatformDeviceWin::processPixels(int x,
+ int y,
+ int width,
+ int height) {
+ const SkBitmap& bitmap = accessBitmap(true);
+ DCHECK_EQ(bitmap.config(), SkBitmap::kARGB_8888_Config);
+ const SkMatrix& matrix = data_->transform();
+ int bitmap_start_x = SkScalarRound(matrix.getTranslateX()) + x;
+ int bitmap_start_y = SkScalarRound(matrix.getTranslateY()) + y;
+
+ if (Constrain(bitmap.width(), &bitmap_start_x, &width) &&
+ Constrain(bitmap.height(), &bitmap_start_y, &height)) {
+ SkAutoLockPixels lock(bitmap);
+ DCHECK_EQ(bitmap.rowBytes() % sizeof(uint32_t), 0u);
+ size_t row_words = bitmap.rowBytes() / sizeof(uint32_t);
+ // Set data to the first pixel to be modified.
+ uint32_t* data = bitmap.getAddr32(0, 0) + (bitmap_start_y * row_words) +
+ bitmap_start_x;
+ for (int i = 0; i < height; i++) {
+ for (int j = 0; j < width; j++) {
+ adjustor(data + j);
+ }
+ data += row_words;
+ }
+ }
+}
+
+} // namespace gfx
+
diff --git a/skia/ext/bitmap_platform_device_win.h b/skia/ext/bitmap_platform_device_win.h
new file mode 100644
index 0000000..49b72a4
--- /dev/null
+++ b/skia/ext/bitmap_platform_device_win.h
@@ -0,0 +1,111 @@
+// 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.
+
+#ifndef SKIA_BITMAP_PLATFORM_DEVICE_WIN_H_
+#define SKIA_BITMAP_PLATFORM_DEVICE_WIN_H_
+
+#include "base/gfx/platform_device_win.h"
+#include "base/ref_counted.h"
+
+namespace gfx {
+
+// A device is basically a wrapper around SkBitmap that provides a surface for
+// SkCanvas to draw into. Our device provides a surface Windows can also write
+// to. BitmapPlatformDeviceWin creates a bitmap using CreateDIBSection() in a
+// format that Skia supports and can then use this to draw ClearType into, etc.
+// This pixel data is provided to the bitmap that the device contains so that it
+// can be shared.
+//
+// The device owns the pixel data, when the device goes away, the pixel data
+// also becomes invalid. THIS IS DIFFERENT THAN NORMAL SKIA which uses
+// reference counting for the pixel data. In normal Skia, you could assign
+// another bitmap to this device's bitmap and everything will work properly.
+// For us, that other bitmap will become invalid as soon as the device becomes
+// invalid, which may lead to subtle bugs. Therefore, DO NOT ASSIGN THE
+// DEVICE'S PIXEL DATA TO ANOTHER BITMAP, make sure you copy instead.
+class BitmapPlatformDeviceWin : public PlatformDeviceWin {
+ public:
+ // Factory function. The screen DC is used to create the bitmap, and will not
+ // be stored beyond this function. is_opaque should be set if the caller
+ // knows the bitmap will be completely opaque and allows some optimizations.
+ //
+ // The shared_section parameter is optional (pass NULL for default behavior).
+ // If shared_section is non-null, then it must be a handle to a file-mapping
+ // object returned by CreateFileMapping. See CreateDIBSection for details.
+ static BitmapPlatformDeviceWin* create(HDC screen_dc,
+ int width,
+ int height,
+ bool is_opaque,
+ HANDLE shared_section);
+
+ // Copy constructor. When copied, devices duplicate their internal data, so
+ // stay linked. This is because their implementation is very heavyweight
+ // (lots of memory and some GDI objects). If a device has been copied, both
+ // clip rects and other state will stay in sync.
+ //
+ // This means it will NOT work to duplicate a device and assign it to a
+ // canvas, because the two canvases will each set their own clip rects, and
+ // the resulting GDI clip rect will be random.
+ //
+ // Copy constucting and "=" is designed for saving the device or passing it
+ // around to another routine willing to deal with the bitmap data directly.
+ BitmapPlatformDeviceWin(const BitmapPlatformDeviceWin& other);
+ virtual ~BitmapPlatformDeviceWin();
+
+ // See warning for copy constructor above.
+ BitmapPlatformDeviceWin& operator=(const BitmapPlatformDeviceWin& other);
+
+ // Retrieves the bitmap DC, which is the memory DC for our bitmap data. The
+ // bitmap DC is lazy created.
+ virtual HDC getBitmapDC();
+ virtual void setMatrixClip(const SkMatrix& transform, const SkRegion& region);
+
+ virtual void drawToHDC(HDC dc, int x, int y, const RECT* src_rect);
+ virtual void prepareForGDI(int x, int y, int width, int height);
+ virtual void postProcessGDI(int x, int y, int width, int height);
+ virtual void makeOpaque(int x, int y, int width, int height);
+ virtual void fixupAlphaBeforeCompositing();
+ virtual bool IsVectorial() { return false; }
+
+ // Returns the color value at the specified location. This does not
+ // consider any transforms that may be set on the device.
+ SkColor getColorAt(int x, int y);
+
+ protected:
+ // Flushes the Windows device context so that the pixel data can be accessed
+ // directly by Skia. Overridden from SkDevice, this is called when Skia
+ // starts accessing pixel data.
+ virtual void onAccessBitmap(SkBitmap* bitmap);
+
+ private:
+ // Function pointer used by the processPixels method for setting the alpha
+ // value of a particular pixel.
+ typedef void (*adjustAlpha)(uint32_t* pixel);
+
+ // Reference counted data that can be shared between multiple devices. This
+ // allows copy constructors and operator= for devices to work properly. The
+ // bitmaps used by the base device class are already refcounted and copyable.
+ class BitmapPlatformDeviceWinData;
+
+ // Private constructor.
+ BitmapPlatformDeviceWin(BitmapPlatformDeviceWinData* data,
+ const SkBitmap& bitmap);
+
+ // Loops through each of the pixels in the specified range, invoking
+ // adjustor for the alpha value of each pixel. If |width| or |height| are -1,
+ // the available width/height is used.
+ template<adjustAlpha adjustor>
+ void processPixels(int x,
+ int y,
+ int width,
+ int height);
+
+ // Data associated with this device, guaranteed non-null.
+ scoped_refptr<BitmapPlatformDeviceWinData> data_;
+};
+
+} // namespace gfx
+
+#endif // SKIA_BITMAP_PLATFORM_DEVICE_WIN_H_
+
diff --git a/skia/ext/platform_canvas_unittest.cc b/skia/ext/platform_canvas_unittest.cc
new file mode 100644
index 0000000..8e227ad
--- /dev/null
+++ b/skia/ext/platform_canvas_unittest.cc
@@ -0,0 +1,296 @@
+// 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.
+
+// TODO(awalker): clean up the const/non-const reference handling in this test
+
+#include "build/build_config.h"
+
+#if defined(OS_WIN)
+#include <windows.h>
+#else
+#include <unistd.h>
+#endif
+
+#include "base/gfx/platform_canvas.h"
+#include "base/gfx/platform_device.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+#include "SkColor.h"
+
+namespace gfx {
+
+namespace {
+
+// Return true if the canvas is filled to canvas_color,
+// and contains a single rectangle filled to rect_color.
+bool VerifyRect(const PlatformCanvas& canvas,
+ uint32_t canvas_color, uint32_t rect_color,
+ int x, int y, int w, int h) {
+ PlatformDevice& device = canvas.getTopPlatformDevice();
+ const SkBitmap& bitmap = device.accessBitmap(false);
+ SkAutoLockPixels lock(bitmap);
+
+ for (int cur_y = 0; cur_y < bitmap.height(); cur_y++) {
+ for (int cur_x = 0; cur_x < bitmap.width(); cur_x++) {
+ if (cur_x >= x && cur_x < x + w &&
+ cur_y >= y && cur_y < y + h) {
+ // Inside the square should be rect_color
+ if (*bitmap.getAddr32(cur_x, cur_y) != rect_color)
+ return false;
+ } else {
+ // Outside the square should be canvas_color
+ if (*bitmap.getAddr32(cur_x, cur_y) != canvas_color)
+ return false;
+ }
+ }
+ }
+ return true;
+}
+
+// Checks whether there is a white canvas with a black square at the given
+// location in pixels (not in the canvas coordinate system).
+// TODO(ericroman): rename Square to Rect
+bool VerifyBlackSquare(const PlatformCanvas& canvas, int x, int y, int w, int h) {
+ return VerifyRect(canvas, SK_ColorWHITE, SK_ColorBLACK, x, y, w, h);
+}
+
+// Check that every pixel in the canvas is a single color.
+bool VerifyCanvasColor(const PlatformCanvas& canvas, uint32_t canvas_color) {
+ return VerifyRect(canvas, canvas_color, 0, 0, 0, 0, 0);
+}
+
+#if defined(OS_WIN)
+void DrawNativeRect(PlatformCanvas& canvas, int x, int y, int w, int h) {
+ HDC dc = canvas.beginPlatformPaint();
+
+ RECT inner_rc;
+ inner_rc.left = x;
+ inner_rc.top = y;
+ inner_rc.right = x + w;
+ inner_rc.bottom = y + h;
+ FillRect(dc, &inner_rc, reinterpret_cast<HBRUSH>(GetStockObject(BLACK_BRUSH)));
+
+ canvas.endPlatformPaint();
+}
+#elif defined(OS_MACOSX)
+void DrawNativeRect(PlatformCanvas& canvas, int x, int y, int w, int h) {
+ CGContextRef context = canvas.beginPlatformPaint();
+
+ CGRect inner_rc = CGRectMake(x, y, w, h);
+ // RGBA opaque black
+ CGColorRef black = CGColorCreateGenericRGB(0.0, 0.0, 0.0, 1.0);
+ CGContextSetFillColorWithColor(context, black);
+ CGColorRelease(black);
+ CGContextFillRect(context, inner_rc);
+
+ canvas.endPlatformPaint();
+}
+#else
+void DrawNativeRect(PlatformCanvas& canvas, int x, int y, int w, int h) {
+ NOTIMPLEMENTED();
+}
+#endif
+
+// Clips the contents of the canvas to the given rectangle. This will be
+// intersected with any existing clip.
+void AddClip(PlatformCanvas& canvas, int x, int y, int w, int h) {
+ SkRect rect;
+ rect.set(SkIntToScalar(x), SkIntToScalar(y),
+ SkIntToScalar(x + w), SkIntToScalar(y + h));
+ canvas.clipRect(rect);
+}
+
+class LayerSaver {
+ public:
+ LayerSaver(PlatformCanvas& canvas, int x, int y, int w, int h)
+ : canvas_(canvas),
+ x_(x),
+ y_(y),
+ w_(w),
+ h_(h) {
+ SkRect bounds;
+ bounds.set(SkIntToScalar(x_), SkIntToScalar(y_),
+ SkIntToScalar(right()), SkIntToScalar(bottom()));
+ canvas_.saveLayer(&bounds, NULL);
+ }
+
+ ~LayerSaver() {
+ canvas_.getTopPlatformDevice().fixupAlphaBeforeCompositing();
+ canvas_.restore();
+ }
+
+ int x() const { return x_; }
+ int y() const { return y_; }
+ int w() const { return w_; }
+ int h() const { return h_; }
+
+ // Returns the EXCLUSIVE far bounds of the layer.
+ int right() const { return x_ + w_; }
+ int bottom() const { return y_ + h_; }
+
+ private:
+ PlatformCanvas& canvas_;
+ int x_, y_, w_, h_;
+};
+
+// Size used for making layers in many of the below tests.
+const int kLayerX = 2;
+const int kLayerY = 3;
+const int kLayerW = 9;
+const int kLayerH = 7;
+
+// Size used by some tests to draw a rectangle inside the layer.
+const int kInnerX = 4;
+const int kInnerY = 5;
+const int kInnerW = 2;
+const int kInnerH = 3;
+
+}
+
+// This just checks that our checking code is working properly, it just uses
+// regular skia primitives.
+TEST(PlatformCanvas, SkLayer) {
+ // Create the canvas initialized to opaque white.
+ PlatformCanvas canvas(16, 16, true);
+ canvas.drawColor(SK_ColorWHITE);
+
+ // Make a layer and fill it completely to make sure that the bounds are
+ // correct.
+ {
+ LayerSaver layer(canvas, kLayerX, kLayerY, kLayerW, kLayerH);
+ canvas.drawColor(SK_ColorBLACK);
+ }
+ EXPECT_TRUE(VerifyBlackSquare(canvas, kLayerX, kLayerY, kLayerW, kLayerH));
+}
+
+// Test native clipping.
+TEST(PlatformCanvas, ClipRegion) {
+ // Initialize a white canvas
+ PlatformCanvas canvas(16, 16, true);
+ canvas.drawColor(SK_ColorWHITE);
+ EXPECT_TRUE(VerifyCanvasColor(canvas, SK_ColorWHITE));
+
+ // Test that initially the canvas has no clip region, by filling it
+ // with a black rectangle.
+ // Note: Don't use LayerSaver, since internally it sets a clip region.
+ DrawNativeRect(canvas, 0, 0, 16, 16);
+ canvas.getTopPlatformDevice().fixupAlphaBeforeCompositing();
+ EXPECT_TRUE(VerifyCanvasColor(canvas, SK_ColorBLACK));
+
+ // Test that intersecting disjoint clip rectangles sets an empty clip region
+ canvas.drawColor(SK_ColorWHITE);
+ EXPECT_TRUE(VerifyCanvasColor(canvas, SK_ColorWHITE));
+ {
+ LayerSaver layer(canvas, 0, 0, 16, 16);
+ AddClip(canvas, 2, 3, 4, 5);
+ AddClip(canvas, 4, 9, 10, 10);
+ DrawNativeRect(canvas, 0, 0, 16, 16);
+ }
+ EXPECT_TRUE(VerifyCanvasColor(canvas, SK_ColorWHITE));
+}
+
+// Test the layers get filled properly by native rendering.
+TEST(PlatformCanvas, FillLayer) {
+ // Create the canvas initialized to opaque white.
+ PlatformCanvas canvas(16, 16, true);
+
+ // Make a layer and fill it completely to make sure that the bounds are
+ // correct.
+ canvas.drawColor(SK_ColorWHITE);
+ {
+ LayerSaver layer(canvas, kLayerX, kLayerY, kLayerW, kLayerH);
+ DrawNativeRect(canvas, 0, 0, 100, 100);
+ }
+ EXPECT_TRUE(VerifyBlackSquare(canvas, kLayerX, kLayerY, kLayerW, kLayerH));
+
+ // Make a layer and fill it partially to make sure the translation is correct.
+ canvas.drawColor(SK_ColorWHITE);
+ {
+ LayerSaver layer(canvas, kLayerX, kLayerY, kLayerW, kLayerH);
+ DrawNativeRect(canvas, kInnerX, kInnerY, kInnerW, kInnerH);
+ }
+ EXPECT_TRUE(VerifyBlackSquare(canvas, kInnerX, kInnerY, kInnerW, kInnerH));
+
+ // Add a clip on the layer and fill to make sure clip is correct.
+ canvas.drawColor(SK_ColorWHITE);
+ {
+ LayerSaver layer(canvas, kLayerX, kLayerY, kLayerW, kLayerH);
+ canvas.save();
+ AddClip(canvas, kInnerX, kInnerY, kInnerW, kInnerH);
+ DrawNativeRect(canvas, 0, 0, 100, 100);
+ canvas.restore();
+ }
+ EXPECT_TRUE(VerifyBlackSquare(canvas, kInnerX, kInnerY, kInnerW, kInnerH));
+
+ // Add a clip and then make the layer to make sure the clip is correct.
+ canvas.drawColor(SK_ColorWHITE);
+ canvas.save();
+ AddClip(canvas, kInnerX, kInnerY, kInnerW, kInnerH);
+ {
+ LayerSaver layer(canvas, kLayerX, kLayerY, kLayerW, kLayerH);
+ DrawNativeRect(canvas, 0, 0, 100, 100);
+ }
+ canvas.restore();
+ EXPECT_TRUE(VerifyBlackSquare(canvas, kInnerX, kInnerY, kInnerW, kInnerH));
+}
+
+// Test that translation + make layer works properly.
+TEST(PlatformCanvas, TranslateLayer) {
+ // Create the canvas initialized to opaque white.
+ PlatformCanvas canvas(16, 16, true);
+
+ // Make a layer and fill it completely to make sure that the bounds are
+ // correct.
+ canvas.drawColor(SK_ColorWHITE);
+ canvas.save();
+ canvas.translate(1, 1);
+ {
+ LayerSaver layer(canvas, kLayerX, kLayerY, kLayerW, kLayerH);
+ DrawNativeRect(canvas, 0, 0, 100, 100);
+ }
+ canvas.restore();
+ EXPECT_TRUE(VerifyBlackSquare(canvas, kLayerX + 1, kLayerY + 1,
+ kLayerW, kLayerH));
+
+ // Translate then make the layer.
+ canvas.drawColor(SK_ColorWHITE);
+ canvas.save();
+ canvas.translate(1, 1);
+ {
+ LayerSaver layer(canvas, kLayerX, kLayerY, kLayerW, kLayerH);
+ DrawNativeRect(canvas, kInnerX, kInnerY, kInnerW, kInnerH);
+ }
+ canvas.restore();
+ EXPECT_TRUE(VerifyBlackSquare(canvas, kInnerX + 1, kInnerY + 1,
+ kInnerW, kInnerH));
+
+ // Make the layer then translate.
+ canvas.drawColor(SK_ColorWHITE);
+ canvas.save();
+ {
+ LayerSaver layer(canvas, kLayerX, kLayerY, kLayerW, kLayerH);
+ canvas.translate(1, 1);
+ DrawNativeRect(canvas, kInnerX, kInnerY, kInnerW, kInnerH);
+ }
+ canvas.restore();
+ EXPECT_TRUE(VerifyBlackSquare(canvas, kInnerX + 1, kInnerY + 1,
+ kInnerW, kInnerH));
+
+ // Translate both before and after, and have a clip.
+ canvas.drawColor(SK_ColorWHITE);
+ canvas.save();
+ canvas.translate(1, 1);
+ {
+ LayerSaver layer(canvas, kLayerX, kLayerY, kLayerW, kLayerH);
+ canvas.translate(1, 1);
+ AddClip(canvas, kInnerX, kInnerY, kInnerW, kInnerH);
+ DrawNativeRect(canvas, 0, 0, 100, 100);
+ }
+ canvas.restore();
+ EXPECT_TRUE(VerifyBlackSquare(canvas, kInnerX + 2, kInnerY + 2,
+ kInnerW, kInnerH));
+}
+
+} // namespace
+
diff --git a/skia/ext/platform_canvas_win.cc b/skia/ext/platform_canvas_win.cc
new file mode 100644
index 0000000..e3dffeb7
--- /dev/null
+++ b/skia/ext/platform_canvas_win.cc
@@ -0,0 +1,117 @@
+// 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/platform_canvas_win.h"
+
+#include "base/logging.h"
+#include "base/process_util.h"
+#include "skia/ext/bitmap_platform_device_win.h"
+
+namespace gfx {
+
+// Crashes the process. This is called when a bitmap allocation fails, and this
+// function tries to determine why it might have failed, and crash on different
+// lines. This allows us to see in crash dumps the most likely reason for the
+// failure. It takes the size of the bitmap we were trying to allocate as its
+// arguments so we can check that as well.
+void CrashForBitmapAllocationFailure(int w, int h) {
+ // The maximum number of GDI objects per process is 10K. If we're very close
+ // to that, it's probably the problem.
+ const int kLotsOfGDIObjs = 9990;
+ CHECK(GetGuiResources(GetCurrentProcess(), GR_GDIOBJECTS) < kLotsOfGDIObjs);
+
+ // If the bitmap is ginormous, then we probably can't allocate it.
+ // We use 64M pixels = 256MB @ 4 bytes per pixel.
+ const int64 kGinormousBitmapPxl = 64000000;
+ CHECK(static_cast<int64>(w) * static_cast<int64>(h) < kGinormousBitmapPxl);
+
+ // If we're using a crazy amount of virtual address space, then maybe there
+ // isn't enough for our bitmap.
+ const int64 kLotsOfMem = 1500000000; // 1.5GB.
+ scoped_ptr<base::ProcessMetrics> process_metrics(
+ base::ProcessMetrics::CreateProcessMetrics(GetCurrentProcess()));
+ CHECK(process_metrics->GetPagefileUsage() < kLotsOfMem);
+
+ // Everything else.
+ CHECK(0);
+}
+
+
+PlatformCanvasWin::PlatformCanvasWin() : SkCanvas() {
+}
+
+PlatformCanvasWin::PlatformCanvasWin(int width, int height, bool is_opaque)
+ : SkCanvas() {
+ bool initialized = initialize(width, height, is_opaque, NULL);
+ if (!initialized)
+ CrashForBitmapAllocationFailure(width, height);
+}
+
+PlatformCanvasWin::PlatformCanvasWin(int width,
+ int height,
+ bool is_opaque,
+ HANDLE shared_section)
+ : SkCanvas() {
+ bool initialized = initialize(width, height, is_opaque, shared_section);
+ if (!initialized)
+ CrashForBitmapAllocationFailure(width, height);
+}
+
+PlatformCanvasWin::~PlatformCanvasWin() {
+}
+
+bool PlatformCanvasWin::initialize(int width,
+ int height,
+ bool is_opaque,
+ HANDLE shared_section) {
+ SkDevice* device =
+ createPlatformDevice(width, height, is_opaque, shared_section);
+ if (!device)
+ return false;
+
+ setDevice(device);
+ device->unref(); // was created with refcount 1, and setDevice also refs
+ return true;
+}
+
+HDC PlatformCanvasWin::beginPlatformPaint() {
+ return getTopPlatformDevice().getBitmapDC();
+}
+
+void PlatformCanvasWin::endPlatformPaint() {
+ // we don't clear the DC here since it will be likely to be used again
+ // flushing will be done in onAccessBitmap
+}
+
+PlatformDeviceWin& PlatformCanvasWin::getTopPlatformDevice() const {
+ // All of our devices should be our special PlatformDevice.
+ SkCanvas::LayerIter iter(const_cast<PlatformCanvasWin*>(this), false);
+ return *static_cast<PlatformDeviceWin*>(iter.device());
+}
+
+SkDevice* PlatformCanvasWin::createDevice(SkBitmap::Config config,
+ int width,
+ int height,
+ bool is_opaque, bool isForLayer) {
+ DCHECK(config == SkBitmap::kARGB_8888_Config);
+ return createPlatformDevice(width, height, is_opaque, NULL);
+}
+
+SkDevice* PlatformCanvasWin::createPlatformDevice(int width,
+ int height,
+ bool is_opaque,
+ HANDLE shared_section) {
+ HDC screen_dc = GetDC(NULL);
+ SkDevice* device = BitmapPlatformDeviceWin::create(screen_dc, width, height,
+ is_opaque, shared_section);
+ ReleaseDC(NULL, screen_dc);
+ return device;
+}
+
+SkDevice* PlatformCanvasWin::setBitmapDevice(const SkBitmap&) {
+ NOTREACHED();
+ return NULL;
+}
+
+} // namespace gfx
diff --git a/skia/ext/platform_canvas_win.h b/skia/ext/platform_canvas_win.h
new file mode 100644
index 0000000..c3f42a3
--- /dev/null
+++ b/skia/ext/platform_canvas_win.h
@@ -0,0 +1,202 @@
+// 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.
+
+#ifndef SKIA_EXT_PLATFORM_CANVAS_WIN_H_
+#define SKIA_EXT_PLATFORM_CANVAS_WIN_H_
+
+#include <windows.h>
+
+#include "base/basictypes.h"
+#include "skia/ext/platform_device_win.h"
+
+#include "SkCanvas.h"
+
+namespace gfx {
+
+// This class is a specialization of the regular SkCanvas that is designed to
+// work with a gfx::PlatformDevice to manage platform-specific drawing. It
+// allows using both Skia operations and platform-specific operations.
+class PlatformCanvasWin : public SkCanvas {
+ public:
+ // Set is_opaque if you are going to erase the bitmap and not use
+ // transparency: this will enable some optimizations. The shared_section
+ // parameter is passed to gfx::PlatformDevice::create. See it for details.
+ //
+ // If you use the version with no arguments, you MUST call initialize()
+ PlatformCanvasWin();
+ PlatformCanvasWin(int width, int height, bool is_opaque);
+ PlatformCanvasWin(int width, int height, bool is_opaque,
+ HANDLE shared_section);
+ virtual ~PlatformCanvasWin();
+
+ // For two-part init, call if you use the no-argument constructor above. Note
+ // that we want this to optionally match the Linux initialize if you only
+ // pass 3 arguments, hence the evil default argument.
+ bool initialize(int width, int height, bool is_opaque,
+ HANDLE shared_section = NULL);
+
+ // These calls should surround calls to platform drawing routines, the DC
+ // returned by beginPlatformPaint is the DC that can be used to draw into.
+ // Call endPlatformPaint when you are done and want to use Skia operations
+ // again; this will synchronize the bitmap to Windows.
+ virtual HDC beginPlatformPaint();
+ virtual void endPlatformPaint();
+
+ // Returns the platform device pointer of the topmost rect with a non-empty
+ // clip. In practice, this is usually either the top layer or nothing, since
+ // we usually set the clip to new layers when we make them.
+ //
+ // If there is no layer that is not all clipped out, this will return a
+ // dummy device so callers do not have to check. If you are concerned about
+ // performance, check the clip before doing any painting.
+ //
+ // This is different than SkCanvas' getDevice, because that returns the
+ // bottommost device.
+ //
+ // Danger: the resulting device should not be saved. It will be invalidated
+ // by the next call to save() or restore().
+ PlatformDeviceWin& getTopPlatformDevice() const;
+
+ protected:
+ // Creates a device store for use by the canvas. We override this so that
+ // the device is always our own so we know that we can use GDI operations
+ // on it. Simply calls into createPlatformDevice().
+ virtual SkDevice* createDevice(SkBitmap::Config, int width, int height,
+ bool is_opaque, bool isForLayer);
+
+ // Creates a device store for use by the canvas. By default, it creates a
+ // BitmapPlatformDeviceWin. Can be overridden to change the object type.
+ virtual SkDevice* createPlatformDevice(int width, int height, bool is_opaque,
+ HANDLE shared_section);
+
+ private:
+ // Unimplemented.
+ virtual SkDevice* setBitmapDevice(const SkBitmap& bitmap);
+
+ // Disallow copy and assign.
+ PlatformCanvasWin(const PlatformCanvasWin&);
+ PlatformCanvasWin& operator=(const PlatformCanvasWin&);
+};
+
+// A class designed to help with WM_PAINT operations on Windows. It will
+// do BeginPaint/EndPaint on init/destruction, and will create the bitmap and
+// canvas with the correct size and transform for the dirty rect. The bitmap
+// will be automatically painted to the screen on destruction.
+//
+// You MUST call isEmpty before painting to determine if anything needs
+// painting. Sometimes the dirty rect can actually be empty, and this makes
+// the bitmap functions we call unhappy. The caller should not paint in this
+// case.
+//
+// Therefore, all you need to do is:
+// case WM_PAINT: {
+// gfx::PlatformCanvasWinPaint canvas(hwnd);
+// if (!canvas.isEmpty()) {
+// ... paint to the canvas ...
+// }
+// return 0;
+// }
+template <class T>
+class CanvasPaintT : public T {
+ public:
+ CanvasPaintT(HWND hwnd) : hwnd_(hwnd), paint_dc_(NULL), for_paint_(true) {
+ memset(&ps_, 0, sizeof(ps_));
+ initPaint(true);
+ }
+
+ CanvasPaintT(HWND hwnd, bool opaque) : hwnd_(hwnd), paint_dc_(NULL),
+ for_paint_(true) {
+ memset(&ps_, 0, sizeof(ps_));
+ initPaint(opaque);
+ }
+
+ // Creates a CanvasPaintT for the specified region that paints to the
+ // specified dc. This does NOT do BeginPaint/EndPaint.
+ CanvasPaintT(HDC dc, bool opaque, int x, int y, int w, int h)
+ : hwnd_(NULL),
+ paint_dc_(dc),
+ for_paint_(false) {
+ memset(&ps_, 0, sizeof(ps_));
+ ps_.rcPaint.left = x;
+ ps_.rcPaint.right = x + w;
+ ps_.rcPaint.top = y;
+ ps_.rcPaint.bottom = y + h;
+ init(opaque);
+ }
+
+
+ virtual ~CanvasPaintT() {
+ if (!isEmpty()) {
+ restoreToCount(1);
+ // Commit the drawing to the screen
+ getTopPlatformDevice().drawToHDC(paint_dc_,
+ ps_.rcPaint.left, ps_.rcPaint.top,
+ NULL);
+ }
+ if (for_paint_)
+ EndPaint(hwnd_, &ps_);
+ }
+
+ // Returns true if the invalid region is empty. The caller should call this
+ // function to determine if anything needs painting.
+ bool isEmpty() const {
+ return ps_.rcPaint.right - ps_.rcPaint.left == 0 ||
+ ps_.rcPaint.bottom - ps_.rcPaint.top == 0;
+ }
+
+ // Use to access the Windows painting parameters, especially useful for
+ // getting the bounding rect for painting: paintstruct().rcPaint
+ const PAINTSTRUCT& paintStruct() const {
+ return ps_;
+ }
+
+ // Returns the DC that will be painted to
+ HDC paintDC() const {
+ return paint_dc_;
+ }
+
+ protected:
+ HWND hwnd_;
+ HDC paint_dc_;
+ PAINTSTRUCT ps_;
+
+ private:
+ void initPaint(bool opaque) {
+ paint_dc_ = BeginPaint(hwnd_, &ps_);
+
+ init(opaque);
+ }
+
+ void init(bool opaque) {
+ // FIXME(brettw) for ClearType, we probably want to expand the bounds of
+ // painting by one pixel so that the boundaries will be correct (ClearType
+ // text can depend on the adjacent pixel). Then we would paint just the
+ // inset pixels to the screen.
+ const int width = ps_.rcPaint.right - ps_.rcPaint.left;
+ const int height = ps_.rcPaint.bottom - ps_.rcPaint.top;
+ if (!initialize(width, height, opaque, NULL)) {
+ // Cause a deliberate crash;
+ *(char*) 0 = 0;
+ }
+
+ // This will bring the canvas into the screen coordinate system for the
+ // dirty rect
+ translate(SkIntToScalar(-ps_.rcPaint.left),
+ SkIntToScalar(-ps_.rcPaint.top));
+ }
+
+ // If true, this canvas was created for a BeginPaint.
+ const bool for_paint_;
+
+ // Disallow copy and assign.
+ CanvasPaintT(const CanvasPaintT&);
+ CanvasPaintT& operator=(const CanvasPaintT&);
+};
+
+typedef CanvasPaintT<PlatformCanvasWin> PlatformCanvasWinPaint;
+
+} // namespace gfx
+
+#endif // SKIA_EXT_PLATFORM_CANVAS_WIN_H_
+
diff --git a/skia/ext/platform_device.h b/skia/ext/platform_device.h
new file mode 100644
index 0000000..3072e08
--- /dev/null
+++ b/skia/ext/platform_device.h
@@ -0,0 +1,27 @@
+// 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.
+
+// Declare a platform-neutral name for this platform's device class
+// that can be used by upper-level classes that just need to pass a reference
+// around.
+
+#if defined(WIN32)
+#include "PlatformDeviceWin.h"
+#elif defined(__APPLE__)
+#include "PlatformDeviceMac.h"
+#elif defined(__linux__)
+#include "PlatformDeviceLinux.h"
+#endif
+
+namespace gfx {
+
+#if defined(WIN32)
+typedef PlatformDeviceWin PlatformDevice;
+#elif defined(__APPLE__)
+typedef PlatformDeviceMac PlatformDevice;
+#elif defined(__linux__)
+typedef PlatformDeviceLinux PlatformDevice;
+#endif
+
+} // namespace gfx
diff --git a/skia/ext/platform_device_win.cc b/skia/ext/platform_device_win.cc
new file mode 100644
index 0000000..15201c0
--- /dev/null
+++ b/skia/ext/platform_device_win.cc
@@ -0,0 +1,229 @@
+// 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/platform_device_win.h"
+
+#include "base/logging.h"
+#include "base/gfx/skia_utils.h"
+#include "SkMatrix.h"
+#include "SkPath.h"
+#include "SkRegion.h"
+#include "SkUtils.h"
+
+namespace gfx {
+
+PlatformDeviceWin::PlatformDeviceWin(const SkBitmap& bitmap)
+ : SkDevice(bitmap) {
+}
+
+// static
+void PlatformDeviceWin::InitializeDC(HDC context) {
+ // Enables world transformation.
+ // If the GM_ADVANCED graphics mode is set, GDI always draws arcs in the
+ // counterclockwise direction in logical space. This is equivalent to the
+ // statement that, in the GM_ADVANCED graphics mode, both arc control points
+ // and arcs themselves fully respect the device context's world-to-device
+ // transformation.
+ BOOL res = SetGraphicsMode(context, GM_ADVANCED);
+ DCHECK_NE(res, 0);
+
+ // Enables dithering.
+ res = SetStretchBltMode(context, HALFTONE);
+ DCHECK_NE(res, 0);
+ // As per SetStretchBltMode() documentation, SetBrushOrgEx() must be called
+ // right after.
+ res = SetBrushOrgEx(context, 0, 0, NULL);
+ DCHECK_NE(res, 0);
+
+ // Sets up default orientation.
+ res = SetArcDirection(context, AD_CLOCKWISE);
+ DCHECK_NE(res, 0);
+
+ // Sets up default colors.
+ res = SetBkColor(context, RGB(255, 255, 255));
+ DCHECK_NE(res, CLR_INVALID);
+ res = SetTextColor(context, RGB(0, 0, 0));
+ DCHECK_NE(res, CLR_INVALID);
+ res = SetDCBrushColor(context, RGB(255, 255, 255));
+ DCHECK_NE(res, CLR_INVALID);
+ res = SetDCPenColor(context, RGB(0, 0, 0));
+ DCHECK_NE(res, CLR_INVALID);
+
+ // Sets up default transparency.
+ res = SetBkMode(context, OPAQUE);
+ DCHECK_NE(res, 0);
+ res = SetROP2(context, R2_COPYPEN);
+ DCHECK_NE(res, 0);
+}
+
+// static
+void PlatformDeviceWin::LoadPathToDC(HDC context, const SkPath& path) {
+ switch (path.getFillType()) {
+ case SkPath::kWinding_FillType: {
+ int res = SetPolyFillMode(context, WINDING);
+ DCHECK(res != 0);
+ break;
+ }
+ case SkPath::kEvenOdd_FillType: {
+ int res = SetPolyFillMode(context, ALTERNATE);
+ DCHECK(res != 0);
+ break;
+ }
+ default: {
+ NOTREACHED();
+ break;
+ }
+ }
+ BOOL res = BeginPath(context);
+ DCHECK(res != 0);
+
+ CubicPaths paths;
+ if (!SkPathToCubicPaths(&paths, path))
+ return;
+
+ std::vector<POINT> points;
+ for (CubicPaths::const_iterator path(paths.begin()); path != paths.end();
+ ++path) {
+ if (!path->size())
+ continue;
+ // DCHECK_EQ(points.size() % 4, 0);
+ points.resize(0);
+ points.reserve(path->size() * 3 / 4 + 1);
+ points.push_back(SkPointToPOINT(path->front().p[0]));
+ for (CubicPath::const_iterator point(path->begin()); point != path->end();
+ ++point) {
+ // Never add point->p[0]
+ points.push_back(SkPointToPOINT(point->p[1]));
+ points.push_back(SkPointToPOINT(point->p[2]));
+ points.push_back(SkPointToPOINT(point->p[3]));
+ }
+ DCHECK_EQ((points.size() - 1) % 3, 0);
+ // This is slightly inefficient since all straight line and quadratic lines
+ // are "upgraded" to a cubic line.
+ // TODO(maruel): http://b/1147346 We should use
+ // PolyDraw/PolyBezier/Polyline whenever possible.
+ res = PolyBezier(context, &points.front(),
+ static_cast<DWORD>(points.size()));
+ DCHECK_NE(res, 0);
+ if (res == 0)
+ break;
+ }
+ if (res == 0) {
+ // Make sure the path is discarded.
+ AbortPath(context);
+ } else {
+ res = EndPath(context);
+ DCHECK(res != 0);
+ }
+}
+
+// static
+void PlatformDeviceWin::LoadTransformToDC(HDC dc, const SkMatrix& matrix) {
+ XFORM xf;
+ xf.eM11 = matrix[SkMatrix::kMScaleX];
+ xf.eM21 = matrix[SkMatrix::kMSkewX];
+ xf.eDx = matrix[SkMatrix::kMTransX];
+ xf.eM12 = matrix[SkMatrix::kMSkewY];
+ xf.eM22 = matrix[SkMatrix::kMScaleY];
+ xf.eDy = matrix[SkMatrix::kMTransY];
+ SetWorldTransform(dc, &xf);
+}
+
+// static
+bool PlatformDeviceWin::SkPathToCubicPaths(CubicPaths* paths,
+ const SkPath& skpath) {
+ paths->clear();
+ CubicPath* current_path = NULL;
+ SkPoint current_points[4];
+ CubicPoints points_to_add;
+ SkPath::Iter iter(skpath, false);
+ for (SkPath::Verb verb = iter.next(current_points);
+ verb != SkPath::kDone_Verb;
+ verb = iter.next(current_points)) {
+ switch (verb) {
+ case SkPath::kMove_Verb: { // iter.next returns 1 point
+ // Ignores it since the point is copied in the next operation. See
+ // SkPath::Iter::next() for reference.
+ paths->push_back(CubicPath());
+ current_path = &paths->back();
+ // Skip point addition.
+ continue;
+ }
+ case SkPath::kLine_Verb: { // iter.next returns 2 points
+ points_to_add.p[0] = current_points[0];
+ points_to_add.p[1] = current_points[0];
+ points_to_add.p[2] = current_points[1];
+ points_to_add.p[3] = current_points[1];
+ break;
+ }
+ case SkPath::kQuad_Verb: { // iter.next returns 3 points
+ points_to_add.p[0] = current_points[0];
+ points_to_add.p[1] = current_points[1];
+ points_to_add.p[2] = current_points[2];
+ points_to_add.p[3] = current_points[2];
+ break;
+ }
+ case SkPath::kCubic_Verb: { // iter.next returns 4 points
+ points_to_add.p[0] = current_points[0];
+ points_to_add.p[1] = current_points[1];
+ points_to_add.p[2] = current_points[2];
+ points_to_add.p[3] = current_points[3];
+ break;
+ }
+ case SkPath::kClose_Verb: { // iter.next returns 1 point (the last point)
+ paths->push_back(CubicPath());
+ current_path = &paths->back();
+ continue;
+ }
+ case SkPath::kDone_Verb: // iter.next returns 0 points
+ default: {
+ current_path = NULL;
+ // Will return false.
+ break;
+ }
+ }
+ DCHECK(current_path);
+ if (!current_path) {
+ paths->clear();
+ return false;
+ }
+ current_path->push_back(points_to_add);
+ }
+ return true;
+}
+
+// static
+void PlatformDeviceWin::LoadClippingRegionToDC(HDC context,
+ const SkRegion& region,
+ const SkMatrix& transformation) {
+ HRGN hrgn;
+ if (region.isEmpty()) {
+ // region can be empty, in which case everything will be clipped.
+ hrgn = CreateRectRgn(0, 0, 0, 0);
+ } else if (region.isRect()) {
+ // Do the transformation.
+ SkRect rect;
+ rect.set(region.getBounds());
+ transformation.mapRect(&rect);
+ SkIRect irect;
+ rect.round(&irect);
+ hrgn = CreateRectRgnIndirect(&SkIRectToRECT(irect));
+ } else {
+ // It is complex.
+ SkPath path;
+ region.getBoundaryPath(&path);
+ // Clip. Note that windows clipping regions are not affected by the
+ // transform so apply it manually.
+ path.transform(transformation);
+ LoadPathToDC(context, path);
+ hrgn = PathToRegion(context);
+ }
+ int result = SelectClipRgn(context, hrgn);
+ DCHECK_NE(result, ERROR);
+ result = DeleteObject(hrgn);
+ DCHECK_NE(result, 0);
+}
+
+} // namespace gfx
+
diff --git a/skia/ext/platform_device_win.h b/skia/ext/platform_device_win.h
new file mode 100644
index 0000000..e01a2c5
--- /dev/null
+++ b/skia/ext/platform_device_win.h
@@ -0,0 +1,98 @@
+// 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.
+
+#ifndef SKIA_EXT_PLATFORM_DEVICE_WIN_H_
+#define SKIA_EXT_PLATFORM_DEVICE_WIN_H_
+
+#include <windows.h>
+
+#include <vector>
+
+#include "SkDevice.h"
+
+class SkMatrix;
+class SkPath;
+class SkRegion;
+
+namespace gfx {
+
+// A device is basically a wrapper around SkBitmap that provides a surface for
+// SkCanvas to draw into. Our device provides a surface Windows can also write
+// to. It also provides functionality to play well with GDI drawing functions.
+// This class is abstract and must be subclassed. It provides the basic
+// interface to implement it either with or without a bitmap backend.
+class PlatformDeviceWin : public SkDevice {
+ public:
+ // The DC that corresponds to the bitmap, used for GDI operations drawing
+ // into the bitmap. This is possibly heavyweight, so it should be existant
+ // only during one pass of rendering.
+ virtual HDC getBitmapDC() = 0;
+
+ // Draws to the given screen DC, if the bitmap DC doesn't exist, this will
+ // temporarily create it. However, if you have created the bitmap DC, it will
+ // be more efficient if you don't free it until after this call so it doesn't
+ // have to be created twice. If src_rect is null, then the entirety of the
+ // source device will be copied.
+ virtual void drawToHDC(HDC dc, int x, int y, const RECT* src_rect) = 0;
+
+ // Invoke before using GDI functions. See description in platform_device.cc
+ // for specifics.
+ // NOTE: x,y,width and height are relative to the current transform.
+ virtual void prepareForGDI(int x, int y, int width, int height) { }
+
+ // Invoke after using GDI functions. See description in platform_device.cc
+ // for specifics.
+ // NOTE: x,y,width and height are relative to the current transform.
+ virtual void postProcessGDI(int x, int y, int width, int height) { }
+
+ // Sets the opacity of each pixel in the specified region to be opaque.
+ virtual void makeOpaque(int x, int y, int width, int height) { }
+
+ // Call this function to fix the alpha channels before compositing this layer
+ // onto another. Internally, the device uses a special alpha method to work
+ // around problems with Windows. This call will put the values into what
+ // Skia expects, so it can be composited onto other layers.
+ //
+ // After this call, no more drawing can be done because the
+ // alpha channels will be "correct", which, if this function is called again
+ // will make them wrong. See the implementation for more discussion.
+ virtual void fixupAlphaBeforeCompositing() { }
+
+ // Returns if the preferred rendering engine is vectorial or bitmap based.
+ virtual bool IsVectorial() = 0;
+
+ // Initializes the default settings and colors in a device context.
+ static void InitializeDC(HDC context);
+
+ // Loads a SkPath into the GDI context. The path can there after be used for
+ // clipping or as a stroke.
+ static void LoadPathToDC(HDC context, const SkPath& path);
+
+ // Loads a SkRegion into the GDI context.
+ static void LoadClippingRegionToDC(HDC context, const SkRegion& region,
+ const SkMatrix& transformation);
+
+ protected:
+ // Arrays must be inside structures.
+ struct CubicPoints {
+ SkPoint p[4];
+ };
+ typedef std::vector<CubicPoints> CubicPath;
+ typedef std::vector<CubicPath> CubicPaths;
+
+ // Forwards |bitmap| to SkDevice's constructor.
+ PlatformDeviceWin(const SkBitmap& bitmap);
+
+ // Loads the specified Skia transform into the device context, excluding
+ // perspective (which GDI doesn't support).
+ static void LoadTransformToDC(HDC dc, const SkMatrix& matrix);
+
+ // Transforms SkPath's paths into a series of cubic path.
+ static bool SkPathToCubicPaths(CubicPaths* paths, const SkPath& skpath);
+};
+
+} // namespace gfx
+
+#endif // SKIA_EXT_PLATFORM_DEVICE_WIN_H_
+
diff --git a/skia/ext/vector_canvas.cc b/skia/ext/vector_canvas.cc
new file mode 100644
index 0000000..440ee64
--- /dev/null
+++ b/skia/ext/vector_canvas.cc
@@ -0,0 +1,90 @@
+// 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 "base/gfx/vector_canvas.h"
+
+#include "base/gfx/vector_device.h"
+#include "base/logging.h"
+
+namespace gfx {
+
+VectorCanvas::VectorCanvas() {
+}
+
+VectorCanvas::VectorCanvas(HDC dc, int width, int height) {
+ bool initialized = initialize(dc, width, height);
+ CHECK(initialized);
+}
+
+VectorCanvas::~VectorCanvas() {
+}
+
+bool VectorCanvas::initialize(HDC context, int width, int height) {
+ SkDevice* device = createPlatformDevice(width, height, true, context);
+ if (!device)
+ return false;
+
+ setDevice(device);
+ device->unref(); // was created with refcount 1, and setDevice also refs
+ return true;
+}
+
+SkBounder* VectorCanvas::setBounder(SkBounder* bounder) {
+ if (!IsTopDeviceVectorial())
+ return PlatformCanvasWin::setBounder(bounder);
+
+ // This function isn't used in the code. Verify this assumption.
+ NOTREACHED();
+ return NULL;
+}
+
+SkDevice* VectorCanvas::createDevice(SkBitmap::Config config,
+ int width, int height,
+ bool is_opaque, bool isForLayer) {
+ DCHECK(config == SkBitmap::kARGB_8888_Config);
+ return createPlatformDevice(width, height, is_opaque, NULL);
+}
+
+SkDrawFilter* VectorCanvas::setDrawFilter(SkDrawFilter* filter) {
+ // This function isn't used in the code. Verify this assumption.
+ NOTREACHED();
+ return NULL;
+}
+
+SkDevice* VectorCanvas::createPlatformDevice(int width,
+ int height, bool is_opaque,
+ HANDLE shared_section) {
+ if (!is_opaque) {
+ // TODO(maruel): http://b/1184002 1184002 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
+ // BitmapPlatformDeviceWin 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 PlatformCanvasWin::createPlatformDevice(width, height, is_opaque,
+ shared_section);
+ }
+
+ // TODO(maruel): http://b/1183870 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.
+ DCHECK(shared_section);
+ PlatformDeviceWin* device = VectorDevice::create(
+ reinterpret_cast<HDC>(shared_section), width, height);
+ return device;
+}
+
+bool VectorCanvas::IsTopDeviceVectorial() const {
+ return getTopPlatformDevice().IsVectorial();
+}
+
+} // namespace gfx
+
diff --git a/skia/ext/vector_canvas.h b/skia/ext/vector_canvas.h
new file mode 100644
index 0000000..8538366
--- /dev/null
+++ b/skia/ext/vector_canvas.h
@@ -0,0 +1,46 @@
+// 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.
+
+#ifndef BASE_GFX_VECTOR_CANVAS_H_
+#define BASE_GFX_VECTOR_CANVAS_H_
+
+#include "base/gfx/platform_canvas_win.h"
+#include "base/gfx/vector_device.h"
+
+namespace gfx {
+
+// This class is a specialization of the regular PlatformCanvas. It is designed
+// to work with a VectorDevice to manage platform-specific drawing. It allows
+// using both Skia operations and platform-specific operations. It *doesn't*
+// support reading back from the bitmap backstore since it is not used.
+class VectorCanvas : public PlatformCanvasWin {
+ public:
+ VectorCanvas();
+ VectorCanvas(HDC dc, int width, int height);
+ virtual ~VectorCanvas();
+
+ // For two-part init, call if you use the no-argument constructor above
+ bool initialize(HDC context, int width, int height);
+
+ virtual SkBounder* setBounder(SkBounder*);
+ virtual SkDevice* createDevice(SkBitmap::Config config,
+ int width, int height,
+ bool is_opaque, bool isForLayer);
+ virtual SkDrawFilter* setDrawFilter(SkDrawFilter* filter);
+
+ private:
+ // |is_opaque| is unused. |shared_section| is in fact the HDC used for output.
+ virtual SkDevice* createPlatformDevice(int width, int height, bool is_opaque,
+ HANDLE shared_section);
+
+ // Returns true if the top device is vector based and not bitmap based.
+ bool IsTopDeviceVectorial() const;
+
+ DISALLOW_COPY_AND_ASSIGN(VectorCanvas);
+};
+
+} // namespace gfx
+
+#endif // BASE_GFX_VECTOR_CANVAS_H_
+
diff --git a/skia/ext/vector_canvas_unittest.cc b/skia/ext/vector_canvas_unittest.cc
new file mode 100644
index 0000000..b50c230
--- /dev/null
+++ b/skia/ext/vector_canvas_unittest.cc
@@ -0,0 +1,1009 @@
+// 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_canvas.h"
+
+#include <vector>
+
+#include "base/command_line.h"
+#include "base/file_util.h"
+#include "base/gfx/gdi_util.h"
+#include "base/gfx/png_decoder.h"
+#include "base/gfx/png_encoder.h"
+#include "base/gfx/size.h"
+#include "base/path_service.h"
+#include "base/string_util.h"
+#include "base/win_util.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+#include "SkDashPathEffect.h"
+
+namespace {
+
+const wchar_t* const kGenerateSwitch = L"vector-canvas-generate";
+
+// Base class for unit test that uses data. It initializes a directory path
+// based on the test's name.
+class DataUnitTest : public testing::Test {
+ public:
+ DataUnitTest(const std::wstring& base_path) : base_path_(base_path) { }
+
+ protected:
+ // Load the test's data path.
+ virtual void SetUp() {
+ const testing::TestInfo& test_info =
+ *testing::UnitTest::GetInstance()->current_test_info();
+ PathService::Get(base::DIR_SOURCE_ROOT, &test_dir_);
+ file_util::AppendToPath(&test_dir_, base_path_);
+ file_util::AppendToPath(&test_dir_, L"data");
+ file_util::AppendToPath(&test_dir_,
+ ASCIIToWide(test_info.test_case_name()));
+ file_util::AppendToPath(&test_dir_, ASCIIToWide(test_info.name()));
+
+ // Hack for a quick lowercase. We assume all the tests names are ASCII.
+ std::string tmp(WideToASCII(test_dir_));
+ for (size_t i = 0; i < tmp.size(); ++i)
+ tmp[i] = ToLowerASCII(tmp[i]);
+ test_dir_ = ASCIIToWide(tmp);
+ }
+
+ // Returns the fully qualified path of directory containing test data files.
+ const std::wstring& test_dir() const {
+ return test_dir_;
+ }
+
+ // Returns the fully qualified path of a data file.
+ std::wstring test_file(const std::wstring& filename) const {
+ // Hack for a quick lowercase. We assume all the test data file names are
+ // ASCII.
+ std::string tmp(WideToASCII(filename));
+ for (size_t i = 0; i < tmp.size(); ++i)
+ tmp[i] = ToLowerASCII(tmp[i]);
+
+ std::wstring path(test_dir());
+ file_util::AppendToPath(&path, ASCIIToWide(tmp));
+ return path;
+ }
+
+ private:
+ // Path where the unit test is coming from: base, net, chrome, etc.
+ std::wstring base_path_;
+
+ // Path to directory used to contain the test data.
+ std::wstring test_dir_;
+
+ DISALLOW_EVIL_CONSTRUCTORS(DataUnitTest);
+};
+
+// Lightweight HDC management.
+class Context {
+ public:
+ Context() : context_(CreateCompatibleDC(NULL)) {
+ EXPECT_TRUE(context_);
+ }
+ ~Context() {
+ DeleteDC(context_);
+ }
+
+ HDC context() const { return context_; }
+
+ private:
+ HDC context_;
+
+ DISALLOW_EVIL_CONSTRUCTORS(Context);
+};
+
+// Lightweight HBITMAP management.
+class Bitmap {
+ public:
+ Bitmap(const Context& context, int x, int y) {
+ BITMAPINFOHEADER hdr;
+ gfx::CreateBitmapHeader(x, y, &hdr);
+ bitmap_ = CreateDIBSection(context.context(),
+ reinterpret_cast<BITMAPINFO*>(&hdr), 0,
+ &data_, NULL, 0);
+ EXPECT_TRUE(bitmap_);
+ EXPECT_TRUE(SelectObject(context.context(), bitmap_));
+ }
+ ~Bitmap() {
+ EXPECT_TRUE(DeleteObject(bitmap_));
+ }
+
+ private:
+ HBITMAP bitmap_;
+
+ void* data_;
+
+ DISALLOW_EVIL_CONSTRUCTORS(Bitmap);
+};
+
+// Lightweight raw-bitmap management. The image, once initialized, is immuable.
+// It is mainly used for comparison.
+class Image {
+ public:
+ // Creates the image from the given filename on disk.
+ Image(const std::wstring& filename) : ignore_alpha_(true) {
+ std::string compressed;
+ file_util::ReadFileToString(filename, &compressed);
+ EXPECT_TRUE(compressed.size());
+
+ int w;
+ int h;
+ EXPECT_TRUE(PNGDecoder::Decode(
+ reinterpret_cast<const unsigned char*>(compressed.c_str()),
+ compressed.size(), PNGDecoder::FORMAT_BGRA, &data_, &w, &h));
+ size_.SetSize(w, h);
+ row_length_ = w * sizeof(uint32);
+ }
+
+ // Loads the image from a canvas.
+ Image(const gfx::PlatformCanvasWin& canvas) : ignore_alpha_(true) {
+ // Use a different way to access the bitmap. The normal way would be to
+ // query the SkBitmap.
+ HDC context = canvas.getTopPlatformDevice().getBitmapDC();
+ HGDIOBJ bitmap = GetCurrentObject(context, OBJ_BITMAP);
+ EXPECT_TRUE(bitmap != NULL);
+ // Initialize the clip region to the entire bitmap.
+ BITMAP bitmap_data;
+ EXPECT_EQ(GetObject(bitmap, sizeof(BITMAP), &bitmap_data),
+ sizeof(BITMAP));
+ size_.SetSize(bitmap_data.bmWidth, bitmap_data.bmHeight);
+ row_length_ = bitmap_data.bmWidthBytes;
+ size_t size = row_length_ * size_.height();
+ data_.resize(size);
+ memcpy(&*data_.begin(), bitmap_data.bmBits, size);
+ }
+
+ // Loads the image from a canvas.
+ Image(const SkBitmap& bitmap) : ignore_alpha_(true) {
+ SkAutoLockPixels lock(bitmap);
+ size_.SetSize(bitmap.width(), bitmap.height());
+ row_length_ = static_cast<int>(bitmap.rowBytes());
+ size_t size = row_length_ * size_.height();
+ data_.resize(size);
+ memcpy(&*data_.begin(), bitmap.getAddr(0, 0), size);
+ }
+
+ const gfx::Size& size() const {
+ return size_;
+ }
+
+ int row_length() const {
+ return row_length_;
+ }
+
+ // Save the image to a png file. Used to create the initial test files.
+ void SaveToFile(const std::wstring& filename) {
+ std::vector<unsigned char> compressed;
+ ASSERT_TRUE(PNGEncoder::Encode(&*data_.begin(),
+ PNGEncoder::FORMAT_BGRA,
+ size_.width(),
+ size_.height(),
+ row_length_,
+ true,
+ &compressed));
+ ASSERT_TRUE(compressed.size());
+ FILE* f = file_util::OpenFile(filename, "wb");
+ ASSERT_TRUE(f);
+ ASSERT_EQ(fwrite(&*compressed.begin(), 1, compressed.size(), f),
+ compressed.size());
+ file_util::CloseFile(f);
+ }
+
+ // Returns the percentage of the image that is different from the other,
+ // between 0 and 100.
+ double PercentageDifferent(const Image& rhs) const {
+ if (size_ != rhs.size_ || row_length_ != rhs.row_length_ ||
+ size_.width() == 0 || size_.height() == 0)
+ return 100.; // When of different size or empty, they are 100% different.
+
+ // Compute pixels different in the overlap
+ int pixels_different = 0;
+ for (int y = 0; y < size_.height(); ++y) {
+ for (int x = 0; x < size_.width(); ++x) {
+ uint32_t lhs_pixel = pixel_at(x, y);
+ uint32_t rhs_pixel = rhs.pixel_at(x, y);
+ if (lhs_pixel != rhs_pixel)
+ ++pixels_different;
+ }
+ }
+
+ // Like the WebKit ImageDiff tool, we define percentage different in terms
+ // of the size of the 'actual' bitmap.
+ double total_pixels = static_cast<double>(size_.width()) *
+ static_cast<double>(size_.height());
+ return static_cast<double>(pixels_different) / total_pixels * 100.;
+ }
+
+ // Returns the 0x0RGB or 0xARGB value of the pixel at the given location,
+ // depending on ignore_alpha_.
+ uint32 pixel_at(int x, int y) const {
+ EXPECT_TRUE(x >= 0 && x < size_.width());
+ EXPECT_TRUE(y >= 0 && y < size_.height());
+ const uint32* data = reinterpret_cast<const uint32*>(&*data_.begin());
+ const uint32* data_row = data + y * row_length_ / sizeof(uint32);
+ if (ignore_alpha_)
+ return data_row[x] & 0xFFFFFF; // Strip out A.
+ else
+ return data_row[x];
+ }
+
+ private:
+ // Pixel dimensions of the image.
+ gfx::Size size_;
+
+ // Length of a line in bytes.
+ int row_length_;
+
+ // Actual bitmap data in arrays of RGBAs (so when loaded as uint32, it's
+ // 0xABGR).
+ std::vector<unsigned char> data_;
+
+ // Flag to signal if the comparison functions should ignore the alpha channel.
+ const bool ignore_alpha_;
+
+ DISALLOW_EVIL_CONSTRUCTORS(Image);
+};
+
+// Base for tests. Capability to process an image.
+class ImageTest : public DataUnitTest {
+ public:
+ typedef DataUnitTest parent;
+
+ // In what state is the test running.
+ enum ProcessAction {
+ GENERATE,
+ COMPARE,
+ NOOP,
+ };
+
+ ImageTest(const std::wstring& base_path, ProcessAction default_action)
+ : parent(base_path),
+ action_(default_action) {
+ }
+
+ protected:
+ virtual void SetUp() {
+ parent::SetUp();
+
+ if (action_ == GENERATE) {
+ // Make sure the directory exist.
+ file_util::CreateDirectory(test_dir());
+ }
+ }
+
+ // Compares or saves the bitmap currently loaded in the context, depending on
+ // kGenerating value. Returns 0 on success or any positive value between ]0,
+ // 100] on failure. The return value is the percentage of difference between
+ // the image in the file and the image in the canvas.
+ double ProcessCanvas(const gfx::PlatformCanvasWin& canvas,
+ std::wstring filename) const {
+ filename += L".png";
+ switch (action_) {
+ case GENERATE:
+ SaveImage(canvas, filename);
+ return 0.;
+ case COMPARE:
+ return CompareImage(canvas, filename);
+ case NOOP:
+ return 0;
+ default:
+ // Invalid state, returns that the image is 100 different.
+ return 100.;
+ }
+ }
+
+ // Compares the bitmap currently loaded in the context with the file. Returns
+ // the percentage of pixel difference between both images, between 0 and 100.
+ double CompareImage(const gfx::PlatformCanvasWin& canvas,
+ const std::wstring& filename) const {
+ Image image1(canvas);
+ Image image2(test_file(filename));
+ double diff = image1.PercentageDifferent(image2);
+ return diff;
+ }
+
+ // Saves the bitmap currently loaded in the context into the file.
+ void SaveImage(const gfx::PlatformCanvasWin& canvas,
+ const std::wstring& filename) const {
+ Image(canvas).SaveToFile(test_file(filename));
+ }
+
+ ProcessAction action_;
+
+ DISALLOW_EVIL_CONSTRUCTORS(ImageTest);
+};
+
+// Premultiply the Alpha channel on the R, B and G channels.
+void Premultiply(SkBitmap bitmap) {
+ SkAutoLockPixels lock(bitmap);
+ for (int x = 0; x < bitmap.width(); ++x) {
+ for (int y = 0; y < bitmap.height(); ++y) {
+ uint32_t* pixel_addr = bitmap.getAddr32(x, y);
+ uint32_t color = *pixel_addr;
+ BYTE alpha = SkColorGetA(color);
+ if (!alpha) {
+ *pixel_addr = 0;
+ } else {
+ BYTE alpha_offset = alpha / 2;
+ *pixel_addr = SkColorSetARGB(
+ SkColorGetA(color),
+ (SkColorGetR(color) * 255 + alpha_offset) / alpha,
+ (SkColorGetG(color) * 255 + alpha_offset) / alpha,
+ (SkColorGetB(color) * 255 + alpha_offset) / alpha);
+ }
+ }
+ }
+}
+
+void LoadPngFileToSkBitmap(const std::wstring& file, SkBitmap* bitmap) {
+ std::string compressed;
+ file_util::ReadFileToString(file, &compressed);
+ EXPECT_TRUE(compressed.size());
+ // Extra-lame. If you care, fix it.
+ std::vector<unsigned char> data;
+ data.assign(reinterpret_cast<const unsigned char*>(compressed.c_str()),
+ reinterpret_cast<const unsigned char*>(compressed.c_str() +
+ compressed.size()));
+ EXPECT_TRUE(PNGDecoder::Decode(&data, bitmap));
+ EXPECT_FALSE(bitmap->isOpaque());
+ Premultiply(*bitmap);
+}
+
+} // namespace
+
+// Streams an image.
+inline std::ostream& operator<<(std::ostream& out, const Image& image) {
+ return out << "Image(" << image.size().width() << ", "
+ << image.size().height() << ", " << image.row_length() << ")";
+}
+
+// Runs simultaneously the same drawing commands on VectorCanvas and
+// PlatformCanvas and compare the results.
+class VectorCanvasTest : public ImageTest {
+ public:
+ typedef ImageTest parent;
+
+ VectorCanvasTest() : parent(L"base", CurrentMode()), compare_canvas_(true) {
+ }
+
+ protected:
+ virtual void SetUp() {
+ parent::SetUp();
+ Init(100);
+ number_ = 0;
+ }
+
+ virtual void TearDown() {
+ delete pcanvas_;
+ pcanvas_ = NULL;
+
+ delete vcanvas_;
+ vcanvas_ = NULL;
+
+ delete bitmap_;
+ bitmap_ = NULL;
+
+ delete context_;
+ context_ = NULL;
+
+ parent::TearDown();
+ }
+
+ void Init(int size) {
+ size_ = size;
+ context_ = new Context();
+ bitmap_ = new Bitmap(*context_, size_, size_);
+ vcanvas_ = new gfx::VectorCanvas(context_->context(), size_, size_);
+ pcanvas_ = new gfx::PlatformCanvasWin(size_, size_, false);
+
+ // Clear white.
+ vcanvas_->drawARGB(255, 255, 255, 255, SkPorterDuff::kSrc_Mode);
+ pcanvas_->drawARGB(255, 255, 255, 255, SkPorterDuff::kSrc_Mode);
+ }
+
+ // Compares both canvas and returns the pixel difference in percentage between
+ // both images. 0 on success and ]0, 100] on failure.
+ double ProcessImage(const std::wstring& filename) {
+ std::wstring number(StringPrintf(L"%02d_", number_++));
+ double diff1 = parent::ProcessCanvas(*vcanvas_, number + L"vc_" + filename);
+ double diff2 = parent::ProcessCanvas(*pcanvas_, number + L"pc_" + filename);
+ if (!compare_canvas_)
+ return std::max(diff1, diff2);
+
+ Image image1(*vcanvas_);
+ Image image2(*pcanvas_);
+ double diff = image1.PercentageDifferent(image2);
+ return std::max(std::max(diff1, diff2), diff);
+ }
+
+ // Returns COMPARE, which is the default. If kGenerateSwitch command
+ // line argument is used to start this process, GENERATE is returned instead.
+ static ProcessAction CurrentMode() {
+ return CommandLine().HasSwitch(kGenerateSwitch) ? GENERATE : COMPARE;
+ }
+
+ // Length in x and y of the square canvas.
+ int size_;
+
+ // Current image number in the current test. Used to number of test files.
+ int number_;
+
+ // A temporary HDC to draw into.
+ Context* context_;
+
+ // Bitmap created inside context_.
+ Bitmap* bitmap_;
+
+ // Vector based canvas.
+ gfx::VectorCanvas* vcanvas_;
+
+ // Pixel based canvas.
+ gfx::PlatformCanvasWin* pcanvas_;
+
+ // When true (default), vcanvas_ and pcanvas_ contents are compared and
+ // verified to be identical.
+ bool compare_canvas_;
+};
+
+
+////////////////////////////////////////////////////////////////////////////////
+// Actual tests
+
+TEST_F(VectorCanvasTest, Uninitialized) {
+ // Do a little mubadumba do get uninitialized stuff.
+ VectorCanvasTest::TearDown();
+
+ // The goal is not to verify that have the same uninitialized data.
+ compare_canvas_ = false;
+
+ context_ = new Context();
+ bitmap_ = new Bitmap(*context_, size_, size_);
+ vcanvas_ = new gfx::VectorCanvas(context_->context(), size_, size_);
+ pcanvas_ = new gfx::PlatformCanvasWin(size_, size_, false);
+
+ // VectorCanvas default initialization is black.
+ // PlatformCanvas default initialization is almost white 0x01FFFEFD (invalid
+ // Skia color) in both Debug and Release. See magicTransparencyColor in
+ // platform_device.cc
+ EXPECT_EQ(0., ProcessImage(L"empty"));
+}
+
+TEST_F(VectorCanvasTest, BasicDrawing) {
+ EXPECT_EQ(Image(*vcanvas_).PercentageDifferent(Image(*pcanvas_)), 0.)
+ << L"clean";
+ EXPECT_EQ(0., ProcessImage(L"clean"));
+
+ // Clear white.
+ {
+ vcanvas_->drawARGB(255, 255, 255, 255, SkPorterDuff::kSrc_Mode);
+ pcanvas_->drawARGB(255, 255, 255, 255, SkPorterDuff::kSrc_Mode);
+ }
+ EXPECT_EQ(0., ProcessImage(L"drawARGB"));
+
+ // Diagonal line top-left to bottom-right.
+ {
+ SkPaint paint;
+ // Default color is black.
+ vcanvas_->drawLine(10, 10, 90, 90, paint);
+ pcanvas_->drawLine(10, 10, 90, 90, paint);
+ }
+ EXPECT_EQ(0., ProcessImage(L"drawLine_black"));
+
+ // Rect.
+ {
+ SkPaint paint;
+ paint.setColor(SK_ColorGREEN);
+ vcanvas_->drawRectCoords(25, 25, 75, 75, paint);
+ pcanvas_->drawRectCoords(25, 25, 75, 75, paint);
+ }
+ EXPECT_EQ(0., ProcessImage(L"drawRect_green"));
+
+ // A single-point rect doesn't leave any mark.
+ {
+ SkPaint paint;
+ paint.setColor(SK_ColorBLUE);
+ vcanvas_->drawRectCoords(5, 5, 5, 5, paint);
+ pcanvas_->drawRectCoords(5, 5, 5, 5, paint);
+ }
+ EXPECT_EQ(0., ProcessImage(L"drawRect_noop"));
+
+ // Rect.
+ {
+ SkPaint paint;
+ paint.setColor(SK_ColorBLUE);
+ vcanvas_->drawRectCoords(75, 50, 80, 55, paint);
+ pcanvas_->drawRectCoords(75, 50, 80, 55, paint);
+ }
+ EXPECT_EQ(0., ProcessImage(L"drawRect_noop"));
+
+ // Empty again
+ {
+ vcanvas_->drawPaint(SkPaint());
+ pcanvas_->drawPaint(SkPaint());
+ }
+ EXPECT_EQ(0., ProcessImage(L"drawPaint_black"));
+
+ // Horizontal line left to right.
+ {
+ SkPaint paint;
+ paint.setColor(SK_ColorRED);
+ vcanvas_->drawLine(10, 20, 90, 20, paint);
+ pcanvas_->drawLine(10, 20, 90, 20, paint);
+ }
+ EXPECT_EQ(0., ProcessImage(L"drawLine_left_to_right"));
+
+ // Vertical line downward.
+ {
+ SkPaint paint;
+ paint.setColor(SK_ColorRED);
+ vcanvas_->drawLine(30, 10, 30, 90, paint);
+ pcanvas_->drawLine(30, 10, 30, 90, paint);
+ }
+ EXPECT_EQ(0., ProcessImage(L"drawLine_red"));
+}
+
+TEST_F(VectorCanvasTest, Circles) {
+ // There is NO WAY to make them agree. At least verify that the output doesn't
+ // change across versions. This test is disabled. See bug 1060231.
+ compare_canvas_ = false;
+
+ // Stroked Circle.
+ {
+ SkPaint paint;
+ SkPath path;
+ path.addCircle(50, 75, 10);
+ paint.setStyle(SkPaint::kStroke_Style);
+ paint.setColor(SK_ColorMAGENTA);
+ vcanvas_->drawPath(path, paint);
+ pcanvas_->drawPath(path, paint);
+ }
+ EXPECT_EQ(0., ProcessImage(L"circle_stroke"));
+
+ // Filled Circle.
+ {
+ SkPaint paint;
+ SkPath path;
+ path.addCircle(50, 25, 10);
+ paint.setStyle(SkPaint::kFill_Style);
+ vcanvas_->drawPath(path, paint);
+ pcanvas_->drawPath(path, paint);
+ }
+ EXPECT_EQ(0., ProcessImage(L"circle_fill"));
+
+ // Stroked Circle over.
+ {
+ SkPaint paint;
+ SkPath path;
+ path.addCircle(50, 25, 10);
+ paint.setStyle(SkPaint::kStroke_Style);
+ paint.setColor(SK_ColorBLUE);
+ vcanvas_->drawPath(path, paint);
+ pcanvas_->drawPath(path, paint);
+ }
+ EXPECT_EQ(0., ProcessImage(L"circle_over_strike"));
+
+ // Stroke and Fill Circle.
+ {
+ SkPaint paint;
+ SkPath path;
+ path.addCircle(12, 50, 10);
+ paint.setStyle(SkPaint::kStrokeAndFill_Style);
+ paint.setColor(SK_ColorRED);
+ vcanvas_->drawPath(path, paint);
+ pcanvas_->drawPath(path, paint);
+ }
+ EXPECT_EQ(0., ProcessImage(L"circle_stroke_and_fill"));
+
+ // Line + Quad + Cubic.
+ {
+ SkPaint paint;
+ SkPath path;
+ paint.setStyle(SkPaint::kStroke_Style);
+ paint.setColor(SK_ColorGREEN);
+ path.moveTo(1, 1);
+ path.lineTo(60, 40);
+ path.lineTo(80, 80);
+ path.quadTo(20, 50, 10, 90);
+ path.quadTo(50, 20, 90, 10);
+ path.cubicTo(20, 40, 50, 50, 10, 10);
+ path.cubicTo(30, 20, 50, 50, 90, 10);
+ path.addRect(90, 90, 95, 96);
+ vcanvas_->drawPath(path, paint);
+ pcanvas_->drawPath(path, paint);
+ }
+ EXPECT_EQ(0., ProcessImage(L"mixed_stroke"));
+}
+
+TEST_F(VectorCanvasTest, LineOrientation) {
+ // There is NO WAY to make them agree. At least verify that the output doesn't
+ // change across versions. This test is disabled. See bug 1060231.
+ compare_canvas_ = false;
+
+ // Horizontal lines.
+ {
+ SkPaint paint;
+ paint.setColor(SK_ColorRED);
+ // Left to right.
+ vcanvas_->drawLine(10, 20, 90, 20, paint);
+ pcanvas_->drawLine(10, 20, 90, 20, paint);
+ // Right to left.
+ vcanvas_->drawLine(90, 30, 10, 30, paint);
+ pcanvas_->drawLine(90, 30, 10, 30, paint);
+ }
+ EXPECT_EQ(0., ProcessImage(L"horizontal"));
+
+ // Vertical lines.
+ {
+ SkPaint paint;
+ paint.setColor(SK_ColorRED);
+ // Top down.
+ vcanvas_->drawLine(20, 10, 20, 90, paint);
+ pcanvas_->drawLine(20, 10, 20, 90, paint);
+ // Bottom up.
+ vcanvas_->drawLine(30, 90, 30, 10, paint);
+ pcanvas_->drawLine(30, 90, 30, 10, paint);
+ }
+ EXPECT_EQ(0., ProcessImage(L"vertical"));
+
+ // Try again with a 180 degres rotation.
+ vcanvas_->rotate(180);
+ pcanvas_->rotate(180);
+
+ // Horizontal lines (rotated).
+ {
+ SkPaint paint;
+ paint.setColor(SK_ColorRED);
+ vcanvas_->drawLine(-10, -25, -90, -25, paint);
+ pcanvas_->drawLine(-10, -25, -90, -25, paint);
+ vcanvas_->drawLine(-90, -35, -10, -35, paint);
+ pcanvas_->drawLine(-90, -35, -10, -35, paint);
+ }
+ EXPECT_EQ(0., ProcessImage(L"horizontal_180"));
+
+ // Vertical lines (rotated).
+ {
+ SkPaint paint;
+ paint.setColor(SK_ColorRED);
+ vcanvas_->drawLine(-25, -10, -25, -90, paint);
+ pcanvas_->drawLine(-25, -10, -25, -90, paint);
+ vcanvas_->drawLine(-35, -90, -35, -10, paint);
+ pcanvas_->drawLine(-35, -90, -35, -10, paint);
+ }
+ EXPECT_EQ(0., ProcessImage(L"vertical_180"));
+}
+
+TEST_F(VectorCanvasTest, PathOrientation) {
+ // There is NO WAY to make them agree. At least verify that the output doesn't
+ // change across versions. This test is disabled. See bug 1060231.
+ compare_canvas_ = false;
+
+ // Horizontal lines.
+ {
+ SkPaint paint;
+ paint.setStyle(SkPaint::kStroke_Style);
+ paint.setColor(SK_ColorRED);
+ SkPath path;
+ SkPoint start;
+ start.set(10, 20);
+ SkPoint end;
+ end.set(90, 20);
+ path.moveTo(start);
+ path.lineTo(end);
+ vcanvas_->drawPath(path, paint);
+ pcanvas_->drawPath(path, paint);
+ }
+ EXPECT_EQ(0., ProcessImage(L"drawPath_ltr"));
+
+ // Horizontal lines.
+ {
+ SkPaint paint;
+ paint.setStyle(SkPaint::kStroke_Style);
+ paint.setColor(SK_ColorRED);
+ SkPath path;
+ SkPoint start;
+ start.set(90, 30);
+ SkPoint end;
+ end.set(10, 30);
+ path.moveTo(start);
+ path.lineTo(end);
+ vcanvas_->drawPath(path, paint);
+ pcanvas_->drawPath(path, paint);
+ }
+ EXPECT_EQ(0., ProcessImage(L"drawPath_rtl"));
+}
+
+TEST_F(VectorCanvasTest, DiagonalLines) {
+ SkPaint paint;
+ paint.setColor(SK_ColorRED);
+
+ vcanvas_->drawLine(10, 10, 90, 90, paint);
+ pcanvas_->drawLine(10, 10, 90, 90, paint);
+ EXPECT_EQ(0., ProcessImage(L"nw-se"));
+
+ // Starting here, there is NO WAY to make them agree. At least verify that the
+ // output doesn't change across versions. This test is disabled. See bug
+ // 1060231.
+ compare_canvas_ = false;
+
+ vcanvas_->drawLine(10, 95, 90, 15, paint);
+ pcanvas_->drawLine(10, 95, 90, 15, paint);
+ EXPECT_EQ(0., ProcessImage(L"sw-ne"));
+
+ vcanvas_->drawLine(90, 10, 10, 90, paint);
+ pcanvas_->drawLine(90, 10, 10, 90, paint);
+ EXPECT_EQ(0., ProcessImage(L"ne-sw"));
+
+ vcanvas_->drawLine(95, 90, 15, 10, paint);
+ pcanvas_->drawLine(95, 90, 15, 10, paint);
+ EXPECT_EQ(0., ProcessImage(L"se-nw"));
+}
+
+TEST_F(VectorCanvasTest, PathEffects) {
+ {
+ SkPaint paint;
+ SkScalar intervals[] = { 1, 1 };
+ SkPathEffect* effect = new SkDashPathEffect(intervals, arraysize(intervals),
+ 0);
+ paint.setPathEffect(effect)->unref();
+ paint.setColor(SK_ColorMAGENTA);
+ paint.setStyle(SkPaint::kStroke_Style);
+
+ vcanvas_->drawLine(10, 10, 90, 10, paint);
+ pcanvas_->drawLine(10, 10, 90, 10, paint);
+ }
+ EXPECT_EQ(0., ProcessImage(L"dash_line"));
+
+
+ // Starting here, there is NO WAY to make them agree. At least verify that the
+ // output doesn't change across versions. This test is disabled. See bug
+ // 1060231.
+ compare_canvas_ = false;
+
+ {
+ SkPaint paint;
+ SkScalar intervals[] = { 3, 5 };
+ SkPathEffect* effect = new SkDashPathEffect(intervals, arraysize(intervals),
+ 0);
+ paint.setPathEffect(effect)->unref();
+ paint.setColor(SK_ColorMAGENTA);
+ paint.setStyle(SkPaint::kStroke_Style);
+
+ SkPath path;
+ path.moveTo(10, 15);
+ path.lineTo(90, 15);
+ path.lineTo(90, 90);
+ vcanvas_->drawPath(path, paint);
+ pcanvas_->drawPath(path, paint);
+ }
+ EXPECT_EQ(0., ProcessImage(L"dash_path"));
+
+ {
+ SkPaint paint;
+ SkScalar intervals[] = { 2, 1 };
+ SkPathEffect* effect = new SkDashPathEffect(intervals, arraysize(intervals),
+ 0);
+ paint.setPathEffect(effect)->unref();
+ paint.setColor(SK_ColorMAGENTA);
+ paint.setStyle(SkPaint::kStroke_Style);
+
+ vcanvas_->drawRectCoords(20, 20, 30, 30, paint);
+ pcanvas_->drawRectCoords(20, 20, 30, 30, paint);
+ }
+ EXPECT_EQ(0., ProcessImage(L"dash_rect"));
+
+ // This thing looks like it has been drawn by a 3 years old kid. I haven't
+ // filed a bug on this since I guess nobody is expecting this to look nice.
+ {
+ SkPaint paint;
+ SkScalar intervals[] = { 1, 1 };
+ SkPathEffect* effect = new SkDashPathEffect(intervals, arraysize(intervals),
+ 0);
+ paint.setPathEffect(effect)->unref();
+ paint.setColor(SK_ColorMAGENTA);
+ paint.setStyle(SkPaint::kStroke_Style);
+
+ SkPath path;
+ path.addCircle(50, 75, 10);
+ vcanvas_->drawPath(path, paint);
+ pcanvas_->drawPath(path, paint);
+ EXPECT_EQ(0., ProcessImage(L"circle"));
+ }
+}
+
+TEST_F(VectorCanvasTest, Bitmaps) {
+ // ICM is enabled on VectorCanvas only on Windows 2000 so bitmap-based tests
+ // can't compare the pixels between PlatformCanvas and VectorCanvas. We don't
+ // really care about Windows 2000 pixel colors.
+ if (win_util::GetWinVersion() <= win_util::WINVERSION_2000)
+ return;
+ {
+ SkBitmap bitmap;
+ LoadPngFileToSkBitmap(test_file(L"bitmap_opaque.png"), &bitmap);
+ vcanvas_->drawBitmap(bitmap, 13, 3, NULL);
+ pcanvas_->drawBitmap(bitmap, 13, 3, NULL);
+ EXPECT_EQ(0., ProcessImage(L"opaque"));
+ }
+
+ {
+ SkBitmap bitmap;
+ LoadPngFileToSkBitmap(test_file(L"bitmap_alpha.png"), &bitmap);
+ vcanvas_->drawBitmap(bitmap, 5, 15, NULL);
+ pcanvas_->drawBitmap(bitmap, 5, 15, NULL);
+ EXPECT_EQ(0., ProcessImage(L"alpha"));
+ }
+}
+
+TEST_F(VectorCanvasTest, ClippingRect) {
+ // ICM is enabled on VectorCanvas only on Windows 2000 so bitmap-based tests
+ // can't compare the pixels between PlatformCanvas and VectorCanvas. We don't
+ // really care about Windows 2000 pixel colors.
+ if (win_util::GetWinVersion() <= win_util::WINVERSION_2000)
+ return;
+ SkBitmap bitmap;
+ LoadPngFileToSkBitmap(test_file(L"..\\bitmaps\\bitmap_opaque.png"), &bitmap);
+ SkRect rect;
+ rect.fLeft = 2;
+ rect.fTop = 2;
+ rect.fRight = 30.5f;
+ rect.fBottom = 30.5f;
+ vcanvas_->clipRect(rect);
+ pcanvas_->clipRect(rect);
+
+ vcanvas_->drawBitmap(bitmap, 13, 3, NULL);
+ pcanvas_->drawBitmap(bitmap, 13, 3, NULL);
+ EXPECT_EQ(0., ProcessImage(L"rect"));
+}
+
+TEST_F(VectorCanvasTest, ClippingPath) {
+ // ICM is enabled on VectorCanvas only on Windows 2000 so bitmap-based tests
+ // can't compare the pixels between PlatformCanvas and VectorCanvas. We don't
+ // really care about Windows 2000 pixel colors.
+ if (win_util::GetWinVersion() <= win_util::WINVERSION_2000)
+ return;
+ SkBitmap bitmap;
+ LoadPngFileToSkBitmap(test_file(L"..\\bitmaps\\bitmap_opaque.png"), &bitmap);
+ SkPath path;
+ path.addCircle(20, 20, 10);
+ vcanvas_->clipPath(path);
+ pcanvas_->clipPath(path);
+
+ vcanvas_->drawBitmap(bitmap, 14, 3, NULL);
+ pcanvas_->drawBitmap(bitmap, 14, 3, NULL);
+ EXPECT_EQ(0., ProcessImage(L"path"));
+}
+
+TEST_F(VectorCanvasTest, ClippingCombined) {
+ // ICM is enabled on VectorCanvas only on Windows 2000 so bitmap-based tests
+ // can't compare the pixels between PlatformCanvas and VectorCanvas. We don't
+ // really care about Windows 2000 pixel colors.
+ if (win_util::GetWinVersion() <= win_util::WINVERSION_2000)
+ return;
+ SkBitmap bitmap;
+ LoadPngFileToSkBitmap(test_file(L"..\\bitmaps\\bitmap_opaque.png"), &bitmap);
+
+ SkRect rect;
+ rect.fLeft = 2;
+ rect.fTop = 2;
+ rect.fRight = 30.5f;
+ rect.fBottom = 30.5f;
+ vcanvas_->clipRect(rect);
+ pcanvas_->clipRect(rect);
+ SkPath path;
+ path.addCircle(20, 20, 10);
+ vcanvas_->clipPath(path, SkRegion::kUnion_Op);
+ pcanvas_->clipPath(path, SkRegion::kUnion_Op);
+
+ vcanvas_->drawBitmap(bitmap, 15, 3, NULL);
+ pcanvas_->drawBitmap(bitmap, 15, 3, NULL);
+ EXPECT_EQ(0., ProcessImage(L"combined"));
+}
+
+TEST_F(VectorCanvasTest, ClippingIntersect) {
+ // ICM is enabled on VectorCanvas only on Windows 2000 so bitmap-based tests
+ // can't compare the pixels between PlatformCanvas and VectorCanvas. We don't
+ // really care about Windows 2000 pixel colors.
+ if (win_util::GetWinVersion() <= win_util::WINVERSION_2000)
+ return;
+ SkBitmap bitmap;
+ LoadPngFileToSkBitmap(test_file(L"..\\bitmaps\\bitmap_opaque.png"), &bitmap);
+
+ SkRect rect;
+ rect.fLeft = 2;
+ rect.fTop = 2;
+ rect.fRight = 30.5f;
+ rect.fBottom = 30.5f;
+ vcanvas_->clipRect(rect);
+ pcanvas_->clipRect(rect);
+ SkPath path;
+ path.addCircle(23, 23, 15);
+ vcanvas_->clipPath(path);
+ pcanvas_->clipPath(path);
+
+ vcanvas_->drawBitmap(bitmap, 15, 3, NULL);
+ pcanvas_->drawBitmap(bitmap, 15, 3, NULL);
+ EXPECT_EQ(0., ProcessImage(L"intersect"));
+}
+
+TEST_F(VectorCanvasTest, ClippingClean) {
+ // ICM is enabled on VectorCanvas only on Windows 2000 so bitmap-based tests
+ // can't compare the pixels between PlatformCanvas and VectorCanvas. We don't
+ // really care about Windows 2000 pixel colors.
+ if (win_util::GetWinVersion() <= win_util::WINVERSION_2000)
+ return;
+ SkBitmap bitmap;
+ LoadPngFileToSkBitmap(test_file(L"..\\bitmaps\\bitmap_opaque.png"), &bitmap);
+ {
+ SkRegion old_region(pcanvas_->getTotalClip());
+ SkRect rect;
+ rect.fLeft = 2;
+ rect.fTop = 2;
+ rect.fRight = 30.5f;
+ rect.fBottom = 30.5f;
+ vcanvas_->clipRect(rect);
+ pcanvas_->clipRect(rect);
+
+ vcanvas_->drawBitmap(bitmap, 15, 3, NULL);
+ pcanvas_->drawBitmap(bitmap, 15, 3, NULL);
+ EXPECT_EQ(0., ProcessImage(L"clipped"));
+ vcanvas_->clipRegion(old_region, SkRegion::kReplace_Op);
+ pcanvas_->clipRegion(old_region, SkRegion::kReplace_Op);
+ }
+ {
+ // Verify that the clipping region has been fixed back.
+ vcanvas_->drawBitmap(bitmap, 55, 3, NULL);
+ pcanvas_->drawBitmap(bitmap, 55, 3, NULL);
+ EXPECT_EQ(0., ProcessImage(L"unclipped"));
+ }
+}
+
+TEST_F(VectorCanvasTest, Matrix) {
+ // ICM is enabled on VectorCanvas only on Windows 2000 so bitmap-based tests
+ // can't compare the pixels between PlatformCanvas and VectorCanvas. We don't
+ // really care about Windows 2000 pixel colors.
+ if (win_util::GetWinVersion() <= win_util::WINVERSION_2000)
+ return;
+ SkBitmap bitmap;
+ LoadPngFileToSkBitmap(test_file(L"..\\bitmaps\\bitmap_opaque.png"), &bitmap);
+ {
+ vcanvas_->translate(15, 3);
+ pcanvas_->translate(15, 3);
+ vcanvas_->drawBitmap(bitmap, 0, 0, NULL);
+ pcanvas_->drawBitmap(bitmap, 0, 0, NULL);
+ EXPECT_EQ(0., ProcessImage(L"translate1"));
+ }
+ {
+ vcanvas_->translate(-30, -23);
+ pcanvas_->translate(-30, -23);
+ vcanvas_->drawBitmap(bitmap, 0, 0, NULL);
+ pcanvas_->drawBitmap(bitmap, 0, 0, NULL);
+ EXPECT_EQ(0., ProcessImage(L"translate2"));
+ }
+ vcanvas_->resetMatrix();
+ pcanvas_->resetMatrix();
+
+ // For scaling and rotation, they use a different algorithm (nearest
+ // neighborhood vs smoothing). At least verify that the output doesn't change
+ // across versions.
+ compare_canvas_ = false;
+
+ {
+ vcanvas_->scale(SkDoubleToScalar(1.9), SkDoubleToScalar(1.5));
+ pcanvas_->scale(SkDoubleToScalar(1.9), SkDoubleToScalar(1.5));
+ vcanvas_->drawBitmap(bitmap, 1, 1, NULL);
+ pcanvas_->drawBitmap(bitmap, 1, 1, NULL);
+ EXPECT_EQ(0., ProcessImage(L"scale"));
+ }
+ vcanvas_->resetMatrix();
+ pcanvas_->resetMatrix();
+
+ {
+ vcanvas_->rotate(67);
+ pcanvas_->rotate(67);
+ vcanvas_->drawBitmap(bitmap, 20, -50, NULL);
+ pcanvas_->drawBitmap(bitmap, 20, -50, NULL);
+ EXPECT_EQ(0., ProcessImage(L"rotate"));
+ }
+}
+
diff --git a/skia/ext/vector_device.cc b/skia/ext/vector_device.cc
new file mode 100644
index 0000000..a587e0c
--- /dev/null
+++ b/skia/ext/vector_device.cc
@@ -0,0 +1,612 @@
+// 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 "base/gfx/skia_utils.h"
+#include "base/logging.h"
+#include "base/scoped_handle.h"
+
+#include "SkUtils.h"
+
+namespace gfx {
+
+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() {
+ DCHECK(previous_brush_ == NULL);
+ DCHECK(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) {
+ NOTREACHED();
+ 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) {
+ NOTREACHED();
+ 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:
+ NOTREACHED();
+ 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))) {
+ NOTREACHED();
+ }
+ 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);
+ DCHECK(res != 0);
+ break;
+ }
+ case SkPaint::kStroke_Style: {
+ BOOL res = StrokePath(dc);
+ DCHECK(res != 0);
+ break;
+ }
+ case SkPaint::kStrokeAndFill_Style: {
+ BOOL res = StrokeAndFillPath(dc);
+ DCHECK(res != 0);
+ break;
+ }
+ default:
+ NOTREACHED();
+ 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.
+ NOTREACHED();
+}
+
+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.
+ NOTREACHED();
+}
+
+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.
+ NOTREACHED();
+}
+
+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.
+ NOTREACHED();
+}
+
+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
+ // DCHECK(!paint.getShader());
+
+ // http://b/1106647 Implement loopers and mask filter. Looper currently in
+ // use:
+ // SkBlurDrawLooper is used for shadows.
+ // DCHECK(!paint.getLooper());
+ // DCHECK(!paint.getMaskFilter());
+
+ // http://b/1165900 Implement xfermode.
+ // DCHECK(!paint.getXfermode());
+
+ // The path effect should be processed before arriving here.
+ DCHECK(!paint.getPathEffect());
+
+ // These aren't used in the code. Verify this assumption.
+ DCHECK(!paint.getColorFilter());
+ DCHECK(!paint.getRasterizer());
+ // Reuse code to load Win32 Fonts.
+ DCHECK(!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) {
+ NOTREACHED();
+}
+
+void VectorDevice::LoadClipRegion() {
+ SkMatrix t;
+ t.reset();
+ LoadClippingRegionToDC(hdc_, clip_region_, t);
+}
+
+bool VectorDevice::CreateBrush(bool use_brush, COLORREF color) {
+ DCHECK(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)) {
+ NOTREACHED();
+ return false;
+ }
+
+ // Select the NULL brush.
+ previous_brush_ = SelectObject(GetStockObject(NULL_BRUSH));
+ return previous_brush_ != NULL;
+ }
+
+ // Set the opacity.
+ if (0 == SetBkMode(hdc_, OPAQUE)) {
+ NOTREACHED();
+ 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) {
+ DCHECK(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);
+ DCHECK(pen != NULL);
+ previous_pen_ = SelectObject(pen);
+ if (previous_pen_ == NULL)
+ return false;
+
+ if (!SetMiterLimit(hdc_, stroke_miter, NULL)) {
+ NOTREACHED();
+ return false;
+ }
+ return true;
+}
+
+void VectorDevice::Cleanup() {
+ if (previous_brush_) {
+ HGDIOBJ result = SelectObject(previous_brush_);
+ previous_brush_ = NULL;
+ if (result) {
+ BOOL res = DeleteObject(result);
+ DCHECK(res != 0);
+ }
+ }
+ if (previous_pen_) {
+ HGDIOBJ result = SelectObject(previous_pen_);
+ previous_pen_ = NULL;
+ if (result) {
+ BOOL res = DeleteObject(result);
+ DCHECK(res != 0);
+ }
+ }
+ // Remove any loaded path from the context.
+ AbortPath(hdc_);
+}
+
+HGDIOBJ VectorDevice::SelectObject(HGDIOBJ object) {
+ HGDIOBJ result = ::SelectObject(hdc_, object);
+ DCHECK(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.
+ NOTREACHED();
+ 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.
+ NOTREACHED();
+ 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:
+ NOTREACHED();
+ 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:
+ NOTREACHED();
+ 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) {
+ uint8 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);
+ DCHECK_EQ(bitmap.getConfig(), SkBitmap::kARGB_8888_Config);
+ const uint32_t* pixels = static_cast<const uint32_t*>(bitmap.getPixels());
+ if (pixels == NULL) {
+ NOTREACHED();
+ 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.
+ ScopedHDC bitmap_dc(::CreateCompatibleDC(dc));
+ void* bits = NULL;
+ ScopedBitmap hbitmap(::CreateDIBSection(
+ bitmap_dc, reinterpret_cast<const BITMAPINFO*>(&hdr),
+ DIB_RGB_COLORS, &bits, NULL, 0));
+ memcpy(bits, pixels, bitmap.getSize());
+ DCHECK(hbitmap);
+ HGDIOBJ old_bitmap = ::SelectObject(bitmap_dc, hbitmap);
+ DeleteObject(old_bitmap);
+
+ // 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);
+ DCHECK(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);
+ DCHECK(result);
+ result = SetStretchBltMode(dc, previous_mode);
+ DCHECK(result);
+ } 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);
+ DCHECK(result);
+ }
+ Cleanup();
+}
+
+} // namespace gfx
+
diff --git a/skia/ext/vector_device.h b/skia/ext/vector_device.h
new file mode 100644
index 0000000..8579689
--- /dev/null
+++ b/skia/ext/vector_device.h
@@ -0,0 +1,119 @@
+// 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.
+
+#ifndef VectorDevice_h
+#define VectorDevice_h
+
+#include "base/basictypes.h"
+#include "base/gfx/platform_device_win.h"
+#include "SkMatrix.h"
+#include "SkRegion.h"
+
+namespace gfx {
+
+// A device is basically a wrapper around SkBitmap that provides a surface for
+// SkCanvas to draw into. This specific device is not not backed by a surface
+// and is thus unreadable. This is because the backend is completely vectorial.
+// This device is a simple wrapper over a Windows device context (HDC) handle.
+class VectorDevice : public PlatformDeviceWin {
+ public:
+ // Factory function. The DC is kept as the output context.
+ static VectorDevice* create(HDC dc, int width, int height);
+
+ VectorDevice(HDC dc, const SkBitmap& bitmap);
+ virtual ~VectorDevice();
+
+ virtual HDC getBitmapDC() {
+ return hdc_;
+ }
+
+ virtual void drawPaint(const SkDraw& draw, const SkPaint& paint);
+ virtual void drawPoints(const SkDraw& draw, SkCanvas::PointMode mode,
+ size_t count, const SkPoint[], const SkPaint& paint);
+ virtual void drawRect(const SkDraw& draw, const SkRect& r,
+ const SkPaint& paint);
+ virtual void drawPath(const SkDraw& draw, const SkPath& path,
+ const SkPaint& paint);
+ virtual void drawBitmap(const SkDraw& draw, const SkBitmap& bitmap,
+ const SkMatrix& matrix, const SkPaint& paint);
+ virtual void drawSprite(const SkDraw& draw, const SkBitmap& bitmap,
+ int x, int y, const SkPaint& paint);
+ virtual void drawText(const SkDraw& draw, const void* text, size_t len,
+ SkScalar x, SkScalar y, const SkPaint& paint);
+ virtual void drawPosText(const SkDraw& draw, const void* text, size_t len,
+ const SkScalar pos[], SkScalar constY,
+ int scalarsPerPos, const SkPaint& paint);
+ virtual void drawTextOnPath(const SkDraw& draw, const void* text, size_t len,
+ const SkPath& path, const SkMatrix* matrix,
+ const SkPaint& paint);
+ virtual void drawVertices(const SkDraw& draw, SkCanvas::VertexMode,
+ int vertexCount,
+ const SkPoint verts[], const SkPoint texs[],
+ const SkColor colors[], SkXfermode* xmode,
+ const uint16_t indices[], int indexCount,
+ const SkPaint& paint);
+ virtual void drawDevice(const SkDraw& draw, SkDevice*, int x, int y,
+ const SkPaint&);
+
+
+ virtual void setMatrixClip(const SkMatrix& transform, const SkRegion& region);
+ virtual void drawToHDC(HDC dc, int x, int y, const RECT* src_rect);
+ virtual bool IsVectorial() { return true; }
+
+ void LoadClipRegion();
+
+ private:
+ // Applies the SkPaint's painting properties in the current GDI context, if
+ // possible. If GDI can't support all paint's properties, returns false. It
+ // doesn't execute the "commands" in SkPaint.
+ bool ApplyPaint(const SkPaint& paint);
+
+ // Selects a new object in the device context. It can be a pen, a brush, a
+ // clipping region, a bitmap or a font. Returns the old selected object.
+ HGDIOBJ SelectObject(HGDIOBJ object);
+
+ // Creates a brush according to SkPaint's properties.
+ bool CreateBrush(bool use_brush, const SkPaint& paint);
+
+ // Creates a pen according to SkPaint's properties.
+ bool CreatePen(bool use_pen, const SkPaint& paint);
+
+ // Restores back the previous objects (pen, brush, etc) after a paint command.
+ void Cleanup();
+
+ // Creates a brush according to SkPaint's properties.
+ bool CreateBrush(bool use_brush, COLORREF color);
+
+ // Creates a pen according to SkPaint's properties.
+ bool CreatePen(bool use_pen, COLORREF color, int stroke_width,
+ float stroke_miter, DWORD pen_style);
+
+ // Draws a bitmap in the the device, using the currently loaded matrix.
+ void InternalDrawBitmap(const SkBitmap& bitmap, int x, int y,
+ const SkPaint& paint);
+
+ // The Windows Device Context handle. It is the backend used with GDI drawing.
+ // This backend is write-only and vectorial.
+ HDC hdc_;
+
+ // Translation assigned to the DC: we need to keep track of this separately
+ // so it can be updated even if the DC isn't created yet.
+ SkMatrix transform_;
+
+ // The current clipping
+ SkRegion clip_region_;
+
+ // Previously selected brush before the current drawing.
+ HGDIOBJ previous_brush_;
+
+ // Previously selected pen before the current drawing.
+ HGDIOBJ previous_pen_;
+
+ DISALLOW_COPY_AND_ASSIGN(VectorDevice);
+};
+
+} // namespace gfx
+
+#endif // VectorDevice_h
+
diff --git a/skia/skia.vcproj b/skia/skia.vcproj
index 7d12574..5e06d73 100644
--- a/skia/skia.vcproj
+++ b/skia/skia.vcproj
@@ -1305,6 +1305,62 @@
>
</File>
</Filter>
+ <Filter
+ Name="ext"
+ >
+ <File
+ RelativePath=".\ext\bitmap_platform_device.h"
+ >
+ </File>
+ <File
+ RelativePath=".\ext\bitmap_platform_device_win.cc"
+ >
+ </File>
+ <File
+ RelativePath=".\ext\bitmap_platform_device_win.h"
+ >
+ </File>
+ <File
+ RelativePath=".\ext\platform_canvas.h"
+ >
+ </File>
+ <File
+ RelativePath=".\ext\platform_canvas_win.cc"
+ >
+ </File>
+ <File
+ RelativePath=".\ext\platform_canvas_win.h"
+ >
+ </File>
+ <File
+ RelativePath=".\ext\platform_device.h"
+ >
+ </File>
+ <File
+ RelativePath=".\ext\platform_device_win.cc"
+ >
+ </File>
+ <File
+ RelativePath=".\ext\platform_device_win.h"
+ >
+ </File>
+ <File
+ RelativePath=".\ext\vector_canvas.cc"
+ >
+ </File>
+ <File
+ RelativePath=".\ext\vector_canvas.h"
+ >
+ </File>
+ <File
+ RelativePath=".\ext\vector_device.cc"
+ >
+ </File>
+ <File
+ RelativePath=".\ext\vector_device.h"
+ >
+ </File>
+ </Filter>
<File
RelativePath=".\precompiled.cc"
>