summaryrefslogtreecommitdiffstats
path: root/base/gfx
diff options
context:
space:
mode:
authorinitial.commit <initial.commit@0039d316-1c4b-4281-b951-d872f2087c98>2008-07-26 21:49:38 +0000
committerinitial.commit <initial.commit@0039d316-1c4b-4281-b951-d872f2087c98>2008-07-26 21:49:38 +0000
commitd7cae12696b96500c05dd2d430f6238922c20c96 (patch)
treeecff27b367735535b2a66477f8cd89d3c462a6c0 /base/gfx
parentee2815e28d408216cf94e874825b6bcf76c69083 (diff)
downloadchromium_src-d7cae12696b96500c05dd2d430f6238922c20c96.zip
chromium_src-d7cae12696b96500c05dd2d430f6238922c20c96.tar.gz
chromium_src-d7cae12696b96500c05dd2d430f6238922c20c96.tar.bz2
Add base to the repository.
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@8 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'base/gfx')
-rw-r--r--base/gfx/SConscript83
-rw-r--r--base/gfx/bitmap_header.cc88
-rw-r--r--base/gfx/bitmap_header.h56
-rw-r--r--base/gfx/bitmap_platform_device.cc482
-rw-r--r--base/gfx/bitmap_platform_device.h135
-rw-r--r--base/gfx/convolver.cc359
-rw-r--r--base/gfx/convolver.h156
-rw-r--r--base/gfx/convolver_unittest.cc151
-rw-r--r--base/gfx/font_utils.cc305
-rw-r--r--base/gfx/font_utils.h105
-rw-r--r--base/gfx/image_operations.cc387
-rw-r--r--base/gfx/image_operations.h87
-rw-r--r--base/gfx/image_operations_unittest.cc172
-rw-r--r--base/gfx/img_resize_perftest.cc94
-rw-r--r--base/gfx/native_theme.cc626
-rw-r--r--base/gfx/native_theme.h310
-rw-r--r--base/gfx/native_theme_unittest.cc36
-rw-r--r--base/gfx/platform_canvas.cc105
-rw-r--r--base/gfx/platform_canvas.h209
-rw-r--r--base/gfx/platform_canvas_unittest.cc293
-rw-r--r--base/gfx/platform_device.cc253
-rw-r--r--base/gfx/platform_device.h120
-rw-r--r--base/gfx/png_codec_unittest.cc223
-rw-r--r--base/gfx/png_decoder.cc376
-rw-r--r--base/gfx/png_decoder.h88
-rw-r--r--base/gfx/png_encoder.cc217
-rw-r--r--base/gfx/png_encoder.h83
-rw-r--r--base/gfx/point.cc52
-rw-r--r--base/gfx/point.h88
-rw-r--r--base/gfx/rect.cc217
-rw-r--r--base/gfx/rect.h153
-rw-r--r--base/gfx/rect_unittest.cc295
-rw-r--r--base/gfx/size.cc49
-rw-r--r--base/gfx/size.h91
-rw-r--r--base/gfx/skia_utils.cc99
-rw-r--r--base/gfx/skia_utils.h80
-rw-r--r--base/gfx/uniscribe.cc872
-rw-r--r--base/gfx/uniscribe.h390
-rw-r--r--base/gfx/uniscribe_unittest.cc164
-rw-r--r--base/gfx/vector_canvas.cc109
-rw-r--r--base/gfx/vector_canvas.h70
-rw-r--r--base/gfx/vector_canvas_unittest.cc1032
-rw-r--r--base/gfx/vector_device.cc646
-rw-r--r--base/gfx/vector_device.h146
44 files changed, 10152 insertions, 0 deletions
diff --git a/base/gfx/SConscript b/base/gfx/SConscript
new file mode 100644
index 0000000..4ec9e50
--- /dev/null
+++ b/base/gfx/SConscript
@@ -0,0 +1,83 @@
+# Copyright 2008, Google Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above
+# copyright notice, this list of conditions and the following disclaimer
+# in the documentation and/or other materials provided with the
+# distribution.
+# * Neither the name of Google Inc. nor the names of its
+# contributors may be used to endorse or promote products derived from
+# this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+Import('env')
+
+env = env.Clone()
+
+env.Prepend(
+ CPPPATH = [
+ '$SKIA_DIR/include',
+ '$SKIA_DIR/include/corecg',
+ '$SKIA_DIR/include/platform',
+ '$ZLIB_DIR',
+ '$LIBPNG_DIR',
+ '$ICU38_DIR/public/common',
+ '$ICU38_DIR/public/i18n',
+ '../..',
+ ],
+ CPPDEFINES = [
+ 'PNG_USER_CONFIG',
+ 'CHROME_PNG_WRITE_SUPPORT',
+ 'U_STATIC_IMPLEMENTATION',
+ 'CERT_CHAIN_PARA_HAS_EXTRA_FIELDS',
+ 'WIN32_LEAN_AND_MEAN',
+ ],
+ CCFLAGS = [
+ '/TP',
+
+ '/Wp64',
+ '/WX',
+
+ '/wd4503',
+ '/wd4819',
+ ],
+)
+
+input_files = [
+ 'bitmap_header.cc',
+ 'bitmap_platform_device.cc',
+ 'convolver.cc',
+ 'font_utils.cc',
+ 'image_operations.cc',
+ 'native_theme.cc',
+ 'platform_canvas.cc',
+ 'platform_device.cc',
+ 'png_decoder.cc',
+ 'png_encoder.cc',
+ 'point.cc',
+ 'rect.cc',
+ 'size.cc',
+ 'skia_utils.cc',
+ 'uniscribe.cc',
+ 'vector_canvas.cc',
+ 'vector_device.cc',
+]
+
+env.StaticLibrary('base_gfx', input_files)
diff --git a/base/gfx/bitmap_header.cc b/base/gfx/bitmap_header.cc
new file mode 100644
index 0000000..3c54694
--- /dev/null
+++ b/base/gfx/bitmap_header.cc
@@ -0,0 +1,88 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "base/gfx/bitmap_header.h"
+
+namespace gfx {
+
+void CreateBitmapHeader(int width, int height, BITMAPINFOHEADER* hdr) {
+ CreateBitmapHeaderWithColorDepth(width, height, 32, hdr);
+}
+
+void CreateBitmapHeaderWithColorDepth(int width, int height, int color_depth,
+ BITMAPINFOHEADER* hdr) {
+ // These values are shared with gfx::PlatformDevice
+ hdr->biSize = sizeof(BITMAPINFOHEADER);
+ hdr->biWidth = width;
+ hdr->biHeight = -height; // minus means top-down bitmap
+ hdr->biPlanes = 1;
+ hdr->biBitCount = color_depth;
+ hdr->biCompression = BI_RGB; // no compression
+ hdr->biSizeImage = 0;
+ hdr->biXPelsPerMeter = 1;
+ hdr->biYPelsPerMeter = 1;
+ hdr->biClrUsed = 0;
+ hdr->biClrImportant = 0;
+}
+
+
+void CreateBitmapV4Header(int width, int height, BITMAPV4HEADER* hdr) {
+ // Because bmp v4 header is just an extension, we just create a v3 header and
+ // copy the bits over to the v4 header.
+ BITMAPINFOHEADER header_v3;
+ CreateBitmapHeader(width, height, &header_v3);
+ memset(hdr, 0, sizeof(BITMAPV4HEADER));
+ memcpy(hdr, &header_v3, sizeof(BITMAPINFOHEADER));
+
+ // Correct the size of the header and fill in the mask values.
+ hdr->bV4Size = sizeof(BITMAPV4HEADER);
+ hdr->bV4RedMask = 0x00ff0000;
+ hdr->bV4GreenMask = 0x0000ff00;
+ hdr->bV4BlueMask = 0x000000ff;
+ hdr->bV4AlphaMask = 0xff000000;
+}
+
+// Creates a monochrome bitmap header.
+void CreateMonochromeBitmapHeader(int width,
+ int height,
+ BITMAPINFOHEADER* hdr) {
+ hdr->biSize = sizeof(BITMAPINFOHEADER);
+ hdr->biWidth = width;
+ hdr->biHeight = -height;
+ hdr->biPlanes = 1;
+ hdr->biBitCount = 1;
+ hdr->biCompression = BI_RGB;
+ hdr->biSizeImage = 0;
+ hdr->biXPelsPerMeter = 1;
+ hdr->biYPelsPerMeter = 1;
+ hdr->biClrUsed = 0;
+ hdr->biClrImportant = 0;
+}
+
+} // namespace gfx
diff --git a/base/gfx/bitmap_header.h b/base/gfx/bitmap_header.h
new file mode 100644
index 0000000..e8179c8
--- /dev/null
+++ b/base/gfx/bitmap_header.h
@@ -0,0 +1,56 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#ifndef BASE_GFX_BITMAP_HEADER_H__
+#define BASE_GFX_BITMAP_HEADER_H__
+
+#include <windows.h>
+
+namespace gfx {
+
+// Creates a BITMAPINFOHEADER structure given the bitmap's size.
+void CreateBitmapHeader(int width, int height, BITMAPINFOHEADER* hdr);
+
+// Creates a BITMAPINFOHEADER structure given the bitmap's size and
+// color depth in bits per pixel.
+void CreateBitmapHeaderWithColorDepth(int width, int height, int color_depth,
+ BITMAPINFOHEADER* hdr);
+
+// Creates a BITMAPV4HEADER structure given the bitmap's size. You probably
+// only need to use BMP V4 if you need transparency (alpha channel). This
+// function sets the AlphaMask to 0xff000000.
+void CreateBitmapV4Header(int width, int height, BITMAPV4HEADER* hdr);
+
+// Creates a monochrome bitmap header.
+void CreateMonochromeBitmapHeader(int width, int height, BITMAPINFOHEADER* hdr);
+
+
+} // namespace gfx
+
+#endif // BASE_GFX_BITMAP_HEADER_H__
diff --git a/base/gfx/bitmap_platform_device.cc b/base/gfx/bitmap_platform_device.cc
new file mode 100644
index 0000000..0a914e2
--- /dev/null
+++ b/base/gfx/bitmap_platform_device.cc
@@ -0,0 +1,482 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "base/gfx/bitmap_platform_device.h"
+
+#include "base/gfx/bitmap_header.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 BitmapPlatformDevice::BitmapPlatformDeviceData
+ : public base::RefCounted<BitmapPlatformDeviceData> {
+ public:
+ explicit BitmapPlatformDeviceData(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);
+ // The device offset is already modified according to the transformation.
+ void SetDeviceOffset(int x, int y);
+
+ const SkMatrix& transform() const {
+ return transform_;
+ }
+
+ protected:
+ // Loads the current transform (taking into account offset_*_) 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_;
+
+ // Additional offset applied to the transform. See setDeviceOffset().
+ int offset_x_;
+ int offset_y_;
+
+ // 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<BitmapPlatformDeviceData>;
+ ~BitmapPlatformDeviceData();
+
+ DISALLOW_EVIL_CONSTRUCTORS(BitmapPlatformDeviceData);
+};
+
+BitmapPlatformDevice::BitmapPlatformDeviceData::BitmapPlatformDeviceData(
+ HBITMAP hbitmap)
+ : hbitmap_(hbitmap),
+ hdc_(NULL),
+ offset_x_(0),
+ offset_y_(0),
+ 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();
+}
+
+BitmapPlatformDevice::BitmapPlatformDeviceData::~BitmapPlatformDeviceData() {
+ if (hdc_)
+ ReleaseBitmapDC();
+
+ // this will free the bitmap data as well as the bitmap handle
+ DeleteObject(hbitmap_);
+}
+
+HDC BitmapPlatformDevice::BitmapPlatformDeviceData::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 BitmapPlatformDevice::BitmapPlatformDeviceData::ReleaseBitmapDC() {
+ DCHECK(hdc_);
+ DeleteDC(hdc_);
+ hdc_ = NULL;
+}
+
+bool BitmapPlatformDevice::BitmapPlatformDeviceData::IsBitmapDCCreated() const {
+ return hdc_ != NULL;
+}
+
+
+void BitmapPlatformDevice::BitmapPlatformDeviceData::SetMatrixClip(
+ const SkMatrix& transform,
+ const SkRegion& region) {
+ transform_ = transform;
+ clip_region_ = region;
+ config_dirty_ = true;
+}
+
+void BitmapPlatformDevice::BitmapPlatformDeviceData::SetDeviceOffset(int x,
+ int y) {
+ offset_x_ = x;
+ offset_y_ = y;
+ config_dirty_ = true;
+}
+
+void BitmapPlatformDevice::BitmapPlatformDeviceData::LoadConfig() {
+ if (!config_dirty_ || !hdc_)
+ return; // Nothing to do.
+ config_dirty_ = false;
+
+ // Transform.
+ SkMatrix t(transform_);
+ t.postTranslate(SkIntToScalar(-offset_x_), SkIntToScalar(-offset_y_));
+ 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();
+ t.postTranslate(SkIntToScalar(-offset_x_), SkIntToScalar(-offset_y_));
+ 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.
+BitmapPlatformDevice* BitmapPlatformDevice::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
+ // we just expand it here.
+ if (width == 0)
+ width = 1;
+ if (height == 0)
+ height = 1;
+
+ BITMAPINFOHEADER hdr;
+ CreateBitmapHeader(width, height, &hdr);
+
+ void* data;
+ 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.
+ CHECK(hbitmap);
+
+ 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 BitmapPlatformDevice(new BitmapPlatformDeviceData(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.
+BitmapPlatformDevice::BitmapPlatformDevice(BitmapPlatformDeviceData* data,
+ const SkBitmap& bitmap)
+ : PlatformDevice(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).
+BitmapPlatformDevice::BitmapPlatformDevice(const BitmapPlatformDevice& other)
+ : PlatformDevice(
+ const_cast<BitmapPlatformDevice&>(other).accessBitmap(true)),
+ data_(other.data_) {
+}
+
+BitmapPlatformDevice::~BitmapPlatformDevice() {
+}
+
+BitmapPlatformDevice& BitmapPlatformDevice::operator=(
+ const BitmapPlatformDevice& other) {
+ data_ = other.data_;
+ return *this;
+}
+
+HDC BitmapPlatformDevice::getBitmapDC() {
+ return data_->GetBitmapDC();
+}
+
+void BitmapPlatformDevice::setMatrixClip(const SkMatrix& transform,
+ const SkRegion& region) {
+ data_->SetMatrixClip(transform, region);
+}
+
+void BitmapPlatformDevice::setDeviceOffset(int x, int y) {
+ data_->SetDeviceOffset(x, y);
+}
+
+void BitmapPlatformDevice::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 {
+ BLENDFUNCTION blend_function = {AC_SRC_OVER, 0, 255, AC_SRC_ALPHA};
+ AlphaBlend(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 BitmapPlatformDevice::prepareForGDI(int x, int y, int width, int height) {
+ processPixels<PrepareAlphaForGDI>(x, y, width, height);
+}
+
+void BitmapPlatformDevice::postProcessGDI(int x, int y, int width, int height) {
+ processPixels<PostProcessAlphaForGDI>(x, y, width, height);
+}
+
+void BitmapPlatformDevice::makeOpaque(int x, int y, int width, int height) {
+ processPixels<MakeOpaqueAlphaAdjuster>(x, y, width, height);
+}
+
+void BitmapPlatformDevice::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 BitmapPlatformDevice::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 BitmapPlatformDevice::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<BitmapPlatformDevice::adjustAlpha adjustor>
+void BitmapPlatformDevice::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/base/gfx/bitmap_platform_device.h b/base/gfx/bitmap_platform_device.h
new file mode 100644
index 0000000..481067a
--- /dev/null
+++ b/base/gfx/bitmap_platform_device.h
@@ -0,0 +1,135 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#ifndef BASE_GFX_BITMAP_PLATFORM_DEVICE_H__
+#define BASE_GFX_BITMAP_PLATFORM_DEVICE_H__
+
+#include "base/gfx/platform_device.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. BitmapPlatformDevice 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 BitmapPlatformDevice : public PlatformDevice {
+ 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 BitmapPlatformDevice* 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.
+ BitmapPlatformDevice(const BitmapPlatformDevice& other);
+ virtual ~BitmapPlatformDevice();
+
+ // See warning for copy constructor above.
+ BitmapPlatformDevice& operator=(const BitmapPlatformDevice& 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 setDeviceOffset(int x, int y);
+
+ 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 BitmapPlatformDeviceData;
+
+ // Private constructor.
+ BitmapPlatformDevice(BitmapPlatformDeviceData* 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<BitmapPlatformDeviceData> data_;
+};
+
+} // namespace gfx
+
+#endif // BASE_GFX_BITMAP_PLATFORM_DEVICE_H__
diff --git a/base/gfx/convolver.cc b/base/gfx/convolver.cc
new file mode 100644
index 0000000..e56493e
--- /dev/null
+++ b/base/gfx/convolver.cc
@@ -0,0 +1,359 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include <algorithm>
+
+#include "base/basictypes.h"
+#include "base/gfx/convolver.h"
+#include "base/logging.h"
+
+namespace gfx {
+
+namespace {
+
+// Converts the argument to an 8-bit unsigned value by clamping to the range
+// 0-255.
+inline uint8 ClampTo8(int32 a) {
+ if (static_cast<uint32>(a) < 256)
+ return a; // Avoid the extra check in the common case.
+ if (a < 0)
+ return 0;
+ return 255;
+}
+
+// Stores a list of rows in a circular buffer. The usage is you write into it
+// by calling AdvanceRow. It will keep track of which row in the buffer it
+// should use next, and the total number of rows added.
+class CircularRowBuffer {
+ public:
+ // The number of pixels in each row is given in |source_row_pixel_width|.
+ // The maximum number of rows needed in the buffer is |max_y_filter_size|
+ // (we only need to store enough rows for the biggest filter).
+ //
+ // We use the |first_input_row| to compute the coordinates of all of the
+ // following rows returned by Advance().
+ CircularRowBuffer(int dest_row_pixel_width, int max_y_filter_size,
+ int first_input_row)
+ : row_byte_width_(dest_row_pixel_width * 4),
+ num_rows_(max_y_filter_size),
+ next_row_(0),
+ next_row_coordinate_(first_input_row) {
+ buffer_.resize(row_byte_width_ * max_y_filter_size);
+ row_addresses_.resize(num_rows_);
+ }
+
+ // Moves to the next row in the buffer, returning a pointer to the beginning
+ // of it.
+ uint8* AdvanceRow() {
+ uint8* row = &buffer_[next_row_ * row_byte_width_];
+ next_row_coordinate_++;
+
+ // Set the pointer to the next row to use, wrapping around if necessary.
+ next_row_++;
+ if (next_row_ == num_rows_)
+ next_row_ = 0;
+ return row;
+ }
+
+ // Returns a pointer to an "unrolled" array of rows. These rows will start
+ // at the y coordinate placed into |*first_row_index| and will continue in
+ // order for the maximum number of rows in this circular buffer.
+ //
+ // The |first_row_index_| may be negative. This means the circular buffer
+ // starts before the top of the image (it hasn't been filled yet).
+ uint8* const* GetRowAddresses(int* first_row_index) {
+ // Example for a 4-element circular buffer holding coords 6-9.
+ // Row 0 Coord 8
+ // Row 1 Coord 9
+ // Row 2 Coord 6 <- next_row_ = 2, next_row_coordinate_ = 10.
+ // Row 3 Coord 7
+ //
+ // The "next" row is also the first (lowest) coordinate. This computation
+ // may yield a negative value, but that's OK, the math will work out
+ // since the user of this buffer will compute the offset relative
+ // to the first_row_index and the negative rows will never be used.
+ *first_row_index = next_row_coordinate_ - num_rows_;
+
+ int cur_row = next_row_;
+ for (int i = 0; i < num_rows_; i++) {
+ row_addresses_[i] = &buffer_[cur_row * row_byte_width_];
+
+ // Advance to the next row, wrapping if nexessary.
+ cur_row++;
+ if (cur_row == num_rows_)
+ cur_row = 0;
+ }
+ return &row_addresses_[0];
+ }
+
+ private:
+ // The buffer storing the rows. They are packed, each one row_byte_width_.
+ std::vector<uint8> buffer_;
+
+ // Number of bytes per row in the |buffer_|.
+ int row_byte_width_;
+
+ // The number of rows available in the buffer.
+ int num_rows_;
+
+ // The next row index we should write into. This wraps around as the
+ // circular buffer is used.
+ int next_row_;
+
+ // The y coordinate of the |next_row_|. This is incremented each time a
+ // new row is appended and does not wrap.
+ int next_row_coordinate_;
+
+ // Buffer used by GetRowAddresses().
+ std::vector<uint8*> row_addresses_;
+};
+
+// Convolves horizontally along a single row. The row data is given in
+// |src_data| and continues for the num_values() of the filter.
+template<bool has_alpha>
+void ConvolveHorizontally(const uint8* src_data,
+ const ConvolusionFilter1D& filter,
+ unsigned char* out_row) {
+ // Loop over each pixel on this row in the output image.
+ int num_values = filter.num_values();
+ for (int out_x = 0; out_x < num_values; out_x++) {
+ // Get the filter that determines the current output pixel.
+ int filter_offset, filter_length;
+ const int16* filter_values =
+ filter.FilterForValue(out_x, &filter_offset, &filter_length);
+
+ // Compute the first pixel in this row that the filter affects. It will
+ // touch |filter_length| pixels (4 bytes each) after this.
+ const uint8* row_to_filter = &src_data[filter_offset * 4];
+
+ // Apply the filter to the row to get the destination pixel in |accum|.
+ int32 accum[4] = {0};
+ for (int filter_x = 0; filter_x < filter_length; filter_x++) {
+ int16 cur_filter = filter_values[filter_x];
+ accum[0] += cur_filter * row_to_filter[filter_x * 4 + 0];
+ accum[1] += cur_filter * row_to_filter[filter_x * 4 + 1];
+ accum[2] += cur_filter * row_to_filter[filter_x * 4 + 2];
+ if (has_alpha)
+ accum[3] += cur_filter * row_to_filter[filter_x * 4 + 3];
+ }
+
+ // Bring this value back in range. All of the filter scaling factors
+ // are in fixed point with kShiftBits bits of fractional part.
+ accum[0] >>= ConvolusionFilter1D::kShiftBits;
+ accum[1] >>= ConvolusionFilter1D::kShiftBits;
+ accum[2] >>= ConvolusionFilter1D::kShiftBits;
+ if (has_alpha)
+ accum[3] >>= ConvolusionFilter1D::kShiftBits;
+
+ // Store the new pixel.
+ out_row[out_x * 4 + 0] = ClampTo8(accum[0]);
+ out_row[out_x * 4 + 1] = ClampTo8(accum[1]);
+ out_row[out_x * 4 + 2] = ClampTo8(accum[2]);
+ if (has_alpha)
+ out_row[out_x * 4 + 3] = ClampTo8(accum[3]);
+ }
+}
+
+// Does vertical convolusion to produce one output row. The filter values and
+// length are given in the first two parameters. These are applied to each
+// of the rows pointed to in the |source_data_rows| array, with each row
+// being |pixel_width| wide.
+//
+// The output must have room for |pixel_width * 4| bytes.
+template<bool has_alpha>
+void ConvolveVertically(const int16* filter_values,
+ int filter_length,
+ uint8* const* source_data_rows,
+ int pixel_width,
+ uint8* out_row) {
+ // We go through each column in the output and do a vertical convolusion,
+ // generating one output pixel each time.
+ for (int out_x = 0; out_x < pixel_width; out_x++) {
+ // Compute the number of bytes over in each row that the current column
+ // we're convolving starts at. The pixel will cover the next 4 bytes.
+ int byte_offset = out_x * 4;
+
+ // Apply the filter to one column of pixels.
+ int32 accum[4] = {0};
+ for (int filter_y = 0; filter_y < filter_length; filter_y++) {
+ int16 cur_filter = filter_values[filter_y];
+ accum[0] += cur_filter * source_data_rows[filter_y][byte_offset + 0];
+ accum[1] += cur_filter * source_data_rows[filter_y][byte_offset + 1];
+ accum[2] += cur_filter * source_data_rows[filter_y][byte_offset + 2];
+ if (has_alpha)
+ accum[3] += cur_filter * source_data_rows[filter_y][byte_offset + 3];
+ }
+
+ // Bring this value back in range. All of the filter scaling factors
+ // are in fixed point with kShiftBits bits of precision.
+ accum[0] >>= ConvolusionFilter1D::kShiftBits;
+ accum[1] >>= ConvolusionFilter1D::kShiftBits;
+ accum[2] >>= ConvolusionFilter1D::kShiftBits;
+ if (has_alpha)
+ accum[3] >>= ConvolusionFilter1D::kShiftBits;
+
+ // Store the new pixel.
+ out_row[byte_offset + 0] = ClampTo8(accum[0]);
+ out_row[byte_offset + 1] = ClampTo8(accum[1]);
+ out_row[byte_offset + 2] = ClampTo8(accum[2]);
+ if (has_alpha) {
+ uint8 alpha = ClampTo8(accum[3]);
+
+ // Make sure the alpha channel doesn't come out larger than any of the
+ // color channels. We use premultipled alpha channels, so this should
+ // never happen, but rounding errors will cause this from time to time.
+ // These "impossible" colors will cause overflows (and hence random pixel
+ // values) when the resulting bitmap is drawn to the screen.
+ //
+ // We only need to do this when generating the final output row (here).
+ int max_color_channel = std::max(out_row[byte_offset + 0],
+ std::max(out_row[byte_offset + 1], out_row[byte_offset + 2]));
+ if (alpha < max_color_channel)
+ out_row[byte_offset + 3] = max_color_channel;
+ else
+ out_row[byte_offset + 3] = alpha;
+ } else {
+ // No alpha channel, the image is opqaue.
+ out_row[byte_offset + 3] = 0xff;
+ }
+ }
+}
+
+} // namespace
+
+// ConvolusionFilter1D ---------------------------------------------------------
+
+void ConvolusionFilter1D::AddFilter(int filter_offset,
+ const float* filter_values,
+ int filter_length) {
+ FilterInstance instance;
+ instance.data_location = static_cast<int>(filter_values_.size());
+ instance.offset = filter_offset;
+ instance.length = filter_length;
+ filters_.push_back(instance);
+
+ DCHECK(filter_length > 0);
+ for (int i = 0; i < filter_length; i++)
+ filter_values_.push_back(FloatToFixed(filter_values[i]));
+
+ max_filter_ = std::max(max_filter_, filter_length);
+}
+
+void ConvolusionFilter1D::AddFilter(int filter_offset,
+ const int16* filter_values,
+ int filter_length) {
+ FilterInstance instance;
+ instance.data_location = static_cast<int>(filter_values_.size());
+ instance.offset = filter_offset;
+ instance.length = filter_length;
+ filters_.push_back(instance);
+
+ DCHECK(filter_length > 0);
+ for (int i = 0; i < filter_length; i++)
+ filter_values_.push_back(filter_values[i]);
+
+ max_filter_ = std::max(max_filter_, filter_length);
+}
+
+// BGRAConvolve2D -------------------------------------------------------------
+
+void BGRAConvolve2D(const uint8* source_data,
+ int source_byte_row_stride,
+ bool source_has_alpha,
+ const ConvolusionFilter1D& filter_x,
+ const ConvolusionFilter1D& filter_y,
+ uint8* output) {
+ int max_y_filter_size = filter_y.max_filter();
+
+ // The next row in the input that we will generate a horizontally
+ // convolved row for. If the filter doesn't start at the beginning of the
+ // image (this is the case when we are only resizing a subset), then we
+ // don't want to generate any output rows before that. Compute the starting
+ // row for convolusion as the first pixel for the first vertical filter.
+ int filter_offset, filter_length;
+ const int16* filter_values =
+ filter_y.FilterForValue(0, &filter_offset, &filter_length);
+ int next_x_row = filter_offset;
+
+ // We loop over each row in the input doing a horizontal convolusion. This
+ // will result in a horizontally convolved image. We write the results into
+ // a circular buffer of convolved rows and do vertical convolusion as rows
+ // are available. This prevents us from having to store the entire
+ // intermediate image and helps cache coherency.
+ CircularRowBuffer row_buffer(filter_x.num_values(), max_y_filter_size,
+ filter_offset);
+
+ // Loop over every possible output row, processing just enough horizontal
+ // convolusions to run each subsequent vertical convolusion.
+ int output_row_byte_width = filter_x.num_values() * 4;
+ int num_output_rows = filter_y.num_values();
+ for (int out_y = 0; out_y < num_output_rows; out_y++) {
+ filter_values = filter_y.FilterForValue(out_y,
+ &filter_offset, &filter_length);
+
+ // Generate output rows until we have enough to run the current filter.
+ while (next_x_row < filter_offset + filter_length) {
+ if (source_has_alpha) {
+ ConvolveHorizontally<true>(
+ &source_data[next_x_row * source_byte_row_stride],
+ filter_x, row_buffer.AdvanceRow());
+ } else {
+ ConvolveHorizontally<false>(
+ &source_data[next_x_row * source_byte_row_stride],
+ filter_x, row_buffer.AdvanceRow());
+ }
+ next_x_row++;
+ }
+
+ // Compute where in the output image this row of final data will go.
+ uint8* cur_output_row = &output[out_y * output_row_byte_width];
+
+ // Get the list of rows that the circular buffer has, in order.
+ int first_row_in_circular_buffer;
+ uint8* const* rows_to_convolve =
+ row_buffer.GetRowAddresses(&first_row_in_circular_buffer);
+
+ // Now compute the start of the subset of those rows that the filter
+ // needs.
+ uint8* const* first_row_for_filter =
+ &rows_to_convolve[filter_offset - first_row_in_circular_buffer];
+
+ if (source_has_alpha) {
+ ConvolveVertically<true>(filter_values, filter_length,
+ first_row_for_filter,
+ filter_x.num_values(), cur_output_row);
+ } else {
+ ConvolveVertically<false>(filter_values, filter_length,
+ first_row_for_filter,
+ filter_x.num_values(), cur_output_row);
+ }
+ }
+}
+
+} // namespace gfx \ No newline at end of file
diff --git a/base/gfx/convolver.h b/base/gfx/convolver.h
new file mode 100644
index 0000000..8a2310e
--- /dev/null
+++ b/base/gfx/convolver.h
@@ -0,0 +1,156 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#ifndef BASE_GFX_CONVOLVER_H__
+#define BASE_GFX_CONVOLVER_H__
+
+#include <vector>
+
+#include "base/basictypes.h"
+
+namespace gfx {
+
+// Represents a filter in one dimension. Each output pixel has one entry in this
+// object for the filter values contributing to it. You build up the filter
+// list by calling AddFilter for each output pixel (in order).
+//
+// We do 2-dimensional convolusion by first convolving each row by one
+// ConvolusionFilter1D, then convolving each column by another one.
+//
+// Entries are stored in fixed point, shifted left by kShiftBits.
+class ConvolusionFilter1D {
+ public:
+ // The number of bits that fixed point values are shifted by.
+ enum { kShiftBits = 14 };
+
+ ConvolusionFilter1D() : max_filter_(0) {
+ }
+
+ // Convert between floating point and our fixed point representation.
+ static inline int16 FloatToFixed(float f) {
+ return static_cast<int16>(f * (1 << kShiftBits));
+ }
+ static inline unsigned char FixedToChar(int16 x) {
+ return static_cast<unsigned char>(x >> kShiftBits);
+ }
+
+ // Returns the maximum pixel span of a filter.
+ int max_filter() const { return max_filter_; }
+
+ // Returns the number of filters in this filter. This is the dimension of the
+ // output image.
+ int num_values() const { return static_cast<int>(filters_.size()); }
+
+ // Appends the given list of scaling values for generating a given output
+ // pixel. |filter_offset| is the distance from the edge of the image to where
+ // the scaling factors start. The scaling factors apply to the source pixels
+ // starting from this position, and going for the next |filter_length| pixels.
+ //
+ // You will probably want to make sure your input is normalized (that is,
+ // all entries in |filter_values| sub to one) to prevent affecting the overall
+ // brighness of the image.
+ //
+ // The filter_length must be > 0.
+ //
+ // This version will automatically convert your input to fixed point.
+ void AddFilter(int filter_offset,
+ const float* filter_values,
+ int filter_length);
+
+ // Same as the above version, but the input is already fixed point.
+ void AddFilter(int filter_offset,
+ const int16* filter_values,
+ int filter_length);
+
+ // Retrieves a filter for the given |value_offset|, a position in the output
+ // image in the direction we're convolving. The offset and length of the
+ // filter values are put into the corresponding out arguments (see AddFilter
+ // above for what these mean), and a pointer to the first scaling factor is
+ // returned. There will be |filter_length| values in this array.
+ inline const int16* FilterForValue(int value_offset,
+ int* filter_offset,
+ int* filter_length) const {
+ const FilterInstance& filter = filters_[value_offset];
+ *filter_offset = filter.offset;
+ *filter_length = filter.length;
+ return &filter_values_[filter.data_location];
+ }
+
+ private:
+ struct FilterInstance {
+ // Offset within filter_values for this instance of the filter.
+ int data_location;
+
+ // Distance from the left of the filter to the center. IN PIXELS
+ int offset;
+
+ // Number of values in this filter instance.
+ int length;
+ };
+
+ // Stores the information for each filter added to this class.
+ std::vector<FilterInstance> filters_;
+
+ // We store all the filter values in this flat list, indexed by
+ // |FilterInstance.data_location| to avoid the mallocs required for storing
+ // each one separately.
+ std::vector<int16> filter_values_;
+
+ // The maximum size of any filter we've added.
+ int max_filter_;
+};
+
+// Does a two-dimensional convolusion on the given source image.
+//
+// It is assumed the source pixel offsets referenced in the input filters
+// reference only valid pixels, so the source image size is not required. Each
+// row of the source image starts |source_byte_row_stride| after the previous
+// one (this allows you to have rows with some padding at the end).
+//
+// The result will be put into the given output buffer. The destination image
+// size will be xfilter.num_values() * yfilter.num_values() pixels. It will be
+// in rows of exactly xfilter.num_values() * 4 bytes.
+//
+// |source_has_alpha| is a hint that allows us to avoid doing computations on
+// the alpha channel if the image is opaque. If you don't know, set this to
+// true and it will work properly, but setting this to false will be a few
+// percent faster if you know the image is opaque.
+//
+// The layout in memory is assumed to be 4-bytes per pixel in B-G-R-A order
+// (this is ARGB when loaded into 32-bit words on a little-endian machine).
+void BGRAConvolve2D(const uint8* source_data,
+ int source_byte_row_stride,
+ bool source_has_alpha,
+ const ConvolusionFilter1D& xfilter,
+ const ConvolusionFilter1D& yfilter,
+ uint8* output);
+
+} // namespace gfx
+
+#endif // BASE_GFX_CONVOLVER_H__
diff --git a/base/gfx/convolver_unittest.cc b/base/gfx/convolver_unittest.cc
new file mode 100644
index 0000000..b9d210a
--- /dev/null
+++ b/base/gfx/convolver_unittest.cc
@@ -0,0 +1,151 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include <string.h>
+#include <time.h>
+#include <vector>
+
+#include "base/gfx/convolver.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace gfx {
+
+namespace {
+
+// Fills the given filter with impulse functions for the range 0->num_entries.
+ void FillImpulseFilter(int num_entries, ConvolusionFilter1D* filter) {
+ float one = 1.0f;
+ for (int i = 0; i < num_entries; i++)
+ filter->AddFilter(i, &one, 1);
+}
+
+// Filters the given input with the impulse function, and verifies that it
+// does not change.
+void TestImpulseConvolusion(const unsigned char* data, int width, int height) {
+ int byte_count = width * height * 4;
+
+ ConvolusionFilter1D filter_x;
+ FillImpulseFilter(width, &filter_x);
+
+ ConvolusionFilter1D filter_y;
+ FillImpulseFilter(height, &filter_y);
+
+ std::vector<unsigned char> output;
+ output.resize(byte_count);
+ BGRAConvolve2D(data, width * 4, true, filter_x, filter_y, &output[0]);
+
+ // Output should exactly match input.
+ EXPECT_EQ(0, memcmp(data, &output[0], byte_count));
+}
+
+// Fills the destination filter with a box filter averaging every two pixels
+// to produce the output.
+void FillBoxFilter(int size, ConvolusionFilter1D* filter) {
+ const float box[2] = { 0.5, 0.5 };
+ for (int i = 0; i < size; i++)
+ filter->AddFilter(i * 2, box, 2);
+}
+
+} // namespace
+
+// Tests that each pixel, when set and run through the impulse filter, does
+// not change.
+TEST(Convolver, Impulse) {
+ // We pick an "odd" size that is not likely to fit on any boundaries so that
+ // we can see if all the widths and paddings are handled properly.
+ int width = 15;
+ int height = 31;
+ int byte_count = width * height * 4;
+ std::vector<unsigned char> input;
+ input.resize(byte_count);
+
+ unsigned char* input_ptr = &input[0];
+ for (int y = 0; y < height; y++) {
+ for (int x = 0; x < width; x++) {
+ for (int channel = 0; channel < 3; channel++) {
+ memset(input_ptr, 0, byte_count);
+ input_ptr[(y * width + x) * 4 + channel] = 0xff;
+ // Always set the alpha channel or it will attempt to "fix" it for us.
+ input_ptr[(y * width + x) * 4 + 3] = 0xff;
+ TestImpulseConvolusion(input_ptr, width, height);
+ }
+ }
+ }
+}
+
+// Tests that using a box filter to halve an image results in every square of 4
+// pixels in the original get averaged to a pixel in the output.
+TEST(Convolver, Halve) {
+ static const int kSize = 16;
+
+ int src_width = kSize;
+ int src_height = kSize;
+ int src_row_stride = src_width * 4;
+ int src_byte_count = src_row_stride * src_height;
+ std::vector<unsigned char> input;
+ input.resize(src_byte_count);
+
+ int dest_width = src_width / 2;
+ int dest_height = src_height / 2;
+ int dest_byte_count = dest_width * dest_height * 4;
+ std::vector<unsigned char> output;
+ output.resize(dest_byte_count);
+
+ // First fill the array with a bunch of random data.
+ srand(static_cast<unsigned>(time(NULL)));
+ for (int i = 0; i < src_byte_count; i++)
+ input[i] = rand() * 255 / RAND_MAX;
+
+ // Compute the filters.
+ ConvolusionFilter1D filter_x, filter_y;
+ FillBoxFilter(dest_width, &filter_x);
+ FillBoxFilter(dest_height, &filter_y);
+
+ // Do the convolusion.
+ BGRAConvolve2D(&input[0], src_width, true, filter_x, filter_y, &output[0]);
+
+ // Compute the expected results and check, allowing for a small difference
+ // to account for rounding errors.
+ for (int y = 0; y < dest_height; y++) {
+ for (int x = 0; x < dest_width; x++) {
+ for (int channel = 0; channel < 4; channel++) {
+ int src_offset = (y * 2 * src_row_stride + x * 2 * 4) + channel;
+ int value = input[src_offset] + // Top left source pixel.
+ input[src_offset + 4] + // Top right source pixel.
+ input[src_offset + src_row_stride] + // Lower left.
+ input[src_offset + src_row_stride + 4]; // Lower right.
+ value /= 4; // Average.
+ int difference = value - output[(y * dest_width + x) * 4 + channel];
+ EXPECT_TRUE(difference >= -1 || difference <= 1);
+ }
+ }
+ }
+}
+
+} // namespace gfx \ No newline at end of file
diff --git a/base/gfx/font_utils.cc b/base/gfx/font_utils.cc
new file mode 100644
index 0000000..340dbbc
--- /dev/null
+++ b/base/gfx/font_utils.cc
@@ -0,0 +1,305 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "base/gfx/font_utils.h"
+
+#include <limits>
+#include <map>
+
+#include "base/gfx/uniscribe.h"
+#include "base/logging.h"
+#include "base/singleton.h"
+#include "base/string_util.h"
+#include "unicode/locid.h"
+
+namespace gfx {
+
+namespace {
+
+// hash_map has extra cost with no sizable gain for a small number of integer
+// key items. When the map size becomes much bigger (which will be later as
+// more scripts are added) and this turns out to be prominent in the profile, we
+// may consider switching to hash_map (or just an array if we support all the
+// scripts)
+typedef std::map<UScriptCode, const wchar_t*> ScriptToFontMap;
+
+struct ScriptToFontMapSingletonTraits
+ : public DefaultSingletonTraits<ScriptToFontMap> {
+ static ScriptToFontMap* New() {
+ struct FontMap {
+ UScriptCode script;
+ const wchar_t* family;
+ };
+
+ const static FontMap font_map[] = {
+ {USCRIPT_SIMPLIFIED_HAN, L"simsun"},
+ {USCRIPT_TRADITIONAL_HAN, L"pmingliu"},
+ {USCRIPT_HIRAGANA, L"ms pgothic"},
+ {USCRIPT_KATAKANA, L"ms pgothic"},
+ {USCRIPT_KATAKANA_OR_HIRAGANA, L"ms pgothic"},
+ {USCRIPT_HANGUL, L"gulim"},
+ {USCRIPT_THAI, L"tahoma"},
+ {USCRIPT_HEBREW, L"david"},
+ {USCRIPT_ARABIC, L"simplified arabic"},
+ {USCRIPT_DEVANAGARI, L"mangal"},
+ {USCRIPT_BENGALI, L"vrinda"},
+ {USCRIPT_GURMUKHI, L"raavi"},
+ {USCRIPT_GUJARATI, L"shruti"},
+ {USCRIPT_ORIYA, L"kalinga"},
+ {USCRIPT_TAMIL, L"latha"},
+ {USCRIPT_TELUGU, L"gautami"},
+ {USCRIPT_KANNADA, L"tunga"},
+ {USCRIPT_MALAYALAM, L"kartika"},
+ {USCRIPT_LAO, L"dokchampa"},
+ {USCRIPT_TIBETAN, L"microsoft himalaya"},
+ {USCRIPT_GEORGIAN, L"sylfaen"},
+ {USCRIPT_ARMENIAN, L"sylfaen"},
+ {USCRIPT_ETHIOPIC, L"nyala"},
+ {USCRIPT_CANADIAN_ABORIGINAL, L"euphemia"},
+ {USCRIPT_CHEROKEE, L"plantagenet cherokee"},
+ {USCRIPT_YI, L"microsoft yi balti"},
+ {USCRIPT_SINHALA, L"iskoola pota"},
+ {USCRIPT_SYRIAC, L"estrangelo edessa"},
+ {USCRIPT_KHMER, L"daunpenh"},
+ {USCRIPT_THAANA, L"mv boli"},
+ {USCRIPT_MONGOLIAN, L"mongolian balti"},
+ // For common, perhaps we should return a font
+ // for the current application/system locale.
+ //{USCRIPT_COMMON, L"times new roman"}
+ };
+
+ ScriptToFontMap* new_instance = new ScriptToFontMap;
+ // Cannot recover from OOM so that there's no need to check.
+ for (int i = 0; i < arraysize(font_map); ++i)
+ (*new_instance)[font_map[i].script] = font_map[i].family;
+
+ // Initialize the locale-dependent mapping.
+ // Since Chrome synchronizes the ICU default locale with its UI locale,
+ // this ICU locale tells the current UI locale of Chrome.
+ Locale locale = Locale::getDefault();
+ ScriptToFontMap::const_iterator iter;
+ if (locale == Locale::getKorean()) {
+ iter = new_instance->find(USCRIPT_HANGUL);
+ } else if (locale == Locale::getJapanese()) {
+ iter = new_instance->find(USCRIPT_KATAKANA_OR_HIRAGANA);
+ } else if (locale == Locale::getTraditionalChinese()) {
+ iter = new_instance->find(USCRIPT_TRADITIONAL_HAN);
+ } else {
+ iter = new_instance->find(USCRIPT_SIMPLIFIED_HAN);
+ }
+ if (iter != new_instance->end())
+ (*new_instance)[USCRIPT_HAN] = iter->second;
+
+ return new_instance;
+ }
+};
+
+Singleton<ScriptToFontMap, ScriptToFontMapSingletonTraits> script_font_map;
+
+const int kUndefinedAscent = std::numeric_limits<int>::min();
+
+// Given an HFONT, return the ascent. If GetTextMetrics fails,
+// kUndefinedAscent is returned, instead.
+int GetAscent(HFONT hfont) {
+ HDC dc = GetDC(NULL);
+ HGDIOBJ oldFont = SelectObject(dc, hfont);
+ TEXTMETRIC tm;
+ BOOL got_metrics = GetTextMetrics(dc, &tm);
+ SelectObject(dc, oldFont);
+ ReleaseDC(NULL, dc);
+ return got_metrics ? tm.tmAscent : kUndefinedAscent;
+}
+
+struct FontData {
+ FontData() : hfont(NULL), ascent(kUndefinedAscent), script_cache(NULL) {}
+ HFONT hfont;
+ int ascent;
+ mutable SCRIPT_CACHE script_cache;
+};
+
+// Again, using hash_map does not earn us much here.
+// page_cycler_test intl2 gave us a 'better' result with map than with hash_map
+// even though they're well-within 1-sigma of each other so that the difference
+// is not significant. On the other hand, some pages in intl2 seem to
+// take longer to load with map in the 1st pass. Need to experiment further.
+typedef std::map<std::wstring, FontData*> FontDataCache;
+struct FontDataCacheSingletonTraits
+ : public DefaultSingletonTraits<FontDataCache> {
+ static void Delete(FontDataCache* cache) {
+ FontDataCache::iterator iter = cache->begin();
+ while (iter != cache->end()) {
+ SCRIPT_CACHE script_cache = iter->second->script_cache;
+ if (script_cache)
+ ScriptFreeCache(&script_cache);
+ delete iter->second;
+ ++iter;
+ }
+ delete cache;
+ }
+};
+
+} // namespace
+
+// TODO(jungshik) : this is font fallback code version 0.1
+// - Cover all the scripts
+// - Get the default font for each script/generic family from the
+// preference instead of hardcoding in the source.
+// - Support generic families (from FontDescription)
+// - If the default font for a script is not available,
+// use EnumFontFamilies or similar APIs to come up with a list of
+// fonts supporting the script and cache the result.
+// - Consider using UnicodeSet (or UnicodeMap) to keep track of which
+// character is supported by which font
+// - Update script_font_cache in response to WM_FONTCHANGE
+
+const wchar_t* GetFontFamilyForScript(UScriptCode script,
+ GenericFamilyType generic) {
+ ScriptToFontMap::const_iterator iter = script_font_map->find(script);
+ const wchar_t* family = NULL;
+ if (iter != script_font_map->end()) {
+ family = iter->second;
+ }
+ return family;
+}
+
+// TODO(jungshik)
+// - Handle 'Inherited', 'Common' and 'Unknown'
+// (see http://www.unicode.org/reports/tr24/#Usage_Model )
+// For 'Inherited' and 'Common', perhaps we need to
+// accept another parameter indicating the previous family
+// and just return it.
+// - All the characters (or characters up to the point a single
+// font can cover) need to be taken into account
+const wchar_t* GetFallbackFamily(const wchar_t* characters,
+ int length,
+ GenericFamilyType generic) {
+ DCHECK(characters && characters[0] && length > 0);
+ UScriptCode script = USCRIPT_COMMON;
+
+ // Sometimes characters common to script (e.g. space) is at
+ // the beginning of a string so that we need to skip them
+ // to get a font required to render the string.
+ int i = 0;
+ UChar32 ucs4 = 0;
+ while (i < length && script == USCRIPT_COMMON ||
+ script == USCRIPT_INVALID_CODE) {
+ U16_NEXT(characters, i, length, ucs4);
+ UErrorCode err = U_ZERO_ERROR;
+ script = uscript_getScript(ucs4, &err);
+ // silently ignore the error
+ }
+
+ // hack for full width ASCII. Japanese are most fond of full-width ASCII.
+ // TODO(jungshik) find a better way !
+ if (0xFF00 < ucs4 && ucs4 < 0xFF5F)
+ return L"ms pgothic";
+
+ const wchar_t* family = GetFontFamilyForScript(script, generic);
+ if (!family) {
+ int plane = ucs4 >> 16;
+ switch (plane) {
+ case 1:
+ family = L"code2001";
+ break;
+ case 2:
+ family = L"simsun ext b";
+ break;
+ default:
+ family = L"arial unicode ms";
+ }
+ }
+
+ return family;
+}
+
+
+
+// Be aware that this is not thread-safe.
+bool GetDerivedFontData(const wchar_t *family,
+ int style,
+ LOGFONT *logfont,
+ int *ascent,
+ HFONT *hfont,
+ SCRIPT_CACHE **script_cache) {
+ DCHECK(logfont && family && *family);
+ // Using |Singleton| here is not free, but the intl2 page cycler test
+ // does not show any noticeable difference with and without it. Leaking
+ // the contents of FontDataCache (especially SCRIPT_CACHE) at the end
+ // of a renderer process may not be a good idea. We may use
+ // atexit(). However, with no noticeable performance difference, |Singleton|
+ // is cleaner, I believe.
+ FontDataCache* font_data_cache =
+ Singleton<FontDataCache, FontDataCacheSingletonTraits>::get();
+ // TODO(jungshik) : This comes up pretty high in the profile so that
+ // we need to measure whether using SHA256 (after coercing all the
+ // fields to char*) is faster than StringPrintf.
+ std::wstring font_key = StringPrintf(L"%1d:%d:%s", style, logfont->lfHeight,
+ family);
+ FontDataCache::const_iterator iter = font_data_cache->find(font_key);
+ FontData *derived;
+ if (iter == font_data_cache->end()) {
+ DCHECK(wcslen(family) < LF_FACESIZE);
+ wcscpy_s(logfont->lfFaceName, LF_FACESIZE, family);
+ // TODO(jungshik): CreateFontIndirect always comes up with
+ // a font even if there's no font matching the name. Need to
+ // check it against what we actually want (as is done in FontCacheWin.cpp)
+ derived = new FontData;
+ derived->hfont = CreateFontIndirect(logfont);
+ // GetAscent may return kUndefinedAscent, but we still want to
+ // cache it so that we won't have to call CreateFontIndirect once
+ // more for HFONT next time.
+ derived->ascent = GetAscent(derived->hfont);
+ (*font_data_cache)[font_key] = derived;
+ } else {
+ derived = iter->second;
+ // Last time, GetAscent failed so that only HFONT was
+ // cached. Try once more assuming that TryPreloadFont
+ // was called by a caller between calls.
+ if (kUndefinedAscent == derived->ascent)
+ derived->ascent = GetAscent(derived->hfont);
+ }
+ *hfont = derived->hfont;
+ *ascent = derived->ascent;
+ *script_cache = &(derived->script_cache);
+ return *ascent != kUndefinedAscent;
+}
+
+int GetStyleFromLogfont(const LOGFONT* logfont) {
+ // TODO(jungshik) : consider defining UNDEFINED or INVALID for style and
+ // returning it when logfont is NULL
+ if (!logfont) {
+ NOTREACHED();
+ return FONT_STYLE_NORMAL;
+ }
+ return (logfont->lfItalic ? FONT_STYLE_ITALIC : FONT_STYLE_NORMAL) |
+ (logfont->lfUnderline ? FONT_STYLE_UNDERLINED : FONT_STYLE_NORMAL) |
+ (logfont->lfWeight >= 700 ? FONT_STYLE_BOLD : FONT_STYLE_NORMAL);
+}
+
+} // namespace gfx
diff --git a/base/gfx/font_utils.h b/base/gfx/font_utils.h
new file mode 100644
index 0000000..e06b588
--- /dev/null
+++ b/base/gfx/font_utils.h
@@ -0,0 +1,105 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+//
+// A collection of utilities for font handling.
+
+#ifndef BASE_GFX_FONT_UTILS_H__
+#define BASE_GFX_FONT_UTILS_H__
+
+#include <usp10.h>
+#include <wchar.h>
+#include <windows.h>
+
+#include <unicode/uscript.h>
+
+namespace gfx {
+
+// The order of family types needs to be exactly the same as
+// WebCore::FontDescription::GenericFamilyType. We may lift that restriction
+// when we make webkit_glue::WebkitGenericToChromeGenericFamily more
+// intelligent.
+enum GenericFamilyType {
+ GENERIC_FAMILY_NONE = 0,
+ GENERIC_FAMILY_STANDARD,
+ GENERIC_FAMILY_SERIF,
+ GENERIC_FAMILY_SANSSERIF,
+ GENERIC_FAMILY_MONOSPACE,
+ GENERIC_FAMILY_CURSIVE,
+ GENERIC_FAMILY_FANTASY
+};
+
+// Return a font family that supports a script and belongs to |generic| font family.
+// It can retun NULL and a caller has to implement its own fallback.
+const wchar_t* GetFontFamilyForScript(UScriptCode script,
+ GenericFamilyType generic);
+
+// Return a font family that can render |characters| based on
+// what script characters belong to.
+const wchar_t* GetFallbackFamily(const wchar_t *characters, int length,
+ GenericFamilyType generic);
+
+// Derive a new HFONT by replacing lfFaceName of LOGFONT with |family|,
+// calculate the ascent for the derived HFONT, and initialize SCRIPT_CACHE
+// in FontData.
+// |style| is only used for cache key generation. |style| is
+// bit-wise OR of BOLD(1), UNDERLINED(2) and ITALIC(4) and
+// should match what's contained in LOGFONT. It should be calculated
+// by calling GetStyleFromLogFont.
+// Returns false if the font is not accessible, in which case |ascent| field
+// of |fontdata| is set to kUndefinedAscent.
+// Be aware that this is not thread-safe.
+// TODO(jungshik): Instead of having three out params, we'd better have one
+// (|*FontData|), but somehow it mysteriously messes up the layout for
+// certain complex script pages (e.g. hi.wikipedia.org) and also crashes
+// at the start-up if recently visited page list includes pages with complex
+// scripts in their title. Moreover, somehow the very first-pass of
+// intl2 page-cycler test is noticeably slower with one out param than
+// the current version although the subsequent 9 passes take about the
+// same time.
+bool GetDerivedFontData(const wchar_t *family,
+ int style,
+ LOGFONT *logfont,
+ int *ascent,
+ HFONT *hfont,
+ SCRIPT_CACHE **script_cache);
+
+enum {
+ FONT_STYLE_NORMAL = 0,
+ FONT_STYLE_BOLD = 1,
+ FONT_STYLE_ITALIC = 2,
+ FONT_STYLE_UNDERLINED = 4
+};
+
+// Derive style (bit-wise OR of FONT_STYLE_BOLD, FONT_STYLE_UNDERLINED, and
+// FONT_STYLE_ITALIC) from LOGFONT. Returns 0 if |*logfont| is NULL.
+int GetStyleFromLogfont(const LOGFONT *logfont);
+
+} // namespace gfx
+
+#endif // BASE_GFX_FONT_UTILS_H__
diff --git a/base/gfx/image_operations.cc b/base/gfx/image_operations.cc
new file mode 100644
index 0000000..4dde08c
--- /dev/null
+++ b/base/gfx/image_operations.cc
@@ -0,0 +1,387 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+//
+#define _USE_MATH_DEFINES
+#include <cmath>
+#include <limits>
+#include <vector>
+
+#include "base/gfx/image_operations.h"
+
+#include "base/gfx/convolver.h"
+#include "base/gfx/rect.h"
+#include "base/gfx/size.h"
+#include "base/logging.h"
+#include "base/stack_container.h"
+#include "SkBitmap.h"
+
+namespace gfx {
+
+namespace {
+
+// Returns the ceiling/floor as an integer.
+inline int CeilInt(float val) {
+ return static_cast<int>(ceil(val));
+}
+inline int FloorInt(float val) {
+ return static_cast<int>(floor(val));
+}
+
+// Filter function computation -------------------------------------------------
+
+// Evaluates the box filter, which goes from -0.5 to +0.5.
+float EvalBox(float x) {
+ return (x >= -0.5f && x < 0.5f) ? 1.0f : 0.0f;
+}
+
+// Evaluates the Lanczos filter of the given filter size window for the given
+// position.
+//
+// |filter_size| is the width of the filter (the "window"), outside of which
+// the value of the function is 0. Inside of the window, the value is the
+// normalized sinc function:
+// lanczos(x) = sinc(x) * sinc(x / filter_size);
+// where
+// sinc(x) = sin(pi*x) / (pi*x);
+float EvalLanczos(int filter_size, float x) {
+ if (x <= -filter_size || x >= filter_size)
+ return 0.0f; // Outside of the window.
+ if (x > -std::numeric_limits<float>::epsilon() &&
+ x < std::numeric_limits<float>::epsilon())
+ return 1.0f; // Special case the discontinuity at the origin.
+ float xpi = x * static_cast<float>(M_PI);
+ return (sin(xpi) / xpi) * // sinc(x)
+ sin(xpi / filter_size) / (xpi / filter_size); // sinc(x/filter_size)
+}
+
+// ResizeFilter ----------------------------------------------------------------
+
+// Encapsulates computation and storage of the filters required for one complete
+// resize operation.
+class ResizeFilter {
+ public:
+ ResizeFilter(ImageOperations::ResizeMethod method,
+ const Size& src_full_size,
+ const Size& dest_size,
+ const Rect& dest_subset);
+
+ // Returns the bounds in the input bitmap of data that is used in the output.
+ // The filter offsets are within this rectangle.
+ const Rect& src_depend() { return src_depend_; }
+
+ // Returns the filled filter values.
+ const ConvolusionFilter1D& x_filter() { return x_filter_; }
+ const ConvolusionFilter1D& y_filter() { return y_filter_; }
+
+ private:
+ // Returns the number of pixels that the filer spans, in filter space (the
+ // destination image).
+ float GetFilterSupport(float scale) {
+ switch (method_) {
+ case ImageOperations::RESIZE_BOX:
+ // The box filter just scales with the image scaling.
+ return 0.5f; // Only want one side of the filter = /2.
+ case ImageOperations::RESIZE_LANCZOS3:
+ // The lanczos filter takes as much space in the source image in
+ // each direction as the size of the window = 3 for Lanczos3.
+ return 3.0f;
+ default:
+ NOTREACHED();
+ return 1.0f;
+ }
+ }
+
+ // Computes one set of filters either horizontally or vertically. The caller
+ // will specify the "min" and "max" rather than the bottom/top and
+ // right/bottom so that the same code can be re-used in each dimension.
+ //
+ // |src_depend_lo| and |src_depend_size| gives the range for the source
+ // depend rectangle (horizontally or vertically at the caller's discretion
+ // -- see above for what this means).
+ //
+ // Likewise, the range of destination values to compute and the scale factor
+ // for the transform is also specified.
+ void ComputeFilters(int src_size,
+ int dest_subset_lo, int dest_subset_size,
+ float scale, float src_support,
+ ConvolusionFilter1D* output);
+
+ // Computes the filter value given the coordinate in filter space.
+ inline float ComputeFilter(float pos) {
+ switch (method_) {
+ case ImageOperations::RESIZE_BOX:
+ return EvalBox(pos);
+ case ImageOperations::RESIZE_LANCZOS3:
+ return EvalLanczos(3, pos);
+ default:
+ NOTREACHED();
+ return 0;
+ }
+ }
+
+ ImageOperations::ResizeMethod method_;
+
+ // Subset of source the filters will touch.
+ Rect src_depend_;
+
+ // Size of the filter support on one side only in the destination space.
+ // See GetFilterSupport.
+ float x_filter_support_;
+ float y_filter_support_;
+
+ // Subset of scaled destination bitmap to compute.
+ Rect out_bounds_;
+
+ ConvolusionFilter1D x_filter_;
+ ConvolusionFilter1D y_filter_;
+
+ DISALLOW_EVIL_CONSTRUCTORS(ResizeFilter);
+};
+
+ResizeFilter::ResizeFilter(ImageOperations::ResizeMethod method,
+ const Size& src_full_size,
+ const Size& dest_size,
+ const Rect& dest_subset)
+ : method_(method),
+ out_bounds_(dest_subset) {
+ float scale_x = static_cast<float>(dest_size.width()) /
+ static_cast<float>(src_full_size.width());
+ float scale_y = static_cast<float>(dest_size.height()) /
+ static_cast<float>(src_full_size.height());
+
+ x_filter_support_ = GetFilterSupport(scale_x);
+ y_filter_support_ = GetFilterSupport(scale_y);
+
+ gfx::Rect src_full(0, 0, src_full_size.width(), src_full_size.height());
+ gfx::Rect dest_full(0, 0,
+ static_cast<int>(src_full_size.width() * scale_x + 0.5),
+ static_cast<int>(src_full_size.height() * scale_y + 0.5));
+
+ // Support of the filter in source space.
+ float src_x_support = x_filter_support_ / scale_x;
+ float src_y_support = y_filter_support_ / scale_y;
+
+ ComputeFilters(src_full_size.width(), dest_subset.x(), dest_subset.width(),
+ scale_x, src_x_support, &x_filter_);
+ ComputeFilters(src_full_size.height(), dest_subset.y(), dest_subset.height(),
+ scale_y, src_y_support, &y_filter_);
+}
+
+void ResizeFilter::ComputeFilters(int src_size,
+ int dest_subset_lo, int dest_subset_size,
+ float scale, float src_support,
+ ConvolusionFilter1D* output) {
+ int dest_subset_hi = dest_subset_lo + dest_subset_size; // [lo, hi)
+
+ // When we're doing a magnification, the scale will be larger than one. This
+ // means the destination pixels are much smaller than the source pixels, and
+ // that the range covered by the filter won't necessarily cover any source
+ // pixel boundaries. Therefore, we use these clamped values (max of 1) for
+ // some computations.
+ float clamped_src_support = std::min(1.0f, src_support);
+ float clamped_scale = std::min(1.0f, scale);
+
+ // Speed up the divisions below by turning them into multiplies.
+ float inv_scale = 1.0f / scale;
+
+ StackVector<float, 64> filter_values;
+ StackVector<int16, 64> fixed_filter_values;
+
+ // Loop over all pixels in the output range. We will generate one set of
+ // filter values for each one. Those values will tell us how to blend the
+ // source pixels to compute the destination pixel.
+ for (int dest_subset_i = dest_subset_lo; dest_subset_i < dest_subset_hi;
+ dest_subset_i++) {
+ // Reset the arrays. We don't declare them inside so they can re-use the
+ // same malloc-ed buffer.
+ filter_values->clear();
+ fixed_filter_values->clear();
+
+ // This is the pixel in the source directly under the pixel in the dest.
+ float src_pixel = dest_subset_i * inv_scale;
+
+ // Compute the (inclusive) range of source pixels the filter covers.
+ int src_begin = std::max(0, FloorInt(src_pixel - src_support));
+ int src_end = std::min(src_size - 1, CeilInt(src_pixel + src_support));
+
+ // Compute the unnormalized filter value at each location of the source
+ // it covers.
+ float filter_sum = 0.0f; // Sub of the filter values for normalizing.
+ for (int cur_filter_pixel = src_begin; cur_filter_pixel <= src_end;
+ cur_filter_pixel++) {
+ // Distance from the center of the filter, this is the filter coordinate
+ // in source space.
+ float src_filter_pos = cur_filter_pixel - src_pixel;
+
+ // Since the filter really exists in dest space, map it there.
+ float dest_filter_pos = src_filter_pos * clamped_scale;
+
+ // Compute the filter value at that location.
+ float filter_value = ComputeFilter(dest_filter_pos);
+ filter_values->push_back(filter_value);
+
+ filter_sum += filter_value;
+ }
+ DCHECK(!filter_values->empty()) << "We should always get a filter!";
+
+ // The filter must be normalized so that we don't affect the brightness of
+ // the image. Convert to normalized fixed point.
+ int16 fixed_sum = 0;
+ for (size_t i = 0; i < filter_values->size(); i++) {
+ int16 cur_fixed = output->FloatToFixed(filter_values[i] / filter_sum);
+ fixed_sum += cur_fixed;
+ fixed_filter_values->push_back(cur_fixed);
+ }
+
+ // The conversion to fixed point will leave some rounding errors, which
+ // we add back in to avoid affecting the brightness of the image. We
+ // arbitrarily add this to the center of the filter array (this won't always
+ // be the center of the filter function since it could get clipped on the
+ // edges, but it doesn't matter enough to worry about that case).
+ int16 leftovers = output->FloatToFixed(1.0f) - fixed_sum;
+ fixed_filter_values[fixed_filter_values->size() / 2] += leftovers;
+
+ // Now it's ready to go.
+ output->AddFilter(src_begin, &fixed_filter_values[0],
+ static_cast<int>(fixed_filter_values->size()));
+ }
+}
+
+} // namespace
+
+// Resize ----------------------------------------------------------------------
+
+// static
+SkBitmap ImageOperations::Resize(const SkBitmap& source,
+ ResizeMethod method,
+ const Size& dest_size,
+ const Rect& dest_subset) {
+ DCHECK(Rect(dest_size.width(), dest_size.height()).Contains(dest_subset)) <<
+ "The supplied subset does not fall within the destination image.";
+
+ // If the size of source or destination is 0, i.e. 0x0, 0xN or Nx0, just
+ // return empty
+ if (source.width() < 1 || source.height() < 1 ||
+ dest_size.width() < 1 || dest_size.height() < 1)
+ return SkBitmap();
+
+ SkAutoLockPixels locker(source);
+
+ ResizeFilter filter(method, Size(source.width(), source.height()),
+ dest_size, dest_subset);
+
+ // Get a source bitmap encompassing this touched area. We construct the
+ // offsets and row strides such that it looks like a new bitmap, while
+ // referring to the old data.
+ const uint8* source_subset =
+ reinterpret_cast<const uint8*>(source.getPixels());
+
+ // Convolve into the result.
+ SkBitmap result;
+ result.setConfig(SkBitmap::kARGB_8888_Config,
+ dest_subset.width(), dest_subset.height());
+ result.allocPixels();
+ BGRAConvolve2D(source_subset, static_cast<int>(source.rowBytes()),
+ !source.isOpaque(), filter.x_filter(), filter.y_filter(),
+ static_cast<unsigned char*>(result.getPixels()));
+
+ // Preserve the "opaque" flag for use as an optimization later.
+ result.setIsOpaque(source.isOpaque());
+
+ return result;
+}
+
+// static
+SkBitmap ImageOperations::Resize(const SkBitmap& source,
+ ResizeMethod method,
+ const Size& dest_size) {
+ Rect dest_subset(0, 0, dest_size.width(), dest_size.height());
+ return Resize(source, method, dest_size, dest_subset);
+}
+
+// static
+SkBitmap ImageOperations::CreateBlendedBitmap(const SkBitmap& first,
+ const SkBitmap& second,
+ double alpha) {
+ DCHECK(alpha <= 1 && alpha >= 0);
+ DCHECK(first.width() == second.width());
+ DCHECK(first.height() == second.height());
+ DCHECK(first.bytesPerPixel() == second.bytesPerPixel());
+ DCHECK(first.config() == SkBitmap::kARGB_8888_Config);
+
+ // Optimize for case where we won't need to blend anything.
+ static const double alpha_min = 1.0 / 255;
+ static const double alpha_max = 254.0 / 255;
+ if (alpha < alpha_min) {
+ return first;
+ } else if (alpha > alpha_max) {
+ return second;
+ }
+
+ SkAutoLockPixels lock_first(first);
+ SkAutoLockPixels lock_second(second);
+
+ SkBitmap blended;
+ blended.setConfig(SkBitmap::kARGB_8888_Config, first.width(),
+ first.height(), 0);
+ blended.allocPixels();
+ blended.eraseARGB(0, 0, 0, 0);
+
+ double first_alpha = 1 - alpha;
+
+ for (int y = 0; y < first.height(); y++) {
+ uint32* first_row = first.getAddr32(0, y);
+ uint32* second_row = second.getAddr32(0, y);
+ uint32* dst_row = blended.getAddr32(0, y);
+
+ for (int x = 0; x < first.width(); x++) {
+ uint32 first_pixel = first_row[x];
+ uint32 second_pixel = second_row[x];
+
+ int a = static_cast<int>(
+ SkColorGetA(first_pixel) * first_alpha +
+ SkColorGetA(second_pixel) * alpha);
+ int r = static_cast<int>(
+ SkColorGetR(first_pixel) * first_alpha +
+ SkColorGetR(second_pixel) * alpha);
+ int g = static_cast<int>(
+ SkColorGetG(first_pixel) * first_alpha +
+ SkColorGetG(second_pixel) * alpha);
+ int b = static_cast<int>(
+ SkColorGetB(first_pixel) * first_alpha +
+ SkColorGetB(second_pixel) * alpha);
+
+ dst_row[x] = SkColorSetARGB(a, r, g, b);
+ }
+ }
+
+ return blended;
+}
+
+} // namespace gfx
diff --git a/base/gfx/image_operations.h b/base/gfx/image_operations.h
new file mode 100644
index 0000000..5d3eeef
--- /dev/null
+++ b/base/gfx/image_operations.h
@@ -0,0 +1,87 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#ifndef BASE_GFX_IMAGE_OPERATIONS_H__
+#define BASE_GFX_IMAGE_OPERATIONS_H__
+
+#include "base/basictypes.h"
+#include "base/gfx/rect.h"
+
+class SkBitmap;
+
+namespace gfx {
+
+class ImageOperations {
+ public:
+ enum ResizeMethod {
+ // Box filter. This is a weighted average of all of the pixels touching
+ // the destination pixel. For enlargement, this is nearest neighbor.
+ //
+ // You probably don't want this, it is here for testing since it is easy to
+ // compute. Use RESIZE_LANCZOS3 instead.
+ RESIZE_BOX,
+
+ // 3-cycle Lanczos filter. This is tall in the middle, goes negative on
+ // each side, then oscillates 2 more times. It gives nice sharp edges.
+ RESIZE_LANCZOS3,
+ };
+
+ // Resizes the given source bitmap using the specified resize method, so that
+ // the entire image is (dest_size) big. The dest_subset is the rectangle in
+ // this destination image that should actually be returned.
+ //
+ // The output image will be (dest_subset.width(), dest_subset.height()). This
+ // will save work if you do not need the entire bitmap.
+ //
+ // The destination subset must be smaller than the destination image.
+ static SkBitmap Resize(const SkBitmap& source,
+ ResizeMethod method,
+ const Size& dest_size,
+ const Rect& dest_subset);
+
+ // Alternate version for resizing and returning the entire bitmap rather than
+ // a subset.
+ static SkBitmap Resize(const SkBitmap& source,
+ ResizeMethod method,
+ const Size& dest_size);
+
+
+ // Create a bitmap that is a blend of two others. The alpha argument
+ // specifies the opacity of the second bitmap. The provided bitmaps must
+ // use have the kARGB_8888_Config config and be of equal dimensions.
+ static SkBitmap CreateBlendedBitmap(const SkBitmap& first,
+ const SkBitmap& second,
+ double alpha);
+ private:
+ ImageOperations(); // Class for scoping only.
+};
+
+} // namespace gfx
+
+#endif // BASE_GFX_IMAGE_OPERATIONS_H__
diff --git a/base/gfx/image_operations_unittest.cc b/base/gfx/image_operations_unittest.cc
new file mode 100644
index 0000000..1c2b40f
--- /dev/null
+++ b/base/gfx/image_operations_unittest.cc
@@ -0,0 +1,172 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include <stdlib.h>
+
+#include "base/gfx/image_operations.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "SkBitmap.h"
+
+namespace {
+
+// Computes the average pixel value for the given range, inclusive.
+uint32_t AveragePixel(const SkBitmap& bmp,
+ int x_min, int x_max,
+ int y_min, int y_max) {
+ float accum[4] = {0, 0, 0, 0};
+ int count = 0;
+ for (int y = y_min; y <= y_max; y++) {
+ for (int x = x_min; x <= x_max; x++) {
+ uint32_t cur = *bmp.getAddr32(x, y);
+ accum[0] += SkColorGetB(cur);
+ accum[1] += SkColorGetG(cur);
+ accum[2] += SkColorGetR(cur);
+ accum[3] += SkColorGetA(cur);
+ count++;
+ }
+ }
+
+ return SkColorSetARGB(static_cast<unsigned char>(accum[3] / count),
+ static_cast<unsigned char>(accum[2] / count),
+ static_cast<unsigned char>(accum[1] / count),
+ static_cast<unsigned char>(accum[0] / count));
+}
+
+// Returns true if each channel of the given two colors are "close." This is
+// used for comparing colors where rounding errors may cause off-by-one.
+bool ColorsClose(uint32_t a, uint32_t b) {
+ return abs(static_cast<int>(SkColorGetB(a) - SkColorGetB(b))) < 2 &&
+ abs(static_cast<int>(SkColorGetG(a) - SkColorGetG(b))) < 2 &&
+ abs(static_cast<int>(SkColorGetR(a) - SkColorGetR(b))) < 2 &&
+ abs(static_cast<int>(SkColorGetA(a) - SkColorGetA(b))) < 2;
+}
+
+void FillDataToBitmap(int w, int h, SkBitmap* bmp) {
+ bmp->setConfig(SkBitmap::kARGB_8888_Config, w, h);
+ bmp->allocPixels();
+
+ unsigned char* src_data =
+ reinterpret_cast<unsigned char*>(bmp->getAddr32(0, 0));
+ for (int i = 0; i < w * h; i++) {
+ src_data[i * 4 + 0] = static_cast<unsigned char>(i % 255);
+ src_data[i * 4 + 1] = static_cast<unsigned char>(i % 255);
+ src_data[i * 4 + 2] = static_cast<unsigned char>(i % 255);
+ src_data[i * 4 + 3] = static_cast<unsigned char>(i % 255);
+ }
+}
+
+} // namespace
+
+// Makes the bitmap 50% the size as the original using a box filter. This is
+// an easy operation that we can check the results for manually.
+TEST(ImageOperations, Halve) {
+ // Make our source bitmap.
+ int src_w = 30, src_h = 38;
+ SkBitmap src;
+ FillDataToBitmap(src_w, src_h, &src);
+
+ // Do a halving of the full bitmap.
+ SkBitmap actual_results = gfx::ImageOperations::Resize(
+ src, gfx::ImageOperations::RESIZE_BOX, gfx::Size(src_w / 2, src_h / 2));
+ ASSERT_EQ(src_w / 2, actual_results.width());
+ ASSERT_EQ(src_h / 2, actual_results.height());
+
+ // Compute the expected values & compare.
+ SkAutoLockPixels lock(actual_results);
+ for (int y = 0; y < actual_results.height(); y++) {
+ for (int x = 0; x < actual_results.width(); x++) {
+ int first_x = std::max(0, x * 2 - 1);
+ int last_x = std::min(src_w - 1, x * 2);
+
+ int first_y = std::max(0, y * 2 - 1);
+ int last_y = std::min(src_h - 1, y * 2);
+
+ uint32_t expected_color = AveragePixel(src,
+ first_x, last_x, first_y, last_y);
+ EXPECT_TRUE(ColorsClose(expected_color, *actual_results.getAddr32(x, y)));
+ }
+ }
+}
+
+TEST(ImageOperations, HalveSubset) {
+ // Make our source bitmap.
+ int src_w = 16, src_h = 34;
+ SkBitmap src;
+ FillDataToBitmap(src_w, src_h, &src);
+
+ // Do a halving of the full bitmap.
+ SkBitmap full_results = gfx::ImageOperations::Resize(
+ src, gfx::ImageOperations::RESIZE_BOX, gfx::Size(src_w / 2, src_h / 2));
+ ASSERT_EQ(src_w / 2, full_results.width());
+ ASSERT_EQ(src_h / 2, full_results.height());
+
+ // Now do a halving of a a subset, recall the destination subset is in the
+ // destination coordinate system (max = half of the original image size).
+ gfx::Rect subset_rect(2, 3, 3, 6);
+ SkBitmap subset_results = gfx::ImageOperations::Resize(
+ src, gfx::ImageOperations::RESIZE_BOX,
+ gfx::Size(src_w / 2, src_h / 2), subset_rect);
+ ASSERT_EQ(subset_rect.width(), subset_results.width());
+ ASSERT_EQ(subset_rect.height(), subset_results.height());
+
+ // The computed subset and the corresponding subset of the original image
+ // should be the same.
+ SkAutoLockPixels full_lock(full_results);
+ SkAutoLockPixels subset_lock(subset_results);
+ for (int y = 0; y < subset_rect.height(); y++) {
+ for (int x = 0; x < subset_rect.width(); x++) {
+ ASSERT_EQ(
+ *full_results.getAddr32(x + subset_rect.x(), y + subset_rect.y()),
+ *subset_results.getAddr32(x, y));
+ }
+ }
+}
+
+// Resamples an iamge to the same image, it should give almost the same result.
+TEST(ImageOperations, ResampleToSame) {
+ // Make our source bitmap.
+ int src_w = 16, src_h = 34;
+ SkBitmap src;
+ FillDataToBitmap(src_w, src_h, &src);
+
+ // Do a resize of the full bitmap to the same size. The lanczos filter is good
+ // enough that we should get exactly the same image for output.
+ SkBitmap results = gfx::ImageOperations::Resize(
+ src, gfx::ImageOperations::RESIZE_LANCZOS3, gfx::Size(src_w, src_h));
+ ASSERT_EQ(src_w, results.width());
+ ASSERT_EQ(src_h, results.height());
+
+ SkAutoLockPixels src_lock(src);
+ SkAutoLockPixels results_lock(results);
+ for (int y = 0; y < src_h; y++) {
+ for (int x = 0; x < src_w; x++) {
+ EXPECT_EQ(*src.getAddr32(x, y), *results.getAddr32(x, y));
+ }
+ }
+} \ No newline at end of file
diff --git a/base/gfx/img_resize_perftest.cc b/base/gfx/img_resize_perftest.cc
new file mode 100644
index 0000000..43f8deb
--- /dev/null
+++ b/base/gfx/img_resize_perftest.cc
@@ -0,0 +1,94 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include <stdlib.h>
+#include <time.h>
+
+#include "base/perftimer.h"
+#include "base/gfx/convolver.h"
+#include "base/gfx/image_operations.h"
+#include "base/gfx/image_resizer.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace {
+
+void FillRandomData(char* dest, int byte_count) {
+ srand(static_cast<unsigned>(time(NULL)));
+ for (int i = 0; i < byte_count; i++)
+ dest[i] = rand() * 255 / RAND_MAX;
+}
+
+} // namespace
+
+// Old code gives [1521, 1519]ms for this, 4000x4000 -> 2100x2100 lanczos8
+
+TEST(ImageResizePerf, BigFilter) {
+ static const int kSrcWidth = 4000;
+ static const int kSrcHeight = 4000;
+ static const int kSrcByteSize = kSrcWidth * kSrcHeight * 4;
+
+ SkBitmap src_bmp;
+ src_bmp.setConfig(SkBitmap::kARGB_8888_Config, kSrcWidth, kSrcHeight);
+ src_bmp.allocPixels();
+ FillRandomData(reinterpret_cast<char*>(src_bmp.getAddr32(0, 0)),
+ kSrcByteSize);
+
+ // Make the dest size > 1/2 so the 50% optimization doesn't kick in.
+ static const int kDestWidth = 1400;
+ static const int kDestHeight = 1400;
+
+ PerfTimeLogger resize_timer("resize");
+ gfx::ImageResizer resizer(gfx::ImageResizer::LANCZOS3);
+ SkBitmap dest = resizer.Resize(src_bmp, kDestWidth, kDestHeight);
+}
+
+// The original image filter we were using took 523ms for this test, while this
+// one takes 857ms.
+// TODO(brettw) make this at least 64% faster.
+TEST(ImageOperationPerf, BigFilter) {
+ static const int kSrcWidth = 4000;
+ static const int kSrcHeight = 4000;
+ static const int kSrcByteSize = kSrcWidth * kSrcHeight * 4;
+
+ SkBitmap src_bmp;
+ src_bmp.setConfig(SkBitmap::kARGB_8888_Config, kSrcWidth, kSrcHeight);
+ src_bmp.allocPixels();
+ src_bmp.setIsOpaque(true);
+ FillRandomData(reinterpret_cast<char*>(src_bmp.getAddr32(0, 0)),
+ kSrcByteSize);
+
+ // Make the dest size > 1/2 so the 50% optimization doesn't kick in.
+ static const int kDestWidth = 1400;
+ static const int kDestHeight = 1400;
+
+ PerfTimeLogger resize_timer("resize");
+ SkBitmap dest = gfx::ImageOperations::Resize(src_bmp,
+ gfx::ImageOperations::RESIZE_LANCZOS3, (float)kDestWidth / (float)kSrcWidth,
+ (float)kDestHeight / (float)kSrcHeight);
+}
diff --git a/base/gfx/native_theme.cc b/base/gfx/native_theme.cc
new file mode 100644
index 0000000..8ab4ca3
--- /dev/null
+++ b/base/gfx/native_theme.cc
@@ -0,0 +1,626 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "base/gfx/native_theme.h"
+
+#include <windows.h>
+#include <uxtheme.h>
+#include <vsstyle.h>
+#include <vssym32.h>
+
+#include "base/gfx/bitmap_header.h"
+#include "base/gfx/platform_canvas.h"
+#include "base/gfx/skia_utils.h"
+#include "base/gfx/rect.h"
+#include "base/logging.h"
+#include "base/scoped_handle.h"
+#include "skia/include/SkShader.h"
+
+namespace gfx {
+
+/* static */
+const NativeTheme* NativeTheme::instance() {
+ // The global NativeTheme instance.
+ static const NativeTheme s_native_theme;
+ return &s_native_theme;
+}
+
+NativeTheme::NativeTheme()
+ : theme_dll_(LoadLibrary(L"uxtheme.dll")),
+ draw_theme_(NULL),
+ draw_theme_ex_(NULL),
+ get_theme_color_(NULL),
+ get_theme_content_rect_(NULL),
+ get_theme_part_size_(NULL),
+ open_theme_(NULL),
+ close_theme_(NULL),
+ set_theme_properties_(NULL),
+ is_theme_active_(NULL),
+ get_theme_int_(NULL) {
+ if (theme_dll_) {
+ draw_theme_ = reinterpret_cast<DrawThemeBackgroundPtr>(
+ GetProcAddress(theme_dll_, "DrawThemeBackground"));
+ draw_theme_ex_ = reinterpret_cast<DrawThemeBackgroundExPtr>(
+ GetProcAddress(theme_dll_, "DrawThemeBackgroundEx"));
+ get_theme_color_ = reinterpret_cast<GetThemeColorPtr>(
+ GetProcAddress(theme_dll_, "GetThemeColor"));
+ get_theme_content_rect_ = reinterpret_cast<GetThemeContentRectPtr>(
+ GetProcAddress(theme_dll_, "GetThemeBackgroundContentRect"));
+ get_theme_part_size_ = reinterpret_cast<GetThemePartSizePtr>(
+ GetProcAddress(theme_dll_, "GetThemePartSize"));
+ open_theme_ = reinterpret_cast<OpenThemeDataPtr>(
+ GetProcAddress(theme_dll_, "OpenThemeData"));
+ close_theme_ = reinterpret_cast<CloseThemeDataPtr>(
+ GetProcAddress(theme_dll_, "CloseThemeData"));
+ set_theme_properties_ = reinterpret_cast<SetThemeAppPropertiesPtr>(
+ GetProcAddress(theme_dll_, "SetThemeAppProperties"));
+ is_theme_active_ = reinterpret_cast<IsThemeActivePtr>(
+ GetProcAddress(theme_dll_, "IsThemeActive"));
+ get_theme_int_ = reinterpret_cast<GetThemeIntPtr>(
+ GetProcAddress(theme_dll_, "GetThemeInt"));
+ }
+ memset(theme_handles_, 0, sizeof(theme_handles_));
+}
+
+NativeTheme::~NativeTheme() {
+ if (theme_dll_) {
+ CloseHandles();
+ FreeLibrary(theme_dll_);
+ }
+}
+
+HRESULT NativeTheme::PaintButton(HDC hdc,
+ int part_id,
+ int state_id,
+ int classic_state,
+ RECT* rect) const {
+ HANDLE handle = GetThemeHandle(BUTTON);
+ if (handle && draw_theme_)
+ return draw_theme_(handle, hdc, part_id, state_id, rect, NULL);
+
+ // Draw it manually.
+ // All pressed states have both low bits set, and no other states do.
+ const bool focused = ((state_id & PBS_PRESSED) == PBS_PRESSED);
+ if ((part_id == BP_PUSHBUTTON) && focused) {
+ // BP_PUSHBUTTON has a focus rect drawn around the outer edge, and the
+ // button itself is shrunk by 1 pixel.
+ HBRUSH brush = GetSysColorBrush(COLOR_3DDKSHADOW);
+ if (brush) {
+ FrameRect(hdc, rect, brush);
+ InflateRect(rect, -1, -1);
+ }
+ }
+
+ DrawFrameControl(hdc, rect, DFC_BUTTON, classic_state);
+
+ // BP_RADIOBUTTON, BP_CHECKBOX, BP_GROUPBOX and BP_USERBUTTON have their
+ // focus drawn over the control.
+ if ((part_id != BP_PUSHBUTTON) && focused)
+ DrawFocusRect(hdc, rect);
+
+ return S_OK;
+}
+
+HRESULT NativeTheme::PaintTextField(HDC hdc,
+ int part_id,
+ int state_id,
+ int classic_state,
+ RECT* rect,
+ COLORREF color,
+ bool fill_content_area,
+ bool draw_edges) const {
+ // TODO(ojan): http://b/1210017 Figure out how to give the ability to
+ // exclude individual edges from being drawn.
+
+ HANDLE handle = GetThemeHandle(TEXTFIELD);
+ // TODO(mpcomplete): can we detect if the color is specified by the user,
+ // and if not, just use the system color?
+ // CreateSolidBrush() accepts a RGB value but alpha must be 0.
+ HBRUSH bg_brush = CreateSolidBrush(color);
+ HRESULT hr;
+ // DrawThemeBackgroundEx was introduced in XP SP2, so that it's possible
+ // draw_theme_ex_ is NULL and draw_theme_ is non-null.
+ if (handle && (draw_theme_ex_ || (draw_theme_ && draw_edges))) {
+ if (draw_theme_ex_) {
+ static DTBGOPTS omit_border_options = {
+ sizeof(DTBGOPTS),
+ DTBG_OMITBORDER,
+ {0,0,0,0}
+ };
+ DTBGOPTS* draw_opts = draw_edges ? NULL : &omit_border_options;
+ hr = draw_theme_ex_(handle, hdc, part_id, state_id, rect, draw_opts);
+ } else {
+ hr = draw_theme_(handle, hdc, part_id, state_id, rect, NULL);
+ }
+
+ // TODO(maruel): Need to be fixed if get_theme_content_rect_ is NULL.
+ if (fill_content_area && get_theme_content_rect_) {
+ RECT content_rect;
+ hr = get_theme_content_rect_(handle, hdc, part_id, state_id, rect,
+ &content_rect);
+ FillRect(hdc, &content_rect, bg_brush);
+ }
+ } else {
+ // Draw it manually.
+ if (draw_edges)
+ DrawEdge(hdc, rect, EDGE_SUNKEN, BF_RECT | BF_ADJUST);
+
+ if (fill_content_area) {
+ FillRect(hdc, rect, (classic_state & DFCS_INACTIVE) ?
+ reinterpret_cast<HBRUSH>(COLOR_BTNFACE + 1) : bg_brush);
+ }
+ hr = S_OK;
+ }
+ DeleteObject(bg_brush);
+ return hr;
+}
+
+HRESULT NativeTheme::PaintMenuList(HDC hdc,
+ int part_id,
+ int state_id,
+ int classic_state,
+ RECT* rect) const {
+ HANDLE handle = GetThemeHandle(MENULIST);
+ if (handle && draw_theme_)
+ return draw_theme_(handle, hdc, part_id, state_id, rect, NULL);
+
+ // Draw it manually.
+ DrawFrameControl(hdc, rect, DFC_SCROLL, DFCS_SCROLLCOMBOBOX | classic_state);
+ return S_OK;
+}
+
+HRESULT NativeTheme::PaintScrollbarArrow(HDC hdc,
+ int state_id,
+ int classic_state,
+ RECT* rect) const {
+ HANDLE handle = GetThemeHandle(SCROLLBAR);
+ if (handle && draw_theme_)
+ return draw_theme_(handle, hdc, SBP_ARROWBTN, state_id, rect, NULL);
+
+ // Draw it manually.
+ DrawFrameControl(hdc, rect, DFC_SCROLL, classic_state);
+ return S_OK;
+}
+
+HRESULT NativeTheme::PaintScrollbarTrack(HDC hdc,
+ int part_id,
+ int state_id,
+ int classic_state,
+ RECT* target_rect,
+ RECT* align_rect,
+ PlatformCanvas* canvas) const {
+ HANDLE handle = GetThemeHandle(SCROLLBAR);
+ if (handle && draw_theme_)
+ return draw_theme_(handle, hdc, part_id, state_id, target_rect, NULL);
+
+ // Draw it manually.
+ const DWORD colorScrollbar = GetSysColor(COLOR_SCROLLBAR);
+ const DWORD color3DFace = GetSysColor(COLOR_3DFACE);
+ if ((colorScrollbar != color3DFace) &&
+ (colorScrollbar != GetSysColor(COLOR_WINDOW))) {
+ FillRect(hdc, target_rect, reinterpret_cast<HBRUSH>(COLOR_SCROLLBAR + 1));
+ } else {
+ // Create a 2x2 checkerboard pattern using the 3D face and highlight
+ // colors.
+ SkColor face = COLORREFToSkColor(color3DFace);
+ SkColor highlight = COLORREFToSkColor(GetSysColor(COLOR_3DHILIGHT));
+ SkColor buffer[] = { face, highlight, highlight, face };
+ SkBitmap bitmap;
+ bitmap.setConfig(SkBitmap::kARGB_8888_Config, 2, 2);
+ bitmap.setPixels(buffer);
+ SkShader* shader = SkShader::CreateBitmapShader(bitmap,
+ SkShader::kRepeat_TileMode,
+ SkShader::kRepeat_TileMode);
+
+ // Draw that pattern into the target rect, setting the origin to the top
+ // left corner of the scrollbar track (so the checked rect below the thumb
+ // aligns properly with the portion above the thumb).
+ SkMatrix matrix;
+ matrix.setTranslate(SkIntToScalar(align_rect->left),
+ SkIntToScalar(align_rect->top));
+ shader->setLocalMatrix(matrix);
+ SkPaint paint;
+ paint.setShader(shader)->unref();
+ canvas->drawIRect(RECTToSkIRect(*target_rect), paint);
+ }
+ if (classic_state & DFCS_PUSHED)
+ InvertRect(hdc, target_rect);
+ return S_OK;
+}
+
+HRESULT NativeTheme::PaintScrollbarThumb(HDC hdc,
+ int part_id,
+ int state_id,
+ int classic_state,
+ RECT* rect) const {
+ HANDLE handle = GetThemeHandle(SCROLLBAR);
+ if (handle && draw_theme_)
+ return draw_theme_(handle, hdc, part_id, state_id, rect, NULL);
+
+ // Draw it manually.
+ if ((part_id == SBP_THUMBBTNHORZ) || (part_id == SBP_THUMBBTNVERT))
+ DrawEdge(hdc, rect, EDGE_RAISED, BF_RECT | BF_MIDDLE);
+ // Classic mode doesn't have a gripper.
+ return S_OK;
+}
+
+HRESULT NativeTheme::PaintStatusGripper(HDC hdc,
+ int part_id,
+ int state_id,
+ int classic_state,
+ RECT* rect) const {
+ HANDLE handle = GetThemeHandle(STATUS);
+ if (handle && draw_theme_) {
+ // Paint the status bar gripper. There doesn't seem to be a
+ // standard gripper in Windows for the space between
+ // scrollbars. This is pretty close, but it's supposed to be
+ // painted over a status bar.
+ return draw_theme_(handle, hdc, SP_GRIPPER, 0, rect, 0);
+ }
+
+ // Draw a windows classic scrollbar gripper.
+ DrawFrameControl(hdc, rect, DFC_SCROLL, DFCS_SCROLLSIZEGRIP);
+ return S_OK;
+}
+
+HRESULT NativeTheme::PaintDialogBackground(HDC hdc, bool active,
+ RECT* rect) const {
+ HANDLE handle = GetThemeHandle(WINDOW);
+ if (handle && draw_theme_) {
+ return draw_theme_(handle, hdc, WP_DIALOG,
+ active ? FS_ACTIVE : FS_INACTIVE, rect, NULL);
+ }
+
+ // Classic just renders a flat color background.
+ FillRect(hdc, rect, reinterpret_cast<HBRUSH>(COLOR_3DFACE + 1));
+ return S_OK;
+}
+
+HRESULT NativeTheme::PaintTabPanelBackground(HDC hdc, RECT* rect) const {
+ HANDLE handle = GetThemeHandle(TAB);
+ if (handle && draw_theme_)
+ return draw_theme_(handle, hdc, TABP_BODY, 0, rect, NULL);
+
+ // Classic just renders a flat color background.
+ FillRect(hdc, rect, reinterpret_cast<HBRUSH>(COLOR_3DFACE + 1));
+ return S_OK;
+}
+
+HRESULT NativeTheme::PaintListBackground(HDC hdc,
+ bool enabled,
+ RECT* rect) const {
+ HANDLE handle = GetThemeHandle(LIST);
+ if (handle && draw_theme_)
+ return draw_theme_(handle, hdc, 1, TS_NORMAL, rect, NULL);
+
+ // Draw it manually.
+ HBRUSH bg_brush = GetSysColorBrush(COLOR_WINDOW);
+ FillRect(hdc, rect, bg_brush);
+ DrawEdge(hdc, rect, EDGE_SUNKEN, BF_RECT | BF_ADJUST);
+ return S_OK;
+}
+
+bool NativeTheme::IsThemingActive() const {
+ if (is_theme_active_)
+ return !!is_theme_active_();
+ return false;
+}
+
+HRESULT NativeTheme::PaintMenuArrow(ThemeName theme,
+ HDC hdc,
+ int part_id,
+ int state_id,
+ RECT* rect,
+ MenuArrowDirection arrow_direction,
+ bool is_highlighted) const {
+ HANDLE handle = GetThemeHandle(MENU);
+ if (handle && draw_theme_) {
+ if (arrow_direction == RIGHT_POINTING_ARROW) {
+ return draw_theme_(handle, hdc, part_id, state_id, rect, NULL);
+ } else {
+ // There is no way to tell the uxtheme API to draw a left pointing arrow;
+ // it doesn't have a flag equivalent to DFCS_MENUARROWRIGHT. But they
+ // are needed for RTL locales on Vista. So use a memory DC and mirror
+ // the region with GDI's StretchBlt.
+ Rect r(*rect);
+ ScopedHDC mem_dc(CreateCompatibleDC(hdc));
+ ScopedBitmap mem_bitmap(CreateCompatibleBitmap(hdc, r.width(),
+ r.height()));
+ HGDIOBJ old_bitmap = SelectObject(mem_dc, mem_bitmap);
+ // Copy and horizontally mirror the background from hdc into mem_dc. Use
+ // a negative-width source rect, starting at the rightmost pixel.
+ StretchBlt(mem_dc, 0, 0, r.width(), r.height(),
+ hdc, r.right()-1, r.y(), -r.width(), r.height(), SRCCOPY);
+ // Draw the arrow.
+ RECT theme_rect = {0, 0, r.width(), r.height()};
+ HRESULT result = draw_theme_(handle, mem_dc, part_id,
+ state_id, &theme_rect, NULL);
+ // Copy and mirror the result back into mem_dc.
+ StretchBlt(hdc, r.x(), r.y(), r.width(), r.height(),
+ mem_dc, r.width()-1, 0, -r.width(), r.height(), SRCCOPY);
+ SelectObject(mem_dc, old_bitmap);
+ return result;
+ }
+ }
+
+ // For some reason, Windows uses the name DFCS_MENUARROWRIGHT to indicate a
+ // left pointing arrow. This makes the following 'if' statement slightly
+ // counterintuitive.
+ UINT state;
+ if (arrow_direction == RIGHT_POINTING_ARROW)
+ state = DFCS_MENUARROW;
+ else
+ state = DFCS_MENUARROWRIGHT;
+ return PaintFrameControl(hdc, rect, DFC_MENU, state, is_highlighted);
+}
+
+HRESULT NativeTheme::PaintMenuBackground(ThemeName theme,
+ HDC hdc,
+ int part_id,
+ int state_id,
+ RECT* rect) const {
+ HANDLE handle = GetThemeHandle(MENU);
+ if (handle && draw_theme_) {
+ HRESULT result = draw_theme_(handle, hdc, part_id, state_id, rect, NULL);
+ FrameRect(hdc, rect, GetSysColorBrush(COLOR_3DSHADOW));
+ return result;
+ }
+
+ FillRect(hdc, rect, GetSysColorBrush(COLOR_MENU));
+ DrawEdge(hdc, rect, EDGE_RAISED, BF_RECT);
+ return S_OK;
+}
+
+HRESULT NativeTheme::PaintMenuCheckBackground(ThemeName theme,
+ HDC hdc,
+ int part_id,
+ int state_id,
+ RECT* rect) const {
+ HANDLE handle = GetThemeHandle(MENU);
+ if (handle && draw_theme_)
+ return draw_theme_(handle, hdc, part_id, state_id, rect, NULL);
+ // Nothing to do for background.
+ return S_OK;
+}
+
+HRESULT NativeTheme::PaintMenuCheck(ThemeName theme,
+ HDC hdc,
+ int part_id,
+ int state_id,
+ RECT* rect,
+ bool is_highlighted) const {
+ HANDLE handle = GetThemeHandle(MENU);
+ if (handle && draw_theme_) {
+ return draw_theme_(handle, hdc, part_id, state_id, rect, NULL);
+ }
+ return PaintFrameControl(hdc, rect, DFC_MENU, DFCS_MENUCHECK, is_highlighted);
+}
+
+HRESULT NativeTheme::PaintMenuGutter(HDC hdc,
+ int part_id,
+ int state_id,
+ RECT* rect) const {
+ HANDLE handle = GetThemeHandle(MENU);
+ if (handle && draw_theme_)
+ return draw_theme_(handle, hdc, part_id, state_id, rect, NULL);
+ return E_NOTIMPL;
+}
+
+HRESULT NativeTheme::PaintMenuSeparator(HDC hdc,
+ int part_id,
+ int state_id,
+ RECT* rect) const {
+ HANDLE handle = GetThemeHandle(MENU);
+ if (handle && draw_theme_)
+ return draw_theme_(handle, hdc, part_id, state_id, rect, NULL);
+ DrawEdge(hdc, rect, EDGE_ETCHED, BF_TOP);
+ return S_OK;
+}
+
+HRESULT NativeTheme::PaintMenuItemBackground(ThemeName theme,
+ HDC hdc,
+ int part_id,
+ int state_id,
+ bool selected,
+ RECT* rect) const {
+ HANDLE handle = GetThemeHandle(MENU);
+ if (handle && draw_theme_)
+ return draw_theme_(handle, hdc, part_id, state_id, rect, NULL);
+ if (selected)
+ FillRect(hdc, rect, GetSysColorBrush(COLOR_HIGHLIGHT));
+ return S_OK;
+}
+
+HRESULT NativeTheme::GetThemePartSize(ThemeName theme_name,
+ HDC hdc,
+ int part_id,
+ int state_id,
+ RECT* rect,
+ int ts,
+ SIZE* size) const {
+ HANDLE handle = GetThemeHandle(theme_name);
+ if (handle && get_theme_part_size_)
+ return get_theme_part_size_(handle, hdc, part_id, state_id, rect, ts, size);
+
+ return E_NOTIMPL;
+}
+
+HRESULT NativeTheme::GetThemeColor(ThemeName theme,
+ int part_id,
+ int state_id,
+ int prop_id,
+ SkColor* color) const {
+ HANDLE handle = GetThemeHandle(theme);
+ if (handle && get_theme_color_) {
+ COLORREF color_ref;
+ if (get_theme_color_(handle, part_id, state_id, prop_id, &color_ref) ==
+ S_OK) {
+ *color = gfx::COLORREFToSkColor(color_ref);
+ return S_OK;
+ }
+ }
+ return E_NOTIMPL;
+}
+
+SkColor NativeTheme::GetThemeColorWithDefault(ThemeName theme,
+ int part_id,
+ int state_id,
+ int prop_id,
+ int default_sys_color) const {
+ SkColor color;
+ if (GetThemeColor(theme, part_id, state_id, prop_id, &color) != S_OK)
+ color = gfx::COLORREFToSkColor(GetSysColor(default_sys_color));
+ return color;
+}
+
+HRESULT NativeTheme::GetThemeInt(ThemeName theme,
+ int part_id,
+ int state_id,
+ int prop_id,
+ int *value) const {
+ HANDLE handle = GetThemeHandle(theme);
+ if (handle && get_theme_int_)
+ return get_theme_int_(handle, part_id, state_id, prop_id, value);
+ return E_NOTIMPL;
+}
+
+Size NativeTheme::GetThemeBorderSize(ThemeName theme) const {
+ // For simplicity use the wildcard state==0, part==0, since it works
+ // for the cases we currently depend on.
+ int border;
+ if (GetThemeInt(theme, 0, 0, TMT_BORDERSIZE, &border) == S_OK)
+ return Size(border, border);
+ else
+ return Size(GetSystemMetrics(SM_CXEDGE), GetSystemMetrics(SM_CYEDGE));
+}
+
+
+void NativeTheme::DisableTheming() const {
+ if (!set_theme_properties_)
+ return;
+ set_theme_properties_(0);
+}
+
+HRESULT NativeTheme::PaintFrameControl(HDC hdc,
+ RECT* rect,
+ UINT type,
+ UINT state,
+ bool is_highlighted) const {
+ const int width = rect->right - rect->left;
+ const int height = rect->bottom - rect->top;
+
+ // DrawFrameControl for menu arrow/check wants a monochrome bitmap.
+ ScopedBitmap mask_bitmap(CreateBitmap(width, height, 1, 1, NULL));
+
+ if (mask_bitmap == NULL)
+ return E_OUTOFMEMORY;
+
+ ScopedHDC bitmap_dc(CreateCompatibleDC(NULL));
+ HGDIOBJ org_bitmap = SelectObject(bitmap_dc, mask_bitmap);
+ RECT local_rect = { 0, 0, width, height };
+ DrawFrameControl(bitmap_dc, &local_rect, type, state);
+
+ // We're going to use BitBlt with a b&w mask. This results in using the dest
+ // dc's text color for the black bits in the mask, and the dest dc's
+ // background color for the white bits in the mask. DrawFrameControl draws the
+ // check in black, and the background in white.
+ COLORREF old_bg_color =
+ SetBkColor(hdc,
+ GetSysColor(is_highlighted ? COLOR_HIGHLIGHT : COLOR_MENU));
+ COLORREF old_text_color =
+ SetTextColor(hdc,
+ GetSysColor(is_highlighted ? COLOR_HIGHLIGHTTEXT :
+ COLOR_MENUTEXT));
+ BitBlt(hdc, rect->left, rect->top, width, height, bitmap_dc, 0, 0, SRCCOPY);
+ SetBkColor(hdc, old_bg_color);
+ SetTextColor(hdc, old_text_color);
+
+ SelectObject(bitmap_dc, org_bitmap);
+
+ return S_OK;
+}
+
+void NativeTheme::CloseHandles() const
+{
+ if (!close_theme_)
+ return;
+
+ for (int i = 0; i < LAST; ++i) {
+ if (theme_handles_[i])
+ close_theme_(theme_handles_[i]);
+ theme_handles_[i] = NULL;
+ }
+}
+
+HANDLE NativeTheme::GetThemeHandle(ThemeName theme_name) const
+{
+ if (!open_theme_ || theme_name < 0 || theme_name >= LAST)
+ return 0;
+
+ if (theme_handles_[theme_name])
+ return theme_handles_[theme_name];
+
+ // Not found, try to load it.
+ HANDLE handle = 0;
+ switch (theme_name) {
+ case NativeTheme::BUTTON:
+ handle = open_theme_(NULL, L"Button");
+ break;
+ case NativeTheme::TEXTFIELD:
+ handle = open_theme_(NULL, L"Edit");
+ break;
+ case NativeTheme::MENULIST:
+ handle = open_theme_(NULL, L"Combobox");
+ break;
+ case NativeTheme::SCROLLBAR:
+ handle = open_theme_(NULL, L"Scrollbar");
+ break;
+ case NativeTheme::STATUS:
+ handle = open_theme_(NULL, L"Status");
+ break;
+ case NativeTheme::MENU:
+ handle = open_theme_(NULL, L"Menu");
+ break;
+ case NativeTheme::WINDOW:
+ handle = open_theme_(NULL, L"Window");
+ break;
+ case NativeTheme::TAB:
+ handle = open_theme_(NULL, L"Tab");
+ break;
+ case NativeTheme::LIST:
+ handle = open_theme_(NULL, L"Listview");
+ break;
+ default:
+ NOTREACHED();
+ }
+ theme_handles_[theme_name] = handle;
+ return handle;
+}
+
+} // namespace gfx
diff --git a/base/gfx/native_theme.h b/base/gfx/native_theme.h
new file mode 100644
index 0000000..918a282
--- /dev/null
+++ b/base/gfx/native_theme.h
@@ -0,0 +1,310 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+//
+// A wrapper class for working with custom XP/Vista themes provided in
+// uxtheme.dll. This is a singleton class that can be grabbed using
+// NativeTheme::instance().
+// For more information on visual style parts and states, see:
+// http://msdn.microsoft.com/library/default.asp?url=/library/en-us/shellcc/platform/commctls/userex/topics/partsandstates.asp
+
+#ifndef BASE_GFX_NATIVE_THEME_H__
+#define BASE_GFX_NATIVE_THEME_H__
+
+#include <windows.h>
+#include <uxtheme.h>
+#include "base/basictypes.h"
+#include "base/gfx/size.h"
+#include "skia/include/SkColor.h"
+
+namespace gfx {
+class PlatformCanvas;
+
+// TODO: Define class member enums to replace part_id and state_id parameters
+// that are currently defined in <vssym32.h>. Afterward, classic_state should
+// be removed and class users wouldn't need to include <vssym32.h> anymore.
+// This would enable HOT state on non-themed UI (like when RDP'ing) and would
+// simplify usage.
+// TODO: This class should probably be changed to be platform independent at
+// the same time.
+class NativeTheme {
+ public:
+ enum ThemeName {
+ BUTTON,
+ TEXTFIELD,
+ MENULIST,
+ SCROLLBAR,
+ STATUS,
+ MENU,
+ WINDOW,
+ TAB,
+ LIST,
+ LAST
+ };
+
+ // This enumeration is used within PaintMenuArrow in order to indicate the
+ // direction the menu arrow should point to.
+ enum MenuArrowDirection {
+ LEFT_POINTING_ARROW,
+ RIGHT_POINTING_ARROW
+ };
+
+ typedef HRESULT (WINAPI* DrawThemeBackgroundPtr)(HANDLE theme,
+ HDC hdc,
+ int part_id,
+ int state_id,
+ const RECT* rect,
+ const RECT* clip_rect);
+ typedef HRESULT (WINAPI* DrawThemeBackgroundExPtr)(HANDLE theme,
+ HDC hdc,
+ int part_id,
+ int state_id,
+ const RECT* rect,
+ const DTBGOPTS* opts);
+ typedef HRESULT (WINAPI* GetThemeColorPtr)(HANDLE hTheme,
+ int part_id,
+ int state_id,
+ int prop_id,
+ COLORREF* color);
+ typedef HRESULT (WINAPI* GetThemeContentRectPtr)(HANDLE hTheme,
+ HDC hdc,
+ int part_id,
+ int state_id,
+ const RECT* rect,
+ RECT* content_rect);
+ typedef HRESULT (WINAPI* GetThemePartSizePtr)(HANDLE hTheme,
+ HDC hdc,
+ int part_id,
+ int state_id,
+ RECT* rect,
+ int ts,
+ SIZE* size);
+ typedef HANDLE (WINAPI* OpenThemeDataPtr)(HWND window,
+ LPCWSTR class_list);
+ typedef HRESULT (WINAPI* CloseThemeDataPtr)(HANDLE theme);
+
+ typedef void (WINAPI* SetThemeAppPropertiesPtr) (DWORD flags);
+ typedef BOOL (WINAPI* IsThemeActivePtr)();
+ typedef HRESULT (WINAPI* GetThemeIntPtr)(HANDLE hTheme,
+ int part_id,
+ int state_id,
+ int prop_id,
+ int *value);
+
+ HRESULT PaintButton(HDC hdc,
+ int part_id,
+ int state_id,
+ int classic_state,
+ RECT* rect) const;
+
+ HRESULT PaintTextField(HDC hdc,
+ int part_id,
+ int state_id,
+ int classic_state,
+ RECT* rect,
+ COLORREF color,
+ bool fill_content_area,
+ bool draw_edges) const;
+
+ HRESULT PaintMenuList(HDC hdc,
+ int part_id,
+ int state_id,
+ int classic_state,
+ RECT* rect) const;
+
+ // Paints a scrollbar arrow. |classic_state| should have the appropriate
+ // classic part number ORed in already.
+ HRESULT PaintScrollbarArrow(HDC hdc,
+ int state_id,
+ int classic_state,
+ RECT* rect) const;
+
+ // Paints a scrollbar track section. |align_rect| is only used in classic
+ // mode, and makes sure the checkerboard pattern in |target_rect| is aligned
+ // with one presumed to be in |align_rect|.
+ HRESULT PaintScrollbarTrack(HDC hdc,
+ int part_id,
+ int state_id,
+ int classic_state,
+ RECT* target_rect,
+ RECT* align_rect,
+ PlatformCanvas* canvas) const;
+
+ // |arrow_direction| determines whether the arrow is pointing to the left or
+ // to the right. In RTL locales, sub-menus open from right to left and
+ // therefore the menu arrow should point to the left and not to the right.
+ HRESULT PaintMenuArrow(ThemeName theme,
+ HDC hdc,
+ int part_id,
+ int state_id,
+ RECT* rect,
+ MenuArrowDirection arrow_direction,
+ bool is_highlighted) const;
+
+ HRESULT PaintMenuBackground(ThemeName theme,
+ HDC hdc,
+ int part_id,
+ int state_id,
+ RECT* rect) const;
+
+ HRESULT PaintMenuCheck(ThemeName theme,
+ HDC hdc,
+ int part_id,
+ int state_id,
+ RECT* rect,
+ bool is_highlighted) const;
+
+ HRESULT PaintMenuCheckBackground(ThemeName theme,
+ HDC hdc,
+ int part_id,
+ int state_id,
+ RECT* rect) const;
+
+ HRESULT PaintMenuGutter(HDC hdc,
+ int part_id,
+ int state_id,
+ RECT* rect) const;
+
+ HRESULT PaintMenuSeparator(HDC hdc,
+ int part_id,
+ int state_id,
+ RECT* rect) const;
+
+ HRESULT PaintMenuItemBackground(ThemeName theme,
+ HDC hdc,
+ int part_id,
+ int state_id,
+ bool selected,
+ RECT* rect) const;
+
+ // Paints a scrollbar thumb or gripper.
+ HRESULT PaintScrollbarThumb(HDC hdc,
+ int part_id,
+ int state_id,
+ int classic_state,
+ RECT* rect) const;
+
+ HRESULT PaintStatusGripper(HDC hdc,
+ int part_id,
+ int state_id,
+ int classic_state,
+ RECT* rect) const;
+
+ HRESULT PaintDialogBackground(HDC dc, bool active, RECT* rect) const;
+
+ HRESULT PaintTabPanelBackground(HDC dc, RECT* rect) const;
+
+ HRESULT PaintListBackground(HDC dc, bool enabled, RECT* rect) const;
+
+ bool IsThemingActive() const;
+
+ HRESULT GetThemePartSize(ThemeName themeName,
+ HDC hdc,
+ int part_id,
+ int state_id,
+ RECT* rect,
+ int ts,
+ SIZE* size) const;
+
+ HRESULT GetThemeColor(ThemeName theme,
+ int part_id,
+ int state_id,
+ int prop_id,
+ SkColor* color) const;
+
+ // Get the theme color if theming is enabled. If theming is unsupported
+ // for this part, use Win32's GetSysColor to find the color specified
+ // by default_sys_color.
+ SkColor GetThemeColorWithDefault(ThemeName theme,
+ int part_id,
+ int state_id,
+ int prop_id,
+ int default_sys_color) const;
+
+ HRESULT GetThemeInt(ThemeName theme,
+ int part_id,
+ int state_id,
+ int prop_id,
+ int *result) const;
+
+ // Get the thickness of the border associated with the specified theme,
+ // defaulting to GetSystemMetrics edge size if themes are disabled.
+ // In Classic Windows, borders are typically 2px; on XP+, they are 1px.
+ Size GetThemeBorderSize(ThemeName theme) const;
+
+ // Disables all theming for top-level windows in the entire process, from
+ // when this method is called until the process exits. All the other
+ // methods in this class will continue to work, but their output will ignore
+ // the user's theme. This is meant for use when running tests that require
+ // consistent visual results.
+ void DisableTheming() const;
+
+ // Closes cached theme handles so we can unload the DLL or update our UI
+ // for a theme change.
+ void CloseHandles() const;
+
+ // Gets our singleton instance.
+ static const NativeTheme* instance();
+
+ private:
+ NativeTheme();
+ ~NativeTheme();
+
+ HRESULT PaintFrameControl(HDC hdc,
+ RECT* rect,
+ UINT type,
+ UINT state,
+ bool is_highlighted) const;
+
+ // Returns a handle to the theme data.
+ HANDLE GetThemeHandle(ThemeName theme_name) const;
+
+ // Function pointers into uxtheme.dll.
+ DrawThemeBackgroundPtr draw_theme_;
+ DrawThemeBackgroundExPtr draw_theme_ex_;
+ GetThemeColorPtr get_theme_color_;
+ GetThemeContentRectPtr get_theme_content_rect_;
+ GetThemePartSizePtr get_theme_part_size_;
+ OpenThemeDataPtr open_theme_;
+ CloseThemeDataPtr close_theme_;
+ SetThemeAppPropertiesPtr set_theme_properties_;
+ IsThemeActivePtr is_theme_active_;
+ GetThemeIntPtr get_theme_int_;
+
+ // Handle to uxtheme.dll.
+ HMODULE theme_dll_;
+
+ // A cache of open theme handles.
+ mutable HANDLE theme_handles_[LAST];
+
+ DISALLOW_EVIL_CONSTRUCTORS(NativeTheme);
+};
+
+} // namespace gfx
+
+#endif // BASE_GFX_NATIVE_THEME_H__
diff --git a/base/gfx/native_theme_unittest.cc b/base/gfx/native_theme_unittest.cc
new file mode 100644
index 0000000..6ed50e4
--- /dev/null
+++ b/base/gfx/native_theme_unittest.cc
@@ -0,0 +1,36 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "base/gfx/native_theme.h"
+
+#include "testing/gtest/include/gtest/gtest.h"
+
+TEST(NativeThemeTest, Init) {
+ ASSERT_TRUE(gfx::NativeTheme::instance() != NULL);
+}
diff --git a/base/gfx/platform_canvas.cc b/base/gfx/platform_canvas.cc
new file mode 100644
index 0000000..a421069
--- /dev/null
+++ b/base/gfx/platform_canvas.cc
@@ -0,0 +1,105 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "base/gfx/platform_canvas.h"
+
+#include "base/gfx/bitmap_platform_device.h"
+#include "base/logging.h"
+
+namespace gfx {
+
+PlatformCanvas::PlatformCanvas() : SkCanvas() {
+}
+
+PlatformCanvas::PlatformCanvas(int width, int height, bool is_opaque)
+ : SkCanvas() {
+ initialize(width, height, is_opaque, NULL);
+}
+
+PlatformCanvas::PlatformCanvas(int width,
+ int height,
+ bool is_opaque,
+ HANDLE shared_section)
+ : SkCanvas() {
+ initialize(width, height, is_opaque, shared_section);
+}
+
+PlatformCanvas::~PlatformCanvas() {
+}
+
+void PlatformCanvas::initialize(int width,
+ int height,
+ bool is_opaque,
+ HANDLE shared_section) {
+ SkDevice* device =
+ createPlatformDevice(width, height, is_opaque, shared_section);
+ setDevice(device);
+ device->unref(); // was created with refcount 1, and setDevice also refs
+}
+
+HDC PlatformCanvas::beginPlatformPaint() {
+ return getTopPlatformDevice().getBitmapDC();
+}
+
+void PlatformCanvas::endPlatformPaint() {
+ // we don't clear the DC here since it will be likely to be used again
+ // flushing will be done in onAccessBitmap
+}
+
+PlatformDevice& PlatformCanvas::getTopPlatformDevice() const {
+ // All of our devices should be our special PlatformDevice.
+ SkCanvas::LayerIter iter(const_cast<PlatformCanvas*>(this), false);
+ return *static_cast<PlatformDevice*>(iter.device());
+}
+
+SkDevice* PlatformCanvas::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* PlatformCanvas::createPlatformDevice(int width,
+ int height,
+ bool is_opaque,
+ HANDLE shared_section) {
+ HDC screen_dc = GetDC(NULL);
+ SkDevice* device = BitmapPlatformDevice::create(screen_dc, width, height,
+ is_opaque, shared_section);
+ ReleaseDC(NULL, screen_dc);
+ return device;
+}
+
+SkDevice* PlatformCanvas::setBitmapDevice(const SkBitmap&) {
+ NOTREACHED();
+ return NULL;
+}
+
+} // namespace gfx \ No newline at end of file
diff --git a/base/gfx/platform_canvas.h b/base/gfx/platform_canvas.h
new file mode 100644
index 0000000..c024a2f
--- /dev/null
+++ b/base/gfx/platform_canvas.h
@@ -0,0 +1,209 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#ifndef BASE_GFX_PLATFORM_CANVAS_H__
+#define BASE_GFX_PLATFORM_CANVAS_H__
+
+#include "base/gfx/platform_device.h"
+#include "base/basictypes.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 PlatformCanvas : public SkCanvas {
+ public:
+ // Set is_opaque if you are going to erase the bitmap and not use
+ // tranparency: 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()
+ PlatformCanvas();
+ PlatformCanvas(int width, int height, bool is_opaque);
+ PlatformCanvas(int width, int height, bool is_opaque, HANDLE shared_section);
+ virtual ~PlatformCanvas();
+
+ // For two-part init, call if you use the no-argument constructor above
+ void initialize(int width, int height, bool is_opaque, HANDLE shared_section);
+
+ // 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().
+ PlatformDevice& 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
+ // BitmapPlatformDevice object. 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_EVIL_CONSTRUCTORS(PlatformCanvas);
+};
+
+// 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::PlatformCanvasPaint canvas(hwnd);
+// if (!canvas.isEmpty()) {
+// ... paint to the canvas ...
+// }
+// return 0;
+// }
+template <class T>
+class CanvasPaintT : public T {
+ public:
+ CanvasPaintT(HWND hwnd) : hwnd_(hwnd), for_paint_(true) {
+ initPaint(true);
+ }
+
+ CanvasPaintT(HWND hwnd, bool opaque) : hwnd_(hwnd), for_paint_(true) {
+ 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.
+ initialize(ps_.rcPaint.right - ps_.rcPaint.left,
+ ps_.rcPaint.bottom - ps_.rcPaint.top, opaque, NULL);
+
+ // 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_EVIL_CONSTRUCTORS(CanvasPaintT);
+};
+
+typedef CanvasPaintT<PlatformCanvas> PlatformCanvasPaint;
+
+} // namespace gfx
+
+#endif // BASE_GFX_PLATFORM_CANVAS_H__
diff --git a/base/gfx/platform_canvas_unittest.cc b/base/gfx/platform_canvas_unittest.cc
new file mode 100644
index 0000000..2d127a7
--- /dev/null
+++ b/base/gfx/platform_canvas_unittest.cc
@@ -0,0 +1,293 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include <windows.h>
+
+#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);
+}
+
+void DrawGDIRect(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();
+}
+
+// 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 the GDI clipping.
+TEST(PlatformCanvas, GDIClipRegion) {
+ // 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.
+ DrawGDIRect(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);
+ DrawGDIRect(canvas, 0, 0, 16, 16);
+ }
+ EXPECT_TRUE(VerifyCanvasColor(canvas, SK_ColorWHITE));
+}
+
+// Test the layers get filled properly by GDI.
+TEST(PlatformCanvas, GDILayer) {
+ // 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);
+ DrawGDIRect(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);
+ DrawGDIRect(canvas, kInnerX, kInnerY, kInnerW, kInnerH);
+ }
+ EXPECT_TRUE(VerifyBlackSquare(canvas, kInnerX, kInnerY, kInnerW, kInnerH));
+
+ // Add a clip on the layer and fill to make make sure clip is correct.
+ canvas.drawColor(SK_ColorWHITE);
+ {
+ LayerSaver layer(canvas, kLayerX, kLayerY, kLayerW, kLayerH);
+ canvas.save();
+ AddClip(canvas, kInnerX, kInnerY, kInnerW, kInnerH);
+ DrawGDIRect(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);
+ DrawGDIRect(canvas, 0, 0, 100, 100);
+ }
+ canvas.restore();
+ EXPECT_TRUE(VerifyBlackSquare(canvas, kInnerX, kInnerY, kInnerW, kInnerH));
+}
+
+// Test that translation + make layer works properly.
+TEST(PlatformCanvas, GDITranslateLayer) {
+ // 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);
+ DrawGDIRect(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);
+ DrawGDIRect(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);
+ DrawGDIRect(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);
+ DrawGDIRect(canvas, 0, 0, 100, 100);
+ }
+ canvas.restore();
+ EXPECT_TRUE(VerifyBlackSquare(canvas, kInnerX + 2, kInnerY + 2,
+ kInnerW, kInnerH));
+}
+
+} // namespace
diff --git a/base/gfx/platform_device.cc b/base/gfx/platform_device.cc
new file mode 100644
index 0000000..e23ccf3
--- /dev/null
+++ b/base/gfx/platform_device.cc
@@ -0,0 +1,253 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "base/gfx/platform_device.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 {
+
+PlatformDevice::PlatformDevice(const SkBitmap& bitmap)
+ : SkDevice(bitmap) {
+}
+
+// static
+void PlatformDevice::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 PlatformDevice::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 PlatformDevice::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 PlatformDevice::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 PlatformDevice::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/base/gfx/platform_device.h b/base/gfx/platform_device.h
new file mode 100644
index 0000000..c3dc998
--- /dev/null
+++ b/base/gfx/platform_device.h
@@ -0,0 +1,120 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#ifndef BASE_GFX_PLATFORM_DEVICE_H__
+#define BASE_GFX_PLATFORM_DEVICE_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 PlatformDevice : 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.
+ PlatformDevice(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 // BASE_GFX_PLATFORM_DEVICE_H__
diff --git a/base/gfx/png_codec_unittest.cc b/base/gfx/png_codec_unittest.cc
new file mode 100644
index 0000000..1b510aa
--- /dev/null
+++ b/base/gfx/png_codec_unittest.cc
@@ -0,0 +1,223 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include <math.h>
+
+#include "base/gfx/png_encoder.h"
+#include "base/gfx/png_decoder.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+static void MakeRGBImage(int w, int h, std::vector<unsigned char>* dat) {
+ dat->resize(w * h * 3);
+ for (int y = 0; y < h; y++) {
+ for (int x = 0; x < w; x++) {
+ unsigned char* org_px = &(*dat)[(y * w + x) * 3];
+ org_px[0] = x * 3; // r
+ org_px[1] = x * 3 + 1; // g
+ org_px[2] = x * 3 + 2; // b
+ }
+ }
+}
+
+// Set use_transparency to write data into the alpha channel, otherwise it will
+// be filled with 0xff. With the alpha channel stripped, this should yield the
+// same image as MakeRGBImage above, so the code below can make reference
+// images for conversion testing.
+static void MakeRGBAImage(int w, int h, bool use_transparency,
+ std::vector<unsigned char>* dat) {
+ dat->resize(w * h * 4);
+ for (int y = 0; y < h; y++) {
+ for (int x = 0; x < w; x++) {
+ unsigned char* org_px = &(*dat)[(y * w + x) * 4];
+ org_px[0] = x * 3; // r
+ org_px[1] = x * 3 + 1; // g
+ org_px[2] = x * 3 + 2; // b
+ if (use_transparency)
+ org_px[3] = x*3 + 3; // a
+ else
+ org_px[3] = 0xFF; // a (opaque)
+ }
+ }
+}
+
+TEST(PNGCodec, EncodeDecodeRGB) {
+ const int w = 20, h = 20;
+
+ // create an image with known values
+ std::vector<unsigned char> original;
+ MakeRGBImage(w, h, &original);
+
+ // encode
+ std::vector<unsigned char> encoded;
+ EXPECT_TRUE(PNGEncoder::Encode(&original[0], PNGEncoder::FORMAT_RGB, w, h,
+ w * 3, false, &encoded));
+
+ // decode, it should have the same size as the original
+ std::vector<unsigned char> decoded;
+ int outw, outh;
+ EXPECT_TRUE(PNGDecoder::Decode(&encoded[0], encoded.size(),
+ PNGDecoder::FORMAT_RGB, &decoded,
+ &outw, &outh));
+ ASSERT_EQ(w, outw);
+ ASSERT_EQ(h, outh);
+ ASSERT_EQ(original.size(), decoded.size());
+
+ // Images must be equal
+ ASSERT_TRUE(original == decoded);
+}
+
+TEST(PNGCodec, EncodeDecodeRGBA) {
+ const int w = 20, h = 20;
+
+ // create an image with known values, a must be opaque because it will be
+ // lost during encoding
+ std::vector<unsigned char> original;
+ MakeRGBAImage(w, h, true, &original);
+
+ // encode
+ std::vector<unsigned char> encoded;
+ EXPECT_TRUE(PNGEncoder::Encode(&original[0], PNGEncoder::FORMAT_RGBA, w, h,
+ w * 4, false, &encoded));
+
+ // decode, it should have the same size as the original
+ std::vector<unsigned char> decoded;
+ int outw, outh;
+ EXPECT_TRUE(PNGDecoder::Decode(&encoded[0], encoded.size(),
+ PNGDecoder::FORMAT_RGBA, &decoded,
+ &outw, &outh));
+ ASSERT_EQ(w, outw);
+ ASSERT_EQ(h, outh);
+ ASSERT_EQ(original.size(), decoded.size());
+
+ // Images must be exactly equal
+ ASSERT_TRUE(original == decoded);
+}
+
+// Test that corrupted data decompression causes failures.
+TEST(PNGCodec, DecodeCorrupted) {
+ int w = 20, h = 20;
+
+ // Make some random data (an uncompressed image).
+ std::vector<unsigned char> original;
+ MakeRGBImage(w, h, &original);
+
+ // It should fail when given non-JPEG compressed data.
+ std::vector<unsigned char> output;
+ int outw, outh;
+ EXPECT_FALSE(PNGDecoder::Decode(&original[0], original.size(),
+ PNGDecoder::FORMAT_RGB, &output,
+ &outw, &outh));
+
+ // Make some compressed data.
+ std::vector<unsigned char> compressed;
+ EXPECT_TRUE(PNGEncoder::Encode(&original[0], PNGEncoder::FORMAT_RGB, w, h,
+ w * 3, false, &compressed));
+
+ // Try decompressing a truncated version.
+ EXPECT_FALSE(PNGDecoder::Decode(&compressed[0], compressed.size() / 2,
+ PNGDecoder::FORMAT_RGB, &output,
+ &outw, &outh));
+
+ // Corrupt it and try decompressing that.
+ for (int i = 10; i < 30; i++)
+ compressed[i] = i;
+ EXPECT_FALSE(PNGDecoder::Decode(&compressed[0], compressed.size(),
+ PNGDecoder::FORMAT_RGB, &output,
+ &outw, &outh));
+}
+
+TEST(PNGCodec, EncodeDecodeBGRA) {
+ const int w = 20, h = 20;
+
+ // Create an image with known values, alpha must be opaque because it will be
+ // lost during encoding.
+ std::vector<unsigned char> original;
+ MakeRGBAImage(w, h, true, &original);
+
+ // Encode.
+ std::vector<unsigned char> encoded;
+ EXPECT_TRUE(PNGEncoder::Encode(&original[0], PNGEncoder::FORMAT_BGRA, w, h,
+ w * 4, false, &encoded));
+
+ // Decode, it should have the same size as the original.
+ std::vector<unsigned char> decoded;
+ int outw, outh;
+ EXPECT_TRUE(PNGDecoder::Decode(&encoded[0], encoded.size(),
+ PNGDecoder::FORMAT_BGRA, &decoded,
+ &outw, &outh));
+ ASSERT_EQ(w, outw);
+ ASSERT_EQ(h, outh);
+ ASSERT_EQ(original.size(), decoded.size());
+
+ // Images must be exactly equal.
+ ASSERT_TRUE(original == decoded);
+}
+
+TEST(PNGCodec, StripAddAlpha) {
+ const int w = 20, h = 20;
+
+ // These should be the same except one has a 0xff alpha channel.
+ std::vector<unsigned char> original_rgb;
+ MakeRGBImage(w, h, &original_rgb);
+ std::vector<unsigned char> original_rgba;
+ MakeRGBAImage(w, h, false, &original_rgba);
+
+ // Encode RGBA data as RGB.
+ std::vector<unsigned char> encoded;
+ EXPECT_TRUE(PNGEncoder::Encode(&original_rgba[0], PNGEncoder::FORMAT_RGBA, w, h,
+ w * 4, true, &encoded));
+
+ // Decode the RGB to RGBA.
+ std::vector<unsigned char> decoded;
+ int outw, outh;
+ EXPECT_TRUE(PNGDecoder::Decode(&encoded[0], encoded.size(),
+ PNGDecoder::FORMAT_RGBA, &decoded,
+ &outw, &outh));
+
+ // Decoded and reference should be the same (opaque alpha).
+ ASSERT_EQ(w, outw);
+ ASSERT_EQ(h, outh);
+ ASSERT_EQ(original_rgba.size(), decoded.size());
+ ASSERT_TRUE(original_rgba == decoded);
+
+ // Encode RGBA to RGBA.
+ EXPECT_TRUE(PNGEncoder::Encode(&original_rgba[0], PNGEncoder::FORMAT_RGBA, w, h,
+ w * 4, false, &encoded));
+
+ // Decode the RGBA to RGB.
+ EXPECT_TRUE(PNGDecoder::Decode(&encoded[0], encoded.size(),
+ PNGDecoder::FORMAT_RGB, &decoded,
+ &outw, &outh));
+
+ // It should be the same as our non-alpha-channel reference.
+ ASSERT_EQ(w, outw);
+ ASSERT_EQ(h, outh);
+ ASSERT_EQ(original_rgb.size(), decoded.size());
+ ASSERT_TRUE(original_rgb == decoded);
+}
diff --git a/base/gfx/png_decoder.cc b/base/gfx/png_decoder.cc
new file mode 100644
index 0000000..40ba2e8
--- /dev/null
+++ b/base/gfx/png_decoder.cc
@@ -0,0 +1,376 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "base/gfx/png_decoder.h"
+
+#include "base/logging.h"
+#include "skia/include/SkBitmap.h"
+
+extern "C" {
+#include "png.h"
+}
+
+namespace {
+
+// Converts BGRA->RGBA and RGBA->BGRA.
+void ConvertBetweenBGRAandRGBA(const unsigned char* input, int pixel_width,
+ unsigned char* output) {
+ for (int x = 0; x < pixel_width; x++) {
+ const unsigned char* pixel_in = &input[x * 4];
+ unsigned char* pixel_out = &output[x * 4];
+ pixel_out[0] = pixel_in[2];
+ pixel_out[1] = pixel_in[1];
+ pixel_out[2] = pixel_in[0];
+ pixel_out[3] = pixel_in[3];
+ }
+}
+
+void ConvertRGBAtoRGB(const unsigned char* rgba, int pixel_width,
+ unsigned char* rgb) {
+ for (int x = 0; x < pixel_width; x++) {
+ const unsigned char* pixel_in = &rgba[x * 4];
+ unsigned char* pixel_out = &rgb[x * 3];
+ pixel_out[0] = pixel_in[0];
+ pixel_out[1] = pixel_in[1];
+ pixel_out[2] = pixel_in[2];
+ }
+}
+
+} // namespace
+
+// Decoder --------------------------------------------------------------------
+//
+// This code is based on WebKit libpng interface (PNGImageDecoder), which is
+// in turn based on the Mozilla png decoder.
+
+namespace {
+
+// Gamma constants: We assume we're on Windows which uses a gamma of 2.2.
+const double kMaxGamma = 21474.83; // Maximum gamma accepted by png library.
+const double kDefaultGamma = 2.2;
+const double kInverseGamma = 1.0 / kDefaultGamma;
+
+// Maximum pixel dimension we'll try to decode.
+const png_uint_32 kMaxSize = 4096;
+
+class PngDecoderState {
+ public:
+ PngDecoderState(PNGDecoder::ColorFormat ofmt, std::vector<unsigned char>* o)
+ : output_format(ofmt),
+ output_channels(0),
+ output(o),
+ row_converter(NULL),
+ width(0),
+ height(0),
+ done(false) {
+ }
+
+ PNGDecoder::ColorFormat output_format;
+ int output_channels;
+
+ std::vector<unsigned char>* output;
+
+ // Called to convert a row from the library to the correct output format.
+ // When NULL, no conversion is necessary.
+ void (*row_converter)(const unsigned char* in, int w, unsigned char* out);
+
+ // Size of the image, set in the info callback.
+ int width;
+ int height;
+
+ // Set to true when we've found the end of the data.
+ bool done;
+
+ private:
+ DISALLOW_EVIL_CONSTRUCTORS(PngDecoderState);
+};
+
+void ConvertRGBtoRGBA(const unsigned char* rgb, int pixel_width,
+ unsigned char* rgba) {
+ for (int x = 0; x < pixel_width; x++) {
+ const unsigned char* pixel_in = &rgb[x * 3];
+ unsigned char* pixel_out = &rgba[x * 4];
+ pixel_out[0] = pixel_in[0];
+ pixel_out[1] = pixel_in[1];
+ pixel_out[2] = pixel_in[2];
+ pixel_out[3] = 0xff;
+ }
+}
+
+void ConvertRGBtoBGRA(const unsigned char* rgb, int pixel_width,
+ unsigned char* bgra) {
+ for (int x = 0; x < pixel_width; x++) {
+ const unsigned char* pixel_in = &rgb[x * 3];
+ unsigned char* pixel_out = &bgra[x * 4];
+ pixel_out[0] = pixel_in[2];
+ pixel_out[1] = pixel_in[1];
+ pixel_out[2] = pixel_in[0];
+ pixel_out[3] = 0xff;
+ }
+}
+
+// Called when the png header has been read. This code is based on the WebKit
+// PNGImageDecoder
+void DecodeInfoCallback(png_struct* png_ptr, png_info* info_ptr) {
+ PngDecoderState* state = static_cast<PngDecoderState*>(
+ png_get_progressive_ptr(png_ptr));
+
+ int bit_depth, color_type, interlace_type, compression_type;
+ int filter_type, channels;
+ png_uint_32 w, h;
+ png_get_IHDR(png_ptr, info_ptr, &w, &h, &bit_depth, &color_type,
+ &interlace_type, &compression_type, &filter_type);
+
+ // Bounds check. When the image is unreasonably big, we'll error out and
+ // end up back at the setjmp call when we set up decoding.
+ if (w > kMaxSize || h > kMaxSize)
+ longjmp(png_ptr->jmpbuf, 1);
+ state->width = static_cast<int>(w);
+ state->height = static_cast<int>(h);
+
+ // Expand to ensure we use 24-bit for RGB and 32-bit for RGBA.
+ if (color_type == PNG_COLOR_TYPE_PALETTE ||
+ (color_type == PNG_COLOR_TYPE_GRAY && bit_depth < 8))
+ png_set_expand(png_ptr);
+
+ // Transparency for paletted images.
+ if (png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS))
+ png_set_expand(png_ptr);
+
+ // Convert 16-bit to 8-bit.
+ if (bit_depth == 16)
+ png_set_strip_16(png_ptr);
+
+ // Expand grayscale to RGB.
+ if (color_type == PNG_COLOR_TYPE_GRAY ||
+ color_type == PNG_COLOR_TYPE_GRAY_ALPHA)
+ png_set_gray_to_rgb(png_ptr);
+
+ // Deal with gamma and keep it under our control.
+ double gamma;
+ if (png_get_gAMA(png_ptr, info_ptr, &gamma)) {
+ if (gamma <= 0.0 || gamma > kMaxGamma) {
+ gamma = kInverseGamma;
+ png_set_gAMA(png_ptr, info_ptr, gamma);
+ }
+ png_set_gamma(png_ptr, kDefaultGamma, gamma);
+ } else {
+ png_set_gamma(png_ptr, kDefaultGamma, kInverseGamma);
+ }
+
+ // Tell libpng to send us rows for interlaced pngs.
+ if (interlace_type == PNG_INTERLACE_ADAM7)
+ png_set_interlace_handling(png_ptr);
+
+ // Update our info now
+ png_read_update_info(png_ptr, info_ptr);
+ channels = png_get_channels(png_ptr, info_ptr);
+
+ // Pick our row format converter necessary for this data.
+ if (channels == 3) {
+ switch (state->output_format) {
+ case PNGDecoder::FORMAT_RGB:
+ state->row_converter = NULL; // no conversion necessary
+ state->output_channels = 3;
+ break;
+ case PNGDecoder::FORMAT_RGBA:
+ state->row_converter = &ConvertRGBtoRGBA;
+ state->output_channels = 4;
+ break;
+ case PNGDecoder::FORMAT_BGRA:
+ state->row_converter = &ConvertRGBtoBGRA;
+ state->output_channels = 4;
+ break;
+ default:
+ NOTREACHED() << "Unknown output format";
+ break;
+ }
+ } else if (channels == 4) {
+ switch (state->output_format) {
+ case PNGDecoder::FORMAT_RGB:
+ state->row_converter = &ConvertRGBAtoRGB;
+ state->output_channels = 3;
+ break;
+ case PNGDecoder::FORMAT_RGBA:
+ state->row_converter = NULL; // no conversion necessary
+ state->output_channels = 4;
+ break;
+ case PNGDecoder::FORMAT_BGRA:
+ state->row_converter = &ConvertBetweenBGRAandRGBA;
+ state->output_channels = 4;
+ break;
+ default:
+ NOTREACHED() << "Unknown output format";
+ break;
+ }
+ } else {
+ NOTREACHED() << "Unknown input channels";
+ longjmp(png_ptr->jmpbuf, 1);
+ }
+
+ state->output->resize(state->width * state->output_channels * state->height);
+}
+
+void DecodeRowCallback(png_struct* png_ptr, png_byte* new_row,
+ png_uint_32 row_num, int pass) {
+ PngDecoderState* state = static_cast<PngDecoderState*>(
+ png_get_progressive_ptr(png_ptr));
+
+ DCHECK(pass == 0) << "We didn't turn on interlace handling, but libpng is "
+ "giving us interlaced data.";
+ if (static_cast<int>(row_num) > state->height) {
+ NOTREACHED() << "Invalid row";
+ return;
+ }
+
+ unsigned char* dest = &(*state->output)[
+ state->width * state->output_channels * row_num];
+ if (state->row_converter)
+ state->row_converter(new_row, state->width, dest);
+ else
+ memcpy(dest, new_row, state->width * state->output_channels);
+}
+
+void DecodeEndCallback(png_struct* png_ptr, png_info* info) {
+ PngDecoderState* state = static_cast<PngDecoderState*>(
+ png_get_progressive_ptr(png_ptr));
+
+ // Mark the image as complete, this will tell the Decode function that we
+ // have successfully found the end of the data.
+ state->done = true;
+}
+
+// Automatically destroys the given read structs on destruction to make
+// cleanup and error handling code cleaner.
+class PngReadStructDestroyer {
+ public:
+ PngReadStructDestroyer(png_struct** ps, png_info** pi) : ps_(ps), pi_(pi) {
+ }
+ ~PngReadStructDestroyer() {
+ png_destroy_read_struct(ps_, pi_, NULL);
+ }
+ private:
+ png_struct** ps_;
+ png_info** pi_;
+};
+
+} // namespace
+
+// static
+bool PNGDecoder::Decode(const unsigned char* input, size_t input_size,
+ ColorFormat format, std::vector<unsigned char>* output,
+ int* w, int* h) {
+ if (input_size < 8)
+ return false; // Input data too small to be a png
+
+ // Have libpng check the signature, it likes the first 8 bytes.
+ if (png_sig_cmp(const_cast<unsigned char*>(input), 0, 8) != 0)
+ return false;
+
+ png_struct* png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING,
+ png_voidp_NULL,
+ png_error_ptr_NULL,
+ png_error_ptr_NULL);
+ if (!png_ptr)
+ return false;
+
+ png_info* info_ptr = png_create_info_struct(png_ptr);
+ if (!info_ptr) {
+ png_destroy_read_struct(&png_ptr, NULL, NULL);
+ return false;
+ }
+
+ PngReadStructDestroyer destroyer(&png_ptr, &info_ptr);
+ if (setjmp(png_jmpbuf(png_ptr))) {
+ // The destroyer will ensure that the structures are cleaned up in this
+ // case, even though we may get here as a jump from random parts of the
+ // PNG library called below.
+ return false;
+ }
+
+ PngDecoderState state(format, output);
+
+ png_set_progressive_read_fn(png_ptr, &state, &DecodeInfoCallback,
+ &DecodeRowCallback, &DecodeEndCallback);
+ png_process_data(png_ptr, info_ptr, const_cast<unsigned char*>(input), input_size);
+
+ if (!state.done) {
+ // Fed it all the data but the library didn't think we got all the data, so
+ // this file must be truncated.
+ output->clear();
+ return false;
+ }
+
+ *w = state.width;
+ *h = state.height;
+ return true;
+}
+
+// static
+bool PNGDecoder::Decode(const std::vector<unsigned char>* data,
+ SkBitmap* bitmap) {
+ DCHECK(bitmap);
+ if (!data || data->empty())
+ return false;
+ int width, height;
+ std::vector<unsigned char> decoded_data;
+ if (PNGDecoder::Decode(&data->front(), data->size(), PNGDecoder::FORMAT_BGRA,
+ &decoded_data, &width, &height)) {
+ bitmap->setConfig(SkBitmap::kARGB_8888_Config, width, height);
+ bitmap->allocPixels();
+ memcpy(bitmap->getPixels(), &decoded_data.front(), width * height * 4);
+ return true;
+ }
+ return false;
+}
+
+//static
+SkBitmap* PNGDecoder::CreateSkBitmapFromBGRAFormat(
+ std::vector<unsigned char>& bgra, int width, int height) {
+ SkBitmap* bitmap = new SkBitmap();
+ bitmap->setConfig(SkBitmap::kARGB_8888_Config, width, height);
+ bitmap->allocPixels();
+
+ bool opaque = false;
+ unsigned char* bitmap_data =
+ reinterpret_cast<unsigned char*>(bitmap->getAddr32(0, 0));
+ for (int i = width * height * 4 - 4; i >= 0; i -= 4) {
+ unsigned char alpha = bgra[i + 3];
+ if (!opaque && alpha != 255) {
+ opaque = false;
+ }
+ bitmap_data[i + 3] = alpha;
+ bitmap_data[i] = (bgra[i] * alpha) >> 8;
+ bitmap_data[i + 1] = (bgra[i + 1] * alpha) >> 8;
+ bitmap_data[i + 2] = (bgra[i + 2] * alpha) >> 8;
+ }
+
+ bitmap->setIsOpaque(opaque);
+ return bitmap;
+}
diff --git a/base/gfx/png_decoder.h b/base/gfx/png_decoder.h
new file mode 100644
index 0000000..4912187
--- /dev/null
+++ b/base/gfx/png_decoder.h
@@ -0,0 +1,88 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#ifndef BASE_GFX_PNG_DECODER_H__
+#define BASE_GFX_PNG_DECODER_H__
+
+#include <vector>
+
+#include "base/basictypes.h"
+
+class SkBitmap;
+
+// Interface for decoding PNG data. This is a wrapper around libpng,
+// which has an inconvenient interface for callers. This is currently designed
+// for use in tests only (where we control the files), so the handling isn't as
+// robust as would be required for a browser (see Decode() for more). WebKit
+// has its own more complicated PNG decoder which handles, among other things,
+// partially downloaded data.
+class PNGDecoder {
+ public:
+ enum ColorFormat {
+ // 3 bytes per pixel (packed), in RGB order regardless of endianness.
+ // This is the native JPEG format.
+ FORMAT_RGB,
+
+ // 4 bytes per pixel, in RGBA order in memory regardless of endianness.
+ FORMAT_RGBA,
+
+ // 4 bytes per pixel, in BGRA order in memory regardless of endianness.
+ // This is the default Windows DIB order.
+ FORMAT_BGRA
+ };
+
+ // Decodes the PNG data contained in input of length input_size. The
+ // decoded data will be placed in *output with the dimensions in *w and *h
+ // on success (returns true). This data will be written in the 'format'
+ // format. On failure, the values of these output variables are undefined.
+ //
+ // This function may not support all PNG types, and it hasn't been tested
+ // with a large number of images, so assume a new format may not work. It's
+ // really designed to be able to read in something written by Encode() above.
+ static bool Decode(const unsigned char* input, size_t input_size,
+ ColorFormat format, std::vector<unsigned char>* output,
+ int* w, int* h);
+
+ // A convenience function for decoding PNGs as previously encoded by the PNG
+ // encoder. Chrome encodes png in the format PNGDecoder::FORMAT_BGRA.
+ //
+ // Returns true if data is non-null and can be decoded as a png, false
+ // otherwise.
+ static bool Decode(const std::vector<unsigned char>* data, SkBitmap* icon);
+
+ // Create a SkBitmap from a decoded BGRA DIB. The caller owns the returned
+ // SkBitmap.
+ static SkBitmap* CreateSkBitmapFromBGRAFormat(
+ std::vector<unsigned char>& bgra, int width, int height);
+
+ private:
+ DISALLOW_EVIL_CONSTRUCTORS(PNGDecoder);
+};
+
+#endif // BASE_GFX_PNG_DECODER_H__
diff --git a/base/gfx/png_encoder.cc b/base/gfx/png_encoder.cc
new file mode 100644
index 0000000..ee31a2c
--- /dev/null
+++ b/base/gfx/png_encoder.cc
@@ -0,0 +1,217 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "base/basictypes.h"
+#include "base/gfx/png_encoder.h"
+#include "base/logging.h"
+
+extern "C" {
+#include "png.h"
+}
+
+namespace {
+
+// Converts BGRA->RGBA and RGBA->BGRA.
+void ConvertBetweenBGRAandRGBA(const unsigned char* input, int pixel_width,
+ unsigned char* output) {
+ for (int x = 0; x < pixel_width; x++) {
+ const unsigned char* pixel_in = &input[x * 4];
+ unsigned char* pixel_out = &output[x * 4];
+ pixel_out[0] = pixel_in[2];
+ pixel_out[1] = pixel_in[1];
+ pixel_out[2] = pixel_in[0];
+ pixel_out[3] = pixel_in[3];
+ }
+}
+
+void ConvertRGBAtoRGB(const unsigned char* rgba, int pixel_width,
+ unsigned char* rgb) {
+ for (int x = 0; x < pixel_width; x++) {
+ const unsigned char* pixel_in = &rgba[x * 4];
+ unsigned char* pixel_out = &rgb[x * 3];
+ pixel_out[0] = pixel_in[0];
+ pixel_out[1] = pixel_in[1];
+ pixel_out[2] = pixel_in[2];
+ }
+}
+
+} // namespace
+
+// Encoder --------------------------------------------------------------------
+//
+// This section of the code is based on nsPNGEncoder.cpp in Mozilla
+// (Copyright 2005 Google Inc.)
+
+namespace {
+
+// Passed around as the io_ptr in the png structs so our callbacks know where
+// to write data.
+struct PngEncoderState {
+ PngEncoderState(std::vector<unsigned char>* o) : out(o) {}
+ std::vector<unsigned char>* out;
+};
+
+// Called by libpng to flush its internal buffer to ours.
+void EncoderWriteCallback(png_structp png, png_bytep data, png_size_t size) {
+ PngEncoderState* state = static_cast<PngEncoderState*>(png_get_io_ptr(png));
+ DCHECK(state->out);
+
+ size_t old_size = state->out->size();
+ state->out->resize(old_size + size);
+ memcpy(&(*state->out)[old_size], data, size);
+}
+
+void ConvertBGRAtoRGB(const unsigned char* bgra, int pixel_width,
+ unsigned char* rgb) {
+ for (int x = 0; x < pixel_width; x++) {
+ const unsigned char* pixel_in = &bgra[x * 4];
+ unsigned char* pixel_out = &rgb[x * 3];
+ pixel_out[0] = pixel_in[2];
+ pixel_out[1] = pixel_in[1];
+ pixel_out[2] = pixel_in[0];
+ }
+}
+
+// Automatically destroys the given write structs on destruction to make
+// cleanup and error handling code cleaner.
+class PngWriteStructDestroyer {
+ public:
+ PngWriteStructDestroyer(png_struct** ps, png_info** pi) : ps_(ps), pi_(pi) {
+ }
+ ~PngWriteStructDestroyer() {
+ png_destroy_write_struct(ps_, pi_);
+ }
+ private:
+ png_struct** ps_;
+ png_info** pi_;
+
+ DISALLOW_EVIL_CONSTRUCTORS(PngWriteStructDestroyer);
+};
+
+} // namespace
+
+// static
+bool PNGEncoder::Encode(const unsigned char* input, ColorFormat format,
+ int w, int h, int row_byte_width,
+ bool discard_transparency,
+ std::vector<unsigned char>* output) {
+ // Run to convert an input row into the output row format, NULL means no
+ // conversion is necessary.
+ void (*converter)(const unsigned char* in, int w, unsigned char* out) = NULL;
+
+ int input_color_components, output_color_components;
+ int png_output_color_type;
+ switch (format) {
+ case FORMAT_RGB:
+ input_color_components = 3;
+ output_color_components = 3;
+ png_output_color_type = PNG_COLOR_TYPE_RGB;
+ discard_transparency = false;
+ break;
+
+ case FORMAT_RGBA:
+ input_color_components = 4;
+ if (discard_transparency) {
+ output_color_components = 3;
+ png_output_color_type = PNG_COLOR_TYPE_RGB;
+ converter = ConvertRGBAtoRGB;
+ } else {
+ output_color_components = 4;
+ png_output_color_type = PNG_COLOR_TYPE_RGB_ALPHA;
+ converter = NULL;
+ }
+ break;
+
+ case FORMAT_BGRA:
+ input_color_components = 4;
+ if (discard_transparency) {
+ output_color_components = 3;
+ png_output_color_type = PNG_COLOR_TYPE_RGB;
+ converter = ConvertBGRAtoRGB;
+ } else {
+ output_color_components = 4;
+ png_output_color_type = PNG_COLOR_TYPE_RGB_ALPHA;
+ converter = ConvertBetweenBGRAandRGBA;
+ }
+ break;
+
+ default:
+ NOTREACHED() << "Unknown pixel format";
+ return false;
+ }
+
+ // Row stride should be at least as long as the length of the data.
+ DCHECK(input_color_components * w <= row_byte_width);
+
+ png_struct* png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING,
+ png_voidp_NULL,
+ png_error_ptr_NULL,
+ png_error_ptr_NULL);
+ if (!png_ptr)
+ return false;
+ png_info* info_ptr = png_create_info_struct(png_ptr);
+ if (!info_ptr) {
+ png_destroy_write_struct(&png_ptr, NULL);
+ return false;
+ }
+ PngWriteStructDestroyer destroyer(&png_ptr, &info_ptr);
+
+ if (setjmp(png_jmpbuf(png_ptr))) {
+ // The destroyer will ensure that the structures are cleaned up in this
+ // case, even though we may get here as a jump from random parts of the
+ // PNG library called below.
+ return false;
+ }
+
+ // Set our callback for libpng to give us the data.
+ PngEncoderState state(output);
+ png_set_write_fn(png_ptr, &state, EncoderWriteCallback, NULL);
+
+ png_set_IHDR(png_ptr, info_ptr, w, h, 8, png_output_color_type,
+ PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT,
+ PNG_FILTER_TYPE_DEFAULT);
+ png_write_info(png_ptr, info_ptr);
+
+ if (!converter) {
+ // No conversion needed, give the data directly to libpng.
+ for (int y = 0; y < h; y ++)
+ png_write_row(png_ptr, const_cast<unsigned char*>(&input[y * row_byte_width]));
+ } else {
+ // Needs conversion using a separate buffer.
+ unsigned char* row = new unsigned char[w * output_color_components];
+ for (int y = 0; y < h; y ++) {
+ converter(&input[y * row_byte_width], w, row);
+ png_write_row(png_ptr, row);
+ }
+ delete[] row;
+ }
+
+ png_write_end(png_ptr, info_ptr);
+ return true;
+}
diff --git a/base/gfx/png_encoder.h b/base/gfx/png_encoder.h
new file mode 100644
index 0000000..3129775
--- /dev/null
+++ b/base/gfx/png_encoder.h
@@ -0,0 +1,83 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#ifndef BASE_GFX_PNG_ENCODER_H__
+#define BASE_GFX_PNG_ENCODER_H__
+
+#include <vector>
+
+#include "base/basictypes.h"
+
+// Interface for encoding PNG data. This is a wrapper around libpng,
+// which has an inconvenient interface for callers. This is currently designed
+// for use in tests only (where we control the files), so the handling isn't as
+// robust as would be required for a browser (see Decode() for more). WebKit
+// has its own more complicated PNG decoder which handles, among other things,
+// partially downloaded data.
+class PNGEncoder {
+ public:
+ enum ColorFormat {
+ // 3 bytes per pixel (packed), in RGB order regardless of endianness.
+ // This is the native JPEG format.
+ FORMAT_RGB,
+
+ // 4 bytes per pixel, in RGBA order in memory regardless of endianness.
+ FORMAT_RGBA,
+
+ // 4 bytes per pixel, in BGRA order in memory regardless of endianness.
+ // This is the default Windows DIB order.
+ FORMAT_BGRA
+ };
+
+ // Encodes the given raw 'input' data, with each pixel being represented as
+ // given in 'format'. The encoded PNG data will be written into the supplied
+ // vector and true will be returned on success. On failure (false), the
+ // contents of the output buffer are undefined.
+ //
+ // When writing alpha values, the input colors are assumed to be post
+ // multiplied.
+ //
+ // w, h: dimensions of the image
+ // row_byte_width: the width in bytes of each row. This may be greater than
+ // w * bytes_per_pixel if there is extra padding at the end of each row
+ // (often, each row is padded to the next machine word).
+ // discard_transparency: when true, and when the input data format includes
+ // alpha values, these alpha values will be discarded and only RGB will be
+ // written to the resulting file. Otherwise, alpha values in the input
+ // will be preserved.
+ static bool Encode(const unsigned char* input, ColorFormat format,
+ int w, int h, int row_byte_width,
+ bool discard_transparency,
+ std::vector<unsigned char>* output);
+
+ private:
+ DISALLOW_EVIL_CONSTRUCTORS(PNGEncoder);
+};
+
+#endif // BASE_GFX_PNG_ENCODER_H__
diff --git a/base/gfx/point.cc b/base/gfx/point.cc
new file mode 100644
index 0000000..7c435a0
--- /dev/null
+++ b/base/gfx/point.cc
@@ -0,0 +1,52 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "base/gfx/point.h"
+
+#include <windows.h>
+
+namespace gfx {
+
+Point::Point() : x_(0), y_(0) {
+}
+
+Point::Point(int x, int y) : x_(x), y_(y) {
+}
+
+Point::Point(const POINT& point) : x_(point.x), y_(point.y) {
+}
+
+POINT Point::ToPOINT() const {
+ POINT p;
+ p.x = x_;
+ p.y = y_;
+ return p;
+}
+
+} // namespace gfx
diff --git a/base/gfx/point.h b/base/gfx/point.h
new file mode 100644
index 0000000..3e09a81
--- /dev/null
+++ b/base/gfx/point.h
@@ -0,0 +1,88 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#ifndef BASE_GFX_POINT_H__
+#define BASE_GFX_POINT_H__
+
+#ifdef UNIT_TEST
+#include <iostream>
+#endif
+
+typedef struct tagPOINT POINT;
+
+namespace gfx {
+
+//
+// A point has an x and y coordinate.
+//
+class Point {
+ public:
+ Point();
+ Point(int x, int y);
+ explicit Point(const POINT& point);
+
+ ~Point() {}
+
+ int x() const { return x_; }
+ int y() const { return y_; }
+
+ void SetPoint(int x, int y) {
+ x_ = x;
+ y_ = y;
+ }
+
+ void set_x(int x) { x_ = x; }
+ void set_y(int y) { y_ = y; }
+
+ bool operator==(const Point& rhs) const {
+ return x_ == rhs.x_ && y_ == rhs.y_;
+ }
+
+ bool operator!=(const Point& rhs) const {
+ return !(*this == rhs);
+ }
+
+ POINT ToPOINT() const;
+
+ private:
+ int x_;
+ int y_;
+};
+
+} // namespace gfx
+
+#ifdef UNIT_TEST
+
+inline std::ostream& operator<<(std::ostream& out, const gfx::Point& p) {
+ return out << p.x() << "," << p.y();
+}
+
+#endif // #ifdef UNIT_TEST
+
+#endif // BASE_GFX_POINT_H__
diff --git a/base/gfx/rect.cc b/base/gfx/rect.cc
new file mode 100644
index 0000000..dda7e67
--- /dev/null
+++ b/base/gfx/rect.cc
@@ -0,0 +1,217 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "base/gfx/rect.h"
+
+#include <windows.h>
+
+#include "base/logging.h"
+
+namespace {
+
+void AdjustAlongAxis(int dst_origin, int dst_size, int* origin, int* size) {
+ if (*origin < dst_origin) {
+ *origin = dst_origin;
+ *size = std::min(dst_size, *size);
+ } else {
+ *size = std::min(dst_size, *size);
+ *origin = std::min(dst_origin + dst_size, *origin + *size) - *size;
+ }
+}
+
+} // namespace
+
+namespace gfx {
+
+Rect::Rect() {
+}
+
+Rect::Rect(int width, int height) {
+ set_width(width);
+ set_height(height);
+}
+
+Rect::Rect(int x, int y, int width, int height)
+ : origin_(x, y) {
+ set_width(width);
+ set_height(height);
+}
+
+Rect::Rect(const RECT& r)
+ : origin_(r.left, r.top) {
+ set_width(r.right - r.left);
+ set_height(r.bottom - r.top);
+}
+
+Rect& Rect::operator=(const RECT& r) {
+ origin_.SetPoint(r.left, r.top);
+ set_width(r.right - r.left);
+ set_height(r.bottom - r.top);
+ return *this;
+}
+
+void Rect::set_width(int width) {
+ if (width < 0) {
+ NOTREACHED();
+ width = 0;
+ }
+
+ size_.set_width(width);
+}
+void Rect::set_height(int height) {
+ if (height < 0) {
+ NOTREACHED();
+ height = 0;
+ }
+
+ size_.set_height(height);
+}
+
+void Rect::SetRect(int x, int y, int width, int height) {
+ origin_.SetPoint(x, y);
+ set_width(width);
+ set_height(height);
+}
+
+void Rect::Inset(int horizontal, int vertical) {
+ set_x(x() + horizontal);
+ set_y(y() + vertical);
+ set_width(std::max(width() - (horizontal * 2), 0));
+ set_height(std::max(height() - (vertical * 2), 0));
+}
+
+void Rect::Offset(int horizontal, int vertical) {
+ set_x(x() + horizontal);
+ set_y(y() + vertical);
+}
+
+bool Rect::IsEmpty() const {
+ return width() == 0 || height() == 0;
+}
+
+bool Rect::operator==(const Rect& other) const {
+ return origin_ == other.origin_ && size_ == other.size_;
+}
+
+RECT Rect::ToRECT() const {
+ RECT r;
+ r.left = x();
+ r.right = right();
+ r.top = y();
+ r.bottom = bottom();
+ return r;
+}
+
+bool Rect::Contains(int point_x, int point_y) const {
+ return (point_x >= x()) && (point_x < right()) &&
+ (point_y >= y()) && (point_y < bottom());
+}
+
+bool Rect::Contains(const Rect& rect) const {
+ return (rect.x() >= x() && rect.right() <= right() &&
+ rect.y() >= y() && rect.bottom() <= bottom());
+}
+
+bool Rect::Intersects(const Rect& rect) const {
+ return !(rect.x() >= right() || rect.right() <= x() ||
+ rect.y() >= bottom() || rect.bottom() <= y());
+}
+
+Rect Rect::Intersect(const Rect& rect) const {
+ int rx = std::max(x(), rect.x());
+ int ry = std::max(y(), rect.y());
+ int rr = std::min(right(), rect.right());
+ int rb = std::min(bottom(), rect.bottom());
+
+ if (rx >= rr || ry >= rb)
+ rx = ry = rr = rb = 0; // non-intersecting
+
+ return Rect(rx, ry, rr - rx, rb - ry);
+}
+
+Rect Rect::Union(const Rect& rect) const {
+ // special case empty rects...
+ if (IsEmpty())
+ return rect;
+ if (rect.IsEmpty())
+ return *this;
+
+ int rx = std::min(x(), rect.x());
+ int ry = std::min(y(), rect.y());
+ int rr = std::max(right(), rect.right());
+ int rb = std::max(bottom(), rect.bottom());
+
+ return Rect(rx, ry, rr - rx, rb - ry);
+}
+
+Rect Rect::Subtract(const Rect& rect) const {
+ // boundary cases:
+ if (!Intersects(rect))
+ return *this;
+ if (rect.Contains(*this))
+ return Rect();
+
+ int rx = x();
+ int ry = y();
+ int rr = right();
+ int rb = bottom();
+
+ if (rect.y() <= y() && rect.bottom() >= bottom()) {
+ // complete intersection in the y-direction
+ if (rect.x() <= x()) {
+ rx = rect.right();
+ } else {
+ rr = rect.x();
+ }
+ } else if (rect.x() <= x() && rect.right() >= right()) {
+ // complete intersection in the x-direction
+ if (rect.y() <= y()) {
+ ry = rect.bottom();
+ } else {
+ rb = rect.y();
+ }
+ }
+ return Rect(rx, ry, rr - rx, rb - ry);
+}
+
+Rect Rect::AdjustToFit(const Rect& rect) const {
+ int new_x = x();
+ int new_y = y();
+ int new_width = width();
+ int new_height = height();
+ AdjustAlongAxis(rect.x(), rect.width(), &new_x, &new_width);
+ AdjustAlongAxis(rect.y(), rect.height(), &new_y, &new_height);
+ return Rect(new_x, new_y, new_width, new_height);
+}
+
+Point Rect::CenterPoint() const {
+ return Point(x() + (width() + 1) / 2, y() + (height() + 1) / 2);
+}
+
+} // namespace gfx
diff --git a/base/gfx/rect.h b/base/gfx/rect.h
new file mode 100644
index 0000000..486f799
--- /dev/null
+++ b/base/gfx/rect.h
@@ -0,0 +1,153 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+// Defines a simple integer rectangle class. The containment semantics
+// are array-like; that is, the coordinate (x, y) is considered to be
+// contained by the rectangle, but the coordinate (x + width, y) is not.
+// The class will happily let you create malformed rectangles (that is,
+// rectangles with negative width and/or height), but there will be assertions
+// in the operations (such as contain()) to complain in this case.
+
+#ifndef BASE_GFX_RECT_H__
+#define BASE_GFX_RECT_H__
+
+#include "base/gfx/size.h"
+#include "base/gfx/point.h"
+
+typedef struct tagRECT RECT;
+
+namespace gfx {
+
+class Rect {
+ public:
+ Rect();
+ Rect(int width, int height);
+ Rect(int x, int y, int width, int height);
+ explicit Rect(const RECT& r);
+ Rect(const gfx::Point& origin, const gfx::Size& size);
+
+ ~Rect() {}
+
+ Rect& operator=(const RECT& r);
+
+ int x() const { return origin_.x(); }
+ void set_x(int x) { origin_.set_x(x); }
+
+ int y() const { return origin_.y(); }
+ void set_y(int y) { origin_.set_y(y); }
+
+ int width() const { return size_.width(); }
+ void set_width(int width);
+
+ int height() const { return size_.height(); }
+ void set_height(int height);
+
+ const gfx::Point& origin() const { return origin_; }
+ void set_origin(const gfx::Point& origin) { origin_ = origin; }
+
+ const gfx::Size& size() const { return size_; }
+
+ int right() const { return x() + width(); }
+ int bottom() const { return y() + height(); }
+
+ void SetRect(int x, int y, int width, int height);
+
+ // Shrink the rectangle by a horizontal and vertical distance on all sides.
+ void Inset(int horizontal, int vertical);
+
+ // Move the rectangle by a horizontal and vertical distance.
+ void Offset(int horizontal, int vertical);
+
+ // Returns true if the area of the rectangle is zero.
+ bool IsEmpty() const;
+
+ bool operator==(const Rect& other) const;
+
+ bool operator!=(const Rect& other) const {
+ return !(*this == other);
+ }
+
+ // Construct an equivalent Win32 RECT object.
+ RECT ToRECT() const;
+
+ // Returns true if the point identified by point_x and point_y falls inside
+ // this rectangle. The point (x, y) is inside the rectangle, but the
+ // point (x + width, y + height) is not.
+ bool Contains(int point_x, int point_y) const;
+
+ // Returns true if this rectangle contains the specified rectangle.
+ bool Contains(const Rect& rect) const;
+
+ // Returns true if this rectangle intersects the specified rectangle.
+ bool Intersects(const Rect& rect) const;
+
+ // Computes the intersection of this rectangle with the given rectangle.
+ Rect Intersect(const Rect& rect) const;
+
+ // Computes the union of this rectangle with the given rectangle. The union
+ // is the smallest rectangle containing both rectangles.
+ Rect Union(const Rect& rect) const;
+
+ // Computes the rectangle resulting from subtracting |rect| from |this|. If
+ // |rect| does not intersect completely in either the x- or y-direction, then
+ // |*this| is returned. If |rect| contains |this|, then an empty Rect is
+ // returned.
+ Rect Subtract(const Rect& rect) const;
+
+ // Returns true if this rectangle equals that of the supplied rectangle.
+ bool Equals(const Rect& rect) const {
+ return *this == rect;
+ }
+
+ // Fits as much of the receiving rectangle into the supplied rectangle as
+ // possible, returning the result. For example, if the receiver had
+ // a x-location of 2 and a width of 4, and the supplied rectangle had
+ // an x-location of 0 with a width of 5, the returned rectangle would have
+ // an x-location of 1 with a width of 4.
+ Rect AdjustToFit(const Rect& rect) const;
+
+ // Returns the center of this rectangle.
+ Point CenterPoint() const;
+
+ private:
+ gfx::Point origin_;
+ gfx::Size size_;
+};
+
+} // namespace gfx
+
+#ifdef UNIT_TEST
+
+inline std::ostream& operator<<(std::ostream& out, const gfx::Rect& r) {
+ return out << r.origin() << " " << r.size();
+}
+
+#endif // #ifdef UNIT_TEST
+
+#endif // BASE_GFX_RECT_H__
diff --git a/base/gfx/rect_unittest.cc b/base/gfx/rect_unittest.cc
new file mode 100644
index 0000000..c218cc9
--- /dev/null
+++ b/base/gfx/rect_unittest.cc
@@ -0,0 +1,295 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "base/basictypes.h"
+#include "base/gfx/rect.h"
+#include "base/logging.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+typedef testing::Test RectTest;
+
+TEST(RectTest, Contains) {
+ static const struct ContainsCase {
+ int rect_x;
+ int rect_y;
+ int rect_width;
+ int rect_height;
+ int point_x;
+ int point_y;
+ bool contained;
+ } contains_cases[] = {
+ {0, 0, 10, 10, 0, 0, true},
+ {0, 0, 10, 10, 5, 5, true},
+ {0, 0, 10, 10, 9, 9, true},
+ {0, 0, 10, 10, 5, 10, false},
+ {0, 0, 10, 10, 10, 5, false},
+ {0, 0, 10, 10, -1, -1, false},
+ {0, 0, 10, 10, 50, 50, false},
+ #ifdef NDEBUG
+ {0, 0, -10, -10, 0, 0, false},
+ #endif // NDEBUG
+ };
+ for (int i = 0; i < arraysize(contains_cases); ++i) {
+ const ContainsCase& value = contains_cases[i];
+ gfx::Rect rect(value.rect_x, value.rect_y,
+ value.rect_width, value.rect_height);
+ EXPECT_EQ(value.contained, rect.Contains(value.point_x, value.point_y));
+ }
+}
+
+TEST(RectTest, Intersects) {
+ static const struct {
+ int x1; // rect 1
+ int y1;
+ int w1;
+ int h1;
+ int x2; // rect 2
+ int y2;
+ int w2;
+ int h2;
+ bool intersects;
+ } tests[] = {
+ { 0, 0, 0, 0, 0, 0, 0, 0, false },
+ { 0, 0, 10, 10, 0, 0, 10, 10, true },
+ { 0, 0, 10, 10, 10, 10, 10, 10, false },
+ { 10, 10, 10, 10, 0, 0, 10, 10, false },
+ { 10, 10, 10, 10, 5, 5, 10, 10, true },
+ { 10, 10, 10, 10, 15, 15, 10, 10, true },
+ { 10, 10, 10, 10, 20, 15, 10, 10, false },
+ { 10, 10, 10, 10, 21, 15, 10, 10, false }
+ };
+ for (size_t i = 0; i < arraysize(tests); ++i) {
+ gfx::Rect r1(tests[i].x1, tests[i].y1, tests[i].w1, tests[i].h1);
+ gfx::Rect r2(tests[i].x2, tests[i].y2, tests[i].w2, tests[i].h2);
+ EXPECT_EQ(tests[i].intersects, r1.Intersects(r2));
+ }
+}
+
+TEST(RectTest, Intersect) {
+ static const struct {
+ int x1; // rect 1
+ int y1;
+ int w1;
+ int h1;
+ int x2; // rect 2
+ int y2;
+ int w2;
+ int h2;
+ int x3; // rect 3: the union of rects 1 and 2
+ int y3;
+ int w3;
+ int h3;
+ } tests[] = {
+ { 0, 0, 0, 0, // zeros
+ 0, 0, 0, 0,
+ 0, 0, 0, 0 },
+ { 0, 0, 4, 4, // equal
+ 0, 0, 4, 4,
+ 0, 0, 4, 4 },
+ { 0, 0, 4, 4, // neighboring
+ 4, 4, 4, 4,
+ 0, 0, 0, 0 },
+ { 0, 0, 4, 4, // overlapping corners
+ 2, 2, 4, 4,
+ 2, 2, 2, 2 },
+ { 0, 0, 4, 4, // T junction
+ 3, 1, 4, 2,
+ 3, 1, 1, 2 },
+ { 3, 0, 2, 2, // gap
+ 0, 0, 2, 2,
+ 0, 0, 0, 0 }
+ };
+ for (size_t i = 0; i < arraysize(tests); ++i) {
+ gfx::Rect r1(tests[i].x1, tests[i].y1, tests[i].w1, tests[i].h1);
+ gfx::Rect r2(tests[i].x2, tests[i].y2, tests[i].w2, tests[i].h2);
+ gfx::Rect r3(tests[i].x3, tests[i].y3, tests[i].w3, tests[i].h3);
+ gfx::Rect ir = r1.Intersect(r2);
+ EXPECT_EQ(r3.x(), ir.x());
+ EXPECT_EQ(r3.y(), ir.y());
+ EXPECT_EQ(r3.width(), ir.width());
+ EXPECT_EQ(r3.height(), ir.height());
+ }
+}
+
+TEST(RectTest, Union) {
+ static const struct {
+ int x1; // rect 1
+ int y1;
+ int w1;
+ int h1;
+ int x2; // rect 2
+ int y2;
+ int w2;
+ int h2;
+ int x3; // rect 3: the union of rects 1 and 2
+ int y3;
+ int w3;
+ int h3;
+ } tests[] = {
+ { 0, 0, 0, 0,
+ 0, 0, 0, 0,
+ 0, 0, 0, 0 },
+ { 0, 0, 4, 4,
+ 0, 0, 4, 4,
+ 0, 0, 4, 4 },
+ { 0, 0, 4, 4,
+ 4, 4, 4, 4,
+ 0, 0, 8, 8 },
+ { 0, 0, 4, 4,
+ 0, 5, 4, 4,
+ 0, 0, 4, 9 },
+ { 0, 0, 2, 2,
+ 3, 3, 2, 2,
+ 0, 0, 5, 5 },
+ { 3, 3, 2, 2, // reverse r1 and r2 from previous test
+ 0, 0, 2, 2,
+ 0, 0, 5, 5 },
+ { 0, 0, 0, 0, // union with empty rect
+ 2, 2, 2, 2,
+ 2, 2, 2, 2 }
+ };
+ for (size_t i = 0; i < arraysize(tests); ++i) {
+ gfx::Rect r1(tests[i].x1, tests[i].y1, tests[i].w1, tests[i].h1);
+ gfx::Rect r2(tests[i].x2, tests[i].y2, tests[i].w2, tests[i].h2);
+ gfx::Rect r3(tests[i].x3, tests[i].y3, tests[i].w3, tests[i].h3);
+ gfx::Rect u = r1.Union(r2);
+ EXPECT_EQ(r3.x(), u.x());
+ EXPECT_EQ(r3.y(), u.y());
+ EXPECT_EQ(r3.width(), u.width());
+ EXPECT_EQ(r3.height(), u.height());
+ }
+}
+
+TEST(RectTest, Equals) {
+ ASSERT_TRUE(gfx::Rect(0, 0, 0, 0).Equals(gfx::Rect(0, 0, 0, 0)));
+ ASSERT_TRUE(gfx::Rect(1, 2, 3, 4).Equals(gfx::Rect(1, 2, 3, 4)));
+ ASSERT_FALSE(gfx::Rect(0, 0, 0, 0).Equals(gfx::Rect(0, 0, 0, 1)));
+ ASSERT_FALSE(gfx::Rect(0, 0, 0, 0).Equals(gfx::Rect(0, 0, 1, 0)));
+ ASSERT_FALSE(gfx::Rect(0, 0, 0, 0).Equals(gfx::Rect(0, 1, 0, 0)));
+ ASSERT_FALSE(gfx::Rect(0, 0, 0, 0).Equals(gfx::Rect(1, 0, 0, 0)));
+}
+
+TEST(RectTest, AdjustToFit) {
+ static const struct {
+ int x1; // source
+ int y1;
+ int w1;
+ int h1;
+ int x2; // target
+ int y2;
+ int w2;
+ int h2;
+ int x3; // rect 3: results of invoking AdjustToFit
+ int y3;
+ int w3;
+ int h3;
+ } tests[] = {
+ { 0, 0, 2, 2,
+ 0, 0, 2, 2,
+ 0, 0, 2, 2 },
+ { 2, 2, 3, 3,
+ 0, 0, 4, 4,
+ 1, 1, 3, 3 },
+ { -1, -1, 5, 5,
+ 0, 0, 4, 4,
+ 0, 0, 4, 4 },
+ { 2, 2, 4, 4,
+ 0, 0, 3, 3,
+ 0, 0, 3, 3 },
+ { 2, 2, 1, 1,
+ 0, 0, 3, 3,
+ 2, 2, 1, 1 }
+ };
+ for (size_t i = 0; i < arraysize(tests); ++i) {
+ gfx::Rect r1(tests[i].x1, tests[i].y1, tests[i].w1, tests[i].h1);
+ gfx::Rect r2(tests[i].x2, tests[i].y2, tests[i].w2, tests[i].h2);
+ gfx::Rect r3(tests[i].x3, tests[i].y3, tests[i].w3, tests[i].h3);
+ gfx::Rect u(r1.AdjustToFit(r2));
+ EXPECT_EQ(r3.x(), u.x());
+ EXPECT_EQ(r3.y(), u.y());
+ EXPECT_EQ(r3.width(), u.width());
+ EXPECT_EQ(r3.height(), u.height());
+ }
+}
+
+TEST(RectText, Subtract) {
+ // Matching
+ EXPECT_TRUE(
+ gfx::Rect(10, 10, 20, 20).Subtract(
+ gfx::Rect(10, 10, 20, 20)).Equals(
+ gfx::Rect(0, 0, 0, 0)));
+
+ // Contains
+ EXPECT_TRUE(
+ gfx::Rect(10, 10, 20, 20).Subtract(
+ gfx::Rect(5, 5, 30, 30)).Equals(
+ gfx::Rect(0, 0, 0, 0)));
+
+ // No intersection
+ EXPECT_TRUE(
+ gfx::Rect(10, 10, 20, 20).Subtract(
+ gfx::Rect(30, 30, 20, 20)).Equals(
+ gfx::Rect(10, 10, 20, 20)));
+
+ // Not a complete intersection in either direction
+ EXPECT_TRUE(
+ gfx::Rect(10, 10, 20, 20).Subtract(
+ gfx::Rect(15, 15, 20, 20)).Equals(
+ gfx::Rect(10, 10, 20, 20)));
+
+ // Complete intersection in the x-direction
+ EXPECT_TRUE(
+ gfx::Rect(10, 10, 20, 20).Subtract(
+ gfx::Rect(10, 15, 20, 20)).Equals(
+ gfx::Rect(10, 10, 20, 5)));
+
+ // Complete intersection in the x-direction
+ EXPECT_TRUE(
+ gfx::Rect(10, 10, 20, 20).Subtract(
+ gfx::Rect(5, 15, 30, 20)).Equals(
+ gfx::Rect(10, 10, 20, 5)));
+
+ // Complete intersection in the x-direction
+ EXPECT_TRUE(
+ gfx::Rect(10, 10, 20, 20).Subtract(
+ gfx::Rect(5, 5, 30, 20)).Equals(
+ gfx::Rect(10, 25, 20, 5)));
+
+ // Complete intersection in the y-direction
+ EXPECT_TRUE(
+ gfx::Rect(10, 10, 20, 20).Subtract(
+ gfx::Rect(10, 10, 10, 30)).Equals(
+ gfx::Rect(20, 10, 10, 20)));
+
+ // Complete intersection in the y-direction
+ EXPECT_TRUE(
+ gfx::Rect(10, 10, 20, 20).Subtract(
+ gfx::Rect(5, 5, 20, 30)).Equals(
+ gfx::Rect(25, 10, 5, 20)));
+}
diff --git a/base/gfx/size.cc b/base/gfx/size.cc
new file mode 100644
index 0000000..153c29e
--- /dev/null
+++ b/base/gfx/size.cc
@@ -0,0 +1,49 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "base/gfx/size.h"
+
+#include <windows.h>
+
+#include <ostream>
+
+namespace gfx {
+
+SIZE Size::ToSIZE() const {
+ SIZE s;
+ s.cx = width_;
+ s.cy = height_;
+ return s;
+}
+
+std::ostream& operator<<(std::ostream& out, const gfx::Size& s) {
+ return out << s.width() << "x" << s.height();
+}
+
+} // namespace gfx
diff --git a/base/gfx/size.h b/base/gfx/size.h
new file mode 100644
index 0000000..cba2e51
--- /dev/null
+++ b/base/gfx/size.h
@@ -0,0 +1,91 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#ifndef BASE_GFX_SIZE_H__
+#define BASE_GFX_SIZE_H__
+
+#ifdef UNIT_TEST
+#include <iostream>
+#endif
+
+typedef struct tagSIZE SIZE;
+
+namespace gfx {
+
+//
+// A size has width and height values.
+//
+class Size {
+ public:
+ Size() : width_(0), height_(0) {}
+ Size(int width, int height) : width_(width), height_(height) {}
+
+ ~Size() {}
+
+ int width() const { return width_; }
+ int height() const { return height_; }
+
+ void SetSize(int width, int height) {
+ width_ = width;
+ height_ = height;
+ }
+
+ void set_width(int width) { width_ = width; }
+ void set_height(int height) { height_ = height; }
+
+ bool operator==(const Size& s) const {
+ return width_ == s.width_ && height_ == s.height_;
+ }
+
+ bool operator!=(const Size& s) const {
+ return !(*this == s);
+ }
+
+ bool IsEmpty() const {
+ return !width_ && !height_;
+ }
+
+ SIZE ToSIZE() const;
+
+ private:
+ int width_;
+ int height_;
+};
+
+} // namespace gfx
+
+#ifdef UNIT_TEST
+
+inline std::ostream& operator<<(std::ostream& out, const gfx::Size& s) {
+ return out << s.width() << "x" << s.height();
+}
+
+#endif // #ifdef UNIT_TEST
+
+#endif // BASE_GFX_SIZE_H__
diff --git a/base/gfx/skia_utils.cc b/base/gfx/skia_utils.cc
new file mode 100644
index 0000000..60e977d
--- /dev/null
+++ b/base/gfx/skia_utils.cc
@@ -0,0 +1,99 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "base/gfx/skia_utils.h"
+
+#include "base/logging.h"
+#include "SkRect.h"
+#include "SkGradientShader.h"
+
+namespace {
+
+COMPILE_ASSERT(offsetof(RECT, left) == offsetof(SkIRect, fLeft), o1);
+COMPILE_ASSERT(offsetof(RECT, top) == offsetof(SkIRect, fTop), o2);
+COMPILE_ASSERT(offsetof(RECT, right) == offsetof(SkIRect, fRight), o3);
+COMPILE_ASSERT(offsetof(RECT, bottom) == offsetof(SkIRect, fBottom), o4);
+COMPILE_ASSERT(sizeof(RECT().left) == sizeof(SkIRect().fLeft), o5);
+COMPILE_ASSERT(sizeof(RECT().top) == sizeof(SkIRect().fTop), o6);
+COMPILE_ASSERT(sizeof(RECT().right) == sizeof(SkIRect().fRight), o7);
+COMPILE_ASSERT(sizeof(RECT().bottom) == sizeof(SkIRect().fBottom), o8);
+COMPILE_ASSERT(sizeof(RECT) == sizeof(SkIRect), o9);
+
+} // namespace
+
+namespace gfx {
+
+POINT SkPointToPOINT(const SkPoint& point) {
+ POINT win_point = { SkScalarRound(point.fX), SkScalarRound(point.fY) };
+ return win_point;
+}
+
+SkRect RECTToSkRect(const RECT& rect) {
+ SkRect sk_rect = { SkIntToScalar(rect.left), SkIntToScalar(rect.top),
+ SkIntToScalar(rect.right), SkIntToScalar(rect.bottom) };
+ return sk_rect;
+}
+
+SkShader* CreateGradientShader(int start_point,
+ int end_point,
+ SkColor start_color,
+ SkColor end_color) {
+ SkColor grad_colors[2] = { start_color, end_color};
+ SkPoint grad_points[2];
+ grad_points[0].set(SkIntToScalar(0), SkIntToScalar(start_point));
+ grad_points[1].set(SkIntToScalar(0), SkIntToScalar(end_point));
+
+ return SkGradientShader::CreateLinear(
+ grad_points, grad_colors, NULL, 2, SkShader::kRepeat_TileMode);
+}
+
+
+SkColor COLORREFToSkColor(COLORREF color) {
+#ifndef _MSC_VER
+ return SkColorSetRGB(GetRValue(color), GetGValue(color), GetBValue(color));
+#else
+ // ARGB = 0xFF000000 | ((0BGR -> RGB0) >> 8)
+ return 0xFF000000u | (_byteswap_ulong(color) >> 8);
+#endif
+}
+
+COLORREF SkColorToCOLORREF(SkColor color) {
+ // Currently, Alpha is always 255 or the color is 0 so there is no need to
+ // demultiply the channels. If this DCHECK() is ever hit, the full
+ // (SkColorGetX(color) * 255 / a) will have to be added in the conversion.
+ DCHECK((0xFF == SkColorGetA(color)) || (0 == color));
+#ifndef _MSC_VER
+ return RGB(SkColorGetR(color), SkColorGetG(color), SkColorGetB(color));
+#else
+ // 0BGR = ((ARGB -> BGRA) >> 8)
+ return (_byteswap_ulong(color) >> 8);
+#endif
+}
+
+} // namespace gfx
diff --git a/base/gfx/skia_utils.h b/base/gfx/skia_utils.h
new file mode 100644
index 0000000..989a17e
--- /dev/null
+++ b/base/gfx/skia_utils.h
@@ -0,0 +1,80 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#ifndef BASE_GFX_SKIA_UTILS_H__
+#define BASE_GFX_SKIA_UTILS_H__
+
+#include "SkColor.h"
+#include "SkShader.h"
+
+struct SkIRect;
+struct SkPoint;
+struct SkRect;
+typedef unsigned long DWORD;
+typedef DWORD COLORREF;
+typedef struct tagPOINT POINT;
+typedef struct tagRECT RECT;
+
+namespace gfx {
+
+// Converts a Skia point to a Windows POINT.
+POINT SkPointToPOINT(const SkPoint& point);
+
+// Converts a Windows RECT to a Skia rect.
+SkRect RECTToSkRect(const RECT& rect);
+
+// Converts a Windows RECT to a Skia rect.
+// Both use same in-memory format. Verified by COMPILE_ASSERT() in
+// skia_utils.cc.
+inline const SkIRect& RECTToSkIRect(const RECT& rect) {
+ return reinterpret_cast<const SkIRect&>(rect);
+}
+
+// Converts a Skia rect to a Windows RECT.
+// Both use same in-memory format. Verified by COMPILE_ASSERT() in
+// skia_utils.cc.
+inline const RECT& SkIRectToRECT(const SkIRect& rect) {
+ return reinterpret_cast<const RECT&>(rect);
+}
+
+// Creates a vertical gradient shader. The caller owns the shader.
+SkShader* CreateGradientShader(int start_point,
+ int end_point,
+ SkColor start_color,
+ SkColor end_color);
+
+// Converts COLORREFs (0BGR) to the ARGB layout Skia expects.
+SkColor COLORREFToSkColor(COLORREF color);
+
+// Converts ARGB to COLORREFs (0BGR).
+COLORREF SkColorToCOLORREF(SkColor color);
+
+} // namespace gfx
+
+#endif
diff --git a/base/gfx/uniscribe.cc b/base/gfx/uniscribe.cc
new file mode 100644
index 0000000..fbfb751
--- /dev/null
+++ b/base/gfx/uniscribe.cc
@@ -0,0 +1,872 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include <windows.h>
+
+#include "base/gfx/uniscribe.h"
+
+#include "base/gfx/font_utils.h"
+#include "base/logging.h"
+
+namespace gfx {
+
+// This function is used to see where word spacing should be applied inside
+// runs. Note that this must match Font::treatAsSpace so we all agree where
+// and how much space this is, so we don't want to do more general Unicode
+// "is this a word break" thing.
+static bool TreatAsSpace(wchar_t c) {
+ return c == ' ' || c == '\t' || c == '\n' || c == 0x00A0;
+}
+
+// SCRIPT_FONTPROPERTIES contains glyph indices for default, invalid
+// and blank glyphs. Just because ScriptShape succeeds does not mean
+// that a text run is rendered correctly. Some characters may be rendered
+// with default/invalid/blank glyphs. Therefore, we need to check if the glyph
+// array returned by ScriptShape contains any of those glyphs to make
+// sure that the text run is rendered successfully.
+static bool ContainsMissingGlyphs(WORD *glyphs,
+ int length,
+ SCRIPT_FONTPROPERTIES* properties) {
+ for (int i = 0; i < length; ++i) {
+ if (glyphs[i] == properties->wgDefault ||
+ (glyphs[i] == properties->wgInvalid && glyphs[i] != properties->wgBlank))
+ return true;
+ }
+
+ return false;
+}
+
+// HFONT is the 'incarnation' of 'everything' about font, but it's an opaque
+// handle and we can't directly query it to make a new HFONT sharing
+// its characteristics (height, style, etc) except for family name.
+// This function uses GetObject to convert HFONT back to LOGFONT,
+// resets the fields of LOGFONT and calculates style to use later
+// for the creation of a font identical to HFONT other than family name.
+static void SetLogFontAndStyle(HFONT hfont, LOGFONT *logfont, int *style) {
+ DCHECK(hfont && logfont);
+ if (!hfont || !logfont)
+ return;
+
+ GetObject(hfont, sizeof(LOGFONT), logfont);
+ // We reset these fields to values appropriate for CreateFontIndirect.
+ // while keeping lfHeight, which is the most important value in creating
+ // a new font similar to hfont.
+ logfont->lfWidth = 0;
+ logfont->lfEscapement = 0;
+ logfont->lfOrientation = 0;
+ logfont->lfCharSet = DEFAULT_CHARSET;
+ logfont->lfOutPrecision = OUT_TT_ONLY_PRECIS;
+ logfont->lfQuality = DEFAULT_QUALITY; // Honor user's desktop settings.
+ logfont->lfPitchAndFamily = DEFAULT_PITCH | FF_DONTCARE;
+ if (style)
+ *style = gfx::GetStyleFromLogfont(logfont);
+}
+
+UniscribeState::UniscribeState(const wchar_t* input,
+ int input_length,
+ bool is_rtl,
+ HFONT hfont,
+ SCRIPT_CACHE* script_cache,
+ SCRIPT_FONTPROPERTIES* font_properties)
+ : input_(input),
+ input_length_(input_length),
+ is_rtl_(is_rtl),
+ hfont_(hfont),
+ script_cache_(script_cache),
+ font_properties_(font_properties),
+ directional_override_(false),
+ inhibit_ligate_(false),
+ letter_spacing_(0),
+ space_width_(0),
+ word_spacing_(0),
+ ascent_(0) {
+ logfont_.lfFaceName[0] = 0;
+}
+
+UniscribeState::~UniscribeState() {
+}
+
+void UniscribeState::InitWithOptionalLengthProtection(bool length_protection) {
+ // We cap the input length and just don't do anything. We'll allocate a lot
+ // of things of the size of the number of characters, so the allocated memory
+ // will be several times the input length. Plus shaping such a large buffer
+ // may be a form of denial of service. No legitimate text should be this long.
+ // It also appears that Uniscribe flatly rejects very long strings, so we
+ // don't lose anything by doing this.
+ //
+ // The input length protection may be disabled by the unit tests to cause
+ // an error condition.
+ static const int kMaxInputLength = 65535;
+ if (input_length_ == 0 ||
+ (length_protection && input_length_ > kMaxInputLength))
+ return;
+
+ FillRuns();
+ FillShapes();
+ FillScreenOrder();
+}
+
+int UniscribeState::Width() const {
+ int width = 0;
+ for (int item_index = 0; item_index < static_cast<int>(runs_->size());
+ item_index++) {
+ width += AdvanceForItem(item_index);
+ }
+ return width;
+}
+
+void UniscribeState::Justify(int additional_space) {
+ // Count the total number of glyphs we have so we know how big to make the
+ // buffers below.
+ int total_glyphs = 0;
+ for (size_t run = 0; run < runs_->size(); run++) {
+ int run_idx = screen_order_[run];
+ total_glyphs += static_cast<int>(shapes_[run_idx].glyph_length());
+ }
+ if (total_glyphs == 0)
+ return; // Nothing to do.
+
+ // We make one big buffer in screen order of all the glyphs we are drawing
+ // across runs so that the justification function will adjust evenly across
+ // all glyphs.
+ StackVector<SCRIPT_VISATTR, 64> visattr;
+ visattr->resize(total_glyphs);
+ StackVector<int, 64> advances;
+ advances->resize(total_glyphs);
+ StackVector<int, 64> justify;
+ justify->resize(total_glyphs);
+
+ // Build the packed input.
+ int dest_index = 0;
+ for (size_t run = 0; run < runs_->size(); run++) {
+ int run_idx = screen_order_[run];
+ const Shaping& shaping = shapes_[run_idx];
+
+ for (int i = 0; i < shaping.glyph_length(); i++, dest_index++) {
+ memcpy(&visattr[dest_index], &shaping.visattr[i], sizeof(SCRIPT_VISATTR));
+ advances[dest_index] = shaping.advance[i];
+ }
+ }
+
+ // The documentation for ScriptJustify is wrong, the parameter is the space
+ // to add and not the width of the column you want.
+ const int min_kashida = 1; // How do we decide what this should be?
+ ScriptJustify(&visattr[0], &advances[0], total_glyphs, additional_space,
+ min_kashida, &justify[0]);
+
+ // Now we have to unpack the justification amounts back into the runs so
+ // the glyph indices match.
+ int global_glyph_index = 0;
+ for (size_t run = 0; run < runs_->size(); run++) {
+ int run_idx = screen_order_[run];
+ Shaping& shaping = shapes_[run_idx];
+
+ shaping.justify->resize(shaping.glyph_length());
+ for (int i = 0; i < shaping.glyph_length(); i++, global_glyph_index++)
+ shaping.justify[i] = justify[global_glyph_index];
+ }
+}
+
+int UniscribeState::CharacterToX(int offset) const {
+ HRESULT hr;
+ DCHECK(offset <= input_length_);
+
+ // Our algorithm is to traverse the items in screen order from left to
+ // right, adding in each item's screen width until we find the item with
+ // the requested character in it.
+ int width = 0;
+ for (size_t screen_idx = 0; screen_idx < runs_->size(); screen_idx++) {
+ // Compute the length of this run.
+ int item_idx = screen_order_[screen_idx];
+ const SCRIPT_ITEM& item = runs_[item_idx];
+ const Shaping& shaping = shapes_[item_idx];
+ int item_length = shaping.char_length();
+
+ if (offset >= item.iCharPos && offset <= item.iCharPos + item_length) {
+ // Character offset is in this run.
+ int char_len = offset - item.iCharPos;
+
+ int cur_x = 0;
+ hr = ScriptCPtoX(char_len, FALSE, item_length, shaping.glyph_length(),
+ &shaping.logs[0], &shaping.visattr[0],
+ shaping.effective_advances(), &item.a, &cur_x);
+ if (FAILED(hr))
+ return 0;
+
+ width += cur_x + shaping.pre_padding;
+ DCHECK(width >= 0);
+ return width;
+ }
+
+ // Move to the next item.
+ width += AdvanceForItem(item_idx);
+ }
+ DCHECK(width >= 0);
+ return width;
+}
+
+int UniscribeState::XToCharacter(int x) const {
+ // We iterate in screen order until we find the item with the given pixel
+ // position in it. When we find that guy, we ask Uniscribe for the
+ // character index.
+ HRESULT hr;
+ for (size_t screen_idx = 0; screen_idx < runs_->size(); screen_idx++) {
+ int item_idx = screen_order_[screen_idx];
+ int advance_for_item = AdvanceForItem(item_idx);
+
+ // Note that the run may be empty if shaping failed, so we want to skip
+ // over it.
+ const Shaping& shaping = shapes_[item_idx];
+ int item_length = shaping.char_length();
+ if (x <= advance_for_item && item_length > 0) {
+ // The requested offset is within this item.
+ const SCRIPT_ITEM& item = runs_[item_idx];
+
+ // Account for the leading space we've added to this run that Uniscribe
+ // doesn't know about.
+ x -= shaping.pre_padding;
+
+ int char_x = 0;
+ int trailing;
+ hr = ScriptXtoCP(x, item_length, shaping.glyph_length(),
+ &shaping.logs[0], &shaping.visattr[0],
+ shaping.effective_advances(), &item.a, &char_x,
+ &trailing);
+
+ // The character offset is within the item. We need to add the item's
+ // offset to transform it into the space of the TextRun
+ return char_x + item.iCharPos;
+ }
+
+ // The offset is beyond this item, account for its length and move on.
+ x -= advance_for_item;
+ }
+
+ // Error condition, we don't know what to do if we don't have that X
+ // position in any of our items.
+ return 0;
+}
+
+void UniscribeState::Draw(HDC dc, int x, int y, int from, int to) {
+ HGDIOBJ old_font = 0;
+ int cur_x = x;
+ bool first_run = true;
+
+ for (size_t screen_idx = 0; screen_idx < runs_->size(); screen_idx++) {
+ int item_idx = screen_order_[screen_idx];
+ const SCRIPT_ITEM& item = runs_[item_idx];
+ const Shaping& shaping = shapes_[item_idx];
+
+ // Character offsets within this run. THESE MAY NOT BE IN RANGE and may
+ // be negative, etc. The code below handles this.
+ int from_char = from - item.iCharPos;
+ int to_char = to - item.iCharPos;
+
+ // See if we need to draw any characters in this item.
+ if (shaping.char_length() == 0 ||
+ from_char >= shaping.char_length() || to_char <= 0) {
+ // No chars in this item to display.
+ cur_x += AdvanceForItem(item_idx);
+ continue;
+ }
+
+ // Compute the starting glyph within this span. |from| and |to| are
+ // global offsets that may intersect arbitrarily with our local run.
+ int from_glyph, after_glyph;
+ if (item.a.fRTL) {
+ // To compute the first glyph when going RTL, we use |to|.
+ if (to_char >= shaping.char_length()) {
+ // The end of the text is after (to the left) of us.
+ from_glyph = 0;
+ } else {
+ // Since |to| is exclusive, the first character we draw on the left
+ // is actually the one right before (to the right) of |to|.
+ from_glyph = shaping.logs[to_char - 1];
+ }
+
+ // The last glyph is actually the first character in the range.
+ if (from_char <= 0) {
+ // The first character to draw is before (to the right) of this span,
+ // so draw all the way to the end.
+ after_glyph = shaping.glyph_length();
+ } else {
+ // We want to draw everything up until the character to the right of
+ // |from|. To the right is - 1, so we look that up (remember our
+ // character could be more than one glyph, so we can't look up our
+ // glyph and add one).
+ after_glyph = shaping.logs[from_char - 1];
+ }
+ } else {
+ // Easy case, everybody agrees about directions. We only need to handle
+ // boundary conditions to get a range inclusive at the beginning, and
+ // exclusive at the ending. We have to do some computation to see the
+ // glyph one past the end.
+ from_glyph = shaping.logs[from_char < 0 ? 0 : from_char];
+ if (to_char >= shaping.char_length())
+ after_glyph = shaping.glyph_length();
+ else
+ after_glyph = shaping.logs[to_char];
+ }
+
+ // Account for the characters that were skipped in this run. When
+ // WebKit asks us to draw a subset of the run, it actually tells us
+ // to draw at the X offset of the beginning of the run, since it
+ // doesn't know the internal position of any of our characters.
+ const int* effective_advances = shaping.effective_advances();
+ int inner_offset = 0;
+ for (int i = 0; i < from_glyph; i++)
+ inner_offset += effective_advances[i];
+
+ // Actually draw the glyphs we found.
+ int glyph_count = after_glyph - from_glyph;
+ if (from_glyph >= 0 && glyph_count > 0) {
+ // Account for the preceeding space we need to add to this run. We don't
+ // need to count for the following space because that will be counted
+ // in AdvanceForItem below when we move to the next run.
+ inner_offset += shaping.pre_padding;
+
+ // Pass NULL in when there is no justification.
+ const int* justify = shaping.justify->empty() ?
+ NULL : &shaping.justify[from_glyph];
+
+ if (first_run) {
+ old_font = SelectObject(dc, shaping.hfont_);
+ first_run = false;
+ } else {
+ SelectObject(dc, shaping.hfont_);
+ }
+
+ // TODO(brettw) bug 698452: if a half a character is selected,
+ // we should set up a clip rect so we draw the half of the glyph
+ // correctly.
+ // Fonts with different ascents can be used to render different runs.
+ // 'Across-runs' y-coordinate correction needs to be adjusted
+ // for each font.
+ HRESULT hr = S_FALSE;
+ for (int executions = 0; executions < 2; ++executions) {
+ hr = ScriptTextOut(dc, shaping.script_cache_, cur_x + inner_offset,
+ y - shaping.ascent_offset_, 0, NULL, &item.a, NULL,
+ 0, &shaping.glyphs[from_glyph],
+ glyph_count, &shaping.advance[from_glyph],
+ justify, &shaping.offsets[from_glyph]);
+ if (S_OK != hr && 0 == executions) {
+ // If this ScriptTextOut is called from the renderer it might fail
+ // because the sandbox is preventing it from opening the font files.
+ // If we are running in the renderer, TryToPreloadFont is overridden
+ // to ask the browser to preload the font for us so we can access it.
+ TryToPreloadFont(shaping.hfont_);
+ continue;
+ }
+ break;
+ }
+
+ DCHECK(S_OK == hr);
+
+
+ }
+
+ cur_x += AdvanceForItem(item_idx);
+ }
+
+ if (old_font)
+ SelectObject(dc, old_font);
+}
+
+WORD UniscribeState::FirstGlyphForCharacter(int char_offset) const {
+ // Find the run for the given character.
+ for (int i = 0; i < static_cast<int>(runs_->size()); i++) {
+ int first_char = runs_[i].iCharPos;
+ const Shaping& shaping = shapes_[i];
+ int local_offset = char_offset - first_char;
+ if (local_offset >= 0 && local_offset < shaping.char_length()) {
+ // The character is in this run, return the first glyph for it (should
+ // generally be the only glyph). It seems Uniscribe gives glyph 0 for
+ // empty, which is what we want to return in the "missing" case.
+ size_t glyph_index = shaping.logs[local_offset];
+ if (glyph_index >= shaping.glyphs->size()) {
+ // The glyph should be in this run, but the run has too few actual
+ // characters. This can happen when shaping the run fails, in which
+ // case, we should have no data in the logs at all.
+ DCHECK(shaping.glyphs->empty());
+ return 0;
+ }
+ return shaping.glyphs[glyph_index];
+ }
+ }
+ return 0;
+}
+
+void UniscribeState::FillRuns() {
+ HRESULT hr;
+ runs_->resize(UNISCRIBE_STATE_STACK_RUNS);
+
+ SCRIPT_STATE input_state;
+ input_state.uBidiLevel = is_rtl_;
+ input_state.fOverrideDirection = directional_override_;
+ input_state.fInhibitSymSwap = false;
+ input_state.fCharShape = false; // Not implemented in Uniscribe
+ input_state.fDigitSubstitute = false; // Do we want this for Arabic?
+ input_state.fInhibitLigate = inhibit_ligate_;
+ input_state.fDisplayZWG = false; // Don't draw control characters.
+ input_state.fArabicNumContext = is_rtl_; // Do we want this for Arabic?
+ input_state.fGcpClusters = false;
+ input_state.fReserved = 0;
+ input_state.fEngineReserved = 0;
+ // The psControl argument to ScriptItemize should be non-NULL for RTL text,
+ // per http://msdn.microsoft.com/en-us/library/ms776532.aspx . So use a
+ // SCRIPT_CONTROL that is set to all zeros. Zero as a locale ID means the
+ // neutral locale per http://msdn.microsoft.com/en-us/library/ms776294.aspx .
+ static SCRIPT_CONTROL input_control = {0, // uDefaultLanguage :16;
+ 0, // fContextDigits :1;
+ 0, // fInvertPreBoundDir :1;
+ 0, // fInvertPostBoundDir :1;
+ 0, // fLinkStringBefore :1;
+ 0, // fLinkStringAfter :1;
+ 0, // fNeutralOverride :1;
+ 0, // fNumericOverride :1;
+ 0, // fLegacyBidiClass :1;
+ 0, // fMergeNeutralItems :1;
+ 0};// fReserved :7;
+ // Calling ScriptApplyDigitSubstitution( NULL, &input_control, &input_state)
+ // here would be appropriate if we wanted to set the language ID, and get
+ // local digit substitution behavior. For now, don't do it.
+
+ while (true) {
+ int num_items = 0;
+
+ // Ideally, we would have a way to know the runs before and after this
+ // one, and put them into the control parameter of ScriptItemize. This
+ // would allow us to shape characters properly that cross style
+ // boundaries (WebKit bug 6148).
+ //
+ // We tell ScriptItemize that the output list of items is one smaller
+ // than it actually is. According to Mozilla bug 366643, if there is
+ // not enough room in the array on pre-SP2 systems, ScriptItemize will
+ // write one past the end of the buffer.
+ //
+ // ScriptItemize is very strange. It will often require a much larger
+ // ITEM buffer internally than it will give us as output. For example,
+ // it will say a 16-item buffer is not big enough, and will write
+ // interesting numbers into all those items. But when we give it a 32
+ // item buffer and it succeeds, it only has one item output.
+ //
+ // It seems to be doing at least two passes, the first where it puts a
+ // lot of intermediate data into our items, and the second where it
+ // collates them.
+ hr = ScriptItemize(input_, input_length_,
+ static_cast<int>(runs_->size()) - 1, &input_control, &input_state,
+ &runs_[0], &num_items);
+ if (SUCCEEDED(hr)) {
+ runs_->resize(num_items);
+ break;
+ }
+ if (hr != E_OUTOFMEMORY) {
+ // Some kind of unexpected error.
+ runs_->resize(0);
+ break;
+ }
+ // There was not enough items for it to write into, expand.
+ runs_->resize(runs_->size() * 2);
+ }
+
+ // Fix up the directions of the items so they're what WebKit thinks
+ // they are. WebKit (and we assume any other caller) always knows what
+ // direction it wants things to be in, and will only give us runs that are in
+ // the same direction. Sometimes, Uniscibe disagrees, for example, if you
+ // have embedded ASCII punctuation in an Arabic string, WebKit will
+ // (correctly) know that is should still be rendered RTL, but Uniscibe might
+ // think LTR is better.
+ //
+ // TODO(brettw) bug 747235:
+ // This workaround fixes the bug but causes spacing problems in other cases.
+ // WebKit sometimes gives us a big run that includes ASCII and Arabic, and
+ // this forcing direction makes those cases incorrect. This seems to happen
+ // during layout only, so it ends up that spacing is incorrect (because being
+ // the wrong direction changes ligatures and stuff).
+ //
+ //for (size_t i = 0; i < runs_->size(); i++)
+ // runs_[i].a.fRTL = is_rtl_;
+}
+
+
+bool UniscribeState::Shape(const wchar_t* input,
+ int item_length,
+ int num_glyphs,
+ SCRIPT_ITEM& run,
+ Shaping& shaping) {
+ HFONT hfont = hfont_;
+ SCRIPT_CACHE* script_cache = script_cache_;
+ SCRIPT_FONTPROPERTIES* font_properties = font_properties_;
+ int ascent = ascent_;
+ HDC temp_dc = NULL;
+ HGDIOBJ old_font = 0;
+ HRESULT hr;
+ bool lastFallbackTried = false;
+ bool result;
+
+ int generated_glyphs = 0;
+
+ // In case HFONT passed in ctor cannot render this run, we have to scan
+ // other fonts from the beginning of the font list.
+ ResetFontIndex();
+
+ // Compute shapes.
+ while (true) {
+ shaping.logs->resize(item_length);
+ shaping.glyphs->resize(num_glyphs);
+ shaping.visattr->resize(num_glyphs);
+
+ // Firefox sets SCRIPT_ANALYSIS.SCRIPT_STATE.fDisplayZWG to true
+ // here. Is that what we want? It will display control characters.
+ hr = ScriptShape(temp_dc, script_cache, input, item_length,
+ num_glyphs, &run.a,
+ &shaping.glyphs[0], &shaping.logs[0],
+ &shaping.visattr[0], &generated_glyphs);
+ if (hr == E_PENDING) {
+ // Allocate the DC.
+ temp_dc = GetDC(NULL);
+ old_font = SelectObject(temp_dc, hfont);
+ continue;
+ } else if (hr == E_OUTOFMEMORY) {
+ num_glyphs *= 2;
+ continue;
+ } else if (SUCCEEDED(hr) &&
+ (lastFallbackTried || !ContainsMissingGlyphs(&shaping.glyphs[0],
+ generated_glyphs, font_properties))) {
+ break;
+ }
+
+ // The current font can't render this run. clear DC and try
+ // next font.
+ if (temp_dc) {
+ SelectObject(temp_dc, old_font);
+ ReleaseDC(NULL, temp_dc);
+ temp_dc = NULL;
+ }
+
+ if (NextWinFontData(&hfont, &script_cache, &font_properties, &ascent)) {
+ // The primary font does not support this run. Try next font.
+ // In case of web page rendering, they come from fonts specified in
+ // CSS stylesheets.
+ continue;
+ } else if (!lastFallbackTried) {
+ lastFallbackTried = true;
+
+ // Generate a last fallback font based on the script of
+ // a character to draw while inheriting size and styles
+ // from the primary font
+ if (!logfont_.lfFaceName[0])
+ SetLogFontAndStyle(hfont_, &logfont_, &style_);
+
+ // TODO(jungshik): generic type should come from webkit for
+ // UniscribeStateTextRun (a derived class used in webkit).
+ const wchar_t *family = GetFallbackFamily(input, item_length,
+ GENERIC_FAMILY_STANDARD);
+ bool font_ok = GetDerivedFontData(family, style_, &logfont_, &ascent, &hfont, &script_cache);
+
+ if (!font_ok) {
+ // If this GetDerivedFontData is called from the renderer it might fail
+ // because the sandbox is preventing it from opening the font files.
+ // If we are running in the renderer, TryToPreloadFont is overridden to
+ // ask the browser to preload the font for us so we can access it.
+ TryToPreloadFont(hfont);
+
+ // Try again.
+ font_ok = GetDerivedFontData(family, style_, &logfont_, &ascent, &hfont, &script_cache);
+ DCHECK(font_ok);
+ }
+
+ // TODO(jungshik) : Currently GetDerivedHFont always returns a
+ // a valid HFONT, but in the future, I may change it to return 0.
+ DCHECK(hfont);
+
+ // We don't need a font_properties for the last resort fallback font
+ // because we don't have anything more to try and are forced to
+ // accept empty glyph boxes. If we tried a series of fonts as
+ // 'last-resort fallback', we'd need it, but currently, we don't.
+ continue;
+ } else if (hr == USP_E_SCRIPT_NOT_IN_FONT) {
+ run.a.eScript = SCRIPT_UNDEFINED;
+ continue;
+ } else if (FAILED(hr)) {
+ // Error shaping.
+ generated_glyphs = 0;
+ result = false;
+ goto cleanup;
+ }
+ }
+
+ // Sets Windows font data for this run to those corresponding to
+ // a font supporting this run. we don't need to store font_properties
+ // because it's not used elsewhere.
+ shaping.hfont_ = hfont;
+ shaping.script_cache_ = script_cache;
+
+ // The ascent of a font for this run can be different from
+ // that of the primary font so that we need to keep track of
+ // the difference per run and take that into account when calling
+ // ScriptTextOut in |Draw|. Otherwise, different runs rendered by
+ // different fonts would not be aligned vertically.
+ shaping.ascent_offset_ = ascent_ ? ascent - ascent_ : 0;
+ result = true;
+
+cleanup:
+ shaping.glyphs->resize(generated_glyphs);
+ shaping.visattr->resize(generated_glyphs);
+ shaping.advance->resize(generated_glyphs);
+ shaping.offsets->resize(generated_glyphs);
+ if (temp_dc) {
+ SelectObject(temp_dc, old_font);
+ ReleaseDC(NULL, temp_dc);
+ }
+ // On failure, our logs don't mean anything, so zero those out.
+ if (!result)
+ shaping.logs->clear();
+
+ return result;
+}
+
+void UniscribeState::FillShapes() {
+ shapes_->resize(runs_->size());
+ for (size_t i = 0; i < runs_->size(); i++) {
+ int start_item = runs_[i].iCharPos;
+ int item_length = input_length_ - start_item;
+ if (i < runs_->size() - 1)
+ item_length = runs_[i + 1].iCharPos - start_item;
+
+ int num_glyphs;
+ if (item_length < UNISCRIBE_STATE_STACK_CHARS) {
+ // We'll start our buffer sizes with the current stack space available
+ // in our buffers if the current input fits. As long as it
+ // doesn't expand past that we'll save a lot of time mallocing.
+ num_glyphs = UNISCRIBE_STATE_STACK_CHARS;
+ } else {
+ // When the input doesn't fit, give up with the stack since it will
+ // almost surely not be enough room (unless the input actually shrinks,
+ // which is unlikely) and just start with the length recommended by
+ // the Uniscribe documentation as a "usually fits" size.
+ num_glyphs = item_length * 3 / 2 + 16;
+ }
+
+ // Convert a string to a glyph string trying the primary font,
+ // fonts in the fallback list and then script-specific last resort font.
+ Shaping& shaping = shapes_[i];
+ if (!Shape(&input_[start_item], item_length, num_glyphs, runs_[i], shaping))
+ continue;
+
+ // Compute placements. Note that offsets is documented incorrectly
+ // and is actually an array.
+
+ // DC that we lazily create if Uniscribe commands us to.
+ // (this does not happen often because script_cache is already
+ // updated when calling ScriptShape).
+ HDC temp_dc = NULL;
+ HGDIOBJ old_font = NULL;
+ HRESULT hr;
+ while (true) {
+ shaping.pre_padding = 0;
+ hr = ScriptPlace(temp_dc, shaping.script_cache_, &shaping.glyphs[0],
+ static_cast<int>(shaping.glyphs->size()),
+ &shaping.visattr[0], &runs_[i].a,
+ &shaping.advance[0], &shaping.offsets[0],
+ &shaping.abc);
+ if (hr != E_PENDING)
+ break;
+
+ // Allocate the DC and run the loop again.
+ temp_dc = GetDC(NULL);
+ old_font = SelectObject(temp_dc, shaping.hfont_);
+ }
+
+ if (FAILED(hr)) {
+ // Some error we don't know how to handle. Nuke all of our data
+ // since we can't deal with partially valid data later.
+ runs_->clear();
+ shapes_->clear();
+ screen_order_->clear();
+ }
+
+ if (temp_dc) {
+ SelectObject(temp_dc, old_font);
+ ReleaseDC(NULL, temp_dc);
+ }
+ }
+
+ AdjustSpaceAdvances();
+
+ if (letter_spacing_ != 0 || word_spacing_ != 0)
+ ApplySpacing();
+}
+
+void UniscribeState::FillScreenOrder() {
+ screen_order_->resize(runs_->size());
+
+ // We assume that the input has only one text direction in it.
+ // TODO(brettw) are we sure we want to keep this restriction?
+ if (is_rtl_) {
+ for (int i = 0; i < static_cast<int>(screen_order_->size()); i++)
+ screen_order_[static_cast<int>(screen_order_->size()) - i - 1] = i;
+ } else {
+ for (int i = 0; i < static_cast<int>(screen_order_->size()); i++)
+ screen_order_[i] = i;
+ }
+}
+
+void UniscribeState::AdjustSpaceAdvances() {
+ if (space_width_ == 0)
+ return;
+
+ int space_width_without_letter_spacing = space_width_ - letter_spacing_;
+
+ // This mostly matches what WebKit's UniscribeController::shapeAndPlaceItem.
+ for (size_t run = 0; run < runs_->size(); run++) {
+ Shaping& shaping = shapes_[run];
+
+ for (int i = 0; i < shaping.char_length(); i++) {
+ if (!TreatAsSpace(input_[runs_[run].iCharPos + i]))
+ continue;
+
+ int glyph_index = shaping.logs[i];
+ int current_advance = shaping.advance[glyph_index];
+ // Don't give zero-width spaces a width.
+ if (!current_advance)
+ continue;
+
+ // current_advance does not include additional letter-spacing, but
+ // space_width does. Here we find out how off we are from the correct
+ // width for the space not including letter-spacing, then just subtract
+ // that diff.
+ int diff = current_advance - space_width_without_letter_spacing;
+ // The shaping can consist of a run of text, so only subtract the
+ // difference in the width of the glyph.
+ shaping.advance[glyph_index] -= diff;
+ shaping.abc.abcB -= diff;
+ }
+ }
+}
+
+void UniscribeState::ApplySpacing() {
+ for (size_t run = 0; run < runs_->size(); run++) {
+ Shaping& shaping = shapes_[run];
+ bool is_rtl = runs_[run].a.fRTL;
+
+ if (letter_spacing_ != 0) {
+ // RTL text gets padded to the left of each character. We increment the
+ // run's advance to make this happen. This will be balanced out by NOT
+ // adding additional advance to the last glyph in the run.
+ if (is_rtl)
+ shaping.pre_padding += letter_spacing_;
+
+ // Go through all the glyphs in this run and increase the "advance" to
+ // account for letter spacing. We adjust letter spacing only on cluster
+ // boundaries.
+ //
+ // This works for most scripts, but may have problems with some indic
+ // scripts. This behavior is better than Firefox or IE for Hebrew.
+ for (int i = 0; i < shaping.glyph_length(); i++) {
+ if (shaping.visattr[i].fClusterStart) {
+ // Ick, we need to assign the extra space so that the glyph comes
+ // first, then is followed by the space. This is opposite for RTL.
+ if (is_rtl) {
+ if (i != shaping.glyph_length() - 1) {
+ // All but the last character just get the spacing applied to
+ // their advance. The last character doesn't get anything,
+ shaping.advance[i] += letter_spacing_;
+ shaping.abc.abcB += letter_spacing_;
+ }
+ } else {
+ // LTR case is easier, we just add to the advance.
+ shaping.advance[i] += letter_spacing_;
+ shaping.abc.abcB += letter_spacing_;
+ }
+ }
+ }
+ }
+
+ // Go through all the characters to find whitespace and insert the extra
+ // wordspacing amount for the glyphs they correspond to.
+ if (word_spacing_ != 0) {
+ for (int i = 0; i < shaping.char_length(); i++) {
+ if (!TreatAsSpace(input_[runs_[run].iCharPos + i]))
+ continue;
+
+ // The char in question is a word separator...
+ int glyph_index = shaping.logs[i];
+
+ // Spaces will not have a glyph in Uniscribe, it will just add
+ // additional advance to the character to the left of the space. The
+ // space's corresponding glyph will be the character following it in
+ // reading order.
+ if (is_rtl) {
+ // In RTL, the glyph to the left of the space is the same as the
+ // first glyph of the following character, so we can just increment
+ // it.
+ shaping.advance[glyph_index] += word_spacing_;
+ shaping.abc.abcB += word_spacing_;
+ } else {
+ // LTR is actually more complex here, we apply it to the previous
+ // character if there is one, otherwise we have to apply it to the
+ // leading space of the run.
+ if (glyph_index == 0) {
+ shaping.pre_padding += word_spacing_;
+ } else {
+ shaping.advance[glyph_index - 1] += word_spacing_;
+ shaping.abc.abcB += word_spacing_;
+ }
+ }
+ }
+ } // word_spacing_ != 0
+
+ // Loop for next run...
+ }
+}
+
+// The advance is the ABC width of the run
+int UniscribeState::AdvanceForItem(int item_index) const {
+ int accum = 0;
+ const Shaping& shaping = shapes_[item_index];
+
+ if (shaping.justify->empty()) {
+ // Easy case with no justification, the width is just the ABC width of t
+ // the run. (The ABC width is the sum of the advances).
+ return shaping.abc.abcA + shaping.abc.abcB + shaping.abc.abcC +
+ shaping.pre_padding;
+ }
+
+ // With justification, we use the justified amounts instead. The
+ // justification array contains both the advance and the extra space
+ // added for justification, so is the width we want.
+ int justification = 0;
+ for (size_t i = 0; i < shaping.justify->size(); i++)
+ justification += shaping.justify[i];
+
+ return shaping.pre_padding + justification;
+}
+
+} // namespace gfx
diff --git a/base/gfx/uniscribe.h b/base/gfx/uniscribe.h
new file mode 100644
index 0000000..29f0d811
--- /dev/null
+++ b/base/gfx/uniscribe.h
@@ -0,0 +1,390 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+//
+// A wrapper around Uniscribe that provides a reasonable API.
+
+#ifndef BASE_GFX_UNISCRIBE_H__
+#define BASE_GFX_UNISCRIBE_H__
+
+#include <windows.h>
+#include <usp10.h>
+#include <wchar.h>
+#include <map>
+#include <vector>
+
+#include "base/stack_container.h"
+#include "testing/gtest/include/gtest/gtest_prod.h"
+
+namespace gfx {
+
+#define UNISCRIBE_STATE_STACK_RUNS 8
+#define UNISCRIBE_STATE_STACK_CHARS 32
+
+// This object should be safe to create & destroy frequently, as long as the
+// caller preserves the script_cache when possible (this data may be slow to
+// compute).
+//
+// This object is "kind of large" (~1K) because it reserves a lot of space for
+// working with to avoid expensive heap operations. Therefore, not only should
+// you not worry about creating and destroying it, you should try to not keep
+// them around.
+class UniscribeState {
+ public:
+ // Initializes this Uniscribe run with the text pointed to by |run| with
+ // |length|. The input is NOT null terminated.
+ //
+ // The is_rtl flag should be set if the input script is RTL. It is assumed
+ // that the caller has already divided up the input text (using ICU, for
+ // example) into runs of the same direction of script. This avoids
+ // disagreements between the caller and Uniscribe later (see FillItems).
+ //
+ // A script cache should be provided by the caller that is initialized to
+ // NULL. When the caller is done with the cache (it may be stored between
+ // runs as long as it is used consistently with the same HFONT), it should
+ // call ScriptFreeCache().
+ UniscribeState(const wchar_t* input,
+ int input_length,
+ bool is_rtl,
+ HFONT hfont,
+ SCRIPT_CACHE* script_cache,
+ SCRIPT_FONTPROPERTIES* font_properties);
+
+ virtual ~UniscribeState();
+
+ // Sets Uniscribe's directional override flag. False by default.
+ bool directional_override() const {
+ return directional_override_;
+ }
+ void set_directional_override(bool override) {
+ directional_override_ = override;
+ }
+
+ // Set's Uniscribe's no-ligate override flag. False by default.
+ bool inhibit_ligate() const {
+ return inhibit_ligate_;
+ }
+ void set_inhibit_ligate(bool inhibit) {
+ inhibit_ligate_ = inhibit;
+ }
+
+ // Set letter spacing. We will try to insert this much space between
+ // graphemes (one or more glyphs perceived as a single unit by ordinary users
+ // of a script). Positive values increase letter spacing, negative values
+ // decrease it. 0 by default.
+ int letter_spacing() const {
+ return letter_spacing_;
+ }
+ void set_letter_spacing(int letter_spacing) {
+ letter_spacing_ = letter_spacing;
+ }
+
+ // Set the width of a standard space character. We use this to normalize
+ // space widths. Windows will make spaces after Hindi characters larger than
+ // other spaces. A space_width of 0 means to use the default space width.
+ //
+ // Must be set before Init() is called.
+ int space_width() const {
+ return space_width_;
+ }
+ void set_space_width(int space_width) {
+ space_width_ = space_width;
+ }
+
+ // Set word spacing. We will try to insert this much extra space between
+ // each word in the input (beyond whatever whitespace character separates
+ // words). Positive values lead to increased letter spacing, negative values
+ // decrease it. 0 by default.
+ //
+ // Must be set before Init() is called.
+ int word_spacing() const {
+ return word_spacing_;
+ }
+ void set_word_spacing(int word_spacing) {
+ word_spacing_ = word_spacing;
+ }
+ void set_ascent(int ascent) {
+ ascent_ = ascent;
+ }
+
+ // You must call this after setting any options but before doing any
+ // other calls like asking for widths or drawing.
+ void Init() { InitWithOptionalLengthProtection(true); }
+
+ // Returns the total width in pixels of the text run.
+ int Width() const;
+
+ // Call to justify the text, with the amount of space that should be ADDED to
+ // get the desired width that the column should be justified to. Normally,
+ // spaces are inserted, but for Arabic there will be kashidas (extra strokes)
+ // inserted instead.
+ //
+ // This function MUST be called AFTER Init().
+ void Justify(int additional_space);
+
+ // Computes the given character offset into a pixel offset of the beginning
+ // of that character.
+ int CharacterToX(int offset) const;
+
+ // Converts the given pixel X position into a logical character offset into
+ // the run. For positions appearing before the first character, this will
+ // return -1.
+ int XToCharacter(int x) const;
+
+ // Draws the given characters to (x, y) in the given DC. The font will be
+ // handled by this function, but the font color and other attributes should
+ // be pre-set.
+ //
+ // The y position is the upper left corner, NOT the baseline.
+ void Draw(HDC dc, int x, int y, int from, int to);
+
+ // Returns the first glyph assigned to the character at the given offset.
+ // This function is used to retrieve glyph information when Uniscribe is
+ // being used to generate glyphs for non-complex, non-BMP (above U+FFFF)
+ // characters. These characters are not otherwise special and have no
+ // complex shaping rules, so we don't otherwise need Uniscribe, except
+ // Uniscribe is the only way to get glyphs for non-BMP characters.
+ //
+ // Returns 0 if there is no glyph for the given character.
+ WORD FirstGlyphForCharacter(int char_offset) const;
+
+ protected:
+ // Backend for init. The flag allows the unit test to specify whether we
+ // should fail early for very long strings like normal, or try to pass the
+ // long string to Uniscribe. The latter provides a way to force failure of
+ // shaping.
+ void InitWithOptionalLengthProtection(bool length_protection);
+
+ // Tries to preload the font when the it is not accessible.
+ // This is the default implementation and it does not do anything.
+ virtual void TryToPreloadFont(HFONT font) {}
+
+ private:
+ FRIEND_TEST(UniscribeTest, TooBig);
+
+ // An array corresponding to each item in runs_ containing information
+ // on each of the glyphs that were generated. Like runs_, this is in
+ // reading order. However, for rtl text, the characters within each
+ // item will be reversed.
+ struct Shaping {
+ Shaping()
+ : pre_padding(0),
+ hfont_(NULL),
+ script_cache_(NULL),
+ ascent_offset_(0) {
+ abc.abcA = 0;
+ abc.abcB = 0;
+ abc.abcC = 0;
+ }
+
+ // Returns the number of glyphs (which will be drawn to the screen)
+ // in this run.
+ int glyph_length() const {
+ return static_cast<int>(glyphs->size());
+ }
+
+ // Returns the number of characters (that we started with) in this run.
+ int char_length() const {
+ return static_cast<int>(logs->size());
+ }
+
+ // Returns the advance array that should be used when measuring glyphs.
+ // The returned pointer will indicate an array with glyph_length() elements
+ // and the advance that should be used for each one. This is either the
+ // real advance, or the justified advances if there is one, and is the
+ // array we want to use for measurement.
+ const int* effective_advances() const {
+ if (advance->empty())
+ return 0;
+ if (justify->empty())
+ return &advance[0];
+ return &justify[0];
+ }
+
+ // This is the advance amount of space that we have added to the beginning
+ // of the run. It is like the ABC's |A| advance but one that we create and
+ // must handle internally whenever computing with pixel offsets.
+ int pre_padding;
+
+ // Glyph indices in the font used to display this item. These indices
+ // are in screen order.
+ StackVector<WORD, UNISCRIBE_STATE_STACK_CHARS> glyphs;
+
+ // For each input character, this tells us the first glyph index it
+ // generated. This is the only array with size of the input chars.
+ //
+ // All offsets are from the beginning of this run. Multiple characters can
+ // generate one glyph, in which case there will be adjacent duplicates in
+ // this list. One character can also generate multiple glyphs, in which
+ // case there will be skipped indices in this list.
+ StackVector<WORD, UNISCRIBE_STATE_STACK_CHARS> logs;
+
+ // Flags and such for each glyph.
+ StackVector<SCRIPT_VISATTR, UNISCRIBE_STATE_STACK_CHARS> visattr;
+
+ // Horizontal advances for each glyph listed above, this is basically
+ // how wide each glyph is.
+ StackVector<int, UNISCRIBE_STATE_STACK_CHARS> advance;
+
+ // This contains glyph offsets, from the nominal position of a glyph. It
+ // is used to adjust the positions of multiple combining characters
+ // around/above/below base characters in a context-sensitive manner so
+ // that they don't bump against each other and the base character.
+ StackVector<GOFFSET, UNISCRIBE_STATE_STACK_CHARS> offsets;
+
+ // Filled by a call to Justify, this is empty for nonjustified text.
+ // If nonempty, this contains the array of justify characters for each
+ // character as returned by ScriptJustify.
+ //
+ // This is the same as the advance array, but with extra space added for
+ // some characters. The difference between a glyph's |justify| width and
+ // it's |advance| width is the extra space added.
+ StackVector<int, UNISCRIBE_STATE_STACK_CHARS> justify;
+
+ // Sizing information for this run. This treats the entire run as a
+ // character with a preceeding advance, width, and ending advance.
+ // The B width is the sum of the |advance| array, and the A and C widths
+ // are any extra spacing applied to each end.
+ //
+ // It is unclear from the documentation what this actually means. From
+ // experimentation, it seems that the sum of the character advances is
+ // always the sum of the ABC values, and I'm not sure what you're supposed
+ // to do with the ABC values.
+ ABC abc;
+
+ // Pointers to windows font data used to render this run.
+ HFONT hfont_;
+ SCRIPT_CACHE* script_cache_;
+
+ // Ascent offset between the ascent of the primary font
+ // and that of the fallback font. The offset needs to be applied,
+ // when drawing a string, to align multiple runs rendered with
+ // different fonts.
+ int ascent_offset_;
+ };
+
+ // Computes the runs_ array from the text run.
+ void FillRuns();
+
+ // Computes the shapes_ array given an runs_ array already filled in.
+ void FillShapes();
+
+ // Fills in the screen_order_ array (see below).
+ void FillScreenOrder();
+
+ // Called to update the glyph positions based on the current spacing options
+ // that are set.
+ void ApplySpacing();
+
+ // Normalizes all advances for spaces to the same width. This keeps windows
+ // from making spaces after Hindi characters larger, which is then
+ // inconsistent with our meaure of the width since WebKit doesn't include
+ // spaces in text-runs sent to uniscribe unless white-space:pre.
+ void AdjustSpaceAdvances();
+
+ // Returns the total width of a single item.
+ int AdvanceForItem(int item_index) const;
+
+ // Shapes a run (pointed to by |input|) using |hfont| first.
+ // Tries a series of fonts specified retrieved with NextWinFontData
+ // and finally a font covering characters in |*input|. A string pointed
+ // by |input| comes from ScriptItemize and is supposed to contain
+ // characters belonging to a single script aside from characters
+ // common to all scripts (e.g. space).
+ bool Shape(const wchar_t* input,
+ int item_length,
+ int num_glyphs,
+ SCRIPT_ITEM& run,
+ Shaping& shaping);
+
+ // Gets Windows font data for the next best font to try in the list
+ // of fonts. When there's no more font available, returns false
+ // without touching any of out params. Need to call ResetFontIndex
+ // to start scanning of the font list from the beginning.
+ virtual bool NextWinFontData(HFONT* hfont,
+ SCRIPT_CACHE** script_cache,
+ SCRIPT_FONTPROPERTIES** font_properties,
+ int* ascent) {
+ return false;
+ }
+
+ // Resets the font index to the first in the list of fonts
+ // to try after the primaryFont turns out not to work. With font_index
+ // reset, NextWinFontData scans fallback fonts from the beginning.
+ virtual void ResetFontIndex() {}
+
+ // The input data for this run of Uniscribe. See the constructor.
+ const wchar_t* input_;
+ const int input_length_;
+ const bool is_rtl_;
+
+ // Windows font data for the primary font :
+ // In a sense, logfont_ and style_ are redundant because
+ // hfont_ contains all the information. However, invoking GetObject,
+ // everytime we need the height and the style, is rather expensive so
+ // that we cache them. Would it be better to add getter and (virtual)
+ // setter for the height and the style of the primary font, instead of
+ // logfont_? Then, a derived class ctor can set ascent_, height_ and style_
+ // if they're known. Getters for them would have to 'infer' their values from
+ // hfont_ ONLY when they're not set.
+ HFONT hfont_;
+ SCRIPT_CACHE* script_cache_;
+ SCRIPT_FONTPROPERTIES* font_properties_;
+ int ascent_;
+ LOGFONT logfont_;
+ int style_;
+
+ // Options, see the getters/setters above.
+ bool directional_override_;
+ bool inhibit_ligate_;
+ int letter_spacing_;
+ int space_width_;
+ int word_spacing_;
+ int justification_width_;
+
+ // Uniscribe breaks the text into Runs. These are one length of text that is
+ // in one script and one direction. This array is in reading order.
+ StackVector<SCRIPT_ITEM, UNISCRIBE_STATE_STACK_RUNS> runs_;
+
+ StackVector<Shaping, UNISCRIBE_STATE_STACK_RUNS> shapes_;
+
+ // This is a mapping between reading order and screen order for the items.
+ // Uniscribe's items array are in reading order. For right-to-left text,
+ // or mixed (although WebKit's |TextRun| should really be only one
+ // direction), this makes it very difficult to compute character offsets
+ // and positions. This list is in screen order from left to right, and
+ // gives the index into the |runs_| and |shapes_| arrays of each
+ // subsequent item.
+ StackVector<int, UNISCRIBE_STATE_STACK_RUNS> screen_order_;
+
+ DISALLOW_EVIL_CONSTRUCTORS(UniscribeState);
+};
+
+} // namespace gfx
+
+#endif // BASE_GFX_UNISCRIBE_H__
diff --git a/base/gfx/uniscribe_unittest.cc b/base/gfx/uniscribe_unittest.cc
new file mode 100644
index 0000000..8b2419c
--- /dev/null
+++ b/base/gfx/uniscribe_unittest.cc
@@ -0,0 +1,164 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "base/gfx/uniscribe.h"
+#include "base/win_util.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+// This must be in the gfx namespace for the friend statements in uniscribe.h
+// to work.
+namespace gfx {
+
+namespace {
+
+class UniscribeTest : public testing::Test {
+ public:
+ UniscribeTest() {
+ }
+
+ // Returns an HFONT with the given name. The caller does not have to free
+ // this, it will be automatically freed at the end of the test. Returns NULL
+ // on failure. On success, the
+ HFONT MakeFont(const wchar_t* font_name, SCRIPT_CACHE** cache) {
+ LOGFONT lf;
+ memset(&lf, 0, sizeof(LOGFONT));
+ lf.lfHeight = 20;
+ wcscpy_s(lf.lfFaceName, font_name);
+
+ HFONT hfont = CreateFontIndirect(&lf);
+ if (!hfont)
+ return NULL;
+
+ *cache = new SCRIPT_CACHE;
+ **cache = NULL;
+ created_fonts_.push_back(std::make_pair(hfont, *cache));
+ return hfont;
+ }
+
+ protected:
+ // Default font properties structure for tests to use.
+ SCRIPT_FONTPROPERTIES properties_;
+
+ private:
+ virtual void SetUp() {
+ memset(&properties_, 0, sizeof(SCRIPT_FONTPROPERTIES));
+ properties_.cBytes = sizeof(SCRIPT_FONTPROPERTIES);
+ properties_.wgBlank = ' ';
+ properties_.wgDefault = '?'; // Used when the character is not in the font.
+ properties_.wgInvalid = '#'; // Used for invalid characters.
+ }
+
+ virtual void TearDown() {
+ // Free any allocated fonts.
+ for (size_t i = 0; i < created_fonts_.size(); i++) {
+ DeleteObject(created_fonts_[i].first);
+ ScriptFreeCache(created_fonts_[i].second);
+ delete created_fonts_[i].second;
+ }
+ created_fonts_.clear();
+ }
+
+ // Tracks allocated fonts so we can delete them at the end of the test.
+ // The script cache pointer is heap allocated and must be freed.
+ std::vector< std::pair<HFONT, SCRIPT_CACHE*> > created_fonts_;
+
+ DISALLOW_EVIL_CONSTRUCTORS(UniscribeTest);
+};
+
+} // namespace
+
+// This test tests giving Uniscribe a very large buffer, which will cause a
+// failure.
+TEST_F(UniscribeTest, TooBig) {
+ // This test will only run on Windows XP. It seems Uniscribe does not have the
+ // internal limit on Windows 2000 that we rely on to cause this failure.
+ if (win_util::GetWinVersion() <= win_util::WINVERSION_2000)
+ return;
+
+ // Make a large string with an e with a zillion combining accents.
+ std::wstring input(L"e");
+ for (int i = 0; i < 100000; i++)
+ input.push_back(0x301); // Combining acute accent.
+
+ SCRIPT_CACHE* script_cache;
+ HFONT hfont = MakeFont(L"Times New Roman", &script_cache);
+ ASSERT_TRUE(hfont);
+
+ // Test a long string without the normal length protection we have. This will
+ // cause shaping to fail.
+ {
+ gfx::UniscribeState uniscribe(input.data(), static_cast<int>(input.size()),
+ false, hfont, script_cache, &properties_);
+ uniscribe.InitWithOptionalLengthProtection(false);
+
+ // There should be one shaping entry, with nothing in it.
+ ASSERT_EQ(1, uniscribe.shapes_->size());
+ EXPECT_EQ(0, uniscribe.shapes_[0].glyphs->size());
+ EXPECT_EQ(0, uniscribe.shapes_[0].logs->size());
+ EXPECT_EQ(0, uniscribe.shapes_[0].visattr->size());
+ EXPECT_EQ(0, uniscribe.shapes_[0].advance->size());
+ EXPECT_EQ(0, uniscribe.shapes_[0].offsets->size());
+ EXPECT_EQ(0, uniscribe.shapes_[0].justify->size());
+ EXPECT_EQ(0, uniscribe.shapes_[0].abc.abcA);
+ EXPECT_EQ(0, uniscribe.shapes_[0].abc.abcB);
+ EXPECT_EQ(0, uniscribe.shapes_[0].abc.abcC);
+
+ // The sizes of the other stuff should match the shaping entry.
+ EXPECT_EQ(1, uniscribe.runs_->size());
+ EXPECT_EQ(1, uniscribe.screen_order_->size());
+
+ // Check that the various querying functions handle the empty case properly.
+ EXPECT_EQ(0, uniscribe.Width());
+ EXPECT_EQ(0, uniscribe.FirstGlyphForCharacter(0));
+ EXPECT_EQ(0, uniscribe.FirstGlyphForCharacter(1000));
+ EXPECT_EQ(0, uniscribe.XToCharacter(0));
+ EXPECT_EQ(0, uniscribe.XToCharacter(1000));
+ }
+
+ // Now test the very large string and make sure it is handled properly by the
+ // length protection.
+ {
+ gfx::UniscribeState uniscribe(input.data(), static_cast<int>(input.size()),
+ false, hfont, script_cache, &properties_);
+ uniscribe.InitWithOptionalLengthProtection(true);
+
+ // There should be 0 runs and shapes.
+ EXPECT_EQ(0, uniscribe.runs_->size());
+ EXPECT_EQ(0, uniscribe.shapes_->size());
+ EXPECT_EQ(0, uniscribe.screen_order_->size());
+
+ EXPECT_EQ(0, uniscribe.Width());
+ EXPECT_EQ(0, uniscribe.FirstGlyphForCharacter(0));
+ EXPECT_EQ(0, uniscribe.FirstGlyphForCharacter(1000));
+ EXPECT_EQ(0, uniscribe.XToCharacter(0));
+ EXPECT_EQ(0, uniscribe.XToCharacter(1000));
+ }
+}
+
+} // namespace gfx \ No newline at end of file
diff --git a/base/gfx/vector_canvas.cc b/base/gfx/vector_canvas.cc
new file mode 100644
index 0000000..6f02f11
--- /dev/null
+++ b/base/gfx/vector_canvas.cc
@@ -0,0 +1,109 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#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) {
+ initialize(dc, width, height);
+}
+
+VectorCanvas::~VectorCanvas() {
+}
+
+void VectorCanvas::initialize(HDC context, int width, int height) {
+ SkDevice* device = createPlatformDevice(width, height, true, context);
+ setDevice(device);
+ device->unref(); // was created with refcount 1, and setDevice also refs
+}
+
+SkBounder* VectorCanvas::setBounder(SkBounder* bounder) {
+ if (!IsTopDeviceVectorial())
+ return PlatformCanvas::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
+ // BitmapPlatformDevice is created when VectorCanvas think a saveLayers()
+ // call is being done. The way to save a layer would be to create an
+ // EMF-based VectorDevice and have this device registers the drawing. When
+ // playing back the device into a bitmap, do it at the printer's dpi instead
+ // of the layout's dpi (which is much lower).
+ return PlatformCanvas::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);
+ PlatformDevice* device = VectorDevice::create(
+ reinterpret_cast<HDC>(shared_section), width, height);
+ return device;
+}
+
+bool VectorCanvas::IsTopDeviceVectorial() const {
+ return getTopPlatformDevice().IsVectorial();
+}
+
+} // namespace gfx
diff --git a/base/gfx/vector_canvas.h b/base/gfx/vector_canvas.h
new file mode 100644
index 0000000..d6a5703
--- /dev/null
+++ b/base/gfx/vector_canvas.h
@@ -0,0 +1,70 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#ifndef BASE_GFX_VECTOR_CANVAS_H__
+#define BASE_GFX_VECTOR_CANVAS_H__
+
+#include "base/gfx/platform_canvas.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 PlatformCanvas {
+ public:
+ VectorCanvas();
+ VectorCanvas(HDC dc, int width, int height);
+ virtual ~VectorCanvas();
+
+ // For two-part init, call if you use the no-argument constructor above
+ void 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_EVIL_CONSTRUCTORS(VectorCanvas);
+};
+
+} // namespace gfx
+
+#endif // BASE_GFX_VECTOR_CANVAS_H__
diff --git a/base/gfx/vector_canvas_unittest.cc b/base/gfx/vector_canvas_unittest.cc
new file mode 100644
index 0000000..57ff34d
--- /dev/null
+++ b/base/gfx/vector_canvas_unittest.cc
@@ -0,0 +1,1032 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "base/gfx/vector_canvas.h"
+
+#include <vector>
+
+#include "base/command_line.h"
+#include "base/file_util.h"
+#include "base/gfx/bitmap_header.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::PlatformCanvas& 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;
+ ASSERT_EQ(_wfopen_s(&f, filename.c_str(), L"wbS"), 0);
+ ASSERT_EQ(fwrite(&*compressed.begin(), 1, compressed.size(), f),
+ compressed.size());
+ fclose(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::PlatformCanvas& 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::PlatformCanvas& 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::PlatformCanvas& 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() << ", " << 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::PlatformCanvas(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::PlatformCanvas* 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::PlatformCanvas(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/base/gfx/vector_device.cc b/base/gfx/vector_device.cc
new file mode 100644
index 0000000..c7e20f6
--- /dev/null
+++ b/base/gfx/vector_device.cc
@@ -0,0 +1,646 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "base/gfx/vector_device.h"
+
+#include "base/gfx/bitmap_header.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)
+ : PlatformDevice(bitmap),
+ hdc_(dc),
+ previous_brush_(NULL),
+ previous_pen_(NULL),
+ offset_x_(0),
+ offset_y_(0) {
+ 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();
+ PlatformDevice::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::setDeviceOffset(int x, int y) {
+ offset_x_ = x;
+ offset_y_ = y;
+}
+
+void VectorDevice::drawToHDC(HDC dc, int x, int y, const RECT* src_rect) {
+ NOTREACHED();
+}
+
+void VectorDevice::LoadClipRegion() {
+ // We don't use transform_ for the clipping region since the translation is
+ // already applied to offset_x_ and offset_y_.
+ SkMatrix t;
+ t.reset();
+ t.postTranslate(SkIntToScalar(-offset_x_), SkIntToScalar(-offset_y_));
+ 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 = AlphaBlend(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/base/gfx/vector_device.h b/base/gfx/vector_device.h
new file mode 100644
index 0000000..002ce17
--- /dev/null
+++ b/base/gfx/vector_device.h
@@ -0,0 +1,146 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#ifndef BASE_GFX_VECTOR_DEVICE_H__
+#define BASE_GFX_VECTOR_DEVICE_H__
+
+#include "base/basictypes.h"
+#include "base/gfx/platform_device.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 PlatformDevice {
+ 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 setDeviceOffset(int x, int y);
+ 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_;
+
+ int offset_x_;
+ int offset_y_;
+
+ DISALLOW_EVIL_CONSTRUCTORS(VectorDevice);
+};
+
+} // namespace gfx
+
+#endif // BASE_GFX_VECTOR_DEVICE_H__