diff options
author | sail@chromium.org <sail@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-02-02 23:03:07 +0000 |
---|---|---|
committer | sail@chromium.org <sail@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-02-02 23:03:07 +0000 |
commit | 267c03d478d9c83ef8c37999f3abb1cd822881ff (patch) | |
tree | 6286c650d0ec58cdee9c7482fda929252bd985b5 /ui | |
parent | 7c085463177741a3844215675577bba3de1be836 (diff) | |
download | chromium_src-267c03d478d9c83ef8c37999f3abb1cd822881ff.zip chromium_src-267c03d478d9c83ef8c37999f3abb1cd822881ff.tar.gz chromium_src-267c03d478d9c83ef8c37999f3abb1cd822881ff.tar.bz2 |
Move src/gfx/ to src/ui/gfx
To reduce the size of this change I've left stub header files in src/gfx/. Once all includes have been updated I'll delete the stub files.
BUG=71063
TEST=Still doing test builds.
Review URL: http://codereview.chromium.org/6246027
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@73530 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'ui')
94 files changed, 15079 insertions, 1 deletions
diff --git a/ui/base/ui_base.gypi b/ui/base/ui_base.gypi index 3822d25..71443f6 100644 --- a/ui/base/ui_base.gypi +++ b/ui/base/ui_base.gypi @@ -46,7 +46,7 @@ 'type': '<(library)', 'dependencies': [ '../base/base.gyp:base', - '../gfx/gfx.gyp:gfx', + '../ui/gfx/gfx.gyp:gfx', '../skia/skia.gyp:skia', '../third_party/icu/icu.gyp:icui18n', '../third_party/icu/icu.gyp:icuuc', diff --git a/ui/gfx/DEPS b/ui/gfx/DEPS new file mode 100644 index 0000000..548fe15 --- /dev/null +++ b/ui/gfx/DEPS @@ -0,0 +1,5 @@ +include_rules = [ + "+base", + "+grit/gfx_resources.h", + "+skia", +] diff --git a/ui/gfx/blit.cc b/ui/gfx/blit.cc new file mode 100644 index 0000000..8853339 --- /dev/null +++ b/ui/gfx/blit.cc @@ -0,0 +1,202 @@ +// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "gfx/blit.h" + +#include "base/logging.h" +#include "build/build_config.h" +#include "gfx/point.h" +#include "gfx/rect.h" +#include "skia/ext/platform_canvas.h" +#include "skia/ext/platform_device.h" + +#if defined(OS_POSIX) && !defined(OS_MACOSX) +#include <cairo/cairo.h> +#endif + +#if defined(OS_MACOSX) +#include "base/mac/scoped_cftyperef.h" +#endif + +namespace gfx { + +namespace { + +// Returns true if the given canvas has any part of itself clipped out or +// any non-identity tranform. +bool HasClipOrTransform(const skia::PlatformCanvas& canvas) { + if (!canvas.getTotalMatrix().isIdentity()) + return true; + + const SkRegion& clip_region = canvas.getTotalClip(); + if (clip_region.isEmpty() || clip_region.isComplex()) + return true; + + // Now we know the clip is a regular rectangle, make sure it covers the + // entire canvas. + const SkBitmap& bitmap = canvas.getTopPlatformDevice().accessBitmap(false); + const SkIRect& clip_bounds = clip_region.getBounds(); + if (clip_bounds.fLeft != 0 || clip_bounds.fTop != 0 || + clip_bounds.fRight != bitmap.width() || + clip_bounds.fBottom != bitmap.height()) + return true; + + return false; +} + +} // namespace + +void BlitContextToContext(NativeDrawingContext dst_context, + const Rect& dst_rect, + NativeDrawingContext src_context, + const Point& src_origin) { +#if defined(OS_WIN) + BitBlt(dst_context, dst_rect.x(), dst_rect.y(), + dst_rect.width(), dst_rect.height(), + src_context, src_origin.x(), src_origin.y(), SRCCOPY); +#elif defined(OS_MACOSX) + // Only translations and/or vertical flips in the source context are + // supported; more complex source context transforms will be ignored. + + // If there is a translation on the source context, we need to account for + // it ourselves since CGBitmapContextCreateImage will bypass it. + Rect src_rect(src_origin, dst_rect.size()); + CGAffineTransform transform = CGContextGetCTM(src_context); + bool flipped = fabs(transform.d + 1) < 0.0001; + CGFloat delta_y = flipped ? CGBitmapContextGetHeight(src_context) - + transform.ty + : transform.ty; + src_rect.Offset(transform.tx, delta_y); + + base::mac::ScopedCFTypeRef<CGImageRef> + src_image(CGBitmapContextCreateImage(src_context)); + base::mac::ScopedCFTypeRef<CGImageRef> src_sub_image( + CGImageCreateWithImageInRect(src_image, src_rect.ToCGRect())); + CGContextDrawImage(dst_context, dst_rect.ToCGRect(), src_sub_image); +#else // Linux, BSD, others + // Only translations in the source context are supported; more complex + // source context transforms will be ignored. + cairo_save(dst_context); + double surface_x = src_origin.x(); + double surface_y = src_origin.y(); + cairo_user_to_device(src_context, &surface_x, &surface_y); + cairo_set_source_surface(dst_context, cairo_get_target(src_context), + dst_rect.x()-surface_x, dst_rect.y()-surface_y); + cairo_rectangle(dst_context, dst_rect.x(), dst_rect.y(), + dst_rect.width(), dst_rect.height()); + cairo_clip(dst_context); + cairo_paint(dst_context); + cairo_restore(dst_context); +#endif +} + +static NativeDrawingContext GetContextFromCanvas( + skia::PlatformCanvas *canvas) { + skia::PlatformDevice& device = canvas->getTopPlatformDevice(); +#if defined(OS_WIN) + return device.getBitmapDC(); +#elif defined(OS_MACOSX) + return device.GetBitmapContext(); +#else // Linux, BSD, others + return device.beginPlatformPaint(); +#endif +} + +void BlitContextToCanvas(skia::PlatformCanvas *dst_canvas, + const Rect& dst_rect, + NativeDrawingContext src_context, + const Point& src_origin) { + BlitContextToContext(GetContextFromCanvas(dst_canvas), dst_rect, + src_context, src_origin); +} + +void BlitCanvasToContext(NativeDrawingContext dst_context, + const Rect& dst_rect, + skia::PlatformCanvas *src_canvas, + const Point& src_origin) { + BlitContextToContext(dst_context, dst_rect, + GetContextFromCanvas(src_canvas), src_origin); +} + +void BlitCanvasToCanvas(skia::PlatformCanvas *dst_canvas, + const Rect& dst_rect, + skia::PlatformCanvas *src_canvas, + const Point& src_origin) { + BlitContextToContext(GetContextFromCanvas(dst_canvas), dst_rect, + GetContextFromCanvas(src_canvas), src_origin); +} + +#if defined(OS_WIN) + +void ScrollCanvas(skia::PlatformCanvas* canvas, + const gfx::Rect& clip, + const gfx::Point& amount) { + DCHECK(!HasClipOrTransform(*canvas)); // Don't support special stuff. + HDC hdc = canvas->beginPlatformPaint(); + + RECT damaged_rect; + RECT r = clip.ToRECT(); + ScrollDC(hdc, amount.x(), amount.y(), NULL, &r, NULL, &damaged_rect); + + canvas->endPlatformPaint(); +} + +#elif defined(OS_POSIX) +// Cairo has no nice scroll function so we do our own. On Mac it's possible to +// use platform scroll code, but it's complex so we just use the same path +// here. Either way it will be software-only, so it shouldn't matter much. + +void ScrollCanvas(skia::PlatformCanvas* canvas, + const gfx::Rect& in_clip, + const gfx::Point& amount) { + DCHECK(!HasClipOrTransform(*canvas)); // Don't support special stuff. + SkBitmap& bitmap = const_cast<SkBitmap&>( + canvas->getTopPlatformDevice().accessBitmap(true)); + SkAutoLockPixels lock(bitmap); + + // We expect all coords to be inside the canvas, so clip here. + gfx::Rect clip = in_clip.Intersect( + gfx::Rect(0, 0, bitmap.width(), bitmap.height())); + + // Compute the set of pixels we'll actually end up painting. + gfx::Rect dest_rect = clip; + dest_rect.Offset(amount); + dest_rect = dest_rect.Intersect(clip); + if (dest_rect.size() == gfx::Size()) + return; // Nothing to do. + + // Compute the source pixels that will map to the dest_rect + gfx::Rect src_rect = dest_rect; + src_rect.Offset(-amount.x(), -amount.y()); + + size_t row_bytes = dest_rect.width() * 4; + if (amount.y() > 0) { + // Data is moving down, copy from the bottom up. + for (int y = dest_rect.height() - 1; y >= 0; y--) { + memcpy(bitmap.getAddr32(dest_rect.x(), dest_rect.y() + y), + bitmap.getAddr32(src_rect.x(), src_rect.y() + y), + row_bytes); + } + } else if (amount.y() < 0) { + // Data is moving up, copy from the top down. + for (int y = 0; y < dest_rect.height(); y++) { + memcpy(bitmap.getAddr32(dest_rect.x(), dest_rect.y() + y), + bitmap.getAddr32(src_rect.x(), src_rect.y() + y), + row_bytes); + } + } else if (amount.x() != 0) { + // Horizontal-only scroll. We can do it in either top-to-bottom or bottom- + // to-top, but have to be careful about the order for copying each row. + // Fortunately, memmove already handles this for us. + for (int y = 0; y < dest_rect.height(); y++) { + memmove(bitmap.getAddr32(dest_rect.x(), dest_rect.y() + y), + bitmap.getAddr32(src_rect.x(), src_rect.y() + y), + row_bytes); + } + } +} + +#endif + +} // namespace gfx diff --git a/ui/gfx/blit.h b/ui/gfx/blit.h new file mode 100644 index 0000000..d5b1b5e --- /dev/null +++ b/ui/gfx/blit.h @@ -0,0 +1,53 @@ +// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_BLIT_H_ +#define UI_GFX_BLIT_H_ +#pragma once + +#include "gfx/native_widget_types.h" + +namespace skia { +class PlatformCanvas; +} // namespace skia + +namespace gfx { + +class Point; +class Rect; + +// Blits a rectangle from the source context into the destination context. +void BlitContextToContext(NativeDrawingContext dst_context, + const Rect& dst_rect, + NativeDrawingContext src_context, + const Point& src_origin); + +// Blits a rectangle from the source context into the destination canvas. +void BlitContextToCanvas(skia::PlatformCanvas *dst_canvas, + const Rect& dst_rect, + NativeDrawingContext src_context, + const Point& src_origin); + +// Blits a rectangle from the source canvas into the destination context. +void BlitCanvasToContext(NativeDrawingContext dst_context, + const Rect& dst_rect, + skia::PlatformCanvas *src_canvas, + const Point& src_origin); + +// Blits a rectangle from the source canvas into the destination canvas. +void BlitCanvasToCanvas(skia::PlatformCanvas *dst_canvas, + const Rect& dst_rect, + skia::PlatformCanvas *src_canvas, + const Point& src_origin); + +// Scrolls the given subset of the given canvas by the given amount. +// The canvas should not have a clip or a transform applied, since platforms +// may implement those operations differently. +void ScrollCanvas(skia::PlatformCanvas* canvas, + const Rect& clip, + const Point& amount); + +} // namespace gfx + +#endif // UI_GFX_BLIT_H_ diff --git a/ui/gfx/blit_unittest.cc b/ui/gfx/blit_unittest.cc new file mode 100644 index 0000000..cea3296 --- /dev/null +++ b/ui/gfx/blit_unittest.cc @@ -0,0 +1,135 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/basictypes.h" +#include "gfx/blit.h" +#include "gfx/point.h" +#include "gfx/rect.h" +#include "skia/ext/platform_canvas.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace { + +// Fills the given canvas with the values by duplicating the values into each +// color channel for the corresponding pixel. +// +// Example values = {{0x0, 0x01}, {0x12, 0xFF}} would give a canvas with: +// 0x00000000 0x01010101 +// 0x12121212 0xFFFFFFFF +template<int w, int h> +void SetToCanvas(skia::PlatformCanvas* canvas, uint8 values[h][w]) { + SkBitmap& bitmap = const_cast<SkBitmap&>( + canvas->getTopPlatformDevice().accessBitmap(true)); + SkAutoLockPixels lock(bitmap); + ASSERT_EQ(w, bitmap.width()); + ASSERT_EQ(h, bitmap.height()); + + for (int y = 0; y < h; y++) { + for (int x = 0; x < w; x++) { + uint8 value = values[y][x]; + *bitmap.getAddr32(x, y) = + (value << 24) | (value << 16) | (value << 8) | value; + } + } +} + +// Checks each pixel in the given canvas and see if it is made up of the given +// values, where each value has been duplicated into each channel of the given +// bitmap (see SetToCanvas above). +template<int w, int h> +void VerifyCanvasValues(skia::PlatformCanvas* canvas, uint8 values[h][w]) { + SkBitmap& bitmap = const_cast<SkBitmap&>( + canvas->getTopPlatformDevice().accessBitmap(true)); + SkAutoLockPixels lock(bitmap); + ASSERT_EQ(w, bitmap.width()); + ASSERT_EQ(h, bitmap.height()); + + for (int y = 0; y < h; y++) { + for (int x = 0; x < w; x++) { + uint8 value = values[y][x]; + uint32 expected = + (value << 24) | (value << 16) | (value << 8) | value; + ASSERT_EQ(expected, *bitmap.getAddr32(x, y)); + } + } +} + +} // namespace + +TEST(Blit, ScrollCanvas) { + static const int kCanvasWidth = 5; + static const int kCanvasHeight = 5; + skia::PlatformCanvas canvas(kCanvasWidth, kCanvasHeight, true); + uint8 initial_values[kCanvasHeight][kCanvasWidth] = { + { 0x00, 0x01, 0x02, 0x03, 0x04 }, + { 0x10, 0x11, 0x12, 0x13, 0x14 }, + { 0x20, 0x21, 0x22, 0x23, 0x24 }, + { 0x30, 0x31, 0x32, 0x33, 0x34 }, + { 0x40, 0x41, 0x42, 0x43, 0x44 }}; + SetToCanvas<5, 5>(&canvas, initial_values); + + // Sanity check on input. + VerifyCanvasValues<5, 5>(&canvas, initial_values); + + // Scroll none and make sure it's a NOP. + gfx::ScrollCanvas(&canvas, + gfx::Rect(0, 0, kCanvasWidth, kCanvasHeight), + gfx::Point(0, 0)); + VerifyCanvasValues<5, 5>(&canvas, initial_values); + + // Scroll the center 3 pixels up one. + gfx::Rect center_three(1, 1, 3, 3); + gfx::ScrollCanvas(&canvas, center_three, gfx::Point(0, -1)); + uint8 scroll_up_expected[kCanvasHeight][kCanvasWidth] = { + { 0x00, 0x01, 0x02, 0x03, 0x04 }, + { 0x10, 0x21, 0x22, 0x23, 0x14 }, + { 0x20, 0x31, 0x32, 0x33, 0x24 }, + { 0x30, 0x31, 0x32, 0x33, 0x34 }, + { 0x40, 0x41, 0x42, 0x43, 0x44 }}; + VerifyCanvasValues<5, 5>(&canvas, scroll_up_expected); + + // Reset and scroll the center 3 pixels down one. + SetToCanvas<5, 5>(&canvas, initial_values); + gfx::ScrollCanvas(&canvas, center_three, gfx::Point(0, 1)); + uint8 scroll_down_expected[kCanvasHeight][kCanvasWidth] = { + { 0x00, 0x01, 0x02, 0x03, 0x04 }, + { 0x10, 0x11, 0x12, 0x13, 0x14 }, + { 0x20, 0x11, 0x12, 0x13, 0x24 }, + { 0x30, 0x21, 0x22, 0x23, 0x34 }, + { 0x40, 0x41, 0x42, 0x43, 0x44 }}; + VerifyCanvasValues<5, 5>(&canvas, scroll_down_expected); + + // Reset and scroll the center 3 pixels right one. + SetToCanvas<5, 5>(&canvas, initial_values); + gfx::ScrollCanvas(&canvas, center_three, gfx::Point(1, 0)); + uint8 scroll_right_expected[kCanvasHeight][kCanvasWidth] = { + { 0x00, 0x01, 0x02, 0x03, 0x04 }, + { 0x10, 0x11, 0x11, 0x12, 0x14 }, + { 0x20, 0x21, 0x21, 0x22, 0x24 }, + { 0x30, 0x31, 0x31, 0x32, 0x34 }, + { 0x40, 0x41, 0x42, 0x43, 0x44 }}; + VerifyCanvasValues<5, 5>(&canvas, scroll_right_expected); + + // Reset and scroll the center 3 pixels left one. + SetToCanvas<5, 5>(&canvas, initial_values); + gfx::ScrollCanvas(&canvas, center_three, gfx::Point(-1, 0)); + uint8 scroll_left_expected[kCanvasHeight][kCanvasWidth] = { + { 0x00, 0x01, 0x02, 0x03, 0x04 }, + { 0x10, 0x12, 0x13, 0x13, 0x14 }, + { 0x20, 0x22, 0x23, 0x23, 0x24 }, + { 0x30, 0x32, 0x33, 0x33, 0x34 }, + { 0x40, 0x41, 0x42, 0x43, 0x44 }}; + VerifyCanvasValues<5, 5>(&canvas, scroll_left_expected); + + // Diagonal scroll. + SetToCanvas<5, 5>(&canvas, initial_values); + gfx::ScrollCanvas(&canvas, center_three, gfx::Point(2, 2)); + uint8 scroll_diagonal_expected[kCanvasHeight][kCanvasWidth] = { + { 0x00, 0x01, 0x02, 0x03, 0x04 }, + { 0x10, 0x11, 0x12, 0x13, 0x14 }, + { 0x20, 0x21, 0x22, 0x23, 0x24 }, + { 0x30, 0x31, 0x32, 0x11, 0x34 }, + { 0x40, 0x41, 0x42, 0x43, 0x44 }}; + VerifyCanvasValues<5, 5>(&canvas, scroll_diagonal_expected); +} diff --git a/ui/gfx/brush.h b/ui/gfx/brush.h new file mode 100644 index 0000000..d6e92af --- /dev/null +++ b/ui/gfx/brush.h @@ -0,0 +1,24 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_BRUSH_H_ +#define UI_GFX_BRUSH_H_ +#pragma once + +namespace gfx { + +// An object that encapsulates a platform native brush. +// Subclasses handle memory management of the underlying native brush. +class Brush { + public: + Brush() {} + virtual ~Brush() {} + + private: + DISALLOW_COPY_AND_ASSIGN(Brush); +}; + +} // namespace gfx + +#endif // UI_GFX_BRUSH_H_ diff --git a/ui/gfx/canvas.cc b/ui/gfx/canvas.cc new file mode 100644 index 0000000..28e6a8a --- /dev/null +++ b/ui/gfx/canvas.cc @@ -0,0 +1,17 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "gfx/canvas.h" + +namespace gfx { + +CanvasSkia* Canvas::AsCanvasSkia() { + return NULL; +} + +const CanvasSkia* Canvas::AsCanvasSkia() const { + return NULL; +} + +} // namespace gfx; diff --git a/ui/gfx/canvas.h b/ui/gfx/canvas.h new file mode 100644 index 0000000..9517602 --- /dev/null +++ b/ui/gfx/canvas.h @@ -0,0 +1,238 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_CANVAS_H_ +#define UI_GFX_CANVAS_H_ +#pragma once + +#include <string> + +#include "base/string16.h" +#include "gfx/native_widget_types.h" +// TODO(beng): remove this include when we no longer depend on SkTypes. +#include "skia/ext/platform_canvas.h" + +namespace gfx { + +class Brush; +class CanvasSkia; +class Font; +class Point; +class Rect; + +// TODO(beng): documentation. +class Canvas { + public: + // Specifies the alignment for text rendered with the DrawStringInt method. + enum { + TEXT_ALIGN_LEFT = 1, + TEXT_ALIGN_CENTER = 2, + TEXT_ALIGN_RIGHT = 4, + TEXT_VALIGN_TOP = 8, + TEXT_VALIGN_MIDDLE = 16, + TEXT_VALIGN_BOTTOM = 32, + + // Specifies the text consists of multiple lines. + MULTI_LINE = 64, + + // By default DrawStringInt does not process the prefix ('&') character + // specially. That is, the string "&foo" is rendered as "&foo". When + // rendering text from a resource that uses the prefix character for + // mnemonics, the prefix should be processed and can be rendered as an + // underline (SHOW_PREFIX), or not rendered at all (HIDE_PREFIX). + SHOW_PREFIX = 128, + HIDE_PREFIX = 256, + + // Prevent ellipsizing + NO_ELLIPSIS = 512, + + // Specifies if words can be split by new lines. + // This only works with MULTI_LINE. + CHARACTER_BREAK = 1024, + + // Instructs DrawStringInt() to render the text using RTL directionality. + // In most cases, passing this flag is not necessary because information + // about the text directionality is going to be embedded within the string + // in the form of special Unicode characters. However, we don't insert + // directionality characters into strings if the locale is LTR because some + // platforms (for example, an English Windows XP with no RTL fonts + // installed) don't support these characters. Thus, this flag should be + // used to render text using RTL directionality when the locale is LTR. + FORCE_RTL_DIRECTIONALITY = 2048, + }; + + virtual ~Canvas() {} + + // Creates an empty canvas. Must be initialized before it can be used. + static Canvas* CreateCanvas(); + + // Creates a canvas with the specified size. + static Canvas* CreateCanvas(int width, int height, bool is_opaque); + + // Saves a copy of the drawing state onto a stack, operating on this copy + // until a balanced call to Restore() is made. + virtual void Save() = 0; + + // As with Save(), except draws to a layer that is blended with the canvas + // at the specified alpha once Restore() is called. + // |layer_bounds| are the bounds of the layer relative to the current + // transform. + virtual void SaveLayerAlpha(uint8 alpha) = 0; + virtual void SaveLayerAlpha(uint8 alpha, const gfx::Rect& layer_bounds) = 0; + + // Restores the drawing state after a call to Save*(). It is an error to + // call Restore() more times than Save*(). + virtual void Restore() = 0; + + // Wrapper function that takes integer arguments. + // Returns true if the clip is non-empty. + // See clipRect for specifics. + virtual bool ClipRectInt(int x, int y, int w, int h) = 0; + + // Wrapper function that takes integer arguments. + // See translate() for specifics. + virtual void TranslateInt(int x, int y) = 0; + + // Wrapper function that takes integer arguments. + // See scale() for specifics. + virtual void ScaleInt(int x, int y) = 0; + + // Fills the specified region with the specified color using a transfer + // mode of SkXfermode::kSrcOver_Mode. + virtual void FillRectInt(const SkColor& color, + int x, int y, int w, int h) = 0; + + // Fills the specified region with the specified color and mode + virtual void FillRectInt(const SkColor& color, + int x, int y, int w, int h, + SkXfermode::Mode mode) = 0; + + // Fills the specified region with the specified brush. + virtual void FillRectInt(const gfx::Brush* brush, + int x, int y, int w, int h) = 0; + + // Draws a single pixel rect in the specified region with the specified + // color, using a transfer mode of SkXfermode::kSrcOver_Mode. + // + // NOTE: if you need a single pixel line, use DrawLineInt. + virtual void DrawRectInt(const SkColor& color, + int x, int y, int w, int h) = 0; + + // Draws a single pixel rect in the specified region with the specified + // color and transfer mode. + // + // NOTE: if you need a single pixel line, use DrawLineInt. + virtual void DrawRectInt(const SkColor& color, + int x, int y, int w, int h, + SkXfermode::Mode mode) = 0; + + // Draws the given rectangle with the given paint's parameters. + virtual void DrawRectInt(int x, int y, int w, int h, + const SkPaint& paint) = 0; + + // Draws a single pixel line with the specified color. + virtual void DrawLineInt(const SkColor& color, + int x1, int y1, + int x2, int y2) = 0; + + // Draws a bitmap with the origin at the specified location. The upper left + // corner of the bitmap is rendered at the specified location. + virtual void DrawBitmapInt(const SkBitmap& bitmap, int x, int y) = 0; + + // Draws a bitmap with the origin at the specified location, using the + // specified paint. The upper left corner of the bitmap is rendered at the + // specified location. + virtual void DrawBitmapInt(const SkBitmap& bitmap, + int x, int y, + const SkPaint& paint) = 0; + + // Draws a portion of a bitmap in the specified location. The src parameters + // correspond to the region of the bitmap to draw in the region defined + // by the dest coordinates. + // + // If the width or height of the source differs from that of the destination, + // the bitmap will be scaled. When scaling down, it is highly recommended + // that you call buildMipMap(false) on your bitmap to ensure that it has + // a mipmap, which will result in much higher-quality output. Set |filter| + // to use filtering for bitmaps, otherwise the nearest-neighbor algorithm + // is used for resampling. + // + // An optional custom SkPaint can be provided. + virtual void DrawBitmapInt(const SkBitmap& bitmap, + int src_x, int src_y, int src_w, int src_h, + int dest_x, int dest_y, int dest_w, int dest_h, + bool filter) = 0; + virtual void DrawBitmapInt(const SkBitmap& bitmap, + int src_x, int src_y, int src_w, int src_h, + int dest_x, int dest_y, int dest_w, int dest_h, + bool filter, + const SkPaint& paint) = 0; + + // Draws text with the specified color, font and location. The text is + // aligned to the left, vertically centered, clipped to the region. If the + // text is too big, it is truncated and '...' is added to the end. + virtual void DrawStringInt(const string16& text, const + gfx::Font& font, + const SkColor& color, + int x, int y, int w, int h) = 0; + virtual void DrawStringInt(const string16& text, + const gfx::Font& font, + const SkColor& color, + const gfx::Rect& display_rect) = 0; + + // Draws text with the specified color, font and location. The last argument + // specifies flags for how the text should be rendered. It can be one of + // TEXT_ALIGN_CENTER, TEXT_ALIGN_RIGHT or TEXT_ALIGN_LEFT. + virtual void DrawStringInt(const string16& text, + const gfx::Font& font, + const SkColor& color, + int x, int y, int w, int h, + int flags) = 0; + + // Draws a dotted gray rectangle used for focus purposes. + virtual void DrawFocusRect(int x, int y, int width, int height) = 0; + + // Tiles the image in the specified region. + virtual void TileImageInt(const SkBitmap& bitmap, + int x, int y, int w, int h) = 0; + virtual void TileImageInt(const SkBitmap& bitmap, + int src_x, int src_y, + int dest_x, int dest_y, int w, int h) = 0; + + // Returns a native drawing context for platform specific drawing routines to + // use. Must be balanced by a call to EndPlatformPaint(). + virtual gfx::NativeDrawingContext BeginPlatformPaint() = 0; + + // Signifies the end of platform drawing using the native drawing context + // returned by BeginPlatformPaint(). + virtual void EndPlatformPaint() = 0; + + // TODO(beng): remove this once we don't need to use any skia-specific methods + // through this interface. + // A quick and dirty way to obtain the underlying SkCanvas. + virtual CanvasSkia* AsCanvasSkia(); + virtual const CanvasSkia* AsCanvasSkia() const; +}; + +class CanvasPaint { + public: + virtual ~CanvasPaint() {} + + // Creates a canvas that paints to |view| when it is destroyed. The canvas is + // sized to the client area of |view|. + static CanvasPaint* CreateCanvasPaint(gfx::NativeView view); + + // Returns true if the canvas has an invalid rect that needs to be repainted. + virtual bool IsValid() const = 0; + + // Returns the rectangle that is invalid. + virtual gfx::Rect GetInvalidRect() const = 0; + + // Returns the underlying Canvas. + virtual Canvas* AsCanvas() = 0; +}; + +} // namespace gfx; + +#endif // UI_GFX_CANVAS_H_ diff --git a/ui/gfx/canvas_direct2d.cc b/ui/gfx/canvas_direct2d.cc new file mode 100644 index 0000000..cba7b64 --- /dev/null +++ b/ui/gfx/canvas_direct2d.cc @@ -0,0 +1,362 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "gfx/canvas_direct2d.h" + +#include "base/scoped_ptr.h" +#include "gfx/brush.h" +#include "gfx/rect.h" + +namespace { + +// Converts a SkColor to a ColorF. +D2D1_COLOR_F SkColorToColorF(SkColor color) { + return D2D1::ColorF(static_cast<float>(SkColorGetR(color)) / 0xFF, + static_cast<float>(SkColorGetG(color)) / 0xFF, + static_cast<float>(SkColorGetB(color)) / 0xFF, + static_cast<float>(SkColorGetA(color)) / 0xFF); +} + +D2D1_RECT_F RectToRectF(int x, int y, int w, int h) { + return D2D1::RectF(static_cast<float>(x), static_cast<float>(y), + static_cast<float>(x + w), static_cast<float>(y + h)); +} + +D2D1_RECT_F RectToRectF(const gfx::Rect& rect) { + return RectToRectF(rect.x(), rect.y(), rect.width(), rect.height()); +} + +D2D1_POINT_2F PointToPoint2F(int x, int y) { + return D2D1::Point2F(static_cast<float>(x), static_cast<float>(y)); +} + +D2D1_BITMAP_INTERPOLATION_MODE FilterToInterpolationMode(bool filter) { + return filter ? D2D1_BITMAP_INTERPOLATION_MODE_LINEAR + : D2D1_BITMAP_INTERPOLATION_MODE_NEAREST_NEIGHBOR; +} + +// Creates a Direct2D bitmap object from the contents of a SkBitmap. The caller +// is responsible for releasing this object. +ID2D1Bitmap* CreateD2D1BitmapFromSkBitmap(ID2D1RenderTarget* render_target, + const SkBitmap& bitmap) { + ID2D1Bitmap* d2d1_bitmap = NULL; + HRESULT hr = render_target->CreateBitmap( + D2D1::SizeU(bitmap.width(), bitmap.height()), + NULL, + NULL, + D2D1::BitmapProperties( + D2D1::PixelFormat(DXGI_FORMAT_B8G8R8A8_UNORM, + D2D1_ALPHA_MODE_IGNORE)), + &d2d1_bitmap); + if (FAILED(hr)) + return NULL; + bitmap.lockPixels(); + d2d1_bitmap->CopyFromMemory(NULL, bitmap.getPixels(), bitmap.rowBytes()); + bitmap.unlockPixels(); + return d2d1_bitmap; +} + +// Creates a Direct2D bitmap brush from the contents of a SkBitmap. The caller +// is responsible for releasing this object. +ID2D1Brush* CreateD2D1BrushFromSkBitmap(ID2D1RenderTarget* render_target, + const SkBitmap& bitmap, + D2D1_EXTEND_MODE extend_mode_x, + D2D1_EXTEND_MODE extend_mode_y) { + ScopedComPtr<ID2D1Bitmap> d2d1_bitmap( + CreateD2D1BitmapFromSkBitmap(render_target, bitmap)); + + ID2D1BitmapBrush* brush = NULL; + render_target->CreateBitmapBrush( + d2d1_bitmap, + D2D1::BitmapBrushProperties(extend_mode_x, extend_mode_y), + D2D1::BrushProperties(), + &brush); + return brush; +} + +// A platform wrapper for a Direct2D brush that makes sure the underlying +// ID2D1Brush COM object is released when this object is destroyed. +class Direct2DBrush : public gfx::Brush { + public: + explicit Direct2DBrush(ID2D1Brush* brush) : brush_(brush) { + } + + ID2D1Brush* brush() const { return brush_.get(); } + + private: + ScopedComPtr<ID2D1Brush> brush_; + + DISALLOW_COPY_AND_ASSIGN(Direct2DBrush); +}; + + +} // namespace + +namespace gfx { + +// static +ID2D1Factory* CanvasDirect2D::d2d1_factory_ = NULL; + +//////////////////////////////////////////////////////////////////////////////// +// CanvasDirect2D, public: + +CanvasDirect2D::CanvasDirect2D(ID2D1RenderTarget* rt) : rt_(rt) { + // A RenderState entry is pushed onto the stack to track the clip count prior + // to any calls to Save*(). + state_.push(RenderState()); + rt_->BeginDraw(); +} + +CanvasDirect2D::~CanvasDirect2D() { + // Unwind any clips that were pushed outside of any Save*()/Restore() pairs. + int clip_count = state_.top().clip_count; + for (int i = 0; i < clip_count; ++i) + rt_->PopAxisAlignedClip(); + rt_->EndDraw(); +} + +// static +ID2D1Factory* CanvasDirect2D::GetD2D1Factory() { + if (!d2d1_factory_) + D2D1CreateFactory(D2D1_FACTORY_TYPE_SINGLE_THREADED, &d2d1_factory_); + return d2d1_factory_; +} + +//////////////////////////////////////////////////////////////////////////////// +// CanvasDirect2D, Canvas implementation: + +void CanvasDirect2D::Save() { + SaveInternal(NULL); +} + +void CanvasDirect2D::SaveLayerAlpha(uint8 alpha) { + SaveLayerAlpha(alpha, gfx::Rect()); +} + +void CanvasDirect2D::SaveLayerAlpha(uint8 alpha, + const gfx::Rect& layer_bounds) { + D2D1_RECT_F bounds = D2D1::InfiniteRect(); + if (!layer_bounds.IsEmpty()) + bounds = RectToRectF(layer_bounds); + ID2D1Layer* layer = NULL; + HRESULT hr = rt_->CreateLayer(NULL, &layer); + if (SUCCEEDED(hr)) { + rt_->PushLayer(D2D1::LayerParameters(bounds, + NULL, + D2D1_ANTIALIAS_MODE_PER_PRIMITIVE, + D2D1::IdentityMatrix(), + static_cast<float>(alpha) / 0xFF, + NULL, + D2D1_LAYER_OPTIONS_NONE), + layer); + } + SaveInternal(layer); +} + +void CanvasDirect2D::Restore() { + ID2D1Layer* layer = state_.top().layer; + if (layer) { + rt_->PopLayer(); + layer->Release(); + } + + int clip_count = state_.top().clip_count; + for (int i = 0; i < clip_count; ++i) + rt_->PopAxisAlignedClip(); + + state_.pop(); + // The state_ stack should never be empty - we should always have at least one + // entry to hold a clip count when there is no active save/restore entry. + CHECK(!state_.empty()) << "Called Restore() once too often!"; + + rt_->RestoreDrawingState(drawing_state_block_); +} + +bool CanvasDirect2D::ClipRectInt(int x, int y, int w, int h) { + rt_->PushAxisAlignedClip(RectToRectF(x, y, w, h), + D2D1_ANTIALIAS_MODE_PER_PRIMITIVE); + // Increment the clip count so the call to PushAxisAlignedClip() can be + // balanced with a call to PopAxisAlignedClip in the next Restore(). + ++state_.top().clip_count; + return w > 0 && h > 0; +} + +void CanvasDirect2D::TranslateInt(int x, int y) { + D2D1_MATRIX_3X2_F raw; + rt_->GetTransform(&raw); + D2D1::Matrix3x2F transform(raw._11, raw._12, raw._21, raw._22, raw._31, + raw._32); + transform = D2D1::Matrix3x2F::Translation(static_cast<float>(x), + static_cast<float>(y)) * transform; + rt_->SetTransform(transform); +} + +void CanvasDirect2D::ScaleInt(int x, int y) { + D2D1_MATRIX_3X2_F raw; + rt_->GetTransform(&raw); + D2D1::Matrix3x2F transform(raw._11, raw._12, raw._21, raw._22, raw._31, + raw._32); + transform = D2D1::Matrix3x2F::Scale(static_cast<float>(x), + static_cast<float>(y)) * transform; + rt_->SetTransform(transform); +} + +void CanvasDirect2D::FillRectInt(const SkColor& color, + int x, int y, int w, int h) { + ScopedComPtr<ID2D1SolidColorBrush> solid_brush; + rt_->CreateSolidColorBrush(SkColorToColorF(color), solid_brush.Receive()); + rt_->FillRectangle(RectToRectF(x, y, w, h), solid_brush); +} + +void CanvasDirect2D::FillRectInt(const SkColor& color, + int x, int y, int w, int h, + SkXfermode::Mode mode) { + NOTIMPLEMENTED(); +} + +void CanvasDirect2D::FillRectInt(const gfx::Brush* brush, + int x, int y, int w, int h) { + const Direct2DBrush* d2d_brush = static_cast<const Direct2DBrush*>(brush); + rt_->FillRectangle(RectToRectF(x, y, w, h), d2d_brush->brush()); +} + +void CanvasDirect2D::DrawRectInt(const SkColor& color, + int x, int y, int w, int h) { + ScopedComPtr<ID2D1SolidColorBrush> solid_brush; + rt_->CreateSolidColorBrush(SkColorToColorF(color), solid_brush.Receive()); + rt_->DrawRectangle(RectToRectF(x, y, w, h), solid_brush); +} + +void CanvasDirect2D::DrawRectInt(const SkColor& color, + int x, int y, int w, int h, + SkXfermode::Mode mode) { + NOTIMPLEMENTED(); +} + +void CanvasDirect2D::DrawRectInt(int x, int y, int w, int h, + const SkPaint& paint) { + NOTIMPLEMENTED(); +} + +void CanvasDirect2D::DrawLineInt(const SkColor& color, + int x1, int y1, + int x2, int y2) { + ScopedComPtr<ID2D1SolidColorBrush> solid_brush; + rt_->CreateSolidColorBrush(SkColorToColorF(color), solid_brush.Receive()); + rt_->DrawLine(PointToPoint2F(x1, y1), PointToPoint2F(x2, y2), solid_brush); +} + +void CanvasDirect2D::DrawBitmapInt(const SkBitmap& bitmap, int x, int y) { + ScopedComPtr<ID2D1Bitmap> d2d1_bitmap( + CreateD2D1BitmapFromSkBitmap(rt_, bitmap)); + rt_->DrawBitmap(d2d1_bitmap, + RectToRectF(x, y, bitmap.width(), bitmap.height()), + 1.0f, + D2D1_BITMAP_INTERPOLATION_MODE_NEAREST_NEIGHBOR, + RectToRectF(0, 0, bitmap.width(), bitmap.height())); +} + +void CanvasDirect2D::DrawBitmapInt(const SkBitmap& bitmap, + int x, int y, + const SkPaint& paint) { + NOTIMPLEMENTED(); +} + +void CanvasDirect2D::DrawBitmapInt(const SkBitmap& bitmap, + int src_x, int src_y, int src_w, int src_h, + int dest_x, int dest_y, + int dest_w, int dest_h, + bool filter) { + ScopedComPtr<ID2D1Bitmap> d2d1_bitmap( + CreateD2D1BitmapFromSkBitmap(rt_, bitmap)); + rt_->DrawBitmap(d2d1_bitmap, + RectToRectF(dest_x, dest_y, dest_w, dest_h), + 1.0f, + FilterToInterpolationMode(filter), + RectToRectF(src_x, src_y, src_w, src_h)); +} + +void CanvasDirect2D::DrawBitmapInt(const SkBitmap& bitmap, + int src_x, int src_y, int src_w, int src_h, + int dest_x, int dest_y, + int dest_w, int dest_h, + bool filter, + const SkPaint& paint) { + NOTIMPLEMENTED(); +} + +void CanvasDirect2D::DrawStringInt(const string16& text, + const gfx::Font& font, + const SkColor& color, + int x, int y, int w, int h) { + NOTIMPLEMENTED(); +} + +void CanvasDirect2D::DrawStringInt(const string16& text, + const gfx::Font& font, + const SkColor& color, + const gfx::Rect& display_rect) { + NOTIMPLEMENTED(); +} + +void CanvasDirect2D::DrawStringInt(const string16& text, + const gfx::Font& font, + const SkColor& color, + int x, int y, int w, int h, + int flags) { + NOTIMPLEMENTED(); +} + +void CanvasDirect2D::DrawFocusRect(int x, int y, int width, int height) { + NOTIMPLEMENTED(); +} + +void CanvasDirect2D::TileImageInt(const SkBitmap& bitmap, + int x, int y, int w, int h) { + ScopedComPtr<ID2D1Brush> brush( + CreateD2D1BrushFromSkBitmap(rt_, bitmap, D2D1_EXTEND_MODE_WRAP, + D2D1_EXTEND_MODE_WRAP)); + rt_->FillRectangle(RectToRectF(x, y, w, h), brush); +} + +void CanvasDirect2D::TileImageInt(const SkBitmap& bitmap, + int src_x, int src_y, + int dest_x, int dest_y, int w, int h) { + NOTIMPLEMENTED(); +} + +gfx::NativeDrawingContext CanvasDirect2D::BeginPlatformPaint() { + DCHECK(!interop_rt_.get()); + interop_rt_.QueryFrom(rt_); + HDC dc = NULL; + if (interop_rt_.get()) + interop_rt_->GetDC(D2D1_DC_INITIALIZE_MODE_COPY, &dc); + return dc; +} + +void CanvasDirect2D::EndPlatformPaint() { + DCHECK(interop_rt_.get()); + interop_rt_->ReleaseDC(NULL); + interop_rt_.release(); +} + +CanvasSkia* CanvasDirect2D::AsCanvasSkia() { + return NULL; +} + +const CanvasSkia* CanvasDirect2D::AsCanvasSkia() const { + return NULL; +} + +//////////////////////////////////////////////////////////////////////////////// +// CanvasDirect2D, private: + +void CanvasDirect2D::SaveInternal(ID2D1Layer* layer) { + if (!drawing_state_block_) + GetD2D1Factory()->CreateDrawingStateBlock(drawing_state_block_.Receive()); + rt_->SaveDrawingState(drawing_state_block_.get()); + state_.push(RenderState(layer)); +} + +} // namespace gfx diff --git a/ui/gfx/canvas_direct2d.h b/ui/gfx/canvas_direct2d.h new file mode 100644 index 0000000..101688a --- /dev/null +++ b/ui/gfx/canvas_direct2d.h @@ -0,0 +1,112 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_CANVAS_DIRECT2D_H_ +#define UI_GFX_CANVAS_DIRECT2D_H_ +#pragma once + +#include <d2d1.h> + +#include <stack> + +#include "base/scoped_comptr_win.h" +#include "gfx/canvas.h" + +namespace gfx { + +class CanvasDirect2D : public Canvas { + public: + // Creates an empty Canvas. + explicit CanvasDirect2D(ID2D1RenderTarget* rt); + virtual ~CanvasDirect2D(); + + // Retrieves the application's D2D1 Factory. + static ID2D1Factory* GetD2D1Factory(); + + // Overridden from Canvas: + virtual void Save(); + virtual void SaveLayerAlpha(uint8 alpha); + virtual void SaveLayerAlpha(uint8 alpha, const gfx::Rect& layer_bounds); + virtual void Restore(); + virtual bool ClipRectInt(int x, int y, int w, int h); + virtual void TranslateInt(int x, int y); + virtual void ScaleInt(int x, int y); + virtual void FillRectInt(const SkColor& color, int x, int y, int w, int h); + virtual void FillRectInt(const SkColor& color, int x, int y, int w, int h, + SkXfermode::Mode mode); + virtual void FillRectInt(const gfx::Brush* brush, int x, int y, int w, int h); + virtual void DrawRectInt(const SkColor& color, int x, int y, int w, int h); + virtual void DrawRectInt(const SkColor& color, + int x, int y, int w, int h, + SkXfermode::Mode mode); + virtual void DrawRectInt(int x, int y, int w, int h, const SkPaint& paint); + virtual void DrawLineInt(const SkColor& color, + int x1, int y1, + int x2, int y2); + virtual void DrawBitmapInt(const SkBitmap& bitmap, int x, int y); + virtual void DrawBitmapInt(const SkBitmap& bitmap, + int x, int y, + const SkPaint& paint); + virtual void DrawBitmapInt(const SkBitmap& bitmap, + int src_x, int src_y, int src_w, int src_h, + int dest_x, int dest_y, int dest_w, int dest_h, + bool filter); + virtual void DrawBitmapInt(const SkBitmap& bitmap, + int src_x, int src_y, int src_w, int src_h, + int dest_x, int dest_y, int dest_w, int dest_h, + bool filter, + const SkPaint& paint); + virtual void DrawStringInt(const string16& text, + const gfx::Font& font, + const SkColor& color, + int x, int y, int w, int h); + virtual void DrawStringInt(const string16& text, + const gfx::Font& font, + const SkColor& color, + const gfx::Rect& display_rect); + virtual void DrawStringInt(const string16& text, + const gfx::Font& font, + const SkColor& color, + int x, int y, int w, int h, + int flags); + virtual void DrawFocusRect(int x, int y, int width, int height); + virtual void TileImageInt(const SkBitmap& bitmap, int x, int y, int w, int h); + virtual void TileImageInt(const SkBitmap& bitmap, + int src_x, int src_y, + int dest_x, int dest_y, int w, int h); + virtual gfx::NativeDrawingContext BeginPlatformPaint(); + virtual void EndPlatformPaint(); + virtual CanvasSkia* AsCanvasSkia(); + virtual const CanvasSkia* AsCanvasSkia() const; + + private: + void SaveInternal(ID2D1Layer* layer); + + ID2D1RenderTarget* rt_; + ScopedComPtr<ID2D1GdiInteropRenderTarget> interop_rt_; + ScopedComPtr<ID2D1DrawingStateBlock> drawing_state_block_; + static ID2D1Factory* d2d1_factory_; + + // Every time Save* is called, a RenderState object is pushed onto the + // RenderState stack. + struct RenderState { + explicit RenderState(ID2D1Layer* layer) : layer(layer), clip_count(0) {} + RenderState() : layer(NULL), clip_count(0) {} + + // A D2D layer associated with this state, or NULL if there is no layer. + // The layer is created and owned by the Canvas. + ID2D1Layer* layer; + // The number of clip operations performed. This is used to balance calls to + // PushAxisAlignedClip with calls to PopAxisAlignedClip when Restore() is + // called. + int clip_count; + }; + std::stack<RenderState> state_; + + DISALLOW_COPY_AND_ASSIGN(CanvasDirect2D); +}; + +} // namespace gfx; + +#endif // UI_GFX_CANVAS_DIRECT2D_H_ diff --git a/ui/gfx/canvas_direct2d_unittest.cc b/ui/gfx/canvas_direct2d_unittest.cc new file mode 100644 index 0000000..8884f32 --- /dev/null +++ b/ui/gfx/canvas_direct2d_unittest.cc @@ -0,0 +1,350 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include <windows.h> +#include <uxtheme.h> +#include <vsstyle.h> +#include <vssym32.h> + +#include "base/command_line.h" +#include "base/ref_counted_memory.h" +#include "base/resource_util.h" +#include "base/scoped_ptr.h" +#include "gfx/brush.h" +#include "gfx/canvas_direct2d.h" +#include "gfx/canvas_skia.h" +#include "gfx/codec/png_codec.h" +#include "gfx/native_theme_win.h" +#include "gfx/rect.h" +#include "gfx/win_util.h" +#include "grit/gfx_resources.h" +#include "testing/gtest/include/gtest/gtest.h" + + +namespace { + +const char kVisibleModeFlag[] = "d2d-canvas-visible"; +const wchar_t kWindowClassName[] = L"GFXD2DTestWindowClass"; + +class TestWindow { + public: + static const int kWindowSize = 500; + static const int kWindowPosition = 10; + + TestWindow() : hwnd_(NULL) { + if (CommandLine::ForCurrentProcess()->HasSwitch(kVisibleModeFlag)) + Sleep(1000); + + RegisterMyClass(); + + hwnd_ = CreateWindowEx(0, kWindowClassName, NULL, + WS_OVERLAPPEDWINDOW, + kWindowPosition, kWindowPosition, + kWindowSize, kWindowSize, + NULL, NULL, NULL, this); + DCHECK(hwnd_); + + // Initialize the RenderTarget for the window. + rt_ = MakeHWNDRenderTarget(); + + if (CommandLine::ForCurrentProcess()->HasSwitch(kVisibleModeFlag)) + ShowWindow(hwnd(), SW_SHOW); + } + virtual ~TestWindow() { + if (CommandLine::ForCurrentProcess()->HasSwitch(kVisibleModeFlag)) + Sleep(1000); + DestroyWindow(hwnd()); + UnregisterMyClass(); + } + + HWND hwnd() const { return hwnd_; } + + ID2D1RenderTarget* rt() const { return rt_.get(); } + + private: + ID2D1RenderTarget* MakeHWNDRenderTarget() { + D2D1_RENDER_TARGET_PROPERTIES rt_properties = + D2D1::RenderTargetProperties(); + rt_properties.usage = D2D1_RENDER_TARGET_USAGE_GDI_COMPATIBLE; + + ID2D1HwndRenderTarget* rt = NULL; + gfx::CanvasDirect2D::GetD2D1Factory()->CreateHwndRenderTarget( + rt_properties, + D2D1::HwndRenderTargetProperties(hwnd(), D2D1::SizeU(500, 500)), + &rt); + return rt; + } + + void RegisterMyClass() { + WNDCLASSEX class_ex; + class_ex.cbSize = sizeof(WNDCLASSEX); + class_ex.style = CS_DBLCLKS; + class_ex.lpfnWndProc = &DefWindowProc; + class_ex.cbClsExtra = 0; + class_ex.cbWndExtra = 0; + class_ex.hInstance = NULL; + class_ex.hIcon = NULL; + class_ex.hCursor = LoadCursor(NULL, IDC_ARROW); + class_ex.hbrBackground = reinterpret_cast<HBRUSH>(COLOR_BACKGROUND); + class_ex.lpszMenuName = NULL; + class_ex.lpszClassName = kWindowClassName; + class_ex.hIconSm = class_ex.hIcon; + ATOM atom = RegisterClassEx(&class_ex); + DCHECK(atom); + } + + void UnregisterMyClass() { + ::UnregisterClass(kWindowClassName, NULL); + } + + HWND hwnd_; + + ScopedComPtr<ID2D1RenderTarget> rt_; + + DISALLOW_COPY_AND_ASSIGN(TestWindow); +}; + +// Loads a png data blob from the data resources associated with this +// executable, decodes it and returns a SkBitmap. +SkBitmap LoadBitmapFromResources(int resource_id) { + SkBitmap bitmap; + + HINSTANCE resource_instance = GetModuleHandle(NULL); + void* data_ptr; + size_t data_size; + if (base::GetDataResourceFromModule(resource_instance, resource_id, &data_ptr, + &data_size)) { + scoped_refptr<RefCountedMemory> memory(new RefCountedStaticMemory( + reinterpret_cast<const unsigned char*>(data_ptr), data_size)); + if (!memory) + return bitmap; + + if (!gfx::PNGCodec::Decode(memory->front(), memory->size(), &bitmap)) + NOTREACHED() << "Unable to decode theme image resource " << resource_id; + } + return bitmap; +} + +bool CheckForD2DCompatibility() { + if (!gfx::Direct2dIsAvailable()) { + LOG(WARNING) << "Test is disabled as it requires either Windows 7 or " << + "Vista with Platform Update KB971644"; + return false; + } + return true; +} + +} // namespace + +TEST(CanvasDirect2D, CreateCanvas) { + if (!CheckForD2DCompatibility()) + return; + TestWindow window; + gfx::CanvasDirect2D canvas(window.rt()); +} + +TEST(CanvasDirect2D, SaveRestoreNesting) { + if (!CheckForD2DCompatibility()) + return; + TestWindow window; + gfx::CanvasDirect2D canvas(window.rt()); + + // Simple. + canvas.Save(); + canvas.Restore(); + + // Nested. + canvas.Save(); + canvas.Save(); + canvas.Restore(); + canvas.Restore(); + + // Simple alpha. + canvas.SaveLayerAlpha(127); + canvas.Restore(); + + // Alpha with sub-rect. + canvas.SaveLayerAlpha(127, gfx::Rect(20, 20, 100, 100)); + canvas.Restore(); + + // Nested alpha. + canvas.Save(); + canvas.SaveLayerAlpha(127); + canvas.Save(); + canvas.Restore(); + canvas.Restore(); + canvas.Restore(); +} + +TEST(CanvasDirect2D, SaveLayerAlpha) { + if (!CheckForD2DCompatibility()) + return; + TestWindow window; + gfx::CanvasDirect2D canvas(window.rt()); + + canvas.Save(); + canvas.FillRectInt(SK_ColorBLUE, 20, 20, 100, 100); + canvas.SaveLayerAlpha(127); + canvas.FillRectInt(SK_ColorRED, 60, 60, 100, 100); + canvas.Restore(); + canvas.Restore(); +} + +TEST(CanvasDirect2D, SaveLayerAlphaWithBounds) { + if (!CheckForD2DCompatibility()) + return; + TestWindow window; + gfx::CanvasDirect2D canvas(window.rt()); + + canvas.Save(); + canvas.FillRectInt(SK_ColorBLUE, 20, 20, 100, 100); + canvas.SaveLayerAlpha(127, gfx::Rect(60, 60, 50, 50)); + canvas.FillRectInt(SK_ColorRED, 60, 60, 100, 100); + canvas.Restore(); + canvas.Restore(); +} + +TEST(CanvasDirect2D, FillRect) { + if (!CheckForD2DCompatibility()) + return; + TestWindow window; + gfx::CanvasDirect2D canvas(window.rt()); + + canvas.FillRectInt(SK_ColorRED, 20, 20, 100, 100); +} + +TEST(CanvasDirect2D, PlatformPainting) { + if (!CheckForD2DCompatibility()) + return; + TestWindow window; + gfx::CanvasDirect2D canvas(window.rt()); + + gfx::NativeDrawingContext dc = canvas.BeginPlatformPaint(); + + // Use the system theme engine to draw a native button. This only works on a + // GDI device context. + RECT r = { 20, 20, 220, 80 }; + gfx::NativeTheme::instance()->PaintButton( + dc, BP_PUSHBUTTON, PBS_NORMAL, DFCS_BUTTONPUSH, &r); + + canvas.EndPlatformPaint(); +} + +TEST(CanvasDirect2D, ClipRect) { + if (!CheckForD2DCompatibility()) + return; + TestWindow window; + gfx::CanvasDirect2D canvas(window.rt()); + + canvas.FillRectInt(SK_ColorGREEN, 0, 0, 500, 500); + canvas.ClipRectInt(20, 20, 120, 120); + canvas.FillRectInt(SK_ColorBLUE, 0, 0, 500, 500); +} + +TEST(CanvasDirect2D, ClipRectWithTranslate) { + if (!CheckForD2DCompatibility()) + return; + TestWindow window; + gfx::CanvasDirect2D canvas(window.rt()); + + // Repeat the same rendering as in ClipRect... + canvas.Save(); + canvas.FillRectInt(SK_ColorGREEN, 0, 0, 500, 500); + canvas.ClipRectInt(20, 20, 120, 120); + canvas.FillRectInt(SK_ColorBLUE, 0, 0, 500, 500); + canvas.Restore(); + + // ... then translate, clip and fill again relative to the new origin. + canvas.Save(); + canvas.TranslateInt(150, 150); + canvas.ClipRectInt(10, 10, 110, 110); + canvas.FillRectInt(SK_ColorRED, 0, 0, 500, 500); + canvas.Restore(); +} + +TEST(CanvasDirect2D, ClipRectWithScale) { + if (!CheckForD2DCompatibility()) + return; + TestWindow window; + gfx::CanvasDirect2D canvas(window.rt()); + + // Repeat the same rendering as in ClipRect... + canvas.Save(); + canvas.FillRectInt(SK_ColorGREEN, 0, 0, 500, 500); + canvas.ClipRectInt(20, 20, 120, 120); + canvas.FillRectInt(SK_ColorBLUE, 0, 0, 500, 500); + canvas.Restore(); + + // ... then translate and scale, clip and fill again relative to the new + // origin. + canvas.Save(); + canvas.TranslateInt(150, 150); + canvas.ScaleInt(2, 2); + canvas.ClipRectInt(10, 10, 110, 110); + canvas.FillRectInt(SK_ColorRED, 0, 0, 500, 500); + canvas.Restore(); +} + +TEST(CanvasDirect2D, DrawRectInt) { + if (!CheckForD2DCompatibility()) + return; + TestWindow window; + gfx::CanvasDirect2D canvas(window.rt()); + + canvas.Save(); + canvas.DrawRectInt(SK_ColorRED, 10, 10, 200, 200); + canvas.Restore(); +} + +TEST(CanvasDirect2D, DrawLineInt) { + if (!CheckForD2DCompatibility()) + return; + TestWindow window; + gfx::CanvasDirect2D canvas(window.rt()); + + canvas.Save(); + canvas.DrawLineInt(SK_ColorRED, 10, 10, 210, 210); + canvas.Restore(); +} + +TEST(CanvasDirect2D, DrawBitmapInt) { + if (!CheckForD2DCompatibility()) + return; + TestWindow window; + gfx::CanvasDirect2D canvas(window.rt()); + + SkBitmap bitmap = LoadBitmapFromResources(IDR_BITMAP_BRUSH_IMAGE); + + canvas.Save(); + canvas.DrawBitmapInt(bitmap, 100, 100); + canvas.Restore(); +} + +TEST(CanvasDirect2D, DrawBitmapInt2) { + if (!CheckForD2DCompatibility()) + return; + TestWindow window; + gfx::CanvasDirect2D canvas(window.rt()); + + SkBitmap bitmap = LoadBitmapFromResources(IDR_BITMAP_BRUSH_IMAGE); + + canvas.Save(); + canvas.DrawBitmapInt(bitmap, 5, 5, 30, 30, 10, 10, 30, 30, false); + canvas.DrawBitmapInt(bitmap, 5, 5, 30, 30, 110, 110, 100, 100, true); + canvas.DrawBitmapInt(bitmap, 5, 5, 30, 30, 220, 220, 100, 100, false); + canvas.Restore(); +} + +TEST(CanvasDirect2D, TileImageInt) { + if (!CheckForD2DCompatibility()) + return; + TestWindow window; + gfx::CanvasDirect2D canvas(window.rt()); + + SkBitmap bitmap = LoadBitmapFromResources(IDR_BITMAP_BRUSH_IMAGE); + + canvas.Save(); + canvas.TileImageInt(bitmap, 10, 10, 300, 300); + canvas.Restore(); +} diff --git a/ui/gfx/canvas_skia.cc b/ui/gfx/canvas_skia.cc new file mode 100644 index 0000000..aa0cb9d --- /dev/null +++ b/ui/gfx/canvas_skia.cc @@ -0,0 +1,391 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "gfx/canvas_skia.h" + +#include <limits> + +#include "base/i18n/rtl.h" +#include "base/logging.h" +#include "gfx/brush.h" +#include "gfx/font.h" +#include "gfx/rect.h" +#include "third_party/skia/include/effects/SkGradientShader.h" + +#if defined(OS_WIN) +#include "gfx/canvas_skia_paint.h" +#endif + +namespace { + +// A platform wrapper for a Skia shader that makes sure the underlying +// SkShader object is unref'ed when this object is destroyed. +class SkiaShader : public gfx::Brush { + public: + explicit SkiaShader(SkShader* shader) : shader_(shader) { + } + virtual ~SkiaShader() { + shader_->unref(); + } + + // Accessor for shader, so it can be associated with a SkPaint. + SkShader* shader() const { return shader_; } + + private: + SkShader* shader_; + + DISALLOW_COPY_AND_ASSIGN(SkiaShader); +}; + + +} // namespace + +namespace gfx { + +//////////////////////////////////////////////////////////////////////////////// +// CanvasSkia, public: + +// static +int CanvasSkia::DefaultCanvasTextAlignment() { + if (!base::i18n::IsRTL()) + return gfx::Canvas::TEXT_ALIGN_LEFT; + return gfx::Canvas::TEXT_ALIGN_RIGHT; +} + +SkBitmap CanvasSkia::ExtractBitmap() const { + const SkBitmap& device_bitmap = getDevice()->accessBitmap(false); + + // Make a bitmap to return, and a canvas to draw into it. We don't just want + // to call extractSubset or the copy constructor, since we want an actual copy + // of the bitmap. + SkBitmap result; + device_bitmap.copyTo(&result, SkBitmap::kARGB_8888_Config); + return result; +} + +//////////////////////////////////////////////////////////////////////////////// +// CanvasSkia, Canvas implementation: + +void CanvasSkia::Save() { + save(); +} + +void CanvasSkia::SaveLayerAlpha(uint8 alpha) { + saveLayerAlpha(NULL, alpha); +} + + +void CanvasSkia::SaveLayerAlpha(uint8 alpha, const gfx::Rect& layer_bounds) { + SkRect bounds; + bounds.set(SkIntToScalar(layer_bounds.x()), + SkIntToScalar(layer_bounds.y()), + SkIntToScalar(layer_bounds.right()), + SkIntToScalar(layer_bounds.bottom())); + saveLayerAlpha(&bounds, alpha); +} + +void CanvasSkia::Restore() { + restore(); +} + +bool CanvasSkia::ClipRectInt(int x, int y, int w, int h) { + SkRect new_clip; + new_clip.set(SkIntToScalar(x), SkIntToScalar(y), + SkIntToScalar(x + w), SkIntToScalar(y + h)); + return clipRect(new_clip); +} + +void CanvasSkia::TranslateInt(int x, int y) { + translate(SkIntToScalar(x), SkIntToScalar(y)); +} + +void CanvasSkia::ScaleInt(int x, int y) { + scale(SkIntToScalar(x), SkIntToScalar(y)); +} + +void CanvasSkia::FillRectInt(const SkColor& color, int x, int y, int w, int h) { + FillRectInt(color, x, y, w, h, SkXfermode::kSrcOver_Mode); +} + +void CanvasSkia::FillRectInt(const SkColor& color, + int x, int y, int w, int h, + SkXfermode::Mode mode) { + SkPaint paint; + paint.setColor(color); + paint.setStyle(SkPaint::kFill_Style); + paint.setXfermodeMode(mode); + DrawRectInt(x, y, w, h, paint); +} + +void CanvasSkia::FillRectInt(const gfx::Brush* brush, + int x, int y, int w, int h) { + const SkiaShader* shader = static_cast<const SkiaShader*>(brush); + SkPaint paint; + paint.setShader(shader->shader()); + // TODO(beng): set shader transform to match canvas transform. + DrawRectInt(x, y, w, h, paint); +} + +void CanvasSkia::DrawRectInt(const SkColor& color, int x, int y, int w, int h) { + DrawRectInt(color, x, y, w, h, SkXfermode::kSrcOver_Mode); +} + +void CanvasSkia::DrawRectInt(const SkColor& color, + int x, int y, int w, int h, + SkXfermode::Mode mode) { + SkPaint paint; + paint.setColor(color); + paint.setStyle(SkPaint::kStroke_Style); + // Set a stroke width of 0, which will put us down the stroke rect path. If + // we set a stroke width of 1, for example, this will internally create a + // path and fill it, which causes problems near the edge of the canvas. + paint.setStrokeWidth(SkIntToScalar(0)); + paint.setXfermodeMode(mode); + + DrawRectInt(x, y, w, h, paint); +} + +void CanvasSkia::DrawRectInt(int x, int y, int w, int h, const SkPaint& paint) { + SkIRect rc = { x, y, x + w, y + h }; + drawIRect(rc, paint); +} + +void CanvasSkia::DrawLineInt(const SkColor& color, + int x1, int y1, + int x2, int y2) { + SkPaint paint; + paint.setColor(color); + paint.setStrokeWidth(SkIntToScalar(1)); + drawLine(SkIntToScalar(x1), SkIntToScalar(y1), SkIntToScalar(x2), + SkIntToScalar(y2), paint); +} + +void CanvasSkia::DrawFocusRect(int x, int y, int width, int height) { + // Create a 2D bitmap containing alternating on/off pixels - we do this + // so that you never get two pixels of the same color around the edges + // of the focus rect (this may mean that opposing edges of the rect may + // have a dot pattern out of phase to each other). + static SkBitmap* dots = NULL; + if (!dots) { + int col_pixels = 32; + int row_pixels = 32; + + dots = new SkBitmap; + dots->setConfig(SkBitmap::kARGB_8888_Config, col_pixels, row_pixels); + dots->allocPixels(); + dots->eraseARGB(0, 0, 0, 0); + + uint32_t* dot = dots->getAddr32(0, 0); + for (int i = 0; i < row_pixels; i++) { + for (int u = 0; u < col_pixels; u++) { + if ((u % 2 + i % 2) % 2 != 0) { + dot[i * row_pixels + u] = SK_ColorGRAY; + } + } + } + } + + // First the horizontal lines. + + // Make a shader for the bitmap with an origin of the box we'll draw. This + // shader is refcounted and will have an initial refcount of 1. + SkShader* shader = SkShader::CreateBitmapShader( + *dots, SkShader::kRepeat_TileMode, SkShader::kRepeat_TileMode); + // Assign the shader to the paint & release our reference. The paint will + // now own the shader and the shader will be destroyed when the paint goes + // out of scope. + SkPaint paint; + paint.setShader(shader); + shader->unref(); + + DrawRectInt(x, y, width, 1, paint); + DrawRectInt(x, y + height - 1, width, 1, paint); + DrawRectInt(x, y, 1, height, paint); + DrawRectInt(x + width - 1, y, 1, height, paint); +} + +void CanvasSkia::DrawBitmapInt(const SkBitmap& bitmap, int x, int y) { + drawBitmap(bitmap, SkIntToScalar(x), SkIntToScalar(y)); +} + +void CanvasSkia::DrawBitmapInt(const SkBitmap& bitmap, + int x, int y, + const SkPaint& paint) { + drawBitmap(bitmap, SkIntToScalar(x), SkIntToScalar(y), &paint); +} + +void CanvasSkia::DrawBitmapInt(const SkBitmap& bitmap, + int src_x, int src_y, int src_w, int src_h, + int dest_x, int dest_y, int dest_w, int dest_h, + bool filter) { + SkPaint p; + DrawBitmapInt(bitmap, src_x, src_y, src_w, src_h, dest_x, dest_y, + dest_w, dest_h, filter, p); +} + +void CanvasSkia::DrawBitmapInt(const SkBitmap& bitmap, + int src_x, int src_y, int src_w, int src_h, + int dest_x, int dest_y, int dest_w, int dest_h, + bool filter, + const SkPaint& paint) { + DLOG_ASSERT(src_x + src_w < std::numeric_limits<int16_t>::max() && + src_y + src_h < std::numeric_limits<int16_t>::max()); + if (src_w <= 0 || src_h <= 0 || dest_w <= 0 || dest_h <= 0) { + NOTREACHED() << "Attempting to draw bitmap to/from an empty rect!"; + return; + } + + if (!IntersectsClipRectInt(dest_x, dest_y, dest_w, dest_h)) + return; + + SkRect dest_rect = { SkIntToScalar(dest_x), + SkIntToScalar(dest_y), + SkIntToScalar(dest_x + dest_w), + SkIntToScalar(dest_y + dest_h) }; + + if (src_w == dest_w && src_h == dest_h) { + // Workaround for apparent bug in Skia that causes image to occasionally + // shift. + SkIRect src_rect = { src_x, src_y, src_x + src_w, src_y + src_h }; + drawBitmapRect(bitmap, &src_rect, dest_rect, &paint); + return; + } + + // Make a bitmap shader that contains the bitmap we want to draw. This is + // basically what SkCanvas.drawBitmap does internally, but it gives us + // more control over quality and will use the mipmap in the source image if + // it has one, whereas drawBitmap won't. + SkShader* shader = SkShader::CreateBitmapShader(bitmap, + SkShader::kRepeat_TileMode, + SkShader::kRepeat_TileMode); + SkMatrix shader_scale; + shader_scale.setScale(SkFloatToScalar(static_cast<float>(dest_w) / src_w), + SkFloatToScalar(static_cast<float>(dest_h) / src_h)); + shader_scale.preTranslate(SkIntToScalar(-src_x), SkIntToScalar(-src_y)); + shader_scale.postTranslate(SkIntToScalar(dest_x), SkIntToScalar(dest_y)); + shader->setLocalMatrix(shader_scale); + + // Set up our paint to use the shader & release our reference (now just owned + // by the paint). + SkPaint p(paint); + p.setFilterBitmap(filter); + p.setShader(shader); + shader->unref(); + + // The rect will be filled by the bitmap. + drawRect(dest_rect, p); +} + +void CanvasSkia::DrawStringInt(const string16& text, + const gfx::Font& font, + const SkColor& color, + int x, int y, int w, int h) { + DrawStringInt(text, font, color, x, y, w, h, + gfx::CanvasSkia::DefaultCanvasTextAlignment()); +} + +void CanvasSkia::DrawStringInt(const string16& text, + const gfx::Font& font, + const SkColor& color, + const gfx::Rect& display_rect) { + DrawStringInt(text, font, color, display_rect.x(), display_rect.y(), + display_rect.width(), display_rect.height()); +} + +void CanvasSkia::TileImageInt(const SkBitmap& bitmap, + int x, int y, int w, int h) { + TileImageInt(bitmap, 0, 0, x, y, w, h); +} + +void CanvasSkia::TileImageInt(const SkBitmap& bitmap, + int src_x, int src_y, + int dest_x, int dest_y, int w, int h) { + if (!IntersectsClipRectInt(dest_x, dest_y, w, h)) + return; + + SkPaint paint; + + SkShader* shader = SkShader::CreateBitmapShader(bitmap, + SkShader::kRepeat_TileMode, + SkShader::kRepeat_TileMode); + paint.setShader(shader); + paint.setXfermodeMode(SkXfermode::kSrcOver_Mode); + + // CreateBitmapShader returns a Shader with a reference count of one, we + // need to unref after paint takes ownership of the shader. + shader->unref(); + save(); + translate(SkIntToScalar(dest_x - src_x), SkIntToScalar(dest_y - src_y)); + ClipRectInt(src_x, src_y, w, h); + drawPaint(paint); + restore(); +} + +gfx::NativeDrawingContext CanvasSkia::BeginPlatformPaint() { + return beginPlatformPaint(); +} + +void CanvasSkia::EndPlatformPaint() { + endPlatformPaint(); +} + +CanvasSkia* CanvasSkia::AsCanvasSkia() { + return this; +} + +const CanvasSkia* CanvasSkia::AsCanvasSkia() const { + return this; +} + +//////////////////////////////////////////////////////////////////////////////// +// CanvasSkia, private: + +bool CanvasSkia::IntersectsClipRectInt(int x, int y, int w, int h) { + SkRect clip; + return getClipBounds(&clip) && + clip.intersect(SkIntToScalar(x), SkIntToScalar(y), SkIntToScalar(x + w), + SkIntToScalar(y + h)); +} + +//////////////////////////////////////////////////////////////////////////////// +// Canvas, public: + +Canvas* Canvas::CreateCanvas() { + return new CanvasSkia; +} + +Canvas* Canvas::CreateCanvas(int width, int height, bool is_opaque) { + return new CanvasSkia(width, height, is_opaque); +} + +#if defined(OS_WIN) +// TODO(beng): move to canvas_win.cc, etc. +class CanvasPaintWin : public CanvasSkiaPaint, public CanvasPaint { + public: + CanvasPaintWin(gfx::NativeView view) : CanvasSkiaPaint(view) {} + + // Overridden from CanvasPaint2: + virtual bool IsValid() const { + return isEmpty(); + } + + virtual gfx::Rect GetInvalidRect() const { + return gfx::Rect(paintStruct().rcPaint); + } + + virtual Canvas* AsCanvas() { + return this; + } +}; +#endif + +CanvasPaint* CanvasPaint::CreateCanvasPaint(gfx::NativeView view) { +#if defined(OS_WIN) + return new CanvasPaintWin(view); +#else + return NULL; +#endif +} + +} // namespace gfx diff --git a/ui/gfx/canvas_skia.h b/ui/gfx/canvas_skia.h new file mode 100644 index 0000000..ce16fae --- /dev/null +++ b/ui/gfx/canvas_skia.h @@ -0,0 +1,165 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_CANVAS_SKIA_H_ +#define UI_GFX_CANVAS_SKIA_H_ +#pragma once + +#include "base/basictypes.h" +#include "base/string16.h" +#include "gfx/canvas.h" +#include "skia/ext/platform_canvas.h" + +#if defined(OS_POSIX) && !defined(OS_MACOSX) +typedef struct _GdkPixbuf GdkPixbuf; +#endif + +namespace gfx { + +class Canvas; + +// CanvasSkia is a SkCanvas subclass that provides a number of methods for +// common operations used throughout an application built using base/gfx and +// app/gfx. +// +// All methods that take integer arguments (as is used throughout views) +// end with Int. If you need to use methods provided by the superclass +// you'll need to do a conversion. In particular you'll need to use +// macro SkIntToScalar(xxx), or if converting from a scalar to an integer +// SkScalarRound. +// +// A handful of methods in this class are overloaded providing an additional +// argument of type SkXfermode::Mode. SkXfermode::Mode specifies how the +// source and destination colors are combined. Unless otherwise specified, +// the variant that does not take a SkXfermode::Mode uses a transfer mode +// of kSrcOver_Mode. +class CanvasSkia : public skia::PlatformCanvas, + public Canvas { + public: + // Creates an empty Canvas. Callers must use initialize before using the + // canvas. + CanvasSkia(); + + CanvasSkia(int width, int height, bool is_opaque); + + virtual ~CanvasSkia(); + + // Compute the size required to draw some text with the provided font. + // Attempts to fit the text with the provided width and height. Increases + // height and then width as needed to make the text fit. This method + // supports multiple lines. + static void SizeStringInt(const string16& text, + const gfx::Font& font, + int* width, int* height, + int flags); + + // Returns the default text alignment to be used when drawing text on a + // gfx::CanvasSkia based on the directionality of the system locale language. + // This function is used by gfx::Canvas::DrawStringInt when the text alignment + // is not specified. + // + // This function returns either gfx::Canvas::TEXT_ALIGN_LEFT or + // gfx::Canvas::TEXT_ALIGN_RIGHT. + static int DefaultCanvasTextAlignment(); + +#if defined(OS_POSIX) && !defined(OS_MACOSX) + // Draw the pixbuf in its natural size at (x, y). + void DrawGdkPixbuf(GdkPixbuf* pixbuf, int x, int y); +#endif + +#if defined(OS_WIN) || (defined(OS_POSIX) && !defined(OS_MACOSX)) + // Draws text with a 1-pixel halo around it of the given color. + // On Windows, it allows ClearType to be drawn to an otherwise transparenct + // bitmap for drag images. Drag images have only 1-bit of transparency, so + // we don't do any fancy blurring. + // On Linux, text with halo is created by stroking it with 2px |halo_color| + // then filling it with |text_color|. + void DrawStringWithHalo(const string16& text, + const gfx::Font& font, + const SkColor& text_color, + const SkColor& halo_color, + int x, int y, int w, int h, + int flags); +#endif + + // Extracts a bitmap from the contents of this canvas. + SkBitmap ExtractBitmap() const; + + // Overridden from Canvas: + virtual void Save(); + virtual void SaveLayerAlpha(uint8 alpha); + virtual void SaveLayerAlpha(uint8 alpha, const gfx::Rect& layer_bounds); + virtual void Restore(); + virtual bool ClipRectInt(int x, int y, int w, int h); + virtual void TranslateInt(int x, int y); + virtual void ScaleInt(int x, int y); + virtual void FillRectInt(const SkColor& color, int x, int y, int w, int h); + virtual void FillRectInt(const SkColor& color, int x, int y, int w, int h, + SkXfermode::Mode mode); + virtual void FillRectInt(const gfx::Brush* brush, int x, int y, int w, int h); + virtual void DrawRectInt(const SkColor& color, int x, int y, int w, int h); + virtual void DrawRectInt(const SkColor& color, + int x, int y, int w, int h, + SkXfermode::Mode mode); + virtual void DrawRectInt(int x, int y, int w, int h, const SkPaint& paint); + virtual void DrawLineInt(const SkColor& color, + int x1, int y1, + int x2, int y2); + virtual void DrawBitmapInt(const SkBitmap& bitmap, int x, int y); + virtual void DrawBitmapInt(const SkBitmap& bitmap, + int x, int y, + const SkPaint& paint); + virtual void DrawBitmapInt(const SkBitmap& bitmap, + int src_x, int src_y, int src_w, int src_h, + int dest_x, int dest_y, int dest_w, int dest_h, + bool filter); + virtual void DrawBitmapInt(const SkBitmap& bitmap, + int src_x, int src_y, int src_w, int src_h, + int dest_x, int dest_y, int dest_w, int dest_h, + bool filter, + const SkPaint& paint); + virtual void DrawStringInt(const string16& text, + const gfx::Font& font, + const SkColor& color, + int x, int y, int w, int h); + virtual void DrawStringInt(const string16& text, + const gfx::Font& font, + const SkColor& color, + const gfx::Rect& display_rect); + virtual void DrawStringInt(const string16& text, + const gfx::Font& font, + const SkColor& color, + int x, int y, int w, int h, + int flags); + virtual void DrawFocusRect(int x, int y, int width, int height); + virtual void TileImageInt(const SkBitmap& bitmap, int x, int y, int w, int h); + virtual void TileImageInt(const SkBitmap& bitmap, + int src_x, int src_y, + int dest_x, int dest_y, int w, int h); + virtual gfx::NativeDrawingContext BeginPlatformPaint(); + virtual void EndPlatformPaint(); + virtual CanvasSkia* AsCanvasSkia(); + virtual const CanvasSkia* AsCanvasSkia() const; + + private: + // Test whether the provided rectangle intersects the current clip rect. + bool IntersectsClipRectInt(int x, int y, int w, int h); + +#if defined(OS_WIN) + // Draws text with the specified color, font and location. The text is + // aligned to the left, vertically centered, clipped to the region. If the + // text is too big, it is truncated and '...' is added to the end. + void DrawStringInt(const string16& text, + HFONT font, + const SkColor& color, + int x, int y, int w, int h, + int flags); +#endif + + DISALLOW_COPY_AND_ASSIGN(CanvasSkia); +}; + +} // namespace gfx; + +#endif // UI_GFX_CANVAS_SKIA_H_ diff --git a/ui/gfx/canvas_skia_linux.cc b/ui/gfx/canvas_skia_linux.cc new file mode 100644 index 0000000..ce2ae01 --- /dev/null +++ b/ui/gfx/canvas_skia_linux.cc @@ -0,0 +1,387 @@ +// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "gfx/canvas_skia.h" + +#include <cairo/cairo.h> +#include <gtk/gtk.h> +#include <pango/pango.h> +#include <pango/pangocairo.h> + +#include "base/logging.h" +#include "base/utf_string_conversions.h" +#include "gfx/font.h" +#include "gfx/gtk_util.h" +#include "gfx/platform_font_gtk.h" +#include "gfx/rect.h" + +namespace { + +const gunichar kAcceleratorChar = '&'; + +// Font settings that we initialize once and then use when drawing text in +// DrawStringInt(). +static cairo_font_options_t* cairo_font_options = NULL; + +// Update |cairo_font_options| based on GtkSettings, allocating it if needed. +static void UpdateCairoFontOptions() { + if (!cairo_font_options) + cairo_font_options = cairo_font_options_create(); + + GtkSettings* gtk_settings = gtk_settings_get_default(); + gint antialias = 0; + gint hinting = 0; + gchar* hint_style = NULL; + gchar* rgba_style = NULL; + g_object_get(gtk_settings, + "gtk-xft-antialias", &antialias, + "gtk-xft-hinting", &hinting, + "gtk-xft-hintstyle", &hint_style, + "gtk-xft-rgba", &rgba_style, + NULL); + + // g_object_get() doesn't tell us whether the properties were present or not, + // but if they aren't (because gnome-settings-daemon isn't running), we'll get + // NULL values for the strings. + if (hint_style && rgba_style) { + if (!antialias) { + cairo_font_options_set_antialias(cairo_font_options, + CAIRO_ANTIALIAS_NONE); + } else if (strcmp(rgba_style, "none") == 0) { + cairo_font_options_set_antialias(cairo_font_options, + CAIRO_ANTIALIAS_GRAY); + } else { + cairo_font_options_set_antialias(cairo_font_options, + CAIRO_ANTIALIAS_SUBPIXEL); + cairo_subpixel_order_t cairo_subpixel_order = + CAIRO_SUBPIXEL_ORDER_DEFAULT; + if (strcmp(rgba_style, "rgb") == 0) { + cairo_subpixel_order = CAIRO_SUBPIXEL_ORDER_RGB; + } else if (strcmp(rgba_style, "bgr") == 0) { + cairo_subpixel_order = CAIRO_SUBPIXEL_ORDER_BGR; + } else if (strcmp(rgba_style, "vrgb") == 0) { + cairo_subpixel_order = CAIRO_SUBPIXEL_ORDER_VRGB; + } else if (strcmp(rgba_style, "vbgr") == 0) { + cairo_subpixel_order = CAIRO_SUBPIXEL_ORDER_VBGR; + } + cairo_font_options_set_subpixel_order(cairo_font_options, + cairo_subpixel_order); + } + + cairo_hint_style_t cairo_hint_style = CAIRO_HINT_STYLE_DEFAULT; + if (hinting == 0 || strcmp(hint_style, "hintnone") == 0) { + cairo_hint_style = CAIRO_HINT_STYLE_NONE; + } else if (strcmp(hint_style, "hintslight") == 0) { + cairo_hint_style = CAIRO_HINT_STYLE_SLIGHT; + } else if (strcmp(hint_style, "hintmedium") == 0) { + cairo_hint_style = CAIRO_HINT_STYLE_MEDIUM; + } else if (strcmp(hint_style, "hintfull") == 0) { + cairo_hint_style = CAIRO_HINT_STYLE_FULL; + } + cairo_font_options_set_hint_style(cairo_font_options, cairo_hint_style); + } + + if (hint_style) + g_free(hint_style); + if (rgba_style) + g_free(rgba_style); +} + +// Pass a width > 0 to force wrapping and elliding. +static void SetupPangoLayout(PangoLayout* layout, + const string16& text, + const gfx::Font& font, + int width, + int flags) { + if (!cairo_font_options) + UpdateCairoFontOptions(); + // This needs to be done early on; it has no effect when called just before + // pango_cairo_show_layout(). + pango_cairo_context_set_font_options( + pango_layout_get_context(layout), cairo_font_options); + + // Callers of DrawStringInt handle RTL layout themselves, so tell pango to not + // scope out RTL characters. + pango_layout_set_auto_dir(layout, FALSE); + + if (width > 0) + pango_layout_set_width(layout, width * PANGO_SCALE); + + if (flags & gfx::Canvas::NO_ELLIPSIS) { + pango_layout_set_ellipsize(layout, PANGO_ELLIPSIZE_NONE); + } else { + pango_layout_set_ellipsize(layout, PANGO_ELLIPSIZE_END); + } + + if (flags & gfx::Canvas::TEXT_ALIGN_CENTER) { + pango_layout_set_alignment(layout, PANGO_ALIGN_CENTER); + } else if (flags & gfx::Canvas::TEXT_ALIGN_RIGHT) { + pango_layout_set_alignment(layout, PANGO_ALIGN_RIGHT); + } + + if (flags & gfx::Canvas::MULTI_LINE) { + pango_layout_set_wrap(layout, + (flags & gfx::Canvas::CHARACTER_BREAK) ? + PANGO_WRAP_WORD_CHAR : PANGO_WRAP_WORD); + } + + // Set the resolution to match that used by Gtk. If we don't set the + // resolution and the resolution differs from the default, Gtk and Chrome end + // up drawing at different sizes. + double resolution = gfx::GetPangoResolution(); + if (resolution > 0) { + pango_cairo_context_set_resolution(pango_layout_get_context(layout), + resolution); + } + + PangoFontDescription* desc = font.GetNativeFont(); + pango_layout_set_font_description(layout, desc); + pango_font_description_free(desc); + + // Set text and accelerator character if needed. + std::string utf8 = UTF16ToUTF8(text); + if (flags & gfx::Canvas::SHOW_PREFIX) { + // Escape the text string to be used as markup. + gchar* escaped_text = g_markup_escape_text(utf8.c_str(), utf8.size()); + pango_layout_set_markup_with_accel(layout, + escaped_text, + strlen(escaped_text), + kAcceleratorChar, NULL); + g_free(escaped_text); + } else if (flags & gfx::Canvas::HIDE_PREFIX) { + // Remove the ampersand character. + utf8 = gfx::RemoveWindowsStyleAccelerators(utf8); + pango_layout_set_text(layout, utf8.data(), utf8.size()); + } else { + pango_layout_set_text(layout, utf8.data(), utf8.size()); + } +} + +// A class to encapsulate string drawing params and operations. +class DrawStringContext { + public: + DrawStringContext(gfx::CanvasSkia* canvas, + const string16& text, + const gfx::Font& font, + const gfx::Rect& bounds, + const gfx::Rect& clip, + int flags); + ~DrawStringContext(); + + void Draw(const SkColor& text_color); + void DrawWithHalo(const SkColor& text_color, + const SkColor& halo_color); + + private: + const gfx::Rect& bounds_; + int flags_; + const gfx::Font& font_; + + gfx::CanvasSkia* canvas_; + cairo_t* cr_; + PangoLayout* layout_; + + int text_x_; + int text_y_; + int text_width_; + int text_height_; + + DISALLOW_COPY_AND_ASSIGN(DrawStringContext); +}; + +DrawStringContext::DrawStringContext(gfx::CanvasSkia* canvas, + const string16& text, + const gfx::Font& font, + const gfx::Rect& bounds, + const gfx::Rect& clip, + int flags) + : bounds_(bounds), + flags_(flags), + font_(font), + canvas_(canvas), + cr_(NULL), + layout_(NULL), + text_x_(bounds.x()), + text_y_(bounds.y()), + text_width_(0), + text_height_(0) { + DCHECK(!bounds_.IsEmpty()); + + cr_ = canvas_->beginPlatformPaint(); + layout_ = pango_cairo_create_layout(cr_); + + SetupPangoLayout(layout_, text, font, bounds_.width(), flags_); + + pango_layout_set_height(layout_, bounds_.height() * PANGO_SCALE); + + cairo_save(cr_); + + cairo_rectangle(cr_, clip.x(), clip.y(), clip.width(), clip.height()); + cairo_clip(cr_); + + pango_layout_get_pixel_size(layout_, &text_width_, &text_height_); + + if (flags_ & gfx::Canvas::TEXT_VALIGN_TOP) { + // Cairo should draw from the top left corner already. + } else if (flags_ & gfx::Canvas::TEXT_VALIGN_BOTTOM) { + text_y_ += (bounds.height() - text_height_); + } else { + // Vertically centered. + text_y_ += ((bounds.height() - text_height_) / 2); + } +} + +DrawStringContext::~DrawStringContext() { + if (font_.GetStyle() & gfx::Font::UNDERLINED) { + gfx::PlatformFontGtk* platform_font = + static_cast<gfx::PlatformFontGtk*>(font_.platform_font()); + double underline_y = + static_cast<double>(text_y_) + text_height_ + + platform_font->underline_position(); + cairo_set_line_width(cr_, platform_font->underline_thickness()); + cairo_move_to(cr_, text_x_, underline_y); + cairo_line_to(cr_, text_x_ + text_width_, underline_y); + cairo_stroke(cr_); + } + cairo_restore(cr_); + + g_object_unref(layout_); + // NOTE: beginPlatformPaint returned its surface, we shouldn't destroy it. +} + +void DrawStringContext::Draw(const SkColor& text_color) { + cairo_set_source_rgba(cr_, + SkColorGetR(text_color) / 255.0, + SkColorGetG(text_color) / 255.0, + SkColorGetB(text_color) / 255.0, + SkColorGetA(text_color) / 255.0); + cairo_move_to(cr_, text_x_, text_y_); + pango_cairo_show_layout(cr_, layout_); +} + +void DrawStringContext::DrawWithHalo(const SkColor& text_color, + const SkColor& halo_color) { + gfx::CanvasSkia text_canvas(bounds_.width() + 2, bounds_.height() + 2, false); + text_canvas.FillRectInt(static_cast<SkColor>(0), + 0, 0, bounds_.width() + 2, bounds_.height() + 2); + + cairo_t* text_cr = text_canvas.beginPlatformPaint(); + + cairo_move_to(text_cr, 1, 1); + pango_cairo_layout_path(text_cr, layout_); + + cairo_set_source_rgba(text_cr, + SkColorGetR(halo_color) / 255.0, + SkColorGetG(halo_color) / 255.0, + SkColorGetB(halo_color) / 255.0, + SkColorGetA(halo_color) / 255.0); + cairo_set_line_width(text_cr, 2.0); + cairo_set_line_join(text_cr, CAIRO_LINE_JOIN_ROUND); + cairo_stroke_preserve(text_cr); + + cairo_set_operator(text_cr, CAIRO_OPERATOR_SOURCE); + cairo_set_source_rgba(text_cr, + SkColorGetR(text_color) / 255.0, + SkColorGetG(text_color) / 255.0, + SkColorGetB(text_color) / 255.0, + SkColorGetA(text_color) / 255.0); + cairo_fill(text_cr); + + text_canvas.endPlatformPaint(); + + const SkBitmap& text_bitmap = const_cast<SkBitmap&>( + text_canvas.getTopPlatformDevice().accessBitmap(false)); + canvas_->DrawBitmapInt(text_bitmap, text_x_ - 1, text_y_ - 1); +} + +} // namespace + +namespace gfx { + +CanvasSkia::CanvasSkia(int width, int height, bool is_opaque) + : skia::PlatformCanvas(width, height, is_opaque) { +} + +CanvasSkia::CanvasSkia() : skia::PlatformCanvas() { +} + +CanvasSkia::~CanvasSkia() { +} + +// static +void CanvasSkia::SizeStringInt(const string16& text, + const gfx::Font& font, + int* width, int* height, + int flags) { + int org_width = *width; + cairo_surface_t* surface = + cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 0, 0); + cairo_t* cr = cairo_create(surface); + PangoLayout* layout = pango_cairo_create_layout(cr); + + SetupPangoLayout(layout, text, font, *width, flags); + + pango_layout_get_pixel_size(layout, width, height); + + if (org_width > 0 && flags & Canvas::MULTI_LINE && + pango_layout_is_wrapped(layout)) { + // The text wrapped. There seems to be a bug in Pango when this happens + // such that the width returned from pango_layout_get_pixel_size is too + // small. Using the width from pango_layout_get_pixel_size in this case + // results in wrapping across more lines, which requires a bigger height. + // As a workaround we use the original width, which is not necessarily + // exactly correct, but isn't wrong by much. + // + // It looks like Pango uses the size of whitespace in calculating wrapping + // but doesn't include the size of the whitespace when the extents are + // asked for. See the loop in pango-layout.c process_item that determines + // where to wrap. + *width = org_width; + } + + g_object_unref(layout); + cairo_destroy(cr); + cairo_surface_destroy(surface); +} + +void CanvasSkia::DrawStringWithHalo(const string16& text, + const gfx::Font& font, + const SkColor& text_color, + const SkColor& halo_color, + int x, int y, int w, int h, + int flags) { + if (w <= 0 || h <= 0) + return; + + gfx::Rect bounds(x, y, w, h); + gfx::Rect clip(x - 1, y - 1, w + 2, h + 2); // Bigger clip for halo + DrawStringContext context(this, text, font, bounds, clip,flags); + context.DrawWithHalo(text_color, halo_color); +} + +void CanvasSkia::DrawStringInt(const string16& text, + const gfx::Font& font, + const SkColor& color, + int x, int y, int w, int h, + int flags) { + if (w <= 0 || h <= 0) + return; + + gfx::Rect bounds(x, y, w, h); + DrawStringContext context(this, text, font, bounds, bounds, flags); + context.Draw(color); +} + +void CanvasSkia::DrawGdkPixbuf(GdkPixbuf* pixbuf, int x, int y) { + if (!pixbuf) { + NOTREACHED(); + return; + } + + cairo_t* cr = beginPlatformPaint(); + gdk_cairo_set_source_pixbuf(cr, pixbuf, x, y); + cairo_paint(cr); +} + +} // namespace gfx diff --git a/ui/gfx/canvas_skia_mac.mm b/ui/gfx/canvas_skia_mac.mm new file mode 100644 index 0000000..fb30055 --- /dev/null +++ b/ui/gfx/canvas_skia_mac.mm @@ -0,0 +1,88 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#import <Cocoa/Cocoa.h> + +#include "gfx/canvas_skia.h" + +#include "base/mac/scoped_cftyperef.h" +#include "base/sys_string_conversions.h" +#include "gfx/font.h" +#include "gfx/rect.h" +#include "third_party/skia/include/core/SkShader.h" + +namespace gfx { + +CanvasSkia::CanvasSkia(int width, int height, bool is_opaque) + : skia::PlatformCanvas(width, height, is_opaque) { +} + +CanvasSkia::CanvasSkia() : skia::PlatformCanvas() { +} + +CanvasSkia::~CanvasSkia() { +} + +// static +void CanvasSkia::SizeStringInt(const string16& text, + const gfx::Font& font, + int* width, int* height, + int flags) { + NSFont* native_font = font.GetNativeFont(); + NSString* ns_string = base::SysUTF16ToNSString(text); + NSDictionary* attributes = + [NSDictionary dictionaryWithObject:native_font + forKey:NSFontAttributeName]; + NSSize string_size = [ns_string sizeWithAttributes:attributes]; + *width = string_size.width; + *height = font.GetHeight(); +} + +void CanvasSkia::DrawStringInt(const string16& text, + const gfx::Font& font, + const SkColor& color, + int x, int y, int w, int h, + int flags) { + if (!IntersectsClipRectInt(x, y, w, h)) + return; + + CGContextRef context = beginPlatformPaint(); + CGContextSaveGState(context); + + NSColor* ns_color = [NSColor colorWithDeviceRed:SkColorGetR(color) / 255.0 + green:SkColorGetG(color) / 255.0 + blue:SkColorGetB(color) / 255.0 + alpha:SkColorGetA(color) / 255.0]; + NSMutableParagraphStyle *ns_style = + [[[NSParagraphStyle alloc] init] autorelease]; + if (flags & TEXT_ALIGN_CENTER) + [ns_style setAlignment:NSCenterTextAlignment]; + // TODO(awalker): Implement the rest of the Canvas text flags + + NSDictionary* attributes = + [NSDictionary dictionaryWithObjectsAndKeys: + font.GetNativeFont(), NSFontAttributeName, + ns_color, NSForegroundColorAttributeName, + ns_style, NSParagraphStyleAttributeName, + nil]; + + NSAttributedString* ns_string = + [[[NSAttributedString alloc] initWithString:base::SysUTF16ToNSString(text) + attributes:attributes] autorelease]; + base::mac::ScopedCFTypeRef<CTFramesetterRef> framesetter( + CTFramesetterCreateWithAttributedString( + reinterpret_cast<CFAttributedStringRef>(ns_string))); + + CGRect text_bounds = CGRectMake(x, y, w, h); + CGMutablePathRef path = CGPathCreateMutable(); + CGPathAddRect(path, NULL, text_bounds); + + base::mac::ScopedCFTypeRef<CTFrameRef> frame( + CTFramesetterCreateFrame(framesetter, CFRangeMake(0, 0), path, NULL)); + CTFrameDraw(frame, context); + CGContextRestoreGState(context); + endPlatformPaint(); +} + +} // namespace gfx diff --git a/ui/gfx/canvas_skia_paint.h b/ui/gfx/canvas_skia_paint.h new file mode 100644 index 0000000..6ad854a --- /dev/null +++ b/ui/gfx/canvas_skia_paint.h @@ -0,0 +1,21 @@ +// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_CANVAS_SKIA_PAINT_H_ +#define UI_GFX_CANVAS_SKIA_PAINT_H_ +#pragma once + +#include "gfx/canvas_skia.h" +#include "skia/ext/canvas_paint.h" + +// Define a gfx::CanvasSkiaPaint type that wraps our gfx::Canvas like the +// skia::PlatformCanvasPaint wraps PlatformCanvas. + +namespace gfx { + +typedef skia::CanvasPaintT<CanvasSkia> CanvasSkiaPaint; + +} // namespace gfx + +#endif // UI_GFX_CANVAS_SKIA_PAINT_H_ diff --git a/ui/gfx/canvas_skia_win.cc b/ui/gfx/canvas_skia_win.cc new file mode 100644 index 0000000..03186fa --- /dev/null +++ b/ui/gfx/canvas_skia_win.cc @@ -0,0 +1,301 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "gfx/canvas_skia.h" + +#include <limits> + +#include "base/i18n/rtl.h" +#include "gfx/font.h" +#include "gfx/rect.h" +#include "third_party/skia/include/core/SkShader.h" + +namespace { + +// We make sure that LTR text we draw in an RTL context is modified +// appropriately to make sure it maintains it LTR orientation. +void DoDrawText(HDC hdc, + const string16& text, + RECT* text_bounds, + int flags) { + // Only adjust string directionality if both of the following are true: + // 1. The current locale is RTL. + // 2. The string itself has RTL directionality. + const wchar_t* string_ptr = text.c_str(); + int string_size = static_cast<int>(text.length()); + + string16 localized_text; + if (flags & DT_RTLREADING) { + localized_text = text; + base::i18n::AdjustStringForLocaleDirection(&localized_text); + string_ptr = localized_text.c_str(); + string_size = static_cast<int>(localized_text.length()); + } + + DrawText(hdc, string_ptr, string_size, text_bounds, flags); +} + +// Compute the windows flags necessary to implement the provided text Canvas +// flags. +int ComputeFormatFlags(int flags, const string16& text) { + // Setting the text alignment explicitly in case it hasn't already been set. + // This will make sure that we don't align text to the left on RTL locales + // just because no alignment flag was passed to DrawStringInt(). + if (!(flags & (gfx::Canvas::TEXT_ALIGN_CENTER | + gfx::Canvas::TEXT_ALIGN_RIGHT | + gfx::Canvas::TEXT_ALIGN_LEFT))) { + flags |= gfx::CanvasSkia::DefaultCanvasTextAlignment(); + } + + // horizontal alignment + int f = 0; + if (flags & gfx::Canvas::TEXT_ALIGN_CENTER) + f |= DT_CENTER; + else if (flags & gfx::Canvas::TEXT_ALIGN_RIGHT) + f |= DT_RIGHT; + else + f |= DT_LEFT; + + // vertical alignment + if (flags & gfx::Canvas::TEXT_VALIGN_TOP) + f |= DT_TOP; + else if (flags & gfx::Canvas::TEXT_VALIGN_BOTTOM) + f |= DT_BOTTOM; + else + f |= DT_VCENTER; + + if (flags & gfx::Canvas::MULTI_LINE) { + f |= DT_WORDBREAK; + if (flags & gfx::Canvas::CHARACTER_BREAK) + f |= DT_EDITCONTROL; // Turns on character breaking (not documented) + else if (!(flags & gfx::Canvas::NO_ELLIPSIS)) + f |= DT_WORD_ELLIPSIS; + } else { + f |= DT_SINGLELINE; + } + + if (flags & gfx::Canvas::HIDE_PREFIX) + f |= DT_HIDEPREFIX; + else if ((flags & gfx::Canvas::SHOW_PREFIX) == 0) + f |= DT_NOPREFIX; + + if (!(flags & gfx::Canvas::NO_ELLIPSIS)) + f |= DT_END_ELLIPSIS; + + // In order to make sure RTL/BiDi strings are rendered correctly, we must + // pass the flag DT_RTLREADING to DrawText (when the locale's language is + // a right-to-left language) so that Windows does the right thing. + // + // In addition to correctly displaying text containing both RTL and LTR + // elements (for example, a string containing a telephone number within a + // sentence in Hebrew, or a sentence in Hebrew that contains a word in + // English) this flag also makes sure that if there is not enough space to + // display the entire string, the ellipsis is displayed on the left hand side + // of the truncated string and not on the right hand side. + // + // We make a distinction between Chrome UI strings and text coming from a web + // page. + // + // For text coming from a web page we determine the alignment based on the + // first character with strong directionality. If the directionality of the + // first character with strong directionality in the text is LTR, the + // alignment is set to DT_LEFT, and the directionality should not be set as + // DT_RTLREADING. + // + // This heuristic doesn't work for Chrome UI strings since even in RTL + // locales, some of those might start with English text but we know they're + // localized so we always want them to be right aligned, and their + // directionality should be set as DT_RTLREADING. + // + // Caveat: If the string is purely LTR, don't set DTL_RTLREADING since when + // the flag is set, LRE-PDF don't have the desired effect of rendering + // multiline English-only text as LTR. + // + // Note that if the caller is explicitly requesting displaying the text + // using RTL directionality then we respect that and pass DT_RTLREADING to + // ::DrawText even if the locale is LTR. + if ((flags & gfx::Canvas::FORCE_RTL_DIRECTIONALITY) || + (base::i18n::IsRTL() && + (f & DT_RIGHT) && base::i18n::StringContainsStrongRTLChars(text))) { + f |= DT_RTLREADING; + } + + return f; +} + +} // anonymous namespace + +namespace gfx { + +CanvasSkia::CanvasSkia(int width, int height, bool is_opaque) + : skia::PlatformCanvas(width, height, is_opaque) { +} + +CanvasSkia::CanvasSkia() : skia::PlatformCanvas() { +} + +CanvasSkia::~CanvasSkia() { +} + +// static +void CanvasSkia::SizeStringInt(const string16& text, + const gfx::Font& font, + int* width, int* height, + int flags) { + // Clamp the max amount of text we'll measure to 2K. When the string is + // actually drawn, it will be clipped to whatever size box is provided, and + // the time to do that doesn't depend on the length being clipped off. + const int kMaxStringLength = 2048 - 1; // So the trailing \0 fits in 2K. + string16 clamped_string(text.substr(0, kMaxStringLength)); + + if (*width == 0) { + // If multi-line + character break are on, the computed width will be one + // character wide (useless). Furthermore, if in this case the provided text + // contains very long "words" (substrings without a word-breaking point), + // DrawText() can run extremely slowly (e.g. several seconds). So in this + // case, we turn character breaking off to get a more accurate "desired" + // width and avoid the slowdown. + if (flags & (gfx::Canvas::MULTI_LINE | gfx::Canvas::CHARACTER_BREAK)) + flags &= ~gfx::Canvas::CHARACTER_BREAK; + + // Weird undocumented behavior: if the width is 0, DoDrawText() won't + // calculate a size at all. So set it to 1, which it will then change. + if (!text.empty()) + *width = 1; + } + RECT r = { 0, 0, *width, *height }; + + HDC dc = GetDC(NULL); + HFONT old_font = static_cast<HFONT>(SelectObject(dc, font.GetNativeFont())); + DoDrawText(dc, clamped_string, &r, + ComputeFormatFlags(flags, clamped_string) | DT_CALCRECT); + SelectObject(dc, old_font); + ReleaseDC(NULL, dc); + + *width = r.right; + *height = r.bottom; +} + +void CanvasSkia::DrawStringInt(const string16& text, + HFONT font, + const SkColor& color, + int x, int y, int w, int h, + int flags) { + if (!IntersectsClipRectInt(x, y, w, h)) + return; + + // Clamp the max amount of text we'll draw to 32K. There seem to be bugs in + // DrawText() if you e.g. ask it to character-break a no-whitespace string of + // length > 43680 (for which it draws nothing), and since we clamped to 2K in + // SizeStringInt() we're unlikely to be able to display this much anyway. + const int kMaxStringLength = 32768 - 1; // So the trailing \0 fits in 32K. + string16 clamped_string(text.substr(0, kMaxStringLength)); + + RECT text_bounds = { x, y, x + w, y + h }; + HDC dc = beginPlatformPaint(); + SetBkMode(dc, TRANSPARENT); + HFONT old_font = (HFONT)SelectObject(dc, font); + COLORREF brush_color = RGB(SkColorGetR(color), SkColorGetG(color), + SkColorGetB(color)); + SetTextColor(dc, brush_color); + + int f = ComputeFormatFlags(flags, clamped_string); + DoDrawText(dc, clamped_string, &text_bounds, f); + endPlatformPaint(); + + // Restore the old font. This way we don't have to worry if the caller + // deletes the font and the DC lives longer. + SelectObject(dc, old_font); + + // Windows will have cleared the alpha channel of the text we drew. Assume + // we're drawing to an opaque surface, or at least the text rect area is + // opaque. + getTopPlatformDevice().makeOpaque(x, y, w, h); +} + +void CanvasSkia::DrawStringInt(const string16& text, + const gfx::Font& font, + const SkColor& color, + int x, int y, int w, int h, + int flags) { + DrawStringInt(text, font.GetNativeFont(), color, x, y, w, h, flags); +} + +// Checks each pixel immediately adjacent to the given pixel in the bitmap. If +// any of them are not the halo color, returns true. This defines the halo of +// pixels that will appear around the text. Note that we have to check each +// pixel against both the halo color and transparent since DrawStringWithHalo +// will modify the bitmap as it goes, and clears pixels shouldn't count as +// changed. +static bool pixelShouldGetHalo(const SkBitmap& bitmap, + int x, int y, + SkColor halo_color) { + if (x > 0 && + *bitmap.getAddr32(x - 1, y) != halo_color && + *bitmap.getAddr32(x - 1, y) != 0) + return true; // Touched pixel to the left. + if (x < bitmap.width() - 1 && + *bitmap.getAddr32(x + 1, y) != halo_color && + *bitmap.getAddr32(x + 1, y) != 0) + return true; // Touched pixel to the right. + if (y > 0 && + *bitmap.getAddr32(x, y - 1) != halo_color && + *bitmap.getAddr32(x, y - 1) != 0) + return true; // Touched pixel above. + if (y < bitmap.height() - 1 && + *bitmap.getAddr32(x, y + 1) != halo_color && + *bitmap.getAddr32(x, y + 1) != 0) + return true; // Touched pixel below. + return false; +} + +void CanvasSkia::DrawStringWithHalo(const string16& text, + const gfx::Font& font, + const SkColor& text_color, + const SkColor& halo_color_in, + int x, int y, int w, int h, + int flags) { + // Some callers will have semitransparent halo colors, which we don't handle + // (since the resulting image can have 1-bit transparency only). + SkColor halo_color = halo_color_in | 0xFF000000; + + // Create a temporary buffer filled with the halo color. It must leave room + // for the 1-pixel border around the text. + CanvasSkia text_canvas(w + 2, h + 2, true); + SkPaint bkgnd_paint; + bkgnd_paint.setColor(halo_color); + text_canvas.DrawRectInt(0, 0, w + 2, h + 2, bkgnd_paint); + + // Draw the text into the temporary buffer. This will have correct + // ClearType since the background color is the same as the halo color. + text_canvas.DrawStringInt(text, font, text_color, 1, 1, w, h, flags); + + // Windows will have cleared the alpha channel for the pixels it drew. Make it + // opaque. We have to do this first since pixelShouldGetHalo will check for + // 0 to see if a pixel has been modified to transparent, and black text that + // Windows draw will look transparent to it! + text_canvas.getTopPlatformDevice().makeOpaque(0, 0, w + 2, h + 2); + + uint32_t halo_premul = SkPreMultiplyColor(halo_color); + SkBitmap& text_bitmap = const_cast<SkBitmap&>( + text_canvas.getTopPlatformDevice().accessBitmap(true)); + for (int cur_y = 0; cur_y < h + 2; cur_y++) { + uint32_t* text_row = text_bitmap.getAddr32(0, cur_y); + for (int cur_x = 0; cur_x < w + 2; cur_x++) { + if (text_row[cur_x] == halo_premul) { + // This pixel was not touched by the text routines. See if it borders + // a touched pixel in any of the 4 directions (not diagonally). + if (!pixelShouldGetHalo(text_bitmap, cur_x, cur_y, halo_premul)) + text_row[cur_x] = 0; // Make transparent. + } else { + text_row[cur_x] |= 0xff << SK_A32_SHIFT; // Make opaque. + } + } + } + + // Draw the halo bitmap with blur. + DrawBitmapInt(text_bitmap, x - 1, y - 1); +} + +} // namespace gfx diff --git a/ui/gfx/codec/DEPS b/ui/gfx/codec/DEPS new file mode 100644 index 0000000..e4907a6c --- /dev/null +++ b/ui/gfx/codec/DEPS @@ -0,0 +1,5 @@ +include_rules = [ + "+skia", + "+third_party/libjpeg", + "+third_party/libpng", +] diff --git a/ui/gfx/codec/jpeg_codec.cc b/ui/gfx/codec/jpeg_codec.cc new file mode 100644 index 0000000..ad499e5 --- /dev/null +++ b/ui/gfx/codec/jpeg_codec.cc @@ -0,0 +1,535 @@ +// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "gfx/codec/jpeg_codec.h" + +#include <setjmp.h> + +#include "base/logging.h" +#include "base/scoped_ptr.h" +#include "third_party/skia/include/core/SkBitmap.h" +#include "third_party/skia/include/core/SkColorPriv.h" + +extern "C" { +#if defined(USE_SYSTEM_LIBJPEG) +#include <jpeglib.h> +#else +#include "jpeglib.h" +#endif +} + +namespace gfx { + +// Encoder/decoder shared stuff ------------------------------------------------ + +namespace { + +// used to pass error info through the JPEG library +struct CoderErrorMgr { + jpeg_error_mgr pub; + jmp_buf setjmp_buffer; +}; + +void ErrorExit(jpeg_common_struct* cinfo) { + CoderErrorMgr *err = reinterpret_cast<CoderErrorMgr*>(cinfo->err); + + // Return control to the setjmp point. + longjmp(err->setjmp_buffer, false); +} + +} // namespace + +// Encoder --------------------------------------------------------------------- +// +// This code is based on nsJPEGEncoder from Mozilla. +// Copyright 2005 Google Inc. (Brett Wilson, contributor) + +namespace { + +// Initial size for the output buffer in the JpegEncoderState below. +static const int initial_output_buffer_size = 8192; + +struct JpegEncoderState { + explicit JpegEncoderState(std::vector<unsigned char>* o) + : out(o), + image_buffer_used(0) { + } + + // Output buffer, of which 'image_buffer_used' bytes are actually used (this + // will often be less than the actual size of the vector because we size it + // so that libjpeg can write directly into it. + std::vector<unsigned char>* out; + + // Number of bytes in the 'out' buffer that are actually used (see above). + size_t image_buffer_used; +}; + +// Initializes the JpegEncoderState for encoding, and tells libjpeg about where +// the output buffer is. +// +// From the JPEG library: +// "Initialize destination. This is called by jpeg_start_compress() before +// any data is actually written. It must initialize next_output_byte and +// free_in_buffer. free_in_buffer must be initialized to a positive value." +void InitDestination(jpeg_compress_struct* cinfo) { + JpegEncoderState* state = static_cast<JpegEncoderState*>(cinfo->client_data); + DCHECK(state->image_buffer_used == 0) << "initializing after use"; + + state->out->resize(initial_output_buffer_size); + state->image_buffer_used = 0; + + cinfo->dest->next_output_byte = &(*state->out)[0]; + cinfo->dest->free_in_buffer = initial_output_buffer_size; +} + +// Resize the buffer that we give to libjpeg and update our and its state. +// +// From the JPEG library: +// "Callback used by libjpeg whenever the buffer has filled (free_in_buffer +// reaches zero). In typical applications, it should write out the *entire* +// buffer (use the saved start address and buffer length; ignore the current +// state of next_output_byte and free_in_buffer). Then reset the pointer & +// count to the start of the buffer, and return TRUE indicating that the +// buffer has been dumped. free_in_buffer must be set to a positive value +// when TRUE is returned. A FALSE return should only be used when I/O +// suspension is desired (this operating mode is discussed in the next +// section)." +boolean EmptyOutputBuffer(jpeg_compress_struct* cinfo) { + JpegEncoderState* state = static_cast<JpegEncoderState*>(cinfo->client_data); + + // note the new size, the buffer is full + state->image_buffer_used = state->out->size(); + + // expand buffer, just double size each time + state->out->resize(state->out->size() * 2); + + // tell libjpeg where to write the next data + cinfo->dest->next_output_byte = &(*state->out)[state->image_buffer_used]; + cinfo->dest->free_in_buffer = state->out->size() - state->image_buffer_used; + return 1; +} + +// Cleans up the JpegEncoderState to prepare for returning in the final form. +// +// From the JPEG library: +// "Terminate destination --- called by jpeg_finish_compress() after all data +// has been written. In most applications, this must flush any data +// remaining in the buffer. Use either next_output_byte or free_in_buffer to +// determine how much data is in the buffer." +void TermDestination(jpeg_compress_struct* cinfo) { + JpegEncoderState* state = static_cast<JpegEncoderState*>(cinfo->client_data); + DCHECK(state->out->size() >= state->image_buffer_used); + + // update the used byte based on the next byte libjpeg would write to + state->image_buffer_used = cinfo->dest->next_output_byte - &(*state->out)[0]; + DCHECK(state->image_buffer_used < state->out->size()) << + "JPEG library busted, got a bad image buffer size"; + + // update our buffer so that it exactly encompases the desired data + state->out->resize(state->image_buffer_used); +} + +// Converts RGBA to RGB (removing the alpha values) to prepare to send data to +// libjpeg. This converts one row of data in rgba with the given width in +// pixels the the given rgb destination buffer (which should have enough space +// reserved for the final data). +void StripAlpha(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]; + } +} + +// Converts BGRA to RGB by reordering the color components and dropping the +// alpha. This converts one row of data in rgba with the given width in +// pixels the the given rgb destination buffer (which should have enough space +// reserved for the final data). +void BGRAtoRGB(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]; + } +} + +// This class destroys the given jpeg_compress object when it goes out of +// scope. It simplifies the error handling in Encode (and even applies to the +// success case). +class CompressDestroyer { + public: + CompressDestroyer() : cinfo_(NULL) { + } + ~CompressDestroyer() { + DestroyManagedObject(); + } + void SetManagedObject(jpeg_compress_struct* ci) { + DestroyManagedObject(); + cinfo_ = ci; + } + void DestroyManagedObject() { + if (cinfo_) { + jpeg_destroy_compress(cinfo_); + cinfo_ = NULL; + } + } + private: + jpeg_compress_struct* cinfo_; +}; + +} // namespace + +bool JPEGCodec::Encode(const unsigned char* input, ColorFormat format, + int w, int h, int row_byte_width, + int quality, std::vector<unsigned char>* output) { + jpeg_compress_struct cinfo; + CompressDestroyer destroyer; + destroyer.SetManagedObject(&cinfo); + output->clear(); + + // We set up the normal JPEG error routines, then override error_exit. + // This must be done before the call to create_compress. + CoderErrorMgr errmgr; + cinfo.err = jpeg_std_error(&errmgr.pub); + errmgr.pub.error_exit = ErrorExit; + // Establish the setjmp return context for ErrorExit to use. + if (setjmp(errmgr.setjmp_buffer)) { + // If we get here, the JPEG code has signaled an error. + // MSDN notes: "if you intend your code to be portable, do not rely on + // correct destruction of frame-based objects when executing a nonlocal + // goto using a call to longjmp." So we delete the CompressDestroyer's + // object manually instead. + destroyer.DestroyManagedObject(); + return false; + } + + // The destroyer will destroy() cinfo on exit. + jpeg_create_compress(&cinfo); + + cinfo.image_width = w; + cinfo.image_height = h; + cinfo.input_components = 3; + cinfo.in_color_space = JCS_RGB; + cinfo.data_precision = 8; + + jpeg_set_defaults(&cinfo); + jpeg_set_quality(&cinfo, quality, 1); // quality here is 0-100 + + // set up the destination manager + jpeg_destination_mgr destmgr; + destmgr.init_destination = InitDestination; + destmgr.empty_output_buffer = EmptyOutputBuffer; + destmgr.term_destination = TermDestination; + cinfo.dest = &destmgr; + + JpegEncoderState state(output); + cinfo.client_data = &state; + + jpeg_start_compress(&cinfo, 1); + + // feed it the rows, doing necessary conversions for the color format + if (format == FORMAT_RGB) { + // no conversion necessary + while (cinfo.next_scanline < cinfo.image_height) { + const unsigned char* row = &input[cinfo.next_scanline * row_byte_width]; + jpeg_write_scanlines(&cinfo, const_cast<unsigned char**>(&row), 1); + } + } else { + // get the correct format converter + void (*converter)(const unsigned char* in, int w, unsigned char* rgb); + if (format == FORMAT_RGBA || + (format == FORMAT_SkBitmap && SK_R32_SHIFT == 0)) { + converter = StripAlpha; + } else if (format == FORMAT_BGRA || + (format == FORMAT_SkBitmap && SK_B32_SHIFT == 0)) { + converter = BGRAtoRGB; + } else { + NOTREACHED() << "Invalid pixel format"; + return false; + } + + // output row after converting + unsigned char* row = new unsigned char[w * 3]; + + while (cinfo.next_scanline < cinfo.image_height) { + converter(&input[cinfo.next_scanline * row_byte_width], w, row); + jpeg_write_scanlines(&cinfo, &row, 1); + } + delete[] row; + } + + jpeg_finish_compress(&cinfo); + return true; +} + +// Decoder -------------------------------------------------------------------- + +namespace { + +struct JpegDecoderState { + JpegDecoderState(const unsigned char* in, size_t len) + : input_buffer(in), input_buffer_length(len) { + } + + const unsigned char* input_buffer; + size_t input_buffer_length; +}; + +// Callback to initialize the source. +// +// From the JPEG library: +// "Initialize source. This is called by jpeg_read_header() before any data is +// actually read. May leave bytes_in_buffer set to 0 (in which case a +// fill_input_buffer() call will occur immediately)." +void InitSource(j_decompress_ptr cinfo) { + JpegDecoderState* state = static_cast<JpegDecoderState*>(cinfo->client_data); + cinfo->src->next_input_byte = state->input_buffer; + cinfo->src->bytes_in_buffer = state->input_buffer_length; +} + +// Callback to fill the buffer. Since our buffer already contains all the data, +// we should never need to provide more data. If libjpeg thinks it needs more +// data, our input is probably corrupt. +// +// From the JPEG library: +// "This is called whenever bytes_in_buffer has reached zero and more data is +// wanted. In typical applications, it should read fresh data into the buffer +// (ignoring the current state of next_input_byte and bytes_in_buffer), reset +// the pointer & count to the start of the buffer, and return TRUE indicating +// that the buffer has been reloaded. It is not necessary to fill the buffer +// entirely, only to obtain at least one more byte. bytes_in_buffer MUST be +// set to a positive value if TRUE is returned. A FALSE return should only +// be used when I/O suspension is desired." +boolean FillInputBuffer(j_decompress_ptr cinfo) { + return false; +} + +// Skip data in the buffer. Since we have all the data at once, this operation +// is easy. It is not clear if this ever gets called because the JPEG library +// should be able to do the skip itself (it has all the data). +// +// From the JPEG library: +// "Skip num_bytes worth of data. The buffer pointer and count should be +// advanced over num_bytes input bytes, refilling the buffer as needed. This +// is used to skip over a potentially large amount of uninteresting data +// (such as an APPn marker). In some applications it may be possible to +// optimize away the reading of the skipped data, but it's not clear that +// being smart is worth much trouble; large skips are uncommon. +// bytes_in_buffer may be zero on return. A zero or negative skip count +// should be treated as a no-op." +void SkipInputData(j_decompress_ptr cinfo, long num_bytes) { + if (num_bytes > static_cast<long>(cinfo->src->bytes_in_buffer)) { + // Since all our data should be in the buffer, trying to skip beyond it + // means that there is some kind of error or corrupt input data. A 0 for + // bytes left means it will call FillInputBuffer which will then fail. + cinfo->src->next_input_byte += cinfo->src->bytes_in_buffer; + cinfo->src->bytes_in_buffer = 0; + } else if (num_bytes > 0) { + cinfo->src->bytes_in_buffer -= static_cast<size_t>(num_bytes); + cinfo->src->next_input_byte += num_bytes; + } +} + +// Our source doesn't need any cleanup, so this is a NOP. +// +// From the JPEG library: +// "Terminate source --- called by jpeg_finish_decompress() after all data has +// been read to clean up JPEG source manager. NOT called by jpeg_abort() or +// jpeg_destroy()." +void TermSource(j_decompress_ptr cinfo) { +} + +// Converts one row of rgb data to rgba data by adding a fully-opaque alpha +// value. +void AddAlpha(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; + } +} + +// Converts one row of RGB data to BGRA by reordering the color components and +// adding alpha values of 0xff. +void RGBtoBGRA(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 * 3]; + unsigned char* pixel_out = &rgb[x * 4]; + pixel_out[0] = pixel_in[2]; + pixel_out[1] = pixel_in[1]; + pixel_out[2] = pixel_in[0]; + pixel_out[3] = 0xff; + } +} + +// This class destroys the given jpeg_decompress object when it goes out of +// scope. It simplifies the error handling in Decode (and even applies to the +// success case). +class DecompressDestroyer { + public: + DecompressDestroyer() : cinfo_(NULL) { + } + ~DecompressDestroyer() { + DestroyManagedObject(); + } + void SetManagedObject(jpeg_decompress_struct* ci) { + DestroyManagedObject(); + cinfo_ = ci; + } + void DestroyManagedObject() { + if (cinfo_) { + jpeg_destroy_decompress(cinfo_); + cinfo_ = NULL; + } + } + private: + jpeg_decompress_struct* cinfo_; +}; + +} // namespace + +bool JPEGCodec::Decode(const unsigned char* input, size_t input_size, + ColorFormat format, std::vector<unsigned char>* output, + int* w, int* h) { + jpeg_decompress_struct cinfo; + DecompressDestroyer destroyer; + destroyer.SetManagedObject(&cinfo); + output->clear(); + + // We set up the normal JPEG error routines, then override error_exit. + // This must be done before the call to create_decompress. + CoderErrorMgr errmgr; + cinfo.err = jpeg_std_error(&errmgr.pub); + errmgr.pub.error_exit = ErrorExit; + // Establish the setjmp return context for ErrorExit to use. + if (setjmp(errmgr.setjmp_buffer)) { + // If we get here, the JPEG code has signaled an error. + // See note in JPEGCodec::Encode() for why we need to destroy the cinfo + // manually here. + destroyer.DestroyManagedObject(); + return false; + } + + // The destroyer will destroy() cinfo on exit. We don't want to set the + // destroyer's object until cinfo is initialized. + jpeg_create_decompress(&cinfo); + + // set up the source manager + jpeg_source_mgr srcmgr; + srcmgr.init_source = InitSource; + srcmgr.fill_input_buffer = FillInputBuffer; + srcmgr.skip_input_data = SkipInputData; + srcmgr.resync_to_restart = jpeg_resync_to_restart; // use default routine + srcmgr.term_source = TermSource; + cinfo.src = &srcmgr; + + JpegDecoderState state(input, input_size); + cinfo.client_data = &state; + + // fill the file metadata into our buffer + if (jpeg_read_header(&cinfo, true) != JPEG_HEADER_OK) + return false; + + // we want to always get RGB data out + switch (cinfo.jpeg_color_space) { + case JCS_GRAYSCALE: + case JCS_RGB: + case JCS_YCbCr: + cinfo.out_color_space = JCS_RGB; + break; + case JCS_CMYK: + case JCS_YCCK: + default: + // Mozilla errors out on these color spaces, so I presume that the jpeg + // library can't do automatic color space conversion for them. We don't + // care about these anyway. + return false; + } + cinfo.output_components = 3; + + jpeg_calc_output_dimensions(&cinfo); + *w = cinfo.output_width; + *h = cinfo.output_height; + + jpeg_start_decompress(&cinfo); + + // FIXME(brettw) we may want to allow the capability for callers to request + // how to align row lengths as we do for the compressor. + int row_read_stride = cinfo.output_width * cinfo.output_components; + + if (format == FORMAT_RGB) { + // easy case, row needs no conversion + int row_write_stride = row_read_stride; + output->resize(row_write_stride * cinfo.output_height); + + for (int row = 0; row < static_cast<int>(cinfo.output_height); row++) { + unsigned char* rowptr = &(*output)[row * row_write_stride]; + if (!jpeg_read_scanlines(&cinfo, &rowptr, 1)) + return false; + } + } else { + // Rows need conversion to output format: read into a temporary buffer and + // expand to the final one. Performance: we could avoid the extra + // allocation by doing the expansion in-place. + int row_write_stride; + void (*converter)(const unsigned char* rgb, int w, unsigned char* out); + if (format == FORMAT_RGBA || + (format == FORMAT_SkBitmap && SK_R32_SHIFT == 0)) { + row_write_stride = cinfo.output_width * 4; + converter = AddAlpha; + } else if (format == FORMAT_BGRA || + (format == FORMAT_SkBitmap && SK_B32_SHIFT == 0)) { + row_write_stride = cinfo.output_width * 4; + converter = RGBtoBGRA; + } else { + NOTREACHED() << "Invalid pixel format"; + jpeg_destroy_decompress(&cinfo); + return false; + } + + output->resize(row_write_stride * cinfo.output_height); + + scoped_array<unsigned char> row_data(new unsigned char[row_read_stride]); + unsigned char* rowptr = row_data.get(); + for (int row = 0; row < static_cast<int>(cinfo.output_height); row++) { + if (!jpeg_read_scanlines(&cinfo, &rowptr, 1)) + return false; + converter(rowptr, *w, &(*output)[row * row_write_stride]); + } + } + + jpeg_finish_decompress(&cinfo); + jpeg_destroy_decompress(&cinfo); + return true; +} + +// static +SkBitmap* JPEGCodec::Decode(const unsigned char* input, size_t input_size) { + int w, h; + std::vector<unsigned char> data_vector; + if (!Decode(input, input_size, FORMAT_SkBitmap, &data_vector, &w, &h)) + return NULL; + + // Skia only handles 32 bit images. + int data_length = w * h * 4; + + SkBitmap* bitmap = new SkBitmap(); + bitmap->setConfig(SkBitmap::kARGB_8888_Config, w, h); + bitmap->allocPixels(); + memcpy(bitmap->getAddr32(0, 0), &data_vector[0], data_length); + + return bitmap; +} + +} // namespace gfx diff --git a/ui/gfx/codec/jpeg_codec.h b/ui/gfx/codec/jpeg_codec.h new file mode 100644 index 0000000..5ad4a4a8 --- /dev/null +++ b/ui/gfx/codec/jpeg_codec.h @@ -0,0 +1,68 @@ +// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_CODEC_JPEG_CODEC_H_ +#define UI_GFX_CODEC_JPEG_CODEC_H_ +#pragma once + +#include <vector> + +class SkBitmap; + +namespace gfx { + +// Interface for encoding/decoding JPEG data. This is a wrapper around libjpeg, +// which has an inconvenient interface for callers. This is only used for UI +// elements, WebKit has its own more complicated JPEG decoder which handles, +// among other things, partially downloaded data. +class JPEGCodec { + 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 mem regardless of endianness. + FORMAT_RGBA, + + // 4 bytes per pixel, in BGRA order in mem regardless of endianness. + // This is the default Windows DIB order. + FORMAT_BGRA, + + // 4 bytes per pixel, it can be either RGBA or BGRA. It depends on the bit + // order in kARGB_8888_Config skia bitmap. + FORMAT_SkBitmap + }; + + // Encodes the given raw 'input' data, with each pixel being represented as + // given in 'format'. The encoded JPEG 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. + // + // 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). + // quality: an integer in the range 0-100, where 100 is the highest quality. + static bool Encode(const unsigned char* input, ColorFormat format, + int w, int h, int row_byte_width, + int quality, std::vector<unsigned char>* output); + + // Decodes the JPEG 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 is undefined. + static bool Decode(const unsigned char* input, size_t input_size, + ColorFormat format, std::vector<unsigned char>* output, + int* w, int* h); + + // Decodes the JPEG data contained in input of length input_size. If + // successful, a SkBitmap is created and returned. It is up to the caller + // to delete the returned bitmap. + static SkBitmap* Decode(const unsigned char* input, size_t input_size); +}; + +} // namespace gfx + +#endif // UI_GFX_CODEC_JPEG_CODEC_H_ diff --git a/ui/gfx/codec/jpeg_codec_unittest.cc b/ui/gfx/codec/jpeg_codec_unittest.cc new file mode 100644 index 0000000..16fc848 --- /dev/null +++ b/ui/gfx/codec/jpeg_codec_unittest.cc @@ -0,0 +1,151 @@ +// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include <math.h> + +#include "gfx/codec/jpeg_codec.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace gfx { + +// out of 100, this indicates how compressed it will be, this should be changed +// with jpeg equality threshold +// static int jpeg_quality = 75; // FIXME(brettw) +static int jpeg_quality = 100; + +// The threshold of average color differences where we consider two images +// equal. This number was picked to be a little above the observed difference +// using the above quality. +static double jpeg_equality_threshold = 1.0; + +// Computes the average difference between each value in a and b. A and b +// should be the same size. Used to see if two images are approximately equal +// in the presence of compression. +static double AveragePixelDelta(const std::vector<unsigned char>& a, + const std::vector<unsigned char>& b) { + // if the sizes are different, say the average difference is the maximum + if (a.size() != b.size()) + return 255.0; + if (a.size() == 0) + return 0; // prevent divide by 0 below + + double acc = 0.0; + for (size_t i = 0; i < a.size(); i++) + acc += fabs(static_cast<double>(a[i]) - static_cast<double>(b[i])); + + return acc / static_cast<double>(a.size()); +} + +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 + } + } +} + +TEST(JPEGCodec, EncodeDecodeRGB) { + int w = 20, h = 20; + + // create an image with known values + std::vector<unsigned char> original; + MakeRGBImage(w, h, &original); + + // encode, making sure it was compressed some + std::vector<unsigned char> encoded; + EXPECT_TRUE(JPEGCodec::Encode(&original[0], JPEGCodec::FORMAT_RGB, w, h, + w * 3, jpeg_quality, &encoded)); + EXPECT_GT(original.size(), encoded.size()); + + // decode, it should have the same size as the original + std::vector<unsigned char> decoded; + int outw, outh; + EXPECT_TRUE(JPEGCodec::Decode(&encoded[0], encoded.size(), + JPEGCodec::FORMAT_RGB, &decoded, + &outw, &outh)); + ASSERT_EQ(w, outw); + ASSERT_EQ(h, outh); + ASSERT_EQ(original.size(), decoded.size()); + + // Images must be approximately equal (compression will have introduced some + // minor artifacts). + ASSERT_GE(jpeg_equality_threshold, AveragePixelDelta(original, decoded)); +} + +TEST(JPEGCodec, EncodeDecodeRGBA) { + int w = 20, h = 20; + + // create an image with known values, a must be opaque because it will be + // lost during compression + std::vector<unsigned char> original; + original.resize(w * h * 4); + for (int y = 0; y < h; y++) { + for (int x = 0; x < w; x++) { + unsigned char* org_px = &original[(y * w + x) * 4]; + org_px[0] = x * 3; // r + org_px[1] = x * 3 + 1; // g + org_px[2] = x * 3 + 2; // b + org_px[3] = 0xFF; // a (opaque) + } + } + + // encode, making sure it was compressed some + std::vector<unsigned char> encoded; + EXPECT_TRUE(JPEGCodec::Encode(&original[0], JPEGCodec::FORMAT_RGBA, w, h, + w * 4, jpeg_quality, &encoded)); + EXPECT_GT(original.size(), encoded.size()); + + // decode, it should have the same size as the original + std::vector<unsigned char> decoded; + int outw, outh; + EXPECT_TRUE(JPEGCodec::Decode(&encoded[0], encoded.size(), + JPEGCodec::FORMAT_RGBA, &decoded, + &outw, &outh)); + ASSERT_EQ(w, outw); + ASSERT_EQ(h, outh); + ASSERT_EQ(original.size(), decoded.size()); + + // Images must be approximately equal (compression will have introduced some + // minor artifacts). + ASSERT_GE(jpeg_equality_threshold, AveragePixelDelta(original, decoded)); +} + +// Test that corrupted data decompression causes failures. +TEST(JPEGCodec, DecodeCorrupted) { + int w = 20, h = 20; + + // 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; + ASSERT_FALSE(JPEGCodec::Decode(&original[0], original.size(), + JPEGCodec::FORMAT_RGB, &output, + &outw, &outh)); + + // make some compressed data + std::vector<unsigned char> compressed; + ASSERT_TRUE(JPEGCodec::Encode(&original[0], JPEGCodec::FORMAT_RGB, w, h, + w * 3, jpeg_quality, &compressed)); + + // try decompressing a truncated version + ASSERT_FALSE(JPEGCodec::Decode(&compressed[0], compressed.size() / 2, + JPEGCodec::FORMAT_RGB, &output, + &outw, &outh)); + + // corrupt it and try decompressing that + for (int i = 10; i < 30; i++) + compressed[i] = i; + ASSERT_FALSE(JPEGCodec::Decode(&compressed[0], compressed.size(), + JPEGCodec::FORMAT_RGB, &output, + &outw, &outh)); +} + +} // namespace gfx diff --git a/ui/gfx/codec/png_codec.cc b/ui/gfx/codec/png_codec.cc new file mode 100644 index 0000000..5fd6d7a --- /dev/null +++ b/ui/gfx/codec/png_codec.cc @@ -0,0 +1,699 @@ +// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "gfx/codec/png_codec.h" + +#include "base/logging.h" +#include "base/scoped_ptr.h" +#include "third_party/skia/include/core/SkBitmap.h" +#include "third_party/skia/include/core/SkUnPreMultiply.h" +#include "third_party/skia/include/core/SkColorPriv.h" + +extern "C" { +#if defined(USE_SYSTEM_LIBPNG) +#include <png.h> +#else +#include "third_party/libpng/png.h" +#endif +} + +namespace gfx { + +namespace { + +// Converts BGRA->RGBA and RGBA->BGRA. +void ConvertBetweenBGRAandRGBA(const unsigned char* input, int pixel_width, + unsigned char* output, bool* is_opaque) { + 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, bool* is_opaque) { + 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]; + } +} + +void ConvertRGBtoSkia(const unsigned char* rgb, int pixel_width, + unsigned char* rgba, bool* is_opaque) { + for (int x = 0; x < pixel_width; x++) { + const unsigned char* pixel_in = &rgb[x * 3]; + uint32_t* pixel_out = reinterpret_cast<uint32_t*>(&rgba[x * 4]); + *pixel_out = SkPackARGB32(0xFF, pixel_in[0], pixel_in[1], pixel_in[2]); + } +} + +void ConvertRGBAtoSkia(const unsigned char* rgb, int pixel_width, + unsigned char* rgba, bool* is_opaque) { + int total_length = pixel_width * 4; + for (int x = 0; x < total_length; x += 4) { + const unsigned char* pixel_in = &rgb[x]; + uint32_t* pixel_out = reinterpret_cast<uint32_t*>(&rgba[x]); + + unsigned char alpha = pixel_in[3]; + if (alpha != 255) { + *is_opaque = false; + *pixel_out = SkPreMultiplyARGB(alpha, + pixel_in[0], pixel_in[1], pixel_in[2]); + } else { + *pixel_out = SkPackARGB32(alpha, + pixel_in[0], pixel_in[1], pixel_in[2]); + } + } +} + +void ConvertSkiatoRGB(const unsigned char* skia, int pixel_width, + unsigned char* rgb, bool* is_opaque) { + for (int x = 0; x < pixel_width; x++) { + const uint32_t pixel_in = *reinterpret_cast<const uint32_t*>(&skia[x * 4]); + unsigned char* pixel_out = &rgb[x * 3]; + + int alpha = SkGetPackedA32(pixel_in); + if (alpha != 0 && alpha != 255) { + SkColor unmultiplied = SkUnPreMultiply::PMColorToColor(pixel_in); + pixel_out[0] = SkColorGetR(unmultiplied); + pixel_out[1] = SkColorGetG(unmultiplied); + pixel_out[2] = SkColorGetB(unmultiplied); + } else { + pixel_out[0] = SkGetPackedR32(pixel_in); + pixel_out[1] = SkGetPackedG32(pixel_in); + pixel_out[2] = SkGetPackedB32(pixel_in); + } + } +} + +void ConvertSkiatoRGBA(const unsigned char* skia, int pixel_width, + unsigned char* rgba, bool* is_opaque) { + int total_length = pixel_width * 4; + for (int i = 0; i < total_length; i += 4) { + const uint32_t pixel_in = *reinterpret_cast<const uint32_t*>(&skia[i]); + + // Pack the components here. + int alpha = SkGetPackedA32(pixel_in); + if (alpha != 0 && alpha != 255) { + SkColor unmultiplied = SkUnPreMultiply::PMColorToColor(pixel_in); + rgba[i + 0] = SkColorGetR(unmultiplied); + rgba[i + 1] = SkColorGetG(unmultiplied); + rgba[i + 2] = SkColorGetB(unmultiplied); + rgba[i + 3] = alpha; + } else { + rgba[i + 0] = SkGetPackedR32(pixel_in); + rgba[i + 1] = SkGetPackedG32(pixel_in); + rgba[i + 2] = SkGetPackedB32(pixel_in); + rgba[i + 3] = alpha; + } + } +} + +} // 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; + +class PngDecoderState { + public: + // Output is a vector<unsigned char>. + PngDecoderState(PNGCodec::ColorFormat ofmt, std::vector<unsigned char>* o) + : output_format(ofmt), + output_channels(0), + bitmap(NULL), + is_opaque(true), + output(o), + row_converter(NULL), + width(0), + height(0), + done(false) { + } + + // Output is an SkBitmap. + explicit PngDecoderState(SkBitmap* skbitmap) + : output_format(PNGCodec::FORMAT_SkBitmap), + output_channels(0), + bitmap(skbitmap), + is_opaque(true), + output(NULL), + row_converter(NULL), + width(0), + height(0), + done(false) { + } + + PNGCodec::ColorFormat output_format; + int output_channels; + + // An incoming SkBitmap to write to. If NULL, we write to output instead. + SkBitmap* bitmap; + + // Used during the reading of an SkBitmap. Defaults to true until we see a + // pixel with anything other than an alpha of 255. + bool is_opaque; + + // The other way to decode output, where we write into an intermediary buffer + // instead of directly to an SkBitmap. + 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, + bool* is_opaque); + + // 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_COPY_AND_ASSIGN(PngDecoderState); +}; + +void ConvertRGBtoRGBA(const unsigned char* rgb, int pixel_width, + unsigned char* rgba, bool* is_opaque) { + 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, bool* is_opaque) { + 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. "Unreasonably big" + // means "big enough that w * h * 32bpp might overflow an int"; we choose this + // threshold to match WebKit and because a number of places in code assume + // that an image's size (in bytes) fits in a (signed) int. + unsigned long long total_size = + static_cast<unsigned long long>(w) * static_cast<unsigned long long>(h); + if (total_size > ((1 << 29) - 1)) + longjmp(png_jmpbuf(png_ptr), 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 PNGCodec::FORMAT_RGB: + state->row_converter = NULL; // no conversion necessary + state->output_channels = 3; + break; + case PNGCodec::FORMAT_RGBA: + state->row_converter = &ConvertRGBtoRGBA; + state->output_channels = 4; + break; + case PNGCodec::FORMAT_BGRA: + state->row_converter = &ConvertRGBtoBGRA; + state->output_channels = 4; + break; + case PNGCodec::FORMAT_SkBitmap: + state->row_converter = &ConvertRGBtoSkia; + state->output_channels = 4; + break; + default: + NOTREACHED() << "Unknown output format"; + break; + } + } else if (channels == 4) { + switch (state->output_format) { + case PNGCodec::FORMAT_RGB: + state->row_converter = &ConvertRGBAtoRGB; + state->output_channels = 3; + break; + case PNGCodec::FORMAT_RGBA: + state->row_converter = NULL; // no conversion necessary + state->output_channels = 4; + break; + case PNGCodec::FORMAT_BGRA: + state->row_converter = &ConvertBetweenBGRAandRGBA; + state->output_channels = 4; + break; + case PNGCodec::FORMAT_SkBitmap: + state->row_converter = &ConvertRGBAtoSkia; + state->output_channels = 4; + break; + default: + NOTREACHED() << "Unknown output format"; + break; + } + } else { + NOTREACHED() << "Unknown input channels"; + longjmp(png_jmpbuf(png_ptr), 1); + } + + if (state->bitmap) { + state->bitmap->setConfig(SkBitmap::kARGB_8888_Config, + state->width, state->height); + state->bitmap->allocPixels(); + } else if (state->output) { + 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* base = NULL; + if (state->bitmap) + base = reinterpret_cast<unsigned char*>(state->bitmap->getAddr32(0, 0)); + else if (state->output) + base = &state->output->front(); + + unsigned char* dest = &base[state->width * state->output_channels * row_num]; + if (state->row_converter) + state->row_converter(new_row, state->width, dest, &state->is_opaque); + 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_; +}; + +bool BuildPNGStruct(const unsigned char* input, size_t input_size, + png_struct** png_ptr, png_info** info_ptr) { + 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_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); + if (!*png_ptr) + return false; + + *info_ptr = png_create_info_struct(*png_ptr); + if (!*info_ptr) { + png_destroy_read_struct(png_ptr, NULL, NULL); + return false; + } + + return true; +} + +} // namespace + +// static +bool PNGCodec::Decode(const unsigned char* input, size_t input_size, + ColorFormat format, std::vector<unsigned char>* output, + int* w, int* h) { + png_struct* png_ptr = NULL; + png_info* info_ptr = NULL; + if (!BuildPNGStruct(input, input_size, &png_ptr, &info_ptr)) + 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 PNGCodec::Decode(const unsigned char* input, size_t input_size, + SkBitmap* bitmap) { + DCHECK(bitmap); + png_struct* png_ptr = NULL; + png_info* info_ptr = NULL; + if (!BuildPNGStruct(input, input_size, &png_ptr, &info_ptr)) + 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(bitmap); + + 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) { + return false; + } + + // Set the bitmap's opaqueness based on what we saw. + bitmap->setIsOpaque(state.is_opaque); + + return true; +} + +// static +SkBitmap* PNGCodec::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; +} + +// 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 { + explicit 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 FakeFlushCallback(png_structp png) { + // We don't need to perform any flushing since we aren't doing real IO, but + // we're required to provide this function by libpng. +} + +void ConvertBGRAtoRGB(const unsigned char* bgra, int pixel_width, + unsigned char* rgb, bool* is_opaque) { + 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]; + } +} + +// The type of functions usable for converting between pixel formats. +typedef void (*FormatConverter)(const unsigned char* in, int w, + unsigned char* out, bool* is_opaque); + +// libpng uses a wacky setjmp-based API, which makes the compiler nervous. +// We constrain all of the calls we make to libpng where the setjmp() is in +// place to this function. +// Returns true on success. +bool DoLibpngWrite(png_struct* png_ptr, png_info* info_ptr, + PngEncoderState* state, + int width, int height, int row_byte_width, + const unsigned char* input, + int png_output_color_type, int output_color_components, + FormatConverter converter) { + // Make sure to not declare any locals here -- locals in the presence + // of setjmp() in C++ code makes gcc complain. + + if (setjmp(png_jmpbuf(png_ptr))) + return false; + + // Set our callback for libpng to give us the data. + png_set_write_fn(png_ptr, state, EncoderWriteCallback, FakeFlushCallback); + + png_set_IHDR(png_ptr, info_ptr, width, height, 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 < height; 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[width * output_color_components]; + for (int y = 0; y < height; y ++) { + converter(&input[y * row_byte_width], width, row, NULL); + png_write_row(png_ptr, row); + } + delete[] row; + } + + png_write_end(png_ptr, info_ptr); + return true; +} + +} // namespace + +// static +bool PNGCodec::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. + FormatConverter converter = 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; + + case FORMAT_SkBitmap: + input_color_components = 4; + if (discard_transparency) { + output_color_components = 3; + png_output_color_type = PNG_COLOR_TYPE_RGB; + converter = ConvertSkiatoRGB; + } else { + output_color_components = 4; + png_output_color_type = PNG_COLOR_TYPE_RGB_ALPHA; + converter = ConvertSkiatoRGBA; + } + 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, + NULL, NULL, 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; + } + + PngEncoderState state(output); + bool success = DoLibpngWrite(png_ptr, info_ptr, &state, + w, h, row_byte_width, input, + png_output_color_type, output_color_components, + converter); + png_destroy_write_struct(&png_ptr, &info_ptr); + + return success; +} + +// static +bool PNGCodec::EncodeBGRASkBitmap(const SkBitmap& input, + bool discard_transparency, + std::vector<unsigned char>* output) { + static const int bbp = 4; + + SkAutoLockPixels lock_input(input); + DCHECK(input.empty() || input.bytesPerPixel() == bbp); + + return Encode(reinterpret_cast<unsigned char*>(input.getAddr32(0, 0)), + FORMAT_SkBitmap, input.width(), input.height(), + input.width() * bbp, discard_transparency, output); +} + +} // namespace gfx diff --git a/ui/gfx/codec/png_codec.h b/ui/gfx/codec/png_codec.h new file mode 100644 index 0000000..73453fe --- /dev/null +++ b/ui/gfx/codec/png_codec.h @@ -0,0 +1,105 @@ +// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_CODEC_PNG_CODEC_H_ +#define UI_GFX_CODEC_PNG_CODEC_H_ +#pragma once + +#include <vector> + +#include "base/basictypes.h" + +class SkBitmap; + +namespace gfx { + +// Interface for encoding and 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 PNGCodec { + 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, + + // 4 bytes per pixel, in pre-multiplied kARGB_8888_Config format. For use + // with directly writing to a skia bitmap. + FORMAT_SkBitmap + }; + + // 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); + + // Call PNGCodec::Encode on the supplied SkBitmap |input|, which is assumed + // to be BGRA, 32 bits per pixel. The params |discard_transparency| and + // |output| are passed directly to Encode; refer to Encode for more + // information. During the call, an SkAutoLockPixels lock is held on |input|. + static bool EncodeBGRASkBitmap(const SkBitmap& input, + bool discard_transparency, + std::vector<unsigned char>* output); + + // 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); + + // Decodes the PNG data directly into the passed in SkBitmap. This is + // significantly faster than the vector<unsigned char> version of Decode() + // above when dealing with PNG files that are >500K, which a lot of theme + // images are. (There are a lot of themes that have a NTP image of about ~1 + // megabyte, and those require a 7-10 megabyte side buffer.) + // + // Returns true if data is non-null and can be decoded as a png, false + // otherwise. + static bool Decode(const unsigned char* input, size_t input_size, + SkBitmap* bitmap); + + // 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_COPY_AND_ASSIGN(PNGCodec); +}; + +} // namespace gfx + +#endif // UI_GFX_CODEC_PNG_CODEC_H_ diff --git a/ui/gfx/codec/png_codec_unittest.cc b/ui/gfx/codec/png_codec_unittest.cc new file mode 100644 index 0000000..19d1f19 --- /dev/null +++ b/ui/gfx/codec/png_codec_unittest.cc @@ -0,0 +1,297 @@ +// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include <math.h> + +#include "gfx/codec/png_codec.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "third_party/skia/include/core/SkBitmap.h" +#include "third_party/skia/include/core/SkUnPreMultiply.h" + +namespace gfx { + +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) + } + } +} + +// 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; +} + +// Returns true if the RGB components are "close." +bool NonAlphaColorsClose(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; +} + +void MakeTestSkBitmap(int w, int h, SkBitmap* bmp) { + bmp->setConfig(SkBitmap::kARGB_8888_Config, w, h); + bmp->allocPixels(); + + uint32_t* src_data = bmp->getAddr32(0, 0); + for (int i = 0; i < w * h; i++) { + src_data[i] = SkPreMultiplyARGB(i % 255, i % 250, i % 245, i % 240); + } +} + +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(PNGCodec::Encode(&original[0], PNGCodec::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(PNGCodec::Decode(&encoded[0], encoded.size(), + PNGCodec::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(PNGCodec::Encode(&original[0], PNGCodec::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(PNGCodec::Decode(&encoded[0], encoded.size(), + PNGCodec::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(PNGCodec::Decode(&original[0], original.size(), + PNGCodec::FORMAT_RGB, &output, + &outw, &outh)); + + // Make some compressed data. + std::vector<unsigned char> compressed; + EXPECT_TRUE(PNGCodec::Encode(&original[0], PNGCodec::FORMAT_RGB, w, h, + w * 3, false, &compressed)); + + // Try decompressing a truncated version. + EXPECT_FALSE(PNGCodec::Decode(&compressed[0], compressed.size() / 2, + PNGCodec::FORMAT_RGB, &output, + &outw, &outh)); + + // Corrupt it and try decompressing that. + for (int i = 10; i < 30; i++) + compressed[i] = i; + EXPECT_FALSE(PNGCodec::Decode(&compressed[0], compressed.size(), + PNGCodec::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(PNGCodec::Encode(&original[0], PNGCodec::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(PNGCodec::Decode(&encoded[0], encoded.size(), + PNGCodec::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(PNGCodec::Encode(&original_rgba[0], + PNGCodec::FORMAT_RGBA, + w, h, + w * 4, true, &encoded)); + + // Decode the RGB to RGBA. + std::vector<unsigned char> decoded; + int outw, outh; + EXPECT_TRUE(PNGCodec::Decode(&encoded[0], encoded.size(), + PNGCodec::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(PNGCodec::Encode(&original_rgba[0], + PNGCodec::FORMAT_RGBA, + w, h, + w * 4, false, &encoded)); + + // Decode the RGBA to RGB. + EXPECT_TRUE(PNGCodec::Decode(&encoded[0], encoded.size(), + PNGCodec::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); +} + +TEST(PNGCodec, EncodeBGRASkBitmap) { + const int w = 20, h = 20; + + SkBitmap original_bitmap; + MakeTestSkBitmap(w, h, &original_bitmap); + + // Encode the bitmap. + std::vector<unsigned char> encoded; + PNGCodec::EncodeBGRASkBitmap(original_bitmap, false, &encoded); + + // Decode the encoded string. + SkBitmap decoded_bitmap; + EXPECT_TRUE(PNGCodec::Decode(&encoded.front(), encoded.size(), + &decoded_bitmap)); + + // Compare the original bitmap and the output bitmap. We use ColorsClose + // as SkBitmaps are considered to be pre-multiplied, the unpremultiplication + // (in Encode) and repremultiplication (in Decode) can be lossy. + for (int x = 0; x < w; x++) { + for (int y = 0; y < h; y++) { + uint32_t original_pixel = original_bitmap.getAddr32(0, y)[x]; + uint32_t decoded_pixel = decoded_bitmap.getAddr32(0, y)[x]; + EXPECT_TRUE(ColorsClose(original_pixel, decoded_pixel)); + } + } +} + +TEST(PNGCodec, EncodeBGRASkBitmapDiscardTransparency) { + const int w = 20, h = 20; + + SkBitmap original_bitmap; + MakeTestSkBitmap(w, h, &original_bitmap); + + // Encode the bitmap. + std::vector<unsigned char> encoded; + PNGCodec::EncodeBGRASkBitmap(original_bitmap, true, &encoded); + + // Decode the encoded string. + SkBitmap decoded_bitmap; + EXPECT_TRUE(PNGCodec::Decode(&encoded.front(), encoded.size(), + &decoded_bitmap)); + + // Compare the original bitmap and the output bitmap. We need to + // unpremultiply original_pixel, as the decoded bitmap doesn't have an alpha + // channel. + for (int x = 0; x < w; x++) { + for (int y = 0; y < h; y++) { + uint32_t original_pixel = original_bitmap.getAddr32(0, y)[x]; + uint32_t unpremultiplied = + SkUnPreMultiply::PMColorToColor(original_pixel); + uint32_t decoded_pixel = decoded_bitmap.getAddr32(0, y)[x]; + EXPECT_TRUE(NonAlphaColorsClose(unpremultiplied, decoded_pixel)) + << "Original_pixel: (" + << SkColorGetR(unpremultiplied) << ", " + << SkColorGetG(unpremultiplied) << ", " + << SkColorGetB(unpremultiplied) << "), " + << "Decoded pixel: (" + << SkColorGetR(decoded_pixel) << ", " + << SkColorGetG(decoded_pixel) << ", " + << SkColorGetB(decoded_pixel) << ")"; + } + } +} + +} // namespace gfx diff --git a/ui/gfx/color_utils.cc b/ui/gfx/color_utils.cc new file mode 100644 index 0000000..73c585b --- /dev/null +++ b/ui/gfx/color_utils.cc @@ -0,0 +1,300 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "gfx/color_utils.h" + +#include <math.h> +#if defined(OS_WIN) +#include <windows.h> +#endif + +#include <algorithm> + +#include "base/basictypes.h" +#include "base/logging.h" +#include "build/build_config.h" +#if defined(OS_WIN) +#include "skia/ext/skia_utils_win.h" +#endif +#include "third_party/skia/include/core/SkBitmap.h" + +namespace color_utils { + +// Helper functions ----------------------------------------------------------- + +namespace { + +double calcHue(double temp1, double temp2, double hue) { + if (hue < 0.0) + ++hue; + else if (hue > 1.0) + --hue; + + if (hue * 6.0 < 1.0) + return temp1 + (temp2 - temp1) * hue * 6.0; + if (hue * 2.0 < 1.0) + return temp2; + if (hue * 3.0 < 2.0) + return temp1 + (temp2 - temp1) * (2.0 / 3.0 - hue) * 6.0; + + return temp1; +} + +int GetLumaForColor(SkColor* color) { + int luma = static_cast<int>((0.3 * SkColorGetR(*color)) + + (0.59 * SkColorGetG(*color)) + + (0.11 * SkColorGetB(*color))); + return std::max(std::min(luma, 255), 0); +} + +// Next two functions' formulas from: +// http://www.w3.org/TR/WCAG20/#relativeluminancedef +// http://www.w3.org/TR/WCAG20/#contrast-ratiodef + +double ConvertSRGB(double eight_bit_component) { + const double component = eight_bit_component / 255.0; + return (component <= 0.03928) ? + (component / 12.92) : pow((component + 0.055) / 1.055, 2.4); +} + +SkColor LumaInvertColor(const SkColor& color) { + HSL hsl; + SkColorToHSL(color, &hsl); + hsl.l = 1.0 - hsl.l; + return HSLToSkColor(hsl, 255); +} + +double ContrastRatio(double foreground_luminance, double background_luminance) { + // NOTE: Only pass in numbers obtained from RelativeLuminance(), since those + // are guaranteed to be > 0 and thus not cause a divide-by-zero error here. + return (foreground_luminance > background_luminance) ? + (foreground_luminance / background_luminance) : + (background_luminance / foreground_luminance); +} + +} // namespace + +// ---------------------------------------------------------------------------- + +double RelativeLuminance(SkColor color) { + return (0.2126 * ConvertSRGB(SkColorGetR(color))) + + (0.7152 * ConvertSRGB(SkColorGetG(color))) + + (0.0722 * ConvertSRGB(SkColorGetB(color))) + 0.05; +} + +void SkColorToHSL(SkColor c, HSL* hsl) { + double r = static_cast<double>(SkColorGetR(c)) / 255.0; + double g = static_cast<double>(SkColorGetG(c)) / 255.0; + double b = static_cast<double>(SkColorGetB(c)) / 255.0; + double vmax = std::max(std::max(r, g), b); + double vmin = std::min(std::min(r, g), b); + double delta = vmax - vmin; + hsl->l = (vmax + vmin) / 2; + if (delta) { + double dr = (((vmax - r) / 6.0) + (delta / 2.0)) / delta; + double dg = (((vmax - g) / 6.0) + (delta / 2.0)) / delta; + double db = (((vmax - b) / 6.0) + (delta / 2.0)) / delta; + // We need to compare for the max value because comparing vmax to r, + // g or b can sometimes result in values overflowing registers. + if (r >= g && r >= b) + hsl->h = db - dg; + else if (g >= r && g >= b) + hsl->h = (1.0 / 3.0) + dr - db; + else // (b >= r && b >= g) + hsl->h = (2.0 / 3.0) + dg - dr; + + if (hsl->h < 0.0) + ++hsl->h; + else if (hsl->h > 1.0) + --hsl->h; + + hsl->s = delta / ((hsl->l < 0.5) ? (vmax + vmin) : (2 - vmax - vmin)); + } else { + hsl->h = hsl->s = 0; + } +} + +SkColor HSLToSkColor(const HSL& hsl, SkAlpha alpha) { + double hue = hsl.h; + double saturation = hsl.s; + double lightness = hsl.l; + + // If there's no color, we don't care about hue and can do everything based + // on brightness. + if (!saturation) { + uint8 light; + + if (lightness < 0) + light = 0; + else if (lightness >= 1.0) + light = 255; + else + light = SkDoubleToFixed(lightness) >> 8; + + return SkColorSetARGB(alpha, light, light, light); + } + + double temp2 = (lightness < 0.5) ? + (lightness * (1.0 + saturation)) : + (lightness + saturation - (lightness * saturation)); + double temp1 = 2.0 * lightness - temp2; + return SkColorSetARGB(alpha, + static_cast<int>(calcHue(temp1, temp2, hue + 1.0 / 3.0) * 255), + static_cast<int>(calcHue(temp1, temp2, hue) * 255), + static_cast<int>(calcHue(temp1, temp2, hue - 1.0 / 3.0) * 255)); +} + +SkColor HSLShift(SkColor color, const HSL& shift) { + HSL hsl; + int alpha = SkColorGetA(color); + SkColorToHSL(color, &hsl); + + // Replace the hue with the tint's hue. + if (shift.h >= 0) + hsl.h = shift.h; + + // Change the saturation. + if (shift.s >= 0) { + if (shift.s <= 0.5) + hsl.s *= shift.s * 2.0; + else + hsl.s += (1.0 - hsl.s) * ((shift.s - 0.5) * 2.0); + } + + SkColor result = HSLToSkColor(hsl, alpha); + + if (shift.l < 0) + return result; + + // Lightness shifts in the style of popular image editors aren't + // actually represented in HSL - the L value does have some effect + // on saturation. + double r = static_cast<double>(SkColorGetR(result)); + double g = static_cast<double>(SkColorGetG(result)); + double b = static_cast<double>(SkColorGetB(result)); + if (shift.l <= 0.5) { + r *= (shift.l * 2.0); + g *= (shift.l * 2.0); + b *= (shift.l * 2.0); + } else { + r += (255.0 - r) * ((shift.l - 0.5) * 2.0); + g += (255.0 - g) * ((shift.l - 0.5) * 2.0); + b += (255.0 - b) * ((shift.l - 0.5) * 2.0); + } + return SkColorSetARGB(alpha, + static_cast<int>(r), + static_cast<int>(g), + static_cast<int>(b)); +} + +bool IsColorCloseToTransparent(SkAlpha alpha) { + const int kCloseToBoundary = 64; + return alpha < kCloseToBoundary; +} + +bool IsColorCloseToGrey(int r, int g, int b) { + const int kAverageBoundary = 15; + int average = (r + g + b) / 3; + return (abs(r - average) < kAverageBoundary) && + (abs(g - average) < kAverageBoundary) && + (abs(b - average) < kAverageBoundary); +} + +SkColor GetAverageColorOfFavicon(SkBitmap* favicon, SkAlpha alpha) { + int r = 0, g = 0, b = 0; + + SkAutoLockPixels favicon_lock(*favicon); + SkColor* pixels = static_cast<SkColor*>(favicon->getPixels()); + // Assume ARGB_8888 format. + DCHECK(favicon->getConfig() == SkBitmap::kARGB_8888_Config); + SkColor* current_color = pixels; + + DCHECK(favicon->width() <= 16 && favicon->height() <= 16); + + int pixel_count = favicon->width() * favicon->height(); + int color_count = 0; + for (int i = 0; i < pixel_count; ++i, ++current_color) { + // Disregard this color if it is close to black, close to white, or close + // to transparent since any of those pixels do not contribute much to the + // color makeup of this icon. + int cr = SkColorGetR(*current_color); + int cg = SkColorGetG(*current_color); + int cb = SkColorGetB(*current_color); + + if (IsColorCloseToTransparent(SkColorGetA(*current_color)) || + IsColorCloseToGrey(cr, cg, cb)) + continue; + + r += cr; + g += cg; + b += cb; + ++color_count; + } + + return color_count ? + SkColorSetARGB(alpha, r / color_count, g / color_count, b / color_count) : + SkColorSetARGB(alpha, 0, 0, 0); +} + +void BuildLumaHistogram(SkBitmap* bitmap, int histogram[256]) { + SkAutoLockPixels bitmap_lock(*bitmap); + // Assume ARGB_8888 format. + DCHECK(bitmap->getConfig() == SkBitmap::kARGB_8888_Config); + + int pixel_width = bitmap->width(); + int pixel_height = bitmap->height(); + for (int y = 0; y < pixel_height; ++y) { + SkColor* current_color = static_cast<uint32_t*>(bitmap->getAddr32(0, y)); + for (int x = 0; x < pixel_width; ++x, ++current_color) + histogram[GetLumaForColor(current_color)]++; + } +} + +SkColor AlphaBlend(SkColor foreground, SkColor background, SkAlpha alpha) { + if (alpha == 0) + return background; + if (alpha == 255) + return foreground; + + int f_alpha = SkColorGetA(foreground); + int b_alpha = SkColorGetA(background); + + double normalizer = (f_alpha * alpha + b_alpha * (255 - alpha)) / 255.0; + if (normalizer == 0.0) + return SkColorSetARGB(0, 0, 0, 0); + + double f_weight = f_alpha * alpha / normalizer; + double b_weight = b_alpha * (255 - alpha) / normalizer; + + double r = (SkColorGetR(foreground) * f_weight + + SkColorGetR(background) * b_weight) / 255.0; + double g = (SkColorGetG(foreground) * f_weight + + SkColorGetG(background) * b_weight) / 255.0; + double b = (SkColorGetB(foreground) * f_weight + + SkColorGetB(background) * b_weight) / 255.0; + + return SkColorSetARGB(static_cast<int>(normalizer), + static_cast<int>(r), + static_cast<int>(g), + static_cast<int>(b)); +} + +SkColor GetReadableColor(SkColor foreground, SkColor background) { + const SkColor foreground2 = LumaInvertColor(foreground); + const double background_luminance = RelativeLuminance(background); + return (ContrastRatio(RelativeLuminance(foreground), background_luminance) >= + ContrastRatio(RelativeLuminance(foreground2), background_luminance)) ? + foreground : foreground2; +} + +SkColor GetSysSkColor(int which) { +#if defined(OS_WIN) + return skia::COLORREFToSkColor(GetSysColor(which)); +#else + NOTIMPLEMENTED(); + return SK_ColorLTGRAY; +#endif +} + +} // namespace color_utils diff --git a/ui/gfx/color_utils.h b/ui/gfx/color_utils.h new file mode 100644 index 0000000..a043b82 --- /dev/null +++ b/ui/gfx/color_utils.h @@ -0,0 +1,80 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_COLOR_UTILS_H_ +#define UI_GFX_COLOR_UTILS_H_ +#pragma once + +#include "third_party/skia/include/core/SkColor.h" + +class SkBitmap; + +namespace color_utils { + +// Represents an HSL color. +struct HSL { + double h; + double s; + double l; +}; + +// Calculated according to http://www.w3.org/TR/WCAG20/#relativeluminancedef +double RelativeLuminance(SkColor color); + +// Note: these transformations assume sRGB as the source color space +void SkColorToHSL(SkColor c, HSL* hsl); +SkColor HSLToSkColor(const HSL& hsl, SkAlpha alpha); + +// HSL-Shift an SkColor. The shift values are in the range of 0-1, with the +// option to specify -1 for 'no change'. The shift values are defined as: +// hsl_shift[0] (hue): The absolute hue value - 0 and 1 map +// to 0 and 360 on the hue color wheel (red). +// hsl_shift[1] (saturation): A saturation shift, with the +// following key values: +// 0 = remove all color. +// 0.5 = leave unchanged. +// 1 = fully saturate the image. +// hsl_shift[2] (lightness): A lightness shift, with the +// following key values: +// 0 = remove all lightness (make all pixels black). +// 0.5 = leave unchanged. +// 1 = full lightness (make all pixels white). +SkColor HSLShift(SkColor color, const HSL& shift); + +// Determine if a given alpha value is nearly completely transparent. +bool IsColorCloseToTransparent(SkAlpha alpha); + +// Determine if a color is near grey. +bool IsColorCloseToGrey(int r, int g, int b); + +// Gets a color representing a bitmap. The definition of "representing" is the +// average color in the bitmap. The color returned is modified to have the +// specified alpha. +SkColor GetAverageColorOfFavicon(SkBitmap* bitmap, SkAlpha alpha); + +// Builds a histogram based on the Y' of the Y'UV representation of +// this image. +void BuildLumaHistogram(SkBitmap* bitmap, int histogram[256]); + +// Returns a blend of the supplied colors, ranging from |background| (for +// |alpha| == 0) to |foreground| (for |alpha| == 255). The alpha channels of +// the supplied colors are also taken into account, so the returned color may +// be partially transparent. +SkColor AlphaBlend(SkColor foreground, SkColor background, SkAlpha alpha); + +// Given a foreground and background color, try to return a foreground color +// that is "readable" over the background color by luma-inverting the foreground +// color and then picking whichever foreground color has higher contrast against +// the background color. +// +// NOTE: This won't do anything but waste time if the supplied foreground color +// has a luma value close to the midpoint (0.5 in the HSL representation). +SkColor GetReadableColor(SkColor foreground, SkColor background); + +// Gets a Windows system color as a SkColor +SkColor GetSysSkColor(int which); + +} // namespace color_utils + +#endif // UI_GFX_COLOR_UTILS_H_ diff --git a/ui/gfx/color_utils_unittest.cc b/ui/gfx/color_utils_unittest.cc new file mode 100644 index 0000000..30cf514 --- /dev/null +++ b/ui/gfx/color_utils_unittest.cc @@ -0,0 +1,67 @@ +// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include <stdlib.h> + +#include "gfx/color_utils.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "third_party/skia/include/core/SkBitmap.h" +#include "third_party/skia/include/core/SkColorPriv.h" + +TEST(ColorUtils, SkColorToHSLRed) { + color_utils::HSL hsl = { 0, 0, 0 }; + color_utils::SkColorToHSL(SK_ColorRED, &hsl); + EXPECT_EQ(hsl.h, 0); + EXPECT_EQ(hsl.s, 1); + EXPECT_EQ(hsl.l, 0.5); +} + +TEST(ColorUtils, SkColorToHSLGrey) { + color_utils::HSL hsl = { 0, 0, 0 }; + color_utils::SkColorToHSL(SkColorSetARGB(255, 128, 128, 128), &hsl); + EXPECT_EQ(hsl.h, 0); + EXPECT_EQ(hsl.s, 0); + EXPECT_EQ(static_cast<int>(hsl.l * 100), + static_cast<int>(0.5 * 100)); // Accurate to two decimal places. +} + +TEST(ColorUtils, HSLToSkColorWithAlpha) { + SkColor red = SkColorSetARGB(128, 255, 0, 0); + color_utils::HSL hsl = { 0, 1, 0.5 }; + SkColor result = color_utils::HSLToSkColor(hsl, 128); + EXPECT_EQ(SkColorGetA(red), SkColorGetA(result)); + EXPECT_EQ(SkColorGetR(red), SkColorGetR(result)); + EXPECT_EQ(SkColorGetG(red), SkColorGetG(result)); + EXPECT_EQ(SkColorGetB(red), SkColorGetB(result)); +} + +TEST(ColorUtils, ColorToHSLRegisterSpill) { + // In a opt build on Linux, this was causing a register spill on my laptop + // (Pentium M) when converting from SkColor to HSL. + SkColor input = SkColorSetARGB(255, 206, 154, 89); + color_utils::HSL hsl = { -1, -1, -1 }; + SkColor result = color_utils::HSLShift(input, hsl); + EXPECT_EQ(255U, SkColorGetA(result)); + EXPECT_EQ(206U, SkColorGetR(result)); + EXPECT_EQ(153U, SkColorGetG(result)); + EXPECT_EQ(88U, SkColorGetB(result)); +} + +TEST(ColorUtils, AlphaBlend) { + SkColor fore = SkColorSetARGB(255, 200, 200, 200); + SkColor back = SkColorSetARGB(255, 100, 100, 100); + + EXPECT_TRUE(color_utils::AlphaBlend(fore, back, 255) == + fore); + EXPECT_TRUE(color_utils::AlphaBlend(fore, back, 0) == + back); + + // One is fully transparent, result is partially transparent. + back = SkColorSetA(back, 0); + EXPECT_EQ(136U, SkColorGetA(color_utils::AlphaBlend(fore, back, 136))); + + // Both are fully transparent, result is fully transparent. + fore = SkColorSetA(fore, 0); + EXPECT_EQ(0U, SkColorGetA(color_utils::AlphaBlend(fore, back, 255))); +} diff --git a/ui/gfx/empty.cc b/ui/gfx/empty.cc new file mode 100644 index 0000000..63672f6 --- /dev/null +++ b/ui/gfx/empty.cc @@ -0,0 +1,6 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +void MacSux() { +} diff --git a/ui/gfx/favicon_size.h b/ui/gfx/favicon_size.h new file mode 100644 index 0000000..5e9cd55 --- /dev/null +++ b/ui/gfx/favicon_size.h @@ -0,0 +1,34 @@ +// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_FAVICON_SIZE_H_ +#define UI_GFX_FAVICON_SIZE_H_ +#pragma once + +#include "base/compiler_specific.h" + +// Size (along each axis) of the favicon. +const int kFavIconSize = 16; + +// If the width or height is bigger than the favicon size, a new width/height +// is calculated and returned in width/height that maintains the aspect +// ratio of the supplied values. +static void calc_favicon_target_size(int* width, int* height) ALLOW_UNUSED; + +// static +void calc_favicon_target_size(int* width, int* height) { + if (*width > kFavIconSize || *height > kFavIconSize) { + // Too big, resize it maintaining the aspect ratio. + float aspect_ratio = static_cast<float>(*width) / + static_cast<float>(*height); + *height = kFavIconSize; + *width = static_cast<int>(aspect_ratio * *height); + if (*width > kFavIconSize) { + *width = kFavIconSize; + *height = static_cast<int>(*width / aspect_ratio); + } + } +} + +#endif // UI_GFX_FAVICON_SIZE_H_ diff --git a/ui/gfx/font.cc b/ui/gfx/font.cc new file mode 100644 index 0000000..78b1a34 --- /dev/null +++ b/ui/gfx/font.cc @@ -0,0 +1,85 @@ +// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "gfx/font.h" + +#include "base/utf_string_conversions.h" +#include "gfx/platform_font.h" + +namespace gfx { + +//////////////////////////////////////////////////////////////////////////////// +// Font, public: + +Font::Font() : platform_font_(PlatformFont::CreateDefault()) { +} + +Font::Font(const Font& other) : platform_font_(other.platform_font_) { +} + +gfx::Font& Font::operator=(const Font& other) { + platform_font_ = other.platform_font_; + return *this; +} + +Font::Font(NativeFont native_font) + : platform_font_(PlatformFont::CreateFromNativeFont(native_font)) { +} + +Font::Font(PlatformFont* platform_font) : platform_font_(platform_font) { +} + +Font::Font(const string16& font_name, int font_size) + : platform_font_(PlatformFont::CreateFromNameAndSize(font_name, + font_size)) { +} + +Font::~Font() { +} + +Font Font::DeriveFont(int size_delta) const { + return DeriveFont(size_delta, GetStyle()); +} + +Font Font::DeriveFont(int size_delta, int style) const { + return platform_font_->DeriveFont(size_delta, style); +} + +int Font::GetHeight() const { + return platform_font_->GetHeight(); +} + +int Font::GetBaseline() const { + return platform_font_->GetBaseline(); +} + +int Font::GetAverageCharacterWidth() const { + return platform_font_->GetAverageCharacterWidth(); +} + +int Font::GetStringWidth(const string16& text) const { + return platform_font_->GetStringWidth(text); +} + +int Font::GetExpectedTextWidth(int length) const { + return platform_font_->GetExpectedTextWidth(length); +} + +int Font::GetStyle() const { + return platform_font_->GetStyle(); +} + +string16 Font::GetFontName() const { + return platform_font_->GetFontName(); +} + +int Font::GetFontSize() const { + return platform_font_->GetFontSize(); +} + +NativeFont Font::GetNativeFont() const { + return platform_font_->GetNativeFont(); +} + +} // namespace gfx diff --git a/ui/gfx/font.h b/ui/gfx/font.h new file mode 100644 index 0000000..2091ae3 --- /dev/null +++ b/ui/gfx/font.h @@ -0,0 +1,113 @@ +// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_FONT_H_ +#define UI_GFX_FONT_H_ +#pragma once + +#include <string> + +#include "base/ref_counted.h" +#include "base/string16.h" +#include "gfx/native_widget_types.h" + +namespace gfx { + +class PlatformFont; + +// Font provides a wrapper around an underlying font. Copy and assignment +// operators are explicitly allowed, and cheap. +class Font { + public: + // The following constants indicate the font style. + enum FontStyle { + NORMAL = 0, + BOLD = 1, + ITALIC = 2, + UNDERLINED = 4, + }; + + // Creates a font with the default name and style. + Font(); + + // Creates a font that is a clone of another font object. + Font(const Font& other); + gfx::Font& operator=(const Font& other); + + // Creates a font from the specified native font. + explicit Font(NativeFont native_font); + + // Construct a Font object with the specified PlatformFont object. The Font + // object takes ownership of the PlatformFont object. + explicit Font(PlatformFont* platform_font); + + // Creates a font with the specified name and size. + Font(const string16& font_name, int font_size); + + ~Font(); + + // Returns a new Font derived from the existing font. + // size_deta is the size to add to the current font. For example, a value + // of 5 results in a font 5 units bigger than this font. + Font DeriveFont(int size_delta) const; + + // Returns a new Font derived from the existing font. + // size_delta is the size to add to the current font. See the single + // argument version of this method for an example. + // The style parameter specifies the new style for the font, and is a + // bitmask of the values: BOLD, ITALIC and UNDERLINED. + Font DeriveFont(int size_delta, int style) const; + + // Returns the number of vertical pixels needed to display characters from + // the specified font. This may include some leading, i.e. height may be + // greater than just ascent + descent. Specifically, the Windows and Mac + // implementations include leading and the Linux one does not. This may + // need to be revisited in the future. + int GetHeight() const; + + // Returns the baseline, or ascent, of the font. + int GetBaseline() const; + + // Returns the average character width for the font. + int GetAverageCharacterWidth() const; + + // Returns the number of horizontal pixels needed to display the specified + // string. + int GetStringWidth(const string16& text) const; + + // Returns the expected number of horizontal pixels needed to display the + // specified length of characters. Call GetStringWidth() to retrieve the + // actual number. + int GetExpectedTextWidth(int length) const; + + // Returns the style of the font. + int GetStyle() const; + + // Returns the font name. + string16 GetFontName() const; + + // Returns the font size in pixels. + int GetFontSize() const; + + // Returns the native font handle. + // Lifetime lore: + // Windows: This handle is owned by the Font object, and should not be + // destroyed by the caller. + // Mac: Caller must release this object. + // Gtk: This handle is created on demand, and must be freed by calling + // pango_font_description_free() when the caller is done using it. + NativeFont GetNativeFont() const; + + // Raw access to the underlying platform font implementation. Can be + // static_cast to a known implementation type if needed. + PlatformFont* platform_font() const { return platform_font_.get(); } + + private: + // Wrapped platform font implementation. + scoped_refptr<PlatformFont> platform_font_; +}; + +} // namespace gfx + +#endif // UI_GFX_FONT_H_ diff --git a/ui/gfx/font_unittest.cc b/ui/gfx/font_unittest.cc new file mode 100644 index 0000000..43eaa7c --- /dev/null +++ b/ui/gfx/font_unittest.cc @@ -0,0 +1,120 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "gfx/font.h" + +#include "base/utf_string_conversions.h" +#if defined(OS_WIN) +#include "gfx/platform_font_win.h" +#endif // defined(OS_WIN) +#include "testing/gtest/include/gtest/gtest.h" + +namespace { + +using gfx::Font; + +class FontTest : public testing::Test { +}; + +#if defined(OS_WIN) +class ScopedMinimumFontSizeCallback { + public: + explicit ScopedMinimumFontSizeCallback(int minimum_size) { + minimum_size_ = minimum_size; + old_callback_ = gfx::PlatformFontWin::get_minimum_font_size_callback; + gfx::PlatformFontWin::get_minimum_font_size_callback = &GetMinimumFontSize; + } + + ~ScopedMinimumFontSizeCallback() { + gfx::PlatformFontWin::get_minimum_font_size_callback = old_callback_; + } + + private: + static int GetMinimumFontSize() { + return minimum_size_; + } + + gfx::PlatformFontWin::GetMinimumFontSizeCallback old_callback_; + static int minimum_size_; + + DISALLOW_COPY_AND_ASSIGN(ScopedMinimumFontSizeCallback); +}; + +int ScopedMinimumFontSizeCallback::minimum_size_ = 0; +#endif // defined(OS_WIN) + + +TEST_F(FontTest, LoadArial) { + Font cf(ASCIIToUTF16("Arial"), 16); + ASSERT_TRUE(cf.GetNativeFont()); + ASSERT_EQ(cf.GetStyle(), Font::NORMAL); + ASSERT_EQ(cf.GetFontSize(), 16); + ASSERT_EQ(cf.GetFontName(), ASCIIToUTF16("Arial")); +} + +TEST_F(FontTest, LoadArialBold) { + Font cf(ASCIIToUTF16("Arial"), 16); + Font bold(cf.DeriveFont(0, Font::BOLD)); + ASSERT_TRUE(bold.GetNativeFont()); + ASSERT_EQ(bold.GetStyle(), Font::BOLD); +} + +TEST_F(FontTest, Ascent) { + Font cf(ASCIIToUTF16("Arial"), 16); + ASSERT_GT(cf.GetBaseline(), 2); + ASSERT_LE(cf.GetBaseline(), 22); +} + +TEST_F(FontTest, Height) { + Font cf(ASCIIToUTF16("Arial"), 16); + ASSERT_GE(cf.GetHeight(), 16); + // TODO(akalin): Figure out why height is so large on Linux. + ASSERT_LE(cf.GetHeight(), 26); +} + +TEST_F(FontTest, AvgWidths) { + Font cf(ASCIIToUTF16("Arial"), 16); + ASSERT_EQ(cf.GetExpectedTextWidth(0), 0); + ASSERT_GT(cf.GetExpectedTextWidth(1), cf.GetExpectedTextWidth(0)); + ASSERT_GT(cf.GetExpectedTextWidth(2), cf.GetExpectedTextWidth(1)); + ASSERT_GT(cf.GetExpectedTextWidth(3), cf.GetExpectedTextWidth(2)); +} + +TEST_F(FontTest, Widths) { + Font cf(ASCIIToUTF16("Arial"), 16); + ASSERT_EQ(cf.GetStringWidth(ASCIIToUTF16("")), 0); + ASSERT_GT(cf.GetStringWidth(ASCIIToUTF16("a")), + cf.GetStringWidth(ASCIIToUTF16(""))); + ASSERT_GT(cf.GetStringWidth(ASCIIToUTF16("ab")), + cf.GetStringWidth(ASCIIToUTF16("a"))); + ASSERT_GT(cf.GetStringWidth(ASCIIToUTF16("abc")), + cf.GetStringWidth(ASCIIToUTF16("ab"))); +} + +#if defined(OS_WIN) +TEST_F(FontTest, DeriveFontResizesIfSizeTooSmall) { + // This creates font of height -8. + Font cf(L"Arial", 6); + // The minimum font size is set to 5 in browser_main.cc. + ScopedMinimumFontSizeCallback minimum_size(5); + + Font derived_font = cf.DeriveFont(-4); + LOGFONT font_info; + GetObject(derived_font.GetNativeFont(), sizeof(LOGFONT), &font_info); + EXPECT_EQ(-5, font_info.lfHeight); +} + +TEST_F(FontTest, DeriveFontKeepsOriginalSizeIfHeightOk) { + // This creates font of height -8. + Font cf(L"Arial", 6); + // The minimum font size is set to 5 in browser_main.cc. + ScopedMinimumFontSizeCallback minimum_size(5); + + Font derived_font = cf.DeriveFont(-2); + LOGFONT font_info; + GetObject(derived_font.GetNativeFont(), sizeof(LOGFONT), &font_info); + EXPECT_EQ(-6, font_info.lfHeight); +} +#endif +} // namespace diff --git a/ui/gfx/gdi_util.cc b/ui/gfx/gdi_util.cc new file mode 100644 index 0000000..5dbb5b5 --- /dev/null +++ b/ui/gfx/gdi_util.cc @@ -0,0 +1,79 @@ +// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "gfx/gdi_util.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; +} + +void SubtractRectanglesFromRegion(HRGN hrgn, + const std::vector<gfx::Rect>& cutouts) { + if (cutouts.size()) { + HRGN cutout = ::CreateRectRgn(0, 0, 0, 0); + for (size_t i = 0; i < cutouts.size(); i++) { + ::SetRectRgn(cutout, + cutouts[i].x(), + cutouts[i].y(), + cutouts[i].right(), + cutouts[i].bottom()); + ::CombineRgn(hrgn, hrgn, cutout, RGN_DIFF); + } + ::DeleteObject(cutout); + } +} + +} // namespace gfx diff --git a/ui/gfx/gdi_util.h b/ui/gfx/gdi_util.h new file mode 100644 index 0000000..e79a34f --- /dev/null +++ b/ui/gfx/gdi_util.h @@ -0,0 +1,38 @@ +// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_GDI_UTIL_H_ +#define UI_GFX_GDI_UTIL_H_ +#pragma once + +#include <vector> +#include <windows.h> + +#include "gfx/rect.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); + +// Modify the given hrgn by subtracting the given rectangles. +void SubtractRectanglesFromRegion(HRGN hrgn, + const std::vector<gfx::Rect>& cutouts); + +} // namespace gfx + +#endif // UI_GFX_GDI_UTIL_H_ diff --git a/ui/gfx/gfx.gyp b/ui/gfx/gfx.gyp new file mode 100644 index 0000000..04df70d --- /dev/null +++ b/ui/gfx/gfx.gyp @@ -0,0 +1,231 @@ +# Copyright (c) 2011 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +{ + 'variables': { + 'chromium_code': 1, + 'grit_info_cmd': ['python', '../../tools/grit/grit_info.py', + '<@(grit_defines)'], + 'grit_cmd': ['python', '../../tools/grit/grit.py'], + 'grit_out_dir': '<(SHARED_INTERMEDIATE_DIR)/ui/gfx', + }, + 'targets': [ + { + 'target_name': 'gfx_unittests', + 'type': 'executable', + 'msvs_guid': 'C8BD2821-EAE5-4AC6-A0E4-F82CAC2956CC', + 'dependencies': [ + 'gfx', + 'gfx_resources', + '../../base/base.gyp:test_support_base', + '../../skia/skia.gyp:skia', + '../../testing/gtest.gyp:gtest', + ], + 'sources': [ + 'blit_unittest.cc', + 'codec/jpeg_codec_unittest.cc', + 'codec/png_codec_unittest.cc', + 'color_utils_unittest.cc', + 'font_unittest.cc', + 'insets_unittest.cc', + 'rect_unittest.cc', + 'run_all_unittests.cc', + 'scoped_image_unittest.cc', + 'skbitmap_operations_unittest.cc', + 'test_suite.cc', + 'test_suite.h', + '<(SHARED_INTERMEDIATE_DIR)/ui/gfx/gfx_resources.rc', + ], + 'include_dirs': [ + '../..', + ], + 'conditions': [ + ['OS=="win"', { + 'sources': [ + # TODO(brettw) re-enable this when the dependencies on WindowImpl are fixed! + 'canvas_direct2d_unittest.cc', + 'icon_util_unittest.cc', + 'native_theme_win_unittest.cc', + ], + 'include_dirs': [ + '../..', + '<(DEPTH)/third_party/wtl/include', + ], + 'msvs_settings': { + 'VCLinkerTool': { + 'DelayLoadDLLs': [ + 'd2d1.dll', + 'd3d10_1.dll', + ], + 'AdditionalDependencies': [ + 'd2d1.lib', + 'd3d10_1.lib', + ], + }, + } + }], + ['OS=="linux" or OS=="freebsd" or OS=="openbsd"', { + 'dependencies': [ + '../../build/linux/system.gyp:gtk', + ], + }], + ], + }, + { + 'target_name': 'gfx', + 'type': '<(library)', + 'msvs_guid': '13A8D36C-0467-4B4E-BAA3-FD69C45F076A', + 'dependencies': [ + '../../base/base.gyp:base', + '../../base/base.gyp:base_i18n', + '../../skia/skia.gyp:skia', + '../../third_party/icu/icu.gyp:icui18n', + '../../third_party/icu/icu.gyp:icuuc', + '../../third_party/libpng/libpng.gyp:libpng', + '../../third_party/zlib/zlib.gyp:zlib', + 'gfx_resources', + '<(libjpeg_gyp_path):libjpeg', + ], + 'sources': [ + 'blit.cc', + 'blit.h', + 'brush.h', + 'canvas.cc', + 'canvas.h', + 'canvas_skia.h', + 'canvas_skia.cc', + 'canvas_skia_linux.cc', + 'canvas_skia_mac.mm', + 'canvas_skia_paint.h', + 'canvas_skia_win.cc', + 'codec/jpeg_codec.cc', + 'codec/jpeg_codec.h', + 'codec/png_codec.cc', + 'codec/png_codec.h', + 'color_utils.cc', + 'color_utils.h', + 'favicon_size.h', + 'font.h', + 'font.cc', + 'gfx_paths.cc', + 'gfx_paths.h', + 'gfx_module.cc', + 'gfx_module.h', + 'insets.cc', + 'insets.h', + 'native_widget_types.h', + 'path.cc', + 'path.h', + 'path_gtk.cc', + 'path_win.cc', + 'platform_font.h', + 'platform_font_gtk.h', + 'platform_font_gtk.cc', + 'platform_font_mac.h', + 'platform_font_mac.mm', + 'platform_font_win.h', + 'platform_font_win.cc', + 'point.cc', + 'point.h', + 'rect.cc', + 'rect.h', + 'scoped_cg_context_state_mac.h', + 'scoped_image.h', + 'scrollbar_size.cc', + 'scrollbar_size.h', + 'size.cc', + 'size.h', + 'skbitmap_operations.cc', + 'skbitmap_operations.h', + 'skia_util.cc', + 'skia_util.h', + 'skia_utils_gtk.cc', + 'skia_utils_gtk.h', + ], + 'conditions': [ + ['OS=="win"', { + 'sources': [ + 'canvas_direct2d.cc', + 'canvas_direct2d.h', + 'gdi_util.cc', + 'gdi_util.h', + 'icon_util.cc', + 'icon_util.h', + 'native_theme_win.cc', + 'native_theme_win.h', + 'win_util.cc', + 'win_util.h', + ], + 'include_dirs': [ + '../..', + '<(DEPTH)/third_party/wtl/include', + ], + }], + ['OS=="linux" or OS=="freebsd" or OS=="openbsd"', { + 'dependencies': [ + # font_gtk.cc uses fontconfig. + # TODO(evanm): I think this is wrong; it should just use GTK. + '../../build/linux/system.gyp:fontconfig', + '../../build/linux/system.gyp:gtk', + ], + 'sources': [ + 'gtk_native_view_id_manager.cc', + 'gtk_native_view_id_manager.h', + 'gtk_preserve_window.cc', + 'gtk_preserve_window.h', + 'gtk_util.cc', + 'gtk_util.h', + 'native_theme_linux.cc', + 'native_theme_linux.h', + 'native_widget_types_gtk.cc', + ], + }], + ], + }, + { + # theme_resources also generates a .cc file, so it can't use the rules above. + 'target_name': 'gfx_resources', + 'type': 'none', + 'msvs_guid' : '5738AE53-E919-4987-A2EF-15FDBD8F90F6', + 'actions': [ + { + 'action_name': 'gfx_resources', + 'variables': { + 'input_path': 'gfx_resources.grd', + }, + 'inputs': [ + '<!@(<(grit_info_cmd) --inputs <(input_path))', + ], + 'outputs': [ + '<!@(<(grit_info_cmd) --outputs \'<(grit_out_dir)\' <(input_path))', + ], + 'action': [ + '<@(grit_cmd)', + '-i', '<(input_path)', 'build', + '-o', '<(grit_out_dir)', + '<@(grit_defines)', + ], + 'message': 'Generating resources from <(input_path)', + }, + ], + 'direct_dependent_settings': { + 'include_dirs': [ + '<(grit_out_dir)', + ], + }, + 'conditions': [ + ['OS=="win"', { + 'dependencies': ['../../build/win/system.gyp:cygwin'], + }], + ], + }, + + ], +} + +# Local Variables: +# tab-width:2 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=2 shiftwidth=2: diff --git a/ui/gfx/gfx_module.cc b/ui/gfx/gfx_module.cc new file mode 100644 index 0000000..882efad --- /dev/null +++ b/ui/gfx/gfx_module.cc @@ -0,0 +1,21 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "gfx/gfx_module.h" + +namespace gfx { + +static GfxModule::ResourceProvider resource_provider = NULL; + +// static +void GfxModule::SetResourceProvider(ResourceProvider func) { + resource_provider = func; +} + +// static +base::StringPiece GfxModule::GetResource(int key) { + return resource_provider ? resource_provider(key) : base::StringPiece(); +} + +} // namespace gfx diff --git a/ui/gfx/gfx_module.h b/ui/gfx/gfx_module.h new file mode 100644 index 0000000..0070eac --- /dev/null +++ b/ui/gfx/gfx_module.h @@ -0,0 +1,34 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_MODULE_H_ +#define UI_GFX_MODULE_H_ +#pragma once + +#include "base/basictypes.h" +#include "base/string_piece.h" + +namespace gfx { + +// Defines global initializers and associated methods for the gfx module. +// See net/base/net_module.h for more details. +class GfxModule { + public: + typedef base::StringPiece (*ResourceProvider)(int key); + + // Set the function to call when the gfx module needs resources + static void SetResourceProvider(ResourceProvider func); + + // Call the resource provider (if one exists) to get the specified resource. + // Returns an empty string if the resource does not exist or if there is no + // resource provider. + static base::StringPiece GetResource(int key); + + private: + DISALLOW_IMPLICIT_CONSTRUCTORS(GfxModule); +}; + +} // namespace gfx + +#endif // UI_GFX_MODULE_H_ diff --git a/ui/gfx/gfx_paths.cc b/ui/gfx/gfx_paths.cc new file mode 100644 index 0000000..bcb82ab --- /dev/null +++ b/ui/gfx/gfx_paths.cc @@ -0,0 +1,51 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "gfx/gfx_paths.h" + +#include "base/command_line.h" +#include "base/file_path.h" +#include "base/file_util.h" +#include "base/path_service.h" + +namespace gfx { + +bool PathProvider(int key, FilePath* result) { + // Assume that we will not need to create the directory if it does not exist. + // This flag can be set to true for the cases where we want to create it. + bool create_dir = false; + + FilePath cur; + switch (key) { + // The following are only valid in the development environment, and + // will fail if executed from an installed executable (because the + // generated path won't exist). + case DIR_TEST_DATA: + if (!PathService::Get(base::DIR_SOURCE_ROOT, &cur)) + return false; + cur = cur.Append(FILE_PATH_LITERAL("gfx")); + cur = cur.Append(FILE_PATH_LITERAL("test")); + cur = cur.Append(FILE_PATH_LITERAL("data")); + if (!file_util::PathExists(cur)) // we don't want to create this + return false; + break; + default: + return false; + } + + if (create_dir && !file_util::PathExists(cur) && + !file_util::CreateDirectory(cur)) + return false; + + *result = cur; + return true; +} + +// This cannot be done as a static initializer sadly since Visual Studio will +// eliminate this object file if there is no direct entry point into it. +void RegisterPathProvider() { + PathService::RegisterProvider(PathProvider, PATH_START, PATH_END); +} + +} // namespace gfx diff --git a/ui/gfx/gfx_paths.h b/ui/gfx/gfx_paths.h new file mode 100644 index 0000000..6be0cc3 --- /dev/null +++ b/ui/gfx/gfx_paths.h @@ -0,0 +1,28 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_GFX_PATHS_H_ +#define UI_GFX_GFX_PATHS_H_ +#pragma once + +// This file declares path keys for the app module. These can be used with +// the PathService to access various special directories and files. + +namespace gfx { + +enum { + PATH_START = 2000, + + // Valid only in development environment; TODO(darin): move these + DIR_TEST_DATA, // Directory where unit test data resides. + + PATH_END +}; + +// Call once to register the provider for the path keys defined above. +void RegisterPathProvider(); + +} // namespace gfx + +#endif // UI_GFX_GFX_PATHS_H_ diff --git a/ui/gfx/gfx_resources.grd b/ui/gfx/gfx_resources.grd new file mode 100644 index 0000000..8b9b4b4 --- /dev/null +++ b/ui/gfx/gfx_resources.grd @@ -0,0 +1,40 @@ +<?xml version="1.0" encoding="UTF-8"?> +<grit latest_public_release="0" current_release="1"> + <outputs> + <output filename="grit/gfx_resources.h" type="rc_header"> + <emit emit_type='prepend'></emit> + </output> + <output filename="grit/gfx_resources_map.cc" type="resource_map_source" /> + <output filename="grit/gfx_resources_map.h" type="resource_map_header" /> + <output filename="gfx_resources.pak" type="data_package" /> + <!-- TODO(sail): remove this file once WebKit has been updated. --> + <output filename="../../gfx/gfx_resources.pak" type="data_package" /> + <output filename="gfx_resources.rc" type="rc_all" /> + </outputs> + <release seq="1"> + <includes> + <if expr="os.find('win') != -1"> + <!-- IDR_BITMAP_BRUSH_IMAGE is for canvas_direct2d_unittest on win --> + <include name="IDR_BITMAP_BRUSH_IMAGE" file="resources\bitmap_brush_image.png" type="BINDATA" /> + </if> + + <if expr="os == 'linux2' or os.find('bsd') != -1 or os == 'sunos5'"> + <include name="IDR_LINUX_CHECKBOX_DISABLED_INDETERMINATE" file="resources\linux-checkbox-disabled-indeterminate.png" type="BINDATA" /> + <include name="IDR_LINUX_CHECKBOX_DISABLED_OFF" file="resources\linux-checkbox-disabled-off.png" type="BINDATA" /> + <include name="IDR_LINUX_CHECKBOX_DISABLED_ON" file="resources\linux-checkbox-disabled-on.png" type="BINDATA" /> + <include name="IDR_LINUX_CHECKBOX_INDETERMINATE" file="resources\linux-checkbox-indeterminate.png" type="BINDATA" /> + <include name="IDR_LINUX_CHECKBOX_OFF" file="resources\linux-checkbox-off.png" type="BINDATA" /> + <include name="IDR_LINUX_CHECKBOX_ON" file="resources\linux-checkbox-on.png" type="BINDATA" /> + <include name="IDR_LINUX_RADIO_DISABLED_OFF" file="resources\linux-radio-disabled-off.png" type="BINDATA" /> + <include name="IDR_LINUX_RADIO_DISABLED_ON" file="resources\linux-radio-disabled-on.png" type="BINDATA" /> + <include name="IDR_LINUX_RADIO_OFF" file="resources\linux-radio-off.png" type="BINDATA" /> + <include name="IDR_LINUX_RADIO_ON" file="resources\linux-radio-on.png" type="BINDATA" /> + <include name="IDR_PROGRESS_BAR" file="resources\linux-progress-bar.png" type="BINDATA" /> + <include name="IDR_PROGRESS_BORDER_LEFT" file="resources\linux-progress-border-left.png" type="BINDATA" /> + <include name="IDR_PROGRESS_BORDER_RIGHT" file="resources\linux-progress-border-right.png" type="BINDATA" /> + <include name="IDR_PROGRESS_VALUE" file="resources\linux-progress-value.png" type="BINDATA" /> + </if> + </includes> + </release> +</grit> + diff --git a/ui/gfx/gtk_native_view_id_manager.cc b/ui/gfx/gtk_native_view_id_manager.cc new file mode 100644 index 0000000..e9e72f2 --- /dev/null +++ b/ui/gfx/gtk_native_view_id_manager.cc @@ -0,0 +1,223 @@ +// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "gfx/gtk_native_view_id_manager.h" + +#include <gtk/gtk.h> +#include <gdk/gdkx.h> + +#include "base/logging.h" +#include "base/rand_util.h" +#include "gfx/gtk_preserve_window.h" + +// ----------------------------------------------------------------------------- +// Bounce functions for GTK to callback into a C++ object... + +void OnRealize(gfx::NativeView widget, void* arg) { + GtkNativeViewManager* manager = reinterpret_cast<GtkNativeViewManager*>(arg); + manager->OnRealize(widget); +} + +void OnUnrealize(gfx::NativeView widget, void *arg) { + GtkNativeViewManager* manager = reinterpret_cast<GtkNativeViewManager*>(arg); + manager->OnUnrealize(widget); +} + +static void OnDestroy(GtkObject* obj, void* arg) { + GtkNativeViewManager* manager = reinterpret_cast<GtkNativeViewManager*>(arg); + manager->OnDestroy(reinterpret_cast<GtkWidget*>(obj)); +} + +// ----------------------------------------------------------------------------- + + +// ----------------------------------------------------------------------------- +// Public functions... + +GtkNativeViewManager::GtkNativeViewManager() { +} + +GtkNativeViewManager::~GtkNativeViewManager() { +} + +// static +GtkNativeViewManager* GtkNativeViewManager::GetInstance() { + return Singleton<GtkNativeViewManager>::get(); +} + +gfx::NativeViewId GtkNativeViewManager::GetIdForWidget(gfx::NativeView widget) { + // This is just for unit tests: + if (!widget) + return 0; + + base::AutoLock locked(lock_); + + std::map<gfx::NativeView, gfx::NativeViewId>::const_iterator i = + native_view_to_id_.find(widget); + + if (i != native_view_to_id_.end()) + return i->second; + + gfx::NativeViewId new_id = + static_cast<gfx::NativeViewId>(base::RandUint64()); + while (id_to_info_.find(new_id) != id_to_info_.end()) + new_id = static_cast<gfx::NativeViewId>(base::RandUint64()); + + NativeViewInfo info; + info.widget = widget; + if (GTK_WIDGET_REALIZED(widget)) { + GdkWindow *gdk_window = widget->window; + DCHECK(gdk_window); + info.x_window_id = GDK_WINDOW_XID(gdk_window); + } + + native_view_to_id_[widget] = new_id; + id_to_info_[new_id] = info; + + g_signal_connect(widget, "realize", G_CALLBACK(::OnRealize), this); + g_signal_connect(widget, "unrealize", G_CALLBACK(::OnUnrealize), this); + g_signal_connect(widget, "destroy", G_CALLBACK(::OnDestroy), this); + + return new_id; +} + +bool GtkNativeViewManager::GetXIDForId(XID* output, gfx::NativeViewId id) { + base::AutoLock locked(lock_); + + std::map<gfx::NativeViewId, NativeViewInfo>::const_iterator i = + id_to_info_.find(id); + + if (i == id_to_info_.end()) + return false; + + *output = i->second.x_window_id; + return true; +} + +bool GtkNativeViewManager::GetPermanentXIDForId(XID* output, + gfx::NativeViewId id) { + base::AutoLock locked(lock_); + + std::map<gfx::NativeViewId, NativeViewInfo>::iterator i = + id_to_info_.find(id); + + if (i == id_to_info_.end()) + return false; + + // We only return permanent XIDs for widgets that allow us to guarantee that + // the XID will not change. + DCHECK(GTK_IS_PRESERVE_WINDOW(i->second.widget)); + GtkPreserveWindow* widget = + reinterpret_cast<GtkPreserveWindow*>(i->second.widget); + gtk_preserve_window_set_preserve(widget, TRUE); + + *output = GDK_WINDOW_XID(i->second.widget->window); + + // Update the reference count on the permanent XID. + PermanentXIDInfo info; + info.widget = widget; + info.ref_count = 1; + std::pair<std::map<XID, PermanentXIDInfo>::iterator, bool> ret = + perm_xid_to_info_.insert(std::make_pair(*output, info)); + + if (!ret.second) { + DCHECK(ret.first->second.widget == widget); + ret.first->second.ref_count++; + } + + return true; +} + +void GtkNativeViewManager::ReleasePermanentXID(XID xid) { + base::AutoLock locked(lock_); + + std::map<XID, PermanentXIDInfo>::iterator i = + perm_xid_to_info_.find(xid); + + if (i == perm_xid_to_info_.end()) + return; + + if (i->second.ref_count > 1) { + i->second.ref_count--; + } else { + if (i->second.widget) { + gtk_preserve_window_set_preserve(i->second.widget, FALSE); + } else { + GdkWindow* window = reinterpret_cast<GdkWindow*>( + gdk_xid_table_lookup(xid)); + DCHECK(window); + gdk_window_destroy(window); + } + perm_xid_to_info_.erase(i); + } +} + +// ----------------------------------------------------------------------------- + + +// ----------------------------------------------------------------------------- +// Private functions... + +gfx::NativeViewId GtkNativeViewManager::GetWidgetId(gfx::NativeView widget) { + lock_.AssertAcquired(); + + std::map<gfx::NativeView, gfx::NativeViewId>::const_iterator i = + native_view_to_id_.find(widget); + + CHECK(i != native_view_to_id_.end()); + return i->second; +} + +void GtkNativeViewManager::OnRealize(gfx::NativeView widget) { + base::AutoLock locked(lock_); + + const gfx::NativeViewId id = GetWidgetId(widget); + std::map<gfx::NativeViewId, NativeViewInfo>::iterator i = + id_to_info_.find(id); + + CHECK(i != id_to_info_.end()); + CHECK(widget->window); + + i->second.x_window_id = GDK_WINDOW_XID(widget->window); +} + +void GtkNativeViewManager::OnUnrealize(gfx::NativeView widget) { + base::AutoLock unrealize_locked(unrealize_lock_); + base::AutoLock locked(lock_); + + const gfx::NativeViewId id = GetWidgetId(widget); + std::map<gfx::NativeViewId, NativeViewInfo>::iterator i = + id_to_info_.find(id); + + CHECK(i != id_to_info_.end()); +} + +void GtkNativeViewManager::OnDestroy(gfx::NativeView widget) { + base::AutoLock locked(lock_); + + std::map<gfx::NativeView, gfx::NativeViewId>::iterator i = + native_view_to_id_.find(widget); + CHECK(i != native_view_to_id_.end()); + + std::map<gfx::NativeViewId, NativeViewInfo>::iterator j = + id_to_info_.find(i->second); + CHECK(j != id_to_info_.end()); + + // If the XID is supposed to outlive the widget, mark it + // in the lookup table. + if (GTK_IS_PRESERVE_WINDOW(widget) && + gtk_preserve_window_get_preserve( + reinterpret_cast<GtkPreserveWindow*>(widget))) { + std::map<XID, PermanentXIDInfo>::iterator k = + perm_xid_to_info_.find(GDK_WINDOW_XID(widget->window)); + + if (k != perm_xid_to_info_.end()) + k->second.widget = NULL; + } + + native_view_to_id_.erase(i); + id_to_info_.erase(j); +} + +// ----------------------------------------------------------------------------- diff --git a/ui/gfx/gtk_native_view_id_manager.h b/ui/gfx/gtk_native_view_id_manager.h new file mode 100644 index 0000000..6befcfc --- /dev/null +++ b/ui/gfx/gtk_native_view_id_manager.h @@ -0,0 +1,141 @@ +// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_GTK_NATIVE_VIEW_ID_MANAGER_H_ +#define UI_GFX_GTK_NATIVE_VIEW_ID_MANAGER_H_ +#pragma once + +#include <map> + +#include "base/singleton.h" +#include "base/synchronization/lock.h" +#include "gfx/native_widget_types.h" + +typedef unsigned long XID; +struct _GtkPreserveWindow; + +// NativeViewIds are the opaque values which the renderer holds as a reference +// to a window. These ids are often used in sync calls from the renderer and +// one cannot terminate sync calls on the UI thread as that can lead to +// deadlocks. +// +// Because of this, we have the BACKGROUND_X11 thread for these calls and this +// thread has a separate X connection in order to answer them. But one cannot +// use GTK on multiple threads, so the BACKGROUND_X11 thread deals only in Xlib +// calls and, thus, XIDs. +// +// So we could make NativeViewIds be the X id of the window. However, at the +// time when we need to tell the renderer about its NativeViewId, an XID isn't +// availible and it goes very much against the grain of the code to make it so. +// Also, we worry that GTK might choose to change the underlying X window id +// when, say, the widget is hidden or repacked. Finally, if we used XIDs then a +// compromised renderer could start asking questions about any X windows on the +// system. +// +// Thus, we have this object. It produces random NativeViewIds from GtkWidget +// pointers and observes the various signals from the widget for when an X +// window is created, destroyed etc. Thus it provides a thread safe mapping +// from NativeViewIds to the current XID for that widget. +class GtkNativeViewManager { + public: + // Returns the singleton instance. + static GtkNativeViewManager* GetInstance(); + + // Must be called from the UI thread: + // + // Return a NativeViewId for the given widget and attach to the various + // signals emitted by that widget. The NativeViewId is pseudo-randomly + // allocated so that a compromised renderer trying to guess values will fail + // with high probability. The NativeViewId will not be reused for the + // lifetime of the GtkWidget. + gfx::NativeViewId GetIdForWidget(gfx::NativeView widget); + + // May be called from any thread: + // + // xid: (output) the resulting X window ID, or 0 + // id: a value previously returned from GetIdForWidget + // returns: true if |id| is a valid id, false otherwise. + // + // If the widget referenced by |id| does not current have an X window id, + // |*xid| is set to 0. + bool GetXIDForId(XID* xid, gfx::NativeViewId id); + + // Must be called from the UI thread because we may need the associated + // widget to create a window. + // + // Keeping the XID permanent requires a bit of overhead, so it must + // be explicitly requested. + // + // xid: (output) the resulting X window + // id: a value previously returned from GetIdForWidget + // returns: true if |id| is a valid id, false otherwise. + bool GetPermanentXIDForId(XID* xid, gfx::NativeViewId id); + + // Must be called from the UI thread because we may need to access a + // GtkWidget or destroy a GdkWindow. + // + // If the widget associated with the XID is still alive, allow the widget + // to destroy the associated XID when it wants. Otherwise, destroy the + // GdkWindow associated with the XID. + void ReleasePermanentXID(XID xid); + + // These are actually private functions, but need to be called from statics. + void OnRealize(gfx::NativeView widget); + void OnUnrealize(gfx::NativeView widget); + void OnDestroy(gfx::NativeView widget); + + base::Lock& unrealize_lock() { return unrealize_lock_; } + + private: + // This object is a singleton: + GtkNativeViewManager(); + ~GtkNativeViewManager(); + friend struct DefaultSingletonTraits<GtkNativeViewManager>; + + struct NativeViewInfo { + NativeViewInfo() : widget(NULL), x_window_id(0) { + } + gfx::NativeView widget; + XID x_window_id; + }; + + gfx::NativeViewId GetWidgetId(gfx::NativeView id); + + // This lock can be used to block GTK from unrealizing windows. This is needed + // when the BACKGROUND_X11 thread is using a window obtained via GetXIDForId, + // and can't allow the X11 resource to be deleted. + base::Lock unrealize_lock_; + + // protects native_view_to_id_ and id_to_info_ + base::Lock lock_; + + // If asked for an id for the same widget twice, we want to return the same + // id. So this records the current mapping. + std::map<gfx::NativeView, gfx::NativeViewId> native_view_to_id_; + std::map<gfx::NativeViewId, NativeViewInfo> id_to_info_; + + struct PermanentXIDInfo { + PermanentXIDInfo() : widget(NULL), ref_count(0) { + } + _GtkPreserveWindow* widget; + int ref_count; + }; + + // Used to maintain the reference count for permanent XIDs + // (referenced by GetPermanentXIDForId and dereferenced by + // ReleasePermanentXID). Only those XIDs with a positive reference count + // will be in the table. + // + // In general, several GTK widgets may share the same X window. We assume + // that is not true of the widgets stored in this registry. + // + // An XID will map to NULL, if there is an outstanding reference but the + // widget was destroyed. In this case, the destruction of the X window + // is deferred to the dropping of all references. + std::map<XID, PermanentXIDInfo> perm_xid_to_info_; + + DISALLOW_COPY_AND_ASSIGN(GtkNativeViewManager); +}; + +#endif // UI_GFX_GTK_NATIVE_VIEW_ID_MANAGER_H_ diff --git a/ui/gfx/gtk_preserve_window.cc b/ui/gfx/gtk_preserve_window.cc new file mode 100644 index 0000000..20215d1 --- /dev/null +++ b/ui/gfx/gtk_preserve_window.cc @@ -0,0 +1,200 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "gfx/gtk_preserve_window.h" + +#include <gdk/gdkwindow.h> +#include <gtk/gtk.h> +#include <gtk/gtkwidget.h> +#include <gtk/gtkfixed.h> + +G_BEGIN_DECLS + +#define GTK_PRESERVE_WINDOW_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), \ + GTK_TYPE_PRESERVE_WINDOW, \ + GtkPreserveWindowPrivate)) + +typedef struct _GtkPreserveWindowPrivate GtkPreserveWindowPrivate; + +struct _GtkPreserveWindowPrivate { + // If true, don't create/destroy windows on realize/unrealize. + gboolean preserve_window; + + // Whether or not we delegate the resize of the GdkWindow + // to someone else. + gboolean delegate_resize; +}; + +G_DEFINE_TYPE(GtkPreserveWindow, gtk_preserve_window, GTK_TYPE_FIXED) + +static void gtk_preserve_window_destroy(GtkObject* object); +static void gtk_preserve_window_realize(GtkWidget* widget); +static void gtk_preserve_window_unrealize(GtkWidget* widget); +static void gtk_preserve_window_size_allocate(GtkWidget* widget, + GtkAllocation* allocation); + +static void gtk_preserve_window_class_init(GtkPreserveWindowClass *klass) { + GtkWidgetClass* widget_class = reinterpret_cast<GtkWidgetClass*>(klass); + widget_class->realize = gtk_preserve_window_realize; + widget_class->unrealize = gtk_preserve_window_unrealize; + widget_class->size_allocate = gtk_preserve_window_size_allocate; + + GtkObjectClass* object_class = reinterpret_cast<GtkObjectClass*>(klass); + object_class->destroy = gtk_preserve_window_destroy; + + GObjectClass* gobject_class = G_OBJECT_CLASS(klass); + g_type_class_add_private(gobject_class, sizeof(GtkPreserveWindowPrivate)); +} + +static void gtk_preserve_window_init(GtkPreserveWindow* widget) { + GtkPreserveWindowPrivate* priv = GTK_PRESERVE_WINDOW_GET_PRIVATE(widget); + priv->preserve_window = FALSE; + + // These widgets always have their own window. + gtk_fixed_set_has_window(GTK_FIXED(widget), TRUE); +} + +GtkWidget* gtk_preserve_window_new() { + return GTK_WIDGET(g_object_new(GTK_TYPE_PRESERVE_WINDOW, NULL)); +} + +static void gtk_preserve_window_destroy(GtkObject* object) { + GtkWidget* widget = reinterpret_cast<GtkWidget*>(object); + GtkPreserveWindowPrivate* priv = GTK_PRESERVE_WINDOW_GET_PRIVATE(widget); + + if (widget->window) { + gdk_window_set_user_data(widget->window, NULL); + // If the window is preserved, someone else must destroy it. + if (!priv->preserve_window) + gdk_window_destroy(widget->window); + widget->window = NULL; + } + + GTK_OBJECT_CLASS(gtk_preserve_window_parent_class)->destroy(object); +} + +static void gtk_preserve_window_realize(GtkWidget* widget) { + g_return_if_fail(GTK_IS_PRESERVE_WINDOW(widget)); + + if (widget->window) { + gdk_window_reparent(widget->window, + gtk_widget_get_parent_window(widget), + widget->allocation.x, + widget->allocation.y); + widget->style = gtk_style_attach(widget->style, widget->window); + gtk_style_set_background(widget->style, widget->window, GTK_STATE_NORMAL); + + gint event_mask = gtk_widget_get_events(widget); + event_mask |= GDK_EXPOSURE_MASK | GDK_BUTTON_PRESS_MASK; + gdk_window_set_events(widget->window, (GdkEventMask) event_mask); + gdk_window_set_user_data(widget->window, widget); + + // Deprecated as of GTK 2.22. Used for compatibility. + // It should be: gtk_widget_set_realized(widget, TRUE) + GTK_WIDGET_SET_FLAGS(widget, GTK_REALIZED); + } else { + GTK_WIDGET_CLASS(gtk_preserve_window_parent_class)->realize(widget); + } +} + +static void gtk_preserve_window_unrealize(GtkWidget* widget) { + g_return_if_fail(GTK_IS_PRESERVE_WINDOW(widget)); + + GtkPreserveWindowPrivate* priv = GTK_PRESERVE_WINDOW_GET_PRIVATE(widget); + if (priv->preserve_window) { + GtkWidgetClass* widget_class = + GTK_WIDGET_CLASS(gtk_preserve_window_parent_class); + GtkContainerClass* container_class = + GTK_CONTAINER_CLASS(gtk_preserve_window_parent_class); + + // Deprecated as of GTK 2.22. Used for compatibility. + // It should be: gtk_widget_get_mapped() + if (GTK_WIDGET_MAPPED(widget)) { + widget_class->unmap(widget); + + // Deprecated as of GTK 2.22. Used for compatibility. + // It should be: gtk_widget_set_mapped(widget, FALSE) + GTK_WIDGET_UNSET_FLAGS(widget, GTK_MAPPED); + } + + // This is the behavior from GtkWidget, inherited by GtkFixed. + // It is unclear why we should not call the potentially overridden + // unrealize method (via the callback), but doing so causes errors. + container_class->forall( + GTK_CONTAINER(widget), FALSE, + reinterpret_cast<GtkCallback>(gtk_widget_unrealize), NULL); + + gtk_style_detach(widget->style); + gdk_window_reparent(widget->window, gdk_get_default_root_window(), 0, 0); + gtk_selection_remove_all(widget); + gdk_window_set_user_data(widget->window, NULL); + + // Deprecated as of GTK 2.22. Used for compatibility. + // It should be: gtk_widget_set_realized(widget, FALSE) + GTK_WIDGET_UNSET_FLAGS(widget, GTK_REALIZED); + } else { + GTK_WIDGET_CLASS(gtk_preserve_window_parent_class)->unrealize(widget); + } +} + +gboolean gtk_preserve_window_get_preserve(GtkPreserveWindow* window) { + g_return_val_if_fail(GTK_IS_PRESERVE_WINDOW(window), FALSE); + GtkPreserveWindowPrivate* priv = GTK_PRESERVE_WINDOW_GET_PRIVATE(window); + + return priv->preserve_window; +} + +void gtk_preserve_window_set_preserve(GtkPreserveWindow* window, + gboolean value) { + g_return_if_fail(GTK_IS_PRESERVE_WINDOW(window)); + GtkPreserveWindowPrivate* priv = GTK_PRESERVE_WINDOW_GET_PRIVATE(window); + priv->preserve_window = value; + + GtkWidget* widget = GTK_WIDGET(window); + if (value && !widget->window) { + GdkWindowAttr attributes; + gint attributes_mask; + + // We may not know the width and height, so we rely on the fact + // that a size-allocation will resize it later. + attributes.width = 1; + attributes.height = 1; + + attributes.window_type = GDK_WINDOW_CHILD; + attributes.wclass = GDK_INPUT_OUTPUT; + + attributes.visual = gtk_widget_get_visual(widget); + attributes.colormap = gtk_widget_get_colormap(widget); + + attributes_mask = GDK_WA_VISUAL | GDK_WA_COLORMAP; + widget->window = gdk_window_new( + gdk_get_default_root_window(), &attributes, attributes_mask); + } else if (!value && widget->window && !GTK_WIDGET_REALIZED(widget)) { + gdk_window_destroy(widget->window); + widget->window = NULL; + } +} + +void gtk_preserve_window_size_allocate(GtkWidget* widget, + GtkAllocation* allocation) { + g_return_if_fail(GTK_IS_PRESERVE_WINDOW(widget)); + GtkPreserveWindowPrivate* priv = GTK_PRESERVE_WINDOW_GET_PRIVATE(widget); + + if (priv->delegate_resize) { + // Just update the state. Someone else will gdk_window_resize the + // associated GdkWindow. + widget->allocation = *allocation; + } else { + GTK_WIDGET_CLASS(gtk_preserve_window_parent_class)->size_allocate( + widget, allocation); + } +} + +void gtk_preserve_window_delegate_resize(GtkPreserveWindow* widget, + gboolean delegate) { + GtkPreserveWindowPrivate* priv = GTK_PRESERVE_WINDOW_GET_PRIVATE(widget); + priv->delegate_resize = delegate; +} + +G_END_DECLS diff --git a/ui/gfx/gtk_preserve_window.h b/ui/gfx/gtk_preserve_window.h new file mode 100644 index 0000000..3c9b1d8 --- /dev/null +++ b/ui/gfx/gtk_preserve_window.h @@ -0,0 +1,64 @@ +// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_GTK_PRESERVE_WINDOW_H_ +#define UI_GFX_GTK_PRESERVE_WINDOW_H_ +#pragma once + +#include <gdk/gdk.h> +#include <gtk/gtk.h> + +// GtkFixed creates an X window when realized and destroys an X window +// when unrealized. GtkPreserveWindow allows overrides this +// behaviour. When preserve is set (via gtk_preserve_window_set_preserve), +// the X window is only destroyed when the widget is destroyed. + +G_BEGIN_DECLS + +#define GTK_TYPE_PRESERVE_WINDOW \ + (gtk_preserve_window_get_type()) +#define GTK_PRESERVE_WINDOW(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj), GTK_TYPE_PERSERVE_WINDOW, \ + GtkPreserveWindow)) +#define GTK_PRESERVE_WINDOW_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass), GTK_TYPE_PRESERVE_WINDOW, \ + GtkPreserveWindowClass)) +#define GTK_IS_PRESERVE_WINDOW(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE((obj), GTK_TYPE_PRESERVE_WINDOW)) +#define GTK_IS_PRESERVE_WINDOW_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE((klass), GTK_TYPE_PRESERVE_WINDOW)) +#define GTK_PRESERVE_WINDOW_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS((obj), GTK_TYPE_PRESERVE_WINDOW, \ + GtkPreserveWindowClass)) + +typedef struct _GtkPreserveWindow GtkPreserveWindow; +typedef struct _GtkPreserveWindowClass GtkPreserveWindowClass; + +struct _GtkPreserveWindow { + // Parent class. + GtkFixed fixed; +}; + +struct _GtkPreserveWindowClass { + GtkFixedClass parent_class; +}; + +GType gtk_preserve_window_get_type() G_GNUC_CONST; +GtkWidget* gtk_preserve_window_new(); + +// Whether or not we should preserve associated windows as the widget +// is realized or unrealized. +gboolean gtk_preserve_window_get_preserve(GtkPreserveWindow* widget); +void gtk_preserve_window_set_preserve(GtkPreserveWindow* widget, + gboolean value); + +// Whether or not someone else will gdk_window_resize the GdkWindow associated +// with this widget (needed by the GPU process to synchronize resizing +// with swapped between front and back buffer). +void gtk_preserve_window_delegate_resize(GtkPreserveWindow* widget, + gboolean delegate); + +G_END_DECLS + +#endif // UI_GFX_GTK_PRESERVE_WINDOW_H_ diff --git a/ui/gfx/gtk_util.cc b/ui/gfx/gtk_util.cc new file mode 100644 index 0000000..fc240e0 --- /dev/null +++ b/ui/gfx/gtk_util.cc @@ -0,0 +1,212 @@ +// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "gfx/gtk_util.h" + +#include <gdk/gdk.h> +#include <gtk/gtk.h> +#include <stdlib.h> + +#include "base/basictypes.h" +#include "base/command_line.h" +#include "base/linux_util.h" +#include "gfx/rect.h" +#include "third_party/skia/include/core/SkBitmap.h" +#include "third_party/skia/include/core/SkUnPreMultiply.h" + +namespace { + +// A process wide singleton that manages our usage of gdk +// cursors. gdk_cursor_new() hits the disk in several places and GdkCursor +// instances can be reused throughout the process. +class GdkCursorCache { + public: + GdkCursorCache() {} + ~GdkCursorCache() { + for (std::map<GdkCursorType, GdkCursor*>::iterator it = + cursor_cache_.begin(); it != cursor_cache_.end(); ++it) { + gdk_cursor_unref(it->second); + } + cursor_cache_.clear(); + } + + GdkCursor* GetCursorImpl(GdkCursorType type) { + std::map<GdkCursorType, GdkCursor*>::iterator it = cursor_cache_.find(type); + GdkCursor* cursor = NULL; + if (it == cursor_cache_.end()) { + cursor = gdk_cursor_new(type); + cursor_cache_.insert(std::make_pair(type, cursor)); + } else { + cursor = it->second; + } + + // It is not necessary to add a reference here. The callers can ref the + // cursor if they need it for something. + return cursor; + } + + std::map<GdkCursorType, GdkCursor*> cursor_cache_; + + DISALLOW_COPY_AND_ASSIGN(GdkCursorCache); +}; + +void FreePixels(guchar* pixels, gpointer data) { + free(data); +} + +// Common implementation of ConvertAcceleratorsFromWindowsStyle() and +// RemoveWindowsStyleAccelerators(). +// Replaces all ampersands (as used in our grd files to indicate mnemonics) +// to |target|. Similarly any underscores get replaced with two underscores as +// is needed by pango. +std::string ConvertAmperstandsTo(const std::string& label, + const std::string& target) { + std::string ret; + ret.reserve(label.length() * 2); + for (size_t i = 0; i < label.length(); ++i) { + if ('_' == label[i]) { + ret.push_back('_'); + ret.push_back('_'); + } else if ('&' == label[i]) { + if (i + 1 < label.length() && '&' == label[i + 1]) { + ret.push_back('&'); + ++i; + } else { + ret.append(target); + } + } else { + ret.push_back(label[i]); + } + } + + return ret; +} + +} // namespace + +namespace gfx { + +void GtkInitFromCommandLine(const CommandLine& command_line) { + const std::vector<std::string>& args = command_line.argv(); + int argc = args.size(); + scoped_array<char *> argv(new char *[argc + 1]); + for (size_t i = 0; i < args.size(); ++i) { + // TODO(piman@google.com): can gtk_init modify argv? Just being safe + // here. + argv[i] = strdup(args[i].c_str()); + } + argv[argc] = NULL; + char **argv_pointer = argv.get(); + + gtk_init(&argc, &argv_pointer); + for (size_t i = 0; i < args.size(); ++i) { + free(argv[i]); + } +} + +GdkPixbuf* GdkPixbufFromSkBitmap(const SkBitmap* bitmap) { + if (bitmap->isNull()) + return NULL; + + bitmap->lockPixels(); + + int width = bitmap->width(); + int height = bitmap->height(); + int stride = bitmap->rowBytes(); + + // SkBitmaps are premultiplied, we need to unpremultiply them. + const int kBytesPerPixel = 4; + uint8* divided = static_cast<uint8*>(malloc(height * stride)); + + for (int y = 0, i = 0; y < height; y++) { + for (int x = 0; x < width; x++) { + uint32 pixel = bitmap->getAddr32(0, y)[x]; + + int alpha = SkColorGetA(pixel); + if (alpha != 0 && alpha != 255) { + SkColor unmultiplied = SkUnPreMultiply::PMColorToColor(pixel); + divided[i + 0] = SkColorGetR(unmultiplied); + divided[i + 1] = SkColorGetG(unmultiplied); + divided[i + 2] = SkColorGetB(unmultiplied); + divided[i + 3] = alpha; + } else { + divided[i + 0] = SkColorGetR(pixel); + divided[i + 1] = SkColorGetG(pixel); + divided[i + 2] = SkColorGetB(pixel); + divided[i + 3] = alpha; + } + i += kBytesPerPixel; + } + } + + // This pixbuf takes ownership of our malloc()ed data and will + // free it for us when it is destroyed. + GdkPixbuf* pixbuf = gdk_pixbuf_new_from_data( + divided, + GDK_COLORSPACE_RGB, // The only colorspace gtk supports. + true, // There is an alpha channel. + 8, + width, height, stride, &FreePixels, divided); + + bitmap->unlockPixels(); + return pixbuf; +} + +void SubtractRectanglesFromRegion(GdkRegion* region, + const std::vector<Rect>& cutouts) { + for (size_t i = 0; i < cutouts.size(); ++i) { + GdkRectangle rect = cutouts[i].ToGdkRectangle(); + GdkRegion* rect_region = gdk_region_rectangle(&rect); + gdk_region_subtract(region, rect_region); + // TODO(deanm): It would be nice to be able to reuse the GdkRegion here. + gdk_region_destroy(rect_region); + } +} + +double GetPangoResolution() { + static double resolution; + static bool determined_resolution = false; + if (!determined_resolution) { + determined_resolution = true; + PangoContext* default_context = gdk_pango_context_get(); + resolution = pango_cairo_context_get_resolution(default_context); + g_object_unref(default_context); + } + return resolution; +} + +GdkCursor* GetCursor(int type) { + static GdkCursorCache impl; + return impl.GetCursorImpl(static_cast<GdkCursorType>(type)); +} + +std::string ConvertAcceleratorsFromWindowsStyle(const std::string& label) { + return ConvertAmperstandsTo(label, "_"); +} + +std::string RemoveWindowsStyleAccelerators(const std::string& label) { + return ConvertAmperstandsTo(label, ""); +} + +uint8_t* BGRAToRGBA(const uint8_t* pixels, int width, int height, int stride) { + if (stride == 0) + stride = width * 4; + + uint8_t* new_pixels = static_cast<uint8_t*>(malloc(height * stride)); + + // We have to copy the pixels and swap from BGRA to RGBA. + for (int i = 0; i < height; ++i) { + for (int j = 0; j < width; ++j) { + int idx = i * stride + j * 4; + new_pixels[idx] = pixels[idx + 2]; + new_pixels[idx + 1] = pixels[idx + 1]; + new_pixels[idx + 2] = pixels[idx]; + new_pixels[idx + 3] = pixels[idx + 3]; + } + } + + return new_pixels; +} + +} // namespace gfx diff --git a/ui/gfx/gtk_util.h b/ui/gfx/gtk_util.h new file mode 100644 index 0000000..71cb792 --- /dev/null +++ b/ui/gfx/gtk_util.h @@ -0,0 +1,84 @@ +// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_GTK_UTIL_H_ +#define UI_GFX_GTK_UTIL_H_ +#pragma once + +#include <glib-object.h> +#include <stdint.h> + +#include <string> +#include <vector> + +#include "base/scoped_ptr.h" + +typedef struct _GdkPixbuf GdkPixbuf; +typedef struct _GdkRegion GdkRegion; +typedef struct _GdkCursor GdkCursor; + +class CommandLine; +class SkBitmap; + +namespace gfx { + +class Rect; + +// Call gtk_init() using the argc and argv from command_line. +// gtk_init() wants an argc and argv that it can mutate; we provide those, +// but leave the original CommandLine unaltered. +void GtkInitFromCommandLine(const CommandLine& command_line); + +// Convert and copy a SkBitmap to a GdkPixbuf. NOTE: this uses BGRAToRGBA, so +// it is an expensive operation. The returned GdkPixbuf will have a refcount of +// 1, and the caller is responsible for unrefing it when done. +GdkPixbuf* GdkPixbufFromSkBitmap(const SkBitmap* bitmap); + +// Modify the given region by subtracting the given rectangles. +void SubtractRectanglesFromRegion(GdkRegion* region, + const std::vector<Rect>& cutouts); + +// Returns the resolution (DPI) used by pango. A negative values means the +// resolution hasn't been set. +double GetPangoResolution(); + +// Returns a static instance of a GdkCursor* object, sharable across the +// process. Caller must gdk_cursor_ref() it if they want to assume ownership. +GdkCursor* GetCursor(int type); + +// Change windows accelerator style to GTK style. (GTK uses _ for +// accelerators. Windows uses & with && as an escape for &.) +std::string ConvertAcceleratorsFromWindowsStyle(const std::string& label); + +// Removes the "&" accelerators from a Windows label. +std::string RemoveWindowsStyleAccelerators(const std::string& label); + +// Makes a copy of |pixels| with the ordering changed from BGRA to RGBA. +// The caller is responsible for free()ing the data. If |stride| is 0, it's +// assumed to be 4 * |width|. +uint8_t* BGRAToRGBA(const uint8_t* pixels, int width, int height, int stride); + +} // namespace gfx + +namespace { +// A helper class that will g_object_unref |p| when it goes out of scope. +// This never adds a ref, it only unrefs. +template <typename Type> +struct GObjectUnrefer { + void operator()(Type* ptr) const { + if (ptr) + g_object_unref(ptr); + } +}; +} // namespace + +// It's not legal C++ to have a templatized typedefs, so we wrap it in a +// struct. When using this, you need to include ::Type. E.g., +// ScopedGObject<GdkPixbufLoader>::Type loader(gdk_pixbuf_loader_new()); +template<class T> +struct ScopedGObject { + typedef scoped_ptr_malloc<T, GObjectUnrefer<T> > Type; +}; + +#endif // UI_GFX_GTK_UTIL_H_ diff --git a/ui/gfx/icon_util.cc b/ui/gfx/icon_util.cc new file mode 100644 index 0000000..cabc505 --- /dev/null +++ b/ui/gfx/icon_util.cc @@ -0,0 +1,457 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "gfx/icon_util.h" + +#include "base/file_util.h" +#include "base/logging.h" +#include "base/scoped_ptr.h" +#include "base/win/scoped_handle.h" +#include "gfx/size.h" +#include "skia/ext/image_operations.h" +#include "third_party/skia/include/core/SkBitmap.h" + +// Defining the dimensions for the icon images. We store only one value because +// we always resize to a square image; that is, the value 48 means that we are +// going to resize the given bitmap to a 48 by 48 pixels bitmap. +// +// The icon images appear in the icon file in same order in which their +// corresponding dimensions appear in the |icon_dimensions_| array, so it is +// important to keep this array sorted. Also note that the maximum icon image +// size we can handle is 255 by 255. +const int IconUtil::icon_dimensions_[] = { + 8, // Recommended by the MSDN as a nice to have icon size. + 10, // Used by the Shell (e.g. for shortcuts). + 14, // Recommended by the MSDN as a nice to have icon size. + 16, // Toolbar, Application and Shell icon sizes. + 22, // Recommended by the MSDN as a nice to have icon size. + 24, // Used by the Shell (e.g. for shortcuts). + 32, // Toolbar, Dialog and Wizard icon size. + 40, // Quick Launch. + 48, // Alt+Tab icon size. + 64, // Recommended by the MSDN as a nice to have icon size. + 96, // Recommended by the MSDN as a nice to have icon size. + 128 // Used by the Shell (e.g. for shortcuts). +}; + +HICON IconUtil::CreateHICONFromSkBitmap(const SkBitmap& bitmap) { + // Only 32 bit ARGB bitmaps are supported. We also try to perform as many + // validations as we can on the bitmap. + SkAutoLockPixels bitmap_lock(bitmap); + if ((bitmap.getConfig() != SkBitmap::kARGB_8888_Config) || + (bitmap.width() <= 0) || (bitmap.height() <= 0) || + (bitmap.getPixels() == NULL)) + return NULL; + + // We start by creating a DIB which we'll use later on in order to create + // the HICON. We use BITMAPV5HEADER since the bitmap we are about to convert + // may contain an alpha channel and the V5 header allows us to specify the + // alpha mask for the DIB. + BITMAPV5HEADER bitmap_header; + InitializeBitmapHeader(&bitmap_header, bitmap.width(), bitmap.height()); + void* bits; + HDC hdc = ::GetDC(NULL); + HBITMAP dib; + dib = ::CreateDIBSection(hdc, reinterpret_cast<BITMAPINFO*>(&bitmap_header), + DIB_RGB_COLORS, &bits, NULL, 0); + DCHECK(dib); + ::ReleaseDC(NULL, hdc); + memcpy(bits, bitmap.getPixels(), bitmap.width() * bitmap.height() * 4); + + // Icons are generally created using an AND and XOR masks where the AND + // specifies boolean transparency (the pixel is either opaque or + // transparent) and the XOR mask contains the actual image pixels. If the XOR + // mask bitmap has an alpha channel, the AND monochrome bitmap won't + // actually be used for computing the pixel transparency. Even though all our + // bitmap has an alpha channel, Windows might not agree when all alpha values + // are zero. So the monochrome bitmap is created with all pixels transparent + // for this case. Otherwise, it is created with all pixels opaque. + bool bitmap_has_alpha_channel = PixelsHaveAlpha( + static_cast<const uint32*>(bitmap.getPixels()), + bitmap.width() * bitmap.height()); + + scoped_array<uint8> mask_bits; + if (!bitmap_has_alpha_channel) { + // Bytes per line with paddings to make it word alignment. + size_t bytes_per_line = (bitmap.width() + 0xF) / 16 * 2; + size_t mask_bits_size = bytes_per_line * bitmap.height(); + + mask_bits.reset(new uint8[mask_bits_size]); + DCHECK(mask_bits.get()); + + // Make all pixels transparent. + memset(mask_bits.get(), 0xFF, mask_bits_size); + } + + HBITMAP mono_bitmap = ::CreateBitmap(bitmap.width(), bitmap.height(), 1, 1, + reinterpret_cast<LPVOID>(mask_bits.get())); + DCHECK(mono_bitmap); + + ICONINFO icon_info; + icon_info.fIcon = TRUE; + icon_info.xHotspot = 0; + icon_info.yHotspot = 0; + icon_info.hbmMask = mono_bitmap; + icon_info.hbmColor = dib; + HICON icon = ::CreateIconIndirect(&icon_info); + ::DeleteObject(dib); + ::DeleteObject(mono_bitmap); + return icon; +} + +SkBitmap* IconUtil::CreateSkBitmapFromHICON(HICON icon, const gfx::Size& s) { + // We start with validating parameters. + ICONINFO icon_info; + if (!icon || !(::GetIconInfo(icon, &icon_info)) || + !icon_info.fIcon || s.IsEmpty()) + return NULL; + + // Allocating memory for the SkBitmap object. We are going to create an ARGB + // bitmap so we should set the configuration appropriately. + SkBitmap* bitmap = new SkBitmap; + DCHECK(bitmap); + bitmap->setConfig(SkBitmap::kARGB_8888_Config, s.width(), s.height()); + bitmap->allocPixels(); + bitmap->eraseARGB(0, 0, 0, 0); + SkAutoLockPixels bitmap_lock(*bitmap); + + // Now we should create a DIB so that we can use ::DrawIconEx in order to + // obtain the icon's image. + BITMAPV5HEADER h; + InitializeBitmapHeader(&h, s.width(), s.height()); + HDC dc = ::GetDC(NULL); + uint32* bits; + HBITMAP dib = ::CreateDIBSection(dc, reinterpret_cast<BITMAPINFO*>(&h), + DIB_RGB_COLORS, reinterpret_cast<void**>(&bits), NULL, 0); + DCHECK(dib); + HDC dib_dc = CreateCompatibleDC(dc); + DCHECK(dib_dc); + ::SelectObject(dib_dc, dib); + + // Windows icons are defined using two different masks. The XOR mask, which + // represents the icon image and an AND mask which is a monochrome bitmap + // which indicates the transparency of each pixel. + // + // To make things more complex, the icon image itself can be an ARGB bitmap + // and therefore contain an alpha channel which specifies the transparency + // for each pixel. Unfortunately, there is no easy way to determine whether + // or not a bitmap has an alpha channel and therefore constructing the bitmap + // for the icon is nothing but straightforward. + // + // The idea is to read the AND mask but use it only if we know for sure that + // the icon image does not have an alpha channel. The only way to tell if the + // bitmap has an alpha channel is by looking through the pixels and checking + // whether there are non-zero alpha bytes. + // + // We start by drawing the AND mask into our DIB. + size_t num_pixels = s.GetArea(); + memset(bits, 0, num_pixels * 4); + ::DrawIconEx(dib_dc, 0, 0, icon, s.width(), s.height(), 0, NULL, DI_MASK); + + // Capture boolean opacity. We may not use it if we find out the bitmap has + // an alpha channel. + bool* opaque = new bool[num_pixels]; + DCHECK(opaque); + for (size_t i = 0; i < num_pixels; ++i) + opaque[i] = !bits[i]; + + // Then draw the image itself which is really the XOR mask. + memset(bits, 0, num_pixels * 4); + ::DrawIconEx(dib_dc, 0, 0, icon, s.width(), s.height(), 0, NULL, DI_NORMAL); + memcpy(bitmap->getPixels(), static_cast<void*>(bits), num_pixels * 4); + + // Finding out whether the bitmap has an alpha channel. + bool bitmap_has_alpha_channel = PixelsHaveAlpha( + static_cast<const uint32*>(bitmap->getPixels()), num_pixels); + + // If the bitmap does not have an alpha channel, we need to build it using + // the previously captured AND mask. Otherwise, we are done. + if (!bitmap_has_alpha_channel) { + uint32* p = static_cast<uint32*>(bitmap->getPixels()); + for (size_t i = 0; i < num_pixels; ++p, ++i) { + DCHECK_EQ((*p & 0xff000000), 0u); + if (opaque[i]) + *p |= 0xff000000; + else + *p &= 0x00ffffff; + } + } + + delete [] opaque; + ::DeleteDC(dib_dc); + ::DeleteObject(dib); + ::ReleaseDC(NULL, dc); + + return bitmap; +} + +bool IconUtil::CreateIconFileFromSkBitmap(const SkBitmap& bitmap, + const FilePath& icon_path) { + // Only 32 bit ARGB bitmaps are supported. We also make sure the bitmap has + // been properly initialized. + SkAutoLockPixels bitmap_lock(bitmap); + if ((bitmap.getConfig() != SkBitmap::kARGB_8888_Config) || + (bitmap.height() <= 0) || (bitmap.width() <= 0) || + (bitmap.getPixels() == NULL)) + return false; + + // We start by creating the file. + base::win::ScopedHandle icon_file(::CreateFile(icon_path.value().c_str(), + GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL)); + + if (icon_file.Get() == INVALID_HANDLE_VALUE) + return false; + + // Creating a set of bitmaps corresponding to the icon images we'll end up + // storing in the icon file. Each bitmap is created by resizing the given + // bitmap to the desired size. + std::vector<SkBitmap> bitmaps; + CreateResizedBitmapSet(bitmap, &bitmaps); + DCHECK(!bitmaps.empty()); + size_t bitmap_count = bitmaps.size(); + + // Computing the total size of the buffer we need in order to store the + // images in the desired icon format. + size_t buffer_size = ComputeIconFileBufferSize(bitmaps); + unsigned char* buffer = new unsigned char[buffer_size]; + DCHECK(buffer != NULL); + memset(buffer, 0, buffer_size); + + // Setting the information in the structures residing within the buffer. + // First, we set the information which doesn't require iterating through the + // bitmap set and then we set the bitmap specific structures. In the latter + // step we also copy the actual bits. + ICONDIR* icon_dir = reinterpret_cast<ICONDIR*>(buffer); + icon_dir->idType = kResourceTypeIcon; + icon_dir->idCount = bitmap_count; + size_t icon_dir_count = bitmap_count - 1; // Note DCHECK(!bitmaps.empty())! + size_t offset = sizeof(ICONDIR) + (sizeof(ICONDIRENTRY) * icon_dir_count); + for (size_t i = 0; i < bitmap_count; i++) { + ICONIMAGE* image = reinterpret_cast<ICONIMAGE*>(buffer + offset); + DCHECK_LT(offset, buffer_size); + size_t icon_image_size = 0; + SetSingleIconImageInformation(bitmaps[i], i, icon_dir, image, offset, + &icon_image_size); + DCHECK_GT(icon_image_size, 0U); + offset += icon_image_size; + } + DCHECK_EQ(offset, buffer_size); + + // Finally, writing the data info the file. + DWORD bytes_written; + bool delete_file = false; + if (!WriteFile(icon_file.Get(), buffer, buffer_size, &bytes_written, NULL) || + bytes_written != buffer_size) + delete_file = true; + + ::CloseHandle(icon_file.Take()); + delete [] buffer; + if (delete_file) { + bool success = file_util::Delete(icon_path, false); + DCHECK(success); + } + + return !delete_file; +} + +bool IconUtil::PixelsHaveAlpha(const uint32* pixels, size_t num_pixels) { + for (const uint32* end = pixels + num_pixels; pixels != end; ++pixels) { + if ((*pixels & 0xff000000) != 0) + return true; + } + + return false; +} + +void IconUtil::InitializeBitmapHeader(BITMAPV5HEADER* header, int width, + int height) { + DCHECK(header); + memset(header, 0, sizeof(BITMAPV5HEADER)); + header->bV5Size = sizeof(BITMAPV5HEADER); + + // Note that icons are created using top-down DIBs so we must negate the + // value used for the icon's height. + header->bV5Width = width; + header->bV5Height = -height; + header->bV5Planes = 1; + header->bV5Compression = BI_RGB; + + // Initializing the bitmap format to 32 bit ARGB. + header->bV5BitCount = 32; + header->bV5RedMask = 0x00FF0000; + header->bV5GreenMask = 0x0000FF00; + header->bV5BlueMask = 0x000000FF; + header->bV5AlphaMask = 0xFF000000; + + // Use the system color space. The default value is LCS_CALIBRATED_RGB, which + // causes us to crash if we don't specify the approprite gammas, etc. See + // <http://msdn.microsoft.com/en-us/library/ms536531(VS.85).aspx> and + // <http://b/1283121>. + header->bV5CSType = LCS_WINDOWS_COLOR_SPACE; + + // Use a valid value for bV5Intent as 0 is not a valid one. + // <http://msdn.microsoft.com/en-us/library/dd183381(VS.85).aspx> + header->bV5Intent = LCS_GM_IMAGES; +} + +void IconUtil::SetSingleIconImageInformation(const SkBitmap& bitmap, + size_t index, + ICONDIR* icon_dir, + ICONIMAGE* icon_image, + size_t image_offset, + size_t* image_byte_count) { + DCHECK(icon_dir != NULL); + DCHECK(icon_image != NULL); + DCHECK_GT(image_offset, 0U); + DCHECK(image_byte_count != NULL); + + // We start by computing certain image values we'll use later on. + size_t xor_mask_size, bytes_in_resource; + ComputeBitmapSizeComponents(bitmap, + &xor_mask_size, + &bytes_in_resource); + + icon_dir->idEntries[index].bWidth = static_cast<BYTE>(bitmap.width()); + icon_dir->idEntries[index].bHeight = static_cast<BYTE>(bitmap.height()); + icon_dir->idEntries[index].wPlanes = 1; + icon_dir->idEntries[index].wBitCount = 32; + icon_dir->idEntries[index].dwBytesInRes = bytes_in_resource; + icon_dir->idEntries[index].dwImageOffset = image_offset; + icon_image->icHeader.biSize = sizeof(BITMAPINFOHEADER); + + // The width field in the BITMAPINFOHEADER structure accounts for the height + // of both the AND mask and the XOR mask so we need to multiply the bitmap's + // height by 2. The same does NOT apply to the width field. + icon_image->icHeader.biHeight = bitmap.height() * 2; + icon_image->icHeader.biWidth = bitmap.width(); + icon_image->icHeader.biPlanes = 1; + icon_image->icHeader.biBitCount = 32; + + // We use a helper function for copying to actual bits from the SkBitmap + // object into the appropriate space in the buffer. We use a helper function + // (rather than just copying the bits) because there is no way to specify the + // orientation (bottom-up vs. top-down) of a bitmap residing in a .ico file. + // Thus, if we just copy the bits, we'll end up with a bottom up bitmap in + // the .ico file which will result in the icon being displayed upside down. + // The helper function copies the image into the buffer one scanline at a + // time. + // + // Note that we don't need to initialize the AND mask since the memory + // allocated for the icon data buffer was initialized to zero. The icon we + // create will therefore use an AND mask containing only zeros, which is OK + // because the underlying image has an alpha channel. An AND mask containing + // only zeros essentially means we'll initially treat all the pixels as + // opaque. + unsigned char* image_addr = reinterpret_cast<unsigned char*>(icon_image); + unsigned char* xor_mask_addr = image_addr + sizeof(BITMAPINFOHEADER); + CopySkBitmapBitsIntoIconBuffer(bitmap, xor_mask_addr, xor_mask_size); + *image_byte_count = bytes_in_resource; +} + +void IconUtil::CopySkBitmapBitsIntoIconBuffer(const SkBitmap& bitmap, + unsigned char* buffer, + size_t buffer_size) { + SkAutoLockPixels bitmap_lock(bitmap); + unsigned char* bitmap_ptr = static_cast<unsigned char*>(bitmap.getPixels()); + size_t bitmap_size = bitmap.height() * bitmap.width() * 4; + DCHECK_EQ(buffer_size, bitmap_size); + for (size_t i = 0; i < bitmap_size; i += bitmap.width() * 4) { + memcpy(buffer + bitmap_size - bitmap.width() * 4 - i, + bitmap_ptr + i, + bitmap.width() * 4); + } +} + +void IconUtil::CreateResizedBitmapSet(const SkBitmap& bitmap_to_resize, + std::vector<SkBitmap>* bitmaps) { + DCHECK(bitmaps != NULL); + DCHECK(bitmaps->empty()); + + bool inserted_original_bitmap = false; + for (size_t i = 0; i < arraysize(icon_dimensions_); i++) { + // If the dimensions of the bitmap we are resizing are the same as the + // current dimensions, then we should insert the bitmap and not a resized + // bitmap. If the bitmap's dimensions are smaller, we insert our bitmap + // first so that the bitmaps we return in the vector are sorted based on + // their dimensions. + if (!inserted_original_bitmap) { + if ((bitmap_to_resize.width() == icon_dimensions_[i]) && + (bitmap_to_resize.height() == icon_dimensions_[i])) { + bitmaps->push_back(bitmap_to_resize); + inserted_original_bitmap = true; + continue; + } + + if ((bitmap_to_resize.width() < icon_dimensions_[i]) && + (bitmap_to_resize.height() < icon_dimensions_[i])) { + bitmaps->push_back(bitmap_to_resize); + inserted_original_bitmap = true; + } + } + bitmaps->push_back(skia::ImageOperations::Resize( + bitmap_to_resize, skia::ImageOperations::RESIZE_LANCZOS3, + icon_dimensions_[i], icon_dimensions_[i])); + } + + if (!inserted_original_bitmap) + bitmaps->push_back(bitmap_to_resize); +} + +size_t IconUtil::ComputeIconFileBufferSize(const std::vector<SkBitmap>& set) { + DCHECK(!set.empty()); + + // We start by counting the bytes for the structures that don't depend on the + // number of icon images. Note that sizeof(ICONDIR) already accounts for a + // single ICONDIRENTRY structure, which is why we subtract one from the + // number of bitmaps. + size_t total_buffer_size = sizeof(ICONDIR); + size_t bitmap_count = set.size(); + total_buffer_size += sizeof(ICONDIRENTRY) * (bitmap_count - 1); + DCHECK_GE(bitmap_count, arraysize(icon_dimensions_)); + + // Add the bitmap specific structure sizes. + for (size_t i = 0; i < bitmap_count; i++) { + size_t xor_mask_size, bytes_in_resource; + ComputeBitmapSizeComponents(set[i], + &xor_mask_size, + &bytes_in_resource); + total_buffer_size += bytes_in_resource; + } + return total_buffer_size; +} + +void IconUtil::ComputeBitmapSizeComponents(const SkBitmap& bitmap, + size_t* xor_mask_size, + size_t* bytes_in_resource) { + // The XOR mask size is easy to calculate since we only deal with 32bpp + // images. + *xor_mask_size = bitmap.width() * bitmap.height() * 4; + + // Computing the AND mask is a little trickier since it is a monochrome + // bitmap (regardless of the number of bits per pixels used in the XOR mask). + // There are two things we must make sure we do when computing the AND mask + // size: + // + // 1. Make sure the right number of bytes is allocated for each AND mask + // scan line in case the number of pixels in the image is not divisible by + // 8. For example, in a 15X15 image, 15 / 8 is one byte short of + // containing the number of bits we need in order to describe a single + // image scan line so we need to add a byte. Thus, we need 2 bytes instead + // of 1 for each scan line. + // + // 2. Make sure each scan line in the AND mask is 4 byte aligned (so that the + // total icon image has a 4 byte alignment). In the 15X15 image example + // above, we can not use 2 bytes so we increase it to the next multiple of + // 4 which is 4. + // + // Once we compute the size for a singe AND mask scan line, we multiply that + // number by the image height in order to get the total number of bytes for + // the AND mask. Thus, for a 15X15 image, we need 15 * 4 which is 60 bytes + // for the monochrome bitmap representing the AND mask. + size_t and_line_length = (bitmap.width() + 7) >> 3; + and_line_length = (and_line_length + 3) & ~3; + size_t and_mask_size = and_line_length * bitmap.height(); + size_t masks_size = *xor_mask_size + and_mask_size; + *bytes_in_resource = masks_size + sizeof(BITMAPINFOHEADER); +} diff --git a/ui/gfx/icon_util.h b/ui/gfx/icon_util.h new file mode 100644 index 0000000..ae23601 --- /dev/null +++ b/ui/gfx/icon_util.h @@ -0,0 +1,194 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_ICON_UTIL_H_ +#define UI_GFX_ICON_UTIL_H_ +#pragma once + +#include <windows.h> +#include <string> +#include <vector> +#include "base/basictypes.h" + +namespace gfx { +class Size; +} +class FilePath; +class SkBitmap; + +/////////////////////////////////////////////////////////////////////////////// +// +// The IconUtil class contains helper functions for manipulating Windows icons. +// The class interface contains methods for converting an HICON handle into an +// SkBitmap object and vice versa. The class can also create a .ico file given +// a PNG image contained in an SkBitmap object. The following code snippet +// shows an example usage of IconUtil::CreateHICONFromSkBitmap(): +// +// SkBitmap bitmap; +// +// // Fill |bitmap| with valid data +// bitmap.setConfig(...); +// bitmap.allocPixels(); +// +// ... +// +// // Convert the bitmap into a Windows HICON +// HICON icon = IconUtil::CreateHICONFromSkBitmap(bitmap); +// if (icon == NULL) { +// // Handle error +// ... +// } +// +// // Use the icon with a WM_SETICON message +// ::SendMessage(hwnd, WM_SETICON, static_cast<WPARAM>(ICON_BIG), +// reinterpret_cast<LPARAM>(icon)); +// +// // Destroy the icon when we are done +// ::DestroyIcon(icon); +// +/////////////////////////////////////////////////////////////////////////////// +class IconUtil { + public: + // Given an SkBitmap object, the function converts the bitmap to a Windows + // icon and returns the corresponding HICON handle. If the function cannot + // convert the bitmap, NULL is returned. + // + // The client is responsible for destroying the icon when it is no longer + // needed by calling ::DestroyIcon(). + static HICON CreateHICONFromSkBitmap(const SkBitmap& bitmap); + + // Given a valid HICON handle representing an icon, this function converts + // the icon into an SkBitmap object containing an ARGB bitmap using the + // dimensions specified in |s|. |s| must specify valid dimensions (both + // width() an height() must be greater than zero). If the function cannot + // convert the icon to a bitmap (most probably due to an invalid parameter), + // the return value is NULL. + // + // The client owns the returned bitmap object and is responsible for deleting + // it when it is no longer needed. + static SkBitmap* CreateSkBitmapFromHICON(HICON icon, const gfx::Size& s); + + // Given an initialized SkBitmap object and a file name, this function + // creates a .ico file with the given name using the provided bitmap. The + // icon file is created with multiple icon images of varying predefined + // dimensions because Windows uses different image sizes when loading icons, + // depending on where the icon is drawn (ALT+TAB window, desktop shortcut, + // Quick Launch, etc.). |icon_file_name| needs to specify the full path for + // the desired .ico file. + // + // The function returns true on success and false otherwise. + static bool CreateIconFileFromSkBitmap(const SkBitmap& bitmap, + const FilePath& icon_path); + + private: + // The icon format is published in the MSDN but there is no definition of + // the icon file structures in any of the Windows header files so we need to + // define these structure within the class. We must make sure we use 2 byte + // packing so that the structures are layed out properly within the file. +#pragma pack(push) +#pragma pack(2) + + // ICONDIRENTRY contains meta data for an individual icon image within a + // .ico file. + struct ICONDIRENTRY { + BYTE bWidth; + BYTE bHeight; + BYTE bColorCount; + BYTE bReserved; + WORD wPlanes; + WORD wBitCount; + DWORD dwBytesInRes; + DWORD dwImageOffset; + }; + + // ICONDIR Contains information about all the icon images contained within a + // single .ico file. + struct ICONDIR { + WORD idReserved; + WORD idType; + WORD idCount; + ICONDIRENTRY idEntries[1]; + }; + + // Contains the actual icon image. + struct ICONIMAGE { + BITMAPINFOHEADER icHeader; + RGBQUAD icColors[1]; + BYTE icXOR[1]; + BYTE icAND[1]; + }; +#pragma pack(pop) + + // Used for indicating that the .ico contains an icon (rather than a cursor) + // image. This value is set in the |idType| field of the ICONDIR structure. + static const int kResourceTypeIcon = 1; + + // The dimensions of the icon images we insert into the .ico file. + static const int icon_dimensions_[]; + + // Returns true if any pixel in the given pixels buffer has an non-zero alpha. + static bool PixelsHaveAlpha(const uint32* pixels, size_t num_pixels); + + // A helper function that initializes a BITMAPV5HEADER structure with a set + // of values. + static void InitializeBitmapHeader(BITMAPV5HEADER* header, int width, + int height); + + // Given a single SkBitmap object and pointers to the corresponding icon + // structures within the icon data buffer, this function sets the image + // information (dimensions, color depth, etc.) in the icon structures and + // also copies the underlying icon image into the appropriate location. + // + // The function will set the data pointed to by |image_byte_count| with the + // number of image bytes written to the buffer. Note that the number of bytes + // includes only the image data written into the memory pointed to by + // |icon_image|. + static void SetSingleIconImageInformation(const SkBitmap& bitmap, + size_t index, + ICONDIR* icon_dir, + ICONIMAGE* icon_image, + size_t image_offset, + size_t* image_byte_count); + + // Copies the bits of an SkBitmap object into a buffer holding the bits of + // the corresponding image for an icon within the .ico file. + static void CopySkBitmapBitsIntoIconBuffer(const SkBitmap& bitmap, + unsigned char* buffer, + size_t buffer_size); + + // Given a single bitmap, this function creates a set of bitmaps with + // specific dimensions by resizing the given bitmap to the appropriate sizes. + static void CreateResizedBitmapSet(const SkBitmap& bitmap_to_resize, + std::vector<SkBitmap>* bitmaps); + + // Given a set of bitmaps with varying dimensions, this function computes + // the amount of memory needed in order to store the bitmaps as image icons + // in a .ico file. + static size_t ComputeIconFileBufferSize(const std::vector<SkBitmap>& set); + + // A helper function for computing various size components of a given bitmap. + // The different sizes can be used within the various .ico file structures. + // + // |xor_mask_size| - the size, in bytes, of the XOR mask in the ICONIMAGE + // structure. + // |and_mask_size| - the size, in bytes, of the AND mask in the ICONIMAGE + // structure. + // |bytes_in_resource| - the total number of bytes set in the ICONIMAGE + // structure. This value is equal to the sum of the + // bytes in the AND mask and the XOR mask plus the size + // of the BITMAPINFOHEADER structure. Note that since + // only 32bpp are handled by the IconUtil class, the + // icColors field in the ICONIMAGE structure is ignored + // and is not accounted for when computing the + // different size components. + static void ComputeBitmapSizeComponents(const SkBitmap& bitmap, + size_t* xor_mask_size, + size_t* bytes_in_resource); + + // Prevent clients from instantiating objects of that class by declaring the + // ctor/dtor as private. + DISALLOW_IMPLICIT_CONSTRUCTORS(IconUtil); +}; + +#endif // UI_GFX_ICON_UTIL_H_ diff --git a/ui/gfx/icon_util_unittest.cc b/ui/gfx/icon_util_unittest.cc new file mode 100644 index 0000000..43eca49 --- /dev/null +++ b/ui/gfx/icon_util_unittest.cc @@ -0,0 +1,256 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/file_util.h" +#include "base/path_service.h" +#include "base/scoped_ptr.h" +#include "gfx/gfx_paths.h" +#include "gfx/icon_util.h" +#include "gfx/size.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "third_party/skia/include/core/SkBitmap.h" + +namespace { + +static const char kSmallIconName[] = "icon_util/16_X_16_icon.ico"; +static const char kLargeIconName[] = "icon_util/128_X_128_icon.ico"; +static const char kTempIconFilename[] = "temp_test_icon.ico"; + +class IconUtilTest : public testing::Test { + public: + IconUtilTest() { + PathService::Get(gfx::DIR_TEST_DATA, &test_data_directory_); + } + ~IconUtilTest() {} + + static const int kSmallIconWidth = 16; + static const int kSmallIconHeight = 16; + static const int kLargeIconWidth = 128; + static const int kLargeIconHeight = 128; + + // Given a file name for an .ico file and an image dimentions, this + // function loads the icon and returns an HICON handle. + HICON LoadIconFromFile(const FilePath& filename, int width, int height) { + HICON icon = static_cast<HICON>(LoadImage(NULL, + filename.value().c_str(), + IMAGE_ICON, + width, + height, + LR_LOADTRANSPARENT | LR_LOADFROMFILE)); + return icon; + } + + protected: + // The root directory for test files. + FilePath test_data_directory_; + + private: + DISALLOW_COPY_AND_ASSIGN(IconUtilTest); +}; + +} // namespace + +// The following test case makes sure IconUtil::SkBitmapFromHICON fails +// gracefully when called with invalid input parameters. +TEST_F(IconUtilTest, TestIconToBitmapInvalidParameters) { + FilePath icon_filename = test_data_directory_.AppendASCII(kSmallIconName); + gfx::Size icon_size(kSmallIconWidth, kSmallIconHeight); + HICON icon = LoadIconFromFile(icon_filename, + icon_size.width(), + icon_size.height()); + ASSERT_TRUE(icon != NULL); + + // Invalid size parameter. + gfx::Size invalid_icon_size(kSmallIconHeight, 0); + EXPECT_EQ(IconUtil::CreateSkBitmapFromHICON(icon, invalid_icon_size), + static_cast<SkBitmap*>(NULL)); + + // Invalid icon. + EXPECT_EQ(IconUtil::CreateSkBitmapFromHICON(NULL, icon_size), + static_cast<SkBitmap*>(NULL)); + + // The following code should succeed. + scoped_ptr<SkBitmap> bitmap; + bitmap.reset(IconUtil::CreateSkBitmapFromHICON(icon, icon_size)); + EXPECT_NE(bitmap.get(), static_cast<SkBitmap*>(NULL)); + ::DestroyIcon(icon); +} + +// The following test case makes sure IconUtil::CreateHICONFromSkBitmap fails +// gracefully when called with invalid input parameters. +TEST_F(IconUtilTest, TestBitmapToIconInvalidParameters) { + HICON icon = NULL; + scoped_ptr<SkBitmap> bitmap; + + // Wrong bitmap format. + bitmap.reset(new SkBitmap); + ASSERT_NE(bitmap.get(), static_cast<SkBitmap*>(NULL)); + bitmap->setConfig(SkBitmap::kA8_Config, kSmallIconWidth, kSmallIconHeight); + icon = IconUtil::CreateHICONFromSkBitmap(*bitmap); + EXPECT_EQ(icon, static_cast<HICON>(NULL)); + + // Invalid bitmap size. + bitmap.reset(new SkBitmap); + ASSERT_NE(bitmap.get(), static_cast<SkBitmap*>(NULL)); + bitmap->setConfig(SkBitmap::kARGB_8888_Config, 0, 0); + icon = IconUtil::CreateHICONFromSkBitmap(*bitmap); + EXPECT_EQ(icon, static_cast<HICON>(NULL)); + + // Valid bitmap configuration but no pixels allocated. + bitmap.reset(new SkBitmap); + ASSERT_NE(bitmap.get(), static_cast<SkBitmap*>(NULL)); + bitmap->setConfig(SkBitmap::kARGB_8888_Config, + kSmallIconWidth, + kSmallIconHeight); + icon = IconUtil::CreateHICONFromSkBitmap(*bitmap); + EXPECT_TRUE(icon == NULL); +} + +// The following test case makes sure IconUtil::CreateIconFileFromSkBitmap +// fails gracefully when called with invalid input parameters. +TEST_F(IconUtilTest, TestCreateIconFileInvalidParameters) { + scoped_ptr<SkBitmap> bitmap; + FilePath valid_icon_filename = test_data_directory_.AppendASCII( + kSmallIconName); + FilePath invalid_icon_filename(FILE_PATH_LITERAL("C:\\<>?.ico")); + + // Wrong bitmap format. + bitmap.reset(new SkBitmap); + ASSERT_NE(bitmap.get(), static_cast<SkBitmap*>(NULL)); + bitmap->setConfig(SkBitmap::kA8_Config, kSmallIconWidth, kSmallIconHeight); + EXPECT_FALSE(IconUtil::CreateIconFileFromSkBitmap(*bitmap, + valid_icon_filename)); + + // Invalid bitmap size. + bitmap.reset(new SkBitmap); + ASSERT_NE(bitmap.get(), static_cast<SkBitmap*>(NULL)); + bitmap->setConfig(SkBitmap::kARGB_8888_Config, 0, 0); + EXPECT_FALSE(IconUtil::CreateIconFileFromSkBitmap(*bitmap, + valid_icon_filename)); + + // Bitmap with no allocated pixels. + bitmap.reset(new SkBitmap); + ASSERT_NE(bitmap.get(), static_cast<SkBitmap*>(NULL)); + bitmap->setConfig(SkBitmap::kARGB_8888_Config, + kSmallIconWidth, + kSmallIconHeight); + EXPECT_FALSE(IconUtil::CreateIconFileFromSkBitmap(*bitmap, + valid_icon_filename)); + + // Invalid file name. + bitmap->allocPixels(); + // Setting the pixels to black. + memset(bitmap->getPixels(), 0, bitmap->width() * bitmap->height() * 4); + EXPECT_FALSE(IconUtil::CreateIconFileFromSkBitmap(*bitmap, + invalid_icon_filename)); +} + +// This test case makes sure that when we load an icon from disk and convert +// the HICON into a bitmap, the bitmap has the expected format and dimentions. +TEST_F(IconUtilTest, TestCreateSkBitmapFromHICON) { + scoped_ptr<SkBitmap> bitmap; + FilePath small_icon_filename = test_data_directory_.AppendASCII( + kSmallIconName); + gfx::Size small_icon_size(kSmallIconWidth, kSmallIconHeight); + HICON small_icon = LoadIconFromFile(small_icon_filename, + small_icon_size.width(), + small_icon_size.height()); + ASSERT_NE(small_icon, static_cast<HICON>(NULL)); + bitmap.reset(IconUtil::CreateSkBitmapFromHICON(small_icon, small_icon_size)); + ASSERT_NE(bitmap.get(), static_cast<SkBitmap*>(NULL)); + EXPECT_EQ(bitmap->width(), small_icon_size.width()); + EXPECT_EQ(bitmap->height(), small_icon_size.height()); + EXPECT_EQ(bitmap->config(), SkBitmap::kARGB_8888_Config); + ::DestroyIcon(small_icon); + + FilePath large_icon_filename = test_data_directory_.AppendASCII( + kLargeIconName); + gfx::Size large_icon_size(kLargeIconWidth, kLargeIconHeight); + HICON large_icon = LoadIconFromFile(large_icon_filename, + large_icon_size.width(), + large_icon_size.height()); + ASSERT_NE(large_icon, static_cast<HICON>(NULL)); + bitmap.reset(IconUtil::CreateSkBitmapFromHICON(large_icon, large_icon_size)); + ASSERT_NE(bitmap.get(), static_cast<SkBitmap*>(NULL)); + EXPECT_EQ(bitmap->width(), large_icon_size.width()); + EXPECT_EQ(bitmap->height(), large_icon_size.height()); + EXPECT_EQ(bitmap->config(), SkBitmap::kARGB_8888_Config); + ::DestroyIcon(large_icon); +} + +// This test case makes sure that when an HICON is created from an SkBitmap, +// the returned handle is valid and refers to an icon with the expected +// dimentions color depth etc. +TEST_F(IconUtilTest, TestBasicCreateHICONFromSkBitmap) { + scoped_ptr<SkBitmap> bitmap; + bitmap.reset(new SkBitmap); + ASSERT_NE(bitmap.get(), static_cast<SkBitmap*>(NULL)); + bitmap->setConfig(SkBitmap::kARGB_8888_Config, + kSmallIconWidth, + kSmallIconHeight); + bitmap->allocPixels(); + HICON icon = IconUtil::CreateHICONFromSkBitmap(*bitmap); + EXPECT_NE(icon, static_cast<HICON>(NULL)); + ICONINFO icon_info; + ASSERT_TRUE(::GetIconInfo(icon, &icon_info)); + EXPECT_TRUE(icon_info.fIcon); + + // Now that have the icon information, we should obtain the specification of + // the icon's bitmap and make sure it matches the specification of the + // SkBitmap we started with. + // + // The bitmap handle contained in the icon information is a handle to a + // compatible bitmap so we need to call ::GetDIBits() in order to retrieve + // the bitmap's header information. + BITMAPINFO bitmap_info; + ::ZeroMemory(&bitmap_info, sizeof(BITMAPINFO)); + bitmap_info.bmiHeader.biSize = sizeof(BITMAPINFO); + HDC hdc = ::GetDC(NULL); + int result = ::GetDIBits(hdc, + icon_info.hbmColor, + 0, + kSmallIconWidth, + NULL, + &bitmap_info, + DIB_RGB_COLORS); + ASSERT_GT(result, 0); + EXPECT_EQ(bitmap_info.bmiHeader.biWidth, kSmallIconWidth); + EXPECT_EQ(bitmap_info.bmiHeader.biHeight, kSmallIconHeight); + EXPECT_EQ(bitmap_info.bmiHeader.biPlanes, 1); + EXPECT_EQ(bitmap_info.bmiHeader.biBitCount, 32); + ::ReleaseDC(NULL, hdc); + ::DestroyIcon(icon); +} + +// The following test case makes sure IconUtil::CreateIconFileFromSkBitmap +// creates a valid .ico file given an SkBitmap. +TEST_F(IconUtilTest, TestCreateIconFile) { + scoped_ptr<SkBitmap> bitmap; + FilePath icon_filename = test_data_directory_.AppendASCII(kTempIconFilename); + + // Allocating the bitmap. + bitmap.reset(new SkBitmap); + ASSERT_NE(bitmap.get(), static_cast<SkBitmap*>(NULL)); + bitmap->setConfig(SkBitmap::kARGB_8888_Config, + kSmallIconWidth, + kSmallIconHeight); + bitmap->allocPixels(); + + // Setting the pixels to black. + memset(bitmap->getPixels(), 0, bitmap->width() * bitmap->height() * 4); + + EXPECT_TRUE(IconUtil::CreateIconFileFromSkBitmap(*bitmap, + icon_filename)); + + // We are currently only testing that it is possible to load an icon from + // the .ico file we just created. We don't really check the additional icon + // images created by IconUtil::CreateIconFileFromSkBitmap. + HICON icon = LoadIconFromFile(icon_filename, + kSmallIconWidth, + kSmallIconHeight); + EXPECT_NE(icon, static_cast<HICON>(NULL)); + if (icon != NULL) { + ::DestroyIcon(icon); + } +} diff --git a/ui/gfx/insets.cc b/ui/gfx/insets.cc new file mode 100644 index 0000000..06cc6aa --- /dev/null +++ b/ui/gfx/insets.cc @@ -0,0 +1,16 @@ +// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "gfx/insets.h" + +#include "base/string_util.h" + +namespace gfx { + +std::string Insets::ToString() const { + // Print members in the same order of the constructor parameters. + return StringPrintf("%d,%d,%d,%d", top_, left_, bottom_, right_); +} + +} // namespace gfx diff --git a/ui/gfx/insets.h b/ui/gfx/insets.h new file mode 100644 index 0000000..31909b4 --- /dev/null +++ b/ui/gfx/insets.h @@ -0,0 +1,94 @@ +// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_INSETS_H_ +#define UI_GFX_INSETS_H_ +#pragma once + +#include "build/build_config.h" + +#if defined(OS_POSIX) && !defined(OS_MACOSX) +#include <gtk/gtkstyle.h> +#endif + +#include <string> + +namespace gfx { + +// +// An insets represents the borders of a container (the space the container must +// leave at each of its edges). +// + +class Insets { + public: + Insets() : top_(0), left_(0), bottom_(0), right_(0) {} + Insets(int top, int left, int bottom, int right) + : top_(top), + left_(left), + bottom_(bottom), + right_(right) {} +#if defined(OS_POSIX) && !defined(OS_MACOSX) + explicit Insets(const GtkBorder& border) + : top_(border.top), + left_(border.left), + bottom_(border.bottom), + right_(border.right) {} +#endif + + ~Insets() {} + + int top() const { return top_; } + int left() const { return left_; } + int bottom() const { return bottom_; } + int right() const { return right_; } + + // Returns the total width taken up by the insets, which is the sum of the + // left and right insets. + int width() const { return left_ + right_; } + + // Returns the total height taken up by the insets, which is the sum of the + // top and bottom insets. + int height() const { return top_ + bottom_; } + + // Returns true if the insets are empty. + bool empty() const { return width() == 0 && height() == 0; } + + void Set(int top, int left, int bottom, int right) { + top_ = top; + left_ = left; + bottom_ = bottom; + right_ = right; + } + + bool operator==(const Insets& insets) const { + return top_ == insets.top_ && left_ == insets.left_ && + bottom_ == insets.bottom_ && right_ == insets.right_; + } + + bool operator!=(const Insets& insets) const { + return !(*this == insets); + } + + Insets& operator+=(const Insets& insets) { + top_ += insets.top_; + left_ += insets.left_; + bottom_ += insets.bottom_; + right_ += insets.right_; + return *this; + } + + // Returns a string representation of the insets. + std::string ToString() const; + + private: + int top_; + int left_; + int bottom_; + int right_; +}; + +} // namespace gfx + +#endif // UI_GFX_INSETS_H_ diff --git a/ui/gfx/insets_unittest.cc b/ui/gfx/insets_unittest.cc new file mode 100644 index 0000000..30d6d7f --- /dev/null +++ b/ui/gfx/insets_unittest.cc @@ -0,0 +1,66 @@ +// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "gfx/insets.h" + +#include "testing/gtest/include/gtest/gtest.h" + +TEST(InsetsTest, InsetsDefault) { + gfx::Insets insets; + EXPECT_EQ(0, insets.top()); + EXPECT_EQ(0, insets.left()); + EXPECT_EQ(0, insets.bottom()); + EXPECT_EQ(0, insets.right()); + EXPECT_EQ(0, insets.width()); + EXPECT_EQ(0, insets.height()); + EXPECT_TRUE(insets.empty()); +} + +TEST(InsetsTest, Insets) { + gfx::Insets insets(1, 2, 3, 4); + EXPECT_EQ(1, insets.top()); + EXPECT_EQ(2, insets.left()); + EXPECT_EQ(3, insets.bottom()); + EXPECT_EQ(4, insets.right()); + EXPECT_EQ(6, insets.width()); // Left + right. + EXPECT_EQ(4, insets.height()); // Top + bottom. + EXPECT_FALSE(insets.empty()); +} + +TEST(InsetsTest, Set) { + gfx::Insets insets; + insets.Set(1, 2, 3, 4); + EXPECT_EQ(1, insets.top()); + EXPECT_EQ(2, insets.left()); + EXPECT_EQ(3, insets.bottom()); + EXPECT_EQ(4, insets.right()); +} + +TEST(InsetsTest, Add) { + gfx::Insets insets; + insets.Set(1, 2, 3, 4); + insets += gfx::Insets(5, 6, 7, 8); + EXPECT_EQ(6, insets.top()); + EXPECT_EQ(8, insets.left()); + EXPECT_EQ(10, insets.bottom()); + EXPECT_EQ(12, insets.right()); +} + +TEST(InsetsTest, Equality) { + gfx::Insets insets1; + insets1.Set(1, 2, 3, 4); + gfx::Insets insets2; + // Test operator== and operator!=. + EXPECT_FALSE(insets1 == insets2); + EXPECT_TRUE(insets1 != insets2); + + insets2.Set(1, 2, 3, 4); + EXPECT_TRUE(insets1 == insets2); + EXPECT_FALSE(insets1 != insets2); +} + +TEST(InsetsTest, ToString) { + gfx::Insets insets(1, 2, 3, 4); + EXPECT_EQ("1,2,3,4", insets.ToString()); +} diff --git a/ui/gfx/native_theme_linux.cc b/ui/gfx/native_theme_linux.cc new file mode 100644 index 0000000..b2087fe --- /dev/null +++ b/ui/gfx/native_theme_linux.cc @@ -0,0 +1,941 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "gfx/native_theme_linux.h" + +#include <limits> + +#include "base/logging.h" +#include "gfx/codec/png_codec.h" +#include "gfx/color_utils.h" +#include "gfx/gfx_module.h" +#include "gfx/size.h" +#include "gfx/rect.h" +#include "grit/gfx_resources.h" +#include "third_party/skia/include/effects/SkGradientShader.h" + +namespace gfx { + +unsigned int NativeThemeLinux::button_length_ = 14; +unsigned int NativeThemeLinux::scrollbar_width_ = 15; +unsigned int NativeThemeLinux::thumb_inactive_color_ = 0xeaeaea; +unsigned int NativeThemeLinux::thumb_active_color_ = 0xf4f4f4; +unsigned int NativeThemeLinux::track_color_ = 0xd3d3d3; + +// These are the default dimensions of radio buttons and checkboxes. +static const int kCheckboxAndRadioWidth = 13; +static const int kCheckboxAndRadioHeight = 13; + +// These sizes match the sizes in Chromium Win. +static const int kSliderThumbWidth = 11; +static const int kSliderThumbHeight = 21; + +static const SkColor kSliderTrackBackgroundColor = + SkColorSetRGB(0xe3, 0xdd, 0xd8); +static const SkColor kSliderThumbLightGrey = SkColorSetRGB(0xf4, 0xf2, 0xef); +static const SkColor kSliderThumbDarkGrey = SkColorSetRGB(0xea, 0xe5, 0xe0); +static const SkColor kSliderThumbBorderDarkGrey = + SkColorSetRGB(0x9d, 0x96, 0x8e); + +#if !defined(OS_CHROMEOS) +// Chromeos has a different look. +// static +NativeThemeLinux* NativeThemeLinux::instance() { + // The global NativeThemeLinux instance. + static NativeThemeLinux s_native_theme; + return &s_native_theme; +} +#endif + +// Get lightness adjusted color. +static SkColor BrightenColor(const color_utils::HSL& hsl, SkAlpha alpha, + double lightness_amount) { + color_utils::HSL adjusted = hsl; + adjusted.l += lightness_amount; + if (adjusted.l > 1.0) + adjusted.l = 1.0; + if (adjusted.l < 0.0) + adjusted.l = 0.0; + + return color_utils::HSLToSkColor(adjusted, alpha); +} + +static SkBitmap* GfxGetBitmapNamed(int key) { + base::StringPiece data = GfxModule::GetResource(key); + if (!data.size()) { + NOTREACHED() << "Unable to load image resource " << key; + return NULL; + } + + SkBitmap bitmap; + if (!gfx::PNGCodec::Decode( + reinterpret_cast<const unsigned char*>(data.data()), + data.size(), &bitmap)) { + NOTREACHED() << "Unable to decode image resource " << key; + return NULL; + } + + return new SkBitmap(bitmap); +} + +NativeThemeLinux::NativeThemeLinux() { +} + +NativeThemeLinux::~NativeThemeLinux() { +} + +gfx::Size NativeThemeLinux::GetPartSize(Part part) const { + switch (part) { + case kScrollbarDownArrow: + case kScrollbarUpArrow: + return gfx::Size(scrollbar_width_, button_length_); + case kScrollbarLeftArrow: + case kScrollbarRightArrow: + return gfx::Size(button_length_, scrollbar_width_); + case kScrollbarHorizontalThumb: + // This matches Firefox on Linux. + return gfx::Size(2 * scrollbar_width_, scrollbar_width_); + case kScrollbarVerticalThumb: + // This matches Firefox on Linux. + return gfx::Size(scrollbar_width_, 2 * scrollbar_width_); + break; + case kScrollbarHorizontalTrack: + return gfx::Size(0, scrollbar_width_); + case kScrollbarVerticalTrack: + return gfx::Size(scrollbar_width_, 0); + case kCheckbox: + case kRadio: + return gfx::Size(kCheckboxAndRadioWidth, kCheckboxAndRadioHeight); + case kSliderThumb: + // These sizes match the sizes in Chromium Win. + return gfx::Size(kSliderThumbWidth, kSliderThumbHeight); + case kInnerSpinButton: + return gfx::Size(scrollbar_width_, 0); + case kPushButton: + case kTextField: + case kMenuList: + case kSliderTrack: + case kProgressBar: + return gfx::Size(); // No default size. + } + return gfx::Size(); +} + +void NativeThemeLinux::PaintArrowButton( + skia::PlatformCanvas* canvas, + const gfx::Rect& rect, Part direction, State state) { + int widthMiddle, lengthMiddle; + SkPaint paint; + if (direction == kScrollbarUpArrow || direction == kScrollbarDownArrow) { + widthMiddle = rect.width() / 2 + 1; + lengthMiddle = rect.height() / 2 + 1; + } else { + lengthMiddle = rect.width() / 2 + 1; + widthMiddle = rect.height() / 2 + 1; + } + + // Calculate button color. + SkScalar trackHSV[3]; + SkColorToHSV(track_color_, trackHSV); + SkColor buttonColor = SaturateAndBrighten(trackHSV, 0, 0.2); + SkColor backgroundColor = buttonColor; + if (state == kPressed) { + SkScalar buttonHSV[3]; + SkColorToHSV(buttonColor, buttonHSV); + buttonColor = SaturateAndBrighten(buttonHSV, 0, -0.1); + } else if (state == kHovered) { + SkScalar buttonHSV[3]; + SkColorToHSV(buttonColor, buttonHSV); + buttonColor = SaturateAndBrighten(buttonHSV, 0, 0.05); + } + + SkIRect skrect; + skrect.set(rect.x(), rect.y(), rect.x() + rect.width(), rect.y() + + rect.height()); + // Paint the background (the area visible behind the rounded corners). + paint.setColor(backgroundColor); + canvas->drawIRect(skrect, paint); + + // Paint the button's outline and fill the middle + SkPath outline; + switch (direction) { + case kScrollbarUpArrow: + outline.moveTo(rect.x() + 0.5, rect.y() + rect.height() + 0.5); + outline.rLineTo(0, -(rect.height() - 2)); + outline.rLineTo(2, -2); + outline.rLineTo(rect.width() - 5, 0); + outline.rLineTo(2, 2); + outline.rLineTo(0, rect.height() - 2); + break; + case kScrollbarDownArrow: + outline.moveTo(rect.x() + 0.5, rect.y() - 0.5); + outline.rLineTo(0, rect.height() - 2); + outline.rLineTo(2, 2); + outline.rLineTo(rect.width() - 5, 0); + outline.rLineTo(2, -2); + outline.rLineTo(0, -(rect.height() - 2)); + break; + case kScrollbarRightArrow: + outline.moveTo(rect.x() - 0.5, rect.y() + 0.5); + outline.rLineTo(rect.width() - 2, 0); + outline.rLineTo(2, 2); + outline.rLineTo(0, rect.height() - 5); + outline.rLineTo(-2, 2); + outline.rLineTo(-(rect.width() - 2), 0); + break; + case kScrollbarLeftArrow: + outline.moveTo(rect.x() + rect.width() + 0.5, rect.y() + 0.5); + outline.rLineTo(-(rect.width() - 2), 0); + outline.rLineTo(-2, 2); + outline.rLineTo(0, rect.height() - 5); + outline.rLineTo(2, 2); + outline.rLineTo(rect.width() - 2, 0); + break; + default: + break; + } + outline.close(); + + paint.setStyle(SkPaint::kFill_Style); + paint.setColor(buttonColor); + canvas->drawPath(outline, paint); + + paint.setAntiAlias(true); + paint.setStyle(SkPaint::kStroke_Style); + SkScalar thumbHSV[3]; + SkColorToHSV(thumb_inactive_color_, thumbHSV); + paint.setColor(OutlineColor(trackHSV, thumbHSV)); + canvas->drawPath(outline, paint); + + // If the button is disabled or read-only, the arrow is drawn with the + // outline color. + if (state != kDisabled) + paint.setColor(SK_ColorBLACK); + + paint.setAntiAlias(false); + paint.setStyle(SkPaint::kFill_Style); + + SkPath path; + // The constants in this block of code are hand-tailored to produce good + // looking arrows without anti-aliasing. + switch (direction) { + case kScrollbarUpArrow: + path.moveTo(rect.x() + widthMiddle - 4, rect.y() + lengthMiddle + 2); + path.rLineTo(7, 0); + path.rLineTo(-4, -4); + break; + case kScrollbarDownArrow: + path.moveTo(rect.x() + widthMiddle - 4, rect.y() + lengthMiddle - 3); + path.rLineTo(7, 0); + path.rLineTo(-4, 4); + break; + case kScrollbarRightArrow: + path.moveTo(rect.x() + lengthMiddle - 3, rect.y() + widthMiddle - 4); + path.rLineTo(0, 7); + path.rLineTo(4, -4); + break; + case kScrollbarLeftArrow: + path.moveTo(rect.x() + lengthMiddle + 1, rect.y() + widthMiddle - 5); + path.rLineTo(0, 9); + path.rLineTo(-4, -4); + break; + default: + break; + } + path.close(); + + canvas->drawPath(path, paint); +} + +void NativeThemeLinux::Paint(skia::PlatformCanvas* canvas, + Part part, + State state, + const gfx::Rect& rect, + const ExtraParams& extra) { + switch (part) { + case kScrollbarDownArrow: + case kScrollbarUpArrow: + case kScrollbarLeftArrow: + case kScrollbarRightArrow: + PaintArrowButton(canvas, rect, part, state); + break; + case kScrollbarHorizontalThumb: + case kScrollbarVerticalThumb: + PaintScrollbarThumb(canvas, part, state, rect); + break; + case kScrollbarHorizontalTrack: + case kScrollbarVerticalTrack: + PaintScrollbarTrack(canvas, part, state, extra.scrollbar_track, rect); + break; + case kCheckbox: + PaintCheckbox(canvas, state, rect, extra.button); + break; + case kRadio: + PaintRadio(canvas, state, rect, extra.button); + break; + case kPushButton: + PaintButton(canvas, state, rect, extra.button); + break; + case kTextField: + PaintTextField(canvas, state, rect, extra.text_field); + break; + case kMenuList: + PaintMenuList(canvas, state, rect, extra.menu_list); + break; + case kSliderTrack: + PaintSliderTrack(canvas, state, rect, extra.slider); + break; + case kSliderThumb: + PaintSliderThumb(canvas, state, rect, extra.slider); + break; + case kInnerSpinButton: + PaintInnerSpinButton(canvas, state, rect, extra.inner_spin); + break; + case kProgressBar: + PaintProgressBar(canvas, state, rect, extra.progress_bar); + break; + } +} + +void NativeThemeLinux::PaintScrollbarTrack(skia::PlatformCanvas* canvas, + Part part, + State state, + const ScrollbarTrackExtraParams& extra_params, + const gfx::Rect& rect) { + SkPaint paint; + SkIRect skrect; + + skrect.set(rect.x(), rect.y(), rect.right(), rect.bottom()); + SkScalar track_hsv[3]; + SkColorToHSV(track_color_, track_hsv); + paint.setColor(SaturateAndBrighten(track_hsv, 0, 0)); + canvas->drawIRect(skrect, paint); + + SkScalar thumb_hsv[3]; + SkColorToHSV(thumb_inactive_color_, thumb_hsv); + + paint.setColor(OutlineColor(track_hsv, thumb_hsv)); + DrawBox(canvas, rect, paint); +} + +void NativeThemeLinux::PaintScrollbarThumb(skia::PlatformCanvas* canvas, + Part part, + State state, + const gfx::Rect& rect) { + const bool hovered = state == kHovered; + const int midx = rect.x() + rect.width() / 2; + const int midy = rect.y() + rect.height() / 2; + const bool vertical = part == kScrollbarVerticalThumb; + + SkScalar thumb[3]; + SkColorToHSV(hovered ? thumb_active_color_ : thumb_inactive_color_, thumb); + + SkPaint paint; + paint.setColor(SaturateAndBrighten(thumb, 0, 0.02)); + + SkIRect skrect; + if (vertical) + skrect.set(rect.x(), rect.y(), midx + 1, rect.y() + rect.height()); + else + skrect.set(rect.x(), rect.y(), rect.x() + rect.width(), midy + 1); + + canvas->drawIRect(skrect, paint); + + paint.setColor(SaturateAndBrighten(thumb, 0, -0.02)); + + if (vertical) { + skrect.set( + midx + 1, rect.y(), rect.x() + rect.width(), rect.y() + rect.height()); + } else { + skrect.set( + rect.x(), midy + 1, rect.x() + rect.width(), rect.y() + rect.height()); + } + + canvas->drawIRect(skrect, paint); + + SkScalar track[3]; + SkColorToHSV(track_color_, track); + paint.setColor(OutlineColor(track, thumb)); + DrawBox(canvas, rect, paint); + + if (rect.height() > 10 && rect.width() > 10) { + const int grippy_half_width = 2; + const int inter_grippy_offset = 3; + if (vertical) { + DrawHorizLine(canvas, + midx - grippy_half_width, + midx + grippy_half_width, + midy - inter_grippy_offset, + paint); + DrawHorizLine(canvas, + midx - grippy_half_width, + midx + grippy_half_width, + midy, + paint); + DrawHorizLine(canvas, + midx - grippy_half_width, + midx + grippy_half_width, + midy + inter_grippy_offset, + paint); + } else { + DrawVertLine(canvas, + midx - inter_grippy_offset, + midy - grippy_half_width, + midy + grippy_half_width, + paint); + DrawVertLine(canvas, + midx, + midy - grippy_half_width, + midy + grippy_half_width, + paint); + DrawVertLine(canvas, + midx + inter_grippy_offset, + midy - grippy_half_width, + midy + grippy_half_width, + paint); + } + } +} + +void NativeThemeLinux::PaintCheckbox(skia::PlatformCanvas* canvas, + State state, + const gfx::Rect& rect, + const ButtonExtraParams& button) { + static SkBitmap* image_disabled_indeterminate = GfxGetBitmapNamed( + IDR_LINUX_CHECKBOX_DISABLED_INDETERMINATE); + static SkBitmap* image_indeterminate = GfxGetBitmapNamed( + IDR_LINUX_CHECKBOX_INDETERMINATE); + static SkBitmap* image_disabled_on = GfxGetBitmapNamed( + IDR_LINUX_CHECKBOX_DISABLED_ON); + static SkBitmap* image_on = GfxGetBitmapNamed(IDR_LINUX_CHECKBOX_ON); + static SkBitmap* image_disabled_off = GfxGetBitmapNamed( + IDR_LINUX_CHECKBOX_DISABLED_OFF); + static SkBitmap* image_off = GfxGetBitmapNamed(IDR_LINUX_CHECKBOX_OFF); + + SkBitmap* image = NULL; + if (button.indeterminate) { + image = state == kDisabled ? image_disabled_indeterminate + : image_indeterminate; + } else if (button.checked) { + image = state == kDisabled ? image_disabled_on : image_on; + } else { + image = state == kDisabled ? image_disabled_off : image_off; + } + + gfx::Rect bounds = rect.Center(gfx::Size(image->width(), image->height())); + DrawBitmapInt(canvas, *image, 0, 0, image->width(), image->height(), + bounds.x(), bounds.y(), bounds.width(), bounds.height()); +} + +void NativeThemeLinux::PaintRadio(skia::PlatformCanvas* canvas, + State state, + const gfx::Rect& rect, + const ButtonExtraParams& button) { + static SkBitmap* image_disabled_on = GfxGetBitmapNamed( + IDR_LINUX_RADIO_DISABLED_ON); + static SkBitmap* image_on = GfxGetBitmapNamed(IDR_LINUX_RADIO_ON); + static SkBitmap* image_disabled_off = GfxGetBitmapNamed( + IDR_LINUX_RADIO_DISABLED_OFF); + static SkBitmap* image_off = GfxGetBitmapNamed(IDR_LINUX_RADIO_OFF); + + SkBitmap* image = NULL; + if (state == kDisabled) + image = button.checked ? image_disabled_on : image_disabled_off; + else + image = button.checked ? image_on : image_off; + + gfx::Rect bounds = rect.Center(gfx::Size(image->width(), image->height())); + DrawBitmapInt(canvas, *image, 0, 0, image->width(), image->height(), + bounds.x(), bounds.y(), bounds.width(), bounds.height()); +} + +void NativeThemeLinux::PaintButton(skia::PlatformCanvas* canvas, + State state, + const gfx::Rect& rect, + const ButtonExtraParams& button) { + SkPaint paint; + SkRect skrect; + const int kRight = rect.right(); + const int kBottom = rect.bottom(); + SkColor base_color = button.background_color; + + color_utils::HSL base_hsl; + color_utils::SkColorToHSL(base_color, &base_hsl); + + // Our standard gradient is from 0xdd to 0xf8. This is the amount of + // increased luminance between those values. + SkColor light_color(BrightenColor(base_hsl, SkColorGetA(base_color), 0.105)); + + // If the button is too small, fallback to drawing a single, solid color + if (rect.width() < 5 || rect.height() < 5) { + paint.setColor(base_color); + skrect.set(rect.x(), rect.y(), kRight, kBottom); + canvas->drawRect(skrect, paint); + return; + } + + const int kBorderAlpha = state == kHovered ? 0x80 : 0x55; + paint.setARGB(kBorderAlpha, 0, 0, 0); + canvas->drawLine(rect.x() + 1, rect.y(), kRight - 1, rect.y(), paint); + canvas->drawLine(kRight - 1, rect.y() + 1, kRight - 1, kBottom - 1, paint); + canvas->drawLine(rect.x() + 1, kBottom - 1, kRight - 1, kBottom - 1, paint); + canvas->drawLine(rect.x(), rect.y() + 1, rect.x(), kBottom - 1, paint); + + paint.setColor(SK_ColorBLACK); + const int kLightEnd = state == kPressed ? 1 : 0; + const int kDarkEnd = !kLightEnd; + SkPoint gradient_bounds[2]; + gradient_bounds[kLightEnd].set(SkIntToScalar(rect.x()), + SkIntToScalar(rect.y())); + gradient_bounds[kDarkEnd].set(SkIntToScalar(rect.x()), + SkIntToScalar(kBottom - 1)); + SkColor colors[2]; + colors[0] = light_color; + colors[1] = base_color; + + SkShader* shader = SkGradientShader::CreateLinear( + gradient_bounds, colors, NULL, 2, SkShader::kClamp_TileMode, NULL); + paint.setStyle(SkPaint::kFill_Style); + paint.setShader(shader); + shader->unref(); + + skrect.set(rect.x() + 1, rect.y() + 1, kRight - 1, kBottom - 1); + canvas->drawRect(skrect, paint); + + paint.setShader(NULL); + paint.setColor(BrightenColor(base_hsl, SkColorGetA(base_color), -0.0588)); + canvas->drawPoint(rect.x() + 1, rect.y() + 1, paint); + canvas->drawPoint(kRight - 2, rect.y() + 1, paint); + canvas->drawPoint(rect.x() + 1, kBottom - 2, paint); + canvas->drawPoint(kRight - 2, kBottom - 2, paint); +} + +void NativeThemeLinux::PaintTextField(skia::PlatformCanvas* canvas, + State state, + const gfx::Rect& rect, + const TextFieldExtraParams& text) { + // The following drawing code simulates the user-agent css border for + // text area and text input so that we do not break layout tests. Once we + // have decided the desired looks, we should update the code here and + // the layout test expectations. + SkRect bounds; + bounds.set(rect.x(), rect.y(), rect.right() - 1, rect.bottom() - 1); + + SkPaint fill_paint; + fill_paint.setStyle(SkPaint::kFill_Style); + fill_paint.setColor(text.background_color); + canvas->drawRect(bounds, fill_paint); + + if (text.is_text_area) { + // Draw text area border: 1px solid black + SkPaint stroke_paint; + fill_paint.setStyle(SkPaint::kStroke_Style); + fill_paint.setColor(SK_ColorBLACK); + canvas->drawRect(bounds, fill_paint); + } else { + // Draw text input and listbox inset border + // Text Input: 2px inset #eee + // Listbox: 1px inset #808080 + const SkColor kLightColor = text.is_listbox ? + SkColorSetRGB(0x80, 0x80, 0x80) : SkColorSetRGB(0xee, 0xee, 0xee); + const SkColor kDarkColor = text.is_listbox ? + SkColorSetRGB(0x2c, 0x2c, 0x2c) : SkColorSetRGB(0x9a, 0x9a, 0x9a); + const int kBorderWidth = text.is_listbox ? 1 : 2; + + SkPaint dark_paint; + dark_paint.setAntiAlias(true); + dark_paint.setStyle(SkPaint::kFill_Style); + dark_paint.setColor(kDarkColor); + + SkPaint light_paint; + light_paint.setAntiAlias(true); + light_paint.setStyle(SkPaint::kFill_Style); + light_paint.setColor(kLightColor); + + int left = rect.x(); + int top = rect.y(); + int right = rect.right(); + int bottom = rect.bottom(); + + SkPath path; + path.incReserve(4); + + // Top + path.moveTo(SkIntToScalar(left), SkIntToScalar(top)); + path.lineTo(SkIntToScalar(left + kBorderWidth), + SkIntToScalar(top + kBorderWidth)); + path.lineTo(SkIntToScalar(right - kBorderWidth), + SkIntToScalar(top + kBorderWidth)); + path.lineTo(SkIntToScalar(right), SkIntToScalar(top)); + canvas->drawPath(path, dark_paint); + + // Bottom + path.reset(); + path.moveTo(SkIntToScalar(left + kBorderWidth), + SkIntToScalar(bottom - kBorderWidth)); + path.lineTo(SkIntToScalar(left), SkIntToScalar(bottom)); + path.lineTo(SkIntToScalar(right), SkIntToScalar(bottom)); + path.lineTo(SkIntToScalar(right - kBorderWidth), + SkIntToScalar(bottom - kBorderWidth)); + canvas->drawPath(path, light_paint); + + // Left + path.reset(); + path.moveTo(SkIntToScalar(left), SkIntToScalar(top)); + path.lineTo(SkIntToScalar(left), SkIntToScalar(bottom)); + path.lineTo(SkIntToScalar(left + kBorderWidth), + SkIntToScalar(bottom - kBorderWidth)); + path.lineTo(SkIntToScalar(left + kBorderWidth), + SkIntToScalar(top + kBorderWidth)); + canvas->drawPath(path, dark_paint); + + // Right + path.reset(); + path.moveTo(SkIntToScalar(right - kBorderWidth), + SkIntToScalar(top + kBorderWidth)); + path.lineTo(SkIntToScalar(right - kBorderWidth), SkIntToScalar(bottom)); + path.lineTo(SkIntToScalar(right), SkIntToScalar(bottom)); + path.lineTo(SkIntToScalar(right), SkIntToScalar(top)); + canvas->drawPath(path, light_paint); + } +} + +void NativeThemeLinux::PaintMenuList(skia::PlatformCanvas* canvas, + State state, + const gfx::Rect& rect, + const MenuListExtraParams& menu_list) { + ButtonExtraParams button = { 0 }; + button.background_color = menu_list.background_color; + PaintButton(canvas, state, rect, button); + + SkPaint paint; + paint.setColor(SK_ColorBLACK); + paint.setAntiAlias(true); + paint.setStyle(SkPaint::kFill_Style); + + SkPath path; + path.moveTo(menu_list.arrow_x, menu_list.arrow_y - 3); + path.rLineTo(6, 0); + path.rLineTo(-3, 6); + path.close(); + canvas->drawPath(path, paint); +} + +void NativeThemeLinux::PaintSliderTrack(skia::PlatformCanvas* canvas, + State state, + const gfx::Rect& rect, + const SliderExtraParams& slider) { + const int kMidX = rect.x() + rect.width() / 2; + const int kMidY = rect.y() + rect.height() / 2; + + SkPaint paint; + paint.setColor(kSliderTrackBackgroundColor); + + SkRect skrect; + if (slider.vertical) { + skrect.set(std::max(rect.x(), kMidX - 2), + rect.y(), + std::min(rect.right(), kMidX + 2), + rect.bottom()); + } else { + skrect.set(rect.x(), + std::max(rect.y(), kMidY - 2), + rect.right(), + std::min(rect.bottom(), kMidY + 2)); + } + canvas->drawRect(skrect, paint); +} + +void NativeThemeLinux::PaintSliderThumb(skia::PlatformCanvas* canvas, + State state, + const gfx::Rect& rect, + const SliderExtraParams& slider) { + const bool hovered = (state == kHovered) || slider.in_drag; + const int kMidX = rect.x() + rect.width() / 2; + const int kMidY = rect.y() + rect.height() / 2; + + SkPaint paint; + paint.setColor(hovered ? SK_ColorWHITE : kSliderThumbLightGrey); + + SkIRect skrect; + if (slider.vertical) + skrect.set(rect.x(), rect.y(), kMidX + 1, rect.bottom()); + else + skrect.set(rect.x(), rect.y(), rect.right(), kMidY + 1); + + canvas->drawIRect(skrect, paint); + + paint.setColor(hovered ? kSliderThumbLightGrey : kSliderThumbDarkGrey); + + if (slider.vertical) + skrect.set(kMidX + 1, rect.y(), rect.right(), rect.bottom()); + else + skrect.set(rect.x(), kMidY + 1, rect.right(), rect.bottom()); + + canvas->drawIRect(skrect, paint); + + paint.setColor(kSliderThumbBorderDarkGrey); + DrawBox(canvas, rect, paint); + + if (rect.height() > 10 && rect.width() > 10) { + DrawHorizLine(canvas, kMidX - 2, kMidX + 2, kMidY, paint); + DrawHorizLine(canvas, kMidX - 2, kMidX + 2, kMidY - 3, paint); + DrawHorizLine(canvas, kMidX - 2, kMidX + 2, kMidY + 3, paint); + } +} + +void NativeThemeLinux::PaintInnerSpinButton(skia::PlatformCanvas* canvas, + State state, + const gfx::Rect& rect, + const InnerSpinButtonExtraParams& spin_button) { + if (spin_button.read_only) + state = kDisabled; + + State north_state = state; + State south_state = state; + if (spin_button.spin_up) + south_state = south_state != kDisabled ? kNormal : kDisabled; + else + north_state = north_state != kDisabled ? kNormal : kDisabled; + + gfx::Rect half = rect; + half.set_height(rect.height() / 2); + PaintArrowButton(canvas, half, kScrollbarUpArrow, north_state); + + half.set_y(rect.y() + rect.height() / 2); + PaintArrowButton(canvas, half, kScrollbarDownArrow, south_state); +} + +void NativeThemeLinux::PaintProgressBar(skia::PlatformCanvas* canvas, + State state, + const gfx::Rect& rect, + const ProgressBarExtraParams& progress_bar) { + static SkBitmap* bar_image = GfxGetBitmapNamed(IDR_PROGRESS_BAR); + static SkBitmap* value_image = GfxGetBitmapNamed(IDR_PROGRESS_VALUE); + static SkBitmap* left_border_image = GfxGetBitmapNamed( + IDR_PROGRESS_BORDER_LEFT); + static SkBitmap* right_border_image = GfxGetBitmapNamed( + IDR_PROGRESS_BORDER_RIGHT); + + double tile_scale = static_cast<double>(rect.height()) / + bar_image->height(); + + int new_tile_width = static_cast<int>(bar_image->width() * tile_scale); + double tile_scale_x = static_cast<double>(new_tile_width) / + bar_image->width(); + + DrawTiledImage(canvas, *bar_image, 0, 0, tile_scale_x, tile_scale, + rect.x(), rect.y(), rect.width(), rect.height()); + + if (progress_bar.value_rect_width) { + + new_tile_width = static_cast<int>(value_image->width() * tile_scale); + tile_scale_x = static_cast<double>(new_tile_width) / + value_image->width(); + + DrawTiledImage(canvas, *value_image, 0, 0, tile_scale_x, tile_scale, + progress_bar.value_rect_x, + progress_bar.value_rect_y, + progress_bar.value_rect_width, + progress_bar.value_rect_height); + } + + int dest_left_border_width = static_cast<int>(left_border_image->width() * + tile_scale); + SkRect dest_rect = { + SkIntToScalar(rect.x()), + SkIntToScalar(rect.y()), + SkIntToScalar(rect.x() + dest_left_border_width), + SkIntToScalar(rect.bottom()) + }; + canvas->drawBitmapRect(*left_border_image, NULL, dest_rect); + + int dest_right_border_width = static_cast<int>(right_border_image->width() * + tile_scale); + dest_rect.set(SkIntToScalar(rect.right() - dest_right_border_width), + SkIntToScalar(rect.y()), + SkIntToScalar(rect.right()), + SkIntToScalar(rect.bottom())); + canvas->drawBitmapRect(*right_border_image, NULL, dest_rect); +} + +bool NativeThemeLinux::IntersectsClipRectInt( + skia::PlatformCanvas* canvas, int x, int y, int w, int h) { + SkRect clip; + return canvas->getClipBounds(&clip) && + clip.intersect(SkIntToScalar(x), SkIntToScalar(y), SkIntToScalar(x + w), + SkIntToScalar(y + h)); +} + +void NativeThemeLinux::DrawVertLine(SkCanvas* canvas, + int x, + int y1, + int y2, + const SkPaint& paint) const { + SkIRect skrect; + skrect.set(x, y1, x + 1, y2 + 1); + canvas->drawIRect(skrect, paint); +} + +void NativeThemeLinux::DrawHorizLine(SkCanvas* canvas, + int x1, + int x2, + int y, + const SkPaint& paint) const { + SkIRect skrect; + skrect.set(x1, y, x2 + 1, y + 1); + canvas->drawIRect(skrect, paint); +} + +void NativeThemeLinux::DrawBox(SkCanvas* canvas, + const gfx::Rect& rect, + const SkPaint& paint) const { + const int right = rect.x() + rect.width() - 1; + const int bottom = rect.y() + rect.height() - 1; + DrawHorizLine(canvas, rect.x(), right, rect.y(), paint); + DrawVertLine(canvas, right, rect.y(), bottom, paint); + DrawHorizLine(canvas, rect.x(), right, bottom, paint); + DrawVertLine(canvas, rect.x(), rect.y(), bottom, paint); +} + +void NativeThemeLinux::DrawBitmapInt( + skia::PlatformCanvas* canvas, const SkBitmap& bitmap, + int src_x, int src_y, int src_w, int src_h, + int dest_x, int dest_y, int dest_w, int dest_h) { + DLOG_ASSERT(src_x + src_w < std::numeric_limits<int16_t>::max() && + src_y + src_h < std::numeric_limits<int16_t>::max()); + if (src_w <= 0 || src_h <= 0 || dest_w <= 0 || dest_h <= 0) { + NOTREACHED() << "Attempting to draw bitmap to/from an empty rect!"; + return; + } + + if (!IntersectsClipRectInt(canvas, dest_x, dest_y, dest_w, dest_h)) + return; + + SkRect dest_rect = { SkIntToScalar(dest_x), + SkIntToScalar(dest_y), + SkIntToScalar(dest_x + dest_w), + SkIntToScalar(dest_y + dest_h) }; + + if (src_w == dest_w && src_h == dest_h) { + // Workaround for apparent bug in Skia that causes image to occasionally + // shift. + SkIRect src_rect = { src_x, src_y, src_x + src_w, src_y + src_h }; + canvas->drawBitmapRect(bitmap, &src_rect, dest_rect); + return; + } + + // Make a bitmap shader that contains the bitmap we want to draw. This is + // basically what SkCanvas.drawBitmap does internally, but it gives us + // more control over quality and will use the mipmap in the source image if + // it has one, whereas drawBitmap won't. + SkShader* shader = SkShader::CreateBitmapShader(bitmap, + SkShader::kRepeat_TileMode, + SkShader::kRepeat_TileMode); + SkMatrix shader_scale; + shader_scale.setScale(SkFloatToScalar(static_cast<float>(dest_w) / src_w), + SkFloatToScalar(static_cast<float>(dest_h) / src_h)); + shader_scale.preTranslate(SkIntToScalar(-src_x), SkIntToScalar(-src_y)); + shader_scale.postTranslate(SkIntToScalar(dest_x), SkIntToScalar(dest_y)); + shader->setLocalMatrix(shader_scale); + + // The rect will be filled by the bitmap. + SkPaint p; + p.setFilterBitmap(true); + p.setShader(shader); + shader->unref(); + canvas->drawRect(dest_rect, p); +} + +void NativeThemeLinux::DrawTiledImage(SkCanvas* canvas, + const SkBitmap& bitmap, + int src_x, int src_y, double tile_scale_x, double tile_scale_y, + int dest_x, int dest_y, int w, int h) const { + SkShader* shader = SkShader::CreateBitmapShader(bitmap, + SkShader::kRepeat_TileMode, + SkShader::kRepeat_TileMode); + if (tile_scale_x != 1.0 || tile_scale_y != 1.0) { + SkMatrix shader_scale; + shader_scale.setScale(SkDoubleToScalar(tile_scale_x), + SkDoubleToScalar(tile_scale_y)); + shader->setLocalMatrix(shader_scale); + } + + SkPaint paint; + paint.setShader(shader); + paint.setXfermodeMode(SkXfermode::kSrcOver_Mode); + + // CreateBitmapShader returns a Shader with a reference count of one, we + // need to unref after paint takes ownership of the shader. + shader->unref(); + canvas->save(); + canvas->translate(SkIntToScalar(dest_x - src_x), + SkIntToScalar(dest_y - src_y)); + canvas->clipRect(SkRect::MakeXYWH(src_x, src_y, w, h)); + canvas->drawPaint(paint); + canvas->restore(); +} + +SkScalar NativeThemeLinux::Clamp(SkScalar value, + SkScalar min, + SkScalar max) const { + return std::min(std::max(value, min), max); +} + +SkColor NativeThemeLinux::SaturateAndBrighten(SkScalar* hsv, + SkScalar saturate_amount, + SkScalar brighten_amount) const { + SkScalar color[3]; + color[0] = hsv[0]; + color[1] = Clamp(hsv[1] + saturate_amount, 0.0, 1.0); + color[2] = Clamp(hsv[2] + brighten_amount, 0.0, 1.0); + return SkHSVToColor(color); +} + +SkColor NativeThemeLinux::OutlineColor(SkScalar* hsv1, SkScalar* hsv2) const { + // GTK Theme engines have way too much control over the layout of + // the scrollbar. We might be able to more closely approximate its + // look-and-feel, if we sent whole images instead of just colors + // from the browser to the renderer. But even then, some themes + // would just break. + // + // So, instead, we don't even try to 100% replicate the look of + // the native scrollbar. We render our own version, but we make + // sure to pick colors that blend in nicely with the system GTK + // theme. In most cases, we can just sample a couple of pixels + // from the system scrollbar and use those colors to draw our + // scrollbar. + // + // This works fine for the track color and the overall thumb + // color. But it fails spectacularly for the outline color used + // around the thumb piece. Not all themes have a clearly defined + // outline. For some of them it is partially transparent, and for + // others the thickness is very unpredictable. + // + // So, instead of trying to approximate the system theme, we + // instead try to compute a reasonable looking choice based on the + // known color of the track and the thumb piece. This is difficult + // when trying to deal both with high- and low-contrast themes, + // and both with positive and inverted themes. + // + // The following code has been tested to look OK with all of the + // default GTK themes. + SkScalar min_diff = Clamp((hsv1[1] + hsv2[1]) * 1.2, 0.28, 0.5); + SkScalar diff = Clamp(fabs(hsv1[2] - hsv2[2]) / 2, min_diff, 0.5); + + if (hsv1[2] + hsv2[2] > 1.0) + diff = -diff; + + return SaturateAndBrighten(hsv2, -0.2, diff); +} + +void NativeThemeLinux::SetScrollbarColors(unsigned inactive_color, + unsigned active_color, + unsigned track_color) const { + thumb_inactive_color_ = inactive_color; + thumb_active_color_ = active_color; + track_color_ = track_color; +} + +} // namespace gfx diff --git a/ui/gfx/native_theme_linux.h b/ui/gfx/native_theme_linux.h new file mode 100644 index 0000000..b557dfc --- /dev/null +++ b/ui/gfx/native_theme_linux.h @@ -0,0 +1,238 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_NATIVE_THEME_LINUX_H_ +#define UI_GFX_NATIVE_THEME_LINUX_H_ + +#include "base/basictypes.h" +#include "skia/ext/platform_canvas.h" + +namespace skia { +class PlatformCanvas; +} + +namespace gfx { +class Rect; +class Size; + +// Linux theming API. +class NativeThemeLinux { + public: + // The part to be painted / sized. + enum Part { + kScrollbarDownArrow, + kScrollbarLeftArrow, + kScrollbarRightArrow, + kScrollbarUpArrow, + kScrollbarHorizontalThumb, + kScrollbarVerticalThumb, + kScrollbarHorizontalTrack, + kScrollbarVerticalTrack, + kCheckbox, + kRadio, + kPushButton, + kTextField, + kMenuList, + kSliderTrack, + kSliderThumb, + kInnerSpinButton, + kProgressBar, + }; + + // The state of the part. + enum State { + kDisabled, + kHovered, + kNormal, + kPressed, + }; + + // Extra data needed to draw scrollbar track correctly. + struct ScrollbarTrackExtraParams { + int track_x; + int track_y; + int track_width; + int track_height; + }; + + struct ButtonExtraParams { + bool checked; + bool indeterminate; // Whether the button state is indeterminate. + bool is_default; // Whether the button is default button. + SkColor background_color; + }; + + struct TextFieldExtraParams { + bool is_text_area; + bool is_listbox; + SkColor background_color; + }; + + struct MenuListExtraParams { + int arrow_x; + int arrow_y; + SkColor background_color; + }; + + struct SliderExtraParams { + bool vertical; + bool in_drag; + }; + + struct InnerSpinButtonExtraParams { + bool spin_up; + bool read_only; + }; + + struct ProgressBarExtraParams { + bool determinate; + int value_rect_x; + int value_rect_y; + int value_rect_width; + int value_rect_height; + }; + + union ExtraParams { + ScrollbarTrackExtraParams scrollbar_track; + ButtonExtraParams button; + MenuListExtraParams menu_list; + SliderExtraParams slider; + TextFieldExtraParams text_field; + InnerSpinButtonExtraParams inner_spin; + ProgressBarExtraParams progress_bar; + }; + + // Gets our singleton instance. + static NativeThemeLinux* instance(); + + // Return the size of the part. + virtual gfx::Size GetPartSize(Part part) const; + // Paint the part to the canvas. + virtual void Paint(skia::PlatformCanvas* canvas, + Part part, + State state, + const gfx::Rect& rect, + const ExtraParams& extra); + // Supports theme specific colors. + void SetScrollbarColors(unsigned inactive_color, + unsigned active_color, + unsigned track_color) const; + + protected: + NativeThemeLinux(); + virtual ~NativeThemeLinux(); + + // Draw the arrow. Used by scrollbar and inner spin button. + virtual void PaintArrowButton( + skia::PlatformCanvas* gc, + const gfx::Rect& rect, + Part direction, + State state); + // Paint the scrollbar track. Done before the thumb so that it can contain + // alpha. + virtual void PaintScrollbarTrack(skia::PlatformCanvas* canvas, + Part part, + State state, + const ScrollbarTrackExtraParams& extra_params, + const gfx::Rect& rect); + // Draw the scrollbar thumb over the track. + virtual void PaintScrollbarThumb(skia::PlatformCanvas* canvas, + Part part, + State state, + const gfx::Rect& rect); + // Draw the checkbox. + virtual void PaintCheckbox(skia::PlatformCanvas* canvas, + State state, + const gfx::Rect& rect, + const ButtonExtraParams& button); + // Draw the radio. + virtual void PaintRadio(skia::PlatformCanvas* canvas, + State state, + const gfx::Rect& rect, + const ButtonExtraParams& button); + // Draw the push button. + virtual void PaintButton(skia::PlatformCanvas* canvas, + State state, + const gfx::Rect& rect, + const ButtonExtraParams& button); + // Draw the text field. + virtual void PaintTextField(skia::PlatformCanvas* canvas, + State state, + const gfx::Rect& rect, + const TextFieldExtraParams& text); + // Draw the menu list. + virtual void PaintMenuList(skia::PlatformCanvas* canvas, + State state, + const gfx::Rect& rect, + const MenuListExtraParams& menu_list); + // Draw the slider track. + virtual void PaintSliderTrack(skia::PlatformCanvas* canvas, + State state, + const gfx::Rect& rect, + const SliderExtraParams& slider); + // Draw the slider thumb. + virtual void PaintSliderThumb(skia::PlatformCanvas* canvas, + State state, + const gfx::Rect& rect, + const SliderExtraParams& slider); + // Draw the inner spin button. + virtual void PaintInnerSpinButton(skia::PlatformCanvas* canvas, + State state, + const gfx::Rect& rect, + const InnerSpinButtonExtraParams& spin_button); + // Draw the progress bar. + virtual void PaintProgressBar(skia::PlatformCanvas* canvas, + State state, + const gfx::Rect& rect, + const ProgressBarExtraParams& progress_bar); + + protected: + bool IntersectsClipRectInt(skia::PlatformCanvas* canvas, + int x, int y, int w, int h); + + void DrawBitmapInt(skia::PlatformCanvas* canvas, const SkBitmap& bitmap, + int src_x, int src_y, int src_w, int src_h, + int dest_x, int dest_y, int dest_w, int dest_h); + + void DrawTiledImage(SkCanvas* canvas, + const SkBitmap& bitmap, + int src_x, int src_y, + double tile_scale_x, double tile_scale_y, + int dest_x, int dest_y, int w, int h) const; + + SkColor SaturateAndBrighten(SkScalar* hsv, + SkScalar saturate_amount, + SkScalar brighten_amount) const; + + private: + void DrawVertLine(SkCanvas* canvas, + int x, + int y1, + int y2, + const SkPaint& paint) const; + void DrawHorizLine(SkCanvas* canvas, + int x1, + int x2, + int y, + const SkPaint& paint) const; + void DrawBox(SkCanvas* canvas, + const gfx::Rect& rect, + const SkPaint& paint) const; + SkScalar Clamp(SkScalar value, + SkScalar min, + SkScalar max) const; + SkColor OutlineColor(SkScalar* hsv1, SkScalar* hsv2) const; + + static unsigned int scrollbar_width_; + static unsigned int button_length_; + static unsigned int thumb_inactive_color_; + static unsigned int thumb_active_color_; + static unsigned int track_color_; + + DISALLOW_COPY_AND_ASSIGN(NativeThemeLinux); +}; + +} // namespace gfx + +#endif // UI_GFX_NATIVE_THEME_LINUX_H_ diff --git a/ui/gfx/native_theme_win.cc b/ui/gfx/native_theme_win.cc new file mode 100644 index 0000000..b563b4f --- /dev/null +++ b/ui/gfx/native_theme_win.cc @@ -0,0 +1,874 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "gfx/native_theme_win.h" + +#include <windows.h> +#include <uxtheme.h> +#include <vsstyle.h> +#include <vssym32.h> + +#include "base/logging.h" +#include "base/scoped_handle.h" +#include "base/win/scoped_gdi_object.h" +#include "base/win/scoped_hdc.h" +#include "base/win/windows_version.h" +#include "gfx/gdi_util.h" +#include "gfx/rect.h" +#include "skia/ext/platform_canvas.h" +#include "skia/ext/skia_utils_win.h" +#include "third_party/skia/include/core/SkShader.h" + +namespace { + +void SetCheckerboardShader(SkPaint* paint, const RECT& align_rect) { + // Create a 2x2 checkerboard pattern using the 3D face and highlight colors. + SkColor face = skia::COLORREFToSkColor(GetSysColor(COLOR_3DFACE)); + SkColor highlight = skia::COLORREFToSkColor(GetSysColor(COLOR_3DHILIGHT)); + SkColor buffer[] = { face, highlight, highlight, face }; + // Confusing bit: we first create a temporary bitmap with our desired pattern, + // then copy it to another bitmap. The temporary bitmap doesn't take + // ownership of the pixel data, and so will point to garbage when this + // function returns. The copy will copy the pixel data into a place owned by + // the bitmap, which is in turn owned by the shader, etc., so it will live + // until we're done using it. + SkBitmap temp_bitmap; + temp_bitmap.setConfig(SkBitmap::kARGB_8888_Config, 2, 2); + temp_bitmap.setPixels(buffer); + SkBitmap bitmap; + temp_bitmap.copyTo(&bitmap, temp_bitmap.config()); + SkShader* shader = SkShader::CreateBitmapShader(bitmap, + SkShader::kRepeat_TileMode, + SkShader::kRepeat_TileMode); + + // Align the pattern with the upper corner of |align_rect|. + SkMatrix matrix; + matrix.setTranslate(SkIntToScalar(align_rect.left), + SkIntToScalar(align_rect.top)); + shader->setLocalMatrix(matrix); + SkSafeUnref(paint->setShader(shader)); +} + +} // namespace + +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_) { + // todo (cpu): fix this soon. + // 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 & ETS_FOCUSED) == ETS_FOCUSED); + const bool pressed = ((state_id & PBS_PRESSED) == PBS_PRESSED); + if ((BP_PUSHBUTTON == part_id) && (pressed || 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); + + // Draw the focus rectangle (the dotted line box) only on buttons. For radio + // and checkboxes, we let webkit draw the focus rectangle (orange glow). + if ((BP_PUSHBUTTON == part_id) && focused) { + // The focus rect is inside the button. The exact number of pixels depends + // on whether we're in classic mode or using uxtheme. + if (handle && get_theme_content_rect_) { + get_theme_content_rect_(handle, hdc, part_id, state_id, rect, rect); + } else { + InflateRect(rect, -GetSystemMetrics(SM_CXEDGE), + -GetSystemMetrics(SM_CYEDGE)); + } + DrawFocusRect(hdc, rect); + } + + 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::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; +} + +HRESULT NativeTheme::PaintMenuArrow(ThemeName theme, + HDC hdc, + int part_id, + int state_id, + RECT* rect, + MenuArrowDirection arrow_direction, + ControlState control_state) 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); + base::win::ScopedHDC mem_dc(CreateCompatibleDC(hdc)); + base::win::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, control_state); +} + +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, + ControlState control_state) 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, control_state); +} + +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::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::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::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::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, + skia::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 { + SkPaint paint; + SetCheckerboardShader(&paint, *align_rect); + canvas->drawIRect(skia::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::PaintSpinButton(HDC hdc, + int part_id, + int state_id, + int classic_state, + RECT* rect) const { + HANDLE handle = GetThemeHandle(SPIN); + if (handle && draw_theme_) + return draw_theme_(handle, hdc, part_id, state_id, rect, NULL); + DrawFrameControl(hdc, rect, DFC_SCROLL, classic_state); + 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, NULL); + } + + // Draw a windows classic scrollbar gripper. + DrawFrameControl(hdc, rect, DFC_SCROLL, DFCS_SCROLLSIZEGRIP); + 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::PaintTrackbar(HDC hdc, + int part_id, + int state_id, + int classic_state, + RECT* rect, + skia::PlatformCanvas* canvas) const { + // Make the channel be 4 px thick in the center of the supplied rect. (4 px + // matches what XP does in various menus; GetThemePartSize() doesn't seem to + // return good values here.) + RECT channel_rect = *rect; + const int channel_thickness = 4; + if (part_id == TKP_TRACK) { + channel_rect.top += + ((channel_rect.bottom - channel_rect.top - channel_thickness) / 2); + channel_rect.bottom = channel_rect.top + channel_thickness; + } else if (part_id == TKP_TRACKVERT) { + channel_rect.left += + ((channel_rect.right - channel_rect.left - channel_thickness) / 2); + channel_rect.right = channel_rect.left + channel_thickness; + } // else this isn't actually a channel, so |channel_rect| == |rect|. + + HANDLE handle = GetThemeHandle(TRACKBAR); + if (handle && draw_theme_) + return draw_theme_(handle, hdc, part_id, state_id, &channel_rect, NULL); + + // Classic mode, draw it manually. + if ((part_id == TKP_TRACK) || (part_id == TKP_TRACKVERT)) { + DrawEdge(hdc, &channel_rect, EDGE_SUNKEN, BF_RECT); + } else if (part_id == TKP_THUMBVERT) { + DrawEdge(hdc, rect, EDGE_RAISED, BF_RECT | BF_SOFT | BF_MIDDLE); + } else { + // Split rect into top and bottom pieces. + RECT top_section = *rect; + RECT bottom_section = *rect; + top_section.bottom -= ((bottom_section.right - bottom_section.left) / 2); + bottom_section.top = top_section.bottom; + DrawEdge(hdc, &top_section, EDGE_RAISED, + BF_LEFT | BF_TOP | BF_RIGHT | BF_SOFT | BF_MIDDLE | BF_ADJUST); + + // Split triangular piece into two diagonals. + RECT& left_half = bottom_section; + RECT right_half = bottom_section; + right_half.left += ((bottom_section.right - bottom_section.left) / 2); + left_half.right = right_half.left; + DrawEdge(hdc, &left_half, EDGE_RAISED, + BF_DIAGONAL_ENDTOPLEFT | BF_SOFT | BF_MIDDLE | BF_ADJUST); + DrawEdge(hdc, &right_half, EDGE_RAISED, + BF_DIAGONAL_ENDBOTTOMLEFT | BF_SOFT | BF_MIDDLE | BF_ADJUST); + + // If the button is pressed, draw hatching. + if (classic_state & DFCS_PUSHED) { + SkPaint paint; + SetCheckerboardShader(&paint, *rect); + + // Fill all three pieces with the pattern. + canvas->drawIRect(skia::RECTToSkIRect(top_section), paint); + + SkScalar left_triangle_top = SkIntToScalar(left_half.top); + SkScalar left_triangle_right = SkIntToScalar(left_half.right); + SkPath left_triangle; + left_triangle.moveTo(SkIntToScalar(left_half.left), left_triangle_top); + left_triangle.lineTo(left_triangle_right, left_triangle_top); + left_triangle.lineTo(left_triangle_right, + SkIntToScalar(left_half.bottom)); + left_triangle.close(); + canvas->drawPath(left_triangle, paint); + + SkScalar right_triangle_left = SkIntToScalar(right_half.left); + SkScalar right_triangle_top = SkIntToScalar(right_half.top); + SkPath right_triangle; + right_triangle.moveTo(right_triangle_left, right_triangle_top); + right_triangle.lineTo(SkIntToScalar(right_half.right), + right_triangle_top); + right_triangle.lineTo(right_triangle_left, + SkIntToScalar(right_half.bottom)); + right_triangle.close(); + canvas->drawPath(right_triangle, paint); + } + } + return S_OK; +} + +// <-a-> +// [ ***** ] +// ____ | | +// <-a-> <------b-----> +// a: object_width +// b: frame_width +// *: animating object +// +// - the animation goes from "[" to "]" repeatedly. +// - the animation offset is at first "|" +// +static int ComputeAnimationProgress(int frame_width, + int object_width, + int pixels_per_second, + double animated_seconds) { + int animation_width = frame_width + object_width; + double interval = static_cast<double>(animation_width) / pixels_per_second; + double ratio = fmod(animated_seconds, interval) / interval; + return static_cast<int>(animation_width * ratio) - object_width; +} + +static RECT InsetRect(const RECT* rect, int size) { + gfx::Rect result(*rect); + result.Inset(size, size); + return result.ToRECT(); +} + +HRESULT NativeTheme::PaintProgressBar(HDC hdc, + RECT* bar_rect, + RECT* value_rect, + bool determinate, + double animated_seconds, + skia::PlatformCanvas* canvas) const { + // There is no documentation about the animation speed, frame-rate, nor + // size of moving overlay of the indeterminate progress bar. + // So we just observed real-world programs and guessed following parameters. + const int kDeteminateOverlayPixelsPerSecond = 300; + const int kDeteminateOverlayWidth = 120; + const int kIndeterminateOverlayPixelsPerSecond = 175; + const int kVistaIndeterminateOverlayWidth = 120; + const int kXPIndeterminateOverlayWidth = 55; + // The thickness of the bar frame inside |value_rect| + const int kXPBarPadding = 3; + + bool pre_vista = base::win::GetVersion() < base::win::VERSION_VISTA; + HANDLE handle = GetThemeHandle(PROGRESS); + if (handle && draw_theme_ && draw_theme_ex_) { + draw_theme_(handle, hdc, PP_BAR, 0, bar_rect, NULL); + + int bar_width = bar_rect->right - bar_rect->left; + if (determinate) { + // TODO(morrita): this RTL guess can be wrong. + // We should pass the direction from WebKit side. + bool is_rtl = (bar_rect->right == value_rect->right && + bar_rect->left != value_rect->left); + // We should care the direction here because PP_CNUNK painting + // is asymmetric. + DTBGOPTS value_draw_options; + value_draw_options.dwSize = sizeof(DTBGOPTS); + value_draw_options.dwFlags = is_rtl ? DTBG_MIRRORDC : 0; + value_draw_options.rcClip = *bar_rect; + + if (pre_vista) { + // On XP, progress bar is chunk-style and has no glossy effect. + // We need to shrink destination rect to fit the part inside the bar + // with an appropriate margin. + RECT shrunk_value_rect = InsetRect(value_rect, kXPBarPadding); + draw_theme_ex_(handle, hdc, PP_CHUNK, 0, + &shrunk_value_rect, &value_draw_options); + } else { + // On Vista or later, the progress bar part has a + // single-block value part. It also has glossy effect. + // And the value part has exactly same height as the bar part + // so we don't need to shrink the rect. + draw_theme_ex_(handle, hdc, PP_FILL, 0, + value_rect, &value_draw_options); + + int dx = ComputeAnimationProgress(bar_width, + kDeteminateOverlayWidth, + kDeteminateOverlayPixelsPerSecond, + animated_seconds); + RECT overlay_rect = *value_rect; + overlay_rect.left += dx; + overlay_rect.right = overlay_rect.left + kDeteminateOverlayWidth; + draw_theme_(handle, hdc, PP_MOVEOVERLAY, 0, &overlay_rect, value_rect); + } + } else { + // A glossy overlay for indeterminate progress bar has small pause + // after each animation. We emulate this by adding an invisible margin + // the animation has to traverse. + int width_with_margin = bar_width + kIndeterminateOverlayPixelsPerSecond; + int overlay_width = pre_vista ? + kXPIndeterminateOverlayWidth : kVistaIndeterminateOverlayWidth; + int dx = ComputeAnimationProgress(width_with_margin, + overlay_width, + kIndeterminateOverlayPixelsPerSecond, + animated_seconds); + RECT overlay_rect = *bar_rect; + overlay_rect.left += dx; + overlay_rect.right = overlay_rect.left + overlay_width; + if (pre_vista) { + RECT shrunk_rect = InsetRect(&overlay_rect, kXPBarPadding); + RECT shrunk_bar_rect = InsetRect(bar_rect, kXPBarPadding); + draw_theme_(handle, hdc, PP_CHUNK, 0, &shrunk_rect, &shrunk_bar_rect); + } else { + draw_theme_(handle, hdc, PP_MOVEOVERLAY, 0, &overlay_rect, bar_rect); + } + } + + return S_OK; + } + + HBRUSH bg_brush = GetSysColorBrush(COLOR_BTNFACE); + HBRUSH fg_brush = GetSysColorBrush(COLOR_BTNSHADOW); + FillRect(hdc, bar_rect, bg_brush); + FillRect(hdc, value_rect, fg_brush); + DrawEdge(hdc, bar_rect, EDGE_SUNKEN, BF_RECT | BF_ADJUST); + 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; +} + +bool NativeTheme::IsThemingActive() const { + if (is_theme_active_) + return !!is_theme_active_(); + return false; +} + +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 = skia::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 = skia::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, + ControlState control_state) const { + const int width = rect->right - rect->left; + const int height = rect->bottom - rect->top; + + // DrawFrameControl for menu arrow/check wants a monochrome bitmap. + base::win::ScopedBitmap mask_bitmap(CreateBitmap(width, height, 1, 1, NULL)); + + if (mask_bitmap == NULL) + return E_OUTOFMEMORY; + + base::win::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. + int bg_color_key; + int text_color_key; + switch (control_state) { + case CONTROL_HIGHLIGHTED: + bg_color_key = COLOR_HIGHLIGHT; + text_color_key = COLOR_HIGHLIGHTTEXT; + break; + case CONTROL_NORMAL: + bg_color_key = COLOR_MENU; + text_color_key = COLOR_MENUTEXT; + break; + case CONTROL_DISABLED: + bg_color_key = COLOR_MENU; + text_color_key = COLOR_GRAYTEXT; + break; + default: + NOTREACHED(); + bg_color_key = COLOR_MENU; + text_color_key = COLOR_MENUTEXT; + break; + } + COLORREF old_bg_color = SetBkColor(hdc, GetSysColor(bg_color_key)); + COLORREF old_text_color = SetTextColor(hdc, GetSysColor(text_color_key)); + 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; + } +} + +bool NativeTheme::IsClassicTheme(ThemeName name) const { + if (!theme_dll_) + return true; + + return !GetThemeHandle(name); +} + +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 BUTTON: + handle = open_theme_(NULL, L"Button"); + break; + case LIST: + handle = open_theme_(NULL, L"Listview"); + break; + case MENU: + handle = open_theme_(NULL, L"Menu"); + break; + case MENULIST: + handle = open_theme_(NULL, L"Combobox"); + break; + case SCROLLBAR: + handle = open_theme_(NULL, L"Scrollbar"); + break; + case STATUS: + handle = open_theme_(NULL, L"Status"); + break; + case TAB: + handle = open_theme_(NULL, L"Tab"); + break; + case TEXTFIELD: + handle = open_theme_(NULL, L"Edit"); + break; + case TRACKBAR: + handle = open_theme_(NULL, L"Trackbar"); + break; + case WINDOW: + handle = open_theme_(NULL, L"Window"); + break; + case PROGRESS: + handle = open_theme_(NULL, L"Progress"); + break; + case SPIN: + handle = open_theme_(NULL, L"Spin"); + break; + default: + NOTREACHED(); + } + theme_handles_[theme_name] = handle; + return handle; +} + +} // namespace gfx diff --git a/ui/gfx/native_theme_win.h b/ui/gfx/native_theme_win.h new file mode 100644 index 0000000..ff94e52 --- /dev/null +++ b/ui/gfx/native_theme_win.h @@ -0,0 +1,321 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +// +// 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 UI_GFX_NATIVE_THEME_WIN_H_ +#define UI_GFX_NATIVE_THEME_WIN_H_ +#pragma once + +#include <windows.h> +#include <uxtheme.h> +#include "base/basictypes.h" +#include "gfx/size.h" +#include "third_party/skia/include/core/SkColor.h" + +namespace skia { +class PlatformCanvas; +} // namespace skia + +namespace gfx { + +// 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, + LIST, + MENU, + MENULIST, + SCROLLBAR, + STATUS, + TAB, + TEXTFIELD, + TRACKBAR, + WINDOW, + PROGRESS, + SPIN, + 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 + }; + + enum ControlState { + CONTROL_NORMAL, + CONTROL_HIGHLIGHTED, + CONTROL_DISABLED + }; + + 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 PaintDialogBackground(HDC dc, bool active, RECT* rect) const; + + HRESULT PaintListBackground(HDC dc, bool enabled, RECT* rect) 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, + ControlState state) 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, + ControlState state) 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 PaintMenuItemBackground(ThemeName theme, + HDC hdc, + int part_id, + int state_id, + bool selected, + RECT* rect) const; + + HRESULT PaintMenuList(HDC hdc, + int part_id, + int state_id, + int classic_state, + RECT* rect) const; + + HRESULT PaintMenuSeparator(HDC hdc, + int part_id, + int state_id, + 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, + skia::PlatformCanvas* canvas) const; + + // Paints a scrollbar thumb or gripper. + HRESULT PaintScrollbarThumb(HDC hdc, + int part_id, + int state_id, + int classic_state, + RECT* rect) const; + + HRESULT PaintSpinButton(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 PaintTabPanelBackground(HDC dc, 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 PaintTrackbar(HDC hdc, + int part_id, + int state_id, + int classic_state, + RECT* rect, + skia::PlatformCanvas* canvas) const; + + HRESULT PaintProgressBar(HDC hdc, + RECT* bar_rect, + RECT* value_rect, + bool determinate, + double animated_seconds, + skia::PlatformCanvas* canvas) 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; + + // Returns true if classic theme is in use. + bool IsClassicTheme(ThemeName name) const; + + // Gets our singleton instance. + static const NativeTheme* instance(); + + private: + NativeTheme(); + ~NativeTheme(); + + HRESULT PaintFrameControl(HDC hdc, + RECT* rect, + UINT type, + UINT state, + ControlState control_state) 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_COPY_AND_ASSIGN(NativeTheme); +}; + +} // namespace gfx + +#endif // UI_GFX_NATIVE_THEME_WIN_H_ diff --git a/ui/gfx/native_theme_win_unittest.cc b/ui/gfx/native_theme_win_unittest.cc new file mode 100644 index 0000000..b087da6 --- /dev/null +++ b/ui/gfx/native_theme_win_unittest.cc @@ -0,0 +1,11 @@ +// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "gfx/native_theme_win.h" + +#include "testing/gtest/include/gtest/gtest.h" + +TEST(NativeThemeTest, Init) { + ASSERT_TRUE(gfx::NativeTheme::instance() != NULL); +} diff --git a/ui/gfx/native_widget_types.h b/ui/gfx/native_widget_types.h new file mode 100644 index 0000000..bc60777 --- /dev/null +++ b/ui/gfx/native_widget_types.h @@ -0,0 +1,176 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_NATIVE_WIDGET_TYPES_H_ +#define UI_GFX_NATIVE_WIDGET_TYPES_H_ +#pragma once + +#include "base/basictypes.h" +#include "build/build_config.h" + +// This file provides cross platform typedefs for native widget types. +// NativeWindow: this is a handle to a native, top-level window +// NativeView: this is a handle to a native UI element. It may be the +// same type as a NativeWindow on some platforms. +// NativeViewId: Often, in our cross process model, we need to pass around a +// reference to a "window". This reference will, say, be echoed back from a +// renderer to the browser when it wishes to query its size. On Windows we +// use an HWND for this. +// +// As a rule of thumb - if you're in the renderer, you should be dealing +// with NativeViewIds. This should remind you that you shouldn't be doing +// direct operations on platform widgets from the renderer process. +// +// If you're in the browser, you're probably dealing with NativeViews, +// unless you're in the IPC layer, which will be translating between +// NativeViewIds from the renderer and NativeViews. +// +// NativeEditView: a handle to a native edit-box. The Mac folks wanted this +// specific typedef. +// +// NativeImage: The platform-specific image type used for drawing UI elements +// in the browser. +// +// The name 'View' here meshes with OS X where the UI elements are called +// 'views' and with our Chrome UI code where the elements are also called +// 'views'. + +#if defined(OS_WIN) +#include <windows.h> // NOLINT +typedef struct HFONT__* HFONT; +#elif defined(OS_MACOSX) +struct CGContext; +#ifdef __OBJC__ +@class NSFont; +@class NSImage; +@class NSView; +@class NSWindow; +@class NSTextField; +#else +class NSFont; +class NSImage; +class NSView; +class NSWindow; +class NSTextField; +#endif // __OBJC__ +#elif defined(TOOLKIT_USES_GTK) +typedef struct _PangoFontDescription PangoFontDescription; +typedef struct _GdkCursor GdkCursor; +typedef struct _GdkPixbuf GdkPixbuf; +typedef struct _GdkRegion GdkRegion; +typedef struct _GtkWidget GtkWidget; +typedef struct _GtkWindow GtkWindow; +typedef struct _cairo cairo_t; +#endif +class SkBitmap; + +namespace gfx { + +#if defined(OS_WIN) +typedef HFONT NativeFont; +typedef HWND NativeView; +typedef HWND NativeWindow; +typedef HWND NativeEditView; +typedef HDC NativeDrawingContext; +typedef HCURSOR NativeCursor; +typedef HMENU NativeMenu; +typedef HRGN NativeRegion; +#elif defined(OS_MACOSX) +typedef NSFont* NativeFont; +typedef NSView* NativeView; +typedef NSWindow* NativeWindow; +typedef NSTextField* NativeEditView; +typedef CGContext* NativeDrawingContext; +typedef void* NativeCursor; +typedef void* NativeMenu; +#elif defined(USE_X11) +typedef PangoFontDescription* NativeFont; +typedef GtkWidget* NativeView; +typedef GtkWindow* NativeWindow; +typedef GtkWidget* NativeEditView; +typedef cairo_t* NativeDrawingContext; +typedef GdkCursor* NativeCursor; +typedef GtkWidget* NativeMenu; +typedef GdkRegion* NativeRegion; +#endif + +#if defined(OS_MACOSX) +typedef NSImage NativeImageType; +#elif defined(OS_LINUX) && !defined(TOOLKIT_VIEWS) +typedef GdkPixbuf NativeImageType; +#else +typedef SkBitmap NativeImageType; +#endif +typedef NativeImageType* NativeImage; + +// Note: for test_shell we're packing a pointer into the NativeViewId. So, if +// you make it a type which is smaller than a pointer, you have to fix +// test_shell. +// +// See comment at the top of the file for usage. +typedef intptr_t NativeViewId; + +#if defined(OS_WIN) +// Convert a NativeViewId to a NativeView. +// +// On Windows, we pass an HWND into the renderer. As stated above, the renderer +// should not be performing operations on the view. +static inline NativeView NativeViewFromId(NativeViewId id) { + return reinterpret_cast<NativeView>(id); +} +#define NativeViewFromIdInBrowser(x) NativeViewFromId(x) +#elif defined(OS_POSIX) +// On Mac and Linux, a NativeView is a pointer to an object, and is useless +// outside the process in which it was created. NativeViewFromId should only be +// used inside the appropriate platform ifdef outside of the browser. +// (NativeViewFromIdInBrowser can be used everywhere in the browser.) If your +// cross-platform design involves a call to NativeViewFromId from outside the +// browser it will never work on Mac or Linux and is fundamentally broken. + +// Please do not call this from outside the browser. It won't work; the name +// should give you a subtle hint. +static inline NativeView NativeViewFromIdInBrowser(NativeViewId id) { + return reinterpret_cast<NativeView>(id); +} +#endif // defined(OS_POSIX) + +// Convert a NativeView to a NativeViewId. See the comments at the top of +// this file. +#if defined(OS_WIN) || defined(OS_MACOSX) +static inline NativeViewId IdFromNativeView(NativeView view) { + return reinterpret_cast<NativeViewId>(view); +} +#elif defined(USE_X11) +// Not inlined because it involves pulling too many headers. +NativeViewId IdFromNativeView(NativeView view); +#endif // defined(USE_X11) + + +// PluginWindowHandle is an abstraction wrapping "the types of windows +// used by NPAPI plugins". On Windows it's an HWND, on X it's an X +// window id. +#if defined(OS_WIN) + typedef HWND PluginWindowHandle; + const PluginWindowHandle kNullPluginWindow = NULL; +#elif defined(USE_X11) + typedef unsigned long PluginWindowHandle; + const PluginWindowHandle kNullPluginWindow = 0; +#else + // On OS X we don't have windowed plugins. + // We use a NULL/0 PluginWindowHandle in shared code to indicate there + // is no window present, so mirror that behavior here. + // + // The GPU plugin is currently an exception to this rule. As of this + // writing it uses some NPAPI infrastructure, and minimally we need + // to identify the plugin instance via this window handle. When the + // GPU plugin becomes a full-on GPU process, this typedef can be + // returned to a bool. For now we use a type large enough to hold a + // pointer on 64-bit architectures in case we need this capability. + typedef uint64 PluginWindowHandle; + const PluginWindowHandle kNullPluginWindow = 0; +#endif + +} // namespace gfx + +#endif // UI_GFX_NATIVE_WIDGET_TYPES_H_ diff --git a/ui/gfx/native_widget_types_gtk.cc b/ui/gfx/native_widget_types_gtk.cc new file mode 100644 index 0000000..ccf428c --- /dev/null +++ b/ui/gfx/native_widget_types_gtk.cc @@ -0,0 +1,15 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "gfx/native_widget_types.h" + +#include "gfx/gtk_native_view_id_manager.h" + +namespace gfx { + +NativeViewId IdFromNativeView(NativeView view) { + return GtkNativeViewManager::GetInstance()->GetIdForWidget(view); +} + +} // namespace gfx diff --git a/ui/gfx/path.cc b/ui/gfx/path.cc new file mode 100644 index 0000000..e456679 --- /dev/null +++ b/ui/gfx/path.cc @@ -0,0 +1,26 @@ +// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "gfx/path.h" + +#include "base/logging.h" + +namespace gfx { + +Path::Path() + : SkPath() { + moveTo(0, 0); +} + +Path::Path(const Point* points, size_t count) { + DCHECK(count > 1); + moveTo(SkIntToScalar(points[0].x), SkIntToScalar(points[0].y)); + for (size_t i = 1; i < count; ++i) + lineTo(SkIntToScalar(points[i].x), SkIntToScalar(points[i].y)); +} + +Path::~Path() { +} + +} // namespace gfx diff --git a/ui/gfx/path.h b/ui/gfx/path.h new file mode 100644 index 0000000..9202e8e --- /dev/null +++ b/ui/gfx/path.h @@ -0,0 +1,57 @@ +// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_PATH_H_ +#define UI_GFX_PATH_H_ +#pragma once + +#include "base/basictypes.h" +#include "gfx/native_widget_types.h" + +#include "third_party/skia/include/core/SkPath.h" + +namespace gfx { + +class Path : public SkPath { + public: + // Used by Path(Point,size_t) constructor. + struct Point { + int x; + int y; + }; + + Path(); + + // Creates a path populated with the specified points. + Path(const Point* points, size_t count); + + ~Path(); + +#if defined(OS_WIN) || defined(USE_X11) + // Creates a NativeRegion from the path. The caller is responsible for freeing + // resources used by this region. This only supports polygon paths. + NativeRegion CreateNativeRegion() const; + + // Returns the intersection of the two regions. The caller owns the returned + // object. + static gfx::NativeRegion IntersectRegions(gfx::NativeRegion r1, + gfx::NativeRegion r2); + + // Returns the union of the two regions. The caller owns the returned object. + static gfx::NativeRegion CombineRegions(gfx::NativeRegion r1, + gfx::NativeRegion r2); + + // Returns the difference of the two regions. The caller owns the returned + // object. + static gfx::NativeRegion SubtractRegion(gfx::NativeRegion r1, + gfx::NativeRegion r2); +#endif + + private: + DISALLOW_COPY_AND_ASSIGN(Path); +}; + +} + +#endif // UI_GFX_PATH_H_ diff --git a/ui/gfx/path_gtk.cc b/ui/gfx/path_gtk.cc new file mode 100644 index 0000000..2149aad --- /dev/null +++ b/ui/gfx/path_gtk.cc @@ -0,0 +1,55 @@ +// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "gfx/path.h" + +#include <gdk/gdk.h> + +#include "base/scoped_ptr.h" +#include "base/command_line.h" + +namespace gfx { + +GdkRegion* Path::CreateNativeRegion() const { + int point_count = getPoints(NULL, 0); + if (point_count <= 1) { + // NOTE: ideally this would return gdk_empty_region, but that returns a + // region with nothing in it. + return NULL; + } + + scoped_array<SkPoint> points(new SkPoint[point_count]); + getPoints(points.get(), point_count); + + scoped_array<GdkPoint> gdk_points(new GdkPoint[point_count]); + for (int i = 0; i < point_count; ++i) { + gdk_points[i].x = SkScalarRound(points[i].fX); + gdk_points[i].y = SkScalarRound(points[i].fY); + } + + return gdk_region_polygon(gdk_points.get(), point_count, GDK_EVEN_ODD_RULE); +} + +// static +NativeRegion Path::IntersectRegions(NativeRegion r1, NativeRegion r2) { + GdkRegion* copy = gdk_region_copy(r1); + gdk_region_intersect(copy, r2); + return copy; +} + +// static +NativeRegion Path::CombineRegions(NativeRegion r1, NativeRegion r2) { + GdkRegion* copy = gdk_region_copy(r1); + gdk_region_union(copy, r2); + return copy; +} + +// static +NativeRegion Path::SubtractRegion(NativeRegion r1, NativeRegion r2) { + GdkRegion* copy = gdk_region_copy(r1); + gdk_region_subtract(copy, r2); + return copy; +} + +} // namespace gfx diff --git a/ui/gfx/path_win.cc b/ui/gfx/path_win.cc new file mode 100644 index 0000000..b5f206c --- /dev/null +++ b/ui/gfx/path_win.cc @@ -0,0 +1,45 @@ +// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "gfx/path.h" + +#include "base/scoped_ptr.h" + +namespace gfx { + +HRGN Path::CreateNativeRegion() const { + int point_count = getPoints(NULL, 0); + scoped_array<SkPoint> points(new SkPoint[point_count]); + getPoints(points.get(), point_count); + scoped_array<POINT> windows_points(new POINT[point_count]); + for (int i = 0; i < point_count; ++i) { + windows_points[i].x = SkScalarRound(points[i].fX); + windows_points[i].y = SkScalarRound(points[i].fY); + } + + return ::CreatePolygonRgn(windows_points.get(), point_count, ALTERNATE); +} + +// static +NativeRegion Path::IntersectRegions(NativeRegion r1, NativeRegion r2) { + HRGN dest = CreateRectRgn(0, 0, 1, 1); + CombineRgn(dest, r1, r2, RGN_AND); + return dest; +} + +// static +NativeRegion Path::CombineRegions(NativeRegion r1, NativeRegion r2) { + HRGN dest = CreateRectRgn(0, 0, 1, 1); + CombineRgn(dest, r1, r2, RGN_OR); + return dest; +} + +// static +NativeRegion Path::SubtractRegion(NativeRegion r1, NativeRegion r2) { + HRGN dest = CreateRectRgn(0, 0, 1, 1); + CombineRgn(dest, r1, r2, RGN_DIFF); + return dest; +} + +} // namespace gfx diff --git a/ui/gfx/platform_font.h b/ui/gfx/platform_font.h new file mode 100644 index 0000000..32cba2c --- /dev/null +++ b/ui/gfx/platform_font.h @@ -0,0 +1,79 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_PLATFORM_FONT_ +#define UI_GFX_PLATFORM_FONT_ +#pragma once + +#include <string> + +#include "base/ref_counted.h" +#include "base/string16.h" +#include "gfx/native_widget_types.h" + +namespace gfx { + +class Font; + +class PlatformFont : public base::RefCounted<PlatformFont> { + public: + // Create an appropriate PlatformFont implementation. + static PlatformFont* CreateDefault(); + static PlatformFont* CreateFromFont(const Font& other); + static PlatformFont* CreateFromNativeFont(NativeFont native_font); + static PlatformFont* CreateFromNameAndSize(const string16& font_name, + int font_size); + + // Returns a new Font derived from the existing font. + // size_delta is the size to add to the current font. See the single + // argument version of this method for an example. + // The style parameter specifies the new style for the font, and is a + // bitmask of the values: BOLD, ITALIC and UNDERLINED. + virtual Font DeriveFont(int size_delta, int style) const = 0; + + // Returns the number of vertical pixels needed to display characters from + // the specified font. This may include some leading, i.e. height may be + // greater than just ascent + descent. Specifically, the Windows and Mac + // implementations include leading and the Linux one does not. This may + // need to be revisited in the future. + virtual int GetHeight() const = 0; + + // Returns the baseline, or ascent, of the font. + virtual int GetBaseline() const = 0; + + // Returns the average character width for the font. + virtual int GetAverageCharacterWidth() const = 0; + + // Returns the number of horizontal pixels needed to display the specified + // string. + virtual int GetStringWidth(const string16& text) const = 0; + + // Returns the expected number of horizontal pixels needed to display the + // specified length of characters. Call GetStringWidth() to retrieve the + // actual number. + virtual int GetExpectedTextWidth(int length) const = 0; + + // Returns the style of the font. + virtual int GetStyle() const = 0; + + // Returns the font name. + virtual string16 GetFontName() const = 0; + + // Returns the font size in pixels. + virtual int GetFontSize() const = 0; + + // Returns the native font handle. + virtual NativeFont GetNativeFont() const = 0; + + protected: + virtual ~PlatformFont() {} + + private: + friend class base::RefCounted<PlatformFont>; +}; + +} // namespace gfx + +#endif // UI_GFX_PLATFORM_FONT_ + diff --git a/ui/gfx/platform_font_gtk.cc b/ui/gfx/platform_font_gtk.cc new file mode 100644 index 0000000..82b2e06 --- /dev/null +++ b/ui/gfx/platform_font_gtk.cc @@ -0,0 +1,450 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "gfx/platform_font_gtk.h" + +#include <algorithm> +#include <fontconfig/fontconfig.h> +#include <gdk/gdk.h> +#include <gtk/gtk.h> +#include <map> +#include <pango/pango.h> + +#include "base/logging.h" +#include "base/string_piece.h" +#include "base/utf_string_conversions.h" +#include "gfx/canvas_skia.h" +#include "gfx/font.h" +#include "gfx/gtk_util.h" +#include "third_party/skia/include/core/SkTypeface.h" +#include "third_party/skia/include/core/SkPaint.h" + +namespace { + +// The font family name which is used when a user's application font for +// GNOME/KDE is a non-scalable one. The name should be listed in the +// IsFallbackFontAllowed function in skia/ext/SkFontHost_fontconfig_direct.cpp. +const char* kFallbackFontFamilyName = "sans"; + +// Returns the number of pixels in a point. +// - multiply a point size by this to get pixels ("device units") +// - divide a pixel size by this to get points +float GetPixelsInPoint() { + static float pixels_in_point = 1.0; + static bool determined_value = false; + + if (!determined_value) { + // http://goo.gl/UIh5m: "This is a scale factor between points specified in + // a PangoFontDescription and Cairo units. The default value is 96, meaning + // that a 10 point font will be 13 units high. (10 * 96. / 72. = 13.3)." + double pango_dpi = gfx::GetPangoResolution(); + if (pango_dpi <= 0) + pango_dpi = 96.0; + pixels_in_point = pango_dpi / 72.0; // 72 points in an inch + determined_value = true; + } + + return pixels_in_point; +} + +// Retrieves the pango metrics for a pango font description. Caches the metrics +// and never frees them. The metrics objects are relatively small and +// very expensive to look up. +PangoFontMetrics* GetPangoFontMetrics(PangoFontDescription* desc) { + static std::map<int, PangoFontMetrics*>* desc_to_metrics = NULL; + static PangoContext* context = NULL; + + if (!context) { + context = gdk_pango_context_get_for_screen(gdk_screen_get_default()); + pango_context_set_language(context, pango_language_get_default()); + } + + if (!desc_to_metrics) { + desc_to_metrics = new std::map<int, PangoFontMetrics*>(); + } + + int desc_hash = pango_font_description_hash(desc); + std::map<int, PangoFontMetrics*>::iterator i = + desc_to_metrics->find(desc_hash); + + if (i == desc_to_metrics->end()) { + PangoFontMetrics* metrics = pango_context_get_metrics(context, desc, NULL); + (*desc_to_metrics)[desc_hash] = metrics; + return metrics; + } else { + return i->second; + } +} + +// Find the best match font for |family_name| in the same way as Skia +// to make sure CreateFont() successfully creates a default font. In +// Skia, it only checks the best match font. If it failed to find +// one, SkTypeface will be NULL for that font family. It eventually +// causes a segfault. For example, family_name = "Sans" and system +// may have various fonts. The first font family in FcPattern will be +// "DejaVu Sans" but a font family returned by FcFontMatch will be "VL +// PGothic". In this case, SkTypeface for "Sans" returns NULL even if +// the system has a font for "Sans" font family. See FontMatch() in +// skia/ports/SkFontHost_fontconfig.cpp for more detail. +string16 FindBestMatchFontFamilyName(const char* family_name) { + FcPattern* pattern = FcPatternCreate(); + FcValue fcvalue; + fcvalue.type = FcTypeString; + char* family_name_copy = strdup(family_name); + fcvalue.u.s = reinterpret_cast<FcChar8*>(family_name_copy); + FcPatternAdd(pattern, FC_FAMILY, fcvalue, 0); + FcConfigSubstitute(0, pattern, FcMatchPattern); + FcDefaultSubstitute(pattern); + FcResult result; + FcPattern* match = FcFontMatch(0, pattern, &result); + DCHECK(match) << "Could not find font: " << family_name; + FcChar8* match_family; + FcPatternGetString(match, FC_FAMILY, 0, &match_family); + + string16 font_family = UTF8ToUTF16(reinterpret_cast<char*>(match_family)); + FcPatternDestroy(match); + FcPatternDestroy(pattern); + free(family_name_copy); + return font_family; +} + +} // namespace + +namespace gfx { + +Font* PlatformFontGtk::default_font_ = NULL; + +//////////////////////////////////////////////////////////////////////////////// +// PlatformFontGtk, public: + +PlatformFontGtk::PlatformFontGtk() { + if (default_font_ == NULL) { + GtkSettings* settings = gtk_settings_get_default(); + + gchar* font_name = NULL; + g_object_get(settings, "gtk-font-name", &font_name, NULL); + + // Temporary CHECK for helping track down + // http://code.google.com/p/chromium/issues/detail?id=12530 + CHECK(font_name) << " Unable to get gtk-font-name for default font."; + + PangoFontDescription* desc = + pango_font_description_from_string(font_name); + default_font_ = new Font(desc); + pango_font_description_free(desc); + g_free(font_name); + + DCHECK(default_font_); + } + + InitFromPlatformFont( + static_cast<PlatformFontGtk*>(default_font_->platform_font())); +} + +PlatformFontGtk::PlatformFontGtk(const Font& other) { + InitFromPlatformFont( + static_cast<PlatformFontGtk*>(other.platform_font())); +} + +PlatformFontGtk::PlatformFontGtk(NativeFont native_font) { + const char* family_name = pango_font_description_get_family(native_font); + + gint size_in_pixels = 0; + if (pango_font_description_get_size_is_absolute(native_font)) { + // If the size is absolute, then it's in Pango units rather than points. + // There are PANGO_SCALE Pango units in a device unit (pixel). + size_in_pixels = pango_font_description_get_size(native_font) / PANGO_SCALE; + } else { + // Otherwise, we need to convert from points. + size_in_pixels = + pango_font_description_get_size(native_font) * GetPixelsInPoint() / + PANGO_SCALE; + } + + // Find best match font for |family_name| to make sure we can get + // a SkTypeface for the default font. + // TODO(agl): remove this. + string16 font_family = FindBestMatchFontFamilyName(family_name); + + InitWithNameAndSize(font_family, size_in_pixels); + int style = 0; + if (pango_font_description_get_weight(native_font) == PANGO_WEIGHT_BOLD) { + // TODO(davemoore) What should we do about other weights? We currently + // only support BOLD. + style |= gfx::Font::BOLD; + } + if (pango_font_description_get_style(native_font) == PANGO_STYLE_ITALIC) { + // TODO(davemoore) What about PANGO_STYLE_OBLIQUE? + style |= gfx::Font::ITALIC; + } + if (style != 0) + style_ = style; +} + +PlatformFontGtk::PlatformFontGtk(const string16& font_name, + int font_size) { + InitWithNameAndSize(font_name, font_size); +} + +double PlatformFontGtk::underline_position() const { + const_cast<PlatformFontGtk*>(this)->InitPangoMetrics(); + return underline_position_pixels_; +} + +double PlatformFontGtk::underline_thickness() const { + const_cast<PlatformFontGtk*>(this)->InitPangoMetrics(); + return underline_thickness_pixels_; +} + +//////////////////////////////////////////////////////////////////////////////// +// PlatformFontGtk, PlatformFont implementation: + +Font PlatformFontGtk::DeriveFont(int size_delta, int style) const { + // If the delta is negative, if must not push the size below 1 + if (size_delta < 0) + DCHECK_LT(-size_delta, font_size_pixels_); + + if (style == style_) { + // Fast path, we just use the same typeface at a different size + return Font(new PlatformFontGtk(typeface_, + font_family_, + font_size_pixels_ + size_delta, + style_)); + } + + // If the style has changed we may need to load a new face + int skstyle = SkTypeface::kNormal; + if (gfx::Font::BOLD & style) + skstyle |= SkTypeface::kBold; + if (gfx::Font::ITALIC & style) + skstyle |= SkTypeface::kItalic; + + SkTypeface* typeface = SkTypeface::CreateFromName( + UTF16ToUTF8(font_family_).c_str(), + static_cast<SkTypeface::Style>(skstyle)); + SkAutoUnref tf_helper(typeface); + + return Font(new PlatformFontGtk(typeface, + font_family_, + font_size_pixels_ + size_delta, + style)); +} + +int PlatformFontGtk::GetHeight() const { + return height_pixels_; +} + +int PlatformFontGtk::GetBaseline() const { + return ascent_pixels_; +} + +int PlatformFontGtk::GetAverageCharacterWidth() const { + return SkScalarRound(average_width_pixels_); +} + +int PlatformFontGtk::GetStringWidth(const string16& text) const { + int width = 0, height = 0; + CanvasSkia::SizeStringInt(text, Font(const_cast<PlatformFontGtk*>(this)), + &width, &height, gfx::Canvas::NO_ELLIPSIS); + return width; +} + +int PlatformFontGtk::GetExpectedTextWidth(int length) const { + double char_width = const_cast<PlatformFontGtk*>(this)->GetAverageWidth(); + return round(static_cast<float>(length) * char_width); +} + +int PlatformFontGtk::GetStyle() const { + return style_; +} + +string16 PlatformFontGtk::GetFontName() const { + return font_family_; +} + +int PlatformFontGtk::GetFontSize() const { + return font_size_pixels_; +} + +NativeFont PlatformFontGtk::GetNativeFont() const { + PangoFontDescription* pfd = pango_font_description_new(); + pango_font_description_set_family(pfd, UTF16ToUTF8(GetFontName()).c_str()); + // Set the absolute size to avoid overflowing UI elements. + // pango_font_description_set_absolute_size() takes a size in Pango units. + // There are PANGO_SCALE Pango units in one device unit. Screen output + // devices use pixels as their device units. + pango_font_description_set_absolute_size( + pfd, font_size_pixels_ * PANGO_SCALE); + + switch (GetStyle()) { + case gfx::Font::NORMAL: + // Nothing to do, should already be PANGO_STYLE_NORMAL. + break; + case gfx::Font::BOLD: + pango_font_description_set_weight(pfd, PANGO_WEIGHT_BOLD); + break; + case gfx::Font::ITALIC: + pango_font_description_set_style(pfd, PANGO_STYLE_ITALIC); + break; + case gfx::Font::UNDERLINED: + // TODO(deanm): How to do underlined? Where do we use it? Probably have + // to paint it ourselves, see pango_font_metrics_get_underline_position. + break; + } + + return pfd; +} + +//////////////////////////////////////////////////////////////////////////////// +// PlatformFontGtk, private: + +PlatformFontGtk::PlatformFontGtk(SkTypeface* typeface, + const string16& name, + int size, + int style) { + InitWithTypefaceNameSizeAndStyle(typeface, name, size, style); +} + +PlatformFontGtk::~PlatformFontGtk() {} + +void PlatformFontGtk::InitWithNameAndSize(const string16& font_name, + int font_size) { + DCHECK_GT(font_size, 0); + string16 fallback; + + SkTypeface* typeface = SkTypeface::CreateFromName( + UTF16ToUTF8(font_name).c_str(), SkTypeface::kNormal); + if (!typeface) { + // A non-scalable font such as .pcf is specified. Falls back to a default + // scalable font. + typeface = SkTypeface::CreateFromName( + kFallbackFontFamilyName, SkTypeface::kNormal); + CHECK(typeface) << "Could not find any font: " + << UTF16ToUTF8(font_name) + << ", " << kFallbackFontFamilyName; + fallback = UTF8ToUTF16(kFallbackFontFamilyName); + } + SkAutoUnref typeface_helper(typeface); + + InitWithTypefaceNameSizeAndStyle(typeface, + fallback.empty() ? font_name : fallback, + font_size, + gfx::Font::NORMAL); +} + +void PlatformFontGtk::InitWithTypefaceNameSizeAndStyle( + SkTypeface* typeface, + const string16& font_family, + int font_size, + int style) { + typeface_helper_.reset(new SkAutoUnref(typeface)); + typeface_ = typeface; + typeface_->ref(); + font_family_ = font_family; + font_size_pixels_ = font_size; + style_ = style; + pango_metrics_inited_ = false; + average_width_pixels_ = 0.0f; + underline_position_pixels_ = 0.0f; + underline_thickness_pixels_ = 0.0f; + + SkPaint paint; + SkPaint::FontMetrics metrics; + PaintSetup(&paint); + paint.getFontMetrics(&metrics); + + ascent_pixels_ = SkScalarCeil(-metrics.fAscent); + height_pixels_ = ascent_pixels_ + SkScalarCeil(metrics.fDescent); +} + +void PlatformFontGtk::InitFromPlatformFont(const PlatformFontGtk* other) { + typeface_helper_.reset(new SkAutoUnref(other->typeface_)); + typeface_ = other->typeface_; + typeface_->ref(); + font_family_ = other->font_family_; + font_size_pixels_ = other->font_size_pixels_; + style_ = other->style_; + height_pixels_ = other->height_pixels_; + ascent_pixels_ = other->ascent_pixels_; + pango_metrics_inited_ = other->pango_metrics_inited_; + average_width_pixels_ = other->average_width_pixels_; + underline_position_pixels_ = other->underline_position_pixels_; + underline_thickness_pixels_ = other->underline_thickness_pixels_; +} + +void PlatformFontGtk::PaintSetup(SkPaint* paint) const { + paint->setAntiAlias(false); + paint->setSubpixelText(false); + paint->setTextSize(font_size_pixels_); + paint->setTypeface(typeface_); + paint->setFakeBoldText((gfx::Font::BOLD & style_) && !typeface_->isBold()); + paint->setTextSkewX((gfx::Font::ITALIC & style_) && !typeface_->isItalic() ? + -SK_Scalar1/4 : 0); +} + +void PlatformFontGtk::InitPangoMetrics() { + if (!pango_metrics_inited_) { + pango_metrics_inited_ = true; + PangoFontDescription* pango_desc = GetNativeFont(); + PangoFontMetrics* pango_metrics = GetPangoFontMetrics(pango_desc); + + underline_position_pixels_ = + pango_font_metrics_get_underline_position(pango_metrics) / + PANGO_SCALE; + + // TODO(davemoore): Come up with a better solution. + // This is a hack, but without doing this the underlines + // we get end up fuzzy. So we align to the midpoint of a pixel. + underline_position_pixels_ /= 2; + + underline_thickness_pixels_ = + pango_font_metrics_get_underline_thickness(pango_metrics) / + PANGO_SCALE; + + // First get the Pango-based width (converting from Pango units to pixels). + double pango_width_pixels = + pango_font_metrics_get_approximate_char_width(pango_metrics) / + PANGO_SCALE; + + // Yes, this is how Microsoft recommends calculating the dialog unit + // conversions. + int text_width_pixels = GetStringWidth( + ASCIIToUTF16("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz")); + double dialog_units_pixels = (text_width_pixels / 26 + 1) / 2; + average_width_pixels_ = std::min(pango_width_pixels, dialog_units_pixels); + pango_font_description_free(pango_desc); + } +} + + +double PlatformFontGtk::GetAverageWidth() const { + const_cast<PlatformFontGtk*>(this)->InitPangoMetrics(); + return average_width_pixels_; +} + +//////////////////////////////////////////////////////////////////////////////// +// PlatformFont, public: + +// static +PlatformFont* PlatformFont::CreateDefault() { + return new PlatformFontGtk; +} + +// static +PlatformFont* PlatformFont::CreateFromFont(const Font& other) { + return new PlatformFontGtk(other); +} + +// static +PlatformFont* PlatformFont::CreateFromNativeFont(NativeFont native_font) { + return new PlatformFontGtk(native_font); +} + +// static +PlatformFont* PlatformFont::CreateFromNameAndSize(const string16& font_name, + int font_size) { + return new PlatformFontGtk(font_name, font_size); +} + +} // namespace gfx diff --git a/ui/gfx/platform_font_gtk.h b/ui/gfx/platform_font_gtk.h new file mode 100644 index 0000000..4b265dd --- /dev/null +++ b/ui/gfx/platform_font_gtk.h @@ -0,0 +1,107 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_PLATFORM_FONT_GTK_ +#define UI_GFX_PLATFORM_FONT_GTK_ +#pragma once + +#include "base/scoped_ptr.h" +#include "gfx/platform_font.h" +#include "third_party/skia/include/core/SkRefCnt.h" + +class SkTypeface; +class SkPaint; + +namespace gfx { + +class PlatformFontGtk : public PlatformFont { + public: + PlatformFontGtk(); + explicit PlatformFontGtk(const Font& other); + explicit PlatformFontGtk(NativeFont native_font); + PlatformFontGtk(const string16& font_name, + int font_size); + + // Converts |gfx_font| to a new pango font. Free the returned font with + // pango_font_description_free(). + static PangoFontDescription* PangoFontFromGfxFont(const gfx::Font& gfx_font); + + // Position as an offset from the height of the drawn text, used to draw + // an underline. This is a negative number, so the underline would be + // drawn at y + height + underline_position; + double underline_position() const; + // The thickness to draw the underline. + double underline_thickness() const; + + // Overridden from PlatformFont: + virtual Font DeriveFont(int size_delta, int style) const; + virtual int GetHeight() const; + virtual int GetBaseline() const; + virtual int GetAverageCharacterWidth() const; + virtual int GetStringWidth(const string16& text) const; + virtual int GetExpectedTextWidth(int length) const; + virtual int GetStyle() const; + virtual string16 GetFontName() const; + virtual int GetFontSize() const; + virtual NativeFont GetNativeFont() const; + + private: + // Create a new instance of this object with the specified properties. Called + // from DeriveFont. + PlatformFontGtk(SkTypeface* typeface, + const string16& name, + int size, + int style); + virtual ~PlatformFontGtk(); + + // Initialize this object. + void InitWithNameAndSize(const string16& font_name, int font_size); + void InitWithTypefaceNameSizeAndStyle(SkTypeface* typeface, + const string16& name, + int size, + int style); + void InitFromPlatformFont(const PlatformFontGtk* other); + + // Potentially slow call to get pango metrics (average width, underline info). + void InitPangoMetrics(); + + // Setup a Skia context to use the current typeface + void PaintSetup(SkPaint* paint) const; + + // Make |this| a copy of |other|. + void CopyFont(const Font& other); + + // The average width of a character, initialized and cached if needed. + double GetAverageWidth() const; + + // These two both point to the same SkTypeface. We use the SkAutoUnref to + // handle the reference counting, but without @typeface_ we would have to + // cast the SkRefCnt from @typeface_helper_ every time. + scoped_ptr<SkAutoUnref> typeface_helper_; + SkTypeface *typeface_; + + // Additional information about the face + // Skia actually expects a family name and not a font name. + string16 font_family_; + int font_size_pixels_; + int style_; + + // Cached metrics, generated at construction + int height_pixels_; + int ascent_pixels_; + + // The pango metrics are much more expensive so we wait until we need them + // to compute them. + bool pango_metrics_inited_; + double average_width_pixels_; + double underline_position_pixels_; + double underline_thickness_pixels_; + + // The default font, used for the default constructor. + static Font* default_font_; +}; + +} // namespace gfx + +#endif // UI_GFX_PLATFORM_FONT_GTK_ diff --git a/ui/gfx/platform_font_mac.h b/ui/gfx/platform_font_mac.h new file mode 100644 index 0000000..123400c --- /dev/null +++ b/ui/gfx/platform_font_mac.h @@ -0,0 +1,57 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_PLATFORM_FONT_MAC_ +#define UI_GFX_PLATFORM_FONT_MAC_ +#pragma once + +#include "gfx/platform_font.h" + +namespace gfx { + +class PlatformFontMac : public PlatformFont { + public: + PlatformFontMac(); + explicit PlatformFontMac(const Font& other); + explicit PlatformFontMac(NativeFont native_font); + PlatformFontMac(const string16& font_name, + int font_size); + + // Overridden from PlatformFont: + virtual Font DeriveFont(int size_delta, int style) const; + virtual int GetHeight() const; + virtual int GetBaseline() const; + virtual int GetAverageCharacterWidth() const; + virtual int GetStringWidth(const string16& text) const; + virtual int GetExpectedTextWidth(int length) const; + virtual int GetStyle() const; + virtual string16 GetFontName() const; + virtual int GetFontSize() const; + virtual NativeFont GetNativeFont() const; + + private: + PlatformFontMac(const string16& font_name, int font_size, int style); + virtual ~PlatformFontMac() {} + + // Initialize the object with the specified parameters. + void InitWithNameSizeAndStyle(const string16& font_name, + int font_size, + int style); + + // Calculate and cache the font metrics. + void CalculateMetrics(); + + string16 font_name_; + int font_size_; + int style_; + + // Cached metrics, generated at construction + int height_; + int ascent_; + int average_width_; +}; + +} // namespace gfx + +#endif // UI_GFX_PLATFORM_FONT_MAC_ diff --git a/ui/gfx/platform_font_mac.mm b/ui/gfx/platform_font_mac.mm new file mode 100644 index 0000000..4aa9b88 --- /dev/null +++ b/ui/gfx/platform_font_mac.mm @@ -0,0 +1,143 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "gfx/platform_font_mac.h" + +#include <Cocoa/Cocoa.h> + +#include "base/basictypes.h" +#include "base/scoped_nsobject.h" +#include "base/sys_string_conversions.h" +#include "base/utf_string_conversions.h" +#include "gfx/canvas_skia.h" +#include "gfx/font.h" + +namespace gfx { + +//////////////////////////////////////////////////////////////////////////////// +// PlatformFontMac, public: + +PlatformFontMac::PlatformFontMac() { + font_size_ = [NSFont systemFontSize]; + style_ = gfx::Font::NORMAL; + NSFont* system_font = [NSFont systemFontOfSize:font_size_]; + font_name_ = base::SysNSStringToUTF16([system_font fontName]); + CalculateMetrics(); +} + +PlatformFontMac::PlatformFontMac(const Font& other) { +} + +PlatformFontMac::PlatformFontMac(NativeFont native_font) { +} + +PlatformFontMac::PlatformFontMac(const string16& font_name, + int font_size) { + InitWithNameSizeAndStyle(font_name, font_size, gfx::Font::NORMAL); +} + +//////////////////////////////////////////////////////////////////////////////// +// PlatformFontMac, PlatformFont implementation: + +Font PlatformFontMac::DeriveFont(int size_delta, int style) const { + return Font(new PlatformFontMac(font_name_, font_size_ + size_delta, style)); +} + +int PlatformFontMac::GetHeight() const { + return height_; +} + +int PlatformFontMac::GetBaseline() const { + return ascent_; +} + +int PlatformFontMac::GetAverageCharacterWidth() const { + return average_width_; +} + +int PlatformFontMac::GetStringWidth(const string16& text) const { + int width = 0, height = 0; + CanvasSkia::SizeStringInt(text, Font(const_cast<PlatformFontMac*>(this)), + &width, &height, gfx::Canvas::NO_ELLIPSIS); + return width; +} + +int PlatformFontMac::GetExpectedTextWidth(int length) const { + return length * average_width_; +} + +int PlatformFontMac::GetStyle() const { + return style_; +} + +string16 PlatformFontMac::GetFontName() const { + return font_name_; +} + +int PlatformFontMac::GetFontSize() const { + return font_size_; +} + +NativeFont PlatformFontMac::GetNativeFont() const { + // TODO(pinkerton): apply |style_| to font. http://crbug.com/34667 + // We could cache this, but then we'd have to conditionally change the + // dtor just for MacOS. Not sure if we want to/need to do that. + return [NSFont fontWithName:base::SysUTF16ToNSString(font_name_) + size:font_size_]; +} + +//////////////////////////////////////////////////////////////////////////////// +// PlatformFontMac, private: + +PlatformFontMac::PlatformFontMac(const string16& font_name, + int font_size, + int style) { + InitWithNameSizeAndStyle(font_name, font_size, style); +} + +void PlatformFontMac::InitWithNameSizeAndStyle(const string16& font_name, + int font_size, + int style) { + font_name_ = font_name; + font_size_ = font_size; + style_ = style; + CalculateMetrics(); +} + +void PlatformFontMac::CalculateMetrics() { + NSFont* font = GetNativeFont(); + scoped_nsobject<NSLayoutManager> layout_manager( + [[NSLayoutManager alloc] init]); + height_ = [layout_manager defaultLineHeightForFont:font]; + ascent_ = [font ascender]; + average_width_ = + [font boundingRectForGlyph:[font glyphWithName:@"x"]].size.width; +} + +//////////////////////////////////////////////////////////////////////////////// +// PlatformFont, public: + +// static +PlatformFont* PlatformFont::CreateDefault() { + return new PlatformFontMac; +} + +// static +PlatformFont* PlatformFont::CreateFromFont(const Font& other) { + return new PlatformFontMac(other); +} + +// static +PlatformFont* PlatformFont::CreateFromNativeFont(NativeFont native_font) { + return new PlatformFontMac(native_font); +} + +// static +PlatformFont* PlatformFont::CreateFromNameAndSize(const string16& font_name, + int font_size) { + return new PlatformFontMac(font_name, font_size); +} + +} // namespace gfx + diff --git a/ui/gfx/platform_font_win.cc b/ui/gfx/platform_font_win.cc new file mode 100644 index 0000000..8071640 --- /dev/null +++ b/ui/gfx/platform_font_win.cc @@ -0,0 +1,269 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "gfx/platform_font_win.h" + +#include <windows.h> +#include <math.h> + +#include <algorithm> + +#include "base/logging.h" +#include "base/string_util.h" +#include "base/utf_string_conversions.h" +#include "base/win/win_util.h" +#include "gfx/canvas_skia.h" +#include "gfx/font.h" + +namespace { + +// If the tmWeight field of a TEXTMETRIC structure has a value >= this, the +// font is bold. +const int kTextMetricWeightBold = 700; + +// Returns either minimum font allowed for a current locale or +// lf_height + size_delta value. +int AdjustFontSize(int lf_height, int size_delta) { + if (lf_height < 0) { + lf_height -= size_delta; + } else { + lf_height += size_delta; + } + int min_font_size = 0; + if (gfx::PlatformFontWin::get_minimum_font_size_callback) + min_font_size = gfx::PlatformFontWin::get_minimum_font_size_callback(); + // Make sure lf_height is not smaller than allowed min font size for current + // locale. + if (abs(lf_height) < min_font_size) { + return lf_height < 0 ? -min_font_size : min_font_size; + } else { + return lf_height; + } +} + +} // namespace + +namespace gfx { + +// static +PlatformFontWin::HFontRef* PlatformFontWin::base_font_ref_; + +// static +PlatformFontWin::AdjustFontCallback + PlatformFontWin::adjust_font_callback = NULL; +PlatformFontWin::GetMinimumFontSizeCallback + PlatformFontWin::get_minimum_font_size_callback = NULL; + +//////////////////////////////////////////////////////////////////////////////// +// PlatformFontWin, public + +PlatformFontWin::PlatformFontWin() : font_ref_(GetBaseFontRef()) { +} + +PlatformFontWin::PlatformFontWin(const Font& other) { + InitWithCopyOfHFONT(other.GetNativeFont()); +} + +PlatformFontWin::PlatformFontWin(NativeFont native_font) { + InitWithCopyOfHFONT(native_font); +} + +PlatformFontWin::PlatformFontWin(const string16& font_name, + int font_size) { + InitWithFontNameAndSize(font_name, font_size); +} + +//////////////////////////////////////////////////////////////////////////////// +// PlatformFontWin, PlatformFont implementation: + +Font PlatformFontWin::DeriveFont(int size_delta, int style) const { + LOGFONT font_info; + GetObject(GetNativeFont(), sizeof(LOGFONT), &font_info); + font_info.lfHeight = AdjustFontSize(font_info.lfHeight, size_delta); + font_info.lfUnderline = + ((style & gfx::Font::UNDERLINED) == gfx::Font::UNDERLINED); + font_info.lfItalic = ((style & gfx::Font::ITALIC) == gfx::Font::ITALIC); + font_info.lfWeight = (style & gfx::Font::BOLD) ? FW_BOLD : FW_NORMAL; + + HFONT hfont = CreateFontIndirect(&font_info); + return Font(new PlatformFontWin(CreateHFontRef(hfont))); +} + +int PlatformFontWin::GetHeight() const { + return font_ref_->height(); +} + +int PlatformFontWin::GetBaseline() const { + return font_ref_->baseline(); +} + +int PlatformFontWin::GetAverageCharacterWidth() const { + return font_ref_->ave_char_width(); +} + +int PlatformFontWin::GetStringWidth(const string16& text) const { + int width = 0, height = 0; + CanvasSkia::SizeStringInt(text, Font(const_cast<PlatformFontWin*>(this)), + &width, &height, gfx::Canvas::NO_ELLIPSIS); + return width; +} + +int PlatformFontWin::GetExpectedTextWidth(int length) const { + return length * std::min(font_ref_->dlu_base_x(), GetAverageCharacterWidth()); +} + +int PlatformFontWin::GetStyle() const { + return font_ref_->style(); +} + +string16 PlatformFontWin::GetFontName() const { + return font_ref_->font_name(); +} + +int PlatformFontWin::GetFontSize() const { + LOGFONT font_info; + GetObject(font_ref_->hfont(), sizeof(LOGFONT), &font_info); + long lf_height = font_info.lfHeight; + HDC hdc = GetDC(NULL); + int device_caps = GetDeviceCaps(hdc, LOGPIXELSY); + int font_size = 0; + if (device_caps != 0) { + float font_size_float = -static_cast<float>(lf_height)*72/device_caps; + font_size = static_cast<int>(::ceil(font_size_float - 0.5)); + } + ReleaseDC(NULL, hdc); + return font_size; +} + +NativeFont PlatformFontWin::GetNativeFont() const { + return font_ref_->hfont(); +} + +//////////////////////////////////////////////////////////////////////////////// +// Font, private: + +void PlatformFontWin::InitWithCopyOfHFONT(HFONT hfont) { + DCHECK(hfont); + LOGFONT font_info; + GetObject(hfont, sizeof(LOGFONT), &font_info); + font_ref_ = CreateHFontRef(CreateFontIndirect(&font_info)); +} + +void PlatformFontWin::InitWithFontNameAndSize(const string16& font_name, + int font_size) { + HDC hdc = GetDC(NULL); + long lf_height = -MulDiv(font_size, GetDeviceCaps(hdc, LOGPIXELSY), 72); + ReleaseDC(NULL, hdc); + HFONT hf = ::CreateFont(lf_height, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + font_name.c_str()); + font_ref_ = CreateHFontRef(hf); +} + +// static +PlatformFontWin::HFontRef* PlatformFontWin::GetBaseFontRef() { + if (base_font_ref_ == NULL) { + NONCLIENTMETRICS metrics; + base::win::GetNonClientMetrics(&metrics); + + if (adjust_font_callback) + adjust_font_callback(&metrics.lfMessageFont); + metrics.lfMessageFont.lfHeight = + AdjustFontSize(metrics.lfMessageFont.lfHeight, 0); + HFONT font = CreateFontIndirect(&metrics.lfMessageFont); + DLOG_ASSERT(font); + base_font_ref_ = PlatformFontWin::CreateHFontRef(font); + // base_font_ref_ is global, up the ref count so it's never deleted. + base_font_ref_->AddRef(); + } + return base_font_ref_; +} + +PlatformFontWin::HFontRef* PlatformFontWin::CreateHFontRef(HFONT font) { + TEXTMETRIC font_metrics; + HDC screen_dc = GetDC(NULL); + HFONT previous_font = static_cast<HFONT>(SelectObject(screen_dc, font)); + int last_map_mode = SetMapMode(screen_dc, MM_TEXT); + GetTextMetrics(screen_dc, &font_metrics); + // Yes, this is how Microsoft recommends calculating the dialog unit + // conversions. + SIZE ave_text_size; + GetTextExtentPoint32(screen_dc, + L"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz", + 52, &ave_text_size); + const int dlu_base_x = (ave_text_size.cx / 26 + 1) / 2; + // To avoid the DC referencing font_handle_, select the previous font. + SelectObject(screen_dc, previous_font); + SetMapMode(screen_dc, last_map_mode); + ReleaseDC(NULL, screen_dc); + + const int height = std::max(1, static_cast<int>(font_metrics.tmHeight)); + const int baseline = std::max(1, static_cast<int>(font_metrics.tmAscent)); + const int ave_char_width = + std::max(1, static_cast<int>(font_metrics.tmAveCharWidth)); + int style = 0; + if (font_metrics.tmItalic) + style |= Font::ITALIC; + if (font_metrics.tmUnderlined) + style |= Font::UNDERLINED; + if (font_metrics.tmWeight >= kTextMetricWeightBold) + style |= Font::BOLD; + + return new HFontRef(font, height, baseline, ave_char_width, style, + dlu_base_x); +} + +PlatformFontWin::PlatformFontWin(HFontRef* hfont_ref) : font_ref_(hfont_ref) { +} + +//////////////////////////////////////////////////////////////////////////////// +// PlatformFontWin::HFontRef: + +PlatformFontWin::HFontRef::HFontRef(HFONT hfont, + int height, + int baseline, + int ave_char_width, + int style, + int dlu_base_x) + : hfont_(hfont), + height_(height), + baseline_(baseline), + ave_char_width_(ave_char_width), + style_(style), + dlu_base_x_(dlu_base_x) { + DLOG_ASSERT(hfont); + + LOGFONT font_info; + GetObject(hfont_, sizeof(LOGFONT), &font_info); + font_name_ = string16(font_info.lfFaceName); +} + +PlatformFontWin::HFontRef::~HFontRef() { + DeleteObject(hfont_); +} + +//////////////////////////////////////////////////////////////////////////////// +// PlatformFont, public: + +// static +PlatformFont* PlatformFont::CreateDefault() { + return new PlatformFontWin; +} + +// static +PlatformFont* PlatformFont::CreateFromFont(const Font& other) { + return new PlatformFontWin(other); +} + +// static +PlatformFont* PlatformFont::CreateFromNativeFont(NativeFont native_font) { + return new PlatformFontWin(native_font); +} + +// static +PlatformFont* PlatformFont::CreateFromNameAndSize(const string16& font_name, + int font_size) { + return new PlatformFontWin(font_name, font_size); +} + +} // namespace gfx diff --git a/ui/gfx/platform_font_win.h b/ui/gfx/platform_font_win.h new file mode 100644 index 0000000..4b18cd1 --- /dev/null +++ b/ui/gfx/platform_font_win.h @@ -0,0 +1,131 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_PLATFORM_FONT_WIN_ +#define UI_GFX_PLATFORM_FONT_WIN_ +#pragma once + +#include "base/ref_counted.h" +#include "gfx/platform_font.h" + +namespace gfx { + +class PlatformFontWin : public PlatformFont { + public: + PlatformFontWin(); + explicit PlatformFontWin(const Font& other); + explicit PlatformFontWin(NativeFont native_font); + PlatformFontWin(const string16& font_name, + int font_size); + + // Dialog units to pixels conversion. + // See http://support.microsoft.com/kb/145994 for details. + int horizontal_dlus_to_pixels(int dlus) const { + return dlus * font_ref_->dlu_base_x() / 4; + } + int vertical_dlus_to_pixels(int dlus) const { + return dlus * font_ref_->height() / 8; + } + + // Callback that returns the minimum height that should be used for + // gfx::Fonts. Optional. If not specified, the minimum font size is 0. + typedef int (*GetMinimumFontSizeCallback)(); + static GetMinimumFontSizeCallback get_minimum_font_size_callback; + + // Callback that adjusts a LOGFONT to meet suitability requirements of the + // embedding application. Optional. If not specified, no adjustments are + // performed other than clamping to a minimum font height if + // |get_minimum_font_size_callback| is specified. + typedef void (*AdjustFontCallback)(LOGFONT* lf); + static AdjustFontCallback adjust_font_callback; + + // Overridden from PlatformFont: + virtual Font DeriveFont(int size_delta, int style) const; + virtual int GetHeight() const; + virtual int GetBaseline() const; + virtual int GetAverageCharacterWidth() const; + virtual int GetStringWidth(const string16& text) const; + virtual int GetExpectedTextWidth(int length) const; + virtual int GetStyle() const; + virtual string16 GetFontName() const; + virtual int GetFontSize() const; + virtual NativeFont GetNativeFont() const; + + private: + virtual ~PlatformFontWin() {} + + // Chrome text drawing bottoms out in the Windows GDI functions that take an + // HFONT (an opaque handle into Windows). To avoid lots of GDI object + // allocation and destruction, Font indirectly refers to the HFONT by way of + // an HFontRef. That is, every Font has an HFontRef, which has an HFONT. + // + // HFontRef is reference counted. Upon deletion, it deletes the HFONT. + // By making HFontRef maintain the reference to the HFONT, multiple + // HFontRefs can share the same HFONT, and Font can provide value semantics. + class HFontRef : public base::RefCounted<HFontRef> { + public: + // This constructor takes control of the HFONT, and will delete it when + // the HFontRef is deleted. + HFontRef(HFONT hfont, + int height, + int baseline, + int ave_char_width, + int style, + int dlu_base_x); + + // Accessors + HFONT hfont() const { return hfont_; } + int height() const { return height_; } + int baseline() const { return baseline_; } + int ave_char_width() const { return ave_char_width_; } + int style() const { return style_; } + int dlu_base_x() const { return dlu_base_x_; } + const string16& font_name() const { return font_name_; } + + private: + friend class base::RefCounted<HFontRef>; + + ~HFontRef(); + + const HFONT hfont_; + const int height_; + const int baseline_; + const int ave_char_width_; + const int style_; + // Constants used in converting dialog units to pixels. + const int dlu_base_x_; + string16 font_name_; + + DISALLOW_COPY_AND_ASSIGN(HFontRef); + }; + + // Initializes this object with a copy of the specified HFONT. + void InitWithCopyOfHFONT(HFONT hfont); + + // Initializes this object with the specified font name and size. + void InitWithFontNameAndSize(const string16& font_name, + int font_size); + + // Returns the base font ref. This should ONLY be invoked on the + // UI thread. + static HFontRef* GetBaseFontRef(); + + // Creates and returns a new HFONTRef from the specified HFONT. + static HFontRef* CreateHFontRef(HFONT font); + + // Creates a new PlatformFontWin with the specified HFontRef. Used when + // constructing a Font from a HFONT we don't want to copy. + explicit PlatformFontWin(HFontRef* hfont_ref); + + // Reference to the base font all fonts are derived from. + static HFontRef* base_font_ref_; + + // Indirect reference to the HFontRef, which references the underlying HFONT. + scoped_refptr<HFontRef> font_ref_; +}; + +} // namespace gfx + +#endif // UI_GFX_PLATFORM_FONT_WIN_ + diff --git a/ui/gfx/point.cc b/ui/gfx/point.cc new file mode 100644 index 0000000..d601580 --- /dev/null +++ b/ui/gfx/point.cc @@ -0,0 +1,56 @@ +// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "gfx/point.h" + +#if defined(OS_WIN) +#include <windows.h> +#endif + +#include <ostream> + +namespace gfx { + +Point::Point() : x_(0), y_(0) { +} + +Point::Point(int x, int y) : x_(x), y_(y) { +} + +#if defined(OS_WIN) +Point::Point(DWORD point) { + POINTS points = MAKEPOINTS(point); + x_ = points.x; + y_ = points.y; +} + +Point::Point(const POINT& point) : x_(point.x), y_(point.y) { +} + +Point& Point::operator=(const POINT& point) { + x_ = point.x; + y_ = point.y; + return *this; +} + +POINT Point::ToPOINT() const { + POINT p; + p.x = x_; + p.y = y_; + return p; +} +#elif defined(OS_MACOSX) +Point::Point(const CGPoint& point) : x_(point.x), y_(point.y) { +} + +CGPoint Point::ToCGPoint() const { + return CGPointMake(x_, y_); +} +#endif + +std::ostream& operator<<(std::ostream& out, const gfx::Point& p) { + return out << p.x() << "," << p.y(); +} + +} // namespace gfx diff --git a/ui/gfx/point.h b/ui/gfx/point.h new file mode 100644 index 0000000..b208c07 --- /dev/null +++ b/ui/gfx/point.h @@ -0,0 +1,101 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_POINT_H_ +#define UI_GFX_POINT_H_ +#pragma once + +#include "build/build_config.h" + +#include <iosfwd> + +#if defined(OS_WIN) +typedef unsigned long DWORD; +typedef struct tagPOINT POINT; +#elif defined(OS_MACOSX) +#include <ApplicationServices/ApplicationServices.h> +#endif + +namespace gfx { + +// A point has an x and y coordinate. +class Point { + public: + Point(); + Point(int x, int y); +#if defined(OS_WIN) + // |point| is a DWORD value that contains a coordinate. The x-coordinate is + // the low-order short and the y-coordinate is the high-order short. This + // value is commonly acquired from GetMessagePos/GetCursorPos. + explicit Point(DWORD point); + explicit Point(const POINT& point); + Point& operator=(const POINT& point); +#elif defined(OS_MACOSX) + explicit Point(const CGPoint& point); +#endif + + ~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; } + + void Offset(int delta_x, int delta_y) { + x_ += delta_x; + y_ += delta_y; + } + + Point Add(const Point& other) const{ + Point copy = *this; + copy.Offset(other.x_, other.y_); + return copy; + } + + Point Subtract(const Point& other) const { + Point copy = *this; + copy.Offset(-other.x_, -other.y_); + return copy; + } + + bool operator==(const Point& rhs) const { + return x_ == rhs.x_ && y_ == rhs.y_; + } + + bool operator!=(const Point& rhs) const { + return !(*this == rhs); + } + + // A point is less than another point if its y-value is closer + // to the origin. If the y-values are the same, then point with + // the x-value closer to the origin is considered less than the + // other. + // This comparison is required to use Points in sets, or sorted + // vectors. + bool operator<(const Point& rhs) const { + return (y_ == rhs.y_) ? (x_ < rhs.x_) : (y_ < rhs.y_); + } + +#if defined(OS_WIN) + POINT ToPOINT() const; +#elif defined(OS_MACOSX) + CGPoint ToCGPoint() const; +#endif + + private: + int x_; + int y_; +}; + +std::ostream& operator<<(std::ostream& out, const gfx::Point& p); + +} // namespace gfx + +#endif // UI_GFX_POINT_H_ diff --git a/ui/gfx/rect.cc b/ui/gfx/rect.cc new file mode 100644 index 0000000..a1a72cb --- /dev/null +++ b/ui/gfx/rect.cc @@ -0,0 +1,256 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "gfx/rect.h" + +#if defined(OS_WIN) +#include <windows.h> +#elif defined(OS_MACOSX) +#include <CoreGraphics/CGGeometry.h> +#elif defined(OS_POSIX) +#include <gdk/gdk.h> +#endif + +#include <ostream> + +#include "gfx/insets.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) + : size_(width, height) { +} + +Rect::Rect(int x, int y, int width, int height) + : origin_(x, y), size_(width, height) { +} + +Rect::Rect(const gfx::Size& size) + : size_(size) { +} + +Rect::Rect(const gfx::Point& origin, const gfx::Size& size) + : origin_(origin), size_(size) { +} + +#if defined(OS_WIN) +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; +} +#elif defined(OS_MACOSX) +Rect::Rect(const CGRect& r) + : origin_(r.origin.x, r.origin.y) { + set_width(r.size.width); + set_height(r.size.height); +} + +Rect& Rect::operator=(const CGRect& r) { + origin_.SetPoint(r.origin.x, r.origin.y); + set_width(r.size.width); + set_height(r.size.height); + return *this; +} +#elif defined(OS_POSIX) +Rect::Rect(const GdkRectangle& r) + : origin_(r.x, r.y) { + set_width(r.width); + set_height(r.height); +} + +Rect& Rect::operator=(const GdkRectangle& r) { + origin_.SetPoint(r.x, r.y); + set_width(r.width); + set_height(r.height); + return *this; +} +#endif + +void Rect::SetRect(int x, int y, int width, int height) { + origin_.SetPoint(x, y); + set_width(width); + set_height(height); +} + +void Rect::Inset(const gfx::Insets& insets) { + Inset(insets.left(), insets.top(), insets.right(), insets.bottom()); +} + +void Rect::Inset(int left, int top, int right, int bottom) { + Offset(left, top); + set_width(std::max(width() - left - right, 0)); + set_height(std::max(height() - top - bottom, 0)); +} + +void Rect::Offset(int horizontal, int vertical) { + origin_.Offset(horizontal, vertical); +} + +bool Rect::operator==(const Rect& other) const { + return origin_ == other.origin_ && size_ == other.size_; +} + +bool Rect::operator<(const Rect& other) const { + if (origin_ == other.origin_) { + if (width() == other.width()) { + return height() < other.height(); + } else { + return width() < other.width(); + } + } else { + return origin_ < other.origin_; + } +} + +#if defined(OS_WIN) +RECT Rect::ToRECT() const { + RECT r; + r.left = x(); + r.right = right(); + r.top = y(); + r.bottom = bottom(); + return r; +} +#elif defined(OS_MACOSX) +CGRect Rect::ToCGRect() const { + return CGRectMake(x(), y(), width(), height()); +} +#elif defined(OS_POSIX) +GdkRectangle Rect::ToGdkRectangle() const { + GdkRectangle r = {x(), y(), width(), height()}; + return r; +} +#endif + +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); +} + +Rect Rect::Center(const gfx::Size& size) const { + int new_width = std::min(width(), size.width()); + int new_height = std::min(height(), size.height()); + int new_x = x() + (width() - new_width) / 2; + int new_y = y() + (height() - new_height) / 2; + return Rect(new_x, new_y, new_width, new_height); +} + +bool Rect::SharesEdgeWith(const gfx::Rect& rect) const { + return (y() == rect.y() && height() == rect.height() && + (x() == rect.right() || right() == rect.x())) || + (x() == rect.x() && width() == rect.width() && + (y() == rect.bottom() || bottom() == rect.y())); +} + +std::ostream& operator<<(std::ostream& out, const gfx::Rect& r) { + return out << r.origin() << " " << r.size(); +} + +} // namespace gfx diff --git a/ui/gfx/rect.h b/ui/gfx/rect.h new file mode 100644 index 0000000..1a1d9e6 --- /dev/null +++ b/ui/gfx/rect.h @@ -0,0 +1,184 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// 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 Contains()) to complain in this case. + +#ifndef UI_GFX_RECT_H_ +#define UI_GFX_RECT_H_ +#pragma once + +#include <iosfwd> + +#include "gfx/point.h" +#include "gfx/size.h" + +#if defined(OS_WIN) +typedef struct tagRECT RECT; +#elif defined(USE_X11) +typedef struct _GdkRectangle GdkRectangle; +#endif + +namespace gfx { + +class Insets; + +class Rect { + public: + Rect(); + Rect(int width, int height); + Rect(int x, int y, int width, int height); +#if defined(OS_WIN) + explicit Rect(const RECT& r); +#elif defined(OS_MACOSX) + explicit Rect(const CGRect& r); +#elif defined(USE_X11) + explicit Rect(const GdkRectangle& r); +#endif + explicit Rect(const gfx::Size& size); + Rect(const gfx::Point& origin, const gfx::Size& size); + + ~Rect() {} + +#if defined(OS_WIN) + Rect& operator=(const RECT& r); +#elif defined(OS_MACOSX) + Rect& operator=(const CGRect& r); +#elif defined(USE_X11) + Rect& operator=(const GdkRectangle& r); +#endif + + 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) { size_.set_width(width); } + + int height() const { return size_.height(); } + void set_height(int height) { size_.set_height(height); } + + const gfx::Point& origin() const { return origin_; } + void set_origin(const gfx::Point& origin) { origin_ = origin; } + + const gfx::Size& size() const { return size_; } + void set_size(const gfx::Size& size) { size_ = 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) { + Inset(horizontal, vertical, horizontal, vertical); + } + + // Shrink the rectangle by the given insets. + void Inset(const gfx::Insets& insets); + + // Shrink the rectangle by the specified amount on each side. + void Inset(int left, int top, int right, int bottom); + + // Move the rectangle by a horizontal and vertical distance. + void Offset(int horizontal, int vertical); + void Offset(const gfx::Point& point) { + Offset(point.x(), point.y()); + } + + // Returns true if the area of the rectangle is zero. + bool IsEmpty() const { return size_.IsEmpty(); } + + bool operator==(const Rect& other) const; + + bool operator!=(const Rect& other) const { + return !(*this == other); + } + + // A rect is less than another rect if its origin is less than + // the other rect's origin. If the origins are equal, then the + // shortest rect is less than the other. If the origin and the + // height are equal, then the narrowest rect is less than. + // This comparison is required to use Rects in sets, or sorted + // vectors. + bool operator<(const Rect& other) const; + +#if defined(OS_WIN) + // Construct an equivalent Win32 RECT object. + RECT ToRECT() const; +#elif defined(USE_X11) + GdkRectangle ToGdkRectangle() const; +#elif defined(OS_MACOSX) + // Construct an equivalent CoreGraphics object. + CGRect ToCGRect() const; +#endif + + // 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 the specified point is contained by this rectangle. + bool Contains(const gfx::Point& point) const { + return Contains(point.x(), point.y()); + } + + // 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; + + // Return a rectangle that has the same center point but with a size capped + // at given |size|. + Rect Center(const gfx::Size& size) const; + + // Returns true if this rectangle shares an entire edge (i.e., same width or + // same height) with the given rectangle, and the rectangles do not overlap. + bool SharesEdgeWith(const gfx::Rect& rect) const; + + private: + gfx::Point origin_; + gfx::Size size_; +}; + +std::ostream& operator<<(std::ostream& out, const gfx::Rect& r); + +} // namespace gfx + +#endif // UI_GFX_RECT_H_ diff --git a/ui/gfx/rect_unittest.cc b/ui/gfx/rect_unittest.cc new file mode 100644 index 0000000..f5b4d9b --- /dev/null +++ b/ui/gfx/rect_unittest.cc @@ -0,0 +1,314 @@ +// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/basictypes.h" +#include "gfx/rect.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 (size_t i = 0; i < ARRAYSIZE_UNSAFE(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_UNSAFE(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_UNSAFE(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 Test { + 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_UNSAFE(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 Test { + 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_UNSAFE(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(RectTest, 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))); +} + +TEST(RectTest, IsEmpty) { + EXPECT_TRUE(gfx::Rect(0, 0, 0, 0).IsEmpty()); + EXPECT_TRUE(gfx::Rect(0, 0, 0, 0).size().IsEmpty()); + EXPECT_TRUE(gfx::Rect(0, 0, 10, 0).IsEmpty()); + EXPECT_TRUE(gfx::Rect(0, 0, 10, 0).size().IsEmpty()); + EXPECT_TRUE(gfx::Rect(0, 0, 0, 10).IsEmpty()); + EXPECT_TRUE(gfx::Rect(0, 0, 0, 10).size().IsEmpty()); + EXPECT_FALSE(gfx::Rect(0, 0, 10, 10).IsEmpty()); + EXPECT_FALSE(gfx::Rect(0, 0, 10, 10).size().IsEmpty()); +} + +TEST(RectTest, SharesEdgeWith) { + gfx::Rect r(2, 3, 4, 5); + + // Must be non-overlapping + EXPECT_FALSE(r.SharesEdgeWith(r)); + + gfx::Rect just_above(2, 1, 4, 2); + gfx::Rect just_below(2, 8, 4, 2); + gfx::Rect just_left(0, 3, 2, 5); + gfx::Rect just_right(6, 3, 2, 5); + + EXPECT_TRUE(r.SharesEdgeWith(just_above)); + EXPECT_TRUE(r.SharesEdgeWith(just_below)); + EXPECT_TRUE(r.SharesEdgeWith(just_left)); + EXPECT_TRUE(r.SharesEdgeWith(just_right)); + + // Wrong placement + gfx::Rect same_height_no_edge(0, 0, 1, 5); + gfx::Rect same_width_no_edge(0, 0, 4, 1); + + EXPECT_FALSE(r.SharesEdgeWith(same_height_no_edge)); + EXPECT_FALSE(r.SharesEdgeWith(same_width_no_edge)); + + gfx::Rect just_above_no_edge(2, 1, 5, 2); // too wide + gfx::Rect just_below_no_edge(2, 8, 3, 2); // too narrow + gfx::Rect just_left_no_edge(0, 3, 2, 6); // too tall + gfx::Rect just_right_no_edge(6, 3, 2, 4); // too short + + EXPECT_FALSE(r.SharesEdgeWith(just_above_no_edge)); + EXPECT_FALSE(r.SharesEdgeWith(just_below_no_edge)); + EXPECT_FALSE(r.SharesEdgeWith(just_left_no_edge)); + EXPECT_FALSE(r.SharesEdgeWith(just_right_no_edge)); +} diff --git a/ui/gfx/run_all_unittests.cc b/ui/gfx/run_all_unittests.cc new file mode 100644 index 0000000..834d7c8 --- /dev/null +++ b/ui/gfx/run_all_unittests.cc @@ -0,0 +1,9 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "gfx/test_suite.h" + +int main(int argc, char** argv) { + return GfxTestSuite(argc, argv).Run(); +} diff --git a/ui/gfx/scoped_cg_context_state_mac.h b/ui/gfx/scoped_cg_context_state_mac.h new file mode 100644 index 0000000..6380bf1 --- /dev/null +++ b/ui/gfx/scoped_cg_context_state_mac.h @@ -0,0 +1,30 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_SCOPED_CG_CONTEXT_STATE_MAC_H_ +#define UI_GFX_SCOPED_CG_CONTEXT_STATE_MAC_H_ + +#import <QuartzCore/QuartzCore.h> + +namespace gfx { + +class ScopedCGContextSaveGState { + public: + explicit ScopedCGContextSaveGState(CGContextRef context) : context_(context) { + CGContextSaveGState(context_); + } + + ~ScopedCGContextSaveGState() { + CGContextRestoreGState(context_); + } + + private: + CGContextRef context_; + + DISALLOW_COPY_AND_ASSIGN(ScopedCGContextSaveGState); +}; + +} // namespace gfx + +#endif // UI_GFX_SCOPED_CG_CONTEXT_STATE_MAC_H_ diff --git a/ui/gfx/scoped_image.h b/ui/gfx/scoped_image.h new file mode 100644 index 0000000..58cb900f --- /dev/null +++ b/ui/gfx/scoped_image.h @@ -0,0 +1,147 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_SCOPED_IMAGE_H_ +#define UI_GFX_SCOPED_IMAGE_H_ +#pragma once + +#include "base/basictypes.h" +#include "build/build_config.h" +#include "gfx/native_widget_types.h" +#include "third_party/skia/include/core/SkBitmap.h" + +#if defined(OS_LINUX) +#include <glib-object.h> +#elif defined(OS_MACOSX) +#include "base/mac/mac_util.h" +#endif + +namespace gfx { + +namespace internal { + +// ScopedImage is class that encapsulates one of the three platform-specific +// images used: SkBitmap, NSImage, and GdkPixbuf. This is the abstract interface +// that all ScopedImages respond to. This wrapper expects to own the image it +// holds, unless it is Release()ed or Free()ed. +// +// This class is abstract and callers should use the specialized versions below, +// which are not in the internal namespace. +template <class ImageType> +class ScopedImage { + public: + virtual ~ScopedImage() {} + + // Frees the actual image that this boxes. + virtual void Free() = 0; + + // Returns the image that this boxes. + ImageType* Get() { + return image_; + } + + // Frees the current image and sets a new one. + void Set(ImageType* new_image) { + Free(); + image_ = new_image; + } + + // Returns the image this boxes and relinquishes ownership. + ImageType* Release() { + ImageType* tmp = image_; + image_ = NULL; + return tmp; + } + + protected: + explicit ScopedImage(ImageType* image) : image_(image) {} + ImageType* image_; + + private: + DISALLOW_COPY_AND_ASSIGN(ScopedImage); +}; + +} // namespace internal + +// Generic template. +template <class ImageType = gfx::NativeImageType> +class ScopedImage : public gfx::internal::ScopedImage<ImageType> { + public: + explicit ScopedImage(gfx::NativeImage image) + : gfx::internal::ScopedImage<ImageType>(image) {} + + private: + DISALLOW_COPY_AND_ASSIGN(ScopedImage<ImageType>); +}; + +// Specialization for SkBitmap on all platforms. +template <> +class ScopedImage<SkBitmap> : public gfx::internal::ScopedImage<SkBitmap> { + public: + explicit ScopedImage(SkBitmap* image) + : gfx::internal::ScopedImage<SkBitmap>(image) {} + virtual ~ScopedImage() { + Free(); + } + + virtual void Free() { + delete image_; + image_ = NULL; + } + + private: + DISALLOW_COPY_AND_ASSIGN(ScopedImage); +}; + +// Specialization for the NSImage type on Mac OS X. +#if defined(OS_MACOSX) +template <> +class ScopedImage<NSImage> : public gfx::internal::ScopedImage<NSImage> { + public: + explicit ScopedImage(NSImage* image) + : gfx::internal::ScopedImage<NSImage>(image) {} + virtual ~ScopedImage() { + Free(); + } + + virtual void Free() { + base::mac::NSObjectRelease(image_); + image_ = NULL; + } + + private: + DISALLOW_COPY_AND_ASSIGN(ScopedImage); +}; +#endif // defined(OS_MACOSX) + +// Specialization for the GdkPixbuf type on Linux. +#if defined(OS_LINUX) +template <> +class ScopedImage<GdkPixbuf> : public gfx::internal::ScopedImage<GdkPixbuf> { + public: + explicit ScopedImage(GdkPixbuf* image) + : gfx::internal::ScopedImage<GdkPixbuf>(image) {} + virtual ~ScopedImage() { + Free(); + } + + virtual void Free() { + if (image_) { + g_object_unref(image_); + image_ = NULL; + } + } + + private: + DISALLOW_COPY_AND_ASSIGN(ScopedImage); +}; +#endif // defined(OS_LINUX) + +// Typedef ScopedNativeImage to the default template argument. This allows for +// easy exchange between gfx::NativeImage and a gfx::ScopedNativeImage. +typedef ScopedImage<> ScopedNativeImage; + +} // namespace gfx + +#endif // UI_GFX_SCOPED_IMAGE_H_ diff --git a/ui/gfx/scoped_image_unittest.cc b/ui/gfx/scoped_image_unittest.cc new file mode 100644 index 0000000..8e8b312 --- /dev/null +++ b/ui/gfx/scoped_image_unittest.cc @@ -0,0 +1,98 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/scoped_ptr.h" +#include "gfx/scoped_image.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "third_party/skia/include/core/SkBitmap.h" + +#if defined(OS_LINUX) +#include "gfx/gtk_util.h" +#elif defined(OS_MACOSX) +#include "base/mac/mac_util.h" +#include "skia/ext/skia_utils_mac.h" +#endif + +namespace { + +class ScopedImageTest : public testing::Test { + public: + SkBitmap* CreateBitmap() { + SkBitmap* bitmap = new SkBitmap(); + bitmap->setConfig(SkBitmap::kARGB_8888_Config, 25, 25); + bitmap->allocPixels(); + bitmap->eraseRGB(255, 0, 0); + return bitmap; + } + + gfx::NativeImage CreateNativeImage() { + scoped_ptr<SkBitmap> bitmap(CreateBitmap()); +#if defined(OS_MACOSX) + NSImage* image = gfx::SkBitmapToNSImage(*(bitmap.get())); + base::mac::NSObjectRetain(image); + return image; +#elif defined(OS_LINUX) && !defined(TOOLKIT_VIEWS) + return gfx::GdkPixbufFromSkBitmap(bitmap.get()); +#else + return bitmap.release(); +#endif + } +}; + +TEST_F(ScopedImageTest, Initialize) { + gfx::ScopedImage<SkBitmap> image(CreateBitmap()); + EXPECT_TRUE(image.Get()); +} + +TEST_F(ScopedImageTest, Free) { + gfx::ScopedImage<SkBitmap> image(CreateBitmap()); + EXPECT_TRUE(image.Get()); + image.Free(); + EXPECT_FALSE(image.Get()); +} + +TEST_F(ScopedImageTest, Release) { + gfx::ScopedImage<SkBitmap> image(CreateBitmap()); + EXPECT_TRUE(image.Get()); + scoped_ptr<SkBitmap> bitmap(image.Release()); + EXPECT_FALSE(image.Get()); +} + +TEST_F(ScopedImageTest, Set) { + gfx::ScopedImage<SkBitmap> image(CreateBitmap()); + EXPECT_TRUE(image.Get()); + SkBitmap* image2 = CreateBitmap(); + image.Set(image2); + EXPECT_EQ(image2, image.Get()); +} + +TEST_F(ScopedImageTest, NativeInitialize) { + gfx::ScopedNativeImage image(CreateNativeImage()); + EXPECT_TRUE(image.Get()); +} + +TEST_F(ScopedImageTest, NativeFree) { + gfx::ScopedNativeImage image(CreateNativeImage()); + EXPECT_TRUE(image.Get()); + image.Free(); + EXPECT_FALSE(image.Get()); +} + +TEST_F(ScopedImageTest, NativeRelease) { + gfx::ScopedNativeImage image(CreateNativeImage()); + EXPECT_TRUE(image.Get()); + gfx::ScopedNativeImage image2(image.Release()); + EXPECT_FALSE(image.Get()); + EXPECT_TRUE(image2.Get()); +} + +TEST_F(ScopedImageTest, NativeSet) { + gfx::ScopedNativeImage image(CreateNativeImage()); + EXPECT_TRUE(image.Get()); + gfx::NativeImage image2 = CreateNativeImage(); + image.Set(image2); + EXPECT_EQ(image2, image.Get()); +} + +} // namespace diff --git a/ui/gfx/scrollbar_size.cc b/ui/gfx/scrollbar_size.cc new file mode 100644 index 0000000..426b0ac --- /dev/null +++ b/ui/gfx/scrollbar_size.cc @@ -0,0 +1,23 @@ +// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "gfx/scrollbar_size.h" + +#include "base/compiler_specific.h" + +#if defined(OS_WIN) +#include <windows.h> +#endif + +namespace gfx { + +int scrollbar_size() { +#if defined(OS_WIN) + return GetSystemMetrics(SM_CXVSCROLL); +#else + return 15; +#endif +} + +} // namespace gfx diff --git a/ui/gfx/scrollbar_size.h b/ui/gfx/scrollbar_size.h new file mode 100644 index 0000000..771b9db --- /dev/null +++ b/ui/gfx/scrollbar_size.h @@ -0,0 +1,18 @@ +// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_SCROLLBAR_SIZE_H_ +#define UI_GFX_SCROLLBAR_SIZE_H_ +#pragma once + +namespace gfx { + +// This should return the thickness, in pixels, of a scrollbar in web content. +// This needs to match the values in WebCore's +// ScrollbarThemeChromiumXXX.cpp::scrollbarThickness(). +int scrollbar_size(); + +} // namespace gfx + +#endif // UI_GFX_SCROLLBAR_SIZE_H_ diff --git a/ui/gfx/size.cc b/ui/gfx/size.cc new file mode 100644 index 0000000..6e5528e --- /dev/null +++ b/ui/gfx/size.cc @@ -0,0 +1,70 @@ +// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "gfx/size.h" + +#if defined(OS_WIN) +#include <windows.h> +#elif defined(OS_MACOSX) +#include <CoreGraphics/CGGeometry.h> +#endif + +#include <ostream> + +#include "base/logging.h" + +namespace gfx { + +Size::Size(int width, int height) { + set_width(width); + set_height(height); +} + +#if defined(OS_MACOSX) +Size::Size(const CGSize& s) { + set_width(s.width); + set_height(s.height); +} + +Size& Size::operator=(const CGSize& s) { + set_width(s.width); + set_height(s.height); + return *this; +} +#endif + +#if defined(OS_WIN) +SIZE Size::ToSIZE() const { + SIZE s; + s.cx = width_; + s.cy = height_; + return s; +} +#elif defined(OS_MACOSX) +CGSize Size::ToCGSize() const { + return CGSizeMake(width_, height_); +} +#endif + +void Size::set_width(int width) { + if (width < 0) { + NOTREACHED() << "negative width:" << width; + width = 0; + } + width_ = width; +} + +void Size::set_height(int height) { + if (height < 0) { + NOTREACHED() << "negative height:" << height; + height = 0; + } + height_ = height; +} + +std::ostream& operator<<(std::ostream& out, const gfx::Size& s) { + return out << s.width() << "x" << s.height(); +} + +} // namespace gfx diff --git a/ui/gfx/size.h b/ui/gfx/size.h new file mode 100644 index 0000000..55468eae --- /dev/null +++ b/ui/gfx/size.h @@ -0,0 +1,82 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_SIZE_H_ +#define UI_GFX_SIZE_H_ +#pragma once + +#include "build/build_config.h" + +#include <iosfwd> + +#if defined(OS_WIN) +typedef struct tagSIZE SIZE; +#elif defined(OS_MACOSX) +#include <ApplicationServices/ApplicationServices.h> +#endif + +namespace gfx { + +// A size has width and height values. +class Size { + public: + Size() : width_(0), height_(0) {} + Size(int width, int height); +#if defined(OS_MACOSX) + explicit Size(const CGSize& s); +#endif + + ~Size() {} + +#if defined(OS_MACOSX) + Size& operator=(const CGSize& s); +#endif + + int width() const { return width_; } + int height() const { return height_; } + + int GetArea() const { return width_ * height_; } + + void SetSize(int width, int height) { + set_width(width); + set_height(height); + } + + void Enlarge(int width, int height) { + set_width(width_ + width); + set_height(height_ + height); + } + + void set_width(int width); + void set_height(int 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 { + // Size doesn't allow negative dimensions, so testing for 0 is enough. + return (width_ == 0) || (height_ == 0); + } + +#if defined(OS_WIN) + SIZE ToSIZE() const; +#elif defined(OS_MACOSX) + CGSize ToCGSize() const; +#endif + + private: + int width_; + int height_; +}; + +std::ostream& operator<<(std::ostream& out, const gfx::Size& s); + +} // namespace gfx + +#endif // UI_GFX_SIZE_H_ diff --git a/ui/gfx/skbitmap_operations.cc b/ui/gfx/skbitmap_operations.cc new file mode 100644 index 0000000..6899553 --- /dev/null +++ b/ui/gfx/skbitmap_operations.cc @@ -0,0 +1,721 @@ +// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "gfx/skbitmap_operations.h" + +#include <algorithm> +#include <string.h> + +#include "base/logging.h" +#include "third_party/skia/include/core/SkBitmap.h" +#include "third_party/skia/include/core/SkCanvas.h" +#include "third_party/skia/include/core/SkColorPriv.h" +#include "third_party/skia/include/core/SkUnPreMultiply.h" + +// static +SkBitmap SkBitmapOperations::CreateInvertedBitmap(const SkBitmap& image) { + DCHECK(image.config() == SkBitmap::kARGB_8888_Config); + + SkAutoLockPixels lock_image(image); + + SkBitmap inverted; + inverted.setConfig(SkBitmap::kARGB_8888_Config, image.width(), image.height(), + 0); + inverted.allocPixels(); + inverted.eraseARGB(0, 0, 0, 0); + + for (int y = 0; y < image.height(); ++y) { + uint32* image_row = image.getAddr32(0, y); + uint32* dst_row = inverted.getAddr32(0, y); + + for (int x = 0; x < image.width(); ++x) { + uint32 image_pixel = image_row[x]; + dst_row[x] = (image_pixel & 0xFF000000) | + (0x00FFFFFF - (image_pixel & 0x00FFFFFF)); + } + } + + return inverted; +} + +// static +SkBitmap SkBitmapOperations::CreateSuperimposedBitmap(const SkBitmap& first, + const SkBitmap& second) { + DCHECK(first.width() == second.width()); + DCHECK(first.height() == second.height()); + DCHECK(first.bytesPerPixel() == second.bytesPerPixel()); + DCHECK(first.config() == SkBitmap::kARGB_8888_Config); + + SkAutoLockPixels lock_first(first); + SkAutoLockPixels lock_second(second); + + SkBitmap superimposed; + superimposed.setConfig(SkBitmap::kARGB_8888_Config, + first.width(), first.height()); + superimposed.allocPixels(); + superimposed.eraseARGB(0, 0, 0, 0); + + SkCanvas canvas(superimposed); + + SkRect rect; + rect.fLeft = 0; + rect.fTop = 0; + rect.fRight = SkIntToScalar(first.width()); + rect.fBottom = SkIntToScalar(first.height()); + + canvas.drawBitmapRect(first, NULL, rect); + canvas.drawBitmapRect(second, NULL, rect); + + return superimposed; +} + +// static +SkBitmap SkBitmapOperations::CreateBlendedBitmap(const SkBitmap& first, + const SkBitmap& second, + double alpha) { + DCHECK((alpha >= 0) && (alpha <= 1)); + 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; +} + +// static +SkBitmap SkBitmapOperations::CreateMaskedBitmap(const SkBitmap& rgb, + const SkBitmap& alpha) { + DCHECK(rgb.width() == alpha.width()); + DCHECK(rgb.height() == alpha.height()); + DCHECK(rgb.bytesPerPixel() == alpha.bytesPerPixel()); + DCHECK(rgb.config() == SkBitmap::kARGB_8888_Config); + DCHECK(alpha.config() == SkBitmap::kARGB_8888_Config); + + SkBitmap masked; + masked.setConfig(SkBitmap::kARGB_8888_Config, rgb.width(), rgb.height(), 0); + masked.allocPixels(); + masked.eraseARGB(0, 0, 0, 0); + + SkAutoLockPixels lock_rgb(rgb); + SkAutoLockPixels lock_alpha(alpha); + SkAutoLockPixels lock_masked(masked); + + for (int y = 0; y < masked.height(); ++y) { + uint32* rgb_row = rgb.getAddr32(0, y); + uint32* alpha_row = alpha.getAddr32(0, y); + uint32* dst_row = masked.getAddr32(0, y); + + for (int x = 0; x < masked.width(); ++x) { + SkColor rgb_pixel = SkUnPreMultiply::PMColorToColor(rgb_row[x]); + int alpha = SkAlphaMul(SkColorGetA(rgb_pixel), SkColorGetA(alpha_row[x])); + dst_row[x] = SkColorSetARGB(alpha, + SkAlphaMul(SkColorGetR(rgb_pixel), alpha), + SkAlphaMul(SkColorGetG(rgb_pixel), alpha), + SkAlphaMul(SkColorGetB(rgb_pixel), alpha)); + } + } + + return masked; +} + +// static +SkBitmap SkBitmapOperations::CreateButtonBackground(SkColor color, + const SkBitmap& image, + const SkBitmap& mask) { + DCHECK(image.config() == SkBitmap::kARGB_8888_Config); + DCHECK(mask.config() == SkBitmap::kARGB_8888_Config); + + SkBitmap background; + background.setConfig( + SkBitmap::kARGB_8888_Config, mask.width(), mask.height(), 0); + background.allocPixels(); + + double bg_a = SkColorGetA(color); + double bg_r = SkColorGetR(color); + double bg_g = SkColorGetG(color); + double bg_b = SkColorGetB(color); + + SkAutoLockPixels lock_mask(mask); + SkAutoLockPixels lock_image(image); + SkAutoLockPixels lock_background(background); + + for (int y = 0; y < mask.height(); ++y) { + uint32* dst_row = background.getAddr32(0, y); + uint32* image_row = image.getAddr32(0, y % image.height()); + uint32* mask_row = mask.getAddr32(0, y); + + for (int x = 0; x < mask.width(); ++x) { + uint32 image_pixel = image_row[x % image.width()]; + + double img_a = SkColorGetA(image_pixel); + double img_r = SkColorGetR(image_pixel); + double img_g = SkColorGetG(image_pixel); + double img_b = SkColorGetB(image_pixel); + + double img_alpha = static_cast<double>(img_a) / 255.0; + double img_inv = 1 - img_alpha; + + double mask_a = static_cast<double>(SkColorGetA(mask_row[x])) / 255.0; + + dst_row[x] = SkColorSetARGB( + static_cast<int>(std::min(255.0, bg_a + img_a) * mask_a), + static_cast<int>(((bg_r * img_inv) + (img_r * img_alpha)) * mask_a), + static_cast<int>(((bg_g * img_inv) + (img_g * img_alpha)) * mask_a), + static_cast<int>(((bg_b * img_inv) + (img_b * img_alpha)) * mask_a)); + } + } + + return background; +} + +namespace { +namespace HSLShift { + +// TODO(viettrungluu): Some things have yet to be optimized at all. + +// Notes on and conventions used in the following code +// +// Conventions: +// - R, G, B, A = obvious; as variables: |r|, |g|, |b|, |a| (see also below) +// - H, S, L = obvious; as variables: |h|, |s|, |l| (see also below) +// - variables derived from S, L shift parameters: |sdec| and |sinc| for S +// increase and decrease factors, |ldec| and |linc| for L (see also below) +// +// To try to optimize HSL shifts, we do several things: +// - Avoid unpremultiplying (then processing) then premultiplying. This means +// that R, G, B values (and also L, but not H and S) should be treated as +// having a range of 0..A (where A is alpha). +// - Do things in integer/fixed-point. This avoids costly conversions between +// floating-point and integer, though I should study the tradeoff more +// carefully (presumably, at some point of processing complexity, converting +// and processing using simpler floating-point code will begin to win in +// performance). Also to be studied is the speed/type of floating point +// conversions; see, e.g., <http://www.stereopsis.com/sree/fpu2006.html>. +// +// Conventions for fixed-point arithmetic +// - Each function has a constant denominator (called |den|, which should be a +// power of 2), appropriate for the computations done in that function. +// - A value |x| is then typically represented by a numerator, named |x_num|, +// so that its actual value is |x_num / den| (casting to floating-point +// before division). +// - To obtain |x_num| from |x|, simply multiply by |den|, i.e., |x_num = x * +// den| (casting appropriately). +// - When necessary, a value |x| may also be represented as a numerator over +// the denominator squared (set |den2 = den * den|). In such a case, the +// corresponding variable is called |x_num2| (so that its actual value is +// |x_num^2 / den2|. +// - The representation of the product of |x| and |y| is be called |x_y_num| if +// |x * y == x_y_num / den|, and |xy_num2| if |x * y == x_y_num2 / den2|. In +// the latter case, notice that one can calculate |x_y_num2 = x_num * y_num|. + +// Routine used to process a line; typically specialized for specific kinds of +// HSL shifts (to optimize). +typedef void (*LineProcessor)(color_utils::HSL, + const SkPMColor*, + SkPMColor*, + int width); + +enum OperationOnH { kOpHNone = 0, kOpHShift, kNumHOps }; +enum OperationOnS { kOpSNone = 0, kOpSDec, kOpSInc, kNumSOps }; +enum OperationOnL { kOpLNone = 0, kOpLDec, kOpLInc, kNumLOps }; + +// Epsilon used to judge when shift values are close enough to various critical +// values (typically 0.5, which yields a no-op for S and L shifts. 1/256 should +// be small enough, but let's play it safe> +const double epsilon = 0.0005; + +// Line processor: default/universal (i.e., old-school). +void LineProcDefault(color_utils::HSL hsl_shift, const SkPMColor* in, + SkPMColor* out, int width) { + for (int x = 0; x < width; x++) { + out[x] = SkPreMultiplyColor(color_utils::HSLShift( + SkUnPreMultiply::PMColorToColor(in[x]), hsl_shift)); + } +} + +// Line processor: no-op (i.e., copy). +void LineProcCopy(color_utils::HSL hsl_shift, const SkPMColor* in, + SkPMColor* out, int width) { + DCHECK(hsl_shift.h < 0); + DCHECK(hsl_shift.s < 0 || fabs(hsl_shift.s - 0.5) < HSLShift::epsilon); + DCHECK(hsl_shift.l < 0 || fabs(hsl_shift.l - 0.5) < HSLShift::epsilon); + memcpy(out, in, static_cast<size_t>(width) * sizeof(out[0])); +} + +// Line processor: H no-op, S no-op, L decrease. +void LineProcHnopSnopLdec(color_utils::HSL hsl_shift, const SkPMColor* in, + SkPMColor* out, int width) { + const uint32_t den = 65536; + + DCHECK(hsl_shift.h < 0); + DCHECK(hsl_shift.s < 0 || fabs(hsl_shift.s - 0.5) < HSLShift::epsilon); + DCHECK(hsl_shift.l <= 0.5 - HSLShift::epsilon && hsl_shift.l >= 0); + + uint32_t ldec_num = static_cast<uint32_t>(hsl_shift.l * 2 * den); + for (int x = 0; x < width; x++) { + uint32_t a = SkGetPackedA32(in[x]); + uint32_t r = SkGetPackedR32(in[x]); + uint32_t g = SkGetPackedG32(in[x]); + uint32_t b = SkGetPackedB32(in[x]); + r = r * ldec_num / den; + g = g * ldec_num / den; + b = b * ldec_num / den; + out[x] = SkPackARGB32(a, r, g, b); + } +} + +// Line processor: H no-op, S no-op, L increase. +void LineProcHnopSnopLinc(color_utils::HSL hsl_shift, const SkPMColor* in, + SkPMColor* out, int width) { + const uint32_t den = 65536; + + DCHECK(hsl_shift.h < 0); + DCHECK(hsl_shift.s < 0 || fabs(hsl_shift.s - 0.5) < HSLShift::epsilon); + DCHECK(hsl_shift.l >= 0.5 + HSLShift::epsilon && hsl_shift.l <= 1); + + uint32_t linc_num = static_cast<uint32_t>((hsl_shift.l - 0.5) * 2 * den); + for (int x = 0; x < width; x++) { + uint32_t a = SkGetPackedA32(in[x]); + uint32_t r = SkGetPackedR32(in[x]); + uint32_t g = SkGetPackedG32(in[x]); + uint32_t b = SkGetPackedB32(in[x]); + r += (a - r) * linc_num / den; + g += (a - g) * linc_num / den; + b += (a - b) * linc_num / den; + out[x] = SkPackARGB32(a, r, g, b); + } +} + +// Saturation changes modifications in RGB +// +// (Note that as a further complication, the values we deal in are +// premultiplied, so R/G/B values must be in the range 0..A. For mathematical +// purposes, one may as well use r=R/A, g=G/A, b=B/A. Without loss of +// generality, assume that R/G/B values are in the range 0..1.) +// +// Let Max = max(R,G,B), Min = min(R,G,B), and Med be the median value. Then L = +// (Max+Min)/2. If L is to remain constant, Max+Min must also remain constant. +// +// For H to remain constant, first, the (numerical) order of R/G/B (from +// smallest to largest) must remain the same. Second, all the ratios +// (R-G)/(Max-Min), (R-B)/(Max-Min), (G-B)/(Max-Min) must remain constant (of +// course, if Max = Min, then S = 0 and no saturation change is well-defined, +// since H is not well-defined). +// +// Let C_max be a colour with value Max, C_min be one with value Min, and C_med +// the remaining colour. Increasing saturation (to the maximum) is accomplished +// by increasing the value of C_max while simultaneously decreasing C_min and +// changing C_med so that the ratios are maintained; for the latter, it suffices +// to keep (C_med-C_min)/(C_max-C_min) constant (and equal to +// (Med-Min)/(Max-Min)). + +// Line processor: H no-op, S decrease, L no-op. +void LineProcHnopSdecLnop(color_utils::HSL hsl_shift, const SkPMColor* in, + SkPMColor* out, int width) { + DCHECK(hsl_shift.h < 0); + DCHECK(hsl_shift.s >= 0 && hsl_shift.s <= 0.5 - HSLShift::epsilon); + DCHECK(hsl_shift.l < 0 || fabs(hsl_shift.l - 0.5) < HSLShift::epsilon); + + const int32_t denom = 65536; + int32_t s_numer = static_cast<int32_t>(hsl_shift.s * 2 * denom); + for (int x = 0; x < width; x++) { + int32_t a = static_cast<int32_t>(SkGetPackedA32(in[x])); + int32_t r = static_cast<int32_t>(SkGetPackedR32(in[x])); + int32_t g = static_cast<int32_t>(SkGetPackedG32(in[x])); + int32_t b = static_cast<int32_t>(SkGetPackedB32(in[x])); + + int32_t vmax, vmin; + if (r > g) { // This uses 3 compares rather than 4. + vmax = std::max(r, b); + vmin = std::min(g, b); + } else { + vmax = std::max(g, b); + vmin = std::min(r, b); + } + + // Use denom * L to avoid rounding. + int32_t denom_l = (vmax + vmin) * (denom / 2); + int32_t s_numer_l = (vmax + vmin) * s_numer / 2; + + r = (denom_l + r * s_numer - s_numer_l) / denom; + g = (denom_l + g * s_numer - s_numer_l) / denom; + b = (denom_l + b * s_numer - s_numer_l) / denom; + out[x] = SkPackARGB32(a, r, g, b); + } +} + +// Line processor: H no-op, S decrease, L decrease. +void LineProcHnopSdecLdec(color_utils::HSL hsl_shift, const SkPMColor* in, + SkPMColor* out, int width) { + DCHECK(hsl_shift.h < 0); + DCHECK(hsl_shift.s >= 0 && hsl_shift.s <= 0.5 - HSLShift::epsilon); + DCHECK(hsl_shift.l >= 0 && hsl_shift.l <= 0.5 - HSLShift::epsilon); + + // Can't be too big since we need room for denom*denom and a bit for sign. + const int32_t denom = 1024; + int32_t l_numer = static_cast<int32_t>(hsl_shift.l * 2 * denom); + int32_t s_numer = static_cast<int32_t>(hsl_shift.s * 2 * denom); + for (int x = 0; x < width; x++) { + int32_t a = static_cast<int32_t>(SkGetPackedA32(in[x])); + int32_t r = static_cast<int32_t>(SkGetPackedR32(in[x])); + int32_t g = static_cast<int32_t>(SkGetPackedG32(in[x])); + int32_t b = static_cast<int32_t>(SkGetPackedB32(in[x])); + + int32_t vmax, vmin; + if (r > g) { // This uses 3 compares rather than 4. + vmax = std::max(r, b); + vmin = std::min(g, b); + } else { + vmax = std::max(g, b); + vmin = std::min(r, b); + } + + // Use denom * L to avoid rounding. + int32_t denom_l = (vmax + vmin) * (denom / 2); + int32_t s_numer_l = (vmax + vmin) * s_numer / 2; + + r = (denom_l + r * s_numer - s_numer_l) * l_numer / (denom * denom); + g = (denom_l + g * s_numer - s_numer_l) * l_numer / (denom * denom); + b = (denom_l + b * s_numer - s_numer_l) * l_numer / (denom * denom); + out[x] = SkPackARGB32(a, r, g, b); + } +} + +// Line processor: H no-op, S decrease, L increase. +void LineProcHnopSdecLinc(color_utils::HSL hsl_shift, const SkPMColor* in, + SkPMColor* out, int width) { + DCHECK(hsl_shift.h < 0); + DCHECK(hsl_shift.s >= 0 && hsl_shift.s <= 0.5 - HSLShift::epsilon); + DCHECK(hsl_shift.l >= 0.5 + HSLShift::epsilon && hsl_shift.l <= 1); + + // Can't be too big since we need room for denom*denom and a bit for sign. + const int32_t denom = 1024; + int32_t l_numer = static_cast<int32_t>((hsl_shift.l - 0.5) * 2 * denom); + int32_t s_numer = static_cast<int32_t>(hsl_shift.s * 2 * denom); + for (int x = 0; x < width; x++) { + int32_t a = static_cast<int32_t>(SkGetPackedA32(in[x])); + int32_t r = static_cast<int32_t>(SkGetPackedR32(in[x])); + int32_t g = static_cast<int32_t>(SkGetPackedG32(in[x])); + int32_t b = static_cast<int32_t>(SkGetPackedB32(in[x])); + + int32_t vmax, vmin; + if (r > g) { // This uses 3 compares rather than 4. + vmax = std::max(r, b); + vmin = std::min(g, b); + } else { + vmax = std::max(g, b); + vmin = std::min(r, b); + } + + // Use denom * L to avoid rounding. + int32_t denom_l = (vmax + vmin) * (denom / 2); + int32_t s_numer_l = (vmax + vmin) * s_numer / 2; + + r = denom_l + r * s_numer - s_numer_l; + g = denom_l + g * s_numer - s_numer_l; + b = denom_l + b * s_numer - s_numer_l; + + r = (r * denom + (a * denom - r) * l_numer) / (denom * denom); + g = (g * denom + (a * denom - g) * l_numer) / (denom * denom); + b = (b * denom + (a * denom - b) * l_numer) / (denom * denom); + out[x] = SkPackARGB32(a, r, g, b); + } +} + +const LineProcessor kLineProcessors[kNumHOps][kNumSOps][kNumLOps] = { + { // H: kOpHNone + { // S: kOpSNone + LineProcCopy, // L: kOpLNone + LineProcHnopSnopLdec, // L: kOpLDec + LineProcHnopSnopLinc // L: kOpLInc + }, + { // S: kOpSDec + LineProcHnopSdecLnop, // L: kOpLNone + LineProcHnopSdecLdec, // L: kOpLDec + LineProcHnopSdecLinc // L: kOpLInc + }, + { // S: kOpSInc + LineProcDefault, // L: kOpLNone + LineProcDefault, // L: kOpLDec + LineProcDefault // L: kOpLInc + } + }, + { // H: kOpHShift + { // S: kOpSNone + LineProcDefault, // L: kOpLNone + LineProcDefault, // L: kOpLDec + LineProcDefault // L: kOpLInc + }, + { // S: kOpSDec + LineProcDefault, // L: kOpLNone + LineProcDefault, // L: kOpLDec + LineProcDefault // L: kOpLInc + }, + { // S: kOpSInc + LineProcDefault, // L: kOpLNone + LineProcDefault, // L: kOpLDec + LineProcDefault // L: kOpLInc + } + } +}; + +} // namespace HSLShift +} // namespace + +// static +SkBitmap SkBitmapOperations::CreateHSLShiftedBitmap( + const SkBitmap& bitmap, + color_utils::HSL hsl_shift) { + // Default to NOPs. + HSLShift::OperationOnH H_op = HSLShift::kOpHNone; + HSLShift::OperationOnS S_op = HSLShift::kOpSNone; + HSLShift::OperationOnL L_op = HSLShift::kOpLNone; + + if (hsl_shift.h >= 0 && hsl_shift.h <= 1) + H_op = HSLShift::kOpHShift; + + // Saturation shift: 0 -> fully desaturate, 0.5 -> NOP, 1 -> fully saturate. + if (hsl_shift.s >= 0 && hsl_shift.s <= (0.5 - HSLShift::epsilon)) + S_op = HSLShift::kOpSDec; + else if (hsl_shift.s >= (0.5 + HSLShift::epsilon)) + S_op = HSLShift::kOpSInc; + + // Lightness shift: 0 -> black, 0.5 -> NOP, 1 -> white. + if (hsl_shift.l >= 0 && hsl_shift.l <= (0.5 - HSLShift::epsilon)) + L_op = HSLShift::kOpLDec; + else if (hsl_shift.l >= (0.5 + HSLShift::epsilon)) + L_op = HSLShift::kOpLInc; + + HSLShift::LineProcessor line_proc = + HSLShift::kLineProcessors[H_op][S_op][L_op]; + + DCHECK(bitmap.empty() == false); + DCHECK(bitmap.config() == SkBitmap::kARGB_8888_Config); + + SkBitmap shifted; + shifted.setConfig(SkBitmap::kARGB_8888_Config, bitmap.width(), + bitmap.height(), 0); + shifted.allocPixels(); + shifted.eraseARGB(0, 0, 0, 0); + shifted.setIsOpaque(false); + + SkAutoLockPixels lock_bitmap(bitmap); + SkAutoLockPixels lock_shifted(shifted); + + // Loop through the pixels of the original bitmap. + for (int y = 0; y < bitmap.height(); ++y) { + SkPMColor* pixels = bitmap.getAddr32(0, y); + SkPMColor* tinted_pixels = shifted.getAddr32(0, y); + + (*line_proc)(hsl_shift, pixels, tinted_pixels, bitmap.width()); + } + + return shifted; +} + +// static +SkBitmap SkBitmapOperations::CreateTiledBitmap(const SkBitmap& source, + int src_x, int src_y, + int dst_w, int dst_h) { + DCHECK(source.getConfig() == SkBitmap::kARGB_8888_Config); + + SkBitmap cropped; + cropped.setConfig(SkBitmap::kARGB_8888_Config, dst_w, dst_h, 0); + cropped.allocPixels(); + cropped.eraseARGB(0, 0, 0, 0); + + SkAutoLockPixels lock_source(source); + SkAutoLockPixels lock_cropped(cropped); + + // Loop through the pixels of the original bitmap. + for (int y = 0; y < dst_h; ++y) { + int y_pix = (src_y + y) % source.height(); + while (y_pix < 0) + y_pix += source.height(); + + uint32* source_row = source.getAddr32(0, y_pix); + uint32* dst_row = cropped.getAddr32(0, y); + + for (int x = 0; x < dst_w; ++x) { + int x_pix = (src_x + x) % source.width(); + while (x_pix < 0) + x_pix += source.width(); + + dst_row[x] = source_row[x_pix]; + } + } + + return cropped; +} + +// static +SkBitmap SkBitmapOperations::DownsampleByTwoUntilSize(const SkBitmap& bitmap, + int min_w, int min_h) { + if ((bitmap.width() <= min_w) || (bitmap.height() <= min_h) || + (min_w < 0) || (min_h < 0)) + return bitmap; + + // Since bitmaps are refcounted, this copy will be fast. + SkBitmap current = bitmap; + while ((current.width() >= min_w * 2) && (current.height() >= min_h * 2) && + (current.width() > 1) && (current.height() > 1)) + current = DownsampleByTwo(current); + return current; +} + +// static +SkBitmap SkBitmapOperations::DownsampleByTwo(const SkBitmap& bitmap) { + // Handle the nop case. + if ((bitmap.width() <= 1) || (bitmap.height() <= 1)) + return bitmap; + + SkBitmap result; + result.setConfig(SkBitmap::kARGB_8888_Config, + (bitmap.width() + 1) / 2, (bitmap.height() + 1) / 2); + result.allocPixels(); + + SkAutoLockPixels lock(bitmap); + for (int dest_y = 0; dest_y < result.height(); ++dest_y) { + for (int dest_x = 0; dest_x < result.width(); ++dest_x) { + // This code is based on downsampleby2_proc32 in SkBitmap.cpp. It is very + // clever in that it does two channels at once: alpha and green ("ag") + // and red and blue ("rb"). Each channel gets averaged across 4 pixels + // to get the result. + int src_x = dest_x << 1; + int src_y = dest_y << 1; + const SkPMColor* cur_src = bitmap.getAddr32(src_x, src_y); + SkPMColor tmp, ag, rb; + + // Top left pixel of the 2x2 block. + tmp = *cur_src; + ag = (tmp >> 8) & 0xFF00FF; + rb = tmp & 0xFF00FF; + if (src_x < (bitmap.width() - 1)) + ++cur_src; + + // Top right pixel of the 2x2 block. + tmp = *cur_src; + ag += (tmp >> 8) & 0xFF00FF; + rb += tmp & 0xFF00FF; + if (src_y < (bitmap.height() - 1)) + cur_src = bitmap.getAddr32(src_x, src_y + 1); + else + cur_src = bitmap.getAddr32(src_x, src_y); // Move back to the first. + + // Bottom left pixel of the 2x2 block. + tmp = *cur_src; + ag += (tmp >> 8) & 0xFF00FF; + rb += tmp & 0xFF00FF; + if (src_x < (bitmap.width() - 1)) + ++cur_src; + + // Bottom right pixel of the 2x2 block. + tmp = *cur_src; + ag += (tmp >> 8) & 0xFF00FF; + rb += tmp & 0xFF00FF; + + // Put the channels back together, dividing each by 4 to get the average. + // |ag| has the alpha and green channels shifted right by 8 bits from + // there they should end up, so shifting left by 6 gives them in the + // correct position divided by 4. + *result.getAddr32(dest_x, dest_y) = + ((rb >> 2) & 0xFF00FF) | ((ag << 6) & 0xFF00FF00); + } + } + + return result; +} + +// static +SkBitmap SkBitmapOperations::UnPreMultiply(const SkBitmap& bitmap) { + if (bitmap.isNull()) + return bitmap; + if (bitmap.isOpaque()) + return bitmap; + + SkBitmap opaque_bitmap; + opaque_bitmap.setConfig(bitmap.config(), bitmap.width(), bitmap.height()); + opaque_bitmap.allocPixels(); + + { + SkAutoLockPixels bitmap_lock(bitmap); + SkAutoLockPixels opaque_bitmap_lock(opaque_bitmap); + for (int y = 0; y < opaque_bitmap.height(); y++) { + for (int x = 0; x < opaque_bitmap.width(); x++) { + uint32 src_pixel = *bitmap.getAddr32(x, y); + uint32* dst_pixel = opaque_bitmap.getAddr32(x, y); + SkColor unmultiplied = SkUnPreMultiply::PMColorToColor(src_pixel); + *dst_pixel = unmultiplied; + } + } + } + + opaque_bitmap.setIsOpaque(true); + return opaque_bitmap; +} + +// static +SkBitmap SkBitmapOperations::CreateTransposedBtmap(const SkBitmap& image) { + DCHECK(image.config() == SkBitmap::kARGB_8888_Config); + + SkAutoLockPixels lock_image(image); + + SkBitmap transposed; + transposed.setConfig( + SkBitmap::kARGB_8888_Config, image.height(), image.width(), 0); + transposed.allocPixels(); + transposed.eraseARGB(0, 0, 0, 0); + + for (int y = 0; y < image.height(); ++y) { + uint32* image_row = image.getAddr32(0, y); + for (int x = 0; x < image.width(); ++x) { + uint32* dst = transposed.getAddr32(y, x); + *dst = image_row[x]; + } + } + + return transposed; +} + diff --git a/ui/gfx/skbitmap_operations.h b/ui/gfx/skbitmap_operations.h new file mode 100644 index 0000000..12ae86a --- /dev/null +++ b/ui/gfx/skbitmap_operations.h @@ -0,0 +1,102 @@ +// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_SKBITMAP_OPERATIONS_H_ +#define UI_GFX_SKBITMAP_OPERATIONS_H_ +#pragma once + +#include "base/gtest_prod_util.h" +#include "gfx/color_utils.h" + +class SkBitmap; + +class SkBitmapOperations { + public: + // Create a bitmap that is an inverted image of the passed in image. + // Each color becomes its inverse in the color wheel. So (255, 15, 0) becomes + // (0, 240, 255). The alpha value is not inverted. + static SkBitmap CreateInvertedBitmap(const SkBitmap& image); + + // Create a bitmap that is a superimposition of the second bitmap on top of + // the first. The provided bitmaps must use have the kARGB_8888_Config config + // and be of equal dimensions. + static SkBitmap CreateSuperimposedBitmap(const SkBitmap& first, + const SkBitmap& second); + + // 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); + + // Create a bitmap that is the original bitmap masked out by the mask defined + // in the alpha bitmap. The images must use the kARGB_8888_Config config and + // be of equal dimensions. + static SkBitmap CreateMaskedBitmap(const SkBitmap& first, + const SkBitmap& alpha); + + // We create a button background image by compositing the color and image + // together, then applying the mask. This is a highly specialized composite + // operation that is the equivalent of drawing a background in |color|, + // tiling |image| over the top, and then masking the result out with |mask|. + // The images must use kARGB_8888_Config config. + static SkBitmap CreateButtonBackground(SkColor color, + const SkBitmap& image, + const SkBitmap& mask); + + // Shift a bitmap's HSL values. The shift values are in the range of 0-1, + // with the option to specify -1 for 'no change'. The shift values are + // defined as: + // hsl_shift[0] (hue): The absolute hue value for the image - 0 and 1 map + // to 0 and 360 on the hue color wheel (red). + // hsl_shift[1] (saturation): A saturation shift for the image, with the + // following key values: + // 0 = remove all color. + // 0.5 = leave unchanged. + // 1 = fully saturate the image. + // hsl_shift[2] (lightness): A lightness shift for the image, with the + // following key values: + // 0 = remove all lightness (make all pixels black). + // 0.5 = leave unchanged. + // 1 = full lightness (make all pixels white). + static SkBitmap CreateHSLShiftedBitmap(const SkBitmap& bitmap, + color_utils::HSL hsl_shift); + + // Create a bitmap that is cropped from another bitmap. This is special + // because it tiles the original bitmap, so your coordinates can extend + // outside the bounds of the original image. + static SkBitmap CreateTiledBitmap(const SkBitmap& bitmap, + int src_x, int src_y, + int dst_w, int dst_h); + + // Iteratively downsamples by 2 until the bitmap is no smaller than the + // input size. The normal use of this is to downsample the bitmap "close" to + // the final size, and then use traditional resampling on the result. + // Because the bitmap will be closer to the final size, it will be faster, + // and linear interpolation will generally work well as a second step. + static SkBitmap DownsampleByTwoUntilSize(const SkBitmap& bitmap, + int min_w, int min_h); + + // Makes a bitmap half has large in each direction by averaging groups of + // 4 pixels. This is one step in generating a mipmap. + static SkBitmap DownsampleByTwo(const SkBitmap& bitmap); + + // Unpremultiplies all pixels in |bitmap|. You almost never want to call + // this, as |SkBitmap|s are always premultiplied by conversion. Call this + // only if you will pass the bitmap's data into a system function that + // doesn't expect premultiplied colors. + static SkBitmap UnPreMultiply(const SkBitmap& bitmap); + + // Transpose the pixels in |bitmap| by swapping x and y. + static SkBitmap CreateTransposedBtmap(const SkBitmap& bitmap); + + private: + SkBitmapOperations(); // Class for scoping only. + + FRIEND_TEST_ALL_PREFIXES(SkBitmapOperationsTest, DownsampleByTwo); + FRIEND_TEST_ALL_PREFIXES(SkBitmapOperationsTest, DownsampleByTwoSmall); +}; + +#endif // UI_GFX_SKBITMAP_OPERATIONS_H_ diff --git a/ui/gfx/skbitmap_operations_unittest.cc b/ui/gfx/skbitmap_operations_unittest.cc new file mode 100644 index 0000000..bcad287 --- /dev/null +++ b/ui/gfx/skbitmap_operations_unittest.cc @@ -0,0 +1,517 @@ +// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "gfx/skbitmap_operations.h" + +#include "testing/gtest/include/gtest/gtest.h" +#include "third_party/skia/include/core/SkBitmap.h" +#include "third_party/skia/include/core/SkColorPriv.h" +#include "third_party/skia/include/core/SkUnPreMultiply.h" + +namespace { + +// 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. +inline 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; +} + +inline bool MultipliedColorsClose(uint32_t a, uint32_t b) { + return ColorsClose(SkUnPreMultiply::PMColorToColor(a), + SkUnPreMultiply::PMColorToColor(b)); +} + +bool BitmapsClose(const SkBitmap& a, const SkBitmap& b) { + SkAutoLockPixels a_lock(a); + SkAutoLockPixels b_lock(b); + + for (int y = 0; y < a.height(); y++) { + for (int x = 0; x < a.width(); x++) { + SkColor a_pixel = *a.getAddr32(x, y); + SkColor b_pixel = *b.getAddr32(x, y); + if (!ColorsClose(a_pixel, b_pixel)) + return false; + } + } + return true; +} + +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); + } +} + +// The reference (i.e., old) implementation of |CreateHSLShiftedBitmap()|. +SkBitmap ReferenceCreateHSLShiftedBitmap( + const SkBitmap& bitmap, + color_utils::HSL hsl_shift) { + SkBitmap shifted; + shifted.setConfig(SkBitmap::kARGB_8888_Config, bitmap.width(), + bitmap.height(), 0); + shifted.allocPixels(); + shifted.eraseARGB(0, 0, 0, 0); + shifted.setIsOpaque(false); + + SkAutoLockPixels lock_bitmap(bitmap); + SkAutoLockPixels lock_shifted(shifted); + + // Loop through the pixels of the original bitmap. + for (int y = 0; y < bitmap.height(); ++y) { + SkPMColor* pixels = bitmap.getAddr32(0, y); + SkPMColor* tinted_pixels = shifted.getAddr32(0, y); + + for (int x = 0; x < bitmap.width(); ++x) { + tinted_pixels[x] = SkPreMultiplyColor(color_utils::HSLShift( + SkUnPreMultiply::PMColorToColor(pixels[x]), hsl_shift)); + } + } + + return shifted; +} + +} // namespace + +// Invert bitmap and verify the each pixel is inverted and the alpha value is +// not changed. +TEST(SkBitmapOperationsTest, CreateInvertedBitmap) { + int src_w = 16, src_h = 16; + SkBitmap src; + src.setConfig(SkBitmap::kARGB_8888_Config, src_w, src_h); + src.allocPixels(); + + for (int y = 0; y < src_h; y++) { + for (int x = 0; x < src_w; x++) { + int i = y * src_w + x; + *src.getAddr32(x, y) = + SkColorSetARGB((255 - i) % 255, i % 255, i * 4 % 255, 0); + } + } + + SkBitmap inverted = SkBitmapOperations::CreateInvertedBitmap(src); + SkAutoLockPixels src_lock(src); + SkAutoLockPixels inverted_lock(inverted); + + for (int y = 0; y < src_h; y++) { + for (int x = 0; x < src_w; x++) { + int i = y * src_w + x; + EXPECT_EQ(static_cast<unsigned int>((255 - i) % 255), + SkColorGetA(*inverted.getAddr32(x, y))); + EXPECT_EQ(static_cast<unsigned int>(255 - (i % 255)), + SkColorGetR(*inverted.getAddr32(x, y))); + EXPECT_EQ(static_cast<unsigned int>(255 - (i * 4 % 255)), + SkColorGetG(*inverted.getAddr32(x, y))); + EXPECT_EQ(static_cast<unsigned int>(255), + SkColorGetB(*inverted.getAddr32(x, y))); + } + } +} + +// Blend two bitmaps together at 50% alpha and verify that the result +// is the middle-blend of the two. +TEST(SkBitmapOperationsTest, CreateBlendedBitmap) { + int src_w = 16, src_h = 16; + SkBitmap src_a; + src_a.setConfig(SkBitmap::kARGB_8888_Config, src_w, src_h); + src_a.allocPixels(); + + SkBitmap src_b; + src_b.setConfig(SkBitmap::kARGB_8888_Config, src_w, src_h); + src_b.allocPixels(); + + for (int y = 0, i = 0; y < src_h; y++) { + for (int x = 0; x < src_w; x++) { + *src_a.getAddr32(x, y) = SkColorSetARGB(255, 0, i * 2 % 255, i % 255); + *src_b.getAddr32(x, y) = + SkColorSetARGB((255 - i) % 255, i % 255, i * 4 % 255, 0); + i++; + } + } + + // Shift to red. + SkBitmap blended = SkBitmapOperations::CreateBlendedBitmap( + src_a, src_b, 0.5); + SkAutoLockPixels srca_lock(src_a); + SkAutoLockPixels srcb_lock(src_b); + SkAutoLockPixels blended_lock(blended); + + for (int y = 0; y < src_h; y++) { + for (int x = 0; x < src_w; x++) { + int i = y * src_w + x; + EXPECT_EQ(static_cast<unsigned int>((255 + ((255 - i) % 255)) / 2), + SkColorGetA(*blended.getAddr32(x, y))); + EXPECT_EQ(static_cast<unsigned int>(i % 255 / 2), + SkColorGetR(*blended.getAddr32(x, y))); + EXPECT_EQ((static_cast<unsigned int>((i * 2) % 255 + (i * 4) % 255) / 2), + SkColorGetG(*blended.getAddr32(x, y))); + EXPECT_EQ(static_cast<unsigned int>(i % 255 / 2), + SkColorGetB(*blended.getAddr32(x, y))); + } + } +} + +// Test our masking functions. +TEST(SkBitmapOperationsTest, CreateMaskedBitmap) { + int src_w = 16, src_h = 16; + + SkBitmap src; + FillDataToBitmap(src_w, src_h, &src); + + // Generate alpha mask + SkBitmap alpha; + alpha.setConfig(SkBitmap::kARGB_8888_Config, src_w, src_h); + alpha.allocPixels(); + for (int y = 0, i = 0; y < src_h; y++) { + for (int x = 0; x < src_w; x++) { + *alpha.getAddr32(x, y) = SkColorSetARGB((i + 128) % 255, + (i + 128) % 255, + (i + 64) % 255, + (i + 0) % 255); + i++; + } + } + + SkBitmap masked = SkBitmapOperations::CreateMaskedBitmap(src, alpha); + + SkAutoLockPixels src_lock(src); + SkAutoLockPixels alpha_lock(alpha); + SkAutoLockPixels masked_lock(masked); + for (int y = 0; y < src_h; y++) { + for (int x = 0; x < src_w; x++) { + // Test that the alpha is equal. + SkColor src_pixel = SkUnPreMultiply::PMColorToColor(*src.getAddr32(x, y)); + SkColor alpha_pixel = + SkUnPreMultiply::PMColorToColor(*alpha.getAddr32(x, y)); + SkColor masked_pixel = *masked.getAddr32(x, y); + + int alpha_value = SkAlphaMul(SkColorGetA(src_pixel), + SkColorGetA(alpha_pixel)); + SkColor expected_pixel = SkColorSetARGB( + alpha_value, + SkAlphaMul(SkColorGetR(src_pixel), alpha_value), + SkAlphaMul(SkColorGetG(src_pixel), alpha_value), + SkAlphaMul(SkColorGetB(src_pixel), alpha_value)); + + EXPECT_TRUE(ColorsClose(expected_pixel, masked_pixel)); + } + } +} + +// Make sure that when shifting a bitmap without any shift parameters, +// the end result is close enough to the original (rounding errors +// notwithstanding). +TEST(SkBitmapOperationsTest, CreateHSLShiftedBitmapToSame) { + int src_w = 16, src_h = 16; + SkBitmap src; + src.setConfig(SkBitmap::kARGB_8888_Config, src_w, src_h); + src.allocPixels(); + + for (int y = 0, i = 0; y < src_h; y++) { + for (int x = 0; x < src_w; x++) { + *src.getAddr32(x, y) = SkPreMultiplyColor(SkColorSetARGB((i + 128) % 255, + (i + 128) % 255, (i + 64) % 255, (i + 0) % 255)); + i++; + } + } + + color_utils::HSL hsl = { -1, -1, -1 }; + SkBitmap shifted = ReferenceCreateHSLShiftedBitmap(src, hsl); + + SkAutoLockPixels src_lock(src); + SkAutoLockPixels shifted_lock(shifted); + + for (int y = 0; y < src_h; y++) { + for (int x = 0; x < src_w; x++) { + SkColor src_pixel = *src.getAddr32(x, y); + SkColor shifted_pixel = *shifted.getAddr32(x, y); + EXPECT_TRUE(MultipliedColorsClose(src_pixel, shifted_pixel)) << + "source: (a,r,g,b) = (" << SkColorGetA(src_pixel) << "," << + SkColorGetR(src_pixel) << "," << + SkColorGetG(src_pixel) << "," << + SkColorGetB(src_pixel) << "); " << + "shifted: (a,r,g,b) = (" << SkColorGetA(shifted_pixel) << "," << + SkColorGetR(shifted_pixel) << "," << + SkColorGetG(shifted_pixel) << "," << + SkColorGetB(shifted_pixel) << ")"; + } + } +} + +// Shift a blue bitmap to red. +TEST(SkBitmapOperationsTest, CreateHSLShiftedBitmapHueOnly) { + int src_w = 16, src_h = 16; + SkBitmap src; + src.setConfig(SkBitmap::kARGB_8888_Config, src_w, src_h); + src.allocPixels(); + + for (int y = 0, i = 0; y < src_h; y++) { + for (int x = 0; x < src_w; x++) { + *src.getAddr32(x, y) = SkColorSetARGB(255, 0, 0, i % 255); + i++; + } + } + + // Shift to red. + color_utils::HSL hsl = { 0, -1, -1 }; + + SkBitmap shifted = SkBitmapOperations::CreateHSLShiftedBitmap(src, hsl); + + SkAutoLockPixels src_lock(src); + SkAutoLockPixels shifted_lock(shifted); + + for (int y = 0, i = 0; y < src_h; y++) { + for (int x = 0; x < src_w; x++) { + EXPECT_TRUE(ColorsClose(*shifted.getAddr32(x, y), + SkColorSetARGB(255, i % 255, 0, 0))); + i++; + } + } +} + +// Validate HSL shift. +TEST(SkBitmapOperationsTest, ValidateHSLShift) { + // Note: 255/51 = 5 (exactly) => 6 including 0! + const int inc = 51; + const int dim = 255 / inc + 1; + SkBitmap src; + src.setConfig(SkBitmap::kARGB_8888_Config, dim*dim, dim*dim); + src.allocPixels(); + + for (int a = 0, y = 0; a <= 255; a += inc) { + for (int r = 0; r <= 255; r += inc, y++) { + for (int g = 0, x = 0; g <= 255; g += inc) { + for (int b = 0; b <= 255; b+= inc, x++) { + *src.getAddr32(x, y) = + SkPreMultiplyColor(SkColorSetARGB(a, r, g, b)); + } + } + } + } + + // Shhhh. The spec says I should set things to -1 for "no change", but + // actually -0.1 will do. Don't tell anyone I did this. + for (double h = -0.1; h <= 1.0001; h += 0.1) { + for (double s = -0.1; s <= 1.0001; s += 0.1) { + for (double l = -0.1; l <= 1.0001; l += 0.1) { + color_utils::HSL hsl = { h, s, l }; + SkBitmap ref_shifted = ReferenceCreateHSLShiftedBitmap(src, hsl); + SkBitmap shifted = SkBitmapOperations::CreateHSLShiftedBitmap(src, hsl); + EXPECT_TRUE(BitmapsClose(ref_shifted, shifted)) + << "h = " << h << ", s = " << s << ", l = " << l; + } + } + } +} + +// Test our cropping. +TEST(SkBitmapOperationsTest, CreateCroppedBitmap) { + int src_w = 16, src_h = 16; + SkBitmap src; + FillDataToBitmap(src_w, src_h, &src); + + SkBitmap cropped = SkBitmapOperations::CreateTiledBitmap(src, 4, 4, + 8, 8); + ASSERT_EQ(8, cropped.width()); + ASSERT_EQ(8, cropped.height()); + + SkAutoLockPixels src_lock(src); + SkAutoLockPixels cropped_lock(cropped); + for (int y = 4; y < 12; y++) { + for (int x = 4; x < 12; x++) { + EXPECT_EQ(*src.getAddr32(x, y), + *cropped.getAddr32(x - 4, y - 4)); + } + } +} + +// Test whether our cropping correctly wraps across image boundaries. +TEST(SkBitmapOperationsTest, CreateCroppedBitmapWrapping) { + int src_w = 16, src_h = 16; + SkBitmap src; + FillDataToBitmap(src_w, src_h, &src); + + SkBitmap cropped = SkBitmapOperations::CreateTiledBitmap( + src, src_w / 2, src_h / 2, src_w, src_h); + ASSERT_EQ(src_w, cropped.width()); + ASSERT_EQ(src_h, cropped.height()); + + SkAutoLockPixels src_lock(src); + SkAutoLockPixels cropped_lock(cropped); + for (int y = 0; y < src_h; y++) { + for (int x = 0; x < src_w; x++) { + EXPECT_EQ(*src.getAddr32(x, y), + *cropped.getAddr32((x + src_w / 2) % src_w, + (y + src_h / 2) % src_h)); + } + } +} + +TEST(SkBitmapOperationsTest, DownsampleByTwo) { + // Use an odd-sized bitmap to make sure the edge cases where there isn't a + // 2x2 block of pixels is handled correctly. + // Here's the ARGB example + // + // 50% transparent green opaque 50% blue white + // 80008000 FF000080 FFFFFFFF + // + // 50% transparent red opaque 50% gray black + // 80800000 80808080 FF000000 + // + // black white 50% gray + // FF000000 FFFFFFFF FF808080 + // + // The result of this computation should be: + // A0404040 FF808080 + // FF808080 FF808080 + SkBitmap input; + input.setConfig(SkBitmap::kARGB_8888_Config, 3, 3); + input.allocPixels(); + + // The color order may be different, but we don't care (the channels are + // trated the same). + *input.getAddr32(0, 0) = 0x80008000; + *input.getAddr32(1, 0) = 0xFF000080; + *input.getAddr32(2, 0) = 0xFFFFFFFF; + *input.getAddr32(0, 1) = 0x80800000; + *input.getAddr32(1, 1) = 0x80808080; + *input.getAddr32(2, 1) = 0xFF000000; + *input.getAddr32(0, 2) = 0xFF000000; + *input.getAddr32(1, 2) = 0xFFFFFFFF; + *input.getAddr32(2, 2) = 0xFF808080; + + SkBitmap result = SkBitmapOperations::DownsampleByTwo(input); + EXPECT_EQ(2, result.width()); + EXPECT_EQ(2, result.height()); + + // Some of the values are off-by-one due to rounding. + SkAutoLockPixels lock(result); + EXPECT_EQ(0x9f404040, *result.getAddr32(0, 0)); + EXPECT_EQ(0xFF7f7f7f, *result.getAddr32(1, 0)); + EXPECT_EQ(0xFF7f7f7f, *result.getAddr32(0, 1)); + EXPECT_EQ(0xFF808080, *result.getAddr32(1, 1)); +} + +// Test edge cases for DownsampleByTwo. +TEST(SkBitmapOperationsTest, DownsampleByTwoSmall) { + SkPMColor reference = 0xFF4080FF; + + // Test a 1x1 bitmap. + SkBitmap one_by_one; + one_by_one.setConfig(SkBitmap::kARGB_8888_Config, 1, 1); + one_by_one.allocPixels(); + *one_by_one.getAddr32(0, 0) = reference; + SkBitmap result = SkBitmapOperations::DownsampleByTwo(one_by_one); + SkAutoLockPixels lock1(result); + EXPECT_EQ(1, result.width()); + EXPECT_EQ(1, result.height()); + EXPECT_EQ(reference, *result.getAddr32(0, 0)); + + // Test an n by 1 bitmap. + SkBitmap one_by_n; + one_by_n.setConfig(SkBitmap::kARGB_8888_Config, 300, 1); + one_by_n.allocPixels(); + result = SkBitmapOperations::DownsampleByTwo(one_by_n); + SkAutoLockPixels lock2(result); + EXPECT_EQ(300, result.width()); + EXPECT_EQ(1, result.height()); + + // Test a 1 by n bitmap. + SkBitmap n_by_one; + n_by_one.setConfig(SkBitmap::kARGB_8888_Config, 1, 300); + n_by_one.allocPixels(); + result = SkBitmapOperations::DownsampleByTwo(n_by_one); + SkAutoLockPixels lock3(result); + EXPECT_EQ(1, result.width()); + EXPECT_EQ(300, result.height()); + + // Test an empty bitmap + SkBitmap empty; + result = SkBitmapOperations::DownsampleByTwo(empty); + EXPECT_TRUE(result.isNull()); + EXPECT_EQ(0, result.width()); + EXPECT_EQ(0, result.height()); +} + +// Here we assume DownsampleByTwo works correctly (it's tested above) and +// just make sure that the wrapper function does the right thing. +TEST(SkBitmapOperationsTest, DownsampleByTwoUntilSize) { + // First make sure a "too small" bitmap doesn't get modified at all. + SkBitmap too_small; + too_small.setConfig(SkBitmap::kARGB_8888_Config, 10, 10); + too_small.allocPixels(); + SkBitmap result = SkBitmapOperations::DownsampleByTwoUntilSize( + too_small, 16, 16); + EXPECT_EQ(10, result.width()); + EXPECT_EQ(10, result.height()); + + // Now make sure giving it a 0x0 target returns something reasonable. + result = SkBitmapOperations::DownsampleByTwoUntilSize(too_small, 0, 0); + EXPECT_EQ(1, result.width()); + EXPECT_EQ(1, result.height()); + + // Test multiple steps of downsampling. + SkBitmap large; + large.setConfig(SkBitmap::kARGB_8888_Config, 100, 43); + large.allocPixels(); + result = SkBitmapOperations::DownsampleByTwoUntilSize(large, 6, 6); + + // The result should be divided in half 100x43 -> 50x22 -> 25x11 + EXPECT_EQ(25, result.width()); + EXPECT_EQ(11, result.height()); +} + +TEST(SkBitmapOperationsTest, UnPreMultiply) { + SkBitmap input; + input.setConfig(SkBitmap::kARGB_8888_Config, 2, 2); + input.allocPixels(); + + *input.getAddr32(0, 0) = 0x80000000; + *input.getAddr32(1, 0) = 0x80808080; + *input.getAddr32(0, 1) = 0xFF00CC88; + *input.getAddr32(1, 1) = 0x0000CC88; + + SkBitmap result = SkBitmapOperations::UnPreMultiply(input); + EXPECT_EQ(2, result.width()); + EXPECT_EQ(2, result.height()); + + SkAutoLockPixels lock(result); + EXPECT_EQ(0x80000000, *result.getAddr32(0, 0)); + EXPECT_EQ(0x80FFFFFF, *result.getAddr32(1, 0)); + EXPECT_EQ(0xFF00CC88, *result.getAddr32(0, 1)); + EXPECT_EQ(0x00000000u, *result.getAddr32(1, 1)); // "Division by zero". +} + +TEST(SkBitmapOperationsTest, CreateTransposedBtmap) { + SkBitmap input; + input.setConfig(SkBitmap::kARGB_8888_Config, 2, 3); + input.allocPixels(); + + for (int x = 0; x < input.width(); ++x) { + for (int y = 0; y < input.height(); ++y) { + *input.getAddr32(x, y) = x * input.width() + y; + } + } + + SkBitmap result = SkBitmapOperations::CreateTransposedBtmap(input); + EXPECT_EQ(3, result.width()); + EXPECT_EQ(2, result.height()); + + SkAutoLockPixels lock(result); + for (int x = 0; x < input.width(); ++x) { + for (int y = 0; y < input.height(); ++y) { + EXPECT_EQ(*input.getAddr32(x, y), *result.getAddr32(y, x)); + } + } +} diff --git a/ui/gfx/skia_util.cc b/ui/gfx/skia_util.cc new file mode 100644 index 0000000..865f8fda --- /dev/null +++ b/ui/gfx/skia_util.cc @@ -0,0 +1,61 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "gfx/skia_util.h" + +#include "gfx/rect.h" +#include "third_party/skia/include/core/SkBitmap.h" +#include "third_party/skia/include/core/SkColorPriv.h" +#include "third_party/skia/include/core/SkShader.h" +#include "third_party/skia/include/effects/SkGradientShader.h" + +namespace gfx { + +SkRect RectToSkRect(const gfx::Rect& rect) { + SkRect r; + r.set(SkIntToScalar(rect.x()), SkIntToScalar(rect.y()), + SkIntToScalar(rect.right()), SkIntToScalar(rect.bottom())); + return r; +} + +gfx::Rect SkRectToRect(const SkRect& rect) { + return gfx::Rect(SkScalarToFixed(rect.fLeft), + SkScalarToFixed(rect.fTop), + SkScalarToFixed(rect.width()), + SkScalarToFixed(rect.height())); +} + +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); +} + +bool BitmapsAreEqual(const SkBitmap& bitmap1, const SkBitmap& bitmap2) { + void* addr1 = NULL; + void* addr2 = NULL; + size_t size1 = 0; + size_t size2 = 0; + + bitmap1.lockPixels(); + addr1 = bitmap1.getAddr32(0, 0); + size1 = bitmap1.getSize(); + bitmap1.unlockPixels(); + + bitmap2.lockPixels(); + addr2 = bitmap2.getAddr32(0, 0); + size2 = bitmap2.getSize(); + bitmap2.unlockPixels(); + + return (size1 == size2) && (0 == memcmp(addr1, addr2, bitmap1.getSize())); +} + +} // namespace gfx diff --git a/ui/gfx/skia_util.h b/ui/gfx/skia_util.h new file mode 100644 index 0000000..619ff37 --- /dev/null +++ b/ui/gfx/skia_util.h @@ -0,0 +1,39 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_SKIA_UTIL_H_ +#define UI_GFX_SKIA_UTIL_H_ +#pragma once + +#include "third_party/skia/include/core/SkColor.h" +#include "third_party/skia/include/core/SkRect.h" + +class SkBitmap; +class SkShader; + +namespace gfx { + +class Rect; + +// Convert between Skia and gfx rect types. +SkRect RectToSkRect(const gfx::Rect& rect); +gfx::Rect SkRectToRect(const SkRect& rect); + +// Creates a vertical gradient shader. The caller owns the shader. +// Example usage to avoid leaks: +// SkSafeUnref(paint.setShader(gfx::CreateGradientShader(0, 10, red, blue))); +// +// (The old shader in the paint, if any, needs to be freed, and SkSafeUnref will +// handle the NULL case.) +SkShader* CreateGradientShader(int start_point, + int end_point, + SkColor start_color, + SkColor end_color); + +// Returns true if the two bitmaps contain the same pixels. +bool BitmapsAreEqual(const SkBitmap& bitmap1, const SkBitmap& bitmap2); + +} // namespace gfx; + +#endif // UI_GFX_SKIA_UTIL_H_ diff --git a/ui/gfx/skia_utils_gtk.cc b/ui/gfx/skia_utils_gtk.cc new file mode 100644 index 0000000..8ed4bec --- /dev/null +++ b/ui/gfx/skia_utils_gtk.cc @@ -0,0 +1,32 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "gfx/skia_utils_gtk.h" + +#include <gdk/gdkcolor.h> + +namespace gfx { + +const int kSkiaToGDKMultiplier = 257; + +// GDK_COLOR_RGB multiplies by 257 (= 0x10001) to distribute the bits evenly +// See: http://www.mindcontrol.org/~hplus/graphics/expand-bits.html +// To get back, we can just right shift by eight +// (or, formulated differently, i == (i*257)/256 for all i < 256). + +SkColor GdkColorToSkColor(GdkColor color) { + return SkColorSetRGB(color.red >> 8, color.green >> 8, color.blue >> 8); +} + +GdkColor SkColorToGdkColor(SkColor color) { + GdkColor gdk_color = { + 0, + SkColorGetR(color) * kSkiaToGDKMultiplier, + SkColorGetG(color) * kSkiaToGDKMultiplier, + SkColorGetB(color) * kSkiaToGDKMultiplier + }; + return gdk_color; +} + +} // namespace gfx diff --git a/ui/gfx/skia_utils_gtk.h b/ui/gfx/skia_utils_gtk.h new file mode 100644 index 0000000..5682f09 --- /dev/null +++ b/ui/gfx/skia_utils_gtk.h @@ -0,0 +1,23 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_SKIA_UTILS_GTK_H_ +#define UI_GFX_SKIA_UTILS_GTK_H_ +#pragma once + +#include "third_party/skia/include/core/SkColor.h" + +typedef struct _GdkColor GdkColor; + +namespace gfx { + +// Converts GdkColors to the ARGB layout Skia expects. +SkColor GdkColorToSkColor(GdkColor color); + +// Converts ARGB to GdkColor. +GdkColor SkColorToGdkColor(SkColor color); + +} // namespace gfx + +#endif // UI_GFX_SKIA_UTILS_GTK_H_ diff --git a/ui/gfx/test_suite.cc b/ui/gfx/test_suite.cc new file mode 100644 index 0000000..02e1f7f --- /dev/null +++ b/ui/gfx/test_suite.cc @@ -0,0 +1,48 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "gfx/test_suite.h" + +#include "gfx/gfx_paths.h" +#include "base/file_path.h" +#include "base/path_service.h" +#include "base/mac/scoped_nsautorelease_pool.h" + +#if defined(OS_MACOSX) +#include "base/mac/mac_util.h" +#endif + +GfxTestSuite::GfxTestSuite(int argc, char** argv) : TestSuite(argc, argv) {} + +void GfxTestSuite::Initialize() { + base::mac::ScopedNSAutoreleasePool autorelease_pool; + + TestSuite::Initialize(); + + gfx::RegisterPathProvider(); + +#if defined(OS_MACOSX) + // Look in the framework bundle for resources. + // TODO(port): make a resource bundle for non-app exes. What's done here + // isn't really right because this code needs to depend on chrome_dll + // being built. This is inappropriate in app. + FilePath path; + PathService::Get(base::DIR_EXE, &path); +#if defined(GOOGLE_CHROME_BUILD) + path = path.AppendASCII("Google Chrome Framework.framework"); +#elif defined(CHROMIUM_BUILD) + path = path.AppendASCII("Chromium Framework.framework"); +#else +#error Unknown branding +#endif + base::mac::SetOverrideAppBundlePath(path); +#endif // OS_MACOSX +} + +void GfxTestSuite::Shutdown() { +#if defined(OS_MACOSX) + base::mac::SetOverrideAppBundle(NULL); +#endif + TestSuite::Shutdown(); +} diff --git a/ui/gfx/test_suite.h b/ui/gfx/test_suite.h new file mode 100644 index 0000000..488ea31 --- /dev/null +++ b/ui/gfx/test_suite.h @@ -0,0 +1,24 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_TEST_SUITE_H_ +#define UI_GFX_TEST_SUITE_H_ +#pragma once + +#include <string> + +#include "base/test/test_suite.h" +#include "build/build_config.h" + +class GfxTestSuite : public base::TestSuite { + public: + GfxTestSuite(int argc, char** argv); + + protected: + // Overridden from base::TestSuite: + virtual void Initialize(); + virtual void Shutdown(); +}; + +#endif // UI_GFX_TEST_SUITE_H_ diff --git a/ui/gfx/win_util.cc b/ui/gfx/win_util.cc new file mode 100644 index 0000000..9941a65 --- /dev/null +++ b/ui/gfx/win_util.cc @@ -0,0 +1,63 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "gfx/win_util.h" + +#include <windows.h> + +#include "base/win/windows_version.h" + +namespace { + +bool DynamicLibraryPresent(const wchar_t* lib_name) { + HINSTANCE lib = LoadLibrary(lib_name); + if (lib) { + FreeLibrary(lib); + return true; + } + return false; +} + +} // namespace + +namespace gfx { + +// Returns true if Direct2d is available, false otherwise. +bool Direct2dIsAvailable() { + static bool checked = false; + static bool available = false; + + if (!checked) { + base::win::Version version = base::win::GetVersion(); + if (version < base::win::VERSION_VISTA) + available = false; + else if (version >= base::win::VERSION_WIN7) + available = true; + else + available = DynamicLibraryPresent(L"d2d1.dll"); + checked = true; + } + return available; +} + +// Returns true if DirectWrite is available, false otherwise. +bool DirectWriteIsAvailable() { + static bool checked = false; + static bool available = false; + + if (!checked) { + base::win::Version version = base::win::GetVersion(); + if (version < base::win::VERSION_VISTA) + available = false; + else if (version >= base::win::VERSION_WIN7) + available = true; + else + available = DynamicLibraryPresent(L"dwrite.dll"); + checked = true; + } + return available; +} + +} // namespace gfx + diff --git a/ui/gfx/win_util.h b/ui/gfx/win_util.h new file mode 100644 index 0000000..3414f13 --- /dev/null +++ b/ui/gfx/win_util.h @@ -0,0 +1,20 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_WIN_UTIL_H_ +#define UI_GFX_WIN_UTIL_H_ +#pragma once + +namespace gfx { + +// Returns true if Direct2d is available, false otherwise. +bool Direct2dIsAvailable(); + +// Returns true if DirectWrite is available, false otherwise. +bool DirectWriteIsAvailable(); + +} // namespace gfx; + +#endif // UI_GFX_WIN_UTIL_H_ + |