diff options
author | initial.commit <initial.commit@0039d316-1c4b-4281-b951-d872f2087c98> | 2008-07-26 21:49:38 +0000 |
---|---|---|
committer | initial.commit <initial.commit@0039d316-1c4b-4281-b951-d872f2087c98> | 2008-07-26 21:49:38 +0000 |
commit | d7cae12696b96500c05dd2d430f6238922c20c96 (patch) | |
tree | ecff27b367735535b2a66477f8cd89d3c462a6c0 /base/gfx | |
parent | ee2815e28d408216cf94e874825b6bcf76c69083 (diff) | |
download | chromium_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')
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__ |