diff options
author | brettw@google.com <brettw@google.com@0039d316-1c4b-4281-b951-d872f2087c98> | 2008-11-21 23:03:56 +0000 |
---|---|---|
committer | brettw@google.com <brettw@google.com@0039d316-1c4b-4281-b951-d872f2087c98> | 2008-11-21 23:03:56 +0000 |
commit | 156f2c0e187c0283f026fd356a4b682d4b4eaaa1 (patch) | |
tree | 209d254abb43d9ff2cb9a904dd16eed00a85bfb4 /skia | |
parent | bd2e63eed24e8c097be6ea3fb46aa354c638e88e (diff) | |
download | chromium_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/SConscript | 6 | ||||
-rw-r--r-- | skia/ext/bitmap_platform_device_win.cc | 446 | ||||
-rw-r--r-- | skia/ext/bitmap_platform_device_win.h | 111 | ||||
-rw-r--r-- | skia/ext/platform_canvas_unittest.cc | 296 | ||||
-rw-r--r-- | skia/ext/platform_canvas_win.cc | 117 | ||||
-rw-r--r-- | skia/ext/platform_canvas_win.h | 202 | ||||
-rw-r--r-- | skia/ext/platform_device.h | 27 | ||||
-rw-r--r-- | skia/ext/platform_device_win.cc | 229 | ||||
-rw-r--r-- | skia/ext/platform_device_win.h | 98 | ||||
-rw-r--r-- | skia/ext/vector_canvas.cc | 90 | ||||
-rw-r--r-- | skia/ext/vector_canvas.h | 46 | ||||
-rw-r--r-- | skia/ext/vector_canvas_unittest.cc | 1009 | ||||
-rw-r--r-- | skia/ext/vector_device.cc | 612 | ||||
-rw-r--r-- | skia/ext/vector_device.h | 119 | ||||
-rw-r--r-- | skia/skia.vcproj | 56 |
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" > |