summaryrefslogtreecommitdiff
path: root/chromium/ui/gfx
diff options
context:
space:
mode:
authorZeno Albisser <zeno.albisser@digia.com>2013-08-15 21:46:11 +0200
committerZeno Albisser <zeno.albisser@digia.com>2013-08-15 21:46:11 +0200
commit679147eead574d186ebf3069647b4c23e8ccace6 (patch)
treefc247a0ac8ff119f7c8550879ebb6d3dd8d1ff69 /chromium/ui/gfx
downloadqtwebengine-chromium-679147eead574d186ebf3069647b4c23e8ccace6.tar.gz
Initial import.
Diffstat (limited to 'chromium/ui/gfx')
-rw-r--r--chromium/ui/gfx/DEPS5
-rw-r--r--chromium/ui/gfx/OWNERS19
-rw-r--r--chromium/ui/gfx/android/OWNERS1
-rw-r--r--chromium/ui/gfx/android/device_display_info.cc66
-rw-r--r--chromium/ui/gfx/android/device_display_info.h51
-rw-r--r--chromium/ui/gfx/android/java_bitmap.cc108
-rw-r--r--chromium/ui/gfx/android/java_bitmap.h52
-rw-r--r--chromium/ui/gfx/blit.cc194
-rw-r--r--chromium/ui/gfx/blit.h52
-rw-r--r--chromium/ui/gfx/blit_unittest.cc166
-rw-r--r--chromium/ui/gfx/box_f.cc52
-rw-r--r--chromium/ui/gfx/box_f.h142
-rw-r--r--chromium/ui/gfx/box_unittest.cc143
-rw-r--r--chromium/ui/gfx/break_list.h174
-rw-r--r--chromium/ui/gfx/break_list_unittest.cc166
-rw-r--r--chromium/ui/gfx/canvas.cc543
-rw-r--r--chromium/ui/gfx/canvas.h412
-rw-r--r--chromium/ui/gfx/canvas_android.cc32
-rw-r--r--chromium/ui/gfx/canvas_mac.mm89
-rw-r--r--chromium/ui/gfx/canvas_paint.h36
-rw-r--r--chromium/ui/gfx/canvas_paint_gtk.cc69
-rw-r--r--chromium/ui/gfx/canvas_paint_gtk.h62
-rw-r--r--chromium/ui/gfx/canvas_paint_mac.h59
-rw-r--r--chromium/ui/gfx/canvas_paint_mac.mm77
-rw-r--r--chromium/ui/gfx/canvas_paint_win.cc72
-rw-r--r--chromium/ui/gfx/canvas_paint_win.h76
-rw-r--r--chromium/ui/gfx/canvas_skia.cc454
-rw-r--r--chromium/ui/gfx/canvas_skia_paint.h23
-rw-r--r--chromium/ui/gfx/canvas_unittest.cc61
-rw-r--r--chromium/ui/gfx/codec/DEPS7
-rw-r--r--chromium/ui/gfx/codec/jpeg_codec.cc632
-rw-r--r--chromium/ui/gfx/codec/jpeg_codec.h79
-rw-r--r--chromium/ui/gfx/codec/jpeg_codec_unittest.cc217
-rw-r--r--chromium/ui/gfx/codec/png_codec.cc786
-rw-r--r--chromium/ui/gfx/codec/png_codec.h134
-rw-r--r--chromium/ui/gfx/codec/png_codec_unittest.cc1162
-rw-r--r--chromium/ui/gfx/color_analysis.cc563
-rw-r--r--chromium/ui/gfx/color_analysis.h127
-rw-r--r--chromium/ui/gfx/color_analysis_unittest.cc479
-rw-r--r--chromium/ui/gfx/color_profile.cc23
-rw-r--r--chromium/ui/gfx/color_profile.h40
-rw-r--r--chromium/ui/gfx/color_profile_mac.cc27
-rw-r--r--chromium/ui/gfx/color_profile_win.cc35
-rw-r--r--chromium/ui/gfx/color_utils.cc271
-rw-r--r--chromium/ui/gfx/color_utils.h89
-rw-r--r--chromium/ui/gfx/color_utils_unittest.cc100
-rw-r--r--chromium/ui/gfx/display.cc150
-rw-r--r--chromium/ui/gfx/display.h116
-rw-r--r--chromium/ui/gfx/display_observer.cc12
-rw-r--r--chromium/ui/gfx/display_observer.h33
-rw-r--r--chromium/ui/gfx/display_unittest.cc49
-rw-r--r--chromium/ui/gfx/favicon_size.cc25
-rw-r--r--chromium/ui/gfx/favicon_size.h22
-rw-r--r--chromium/ui/gfx/font.cc85
-rw-r--r--chromium/ui/gfx/font.h114
-rw-r--r--chromium/ui/gfx/font_fallback_win.cc243
-rw-r--r--chromium/ui/gfx/font_fallback_win.h82
-rw-r--r--chromium/ui/gfx/font_fallback_win_unittest.cc117
-rw-r--r--chromium/ui/gfx/font_list.cc273
-rw-r--r--chromium/ui/gfx/font_list.h141
-rw-r--r--chromium/ui/gfx/font_list_unittest.cc318
-rw-r--r--chromium/ui/gfx/font_render_params_android.cc40
-rw-r--r--chromium/ui/gfx/font_render_params_linux.cc150
-rw-r--r--chromium/ui/gfx/font_render_params_linux.h70
-rw-r--r--chromium/ui/gfx/font_smoothing_win.cc121
-rw-r--r--chromium/ui/gfx/font_smoothing_win.h16
-rw-r--r--chromium/ui/gfx/font_unittest.cc136
-rw-r--r--chromium/ui/gfx/gdi_util.cc145
-rw-r--r--chromium/ui/gfx/gdi_util.h53
-rw-r--r--chromium/ui/gfx/gfx_paths.cc44
-rw-r--r--chromium/ui/gfx/gfx_paths.h29
-rw-r--r--chromium/ui/gfx/gpu_memory_buffer.cc13
-rw-r--r--chromium/ui/gfx/gpu_memory_buffer.h82
-rw-r--r--chromium/ui/gfx/gtk_native_view_id_manager.cc254
-rw-r--r--chromium/ui/gfx/gtk_native_view_id_manager.h138
-rw-r--r--chromium/ui/gfx/gtk_preserve_window.cc264
-rw-r--r--chromium/ui/gfx/gtk_preserve_window.h74
-rw-r--r--chromium/ui/gfx/gtk_util.cc190
-rw-r--r--chromium/ui/gfx/gtk_util.h52
-rw-r--r--chromium/ui/gfx/icon_util.cc687
-rw-r--r--chromium/ui/gfx/icon_util.h273
-rw-r--r--chromium/ui/gfx/icon_util_unittest.cc435
-rw-r--r--chromium/ui/gfx/image/OWNERS4
-rw-r--r--chromium/ui/gfx/image/cairo_cached_surface.cc109
-rw-r--r--chromium/ui/gfx/image/cairo_cached_surface.h84
-rw-r--r--chromium/ui/gfx/image/canvas_image_source.cc28
-rw-r--r--chromium/ui/gfx/image/canvas_image_source.h46
-rw-r--r--chromium/ui/gfx/image/image.cc957
-rw-r--r--chromium/ui/gfx/image/image.h211
-rw-r--r--chromium/ui/gfx/image/image_family.cc128
-rw-r--r--chromium/ui/gfx/image/image_family.h156
-rw-r--r--chromium/ui/gfx/image/image_family_unittest.cc177
-rw-r--r--chromium/ui/gfx/image/image_ios.mm137
-rw-r--r--chromium/ui/gfx/image/image_mac.mm111
-rw-r--r--chromium/ui/gfx/image/image_mac_unittest.mm213
-rw-r--r--chromium/ui/gfx/image/image_png_rep.cc40
-rw-r--r--chromium/ui/gfx/image/image_png_rep.h35
-rw-r--r--chromium/ui/gfx/image/image_skia.cc413
-rw-r--r--chromium/ui/gfx/image/image_skia.h167
-rw-r--r--chromium/ui/gfx/image/image_skia_operations.cc567
-rw-r--r--chromium/ui/gfx/image/image_skia_operations.h103
-rw-r--r--chromium/ui/gfx/image/image_skia_rep.cc46
-rw-r--r--chromium/ui/gfx/image/image_skia_rep.h61
-rw-r--r--chromium/ui/gfx/image/image_skia_source.h28
-rw-r--r--chromium/ui/gfx/image/image_skia_unittest.cc382
-rw-r--r--chromium/ui/gfx/image/image_skia_util_ios.h44
-rw-r--r--chromium/ui/gfx/image/image_skia_util_ios.mm58
-rw-r--r--chromium/ui/gfx/image/image_skia_util_mac.h48
-rw-r--r--chromium/ui/gfx/image/image_skia_util_mac.mm124
-rw-r--r--chromium/ui/gfx/image/image_unittest.cc691
-rw-r--r--chromium/ui/gfx/image/image_unittest_util.cc287
-rw-r--r--chromium/ui/gfx/image/image_unittest_util.h90
-rw-r--r--chromium/ui/gfx/image/image_unittest_util_ios.mm41
-rw-r--r--chromium/ui/gfx/image/image_unittest_util_mac.mm24
-rw-r--r--chromium/ui/gfx/image/image_util.cc48
-rw-r--r--chromium/ui/gfx/image/image_util.h36
-rw-r--r--chromium/ui/gfx/image/image_util_ios.mm27
-rw-r--r--chromium/ui/gfx/image/image_util_unittest.cc25
-rw-r--r--chromium/ui/gfx/insets.cc38
-rw-r--r--chromium/ui/gfx/insets.h52
-rw-r--r--chromium/ui/gfx/insets_base.h80
-rw-r--r--chromium/ui/gfx/insets_f.cc25
-rw-r--r--chromium/ui/gfx/insets_f.h33
-rw-r--r--chromium/ui/gfx/insets_unittest.cc66
-rw-r--r--chromium/ui/gfx/interpolated_transform.cc373
-rw-r--r--chromium/ui/gfx/interpolated_transform.h264
-rw-r--r--chromium/ui/gfx/interpolated_transform_unittest.cc234
-rw-r--r--chromium/ui/gfx/mac/nsimage_cache_unittest.cc.README3
-rw-r--r--chromium/ui/gfx/mac/scoped_ns_disable_screen_updates.h34
-rw-r--r--chromium/ui/gfx/matrix3_f.cc237
-rw-r--r--chromium/ui/gfx/matrix3_f.h108
-rw-r--r--chromium/ui/gfx/matrix3_unittest.cc148
-rw-r--r--chromium/ui/gfx/native_widget_types.h330
-rw-r--r--chromium/ui/gfx/pango_util.cc416
-rw-r--r--chromium/ui/gfx/pango_util.h116
-rw-r--r--chromium/ui/gfx/path.cc32
-rw-r--r--chromium/ui/gfx/path.h61
-rw-r--r--chromium/ui/gfx/path_aura.cc46
-rw-r--r--chromium/ui/gfx/path_gtk.cc55
-rw-r--r--chromium/ui/gfx/path_win.cc55
-rw-r--r--chromium/ui/gfx/path_win.h22
-rw-r--r--chromium/ui/gfx/path_x11.cc27
-rw-r--r--chromium/ui/gfx/path_x11.h23
-rw-r--r--chromium/ui/gfx/platform_font.h79
-rw-r--r--chromium/ui/gfx/platform_font_android.cc30
-rw-r--r--chromium/ui/gfx/platform_font_ios.h57
-rw-r--r--chromium/ui/gfx/platform_font_ios.mm128
-rw-r--r--chromium/ui/gfx/platform_font_mac.h58
-rw-r--r--chromium/ui/gfx/platform_font_mac.mm155
-rw-r--r--chromium/ui/gfx/platform_font_mac_unittest.mm56
-rw-r--r--chromium/ui/gfx/platform_font_pango.cc393
-rw-r--r--chromium/ui/gfx/platform_font_pango.h110
-rw-r--r--chromium/ui/gfx/platform_font_pango_unittest.cc48
-rw-r--r--chromium/ui/gfx/platform_font_win.cc324
-rw-r--r--chromium/ui/gfx/platform_font_win.h158
-rw-r--r--chromium/ui/gfx/platform_font_win_unittest.cc104
-rw-r--r--chromium/ui/gfx/point.cc54
-rw-r--r--chromium/ui/gfx/point.h90
-rw-r--r--chromium/ui/gfx/point3_f.cc40
-rw-r--r--chromium/ui/gfx/point3_f.h120
-rw-r--r--chromium/ui/gfx/point3_unittest.cc70
-rw-r--r--chromium/ui/gfx/point_base.h87
-rw-r--r--chromium/ui/gfx/point_conversions.cc30
-rw-r--r--chromium/ui/gfx/point_conversions.h24
-rw-r--r--chromium/ui/gfx/point_f.cc24
-rw-r--r--chromium/ui/gfx/point_f.h75
-rw-r--r--chromium/ui/gfx/point_unittest.cc174
-rw-r--r--chromium/ui/gfx/quad_f.cc127
-rw-r--r--chromium/ui/gfx/quad_f.h108
-rw-r--r--chromium/ui/gfx/quad_unittest.cc360
-rw-r--r--chromium/ui/gfx/rect.cc110
-rw-r--r--chromium/ui/gfx/rect.h145
-rw-r--r--chromium/ui/gfx/rect_base.h164
-rw-r--r--chromium/ui/gfx/rect_base_impl.h317
-rw-r--r--chromium/ui/gfx/rect_conversions.cc81
-rw-r--r--chromium/ui/gfx/rect_conversions.h36
-rw-r--r--chromium/ui/gfx/rect_f.cc60
-rw-r--r--chromium/ui/gfx/rect_f.h113
-rw-r--r--chromium/ui/gfx/rect_unittest.cc868
-rw-r--r--chromium/ui/gfx/render_text.cc1036
-rw-r--r--chromium/ui/gfx/render_text.h582
-rw-r--r--chromium/ui/gfx/render_text_linux.cc513
-rw-r--r--chromium/ui/gfx/render_text_linux.h88
-rw-r--r--chromium/ui/gfx/render_text_mac.cc343
-rw-r--r--chromium/ui/gfx/render_text_mac.h101
-rw-r--r--chromium/ui/gfx/render_text_unittest.cc1654
-rw-r--r--chromium/ui/gfx/render_text_win.cc906
-rw-r--r--chromium/ui/gfx/render_text_win.h140
-rw-r--r--chromium/ui/gfx/safe_integer_conversions.h54
-rw-r--r--chromium/ui/gfx/safe_integer_conversions_unittest.cc109
-rw-r--r--chromium/ui/gfx/scoped_cg_context_save_gstate_mac.h30
-rw-r--r--chromium/ui/gfx/scoped_ns_graphics_context_save_gstate_mac.h33
-rw-r--r--chromium/ui/gfx/scoped_ns_graphics_context_save_gstate_mac.mm23
-rw-r--r--chromium/ui/gfx/scoped_sk_region.h45
-rw-r--r--chromium/ui/gfx/scoped_ui_graphics_push_context_ios.h27
-rw-r--r--chromium/ui/gfx/scoped_ui_graphics_push_context_ios.mm23
-rw-r--r--chromium/ui/gfx/screen.cc58
-rw-r--r--chromium/ui/gfx/screen.h86
-rw-r--r--chromium/ui/gfx/screen_android.cc76
-rw-r--r--chromium/ui/gfx/screen_aura.cc16
-rw-r--r--chromium/ui/gfx/screen_gtk.cc188
-rw-r--r--chromium/ui/gfx/screen_ios.mm84
-rw-r--r--chromium/ui/gfx/screen_mac.mm185
-rw-r--r--chromium/ui/gfx/screen_type_delegate.h32
-rw-r--r--chromium/ui/gfx/screen_unittest.cc24
-rw-r--r--chromium/ui/gfx/screen_win.cc139
-rw-r--r--chromium/ui/gfx/screen_win.h47
-rw-r--r--chromium/ui/gfx/scrollbar_size.cc23
-rw-r--r--chromium/ui/gfx/scrollbar_size.h19
-rw-r--r--chromium/ui/gfx/selection_model.cc37
-rw-r--r--chromium/ui/gfx/selection_model.h113
-rw-r--r--chromium/ui/gfx/shadow_value.cc70
-rw-r--r--chromium/ui/gfx/shadow_value.h62
-rw-r--r--chromium/ui/gfx/shadow_value_unittest.cc63
-rw-r--r--chromium/ui/gfx/size.cc46
-rw-r--r--chromium/ui/gfx/size.h67
-rw-r--r--chromium/ui/gfx/size_base.h69
-rw-r--r--chromium/ui/gfx/size_conversions.cc30
-rw-r--r--chromium/ui/gfx/size_conversions.h24
-rw-r--r--chromium/ui/gfx/size_f.cc23
-rw-r--r--chromium/ui/gfx/size_f.h54
-rw-r--r--chromium/ui/gfx/size_unittest.cc128
-rw-r--r--chromium/ui/gfx/skbitmap_operations.cc851
-rw-r--r--chromium/ui/gfx/skbitmap_operations.h130
-rw-r--r--chromium/ui/gfx/skbitmap_operations_unittest.cc583
-rw-r--r--chromium/ui/gfx/skia_util.cc184
-rw-r--r--chromium/ui/gfx/skia_util.h74
-rw-r--r--chromium/ui/gfx/skia_utils_gtk.cc32
-rw-r--r--chromium/ui/gfx/skia_utils_gtk.h23
-rw-r--r--chromium/ui/gfx/switches.cc21
-rw-r--r--chromium/ui/gfx/switches.h19
-rw-r--r--chromium/ui/gfx/sys_color_change_listener.cc119
-rw-r--r--chromium/ui/gfx/sys_color_change_listener.h44
-rw-r--r--chromium/ui/gfx/text_constants.h66
-rw-r--r--chromium/ui/gfx/text_utils.cc49
-rw-r--r--chromium/ui/gfx/text_utils.h25
-rw-r--r--chromium/ui/gfx/text_utils_unittest.cc62
-rw-r--r--chromium/ui/gfx/transform.cc507
-rw-r--r--chromium/ui/gfx/transform.h238
-rw-r--r--chromium/ui/gfx/transform_unittest.cc2509
-rw-r--r--chromium/ui/gfx/transform_util.cc322
-rw-r--r--chromium/ui/gfx/transform_util.h56
-rw-r--r--chromium/ui/gfx/transform_util_unittest.cc34
-rw-r--r--chromium/ui/gfx/vector2d.cc39
-rw-r--r--chromium/ui/gfx/vector2d.h91
-rw-r--r--chromium/ui/gfx/vector2d_conversions.cc30
-rw-r--r--chromium/ui/gfx/vector2d_conversions.h24
-rw-r--r--chromium/ui/gfx/vector2d_f.cc60
-rw-r--r--chromium/ui/gfx/vector2d_f.h112
-rw-r--r--chromium/ui/gfx/vector2d_unittest.cc250
-rw-r--r--chromium/ui/gfx/vector3d_f.cc88
-rw-r--r--chromium/ui/gfx/vector3d_f.h124
-rw-r--r--chromium/ui/gfx/vector3d_unittest.cc264
253 files changed, 41374 insertions, 0 deletions
diff --git a/chromium/ui/gfx/DEPS b/chromium/ui/gfx/DEPS
new file mode 100644
index 00000000000..c2b61851728
--- /dev/null
+++ b/chromium/ui/gfx/DEPS
@@ -0,0 +1,5 @@
+include_rules = [
+ "+base",
+ "+skia",
+ "+third_party/angle",
+]
diff --git a/chromium/ui/gfx/OWNERS b/chromium/ui/gfx/OWNERS
new file mode 100644
index 00000000000..fa08e88ba74
--- /dev/null
+++ b/chromium/ui/gfx/OWNERS
@@ -0,0 +1,19 @@
+# Font rendering and what not.
+asvitkine@chromium.org
+
+# Primitive graphics types (size, rect, vector ...)
+danakj@chromium.org
+
+# RenderText and related classes.
+msw@chromium.org
+
+# RenderText and related classes.
+xji@chromium.org
+
+# Display and related classes.
+per-file display*=oshima@chromium.org
+per-file screen*=oshima@chromium.org
+
+# Transform, interpolated transform and transform util.
+per-file transform*=vollick@chromium.org
+per-file interpolated_transform*=vollick@chromium.org
diff --git a/chromium/ui/gfx/android/OWNERS b/chromium/ui/gfx/android/OWNERS
new file mode 100644
index 00000000000..c87688f018a
--- /dev/null
+++ b/chromium/ui/gfx/android/OWNERS
@@ -0,0 +1 @@
+yfriedman@chromium.org
diff --git a/chromium/ui/gfx/android/device_display_info.cc b/chromium/ui/gfx/android/device_display_info.cc
new file mode 100644
index 00000000000..16fd2acb1d8
--- /dev/null
+++ b/chromium/ui/gfx/android/device_display_info.cc
@@ -0,0 +1,66 @@
+// Copyright (c) 2012 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 "ui/gfx/android/device_display_info.h"
+
+#include "base/android/jni_android.h"
+#include "base/android/jni_string.h"
+#include "base/logging.h"
+#include "jni/DeviceDisplayInfo_jni.h"
+
+using base::android::AttachCurrentThread;
+using base::android::ScopedJavaLocalRef;
+
+namespace gfx {
+
+DeviceDisplayInfo::DeviceDisplayInfo() {
+ JNIEnv* env = AttachCurrentThread();
+ j_device_info_.Reset(Java_DeviceDisplayInfo_create(env,
+ base::android::GetApplicationContext()));
+}
+
+DeviceDisplayInfo::~DeviceDisplayInfo() {
+}
+
+int DeviceDisplayInfo::GetDisplayHeight() {
+ JNIEnv* env = AttachCurrentThread();
+ jint result =
+ Java_DeviceDisplayInfo_getDisplayHeight(env, j_device_info_.obj());
+ return static_cast<int>(result);
+}
+
+int DeviceDisplayInfo::GetDisplayWidth() {
+ JNIEnv* env = AttachCurrentThread();
+ jint result =
+ Java_DeviceDisplayInfo_getDisplayWidth(env, j_device_info_.obj());
+ return static_cast<int>(result);
+}
+
+int DeviceDisplayInfo::GetBitsPerPixel() {
+ JNIEnv* env = AttachCurrentThread();
+ jint result =
+ Java_DeviceDisplayInfo_getBitsPerPixel(env, j_device_info_.obj());
+ return static_cast<int>(result);
+}
+
+int DeviceDisplayInfo::GetBitsPerComponent() {
+ JNIEnv* env = AttachCurrentThread();
+ jint result =
+ Java_DeviceDisplayInfo_getBitsPerComponent(env, j_device_info_.obj());
+ return static_cast<int>(result);
+}
+
+double DeviceDisplayInfo::GetDIPScale() {
+ JNIEnv* env = AttachCurrentThread();
+ jdouble result =
+ Java_DeviceDisplayInfo_getDIPScale(env, j_device_info_.obj());
+ return static_cast<double>(result);
+}
+
+// static
+bool DeviceDisplayInfo::RegisterDeviceDisplayInfo(JNIEnv* env) {
+ return RegisterNativesImpl(env);
+}
+
+} // namespace gfx
diff --git a/chromium/ui/gfx/android/device_display_info.h b/chromium/ui/gfx/android/device_display_info.h
new file mode 100644
index 00000000000..af7bb622b25
--- /dev/null
+++ b/chromium/ui/gfx/android/device_display_info.h
@@ -0,0 +1,51 @@
+// Copyright (c) 2012 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_ANDROID_DEVICE_DISPLAY_INFO_H_
+#define UI_GFX_ANDROID_DEVICE_DISPLAY_INFO_H_
+
+#include <jni.h>
+#include <string>
+
+#include "base/android/scoped_java_ref.h"
+#include "base/basictypes.h"
+#include "ui/base/ui_export.h"
+
+namespace gfx {
+
+// Facilitates access to device information typically only
+// available using the Android SDK, including Display properties.
+class UI_EXPORT DeviceDisplayInfo {
+ public:
+ DeviceDisplayInfo();
+ ~DeviceDisplayInfo();
+
+ // Returns display height in physical pixels.
+ int GetDisplayHeight();
+
+ // Returns display width in physical pixels.
+ int GetDisplayWidth();
+
+ // Returns number of bits per pixel.
+ int GetBitsPerPixel();
+
+ // Returns number of bits per component.
+ int GetBitsPerComponent();
+
+ // Returns a scaling factor for Density Independent Pixel unit
+ // (1.0 is 160dpi, 0.75 is 120dpi, 2.0 is 320dpi).
+ double GetDIPScale();
+
+ // Registers methods with JNI and returns true if succeeded.
+ static bool RegisterDeviceDisplayInfo(JNIEnv* env);
+
+ private:
+ base::android::ScopedJavaGlobalRef<jobject> j_device_info_;
+
+ DISALLOW_COPY_AND_ASSIGN(DeviceDisplayInfo);
+};
+
+} // namespace gfx
+
+#endif // UI_GFX_ANDROID_DEVICE_DISPLAY_INFO_H_
diff --git a/chromium/ui/gfx/android/java_bitmap.cc b/chromium/ui/gfx/android/java_bitmap.cc
new file mode 100644
index 00000000000..e292b385aa1
--- /dev/null
+++ b/chromium/ui/gfx/android/java_bitmap.cc
@@ -0,0 +1,108 @@
+// Copyright (c) 2012 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 "ui/gfx/android/java_bitmap.h"
+
+#include <android/bitmap.h>
+
+#include "base/logging.h"
+#include "jni/BitmapHelper_jni.h"
+#include "skia/ext/image_operations.h"
+#include "third_party/skia/include/core/SkBitmap.h"
+#include "ui/gfx/size.h"
+
+using base::android::AttachCurrentThread;
+
+namespace gfx {
+
+JavaBitmap::JavaBitmap(jobject bitmap)
+ : bitmap_(bitmap),
+ pixels_(NULL) {
+ int err = AndroidBitmap_lockPixels(AttachCurrentThread(), bitmap_, &pixels_);
+ DCHECK(!err);
+ DCHECK(pixels_);
+
+ AndroidBitmapInfo info;
+ err = AndroidBitmap_getInfo(AttachCurrentThread(), bitmap_, &info);
+ DCHECK(!err);
+ size_ = gfx::Size(info.width, info.height);
+ format_ = info.format;
+ stride_ = info.stride;
+}
+
+JavaBitmap::~JavaBitmap() {
+ int err = AndroidBitmap_unlockPixels(AttachCurrentThread(), bitmap_);
+ DCHECK(!err);
+}
+
+// static
+bool JavaBitmap::RegisterJavaBitmap(JNIEnv* env) {
+ return ui::RegisterNativesImpl(env);
+}
+
+static ScopedJavaLocalRef<jobject> CreateJavaBitmap(const gfx::Size& size) {
+ return ui::Java_BitmapHelper_createBitmap(AttachCurrentThread(),
+ size.width(), size.height());
+}
+
+ScopedJavaLocalRef<jobject> ConvertToJavaBitmap(const SkBitmap* skbitmap) {
+ DCHECK(skbitmap);
+ DCHECK_EQ(skbitmap->bytesPerPixel(), 4);
+
+ ScopedJavaLocalRef<jobject> jbitmap =
+ CreateJavaBitmap(gfx::Size(skbitmap->width(), skbitmap->height()));
+ SkAutoLockPixels src_lock(*skbitmap);
+ JavaBitmap dst_lock(jbitmap.obj());
+ void* src_pixels = skbitmap->getPixels();
+ void* dst_pixels = dst_lock.pixels();
+ memcpy(dst_pixels, src_pixels, skbitmap->getSize());
+
+ return jbitmap;
+}
+
+static ScopedJavaLocalRef<jobject> CreateJavaBitmapFromResource(
+ const char* name, gfx::Size requested_size) {
+ JNIEnv* env = AttachCurrentThread();
+ ScopedJavaLocalRef<jstring> jname(env, env->NewStringUTF(name));
+ return ui::Java_BitmapHelper_decodeDrawableResource(env,
+ jname.obj(),
+ requested_size.width(),
+ requested_size.height());
+}
+
+static SkBitmap ConvertToSkBitmap(ScopedJavaLocalRef<jobject> jbitmap) {
+ if (jbitmap.is_null())
+ return SkBitmap();
+
+ JavaBitmap src_lock(jbitmap.obj());
+ DCHECK_EQ(src_lock.format(), ANDROID_BITMAP_FORMAT_RGBA_8888);
+
+ gfx::Size src_size = src_lock.size();
+
+ SkBitmap skbitmap;
+ skbitmap.setConfig(SkBitmap::kARGB_8888_Config,
+ src_size.width(), src_size.height(), src_lock.stride());
+ skbitmap.allocPixels();
+ SkAutoLockPixels dst_lock(skbitmap);
+
+ void* src_pixels = src_lock.pixels();
+ void* dst_pixels = skbitmap.getPixels();
+
+ memcpy(dst_pixels, src_pixels, skbitmap.getSize());
+
+ return skbitmap;
+}
+
+SkBitmap CreateSkBitmapFromResource(const char* name, gfx::Size size) {
+ DCHECK(!size.IsEmpty());
+ SkBitmap bitmap =
+ ConvertToSkBitmap(CreateJavaBitmapFromResource(name, size));
+ if (bitmap.isNull())
+ return bitmap;
+ return skia::ImageOperations::Resize(bitmap,
+ skia::ImageOperations::RESIZE_GOOD,
+ size.width(), size.height());
+}
+
+} // namespace gfx
diff --git a/chromium/ui/gfx/android/java_bitmap.h b/chromium/ui/gfx/android/java_bitmap.h
new file mode 100644
index 00000000000..9dd49fb8a38
--- /dev/null
+++ b/chromium/ui/gfx/android/java_bitmap.h
@@ -0,0 +1,52 @@
+// Copyright (c) 2012 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_ANDROID_JAVA_BITMAP_H_
+#define UI_GFX_ANDROID_JAVA_BITMAP_H_
+
+#include <jni.h>
+
+#include "base/android/scoped_java_ref.h"
+#include "ui/gfx/size.h"
+
+class SkBitmap;
+
+namespace gfx {
+
+// This class wraps a JNI AndroidBitmap object to make it easier to use. It
+// handles locking and unlocking of the underlying pixels, along with wrapping
+// various JNI methods.
+class UI_EXPORT JavaBitmap {
+ public:
+ explicit JavaBitmap(jobject bitmap);
+ ~JavaBitmap();
+
+ inline void* pixels() { return pixels_; }
+ inline const gfx::Size& size() const { return size_; }
+ // Formats are in android/bitmap.h; e.g. ANDROID_BITMAP_FORMAT_RGBA_8888
+ inline int format() const { return format_; }
+ inline uint32_t stride() const { return stride_; }
+
+ // Registers methods with JNI and returns true if succeeded.
+ static bool RegisterJavaBitmap(JNIEnv* env);
+
+ private:
+ jobject bitmap_;
+ void* pixels_;
+ gfx::Size size_;
+ int format_;
+ uint32_t stride_;
+
+ DISALLOW_COPY_AND_ASSIGN(JavaBitmap);
+};
+
+UI_EXPORT base::android::ScopedJavaLocalRef<jobject> ConvertToJavaBitmap(
+ const SkBitmap* skbitmap);
+
+// If the resource loads successfully, it will be resized to |size|.
+UI_EXPORT SkBitmap CreateSkBitmapFromResource(const char* name, gfx::Size size);
+
+} // namespace gfx
+
+#endif // UI_GFX_ANDROID_JAVA_BITMAP_H_
diff --git a/chromium/ui/gfx/blit.cc b/chromium/ui/gfx/blit.cc
new file mode 100644
index 00000000000..a0bae8a17ed
--- /dev/null
+++ b/chromium/ui/gfx/blit.cc
@@ -0,0 +1,194 @@
+// Copyright (c) 2012 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 "ui/gfx/blit.h"
+
+#include "base/logging.h"
+#include "build/build_config.h"
+#include "skia/ext/platform_canvas.h"
+#include "ui/gfx/point.h"
+#include "ui/gfx/rect.h"
+#include "ui/gfx/vector2d.h"
+
+#if defined(OS_OPENBSD)
+#include <cairo.h>
+#elif defined(OS_POSIX) && !defined(OS_MACOSX) && !defined(OS_ANDROID)
+#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 SkCanvas& 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 = skia::GetTopDevice(canvas)->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::ScopedCFTypeRef<CGImageRef> src_image(
+ CGBitmapContextCreateImage(src_context));
+ base::ScopedCFTypeRef<CGImageRef> src_sub_image(
+ CGImageCreateWithImageInRect(src_image, src_rect.ToCGRect()));
+ CGContextDrawImage(dst_context, dst_rect.ToCGRect(), src_sub_image);
+#elif defined(OS_ANDROID)
+ NOTIMPLEMENTED();
+#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
+}
+
+void BlitContextToCanvas(SkCanvas *dst_canvas,
+ const Rect& dst_rect,
+ NativeDrawingContext src_context,
+ const Point& src_origin) {
+ DCHECK(skia::SupportsPlatformPaint(dst_canvas));
+ BlitContextToContext(skia::BeginPlatformPaint(dst_canvas), dst_rect,
+ src_context, src_origin);
+ skia::EndPlatformPaint(dst_canvas);
+}
+
+void BlitCanvasToContext(NativeDrawingContext dst_context,
+ const Rect& dst_rect,
+ SkCanvas *src_canvas,
+ const Point& src_origin) {
+ DCHECK(skia::SupportsPlatformPaint(src_canvas));
+ BlitContextToContext(dst_context, dst_rect,
+ skia::BeginPlatformPaint(src_canvas), src_origin);
+ skia::EndPlatformPaint(src_canvas);
+}
+
+void BlitCanvasToCanvas(SkCanvas *dst_canvas,
+ const Rect& dst_rect,
+ SkCanvas *src_canvas,
+ const Point& src_origin) {
+ DCHECK(skia::SupportsPlatformPaint(dst_canvas));
+ DCHECK(skia::SupportsPlatformPaint(src_canvas));
+ BlitContextToContext(skia::BeginPlatformPaint(dst_canvas), dst_rect,
+ skia::BeginPlatformPaint(src_canvas), src_origin);
+ skia::EndPlatformPaint(src_canvas);
+ skia::EndPlatformPaint(dst_canvas);
+}
+
+void ScrollCanvas(SkCanvas* canvas,
+ const gfx::Rect& in_clip,
+ const gfx::Vector2d& offset) {
+ DCHECK(!HasClipOrTransform(*canvas)); // Don't support special stuff.
+#if defined(OS_WIN)
+ // If we have a PlatformCanvas, we should use ScrollDC. Otherwise, fall
+ // through to the software implementation.
+ if (skia::SupportsPlatformPaint(canvas)) {
+ skia::ScopedPlatformPaint scoped_platform_paint(canvas);
+ HDC hdc = scoped_platform_paint.GetPlatformSurface();
+
+ RECT damaged_rect;
+ RECT r = in_clip.ToRECT();
+ ScrollDC(hdc, offset.x(), offset.y(), NULL, &r, NULL, &damaged_rect);
+ return;
+ }
+#endif // defined(OS_WIN)
+ // For non-windows, always do scrolling in software.
+ // 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.
+ SkBitmap& bitmap = const_cast<SkBitmap&>(
+ skia::GetTopDevice(*canvas)->accessBitmap(true));
+ SkAutoLockPixels lock(bitmap);
+
+ // We expect all coords to be inside the canvas, so clip here.
+ gfx::Rect clip = gfx::IntersectRects(
+ in_clip, gfx::Rect(0, 0, bitmap.width(), bitmap.height()));
+
+ // Compute the set of pixels we'll actually end up painting.
+ gfx::Rect dest_rect = gfx::IntersectRects(clip + offset, clip);
+ if (dest_rect.size().IsEmpty())
+ return; // Nothing to do.
+
+ // Compute the source pixels that will map to the dest_rect
+ gfx::Rect src_rect = dest_rect - offset;
+
+ size_t row_bytes = dest_rect.width() * 4;
+ if (offset.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 (offset.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 (offset.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);
+ }
+ }
+}
+
+} // namespace gfx
diff --git a/chromium/ui/gfx/blit.h b/chromium/ui/gfx/blit.h
new file mode 100644
index 00000000000..616fda3c18d
--- /dev/null
+++ b/chromium/ui/gfx/blit.h
@@ -0,0 +1,52 @@
+// 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_BLIT_H_
+#define UI_GFX_BLIT_H_
+
+#include "ui/gfx/native_widget_types.h"
+#include "ui/base/ui_export.h"
+
+class SkCanvas;
+
+namespace gfx {
+
+class Point;
+class Rect;
+class Vector2d;
+
+// Blits a rectangle from the source context into the destination context.
+UI_EXPORT 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.
+UI_EXPORT void BlitContextToCanvas(SkCanvas *dst_canvas,
+ const Rect& dst_rect,
+ NativeDrawingContext src_context,
+ const Point& src_origin);
+
+// Blits a rectangle from the source canvas into the destination context.
+UI_EXPORT void BlitCanvasToContext(NativeDrawingContext dst_context,
+ const Rect& dst_rect,
+ SkCanvas *src_canvas,
+ const Point& src_origin);
+
+// Blits a rectangle from the source canvas into the destination canvas.
+UI_EXPORT void BlitCanvasToCanvas(SkCanvas *dst_canvas,
+ const Rect& dst_rect,
+ SkCanvas *src_canvas,
+ const Point& src_origin);
+
+// Scrolls the given subset of the given canvas by the given offset.
+// The canvas should not have a clip or a transform applied, since platforms
+// may implement those operations differently.
+UI_EXPORT void ScrollCanvas(SkCanvas* canvas,
+ const Rect& clip,
+ const Vector2d& offset);
+
+} // namespace gfx
+
+#endif // UI_GFX_BLIT_H_
diff --git a/chromium/ui/gfx/blit_unittest.cc b/chromium/ui/gfx/blit_unittest.cc
new file mode 100644
index 00000000000..f52272e2610
--- /dev/null
+++ b/chromium/ui/gfx/blit_unittest.cc
@@ -0,0 +1,166 @@
+// 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 "base/memory/shared_memory.h"
+#include "skia/ext/platform_canvas.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "ui/gfx/blit.h"
+#include "ui/gfx/point.h"
+#include "ui/gfx/rect.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&>(
+ skia::GetTopDevice(*canvas)->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&>(
+ skia::GetTopDevice(*canvas)->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::RefPtr<SkCanvas> canvas = skia::AdoptRef(
+ skia::CreatePlatformCanvas(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.get(), initial_values);
+
+ // Sanity check on input.
+ VerifyCanvasValues<5, 5>(canvas.get(), initial_values);
+
+ // Scroll none and make sure it's a NOP.
+ gfx::ScrollCanvas(canvas.get(),
+ gfx::Rect(0, 0, kCanvasWidth, kCanvasHeight),
+ gfx::Vector2d(0, 0));
+ VerifyCanvasValues<5, 5>(canvas.get(), initial_values);
+
+ // Scroll the center 3 pixels up one.
+ gfx::Rect center_three(1, 1, 3, 3);
+ gfx::ScrollCanvas(canvas.get(), center_three, gfx::Vector2d(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.get(), scroll_up_expected);
+
+ // Reset and scroll the center 3 pixels down one.
+ SetToCanvas<5, 5>(canvas.get(), initial_values);
+ gfx::ScrollCanvas(canvas.get(), center_three, gfx::Vector2d(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.get(), scroll_down_expected);
+
+ // Reset and scroll the center 3 pixels right one.
+ SetToCanvas<5, 5>(canvas.get(), initial_values);
+ gfx::ScrollCanvas(canvas.get(), center_three, gfx::Vector2d(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.get(), scroll_right_expected);
+
+ // Reset and scroll the center 3 pixels left one.
+ SetToCanvas<5, 5>(canvas.get(), initial_values);
+ gfx::ScrollCanvas(canvas.get(), center_three, gfx::Vector2d(-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.get(), scroll_left_expected);
+
+ // Diagonal scroll.
+ SetToCanvas<5, 5>(canvas.get(), initial_values);
+ gfx::ScrollCanvas(canvas.get(), center_three, gfx::Vector2d(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.get(), scroll_diagonal_expected);
+}
+
+#if defined(OS_WIN)
+
+TEST(Blit, WithSharedMemory) {
+ const int kCanvasWidth = 5;
+ const int kCanvasHeight = 5;
+ base::SharedMemory shared_mem;
+ ASSERT_TRUE(shared_mem.CreateAnonymous(kCanvasWidth * kCanvasHeight));
+ base::SharedMemoryHandle section = shared_mem.handle();
+ skia::RefPtr<SkCanvas> canvas = skia::AdoptRef(
+ skia::CreatePlatformCanvas(kCanvasWidth, kCanvasHeight, true, section,
+ skia::RETURN_NULL_ON_FAILURE));
+ ASSERT_TRUE(canvas);
+ shared_mem.Close();
+
+ 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.get(), initial_values);
+
+ // Sanity check on input.
+ VerifyCanvasValues<5, 5>(canvas.get(), initial_values);
+}
+
+#endif
+
diff --git a/chromium/ui/gfx/box_f.cc b/chromium/ui/gfx/box_f.cc
new file mode 100644
index 00000000000..107c5d691bf
--- /dev/null
+++ b/chromium/ui/gfx/box_f.cc
@@ -0,0 +1,52 @@
+// Copyright 2013 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 "ui/gfx/box_f.h"
+
+#include "base/strings/stringprintf.h"
+
+namespace gfx {
+
+std::string BoxF::ToString() const {
+ return base::StringPrintf("%s %fx%fx%f",
+ origin().ToString().c_str(),
+ width_,
+ height_,
+ depth_);
+}
+
+bool BoxF::IsEmpty() const {
+ return (width_ == 0 && height_ == 0) ||
+ (width_ == 0 && depth_ == 0) ||
+ (height_ == 0 && depth_ == 0);
+}
+
+void BoxF::Union(const BoxF& box) {
+ if (IsEmpty()) {
+ *this = box;
+ return;
+ }
+ if (box.IsEmpty())
+ return;
+
+ float min_x = std::min(x(), box.x());
+ float min_y = std::min(y(), box.y());
+ float min_z = std::min(z(), box.z());
+ float max_x = std::max(right(), box.right());
+ float max_y = std::max(bottom(), box.bottom());
+ float max_z = std::max(front(), box.front());
+
+ origin_.SetPoint(min_x, min_y, min_z);
+ width_ = max_x - min_x;
+ height_ = max_y - min_y;
+ depth_ = max_z - min_z;
+}
+
+BoxF UnionBoxes(const BoxF& a, const BoxF& b) {
+ BoxF result = a;
+ result.Union(b);
+ return result;
+}
+
+} // namespace gfx
diff --git a/chromium/ui/gfx/box_f.h b/chromium/ui/gfx/box_f.h
new file mode 100644
index 00000000000..01063b19712
--- /dev/null
+++ b/chromium/ui/gfx/box_f.h
@@ -0,0 +1,142 @@
+// Copyright 2013 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_BOX_F_H_
+#define UI_GFX_BOX_F_H_
+
+#include "ui/gfx/point3_f.h"
+#include "ui/gfx/vector3d_f.h"
+
+namespace gfx {
+
+// A 3d version of gfx::RectF, with the positive z-axis pointed towards
+// the camera.
+class UI_EXPORT BoxF {
+ public:
+ BoxF()
+ : width_(0.f),
+ height_(0.f),
+ depth_(0.f) {}
+
+ BoxF(float width, float height, float depth)
+ : width_(width < 0 ? 0 : width),
+ height_(height < 0 ? 0 : height),
+ depth_(depth < 0 ? 0 : depth) {}
+
+ BoxF(float x, float y, float z, float width, float height, float depth)
+ : origin_(x, y, z),
+ width_(width < 0 ? 0 : width),
+ height_(height < 0 ? 0 : height),
+ depth_(depth < 0 ? 0 : depth) {}
+
+ BoxF(const Point3F& origin, float width, float height, float depth)
+ : origin_(origin),
+ width_(width < 0 ? 0 : width),
+ height_(height < 0 ? 0 : height),
+ depth_(depth < 0 ? 0 : depth) {}
+
+ ~BoxF() {}
+
+ // Scales all three axes by the given scale.
+ void Scale(float scale) {
+ Scale(scale, scale, scale);
+ }
+
+ // Scales each axis by the corresponding given scale.
+ void Scale(float x_scale, float y_scale, float z_scale) {
+ origin_.Scale(x_scale, y_scale, z_scale);
+ set_size(width_ * x_scale, height_ * y_scale, depth_ * z_scale);
+ }
+
+ // Moves the box by the specified distance in each dimension.
+ void operator+=(const Vector3dF& offset) {
+ origin_ += offset;
+ }
+
+ // Returns true if the box has no interior points.
+ bool IsEmpty() const;
+
+ // Computes the union of this box with the given box. The union is the
+ // smallest box that contains both boxes.
+ void Union(const BoxF& box);
+
+ std::string ToString() const;
+
+ float x() const { return origin_.x(); }
+ void set_x(float x) { origin_.set_x(x); }
+
+ float y() const { return origin_.y(); }
+ void set_y(float y) { origin_.set_y(y); }
+
+ float z() const { return origin_.z(); }
+ void set_z(float z) { origin_.set_z(z); }
+
+ float width() const { return width_; }
+ void set_width(float width) { width_ = width < 0 ? 0 : width; }
+
+ float height() const { return height_; }
+ void set_height(float height) { height_ = height < 0 ? 0 : height; }
+
+ float depth() const { return depth_; }
+ void set_depth(float depth) { depth_ = depth < 0 ? 0 : depth; }
+
+ float right() const { return x() + width(); }
+ float bottom() const { return y() + height(); }
+ float front() const { return z() + depth(); }
+
+ void set_size(float width, float height, float depth) {
+ width_ = width < 0 ? 0 : width;
+ height_ = height < 0 ? 0 : height;
+ depth_ = depth < 0 ? 0 : depth;
+ }
+
+ const Point3F& origin() const { return origin_; }
+ void set_origin(const Point3F& origin) { origin_ = origin; }
+
+ private:
+ Point3F origin_;
+ float width_;
+ float height_;
+ float depth_;
+};
+
+UI_EXPORT BoxF UnionBoxes(const BoxF& a, const BoxF& b);
+
+inline BoxF ScaleBox(const BoxF& b,
+ float x_scale,
+ float y_scale,
+ float z_scale) {
+ return BoxF(b.x() * x_scale,
+ b.y() * y_scale,
+ b.z() * z_scale,
+ b.width() * x_scale,
+ b.height() * y_scale,
+ b.depth() * z_scale);
+}
+
+inline BoxF ScaleBox(const BoxF& b, float scale) {
+ return ScaleBox(b, scale, scale, scale);
+}
+
+inline bool operator==(const BoxF& a, const BoxF& b) {
+ return a.origin() == b.origin() && a.width() == b.width() &&
+ a.height() == b.height() && a.depth() == b.depth();
+}
+
+inline bool operator!=(const BoxF& a, const BoxF& b) {
+ return !(a == b);
+}
+
+inline BoxF operator+(const BoxF& b, const Vector3dF& v) {
+ return BoxF(b.x() + v.x(),
+ b.y() + v.y(),
+ b.z() + v.z(),
+ b.width(),
+ b.height(),
+ b.depth());
+}
+
+} // namespace gfx
+
+#endif // UI_GFX_BOX_F_H_
diff --git a/chromium/ui/gfx/box_unittest.cc b/chromium/ui/gfx/box_unittest.cc
new file mode 100644
index 00000000000..0e944ec5a12
--- /dev/null
+++ b/chromium/ui/gfx/box_unittest.cc
@@ -0,0 +1,143 @@
+// Copyright 2013 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 "testing/gtest/include/gtest/gtest.h"
+#include "ui/gfx/box_f.h"
+
+namespace gfx {
+
+TEST(BoxTest, Constructors) {
+ EXPECT_EQ(BoxF(0.f, 0.f, 0.f, 0.f, 0.f, 0.f).ToString(),
+ BoxF().ToString());
+ EXPECT_EQ(BoxF(0.f, 0.f, 0.f, -3.f, -5.f, -7.f).ToString(),
+ BoxF().ToString());
+
+ EXPECT_EQ(BoxF(0.f, 0.f, 0.f, 3.f, 5.f, 7.f).ToString(),
+ BoxF(3.f, 5.f, 7.f).ToString());
+ EXPECT_EQ(BoxF(0.f, 0.f, 0.f, 0.f, 0.f, 0.f).ToString(),
+ BoxF(-3.f, -5.f, -7.f).ToString());
+
+ EXPECT_EQ(BoxF(2.f, 4.f, 6.f, 3.f, 5.f, 7.f).ToString(),
+ BoxF(Point3F(2.f, 4.f, 6.f), 3.f, 5.f, 7.f).ToString());
+ EXPECT_EQ(BoxF(2.f, 4.f, 6.f, 0.f, 0.f, 0.f).ToString(),
+ BoxF(Point3F(2.f, 4.f, 6.f), -3.f, -5.f, -7.f).ToString());
+}
+
+TEST(BoxTest, IsEmpty) {
+ EXPECT_TRUE(BoxF(0.f, 0.f, 0.f, 0.f, 0.f, 0.f).IsEmpty());
+ EXPECT_TRUE(BoxF(1.f, 2.f, 3.f, 0.f, 0.f, 0.f).IsEmpty());
+
+ EXPECT_TRUE(BoxF(0.f, 0.f, 0.f, 2.f, 0.f, 0.f).IsEmpty());
+ EXPECT_TRUE(BoxF(1.f, 2.f, 3.f, 2.f, 0.f, 0.f).IsEmpty());
+ EXPECT_TRUE(BoxF(0.f, 0.f, 0.f, 0.f, 2.f, 0.f).IsEmpty());
+ EXPECT_TRUE(BoxF(1.f, 2.f, 3.f, 0.f, 2.f, 0.f).IsEmpty());
+ EXPECT_TRUE(BoxF(0.f, 0.f, 0.f, 0.f, 0.f, 2.f).IsEmpty());
+ EXPECT_TRUE(BoxF(1.f, 2.f, 3.f, 0.f, 0.f, 2.f).IsEmpty());
+
+ EXPECT_FALSE(BoxF(0.f, 0.f, 0.f, 0.f, 2.f, 2.f).IsEmpty());
+ EXPECT_FALSE(BoxF(1.f, 2.f, 3.f, 0.f, 2.f, 2.f).IsEmpty());
+ EXPECT_FALSE(BoxF(0.f, 0.f, 0.f, 2.f, 0.f, 2.f).IsEmpty());
+ EXPECT_FALSE(BoxF(1.f, 2.f, 3.f, 2.f, 0.f, 2.f).IsEmpty());
+ EXPECT_FALSE(BoxF(0.f, 0.f, 0.f, 2.f, 2.f, 0.f).IsEmpty());
+ EXPECT_FALSE(BoxF(1.f, 2.f, 3.f, 2.f, 2.f, 0.f).IsEmpty());
+
+ EXPECT_FALSE(BoxF(0.f, 0.f, 0.f, 2.f, 2.f, 2.f).IsEmpty());
+ EXPECT_FALSE(BoxF(1.f, 2.f, 3.f, 2.f, 2.f, 2.f).IsEmpty());
+}
+
+TEST(BoxTest, Union) {
+ BoxF empty_box;
+ BoxF box1(0.f, 0.f, 0.f, 1.f, 1.f, 1.f);
+ BoxF box2(0.f, 0.f, 0.f, 4.f, 6.f, 8.f);
+ BoxF box3(3.f, 4.f, 5.f, 6.f, 4.f, 0.f);
+
+ EXPECT_EQ(empty_box.ToString(), UnionBoxes(empty_box, empty_box).ToString());
+ EXPECT_EQ(box1.ToString(), UnionBoxes(empty_box, box1).ToString());
+ EXPECT_EQ(box1.ToString(), UnionBoxes(box1, empty_box).ToString());
+ EXPECT_EQ(box2.ToString(), UnionBoxes(empty_box, box2).ToString());
+ EXPECT_EQ(box2.ToString(), UnionBoxes(box2, empty_box).ToString());
+ EXPECT_EQ(box3.ToString(), UnionBoxes(empty_box, box3).ToString());
+ EXPECT_EQ(box3.ToString(), UnionBoxes(box3, empty_box).ToString());
+
+ // box_1 is contained in box_2
+ EXPECT_EQ(box2.ToString(), UnionBoxes(box1, box2).ToString());
+ EXPECT_EQ(box2.ToString(), UnionBoxes(box2, box1).ToString());
+
+ // box_1 and box_3 are disjoint
+ EXPECT_EQ(BoxF(0.f, 0.f, 0.f, 9.f, 8.f, 5.f).ToString(),
+ UnionBoxes(box1, box3).ToString());
+ EXPECT_EQ(BoxF(0.f, 0.f, 0.f, 9.f, 8.f, 5.f).ToString(),
+ UnionBoxes(box3, box1).ToString());
+
+ // box_2 and box_3 intersect, but neither contains the other
+ EXPECT_EQ(BoxF(0.f, 0.f, 0.f, 9.f, 8.f, 8.f).ToString(),
+ UnionBoxes(box2, box3).ToString());
+ EXPECT_EQ(BoxF(0.f, 0.f, 0.f, 9.f, 8.f, 8.f).ToString(),
+ UnionBoxes(box3, box2).ToString());
+}
+
+TEST(BoxTest, Scale) {
+ BoxF box1(2.f, 3.f, 4.f, 5.f, 6.f, 7.f);
+
+ EXPECT_EQ(BoxF().ToString(), ScaleBox(box1, 0.f).ToString());
+ EXPECT_EQ(box1.ToString(), ScaleBox(box1, 1.f).ToString());
+ EXPECT_EQ(BoxF(4.f, 12.f, 24.f, 10.f, 24.f, 42.f).ToString(),
+ ScaleBox(box1, 2.f, 4.f, 6.f).ToString());
+
+ BoxF box2 = box1;
+ box2.Scale(0.f);
+ EXPECT_EQ(BoxF().ToString(), box2.ToString());
+
+ box2 = box1;
+ box2.Scale(1.f);
+ EXPECT_EQ(box1.ToString(), box2.ToString());
+
+ box2.Scale(2.f, 4.f, 6.f);
+ EXPECT_EQ(BoxF(4.f, 12.f, 24.f, 10.f, 24.f, 42.f).ToString(),
+ box2.ToString());
+}
+
+TEST(BoxTest, Equals) {
+ EXPECT_TRUE(BoxF() == BoxF());
+ EXPECT_TRUE(BoxF(2.f, 3.f, 4.f, 6.f, 8.f, 10.f) ==
+ BoxF(2.f, 3.f, 4.f, 6.f, 8.f, 10.f));
+ EXPECT_FALSE(BoxF() == BoxF(0.f, 0.f, 0.f, 0.f, 0.f, 1.f));
+ EXPECT_FALSE(BoxF() == BoxF(0.f, 0.f, 0.f, 0.f, 1.f, 0.f));
+ EXPECT_FALSE(BoxF() == BoxF(0.f, 0.f, 0.f, 1.f, 0.f, 0.f));
+ EXPECT_FALSE(BoxF() == BoxF(0.f, 0.f, 1.f, 0.f, 0.f, 0.f));
+ EXPECT_FALSE(BoxF() == BoxF(0.f, 1.f, 0.f, 0.f, 0.f, 0.f));
+ EXPECT_FALSE(BoxF() == BoxF(1.f, 0.f, 0.f, 0.f, 0.f, 0.f));
+}
+
+TEST(BoxTest, NotEquals) {
+ EXPECT_FALSE(BoxF() != BoxF());
+ EXPECT_FALSE(BoxF(2.f, 3.f, 4.f, 6.f, 8.f, 10.f) !=
+ BoxF(2.f, 3.f, 4.f, 6.f, 8.f, 10.f));
+ EXPECT_TRUE(BoxF() != BoxF(0.f, 0.f, 0.f, 0.f, 0.f, 1.f));
+ EXPECT_TRUE(BoxF() != BoxF(0.f, 0.f, 0.f, 0.f, 1.f, 0.f));
+ EXPECT_TRUE(BoxF() != BoxF(0.f, 0.f, 0.f, 1.f, 0.f, 0.f));
+ EXPECT_TRUE(BoxF() != BoxF(0.f, 0.f, 1.f, 0.f, 0.f, 0.f));
+ EXPECT_TRUE(BoxF() != BoxF(0.f, 1.f, 0.f, 0.f, 0.f, 0.f));
+ EXPECT_TRUE(BoxF() != BoxF(1.f, 0.f, 0.f, 0.f, 0.f, 0.f));
+}
+
+
+TEST(BoxTest, Offset) {
+ BoxF box1(2.f, 3.f, 4.f, 5.f, 6.f, 7.f);
+
+ EXPECT_EQ(box1.ToString(), (box1 + Vector3dF(0.f, 0.f, 0.f)).ToString());
+ EXPECT_EQ(BoxF(3.f, 1.f, 0.f, 5.f, 6.f, 7.f).ToString(),
+ (box1 + Vector3dF(1.f, -2.f, -4.f)).ToString());
+
+ BoxF box2 = box1;
+ box2 += Vector3dF(0.f, 0.f, 0.f);
+ EXPECT_EQ(box1.ToString(), box2.ToString());
+
+ box2 += Vector3dF(1.f, -2.f, -4.f);
+ EXPECT_EQ(BoxF(3.f, 1.f, 0.f, 5.f, 6.f, 7.f).ToString(),
+ box2.ToString());
+}
+
+} // namespace gfx
diff --git a/chromium/ui/gfx/break_list.h b/chromium/ui/gfx/break_list.h
new file mode 100644
index 00000000000..770386e9020
--- /dev/null
+++ b/chromium/ui/gfx/break_list.h
@@ -0,0 +1,174 @@
+// Copyright (c) 2012 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_BREAK_LIST_H_
+#define UI_GFX_BREAK_LIST_H_
+
+#include <utility>
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/logging.h"
+#include "ui/base/range/range.h"
+
+namespace gfx {
+
+// BreakLists manage ordered, non-overlapping, and non-repeating ranged values.
+// These may be used to apply ranged colors and styles to text, for an example.
+//
+// Each break stores the start position and value of its associated range.
+// A solitary break at position 0 applies to the entire space [0, max_).
+// |max_| is initially 0 and should be set to match the available ranged space.
+// The first break always has position 0, to ensure all positions have a value.
+// The value of the terminal break applies to the range [break.first, max_).
+// The value of other breaks apply to the range [break.first, (break+1).first).
+template <typename T>
+class BreakList {
+ public:
+ // The break type and const iterator, typedef'ed for convenience.
+ typedef std::pair<size_t, T> Break;
+ typedef typename std::vector<Break>::const_iterator const_iterator;
+
+ // Initialize a break at position 0 with the default or supplied |value|.
+ BreakList();
+ explicit BreakList(T value);
+
+ const std::vector<Break>& breaks() const { return breaks_; }
+
+ // Clear the breaks and set a break at position 0 with the supplied |value|.
+ void SetValue(T value);
+
+ // Adjust the breaks to apply |value| over the supplied |range|.
+ void ApplyValue(T value, const ui::Range& range);
+
+ // Set the max position and trim any breaks at or beyond that position.
+ void SetMax(size_t max);
+
+ // Get the break applicable to |position| (at or preceeding |position|).
+ typename std::vector<Break>::iterator GetBreak(size_t position);
+ typename std::vector<Break>::const_iterator GetBreak(size_t position) const;
+
+ // Get the range of the supplied break; returns the break's start position and
+ // the next break's start position (or |max_| for the terminal break).
+ ui::Range GetRange(const typename BreakList<T>::const_iterator& i) const;
+
+ // Comparison functions for testing purposes.
+ bool EqualsValueForTesting(T value) const;
+ bool EqualsForTesting(const std::vector<Break>& breaks) const;
+
+ private:
+#ifndef NDEBUG
+ // Check for ordered breaks [0, |max_|) with no adjacent equivalent values.
+ void CheckBreaks();
+#endif
+
+ std::vector<Break> breaks_;
+ size_t max_;
+};
+
+template<class T>
+BreakList<T>::BreakList() : breaks_(1, Break(0, T())), max_(0) {
+}
+
+template<class T>
+BreakList<T>::BreakList(T value) : breaks_(1, Break(0, value)), max_(0) {
+}
+
+template<class T>
+void BreakList<T>::SetValue(T value) {
+ breaks_.clear();
+ breaks_.push_back(Break(0, value));
+}
+
+template<class T>
+void BreakList<T>::ApplyValue(T value, const ui::Range& range) {
+ if (!range.IsValid() || range.is_empty())
+ return;
+ DCHECK(!breaks_.empty());
+ DCHECK(!range.is_reversed());
+ DCHECK(ui::Range(0, max_).Contains(range));
+
+ // Erase any breaks in |range|, then add start and end breaks as needed.
+ typename std::vector<Break>::iterator start = GetBreak(range.start());
+ start += start->first < range.start() ? 1 : 0;
+ typename std::vector<Break>::iterator end = GetBreak(range.end());
+ T trailing_value = end->second;
+ typename std::vector<Break>::iterator i =
+ start == breaks_.end() ? start : breaks_.erase(start, end + 1);
+ if (range.start() == 0 || (i - 1)->second != value)
+ i = breaks_.insert(i, Break(range.start(), value)) + 1;
+ if (trailing_value != value && range.end() != max_)
+ breaks_.insert(i, Break(range.end(), trailing_value));
+
+#ifndef NDEBUG
+ CheckBreaks();
+#endif
+}
+
+template<class T>
+void BreakList<T>::SetMax(size_t max) {
+ typename std::vector<Break>::iterator i = GetBreak(max);
+ i += (i == breaks_.begin() || i->first < max) ? 1 : 0;
+ breaks_.erase(i, breaks_.end());
+ max_ = max;
+
+#ifndef NDEBUG
+ CheckBreaks();
+#endif
+}
+
+template<class T>
+typename std::vector<std::pair<size_t, T> >::iterator BreakList<T>::GetBreak(
+ size_t position) {
+ typename std::vector<Break>::iterator i = breaks_.end() - 1;
+ for (; i != breaks_.begin() && i->first > position; --i);
+ return i;
+}
+
+template<class T>
+typename std::vector<std::pair<size_t, T> >::const_iterator
+ BreakList<T>::GetBreak(size_t position) const {
+ typename std::vector<Break>::const_iterator i = breaks_.end() - 1;
+ for (; i != breaks_.begin() && i->first > position; --i);
+ return i;
+}
+
+template<class T>
+ui::Range BreakList<T>::GetRange(
+ const typename BreakList<T>::const_iterator& i) const {
+ const typename BreakList<T>::const_iterator next = i + 1;
+ return ui::Range(i->first, next == breaks_.end() ? max_ : next->first);
+}
+
+template<class T>
+bool BreakList<T>::EqualsValueForTesting(T value) const {
+ return breaks_.size() == 1 && breaks_[0] == Break(0, value);
+}
+
+template<class T>
+bool BreakList<T>::EqualsForTesting(const std::vector<Break>& breaks) const {
+ if (breaks_.size() != breaks.size())
+ return false;
+ for (size_t i = 0; i < breaks.size(); ++i)
+ if (breaks_[i] != breaks[i])
+ return false;
+ return true;
+}
+
+#ifndef NDEBUG
+template <class T>
+void BreakList<T>::CheckBreaks() {
+ DCHECK_EQ(breaks_[0].first, 0U) << "The first break must be at position 0.";
+ for (size_t i = 0; i < breaks_.size() - 1; ++i) {
+ DCHECK_LT(breaks_[i].first, breaks_[i + 1].first) << "Break out of order.";
+ DCHECK_NE(breaks_[i].second, breaks_[i + 1].second) << "Redundant break.";
+ }
+ if (max_ > 0)
+ DCHECK_LT(breaks_.back().first, max_) << "Break beyond max position.";
+}
+#endif
+
+} // namespace gfx
+
+#endif // UI_GFX_BREAK_LIST_H_
diff --git a/chromium/ui/gfx/break_list_unittest.cc b/chromium/ui/gfx/break_list_unittest.cc
new file mode 100644
index 00000000000..4acb0d264eb
--- /dev/null
+++ b/chromium/ui/gfx/break_list_unittest.cc
@@ -0,0 +1,166 @@
+// Copyright (c) 2012 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 "ui/gfx/break_list.h"
+
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/skia/include/core/SkColor.h"
+#include "ui/base/range/range.h"
+
+namespace gfx {
+
+class BreakListTest : public testing::Test {};
+
+TEST_F(BreakListTest, SetValue) {
+ // Check the default values applied to new instances.
+ BreakList<bool> style_breaks(false);
+ EXPECT_TRUE(style_breaks.EqualsValueForTesting(false));
+ style_breaks.SetValue(true);
+ EXPECT_TRUE(style_breaks.EqualsValueForTesting(true));
+
+ // Ensure that setting values works correctly.
+ BreakList<SkColor> color_breaks(SK_ColorRED);
+ EXPECT_TRUE(color_breaks.EqualsValueForTesting(SK_ColorRED));
+ color_breaks.SetValue(SK_ColorBLACK);
+ EXPECT_TRUE(color_breaks.EqualsValueForTesting(SK_ColorBLACK));
+}
+
+TEST_F(BreakListTest, ApplyValue) {
+ BreakList<bool> breaks(false);
+ const size_t max = 99;
+ breaks.SetMax(max);
+
+ // Ensure ApplyValue is a no-op on invalid and empty ranges.
+ breaks.ApplyValue(true, ui::Range::InvalidRange());
+ EXPECT_TRUE(breaks.EqualsValueForTesting(false));
+ for (size_t i = 0; i < 3; ++i) {
+ breaks.ApplyValue(true, ui::Range(i, i));
+ EXPECT_TRUE(breaks.EqualsValueForTesting(false));
+ }
+
+ // Apply a value to a valid range, check breaks; repeating should be no-op.
+ std::vector<std::pair<size_t, bool> > expected;
+ expected.push_back(std::pair<size_t, bool>(0, false));
+ expected.push_back(std::pair<size_t, bool>(2, true));
+ expected.push_back(std::pair<size_t, bool>(3, false));
+ for (size_t i = 0; i < 2; ++i) {
+ breaks.ApplyValue(true, ui::Range(2, 3));
+ EXPECT_TRUE(breaks.EqualsForTesting(expected));
+ }
+
+ // Ensure setting a value overrides the ranged value.
+ breaks.SetValue(true);
+ EXPECT_TRUE(breaks.EqualsValueForTesting(true));
+
+ // Ensure applying a value over [0, |max|) is the same as setting a value.
+ breaks.ApplyValue(false, ui::Range(0, max));
+ EXPECT_TRUE(breaks.EqualsValueForTesting(false));
+
+ // Ensure applying a value that is already applied has no effect.
+ breaks.ApplyValue(false, ui::Range(0, 2));
+ breaks.ApplyValue(false, ui::Range(3, 6));
+ breaks.ApplyValue(false, ui::Range(7, max));
+ EXPECT_TRUE(breaks.EqualsValueForTesting(false));
+
+ // Ensure applying an identical neighboring value merges the ranges.
+ breaks.ApplyValue(true, ui::Range(0, 3));
+ breaks.ApplyValue(true, ui::Range(3, 6));
+ breaks.ApplyValue(true, ui::Range(6, max));
+ EXPECT_TRUE(breaks.EqualsValueForTesting(true));
+
+ // Ensure applying a value with the same range overrides the ranged value.
+ breaks.ApplyValue(false, ui::Range(2, 3));
+ breaks.ApplyValue(true, ui::Range(2, 3));
+ EXPECT_TRUE(breaks.EqualsValueForTesting(true));
+
+ // Ensure applying a value with a containing range overrides contained values.
+ breaks.ApplyValue(false, ui::Range(0, 1));
+ breaks.ApplyValue(false, ui::Range(2, 3));
+ breaks.ApplyValue(true, ui::Range(0, 3));
+ EXPECT_TRUE(breaks.EqualsValueForTesting(true));
+ breaks.ApplyValue(false, ui::Range(4, 5));
+ breaks.ApplyValue(false, ui::Range(6, 7));
+ breaks.ApplyValue(false, ui::Range(8, 9));
+ breaks.ApplyValue(true, ui::Range(4, 9));
+ EXPECT_TRUE(breaks.EqualsValueForTesting(true));
+
+ // Ensure applying various overlapping values yields the intended results.
+ breaks.ApplyValue(false, ui::Range(1, 4));
+ breaks.ApplyValue(false, ui::Range(5, 8));
+ breaks.ApplyValue(true, ui::Range(0, 2));
+ breaks.ApplyValue(true, ui::Range(3, 6));
+ breaks.ApplyValue(true, ui::Range(7, max));
+ std::vector<std::pair<size_t, bool> > overlap;
+ overlap.push_back(std::pair<size_t, bool>(0, true));
+ overlap.push_back(std::pair<size_t, bool>(2, false));
+ overlap.push_back(std::pair<size_t, bool>(3, true));
+ overlap.push_back(std::pair<size_t, bool>(6, false));
+ overlap.push_back(std::pair<size_t, bool>(7, true));
+ EXPECT_TRUE(breaks.EqualsForTesting(overlap));
+}
+
+TEST_F(BreakListTest, SetMax) {
+ // Ensure values adjust to accommodate max position changes.
+ BreakList<bool> breaks(false);
+ breaks.SetMax(9);
+ breaks.ApplyValue(true, ui::Range(0, 2));
+ breaks.ApplyValue(true, ui::Range(3, 6));
+ breaks.ApplyValue(true, ui::Range(7, 9));
+
+ std::vector<std::pair<size_t, bool> > expected;
+ expected.push_back(std::pair<size_t, bool>(0, true));
+ expected.push_back(std::pair<size_t, bool>(2, false));
+ expected.push_back(std::pair<size_t, bool>(3, true));
+ expected.push_back(std::pair<size_t, bool>(6, false));
+ expected.push_back(std::pair<size_t, bool>(7, true));
+ EXPECT_TRUE(breaks.EqualsForTesting(expected));
+
+ // Setting a smaller max should remove any corresponding breaks.
+ breaks.SetMax(7);
+ expected.resize(4);
+ EXPECT_TRUE(breaks.EqualsForTesting(expected));
+ breaks.SetMax(4);
+ expected.resize(3);
+ EXPECT_TRUE(breaks.EqualsForTesting(expected));
+ breaks.SetMax(4);
+ EXPECT_TRUE(breaks.EqualsForTesting(expected));
+
+ // Setting a larger max should not change any breaks.
+ breaks.SetMax(50);
+ EXPECT_TRUE(breaks.EqualsForTesting(expected));
+}
+
+TEST_F(BreakListTest, GetBreakAndRange) {
+ BreakList<bool> breaks(false);
+ breaks.SetMax(8);
+ breaks.ApplyValue(true, ui::Range(1, 2));
+ breaks.ApplyValue(true, ui::Range(4, 6));
+
+ struct {
+ size_t position;
+ size_t break_index;
+ ui::Range range;
+ } cases[] = {
+ { 0, 0, ui::Range(0, 1) },
+ { 1, 1, ui::Range(1, 2) },
+ { 2, 2, ui::Range(2, 4) },
+ { 3, 2, ui::Range(2, 4) },
+ { 4, 3, ui::Range(4, 6) },
+ { 5, 3, ui::Range(4, 6) },
+ { 6, 4, ui::Range(6, 8) },
+ { 7, 4, ui::Range(6, 8) },
+ // Positions at or beyond the max simply return the last break and range.
+ { 8, 4, ui::Range(6, 8) },
+ { 9, 4, ui::Range(6, 8) },
+ };
+
+
+ for (size_t i = 0; i < ARRAYSIZE_UNSAFE(cases); ++i) {
+ BreakList<bool>::const_iterator it = breaks.GetBreak(cases[i].position);
+ EXPECT_EQ(breaks.breaks()[cases[i].break_index], *it);
+ EXPECT_EQ(breaks.GetRange(it), cases[i].range);
+ }
+}
+
+} // namespace gfx
diff --git a/chromium/ui/gfx/canvas.cc b/chromium/ui/gfx/canvas.cc
new file mode 100644
index 00000000000..2b0f5046cf0
--- /dev/null
+++ b/chromium/ui/gfx/canvas.cc
@@ -0,0 +1,543 @@
+// Copyright (c) 2012 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 "ui/gfx/canvas.h"
+
+#include <limits>
+
+#include "base/i18n/rtl.h"
+#include "base/logging.h"
+#include "third_party/skia/include/core/SkBitmap.h"
+#include "third_party/skia/include/effects/SkGradientShader.h"
+#include "ui/gfx/canvas.h"
+#include "ui/gfx/font.h"
+#include "ui/gfx/rect.h"
+#include "ui/gfx/size_conversions.h"
+#include "ui/gfx/skia_util.h"
+#include "ui/gfx/transform.h"
+
+#if defined(OS_WIN)
+#include "ui/gfx/canvas_skia_paint.h"
+#endif
+
+namespace gfx {
+
+Canvas::Canvas(const gfx::Size& size,
+ ui::ScaleFactor scale_factor,
+ bool is_opaque)
+ : scale_factor_(scale_factor),
+ canvas_(NULL) {
+ gfx::Size pixel_size = gfx::ToCeiledSize(
+ gfx::ScaleSize(size, ui::GetScaleFactorScale(scale_factor)));
+ owned_canvas_ = skia::AdoptRef(skia::CreatePlatformCanvas(pixel_size.width(),
+ pixel_size.height(),
+ is_opaque));
+ canvas_ = owned_canvas_.get();
+#if defined(OS_WIN) || defined(OS_MACOSX)
+ // skia::PlatformCanvas instances are initialized to 0 by Cairo on Linux, but
+ // uninitialized on Win and Mac.
+ if (!is_opaque)
+ owned_canvas_->clear(SkColorSetARGB(0, 0, 0, 0));
+#endif
+
+ SkScalar scale = SkFloatToScalar(ui::GetScaleFactorScale(scale_factor));
+ canvas_->scale(scale, scale);
+}
+
+Canvas::Canvas(const gfx::ImageSkiaRep& image_rep, bool is_opaque)
+ : scale_factor_(image_rep.scale_factor()),
+ owned_canvas_(skia::AdoptRef(
+ skia::CreatePlatformCanvas(image_rep.pixel_width(),
+ image_rep.pixel_height(),
+ is_opaque))),
+ canvas_(owned_canvas_.get()) {
+ SkScalar scale = SkFloatToScalar(ui::GetScaleFactorScale(scale_factor_));
+ canvas_->scale(scale, scale);
+ DrawImageInt(gfx::ImageSkia(image_rep), 0, 0);
+}
+
+Canvas::Canvas()
+ : scale_factor_(ui::SCALE_FACTOR_100P),
+ owned_canvas_(skia::AdoptRef(skia::CreatePlatformCanvas(0, 0, false))),
+ canvas_(owned_canvas_.get()) {
+}
+
+Canvas::~Canvas() {
+}
+
+// static
+Canvas* Canvas::CreateCanvasWithoutScaling(SkCanvas* canvas,
+ ui::ScaleFactor scale_factor) {
+ return new Canvas(canvas, scale_factor);
+}
+
+void Canvas::RecreateBackingCanvas(const gfx::Size& size,
+ ui::ScaleFactor scale_factor,
+ bool is_opaque) {
+ scale_factor_ = scale_factor;
+ gfx::Size pixel_size = gfx::ToFlooredSize(
+ gfx::ScaleSize(size, ui::GetScaleFactorScale(scale_factor)));
+ owned_canvas_ = skia::AdoptRef(skia::CreatePlatformCanvas(pixel_size.width(),
+ pixel_size.height(),
+ is_opaque));
+ canvas_ = owned_canvas_.get();
+ SkScalar scale = SkFloatToScalar(ui::GetScaleFactorScale(scale_factor_));
+ canvas_->scale(scale, scale);
+}
+
+// static
+int Canvas::GetStringWidth(const base::string16& text, const gfx::Font& font) {
+ int width = 0, height = 0;
+ Canvas::SizeStringInt(text, font, &width, &height, 0, NO_ELLIPSIS);
+ return width;
+}
+
+// static
+int Canvas::DefaultCanvasTextAlignment() {
+ return base::i18n::IsRTL() ? TEXT_ALIGN_RIGHT : TEXT_ALIGN_LEFT;
+}
+
+gfx::ImageSkiaRep Canvas::ExtractImageRep() const {
+ const SkBitmap& device_bitmap = canvas_->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 gfx::ImageSkiaRep(result, scale_factor_);
+}
+
+void Canvas::DrawDashedRect(const gfx::Rect& rect, SkColor color) {
+ // 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 SkColor last_color;
+ static SkBitmap* dots = NULL;
+ if (!dots || last_color != color) {
+ int col_pixels = 32;
+ int row_pixels = 32;
+
+ delete dots;
+ last_color = color;
+ 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] = color;
+ }
+ }
+ }
+ }
+
+ // 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.
+ skia::RefPtr<SkShader> shader = skia::AdoptRef(
+ 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.get());
+
+ DrawRect(gfx::Rect(rect.x(), rect.y(), rect.width(), 1), paint);
+ DrawRect(gfx::Rect(rect.x(), rect.y() + rect.height() - 1, rect.width(), 1),
+ paint);
+ DrawRect(gfx::Rect(rect.x(), rect.y(), 1, rect.height()), paint);
+ DrawRect(gfx::Rect(rect.x() + rect.width() - 1, rect.y(), 1, rect.height()),
+ paint);
+}
+
+void Canvas::Save() {
+ canvas_->save();
+}
+
+void Canvas::SaveLayerAlpha(uint8 alpha) {
+ canvas_->saveLayerAlpha(NULL, alpha);
+}
+
+
+void Canvas::SaveLayerAlpha(uint8 alpha, const gfx::Rect& layer_bounds) {
+ SkRect bounds(gfx::RectToSkRect(layer_bounds));
+ canvas_->saveLayerAlpha(&bounds, alpha);
+}
+
+void Canvas::Restore() {
+ canvas_->restore();
+}
+
+bool Canvas::ClipRect(const gfx::Rect& rect) {
+ return canvas_->clipRect(gfx::RectToSkRect(rect));
+}
+
+bool Canvas::ClipPath(const SkPath& path) {
+ return canvas_->clipPath(path);
+}
+
+bool Canvas::GetClipBounds(gfx::Rect* bounds) {
+ SkRect out;
+ bool has_non_empty_clip = canvas_->getClipBounds(&out);
+ bounds->SetRect(out.left(), out.top(), out.width(), out.height());
+ return has_non_empty_clip;
+}
+
+void Canvas::Translate(const gfx::Vector2d& offset) {
+ canvas_->translate(SkIntToScalar(offset.x()), SkIntToScalar(offset.y()));
+}
+
+void Canvas::Scale(int x_scale, int y_scale) {
+ canvas_->scale(SkIntToScalar(x_scale), SkIntToScalar(y_scale));
+}
+
+void Canvas::DrawColor(SkColor color) {
+ DrawColor(color, SkXfermode::kSrcOver_Mode);
+}
+
+void Canvas::DrawColor(SkColor color, SkXfermode::Mode mode) {
+ canvas_->drawColor(color, mode);
+}
+
+void Canvas::FillRect(const gfx::Rect& rect, SkColor color) {
+ FillRect(rect, color, SkXfermode::kSrcOver_Mode);
+}
+
+void Canvas::FillRect(const gfx::Rect& rect,
+ SkColor color,
+ SkXfermode::Mode mode) {
+ SkPaint paint;
+ paint.setColor(color);
+ paint.setStyle(SkPaint::kFill_Style);
+ paint.setXfermodeMode(mode);
+ DrawRect(rect, paint);
+}
+
+void Canvas::DrawRect(const gfx::Rect& rect, SkColor color) {
+ DrawRect(rect, color, SkXfermode::kSrcOver_Mode);
+}
+
+void Canvas::DrawRect(const gfx::Rect& rect,
+ SkColor color,
+ 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);
+
+ DrawRect(rect, paint);
+}
+
+void Canvas::DrawRect(const gfx::Rect& rect, const SkPaint& paint) {
+ canvas_->drawIRect(RectToSkIRect(rect), paint);
+}
+
+void Canvas::DrawPoint(const gfx::Point& p1, const SkPaint& paint) {
+ canvas_->drawPoint(SkIntToScalar(p1.x()), SkIntToScalar(p1.y()), paint);
+}
+
+void Canvas::DrawLine(const gfx::Point& p1,
+ const gfx::Point& p2,
+ SkColor color) {
+ SkPaint paint;
+ paint.setColor(color);
+ paint.setStrokeWidth(SkIntToScalar(1));
+ DrawLine(p1, p2, paint);
+}
+
+void Canvas::DrawLine(const gfx::Point& p1,
+ const gfx::Point& p2,
+ const SkPaint& paint) {
+ canvas_->drawLine(SkIntToScalar(p1.x()), SkIntToScalar(p1.y()),
+ SkIntToScalar(p2.x()), SkIntToScalar(p2.y()), paint);
+}
+
+void Canvas::DrawCircle(const gfx::Point& center_point,
+ int radius,
+ const SkPaint& paint) {
+ canvas_->drawCircle(SkIntToScalar(center_point.x()),
+ SkIntToScalar(center_point.y()), SkIntToScalar(radius), paint);
+}
+
+void Canvas::DrawRoundRect(const gfx::Rect& rect,
+ int radius,
+ const SkPaint& paint) {
+ canvas_->drawRoundRect(RectToSkRect(rect), SkIntToScalar(radius),
+ SkIntToScalar(radius), paint);
+}
+
+void Canvas::DrawPath(const SkPath& path, const SkPaint& paint) {
+ canvas_->drawPath(path, paint);
+}
+
+void Canvas::DrawFocusRect(const gfx::Rect& rect) {
+ DrawDashedRect(rect, SK_ColorGRAY);
+}
+
+void Canvas::DrawImageInt(const gfx::ImageSkia& image, int x, int y) {
+ SkPaint paint;
+ DrawImageInt(image, x, y, paint);
+}
+
+void Canvas::DrawImageInt(const gfx::ImageSkia& image, int x, int y, uint8 a) {
+ SkPaint paint;
+ paint.setAlpha(a);
+ DrawImageInt(image, x, y, paint);
+}
+
+void Canvas::DrawImageInt(const gfx::ImageSkia& image,
+ int x, int y,
+ const SkPaint& paint) {
+ const gfx::ImageSkiaRep& image_rep = GetImageRepToPaint(image);
+ if (image_rep.is_null())
+ return;
+ const SkBitmap& bitmap = image_rep.sk_bitmap();
+ float bitmap_scale = image_rep.GetScale();
+
+ canvas_->save();
+ canvas_->scale(SkFloatToScalar(1.0f / bitmap_scale),
+ SkFloatToScalar(1.0f / bitmap_scale));
+ canvas_->drawBitmap(bitmap,
+ SkFloatToScalar(x * bitmap_scale),
+ SkFloatToScalar(y * bitmap_scale),
+ &paint);
+ canvas_->restore();
+}
+
+void Canvas::DrawImageInt(const gfx::ImageSkia& image,
+ 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;
+ DrawImageInt(image, src_x, src_y, src_w, src_h, dest_x, dest_y,
+ dest_w, dest_h, filter, p);
+}
+
+void Canvas::DrawImageInt(const gfx::ImageSkia& image,
+ 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) {
+ NOTREACHED() << "Attempting to draw bitmap from an empty rect!";
+ return;
+ }
+
+ if (!IntersectsClipRectInt(dest_x, dest_y, dest_w, dest_h))
+ return;
+
+ float user_scale_x = static_cast<float>(dest_w) / src_w;
+ float user_scale_y = static_cast<float>(dest_h) / src_h;
+
+ const gfx::ImageSkiaRep& image_rep = GetImageRepToPaint(image,
+ user_scale_x, user_scale_y);
+ if (image_rep.is_null())
+ 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 &&
+ user_scale_x == 1.0f && user_scale_y == 1.0f &&
+ image_rep.scale_factor() == ui::SCALE_FACTOR_100P) {
+ // 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 };
+ const SkBitmap& bitmap = image_rep.sk_bitmap();
+ canvas_->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.
+ SkMatrix shader_scale;
+ shader_scale.setScale(SkFloatToScalar(user_scale_x),
+ SkFloatToScalar(user_scale_y));
+ shader_scale.preTranslate(SkIntToScalar(-src_x), SkIntToScalar(-src_y));
+ shader_scale.postTranslate(SkIntToScalar(dest_x), SkIntToScalar(dest_y));
+
+ skia::RefPtr<SkShader> shader = gfx::CreateImageRepShader(
+ image_rep,
+ SkShader::kRepeat_TileMode,
+ 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.get());
+
+ // The rect will be filled by the bitmap.
+ canvas_->drawRect(dest_rect, p);
+}
+
+void Canvas::DrawImageInPath(const gfx::ImageSkia& image,
+ int x,
+ int y,
+ const SkPath& path,
+ const SkPaint& paint) {
+ const gfx::ImageSkiaRep& image_rep = GetImageRepToPaint(image);
+ if (image_rep.is_null())
+ return;
+
+ SkMatrix matrix;
+ matrix.setTranslate(SkIntToScalar(x), SkIntToScalar(y));
+ skia::RefPtr<SkShader> shader = gfx::CreateImageRepShader(
+ image_rep,
+ SkShader::kRepeat_TileMode,
+ matrix);
+
+ SkPaint p(paint);
+ p.setShader(shader.get());
+ canvas_->drawPath(path, p);
+}
+
+void Canvas::DrawStringInt(const base::string16& text,
+ const gfx::Font& font,
+ SkColor color,
+ int x, int y, int w, int h) {
+ DrawStringInt(text, font, color, x, y, w, h, DefaultCanvasTextAlignment());
+}
+
+void Canvas::DrawStringInt(const base::string16& text,
+ const gfx::Font& font,
+ SkColor color,
+ const gfx::Rect& display_rect) {
+ DrawStringInt(text, font, color, display_rect.x(), display_rect.y(),
+ display_rect.width(), display_rect.height());
+}
+
+void Canvas::DrawStringInt(const base::string16& text,
+ const gfx::Font& font,
+ SkColor color,
+ int x, int y, int w, int h,
+ int flags) {
+ DrawStringWithShadows(text,
+ font,
+ color,
+ gfx::Rect(x, y, w, h),
+ 0,
+ flags,
+ ShadowValues());
+}
+
+void Canvas::TileImageInt(const gfx::ImageSkia& image,
+ int x, int y, int w, int h) {
+ TileImageInt(image, 0, 0, x, y, w, h);
+}
+
+void Canvas::TileImageInt(const gfx::ImageSkia& image,
+ int src_x, int src_y,
+ int dest_x, int dest_y, int w, int h) {
+ TileImageInt(image, src_x, src_y, 1.0f, 1.0f, dest_x, dest_y, w, h);
+}
+
+void Canvas::TileImageInt(const gfx::ImageSkia& image,
+ int src_x, int src_y,
+ float tile_scale_x, float tile_scale_y,
+ int dest_x, int dest_y, int w, int h) {
+ if (!IntersectsClipRectInt(dest_x, dest_y, w, h))
+ return;
+
+ const gfx::ImageSkiaRep& image_rep = GetImageRepToPaint(image,
+ tile_scale_x, tile_scale_y);
+ if (image_rep.is_null())
+ return;
+
+ SkMatrix shader_scale;
+ shader_scale.setScale(SkFloatToScalar(tile_scale_x),
+ SkFloatToScalar(tile_scale_y));
+ shader_scale.preTranslate(SkIntToScalar(-src_x), SkIntToScalar(-src_y));
+ shader_scale.postTranslate(SkIntToScalar(dest_x), SkIntToScalar(dest_y));
+
+ skia::RefPtr<SkShader> shader = gfx::CreateImageRepShader(
+ image_rep,
+ SkShader::kRepeat_TileMode,
+ shader_scale);
+
+ SkPaint paint;
+ paint.setShader(shader.get());
+ paint.setXfermodeMode(SkXfermode::kSrcOver_Mode);
+
+ SkRect dest_rect = { SkIntToScalar(dest_x),
+ SkIntToScalar(dest_y),
+ SkIntToScalar(dest_x + w),
+ SkIntToScalar(dest_y + h) };
+ canvas_->drawRect(dest_rect, paint);
+}
+
+gfx::NativeDrawingContext Canvas::BeginPlatformPaint() {
+ return skia::BeginPlatformPaint(canvas_);
+}
+
+void Canvas::EndPlatformPaint() {
+ skia::EndPlatformPaint(canvas_);
+}
+
+void Canvas::Transform(const gfx::Transform& transform) {
+ canvas_->concat(transform.matrix());
+}
+
+Canvas::Canvas(SkCanvas* canvas, ui::ScaleFactor scale_factor)
+ : scale_factor_(scale_factor),
+ owned_canvas_(),
+ canvas_(canvas) {
+ DCHECK(canvas);
+}
+
+bool Canvas::IntersectsClipRectInt(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));
+}
+
+bool Canvas::IntersectsClipRect(const gfx::Rect& rect) {
+ return IntersectsClipRectInt(rect.x(), rect.y(),
+ rect.width(), rect.height());
+}
+
+const gfx::ImageSkiaRep& Canvas::GetImageRepToPaint(
+ const gfx::ImageSkia& image) const {
+ return GetImageRepToPaint(image, 1.0f, 1.0f);
+}
+
+const gfx::ImageSkiaRep& Canvas::GetImageRepToPaint(
+ const gfx::ImageSkia& image,
+ float user_additional_scale_x,
+ float user_additional_scale_y) const {
+ const gfx::ImageSkiaRep& image_rep = image.GetRepresentation(scale_factor_);
+
+ if (!image_rep.is_null()) {
+ SkMatrix m = canvas_->getTotalMatrix();
+ float scale_x = SkScalarToFloat(SkScalarAbs(m.getScaleX())) *
+ user_additional_scale_x;
+ float scale_y = SkScalarToFloat(SkScalarAbs(m.getScaleY())) *
+ user_additional_scale_y;
+
+ float bitmap_scale = image_rep.GetScale();
+ if (scale_x < bitmap_scale || scale_y < bitmap_scale)
+ const_cast<SkBitmap&>(image_rep.sk_bitmap()).buildMipMap();
+ }
+
+ return image_rep;
+}
+
+} // namespace gfx
diff --git a/chromium/ui/gfx/canvas.h b/chromium/ui/gfx/canvas.h
new file mode 100644
index 00000000000..8baa45c11e3
--- /dev/null
+++ b/chromium/ui/gfx/canvas.h
@@ -0,0 +1,412 @@
+// Copyright (c) 2012 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_
+
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/strings/string16.h"
+#include "skia/ext/platform_canvas.h"
+#include "skia/ext/refptr.h"
+#include "ui/gfx/image/image_skia.h"
+#include "ui/gfx/native_widget_types.h"
+#include "ui/gfx/shadow_value.h"
+
+namespace gfx {
+
+class Rect;
+class Font;
+class Point;
+class Size;
+class Transform;
+
+// Canvas is a SkCanvas wrapper that provides a number of methods for
+// common operations used throughout an application built using ui/gfx.
+//
+// All methods that take integer arguments (as is used throughout views)
+// end with Int. If you need to use methods provided by SkCanvas, you'll
+// need to do a conversion. In particular you'll need to use |SkIntToScalar()|,
+// 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 UI_EXPORT Canvas {
+ public:
+ enum TruncateFadeMode {
+ TruncateFadeTail,
+ TruncateFadeHead,
+ TruncateFadeHeadAndTail,
+ };
+
+ // Specifies the alignment for text rendered with the DrawStringInt method.
+ enum {
+ TEXT_ALIGN_LEFT = 1 << 0,
+ TEXT_ALIGN_CENTER = 1 << 1,
+ TEXT_ALIGN_RIGHT = 1 << 2,
+
+ // Specifies the text consists of multiple lines.
+ MULTI_LINE = 1 << 3,
+
+ // 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 = 1 << 4,
+ HIDE_PREFIX = 1 << 5,
+
+ // Prevent ellipsizing
+ NO_ELLIPSIS = 1 << 6,
+
+ // Specifies if words can be split by new lines.
+ // This only works with MULTI_LINE.
+ CHARACTER_BREAK = 1 << 7,
+
+ // 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 = 1 << 8,
+
+ // Similar to FORCE_RTL_DIRECTIONALITY, but left-to-right.
+ // See FORCE_RTL_DIRECTIONALITY for details.
+ FORCE_LTR_DIRECTIONALITY = 1 << 9,
+
+ // Instructs DrawStringInt() to not use subpixel rendering. This is useful
+ // when rendering text onto a fully- or partially-transparent background
+ // that will later be blended with another image.
+ NO_SUBPIXEL_RENDERING = 1 << 10,
+ };
+
+ // Creates an empty canvas with scale factor of 1x.
+ Canvas();
+
+ // Creates canvas with provided DIP |size| and |scale_factor|.
+ // If this canvas is not opaque, it's explicitly cleared to transparent before
+ // being returned.
+ Canvas(const gfx::Size& size,
+ ui::ScaleFactor scale_factor,
+ bool is_opaque);
+
+ // Constructs a canvas with the size and the scale factor of the
+ // provided |image_rep|, and draws the |image_rep| into it.
+ Canvas(const gfx::ImageSkiaRep& image_rep, bool is_opaque);
+
+ virtual ~Canvas();
+
+ // Creates a gfx::Canvas backed by an |sk_canvas| with |scale_factor|.
+ // |sk_canvas| is assumed to be already scaled based on |scale_factor|
+ // so no additional scaling is applied.
+ static Canvas* CreateCanvasWithoutScaling(SkCanvas* sk_canvas,
+ ui::ScaleFactor scale_factor);
+
+ // Recreates the backing platform canvas with DIP |size| and |scale_factor|.
+ // If the canvas is not opaque, it is explicitly cleared.
+ // This method is public so that canvas_skia_paint can recreate the platform
+ // canvas after having initialized the canvas.
+ // TODO(pkotwicz): Push the scale factor into skia::PlatformCanvas such that
+ // this method can be private.
+ void RecreateBackingCanvas(const gfx::Size& size,
+ ui::ScaleFactor scale_factor,
+ bool is_opaque);
+
+ // 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. On Skia only a line_height can be specified and
+ // specifying a 0 value for it will cause the default height to be used.
+ static void SizeStringInt(const base::string16& text,
+ const gfx::Font& font,
+ int* width, int* height,
+ int line_height,
+ int flags);
+
+ // Returns the number of horizontal pixels needed to display the specified
+ // |text| with |font|.
+ static int GetStringWidth(const base::string16& text, const gfx::Font& font);
+
+ // Returns the default text alignment to be used when drawing text on a
+ // gfx::Canvas 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();
+
+ // 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|.
+ // On Mac, NOTIMPLEMENTED.
+ // TODO(dhollowa): Skia-native implementation is underway. Cut over to
+ // that when ready. http::/crbug.com/109946
+ void DrawStringWithHalo(const base::string16& text,
+ const gfx::Font& font,
+ SkColor text_color,
+ SkColor halo_color,
+ int x, int y, int w, int h,
+ int flags);
+
+ // Extracts an ImageSkiaRep from the contents of this canvas.
+ gfx::ImageSkiaRep ExtractImageRep() const;
+
+ // Draws a dashed rectangle of the specified color.
+ void DrawDashedRect(const gfx::Rect& rect, SkColor color);
+
+ // Saves a copy of the drawing state onto a stack, operating on this copy
+ // until a balanced call to Restore() is made.
+ void Save();
+
+ // 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.
+ void SaveLayerAlpha(uint8 alpha);
+ void SaveLayerAlpha(uint8 alpha, const gfx::Rect& layer_bounds);
+
+ // Restores the drawing state after a call to Save*(). It is an error to
+ // call Restore() more times than Save*().
+ void Restore();
+
+ // Adds |rect| to the current clip. Returns true if the resulting clip is
+ // non-empty.
+ bool ClipRect(const gfx::Rect& rect);
+
+ // Adds |path| to the current clip. Returns true if the resulting clip is
+ // non-empty.
+ bool ClipPath(const SkPath& path);
+
+ // Returns the bounds of the current clip (in local coordinates) in the
+ // |bounds| parameter, and returns true if it is non empty.
+ bool GetClipBounds(gfx::Rect* bounds);
+
+ void Translate(const gfx::Vector2d& offset);
+
+ void Scale(int x_scale, int y_scale);
+
+ // Fills the entire canvas' bitmap (restricted to current clip) with
+ // specified |color| using a transfer mode of SkXfermode::kSrcOver_Mode.
+ void DrawColor(SkColor color);
+
+ // Fills the entire canvas' bitmap (restricted to current clip) with
+ // specified |color| and |mode|.
+ void DrawColor(SkColor color, SkXfermode::Mode mode);
+
+ // Fills |rect| with |color| using a transfer mode of
+ // SkXfermode::kSrcOver_Mode.
+ void FillRect(const gfx::Rect& rect, SkColor color);
+
+ // Fills |rect| with the specified |color| and |mode|.
+ void FillRect(const gfx::Rect& rect, SkColor color, SkXfermode::Mode mode);
+
+ // 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 DrawLine.
+ void DrawRect(const gfx::Rect& rect, SkColor color);
+
+ // 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 DrawLine.
+ void DrawRect(const gfx::Rect& rect, SkColor color, SkXfermode::Mode mode);
+
+ // Draws the given rectangle with the given |paint| parameters.
+ void DrawRect(const gfx::Rect& rect, const SkPaint& paint);
+
+ // Draw the given point with the given |paint| parameters.
+ void DrawPoint(const gfx::Point& p, const SkPaint& paint);
+
+ // Draws a single pixel line with the specified color.
+ void DrawLine(const gfx::Point& p1, const gfx::Point& p2, SkColor color);
+
+ // Draws a line with the given |paint| parameters.
+ void DrawLine(const gfx::Point& p1,
+ const gfx::Point& p2,
+ const SkPaint& paint);
+
+ // Draws a circle with the given |paint| parameters.
+ void DrawCircle(const gfx::Point& center_point,
+ int radius,
+ const SkPaint& paint);
+
+ // Draws the given rectangle with rounded corners of |radius| using the
+ // given |paint| parameters.
+ void DrawRoundRect(const gfx::Rect& rect, int radius, const SkPaint& paint);
+
+ // Draws the given path using the given |paint| parameters.
+ void DrawPath(const SkPath& path, const SkPaint& paint);
+
+ // Draws an image with the origin at the specified location. The upper left
+ // corner of the bitmap is rendered at the specified location.
+ // Parameters are specified relative to current canvas scale not in pixels.
+ // Thus, x is 2 pixels if canvas scale = 2 & |x| = 1.
+ void DrawImageInt(const gfx::ImageSkia&, int x, int y);
+
+ // Helper for DrawImageInt(..., paint) that constructs a temporary paint and
+ // calls paint.setAlpha(alpha).
+ void DrawImageInt(const gfx::ImageSkia&, int x, int y, uint8 alpha);
+
+ // Draws an image with the origin at the specified location, using the
+ // specified paint. The upper left corner of the bitmap is rendered at the
+ // specified location.
+ // Parameters are specified relative to current canvas scale not in pixels.
+ // Thus, |x| is 2 pixels if canvas scale = 2 & |x| = 1.
+ void DrawImageInt(const gfx::ImageSkia& image,
+ int x, int y,
+ const SkPaint& paint);
+
+ // Draws a portion of an image 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 image will be scaled. When scaling down, a mipmap will be generated.
+ // Set |filter| to use filtering for images, otherwise the nearest-neighbor
+ // algorithm is used for resampling.
+ //
+ // An optional custom SkPaint can be provided.
+ // Parameters are specified relative to current canvas scale not in pixels.
+ // Thus, |x| is 2 pixels if canvas scale = 2 & |x| = 1.
+ void DrawImageInt(const gfx::ImageSkia& image,
+ 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);
+ void DrawImageInt(const gfx::ImageSkia& image,
+ 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);
+
+ // Draws an |image| with the top left corner at |x| and |y|, clipped to
+ // |path|.
+ // Parameters are specified relative to current canvas scale not in pixels.
+ // Thus, x is 2 pixels if canvas scale = 2 & |x| = 1.
+ void DrawImageInPath(const gfx::ImageSkia& image,
+ int x,
+ int y,
+ const SkPath& path,
+ const SkPaint& paint);
+
+ // 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 base::string16& text,
+ const gfx::Font& font,
+ SkColor color,
+ int x, int y, int w, int h);
+ void DrawStringInt(const base::string16& text,
+ const gfx::Font& font,
+ SkColor color,
+ const gfx::Rect& display_rect);
+
+ // 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.
+ void DrawStringInt(const base::string16& text,
+ const gfx::Font& font,
+ SkColor color,
+ int x, int y, int w, int h,
+ int flags);
+
+ // Similar to above DrawStringInt method but with text shadows support.
+ // Currently it's only implemented for canvas skia. Specifying a 0 line_height
+ // will cause the default height to be used.
+ void DrawStringWithShadows(const base::string16& text,
+ const gfx::Font& font,
+ SkColor color,
+ const gfx::Rect& text_bounds,
+ int line_height,
+ int flags,
+ const ShadowValues& shadows);
+
+ // Draws a dotted gray rectangle used for focus purposes.
+ void DrawFocusRect(const gfx::Rect& rect);
+
+ // Tiles the image in the specified region.
+ // Parameters are specified relative to current canvas scale not in pixels.
+ // Thus, |x| is 2 pixels if canvas scale = 2 & |x| = 1.
+ void TileImageInt(const gfx::ImageSkia& image,
+ int x, int y, int w, int h);
+ void TileImageInt(const gfx::ImageSkia& image,
+ int src_x, int src_y,
+ int dest_x, int dest_y, int w, int h);
+ void TileImageInt(const gfx::ImageSkia& image,
+ int src_x, int src_y,
+ float tile_scale_x, float tile_scale_y,
+ int dest_x, int dest_y, int w, int h);
+
+ // Returns a native drawing context for platform specific drawing routines to
+ // use. Must be balanced by a call to EndPlatformPaint().
+ NativeDrawingContext BeginPlatformPaint();
+
+ // Signifies the end of platform drawing using the native drawing context
+ // returned by BeginPlatformPaint().
+ void EndPlatformPaint();
+
+ // Apply transformation on the canvas.
+ void Transform(const gfx::Transform& transform);
+
+ // Draws the given string with the beginning and/or the end using a fade
+ // gradient. When truncating the head
+ // |desired_characters_to_truncate_from_head| specifies the maximum number of
+ // characters that can be truncated.
+ void DrawFadeTruncatingString(
+ const base::string16& text,
+ TruncateFadeMode truncate_mode,
+ size_t desired_characters_to_truncate_from_head,
+ const gfx::Font& font,
+ SkColor color,
+ const gfx::Rect& display_rect);
+
+ skia::PlatformCanvas* platform_canvas() const { return owned_canvas_.get(); }
+ SkCanvas* sk_canvas() const { return canvas_; }
+ ui::ScaleFactor scale_factor() const { return scale_factor_; }
+
+ private:
+ Canvas(SkCanvas* canvas, ui::ScaleFactor scale_factor);
+
+ // Test whether the provided rectangle intersects the current clip rect.
+ bool IntersectsClipRectInt(int x, int y, int w, int h);
+ bool IntersectsClipRect(const gfx::Rect& rect);
+
+ // Returns the image rep which best matches the canvas |scale_factor_|.
+ // Returns a null image rep if |image| contains no image reps.
+ // Builds mip map for returned image rep if necessary.
+ //
+ // An optional additional user defined scale can be provided.
+ const gfx::ImageSkiaRep& GetImageRepToPaint(
+ const gfx::ImageSkia& image) const;
+ const gfx::ImageSkiaRep& GetImageRepToPaint(
+ const gfx::ImageSkia& image,
+ float user_defined_scale_factor_x,
+ float user_defined_scale_factor_y) const;
+
+ // The device scale factor at which drawing on this canvas occurs.
+ // An additional scale can be applied via Canvas::Scale(). However,
+ // Canvas::Scale() does not affect |scale_factor_|.
+ ui::ScaleFactor scale_factor_;
+
+ skia::RefPtr<skia::PlatformCanvas> owned_canvas_;
+ SkCanvas* canvas_;
+
+ DISALLOW_COPY_AND_ASSIGN(Canvas);
+};
+
+} // namespace gfx
+
+#endif // UI_GFX_CANVAS_H_
diff --git a/chromium/ui/gfx/canvas_android.cc b/chromium/ui/gfx/canvas_android.cc
new file mode 100644
index 00000000000..c26a668f1d4
--- /dev/null
+++ b/chromium/ui/gfx/canvas_android.cc
@@ -0,0 +1,32 @@
+// Copyright (c) 2012 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 "ui/gfx/canvas.h"
+
+#include "base/logging.h"
+#include "ui/gfx/font.h"
+
+namespace gfx {
+
+// static
+void Canvas::SizeStringInt(const base::string16& text,
+ const gfx::Font& font,
+ int* width,
+ int* height,
+ int line_height,
+ int flags) {
+ NOTIMPLEMENTED();
+}
+
+void Canvas::DrawStringWithShadows(const base::string16& text,
+ const gfx::Font& font,
+ SkColor color,
+ const gfx::Rect& text_bounds,
+ int line_height,
+ int flags,
+ const ShadowValues& shadows) {
+ NOTIMPLEMENTED();
+}
+
+} // namespace gfx
diff --git a/chromium/ui/gfx/canvas_mac.mm b/chromium/ui/gfx/canvas_mac.mm
new file mode 100644
index 00000000000..a906873d4e5
--- /dev/null
+++ b/chromium/ui/gfx/canvas_mac.mm
@@ -0,0 +1,89 @@
+// Copyright (c) 2012 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 "ui/gfx/canvas.h"
+
+#include "base/logging.h"
+#include "base/strings/sys_string_conversions.h"
+#include "third_party/skia/include/core/SkTypeface.h"
+#include "ui/gfx/font.h"
+#include "ui/gfx/rect.h"
+
+// Note: This is a temporary Skia-based implementation of the ui/gfx text
+// rendering routines for views/aura. It replaces the stale Cocoa-based
+// implementation. A future |canvas_skia.cc| implementation will supersede
+// this and the other platform-specific implmenentations. Most drawing options,
+// such as alignment, multi-line, and line heights are not implemented here.
+
+namespace {
+
+SkTypeface::Style FontTypefaceStyle(const gfx::Font& font) {
+ int style = 0;
+ if (font.GetStyle() & gfx::Font::BOLD)
+ style |= SkTypeface::kBold;
+ if (font.GetStyle() & gfx::Font::ITALIC)
+ style |= SkTypeface::kItalic;
+
+ return static_cast<SkTypeface::Style>(style);
+}
+
+} // namespace
+
+namespace gfx {
+
+// static
+void Canvas::SizeStringInt(const base::string16& text,
+ const gfx::Font& font,
+ int* width,
+ int* height,
+ int line_height,
+ int flags) {
+ DLOG_IF(WARNING, line_height != 0) << "Line heights not implemented.";
+ DLOG_IF(WARNING, flags & Canvas::MULTI_LINE) << "Multi-line not implemented.";
+
+ 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 Canvas::DrawStringWithShadows(const base::string16& text,
+ const gfx::Font& font,
+ SkColor color,
+ const gfx::Rect& text_bounds,
+ int line_height,
+ int flags,
+ const ShadowValues& shadows) {
+ DLOG_IF(WARNING, line_height != 0) << "Line heights not implemented.";
+ DLOG_IF(WARNING, flags & Canvas::MULTI_LINE) << "Multi-line not implemented.";
+ DLOG_IF(WARNING, !shadows.empty()) << "Text shadows not implemented.";
+
+ skia::RefPtr<SkTypeface> typeface = skia::AdoptRef(
+ SkTypeface::CreateFromName(
+ font.GetFontName().c_str(), FontTypefaceStyle(font)));
+ SkPaint paint;
+ paint.setTypeface(typeface.get());
+ paint.setColor(color);
+ canvas_->drawText(text.c_str(),
+ text.size() * sizeof(base::string16::value_type),
+ text_bounds.x(),
+ text_bounds.bottom(),
+ paint);
+}
+
+void Canvas::DrawStringWithHalo(const base::string16& text,
+ const gfx::Font& font,
+ SkColor text_color,
+ SkColor halo_color,
+ int x, int y, int w, int h,
+ int flags) {
+}
+
+} // namespace gfx
diff --git a/chromium/ui/gfx/canvas_paint.h b/chromium/ui/gfx/canvas_paint.h
new file mode 100644
index 00000000000..1db5341adae
--- /dev/null
+++ b/chromium/ui/gfx/canvas_paint.h
@@ -0,0 +1,36 @@
+// Copyright (c) 2012 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_PAINT_H_
+#define UI_GFX_CANVAS_PAINT_H_
+
+#include "ui/base/ui_export.h"
+#include "ui/gfx/native_widget_types.h"
+
+namespace gfx {
+
+class Canvas;
+class Rect;
+
+class CanvasPaint {
+ public:
+ // Creates a canvas that paints to |view| when it is destroyed. The canvas is
+ // sized to the client area of |view|.
+ UI_EXPORT static CanvasPaint* CreateCanvasPaint(gfx::NativeView view);
+
+ virtual ~CanvasPaint() {}
+
+ // 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_PAINT_H_
diff --git a/chromium/ui/gfx/canvas_paint_gtk.cc b/chromium/ui/gfx/canvas_paint_gtk.cc
new file mode 100644
index 00000000000..4c185c02e03
--- /dev/null
+++ b/chromium/ui/gfx/canvas_paint_gtk.cc
@@ -0,0 +1,69 @@
+// Copyright (c) 2012 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 "base/compiler_specific.h"
+#include "ui/gfx/canvas.h"
+#include "ui/gfx/canvas_skia_paint.h"
+#include "ui/gfx/rect.h"
+
+namespace gfx {
+
+CanvasSkiaPaint::CanvasSkiaPaint(GdkEventExpose* event)
+ : context_(NULL),
+ window_(event->window),
+ region_(gdk_region_copy(event->region)),
+ composite_alpha_(false) {
+ Init(true);
+}
+
+CanvasSkiaPaint::CanvasSkiaPaint(GdkEventExpose* event, bool opaque)
+ : context_(NULL),
+ window_(event->window),
+ region_(gdk_region_copy(event->region)),
+ composite_alpha_(false) {
+ Init(opaque);
+}
+
+CanvasSkiaPaint::~CanvasSkiaPaint() {
+ if (!is_empty()) {
+ platform_canvas()->restoreToCount(1);
+
+ // Blit the dirty rect to the window.
+ CHECK(window_);
+ cairo_t* cr = gdk_cairo_create(window_);
+ CHECK(cr);
+ if (composite_alpha_)
+ cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE);
+ cairo_surface_t* source_surface = cairo_get_target(context_);
+ CHECK(source_surface);
+ // Flush cairo's cache of the surface.
+ cairo_surface_mark_dirty(source_surface);
+ GdkRectangle bounds = rectangle();
+ cairo_set_source_surface(cr, source_surface, bounds.x, bounds.y);
+ gdk_cairo_region(cr, region_);
+ cairo_fill(cr);
+ cairo_destroy(cr);
+ }
+
+ gdk_region_destroy(region_);
+}
+
+void CanvasSkiaPaint::Init(bool opaque) {
+ GdkRectangle bounds = rectangle();
+ RecreateBackingCanvas(gfx::Size(bounds.width, bounds.height),
+ ui::SCALE_FACTOR_100P, opaque);
+
+ skia::PlatformCanvas* canvas = platform_canvas();
+
+ // Need to translate so that the dirty region appears at the origin of the
+ // surface.
+ canvas->translate(-SkIntToScalar(bounds.x), -SkIntToScalar(bounds.y));
+
+ context_ = skia::BeginPlatformPaint(canvas);
+}
+
+} // namespace gfx
+
+
diff --git a/chromium/ui/gfx/canvas_paint_gtk.h b/chromium/ui/gfx/canvas_paint_gtk.h
new file mode 100644
index 00000000000..889f30895b4
--- /dev/null
+++ b/chromium/ui/gfx/canvas_paint_gtk.h
@@ -0,0 +1,62 @@
+
+// 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_CANVAS_PAINT_LINUX_H_
+#define UI_GFX_CANVAS_PAINT_LINUX_H_
+
+#include "base/logging.h"
+#include "skia/ext/platform_canvas.h"
+#include "ui/gfx/canvas.h"
+#include <gdk/gdk.h>
+
+namespace gfx {
+
+// A class designed to translate skia painting into a region in a GdkWindow.
+// On construction, it will set up a context for painting into, and on
+// destruction, it will commit it to the GdkWindow.
+// Note: The created context is always inialized to (0, 0, 0, 0).
+class UI_EXPORT CanvasSkiaPaint : public Canvas {
+ public:
+ // This constructor assumes the result is opaque.
+ explicit CanvasSkiaPaint(GdkEventExpose* event);
+ CanvasSkiaPaint(GdkEventExpose* event, bool opaque);
+ virtual ~CanvasSkiaPaint();
+
+ // Sets whether the bitmap is composited in such a way that the alpha channel
+ // is honored. This is only useful if you've enabled an RGBA colormap on the
+ // widget. The default is false.
+ void set_composite_alpha(bool composite_alpha) {
+ composite_alpha_ = composite_alpha;
+ }
+
+ // Returns true if the invalid region is empty. The caller should call this
+ // function to determine if anything needs painting.
+ bool is_empty() const {
+ return gdk_region_empty(region_);
+ }
+
+ GdkRectangle rectangle() const {
+ GdkRectangle bounds;
+ gdk_region_get_clipbox(region_, &bounds);
+ return bounds;
+ }
+
+ private:
+ void Init(bool opaque);
+
+ cairo_t* context_;
+ GdkWindow* window_;
+ GdkRegion* region_;
+ // See description above setter.
+ bool composite_alpha_;
+
+ // Disallow copy and assign.
+ CanvasSkiaPaint(const CanvasSkiaPaint&);
+ CanvasSkiaPaint& operator=(const CanvasSkiaPaint&);
+};
+
+} // namespace gfx
+
+#endif // UI_GFX_CANVAS_PAINT_LINUX_H_
diff --git a/chromium/ui/gfx/canvas_paint_mac.h b/chromium/ui/gfx/canvas_paint_mac.h
new file mode 100644
index 00000000000..16d74109f65
--- /dev/null
+++ b/chromium/ui/gfx/canvas_paint_mac.h
@@ -0,0 +1,59 @@
+
+// Copyright (c) 2012 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_PAINT_MAC_H_
+#define UI_GFX_CANVAS_PAINT_MAC_H_
+
+#include "skia/ext/platform_canvas.h"
+#include "ui/gfx/canvas.h"
+
+#import <Cocoa/Cocoa.h>
+
+namespace gfx {
+
+// A class designed to translate skia painting into a region to the current
+// graphics context. On construction, it will set up a context for painting
+// into, and on destruction, it will commit it to the current context.
+// Note: The created context is always inialized to (0, 0, 0, 0).
+class UI_EXPORT CanvasSkiaPaint : public Canvas {
+ public:
+ // This constructor assumes the result is opaque.
+ explicit CanvasSkiaPaint(NSRect dirtyRect);
+ CanvasSkiaPaint(NSRect dirtyRect, bool opaque);
+ virtual ~CanvasSkiaPaint();
+
+ // If true, the data painted into the CanvasSkiaPaint is blended onto the
+ // current context, else it is copied.
+ void set_composite_alpha(bool composite_alpha) {
+ composite_alpha_ = composite_alpha;
+ }
+
+ // Returns true if the invalid region is empty. The caller should call this
+ // function to determine if anything needs painting.
+ bool is_empty() const {
+ return NSIsEmptyRect(rectangle_);
+ }
+
+ const NSRect& rectangle() const {
+ return rectangle_;
+ }
+
+ private:
+ void Init(bool opaque);
+
+ CGContext* context_;
+ NSRect rectangle_;
+ // See description above setter.
+ bool composite_alpha_;
+
+ // Disallow copy and assign.
+ CanvasSkiaPaint(const CanvasSkiaPaint&);
+ CanvasSkiaPaint& operator=(const CanvasSkiaPaint&);
+};
+
+} // namespace gfx
+
+
+#endif // UI_GFX_CANVAS_PAINT_MAC_H_
diff --git a/chromium/ui/gfx/canvas_paint_mac.mm b/chromium/ui/gfx/canvas_paint_mac.mm
new file mode 100644
index 00000000000..c8796afedae
--- /dev/null
+++ b/chromium/ui/gfx/canvas_paint_mac.mm
@@ -0,0 +1,77 @@
+// Copyright (c) 2012 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 "ui/gfx/canvas_paint_mac.h"
+#include "ui/gfx/size.h"
+
+namespace gfx {
+
+CanvasSkiaPaint::CanvasSkiaPaint(NSRect dirtyRect)
+ : context_(NULL),
+ rectangle_(dirtyRect),
+ composite_alpha_(false) {
+ Init(true);
+}
+
+CanvasSkiaPaint::CanvasSkiaPaint(NSRect dirtyRect, bool opaque)
+ : context_(NULL),
+ rectangle_(dirtyRect),
+ composite_alpha_(false) {
+ Init(opaque);
+}
+
+CanvasSkiaPaint::~CanvasSkiaPaint() {
+ if (!is_empty()) {
+ platform_canvas()->restoreToCount(1);
+
+ // Blit the dirty rect to the current context.
+ CGImageRef image = CGBitmapContextCreateImage(context_);
+ CGRect dest_rect = NSRectToCGRect(rectangle_);
+
+ CGContextRef destination_context =
+ (CGContextRef)[[NSGraphicsContext currentContext] graphicsPort];
+ CGContextSaveGState(destination_context);
+ CGContextSetBlendMode(
+ destination_context,
+ composite_alpha_ ? kCGBlendModeNormal : kCGBlendModeCopy);
+
+ if ([[NSGraphicsContext currentContext] isFlipped]) {
+ // Mirror context on the target's rect middle scanline.
+ CGContextTranslateCTM(destination_context, 0.0, NSMidY(rectangle_));
+ CGContextScaleCTM(destination_context, 1.0, -1.0);
+ CGContextTranslateCTM(destination_context, 0.0, -NSMidY(rectangle_));
+ }
+
+ CGContextDrawImage(destination_context, dest_rect, image);
+ CGContextRestoreGState(destination_context);
+
+ CFRelease(image);
+ }
+}
+
+void CanvasSkiaPaint::Init(bool opaque) {
+ CGContextRef destination_context =
+ (CGContextRef)[[NSGraphicsContext currentContext] graphicsPort];
+ CGRect scaled_unit_rect = CGContextConvertRectToDeviceSpace(
+ destination_context, CGRectMake(0, 0, 1, 1));
+ // Assume that the x scale and the y scale are the same.
+ CGFloat scale = scaled_unit_rect.size.width;
+
+ ui::ScaleFactor scale_factor = ui::GetScaleFactorFromScale(scale);
+ gfx::Size size(NSWidth(rectangle_), NSHeight(rectangle_));
+ RecreateBackingCanvas(size, scale_factor, opaque);
+ skia::PlatformCanvas* canvas = platform_canvas();
+ canvas->clear(SkColorSetARGB(0, 0, 0, 0));
+
+ // Need to translate so that the dirty region appears at the origin of the
+ // surface.
+ canvas->translate(-SkDoubleToScalar(NSMinX(rectangle_)),
+ -SkDoubleToScalar(NSMinY(rectangle_)));
+
+ context_ = skia::GetBitmapContext(skia::GetTopDevice(*canvas));
+}
+
+} // namespace skia
+
+
diff --git a/chromium/ui/gfx/canvas_paint_win.cc b/chromium/ui/gfx/canvas_paint_win.cc
new file mode 100644
index 00000000000..661b2976fd7
--- /dev/null
+++ b/chromium/ui/gfx/canvas_paint_win.cc
@@ -0,0 +1,72 @@
+// Copyright (c) 2012 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 "base/compiler_specific.h"
+#include "ui/gfx/canvas.h"
+#include "ui/gfx/canvas_skia_paint.h"
+#include "ui/gfx/rect.h"
+
+namespace gfx {
+
+CanvasSkiaPaint::CanvasSkiaPaint(HWND hwnd, HDC dc, const PAINTSTRUCT& ps)
+ : hwnd_(hwnd),
+ paint_dc_(dc) {
+ memset(&ps_, 0, sizeof(ps_));
+ ps_.rcPaint.left = ps.rcPaint.left;
+ ps_.rcPaint.right = ps.rcPaint.right;
+ ps_.rcPaint.top = ps.rcPaint.top;
+ ps_.rcPaint.bottom = ps.rcPaint.bottom;
+ Init(true);
+}
+
+CanvasSkiaPaint::CanvasSkiaPaint(HDC dc, bool opaque, int x, int y,
+ int w, int h)
+ : hwnd_(NULL),
+ paint_dc_(dc) {
+ memset(&ps_, 0, sizeof(ps_));
+ ps_.rcPaint.left = x;
+ ps_.rcPaint.right = x + w;
+ ps_.rcPaint.top = y;
+ ps_.rcPaint.bottom = y + h;
+ Init(opaque);
+}
+
+CanvasSkiaPaint::~CanvasSkiaPaint() {
+ if (!is_empty()) {
+ skia::PlatformCanvas* canvas = platform_canvas();
+ canvas->restoreToCount(1);
+ // Commit the drawing to the screen
+ skia::DrawToNativeContext(canvas, paint_dc_, ps_.rcPaint.left,
+ ps_.rcPaint.top, NULL);
+ }
+}
+
+gfx::Rect CanvasSkiaPaint::GetInvalidRect() const {
+ return gfx::Rect(paint_struct().rcPaint);
+}
+
+void CanvasSkiaPaint::Init(bool opaque) {
+ // FIXME(brettw) for ClearType, we probably want to expand the bounds of
+ // painting by one pixel so that the boundaries will be correct (ClearType
+ // text can depend on the adjacent pixel). Then we would paint just the
+ // inset pixels to the screen.
+ const int width = ps_.rcPaint.right - ps_.rcPaint.left;
+ const int height = ps_.rcPaint.bottom - ps_.rcPaint.top;
+
+ RecreateBackingCanvas(gfx::Size(width, height),
+ ui::GetScaleFactorFromScale(ui::win::GetDeviceScaleFactor()),
+ opaque);
+ skia::PlatformCanvas* canvas = platform_canvas();
+
+ canvas->clear(SkColorSetARGB(0, 0, 0, 0));
+
+ // This will bring the canvas into the screen coordinate system for the
+ // dirty rect
+ canvas->translate(
+ -ps_.rcPaint.left / ui::win::GetDeviceScaleFactor(),
+ -ps_.rcPaint.top / ui::win::GetDeviceScaleFactor());
+}
+
+} // namespace gfx
diff --git a/chromium/ui/gfx/canvas_paint_win.h b/chromium/ui/gfx/canvas_paint_win.h
new file mode 100644
index 00000000000..d9326e30c07
--- /dev/null
+++ b/chromium/ui/gfx/canvas_paint_win.h
@@ -0,0 +1,76 @@
+// 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_CANVAS_PAINT_WIN_H_
+#define UI_GFX_CANVAS_PAINT_WIN_H_
+
+#include "skia/ext/platform_canvas.h"
+#include "ui/base/win/dpi.h"
+#include "ui/gfx/canvas.h"
+#include "ui/gfx/size.h"
+
+namespace gfx {
+
+// A class designed to help with WM_PAINT operations on Windows. It will create
+// the bitmap and canvas with the correct size and transform for the dirty rect.
+// The bitmap will be automatically painted to the screen on destruction.
+//
+// You MUST call isEmpty before painting to determine if anything needs
+// painting. Sometimes the dirty rect can actually be empty, and this makes
+// the bitmap functions we call unhappy. The caller should not paint in this
+// case.
+//
+// Therefore, all you need to do is:
+// case WM_PAINT: {
+// PAINTSTRUCT ps;
+// HDC hdc = BeginPaint(hwnd, &ps);
+// gfx::CanvasSkiaPaint canvas(hwnd, hdc, ps);
+// if (!canvas.isEmpty()) {
+// ... paint to the canvas ...
+// }
+// EndPaint(hwnd, &ps);
+// return 0;
+// }
+// Note: The created context is always inialized to (0, 0, 0, 0).
+class UI_EXPORT CanvasSkiaPaint : public Canvas {
+ public:
+ // This constructor assumes the canvas is opaque.
+ CanvasSkiaPaint(HWND hwnd, HDC dc, const PAINTSTRUCT& ps);
+ virtual ~CanvasSkiaPaint();
+
+ // Creates a CanvasSkiaPaint for the specified region that paints to the
+ // specified dc.
+ CanvasSkiaPaint(HDC dc, bool opaque, int x, int y, int w, int h);
+
+ // Returns the rectangle that is invalid.
+ virtual gfx::Rect GetInvalidRect() const;
+
+ // Returns true if the invalid region is empty. The caller should call this
+ // function to determine if anything needs painting.
+ bool is_empty() const {
+ return ps_.rcPaint.right - ps_.rcPaint.left == 0 ||
+ ps_.rcPaint.bottom - ps_.rcPaint.top == 0;
+ };
+
+ // Use to access the Windows painting parameters, especially useful for
+ // getting the bounding rect for painting: paintstruct().rcPaint
+ const PAINTSTRUCT& paint_struct() const { return ps_; }
+
+ // Returns the DC that will be painted to
+ HDC paint_dc() const { return paint_dc_; }
+
+ private:
+ void Init(bool opaque);
+
+ HWND hwnd_;
+ HDC paint_dc_;
+ PAINTSTRUCT ps_;
+
+ // Disallow copy and assign.
+ DISALLOW_COPY_AND_ASSIGN(CanvasSkiaPaint);
+};
+
+} // namespace gfx
+
+#endif // UI_GFX_CANVAS_PAINT_WIN_H_
diff --git a/chromium/ui/gfx/canvas_skia.cc b/chromium/ui/gfx/canvas_skia.cc
new file mode 100644
index 00000000000..bb8ce7db5e5
--- /dev/null
+++ b/chromium/ui/gfx/canvas_skia.cc
@@ -0,0 +1,454 @@
+// Copyright (c) 2012 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 "ui/gfx/canvas.h"
+
+#include "base/i18n/rtl.h"
+#include "base/logging.h"
+#include "base/memory/scoped_ptr.h"
+#include "ui/base/range/range.h"
+#include "ui/base/text/text_elider.h"
+#include "ui/gfx/font.h"
+#include "ui/gfx/font_list.h"
+#include "ui/gfx/insets.h"
+#include "ui/gfx/rect.h"
+#include "ui/gfx/render_text.h"
+#include "ui/gfx/shadow_value.h"
+#include "ui/gfx/text_utils.h"
+
+namespace gfx {
+
+namespace {
+
+// If necessary, wraps |text| with RTL/LTR directionality characters based on
+// |flags| and |text| content.
+// Returns true if the text will be rendered right-to-left.
+// TODO(msw): Nix this, now that RenderTextWin supports directionality directly.
+bool AdjustStringDirection(int flags, base::string16* text) {
+ // TODO(msw): FORCE_LTR_DIRECTIONALITY does not work for RTL text now.
+
+ // If the string is empty or LTR was forced, simply return false since the
+ // default RenderText directionality is already LTR.
+ if (text->empty() || (flags & Canvas::FORCE_LTR_DIRECTIONALITY))
+ return false;
+
+ // If RTL is forced, apply it to the string.
+ if (flags & Canvas::FORCE_RTL_DIRECTIONALITY) {
+ base::i18n::WrapStringWithRTLFormatting(text);
+ return true;
+ }
+
+ // If a direction wasn't forced but the UI language is RTL and there were
+ // strong RTL characters, ensure RTL is applied.
+ if (base::i18n::IsRTL() && base::i18n::StringContainsStrongRTLChars(*text)) {
+ base::i18n::WrapStringWithRTLFormatting(text);
+ return true;
+ }
+
+ // In the default case, the string should be rendered as LTR. RenderText's
+ // default directionality is LTR, so the text doesn't need to be wrapped.
+ // Note that individual runs within the string may still be rendered RTL
+ // (which will be the case for RTL text under non-RTL locales, since under RTL
+ // locales it will be handled by the if statement above).
+ return false;
+}
+
+// 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 cleared pixels shouldn't count as
+// changed.
+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;
+}
+
+// Strips accelerator character prefixes in |text| if needed, based on |flags|.
+// Returns a range in |text| to underline or ui::Range::InvalidRange() if
+// underlining is not needed.
+ui::Range StripAcceleratorChars(int flags, base::string16* text) {
+ if (flags & (Canvas::SHOW_PREFIX | Canvas::HIDE_PREFIX)) {
+ int char_pos = -1;
+ int char_span = 0;
+ *text = RemoveAcceleratorChar(*text, '&', &char_pos, &char_span);
+ if ((flags & Canvas::SHOW_PREFIX) && char_pos != -1)
+ return ui::Range(char_pos, char_pos + char_span);
+ }
+ return ui::Range::InvalidRange();
+}
+
+// Elides |text| and adjusts |range| appropriately. If eliding causes |range|
+// to no longer point to the same character in |text|, |range| is made invalid.
+void ElideTextAndAdjustRange(const Font& font,
+ int width,
+ base::string16* text,
+ ui::Range* range) {
+ const base::char16 start_char =
+ (range->IsValid() ? text->at(range->start()) : 0);
+ *text = ui::ElideText(*text, font, width, ui::ELIDE_AT_END);
+ if (!range->IsValid())
+ return;
+ if (range->start() >= text->length() ||
+ text->at(range->start()) != start_char) {
+ *range = ui::Range::InvalidRange();
+ }
+}
+
+// Updates |render_text| from the specified parameters.
+void UpdateRenderText(const Rect& rect,
+ const base::string16& text,
+ const Font& font,
+ int flags,
+ SkColor color,
+ RenderText* render_text) {
+ render_text->SetFont(font);
+ render_text->SetText(text);
+ render_text->SetCursorEnabled(false);
+
+ Rect display_rect = rect;
+ display_rect.set_height(font.GetHeight());
+ render_text->SetDisplayRect(display_rect);
+
+ // Set the text alignment explicitly based on the directionality of the UI,
+ // if not specified.
+ if (!(flags & (Canvas::TEXT_ALIGN_CENTER |
+ Canvas::TEXT_ALIGN_RIGHT |
+ Canvas::TEXT_ALIGN_LEFT))) {
+ flags |= Canvas::DefaultCanvasTextAlignment();
+ }
+
+ if (flags & Canvas::TEXT_ALIGN_RIGHT)
+ render_text->SetHorizontalAlignment(ALIGN_RIGHT);
+ else if (flags & Canvas::TEXT_ALIGN_CENTER)
+ render_text->SetHorizontalAlignment(ALIGN_CENTER);
+ else
+ render_text->SetHorizontalAlignment(ALIGN_LEFT);
+
+ if (flags & Canvas::NO_SUBPIXEL_RENDERING)
+ render_text->set_background_is_transparent(true);
+
+ render_text->SetColor(color);
+ render_text->SetStyle(BOLD, (font.GetStyle() & Font::BOLD) != 0);
+ render_text->SetStyle(ITALIC, (font.GetStyle() & Font::ITALIC) != 0);
+ render_text->SetStyle(UNDERLINE, (font.GetStyle() & Font::UNDERLINE) != 0);
+}
+
+// Returns updated |flags| to match platform-specific expected behavior.
+int AdjustPlatformSpecificFlags(const base::string16& text, int flags) {
+#if defined(OS_LINUX)
+ // TODO(asvitkine): ash/tooltips/tooltip_controller.cc adds \n's to the string
+ // without passing MULTI_LINE.
+ if (text.find('\n') != base::string16::npos)
+ flags |= Canvas::MULTI_LINE;
+#endif
+
+ return flags;
+}
+
+} // namespace
+
+// static
+void Canvas::SizeStringInt(const base::string16& text,
+ const Font& font,
+ int* width, int* height,
+ int line_height,
+ int flags) {
+ DCHECK_GE(*width, 0);
+ DCHECK_GE(*height, 0);
+
+ flags = AdjustPlatformSpecificFlags(text, flags);
+
+ base::string16 adjusted_text = text;
+#if defined(OS_WIN)
+ AdjustStringDirection(flags, &adjusted_text);
+#endif
+
+ if ((flags & MULTI_LINE) && *width != 0) {
+ ui::WordWrapBehavior wrap_behavior = ui::TRUNCATE_LONG_WORDS;
+ if (flags & CHARACTER_BREAK)
+ wrap_behavior = ui::WRAP_LONG_WORDS;
+ else if (!(flags & NO_ELLIPSIS))
+ wrap_behavior = ui::ELIDE_LONG_WORDS;
+
+ Rect rect(*width, INT_MAX);
+ std::vector<base::string16> strings;
+ ui::ElideRectangleText(adjusted_text, font, rect.width(), rect.height(),
+ wrap_behavior, &strings);
+ scoped_ptr<RenderText> render_text(RenderText::CreateInstance());
+ UpdateRenderText(rect, base::string16(), font, flags, 0, render_text.get());
+
+ int h = 0;
+ int w = 0;
+ for (size_t i = 0; i < strings.size(); ++i) {
+ StripAcceleratorChars(flags, &strings[i]);
+ render_text->SetText(strings[i]);
+ const Size string_size = render_text->GetStringSize();
+ w = std::max(w, string_size.width());
+ h += (i > 0 && line_height > 0) ? line_height : string_size.height();
+ }
+ *width = w;
+ *height = h;
+ } else {
+ // If the string is too long, the call by |RenderTextWin| to |ScriptShape()|
+ // will inexplicably fail with result E_INVALIDARG. Guard against this.
+ const size_t kMaxRenderTextLength = 5000;
+ if (adjusted_text.length() >= kMaxRenderTextLength) {
+ *width = adjusted_text.length() * font.GetAverageCharacterWidth();
+ *height = font.GetHeight();
+ } else {
+ scoped_ptr<RenderText> render_text(RenderText::CreateInstance());
+ Rect rect(*width, *height);
+ StripAcceleratorChars(flags, &adjusted_text);
+ UpdateRenderText(rect, adjusted_text, font, flags, 0, render_text.get());
+ const Size string_size = render_text->GetStringSize();
+ *width = string_size.width();
+ *height = string_size.height();
+ }
+ }
+}
+
+void Canvas::DrawStringWithShadows(const base::string16& text,
+ const Font& font,
+ SkColor color,
+ const Rect& text_bounds,
+ int line_height,
+ int flags,
+ const ShadowValues& shadows) {
+ if (!IntersectsClipRect(text_bounds))
+ return;
+
+ flags = AdjustPlatformSpecificFlags(text, flags);
+
+ Rect clip_rect(text_bounds);
+ clip_rect.Inset(ShadowValue::GetMargin(shadows));
+
+ canvas_->save(SkCanvas::kClip_SaveFlag);
+ ClipRect(clip_rect);
+
+ Rect rect(text_bounds);
+ base::string16 adjusted_text = text;
+
+#if defined(OS_WIN)
+ AdjustStringDirection(flags, &adjusted_text);
+#endif
+
+ scoped_ptr<RenderText> render_text(RenderText::CreateInstance());
+ render_text->SetTextShadows(shadows);
+
+ if (flags & MULTI_LINE) {
+ ui::WordWrapBehavior wrap_behavior = ui::IGNORE_LONG_WORDS;
+ if (flags & CHARACTER_BREAK)
+ wrap_behavior = ui::WRAP_LONG_WORDS;
+ else if (!(flags & NO_ELLIPSIS))
+ wrap_behavior = ui::ELIDE_LONG_WORDS;
+
+ std::vector<base::string16> strings;
+ ui::ElideRectangleText(adjusted_text,
+ font,
+ text_bounds.width(), text_bounds.height(),
+ wrap_behavior,
+ &strings);
+
+ for (size_t i = 0; i < strings.size(); i++) {
+ ui::Range range = StripAcceleratorChars(flags, &strings[i]);
+ UpdateRenderText(rect, strings[i], font, flags, color, render_text.get());
+ int line_padding = 0;
+ if (line_height > 0)
+ line_padding = line_height - render_text->GetStringSize().height();
+ else
+ line_height = render_text->GetStringSize().height();
+
+ // TODO(msw|asvitkine): Center Windows multi-line text: crbug.com/107357
+#if !defined(OS_WIN)
+ if (i == 0) {
+ // TODO(msw|asvitkine): Support multi-line text with varied heights.
+ const int text_height = strings.size() * line_height - line_padding;
+ rect += Vector2d(0, (text_bounds.height() - text_height) / 2);
+ }
+#endif
+
+ rect.set_height(line_height - line_padding);
+
+ if (range.IsValid())
+ render_text->ApplyStyle(UNDERLINE, true, range);
+ render_text->SetDisplayRect(rect);
+ render_text->Draw(this);
+ rect += Vector2d(0, line_height);
+ }
+ } else {
+ ui::Range range = StripAcceleratorChars(flags, &adjusted_text);
+ bool elide_text = ((flags & NO_ELLIPSIS) == 0);
+
+#if defined(OS_LINUX)
+ // On Linux, eliding really means fading the end of the string. But only
+ // for LTR text. RTL text is still elided (on the left) with "...".
+ if (elide_text) {
+ render_text->SetText(adjusted_text);
+ if (render_text->GetTextDirection() == base::i18n::LEFT_TO_RIGHT) {
+ render_text->set_fade_tail(true);
+ elide_text = false;
+ }
+ }
+#endif
+
+ if (elide_text) {
+ ElideTextAndAdjustRange(font,
+ text_bounds.width(),
+ &adjusted_text,
+ &range);
+ }
+
+ UpdateRenderText(rect, adjusted_text, font, flags, color,
+ render_text.get());
+
+ const int text_height = render_text->GetStringSize().height();
+ // Center the text vertically.
+ rect += Vector2d(0, (text_bounds.height() - text_height) / 2);
+ rect.set_height(text_height);
+ render_text->SetDisplayRect(rect);
+ if (range.IsValid())
+ render_text->ApplyStyle(UNDERLINE, true, range);
+ render_text->Draw(this);
+ }
+
+ canvas_->restore();
+}
+
+void Canvas::DrawStringWithHalo(const base::string16& text,
+ const Font& font,
+ SkColor text_color,
+ 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 = SkColorSetA(halo_color_in, 0xFF);
+
+ // Create a temporary buffer filled with the halo color. It must leave room
+ // for the 1-pixel border around the text.
+ Size size(w + 2, h + 2);
+ Canvas text_canvas(size, scale_factor(), true);
+ SkPaint bkgnd_paint;
+ bkgnd_paint.setColor(halo_color);
+ text_canvas.DrawRect(Rect(size), 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);
+
+ uint32_t halo_premul = SkPreMultiplyColor(halo_color);
+ SkBitmap& text_bitmap = const_cast<SkBitmap&>(
+ skia::GetTopDevice(*text_canvas.sk_canvas())->accessBitmap(true));
+
+ for (int cur_y = 0; cur_y < text_bitmap.height(); cur_y++) {
+ uint32_t* text_row = text_bitmap.getAddr32(0, cur_y);
+ for (int cur_x = 0; cur_x < text_bitmap.width(); 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.
+ ImageSkia text_image = ImageSkia(ImageSkiaRep(text_bitmap,
+ text_canvas.scale_factor()));
+ DrawImageInt(text_image, x - 1, y - 1);
+}
+
+void Canvas::DrawFadeTruncatingString(
+ const base::string16& text,
+ TruncateFadeMode truncate_mode,
+ size_t desired_characters_to_truncate_from_head,
+ const Font& font,
+ SkColor color,
+ const Rect& display_rect) {
+ int flags = NO_ELLIPSIS;
+
+ // If the whole string fits in the destination then just draw it directly.
+ if (GetStringWidth(text, font) <= display_rect.width()) {
+ DrawStringInt(text, font, color, display_rect.x(), display_rect.y(),
+ display_rect.width(), display_rect.height(), flags);
+ return;
+ }
+
+ scoped_ptr<RenderText> render_text(RenderText::CreateInstance());
+ base::string16 clipped_text = text;
+ const bool is_rtl = AdjustStringDirection(flags, &clipped_text);
+
+ switch (truncate_mode) {
+ case TruncateFadeTail:
+ render_text->set_fade_tail(true);
+ if (is_rtl)
+ flags |= TEXT_ALIGN_RIGHT;
+ break;
+ case TruncateFadeHead:
+ render_text->set_fade_head(true);
+ if (!is_rtl)
+ flags |= TEXT_ALIGN_RIGHT;
+ break;
+ case TruncateFadeHeadAndTail:
+ DCHECK_GT(desired_characters_to_truncate_from_head, 0u);
+ // Due to the fade effect the first character is hard to see.
+ // We want to make sure that the first character starting at
+ // |desired_characters_to_truncate_from_head| is readable so we reduce
+ // the offset by a little bit.
+ desired_characters_to_truncate_from_head =
+ std::max<int>(0, desired_characters_to_truncate_from_head - 2);
+
+ if (desired_characters_to_truncate_from_head) {
+ // Make sure to clip the text at a UTF16 boundary.
+ U16_SET_CP_LIMIT(text.data(), 0,
+ desired_characters_to_truncate_from_head,
+ text.length());
+ clipped_text = text.substr(desired_characters_to_truncate_from_head);
+ }
+
+ render_text->set_fade_tail(true);
+ render_text->set_fade_head(true);
+ break;
+ }
+
+ // Default to left alignment unless right alignment was chosen above.
+ if (!(flags & TEXT_ALIGN_RIGHT))
+ flags |= TEXT_ALIGN_LEFT;
+
+ Rect rect = display_rect;
+ UpdateRenderText(rect, clipped_text, font, flags, color, render_text.get());
+
+ const int line_height = render_text->GetStringSize().height();
+ // Center the text vertically.
+ rect += Vector2d(0, (display_rect.height() - line_height) / 2);
+ rect.set_height(line_height);
+ render_text->SetDisplayRect(rect);
+
+ canvas_->save(SkCanvas::kClip_SaveFlag);
+ ClipRect(display_rect);
+ render_text->Draw(this);
+ canvas_->restore();
+}
+
+} // namespace gfx
diff --git a/chromium/ui/gfx/canvas_skia_paint.h b/chromium/ui/gfx/canvas_skia_paint.h
new file mode 100644
index 00000000000..3f21f5cc410
--- /dev/null
+++ b/chromium/ui/gfx/canvas_skia_paint.h
@@ -0,0 +1,23 @@
+// Copyright (c) 2012 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_
+
+// This file provides an easy way to include the appropriate CanvasPaint
+// header file on your platform.
+
+#if defined(WIN32)
+#include "ui/gfx/canvas_paint_win.h"
+#elif defined(__APPLE__)
+#include "ui/gfx/canvas_paint_mac.h"
+#elif defined(__linux__) || defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__sun)
+#if defined(TOOLKIT_GTK)
+#include "ui/gfx/canvas_paint_gtk.h"
+#else
+#error "No canvas paint for this platform"
+#endif
+#endif
+
+#endif // UI_GFX_CANVAS_SKIA_PAINT_H_
diff --git a/chromium/ui/gfx/canvas_unittest.cc b/chromium/ui/gfx/canvas_unittest.cc
new file mode 100644
index 00000000000..21b9f51e57e
--- /dev/null
+++ b/chromium/ui/gfx/canvas_unittest.cc
@@ -0,0 +1,61 @@
+// Copyright (c) 2012 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 <limits>
+
+#include "base/strings/utf_string_conversions.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "ui/gfx/canvas.h"
+#include "ui/gfx/font.h"
+
+namespace gfx {
+
+class CanvasTest : public testing::Test {
+ protected:
+ int GetStringWidth(const char *text) {
+ return Canvas::GetStringWidth(UTF8ToUTF16(text), font_);
+ }
+
+ gfx::Size SizeStringInt(const char *text, int width, int line_height) {
+ base::string16 text16 = UTF8ToUTF16(text);
+ int height = 0;
+ int flags =
+ (text16.find('\n') != base::string16::npos) ? Canvas::MULTI_LINE : 0;
+ Canvas::SizeStringInt(text16, font_, &width, &height, line_height, flags);
+ return gfx::Size(width, height);
+ }
+
+ private:
+ gfx::Font font_;
+};
+
+TEST_F(CanvasTest, StringWidth) {
+ EXPECT_GT(GetStringWidth("Test"), 0);
+}
+
+TEST_F(CanvasTest, StringWidthEmptyString) {
+ EXPECT_EQ(0, GetStringWidth(""));
+}
+
+TEST_F(CanvasTest, StringSizeEmptyString) {
+ gfx::Size size = SizeStringInt("", 0, 0);
+ EXPECT_EQ(0, size.width());
+ EXPECT_GT(size.height(), 0);
+}
+
+// Line height is only supported on Skia.
+#if defined(OS_MACOSX) || defined(OS_ANDROID)
+#define MAYBE_StringSizeWithLineHeight DISABLED_StringSizeWithLineHeight
+#else
+#define MAYBE_StringSizeWithLineHeight StringSizeWithLineHeight
+#endif
+
+TEST_F(CanvasTest, MAYBE_StringSizeWithLineHeight) {
+ gfx::Size one_line_size = SizeStringInt("Q", 0, 0);
+ gfx::Size four_line_size = SizeStringInt("Q\nQ\nQ\nQ", 1000000, 1000);
+ EXPECT_EQ(one_line_size.width(), four_line_size.width());
+ EXPECT_EQ(3 * 1000 + one_line_size.height(), four_line_size.height());
+}
+
+} // namespace gfx
diff --git a/chromium/ui/gfx/codec/DEPS b/chromium/ui/gfx/codec/DEPS
new file mode 100644
index 00000000000..6a58de326fb
--- /dev/null
+++ b/chromium/ui/gfx/codec/DEPS
@@ -0,0 +1,7 @@
+include_rules = [
+ "+skia",
+ "+third_party/libjpeg",
+ "+third_party/libjpeg_turbo",
+ "+third_party/libpng",
+ "+third_party/zlib",
+]
diff --git a/chromium/ui/gfx/codec/jpeg_codec.cc b/chromium/ui/gfx/codec/jpeg_codec.cc
new file mode 100644
index 00000000000..a9938163672
--- /dev/null
+++ b/chromium/ui/gfx/codec/jpeg_codec.cc
@@ -0,0 +1,632 @@
+// 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 "ui/gfx/codec/jpeg_codec.h"
+
+#include <setjmp.h>
+
+#include "base/logging.h"
+#include "base/memory/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>
+#elif defined(USE_LIBJPEG_TURBO)
+#include "third_party/libjpeg_turbo/jpeglib.h"
+#else
+#include "third_party/libjpeg/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
+
+// This method helps identify at run time which library chromium is using.
+JPEGCodec::LibraryVariant JPEGCodec::JpegLibraryVariant() {
+#if defined(USE_SYSTEM_LIBJPEG)
+ return SYSTEM_LIBJPEG;
+#elif defined(USE_LIBJPEG_TURBO)
+ return LIBJPEG_TURBO;
+#else
+ return IJG_LIBJPEG;
+#endif
+}
+
+// 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);
+}
+
+#if !defined(JCS_EXTENSIONS)
+// 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];
+ }
+}
+#endif // !defined(JCS_EXTENSIONS)
+
+// 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();
+#if !defined(JCS_EXTENSIONS)
+ unsigned char* row_buffer = NULL;
+#endif
+
+ // 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();
+#if !defined(JCS_EXTENSIONS)
+ delete[] row_buffer;
+#endif
+ 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;
+#ifdef JCS_EXTENSIONS
+ // Choose an input colorspace and return if it is an unsupported one. Since
+ // libjpeg-turbo supports all input formats used by Chromium (i.e. RGB, RGBA,
+ // and BGRA), we just map the input parameters to a colorspace used by
+ // libjpeg-turbo.
+ if (format == FORMAT_RGB) {
+ cinfo.input_components = 3;
+ cinfo.in_color_space = JCS_RGB;
+ } else if (format == FORMAT_RGBA ||
+ (format == FORMAT_SkBitmap && SK_R32_SHIFT == 0)) {
+ cinfo.input_components = 4;
+ cinfo.in_color_space = JCS_EXT_RGBX;
+ } else if (format == FORMAT_BGRA ||
+ (format == FORMAT_SkBitmap && SK_B32_SHIFT == 0)) {
+ cinfo.input_components = 4;
+ cinfo.in_color_space = JCS_EXT_BGRX;
+ } else {
+ // We can exit this function without calling jpeg_destroy_compress() because
+ // CompressDestroyer automaticaly calls it.
+ NOTREACHED() << "Invalid pixel format";
+ return false;
+ }
+#else
+ cinfo.in_color_space = JCS_RGB;
+#endif
+ 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
+#ifdef JCS_EXTENSIONS
+ // This function already returns when the input format is not supported by
+ // libjpeg-turbo and needs conversion. Therefore, we just encode lines without
+ // conversions.
+ 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
+ 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
+ row_buffer = new unsigned char[w * 3];
+
+ while (cinfo.next_scanline < cinfo.image_height) {
+ converter(&input[cinfo.next_scanline * row_byte_width], w, row_buffer);
+ jpeg_write_scanlines(&cinfo, &row_buffer, 1);
+ }
+ delete[] row_buffer;
+ }
+#endif
+
+ 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) {
+}
+
+#if !defined(JCS_EXTENSIONS)
+// 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;
+ }
+}
+#endif // !defined(JCS_EXTENSIONS)
+
+// 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:
+#ifdef JCS_EXTENSIONS
+ // Choose an output colorspace and return if it is an unsupported one.
+ // Same as JPEGCodec::Encode(), libjpeg-turbo supports all input formats
+ // used by Chromium (i.e. RGB, RGBA, and BGRA) and we just map the input
+ // parameters to a colorspace.
+ if (format == FORMAT_RGB) {
+ cinfo.out_color_space = JCS_RGB;
+ cinfo.output_components = 3;
+ } else if (format == FORMAT_RGBA ||
+ (format == FORMAT_SkBitmap && SK_R32_SHIFT == 0)) {
+ cinfo.out_color_space = JCS_EXT_RGBX;
+ cinfo.output_components = 4;
+ } else if (format == FORMAT_BGRA ||
+ (format == FORMAT_SkBitmap && SK_B32_SHIFT == 0)) {
+ cinfo.out_color_space = JCS_EXT_BGRX;
+ cinfo.output_components = 4;
+ } else {
+ // We can exit this function without calling jpeg_destroy_decompress()
+ // because DecompressDestroyer automaticaly calls it.
+ NOTREACHED() << "Invalid pixel format";
+ return false;
+ }
+#else
+ cinfo.out_color_space = JCS_RGB;
+#endif
+ 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;
+ }
+#ifndef JCS_EXTENSIONS
+ cinfo.output_components = 3;
+#endif
+
+ 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;
+
+#ifdef JCS_EXTENSIONS
+ // Create memory for a decoded image and write decoded lines to the memory
+ // without conversions same as JPEGCodec::Encode().
+ 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
+ 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_ptr<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]);
+ }
+ }
+#endif
+
+ 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/chromium/ui/gfx/codec/jpeg_codec.h b/chromium/ui/gfx/codec/jpeg_codec.h
new file mode 100644
index 00000000000..e4edeee2236
--- /dev/null
+++ b/chromium/ui/gfx/codec/jpeg_codec.h
@@ -0,0 +1,79 @@
+// 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_CODEC_JPEG_CODEC_H_
+#define UI_GFX_CODEC_JPEG_CODEC_H_
+
+#include <stddef.h>
+#include <vector>
+
+#include "ui/base/ui_export.h"
+
+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 UI_EXPORT 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
+ };
+
+ enum LibraryVariant {
+ SYSTEM_LIBJPEG = 0,
+ LIBJPEG_TURBO,
+ IJG_LIBJPEG,
+ };
+
+ // This method helps identify at run time which library chromium is using.
+ static LibraryVariant JpegLibraryVariant();
+
+ // 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/chromium/ui/gfx/codec/jpeg_codec_unittest.cc b/chromium/ui/gfx/codec/jpeg_codec_unittest.cc
new file mode 100644
index 00000000000..7a8756a2395
--- /dev/null
+++ b/chromium/ui/gfx/codec/jpeg_codec_unittest.cc
@@ -0,0 +1,217 @@
+// 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 <math.h>
+
+#include "base/basictypes.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "ui/gfx/codec/jpeg_codec.h"
+
+namespace {
+
+// A JPEG image used by TopSitesMigrationTest, whose size is 1x1.
+// This image causes an invalid-read error to libjpeg-turbo 1.0.1.
+const uint8 kTopSitesMigrationTestImage[] =
+ "\xff\xd8\xff\xe0\x00\x10\x4a\x46\x49\x46\x00\x01\x01\x00\x00\x01"
+ "\x00\x01\x00\x00\xff\xdb\x00\x43\x00\x03\x02\x02\x03\x02\x02\x03"
+ "\x03\x03\x03\x04\x03\x03\x04\x05\x08\x05\x05\x04\x04\x05\x0a\x07"
+ "\x07\x06\x08\x0c\x0a\x0c\x0c\x0b\x0a\x0b\x0b\x0d\x0e\x12\x10\x0d"
+ "\x0e\x11\x0e\x0b\x0b\x10\x16\x10\x11\x13\x14\x15\x15\x15\x0c\x0f"
+ "\x17\x18\x16\x14\x18\x12\x14\x15\x14\xff\xdb\x00\x43\x01\x03\x04"
+ "\x04\x05\x04\x05\x09\x05\x05\x09\x14\x0d\x0b\x0d\x14\x14\x14\x14"
+ "\x14\x14\x14\x14\x14\x14\x14\x14\x14\x14\x14\x14\x14\x14\x14\x14"
+ "\x14\x14\x14\x14\x14\x14\x14\x14\x14\x14\x14\x14\x14\x14\x14\x14"
+ "\x14\x14\x14\x14\x14\x14\x14\x14\x14\x14\x14\x14\x14\x14\xff\xc0"
+ "\x00\x11\x08\x00\x01\x00\x01\x03\x01\x22\x00\x02\x11\x01\x03\x11"
+ "\x01\xff\xc4\x00\x1f\x00\x00\x01\x05\x01\x01\x01\x01\x01\x01\x00"
+ "\x00\x00\x00\x00\x00\x00\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09"
+ "\x0a\x0b\xff\xc4\x00\xb5\x10\x00\x02\x01\x03\x03\x02\x04\x03\x05"
+ "\x05\x04\x04\x00\x00\x01\x7d\x01\x02\x03\x00\x04\x11\x05\x12\x21"
+ "\x31\x41\x06\x13\x51\x61\x07\x22\x71\x14\x32\x81\x91\xa1\x08\x23"
+ "\x42\xb1\xc1\x15\x52\xd1\xf0\x24\x33\x62\x72\x82\x09\x0a\x16\x17"
+ "\x18\x19\x1a\x25\x26\x27\x28\x29\x2a\x34\x35\x36\x37\x38\x39\x3a"
+ "\x43\x44\x45\x46\x47\x48\x49\x4a\x53\x54\x55\x56\x57\x58\x59\x5a"
+ "\x63\x64\x65\x66\x67\x68\x69\x6a\x73\x74\x75\x76\x77\x78\x79\x7a"
+ "\x83\x84\x85\x86\x87\x88\x89\x8a\x92\x93\x94\x95\x96\x97\x98\x99"
+ "\x9a\xa2\xa3\xa4\xa5\xa6\xa7\xa8\xa9\xaa\xb2\xb3\xb4\xb5\xb6\xb7"
+ "\xb8\xb9\xba\xc2\xc3\xc4\xc5\xc6\xc7\xc8\xc9\xca\xd2\xd3\xd4\xd5"
+ "\xd6\xd7\xd8\xd9\xda\xe1\xe2\xe3\xe4\xe5\xe6\xe7\xe8\xe9\xea\xf1"
+ "\xf2\xf3\xf4\xf5\xf6\xf7\xf8\xf9\xfa\xff\xc4\x00\x1f\x01\x00\x03"
+ "\x01\x01\x01\x01\x01\x01\x01\x01\x01\x00\x00\x00\x00\x00\x00\x01"
+ "\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\xff\xc4\x00\xb5\x11\x00"
+ "\x02\x01\x02\x04\x04\x03\x04\x07\x05\x04\x04\x00\x01\x02\x77\x00"
+ "\x01\x02\x03\x11\x04\x05\x21\x31\x06\x12\x41\x51\x07\x61\x71\x13"
+ "\x22\x32\x81\x08\x14\x42\x91\xa1\xb1\xc1\x09\x23\x33\x52\xf0\x15"
+ "\x62\x72\xd1\x0a\x16\x24\x34\xe1\x25\xf1\x17\x18\x19\x1a\x26\x27"
+ "\x28\x29\x2a\x35\x36\x37\x38\x39\x3a\x43\x44\x45\x46\x47\x48\x49"
+ "\x4a\x53\x54\x55\x56\x57\x58\x59\x5a\x63\x64\x65\x66\x67\x68\x69"
+ "\x6a\x73\x74\x75\x76\x77\x78\x79\x7a\x82\x83\x84\x85\x86\x87\x88"
+ "\x89\x8a\x92\x93\x94\x95\x96\x97\x98\x99\x9a\xa2\xa3\xa4\xa5\xa6"
+ "\xa7\xa8\xa9\xaa\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9\xba\xc2\xc3\xc4"
+ "\xc5\xc6\xc7\xc8\xc9\xca\xd2\xd3\xd4\xd5\xd6\xd7\xd8\xd9\xda\xe2"
+ "\xe3\xe4\xe5\xe6\xe7\xe8\xe9\xea\xf2\xf3\xf4\xf5\xf6\xf7\xf8\xf9"
+ "\xfa\xff\xda\x00\x0c\x03\x01\x00\x02\x11\x03\x11\x00\x3f\x00\xf9"
+ "\xd2\x8a\x28\xaf\xc3\x0f\xf5\x4c\xff\xd9";
+
+} // namespace
+
+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.empty())
+ 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));
+}
+
+// Test that we can decode JPEG images without invalid-read errors on valgrind.
+// This test decodes a 1x1 JPEG image and writes the decoded RGB (or RGBA) pixel
+// to the output buffer without OOB reads.
+TEST(JPEGCodec, InvalidRead) {
+ std::vector<unsigned char> output;
+ int outw, outh;
+ JPEGCodec::Decode(kTopSitesMigrationTestImage,
+ arraysize(kTopSitesMigrationTestImage),
+ JPEGCodec::FORMAT_RGB, &output,
+ &outw, &outh);
+
+ JPEGCodec::Decode(kTopSitesMigrationTestImage,
+ arraysize(kTopSitesMigrationTestImage),
+ JPEGCodec::FORMAT_RGBA, &output,
+ &outw, &outh);
+}
+
+} // namespace gfx
diff --git a/chromium/ui/gfx/codec/png_codec.cc b/chromium/ui/gfx/codec/png_codec.cc
new file mode 100644
index 00000000000..360526b3436
--- /dev/null
+++ b/chromium/ui/gfx/codec/png_codec.cc
@@ -0,0 +1,786 @@
+// Copyright (c) 2012 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 "ui/gfx/codec/png_codec.h"
+
+#include "base/logging.h"
+#include "base/strings/string_util.h"
+#include "third_party/libpng/png.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"
+#include "third_party/zlib/zlib.h"
+#include "ui/gfx/size.h"
+#include "ui/gfx/skia_util.h"
+
+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 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) {
+ gfx::ConvertSkiaToRGBA(skia, pixel_width, rgba);
+}
+
+} // 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),
+ 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),
+ 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;
+
+ // 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);
+};
+
+// User transform (passed to libpng) which converts a row decoded by libpng to
+// Skia format. Expects the row to have 4 channels, otherwise there won't be
+// enough room in |data|.
+void ConvertRGBARowToSkia(png_structp png_ptr,
+ png_row_infop row_info,
+ png_bytep data) {
+ const int channels = row_info->channels;
+ DCHECK_EQ(channels, 4);
+
+ PngDecoderState* state =
+ static_cast<PngDecoderState*>(png_get_user_transform_ptr(png_ptr));
+ DCHECK(state) << "LibPNG user transform pointer is NULL";
+
+ unsigned char* const end = data + row_info->rowbytes;
+ for (unsigned char* p = data; p < end; p += channels) {
+ uint32_t* sk_pixel = reinterpret_cast<uint32_t*>(p);
+ const unsigned char alpha = p[channels - 1];
+ if (alpha != 255) {
+ state->is_opaque = false;
+ *sk_pixel = SkPreMultiplyARGB(alpha, p[0], p[1], p[2]);
+ } else {
+ *sk_pixel = SkPackARGB32(alpha, p[0], p[1], p[2]);
+ }
+ }
+}
+
+// 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;
+ 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);
+
+ // The following png_set_* calls have to be done in the order dictated by
+ // the libpng docs. Please take care if you have to move any of them. This
+ // is also why certain things are done outside of the switch, even though
+ // they look like they belong there.
+
+ // 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);
+
+ // The '!= 0' is for silencing a Windows compiler warning.
+ bool input_has_alpha = ((color_type & PNG_COLOR_MASK_ALPHA) != 0);
+
+ // Transparency for paletted images.
+ if (png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS)) {
+ png_set_expand(png_ptr);
+ input_has_alpha = true;
+ }
+
+ // Convert 16-bit to 8-bit.
+ if (bit_depth == 16)
+ png_set_strip_16(png_ptr);
+
+ // Pick our row format converter necessary for this data.
+ if (!input_has_alpha) {
+ switch (state->output_format) {
+ case PNGCodec::FORMAT_RGB:
+ state->output_channels = 3;
+ break;
+ case PNGCodec::FORMAT_RGBA:
+ state->output_channels = 4;
+ png_set_add_alpha(png_ptr, 0xFF, PNG_FILLER_AFTER);
+ break;
+ case PNGCodec::FORMAT_BGRA:
+ state->output_channels = 4;
+ png_set_bgr(png_ptr);
+ png_set_add_alpha(png_ptr, 0xFF, PNG_FILLER_AFTER);
+ break;
+ case PNGCodec::FORMAT_SkBitmap:
+ state->output_channels = 4;
+ png_set_add_alpha(png_ptr, 0xFF, PNG_FILLER_AFTER);
+ break;
+ }
+ } else {
+ switch (state->output_format) {
+ case PNGCodec::FORMAT_RGB:
+ state->output_channels = 3;
+ png_set_strip_alpha(png_ptr);
+ break;
+ case PNGCodec::FORMAT_RGBA:
+ state->output_channels = 4;
+ break;
+ case PNGCodec::FORMAT_BGRA:
+ state->output_channels = 4;
+ png_set_bgr(png_ptr);
+ break;
+ case PNGCodec::FORMAT_SkBitmap:
+ state->output_channels = 4;
+ break;
+ }
+ }
+
+ // 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);
+ }
+
+ // Setting the user transforms here (as opposed to inside the switch above)
+ // because all png_set_* calls need to be done in the specific order
+ // mandated by libpng.
+ if (state->output_format == PNGCodec::FORMAT_SkBitmap) {
+ png_set_read_user_transform_fn(png_ptr, ConvertRGBARowToSkia);
+ png_set_user_transform_info(png_ptr, state, 0, 0);
+ }
+
+ // Tell libpng to send us rows for interlaced pngs.
+ if (interlace_type == PNG_INTERLACE_ADAM7)
+ png_set_interlace_handling(png_ptr);
+
+ png_read_update_info(png_ptr, info_ptr);
+
+ 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) {
+ if (!new_row)
+ return; // Interlaced image; row didn't change this pass.
+
+ PngDecoderState* state = static_cast<PngDecoderState*>(
+ png_get_progressive_ptr(png_ptr));
+
+ 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];
+ png_progressive_combine_row(png_ptr, dest, new_row);
+}
+
+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_;
+ DISALLOW_COPY_AND_ASSIGN(PngReadStructDestroyer);
+};
+
+// Automatically destroys the given write structs on destruction to make
+// cleanup and error handling code cleaner.
+class PngWriteStructDestroyer {
+ public:
+ explicit PngWriteStructDestroyer(png_struct** ps) : ps_(ps), pi_(0) {
+ }
+ ~PngWriteStructDestroyer() {
+ png_destroy_write_struct(ps_, pi_);
+ }
+ void SetInfoStruct(png_info** pi) {
+ pi_ = pi;
+ }
+ private:
+ png_struct** ps_;
+ png_info** pi_;
+ DISALLOW_COPY_AND_ASSIGN(PngWriteStructDestroyer);
+};
+
+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;
+}
+
+// Libpng user error and warning functions which allows us to print libpng
+// errors and warnings using Chrome's logging facilities instead of stderr.
+
+void LogLibPNGDecodeError(png_structp png_ptr, png_const_charp error_msg) {
+ DLOG(ERROR) << "libpng decode error: " << error_msg;
+ longjmp(png_jmpbuf(png_ptr), 1);
+}
+
+void LogLibPNGDecodeWarning(png_structp png_ptr, png_const_charp warning_msg) {
+ DLOG(ERROR) << "libpng decode warning: " << warning_msg;
+}
+
+void LogLibPNGEncodeError(png_structp png_ptr, png_const_charp error_msg) {
+ DLOG(ERROR) << "libpng encode error: " << error_msg;
+ longjmp(png_jmpbuf(png_ptr), 1);
+}
+
+void LogLibPNGEncodeWarning(png_structp png_ptr, png_const_charp warning_msg) {
+ DLOG(ERROR) << "libpng encode warning: " << warning_msg;
+}
+
+} // 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_error_fn(png_ptr, NULL, LogLibPNGDecodeError, LogLibPNGDecodeWarning);
+ 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];
+ }
+}
+
+#ifdef PNG_TEXT_SUPPORTED
+class CommentWriter {
+ public:
+ explicit CommentWriter(const std::vector<PNGCodec::Comment>& comments)
+ : comments_(comments),
+ png_text_(new png_text[comments.size()]) {
+ for (size_t i = 0; i < comments.size(); ++i)
+ AddComment(i, comments[i]);
+ }
+
+ ~CommentWriter() {
+ for (size_t i = 0; i < comments_.size(); ++i) {
+ free(png_text_[i].key);
+ free(png_text_[i].text);
+ }
+ delete [] png_text_;
+ }
+
+ bool HasComments() {
+ return !comments_.empty();
+ }
+
+ png_text* get_png_text() {
+ return png_text_;
+ }
+
+ int size() {
+ return static_cast<int>(comments_.size());
+ }
+
+ private:
+ void AddComment(size_t pos, const PNGCodec::Comment& comment) {
+ png_text_[pos].compression = PNG_TEXT_COMPRESSION_NONE;
+ // A PNG comment's key can only be 79 characters long.
+ DCHECK(comment.key.length() < 79);
+ png_text_[pos].key = base::strdup(comment.key.substr(0, 78).c_str());
+ png_text_[pos].text = base::strdup(comment.text.c_str());
+ png_text_[pos].text_length = comment.text.length();
+#ifdef PNG_iTXt_SUPPORTED
+ png_text_[pos].itxt_length = 0;
+ png_text_[pos].lang = 0;
+ png_text_[pos].lang_key = 0;
+#endif
+ }
+
+ DISALLOW_COPY_AND_ASSIGN(CommentWriter);
+
+ const std::vector<PNGCodec::Comment> comments_;
+ png_text* png_text_;
+};
+#endif // PNG_TEXT_SUPPORTED
+
+// 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 compression_level,
+ int png_output_color_type, int output_color_components,
+ FormatConverter converter,
+ const std::vector<PNGCodec::Comment>& comments) {
+#ifdef PNG_TEXT_SUPPORTED
+ CommentWriter comment_writer(comments);
+#endif
+ unsigned char* row_buffer = NULL;
+
+ // 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))) {
+ delete[] row_buffer;
+ return false;
+ }
+
+ png_set_compression_level(png_ptr, compression_level);
+
+ // Set our callback for libpng to give us the data.
+ png_set_write_fn(png_ptr, state, EncoderWriteCallback, FakeFlushCallback);
+ png_set_error_fn(png_ptr, NULL, LogLibPNGEncodeError, LogLibPNGEncodeWarning);
+
+ 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);
+
+#ifdef PNG_TEXT_SUPPORTED
+ if (comment_writer.HasComments()) {
+ png_set_text(png_ptr, info_ptr, comment_writer.get_png_text(),
+ comment_writer.size());
+ }
+#endif
+
+ 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.
+ row_buffer = new unsigned char[width * output_color_components];
+ for (int y = 0; y < height; y ++) {
+ converter(&input[y * row_byte_width], width, row_buffer, NULL);
+ png_write_row(png_ptr, row_buffer);
+ }
+ delete[] row_buffer;
+ }
+
+ png_write_end(png_ptr, info_ptr);
+ return true;
+}
+
+} // namespace
+
+// static
+bool PNGCodec::Encode(const unsigned char* input, ColorFormat format,
+ const Size& size, int row_byte_width,
+ bool discard_transparency,
+ const std::vector<Comment>& comments,
+ std::vector<unsigned char>* output) {
+ return PNGCodec::EncodeWithCompressionLevel(input, format, size,
+ row_byte_width,
+ discard_transparency,
+ comments, Z_DEFAULT_COMPRESSION,
+ output);
+}
+
+// static
+bool PNGCodec::EncodeWithCompressionLevel(const unsigned char* input,
+ ColorFormat format, const Size& size,
+ int row_byte_width,
+ bool discard_transparency,
+ const std::vector<Comment>& comments,
+ int compression_level,
+ 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;
+ 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 * size.width() <= row_byte_width);
+
+ png_struct* png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING,
+ NULL, NULL, NULL);
+ if (!png_ptr)
+ return false;
+ PngWriteStructDestroyer destroyer(&png_ptr);
+ png_info* info_ptr = png_create_info_struct(png_ptr);
+ if (!info_ptr)
+ return false;
+ destroyer.SetInfoStruct(&info_ptr);
+
+ output->clear();
+
+ PngEncoderState state(output);
+ bool success = DoLibpngWrite(png_ptr, info_ptr, &state,
+ size.width(), size.height(), row_byte_width,
+ input, compression_level, png_output_color_type,
+ output_color_components, converter, comments);
+
+ 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);
+ if (input.empty())
+ return false;
+ DCHECK(input.bytesPerPixel() == bbp);
+ DCHECK(static_cast<int>(input.rowBytes()) >= input.width() * bbp);
+
+ return Encode(reinterpret_cast<unsigned char*>(input.getAddr32(0, 0)),
+ FORMAT_SkBitmap, Size(input.width(), input.height()),
+ static_cast<int>(input.rowBytes()), discard_transparency,
+ std::vector<Comment>(), output);
+}
+
+PNGCodec::Comment::Comment(const std::string& k, const std::string& t)
+ : key(k), text(t) {
+}
+
+PNGCodec::Comment::~Comment() {
+}
+
+} // namespace gfx
diff --git a/chromium/ui/gfx/codec/png_codec.h b/chromium/ui/gfx/codec/png_codec.h
new file mode 100644
index 00000000000..3a6d295dbdf
--- /dev/null
+++ b/chromium/ui/gfx/codec/png_codec.h
@@ -0,0 +1,134 @@
+// 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_CODEC_PNG_CODEC_H_
+#define UI_GFX_CODEC_PNG_CODEC_H_
+
+#include <string>
+#include <vector>
+
+#include "base/basictypes.h"
+#include "ui/base/ui_export.h"
+
+class SkBitmap;
+
+namespace gfx {
+
+class Size;
+
+// 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 UI_EXPORT 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
+ };
+
+ // Represents a comment in the tEXt ancillary chunk of the png.
+ struct UI_EXPORT Comment {
+ Comment(const std::string& k, const std::string& t);
+ ~Comment();
+
+ std::string key;
+ std::string text;
+ };
+
+ // Calls PNGCodec::EncodeWithCompressionLevel with the default compression
+ // level.
+ static bool Encode(const unsigned char* input,
+ ColorFormat format,
+ const Size& size,
+ int row_byte_width,
+ bool discard_transparency,
+ const std::vector<Comment>& comments,
+ std::vector<unsigned char>* output);
+
+ // 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.
+ //
+ // size: 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.
+ // comments: comments to be written in the png's metadata.
+ // compression_level: An integer between -1 and 9, corresponding to zlib's
+ // compression levels. -1 is the default.
+ static bool EncodeWithCompressionLevel(const unsigned char* input,
+ ColorFormat format,
+ const Size& size,
+ int row_byte_width,
+ bool discard_transparency,
+ const std::vector<Comment>& comments,
+ int compression_level,
+ 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/chromium/ui/gfx/codec/png_codec_unittest.cc b/chromium/ui/gfx/codec/png_codec_unittest.cc
new file mode 100644
index 00000000000..e3540a27114
--- /dev/null
+++ b/chromium/ui/gfx/codec/png_codec_unittest.cc
@@ -0,0 +1,1162 @@
+// Copyright (c) 2012 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 <algorithm>
+#include <cmath>
+
+#include "base/logging.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/libpng/png.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"
+#include "third_party/zlib/zlib.h"
+#include "ui/gfx/codec/png_codec.h"
+#include "ui/gfx/size.h"
+
+namespace gfx {
+
+namespace {
+
+void MakeRGBImage(int w, int h, std::vector<unsigned char>* data) {
+ data->resize(w * h * 3);
+ for (int y = 0; y < h; y++) {
+ for (int x = 0; x < w; x++) {
+ unsigned char* org_px = &(*data)[(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.
+void MakeRGBAImage(int w, int h, bool use_transparency,
+ std::vector<unsigned char>* data) {
+ data->resize(w * h * 4);
+ for (int y = 0; y < h; y++) {
+ for (int x = 0; x < w; x++) {
+ unsigned char* org_px = &(*data)[(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)
+ }
+ }
+}
+
+// Creates a palette-based image.
+void MakePaletteImage(int w, int h,
+ std::vector<unsigned char>* data,
+ std::vector<png_color>* palette,
+ std::vector<unsigned char>* trans_chunk = 0) {
+ data->resize(w * h);
+ palette->resize(w);
+ for (int i = 0; i < w; ++i) {
+ png_color& color = (*palette)[i];
+ color.red = i * 3;
+ color.green = color.red + 1;
+ color.blue = color.red + 2;
+ }
+ for (int y = 0; y < h; y++) {
+ for (int x = 0; x < w; x++) {
+ (*data)[y * w + x] = x; // palette index
+ }
+ }
+ if (trans_chunk) {
+ trans_chunk->resize(palette->size());
+ for (std::size_t i = 0; i < trans_chunk->size(); ++i) {
+ (*trans_chunk)[i] = i % 256;
+ }
+ }
+}
+
+// Creates a grayscale image without an alpha channel.
+void MakeGrayscaleImage(int w, int h,
+ std::vector<unsigned char>* data) {
+ data->resize(w * h);
+ for (int y = 0; y < h; y++) {
+ for (int x = 0; x < w; x++) {
+ (*data)[y * w + x] = x; // gray value
+ }
+ }
+}
+
+// Creates a grayscale image with an alpha channel.
+void MakeGrayscaleAlphaImage(int w, int h,
+ std::vector<unsigned char>* data) {
+ data->resize(w * h * 2);
+ for (int y = 0; y < h; y++) {
+ for (int x = 0; x < w; x++) {
+ unsigned char* px = &(*data)[(y * w + x) * 2];
+ px[0] = x; // gray value
+ px[1] = x % 256; // alpha
+ }
+ }
+}
+
+// User write function (to be passed to libpng by EncodeImage) which writes
+// into a buffer instead of to a file.
+void WriteImageData(png_structp png_ptr,
+ png_bytep data,
+ png_size_t length) {
+ std::vector<unsigned char>& v =
+ *static_cast<std::vector<unsigned char>*>(png_get_io_ptr(png_ptr));
+ v.resize(v.size() + length);
+ memcpy(&v[v.size() - length], data, length);
+}
+
+// User flush function; goes with WriteImageData, above.
+void FlushImageData(png_structp /*png_ptr*/) {
+}
+
+// Libpng user error function which allows us to print libpng errors using
+// Chrome's logging facilities instead of stderr.
+void LogLibPNGError(png_structp png_ptr,
+ png_const_charp error_msg) {
+ DLOG(ERROR) << "libpng encode error: " << error_msg;
+ longjmp(png_jmpbuf(png_ptr), 1);
+}
+
+// Goes with LogLibPNGError, above.
+void LogLibPNGWarning(png_structp png_ptr,
+ png_const_charp warning_msg) {
+ DLOG(ERROR) << "libpng encode warning: " << warning_msg;
+}
+
+// Color types supported by EncodeImage. Required because neither libpng nor
+// PNGCodec::Encode supports all of the required values.
+enum ColorType {
+ COLOR_TYPE_GRAY = PNG_COLOR_TYPE_GRAY,
+ COLOR_TYPE_GRAY_ALPHA = PNG_COLOR_TYPE_GRAY_ALPHA,
+ COLOR_TYPE_PALETTE = PNG_COLOR_TYPE_PALETTE,
+ COLOR_TYPE_RGB = PNG_COLOR_TYPE_RGB,
+ COLOR_TYPE_RGBA = PNG_COLOR_TYPE_RGBA,
+ COLOR_TYPE_BGR,
+ COLOR_TYPE_BGRA
+};
+
+// PNG encoder used for testing. Required because PNGCodec::Encode doesn't do
+// interlaced, palette-based, or grayscale images, but PNGCodec::Decode is
+// actually asked to decode these types of images by Chrome.
+bool EncodeImage(const std::vector<unsigned char>& input,
+ const int width,
+ const int height,
+ ColorType output_color_type,
+ std::vector<unsigned char>* output,
+ const int interlace_type = PNG_INTERLACE_NONE,
+ std::vector<png_color>* palette = 0,
+ std::vector<unsigned char>* palette_alpha = 0) {
+ DCHECK(output);
+
+ int input_rowbytes = 0;
+ int transforms = PNG_TRANSFORM_IDENTITY;
+
+ switch (output_color_type) {
+ case COLOR_TYPE_GRAY:
+ input_rowbytes = width;
+ break;
+ case COLOR_TYPE_GRAY_ALPHA:
+ input_rowbytes = width * 2;
+ break;
+ case COLOR_TYPE_PALETTE:
+ if (!palette)
+ return false;
+ input_rowbytes = width;
+ break;
+ case COLOR_TYPE_RGB:
+ input_rowbytes = width * 3;
+ break;
+ case COLOR_TYPE_RGBA:
+ input_rowbytes = width * 4;
+ break;
+ case COLOR_TYPE_BGR:
+ input_rowbytes = width * 3;
+ output_color_type = static_cast<ColorType>(PNG_COLOR_TYPE_RGB);
+ transforms |= PNG_TRANSFORM_BGR;
+ break;
+ case COLOR_TYPE_BGRA:
+ input_rowbytes = width * 4;
+ output_color_type = static_cast<ColorType>(PNG_COLOR_TYPE_RGBA);
+ transforms |= PNG_TRANSFORM_BGR;
+ break;
+ };
+
+ png_struct* png_ptr =
+ png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
+ if (!png_ptr)
+ return false;
+ png_infop info_ptr = png_create_info_struct(png_ptr);
+ if (!info_ptr) {
+ png_destroy_write_struct(&png_ptr, NULL);
+ return false;
+ }
+
+ std::vector<png_bytep> row_pointers(height);
+ for (int y = 0 ; y < height; ++y) {
+ row_pointers[y] = const_cast<unsigned char*>(&input[y * input_rowbytes]);
+ }
+
+ if (setjmp(png_jmpbuf(png_ptr))) {
+ png_destroy_write_struct(&png_ptr, &info_ptr);
+ return false;
+ }
+
+ png_set_error_fn(png_ptr, NULL, LogLibPNGError, LogLibPNGWarning);
+ png_set_rows(png_ptr, info_ptr, &row_pointers[0]);
+ png_set_write_fn(png_ptr, output, WriteImageData, FlushImageData);
+ png_set_IHDR(png_ptr, info_ptr, width, height, 8, output_color_type,
+ interlace_type, PNG_COMPRESSION_TYPE_DEFAULT,
+ PNG_FILTER_TYPE_DEFAULT);
+ if (output_color_type == COLOR_TYPE_PALETTE) {
+ png_set_PLTE(png_ptr, info_ptr, &palette->front(), palette->size());
+ if (palette_alpha) {
+ unsigned char* alpha_data = &palette_alpha->front();
+ size_t alpha_size = palette_alpha->size();
+ png_set_tRNS(png_ptr, info_ptr, alpha_data, alpha_size, NULL);
+ }
+ }
+
+ png_write_png(png_ptr, info_ptr, transforms, NULL);
+
+ png_destroy_write_struct(&png_ptr, &info_ptr);
+ return true;
+}
+
+} // 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.
+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;
+ ASSERT_TRUE(PNGCodec::Encode(&original[0], PNGCodec::FORMAT_RGB,
+ Size(w, h), w * 3, false,
+ std::vector<PNGCodec::Comment>(),
+ &encoded));
+
+ // decode, it should have the same size as the original
+ std::vector<unsigned char> decoded;
+ int outw, outh;
+ ASSERT_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;
+ ASSERT_TRUE(PNGCodec::Encode(&original[0], PNGCodec::FORMAT_RGBA,
+ Size(w, h), w * 4, false,
+ std::vector<PNGCodec::Comment>(),
+ &encoded));
+
+ // decode, it should have the same size as the original
+ std::vector<unsigned char> decoded;
+ int outw, outh;
+ ASSERT_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(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;
+ ASSERT_TRUE(PNGCodec::Encode(&original[0], PNGCodec::FORMAT_BGRA,
+ Size(w, h), w * 4, false,
+ std::vector<PNGCodec::Comment>(),
+ &encoded));
+
+ // Decode, it should have the same size as the original.
+ std::vector<unsigned char> decoded;
+ int outw, outh;
+ ASSERT_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, DecodePalette) {
+ const int w = 20, h = 20;
+
+ // create an image with known values
+ std::vector<unsigned char> original;
+ std::vector<png_color> original_palette;
+ std::vector<unsigned char> original_trans_chunk;
+ MakePaletteImage(w, h, &original, &original_palette, &original_trans_chunk);
+
+ // encode
+ std::vector<unsigned char> encoded;
+ ASSERT_TRUE(EncodeImage(original,
+ w, h,
+ COLOR_TYPE_PALETTE,
+ &encoded,
+ PNG_INTERLACE_NONE,
+ &original_palette,
+ &original_trans_chunk));
+
+ // decode
+ std::vector<unsigned char> decoded;
+ int outw, outh;
+ ASSERT_TRUE(PNGCodec::Decode(&encoded[0], encoded.size(),
+ PNGCodec::FORMAT_RGBA, &decoded,
+ &outw, &outh));
+ ASSERT_EQ(w, outw);
+ ASSERT_EQ(h, outh);
+ ASSERT_EQ(decoded.size(), w * h * 4U);
+
+ // Images must be equal
+ for (int y = 0; y < h; ++y) {
+ for (int x = 0; x < w; ++x) {
+ unsigned char palette_pixel = original[y * w + x];
+ png_color& palette_color = original_palette[palette_pixel];
+ int alpha = original_trans_chunk[palette_pixel];
+ unsigned char* rgba_pixel = &decoded[(y * w + x) * 4];
+
+ EXPECT_EQ(palette_color.red, rgba_pixel[0]);
+ EXPECT_EQ(palette_color.green, rgba_pixel[1]);
+ EXPECT_EQ(palette_color.blue, rgba_pixel[2]);
+ EXPECT_EQ(alpha, rgba_pixel[3]);
+ }
+ }
+}
+
+TEST(PNGCodec, DecodePaletteDiscardAlpha) {
+ const int w = 20, h = 20;
+
+ // create an image with known values
+ std::vector<unsigned char> original;
+ std::vector<png_color> original_palette;
+ std::vector<unsigned char> original_trans_chunk;
+ MakePaletteImage(w, h, &original, &original_palette, &original_trans_chunk);
+
+ // encode
+ std::vector<unsigned char> encoded;
+ ASSERT_TRUE(EncodeImage(original,
+ w, h,
+ COLOR_TYPE_PALETTE,
+ &encoded,
+ PNG_INTERLACE_NONE,
+ &original_palette,
+ &original_trans_chunk));
+
+ // decode
+ std::vector<unsigned char> decoded;
+ int outw, outh;
+ ASSERT_TRUE(PNGCodec::Decode(&encoded[0], encoded.size(),
+ PNGCodec::FORMAT_RGB, &decoded,
+ &outw, &outh));
+ ASSERT_EQ(w, outw);
+ ASSERT_EQ(h, outh);
+ ASSERT_EQ(decoded.size(), w * h * 3U);
+
+ // Images must be equal
+ for (int y = 0; y < h; ++y) {
+ for (int x = 0; x < w; ++x) {
+ unsigned char palette_pixel = original[y * w + x];
+ png_color& palette_color = original_palette[palette_pixel];
+ unsigned char* rgba_pixel = &decoded[(y * w + x) * 3];
+
+ EXPECT_EQ(palette_color.red, rgba_pixel[0]);
+ EXPECT_EQ(palette_color.green, rgba_pixel[1]);
+ EXPECT_EQ(palette_color.blue, rgba_pixel[2]);
+ }
+ }
+}
+
+TEST(PNGCodec, DecodeInterlacedPalette) {
+ const int w = 20, h = 20;
+
+ // create an image with known values
+ std::vector<unsigned char> original;
+ std::vector<png_color> original_palette;
+ std::vector<unsigned char> original_trans_chunk;
+ MakePaletteImage(w, h, &original, &original_palette, &original_trans_chunk);
+
+ // encode
+ std::vector<unsigned char> encoded;
+ ASSERT_TRUE(EncodeImage(original,
+ w, h,
+ COLOR_TYPE_PALETTE,
+ &encoded,
+ PNG_INTERLACE_ADAM7,
+ &original_palette,
+ &original_trans_chunk));
+
+ // decode
+ std::vector<unsigned char> decoded;
+ int outw, outh;
+ ASSERT_TRUE(PNGCodec::Decode(&encoded[0], encoded.size(),
+ PNGCodec::FORMAT_RGBA, &decoded,
+ &outw, &outh));
+ ASSERT_EQ(w, outw);
+ ASSERT_EQ(h, outh);
+ ASSERT_EQ(decoded.size(), w * h * 4U);
+
+ // Images must be equal
+ for (int y = 0; y < h; ++y) {
+ for (int x = 0; x < w; ++x) {
+ unsigned char palette_pixel = original[y * w + x];
+ png_color& palette_color = original_palette[palette_pixel];
+ int alpha = original_trans_chunk[palette_pixel];
+ unsigned char* rgba_pixel = &decoded[(y * w + x) * 4];
+
+ EXPECT_EQ(palette_color.red, rgba_pixel[0]);
+ EXPECT_EQ(palette_color.green, rgba_pixel[1]);
+ EXPECT_EQ(palette_color.blue, rgba_pixel[2]);
+ EXPECT_EQ(alpha, rgba_pixel[3]);
+ }
+ }
+}
+
+TEST(PNGCodec, DecodeGrayscale) {
+ const int w = 20, h = 20;
+
+ // create an image with known values
+ std::vector<unsigned char> original;
+ MakeGrayscaleImage(w, h, &original);
+
+ // encode
+ std::vector<unsigned char> encoded;
+ ASSERT_TRUE(EncodeImage(original, w, h, COLOR_TYPE_GRAY, &encoded));
+
+ // decode
+ std::vector<unsigned char> decoded;
+ int outw, outh;
+ ASSERT_TRUE(PNGCodec::Decode(&encoded[0], encoded.size(),
+ PNGCodec::FORMAT_RGB, &decoded,
+ &outw, &outh));
+ ASSERT_EQ(w, outw);
+ ASSERT_EQ(h, outh);
+ ASSERT_EQ(decoded.size(), original.size() * 3);
+
+ // Images must be equal
+ for (int y = 0; y < h; ++y) {
+ for (int x = 0; x < w; ++x) {
+ unsigned char gray_pixel = original[(y * w + x)];
+ unsigned char* rgba_pixel = &decoded[(y * w + x) * 3];
+ EXPECT_EQ(rgba_pixel[0], gray_pixel);
+ EXPECT_EQ(rgba_pixel[1], gray_pixel);
+ EXPECT_EQ(rgba_pixel[2], gray_pixel);
+ }
+ }
+}
+
+TEST(PNGCodec, DecodeGrayscaleWithAlpha) {
+ const int w = 20, h = 20;
+
+ // create an image with known values
+ std::vector<unsigned char> original;
+ MakeGrayscaleAlphaImage(w, h, &original);
+
+ // encode
+ std::vector<unsigned char> encoded;
+ ASSERT_TRUE(EncodeImage(original,
+ w, h,
+ COLOR_TYPE_GRAY_ALPHA,
+ &encoded));
+
+ // decode
+ std::vector<unsigned char> decoded;
+ int outw, outh;
+ ASSERT_TRUE(PNGCodec::Decode(&encoded[0], encoded.size(),
+ PNGCodec::FORMAT_RGBA, &decoded,
+ &outw, &outh));
+ ASSERT_EQ(w, outw);
+ ASSERT_EQ(h, outh);
+ ASSERT_EQ(decoded.size(), original.size() * 2);
+
+ // Images must be equal
+ for (int y = 0; y < h; ++y) {
+ for (int x = 0; x < w; ++x) {
+ unsigned char* gray_pixel = &original[(y * w + x) * 2];
+ unsigned char* rgba_pixel = &decoded[(y * w + x) * 4];
+ EXPECT_EQ(rgba_pixel[0], gray_pixel[0]);
+ EXPECT_EQ(rgba_pixel[1], gray_pixel[0]);
+ EXPECT_EQ(rgba_pixel[2], gray_pixel[0]);
+ EXPECT_EQ(rgba_pixel[3], gray_pixel[1]);
+ }
+ }
+}
+
+TEST(PNGCodec, DecodeGrayscaleWithAlphaDiscardAlpha) {
+ const int w = 20, h = 20;
+
+ // create an image with known values
+ std::vector<unsigned char> original;
+ MakeGrayscaleAlphaImage(w, h, &original);
+
+ // encode
+ std::vector<unsigned char> encoded;
+ ASSERT_TRUE(EncodeImage(original,
+ w, h,
+ COLOR_TYPE_GRAY_ALPHA,
+ &encoded));
+
+ // decode
+ std::vector<unsigned char> decoded;
+ int outw, outh;
+ ASSERT_TRUE(PNGCodec::Decode(&encoded[0], encoded.size(),
+ PNGCodec::FORMAT_RGB, &decoded,
+ &outw, &outh));
+ ASSERT_EQ(w, outw);
+ ASSERT_EQ(h, outh);
+ ASSERT_EQ(decoded.size(), w * h * 3U);
+
+ // Images must be equal
+ for (int y = 0; y < h; ++y) {
+ for (int x = 0; x < w; ++x) {
+ unsigned char* gray_pixel = &original[(y * w + x) * 2];
+ unsigned char* rgba_pixel = &decoded[(y * w + x) * 3];
+ EXPECT_EQ(rgba_pixel[0], gray_pixel[0]);
+ EXPECT_EQ(rgba_pixel[1], gray_pixel[0]);
+ EXPECT_EQ(rgba_pixel[2], gray_pixel[0]);
+ }
+ }
+}
+
+TEST(PNGCodec, DecodeInterlacedGrayscale) {
+ const int w = 20, h = 20;
+
+ // create an image with known values
+ std::vector<unsigned char> original;
+ MakeGrayscaleImage(w, h, &original);
+
+ // encode
+ std::vector<unsigned char> encoded;
+ ASSERT_TRUE(EncodeImage(original,
+ w, h,
+ COLOR_TYPE_GRAY,
+ &encoded,
+ PNG_INTERLACE_ADAM7));
+
+ // decode
+ std::vector<unsigned char> decoded;
+ int outw, outh;
+ ASSERT_TRUE(PNGCodec::Decode(&encoded[0], encoded.size(),
+ PNGCodec::FORMAT_RGBA, &decoded,
+ &outw, &outh));
+ ASSERT_EQ(w, outw);
+ ASSERT_EQ(h, outh);
+ ASSERT_EQ(decoded.size(), original.size() * 4);
+
+ // Images must be equal
+ for (int y = 0; y < h; ++y) {
+ for (int x = 0; x < w; ++x) {
+ unsigned char gray_pixel = original[(y * w + x)];
+ unsigned char* rgba_pixel = &decoded[(y * w + x) * 4];
+ EXPECT_EQ(rgba_pixel[0], gray_pixel);
+ EXPECT_EQ(rgba_pixel[1], gray_pixel);
+ EXPECT_EQ(rgba_pixel[2], gray_pixel);
+ EXPECT_EQ(rgba_pixel[3], 0xFF);
+ }
+ }
+}
+
+TEST(PNGCodec, DecodeInterlacedGrayscaleWithAlpha) {
+ const int w = 20, h = 20;
+
+ // create an image with known values
+ std::vector<unsigned char> original;
+ MakeGrayscaleAlphaImage(w, h, &original);
+
+ // encode
+ std::vector<unsigned char> encoded;
+ ASSERT_TRUE(EncodeImage(original,
+ w, h,
+ COLOR_TYPE_GRAY_ALPHA,
+ &encoded,
+ PNG_INTERLACE_ADAM7));
+
+ // decode
+ std::vector<unsigned char> decoded;
+ int outw, outh;
+ ASSERT_TRUE(PNGCodec::Decode(&encoded[0], encoded.size(),
+ PNGCodec::FORMAT_RGBA, &decoded,
+ &outw, &outh));
+ ASSERT_EQ(w, outw);
+ ASSERT_EQ(h, outh);
+ ASSERT_EQ(decoded.size(), original.size() * 2);
+
+ // Images must be equal
+ for (int y = 0; y < h; ++y) {
+ for (int x = 0; x < w; ++x) {
+ unsigned char* gray_pixel = &original[(y * w + x) * 2];
+ unsigned char* rgba_pixel = &decoded[(y * w + x) * 4];
+ EXPECT_EQ(rgba_pixel[0], gray_pixel[0]);
+ EXPECT_EQ(rgba_pixel[1], gray_pixel[0]);
+ EXPECT_EQ(rgba_pixel[2], gray_pixel[0]);
+ EXPECT_EQ(rgba_pixel[3], gray_pixel[1]);
+ }
+ }
+}
+
+TEST(PNGCodec, DecodeInterlacedRGB) {
+ 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;
+ ASSERT_TRUE(EncodeImage(original,
+ w, h,
+ COLOR_TYPE_RGB,
+ &encoded,
+ PNG_INTERLACE_ADAM7));
+
+ // decode, it should have the same size as the original
+ std::vector<unsigned char> decoded;
+ int outw, outh;
+ ASSERT_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_EQ(original, decoded);
+}
+
+TEST(PNGCodec, DecodeInterlacedRGBA) {
+ const int w = 20, h = 20;
+
+ // create an image with known values
+ std::vector<unsigned char> original;
+ MakeRGBAImage(w, h, false, &original);
+
+ // encode
+ std::vector<unsigned char> encoded;
+ ASSERT_TRUE(EncodeImage(original,
+ w, h,
+ COLOR_TYPE_RGBA,
+ &encoded,
+ PNG_INTERLACE_ADAM7));
+
+ // decode, it should have the same size as the original
+ std::vector<unsigned char> decoded;
+ int outw, outh;
+ ASSERT_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 equal
+ ASSERT_EQ(original, decoded);
+}
+
+TEST(PNGCodec, DecodeInterlacedRGBADiscardAlpha) {
+ const int w = 20, h = 20;
+
+ // create an image with known values
+ std::vector<unsigned char> original;
+ MakeRGBAImage(w, h, false, &original);
+
+ // encode
+ std::vector<unsigned char> encoded;
+ ASSERT_TRUE(EncodeImage(original,
+ w, h,
+ COLOR_TYPE_RGBA,
+ &encoded,
+ PNG_INTERLACE_ADAM7));
+
+ // decode
+ std::vector<unsigned char> decoded;
+ int outw, outh;
+ ASSERT_TRUE(PNGCodec::Decode(&encoded[0], encoded.size(),
+ PNGCodec::FORMAT_RGB, &decoded,
+ &outw, &outh));
+ ASSERT_EQ(w, outw);
+ ASSERT_EQ(h, outh);
+ ASSERT_EQ(decoded.size(), w * h * 3U);
+
+ // Images must be equal
+ for (int x = 0; x < w; x++) {
+ for (int y = 0; y < h; y++) {
+ unsigned char* orig_px = &original[(y * w + x) * 4];
+ unsigned char* dec_px = &decoded[(y * w + x) * 3];
+ EXPECT_EQ(dec_px[0], orig_px[0]);
+ EXPECT_EQ(dec_px[1], orig_px[1]);
+ EXPECT_EQ(dec_px[2], orig_px[2]);
+ }
+ }
+}
+
+TEST(PNGCodec, DecodeInterlacedBGR) {
+ 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;
+ ASSERT_TRUE(EncodeImage(original,
+ w, h,
+ COLOR_TYPE_BGR,
+ &encoded,
+ PNG_INTERLACE_ADAM7));
+
+ // decode, it should have the same size as the original
+ std::vector<unsigned char> decoded;
+ int outw, outh;
+ ASSERT_TRUE(PNGCodec::Decode(&encoded[0], encoded.size(),
+ PNGCodec::FORMAT_BGRA, &decoded,
+ &outw, &outh));
+ ASSERT_EQ(w, outw);
+ ASSERT_EQ(h, outh);
+ ASSERT_EQ(decoded.size(), w * h * 4U);
+
+ // Images must be equal
+ for (int x = 0; x < w; x++) {
+ for (int y = 0; y < h; y++) {
+ unsigned char* orig_px = &original[(y * w + x) * 3];
+ unsigned char* dec_px = &decoded[(y * w + x) * 4];
+ EXPECT_EQ(dec_px[0], orig_px[0]);
+ EXPECT_EQ(dec_px[1], orig_px[1]);
+ EXPECT_EQ(dec_px[2], orig_px[2]);
+ }
+ }
+}
+
+TEST(PNGCodec, DecodeInterlacedBGRA) {
+ const int w = 20, h = 20;
+
+ // create an image with known values
+ std::vector<unsigned char> original;
+ MakeRGBAImage(w, h, false, &original);
+
+ // encode
+ std::vector<unsigned char> encoded;
+ ASSERT_TRUE(EncodeImage(original,
+ w, h,
+ COLOR_TYPE_BGRA,
+ &encoded,
+ PNG_INTERLACE_ADAM7));
+
+ // decode, it should have the same size as the original
+ std::vector<unsigned char> decoded;
+ int outw, outh;
+ ASSERT_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 equal
+ ASSERT_EQ(original, decoded);
+}
+
+// Not encoding an interlaced PNG from SkBitmap because we don't do it
+// anywhere, and the ability to do that requires more code changes.
+TEST(PNGCodec, DecodeInterlacedRGBtoSkBitmap) {
+ 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;
+ ASSERT_TRUE(EncodeImage(original,
+ w, h,
+ COLOR_TYPE_RGB,
+ &encoded,
+ PNG_INTERLACE_ADAM7));
+
+ // Decode the encoded string.
+ SkBitmap decoded_bitmap;
+ ASSERT_TRUE(PNGCodec::Decode(&encoded.front(), encoded.size(),
+ &decoded_bitmap));
+
+ for (int x = 0; x < w; x++) {
+ for (int y = 0; y < h; y++) {
+ const unsigned char* original_pixel = &original[(y * w + x) * 3];
+ const uint32_t original_pixel_sk = SkPackARGB32(0xFF,
+ original_pixel[0],
+ original_pixel[1],
+ original_pixel[2]);
+ const uint32_t decoded_pixel = decoded_bitmap.getAddr32(0, y)[x];
+ EXPECT_EQ(original_pixel_sk, decoded_pixel);
+ }
+ }
+}
+
+TEST(PNGCodec, DecodeInterlacedRGBAtoSkBitmap) {
+ const int w = 20, h = 20;
+
+ // create an image with known values
+ std::vector<unsigned char> original;
+ MakeRGBAImage(w, h, false, &original);
+
+ // encode
+ std::vector<unsigned char> encoded;
+ ASSERT_TRUE(EncodeImage(original,
+ w, h,
+ COLOR_TYPE_RGBA,
+ &encoded,
+ PNG_INTERLACE_ADAM7));
+
+ // Decode the encoded string.
+ SkBitmap decoded_bitmap;
+ ASSERT_TRUE(PNGCodec::Decode(&encoded.front(), encoded.size(),
+ &decoded_bitmap));
+
+ for (int x = 0; x < w; x++) {
+ for (int y = 0; y < h; y++) {
+ const unsigned char* original_pixel = &original[(y * w + x) * 4];
+ const uint32_t original_pixel_sk = SkPackARGB32(original_pixel[3],
+ original_pixel[0],
+ original_pixel[1],
+ original_pixel[2]);
+ const uint32_t decoded_pixel = decoded_bitmap.getAddr32(0, y)[x];
+ EXPECT_EQ(original_pixel_sk, decoded_pixel);
+ }
+ }
+}
+
+// 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;
+ ASSERT_TRUE(PNGCodec::Encode(&original[0], PNGCodec::FORMAT_RGB,
+ Size(w, h), w * 3, false,
+ std::vector<PNGCodec::Comment>(),
+ &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, 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,
+ Size(w, h), w * 4, true,
+ std::vector<PNGCodec::Comment>(),
+ &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_EQ(original_rgba, decoded);
+
+ // Encode RGBA to RGBA.
+ EXPECT_TRUE(PNGCodec::Encode(&original_rgba[0], PNGCodec::FORMAT_RGBA,
+ Size(w, h), w * 4, false,
+ std::vector<PNGCodec::Comment>(),
+ &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_EQ(original_rgb, decoded);
+}
+
+TEST(PNGCodec, EncodeBGRASkBitmapStridePadded) {
+ const int kWidth = 20;
+ const int kHeight = 20;
+ const int kPaddedWidth = 32;
+ const int kBytesPerPixel = 4;
+ const int kPaddedSize = kPaddedWidth * kHeight;
+ const int kRowBytes = kPaddedWidth * kBytesPerPixel;
+
+ SkBitmap original_bitmap;
+ original_bitmap.setConfig(SkBitmap::kARGB_8888_Config,
+ kWidth, kHeight, kRowBytes);
+ original_bitmap.allocPixels();
+
+ // Write data over the source bitmap.
+ // We write on the pad area here too.
+ // The encoder should ignore the pad area.
+ uint32_t* src_data = original_bitmap.getAddr32(0, 0);
+ for (int i = 0; i < kPaddedSize; i++) {
+ src_data[i] = SkPreMultiplyARGB(i % 255, i % 250, i % 245, i % 240);
+ }
+
+ // 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 < kWidth; x++) {
+ for (int y = 0; y < kHeight; 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, 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];
+ uint32_t unpremultiplied_decoded =
+ SkUnPreMultiply::PMColorToColor(decoded_pixel);
+
+ EXPECT_TRUE(NonAlphaColorsClose(unpremultiplied, unpremultiplied_decoded))
+ << "Original_pixel: ("
+ << SkColorGetR(unpremultiplied) << ", "
+ << SkColorGetG(unpremultiplied) << ", "
+ << SkColorGetB(unpremultiplied) << "), "
+ << "Decoded pixel: ("
+ << SkColorGetR(unpremultiplied_decoded) << ", "
+ << SkColorGetG(unpremultiplied_decoded) << ", "
+ << SkColorGetB(unpremultiplied_decoded) << ")";
+ }
+ }
+}
+
+TEST(PNGCodec, EncodeWithComment) {
+ const int w = 10, h = 10;
+
+ std::vector<unsigned char> original;
+ MakeRGBImage(w, h, &original);
+
+ std::vector<unsigned char> encoded;
+ std::vector<PNGCodec::Comment> comments;
+ comments.push_back(PNGCodec::Comment("key", "text"));
+ comments.push_back(PNGCodec::Comment("test", "something"));
+ comments.push_back(PNGCodec::Comment("have some", "spaces in both"));
+ EXPECT_TRUE(PNGCodec::Encode(&original[0], PNGCodec::FORMAT_RGB,
+ Size(w, h), w * 3, false, comments, &encoded));
+
+ // Each chunk is of the form length (4 bytes), chunk type (tEXt), data,
+ // checksum (4 bytes). Make sure we find all of them in the encoded
+ // results.
+ const unsigned char kExpected1[] =
+ "\x00\x00\x00\x08tEXtkey\x00text\x9e\xe7\x66\x51";
+ const unsigned char kExpected2[] =
+ "\x00\x00\x00\x0etEXttest\x00something\x29\xba\xef\xac";
+ const unsigned char kExpected3[] =
+ "\x00\x00\x00\x18tEXthave some\x00spaces in both\x8d\x69\x34\x2d";
+
+ EXPECT_NE(std::search(encoded.begin(), encoded.end(), kExpected1,
+ kExpected1 + arraysize(kExpected1)),
+ encoded.end());
+ EXPECT_NE(std::search(encoded.begin(), encoded.end(), kExpected2,
+ kExpected2 + arraysize(kExpected2)),
+ encoded.end());
+ EXPECT_NE(std::search(encoded.begin(), encoded.end(), kExpected3,
+ kExpected3 + arraysize(kExpected3)),
+ encoded.end());
+}
+
+TEST(PNGCodec, EncodeDecodeWithVaryingCompressionLevels) {
+ 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_fast;
+ EXPECT_TRUE(PNGCodec::EncodeWithCompressionLevel(
+ &original[0], PNGCodec::FORMAT_RGBA, Size(w, h), w * 4, false,
+ std::vector<PNGCodec::Comment>(), Z_BEST_SPEED, &encoded_fast));
+
+ std::vector<unsigned char> encoded_best;
+ EXPECT_TRUE(PNGCodec::EncodeWithCompressionLevel(
+ &original[0], PNGCodec::FORMAT_RGBA, Size(w, h), w * 4, false,
+ std::vector<PNGCodec::Comment>(), Z_BEST_COMPRESSION, &encoded_best));
+
+ // Make sure the different compression settings actually do something; the
+ // sizes should be different.
+ EXPECT_NE(encoded_fast.size(), encoded_best.size());
+
+ // decode, it should have the same size as the original
+ std::vector<unsigned char> decoded;
+ int outw, outh;
+ EXPECT_TRUE(PNGCodec::Decode(&encoded_fast[0], encoded_fast.size(),
+ PNGCodec::FORMAT_RGBA, &decoded,
+ &outw, &outh));
+ ASSERT_EQ(w, outw);
+ ASSERT_EQ(h, outh);
+ ASSERT_EQ(original.size(), decoded.size());
+
+ EXPECT_TRUE(PNGCodec::Decode(&encoded_best[0], encoded_best.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);
+}
+
+
+} // namespace gfx
diff --git a/chromium/ui/gfx/color_analysis.cc b/chromium/ui/gfx/color_analysis.cc
new file mode 100644
index 00000000000..f8987cfb546
--- /dev/null
+++ b/chromium/ui/gfx/color_analysis.cc
@@ -0,0 +1,563 @@
+// Copyright (c) 2012 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 "ui/gfx/color_analysis.h"
+
+#include <algorithm>
+#include <limits>
+#include <vector>
+
+#include "base/logging.h"
+#include "base/memory/scoped_ptr.h"
+#include "third_party/skia/include/core/SkBitmap.h"
+#include "third_party/skia/include/core/SkUnPreMultiply.h"
+#include "ui/gfx/codec/png_codec.h"
+
+namespace {
+
+// RGBA KMean Constants
+const uint32_t kNumberOfClusters = 4;
+const int kNumberOfIterations = 50;
+const uint32_t kMaxBrightness = 665;
+const uint32_t kMinDarkness = 100;
+
+// Background Color Modification Constants
+const SkColor kDefaultBgColor = SK_ColorWHITE;
+
+// Support class to hold information about each cluster of pixel data in
+// the KMean algorithm. While this class does not contain all of the points
+// that exist in the cluster, it keeps track of the aggregate sum so it can
+// compute the new center appropriately.
+class KMeanCluster {
+ public:
+ KMeanCluster() {
+ Reset();
+ }
+
+ void Reset() {
+ centroid[0] = centroid[1] = centroid[2] = 0;
+ aggregate[0] = aggregate[1] = aggregate[2] = 0;
+ counter = 0;
+ weight = 0;
+ }
+
+ inline void SetCentroid(uint8_t r, uint8_t g, uint8_t b) {
+ centroid[0] = r;
+ centroid[1] = g;
+ centroid[2] = b;
+ }
+
+ inline void GetCentroid(uint8_t* r, uint8_t* g, uint8_t* b) {
+ *r = centroid[0];
+ *g = centroid[1];
+ *b = centroid[2];
+ }
+
+ inline bool IsAtCentroid(uint8_t r, uint8_t g, uint8_t b) {
+ return r == centroid[0] && g == centroid[1] && b == centroid[2];
+ }
+
+ // Recomputes the centroid of the cluster based on the aggregate data. The
+ // number of points used to calculate this center is stored for weighting
+ // purposes. The aggregate and counter are then cleared to be ready for the
+ // next iteration.
+ inline void RecomputeCentroid() {
+ if (counter > 0) {
+ centroid[0] = aggregate[0] / counter;
+ centroid[1] = aggregate[1] / counter;
+ centroid[2] = aggregate[2] / counter;
+
+ aggregate[0] = aggregate[1] = aggregate[2] = 0;
+ weight = counter;
+ counter = 0;
+ }
+ }
+
+ inline void AddPoint(uint8_t r, uint8_t g, uint8_t b) {
+ aggregate[0] += r;
+ aggregate[1] += g;
+ aggregate[2] += b;
+ ++counter;
+ }
+
+ // Just returns the distance^2. Since we are comparing relative distances
+ // there is no need to perform the expensive sqrt() operation.
+ inline uint32_t GetDistanceSqr(uint8_t r, uint8_t g, uint8_t b) {
+ return (r - centroid[0]) * (r - centroid[0]) +
+ (g - centroid[1]) * (g - centroid[1]) +
+ (b - centroid[2]) * (b - centroid[2]);
+ }
+
+ // In order to determine if we have hit convergence or not we need to see
+ // if the centroid of the cluster has moved. This determines whether or
+ // not the centroid is the same as the aggregate sum of points that will be
+ // used to generate the next centroid.
+ inline bool CompareCentroidWithAggregate() {
+ if (counter == 0)
+ return false;
+
+ return aggregate[0] / counter == centroid[0] &&
+ aggregate[1] / counter == centroid[1] &&
+ aggregate[2] / counter == centroid[2];
+ }
+
+ // Returns the previous counter, which is used to determine the weight
+ // of the cluster for sorting.
+ inline uint32_t GetWeight() const {
+ return weight;
+ }
+
+ static bool SortKMeanClusterByWeight(const KMeanCluster& a,
+ const KMeanCluster& b) {
+ return a.GetWeight() > b.GetWeight();
+ }
+
+ private:
+ uint8_t centroid[3];
+
+ // Holds the sum of all the points that make up this cluster. Used to
+ // generate the next centroid as well as to check for convergence.
+ uint32_t aggregate[3];
+ uint32_t counter;
+
+ // The weight of the cluster, determined by how many points were used
+ // to generate the previous centroid.
+ uint32_t weight;
+};
+
+// Un-premultiplies each pixel in |bitmap| into an output |buffer|. Requires
+// approximately 10 microseconds for a 16x16 icon on an Intel Core i5.
+void UnPreMultiply(const SkBitmap& bitmap, uint32_t* buffer, int buffer_size) {
+ SkAutoLockPixels auto_lock(bitmap);
+ uint32_t* in = static_cast<uint32_t*>(bitmap.getPixels());
+ uint32_t* out = buffer;
+ int pixel_count = std::min(bitmap.width() * bitmap.height(), buffer_size);
+ for (int i = 0; i < pixel_count; ++i)
+ *out++ = SkUnPreMultiply::PMColorToColor(*in++);
+}
+
+} // namespace
+
+namespace color_utils {
+
+KMeanImageSampler::KMeanImageSampler() {
+}
+
+KMeanImageSampler::~KMeanImageSampler() {
+}
+
+GridSampler::GridSampler() : calls_(0) {
+}
+
+GridSampler::~GridSampler() {
+}
+
+int GridSampler::GetSample(int width, int height) {
+ // Hand-drawn bitmaps often have special outlines or feathering at the edges.
+ // Start our sampling inset from the top and left edges. For example, a 10x10
+ // image with 4 clusters would be sampled like this:
+ // ..........
+ // .0.4.8....
+ // ..........
+ // .1.5.9....
+ // ..........
+ // .2.6......
+ // ..........
+ // .3.7......
+ // ..........
+ const int kPadX = 1;
+ const int kPadY = 1;
+ int x = kPadX +
+ (calls_ / kNumberOfClusters) * ((width - 2 * kPadX) / kNumberOfClusters);
+ int y = kPadY +
+ (calls_ % kNumberOfClusters) * ((height - 2 * kPadY) / kNumberOfClusters);
+ int index = x + (y * width);
+ ++calls_;
+ return index % (width * height);
+}
+
+SkColor FindClosestColor(const uint8_t* image,
+ int width,
+ int height,
+ SkColor color) {
+ uint8_t in_r = SkColorGetR(color);
+ uint8_t in_g = SkColorGetG(color);
+ uint8_t in_b = SkColorGetB(color);
+ // Search using distance-squared to avoid expensive sqrt() operations.
+ int best_distance_squared = kint32max;
+ SkColor best_color = color;
+ const uint8_t* byte = image;
+ for (int i = 0; i < width * height; ++i) {
+ uint8_t b = *(byte++);
+ uint8_t g = *(byte++);
+ uint8_t r = *(byte++);
+ uint8_t a = *(byte++);
+ // Ignore fully transparent pixels.
+ if (a == 0)
+ continue;
+ int distance_squared =
+ (in_b - b) * (in_b - b) +
+ (in_g - g) * (in_g - g) +
+ (in_r - r) * (in_r - r);
+ if (distance_squared < best_distance_squared) {
+ best_distance_squared = distance_squared;
+ best_color = SkColorSetRGB(r, g, b);
+ }
+ }
+ return best_color;
+}
+
+// For a 16x16 icon on an Intel Core i5 this function takes approximately
+// 0.5 ms to run.
+// TODO(port): This code assumes the CPU architecture is little-endian.
+SkColor CalculateKMeanColorOfBuffer(uint8_t* decoded_data,
+ int img_width,
+ int img_height,
+ uint32_t darkness_limit,
+ uint32_t brightness_limit,
+ KMeanImageSampler* sampler) {
+ SkColor color = kDefaultBgColor;
+ if (img_width > 0 && img_height > 0) {
+ std::vector<KMeanCluster> clusters;
+ clusters.resize(kNumberOfClusters, KMeanCluster());
+
+ // Pick a starting point for each cluster
+ std::vector<KMeanCluster>::iterator cluster = clusters.begin();
+ while (cluster != clusters.end()) {
+ // Try up to 10 times to find a unique color. If no unique color can be
+ // found, destroy this cluster.
+ bool color_unique = false;
+ for (int i = 0; i < 10; ++i) {
+ int pixel_pos = sampler->GetSample(img_width, img_height) %
+ (img_width * img_height);
+
+ uint8_t b = decoded_data[pixel_pos * 4];
+ uint8_t g = decoded_data[pixel_pos * 4 + 1];
+ uint8_t r = decoded_data[pixel_pos * 4 + 2];
+ uint8_t a = decoded_data[pixel_pos * 4 + 3];
+ // Skip fully transparent pixels as they usually contain black in their
+ // RGB channels but do not contribute to the visual image.
+ if (a == 0)
+ continue;
+
+ // Loop through the previous clusters and check to see if we have seen
+ // this color before.
+ color_unique = true;
+ for (std::vector<KMeanCluster>::iterator
+ cluster_check = clusters.begin();
+ cluster_check != cluster; ++cluster_check) {
+ if (cluster_check->IsAtCentroid(r, g, b)) {
+ color_unique = false;
+ break;
+ }
+ }
+
+ // If we have a unique color set the center of the cluster to
+ // that color.
+ if (color_unique) {
+ cluster->SetCentroid(r, g, b);
+ break;
+ }
+ }
+
+ // If we don't have a unique color erase this cluster.
+ if (!color_unique) {
+ cluster = clusters.erase(cluster);
+ } else {
+ // Have to increment the iterator here, otherwise the increment in the
+ // for loop will skip a cluster due to the erase if the color wasn't
+ // unique.
+ ++cluster;
+ }
+ }
+
+ // If all pixels in the image are transparent we will have no clusters.
+ if (clusters.empty())
+ return color;
+
+ bool convergence = false;
+ for (int iteration = 0;
+ iteration < kNumberOfIterations && !convergence;
+ ++iteration) {
+
+ // Loop through each pixel so we can place it in the appropriate cluster.
+ uint8_t* pixel = decoded_data;
+ uint8_t* decoded_data_end = decoded_data + (img_width * img_height * 4);
+ while (pixel < decoded_data_end) {
+ uint8_t b = *(pixel++);
+ uint8_t g = *(pixel++);
+ uint8_t r = *(pixel++);
+ uint8_t a = *(pixel++);
+ // Skip transparent pixels, see above.
+ if (a == 0)
+ continue;
+
+ uint32_t distance_sqr_to_closest_cluster = UINT_MAX;
+ std::vector<KMeanCluster>::iterator closest_cluster = clusters.begin();
+
+ // Figure out which cluster this color is closest to in RGB space.
+ for (std::vector<KMeanCluster>::iterator cluster = clusters.begin();
+ cluster != clusters.end(); ++cluster) {
+ uint32_t distance_sqr = cluster->GetDistanceSqr(r, g, b);
+
+ if (distance_sqr < distance_sqr_to_closest_cluster) {
+ distance_sqr_to_closest_cluster = distance_sqr;
+ closest_cluster = cluster;
+ }
+ }
+
+ closest_cluster->AddPoint(r, g, b);
+ }
+
+ // Calculate the new cluster centers and see if we've converged or not.
+ convergence = true;
+ for (std::vector<KMeanCluster>::iterator cluster = clusters.begin();
+ cluster != clusters.end(); ++cluster) {
+ convergence &= cluster->CompareCentroidWithAggregate();
+
+ cluster->RecomputeCentroid();
+ }
+ }
+
+ // Sort the clusters by population so we can tell what the most popular
+ // color is.
+ std::sort(clusters.begin(), clusters.end(),
+ KMeanCluster::SortKMeanClusterByWeight);
+
+ // Loop through the clusters to figure out which cluster has an appropriate
+ // color. Skip any that are too bright/dark and go in order of weight.
+ for (std::vector<KMeanCluster>::iterator cluster = clusters.begin();
+ cluster != clusters.end(); ++cluster) {
+ uint8_t r, g, b;
+ cluster->GetCentroid(&r, &g, &b);
+ // Sum the RGB components to determine if the color is too bright or too
+ // dark.
+ // TODO (dtrainor): Look into using HSV here instead. This approximation
+ // might be fine though.
+ uint32_t summed_color = r + g + b;
+
+ if (summed_color < brightness_limit && summed_color > darkness_limit) {
+ // If we found a valid color just set it and break. We don't want to
+ // check the other ones.
+ color = SkColorSetARGB(0xFF, r, g, b);
+ break;
+ } else if (cluster == clusters.begin()) {
+ // We haven't found a valid color, but we are at the first color so
+ // set the color anyway to make sure we at least have a value here.
+ color = SkColorSetARGB(0xFF, r, g, b);
+ }
+ }
+ }
+
+ // Find a color that actually appears in the image (the K-mean cluster center
+ // will not usually be a color that appears in the image).
+ return FindClosestColor(decoded_data, img_width, img_height, color);
+}
+
+SkColor CalculateKMeanColorOfPNG(scoped_refptr<base::RefCountedMemory> png,
+ uint32_t darkness_limit,
+ uint32_t brightness_limit,
+ KMeanImageSampler* sampler) {
+ int img_width = 0;
+ int img_height = 0;
+ std::vector<uint8_t> decoded_data;
+ SkColor color = kDefaultBgColor;
+
+ if (png.get() &&
+ png->size() &&
+ gfx::PNGCodec::Decode(png->front(),
+ png->size(),
+ gfx::PNGCodec::FORMAT_BGRA,
+ &decoded_data,
+ &img_width,
+ &img_height)) {
+ return CalculateKMeanColorOfBuffer(&decoded_data[0],
+ img_width,
+ img_height,
+ darkness_limit,
+ brightness_limit,
+ sampler);
+ }
+ return color;
+}
+
+SkColor CalculateKMeanColorOfBitmap(const SkBitmap& bitmap) {
+ // SkBitmap uses pre-multiplied alpha but the KMean clustering function
+ // above uses non-pre-multiplied alpha. Transform the bitmap before we
+ // analyze it because the function reads each pixel multiple times.
+ int pixel_count = bitmap.width() * bitmap.height();
+ scoped_ptr<uint32_t[]> image(new uint32_t[pixel_count]);
+ UnPreMultiply(bitmap, image.get(), pixel_count);
+
+ GridSampler sampler;
+ SkColor color = CalculateKMeanColorOfBuffer(
+ reinterpret_cast<uint8_t*>(image.get()),
+ bitmap.width(),
+ bitmap.height(),
+ kMinDarkness,
+ kMaxBrightness,
+ &sampler);
+ return color;
+}
+
+gfx::Matrix3F ComputeColorCovariance(const SkBitmap& bitmap) {
+ // First need basic stats to normalize each channel separately.
+ SkAutoLockPixels bitmap_lock(bitmap);
+ gfx::Matrix3F covariance = gfx::Matrix3F::Zeros();
+ if (!bitmap.getPixels())
+ return covariance;
+
+ // Assume ARGB_8888 format.
+ DCHECK(bitmap.config() == SkBitmap::kARGB_8888_Config);
+
+ int64_t r_sum = 0;
+ int64_t g_sum = 0;
+ int64_t b_sum = 0;
+ int64_t rr_sum = 0;
+ int64_t gg_sum = 0;
+ int64_t bb_sum = 0;
+ int64_t rg_sum = 0;
+ int64_t rb_sum = 0;
+ int64_t gb_sum = 0;
+
+ for (int y = 0; y < bitmap.height(); ++y) {
+ SkPMColor* current_color = static_cast<uint32_t*>(bitmap.getAddr32(0, y));
+ for (int x = 0; x < bitmap.width(); ++x, ++current_color) {
+ SkColor c = SkUnPreMultiply::PMColorToColor(*current_color);
+ SkColor r = SkColorGetR(c);
+ SkColor g = SkColorGetG(c);
+ SkColor b = SkColorGetB(c);
+
+ r_sum += r;
+ g_sum += g;
+ b_sum += b;
+ rr_sum += r * r;
+ gg_sum += g * g;
+ bb_sum += b * b;
+ rg_sum += r * g;
+ rb_sum += r * b;
+ gb_sum += g * b;
+ }
+ }
+
+ // Covariance (not normalized) is E(X*X.t) - m * m.t and this is how it
+ // is calculated below.
+ // Each row below represents a row of the matrix describing (co)variances
+ // of R, G and B channels with (R, G, B)
+ int pixel_n = bitmap.width() * bitmap.height();
+ covariance.set(
+ (static_cast<double>(rr_sum) / pixel_n -
+ static_cast<double>(r_sum * r_sum) / pixel_n / pixel_n),
+ (static_cast<double>(rg_sum) / pixel_n -
+ static_cast<double>(r_sum * g_sum) / pixel_n / pixel_n),
+ (static_cast<double>(rb_sum) / pixel_n -
+ static_cast<double>(r_sum * b_sum) / pixel_n / pixel_n),
+ (static_cast<double>(rg_sum) / pixel_n -
+ static_cast<double>(r_sum * g_sum) / pixel_n / pixel_n),
+ (static_cast<double>(gg_sum) / pixel_n -
+ static_cast<double>(g_sum * g_sum) / pixel_n / pixel_n),
+ (static_cast<double>(gb_sum) / pixel_n -
+ static_cast<double>(g_sum * b_sum) / pixel_n / pixel_n),
+ (static_cast<double>(rb_sum) / pixel_n -
+ static_cast<double>(r_sum * b_sum) / pixel_n / pixel_n),
+ (static_cast<double>(gb_sum) / pixel_n -
+ static_cast<double>(g_sum * b_sum) / pixel_n / pixel_n),
+ (static_cast<double>(bb_sum) / pixel_n -
+ static_cast<double>(b_sum * b_sum) / pixel_n / pixel_n));
+ return covariance;
+}
+
+bool ApplyColorReduction(const SkBitmap& source_bitmap,
+ const gfx::Vector3dF& color_transform,
+ bool fit_to_range,
+ SkBitmap* target_bitmap) {
+ DCHECK(target_bitmap);
+ SkAutoLockPixels source_lock(source_bitmap);
+ SkAutoLockPixels target_lock(*target_bitmap);
+
+ DCHECK(source_bitmap.getPixels());
+ DCHECK(target_bitmap->getPixels());
+ DCHECK_EQ(SkBitmap::kARGB_8888_Config, source_bitmap.config());
+ DCHECK_EQ(SkBitmap::kA8_Config, target_bitmap->config());
+ DCHECK_EQ(source_bitmap.height(), target_bitmap->height());
+ DCHECK_EQ(source_bitmap.width(), target_bitmap->width());
+ DCHECK(!source_bitmap.empty());
+
+ // Elements of color_transform are explicitly off-loaded to local values for
+ // efficiency reasons. Note that in practice images may correspond to entire
+ // tab captures.
+ float t0 = 0.0;
+ float tr = color_transform.x();
+ float tg = color_transform.y();
+ float tb = color_transform.z();
+
+ if (fit_to_range) {
+ // We will figure out min/max in a preprocessing step and adjust
+ // actual_transform as required.
+ float max_val = std::numeric_limits<float>::min();
+ float min_val = std::numeric_limits<float>::max();
+ for (int y = 0; y < source_bitmap.height(); ++y) {
+ const SkPMColor* source_color_row = static_cast<SkPMColor*>(
+ source_bitmap.getAddr32(0, y));
+ for (int x = 0; x < source_bitmap.width(); ++x) {
+ SkColor c = SkUnPreMultiply::PMColorToColor(source_color_row[x]);
+ float r = SkColorGetR(c);
+ float g = SkColorGetG(c);
+ float b = SkColorGetB(c);
+ float gray_level = tr * r + tg * g + tb * b;
+ max_val = std::max(max_val, gray_level);
+ min_val = std::min(min_val, gray_level);
+ }
+ }
+
+ // Adjust the transform so that the result is scaling.
+ float scale = 0.0;
+ t0 = -min_val;
+ if (max_val > min_val)
+ scale = 255.0 / (max_val - min_val);
+ t0 *= scale;
+ tr *= scale;
+ tg *= scale;
+ tb *= scale;
+ }
+
+ for (int y = 0; y < source_bitmap.height(); ++y) {
+ const SkPMColor* source_color_row = static_cast<SkPMColor*>(
+ source_bitmap.getAddr32(0, y));
+ uint8_t* target_color_row = target_bitmap->getAddr8(0, y);
+ for (int x = 0; x < source_bitmap.width(); ++x) {
+ SkColor c = SkUnPreMultiply::PMColorToColor(source_color_row[x]);
+ float r = SkColorGetR(c);
+ float g = SkColorGetG(c);
+ float b = SkColorGetB(c);
+
+ float gl = t0 + tr * r + tg * g + tb * b;
+ if (gl < 0)
+ gl = 0;
+ if (gl > 0xFF)
+ gl = 0xFF;
+ target_color_row[x] = static_cast<uint8_t>(gl);
+ }
+ }
+
+ return true;
+}
+
+bool ComputePrincipalComponentImage(const SkBitmap& source_bitmap,
+ SkBitmap* target_bitmap) {
+ if (!target_bitmap) {
+ NOTREACHED();
+ return false;
+ }
+
+ gfx::Matrix3F covariance = ComputeColorCovariance(source_bitmap);
+ gfx::Matrix3F eigenvectors = gfx::Matrix3F::Zeros();
+ gfx::Vector3dF eigenvals = covariance.SolveEigenproblem(&eigenvectors);
+ gfx::Vector3dF principal = eigenvectors.get_column(0);
+ if (eigenvals == gfx::Vector3dF() || principal == gfx::Vector3dF())
+ return false; // This may happen for some edge cases.
+ return ApplyColorReduction(source_bitmap, principal, true, target_bitmap);
+}
+
+} // color_utils
diff --git a/chromium/ui/gfx/color_analysis.h b/chromium/ui/gfx/color_analysis.h
new file mode 100644
index 00000000000..1d0b1a51409
--- /dev/null
+++ b/chromium/ui/gfx/color_analysis.h
@@ -0,0 +1,127 @@
+// Copyright (c) 2012 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_ANALYSIS_H_
+#define UI_GFX_COLOR_ANALYSIS_H_
+
+#include "base/basictypes.h"
+#include "base/compiler_specific.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/ref_counted_memory.h"
+#include "third_party/skia/include/core/SkColor.h"
+#include "ui/base/ui_export.h"
+#include "ui/gfx/matrix3_f.h"
+
+class SkBitmap;
+
+namespace color_utils {
+
+// This class exposes the sampling method to the caller, which allows
+// stubbing out for things like unit tests. Might be useful to pass more
+// arguments into the GetSample method in the future (such as which
+// cluster is being worked on, etc.).
+//
+// Note: Samplers should be deterministic, as the same image may be analyzed
+// twice with two sampler instances and the results displayed side-by-side
+// to the user.
+class UI_EXPORT KMeanImageSampler {
+ public:
+ virtual int GetSample(int width, int height) = 0;
+
+ protected:
+ KMeanImageSampler();
+ virtual ~KMeanImageSampler();
+};
+
+// This sampler will pick pixels from an evenly spaced grid.
+class UI_EXPORT GridSampler : public KMeanImageSampler {
+ public:
+ GridSampler();
+ virtual ~GridSampler();
+
+ virtual int GetSample(int width, int height) OVERRIDE;
+
+ private:
+ // The number of times GetSample has been called.
+ int calls_;
+};
+
+// Returns the color in an ARGB |image| that is closest in RGB-space to the
+// provided |color|. Exported for testing.
+UI_EXPORT SkColor FindClosestColor(const uint8_t* image, int width, int height,
+ SkColor color);
+
+// Returns an SkColor that represents the calculated dominant color in the png.
+// This uses a KMean clustering algorithm to find clusters of pixel colors in
+// RGB space.
+// |png| represents the data of a png encoded image.
+// |darkness_limit| represents the minimum sum of the RGB components that is
+// acceptable as a color choice. This can be from 0 to 765.
+// |brightness_limit| represents the maximum sum of the RGB components that is
+// acceptable as a color choice. This can be from 0 to 765.
+//
+// RGB KMean Algorithm (N clusters, M iterations):
+// 1.Pick N starting colors by randomly sampling the pixels. If you see a
+// color you already saw keep sampling. After a certain number of tries
+// just remove the cluster and continue with N = N-1 clusters (for an image
+// with just one color this should devolve to N=1). These colors are the
+// centers of your N clusters.
+// 2.For each pixel in the image find the cluster that it is closest to in RGB
+// space. Add that pixel's color to that cluster (we keep a sum and a count
+// of all of the pixels added to the space, so just add it to the sum and
+// increment count).
+// 3.Calculate the new cluster centroids by getting the average color of all of
+// the pixels in each cluster (dividing the sum by the count).
+// 4.See if the new centroids are the same as the old centroids.
+// a) If this is the case for all N clusters than we have converged and
+// can move on.
+// b) If any centroid moved, repeat step 2 with the new centroids for up
+// to M iterations.
+// 5.Once the clusters have converged or M iterations have been tried, sort
+// the clusters by weight (where weight is the number of pixels that make up
+// this cluster).
+// 6.Going through the sorted list of clusters, pick the first cluster with the
+// largest weight that's centroid fulfills the equation
+// |darkness_limit| < SUM(R, G, B) < |brightness_limit|. Return that color.
+// If no color fulfills that requirement return the color with the largest
+// weight regardless of whether or not it fulfills the equation above.
+//
+// Note: Switching to HSV space did not improve the results of this algorithm
+// for typical favicon images.
+UI_EXPORT SkColor CalculateKMeanColorOfPNG(
+ scoped_refptr<base::RefCountedMemory> png,
+ uint32_t darkness_limit,
+ uint32_t brightness_limit,
+ KMeanImageSampler* sampler);
+
+// Computes a dominant color for an SkBitmap using the above algorithm and
+// reasonable defaults for |darkness_limit|, |brightness_limit| and |sampler|.
+UI_EXPORT SkColor CalculateKMeanColorOfBitmap(const SkBitmap& bitmap);
+
+// Compute color covariance matrix for the input bitmap.
+UI_EXPORT gfx::Matrix3F ComputeColorCovariance(const SkBitmap& bitmap);
+
+// Apply a color reduction transform defined by |color_transform| vector to
+// |source_bitmap|. The result is put into |target_bitmap|, which is expected
+// to be initialized to the required size and type (SkBitmap::kA8_Config).
+// If |fit_to_range|, result is transfored linearly to fit 0-0xFF range.
+// Otherwise, data is clipped.
+// Returns true if the target has been computed.
+UI_EXPORT bool ApplyColorReduction(const SkBitmap& source_bitmap,
+ const gfx::Vector3dF& color_transform,
+ bool fit_to_range,
+ SkBitmap* target_bitmap);
+
+// Compute a monochrome image representing the principal color component of
+// the |source_bitmap|. The result is stored in |target_bitmap|, which must be
+// initialized to the required size and type (SkBitmap::kA8_Config).
+// Returns true if the conversion succeeded. Note that there might be legitimate
+// reasons for the process to fail even if all input was correct. This is a
+// condition the caller must be able to handle.
+UI_EXPORT bool ComputePrincipalComponentImage(const SkBitmap& source_bitmap,
+ SkBitmap* target_bitmap);
+
+} // namespace color_utils
+
+#endif // UI_GFX_COLOR_ANALYSIS_H_
diff --git a/chromium/ui/gfx/color_analysis_unittest.cc b/chromium/ui/gfx/color_analysis_unittest.cc
new file mode 100644
index 00000000000..c76681c2ac9
--- /dev/null
+++ b/chromium/ui/gfx/color_analysis_unittest.cc
@@ -0,0 +1,479 @@
+// Copyright (c) 2012 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 "ui/gfx/color_analysis.h"
+
+#include <vector>
+
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/skia/include/core/SkBitmap.h"
+#include "third_party/skia/include/core/SkColor.h"
+#include "ui/gfx/canvas.h"
+#include "ui/gfx/rect.h"
+
+using color_utils::FindClosestColor;
+
+namespace {
+
+const unsigned char k1x1White[] = {
+ 0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a,
+ 0x00, 0x00, 0x00, 0x0d, 0x49, 0x48, 0x44, 0x52,
+ 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01,
+ 0x08, 0x02, 0x00, 0x00, 0x00, 0x90, 0x77, 0x53,
+ 0xde, 0x00, 0x00, 0x00, 0x01, 0x73, 0x52, 0x47,
+ 0x42, 0x00, 0xae, 0xce, 0x1c, 0xe9, 0x00, 0x00,
+ 0x00, 0x09, 0x70, 0x48, 0x59, 0x73, 0x00, 0x00,
+ 0x0b, 0x13, 0x00, 0x00, 0x0b, 0x13, 0x01, 0x00,
+ 0x9a, 0x9c, 0x18, 0x00, 0x00, 0x00, 0x07, 0x74,
+ 0x49, 0x4d, 0x45, 0x07, 0xdb, 0x02, 0x11, 0x15,
+ 0x16, 0x1b, 0xaa, 0x58, 0x38, 0x76, 0x00, 0x00,
+ 0x00, 0x19, 0x74, 0x45, 0x58, 0x74, 0x43, 0x6f,
+ 0x6d, 0x6d, 0x65, 0x6e, 0x74, 0x00, 0x43, 0x72,
+ 0x65, 0x61, 0x74, 0x65, 0x64, 0x20, 0x77, 0x69,
+ 0x74, 0x68, 0x20, 0x47, 0x49, 0x4d, 0x50, 0x57,
+ 0x81, 0x0e, 0x17, 0x00, 0x00, 0x00, 0x0c, 0x49,
+ 0x44, 0x41, 0x54, 0x08, 0xd7, 0x63, 0xf8, 0xff,
+ 0xff, 0x3f, 0x00, 0x05, 0xfe, 0x02, 0xfe, 0xdc,
+ 0xcc, 0x59, 0xe7, 0x00, 0x00, 0x00, 0x00, 0x49,
+ 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82
+};
+
+const unsigned char k1x3BlueWhite[] = {
+ 0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a,
+ 0x00, 0x00, 0x00, 0x0d, 0x49, 0x48, 0x44, 0x52,
+ 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x03,
+ 0x08, 0x02, 0x00, 0x00, 0x00, 0xdd, 0xbf, 0xf2,
+ 0xd5, 0x00, 0x00, 0x00, 0x01, 0x73, 0x52, 0x47,
+ 0x42, 0x00, 0xae, 0xce, 0x1c, 0xe9, 0x00, 0x00,
+ 0x00, 0x09, 0x70, 0x48, 0x59, 0x73, 0x00, 0x00,
+ 0x0b, 0x13, 0x00, 0x00, 0x0b, 0x13, 0x01, 0x00,
+ 0x9a, 0x9c, 0x18, 0x00, 0x00, 0x00, 0x07, 0x74,
+ 0x49, 0x4d, 0x45, 0x07, 0xdb, 0x02, 0x12, 0x01,
+ 0x0a, 0x2c, 0xfd, 0x08, 0x64, 0x66, 0x00, 0x00,
+ 0x00, 0x19, 0x74, 0x45, 0x58, 0x74, 0x43, 0x6f,
+ 0x6d, 0x6d, 0x65, 0x6e, 0x74, 0x00, 0x43, 0x72,
+ 0x65, 0x61, 0x74, 0x65, 0x64, 0x20, 0x77, 0x69,
+ 0x74, 0x68, 0x20, 0x47, 0x49, 0x4d, 0x50, 0x57,
+ 0x81, 0x0e, 0x17, 0x00, 0x00, 0x00, 0x14, 0x49,
+ 0x44, 0x41, 0x54, 0x08, 0xd7, 0x63, 0xf8, 0xff,
+ 0xff, 0x3f, 0x13, 0x03, 0x03, 0x03, 0x03, 0x03,
+ 0xc3, 0x7f, 0x00, 0x1e, 0xfd, 0x03, 0xff, 0xde,
+ 0x72, 0x58, 0x0e, 0x00, 0x00, 0x00, 0x00, 0x49,
+ 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82
+};
+
+const unsigned char k1x3BlueRed[] = {
+ 0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a,
+ 0x00, 0x00, 0x00, 0x0d, 0x49, 0x48, 0x44, 0x52,
+ 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x03,
+ 0x08, 0x02, 0x00, 0x00, 0x00, 0xdd, 0xbf, 0xf2,
+ 0xd5, 0x00, 0x00, 0x00, 0x01, 0x73, 0x52, 0x47,
+ 0x42, 0x00, 0xae, 0xce, 0x1c, 0xe9, 0x00, 0x00,
+ 0x00, 0x09, 0x70, 0x48, 0x59, 0x73, 0x00, 0x00,
+ 0x0b, 0x13, 0x00, 0x00, 0x0b, 0x13, 0x01, 0x00,
+ 0x9a, 0x9c, 0x18, 0x00, 0x00, 0x00, 0x07, 0x74,
+ 0x49, 0x4d, 0x45, 0x07, 0xdb, 0x02, 0x12, 0x01,
+ 0x07, 0x09, 0x03, 0xa2, 0xce, 0x6c, 0x00, 0x00,
+ 0x00, 0x19, 0x74, 0x45, 0x58, 0x74, 0x43, 0x6f,
+ 0x6d, 0x6d, 0x65, 0x6e, 0x74, 0x00, 0x43, 0x72,
+ 0x65, 0x61, 0x74, 0x65, 0x64, 0x20, 0x77, 0x69,
+ 0x74, 0x68, 0x20, 0x47, 0x49, 0x4d, 0x50, 0x57,
+ 0x81, 0x0e, 0x17, 0x00, 0x00, 0x00, 0x14, 0x49,
+ 0x44, 0x41, 0x54, 0x08, 0xd7, 0x63, 0xf8, 0xcf,
+ 0xc0, 0xc0, 0xc4, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0,
+ 0xf0, 0x1f, 0x00, 0x0c, 0x10, 0x02, 0x01, 0x2c,
+ 0x8f, 0x8b, 0x8c, 0x00, 0x00, 0x00, 0x00, 0x49,
+ 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82
+};
+
+class MockKMeanImageSampler : public color_utils::KMeanImageSampler {
+ public:
+ MockKMeanImageSampler() : current_result_index_(0) {
+ }
+
+ explicit MockKMeanImageSampler(const std::vector<int>& samples)
+ : prebaked_sample_results_(samples),
+ current_result_index_(0) {
+ }
+
+ virtual ~MockKMeanImageSampler() {
+ }
+
+ void AddSample(int sample) {
+ prebaked_sample_results_.push_back(sample);
+ }
+
+ void Reset() {
+ prebaked_sample_results_.clear();
+ ResetCounter();
+ }
+
+ void ResetCounter() {
+ current_result_index_ = 0;
+ }
+
+ virtual int GetSample(int width, int height) OVERRIDE {
+ if (current_result_index_ >= prebaked_sample_results_.size()) {
+ current_result_index_ = 0;
+ }
+
+ if (prebaked_sample_results_.empty()) {
+ return 0;
+ }
+
+ return prebaked_sample_results_[current_result_index_++];
+ }
+
+ protected:
+ std::vector<int> prebaked_sample_results_;
+ size_t current_result_index_;
+};
+
+// Return true if a color channel is approximately equal to an expected value.
+bool ChannelApproximatelyEqual(int expected, uint8_t channel) {
+ return (abs(expected - static_cast<int>(channel)) <= 1);
+}
+
+// Compute minimal and maximal graylevel (or alphalevel) of the input |bitmap|.
+// |bitmap| has to be allocated and configured to kA8_Config.
+void Calculate8bitBitmapMinMax(const SkBitmap& bitmap,
+ uint8_t* min_gl,
+ uint8_t* max_gl) {
+ SkAutoLockPixels bitmap_lock(bitmap);
+ DCHECK(bitmap.getPixels());
+ DCHECK(bitmap.config() == SkBitmap::kA8_Config);
+ DCHECK(min_gl);
+ DCHECK(max_gl);
+ *min_gl = std::numeric_limits<uint8_t>::max();
+ *max_gl = std::numeric_limits<uint8_t>::min();
+ for (int y = 0; y < bitmap.height(); ++y) {
+ uint8_t* current_color = bitmap.getAddr8(0, y);
+ for (int x = 0; x < bitmap.width(); ++x, ++current_color) {
+ *min_gl = std::min(*min_gl, *current_color);
+ *max_gl = std::max(*max_gl, *current_color);
+ }
+ }
+}
+
+} // namespace
+
+class ColorAnalysisTest : public testing::Test {
+};
+
+TEST_F(ColorAnalysisTest, CalculatePNGKMeanAllWhite) {
+ MockKMeanImageSampler test_sampler;
+ test_sampler.AddSample(0);
+
+ scoped_refptr<base::RefCountedBytes> png(
+ new base::RefCountedBytes(
+ std::vector<unsigned char>(
+ k1x1White,
+ k1x1White + sizeof(k1x1White) / sizeof(unsigned char))));
+
+ SkColor color =
+ color_utils::CalculateKMeanColorOfPNG(png, 100, 600, &test_sampler);
+
+ EXPECT_EQ(color, SK_ColorWHITE);
+}
+
+TEST_F(ColorAnalysisTest, CalculatePNGKMeanIgnoreWhite) {
+ MockKMeanImageSampler test_sampler;
+ test_sampler.AddSample(0);
+ test_sampler.AddSample(1);
+ test_sampler.AddSample(2);
+
+ scoped_refptr<base::RefCountedBytes> png(
+ new base::RefCountedBytes(
+ std::vector<unsigned char>(
+ k1x3BlueWhite,
+ k1x3BlueWhite + sizeof(k1x3BlueWhite) / sizeof(unsigned char))));
+
+ SkColor color =
+ color_utils::CalculateKMeanColorOfPNG(png, 100, 600, &test_sampler);
+
+ EXPECT_EQ(color, SkColorSetARGB(0xFF, 0x00, 0x00, 0xFF));
+}
+
+TEST_F(ColorAnalysisTest, CalculatePNGKMeanPickMostCommon) {
+ MockKMeanImageSampler test_sampler;
+ test_sampler.AddSample(0);
+ test_sampler.AddSample(1);
+ test_sampler.AddSample(2);
+
+ scoped_refptr<base::RefCountedBytes> png(
+ new base::RefCountedBytes(
+ std::vector<unsigned char>(
+ k1x3BlueRed,
+ k1x3BlueRed + sizeof(k1x3BlueRed) / sizeof(unsigned char))));
+
+ SkColor color =
+ color_utils::CalculateKMeanColorOfPNG(png, 100, 600, &test_sampler);
+
+ EXPECT_EQ(color, SkColorSetARGB(0xFF, 0xFF, 0x00, 0x00));
+}
+
+TEST_F(ColorAnalysisTest, GridSampler) {
+ color_utils::GridSampler sampler;
+ const int kWidth = 16;
+ const int kHeight = 16;
+ // Sample starts at 1,1.
+ EXPECT_EQ(1 + 1 * kWidth, sampler.GetSample(kWidth, kHeight));
+ EXPECT_EQ(1 + 4 * kWidth, sampler.GetSample(kWidth, kHeight));
+ EXPECT_EQ(1 + 7 * kWidth, sampler.GetSample(kWidth, kHeight));
+ EXPECT_EQ(1 + 10 * kWidth, sampler.GetSample(kWidth, kHeight));
+ // Step over by 3.
+ EXPECT_EQ(4 + 1 * kWidth, sampler.GetSample(kWidth, kHeight));
+ EXPECT_EQ(4 + 4 * kWidth, sampler.GetSample(kWidth, kHeight));
+ EXPECT_EQ(4 + 7 * kWidth, sampler.GetSample(kWidth, kHeight));
+ EXPECT_EQ(4 + 10 * kWidth, sampler.GetSample(kWidth, kHeight));
+}
+
+TEST_F(ColorAnalysisTest, FindClosestColor) {
+ // Empty image returns input color.
+ SkColor color = FindClosestColor(NULL, 0, 0, SK_ColorRED);
+ EXPECT_EQ(SK_ColorRED, color);
+
+ // Single color image returns that color.
+ SkBitmap bitmap;
+ bitmap.setConfig(SkBitmap::kARGB_8888_Config, 16, 16);
+ bitmap.allocPixels();
+ bitmap.eraseColor(SK_ColorWHITE);
+ color = FindClosestColor(static_cast<uint8_t*>(bitmap.getPixels()),
+ bitmap.width(),
+ bitmap.height(),
+ SK_ColorRED);
+ EXPECT_EQ(SK_ColorWHITE, color);
+
+ // Write a black pixel into the image. A dark grey input pixel should match
+ // the black one in the image.
+ uint32_t* pixel = bitmap.getAddr32(0, 0);
+ *pixel = SK_ColorBLACK;
+ color = FindClosestColor(static_cast<uint8_t*>(bitmap.getPixels()),
+ bitmap.width(),
+ bitmap.height(),
+ SK_ColorDKGRAY);
+ EXPECT_EQ(SK_ColorBLACK, color);
+}
+
+TEST_F(ColorAnalysisTest, CalculateKMeanColorOfBitmap) {
+ // Create a 16x16 bitmap to represent a favicon.
+ SkBitmap bitmap;
+ bitmap.setConfig(SkBitmap::kARGB_8888_Config, 16, 16);
+ bitmap.allocPixels();
+ bitmap.eraseARGB(255, 100, 150, 200);
+
+ SkColor color = color_utils::CalculateKMeanColorOfBitmap(bitmap);
+ EXPECT_EQ(255u, SkColorGetA(color));
+ // Color values are not exactly equal due to reversal of premultiplied alpha.
+ EXPECT_TRUE(ChannelApproximatelyEqual(100, SkColorGetR(color)));
+ EXPECT_TRUE(ChannelApproximatelyEqual(150, SkColorGetG(color)));
+ EXPECT_TRUE(ChannelApproximatelyEqual(200, SkColorGetB(color)));
+
+ // Test a bitmap with an alpha channel.
+ bitmap.eraseARGB(128, 100, 150, 200);
+ color = color_utils::CalculateKMeanColorOfBitmap(bitmap);
+
+ // Alpha channel should be ignored for dominant color calculation.
+ EXPECT_EQ(255u, SkColorGetA(color));
+ EXPECT_TRUE(ChannelApproximatelyEqual(100, SkColorGetR(color)));
+ EXPECT_TRUE(ChannelApproximatelyEqual(150, SkColorGetG(color)));
+ EXPECT_TRUE(ChannelApproximatelyEqual(200, SkColorGetB(color)));
+}
+
+TEST_F(ColorAnalysisTest, ComputeColorCovarianceTrivial) {
+ SkBitmap bitmap;
+ bitmap.setConfig(SkBitmap::kARGB_8888_Config, 100, 200);
+
+ EXPECT_EQ(gfx::Matrix3F::Zeros(),
+ color_utils::ComputeColorCovariance(bitmap));
+ bitmap.allocPixels();
+ bitmap.eraseRGB(50, 150, 200);
+ gfx::Matrix3F covariance = color_utils::ComputeColorCovariance(bitmap);
+ // The answer should be all zeros.
+ EXPECT_TRUE(covariance == gfx::Matrix3F::Zeros());
+}
+
+TEST_F(ColorAnalysisTest, ComputeColorCovarianceWithCanvas) {
+ gfx::Canvas canvas(gfx::Size(250, 200), ui::SCALE_FACTOR_100P, true);
+ // The image consists of vertical stripes, with color bands set to 100
+ // in overlapping stripes 150 pixels wide.
+ canvas.FillRect(gfx::Rect(0, 0, 50, 200), SkColorSetRGB(100, 0, 0));
+ canvas.FillRect(gfx::Rect(50, 0, 50, 200), SkColorSetRGB(100, 100, 0));
+ canvas.FillRect(gfx::Rect(100, 0, 50, 200), SkColorSetRGB(100, 100, 100));
+ canvas.FillRect(gfx::Rect(150, 0, 50, 200), SkColorSetRGB(0, 100, 100));
+ canvas.FillRect(gfx::Rect(200, 0, 50, 200), SkColorSetRGB(0, 0, 100));
+
+ SkBitmap bitmap =
+ skia::GetTopDevice(*canvas.sk_canvas())->accessBitmap(false);
+ gfx::Matrix3F covariance = color_utils::ComputeColorCovariance(bitmap);
+
+ gfx::Matrix3F expected_covariance = gfx::Matrix3F::Zeros();
+ expected_covariance.set(2400, 400, -1600,
+ 400, 2400, 400,
+ -1600, 400, 2400);
+ EXPECT_EQ(expected_covariance, covariance);
+}
+
+TEST_F(ColorAnalysisTest, ApplyColorReductionSingleColor) {
+ // The test runs color reduction on a single-colot image, where results are
+ // bound to be uninteresting. This is an important edge case, though.
+ SkBitmap source, result;
+ source.setConfig(SkBitmap::kARGB_8888_Config, 300, 200);
+ result.setConfig(SkBitmap::kA8_Config, 300, 200);
+
+ source.allocPixels();
+ result.allocPixels();
+ source.eraseRGB(50, 150, 200);
+
+ gfx::Vector3dF transform(1.0f, .5f, 0.1f);
+ // This transform, if not scaled, should result in GL=145.
+ EXPECT_TRUE(color_utils::ApplyColorReduction(
+ source, transform, false, &result));
+
+ uint8_t min_gl = 0;
+ uint8_t max_gl = 0;
+ Calculate8bitBitmapMinMax(result, &min_gl, &max_gl);
+ EXPECT_EQ(145, min_gl);
+ EXPECT_EQ(145, max_gl);
+
+ // Now scan requesting rescale. Expect all 0.
+ EXPECT_TRUE(color_utils::ApplyColorReduction(
+ source, transform, true, &result));
+ Calculate8bitBitmapMinMax(result, &min_gl, &max_gl);
+ EXPECT_EQ(0, min_gl);
+ EXPECT_EQ(0, max_gl);
+
+ // Test cliping to upper limit.
+ transform.set_z(1.1f);
+ EXPECT_TRUE(color_utils::ApplyColorReduction(
+ source, transform, false, &result));
+ Calculate8bitBitmapMinMax(result, &min_gl, &max_gl);
+ EXPECT_EQ(0xFF, min_gl);
+ EXPECT_EQ(0xFF, max_gl);
+
+ // Test cliping to upper limit.
+ transform.Scale(-1.0f);
+ EXPECT_TRUE(color_utils::ApplyColorReduction(
+ source, transform, false, &result));
+ Calculate8bitBitmapMinMax(result, &min_gl, &max_gl);
+ EXPECT_EQ(0x0, min_gl);
+ EXPECT_EQ(0x0, max_gl);
+}
+
+TEST_F(ColorAnalysisTest, ApplyColorReductionBlackAndWhite) {
+ // Check with images with multiple colors. This is really different only when
+ // the result is scaled.
+ gfx::Canvas canvas(gfx::Size(300, 200), ui::SCALE_FACTOR_100P, true);
+
+ // The image consists of vertical non-overlapping stripes 150 pixels wide.
+ canvas.FillRect(gfx::Rect(0, 0, 150, 200), SkColorSetRGB(0, 0, 0));
+ canvas.FillRect(gfx::Rect(150, 0, 150, 200), SkColorSetRGB(255, 255, 255));
+ SkBitmap source =
+ skia::GetTopDevice(*canvas.sk_canvas())->accessBitmap(false);
+ SkBitmap result;
+ result.setConfig(SkBitmap::kA8_Config, 300, 200);
+ result.allocPixels();
+
+ gfx::Vector3dF transform(1.0f, 0.5f, 0.1f);
+ EXPECT_TRUE(color_utils::ApplyColorReduction(
+ source, transform, true, &result));
+ uint8_t min_gl = 0;
+ uint8_t max_gl = 0;
+ Calculate8bitBitmapMinMax(result, &min_gl, &max_gl);
+
+ EXPECT_EQ(0, min_gl);
+ EXPECT_EQ(255, max_gl);
+ EXPECT_EQ(min_gl, SkColorGetA(result.getColor(0, 0)));
+ EXPECT_EQ(max_gl, SkColorGetA(result.getColor(299, 199)));
+
+ // Reverse test.
+ transform.Scale(-1.0f);
+ EXPECT_TRUE(color_utils::ApplyColorReduction(
+ source, transform, true, &result));
+ min_gl = 0;
+ max_gl = 0;
+ Calculate8bitBitmapMinMax(result, &min_gl, &max_gl);
+
+ EXPECT_EQ(0, min_gl);
+ EXPECT_EQ(255, max_gl);
+ EXPECT_EQ(max_gl, SkColorGetA(result.getColor(0, 0)));
+ EXPECT_EQ(min_gl, SkColorGetA(result.getColor(299, 199)));
+}
+
+TEST_F(ColorAnalysisTest, ApplyColorReductionMultiColor) {
+ // Check with images with multiple colors. This is really different only when
+ // the result is scaled.
+ gfx::Canvas canvas(gfx::Size(300, 200), ui::SCALE_FACTOR_100P, true);
+
+ // The image consists of vertical non-overlapping stripes 100 pixels wide.
+ canvas.FillRect(gfx::Rect(0, 0, 100, 200), SkColorSetRGB(100, 0, 0));
+ canvas.FillRect(gfx::Rect(100, 0, 100, 200), SkColorSetRGB(0, 255, 0));
+ canvas.FillRect(gfx::Rect(200, 0, 100, 200), SkColorSetRGB(0, 0, 128));
+ SkBitmap source =
+ skia::GetTopDevice(*canvas.sk_canvas())->accessBitmap(false);
+ SkBitmap result;
+ result.setConfig(SkBitmap::kA8_Config, 300, 200);
+ result.allocPixels();
+
+ gfx::Vector3dF transform(1.0f, 0.5f, 0.1f);
+ EXPECT_TRUE(color_utils::ApplyColorReduction(
+ source, transform, false, &result));
+ uint8_t min_gl = 0;
+ uint8_t max_gl = 0;
+ Calculate8bitBitmapMinMax(result, &min_gl, &max_gl);
+ EXPECT_EQ(12, min_gl);
+ EXPECT_EQ(127, max_gl);
+ EXPECT_EQ(min_gl, SkColorGetA(result.getColor(299, 199)));
+ EXPECT_EQ(max_gl, SkColorGetA(result.getColor(150, 0)));
+ EXPECT_EQ(100U, SkColorGetA(result.getColor(0, 0)));
+
+ EXPECT_TRUE(color_utils::ApplyColorReduction(
+ source, transform, true, &result));
+ Calculate8bitBitmapMinMax(result, &min_gl, &max_gl);
+ EXPECT_EQ(0, min_gl);
+ EXPECT_EQ(255, max_gl);
+ EXPECT_EQ(min_gl, SkColorGetA(result.getColor(299, 199)));
+ EXPECT_EQ(max_gl, SkColorGetA(result.getColor(150, 0)));
+ EXPECT_EQ(193U, SkColorGetA(result.getColor(0, 0)));
+}
+
+TEST_F(ColorAnalysisTest, ComputePrincipalComponentImageNotComputable) {
+ SkBitmap source, result;
+ source.setConfig(SkBitmap::kARGB_8888_Config, 300, 200);
+ result.setConfig(SkBitmap::kA8_Config, 300, 200);
+
+ source.allocPixels();
+ result.allocPixels();
+ source.eraseRGB(50, 150, 200);
+
+ // This computation should fail since all colors always vary together.
+ EXPECT_FALSE(color_utils::ComputePrincipalComponentImage(source, &result));
+}
+
+TEST_F(ColorAnalysisTest, ComputePrincipalComponentImage) {
+ gfx::Canvas canvas(gfx::Size(300, 200), ui::SCALE_FACTOR_100P, true);
+
+ // The image consists of vertical non-overlapping stripes 100 pixels wide.
+ canvas.FillRect(gfx::Rect(0, 0, 100, 200), SkColorSetRGB(10, 10, 10));
+ canvas.FillRect(gfx::Rect(100, 0, 100, 200), SkColorSetRGB(100, 100, 100));
+ canvas.FillRect(gfx::Rect(200, 0, 100, 200), SkColorSetRGB(255, 255, 255));
+ SkBitmap source =
+ skia::GetTopDevice(*canvas.sk_canvas())->accessBitmap(false);
+ SkBitmap result;
+ result.setConfig(SkBitmap::kA8_Config, 300, 200);
+ result.allocPixels();
+
+ // This computation should fail since all colors always vary together.
+ EXPECT_TRUE(color_utils::ComputePrincipalComponentImage(source, &result));
+
+ uint8_t min_gl = 0;
+ uint8_t max_gl = 0;
+ Calculate8bitBitmapMinMax(result, &min_gl, &max_gl);
+
+ EXPECT_EQ(0, min_gl);
+ EXPECT_EQ(255, max_gl);
+ EXPECT_EQ(min_gl, SkColorGetA(result.getColor(0, 0)));
+ EXPECT_EQ(max_gl, SkColorGetA(result.getColor(299, 199)));
+ EXPECT_EQ(93U, SkColorGetA(result.getColor(150, 0)));
+}
diff --git a/chromium/ui/gfx/color_profile.cc b/chromium/ui/gfx/color_profile.cc
new file mode 100644
index 00000000000..fcd9a52ea0c
--- /dev/null
+++ b/chromium/ui/gfx/color_profile.cc
@@ -0,0 +1,23 @@
+// Copyright (c) 2012 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 "ui/gfx/color_profile.h"
+
+namespace gfx {
+
+#if defined(OS_WIN) || defined(OS_MACOSX)
+void ReadColorProfile(std::vector<char>* profile);
+#else
+void ReadColorProfile(std::vector<char>* profile) { }
+#endif
+
+ColorProfile::ColorProfile() {
+ // TODO: support multiple monitors.
+ ReadColorProfile(&profile_);
+}
+
+ColorProfile::~ColorProfile() {
+}
+
+} // namespace gfx
diff --git a/chromium/ui/gfx/color_profile.h b/chromium/ui/gfx/color_profile.h
new file mode 100644
index 00000000000..5e50f6b88a2
--- /dev/null
+++ b/chromium/ui/gfx/color_profile.h
@@ -0,0 +1,40 @@
+// Copyright (c) 2012 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_PROFILE_H_
+#define UI_GFX_COLOR_PROFILE_H_
+
+#include <vector>
+
+#include "base/basictypes.h"
+
+#include "ui/base/ui_export.h"
+
+namespace gfx {
+
+static const size_t kMinProfileLength = 128;
+static const size_t kMaxProfileLength = 4 * 1024 * 1024;
+
+class UI_EXPORT ColorProfile {
+ public:
+ // On Windows, this reads a file from disk so it shouldn't be run on the UI
+ // or IO thread.
+ ColorProfile();
+ ~ColorProfile();
+
+ const std::vector<char>& profile() const { return profile_; }
+
+ private:
+ std::vector<char> profile_;
+
+ DISALLOW_COPY_AND_ASSIGN(ColorProfile);
+};
+
+// Loads the monitor color space if available.
+UI_EXPORT void GetColorProfile(std::vector<char>* profile);
+
+} // namespace gfx
+
+#endif // UI_GFX_COLOR_PROFILE_H_
+
diff --git a/chromium/ui/gfx/color_profile_mac.cc b/chromium/ui/gfx/color_profile_mac.cc
new file mode 100644
index 00000000000..2c7f686a5f4
--- /dev/null
+++ b/chromium/ui/gfx/color_profile_mac.cc
@@ -0,0 +1,27 @@
+// Copyright (c) 2012 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 "ui/gfx/color_profile.h"
+
+#include "base/mac/mac_util.h"
+
+namespace gfx {
+
+void ReadColorProfile(std::vector<char>* profile) {
+ CGColorSpaceRef monitor_color_space(base::mac::GetSystemColorSpace());
+ base::ScopedCFTypeRef<CFDataRef> icc_profile(
+ CGColorSpaceCopyICCProfile(monitor_color_space));
+ if (icc_profile) {
+ size_t length = CFDataGetLength(icc_profile);
+ if (length > gfx::kMaxProfileLength)
+ return;
+ if (length < gfx::kMinProfileLength)
+ return;
+ const unsigned char* sys_profile = CFDataGetBytePtr(icc_profile);
+ profile->assign(sys_profile, sys_profile + length);
+ }
+}
+
+} // namespace gfx
+
diff --git a/chromium/ui/gfx/color_profile_win.cc b/chromium/ui/gfx/color_profile_win.cc
new file mode 100644
index 00000000000..51b1b312d08
--- /dev/null
+++ b/chromium/ui/gfx/color_profile_win.cc
@@ -0,0 +1,35 @@
+// Copyright (c) 2012 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 "ui/gfx/color_profile.h"
+
+#include <windows.h>
+
+#include "base/file_util.h"
+
+namespace gfx {
+
+void ReadColorProfile(std::vector<char>* profile) {
+ // TODO: support multiple monitors.
+ HDC screen_dc = GetDC(NULL);
+ DWORD path_len = MAX_PATH;
+ WCHAR path[MAX_PATH + 1];
+
+ BOOL res = GetICMProfile(screen_dc, &path_len, path);
+ ReleaseDC(NULL, screen_dc);
+ if (!res)
+ return;
+ std::string profileData;
+ if (!file_util::ReadFileToString(base::FilePath(path), &profileData))
+ return;
+ size_t length = profileData.size();
+ if (length > gfx::kMaxProfileLength)
+ return;
+ if (length < gfx::kMinProfileLength)
+ return;
+ profile->assign(profileData.data(), profileData.data() + length);
+}
+
+} // namespace gfx
+
diff --git a/chromium/ui/gfx/color_utils.cc b/chromium/ui/gfx/color_utils.cc
new file mode 100644
index 00000000000..9ced80f3952
--- /dev/null
+++ b/chromium/ui/gfx/color_utils.cc
@@ -0,0 +1,271 @@
+// Copyright (c) 2012 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 "ui/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 {
+
+int calcHue(double temp1, double temp2, double hue) {
+ if (hue < 0.0)
+ ++hue;
+ else if (hue > 1.0)
+ --hue;
+
+ double result = temp1;
+ if (hue * 6.0 < 1.0)
+ result = temp1 + (temp2 - temp1) * hue * 6.0;
+ else if (hue * 2.0 < 1.0)
+ result = temp2;
+ else if (hue * 3.0 < 2.0)
+ result = temp1 + (temp2 - temp1) * (2.0 / 3.0 - hue) * 6.0;
+
+ // Scale the result from 0 - 255 and round off the value.
+ return static_cast<int>(result * 255 + .5);
+}
+
+// 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(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) {
+ DCHECK_GE(foreground_luminance, 0.0);
+ DCHECK_GE(background_luminance, 0.0);
+ foreground_luminance += 0.05;
+ background_luminance += 0.05;
+ return (foreground_luminance > background_luminance) ?
+ (foreground_luminance / background_luminance) :
+ (background_luminance / foreground_luminance);
+}
+
+} // namespace
+
+
+// ----------------------------------------------------------------------------
+
+unsigned char GetLuminanceForColor(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);
+}
+
+double RelativeLuminance(SkColor color) {
+ return (0.2126 * ConvertSRGB(SkColorGetR(color))) +
+ (0.7152 * ConvertSRGB(SkColorGetG(color))) +
+ (0.0722 * ConvertSRGB(SkColorGetB(color)));
+}
+
+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 (SkColorGetR(c) == SkColorGetG(c) && SkColorGetR(c) == SkColorGetB(c)) {
+ hsl->h = hsl->s = 0;
+ } else {
+ 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));
+ }
+}
+
+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,
+ calcHue(temp1, temp2, hue + 1.0 / 3.0),
+ calcHue(temp1, temp2, hue),
+ calcHue(temp1, temp2, hue - 1.0 / 3.0));
+}
+
+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));
+}
+
+void BuildLumaHistogram(const SkBitmap& bitmap, int histogram[256]) {
+ DCHECK_EQ(SkBitmap::kARGB_8888_Config, bitmap.config());
+
+ SkAutoLockPixels bitmap_lock(bitmap);
+
+ int pixel_width = bitmap.width();
+ int pixel_height = bitmap.height();
+ for (int y = 0; y < pixel_height; ++y) {
+ for (int x = 0; x < pixel_width; ++x)
+ ++histogram[GetLuminanceForColor(bitmap.getColor(x, y))];
+ }
+}
+
+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 SK_ColorTRANSPARENT;
+
+ 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 BlendTowardOppositeLuminance(SkColor color, SkAlpha alpha) {
+ unsigned char background_luminance =
+ color_utils::GetLuminanceForColor(color);
+ const SkColor blend_color =
+ (background_luminance < 128) ? SK_ColorWHITE : SK_ColorBLACK;
+ return color_utils::AlphaBlend(blend_color, color, alpha);
+}
+
+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 InvertColor(SkColor color) {
+ return SkColorSetARGB(
+ SkColorGetA(color),
+ 255 - SkColorGetR(color),
+ 255 - SkColorGetG(color),
+ 255 - SkColorGetB(color));
+}
+
+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/chromium/ui/gfx/color_utils.h b/chromium/ui/gfx/color_utils.h
new file mode 100644
index 00000000000..c9f62066427
--- /dev/null
+++ b/chromium/ui/gfx/color_utils.h
@@ -0,0 +1,89 @@
+// Copyright (c) 2012 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_
+
+#include "base/basictypes.h"
+#include "third_party/skia/include/core/SkColor.h"
+#include "ui/base/ui_export.h"
+
+class SkBitmap;
+
+namespace color_utils {
+
+// Represents an HSL color.
+struct HSL {
+ double h;
+ double s;
+ double l;
+};
+
+UI_EXPORT unsigned char GetLuminanceForColor(SkColor color);
+
+// Calculated according to http://www.w3.org/TR/WCAG20/#relativeluminancedef
+UI_EXPORT double RelativeLuminance(SkColor color);
+
+// Note: these transformations assume sRGB as the source color space
+UI_EXPORT void SkColorToHSL(SkColor c, HSL* hsl);
+UI_EXPORT 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).
+UI_EXPORT 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);
+
+// Builds a histogram based on the Y' of the Y'UV representation of
+// this image.
+UI_EXPORT void BuildLumaHistogram(const 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.
+UI_EXPORT SkColor AlphaBlend(SkColor foreground, SkColor background,
+ SkAlpha alpha);
+
+// Makes a dark color lighter or a light color darker by blending |color| with
+// white or black depending on its current luminance. |alpha| controls the
+// amount of white or black that will be alpha-blended into |color|.
+UI_EXPORT SkColor BlendTowardOppositeLuminance(SkColor color, SkAlpha alpha);
+
+// Given an opaque 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. You should not pass colors with
+// non-255 alpha to this routine, since determining the correct behavior in such
+// cases can be impossible.
+//
+// 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).
+UI_EXPORT SkColor GetReadableColor(SkColor foreground, SkColor background);
+
+// Invert a color.
+UI_EXPORT SkColor InvertColor(SkColor color);
+
+// Gets a Windows system color as a SkColor
+UI_EXPORT SkColor GetSysSkColor(int which);
+
+} // namespace color_utils
+
+#endif // UI_GFX_COLOR_UTILS_H_
diff --git a/chromium/ui/gfx/color_utils_unittest.cc b/chromium/ui/gfx/color_utils_unittest.cc
new file mode 100644
index 00000000000..59eaeba1e3e
--- /dev/null
+++ b/chromium/ui/gfx/color_utils_unittest.cc
@@ -0,0 +1,100 @@
+// 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 "testing/gtest/include/gtest/gtest.h"
+#include "third_party/skia/include/core/SkBitmap.h"
+#include "third_party/skia/include/core/SkColorPriv.h"
+#include "ui/gfx/color_utils.h"
+
+TEST(ColorUtils, SkColorToHSLRed) {
+ color_utils::HSL hsl = { 0, 0, 0 };
+ color_utils::SkColorToHSL(SK_ColorRED, &hsl);
+ EXPECT_DOUBLE_EQ(hsl.h, 0);
+ EXPECT_DOUBLE_EQ(hsl.s, 1);
+ EXPECT_DOUBLE_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_DOUBLE_EQ(hsl.h, 0);
+ EXPECT_DOUBLE_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, RGBtoHSLRoundTrip) {
+ // Just spot check values near the edges.
+ for (int r = 0; r < 10; ++r) {
+ for (int g = 0; g < 10; ++g) {
+ for (int b = 0; b < 10; ++b) {
+ SkColor rgb = SkColorSetARGB(255, r, g, b);
+ color_utils::HSL hsl = { 0, 0, 0 };
+ color_utils::SkColorToHSL(rgb, &hsl);
+ SkColor out = color_utils::HSLToSkColor(hsl, 255);
+ EXPECT_EQ(SkColorGetR(out), SkColorGetR(rgb));
+ EXPECT_EQ(SkColorGetG(out), SkColorGetG(rgb));
+ EXPECT_EQ(SkColorGetB(out), SkColorGetB(rgb));
+ }
+ }
+ }
+ for (int r = 240; r < 256; ++r) {
+ for (int g = 240; g < 256; ++g) {
+ for (int b = 240; b < 256; ++b) {
+ SkColor rgb = SkColorSetARGB(255, r, g, b);
+ color_utils::HSL hsl = { 0, 0, 0 };
+ color_utils::SkColorToHSL(rgb, &hsl);
+ SkColor out = color_utils::HSLToSkColor(hsl, 255);
+ EXPECT_EQ(SkColorGetR(out), SkColorGetR(rgb));
+ EXPECT_EQ(SkColorGetG(out), SkColorGetG(rgb));
+ EXPECT_EQ(SkColorGetB(out), SkColorGetB(rgb));
+ }
+ }
+ }
+}
+
+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);
+ // |result| should be the same as |input| since we passed in a value meaning
+ // no color shift.
+ EXPECT_EQ(SkColorGetA(input), SkColorGetA(result));
+ EXPECT_EQ(SkColorGetR(input), SkColorGetR(result));
+ EXPECT_EQ(SkColorGetG(input), SkColorGetG(result));
+ EXPECT_EQ(SkColorGetB(input), 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/chromium/ui/gfx/display.cc b/chromium/ui/gfx/display.cc
new file mode 100644
index 00000000000..02d97a903fc
--- /dev/null
+++ b/chromium/ui/gfx/display.cc
@@ -0,0 +1,150 @@
+// Copyright (c) 2012 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 "ui/gfx/display.h"
+
+#include "base/command_line.h"
+#include "base/logging.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/stringprintf.h"
+#include "ui/base/ui_base_switches.h"
+#include "ui/gfx/insets.h"
+#include "ui/gfx/point_conversions.h"
+#include "ui/gfx/point_f.h"
+#include "ui/gfx/size_conversions.h"
+
+namespace gfx {
+namespace {
+
+bool HasForceDeviceScaleFactorImpl() {
+ return CommandLine::ForCurrentProcess()->HasSwitch(
+ switches::kForceDeviceScaleFactor);
+}
+
+float GetForcedDeviceScaleFactorImpl() {
+ double scale_in_double = 1.0;
+ if (HasForceDeviceScaleFactorImpl()) {
+ std::string value = CommandLine::ForCurrentProcess()->
+ GetSwitchValueASCII(switches::kForceDeviceScaleFactor);
+ if (!base::StringToDouble(value, &scale_in_double))
+ LOG(ERROR) << "Failed to parse the default device scale factor:" << value;
+ }
+ return static_cast<float>(scale_in_double);
+}
+
+const int64 kInvalidDisplayIDForCompileTimeInit = -1;
+int64 internal_display_id_ = kInvalidDisplayIDForCompileTimeInit;
+
+} // namespace
+
+const int64 Display::kInvalidDisplayID = kInvalidDisplayIDForCompileTimeInit;
+
+// static
+float Display::GetForcedDeviceScaleFactor() {
+ static const float kForcedDeviceScaleFactor =
+ GetForcedDeviceScaleFactorImpl();
+ return kForcedDeviceScaleFactor;
+}
+
+//static
+bool Display::HasForceDeviceScaleFactor() {
+ return HasForceDeviceScaleFactorImpl();
+}
+
+Display::Display()
+ : id_(kInvalidDisplayID),
+ device_scale_factor_(GetForcedDeviceScaleFactor()),
+ rotation_(ROTATE_0) {
+}
+
+Display::Display(int64 id)
+ : id_(id),
+ device_scale_factor_(GetForcedDeviceScaleFactor()),
+ rotation_(ROTATE_0) {
+}
+
+Display::Display(int64 id, const gfx::Rect& bounds)
+ : id_(id),
+ bounds_(bounds),
+ work_area_(bounds),
+ device_scale_factor_(GetForcedDeviceScaleFactor()),
+ rotation_(ROTATE_0) {
+#if defined(USE_AURA)
+ SetScaleAndBounds(device_scale_factor_, bounds);
+#endif
+}
+
+Display::~Display() {
+}
+
+Insets Display::GetWorkAreaInsets() const {
+ return gfx::Insets(work_area_.y() - bounds_.y(),
+ work_area_.x() - bounds_.x(),
+ bounds_.bottom() - work_area_.bottom(),
+ bounds_.right() - work_area_.right());
+}
+
+void Display::SetScaleAndBounds(
+ float device_scale_factor,
+ const gfx::Rect& bounds_in_pixel) {
+ Insets insets = bounds_.InsetsFrom(work_area_);
+ if (!HasForceDeviceScaleFactor()) {
+#if defined(OS_MACOSX)
+ // Unless an explicit scale factor was provided for testing, ensure the
+ // scale is integral.
+ device_scale_factor = static_cast<int>(device_scale_factor);
+#endif
+ device_scale_factor_ = device_scale_factor;
+ }
+ device_scale_factor_ = std::max(1.0f, device_scale_factor_);
+ bounds_ = gfx::Rect(
+ gfx::ToFlooredPoint(gfx::ScalePoint(bounds_in_pixel.origin(),
+ 1.0f / device_scale_factor_)),
+ gfx::ToFlooredSize(gfx::ScaleSize(bounds_in_pixel.size(),
+ 1.0f / device_scale_factor_)));
+ UpdateWorkAreaFromInsets(insets);
+}
+
+void Display::SetSize(const gfx::Size& size_in_pixel) {
+ gfx::Point origin = bounds_.origin();
+#if defined(USE_AURA)
+ gfx::PointF origin_f = origin;
+ origin_f.Scale(device_scale_factor_);
+ origin.SetPoint(origin_f.x(), origin_f.y());
+#endif
+ SetScaleAndBounds(device_scale_factor_, gfx::Rect(origin, size_in_pixel));
+}
+
+void Display::UpdateWorkAreaFromInsets(const gfx::Insets& insets) {
+ work_area_ = bounds_;
+ work_area_.Inset(insets);
+}
+
+gfx::Size Display::GetSizeInPixel() const {
+ return gfx::ToFlooredSize(gfx::ScaleSize(size(), device_scale_factor_));
+}
+
+std::string Display::ToString() const {
+ return base::StringPrintf(
+ "Display[%lld] bounds=%s, workarea=%s, scale=%f, %s",
+ static_cast<long long int>(id_),
+ bounds_.ToString().c_str(),
+ work_area_.ToString().c_str(),
+ device_scale_factor_,
+ IsInternal() ? "internal" : "external");
+}
+
+bool Display::IsInternal() const {
+ return is_valid() && (id_ == internal_display_id_);
+}
+
+int64 Display::InternalDisplayId() {
+ return internal_display_id_;
+}
+
+void Display::SetInternalDisplayId(int64 internal_display_id) {
+ internal_display_id_ = internal_display_id;
+}
+
+} // namespace gfx
diff --git a/chromium/ui/gfx/display.h b/chromium/ui/gfx/display.h
new file mode 100644
index 00000000000..2282cb0a568
--- /dev/null
+++ b/chromium/ui/gfx/display.h
@@ -0,0 +1,116 @@
+// Copyright (c) 2012 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_DISPLAY_H_
+#define UI_GFX_DISPLAY_H_
+
+#include "base/basictypes.h"
+#include "base/compiler_specific.h"
+#include "ui/base/ui_export.h"
+#include "ui/gfx/rect.h"
+
+namespace gfx {
+
+// Note: The screen and display currently uses pixel coordinate
+// system. For platforms that support DIP (density independent pixel),
+// |bounds()| and |work_area| will return values in DIP coordinate
+// system, not in backing pixels.
+class UI_EXPORT Display {
+ public:
+ // Screen Rotation in clock-wise degrees.
+ enum Rotation {
+ ROTATE_0 = 0,
+ ROTATE_90,
+ ROTATE_180,
+ ROTATE_270,
+ };
+
+ // Creates a display with kInvalidDisplayID as default.
+ Display();
+ explicit Display(int64 id);
+ Display(int64 id, const Rect& bounds);
+ ~Display();
+
+ // Returns the forced device scale factor, which is given by
+ // "--force-device-scale-factor".
+ static float GetForcedDeviceScaleFactor();
+
+ // Indicates if a device scale factor is being explicitly enforced from the
+ // command line via "--force-device-scale-factor".
+ static bool HasForceDeviceScaleFactor();
+
+ // Sets/Gets unique identifier associated with the display.
+ // -1 means invalid display and it doesn't not exit.
+ int64 id() const { return id_; }
+ void set_id(int64 id) { id_ = id; }
+
+ // Gets/Sets the display's bounds in gfx::Screen's coordinates.
+ const Rect& bounds() const { return bounds_; }
+ void set_bounds(const Rect& bounds) { bounds_ = bounds; }
+
+ // Gets/Sets the display's work area in gfx::Screen's coordinates.
+ const Rect& work_area() const { return work_area_; }
+ void set_work_area(const Rect& work_area) { work_area_ = work_area; }
+
+ // Output device's pixel scale factor. This specifies how much the
+ // UI should be scaled when the actual output has more pixels than
+ // standard displays (which is around 100~120dpi.) The potential return
+ // values depend on each platforms.
+ float device_scale_factor() const { return device_scale_factor_; }
+ void set_device_scale_factor(float scale) { device_scale_factor_ = scale; }
+
+ Rotation rotation() const { return rotation_; }
+ void set_rotation(Rotation rotation) { rotation_ = rotation; }
+
+ // Utility functions that just return the size of display and
+ // work area.
+ const Size& size() const { return bounds_.size(); }
+ const Size& work_area_size() const { return work_area_.size(); }
+
+ // Returns the work area insets.
+ Insets GetWorkAreaInsets() const;
+
+ // Sets the device scale factor and display bounds in pixel. This
+ // updates the work are using the same insets between old bounds and
+ // work area.
+ void SetScaleAndBounds(float device_scale_factor,
+ const gfx::Rect& bounds_in_pixel);
+
+ // Sets the display's size. This updates the work area using the same insets
+ // between old bounds and work area.
+ void SetSize(const gfx::Size& size_in_pixel);
+
+ // Computes and updates the display's work are using
+ // |work_area_insets| and the bounds.
+ void UpdateWorkAreaFromInsets(const gfx::Insets& work_area_insets);
+
+ // Returns the display's size in pixel coordinates.
+ gfx::Size GetSizeInPixel() const;
+
+ // Returns a string representation of the display;
+ std::string ToString() const;
+
+ // True if the display contains valid display id.
+ bool is_valid() const { return id_ != kInvalidDisplayID; }
+
+ // True if the display corresponds to internal panel.
+ bool IsInternal() const;
+
+ // Gets/Sets an id of display corresponding to internal panel.
+ static int64 InternalDisplayId();
+ static void SetInternalDisplayId(int64 internal_display_id);
+
+ static const int64 kInvalidDisplayID;
+
+ private:
+ int64 id_;
+ Rect bounds_;
+ Rect work_area_;
+ float device_scale_factor_;
+ Rotation rotation_;
+};
+
+} // namespace gfx
+
+#endif // UI_GFX_DISPLAY_H_
diff --git a/chromium/ui/gfx/display_observer.cc b/chromium/ui/gfx/display_observer.cc
new file mode 100644
index 00000000000..f8e3c076401
--- /dev/null
+++ b/chromium/ui/gfx/display_observer.cc
@@ -0,0 +1,12 @@
+// Copyright (c) 2012 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 "ui/gfx/display_observer.h"
+
+namespace gfx {
+
+DisplayObserver::~DisplayObserver() {
+}
+
+} // namespace gfx
diff --git a/chromium/ui/gfx/display_observer.h b/chromium/ui/gfx/display_observer.h
new file mode 100644
index 00000000000..85a4086bb5a
--- /dev/null
+++ b/chromium/ui/gfx/display_observer.h
@@ -0,0 +1,33 @@
+// Copyright (c) 2012 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_DISPLAY_OBSERVER_H_
+#define UI_GFX_DISPLAY_OBSERVER_H_
+
+#include "ui/base/ui_export.h"
+
+namespace gfx {
+class Display;
+
+// Observers for display configuration changes.
+// TODO(oshima): consolidate |WorkAreaWatcherObserver| and
+// |DisplaySettingsProvier|. crbug.com/122863.
+class UI_EXPORT DisplayObserver {
+ public:
+ // Called when the |display|'s bound has changed.
+ virtual void OnDisplayBoundsChanged(const Display& display) = 0;
+
+ // Called when |new_display| has been added.
+ virtual void OnDisplayAdded(const Display& new_display) = 0;
+
+ // Called when |old_display| has been removed.
+ virtual void OnDisplayRemoved(const Display& old_display) = 0;
+
+ protected:
+ virtual ~DisplayObserver();
+};
+
+} // namespace gfx
+
+#endif // UI_GFX_DISPLAY_OBSERVER_H_
diff --git a/chromium/ui/gfx/display_unittest.cc b/chromium/ui/gfx/display_unittest.cc
new file mode 100644
index 00000000000..3ab7aae372c
--- /dev/null
+++ b/chromium/ui/gfx/display_unittest.cc
@@ -0,0 +1,49 @@
+// Copyright (c) 2012 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 "ui/gfx/display.h"
+
+#include "testing/gtest/include/gtest/gtest.h"
+#include "ui/gfx/insets.h"
+
+namespace {
+
+TEST(DisplayTest, WorkArea) {
+ gfx::Display display(0, gfx::Rect(0, 0, 100, 100));
+ EXPECT_EQ("0,0 100x100", display.bounds().ToString());
+ EXPECT_EQ("0,0 100x100", display.work_area().ToString());
+
+ display.set_work_area(gfx::Rect(3, 4, 90, 80));
+ EXPECT_EQ("0,0 100x100", display.bounds().ToString());
+ EXPECT_EQ("3,4 90x80", display.work_area().ToString());
+
+ display.SetScaleAndBounds(1.0f, gfx::Rect(10, 20, 50, 50));
+ EXPECT_EQ("10,20 50x50", display.bounds().ToString());
+ EXPECT_EQ("13,24 40x30", display.work_area().ToString());
+
+ display.SetSize(gfx::Size(200, 200));
+ EXPECT_EQ("13,24 190x180", display.work_area().ToString());
+
+ display.UpdateWorkAreaFromInsets(gfx::Insets(3, 4, 5, 6));
+ EXPECT_EQ("14,23 190x192", display.work_area().ToString());
+}
+
+TEST(DisplayTest, Scale) {
+ gfx::Display display(0, gfx::Rect(0, 0, 100, 100));
+ display.set_work_area(gfx::Rect(10, 10, 80, 80));
+ EXPECT_EQ("0,0 100x100", display.bounds().ToString());
+ EXPECT_EQ("10,10 80x80", display.work_area().ToString());
+
+ // Scale it back to 2x
+ display.SetScaleAndBounds(2.0f, gfx::Rect(0, 0, 140, 140));
+ EXPECT_EQ("0,0 70x70", display.bounds().ToString());
+ EXPECT_EQ("10,10 50x50", display.work_area().ToString());
+
+ // Scale it back to 1x
+ display.SetScaleAndBounds(1.0f, gfx::Rect(0, 0, 100, 100));
+ EXPECT_EQ("0,0 100x100", display.bounds().ToString());
+ EXPECT_EQ("10,10 80x80", display.work_area().ToString());
+}
+
+}
diff --git a/chromium/ui/gfx/favicon_size.cc b/chromium/ui/gfx/favicon_size.cc
new file mode 100644
index 00000000000..d0ba48a4ccd
--- /dev/null
+++ b/chromium/ui/gfx/favicon_size.cc
@@ -0,0 +1,25 @@
+// 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 "ui/gfx/favicon_size.h"
+
+namespace gfx {
+
+const int kFaviconSize = 16;
+
+void CalculateFaviconTargetSize(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);
+ }
+ }
+}
+
+} // namespace gfx
diff --git a/chromium/ui/gfx/favicon_size.h b/chromium/ui/gfx/favicon_size.h
new file mode 100644
index 00000000000..6b2c6a14f4f
--- /dev/null
+++ b/chromium/ui/gfx/favicon_size.h
@@ -0,0 +1,22 @@
+// 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_FAVICON_SIZE_H_
+#define UI_GFX_FAVICON_SIZE_H_
+
+#include "ui/base/ui_export.h"
+
+namespace gfx {
+
+// Size (along each axis) of the favicon.
+UI_EXPORT extern const int kFaviconSize;
+
+// 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.
+UI_EXPORT void CalculateFaviconTargetSize(int* width, int* height);
+
+} // namespace gfx
+
+#endif // UI_GFX_FAVICON_SIZE_H_
diff --git a/chromium/ui/gfx/font.cc b/chromium/ui/gfx/font.cc
new file mode 100644
index 00000000000..aa4638c7efc
--- /dev/null
+++ b/chromium/ui/gfx/font.cc
@@ -0,0 +1,85 @@
+// Copyright (c) 2012 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 "ui/gfx/font.h"
+
+#include "base/strings/utf_string_conversions.h"
+#include "ui/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 std::string& 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 base::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();
+}
+
+std::string 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/chromium/ui/gfx/font.h b/chromium/ui/gfx/font.h
new file mode 100644
index 00000000000..bd6e4650767
--- /dev/null
+++ b/chromium/ui/gfx/font.h
@@ -0,0 +1,114 @@
+// Copyright (c) 2012 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_
+
+#include <string>
+
+#include "base/memory/ref_counted.h"
+#include "base/strings/string16.h"
+#include "ui/base/ui_export.h"
+#include "ui/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 UI_EXPORT Font {
+ public:
+ // The following constants indicate the font style.
+ enum FontStyle {
+ NORMAL = 0,
+ BOLD = 1,
+ ITALIC = 2,
+ UNDERLINE = 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);
+
+ // Constructs 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 in UTF-8 and size in pixels.
+ Font(const std::string& font_name, int font_size);
+
+ ~Font();
+
+ // Returns a new Font derived from the existing font.
+ // |size_deta| is the size in pixels to add to the current font. For example,
+ // a value of 5 results in a font 5 pixels bigger than this font.
+ Font DeriveFont(int size_delta) const;
+
+ // Returns a new Font derived from the existing font.
+ // |size_delta| is the size in pixels 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 UNDERLINE.
+ 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 base::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 in UTF-8.
+ std::string 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: The object is owned by the system and should not be released.
+ // Gtk: This handle is created on demand, and must be freed by calling
+ // pango_font_description_free() when the caller is done using it or
+ // by using ScopedPangoFontDescription.
+ 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/chromium/ui/gfx/font_fallback_win.cc b/chromium/ui/gfx/font_fallback_win.cc
new file mode 100644
index 00000000000..40666ee239a
--- /dev/null
+++ b/chromium/ui/gfx/font_fallback_win.cc
@@ -0,0 +1,243 @@
+// Copyright (c) 2012 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 "ui/gfx/font_fallback_win.h"
+
+#include <map>
+
+#include "base/memory/singleton.h"
+#include "base/strings/string_split.h"
+#include "base/strings/string_util.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/win/registry.h"
+#include "ui/gfx/font.h"
+
+namespace gfx {
+
+namespace {
+
+// Queries the registry to get a mapping from font filenames to font names.
+void QueryFontsFromRegistry(std::map<std::string, std::string>* map) {
+ const wchar_t* kFonts =
+ L"Software\\Microsoft\\Windows NT\\CurrentVersion\\Fonts";
+
+ base::win::RegistryValueIterator it(HKEY_LOCAL_MACHINE, kFonts);
+ for (; it.Valid(); ++it) {
+ const std::string filename = StringToLowerASCII(WideToUTF8(it.Value()));
+ (*map)[filename] = WideToUTF8(it.Name());
+ }
+}
+
+// Fills |font_names| with a list of font families found in the font file at
+// |filename|. Takes in a |font_map| from font filename to font families, which
+// is filled-in by querying the registry, if empty.
+void GetFontNamesFromFilename(const std::string& filename,
+ std::map<std::string, std::string>* font_map,
+ std::vector<std::string>* font_names) {
+ if (font_map->empty())
+ QueryFontsFromRegistry(font_map);
+
+ std::map<std::string, std::string>::const_iterator it =
+ font_map->find(StringToLowerASCII(filename));
+ if (it == font_map->end())
+ return;
+
+ internal::ParseFontFamilyString(it->second, font_names);
+}
+
+// Returns true if |text| contains only ASCII digits.
+bool ContainsOnlyDigits(const std::string& text) {
+ return text.find_first_not_of("0123456789") == base::string16::npos;
+}
+
+// Appends a Font with the given |name| and |size| to |fonts| unless the last
+// entry is already a font with that name.
+void AppendFont(const std::string& name, int size, std::vector<Font>* fonts) {
+ if (fonts->empty() || fonts->back().GetFontName() != name)
+ fonts->push_back(Font(name, size));
+}
+
+// Queries the registry to get a list of linked fonts for |font|.
+void QueryLinkedFontsFromRegistry(const Font& font,
+ std::map<std::string, std::string>* font_map,
+ std::vector<Font>* linked_fonts) {
+ const wchar_t* kSystemLink =
+ L"Software\\Microsoft\\Windows NT\\CurrentVersion\\FontLink\\SystemLink";
+
+ base::win::RegKey key;
+ if (FAILED(key.Open(HKEY_LOCAL_MACHINE, kSystemLink, KEY_READ)))
+ return;
+
+ const std::wstring original_font_name = UTF8ToWide(font.GetFontName());
+ std::vector<std::wstring> values;
+ if (FAILED(key.ReadValues(original_font_name.c_str(), &values))) {
+ key.Close();
+ return;
+ }
+
+ std::string filename;
+ std::string font_name;
+ for (size_t i = 0; i < values.size(); ++i) {
+ internal::ParseFontLinkEntry(WideToUTF8(values[i]), &filename, &font_name);
+ // If the font name is present, add that directly, otherwise add the
+ // font names corresponding to the filename.
+ if (!font_name.empty()) {
+ AppendFont(font_name, font.GetFontSize(), linked_fonts);
+ } else if (!filename.empty()) {
+ std::vector<std::string> font_names;
+ GetFontNamesFromFilename(filename, font_map, &font_names);
+ for (size_t i = 0; i < font_names.size(); ++i)
+ AppendFont(font_names[i], font.GetFontSize(), linked_fonts);
+ }
+ }
+
+ key.Close();
+}
+
+// CachedFontLinkSettings is a singleton cache of the Windows font settings
+// from the registry. It maintains a cached view of the registry's list of
+// system fonts and their font link chains.
+class CachedFontLinkSettings {
+ public:
+ static CachedFontLinkSettings* GetInstance();
+
+ // Returns the linked fonts list correspond to |font|. Returned value will
+ // never be null.
+ const std::vector<Font>* GetLinkedFonts(const Font& font);
+
+ private:
+ friend struct DefaultSingletonTraits<CachedFontLinkSettings>;
+
+ CachedFontLinkSettings();
+ virtual ~CachedFontLinkSettings();
+
+ // Map of system fonts, from file names to font families.
+ std::map<std::string, std::string> cached_system_fonts_;
+
+ // Map from font names to vectors of linked fonts.
+ std::map<std::string, std::vector<Font> > cached_linked_fonts_;
+
+ DISALLOW_COPY_AND_ASSIGN(CachedFontLinkSettings);
+};
+
+// static
+CachedFontLinkSettings* CachedFontLinkSettings::GetInstance() {
+ return Singleton<CachedFontLinkSettings,
+ LeakySingletonTraits<CachedFontLinkSettings> >::get();
+}
+
+const std::vector<Font>* CachedFontLinkSettings::GetLinkedFonts(
+ const Font& font) {
+ const std::string& font_name = font.GetFontName();
+ std::map<std::string, std::vector<Font> >::const_iterator it =
+ cached_linked_fonts_.find(font_name);
+ if (it != cached_linked_fonts_.end())
+ return &it->second;
+
+ cached_linked_fonts_[font_name] = std::vector<Font>();
+ std::vector<Font>* linked_fonts = &cached_linked_fonts_[font_name];
+ QueryLinkedFontsFromRegistry(font, &cached_system_fonts_, linked_fonts);
+ return linked_fonts;
+}
+
+CachedFontLinkSettings::CachedFontLinkSettings() {
+}
+
+CachedFontLinkSettings::~CachedFontLinkSettings() {
+}
+
+} // namespace
+
+namespace internal {
+
+void ParseFontLinkEntry(const std::string& entry,
+ std::string* filename,
+ std::string* font_name) {
+ std::vector<std::string> parts;
+ base::SplitString(entry, ',', &parts);
+ filename->clear();
+ font_name->clear();
+ if (parts.size() > 0)
+ *filename = parts[0];
+ // The second entry may be the font name or the first scaling factor, if the
+ // entry does not contain a font name. If it contains only digits, assume it
+ // is a scaling factor.
+ if (parts.size() > 1 && !ContainsOnlyDigits(parts[1]))
+ *font_name = parts[1];
+}
+
+void ParseFontFamilyString(const std::string& family,
+ std::vector<std::string>* font_names) {
+ // The entry is comma separated, having the font filename as the first value
+ // followed optionally by the font family name and a pair of integer scaling
+ // factors.
+ // TODO(asvitkine): Should we support these scaling factors?
+ base::SplitString(family, '&', font_names);
+ if (!font_names->empty()) {
+ const size_t index = font_names->back().find('(');
+ if (index != std::string::npos) {
+ font_names->back().resize(index);
+ TrimWhitespace(font_names->back(), TRIM_TRAILING, &font_names->back());
+ }
+ }
+}
+
+} // namespace internal
+
+LinkedFontsIterator::LinkedFontsIterator(Font font)
+ : original_font_(font),
+ next_font_set_(false),
+ linked_fonts_(NULL),
+ linked_font_index_(0) {
+ SetNextFont(original_font_);
+}
+
+LinkedFontsIterator::~LinkedFontsIterator() {
+}
+
+void LinkedFontsIterator::SetNextFont(Font font) {
+ next_font_ = font;
+ next_font_set_ = true;
+}
+
+bool LinkedFontsIterator::NextFont(Font* font) {
+ if (next_font_set_) {
+ next_font_set_ = false;
+ current_font_ = next_font_;
+ *font = current_font_;
+ return true;
+ }
+
+ // First time through, get the linked fonts list.
+ if (linked_fonts_ == NULL)
+ linked_fonts_ = GetLinkedFonts();
+
+ if (linked_font_index_ == linked_fonts_->size())
+ return false;
+
+ current_font_ = linked_fonts_->at(linked_font_index_++);
+ *font = current_font_;
+ return true;
+}
+
+const std::vector<Font>* LinkedFontsIterator::GetLinkedFonts() const {
+ CachedFontLinkSettings* font_link = CachedFontLinkSettings::GetInstance();
+
+ // First, try to get the list for the original font.
+ const std::vector<Font>* fonts = font_link->GetLinkedFonts(original_font_);
+
+ // If there are no linked fonts for the original font, try querying the
+ // ones for the current font. This may happen if the first font is a custom
+ // font that has no linked fonts in the registry.
+ //
+ // Note: One possibility would be to always merge both lists of fonts,
+ // but it is not clear whether there are any real world scenarios
+ // where this would actually help.
+ if (fonts->empty())
+ fonts = font_link->GetLinkedFonts(current_font_);
+
+ return fonts;
+}
+
+} // namespace gfx
diff --git a/chromium/ui/gfx/font_fallback_win.h b/chromium/ui/gfx/font_fallback_win.h
new file mode 100644
index 00000000000..7e206da4f43
--- /dev/null
+++ b/chromium/ui/gfx/font_fallback_win.h
@@ -0,0 +1,82 @@
+// Copyright (c) 2012 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_FALLBACK_WIN_H_
+#define UI_GFX_FONT_FALLBACK_WIN_H_
+
+#include <string>
+#include <vector>
+
+#include "ui/gfx/font.h"
+
+namespace gfx {
+
+// Internals of font_fallback_win.cc exposed for testing.
+namespace internal {
+
+// Parses comma separated SystemLink |entry|, per the format described here:
+// http://msdn.microsoft.com/en-us/goglobal/bb688134.aspx
+//
+// Sets |filename| and |font_name| respectively. If a field is not present
+// or could not be parsed, the corresponding parameter will be cleared.
+void UI_EXPORT ParseFontLinkEntry(const std::string& entry,
+ std::string* filename,
+ std::string* font_name);
+
+// Parses a font |family| in the format "FamilyFoo & FamilyBar (TrueType)".
+// Splits by '&' and strips off the trailing parenthesized expression.
+void UI_EXPORT ParseFontFamilyString(const std::string& family,
+ std::vector<std::string>* font_names);
+
+} // namespace internal
+
+// Iterator over linked fallback fonts for a given font. The linked font chain
+// comes from the Windows registry, but gets cached between uses.
+class UI_EXPORT LinkedFontsIterator {
+ public:
+ // Instantiates the iterator over the linked font chain for |font|. The first
+ // item will be |font| itself.
+ explicit LinkedFontsIterator(Font font);
+ virtual ~LinkedFontsIterator();
+
+ // Sets the font that would be returned by the next call to |NextFont()|,
+ // useful for inserting one-time entries into the iterator chain.
+ void SetNextFont(Font font);
+
+ // Gets the next font in the link chain, if available, and increments the
+ // iterator. Returns |true| on success or |false| if the iterator is past
+ // last item (in that case, the value of |font| should not be used). If
+ // |SetNextFont()| was called, returns the font set that way and clears it.
+ bool NextFont(Font* font);
+
+ protected:
+ // Retrieves the list of linked fonts. Protected and virtual so that it may
+ // be overridden by tests.
+ virtual const std::vector<Font>* GetLinkedFonts() const;
+
+ private:
+ // Original font whose linked fonts are being iterated over.
+ Font original_font_;
+
+ // Font that was set via |SetNextFont()|.
+ Font next_font_;
+
+ // Indicates whether |SetNextFont()| was called.
+ bool next_font_set_;
+
+ // The font most recently returned by |NextFont()|.
+ Font current_font_;
+
+ // List of linked fonts; weak pointer.
+ const std::vector<Font>* linked_fonts_;
+
+ // Index of the current entry in the |linked_fonts_| list.
+ size_t linked_font_index_;
+
+ DISALLOW_COPY_AND_ASSIGN(LinkedFontsIterator);
+};
+
+} // namespace gfx
+
+#endif // UI_GFX_FONT_FALLBACK_WIN_H_
diff --git a/chromium/ui/gfx/font_fallback_win_unittest.cc b/chromium/ui/gfx/font_fallback_win_unittest.cc
new file mode 100644
index 00000000000..794f2a8b202
--- /dev/null
+++ b/chromium/ui/gfx/font_fallback_win_unittest.cc
@@ -0,0 +1,117 @@
+// Copyright (c) 2012 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 "ui/gfx/font_fallback_win.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace gfx {
+
+namespace {
+
+// Subclass of LinkedFontsIterator for testing that allows mocking the linked
+// fonts vector.
+class TestLinkedFontsIterator : public LinkedFontsIterator {
+ public:
+ explicit TestLinkedFontsIterator(Font font) : LinkedFontsIterator(font) {
+ }
+
+ virtual ~TestLinkedFontsIterator() {
+ }
+
+ // Add a linked font to the mocked vector of linked fonts.
+ void AddLinkedFontForTesting(Font font) {
+ test_linked_fonts.push_back(font);
+ }
+
+ virtual const std::vector<Font>* GetLinkedFonts() const OVERRIDE {
+ return &test_linked_fonts;
+ }
+
+ private:
+ std::vector<Font> test_linked_fonts;
+
+ DISALLOW_COPY_AND_ASSIGN(TestLinkedFontsIterator);
+};
+
+} // namespace
+
+TEST(FontFallbackWinTest, ParseFontLinkEntry) {
+ std::string file;
+ std::string font;
+
+ internal::ParseFontLinkEntry("TAHOMA.TTF", &file, &font);
+ EXPECT_EQ("TAHOMA.TTF", file);
+ EXPECT_EQ("", font);
+
+ internal::ParseFontLinkEntry("MSGOTHIC.TTC,MS UI Gothic", &file, &font);
+ EXPECT_EQ("MSGOTHIC.TTC", file);
+ EXPECT_EQ("MS UI Gothic", font);
+
+ internal::ParseFontLinkEntry("MALGUN.TTF,128,96", &file, &font);
+ EXPECT_EQ("MALGUN.TTF", file);
+ EXPECT_EQ("", font);
+
+ internal::ParseFontLinkEntry("MEIRYO.TTC,Meiryo,128,85", &file, &font);
+ EXPECT_EQ("MEIRYO.TTC", file);
+ EXPECT_EQ("Meiryo", font);
+}
+
+TEST(FontFallbackWinTest, ParseFontFamilyString) {
+ std::vector<std::string> font_names;
+
+ internal::ParseFontFamilyString("Times New Roman (TrueType)", &font_names);
+ ASSERT_EQ(1U, font_names.size());
+ EXPECT_EQ("Times New Roman", font_names[0]);
+ font_names.clear();
+
+ internal::ParseFontFamilyString("Cambria & Cambria Math (TrueType)",
+ &font_names);
+ ASSERT_EQ(2U, font_names.size());
+ EXPECT_EQ("Cambria", font_names[0]);
+ EXPECT_EQ("Cambria Math", font_names[1]);
+ font_names.clear();
+
+ internal::ParseFontFamilyString(
+ "Meiryo & Meiryo Italic & Meiryo UI & Meiryo UI Italic (TrueType)",
+ &font_names);
+ ASSERT_EQ(4U, font_names.size());
+ EXPECT_EQ("Meiryo", font_names[0]);
+ EXPECT_EQ("Meiryo Italic", font_names[1]);
+ EXPECT_EQ("Meiryo UI", font_names[2]);
+ EXPECT_EQ("Meiryo UI Italic", font_names[3]);
+}
+
+TEST(FontFallbackWinTest, LinkedFontsIterator) {
+ TestLinkedFontsIterator iterator(Font("Arial", 16));
+ iterator.AddLinkedFontForTesting(Font("Times New Roman", 16));
+
+ Font font;
+ EXPECT_TRUE(iterator.NextFont(&font));
+ ASSERT_EQ("Arial", font.GetFontName());
+
+ EXPECT_TRUE(iterator.NextFont(&font));
+ ASSERT_EQ("Times New Roman", font.GetFontName());
+
+ EXPECT_FALSE(iterator.NextFont(&font));
+}
+
+TEST(FontFallbackWinTest, LinkedFontsIteratorSetNextFont) {
+ TestLinkedFontsIterator iterator(Font("Arial", 16));
+ iterator.AddLinkedFontForTesting(Font("Times New Roman", 16));
+
+ Font font;
+ EXPECT_TRUE(iterator.NextFont(&font));
+ ASSERT_EQ("Arial", font.GetFontName());
+
+ iterator.SetNextFont(Font("Tahoma", 16));
+ EXPECT_TRUE(iterator.NextFont(&font));
+ ASSERT_EQ("Tahoma", font.GetFontName());
+
+ EXPECT_TRUE(iterator.NextFont(&font));
+ ASSERT_EQ("Times New Roman", font.GetFontName());
+
+ EXPECT_FALSE(iterator.NextFont(&font));
+}
+
+} // namespace gfx
diff --git a/chromium/ui/gfx/font_list.cc b/chromium/ui/gfx/font_list.cc
new file mode 100644
index 00000000000..e01483a7cc1
--- /dev/null
+++ b/chromium/ui/gfx/font_list.cc
@@ -0,0 +1,273 @@
+// Copyright (c) 2012 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 "ui/gfx/font_list.h"
+
+#include <algorithm>
+
+#include "base/logging.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_split.h"
+#include "base/strings/string_util.h"
+
+namespace {
+
+// Parses font description into |font_names|, |font_style| and |font_size|.
+void ParseFontDescriptionString(const std::string& font_description_string,
+ std::vector<std::string>* font_names,
+ int* font_style,
+ int* font_size) {
+ base::SplitString(font_description_string, ',', font_names);
+ DCHECK_GT(font_names->size(), 1U);
+
+ // The last item is [STYLE_OPTIONS] SIZE.
+ std::vector<std::string> styles_size;
+ base::SplitString(font_names->back(), ' ', &styles_size);
+ DCHECK(!styles_size.empty());
+ base::StringToInt(styles_size.back(), font_size);
+ DCHECK_GT(*font_size, 0);
+ font_names->pop_back();
+
+ // Font supports BOLD and ITALIC; underline is supported via RenderText.
+ *font_style = 0;
+ for (size_t i = 0; i < styles_size.size() - 1; ++i) {
+ // Styles are separated by white spaces. base::SplitString splits styles
+ // by space, and it inserts empty string for continuous spaces.
+ if (styles_size[i].empty())
+ continue;
+ if (!styles_size[i].compare("Bold"))
+ *font_style |= gfx::Font::BOLD;
+ else if (!styles_size[i].compare("Italic"))
+ *font_style |= gfx::Font::ITALIC;
+ else
+ NOTREACHED();
+ }
+}
+
+// Returns the font style and size as a string.
+std::string FontStyleAndSizeToString(int font_style, int font_size) {
+ std::string result;
+ if (font_style & gfx::Font::BOLD)
+ result += "Bold ";
+ if (font_style & gfx::Font::ITALIC)
+ result += "Italic ";
+ result += base::IntToString(font_size);
+ result += "px";
+ return result;
+}
+
+// Returns font description from |font_names|, |font_style|, and |font_size|.
+std::string BuildFontDescription(const std::vector<std::string>& font_names,
+ int font_style,
+ int font_size) {
+ std::string description = JoinString(font_names, ',');
+ description += "," + FontStyleAndSizeToString(font_style, font_size);
+ return description;
+}
+
+} // namespace
+
+namespace gfx {
+
+FontList::FontList()
+ : common_height_(-1),
+ common_baseline_(-1),
+ font_style_(-1),
+ font_size_(-1) {
+ fonts_.push_back(Font());
+}
+
+FontList::FontList(const std::string& font_description_string)
+ : font_description_string_(font_description_string),
+ common_height_(-1),
+ common_baseline_(-1),
+ font_style_(-1),
+ font_size_(-1) {
+ DCHECK(!font_description_string.empty());
+ // DCHECK description string ends with "px" for size in pixel.
+ DCHECK(EndsWith(font_description_string, "px", true));
+}
+
+FontList::FontList(const std::vector<std::string>& font_names,
+ int font_style,
+ int font_size)
+ : font_description_string_(BuildFontDescription(font_names, font_style,
+ font_size)),
+ common_height_(-1),
+ common_baseline_(-1),
+ font_style_(font_style),
+ font_size_(font_size) {
+ DCHECK(!font_names.empty());
+ DCHECK(!font_names[0].empty());
+}
+
+FontList::FontList(const std::vector<Font>& fonts)
+ : fonts_(fonts),
+ common_height_(-1),
+ common_baseline_(-1),
+ font_style_(-1),
+ font_size_(-1) {
+ DCHECK(!fonts.empty());
+ font_style_ = fonts[0].GetStyle();
+ font_size_ = fonts[0].GetFontSize();
+ if (DCHECK_IS_ON()) {
+ for (size_t i = 1; i < fonts.size(); ++i) {
+ DCHECK_EQ(fonts[i].GetStyle(), font_style_);
+ DCHECK_EQ(fonts[i].GetFontSize(), font_size_);
+ }
+ }
+}
+
+FontList::FontList(const Font& font)
+ : common_height_(-1),
+ common_baseline_(-1),
+ font_style_(-1),
+ font_size_(-1) {
+ fonts_.push_back(font);
+}
+
+FontList::~FontList() {
+}
+
+FontList FontList::DeriveFontList(int font_style) const {
+ return DeriveFontListWithSizeDeltaAndStyle(0, font_style);
+}
+
+FontList FontList::DeriveFontListWithSize(int size) const {
+ DCHECK_GT(size, 0);
+ return DeriveFontListWithSizeDeltaAndStyle(size - GetFontSize(),
+ GetFontStyle());
+}
+
+FontList FontList::DeriveFontListWithSizeDelta(int size_delta) const {
+ return DeriveFontListWithSizeDeltaAndStyle(size_delta, GetFontStyle());
+}
+
+FontList FontList::DeriveFontListWithSizeDeltaAndStyle(int size_delta,
+ int style) const {
+ // If there is a font vector, derive from that.
+ if (!fonts_.empty()) {
+ std::vector<Font> fonts = fonts_;
+ for (size_t i = 0; i < fonts.size(); ++i)
+ fonts[i] = fonts[i].DeriveFont(size_delta, style);
+ return FontList(fonts);
+ }
+
+ // Otherwise, parse the font description string to derive from it.
+ std::vector<std::string> font_names;
+ int old_size;
+ int old_style;
+ ParseFontDescriptionString(font_description_string_, &font_names,
+ &old_style, &old_size);
+ int size = old_size + size_delta;
+ DCHECK_GT(size, 0);
+ return FontList(font_names, style, size);
+}
+
+int FontList::GetHeight() const {
+ if (common_height_ == -1)
+ CacheCommonFontHeightAndBaseline();
+ return common_height_;
+}
+
+int FontList::GetBaseline() const {
+ if (common_baseline_ == -1)
+ CacheCommonFontHeightAndBaseline();
+ return common_baseline_;
+}
+
+int FontList::GetStringWidth(const base::string16& text) const {
+ // Rely on the primary font metrics for the time being.
+ // TODO(yukishiino): Not only the first font, all the fonts in the list should
+ // be taken into account to compute the pixels needed to display |text|.
+ // Also this method, including one in Font class, should be deprecated and
+ // client code should call Canvas::GetStringWidth(text, font_list) directly.
+ // Our plan is as follows:
+ // 1. Introduce the FontList version of Canvas::GetStringWidth().
+ // 2. Make client code call Canvas::GetStringWidth().
+ // 3. Retire {Font,FontList}::GetStringWidth().
+ return GetPrimaryFont().GetStringWidth(text);
+}
+
+int FontList::GetExpectedTextWidth(int length) const {
+ // Rely on the primary font metrics for the time being.
+ return GetPrimaryFont().GetExpectedTextWidth(length);
+}
+
+int FontList::GetFontStyle() const {
+ if (font_style_ == -1)
+ CacheFontStyleAndSize();
+ return font_style_;
+}
+
+const std::string& FontList::GetFontDescriptionString() const {
+ if (font_description_string_.empty()) {
+ DCHECK(!fonts_.empty());
+ for (size_t i = 0; i < fonts_.size(); ++i) {
+ std::string name = fonts_[i].GetFontName();
+ font_description_string_ += name;
+ font_description_string_ += ',';
+ }
+ // All fonts have the same style and size.
+ font_description_string_ +=
+ FontStyleAndSizeToString(fonts_[0].GetStyle(), fonts_[0].GetFontSize());
+ }
+ return font_description_string_;
+}
+
+int FontList::GetFontSize() const {
+ if (font_size_ == -1)
+ CacheFontStyleAndSize();
+ return font_size_;
+}
+
+const std::vector<Font>& FontList::GetFonts() const {
+ if (fonts_.empty()) {
+ DCHECK(!font_description_string_.empty());
+
+ std::vector<std::string> font_names;
+ ParseFontDescriptionString(font_description_string_, &font_names,
+ &font_style_, &font_size_);
+ for (size_t i = 0; i < font_names.size(); ++i) {
+ DCHECK(!font_names[i].empty());
+
+ Font font(font_names[i], font_size_);
+ if (font_style_ == Font::NORMAL)
+ fonts_.push_back(font);
+ else
+ fonts_.push_back(font.DeriveFont(0, font_style_));
+ }
+ }
+ return fonts_;
+}
+
+const Font& FontList::GetPrimaryFont() const {
+ return GetFonts()[0];
+}
+
+void FontList::CacheCommonFontHeightAndBaseline() const {
+ int ascent = 0;
+ int descent = 0;
+ const std::vector<Font>& fonts = GetFonts();
+ for (std::vector<Font>::const_iterator i = fonts.begin();
+ i != fonts.end(); ++i) {
+ ascent = std::max(ascent, i->GetBaseline());
+ descent = std::max(descent, i->GetHeight() - i->GetBaseline());
+ }
+ common_height_ = ascent + descent;
+ common_baseline_ = ascent;
+}
+
+void FontList::CacheFontStyleAndSize() const {
+ if (!fonts_.empty()) {
+ font_style_ = fonts_[0].GetStyle();
+ font_size_ = fonts_[0].GetFontSize();
+ } else {
+ std::vector<std::string> font_names;
+ ParseFontDescriptionString(font_description_string_, &font_names,
+ &font_style_, &font_size_);
+ }
+}
+
+} // namespace gfx
diff --git a/chromium/ui/gfx/font_list.h b/chromium/ui/gfx/font_list.h
new file mode 100644
index 00000000000..22be9121cc0
--- /dev/null
+++ b/chromium/ui/gfx/font_list.h
@@ -0,0 +1,141 @@
+// Copyright (c) 2012 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_LIST_H_
+#define UI_GFX_FONT_LIST_H_
+
+#include <string>
+#include <vector>
+
+#include "ui/base/ui_export.h"
+#include "ui/gfx/font.h"
+
+namespace gfx {
+
+// FontList represents a list of fonts either in the form of Font vector or in
+// the form of a string representing font names, styles, and size.
+//
+// The string representation is in the form "FAMILY_LIST [STYLE_OPTIONS] SIZE",
+// where FAMILY_LIST is a comma separated list of families terminated by a
+// comma, STYLE_OPTIONS is a whitespace separated list of words where each word
+// describes one of style, variant, weight, stretch, or gravity, and SIZE is
+// a decimal number followed by "px" for absolute size. STYLE_OPTIONS may be
+// absent.
+//
+// The string format complies with that of Pango detailed at
+// http://developer.gnome.org/pango/stable/pango-Fonts.html#pango-font-description-from-string
+//
+// FontList could be initialized either way without conversion to the other
+// form. The conversion to the other form is done only when asked to get the
+// other form.
+//
+// FontList allows operator= since FontList is a data member type in RenderText,
+// and operator= is used in RenderText::SetFontList().
+class UI_EXPORT FontList {
+ public:
+ // Creates a font list with a Font with default name and style.
+ FontList();
+
+ // Creates a font list from a string representing font names, styles, and
+ // size.
+ explicit FontList(const std::string& font_description_string);
+
+ // Creates a font list from font names, styles and size.
+ FontList(const std::vector<std::string>& font_names,
+ int font_style,
+ int font_size);
+
+ // Creates a font list from a Font vector.
+ // All fonts in this vector should have the same style and size.
+ explicit FontList(const std::vector<Font>& fonts);
+
+ // Creates a font list from a Font.
+ explicit FontList(const Font& font);
+
+ ~FontList();
+
+ // Returns a new FontList with the given |font_style| flags.
+ FontList DeriveFontList(int font_style) const;
+
+ // Returns a new FontList with the same font names and style but with the
+ // given font |size| in pixels.
+ FontList DeriveFontListWithSize(int size) const;
+
+ // Returns a new FontList with the same font names and style but resized.
+ // |size_delta| is the size in pixels to add to the current font size.
+ FontList DeriveFontListWithSizeDelta(int size_delta) const;
+
+ // Returns a new FontList with the same font names but resized and the given
+ // style. |size_delta| is the size in pixels to add to the current font size.
+ // |font_style| specifies the new style, which is a bitmask of the values:
+ // Font::BOLD, Font::ITALIC and Font::UNDERLINE.
+ FontList DeriveFontListWithSizeDeltaAndStyle(int size_delta,
+ int font_style) const;
+
+ // Returns the height of this font list, which is max(ascent) + max(descent)
+ // for all the fonts in the font list.
+ int GetHeight() const;
+
+ // Returns the baseline of this font list, which is max(baseline) for all the
+ // fonts in the font list.
+ int GetBaseline() const;
+
+ // Returns the number of horizontal pixels needed to display |text|.
+ int GetStringWidth(const base::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 |gfx::Font::FontStyle| style flags for this font list.
+ int GetFontStyle() const;
+
+ // Returns a string representing font names, styles, and size. If the FontList
+ // is initialized by a vector of Font, use the first font's style and size
+ // for the description.
+ const std::string& GetFontDescriptionString() const;
+
+ // Returns the font size in pixels.
+ int GetFontSize() const;
+
+ // Returns the Font vector.
+ const std::vector<Font>& GetFonts() const;
+
+ // Returns the first font in the list.
+ const Font& GetPrimaryFont() const;
+
+ private:
+ // Extracts common font height and baseline into |common_height_| and
+ // |common_baseline_|.
+ void CacheCommonFontHeightAndBaseline() const;
+
+ // Extracts font style and size into |font_style_| and |font_size_|.
+ void CacheFontStyleAndSize() const;
+
+ // A vector of Font. If FontList is constructed with font description string,
+ // |fonts_| is not initialized during construction. Instead, it is computed
+ // lazily when user asked to get the font vector.
+ mutable std::vector<Font> fonts_;
+
+ // A string representing font names, styles, and sizes.
+ // Please refer to the comments before class declaration for details on string
+ // format.
+ // If FontList is constructed with a vector of font,
+ // |font_description_string_| is not initialized during construction. Instead,
+ // it is computed lazily when user asked to get the font description string.
+ mutable std::string font_description_string_;
+
+ // The cached common height and baseline of the fonts in the font list.
+ mutable int common_height_;
+ mutable int common_baseline_;
+
+ // Cached font style and size.
+ mutable int font_style_;
+ mutable int font_size_;
+};
+
+} // namespace gfx
+
+#endif // UI_GFX_FONT_LIST_H_
diff --git a/chromium/ui/gfx/font_list_unittest.cc b/chromium/ui/gfx/font_list_unittest.cc
new file mode 100644
index 00000000000..a62a13db469
--- /dev/null
+++ b/chromium/ui/gfx/font_list_unittest.cc
@@ -0,0 +1,318 @@
+// Copyright (c) 2012 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 "ui/gfx/font_list.h"
+
+#include <algorithm>
+#include <string>
+#include <vector>
+
+#include "base/strings/string_number_conversions.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace {
+
+// Helper function for comparing fonts for equality.
+std::string FontToString(const gfx::Font& font) {
+ std::string font_string = font.GetFontName();
+ font_string += "|";
+ font_string += base::IntToString(font.GetFontSize());
+ int style = font.GetStyle();
+ if (style & gfx::Font::BOLD)
+ font_string += "|bold";
+ if (style & gfx::Font::ITALIC)
+ font_string += "|italic";
+ return font_string;
+}
+
+} // namespace
+
+namespace gfx {
+
+TEST(FontListTest, FontDescString_FromDescString) {
+ // Test init from font name style size string.
+ FontList font_list = FontList("Droid Sans serif, Sans serif, 10px");
+ EXPECT_EQ("Droid Sans serif, Sans serif, 10px",
+ font_list.GetFontDescriptionString());
+}
+
+TEST(FontListTest, FontDescString_FromFontNamesStyleAndSize) {
+ // Test init from font names, style and size.
+ std::vector<std::string> font_names;
+ font_names.push_back("Arial");
+ font_names.push_back("Droid Sans serif");
+ int font_style = Font::BOLD | Font::ITALIC;
+ int font_size = 11;
+ FontList font_list = FontList(font_names, font_style, font_size);
+ EXPECT_EQ("Arial,Droid Sans serif,Bold Italic 11px",
+ font_list.GetFontDescriptionString());
+}
+
+TEST(FontListTest, FontDescString_FromFont) {
+ // Test init from Font.
+ Font font("Arial", 8);
+ FontList font_list = FontList(font);
+ EXPECT_EQ("Arial,8px", font_list.GetFontDescriptionString());
+}
+
+TEST(FontListTest, FontDescString_FromFontWithNonNormalStyle) {
+ // Test init from Font with non-normal style.
+ Font font("Arial", 8);
+ FontList font_list = FontList(font.DeriveFont(2, Font::BOLD));
+ EXPECT_EQ("Arial,Bold 10px", font_list.GetFontDescriptionString());
+
+ font_list = FontList(font.DeriveFont(-2, Font::ITALIC));
+ EXPECT_EQ("Arial,Italic 6px", font_list.GetFontDescriptionString());
+}
+
+TEST(FontListTest, FontDescString_FromFontVector) {
+ // Test init from Font vector.
+ Font font("Arial", 8);
+ Font font_1("Sans serif", 10);
+ std::vector<Font> fonts;
+ fonts.push_back(font.DeriveFont(0, Font::BOLD));
+ fonts.push_back(font_1.DeriveFont(-2, Font::BOLD));
+ FontList font_list = FontList(fonts);
+ EXPECT_EQ("Arial,Sans serif,Bold 8px", font_list.GetFontDescriptionString());
+}
+
+TEST(FontListTest, Fonts_FromDescString) {
+ // Test init from font name size string.
+ FontList font_list = FontList("serif,Sans serif, 13px");
+ const std::vector<Font>& fonts = font_list.GetFonts();
+ EXPECT_EQ(2U, fonts.size());
+ EXPECT_EQ("serif|13", FontToString(fonts[0]));
+ EXPECT_EQ("Sans serif|13", FontToString(fonts[1]));
+}
+
+TEST(FontListTest, Fonts_FromDescStringInFlexibleFormat) {
+ // Test init from font name size string with flexible format.
+ FontList font_list = FontList(" serif , Sans serif , 13px");
+ const std::vector<Font>& fonts = font_list.GetFonts();
+ EXPECT_EQ(2U, fonts.size());
+ EXPECT_EQ("serif|13", FontToString(fonts[0]));
+ EXPECT_EQ("Sans serif|13", FontToString(fonts[1]));
+}
+
+TEST(FontListTest, Fonts_FromDescStringWithStyleInFlexibleFormat) {
+ // Test init from font name style size string with flexible format.
+ FontList font_list = FontList(" serif , Sans serif , Bold "
+ " Italic 13px");
+ const std::vector<Font>& fonts = font_list.GetFonts();
+ EXPECT_EQ(2U, fonts.size());
+ EXPECT_EQ("serif|13|bold|italic", FontToString(fonts[0]));
+ EXPECT_EQ("Sans serif|13|bold|italic", FontToString(fonts[1]));
+}
+
+TEST(FontListTest, Fonts_FromFont) {
+ // Test init from Font.
+ Font font("Arial", 8);
+ FontList font_list = FontList(font);
+ const std::vector<Font>& fonts = font_list.GetFonts();
+ EXPECT_EQ(1U, fonts.size());
+ EXPECT_EQ("Arial|8", FontToString(fonts[0]));
+}
+
+TEST(FontListTest, Fonts_FromFontWithNonNormalStyle) {
+ // Test init from Font with non-normal style.
+ Font font("Arial", 8);
+ FontList font_list = FontList(font.DeriveFont(2, Font::BOLD));
+ std::vector<Font> fonts = font_list.GetFonts();
+ EXPECT_EQ(1U, fonts.size());
+ EXPECT_EQ("Arial|10|bold", FontToString(fonts[0]));
+
+ font_list = FontList(font.DeriveFont(-2, Font::ITALIC));
+ fonts = font_list.GetFonts();
+ EXPECT_EQ(1U, fonts.size());
+ EXPECT_EQ("Arial|6|italic", FontToString(fonts[0]));
+}
+
+TEST(FontListTest, Fonts_FromFontVector) {
+ // Test init from Font vector.
+ Font font("Arial", 8);
+ Font font_1("Sans serif", 10);
+ std::vector<Font> input_fonts;
+ input_fonts.push_back(font.DeriveFont(0, Font::BOLD));
+ input_fonts.push_back(font_1.DeriveFont(-2, Font::BOLD));
+ FontList font_list = FontList(input_fonts);
+ const std::vector<Font>& fonts = font_list.GetFonts();
+ EXPECT_EQ(2U, fonts.size());
+ EXPECT_EQ("Arial|8|bold", FontToString(fonts[0]));
+ EXPECT_EQ("Sans serif|8|bold", FontToString(fonts[1]));
+}
+
+TEST(FontListTest, Fonts_DescStringWithStyleInFlexibleFormat_RoundTrip) {
+ // Test round trip from font description string to font vector to
+ // font description string.
+ FontList font_list = FontList(" serif , Sans serif , Bold "
+ " Italic 13px");
+
+ const std::vector<Font>& fonts = font_list.GetFonts();
+ FontList font_list_1 = FontList(fonts);
+ const std::string& desc_str = font_list_1.GetFontDescriptionString();
+
+ EXPECT_EQ("serif,Sans serif,Bold Italic 13px", desc_str);
+}
+
+TEST(FontListTest, Fonts_FontVector_RoundTrip) {
+ // Test round trip from font vector to font description string to font vector.
+ Font font("Arial", 8);
+ Font font_1("Sans serif", 10);
+ std::vector<Font> input_fonts;
+ input_fonts.push_back(font.DeriveFont(0, Font::BOLD));
+ input_fonts.push_back(font_1.DeriveFont(-2, Font::BOLD));
+ FontList font_list = FontList(input_fonts);
+
+ const std::string& desc_string = font_list.GetFontDescriptionString();
+ FontList font_list_1 = FontList(desc_string);
+ const std::vector<Font>& round_trip_fonts = font_list_1.GetFonts();
+
+ EXPECT_EQ(2U, round_trip_fonts.size());
+ EXPECT_EQ("Arial|8|bold", FontToString(round_trip_fonts[0]));
+ EXPECT_EQ("Sans serif|8|bold", FontToString(round_trip_fonts[1]));
+}
+
+TEST(FontListTest, FontDescString_GetStyle) {
+ FontList font_list = FontList("Arial,Sans serif, 8px");
+ EXPECT_EQ(Font::NORMAL, font_list.GetFontStyle());
+
+ font_list = FontList("Arial,Sans serif,Bold 8px");
+ EXPECT_EQ(Font::BOLD, font_list.GetFontStyle());
+
+ font_list = FontList("Arial,Sans serif,Italic 8px");
+ EXPECT_EQ(Font::ITALIC, font_list.GetFontStyle());
+
+ font_list = FontList("Arial,Italic Bold 8px");
+ EXPECT_EQ(Font::BOLD | Font::ITALIC, font_list.GetFontStyle());
+}
+
+TEST(FontListTest, Fonts_GetStyle) {
+ std::vector<Font> fonts;
+ fonts.push_back(gfx::Font("Arial", 8));
+ fonts.push_back(gfx::Font("Sans serif", 8));
+ FontList font_list = FontList(fonts);
+ EXPECT_EQ(Font::NORMAL, font_list.GetFontStyle());
+ fonts[0] = fonts[0].DeriveFont(0, Font::ITALIC | Font::BOLD);
+ fonts[1] = fonts[1].DeriveFont(0, Font::ITALIC | Font::BOLD);
+ font_list = FontList(fonts);
+ EXPECT_EQ(Font::ITALIC | Font::BOLD, font_list.GetFontStyle());
+}
+
+TEST(FontListTest, FontDescString_DeriveFontList) {
+ FontList font_list = FontList("Arial,Sans serif, 8px");
+
+ FontList derived = font_list.DeriveFontList(Font::BOLD | Font::ITALIC);
+ EXPECT_EQ("Arial,Sans serif,Bold Italic 8px",
+ derived.GetFontDescriptionString());
+}
+
+TEST(FontListTest, Fonts_DeriveFontList) {
+ std::vector<Font> fonts;
+ fonts.push_back(gfx::Font("Arial", 8));
+ fonts.push_back(gfx::Font("Sans serif", 8));
+ FontList font_list = FontList(fonts);
+
+ FontList derived = font_list.DeriveFontList(Font::BOLD | Font::ITALIC);
+ const std::vector<Font>& derived_fonts = derived.GetFonts();
+
+ EXPECT_EQ(2U, derived_fonts.size());
+ EXPECT_EQ("Arial|8|bold|italic", FontToString(derived_fonts[0]));
+ EXPECT_EQ("Sans serif|8|bold|italic", FontToString(derived_fonts[1]));
+}
+
+TEST(FontListTest, FontDescString_DeriveFontListWithSize) {
+ FontList font_list = FontList("Arial,Sans serif,Bold Italic 8px");
+
+ FontList derived = font_list.DeriveFontListWithSize(10);
+ EXPECT_EQ("Arial,Sans serif,Bold Italic 10px",
+ derived.GetFontDescriptionString());
+}
+
+TEST(FontListTest, Fonts_DeriveFontListWithSize) {
+ std::vector<Font> fonts;
+ fonts.push_back(gfx::Font("Arial", 8));
+ fonts.push_back(gfx::Font("Sans serif", 8));
+ FontList font_list = FontList(fonts);
+
+ FontList derived = font_list.DeriveFontListWithSize(5);
+ const std::vector<Font>& derived_fonts = derived.GetFonts();
+
+ EXPECT_EQ(2U, derived_fonts.size());
+ EXPECT_EQ("Arial|5", FontToString(derived_fonts[0]));
+ EXPECT_EQ("Sans serif|5", FontToString(derived_fonts[1]));
+}
+
+TEST(FontListTest, FontDescString_DeriveFontListWithSizeDelta) {
+ FontList font_list = FontList("Arial,Sans serif,Bold 18px");
+
+ FontList derived = font_list.DeriveFontListWithSizeDelta(-8);
+ EXPECT_EQ("Arial,Sans serif,Bold 10px",
+ derived.GetFontDescriptionString());
+}
+
+TEST(FontListTest, Fonts_DeriveFontListWithSizeDelta) {
+ std::vector<Font> fonts;
+ fonts.push_back(gfx::Font("Arial", 18).DeriveFont(0, Font::ITALIC));
+ fonts.push_back(gfx::Font("Sans serif", 18).DeriveFont(0, Font::ITALIC));
+ FontList font_list = FontList(fonts);
+
+ FontList derived = font_list.DeriveFontListWithSizeDelta(-5);
+ const std::vector<Font>& derived_fonts = derived.GetFonts();
+
+ EXPECT_EQ(2U, derived_fonts.size());
+ EXPECT_EQ("Arial|13|italic", FontToString(derived_fonts[0]));
+ EXPECT_EQ("Sans serif|13|italic", FontToString(derived_fonts[1]));
+}
+
+TEST(FontListTest, FontDescString_DeriveFontListWithSizeDeltaAndStyle) {
+ FontList font_list = FontList("Arial,Sans serif,Bold Italic 8px");
+
+ FontList derived =
+ font_list.DeriveFontListWithSizeDeltaAndStyle(10, Font::ITALIC);
+ EXPECT_EQ("Arial,Sans serif,Italic 18px",
+ derived.GetFontDescriptionString());
+}
+
+TEST(FontListTest, Fonts_DeriveFontListWithSizeDeltaAndStyle) {
+ std::vector<Font> fonts;
+ fonts.push_back(gfx::Font("Arial", 8));
+ fonts.push_back(gfx::Font("Sans serif", 8));
+ FontList font_list = FontList(fonts);
+
+ FontList derived =
+ font_list.DeriveFontListWithSizeDeltaAndStyle(5, Font::BOLD);
+ const std::vector<Font>& derived_fonts = derived.GetFonts();
+
+ EXPECT_EQ(2U, derived_fonts.size());
+ EXPECT_EQ("Arial|13|bold", FontToString(derived_fonts[0]));
+ EXPECT_EQ("Sans serif|13|bold", FontToString(derived_fonts[1]));
+}
+
+TEST(FontListTest, Fonts_GetHeight_GetBaseline) {
+ // If a font list has only one font, the height and baseline must be the same.
+ Font font1("Arial", 16);
+ FontList font_list1("Arial, 16px");
+ EXPECT_EQ(font1.GetHeight(), font_list1.GetHeight());
+ EXPECT_EQ(font1.GetBaseline(), font_list1.GetBaseline());
+
+ // If there are two different fonts, the font list returns the max value
+ // for ascent and descent.
+ Font font2("Symbol", 16);
+ EXPECT_NE(font1.GetBaseline(), font2.GetBaseline());
+ EXPECT_NE(font1.GetHeight() - font1.GetBaseline(),
+ font2.GetHeight() - font2.GetBaseline());
+ std::vector<Font> fonts;
+ fonts.push_back(font1);
+ fonts.push_back(font2);
+ FontList font_list_mix(fonts);
+ // ascent of FontList == max(ascent of Fonts)
+ EXPECT_EQ(std::max(font1.GetHeight() - font1.GetBaseline(),
+ font2.GetHeight() - font2.GetBaseline()),
+ font_list_mix.GetHeight() - font_list_mix.GetBaseline());
+ // descent of FontList == max(descent of Fonts)
+ EXPECT_EQ(std::max(font1.GetBaseline(), font2.GetBaseline()),
+ font_list_mix.GetBaseline());
+}
+
+} // namespace gfx
diff --git a/chromium/ui/gfx/font_render_params_android.cc b/chromium/ui/gfx/font_render_params_android.cc
new file mode 100644
index 00000000000..3dfd6688d29
--- /dev/null
+++ b/chromium/ui/gfx/font_render_params_android.cc
@@ -0,0 +1,40 @@
+// Copyright (c) 2012 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 "ui/gfx/font_render_params_linux.h"
+
+namespace gfx {
+
+namespace {
+
+// Initializes |params| with the system's default settings.
+void LoadDefaults(FontRenderParams* params) {
+ params->antialiasing = true;
+ params->autohinter = true;
+ params->use_bitmaps = true;
+ params->subpixel_rendering = FontRenderParams::SUBPIXEL_RENDERING_NONE;
+
+ // Use subpixel text positioning to keep consistent character spacing when
+ // the page is scaled by a fractional factor.
+ params->subpixel_positioning = true;
+ // Slight hinting renders much better than normal hinting on Android.
+ params->hinting = FontRenderParams::HINTING_SLIGHT;
+}
+
+} // namespace
+
+const FontRenderParams& GetDefaultFontRenderParams() {
+ static bool loaded_defaults = false;
+ static FontRenderParams default_params;
+ if (!loaded_defaults)
+ LoadDefaults(&default_params);
+ loaded_defaults = true;
+ return default_params;
+}
+
+const FontRenderParams& GetDefaultWebKitFontRenderParams() {
+ return GetDefaultFontRenderParams();
+}
+
+} // namespace gfx
diff --git a/chromium/ui/gfx/font_render_params_linux.cc b/chromium/ui/gfx/font_render_params_linux.cc
new file mode 100644
index 00000000000..2fb369a5d4c
--- /dev/null
+++ b/chromium/ui/gfx/font_render_params_linux.cc
@@ -0,0 +1,150 @@
+// Copyright (c) 2012 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 "ui/gfx/font_render_params_linux.h"
+
+#include "base/command_line.h"
+#include "base/logging.h"
+#include "ui/gfx/switches.h"
+
+#if defined(TOOLKIT_GTK)
+#include <gtk/gtk.h>
+#else
+#include <fontconfig/fontconfig.h>
+#endif
+
+namespace gfx {
+
+namespace {
+
+bool SubpixelPositioningRequested(bool renderer) {
+ return CommandLine::ForCurrentProcess()->HasSwitch(
+ renderer ?
+ switches::kEnableWebkitTextSubpixelPositioning :
+ switches::kEnableBrowserTextSubpixelPositioning);
+}
+
+// Initializes |params| with the system's default settings. |renderer| is true
+// when setting WebKit renderer defaults.
+void LoadDefaults(FontRenderParams* params, bool renderer) {
+#if defined(TOOLKIT_GTK)
+ params->antialiasing = true;
+ // TODO(wangxianzhu): autohinter is now true to keep original behavior
+ // of WebKit, but it might not be the best value.
+ params->autohinter = true;
+ params->use_bitmaps = true;
+ params->hinting = FontRenderParams::HINTING_SLIGHT;
+ params->subpixel_rendering = FontRenderParams::SUBPIXEL_RENDERING_NONE;
+
+ GtkSettings* gtk_settings = gtk_settings_get_default();
+ CHECK(gtk_settings);
+ gint gtk_antialias = 0;
+ gint gtk_hinting = 0;
+ gchar* gtk_hint_style = NULL;
+ gchar* gtk_rgba = NULL;
+ g_object_get(gtk_settings,
+ "gtk-xft-antialias", &gtk_antialias,
+ "gtk-xft-hinting", &gtk_hinting,
+ "gtk-xft-hintstyle", &gtk_hint_style,
+ "gtk-xft-rgba", &gtk_rgba,
+ 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 (gtk_hint_style && gtk_rgba) {
+ params->antialiasing = gtk_antialias;
+
+ if (gtk_hinting == 0 || strcmp(gtk_hint_style, "hintnone") == 0)
+ params->hinting = FontRenderParams::HINTING_NONE;
+ else if (strcmp(gtk_hint_style, "hintslight") == 0)
+ params->hinting = FontRenderParams::HINTING_SLIGHT;
+ else if (strcmp(gtk_hint_style, "hintmedium") == 0)
+ params->hinting = FontRenderParams::HINTING_MEDIUM;
+ else if (strcmp(gtk_hint_style, "hintfull") == 0)
+ params->hinting = FontRenderParams::HINTING_FULL;
+
+ if (strcmp(gtk_rgba, "none") == 0)
+ params->subpixel_rendering = FontRenderParams::SUBPIXEL_RENDERING_NONE;
+ else if (strcmp(gtk_rgba, "rgb") == 0)
+ params->subpixel_rendering = FontRenderParams::SUBPIXEL_RENDERING_RGB;
+ else if (strcmp(gtk_rgba, "bgr") == 0)
+ params->subpixel_rendering = FontRenderParams::SUBPIXEL_RENDERING_BGR;
+ else if (strcmp(gtk_rgba, "vrgb") == 0)
+ params->subpixel_rendering = FontRenderParams::SUBPIXEL_RENDERING_VRGB;
+ else if (strcmp(gtk_rgba, "vbgr") == 0)
+ params->subpixel_rendering = FontRenderParams::SUBPIXEL_RENDERING_VBGR;
+ }
+
+ g_free(gtk_hint_style);
+ g_free(gtk_rgba);
+#else
+ // For non-GTK builds (read: Aura), just use reasonable hardcoded values.
+ params->antialiasing = true;
+ params->autohinter = true;
+ params->use_bitmaps = true;
+ params->hinting = FontRenderParams::HINTING_SLIGHT;
+
+ // Fetch default subpixel rendering settings from FontConfig.
+ FcPattern* pattern = FcPatternCreate();
+ FcConfigSubstitute(NULL, pattern, FcMatchPattern);
+ FcDefaultSubstitute(pattern);
+ FcResult result;
+ FcPattern* match = FcFontMatch(0, pattern, &result);
+ DCHECK(match);
+ int fc_rgba = FC_RGBA_RGB;
+ FcPatternGetInteger(match, FC_RGBA, 0, &fc_rgba);
+ FcPatternDestroy(pattern);
+ FcPatternDestroy(match);
+
+ switch (fc_rgba) {
+ case FC_RGBA_RGB:
+ params->subpixel_rendering = FontRenderParams::SUBPIXEL_RENDERING_RGB;
+ break;
+ case FC_RGBA_BGR:
+ params->subpixel_rendering = FontRenderParams::SUBPIXEL_RENDERING_BGR;
+ break;
+ case FC_RGBA_VRGB:
+ params->subpixel_rendering = FontRenderParams::SUBPIXEL_RENDERING_VRGB;
+ break;
+ case FC_RGBA_VBGR:
+ params->subpixel_rendering = FontRenderParams::SUBPIXEL_RENDERING_VBGR;
+ break;
+ default:
+ params->subpixel_rendering = FontRenderParams::SUBPIXEL_RENDERING_NONE;
+ }
+#endif
+
+ params->subpixel_positioning = SubpixelPositioningRequested(renderer);
+
+ // To enable subpixel positioning, we need to disable hinting.
+ if (params->subpixel_positioning)
+ params->hinting = FontRenderParams::HINTING_NONE;
+}
+
+} // namespace
+
+const FontRenderParams& GetDefaultFontRenderParams() {
+ static bool loaded_defaults = false;
+ static FontRenderParams default_params;
+ if (!loaded_defaults)
+ LoadDefaults(&default_params, /* renderer */ false);
+ loaded_defaults = true;
+ return default_params;
+}
+
+const FontRenderParams& GetDefaultWebKitFontRenderParams() {
+ static bool loaded_defaults = false;
+ static FontRenderParams default_params;
+ if (!loaded_defaults)
+ LoadDefaults(&default_params, /* renderer */ true);
+ loaded_defaults = true;
+ return default_params;
+}
+
+bool GetDefaultWebkitSubpixelPositioning() {
+ return SubpixelPositioningRequested(true);
+}
+
+} // namespace gfx
diff --git a/chromium/ui/gfx/font_render_params_linux.h b/chromium/ui/gfx/font_render_params_linux.h
new file mode 100644
index 00000000000..ec39302dccc
--- /dev/null
+++ b/chromium/ui/gfx/font_render_params_linux.h
@@ -0,0 +1,70 @@
+// Copyright (c) 2012 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_RENDER_PARAMS_LINUX_H_
+#define UI_GFX_FONT_RENDER_PARAMS_LINUX_H_
+
+#include "ui/base/ui_export.h"
+
+namespace gfx {
+
+// A collection of parameters describing how text should be rendered on Linux.
+struct UI_EXPORT FontRenderParams {
+ // No constructor to avoid static initialization.
+
+ // Level of hinting to be applied.
+ enum Hinting {
+ HINTING_NONE = 0,
+ HINTING_SLIGHT,
+ HINTING_MEDIUM,
+ HINTING_FULL,
+ };
+
+ // Different subpixel orders to be used for subpixel rendering.
+ enum SubpixelRendering {
+ SUBPIXEL_RENDERING_NONE = 0,
+ SUBPIXEL_RENDERING_RGB,
+ SUBPIXEL_RENDERING_BGR,
+ SUBPIXEL_RENDERING_VRGB,
+ SUBPIXEL_RENDERING_VBGR,
+ };
+
+ // Antialiasing (grayscale if |subpixel_rendering| is SUBPIXEL_RENDERING_NONE
+ // and RGBA otherwise).
+ bool antialiasing;
+
+ // Should subpixel positioning (i.e. fractional X positions for glyphs) be
+ // used?
+ bool subpixel_positioning;
+
+ // Should FreeType's autohinter be used (as opposed to Freetype's bytecode
+ // interpreter, which uses fonts' own hinting instructions)?
+ bool autohinter;
+
+ // Should embedded bitmaps in fonts should be used?
+ bool use_bitmaps;
+
+ // Hinting level.
+ Hinting hinting;
+
+ // Whether subpixel rendering should be used or not, and if so, the display's
+ // subpixel order.
+ SubpixelRendering subpixel_rendering;
+};
+
+// Returns the system's default parameters for font rendering.
+UI_EXPORT const FontRenderParams& GetDefaultFontRenderParams();
+
+// Returns the system's default parameters for WebKit font rendering.
+UI_EXPORT const FontRenderParams& GetDefaultWebKitFontRenderParams();
+
+// Returns the system's default parameters for WebKit subpixel positioning.
+// Subpixel positioning is special since neither GTK nor FontConfig currently
+// track it as a preference.
+// See https://bugs.freedesktop.org/show_bug.cgi?id=50736
+UI_EXPORT bool GetDefaultWebkitSubpixelPositioning();
+
+} // namespace gfx
+
+#endif // UI_GFX_FONT_RENDER_PARAMS_LINUX_H_
diff --git a/chromium/ui/gfx/font_smoothing_win.cc b/chromium/ui/gfx/font_smoothing_win.cc
new file mode 100644
index 00000000000..a4be72930a4
--- /dev/null
+++ b/chromium/ui/gfx/font_smoothing_win.cc
@@ -0,0 +1,121 @@
+// Copyright (c) 2012 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 "ui/gfx/font_smoothing_win.h"
+
+#include "base/memory/singleton.h"
+#include "ui/base/win/singleton_hwnd.h"
+
+namespace {
+
+// Helper class to cache font smoothing settings and listen for notifications
+// to re-query them from the system.
+class CachedFontSmoothingSettings : public ui::SingletonHwnd::Observer {
+ public:
+ static CachedFontSmoothingSettings* GetInstance();
+
+ // Returns the cached Windows font smoothing settings. Queries the settings
+ // via Windows APIs and begins listening for changes when called for the
+ // first time.
+ void GetFontSmoothingSettings(bool* smoothing_enabled,
+ bool* cleartype_enabled);
+
+ private:
+ friend struct DefaultSingletonTraits<CachedFontSmoothingSettings>;
+
+ CachedFontSmoothingSettings();
+ virtual ~CachedFontSmoothingSettings();
+
+ // Listener for WM_SETTINGCHANGE notifications.
+ virtual void OnWndProc(HWND hwnd,
+ UINT message,
+ WPARAM wparam,
+ LPARAM lparam) OVERRIDE;
+
+ // Queries the font settings from the system.
+ void QueryFontSettings();
+
+ // Indicates whether the MessagePumpObserver has been registered.
+ bool observer_added_;
+
+ // Indicates whether |smoothing_enabled_| and |cleartype_enabled_| are valid
+ // or need to be re-queried from the system.
+ bool need_to_query_settings_;
+
+ // Indicates that font smoothing is enabled.
+ bool smoothing_enabled_;
+
+ // Indicates that the ClearType font smoothing is enabled.
+ bool cleartype_enabled_;
+
+ DISALLOW_COPY_AND_ASSIGN(CachedFontSmoothingSettings);
+};
+
+// static
+CachedFontSmoothingSettings* CachedFontSmoothingSettings::GetInstance() {
+ return Singleton<CachedFontSmoothingSettings>::get();
+}
+
+void CachedFontSmoothingSettings::GetFontSmoothingSettings(
+ bool* smoothing_enabled,
+ bool* cleartype_enabled) {
+ // If cached settings are stale, query them from the OS.
+ if (need_to_query_settings_) {
+ QueryFontSettings();
+ need_to_query_settings_ = false;
+ }
+ if (!observer_added_) {
+ ui::SingletonHwnd::GetInstance()->AddObserver(this);
+ observer_added_ = true;
+ }
+ *smoothing_enabled = smoothing_enabled_;
+ *cleartype_enabled = cleartype_enabled_;
+}
+
+CachedFontSmoothingSettings::CachedFontSmoothingSettings()
+ : observer_added_(false),
+ need_to_query_settings_(true),
+ smoothing_enabled_(false),
+ cleartype_enabled_(false) {
+}
+
+CachedFontSmoothingSettings::~CachedFontSmoothingSettings() {
+ // Can't remove the SingletonHwnd observer here since SingletonHwnd may have
+ // been destroyed already (both singletons).
+}
+
+void CachedFontSmoothingSettings::OnWndProc(HWND hwnd,
+ UINT message,
+ WPARAM wparam,
+ LPARAM lparam) {
+ if (message == WM_SETTINGCHANGE)
+ need_to_query_settings_ = true;
+}
+
+void CachedFontSmoothingSettings::QueryFontSettings() {
+ smoothing_enabled_ = false;
+ cleartype_enabled_ = false;
+
+ BOOL enabled = false;
+ if (SystemParametersInfo(SPI_GETFONTSMOOTHING, 0, &enabled, 0) && enabled) {
+ smoothing_enabled_ = true;
+
+ UINT smooth_type = 0;
+ if (SystemParametersInfo(SPI_GETFONTSMOOTHINGTYPE, 0, &smooth_type, 0))
+ cleartype_enabled_ = (smooth_type == FE_FONTSMOOTHINGCLEARTYPE);
+ }
+}
+
+} // namespace
+
+namespace gfx {
+
+void GetCachedFontSmoothingSettings(bool* smoothing_enabled,
+ bool* cleartype_enabled) {
+ CachedFontSmoothingSettings::GetInstance()->GetFontSmoothingSettings(
+ smoothing_enabled,
+ cleartype_enabled);
+}
+
+} // namespace gfx
diff --git a/chromium/ui/gfx/font_smoothing_win.h b/chromium/ui/gfx/font_smoothing_win.h
new file mode 100644
index 00000000000..25c16e15b87
--- /dev/null
+++ b/chromium/ui/gfx/font_smoothing_win.h
@@ -0,0 +1,16 @@
+// Copyright (c) 2012 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_SMOOTHING_WIN_H_
+#define UI_GFX_FONT_SMOOTHING_WIN_H_
+
+namespace gfx {
+
+// Returns the Windows system font smoothing and ClearType settings.
+void GetCachedFontSmoothingSettings(bool* smoothing_enabled,
+ bool* cleartype_enabled);
+
+} // namespace gfx
+
+#endif // UI_GFX_FONT_SMOOTHING_WIN_H_
diff --git a/chromium/ui/gfx/font_unittest.cc b/chromium/ui/gfx/font_unittest.cc
new file mode 100644
index 00000000000..6b5cb798657
--- /dev/null
+++ b/chromium/ui/gfx/font_unittest.cc
@@ -0,0 +1,136 @@
+// Copyright (c) 2012 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 "ui/gfx/font.h"
+
+#include "base/strings/string16.h"
+#include "base/strings/utf_string_conversions.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+#if defined(OS_POSIX) && !defined(OS_MACOSX) && !defined(OS_ANDROID)
+#include <pango/pango.h>
+#elif defined(OS_WIN)
+#include "ui/gfx/platform_font_win.h"
+#endif
+
+namespace gfx {
+namespace {
+
+class FontTest : public testing::Test {
+ public:
+ // Fulfills the memory management contract as outlined by the comment at
+ // gfx::Font::GetNativeFont().
+ void FreeIfNecessary(NativeFont font) {
+#if defined(OS_POSIX) && !defined(OS_MACOSX) && !defined(OS_ANDROID)
+ pango_font_description_free(font);
+#endif
+ }
+};
+
+#if defined(OS_WIN)
+class ScopedMinimumFontSizeCallback {
+ public:
+ explicit ScopedMinimumFontSizeCallback(int minimum_size) {
+ minimum_size_ = minimum_size;
+ old_callback_ = PlatformFontWin::get_minimum_font_size_callback;
+ PlatformFontWin::get_minimum_font_size_callback = &GetMinimumFontSize;
+ }
+
+ ~ScopedMinimumFontSizeCallback() {
+ PlatformFontWin::get_minimum_font_size_callback = old_callback_;
+ }
+
+ private:
+ static int GetMinimumFontSize() {
+ return minimum_size_;
+ }
+
+ 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("Arial", 16);
+ NativeFont native = cf.GetNativeFont();
+ ASSERT_TRUE(native);
+ ASSERT_EQ(cf.GetStyle(), Font::NORMAL);
+ ASSERT_EQ(cf.GetFontSize(), 16);
+ ASSERT_EQ(cf.GetFontName(), "Arial");
+ FreeIfNecessary(native);
+}
+
+TEST_F(FontTest, LoadArialBold) {
+ Font cf("Arial", 16);
+ Font bold(cf.DeriveFont(0, Font::BOLD));
+ NativeFont native = bold.GetNativeFont();
+ ASSERT_TRUE(native);
+ ASSERT_EQ(bold.GetStyle(), Font::BOLD);
+ FreeIfNecessary(native);
+}
+
+TEST_F(FontTest, Ascent) {
+ Font cf("Arial", 16);
+ ASSERT_GT(cf.GetBaseline(), 2);
+ ASSERT_LE(cf.GetBaseline(), 22);
+}
+
+TEST_F(FontTest, Height) {
+ Font cf("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("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, AvgCharWidth) {
+ Font cf("Arial", 16);
+ ASSERT_GT(cf.GetAverageCharacterWidth(), 0);
+}
+
+TEST_F(FontTest, Widths) {
+ Font cf("Arial", 16);
+ ASSERT_EQ(cf.GetStringWidth(base::string16()), 0);
+ ASSERT_GT(cf.GetStringWidth(ASCIIToUTF16("a")),
+ cf.GetStringWidth(base::string16()));
+ 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) {
+ Font cf("Arial", 8);
+ // The minimum font size is set to 5 in browser_main.cc.
+ ScopedMinimumFontSizeCallback minimum_size(5);
+
+ Font derived_font = cf.DeriveFont(-4);
+ EXPECT_EQ(5, derived_font.GetFontSize());
+}
+
+TEST_F(FontTest, DeriveFontKeepsOriginalSizeIfHeightOk) {
+ Font cf("Arial", 8);
+ // The minimum font size is set to 5 in browser_main.cc.
+ ScopedMinimumFontSizeCallback minimum_size(5);
+
+ Font derived_font = cf.DeriveFont(-2);
+ EXPECT_EQ(6, derived_font.GetFontSize());
+}
+#endif // defined(OS_WIN)
+
+} // namespace
+} // namespace gfx
diff --git a/chromium/ui/gfx/gdi_util.cc b/chromium/ui/gfx/gdi_util.cc
new file mode 100644
index 00000000000..88c09355a28
--- /dev/null
+++ b/chromium/ui/gfx/gdi_util.cc
@@ -0,0 +1,145 @@
+// Copyright (c) 2012 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 "ui/gfx/gdi_util.h"
+
+#include "base/logging.h"
+#include "base/memory/scoped_ptr.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);
+ }
+}
+
+HRGN ConvertPathToHRGN(const gfx::Path& path) {
+#if defined(USE_AURA)
+ int point_count = path.getPoints(NULL, 0);
+ scoped_ptr<SkPoint[]> points(new SkPoint[point_count]);
+ path.getPoints(points.get(), point_count);
+ scoped_ptr<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);
+#elif defined(OS_WIN)
+ return path.CreateNativeRegion();
+#endif
+}
+
+
+double CalculatePageScale(HDC dc, int page_width, int page_height) {
+ int dc_width = GetDeviceCaps(dc, HORZRES);
+ int dc_height = GetDeviceCaps(dc, VERTRES);
+
+ // If page fits DC - no scaling needed.
+ if (dc_width >= page_width && dc_height >= page_height)
+ return 1.0;
+
+ double x_factor =
+ static_cast<double>(dc_width) / static_cast<double>(page_width);
+ double y_factor =
+ static_cast<double>(dc_height) / static_cast<double>(page_height);
+ return std::min(x_factor, y_factor);
+}
+
+// Apply scaling to the DC.
+bool ScaleDC(HDC dc, double scale_factor) {
+ SetGraphicsMode(dc, GM_ADVANCED);
+ XFORM xform = {0};
+ xform.eM11 = xform.eM22 = scale_factor;
+ return !!ModifyWorldTransform(dc, &xform, MWT_LEFTMULTIPLY);
+}
+
+void StretchDIBits(HDC hdc, int dest_x, int dest_y, int dest_w, int dest_h,
+ int src_x, int src_y, int src_w, int src_h, void* pixels,
+ const BITMAPINFO* bitmap_info) {
+ // When blitting a rectangle that touches the bottom, left corner of the
+ // bitmap, StretchDIBits looks at it top-down! For more details, see
+ // http://wiki.allegro.cc/index.php?title=StretchDIBits.
+ int rv;
+ int bitmap_h = -bitmap_info->bmiHeader.biHeight;
+ int bottom_up_src_y = bitmap_h - src_y - src_h;
+ if (bottom_up_src_y == 0 && src_x == 0 && src_h != bitmap_h) {
+ rv = ::StretchDIBits(hdc,
+ dest_x, dest_h + dest_y - 1, dest_w, -dest_h,
+ src_x, bitmap_h - src_y + 1, src_w, -src_h,
+ pixels, bitmap_info, DIB_RGB_COLORS, SRCCOPY);
+ } else {
+ rv = ::StretchDIBits(hdc,
+ dest_x, dest_y, dest_w, dest_h,
+ src_x, bottom_up_src_y, src_w, src_h,
+ pixels, bitmap_info, DIB_RGB_COLORS, SRCCOPY);
+ }
+ DCHECK(rv != GDI_ERROR);
+}
+
+} // namespace gfx
diff --git a/chromium/ui/gfx/gdi_util.h b/chromium/ui/gfx/gdi_util.h
new file mode 100644
index 00000000000..fdf6a67bf20
--- /dev/null
+++ b/chromium/ui/gfx/gdi_util.h
@@ -0,0 +1,53 @@
+// Copyright (c) 2012 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_
+
+#include <vector>
+#include <windows.h>
+
+#include "ui/base/ui_export.h"
+#include "ui/gfx/rect.h"
+#include "ui/gfx/path.h"
+
+namespace gfx {
+
+// Creates a BITMAPINFOHEADER structure given the bitmap's size.
+UI_EXPORT 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.
+UI_EXPORT 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.
+UI_EXPORT void SubtractRectanglesFromRegion(
+ HRGN hrgn,
+ const std::vector<gfx::Rect>& cutouts);
+
+UI_EXPORT HRGN ConvertPathToHRGN(const gfx::Path& path);
+
+// Calculate scale to fit an entire page on DC.
+UI_EXPORT double CalculatePageScale(HDC dc, int page_width, int page_height);
+
+// Apply scaling to the DC.
+UI_EXPORT bool ScaleDC(HDC dc, double scale_factor);
+
+UI_EXPORT void StretchDIBits(HDC hdc,
+ int dest_x, int dest_y, int dest_w, int dest_h,
+ int src_x, int src_y, int src_w, int src_h,
+ void* pixels, const BITMAPINFO* bitmap_info);
+
+} // namespace gfx
+
+#endif // UI_GFX_GDI_UTIL_H_
diff --git a/chromium/ui/gfx/gfx_paths.cc b/chromium/ui/gfx/gfx_paths.cc
new file mode 100644
index 00000000000..3d53dfd1a1b
--- /dev/null
+++ b/chromium/ui/gfx/gfx_paths.cc
@@ -0,0 +1,44 @@
+// 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 "ui/gfx/gfx_paths.h"
+
+#include "base/command_line.h"
+#include "base/file_util.h"
+#include "base/files/file_path.h"
+#include "base/path_service.h"
+
+namespace gfx {
+
+bool PathProvider(int key, base::FilePath* result) {
+ base::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("ui"));
+ cur = cur.Append(FILE_PATH_LITERAL("gfx"));
+ cur = cur.Append(FILE_PATH_LITERAL("test"));
+ cur = cur.Append(FILE_PATH_LITERAL("data"));
+ if (!base::PathExists(cur)) // we don't want to create this
+ return false;
+ break;
+ default:
+ 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/chromium/ui/gfx/gfx_paths.h b/chromium/ui/gfx/gfx_paths.h
new file mode 100644
index 00000000000..24fad8b4cb1
--- /dev/null
+++ b/chromium/ui/gfx/gfx_paths.h
@@ -0,0 +1,29 @@
+// 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_GFX_PATHS_H_
+#define UI_GFX_GFX_PATHS_H_
+
+#include "ui/base/ui_export.h"
+
+// 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.
+UI_EXPORT void RegisterPathProvider();
+
+} // namespace gfx
+
+#endif // UI_GFX_GFX_PATHS_H_
diff --git a/chromium/ui/gfx/gpu_memory_buffer.cc b/chromium/ui/gfx/gpu_memory_buffer.cc
new file mode 100644
index 00000000000..c1de26c374b
--- /dev/null
+++ b/chromium/ui/gfx/gpu_memory_buffer.cc
@@ -0,0 +1,13 @@
+// Copyright 2013 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 "ui/gfx/gpu_memory_buffer.h"
+
+namespace gfx {
+
+GpuMemoryBuffer::GpuMemoryBuffer() {}
+
+GpuMemoryBuffer::~GpuMemoryBuffer() {}
+
+} // namespace gfx
diff --git a/chromium/ui/gfx/gpu_memory_buffer.h b/chromium/ui/gfx/gpu_memory_buffer.h
new file mode 100644
index 00000000000..ff94979ff01
--- /dev/null
+++ b/chromium/ui/gfx/gpu_memory_buffer.h
@@ -0,0 +1,82 @@
+// Copyright 2013 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_GPU_MEMORY_BUFFER_H_
+#define UI_GFX_GPU_MEMORY_BUFFER_H_
+
+#include "base/memory/shared_memory.h"
+#include "build/build_config.h"
+#include "ui/base/ui_export.h"
+
+#if defined(OS_ANDROID)
+#include <third_party/khronos/EGL/egl.h>
+#endif
+
+namespace gfx {
+
+enum GpuMemoryBufferType {
+ EMPTY_BUFFER,
+ SHARED_MEMORY_BUFFER,
+ EGL_CLIENT_BUFFER
+};
+
+struct GpuMemoryBufferHandle {
+ GpuMemoryBufferHandle()
+ : type(EMPTY_BUFFER),
+ handle(base::SharedMemory::NULLHandle())
+#if defined(OS_ANDROID)
+ , native_buffer(NULL)
+#endif
+ {
+ }
+ bool is_null() const { return type == EMPTY_BUFFER; }
+ GpuMemoryBufferType type;
+ base::SharedMemoryHandle handle;
+#if defined(OS_ANDROID)
+ EGLClientBuffer native_buffer;
+#endif
+};
+
+// Interface for creating and accessing a zero-copy GPU memory buffer.
+// This design evolved from the generalization of GraphicBuffer API
+// of Android framework.
+//
+// THREADING CONSIDERATIONS:
+//
+// This interface is thread-safe. However, multiple threads mapping
+// a buffer for Write or ReadOrWrite simultaneously may result in undefined
+// behavior and is not allowed.
+class UI_EXPORT GpuMemoryBuffer {
+ public:
+ enum AccessMode {
+ READ_ONLY,
+ WRITE_ONLY,
+ READ_WRITE,
+ };
+
+ GpuMemoryBuffer();
+ virtual ~GpuMemoryBuffer();
+
+ // Maps the buffer so the client can write the bitmap data in |*vaddr|
+ // subsequently. This call may block, for instance if the hardware needs
+ // to finish rendering or if CPU caches need to be synchronized.
+ virtual void Map(AccessMode mode, void** vaddr) = 0;
+
+ // Unmaps the buffer. Called after all changes to the buffer are
+ // completed.
+ virtual void Unmap() = 0;
+
+ // Returns true iff the buffer is mapped.
+ virtual bool IsMapped() const = 0;
+
+ // Returns the stride in bytes for the buffer.
+ virtual uint32 GetStride() const = 0;
+
+ // Returns a platform specific handle for this buffer.
+ virtual GpuMemoryBufferHandle GetHandle() const = 0;
+};
+
+} // namespace gfx
+
+#endif // UI_GFX_GPU_MEMORY_BUFFER_H_
diff --git a/chromium/ui/gfx/gtk_native_view_id_manager.cc b/chromium/ui/gfx/gtk_native_view_id_manager.cc
new file mode 100644
index 00000000000..07e1ed4c81d
--- /dev/null
+++ b/chromium/ui/gfx/gtk_native_view_id_manager.cc
@@ -0,0 +1,254 @@
+// Copyright (c) 2012 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 "ui/gfx/gtk_native_view_id_manager.h"
+
+#include <gdk/gdkx.h>
+#include <gtk/gtk.h>
+
+#include "base/logging.h"
+#include "base/memory/singleton.h"
+#include "base/rand_util.h"
+#include "ui/base/gtk/gdk_x_compat.h"
+#include "ui/base/gtk/gtk_compat.h"
+#include "ui/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_get_realized(widget)) {
+ GdkWindow *gdk_window = gtk_widget_get_window(widget);
+ 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::GetNativeViewForId(gfx::NativeView* 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.widget;
+ 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(gtk_widget_get_window(i->second.widget));
+
+ // 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;
+}
+
+bool GtkNativeViewManager::AddRefPermanentXID(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 false;
+
+ i->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_x11_window_lookup_for_display(gdk_display_get_default(), 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());
+
+ GdkWindow* gdk_window = gtk_widget_get_window(widget);
+ CHECK(gdk_window);
+ i->second.x_window_id = GDK_WINDOW_XID(gdk_window);
+}
+
+void GtkNativeViewManager::OnUnrealize(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());
+}
+
+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(gtk_widget_get_window(widget)));
+
+ 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/chromium/ui/gfx/gtk_native_view_id_manager.h b/chromium/ui/gfx/gtk_native_view_id_manager.h
new file mode 100644
index 00000000000..7c8afcc0f9d
--- /dev/null
+++ b/chromium/ui/gfx/gtk_native_view_id_manager.h
@@ -0,0 +1,138 @@
+// Copyright (c) 2012 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_
+
+#include <map>
+
+#include "base/synchronization/lock.h"
+#include "ui/base/ui_export.h"
+#include "ui/gfx/native_widget_types.h"
+
+template <typename T> struct DefaultSingletonTraits;
+
+typedef unsigned long XID;
+struct _GtkPreserveWindow;
+
+// NativeViewIds are the opaque values which the renderer holds as a reference
+// to a window.
+//
+// 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 UI_EXPORT 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);
+
+ // May be called from the UI thread:
+ //
+ // Same as GetXIDForId except it returns the NativeView (GtkWidget*).
+ bool GetNativeViewForId(gfx::NativeView* 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);
+
+ // Can be called from any thread.
+ // Will return false if the given XID isn't permanent or has already been
+ // released.
+ bool AddRefPermanentXID(XID xid);
+
+ // 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);
+
+ 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);
+
+ // 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/chromium/ui/gfx/gtk_preserve_window.cc b/chromium/ui/gfx/gtk_preserve_window.cc
new file mode 100644
index 00000000000..8ea1c431650
--- /dev/null
+++ b/chromium/ui/gfx/gtk_preserve_window.cc
@@ -0,0 +1,264 @@
+// Copyright (c) 2012 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 "ui/gfx/gtk_preserve_window.h"
+
+#include <gdk/gdk.h>
+#include <gtk/gtk.h>
+
+#include "ui/base/gtk/gtk_compat.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;
+
+ // Accessible factory and userdata.
+ AtkObject* (*accessible_factory)(void* userdata);
+ void* accessible_factory_userdata;
+};
+
+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 AtkObject* gtk_preserve_window_get_accessible(GtkWidget* widget);
+
+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;
+ widget_class->get_accessible = gtk_preserve_window_get_accessible;
+
+ 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;
+ priv->accessible_factory = NULL;
+ priv->accessible_factory_userdata = NULL;
+
+ // These widgets always have their own window.
+ gtk_widget_set_has_window(GTK_WIDGET(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);
+
+ GdkWindow* gdk_window = gtk_widget_get_window(widget);
+ if (gdk_window) {
+ gdk_window_set_user_data(gdk_window, NULL);
+ // If the window is preserved, someone else must destroy it.
+ if (!priv->preserve_window)
+ gdk_window_destroy(gdk_window);
+ gtk_widget_set_window(widget, 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));
+
+ GdkWindow* gdk_window = gtk_widget_get_window(widget);
+ if (gdk_window) {
+ GtkAllocation allocation;
+ gtk_widget_get_allocation(widget, &allocation);
+
+ gdk_window_reparent(gdk_window,
+ gtk_widget_get_parent_window(widget),
+ allocation.x,
+ allocation.y);
+ GtkPreserveWindowPrivate* priv = GTK_PRESERVE_WINDOW_GET_PRIVATE(widget);
+ if (!priv->delegate_resize) {
+ gdk_window_resize(gdk_window,
+ allocation.width,
+ allocation.height);
+ }
+ gint event_mask = gtk_widget_get_events(widget);
+ event_mask |= GDK_EXPOSURE_MASK | GDK_BUTTON_PRESS_MASK;
+ gdk_window_set_events(gdk_window, (GdkEventMask) event_mask);
+ gdk_window_set_user_data(gdk_window, widget);
+
+ gtk_widget_set_realized(widget, TRUE);
+
+ gtk_widget_style_attach(widget);
+ gtk_style_set_background(gtk_widget_get_style(widget),
+ gdk_window, GTK_STATE_NORMAL);
+ } 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);
+
+ if (gtk_widget_get_mapped(widget)) {
+ widget_class->unmap(widget);
+
+ gtk_widget_set_mapped(widget, FALSE);
+ }
+
+ // 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);
+
+ GdkWindow* gdk_window = gtk_widget_get_window(widget);
+
+ // TODO(erg): Almost all style handling will need to be overhauled in GTK3.
+ gtk_style_detach(gtk_widget_get_style(widget));
+ gdk_window_reparent(gdk_window, gdk_get_default_root_window(), 0, 0);
+ gtk_selection_remove_all(widget);
+ gdk_window_set_user_data(gdk_window, NULL);
+
+ gtk_widget_set_realized(widget, FALSE);
+ } 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);
+ GdkWindow* gdk_window = gtk_widget_get_window(widget);
+ if (value && !gdk_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.override_redirect = TRUE;
+
+ attributes.visual = gtk_widget_get_visual(widget);
+ attributes.colormap = gtk_widget_get_colormap(widget);
+
+ attributes.event_mask = gtk_widget_get_events(widget);
+ attributes.event_mask |= GDK_EXPOSURE_MASK | GDK_BUTTON_PRESS_MASK;
+
+ attributes_mask = GDK_WA_VISUAL | GDK_WA_COLORMAP | GDK_WA_NOREDIR;
+ gdk_window = gdk_window_new(
+ gdk_get_default_root_window(), &attributes, attributes_mask);
+ gtk_widget_set_window(widget, gdk_window);
+ } else if (!value && gdk_window && !gtk_widget_get_realized(widget)) {
+ gdk_window_destroy(gdk_window);
+ gtk_widget_set_window(widget, NULL);
+ }
+}
+
+void gtk_preserve_window_size_allocate(GtkWidget* widget,
+ GtkAllocation* allocation) {
+ g_return_if_fail(GTK_IS_PRESERVE_WINDOW(widget));
+
+ gtk_widget_set_allocation(widget, allocation);
+
+ if (gtk_widget_get_realized(widget)) {
+ GtkPreserveWindowPrivate* priv = GTK_PRESERVE_WINDOW_GET_PRIVATE(widget);
+ GdkWindow* gdk_window = gtk_widget_get_window(widget);
+ if (priv->delegate_resize) {
+ gdk_window_move(gdk_window, allocation->x, allocation->y);
+ } else {
+ gdk_window_move_resize(
+ gdk_window, allocation->x, allocation->y,
+ allocation->width, allocation->height);
+ }
+ }
+
+ // Propagate resize to children
+ guint16 border_width = gtk_container_get_border_width(GTK_CONTAINER(widget));
+ GList *children = GTK_FIXED(widget)->children;
+ while (children) {
+ GtkFixedChild *child = reinterpret_cast<GtkFixedChild*>(children->data);
+ if (gtk_widget_get_visible(child->widget)) {
+ GtkRequisition child_requisition;
+ gtk_widget_get_child_requisition(child->widget, &child_requisition);
+
+ GtkAllocation child_allocation;
+ child_allocation.x = child->x + border_width;
+ child_allocation.y = child->y + border_width;
+ child_allocation.width = child_requisition.width;
+ child_allocation.height = child_requisition.height;
+
+ gtk_widget_size_allocate(child->widget, &child_allocation);
+ }
+ children = children->next;
+ }
+}
+
+void gtk_preserve_window_delegate_resize(GtkPreserveWindow* widget,
+ gboolean delegate) {
+ GtkPreserveWindowPrivate* priv = GTK_PRESERVE_WINDOW_GET_PRIVATE(widget);
+ priv->delegate_resize = delegate;
+}
+
+void gtk_preserve_window_set_accessible_factory(
+ GtkPreserveWindow* widget,
+ AtkObject* (*factory)(void* userdata),
+ gpointer userdata) {
+ GtkPreserveWindowPrivate* priv = GTK_PRESERVE_WINDOW_GET_PRIVATE(widget);
+ priv->accessible_factory = factory;
+ priv->accessible_factory_userdata = userdata;
+}
+
+AtkObject* gtk_preserve_window_get_accessible(GtkWidget* widget) {
+ GtkPreserveWindowPrivate* priv = GTK_PRESERVE_WINDOW_GET_PRIVATE(widget);
+ if (priv->accessible_factory) {
+ return priv->accessible_factory(priv->accessible_factory_userdata);
+ } else {
+ return GTK_WIDGET_CLASS(gtk_preserve_window_parent_class)
+ ->get_accessible(widget);
+ }
+}
+
+G_END_DECLS
diff --git a/chromium/ui/gfx/gtk_preserve_window.h b/chromium/ui/gfx/gtk_preserve_window.h
new file mode 100644
index 00000000000..53a821e44dd
--- /dev/null
+++ b/chromium/ui/gfx/gtk_preserve_window.h
@@ -0,0 +1,74 @@
+// Copyright (c) 2012 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_
+
+#include <atk/atk.h>
+#include <gdk/gdk.h>
+#include <gtk/gtk.h>
+
+#include "ui/base/ui_export.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_PRESERVE_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;
+};
+
+UI_EXPORT GType gtk_preserve_window_get_type() G_GNUC_CONST;
+UI_EXPORT GtkWidget* gtk_preserve_window_new();
+
+// Whether or not we should preserve associated windows as the widget
+// is realized or unrealized.
+UI_EXPORT gboolean gtk_preserve_window_get_preserve(GtkPreserveWindow* widget);
+UI_EXPORT 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).
+UI_EXPORT void gtk_preserve_window_delegate_resize(GtkPreserveWindow* widget,
+ gboolean delegate);
+
+// Provide a function to return an AtkObject* when calls to get_accessible
+// are made on this widget. The parameter |userdata| will be passed to the
+// factory function.
+UI_EXPORT void gtk_preserve_window_set_accessible_factory(
+ GtkPreserveWindow* widget,
+ AtkObject* (*factory)(void* userdata),
+ gpointer userdata);
+
+G_END_DECLS
+
+#endif // UI_GFX_GTK_PRESERVE_WINDOW_H_
diff --git a/chromium/ui/gfx/gtk_util.cc b/chromium/ui/gfx/gtk_util.cc
new file mode 100644
index 00000000000..0c111687572
--- /dev/null
+++ b/chromium/ui/gfx/gtk_util.cc
@@ -0,0 +1,190 @@
+// Copyright (c) 2012 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 "ui/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/memory/scoped_ptr.h"
+#include "third_party/skia/include/core/SkBitmap.h"
+#include "third_party/skia/include/core/SkUnPreMultiply.h"
+#include "ui/gfx/rect.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 (GdkCursorMap::iterator i(cursors_.begin()); i != cursors_.end(); ++i) {
+ gdk_cursor_unref(i->second);
+ }
+ cursors_.clear();
+ }
+
+ GdkCursor* GetCursorImpl(GdkCursorType type) {
+ GdkCursorMap::iterator it = cursors_.find(type);
+ GdkCursor* cursor = NULL;
+ if (it == cursors_.end()) {
+ cursor = gdk_cursor_new(type);
+ cursors_.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;
+ }
+
+ private:
+ typedef std::map<GdkCursorType, GdkCursor*> GdkCursorMap;
+ GdkCursorMap cursors_;
+
+ DISALLOW_COPY_AND_ASSIGN(GdkCursorCache);
+};
+
+} // namespace
+
+namespace gfx {
+
+static void CommonInitFromCommandLine(const CommandLine& command_line,
+ void (*init_func)(gint*, gchar***)) {
+ const std::vector<std::string>& args = command_line.argv();
+ int argc = args.size();
+ scoped_ptr<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();
+
+ init_func(&argc, &argv_pointer);
+ for (size_t i = 0; i < args.size(); ++i) {
+ free(argv[i]);
+ }
+}
+
+void GtkInitFromCommandLine(const CommandLine& command_line) {
+ CommonInitFromCommandLine(command_line, gtk_init);
+}
+
+void GdkInitFromCommandLine(const CommandLine& command_line) {
+ CommonInitFromCommandLine(command_line, gdk_init);
+}
+
+GdkPixbuf* GdkPixbufFromSkBitmap(const SkBitmap& bitmap) {
+ if (bitmap.isNull())
+ return NULL;
+
+ SkAutoLockPixels lock_pixels(bitmap);
+
+ int width = bitmap.width();
+ int height = bitmap.height();
+
+ GdkPixbuf* pixbuf = gdk_pixbuf_new(
+ GDK_COLORSPACE_RGB, // The only colorspace gtk supports.
+ TRUE, // There is an alpha channel.
+ 8,
+ width, height);
+
+ // SkBitmaps are premultiplied, we need to unpremultiply them.
+ const int kBytesPerPixel = 4;
+ uint8* divided = gdk_pixbuf_get_pixels(pixbuf);
+
+ 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;
+ }
+ }
+
+ 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);
+ }
+}
+
+GdkCursor* GetCursor(int type) {
+ CR_DEFINE_STATIC_LOCAL(GdkCursorCache, impl, ());
+ return impl.GetCursorImpl(static_cast<GdkCursorType>(type));
+}
+
+void InitRCStyles() {
+ static const char kRCText[] =
+ // Make our dialogs styled like the GNOME HIG.
+ //
+ // TODO(evanm): content-area-spacing was introduced in a later
+ // version of GTK, so we need to set that manually on all dialogs.
+ // Perhaps it would make sense to have a shared FixupDialog() function.
+ "style \"gnome-dialog\" {\n"
+ " xthickness = 12\n"
+ " GtkDialog::action-area-border = 0\n"
+ " GtkDialog::button-spacing = 6\n"
+ " GtkDialog::content-area-spacing = 18\n"
+ " GtkDialog::content-area-border = 12\n"
+ "}\n"
+ // Note we set it at the "application" priority, so users can override.
+ "widget \"GtkDialog\" style : application \"gnome-dialog\"\n"
+
+ // Make our about dialog special, so the image is flush with the edge.
+ "style \"about-dialog\" {\n"
+ " GtkDialog::action-area-border = 12\n"
+ " GtkDialog::button-spacing = 6\n"
+ " GtkDialog::content-area-spacing = 18\n"
+ " GtkDialog::content-area-border = 0\n"
+ "}\n"
+ "widget \"about-dialog\" style : application \"about-dialog\"\n";
+
+ gtk_rc_parse_string(kRCText);
+}
+
+base::TimeDelta GetCursorBlinkCycle() {
+ // From http://library.gnome.org/devel/gtk/unstable/GtkSettings.html, this is
+ // the default value for gtk-cursor-blink-time.
+ static const gint kGtkDefaultCursorBlinkTime = 1200;
+
+ gint cursor_blink_time = kGtkDefaultCursorBlinkTime;
+ gboolean cursor_blink = TRUE;
+ g_object_get(gtk_settings_get_default(),
+ "gtk-cursor-blink-time", &cursor_blink_time,
+ "gtk-cursor-blink", &cursor_blink,
+ NULL);
+ return cursor_blink ?
+ base::TimeDelta::FromMilliseconds(cursor_blink_time) :
+ base::TimeDelta();
+}
+
+} // namespace gfx
diff --git a/chromium/ui/gfx/gtk_util.h b/chromium/ui/gfx/gtk_util.h
new file mode 100644
index 00000000000..5fe0c081fca
--- /dev/null
+++ b/chromium/ui/gfx/gtk_util.h
@@ -0,0 +1,52 @@
+// Copyright (c) 2012 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_
+
+#include <vector>
+
+#include "base/time/time.h"
+#include "ui/base/ui_export.h"
+
+typedef struct _GdkPixbuf GdkPixbuf;
+typedef struct _GdkRegion GdkRegion;
+typedef struct _GdkCursor GdkCursor;
+
+class CommandLine;
+class SkBitmap;
+
+namespace gfx {
+
+class Rect;
+
+// Call gtk_init() / gdk_init() using the argc and argv from command_line.
+// These init functions want an argc and argv that they can mutate; we provide
+// those, but leave the original CommandLine unaltered.
+UI_EXPORT void GtkInitFromCommandLine(const CommandLine& command_line);
+UI_EXPORT void GdkInitFromCommandLine(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.
+UI_EXPORT GdkPixbuf* GdkPixbufFromSkBitmap(const SkBitmap& bitmap);
+
+// Modify the given region by subtracting the given rectangles.
+UI_EXPORT void SubtractRectanglesFromRegion(GdkRegion* region,
+ const std::vector<Rect>& cutouts);
+
+// Returns a static instance of a GdkCursor* object, sharable across the
+// process. Caller must gdk_cursor_ref() it if they want to assume ownership.
+UI_EXPORT GdkCursor* GetCursor(int type);
+
+// Initialize some GTK settings so that our dialogs are consistent.
+UI_EXPORT void InitRCStyles();
+
+// Queries GtkSettings for the cursor blink cycle time. Returns a 0 duration if
+// blinking is disabled.
+UI_EXPORT base::TimeDelta GetCursorBlinkCycle();
+
+} // namespace gfx
+
+#endif // UI_GFX_GTK_UTIL_H_
diff --git a/chromium/ui/gfx/icon_util.cc b/chromium/ui/gfx/icon_util.cc
new file mode 100644
index 00000000000..223618601e3
--- /dev/null
+++ b/chromium/ui/gfx/icon_util.cc
@@ -0,0 +1,687 @@
+// Copyright (c) 2012 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 "ui/gfx/icon_util.h"
+
+#include "base/file_util.h"
+#include "base/files/important_file_writer.h"
+#include "base/logging.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/win/resource_util.h"
+#include "base/win/scoped_gdi_object.h"
+#include "base/win/scoped_handle.h"
+#include "base/win/scoped_hdc.h"
+#include "skia/ext/image_operations.h"
+#include "third_party/skia/include/core/SkBitmap.h"
+#include "ui/gfx/gdi_util.h"
+#include "ui/gfx/image/image.h"
+#include "ui/gfx/image/image_family.h"
+#include "ui/gfx/size.h"
+
+namespace {
+
+struct ScopedICONINFO : ICONINFO {
+ ScopedICONINFO() {
+ hbmColor = NULL;
+ hbmMask = NULL;
+ }
+
+ ~ScopedICONINFO() {
+ if (hbmColor)
+ ::DeleteObject(hbmColor);
+ if (hbmMask)
+ ::DeleteObject(hbmMask);
+ }
+};
+
+// Creates a new ImageFamily, |resized_image_family|, based on the images in
+// |image_family|, but containing images of specific dimensions desirable for
+// Windows icons. For each desired image dimension, it chooses the most
+// appropriate image for that size, and resizes it to the desired size.
+// Returns true on success, false on failure. Failure can occur if
+// |image_family| is empty, all images in the family have size 0x0, or an image
+// has no allocated pixel data.
+// |resized_image_family| must be empty.
+bool BuildResizedImageFamily(const gfx::ImageFamily& image_family,
+ gfx::ImageFamily* resized_image_family) {
+ DCHECK(resized_image_family);
+ DCHECK(resized_image_family->empty());
+
+ for (size_t i = 0; i < IconUtil::kNumIconDimensions; ++i) {
+ int dimension = IconUtil::kIconDimensions[i];
+ gfx::Size size(dimension, dimension);
+ const gfx::Image* best = image_family.GetBest(size);
+ if (!best || best->IsEmpty()) {
+ // Either |image_family| is empty, or all images have size 0x0.
+ return false;
+ }
+
+ // Optimize for the "Large icons" view in Windows Vista+. This view displays
+ // icons at full size if only if there is a 256x256 (kLargeIconSize) image
+ // in the .ico file. Otherwise, it shrinks icons to 48x48 (kMediumIconSize).
+ if (dimension > IconUtil::kMediumIconSize &&
+ best->Width() <= IconUtil::kMediumIconSize &&
+ best->Height() <= IconUtil::kMediumIconSize) {
+ // There is no source icon larger than 48x48, so do not create any
+ // images larger than 48x48. kIconDimensions is sorted in ascending
+ // order, so it is safe to break here.
+ break;
+ }
+
+ if (best->Size() == size) {
+ resized_image_family->Add(*best);
+ } else {
+ // There is no |dimension|x|dimension| source image.
+ // Resize this one to the desired size, and insert it.
+ SkBitmap best_bitmap = best->AsBitmap();
+ // Only kARGB_8888 images are supported.
+ // This will also filter out images with no pixels.
+ if (best_bitmap.config() != SkBitmap::kARGB_8888_Config)
+ return false;
+ SkBitmap resized_bitmap = skia::ImageOperations::Resize(
+ best_bitmap, skia::ImageOperations::RESIZE_LANCZOS3,
+ dimension, dimension);
+ resized_image_family->Add(gfx::Image::CreateFrom1xBitmap(resized_bitmap));
+ }
+ }
+ return true;
+}
+
+// Creates a set of bitmaps from an image family.
+// All images smaller than 256x256 are converted to SkBitmaps, and inserted into
+// |bitmaps| in order of aspect ratio (thinnest to widest), and then ascending
+// size order. If an image of exactly 256x256 is specified, it is converted into
+// PNG format and stored in |png_bytes|. Images with width or height larger than
+// 256 are ignored.
+// |bitmaps| must be an empty vector, and not NULL.
+// Returns true on success, false on failure. This fails if any image in
+// |image_family| is not a 32-bit ARGB image, or is otherwise invalid.
+bool ConvertImageFamilyToBitmaps(
+ const gfx::ImageFamily& image_family,
+ std::vector<SkBitmap>* bitmaps,
+ scoped_refptr<base::RefCountedMemory>* png_bytes) {
+ DCHECK(bitmaps != NULL);
+ DCHECK(bitmaps->empty());
+
+ for (gfx::ImageFamily::const_iterator it = image_family.begin();
+ it != image_family.end(); ++it) {
+ const gfx::Image& image = *it;
+
+ // All images should have one of the kIconDimensions sizes.
+ DCHECK_GT(image.Width(), 0);
+ DCHECK_LE(image.Width(), IconUtil::kLargeIconSize);
+ DCHECK_GT(image.Height(), 0);
+ DCHECK_LE(image.Height(), IconUtil::kLargeIconSize);
+
+ SkBitmap bitmap = image.AsBitmap();
+
+ // Only 32 bit ARGB bitmaps are supported. We also make sure the bitmap has
+ // been properly initialized.
+ SkAutoLockPixels bitmap_lock(bitmap);
+ if ((bitmap.config() != SkBitmap::kARGB_8888_Config) ||
+ (bitmap.getPixels() == NULL)) {
+ return false;
+ }
+
+ // Special case: Icons exactly 256x256 are stored in PNG format.
+ if (image.Width() == IconUtil::kLargeIconSize &&
+ image.Height() == IconUtil::kLargeIconSize) {
+ *png_bytes = image.As1xPNGBytes();
+ } else {
+ bitmaps->push_back(bitmap);
+ }
+ }
+
+ return true;
+}
+
+} // namespace
+
+// The icon images appear in the icon file in same order in which their
+// corresponding dimensions appear in this array, so it is important to keep
+// this array sorted. Also note that the maximum icon image size we can handle
+// is 256 by 256. See:
+// http://msdn.microsoft.com/en-us/library/windows/desktop/aa511280.aspx#size
+const int IconUtil::kIconDimensions[] = {
+ 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).
+ 256 // Used by Vista onwards for large icons.
+};
+
+const size_t IconUtil::kNumIconDimensions = arraysize(kIconDimensions);
+const size_t IconUtil::kNumIconDimensionsUpToMediumSize = 9;
+
+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.config() != 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_ptr<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.
+ if (!icon || s.IsEmpty())
+ return NULL;
+ ScopedICONINFO icon_info;
+ if (!::GetIconInfo(icon, &icon_info))
+ return NULL;
+ if (!icon_info.fIcon)
+ return NULL;
+ return new SkBitmap(CreateSkBitmapFromHICONHelper(icon, s));
+}
+
+scoped_ptr<SkBitmap> IconUtil::CreateSkBitmapFromIconResource(HMODULE module,
+ int resource_id,
+ int size) {
+ DCHECK_LE(size, kLargeIconSize);
+
+ // For everything except the Vista+ 256x256 icons, use |LoadImage()|.
+ if (size != kLargeIconSize) {
+ HICON icon_handle =
+ static_cast<HICON>(LoadImage(module, MAKEINTRESOURCE(resource_id),
+ IMAGE_ICON, size, size,
+ LR_DEFAULTCOLOR | LR_DEFAULTSIZE));
+ scoped_ptr<SkBitmap> bitmap(IconUtil::CreateSkBitmapFromHICON(icon_handle));
+ DestroyIcon(icon_handle);
+ return bitmap.Pass();
+ }
+
+ // For Vista+ 256x256 PNG icons, read the resource directly and find
+ // the corresponding icon entry to get its PNG bytes.
+ void* icon_dir_data = NULL;
+ size_t icon_dir_size = 0;
+ if (!base::win::GetResourceFromModule(module, resource_id, RT_GROUP_ICON,
+ &icon_dir_data, &icon_dir_size)) {
+ return scoped_ptr<SkBitmap>();
+ }
+ DCHECK(icon_dir_data);
+ DCHECK_GE(icon_dir_size, sizeof(GRPICONDIR));
+
+ const GRPICONDIR* icon_dir =
+ reinterpret_cast<const GRPICONDIR*>(icon_dir_data);
+ const GRPICONDIRENTRY* large_icon_entry = NULL;
+ for (size_t i = 0; i < icon_dir->idCount; ++i) {
+ const GRPICONDIRENTRY* entry = &icon_dir->idEntries[i];
+ // 256x256 icons are stored with width and height set to 0.
+ // See: http://en.wikipedia.org/wiki/ICO_(file_format)
+ if (entry->bWidth == 0 && entry->bHeight == 0) {
+ large_icon_entry = entry;
+ break;
+ }
+ }
+ if (!large_icon_entry)
+ return scoped_ptr<SkBitmap>();
+
+ void* png_data = NULL;
+ size_t png_size = 0;
+ if (!base::win::GetResourceFromModule(module, large_icon_entry->nID, RT_ICON,
+ &png_data, &png_size)) {
+ return scoped_ptr<SkBitmap>();
+ }
+ DCHECK(png_data);
+ DCHECK_EQ(png_size, large_icon_entry->dwBytesInRes);
+
+ const unsigned char* png_bytes =
+ reinterpret_cast<const unsigned char*>(png_data);
+ gfx::Image image = gfx::Image::CreateFrom1xPNGBytes(png_bytes, png_size);
+ return scoped_ptr<SkBitmap>(new SkBitmap(image.AsBitmap()));
+}
+
+SkBitmap* IconUtil::CreateSkBitmapFromHICON(HICON icon) {
+ // We start with validating parameters.
+ if (!icon)
+ return NULL;
+
+ ScopedICONINFO icon_info;
+ BITMAP bitmap_info = { 0 };
+
+ if (!::GetIconInfo(icon, &icon_info))
+ return NULL;
+
+ if (!::GetObject(icon_info.hbmMask, sizeof(bitmap_info), &bitmap_info))
+ return NULL;
+
+ gfx::Size icon_size(bitmap_info.bmWidth, bitmap_info.bmHeight);
+ return new SkBitmap(CreateSkBitmapFromHICONHelper(icon, icon_size));
+}
+
+HICON IconUtil::CreateCursorFromDIB(const gfx::Size& icon_size,
+ const gfx::Point& hotspot,
+ const void* dib_bits,
+ size_t dib_size) {
+ BITMAPINFO icon_bitmap_info = {0};
+ gfx::CreateBitmapHeader(
+ icon_size.width(),
+ icon_size.height(),
+ reinterpret_cast<BITMAPINFOHEADER*>(&icon_bitmap_info));
+
+ base::win::ScopedGetDC dc(NULL);
+ base::win::ScopedCreateDC working_dc(CreateCompatibleDC(dc));
+ base::win::ScopedGDIObject<HBITMAP> bitmap_handle(
+ CreateDIBSection(dc,
+ &icon_bitmap_info,
+ DIB_RGB_COLORS,
+ 0,
+ 0,
+ 0));
+ if (dib_size > 0) {
+ SetDIBits(0,
+ bitmap_handle,
+ 0,
+ icon_size.height(),
+ dib_bits,
+ &icon_bitmap_info,
+ DIB_RGB_COLORS);
+ }
+
+ HBITMAP old_bitmap = reinterpret_cast<HBITMAP>(
+ SelectObject(working_dc, bitmap_handle));
+ SetBkMode(working_dc, TRANSPARENT);
+ SelectObject(working_dc, old_bitmap);
+
+ base::win::ScopedGDIObject<HBITMAP> mask(
+ CreateBitmap(icon_size.width(),
+ icon_size.height(),
+ 1,
+ 1,
+ NULL));
+ ICONINFO ii = {0};
+ ii.fIcon = FALSE;
+ ii.xHotspot = hotspot.x();
+ ii.yHotspot = hotspot.y();
+ ii.hbmMask = mask;
+ ii.hbmColor = bitmap_handle;
+
+ return CreateIconIndirect(&ii);
+}
+
+SkBitmap IconUtil::CreateSkBitmapFromHICONHelper(HICON icon,
+ const gfx::Size& s) {
+ DCHECK(icon);
+ DCHECK(!s.IsEmpty());
+
+ // Allocating memory for the SkBitmap object. We are going to create an ARGB
+ // bitmap so we should set the configuration appropriately.
+ SkBitmap 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 hdc = ::GetDC(NULL);
+ uint32* bits;
+ HBITMAP dib = ::CreateDIBSection(hdc, reinterpret_cast<BITMAPINFO*>(&h),
+ DIB_RGB_COLORS, reinterpret_cast<void**>(&bits), NULL, 0);
+ DCHECK(dib);
+ HDC dib_dc = CreateCompatibleDC(hdc);
+ ::ReleaseDC(NULL, hdc);
+ DCHECK(dib_dc);
+ HGDIOBJ old_obj = ::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.
+ scoped_ptr<bool[]> opaque(new bool[num_pixels]);
+ 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;
+ }
+ }
+
+ ::SelectObject(dib_dc, old_obj);
+ ::DeleteObject(dib);
+ ::DeleteDC(dib_dc);
+
+ return bitmap;
+}
+
+// static
+bool IconUtil::CreateIconFileFromImageFamily(
+ const gfx::ImageFamily& image_family,
+ const base::FilePath& icon_path) {
+ // 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 most
+ // appropriate image from |image_family| to the desired size.
+ gfx::ImageFamily resized_image_family;
+ if (!BuildResizedImageFamily(image_family, &resized_image_family))
+ return false;
+
+ std::vector<SkBitmap> bitmaps;
+ scoped_refptr<base::RefCountedMemory> png_bytes;
+ if (!ConvertImageFamilyToBitmaps(resized_image_family, &bitmaps, &png_bytes))
+ return false;
+
+ // Guaranteed true because BuildResizedImageFamily will provide at least one
+ // image < 256x256.
+ DCHECK(!bitmaps.empty());
+ size_t bitmap_count = bitmaps.size(); // Not including PNG image.
+ // Including PNG image, if any.
+ size_t image_count = bitmap_count + (png_bytes.get() ? 1 : 0);
+
+ // 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);
+ // Account for the bytes needed for the PNG entry.
+ if (png_bytes.get())
+ buffer_size += sizeof(ICONDIRENTRY) + png_bytes->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.
+ std::vector<uint8> buffer(buffer_size);
+ ICONDIR* icon_dir = reinterpret_cast<ICONDIR*>(&buffer[0]);
+ icon_dir->idType = kResourceTypeIcon;
+ icon_dir->idCount = static_cast<WORD>(image_count);
+ // - 1 because there is already one ICONDIRENTRY in ICONDIR.
+ size_t icon_dir_count = image_count - 1;
+
+ 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;
+ }
+
+ // Add the PNG entry, if necessary.
+ if (png_bytes.get()) {
+ ICONDIRENTRY* entry = &icon_dir->idEntries[bitmap_count];
+ entry->bWidth = 0;
+ entry->bHeight = 0;
+ entry->wPlanes = 1;
+ entry->wBitCount = 32;
+ entry->dwBytesInRes = static_cast<DWORD>(png_bytes->size());
+ entry->dwImageOffset = static_cast<DWORD>(offset);
+ memcpy(&buffer[offset], png_bytes->front(), png_bytes->size());
+ offset += png_bytes->size();
+ }
+
+ DCHECK_EQ(offset, buffer_size);
+
+ std::string data(buffer.begin(), buffer.end());
+ return base::ImportantFileWriter::WriteFileAtomically(icon_path, data);
+}
+
+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);
+ DCHECK_LT(bitmap.width(), kLargeIconSize);
+ DCHECK_LT(bitmap.height(), kLargeIconSize);
+
+ // 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);
+ }
+}
+
+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);
+ // May not have all icon sizes, but must have at least up to medium icon size.
+ DCHECK_GE(bitmap_count, kNumIconDimensionsUpToMediumSize);
+
+ // 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/chromium/ui/gfx/icon_util.h b/chromium/ui/gfx/icon_util.h
new file mode 100644
index 00000000000..7ce5606f525
--- /dev/null
+++ b/chromium/ui/gfx/icon_util.h
@@ -0,0 +1,273 @@
+// Copyright (c) 2012 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_
+
+#include <windows.h>
+#include <string>
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/gtest_prod_util.h"
+#include "base/memory/scoped_ptr.h"
+#include "ui/base/ui_export.h"
+#include "ui/gfx/point.h"
+#include "ui/gfx/size.h"
+
+namespace base {
+class FilePath;
+}
+
+namespace gfx {
+class ImageFamily;
+class Size;
+}
+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 UI_EXPORT IconUtil {
+ public:
+ // The size of the large icon entries in .ico files on Windows Vista+.
+ static const int kLargeIconSize = 256;
+ // The size of icons in the medium icons view on Windows Vista+. This is the
+ // maximum size Windows will display an icon that does not have a 256x256
+ // image, even at the large or extra large icons views.
+ static const int kMediumIconSize = 48;
+
+ // The dimensions for icon images in Windows icon files. All sizes are square;
+ // that is, the value 48 means a 48x48 pixel image. Sizes are listed in
+ // ascending order.
+ static const int kIconDimensions[];
+
+ // The number of elements in kIconDimensions.
+ static const size_t kNumIconDimensions;
+ // The number of elements in kIconDimensions <= kMediumIconSize.
+ static const size_t kNumIconDimensionsUpToMediumSize;
+
+ // 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);
+
+ // Loads an icon resource as a SkBitmap for the specified |size| from a
+ // loaded .dll or .exe |module|. Supports loading smaller icon sizes as well
+ // as the Vista+ 256x256 PNG icon size. If the icon could not be loaded or
+ // found, returns a NULL scoped_ptr.
+ static scoped_ptr<SkBitmap> CreateSkBitmapFromIconResource(HMODULE module,
+ int resource_id,
+ int size);
+
+ // Given a valid HICON handle representing an icon, this function converts
+ // the icon into an SkBitmap object containing an ARGB bitmap using the
+ // dimensions of HICON. 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);
+
+ // Creates Windows .ico file at |icon_path|. The icon file is created with
+ // multiple BMP representations at varying predefined dimensions (by resizing
+ // an appropriately sized image from |image_family|) because Windows uses
+ // different image sizes when loading icons, depending on where the icon is
+ // drawn (ALT+TAB window, desktop shortcut, Quick Launch, etc.).
+ //
+ // If |image_family| contains an image larger than 48x48, the resulting icon
+ // will contain all sizes up to 256x256. The 256x256 image will be stored in
+ // PNG format inside the .ico file. If not, the resulting icon will contain
+ // all sizes up to 48x48.
+ //
+ // The function returns true on success and false otherwise. Returns false if
+ // |image_family| is empty.
+ static bool CreateIconFileFromImageFamily(
+ const gfx::ImageFamily& image_family,
+ const base::FilePath& icon_path);
+
+ // Creates a cursor of the specified size from the DIB passed in.
+ // Returns the cursor on success or NULL on failure.
+ static HICON CreateCursorFromDIB(const gfx::Size& icon_size,
+ const gfx::Point& hotspot,
+ const void* dib_bits,
+ size_t dib_size);
+
+ 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.
+ // See: http://msdn.microsoft.com/en-us/library/ms997538.aspx
+#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];
+ };
+
+ // GRPICONDIRENTRY contains meta data for an individual icon image within a
+ // RT_GROUP_ICON resource in an .exe or .dll.
+ struct GRPICONDIRENTRY {
+ BYTE bWidth;
+ BYTE bHeight;
+ BYTE bColorCount;
+ BYTE bReserved;
+ WORD wPlanes;
+ WORD wBitCount;
+ DWORD dwBytesInRes;
+ WORD nID;
+ };
+
+ // GRPICONDIR Contains information about all the icon images contained within
+ // a RT_GROUP_ICON resource in an .exe or .dll.
+ struct GRPICONDIR {
+ WORD idReserved;
+ WORD idType;
+ WORD idCount;
+ GRPICONDIRENTRY idEntries[1];
+ };
+
+ // Contains the actual icon image.
+ struct ICONIMAGE {
+ BITMAPINFOHEADER icHeader;
+ RGBQUAD icColors[1];
+ BYTE icXOR[1];
+ BYTE icAND[1];
+ };
+#pragma pack(pop)
+
+ friend class IconUtilTest;
+
+ // 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;
+
+ // 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 width and height of |bitmap| must be < 256.
+ // (Note that the 256x256 icon is treated specially, as a PNG, and should not
+ // use this method.)
+ //
+ // 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 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);
+
+ // A helper function of CreateSkBitmapFromHICON.
+ static SkBitmap CreateSkBitmapFromHICONHelper(HICON icon,
+ const gfx::Size& s);
+
+ // 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/chromium/ui/gfx/icon_util_unittest.cc b/chromium/ui/gfx/icon_util_unittest.cc
new file mode 100644
index 00000000000..b1548cee447
--- /dev/null
+++ b/chromium/ui/gfx/icon_util_unittest.cc
@@ -0,0 +1,435 @@
+// 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 "base/file_util.h"
+#include "base/files/scoped_temp_dir.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/path_service.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/skia/include/core/SkBitmap.h"
+#include "ui/gfx/gfx_paths.h"
+#include "ui/gfx/icon_util.h"
+#include "ui/gfx/image/image.h"
+#include "ui/gfx/image/image_family.h"
+#include "ui/gfx/size.h"
+#include "ui/test/ui_unittests_resource.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";
+
+} // namespace
+
+class IconUtilTest : public testing::Test {
+ public:
+ virtual void SetUp() OVERRIDE {
+ PathService::Get(gfx::DIR_TEST_DATA, &test_data_directory_);
+ temp_directory_.CreateUniqueTempDir();
+ }
+
+ 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 dimensions, this
+ // function loads the icon and returns an HICON handle.
+ HICON LoadIconFromFile(const base::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;
+ }
+
+ SkBitmap CreateBlackSkBitmap(int width, int height) {
+ SkBitmap bitmap;
+ bitmap.setConfig(SkBitmap::kARGB_8888_Config, width, height);
+ bitmap.allocPixels();
+ // Setting the pixels to black.
+ memset(bitmap.getPixels(), 0, width * height * 4);
+ return bitmap;
+ }
+
+ // Loads an .ico file from |icon_filename| and asserts that it contains all of
+ // the expected icon sizes up to and including |max_icon_size|, and no other
+ // icons. If |max_icon_size| >= 256, this tests for a 256x256 PNG icon entry.
+ void CheckAllIconSizes(const base::FilePath& icon_filename,
+ int max_icon_size);
+
+ protected:
+ // The root directory for test files. This should be treated as read-only.
+ base::FilePath test_data_directory_;
+
+ // Directory for creating files by this test.
+ base::ScopedTempDir temp_directory_;
+};
+
+void IconUtilTest::CheckAllIconSizes(const base::FilePath& icon_filename,
+ int max_icon_size) {
+ ASSERT_TRUE(base::PathExists(icon_filename));
+
+ // Determine how many icons to expect, based on |max_icon_size|.
+ int expected_num_icons = 0;
+ for (size_t i = 0; i < IconUtil::kNumIconDimensions; ++i) {
+ if (IconUtil::kIconDimensions[i] > max_icon_size)
+ break;
+ ++expected_num_icons;
+ }
+
+ // First, use the Windows API to load the icon, a basic validity test.
+ HICON icon = LoadIconFromFile(icon_filename, kSmallIconWidth,
+ kSmallIconHeight);
+ EXPECT_NE(static_cast<HICON>(NULL), icon);
+ if (icon != NULL)
+ ::DestroyIcon(icon);
+
+ // Read the file completely into memory.
+ std::string icon_data;
+ ASSERT_TRUE(file_util::ReadFileToString(icon_filename, &icon_data));
+ ASSERT_GE(icon_data.length(), sizeof(IconUtil::ICONDIR));
+
+ // Ensure that it has exactly the expected number and sizes of icons, in the
+ // expected order. This matches each entry of the loaded file's icon directory
+ // with the corresponding element of kIconDimensions.
+ // Also extracts the 256x256 entry as png_entry.
+ const IconUtil::ICONDIR* icon_dir =
+ reinterpret_cast<const IconUtil::ICONDIR*>(icon_data.data());
+ EXPECT_EQ(expected_num_icons, icon_dir->idCount);
+ ASSERT_GE(IconUtil::kNumIconDimensions, icon_dir->idCount);
+ ASSERT_GE(icon_data.length(),
+ sizeof(IconUtil::ICONDIR) +
+ icon_dir->idCount * sizeof(IconUtil::ICONDIRENTRY));
+ const IconUtil::ICONDIRENTRY* png_entry = NULL;
+ for (size_t i = 0; i < icon_dir->idCount; ++i) {
+ const IconUtil::ICONDIRENTRY* entry = &icon_dir->idEntries[i];
+ // Mod 256 because as a special case in ICONDIRENTRY, the value 0 represents
+ // a width or height of 256.
+ int expected_size = IconUtil::kIconDimensions[i] % 256;
+ EXPECT_EQ(expected_size, static_cast<int>(entry->bWidth));
+ EXPECT_EQ(expected_size, static_cast<int>(entry->bHeight));
+ if (entry->bWidth == 0 && entry->bHeight == 0) {
+ EXPECT_EQ(NULL, png_entry);
+ png_entry = entry;
+ }
+ }
+
+ if (max_icon_size >= 256) {
+ ASSERT_TRUE(png_entry);
+
+ // Convert the PNG entry data back to a SkBitmap to ensure it's valid.
+ ASSERT_GE(icon_data.length(),
+ png_entry->dwImageOffset + png_entry->dwBytesInRes);
+ const unsigned char* png_bytes = reinterpret_cast<const unsigned char*>(
+ icon_data.data() + png_entry->dwImageOffset);
+ gfx::Image image = gfx::Image::CreateFrom1xPNGBytes(
+ png_bytes, png_entry->dwBytesInRes);
+ SkBitmap bitmap = image.AsBitmap();
+ EXPECT_EQ(256, bitmap.width());
+ EXPECT_EQ(256, bitmap.height());
+ }
+}
+
+// The following test case makes sure IconUtil::SkBitmapFromHICON fails
+// gracefully when called with invalid input parameters.
+TEST_F(IconUtilTest, TestIconToBitmapInvalidParameters) {
+ base::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::CreateIconFileFromImageFamily
+// fails gracefully when called with invalid input parameters.
+TEST_F(IconUtilTest, TestCreateIconFileInvalidParameters) {
+ scoped_ptr<SkBitmap> bitmap;
+ gfx::ImageFamily image_family;
+ base::FilePath valid_icon_filename = temp_directory_.path().AppendASCII(
+ kTempIconFilename);
+ base::FilePath invalid_icon_filename = temp_directory_.path().AppendASCII(
+ "<>?.ico");
+
+ // Wrong bitmap format.
+ bitmap.reset(new SkBitmap);
+ ASSERT_NE(bitmap.get(), static_cast<SkBitmap*>(NULL));
+ bitmap->setConfig(SkBitmap::kA8_Config, kSmallIconWidth, kSmallIconHeight);
+ // Must allocate pixels or else ImageSkia will ignore the bitmap and just
+ // return an empty image.
+ bitmap->allocPixels();
+ memset(bitmap->getPixels(), 0, bitmap->width() * bitmap->height());
+ image_family.Add(gfx::Image::CreateFrom1xBitmap(*bitmap));
+ EXPECT_FALSE(IconUtil::CreateIconFileFromImageFamily(image_family,
+ valid_icon_filename));
+ EXPECT_FALSE(base::PathExists(valid_icon_filename));
+
+ // Invalid bitmap size.
+ image_family.clear();
+ bitmap.reset(new SkBitmap);
+ ASSERT_NE(bitmap.get(), static_cast<SkBitmap*>(NULL));
+ bitmap->setConfig(SkBitmap::kARGB_8888_Config, 0, 0);
+ bitmap->allocPixels();
+ image_family.Add(gfx::Image::CreateFrom1xBitmap(*bitmap));
+ EXPECT_FALSE(IconUtil::CreateIconFileFromImageFamily(image_family,
+ valid_icon_filename));
+ EXPECT_FALSE(base::PathExists(valid_icon_filename));
+
+ // Bitmap with no allocated pixels.
+ image_family.clear();
+ bitmap.reset(new SkBitmap);
+ ASSERT_NE(bitmap.get(), static_cast<SkBitmap*>(NULL));
+ bitmap->setConfig(SkBitmap::kARGB_8888_Config,
+ kSmallIconWidth,
+ kSmallIconHeight);
+ image_family.Add(gfx::Image::CreateFrom1xBitmap(*bitmap));
+ EXPECT_FALSE(IconUtil::CreateIconFileFromImageFamily(image_family,
+ valid_icon_filename));
+ EXPECT_FALSE(base::PathExists(valid_icon_filename));
+
+ // Invalid file name.
+ image_family.clear();
+ bitmap->allocPixels();
+ // Setting the pixels to black.
+ memset(bitmap->getPixels(), 0, bitmap->width() * bitmap->height() * 4);
+ image_family.Add(gfx::Image::CreateFrom1xBitmap(*bitmap));
+ EXPECT_FALSE(IconUtil::CreateIconFileFromImageFamily(image_family,
+ invalid_icon_filename));
+ EXPECT_FALSE(base::PathExists(invalid_icon_filename));
+}
+
+// This test case makes sure IconUtil::CreateIconFileFromImageFamily fails if
+// the image family is empty or invalid.
+TEST_F(IconUtilTest, TestCreateIconFileEmptyImageFamily) {
+ base::FilePath icon_filename = temp_directory_.path().AppendASCII(
+ kTempIconFilename);
+
+ // Empty image family.
+ EXPECT_FALSE(IconUtil::CreateIconFileFromImageFamily(gfx::ImageFamily(),
+ icon_filename));
+ EXPECT_FALSE(base::PathExists(icon_filename));
+
+ // Image family with only an empty image.
+ gfx::ImageFamily image_family;
+ image_family.Add(gfx::Image());
+ EXPECT_FALSE(IconUtil::CreateIconFileFromImageFamily(image_family,
+ icon_filename));
+ EXPECT_FALSE(base::PathExists(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 dimensions.
+TEST_F(IconUtilTest, TestCreateSkBitmapFromHICON) {
+ scoped_ptr<SkBitmap> bitmap;
+ base::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);
+
+ base::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
+// dimensions color depth etc.
+TEST_F(IconUtilTest, TestBasicCreateHICONFromSkBitmap) {
+ SkBitmap bitmap = CreateBlackSkBitmap(kSmallIconWidth, kSmallIconHeight);
+ 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);
+}
+
+// This test case makes sure that CreateIconFileFromImageFamily creates a
+// valid .ico file given an ImageFamily, and appropriately creates all icon
+// sizes from the given input.
+TEST_F(IconUtilTest, TestCreateIconFileFromImageFamily) {
+ gfx::ImageFamily image_family;
+ base::FilePath icon_filename =
+ temp_directory_.path().AppendASCII(kTempIconFilename);
+
+ // Test with only a 16x16 icon. Should only scale up to 48x48.
+ image_family.Add(gfx::Image::CreateFrom1xBitmap(
+ CreateBlackSkBitmap(kSmallIconWidth, kSmallIconHeight)));
+ ASSERT_TRUE(IconUtil::CreateIconFileFromImageFamily(image_family,
+ icon_filename));
+ CheckAllIconSizes(icon_filename, 48);
+
+ // Test with a 48x48 icon. Should only scale down.
+ image_family.Add(gfx::Image::CreateFrom1xBitmap(CreateBlackSkBitmap(48, 48)));
+ ASSERT_TRUE(IconUtil::CreateIconFileFromImageFamily(image_family,
+ icon_filename));
+ CheckAllIconSizes(icon_filename, 48);
+
+ // Test with a 64x64 icon. Should scale up to 256x256.
+ image_family.Add(gfx::Image::CreateFrom1xBitmap(CreateBlackSkBitmap(64, 64)));
+ ASSERT_TRUE(IconUtil::CreateIconFileFromImageFamily(image_family,
+ icon_filename));
+ CheckAllIconSizes(icon_filename, 256);
+
+ // Test with a 256x256 icon. Should include the 256x256 in the output.
+ image_family.Add(gfx::Image::CreateFrom1xBitmap(
+ CreateBlackSkBitmap(256, 256)));
+ ASSERT_TRUE(IconUtil::CreateIconFileFromImageFamily(image_family,
+ icon_filename));
+ CheckAllIconSizes(icon_filename, 256);
+
+ // Test with a 49x49 icon. Should scale up to 256x256, but exclude the
+ // original 49x49 representation from the output.
+ image_family.clear();
+ image_family.Add(gfx::Image::CreateFrom1xBitmap(CreateBlackSkBitmap(49, 49)));
+ ASSERT_TRUE(IconUtil::CreateIconFileFromImageFamily(image_family,
+ icon_filename));
+ CheckAllIconSizes(icon_filename, 256);
+
+ // Test with a non-square 16x32 icon. Should scale up to 48, but exclude the
+ // original 16x32 representation from the output.
+ image_family.clear();
+ image_family.Add(gfx::Image::CreateFrom1xBitmap(CreateBlackSkBitmap(16, 32)));
+ ASSERT_TRUE(IconUtil::CreateIconFileFromImageFamily(image_family,
+ icon_filename));
+ CheckAllIconSizes(icon_filename, 48);
+
+ // Test with a non-square 32x49 icon. Should scale up to 256, but exclude the
+ // original 32x49 representation from the output.
+ image_family.Add(gfx::Image::CreateFrom1xBitmap(CreateBlackSkBitmap(32, 49)));
+ ASSERT_TRUE(IconUtil::CreateIconFileFromImageFamily(image_family,
+ icon_filename));
+ CheckAllIconSizes(icon_filename, 256);
+
+ // Test with an empty and non-empty image.
+ // The empty image should be ignored.
+ image_family.clear();
+ image_family.Add(gfx::Image());
+ image_family.Add(gfx::Image::CreateFrom1xBitmap(CreateBlackSkBitmap(16, 16)));
+ ASSERT_TRUE(IconUtil::CreateIconFileFromImageFamily(image_family,
+ icon_filename));
+ CheckAllIconSizes(icon_filename, 48);
+}
+
+TEST_F(IconUtilTest, TestCreateSkBitmapFromIconResource48x48) {
+ HMODULE module = GetModuleHandle(NULL);
+ scoped_ptr<SkBitmap> bitmap(
+ IconUtil::CreateSkBitmapFromIconResource(module, IDR_MAINFRAME, 48));
+ ASSERT_TRUE(bitmap.get());
+ EXPECT_EQ(48, bitmap->width());
+ EXPECT_EQ(48, bitmap->height());
+}
+
+TEST_F(IconUtilTest, TestCreateSkBitmapFromIconResource256x256) {
+ HMODULE module = GetModuleHandle(NULL);
+ scoped_ptr<SkBitmap> bitmap(
+ IconUtil::CreateSkBitmapFromIconResource(module, IDR_MAINFRAME, 256));
+ ASSERT_TRUE(bitmap.get());
+ EXPECT_EQ(256, bitmap->width());
+ EXPECT_EQ(256, bitmap->height());
+}
+
+// This tests that kNumIconDimensionsUpToMediumSize has the correct value.
+TEST_F(IconUtilTest, TestNumIconDimensionsUpToMediumSize) {
+ ASSERT_LE(IconUtil::kNumIconDimensionsUpToMediumSize,
+ IconUtil::kNumIconDimensions);
+ EXPECT_EQ(IconUtil::kMediumIconSize,
+ IconUtil::kIconDimensions[
+ IconUtil::kNumIconDimensionsUpToMediumSize - 1]);
+}
diff --git a/chromium/ui/gfx/image/OWNERS b/chromium/ui/gfx/image/OWNERS
new file mode 100644
index 00000000000..d3273727c23
--- /dev/null
+++ b/chromium/ui/gfx/image/OWNERS
@@ -0,0 +1,4 @@
+rsesek@chromium.org
+
+# ImageSkia related classes except for _mac/_ios stuff
+per-file image_skia.*=oshima@chromium.org
diff --git a/chromium/ui/gfx/image/cairo_cached_surface.cc b/chromium/ui/gfx/image/cairo_cached_surface.cc
new file mode 100644
index 00000000000..2de4a3210b3
--- /dev/null
+++ b/chromium/ui/gfx/image/cairo_cached_surface.cc
@@ -0,0 +1,109 @@
+// 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 "ui/gfx/image/cairo_cached_surface.h"
+
+#include <gtk/gtk.h>
+
+#include "base/basictypes.h"
+#include "base/logging.h"
+
+namespace gfx {
+
+CairoCachedSurface::CairoCachedSurface() : pixbuf_(NULL) {
+}
+
+CairoCachedSurface::~CairoCachedSurface() {
+ Reset();
+}
+
+void CairoCachedSurface::Reset() {
+ for (SurfaceVector::iterator it = surface_map_.begin();
+ it != surface_map_.end(); ++it) {
+ cairo_surface_destroy(it->second);
+ }
+ surface_map_.clear();
+
+ if (pixbuf_) {
+ g_object_unref(pixbuf_);
+ pixbuf_ = NULL;
+ }
+}
+
+int CairoCachedSurface::Width() const {
+ return pixbuf_ ? gdk_pixbuf_get_width(pixbuf_) : -1;
+}
+
+int CairoCachedSurface::Height() const {
+ return pixbuf_ ? gdk_pixbuf_get_height(pixbuf_) : -1;
+}
+
+void CairoCachedSurface::UsePixbuf(GdkPixbuf* pixbuf) {
+ if (pixbuf)
+ g_object_ref(pixbuf);
+
+ Reset();
+
+ pixbuf_ = pixbuf;
+}
+
+void CairoCachedSurface::SetSource(cairo_t* cr, GtkWidget* widget,
+ int x, int y) const {
+ SetSource(cr, gtk_widget_get_display(widget), x, y);
+}
+
+void CairoCachedSurface::SetSource(cairo_t* cr, GdkDisplay* display,
+ int x, int y) const {
+ DCHECK(pixbuf_);
+ DCHECK(cr);
+ DCHECK(display);
+
+ cairo_surface_t* surface = GetSurfaceFor(cr, display);
+ cairo_set_source_surface(cr, surface, x, y);
+}
+
+void CairoCachedSurface::MaskSource(cairo_t* cr, GtkWidget* widget,
+ int x, int y) const {
+ MaskSource(cr, gtk_widget_get_display(widget), x, y);
+}
+
+void CairoCachedSurface::MaskSource(cairo_t* cr, GdkDisplay* display,
+ int x, int y) const {
+ DCHECK(pixbuf_);
+ DCHECK(cr);
+ DCHECK(display);
+
+ cairo_surface_t* surface = GetSurfaceFor(cr, display);
+ cairo_mask_surface(cr, surface, x, y);
+}
+
+cairo_surface_t* CairoCachedSurface::GetSurfaceFor(cairo_t* cr,
+ GdkDisplay* display) const {
+ for (SurfaceVector::const_iterator it = surface_map_.begin();
+ it != surface_map_.end(); ++it) {
+ if (display == it->first) {
+ return it->second;
+ }
+ }
+
+ // First time here since last UsePixbuf call. Generate the surface.
+ cairo_surface_t* target = cairo_get_target(cr);
+ cairo_surface_t* out = cairo_surface_create_similar(
+ target,
+ CAIRO_CONTENT_COLOR_ALPHA,
+ gdk_pixbuf_get_width(pixbuf_),
+ gdk_pixbuf_get_height(pixbuf_));
+
+ DCHECK(out);
+
+ cairo_t* copy_cr = cairo_create(out);
+ gdk_cairo_set_source_pixbuf(copy_cr, pixbuf_, 0, 0);
+ cairo_paint(copy_cr);
+ cairo_destroy(copy_cr);
+
+ surface_map_.push_back(std::make_pair(display, out));
+ return out;
+}
+
+} // namespace gfx
diff --git a/chromium/ui/gfx/image/cairo_cached_surface.h b/chromium/ui/gfx/image/cairo_cached_surface.h
new file mode 100644
index 00000000000..d29d9befd8c
--- /dev/null
+++ b/chromium/ui/gfx/image/cairo_cached_surface.h
@@ -0,0 +1,84 @@
+// Copyright (c) 2012 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_IMAGE_CAIRO_CACHED_SURFACE_H_
+#define UI_GFX_IMAGE_CAIRO_CACHED_SURFACE_H_
+
+#include <vector>
+
+#include "ui/base/ui_export.h"
+
+typedef struct _GdkDisplay GdkDisplay;
+typedef struct _GdkPixbuf GdkPixbuf;
+typedef struct _GtkWidget GtkWidget;
+typedef struct _cairo cairo_t;
+typedef struct _cairo_surface cairo_surface_t;
+
+namespace gfx {
+
+// A helper class that takes a GdkPixbuf* and renders it to the screen. Unlike
+// gdk_cairo_set_source_pixbuf(), CairoCachedSurface assumes that the pixbuf is
+// immutable after UsePixbuf() is called and can be sent to the display server
+// once. From then on, that cached version is used so we don't upload the same
+// image each and every time we expose.
+//
+// Most cached surfaces are owned by the GtkThemeService, which associates
+// them with a certain XDisplay. Some users of surfaces (CustomDrawButtonBase,
+// for example) own their surfaces instead since they interact with the
+// ResourceBundle instead of the GtkThemeService.
+class UI_EXPORT CairoCachedSurface {
+ public:
+ CairoCachedSurface();
+ ~CairoCachedSurface();
+
+ // Whether this CairoCachedSurface owns a GdkPixbuf.
+ bool valid() const {
+ return pixbuf_;
+ }
+
+ // Delete all our data.
+ void Reset();
+
+ // The dimensions of the underlying pixbuf/surface. (or -1 if invalid.)
+ int Width() const;
+ int Height() const;
+
+ // Sets the pixbuf that we pass to cairo. Calling UsePixbuf() only derefs the
+ // current pixbuf and surface (if they exist). Actually transfering data to
+ // the X server occurs at SetSource() time. Calling UsePixbuf() should only
+ // be done once as it clears cached data from the X server.
+ void UsePixbuf(GdkPixbuf* pixbuf);
+
+ // Sets our pixbuf as the active surface starting at (x, y), uploading it in
+ // case we don't have an X backed surface cached.
+ void SetSource(cairo_t* cr, GtkWidget* widget, int x, int y) const;
+ void SetSource(cairo_t* cr, GdkDisplay* display, int x, int y) const;
+
+ // Performs a mask operation, using this surface as the alpha channel.
+ void MaskSource(cairo_t* cr, GtkWidget* widget, int x, int y) const;
+ void MaskSource(cairo_t* cr, GdkDisplay* display, int x, int y) const;
+
+ // Raw access to the pixbuf. May be NULL. Used for a few gdk operations
+ // regarding window shaping.
+ GdkPixbuf* pixbuf() { return pixbuf_; }
+
+ private:
+ typedef std::vector<std::pair<GdkDisplay*, cairo_surface_t*> > SurfaceVector;
+
+ // Returns a surface . Caches results so only one copy of the image data
+ // lives on the display server.
+ cairo_surface_t* GetSurfaceFor(cairo_t* cr, GdkDisplay* display) const;
+
+ // The source pixbuf.
+ GdkPixbuf* pixbuf_;
+
+ // Our list of cached surfaces. 99% of the time, this will only contain a
+ // single entry. At most two. We need to get this right for multiple displays
+ // to work correct, since each GdkDisplay is a different display server.
+ mutable SurfaceVector surface_map_;
+};
+
+} // namespace gfx
+
+#endif // UI_GFX_IMAGE_CAIRO_CACHED_SURFACE_H_
diff --git a/chromium/ui/gfx/image/canvas_image_source.cc b/chromium/ui/gfx/image/canvas_image_source.cc
new file mode 100644
index 00000000000..747625ac60d
--- /dev/null
+++ b/chromium/ui/gfx/image/canvas_image_source.cc
@@ -0,0 +1,28 @@
+// Copyright (c) 2012 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 "ui/gfx/image/canvas_image_source.h"
+
+#include "base/logging.h"
+#include "ui/gfx/canvas.h"
+#include "ui/base/layout.h"
+
+namespace gfx {
+
+////////////////////////////////////////////////////////////////////////////////
+// CanvasImageSource
+
+CanvasImageSource::CanvasImageSource(const gfx::Size& size, bool is_opaque)
+ : size_(size),
+ is_opaque_(is_opaque) {
+}
+
+gfx::ImageSkiaRep CanvasImageSource::GetImageForScale(
+ ui::ScaleFactor scale_factor) {
+ gfx::Canvas canvas(size_, scale_factor, is_opaque_);
+ Draw(&canvas);
+ return canvas.ExtractImageRep();
+}
+
+} // namespace gfx
diff --git a/chromium/ui/gfx/image/canvas_image_source.h b/chromium/ui/gfx/image/canvas_image_source.h
new file mode 100644
index 00000000000..a713b95ce8d
--- /dev/null
+++ b/chromium/ui/gfx/image/canvas_image_source.h
@@ -0,0 +1,46 @@
+// Copyright (c) 2012 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_IMAGE_CANVAS_IMAGE_SOURCE_H_
+#define UI_GFX_IMAGE_CANVAS_IMAGE_SOURCE_H_
+
+#include "base/basictypes.h"
+#include "base/compiler_specific.h"
+#include "ui/base/ui_export.h"
+#include "ui/gfx/image/image_skia_source.h"
+#include "ui/gfx/size.h"
+
+namespace gfx {
+class Canvas;
+class ImageSkiaRep;
+
+// CanvasImageSource is useful if you need to generate an image for
+// a scale factor using gfx::Canvas. It creates a new Canvas
+// with target scale factor and generates ImageSkiaRep when drawing is
+// completed.
+class UI_EXPORT CanvasImageSource : public gfx::ImageSkiaSource {
+ public:
+ CanvasImageSource(const gfx::Size& size, bool is_opaque);
+
+ // Called when a new image needs to be drawn for a scale factor.
+ virtual void Draw(gfx::Canvas* canvas) = 0;
+
+ // Returns the size of images in DIP that this source will generate.
+ const gfx::Size& size() const { return size_; };
+
+ // Overridden from gfx::ImageSkiaSource.
+ virtual gfx::ImageSkiaRep GetImageForScale(
+ ui::ScaleFactor scale_factor) OVERRIDE;
+
+ protected:
+ virtual ~CanvasImageSource() {}
+
+ const gfx::Size size_;
+ const bool is_opaque_;
+ DISALLOW_COPY_AND_ASSIGN(CanvasImageSource);
+};
+
+} // namespace gfx
+
+#endif // UI_GFX_IMAGE_CANVAS_IMAGE_SOURCE_H_
diff --git a/chromium/ui/gfx/image/image.cc b/chromium/ui/gfx/image/image.cc
new file mode 100644
index 00000000000..b0733b7ccda
--- /dev/null
+++ b/chromium/ui/gfx/image/image.cc
@@ -0,0 +1,957 @@
+// Copyright (c) 2012 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 "ui/gfx/image/image.h"
+
+#include <algorithm>
+
+#include "base/logging.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/stl_util.h"
+#include "third_party/skia/include/core/SkBitmap.h"
+#include "ui/gfx/image/image_png_rep.h"
+#include "ui/gfx/image/image_skia.h"
+#include "ui/gfx/size.h"
+
+#if !defined(OS_IOS)
+#include "ui/gfx/codec/png_codec.h"
+#endif
+
+#if defined(TOOLKIT_GTK)
+#include <gdk-pixbuf/gdk-pixbuf.h>
+#include <gdk/gdk.h>
+#include <glib-object.h>
+#include "ui/base/gtk/scoped_gobject.h"
+#include "ui/gfx/canvas.h"
+#include "ui/gfx/gtk_util.h"
+#include "ui/gfx/image/cairo_cached_surface.h"
+#elif defined(OS_IOS)
+#include "base/mac/foundation_util.h"
+#include "ui/gfx/image/image_skia_util_ios.h"
+#elif defined(OS_MACOSX)
+#include "base/mac/mac_util.h"
+#include "ui/gfx/image/image_skia_util_mac.h"
+#endif
+
+namespace gfx {
+
+namespace internal {
+
+#if defined(TOOLKIT_GTK)
+const ImageSkia ImageSkiaFromGdkPixbuf(GdkPixbuf* pixbuf) {
+ CHECK(pixbuf);
+ gfx::Canvas canvas(gfx::Size(gdk_pixbuf_get_width(pixbuf),
+ gdk_pixbuf_get_height(pixbuf)),
+ ui::SCALE_FACTOR_100P,
+ false);
+ skia::ScopedPlatformPaint scoped_platform_paint(canvas.sk_canvas());
+ cairo_t* cr = scoped_platform_paint.GetPlatformSurface();
+ gdk_cairo_set_source_pixbuf(cr, pixbuf, 0, 0);
+ cairo_paint(cr);
+ return ImageSkia(canvas.ExtractImageRep());
+}
+
+// Returns a 16x16 red pixbuf to visually show error in decoding PNG.
+// Also logs error to console.
+GdkPixbuf* GetErrorPixbuf() {
+ LOG(ERROR) << "Unable to decode PNG.";
+ GdkPixbuf* pixbuf = gdk_pixbuf_new(GDK_COLORSPACE_RGB, TRUE, 8, 16, 16);
+ gdk_pixbuf_fill(pixbuf, 0xff0000ff);
+ return pixbuf;
+}
+
+GdkPixbuf* GdkPixbufFromPNG(
+ const std::vector<gfx::ImagePNGRep>& image_png_reps) {
+ scoped_refptr<base::RefCountedMemory> png_bytes(NULL);
+ for (size_t i = 0; i < image_png_reps.size(); ++i) {
+ if (image_png_reps[i].scale_factor == ui::SCALE_FACTOR_100P)
+ png_bytes = image_png_reps[i].raw_data;
+ }
+
+ if (!png_bytes.get())
+ return GetErrorPixbuf();
+
+ GdkPixbuf* pixbuf = NULL;
+ ui::ScopedGObject<GdkPixbufLoader>::Type loader(gdk_pixbuf_loader_new());
+
+ bool ok = gdk_pixbuf_loader_write(loader.get(),
+ reinterpret_cast<const guint8*>(png_bytes->front()), png_bytes->size(),
+ NULL);
+
+ // Calling gdk_pixbuf_loader_close forces the data to be parsed by the
+ // loader. This must be done before calling gdk_pixbuf_loader_get_pixbuf.
+ if (ok)
+ ok = gdk_pixbuf_loader_close(loader.get(), NULL);
+ if (ok)
+ pixbuf = gdk_pixbuf_loader_get_pixbuf(loader.get());
+
+ if (pixbuf) {
+ // The pixbuf is owned by the scoped loader which will delete its ref when
+ // it goes out of scope. Add a ref so that the pixbuf still exists.
+ g_object_ref(pixbuf);
+ } else {
+ return GetErrorPixbuf();
+ }
+
+ return pixbuf;
+}
+
+scoped_refptr<base::RefCountedMemory> Get1xPNGBytesFromPixbuf(
+ GdkPixbuf* pixbuf) {
+ gchar* image = NULL;
+ gsize image_size;
+ GError* error = NULL;
+ CHECK(gdk_pixbuf_save_to_buffer(
+ pixbuf, &image, &image_size, "png", &error, NULL));
+ scoped_refptr<base::RefCountedBytes> png_bytes(
+ new base::RefCountedBytes());
+ png_bytes->data().assign(image, image + image_size);
+ g_free(image);
+ return png_bytes;
+}
+
+#endif // defined(TOOLKIT_GTK)
+
+#if defined(OS_IOS)
+scoped_refptr<base::RefCountedMemory> Get1xPNGBytesFromUIImage(
+ UIImage* uiimage);
+// Caller takes ownership of the returned UIImage.
+UIImage* CreateUIImageFromPNG(
+ const std::vector<gfx::ImagePNGRep>& image_png_reps);
+gfx::Size UIImageSize(UIImage* image);
+#elif defined(OS_MACOSX)
+scoped_refptr<base::RefCountedMemory> Get1xPNGBytesFromNSImage(
+ NSImage* nsimage);
+// Caller takes ownership of the returned NSImage.
+NSImage* NSImageFromPNG(const std::vector<gfx::ImagePNGRep>& image_png_reps,
+ CGColorSpaceRef color_space);
+gfx::Size NSImageSize(NSImage* image);
+#endif // defined(OS_MACOSX)
+
+#if defined(OS_IOS)
+ImageSkia* ImageSkiaFromPNG(
+ const std::vector<gfx::ImagePNGRep>& image_png_reps);
+scoped_refptr<base::RefCountedMemory> Get1xPNGBytesFromImageSkia(
+ const ImageSkia* skia);
+#else
+// Returns a 16x16 red image to visually show error in decoding PNG.
+// Caller takes ownership of returned ImageSkia.
+ImageSkia* GetErrorImageSkia() {
+ SkBitmap bitmap;
+ bitmap.setConfig(SkBitmap::kARGB_8888_Config, 16, 16);
+ bitmap.allocPixels();
+ bitmap.eraseRGB(0xff, 0, 0);
+ return new gfx::ImageSkia(gfx::ImageSkiaRep(bitmap, ui::SCALE_FACTOR_100P));
+}
+
+ImageSkia* ImageSkiaFromPNG(
+ const std::vector<gfx::ImagePNGRep>& image_png_reps) {
+ if (image_png_reps.empty())
+ return GetErrorImageSkia();
+
+ scoped_ptr<gfx::ImageSkia> image_skia(new ImageSkia());
+ for (size_t i = 0; i < image_png_reps.size(); ++i) {
+ scoped_refptr<base::RefCountedMemory> raw_data =
+ image_png_reps[i].raw_data;
+ CHECK(raw_data.get());
+ SkBitmap bitmap;
+ if (!gfx::PNGCodec::Decode(raw_data->front(), raw_data->size(),
+ &bitmap)) {
+ LOG(ERROR) << "Unable to decode PNG for "
+ << ui::GetScaleFactorScale(image_png_reps[i].scale_factor)
+ << ".";
+ return GetErrorImageSkia();
+ }
+ image_skia->AddRepresentation(gfx::ImageSkiaRep(
+ bitmap, image_png_reps[i].scale_factor));
+ }
+ return image_skia.release();
+}
+
+scoped_refptr<base::RefCountedMemory> Get1xPNGBytesFromImageSkia(
+ const ImageSkia* image_skia) {
+ ImageSkiaRep image_skia_rep = image_skia->GetRepresentation(
+ ui::SCALE_FACTOR_100P);
+
+ scoped_refptr<base::RefCountedBytes> png_bytes(new base::RefCountedBytes());
+ if (image_skia_rep.scale_factor() != ui::SCALE_FACTOR_100P ||
+ !gfx::PNGCodec::EncodeBGRASkBitmap(image_skia_rep.sk_bitmap(), false,
+ &png_bytes->data())) {
+ return NULL;
+ }
+ return png_bytes;
+}
+#endif
+
+class ImageRepPNG;
+class ImageRepSkia;
+class ImageRepGdk;
+class ImageRepCairo;
+class ImageRepCocoa;
+class ImageRepCocoaTouch;
+
+// An ImageRep is the object that holds the backing memory for an Image. Each
+// RepresentationType has an ImageRep subclass that is responsible for freeing
+// the memory that the ImageRep holds. When an ImageRep is created, it expects
+// to take ownership of the image, without having to retain it or increase its
+// reference count.
+class ImageRep {
+ public:
+ explicit ImageRep(Image::RepresentationType rep) : type_(rep) {}
+
+ // Deletes the associated pixels of an ImageRep.
+ virtual ~ImageRep() {}
+
+ // Cast helpers ("fake RTTI").
+ ImageRepPNG* AsImageRepPNG() {
+ CHECK_EQ(type_, Image::kImageRepPNG);
+ return reinterpret_cast<ImageRepPNG*>(this);
+ }
+
+ ImageRepSkia* AsImageRepSkia() {
+ CHECK_EQ(type_, Image::kImageRepSkia);
+ return reinterpret_cast<ImageRepSkia*>(this);
+ }
+
+#if defined(TOOLKIT_GTK)
+ ImageRepGdk* AsImageRepGdk() {
+ CHECK_EQ(type_, Image::kImageRepGdk);
+ return reinterpret_cast<ImageRepGdk*>(this);
+ }
+
+ ImageRepCairo* AsImageRepCairo() {
+ CHECK_EQ(type_, Image::kImageRepCairo);
+ return reinterpret_cast<ImageRepCairo*>(this);
+ }
+#endif
+
+#if defined(OS_IOS)
+ ImageRepCocoaTouch* AsImageRepCocoaTouch() {
+ CHECK_EQ(type_, Image::kImageRepCocoaTouch);
+ return reinterpret_cast<ImageRepCocoaTouch*>(this);
+ }
+#elif defined(OS_MACOSX)
+ ImageRepCocoa* AsImageRepCocoa() {
+ CHECK_EQ(type_, Image::kImageRepCocoa);
+ return reinterpret_cast<ImageRepCocoa*>(this);
+ }
+#endif
+
+ Image::RepresentationType type() const { return type_; }
+
+ virtual int Width() const = 0;
+ virtual int Height() const = 0;
+ virtual gfx::Size Size() const = 0;
+
+ private:
+ Image::RepresentationType type_;
+};
+
+class ImageRepPNG : public ImageRep {
+ public:
+ ImageRepPNG() : ImageRep(Image::kImageRepPNG) {
+ }
+
+ ImageRepPNG(const std::vector<ImagePNGRep>& image_png_reps)
+ : ImageRep(Image::kImageRepPNG),
+ image_png_reps_(image_png_reps) {
+ }
+
+ virtual ~ImageRepPNG() {
+ }
+
+ virtual int Width() const OVERRIDE {
+ return Size().width();
+ }
+
+ virtual int Height() const OVERRIDE {
+ return Size().height();
+ }
+
+ virtual gfx::Size Size() const OVERRIDE {
+ // Read the PNG data to get the image size, caching it.
+ if (!size_cache_) {
+ for (std::vector<ImagePNGRep>::const_iterator it = image_reps().begin();
+ it != image_reps().end(); ++it) {
+ if (it->scale_factor == ui::SCALE_FACTOR_100P) {
+ size_cache_.reset(new gfx::Size(it->Size()));
+ return *size_cache_;
+ }
+ }
+ size_cache_.reset(new gfx::Size);
+ }
+
+ return *size_cache_;
+ }
+
+ const std::vector<ImagePNGRep>& image_reps() const { return image_png_reps_; }
+
+ private:
+ std::vector<ImagePNGRep> image_png_reps_;
+
+ // Cached to avoid having to parse the raw data multiple times.
+ mutable scoped_ptr<gfx::Size> size_cache_;
+
+ DISALLOW_COPY_AND_ASSIGN(ImageRepPNG);
+};
+
+class ImageRepSkia : public ImageRep {
+ public:
+ // Takes ownership of |image|.
+ explicit ImageRepSkia(ImageSkia* image)
+ : ImageRep(Image::kImageRepSkia),
+ image_(image) {
+ }
+
+ virtual ~ImageRepSkia() {
+ }
+
+ virtual int Width() const OVERRIDE {
+ return image_->width();
+ }
+
+ virtual int Height() const OVERRIDE {
+ return image_->height();
+ }
+
+ virtual gfx::Size Size() const OVERRIDE {
+ return image_->size();
+ }
+
+ ImageSkia* image() { return image_.get(); }
+
+ private:
+ scoped_ptr<ImageSkia> image_;
+
+ DISALLOW_COPY_AND_ASSIGN(ImageRepSkia);
+};
+
+#if defined(TOOLKIT_GTK)
+class ImageRepGdk : public ImageRep {
+ public:
+ explicit ImageRepGdk(GdkPixbuf* pixbuf)
+ : ImageRep(Image::kImageRepGdk),
+ pixbuf_(pixbuf) {
+ CHECK(pixbuf);
+ }
+
+ virtual ~ImageRepGdk() {
+ if (pixbuf_) {
+ g_object_unref(pixbuf_);
+ pixbuf_ = NULL;
+ }
+ }
+
+ virtual int Width() const OVERRIDE {
+ return gdk_pixbuf_get_width(pixbuf_);
+ }
+
+ virtual int Height() const OVERRIDE {
+ return gdk_pixbuf_get_height(pixbuf_);
+ }
+
+ virtual gfx::Size Size() const OVERRIDE {
+ return gfx::Size(Width(), Height());
+ }
+
+ GdkPixbuf* pixbuf() const { return pixbuf_; }
+
+ private:
+ GdkPixbuf* pixbuf_;
+
+ DISALLOW_COPY_AND_ASSIGN(ImageRepGdk);
+};
+
+// Represents data that lives on the display server instead of in the client.
+class ImageRepCairo : public ImageRep {
+ public:
+ explicit ImageRepCairo(GdkPixbuf* pixbuf)
+ : ImageRep(Image::kImageRepCairo),
+ cairo_cache_(new CairoCachedSurface) {
+ CHECK(pixbuf);
+ cairo_cache_->UsePixbuf(pixbuf);
+ }
+
+ virtual ~ImageRepCairo() {
+ delete cairo_cache_;
+ }
+
+ virtual int Width() const OVERRIDE {
+ return cairo_cache_->Width();
+ }
+
+ virtual int Height() const OVERRIDE {
+ return cairo_cache_->Height();
+ }
+
+ virtual gfx::Size Size() const OVERRIDE {
+ return gfx::Size(Width(), Height());
+ }
+
+ CairoCachedSurface* surface() const { return cairo_cache_; }
+
+ private:
+ CairoCachedSurface* cairo_cache_;
+
+ DISALLOW_COPY_AND_ASSIGN(ImageRepCairo);
+};
+#endif // defined(TOOLKIT_GTK)
+
+#if defined(OS_IOS)
+class ImageRepCocoaTouch : public ImageRep {
+ public:
+ explicit ImageRepCocoaTouch(UIImage* image)
+ : ImageRep(Image::kImageRepCocoaTouch),
+ image_(image) {
+ CHECK(image);
+ }
+
+ virtual ~ImageRepCocoaTouch() {
+ base::mac::NSObjectRelease(image_);
+ image_ = nil;
+ }
+
+ virtual int Width() const OVERRIDE {
+ return Size().width();
+ }
+
+ virtual int Height() const OVERRIDE {
+ return Size().height();
+ }
+
+ virtual gfx::Size Size() const OVERRIDE {
+ return internal::UIImageSize(image_);
+ }
+
+ UIImage* image() const { return image_; }
+
+ private:
+ UIImage* image_;
+
+ DISALLOW_COPY_AND_ASSIGN(ImageRepCocoaTouch);
+};
+#elif defined(OS_MACOSX)
+class ImageRepCocoa : public ImageRep {
+ public:
+ explicit ImageRepCocoa(NSImage* image)
+ : ImageRep(Image::kImageRepCocoa),
+ image_(image) {
+ CHECK(image);
+ }
+
+ virtual ~ImageRepCocoa() {
+ base::mac::NSObjectRelease(image_);
+ image_ = nil;
+ }
+
+ virtual int Width() const OVERRIDE {
+ return Size().width();
+ }
+
+ virtual int Height() const OVERRIDE {
+ return Size().height();
+ }
+
+ virtual gfx::Size Size() const OVERRIDE {
+ return internal::NSImageSize(image_);
+ }
+
+ NSImage* image() const { return image_; }
+
+ private:
+ NSImage* image_;
+
+ DISALLOW_COPY_AND_ASSIGN(ImageRepCocoa);
+};
+#endif // defined(OS_MACOSX)
+
+// The Storage class acts similarly to the pixels in a SkBitmap: the Image
+// class holds a refptr instance of Storage, which in turn holds all the
+// ImageReps. This way, the Image can be cheaply copied.
+class ImageStorage : public base::RefCounted<ImageStorage> {
+ public:
+ ImageStorage(gfx::Image::RepresentationType default_type)
+ : default_representation_type_(default_type),
+#if defined(OS_MACOSX) && !defined(OS_IOS)
+ default_representation_color_space_(
+ base::mac::GetGenericRGBColorSpace()),
+#endif // defined(OS_MACOSX) && !defined(OS_IOS)
+ representations_deleter_(&representations_) {
+ }
+
+ gfx::Image::RepresentationType default_representation_type() {
+ return default_representation_type_;
+ }
+ gfx::Image::RepresentationMap& representations() { return representations_; }
+
+#if defined(OS_MACOSX) && !defined(OS_IOS)
+ void set_default_representation_color_space(CGColorSpaceRef color_space) {
+ default_representation_color_space_ = color_space;
+ }
+ CGColorSpaceRef default_representation_color_space() {
+ return default_representation_color_space_;
+ }
+#endif // defined(OS_MACOSX) && !defined(OS_IOS)
+
+ private:
+ friend class base::RefCounted<ImageStorage>;
+
+ ~ImageStorage() {}
+
+ // The type of image that was passed to the constructor. This key will always
+ // exist in the |representations_| map.
+ gfx::Image::RepresentationType default_representation_type_;
+
+#if defined(OS_MACOSX) && !defined(OS_IOS)
+ // The default representation's colorspace. This is used for converting to
+ // NSImage. This field exists to compensate for PNGCodec not writing or
+ // reading colorspace ancillary chunks. (sRGB, iCCP).
+ // Not owned.
+ CGColorSpaceRef default_representation_color_space_;
+#endif // defined(OS_MACOSX) && !defined(OS_IOS)
+
+ // All the representations of an Image. Size will always be at least one, with
+ // more for any converted representations.
+ gfx::Image::RepresentationMap representations_;
+
+ STLValueDeleter<Image::RepresentationMap> representations_deleter_;
+
+ DISALLOW_COPY_AND_ASSIGN(ImageStorage);
+};
+
+} // namespace internal
+
+Image::Image() {
+ // |storage_| is NULL for empty Images.
+}
+
+Image::Image(const std::vector<ImagePNGRep>& image_reps) {
+ // Do not store obviously invalid ImagePNGReps.
+ std::vector<ImagePNGRep> filtered;
+ for (size_t i = 0; i < image_reps.size(); ++i) {
+ if (image_reps[i].raw_data.get() && image_reps[i].raw_data->size())
+ filtered.push_back(image_reps[i]);
+ }
+
+ if (filtered.empty())
+ return;
+
+ storage_ = new internal::ImageStorage(Image::kImageRepPNG);
+ internal::ImageRepPNG* rep = new internal::ImageRepPNG(filtered);
+ AddRepresentation(rep);
+}
+
+Image::Image(const ImageSkia& image) {
+ if (!image.isNull()) {
+ storage_ = new internal::ImageStorage(Image::kImageRepSkia);
+ internal::ImageRepSkia* rep = new internal::ImageRepSkia(
+ new ImageSkia(image));
+ AddRepresentation(rep);
+ }
+}
+
+#if defined(TOOLKIT_GTK)
+Image::Image(GdkPixbuf* pixbuf) {
+ if (pixbuf) {
+ storage_ = new internal::ImageStorage(Image::kImageRepGdk);
+ internal::ImageRepGdk* rep = new internal::ImageRepGdk(pixbuf);
+ AddRepresentation(rep);
+ }
+}
+#endif
+
+#if defined(OS_IOS)
+Image::Image(UIImage* image)
+ : storage_(new internal::ImageStorage(Image::kImageRepCocoaTouch)) {
+ if (image) {
+ internal::ImageRepCocoaTouch* rep = new internal::ImageRepCocoaTouch(image);
+ AddRepresentation(rep);
+ }
+}
+#elif defined(OS_MACOSX)
+Image::Image(NSImage* image) {
+ if (image) {
+ storage_ = new internal::ImageStorage(Image::kImageRepCocoa);
+ internal::ImageRepCocoa* rep = new internal::ImageRepCocoa(image);
+ AddRepresentation(rep);
+ }
+}
+#endif
+
+Image::Image(const Image& other) : storage_(other.storage_) {
+}
+
+Image& Image::operator=(const Image& other) {
+ storage_ = other.storage_;
+ return *this;
+}
+
+Image::~Image() {
+}
+
+// static
+Image Image::CreateFrom1xBitmap(const SkBitmap& bitmap) {
+ return gfx::Image(ImageSkia::CreateFrom1xBitmap(bitmap));
+}
+
+// static
+Image Image::CreateFrom1xPNGBytes(const unsigned char* input,
+ size_t input_size) {
+ if (input_size == 0u)
+ return gfx::Image();
+
+ scoped_refptr<base::RefCountedBytes> raw_data(new base::RefCountedBytes());
+ raw_data->data().assign(input, input + input_size);
+ std::vector<gfx::ImagePNGRep> image_reps;
+ image_reps.push_back(ImagePNGRep(raw_data, ui::SCALE_FACTOR_100P));
+ return gfx::Image(image_reps);
+}
+
+const SkBitmap* Image::ToSkBitmap() const {
+ // Possibly create and cache an intermediate ImageRepSkia.
+ return ToImageSkia()->bitmap();
+}
+
+const ImageSkia* Image::ToImageSkia() const {
+ internal::ImageRep* rep = GetRepresentation(kImageRepSkia, false);
+ if (!rep) {
+ switch (DefaultRepresentationType()) {
+ case kImageRepPNG: {
+ internal::ImageRepPNG* png_rep =
+ GetRepresentation(kImageRepPNG, true)->AsImageRepPNG();
+ rep = new internal::ImageRepSkia(
+ internal::ImageSkiaFromPNG(png_rep->image_reps()));
+ break;
+ }
+#if defined(TOOLKIT_GTK)
+ case kImageRepGdk: {
+ internal::ImageRepGdk* native_rep =
+ GetRepresentation(kImageRepGdk, true)->AsImageRepGdk();
+ rep = new internal::ImageRepSkia(new ImageSkia(
+ internal::ImageSkiaFromGdkPixbuf(native_rep->pixbuf())));
+ break;
+ }
+#elif defined(OS_IOS)
+ case kImageRepCocoaTouch: {
+ internal::ImageRepCocoaTouch* native_rep =
+ GetRepresentation(kImageRepCocoaTouch, true)
+ ->AsImageRepCocoaTouch();
+ rep = new internal::ImageRepSkia(new ImageSkia(
+ ImageSkiaFromUIImage(native_rep->image())));
+ break;
+ }
+#elif defined(OS_MACOSX)
+ case kImageRepCocoa: {
+ internal::ImageRepCocoa* native_rep =
+ GetRepresentation(kImageRepCocoa, true)->AsImageRepCocoa();
+ rep = new internal::ImageRepSkia(new ImageSkia(
+ ImageSkiaFromNSImage(native_rep->image())));
+ break;
+ }
+#endif
+ default:
+ NOTREACHED();
+ }
+ CHECK(rep);
+ AddRepresentation(rep);
+ }
+ return rep->AsImageRepSkia()->image();
+}
+
+#if defined(TOOLKIT_GTK)
+GdkPixbuf* Image::ToGdkPixbuf() const {
+ internal::ImageRep* rep = GetRepresentation(kImageRepGdk, false);
+ if (!rep) {
+ switch (DefaultRepresentationType()) {
+ case kImageRepPNG: {
+ internal::ImageRepPNG* png_rep =
+ GetRepresentation(kImageRepPNG, true)->AsImageRepPNG();
+ rep = new internal::ImageRepGdk(internal::GdkPixbufFromPNG(
+ png_rep->image_reps()));
+ break;
+ }
+ case kImageRepSkia: {
+ internal::ImageRepSkia* skia_rep =
+ GetRepresentation(kImageRepSkia, true)->AsImageRepSkia();
+ rep = new internal::ImageRepGdk(gfx::GdkPixbufFromSkBitmap(
+ *skia_rep->image()->bitmap()));
+ break;
+ }
+ default:
+ NOTREACHED();
+ }
+ CHECK(rep);
+ AddRepresentation(rep);
+ }
+ return rep->AsImageRepGdk()->pixbuf();
+}
+
+CairoCachedSurface* const Image::ToCairo() const {
+ internal::ImageRep* rep = GetRepresentation(kImageRepCairo, false);
+ if (!rep) {
+ // Handle any-to-Cairo conversion. This may create and cache an intermediate
+ // pixbuf before sending the data to the display server.
+ rep = new internal::ImageRepCairo(ToGdkPixbuf());
+ CHECK(rep);
+ AddRepresentation(rep);
+ }
+ return rep->AsImageRepCairo()->surface();
+}
+#endif
+
+#if defined(OS_IOS)
+UIImage* Image::ToUIImage() const {
+ internal::ImageRep* rep = GetRepresentation(kImageRepCocoaTouch, false);
+ if (!rep) {
+ switch (DefaultRepresentationType()) {
+ case kImageRepPNG: {
+ internal::ImageRepPNG* png_rep =
+ GetRepresentation(kImageRepPNG, true)->AsImageRepPNG();
+ rep = new internal::ImageRepCocoaTouch(internal::CreateUIImageFromPNG(
+ png_rep->image_reps()));
+ break;
+ }
+ case kImageRepSkia: {
+ internal::ImageRepSkia* skia_rep =
+ GetRepresentation(kImageRepSkia, true)->AsImageRepSkia();
+ UIImage* image = UIImageFromImageSkia(*skia_rep->image());
+ base::mac::NSObjectRetain(image);
+ rep = new internal::ImageRepCocoaTouch(image);
+ break;
+ }
+ default:
+ NOTREACHED();
+ }
+ CHECK(rep);
+ AddRepresentation(rep);
+ }
+ return rep->AsImageRepCocoaTouch()->image();
+}
+#elif defined(OS_MACOSX)
+NSImage* Image::ToNSImage() const {
+ internal::ImageRep* rep = GetRepresentation(kImageRepCocoa, false);
+ if (!rep) {
+ CGColorSpaceRef default_representation_color_space =
+ storage_->default_representation_color_space();
+
+ switch (DefaultRepresentationType()) {
+ case kImageRepPNG: {
+ internal::ImageRepPNG* png_rep =
+ GetRepresentation(kImageRepPNG, true)->AsImageRepPNG();
+ rep = new internal::ImageRepCocoa(internal::NSImageFromPNG(
+ png_rep->image_reps(), default_representation_color_space));
+ break;
+ }
+ case kImageRepSkia: {
+ internal::ImageRepSkia* skia_rep =
+ GetRepresentation(kImageRepSkia, true)->AsImageRepSkia();
+ NSImage* image = NSImageFromImageSkiaWithColorSpace(*skia_rep->image(),
+ default_representation_color_space);
+ base::mac::NSObjectRetain(image);
+ rep = new internal::ImageRepCocoa(image);
+ break;
+ }
+ default:
+ NOTREACHED();
+ }
+ CHECK(rep);
+ AddRepresentation(rep);
+ }
+ return rep->AsImageRepCocoa()->image();
+}
+#endif
+
+scoped_refptr<base::RefCountedMemory> Image::As1xPNGBytes() const {
+ if (IsEmpty())
+ return new base::RefCountedBytes();
+
+ internal::ImageRep* rep = GetRepresentation(kImageRepPNG, false);
+
+ if (rep) {
+ const std::vector<gfx::ImagePNGRep>& image_png_reps =
+ rep->AsImageRepPNG()->image_reps();
+ for (size_t i = 0; i < image_png_reps.size(); ++i) {
+ if (image_png_reps[i].scale_factor == ui::SCALE_FACTOR_100P)
+ return image_png_reps[i].raw_data;
+ }
+ return new base::RefCountedBytes();
+ }
+
+ scoped_refptr<base::RefCountedMemory> png_bytes(NULL);
+ switch (DefaultRepresentationType()) {
+#if defined(TOOLKIT_GTK)
+ case kImageRepGdk: {
+ internal::ImageRepGdk* gdk_rep =
+ GetRepresentation(kImageRepGdk, true)->AsImageRepGdk();
+ png_bytes = internal::Get1xPNGBytesFromPixbuf(gdk_rep->pixbuf());
+ break;
+ }
+#elif defined(OS_IOS)
+ case kImageRepCocoaTouch: {
+ internal::ImageRepCocoaTouch* cocoa_touch_rep =
+ GetRepresentation(kImageRepCocoaTouch, true)
+ ->AsImageRepCocoaTouch();
+ png_bytes = internal::Get1xPNGBytesFromUIImage(
+ cocoa_touch_rep->image());
+ break;
+ }
+#elif defined(OS_MACOSX)
+ case kImageRepCocoa: {
+ internal::ImageRepCocoa* cocoa_rep =
+ GetRepresentation(kImageRepCocoa, true)->AsImageRepCocoa();
+ png_bytes = internal::Get1xPNGBytesFromNSImage(cocoa_rep->image());
+ break;
+ }
+#endif
+ case kImageRepSkia: {
+ internal::ImageRepSkia* skia_rep =
+ GetRepresentation(kImageRepSkia, true)->AsImageRepSkia();
+ png_bytes = internal::Get1xPNGBytesFromImageSkia(skia_rep->image());
+ break;
+ }
+ default:
+ NOTREACHED();
+ }
+ if (!png_bytes.get() || !png_bytes->size()) {
+ // Add an ImageRepPNG with no data such that the conversion is not
+ // attempted each time we want the PNG bytes.
+ AddRepresentation(new internal::ImageRepPNG());
+ return new base::RefCountedBytes();
+ }
+
+ // Do not insert representations for scale factors other than 1x even if
+ // they are available because:
+ // - Only the 1x PNG bytes can be accessed.
+ // - ImageRepPNG is not used as an intermediate type in converting to a
+ // final type eg (converting from ImageRepSkia to ImageRepPNG to get an
+ // ImageRepCocoa).
+ std::vector<ImagePNGRep> image_png_reps;
+ image_png_reps.push_back(gfx::ImagePNGRep(png_bytes,
+ ui::SCALE_FACTOR_100P));
+ rep = new internal::ImageRepPNG(image_png_reps);
+ AddRepresentation(rep);
+ return png_bytes;
+}
+
+SkBitmap Image::AsBitmap() const {
+ return IsEmpty() ? SkBitmap() : *ToSkBitmap();
+}
+
+ImageSkia Image::AsImageSkia() const {
+ return IsEmpty() ? ImageSkia() : *ToImageSkia();
+}
+
+#if defined(OS_MACOSX) && !defined(OS_IOS)
+NSImage* Image::AsNSImage() const {
+ return IsEmpty() ? nil : ToNSImage();
+}
+#endif
+
+scoped_refptr<base::RefCountedMemory> Image::Copy1xPNGBytes() const {
+ scoped_refptr<base::RefCountedMemory> original = As1xPNGBytes();
+ scoped_refptr<base::RefCountedBytes> copy(new base::RefCountedBytes());
+ copy->data().assign(original->front(), original->front() + original->size());
+ return copy;
+}
+
+ImageSkia* Image::CopyImageSkia() const {
+ return new ImageSkia(*ToImageSkia());
+}
+
+SkBitmap* Image::CopySkBitmap() const {
+ return new SkBitmap(*ToSkBitmap());
+}
+
+#if defined(TOOLKIT_GTK)
+GdkPixbuf* Image::CopyGdkPixbuf() const {
+ GdkPixbuf* pixbuf = ToGdkPixbuf();
+ g_object_ref(pixbuf);
+ return pixbuf;
+}
+#endif
+
+#if defined(OS_IOS)
+UIImage* Image::CopyUIImage() const {
+ UIImage* image = ToUIImage();
+ base::mac::NSObjectRetain(image);
+ return image;
+}
+#elif defined(OS_MACOSX)
+NSImage* Image::CopyNSImage() const {
+ NSImage* image = ToNSImage();
+ base::mac::NSObjectRetain(image);
+ return image;
+}
+#endif
+
+bool Image::HasRepresentation(RepresentationType type) const {
+ return storage_.get() && storage_->representations().count(type) != 0;
+}
+
+size_t Image::RepresentationCount() const {
+ if (!storage_.get())
+ return 0;
+
+ return storage_->representations().size();
+}
+
+bool Image::IsEmpty() const {
+ return RepresentationCount() == 0;
+}
+
+int Image::Width() const {
+ if (IsEmpty())
+ return 0;
+ return GetRepresentation(DefaultRepresentationType(), true)->Width();
+}
+
+int Image::Height() const {
+ if (IsEmpty())
+ return 0;
+ return GetRepresentation(DefaultRepresentationType(), true)->Height();
+}
+
+gfx::Size Image::Size() const {
+ if (IsEmpty())
+ return gfx::Size();
+ return GetRepresentation(DefaultRepresentationType(), true)->Size();
+}
+
+void Image::SwapRepresentations(gfx::Image* other) {
+ storage_.swap(other->storage_);
+}
+
+#if defined(OS_MACOSX) && !defined(OS_IOS)
+void Image::SetSourceColorSpace(CGColorSpaceRef color_space) {
+ if (storage_.get())
+ storage_->set_default_representation_color_space(color_space);
+}
+#endif // defined(OS_MACOSX) && !defined(OS_IOS)
+
+Image::RepresentationType Image::DefaultRepresentationType() const {
+ CHECK(storage_.get());
+ RepresentationType default_type = storage_->default_representation_type();
+ // The conversions above assume that the default representation type is never
+ // kImageRepCairo.
+ DCHECK_NE(default_type, kImageRepCairo);
+ return default_type;
+}
+
+internal::ImageRep* Image::GetRepresentation(
+ RepresentationType rep_type, bool must_exist) const {
+ CHECK(storage_.get());
+ RepresentationMap::iterator it = storage_->representations().find(rep_type);
+ if (it == storage_->representations().end()) {
+ CHECK(!must_exist);
+ return NULL;
+ }
+ return it->second;
+}
+
+void Image::AddRepresentation(internal::ImageRep* rep) const {
+ CHECK(storage_.get());
+ storage_->representations().insert(std::make_pair(rep->type(), rep));
+}
+
+} // namespace gfx
diff --git a/chromium/ui/gfx/image/image.h b/chromium/ui/gfx/image/image.h
new file mode 100644
index 00000000000..e9619dca21e
--- /dev/null
+++ b/chromium/ui/gfx/image/image.h
@@ -0,0 +1,211 @@
+// Copyright (c) 2012 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.
+
+// An Image wraps an image any flavor, be it platform-native GdkBitmap/NSImage,
+// or a SkBitmap. This also provides easy conversion to other image types
+// through operator overloading. It will cache the converted representations
+// internally to prevent double-conversion.
+//
+// The lifetime of both the initial representation and any converted ones are
+// tied to the lifetime of the Image's internal storage. To allow Images to be
+// cheaply passed around by value, the actual image data is stored in a ref-
+// counted member. When all Images referencing this storage are deleted, the
+// actual representations are deleted, too.
+//
+// Images can be empty, in which case they have no backing representation.
+// Attempting to use an empty Image will result in a crash.
+
+#ifndef UI_GFX_IMAGE_IMAGE_H_
+#define UI_GFX_IMAGE_IMAGE_H_
+
+#include <map>
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/gtest_prod_util.h"
+#include "base/memory/ref_counted_memory.h"
+#include "ui/base/ui_export.h"
+#include "ui/gfx/native_widget_types.h"
+
+#if defined(OS_MACOSX) && !defined(OS_IOS)
+typedef struct CGColorSpace* CGColorSpaceRef;
+#endif
+
+class SkBitmap;
+
+namespace {
+class ImageTest;
+class ImageMacTest;
+}
+
+namespace gfx {
+struct ImagePNGRep;
+class ImageSkia;
+class Size;
+
+#if defined(TOOLKIT_GTK)
+class CairoCachedSurface;
+#endif
+
+namespace internal {
+class ImageRep;
+class ImageStorage;
+}
+
+class UI_EXPORT Image {
+ public:
+ enum RepresentationType {
+ kImageRepGdk,
+ kImageRepCocoa,
+ kImageRepCocoaTouch,
+ kImageRepCairo,
+ kImageRepSkia,
+ kImageRepPNG,
+ };
+
+ typedef std::map<RepresentationType, internal::ImageRep*> RepresentationMap;
+
+ // Creates an empty image with no representations.
+ Image();
+
+ // Creates a new image by copying the raw PNG-encoded input for use as the
+ // default representation.
+ explicit Image(const std::vector<ImagePNGRep>& image_reps);
+
+ // Creates a new image by copying the ImageSkia for use as the default
+ // representation.
+ explicit Image(const ImageSkia& image);
+
+#if defined(TOOLKIT_GTK)
+ // Does not increase |pixbuf|'s reference count; expects to take ownership.
+ explicit Image(GdkPixbuf* pixbuf);
+#elif defined(OS_IOS)
+ // Does not retain |image|; expects to take ownership.
+ explicit Image(UIImage* image);
+#elif defined(OS_MACOSX)
+ // Does not retain |image|; expects to take ownership.
+ // A single NSImage object can contain multiple bitmaps so there's no reason
+ // to pass a vector of these.
+ explicit Image(NSImage* image);
+#endif
+
+ // Initializes a new Image by AddRef()ing |other|'s internal storage.
+ Image(const Image& other);
+
+ // Copies a reference to |other|'s storage.
+ Image& operator=(const Image& other);
+
+ // Deletes the image and, if the only owner of the storage, all of its cached
+ // representations.
+ ~Image();
+
+ // Creates an image from the passed in 1x bitmap.
+ // WARNING: The resulting image will be pixelated when painted on a high
+ // density display.
+ static Image CreateFrom1xBitmap(const SkBitmap& bitmap);
+
+ // Creates an image from the PNG encoded input.
+ // For example (from an std::vector):
+ // std::vector<unsigned char> png = ...;
+ // gfx::Image image =
+ // Image::CreateFrom1xPNGBytes(&png.front(), png.size());
+ static Image CreateFrom1xPNGBytes(const unsigned char* input,
+ size_t input_size);
+
+ // Converts the Image to the desired representation and stores it internally.
+ // The returned result is a weak pointer owned by and scoped to the life of
+ // the Image. Must only be called if IsEmpty() is false.
+ const SkBitmap* ToSkBitmap() const;
+ const ImageSkia* ToImageSkia() const;
+#if defined(TOOLKIT_GTK)
+ GdkPixbuf* ToGdkPixbuf() const;
+ CairoCachedSurface* const ToCairo() const;
+#elif defined(OS_IOS)
+ UIImage* ToUIImage() const;
+#elif defined(OS_MACOSX)
+ NSImage* ToNSImage() const;
+#endif
+
+ // Returns the raw PNG-encoded data for the bitmap at 1x. If the data is
+ // unavailable, either because the image has no data for 1x or because it is
+ // empty, an empty RefCountedBytes object is returned. NULL is never
+ // returned.
+ scoped_refptr<base::RefCountedMemory> As1xPNGBytes() const;
+
+ // Same as ToSkBitmap(), but returns a null SkBitmap if this image is empty.
+ SkBitmap AsBitmap() const;
+
+ // Same as ToImageSkia(), but returns an empty ImageSkia if this
+ // image is empty.
+ ImageSkia AsImageSkia() const;
+
+#if defined(OS_MACOSX) && !defined(OS_IOS)
+ // Same as ToSkBitmap(), but returns nil if this image is empty.
+ NSImage* AsNSImage() const;
+#endif
+
+ // Performs a conversion, like above, but returns a copy of the result rather
+ // than a weak pointer. The caller is responsible for deleting the result.
+ // Note that the result is only a copy in terms of memory management; the
+ // backing pixels are shared amongst all copies (a fact of each of the
+ // converted representations, rather than a limitation imposed by Image) and
+ // so the result should be considered immutable.
+ scoped_refptr<base::RefCountedMemory> Copy1xPNGBytes() const;
+ ImageSkia* CopyImageSkia() const;
+ SkBitmap* CopySkBitmap() const;
+#if defined(TOOLKIT_GTK)
+ GdkPixbuf* CopyGdkPixbuf() const;
+#elif defined(OS_IOS)
+ UIImage* CopyUIImage() const;
+#elif defined(OS_MACOSX)
+ NSImage* CopyNSImage() const;
+#endif
+
+ // Inspects the representations map to see if the given type exists.
+ bool HasRepresentation(RepresentationType type) const;
+
+ // Returns the number of representations.
+ size_t RepresentationCount() const;
+
+ // Returns true if this Image has no representations.
+ bool IsEmpty() const;
+
+ // Width and height of image in DIP coordinate system.
+ int Width() const;
+ int Height() const;
+ gfx::Size Size() const;
+
+ // Swaps this image's internal representations with |other|.
+ void SwapRepresentations(gfx::Image* other);
+
+#if defined(OS_MACOSX) && !defined(OS_IOS)
+ // Set the default representation's color space. This is used for converting
+ // to NSImage. This is used to compensate for PNGCodec not writing or reading
+ // colorspace ancillary chunks. (sRGB, iCCP).
+ void SetSourceColorSpace(CGColorSpaceRef color_space);
+#endif // defined(OS_MACOSX) && !defined(OS_IOS)
+
+ private:
+ // Returns the type of the default representation.
+ RepresentationType DefaultRepresentationType() const;
+
+ // Returns the ImageRep of the appropriate type or NULL if there is no
+ // representation of that type (and must_exist is false).
+ internal::ImageRep* GetRepresentation(
+ RepresentationType rep_type, bool must_exist) const;
+
+ // Stores a representation into the map.
+ void AddRepresentation(internal::ImageRep* rep) const;
+
+ // Internal class that holds all the representations. This allows the Image to
+ // be cheaply copied.
+ scoped_refptr<internal::ImageStorage> storage_;
+
+ friend class ::ImageTest;
+ friend class ::ImageMacTest;
+};
+
+} // namespace gfx
+
+#endif // UI_GFX_IMAGE_IMAGE_H_
diff --git a/chromium/ui/gfx/image/image_family.cc b/chromium/ui/gfx/image/image_family.cc
new file mode 100644
index 00000000000..4a43a92e01f
--- /dev/null
+++ b/chromium/ui/gfx/image/image_family.cc
@@ -0,0 +1,128 @@
+// Copyright 2013 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 "ui/gfx/image/image_family.h"
+
+#include <cmath>
+
+#include "ui/gfx/image/image.h"
+#include "ui/gfx/image/image_skia.h"
+#include "ui/gfx/size.h"
+
+namespace gfx {
+
+ImageFamily::const_iterator::const_iterator() {}
+
+ImageFamily::const_iterator::const_iterator(const const_iterator& other)
+ : map_iterator_(other.map_iterator_) {}
+
+ImageFamily::const_iterator::const_iterator(
+ const std::map<MapKey, gfx::Image>::const_iterator& other)
+ : map_iterator_(other) {}
+
+ImageFamily::ImageFamily() {}
+ImageFamily::~ImageFamily() {}
+
+void ImageFamily::Add(const gfx::Image& image) {
+ gfx::Size size = image.Size();
+ if (size.IsEmpty()) {
+ map_[MapKey(1.0f, 0)] = image;
+ } else {
+ float aspect = static_cast<float>(size.width()) / size.height();
+ DCHECK_GT(aspect, 0.0f);
+ map_[MapKey(aspect, size.width())] = image;
+ }
+}
+
+void ImageFamily::Add(const gfx::ImageSkia& image_skia) {
+ Add(gfx::Image(image_skia));
+}
+
+const gfx::Image* ImageFamily::GetBest(int width, int height) const {
+ if (map_.empty())
+ return NULL;
+
+ // If either |width| or |height| is 0, both are.
+ float desired_aspect;
+ if (height == 0 || width == 0) {
+ desired_aspect = 1.0f;
+ height = 0;
+ width = 0;
+ } else {
+ desired_aspect = static_cast<float>(width) / height;
+ }
+ DCHECK_GT(desired_aspect, 0.0f);
+
+ float closest_aspect = GetClosestAspect(desired_aspect);
+
+ // If thinner than desired, search for images with width such that the
+ // corresponding height is greater than or equal to the desired |height|.
+ int desired_width = closest_aspect <= desired_aspect ?
+ width : static_cast<int>(ceilf(height * closest_aspect));
+
+ // Get the best-sized image with the aspect ratio.
+ return GetWithExactAspect(closest_aspect, desired_width);
+}
+
+float ImageFamily::GetClosestAspect(float desired_aspect) const {
+ // Find the two aspect ratios on either side of |desired_aspect|.
+ std::map<MapKey, gfx::Image>::const_iterator greater_or_equal =
+ map_.lower_bound(MapKey(desired_aspect, 0));
+ // Early exit optimization if there is an exact match.
+ if (greater_or_equal != map_.end() &&
+ greater_or_equal->first.aspect() == desired_aspect) {
+ return desired_aspect;
+ }
+
+ // No exact match; |greater_or_equal| will point to the first image with
+ // aspect ratio >= |desired_aspect|, and |less_than| will point to the last
+ // image with aspect ratio < |desired_aspect|.
+ if (greater_or_equal != map_.begin()) {
+ std::map<MapKey, gfx::Image>::const_iterator less_than =
+ greater_or_equal;
+ --less_than;
+ float thinner_aspect = less_than->first.aspect();
+ DCHECK_GT(thinner_aspect, 0.0f);
+ DCHECK_LT(thinner_aspect, desired_aspect);
+ if (greater_or_equal != map_.end()) {
+ float wider_aspect = greater_or_equal->first.aspect();
+ DCHECK_GT(wider_aspect, desired_aspect);
+ if ((wider_aspect / desired_aspect) < (desired_aspect / thinner_aspect))
+ return wider_aspect;
+ }
+ return thinner_aspect;
+ } else {
+ // No aspect ratio is less than or equal to |desired_aspect|.
+ DCHECK(greater_or_equal != map_.end());
+ float wider_aspect = greater_or_equal->first.aspect();
+ DCHECK_GT(wider_aspect, desired_aspect);
+ return wider_aspect;
+ }
+}
+
+const gfx::Image* ImageFamily::GetBest(const gfx::Size& size) const {
+ return GetBest(size.width(), size.height());
+}
+
+const gfx::Image* ImageFamily::GetWithExactAspect(float aspect,
+ int width) const {
+ // Find the two images of given aspect ratio on either side of |width|.
+ std::map<MapKey, gfx::Image>::const_iterator greater_or_equal =
+ map_.lower_bound(MapKey(aspect, width));
+ if (greater_or_equal != map_.end() &&
+ greater_or_equal->first.aspect() == aspect) {
+ // We have found the smallest image of the same size or greater.
+ return &greater_or_equal->second;
+ }
+
+ DCHECK(greater_or_equal != map_.begin());
+ std::map<MapKey, gfx::Image>::const_iterator less_than = greater_or_equal;
+ --less_than;
+ // This must be true because there must be at least one image with |aspect|.
+ DCHECK_EQ(less_than->first.aspect(), aspect);
+ // We have found the largest image smaller than desired.
+ return &less_than->second;
+}
+
+} // namespace gfx
diff --git a/chromium/ui/gfx/image/image_family.h b/chromium/ui/gfx/image/image_family.h
new file mode 100644
index 00000000000..902dedeb419
--- /dev/null
+++ b/chromium/ui/gfx/image/image_family.h
@@ -0,0 +1,156 @@
+// Copyright 2013 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_IMAGE_IMAGE_FAMILY_H_
+#define UI_GFX_IMAGE_IMAGE_FAMILY_H_
+
+#include <iterator>
+#include <map>
+#include <utility>
+
+#include "ui/base/ui_export.h"
+#include "ui/gfx/image/image.h"
+
+namespace gfx {
+class ImageSkia;
+class Size;
+
+// A collection of images at different sizes. The images should be different
+// representations of the same basic concept (for example, an icon) at various
+// sizes and (optionally) aspect ratios. A method is provided for finding the
+// most appropriate image to fit in a given rectangle.
+//
+// NOTE: This is not appropriate for storing an image at a single logical pixel
+// size, with high-DPI bitmap versions; use an Image or ImageSkia for that. Each
+// image in an ImageFamily should have a different logical size (and may also
+// include high-DPI representations).
+class UI_EXPORT ImageFamily {
+ private:
+ // An <aspect ratio, DIP width> pair.
+ // A 0x0 image has aspect ratio 1.0. 0xN and Nx0 images are treated as 0x0.
+ struct MapKey : std::pair<float, int> {
+ MapKey(float aspect, int width)
+ : std::pair<float, int>(aspect, width) {}
+
+ float aspect() const { return first; }
+
+ int width() const { return second; }
+ };
+
+ public:
+ // Type for iterating over all images in the family, in order.
+ // Dereferencing this iterator returns a gfx::Image.
+ class UI_EXPORT const_iterator :
+ std::iterator<std::bidirectional_iterator_tag, const gfx::Image> {
+ public:
+ const_iterator();
+
+ const_iterator(const const_iterator& other);
+
+ const_iterator& operator++() {
+ ++map_iterator_;
+ return *this;
+ }
+
+ const_iterator operator++(int /*unused*/) {
+ const_iterator result(*this);
+ ++(*this);
+ return result;
+ }
+
+ const_iterator& operator--() {
+ --map_iterator_;
+ return *this;
+ }
+
+ const_iterator operator--(int /*unused*/) {
+ const_iterator result(*this);
+ --(*this);
+ return result;
+ }
+
+ bool operator==(const const_iterator& other) const {
+ return map_iterator_ == other.map_iterator_;
+ }
+
+ bool operator!=(const const_iterator& other) const {
+ return map_iterator_ != other.map_iterator_;
+ }
+
+ const gfx::Image& operator*() const {
+ return map_iterator_->second;
+ }
+
+ const gfx::Image* operator->() const {
+ return &**this;
+ }
+
+ private:
+ friend class ImageFamily;
+
+ explicit const_iterator(
+ const std::map<MapKey, gfx::Image>::const_iterator& other);
+
+ std::map<MapKey, gfx::Image>::const_iterator map_iterator_;
+ };
+
+ ImageFamily();
+ ~ImageFamily();
+
+ // Gets an iterator to the first image.
+ const_iterator begin() const { return const_iterator(map_.begin()); }
+ // Gets an iterator to one after the last image.
+ const_iterator end() const { return const_iterator(map_.end()); }
+
+ // Determines whether the image family has no images in it.
+ bool empty() const { return map_.empty(); }
+
+ // Removes all images from the family.
+ void clear() { return map_.clear(); }
+
+ // Adds an image to the family. If another image is already present at the
+ // same size, it will be overwritten.
+ void Add(const gfx::Image& image);
+
+ // Adds an image to the family. If another image is already present at the
+ // same size, it will be overwritten.
+ void Add(const gfx::ImageSkia& image_skia);
+
+ // Gets the best image to use in a rectangle of |width|x|height|.
+ // Gets an image at the same aspect ratio as |width|:|height|, if possible, or
+ // if not, the closest aspect ratio. Among images of that aspect ratio,
+ // returns the smallest image with both its width and height bigger or equal
+ // to the requested size. If none exists, returns the largest image of that
+ // aspect ratio. If there are no images in the family, returns NULL.
+ const gfx::Image* GetBest(int width, int height) const;
+
+ // Gets the best image to use in a rectangle of |size|.
+ // Gets an image at the same aspect ratio as |size.width()|:|size.height()|,
+ // if possible, or if not, the closest aspect ratio. Among images of that
+ // aspect ratio, returns the smallest image with both its width and height
+ // bigger or equal to the requested size. If none exists, returns the largest
+ // image of that aspect ratio. If there are no images in the family, returns
+ // NULL.
+ const gfx::Image* GetBest(const gfx::Size& size) const;
+
+ private:
+ // Find the closest aspect ratio in the map to |desired_aspect|.
+ // Ties are broken by the thinner aspect.
+ // |map_| must not be empty. |desired_aspect| must be > 0.0.
+ float GetClosestAspect(float desired_aspect) const;
+
+ // Gets an image with aspect ratio |aspect|, at the best size for |width|.
+ // Returns the smallest image of aspect ratio |aspect| with its width bigger
+ // or equal to |width|. If none exists, returns the largest image of aspect
+ // ratio |aspect|. Behavior is undefined if there is not at least one image in
+ // |map_| of aspect ratio |aspect|.
+ const gfx::Image* GetWithExactAspect(float aspect, int width) const;
+
+ // Map from (aspect ratio, width) to image.
+ std::map<MapKey, gfx::Image> map_;
+};
+
+} // namespace gfx
+
+#endif // UI_GFX_IMAGE_IMAGE_FAMILY_H_
diff --git a/chromium/ui/gfx/image/image_family_unittest.cc b/chromium/ui/gfx/image/image_family_unittest.cc
new file mode 100644
index 00000000000..2e80ccf0acf
--- /dev/null
+++ b/chromium/ui/gfx/image/image_family_unittest.cc
@@ -0,0 +1,177 @@
+// Copyright 2013 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 "testing/gtest/include/gtest/gtest.h"
+#include "ui/gfx/image/image.h"
+#include "ui/gfx/image/image_family.h"
+#include "ui/gfx/image/image_skia.h"
+#include "ui/gfx/image/image_unittest_util.h"
+
+namespace {
+
+namespace gt = gfx::test;
+
+// Tests that |image| != NULL, and has the given width and height.
+// This is a macro instead of a function, so that the correct line numbers are
+// reported when a test fails.
+#define EXPECT_IMAGE_NON_NULL_AND_SIZE(image, expected_width, expected_height) \
+do { \
+ const gfx::Image* image_ = image; \
+ EXPECT_TRUE(image_); \
+ EXPECT_EQ(expected_width, image_->Width()); \
+ EXPECT_EQ(expected_height, image_->Height()); \
+} while(0)
+
+class ImageFamilyTest : public testing::Test {
+ public:
+ // Construct an ImageFamily. Implicitly tests Add and Empty.
+ virtual void SetUp() OVERRIDE {
+ EXPECT_TRUE(image_family_.empty());
+
+ // Aspect ratio 1:1.
+ image_family_.Add(gt::CreateImageSkia(32, 32));
+ EXPECT_FALSE(image_family_.empty());
+ image_family_.Add(gt::CreateImageSkia(16, 16));
+ image_family_.Add(gt::CreateImageSkia(64, 64));
+ // Duplicate (should override previous one).
+ // Insert an Image directly, instead of an ImageSkia.
+ gfx::Image image(gt::CreateImageSkia(32, 32));
+ image_family_.Add(image);
+ // Aspect ratio 1:4.
+ image_family_.Add(gt::CreateImageSkia(3, 12));
+ image_family_.Add(gt::CreateImageSkia(12, 48));
+ // Aspect ratio 4:1.
+ image_family_.Add(gt::CreateImageSkia(512, 128));
+ image_family_.Add(gt::CreateImageSkia(256, 64));
+
+ EXPECT_FALSE(image_family_.empty());
+ }
+
+ gfx::ImageFamily image_family_;
+};
+
+TEST_F(ImageFamilyTest, Clear) {
+ image_family_.clear();
+ EXPECT_TRUE(image_family_.empty());
+}
+
+// Tests iteration over an ImageFamily.
+TEST_F(ImageFamilyTest, Iteration) {
+ gfx::ImageFamily::const_iterator it = image_family_.begin();
+ gfx::ImageFamily::const_iterator end = image_family_.end();
+
+ // Expect iteration in order of aspect ratio (from thinnest to widest), then
+ // size.
+ EXPECT_TRUE(it != end);
+ EXPECT_EQ(gfx::Size(3, 12), it->Size());
+ ++it;
+ EXPECT_TRUE(it != end);
+ EXPECT_EQ(gfx::Size(12, 48), it->Size());
+ it++; // Test post-increment.
+ EXPECT_TRUE(it != end);
+ EXPECT_EQ(gfx::Size(16, 16), it->Size());
+ ++it;
+ EXPECT_TRUE(it != end);
+ EXPECT_EQ(gfx::Size(32, 32), it->Size());
+ --it; // Test decrement
+ EXPECT_TRUE(it != end);
+ EXPECT_EQ(gfx::Size(16, 16), it->Size());
+ ++it;
+ ++it;
+ EXPECT_TRUE(it != end);
+ EXPECT_EQ(gfx::Size(64, 64), (*it).Size()); // Test operator*.
+ ++it;
+ EXPECT_TRUE(it != end);
+ EXPECT_EQ(gfx::Size(256, 64), it->Size());
+ ++it;
+ EXPECT_TRUE(it != end);
+ EXPECT_EQ(gfx::Size(512, 128), it->Size());
+ ++it;
+
+ EXPECT_TRUE(it == end);
+}
+
+TEST_F(ImageFamilyTest, Get) {
+ // Get on an empty family.
+ gfx::ImageFamily empty_family;
+ EXPECT_TRUE(empty_family.empty());
+ EXPECT_FALSE(empty_family.GetBest(32, 32));
+ EXPECT_FALSE(empty_family.GetBest(0, 32));
+ EXPECT_FALSE(empty_family.GetBest(32, 0));
+
+ // Get various aspect ratios and sizes on the sample family.
+
+ // 0x0 (expect the smallest square image).
+ EXPECT_IMAGE_NON_NULL_AND_SIZE(image_family_.GetBest(0, 0), 16, 16);
+ // GetBest(0, N) or GetBest(N, 0) should be treated the same as GetBest(0, 0).
+ EXPECT_IMAGE_NON_NULL_AND_SIZE(image_family_.GetBest(0, 16), 16, 16);
+ EXPECT_IMAGE_NON_NULL_AND_SIZE(image_family_.GetBest(0, 64), 16, 16);
+ EXPECT_IMAGE_NON_NULL_AND_SIZE(image_family_.GetBest(16, 0), 16, 16);
+ EXPECT_IMAGE_NON_NULL_AND_SIZE(image_family_.GetBest(64, 0), 16, 16);
+
+ // Thinner than thinnest image.
+ EXPECT_IMAGE_NON_NULL_AND_SIZE(image_family_.GetBest(2, 12), 3, 12);
+ EXPECT_IMAGE_NON_NULL_AND_SIZE(image_family_.GetBest(2, 13), 12, 48);
+ EXPECT_IMAGE_NON_NULL_AND_SIZE(image_family_.GetBest(10, 60), 12, 48);
+
+ // Between two images' aspect ratio.
+ // Note: Testing the boundary around 1:2 and 2:1, half way to 1:4 and 4:1.
+ // Ties are broken by favouring the thinner aspect ratio.
+ EXPECT_IMAGE_NON_NULL_AND_SIZE(image_family_.GetBest(63, 32), 64, 64);
+ EXPECT_IMAGE_NON_NULL_AND_SIZE(image_family_.GetBest(64, 32), 64, 64);
+ EXPECT_IMAGE_NON_NULL_AND_SIZE(image_family_.GetBest(65, 32), 256, 64);
+ EXPECT_IMAGE_NON_NULL_AND_SIZE(image_family_.GetBest(32, 63), 64, 64);
+ EXPECT_IMAGE_NON_NULL_AND_SIZE(image_family_.GetBest(32, 64), 12, 48);
+ EXPECT_IMAGE_NON_NULL_AND_SIZE(image_family_.GetBest(32, 65), 12, 48);
+
+ // Exact match aspect ratio.
+ // Exact match size.
+ EXPECT_IMAGE_NON_NULL_AND_SIZE(image_family_.GetBest(32, 32), 32, 32);
+ // Slightly smaller.
+ EXPECT_IMAGE_NON_NULL_AND_SIZE(image_family_.GetBest(31, 31), 32, 32);
+ // Much smaller.
+ EXPECT_IMAGE_NON_NULL_AND_SIZE(image_family_.GetBest(17, 17), 32, 32);
+ // Exact match size.
+ EXPECT_IMAGE_NON_NULL_AND_SIZE(image_family_.GetBest(16, 16), 16, 16);
+ // Smaller than any image.
+ EXPECT_IMAGE_NON_NULL_AND_SIZE(image_family_.GetBest(3, 3), 16, 16);
+ // Larger than any image.
+ EXPECT_IMAGE_NON_NULL_AND_SIZE(image_family_.GetBest(512, 512), 64, 64);
+ // 1:4 aspect ratio.
+ EXPECT_IMAGE_NON_NULL_AND_SIZE(image_family_.GetBest(16, 64), 12, 48);
+ EXPECT_IMAGE_NON_NULL_AND_SIZE(image_family_.GetBest(2, 8), 3, 12);
+ // 4:1 aspect ratio.
+ EXPECT_IMAGE_NON_NULL_AND_SIZE(image_family_.GetBest(64, 16), 256, 64);
+ EXPECT_IMAGE_NON_NULL_AND_SIZE(image_family_.GetBest(260, 65), 512, 128);
+
+ // Wider than widest image.
+ EXPECT_IMAGE_NON_NULL_AND_SIZE(image_family_.GetBest(255, 51), 256, 64);
+ EXPECT_IMAGE_NON_NULL_AND_SIZE(image_family_.GetBest(260, 52), 512, 128);
+ EXPECT_IMAGE_NON_NULL_AND_SIZE(image_family_.GetBest(654, 129), 512, 128);
+}
+
+// Test adding and looking up images with 0 width and height.
+TEST_F(ImageFamilyTest, ZeroWidthAndHeight) {
+ // An empty Image. Should be considered to have 0 width and height.
+ image_family_.Add(gfx::Image());
+ // Images with 0 width OR height should be treated the same as an image with 0
+ // width AND height (in fact, the ImageSkias should be indistinguishable).
+ image_family_.Add(gt::CreateImageSkia(32, 0));
+ image_family_.Add(gt::CreateImageSkia(0, 32));
+
+ EXPECT_IMAGE_NON_NULL_AND_SIZE(image_family_.GetBest(0, 0), 0, 0);
+ EXPECT_IMAGE_NON_NULL_AND_SIZE(image_family_.GetBest(1, 1), 16, 16);
+ EXPECT_IMAGE_NON_NULL_AND_SIZE(image_family_.GetBest(32, 32), 32, 32);
+
+ // GetBest(0, N) or GetBest(N, 0) should be treated the same as GetBest(0, 0).
+ EXPECT_IMAGE_NON_NULL_AND_SIZE(image_family_.GetBest(0, 1), 0, 0);
+ EXPECT_IMAGE_NON_NULL_AND_SIZE(image_family_.GetBest(0, 32), 0, 0);
+ EXPECT_IMAGE_NON_NULL_AND_SIZE(image_family_.GetBest(1, 0), 0, 0);
+ EXPECT_IMAGE_NON_NULL_AND_SIZE(image_family_.GetBest(32, 0), 0, 0);
+
+ EXPECT_IMAGE_NON_NULL_AND_SIZE(image_family_.GetBest(1, 32), 12, 48);
+ EXPECT_IMAGE_NON_NULL_AND_SIZE(image_family_.GetBest(32, 1), 256, 64);
+}
+
+} // namespace
diff --git a/chromium/ui/gfx/image/image_ios.mm b/chromium/ui/gfx/image/image_ios.mm
new file mode 100644
index 00000000000..01a3c87fbd2
--- /dev/null
+++ b/chromium/ui/gfx/image/image_ios.mm
@@ -0,0 +1,137 @@
+// Copyright 2012 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 "ui/gfx/image/image.h"
+
+#import <UIKit/UIKit.h>
+#include <cmath>
+#include <limits>
+
+#include "base/logging.h"
+#include "base/mac/scoped_cftyperef.h"
+#include "base/mac/scoped_nsobject.h"
+#include "ui/base/layout.h"
+#include "ui/gfx/image/image_png_rep.h"
+#include "ui/gfx/image/image_skia.h"
+#include "ui/gfx/image/image_skia_util_ios.h"
+#include "ui/gfx/size.h"
+
+namespace gfx {
+namespace internal {
+
+namespace {
+
+// Returns a 16x16 red UIImage to visually show when a UIImage cannot be
+// created from PNG data. Logs error as well.
+// Caller takes ownership of returned UIImage.
+UIImage* CreateErrorUIImage(float scale) {
+ LOG(ERROR) << "Unable to decode PNG into UIImage.";
+ base::ScopedCFTypeRef<CGColorSpaceRef> color_space(
+ CGColorSpaceCreateDeviceRGB());
+ base::ScopedCFTypeRef<CGContextRef> context(CGBitmapContextCreate(
+ NULL, // Allow CG to allocate memory.
+ 16, // width
+ 16, // height
+ 8, // bitsPerComponent
+ 0, // CG will calculate by default.
+ color_space,
+ kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Host));
+ CGContextSetRGBFillColor(context, 1.0, 0.0, 0.0, 1.0);
+ CGContextFillRect(context, CGRectMake(0.0, 0.0, 16, 16));
+ base::ScopedCFTypeRef<CGImageRef> cg_image(
+ CGBitmapContextCreateImage(context));
+ return [[UIImage imageWithCGImage:cg_image.get()
+ scale:scale
+ orientation:UIImageOrientationUp] retain];
+}
+
+// Converts from ImagePNGRep to UIImage.
+UIImage* CreateUIImageFromImagePNGRep(const gfx::ImagePNGRep& image_png_rep) {
+ float scale = ui::GetScaleFactorScale(image_png_rep.scale_factor);
+ scoped_refptr<base::RefCountedMemory> png = image_png_rep.raw_data;
+ CHECK(png.get());
+ NSData* data = [NSData dataWithBytes:png->front() length:png->size()];
+ UIImage* image = [[UIImage alloc] initWithData:data scale:scale];
+ return image ? image : CreateErrorUIImage(scale);
+}
+
+} // namespace
+
+scoped_refptr<base::RefCountedMemory> Get1xPNGBytesFromUIImage(
+ UIImage* uiimage) {
+ NSData* data = UIImagePNGRepresentation(uiimage);
+
+ if ([data length] == 0)
+ return NULL;
+
+ scoped_refptr<base::RefCountedBytes> png_bytes(
+ new base::RefCountedBytes());
+ png_bytes->data().resize([data length]);
+ [data getBytes:&png_bytes->data().at(0) length:[data length]];
+ return png_bytes;
+}
+
+UIImage* CreateUIImageFromPNG(
+ const std::vector<gfx::ImagePNGRep>& image_png_reps) {
+ ui::ScaleFactor ideal_scale_factor = ui::GetMaxScaleFactor();
+ float ideal_scale = ui::GetScaleFactorScale(ideal_scale_factor);
+
+ if (image_png_reps.empty())
+ return CreateErrorUIImage(ideal_scale);
+
+ // Find best match for |ideal_scale_factor|.
+ float smallest_diff = std::numeric_limits<float>::max();
+ size_t closest_index = 0u;
+ for (size_t i = 0; i < image_png_reps.size(); ++i) {
+ float scale = ui::GetScaleFactorScale(image_png_reps[i].scale_factor);
+ float diff = std::abs(ideal_scale - scale);
+ if (diff < smallest_diff) {
+ smallest_diff = diff;
+ closest_index = i;
+ }
+ }
+
+ return CreateUIImageFromImagePNGRep(image_png_reps[closest_index]);
+}
+
+scoped_refptr<base::RefCountedMemory> Get1xPNGBytesFromImageSkia(
+ const ImageSkia* skia) {
+ // iOS does not expose libpng, so conversion from ImageSkia to PNG must go
+ // through UIImage.
+ // TODO(rohitrao): Rewrite the callers of this function to save the UIImage
+ // representation in the gfx::Image. If we're generating it, we might as well
+ // hold on to it.
+ const gfx::ImageSkiaRep& image_skia_rep = skia->GetRepresentation(
+ ui::SCALE_FACTOR_100P);
+ if (image_skia_rep.scale_factor() != ui::SCALE_FACTOR_100P)
+ return NULL;
+
+ UIImage* image = UIImageFromImageSkiaRep(image_skia_rep);
+ return Get1xPNGBytesFromUIImage(image);
+}
+
+ImageSkia* ImageSkiaFromPNG(
+ const std::vector<gfx::ImagePNGRep>& image_png_reps) {
+ // iOS does not expose libpng, so conversion from PNG to ImageSkia must go
+ // through UIImage.
+ gfx::ImageSkia* image_skia = new gfx::ImageSkia();
+ for (size_t i = 0; i < image_png_reps.size(); ++i) {
+ base::scoped_nsobject<UIImage> uiimage(
+ CreateUIImageFromImagePNGRep(image_png_reps[i]));
+ gfx::ImageSkiaRep image_skia_rep = ImageSkiaRepOfScaleFactorFromUIImage(
+ uiimage, image_png_reps[i].scale_factor);
+ if (!image_skia_rep.is_null())
+ image_skia->AddRepresentation(image_skia_rep);
+ }
+ return image_skia;
+}
+
+gfx::Size UIImageSize(UIImage* image) {
+ int width = static_cast<int>(image.size.width);
+ int height = static_cast<int>(image.size.height);
+ return gfx::Size(width, height);
+}
+
+} // namespace internal
+} // namespace gfx
diff --git a/chromium/ui/gfx/image/image_mac.mm b/chromium/ui/gfx/image/image_mac.mm
new file mode 100644
index 00000000000..d03b48e0f4b
--- /dev/null
+++ b/chromium/ui/gfx/image/image_mac.mm
@@ -0,0 +1,111 @@
+// Copyright (c) 2012 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 "ui/gfx/image/image.h"
+
+#import <AppKit/AppKit.h>
+
+#include "base/logging.h"
+#include "base/mac/scoped_nsobject.h"
+#include "ui/gfx/image/image_png_rep.h"
+#include "ui/gfx/size.h"
+
+namespace gfx {
+namespace internal {
+
+namespace {
+
+// Returns a 16x16 red NSImage to visually show when a NSImage cannot be
+// created from PNG data.
+// Caller takes ownership of the returned NSImage.
+NSImage* GetErrorNSImage() {
+ NSRect rect = NSMakeRect(0, 0, 16, 16);
+ NSImage* image = [[NSImage alloc] initWithSize:rect.size];
+ [image lockFocus];
+ [[NSColor colorWithDeviceRed:1.0 green:0.0 blue:0.0 alpha:1.0] set];
+ NSRectFill(rect);
+ [image unlockFocus];
+ return image;
+}
+
+} // namespace
+
+scoped_refptr<base::RefCountedMemory> Get1xPNGBytesFromNSImage(
+ NSImage* nsimage) {
+ CGImageRef cg_image = [nsimage CGImageForProposedRect:NULL
+ context:nil
+ hints:nil];
+ base::scoped_nsobject<NSBitmapImageRep> ns_bitmap(
+ [[NSBitmapImageRep alloc] initWithCGImage:cg_image]);
+ NSData* ns_data = [ns_bitmap representationUsingType:NSPNGFileType
+ properties:nil];
+ const unsigned char* bytes =
+ static_cast<const unsigned char*>([ns_data bytes]);
+ scoped_refptr<base::RefCountedBytes> refcounted_bytes(
+ new base::RefCountedBytes());
+ refcounted_bytes->data().assign(bytes, bytes + [ns_data length]);
+ return refcounted_bytes;
+}
+
+NSImage* NSImageFromPNG(const std::vector<gfx::ImagePNGRep>& image_png_reps,
+ CGColorSpaceRef color_space) {
+ if (image_png_reps.empty()) {
+ LOG(ERROR) << "Unable to decode PNG.";
+ return GetErrorNSImage();
+ }
+
+ base::scoped_nsobject<NSImage> image;
+ for (size_t i = 0; i < image_png_reps.size(); ++i) {
+ scoped_refptr<base::RefCountedMemory> png = image_png_reps[i].raw_data;
+ CHECK(png.get());
+ base::scoped_nsobject<NSData> ns_data(
+ [[NSData alloc] initWithBytes:png->front() length:png->size()]);
+ base::scoped_nsobject<NSBitmapImageRep> ns_image_rep(
+ [[NSBitmapImageRep alloc] initWithData:ns_data]);
+ if (!ns_image_rep) {
+ LOG(ERROR) << "Unable to decode PNG at "
+ << ui::GetScaleFactorScale(image_png_reps[i].scale_factor)
+ << ".";
+ return GetErrorNSImage();
+ }
+
+ // PNGCodec ignores colorspace related ancillary chunks (sRGB, iCCP). Ignore
+ // colorspace information when decoding directly from PNG to an NSImage so
+ // that the conversions: PNG -> SkBitmap -> NSImage and PNG -> NSImage
+ // produce visually similar results.
+ CGColorSpaceModel decoded_color_space_model = CGColorSpaceGetModel(
+ [[ns_image_rep colorSpace] CGColorSpace]);
+ CGColorSpaceModel color_space_model = CGColorSpaceGetModel(color_space);
+ if (decoded_color_space_model == color_space_model) {
+ base::scoped_nsobject<NSColorSpace> ns_color_space(
+ [[NSColorSpace alloc] initWithCGColorSpace:color_space]);
+ NSBitmapImageRep* ns_retagged_image_rep =
+ [ns_image_rep
+ bitmapImageRepByRetaggingWithColorSpace:ns_color_space];
+ if (ns_retagged_image_rep && ns_retagged_image_rep != ns_image_rep)
+ ns_image_rep.reset([ns_retagged_image_rep retain]);
+ }
+
+ if (!image.get()) {
+ float scale = ui::GetScaleFactorScale(image_png_reps[i].scale_factor);
+ NSSize image_size = NSMakeSize([ns_image_rep pixelsWide] / scale,
+ [ns_image_rep pixelsHigh] / scale);
+ image.reset([[NSImage alloc] initWithSize:image_size]);
+ }
+ [image addRepresentation:ns_image_rep];
+ }
+
+ return image.release();
+}
+
+gfx::Size NSImageSize(NSImage* image) {
+ NSSize size = [image size];
+ int width = static_cast<int>(size.width);
+ int height = static_cast<int>(size.height);
+ return gfx::Size(width, height);
+}
+
+} // namespace internal
+} // namespace gfx
+
diff --git a/chromium/ui/gfx/image/image_mac_unittest.mm b/chromium/ui/gfx/image/image_mac_unittest.mm
new file mode 100644
index 00000000000..250deeb5249
--- /dev/null
+++ b/chromium/ui/gfx/image/image_mac_unittest.mm
@@ -0,0 +1,213 @@
+// Copyright (c) 2012 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 <Cocoa/Cocoa.h>
+
+#include "base/logging.h"
+#include "base/mac/scoped_nsobject.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "ui/gfx/image/image.h"
+#include "ui/gfx/image/image_png_rep.h"
+#include "ui/gfx/image/image_skia.h"
+#include "ui/gfx/image/image_skia_util_mac.h"
+#include "ui/gfx/image/image_unittest_util.h"
+
+namespace {
+
+// Returns true if the structure of |ns_image| matches the structure
+// described by |width|, |height|, and |scale_factors|.
+// The structure matches if:
+// - |ns_image| is not nil.
+// - |ns_image| has NSImageReps of |scale_factors|.
+// - Each of the NSImageReps has a pixel size of [|ns_image| size] *
+// scale_factor.
+bool NSImageStructureMatches(
+ NSImage* ns_image,
+ int width,
+ int height,
+ const std::vector<ui::ScaleFactor>& scale_factors) {
+ if (!ns_image ||
+ [ns_image size].width != width ||
+ [ns_image size].height != height ||
+ [ns_image representations].count != scale_factors.size()) {
+ return false;
+ }
+
+ for (size_t i = 0; i < scale_factors.size(); ++i) {
+ float scale = ui::GetScaleFactorScale(scale_factors[i]);
+ bool found_match = false;
+ for (size_t j = 0; j < [ns_image representations].count; ++j) {
+ NSImageRep* ns_image_rep = [[ns_image representations] objectAtIndex:j];
+ if (ns_image_rep &&
+ [ns_image_rep pixelsWide] == width * scale &&
+ [ns_image_rep pixelsHigh] == height * scale) {
+ found_match = true;
+ break;
+ }
+ }
+ if (!found_match)
+ return false;
+ }
+ return true;
+}
+
+void BitmapImageRep(int width, int height,
+ NSBitmapImageRep** image_rep) {
+ *image_rep = [[[NSBitmapImageRep alloc]
+ initWithBitmapDataPlanes:NULL
+ pixelsWide:width
+ pixelsHigh:height
+ bitsPerSample:8
+ samplesPerPixel:3
+ hasAlpha:NO
+ isPlanar:NO
+ colorSpaceName:NSDeviceRGBColorSpace
+ bitmapFormat:0
+ bytesPerRow:0
+ bitsPerPixel:0]
+ autorelease];
+ unsigned char* image_rep_data = [*image_rep bitmapData];
+ for (int i = 0; i < width * height * 3; ++i)
+ image_rep_data[i] = 255;
+}
+
+class ImageMacTest : public testing::Test {
+ public:
+ ImageMacTest()
+ : supported_scale_factors_(gfx::test::Get1xAnd2xScaleFactors()) {
+ }
+
+ virtual ~ImageMacTest() {
+ }
+
+ private:
+ ui::test::ScopedSetSupportedScaleFactors supported_scale_factors_;
+
+ DISALLOW_COPY_AND_ASSIGN(ImageMacTest);
+};
+
+namespace gt = gfx::test;
+
+TEST_F(ImageMacTest, MultiResolutionNSImageToImageSkia) {
+ const int kWidth1x = 10;
+ const int kHeight1x = 12;
+ const int kWidth2x = 20;
+ const int kHeight2x = 24;
+
+ NSBitmapImageRep* ns_image_rep1;
+ BitmapImageRep(kWidth1x, kHeight1x, &ns_image_rep1);
+ NSBitmapImageRep* ns_image_rep2;
+ BitmapImageRep(kWidth2x, kHeight2x, &ns_image_rep2);
+ base::scoped_nsobject<NSImage> ns_image(
+ [[NSImage alloc] initWithSize:NSMakeSize(kWidth1x, kHeight1x)]);
+ [ns_image addRepresentation:ns_image_rep1];
+ [ns_image addRepresentation:ns_image_rep2];
+
+ gfx::Image image(ns_image.release());
+
+ EXPECT_EQ(1u, image.RepresentationCount());
+
+ const gfx::ImageSkia* image_skia = image.ToImageSkia();
+
+ std::vector<ui::ScaleFactor> scale_factors;
+ scale_factors.push_back(ui::SCALE_FACTOR_100P);
+ scale_factors.push_back(ui::SCALE_FACTOR_200P);
+ EXPECT_TRUE(gt::ImageSkiaStructureMatches(*image_skia, kWidth1x, kHeight1x,
+ scale_factors));
+
+ // ToImageSkia should create a second representation.
+ EXPECT_EQ(2u, image.RepresentationCount());
+}
+
+// Test that convertng to an ImageSkia from an NSImage with scale factors
+// other than 1x and 2x results in an ImageSkia with scale factors 1x and
+// 2x;
+TEST_F(ImageMacTest, UnalignedMultiResolutionNSImageToImageSkia) {
+ const int kWidth1x = 10;
+ const int kHeight1x= 12;
+ const int kWidth4x = 40;
+ const int kHeight4x = 48;
+
+ NSBitmapImageRep* ns_image_rep4;
+ BitmapImageRep(kWidth4x, kHeight4x, &ns_image_rep4);
+ base::scoped_nsobject<NSImage> ns_image(
+ [[NSImage alloc] initWithSize:NSMakeSize(kWidth1x, kHeight1x)]);
+ [ns_image addRepresentation:ns_image_rep4];
+
+ gfx::Image image(ns_image.release());
+
+ EXPECT_EQ(1u, image.RepresentationCount());
+
+ const gfx::ImageSkia* image_skia = image.ToImageSkia();
+
+ std::vector<ui::ScaleFactor> scale_factors;
+ scale_factors.push_back(ui::SCALE_FACTOR_100P);
+ scale_factors.push_back(ui::SCALE_FACTOR_200P);
+ EXPECT_TRUE(gt::ImageSkiaStructureMatches(*image_skia, kWidth1x, kHeight1x,
+ scale_factors));
+
+ // ToImageSkia should create a second representation.
+ EXPECT_EQ(2u, image.RepresentationCount());
+}
+
+TEST_F(ImageMacTest, MultiResolutionImageSkiaToNSImage) {
+ const int kWidth1x = 10;
+ const int kHeight1x= 12;
+ const int kWidth2x = 20;
+ const int kHeight2x = 24;
+
+ gfx::ImageSkia image_skia;
+ image_skia.AddRepresentation(gfx::ImageSkiaRep(
+ gt::CreateBitmap(kWidth1x, kHeight1x), ui::SCALE_FACTOR_100P));
+ image_skia.AddRepresentation(gfx::ImageSkiaRep(
+ gt::CreateBitmap(kWidth2x, kHeight2x), ui::SCALE_FACTOR_200P));
+
+ gfx::Image image(image_skia);
+
+ EXPECT_EQ(1u, image.RepresentationCount());
+ EXPECT_EQ(2u, image.ToImageSkia()->image_reps().size());
+
+ NSImage* ns_image = image.ToNSImage();
+
+ std::vector<ui::ScaleFactor> scale_factors;
+ scale_factors.push_back(ui::SCALE_FACTOR_100P);
+ scale_factors.push_back(ui::SCALE_FACTOR_200P);
+ EXPECT_TRUE(NSImageStructureMatches(ns_image, kWidth1x, kHeight1x,
+ scale_factors));
+
+ // Request for NSImage* should create a second representation.
+ EXPECT_EQ(2u, image.RepresentationCount());
+}
+
+TEST_F(ImageMacTest, MultiResolutionPNGToNSImage) {
+ const int kSize1x = 25;
+ const int kSize2x = 50;
+
+ scoped_refptr<base::RefCountedMemory> bytes1x = gt::CreatePNGBytes(kSize1x);
+ scoped_refptr<base::RefCountedMemory> bytes2x = gt::CreatePNGBytes(kSize2x);
+ std::vector<gfx::ImagePNGRep> image_png_reps;
+ image_png_reps.push_back(gfx::ImagePNGRep(bytes1x, ui::SCALE_FACTOR_100P));
+ image_png_reps.push_back(gfx::ImagePNGRep(bytes2x, ui::SCALE_FACTOR_200P));
+
+ gfx::Image image(image_png_reps);
+
+ NSImage* ns_image = image.ToNSImage();
+ std::vector<ui::ScaleFactor> scale_factors;
+ scale_factors.push_back(ui::SCALE_FACTOR_100P);
+ scale_factors.push_back(ui::SCALE_FACTOR_200P);
+ EXPECT_TRUE(NSImageStructureMatches(ns_image, kSize1x, kSize1x,
+ scale_factors));
+
+ // Converting from PNG to NSImage should not go through ImageSkia.
+ EXPECT_FALSE(image.HasRepresentation(gfx::Image::kImageRepSkia));
+
+ // Convert to ImageSkia to check pixel contents of NSImageReps.
+ gfx::ImageSkia image_skia = gfx::ImageSkiaFromNSImage(ns_image);
+ EXPECT_TRUE(gt::IsEqual(bytes1x,
+ image_skia.GetRepresentation(ui::SCALE_FACTOR_100P).sk_bitmap()));
+ EXPECT_TRUE(gt::IsEqual(bytes2x,
+ image_skia.GetRepresentation(ui::SCALE_FACTOR_200P).sk_bitmap()));
+}
+
+} // namespace
diff --git a/chromium/ui/gfx/image/image_png_rep.cc b/chromium/ui/gfx/image/image_png_rep.cc
new file mode 100644
index 00000000000..253a5291afd
--- /dev/null
+++ b/chromium/ui/gfx/image/image_png_rep.cc
@@ -0,0 +1,40 @@
+// Copyright (c) 2012 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 "ui/gfx/image/image_png_rep.h"
+
+#include "third_party/skia/include/core/SkBitmap.h"
+#include "ui/gfx/codec/png_codec.h"
+#include "ui/gfx/size.h"
+
+namespace gfx {
+
+ImagePNGRep::ImagePNGRep()
+ : raw_data(NULL),
+ scale_factor(ui::SCALE_FACTOR_NONE) {
+}
+
+ImagePNGRep::ImagePNGRep(const scoped_refptr<base::RefCountedMemory>& data,
+ ui::ScaleFactor data_scale_factor)
+ : raw_data(data),
+ scale_factor(data_scale_factor) {
+}
+
+ImagePNGRep::~ImagePNGRep() {
+}
+
+gfx::Size ImagePNGRep::Size() const {
+ // The only way to get the width and height of a raw PNG stream, at least
+ // using the gfx::PNGCodec API, is to decode the whole thing.
+ CHECK(raw_data.get());
+ SkBitmap bitmap;
+ if (!gfx::PNGCodec::Decode(raw_data->front(), raw_data->size(),
+ &bitmap)) {
+ LOG(ERROR) << "Unable to decode PNG.";
+ return gfx::Size(0, 0);
+ }
+ return gfx::Size(bitmap.width(), bitmap.height());
+}
+
+} // namespace gfx
diff --git a/chromium/ui/gfx/image/image_png_rep.h b/chromium/ui/gfx/image/image_png_rep.h
new file mode 100644
index 00000000000..466e37c1ac9
--- /dev/null
+++ b/chromium/ui/gfx/image/image_png_rep.h
@@ -0,0 +1,35 @@
+// Copyright (c) 2012 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_IMAGE_IMAGE_PNG_REP_H_
+#define UI_GFX_IMAGE_IMAGE_PNG_REP_H_
+
+#include "base/memory/ref_counted_memory.h"
+#include "ui/base/layout.h"
+#include "ui/base/ui_export.h"
+
+namespace gfx {
+class Size;
+
+// An ImagePNGRep represents a bitmap's png encoded data and the scale factor it
+// was intended for.
+struct UI_EXPORT ImagePNGRep {
+ ImagePNGRep();
+ ImagePNGRep(const scoped_refptr<base::RefCountedMemory>& data,
+ ui::ScaleFactor data_scale_factor);
+ ~ImagePNGRep();
+
+ // Width and height of the image, in pixels.
+ // If the image is invalid, returns gfx::Size(0, 0).
+ // Warning: This operation processes the entire image stream, so its result
+ // should be cached if it is needed multiple times.
+ gfx::Size Size() const;
+
+ scoped_refptr<base::RefCountedMemory> raw_data;
+ ui::ScaleFactor scale_factor;
+};
+
+} // namespace gfx
+
+#endif // UI_GFX_IMAGE_IMAGE_PNG_REP_H_
diff --git a/chromium/ui/gfx/image/image_skia.cc b/chromium/ui/gfx/image/image_skia.cc
new file mode 100644
index 00000000000..1ae9874ad4d
--- /dev/null
+++ b/chromium/ui/gfx/image/image_skia.cc
@@ -0,0 +1,413 @@
+// Copyright (c) 2012 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 "ui/gfx/image/image_skia.h"
+
+#include <algorithm>
+#include <cmath>
+#include <limits>
+
+#include "base/logging.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/threading/non_thread_safe.h"
+#include "ui/gfx/image/image_skia_operations.h"
+#include "ui/gfx/image/image_skia_source.h"
+#include "ui/gfx/rect.h"
+#include "ui/gfx/size.h"
+#include "ui/gfx/skia_util.h"
+
+namespace gfx {
+namespace {
+
+// static
+gfx::ImageSkiaRep& NullImageRep() {
+ CR_DEFINE_STATIC_LOCAL(ImageSkiaRep, null_image_rep, ());
+ return null_image_rep;
+}
+
+} // namespace
+
+namespace internal {
+namespace {
+
+class Matcher {
+ public:
+ explicit Matcher(ui::ScaleFactor scale_factor) : scale_factor_(scale_factor) {
+ }
+
+ bool operator()(const ImageSkiaRep& rep) const {
+ return rep.scale_factor() == scale_factor_;
+ }
+
+ private:
+ ui::ScaleFactor scale_factor_;
+};
+
+} // namespace
+
+// A helper class such that ImageSkia can be cheaply copied. ImageSkia holds a
+// refptr instance of ImageSkiaStorage, which in turn holds all of ImageSkia's
+// information. Having both |base::RefCountedThreadSafe| and
+// |base::NonThreadSafe| may sounds strange but necessary to turn
+// the 'thread-non-safe modifiable ImageSkiaStorage' into
+// the 'thread-safe read-only ImageSkiaStorage'.
+class ImageSkiaStorage : public base::RefCountedThreadSafe<ImageSkiaStorage>,
+ public base::NonThreadSafe {
+ public:
+ ImageSkiaStorage(ImageSkiaSource* source, const gfx::Size& size)
+ : source_(source),
+ size_(size),
+ read_only_(false) {
+ }
+
+ ImageSkiaStorage(ImageSkiaSource* source, ui::ScaleFactor scale_factor)
+ : source_(source),
+ read_only_(false) {
+ ImageSkia::ImageSkiaReps::iterator it =
+ FindRepresentation(scale_factor, true);
+ if (it == image_reps_.end() || it->is_null())
+ source_.reset();
+ else
+ size_.SetSize(it->GetWidth(), it->GetHeight());
+ }
+
+ bool has_source() const { return source_.get() != NULL; }
+
+ std::vector<gfx::ImageSkiaRep>& image_reps() { return image_reps_; }
+
+ const gfx::Size& size() const { return size_; }
+
+ bool read_only() const { return read_only_; }
+
+ void DeleteSource() {
+ source_.reset();
+ }
+
+ void SetReadOnly() {
+ read_only_ = true;
+ }
+
+ void DetachFromThread() {
+ base::NonThreadSafe::DetachFromThread();
+ }
+
+ // Checks if the current thread can safely modify the storage.
+ bool CanModify() const {
+ return !read_only_ && CalledOnValidThread();
+ }
+
+ // Checks if the current thread can safely read the storage.
+ bool CanRead() const {
+ return (read_only_ && !source_.get()) || CalledOnValidThread();
+ }
+
+ // Returns the iterator of the image rep whose density best matches
+ // |scale_factor|. If the image for the |scale_factor| doesn't exist
+ // in the storage and |storage| is set, it fetches new image by calling
+ // |ImageSkiaSource::GetImageForScale|. If the source returns the
+ // image with different scale factor (if the image doesn't exist in
+ // resource, for example), it will fallback to closest image rep.
+ std::vector<ImageSkiaRep>::iterator FindRepresentation(
+ ui::ScaleFactor scale_factor, bool fetch_new_image) const {
+ ImageSkiaStorage* non_const = const_cast<ImageSkiaStorage*>(this);
+
+ float scale = ui::GetScaleFactorScale(scale_factor);
+ ImageSkia::ImageSkiaReps::iterator closest_iter =
+ non_const->image_reps().end();
+ ImageSkia::ImageSkiaReps::iterator exact_iter =
+ non_const->image_reps().end();
+ float smallest_diff = std::numeric_limits<float>::max();
+ for (ImageSkia::ImageSkiaReps::iterator it =
+ non_const->image_reps().begin();
+ it < image_reps_.end(); ++it) {
+ if (it->GetScale() == scale) {
+ // found exact match
+ fetch_new_image = false;
+ if (it->is_null())
+ continue;
+ exact_iter = it;
+ break;
+ }
+ float diff = std::abs(it->GetScale() - scale);
+ if (diff < smallest_diff && !it->is_null()) {
+ closest_iter = it;
+ smallest_diff = diff;
+ }
+ }
+
+ if (fetch_new_image && source_.get()) {
+ DCHECK(CalledOnValidThread()) <<
+ "An ImageSkia with the source must be accessed by the same thread.";
+
+ ImageSkiaRep image = source_->GetImageForScale(scale_factor);
+
+ // If the source returned the new image, store it.
+ if (!image.is_null() &&
+ std::find_if(image_reps_.begin(), image_reps_.end(),
+ Matcher(image.scale_factor())) == image_reps_.end()) {
+ non_const->image_reps().push_back(image);
+ }
+
+ // If the result image's scale factor isn't same as the expected
+ // scale factor, create null ImageSkiaRep with the |scale_factor|
+ // so that the next lookup will fallback to the closest scale.
+ if (image.is_null() || image.scale_factor() != scale_factor) {
+ non_const->image_reps().push_back(
+ ImageSkiaRep(SkBitmap(), scale_factor));
+ }
+
+ // image_reps_ must have the exact much now, so find again.
+ return FindRepresentation(scale_factor, false);
+ }
+ return exact_iter != image_reps_.end() ? exact_iter : closest_iter;
+ }
+
+ private:
+ virtual ~ImageSkiaStorage() {
+ // We only care if the storage is modified by the same thread.
+ // Don't blow up even if someone else deleted the ImageSkia.
+ DetachFromThread();
+ }
+
+ // Vector of bitmaps and their associated scale factor.
+ std::vector<gfx::ImageSkiaRep> image_reps_;
+
+ scoped_ptr<ImageSkiaSource> source_;
+
+ // Size of the image in DIP.
+ gfx::Size size_;
+
+ bool read_only_;
+
+ friend class base::RefCountedThreadSafe<ImageSkiaStorage>;
+};
+
+} // internal
+
+ImageSkia::ImageSkia() : storage_(NULL) {
+}
+
+ImageSkia::ImageSkia(ImageSkiaSource* source, const gfx::Size& size)
+ : storage_(new internal::ImageSkiaStorage(source, size)) {
+ DCHECK(source);
+ // No other thread has reference to this, so it's safe to detach the thread.
+ DetachStorageFromThread();
+}
+
+ImageSkia::ImageSkia(ImageSkiaSource* source, ui::ScaleFactor scale_factor)
+ : storage_(new internal::ImageSkiaStorage(source, scale_factor)) {
+ DCHECK(source);
+ if (!storage_->has_source())
+ storage_ = NULL;
+ // No other thread has reference to this, so it's safe to detach the thread.
+ DetachStorageFromThread();
+}
+
+ImageSkia::ImageSkia(const ImageSkiaRep& image_rep) {
+ Init(image_rep);
+ // No other thread has reference to this, so it's safe to detach the thread.
+ DetachStorageFromThread();
+}
+
+ImageSkia::ImageSkia(const ImageSkia& other) : storage_(other.storage_) {
+}
+
+ImageSkia& ImageSkia::operator=(const ImageSkia& other) {
+ storage_ = other.storage_;
+ return *this;
+}
+
+ImageSkia::~ImageSkia() {
+}
+
+// static
+ImageSkia ImageSkia::CreateFrom1xBitmap(const SkBitmap& bitmap) {
+ return ImageSkia(ImageSkiaRep(bitmap, ui::SCALE_FACTOR_100P));
+}
+
+scoped_ptr<ImageSkia> ImageSkia::DeepCopy() const {
+ ImageSkia* copy = new ImageSkia;
+ if (isNull())
+ return scoped_ptr<ImageSkia>(copy);
+
+ CHECK(CanRead());
+
+ std::vector<gfx::ImageSkiaRep>& reps = storage_->image_reps();
+ for (std::vector<gfx::ImageSkiaRep>::iterator iter = reps.begin();
+ iter != reps.end(); ++iter) {
+ copy->AddRepresentation(*iter);
+ }
+ // The copy has its own storage. Detach the copy from the current
+ // thread so that other thread can use this.
+ if (!copy->isNull())
+ copy->storage_->DetachFromThread();
+ return scoped_ptr<ImageSkia>(copy);
+}
+
+bool ImageSkia::BackedBySameObjectAs(const gfx::ImageSkia& other) const {
+ return storage_.get() == other.storage_.get();
+}
+
+void ImageSkia::AddRepresentation(const ImageSkiaRep& image_rep) {
+ DCHECK(!image_rep.is_null());
+
+ // TODO(oshima): This method should be called |SetRepresentation|
+ // and replace the existing rep if there is already one with the
+ // same scale factor so that we can guarantee that a ImageSkia
+ // instance contians only one image rep per scale factor. This is
+ // not possible now as ImageLoader currently stores need
+ // this feature, but this needs to be fixed.
+ if (isNull()) {
+ Init(image_rep);
+ } else {
+ CHECK(CanModify());
+ storage_->image_reps().push_back(image_rep);
+ }
+}
+
+void ImageSkia::RemoveRepresentation(ui::ScaleFactor scale_factor) {
+ if (isNull())
+ return;
+ CHECK(CanModify());
+
+ ImageSkiaReps& image_reps = storage_->image_reps();
+ ImageSkiaReps::iterator it =
+ storage_->FindRepresentation(scale_factor, false);
+ if (it != image_reps.end() && it->scale_factor() == scale_factor)
+ image_reps.erase(it);
+}
+
+bool ImageSkia::HasRepresentation(ui::ScaleFactor scale_factor) const {
+ if (isNull())
+ return false;
+ CHECK(CanRead());
+
+ ImageSkiaReps::iterator it =
+ storage_->FindRepresentation(scale_factor, false);
+ return (it != storage_->image_reps().end() &&
+ it->scale_factor() == scale_factor);
+}
+
+const ImageSkiaRep& ImageSkia::GetRepresentation(
+ ui::ScaleFactor scale_factor) const {
+ if (isNull())
+ return NullImageRep();
+
+ CHECK(CanRead());
+
+ ImageSkiaReps::iterator it = storage_->FindRepresentation(scale_factor, true);
+ if (it == storage_->image_reps().end())
+ return NullImageRep();
+
+ return *it;
+}
+
+void ImageSkia::SetReadOnly() {
+ CHECK(storage_.get());
+ storage_->SetReadOnly();
+ DetachStorageFromThread();
+}
+
+void ImageSkia::MakeThreadSafe() {
+ CHECK(storage_.get());
+ EnsureRepsForSupportedScaleFactors();
+ // Delete source as we no longer needs it.
+ if (storage_.get())
+ storage_->DeleteSource();
+ storage_->SetReadOnly();
+ CHECK(IsThreadSafe());
+}
+
+bool ImageSkia::IsThreadSafe() const {
+ return !storage_.get() || (storage_->read_only() && !storage_->has_source());
+}
+
+int ImageSkia::width() const {
+ return isNull() ? 0 : storage_->size().width();
+}
+
+gfx::Size ImageSkia::size() const {
+ return gfx::Size(width(), height());
+}
+
+int ImageSkia::height() const {
+ return isNull() ? 0 : storage_->size().height();
+}
+
+std::vector<ImageSkiaRep> ImageSkia::image_reps() const {
+ if (isNull())
+ return std::vector<ImageSkiaRep>();
+
+ CHECK(CanRead());
+
+ ImageSkiaReps internal_image_reps = storage_->image_reps();
+ // Create list of image reps to return, skipping null image reps which were
+ // added for caching purposes only.
+ ImageSkiaReps image_reps;
+ for (ImageSkiaReps::iterator it = internal_image_reps.begin();
+ it != internal_image_reps.end(); ++it) {
+ if (!it->is_null())
+ image_reps.push_back(*it);
+ }
+
+ return image_reps;
+}
+
+void ImageSkia::EnsureRepsForSupportedScaleFactors() const {
+ // Don't check ReadOnly because the source may generate images
+ // even for read only ImageSkia. Concurrent access will be protected
+ // by |DCHECK(CalledOnValidThread())| in FindRepresentation.
+ if (storage_.get() && storage_->has_source()) {
+ std::vector<ui::ScaleFactor> supported_scale_factors =
+ ui::GetSupportedScaleFactors();
+ for (size_t i = 0; i < supported_scale_factors.size(); ++i)
+ storage_->FindRepresentation(supported_scale_factors[i], true);
+ }
+}
+
+void ImageSkia::Init(const ImageSkiaRep& image_rep) {
+ // TODO(pkotwicz): The image should be null whenever image rep is null.
+ if (image_rep.sk_bitmap().empty()) {
+ storage_ = NULL;
+ return;
+ }
+ storage_ = new internal::ImageSkiaStorage(
+ NULL, gfx::Size(image_rep.GetWidth(), image_rep.GetHeight()));
+ storage_->image_reps().push_back(image_rep);
+}
+
+SkBitmap& ImageSkia::GetBitmap() const {
+ if (isNull()) {
+ // Callers expect a ImageSkiaRep even if it is |isNull()|.
+ // TODO(pkotwicz): Fix this.
+ return NullImageRep().mutable_sk_bitmap();
+ }
+
+ // TODO(oshima): This made a few tests flaky on Windows.
+ // Fix the root cause and re-enable this. crbug.com/145623.
+#if !defined(OS_WIN)
+ CHECK(CanRead());
+#endif
+
+ ImageSkiaReps::iterator it =
+ storage_->FindRepresentation(ui::SCALE_FACTOR_100P, true);
+ if (it != storage_->image_reps().end())
+ return it->mutable_sk_bitmap();
+ return NullImageRep().mutable_sk_bitmap();
+}
+
+bool ImageSkia::CanRead() const {
+ return !storage_.get() || storage_->CanRead();
+}
+
+bool ImageSkia::CanModify() const {
+ return !storage_.get() || storage_->CanModify();
+}
+
+void ImageSkia::DetachStorageFromThread() {
+ if (storage_.get())
+ storage_->DetachFromThread();
+}
+
+} // namespace gfx
diff --git a/chromium/ui/gfx/image/image_skia.h b/chromium/ui/gfx/image/image_skia.h
new file mode 100644
index 00000000000..9d0e9afa88f
--- /dev/null
+++ b/chromium/ui/gfx/image/image_skia.h
@@ -0,0 +1,167 @@
+// Copyright (c) 2012 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_IMAGE_IMAGE_SKIA_H_
+#define UI_GFX_IMAGE_IMAGE_SKIA_H_
+
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/gtest_prod_util.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "ui/base/ui_export.h"
+#include "ui/gfx/image/image_skia_rep.h"
+
+namespace gfx {
+class ImageSkiaSource;
+class Size;
+
+namespace internal {
+class ImageSkiaStorage;
+} // namespace internal
+
+namespace test {
+class TestOnThread;
+}
+
+// Container for the same image at different densities, similar to NSImage.
+// Image height and width are in DIP (Density Indepent Pixel) coordinates.
+//
+// ImageSkia should be used whenever possible instead of SkBitmap.
+// Functions that mutate the image should operate on the gfx::ImageSkiaRep
+// returned from ImageSkia::GetRepresentation, not on ImageSkia.
+//
+// ImageSkia is cheap to copy and intentionally supports copy semantics.
+class UI_EXPORT ImageSkia {
+ public:
+ typedef std::vector<ImageSkiaRep> ImageSkiaReps;
+
+ // Creates an instance with no bitmaps.
+ ImageSkia();
+
+ // Creates an instance that will use the |source| to get the image
+ // for scale factors. |size| specifes the size of the image in DIP.
+ // ImageSkia owns |source|.
+ ImageSkia(ImageSkiaSource* source, const gfx::Size& size);
+
+ // Creates an instance that uses the |source|. The constructor loads the image
+ // at |scale_factor| and uses its dimensions to calculate the size in DIP.
+ // ImageSkia owns |source|.
+ ImageSkia(ImageSkiaSource* source, ui::ScaleFactor scale_factor);
+
+ explicit ImageSkia(const gfx::ImageSkiaRep& image_rep);
+
+ // Copies a reference to |other|'s storage.
+ ImageSkia(const ImageSkia& other);
+
+ // Copies a reference to |other|'s storage.
+ ImageSkia& operator=(const ImageSkia& other);
+
+ ~ImageSkia();
+
+ // Creates an image from the passed in bitmap.
+ // DIP width and height are based on scale factor of 1x.
+ // Adds ref to passed in bitmap.
+ // WARNING: The resulting image will be pixelated when painted on a high
+ // density display.
+ static ImageSkia CreateFrom1xBitmap(const SkBitmap& bitmap);
+
+ // Returns a deep copy of this ImageSkia which has its own storage with
+ // the ImageSkiaRep instances that this ImageSkia currently has.
+ // This can be safely passed to and manipulated by another thread.
+ // Note that this does NOT generate ImageSkiaReps from its source.
+ // If you want to create a deep copy with ImageSkiaReps for supported
+ // scale factors, you need to explicitly call
+ // |EnsureRepsForSupportedScaleFactors()| first.
+ scoped_ptr<ImageSkia> DeepCopy() const;
+
+ // Returns true if this object is backed by the same ImageSkiaStorage as
+ // |other|. Will also return true if both images are isNull().
+ bool BackedBySameObjectAs(const gfx::ImageSkia& other) const;
+
+ // Adds |image_rep| to the image reps contained by this object.
+ void AddRepresentation(const gfx::ImageSkiaRep& image_rep);
+
+ // Removes the image rep of |scale_factor| if present.
+ void RemoveRepresentation(ui::ScaleFactor scale_factor);
+
+ // Returns true if the object owns an image rep whose density matches
+ // |scale_factor| exactly.
+ bool HasRepresentation(ui::ScaleFactor scale_factor) const;
+
+ // Returns the image rep whose density best matches
+ // |scale_factor|.
+ // Returns a null image rep if the object contains no image reps.
+ const gfx::ImageSkiaRep& GetRepresentation(
+ ui::ScaleFactor scale_factor) const;
+
+ // Make the ImageSkia instance read-only. Note that this only prevent
+ // modification from client code, and the storage may still be
+ // modified by the source if any (thus, it's not thread safe). This
+ // detaches the storage from currently accessing thread, so its safe
+ // to pass it to other thread as long as it is accessed only by that
+ // thread. If this ImageSkia's storage will be accessed by multiple
+ // threads, use |MakeThreadSafe()| method.
+ void SetReadOnly();
+
+ // Make the image thread safe by making the storage read only and remove
+ // its source if any. All ImageSkia that shares the same storage will also
+ // become thread safe. Note that in order to make it 100% thread safe,
+ // this must be called before it's been passed to anther thread.
+ void MakeThreadSafe();
+ bool IsThreadSafe() const;
+
+ // Returns true if this is a null object.
+ bool isNull() const { return storage_.get() == NULL; }
+
+ // Width and height of image in DIP coordinate system.
+ int width() const;
+ int height() const;
+ gfx::Size size() const;
+
+ // Returns pointer to 1x bitmap contained by this object. If there is no 1x
+ // bitmap, the bitmap whose scale factor is closest to 1x is returned.
+ // This function should only be used in unittests and on platforms which do
+ // not support scale factors other than 1x.
+ // TODO(pkotwicz): Return null SkBitmap when the object has no 1x bitmap.
+ const SkBitmap* bitmap() const { return &GetBitmap(); }
+
+ // Returns a vector with the image reps contained in this object.
+ // There is no guarantee that this will return all images rep for
+ // supported scale factors.
+ std::vector<gfx::ImageSkiaRep> image_reps() const;
+
+ // When the source is available, generates all ImageReps for
+ // supported scale factors. This method is defined as const as
+ // the state change in the storage is agnostic to the caller.
+ void EnsureRepsForSupportedScaleFactors() const;
+
+ private:
+ friend class test::TestOnThread;
+ FRIEND_TEST_ALL_PREFIXES(ImageSkiaTest, EmptyOnThreadTest);
+ FRIEND_TEST_ALL_PREFIXES(ImageSkiaTest, StaticOnThreadTest);
+ FRIEND_TEST_ALL_PREFIXES(ImageSkiaTest, SourceOnThreadTest);
+
+ // Initialize ImageSkiaStorage with passed in parameters.
+ // If the image rep's bitmap is empty, ImageStorage is set to NULL.
+ void Init(const gfx::ImageSkiaRep& image_rep);
+
+ SkBitmap& GetBitmap() const;
+
+ // Checks if the current thread can read/modify the ImageSkia.
+ bool CanRead() const;
+ bool CanModify() const;
+
+ // Detach the storage from the currently assinged thread
+ // so that other thread can access the storage.
+ void DetachStorageFromThread();
+
+ // A refptr so that ImageRepSkia can be copied cheaply.
+ scoped_refptr<internal::ImageSkiaStorage> storage_;
+};
+
+} // namespace gfx
+
+#endif // UI_GFX_IMAGE_IMAGE_SKIA_H_
diff --git a/chromium/ui/gfx/image/image_skia_operations.cc b/chromium/ui/gfx/image/image_skia_operations.cc
new file mode 100644
index 00000000000..663156044d4
--- /dev/null
+++ b/chromium/ui/gfx/image/image_skia_operations.cc
@@ -0,0 +1,567 @@
+// Copyright (c) 2012 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 "ui/gfx/image/image_skia_operations.h"
+
+#include "base/command_line.h"
+#include "base/logging.h"
+#include "skia/ext/image_operations.h"
+#include "ui/base/layout.h"
+#include "ui/gfx/canvas.h"
+#include "ui/gfx/image/canvas_image_source.h"
+#include "ui/gfx/image/image_skia.h"
+#include "ui/gfx/image/image_skia_rep.h"
+#include "ui/gfx/image/image_skia_source.h"
+#include "ui/gfx/insets.h"
+#include "ui/gfx/point.h"
+#include "ui/gfx/point_conversions.h"
+#include "ui/gfx/rect.h"
+#include "ui/gfx/rect_conversions.h"
+#include "ui/gfx/size.h"
+#include "ui/gfx/size_conversions.h"
+#include "ui/gfx/skbitmap_operations.h"
+#include "ui/gfx/skia_util.h"
+
+namespace gfx {
+namespace {
+
+gfx::Size DIPToPixelSize(gfx::Size dip_size, float scale) {
+ return ToCeiledSize(ScaleSize(dip_size, scale));
+}
+
+gfx::Rect DIPToPixelBounds(gfx::Rect dip_bounds, float scale) {
+ return gfx::Rect(ToFlooredPoint(ScalePoint(dip_bounds.origin(), scale)),
+ DIPToPixelSize(dip_bounds.size(), scale));
+}
+
+// Returns an image rep for the ImageSkiaSource to return to visually indicate
+// an error.
+ImageSkiaRep GetErrorImageRep(ui::ScaleFactor scale_factor,
+ const gfx::Size& pixel_size) {
+ SkBitmap bitmap;
+ bitmap.setConfig(
+ SkBitmap::kARGB_8888_Config, pixel_size.width(), pixel_size.height());
+ bitmap.allocPixels();
+ bitmap.eraseColor(SK_ColorRED);
+ return gfx::ImageSkiaRep(bitmap, scale_factor);
+}
+
+// A base image source class that creates an image from two source images.
+// This class guarantees that two ImageSkiaReps have have the same pixel size.
+class BinaryImageSource : public gfx::ImageSkiaSource {
+ protected:
+ BinaryImageSource(const ImageSkia& first,
+ const ImageSkia& second,
+ const char* source_name)
+ : first_(first),
+ second_(second),
+ source_name_(source_name) {
+ }
+ virtual ~BinaryImageSource() {
+ }
+
+ // gfx::ImageSkiaSource overrides:
+ virtual ImageSkiaRep GetImageForScale(ui::ScaleFactor scale_factor) OVERRIDE {
+ ImageSkiaRep first_rep = first_.GetRepresentation(scale_factor);
+ ImageSkiaRep second_rep = second_.GetRepresentation(scale_factor);
+ if (first_rep.pixel_size() != second_rep.pixel_size()) {
+ DCHECK_NE(first_rep.scale_factor(), second_rep.scale_factor());
+ if (first_rep.scale_factor() == second_rep.scale_factor()) {
+ LOG(ERROR) << "ImageSkiaRep size mismatch in " << source_name_;
+ return GetErrorImageRep(first_rep.scale_factor(),
+ first_rep.pixel_size());
+ }
+ first_rep = first_.GetRepresentation(ui::SCALE_FACTOR_100P);
+ second_rep = second_.GetRepresentation(ui::SCALE_FACTOR_100P);
+ DCHECK_EQ(first_rep.pixel_width(), second_rep.pixel_width());
+ DCHECK_EQ(first_rep.pixel_height(), second_rep.pixel_height());
+ if (first_rep.pixel_size() != second_rep.pixel_size()) {
+ LOG(ERROR) << "ImageSkiaRep size mismatch in " << source_name_;
+ return GetErrorImageRep(first_rep.scale_factor(),
+ first_rep.pixel_size());
+ }
+ } else {
+ DCHECK_EQ(first_rep.scale_factor(), second_rep.scale_factor());
+ }
+ return CreateImageSkiaRep(first_rep, second_rep);
+ }
+
+ // Creates a final image from two ImageSkiaReps. The pixel size of
+ // the two images are guaranteed to be the same.
+ virtual ImageSkiaRep CreateImageSkiaRep(
+ const ImageSkiaRep& first_rep,
+ const ImageSkiaRep& second_rep) const = 0;
+
+ private:
+ const ImageSkia first_;
+ const ImageSkia second_;
+ // The name of a class that implements the BinaryImageSource.
+ // The subclass is responsible for managing the memory.
+ const char* source_name_;
+
+ DISALLOW_COPY_AND_ASSIGN(BinaryImageSource);
+};
+
+class BlendingImageSource : public BinaryImageSource {
+ public:
+ BlendingImageSource(const ImageSkia& first,
+ const ImageSkia& second,
+ double alpha)
+ : BinaryImageSource(first, second, "BlendingImageSource"),
+ alpha_(alpha) {
+ }
+
+ virtual ~BlendingImageSource() {
+ }
+
+ // BinaryImageSource overrides:
+ virtual ImageSkiaRep CreateImageSkiaRep(
+ const ImageSkiaRep& first_rep,
+ const ImageSkiaRep& second_rep) const OVERRIDE {
+ SkBitmap blended = SkBitmapOperations::CreateBlendedBitmap(
+ first_rep.sk_bitmap(), second_rep.sk_bitmap(), alpha_);
+ return ImageSkiaRep(blended, first_rep.scale_factor());
+ }
+
+ private:
+ double alpha_;
+
+ DISALLOW_COPY_AND_ASSIGN(BlendingImageSource);
+};
+
+class SuperimposedImageSource : public gfx::CanvasImageSource {
+ public:
+ SuperimposedImageSource(const ImageSkia& first,
+ const ImageSkia& second)
+ : gfx::CanvasImageSource(first.size(), false /* is opaque */),
+ first_(first),
+ second_(second) {
+ }
+
+ virtual ~SuperimposedImageSource() {}
+
+ // gfx::CanvasImageSource override.
+ virtual void Draw(Canvas* canvas) OVERRIDE {
+ canvas->DrawImageInt(first_, 0, 0);
+ canvas->DrawImageInt(second_,
+ (first_.width() - second_.width()) / 2,
+ (first_.height() - second_.height()) / 2);
+ }
+
+ private:
+ const ImageSkia first_;
+ const ImageSkia second_;
+
+ DISALLOW_COPY_AND_ASSIGN(SuperimposedImageSource);
+};
+
+class TransparentImageSource : public gfx::ImageSkiaSource {
+ public:
+ TransparentImageSource(const ImageSkia& image, double alpha)
+ : image_(image),
+ alpha_(alpha) {
+ }
+
+ virtual ~TransparentImageSource() {}
+
+ private:
+ // gfx::ImageSkiaSource overrides:
+ virtual ImageSkiaRep GetImageForScale(ui::ScaleFactor scale_factor) OVERRIDE {
+ ImageSkiaRep image_rep = image_.GetRepresentation(scale_factor);
+ SkBitmap alpha;
+ alpha.setConfig(SkBitmap::kARGB_8888_Config,
+ image_rep.pixel_width(),
+ image_rep.pixel_height());
+ alpha.allocPixels();
+ alpha.eraseColor(SkColorSetARGB(alpha_ * 255, 0, 0, 0));
+ return ImageSkiaRep(
+ SkBitmapOperations::CreateMaskedBitmap(image_rep.sk_bitmap(), alpha),
+ image_rep.scale_factor());
+ }
+
+ ImageSkia image_;
+ double alpha_;
+
+ DISALLOW_COPY_AND_ASSIGN(TransparentImageSource);
+};
+
+class MaskedImageSource : public BinaryImageSource {
+ public:
+ MaskedImageSource(const ImageSkia& rgb, const ImageSkia& alpha)
+ : BinaryImageSource(rgb, alpha, "MaskedImageSource") {
+ }
+
+ virtual ~MaskedImageSource() {
+ }
+
+ // BinaryImageSource overrides:
+ virtual ImageSkiaRep CreateImageSkiaRep(
+ const ImageSkiaRep& first_rep,
+ const ImageSkiaRep& second_rep) const OVERRIDE {
+ return ImageSkiaRep(SkBitmapOperations::CreateMaskedBitmap(
+ first_rep.sk_bitmap(), second_rep.sk_bitmap()),
+ first_rep.scale_factor());
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(MaskedImageSource);
+};
+
+class TiledImageSource : public gfx::ImageSkiaSource {
+ public:
+ TiledImageSource(const ImageSkia& source,
+ int src_x, int src_y,
+ int dst_w, int dst_h)
+ : source_(source),
+ src_x_(src_x),
+ src_y_(src_y),
+ dst_w_(dst_w),
+ dst_h_(dst_h) {
+ }
+
+ virtual ~TiledImageSource() {
+ }
+
+ // gfx::ImageSkiaSource overrides:
+ virtual ImageSkiaRep GetImageForScale(ui::ScaleFactor scale_factor) OVERRIDE {
+ ImageSkiaRep source_rep = source_.GetRepresentation(scale_factor);
+ float scale = ui::GetScaleFactorScale(source_rep.scale_factor());
+ gfx::Rect bounds = DIPToPixelBounds(gfx::Rect(src_x_, src_y_, dst_w_,
+ dst_h_), scale);
+ return ImageSkiaRep(
+ SkBitmapOperations::CreateTiledBitmap(
+ source_rep.sk_bitmap(),
+ bounds.x(), bounds.y(), bounds.width(), bounds.height()),
+ source_rep.scale_factor());
+ }
+
+ private:
+ const ImageSkia source_;
+ const int src_x_;
+ const int src_y_;
+ const int dst_w_;
+ const int dst_h_;
+
+ DISALLOW_COPY_AND_ASSIGN(TiledImageSource);
+};
+
+class HSLImageSource : public gfx::ImageSkiaSource {
+ public:
+ HSLImageSource(const ImageSkia& image,
+ const color_utils::HSL& hsl_shift)
+ : image_(image),
+ hsl_shift_(hsl_shift) {
+ }
+
+ virtual ~HSLImageSource() {
+ }
+
+ // gfx::ImageSkiaSource overrides:
+ virtual ImageSkiaRep GetImageForScale(ui::ScaleFactor scale_factor) OVERRIDE {
+ ImageSkiaRep image_rep = image_.GetRepresentation(scale_factor);
+ return gfx::ImageSkiaRep(
+ SkBitmapOperations::CreateHSLShiftedBitmap(image_rep.sk_bitmap(),
+ hsl_shift_), image_rep.scale_factor());
+ }
+
+ private:
+ const gfx::ImageSkia image_;
+ const color_utils::HSL hsl_shift_;
+ DISALLOW_COPY_AND_ASSIGN(HSLImageSource);
+};
+
+// ImageSkiaSource which uses SkBitmapOperations::CreateButtonBackground
+// to generate image reps for the target image. The image and mask can be
+// diferent sizes (crbug.com/171725).
+class ButtonImageSource: public gfx::ImageSkiaSource {
+ public:
+ ButtonImageSource(SkColor color,
+ const ImageSkia& image,
+ const ImageSkia& mask)
+ : color_(color),
+ image_(image),
+ mask_(mask) {
+ }
+
+ virtual ~ButtonImageSource() {
+ }
+
+ // gfx::ImageSkiaSource overrides:
+ virtual ImageSkiaRep GetImageForScale(ui::ScaleFactor scale_factor) OVERRIDE {
+ ImageSkiaRep image_rep = image_.GetRepresentation(scale_factor);
+ ImageSkiaRep mask_rep = mask_.GetRepresentation(scale_factor);
+ if (image_rep.scale_factor() != mask_rep.scale_factor()) {
+ image_rep = image_.GetRepresentation(ui::SCALE_FACTOR_100P);
+ mask_rep = mask_.GetRepresentation(ui::SCALE_FACTOR_100P);
+ }
+ return gfx::ImageSkiaRep(
+ SkBitmapOperations::CreateButtonBackground(color_,
+ image_rep.sk_bitmap(), mask_rep.sk_bitmap()),
+ image_rep.scale_factor());
+ }
+
+ private:
+ const SkColor color_;
+ const ImageSkia image_;
+ const ImageSkia mask_;
+
+ DISALLOW_COPY_AND_ASSIGN(ButtonImageSource);
+};
+
+// ImageSkiaSource which uses SkBitmap::extractSubset to generate image reps
+// for the target image.
+class ExtractSubsetImageSource: public gfx::ImageSkiaSource {
+ public:
+ ExtractSubsetImageSource(const gfx::ImageSkia& image,
+ const gfx::Rect& subset_bounds)
+ : image_(image),
+ subset_bounds_(subset_bounds) {
+ }
+
+ virtual ~ExtractSubsetImageSource() {
+ }
+
+ // gfx::ImageSkiaSource overrides:
+ virtual ImageSkiaRep GetImageForScale(ui::ScaleFactor scale_factor) OVERRIDE {
+ ImageSkiaRep image_rep = image_.GetRepresentation(scale_factor);
+ float scale_to_pixel = ui::GetScaleFactorScale(image_rep.scale_factor());
+ SkIRect subset_bounds_in_pixel = RectToSkIRect(
+ DIPToPixelBounds(subset_bounds_, scale_to_pixel));
+ SkBitmap dst;
+ bool success = image_rep.sk_bitmap().extractSubset(&dst,
+ subset_bounds_in_pixel);
+ DCHECK(success);
+ return gfx::ImageSkiaRep(dst, image_rep.scale_factor());
+ }
+
+ private:
+ const gfx::ImageSkia image_;
+ const gfx::Rect subset_bounds_;
+
+ DISALLOW_COPY_AND_ASSIGN(ExtractSubsetImageSource);
+};
+
+// ResizeSource resizes relevant image reps in |source| to |target_dip_size|
+// for requested scale factors.
+class ResizeSource : public ImageSkiaSource {
+ public:
+ ResizeSource(const ImageSkia& source,
+ skia::ImageOperations::ResizeMethod method,
+ const Size& target_dip_size)
+ : source_(source),
+ resize_method_(method),
+ target_dip_size_(target_dip_size) {
+ }
+ virtual ~ResizeSource() {}
+
+ // gfx::ImageSkiaSource overrides:
+ virtual ImageSkiaRep GetImageForScale(ui::ScaleFactor scale_factor) OVERRIDE {
+ const ImageSkiaRep& image_rep = source_.GetRepresentation(scale_factor);
+ if (image_rep.GetWidth() == target_dip_size_.width() &&
+ image_rep.GetHeight() == target_dip_size_.height())
+ return image_rep;
+
+ const float scale = ui::GetScaleFactorScale(scale_factor);
+ const Size target_pixel_size = DIPToPixelSize(target_dip_size_, scale);
+ const SkBitmap resized = skia::ImageOperations::Resize(
+ image_rep.sk_bitmap(),
+ resize_method_,
+ target_pixel_size.width(),
+ target_pixel_size.height());
+ return ImageSkiaRep(resized, scale_factor);
+ }
+
+ private:
+ const ImageSkia source_;
+ skia::ImageOperations::ResizeMethod resize_method_;
+ const Size target_dip_size_;
+
+ DISALLOW_COPY_AND_ASSIGN(ResizeSource);
+};
+
+// DropShadowSource generates image reps with drop shadow for image reps in
+// |source| that represent requested scale factors.
+class DropShadowSource : public ImageSkiaSource {
+ public:
+ DropShadowSource(const ImageSkia& source,
+ const ShadowValues& shadows_in_dip)
+ : source_(source),
+ shaodws_in_dip_(shadows_in_dip) {
+ }
+ virtual ~DropShadowSource() {}
+
+ // gfx::ImageSkiaSource overrides:
+ virtual ImageSkiaRep GetImageForScale(ui::ScaleFactor scale_factor) OVERRIDE {
+ const ImageSkiaRep& image_rep = source_.GetRepresentation(scale_factor);
+
+ const float scale = image_rep.GetScale();
+ ShadowValues shadows_in_pixel;
+ for (size_t i = 0; i < shaodws_in_dip_.size(); ++i)
+ shadows_in_pixel.push_back(shaodws_in_dip_[i].Scale(scale));
+
+ const SkBitmap shadow_bitmap = SkBitmapOperations::CreateDropShadow(
+ image_rep.sk_bitmap(),
+ shadows_in_pixel);
+ return ImageSkiaRep(shadow_bitmap, image_rep.scale_factor());
+ }
+
+ private:
+ const ImageSkia source_;
+ const ShadowValues shaodws_in_dip_;
+
+ DISALLOW_COPY_AND_ASSIGN(DropShadowSource);
+};
+
+// RotatedSource generates image reps that are rotations of those in
+// |source| that represent requested scale factors.
+class RotatedSource : public ImageSkiaSource {
+ public:
+ RotatedSource(const ImageSkia& source,
+ SkBitmapOperations::RotationAmount rotation)
+ : source_(source),
+ rotation_(rotation) {
+ }
+ virtual ~RotatedSource() {}
+
+ // gfx::ImageSkiaSource overrides:
+ virtual ImageSkiaRep GetImageForScale(ui::ScaleFactor scale_factor) OVERRIDE {
+ const ImageSkiaRep& image_rep = source_.GetRepresentation(scale_factor);
+ const SkBitmap rotated_bitmap =
+ SkBitmapOperations::Rotate(image_rep.sk_bitmap(), rotation_);
+ return ImageSkiaRep(rotated_bitmap, image_rep.scale_factor());
+ }
+
+ private:
+ const ImageSkia source_;
+ const SkBitmapOperations::RotationAmount rotation_;
+
+ DISALLOW_COPY_AND_ASSIGN(RotatedSource);
+};
+
+
+} // namespace
+
+// static
+ImageSkia ImageSkiaOperations::CreateBlendedImage(const ImageSkia& first,
+ const ImageSkia& second,
+ double alpha) {
+ if (first.isNull() || second.isNull())
+ return ImageSkia();
+
+ return ImageSkia(new BlendingImageSource(first, second, alpha), first.size());
+}
+
+// static
+ImageSkia ImageSkiaOperations::CreateSuperimposedImage(
+ const ImageSkia& first,
+ const ImageSkia& second) {
+ if (first.isNull() || second.isNull())
+ return ImageSkia();
+
+ return ImageSkia(new SuperimposedImageSource(first, second), first.size());
+}
+
+// static
+ImageSkia ImageSkiaOperations::CreateTransparentImage(const ImageSkia& image,
+ double alpha) {
+ if (image.isNull())
+ return ImageSkia();
+
+ return ImageSkia(new TransparentImageSource(image, alpha), image.size());
+}
+
+// static
+ImageSkia ImageSkiaOperations::CreateMaskedImage(const ImageSkia& rgb,
+ const ImageSkia& alpha) {
+ if (rgb.isNull() || alpha.isNull())
+ return ImageSkia();
+
+ return ImageSkia(new MaskedImageSource(rgb, alpha), rgb.size());
+}
+
+// static
+ImageSkia ImageSkiaOperations::CreateTiledImage(const ImageSkia& source,
+ int src_x, int src_y,
+ int dst_w, int dst_h) {
+ if (source.isNull())
+ return ImageSkia();
+
+ return ImageSkia(new TiledImageSource(source, src_x, src_y, dst_w, dst_h),
+ gfx::Size(dst_w, dst_h));
+}
+
+// static
+ImageSkia ImageSkiaOperations::CreateHSLShiftedImage(
+ const ImageSkia& image,
+ const color_utils::HSL& hsl_shift) {
+ if (image.isNull())
+ return ImageSkia();
+
+ return ImageSkia(new HSLImageSource(image, hsl_shift), image.size());
+}
+
+// static
+ImageSkia ImageSkiaOperations::CreateButtonBackground(SkColor color,
+ const ImageSkia& image,
+ const ImageSkia& mask) {
+ if (image.isNull() || mask.isNull())
+ return ImageSkia();
+
+ return ImageSkia(new ButtonImageSource(color, image, mask), mask.size());
+}
+
+// static
+ImageSkia ImageSkiaOperations::ExtractSubset(const ImageSkia& image,
+ const Rect& subset_bounds) {
+ gfx::Rect clipped_bounds =
+ gfx::IntersectRects(subset_bounds, gfx::Rect(image.size()));
+ if (image.isNull() || clipped_bounds.IsEmpty()) {
+ return ImageSkia();
+ }
+
+ return ImageSkia(new ExtractSubsetImageSource(image, clipped_bounds),
+ clipped_bounds.size());
+}
+
+// static
+ImageSkia ImageSkiaOperations::CreateResizedImage(
+ const ImageSkia& source,
+ skia::ImageOperations::ResizeMethod method,
+ const Size& target_dip_size) {
+ if (source.isNull())
+ return ImageSkia();
+
+ return ImageSkia(new ResizeSource(source, method, target_dip_size),
+ target_dip_size);
+}
+
+// static
+ImageSkia ImageSkiaOperations::CreateImageWithDropShadow(
+ const ImageSkia& source,
+ const ShadowValues& shadows) {
+ if (source.isNull())
+ return ImageSkia();
+
+ const gfx::Insets shadow_padding = -gfx::ShadowValue::GetMargin(shadows);
+ gfx::Size shadow_image_size = source.size();
+ shadow_image_size.Enlarge(shadow_padding.width(),
+ shadow_padding.height());
+ return ImageSkia(new DropShadowSource(source, shadows), shadow_image_size);
+}
+
+// static
+ImageSkia ImageSkiaOperations::CreateRotatedImage(
+ const ImageSkia& source,
+ SkBitmapOperations::RotationAmount rotation) {
+ if (source.isNull())
+ return ImageSkia();
+
+ return ImageSkia(new RotatedSource(source, rotation),
+ SkBitmapOperations::ROTATION_180_CW == rotation ?
+ source.size() :
+ gfx::Size(source.height(), source.width()));
+
+}
+
+} // namespace gfx
diff --git a/chromium/ui/gfx/image/image_skia_operations.h b/chromium/ui/gfx/image/image_skia_operations.h
new file mode 100644
index 00000000000..9e397df7e0f
--- /dev/null
+++ b/chromium/ui/gfx/image/image_skia_operations.h
@@ -0,0 +1,103 @@
+// Copyright (c) 2012 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_IMAGE_SKIA_OPERATIONS_H_
+#define UI_GFX_IMAGE_SKIA_OPERATIONS_H_
+
+#include "base/gtest_prod_util.h"
+#include "skia/ext/image_operations.h"
+#include "ui/base/ui_export.h"
+#include "ui/gfx/color_utils.h"
+#include "ui/gfx/shadow_value.h"
+#include "ui/gfx/skbitmap_operations.h"
+
+namespace gfx {
+class ImageSkia;
+class Rect;
+class Size;
+
+class UI_EXPORT ImageSkiaOperations {
+ public:
+ // Create an image that is a blend of two others. The alpha argument
+ // specifies the opacity of the second imag. The provided image must
+ // use the kARGB_8888_Config config and be of equal dimensions.
+ static ImageSkia CreateBlendedImage(const ImageSkia& first,
+ const ImageSkia& second,
+ double alpha);
+
+ // Creates an image that is the original image with opacity set to |alpha|.
+ static ImageSkia CreateTransparentImage(const ImageSkia& image, double alpha);
+
+ // Creates new image by painting first and second image respectively.
+ // The second image is centered in respect to the first image.
+ static ImageSkia CreateSuperimposedImage(const ImageSkia& first,
+ const ImageSkia& second);
+
+ // Create an image that is the original image masked out by the mask defined
+ // in the alpha image. The images must use the kARGB_8888_Config config and
+ // be of equal dimensions.
+ static ImageSkia CreateMaskedImage(const ImageSkia& first,
+ const ImageSkia& alpha);
+
+ // Create an image that is cropped from another image. This is special
+ // because it tiles the original image, so your coordinates can extend
+ // outside the bounds of the original image.
+ static ImageSkia CreateTiledImage(const ImageSkia& image,
+ int src_x, int src_y,
+ int dst_w, int dst_h);
+
+ // Shift an image'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 ImageSkia CreateHSLShiftedImage(const gfx::ImageSkia& image,
+ const color_utils::HSL& hsl_shift);
+
+ // Creates 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 ImageSkia CreateButtonBackground(SkColor color,
+ const gfx::ImageSkia& image,
+ const gfx::ImageSkia& mask);
+
+ // Returns an image which is a subset of |image| with bounds |subset_bounds|.
+ // The |image| cannot use kA1_Config config.
+ static ImageSkia ExtractSubset(const gfx::ImageSkia& image,
+ const gfx::Rect& subset_bounds);
+
+ // Creates an image by resizing |source| to given |target_dip_size|.
+ static ImageSkia CreateResizedImage(const ImageSkia& source,
+ skia::ImageOperations::ResizeMethod methd,
+ const Size& target_dip_size);
+
+ // Creates an image with drop shadow defined in |shadows| for |source|.
+ static ImageSkia CreateImageWithDropShadow(const ImageSkia& source,
+ const ShadowValues& shadows);
+
+ // Creates an image which is a rotation of the |source|. |rotation| is the
+ // amount of clockwise rotation in degrees.
+ static ImageSkia CreateRotatedImage(
+ const ImageSkia& source,
+ SkBitmapOperations::RotationAmount rotation);
+
+ private:
+ ImageSkiaOperations(); // Class for scoping only.
+};
+
+} // namespace gfx
+
+#endif // UI_GFX_IMAGE_IMAGE_SKIA_OPERATIONS_H_
diff --git a/chromium/ui/gfx/image/image_skia_rep.cc b/chromium/ui/gfx/image/image_skia_rep.cc
new file mode 100644
index 00000000000..005b9a3f54b
--- /dev/null
+++ b/chromium/ui/gfx/image/image_skia_rep.cc
@@ -0,0 +1,46 @@
+// Copyright (c) 2012 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 "ui/gfx/image/image_skia_rep.h"
+
+namespace gfx {
+
+ImageSkiaRep::ImageSkiaRep()
+ : scale_factor_(ui::SCALE_FACTOR_NONE) {
+}
+
+ImageSkiaRep::~ImageSkiaRep() {
+}
+
+ImageSkiaRep::ImageSkiaRep(const gfx::Size& size,
+ ui::ScaleFactor scale_factor)
+ : scale_factor_(scale_factor) {
+ float scale = ui::GetScaleFactorScale(scale_factor);
+ bitmap_.setConfig(SkBitmap::kARGB_8888_Config,
+ static_cast<int>(size.width() * scale),
+ static_cast<int>(size.height() * scale));
+ bitmap_.allocPixels();
+}
+
+ImageSkiaRep::ImageSkiaRep(const SkBitmap& src,
+ ui::ScaleFactor scale_factor)
+ : bitmap_(src),
+ scale_factor_(scale_factor) {
+}
+
+int ImageSkiaRep::GetWidth() const {
+ return static_cast<int>(bitmap_.width() /
+ ui::GetScaleFactorScale(scale_factor_));
+}
+
+int ImageSkiaRep::GetHeight() const {
+ return static_cast<int>(bitmap_.height() /
+ ui::GetScaleFactorScale(scale_factor_));
+}
+
+float ImageSkiaRep::GetScale() const {
+ return ui::GetScaleFactorScale(scale_factor_);
+}
+
+} // namespace gfx
diff --git a/chromium/ui/gfx/image/image_skia_rep.h b/chromium/ui/gfx/image/image_skia_rep.h
new file mode 100644
index 00000000000..1314215d242
--- /dev/null
+++ b/chromium/ui/gfx/image/image_skia_rep.h
@@ -0,0 +1,61 @@
+// Copyright (c) 2012 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_IMAGE_IMAGE_SKIA_REP_H_
+#define UI_GFX_IMAGE_IMAGE_SKIA_REP_H_
+
+#include "third_party/skia/include/core/SkBitmap.h"
+#include "ui/base/layout.h"
+#include "ui/base/ui_export.h"
+#include "ui/gfx/size.h"
+
+namespace gfx {
+
+// An ImageSkiaRep represents a bitmap and the scale factor it is intended for.
+class UI_EXPORT ImageSkiaRep {
+ public:
+ // Create null bitmap.
+ ImageSkiaRep();
+ ~ImageSkiaRep();
+
+ // Creates a bitmap with kARGB_8888_Config config with given |size| in DIP.
+ // This allocates pixels in the bitmap.
+ ImageSkiaRep(const gfx::Size& size, ui::ScaleFactor scale_factor);
+
+ // Creates a bitmap with given scale factor.
+ // Adds ref to |src|.
+ ImageSkiaRep(const SkBitmap& src, ui::ScaleFactor scale_factor);
+
+ // Returns true if the backing bitmap is null.
+ bool is_null() const { return bitmap_.isNull(); }
+
+ // Get width and height of bitmap in DIP.
+ int GetWidth() const;
+ int GetHeight() const;
+
+ // Get width and height of bitmap in pixels.
+ int pixel_width() const { return bitmap_.width(); }
+ int pixel_height() const { return bitmap_.height(); }
+ Size pixel_size() const {
+ return Size(pixel_width(), pixel_height());
+ }
+
+ // Retrieves the scale that the bitmap will be painted at.
+ float GetScale() const;
+ ui::ScaleFactor scale_factor() const { return scale_factor_; }
+
+ // Returns backing bitmap.
+ const SkBitmap& sk_bitmap() const { return bitmap_; }
+
+ private:
+ friend class ImageSkia;
+ SkBitmap& mutable_sk_bitmap() { return bitmap_; }
+
+ SkBitmap bitmap_;
+ ui::ScaleFactor scale_factor_;
+};
+
+} // namespace gfx
+
+#endif // UI_GFX_IMAGE_IMAGE_SKIA_REP_H_
diff --git a/chromium/ui/gfx/image/image_skia_source.h b/chromium/ui/gfx/image/image_skia_source.h
new file mode 100644
index 00000000000..1fff1358717
--- /dev/null
+++ b/chromium/ui/gfx/image/image_skia_source.h
@@ -0,0 +1,28 @@
+// Copyright (c) 2012 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_IMAGE_IMAGE_SKIA_SOURCE_H_
+#define UI_GFX_IMAGE_IMAGE_SKIA_SOURCE_H_
+
+#include "ui/base/layout.h"
+#include "ui/base/ui_export.h"
+
+namespace gfx {
+
+class ImageSkiaRep;
+
+class UI_EXPORT ImageSkiaSource {
+ public:
+ virtual ~ImageSkiaSource() {}
+
+ // Returns the ImageSkiaRep for the given |scale_factor|. ImageSkia
+ // caches the returned ImageSkiaRep and calls this method only if it
+ // doesn't have ImageSkaiRep for given |scale_factor|. There is
+ // no need for the implementation to cache the image.
+ virtual gfx::ImageSkiaRep GetImageForScale(ui::ScaleFactor scale_factor) = 0;
+};
+
+} // namespace gfx
+
+#endif // UI_GFX_IMAGE_IMAGE_SKIA_SOURCE_H_
diff --git a/chromium/ui/gfx/image/image_skia_unittest.cc b/chromium/ui/gfx/image/image_skia_unittest.cc
new file mode 100644
index 00000000000..fd17453f8f9
--- /dev/null
+++ b/chromium/ui/gfx/image/image_skia_unittest.cc
@@ -0,0 +1,382 @@
+// Copyright (c) 2012 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 "ui/gfx/image/image_skia.h"
+
+#include "base/logging.h"
+#include "base/threading/simple_thread.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/skia/include/core/SkBitmap.h"
+#include "ui/base/layout.h"
+#include "ui/gfx/image/image_skia_rep.h"
+#include "ui/gfx/image/image_skia_source.h"
+#include "ui/gfx/size.h"
+
+// Duplicated from base/threading/non_thread_safe.h so that we can be
+// good citizens there and undef the macro.
+#if (!defined(NDEBUG) || defined(DCHECK_ALWAYS_ON))
+#define ENABLE_NON_THREAD_SAFE 1
+#else
+#define ENABLE_NON_THREAD_SAFE 0
+#endif
+
+namespace gfx {
+
+namespace {
+
+class FixedSource : public ImageSkiaSource {
+ public:
+ FixedSource(const ImageSkiaRep& image) : image_(image) {}
+
+ virtual ~FixedSource() {
+ }
+
+ virtual ImageSkiaRep GetImageForScale(ui::ScaleFactor scale_factor) OVERRIDE {
+ return image_;
+ }
+
+ private:
+ ImageSkiaRep image_;
+
+ DISALLOW_COPY_AND_ASSIGN(FixedSource);
+};
+
+class DynamicSource : public ImageSkiaSource {
+ public:
+ DynamicSource(const gfx::Size& size) : size_(size) {}
+
+ virtual ~DynamicSource() {
+ }
+
+ virtual ImageSkiaRep GetImageForScale(ui::ScaleFactor scale_factor) OVERRIDE {
+ return gfx::ImageSkiaRep(size_, scale_factor);
+ }
+
+ private:
+ gfx::Size size_;
+
+ DISALLOW_COPY_AND_ASSIGN(DynamicSource);
+};
+
+class NullSource: public ImageSkiaSource {
+ public:
+ NullSource() {
+ }
+
+ virtual ~NullSource() {
+ }
+
+ virtual ImageSkiaRep GetImageForScale(ui::ScaleFactor scale_factor) OVERRIDE {
+ return gfx::ImageSkiaRep();
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(NullSource);
+};
+
+} // namespace
+
+namespace test {
+class TestOnThread : public base::SimpleThread {
+ public:
+ explicit TestOnThread(ImageSkia* image_skia)
+ : SimpleThread("image_skia_on_thread"),
+ image_skia_(image_skia),
+ can_read_(false),
+ can_modify_(false) {
+ }
+
+ virtual void Run() OVERRIDE {
+ can_read_ = image_skia_->CanRead();
+ can_modify_ = image_skia_->CanModify();
+ if (can_read_)
+ image_skia_->image_reps();
+ }
+
+ void StartAndJoin() {
+ Start();
+ Join();
+ }
+
+ bool can_read() const { return can_read_; }
+
+ bool can_modify() const { return can_modify_; }
+
+ private:
+ ImageSkia* image_skia_;
+
+ bool can_read_;
+ bool can_modify_;
+
+ DISALLOW_COPY_AND_ASSIGN(TestOnThread);
+};
+
+} // namespace test
+
+TEST(ImageSkiaTest, FixedSource) {
+ ImageSkiaRep image(Size(100, 200), ui::SCALE_FACTOR_100P);
+ ImageSkia image_skia(new FixedSource(image), Size(100, 200));
+ EXPECT_EQ(0U, image_skia.image_reps().size());
+
+ const ImageSkiaRep& result_100p =
+ image_skia.GetRepresentation(ui::SCALE_FACTOR_100P);
+ EXPECT_EQ(100, result_100p.GetWidth());
+ EXPECT_EQ(200, result_100p.GetHeight());
+ EXPECT_EQ(ui::SCALE_FACTOR_100P, result_100p.scale_factor());
+ EXPECT_EQ(1U, image_skia.image_reps().size());
+
+ const ImageSkiaRep& result_200p =
+ image_skia.GetRepresentation(ui::SCALE_FACTOR_200P);
+
+ EXPECT_EQ(100, result_200p.GetWidth());
+ EXPECT_EQ(200, result_200p.GetHeight());
+ EXPECT_EQ(100, result_200p.pixel_width());
+ EXPECT_EQ(200, result_200p.pixel_height());
+ EXPECT_EQ(ui::SCALE_FACTOR_100P, result_200p.scale_factor());
+ EXPECT_EQ(1U, image_skia.image_reps().size());
+
+ // Get the representation again and make sure it doesn't
+ // generate new image skia rep.
+ image_skia.GetRepresentation(ui::SCALE_FACTOR_100P);
+ image_skia.GetRepresentation(ui::SCALE_FACTOR_200P);
+ EXPECT_EQ(1U, image_skia.image_reps().size());
+}
+
+TEST(ImageSkiaTest, DynamicSource) {
+ ImageSkia image_skia(new DynamicSource(Size(100, 200)), Size(100, 200));
+ EXPECT_EQ(0U, image_skia.image_reps().size());
+ const ImageSkiaRep& result_100p =
+ image_skia.GetRepresentation(ui::SCALE_FACTOR_100P);
+ EXPECT_EQ(100, result_100p.GetWidth());
+ EXPECT_EQ(200, result_100p.GetHeight());
+ EXPECT_EQ(ui::SCALE_FACTOR_100P, result_100p.scale_factor());
+ EXPECT_EQ(1U, image_skia.image_reps().size());
+
+ const ImageSkiaRep& result_200p =
+ image_skia.GetRepresentation(ui::SCALE_FACTOR_200P);
+ EXPECT_EQ(100, result_200p.GetWidth());
+ EXPECT_EQ(200, result_200p.GetHeight());
+ EXPECT_EQ(200, result_200p.pixel_width());
+ EXPECT_EQ(400, result_200p.pixel_height());
+ EXPECT_EQ(ui::SCALE_FACTOR_200P, result_200p.scale_factor());
+ EXPECT_EQ(2U, image_skia.image_reps().size());
+
+ // Get the representation again and make sure it doesn't
+ // generate new image skia rep.
+ image_skia.GetRepresentation(ui::SCALE_FACTOR_100P);
+ EXPECT_EQ(2U, image_skia.image_reps().size());
+ image_skia.GetRepresentation(ui::SCALE_FACTOR_200P);
+ EXPECT_EQ(2U, image_skia.image_reps().size());
+}
+
+// Tests that image_reps returns all of the representations in the
+// image when there are multiple representations for a scale factor.
+// This currently is the case with ImageLoader::LoadImages.
+TEST(ImageSkiaTest, ManyRepsPerScaleFactor) {
+ const int kSmallIcon1x = 16;
+ const int kSmallIcon2x = 32;
+ const int kLargeIcon1x = 32;
+
+ ImageSkia image(new NullSource(), gfx::Size(kSmallIcon1x, kSmallIcon1x));
+ // Simulate a source which loads images on a delay. Upon
+ // GetImageForScaleFactor, it immediately returns null and starts loading
+ // image reps slowly.
+ image.GetRepresentation(ui::SCALE_FACTOR_100P);
+ image.GetRepresentation(ui::SCALE_FACTOR_200P);
+
+ // After a lengthy amount of simulated time, finally loaded image reps.
+ image.AddRepresentation(ImageSkiaRep(
+ gfx::Size(kSmallIcon1x, kSmallIcon1x), ui::SCALE_FACTOR_100P));
+ image.AddRepresentation(ImageSkiaRep(
+ gfx::Size(kSmallIcon2x, kSmallIcon2x), ui::SCALE_FACTOR_200P));
+ image.AddRepresentation(ImageSkiaRep(
+ gfx::Size(kLargeIcon1x, kLargeIcon1x), ui::SCALE_FACTOR_100P));
+
+ std::vector<ImageSkiaRep> image_reps = image.image_reps();
+ EXPECT_EQ(3u, image_reps.size());
+
+ int num_1x = 0;
+ int num_2x = 0;
+ for (size_t i = 0; i < image_reps.size(); ++i) {
+ if (image_reps[i].scale_factor() == ui::SCALE_FACTOR_100P)
+ num_1x++;
+ else if (image_reps[i].scale_factor() == ui::SCALE_FACTOR_200P)
+ num_2x++;
+ }
+ EXPECT_EQ(2, num_1x);
+ EXPECT_EQ(1, num_2x);
+}
+
+TEST(ImageSkiaTest, GetBitmap) {
+ ImageSkia image_skia(new DynamicSource(Size(100, 200)), Size(100, 200));
+ const SkBitmap* bitmap = image_skia.bitmap();
+ EXPECT_NE(static_cast<SkBitmap*>(NULL), bitmap);
+ EXPECT_FALSE(bitmap->isNull());
+}
+
+TEST(ImageSkiaTest, GetBitmapFromEmpty) {
+ // Create an image with 1 representation and remove it so the ImageSkiaStorage
+ // is left with no representations.
+ ImageSkia empty_image(ImageSkiaRep(Size(100, 200), ui::SCALE_FACTOR_100P));
+ ImageSkia empty_image_copy(empty_image);
+ empty_image.RemoveRepresentation(ui::SCALE_FACTOR_100P);
+
+ // Check that ImageSkia::bitmap() still returns a valid SkBitmap pointer for
+ // the image and all its copies.
+ const SkBitmap* bitmap = empty_image_copy.bitmap();
+ ASSERT_NE(static_cast<SkBitmap*>(NULL), bitmap);
+ EXPECT_TRUE(bitmap->isNull());
+ EXPECT_TRUE(bitmap->empty());
+}
+
+TEST(ImageSkiaTest, BackedBySameObjectAs) {
+ // Null images should all be backed by the same object (NULL).
+ ImageSkia image;
+ ImageSkia unrelated;
+ EXPECT_TRUE(image.BackedBySameObjectAs(unrelated));
+
+ image.AddRepresentation(gfx::ImageSkiaRep(gfx::Size(10, 10),
+ ui::SCALE_FACTOR_100P));
+ ImageSkia copy = image;
+ copy.AddRepresentation(gfx::ImageSkiaRep(gfx::Size(10, 10),
+ ui::SCALE_FACTOR_200P));
+ unrelated.AddRepresentation(gfx::ImageSkiaRep(gfx::Size(10, 10),
+ ui::SCALE_FACTOR_100P));
+ EXPECT_TRUE(image.BackedBySameObjectAs(copy));
+ EXPECT_FALSE(image.BackedBySameObjectAs(unrelated));
+ EXPECT_FALSE(copy.BackedBySameObjectAs(unrelated));
+}
+
+#if ENABLE_NON_THREAD_SAFE
+TEST(ImageSkiaTest, EmptyOnThreadTest) {
+ ImageSkia empty;
+ test::TestOnThread empty_on_thread(&empty);
+ empty_on_thread.Start();
+ empty_on_thread.Join();
+ EXPECT_TRUE(empty_on_thread.can_read());
+ EXPECT_TRUE(empty_on_thread.can_modify());
+}
+
+TEST(ImageSkiaTest, StaticOnThreadTest) {
+ ImageSkia image(ImageSkiaRep(Size(100, 200), ui::SCALE_FACTOR_100P));
+ EXPECT_FALSE(image.IsThreadSafe());
+
+ test::TestOnThread image_on_thread(&image);
+ // an image that was never accessed on this thread can be
+ // read by other thread.
+ image_on_thread.StartAndJoin();
+ EXPECT_TRUE(image_on_thread.can_read());
+ EXPECT_TRUE(image_on_thread.can_modify());
+ EXPECT_FALSE(image.CanRead());
+ EXPECT_FALSE(image.CanModify());
+
+ image.DetachStorageFromThread();
+ // An image is accessed by this thread,
+ // so other thread cannot read/modify it.
+ image.image_reps();
+ test::TestOnThread image_on_thread2(&image);
+ image_on_thread2.StartAndJoin();
+ EXPECT_FALSE(image_on_thread2.can_read());
+ EXPECT_FALSE(image_on_thread2.can_modify());
+ EXPECT_TRUE(image.CanRead());
+ EXPECT_TRUE(image.CanModify());
+
+ image.DetachStorageFromThread();
+ scoped_ptr<ImageSkia> deep_copy(image.DeepCopy());
+ EXPECT_FALSE(deep_copy->IsThreadSafe());
+ test::TestOnThread deepcopy_on_thread(deep_copy.get());
+ deepcopy_on_thread.StartAndJoin();
+ EXPECT_TRUE(deepcopy_on_thread.can_read());
+ EXPECT_TRUE(deepcopy_on_thread.can_modify());
+ EXPECT_FALSE(deep_copy->CanRead());
+ EXPECT_FALSE(deep_copy->CanModify());
+
+ scoped_ptr<ImageSkia> deep_copy2(image.DeepCopy());
+ EXPECT_EQ(1U, deep_copy2->image_reps().size());
+ // Access it from current thread so that it can't be
+ // accessed from another thread.
+ deep_copy2->image_reps();
+ EXPECT_FALSE(deep_copy2->IsThreadSafe());
+ test::TestOnThread deepcopy2_on_thread(deep_copy2.get());
+ deepcopy2_on_thread.StartAndJoin();
+ EXPECT_FALSE(deepcopy2_on_thread.can_read());
+ EXPECT_FALSE(deepcopy2_on_thread.can_modify());
+ EXPECT_TRUE(deep_copy2->CanRead());
+ EXPECT_TRUE(deep_copy2->CanModify());
+
+ image.DetachStorageFromThread();
+ image.SetReadOnly();
+ // A read-only ImageSkia with no source is thread safe.
+ EXPECT_TRUE(image.IsThreadSafe());
+ test::TestOnThread readonly_on_thread(&image);
+ readonly_on_thread.StartAndJoin();
+ EXPECT_TRUE(readonly_on_thread.can_read());
+ EXPECT_FALSE(readonly_on_thread.can_modify());
+ EXPECT_TRUE(image.CanRead());
+ EXPECT_FALSE(image.CanModify());
+
+ image.DetachStorageFromThread();
+ image.MakeThreadSafe();
+ EXPECT_TRUE(image.IsThreadSafe());
+ test::TestOnThread threadsafe_on_thread(&image);
+ threadsafe_on_thread.StartAndJoin();
+ EXPECT_TRUE(threadsafe_on_thread.can_read());
+ EXPECT_FALSE(threadsafe_on_thread.can_modify());
+ EXPECT_TRUE(image.CanRead());
+ EXPECT_FALSE(image.CanModify());
+}
+
+TEST(ImageSkiaTest, SourceOnThreadTest) {
+ ImageSkia image(new DynamicSource(Size(100, 200)), Size(100, 200));
+ EXPECT_FALSE(image.IsThreadSafe());
+
+ test::TestOnThread image_on_thread(&image);
+ image_on_thread.StartAndJoin();
+ // an image that was never accessed on this thread can be
+ // read by other thread.
+ EXPECT_TRUE(image_on_thread.can_read());
+ EXPECT_TRUE(image_on_thread.can_modify());
+ EXPECT_FALSE(image.CanRead());
+ EXPECT_FALSE(image.CanModify());
+
+ image.DetachStorageFromThread();
+ // An image is accessed by this thread,
+ // so other thread cannot read/modify it.
+ image.image_reps();
+ test::TestOnThread image_on_thread2(&image);
+ image_on_thread2.StartAndJoin();
+ EXPECT_FALSE(image_on_thread2.can_read());
+ EXPECT_FALSE(image_on_thread2.can_modify());
+ EXPECT_TRUE(image.CanRead());
+ EXPECT_TRUE(image.CanModify());
+
+ image.DetachStorageFromThread();
+ image.SetReadOnly();
+ EXPECT_FALSE(image.IsThreadSafe());
+ test::TestOnThread readonly_on_thread(&image);
+ readonly_on_thread.StartAndJoin();
+ EXPECT_TRUE(readonly_on_thread.can_read());
+ EXPECT_FALSE(readonly_on_thread.can_modify());
+ EXPECT_FALSE(image.CanRead());
+ EXPECT_FALSE(image.CanModify());
+
+ image.DetachStorageFromThread();
+ image.MakeThreadSafe();
+ EXPECT_TRUE(image.IsThreadSafe());
+ // Check if image reps are generated for supported scale factors.
+ EXPECT_EQ(ui::GetSupportedScaleFactors().size(),
+ image.image_reps().size());
+ test::TestOnThread threadsafe_on_thread(&image);
+ threadsafe_on_thread.StartAndJoin();
+ EXPECT_TRUE(threadsafe_on_thread.can_read());
+ EXPECT_FALSE(threadsafe_on_thread.can_modify());
+ EXPECT_TRUE(image.CanRead());
+ EXPECT_FALSE(image.CanModify());
+}
+#endif // ENABLE_NON_THREAD_SAFE
+
+// Just in case we ever get lumped together with other compilation units.
+#undef ENABLE_NON_THREAD_SAFE
+
+} // namespace gfx
diff --git a/chromium/ui/gfx/image/image_skia_util_ios.h b/chromium/ui/gfx/image/image_skia_util_ios.h
new file mode 100644
index 00000000000..ff4468d94ce
--- /dev/null
+++ b/chromium/ui/gfx/image/image_skia_util_ios.h
@@ -0,0 +1,44 @@
+// Copyright 2012 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_IMAGE_IMAGE_SKIA_UTIL_IOS_H_
+#define UI_GFX_IMAGE_IMAGE_SKIA_UTIL_IOS_H_
+
+#include "ui/base/layout.h"
+#include "ui/base/ui_export.h"
+
+#ifdef __OBJC__
+@class UIImage;
+#else
+class UIImage;
+#endif
+
+namespace gfx {
+class ImageSkia;
+class ImageSkiaRep;
+
+// Converts to ImageSkia from UIImage.
+UI_EXPORT gfx::ImageSkia ImageSkiaFromUIImage(UIImage* image);
+
+// Converts to an ImageSkiaRep of |scale_factor| from UIImage.
+// |scale_factor| is passed explicitly in order to allow this method to be used
+// with a |scale_factor| which is not supported by the platform.
+// (ui::GetScaleFactorFromScale() is restricted to the platform's supported
+// scale factors.)
+UI_EXPORT gfx::ImageSkiaRep ImageSkiaRepOfScaleFactorFromUIImage(
+ UIImage* image,
+ ui::ScaleFactor scale_factor);
+
+// Converts to UIImage from ImageSkia. The returned UIImage will be at the scale
+// of the ImageSkiaRep in |image_skia| which most closely matches the device's
+// scale factor (eg Retina iPad -> 2x). Returns an autoreleased UIImage.
+UI_EXPORT UIImage* UIImageFromImageSkia(const gfx::ImageSkia& image_skia);
+
+// Converts to UIImage from ImageSkiaRep. Returns an autoreleased UIImage.
+UI_EXPORT UIImage* UIImageFromImageSkiaRep(
+ const gfx::ImageSkiaRep& image_skia_rep);
+
+} // namespace gfx
+
+#endif // UI_GFX_IMAGE_IMAGE_SKIA_UTIL_IOS_H_
diff --git a/chromium/ui/gfx/image/image_skia_util_ios.mm b/chromium/ui/gfx/image/image_skia_util_ios.mm
new file mode 100644
index 00000000000..0ff8978f786
--- /dev/null
+++ b/chromium/ui/gfx/image/image_skia_util_ios.mm
@@ -0,0 +1,58 @@
+// Copyright 2012 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 "ui/gfx/image/image_skia_util_ios.h"
+
+#include <UIKit/UIKit.h>
+
+#include "base/logging.h"
+#include "base/mac/scoped_cftyperef.h"
+#include "skia/ext/skia_utils_ios.h"
+#include "third_party/skia/include/core/SkBitmap.h"
+#include "ui/gfx/image/image_skia.h"
+
+namespace gfx {
+
+gfx::ImageSkia ImageSkiaFromUIImage(UIImage* image) {
+ gfx::ImageSkia image_skia;
+ gfx::ImageSkiaRep image_skia_rep = ImageSkiaRepOfScaleFactorFromUIImage(
+ image, ui::GetMaxScaleFactor());
+ if (!image_skia_rep.is_null())
+ image_skia.AddRepresentation(image_skia_rep);
+ return image_skia;
+}
+
+gfx::ImageSkiaRep ImageSkiaRepOfScaleFactorFromUIImage(
+ UIImage* image,
+ ui::ScaleFactor scale_factor) {
+ if (!image)
+ return gfx::ImageSkiaRep();
+
+ float scale = ui::GetScaleFactorScale(scale_factor);
+ CGSize size = image.size;
+ CGSize desired_size_for_scale =
+ CGSizeMake(size.width * scale, size.height * scale);
+ SkBitmap bitmap(gfx::CGImageToSkBitmap(image.CGImage,
+ desired_size_for_scale,
+ false));
+ return gfx::ImageSkiaRep(bitmap, scale_factor);
+}
+
+UIImage* UIImageFromImageSkia(const gfx::ImageSkia& image_skia) {
+ return UIImageFromImageSkiaRep(image_skia.GetRepresentation(
+ ui::GetMaxScaleFactor()));
+}
+
+UIImage* UIImageFromImageSkiaRep(const gfx::ImageSkiaRep& image_skia_rep) {
+ if (image_skia_rep.is_null())
+ return nil;
+
+ float scale = ui::GetScaleFactorScale(image_skia_rep.scale_factor());
+ base::ScopedCFTypeRef<CGColorSpaceRef> color_space(
+ CGColorSpaceCreateDeviceRGB());
+ return gfx::SkBitmapToUIImageWithColorSpace(image_skia_rep.sk_bitmap(), scale,
+ color_space);
+}
+
+} // namespace gfx
diff --git a/chromium/ui/gfx/image/image_skia_util_mac.h b/chromium/ui/gfx/image/image_skia_util_mac.h
new file mode 100644
index 00000000000..1a4427da89f
--- /dev/null
+++ b/chromium/ui/gfx/image/image_skia_util_mac.h
@@ -0,0 +1,48 @@
+// Copyright (c) 2012 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_IMAGE_IMAGE_SKIA_UTIL_MAC_H_
+#define UI_GFX_IMAGE_IMAGE_SKIA_UTIL_MAC_H_
+
+#include <ApplicationServices/ApplicationServices.h>
+
+#include "ui/base/ui_export.h"
+
+#ifdef __LP64__
+typedef CGSize NSSize;
+#else
+typedef struct _NSSize NSSize;
+#endif
+
+#ifdef __OBJC__
+@class NSImage;
+#else
+class NSImage;
+#endif
+
+namespace gfx {
+class ImageSkia;
+
+// Converts to ImageSkia from NSImage.
+UI_EXPORT gfx::ImageSkia ImageSkiaFromNSImage(NSImage* image);
+
+// Resizes NSImage to |size| DIP and then converts to ImageSkia.
+UI_EXPORT gfx::ImageSkia ImageSkiaFromResizedNSImage(NSImage* image,
+ NSSize size);
+
+// Resizes |[NSImage imageNamed:@NSApplicationIcon]| to have edge width of
+// |size| DIP and returns result as ImageSkia.
+UI_EXPORT gfx::ImageSkia ApplicationIconAtSize(int size);
+
+// Converts to NSImage from ImageSkia.
+UI_EXPORT NSImage* NSImageFromImageSkia(const gfx::ImageSkia& image_skia);
+
+// Converts to NSImage from given ImageSkia and a color space.
+UI_EXPORT NSImage* NSImageFromImageSkiaWithColorSpace(
+ const gfx::ImageSkia& image_skia,
+ CGColorSpaceRef color_space);
+
+} // namespace gfx
+
+#endif // UI_GFX_IMAGE_IMAGE_SKIA_UTIL_MAC_H_
diff --git a/chromium/ui/gfx/image/image_skia_util_mac.mm b/chromium/ui/gfx/image/image_skia_util_mac.mm
new file mode 100644
index 00000000000..f9d0475af7f
--- /dev/null
+++ b/chromium/ui/gfx/image/image_skia_util_mac.mm
@@ -0,0 +1,124 @@
+// Copyright (c) 2012 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 "ui/gfx/image/image_skia_util_mac.h"
+
+#include <cmath>
+#include <limits>
+
+#import <AppKit/AppKit.h>
+
+#include "base/mac/mac_util.h"
+#include "base/mac/scoped_nsobject.h"
+#include "base/memory/scoped_ptr.h"
+#include "skia/ext/skia_utils_mac.h"
+#include "third_party/skia/include/core/SkBitmap.h"
+#include "ui/gfx/image/image_skia.h"
+
+namespace {
+
+// Returns NSImageRep whose pixel size most closely matches |desired_size|.
+NSImageRep* GetNSImageRepWithPixelSize(NSImage* image,
+ NSSize desired_size) {
+ float smallest_diff = std::numeric_limits<float>::max();
+ NSImageRep* closest_match = nil;
+ for (NSImageRep* image_rep in [image representations]) {
+ float diff = std::abs(desired_size.width - [image_rep pixelsWide]) +
+ std::abs(desired_size.height - [image_rep pixelsHigh]);
+ if (diff < smallest_diff) {
+ smallest_diff = diff;
+ closest_match = image_rep;
+ }
+ }
+ return closest_match;
+}
+
+// Returns true if NSImage has no representations
+bool IsNSImageEmpty(NSImage* image) {
+ return ([image representations].count == 0);
+}
+
+} // namespace
+
+namespace gfx {
+
+gfx::ImageSkia ImageSkiaFromNSImage(NSImage* image) {
+ return ImageSkiaFromResizedNSImage(image, [image size]);
+}
+
+gfx::ImageSkia ImageSkiaFromResizedNSImage(NSImage* image,
+ NSSize desired_size) {
+ // Resize and convert to ImageSkia simultaneously to save on computation.
+ // TODO(pkotwicz): Separate resizing NSImage and converting to ImageSkia.
+ // Convert to ImageSkia by finding the most appropriate NSImageRep for
+ // each supported scale factor and resizing if necessary.
+
+ if (IsNSImageEmpty(image))
+ return gfx::ImageSkia();
+
+ std::vector<ui::ScaleFactor> supported_scale_factors =
+ ui::GetSupportedScaleFactors();
+
+ gfx::ImageSkia image_skia;
+ for (size_t i = 0; i < supported_scale_factors.size(); ++i) {
+ float scale = ui::GetScaleFactorScale(supported_scale_factors[i]);
+ NSSize desired_size_for_scale = NSMakeSize(desired_size.width * scale,
+ desired_size.height * scale);
+ NSImageRep* ns_image_rep = GetNSImageRepWithPixelSize(image,
+ desired_size_for_scale);
+
+ SkBitmap bitmap(gfx::NSImageRepToSkBitmap(ns_image_rep,
+ desired_size_for_scale, false));
+ if (bitmap.isNull())
+ continue;
+
+ image_skia.AddRepresentation(gfx::ImageSkiaRep(bitmap,
+ supported_scale_factors[i]));
+ }
+ return image_skia;
+}
+
+gfx::ImageSkia ApplicationIconAtSize(int desired_size) {
+ NSImage* image = [NSImage imageNamed:@"NSApplicationIcon"];
+ return ImageSkiaFromResizedNSImage(image,
+ NSMakeSize(desired_size, desired_size));
+}
+
+NSImage* NSImageFromImageSkia(const gfx::ImageSkia& image_skia) {
+ if (image_skia.isNull())
+ return nil;
+
+ base::scoped_nsobject<NSImage> image([[NSImage alloc] init]);
+ image_skia.EnsureRepsForSupportedScaleFactors();
+ std::vector<gfx::ImageSkiaRep> image_reps = image_skia.image_reps();
+ for (std::vector<gfx::ImageSkiaRep>::const_iterator it = image_reps.begin();
+ it != image_reps.end(); ++it) {
+ [image addRepresentation:
+ gfx::SkBitmapToNSBitmapImageRep(it->sk_bitmap())];
+ }
+
+ [image setSize:NSMakeSize(image_skia.width(), image_skia.height())];
+ return [image.release() autorelease];
+}
+
+NSImage* NSImageFromImageSkiaWithColorSpace(const gfx::ImageSkia& image_skia,
+ CGColorSpaceRef color_space) {
+ if (image_skia.isNull())
+ return nil;
+
+ base::scoped_nsobject<NSImage> image([[NSImage alloc] init]);
+ image_skia.EnsureRepsForSupportedScaleFactors();
+ std::vector<gfx::ImageSkiaRep> image_reps = image_skia.image_reps();
+ for (std::vector<gfx::ImageSkiaRep>::const_iterator it = image_reps.begin();
+ it != image_reps.end(); ++it) {
+ [image addRepresentation:
+ gfx::SkBitmapToNSBitmapImageRepWithColorSpace(it->sk_bitmap(),
+ color_space)];
+ }
+
+ [image setSize:NSMakeSize(image_skia.width(), image_skia.height())];
+ return [image.release() autorelease];
+}
+
+} // namespace gfx
diff --git a/chromium/ui/gfx/image/image_unittest.cc b/chromium/ui/gfx/image/image_unittest.cc
new file mode 100644
index 00000000000..6d3c649643d
--- /dev/null
+++ b/chromium/ui/gfx/image/image_unittest.cc
@@ -0,0 +1,691 @@
+// Copyright (c) 2012 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 "testing/gtest/include/gtest/gtest.h"
+#include "third_party/skia/include/core/SkCanvas.h"
+#include "third_party/skia/include/core/SkPaint.h"
+#include "ui/base/layout.h"
+#include "ui/gfx/image/image.h"
+#include "ui/gfx/image/image_png_rep.h"
+#include "ui/gfx/image/image_skia.h"
+#include "ui/gfx/image/image_unittest_util.h"
+
+#if defined(TOOLKIT_GTK)
+#include <gtk/gtk.h>
+#include "ui/gfx/gtk_util.h"
+#elif defined(OS_IOS)
+#include "base/mac/foundation_util.h"
+#include "skia/ext/skia_utils_ios.h"
+#elif defined(OS_MACOSX)
+#include "base/mac/mac_util.h"
+#include "skia/ext/skia_utils_mac.h"
+#endif
+
+namespace {
+
+#if defined(TOOLKIT_VIEWS) || defined(OS_ANDROID)
+const bool kUsesSkiaNatively = true;
+#else
+const bool kUsesSkiaNatively = false;
+#endif
+
+class ImageTest : public testing::Test {
+};
+
+namespace gt = gfx::test;
+
+TEST_F(ImageTest, EmptyImage) {
+ // Test the default constructor.
+ gfx::Image image;
+ EXPECT_EQ(0U, image.RepresentationCount());
+ EXPECT_TRUE(image.IsEmpty());
+ EXPECT_EQ(0, image.Width());
+ EXPECT_EQ(0, image.Height());
+
+ // Test the copy constructor.
+ gfx::Image imageCopy(image);
+ EXPECT_TRUE(imageCopy.IsEmpty());
+ EXPECT_EQ(0, imageCopy.Width());
+ EXPECT_EQ(0, imageCopy.Height());
+
+ // Test calling SwapRepresentations() with an empty image.
+ gfx::Image image2(gt::CreateImageSkia(25, 25));
+ EXPECT_FALSE(image2.IsEmpty());
+ EXPECT_EQ(25, image2.Width());
+ EXPECT_EQ(25, image2.Height());
+
+ image.SwapRepresentations(&image2);
+ EXPECT_FALSE(image.IsEmpty());
+ EXPECT_EQ(25, image.Width());
+ EXPECT_EQ(25, image.Height());
+ EXPECT_TRUE(image2.IsEmpty());
+ EXPECT_EQ(0, image2.Width());
+ EXPECT_EQ(0, image2.Height());
+}
+
+// Test constructing a gfx::Image from an empty PlatformImage.
+TEST_F(ImageTest, EmptyImageFromEmptyPlatformImage) {
+#if defined(OS_IOS) || defined(OS_MACOSX) || defined(TOOLKIT_GTK)
+ gfx::Image image1(NULL);
+ EXPECT_TRUE(image1.IsEmpty());
+ EXPECT_EQ(0, image1.Width());
+ EXPECT_EQ(0, image1.Height());
+ EXPECT_EQ(0U, image1.RepresentationCount());
+#endif
+
+ // gfx::ImageSkia and gfx::ImagePNGRep are available on all platforms.
+ gfx::ImageSkia image_skia;
+ EXPECT_TRUE(image_skia.isNull());
+ gfx::Image image2(image_skia);
+ EXPECT_TRUE(image2.IsEmpty());
+ EXPECT_EQ(0, image2.Width());
+ EXPECT_EQ(0, image2.Height());
+ EXPECT_EQ(0U, image2.RepresentationCount());
+
+ std::vector<gfx::ImagePNGRep> image_png_reps;
+ gfx::Image image3(image_png_reps);
+ EXPECT_TRUE(image3.IsEmpty());
+ EXPECT_EQ(0, image3.Width());
+ EXPECT_EQ(0, image3.Height());
+ EXPECT_EQ(0U, image3.RepresentationCount());
+}
+
+// The resulting Image should be empty when it is created using obviously
+// invalid data.
+TEST_F(ImageTest, EmptyImageFromObviouslyInvalidPNGImage) {
+ std::vector<gfx::ImagePNGRep> image_png_reps1;
+ image_png_reps1.push_back(gfx::ImagePNGRep(NULL, ui::SCALE_FACTOR_100P));
+ gfx::Image image1(image_png_reps1);
+ EXPECT_TRUE(image1.IsEmpty());
+ EXPECT_EQ(0U, image1.RepresentationCount());
+
+ std::vector<gfx::ImagePNGRep> image_png_reps2;
+ image_png_reps2.push_back(gfx::ImagePNGRep(
+ new base::RefCountedBytes(), ui::SCALE_FACTOR_100P));
+ gfx::Image image2(image_png_reps2);
+ EXPECT_TRUE(image2.IsEmpty());
+ EXPECT_EQ(0U, image2.RepresentationCount());
+}
+
+// Test the Width, Height and Size of an empty and non-empty image.
+TEST_F(ImageTest, ImageSize) {
+ gfx::Image image;
+ EXPECT_EQ(0, image.Width());
+ EXPECT_EQ(0, image.Height());
+ EXPECT_EQ(gfx::Size(0, 0), image.Size());
+
+ gfx::Image image2(gt::CreateImageSkia(10, 25));
+ EXPECT_EQ(10, image2.Width());
+ EXPECT_EQ(25, image2.Height());
+ EXPECT_EQ(gfx::Size(10, 25), image2.Size());
+}
+
+TEST_F(ImageTest, SkiaToSkia) {
+ gfx::Image image(gt::CreateImageSkia(25, 25));
+ EXPECT_EQ(25, image.Width());
+ EXPECT_EQ(25, image.Height());
+
+ // Test ToImageSkia().
+ const gfx::ImageSkia* image_skia1 = image.ToImageSkia();
+ EXPECT_TRUE(image_skia1);
+ EXPECT_FALSE(image_skia1->isNull());
+ EXPECT_EQ(1U, image.RepresentationCount());
+
+ // Make sure double conversion doesn't happen.
+ const gfx::ImageSkia* image_skia2 = image.ToImageSkia();
+ EXPECT_EQ(1U, image.RepresentationCount());
+
+ // ToImageSkia() should always return the same gfx::ImageSkia.
+ EXPECT_EQ(image_skia1, image_skia2);
+
+ // Test ToSkBitmap().
+ const SkBitmap* bitmap1 = image.ToSkBitmap();
+ const SkBitmap* bitmap2 = image.ToSkBitmap();
+ EXPECT_TRUE(bitmap1);
+ EXPECT_FALSE(bitmap1->isNull());
+ EXPECT_EQ(bitmap1, bitmap2);
+
+ EXPECT_EQ(1U, image.RepresentationCount());
+ EXPECT_TRUE(image.HasRepresentation(gfx::Image::kImageRepSkia));
+ if (!kUsesSkiaNatively)
+ EXPECT_FALSE(image.HasRepresentation(gt::GetPlatformRepresentationType()));
+}
+
+TEST_F(ImageTest, EmptyImageToPNG) {
+ gfx::Image image;
+ scoped_refptr<base::RefCountedMemory> png_bytes = image.As1xPNGBytes();
+ EXPECT_TRUE(png_bytes.get());
+ EXPECT_FALSE(png_bytes->size());
+}
+
+// Check that getting the 1x PNG bytes from images which do not have a 1x
+// representation returns NULL.
+TEST_F(ImageTest, ImageNo1xToPNG) {
+ // Image with 2x only.
+ const int kSize2x = 50;
+ gfx::ImageSkia image_skia;
+ image_skia.AddRepresentation(gfx::ImageSkiaRep(gt::CreateBitmap(
+ kSize2x, kSize2x), ui::SCALE_FACTOR_200P));
+ gfx::Image image1(image_skia);
+ scoped_refptr<base::RefCountedMemory> png_bytes1 = image1.As1xPNGBytes();
+ EXPECT_TRUE(png_bytes1.get());
+ EXPECT_FALSE(png_bytes1->size());
+
+ std::vector<gfx::ImagePNGRep> image_png_reps;
+ image_png_reps.push_back(gfx::ImagePNGRep(
+ gt::CreatePNGBytes(kSize2x), ui::SCALE_FACTOR_200P));
+ gfx::Image image2(image_png_reps);
+ EXPECT_FALSE(image2.IsEmpty());
+ EXPECT_EQ(0, image2.Width());
+ EXPECT_EQ(0, image2.Height());
+ scoped_refptr<base::RefCountedMemory> png_bytes2 = image2.As1xPNGBytes();
+ EXPECT_TRUE(png_bytes2.get());
+ EXPECT_FALSE(png_bytes2->size());
+}
+
+// Check that for an image initialized with multi resolution PNG data,
+// As1xPNGBytes() returns the 1x bytes.
+TEST_F(ImageTest, CreateExtractPNGBytes) {
+ const int kSize1x = 25;
+ const int kSize2x = 50;
+
+ scoped_refptr<base::RefCountedMemory> bytes1x = gt::CreatePNGBytes(kSize1x);
+ std::vector<gfx::ImagePNGRep> image_png_reps;
+ image_png_reps.push_back(gfx::ImagePNGRep(bytes1x, ui::SCALE_FACTOR_100P));
+ image_png_reps.push_back(gfx::ImagePNGRep(
+ gt::CreatePNGBytes(kSize2x), ui::SCALE_FACTOR_200P));
+
+ gfx::Image image(image_png_reps);
+ EXPECT_FALSE(image.IsEmpty());
+ EXPECT_EQ(25, image.Width());
+ EXPECT_EQ(25, image.Height());
+
+ EXPECT_TRUE(std::equal(bytes1x->front(), bytes1x->front() + bytes1x->size(),
+ image.As1xPNGBytes()->front()));
+}
+
+TEST_F(ImageTest, MultiResolutionImageSkiaToPNG) {
+ const int kSize1x = 25;
+ const int kSize2x = 50;
+
+ SkBitmap bitmap_1x = gt::CreateBitmap(kSize1x, kSize1x);
+ gfx::ImageSkia image_skia;
+ image_skia.AddRepresentation(gfx::ImageSkiaRep(bitmap_1x,
+ ui::SCALE_FACTOR_100P));
+ image_skia.AddRepresentation(gfx::ImageSkiaRep(gt::CreateBitmap(
+ kSize2x, kSize2x), ui::SCALE_FACTOR_200P));
+ gfx::Image image(image_skia);
+
+ EXPECT_TRUE(gt::IsEqual(image.As1xPNGBytes(), bitmap_1x));
+ EXPECT_TRUE(image.HasRepresentation(gfx::Image::kImageRepPNG));
+}
+
+TEST_F(ImageTest, MultiResolutionPNGToImageSkia) {
+ const int kSize1x = 25;
+ const int kSize2x = 50;
+
+ scoped_refptr<base::RefCountedMemory> bytes1x = gt::CreatePNGBytes(kSize1x);
+ scoped_refptr<base::RefCountedMemory> bytes2x = gt::CreatePNGBytes(kSize2x);
+
+ std::vector<gfx::ImagePNGRep> image_png_reps;
+ image_png_reps.push_back(gfx::ImagePNGRep(bytes1x, ui::SCALE_FACTOR_100P));
+ image_png_reps.push_back(gfx::ImagePNGRep(bytes2x, ui::SCALE_FACTOR_200P));
+ gfx::Image image(image_png_reps);
+
+ std::vector<ui::ScaleFactor> scale_factors;
+ scale_factors.push_back(ui::SCALE_FACTOR_100P);
+ scale_factors.push_back(ui::SCALE_FACTOR_200P);
+ gfx::ImageSkia image_skia = image.AsImageSkia();
+ EXPECT_TRUE(gt::ImageSkiaStructureMatches(image_skia, kSize1x, kSize1x,
+ scale_factors));
+ EXPECT_TRUE(gt::IsEqual(bytes1x,
+ image_skia.GetRepresentation(ui::SCALE_FACTOR_100P).sk_bitmap()));
+ EXPECT_TRUE(gt::IsEqual(bytes2x,
+ image_skia.GetRepresentation(ui::SCALE_FACTOR_200P).sk_bitmap()));
+}
+
+TEST_F(ImageTest, MultiResolutionPNGToPlatform) {
+ const int kSize1x = 25;
+ const int kSize2x = 50;
+
+ scoped_refptr<base::RefCountedMemory> bytes1x = gt::CreatePNGBytes(kSize1x);
+ scoped_refptr<base::RefCountedMemory> bytes2x = gt::CreatePNGBytes(kSize2x);
+ std::vector<gfx::ImagePNGRep> image_png_reps;
+ image_png_reps.push_back(gfx::ImagePNGRep(bytes1x, ui::SCALE_FACTOR_100P));
+ image_png_reps.push_back(gfx::ImagePNGRep(bytes2x, ui::SCALE_FACTOR_200P));
+
+ gfx::Image from_png(image_png_reps);
+ gfx::Image from_platform(gt::CopyPlatformType(from_png));
+#if defined(OS_IOS)
+ // On iOS the platform type (UIImage) only supports one resolution.
+ std::vector<ui::ScaleFactor> scale_factors = ui::GetSupportedScaleFactors();
+ EXPECT_EQ(scale_factors.size(), 1U);
+ if (scale_factors[0] == ui::SCALE_FACTOR_100P)
+ EXPECT_TRUE(gt::IsEqual(bytes1x, from_platform.AsBitmap()));
+ else if (scale_factors[0] == ui::SCALE_FACTOR_200P)
+ EXPECT_TRUE(gt::IsEqual(bytes2x, from_platform.AsBitmap()));
+ else
+ ADD_FAILURE() << "Unexpected platform scale factor.";
+#else
+ EXPECT_TRUE(gt::IsEqual(bytes1x, from_platform.AsBitmap()));
+#endif // defined(OS_IOS)
+}
+
+
+TEST_F(ImageTest, PlatformToPNGEncodeAndDecode) {
+ gfx::Image image(gt::CreatePlatformImage());
+ scoped_refptr<base::RefCountedMemory> png_data = image.As1xPNGBytes();
+ EXPECT_TRUE(png_data.get());
+ EXPECT_TRUE(png_data->size());
+ EXPECT_TRUE(image.HasRepresentation(gfx::Image::kImageRepPNG));
+
+ std::vector<gfx::ImagePNGRep> image_png_reps;
+ image_png_reps.push_back(gfx::ImagePNGRep(png_data, ui::SCALE_FACTOR_100P));
+ gfx::Image from_png(image_png_reps);
+
+ EXPECT_TRUE(from_png.HasRepresentation(gfx::Image::kImageRepPNG));
+ EXPECT_TRUE(gt::IsPlatformImageValid(gt::ToPlatformType(from_png)));
+}
+
+// The platform types use the platform provided encoding/decoding of PNGs. Make
+// sure these work with the Skia Encode/Decode.
+TEST_F(ImageTest, PNGEncodeFromSkiaDecodeToPlatform) {
+ // Force the conversion sequence skia to png to platform_type.
+ ui::ScaleFactor ideal_scale_factor = ui::GetScaleFactorFromScale(1.0f);
+
+ gfx::Image from_bitmap = gfx::Image::CreateFrom1xBitmap(
+ gt::CreateBitmap(25, 25));
+ scoped_refptr<base::RefCountedMemory> png_bytes =
+ from_bitmap.As1xPNGBytes();
+
+ std::vector<gfx::ImagePNGRep> image_png_reps;
+ image_png_reps.push_back(gfx::ImagePNGRep(png_bytes, ideal_scale_factor));
+ gfx::Image from_png(image_png_reps);
+
+ gfx::Image from_platform(gt::CopyPlatformType(from_png));
+
+ EXPECT_TRUE(gt::IsPlatformImageValid(gt::ToPlatformType(from_platform)));
+ EXPECT_TRUE(gt::IsEqual(png_bytes, from_platform.AsBitmap()));
+}
+
+TEST_F(ImageTest, PNGEncodeFromPlatformDecodeToSkia) {
+ // Force the conversion sequence platform_type to png to skia.
+ gfx::Image from_platform(gt::CreatePlatformImage());
+ scoped_refptr<base::RefCountedMemory> png_bytes =
+ from_platform.As1xPNGBytes();
+ std::vector<gfx::ImagePNGRep> image_png_reps;
+ image_png_reps.push_back(gfx::ImagePNGRep(png_bytes, ui::SCALE_FACTOR_100P));
+ gfx::Image from_png(image_png_reps);
+
+ EXPECT_TRUE(gt::IsEqual(from_platform.AsBitmap(), from_png.AsBitmap()));
+}
+
+TEST_F(ImageTest, PNGDecodeToSkiaFailure) {
+ scoped_refptr<base::RefCountedBytes> invalid_bytes(
+ new base::RefCountedBytes());
+ invalid_bytes->data().push_back('0');
+ std::vector<gfx::ImagePNGRep> image_png_reps;
+ image_png_reps.push_back(gfx::ImagePNGRep(
+ invalid_bytes, ui::SCALE_FACTOR_100P));
+ gfx::Image image(image_png_reps);
+ gt::CheckImageIndicatesPNGDecodeFailure(image);
+}
+
+TEST_F(ImageTest, PNGDecodeToPlatformFailure) {
+ scoped_refptr<base::RefCountedBytes> invalid_bytes(
+ new base::RefCountedBytes());
+ invalid_bytes->data().push_back('0');
+ std::vector<gfx::ImagePNGRep> image_png_reps;
+ image_png_reps.push_back(gfx::ImagePNGRep(
+ invalid_bytes, ui::SCALE_FACTOR_100P));
+ gfx::Image from_png(image_png_reps);
+ gfx::Image from_platform(gt::CopyPlatformType(from_png));
+ gt::CheckImageIndicatesPNGDecodeFailure(from_platform);
+}
+
+TEST_F(ImageTest, SkiaToPlatform) {
+ gfx::Image image(gt::CreateImageSkia(25, 25));
+ EXPECT_EQ(25, image.Width());
+ EXPECT_EQ(25, image.Height());
+ const size_t kRepCount = kUsesSkiaNatively ? 1U : 2U;
+
+ EXPECT_TRUE(image.HasRepresentation(gfx::Image::kImageRepSkia));
+ if (!kUsesSkiaNatively)
+ EXPECT_FALSE(image.HasRepresentation(gt::GetPlatformRepresentationType()));
+
+ EXPECT_TRUE(gt::IsPlatformImageValid(gt::ToPlatformType(image)));
+ EXPECT_EQ(kRepCount, image.RepresentationCount());
+
+ const SkBitmap* bitmap = image.ToSkBitmap();
+ EXPECT_FALSE(bitmap->isNull());
+ EXPECT_EQ(kRepCount, image.RepresentationCount());
+
+ EXPECT_TRUE(image.HasRepresentation(gfx::Image::kImageRepSkia));
+ EXPECT_TRUE(image.HasRepresentation(gt::GetPlatformRepresentationType()));
+ EXPECT_EQ(25, image.Width());
+ EXPECT_EQ(25, image.Height());
+}
+
+TEST_F(ImageTest, PlatformToSkia) {
+ gfx::Image image(gt::CreatePlatformImage());
+ EXPECT_EQ(25, image.Width());
+ EXPECT_EQ(25, image.Height());
+ const size_t kRepCount = kUsesSkiaNatively ? 1U : 2U;
+
+ EXPECT_TRUE(image.HasRepresentation(gt::GetPlatformRepresentationType()));
+ if (!kUsesSkiaNatively)
+ EXPECT_FALSE(image.HasRepresentation(gfx::Image::kImageRepSkia));
+
+ const SkBitmap* bitmap = image.ToSkBitmap();
+ EXPECT_TRUE(bitmap);
+ EXPECT_FALSE(bitmap->isNull());
+ EXPECT_EQ(kRepCount, image.RepresentationCount());
+
+ EXPECT_TRUE(gt::IsPlatformImageValid(gt::ToPlatformType(image)));
+ EXPECT_EQ(kRepCount, image.RepresentationCount());
+
+ EXPECT_TRUE(image.HasRepresentation(gfx::Image::kImageRepSkia));
+ EXPECT_EQ(25, image.Width());
+ EXPECT_EQ(25, image.Height());
+}
+
+TEST_F(ImageTest, PlatformToPlatform) {
+ gfx::Image image(gt::CreatePlatformImage());
+ EXPECT_EQ(25, image.Width());
+ EXPECT_EQ(25, image.Height());
+ EXPECT_TRUE(gt::IsPlatformImageValid(gt::ToPlatformType(image)));
+ EXPECT_EQ(1U, image.RepresentationCount());
+
+ // Make sure double conversion doesn't happen.
+ EXPECT_TRUE(gt::IsPlatformImageValid(gt::ToPlatformType(image)));
+ EXPECT_EQ(1U, image.RepresentationCount());
+
+ EXPECT_TRUE(image.HasRepresentation(gt::GetPlatformRepresentationType()));
+ if (!kUsesSkiaNatively)
+ EXPECT_FALSE(image.HasRepresentation(gfx::Image::kImageRepSkia));
+ EXPECT_EQ(25, image.Width());
+ EXPECT_EQ(25, image.Height());
+}
+
+TEST_F(ImageTest, PlatformToSkiaToCopy) {
+ const gfx::ImageSkia* image_skia = NULL;
+ {
+ gfx::Image image(gt::CreatePlatformImage());
+ image_skia = image.CopyImageSkia();
+ }
+ EXPECT_TRUE(image_skia);
+ EXPECT_FALSE(image_skia->isNull());
+ delete image_skia;
+
+ const SkBitmap* bitmap = NULL;
+ {
+ gfx::Image image(gt::CreatePlatformImage());
+ bitmap = image.CopySkBitmap();
+ }
+
+ EXPECT_TRUE(bitmap);
+ EXPECT_FALSE(bitmap->isNull());
+ delete bitmap;
+}
+
+#if defined(TOOLKIT_GTK)
+TEST_F(ImageTest, SkiaToGdkCopy) {
+ GdkPixbuf* pixbuf;
+
+ {
+ gfx::Image image(gt::CreateImageSkia(25, 25));
+ pixbuf = image.CopyGdkPixbuf();
+ }
+
+ EXPECT_TRUE(pixbuf);
+ g_object_unref(pixbuf);
+}
+
+TEST_F(ImageTest, SkiaToCairoCreatesGdk) {
+ gfx::Image image(gt::CreateImageSkia(25, 25));
+ EXPECT_FALSE(image.HasRepresentation(gfx::Image::kImageRepGdk));
+ EXPECT_TRUE(image.ToCairo());
+ EXPECT_TRUE(image.HasRepresentation(gfx::Image::kImageRepGdk));
+}
+#endif
+
+#if defined(OS_IOS)
+TEST_F(ImageTest, SkiaToCocoaTouchCopy) {
+ UIImage* ui_image;
+
+ {
+ gfx::Image image(gt::CreateImageSkia(25, 25));
+ ui_image = image.CopyUIImage();
+ }
+
+ EXPECT_TRUE(ui_image);
+ base::mac::NSObjectRelease(ui_image);
+}
+#elif defined(OS_MACOSX)
+TEST_F(ImageTest, SkiaToCocoaCopy) {
+ NSImage* ns_image;
+
+ {
+ gfx::Image image(gt::CreateImageSkia(25, 25));
+ ns_image = image.CopyNSImage();
+ }
+
+ EXPECT_TRUE(ns_image);
+ base::mac::NSObjectRelease(ns_image);
+}
+#endif
+
+TEST_F(ImageTest, CheckSkiaColor) {
+ gfx::Image image(gt::CreatePlatformImage());
+
+ const SkBitmap* bitmap = image.ToSkBitmap();
+ SkAutoLockPixels auto_lock(*bitmap);
+ gt::CheckColors(bitmap->getColor(10, 10), SK_ColorGREEN);
+}
+
+TEST_F(ImageTest, SkBitmapConversionPreservesOrientation) {
+ const int width = 50;
+ const int height = 50;
+ SkBitmap bitmap;
+ bitmap.setConfig(SkBitmap::kARGB_8888_Config, width, height);
+ bitmap.allocPixels();
+ bitmap.eraseRGB(0, 255, 0);
+
+ // Paint the upper half of the image in red (lower half is in green).
+ SkCanvas canvas(bitmap);
+ SkPaint red;
+ red.setColor(SK_ColorRED);
+ canvas.drawRect(SkRect::MakeWH(width, height / 2), red);
+ {
+ SCOPED_TRACE("Checking color of the initial SkBitmap");
+ gt::CheckColors(bitmap.getColor(10, 10), SK_ColorRED);
+ gt::CheckColors(bitmap.getColor(10, 40), SK_ColorGREEN);
+ }
+
+ // Convert from SkBitmap to a platform representation, then check the upper
+ // half of the platform image to make sure it is red, not green.
+ gfx::Image from_skbitmap = gfx::Image::CreateFrom1xBitmap(bitmap);
+ {
+ SCOPED_TRACE("Checking color of the platform image");
+ gt::CheckColors(
+ gt::GetPlatformImageColor(gt::ToPlatformType(from_skbitmap), 10, 10),
+ SK_ColorRED);
+ gt::CheckColors(
+ gt::GetPlatformImageColor(gt::ToPlatformType(from_skbitmap), 10, 40),
+ SK_ColorGREEN);
+ }
+
+ // Force a conversion back to SkBitmap and check that the upper half is red.
+ gfx::Image from_platform(gt::CopyPlatformType(from_skbitmap));
+ const SkBitmap* bitmap2 = from_platform.ToSkBitmap();
+ SkAutoLockPixels auto_lock(*bitmap2);
+ {
+ SCOPED_TRACE("Checking color after conversion back to SkBitmap");
+ gt::CheckColors(bitmap2->getColor(10, 10), SK_ColorRED);
+ gt::CheckColors(bitmap2->getColor(10, 40), SK_ColorGREEN);
+ }
+}
+
+TEST_F(ImageTest, SkBitmapConversionPreservesTransparency) {
+ const int width = 50;
+ const int height = 50;
+ SkBitmap bitmap;
+ bitmap.setConfig(SkBitmap::kARGB_8888_Config, width, height);
+ bitmap.allocPixels();
+ bitmap.setIsOpaque(false);
+ bitmap.eraseARGB(0, 0, 255, 0);
+
+ // Paint the upper half of the image in red (lower half is transparent).
+ SkCanvas canvas(bitmap);
+ SkPaint red;
+ red.setColor(SK_ColorRED);
+ canvas.drawRect(SkRect::MakeWH(width, height / 2), red);
+ {
+ SCOPED_TRACE("Checking color of the initial SkBitmap");
+ gt::CheckColors(bitmap.getColor(10, 10), SK_ColorRED);
+ gt::CheckIsTransparent(bitmap.getColor(10, 40));
+ }
+
+ // Convert from SkBitmap to a platform representation, then check the upper
+ // half of the platform image to make sure it is red, not green.
+ gfx::Image from_skbitmap = gfx::Image::CreateFrom1xBitmap(bitmap);
+ {
+ SCOPED_TRACE("Checking color of the platform image");
+ gt::CheckColors(
+ gt::GetPlatformImageColor(gt::ToPlatformType(from_skbitmap), 10, 10),
+ SK_ColorRED);
+ gt::CheckIsTransparent(
+ gt::GetPlatformImageColor(gt::ToPlatformType(from_skbitmap), 10, 40));
+ }
+
+ // Force a conversion back to SkBitmap and check that the upper half is red.
+ gfx::Image from_platform(gt::CopyPlatformType(from_skbitmap));
+ const SkBitmap* bitmap2 = from_platform.ToSkBitmap();
+ SkAutoLockPixels auto_lock(*bitmap2);
+ {
+ SCOPED_TRACE("Checking color after conversion back to SkBitmap");
+ gt::CheckColors(bitmap2->getColor(10, 10), SK_ColorRED);
+ gt::CheckIsTransparent(bitmap.getColor(10, 40));
+ }
+}
+
+TEST_F(ImageTest, SwapRepresentations) {
+ const size_t kRepCount = kUsesSkiaNatively ? 1U : 2U;
+
+ gfx::Image image1(gt::CreateImageSkia(25, 25));
+ const gfx::ImageSkia* image_skia1 = image1.ToImageSkia();
+ EXPECT_EQ(1U, image1.RepresentationCount());
+
+ gfx::Image image2(gt::CreatePlatformImage());
+ const gfx::ImageSkia* image_skia2 = image2.ToImageSkia();
+ gt::PlatformImage platform_image = gt::ToPlatformType(image2);
+ EXPECT_EQ(kRepCount, image2.RepresentationCount());
+
+ image1.SwapRepresentations(&image2);
+
+ EXPECT_EQ(image_skia2, image1.ToImageSkia());
+ EXPECT_TRUE(gt::PlatformImagesEqual(platform_image,
+ gt::ToPlatformType(image1)));
+ EXPECT_EQ(image_skia1, image2.ToImageSkia());
+ EXPECT_EQ(kRepCount, image1.RepresentationCount());
+ EXPECT_EQ(1U, image2.RepresentationCount());
+}
+
+TEST_F(ImageTest, Copy) {
+ const size_t kRepCount = kUsesSkiaNatively ? 1U : 2U;
+
+ gfx::Image image1(gt::CreateImageSkia(25, 25));
+ EXPECT_EQ(25, image1.Width());
+ EXPECT_EQ(25, image1.Height());
+ gfx::Image image2(image1);
+ EXPECT_EQ(25, image2.Width());
+ EXPECT_EQ(25, image2.Height());
+
+ EXPECT_EQ(1U, image1.RepresentationCount());
+ EXPECT_EQ(1U, image2.RepresentationCount());
+ EXPECT_EQ(image1.ToImageSkia(), image2.ToImageSkia());
+
+ EXPECT_TRUE(gt::IsPlatformImageValid(gt::ToPlatformType(image2)));
+ EXPECT_EQ(kRepCount, image2.RepresentationCount());
+ EXPECT_EQ(kRepCount, image1.RepresentationCount());
+}
+
+TEST_F(ImageTest, Assign) {
+ gfx::Image image1(gt::CreatePlatformImage());
+ EXPECT_EQ(25, image1.Width());
+ EXPECT_EQ(25, image1.Height());
+ // Assignment must be on a separate line to the declaration in order to test
+ // assignment operator (instead of copy constructor).
+ gfx::Image image2;
+ image2 = image1;
+ EXPECT_EQ(25, image2.Width());
+ EXPECT_EQ(25, image2.Height());
+
+ EXPECT_EQ(1U, image1.RepresentationCount());
+ EXPECT_EQ(1U, image2.RepresentationCount());
+ EXPECT_EQ(image1.ToSkBitmap(), image2.ToSkBitmap());
+}
+
+TEST_F(ImageTest, MultiResolutionImageSkia) {
+ const int kWidth1x = 10;
+ const int kHeight1x = 12;
+ const int kWidth2x = 20;
+ const int kHeight2x = 24;
+
+ gfx::ImageSkia image_skia;
+ image_skia.AddRepresentation(gfx::ImageSkiaRep(
+ gt::CreateBitmap(kWidth1x, kHeight1x),
+ ui::SCALE_FACTOR_100P));
+ image_skia.AddRepresentation(gfx::ImageSkiaRep(
+ gt::CreateBitmap(kWidth2x, kHeight2x),
+ ui::SCALE_FACTOR_200P));
+
+ std::vector<ui::ScaleFactor> scale_factors;
+ scale_factors.push_back(ui::SCALE_FACTOR_100P);
+ scale_factors.push_back(ui::SCALE_FACTOR_200P);
+ EXPECT_TRUE(gt::ImageSkiaStructureMatches(image_skia, kWidth1x, kHeight1x,
+ scale_factors));
+
+ // Check that the image has a single representation.
+ gfx::Image image(image_skia);
+ EXPECT_EQ(1u, image.RepresentationCount());
+ EXPECT_EQ(kWidth1x, image.Width());
+ EXPECT_EQ(kHeight1x, image.Height());
+}
+
+TEST_F(ImageTest, RemoveFromMultiResolutionImageSkia) {
+ const int kWidth2x = 20;
+ const int kHeight2x = 24;
+
+ gfx::ImageSkia image_skia;
+
+ image_skia.AddRepresentation(gfx::ImageSkiaRep(
+ gt::CreateBitmap(kWidth2x, kHeight2x), ui::SCALE_FACTOR_200P));
+ EXPECT_EQ(1u, image_skia.image_reps().size());
+
+ image_skia.RemoveRepresentation(ui::SCALE_FACTOR_100P);
+ EXPECT_EQ(1u, image_skia.image_reps().size());
+
+ image_skia.RemoveRepresentation(ui::SCALE_FACTOR_200P);
+ EXPECT_EQ(0u, image_skia.image_reps().size());
+}
+
+// Tests that gfx::Image does indeed take ownership of the SkBitmap it is
+// passed.
+TEST_F(ImageTest, OwnershipTest) {
+ gfx::Image image;
+ {
+ SkBitmap bitmap(gt::CreateBitmap(10, 10));
+ EXPECT_TRUE(!bitmap.isNull());
+ image = gfx::Image(gfx::ImageSkia(
+ gfx::ImageSkiaRep(bitmap, ui::SCALE_FACTOR_100P)));
+ }
+ EXPECT_TRUE(!image.ToSkBitmap()->isNull());
+}
+
+// Integration tests with UI toolkit frameworks require linking against the
+// Views library and cannot be here (ui_unittests doesn't include it). They
+// instead live in /chrome/browser/ui/tests/ui_gfx_image_unittest.cc.
+
+} // namespace
diff --git a/chromium/ui/gfx/image/image_unittest_util.cc b/chromium/ui/gfx/image/image_unittest_util.cc
new file mode 100644
index 00000000000..ef59eb61636
--- /dev/null
+++ b/chromium/ui/gfx/image/image_unittest_util.cc
@@ -0,0 +1,287 @@
+// Copyright (c) 2012 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.
+
+// Because the unit tests for gfx::Image are spread across multiple
+// implementation files, this header contains the reusable components.
+
+#include "ui/gfx/image/image_unittest_util.h"
+
+#include <cmath>
+
+#include "base/memory/scoped_ptr.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/skia/include/core/SkBitmap.h"
+#include "ui/gfx/codec/png_codec.h"
+#include "ui/gfx/image/image_skia.h"
+
+#if defined(TOOLKIT_GTK)
+#include <gtk/gtk.h>
+#include "ui/gfx/gtk_util.h"
+#elif defined(OS_IOS)
+#include "base/mac/foundation_util.h"
+#include "base/mac/scoped_cftyperef.h"
+#include "skia/ext/skia_utils_ios.h"
+#elif defined(OS_MACOSX)
+#include "base/mac/mac_util.h"
+#include "skia/ext/skia_utils_mac.h"
+#endif
+
+namespace gfx {
+namespace test {
+
+namespace {
+
+bool ColorComponentsClose(SkColor component1, SkColor component2) {
+ int c1 = static_cast<int>(component1);
+ int c2 = static_cast<int>(component2);
+ return std::abs(c1 - c2) <= 40;
+}
+
+bool ColorsClose(SkColor color1, SkColor color2) {
+ // Be tolerant of floating point rounding and lossy color space conversions.
+ return ColorComponentsClose(SkColorGetR(color1), SkColorGetR(color2)) &&
+ ColorComponentsClose(SkColorGetG(color1), SkColorGetG(color2)) &&
+ ColorComponentsClose(SkColorGetB(color1), SkColorGetB(color2)) &&
+ ColorComponentsClose(SkColorGetA(color1), SkColorGetA(color2));
+}
+
+} // namespace
+
+std::vector<ui::ScaleFactor> Get1xAnd2xScaleFactors() {
+ std::vector<ui::ScaleFactor> scale_factors;
+ scale_factors.push_back(ui::SCALE_FACTOR_100P);
+ scale_factors.push_back(ui::SCALE_FACTOR_200P);
+ return scale_factors;
+}
+
+const SkBitmap CreateBitmap(int width, int height) {
+ SkBitmap bitmap;
+ bitmap.setConfig(SkBitmap::kARGB_8888_Config, width, height);
+ bitmap.allocPixels();
+ bitmap.eraseRGB(0, 255, 0);
+ return bitmap;
+}
+
+gfx::ImageSkia CreateImageSkia(int width, int height) {
+ return gfx::ImageSkia::CreateFrom1xBitmap(CreateBitmap(width, height));
+}
+
+scoped_refptr<base::RefCountedMemory> CreatePNGBytes(int edge_size) {
+ SkBitmap bitmap = CreateBitmap(edge_size, edge_size);
+ scoped_refptr<base::RefCountedBytes> bytes(new base::RefCountedBytes());
+ PNGCodec::EncodeBGRASkBitmap(bitmap, false, &bytes->data());
+ return bytes;
+}
+
+gfx::Image CreateImage() {
+ return CreateImage(100, 50);
+}
+
+gfx::Image CreateImage(int width, int height) {
+ return gfx::Image::CreateFrom1xBitmap(CreateBitmap(width, height));
+}
+
+bool IsEqual(const gfx::Image& img1, const gfx::Image& img2) {
+ std::vector<gfx::ImageSkiaRep> img1_reps = img1.AsImageSkia().image_reps();
+ gfx::ImageSkia image_skia2 = img2.AsImageSkia();
+ if (image_skia2.image_reps().size() != img1_reps.size())
+ return false;
+
+ for (size_t i = 0; i < img1_reps.size(); ++i) {
+ ui::ScaleFactor scale_factor = img1_reps[i].scale_factor();
+ const gfx::ImageSkiaRep& image_rep2 = image_skia2.GetRepresentation(
+ scale_factor);
+ if (image_rep2.scale_factor() != scale_factor ||
+ !IsEqual(img1_reps[i].sk_bitmap(), image_rep2.sk_bitmap())) {
+ return false;
+ }
+ }
+ return true;
+}
+
+bool IsEqual(const SkBitmap& bmp1, const SkBitmap& bmp2) {
+ if (bmp1.isNull() && bmp2.isNull())
+ return true;
+
+ if (bmp1.width() != bmp2.width() ||
+ bmp1.height() != bmp2.height() ||
+ bmp1.config() != SkBitmap::kARGB_8888_Config ||
+ bmp2.config() != SkBitmap::kARGB_8888_Config) {
+ return false;
+ }
+
+ SkAutoLockPixels lock1(bmp1);
+ SkAutoLockPixels lock2(bmp2);
+ if (!bmp1.getPixels() || !bmp2.getPixels())
+ return false;
+
+ for (int y = 0; y < bmp1.height(); ++y) {
+ for (int x = 0; x < bmp1.width(); ++x) {
+ if (!ColorsClose(bmp1.getColor(x,y), bmp2.getColor(x,y)))
+ return false;
+ }
+ }
+
+ return true;
+}
+
+bool IsEqual(const scoped_refptr<base::RefCountedMemory>& bytes,
+ const SkBitmap& bitmap) {
+ SkBitmap decoded;
+ if (!bytes.get() ||
+ !PNGCodec::Decode(bytes->front(), bytes->size(), &decoded)) {
+ return bitmap.isNull();
+ }
+
+ return IsEqual(bitmap, decoded);
+}
+
+void CheckImageIndicatesPNGDecodeFailure(const gfx::Image& image) {
+ SkBitmap bitmap = image.AsBitmap();
+ EXPECT_FALSE(bitmap.isNull());
+ EXPECT_LE(16, bitmap.width());
+ EXPECT_LE(16, bitmap.height());
+ SkAutoLockPixels auto_lock(bitmap);
+ CheckColors(bitmap.getColor(10, 10), SK_ColorRED);
+}
+
+bool ImageSkiaStructureMatches(
+ const gfx::ImageSkia& image_skia,
+ int width,
+ int height,
+ const std::vector<ui::ScaleFactor>& scale_factors) {
+ if (image_skia.isNull() ||
+ image_skia.width() != width ||
+ image_skia.height() != height ||
+ image_skia.image_reps().size() != scale_factors.size()) {
+ return false;
+ }
+
+ for (size_t i = 0; i < scale_factors.size(); ++i) {
+ gfx::ImageSkiaRep image_rep =
+ image_skia.GetRepresentation(scale_factors[i]);
+ if (image_rep.is_null() ||
+ image_rep.scale_factor() != scale_factors[i])
+ return false;
+
+ float scale = ui::GetScaleFactorScale(scale_factors[i]);
+ if (image_rep.pixel_width() != static_cast<int>(width * scale) ||
+ image_rep.pixel_height() != static_cast<int>(height * scale)) {
+ return false;
+ }
+ }
+ return true;
+}
+
+bool IsEmpty(const gfx::Image& image) {
+ const SkBitmap& bmp = *image.ToSkBitmap();
+ return bmp.isNull() ||
+ (bmp.width() == 0 && bmp.height() == 0);
+}
+
+PlatformImage CreatePlatformImage() {
+ const SkBitmap bitmap(CreateBitmap(25, 25));
+#if defined(OS_IOS)
+ ui::ScaleFactor scale_factor = ui::GetMaxScaleFactor();
+ float scale = ui::GetScaleFactorScale(scale_factor);
+
+ base::ScopedCFTypeRef<CGColorSpaceRef> color_space(
+ CGColorSpaceCreateDeviceRGB());
+ UIImage* image =
+ gfx::SkBitmapToUIImageWithColorSpace(bitmap, scale, color_space);
+ base::mac::NSObjectRetain(image);
+ return image;
+#elif defined(OS_MACOSX)
+ NSImage* image = gfx::SkBitmapToNSImage(bitmap);
+ base::mac::NSObjectRetain(image);
+ return image;
+#elif defined(TOOLKIT_GTK)
+ return gfx::GdkPixbufFromSkBitmap(bitmap);
+#else
+ return gfx::ImageSkia::CreateFrom1xBitmap(bitmap);
+#endif
+}
+
+gfx::Image::RepresentationType GetPlatformRepresentationType() {
+#if defined(OS_IOS)
+ return gfx::Image::kImageRepCocoaTouch;
+#elif defined(OS_MACOSX)
+ return gfx::Image::kImageRepCocoa;
+#elif defined(TOOLKIT_GTK)
+ return gfx::Image::kImageRepGdk;
+#else
+ return gfx::Image::kImageRepSkia;
+#endif
+}
+
+PlatformImage ToPlatformType(const gfx::Image& image) {
+#if defined(OS_IOS)
+ return image.ToUIImage();
+#elif defined(OS_MACOSX)
+ return image.ToNSImage();
+#elif defined(TOOLKIT_GTK)
+ return image.ToGdkPixbuf();
+#else
+ return image.AsImageSkia();
+#endif
+}
+
+PlatformImage CopyPlatformType(const gfx::Image& image) {
+#if defined(OS_IOS)
+ return image.CopyUIImage();
+#elif defined(OS_MACOSX)
+ return image.CopyNSImage();
+#elif defined(TOOLKIT_GTK)
+ return image.CopyGdkPixbuf();
+#else
+ return image.AsImageSkia();
+#endif
+}
+
+#if defined(OS_MACOSX)
+// Defined in image_unittest_util_mac.mm.
+#elif defined(TOOLKIT_GTK)
+SkColor GetPlatformImageColor(PlatformImage image, int x, int y) {
+ int n_channels = gdk_pixbuf_get_n_channels(image);
+ int rowstride = gdk_pixbuf_get_rowstride(image);
+ guchar* gdk_pixels = gdk_pixbuf_get_pixels(image);
+
+ guchar* pixel = gdk_pixels + (y * rowstride) + (x * n_channels);
+ guchar alpha = gdk_pixbuf_get_has_alpha(image) ? pixel[3] : 255;
+ return SkColorSetARGB(alpha, pixel[0], pixel[1], pixel[2]);
+}
+#else
+SkColor GetPlatformImageColor(PlatformImage image, int x, int y) {
+ SkBitmap bitmap = *image.bitmap();
+ SkAutoLockPixels auto_lock(bitmap);
+ return bitmap.getColor(x, y);
+}
+#endif
+
+void CheckColors(SkColor color1, SkColor color2) {
+ EXPECT_TRUE(ColorsClose(color1, color2));
+}
+
+void CheckIsTransparent(SkColor color) {
+ EXPECT_LT(SkColorGetA(color) / 255.0, 0.05);
+}
+
+bool IsPlatformImageValid(PlatformImage image) {
+#if defined(OS_MACOSX) || defined(TOOLKIT_GTK)
+ return image != NULL;
+#else
+ return !image.isNull();
+#endif
+}
+
+bool PlatformImagesEqual(PlatformImage image1, PlatformImage image2) {
+#if defined(OS_MACOSX) || defined(TOOLKIT_GTK)
+ return image1 == image2;
+#else
+ return image1.BackedBySameObjectAs(image2);
+#endif
+}
+
+} // namespace test
+} // namespace gfx
diff --git a/chromium/ui/gfx/image/image_unittest_util.h b/chromium/ui/gfx/image/image_unittest_util.h
new file mode 100644
index 00000000000..a2293c8688b
--- /dev/null
+++ b/chromium/ui/gfx/image/image_unittest_util.h
@@ -0,0 +1,90 @@
+// Copyright (c) 2012 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.
+
+// Because the unit tests for gfx::Image are spread across multiple
+// implementation files, this header contains the reusable components.
+
+#ifndef UI_GFX_IMAGE_IMAGE_UNITTEST_UTIL_H_
+#define UI_GFX_IMAGE_IMAGE_UNITTEST_UTIL_H_
+
+#include "ui/base/layout.h"
+#include "ui/gfx/image/image.h"
+#include "third_party/skia/include/core/SkColor.h"
+
+namespace gfx {
+namespace test {
+
+#if defined(OS_IOS)
+typedef UIImage* PlatformImage;
+#elif defined(OS_MACOSX)
+typedef NSImage* PlatformImage;
+#elif defined(TOOLKIT_GTK)
+typedef GdkPixbuf* PlatformImage;
+#else
+typedef gfx::ImageSkia PlatformImage;
+#endif
+
+std::vector<ui::ScaleFactor> Get1xAnd2xScaleFactors();
+
+// Create a bitmap of |width|x|height|.
+const SkBitmap CreateBitmap(int width, int height);
+
+// Creates an ImageSkia of |width|x|height| DIP with bitmap data for an
+// arbitrary scale factor.
+gfx::ImageSkia CreateImageSkia(int width, int height);
+
+// Returns PNG encoded bytes for a bitmap of |edge_size|x|edge_size|.
+scoped_refptr<base::RefCountedMemory> CreatePNGBytes(int edge_size);
+
+// TODO(rohitrao): Remove the no-argument version of CreateImage().
+gfx::Image CreateImage();
+gfx::Image CreateImage(int width, int height);
+
+// Returns true if the images are equal. Converts the images to ImageSkia to
+// compare them.
+bool IsEqual(const gfx::Image& image1, const gfx::Image& image2);
+
+bool IsEqual(const SkBitmap& bitmap1, const SkBitmap& bitmap2);
+
+bool IsEqual(const scoped_refptr<base::RefCountedMemory>& bytes,
+ const SkBitmap& bitmap);
+
+// An image which was not successfully decoded to PNG should be a red bitmap.
+// Fails if the bitmap is not red.
+void CheckImageIndicatesPNGDecodeFailure(const gfx::Image& image);
+
+// Returns true if the structure of |image_skia| matches the structure
+// described by |width|, |height|, and |scale_factors|.
+// The structure matches if:
+// - |image_skia| is non null.
+// - |image_skia| has ImageSkiaReps of |scale_factors|.
+// - Each of the ImageSkiaReps has a pixel size of |image_skia|.size() *
+// scale_factor.
+bool ImageSkiaStructureMatches(
+ const gfx::ImageSkia& image_skia,
+ int width,
+ int height,
+ const std::vector<ui::ScaleFactor>& scale_factors);
+
+bool IsEmpty(const gfx::Image& image);
+
+PlatformImage CreatePlatformImage();
+
+gfx::Image::RepresentationType GetPlatformRepresentationType();
+
+PlatformImage ToPlatformType(const gfx::Image& image);
+PlatformImage CopyPlatformType(const gfx::Image& image);
+
+SkColor GetPlatformImageColor(PlatformImage image, int x, int y);
+void CheckColors(SkColor color1, SkColor color2);
+void CheckIsTransparent(SkColor color);
+
+bool IsPlatformImageValid(PlatformImage image);
+
+bool PlatformImagesEqual(PlatformImage image1, PlatformImage image2);
+
+} // namespace test
+} // namespace gfx
+
+#endif // UI_GFX_IMAGE_IMAGE_UNITTEST_UTIL_H_
diff --git a/chromium/ui/gfx/image/image_unittest_util_ios.mm b/chromium/ui/gfx/image/image_unittest_util_ios.mm
new file mode 100644
index 00000000000..56266178cf0
--- /dev/null
+++ b/chromium/ui/gfx/image/image_unittest_util_ios.mm
@@ -0,0 +1,41 @@
+// Copyright 2012 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 <CoreGraphics/CoreGraphics.h>
+#import <UIKit/UIKit.h>
+
+#include "base/mac/scoped_cftyperef.h"
+#include "skia/ext/skia_utils_ios.h"
+#include "ui/gfx/image/image_unittest_util.h"
+
+namespace gfx {
+namespace test {
+
+SkColor GetPlatformImageColor(PlatformImage image, int x, int y) {
+ // Start by extracting the target pixel into a 1x1 CGImage.
+ base::ScopedCFTypeRef<CGImageRef> pixel_image(
+ CGImageCreateWithImageInRect(image.CGImage, CGRectMake(x, y, 1, 1)));
+
+ // Draw that pixel into a 1x1 bitmap context.
+ base::ScopedCFTypeRef<CGColorSpaceRef> color_space(
+ CGColorSpaceCreateDeviceRGB());
+ base::ScopedCFTypeRef<CGContextRef> bitmap_context(CGBitmapContextCreate(
+ /*data=*/ NULL,
+ /*width=*/ 1,
+ /*height=*/ 1,
+ /*bitsPerComponent=*/ 8,
+ /*bytesPerRow=*/ 4,
+ color_space,
+ kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Host));
+ CGContextDrawImage(bitmap_context, CGRectMake(0, 0, 1, 1), pixel_image);
+
+ // The CGBitmapContext has the same memory layout as SkColor, so we can just
+ // read an SkColor straight out of the context.
+ SkColor* data =
+ reinterpret_cast<SkColor*>(CGBitmapContextGetData(bitmap_context));
+ return *data;
+}
+
+} // namespace test
+} // namespace gfx
diff --git a/chromium/ui/gfx/image/image_unittest_util_mac.mm b/chromium/ui/gfx/image/image_unittest_util_mac.mm
new file mode 100644
index 00000000000..4f4371715f1
--- /dev/null
+++ b/chromium/ui/gfx/image/image_unittest_util_mac.mm
@@ -0,0 +1,24 @@
+// Copyright (c) 2012 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 <AppKit/AppKit.h>
+
+#include "skia/ext/skia_utils_mac.h"
+#include "ui/gfx/image/image_unittest_util.h"
+
+namespace gfx {
+namespace test {
+
+SkColor GetPlatformImageColor(PlatformImage image, int x, int y) {
+ // AppKit's coordinate system is flipped.
+ y = [image size].height - y;
+
+ [image lockFocus];
+ NSColor* color = NSReadPixel(NSMakePoint(x, y));
+ [image unlockFocus];
+ return NSDeviceColorToSkColor(color);
+}
+
+} // namespace test
+} // namespace gfx
diff --git a/chromium/ui/gfx/image/image_util.cc b/chromium/ui/gfx/image/image_util.cc
new file mode 100644
index 00000000000..e230a0b1264
--- /dev/null
+++ b/chromium/ui/gfx/image/image_util.cc
@@ -0,0 +1,48 @@
+// Copyright (c) 2012 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 "ui/gfx/image/image_util.h"
+
+#include "base/memory/scoped_ptr.h"
+#include "third_party/skia/include/core/SkBitmap.h"
+#include "ui/gfx/codec/jpeg_codec.h"
+#include "ui/gfx/image/image.h"
+#include "ui/gfx/image/image_skia.h"
+
+namespace gfx {
+
+// The iOS implementations of the JPEG functions are in image_util_ios.mm.
+#if !defined(OS_IOS)
+Image ImageFrom1xJPEGEncodedData(const unsigned char* input,
+ size_t input_size) {
+ scoped_ptr<SkBitmap> bitmap(gfx::JPEGCodec::Decode(input, input_size));
+ if (bitmap.get())
+ return Image::CreateFrom1xBitmap(*bitmap);
+
+ return Image();
+}
+
+bool JPEG1xEncodedDataFromImage(const Image& image, int quality,
+ std::vector<unsigned char>* dst) {
+ const gfx::ImageSkiaRep& image_skia_rep =
+ image.AsImageSkia().GetRepresentation(ui::SCALE_FACTOR_100P);
+ if (image_skia_rep.scale_factor() != ui::SCALE_FACTOR_100P)
+ return false;
+
+ const SkBitmap& bitmap = image_skia_rep.sk_bitmap();
+ SkAutoLockPixels bitmap_lock(bitmap);
+
+ if (!bitmap.readyToDraw())
+ return false;
+
+ return gfx::JPEGCodec::Encode(
+ reinterpret_cast<unsigned char*>(bitmap.getAddr32(0, 0)),
+ gfx::JPEGCodec::FORMAT_SkBitmap, bitmap.width(),
+ bitmap.height(),
+ static_cast<int>(bitmap.rowBytes()), quality,
+ dst);
+}
+#endif // !defined(OS_IOS)
+
+}
diff --git a/chromium/ui/gfx/image/image_util.h b/chromium/ui/gfx/image/image_util.h
new file mode 100644
index 00000000000..9ada2fe74ff
--- /dev/null
+++ b/chromium/ui/gfx/image/image_util.h
@@ -0,0 +1,36 @@
+// Copyright (c) 2012 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_IMAGE_IMAGE_UTIL_H_
+#define UI_GFX_IMAGE_IMAGE_UTIL_H_
+
+#include <vector>
+
+#include "base/basictypes.h"
+#include "ui/base/ui_export.h"
+
+namespace gfx {
+class Image;
+}
+
+namespace gfx {
+
+// Creates an image from the given JPEG-encoded input. If there was an error
+// creating the image, returns an IsEmpty() Image.
+UI_EXPORT Image ImageFrom1xJPEGEncodedData(const unsigned char* input,
+ size_t input_size);
+
+// Fills the |dst| vector with JPEG-encoded bytes of the 1x representation of
+// the given image.
+// Returns true if the image has a 1x representation and the 1x representation
+// was encoded successfully.
+// |quality| determines the compression level, 0 == lowest, 100 == highest.
+// Returns true if the Image was encoded successfully.
+UI_EXPORT bool JPEG1xEncodedDataFromImage(const Image& image,
+ int quality,
+ std::vector<unsigned char>* dst);
+
+} // namespace gfx
+
+#endif // UI_GFX_IMAGE_IMAGE_UTIL_H_
diff --git a/chromium/ui/gfx/image/image_util_ios.mm b/chromium/ui/gfx/image/image_util_ios.mm
new file mode 100644
index 00000000000..737ee1b2ab0
--- /dev/null
+++ b/chromium/ui/gfx/image/image_util_ios.mm
@@ -0,0 +1,27 @@
+// Copyright (c) 2012 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 <UIKit/UIKit.h>
+
+#include "ui/gfx/image/image.h"
+#include "ui/gfx/image/image_util.h"
+
+#include "base/logging.h"
+
+namespace gfx {
+
+bool JPEG1xEncodedDataFromImage(const Image& image,
+ int quality,
+ std::vector<unsigned char>* dst) {
+ NSData* data = UIImageJPEGRepresentation(image.ToUIImage(), quality / 100.0);
+
+ if ([data length] == 0)
+ return false;
+
+ dst->resize([data length]);
+ [data getBytes:&dst->at(0) length:[data length]];
+ return true;
+}
+
+} // end namespace gfx
diff --git a/chromium/ui/gfx/image/image_util_unittest.cc b/chromium/ui/gfx/image/image_util_unittest.cc
new file mode 100644
index 00000000000..cd9d74898f6
--- /dev/null
+++ b/chromium/ui/gfx/image/image_util_unittest.cc
@@ -0,0 +1,25 @@
+// Copyright (c) 2012 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 "ui/gfx/image/image_util.h"
+
+#include <vector>
+
+#include "base/memory/scoped_ptr.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/skia/include/core/SkBitmap.h"
+#include "ui/gfx/image/image_unittest_util.h"
+
+TEST(ImageUtilTest, JPEGEncodeAndDecode) {
+ gfx::Image original = gfx::test::CreateImage(100, 100);
+
+ std::vector<unsigned char> encoded;
+ ASSERT_TRUE(gfx::JPEG1xEncodedDataFromImage(original, 80, &encoded));
+
+ gfx::Image decoded =
+ gfx::ImageFrom1xJPEGEncodedData(&encoded.front(), encoded.size());
+
+ // JPEG is lossy, so simply check that the image decoded successfully.
+ EXPECT_FALSE(decoded.IsEmpty());
+}
diff --git a/chromium/ui/gfx/insets.cc b/chromium/ui/gfx/insets.cc
new file mode 100644
index 00000000000..44a29916acf
--- /dev/null
+++ b/chromium/ui/gfx/insets.cc
@@ -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.
+
+#include "ui/gfx/insets.h"
+
+#if defined(TOOLKIT_GTK)
+#include <gtk/gtk.h>
+#endif
+
+#include "base/strings/stringprintf.h"
+
+namespace gfx {
+
+template class InsetsBase<Insets, int>;
+
+Insets::Insets() : InsetsBase<Insets, int>(0, 0, 0, 0) {}
+
+Insets::Insets(int top, int left, int bottom, int right)
+ : InsetsBase<Insets, int>(top, left, bottom, right) {}
+
+#if defined(TOOLKIT_GTK)
+Insets::Insets(const GtkBorder& border)
+ : InsetsBase<Insets, int>(border.top,
+ border.left,
+ border.bottom,
+ border.right) {
+}
+#endif
+
+Insets::~Insets() {}
+
+std::string Insets::ToString() const {
+ // Print members in the same order of the constructor parameters.
+ return base::StringPrintf("%d,%d,%d,%d", top(), left(), bottom(), right());
+}
+
+} // namespace gfx
diff --git a/chromium/ui/gfx/insets.h b/chromium/ui/gfx/insets.h
new file mode 100644
index 00000000000..f9ef4ffc314
--- /dev/null
+++ b/chromium/ui/gfx/insets.h
@@ -0,0 +1,52 @@
+// Copyright (c) 2012 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_
+
+#include <string>
+
+#include "build/build_config.h"
+#include "ui/base/ui_export.h"
+#include "ui/gfx/insets_base.h"
+
+#if defined(TOOLKIT_GTK)
+typedef struct _GtkBorder GtkBorder;
+#endif
+
+namespace gfx {
+
+// An integer version of gfx::Insets.
+class UI_EXPORT Insets : public InsetsBase<Insets, int> {
+ public:
+ Insets();
+ Insets(int top, int left, int bottom, int right);
+#if defined(TOOLKIT_GTK)
+ explicit Insets(const GtkBorder& border);
+#endif
+
+ ~Insets();
+
+ Insets Scale(float scale) const {
+ return Scale(scale, scale);
+ }
+
+ Insets Scale(float x_scale, float y_scale) const {
+ return Insets(static_cast<int>(top() * y_scale),
+ static_cast<int>(left() * x_scale),
+ static_cast<int>(bottom() * y_scale),
+ static_cast<int>(right() * x_scale));
+ }
+
+ // Returns a string representation of the insets.
+ std::string ToString() const;
+};
+
+#if !defined(COMPILER_MSVC)
+extern template class InsetsBase<Insets, int>;
+#endif
+
+} // namespace gfx
+
+#endif // UI_GFX_INSETS_H_
diff --git a/chromium/ui/gfx/insets_base.h b/chromium/ui/gfx/insets_base.h
new file mode 100644
index 00000000000..bf0b48309bc
--- /dev/null
+++ b/chromium/ui/gfx/insets_base.h
@@ -0,0 +1,80 @@
+// Copyright 2012 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_BASE_H_
+#define UI_GFX_INSETS_BASE_H_
+
+#include "ui/base/ui_export.h"
+
+namespace gfx {
+
+// An insets represents the borders of a container (the space the container must
+// leave at each of its edges).
+template<typename Class, typename Type>
+class UI_EXPORT InsetsBase {
+ public:
+ Type top() const { return top_; }
+ Type left() const { return left_; }
+ Type bottom() const { return bottom_; }
+ Type right() const { return right_; }
+
+ // Returns the total width taken up by the insets, which is the sum of the
+ // left and right insets.
+ Type width() const { return left_ + right_; }
+
+ // Returns the total height taken up by the insets, which is the sum of the
+ // top and bottom insets.
+ Type height() const { return top_ + bottom_; }
+
+ // Returns true if the insets are empty.
+ bool empty() const { return width() == 0 && height() == 0; }
+
+ void Set(Type top, Type left, Type bottom, Type right) {
+ top_ = top;
+ left_ = left;
+ bottom_ = bottom;
+ right_ = right;
+ }
+
+ bool operator==(const Class& insets) const {
+ return top_ == insets.top_ && left_ == insets.left_ &&
+ bottom_ == insets.bottom_ && right_ == insets.right_;
+ }
+
+ bool operator!=(const Class& insets) const {
+ return !(*this == insets);
+ }
+
+ void operator+=(const Class& insets) {
+ top_ += insets.top_;
+ left_ += insets.left_;
+ bottom_ += insets.bottom_;
+ right_ += insets.right_;
+ }
+
+ Class operator-() const {
+ return Class(-top_, -left_, -bottom_, -right_);
+ }
+
+ protected:
+ InsetsBase(Type top, Type left, Type bottom, Type right)
+ : top_(top),
+ left_(left),
+ bottom_(bottom),
+ right_(right) {}
+
+ // Destructor is intentionally made non virtual and protected.
+ // Do not make this public.
+ ~InsetsBase() {}
+
+ private:
+ Type top_;
+ Type left_;
+ Type bottom_;
+ Type right_;
+};
+
+} // namespace gfx
+
+#endif // UI_GFX_INSETS_BASE_H_
diff --git a/chromium/ui/gfx/insets_f.cc b/chromium/ui/gfx/insets_f.cc
new file mode 100644
index 00000000000..f790b3c4187
--- /dev/null
+++ b/chromium/ui/gfx/insets_f.cc
@@ -0,0 +1,25 @@
+// Copyright (c) 2012 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 "ui/gfx/insets_f.h"
+
+#include "base/strings/stringprintf.h"
+
+namespace gfx {
+
+template class InsetsBase<InsetsF, float>;
+
+InsetsF::InsetsF() : InsetsBase<InsetsF, float>(0, 0, 0, 0) {}
+
+InsetsF::InsetsF(float top, float left, float bottom, float right)
+ : InsetsBase<InsetsF, float>(top, left, bottom, right) {}
+
+InsetsF::~InsetsF() {}
+
+std::string InsetsF::ToString() const {
+ // Print members in the same order of the constructor parameters.
+ return base::StringPrintf("%f,%f,%f,%f", top(), left(), bottom(), right());
+}
+
+} // namespace gfx
diff --git a/chromium/ui/gfx/insets_f.h b/chromium/ui/gfx/insets_f.h
new file mode 100644
index 00000000000..d447d941682
--- /dev/null
+++ b/chromium/ui/gfx/insets_f.h
@@ -0,0 +1,33 @@
+// Copyright (c) 2012 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_F_H_
+#define UI_GFX_INSETS_F_H_
+
+#include <string>
+
+#include "build/build_config.h"
+#include "ui/base/ui_export.h"
+#include "ui/gfx/insets_base.h"
+
+namespace gfx {
+
+// A floating versin of gfx::Insets.
+class UI_EXPORT InsetsF : public InsetsBase<InsetsF, float> {
+ public:
+ InsetsF();
+ InsetsF(float top, float left, float bottom, float right);
+ ~InsetsF();
+
+ // Returns a string representation of the insets.
+ std::string ToString() const;
+};
+
+#if !defined(COMPILER_MSVC)
+extern template class InsetsBase<InsetsF, float>;
+#endif
+
+} // namespace gfx
+
+#endif // UI_GFX_INSETS_F_H_
diff --git a/chromium/ui/gfx/insets_unittest.cc b/chromium/ui/gfx/insets_unittest.cc
new file mode 100644
index 00000000000..563f20fca23
--- /dev/null
+++ b/chromium/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 "ui/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/chromium/ui/gfx/interpolated_transform.cc b/chromium/ui/gfx/interpolated_transform.cc
new file mode 100644
index 00000000000..583c27f05b9
--- /dev/null
+++ b/chromium/ui/gfx/interpolated_transform.cc
@@ -0,0 +1,373 @@
+// Copyright (c) 2012 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 "ui/gfx/interpolated_transform.h"
+
+#include <cmath>
+
+#ifndef M_PI
+#define M_PI 3.14159265358979323846
+#endif
+
+#include "base/logging.h"
+#include "ui/base/animation/tween.h"
+
+namespace {
+
+static const double EPSILON = 1e-6;
+
+bool IsMultipleOfNinetyDegrees(double degrees) {
+ double remainder = fabs(fmod(degrees, 90.0));
+ return remainder < EPSILON || 90.0 - remainder < EPSILON;
+}
+
+bool IsApproximatelyZero(double value) {
+ return fabs(value) < EPSILON;
+}
+
+// Returns false if |degrees| is not a multiple of ninety degrees or if
+// |rotation| is NULL. It does not affect |rotation| in this case. Otherwise
+// *rotation is set to be the appropriate sanitized rotation matrix. That is,
+// the rotation matrix corresponding to |degrees| which has entries that are all
+// either 0, 1 or -1.
+bool MassageRotationIfMultipleOfNinetyDegrees(gfx::Transform* rotation,
+ float degrees) {
+ if (!IsMultipleOfNinetyDegrees(degrees) || !rotation)
+ return false;
+
+ gfx::Transform transform;
+ SkMatrix44& m = transform.matrix();
+ float degrees_by_ninety = degrees / 90.0f;
+
+ int n = static_cast<int>(degrees_by_ninety > 0
+ ? floor(degrees_by_ninety + 0.5f)
+ : ceil(degrees_by_ninety - 0.5f));
+
+ n %= 4;
+ if (n < 0)
+ n += 4;
+
+ // n should now be in the range [0, 3]
+ if (n == 1) {
+ m.set3x3( 0, 1, 0,
+ -1, 0, 0,
+ 0, 0, 1);
+ } else if (n == 2) {
+ m.set3x3(-1, 0, 0,
+ 0, -1, 0,
+ 0, 0, 1);
+ } else if (n == 3) {
+ m.set3x3( 0, -1, 0,
+ 1, 0, 0,
+ 0, 0, 1);
+ }
+
+ *rotation = transform;
+ return true;
+}
+
+} // namespace
+
+namespace ui {
+
+///////////////////////////////////////////////////////////////////////////////
+// InterpolatedTransform
+//
+
+InterpolatedTransform::InterpolatedTransform()
+ : start_time_(0.0f),
+ end_time_(1.0f),
+ reversed_(false) {
+}
+
+InterpolatedTransform::InterpolatedTransform(float start_time,
+ float end_time)
+ : start_time_(start_time),
+ end_time_(end_time),
+ reversed_(false) {
+}
+
+InterpolatedTransform::~InterpolatedTransform() {}
+
+gfx::Transform InterpolatedTransform::Interpolate(float t) const {
+ if (reversed_)
+ t = 1.0f - t;
+ gfx::Transform result = InterpolateButDoNotCompose(t);
+ if (child_.get()) {
+ result.ConcatTransform(child_->Interpolate(t));
+ }
+ return result;
+}
+
+void InterpolatedTransform::SetChild(InterpolatedTransform* child) {
+ child_.reset(child);
+}
+
+inline float InterpolatedTransform::ValueBetween(float time,
+ float start_value,
+ float end_value) const {
+ // can't handle NaN
+ DCHECK(time == time && start_time_ == start_time_ && end_time_ == end_time_);
+ if (time != time || start_time_ != start_time_ || end_time_ != end_time_)
+ return start_value;
+
+ // Ok if equal -- we'll get a step function. Note: if end_time_ ==
+ // start_time_ == x, then if none of the numbers are NaN, then it
+ // must be true that time < x or time >= x, so we will return early
+ // due to one of the following if statements.
+ DCHECK(end_time_ >= start_time_);
+
+ if (time < start_time_)
+ return start_value;
+
+ if (time >= end_time_)
+ return end_value;
+
+ float t = (time - start_time_) / (end_time_ - start_time_);
+ return static_cast<float>(Tween::ValueBetween(t, start_value, end_value));
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// InterpolatedRotation
+//
+
+InterpolatedRotation::InterpolatedRotation(float start_degrees,
+ float end_degrees)
+ : InterpolatedTransform(),
+ start_degrees_(start_degrees),
+ end_degrees_(end_degrees) {
+}
+
+InterpolatedRotation::InterpolatedRotation(float start_degrees,
+ float end_degrees,
+ float start_time,
+ float end_time)
+ : InterpolatedTransform(start_time, end_time),
+ start_degrees_(start_degrees),
+ end_degrees_(end_degrees) {
+}
+
+InterpolatedRotation::~InterpolatedRotation() {}
+
+gfx::Transform InterpolatedRotation::InterpolateButDoNotCompose(float t) const {
+ gfx::Transform result;
+ float interpolated_degrees = ValueBetween(t, start_degrees_, end_degrees_);
+ result.Rotate(interpolated_degrees);
+ if (t == 0.0f || t == 1.0f)
+ MassageRotationIfMultipleOfNinetyDegrees(&result, interpolated_degrees);
+ return result;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// InterpolatedAxisAngleRotation
+//
+
+InterpolatedAxisAngleRotation::InterpolatedAxisAngleRotation(
+ const gfx::Vector3dF& axis,
+ float start_degrees,
+ float end_degrees)
+ : InterpolatedTransform(),
+ axis_(axis),
+ start_degrees_(start_degrees),
+ end_degrees_(end_degrees) {
+}
+
+InterpolatedAxisAngleRotation::InterpolatedAxisAngleRotation(
+ const gfx::Vector3dF& axis,
+ float start_degrees,
+ float end_degrees,
+ float start_time,
+ float end_time)
+ : InterpolatedTransform(start_time, end_time),
+ axis_(axis),
+ start_degrees_(start_degrees),
+ end_degrees_(end_degrees) {
+}
+
+InterpolatedAxisAngleRotation::~InterpolatedAxisAngleRotation() {}
+
+gfx::Transform
+InterpolatedAxisAngleRotation::InterpolateButDoNotCompose(float t) const {
+ gfx::Transform result;
+ result.RotateAbout(axis_, ValueBetween(t, start_degrees_, end_degrees_));
+ return result;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// InterpolatedScale
+//
+
+InterpolatedScale::InterpolatedScale(float start_scale, float end_scale)
+ : InterpolatedTransform(),
+ start_scale_(gfx::Point3F(start_scale, start_scale, start_scale)),
+ end_scale_(gfx::Point3F(end_scale, end_scale, end_scale)) {
+}
+
+InterpolatedScale::InterpolatedScale(float start_scale, float end_scale,
+ float start_time, float end_time)
+ : InterpolatedTransform(start_time, end_time),
+ start_scale_(gfx::Point3F(start_scale, start_scale, start_scale)),
+ end_scale_(gfx::Point3F(end_scale, end_scale, end_scale)) {
+}
+
+InterpolatedScale::InterpolatedScale(const gfx::Point3F& start_scale,
+ const gfx::Point3F& end_scale)
+ : InterpolatedTransform(),
+ start_scale_(start_scale),
+ end_scale_(end_scale) {
+}
+
+InterpolatedScale::InterpolatedScale(const gfx::Point3F& start_scale,
+ const gfx::Point3F& end_scale,
+ float start_time,
+ float end_time)
+ : InterpolatedTransform(start_time, end_time),
+ start_scale_(start_scale),
+ end_scale_(end_scale) {
+}
+
+InterpolatedScale::~InterpolatedScale() {}
+
+gfx::Transform InterpolatedScale::InterpolateButDoNotCompose(float t) const {
+ gfx::Transform result;
+ float scale_x = ValueBetween(t, start_scale_.x(), end_scale_.x());
+ float scale_y = ValueBetween(t, start_scale_.y(), end_scale_.y());
+ // TODO(vollick) 3d xforms.
+ result.Scale(scale_x, scale_y);
+ return result;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// InterpolatedTranslation
+//
+
+InterpolatedTranslation::InterpolatedTranslation(const gfx::Point& start_pos,
+ const gfx::Point& end_pos)
+ : InterpolatedTransform(),
+ start_pos_(start_pos),
+ end_pos_(end_pos) {
+}
+
+InterpolatedTranslation::InterpolatedTranslation(const gfx::Point& start_pos,
+ const gfx::Point& end_pos,
+ float start_time,
+ float end_time)
+ : InterpolatedTransform(start_time, end_time),
+ start_pos_(start_pos),
+ end_pos_(end_pos) {
+}
+
+InterpolatedTranslation::~InterpolatedTranslation() {}
+
+gfx::Transform
+InterpolatedTranslation::InterpolateButDoNotCompose(float t) const {
+ gfx::Transform result;
+ // TODO(vollick) 3d xforms.
+ result.Translate(ValueBetween(t, start_pos_.x(), end_pos_.x()),
+ ValueBetween(t, start_pos_.y(), end_pos_.y()));
+ return result;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// InterpolatedConstantTransform
+//
+
+InterpolatedConstantTransform::InterpolatedConstantTransform(
+ const gfx::Transform& transform)
+ : InterpolatedTransform(),
+ transform_(transform) {
+}
+
+gfx::Transform
+InterpolatedConstantTransform::InterpolateButDoNotCompose(float t) const {
+ return transform_;
+}
+
+InterpolatedConstantTransform::~InterpolatedConstantTransform() {}
+
+///////////////////////////////////////////////////////////////////////////////
+// InterpolatedTransformAboutPivot
+//
+
+InterpolatedTransformAboutPivot::InterpolatedTransformAboutPivot(
+ const gfx::Point& pivot,
+ InterpolatedTransform* transform)
+ : InterpolatedTransform() {
+ Init(pivot, transform);
+}
+
+InterpolatedTransformAboutPivot::InterpolatedTransformAboutPivot(
+ const gfx::Point& pivot,
+ InterpolatedTransform* transform,
+ float start_time,
+ float end_time)
+ : InterpolatedTransform() {
+ Init(pivot, transform);
+}
+
+InterpolatedTransformAboutPivot::~InterpolatedTransformAboutPivot() {}
+
+gfx::Transform
+InterpolatedTransformAboutPivot::InterpolateButDoNotCompose(float t) const {
+ if (transform_.get()) {
+ return transform_->Interpolate(t);
+ }
+ return gfx::Transform();
+}
+
+void InterpolatedTransformAboutPivot::Init(const gfx::Point& pivot,
+ InterpolatedTransform* xform) {
+ gfx::Transform to_pivot;
+ gfx::Transform from_pivot;
+ to_pivot.Translate(-pivot.x(), -pivot.y());
+ from_pivot.Translate(pivot.x(), pivot.y());
+
+ scoped_ptr<InterpolatedTransform> pre_transform(
+ new InterpolatedConstantTransform(to_pivot));
+ scoped_ptr<InterpolatedTransform> post_transform(
+ new InterpolatedConstantTransform(from_pivot));
+
+ pre_transform->SetChild(xform);
+ xform->SetChild(post_transform.release());
+ transform_.reset(pre_transform.release());
+}
+
+InterpolatedMatrixTransform::InterpolatedMatrixTransform(
+ const gfx::Transform& start_transform,
+ const gfx::Transform& end_transform)
+ : InterpolatedTransform() {
+ Init(start_transform, end_transform);
+}
+
+InterpolatedMatrixTransform::InterpolatedMatrixTransform(
+ const gfx::Transform& start_transform,
+ const gfx::Transform& end_transform,
+ float start_time,
+ float end_time)
+ : InterpolatedTransform() {
+ Init(start_transform, end_transform);
+}
+
+InterpolatedMatrixTransform::~InterpolatedMatrixTransform() {}
+
+gfx::Transform
+InterpolatedMatrixTransform::InterpolateButDoNotCompose(float t) const {
+ gfx::DecomposedTransform blended;
+ bool success = gfx::BlendDecomposedTransforms(&blended,
+ end_decomp_,
+ start_decomp_,
+ t);
+ DCHECK(success);
+ return gfx::ComposeTransform(blended);
+}
+
+void InterpolatedMatrixTransform::Init(const gfx::Transform& start_transform,
+ const gfx::Transform& end_transform) {
+ bool success = gfx::DecomposeTransform(&start_decomp_, start_transform);
+ DCHECK(success);
+ success = gfx::DecomposeTransform(&end_decomp_, end_transform);
+ DCHECK(success);
+}
+
+} // namespace ui
diff --git a/chromium/ui/gfx/interpolated_transform.h b/chromium/ui/gfx/interpolated_transform.h
new file mode 100644
index 00000000000..474e3a9dc90
--- /dev/null
+++ b/chromium/ui/gfx/interpolated_transform.h
@@ -0,0 +1,264 @@
+// Copyright (c) 2012 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_INTERPOLATED_TRANSFORM_H_
+#define UI_GFX_INTERPOLATED_TRANSFORM_H_
+
+#include "base/basictypes.h"
+#include "base/memory/scoped_ptr.h"
+#include "ui/gfx/point.h"
+#include "ui/gfx/point3_f.h"
+#include "ui/gfx/transform.h"
+#include "ui/gfx/transform_util.h"
+#include "ui/gfx/vector3d_f.h"
+
+namespace ui {
+
+///////////////////////////////////////////////////////////////////////////////
+// class InterpolatedTransform
+//
+// Abstract base class for transforms that animate over time. These
+// interpolated transforms can be combined to allow for more sophisticated
+// animations. For example, you might combine a rotation of 90 degrees between
+// times 0 and 1, with a scale from 1 to 0.3 between times 0 and 0.25 and a
+// scale from 0.3 to 1 from between times 0.75 and 1.
+//
+///////////////////////////////////////////////////////////////////////////////
+class UI_EXPORT InterpolatedTransform {
+ public:
+ InterpolatedTransform();
+ // The interpolated transform varies only when t in (start_time, end_time).
+ // If t <= start_time, Interpolate(t) will return the initial transform, and
+ // if t >= end_time, Interpolate(t) will return the final transform.
+ InterpolatedTransform(float start_time, float end_time);
+ virtual ~InterpolatedTransform();
+
+ // Returns the interpolated transform at time t. Note: not virtual.
+ gfx::Transform Interpolate(float t) const;
+
+ // The Intepolate ultimately returns the product of our transform at time t
+ // and our child's transform at time t (if we have one).
+ //
+ // This function takes ownership of the passed InterpolatedTransform.
+ void SetChild(InterpolatedTransform* child);
+
+ // If the interpolated transform is reversed, Interpolate(t) will return
+ // Interpolate(1 - t)
+ void SetReversed(bool reversed) { reversed_ = reversed; }
+ bool Reversed() const { return reversed_; }
+
+ protected:
+ // Calculates the interpolated transform without considering our child.
+ virtual gfx::Transform InterpolateButDoNotCompose(float t) const = 0;
+
+ // If time in (start_time_, end_time_], this function linearly interpolates
+ // between start_value and end_value. More precisely it returns
+ // (1 - t) * start_value + t * end_value where
+ // t = (start_time_ - time) / (end_time_ - start_time_).
+ // If time < start_time_ it returns start_value, and if time >= end_time_
+ // it returns end_value.
+ float ValueBetween(float time, float start_value, float end_value) const;
+
+ float start_time() const { return start_time_; }
+ float end_time() const { return end_time_; }
+
+ private:
+ const float start_time_;
+ const float end_time_;
+
+ // The child transform. If you consider an interpolated transform as a
+ // function of t. If, without a child, we are f(t), and our child is
+ // g(t), then with a child we become f'(t) = f(t) * g(t). Using a child
+ // transform, we can chain collections of transforms together.
+ scoped_ptr<InterpolatedTransform> child_;
+
+ bool reversed_;
+
+ DISALLOW_COPY_AND_ASSIGN(InterpolatedTransform);
+};
+
+///////////////////////////////////////////////////////////////////////////////
+// class InterpolatedRotation
+//
+// Represents an animated rotation.
+//
+///////////////////////////////////////////////////////////////////////////////
+class UI_EXPORT InterpolatedRotation : public InterpolatedTransform {
+ public:
+ InterpolatedRotation(float start_degrees, float end_degrees);
+ InterpolatedRotation(float start_degrees,
+ float end_degrees,
+ float start_time,
+ float end_time);
+ virtual ~InterpolatedRotation();
+
+ protected:
+ virtual gfx::Transform InterpolateButDoNotCompose(float t) const OVERRIDE;
+
+ private:
+ const float start_degrees_;
+ const float end_degrees_;
+
+ DISALLOW_COPY_AND_ASSIGN(InterpolatedRotation);
+};
+
+///////////////////////////////////////////////////////////////////////////////
+// class InterpolatedAxisAngleRotation
+//
+// Represents an animated rotation.
+//
+///////////////////////////////////////////////////////////////////////////////
+class UI_EXPORT InterpolatedAxisAngleRotation : public InterpolatedTransform {
+ public:
+ InterpolatedAxisAngleRotation(const gfx::Vector3dF& axis,
+ float start_degrees,
+ float end_degrees);
+ InterpolatedAxisAngleRotation(const gfx::Vector3dF& axis,
+ float start_degrees,
+ float end_degrees,
+ float start_time,
+ float end_time);
+ virtual ~InterpolatedAxisAngleRotation();
+
+ protected:
+ virtual gfx::Transform InterpolateButDoNotCompose(float t) const OVERRIDE;
+
+ private:
+ gfx::Vector3dF axis_;
+ const float start_degrees_;
+ const float end_degrees_;
+
+ DISALLOW_COPY_AND_ASSIGN(InterpolatedAxisAngleRotation);
+};
+
+///////////////////////////////////////////////////////////////////////////////
+// class InterpolatedScale
+//
+// Represents an animated scale.
+//
+///////////////////////////////////////////////////////////////////////////////
+class UI_EXPORT InterpolatedScale : public InterpolatedTransform {
+ public:
+ InterpolatedScale(float start_scale, float end_scale);
+ InterpolatedScale(float start_scale, float end_scale,
+ float start_time, float end_time);
+ InterpolatedScale(const gfx::Point3F& start_scale,
+ const gfx::Point3F& end_scale);
+ InterpolatedScale(const gfx::Point3F& start_scale,
+ const gfx::Point3F& end_scale,
+ float start_time,
+ float end_time);
+ virtual ~InterpolatedScale();
+
+ protected:
+ virtual gfx::Transform InterpolateButDoNotCompose(float t) const OVERRIDE;
+
+ private:
+ const gfx::Point3F start_scale_;
+ const gfx::Point3F end_scale_;
+
+ DISALLOW_COPY_AND_ASSIGN(InterpolatedScale);
+};
+
+class UI_EXPORT InterpolatedTranslation : public InterpolatedTransform {
+ public:
+ InterpolatedTranslation(const gfx::Point& start_pos,
+ const gfx::Point& end_pos);
+ InterpolatedTranslation(const gfx::Point& start_pos,
+ const gfx::Point& end_pos,
+ float start_time,
+ float end_time);
+ virtual ~InterpolatedTranslation();
+
+ protected:
+ virtual gfx::Transform InterpolateButDoNotCompose(float t) const OVERRIDE;
+
+ private:
+ const gfx::Point start_pos_;
+ const gfx::Point end_pos_;
+
+ DISALLOW_COPY_AND_ASSIGN(InterpolatedTranslation);
+};
+
+///////////////////////////////////////////////////////////////////////////////
+// class InterpolatedConstantTransform
+//
+// Represents a transform that is constant over time. This is only useful when
+// composed with other interpolated transforms.
+//
+// See InterpolatedTransformAboutPivot for an example of its usage.
+//
+///////////////////////////////////////////////////////////////////////////////
+class UI_EXPORT InterpolatedConstantTransform : public InterpolatedTransform {
+ public:
+ explicit InterpolatedConstantTransform(const gfx::Transform& transform);
+ virtual ~InterpolatedConstantTransform();
+
+ protected:
+ virtual gfx::Transform InterpolateButDoNotCompose(float t) const OVERRIDE;
+
+ private:
+ const gfx::Transform transform_;
+
+ DISALLOW_COPY_AND_ASSIGN(InterpolatedConstantTransform);
+};
+
+///////////////////////////////////////////////////////////////////////////////
+// class InterpolatedTransformAboutPivot
+//
+// Represents an animated transform with a transformed origin. Essentially,
+// at each time, t, the interpolated transform is created by composing
+// P * T * P^-1 where P is a constant transform to the new origin.
+//
+///////////////////////////////////////////////////////////////////////////////
+class UI_EXPORT InterpolatedTransformAboutPivot : public InterpolatedTransform {
+ public:
+ // Takes ownership of the passed transform.
+ InterpolatedTransformAboutPivot(const gfx::Point& pivot,
+ InterpolatedTransform* transform);
+
+ // Takes ownership of the passed transform.
+ InterpolatedTransformAboutPivot(const gfx::Point& pivot,
+ InterpolatedTransform* transform,
+ float start_time,
+ float end_time);
+ virtual ~InterpolatedTransformAboutPivot();
+
+ protected:
+ virtual gfx::Transform InterpolateButDoNotCompose(float t) const OVERRIDE;
+
+ private:
+ void Init(const gfx::Point& pivot, InterpolatedTransform* transform);
+
+ scoped_ptr<InterpolatedTransform> transform_;
+
+ DISALLOW_COPY_AND_ASSIGN(InterpolatedTransformAboutPivot);
+};
+
+class UI_EXPORT InterpolatedMatrixTransform : public InterpolatedTransform {
+ public:
+ InterpolatedMatrixTransform(const gfx::Transform& start_transform,
+ const gfx::Transform& end_transform);
+
+ InterpolatedMatrixTransform(const gfx::Transform& start_transform,
+ const gfx::Transform& end_transform,
+ float start_time,
+ float end_time);
+
+ virtual ~InterpolatedMatrixTransform();
+
+ protected:
+ virtual gfx::Transform InterpolateButDoNotCompose(float t) const OVERRIDE;
+
+ private:
+ void Init(const gfx::Transform& start_transform,
+ const gfx::Transform& end_transform);
+
+ gfx::DecomposedTransform start_decomp_;
+ gfx::DecomposedTransform end_decomp_;
+};
+
+} // namespace ui
+
+#endif // UI_GFX_INTERPOLATED_TRANSFORM_H_
diff --git a/chromium/ui/gfx/interpolated_transform_unittest.cc b/chromium/ui/gfx/interpolated_transform_unittest.cc
new file mode 100644
index 00000000000..dd31b8b5e2f
--- /dev/null
+++ b/chromium/ui/gfx/interpolated_transform_unittest.cc
@@ -0,0 +1,234 @@
+// Copyright (c) 2012 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 "ui/gfx/interpolated_transform.h"
+
+#include "base/basictypes.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "ui/gfx/rect.h"
+
+namespace {
+
+void CheckApproximatelyEqual(const gfx::Transform& lhs,
+ const gfx::Transform& rhs) {
+ for (int i = 0; i < 4; ++i) {
+ for (int j = 0; j < 4; ++j) {
+ EXPECT_FLOAT_EQ(lhs.matrix().get(i, j), rhs.matrix().get(i, j));
+ }
+ }
+}
+
+float NormalizeAngle(float angle) {
+ while (angle < 0.0f) {
+ angle += 360.0f;
+ }
+ while (angle > 360.0f) {
+ angle -= 360.0f;
+ }
+ return angle;
+}
+
+} // namespace
+
+TEST(InterpolatedTransformTest, InterpolatedRotation) {
+ ui::InterpolatedRotation interpolated_rotation(0, 100);
+ ui::InterpolatedRotation interpolated_rotation_diff_start_end(
+ 0, 100, 100, 200);
+
+ for (int i = 0; i <= 100; ++i) {
+ gfx::Transform rotation;
+ rotation.Rotate(i);
+ gfx::Transform interpolated = interpolated_rotation.Interpolate(i / 100.0f);
+ CheckApproximatelyEqual(rotation, interpolated);
+ interpolated = interpolated_rotation_diff_start_end.Interpolate(i + 100);
+ CheckApproximatelyEqual(rotation, interpolated);
+ }
+}
+
+TEST(InterpolatedTransformTest, InterpolatedScale) {
+ ui::InterpolatedScale interpolated_scale(gfx::Point3F(0, 0, 0),
+ gfx::Point3F(100, 100, 100));
+ ui::InterpolatedScale interpolated_scale_diff_start_end(
+ gfx::Point3F(0, 0, 0), gfx::Point3F(100, 100, 100), 100, 200);
+
+ for (int i = 0; i <= 100; ++i) {
+ gfx::Transform scale;
+ scale.Scale(i, i);
+ gfx::Transform interpolated = interpolated_scale.Interpolate(i / 100.0f);
+ CheckApproximatelyEqual(scale, interpolated);
+ interpolated = interpolated_scale_diff_start_end.Interpolate(i + 100);
+ CheckApproximatelyEqual(scale, interpolated);
+ }
+}
+
+TEST(InterpolatedTransformTest, InterpolatedTranslate) {
+ ui::InterpolatedTranslation interpolated_xform(gfx::Point(0, 0),
+ gfx::Point(100, 100));
+
+ ui::InterpolatedTranslation interpolated_xform_diff_start_end(
+ gfx::Point(0, 0), gfx::Point(100, 100), 100, 200);
+
+ for (int i = 0; i <= 100; ++i) {
+ gfx::Transform xform;
+ xform.Translate(i, i);
+ gfx::Transform interpolated = interpolated_xform.Interpolate(i / 100.0f);
+ CheckApproximatelyEqual(xform, interpolated);
+ interpolated = interpolated_xform_diff_start_end.Interpolate(i + 100);
+ CheckApproximatelyEqual(xform, interpolated);
+ }
+}
+
+TEST(InterpolatedTransformTest, InterpolatedRotationAboutPivot) {
+ gfx::Point pivot(100, 100);
+ gfx::Point above_pivot(100, 200);
+ ui::InterpolatedRotation rot(0, 90);
+ ui::InterpolatedTransformAboutPivot interpolated_xform(
+ pivot,
+ new ui::InterpolatedRotation(0, 90));
+ gfx::Transform result = interpolated_xform.Interpolate(0.0f);
+ CheckApproximatelyEqual(gfx::Transform(), result);
+ result = interpolated_xform.Interpolate(1.0f);
+ gfx::Point expected_result = pivot;
+ result.TransformPoint(pivot);
+ EXPECT_EQ(expected_result, pivot);
+ expected_result = gfx::Point(0, 100);
+ result.TransformPoint(above_pivot);
+ EXPECT_EQ(expected_result, above_pivot);
+}
+
+TEST(InterpolatedTransformTest, InterpolatedScaleAboutPivot) {
+ gfx::Point pivot(100, 100);
+ gfx::Point above_pivot(100, 200);
+ ui::InterpolatedTransformAboutPivot interpolated_xform(
+ pivot,
+ new ui::InterpolatedScale(gfx::Point3F(1, 1, 1), gfx::Point3F(2, 2, 2)));
+ gfx::Transform result = interpolated_xform.Interpolate(0.0f);
+ CheckApproximatelyEqual(gfx::Transform(), result);
+ result = interpolated_xform.Interpolate(1.0f);
+ gfx::Point expected_result = pivot;
+ result.TransformPoint(pivot);
+ EXPECT_EQ(expected_result, pivot);
+ expected_result = gfx::Point(100, 300);
+ result.TransformPoint(above_pivot);
+ EXPECT_EQ(expected_result, above_pivot);
+}
+
+ui::InterpolatedTransform* GetScreenRotation(int degrees, bool reversed) {
+ gfx::Point old_pivot;
+ gfx::Point new_pivot;
+
+ int width = 1920;
+ int height = 180;
+
+ switch (degrees) {
+ case 90:
+ new_pivot = gfx::Point(width, 0);
+ break;
+ case -90:
+ new_pivot = gfx::Point(0, height);
+ break;
+ case 180:
+ case 360:
+ new_pivot = old_pivot = gfx::Point(width / 2, height / 2);
+ break;
+ }
+
+ scoped_ptr<ui::InterpolatedTransform> rotation(
+ new ui::InterpolatedTransformAboutPivot(
+ old_pivot,
+ new ui::InterpolatedRotation(reversed ? degrees : 0,
+ reversed ? 0 : degrees)));
+
+ scoped_ptr<ui::InterpolatedTransform> translation(
+ new ui::InterpolatedTranslation(
+ gfx::Point(0, 0),
+ gfx::Point(new_pivot.x() - old_pivot.x(),
+ new_pivot.y() - old_pivot.y())));
+
+ float scale_factor = 0.9f;
+ scoped_ptr<ui::InterpolatedTransform> scale_down(
+ new ui::InterpolatedScale(1.0f, scale_factor, 0.0f, 0.5f));
+
+ scoped_ptr<ui::InterpolatedTransform> scale_up(
+ new ui::InterpolatedScale(1.0f, 1.0f / scale_factor, 0.5f, 1.0f));
+
+ scoped_ptr<ui::InterpolatedTransform> to_return(
+ new ui::InterpolatedConstantTransform(gfx::Transform()));
+
+ scale_up->SetChild(scale_down.release());
+ translation->SetChild(scale_up.release());
+ rotation->SetChild(translation.release());
+ to_return->SetChild(rotation.release());
+ to_return->SetReversed(reversed);
+
+ return to_return.release();
+}
+
+TEST(InterpolatedTransformTest, ScreenRotationEndsCleanly) {
+ for (int i = 0; i < 2; ++i) {
+ for (int degrees = -360; degrees <= 360; degrees += 90) {
+ const bool reversed = i == 1;
+ scoped_ptr<ui::InterpolatedTransform> screen_rotation(
+ GetScreenRotation(degrees, reversed));
+ gfx::Transform interpolated = screen_rotation->Interpolate(1.0f);
+ SkMatrix44& m = interpolated.matrix();
+ // Upper-left 3x3 matrix should all be 0, 1 or -1.
+ for (int row = 0; row < 3; ++row) {
+ for (int col = 0; col < 3; ++col) {
+ float entry = m.get(row, col);
+ EXPECT_TRUE(entry == 0 || entry == 1 || entry == -1);
+ }
+ }
+ }
+ }
+}
+
+ui::InterpolatedTransform* GetMaximize() {
+ gfx::Rect target_bounds(0, 0, 1920, 1080);
+ gfx::Rect initial_bounds(30, 1000, 192, 108);
+
+ float scale_x = static_cast<float>(
+ target_bounds.height()) / initial_bounds.width();
+ float scale_y = static_cast<float>(
+ target_bounds.width()) / initial_bounds.height();
+
+ scoped_ptr<ui::InterpolatedTransform> scale(
+ new ui::InterpolatedScale(gfx::Point3F(1, 1, 1),
+ gfx::Point3F(scale_x, scale_y, 1)));
+
+ scoped_ptr<ui::InterpolatedTransform> translation(
+ new ui::InterpolatedTranslation(
+ gfx::Point(),
+ gfx::Point(target_bounds.x() - initial_bounds.x(),
+ target_bounds.y() - initial_bounds.y())));
+
+ scoped_ptr<ui::InterpolatedTransform> rotation(
+ new ui::InterpolatedRotation(0, 4.0f));
+
+ scoped_ptr<ui::InterpolatedTransform> rotation_about_pivot(
+ new ui::InterpolatedTransformAboutPivot(
+ gfx::Point(initial_bounds.width() * 0.5,
+ initial_bounds.height() * 0.5),
+ rotation.release()));
+
+ scale->SetChild(translation.release());
+ rotation_about_pivot->SetChild(scale.release());
+
+ rotation_about_pivot->SetReversed(true);
+
+ return rotation_about_pivot.release();
+}
+
+TEST(InterpolatedTransformTest, MaximizeEndsCleanly) {
+ scoped_ptr<ui::InterpolatedTransform> maximize(GetMaximize());
+ gfx::Transform interpolated = maximize->Interpolate(1.0f);
+ SkMatrix44& m = interpolated.matrix();
+ // Upper-left 3x3 matrix should all be 0, 1 or -1.
+ for (int row = 0; row < 3; ++row) {
+ for (int col = 0; col < 3; ++col) {
+ float entry = m.get(row, col);
+ EXPECT_TRUE(entry == 0 || entry == 1 || entry == -1);
+ }
+ }
+}
diff --git a/chromium/ui/gfx/mac/nsimage_cache_unittest.cc.README b/chromium/ui/gfx/mac/nsimage_cache_unittest.cc.README
new file mode 100644
index 00000000000..558426ffd69
--- /dev/null
+++ b/chromium/ui/gfx/mac/nsimage_cache_unittest.cc.README
@@ -0,0 +1,3 @@
+The unit test for this file is in
+chrome/browser/ui/cocoa/nsimage_cache_unittest.mm since it uses certain Chrome
+resources for the test.
diff --git a/chromium/ui/gfx/mac/scoped_ns_disable_screen_updates.h b/chromium/ui/gfx/mac/scoped_ns_disable_screen_updates.h
new file mode 100644
index 00000000000..86b368cc8b5
--- /dev/null
+++ b/chromium/ui/gfx/mac/scoped_ns_disable_screen_updates.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_MAC_SCOPED_NS_DISABLE_SCREEN_UPDATES_H_
+#define UI_GFX_MAC_SCOPED_NS_DISABLE_SCREEN_UPDATES_H_
+
+#import <Cocoa/Cocoa.h>
+
+#include "base/basictypes.h"
+
+namespace gfx {
+
+// A stack-based class to disable Cocoa screen updates. When instantiated, it
+// disables screen updates and enables them when destroyed. Update disabling
+// can be nested, and there is a time-maximum (about 1 second) after which
+// Cocoa will automatically re-enable updating. This class doesn't attempt to
+// overrule that.
+class ScopedNSDisableScreenUpdates {
+ public:
+ ScopedNSDisableScreenUpdates() {
+ NSDisableScreenUpdates();
+ }
+ ~ScopedNSDisableScreenUpdates() {
+ NSEnableScreenUpdates();
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(ScopedNSDisableScreenUpdates);
+};
+
+} // namespace gfx
+
+#endif // UI_GFX_MAC_SCOPED_NS_DISABLE_SCREEN_UPDATES_H_
diff --git a/chromium/ui/gfx/matrix3_f.cc b/chromium/ui/gfx/matrix3_f.cc
new file mode 100644
index 00000000000..562fdb3a964
--- /dev/null
+++ b/chromium/ui/gfx/matrix3_f.cc
@@ -0,0 +1,237 @@
+// Copyright (c) 2013 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 "ui/gfx/matrix3_f.h"
+
+#include <algorithm>
+#include <cmath>
+#include <limits>
+
+#ifndef M_PI
+#define M_PI 3.14159265358979323846
+#endif
+
+namespace {
+
+// This is only to make accessing indices self-explanatory.
+enum MatrixCoordinates {
+ M00,
+ M01,
+ M02,
+ M10,
+ M11,
+ M12,
+ M20,
+ M21,
+ M22,
+ M_END
+};
+
+template<typename T>
+double Determinant3x3(T data[M_END]) {
+ // This routine is separated from the Matrix3F::Determinant because in
+ // computing inverse we do want higher precision afforded by the explicit
+ // use of 'double'.
+ return
+ static_cast<double>(data[M00]) * (
+ static_cast<double>(data[M11]) * data[M22] -
+ static_cast<double>(data[M12]) * data[M21]) +
+ static_cast<double>(data[M01]) * (
+ static_cast<double>(data[M12]) * data[M20] -
+ static_cast<double>(data[M10]) * data[M22]) +
+ static_cast<double>(data[M02]) * (
+ static_cast<double>(data[M10]) * data[M21] -
+ static_cast<double>(data[M11]) * data[M20]);
+}
+
+} // namespace
+
+namespace gfx {
+
+Matrix3F::Matrix3F() {
+}
+
+Matrix3F::~Matrix3F() {
+}
+
+// static
+Matrix3F Matrix3F::Zeros() {
+ Matrix3F matrix;
+ matrix.set(0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f);
+ return matrix;
+}
+
+// static
+Matrix3F Matrix3F::Ones() {
+ Matrix3F matrix;
+ matrix.set(1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f);
+ return matrix;
+}
+
+// static
+Matrix3F Matrix3F::Identity() {
+ Matrix3F matrix;
+ matrix.set(1.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f);
+ return matrix;
+}
+
+// static
+Matrix3F Matrix3F::FromOuterProduct(const Vector3dF& a, const Vector3dF& bt) {
+ Matrix3F matrix;
+ matrix.set(a.x() * bt.x(), a.x() * bt.y(), a.x() * bt.z(),
+ a.y() * bt.x(), a.y() * bt.y(), a.y() * bt.z(),
+ a.z() * bt.x(), a.z() * bt.y(), a.z() * bt.z());
+ return matrix;
+}
+
+bool Matrix3F::IsEqual(const Matrix3F& rhs) const {
+ return 0 == memcmp(data_, rhs.data_, sizeof(data_));
+}
+
+bool Matrix3F::IsNear(const Matrix3F& rhs, float precision) const {
+ DCHECK(precision >= 0);
+ for (int i = 0; i < M_END; ++i) {
+ if (std::abs(data_[i] - rhs.data_[i]) > precision)
+ return false;
+ }
+ return true;
+}
+
+Matrix3F Matrix3F::Inverse() const {
+ Matrix3F inverse = Matrix3F::Zeros();
+ double determinant = Determinant3x3(data_);
+ if (std::numeric_limits<float>::epsilon() > std::abs(determinant))
+ return inverse; // Singular matrix. Return Zeros().
+
+ inverse.set(
+ (data_[M11] * data_[M22] - data_[M12] * data_[M21]) / determinant,
+ (data_[M02] * data_[M21] - data_[M01] * data_[M22]) / determinant,
+ (data_[M01] * data_[M12] - data_[M02] * data_[M11]) / determinant,
+ (data_[M12] * data_[M20] - data_[M10] * data_[M22]) / determinant,
+ (data_[M00] * data_[M22] - data_[M02] * data_[M20]) / determinant,
+ (data_[M02] * data_[M10] - data_[M00] * data_[M12]) / determinant,
+ (data_[M10] * data_[M21] - data_[M11] * data_[M20]) / determinant,
+ (data_[M01] * data_[M20] - data_[M00] * data_[M21]) / determinant,
+ (data_[M00] * data_[M11] - data_[M01] * data_[M10]) / determinant);
+ return inverse;
+}
+
+float Matrix3F::Determinant() const {
+ return static_cast<float>(Determinant3x3(data_));
+}
+
+Vector3dF Matrix3F::SolveEigenproblem(Matrix3F* eigenvectors) const {
+ // The matrix must be symmetric.
+ const float epsilon = std::numeric_limits<float>::epsilon();
+ if (std::abs(data_[M01] - data_[M10]) > epsilon ||
+ std::abs(data_[M02] - data_[M20]) > epsilon ||
+ std::abs(data_[M12] - data_[M21]) > epsilon) {
+ NOTREACHED();
+ return Vector3dF();
+ }
+
+ float eigenvalues[3];
+ float p =
+ data_[M01] * data_[M01] +
+ data_[M02] * data_[M02] +
+ data_[M12] * data_[M12];
+
+ bool diagonal = std::abs(p) < epsilon;
+ if (diagonal) {
+ eigenvalues[0] = data_[M00];
+ eigenvalues[1] = data_[M11];
+ eigenvalues[2] = data_[M22];
+ } else {
+ float q = Trace() / 3.0f;
+ p = (data_[M00] - q) * (data_[M00] - q) +
+ (data_[M11] - q) * (data_[M11] - q) +
+ (data_[M22] - q) * (data_[M22] - q) +
+ 2 * p;
+ p = std::sqrt(p / 6);
+
+ // The computation below puts B as (A - qI) / p, where A is *this.
+ Matrix3F matrix_b(*this);
+ matrix_b.data_[M00] -= q;
+ matrix_b.data_[M11] -= q;
+ matrix_b.data_[M22] -= q;
+ for (int i = 0; i < M_END; ++i)
+ matrix_b.data_[i] /= p;
+
+ double half_det_b = Determinant3x3(matrix_b.data_) / 2.0;
+ // half_det_b should be in <-1, 1>, but beware of rounding error.
+ double phi = 0.0f;
+ if (half_det_b <= -1.0)
+ phi = M_PI / 3;
+ else if (half_det_b < 1.0)
+ phi = acos(half_det_b) / 3;
+
+ eigenvalues[0] = q + 2 * p * static_cast<float>(cos(phi));
+ eigenvalues[2] = q + 2 * p *
+ static_cast<float>(cos(phi + 2.0 * M_PI / 3.0));
+ eigenvalues[1] = 3 * q - eigenvalues[0] - eigenvalues[2];
+ }
+
+ // Put eigenvalues in the descending order.
+ int indices[3] = {0, 1, 2};
+ if (eigenvalues[2] > eigenvalues[1]) {
+ std::swap(eigenvalues[2], eigenvalues[1]);
+ std::swap(indices[2], indices[1]);
+ }
+
+ if (eigenvalues[1] > eigenvalues[0]) {
+ std::swap(eigenvalues[1], eigenvalues[0]);
+ std::swap(indices[1], indices[0]);
+ }
+
+ if (eigenvalues[2] > eigenvalues[1]) {
+ std::swap(eigenvalues[2], eigenvalues[1]);
+ std::swap(indices[2], indices[1]);
+ }
+
+ if (eigenvectors != NULL && diagonal) {
+ // Eigenvectors are e-vectors, just need to be sorted accordingly.
+ *eigenvectors = Zeros();
+ for (int i = 0; i < 3; ++i)
+ eigenvectors->set(indices[i], i, 1.0f);
+ } else if (eigenvectors != NULL) {
+ // Consult the following for a detailed discussion:
+ // Joachim Kopp
+ // Numerical diagonalization of hermitian 3x3 matrices
+ // arXiv.org preprint: physics/0610206
+ // Int. J. Mod. Phys. C19 (2008) 523-548
+
+ // TODO(motek): expand to handle correctly negative and multiple
+ // eigenvalues.
+ for (int i = 0; i < 3; ++i) {
+ float l = eigenvalues[i];
+ // B = A - l * I
+ Matrix3F matrix_b(*this);
+ matrix_b.data_[M00] -= l;
+ matrix_b.data_[M11] -= l;
+ matrix_b.data_[M22] -= l;
+ Vector3dF e1 = CrossProduct(matrix_b.get_column(0),
+ matrix_b.get_column(1));
+ Vector3dF e2 = CrossProduct(matrix_b.get_column(1),
+ matrix_b.get_column(2));
+ Vector3dF e3 = CrossProduct(matrix_b.get_column(2),
+ matrix_b.get_column(0));
+
+ // e1, e2 and e3 should point in the same direction.
+ if (DotProduct(e1, e2) < 0)
+ e2 = -e2;
+
+ if (DotProduct(e1, e3) < 0)
+ e3 = -e3;
+
+ Vector3dF eigvec = e1 + e2 + e3;
+ // Normalize.
+ eigvec.Scale(1.0f / eigvec.Length());
+ eigenvectors->set_column(i, eigvec);
+ }
+ }
+
+ return Vector3dF(eigenvalues[0], eigenvalues[1], eigenvalues[2]);
+}
+
+} // namespace gfx
diff --git a/chromium/ui/gfx/matrix3_f.h b/chromium/ui/gfx/matrix3_f.h
new file mode 100644
index 00000000000..83f9cd96cf8
--- /dev/null
+++ b/chromium/ui/gfx/matrix3_f.h
@@ -0,0 +1,108 @@
+// Copyright (c) 2013 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_MATRIX3_F_H_
+#define UI_GFX_MATRIX3_F_H_
+
+#include "base/logging.h"
+#include "ui/gfx/vector3d_f.h"
+
+namespace gfx {
+
+class UI_EXPORT Matrix3F {
+ public:
+ ~Matrix3F();
+
+ static Matrix3F Zeros();
+ static Matrix3F Ones();
+ static Matrix3F Identity();
+ static Matrix3F FromOuterProduct(const Vector3dF& a, const Vector3dF& bt);
+
+ bool IsEqual(const Matrix3F& rhs) const;
+
+ // Element-wise comparison with given precision.
+ bool IsNear(const Matrix3F& rhs, float precision) const;
+
+ float get(int i, int j) const {
+ return data_[MatrixToArrayCoords(i, j)];
+ }
+
+ void set(int i, int j, float v) {
+ data_[MatrixToArrayCoords(i, j)] = v;
+ }
+
+ void set(float m00, float m01, float m02,
+ float m10, float m11, float m12,
+ float m20, float m21, float m22) {
+ data_[0] = m00;
+ data_[1] = m01;
+ data_[2] = m02;
+ data_[3] = m10;
+ data_[4] = m11;
+ data_[5] = m12;
+ data_[6] = m20;
+ data_[7] = m21;
+ data_[8] = m22;
+ }
+
+ Vector3dF get_column(int i) const {
+ return Vector3dF(
+ data_[MatrixToArrayCoords(0, i)],
+ data_[MatrixToArrayCoords(1, i)],
+ data_[MatrixToArrayCoords(2, i)]);
+ }
+
+ void set_column(int i, const Vector3dF& c) {
+ data_[MatrixToArrayCoords(0, i)] = c.x();
+ data_[MatrixToArrayCoords(1, i)] = c.y();
+ data_[MatrixToArrayCoords(2, i)] = c.z();
+ }
+
+ // Returns an inverse of this if the matrix is non-singular, zero (== Zero())
+ // otherwise.
+ Matrix3F Inverse() const;
+
+ // Value of the determinant of the matrix.
+ float Determinant() const;
+
+ // Trace (sum of diagonal elements) of the matrix.
+ float Trace() const {
+ return data_[MatrixToArrayCoords(0, 0)] +
+ data_[MatrixToArrayCoords(1, 1)] +
+ data_[MatrixToArrayCoords(2, 2)];
+ }
+
+ // Compute eigenvalues and (optionally) normalized eigenvectors of
+ // a positive defnite matrix *this. Eigenvectors are computed only if
+ // non-null |eigenvectors| matrix is passed. If it is NULL, the routine
+ // will not attempt to compute eigenvectors but will still return eigenvalues
+ // if they can be computed.
+ // If eigenvalues cannot be computed (the matrix does not meet constraints)
+ // the 0-vector is returned. Note that to retrieve eigenvalues, the matrix
+ // only needs to be symmetric while eigenvectors require it to be
+ // positive-definite. Passing a non-positive definite matrix will result in
+ // NaNs in vectors which cannot be computed.
+ // Eigenvectors are placed as column in |eigenvectors| in order corresponding
+ // to eigenvalues.
+ Vector3dF SolveEigenproblem(Matrix3F* eigenvectors) const;
+
+ private:
+ Matrix3F(); // Uninitialized default.
+
+ static int MatrixToArrayCoords(int i, int j) {
+ DCHECK(i >= 0 && i < 3);
+ DCHECK(j >= 0 && j < 3);
+ return i * 3 + j;
+ }
+
+ float data_[9];
+};
+
+inline bool operator==(const Matrix3F& lhs, const Matrix3F& rhs) {
+ return lhs.IsEqual(rhs);
+}
+
+} // namespace gfx
+
+#endif // UI_GFX_MATRIX3_F_H_
diff --git a/chromium/ui/gfx/matrix3_unittest.cc b/chromium/ui/gfx/matrix3_unittest.cc
new file mode 100644
index 00000000000..04c656f6d26
--- /dev/null
+++ b/chromium/ui/gfx/matrix3_unittest.cc
@@ -0,0 +1,148 @@
+// Copyright (c) 2013 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 <cmath>
+#include <limits>
+
+#include "base/basictypes.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "ui/gfx/matrix3_f.h"
+
+namespace gfx {
+namespace {
+
+TEST(Matrix3fTest, Constructors) {
+ Matrix3F zeros = Matrix3F::Zeros();
+ Matrix3F ones = Matrix3F::Ones();
+ Matrix3F identity = Matrix3F::Identity();
+
+ Matrix3F product_ones = Matrix3F::FromOuterProduct(
+ Vector3dF(1.0f, 1.0f, 1.0f), Vector3dF(1.0f, 1.0f, 1.0f));
+ Matrix3F product_zeros = Matrix3F::FromOuterProduct(
+ Vector3dF(1.0f, 1.0f, 1.0f), Vector3dF(0.0f, 0.0f, 0.0f));
+ EXPECT_EQ(ones, product_ones);
+ EXPECT_EQ(zeros, product_zeros);
+
+ for (int i = 0; i < 3; ++i) {
+ for (int j = 0; j < 3; ++j)
+ EXPECT_EQ(i == j ? 1.0f : 0.0f, identity.get(i, j));
+ }
+}
+
+TEST(Matrix3fTest, DataAccess) {
+ Matrix3F matrix = Matrix3F::Ones();
+ Matrix3F identity = Matrix3F::Identity();
+
+ EXPECT_EQ(Vector3dF(0.0f, 1.0f, 0.0f), identity.get_column(1));
+ matrix.set(0.0f, 1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f, 7.0f, 8.0f);
+ EXPECT_EQ(Vector3dF(2.0f, 5.0f, 8.0f), matrix.get_column(2));
+ matrix.set_column(0, Vector3dF(0.1f, 0.2f, 0.3f));
+ EXPECT_EQ(Vector3dF(0.1f, 0.2f, 0.3f), matrix.get_column(0));
+
+ EXPECT_EQ(0.1f, matrix.get(0, 0));
+ EXPECT_EQ(5.0f, matrix.get(1, 2));
+}
+
+TEST(Matrix3fTest, Determinant) {
+ EXPECT_EQ(1.0f, Matrix3F::Identity().Determinant());
+ EXPECT_EQ(0.0f, Matrix3F::Zeros().Determinant());
+ EXPECT_EQ(0.0f, Matrix3F::Ones().Determinant());
+
+ // Now for something non-trivial...
+ Matrix3F matrix = Matrix3F::Zeros();
+ matrix.set(0, 5, 6, 8, 7, 0, 1, 9, 0);
+ EXPECT_EQ(390.0f, matrix.Determinant());
+ matrix.set(2, 0, 3 * matrix.get(0, 0));
+ matrix.set(2, 1, 3 * matrix.get(0, 1));
+ matrix.set(2, 2, 3 * matrix.get(0, 2));
+ EXPECT_EQ(0, matrix.Determinant());
+
+ matrix.set(0.57f, 0.205f, 0.942f,
+ 0.314f, 0.845f, 0.826f,
+ 0.131f, 0.025f, 0.962f);
+ EXPECT_NEAR(0.3149f, matrix.Determinant(), 0.0001f);
+}
+
+TEST(Matrix3fTest, Inverse) {
+ Matrix3F identity = Matrix3F::Identity();
+ Matrix3F inv_identity = identity.Inverse();
+ EXPECT_EQ(identity, inv_identity);
+
+ Matrix3F singular = Matrix3F::Zeros();
+ singular.set(1.0f, 3.0f, 4.0f,
+ 2.0f, 11.0f, 5.0f,
+ 0.5f, 1.5f, 2.0f);
+ EXPECT_EQ(0, singular.Determinant());
+ EXPECT_EQ(Matrix3F::Zeros(), singular.Inverse());
+
+ Matrix3F regular = Matrix3F::Zeros();
+ regular.set(0.57f, 0.205f, 0.942f,
+ 0.314f, 0.845f, 0.826f,
+ 0.131f, 0.025f, 0.962f);
+ Matrix3F inv_regular = regular.Inverse();
+ regular.set(2.51540616f, -0.55138018f, -1.98968043f,
+ -0.61552266f, 1.34920184f, -0.55573636f,
+ -0.32653861f, 0.04002158f, 1.32488726f);
+ EXPECT_TRUE(regular.IsNear(inv_regular, 0.00001f));
+}
+
+TEST(Matrix3fTest, EigenvectorsIdentity) {
+ // This block tests the trivial case of eigenvalues of the identity matrix.
+ Matrix3F identity = Matrix3F::Identity();
+ Vector3dF eigenvals = identity.SolveEigenproblem(NULL);
+ EXPECT_EQ(Vector3dF(1.0f, 1.0f, 1.0f), eigenvals);
+}
+
+TEST(Matrix3fTest, EigenvectorsDiagonal) {
+ // This block tests the another trivial case of eigenvalues of a diagonal
+ // matrix. Here we expect values to be sorted.
+ Matrix3F matrix = Matrix3F::Zeros();
+ matrix.set(0, 0, 1.0f);
+ matrix.set(1, 1, -2.5f);
+ matrix.set(2, 2, 3.14f);
+ Matrix3F eigenvectors = Matrix3F::Zeros();
+ Vector3dF eigenvals = matrix.SolveEigenproblem(&eigenvectors);
+ EXPECT_EQ(Vector3dF(3.14f, 1.0f, -2.5f), eigenvals);
+
+ EXPECT_EQ(Vector3dF(0.0f, 0.0f, 1.0f), eigenvectors.get_column(0));
+ EXPECT_EQ(Vector3dF(1.0f, 0.0f, 0.0f), eigenvectors.get_column(1));
+ EXPECT_EQ(Vector3dF(0.0f, 1.0f, 0.0f), eigenvectors.get_column(2));
+}
+
+TEST(Matrix3fTest, EigenvectorsNiceNotPositive) {
+ // This block tests computation of eigenvectors of a matrix where nice
+ // round values are expected.
+ Matrix3F matrix = Matrix3F::Zeros();
+ // This is not a positive-definite matrix but eigenvalues and the first
+ // eigenvector should nonetheless be computed correctly.
+ matrix.set(3, 2, 4, 2, 0, 2, 4, 2, 3);
+ Matrix3F eigenvectors = Matrix3F::Zeros();
+ Vector3dF eigenvals = matrix.SolveEigenproblem(&eigenvectors);
+ EXPECT_EQ(Vector3dF(8.0f, -1.0f, -1.0f), eigenvals);
+
+ Vector3dF expected_principal(0.66666667f, 0.33333333f, 0.66666667f);
+ EXPECT_NEAR(0.0f,
+ (expected_principal - eigenvectors.get_column(0)).Length(),
+ 0.000001f);
+}
+
+TEST(Matrix3fTest, EigenvectorsPositiveDefinite) {
+ // This block tests computation of eigenvectors of a matrix where output
+ // is not as nice as above, but it actually meets the definition.
+ Matrix3F matrix = Matrix3F::Zeros();
+ Matrix3F eigenvectors = Matrix3F::Zeros();
+ Matrix3F expected_eigenvectors = Matrix3F::Zeros();
+ matrix.set(1, -1, 2, -1, 4, 5, 2, 5, 0);
+ Vector3dF eigenvals = matrix.SolveEigenproblem(&eigenvectors);
+ Vector3dF expected_eigv(7.3996266f, 1.91197255f, -4.31159915f);
+ expected_eigv -= eigenvals;
+ EXPECT_NEAR(0, expected_eigv.LengthSquared(), 0.00001f);
+ expected_eigenvectors.set(0.04926317f, -0.92135662f, -0.38558414f,
+ 0.82134249f, 0.25703273f, -0.50924521f,
+ 0.56830419f, -0.2916096f, 0.76941158f);
+ EXPECT_TRUE(expected_eigenvectors.IsNear(eigenvectors, 0.00001f));
+}
+
+}
+}
diff --git a/chromium/ui/gfx/native_widget_types.h b/chromium/ui/gfx/native_widget_types.h
new file mode 100644
index 00000000000..faf3607db92
--- /dev/null
+++ b/chromium/ui/gfx/native_widget_types.h
@@ -0,0 +1,330 @@
+// Copyright (c) 2012 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_
+
+#include "build/build_config.h"
+
+#if defined(OS_ANDROID)
+#include <jni.h>
+#endif
+
+#include "base/basictypes.h"
+#include "base/logging.h"
+#include "ui/base/ui_export.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(USE_AURA)
+#include "ui/base/cursor/cursor.h"
+
+class SkRegion;
+namespace aura {
+class Window;
+}
+namespace ui {
+class Event;
+}
+#endif // defined(USE_AURA)
+
+#if defined(OS_WIN)
+#include <windows.h> // NOLINT
+typedef struct HFONT__* HFONT;
+struct IAccessible;
+#elif defined(OS_IOS)
+struct CGContext;
+#ifdef __OBJC__
+@class UIEvent;
+@class UIFont;
+@class UIImage;
+@class UIView;
+@class UIWindow;
+@class UITextField;
+#else
+class UIEvent;
+class UIFont;
+class UIImage;
+class UIView;
+class UIWindow;
+class UITextField;
+#endif // __OBJC__
+#elif defined(OS_MACOSX)
+struct CGContext;
+#ifdef __OBJC__
+@class NSCursor;
+@class NSEvent;
+@class NSFont;
+@class NSImage;
+@class NSView;
+@class NSWindow;
+@class NSTextField;
+#else
+class NSCursor;
+class NSEvent;
+class NSFont;
+class NSImage;
+struct NSView;
+class NSWindow;
+class NSTextField;
+#endif // __OBJC__
+#elif defined(OS_POSIX)
+typedef struct _PangoFontDescription PangoFontDescription;
+typedef struct _cairo cairo_t;
+#endif
+
+#if defined(TOOLKIT_GTK)
+typedef struct _GdkCursor GdkCursor;
+typedef union _GdkEvent GdkEvent;
+typedef struct _GdkPixbuf GdkPixbuf;
+typedef struct _GdkRegion GdkRegion;
+typedef struct _GtkWidget GtkWidget;
+typedef struct _GtkWindow GtkWindow;
+#elif defined(OS_ANDROID)
+struct ANativeWindow;
+namespace ui {
+class WindowAndroid;
+class ViewAndroid;
+}
+#endif
+class SkBitmap;
+
+namespace gfx {
+
+#if defined(USE_AURA)
+typedef ui::Cursor NativeCursor;
+typedef aura::Window* NativeView;
+typedef aura::Window* NativeWindow;
+typedef SkRegion* NativeRegion;
+typedef ui::Event* NativeEvent;
+#elif defined(OS_WIN)
+typedef HCURSOR NativeCursor;
+typedef HWND NativeView;
+typedef HWND NativeWindow;
+typedef HRGN NativeRegion;
+typedef MSG NativeEvent;
+#elif defined(OS_IOS)
+typedef void* NativeCursor;
+typedef UIView* NativeView;
+typedef UIWindow* NativeWindow;
+typedef UIEvent* NativeEvent;
+#elif defined(OS_MACOSX)
+typedef NSCursor* NativeCursor;
+typedef NSView* NativeView;
+typedef NSWindow* NativeWindow;
+typedef NSEvent* NativeEvent;
+#elif defined(TOOLKIT_GTK)
+typedef GdkCursor* NativeCursor;
+typedef GtkWidget* NativeView;
+typedef GtkWindow* NativeWindow;
+typedef GdkRegion* NativeRegion;
+typedef GdkEvent* NativeEvent;
+#elif defined(OS_ANDROID)
+typedef void* NativeCursor;
+typedef ui::ViewAndroid* NativeView;
+typedef ui::WindowAndroid* NativeWindow;
+typedef void* NativeRegion;
+typedef jobject NativeEvent;
+#endif
+
+#if defined(OS_WIN)
+typedef HFONT NativeFont;
+typedef HWND NativeEditView;
+typedef HDC NativeDrawingContext;
+typedef IAccessible* NativeViewAccessible;
+#elif defined(OS_IOS)
+typedef UIFont* NativeFont;
+typedef UITextField* NativeEditView;
+typedef CGContext* NativeDrawingContext;
+#elif defined(OS_MACOSX)
+typedef NSFont* NativeFont;
+typedef NSTextField* NativeEditView;
+typedef CGContext* NativeDrawingContext;
+typedef void* NativeViewAccessible;
+#elif defined(TOOLKIT_GTK)
+typedef PangoFontDescription* NativeFont;
+typedef GtkWidget* NativeEditView;
+typedef cairo_t* NativeDrawingContext;
+typedef void* NativeViewAccessible;
+#elif defined(USE_AURA)
+typedef PangoFontDescription* NativeFont;
+typedef void* NativeEditView;
+typedef cairo_t* NativeDrawingContext;
+typedef void* NativeViewAccessible;
+#elif defined(OS_ANDROID)
+typedef void* NativeFont;
+typedef void* NativeEditView;
+typedef void* NativeDrawingContext;
+typedef void* NativeViewAccessible;
+#endif
+
+// A constant value to indicate that gfx::NativeCursor refers to no cursor.
+#if defined(USE_AURA)
+const int kNullCursor = 0;
+#else
+const gfx::NativeCursor kNullCursor = static_cast<gfx::NativeCursor>(NULL);
+#endif
+
+#if defined(OS_IOS)
+typedef UIImage NativeImageType;
+#elif defined(OS_MACOSX)
+typedef NSImage NativeImageType;
+#elif defined(TOOLKIT_GTK)
+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) && !defined(USE_AURA)
+// 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) || defined(USE_AURA)
+// On Mac, Linux and USE_AURA, 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)
+
+// 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;
+#elif defined(USE_AURA) && defined(OS_MACOSX)
+ // Mac-Aura uses NSView-backed GLSurface. Regular Mac does not.
+ // TODO(dhollowa): Rationalize these two definitions. http://crbug.com/104551.
+ typedef NSView* PluginWindowHandle;
+ const PluginWindowHandle kNullPluginWindow = 0;
+#elif defined(OS_ANDROID)
+ typedef uint64 PluginWindowHandle;
+ const PluginWindowHandle kNullPluginWindow = 0;
+#elif defined(USE_OZONE)
+ typedef intptr_t 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
+
+enum SurfaceType {
+ EMPTY,
+ NATIVE_DIRECT,
+ NATIVE_TRANSPORT,
+ TEXTURE_TRANSPORT
+};
+
+struct GLSurfaceHandle {
+ GLSurfaceHandle()
+ : handle(kNullPluginWindow),
+ transport_type(EMPTY),
+ parent_gpu_process_id(0),
+ parent_client_id(0) {
+ }
+ GLSurfaceHandle(PluginWindowHandle handle_, SurfaceType transport_)
+ : handle(handle_),
+ transport_type(transport_),
+ parent_gpu_process_id(0),
+ parent_client_id(0) {
+ DCHECK(!is_null() || handle == kNullPluginWindow);
+ DCHECK(transport_type != TEXTURE_TRANSPORT ||
+ handle == kNullPluginWindow);
+ }
+ bool is_null() const { return transport_type == EMPTY; }
+ bool is_transport() const {
+ return transport_type == NATIVE_TRANSPORT ||
+ transport_type == TEXTURE_TRANSPORT;
+ }
+ PluginWindowHandle handle;
+ SurfaceType transport_type;
+ int parent_gpu_process_id;
+ uint32 parent_client_id;
+};
+
+// AcceleratedWidget provides a surface to compositors to paint pixels.
+#if defined(OS_WIN)
+typedef HWND AcceleratedWidget;
+const AcceleratedWidget kNullAcceleratedWidget = NULL;
+#elif defined(USE_X11)
+typedef unsigned long AcceleratedWidget;
+const AcceleratedWidget kNullAcceleratedWidget = 0;
+#elif defined(OS_IOS)
+typedef UIView* AcceleratedWidget;
+const AcceleratedWidget kNullAcceleratedWidget = 0;
+#elif defined(OS_MACOSX)
+typedef NSView* AcceleratedWidget;
+const AcceleratedWidget kNullAcceleratedWidget = 0;
+#elif defined(OS_ANDROID)
+typedef ANativeWindow* AcceleratedWidget;
+const AcceleratedWidget kNullAcceleratedWidget = 0;
+#elif defined(USE_OZONE)
+typedef intptr_t AcceleratedWidget;
+const AcceleratedWidget kNullAcceleratedWidget = 0;
+#else
+#error unknown platform
+#endif
+
+} // namespace gfx
+
+#endif // UI_GFX_NATIVE_WIDGET_TYPES_H_
diff --git a/chromium/ui/gfx/pango_util.cc b/chromium/ui/gfx/pango_util.cc
new file mode 100644
index 00000000000..3f96d4b140d
--- /dev/null
+++ b/chromium/ui/gfx/pango_util.cc
@@ -0,0 +1,416 @@
+// Copyright (c) 2012 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 "ui/gfx/pango_util.h"
+
+#include <cairo/cairo.h>
+#include <fontconfig/fontconfig.h>
+#include <pango/pango.h>
+#include <pango/pangocairo.h>
+#include <string>
+
+#include <algorithm>
+#include <map>
+#include <vector>
+
+#include "base/logging.h"
+#include "base/strings/utf_string_conversions.h"
+#include "ui/gfx/canvas.h"
+#include "ui/gfx/font.h"
+#include "ui/gfx/font_render_params_linux.h"
+#include "ui/gfx/platform_font_pango.h"
+#include "ui/gfx/rect.h"
+#include "ui/gfx/text_utils.h"
+
+#if defined(TOOLKIT_GTK)
+#include <gdk/gdk.h>
+#endif
+
+namespace gfx {
+
+namespace {
+
+// Marker for accelerators in the text.
+const gunichar kAcceleratorChar = '&';
+
+// Multiply by the text height to determine how much text should be faded
+// when elliding.
+const double kFadeWidthFactor = 1.5;
+
+// End state of the elliding fade.
+const double kFadeFinalAlpha = 0.15;
+
+// Return |cairo_font_options|. If needed, allocate and update it.
+// TODO(derat): Return font-specific options: http://crbug.com/125235
+cairo_font_options_t* GetCairoFontOptions() {
+ // Font settings that we initialize once and then use when drawing text.
+ static cairo_font_options_t* cairo_font_options = NULL;
+ if (cairo_font_options)
+ return cairo_font_options;
+
+ cairo_font_options = cairo_font_options_create();
+
+ const FontRenderParams& params = GetDefaultFontRenderParams();
+ FontRenderParams::SubpixelRendering subpixel = params.subpixel_rendering;
+ if (!params.antialiasing) {
+ cairo_font_options_set_antialias(cairo_font_options, CAIRO_ANTIALIAS_NONE);
+ } else if (subpixel == FontRenderParams::SUBPIXEL_RENDERING_NONE) {
+ 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 (subpixel == FontRenderParams::SUBPIXEL_RENDERING_RGB)
+ cairo_subpixel_order = CAIRO_SUBPIXEL_ORDER_RGB;
+ else if (subpixel == FontRenderParams::SUBPIXEL_RENDERING_BGR)
+ cairo_subpixel_order = CAIRO_SUBPIXEL_ORDER_BGR;
+ else if (subpixel == FontRenderParams::SUBPIXEL_RENDERING_VRGB)
+ cairo_subpixel_order = CAIRO_SUBPIXEL_ORDER_VRGB;
+ else if (subpixel == FontRenderParams::SUBPIXEL_RENDERING_VBGR)
+ cairo_subpixel_order = CAIRO_SUBPIXEL_ORDER_VBGR;
+ else
+ NOTREACHED() << "Unhandled subpixel rendering type " << subpixel;
+ cairo_font_options_set_subpixel_order(cairo_font_options,
+ cairo_subpixel_order);
+ }
+
+ if (params.hinting == FontRenderParams::HINTING_NONE ||
+ params.subpixel_positioning) {
+ cairo_font_options_set_hint_style(cairo_font_options,
+ CAIRO_HINT_STYLE_NONE);
+ cairo_font_options_set_hint_metrics(cairo_font_options,
+ CAIRO_HINT_METRICS_OFF);
+ } else {
+ cairo_hint_style_t cairo_hint_style = CAIRO_HINT_STYLE_DEFAULT;
+ if (params.hinting == FontRenderParams::HINTING_SLIGHT)
+ cairo_hint_style = CAIRO_HINT_STYLE_SLIGHT;
+ else if (params.hinting == FontRenderParams::HINTING_MEDIUM)
+ cairo_hint_style = CAIRO_HINT_STYLE_MEDIUM;
+ else if (params.hinting == FontRenderParams::HINTING_FULL)
+ cairo_hint_style = CAIRO_HINT_STYLE_FULL;
+ else
+ NOTREACHED() << "Unhandled hinting style " << params.hinting;
+ cairo_font_options_set_hint_style(cairo_font_options, cairo_hint_style);
+ cairo_font_options_set_hint_metrics(cairo_font_options,
+ CAIRO_HINT_METRICS_ON);
+ }
+
+ return cairo_font_options;
+}
+
+// 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 = 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;
+}
+
+} // namespace
+
+PangoContext* GetPangoContext() {
+#if defined(TOOLKIT_GTK)
+ return gdk_pango_context_get();
+#else
+ PangoFontMap* font_map = pango_cairo_font_map_get_default();
+ return pango_font_map_create_context(font_map);
+#endif
+}
+
+double GetPangoResolution() {
+ static double resolution;
+ static bool determined_resolution = false;
+ if (!determined_resolution) {
+ determined_resolution = true;
+ PangoContext* default_context = GetPangoContext();
+ resolution = pango_cairo_context_get_resolution(default_context);
+ g_object_unref(default_context);
+ }
+ return resolution;
+}
+
+void DrawTextOntoCairoSurface(cairo_t* cr,
+ const base::string16& text,
+ const gfx::Font& font,
+ const gfx::Rect& bounds,
+ const gfx::Rect& clip,
+ SkColor text_color,
+ int flags) {
+ PangoLayout* layout = pango_cairo_create_layout(cr);
+ base::i18n::TextDirection text_direction =
+ base::i18n::GetFirstStrongCharacterDirection(text);
+ DCHECK(!bounds.IsEmpty());
+
+ gfx::SetupPangoLayout(
+ layout, text, font, bounds.width(), text_direction, 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);
+
+ int width = 0, height = 0;
+ pango_layout_get_pixel_size(layout, &width, &height);
+ Rect text_rect(bounds.x(), bounds.y(), width, height);
+ // Vertically center |text_rect| in |bounds|.
+ text_rect += gfx::Vector2d(0, (bounds.height() - text_rect.height()) / 2);
+
+ DrawPangoLayout(cr, layout, font, bounds, text_rect,
+ text_color, text_direction, flags);
+
+ cairo_restore(cr);
+ g_object_unref(layout);
+}
+
+// Pass a width greater than 0 to force wrapping and eliding.
+static void SetupPangoLayoutWithoutFont(
+ PangoLayout* layout,
+ const base::string16& text,
+ int width,
+ base::i18n::TextDirection text_direction,
+ int flags) {
+ cairo_font_options_t* cairo_font_options = GetCairoFontOptions();
+
+ // If we got an explicit request to turn off subpixel rendering, disable it on
+ // a copy of the static font options object.
+ bool copied_cairo_font_options = false;
+ if ((flags & Canvas::NO_SUBPIXEL_RENDERING) &&
+ (cairo_font_options_get_antialias(cairo_font_options) ==
+ CAIRO_ANTIALIAS_SUBPIXEL)) {
+ cairo_font_options = cairo_font_options_copy(cairo_font_options);
+ copied_cairo_font_options = true;
+ cairo_font_options_set_antialias(cairo_font_options, CAIRO_ANTIALIAS_GRAY);
+ }
+
+ // 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);
+
+ if (copied_cairo_font_options) {
+ cairo_font_options_destroy(cairo_font_options);
+ cairo_font_options = NULL;
+ }
+
+ // Set Pango's base text direction explicitly from |text_direction|.
+ pango_layout_set_auto_dir(layout, FALSE);
+ pango_context_set_base_dir(pango_layout_get_context(layout),
+ (text_direction == base::i18n::RIGHT_TO_LEFT ?
+ PANGO_DIRECTION_RTL : PANGO_DIRECTION_LTR));
+
+ if (width > 0)
+ pango_layout_set_width(layout, width * PANGO_SCALE);
+
+ if (flags & Canvas::TEXT_ALIGN_CENTER) {
+ // We don't support center aligned w/ eliding.
+ DCHECK(gfx::Canvas::NO_ELLIPSIS);
+ pango_layout_set_alignment(layout, PANGO_ALIGN_CENTER);
+ } else if (flags & Canvas::TEXT_ALIGN_RIGHT) {
+ pango_layout_set_alignment(layout, PANGO_ALIGN_RIGHT);
+ }
+
+ if (flags & Canvas::NO_ELLIPSIS) {
+ pango_layout_set_ellipsize(layout, PANGO_ELLIPSIZE_NONE);
+ if (flags & Canvas::MULTI_LINE) {
+ pango_layout_set_wrap(layout,
+ (flags & Canvas::CHARACTER_BREAK) ?
+ PANGO_WRAP_WORD_CHAR : PANGO_WRAP_WORD);
+ }
+ } else if (text_direction == base::i18n::RIGHT_TO_LEFT) {
+ pango_layout_set_ellipsize(layout, PANGO_ELLIPSIZE_END);
+ } else {
+ // Fading the text will be handled in the draw operation.
+ // Ensure that the text is only on one line.
+ pango_layout_set_ellipsize(layout, PANGO_ELLIPSIZE_NONE);
+ pango_layout_set_width(layout, -1);
+ }
+
+ // 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 = GetPangoResolution();
+ if (resolution > 0) {
+ pango_cairo_context_set_resolution(pango_layout_get_context(layout),
+ resolution);
+ }
+
+ // Set text and accelerator character if needed.
+ if (flags & Canvas::SHOW_PREFIX) {
+ // Escape the text string to be used as markup.
+ std::string utf8 = UTF16ToUTF8(text);
+ 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 {
+ std::string utf8;
+
+ // Remove the ampersand character. A double ampersand is output as
+ // a single ampersand.
+ if (flags & Canvas::HIDE_PREFIX) {
+ DCHECK_EQ(1, g_unichar_to_utf8(kAcceleratorChar, NULL));
+ base::string16 accelerator_removed =
+ RemoveAcceleratorChar(text, static_cast<char16>(kAcceleratorChar),
+ NULL, NULL);
+ utf8 = UTF16ToUTF8(accelerator_removed);
+ } else {
+ utf8 = UTF16ToUTF8(text);
+ }
+
+ pango_layout_set_text(layout, utf8.data(), utf8.size());
+ }
+}
+
+void SetupPangoLayout(PangoLayout* layout,
+ const base::string16& text,
+ const Font& font,
+ int width,
+ base::i18n::TextDirection text_direction,
+ int flags) {
+ SetupPangoLayoutWithoutFont(layout, text, width, text_direction, flags);
+
+ ScopedPangoFontDescription desc(font.GetNativeFont());
+ pango_layout_set_font_description(layout, desc.get());
+}
+
+void SetupPangoLayoutWithFontDescription(
+ PangoLayout* layout,
+ const base::string16& text,
+ const std::string& font_description,
+ int width,
+ base::i18n::TextDirection text_direction,
+ int flags) {
+ SetupPangoLayoutWithoutFont(layout, text, width, text_direction, flags);
+
+ ScopedPangoFontDescription desc(
+ pango_font_description_from_string(font_description.c_str()));
+ pango_layout_set_font_description(layout, desc.get());
+}
+
+void DrawPangoLayout(cairo_t* cr,
+ PangoLayout* layout,
+ const Font& font,
+ const gfx::Rect& bounds,
+ const gfx::Rect& text_rect,
+ SkColor text_color,
+ base::i18n::TextDirection text_direction,
+ int flags) {
+ double r = SkColorGetR(text_color) / 255.0,
+ g = SkColorGetG(text_color) / 255.0,
+ b = SkColorGetB(text_color) / 255.0,
+ a = SkColorGetA(text_color) / 255.0;
+
+ cairo_pattern_t* pattern = NULL;
+
+ cairo_save(cr);
+
+ // If we're not eliding, use a fixed color.
+ // Otherwise, create a gradient pattern to use as the source.
+ if (text_direction == base::i18n::RIGHT_TO_LEFT ||
+ (flags & gfx::Canvas::NO_ELLIPSIS) ||
+ text_rect.width() <= bounds.width()) {
+ cairo_set_source_rgba(cr, r, g, b, a);
+ } else {
+ // Fade to semi-transparent to elide.
+ int fade_width = static_cast<double>(text_rect.height()) * kFadeWidthFactor;
+ if (fade_width > bounds.width() / 2) {
+ // Don't fade more than half the text.
+ fade_width = bounds.width() / 2;
+ }
+ int fade_x = bounds.x() + bounds.width() - fade_width;
+
+ pattern = cairo_pattern_create_linear(
+ fade_x, bounds.y(), bounds.x() + bounds.width(), bounds.y());
+ cairo_pattern_add_color_stop_rgba(pattern, 0, r, g, b, a);
+ cairo_pattern_add_color_stop_rgba(pattern, 1, r, g, b, kFadeFinalAlpha);
+ cairo_set_source(cr, pattern);
+ }
+
+ cairo_move_to(cr, text_rect.x(), text_rect.y());
+ pango_cairo_show_layout(cr, layout);
+
+ if (font.GetStyle() & gfx::Font::UNDERLINE) {
+ gfx::PlatformFontPango* platform_font =
+ static_cast<gfx::PlatformFontPango*>(font.platform_font());
+ DrawPangoTextUnderline(cr, platform_font, 0.0, text_rect);
+ }
+
+ if (pattern)
+ cairo_pattern_destroy(pattern);
+
+ cairo_restore(cr);
+}
+
+void DrawPangoTextUnderline(cairo_t* cr,
+ gfx::PlatformFontPango* platform_font,
+ double extra_edge_width,
+ const Rect& text_rect) {
+ const double underline_y =
+ static_cast<double>(text_rect.y()) + text_rect.height() +
+ platform_font->underline_position();
+ cairo_set_line_width(
+ cr, platform_font->underline_thickness() + 2 * extra_edge_width);
+ cairo_move_to(cr,
+ text_rect.x() - extra_edge_width,
+ underline_y);
+ cairo_line_to(cr,
+ text_rect.x() + text_rect.width() + extra_edge_width,
+ underline_y);
+ cairo_stroke(cr);
+}
+
+size_t GetPangoFontSizeInPixels(PangoFontDescription* pango_font) {
+ size_t size_in_pixels = pango_font_description_get_size(pango_font);
+ if (pango_font_description_get_size_is_absolute(pango_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_SCALE;
+ } else {
+ // Otherwise, we need to convert from points.
+ size_in_pixels = size_in_pixels * GetPixelsInPoint() / PANGO_SCALE;
+ }
+ return size_in_pixels;
+}
+
+PangoFontMetrics* GetPangoFontMetrics(PangoFontDescription* desc) {
+ static std::map<int, PangoFontMetrics*>* desc_to_metrics = NULL;
+ static PangoContext* context = NULL;
+
+ if (!context) {
+ context = GetPangoContext();
+ pango_context_set_language(context, pango_language_get_default());
+ }
+
+ if (!desc_to_metrics)
+ desc_to_metrics = new std::map<int, PangoFontMetrics*>();
+
+ const 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->insert(std::make_pair(desc_hash, metrics));
+ return metrics;
+ }
+ return i->second;
+}
+
+} // namespace gfx
diff --git a/chromium/ui/gfx/pango_util.h b/chromium/ui/gfx/pango_util.h
new file mode 100644
index 00000000000..abb0a7d550e
--- /dev/null
+++ b/chromium/ui/gfx/pango_util.h
@@ -0,0 +1,116 @@
+// Copyright (c) 2012 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_PANGO_UTIL_H_
+#define UI_GFX_PANGO_UTIL_H_
+
+#include <cairo/cairo.h>
+#include <pango/pango.h>
+#include <string>
+
+#include "base/i18n/rtl.h"
+#include "base/logging.h"
+#include "base/strings/string16.h"
+#include "third_party/skia/include/core/SkColor.h"
+#include "ui/base/ui_export.h"
+
+typedef struct _PangoContext PangoContext;
+
+namespace gfx {
+
+class Font;
+class PlatformFontPango;
+class Rect;
+
+// Creates and returns a PangoContext. The caller owns the context.
+PangoContext* GetPangoContext();
+
+// Returns the resolution (DPI) used by pango. A negative values means the
+// resolution hasn't been set.
+double GetPangoResolution();
+
+// Utility class to ensure that PangoFontDescription is freed.
+class ScopedPangoFontDescription {
+ public:
+ explicit ScopedPangoFontDescription(PangoFontDescription* description)
+ : description_(description) {
+ DCHECK(description);
+ }
+
+ ~ScopedPangoFontDescription() {
+ pango_font_description_free(description_);
+ }
+
+ PangoFontDescription* get() { return description_; }
+
+ private:
+ PangoFontDescription* description_;
+
+ DISALLOW_COPY_AND_ASSIGN(ScopedPangoFontDescription);
+};
+
+// Uses Pango to draw text onto |cr|. This is the public method for d
+void UI_EXPORT DrawTextOntoCairoSurface(cairo_t* cr,
+ const base::string16& text,
+ const gfx::Font& font,
+ const gfx::Rect& bounds,
+ const gfx::Rect& clip,
+ SkColor text_color,
+ int flags);
+
+// ----------------------------------------------------------------------------
+// All other methods in this file are only to be used within the ui/ directory.
+// They are shared with internal skia interfaces.
+// ----------------------------------------------------------------------------
+
+// Setup pango |layout|; set the |text|, the font description based on |font|,
+// the |width| in PANGO_SCALE for RTL locale, the base |text_direction|,
+// alignment, ellipsis, word wrapping, resolution, etc.
+void SetupPangoLayout(PangoLayout* layout,
+ const base::string16& text,
+ const gfx::Font& font,
+ int width,
+ base::i18n::TextDirection text_direction,
+ int flags);
+
+// Setup pango layout |layout| the same way as SetupPangoLayout(), except this
+// sets the font description based on |font_description|.
+void SetupPangoLayoutWithFontDescription(
+ PangoLayout* layout,
+ const base::string16& text,
+ const std::string& font_description,
+ int width,
+ base::i18n::TextDirection text_direction,
+ int flags);
+
+// Draws the |layout| (pango tuple of font, actual text, etc) onto |cr| using
+// |text_color| as the cairo pattern.
+void DrawPangoLayout(cairo_t* cr,
+ PangoLayout* layout,
+ const Font& font,
+ const gfx::Rect& bounds,
+ const gfx::Rect& text_rect,
+ SkColor text_color,
+ base::i18n::TextDirection text_direction,
+ int flags);
+
+// Draw an underline under the text using |cr|, which must already be
+// initialized with the correct source. |extra_edge_width| is added to the
+// outer edge of the line.
+void DrawPangoTextUnderline(cairo_t* cr,
+ gfx::PlatformFontPango* platform_font,
+ double extra_edge_width,
+ const Rect& text_rect);
+
+// Returns the size in pixels for the specified |pango_font|.
+size_t GetPangoFontSizeInPixels(PangoFontDescription* pango_font);
+
+// 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);
+
+} // namespace gfx
+
+#endif // UI_GFX_PANGO_UTIL_H_
diff --git a/chromium/ui/gfx/path.cc b/chromium/ui/gfx/path.cc
new file mode 100644
index 00000000000..70ed739ab8d
--- /dev/null
+++ b/chromium/ui/gfx/path.cc
@@ -0,0 +1,32 @@
+// Copyright (c) 2012 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 "ui/gfx/path.h"
+
+#include "base/logging.h"
+
+namespace gfx {
+
+Path::Path()
+ : SkPath() {
+}
+
+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(const PointF* points, size_t count) {
+ DCHECK(count > 1);
+ moveTo(SkFloatToScalar(points[0].x), SkFloatToScalar(points[0].y));
+ for (size_t i = 1; i < count; ++i)
+ lineTo(SkFloatToScalar(points[i].x), SkFloatToScalar(points[i].y));
+}
+
+Path::~Path() {
+}
+
+} // namespace gfx
diff --git a/chromium/ui/gfx/path.h b/chromium/ui/gfx/path.h
new file mode 100644
index 00000000000..1e8662921c5
--- /dev/null
+++ b/chromium/ui/gfx/path.h
@@ -0,0 +1,61 @@
+// Copyright (c) 2012 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_
+
+#include "base/basictypes.h"
+#include "third_party/skia/include/core/SkPath.h"
+#include "ui/base/ui_export.h"
+#include "ui/gfx/native_widget_types.h"
+
+namespace gfx {
+
+class UI_EXPORT Path : public SkPath {
+ public:
+ // Used by Path(Point,size_t) constructor.
+ struct Point {
+ int x;
+ int y;
+ };
+ struct PointF {
+ float x;
+ float y;
+ };
+
+ Path();
+
+ // Creates a path populated with the specified points.
+ Path(const Point* points, size_t count);
+ Path(const PointF* points, size_t count);
+
+ ~Path();
+
+#if defined(USE_AURA) || 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/chromium/ui/gfx/path_aura.cc b/chromium/ui/gfx/path_aura.cc
new file mode 100644
index 00000000000..cc68458902e
--- /dev/null
+++ b/chromium/ui/gfx/path_aura.cc
@@ -0,0 +1,46 @@
+// Copyright (c) 2012 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 "ui/gfx/path.h"
+
+#include "base/memory/scoped_ptr.h"
+#include "third_party/skia/include/core/SkRegion.h"
+
+namespace gfx {
+
+SkRegion* Path::CreateNativeRegion() const {
+ // Create a clip region that contains |this| path.
+ const SkRect bounds = getBounds();
+ SkIRect ibounds;
+ bounds.round(&ibounds);
+ SkRegion clip_region;
+ clip_region.setRect(ibounds);
+
+ SkRegion* region = new SkRegion;
+ region->setPath(*this, clip_region);
+ return region;
+}
+
+// static
+NativeRegion Path::IntersectRegions(NativeRegion r1, NativeRegion r2) {
+ SkRegion* new_region = new SkRegion;
+ new_region->op(*r1, *r2, SkRegion::kIntersect_Op);
+ return new_region;
+}
+
+// static
+NativeRegion Path::CombineRegions(NativeRegion r1, NativeRegion r2) {
+ SkRegion* new_region = new SkRegion;
+ new_region->op(*r1, *r2, SkRegion::kUnion_Op);
+ return new_region;
+}
+
+// static
+NativeRegion Path::SubtractRegion(NativeRegion r1, NativeRegion r2) {
+ SkRegion* new_region = new SkRegion;
+ new_region->op(*r1, *r2, SkRegion::kDifference_Op);
+ return new_region;
+}
+
+} // namespace gfx
diff --git a/chromium/ui/gfx/path_gtk.cc b/chromium/ui/gfx/path_gtk.cc
new file mode 100644
index 00000000000..99dad2be43b
--- /dev/null
+++ b/chromium/ui/gfx/path_gtk.cc
@@ -0,0 +1,55 @@
+// 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 "ui/gfx/path.h"
+
+#include <gdk/gdk.h>
+
+#include "base/command_line.h"
+#include "base/memory/scoped_ptr.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_ptr<SkPoint[]> points(new SkPoint[point_count]);
+ getPoints(points.get(), point_count);
+
+ scoped_ptr<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/chromium/ui/gfx/path_win.cc b/chromium/ui/gfx/path_win.cc
new file mode 100644
index 00000000000..be5d9e22ecb
--- /dev/null
+++ b/chromium/ui/gfx/path_win.cc
@@ -0,0 +1,55 @@
+// 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 "ui/gfx/path_win.h"
+
+#include "base/memory/scoped_ptr.h"
+#include "ui/gfx/path.h"
+
+namespace gfx {
+
+HRGN CreateHRGNFromSkPath(const SkPath& path) {
+ int point_count = path.getPoints(NULL, 0);
+ scoped_ptr<SkPoint[]> points(new SkPoint[point_count]);
+ path.getPoints(points.get(), point_count);
+ scoped_ptr<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);
+}
+
+// See path_aura.cc for Aura definition of these methods:
+#if !defined(USE_AURA)
+
+NativeRegion Path::CreateNativeRegion() const {
+ return CreateHRGNFromSkPath(*this);
+}
+
+// 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;
+}
+
+#endif
+
+} // namespace gfx
diff --git a/chromium/ui/gfx/path_win.h b/chromium/ui/gfx/path_win.h
new file mode 100644
index 00000000000..6df025b1bfc
--- /dev/null
+++ b/chromium/ui/gfx/path_win.h
@@ -0,0 +1,22 @@
+// Copyright (c) 2012 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_WIN_H_
+#define UI_GFX_PATH_WIN_H_
+
+#include <windows.h>
+
+#include "ui/base/ui_export.h"
+
+class SkPath;
+
+namespace gfx {
+
+// Creates a new HRGN given |path|. The caller is responsible for destroying
+// the returned region.
+UI_EXPORT HRGN CreateHRGNFromSkPath(const SkPath& path);
+
+} // namespace gfx
+
+#endif // UI_GFX_PATH_WIN_H_
diff --git a/chromium/ui/gfx/path_x11.cc b/chromium/ui/gfx/path_x11.cc
new file mode 100644
index 00000000000..2cfd10abe34
--- /dev/null
+++ b/chromium/ui/gfx/path_x11.cc
@@ -0,0 +1,27 @@
+// Copyright 2013 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 "ui/gfx/path_x11.h"
+
+#include <X11/Xutil.h>
+
+#include "base/memory/scoped_ptr.h"
+#include "ui/gfx/path.h"
+
+namespace gfx {
+
+Region CreateRegionFromSkPath(const SkPath& path) {
+ int point_count = path.getPoints(NULL, 0);
+ scoped_ptr<SkPoint[]> points(new SkPoint[point_count]);
+ path.getPoints(points.get(), point_count);
+ scoped_ptr<XPoint[]> x11_points(new XPoint[point_count]);
+ for (int i = 0; i < point_count; ++i) {
+ x11_points[i].x = SkScalarRound(points[i].fX);
+ x11_points[i].y = SkScalarRound(points[i].fY);
+ }
+
+ return XPolygonRegion(x11_points.get(), point_count, EvenOddRule);
+}
+
+} // namespace gfx
diff --git a/chromium/ui/gfx/path_x11.h b/chromium/ui/gfx/path_x11.h
new file mode 100644
index 00000000000..df3ad623d33
--- /dev/null
+++ b/chromium/ui/gfx/path_x11.h
@@ -0,0 +1,23 @@
+// Copyright 2013 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_X11_H_
+#define UI_GFX_PATH_X11_H_
+
+#include <X11/Xlib.h>
+#include <X11/Xregion.h>
+
+#include "ui/base/ui_export.h"
+
+class SkPath;
+
+namespace gfx {
+
+// Creates a new REGION given |path|. The caller is responsible for destroying
+// the returned region.
+UI_EXPORT REGION* CreateRegionFromSkPath(const SkPath& path);
+
+} // namespace gfx
+
+#endif // UI_GFX_PATH_X11_H_
diff --git a/chromium/ui/gfx/platform_font.h b/chromium/ui/gfx/platform_font.h
new file mode 100644
index 00000000000..c5f71202356
--- /dev/null
+++ b/chromium/ui/gfx/platform_font.h
@@ -0,0 +1,79 @@
+// Copyright (c) 2012 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_H_
+#define UI_GFX_PLATFORM_FONT_H_
+
+#include <string>
+
+#include "base/memory/ref_counted.h"
+#include "base/strings/string16.h"
+#include "ui/base/ui_export.h"
+#include "ui/gfx/native_widget_types.h"
+
+namespace gfx {
+
+class Font;
+
+class UI_EXPORT PlatformFont : public base::RefCounted<PlatformFont> {
+ public:
+ // Creates an appropriate PlatformFont implementation.
+ static PlatformFont* CreateDefault();
+ static PlatformFont* CreateFromNativeFont(NativeFont native_font);
+ // Creates a PlatformFont implementation with the specified |font_name|
+ // (encoded in UTF-8) and |font_size| in pixels.
+ static PlatformFont* CreateFromNameAndSize(const std::string& font_name,
+ int font_size);
+
+ // Returns a new Font derived from the existing font.
+ // |size_delta| is the size in pixels to add to the current font.
+ // The style parameter specifies the new style for the font, and is a
+ // bitmask of the values: BOLD, ITALIC and UNDERLINE.
+ 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 base::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 in UTF-8.
+ virtual std::string 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_H_
+
diff --git a/chromium/ui/gfx/platform_font_android.cc b/chromium/ui/gfx/platform_font_android.cc
new file mode 100644
index 00000000000..332a0788ea5
--- /dev/null
+++ b/chromium/ui/gfx/platform_font_android.cc
@@ -0,0 +1,30 @@
+// Copyright (c) 2012 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 "ui/gfx/platform_font.h"
+
+#include "base/logging.h"
+
+namespace gfx {
+
+// static
+PlatformFont* PlatformFont::CreateDefault() {
+ NOTIMPLEMENTED();
+ return NULL;
+}
+
+// static
+PlatformFont* PlatformFont::CreateFromNativeFont(NativeFont native_font) {
+ NOTIMPLEMENTED();
+ return NULL;
+}
+
+// static
+PlatformFont* PlatformFont::CreateFromNameAndSize(const std::string& font_name,
+ int font_size) {
+ NOTIMPLEMENTED();
+ return NULL;
+}
+
+} // namespace gfx
diff --git a/chromium/ui/gfx/platform_font_ios.h b/chromium/ui/gfx/platform_font_ios.h
new file mode 100644
index 00000000000..789b591415d
--- /dev/null
+++ b/chromium/ui/gfx/platform_font_ios.h
@@ -0,0 +1,57 @@
+// Copyright (c) 2012 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_IOS_H_
+#define UI_GFX_PLATFORM_FONT_IOS_H_
+
+#include "ui/gfx/platform_font.h"
+
+namespace gfx {
+
+class PlatformFontIOS : public PlatformFont {
+ public:
+ PlatformFontIOS();
+ explicit PlatformFontIOS(NativeFont native_font);
+ PlatformFontIOS(const std::string& font_name,
+ int font_size);
+
+ // Overridden from PlatformFont:
+ virtual Font DeriveFont(int size_delta, int style) const OVERRIDE;
+ virtual int GetHeight() const OVERRIDE;
+ virtual int GetBaseline() const OVERRIDE;
+ virtual int GetAverageCharacterWidth() const OVERRIDE;
+ virtual int GetStringWidth(const base::string16& text) const OVERRIDE;
+ virtual int GetExpectedTextWidth(int length) const OVERRIDE;
+ virtual int GetStyle() const OVERRIDE;
+ virtual std::string GetFontName() const OVERRIDE;
+ virtual int GetFontSize() const OVERRIDE;
+ virtual NativeFont GetNativeFont() const OVERRIDE;
+
+ private:
+ PlatformFontIOS(const std::string& font_name, int font_size, int style);
+ virtual ~PlatformFontIOS() {}
+
+ // Initialize the object with the specified parameters.
+ void InitWithNameSizeAndStyle(const std::string& font_name,
+ int font_size,
+ int style);
+
+ // Calculate and cache the font metrics.
+ void CalculateMetrics();
+
+ std::string font_name_;
+ int font_size_;
+ int style_;
+
+ // Cached metrics, generated at construction.
+ int height_;
+ int ascent_;
+ int average_width_;
+
+ DISALLOW_COPY_AND_ASSIGN(PlatformFontIOS);
+};
+
+} // namespace gfx
+
+#endif // UI_GFX_PLATFORM_FONT_IOS_H_
diff --git a/chromium/ui/gfx/platform_font_ios.mm b/chromium/ui/gfx/platform_font_ios.mm
new file mode 100644
index 00000000000..cfc69cf4964
--- /dev/null
+++ b/chromium/ui/gfx/platform_font_ios.mm
@@ -0,0 +1,128 @@
+// Copyright (c) 2012 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 "ui/gfx/platform_font_ios.h"
+
+#import <UIKit/UIKit.h>
+
+#include "base/basictypes.h"
+#include "base/strings/sys_string_conversions.h"
+#include "base/strings/utf_string_conversions.h"
+#include "ui/gfx/font.h"
+
+namespace gfx {
+
+////////////////////////////////////////////////////////////////////////////////
+// PlatformFontIOS, public:
+
+PlatformFontIOS::PlatformFontIOS() {
+ font_size_ = [UIFont systemFontSize];
+ style_ = gfx::Font::NORMAL;
+ UIFont* system_font = [UIFont systemFontOfSize:font_size_];
+ font_name_ = base::SysNSStringToUTF8([system_font fontName]);
+ CalculateMetrics();
+}
+
+PlatformFontIOS::PlatformFontIOS(NativeFont native_font) {
+ std::string font_name = base::SysNSStringToUTF8([native_font fontName]);
+ InitWithNameSizeAndStyle(font_name,
+ [native_font pointSize],
+ gfx::Font::NORMAL);
+}
+
+PlatformFontIOS::PlatformFontIOS(const std::string& font_name,
+ int font_size) {
+ InitWithNameSizeAndStyle(font_name, font_size, gfx::Font::NORMAL);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// PlatformFontIOS, PlatformFont implementation:
+
+Font PlatformFontIOS::DeriveFont(int size_delta, int style) const {
+ return Font(new PlatformFontIOS(font_name_, font_size_ + size_delta, style));
+}
+
+int PlatformFontIOS::GetHeight() const {
+ return height_;
+}
+
+int PlatformFontIOS::GetBaseline() const {
+ return ascent_;
+}
+
+int PlatformFontIOS::GetAverageCharacterWidth() const {
+ return average_width_;
+}
+
+int PlatformFontIOS::GetStringWidth(const base::string16& text) const {
+ NSString* ns_text = base::SysUTF16ToNSString(text);
+ return [ns_text sizeWithFont:GetNativeFont()].width;
+}
+
+int PlatformFontIOS::GetExpectedTextWidth(int length) const {
+ return length * average_width_;
+}
+
+int PlatformFontIOS::GetStyle() const {
+ return style_;
+}
+
+std::string PlatformFontIOS::GetFontName() const {
+ return font_name_;
+}
+
+int PlatformFontIOS::GetFontSize() const {
+ return font_size_;
+}
+
+NativeFont PlatformFontIOS::GetNativeFont() const {
+ return [UIFont fontWithName:base::SysUTF8ToNSString(font_name_)
+ size:font_size_];
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// PlatformFontIOS, private:
+
+PlatformFontIOS::PlatformFontIOS(const std::string& font_name,
+ int font_size,
+ int style) {
+ InitWithNameSizeAndStyle(font_name, font_size, style);
+}
+
+void PlatformFontIOS::InitWithNameSizeAndStyle(const std::string& font_name,
+ int font_size,
+ int style) {
+ font_name_ = font_name;
+ font_size_ = font_size;
+ style_ = style;
+ CalculateMetrics();
+}
+
+void PlatformFontIOS::CalculateMetrics() {
+ UIFont* font = GetNativeFont();
+ height_ = font.lineHeight;
+ ascent_ = font.ascender;
+ average_width_ = [@"x" sizeWithFont:font].width;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// PlatformFont, public:
+
+// static
+PlatformFont* PlatformFont::CreateDefault() {
+ return new PlatformFontIOS;
+}
+
+// static
+PlatformFont* PlatformFont::CreateFromNativeFont(NativeFont native_font) {
+ return new PlatformFontIOS(native_font);
+}
+
+// static
+PlatformFont* PlatformFont::CreateFromNameAndSize(const std::string& font_name,
+ int font_size) {
+ return new PlatformFontIOS(font_name, font_size);
+}
+
+} // namespace gfx
diff --git a/chromium/ui/gfx/platform_font_mac.h b/chromium/ui/gfx/platform_font_mac.h
new file mode 100644
index 00000000000..13c8677c228
--- /dev/null
+++ b/chromium/ui/gfx/platform_font_mac.h
@@ -0,0 +1,58 @@
+// Copyright (c) 2012 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_H_
+#define UI_GFX_PLATFORM_FONT_MAC_H_
+
+#include "base/compiler_specific.h"
+#include "ui/gfx/platform_font.h"
+
+namespace gfx {
+
+class PlatformFontMac : public PlatformFont {
+ public:
+ PlatformFontMac();
+ explicit PlatformFontMac(NativeFont native_font);
+ PlatformFontMac(const std::string& font_name,
+ int font_size);
+
+ // Overridden from PlatformFont:
+ virtual Font DeriveFont(int size_delta, int style) const OVERRIDE;
+ virtual int GetHeight() const OVERRIDE;
+ virtual int GetBaseline() const OVERRIDE;
+ virtual int GetAverageCharacterWidth() const OVERRIDE;
+ virtual int GetStringWidth(const base::string16& text) const OVERRIDE;
+ virtual int GetExpectedTextWidth(int length) const OVERRIDE;
+ virtual int GetStyle() const OVERRIDE;
+ virtual std::string GetFontName() const OVERRIDE;
+ virtual int GetFontSize() const OVERRIDE;
+ virtual NativeFont GetNativeFont() const OVERRIDE;
+
+ private:
+ PlatformFontMac(const std::string& font_name, int font_size, int style);
+ virtual ~PlatformFontMac() {}
+
+ // Initialize the object with the specified parameters.
+ void InitWithNameSizeAndStyle(const std::string& font_name,
+ int font_size,
+ int style);
+
+ // Calculate and cache the font metrics.
+ void CalculateMetrics();
+
+ std::string font_name_;
+ int font_size_;
+ int style_;
+
+ // Cached metrics, generated at construction.
+ int height_;
+ int ascent_;
+ int average_width_;
+
+ DISALLOW_COPY_AND_ASSIGN(PlatformFontMac);
+};
+
+} // namespace gfx
+
+#endif // UI_GFX_PLATFORM_FONT_MAC_H_
diff --git a/chromium/ui/gfx/platform_font_mac.mm b/chromium/ui/gfx/platform_font_mac.mm
new file mode 100644
index 00000000000..bf0c0bd54ac
--- /dev/null
+++ b/chromium/ui/gfx/platform_font_mac.mm
@@ -0,0 +1,155 @@
+// Copyright (c) 2012 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 "ui/gfx/platform_font_mac.h"
+
+#include <Cocoa/Cocoa.h>
+
+#include "base/basictypes.h"
+#include "base/mac/scoped_nsobject.h"
+#include "base/strings/sys_string_conversions.h"
+#include "base/strings/utf_string_conversions.h"
+#include "ui/gfx/canvas.h"
+#include "ui/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::SysNSStringToUTF8([system_font fontName]);
+ CalculateMetrics();
+}
+
+PlatformFontMac::PlatformFontMac(NativeFont native_font) {
+ int style = 0;
+ NSFontSymbolicTraits traits = [[native_font fontDescriptor] symbolicTraits];
+ if (traits & NSFontItalicTrait)
+ style |= Font::ITALIC;
+ if (traits & NSFontBoldTrait)
+ style |= Font::BOLD;
+
+ InitWithNameSizeAndStyle(base::SysNSStringToUTF8([native_font familyName]),
+ [native_font pointSize],
+ style);
+}
+
+PlatformFontMac::PlatformFontMac(const std::string& 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 base::string16& text) const {
+ return Canvas::GetStringWidth(text,
+ Font(const_cast<PlatformFontMac*>(this)));
+}
+
+int PlatformFontMac::GetExpectedTextWidth(int length) const {
+ return length * average_width_;
+}
+
+int PlatformFontMac::GetStyle() const {
+ return style_;
+}
+
+std::string PlatformFontMac::GetFontName() const {
+ return font_name_;
+}
+
+int PlatformFontMac::GetFontSize() const {
+ return font_size_;
+}
+
+NativeFont PlatformFontMac::GetNativeFont() const {
+ // 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.
+ NSFont* font = [NSFont fontWithName:base::SysUTF8ToNSString(font_name_)
+ size:font_size_];
+
+ if (style_ & Font::BOLD) {
+ font = [[NSFontManager sharedFontManager] convertFont:font
+ toHaveTrait:NSBoldFontMask];
+ }
+ if (style_ & Font::ITALIC) {
+ font = [[NSFontManager sharedFontManager] convertFont:font
+ toHaveTrait:NSItalicFontMask];
+ }
+ // Mac doesn't support underline as a font trait, just drop it. Underlines
+ // can instead be added as an attribute on an NSAttributedString.
+
+ return font;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// PlatformFontMac, private:
+
+PlatformFontMac::PlatformFontMac(const std::string& font_name,
+ int font_size,
+ int style) {
+ InitWithNameSizeAndStyle(font_name, font_size, style);
+}
+
+void PlatformFontMac::InitWithNameSizeAndStyle(const std::string& font_name,
+ int font_size,
+ int style) {
+ font_name_ = font_name;
+ font_size_ = font_size;
+ style_ = style;
+ CalculateMetrics();
+}
+
+void PlatformFontMac::CalculateMetrics() {
+ NSFont* font = GetNativeFont();
+ base::scoped_nsobject<NSLayoutManager> layout_manager(
+ [[NSLayoutManager alloc] init]);
+ height_ = [layout_manager defaultLineHeightForFont:font];
+ ascent_ = [font ascender];
+ average_width_ =
+ NSWidth([font boundingRectForGlyph:[font glyphWithName:@"x"]]);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// PlatformFont, public:
+
+// static
+PlatformFont* PlatformFont::CreateDefault() {
+ return new PlatformFontMac;
+}
+
+// static
+PlatformFont* PlatformFont::CreateFromNativeFont(NativeFont native_font) {
+ return new PlatformFontMac(native_font);
+}
+
+// static
+PlatformFont* PlatformFont::CreateFromNameAndSize(const std::string& font_name,
+ int font_size) {
+ return new PlatformFontMac(font_name, font_size);
+}
+
+} // namespace gfx
+
diff --git a/chromium/ui/gfx/platform_font_mac_unittest.mm b/chromium/ui/gfx/platform_font_mac_unittest.mm
new file mode 100644
index 00000000000..088d4a9bd2d
--- /dev/null
+++ b/chromium/ui/gfx/platform_font_mac_unittest.mm
@@ -0,0 +1,56 @@
+// Copyright (c) 2012 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 <Cocoa/Cocoa.h>
+
+#include "ui/gfx/font.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+TEST(PlatformFontMacTest, DeriveFont) {
+ // Use a base font that support all traits.
+ gfx::Font base_font("Helvetica", 13);
+
+ // Bold
+ gfx::Font bold_font(base_font.DeriveFont(0, gfx::Font::BOLD));
+ NSFontTraitMask traits = [[NSFontManager sharedFontManager]
+ traitsOfFont:bold_font.GetNativeFont()];
+ EXPECT_EQ(NSBoldFontMask, traits);
+
+ // Italic
+ gfx::Font italic_font(base_font.DeriveFont(0, gfx::Font::ITALIC));
+ traits = [[NSFontManager sharedFontManager]
+ traitsOfFont:italic_font.GetNativeFont()];
+ EXPECT_EQ(NSItalicFontMask, traits);
+
+ // Bold italic
+ gfx::Font bold_italic_font(base_font.DeriveFont(0, gfx::Font::BOLD |
+ gfx::Font::ITALIC));
+ traits = [[NSFontManager sharedFontManager]
+ traitsOfFont:bold_italic_font.GetNativeFont()];
+ EXPECT_EQ(static_cast<NSFontTraitMask>(NSBoldFontMask | NSItalicFontMask),
+ traits);
+}
+
+TEST(PlatformFontMacTest, ConstructFromNativeFont) {
+ gfx::Font normal_font([NSFont fontWithName:@"Helvetica" size:12]);
+ EXPECT_EQ(12, normal_font.GetFontSize());
+ EXPECT_EQ("Helvetica", normal_font.GetFontName());
+ EXPECT_EQ(gfx::Font::NORMAL, normal_font.GetStyle());
+
+ gfx::Font bold_font([NSFont fontWithName:@"Helvetica-Bold" size:14]);
+ EXPECT_EQ(14, bold_font.GetFontSize());
+ EXPECT_EQ("Helvetica", bold_font.GetFontName());
+ EXPECT_EQ(gfx::Font::BOLD, bold_font.GetStyle());
+
+ gfx::Font italic_font([NSFont fontWithName:@"Helvetica-Oblique" size:14]);
+ EXPECT_EQ(14, italic_font.GetFontSize());
+ EXPECT_EQ("Helvetica", italic_font.GetFontName());
+ EXPECT_EQ(gfx::Font::ITALIC, italic_font.GetStyle());
+
+ gfx::Font bold_italic_font(
+ [NSFont fontWithName:@"Helvetica-BoldOblique" size:14]);
+ EXPECT_EQ(14, bold_italic_font.GetFontSize());
+ EXPECT_EQ("Helvetica", bold_italic_font.GetFontName());
+ EXPECT_EQ(gfx::Font::BOLD | gfx::Font::ITALIC, bold_italic_font.GetStyle());
+}
diff --git a/chromium/ui/gfx/platform_font_pango.cc b/chromium/ui/gfx/platform_font_pango.cc
new file mode 100644
index 00000000000..b950d23aac1
--- /dev/null
+++ b/chromium/ui/gfx/platform_font_pango.cc
@@ -0,0 +1,393 @@
+// Copyright (c) 2012 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 "ui/gfx/platform_font_pango.h"
+
+#include <fontconfig/fontconfig.h>
+#include <pango/pango.h>
+
+#include <algorithm>
+#include <string>
+
+#include "base/logging.h"
+#include "base/strings/string_piece.h"
+#include "base/strings/string_split.h"
+#include "base/strings/utf_string_conversions.h"
+#include "grit/app_locale_settings.h"
+#include "third_party/skia/include/core/SkPaint.h"
+#include "third_party/skia/include/core/SkTypeface.h"
+#include "ui/base/l10n/l10n_util.h"
+#include "ui/gfx/canvas.h"
+#include "ui/gfx/font.h"
+#include "ui/gfx/pango_util.h"
+
+#if defined(TOOLKIT_GTK)
+#include <gdk/gdk.h>
+#include <gtk/gtk.h>
+#endif
+
+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 available font family that best (in FontConfig's eyes) matches
+// the supplied list of family names.
+std::string FindBestMatchFontFamilyName(
+ const std::vector<std::string>& family_names) {
+ FcPattern* pattern = FcPatternCreate();
+ for (std::vector<std::string>::const_iterator it = family_names.begin();
+ it != family_names.end(); ++it) {
+ FcValue fcvalue;
+ fcvalue.type = FcTypeString;
+ fcvalue.u.s = reinterpret_cast<const FcChar8*>(it->c_str());
+ FcPatternAdd(pattern, FC_FAMILY, fcvalue, FcTrue /* append */);
+ }
+
+ FcConfigSubstitute(0, pattern, FcMatchPattern);
+ FcDefaultSubstitute(pattern);
+ FcResult result;
+ FcPattern* match = FcFontMatch(0, pattern, &result);
+ DCHECK(match) << "Could not find font";
+ FcChar8* match_family = NULL;
+ FcPatternGetString(match, FC_FAMILY, 0, &match_family);
+ std::string font_family(reinterpret_cast<char*>(match_family));
+ FcPatternDestroy(pattern);
+ FcPatternDestroy(match);
+ return font_family;
+}
+
+// Returns a Pango font description (suitable for parsing by
+// pango_font_description_from_string()) for the default UI font.
+std::string GetDefaultFont() {
+#if !defined(TOOLKIT_GTK)
+#if defined(OS_CHROMEOS)
+ return l10n_util::GetStringUTF8(IDS_UI_FONT_FAMILY_CROS);
+#else
+ return "sans 10";
+#endif // defined(OS_CHROMEOS)
+#else
+ 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.";
+
+ std::string default_font = std::string(font_name);
+ g_free(font_name);
+ return default_font;
+#endif // !defined(TOOLKIT_GTK)
+}
+
+} // namespace
+
+namespace gfx {
+
+Font* PlatformFontPango::default_font_ = NULL;
+
+////////////////////////////////////////////////////////////////////////////////
+// PlatformFontPango, public:
+
+PlatformFontPango::PlatformFontPango() {
+ if (default_font_ == NULL) {
+ std::string font_name = GetDefaultFont();
+
+ ScopedPangoFontDescription desc(
+ pango_font_description_from_string(font_name.c_str()));
+ default_font_ = new Font(desc.get());
+
+ DCHECK(default_font_);
+ }
+
+ InitFromPlatformFont(
+ static_cast<PlatformFontPango*>(default_font_->platform_font()));
+}
+
+PlatformFontPango::PlatformFontPango(NativeFont native_font) {
+ std::vector<std::string> family_names;
+ base::SplitString(pango_font_description_get_family(native_font), ',',
+ &family_names);
+ std::string font_family = FindBestMatchFontFamilyName(family_names);
+ InitWithNameAndSize(font_family, gfx::GetPangoFontSizeInPixels(native_font));
+
+ 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;
+}
+
+PlatformFontPango::PlatformFontPango(const std::string& font_name,
+ int font_size) {
+ InitWithNameAndSize(font_name, font_size);
+}
+
+double PlatformFontPango::underline_position() const {
+ const_cast<PlatformFontPango*>(this)->InitPangoMetrics();
+ return underline_position_pixels_;
+}
+
+double PlatformFontPango::underline_thickness() const {
+ const_cast<PlatformFontPango*>(this)->InitPangoMetrics();
+ return underline_thickness_pixels_;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// PlatformFontPango, PlatformFont implementation:
+
+// static
+void PlatformFontPango::ReloadDefaultFont() {
+ delete default_font_;
+ default_font_ = NULL;
+}
+
+Font PlatformFontPango::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 PlatformFontPango(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;
+
+ skia::RefPtr<SkTypeface> typeface = skia::AdoptRef(
+ SkTypeface::CreateFromName(
+ font_family_.c_str(),
+ static_cast<SkTypeface::Style>(skstyle)));
+
+ return Font(new PlatformFontPango(typeface,
+ font_family_,
+ font_size_pixels_ + size_delta,
+ style));
+}
+
+int PlatformFontPango::GetHeight() const {
+ return height_pixels_;
+}
+
+int PlatformFontPango::GetBaseline() const {
+ return ascent_pixels_;
+}
+
+int PlatformFontPango::GetAverageCharacterWidth() const {
+ const_cast<PlatformFontPango*>(this)->InitPangoMetrics();
+ return SkScalarRound(average_width_pixels_);
+}
+
+int PlatformFontPango::GetStringWidth(const base::string16& text) const {
+ return Canvas::GetStringWidth(text,
+ Font(const_cast<PlatformFontPango*>(this)));
+}
+
+int PlatformFontPango::GetExpectedTextWidth(int length) const {
+ double char_width = const_cast<PlatformFontPango*>(this)->GetAverageWidth();
+ return round(static_cast<float>(length) * char_width);
+}
+
+int PlatformFontPango::GetStyle() const {
+ return style_;
+}
+
+std::string PlatformFontPango::GetFontName() const {
+ return font_family_;
+}
+
+int PlatformFontPango::GetFontSize() const {
+ return font_size_pixels_;
+}
+
+NativeFont PlatformFontPango::GetNativeFont() const {
+ PangoFontDescription* pfd = pango_font_description_new();
+ pango_font_description_set_family(pfd, 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::UNDERLINE:
+ // TODO(deanm): How to do underline? Where do we use it? Probably have
+ // to paint it ourselves, see pango_font_metrics_get_underline_position.
+ break;
+ }
+
+ return pfd;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// PlatformFontPango, private:
+
+PlatformFontPango::PlatformFontPango(const skia::RefPtr<SkTypeface>& typeface,
+ const std::string& name,
+ int size,
+ int style) {
+ InitWithTypefaceNameSizeAndStyle(typeface, name, size, style);
+}
+
+PlatformFontPango::~PlatformFontPango() {}
+
+void PlatformFontPango::InitWithNameAndSize(const std::string& font_name,
+ int font_size) {
+ DCHECK_GT(font_size, 0);
+ std::string fallback;
+
+ skia::RefPtr<SkTypeface> typeface = skia::AdoptRef(
+ SkTypeface::CreateFromName(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 = skia::AdoptRef(
+ SkTypeface::CreateFromName(
+ kFallbackFontFamilyName, SkTypeface::kNormal));
+ CHECK(typeface) << "Could not find any font: "
+ << font_name
+ << ", " << kFallbackFontFamilyName;
+ fallback = kFallbackFontFamilyName;
+ }
+
+ InitWithTypefaceNameSizeAndStyle(typeface,
+ fallback.empty() ? font_name : fallback,
+ font_size,
+ gfx::Font::NORMAL);
+}
+
+void PlatformFontPango::InitWithTypefaceNameSizeAndStyle(
+ const skia::RefPtr<SkTypeface>& typeface,
+ const std::string& font_family,
+ int font_size,
+ int style) {
+ typeface_ = typeface;
+ 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 PlatformFontPango::InitFromPlatformFont(const PlatformFontPango* other) {
+ typeface_ = other->typeface_;
+ 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 PlatformFontPango::PaintSetup(SkPaint* paint) const {
+ paint->setAntiAlias(false);
+ paint->setSubpixelText(false);
+ paint->setTextSize(font_size_pixels_);
+ paint->setTypeface(typeface_.get());
+ paint->setFakeBoldText((gfx::Font::BOLD & style_) && !typeface_->isBold());
+ paint->setTextSkewX((gfx::Font::ITALIC & style_) && !typeface_->isItalic() ?
+ -SK_Scalar1/4 : 0);
+}
+
+void PlatformFontPango::InitPangoMetrics() {
+ if (!pango_metrics_inited_) {
+ pango_metrics_inited_ = true;
+ ScopedPangoFontDescription pango_desc(GetNativeFont());
+ PangoFontMetrics* pango_metrics = GetPangoFontMetrics(pango_desc.get());
+
+ 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).
+ const 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.
+ const int text_width_pixels = GetStringWidth(
+ ASCIIToUTF16("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"));
+ const double dialog_units_pixels = (text_width_pixels / 26 + 1) / 2;
+ average_width_pixels_ = std::min(pango_width_pixels, dialog_units_pixels);
+ }
+}
+
+
+double PlatformFontPango::GetAverageWidth() const {
+ const_cast<PlatformFontPango*>(this)->InitPangoMetrics();
+ return average_width_pixels_;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// PlatformFont, public:
+
+// static
+PlatformFont* PlatformFont::CreateDefault() {
+ return new PlatformFontPango;
+}
+
+// static
+PlatformFont* PlatformFont::CreateFromNativeFont(NativeFont native_font) {
+ return new PlatformFontPango(native_font);
+}
+
+// static
+PlatformFont* PlatformFont::CreateFromNameAndSize(const std::string& font_name,
+ int font_size) {
+ return new PlatformFontPango(font_name, font_size);
+}
+
+} // namespace gfx
diff --git a/chromium/ui/gfx/platform_font_pango.h b/chromium/ui/gfx/platform_font_pango.h
new file mode 100644
index 00000000000..2fce305a1b7
--- /dev/null
+++ b/chromium/ui/gfx/platform_font_pango.h
@@ -0,0 +1,110 @@
+// Copyright (c) 2012 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_PANGO_H_
+#define UI_GFX_PLATFORM_FONT_PANGO_H_
+
+#include "base/compiler_specific.h"
+#include "base/memory/scoped_ptr.h"
+#include "skia/ext/refptr.h"
+#include "third_party/skia/include/core/SkRefCnt.h"
+#include "ui/gfx/platform_font.h"
+
+class SkTypeface;
+class SkPaint;
+
+namespace gfx {
+
+class UI_EXPORT PlatformFontPango : public PlatformFont {
+ public:
+ PlatformFontPango();
+ explicit PlatformFontPango(NativeFont native_font);
+ PlatformFontPango(const std::string& 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);
+
+ // Resets and reloads the cached system font used by the default constructor.
+ // This function is useful when the system font has changed, for example, when
+ // the locale has changed.
+ static void ReloadDefaultFont();
+
+ // 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 OVERRIDE;
+ virtual int GetHeight() const OVERRIDE;
+ virtual int GetBaseline() const OVERRIDE;
+ virtual int GetAverageCharacterWidth() const OVERRIDE;
+ virtual int GetStringWidth(const base::string16& text) const OVERRIDE;
+ virtual int GetExpectedTextWidth(int length) const OVERRIDE;
+ virtual int GetStyle() const OVERRIDE;
+ virtual std::string GetFontName() const OVERRIDE;
+ virtual int GetFontSize() const OVERRIDE;
+ virtual NativeFont GetNativeFont() const OVERRIDE;
+
+ private:
+ // Create a new instance of this object with the specified properties. Called
+ // from DeriveFont.
+ PlatformFontPango(const skia::RefPtr<SkTypeface>& typeface,
+ const std::string& name,
+ int size,
+ int style);
+ virtual ~PlatformFontPango();
+
+ // Initialize this object.
+ void InitWithNameAndSize(const std::string& font_name, int font_size);
+ void InitWithTypefaceNameSizeAndStyle(
+ const skia::RefPtr<SkTypeface>& typeface,
+ const std::string& name,
+ int size,
+ int style);
+ void InitFromPlatformFont(const PlatformFontPango* 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;
+
+ skia::RefPtr<SkTypeface> typeface_;
+
+ // Additional information about the face
+ // Skia actually expects a family name and not a font name.
+ std::string 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_;
+
+ DISALLOW_COPY_AND_ASSIGN(PlatformFontPango);
+};
+
+} // namespace gfx
+
+#endif // UI_GFX_PLATFORM_FONT_PANGO_H_
diff --git a/chromium/ui/gfx/platform_font_pango_unittest.cc b/chromium/ui/gfx/platform_font_pango_unittest.cc
new file mode 100644
index 00000000000..228c66b0201
--- /dev/null
+++ b/chromium/ui/gfx/platform_font_pango_unittest.cc
@@ -0,0 +1,48 @@
+// Copyright (c) 2012 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 "ui/gfx/platform_font_pango.h"
+
+#include <cairo/cairo.h>
+#include <fontconfig/fontconfig.h>
+#include <glib-object.h>
+#include <pango/pangocairo.h>
+#include <pango/pangofc-fontmap.h>
+
+#include <string>
+
+#include "base/memory/ref_counted.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "ui/gfx/pango_util.h"
+
+namespace gfx {
+
+// Test that PlatformFontPango is able to cope with PangoFontDescriptions
+// containing multiple font families. The first family should be preferred.
+TEST(PlatformFontPangoTest, FamilyList) {
+ // Needed for GLib versions prior to 2.36.
+ g_type_init();
+
+ ScopedPangoFontDescription desc(
+ pango_font_description_from_string("Arial,Times New Roman, 13px"));
+ scoped_refptr<gfx::PlatformFontPango> font(
+ new gfx::PlatformFontPango(desc.get()));
+ EXPECT_EQ("Arial", font->GetFontName());
+ EXPECT_EQ(13, font->GetFontSize());
+
+ ScopedPangoFontDescription desc2(
+ pango_font_description_from_string("Times New Roman,Arial, 15px"));
+ scoped_refptr<gfx::PlatformFontPango> font2(
+ new gfx::PlatformFontPango(desc2.get()));
+ EXPECT_EQ("Times New Roman", font2->GetFontName());
+ EXPECT_EQ(15, font2->GetFontSize());
+
+ // Free memory allocated by FontConfig (http://crbug.com/114750).
+ pango_fc_font_map_cache_clear(
+ PANGO_FC_FONT_MAP(pango_cairo_font_map_get_default()));
+ cairo_debug_reset_static_data();
+ FcFini();
+}
+
+} // namespace gfx
diff --git a/chromium/ui/gfx/platform_font_win.cc b/chromium/ui/gfx/platform_font_win.cc
new file mode 100644
index 00000000000..4a792f6ad86
--- /dev/null
+++ b/chromium/ui/gfx/platform_font_win.cc
@@ -0,0 +1,324 @@
+// Copyright (c) 2012 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 "ui/gfx/platform_font_win.h"
+
+#include <windows.h>
+#include <math.h>
+
+#include <algorithm>
+#include <string>
+
+#include "base/logging.h"
+#include "base/strings/string_util.h"
+#include "base/strings/sys_string_conversions.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/win/scoped_hdc.h"
+#include "base/win/scoped_select_object.h"
+#include "base/win/win_util.h"
+#include "ui/base/win/scoped_set_map_mode.h"
+#include "ui/gfx/canvas.h"
+#include "ui/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 the minimum font size, using the minimum size callback, if set.
+int GetMinimumFontSize() {
+ int min_font_size = 0;
+ if (gfx::PlatformFontWin::get_minimum_font_size_callback)
+ min_font_size = gfx::PlatformFontWin::get_minimum_font_size_callback();
+ return min_font_size;
+}
+
+// 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;
+ }
+ const int min_font_size = GetMinimumFontSize();
+ // 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;
+ }
+}
+
+// Sets style properties on |font_info| based on |font_style|.
+void SetLogFontStyle(int font_style, LOGFONT* font_info) {
+ font_info->lfUnderline = (font_style & gfx::Font::UNDERLINE) != 0;
+ font_info->lfItalic = (font_style & gfx::Font::ITALIC) != 0;
+ font_info->lfWeight = (font_style & gfx::Font::BOLD) ? FW_BOLD : FW_NORMAL;
+}
+
+} // 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(NativeFont native_font) {
+ InitWithCopyOfHFONT(native_font);
+}
+
+PlatformFontWin::PlatformFontWin(const std::string& font_name,
+ int font_size) {
+ InitWithFontNameAndSize(font_name, font_size);
+}
+
+Font PlatformFontWin::DeriveFontWithHeight(int height, int style) {
+ DCHECK_GE(height, 0);
+ if (GetHeight() == height && GetStyle() == style)
+ return Font(this);
+
+ // CreateFontIndirect() doesn't return the largest size for the given height
+ // when decreasing the height. Iterate to find it.
+ if (GetHeight() > height) {
+ const int min_font_size = GetMinimumFontSize();
+ Font font = DeriveFont(-1, style);
+ int font_height = font.GetHeight();
+ int font_size = font.GetFontSize();
+ while (font_height > height && font_size != min_font_size) {
+ font = font.DeriveFont(-1, style);
+ if (font_height == font.GetHeight() && font_size == font.GetFontSize())
+ break;
+ font_height = font.GetHeight();
+ font_size = font.GetFontSize();
+ }
+ return font;
+ }
+
+ LOGFONT font_info;
+ GetObject(GetNativeFont(), sizeof(LOGFONT), &font_info);
+ font_info.lfHeight = height;
+ SetLogFontStyle(style, &font_info);
+
+ HFONT hfont = CreateFontIndirect(&font_info);
+ return Font(new PlatformFontWin(CreateHFontRef(hfont)));
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// PlatformFontWin, PlatformFont implementation:
+
+Font PlatformFontWin::DeriveFont(int size_delta, int style) const {
+ LOGFONT font_info;
+ GetObject(GetNativeFont(), sizeof(LOGFONT), &font_info);
+ const int requested_font_size = font_ref_->requested_font_size();
+ font_info.lfHeight = AdjustFontSize(-requested_font_size, size_delta);
+ SetLogFontStyle(style, &font_info);
+
+ 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 base::string16& text) const {
+ return Canvas::GetStringWidth(text,
+ Font(const_cast<PlatformFontWin*>(this)));
+}
+
+int PlatformFontWin::GetExpectedTextWidth(int length) const {
+ return length * std::min(font_ref_->GetDluBaseX(),
+ GetAverageCharacterWidth());
+}
+
+int PlatformFontWin::GetStyle() const {
+ return font_ref_->style();
+}
+
+std::string PlatformFontWin::GetFontName() const {
+ return font_ref_->font_name();
+}
+
+std::string PlatformFontWin::GetLocalizedFontName() const {
+ base::win::ScopedCreateDC memory_dc(CreateCompatibleDC(NULL));
+ if (!memory_dc.Get())
+ return GetFontName();
+
+ // When a font has a localized name for a language matching the system
+ // locale, GetTextFace() returns the localized name.
+ base::win::ScopedSelectObject font(memory_dc, font_ref_->hfont());
+ wchar_t localized_font_name[LF_FACESIZE];
+ int length = GetTextFace(memory_dc, arraysize(localized_font_name),
+ &localized_font_name[0]);
+ if (length <= 0)
+ return GetFontName();
+ return base::SysWideToUTF8(localized_font_name);
+}
+
+int PlatformFontWin::GetFontSize() const {
+ return font_ref_->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 std::string& font_name,
+ int font_size) {
+ HFONT hf = ::CreateFont(-font_size, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ UTF8ToUTF16(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;
+
+ {
+ base::win::ScopedGetDC screen_dc(NULL);
+ base::win::ScopedSelectObject scoped_font(screen_dc, font);
+ ui::ScopedSetMapMode mode(screen_dc, MM_TEXT);
+ GetTextMetrics(screen_dc, &font_metrics);
+ }
+
+ const int height = std::max<int>(1, font_metrics.tmHeight);
+ const int baseline = std::max<int>(1, font_metrics.tmAscent);
+ const int ave_char_width = std::max<int>(1, font_metrics.tmAveCharWidth);
+ const int font_size =
+ std::max<int>(1, font_metrics.tmHeight - font_metrics.tmInternalLeading);
+ int style = 0;
+ if (font_metrics.tmItalic)
+ style |= Font::ITALIC;
+ if (font_metrics.tmUnderlined)
+ style |= Font::UNDERLINE;
+ if (font_metrics.tmWeight >= kTextMetricWeightBold)
+ style |= Font::BOLD;
+
+ return new HFontRef(font, font_size, height, baseline, ave_char_width, style);
+}
+
+PlatformFontWin::PlatformFontWin(HFontRef* hfont_ref) : font_ref_(hfont_ref) {
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// PlatformFontWin::HFontRef:
+
+PlatformFontWin::HFontRef::HFontRef(HFONT hfont,
+ int font_size,
+ int height,
+ int baseline,
+ int ave_char_width,
+ int style)
+ : hfont_(hfont),
+ font_size_(font_size),
+ height_(height),
+ baseline_(baseline),
+ ave_char_width_(ave_char_width),
+ style_(style),
+ dlu_base_x_(-1),
+ requested_font_size_(font_size) {
+ DLOG_ASSERT(hfont);
+
+ LOGFONT font_info;
+ GetObject(hfont_, sizeof(LOGFONT), &font_info);
+ font_name_ = UTF16ToUTF8(base::string16(font_info.lfFaceName));
+ if (font_info.lfHeight < 0)
+ requested_font_size_ = -font_info.lfHeight;
+}
+
+int PlatformFontWin::HFontRef::GetDluBaseX() {
+ if (dlu_base_x_ != -1)
+ return dlu_base_x_;
+
+ base::win::ScopedGetDC screen_dc(NULL);
+ base::win::ScopedSelectObject font(screen_dc, hfont_);
+ ui::ScopedSetMapMode mode(screen_dc, MM_TEXT);
+
+ // Yes, this is how Microsoft recommends calculating the dialog unit
+ // conversions. See: http://support.microsoft.com/kb/125681
+ SIZE ave_text_size;
+ GetTextExtentPoint32(screen_dc,
+ L"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz",
+ 52, &ave_text_size);
+ dlu_base_x_ = (ave_text_size.cx / 26 + 1) / 2;
+
+ DCHECK_NE(dlu_base_x_, -1);
+ return dlu_base_x_;
+}
+
+PlatformFontWin::HFontRef::~HFontRef() {
+ DeleteObject(hfont_);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// PlatformFont, public:
+
+// static
+PlatformFont* PlatformFont::CreateDefault() {
+ return new PlatformFontWin;
+}
+
+// static
+PlatformFont* PlatformFont::CreateFromNativeFont(NativeFont native_font) {
+ return new PlatformFontWin(native_font);
+}
+
+// static
+PlatformFont* PlatformFont::CreateFromNameAndSize(const std::string& font_name,
+ int font_size) {
+ return new PlatformFontWin(font_name, font_size);
+}
+
+} // namespace gfx
diff --git a/chromium/ui/gfx/platform_font_win.h b/chromium/ui/gfx/platform_font_win.h
new file mode 100644
index 00000000000..568bf952b35
--- /dev/null
+++ b/chromium/ui/gfx/platform_font_win.h
@@ -0,0 +1,158 @@
+// Copyright (c) 2012 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_H_
+#define UI_GFX_PLATFORM_FONT_WIN_H_
+
+#include <string>
+
+#include "base/compiler_specific.h"
+#include "base/memory/ref_counted.h"
+#include "ui/base/ui_export.h"
+#include "ui/gfx/platform_font.h"
+
+namespace gfx {
+
+class UI_EXPORT PlatformFontWin : public PlatformFont {
+ public:
+ PlatformFontWin();
+ explicit PlatformFontWin(NativeFont native_font);
+ PlatformFontWin(const std::string& 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_->GetDluBaseX() / 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;
+
+ // Returns the font name for the system locale. Some fonts, particularly
+ // East Asian fonts, have different names per locale. If the localized font
+ // name could not be retrieved, returns GetFontName().
+ std::string GetLocalizedFontName() const;
+
+ // Returns a derived Font with the specified |style| and with height at most
+ // |height|. If the height and style of the receiver already match, it is
+ // returned. Otherwise, the returned Font will have the largest size such that
+ // its height is less than or equal to |height| (since there may not exist a
+ // size that matches the exact |height| specified).
+ Font DeriveFontWithHeight(int height, int style);
+
+ // Overridden from PlatformFont:
+ virtual Font DeriveFont(int size_delta, int style) const OVERRIDE;
+ virtual int GetHeight() const OVERRIDE;
+ virtual int GetBaseline() const OVERRIDE;
+ virtual int GetAverageCharacterWidth() const OVERRIDE;
+ virtual int GetStringWidth(const base::string16& text) const OVERRIDE;
+ virtual int GetExpectedTextWidth(int length) const OVERRIDE;
+ virtual int GetStyle() const OVERRIDE;
+ virtual std::string GetFontName() const OVERRIDE;
+ virtual int GetFontSize() const OVERRIDE;
+ virtual NativeFont GetNativeFont() const OVERRIDE;
+
+ 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 font_size,
+ int height,
+ int baseline,
+ int ave_char_width,
+ int style);
+
+ // 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_; }
+ const std::string& font_name() const { return font_name_; }
+ int font_size() const { return font_size_; }
+ int requested_font_size() const { return requested_font_size_; }
+
+ // Returns the average character width in dialog units.
+ int GetDluBaseX();
+
+ private:
+ friend class base::RefCounted<HFontRef>;
+
+ ~HFontRef();
+
+ const HFONT hfont_;
+ const int font_size_;
+ const int height_;
+ const int baseline_;
+ const int ave_char_width_;
+ const int style_;
+ // Average character width in dialog units. This is queried lazily from the
+ // system, with an initial value of -1 meaning it hasn't yet been queried.
+ int dlu_base_x_;
+ std::string font_name_;
+
+ // If the requested font size is not possible for the font, |font_size_|
+ // will be different than |requested_font_size_|. This is stored separately
+ // so that code that increases the font size in a loop will not cause the
+ // loop to get stuck on the same size.
+ int requested_font_size_;
+
+ 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 std::string& 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_;
+
+ DISALLOW_COPY_AND_ASSIGN(PlatformFontWin);
+};
+
+} // namespace gfx
+
+#endif // UI_GFX_PLATFORM_FONT_WIN_H_
+
diff --git a/chromium/ui/gfx/platform_font_win_unittest.cc b/chromium/ui/gfx/platform_font_win_unittest.cc
new file mode 100644
index 00000000000..bfbe92203d1
--- /dev/null
+++ b/chromium/ui/gfx/platform_font_win_unittest.cc
@@ -0,0 +1,104 @@
+// Copyright (c) 2012 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 "ui/gfx/platform_font_win.h"
+
+#include "base/logging.h"
+#include "base/memory/ref_counted.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "ui/gfx/font.h"
+
+namespace gfx {
+
+namespace {
+
+// Returns a font based on |base_font| with height at most |target_height| and
+// font size maximized. Returns |base_font| if height is already equal.
+gfx::Font AdjustFontSizeForHeight(const gfx::Font& base_font,
+ int target_height) {
+ Font expected_font = base_font;
+ if (base_font.GetHeight() < target_height) {
+ // Increase size while height is <= |target_height|.
+ Font larger_font = base_font.DeriveFont(1, 0);
+ while (larger_font.GetHeight() <= target_height) {
+ expected_font = larger_font;
+ larger_font = larger_font.DeriveFont(1, 0);
+ }
+ } else if (expected_font.GetHeight() > target_height) {
+ // Decrease size until height is <= |target_height|.
+ do {
+ expected_font = expected_font.DeriveFont(-1, 0);
+ } while (expected_font.GetHeight() > target_height);
+ }
+ return expected_font;
+}
+
+} // namespace
+
+TEST(PlatformFontWinTest, DeriveFontWithHeight) {
+ const Font base_font;
+ PlatformFontWin* platform_font =
+ static_cast<PlatformFontWin*>(base_font.platform_font());
+
+ for (int i = -10; i < 10; i++) {
+ const int target_height = base_font.GetHeight() + i;
+ Font expected_font = AdjustFontSizeForHeight(base_font, target_height);
+ ASSERT_LE(expected_font.GetHeight(), target_height);
+
+ Font derived_font = platform_font->DeriveFontWithHeight(target_height, 0);
+ EXPECT_EQ(expected_font.GetFontName(), derived_font.GetFontName());
+ EXPECT_EQ(expected_font.GetFontSize(), derived_font.GetFontSize());
+ EXPECT_LE(expected_font.GetHeight(), target_height);
+ EXPECT_EQ(0, derived_font.GetStyle());
+
+ derived_font = platform_font->DeriveFontWithHeight(target_height,
+ Font::BOLD);
+ EXPECT_EQ(expected_font.GetFontName(), derived_font.GetFontName());
+ EXPECT_EQ(expected_font.GetFontSize(), derived_font.GetFontSize());
+ EXPECT_LE(expected_font.GetHeight(), target_height);
+ EXPECT_EQ(Font::BOLD, derived_font.GetStyle());
+
+ // Test that deriving from the new font has the expected result.
+ Font rederived_font = derived_font.DeriveFont(1, 0);
+ expected_font = Font(derived_font.GetFontName(),
+ derived_font.GetFontSize() + 1);
+ EXPECT_EQ(expected_font.GetFontName(), rederived_font.GetFontName());
+ EXPECT_EQ(expected_font.GetFontSize(), rederived_font.GetFontSize());
+ EXPECT_EQ(expected_font.GetHeight(), rederived_font.GetHeight());
+ }
+}
+
+// Callback function used by DeriveFontWithHeight_MinSize() below.
+static int GetMinFontSize() {
+ return 10;
+}
+
+TEST(PlatformFontWinTest, DeriveFontWithHeight_MinSize) {
+ PlatformFontWin::GetMinimumFontSizeCallback old_callback =
+ PlatformFontWin::get_minimum_font_size_callback;
+ PlatformFontWin::get_minimum_font_size_callback = &GetMinFontSize;
+
+ const Font base_font;
+ const Font min_font(base_font.GetFontName(), GetMinFontSize());
+ PlatformFontWin* platform_font =
+ static_cast<PlatformFontWin*>(base_font.platform_font());
+
+ const Font derived_font =
+ platform_font->DeriveFontWithHeight(min_font.GetHeight() - 1, 0);
+ EXPECT_EQ(min_font.GetFontSize(), derived_font.GetFontSize());
+ EXPECT_EQ(min_font.GetHeight(), derived_font.GetHeight());
+
+ PlatformFontWin::get_minimum_font_size_callback = old_callback;
+}
+
+TEST(PlatformFontWinTest, DeriveFontWithHeight_TooSmall) {
+ const Font base_font;
+ PlatformFontWin* platform_font =
+ static_cast<PlatformFontWin*>(base_font.platform_font());
+
+ const Font derived_font = platform_font->DeriveFontWithHeight(1, 0);
+ EXPECT_GT(derived_font.GetHeight(), 1);
+}
+
+} // namespace gfx
diff --git a/chromium/ui/gfx/point.cc b/chromium/ui/gfx/point.cc
new file mode 100644
index 00000000000..7fdf3560fe3
--- /dev/null
+++ b/chromium/ui/gfx/point.cc
@@ -0,0 +1,54 @@
+// Copyright (c) 2012 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 "ui/gfx/point.h"
+
+#if defined(OS_WIN)
+#include <windows.h>
+#endif
+
+#include "base/strings/stringprintf.h"
+
+namespace gfx {
+
+template class PointBase<Point, int, Vector2d>;
+
+#if defined(OS_WIN)
+Point::Point(DWORD point) : PointBase<Point, int, Vector2d>(0, 0){
+ POINTS points = MAKEPOINTS(point);
+ set_x(points.x);
+ set_y(points.y);
+}
+
+Point::Point(const POINT& point)
+ : PointBase<Point, int, Vector2d>(point.x, point.y) {
+}
+
+Point& Point::operator=(const POINT& point) {
+ set_x(point.x);
+ set_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)
+ : PointBase<Point, int, Vector2d>(point.x, point.y) {
+}
+
+CGPoint Point::ToCGPoint() const {
+ return CGPointMake(x(), y());
+}
+#endif
+
+std::string Point::ToString() const {
+ return base::StringPrintf("%d,%d", x(), y());
+}
+
+} // namespace gfx
diff --git a/chromium/ui/gfx/point.h b/chromium/ui/gfx/point.h
new file mode 100644
index 00000000000..0f8a327c5c8
--- /dev/null
+++ b/chromium/ui/gfx/point.h
@@ -0,0 +1,90 @@
+// Copyright (c) 2012 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_
+
+#include "ui/base/ui_export.h"
+#include "ui/gfx/point_base.h"
+#include "ui/gfx/point_f.h"
+#include "ui/gfx/vector2d.h"
+
+#if defined(OS_WIN)
+typedef unsigned long DWORD;
+typedef struct tagPOINT POINT;
+#elif defined(OS_IOS)
+#include <CoreGraphics/CoreGraphics.h>
+#elif defined(OS_MACOSX)
+#include <ApplicationServices/ApplicationServices.h>
+#endif
+
+namespace gfx {
+
+// A point has an x and y coordinate.
+class UI_EXPORT Point : public PointBase<Point, int, Vector2d> {
+ public:
+ Point() : PointBase<Point, int, Vector2d>(0, 0) {}
+ Point(int x, int y) : PointBase<Point, int, Vector2d>(x, 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() {}
+
+#if defined(OS_WIN)
+ POINT ToPOINT() const;
+#elif defined(OS_MACOSX)
+ CGPoint ToCGPoint() const;
+#endif
+
+ operator PointF() const {
+ return PointF(x(), y());
+ }
+
+ // Returns a string representation of point.
+ std::string ToString() const;
+};
+
+inline bool operator==(const Point& lhs, const Point& rhs) {
+ return lhs.x() == rhs.x() && lhs.y() == rhs.y();
+}
+
+inline bool operator!=(const Point& lhs, const Point& rhs) {
+ return !(lhs == rhs);
+}
+
+inline Point operator+(const Point& lhs, const Vector2d& rhs) {
+ Point result(lhs);
+ result += rhs;
+ return result;
+}
+
+inline Point operator-(const Point& lhs, const Vector2d& rhs) {
+ Point result(lhs);
+ result -= rhs;
+ return result;
+}
+
+inline Vector2d operator-(const Point& lhs, const Point& rhs) {
+ return Vector2d(lhs.x() - rhs.x(), lhs.y() - rhs.y());
+}
+
+inline Point PointAtOffsetFromOrigin(const Vector2d& offset_from_origin) {
+ return Point(offset_from_origin.x(), offset_from_origin.y());
+}
+
+#if !defined(COMPILER_MSVC)
+extern template class PointBase<Point, int, Vector2d>;
+#endif
+
+} // namespace gfx
+
+#endif // UI_GFX_POINT_H_
diff --git a/chromium/ui/gfx/point3_f.cc b/chromium/ui/gfx/point3_f.cc
new file mode 100644
index 00000000000..70089a46c21
--- /dev/null
+++ b/chromium/ui/gfx/point3_f.cc
@@ -0,0 +1,40 @@
+// Copyright (c) 2012 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 "ui/gfx/point3_f.h"
+
+#include "base/strings/stringprintf.h"
+
+namespace gfx {
+
+std::string Point3F::ToString() const {
+ return base::StringPrintf("%f,%f,%f", x_, y_, z_);
+}
+
+Point3F operator+(const Point3F& lhs, const Vector3dF& rhs) {
+ float x = lhs.x() + rhs.x();
+ float y = lhs.y() + rhs.y();
+ float z = lhs.z() + rhs.z();
+ return Point3F(x, y, z);
+}
+
+// Subtract a vector from a point, producing a new point offset by the vector's
+// inverse.
+Point3F operator-(const Point3F& lhs, const Vector3dF& rhs) {
+ float x = lhs.x() - rhs.x();
+ float y = lhs.y() - rhs.y();
+ float z = lhs.z() - rhs.z();
+ return Point3F(x, y, z);
+}
+
+// Subtract one point from another, producing a vector that represents the
+// distances between the two points along each axis.
+Vector3dF operator-(const Point3F& lhs, const Point3F& rhs) {
+ float x = lhs.x() - rhs.x();
+ float y = lhs.y() - rhs.y();
+ float z = lhs.z() - rhs.z();
+ return Vector3dF(x, y, z);
+}
+
+} // namespace gfx
diff --git a/chromium/ui/gfx/point3_f.h b/chromium/ui/gfx/point3_f.h
new file mode 100644
index 00000000000..8c700df47a7
--- /dev/null
+++ b/chromium/ui/gfx/point3_f.h
@@ -0,0 +1,120 @@
+// 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_POINT3_F_H_
+#define UI_GFX_POINT3_F_H_
+
+#include <string>
+
+#include "ui/base/ui_export.h"
+#include "ui/gfx/point_f.h"
+#include "ui/gfx/vector3d_f.h"
+
+namespace gfx {
+
+// A point has an x, y and z coordinate.
+class UI_EXPORT Point3F {
+ public:
+ Point3F() : x_(0), y_(0), z_(0) {}
+
+ Point3F(float x, float y, float z) : x_(x), y_(y), z_(z) {}
+
+ explicit Point3F(const PointF& point) : x_(point.x()), y_(point.y()), z_(0) {}
+
+ ~Point3F() {}
+
+ void Scale(float scale) {
+ Scale(scale, scale, scale);
+ }
+
+ void Scale(float x_scale, float y_scale, float z_scale) {
+ SetPoint(x() * x_scale, y() * y_scale, z() * z_scale);
+ }
+
+ float x() const { return x_; }
+ float y() const { return y_; }
+ float z() const { return z_; }
+
+ void set_x(float x) { x_ = x; }
+ void set_y(float y) { y_ = y; }
+ void set_z(float z) { z_ = z; }
+
+ void SetPoint(float x, float y, float z) {
+ x_ = x;
+ y_ = y;
+ z_ = z;
+ }
+
+ // Offset the point by the given vector.
+ void operator+=(const Vector3dF& v) {
+ x_ += v.x();
+ y_ += v.y();
+ z_ += v.z();
+ }
+
+ // Offset the point by the given vector's inverse.
+ void operator-=(const Vector3dF& v) {
+ x_ -= v.x();
+ y_ -= v.y();
+ z_ -= v.z();
+ }
+
+ // Returns the squared euclidean distance between two points.
+ float SquaredDistanceTo(const Point3F& other) const {
+ float dx = x_ - other.x_;
+ float dy = y_ - other.y_;
+ float dz = z_ - other.z_;
+ return dx * dx + dy * dy + dz * dz;
+ }
+
+ PointF AsPointF() const { return PointF(x_, y_); }
+
+ // Returns a string representation of 3d point.
+ std::string ToString() const;
+
+ private:
+ float x_;
+ float y_;
+ float z_;
+
+ // copy/assign are allowed.
+};
+
+inline bool operator==(const Point3F& lhs, const Point3F& rhs) {
+ return lhs.x() == rhs.x() && lhs.y() == rhs.y() && lhs.z() == rhs.z();
+}
+
+inline bool operator!=(const Point3F& lhs, const Point3F& rhs) {
+ return !(lhs == rhs);
+}
+
+// Add a vector to a point, producing a new point offset by the vector.
+UI_EXPORT Point3F operator+(const Point3F& lhs, const Vector3dF& rhs);
+
+// Subtract a vector from a point, producing a new point offset by the vector's
+// inverse.
+UI_EXPORT Point3F operator-(const Point3F& lhs, const Vector3dF& rhs);
+
+// Subtract one point from another, producing a vector that represents the
+// distances between the two points along each axis.
+UI_EXPORT Vector3dF operator-(const Point3F& lhs, const Point3F& rhs);
+
+inline Point3F PointAtOffsetFromOrigin(const Vector3dF& offset) {
+ return Point3F(offset.x(), offset.y(), offset.z());
+}
+
+inline Point3F ScalePoint(const Point3F& p,
+ float x_scale,
+ float y_scale,
+ float z_scale) {
+ return Point3F(p.x() * x_scale, p.y() * y_scale, p.z() * z_scale);
+}
+
+inline Point3F ScalePoint(const Point3F& p, float scale) {
+ return ScalePoint(p, scale, scale, scale);
+}
+
+} // namespace gfx
+
+#endif // UI_GFX_POINT3_F_H_
diff --git a/chromium/ui/gfx/point3_unittest.cc b/chromium/ui/gfx/point3_unittest.cc
new file mode 100644
index 00000000000..735ffd55425
--- /dev/null
+++ b/chromium/ui/gfx/point3_unittest.cc
@@ -0,0 +1,70 @@
+// Copyright (c) 2012 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 "ui/gfx/point3_f.h"
+
+#include "base/basictypes.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace gfx {
+
+TEST(Point3Test, VectorArithmetic) {
+ gfx::Point3F a(1.6f, 5.1f, 3.2f);
+ gfx::Vector3dF v1(3.1f, -3.2f, 9.3f);
+ gfx::Vector3dF v2(-8.1f, 1.2f, 3.3f);
+
+ static const struct {
+ gfx::Point3F expected;
+ gfx::Point3F actual;
+ } tests[] = {
+ { gfx::Point3F(4.7f, 1.9f, 12.5f), a + v1 },
+ { gfx::Point3F(-1.5f, 8.3f, -6.1f), a - v1 },
+ { a, a - v1 + v1 },
+ { a, a + v1 - v1 },
+ { a, a + gfx::Vector3dF() },
+ { gfx::Point3F(12.8f, 0.7f, 9.2f), a + v1 - v2 },
+ { gfx::Point3F(-9.6f, 9.5f, -2.8f), a - v1 + v2 }
+ };
+
+ for (size_t i = 0; i < ARRAYSIZE_UNSAFE(tests); ++i)
+ EXPECT_EQ(tests[i].expected.ToString(),
+ tests[i].actual.ToString());
+
+ a += v1;
+ EXPECT_EQ(Point3F(4.7f, 1.9f, 12.5f).ToString(), a.ToString());
+
+ a -= v2;
+ EXPECT_EQ(Point3F(12.8f, 0.7f, 9.2f).ToString(), a.ToString());
+}
+
+TEST(Point3Test, VectorFromPoints) {
+ gfx::Point3F a(1.6f, 5.2f, 3.2f);
+ gfx::Vector3dF v1(3.1f, -3.2f, 9.3f);
+
+ gfx::Point3F b(a + v1);
+ EXPECT_EQ((b - a).ToString(), v1.ToString());
+}
+
+TEST(Point3Test, Scale) {
+ EXPECT_EQ(Point3F().ToString(), ScalePoint(Point3F(), 2.f).ToString());
+ EXPECT_EQ(Point3F().ToString(),
+ ScalePoint(Point3F(), 2.f, 2.f, 2.f).ToString());
+
+ EXPECT_EQ(Point3F(2.f, -2.f, 4.f).ToString(),
+ ScalePoint(Point3F(1.f, -1.f, 2.f), 2.f).ToString());
+ EXPECT_EQ(Point3F(2.f, -3.f, 8.f).ToString(),
+ ScalePoint(Point3F(1.f, -1.f, 2.f), 2.f, 3.f, 4.f).ToString());
+
+ Point3F zero;
+ zero.Scale(2.f);
+ zero.Scale(6.f, 3.f, 1.5f);
+ EXPECT_EQ(Point3F().ToString(), zero.ToString());
+
+ Point3F point(1.f, -1.f, 2.f);
+ point.Scale(2.f);
+ point.Scale(6.f, 3.f, 1.5f);
+ EXPECT_EQ(Point3F(12.f, -6.f, 6.f).ToString(), point.ToString());
+}
+
+} // namespace gfx
diff --git a/chromium/ui/gfx/point_base.h b/chromium/ui/gfx/point_base.h
new file mode 100644
index 00000000000..048b0930b2d
--- /dev/null
+++ b/chromium/ui/gfx/point_base.h
@@ -0,0 +1,87 @@
+// Copyright (c) 2012 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_BASE_H_
+#define UI_GFX_POINT_BASE_H_
+
+#include <string>
+
+#include "base/compiler_specific.h"
+#include "build/build_config.h"
+#include "ui/base/ui_export.h"
+
+namespace gfx {
+
+// A point has an x and y coordinate.
+template<typename Class, typename Type, typename VectorClass>
+class UI_EXPORT PointBase {
+ public:
+ Type x() const { return x_; }
+ Type y() const { return y_; }
+
+ void SetPoint(Type x, Type y) {
+ x_ = x;
+ y_ = y;
+ }
+
+ void set_x(Type x) { x_ = x; }
+ void set_y(Type y) { y_ = y; }
+
+ void Offset(Type delta_x, Type delta_y) {
+ x_ += delta_x;
+ y_ += delta_y;
+ }
+
+ void operator+=(const VectorClass& vector) {
+ x_ += vector.x();
+ y_ += vector.y();
+ }
+
+ void operator-=(const VectorClass& vector) {
+ x_ -= vector.x();
+ y_ -= vector.y();
+ }
+
+ void SetToMin(const Class& other) {
+ x_ = x_ <= other.x_ ? x_ : other.x_;
+ y_ = y_ <= other.y_ ? y_ : other.y_;
+ }
+
+ void SetToMax(const Class& other) {
+ x_ = x_ >= other.x_ ? x_ : other.x_;
+ y_ = y_ >= other.y_ ? y_ : other.y_;
+ }
+
+ bool IsOrigin() const {
+ return x_ == 0 && y_ == 0;
+ }
+
+ VectorClass OffsetFromOrigin() const {
+ return VectorClass(x_, y_);
+ }
+
+ // 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 Point in sets, or sorted
+ // vectors.
+ bool operator<(const Class& rhs) const {
+ return (y_ == rhs.y_) ? (x_ < rhs.x_) : (y_ < rhs.y_);
+ }
+
+ protected:
+ PointBase(Type x, Type y) : x_(x), y_(y) {}
+ // Destructor is intentionally made non virtual and protected.
+ // Do not make this public.
+ ~PointBase() {}
+
+ private:
+ Type x_;
+ Type y_;
+};
+
+} // namespace gfx
+
+#endif // UI_GFX_POINT_BASE_H_
diff --git a/chromium/ui/gfx/point_conversions.cc b/chromium/ui/gfx/point_conversions.cc
new file mode 100644
index 00000000000..f7845a03f41
--- /dev/null
+++ b/chromium/ui/gfx/point_conversions.cc
@@ -0,0 +1,30 @@
+// Copyright (c) 2012 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 "ui/gfx/point_conversions.h"
+
+#include "ui/gfx/safe_integer_conversions.h"
+
+namespace gfx {
+
+Point ToFlooredPoint(const PointF& point) {
+ int x = ToFlooredInt(point.x());
+ int y = ToFlooredInt(point.y());
+ return Point(x, y);
+}
+
+Point ToCeiledPoint(const PointF& point) {
+ int x = ToCeiledInt(point.x());
+ int y = ToCeiledInt(point.y());
+ return Point(x, y);
+}
+
+Point ToRoundedPoint(const PointF& point) {
+ int x = ToRoundedInt(point.x());
+ int y = ToRoundedInt(point.y());
+ return Point(x, y);
+}
+
+} // namespace gfx
+
diff --git a/chromium/ui/gfx/point_conversions.h b/chromium/ui/gfx/point_conversions.h
new file mode 100644
index 00000000000..9467a9231dc
--- /dev/null
+++ b/chromium/ui/gfx/point_conversions.h
@@ -0,0 +1,24 @@
+// Copyright (c) 2012 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_CONVERSIONS_H_
+#define UI_GFX_POINT_CONVERSIONS_H_
+
+#include "ui/gfx/point.h"
+#include "ui/gfx/point_f.h"
+
+namespace gfx {
+
+// Returns a Point with each component from the input PointF floored.
+UI_EXPORT Point ToFlooredPoint(const PointF& point);
+
+// Returns a Point with each component from the input PointF ceiled.
+UI_EXPORT Point ToCeiledPoint(const PointF& point);
+
+// Returns a Point with each component from the input PointF rounded.
+UI_EXPORT Point ToRoundedPoint(const PointF& point);
+
+} // namespace gfx
+
+#endif // UI_GFX_POINT_CONVERSIONS_H_
diff --git a/chromium/ui/gfx/point_f.cc b/chromium/ui/gfx/point_f.cc
new file mode 100644
index 00000000000..21028565d99
--- /dev/null
+++ b/chromium/ui/gfx/point_f.cc
@@ -0,0 +1,24 @@
+// Copyright (c) 2012 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 "ui/gfx/point_f.h"
+
+#include "base/strings/stringprintf.h"
+
+namespace gfx {
+
+template class PointBase<PointF, float, Vector2dF>;
+
+std::string PointF::ToString() const {
+ return base::StringPrintf("%f,%f", x(), y());
+}
+
+PointF ScalePoint(const PointF& p, float x_scale, float y_scale) {
+ PointF scaled_p(p);
+ scaled_p.Scale(x_scale, y_scale);
+ return scaled_p;
+}
+
+
+} // namespace gfx
diff --git a/chromium/ui/gfx/point_f.h b/chromium/ui/gfx/point_f.h
new file mode 100644
index 00000000000..a7b841f23db
--- /dev/null
+++ b/chromium/ui/gfx/point_f.h
@@ -0,0 +1,75 @@
+// Copyright (c) 2012 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_F_H_
+#define UI_GFX_POINT_F_H_
+
+#include <string>
+
+#include "ui/base/ui_export.h"
+#include "ui/gfx/point_base.h"
+#include "ui/gfx/vector2d_f.h"
+
+namespace gfx {
+
+// A floating version of gfx::Point.
+class UI_EXPORT PointF : public PointBase<PointF, float, Vector2dF> {
+ public:
+ PointF() : PointBase<PointF, float, Vector2dF>(0, 0) {}
+ PointF(float x, float y) : PointBase<PointF, float, Vector2dF>(x, y) {}
+ ~PointF() {}
+
+ void Scale(float scale) {
+ Scale(scale, scale);
+ }
+
+ void Scale(float x_scale, float y_scale) {
+ SetPoint(x() * x_scale, y() * y_scale);
+ }
+
+ // Returns a string representation of point.
+ std::string ToString() const;
+};
+
+inline bool operator==(const PointF& lhs, const PointF& rhs) {
+ return lhs.x() == rhs.x() && lhs.y() == rhs.y();
+}
+
+inline bool operator!=(const PointF& lhs, const PointF& rhs) {
+ return !(lhs == rhs);
+}
+
+inline PointF operator+(const PointF& lhs, const Vector2dF& rhs) {
+ PointF result(lhs);
+ result += rhs;
+ return result;
+}
+
+inline PointF operator-(const PointF& lhs, const Vector2dF& rhs) {
+ PointF result(lhs);
+ result -= rhs;
+ return result;
+}
+
+inline Vector2dF operator-(const PointF& lhs, const PointF& rhs) {
+ return Vector2dF(lhs.x() - rhs.x(), lhs.y() - rhs.y());
+}
+
+inline PointF PointAtOffsetFromOrigin(const Vector2dF& offset_from_origin) {
+ return PointF(offset_from_origin.x(), offset_from_origin.y());
+}
+
+UI_EXPORT PointF ScalePoint(const PointF& p, float x_scale, float y_scale);
+
+inline PointF ScalePoint(const PointF& p, float scale) {
+ return ScalePoint(p, scale, scale);
+}
+
+#if !defined(COMPILER_MSVC)
+extern template class PointBase<PointF, float, Vector2dF>;
+#endif
+
+} // namespace gfx
+
+#endif // UI_GFX_POINT_F_H_
diff --git a/chromium/ui/gfx/point_unittest.cc b/chromium/ui/gfx/point_unittest.cc
new file mode 100644
index 00000000000..6cf73dd2adb
--- /dev/null
+++ b/chromium/ui/gfx/point_unittest.cc
@@ -0,0 +1,174 @@
+// Copyright (c) 2012 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 "ui/gfx/point_base.h"
+
+#include "base/basictypes.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "ui/gfx/point.h"
+#include "ui/gfx/point_conversions.h"
+#include "ui/gfx/point_f.h"
+
+namespace gfx {
+
+namespace {
+
+int TestPointF(const PointF& p) {
+ return p.x();
+}
+
+} // namespace
+
+TEST(PointTest, ToPointF) {
+ // Check that implicit conversion from integer to float compiles.
+ Point a(10, 20);
+ float x = TestPointF(a);
+ EXPECT_EQ(x, a.x());
+
+ PointF b(10, 20);
+ EXPECT_EQ(a, b);
+ EXPECT_EQ(b, a);
+}
+
+TEST(PointTest, IsOrigin) {
+ EXPECT_FALSE(Point(1, 0).IsOrigin());
+ EXPECT_FALSE(Point(0, 1).IsOrigin());
+ EXPECT_FALSE(Point(1, 2).IsOrigin());
+ EXPECT_FALSE(Point(-1, 0).IsOrigin());
+ EXPECT_FALSE(Point(0, -1).IsOrigin());
+ EXPECT_FALSE(Point(-1, -2).IsOrigin());
+ EXPECT_TRUE(Point(0, 0).IsOrigin());
+
+ EXPECT_FALSE(PointF(0.1f, 0).IsOrigin());
+ EXPECT_FALSE(PointF(0, 0.1f).IsOrigin());
+ EXPECT_FALSE(PointF(0.1f, 2).IsOrigin());
+ EXPECT_FALSE(PointF(-0.1f, 0).IsOrigin());
+ EXPECT_FALSE(PointF(0, -0.1f).IsOrigin());
+ EXPECT_FALSE(PointF(-0.1f, -2).IsOrigin());
+ EXPECT_TRUE(PointF(0, 0).IsOrigin());
+}
+
+TEST(PointTest, VectorArithmetic) {
+ Point a(1, 5);
+ Vector2d v1(3, -3);
+ Vector2d v2(-8, 1);
+
+ static const struct {
+ Point expected;
+ Point actual;
+ } tests[] = {
+ { Point(4, 2), a + v1 },
+ { Point(-2, 8), a - v1 },
+ { a, a - v1 + v1 },
+ { a, a + v1 - v1 },
+ { a, a + Vector2d() },
+ { Point(12, 1), a + v1 - v2 },
+ { Point(-10, 9), a - v1 + v2 }
+ };
+
+ for (size_t i = 0; i < ARRAYSIZE_UNSAFE(tests); ++i)
+ EXPECT_EQ(tests[i].expected.ToString(), tests[i].actual.ToString());
+}
+
+TEST(PointTest, OffsetFromPoint) {
+ Point a(1, 5);
+ Point b(-20, 8);
+ EXPECT_EQ(Vector2d(-20 - 1, 8 - 5).ToString(), (b - a).ToString());
+}
+
+TEST(PointTest, ToRoundedPoint) {
+ EXPECT_EQ(Point(0, 0), ToRoundedPoint(PointF(0, 0)));
+ EXPECT_EQ(Point(0, 0), ToRoundedPoint(PointF(0.0001f, 0.0001f)));
+ EXPECT_EQ(Point(0, 0), ToRoundedPoint(PointF(0.4999f, 0.4999f)));
+ EXPECT_EQ(Point(1, 1), ToRoundedPoint(PointF(0.5f, 0.5f)));
+ EXPECT_EQ(Point(1, 1), ToRoundedPoint(PointF(0.9999f, 0.9999f)));
+
+ EXPECT_EQ(Point(10, 10), ToRoundedPoint(PointF(10, 10)));
+ EXPECT_EQ(Point(10, 10), ToRoundedPoint(PointF(10.0001f, 10.0001f)));
+ EXPECT_EQ(Point(10, 10), ToRoundedPoint(PointF(10.4999f, 10.4999f)));
+ EXPECT_EQ(Point(11, 11), ToRoundedPoint(PointF(10.5f, 10.5f)));
+ EXPECT_EQ(Point(11, 11), ToRoundedPoint(PointF(10.9999f, 10.9999f)));
+
+ EXPECT_EQ(Point(-10, -10), ToRoundedPoint(PointF(-10, -10)));
+ EXPECT_EQ(Point(-10, -10), ToRoundedPoint(PointF(-10.0001f, -10.0001f)));
+ EXPECT_EQ(Point(-10, -10), ToRoundedPoint(PointF(-10.4999f, -10.4999f)));
+ EXPECT_EQ(Point(-11, -11), ToRoundedPoint(PointF(-10.5f, -10.5f)));
+ EXPECT_EQ(Point(-11, -11), ToRoundedPoint(PointF(-10.9999f, -10.9999f)));
+}
+
+TEST(PointTest, Scale) {
+ EXPECT_EQ(PointF().ToString(), ScalePoint(Point(), 2).ToString());
+ EXPECT_EQ(PointF().ToString(), ScalePoint(Point(), 2, 2).ToString());
+
+ EXPECT_EQ(PointF(2, -2).ToString(),
+ ScalePoint(Point(1, -1), 2).ToString());
+ EXPECT_EQ(PointF(2, -2).ToString(),
+ ScalePoint(Point(1, -1), 2, 2).ToString());
+
+ PointF zero;
+ PointF one(1, -1);
+
+ zero.Scale(2);
+ zero.Scale(3, 1.5);
+
+ one.Scale(2);
+ one.Scale(3, 1.5);
+
+ EXPECT_EQ(PointF().ToString(), zero.ToString());
+ EXPECT_EQ(PointF(6, -3).ToString(), one.ToString());
+}
+
+TEST(PointTest, ClampPoint) {
+ Point a;
+
+ a = Point(3, 5);
+ EXPECT_EQ(Point(3, 5).ToString(), a.ToString());
+ a.SetToMax(Point(2, 4));
+ EXPECT_EQ(Point(3, 5).ToString(), a.ToString());
+ a.SetToMax(Point(3, 5));
+ EXPECT_EQ(Point(3, 5).ToString(), a.ToString());
+ a.SetToMax(Point(4, 2));
+ EXPECT_EQ(Point(4, 5).ToString(), a.ToString());
+ a.SetToMax(Point(8, 10));
+ EXPECT_EQ(Point(8, 10).ToString(), a.ToString());
+
+ a.SetToMin(Point(9, 11));
+ EXPECT_EQ(Point(8, 10).ToString(), a.ToString());
+ a.SetToMin(Point(8, 10));
+ EXPECT_EQ(Point(8, 10).ToString(), a.ToString());
+ a.SetToMin(Point(11, 9));
+ EXPECT_EQ(Point(8, 9).ToString(), a.ToString());
+ a.SetToMin(Point(7, 11));
+ EXPECT_EQ(Point(7, 9).ToString(), a.ToString());
+ a.SetToMin(Point(3, 5));
+ EXPECT_EQ(Point(3, 5).ToString(), a.ToString());
+}
+
+TEST(PointTest, ClampPointF) {
+ PointF a;
+
+ a = PointF(3.5f, 5.5f);
+ EXPECT_EQ(PointF(3.5f, 5.5f).ToString(), a.ToString());
+ a.SetToMax(PointF(2.5f, 4.5f));
+ EXPECT_EQ(PointF(3.5f, 5.5f).ToString(), a.ToString());
+ a.SetToMax(PointF(3.5f, 5.5f));
+ EXPECT_EQ(PointF(3.5f, 5.5f).ToString(), a.ToString());
+ a.SetToMax(PointF(4.5f, 2.5f));
+ EXPECT_EQ(PointF(4.5f, 5.5f).ToString(), a.ToString());
+ a.SetToMax(PointF(8.5f, 10.5f));
+ EXPECT_EQ(PointF(8.5f, 10.5f).ToString(), a.ToString());
+
+ a.SetToMin(PointF(9.5f, 11.5f));
+ EXPECT_EQ(PointF(8.5f, 10.5f).ToString(), a.ToString());
+ a.SetToMin(PointF(8.5f, 10.5f));
+ EXPECT_EQ(PointF(8.5f, 10.5f).ToString(), a.ToString());
+ a.SetToMin(PointF(11.5f, 9.5f));
+ EXPECT_EQ(PointF(8.5f, 9.5f).ToString(), a.ToString());
+ a.SetToMin(PointF(7.5f, 11.5f));
+ EXPECT_EQ(PointF(7.5f, 9.5f).ToString(), a.ToString());
+ a.SetToMin(PointF(3.5f, 5.5f));
+ EXPECT_EQ(PointF(3.5f, 5.5f).ToString(), a.ToString());
+}
+
+} // namespace gfx
diff --git a/chromium/ui/gfx/quad_f.cc b/chromium/ui/gfx/quad_f.cc
new file mode 100644
index 00000000000..2796bf192b2
--- /dev/null
+++ b/chromium/ui/gfx/quad_f.cc
@@ -0,0 +1,127 @@
+// Copyright (c) 2012 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 "ui/gfx/quad_f.h"
+
+#include <limits>
+
+#include "base/strings/stringprintf.h"
+
+namespace gfx {
+
+void QuadF::operator=(const RectF& rect) {
+ p1_ = PointF(rect.x(), rect.y());
+ p2_ = PointF(rect.right(), rect.y());
+ p3_ = PointF(rect.right(), rect.bottom());
+ p4_ = PointF(rect.x(), rect.bottom());
+}
+
+std::string QuadF::ToString() const {
+ return base::StringPrintf("%s;%s;%s;%s",
+ p1_.ToString().c_str(),
+ p2_.ToString().c_str(),
+ p3_.ToString().c_str(),
+ p4_.ToString().c_str());
+}
+
+static inline bool WithinEpsilon(float a, float b) {
+ return std::abs(a - b) < std::numeric_limits<float>::epsilon();
+}
+
+bool QuadF::IsRectilinear() const {
+ return
+ (WithinEpsilon(p1_.x(), p2_.x()) && WithinEpsilon(p2_.y(), p3_.y()) &&
+ WithinEpsilon(p3_.x(), p4_.x()) && WithinEpsilon(p4_.y(), p1_.y())) ||
+ (WithinEpsilon(p1_.y(), p2_.y()) && WithinEpsilon(p2_.x(), p3_.x()) &&
+ WithinEpsilon(p3_.y(), p4_.y()) && WithinEpsilon(p4_.x(), p1_.x()));
+}
+
+bool QuadF::IsCounterClockwise() const {
+ // This math computes the signed area of the quad. Positive area
+ // indicates the quad is clockwise; negative area indicates the quad is
+ // counter-clockwise. Note carefully: this is backwards from conventional
+ // math because our geometric space uses screen coordiantes with y-axis
+ // pointing downards.
+ // Reference: http://mathworld.wolfram.com/PolygonArea.html
+
+ // Up-cast to double so this cannot overflow.
+ double determinant1 = static_cast<double>(p1_.x()) * p2_.y()
+ - static_cast<double>(p2_.x()) * p1_.y();
+ double determinant2 = static_cast<double>(p2_.x()) * p3_.y()
+ - static_cast<double>(p3_.x()) * p2_.y();
+ double determinant3 = static_cast<double>(p3_.x()) * p4_.y()
+ - static_cast<double>(p4_.x()) * p3_.y();
+ double determinant4 = static_cast<double>(p4_.x()) * p1_.y()
+ - static_cast<double>(p1_.x()) * p4_.y();
+
+ return determinant1 + determinant2 + determinant3 + determinant4 < 0;
+}
+
+static inline bool PointIsInTriangle(const PointF& point,
+ const PointF& r1,
+ const PointF& r2,
+ const PointF& r3) {
+ // Compute the barycentric coordinates of |point| relative to the triangle
+ // (r1, r2, r3). This algorithm comes from Christer Ericson's Real-Time
+ // Collision Detection.
+ Vector2dF v0 = r2 - r1;
+ Vector2dF v1 = r3 - r1;
+ Vector2dF v2 = point - r1;
+
+ double dot00 = DotProduct(v0, v0);
+ double dot01 = DotProduct(v0, v1);
+ double dot11 = DotProduct(v1, v1);
+ double dot20 = DotProduct(v2, v0);
+ double dot21 = DotProduct(v2, v1);
+
+ double denom = dot00 * dot11 - dot01 * dot01;
+
+ double v = (dot11 * dot20 - dot01 * dot21) / denom;
+ double w = (dot00 * dot21 - dot01 * dot20) / denom;
+ double u = 1 - v - w;
+
+ // Use the barycentric coordinates to test if |point| is inside the
+ // triangle (r1, r2, r2).
+ return (v >= 0) && (w >= 0) && (u >= 0);
+}
+
+bool QuadF::Contains(const PointF& point) const {
+ return PointIsInTriangle(point, p1_, p2_, p3_)
+ || PointIsInTriangle(point, p1_, p3_, p4_);
+}
+
+void QuadF::Scale(float x_scale, float y_scale) {
+ p1_.Scale(x_scale, y_scale);
+ p2_.Scale(x_scale, y_scale);
+ p3_.Scale(x_scale, y_scale);
+ p4_.Scale(x_scale, y_scale);
+}
+
+void QuadF::operator+=(const Vector2dF& rhs) {
+ p1_ += rhs;
+ p2_ += rhs;
+ p3_ += rhs;
+ p4_ += rhs;
+}
+
+void QuadF::operator-=(const Vector2dF& rhs) {
+ p1_ -= rhs;
+ p2_ -= rhs;
+ p3_ -= rhs;
+ p4_ -= rhs;
+}
+
+QuadF operator+(const QuadF& lhs, const Vector2dF& rhs) {
+ QuadF result = lhs;
+ result += rhs;
+ return result;
+}
+
+QuadF operator-(const QuadF& lhs, const Vector2dF& rhs) {
+ QuadF result = lhs;
+ result -= rhs;
+ return result;
+}
+
+} // namespace gfx
diff --git a/chromium/ui/gfx/quad_f.h b/chromium/ui/gfx/quad_f.h
new file mode 100644
index 00000000000..4173dbe0f2f
--- /dev/null
+++ b/chromium/ui/gfx/quad_f.h
@@ -0,0 +1,108 @@
+// Copyright (c) 2012 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_QUAD_F_H_
+#define UI_GFX_QUAD_F_H_
+
+#include <cmath>
+#include <string>
+
+#include "ui/base/ui_export.h"
+#include "ui/gfx/point_f.h"
+#include "ui/gfx/rect_f.h"
+
+namespace gfx {
+
+// A Quad is defined by four corners, allowing it to have edges that are not
+// axis-aligned, unlike a Rect.
+class UI_EXPORT QuadF {
+ public:
+ QuadF() {}
+ QuadF(const PointF& p1, const PointF& p2, const PointF& p3, const PointF& p4)
+ : p1_(p1),
+ p2_(p2),
+ p3_(p3),
+ p4_(p4) {}
+
+ explicit QuadF(const RectF& rect)
+ : p1_(rect.x(), rect.y()),
+ p2_(rect.right(), rect.y()),
+ p3_(rect.right(), rect.bottom()),
+ p4_(rect.x(), rect.bottom()) {}
+
+ void operator=(const RectF& rect);
+
+ void set_p1(const PointF& p) { p1_ = p; }
+ void set_p2(const PointF& p) { p2_ = p; }
+ void set_p3(const PointF& p) { p3_ = p; }
+ void set_p4(const PointF& p) { p4_ = p; }
+
+ const PointF& p1() const { return p1_; }
+ const PointF& p2() const { return p2_; }
+ const PointF& p3() const { return p3_; }
+ const PointF& p4() const { return p4_; }
+
+ // Returns true if the quad is an axis-aligned rectangle.
+ bool IsRectilinear() const;
+
+ // Returns true if the points of the quad are in counter-clockwise order. This
+ // assumes that the quad is convex, and that no three points are collinear.
+ bool IsCounterClockwise() const;
+
+ // Returns true if the |point| is contained within the quad, or lies on on
+ // edge of the quad.
+ bool Contains(const gfx::PointF& point) const;
+
+ // Returns a rectangle that bounds the four points of the quad. The points of
+ // the quad may lie on the right/bottom edge of the resulting rectangle,
+ // rather than being strictly inside it.
+ RectF BoundingBox() const {
+ float rl = std::min(std::min(p1_.x(), p2_.x()), std::min(p3_.x(), p4_.x()));
+ float rr = std::max(std::max(p1_.x(), p2_.x()), std::max(p3_.x(), p4_.x()));
+ float rt = std::min(std::min(p1_.y(), p2_.y()), std::min(p3_.y(), p4_.y()));
+ float rb = std::max(std::max(p1_.y(), p2_.y()), std::max(p3_.y(), p4_.y()));
+ return RectF(rl, rt, rr - rl, rb - rt);
+ }
+
+ // Add a vector to the quad, offseting each point in the quad by the vector.
+ void operator+=(const Vector2dF& rhs);
+ // Subtract a vector from the quad, offseting each point in the quad by the
+ // inverse of the vector.
+ void operator-=(const Vector2dF& rhs);
+
+ // Scale each point in the quad by the |scale| factor.
+ void Scale(float scale) { Scale(scale, scale); }
+
+ // Scale each point in the quad by the scale factors along each axis.
+ void Scale(float x_scale, float y_scale);
+
+ // Returns a string representation of quad.
+ std::string ToString() const;
+
+ private:
+ PointF p1_;
+ PointF p2_;
+ PointF p3_;
+ PointF p4_;
+};
+
+inline bool operator==(const QuadF& lhs, const QuadF& rhs) {
+ return
+ lhs.p1() == rhs.p1() && lhs.p2() == rhs.p2() &&
+ lhs.p3() == rhs.p3() && lhs.p4() == rhs.p4();
+}
+
+inline bool operator!=(const QuadF& lhs, const QuadF& rhs) {
+ return !(lhs == rhs);
+}
+
+// Add a vector to a quad, offseting each point in the quad by the vector.
+UI_EXPORT QuadF operator+(const QuadF& lhs, const Vector2dF& rhs);
+// Subtract a vector from a quad, offseting each point in the quad by the
+// inverse of the vector.
+UI_EXPORT QuadF operator-(const QuadF& lhs, const Vector2dF& rhs);
+
+} // namespace gfx
+
+#endif // UI_GFX_QUAD_F_H_
diff --git a/chromium/ui/gfx/quad_unittest.cc b/chromium/ui/gfx/quad_unittest.cc
new file mode 100644
index 00000000000..8859a0e6972
--- /dev/null
+++ b/chromium/ui/gfx/quad_unittest.cc
@@ -0,0 +1,360 @@
+// Copyright (c) 2012 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 "ui/gfx/quad_f.h"
+
+#include "base/basictypes.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "ui/gfx/rect_f.h"
+
+namespace gfx {
+
+TEST(QuadTest, Construction) {
+ // Verify constructors.
+ PointF a(1, 1);
+ PointF b(2, 1);
+ PointF c(2, 2);
+ PointF d(1, 2);
+ PointF e;
+ QuadF q1;
+ QuadF q2(e, e, e, e);
+ QuadF q3(a, b, c, d);
+ QuadF q4(BoundingRect(a, c));
+ EXPECT_EQ(q1.ToString(), q2.ToString());
+ EXPECT_EQ(q3.ToString(), q4.ToString());
+
+ // Verify getters.
+ EXPECT_EQ(q3.p1().ToString(), a.ToString());
+ EXPECT_EQ(q3.p2().ToString(), b.ToString());
+ EXPECT_EQ(q3.p3().ToString(), c.ToString());
+ EXPECT_EQ(q3.p4().ToString(), d.ToString());
+
+ // Verify setters.
+ q3.set_p1(b);
+ q3.set_p2(c);
+ q3.set_p3(d);
+ q3.set_p4(a);
+ EXPECT_EQ(q3.p1().ToString(), b.ToString());
+ EXPECT_EQ(q3.p2().ToString(), c.ToString());
+ EXPECT_EQ(q3.p3().ToString(), d.ToString());
+ EXPECT_EQ(q3.p4().ToString(), a.ToString());
+
+ // Verify operator=(Rect)
+ EXPECT_NE(q1.ToString(), q4.ToString());
+ q1 = BoundingRect(a, c);
+ EXPECT_EQ(q1.ToString(), q4.ToString());
+
+ // Verify operator=(Quad)
+ EXPECT_NE(q1.ToString(), q3.ToString());
+ q1 = q3;
+ EXPECT_EQ(q1.ToString(), q3.ToString());
+}
+
+TEST(QuadTest, AddingVectors) {
+ PointF a(1, 1);
+ PointF b(2, 1);
+ PointF c(2, 2);
+ PointF d(1, 2);
+ Vector2dF v(3.5f, -2.5f);
+
+ QuadF q1(a, b, c, d);
+ QuadF added = q1 + v;
+ q1 += v;
+ QuadF expected1(PointF(4.5f, -1.5f),
+ PointF(5.5f, -1.5f),
+ PointF(5.5f, -0.5f),
+ PointF(4.5f, -0.5f));
+ EXPECT_EQ(expected1.ToString(), added.ToString());
+ EXPECT_EQ(expected1.ToString(), q1.ToString());
+
+ QuadF q2(a, b, c, d);
+ QuadF subtracted = q2 - v;
+ q2 -= v;
+ QuadF expected2(PointF(-2.5f, 3.5f),
+ PointF(-1.5f, 3.5f),
+ PointF(-1.5f, 4.5f),
+ PointF(-2.5f, 4.5f));
+ EXPECT_EQ(expected2.ToString(), subtracted.ToString());
+ EXPECT_EQ(expected2.ToString(), q2.ToString());
+
+ QuadF q3(a, b, c, d);
+ q3 += v;
+ q3 -= v;
+ EXPECT_EQ(QuadF(a, b, c, d).ToString(), q3.ToString());
+ EXPECT_EQ(q3.ToString(), (q3 + v - v).ToString());
+}
+
+TEST(QuadTest, IsRectilinear) {
+ PointF a(1, 1);
+ PointF b(2, 1);
+ PointF c(2, 2);
+ PointF d(1, 2);
+ Vector2dF v(3.5f, -2.5f);
+
+ EXPECT_TRUE(QuadF().IsRectilinear());
+ EXPECT_TRUE(QuadF(a, b, c, d).IsRectilinear());
+ EXPECT_TRUE((QuadF(a, b, c, d) + v).IsRectilinear());
+
+ float epsilon = std::numeric_limits<float>::epsilon();
+ PointF a2(1 + epsilon / 2, 1 + epsilon / 2);
+ PointF b2(2 + epsilon / 2, 1 + epsilon / 2);
+ PointF c2(2 + epsilon / 2, 2 + epsilon / 2);
+ PointF d2(1 + epsilon / 2, 2 + epsilon / 2);
+ EXPECT_TRUE(QuadF(a2, b, c, d).IsRectilinear());
+ EXPECT_TRUE((QuadF(a2, b, c, d) + v).IsRectilinear());
+ EXPECT_TRUE(QuadF(a, b2, c, d).IsRectilinear());
+ EXPECT_TRUE((QuadF(a, b2, c, d) + v).IsRectilinear());
+ EXPECT_TRUE(QuadF(a, b, c2, d).IsRectilinear());
+ EXPECT_TRUE((QuadF(a, b, c2, d) + v).IsRectilinear());
+ EXPECT_TRUE(QuadF(a, b, c, d2).IsRectilinear());
+ EXPECT_TRUE((QuadF(a, b, c, d2) + v).IsRectilinear());
+
+ struct {
+ PointF a_off, b_off, c_off, d_off;
+ } tests[] = {
+ {
+ PointF(1, 1.00001f),
+ PointF(2, 1.00001f),
+ PointF(2, 2.00001f),
+ PointF(1, 2.00001f)
+ },
+ {
+ PointF(1.00001f, 1),
+ PointF(2.00001f, 1),
+ PointF(2.00001f, 2),
+ PointF(1.00001f, 2)
+ },
+ {
+ PointF(1.00001f, 1.00001f),
+ PointF(2.00001f, 1.00001f),
+ PointF(2.00001f, 2.00001f),
+ PointF(1.00001f, 2.00001f)
+ },
+ {
+ PointF(1, 0.99999f),
+ PointF(2, 0.99999f),
+ PointF(2, 1.99999f),
+ PointF(1, 1.99999f)
+ },
+ {
+ PointF(0.99999f, 1),
+ PointF(1.99999f, 1),
+ PointF(1.99999f, 2),
+ PointF(0.99999f, 2)
+ },
+ {
+ PointF(0.99999f, 0.99999f),
+ PointF(1.99999f, 0.99999f),
+ PointF(1.99999f, 1.99999f),
+ PointF(0.99999f, 1.99999f)
+ }
+ };
+
+ for (size_t i = 0; i < ARRAYSIZE_UNSAFE(tests); ++i) {
+ PointF a_off = tests[i].a_off;
+ PointF b_off = tests[i].b_off;
+ PointF c_off = tests[i].c_off;
+ PointF d_off = tests[i].d_off;
+
+ EXPECT_FALSE(QuadF(a_off, b, c, d).IsRectilinear());
+ EXPECT_FALSE((QuadF(a_off, b, c, d) + v).IsRectilinear());
+ EXPECT_FALSE(QuadF(a, b_off, c, d).IsRectilinear());
+ EXPECT_FALSE((QuadF(a, b_off, c, d) + v).IsRectilinear());
+ EXPECT_FALSE(QuadF(a, b, c_off, d).IsRectilinear());
+ EXPECT_FALSE((QuadF(a, b, c_off, d) + v).IsRectilinear());
+ EXPECT_FALSE(QuadF(a, b, c, d_off).IsRectilinear());
+ EXPECT_FALSE((QuadF(a, b, c, d_off) + v).IsRectilinear());
+ EXPECT_FALSE(QuadF(a_off, b, c_off, d).IsRectilinear());
+ EXPECT_FALSE((QuadF(a_off, b, c_off, d) + v).IsRectilinear());
+ EXPECT_FALSE(QuadF(a, b_off, c, d_off).IsRectilinear());
+ EXPECT_FALSE((QuadF(a, b_off, c, d_off) + v).IsRectilinear());
+ EXPECT_FALSE(QuadF(a, b_off, c_off, d_off).IsRectilinear());
+ EXPECT_FALSE((QuadF(a, b_off, c_off, d_off) + v).IsRectilinear());
+ EXPECT_FALSE(QuadF(a_off, b, c_off, d_off).IsRectilinear());
+ EXPECT_FALSE((QuadF(a_off, b, c_off, d_off) + v).IsRectilinear());
+ EXPECT_FALSE(QuadF(a_off, b_off, c, d_off).IsRectilinear());
+ EXPECT_FALSE((QuadF(a_off, b_off, c, d_off) + v).IsRectilinear());
+ EXPECT_FALSE(QuadF(a_off, b_off, c_off, d).IsRectilinear());
+ EXPECT_FALSE((QuadF(a_off, b_off, c_off, d) + v).IsRectilinear());
+ EXPECT_TRUE(QuadF(a_off, b_off, c_off, d_off).IsRectilinear());
+ EXPECT_TRUE((QuadF(a_off, b_off, c_off, d_off) + v).IsRectilinear());
+ }
+}
+
+TEST(QuadTest, IsCounterClockwise) {
+ PointF a1(1, 1);
+ PointF b1(2, 1);
+ PointF c1(2, 2);
+ PointF d1(1, 2);
+ EXPECT_FALSE(QuadF(a1, b1, c1, d1).IsCounterClockwise());
+ EXPECT_FALSE(QuadF(b1, c1, d1, a1).IsCounterClockwise());
+ EXPECT_TRUE(QuadF(a1, d1, c1, b1).IsCounterClockwise());
+ EXPECT_TRUE(QuadF(c1, b1, a1, d1).IsCounterClockwise());
+
+ // Slightly more complicated quads should work just as easily.
+ PointF a2(1.3f, 1.4f);
+ PointF b2(-0.7f, 4.9f);
+ PointF c2(1.8f, 6.2f);
+ PointF d2(2.1f, 1.6f);
+ EXPECT_TRUE(QuadF(a2, b2, c2, d2).IsCounterClockwise());
+ EXPECT_TRUE(QuadF(b2, c2, d2, a2).IsCounterClockwise());
+ EXPECT_FALSE(QuadF(a2, d2, c2, b2).IsCounterClockwise());
+ EXPECT_FALSE(QuadF(c2, b2, a2, d2).IsCounterClockwise());
+
+ // Quads with 3 collinear points should work correctly, too.
+ PointF a3(0, 0);
+ PointF b3(1, 0);
+ PointF c3(2, 0);
+ PointF d3(1, 1);
+ EXPECT_FALSE(QuadF(a3, b3, c3, d3).IsCounterClockwise());
+ EXPECT_FALSE(QuadF(b3, c3, d3, a3).IsCounterClockwise());
+ EXPECT_TRUE(QuadF(a3, d3, c3, b3).IsCounterClockwise());
+ // The next expectation in particular would fail for an implementation
+ // that incorrectly uses only a cross product of the first 3 vertices.
+ EXPECT_TRUE(QuadF(c3, b3, a3, d3).IsCounterClockwise());
+
+ // Non-convex quads should work correctly, too.
+ PointF a4(0, 0);
+ PointF b4(1, 1);
+ PointF c4(2, 0);
+ PointF d4(1, 3);
+ EXPECT_FALSE(QuadF(a4, b4, c4, d4).IsCounterClockwise());
+ EXPECT_FALSE(QuadF(b4, c4, d4, a4).IsCounterClockwise());
+ EXPECT_TRUE(QuadF(a4, d4, c4, b4).IsCounterClockwise());
+ EXPECT_TRUE(QuadF(c4, b4, a4, d4).IsCounterClockwise());
+
+ // A quad with huge coordinates should not fail this check due to
+ // single-precision overflow.
+ PointF a5(1e30f, 1e30f);
+ PointF b5(1e35f, 1e30f);
+ PointF c5(1e35f, 1e35f);
+ PointF d5(1e30f, 1e35f);
+ EXPECT_FALSE(QuadF(a5, b5, c5, d5).IsCounterClockwise());
+ EXPECT_FALSE(QuadF(b5, c5, d5, a5).IsCounterClockwise());
+ EXPECT_TRUE(QuadF(a5, d5, c5, b5).IsCounterClockwise());
+ EXPECT_TRUE(QuadF(c5, b5, a5, d5).IsCounterClockwise());
+}
+
+TEST(QuadTest, BoundingBox) {
+ RectF r(3.2f, 5.4f, 7.007f, 12.01f);
+ EXPECT_EQ(r.ToString(), QuadF(r).BoundingBox().ToString());
+
+ PointF a(1.3f, 1.4f);
+ PointF b(-0.7f, 4.9f);
+ PointF c(1.8f, 6.2f);
+ PointF d(2.1f, 1.6f);
+ float left = -0.7f;
+ float top = 1.4f;
+ float right = 2.1f;
+ float bottom = 6.2f;
+ EXPECT_EQ(RectF(left, top, right - left, bottom - top).ToString(),
+ QuadF(a, b, c, d).BoundingBox().ToString());
+}
+
+TEST(QuadTest, ContainsPoint) {
+ PointF a(1.3f, 1.4f);
+ PointF b(-0.8f, 4.4f);
+ PointF c(1.8f, 6.1f);
+ PointF d(2.1f, 1.6f);
+
+ Vector2dF epsilon_x(2 * std::numeric_limits<float>::epsilon(), 0);
+ Vector2dF epsilon_y(0, 2 * std::numeric_limits<float>::epsilon());
+
+ Vector2dF ac_center = c - a;
+ ac_center.Scale(0.5f);
+ Vector2dF bd_center = d - b;
+ bd_center.Scale(0.5f);
+
+ EXPECT_TRUE(QuadF(a, b, c, d).Contains(a + ac_center));
+ EXPECT_TRUE(QuadF(a, b, c, d).Contains(b + bd_center));
+ EXPECT_TRUE(QuadF(a, b, c, d).Contains(c - ac_center));
+ EXPECT_TRUE(QuadF(a, b, c, d).Contains(d - bd_center));
+ EXPECT_FALSE(QuadF(a, b, c, d).Contains(a - ac_center));
+ EXPECT_FALSE(QuadF(a, b, c, d).Contains(b - bd_center));
+ EXPECT_FALSE(QuadF(a, b, c, d).Contains(c + ac_center));
+ EXPECT_FALSE(QuadF(a, b, c, d).Contains(d + bd_center));
+
+ EXPECT_TRUE(QuadF(a, b, c, d).Contains(a));
+ EXPECT_FALSE(QuadF(a, b, c, d).Contains(a - epsilon_x));
+ EXPECT_FALSE(QuadF(a, b, c, d).Contains(a - epsilon_y));
+ EXPECT_FALSE(QuadF(a, b, c, d).Contains(a + epsilon_x));
+ EXPECT_TRUE(QuadF(a, b, c, d).Contains(a + epsilon_y));
+
+ EXPECT_TRUE(QuadF(a, b, c, d).Contains(b));
+ EXPECT_FALSE(QuadF(a, b, c, d).Contains(b - epsilon_x));
+ EXPECT_FALSE(QuadF(a, b, c, d).Contains(b - epsilon_y));
+ EXPECT_TRUE(QuadF(a, b, c, d).Contains(b + epsilon_x));
+ EXPECT_FALSE(QuadF(a, b, c, d).Contains(b + epsilon_y));
+
+ EXPECT_TRUE(QuadF(a, b, c, d).Contains(c));
+ EXPECT_FALSE(QuadF(a, b, c, d).Contains(c - epsilon_x));
+ EXPECT_TRUE(QuadF(a, b, c, d).Contains(c - epsilon_y));
+ EXPECT_FALSE(QuadF(a, b, c, d).Contains(c + epsilon_x));
+ EXPECT_FALSE(QuadF(a, b, c, d).Contains(c + epsilon_y));
+
+ EXPECT_TRUE(QuadF(a, b, c, d).Contains(d));
+ EXPECT_TRUE(QuadF(a, b, c, d).Contains(d - epsilon_x));
+ EXPECT_FALSE(QuadF(a, b, c, d).Contains(d - epsilon_y));
+ EXPECT_FALSE(QuadF(a, b, c, d).Contains(d + epsilon_x));
+ EXPECT_FALSE(QuadF(a, b, c, d).Contains(d + epsilon_y));
+
+ // Test a simple square.
+ PointF s1(-1, -1);
+ PointF s2(1, -1);
+ PointF s3(1, 1);
+ PointF s4(-1, 1);
+ // Top edge.
+ EXPECT_FALSE(QuadF(s1, s2, s3, s4).Contains(PointF(-1.1f, -1.0f)));
+ EXPECT_TRUE(QuadF(s1, s2, s3, s4).Contains(PointF(-1.0f, -1.0f)));
+ EXPECT_TRUE(QuadF(s1, s2, s3, s4).Contains(PointF(0.0f, -1.0f)));
+ EXPECT_TRUE(QuadF(s1, s2, s3, s4).Contains(PointF(1.0f, -1.0f)));
+ EXPECT_FALSE(QuadF(s1, s2, s3, s4).Contains(PointF(1.1f, -1.0f)));
+ // Bottom edge.
+ EXPECT_FALSE(QuadF(s1, s2, s3, s4).Contains(PointF(-1.1f, 1.0f)));
+ EXPECT_TRUE(QuadF(s1, s2, s3, s4).Contains(PointF(-1.0f, 1.0f)));
+ EXPECT_TRUE(QuadF(s1, s2, s3, s4).Contains(PointF(0.0f, 1.0f)));
+ EXPECT_TRUE(QuadF(s1, s2, s3, s4).Contains(PointF(1.0f, 1.0f)));
+ EXPECT_FALSE(QuadF(s1, s2, s3, s4).Contains(PointF(1.1f, 1.0f)));
+ // Left edge.
+ EXPECT_FALSE(QuadF(s1, s2, s3, s4).Contains(PointF(-1.0f, -1.1f)));
+ EXPECT_TRUE(QuadF(s1, s2, s3, s4).Contains(PointF(-1.0f, -1.0f)));
+ EXPECT_TRUE(QuadF(s1, s2, s3, s4).Contains(PointF(-1.0f, 0.0f)));
+ EXPECT_TRUE(QuadF(s1, s2, s3, s4).Contains(PointF(-1.0f, 1.0f)));
+ EXPECT_FALSE(QuadF(s1, s2, s3, s4).Contains(PointF(-1.0f, 1.1f)));
+ // Right edge.
+ EXPECT_FALSE(QuadF(s1, s2, s3, s4).Contains(PointF(1.0f, -1.1f)));
+ EXPECT_TRUE(QuadF(s1, s2, s3, s4).Contains(PointF(1.0f, -1.0f)));
+ EXPECT_TRUE(QuadF(s1, s2, s3, s4).Contains(PointF(1.0f, 0.0f)));
+ EXPECT_TRUE(QuadF(s1, s2, s3, s4).Contains(PointF(1.0f, 1.0f)));
+ EXPECT_FALSE(QuadF(s1, s2, s3, s4).Contains(PointF(1.0f, 1.1f)));
+ // Centered inside.
+ EXPECT_TRUE(QuadF(s1, s2, s3, s4).Contains(PointF(0, 0)));
+ // Centered outside.
+ EXPECT_FALSE(QuadF(s1, s2, s3, s4).Contains(PointF(-1.1f, 0)));
+ EXPECT_FALSE(QuadF(s1, s2, s3, s4).Contains(PointF(1.1f, 0)));
+ EXPECT_FALSE(QuadF(s1, s2, s3, s4).Contains(PointF(0, -1.1f)));
+ EXPECT_FALSE(QuadF(s1, s2, s3, s4).Contains(PointF(0, 1.1f)));
+}
+
+TEST(QuadTest, Scale) {
+ PointF a(1.3f, 1.4f);
+ PointF b(-0.8f, 4.4f);
+ PointF c(1.8f, 6.1f);
+ PointF d(2.1f, 1.6f);
+ QuadF q1(a, b, c, d);
+ q1.Scale(1.5f);
+
+ PointF a_scaled = ScalePoint(a, 1.5f);
+ PointF b_scaled = ScalePoint(b, 1.5f);
+ PointF c_scaled = ScalePoint(c, 1.5f);
+ PointF d_scaled = ScalePoint(d, 1.5f);
+ EXPECT_EQ(q1.ToString(),
+ QuadF(a_scaled, b_scaled, c_scaled, d_scaled).ToString());
+
+ QuadF q2;
+ q2.Scale(1.5f);
+ EXPECT_EQ(q2.ToString(), q2.ToString());
+}
+
+} // namespace gfx
diff --git a/chromium/ui/gfx/rect.cc b/chromium/ui/gfx/rect.cc
new file mode 100644
index 00000000000..8372cc4e7c5
--- /dev/null
+++ b/chromium/ui/gfx/rect.cc
@@ -0,0 +1,110 @@
+// Copyright (c) 2012 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 "ui/gfx/rect.h"
+
+#include <algorithm>
+
+#if defined(OS_WIN)
+#include <windows.h>
+#elif defined(TOOLKIT_GTK)
+#include <gdk/gdk.h>
+#endif
+
+#include "base/logging.h"
+#include "base/strings/stringprintf.h"
+#include "ui/gfx/insets.h"
+#include "ui/gfx/rect_base_impl.h"
+
+namespace gfx {
+
+template class RectBase<Rect, Point, Size, Insets, Vector2d, int>;
+
+typedef class RectBase<Rect, Point, Size, Insets, Vector2d, int> RectBaseT;
+
+#if defined(OS_WIN)
+Rect::Rect(const RECT& r)
+ : RectBaseT(gfx::Point(r.left, r.top)) {
+ set_width(std::abs(r.right - r.left));
+ set_height(std::abs(r.bottom - r.top));
+}
+#elif defined(OS_MACOSX)
+Rect::Rect(const CGRect& r)
+ : RectBaseT(gfx::Point(r.origin.x, r.origin.y)) {
+ set_width(r.size.width);
+ set_height(r.size.height);
+}
+#elif defined(TOOLKIT_GTK)
+Rect::Rect(const GdkRectangle& r)
+ : RectBaseT(gfx::Point(r.x, r.y)) {
+ set_width(r.width);
+ set_height(r.height);
+}
+#endif
+
+#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(TOOLKIT_GTK)
+GdkRectangle Rect::ToGdkRectangle() const {
+ GdkRectangle r = {x(), y(), width(), height()};
+ return r;
+}
+#endif
+
+std::string Rect::ToString() const {
+ return base::StringPrintf("%s %s",
+ origin().ToString().c_str(),
+ size().ToString().c_str());
+}
+
+Rect operator+(const Rect& lhs, const Vector2d& rhs) {
+ Rect result(lhs);
+ result += rhs;
+ return result;
+}
+
+Rect operator-(const Rect& lhs, const Vector2d& rhs) {
+ Rect result(lhs);
+ result -= rhs;
+ return result;
+}
+
+Rect IntersectRects(const Rect& a, const Rect& b) {
+ Rect result = a;
+ result.Intersect(b);
+ return result;
+}
+
+Rect UnionRects(const Rect& a, const Rect& b) {
+ Rect result = a;
+ result.Union(b);
+ return result;
+}
+
+Rect SubtractRects(const Rect& a, const Rect& b) {
+ Rect result = a;
+ result.Subtract(b);
+ return result;
+}
+
+Rect BoundingRect(const Point& p1, const Point& p2) {
+ int rx = std::min(p1.x(), p2.x());
+ int ry = std::min(p1.y(), p2.y());
+ int rr = std::max(p1.x(), p2.x());
+ int rb = std::max(p1.y(), p2.y());
+ return Rect(rx, ry, rr - rx, rb - ry);
+}
+
+} // namespace gfx
diff --git a/chromium/ui/gfx/rect.h b/chromium/ui/gfx/rect.h
new file mode 100644
index 00000000000..d983770dca8
--- /dev/null
+++ b/chromium/ui/gfx/rect.h
@@ -0,0 +1,145 @@
+// Copyright (c) 2012 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_
+
+#include <cmath>
+#include <string>
+
+#include "ui/gfx/point.h"
+#include "ui/gfx/rect_base.h"
+#include "ui/gfx/rect_f.h"
+#include "ui/gfx/size.h"
+#include "ui/gfx/vector2d.h"
+
+#if defined(OS_WIN)
+typedef struct tagRECT RECT;
+#elif defined(TOOLKIT_GTK)
+typedef struct _GdkRectangle GdkRectangle;
+#elif defined(OS_IOS)
+#include <CoreGraphics/CoreGraphics.h>
+#elif defined(OS_MACOSX)
+#include <ApplicationServices/ApplicationServices.h>
+#endif
+
+namespace gfx {
+
+class Insets;
+
+class UI_EXPORT Rect
+ : public RectBase<Rect, Point, Size, Insets, Vector2d, int> {
+ public:
+ Rect() : RectBase<Rect, Point, Size, Insets, Vector2d, int>(Point()) {}
+
+ Rect(int width, int height)
+ : RectBase<Rect, Point, Size, Insets, Vector2d, int>
+ (Size(width, height)) {}
+
+ Rect(int x, int y, int width, int height)
+ : RectBase<Rect, Point, Size, Insets, Vector2d, int>
+ (Point(x, y), Size(width, height)) {}
+
+#if defined(OS_WIN)
+ explicit Rect(const RECT& r);
+#elif defined(OS_MACOSX)
+ explicit Rect(const CGRect& r);
+#elif defined(TOOLKIT_GTK)
+ explicit Rect(const GdkRectangle& r);
+#endif
+
+ explicit Rect(const gfx::Size& size)
+ : RectBase<Rect, Point, Size, Insets, Vector2d, int>(size) {}
+
+ Rect(const gfx::Point& origin, const gfx::Size& size)
+ : RectBase<Rect, Point, Size, Insets, Vector2d, int>(origin, size) {}
+
+ ~Rect() {}
+
+#if defined(OS_WIN)
+ // Construct an equivalent Win32 RECT object.
+ RECT ToRECT() const;
+#elif defined(TOOLKIT_GTK)
+ GdkRectangle ToGdkRectangle() const;
+#elif defined(OS_MACOSX)
+ // Construct an equivalent CoreGraphics object.
+ CGRect ToCGRect() const;
+#endif
+
+ operator RectF() const {
+ return RectF(origin().x(), origin().y(), size().width(), size().height());
+ }
+
+ std::string ToString() const;
+};
+
+inline bool operator==(const Rect& lhs, const Rect& rhs) {
+ return lhs.origin() == rhs.origin() && lhs.size() == rhs.size();
+}
+
+inline bool operator!=(const Rect& lhs, const Rect& rhs) {
+ return !(lhs == rhs);
+}
+
+UI_EXPORT Rect operator+(const Rect& lhs, const Vector2d& rhs);
+UI_EXPORT Rect operator-(const Rect& lhs, const Vector2d& rhs);
+
+inline Rect operator+(const Vector2d& lhs, const Rect& rhs) {
+ return rhs + lhs;
+}
+
+UI_EXPORT Rect IntersectRects(const Rect& a, const Rect& b);
+UI_EXPORT Rect UnionRects(const Rect& a, const Rect& b);
+UI_EXPORT Rect SubtractRects(const Rect& a, const Rect& b);
+
+// Constructs a rectangle with |p1| and |p2| as opposite corners.
+//
+// This could also be thought of as "the smallest rect that contains both
+// points", except that we consider points on the right/bottom edges of the
+// rect to be outside the rect. So technically one or both points will not be
+// contained within the rect, because they will appear on one of these edges.
+UI_EXPORT Rect BoundingRect(const Point& p1, const Point& p2);
+
+inline Rect ScaleToEnclosingRect(const Rect& rect,
+ float x_scale,
+ float y_scale) {
+ int x = std::floor(rect.x() * x_scale);
+ int y = std::floor(rect.y() * y_scale);
+ int r = rect.width() == 0 ? x : std::ceil(rect.right() * x_scale);
+ int b = rect.height() == 0 ? y : std::ceil(rect.bottom() * y_scale);
+ return Rect(x, y, r - x, b - y);
+}
+
+inline Rect ScaleToEnclosingRect(const Rect& rect, float scale) {
+ return ScaleToEnclosingRect(rect, scale, scale);
+}
+
+inline Rect ScaleToEnclosedRect(const Rect& rect,
+ float x_scale,
+ float y_scale) {
+ int x = std::ceil(rect.x() * x_scale);
+ int y = std::ceil(rect.y() * y_scale);
+ int r = rect.width() == 0 ? x : std::floor(rect.right() * x_scale);
+ int b = rect.height() == 0 ? y : std::floor(rect.bottom() * y_scale);
+ return Rect(x, y, r - x, b - y);
+}
+
+inline Rect ScaleToEnclosedRect(const Rect& rect, float scale) {
+ return ScaleToEnclosedRect(rect, scale, scale);
+}
+
+#if !defined(COMPILER_MSVC)
+extern template class RectBase<Rect, Point, Size, Insets, Vector2d, int>;
+#endif
+
+} // namespace gfx
+
+#endif // UI_GFX_RECT_H_
diff --git a/chromium/ui/gfx/rect_base.h b/chromium/ui/gfx/rect_base.h
new file mode 100644
index 00000000000..f1a286323ec
--- /dev/null
+++ b/chromium/ui/gfx/rect_base.h
@@ -0,0 +1,164 @@
+// Copyright (c) 2012 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 template for a simple 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_BASE_H_
+#define UI_GFX_RECT_BASE_H_
+
+#include <string>
+
+#include "base/compiler_specific.h"
+
+namespace gfx {
+
+template<typename Class,
+ typename PointClass,
+ typename SizeClass,
+ typename InsetsClass,
+ typename VectorClass,
+ typename Type>
+class UI_EXPORT RectBase {
+ public:
+ Type x() const { return origin_.x(); }
+ void set_x(Type x) { origin_.set_x(x); }
+
+ Type y() const { return origin_.y(); }
+ void set_y(Type y) { origin_.set_y(y); }
+
+ Type width() const { return size_.width(); }
+ void set_width(Type width) { size_.set_width(width); }
+
+ Type height() const { return size_.height(); }
+ void set_height(Type height) { size_.set_height(height); }
+
+ const PointClass& origin() const { return origin_; }
+ void set_origin(const PointClass& origin) { origin_ = origin; }
+
+ const SizeClass& size() const { return size_; }
+ void set_size(const SizeClass& size) { size_ = size; }
+
+ Type right() const { return x() + width(); }
+ Type bottom() const { return y() + height(); }
+
+ PointClass top_right() const { return PointClass(right(), y()); }
+ PointClass bottom_left() const { return PointClass(x(), bottom()); }
+ PointClass bottom_right() const { return PointClass(right(), bottom()); }
+
+ VectorClass OffsetFromOrigin() const {
+ return VectorClass(x(), y());
+ }
+
+ void SetRect(Type x, Type y, Type width, Type height);
+
+ // Shrink the rectangle by a horizontal and vertical distance on all sides.
+ void Inset(Type horizontal, Type vertical) {
+ Inset(horizontal, vertical, horizontal, vertical);
+ }
+
+ // Shrink the rectangle by the given insets.
+ void Inset(const InsetsClass& insets);
+
+ // Shrink the rectangle by the specified amount on each side.
+ void Inset(Type left, Type top, Type right, Type bottom);
+
+ // Move the rectangle by a horizontal and vertical distance.
+ void Offset(Type horizontal, Type vertical);
+ void Offset(const VectorClass& distance) {
+ Offset(distance.x(), distance.y());
+ }
+ void operator+=(const VectorClass& offset);
+ void operator-=(const VectorClass& offset);
+
+ InsetsClass InsetsFrom(const Class& inner) const {
+ return InsetsClass(inner.y() - y(),
+ inner.x() - x(),
+ bottom() - inner.bottom(),
+ right() - inner.right());
+ }
+
+ // Returns true if the area of the rectangle is zero.
+ bool IsEmpty() const { return size_.IsEmpty(); }
+
+ // 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 Class& other) const;
+
+ // Returns true if the point identified by point_x and point_y falls inside
+ // this rectangle. The point (x, y) is inside the rectangle, but the
+ // point (x + width, y + height) is not.
+ bool Contains(Type point_x, Type point_y) const;
+
+ // Returns true if the specified point is contained by this rectangle.
+ bool Contains(const PointClass& point) const {
+ return Contains(point.x(), point.y());
+ }
+
+ // Returns true if this rectangle contains the specified rectangle.
+ bool Contains(const Class& rect) const;
+
+ // Returns true if this rectangle intersects the specified rectangle.
+ // An empty rectangle doesn't intersect any rectangle.
+ bool Intersects(const Class& rect) const;
+
+ // Computes the intersection of this rectangle with the given rectangle.
+ void Intersect(const Class& rect);
+
+ // Computes the union of this rectangle with the given rectangle. The union
+ // is the smallest rectangle containing both rectangles.
+ void Union(const Class& rect);
+
+ // Computes the rectangle resulting from subtracting |rect| from |*this|,
+ // i.e. the bounding rect of |Region(*this) - Region(rect)|.
+ void Subtract(const Class& rect);
+
+ // Fits as much of the receiving rectangle into the supplied rectangle as
+ // possible, becoming 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.
+ void AdjustToFit(const Class& rect);
+
+ // Returns the center of this rectangle.
+ PointClass CenterPoint() const;
+
+ // Becomes a rectangle that has the same center point but with a size capped
+ // at given |size|.
+ void ClampToCenteredSize(const SizeClass& size);
+
+ // Splits |this| in two halves, |left_half| and |right_half|.
+ void SplitVertically(Class* left_half, Class* right_half) 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 Class& rect) const;
+
+ protected:
+ RectBase(const PointClass& origin, const SizeClass& size)
+ : origin_(origin), size_(size) {}
+ explicit RectBase(const SizeClass& size)
+ : size_(size) {}
+ explicit RectBase(const PointClass& origin)
+ : origin_(origin) {}
+ // Destructor is intentionally made non virtual and protected.
+ // Do not make this public.
+ ~RectBase() {}
+
+ private:
+ PointClass origin_;
+ SizeClass size_;
+};
+
+} // namespace gfx
+
+#endif // UI_GFX_RECT_BASE_H_
diff --git a/chromium/ui/gfx/rect_base_impl.h b/chromium/ui/gfx/rect_base_impl.h
new file mode 100644
index 00000000000..e44bc00bace
--- /dev/null
+++ b/chromium/ui/gfx/rect_base_impl.h
@@ -0,0 +1,317 @@
+// Copyright (c) 2012 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 "ui/gfx/rect_base.h"
+
+#include "base/logging.h"
+#include "base/strings/stringprintf.h"
+
+// This file provides the implementation for RectBaese template and
+// used to instantiate the base class for Rect and RectF classes.
+#if !defined(UI_IMPLEMENTATION)
+#error "This file is intended for UI implementation only"
+#endif
+
+namespace {
+
+template<typename Type>
+void AdjustAlongAxis(Type dst_origin, Type dst_size, Type* origin, Type* size) {
+ *size = std::min(dst_size, *size);
+ if (*origin < dst_origin)
+ *origin = dst_origin;
+ else
+ *origin = std::min(dst_origin + dst_size, *origin + *size) - *size;
+}
+
+} // namespace
+
+namespace gfx {
+
+template<typename Class,
+ typename PointClass,
+ typename SizeClass,
+ typename InsetsClass,
+ typename VectorClass,
+ typename Type>
+void RectBase<Class, PointClass, SizeClass, InsetsClass, VectorClass, Type>::
+ SetRect(Type x, Type y, Type width, Type height) {
+ origin_.SetPoint(x, y);
+ set_width(width);
+ set_height(height);
+}
+
+template<typename Class,
+ typename PointClass,
+ typename SizeClass,
+ typename InsetsClass,
+ typename VectorClass,
+ typename Type>
+void RectBase<Class, PointClass, SizeClass, InsetsClass, VectorClass, Type>::
+ Inset(const InsetsClass& insets) {
+ Inset(insets.left(), insets.top(), insets.right(), insets.bottom());
+}
+
+template<typename Class,
+ typename PointClass,
+ typename SizeClass,
+ typename InsetsClass,
+ typename VectorClass,
+ typename Type>
+void RectBase<Class, PointClass, SizeClass, InsetsClass, VectorClass, Type>::
+ Inset(Type left, Type top, Type right, Type bottom) {
+ origin_ += VectorClass(left, top);
+ set_width(std::max(width() - left - right, static_cast<Type>(0)));
+ set_height(std::max(height() - top - bottom, static_cast<Type>(0)));
+}
+
+template<typename Class,
+ typename PointClass,
+ typename SizeClass,
+ typename InsetsClass,
+ typename VectorClass,
+ typename Type>
+void RectBase<Class, PointClass, SizeClass, InsetsClass, VectorClass, Type>::
+ Offset(Type horizontal, Type vertical) {
+ origin_ += VectorClass(horizontal, vertical);
+}
+
+template<typename Class,
+ typename PointClass,
+ typename SizeClass,
+ typename InsetsClass,
+ typename VectorClass,
+ typename Type>
+void RectBase<Class, PointClass, SizeClass, InsetsClass, VectorClass, Type>::
+ operator+=(const VectorClass& offset) {
+ origin_ += offset;
+}
+
+template<typename Class,
+ typename PointClass,
+ typename SizeClass,
+ typename InsetsClass,
+ typename VectorClass,
+ typename Type>
+void RectBase<Class, PointClass, SizeClass, InsetsClass, VectorClass, Type>::
+ operator-=(const VectorClass& offset) {
+ origin_ -= offset;
+}
+
+template<typename Class,
+ typename PointClass,
+ typename SizeClass,
+ typename InsetsClass,
+ typename VectorClass,
+ typename Type>
+bool RectBase<Class, PointClass, SizeClass, InsetsClass, VectorClass, Type>::
+ operator<(const Class& other) const {
+ if (origin_ == other.origin_) {
+ if (width() == other.width()) {
+ return height() < other.height();
+ } else {
+ return width() < other.width();
+ }
+ } else {
+ return origin_ < other.origin_;
+ }
+}
+
+template<typename Class,
+ typename PointClass,
+ typename SizeClass,
+ typename InsetsClass,
+ typename VectorClass,
+ typename Type>
+bool RectBase<Class, PointClass, SizeClass, InsetsClass, VectorClass, Type>::
+ Contains(Type point_x, Type point_y) const {
+ return (point_x >= x()) && (point_x < right()) &&
+ (point_y >= y()) && (point_y < bottom());
+}
+
+template<typename Class,
+ typename PointClass,
+ typename SizeClass,
+ typename InsetsClass,
+ typename VectorClass,
+ typename Type>
+bool RectBase<Class, PointClass, SizeClass, InsetsClass, VectorClass, Type>::
+ Contains(const Class& rect) const {
+ return (rect.x() >= x() && rect.right() <= right() &&
+ rect.y() >= y() && rect.bottom() <= bottom());
+}
+
+template<typename Class,
+ typename PointClass,
+ typename SizeClass,
+ typename InsetsClass,
+ typename VectorClass,
+ typename Type>
+bool RectBase<Class, PointClass, SizeClass, InsetsClass, VectorClass, Type>::
+ Intersects(const Class& rect) const {
+ return !(IsEmpty() || rect.IsEmpty() ||
+ rect.x() >= right() || rect.right() <= x() ||
+ rect.y() >= bottom() || rect.bottom() <= y());
+}
+
+template<typename Class,
+ typename PointClass,
+ typename SizeClass,
+ typename InsetsClass,
+ typename VectorClass,
+ typename Type>
+void RectBase<Class, PointClass, SizeClass, InsetsClass, VectorClass, Type>::
+ Intersect(const Class& rect) {
+ if (IsEmpty() || rect.IsEmpty()) {
+ SetRect(0, 0, 0, 0);
+ return;
+ }
+
+ Type rx = std::max(x(), rect.x());
+ Type ry = std::max(y(), rect.y());
+ Type rr = std::min(right(), rect.right());
+ Type rb = std::min(bottom(), rect.bottom());
+
+ if (rx >= rr || ry >= rb)
+ rx = ry = rr = rb = 0; // non-intersecting
+
+ SetRect(rx, ry, rr - rx, rb - ry);
+}
+
+template<typename Class,
+ typename PointClass,
+ typename SizeClass,
+ typename InsetsClass,
+ typename VectorClass,
+ typename Type>
+void RectBase<Class, PointClass, SizeClass, InsetsClass, VectorClass, Type>::
+ Union(const Class& rect) {
+ if (IsEmpty()) {
+ *this = rect;
+ return;
+ }
+ if (rect.IsEmpty())
+ return;
+
+ Type rx = std::min(x(), rect.x());
+ Type ry = std::min(y(), rect.y());
+ Type rr = std::max(right(), rect.right());
+ Type rb = std::max(bottom(), rect.bottom());
+
+ SetRect(rx, ry, rr - rx, rb - ry);
+}
+
+template<typename Class,
+ typename PointClass,
+ typename SizeClass,
+ typename InsetsClass,
+ typename VectorClass,
+ typename Type>
+void RectBase<Class, PointClass, SizeClass, InsetsClass, VectorClass, Type>::
+ Subtract(const Class& rect) {
+ if (!Intersects(rect))
+ return;
+ if (rect.Contains(*static_cast<const Class*>(this))) {
+ SetRect(0, 0, 0, 0);
+ return;
+ }
+
+ Type rx = x();
+ Type ry = y();
+ Type rr = right();
+ Type rb = bottom();
+
+ if (rect.y() <= y() && rect.bottom() >= bottom()) {
+ // complete intersection in the y-direction
+ if (rect.x() <= x()) {
+ rx = rect.right();
+ } else if (rect.right() >= right()) {
+ 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 if (rect.bottom() >= bottom()) {
+ rb = rect.y();
+ }
+ }
+ SetRect(rx, ry, rr - rx, rb - ry);
+}
+
+template<typename Class,
+ typename PointClass,
+ typename SizeClass,
+ typename InsetsClass,
+ typename VectorClass,
+ typename Type>
+void RectBase<Class, PointClass, SizeClass, InsetsClass, VectorClass, Type>::
+ AdjustToFit(const Class& rect) {
+ Type new_x = x();
+ Type new_y = y();
+ Type new_width = width();
+ Type new_height = height();
+ AdjustAlongAxis(rect.x(), rect.width(), &new_x, &new_width);
+ AdjustAlongAxis(rect.y(), rect.height(), &new_y, &new_height);
+ SetRect(new_x, new_y, new_width, new_height);
+}
+
+template<typename Class,
+ typename PointClass,
+ typename SizeClass,
+ typename InsetsClass,
+ typename VectorClass,
+ typename Type>
+PointClass RectBase<Class, PointClass, SizeClass, InsetsClass, VectorClass,
+ Type>::CenterPoint() const {
+ return PointClass(x() + width() / 2, y() + height() / 2);
+}
+
+template<typename Class,
+ typename PointClass,
+ typename SizeClass,
+ typename InsetsClass,
+ typename VectorClass,
+ typename Type>
+void RectBase<Class, PointClass, SizeClass, InsetsClass, VectorClass, Type>::
+ ClampToCenteredSize(const SizeClass& size) {
+ Type new_width = std::min(width(), size.width());
+ Type new_height = std::min(height(), size.height());
+ Type new_x = x() + (width() - new_width) / 2;
+ Type new_y = y() + (height() - new_height) / 2;
+ SetRect(new_x, new_y, new_width, new_height);
+}
+
+template<typename Class,
+ typename PointClass,
+ typename SizeClass,
+ typename InsetsClass,
+ typename VectorClass,
+ typename Type>
+void RectBase<Class, PointClass, SizeClass, InsetsClass, VectorClass, Type>::
+ SplitVertically(Class* left_half, Class* right_half) const {
+ DCHECK(left_half);
+ DCHECK(right_half);
+
+ left_half->SetRect(x(), y(), width() / 2, height());
+ right_half->SetRect(left_half->right(),
+ y(),
+ width() - left_half->width(),
+ height());
+}
+
+template<typename Class,
+ typename PointClass,
+ typename SizeClass,
+ typename InsetsClass,
+ typename VectorClass,
+ typename Type>
+bool RectBase<Class, PointClass, SizeClass, InsetsClass, VectorClass, Type>::
+ SharesEdgeWith(const Class& 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()));
+}
+
+} // namespace gfx
diff --git a/chromium/ui/gfx/rect_conversions.cc b/chromium/ui/gfx/rect_conversions.cc
new file mode 100644
index 00000000000..ac7767b3204
--- /dev/null
+++ b/chromium/ui/gfx/rect_conversions.cc
@@ -0,0 +1,81 @@
+// Copyright (c) 2012 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 "ui/gfx/rect_conversions.h"
+
+#include <cmath>
+
+#include "base/logging.h"
+#include "ui/gfx/safe_integer_conversions.h"
+
+namespace gfx {
+
+Rect ToEnclosingRect(const RectF& rect) {
+ int min_x = ToFlooredInt(rect.x());
+ int min_y = ToFlooredInt(rect.y());
+ float max_x = rect.right();
+ float max_y = rect.bottom();
+ int width = rect.width() == 0 ? 0 : std::max(ToCeiledInt(max_x) - min_x, 0);
+ int height = rect.height() == 0 ? 0 : std::max(ToCeiledInt(max_y) - min_y, 0);
+ return Rect(min_x, min_y, width, height);
+}
+
+Rect ToEnclosedRect(const RectF& rect) {
+ int min_x = ToCeiledInt(rect.x());
+ int min_y = ToCeiledInt(rect.y());
+ float max_x = rect.right();
+ float max_y = rect.bottom();
+ int width = std::max(ToFlooredInt(max_x) - min_x, 0);
+ int height = std::max(ToFlooredInt(max_y) - min_y, 0);
+ return Rect(min_x, min_y, width, height);
+}
+
+Rect ToNearestRect(const RectF& rect) {
+ float float_min_x = rect.x();
+ float float_min_y = rect.y();
+ float float_max_x = rect.right();
+ float float_max_y = rect.bottom();
+
+ int min_x = ToRoundedInt(float_min_x);
+ int min_y = ToRoundedInt(float_min_y);
+ int max_x = ToRoundedInt(float_max_x);
+ int max_y = ToRoundedInt(float_max_y);
+
+ // If these DCHECKs fail, you're using the wrong method, consider using
+ // ToEnclosingRect or ToEnclosedRect instead.
+ DCHECK(std::abs(min_x - float_min_x) < 0.01f);
+ DCHECK(std::abs(min_y - float_min_y) < 0.01f);
+ DCHECK(std::abs(max_x - float_max_x) < 0.01f);
+ DCHECK(std::abs(max_y - float_max_y) < 0.01f);
+
+ return Rect(min_x, min_y, max_x - min_x, max_y - min_y);
+}
+
+bool IsNearestRectWithinDistance(const gfx::RectF& rect, float distance) {
+ float float_min_x = rect.x();
+ float float_min_y = rect.y();
+ float float_max_x = rect.right();
+ float float_max_y = rect.bottom();
+
+ int min_x = ToRoundedInt(float_min_x);
+ int min_y = ToRoundedInt(float_min_y);
+ int max_x = ToRoundedInt(float_max_x);
+ int max_y = ToRoundedInt(float_max_y);
+
+ return
+ (std::abs(min_x - float_min_x) < distance) &&
+ (std::abs(min_y - float_min_y) < distance) &&
+ (std::abs(max_x - float_max_x) < distance) &&
+ (std::abs(max_y - float_max_y) < distance);
+}
+
+Rect ToFlooredRectDeprecated(const RectF& rect) {
+ return Rect(ToFlooredInt(rect.x()),
+ ToFlooredInt(rect.y()),
+ ToFlooredInt(rect.width()),
+ ToFlooredInt(rect.height()));
+}
+
+} // namespace gfx
+
diff --git a/chromium/ui/gfx/rect_conversions.h b/chromium/ui/gfx/rect_conversions.h
new file mode 100644
index 00000000000..854fb6ea392
--- /dev/null
+++ b/chromium/ui/gfx/rect_conversions.h
@@ -0,0 +1,36 @@
+// Copyright (c) 2012 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_RECT_CONVERSIONS_H_
+#define UI_GFX_RECT_CONVERSIONS_H_
+
+#include "ui/gfx/rect.h"
+#include "ui/gfx/rect_f.h"
+
+namespace gfx {
+
+// Returns the smallest Rect that encloses the given RectF.
+UI_EXPORT Rect ToEnclosingRect(const RectF& rect);
+
+// Returns the largest Rect that is enclosed by the given RectF.
+UI_EXPORT Rect ToEnclosedRect(const RectF& rect);
+
+// Returns the Rect after snapping the corners of the RectF to an integer grid.
+// This should only be used when the RectF you provide is expected to be an
+// integer rect with floating point error. If it is an arbitrary RectF, then
+// you should use a different method.
+UI_EXPORT Rect ToNearestRect(const RectF& rect);
+
+// Returns true if the Rect produced after snapping the corners of the RectF
+// to an integer grid is withing |distance|.
+UI_EXPORT bool IsNearestRectWithinDistance(
+ const gfx::RectF& rect, float distance);
+
+// Returns a Rect obtained by flooring the values of the given RectF.
+// Please prefer the previous two functions in new code.
+UI_EXPORT Rect ToFlooredRectDeprecated(const RectF& rect);
+
+} // namespace gfx
+
+#endif // UI_GFX_RECT_CONVERSIONS_H_
diff --git a/chromium/ui/gfx/rect_f.cc b/chromium/ui/gfx/rect_f.cc
new file mode 100644
index 00000000000..c55752aa843
--- /dev/null
+++ b/chromium/ui/gfx/rect_f.cc
@@ -0,0 +1,60 @@
+// Copyright (c) 2012 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 "ui/gfx/rect_f.h"
+
+#include <algorithm>
+
+#include "base/logging.h"
+#include "base/strings/stringprintf.h"
+#include "ui/gfx/insets_f.h"
+#include "ui/gfx/rect_base_impl.h"
+#include "ui/gfx/safe_integer_conversions.h"
+
+namespace gfx {
+
+template class RectBase<RectF, PointF, SizeF, InsetsF, Vector2dF, float>;
+
+typedef class RectBase<RectF, PointF, SizeF, InsetsF, Vector2dF,
+ float> RectBaseT;
+
+bool RectF::IsExpressibleAsRect() const {
+ return IsExpressibleAsInt(x()) && IsExpressibleAsInt(y()) &&
+ IsExpressibleAsInt(width()) && IsExpressibleAsInt(height()) &&
+ IsExpressibleAsInt(right()) && IsExpressibleAsInt(bottom());
+}
+
+std::string RectF::ToString() const {
+ return base::StringPrintf("%s %s",
+ origin().ToString().c_str(),
+ size().ToString().c_str());
+}
+
+RectF IntersectRects(const RectF& a, const RectF& b) {
+ RectF result = a;
+ result.Intersect(b);
+ return result;
+}
+
+RectF UnionRects(const RectF& a, const RectF& b) {
+ RectF result = a;
+ result.Union(b);
+ return result;
+}
+
+RectF SubtractRects(const RectF& a, const RectF& b) {
+ RectF result = a;
+ result.Subtract(b);
+ return result;
+}
+
+RectF BoundingRect(const PointF& p1, const PointF& p2) {
+ float rx = std::min(p1.x(), p2.x());
+ float ry = std::min(p1.y(), p2.y());
+ float rr = std::max(p1.x(), p2.x());
+ float rb = std::max(p1.y(), p2.y());
+ return RectF(rx, ry, rr - rx, rb - ry);
+}
+
+} // namespace gfx
diff --git a/chromium/ui/gfx/rect_f.h b/chromium/ui/gfx/rect_f.h
new file mode 100644
index 00000000000..62bedf2e359
--- /dev/null
+++ b/chromium/ui/gfx/rect_f.h
@@ -0,0 +1,113 @@
+// Copyright (c) 2012 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_RECT_F_H_
+#define UI_GFX_RECT_F_H_
+
+#include <string>
+
+#include "ui/gfx/point_f.h"
+#include "ui/gfx/rect_base.h"
+#include "ui/gfx/size_f.h"
+#include "ui/gfx/vector2d_f.h"
+
+namespace gfx {
+
+class InsetsF;
+
+// A floating version of gfx::Rect.
+class UI_EXPORT RectF
+ : public RectBase<RectF, PointF, SizeF, InsetsF, Vector2dF, float> {
+ public:
+ RectF()
+ : RectBase<RectF, PointF, SizeF, InsetsF, Vector2dF, float>
+ (SizeF()) {}
+
+ RectF(float width, float height)
+ : RectBase<RectF, PointF, SizeF, InsetsF, Vector2dF, float>
+ (SizeF(width, height)) {}
+
+ RectF(float x, float y, float width, float height)
+ : RectBase<RectF, PointF, SizeF, InsetsF, Vector2dF, float>
+ (PointF(x, y), SizeF(width, height)) {}
+
+ explicit RectF(const SizeF& size)
+ : RectBase<RectF, PointF, SizeF, InsetsF, Vector2dF, float>
+ (size) {}
+
+ RectF(const PointF& origin, const SizeF& size)
+ : RectBase<RectF, PointF, SizeF, InsetsF, Vector2dF, float>
+ (origin, size) {}
+
+ ~RectF() {}
+
+ // Scales the rectangle by |scale|.
+ void Scale(float scale) {
+ Scale(scale, scale);
+ }
+
+ void Scale(float x_scale, float y_scale) {
+ set_origin(ScalePoint(origin(), x_scale, y_scale));
+ set_size(ScaleSize(size(), x_scale, y_scale));
+ }
+
+ // This method reports if the RectF can be safely converted to an integer
+ // Rect. When it is false, some dimension of the RectF is outside the bounds
+ // of what an integer can represent, and converting it to a Rect will require
+ // clamping.
+ bool IsExpressibleAsRect() const;
+
+ std::string ToString() const;
+};
+
+inline bool operator==(const RectF& lhs, const RectF& rhs) {
+ return lhs.origin() == rhs.origin() && lhs.size() == rhs.size();
+}
+
+inline bool operator!=(const RectF& lhs, const RectF& rhs) {
+ return !(lhs == rhs);
+}
+
+inline RectF operator+(const RectF& lhs, const Vector2dF& rhs) {
+ return RectF(lhs.x() + rhs.x(), lhs.y() + rhs.y(),
+ lhs.width(), lhs.height());
+}
+
+inline RectF operator-(const RectF& lhs, const Vector2dF& rhs) {
+ return RectF(lhs.x() - rhs.x(), lhs.y() - rhs.y(),
+ lhs.width(), lhs.height());
+}
+
+inline RectF operator+(const Vector2dF& lhs, const RectF& rhs) {
+ return rhs + lhs;
+}
+
+UI_EXPORT RectF IntersectRects(const RectF& a, const RectF& b);
+UI_EXPORT RectF UnionRects(const RectF& a, const RectF& b);
+UI_EXPORT RectF SubtractRects(const RectF& a, const RectF& b);
+
+inline RectF ScaleRect(const RectF& r, float x_scale, float y_scale) {
+ return RectF(r.x() * x_scale, r.y() * y_scale,
+ r.width() * x_scale, r.height() * y_scale);
+}
+
+inline RectF ScaleRect(const RectF& r, float scale) {
+ return ScaleRect(r, scale, scale);
+}
+
+// Constructs a rectangle with |p1| and |p2| as opposite corners.
+//
+// This could also be thought of as "the smallest rect that contains both
+// points", except that we consider points on the right/bottom edges of the
+// rect to be outside the rect. So technically one or both points will not be
+// contained within the rect, because they will appear on one of these edges.
+UI_EXPORT RectF BoundingRect(const PointF& p1, const PointF& p2);
+
+#if !defined(COMPILER_MSVC)
+extern template class RectBase<RectF, PointF, SizeF, InsetsF, Vector2dF, float>;
+#endif
+
+} // namespace gfx
+
+#endif // UI_GFX_RECT_F_H_
diff --git a/chromium/ui/gfx/rect_unittest.cc b/chromium/ui/gfx/rect_unittest.cc
new file mode 100644
index 00000000000..0b5f01eef3f
--- /dev/null
+++ b/chromium/ui/gfx/rect_unittest.cc
@@ -0,0 +1,868 @@
+// 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 "base/basictypes.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "ui/gfx/rect.h"
+#include "ui/gfx/rect_conversions.h"
+#include "ui/gfx/skia_util.h"
+
+#include <limits>
+
+namespace gfx {
+
+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},
+ #if defined(NDEBUG) && !defined(DCHECK_ALWAYS_ON)
+ {0, 0, -10, -10, 0, 0, false},
+ #endif
+ };
+ for (size_t i = 0; i < ARRAYSIZE_UNSAFE(contains_cases); ++i) {
+ const ContainsCase& value = contains_cases[i];
+ 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, 0, 0, -10, -10, 20, 20, false },
+ { -10, 0, 0, 20, 0, -10, 20, 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) {
+ Rect r1(tests[i].x1, tests[i].y1, tests[i].w1, tests[i].h1);
+ Rect r2(tests[i].x2, tests[i].y2, tests[i].w2, tests[i].h2);
+ EXPECT_EQ(tests[i].intersects, r1.Intersects(r2));
+ EXPECT_EQ(tests[i].intersects, r2.Intersects(r1));
+ }
+}
+
+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) {
+ Rect r1(tests[i].x1, tests[i].y1, tests[i].w1, tests[i].h1);
+ Rect r2(tests[i].x2, tests[i].y2, tests[i].w2, tests[i].h2);
+ Rect r3(tests[i].x3, tests[i].y3, tests[i].w3, tests[i].h3);
+ Rect ir = IntersectRects(r1, 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) {
+ Rect r1(tests[i].x1, tests[i].y1, tests[i].w1, tests[i].h1);
+ Rect r2(tests[i].x2, tests[i].y2, tests[i].w2, tests[i].h2);
+ Rect r3(tests[i].x3, tests[i].y3, tests[i].w3, tests[i].h3);
+ Rect u = UnionRects(r1, 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(Rect(0, 0, 0, 0) == Rect(0, 0, 0, 0));
+ ASSERT_TRUE(Rect(1, 2, 3, 4) == Rect(1, 2, 3, 4));
+ ASSERT_FALSE(Rect(0, 0, 0, 0) == Rect(0, 0, 0, 1));
+ ASSERT_FALSE(Rect(0, 0, 0, 0) == Rect(0, 0, 1, 0));
+ ASSERT_FALSE(Rect(0, 0, 0, 0) == Rect(0, 1, 0, 0));
+ ASSERT_FALSE(Rect(0, 0, 0, 0) == 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) {
+ Rect r1(tests[i].x1, tests[i].y1, tests[i].w1, tests[i].h1);
+ Rect r2(tests[i].x2, tests[i].y2, tests[i].w2, tests[i].h2);
+ Rect r3(tests[i].x3, tests[i].y3, tests[i].w3, tests[i].h3);
+ Rect u = r1;
+ u.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) {
+ Rect result;
+
+ // Matching
+ result = Rect(10, 10, 20, 20);
+ result.Subtract(Rect(10, 10, 20, 20));
+ EXPECT_EQ(Rect(0, 0, 0, 0).ToString(), result.ToString());
+
+ // Contains
+ result = Rect(10, 10, 20, 20);
+ result.Subtract(Rect(5, 5, 30, 30));
+ EXPECT_EQ(Rect(0, 0, 0, 0).ToString(), result.ToString());
+
+ // No intersection
+ result = Rect(10, 10, 20, 20);
+ result.Subtract(Rect(30, 30, 30, 30));
+ EXPECT_EQ(Rect(10, 10, 20, 20).ToString(), result.ToString());
+
+ // Not a complete intersection in either direction
+ result = Rect(10, 10, 20, 20);
+ result.Subtract(Rect(15, 15, 20, 20));
+ EXPECT_EQ(Rect(10, 10, 20, 20).ToString(), result.ToString());
+
+ // Complete intersection in the x-direction, top edge is fully covered.
+ result = Rect(10, 10, 20, 20);
+ result.Subtract(Rect(10, 15, 20, 20));
+ EXPECT_EQ(Rect(10, 10, 20, 5).ToString(), result.ToString());
+
+ // Complete intersection in the x-direction, top edge is fully covered.
+ result = Rect(10, 10, 20, 20);
+ result.Subtract(Rect(5, 15, 30, 20));
+ EXPECT_EQ(Rect(10, 10, 20, 5).ToString(), result.ToString());
+
+ // Complete intersection in the x-direction, bottom edge is fully covered.
+ result = Rect(10, 10, 20, 20);
+ result.Subtract(Rect(5, 5, 30, 20));
+ EXPECT_EQ(Rect(10, 25, 20, 5).ToString(), result.ToString());
+
+ // Complete intersection in the x-direction, none of the edges is fully
+ // covered.
+ result = Rect(10, 10, 20, 20);
+ result.Subtract(Rect(5, 15, 30, 1));
+ EXPECT_EQ(Rect(10, 10, 20, 20).ToString(), result.ToString());
+
+ // Complete intersection in the y-direction, left edge is fully covered.
+ result = Rect(10, 10, 20, 20);
+ result.Subtract(Rect(10, 10, 10, 30));
+ EXPECT_EQ(Rect(20, 10, 10, 20).ToString(), result.ToString());
+
+ // Complete intersection in the y-direction, left edge is fully covered.
+ result = Rect(10, 10, 20, 20);
+ result.Subtract(Rect(5, 5, 20, 30));
+ EXPECT_EQ(Rect(25, 10, 5, 20).ToString(), result.ToString());
+
+ // Complete intersection in the y-direction, right edge is fully covered.
+ result = Rect(10, 10, 20, 20);
+ result.Subtract(Rect(20, 5, 20, 30));
+ EXPECT_EQ(Rect(10, 10, 10, 20).ToString(), result.ToString());
+
+ // Complete intersection in the y-direction, none of the edges is fully
+ // covered.
+ result = Rect(10, 10, 20, 20);
+ result.Subtract(Rect(15, 5, 1, 30));
+ EXPECT_EQ(Rect(10, 10, 20, 20).ToString(), result.ToString());
+}
+
+TEST(RectTest, IsEmpty) {
+ EXPECT_TRUE(Rect(0, 0, 0, 0).IsEmpty());
+ EXPECT_TRUE(Rect(0, 0, 0, 0).size().IsEmpty());
+ EXPECT_TRUE(Rect(0, 0, 10, 0).IsEmpty());
+ EXPECT_TRUE(Rect(0, 0, 10, 0).size().IsEmpty());
+ EXPECT_TRUE(Rect(0, 0, 0, 10).IsEmpty());
+ EXPECT_TRUE(Rect(0, 0, 0, 10).size().IsEmpty());
+ EXPECT_FALSE(Rect(0, 0, 10, 10).IsEmpty());
+ EXPECT_FALSE(Rect(0, 0, 10, 10).size().IsEmpty());
+}
+
+TEST(RectTest, SplitVertically) {
+ Rect left_half, right_half;
+
+ // Splitting when origin is (0, 0).
+ Rect(0, 0, 20, 20).SplitVertically(&left_half, &right_half);
+ EXPECT_TRUE(left_half == Rect(0, 0, 10, 20));
+ EXPECT_TRUE(right_half == Rect(10, 0, 10, 20));
+
+ // Splitting when origin is arbitrary.
+ Rect(10, 10, 20, 10).SplitVertically(&left_half, &right_half);
+ EXPECT_TRUE(left_half == Rect(10, 10, 10, 10));
+ EXPECT_TRUE(right_half == Rect(20, 10, 10, 10));
+
+ // Splitting a rectangle of zero width.
+ Rect(10, 10, 0, 10).SplitVertically(&left_half, &right_half);
+ EXPECT_TRUE(left_half == Rect(10, 10, 0, 10));
+ EXPECT_TRUE(right_half == Rect(10, 10, 0, 10));
+
+ // Splitting a rectangle of odd width.
+ Rect(10, 10, 5, 10).SplitVertically(&left_half, &right_half);
+ EXPECT_TRUE(left_half == Rect(10, 10, 2, 10));
+ EXPECT_TRUE(right_half == Rect(12, 10, 3, 10));
+}
+
+TEST(RectTest, CenterPoint) {
+ Point center;
+
+ // When origin is (0, 0).
+ center = Rect(0, 0, 20, 20).CenterPoint();
+ EXPECT_TRUE(center == Point(10, 10));
+
+ // When origin is even.
+ center = Rect(10, 10, 20, 20).CenterPoint();
+ EXPECT_TRUE(center == Point(20, 20));
+
+ // When origin is odd.
+ center = Rect(11, 11, 20, 20).CenterPoint();
+ EXPECT_TRUE(center == Point(21, 21));
+
+ // When 0 width or height.
+ center = Rect(10, 10, 0, 20).CenterPoint();
+ EXPECT_TRUE(center == Point(10, 20));
+ center = Rect(10, 10, 20, 0).CenterPoint();
+ EXPECT_TRUE(center == Point(20, 10));
+
+ // When an odd size.
+ center = Rect(10, 10, 21, 21).CenterPoint();
+ EXPECT_TRUE(center == Point(20, 20));
+
+ // When an odd size and position.
+ center = Rect(11, 11, 21, 21).CenterPoint();
+ EXPECT_TRUE(center == Point(21, 21));
+}
+
+TEST(RectTest, CenterPointF) {
+ PointF center;
+
+ // When origin is (0, 0).
+ center = RectF(0, 0, 20, 20).CenterPoint();
+ EXPECT_TRUE(center == PointF(10, 10));
+
+ // When origin is even.
+ center = RectF(10, 10, 20, 20).CenterPoint();
+ EXPECT_TRUE(center == PointF(20, 20));
+
+ // When origin is odd.
+ center = RectF(11, 11, 20, 20).CenterPoint();
+ EXPECT_TRUE(center == PointF(21, 21));
+
+ // When 0 width or height.
+ center = RectF(10, 10, 0, 20).CenterPoint();
+ EXPECT_TRUE(center == PointF(10, 20));
+ center = RectF(10, 10, 20, 0).CenterPoint();
+ EXPECT_TRUE(center == PointF(20, 10));
+
+ // When an odd size.
+ center = RectF(10, 10, 21, 21).CenterPoint();
+ EXPECT_TRUE(center == PointF(20.5f, 20.5f));
+
+ // When an odd size and position.
+ center = RectF(11, 11, 21, 21).CenterPoint();
+ EXPECT_TRUE(center == PointF(21.5f, 21.5f));
+}
+
+TEST(RectTest, SharesEdgeWith) {
+ Rect r(2, 3, 4, 5);
+
+ // Must be non-overlapping
+ EXPECT_FALSE(r.SharesEdgeWith(r));
+
+ Rect just_above(2, 1, 4, 2);
+ Rect just_below(2, 8, 4, 2);
+ Rect just_left(0, 3, 2, 5);
+ 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
+ Rect same_height_no_edge(0, 0, 1, 5);
+ 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));
+
+ Rect just_above_no_edge(2, 1, 5, 2); // too wide
+ Rect just_below_no_edge(2, 8, 3, 2); // too narrow
+ Rect just_left_no_edge(0, 3, 2, 6); // too tall
+ 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));
+}
+
+TEST(RectTest, SkiaRectConversions) {
+ Rect isrc(10, 20, 30, 40);
+ RectF fsrc(10.5f, 20.5f, 30.5f, 40.5f);
+
+ SkIRect skirect = RectToSkIRect(isrc);
+ EXPECT_EQ(isrc.ToString(), SkIRectToRect(skirect).ToString());
+
+ SkRect skrect = RectToSkRect(isrc);
+ EXPECT_EQ(gfx::RectF(isrc).ToString(), SkRectToRectF(skrect).ToString());
+
+ skrect = RectFToSkRect(fsrc);
+ EXPECT_EQ(fsrc.ToString(), SkRectToRectF(skrect).ToString());
+}
+
+// Similar to EXPECT_FLOAT_EQ, but lets NaN equal NaN
+#define EXPECT_FLOAT_AND_NAN_EQ(a, b) \
+ { if (a == a || b == b) { EXPECT_FLOAT_EQ(a, b); } }
+
+TEST(RectTest, ScaleRect) {
+ static const struct Test {
+ int x1; // source
+ int y1;
+ int w1;
+ int h1;
+ float scale;
+ float x2; // target
+ float y2;
+ float w2;
+ float h2;
+ } tests[] = {
+ { 3, 3, 3, 3,
+ 1.5f,
+ 4.5f, 4.5f, 4.5f, 4.5f },
+ { 3, 3, 3, 3,
+ 0.0f,
+ 0.0f, 0.0f, 0.0f, 0.0f },
+ { 3, 3, 3, 3,
+ std::numeric_limits<float>::quiet_NaN(),
+ std::numeric_limits<float>::quiet_NaN(),
+ std::numeric_limits<float>::quiet_NaN(),
+ std::numeric_limits<float>::quiet_NaN(),
+ std::numeric_limits<float>::quiet_NaN() },
+ { 3, 3, 3, 3,
+ std::numeric_limits<float>::max(),
+ std::numeric_limits<float>::max(),
+ std::numeric_limits<float>::max(),
+ std::numeric_limits<float>::max(),
+ std::numeric_limits<float>::max() }
+ };
+
+ for (size_t i = 0; i < ARRAYSIZE_UNSAFE(tests); ++i) {
+ Rect r1(tests[i].x1, tests[i].y1, tests[i].w1, tests[i].h1);
+ RectF r2(tests[i].x2, tests[i].y2, tests[i].w2, tests[i].h2);
+
+ RectF scaled = ScaleRect(r1, tests[i].scale);
+ EXPECT_FLOAT_AND_NAN_EQ(r2.x(), scaled.x());
+ EXPECT_FLOAT_AND_NAN_EQ(r2.y(), scaled.y());
+ EXPECT_FLOAT_AND_NAN_EQ(r2.width(), scaled.width());
+ EXPECT_FLOAT_AND_NAN_EQ(r2.height(), scaled.height());
+ }
+}
+
+TEST(RectTest, ToEnclosedRect) {
+ static const struct Test {
+ float x1; // source
+ float y1;
+ float w1;
+ float h1;
+ int x2; // target
+ int y2;
+ int w2;
+ int h2;
+ } tests [] = {
+ { 0.0f, 0.0f, 0.0f, 0.0f,
+ 0, 0, 0, 0 },
+ { -1.5f, -1.5f, 3.0f, 3.0f,
+ -1, -1, 2, 2 },
+ { -1.5f, -1.5f, 3.5f, 3.5f,
+ -1, -1, 3, 3 },
+ { std::numeric_limits<float>::max(),
+ std::numeric_limits<float>::max(),
+ 2.0f, 2.0f,
+ std::numeric_limits<int>::max(),
+ std::numeric_limits<int>::max(),
+ 0, 0 },
+ { 0.0f, 0.0f,
+ std::numeric_limits<float>::max(),
+ std::numeric_limits<float>::max(),
+ 0, 0,
+ std::numeric_limits<int>::max(),
+ std::numeric_limits<int>::max() },
+ { 20000.5f, 20000.5f, 0.5f, 0.5f,
+ 20001, 20001, 0, 0 },
+ { std::numeric_limits<float>::quiet_NaN(),
+ std::numeric_limits<float>::quiet_NaN(),
+ std::numeric_limits<float>::quiet_NaN(),
+ std::numeric_limits<float>::quiet_NaN(),
+ 0, 0, 0, 0 }
+ };
+
+ for (size_t i = 0; i < ARRAYSIZE_UNSAFE(tests); ++i) {
+ RectF r1(tests[i].x1, tests[i].y1, tests[i].w1, tests[i].h1);
+ Rect r2(tests[i].x2, tests[i].y2, tests[i].w2, tests[i].h2);
+
+ Rect enclosed = ToEnclosedRect(r1);
+ EXPECT_FLOAT_AND_NAN_EQ(r2.x(), enclosed.x());
+ EXPECT_FLOAT_AND_NAN_EQ(r2.y(), enclosed.y());
+ EXPECT_FLOAT_AND_NAN_EQ(r2.width(), enclosed.width());
+ EXPECT_FLOAT_AND_NAN_EQ(r2.height(), enclosed.height());
+ }
+}
+
+TEST(RectTest, ToEnclosingRect) {
+ static const struct Test {
+ float x1; // source
+ float y1;
+ float w1;
+ float h1;
+ int x2; // target
+ int y2;
+ int w2;
+ int h2;
+ } tests [] = {
+ { 0.0f, 0.0f, 0.0f, 0.0f,
+ 0, 0, 0, 0 },
+ { 5.5f, 5.5f, 0.0f, 0.0f,
+ 5, 5, 0, 0 },
+ { -1.5f, -1.5f, 3.0f, 3.0f,
+ -2, -2, 4, 4 },
+ { -1.5f, -1.5f, 3.5f, 3.5f,
+ -2, -2, 4, 4 },
+ { std::numeric_limits<float>::max(),
+ std::numeric_limits<float>::max(),
+ 2.0f, 2.0f,
+ std::numeric_limits<int>::max(),
+ std::numeric_limits<int>::max(),
+ 0, 0 },
+ { 0.0f, 0.0f,
+ std::numeric_limits<float>::max(),
+ std::numeric_limits<float>::max(),
+ 0, 0,
+ std::numeric_limits<int>::max(),
+ std::numeric_limits<int>::max() },
+ { 20000.5f, 20000.5f, 0.5f, 0.5f,
+ 20000, 20000, 1, 1 },
+ { std::numeric_limits<float>::quiet_NaN(),
+ std::numeric_limits<float>::quiet_NaN(),
+ std::numeric_limits<float>::quiet_NaN(),
+ std::numeric_limits<float>::quiet_NaN(),
+ 0, 0, 0, 0 }
+ };
+
+ for (size_t i = 0; i < ARRAYSIZE_UNSAFE(tests); ++i) {
+ RectF r1(tests[i].x1, tests[i].y1, tests[i].w1, tests[i].h1);
+ Rect r2(tests[i].x2, tests[i].y2, tests[i].w2, tests[i].h2);
+
+ Rect enclosed = ToEnclosingRect(r1);
+ EXPECT_FLOAT_AND_NAN_EQ(r2.x(), enclosed.x());
+ EXPECT_FLOAT_AND_NAN_EQ(r2.y(), enclosed.y());
+ EXPECT_FLOAT_AND_NAN_EQ(r2.width(), enclosed.width());
+ EXPECT_FLOAT_AND_NAN_EQ(r2.height(), enclosed.height());
+ }
+}
+
+TEST(RectTest, ToNearestRect) {
+ Rect rect;
+ EXPECT_EQ(rect.ToString(), ToNearestRect(RectF(rect)).ToString());
+
+ rect = Rect(-1, -1, 3, 3);
+ EXPECT_EQ(rect.ToString(), ToNearestRect(RectF(rect)).ToString());
+
+ RectF rectf(-1.00001f, -0.999999f, 3.0000001f, 2.999999f);
+ EXPECT_EQ(rect.ToString(), ToNearestRect(rectf).ToString());
+}
+
+TEST(RectTest, ToFlooredRect) {
+ static const struct Test {
+ float x1; // source
+ float y1;
+ float w1;
+ float h1;
+ int x2; // target
+ int y2;
+ int w2;
+ int h2;
+ } tests [] = {
+ { 0.0f, 0.0f, 0.0f, 0.0f,
+ 0, 0, 0, 0 },
+ { -1.5f, -1.5f, 3.0f, 3.0f,
+ -2, -2, 3, 3 },
+ { -1.5f, -1.5f, 3.5f, 3.5f,
+ -2, -2, 3, 3 },
+ { 20000.5f, 20000.5f, 0.5f, 0.5f,
+ 20000, 20000, 0, 0 },
+ };
+
+ for (size_t i = 0; i < ARRAYSIZE_UNSAFE(tests); ++i) {
+ RectF r1(tests[i].x1, tests[i].y1, tests[i].w1, tests[i].h1);
+ Rect r2(tests[i].x2, tests[i].y2, tests[i].w2, tests[i].h2);
+
+ Rect floored = ToFlooredRectDeprecated(r1);
+ EXPECT_FLOAT_EQ(r2.x(), floored.x());
+ EXPECT_FLOAT_EQ(r2.y(), floored.y());
+ EXPECT_FLOAT_EQ(r2.width(), floored.width());
+ EXPECT_FLOAT_EQ(r2.height(), floored.height());
+ }
+}
+
+TEST(RectTest, ScaleToEnclosedRect) {
+ static const struct Test {
+ Rect input_rect;
+ float input_scale;
+ Rect expected_rect;
+ } tests[] = {
+ {
+ Rect(),
+ 5.f,
+ Rect(),
+ }, {
+ Rect(1, 1, 1, 1),
+ 5.f,
+ Rect(5, 5, 5, 5),
+ }, {
+ Rect(-1, -1, 0, 0),
+ 5.f,
+ Rect(-5, -5, 0, 0),
+ }, {
+ Rect(1, -1, 0, 1),
+ 5.f,
+ Rect(5, -5, 0, 5),
+ }, {
+ Rect(-1, 1, 1, 0),
+ 5.f,
+ Rect(-5, 5, 5, 0),
+ }, {
+ Rect(1, 2, 3, 4),
+ 1.5f,
+ Rect(2, 3, 4, 6),
+ }, {
+ Rect(-1, -2, 0, 0),
+ 1.5f,
+ Rect(-1, -3, 0, 0),
+ }
+ };
+
+ for (size_t i = 0; i < ARRAYSIZE_UNSAFE(tests); ++i) {
+ Rect result = ScaleToEnclosedRect(tests[i].input_rect,
+ tests[i].input_scale);
+ EXPECT_EQ(tests[i].expected_rect.ToString(), result.ToString());
+ }
+}
+
+TEST(RectTest, ScaleToEnclosingRect) {
+ static const struct Test {
+ Rect input_rect;
+ float input_scale;
+ Rect expected_rect;
+ } tests[] = {
+ {
+ Rect(),
+ 5.f,
+ Rect(),
+ }, {
+ Rect(1, 1, 1, 1),
+ 5.f,
+ Rect(5, 5, 5, 5),
+ }, {
+ Rect(-1, -1, 0, 0),
+ 5.f,
+ Rect(-5, -5, 0, 0),
+ }, {
+ Rect(1, -1, 0, 1),
+ 5.f,
+ Rect(5, -5, 0, 5),
+ }, {
+ Rect(-1, 1, 1, 0),
+ 5.f,
+ Rect(-5, 5, 5, 0),
+ }, {
+ Rect(1, 2, 3, 4),
+ 1.5f,
+ Rect(1, 3, 5, 6),
+ }, {
+ Rect(-1, -2, 0, 0),
+ 1.5f,
+ Rect(-2, -3, 0, 0),
+ }
+ };
+
+ for (size_t i = 0; i < ARRAYSIZE_UNSAFE(tests); ++i) {
+ Rect result = ScaleToEnclosingRect(tests[i].input_rect,
+ tests[i].input_scale);
+ EXPECT_EQ(tests[i].expected_rect.ToString(), result.ToString());
+ }
+}
+
+#if defined(OS_WIN)
+TEST(RectTest, ConstructAndAssign) {
+ const RECT rect_1 = { 0, 0, 10, 10 };
+ const RECT rect_2 = { 0, 0, -10, -10 };
+ Rect test1(rect_1);
+ Rect test2(rect_2);
+}
+#endif
+
+TEST(RectTest, ToRectF) {
+ // Check that implicit conversion from integer to float compiles.
+ Rect a(10, 20, 30, 40);
+ RectF b(10, 20, 30, 40);
+
+ RectF intersect = IntersectRects(a, b);
+ EXPECT_EQ(b.ToString(), intersect.ToString());
+
+ EXPECT_EQ(a, b);
+ EXPECT_EQ(b, a);
+}
+
+TEST(RectTest, BoundingRect) {
+ struct {
+ Point a;
+ Point b;
+ Rect expected;
+ } int_tests[] = {
+ // If point B dominates A, then A should be the origin.
+ { Point(4, 6), Point(4, 6), Rect(4, 6, 0, 0) },
+ { Point(4, 6), Point(8, 6), Rect(4, 6, 4, 0) },
+ { Point(4, 6), Point(4, 9), Rect(4, 6, 0, 3) },
+ { Point(4, 6), Point(8, 9), Rect(4, 6, 4, 3) },
+ // If point A dominates B, then B should be the origin.
+ { Point(4, 6), Point(4, 6), Rect(4, 6, 0, 0) },
+ { Point(8, 6), Point(4, 6), Rect(4, 6, 4, 0) },
+ { Point(4, 9), Point(4, 6), Rect(4, 6, 0, 3) },
+ { Point(8, 9), Point(4, 6), Rect(4, 6, 4, 3) },
+ // If neither point dominates, then the origin is a combination of the two.
+ { Point(4, 6), Point(6, 4), Rect(4, 4, 2, 2) },
+ { Point(-4, -6), Point(-6, -4), Rect(-6, -6, 2, 2) },
+ { Point(-4, 6), Point(6, -4), Rect(-4, -4, 10, 10) },
+ };
+
+ for (size_t i = 0; i < ARRAYSIZE_UNSAFE(int_tests); ++i) {
+ Rect actual = BoundingRect(int_tests[i].a, int_tests[i].b);
+ EXPECT_EQ(int_tests[i].expected.ToString(), actual.ToString());
+ }
+
+ struct {
+ PointF a;
+ PointF b;
+ RectF expected;
+ } float_tests[] = {
+ // If point B dominates A, then A should be the origin.
+ { PointF(4.2f, 6.8f), PointF(4.2f, 6.8f),
+ RectF(4.2f, 6.8f, 0, 0) },
+ { PointF(4.2f, 6.8f), PointF(8.5f, 6.8f),
+ RectF(4.2f, 6.8f, 4.3f, 0) },
+ { PointF(4.2f, 6.8f), PointF(4.2f, 9.3f),
+ RectF(4.2f, 6.8f, 0, 2.5f) },
+ { PointF(4.2f, 6.8f), PointF(8.5f, 9.3f),
+ RectF(4.2f, 6.8f, 4.3f, 2.5f) },
+ // If point A dominates B, then B should be the origin.
+ { PointF(4.2f, 6.8f), PointF(4.2f, 6.8f),
+ RectF(4.2f, 6.8f, 0, 0) },
+ { PointF(8.5f, 6.8f), PointF(4.2f, 6.8f),
+ RectF(4.2f, 6.8f, 4.3f, 0) },
+ { PointF(4.2f, 9.3f), PointF(4.2f, 6.8f),
+ RectF(4.2f, 6.8f, 0, 2.5f) },
+ { PointF(8.5f, 9.3f), PointF(4.2f, 6.8f),
+ RectF(4.2f, 6.8f, 4.3f, 2.5f) },
+ // If neither point dominates, then the origin is a combination of the two.
+ { PointF(4.2f, 6.8f), PointF(6.8f, 4.2f),
+ RectF(4.2f, 4.2f, 2.6f, 2.6f) },
+ { PointF(-4.2f, -6.8f), PointF(-6.8f, -4.2f),
+ RectF(-6.8f, -6.8f, 2.6f, 2.6f) },
+ { PointF(-4.2f, 6.8f), PointF(6.8f, -4.2f),
+ RectF(-4.2f, -4.2f, 11.0f, 11.0f) }
+ };
+
+ for (size_t i = 0; i < ARRAYSIZE_UNSAFE(float_tests); ++i) {
+ RectF actual = BoundingRect(float_tests[i].a, float_tests[i].b);
+ EXPECT_EQ(float_tests[i].expected.ToString(), actual.ToString());
+ }
+}
+
+TEST(RectTest, IsExpressibleAsRect) {
+ EXPECT_TRUE(RectF().IsExpressibleAsRect());
+
+ float min = std::numeric_limits<int>::min();
+ float max = std::numeric_limits<int>::max();
+ float infinity = std::numeric_limits<float>::infinity();
+
+ EXPECT_TRUE(RectF(
+ min + 200, min + 200, max - 200, max - 200).IsExpressibleAsRect());
+ EXPECT_FALSE(RectF(
+ min - 200, min + 200, max + 200, max + 200).IsExpressibleAsRect());
+ EXPECT_FALSE(RectF(
+ min + 200 , min - 200, max + 200, max + 200).IsExpressibleAsRect());
+ EXPECT_FALSE(RectF(
+ min + 200, min + 200, max + 200, max - 200).IsExpressibleAsRect());
+ EXPECT_FALSE(RectF(
+ min + 200, min + 200, max - 200, max + 200).IsExpressibleAsRect());
+
+ EXPECT_TRUE(RectF(0, 0, max - 200, max - 200).IsExpressibleAsRect());
+ EXPECT_FALSE(RectF(200, 0, max + 200, max - 200).IsExpressibleAsRect());
+ EXPECT_FALSE(RectF(0, 200, max - 200, max + 200).IsExpressibleAsRect());
+ EXPECT_FALSE(RectF(0, 0, max + 200, max - 200).IsExpressibleAsRect());
+ EXPECT_FALSE(RectF(0, 0, max - 200, max + 200).IsExpressibleAsRect());
+
+ EXPECT_FALSE(RectF(infinity, 0, 1, 1).IsExpressibleAsRect());
+ EXPECT_FALSE(RectF(0, infinity, 1, 1).IsExpressibleAsRect());
+ EXPECT_FALSE(RectF(0, 0, infinity, 1).IsExpressibleAsRect());
+ EXPECT_FALSE(RectF(0, 0, 1, infinity).IsExpressibleAsRect());
+}
+
+TEST(RectTest, Offset) {
+ Rect i(1, 2, 3, 4);
+
+ EXPECT_EQ(Rect(2, 1, 3, 4).ToString(), (i + Vector2d(1, -1)).ToString());
+ EXPECT_EQ(Rect(2, 1, 3, 4).ToString(), (Vector2d(1, -1) + i).ToString());
+ i += Vector2d(1, -1);
+ EXPECT_EQ(Rect(2, 1, 3, 4).ToString(), i.ToString());
+ EXPECT_EQ(Rect(1, 2, 3, 4).ToString(), (i - Vector2d(1, -1)).ToString());
+ i -= Vector2d(1, -1);
+ EXPECT_EQ(Rect(1, 2, 3, 4).ToString(), i.ToString());
+
+ RectF f(1.1f, 2.2f, 3.3f, 4.4f);
+ EXPECT_EQ(RectF(2.2f, 1.1f, 3.3f, 4.4f).ToString(),
+ (f + Vector2dF(1.1f, -1.1f)).ToString());
+ EXPECT_EQ(RectF(2.2f, 1.1f, 3.3f, 4.4f).ToString(),
+ (Vector2dF(1.1f, -1.1f) + f).ToString());
+ f += Vector2dF(1.1f, -1.1f);
+ EXPECT_EQ(RectF(2.2f, 1.1f, 3.3f, 4.4f).ToString(), f.ToString());
+ EXPECT_EQ(RectF(1.1f, 2.2f, 3.3f, 4.4f).ToString(),
+ (f - Vector2dF(1.1f, -1.1f)).ToString());
+ f -= Vector2dF(1.1f, -1.1f);
+ EXPECT_EQ(RectF(1.1f, 2.2f, 3.3f, 4.4f).ToString(), f.ToString());
+}
+
+TEST(RectTest, Corners) {
+ Rect i(1, 2, 3, 4);
+ RectF f(1.1f, 2.1f, 3.1f, 4.1f);
+
+ EXPECT_EQ(Point(1, 2).ToString(), i.origin().ToString());
+ EXPECT_EQ(Point(4, 2).ToString(), i.top_right().ToString());
+ EXPECT_EQ(Point(1, 6).ToString(), i.bottom_left().ToString());
+ EXPECT_EQ(Point(4, 6).ToString(), i.bottom_right().ToString());
+
+ EXPECT_EQ(PointF(1.1f, 2.1f).ToString(), f.origin().ToString());
+ EXPECT_EQ(PointF(4.2f, 2.1f).ToString(), f.top_right().ToString());
+ EXPECT_EQ(PointF(1.1f, 6.2f).ToString(), f.bottom_left().ToString());
+ EXPECT_EQ(PointF(4.2f, 6.2f).ToString(), f.bottom_right().ToString());
+}
+
+} // namespace gfx
diff --git a/chromium/ui/gfx/render_text.cc b/chromium/ui/gfx/render_text.cc
new file mode 100644
index 00000000000..1fcf18cede4
--- /dev/null
+++ b/chromium/ui/gfx/render_text.cc
@@ -0,0 +1,1036 @@
+// Copyright (c) 2012 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 "ui/gfx/render_text.h"
+
+#include <algorithm>
+
+#include "base/i18n/break_iterator.h"
+#include "base/logging.h"
+#include "base/stl_util.h"
+#include "third_party/icu/source/common/unicode/rbbi.h"
+#include "third_party/icu/source/common/unicode/utf16.h"
+#include "third_party/skia/include/core/SkTypeface.h"
+#include "third_party/skia/include/effects/SkGradientShader.h"
+#include "ui/base/text/text_elider.h"
+#include "ui/base/text/utf16_indexing.h"
+#include "ui/gfx/canvas.h"
+#include "ui/gfx/insets.h"
+#include "ui/gfx/skia_util.h"
+#include "ui/gfx/text_constants.h"
+
+namespace gfx {
+
+namespace {
+
+// All chars are replaced by this char when the password style is set.
+// TODO(benrg): GTK uses the first of U+25CF, U+2022, U+2731, U+273A, '*'
+// that's available in the font (find_invisible_char() in gtkentry.c).
+const char16 kPasswordReplacementChar = '*';
+
+// Default color used for the text and cursor.
+const SkColor kDefaultColor = SK_ColorBLACK;
+
+// Default color used for drawing selection background.
+const SkColor kDefaultSelectionBackgroundColor = SK_ColorGRAY;
+
+// Fraction of the text size to lower a strike through below the baseline.
+const SkScalar kStrikeThroughOffset = (-SK_Scalar1 * 6 / 21);
+// Fraction of the text size to lower an underline below the baseline.
+const SkScalar kUnderlineOffset = (SK_Scalar1 / 9);
+// Fraction of the text size to use for a strike through or under-line.
+const SkScalar kLineThickness = (SK_Scalar1 / 18);
+// Fraction of the text size to use for a top margin of a diagonal strike.
+const SkScalar kDiagonalStrikeMarginOffset = (SK_Scalar1 / 4);
+
+// Converts |gfx::Font::FontStyle| flags to |SkTypeface::Style| flags.
+SkTypeface::Style ConvertFontStyleToSkiaTypefaceStyle(int font_style) {
+ int skia_style = SkTypeface::kNormal;
+ skia_style |= (font_style & gfx::Font::BOLD) ? SkTypeface::kBold : 0;
+ skia_style |= (font_style & gfx::Font::ITALIC) ? SkTypeface::kItalic : 0;
+ return static_cast<SkTypeface::Style>(skia_style);
+}
+
+// Given |font| and |display_width|, returns the width of the fade gradient.
+int CalculateFadeGradientWidth(const Font& font, int display_width) {
+ // Fade in/out about 2.5 characters of the beginning/end of the string.
+ // The .5 here is helpful if one of the characters is a space.
+ // Use a quarter of the display width if the display width is very short.
+ const int average_character_width = font.GetAverageCharacterWidth();
+ const double gradient_width = std::min(average_character_width * 2.5,
+ display_width / 4.0);
+ DCHECK_GE(gradient_width, 0.0);
+ return static_cast<int>(floor(gradient_width + 0.5));
+}
+
+// Appends to |positions| and |colors| values corresponding to the fade over
+// |fade_rect| from color |c0| to color |c1|.
+void AddFadeEffect(const Rect& text_rect,
+ const Rect& fade_rect,
+ SkColor c0,
+ SkColor c1,
+ std::vector<SkScalar>* positions,
+ std::vector<SkColor>* colors) {
+ const SkScalar left = static_cast<SkScalar>(fade_rect.x() - text_rect.x());
+ const SkScalar width = static_cast<SkScalar>(fade_rect.width());
+ const SkScalar p0 = left / text_rect.width();
+ const SkScalar p1 = (left + width) / text_rect.width();
+ // Prepend 0.0 to |positions|, as required by Skia.
+ if (positions->empty() && p0 != 0.0) {
+ positions->push_back(0.0);
+ colors->push_back(c0);
+ }
+ positions->push_back(p0);
+ colors->push_back(c0);
+ positions->push_back(p1);
+ colors->push_back(c1);
+}
+
+// Creates a SkShader to fade the text, with |left_part| specifying the left
+// fade effect, if any, and |right_part| specifying the right fade effect.
+skia::RefPtr<SkShader> CreateFadeShader(const Rect& text_rect,
+ const Rect& left_part,
+ const Rect& right_part,
+ SkColor color) {
+ // Fade alpha of 51/255 corresponds to a fade of 0.2 of the original color.
+ const SkColor fade_color = SkColorSetA(color, 51);
+ std::vector<SkScalar> positions;
+ std::vector<SkColor> colors;
+
+ if (!left_part.IsEmpty())
+ AddFadeEffect(text_rect, left_part, fade_color, color,
+ &positions, &colors);
+ if (!right_part.IsEmpty())
+ AddFadeEffect(text_rect, right_part, color, fade_color,
+ &positions, &colors);
+ DCHECK(!positions.empty());
+
+ // Terminate |positions| with 1.0, as required by Skia.
+ if (positions.back() != 1.0) {
+ positions.push_back(1.0);
+ colors.push_back(colors.back());
+ }
+
+ SkPoint points[2];
+ points[0].iset(text_rect.x(), text_rect.y());
+ points[1].iset(text_rect.right(), text_rect.y());
+
+ return skia::AdoptRef(
+ SkGradientShader::CreateLinear(&points[0], &colors[0], &positions[0],
+ colors.size(), SkShader::kClamp_TileMode));
+}
+
+} // namespace
+
+namespace internal {
+
+// Value of |underline_thickness_| that indicates that underline metrics have
+// not been set explicitly.
+const SkScalar kUnderlineMetricsNotSet = -1.0f;
+
+SkiaTextRenderer::SkiaTextRenderer(Canvas* canvas)
+ : canvas_skia_(canvas->sk_canvas()),
+ started_drawing_(false),
+ underline_thickness_(kUnderlineMetricsNotSet),
+ underline_position_(0.0f) {
+ DCHECK(canvas_skia_);
+ paint_.setTextEncoding(SkPaint::kGlyphID_TextEncoding);
+ paint_.setStyle(SkPaint::kFill_Style);
+ paint_.setAntiAlias(true);
+ paint_.setSubpixelText(true);
+ paint_.setLCDRenderText(true);
+ bounds_.setEmpty();
+}
+
+SkiaTextRenderer::~SkiaTextRenderer() {
+ // Work-around for http://crbug.com/122743, where non-ClearType text is
+ // rendered with incorrect gamma when using the fade shader. Draw the text
+ // to a layer and restore it faded by drawing a rect in kDstIn_Mode mode.
+ //
+ // TODO(asvitkine): Remove this work-around once the Skia bug is fixed.
+ // http://code.google.com/p/skia/issues/detail?id=590
+ if (deferred_fade_shader_.get()) {
+ paint_.setShader(deferred_fade_shader_.get());
+ paint_.setXfermodeMode(SkXfermode::kDstIn_Mode);
+ canvas_skia_->drawRect(bounds_, paint_);
+ canvas_skia_->restore();
+ }
+}
+
+void SkiaTextRenderer::SetDrawLooper(SkDrawLooper* draw_looper) {
+ paint_.setLooper(draw_looper);
+}
+
+void SkiaTextRenderer::SetFontSmoothingSettings(bool enable_smoothing,
+ bool enable_lcd_text) {
+ paint_.setAntiAlias(enable_smoothing);
+ paint_.setSubpixelText(enable_smoothing);
+ paint_.setLCDRenderText(enable_lcd_text);
+}
+
+void SkiaTextRenderer::SetTypeface(SkTypeface* typeface) {
+ paint_.setTypeface(typeface);
+}
+
+void SkiaTextRenderer::SetTextSize(SkScalar size) {
+ paint_.setTextSize(size);
+}
+
+void SkiaTextRenderer::SetFontFamilyWithStyle(const std::string& family,
+ int style) {
+ DCHECK(!family.empty());
+
+ SkTypeface::Style skia_style = ConvertFontStyleToSkiaTypefaceStyle(style);
+ skia::RefPtr<SkTypeface> typeface =
+ skia::AdoptRef(SkTypeface::CreateFromName(family.c_str(), skia_style));
+ if (typeface) {
+ // |paint_| adds its own ref. So don't |release()| it from the ref ptr here.
+ SetTypeface(typeface.get());
+
+ // Enable fake bold text if bold style is needed but new typeface does not
+ // have it.
+ paint_.setFakeBoldText((skia_style & SkTypeface::kBold) &&
+ !typeface->isBold());
+ }
+}
+
+void SkiaTextRenderer::SetForegroundColor(SkColor foreground) {
+ paint_.setColor(foreground);
+}
+
+void SkiaTextRenderer::SetShader(SkShader* shader, const Rect& bounds) {
+ bounds_ = RectToSkRect(bounds);
+ paint_.setShader(shader);
+}
+
+void SkiaTextRenderer::SetUnderlineMetrics(SkScalar thickness,
+ SkScalar position) {
+ underline_thickness_ = thickness;
+ underline_position_ = position;
+}
+
+void SkiaTextRenderer::DrawPosText(const SkPoint* pos,
+ const uint16* glyphs,
+ size_t glyph_count) {
+ if (!started_drawing_) {
+ started_drawing_ = true;
+ // Work-around for http://crbug.com/122743, where non-ClearType text is
+ // rendered with incorrect gamma when using the fade shader. Draw the text
+ // to a layer and restore it faded by drawing a rect in kDstIn_Mode mode.
+ //
+ // Skip this when there is a looper which seems not working well with
+ // deferred paint. Currently a looper is only used for text shadows.
+ //
+ // TODO(asvitkine): Remove this work-around once the Skia bug is fixed.
+ // http://code.google.com/p/skia/issues/detail?id=590
+ if (!paint_.isLCDRenderText() &&
+ paint_.getShader() &&
+ !paint_.getLooper()) {
+ deferred_fade_shader_ = skia::SharePtr(paint_.getShader());
+ paint_.setShader(NULL);
+ canvas_skia_->saveLayer(&bounds_, NULL);
+ }
+ }
+
+ const size_t byte_length = glyph_count * sizeof(glyphs[0]);
+ canvas_skia_->drawPosText(&glyphs[0], byte_length, &pos[0], paint_);
+}
+
+void SkiaTextRenderer::DrawDecorations(int x, int y, int width, bool underline,
+ bool strike, bool diagonal_strike) {
+ if (underline)
+ DrawUnderline(x, y, width);
+ if (strike)
+ DrawStrike(x, y, width);
+ if (diagonal_strike)
+ DrawDiagonalStrike(x, y, width);
+}
+
+void SkiaTextRenderer::DrawUnderline(int x, int y, int width) {
+ SkRect r = SkRect::MakeLTRB(x, y + underline_position_, x + width,
+ y + underline_position_ + underline_thickness_);
+ if (underline_thickness_ == kUnderlineMetricsNotSet) {
+ const SkScalar text_size = paint_.getTextSize();
+ r.fTop = SkScalarMulAdd(text_size, kUnderlineOffset, y);
+ r.fBottom = r.fTop + SkScalarMul(text_size, kLineThickness);
+ }
+ canvas_skia_->drawRect(r, paint_);
+}
+
+void SkiaTextRenderer::DrawStrike(int x, int y, int width) const {
+ const SkScalar text_size = paint_.getTextSize();
+ const SkScalar height = SkScalarMul(text_size, kLineThickness);
+ const SkScalar offset = SkScalarMulAdd(text_size, kStrikeThroughOffset, y);
+ const SkRect r = SkRect::MakeLTRB(x, offset, x + width, offset + height);
+ canvas_skia_->drawRect(r, paint_);
+}
+
+void SkiaTextRenderer::DrawDiagonalStrike(int x, int y, int width) const {
+ const SkScalar text_size = paint_.getTextSize();
+ const SkScalar offset = SkScalarMul(text_size, kDiagonalStrikeMarginOffset);
+
+ SkPaint paint(paint_);
+ paint.setAntiAlias(true);
+ paint.setStyle(SkPaint::kFill_Style);
+ paint.setStrokeWidth(SkScalarMul(text_size, kLineThickness) * 2);
+ canvas_skia_->drawLine(x, y, x + width, y - text_size + offset, paint);
+}
+
+StyleIterator::StyleIterator(const BreakList<SkColor>& colors,
+ const std::vector<BreakList<bool> >& styles)
+ : colors_(colors),
+ styles_(styles) {
+ color_ = colors_.breaks().begin();
+ for (size_t i = 0; i < styles_.size(); ++i)
+ style_.push_back(styles_[i].breaks().begin());
+}
+
+StyleIterator::~StyleIterator() {}
+
+ui::Range StyleIterator::GetRange() const {
+ ui::Range range(colors_.GetRange(color_));
+ for (size_t i = 0; i < NUM_TEXT_STYLES; ++i)
+ range = range.Intersect(styles_[i].GetRange(style_[i]));
+ return range;
+}
+
+void StyleIterator::UpdatePosition(size_t position) {
+ color_ = colors_.GetBreak(position);
+ for (size_t i = 0; i < NUM_TEXT_STYLES; ++i)
+ style_[i] = styles_[i].GetBreak(position);
+}
+
+} // namespace internal
+
+RenderText::~RenderText() {
+}
+
+void RenderText::SetText(const base::string16& text) {
+ DCHECK(!composition_range_.IsValid());
+ text_ = text;
+
+ // Adjust ranged styles and colors to accommodate a new text length.
+ const size_t text_length = text_.length();
+ colors_.SetMax(text_length);
+ for (size_t style = 0; style < NUM_TEXT_STYLES; ++style)
+ styles_[style].SetMax(text_length);
+ cached_bounds_and_offset_valid_ = false;
+
+ // Reset selection model. SetText should always followed by SetSelectionModel
+ // or SetCursorPosition in upper layer.
+ SetSelectionModel(SelectionModel());
+
+ // Invalidate the cached text direction if it depends on the text contents.
+ if (directionality_mode_ == DIRECTIONALITY_FROM_TEXT)
+ text_direction_ = base::i18n::UNKNOWN_DIRECTION;
+
+ obscured_reveal_index_ = -1;
+ UpdateLayoutText();
+ ResetLayout();
+}
+
+void RenderText::SetHorizontalAlignment(HorizontalAlignment alignment) {
+ if (horizontal_alignment_ != alignment) {
+ horizontal_alignment_ = alignment;
+ display_offset_ = Vector2d();
+ cached_bounds_and_offset_valid_ = false;
+ }
+}
+
+void RenderText::SetVerticalAlignment(VerticalAlignment alignment) {
+ if (vertical_alignment_ != alignment) {
+ vertical_alignment_ = alignment;
+ display_offset_ = Vector2d();
+ cached_bounds_and_offset_valid_ = false;
+ }
+}
+
+void RenderText::SetFontList(const FontList& font_list) {
+ font_list_ = font_list;
+ cached_bounds_and_offset_valid_ = false;
+ ResetLayout();
+}
+
+void RenderText::SetFont(const Font& font) {
+ SetFontList(FontList(font));
+}
+
+void RenderText::SetFontSize(int size) {
+ SetFontList(font_list_.DeriveFontListWithSize(size));
+}
+
+const Font& RenderText::GetPrimaryFont() const {
+ return font_list_.GetPrimaryFont();
+}
+
+void RenderText::SetCursorEnabled(bool cursor_enabled) {
+ cursor_enabled_ = cursor_enabled;
+ cached_bounds_and_offset_valid_ = false;
+}
+
+void RenderText::ToggleInsertMode() {
+ insert_mode_ = !insert_mode_;
+ cached_bounds_and_offset_valid_ = false;
+}
+
+void RenderText::SetObscured(bool obscured) {
+ if (obscured != obscured_) {
+ obscured_ = obscured;
+ obscured_reveal_index_ = -1;
+ cached_bounds_and_offset_valid_ = false;
+ UpdateLayoutText();
+ ResetLayout();
+ }
+}
+
+void RenderText::SetObscuredRevealIndex(int index) {
+ if (obscured_reveal_index_ == index)
+ return;
+
+ obscured_reveal_index_ = index;
+ cached_bounds_and_offset_valid_ = false;
+ UpdateLayoutText();
+ ResetLayout();
+}
+
+void RenderText::SetDisplayRect(const Rect& r) {
+ display_rect_ = r;
+ cached_bounds_and_offset_valid_ = false;
+}
+
+void RenderText::SetCursorPosition(size_t position) {
+ MoveCursorTo(position, false);
+}
+
+void RenderText::MoveCursor(BreakType break_type,
+ VisualCursorDirection direction,
+ bool select) {
+ SelectionModel position(cursor_position(), selection_model_.caret_affinity());
+ // Cancelling a selection moves to the edge of the selection.
+ if (break_type != LINE_BREAK && !selection().is_empty() && !select) {
+ SelectionModel selection_start = GetSelectionModelForSelectionStart();
+ int start_x = GetCursorBounds(selection_start, true).x();
+ int cursor_x = GetCursorBounds(position, true).x();
+ // Use the selection start if it is left (when |direction| is CURSOR_LEFT)
+ // or right (when |direction| is CURSOR_RIGHT) of the selection end.
+ if (direction == CURSOR_RIGHT ? start_x > cursor_x : start_x < cursor_x)
+ position = selection_start;
+ // For word breaks, use the nearest word boundary in the appropriate
+ // |direction|.
+ if (break_type == WORD_BREAK)
+ position = GetAdjacentSelectionModel(position, break_type, direction);
+ } else {
+ position = GetAdjacentSelectionModel(position, break_type, direction);
+ }
+ if (select)
+ position.set_selection_start(selection().start());
+ MoveCursorTo(position);
+}
+
+bool RenderText::MoveCursorTo(const SelectionModel& model) {
+ // Enforce valid selection model components.
+ size_t text_length = text().length();
+ ui::Range range(std::min(model.selection().start(), text_length),
+ std::min(model.caret_pos(), text_length));
+ // The current model only supports caret positions at valid character indices.
+ if (!IsCursorablePosition(range.start()) ||
+ !IsCursorablePosition(range.end()))
+ return false;
+ SelectionModel sel(range, model.caret_affinity());
+ bool changed = sel != selection_model_;
+ SetSelectionModel(sel);
+ return changed;
+}
+
+bool RenderText::MoveCursorTo(const Point& point, bool select) {
+ SelectionModel position = FindCursorPosition(point);
+ if (select)
+ position.set_selection_start(selection().start());
+ return MoveCursorTo(position);
+}
+
+bool RenderText::SelectRange(const ui::Range& range) {
+ ui::Range sel(std::min(range.start(), text().length()),
+ std::min(range.end(), text().length()));
+ if (!IsCursorablePosition(sel.start()) || !IsCursorablePosition(sel.end()))
+ return false;
+ LogicalCursorDirection affinity =
+ (sel.is_reversed() || sel.is_empty()) ? CURSOR_FORWARD : CURSOR_BACKWARD;
+ SetSelectionModel(SelectionModel(sel, affinity));
+ return true;
+}
+
+bool RenderText::IsPointInSelection(const Point& point) {
+ if (selection().is_empty())
+ return false;
+ SelectionModel cursor = FindCursorPosition(point);
+ return RangeContainsCaret(
+ selection(), cursor.caret_pos(), cursor.caret_affinity());
+}
+
+void RenderText::ClearSelection() {
+ SetSelectionModel(SelectionModel(cursor_position(),
+ selection_model_.caret_affinity()));
+}
+
+void RenderText::SelectAll(bool reversed) {
+ const size_t length = text().length();
+ const ui::Range all = reversed ? ui::Range(length, 0) : ui::Range(0, length);
+ const bool success = SelectRange(all);
+ DCHECK(success);
+}
+
+void RenderText::SelectWord() {
+ if (obscured_) {
+ SelectAll(false);
+ return;
+ }
+
+ size_t selection_max = selection().GetMax();
+
+ base::i18n::BreakIterator iter(text(), base::i18n::BreakIterator::BREAK_WORD);
+ bool success = iter.Init();
+ DCHECK(success);
+ if (!success)
+ return;
+
+ size_t selection_min = selection().GetMin();
+ if (selection_min == text().length() && selection_min != 0)
+ --selection_min;
+
+ for (; selection_min != 0; --selection_min) {
+ if (iter.IsStartOfWord(selection_min) ||
+ iter.IsEndOfWord(selection_min))
+ break;
+ }
+
+ if (selection_min == selection_max && selection_max != text().length())
+ ++selection_max;
+
+ for (; selection_max < text().length(); ++selection_max)
+ if (iter.IsEndOfWord(selection_max) || iter.IsStartOfWord(selection_max))
+ break;
+
+ const bool reversed = selection().is_reversed();
+ MoveCursorTo(reversed ? selection_max : selection_min, false);
+ MoveCursorTo(reversed ? selection_min : selection_max, true);
+}
+
+const ui::Range& RenderText::GetCompositionRange() const {
+ return composition_range_;
+}
+
+void RenderText::SetCompositionRange(const ui::Range& composition_range) {
+ CHECK(!composition_range.IsValid() ||
+ ui::Range(0, text_.length()).Contains(composition_range));
+ composition_range_.set_end(composition_range.end());
+ composition_range_.set_start(composition_range.start());
+ ResetLayout();
+}
+
+void RenderText::SetColor(SkColor value) {
+ colors_.SetValue(value);
+
+#if defined(OS_WIN)
+ // TODO(msw): Windows applies colors and decorations in the layout process.
+ cached_bounds_and_offset_valid_ = false;
+ ResetLayout();
+#endif
+}
+
+void RenderText::ApplyColor(SkColor value, const ui::Range& range) {
+ colors_.ApplyValue(value, range);
+
+#if defined(OS_WIN)
+ // TODO(msw): Windows applies colors and decorations in the layout process.
+ cached_bounds_and_offset_valid_ = false;
+ ResetLayout();
+#endif
+}
+
+void RenderText::SetStyle(TextStyle style, bool value) {
+ styles_[style].SetValue(value);
+
+ // Only invalidate the layout on font changes; not for colors or decorations.
+ bool invalidate = (style == BOLD) || (style == ITALIC);
+#if defined(OS_WIN)
+ // TODO(msw): Windows applies colors and decorations in the layout process.
+ invalidate = true;
+#endif
+ if (invalidate) {
+ cached_bounds_and_offset_valid_ = false;
+ ResetLayout();
+ }
+}
+
+void RenderText::ApplyStyle(TextStyle style,
+ bool value,
+ const ui::Range& range) {
+ styles_[style].ApplyValue(value, range);
+
+ // Only invalidate the layout on font changes; not for colors or decorations.
+ bool invalidate = (style == BOLD) || (style == ITALIC);
+#if defined(OS_WIN)
+ // TODO(msw): Windows applies colors and decorations in the layout process.
+ invalidate = true;
+#endif
+ if (invalidate) {
+ cached_bounds_and_offset_valid_ = false;
+ ResetLayout();
+ }
+}
+
+bool RenderText::GetStyle(TextStyle style) const {
+ return (styles_[style].breaks().size() == 1) &&
+ styles_[style].breaks().front().second;
+}
+
+void RenderText::SetDirectionalityMode(DirectionalityMode mode) {
+ if (mode == directionality_mode_)
+ return;
+
+ directionality_mode_ = mode;
+ text_direction_ = base::i18n::UNKNOWN_DIRECTION;
+ ResetLayout();
+}
+
+base::i18n::TextDirection RenderText::GetTextDirection() {
+ if (text_direction_ == base::i18n::UNKNOWN_DIRECTION) {
+ switch (directionality_mode_) {
+ case DIRECTIONALITY_FROM_TEXT:
+ // Derive the direction from the display text, which differs from text()
+ // in the case of obscured (password) textfields.
+ text_direction_ =
+ base::i18n::GetFirstStrongCharacterDirection(GetLayoutText());
+ break;
+ case DIRECTIONALITY_FROM_UI:
+ text_direction_ = base::i18n::IsRTL() ? base::i18n::RIGHT_TO_LEFT :
+ base::i18n::LEFT_TO_RIGHT;
+ break;
+ case DIRECTIONALITY_FORCE_LTR:
+ text_direction_ = base::i18n::LEFT_TO_RIGHT;
+ break;
+ case DIRECTIONALITY_FORCE_RTL:
+ text_direction_ = base::i18n::RIGHT_TO_LEFT;
+ break;
+ default:
+ NOTREACHED();
+ }
+ }
+
+ return text_direction_;
+}
+
+VisualCursorDirection RenderText::GetVisualDirectionOfLogicalEnd() {
+ return GetTextDirection() == base::i18n::LEFT_TO_RIGHT ?
+ CURSOR_RIGHT : CURSOR_LEFT;
+}
+
+int RenderText::GetContentWidth() {
+ return GetStringSize().width() + (cursor_enabled_ ? 1 : 0);
+}
+
+void RenderText::Draw(Canvas* canvas) {
+ EnsureLayout();
+
+ if (clip_to_display_rect()) {
+ Rect clip_rect(display_rect());
+ clip_rect.Inset(ShadowValue::GetMargin(text_shadows_));
+
+ canvas->Save();
+ canvas->ClipRect(clip_rect);
+ }
+
+ if (!text().empty() && focused())
+ DrawSelection(canvas);
+
+ if (cursor_enabled() && cursor_visible() && focused())
+ DrawCursor(canvas, selection_model_);
+
+ if (!text().empty())
+ DrawVisualText(canvas);
+
+ if (clip_to_display_rect())
+ canvas->Restore();
+}
+
+void RenderText::DrawCursor(Canvas* canvas, const SelectionModel& position) {
+ // Paint cursor. Replace cursor is drawn as rectangle for now.
+ // TODO(msw): Draw a better cursor with a better indication of association.
+ canvas->FillRect(GetCursorBounds(position, true), cursor_color_);
+}
+
+void RenderText::DrawSelectedTextForDrag(Canvas* canvas) {
+ EnsureLayout();
+ const std::vector<Rect> sel = GetSubstringBounds(selection());
+
+ // Override the selection color with black, and force the background to be
+ // transparent so that it's rendered without subpixel antialiasing.
+ const bool saved_background_is_transparent = background_is_transparent();
+ const SkColor saved_selection_color = selection_color();
+ set_background_is_transparent(true);
+ set_selection_color(SK_ColorBLACK);
+
+ for (size_t i = 0; i < sel.size(); ++i) {
+ canvas->Save();
+ canvas->ClipRect(sel[i]);
+ DrawVisualText(canvas);
+ canvas->Restore();
+ }
+
+ // Restore saved transparency and selection color.
+ set_selection_color(saved_selection_color);
+ set_background_is_transparent(saved_background_is_transparent);
+}
+
+Rect RenderText::GetCursorBounds(const SelectionModel& caret,
+ bool insert_mode) {
+ EnsureLayout();
+
+ size_t caret_pos = caret.caret_pos();
+ DCHECK(IsCursorablePosition(caret_pos));
+ // In overtype mode, ignore the affinity and always indicate that we will
+ // overtype the next character.
+ LogicalCursorDirection caret_affinity =
+ insert_mode ? caret.caret_affinity() : CURSOR_FORWARD;
+ int x = 0, width = 1;
+ Size size = GetStringSize();
+ if (caret_pos == (caret_affinity == CURSOR_BACKWARD ? 0 : text().length())) {
+ // The caret is attached to the boundary. Always return a 1-dip width caret,
+ // since there is nothing to overtype.
+ if ((GetTextDirection() == base::i18n::RIGHT_TO_LEFT) == (caret_pos == 0))
+ x = size.width();
+ } else {
+ size_t grapheme_start = (caret_affinity == CURSOR_FORWARD) ?
+ caret_pos : IndexOfAdjacentGrapheme(caret_pos, CURSOR_BACKWARD);
+ ui::Range xspan(GetGlyphBounds(grapheme_start));
+ if (insert_mode) {
+ x = (caret_affinity == CURSOR_BACKWARD) ? xspan.end() : xspan.start();
+ } else { // overtype mode
+ x = xspan.GetMin();
+ width = xspan.length();
+ }
+ }
+ return Rect(ToViewPoint(Point(x, 0)), Size(width, size.height()));
+}
+
+const Rect& RenderText::GetUpdatedCursorBounds() {
+ UpdateCachedBoundsAndOffset();
+ return cursor_bounds_;
+}
+
+size_t RenderText::IndexOfAdjacentGrapheme(size_t index,
+ LogicalCursorDirection direction) {
+ if (index > text().length())
+ return text().length();
+
+ EnsureLayout();
+
+ if (direction == CURSOR_FORWARD) {
+ while (index < text().length()) {
+ index++;
+ if (IsCursorablePosition(index))
+ return index;
+ }
+ return text().length();
+ }
+
+ while (index > 0) {
+ index--;
+ if (IsCursorablePosition(index))
+ return index;
+ }
+ return 0;
+}
+
+SelectionModel RenderText::GetSelectionModelForSelectionStart() {
+ const ui::Range& sel = selection();
+ if (sel.is_empty())
+ return selection_model_;
+ return SelectionModel(sel.start(),
+ sel.is_reversed() ? CURSOR_BACKWARD : CURSOR_FORWARD);
+}
+
+void RenderText::SetTextShadows(const ShadowValues& shadows) {
+ text_shadows_ = shadows;
+}
+
+RenderText::RenderText()
+ : horizontal_alignment_(base::i18n::IsRTL() ? ALIGN_RIGHT : ALIGN_LEFT),
+ vertical_alignment_(ALIGN_VCENTER),
+ directionality_mode_(DIRECTIONALITY_FROM_TEXT),
+ text_direction_(base::i18n::UNKNOWN_DIRECTION),
+ cursor_enabled_(true),
+ cursor_visible_(false),
+ insert_mode_(true),
+ cursor_color_(kDefaultColor),
+ selection_color_(kDefaultColor),
+ selection_background_focused_color_(kDefaultSelectionBackgroundColor),
+ focused_(false),
+ composition_range_(ui::Range::InvalidRange()),
+ colors_(kDefaultColor),
+ styles_(NUM_TEXT_STYLES),
+ composition_and_selection_styles_applied_(false),
+ obscured_(false),
+ obscured_reveal_index_(-1),
+ truncate_length_(0),
+ fade_head_(false),
+ fade_tail_(false),
+ background_is_transparent_(false),
+ clip_to_display_rect_(true),
+ cached_bounds_and_offset_valid_(false) {
+}
+
+const Vector2d& RenderText::GetUpdatedDisplayOffset() {
+ UpdateCachedBoundsAndOffset();
+ return display_offset_;
+}
+
+SelectionModel RenderText::GetAdjacentSelectionModel(
+ const SelectionModel& current,
+ BreakType break_type,
+ VisualCursorDirection direction) {
+ EnsureLayout();
+
+ if (break_type == LINE_BREAK || text().empty())
+ return EdgeSelectionModel(direction);
+ if (break_type == CHARACTER_BREAK)
+ return AdjacentCharSelectionModel(current, direction);
+ DCHECK(break_type == WORD_BREAK);
+ return AdjacentWordSelectionModel(current, direction);
+}
+
+SelectionModel RenderText::EdgeSelectionModel(
+ VisualCursorDirection direction) {
+ if (direction == GetVisualDirectionOfLogicalEnd())
+ return SelectionModel(text().length(), CURSOR_FORWARD);
+ return SelectionModel(0, CURSOR_BACKWARD);
+}
+
+void RenderText::SetSelectionModel(const SelectionModel& model) {
+ DCHECK_LE(model.selection().GetMax(), text().length());
+ selection_model_ = model;
+ cached_bounds_and_offset_valid_ = false;
+}
+
+const base::string16& RenderText::GetLayoutText() const {
+ return layout_text_.empty() ? text_ : layout_text_;
+}
+
+void RenderText::ApplyCompositionAndSelectionStyles() {
+ // Save the underline and color breaks to undo the temporary styles later.
+ DCHECK(!composition_and_selection_styles_applied_);
+ saved_colors_ = colors_;
+ saved_underlines_ = styles_[UNDERLINE];
+
+ // Apply an underline to the composition range in |underlines|.
+ if (composition_range_.IsValid() && !composition_range_.is_empty())
+ styles_[UNDERLINE].ApplyValue(true, composition_range_);
+
+ // Apply the selected text color to the [un-reversed] selection range.
+ if (!selection().is_empty()) {
+ const ui::Range range(selection().GetMin(), selection().GetMax());
+ colors_.ApplyValue(selection_color_, range);
+ }
+ composition_and_selection_styles_applied_ = true;
+}
+
+void RenderText::UndoCompositionAndSelectionStyles() {
+ // Restore the underline and color breaks to undo the temporary styles.
+ DCHECK(composition_and_selection_styles_applied_);
+ colors_ = saved_colors_;
+ styles_[UNDERLINE] = saved_underlines_;
+ composition_and_selection_styles_applied_ = false;
+}
+
+Vector2d RenderText::GetTextOffset() {
+ Vector2d offset = display_rect().OffsetFromOrigin();
+ offset.Add(GetUpdatedDisplayOffset());
+ offset.Add(GetAlignmentOffset());
+ return offset;
+}
+
+Point RenderText::ToTextPoint(const Point& point) {
+ return point - GetTextOffset();
+}
+
+Point RenderText::ToViewPoint(const Point& point) {
+ return point + GetTextOffset();
+}
+
+Vector2d RenderText::GetAlignmentOffset() {
+ Vector2d offset;
+ if (horizontal_alignment_ != ALIGN_LEFT) {
+ offset.set_x(display_rect().width() - GetContentWidth());
+ if (horizontal_alignment_ == ALIGN_CENTER)
+ offset.set_x(offset.x() / 2);
+ }
+ if (vertical_alignment_ != ALIGN_TOP) {
+ offset.set_y(display_rect().height() - GetStringSize().height());
+ if (vertical_alignment_ == ALIGN_VCENTER)
+ offset.set_y(offset.y() / 2);
+ }
+ return offset;
+}
+
+void RenderText::ApplyFadeEffects(internal::SkiaTextRenderer* renderer) {
+ if (!fade_head() && !fade_tail())
+ return;
+
+ const int text_width = GetStringSize().width();
+ const int display_width = display_rect().width();
+
+ // If the text fits as-is, no need to fade.
+ if (text_width <= display_width)
+ return;
+
+ int gradient_width = CalculateFadeGradientWidth(GetPrimaryFont(),
+ display_width);
+ if (gradient_width == 0)
+ return;
+
+ bool fade_left = fade_head();
+ bool fade_right = fade_tail();
+ // Under RTL, |fade_right| == |fade_head|.
+ // TODO(asvitkine): This is currently not based on GetTextDirection() because
+ // RenderTextWin does not return a direction that's based on
+ // the text content.
+ if (horizontal_alignment_ == ALIGN_RIGHT)
+ std::swap(fade_left, fade_right);
+
+ Rect solid_part = display_rect();
+ Rect left_part;
+ Rect right_part;
+ if (fade_left) {
+ left_part = solid_part;
+ left_part.Inset(0, 0, solid_part.width() - gradient_width, 0);
+ solid_part.Inset(gradient_width, 0, 0, 0);
+ }
+ if (fade_right) {
+ right_part = solid_part;
+ right_part.Inset(solid_part.width() - gradient_width, 0, 0, 0);
+ solid_part.Inset(0, 0, gradient_width, 0);
+ }
+
+ Rect text_rect = display_rect();
+ text_rect.Inset(GetAlignmentOffset().x(), 0, 0, 0);
+
+ // TODO(msw): Use the actual text colors corresponding to each faded part.
+ skia::RefPtr<SkShader> shader = CreateFadeShader(
+ text_rect, left_part, right_part, colors_.breaks().front().second);
+ if (shader)
+ renderer->SetShader(shader.get(), display_rect());
+}
+
+void RenderText::ApplyTextShadows(internal::SkiaTextRenderer* renderer) {
+ skia::RefPtr<SkDrawLooper> looper = CreateShadowDrawLooper(text_shadows_);
+ renderer->SetDrawLooper(looper.get());
+}
+
+// static
+bool RenderText::RangeContainsCaret(const ui::Range& range,
+ size_t caret_pos,
+ LogicalCursorDirection caret_affinity) {
+ // NB: exploits unsigned wraparound (WG14/N1124 section 6.2.5 paragraph 9).
+ size_t adjacent = (caret_affinity == CURSOR_BACKWARD) ?
+ caret_pos - 1 : caret_pos + 1;
+ return range.Contains(ui::Range(caret_pos, adjacent));
+}
+
+void RenderText::MoveCursorTo(size_t position, bool select) {
+ size_t cursor = std::min(position, text().length());
+ if (IsCursorablePosition(cursor))
+ SetSelectionModel(SelectionModel(
+ ui::Range(select ? selection().start() : cursor, cursor),
+ (cursor == 0) ? CURSOR_FORWARD : CURSOR_BACKWARD));
+}
+
+void RenderText::UpdateLayoutText() {
+ layout_text_.clear();
+
+ if (obscured_) {
+ size_t obscured_text_length =
+ static_cast<size_t>(ui::UTF16IndexToOffset(text_, 0, text_.length()));
+ layout_text_.assign(obscured_text_length, kPasswordReplacementChar);
+
+ if (obscured_reveal_index_ >= 0 &&
+ obscured_reveal_index_ < static_cast<int>(text_.length())) {
+ // Gets the index range in |text_| to be revealed.
+ size_t start = obscured_reveal_index_;
+ U16_SET_CP_START(text_.data(), 0, start);
+ size_t end = start;
+ UChar32 unused_char;
+ U16_NEXT(text_.data(), end, text_.length(), unused_char);
+
+ // Gets the index in |layout_text_| to be replaced.
+ const size_t cp_start =
+ static_cast<size_t>(ui::UTF16IndexToOffset(text_, 0, start));
+ if (layout_text_.length() > cp_start)
+ layout_text_.replace(cp_start, 1, text_.substr(start, end - start));
+ }
+ }
+
+ const base::string16& text = obscured_ ? layout_text_ : text_;
+ if (truncate_length_ > 0 && truncate_length_ < text.length()) {
+ // Truncate the text at a valid character break and append an ellipsis.
+ icu::StringCharacterIterator iter(text.c_str());
+ iter.setIndex32(truncate_length_ - 1);
+ layout_text_.assign(text.substr(0, iter.getIndex()) + ui::kEllipsisUTF16);
+ }
+}
+
+void RenderText::UpdateCachedBoundsAndOffset() {
+ if (cached_bounds_and_offset_valid_)
+ return;
+
+ // First, set the valid flag true to calculate the current cursor bounds using
+ // the stale |display_offset_|. Applying |delta_offset| at the end of this
+ // function will set |cursor_bounds_| and |display_offset_| to correct values.
+ cached_bounds_and_offset_valid_ = true;
+ cursor_bounds_ = GetCursorBounds(selection_model_, insert_mode_);
+
+ // Update |display_offset_| to ensure the current cursor is visible.
+ const int display_width = display_rect_.width();
+ const int content_width = GetContentWidth();
+
+ int delta_x = 0;
+ if (content_width <= display_width || !cursor_enabled()) {
+ // Don't pan if the text fits in the display width or when the cursor is
+ // disabled.
+ delta_x = -display_offset_.x();
+ } else if (cursor_bounds_.right() > display_rect_.right()) {
+ // TODO(xji): when the character overflow is a RTL character, currently, if
+ // we pan cursor at the rightmost position, the entered RTL character is not
+ // displayed. Should pan cursor to show the last logical characters.
+ //
+ // Pan to show the cursor when it overflows to the right.
+ delta_x = display_rect_.right() - cursor_bounds_.right();
+ } else if (cursor_bounds_.x() < display_rect_.x()) {
+ // TODO(xji): have similar problem as above when overflow character is a
+ // LTR character.
+ //
+ // Pan to show the cursor when it overflows to the left.
+ delta_x = display_rect_.x() - cursor_bounds_.x();
+ } else if (display_offset_.x() != 0) {
+ // Reduce the pan offset to show additional overflow text when the display
+ // width increases.
+ const int negate_rtl = horizontal_alignment_ == ALIGN_RIGHT ? -1 : 1;
+ const int offset = negate_rtl * display_offset_.x();
+ if (display_width > (content_width + offset)) {
+ delta_x = negate_rtl * (display_width - (content_width + offset));
+ }
+ }
+
+ Vector2d delta_offset(delta_x, 0);
+ display_offset_ += delta_offset;
+ cursor_bounds_ += delta_offset;
+}
+
+void RenderText::DrawSelection(Canvas* canvas) {
+ const std::vector<Rect> sel = GetSubstringBounds(selection());
+ for (std::vector<Rect>::const_iterator i = sel.begin(); i < sel.end(); ++i)
+ canvas->FillRect(*i, selection_background_focused_color_);
+}
+
+} // namespace gfx
diff --git a/chromium/ui/gfx/render_text.h b/chromium/ui/gfx/render_text.h
new file mode 100644
index 00000000000..34277400b73
--- /dev/null
+++ b/chromium/ui/gfx/render_text.h
@@ -0,0 +1,582 @@
+// Copyright (c) 2012 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_RENDER_TEXT_H_
+#define UI_GFX_RENDER_TEXT_H_
+
+#include <algorithm>
+#include <cstring>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "base/gtest_prod_util.h"
+#include "base/i18n/rtl.h"
+#include "base/strings/string16.h"
+#include "skia/ext/refptr.h"
+#include "third_party/skia/include/core/SkColor.h"
+#include "third_party/skia/include/core/SkPaint.h"
+#include "third_party/skia/include/core/SkRect.h"
+#include "ui/base/range/range.h"
+#include "ui/gfx/break_list.h"
+#include "ui/gfx/font_list.h"
+#include "ui/gfx/point.h"
+#include "ui/gfx/rect.h"
+#include "ui/gfx/selection_model.h"
+#include "ui/gfx/shadow_value.h"
+#include "ui/gfx/text_constants.h"
+#include "ui/gfx/vector2d.h"
+
+class SkCanvas;
+class SkDrawLooper;
+struct SkPoint;
+class SkShader;
+class SkTypeface;
+
+namespace gfx {
+
+class Canvas;
+class Font;
+class RenderTextTest;
+
+namespace internal {
+
+// Internal helper class used by derived classes to draw text through Skia.
+class SkiaTextRenderer {
+ public:
+ explicit SkiaTextRenderer(Canvas* canvas);
+ ~SkiaTextRenderer();
+
+ void SetDrawLooper(SkDrawLooper* draw_looper);
+ void SetFontSmoothingSettings(bool enable_smoothing, bool enable_lcd_text);
+ void SetTypeface(SkTypeface* typeface);
+ void SetTextSize(SkScalar size);
+ void SetFontFamilyWithStyle(const std::string& family, int font_style);
+ void SetForegroundColor(SkColor foreground);
+ void SetShader(SkShader* shader, const Rect& bounds);
+ // Sets underline metrics to use if the text will be drawn with an underline.
+ // If not set, default values based on the size of the text will be used. The
+ // two metrics must be set together.
+ void SetUnderlineMetrics(SkScalar thickness, SkScalar position);
+ void DrawSelection(const std::vector<Rect>& selection, SkColor color);
+ void DrawPosText(const SkPoint* pos,
+ const uint16* glyphs,
+ size_t glyph_count);
+ // Draw underline and strike-through text decorations.
+ // Based on |SkCanvas::DrawTextDecorations()| and constants from:
+ // third_party/skia/src/core/SkTextFormatParams.h
+ void DrawDecorations(int x, int y, int width, bool underline, bool strike,
+ bool diagonal_strike);
+ void DrawUnderline(int x, int y, int width);
+ void DrawStrike(int x, int y, int width) const;
+ void DrawDiagonalStrike(int x, int y, int width) const;
+
+ private:
+ SkCanvas* canvas_skia_;
+ bool started_drawing_;
+ SkPaint paint_;
+ SkRect bounds_;
+ skia::RefPtr<SkShader> deferred_fade_shader_;
+ SkScalar underline_thickness_;
+ SkScalar underline_position_;
+
+ DISALLOW_COPY_AND_ASSIGN(SkiaTextRenderer);
+};
+
+// Internal helper class used by derived classes to iterate colors and styles.
+class StyleIterator {
+ public:
+ StyleIterator(const BreakList<SkColor>& colors,
+ const std::vector<BreakList<bool> >& styles);
+ ~StyleIterator();
+
+ // Get the colors and styles at the current iterator position.
+ SkColor color() const { return color_->second; }
+ bool style(TextStyle s) const { return style_[s]->second; }
+
+ // Get the intersecting range of the current iterator set.
+ ui::Range GetRange() const;
+
+ // Update the iterator to point to colors and styles applicable at |position|.
+ void UpdatePosition(size_t position);
+
+ private:
+ BreakList<SkColor> colors_;
+ std::vector<BreakList<bool> > styles_;
+
+ BreakList<SkColor>::const_iterator color_;
+ std::vector<BreakList<bool>::const_iterator> style_;
+
+ DISALLOW_COPY_AND_ASSIGN(StyleIterator);
+};
+
+} // namespace internal
+
+// RenderText represents an abstract model of styled text and its corresponding
+// visual layout. Support is built in for a cursor, a selection, simple styling,
+// complex scripts, and bi-directional text. Implementations provide mechanisms
+// for rendering and translation between logical and visual data.
+class UI_EXPORT RenderText {
+ public:
+ virtual ~RenderText();
+
+ // Creates a platform-specific RenderText instance.
+ static RenderText* CreateInstance();
+
+ const base::string16& text() const { return text_; }
+ void SetText(const base::string16& text);
+
+ HorizontalAlignment horizontal_alignment() const {
+ return horizontal_alignment_;
+ }
+ void SetHorizontalAlignment(HorizontalAlignment alignment);
+
+ VerticalAlignment vertical_alignment() const {
+ return vertical_alignment_;
+ }
+ void SetVerticalAlignment(VerticalAlignment alignment);
+
+ const FontList& font_list() const { return font_list_; }
+ void SetFontList(const FontList& font_list);
+ void SetFont(const Font& font);
+
+ // Set the font size to |size| in pixels.
+ void SetFontSize(int size);
+
+ // Get the first font in |font_list_|.
+ const Font& GetPrimaryFont() const;
+
+ bool cursor_enabled() const { return cursor_enabled_; }
+ void SetCursorEnabled(bool cursor_enabled);
+
+ bool cursor_visible() const { return cursor_visible_; }
+ void set_cursor_visible(bool visible) { cursor_visible_ = visible; }
+
+ bool insert_mode() const { return insert_mode_; }
+ void ToggleInsertMode();
+
+ SkColor cursor_color() const { return cursor_color_; }
+ void set_cursor_color(SkColor color) { cursor_color_ = color; }
+
+ SkColor selection_color() const { return selection_color_; }
+ void set_selection_color(SkColor color) { selection_color_ = color; }
+
+ SkColor selection_background_focused_color() const {
+ return selection_background_focused_color_;
+ }
+ void set_selection_background_focused_color(SkColor color) {
+ selection_background_focused_color_ = color;
+ }
+
+ bool focused() const { return focused_; }
+ void set_focused(bool focused) { focused_ = focused; }
+
+ bool clip_to_display_rect() const { return clip_to_display_rect_; }
+ void set_clip_to_display_rect(bool clip) { clip_to_display_rect_ = clip; }
+
+ // In an obscured (password) field, all text is drawn as asterisks or bullets.
+ bool obscured() const { return obscured_; }
+ void SetObscured(bool obscured);
+
+ // Makes a char in obscured text at |index| to be revealed. |index| should be
+ // a UTF16 text index. If there is a previous revealed index, the previous one
+ // is cleared and only the last set index will be revealed. If |index| is -1
+ // or out of range, no char will be revealed. The revealed index is also
+ // cleared when SetText or SetObscured is called.
+ void SetObscuredRevealIndex(int index);
+
+ // Set the maximum length of the displayed layout text, not the actual text.
+ // A |length| of 0 forgoes a hard limit, but does not guarantee proper
+ // functionality of very long strings. Applies to subsequent SetText calls.
+ // WARNING: Only use this for system limits, it lacks complex text support.
+ void set_truncate_length(size_t length) { truncate_length_ = length; }
+
+ const Rect& display_rect() const { return display_rect_; }
+ void SetDisplayRect(const Rect& r);
+
+ void set_fade_head(bool fade_head) { fade_head_ = fade_head; }
+ bool fade_head() const { return fade_head_; }
+ void set_fade_tail(bool fade_tail) { fade_tail_ = fade_tail; }
+ bool fade_tail() const { return fade_tail_; }
+
+ bool background_is_transparent() const { return background_is_transparent_; }
+ void set_background_is_transparent(bool transparent) {
+ background_is_transparent_ = transparent;
+ }
+
+ const SelectionModel& selection_model() const { return selection_model_; }
+
+ const ui::Range& selection() const { return selection_model_.selection(); }
+
+ size_t cursor_position() const { return selection_model_.caret_pos(); }
+ void SetCursorPosition(size_t position);
+
+ // Moves the cursor left or right. Cursor movement is visual, meaning that
+ // left and right are relative to screen, not the directionality of the text.
+ // If |select| is false, the selection start is moved to the same position.
+ void MoveCursor(BreakType break_type,
+ VisualCursorDirection direction,
+ bool select);
+
+ // Set the selection_model_ to the value of |selection|.
+ // The selection range is clamped to text().length() if out of range.
+ // Returns true if the cursor position or selection range changed.
+ // If any index in |selection_model| is not a cursorable position (not on a
+ // grapheme boundary), it is a no-op and returns false.
+ bool MoveCursorTo(const SelectionModel& selection_model);
+
+ // Move the cursor to the position associated with the clicked point.
+ // If |select| is false, the selection start is moved to the same position.
+ // Returns true if the cursor position or selection range changed.
+ bool MoveCursorTo(const Point& point, bool select);
+
+ // Set the selection_model_ based on |range|.
+ // If the |range| start or end is greater than text length, it is modified
+ // to be the text length.
+ // If the |range| start or end is not a cursorable position (not on grapheme
+ // boundary), it is a NO-OP and returns false. Otherwise, returns true.
+ bool SelectRange(const ui::Range& range);
+
+ // Returns true if the local point is over selected text.
+ bool IsPointInSelection(const Point& point);
+
+ // Selects no text, keeping the current cursor position and caret affinity.
+ void ClearSelection();
+
+ // Select the entire text range. If |reversed| is true, the range will end at
+ // the logical beginning of the text; this generally shows the leading portion
+ // of text that overflows its display area.
+ void SelectAll(bool reversed);
+
+ // Selects the word at the current cursor position. If there is a non-empty
+ // selection, the selection bounds are extended to their nearest word
+ // boundaries.
+ void SelectWord();
+
+ const ui::Range& GetCompositionRange() const;
+ void SetCompositionRange(const ui::Range& composition_range);
+
+ // Set the text color over the entire text or a logical character range.
+ // The |range| should be valid, non-reversed, and within [0, text().length()].
+ void SetColor(SkColor value);
+ void ApplyColor(SkColor value, const ui::Range& range);
+
+ // Set various text styles over the entire text or a logical character range.
+ // The respective |style| is applied if |value| is true, or removed if false.
+ // The |range| should be valid, non-reversed, and within [0, text().length()].
+ void SetStyle(TextStyle style, bool value);
+ void ApplyStyle(TextStyle style, bool value, const ui::Range& range);
+
+ // Returns whether this style is enabled consistently across the entire
+ // RenderText.
+ bool GetStyle(TextStyle style) const;
+
+ // Set the text directionality mode and get the text direction yielded.
+ void SetDirectionalityMode(DirectionalityMode mode);
+ base::i18n::TextDirection GetTextDirection();
+
+ // Returns the visual movement direction corresponding to the logical end
+ // of the text, considering only the dominant direction returned by
+ // |GetTextDirection()|, not the direction of a particular run.
+ VisualCursorDirection GetVisualDirectionOfLogicalEnd();
+
+ // Returns the size in pixels of the entire string. For the height, this will
+ // return the maximum height among the different fonts in the text runs.
+ // Note that this returns the raw size of the string, which does not include
+ // the margin area of text shadows.
+ virtual Size GetStringSize() = 0;
+
+ // Returns the width of content, which reserves room for the cursor if
+ // |cursor_enabled_| is true.
+ int GetContentWidth();
+
+ // Returns the common baseline of the text. The returned value is the vertical
+ // offset from the top of |display_rect| to the text baseline, in pixels.
+ virtual int GetBaseline() = 0;
+
+ void Draw(Canvas* canvas);
+
+ // Draws a cursor at |position|.
+ void DrawCursor(Canvas* canvas, const SelectionModel& position);
+
+ // Draw the selected text without a cursor or selection highlight. Subpixel
+ // antialiasing is disabled and foreground color is forced to black.
+ void DrawSelectedTextForDrag(Canvas* canvas);
+
+ // Gets the SelectionModel from a visual point in local coordinates.
+ virtual SelectionModel FindCursorPosition(const Point& point) = 0;
+
+ // Get the visual bounds of a cursor at |selection|. These bounds typically
+ // represent a vertical line, but if |insert_mode| is true they contain the
+ // bounds of the associated glyph. These bounds are in local coordinates, but
+ // may be outside the visible region if the text is longer than the textfield.
+ // Subsequent text, cursor, or bounds changes may invalidate returned values.
+ Rect GetCursorBounds(const SelectionModel& selection, bool insert_mode);
+
+ // Compute the current cursor bounds, panning the text to show the cursor in
+ // the display rect if necessary. These bounds are in local coordinates.
+ // Subsequent text, cursor, or bounds changes may invalidate returned values.
+ const Rect& GetUpdatedCursorBounds();
+
+ // Given an |index| in text(), return the next or previous grapheme boundary
+ // in logical order (that is, the nearest index for which
+ // |IsCursorablePosition(index)| returns true). The return value is in the
+ // range 0 to text().length() inclusive (the input is clamped if it is out of
+ // that range). Always moves by at least one character index unless the
+ // supplied index is already at the boundary of the string.
+ size_t IndexOfAdjacentGrapheme(size_t index,
+ LogicalCursorDirection direction);
+
+ // Return a SelectionModel with the cursor at the current selection's start.
+ // The returned value represents a cursor/caret position without a selection.
+ SelectionModel GetSelectionModelForSelectionStart();
+
+ // Sets shadows to drawn with text.
+ void SetTextShadows(const ShadowValues& shadows);
+
+ typedef std::pair<Font, ui::Range> FontSpan;
+ // For testing purposes, returns which fonts were chosen for which parts of
+ // the text by returning a vector of Font and Range pairs, where each range
+ // specifies the character range for which the corresponding font has been
+ // chosen.
+ virtual std::vector<FontSpan> GetFontSpansForTesting() = 0;
+
+ protected:
+ RenderText();
+
+ const BreakList<SkColor>& colors() const { return colors_; }
+ const std::vector<BreakList<bool> >& styles() const { return styles_; }
+
+ const Vector2d& GetUpdatedDisplayOffset();
+
+ void set_cached_bounds_and_offset_valid(bool valid) {
+ cached_bounds_and_offset_valid_ = valid;
+ }
+
+ // Get the selection model that visually neighbors |position| by |break_type|.
+ // The returned value represents a cursor/caret position without a selection.
+ SelectionModel GetAdjacentSelectionModel(const SelectionModel& current,
+ BreakType break_type,
+ VisualCursorDirection direction);
+
+ // Get the selection model visually left/right of |selection| by one grapheme.
+ // The returned value represents a cursor/caret position without a selection.
+ virtual SelectionModel AdjacentCharSelectionModel(
+ const SelectionModel& selection,
+ VisualCursorDirection direction) = 0;
+
+ // Get the selection model visually left/right of |selection| by one word.
+ // The returned value represents a cursor/caret position without a selection.
+ virtual SelectionModel AdjacentWordSelectionModel(
+ const SelectionModel& selection,
+ VisualCursorDirection direction) = 0;
+
+ // Get the SelectionModels corresponding to visual text ends.
+ // The returned value represents a cursor/caret position without a selection.
+ SelectionModel EdgeSelectionModel(VisualCursorDirection direction);
+
+ // Sets the selection model, the argument is assumed to be valid.
+ virtual void SetSelectionModel(const SelectionModel& model);
+
+ // Get the horizontal bounds (relative to the left of the text, not the view)
+ // of the glyph starting at |index|. If the glyph is RTL then the returned
+ // Range will have is_reversed() true. (This does not return a Rect because a
+ // Rect can't have a negative width.)
+ virtual ui::Range GetGlyphBounds(size_t index) = 0;
+
+ // Get the visual bounds containing the logical substring within the |range|.
+ // If |range| is empty, the result is empty. These bounds could be visually
+ // discontinuous if the substring is split by a LTR/RTL level change.
+ // These bounds are in local coordinates, but may be outside the visible
+ // region if the text is longer than the textfield. Subsequent text, cursor,
+ // or bounds changes may invalidate returned values.
+ virtual std::vector<Rect> GetSubstringBounds(const ui::Range& range) = 0;
+
+ // Convert between indices into |text_| and indices into |obscured_text_|,
+ // which differ when the text is obscured. Regardless of whether or not the
+ // text is obscured, the character (code point) offsets always match.
+ virtual size_t TextIndexToLayoutIndex(size_t index) const = 0;
+ virtual size_t LayoutIndexToTextIndex(size_t index) const = 0;
+
+ // Return true if cursor can appear in front of the character at |position|,
+ // which means it is a grapheme boundary or the first character in the text.
+ virtual bool IsCursorablePosition(size_t position) = 0;
+
+ // Reset the layout to be invalid.
+ virtual void ResetLayout() = 0;
+
+ // Ensure the text is laid out.
+ virtual void EnsureLayout() = 0;
+
+ // Draw the text.
+ virtual void DrawVisualText(Canvas* canvas) = 0;
+
+ // Returns the text used for layout, which may be obscured or truncated.
+ const base::string16& GetLayoutText() const;
+
+ // Apply (and undo) temporary composition underlines and selection colors.
+ void ApplyCompositionAndSelectionStyles();
+ void UndoCompositionAndSelectionStyles();
+
+ // Returns the text offset from the origin after applying text alignment and
+ // display offset.
+ Vector2d GetTextOffset();
+
+ // Convert points from the text space to the view space and back.
+ // Handles the display area, display offset, and the application LTR/RTL mode.
+ Point ToTextPoint(const Point& point);
+ Point ToViewPoint(const Point& point);
+
+ // Returns the text offset from the origin, taking into account text alignment
+ // only.
+ Vector2d GetAlignmentOffset();
+
+ // Applies fade effects to |renderer|.
+ void ApplyFadeEffects(internal::SkiaTextRenderer* renderer);
+
+ // Applies text shadows to |renderer|.
+ void ApplyTextShadows(internal::SkiaTextRenderer* renderer);
+
+ // A convenience function to check whether the glyph attached to the caret
+ // is within the given range.
+ static bool RangeContainsCaret(const ui::Range& range,
+ size_t caret_pos,
+ LogicalCursorDirection caret_affinity);
+
+ private:
+ friend class RenderTextTest;
+ FRIEND_TEST_ALL_PREFIXES(RenderTextTest, DefaultStyle);
+ FRIEND_TEST_ALL_PREFIXES(RenderTextTest, SetColorAndStyle);
+ FRIEND_TEST_ALL_PREFIXES(RenderTextTest, ApplyColorAndStyle);
+ FRIEND_TEST_ALL_PREFIXES(RenderTextTest, ObscuredText);
+ FRIEND_TEST_ALL_PREFIXES(RenderTextTest, RevealObscuredText);
+ FRIEND_TEST_ALL_PREFIXES(RenderTextTest, TruncatedText);
+ FRIEND_TEST_ALL_PREFIXES(RenderTextTest, TruncatedObscuredText);
+ FRIEND_TEST_ALL_PREFIXES(RenderTextTest, GraphemePositions);
+ FRIEND_TEST_ALL_PREFIXES(RenderTextTest, EdgeSelectionModels);
+ FRIEND_TEST_ALL_PREFIXES(RenderTextTest, GetTextOffset);
+ FRIEND_TEST_ALL_PREFIXES(RenderTextTest, GetTextOffsetHorizontalDefaultInRTL);
+
+ // Set the cursor to |position|, with the caret trailing the previous
+ // grapheme, or if there is no previous grapheme, leading the cursor position.
+ // If |select| is false, the selection start is moved to the same position.
+ // If the |position| is not a cursorable position (not on grapheme boundary),
+ // it is a NO-OP.
+ void MoveCursorTo(size_t position, bool select);
+
+ // Updates |layout_text_| if the text is obscured or truncated.
+ void UpdateLayoutText();
+
+ // Update the cached bounds and display offset to ensure that the current
+ // cursor is within the visible display area.
+ void UpdateCachedBoundsAndOffset();
+
+ // Draw the selection.
+ void DrawSelection(Canvas* canvas);
+
+ // Logical UTF-16 string data to be drawn.
+ base::string16 text_;
+
+ // Horizontal alignment of the text with respect to |display_rect_|. The
+ // default is to align left if the application UI is LTR and right if RTL.
+ HorizontalAlignment horizontal_alignment_;
+
+ // Vertical alignment of the text with respect to |display_rect_|. The
+ // default is to align vertically centered.
+ VerticalAlignment vertical_alignment_;
+
+ // The text directionality mode, defaults to DIRECTIONALITY_FROM_TEXT.
+ DirectionalityMode directionality_mode_;
+
+ // The cached text direction, potentially computed from the text or UI locale.
+ // Use GetTextDirection(), do not use this potentially invalid value directly!
+ base::i18n::TextDirection text_direction_;
+
+ // A list of fonts used to render |text_|.
+ FontList font_list_;
+
+ // Logical selection range and visual cursor position.
+ SelectionModel selection_model_;
+
+ // The cached cursor bounds; get these bounds with GetUpdatedCursorBounds.
+ Rect cursor_bounds_;
+
+ // Specifies whether the cursor is enabled. If disabled, no space is reserved
+ // for the cursor when positioning text.
+ bool cursor_enabled_;
+
+ // The cursor visibility and insert mode.
+ bool cursor_visible_;
+ bool insert_mode_;
+
+ // The color used for the cursor.
+ SkColor cursor_color_;
+
+ // The color used for drawing selected text.
+ SkColor selection_color_;
+
+ // The background color used for drawing the selection when focused.
+ SkColor selection_background_focused_color_;
+
+ // The focus state of the text.
+ bool focused_;
+
+ // Composition text range.
+ ui::Range composition_range_;
+
+ // Color and style breaks, used to color and stylize ranges of text.
+ // BreakList positions are stored with text indices, not layout indices.
+ // TODO(msw): Expand to support cursor, selection, background, etc. colors.
+ BreakList<SkColor> colors_;
+ std::vector<BreakList<bool> > styles_;
+
+ // Breaks saved without temporary composition and selection styling.
+ BreakList<SkColor> saved_colors_;
+ BreakList<bool> saved_underlines_;
+ bool composition_and_selection_styles_applied_;
+
+ // A flag to obscure actual text with asterisks for password fields.
+ bool obscured_;
+ // The index at which the char should be revealed in the obscured text.
+ int obscured_reveal_index_;
+
+ // The maximum length of text to display, 0 forgoes a hard limit.
+ size_t truncate_length_;
+
+ // The obscured and/or truncated text that will be displayed.
+ base::string16 layout_text_;
+
+ // Fade text head and/or tail, if text doesn't fit into |display_rect_|.
+ bool fade_head_;
+ bool fade_tail_;
+
+ // Is the background transparent (either partially or fully)?
+ bool background_is_transparent_;
+
+ // The local display area for rendering the text.
+ Rect display_rect_;
+
+ // Flag to work around a Skia bug with the PDF path (http://crbug.com/133548)
+ // that results in incorrect clipping when drawing to the document margins.
+ // This field allows disabling clipping to work around the issue.
+ // TODO(asvitkine): Remove this when the underlying Skia bug is fixed.
+ bool clip_to_display_rect_;
+
+ // The offset for the text to be drawn, relative to the display area.
+ // Get this point with GetUpdatedDisplayOffset (or risk using a stale value).
+ Vector2d display_offset_;
+
+ // The cached bounds and offset are invalidated by changes to the cursor,
+ // selection, font, and other operations that adjust the visible text bounds.
+ bool cached_bounds_and_offset_valid_;
+
+ // Text shadows to be drawn.
+ ShadowValues text_shadows_;
+
+ DISALLOW_COPY_AND_ASSIGN(RenderText);
+};
+
+} // namespace gfx
+
+#endif // UI_GFX_RENDER_TEXT_H_
diff --git a/chromium/ui/gfx/render_text_linux.cc b/chromium/ui/gfx/render_text_linux.cc
new file mode 100644
index 00000000000..0178cf8368b
--- /dev/null
+++ b/chromium/ui/gfx/render_text_linux.cc
@@ -0,0 +1,513 @@
+// Copyright (c) 2012 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 "ui/gfx/render_text_linux.h"
+
+#include <pango/pangocairo.h>
+#include <algorithm>
+#include <string>
+#include <vector>
+
+#include "base/i18n/break_iterator.h"
+#include "base/logging.h"
+#include "third_party/skia/include/core/SkTypeface.h"
+#include "ui/base/text/utf16_indexing.h"
+#include "ui/gfx/canvas.h"
+#include "ui/gfx/font.h"
+#include "ui/gfx/font_render_params_linux.h"
+#include "ui/gfx/pango_util.h"
+
+namespace gfx {
+
+namespace {
+
+// Returns the preceding element in a GSList (O(n)).
+GSList* GSListPrevious(GSList* head, GSList* item) {
+ GSList* prev = NULL;
+ for (GSList* cur = head; cur != item; cur = cur->next) {
+ DCHECK(cur);
+ prev = cur;
+ }
+ return prev;
+}
+
+// Returns true if the given visual cursor |direction| is logically forward
+// motion in the given Pango |item|.
+bool IsForwardMotion(VisualCursorDirection direction, const PangoItem* item) {
+ bool rtl = item->analysis.level & 1;
+ return rtl == (direction == CURSOR_LEFT);
+}
+
+// Checks whether |range| contains |index|. This is not the same as calling
+// |range.Contains(ui::Range(index))| - as that would return true when
+// |index| == |range.end()|.
+bool IndexInRange(const ui::Range& range, size_t index) {
+ return index >= range.start() && index < range.end();
+}
+
+// Sets underline metrics on |renderer| according to Pango font |desc|.
+void SetPangoUnderlineMetrics(PangoFontDescription *desc,
+ internal::SkiaTextRenderer* renderer) {
+ PangoFontMetrics* metrics = GetPangoFontMetrics(desc);
+ int thickness = pango_font_metrics_get_underline_thickness(metrics);
+ // Pango returns the position "above the baseline". Change its sign to convert
+ // it to a vertical offset from the baseline.
+ int position = -pango_font_metrics_get_underline_position(metrics);
+ pango_quantize_line_geometry(&thickness, &position);
+ // Note: pango_quantize_line_geometry() guarantees pixel boundaries, so
+ // PANGO_PIXELS() is safe to use.
+ renderer->SetUnderlineMetrics(PANGO_PIXELS(thickness),
+ PANGO_PIXELS(position));
+}
+
+} // namespace
+
+// TODO(xji): index saved in upper layer is utf16 index. Pango uses utf8 index.
+// Since caret_pos is used internally, we could save utf8 index for caret_pos
+// to avoid conversion.
+
+RenderTextLinux::RenderTextLinux()
+ : layout_(NULL),
+ current_line_(NULL),
+ log_attrs_(NULL),
+ num_log_attrs_(0),
+ layout_text_(NULL) {
+}
+
+RenderTextLinux::~RenderTextLinux() {
+ ResetLayout();
+}
+
+Size RenderTextLinux::GetStringSize() {
+ EnsureLayout();
+ int width = 0, height = 0;
+ pango_layout_get_pixel_size(layout_, &width, &height);
+ // Keep a consistent height between this particular string's PangoLayout and
+ // potentially larger text supported by the FontList.
+ // For example, if a text field contains a Japanese character, which is
+ // smaller than Latin ones, and then later a Latin one is inserted, this
+ // ensures that the text baseline does not shift.
+ return Size(width, std::max(height, font_list().GetHeight()));
+}
+
+int RenderTextLinux::GetBaseline() {
+ EnsureLayout();
+ // Keep a consistent baseline between this particular string's PangoLayout and
+ // potentially larger text supported by the FontList.
+ // See the example in GetStringSize().
+ return std::max(PANGO_PIXELS(pango_layout_get_baseline(layout_)),
+ font_list().GetBaseline());
+}
+
+SelectionModel RenderTextLinux::FindCursorPosition(const Point& point) {
+ EnsureLayout();
+
+ if (text().empty())
+ return SelectionModel(0, CURSOR_FORWARD);
+
+ Point p(ToTextPoint(point));
+
+ // When the point is outside of text, return HOME/END position.
+ if (p.x() < 0)
+ return EdgeSelectionModel(CURSOR_LEFT);
+ if (p.x() > GetStringSize().width())
+ return EdgeSelectionModel(CURSOR_RIGHT);
+
+ int caret_pos = 0, trailing = 0;
+ pango_layout_xy_to_index(layout_, p.x() * PANGO_SCALE, p.y() * PANGO_SCALE,
+ &caret_pos, &trailing);
+
+ DCHECK_GE(trailing, 0);
+ if (trailing > 0) {
+ caret_pos = g_utf8_offset_to_pointer(layout_text_ + caret_pos,
+ trailing) - layout_text_;
+ DCHECK_LE(static_cast<size_t>(caret_pos), strlen(layout_text_));
+ }
+
+ return SelectionModel(LayoutIndexToTextIndex(caret_pos),
+ (trailing > 0) ? CURSOR_BACKWARD : CURSOR_FORWARD);
+}
+
+std::vector<RenderText::FontSpan> RenderTextLinux::GetFontSpansForTesting() {
+ EnsureLayout();
+
+ std::vector<RenderText::FontSpan> spans;
+ for (GSList* it = current_line_->runs; it; it = it->next) {
+ PangoItem* item = reinterpret_cast<PangoLayoutRun*>(it->data)->item;
+ const int start = LayoutIndexToTextIndex(item->offset);
+ const int end = LayoutIndexToTextIndex(item->offset + item->length);
+ const ui::Range range(start, end);
+
+ ScopedPangoFontDescription desc(pango_font_describe(item->analysis.font));
+ spans.push_back(RenderText::FontSpan(Font(desc.get()), range));
+ }
+
+ return spans;
+}
+
+SelectionModel RenderTextLinux::AdjacentCharSelectionModel(
+ const SelectionModel& selection,
+ VisualCursorDirection direction) {
+ GSList* run = GetRunContainingCaret(selection);
+ if (!run) {
+ // The cursor is not in any run: we're at the visual and logical edge.
+ SelectionModel edge = EdgeSelectionModel(direction);
+ if (edge.caret_pos() == selection.caret_pos())
+ return edge;
+ else
+ run = (direction == CURSOR_RIGHT) ?
+ current_line_->runs : g_slist_last(current_line_->runs);
+ } else {
+ // If the cursor is moving within the current run, just move it by one
+ // grapheme in the appropriate direction.
+ PangoItem* item = reinterpret_cast<PangoLayoutRun*>(run->data)->item;
+ size_t caret = selection.caret_pos();
+ if (IsForwardMotion(direction, item)) {
+ if (caret < LayoutIndexToTextIndex(item->offset + item->length)) {
+ caret = IndexOfAdjacentGrapheme(caret, CURSOR_FORWARD);
+ return SelectionModel(caret, CURSOR_BACKWARD);
+ }
+ } else {
+ if (caret > LayoutIndexToTextIndex(item->offset)) {
+ caret = IndexOfAdjacentGrapheme(caret, CURSOR_BACKWARD);
+ return SelectionModel(caret, CURSOR_FORWARD);
+ }
+ }
+ // The cursor is at the edge of a run; move to the visually adjacent run.
+ // TODO(xji): Keep a vector of runs to avoid using a singly-linked list.
+ run = (direction == CURSOR_RIGHT) ?
+ run->next : GSListPrevious(current_line_->runs, run);
+ if (!run)
+ return EdgeSelectionModel(direction);
+ }
+ PangoItem* item = reinterpret_cast<PangoLayoutRun*>(run->data)->item;
+ return IsForwardMotion(direction, item) ?
+ FirstSelectionModelInsideRun(item) : LastSelectionModelInsideRun(item);
+}
+
+SelectionModel RenderTextLinux::AdjacentWordSelectionModel(
+ const SelectionModel& selection,
+ VisualCursorDirection direction) {
+ if (obscured())
+ return EdgeSelectionModel(direction);
+
+ base::i18n::BreakIterator iter(text(), base::i18n::BreakIterator::BREAK_WORD);
+ bool success = iter.Init();
+ DCHECK(success);
+ if (!success)
+ return selection;
+
+ SelectionModel cur(selection);
+ for (;;) {
+ cur = AdjacentCharSelectionModel(cur, direction);
+ GSList* run = GetRunContainingCaret(cur);
+ if (!run)
+ break;
+ PangoItem* item = reinterpret_cast<PangoLayoutRun*>(run->data)->item;
+ size_t cursor = cur.caret_pos();
+ if (IsForwardMotion(direction, item) ?
+ iter.IsEndOfWord(cursor) : iter.IsStartOfWord(cursor))
+ break;
+ }
+
+ return cur;
+}
+
+ui::Range RenderTextLinux::GetGlyphBounds(size_t index) {
+ PangoRectangle pos;
+ pango_layout_index_to_pos(layout_, TextIndexToLayoutIndex(index), &pos);
+ // TODO(derat): Support fractional ranges for subpixel positioning?
+ return ui::Range(PANGO_PIXELS(pos.x), PANGO_PIXELS(pos.x + pos.width));
+}
+
+std::vector<Rect> RenderTextLinux::GetSubstringBounds(const ui::Range& range) {
+ DCHECK_LE(range.GetMax(), text().length());
+ if (range.is_empty())
+ return std::vector<Rect>();
+
+ EnsureLayout();
+ int* ranges = NULL;
+ int n_ranges = 0;
+ pango_layout_line_get_x_ranges(current_line_,
+ TextIndexToLayoutIndex(range.GetMin()),
+ TextIndexToLayoutIndex(range.GetMax()),
+ &ranges,
+ &n_ranges);
+
+ const int height = GetStringSize().height();
+
+ std::vector<Rect> bounds;
+ for (int i = 0; i < n_ranges; ++i) {
+ // TODO(derat): Support fractional bounds for subpixel positioning?
+ int x = PANGO_PIXELS(ranges[2 * i]);
+ int width = PANGO_PIXELS(ranges[2 * i + 1]) - x;
+ Rect rect(x, 0, width, height);
+ rect.set_origin(ToViewPoint(rect.origin()));
+ bounds.push_back(rect);
+ }
+ g_free(ranges);
+ return bounds;
+}
+
+size_t RenderTextLinux::TextIndexToLayoutIndex(size_t index) const {
+ DCHECK(layout_);
+ ptrdiff_t offset = ui::UTF16IndexToOffset(text(), 0, index);
+ // Clamp layout indices to the length of the text actually used for layout.
+ offset = std::min<size_t>(offset, g_utf8_strlen(layout_text_, -1));
+ const char* layout_pointer = g_utf8_offset_to_pointer(layout_text_, offset);
+ return (layout_pointer - layout_text_);
+}
+
+size_t RenderTextLinux::LayoutIndexToTextIndex(size_t index) const {
+ DCHECK(layout_);
+ const char* layout_pointer = layout_text_ + index;
+ const long offset = g_utf8_pointer_to_offset(layout_text_, layout_pointer);
+ return ui::UTF16OffsetToIndex(text(), 0, offset);
+}
+
+bool RenderTextLinux::IsCursorablePosition(size_t position) {
+ if (position == 0 && text().empty())
+ return true;
+ if (position >= text().length())
+ return position == text().length();
+ if (!ui::IsValidCodePointIndex(text(), position))
+ return false;
+
+ EnsureLayout();
+ ptrdiff_t offset = ui::UTF16IndexToOffset(text(), 0, position);
+ // Check that the index corresponds with a valid text code point, that it is
+ // marked as a legitimate cursor position by Pango, and that it is not
+ // truncated from layout text (its glyph is shown on screen).
+ return (offset < num_log_attrs_ && log_attrs_[offset].is_cursor_position &&
+ offset < g_utf8_strlen(layout_text_, -1));
+}
+
+void RenderTextLinux::ResetLayout() {
+ // set_cached_bounds_and_offset_valid(false) is done in RenderText for every
+ // operation that triggers ResetLayout().
+ if (layout_) {
+ g_object_unref(layout_);
+ layout_ = NULL;
+ }
+ if (current_line_) {
+ pango_layout_line_unref(current_line_);
+ current_line_ = NULL;
+ }
+ if (log_attrs_) {
+ g_free(log_attrs_);
+ log_attrs_ = NULL;
+ num_log_attrs_ = 0;
+ }
+ layout_text_ = NULL;
+}
+
+void RenderTextLinux::EnsureLayout() {
+ if (layout_ == NULL) {
+ cairo_surface_t* surface =
+ cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 0, 0);
+ cairo_t* cr = cairo_create(surface);
+
+ layout_ = pango_cairo_create_layout(cr);
+ cairo_destroy(cr);
+ cairo_surface_destroy(surface);
+
+ SetupPangoLayoutWithFontDescription(layout_,
+ GetLayoutText(),
+ font_list().GetFontDescriptionString(),
+ 0,
+ GetTextDirection(),
+ Canvas::DefaultCanvasTextAlignment());
+
+ // No width set so that the x-axis position is relative to the start of the
+ // text. ToViewPoint and ToTextPoint take care of the position conversion
+ // between text space and view spaces.
+ pango_layout_set_width(layout_, -1);
+ // TODO(xji): If RenderText will be used for displaying purpose, such as
+ // label, we will need to remove the single-line-mode setting.
+ pango_layout_set_single_paragraph_mode(layout_, true);
+
+ layout_text_ = pango_layout_get_text(layout_);
+ SetupPangoAttributes(layout_);
+
+ current_line_ = pango_layout_get_line_readonly(layout_, 0);
+ pango_layout_line_ref(current_line_);
+
+ pango_layout_get_log_attrs(layout_, &log_attrs_, &num_log_attrs_);
+ }
+}
+
+void RenderTextLinux::SetupPangoAttributes(PangoLayout* layout) {
+ PangoAttrList* attrs = pango_attr_list_new();
+
+ // Splitting text runs to accommodate styling can break Arabic glyph shaping.
+ // Only split text runs as needed for bold and italic font styles changes.
+ BreakList<bool>::const_iterator bold = styles()[BOLD].breaks().begin();
+ BreakList<bool>::const_iterator italic = styles()[ITALIC].breaks().begin();
+ while (bold != styles()[BOLD].breaks().end() &&
+ italic != styles()[ITALIC].breaks().end()) {
+ const int style = (bold->second ? Font::BOLD : 0) |
+ (italic->second ? Font::ITALIC : 0);
+ const size_t bold_end = styles()[BOLD].GetRange(bold).end();
+ const size_t italic_end = styles()[ITALIC].GetRange(italic).end();
+ const size_t style_end = std::min(bold_end, italic_end);
+ if (style != font_list().GetFontStyle()) {
+ FontList derived_font_list = font_list().DeriveFontList(style);
+ ScopedPangoFontDescription desc(pango_font_description_from_string(
+ derived_font_list.GetFontDescriptionString().c_str()));
+
+ PangoAttribute* pango_attr = pango_attr_font_desc_new(desc.get());
+ pango_attr->start_index =
+ TextIndexToLayoutIndex(std::max(bold->first, italic->first));
+ pango_attr->end_index = TextIndexToLayoutIndex(style_end);
+ pango_attr_list_insert(attrs, pango_attr);
+ }
+ bold += bold_end == style_end ? 1 : 0;
+ italic += italic_end == style_end ? 1 : 0;
+ }
+ DCHECK(bold == styles()[BOLD].breaks().end());
+ DCHECK(italic == styles()[ITALIC].breaks().end());
+
+ pango_layout_set_attributes(layout, attrs);
+ pango_attr_list_unref(attrs);
+}
+
+void RenderTextLinux::DrawVisualText(Canvas* canvas) {
+ DCHECK(layout_);
+
+ // Skia will draw glyphs with respect to the baseline.
+ Vector2d offset(GetTextOffset() + Vector2d(0, GetBaseline()));
+
+ SkScalar x = SkIntToScalar(offset.x());
+ SkScalar y = SkIntToScalar(offset.y());
+
+ std::vector<SkPoint> pos;
+ std::vector<uint16> glyphs;
+
+ internal::SkiaTextRenderer renderer(canvas);
+ ApplyFadeEffects(&renderer);
+ ApplyTextShadows(&renderer);
+
+ // TODO(derat): Use font-specific params: http://crbug.com/125235
+ const gfx::FontRenderParams& render_params =
+ gfx::GetDefaultFontRenderParams();
+ const bool use_subpixel_rendering =
+ render_params.subpixel_rendering !=
+ gfx::FontRenderParams::SUBPIXEL_RENDERING_NONE;
+ renderer.SetFontSmoothingSettings(
+ render_params.antialiasing,
+ use_subpixel_rendering && !background_is_transparent());
+
+ // Temporarily apply composition underlines and selection colors.
+ ApplyCompositionAndSelectionStyles();
+
+ internal::StyleIterator style(colors(), styles());
+ for (GSList* it = current_line_->runs; it; it = it->next) {
+ PangoLayoutRun* run = reinterpret_cast<PangoLayoutRun*>(it->data);
+ int glyph_count = run->glyphs->num_glyphs;
+ // TODO(msw): Skip painting runs outside the display rect area, like Win.
+ if (glyph_count == 0)
+ continue;
+
+ ScopedPangoFontDescription desc(
+ pango_font_describe(run->item->analysis.font));
+
+ const std::string family_name =
+ pango_font_description_get_family(desc.get());
+ renderer.SetTextSize(GetPangoFontSizeInPixels(desc.get()));
+
+ glyphs.resize(glyph_count);
+ pos.resize(glyph_count);
+
+ // Track the current glyph and the glyph at the start of its styled range.
+ int glyph_index = 0;
+ int style_start_glyph_index = glyph_index;
+
+ // Track the x-coordinates for each styled range (|x| marks the current).
+ SkScalar style_start_x = x;
+
+ // Track the current style and its text (not layout) index range.
+ style.UpdatePosition(GetGlyphTextIndex(run, style_start_glyph_index));
+ ui::Range style_range = style.GetRange();
+
+ do {
+ const PangoGlyphInfo& glyph = run->glyphs->glyphs[glyph_index];
+ glyphs[glyph_index] = static_cast<uint16>(glyph.glyph);
+ // Use pango_units_to_double() rather than PANGO_PIXELS() here, so units
+ // are not rounded to the pixel grid if subpixel positioning is enabled.
+ pos[glyph_index].set(x + pango_units_to_double(glyph.geometry.x_offset),
+ y + pango_units_to_double(glyph.geometry.y_offset));
+ x += pango_units_to_double(glyph.geometry.width);
+
+ ++glyph_index;
+ const size_t glyph_text_index = (glyph_index == glyph_count) ?
+ style_range.end() : GetGlyphTextIndex(run, glyph_index);
+ if (!IndexInRange(style_range, glyph_text_index)) {
+ // TODO(asvitkine): For cases like "fi", where "fi" is a single glyph
+ // but can span multiple styles, Pango splits the
+ // styles evenly over the glyph. We can do this too by
+ // clipping and drawing the glyph several times.
+ renderer.SetForegroundColor(style.color());
+ const int font_style = (style.style(BOLD) ? Font::BOLD : 0) |
+ (style.style(ITALIC) ? Font::ITALIC : 0);
+ renderer.SetFontFamilyWithStyle(family_name, font_style);
+ renderer.DrawPosText(&pos[style_start_glyph_index],
+ &glyphs[style_start_glyph_index],
+ glyph_index - style_start_glyph_index);
+ if (style.style(UNDERLINE))
+ SetPangoUnderlineMetrics(desc.get(), &renderer);
+ renderer.DrawDecorations(style_start_x, y, x - style_start_x,
+ style.style(UNDERLINE), style.style(STRIKE),
+ style.style(DIAGONAL_STRIKE));
+ style.UpdatePosition(glyph_text_index);
+ style_range = style.GetRange();
+ style_start_glyph_index = glyph_index;
+ style_start_x = x;
+ }
+ } while (glyph_index < glyph_count);
+ }
+
+ // Undo the temporarily applied composition underlines and selection colors.
+ UndoCompositionAndSelectionStyles();
+}
+
+GSList* RenderTextLinux::GetRunContainingCaret(
+ const SelectionModel& caret) const {
+ size_t position = TextIndexToLayoutIndex(caret.caret_pos());
+ LogicalCursorDirection affinity = caret.caret_affinity();
+ GSList* run = current_line_->runs;
+ while (run) {
+ PangoItem* item = reinterpret_cast<PangoLayoutRun*>(run->data)->item;
+ ui::Range item_range(item->offset, item->offset + item->length);
+ if (RangeContainsCaret(item_range, position, affinity))
+ return run;
+ run = run->next;
+ }
+ return NULL;
+}
+
+SelectionModel RenderTextLinux::FirstSelectionModelInsideRun(
+ const PangoItem* item) {
+ size_t caret = IndexOfAdjacentGrapheme(
+ LayoutIndexToTextIndex(item->offset), CURSOR_FORWARD);
+ return SelectionModel(caret, CURSOR_BACKWARD);
+}
+
+SelectionModel RenderTextLinux::LastSelectionModelInsideRun(
+ const PangoItem* item) {
+ size_t caret = IndexOfAdjacentGrapheme(
+ LayoutIndexToTextIndex(item->offset + item->length), CURSOR_BACKWARD);
+ return SelectionModel(caret, CURSOR_FORWARD);
+}
+
+size_t RenderTextLinux::GetGlyphTextIndex(PangoLayoutRun* run,
+ int glyph_index) const {
+ return LayoutIndexToTextIndex(run->item->offset +
+ run->glyphs->log_clusters[glyph_index]);
+}
+
+RenderText* RenderText::CreateInstance() {
+ return new RenderTextLinux;
+}
+
+} // namespace gfx
diff --git a/chromium/ui/gfx/render_text_linux.h b/chromium/ui/gfx/render_text_linux.h
new file mode 100644
index 00000000000..a2b1e665ae7
--- /dev/null
+++ b/chromium/ui/gfx/render_text_linux.h
@@ -0,0 +1,88 @@
+// Copyright (c) 2012 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_RENDER_TEXT_LINUX_H_
+#define UI_GFX_RENDER_TEXT_LINUX_H_
+
+#include <pango/pango.h>
+#include <vector>
+
+#include "ui/gfx/render_text.h"
+
+namespace gfx {
+
+// RenderTextLinux is the Linux implementation of RenderText using Pango.
+class RenderTextLinux : public RenderText {
+ public:
+ RenderTextLinux();
+ virtual ~RenderTextLinux();
+
+ // Overridden from RenderText:
+ virtual Size GetStringSize() OVERRIDE;
+ virtual int GetBaseline() OVERRIDE;
+ virtual SelectionModel FindCursorPosition(const Point& point) OVERRIDE;
+ virtual std::vector<FontSpan> GetFontSpansForTesting() OVERRIDE;
+
+ protected:
+ // Overridden from RenderText:
+ virtual SelectionModel AdjacentCharSelectionModel(
+ const SelectionModel& selection,
+ VisualCursorDirection direction) OVERRIDE;
+ virtual SelectionModel AdjacentWordSelectionModel(
+ const SelectionModel& selection,
+ VisualCursorDirection direction) OVERRIDE;
+ virtual ui::Range GetGlyphBounds(size_t index) OVERRIDE;
+ virtual std::vector<Rect> GetSubstringBounds(const ui::Range& range) OVERRIDE;
+ virtual size_t TextIndexToLayoutIndex(size_t index) const OVERRIDE;
+ virtual size_t LayoutIndexToTextIndex(size_t index) const OVERRIDE;
+ virtual bool IsCursorablePosition(size_t position) OVERRIDE;
+ virtual void ResetLayout() OVERRIDE;
+ virtual void EnsureLayout() OVERRIDE;
+ virtual void DrawVisualText(Canvas* canvas) OVERRIDE;
+
+ private:
+ friend class RenderTextTest;
+ FRIEND_TEST_ALL_PREFIXES(RenderTextTest, PangoAttributes);
+
+ // Returns the run that contains the character attached to the caret in the
+ // given selection model. Return NULL if not found.
+ GSList* GetRunContainingCaret(const SelectionModel& caret) const;
+
+ // Given a |run|, returns the SelectionModel that contains the logical first
+ // or last caret position inside (not at a boundary of) the run.
+ // The returned value represents a cursor/caret position without a selection.
+ SelectionModel FirstSelectionModelInsideRun(const PangoItem* run);
+ SelectionModel LastSelectionModelInsideRun(const PangoItem* run);
+
+ // Setup pango attribute: foreground, background, font, strike.
+ void SetupPangoAttributes(PangoLayout* layout);
+
+ // Append one pango attribute |pango_attr| into pango attribute list |attrs|.
+ void AppendPangoAttribute(size_t start,
+ size_t end,
+ PangoAttribute* pango_attr,
+ PangoAttrList* attrs);
+
+ // Get the text index corresponding to the |run|'s |glyph_index|.
+ size_t GetGlyphTextIndex(PangoLayoutRun* run, int glyph_index) const;
+
+ // Pango Layout.
+ PangoLayout* layout_;
+ // A single line layout resulting from laying out via |layout_|.
+ PangoLayoutLine* current_line_;
+
+ // Information about character attributes.
+ PangoLogAttr* log_attrs_;
+ // Number of attributes in |log_attrs_|.
+ int num_log_attrs_;
+
+ // The text in the |layout_|.
+ const char* layout_text_;
+
+ DISALLOW_COPY_AND_ASSIGN(RenderTextLinux);
+};
+
+} // namespace gfx
+
+#endif // UI_GFX_RENDER_TEXT_LINUX_H_
diff --git a/chromium/ui/gfx/render_text_mac.cc b/chromium/ui/gfx/render_text_mac.cc
new file mode 100644
index 00000000000..c56c62638f7
--- /dev/null
+++ b/chromium/ui/gfx/render_text_mac.cc
@@ -0,0 +1,343 @@
+// Copyright (c) 2012 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 "ui/gfx/render_text_mac.h"
+
+#include <ApplicationServices/ApplicationServices.h>
+
+#include <algorithm>
+#include <cmath>
+#include <utility>
+
+#include "base/mac/foundation_util.h"
+#include "base/mac/scoped_cftyperef.h"
+#include "base/strings/sys_string_conversions.h"
+#include "skia/ext/skia_utils_mac.h"
+
+namespace gfx {
+
+RenderTextMac::RenderTextMac() : common_baseline_(0), runs_valid_(false) {
+}
+
+RenderTextMac::~RenderTextMac() {
+}
+
+Size RenderTextMac::GetStringSize() {
+ EnsureLayout();
+ return string_size_;
+}
+
+int RenderTextMac::GetBaseline() {
+ EnsureLayout();
+ return common_baseline_;
+}
+
+SelectionModel RenderTextMac::FindCursorPosition(const Point& point) {
+ // TODO(asvitkine): Implement this. http://crbug.com/131618
+ return SelectionModel();
+}
+
+std::vector<RenderText::FontSpan> RenderTextMac::GetFontSpansForTesting() {
+ EnsureLayout();
+ if (!runs_valid_)
+ ComputeRuns();
+
+ std::vector<RenderText::FontSpan> spans;
+ for (size_t i = 0; i < runs_.size(); ++i) {
+ gfx::Font font(runs_[i].font_name, runs_[i].text_size);
+ const CFRange cf_range = CTRunGetStringRange(runs_[i].ct_run);
+ const ui::Range range(cf_range.location,
+ cf_range.location + cf_range.length);
+ spans.push_back(RenderText::FontSpan(font, range));
+ }
+
+ return spans;
+}
+
+SelectionModel RenderTextMac::AdjacentCharSelectionModel(
+ const SelectionModel& selection,
+ VisualCursorDirection direction) {
+ // TODO(asvitkine): Implement this. http://crbug.com/131618
+ return SelectionModel();
+}
+
+SelectionModel RenderTextMac::AdjacentWordSelectionModel(
+ const SelectionModel& selection,
+ VisualCursorDirection direction) {
+ // TODO(asvitkine): Implement this. http://crbug.com/131618
+ return SelectionModel();
+}
+
+ui::Range RenderTextMac::GetGlyphBounds(size_t index) {
+ // TODO(asvitkine): Implement this. http://crbug.com/131618
+ return ui::Range();
+}
+
+std::vector<Rect> RenderTextMac::GetSubstringBounds(const ui::Range& range) {
+ // TODO(asvitkine): Implement this. http://crbug.com/131618
+ return std::vector<Rect>();
+}
+
+size_t RenderTextMac::TextIndexToLayoutIndex(size_t index) const {
+ // TODO(asvitkine): Implement this. http://crbug.com/131618
+ return index;
+}
+
+size_t RenderTextMac::LayoutIndexToTextIndex(size_t index) const {
+ // TODO(asvitkine): Implement this. http://crbug.com/131618
+ return index;
+}
+
+bool RenderTextMac::IsCursorablePosition(size_t position) {
+ // TODO(asvitkine): Implement this. http://crbug.com/131618
+ return true;
+}
+
+void RenderTextMac::ResetLayout() {
+ line_.reset();
+ attributes_.reset();
+ runs_.clear();
+ runs_valid_ = false;
+}
+
+void RenderTextMac::EnsureLayout() {
+ if (line_.get())
+ return;
+ runs_.clear();
+ runs_valid_ = false;
+
+ const Font& font = GetPrimaryFont();
+ base::ScopedCFTypeRef<CFStringRef> font_name_cf_string(
+ base::SysUTF8ToCFStringRef(font.GetFontName()));
+ base::ScopedCFTypeRef<CTFontRef> ct_font(
+ CTFontCreateWithName(font_name_cf_string, font.GetFontSize(), NULL));
+
+ const void* keys[] = { kCTFontAttributeName };
+ const void* values[] = { ct_font };
+ base::ScopedCFTypeRef<CFDictionaryRef> attributes(
+ CFDictionaryCreate(NULL,
+ keys,
+ values,
+ arraysize(keys),
+ NULL,
+ &kCFTypeDictionaryValueCallBacks));
+
+ base::ScopedCFTypeRef<CFStringRef> cf_text(
+ base::SysUTF16ToCFStringRef(text()));
+ base::ScopedCFTypeRef<CFAttributedStringRef> attr_text(
+ CFAttributedStringCreate(NULL, cf_text, attributes));
+ base::ScopedCFTypeRef<CFMutableAttributedStringRef> attr_text_mutable(
+ CFAttributedStringCreateMutableCopy(NULL, 0, attr_text));
+
+ // TODO(asvitkine|msw): Respect GetTextDirection(), which may not match the
+ // natural text direction. See kCTTypesetterOptionForcedEmbeddingLevel, etc.
+
+ ApplyStyles(attr_text_mutable, ct_font);
+ line_.reset(CTLineCreateWithAttributedString(attr_text_mutable));
+
+ CGFloat ascent = 0;
+ CGFloat descent = 0;
+ CGFloat leading = 0;
+ // TODO(asvitkine): Consider using CTLineGetBoundsWithOptions() on 10.8+.
+ double width = CTLineGetTypographicBounds(line_, &ascent, &descent, &leading);
+ // Ensure ascent and descent are not smaller than ones of the font list.
+ // Keep them tall enough to draw often-used characters.
+ // For example, if a text field contains a Japanese character, which is
+ // smaller than Latin ones, and then later a Latin one is inserted, this
+ // ensures that the text baseline does not shift.
+ CGFloat font_list_height = font_list().GetHeight();
+ CGFloat font_list_baseline = font_list().GetBaseline();
+ ascent = std::max(ascent, font_list_baseline);
+ descent = std::max(descent, font_list_height - font_list_baseline);
+ string_size_ = Size(width, ascent + descent + leading);
+ common_baseline_ = ascent;
+}
+
+void RenderTextMac::DrawVisualText(Canvas* canvas) {
+ DCHECK(line_);
+ if (!runs_valid_)
+ ComputeRuns();
+
+ internal::SkiaTextRenderer renderer(canvas);
+ ApplyFadeEffects(&renderer);
+ ApplyTextShadows(&renderer);
+
+ for (size_t i = 0; i < runs_.size(); ++i) {
+ const TextRun& run = runs_[i];
+ renderer.SetForegroundColor(run.foreground);
+ renderer.SetTextSize(run.text_size);
+ renderer.SetFontFamilyWithStyle(run.font_name, run.font_style);
+ renderer.DrawPosText(&run.glyph_positions[0], &run.glyphs[0],
+ run.glyphs.size());
+ renderer.DrawDecorations(run.origin.x(), run.origin.y(), run.width,
+ run.underline, run.strike, run.diagonal_strike);
+ }
+}
+
+RenderTextMac::TextRun::TextRun()
+ : ct_run(NULL),
+ origin(SkPoint::Make(0, 0)),
+ width(0),
+ font_style(Font::NORMAL),
+ text_size(0),
+ foreground(SK_ColorBLACK),
+ underline(false),
+ strike(false),
+ diagonal_strike(false) {
+}
+
+RenderTextMac::TextRun::~TextRun() {
+}
+
+void RenderTextMac::ApplyStyles(CFMutableAttributedStringRef attr_string,
+ CTFontRef font) {
+ // Temporarily apply composition underlines and selection colors.
+ ApplyCompositionAndSelectionStyles();
+
+ // Note: CFAttributedStringSetAttribute() does not appear to retain the values
+ // passed in, as can be verified via CFGetRetainCount(). To ensure the
+ // attribute objects do not leak, they are saved to |attributes_|.
+ // Clear the attributes storage.
+ attributes_.reset(CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks));
+
+ // https://developer.apple.com/library/mac/#documentation/Carbon/Reference/CoreText_StringAttributes_Ref/Reference/reference.html
+ internal::StyleIterator style(colors(), styles());
+ const size_t layout_text_length = GetLayoutText().length();
+ for (size_t i = 0, end = 0; i < layout_text_length; i = end) {
+ end = TextIndexToLayoutIndex(style.GetRange().end());
+ const CFRange range = CFRangeMake(i, end - i);
+ base::ScopedCFTypeRef<CGColorRef> foreground(
+ gfx::CGColorCreateFromSkColor(style.color()));
+ CFAttributedStringSetAttribute(attr_string, range,
+ kCTForegroundColorAttributeName, foreground);
+ CFArrayAppendValue(attributes_, foreground);
+
+ if (style.style(UNDERLINE)) {
+ CTUnderlineStyle value = kCTUnderlineStyleSingle;
+ base::ScopedCFTypeRef<CFNumberRef> underline_value(
+ CFNumberCreate(NULL, kCFNumberSInt32Type, &value));
+ CFAttributedStringSetAttribute(attr_string, range,
+ kCTUnderlineStyleAttributeName,
+ underline_value);
+ CFArrayAppendValue(attributes_, underline_value);
+ }
+
+ const int traits = (style.style(BOLD) ? kCTFontBoldTrait : 0) |
+ (style.style(ITALIC) ? kCTFontItalicTrait : 0);
+ if (traits != 0) {
+ base::ScopedCFTypeRef<CTFontRef> styled_font(
+ CTFontCreateCopyWithSymbolicTraits(font, 0.0, NULL, traits, traits));
+ // TODO(asvitkine): Handle |styled_font| == NULL case better.
+ if (styled_font) {
+ CFAttributedStringSetAttribute(attr_string, range, kCTFontAttributeName,
+ styled_font);
+ CFArrayAppendValue(attributes_, styled_font);
+ }
+ }
+
+ style.UpdatePosition(LayoutIndexToTextIndex(end));
+ }
+
+ // Undo the temporarily applied composition underlines and selection colors.
+ UndoCompositionAndSelectionStyles();
+}
+
+void RenderTextMac::ComputeRuns() {
+ DCHECK(line_);
+
+ CFArrayRef ct_runs = CTLineGetGlyphRuns(line_);
+ const CFIndex ct_runs_count = CFArrayGetCount(ct_runs);
+
+ // TODO(asvitkine): Don't use GetTextOffset() until draw time, since it may be
+ // updated based on alignment changes without resetting the layout.
+ gfx::Vector2d text_offset = GetTextOffset();
+ // Skia will draw glyphs with respect to the baseline.
+ text_offset += gfx::Vector2d(0, common_baseline_);
+
+ const SkScalar x = SkIntToScalar(text_offset.x());
+ const SkScalar y = SkIntToScalar(text_offset.y());
+ SkPoint run_origin = SkPoint::Make(x, y);
+
+ const CFRange empty_cf_range = CFRangeMake(0, 0);
+ for (CFIndex i = 0; i < ct_runs_count; ++i) {
+ CTRunRef ct_run =
+ base::mac::CFCast<CTRunRef>(CFArrayGetValueAtIndex(ct_runs, i));
+ const size_t glyph_count = CTRunGetGlyphCount(ct_run);
+ const double run_width =
+ CTRunGetTypographicBounds(ct_run, empty_cf_range, NULL, NULL, NULL);
+ if (glyph_count == 0) {
+ run_origin.offset(run_width, 0);
+ continue;
+ }
+
+ runs_.push_back(TextRun());
+ TextRun* run = &runs_.back();
+ run->ct_run = ct_run;
+ run->origin = run_origin;
+ run->width = run_width;
+ run->glyphs.resize(glyph_count);
+ CTRunGetGlyphs(ct_run, empty_cf_range, &run->glyphs[0]);
+ // CTRunGetGlyphs() sometimes returns glyphs with value 65535 and zero
+ // width (this has been observed at the beginning of a string containing
+ // Arabic content). Passing these to Skia will trigger an assertion;
+ // instead set their values to 0.
+ for (size_t glyph = 0; glyph < glyph_count; glyph++) {
+ if (run->glyphs[glyph] == 65535)
+ run->glyphs[glyph] = 0;
+ }
+
+ run->glyph_positions.resize(glyph_count);
+ const CGPoint* positions_ptr = CTRunGetPositionsPtr(ct_run);
+ std::vector<CGPoint> positions;
+ if (positions_ptr == NULL) {
+ positions.resize(glyph_count);
+ CTRunGetPositions(ct_run, empty_cf_range, &positions[0]);
+ positions_ptr = &positions[0];
+ }
+ for (size_t glyph = 0; glyph < glyph_count; glyph++) {
+ SkPoint* point = &run->glyph_positions[glyph];
+ point->set(x + SkDoubleToScalar(positions_ptr[glyph].x),
+ y + SkDoubleToScalar(positions_ptr[glyph].y));
+ }
+
+ // TODO(asvitkine): Style boundaries are not necessarily per-run. Handle
+ // this better. Also, support strike and diagonal_strike.
+ CFDictionaryRef attributes = CTRunGetAttributes(ct_run);
+ CTFontRef ct_font =
+ base::mac::GetValueFromDictionary<CTFontRef>(attributes,
+ kCTFontAttributeName);
+ base::ScopedCFTypeRef<CFStringRef> font_name_ref(
+ CTFontCopyFamilyName(ct_font));
+ run->font_name = base::SysCFStringRefToUTF8(font_name_ref);
+ run->text_size = CTFontGetSize(ct_font);
+
+ CTFontSymbolicTraits traits = CTFontGetSymbolicTraits(ct_font);
+ if (traits & kCTFontBoldTrait)
+ run->font_style |= Font::BOLD;
+ if (traits & kCTFontItalicTrait)
+ run->font_style |= Font::ITALIC;
+
+ const CGColorRef foreground =
+ base::mac::GetValueFromDictionary<CGColorRef>(
+ attributes, kCTForegroundColorAttributeName);
+ if (foreground)
+ run->foreground = gfx::CGColorRefToSkColor(foreground);
+
+ const CFNumberRef underline =
+ base::mac::GetValueFromDictionary<CFNumberRef>(
+ attributes, kCTUnderlineStyleAttributeName);
+ CTUnderlineStyle value = kCTUnderlineStyleNone;
+ if (underline && CFNumberGetValue(underline, kCFNumberSInt32Type, &value))
+ run->underline = (value == kCTUnderlineStyleSingle);
+
+ run_origin.offset(run_width, 0);
+ }
+ runs_valid_ = true;
+}
+
+RenderText* RenderText::CreateInstance() {
+ return new RenderTextMac;
+}
+
+} // namespace gfx
diff --git a/chromium/ui/gfx/render_text_mac.h b/chromium/ui/gfx/render_text_mac.h
new file mode 100644
index 00000000000..345fef0e98d
--- /dev/null
+++ b/chromium/ui/gfx/render_text_mac.h
@@ -0,0 +1,101 @@
+// Copyright (c) 2012 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_RENDER_TEXT_MAC_H_
+#define UI_GFX_RENDER_TEXT_MAC_H_
+
+#include <ApplicationServices/ApplicationServices.h>
+
+#include <string>
+#include <vector>
+
+#include "base/mac/scoped_cftyperef.h"
+#include "ui/gfx/render_text.h"
+
+namespace gfx {
+
+// RenderTextMac is the Mac implementation of RenderText that uses CoreText for
+// layout and Skia for drawing.
+//
+// Note: The current implementation only supports drawing and sizing the text,
+// but not text selection or cursor movement.
+class RenderTextMac : public RenderText {
+ public:
+ RenderTextMac();
+ virtual ~RenderTextMac();
+
+ // Overridden from RenderText:
+ virtual Size GetStringSize() OVERRIDE;
+ virtual int GetBaseline() OVERRIDE;
+ virtual SelectionModel FindCursorPosition(const Point& point) OVERRIDE;
+ virtual std::vector<FontSpan> GetFontSpansForTesting() OVERRIDE;
+
+ protected:
+ // Overridden from RenderText:
+ virtual SelectionModel AdjacentCharSelectionModel(
+ const SelectionModel& selection,
+ VisualCursorDirection direction) OVERRIDE;
+ virtual SelectionModel AdjacentWordSelectionModel(
+ const SelectionModel& selection,
+ VisualCursorDirection direction) OVERRIDE;
+ virtual ui::Range GetGlyphBounds(size_t index) OVERRIDE;
+ virtual std::vector<Rect> GetSubstringBounds(const ui::Range& range) OVERRIDE;
+ virtual size_t TextIndexToLayoutIndex(size_t index) const OVERRIDE;
+ virtual size_t LayoutIndexToTextIndex(size_t index) const OVERRIDE;
+ virtual bool IsCursorablePosition(size_t position) OVERRIDE;
+ virtual void ResetLayout() OVERRIDE;
+ virtual void EnsureLayout() OVERRIDE;
+ virtual void DrawVisualText(Canvas* canvas) OVERRIDE;
+
+ private:
+ struct TextRun {
+ CTRunRef ct_run;
+ SkPoint origin;
+ std::vector<uint16> glyphs;
+ std::vector<SkPoint> glyph_positions;
+ SkScalar width;
+ std::string font_name;
+ int font_style;
+ SkScalar text_size;
+ SkColor foreground;
+ bool underline;
+ bool strike;
+ bool diagonal_strike;
+
+ TextRun();
+ ~TextRun();
+ };
+
+ // Applies RenderText styles to |attr_string| with the given |ct_font|.
+ void ApplyStyles(CFMutableAttributedStringRef attr_string, CTFontRef ct_font);
+
+ // Updates |runs_| based on |line_| and sets |runs_valid_| to true.
+ void ComputeRuns();
+
+ // The Core Text line of text. Created by |EnsureLayout()|.
+ base::ScopedCFTypeRef<CTLineRef> line_;
+
+ // Array to hold CFAttributedString attributes that allows Core Text to hold
+ // weak references to them without leaking.
+ base::ScopedCFTypeRef<CFMutableArrayRef> attributes_;
+
+ // Visual dimensions of the text. Computed by |EnsureLayout()|.
+ Size string_size_;
+
+ // Common baseline for this line of text. Computed by |EnsureLayout()|.
+ SkScalar common_baseline_;
+
+ // Visual text runs. Only valid if |runs_valid_| is true. Computed by
+ // |ComputeRuns()|.
+ std::vector<TextRun> runs_;
+
+ // Indicates that |runs_| are valid, set by |ComputeRuns()|.
+ bool runs_valid_;
+
+ DISALLOW_COPY_AND_ASSIGN(RenderTextMac);
+};
+
+} // namespace gfx
+
+#endif // UI_GFX_RENDER_TEXT_MAC_H_
diff --git a/chromium/ui/gfx/render_text_unittest.cc b/chromium/ui/gfx/render_text_unittest.cc
new file mode 100644
index 00000000000..263ef92d815
--- /dev/null
+++ b/chromium/ui/gfx/render_text_unittest.cc
@@ -0,0 +1,1654 @@
+// Copyright (c) 2012 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 "ui/gfx/render_text.h"
+
+#include <algorithm>
+
+#include "base/memory/scoped_ptr.h"
+#include "base/strings/stringprintf.h"
+#include "base/strings/utf_string_conversions.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "ui/gfx/break_list.h"
+#include "ui/gfx/canvas.h"
+
+#if defined(OS_WIN)
+#include "base/win/windows_version.h"
+#include "ui/gfx/render_text_win.h"
+#endif
+
+#if defined(OS_LINUX)
+#include "ui/gfx/render_text_linux.h"
+#endif
+
+#if defined(TOOLKIT_GTK)
+#include <gtk/gtk.h>
+#endif
+
+namespace gfx {
+
+namespace {
+
+// Various weak, LTR, RTL, and Bidi string cases with three characters each.
+const wchar_t kWeak[] = L" . ";
+const wchar_t kLtr[] = L"abc";
+const wchar_t kLtrRtl[] = L"a" L"\x5d0\x5d1";
+const wchar_t kLtrRtlLtr[] = L"a" L"\x5d1" L"b";
+const wchar_t kRtl[] = L"\x5d0\x5d1\x5d2";
+const wchar_t kRtlLtr[] = L"\x5d0\x5d1" L"a";
+const wchar_t kRtlLtrRtl[] = L"\x5d0" L"a" L"\x5d1";
+
+// Checks whether |range| contains |index|. This is not the same as calling
+// |range.Contains(ui::Range(index))| - as that would return true when
+// |index| == |range.end()|.
+bool IndexInRange(const ui::Range& range, size_t index) {
+ return index >= range.start() && index < range.end();
+}
+
+base::string16 GetSelectedText(RenderText* render_text) {
+ return render_text->text().substr(render_text->selection().GetMin(),
+ render_text->selection().length());
+}
+
+// A test utility function to set the application default text direction.
+void SetRTL(bool rtl) {
+ // Override the current locale/direction.
+ base::i18n::SetICUDefaultLocale(rtl ? "he" : "en");
+#if defined(TOOLKIT_GTK)
+ // Do the same for GTK, which does not rely on the ICU default locale.
+ gtk_widget_set_default_direction(rtl ? GTK_TEXT_DIR_RTL : GTK_TEXT_DIR_LTR);
+#endif
+ EXPECT_EQ(rtl, base::i18n::IsRTL());
+}
+
+// Ensure cursor movement in the specified |direction| yields |expected| values.
+void RunMoveCursorLeftRightTest(RenderText* render_text,
+ const std::vector<SelectionModel>& expected,
+ VisualCursorDirection direction) {
+ for (size_t i = 0; i < expected.size(); ++i) {
+ SCOPED_TRACE(base::StringPrintf("Going %s; expected value index %d.",
+ direction == CURSOR_LEFT ? "left" : "right", static_cast<int>(i)));
+ EXPECT_EQ(expected[i], render_text->selection_model());
+ render_text->MoveCursor(CHARACTER_BREAK, direction, false);
+ }
+ // Check that cursoring is clamped at the line edge.
+ EXPECT_EQ(expected.back(), render_text->selection_model());
+ // Check that it is the line edge.
+ render_text->MoveCursor(LINE_BREAK, direction, false);
+ EXPECT_EQ(expected.back(), render_text->selection_model());
+}
+
+} // namespace
+
+class RenderTextTest : public testing::Test {
+};
+
+TEST_F(RenderTextTest, DefaultStyle) {
+ // Check the default styles applied to new instances and adjusted text.
+ scoped_ptr<RenderText> render_text(RenderText::CreateInstance());
+ EXPECT_TRUE(render_text->text().empty());
+ const wchar_t* const cases[] = { kWeak, kLtr, L"Hello", kRtl, L"", L"" };
+ for (size_t i = 0; i < arraysize(cases); ++i) {
+ EXPECT_TRUE(render_text->colors().EqualsValueForTesting(SK_ColorBLACK));
+ for (size_t style = 0; style < NUM_TEXT_STYLES; ++style)
+ EXPECT_TRUE(render_text->styles()[style].EqualsValueForTesting(false));
+ render_text->SetText(WideToUTF16(cases[i]));
+ }
+}
+
+TEST_F(RenderTextTest, SetColorAndStyle) {
+ // Ensure custom default styles persist across setting and clearing text.
+ scoped_ptr<RenderText> render_text(RenderText::CreateInstance());
+ const SkColor color = SK_ColorRED;
+ render_text->SetColor(color);
+ render_text->SetStyle(BOLD, true);
+ render_text->SetStyle(UNDERLINE, false);
+ const wchar_t* const cases[] = { kWeak, kLtr, L"Hello", kRtl, L"", L"" };
+ for (size_t i = 0; i < arraysize(cases); ++i) {
+ EXPECT_TRUE(render_text->colors().EqualsValueForTesting(color));
+ EXPECT_TRUE(render_text->styles()[BOLD].EqualsValueForTesting(true));
+ EXPECT_TRUE(render_text->styles()[UNDERLINE].EqualsValueForTesting(false));
+ render_text->SetText(WideToUTF16(cases[i]));
+
+ // Ensure custom default styles can be applied after text has been set.
+ if (i == 1)
+ render_text->SetStyle(STRIKE, true);
+ if (i >= 1)
+ EXPECT_TRUE(render_text->styles()[STRIKE].EqualsValueForTesting(true));
+ }
+}
+
+TEST_F(RenderTextTest, ApplyColorAndStyle) {
+ scoped_ptr<RenderText> render_text(RenderText::CreateInstance());
+ render_text->SetText(ASCIIToUTF16("012345678"));
+
+ // Apply a ranged color and style and check the resulting breaks.
+ render_text->ApplyColor(SK_ColorRED, ui::Range(1, 4));
+ render_text->ApplyStyle(BOLD, true, ui::Range(2, 5));
+ std::vector<std::pair<size_t, SkColor> > expected_color;
+ expected_color.push_back(std::pair<size_t, SkColor>(0, SK_ColorBLACK));
+ expected_color.push_back(std::pair<size_t, SkColor>(1, SK_ColorRED));
+ expected_color.push_back(std::pair<size_t, SkColor>(4, SK_ColorBLACK));
+ EXPECT_TRUE(render_text->colors().EqualsForTesting(expected_color));
+ std::vector<std::pair<size_t, bool> > expected_style;
+ expected_style.push_back(std::pair<size_t, bool>(0, false));
+ expected_style.push_back(std::pair<size_t, bool>(2, true));
+ expected_style.push_back(std::pair<size_t, bool>(5, false));
+ EXPECT_TRUE(render_text->styles()[BOLD].EqualsForTesting(expected_style));
+
+ // Ensure setting a color and style overrides the ranged colors and styles.
+ render_text->SetColor(SK_ColorBLUE);
+ EXPECT_TRUE(render_text->colors().EqualsValueForTesting(SK_ColorBLUE));
+ render_text->SetStyle(BOLD, false);
+ EXPECT_TRUE(render_text->styles()[BOLD].EqualsValueForTesting(false));
+
+ // Apply a color and style over the text end and check the resulting breaks.
+ // (INT_MAX should be used instead of the text length for the range end)
+ const size_t text_length = render_text->text().length();
+ render_text->ApplyColor(SK_ColorRED, ui::Range(0, text_length));
+ render_text->ApplyStyle(BOLD, true, ui::Range(2, text_length));
+ std::vector<std::pair<size_t, SkColor> > expected_color_end;
+ expected_color_end.push_back(std::pair<size_t, SkColor>(0, SK_ColorRED));
+ EXPECT_TRUE(render_text->colors().EqualsForTesting(expected_color_end));
+ std::vector<std::pair<size_t, bool> > expected_style_end;
+ expected_style_end.push_back(std::pair<size_t, bool>(0, false));
+ expected_style_end.push_back(std::pair<size_t, bool>(2, true));
+ EXPECT_TRUE(render_text->styles()[BOLD].EqualsForTesting(expected_style_end));
+
+ // Ensure ranged values adjust to accommodate text length changes.
+ render_text->ApplyStyle(ITALIC, true, ui::Range(0, 2));
+ render_text->ApplyStyle(ITALIC, true, ui::Range(3, 6));
+ render_text->ApplyStyle(ITALIC, true, ui::Range(7, text_length));
+ std::vector<std::pair<size_t, bool> > expected_italic;
+ expected_italic.push_back(std::pair<size_t, bool>(0, true));
+ expected_italic.push_back(std::pair<size_t, bool>(2, false));
+ expected_italic.push_back(std::pair<size_t, bool>(3, true));
+ expected_italic.push_back(std::pair<size_t, bool>(6, false));
+ expected_italic.push_back(std::pair<size_t, bool>(7, true));
+ EXPECT_TRUE(render_text->styles()[ITALIC].EqualsForTesting(expected_italic));
+
+ // Truncating the text should trim any corresponding breaks.
+ render_text->SetText(ASCIIToUTF16("0123456"));
+ expected_italic.resize(4);
+ EXPECT_TRUE(render_text->styles()[ITALIC].EqualsForTesting(expected_italic));
+ render_text->SetText(ASCIIToUTF16("01234"));
+ expected_italic.resize(3);
+ EXPECT_TRUE(render_text->styles()[ITALIC].EqualsForTesting(expected_italic));
+
+ // Appending text should extend the terminal styles without changing breaks.
+ render_text->SetText(ASCIIToUTF16("012345678"));
+ EXPECT_TRUE(render_text->styles()[ITALIC].EqualsForTesting(expected_italic));
+}
+
+#if defined(OS_LINUX)
+TEST_F(RenderTextTest, PangoAttributes) {
+ scoped_ptr<RenderText> render_text(RenderText::CreateInstance());
+ render_text->SetText(ASCIIToUTF16("012345678"));
+
+ // Apply ranged BOLD/ITALIC styles and check the resulting Pango attributes.
+ render_text->ApplyStyle(BOLD, true, ui::Range(2, 4));
+ render_text->ApplyStyle(ITALIC, true, ui::Range(1, 3));
+
+ struct {
+ int start;
+ int end;
+ bool bold;
+ bool italic;
+ } cases[] = {
+ { 0, 1, false, false },
+ { 1, 2, false, true },
+ { 2, 3, true, true },
+ { 3, 4, true, false },
+ { 4, INT_MAX, false, false },
+ };
+
+ int start = 0, end = 0;
+ RenderTextLinux* rt_linux = static_cast<RenderTextLinux*>(render_text.get());
+ rt_linux->EnsureLayout();
+ PangoAttrList* attributes = pango_layout_get_attributes(rt_linux->layout_);
+ PangoAttrIterator* iter = pango_attr_list_get_iterator(attributes);
+ for (size_t i = 0; i < ARRAYSIZE_UNSAFE(cases); ++i) {
+ pango_attr_iterator_range(iter, &start, &end);
+ EXPECT_EQ(cases[i].start, start);
+ EXPECT_EQ(cases[i].end, end);
+ PangoFontDescription* font = pango_font_description_new();
+ pango_attr_iterator_get_font(iter, font, NULL, NULL);
+ char* description_string = pango_font_description_to_string(font);
+ const base::string16 desc = ASCIIToUTF16(description_string);
+ const bool bold = desc.find(ASCIIToUTF16("Bold")) != std::string::npos;
+ EXPECT_EQ(cases[i].bold, bold);
+ const bool italic = desc.find(ASCIIToUTF16("Italic")) != std::string::npos;
+ EXPECT_EQ(cases[i].italic, italic);
+ pango_attr_iterator_next(iter);
+ pango_font_description_free(font);
+ g_free(description_string);
+ }
+ EXPECT_FALSE(pango_attr_iterator_next(iter));
+ pango_attr_iterator_destroy(iter);
+}
+#endif
+
+// TODO(asvitkine): Cursor movements tests disabled on Mac because RenderTextMac
+// does not implement this yet. http://crbug.com/131618
+#if !defined(OS_MACOSX)
+void TestVisualCursorMotionInObscuredField(RenderText* render_text,
+ const base::string16& text,
+ bool select) {
+ ASSERT_TRUE(render_text->obscured());
+ render_text->SetText(text);
+ int len = text.length();
+ render_text->MoveCursor(LINE_BREAK, CURSOR_RIGHT, select);
+ EXPECT_EQ(SelectionModel(ui::Range(select ? 0 : len, len), CURSOR_FORWARD),
+ render_text->selection_model());
+ render_text->MoveCursor(LINE_BREAK, CURSOR_LEFT, select);
+ EXPECT_EQ(SelectionModel(0, CURSOR_BACKWARD), render_text->selection_model());
+ for (int j = 1; j <= len; ++j) {
+ render_text->MoveCursor(CHARACTER_BREAK, CURSOR_RIGHT, select);
+ EXPECT_EQ(SelectionModel(ui::Range(select ? 0 : j, j), CURSOR_BACKWARD),
+ render_text->selection_model());
+ }
+ for (int j = len - 1; j >= 0; --j) {
+ render_text->MoveCursor(CHARACTER_BREAK, CURSOR_LEFT, select);
+ EXPECT_EQ(SelectionModel(ui::Range(select ? 0 : j, j), CURSOR_FORWARD),
+ render_text->selection_model());
+ }
+ render_text->MoveCursor(WORD_BREAK, CURSOR_RIGHT, select);
+ EXPECT_EQ(SelectionModel(ui::Range(select ? 0 : len, len), CURSOR_FORWARD),
+ render_text->selection_model());
+ render_text->MoveCursor(WORD_BREAK, CURSOR_LEFT, select);
+ EXPECT_EQ(SelectionModel(0, CURSOR_BACKWARD), render_text->selection_model());
+}
+
+TEST_F(RenderTextTest, ObscuredText) {
+ const base::string16 seuss = ASCIIToUTF16("hop on pop");
+ const base::string16 no_seuss = ASCIIToUTF16("**********");
+ scoped_ptr<RenderText> render_text(RenderText::CreateInstance());
+
+ // GetLayoutText() returns asterisks when the obscured bit is set.
+ render_text->SetText(seuss);
+ render_text->SetObscured(true);
+ EXPECT_EQ(seuss, render_text->text());
+ EXPECT_EQ(no_seuss, render_text->GetLayoutText());
+ render_text->SetObscured(false);
+ EXPECT_EQ(seuss, render_text->text());
+ EXPECT_EQ(seuss, render_text->GetLayoutText());
+
+ render_text->SetObscured(true);
+
+ // Surrogate pairs are counted as one code point.
+ const char16 invalid_surrogates[] = {0xDC00, 0xD800, 0};
+ render_text->SetText(invalid_surrogates);
+ EXPECT_EQ(ASCIIToUTF16("**"), render_text->GetLayoutText());
+ const char16 valid_surrogates[] = {0xD800, 0xDC00, 0};
+ render_text->SetText(valid_surrogates);
+ EXPECT_EQ(ASCIIToUTF16("*"), render_text->GetLayoutText());
+ EXPECT_EQ(0U, render_text->cursor_position());
+ render_text->MoveCursor(CHARACTER_BREAK, CURSOR_RIGHT, false);
+ EXPECT_EQ(2U, render_text->cursor_position());
+
+ // Test index conversion and cursor validity with a valid surrogate pair.
+ EXPECT_EQ(0U, render_text->TextIndexToLayoutIndex(0U));
+ EXPECT_EQ(1U, render_text->TextIndexToLayoutIndex(1U));
+ EXPECT_EQ(1U, render_text->TextIndexToLayoutIndex(2U));
+ EXPECT_EQ(0U, render_text->LayoutIndexToTextIndex(0U));
+ EXPECT_EQ(2U, render_text->LayoutIndexToTextIndex(1U));
+ EXPECT_TRUE(render_text->IsCursorablePosition(0U));
+ EXPECT_FALSE(render_text->IsCursorablePosition(1U));
+ EXPECT_TRUE(render_text->IsCursorablePosition(2U));
+
+ // FindCursorPosition() should not return positions between a surrogate pair.
+ render_text->SetDisplayRect(Rect(0, 0, 20, 20));
+ EXPECT_EQ(render_text->FindCursorPosition(Point(0, 0)).caret_pos(), 0U);
+ EXPECT_EQ(render_text->FindCursorPosition(Point(20, 0)).caret_pos(), 2U);
+ for (int x = -1; x <= 20; ++x) {
+ SelectionModel selection = render_text->FindCursorPosition(Point(x, 0));
+ EXPECT_TRUE(selection.caret_pos() == 0U || selection.caret_pos() == 2U);
+ }
+
+ // GetGlyphBounds() should yield the entire string bounds for text index 0.
+ EXPECT_EQ(render_text->GetStringSize().width(),
+ static_cast<int>(render_text->GetGlyphBounds(0U).length()));
+
+ // Cursoring is independent of underlying characters when text is obscured.
+ const wchar_t* const texts[] = {
+ kWeak, kLtr, kLtrRtl, kLtrRtlLtr, kRtl, kRtlLtr, kRtlLtrRtl,
+ L"hop on pop", // Check LTR word boundaries.
+ L"\x05d0\x05d1 \x05d0\x05d2 \x05d1\x05d2", // Check RTL word boundaries.
+ };
+ for (size_t i = 0; i < arraysize(texts); ++i) {
+ base::string16 text = WideToUTF16(texts[i]);
+ TestVisualCursorMotionInObscuredField(render_text.get(), text, false);
+ TestVisualCursorMotionInObscuredField(render_text.get(), text, true);
+ }
+}
+
+TEST_F(RenderTextTest, RevealObscuredText) {
+ const base::string16 seuss = ASCIIToUTF16("hop on pop");
+ const base::string16 no_seuss = ASCIIToUTF16("**********");
+ scoped_ptr<RenderText> render_text(RenderText::CreateInstance());
+
+ render_text->SetText(seuss);
+ render_text->SetObscured(true);
+ EXPECT_EQ(seuss, render_text->text());
+ EXPECT_EQ(no_seuss, render_text->GetLayoutText());
+
+ // Valid reveal index and new revealed index clears previous one.
+ render_text->RenderText::SetObscuredRevealIndex(0);
+ EXPECT_EQ(ASCIIToUTF16("h*********"), render_text->GetLayoutText());
+ render_text->RenderText::SetObscuredRevealIndex(1);
+ EXPECT_EQ(ASCIIToUTF16("*o********"), render_text->GetLayoutText());
+ render_text->RenderText::SetObscuredRevealIndex(2);
+ EXPECT_EQ(ASCIIToUTF16("**p*******"), render_text->GetLayoutText());
+
+ // Invalid reveal index.
+ render_text->RenderText::SetObscuredRevealIndex(-1);
+ EXPECT_EQ(no_seuss, render_text->GetLayoutText());
+ render_text->RenderText::SetObscuredRevealIndex(seuss.length() + 1);
+ EXPECT_EQ(no_seuss, render_text->GetLayoutText());
+
+ // SetObscured clears the revealed index.
+ render_text->RenderText::SetObscuredRevealIndex(0);
+ EXPECT_EQ(ASCIIToUTF16("h*********"), render_text->GetLayoutText());
+ render_text->SetObscured(false);
+ EXPECT_EQ(seuss, render_text->GetLayoutText());
+ render_text->SetObscured(true);
+ EXPECT_EQ(no_seuss, render_text->GetLayoutText());
+
+ // SetText clears the revealed index.
+ render_text->SetText(ASCIIToUTF16("new"));
+ EXPECT_EQ(ASCIIToUTF16("***"), render_text->GetLayoutText());
+ render_text->RenderText::SetObscuredRevealIndex(2);
+ EXPECT_EQ(ASCIIToUTF16("**w"), render_text->GetLayoutText());
+ render_text->SetText(ASCIIToUTF16("new longer"));
+ EXPECT_EQ(ASCIIToUTF16("**********"), render_text->GetLayoutText());
+
+ // Text with invalid surrogates.
+ const char16 invalid_surrogates[] = {0xDC00, 0xD800, 'h', 'o', 'p', 0};
+ render_text->SetText(invalid_surrogates);
+ EXPECT_EQ(ASCIIToUTF16("*****"), render_text->GetLayoutText());
+ render_text->RenderText::SetObscuredRevealIndex(0);
+ const char16 invalid_expect_0[] = {0xDC00, '*', '*', '*', '*', 0};
+ EXPECT_EQ(invalid_expect_0, render_text->GetLayoutText());
+ render_text->RenderText::SetObscuredRevealIndex(1);
+ const char16 invalid_expect_1[] = {'*', 0xD800, '*', '*', '*', 0};
+ EXPECT_EQ(invalid_expect_1, render_text->GetLayoutText());
+ render_text->RenderText::SetObscuredRevealIndex(2);
+ EXPECT_EQ(ASCIIToUTF16("**h**"), render_text->GetLayoutText());
+
+ // Text with valid surrogates before and after the reveal index.
+ const char16 valid_surrogates[] =
+ {0xD800, 0xDC00, 'h', 'o', 'p', 0xD800, 0xDC00, 0};
+ render_text->SetText(valid_surrogates);
+ EXPECT_EQ(ASCIIToUTF16("*****"), render_text->GetLayoutText());
+ render_text->RenderText::SetObscuredRevealIndex(0);
+ const char16 valid_expect_0_and_1[] = {0xD800, 0xDC00, '*', '*', '*', '*', 0};
+ EXPECT_EQ(valid_expect_0_and_1, render_text->GetLayoutText());
+ render_text->RenderText::SetObscuredRevealIndex(1);
+ EXPECT_EQ(valid_expect_0_and_1, render_text->GetLayoutText());
+ render_text->RenderText::SetObscuredRevealIndex(2);
+ EXPECT_EQ(ASCIIToUTF16("*h***"), render_text->GetLayoutText());
+ render_text->RenderText::SetObscuredRevealIndex(5);
+ const char16 valid_expect_5_and_6[] = {'*', '*', '*', '*', 0xD800, 0xDC00, 0};
+ EXPECT_EQ(valid_expect_5_and_6, render_text->GetLayoutText());
+ render_text->RenderText::SetObscuredRevealIndex(6);
+ EXPECT_EQ(valid_expect_5_and_6, render_text->GetLayoutText());
+}
+
+TEST_F(RenderTextTest, TruncatedText) {
+ struct {
+ const wchar_t* text;
+ const wchar_t* layout_text;
+ } cases[] = {
+ // Strings shorter than the truncation length should be laid out in full.
+ { L"", L"" },
+ { kWeak, kWeak },
+ { kLtr, kLtr },
+ { kLtrRtl, kLtrRtl },
+ { kLtrRtlLtr, kLtrRtlLtr },
+ { kRtl, kRtl },
+ { kRtlLtr, kRtlLtr },
+ { kRtlLtrRtl, kRtlLtrRtl },
+ // Strings as long as the truncation length should be laid out in full.
+ { L"01234", L"01234" },
+ // Long strings should be truncated with an ellipsis appended at the end.
+ { L"012345", L"0123\x2026" },
+ { L"012" L" . ", L"012 \x2026" },
+ { L"012" L"abc", L"012a\x2026" },
+ { L"012" L"a" L"\x5d0\x5d1", L"012a\x2026" },
+ { L"012" L"a" L"\x5d1" L"b", L"012a\x2026" },
+ { L"012" L"\x5d0\x5d1\x5d2", L"012\x5d0\x2026" },
+ { L"012" L"\x5d0\x5d1" L"a", L"012\x5d0\x2026" },
+ { L"012" L"\x5d0" L"a" L"\x5d1", L"012\x5d0\x2026" },
+ // Surrogate pairs should be truncated reasonably enough.
+ { L"0123\x0915\x093f", L"0123\x2026" },
+ { L"0\x05e9\x05bc\x05c1\x05b8", L"0\x05e9\x05bc\x05c1\x05b8" },
+ { L"01\x05e9\x05bc\x05c1\x05b8", L"01\x05e9\x05bc\x2026" },
+ { L"012\x05e9\x05bc\x05c1\x05b8", L"012\x05e9\x2026" },
+ { L"0123\x05e9\x05bc\x05c1\x05b8", L"0123\x2026" },
+ { L"01234\x05e9\x05bc\x05c1\x05b8", L"0123\x2026" },
+ { L"012\xF0\x9D\x84\x9E", L"012\xF0\x2026" },
+ };
+
+ scoped_ptr<RenderText> render_text(RenderText::CreateInstance());
+ render_text->set_truncate_length(5);
+ for (size_t i = 0; i < ARRAYSIZE_UNSAFE(cases); i++) {
+ render_text->SetText(WideToUTF16(cases[i].text));
+ EXPECT_EQ(WideToUTF16(cases[i].text), render_text->text());
+ EXPECT_EQ(WideToUTF16(cases[i].layout_text), render_text->GetLayoutText())
+ << "For case " << i << ": " << cases[i].text;
+ }
+}
+
+TEST_F(RenderTextTest, TruncatedObscuredText) {
+ scoped_ptr<RenderText> render_text(RenderText::CreateInstance());
+ render_text->set_truncate_length(3);
+ render_text->SetObscured(true);
+ render_text->SetText(WideToUTF16(L"abcdef"));
+ EXPECT_EQ(WideToUTF16(L"abcdef"), render_text->text());
+ EXPECT_EQ(WideToUTF16(L"**\x2026"), render_text->GetLayoutText());
+}
+
+TEST_F(RenderTextTest, TruncatedCursorMovementLTR) {
+ scoped_ptr<RenderText> render_text(RenderText::CreateInstance());
+ render_text->set_truncate_length(2);
+ render_text->SetText(WideToUTF16(L"abcd"));
+
+ EXPECT_EQ(SelectionModel(0, CURSOR_BACKWARD), render_text->selection_model());
+ render_text->MoveCursor(LINE_BREAK, CURSOR_RIGHT, false);
+ EXPECT_EQ(SelectionModel(4, CURSOR_FORWARD), render_text->selection_model());
+ render_text->MoveCursor(LINE_BREAK, CURSOR_LEFT, false);
+ EXPECT_EQ(SelectionModel(0, CURSOR_BACKWARD), render_text->selection_model());
+
+ std::vector<SelectionModel> expected;
+ expected.push_back(SelectionModel(0, CURSOR_BACKWARD));
+ expected.push_back(SelectionModel(1, CURSOR_BACKWARD));
+ // The cursor hops over the ellipsis and elided text to the line end.
+ expected.push_back(SelectionModel(4, CURSOR_BACKWARD));
+ expected.push_back(SelectionModel(4, CURSOR_FORWARD));
+ RunMoveCursorLeftRightTest(render_text.get(), expected, CURSOR_RIGHT);
+
+ expected.clear();
+ expected.push_back(SelectionModel(4, CURSOR_FORWARD));
+ // The cursor hops over the elided text to preceeding text.
+ expected.push_back(SelectionModel(1, CURSOR_FORWARD));
+ expected.push_back(SelectionModel(0, CURSOR_FORWARD));
+ expected.push_back(SelectionModel(0, CURSOR_BACKWARD));
+ RunMoveCursorLeftRightTest(render_text.get(), expected, CURSOR_LEFT);
+}
+
+TEST_F(RenderTextTest, TruncatedCursorMovementRTL) {
+ scoped_ptr<RenderText> render_text(RenderText::CreateInstance());
+ render_text->set_truncate_length(2);
+ render_text->SetText(WideToUTF16(L"\x5d0\x5d1\x5d2\x5d3"));
+
+ EXPECT_EQ(SelectionModel(0, CURSOR_BACKWARD), render_text->selection_model());
+ render_text->MoveCursor(LINE_BREAK, CURSOR_LEFT, false);
+ EXPECT_EQ(SelectionModel(4, CURSOR_FORWARD), render_text->selection_model());
+ render_text->MoveCursor(LINE_BREAK, CURSOR_RIGHT, false);
+ EXPECT_EQ(SelectionModel(0, CURSOR_BACKWARD), render_text->selection_model());
+
+ std::vector<SelectionModel> expected;
+ expected.push_back(SelectionModel(0, CURSOR_BACKWARD));
+ expected.push_back(SelectionModel(1, CURSOR_BACKWARD));
+ // The cursor hops over the ellipsis and elided text to the line end.
+ expected.push_back(SelectionModel(4, CURSOR_BACKWARD));
+ expected.push_back(SelectionModel(4, CURSOR_FORWARD));
+ RunMoveCursorLeftRightTest(render_text.get(), expected, CURSOR_LEFT);
+
+ expected.clear();
+ expected.push_back(SelectionModel(4, CURSOR_FORWARD));
+ // The cursor hops over the elided text to preceeding text.
+ expected.push_back(SelectionModel(1, CURSOR_FORWARD));
+ expected.push_back(SelectionModel(0, CURSOR_FORWARD));
+ expected.push_back(SelectionModel(0, CURSOR_BACKWARD));
+ RunMoveCursorLeftRightTest(render_text.get(), expected, CURSOR_RIGHT);
+}
+
+TEST_F(RenderTextTest, GetTextDirection) {
+ struct {
+ const wchar_t* text;
+ const base::i18n::TextDirection text_direction;
+ } cases[] = {
+ // Blank strings and those with no/weak directionality default to LTR.
+ { L"", base::i18n::LEFT_TO_RIGHT },
+ { kWeak, base::i18n::LEFT_TO_RIGHT },
+ // Strings that begin with strong LTR characters.
+ { kLtr, base::i18n::LEFT_TO_RIGHT },
+ { kLtrRtl, base::i18n::LEFT_TO_RIGHT },
+ { kLtrRtlLtr, base::i18n::LEFT_TO_RIGHT },
+ // Strings that begin with strong RTL characters.
+ { kRtl, base::i18n::RIGHT_TO_LEFT },
+ { kRtlLtr, base::i18n::RIGHT_TO_LEFT },
+ { kRtlLtrRtl, base::i18n::RIGHT_TO_LEFT },
+ };
+
+ scoped_ptr<RenderText> render_text(RenderText::CreateInstance());
+ const bool was_rtl = base::i18n::IsRTL();
+
+ for (size_t i = 0; i < 2; ++i) {
+ // Toggle the application default text direction (to try each direction).
+ SetRTL(!base::i18n::IsRTL());
+ const base::i18n::TextDirection ui_direction = base::i18n::IsRTL() ?
+ base::i18n::RIGHT_TO_LEFT : base::i18n::LEFT_TO_RIGHT;
+
+ // Ensure that directionality modes yield the correct text directions.
+ for (size_t j = 0; j < ARRAYSIZE_UNSAFE(cases); j++) {
+ render_text->SetText(WideToUTF16(cases[j].text));
+ render_text->SetDirectionalityMode(DIRECTIONALITY_FROM_TEXT);
+ EXPECT_EQ(render_text->GetTextDirection(), cases[j].text_direction);
+ render_text->SetDirectionalityMode(DIRECTIONALITY_FROM_UI);
+ EXPECT_EQ(render_text->GetTextDirection(), ui_direction);
+ render_text->SetDirectionalityMode(DIRECTIONALITY_FORCE_LTR);
+ EXPECT_EQ(render_text->GetTextDirection(), base::i18n::LEFT_TO_RIGHT);
+ render_text->SetDirectionalityMode(DIRECTIONALITY_FORCE_RTL);
+ EXPECT_EQ(render_text->GetTextDirection(), base::i18n::RIGHT_TO_LEFT);
+ }
+ }
+
+ EXPECT_EQ(was_rtl, base::i18n::IsRTL());
+
+ // Ensure that text changes update the direction for DIRECTIONALITY_FROM_TEXT.
+ render_text->SetDirectionalityMode(DIRECTIONALITY_FROM_TEXT);
+ render_text->SetText(WideToUTF16(kLtr));
+ EXPECT_EQ(render_text->GetTextDirection(), base::i18n::LEFT_TO_RIGHT);
+ render_text->SetText(WideToUTF16(kRtl));
+ EXPECT_EQ(render_text->GetTextDirection(), base::i18n::RIGHT_TO_LEFT);
+}
+
+TEST_F(RenderTextTest, MoveCursorLeftRightInLtr) {
+ scoped_ptr<RenderText> render_text(RenderText::CreateInstance());
+
+ // Pure LTR.
+ render_text->SetText(ASCIIToUTF16("abc"));
+ // |expected| saves the expected SelectionModel when moving cursor from left
+ // to right.
+ std::vector<SelectionModel> expected;
+ expected.push_back(SelectionModel(0, CURSOR_BACKWARD));
+ expected.push_back(SelectionModel(1, CURSOR_BACKWARD));
+ expected.push_back(SelectionModel(2, CURSOR_BACKWARD));
+ expected.push_back(SelectionModel(3, CURSOR_BACKWARD));
+ expected.push_back(SelectionModel(3, CURSOR_FORWARD));
+ RunMoveCursorLeftRightTest(render_text.get(), expected, CURSOR_RIGHT);
+
+ expected.clear();
+ expected.push_back(SelectionModel(3, CURSOR_FORWARD));
+ expected.push_back(SelectionModel(2, CURSOR_FORWARD));
+ expected.push_back(SelectionModel(1, CURSOR_FORWARD));
+ expected.push_back(SelectionModel(0, CURSOR_FORWARD));
+ expected.push_back(SelectionModel(0, CURSOR_BACKWARD));
+ RunMoveCursorLeftRightTest(render_text.get(), expected, CURSOR_LEFT);
+}
+
+TEST_F(RenderTextTest, MoveCursorLeftRightInLtrRtl) {
+ scoped_ptr<RenderText> render_text(RenderText::CreateInstance());
+ // LTR-RTL
+ render_text->SetText(WideToUTF16(L"abc\x05d0\x05d1\x05d2"));
+ // The last one is the expected END position.
+ std::vector<SelectionModel> expected;
+ expected.push_back(SelectionModel(0, CURSOR_BACKWARD));
+ expected.push_back(SelectionModel(1, CURSOR_BACKWARD));
+ expected.push_back(SelectionModel(2, CURSOR_BACKWARD));
+ expected.push_back(SelectionModel(3, CURSOR_BACKWARD));
+ expected.push_back(SelectionModel(5, CURSOR_FORWARD));
+ expected.push_back(SelectionModel(4, CURSOR_FORWARD));
+ expected.push_back(SelectionModel(3, CURSOR_FORWARD));
+ expected.push_back(SelectionModel(6, CURSOR_FORWARD));
+ RunMoveCursorLeftRightTest(render_text.get(), expected, CURSOR_RIGHT);
+
+ expected.clear();
+ expected.push_back(SelectionModel(6, CURSOR_FORWARD));
+ expected.push_back(SelectionModel(4, CURSOR_BACKWARD));
+ expected.push_back(SelectionModel(5, CURSOR_BACKWARD));
+ expected.push_back(SelectionModel(6, CURSOR_BACKWARD));
+ expected.push_back(SelectionModel(2, CURSOR_FORWARD));
+ expected.push_back(SelectionModel(1, CURSOR_FORWARD));
+ expected.push_back(SelectionModel(0, CURSOR_FORWARD));
+ expected.push_back(SelectionModel(0, CURSOR_BACKWARD));
+ RunMoveCursorLeftRightTest(render_text.get(), expected, CURSOR_LEFT);
+}
+
+TEST_F(RenderTextTest, MoveCursorLeftRightInLtrRtlLtr) {
+ scoped_ptr<RenderText> render_text(RenderText::CreateInstance());
+ // LTR-RTL-LTR.
+ render_text->SetText(WideToUTF16(L"a" L"\x05d1" L"b"));
+ std::vector<SelectionModel> expected;
+ expected.push_back(SelectionModel(0, CURSOR_BACKWARD));
+ expected.push_back(SelectionModel(1, CURSOR_BACKWARD));
+ expected.push_back(SelectionModel(1, CURSOR_FORWARD));
+ expected.push_back(SelectionModel(3, CURSOR_BACKWARD));
+ expected.push_back(SelectionModel(3, CURSOR_FORWARD));
+ RunMoveCursorLeftRightTest(render_text.get(), expected, CURSOR_RIGHT);
+
+ expected.clear();
+ expected.push_back(SelectionModel(3, CURSOR_FORWARD));
+ expected.push_back(SelectionModel(2, CURSOR_FORWARD));
+ expected.push_back(SelectionModel(2, CURSOR_BACKWARD));
+ expected.push_back(SelectionModel(0, CURSOR_FORWARD));
+ expected.push_back(SelectionModel(0, CURSOR_BACKWARD));
+ RunMoveCursorLeftRightTest(render_text.get(), expected, CURSOR_LEFT);
+}
+
+TEST_F(RenderTextTest, MoveCursorLeftRightInRtl) {
+ scoped_ptr<RenderText> render_text(RenderText::CreateInstance());
+ // Pure RTL.
+ render_text->SetText(WideToUTF16(L"\x05d0\x05d1\x05d2"));
+ render_text->MoveCursor(LINE_BREAK, CURSOR_RIGHT, false);
+ std::vector<SelectionModel> expected;
+
+ expected.push_back(SelectionModel(0, CURSOR_BACKWARD));
+ expected.push_back(SelectionModel(1, CURSOR_BACKWARD));
+ expected.push_back(SelectionModel(2, CURSOR_BACKWARD));
+ expected.push_back(SelectionModel(3, CURSOR_BACKWARD));
+ expected.push_back(SelectionModel(3, CURSOR_FORWARD));
+ RunMoveCursorLeftRightTest(render_text.get(), expected, CURSOR_LEFT);
+
+ expected.clear();
+
+ expected.push_back(SelectionModel(3, CURSOR_FORWARD));
+ expected.push_back(SelectionModel(2, CURSOR_FORWARD));
+ expected.push_back(SelectionModel(1, CURSOR_FORWARD));
+ expected.push_back(SelectionModel(0, CURSOR_FORWARD));
+ expected.push_back(SelectionModel(0, CURSOR_BACKWARD));
+ RunMoveCursorLeftRightTest(render_text.get(), expected, CURSOR_RIGHT);
+}
+
+TEST_F(RenderTextTest, MoveCursorLeftRightInRtlLtr) {
+ scoped_ptr<RenderText> render_text(RenderText::CreateInstance());
+ // RTL-LTR
+ render_text->SetText(WideToUTF16(L"\x05d0\x05d1\x05d2" L"abc"));
+ render_text->MoveCursor(LINE_BREAK, CURSOR_RIGHT, false);
+ std::vector<SelectionModel> expected;
+ expected.push_back(SelectionModel(0, CURSOR_BACKWARD));
+ expected.push_back(SelectionModel(1, CURSOR_BACKWARD));
+ expected.push_back(SelectionModel(2, CURSOR_BACKWARD));
+ expected.push_back(SelectionModel(3, CURSOR_BACKWARD));
+ expected.push_back(SelectionModel(5, CURSOR_FORWARD));
+ expected.push_back(SelectionModel(4, CURSOR_FORWARD));
+ expected.push_back(SelectionModel(3, CURSOR_FORWARD));
+ expected.push_back(SelectionModel(6, CURSOR_FORWARD));
+ RunMoveCursorLeftRightTest(render_text.get(), expected, CURSOR_LEFT);
+
+ expected.clear();
+ expected.push_back(SelectionModel(6, CURSOR_FORWARD));
+ expected.push_back(SelectionModel(4, CURSOR_BACKWARD));
+ expected.push_back(SelectionModel(5, CURSOR_BACKWARD));
+ expected.push_back(SelectionModel(6, CURSOR_BACKWARD));
+ expected.push_back(SelectionModel(2, CURSOR_FORWARD));
+ expected.push_back(SelectionModel(1, CURSOR_FORWARD));
+ expected.push_back(SelectionModel(0, CURSOR_FORWARD));
+ expected.push_back(SelectionModel(0, CURSOR_BACKWARD));
+ RunMoveCursorLeftRightTest(render_text.get(), expected, CURSOR_RIGHT);
+}
+
+TEST_F(RenderTextTest, MoveCursorLeftRightInRtlLtrRtl) {
+ scoped_ptr<RenderText> render_text(RenderText::CreateInstance());
+ // RTL-LTR-RTL.
+ render_text->SetText(WideToUTF16(L"\x05d0" L"a" L"\x05d1"));
+ render_text->MoveCursor(LINE_BREAK, CURSOR_RIGHT, false);
+ std::vector<SelectionModel> expected;
+ expected.push_back(SelectionModel(0, CURSOR_BACKWARD));
+ expected.push_back(SelectionModel(1, CURSOR_BACKWARD));
+ expected.push_back(SelectionModel(1, CURSOR_FORWARD));
+ expected.push_back(SelectionModel(3, CURSOR_BACKWARD));
+ expected.push_back(SelectionModel(3, CURSOR_FORWARD));
+ RunMoveCursorLeftRightTest(render_text.get(), expected, CURSOR_LEFT);
+
+ expected.clear();
+ expected.push_back(SelectionModel(3, CURSOR_FORWARD));
+ expected.push_back(SelectionModel(2, CURSOR_FORWARD));
+ expected.push_back(SelectionModel(2, CURSOR_BACKWARD));
+ expected.push_back(SelectionModel(0, CURSOR_FORWARD));
+ expected.push_back(SelectionModel(0, CURSOR_BACKWARD));
+ RunMoveCursorLeftRightTest(render_text.get(), expected, CURSOR_RIGHT);
+}
+
+// TODO(xji): temporarily disable in platform Win since the complex script
+// characters turned into empty square due to font regression. So, not able
+// to test 2 characters belong to the same grapheme.
+#if defined(OS_LINUX)
+TEST_F(RenderTextTest, MoveCursorLeftRight_ComplexScript) {
+ scoped_ptr<RenderText> render_text(RenderText::CreateInstance());
+
+ render_text->SetText(WideToUTF16(L"\x0915\x093f\x0915\x094d\x0915"));
+ EXPECT_EQ(0U, render_text->cursor_position());
+ render_text->MoveCursor(CHARACTER_BREAK, CURSOR_RIGHT, false);
+ EXPECT_EQ(2U, render_text->cursor_position());
+ render_text->MoveCursor(CHARACTER_BREAK, CURSOR_RIGHT, false);
+ EXPECT_EQ(4U, render_text->cursor_position());
+ render_text->MoveCursor(CHARACTER_BREAK, CURSOR_RIGHT, false);
+ EXPECT_EQ(5U, render_text->cursor_position());
+ render_text->MoveCursor(CHARACTER_BREAK, CURSOR_RIGHT, false);
+ EXPECT_EQ(5U, render_text->cursor_position());
+
+ render_text->MoveCursor(CHARACTER_BREAK, CURSOR_LEFT, false);
+ EXPECT_EQ(4U, render_text->cursor_position());
+ render_text->MoveCursor(CHARACTER_BREAK, CURSOR_LEFT, false);
+ EXPECT_EQ(2U, render_text->cursor_position());
+ render_text->MoveCursor(CHARACTER_BREAK, CURSOR_LEFT, false);
+ EXPECT_EQ(0U, render_text->cursor_position());
+ render_text->MoveCursor(CHARACTER_BREAK, CURSOR_LEFT, false);
+ EXPECT_EQ(0U, render_text->cursor_position());
+}
+#endif
+
+TEST_F(RenderTextTest, MoveCursorLeftRight_MeiryoUILigatures) {
+ scoped_ptr<RenderText> render_text(RenderText::CreateInstance());
+ // Meiryo UI uses single-glyph ligatures for 'ff' and 'ffi', but each letter
+ // (code point) has unique bounds, so mid-glyph cursoring should be possible.
+ render_text->SetFont(Font("Meiryo UI", 12));
+ render_text->SetText(WideToUTF16(L"ff ffi"));
+ EXPECT_EQ(0U, render_text->cursor_position());
+ for (size_t i = 0; i < render_text->text().length(); ++i) {
+ render_text->MoveCursor(CHARACTER_BREAK, CURSOR_RIGHT, false);
+ EXPECT_EQ(i + 1, render_text->cursor_position());
+ }
+ EXPECT_EQ(6U, render_text->cursor_position());
+}
+
+TEST_F(RenderTextTest, GraphemePositions) {
+ // LTR 2-character grapheme, LTR abc, LTR 2-character grapheme.
+ const base::string16 kText1 =
+ WideToUTF16(L"\x0915\x093f" L"abc" L"\x0915\x093f");
+
+ // LTR ab, LTR 2-character grapheme, LTR cd.
+ const base::string16 kText2 = WideToUTF16(L"ab" L"\x0915\x093f" L"cd");
+
+ // The below is 'MUSICAL SYMBOL G CLEF', which is represented in UTF-16 as
+ // two characters forming the surrogate pair 0x0001D11E.
+ const std::string kSurrogate = "\xF0\x9D\x84\x9E";
+
+ // LTR ab, UTF16 surrogate pair, LTR cd.
+ const base::string16 kText3 = UTF8ToUTF16("ab" + kSurrogate + "cd");
+
+ struct {
+ base::string16 text;
+ size_t index;
+ size_t expected_previous;
+ size_t expected_next;
+ } cases[] = {
+ { base::string16(), 0, 0, 0 },
+ { base::string16(), 1, 0, 0 },
+ { base::string16(), 50, 0, 0 },
+ { kText1, 0, 0, 2 },
+ { kText1, 1, 0, 2 },
+ { kText1, 2, 0, 3 },
+ { kText1, 3, 2, 4 },
+ { kText1, 4, 3, 5 },
+ { kText1, 5, 4, 7 },
+ { kText1, 6, 5, 7 },
+ { kText1, 7, 5, 7 },
+ { kText1, 8, 7, 7 },
+ { kText1, 50, 7, 7 },
+ { kText2, 0, 0, 1 },
+ { kText2, 1, 0, 2 },
+ { kText2, 2, 1, 4 },
+ { kText2, 3, 2, 4 },
+ { kText2, 4, 2, 5 },
+ { kText2, 5, 4, 6 },
+ { kText2, 6, 5, 6 },
+ { kText2, 7, 6, 6 },
+ { kText2, 50, 6, 6 },
+ { kText3, 0, 0, 1 },
+ { kText3, 1, 0, 2 },
+ { kText3, 2, 1, 4 },
+ { kText3, 3, 2, 4 },
+ { kText3, 4, 2, 5 },
+ { kText3, 5, 4, 6 },
+ { kText3, 6, 5, 6 },
+ { kText3, 7, 6, 6 },
+ { kText3, 50, 6, 6 },
+ };
+
+ // TODO(asvitkine): Disable tests that fail on XP bots due to lack of complete
+ // font support for some scripts - http://crbug.com/106450
+#if defined(OS_WIN)
+ if (base::win::GetVersion() < base::win::VERSION_VISTA)
+ return;
+#endif
+
+ scoped_ptr<RenderText> render_text(RenderText::CreateInstance());
+ for (size_t i = 0; i < ARRAYSIZE_UNSAFE(cases); i++) {
+ render_text->SetText(cases[i].text);
+
+ size_t next = render_text->IndexOfAdjacentGrapheme(cases[i].index,
+ CURSOR_FORWARD);
+ EXPECT_EQ(cases[i].expected_next, next);
+ EXPECT_TRUE(render_text->IsCursorablePosition(next));
+
+ size_t previous = render_text->IndexOfAdjacentGrapheme(cases[i].index,
+ CURSOR_BACKWARD);
+ EXPECT_EQ(cases[i].expected_previous, previous);
+ EXPECT_TRUE(render_text->IsCursorablePosition(previous));
+ }
+}
+
+TEST_F(RenderTextTest, EdgeSelectionModels) {
+ // Simple Latin text.
+ const base::string16 kLatin = WideToUTF16(L"abc");
+ // LTR 2-character grapheme.
+ const base::string16 kLTRGrapheme = WideToUTF16(L"\x0915\x093f");
+ // LTR 2-character grapheme, LTR a, LTR 2-character grapheme.
+ const base::string16 kHindiLatin =
+ WideToUTF16(L"\x0915\x093f" L"a" L"\x0915\x093f");
+ // RTL 2-character grapheme.
+ const base::string16 kRTLGrapheme = WideToUTF16(L"\x05e0\x05b8");
+ // RTL 2-character grapheme, LTR a, RTL 2-character grapheme.
+ const base::string16 kHebrewLatin =
+ WideToUTF16(L"\x05e0\x05b8" L"a" L"\x05e0\x05b8");
+
+ struct {
+ base::string16 text;
+ base::i18n::TextDirection expected_text_direction;
+ } cases[] = {
+ { base::string16(), base::i18n::LEFT_TO_RIGHT },
+ { kLatin, base::i18n::LEFT_TO_RIGHT },
+ { kLTRGrapheme, base::i18n::LEFT_TO_RIGHT },
+ { kHindiLatin, base::i18n::LEFT_TO_RIGHT },
+ { kRTLGrapheme, base::i18n::RIGHT_TO_LEFT },
+ { kHebrewLatin, base::i18n::RIGHT_TO_LEFT },
+ };
+
+ // TODO(asvitkine): Disable tests that fail on XP bots due to lack of complete
+ // font support for some scripts - http://crbug.com/106450
+#if defined(OS_WIN)
+ if (base::win::GetVersion() < base::win::VERSION_VISTA)
+ return;
+#endif
+
+ scoped_ptr<RenderText> render_text(RenderText::CreateInstance());
+ for (size_t i = 0; i < ARRAYSIZE_UNSAFE(cases); i++) {
+ render_text->SetText(cases[i].text);
+ bool ltr = (cases[i].expected_text_direction == base::i18n::LEFT_TO_RIGHT);
+
+ SelectionModel start_edge =
+ render_text->EdgeSelectionModel(ltr ? CURSOR_LEFT : CURSOR_RIGHT);
+ EXPECT_EQ(start_edge, SelectionModel(0, CURSOR_BACKWARD));
+
+ SelectionModel end_edge =
+ render_text->EdgeSelectionModel(ltr ? CURSOR_RIGHT : CURSOR_LEFT);
+ EXPECT_EQ(end_edge, SelectionModel(cases[i].text.length(), CURSOR_FORWARD));
+ }
+}
+
+TEST_F(RenderTextTest, SelectAll) {
+ const wchar_t* const cases[] =
+ { kWeak, kLtr, kLtrRtl, kLtrRtlLtr, kRtl, kRtlLtr, kRtlLtrRtl };
+
+ // Ensure that SelectAll respects the |reversed| argument regardless of
+ // application locale and text content directionality.
+ scoped_ptr<RenderText> render_text(RenderText::CreateInstance());
+ const SelectionModel expected_reversed(ui::Range(3, 0), CURSOR_FORWARD);
+ const SelectionModel expected_forwards(ui::Range(0, 3), CURSOR_BACKWARD);
+ const bool was_rtl = base::i18n::IsRTL();
+
+ for (size_t i = 0; i < 2; ++i) {
+ SetRTL(!base::i18n::IsRTL());
+ // Test that an empty string produces an empty selection model.
+ render_text->SetText(base::string16());
+ EXPECT_EQ(render_text->selection_model(), SelectionModel());
+
+ // Test the weak, LTR, RTL, and Bidi string cases.
+ for (size_t j = 0; j < ARRAYSIZE_UNSAFE(cases); j++) {
+ render_text->SetText(WideToUTF16(cases[j]));
+ render_text->SelectAll(false);
+ EXPECT_EQ(render_text->selection_model(), expected_forwards);
+ render_text->SelectAll(true);
+ EXPECT_EQ(render_text->selection_model(), expected_reversed);
+ }
+ }
+
+ EXPECT_EQ(was_rtl, base::i18n::IsRTL());
+}
+
+ TEST_F(RenderTextTest, MoveCursorLeftRightWithSelection) {
+ scoped_ptr<RenderText> render_text(RenderText::CreateInstance());
+ render_text->SetText(WideToUTF16(L"abc\x05d0\x05d1\x05d2"));
+ // Left arrow on select ranging (6, 4).
+ render_text->MoveCursor(LINE_BREAK, CURSOR_RIGHT, false);
+ EXPECT_EQ(ui::Range(6), render_text->selection());
+ render_text->MoveCursor(CHARACTER_BREAK, CURSOR_LEFT, false);
+ EXPECT_EQ(ui::Range(4), render_text->selection());
+ render_text->MoveCursor(CHARACTER_BREAK, CURSOR_LEFT, false);
+ EXPECT_EQ(ui::Range(5), render_text->selection());
+ render_text->MoveCursor(CHARACTER_BREAK, CURSOR_LEFT, false);
+ EXPECT_EQ(ui::Range(6), render_text->selection());
+ render_text->MoveCursor(CHARACTER_BREAK, CURSOR_RIGHT, true);
+ EXPECT_EQ(ui::Range(6, 5), render_text->selection());
+ render_text->MoveCursor(CHARACTER_BREAK, CURSOR_RIGHT, true);
+ EXPECT_EQ(ui::Range(6, 4), render_text->selection());
+ render_text->MoveCursor(CHARACTER_BREAK, CURSOR_LEFT, false);
+ EXPECT_EQ(ui::Range(6), render_text->selection());
+
+ // Right arrow on select ranging (4, 6).
+ render_text->MoveCursor(LINE_BREAK, CURSOR_LEFT, false);
+ EXPECT_EQ(ui::Range(0), render_text->selection());
+ render_text->MoveCursor(CHARACTER_BREAK, CURSOR_RIGHT, false);
+ EXPECT_EQ(ui::Range(1), render_text->selection());
+ render_text->MoveCursor(CHARACTER_BREAK, CURSOR_RIGHT, false);
+ EXPECT_EQ(ui::Range(2), render_text->selection());
+ render_text->MoveCursor(CHARACTER_BREAK, CURSOR_RIGHT, false);
+ EXPECT_EQ(ui::Range(3), render_text->selection());
+ render_text->MoveCursor(CHARACTER_BREAK, CURSOR_RIGHT, false);
+ EXPECT_EQ(ui::Range(5), render_text->selection());
+ render_text->MoveCursor(CHARACTER_BREAK, CURSOR_RIGHT, false);
+ EXPECT_EQ(ui::Range(4), render_text->selection());
+ render_text->MoveCursor(CHARACTER_BREAK, CURSOR_LEFT, true);
+ EXPECT_EQ(ui::Range(4, 5), render_text->selection());
+ render_text->MoveCursor(CHARACTER_BREAK, CURSOR_LEFT, true);
+ EXPECT_EQ(ui::Range(4, 6), render_text->selection());
+ render_text->MoveCursor(CHARACTER_BREAK, CURSOR_RIGHT, false);
+ EXPECT_EQ(ui::Range(4), render_text->selection());
+}
+#endif // !defined(OS_MACOSX)
+
+// TODO(xji): Make these work on Windows.
+#if defined(OS_LINUX)
+void MoveLeftRightByWordVerifier(RenderText* render_text,
+ const wchar_t* str) {
+ render_text->SetText(WideToUTF16(str));
+
+ // Test moving by word from left ro right.
+ render_text->MoveCursor(LINE_BREAK, CURSOR_LEFT, false);
+ bool first_word = true;
+ while (true) {
+ // First, test moving by word from a word break position, such as from
+ // "|abc def" to "abc| def".
+ SelectionModel start = render_text->selection_model();
+ render_text->MoveCursor(WORD_BREAK, CURSOR_RIGHT, false);
+ SelectionModel end = render_text->selection_model();
+ if (end == start) // reach the end.
+ break;
+
+ // For testing simplicity, each word is a 3-character word.
+ int num_of_character_moves = first_word ? 3 : 4;
+ first_word = false;
+ render_text->MoveCursorTo(start);
+ for (int j = 0; j < num_of_character_moves; ++j)
+ render_text->MoveCursor(CHARACTER_BREAK, CURSOR_RIGHT, false);
+ EXPECT_EQ(end, render_text->selection_model());
+
+ // Then, test moving by word from positions inside the word, such as from
+ // "a|bc def" to "abc| def", and from "ab|c def" to "abc| def".
+ for (int j = 1; j < num_of_character_moves; ++j) {
+ render_text->MoveCursorTo(start);
+ for (int k = 0; k < j; ++k)
+ render_text->MoveCursor(CHARACTER_BREAK, CURSOR_RIGHT, false);
+ render_text->MoveCursor(WORD_BREAK, CURSOR_RIGHT, false);
+ EXPECT_EQ(end, render_text->selection_model());
+ }
+ }
+
+ // Test moving by word from right to left.
+ render_text->MoveCursor(LINE_BREAK, CURSOR_RIGHT, false);
+ first_word = true;
+ while (true) {
+ SelectionModel start = render_text->selection_model();
+ render_text->MoveCursor(WORD_BREAK, CURSOR_LEFT, false);
+ SelectionModel end = render_text->selection_model();
+ if (end == start) // reach the end.
+ break;
+
+ int num_of_character_moves = first_word ? 3 : 4;
+ first_word = false;
+ render_text->MoveCursorTo(start);
+ for (int j = 0; j < num_of_character_moves; ++j)
+ render_text->MoveCursor(CHARACTER_BREAK, CURSOR_LEFT, false);
+ EXPECT_EQ(end, render_text->selection_model());
+
+ for (int j = 1; j < num_of_character_moves; ++j) {
+ render_text->MoveCursorTo(start);
+ for (int k = 0; k < j; ++k)
+ render_text->MoveCursor(CHARACTER_BREAK, CURSOR_LEFT, false);
+ render_text->MoveCursor(WORD_BREAK, CURSOR_LEFT, false);
+ EXPECT_EQ(end, render_text->selection_model());
+ }
+ }
+}
+
+TEST_F(RenderTextTest, MoveLeftRightByWordInBidiText) {
+ scoped_ptr<RenderText> render_text(RenderText::CreateInstance());
+
+ // For testing simplicity, each word is a 3-character word.
+ std::vector<const wchar_t*> test;
+ test.push_back(L"abc");
+ test.push_back(L"abc def");
+ test.push_back(L"\x05E1\x05E2\x05E3");
+ test.push_back(L"\x05E1\x05E2\x05E3 \x05E4\x05E5\x05E6");
+ test.push_back(L"abc \x05E1\x05E2\x05E3");
+ test.push_back(L"abc def \x05E1\x05E2\x05E3 \x05E4\x05E5\x05E6");
+ test.push_back(L"abc def hij \x05E1\x05E2\x05E3 \x05E4\x05E5\x05E6"
+ L" \x05E7\x05E8\x05E9");
+
+ test.push_back(L"abc \x05E1\x05E2\x05E3 hij");
+ test.push_back(L"abc def \x05E1\x05E2\x05E3 \x05E4\x05E5\x05E6 hij opq");
+ test.push_back(L"abc def hij \x05E1\x05E2\x05E3 \x05E4\x05E5\x05E6"
+ L" \x05E7\x05E8\x05E9" L" opq rst uvw");
+
+ test.push_back(L"\x05E1\x05E2\x05E3 abc");
+ test.push_back(L"\x05E1\x05E2\x05E3 \x05E4\x05E5\x05E6 abc def");
+ test.push_back(L"\x05E1\x05E2\x05E3 \x05E4\x05E5\x05E6 \x05E7\x05E8\x05E9"
+ L" abc def hij");
+
+ test.push_back(L"\x05D1\x05D2\x05D3 abc \x05E1\x05E2\x05E3");
+ test.push_back(L"\x05D1\x05D2\x05D3 \x05D4\x05D5\x05D6 abc def"
+ L" \x05E1\x05E2\x05E3 \x05E4\x05E5\x05E6");
+ test.push_back(L"\x05D1\x05D2\x05D3 \x05D4\x05D5\x05D6 \x05D7\x05D8\x05D9"
+ L" abc def hij \x05E1\x05E2\x05E3 \x05E4\x05E5\x05E6"
+ L" \x05E7\x05E8\x05E9");
+
+ for (size_t i = 0; i < test.size(); ++i)
+ MoveLeftRightByWordVerifier(render_text.get(), test[i]);
+}
+
+TEST_F(RenderTextTest, MoveLeftRightByWordInBidiText_TestEndOfText) {
+ scoped_ptr<RenderText> render_text(RenderText::CreateInstance());
+
+ render_text->SetText(WideToUTF16(L"ab\x05E1"));
+ // Moving the cursor by word from "abC|" to the left should return "|abC".
+ // But since end of text is always treated as a word break, it returns
+ // position "ab|C".
+ // TODO(xji): Need to make it work as expected.
+ render_text->MoveCursor(LINE_BREAK, CURSOR_RIGHT, false);
+ render_text->MoveCursor(WORD_BREAK, CURSOR_LEFT, false);
+ // EXPECT_EQ(SelectionModel(), render_text->selection_model());
+
+ // Moving the cursor by word from "|abC" to the right returns "abC|".
+ render_text->MoveCursor(LINE_BREAK, CURSOR_LEFT, false);
+ render_text->MoveCursor(WORD_BREAK, CURSOR_RIGHT, false);
+ EXPECT_EQ(SelectionModel(3, CURSOR_FORWARD), render_text->selection_model());
+
+ render_text->SetText(WideToUTF16(L"\x05E1\x05E2" L"a"));
+ // For logical text "BCa", moving the cursor by word from "aCB|" to the left
+ // returns "|aCB".
+ render_text->MoveCursor(LINE_BREAK, CURSOR_RIGHT, false);
+ render_text->MoveCursor(WORD_BREAK, CURSOR_LEFT, false);
+ EXPECT_EQ(SelectionModel(3, CURSOR_FORWARD), render_text->selection_model());
+
+ // Moving the cursor by word from "|aCB" to the right should return "aCB|".
+ // But since end of text is always treated as a word break, it returns
+ // position "a|CB".
+ // TODO(xji): Need to make it work as expected.
+ render_text->MoveCursor(LINE_BREAK, CURSOR_LEFT, false);
+ render_text->MoveCursor(WORD_BREAK, CURSOR_RIGHT, false);
+ // EXPECT_EQ(SelectionModel(), render_text->selection_model());
+}
+
+TEST_F(RenderTextTest, MoveLeftRightByWordInTextWithMultiSpaces) {
+ scoped_ptr<RenderText> render_text(RenderText::CreateInstance());
+ render_text->SetText(WideToUTF16(L"abc def"));
+ render_text->MoveCursorTo(SelectionModel(5, CURSOR_FORWARD));
+ render_text->MoveCursor(WORD_BREAK, CURSOR_RIGHT, false);
+ EXPECT_EQ(11U, render_text->cursor_position());
+
+ render_text->MoveCursorTo(SelectionModel(5, CURSOR_FORWARD));
+ render_text->MoveCursor(WORD_BREAK, CURSOR_LEFT, false);
+ EXPECT_EQ(0U, render_text->cursor_position());
+}
+
+TEST_F(RenderTextTest, MoveLeftRightByWordInChineseText) {
+ scoped_ptr<RenderText> render_text(RenderText::CreateInstance());
+ render_text->SetText(WideToUTF16(L"\x6211\x4EEC\x53BB\x516C\x56ED\x73A9"));
+ render_text->MoveCursor(LINE_BREAK, CURSOR_LEFT, false);
+ EXPECT_EQ(0U, render_text->cursor_position());
+ render_text->MoveCursor(WORD_BREAK, CURSOR_RIGHT, false);
+ EXPECT_EQ(2U, render_text->cursor_position());
+ render_text->MoveCursor(WORD_BREAK, CURSOR_RIGHT, false);
+ EXPECT_EQ(3U, render_text->cursor_position());
+ render_text->MoveCursor(WORD_BREAK, CURSOR_RIGHT, false);
+ EXPECT_EQ(5U, render_text->cursor_position());
+ render_text->MoveCursor(WORD_BREAK, CURSOR_RIGHT, false);
+ EXPECT_EQ(6U, render_text->cursor_position());
+ render_text->MoveCursor(WORD_BREAK, CURSOR_RIGHT, false);
+ EXPECT_EQ(6U, render_text->cursor_position());
+}
+#endif
+
+#if defined(OS_WIN)
+TEST_F(RenderTextTest, Win_LogicalClusters) {
+ scoped_ptr<RenderTextWin> render_text(
+ static_cast<RenderTextWin*>(RenderText::CreateInstance()));
+
+ const base::string16 test_string =
+ WideToUTF16(L"\x0930\x0930\x0930\x0930\x0930");
+ render_text->SetText(test_string);
+ render_text->EnsureLayout();
+ ASSERT_EQ(1U, render_text->runs_.size());
+ WORD* logical_clusters = render_text->runs_[0]->logical_clusters.get();
+ for (size_t i = 0; i < test_string.length(); ++i)
+ EXPECT_EQ(i, logical_clusters[i]);
+}
+#endif // defined(OS_WIN)
+
+TEST_F(RenderTextTest, StringSizeSanity) {
+ scoped_ptr<RenderText> render_text(RenderText::CreateInstance());
+ render_text->SetText(UTF8ToUTF16("Hello World"));
+ const Size string_size = render_text->GetStringSize();
+ EXPECT_GT(string_size.width(), 0);
+ EXPECT_GT(string_size.height(), 0);
+}
+
+// TODO(asvitkine): This test fails because PlatformFontMac uses point font
+// sizes instead of pixel sizes like other implementations.
+#if !defined(OS_MACOSX)
+TEST_F(RenderTextTest, StringSizeEmptyString) {
+ // Ascent and descent of Arial and Symbol are different on most platforms.
+ const FontList font_list("Arial,Symbol, 16px");
+ scoped_ptr<RenderText> render_text(RenderText::CreateInstance());
+ render_text->SetFontList(font_list);
+
+ // The empty string respects FontList metrics for non-zero height
+ // and baseline.
+ render_text->SetText(base::string16());
+ EXPECT_EQ(font_list.GetHeight(), render_text->GetStringSize().height());
+ EXPECT_EQ(0, render_text->GetStringSize().width());
+ EXPECT_EQ(font_list.GetBaseline(), render_text->GetBaseline());
+
+ render_text->SetText(UTF8ToUTF16(" "));
+ EXPECT_EQ(font_list.GetHeight(), render_text->GetStringSize().height());
+ EXPECT_EQ(font_list.GetBaseline(), render_text->GetBaseline());
+}
+#endif // !defined(OS_MACOSX)
+
+TEST_F(RenderTextTest, StringSizeRespectsFontListMetrics) {
+ // Check that Arial and Symbol have different font metrics.
+ Font arial_font("Arial", 16);
+ Font symbol_font("Symbol", 16);
+ EXPECT_NE(arial_font.GetHeight(), symbol_font.GetHeight());
+ EXPECT_NE(arial_font.GetBaseline(), symbol_font.GetBaseline());
+ // "a" should be rendered with Arial, not with Symbol.
+ const char* arial_font_text = "a";
+ // "®" (registered trademark symbol) should be rendered with Symbol,
+ // not with Arial.
+ const char* symbol_font_text = "\xC2\xAE";
+
+ Font smaller_font = arial_font;
+ Font larger_font = symbol_font;
+ const char* smaller_font_text = arial_font_text;
+ const char* larger_font_text = symbol_font_text;
+ if (symbol_font.GetHeight() < arial_font.GetHeight() &&
+ symbol_font.GetBaseline() < arial_font.GetBaseline()) {
+ std::swap(smaller_font, larger_font);
+ std::swap(smaller_font_text, larger_font_text);
+ }
+ ASSERT_LT(smaller_font.GetHeight(), larger_font.GetHeight());
+ ASSERT_LT(smaller_font.GetBaseline(), larger_font.GetBaseline());
+
+ // Check |smaller_font_text| is rendered with the smaller font.
+ scoped_ptr<RenderText> render_text(RenderText::CreateInstance());
+ render_text->SetText(UTF8ToUTF16(smaller_font_text));
+ render_text->SetFont(smaller_font);
+ EXPECT_EQ(smaller_font.GetHeight(), render_text->GetStringSize().height());
+ EXPECT_EQ(smaller_font.GetBaseline(), render_text->GetBaseline());
+
+ // Layout the same text with mixed fonts. The text should be rendered with
+ // the smaller font, but the height and baseline are determined with the
+ // metrics of the font list, which is equal to the larger font.
+ std::vector<Font> fonts;
+ fonts.push_back(smaller_font); // The primary font is the smaller font.
+ fonts.push_back(larger_font);
+ const FontList font_list(fonts);
+ render_text->SetFontList(font_list);
+ EXPECT_LT(smaller_font.GetHeight(), render_text->GetStringSize().height());
+ EXPECT_LT(smaller_font.GetBaseline(), render_text->GetBaseline());
+ EXPECT_EQ(font_list.GetHeight(), render_text->GetStringSize().height());
+ EXPECT_EQ(font_list.GetBaseline(), render_text->GetBaseline());
+}
+
+TEST_F(RenderTextTest, SetFont) {
+ scoped_ptr<RenderText> render_text(RenderText::CreateInstance());
+ render_text->SetFont(Font("Arial", 12));
+ EXPECT_EQ("Arial", render_text->GetPrimaryFont().GetFontName());
+ EXPECT_EQ(12, render_text->GetPrimaryFont().GetFontSize());
+}
+
+TEST_F(RenderTextTest, SetFontList) {
+ scoped_ptr<RenderText> render_text(RenderText::CreateInstance());
+ render_text->SetFontList(FontList("Arial,Symbol, 13px"));
+ const std::vector<Font>& fonts = render_text->font_list().GetFonts();
+ ASSERT_EQ(2U, fonts.size());
+ EXPECT_EQ("Arial", fonts[0].GetFontName());
+ EXPECT_EQ("Symbol", fonts[1].GetFontName());
+ EXPECT_EQ(13, render_text->GetPrimaryFont().GetFontSize());
+}
+
+TEST_F(RenderTextTest, StringSizeBoldWidth) {
+ scoped_ptr<RenderText> render_text(RenderText::CreateInstance());
+ render_text->SetText(UTF8ToUTF16("Hello World"));
+
+ const int plain_width = render_text->GetStringSize().width();
+ EXPECT_GT(plain_width, 0);
+
+ // Apply a bold style and check that the new width is greater.
+ render_text->SetStyle(BOLD, true);
+ const int bold_width = render_text->GetStringSize().width();
+ EXPECT_GT(bold_width, plain_width);
+
+ // Now, apply a plain style over the first word only.
+ render_text->ApplyStyle(BOLD, false, ui::Range(0, 5));
+ const int plain_bold_width = render_text->GetStringSize().width();
+ EXPECT_GT(plain_bold_width, plain_width);
+ EXPECT_LT(plain_bold_width, bold_width);
+}
+
+TEST_F(RenderTextTest, StringSizeHeight) {
+ base::string16 cases[] = {
+ WideToUTF16(L"Hello World!"), // English
+ WideToUTF16(L"\x6328\x62f6"), // Japanese
+ WideToUTF16(L"\x0915\x093f"), // Hindi
+ WideToUTF16(L"\x05e0\x05b8"), // Hebrew
+ };
+
+ Font default_font;
+ Font larger_font = default_font.DeriveFont(24, default_font.GetStyle());
+ EXPECT_GT(larger_font.GetHeight(), default_font.GetHeight());
+
+ for (size_t i = 0; i < ARRAYSIZE_UNSAFE(cases); i++) {
+ scoped_ptr<RenderText> render_text(RenderText::CreateInstance());
+ render_text->SetFont(default_font);
+ render_text->SetText(cases[i]);
+
+ const int height1 = render_text->GetStringSize().height();
+ EXPECT_GT(height1, 0);
+
+ // Check that setting the larger font increases the height.
+ render_text->SetFont(larger_font);
+ const int height2 = render_text->GetStringSize().height();
+ EXPECT_GT(height2, height1);
+ }
+}
+
+TEST_F(RenderTextTest, GetBaselineSanity) {
+ scoped_ptr<RenderText> render_text(RenderText::CreateInstance());
+ render_text->SetText(UTF8ToUTF16("Hello World"));
+ const int baseline = render_text->GetBaseline();
+ EXPECT_GT(baseline, 0);
+}
+
+TEST_F(RenderTextTest, CursorBoundsInReplacementMode) {
+ scoped_ptr<RenderText> render_text(RenderText::CreateInstance());
+ render_text->SetText(ASCIIToUTF16("abcdefg"));
+ render_text->SetDisplayRect(Rect(100, 17));
+ SelectionModel sel_b(1, CURSOR_FORWARD);
+ SelectionModel sel_c(2, CURSOR_FORWARD);
+ Rect cursor_around_b = render_text->GetCursorBounds(sel_b, false);
+ Rect cursor_before_b = render_text->GetCursorBounds(sel_b, true);
+ Rect cursor_before_c = render_text->GetCursorBounds(sel_c, true);
+ EXPECT_EQ(cursor_around_b.x(), cursor_before_b.x());
+ EXPECT_EQ(cursor_around_b.right(), cursor_before_c.x());
+}
+
+TEST_F(RenderTextTest, GetTextOffset) {
+ // The default horizontal text offset differs for LTR and RTL, and is only set
+ // when the RenderText object is created. This test will check the default in
+ // LTR mode, and the next test will check the RTL default.
+ const bool was_rtl = base::i18n::IsRTL();
+ SetRTL(false);
+ scoped_ptr<RenderText> render_text(RenderText::CreateInstance());
+ render_text->SetText(ASCIIToUTF16("abcdefg"));
+ render_text->SetFontList(FontList("Arial, 13px"));
+
+ // Set display area's size equal to the font size.
+ const Size font_size(render_text->GetContentWidth(),
+ render_text->GetStringSize().height());
+ Rect display_rect(font_size);
+ render_text->SetDisplayRect(display_rect);
+
+ Vector2d offset = render_text->GetTextOffset();
+ EXPECT_TRUE(offset.IsZero());
+
+ // Set display area's size greater than font size.
+ const int kEnlargement = 2;
+ display_rect.Inset(0, 0, -kEnlargement, -kEnlargement);
+ render_text->SetDisplayRect(display_rect);
+
+ // Check the default horizontal and vertical alignment.
+ offset = render_text->GetTextOffset();
+ EXPECT_EQ(kEnlargement / 2, offset.y());
+ EXPECT_EQ(0, offset.x());
+
+ // Check explicitly setting the horizontal alignment.
+ render_text->SetHorizontalAlignment(ALIGN_LEFT);
+ offset = render_text->GetTextOffset();
+ EXPECT_EQ(0, offset.x());
+ render_text->SetHorizontalAlignment(ALIGN_CENTER);
+ offset = render_text->GetTextOffset();
+ EXPECT_EQ(kEnlargement / 2, offset.x());
+ render_text->SetHorizontalAlignment(ALIGN_RIGHT);
+ offset = render_text->GetTextOffset();
+ EXPECT_EQ(kEnlargement, offset.x());
+
+ // Check explicitly setting the vertical alignment.
+ render_text->SetVerticalAlignment(ALIGN_TOP);
+ offset = render_text->GetTextOffset();
+ EXPECT_EQ(0, offset.y());
+ render_text->SetVerticalAlignment(ALIGN_VCENTER);
+ offset = render_text->GetTextOffset();
+ EXPECT_EQ(kEnlargement / 2, offset.y());
+ render_text->SetVerticalAlignment(ALIGN_BOTTOM);
+ offset = render_text->GetTextOffset();
+ EXPECT_EQ(kEnlargement, offset.y());
+
+ SetRTL(was_rtl);
+}
+
+TEST_F(RenderTextTest, GetTextOffsetHorizontalDefaultInRTL) {
+ // This only checks the default horizontal alignment in RTL mode; all other
+ // GetTextOffset() attributes are checked by the test above.
+ const bool was_rtl = base::i18n::IsRTL();
+ SetRTL(true);
+ scoped_ptr<RenderText> render_text(RenderText::CreateInstance());
+ render_text->SetText(ASCIIToUTF16("abcdefg"));
+ render_text->SetFontList(FontList("Arial, 13px"));
+ const int kEnlargement = 2;
+ const Size font_size(render_text->GetContentWidth() + kEnlargement,
+ render_text->GetStringSize().height());
+ Rect display_rect(font_size);
+ render_text->SetDisplayRect(display_rect);
+ Vector2d offset = render_text->GetTextOffset();
+ EXPECT_EQ(kEnlargement, offset.x());
+ SetRTL(was_rtl);
+}
+
+TEST_F(RenderTextTest, SameFontForParentheses) {
+ struct {
+ const char16 left_char;
+ const char16 right_char;
+ } punctuation_pairs[] = {
+ { '(', ')' },
+ { '{', '}' },
+ { '<', '>' },
+ };
+ struct {
+ base::string16 text;
+ } cases[] = {
+ // English(English)
+ { WideToUTF16(L"Hello World(a)") },
+ // English(English)English
+ { WideToUTF16(L"Hello World(a)Hello World") },
+
+ // Japanese(English)
+ { WideToUTF16(L"\x6328\x62f6(a)") },
+ // Japanese(English)Japanese
+ { WideToUTF16(L"\x6328\x62f6(a)\x6328\x62f6") },
+ // English(Japanese)English
+ { WideToUTF16(L"Hello World(\x6328\x62f6)Hello World") },
+
+ // Hindi(English)
+ { WideToUTF16(L"\x0915\x093f(a)") },
+ // Hindi(English)Hindi
+ { WideToUTF16(L"\x0915\x093f(a)\x0915\x093f") },
+ // English(Hindi)English
+ { WideToUTF16(L"Hello World(\x0915\x093f)Hello World") },
+
+ // Hebrew(English)
+ { WideToUTF16(L"\x05e0\x05b8(a)") },
+ // Hebrew(English)Hebrew
+ { WideToUTF16(L"\x05e0\x05b8(a)\x05e0\x05b8") },
+ // English(Hebrew)English
+ { WideToUTF16(L"Hello World(\x05e0\x05b8)Hello World") },
+ };
+
+ scoped_ptr<RenderText> render_text(RenderText::CreateInstance());
+ for (size_t i = 0; i < ARRAYSIZE_UNSAFE(cases); ++i) {
+ base::string16 text = cases[i].text;
+ const size_t start_paren_char_index = text.find('(');
+ ASSERT_NE(base::string16::npos, start_paren_char_index);
+ const size_t end_paren_char_index = text.find(')');
+ ASSERT_NE(base::string16::npos, end_paren_char_index);
+
+ for (size_t j = 0; j < ARRAYSIZE_UNSAFE(punctuation_pairs); ++j) {
+ text[start_paren_char_index] = punctuation_pairs[j].left_char;
+ text[end_paren_char_index] = punctuation_pairs[j].right_char;
+ render_text->SetText(text);
+
+ const std::vector<RenderText::FontSpan> spans =
+ render_text->GetFontSpansForTesting();
+
+ int start_paren_span_index = -1;
+ int end_paren_span_index = -1;
+ for (size_t k = 0; k < spans.size(); ++k) {
+ if (IndexInRange(spans[k].second, start_paren_char_index))
+ start_paren_span_index = k;
+ if (IndexInRange(spans[k].second, end_paren_char_index))
+ end_paren_span_index = k;
+ }
+ ASSERT_NE(-1, start_paren_span_index);
+ ASSERT_NE(-1, end_paren_span_index);
+
+ const Font& start_font = spans[start_paren_span_index].first;
+ const Font& end_font = spans[end_paren_span_index].first;
+ EXPECT_EQ(start_font.GetFontName(), end_font.GetFontName());
+ EXPECT_EQ(start_font.GetFontSize(), end_font.GetFontSize());
+ EXPECT_EQ(start_font.GetStyle(), end_font.GetStyle());
+ }
+ }
+}
+
+// Make sure the caret width is always >=1 so that the correct
+// caret is drawn at high DPI. crbug.com/164100.
+TEST_F(RenderTextTest, CaretWidth) {
+ scoped_ptr<RenderText> render_text(RenderText::CreateInstance());
+ render_text->SetText(ASCIIToUTF16("abcdefg"));
+ EXPECT_GE(render_text->GetUpdatedCursorBounds().width(), 1);
+}
+
+TEST_F(RenderTextTest, SelectWord) {
+ scoped_ptr<RenderText> render_text(RenderText::CreateInstance());
+ render_text->SetText(ASCIIToUTF16(" foo a.bc.d bar"));
+
+ struct {
+ size_t cursor;
+ size_t selection_start;
+ size_t selection_end;
+ } cases[] = {
+ { 0, 0, 1 },
+ { 1, 1, 4 },
+ { 2, 1, 4 },
+ { 3, 1, 4 },
+ { 4, 4, 6 },
+ { 5, 4, 6 },
+ { 6, 6, 7 },
+ { 7, 7, 8 },
+ { 8, 8, 10 },
+ { 9, 8, 10 },
+ { 10, 10, 11 },
+ { 11, 11, 12 },
+ { 12, 12, 13 },
+ { 13, 13, 16 },
+ { 14, 13, 16 },
+ { 15, 13, 16 },
+ { 16, 13, 16 },
+ };
+
+ for (size_t i = 0; i < ARRAYSIZE_UNSAFE(cases); ++i) {
+ render_text->SetCursorPosition(cases[i].cursor);
+ render_text->SelectWord();
+ EXPECT_EQ(ui::Range(cases[i].selection_start, cases[i].selection_end),
+ render_text->selection());
+ }
+}
+
+// Make sure the last word is selected when the cursor is at text.length().
+TEST_F(RenderTextTest, LastWordSelected) {
+ const std::string kTestURL1 = "http://www.google.com";
+ const std::string kTestURL2 = "http://www.google.com/something/";
+
+ scoped_ptr<RenderText> render_text(RenderText::CreateInstance());
+
+ render_text->SetText(ASCIIToUTF16(kTestURL1));
+ render_text->SetCursorPosition(kTestURL1.length());
+ render_text->SelectWord();
+ EXPECT_EQ(ASCIIToUTF16("com"), GetSelectedText(render_text.get()));
+ EXPECT_FALSE(render_text->selection().is_reversed());
+
+ render_text->SetText(ASCIIToUTF16(kTestURL2));
+ render_text->SetCursorPosition(kTestURL2.length());
+ render_text->SelectWord();
+ EXPECT_EQ(ASCIIToUTF16("/"), GetSelectedText(render_text.get()));
+ EXPECT_FALSE(render_text->selection().is_reversed());
+}
+
+// When given a non-empty selection, SelectWord should expand the selection to
+// nearest word boundaries.
+TEST_F(RenderTextTest, SelectMultipleWords) {
+ const std::string kTestURL = "http://www.google.com";
+
+ scoped_ptr<RenderText> render_text(RenderText::CreateInstance());
+
+ render_text->SetText(ASCIIToUTF16(kTestURL));
+ render_text->SelectRange(ui::Range(16, 20));
+ render_text->SelectWord();
+ EXPECT_EQ(ASCIIToUTF16("google.com"), GetSelectedText(render_text.get()));
+ EXPECT_FALSE(render_text->selection().is_reversed());
+
+ // SelectWord should preserve the selection direction.
+ render_text->SelectRange(ui::Range(20, 16));
+ render_text->SelectWord();
+ EXPECT_EQ(ASCIIToUTF16("google.com"), GetSelectedText(render_text.get()));
+ EXPECT_TRUE(render_text->selection().is_reversed());
+}
+
+// TODO(asvitkine): Cursor movements tests disabled on Mac because RenderTextMac
+// does not implement this yet. http://crbug.com/131618
+#if !defined(OS_MACOSX)
+TEST_F(RenderTextTest, DisplayRectShowsCursorLTR) {
+ ASSERT_FALSE(base::i18n::IsRTL());
+ ASSERT_FALSE(base::i18n::ICUIsRTL());
+
+ scoped_ptr<RenderText> render_text(RenderText::CreateInstance());
+ render_text->SetText(WideToUTF16(L"abcdefghijklmnopqrstuvwxzyabcdefg"));
+ render_text->MoveCursorTo(SelectionModel(render_text->text().length(),
+ CURSOR_FORWARD));
+ int width = render_text->GetStringSize().width();
+ ASSERT_GT(width, 10);
+
+ // Ensure that the cursor is placed at the width of its preceding text.
+ render_text->SetDisplayRect(Rect(width + 10, 1));
+ EXPECT_EQ(width, render_text->GetUpdatedCursorBounds().x());
+
+ // Ensure that shrinking the display rectangle keeps the cursor in view.
+ render_text->SetDisplayRect(Rect(width - 10, 1));
+ EXPECT_EQ(render_text->display_rect().width(),
+ render_text->GetUpdatedCursorBounds().right());
+
+ // Ensure that the text will pan to fill its expanding display rectangle.
+ render_text->SetDisplayRect(Rect(width - 5, 1));
+ EXPECT_EQ(render_text->display_rect().width(),
+ render_text->GetUpdatedCursorBounds().right());
+
+ // Ensure that a sufficiently large display rectangle shows all the text.
+ render_text->SetDisplayRect(Rect(width + 10, 1));
+ EXPECT_EQ(width, render_text->GetUpdatedCursorBounds().x());
+
+ // Repeat the test with RTL text.
+ render_text->SetText(WideToUTF16(L"\x5d0\x5d1\x5d2\x5d3\x5d4\x5d5\x5d6\x5d7"
+ L"\x5d8\x5d9\x5da\x5db\x5dc\x5dd\x5de\x5df"));
+ render_text->MoveCursorTo(SelectionModel(0, CURSOR_FORWARD));
+ width = render_text->GetStringSize().width();
+ ASSERT_GT(width, 10);
+
+ // Ensure that the cursor is placed at the width of its preceding text.
+ render_text->SetDisplayRect(Rect(width + 10, 1));
+ EXPECT_EQ(width, render_text->GetUpdatedCursorBounds().x());
+
+ // Ensure that shrinking the display rectangle keeps the cursor in view.
+ render_text->SetDisplayRect(Rect(width - 10, 1));
+ EXPECT_EQ(render_text->display_rect().width(),
+ render_text->GetUpdatedCursorBounds().right());
+
+ // Ensure that the text will pan to fill its expanding display rectangle.
+ render_text->SetDisplayRect(Rect(width - 5, 1));
+ EXPECT_EQ(render_text->display_rect().width(),
+ render_text->GetUpdatedCursorBounds().right());
+
+ // Ensure that a sufficiently large display rectangle shows all the text.
+ render_text->SetDisplayRect(Rect(width + 10, 1));
+ EXPECT_EQ(width, render_text->GetUpdatedCursorBounds().x());
+}
+
+TEST_F(RenderTextTest, DisplayRectShowsCursorRTL) {
+ // Set the application default text direction to RTL.
+ const bool was_rtl = base::i18n::IsRTL();
+ SetRTL(true);
+
+ scoped_ptr<RenderText> render_text(RenderText::CreateInstance());
+ render_text->SetText(WideToUTF16(L"abcdefghijklmnopqrstuvwxzyabcdefg"));
+ render_text->MoveCursorTo(SelectionModel(0, CURSOR_FORWARD));
+ int width = render_text->GetStringSize().width();
+ ASSERT_GT(width, 10);
+
+ // Ensure that the cursor is placed at the width of its preceding text.
+ render_text->SetDisplayRect(Rect(width + 10, 1));
+ EXPECT_EQ(render_text->display_rect().width() - width - 1,
+ render_text->GetUpdatedCursorBounds().x());
+
+ // Ensure that shrinking the display rectangle keeps the cursor in view.
+ render_text->SetDisplayRect(Rect(width - 10, 1));
+ EXPECT_EQ(0, render_text->GetUpdatedCursorBounds().x());
+
+ // Ensure that the text will pan to fill its expanding display rectangle.
+ render_text->SetDisplayRect(Rect(width - 5, 1));
+ EXPECT_EQ(0, render_text->GetUpdatedCursorBounds().x());
+
+ // Ensure that a sufficiently large display rectangle shows all the text.
+ render_text->SetDisplayRect(Rect(width + 10, 1));
+ EXPECT_EQ(render_text->display_rect().width() - width - 1,
+ render_text->GetUpdatedCursorBounds().x());
+
+ // Repeat the test with RTL text.
+ render_text->SetText(WideToUTF16(L"\x5d0\x5d1\x5d2\x5d3\x5d4\x5d5\x5d6\x5d7"
+ L"\x5d8\x5d9\x5da\x5db\x5dc\x5dd\x5de\x5df"));
+ render_text->MoveCursorTo(SelectionModel(render_text->text().length(),
+ CURSOR_FORWARD));
+ width = render_text->GetStringSize().width();
+ ASSERT_GT(width, 10);
+
+ // Ensure that the cursor is placed at the width of its preceding text.
+ render_text->SetDisplayRect(Rect(width + 10, 1));
+ EXPECT_EQ(render_text->display_rect().width() - width - 1,
+ render_text->GetUpdatedCursorBounds().x());
+
+ // Ensure that shrinking the display rectangle keeps the cursor in view.
+ render_text->SetDisplayRect(Rect(width - 10, 1));
+ EXPECT_EQ(0, render_text->GetUpdatedCursorBounds().x());
+
+ // Ensure that the text will pan to fill its expanding display rectangle.
+ render_text->SetDisplayRect(Rect(width - 5, 1));
+ EXPECT_EQ(0, render_text->GetUpdatedCursorBounds().x());
+
+ // Ensure that a sufficiently large display rectangle shows all the text.
+ render_text->SetDisplayRect(Rect(width + 10, 1));
+ EXPECT_EQ(render_text->display_rect().width() - width - 1,
+ render_text->GetUpdatedCursorBounds().x());
+
+ // Reset the application default text direction to LTR.
+ SetRTL(was_rtl);
+ EXPECT_EQ(was_rtl, base::i18n::IsRTL());
+}
+#endif // !defined(OS_MACOSX)
+
+// Changing colors between or inside ligated glyphs should not break shaping.
+TEST_F(RenderTextTest, SelectionKeepsLigatures) {
+ const wchar_t* kTestStrings[] = {
+ L"\x644\x623",
+ L"\x633\x627"
+ };
+
+ scoped_ptr<RenderText> render_text(RenderText::CreateInstance());
+ render_text->set_selection_color(SK_ColorRED);
+ Canvas canvas;
+
+ for (size_t i = 0; i < arraysize(kTestStrings); ++i) {
+ render_text->SetText(WideToUTF16(kTestStrings[i]));
+ const int expected_width = render_text->GetStringSize().width();
+ render_text->MoveCursorTo(SelectionModel(ui::Range(0, 1), CURSOR_FORWARD));
+ EXPECT_EQ(expected_width, render_text->GetStringSize().width());
+ // Draw the text. It shouldn't hit any DCHECKs or crash.
+ // See http://crbug.com/214150
+ render_text->Draw(&canvas);
+ render_text->MoveCursorTo(SelectionModel(0, CURSOR_FORWARD));
+ }
+}
+
+} // namespace gfx
diff --git a/chromium/ui/gfx/render_text_win.cc b/chromium/ui/gfx/render_text_win.cc
new file mode 100644
index 00000000000..ac018de3f44
--- /dev/null
+++ b/chromium/ui/gfx/render_text_win.cc
@@ -0,0 +1,906 @@
+// Copyright (c) 2012 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 "ui/gfx/render_text_win.h"
+
+#include <algorithm>
+
+#include "base/i18n/break_iterator.h"
+#include "base/i18n/rtl.h"
+#include "base/logging.h"
+#include "base/strings/string_util.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/win/windows_version.h"
+#include "ui/base/text/utf16_indexing.h"
+#include "ui/gfx/canvas.h"
+#include "ui/gfx/font_fallback_win.h"
+#include "ui/gfx/font_smoothing_win.h"
+#include "ui/gfx/platform_font_win.h"
+
+namespace gfx {
+
+namespace {
+
+// The maximum length of text supported for Uniscribe layout and display.
+// This empirically chosen value should prevent major performance degradations.
+// TODO(msw): Support longer text, partial layout/painting, etc.
+const size_t kMaxUniscribeTextLength = 10000;
+
+// The initial guess and maximum supported number of runs; arbitrary values.
+// TODO(msw): Support more runs, determine a better initial guess, etc.
+const int kGuessRuns = 100;
+const size_t kMaxRuns = 10000;
+
+// The maximum number of glyphs per run; ScriptShape fails on larger values.
+const size_t kMaxGlyphs = 65535;
+
+// Callback to |EnumEnhMetaFile()| to intercept font creation.
+int CALLBACK MetaFileEnumProc(HDC hdc,
+ HANDLETABLE* table,
+ CONST ENHMETARECORD* record,
+ int table_entries,
+ LPARAM log_font) {
+ if (record->iType == EMR_EXTCREATEFONTINDIRECTW) {
+ const EMREXTCREATEFONTINDIRECTW* create_font_record =
+ reinterpret_cast<const EMREXTCREATEFONTINDIRECTW*>(record);
+ *reinterpret_cast<LOGFONT*>(log_font) = create_font_record->elfw.elfLogFont;
+ }
+ return 1;
+}
+
+// Finds a fallback font to use to render the specified |text| with respect to
+// an initial |font|. Returns the resulting font via out param |result|. Returns
+// |true| if a fallback font was found.
+// Adapted from WebKit's |FontCache::GetFontDataForCharacters()|.
+// TODO(asvitkine): This should be moved to font_fallback_win.cc.
+bool ChooseFallbackFont(HDC hdc,
+ const Font& font,
+ const wchar_t* text,
+ int text_length,
+ Font* result) {
+ // Use a meta file to intercept the fallback font chosen by Uniscribe.
+ HDC meta_file_dc = CreateEnhMetaFile(hdc, NULL, NULL, NULL);
+ if (!meta_file_dc)
+ return false;
+
+ SelectObject(meta_file_dc, font.GetNativeFont());
+
+ SCRIPT_STRING_ANALYSIS script_analysis;
+ HRESULT hresult =
+ ScriptStringAnalyse(meta_file_dc, text, text_length, 0, -1,
+ SSA_METAFILE | SSA_FALLBACK | SSA_GLYPHS | SSA_LINK,
+ 0, NULL, NULL, NULL, NULL, NULL, &script_analysis);
+
+ if (SUCCEEDED(hresult)) {
+ hresult = ScriptStringOut(script_analysis, 0, 0, 0, NULL, 0, 0, FALSE);
+ ScriptStringFree(&script_analysis);
+ }
+
+ bool found_fallback = false;
+ HENHMETAFILE meta_file = CloseEnhMetaFile(meta_file_dc);
+ if (SUCCEEDED(hresult)) {
+ LOGFONT log_font;
+ log_font.lfFaceName[0] = 0;
+ EnumEnhMetaFile(0, meta_file, MetaFileEnumProc, &log_font, NULL);
+ if (log_font.lfFaceName[0]) {
+ *result = Font(UTF16ToUTF8(log_font.lfFaceName), font.GetFontSize());
+ found_fallback = true;
+ }
+ }
+ DeleteEnhMetaFile(meta_file);
+
+ return found_fallback;
+}
+
+// Changes |font| to have the specified |font_size| (or |font_height| on Windows
+// XP) and |font_style| if it is not the case already. Only considers bold and
+// italic styles, since the underlined style has no effect on glyph shaping.
+void DeriveFontIfNecessary(int font_size,
+ int font_height,
+ int font_style,
+ Font* font) {
+ const int kStyleMask = (Font::BOLD | Font::ITALIC);
+ const int target_style = (font_style & kStyleMask);
+
+ // On Windows XP, the font must be resized using |font_height| instead of
+ // |font_size| to match GDI behavior.
+ if (base::win::GetVersion() < base::win::VERSION_VISTA) {
+ PlatformFontWin* platform_font =
+ static_cast<PlatformFontWin*>(font->platform_font());
+ *font = platform_font->DeriveFontWithHeight(font_height, target_style);
+ return;
+ }
+
+ const int current_style = (font->GetStyle() & kStyleMask);
+ const int current_size = font->GetFontSize();
+ if (current_style != target_style || current_size != font_size)
+ *font = font->DeriveFont(font_size - current_size, target_style);
+}
+
+// Returns true if |c| is a Unicode BiDi control character.
+bool IsUnicodeBidiControlCharacter(char16 c) {
+ return c == base::i18n::kRightToLeftMark ||
+ c == base::i18n::kLeftToRightMark ||
+ c == base::i18n::kLeftToRightEmbeddingMark ||
+ c == base::i18n::kRightToLeftEmbeddingMark ||
+ c == base::i18n::kPopDirectionalFormatting ||
+ c == base::i18n::kLeftToRightOverride ||
+ c == base::i18n::kRightToLeftOverride;
+}
+
+// Returns the corresponding glyph range of the given character range.
+// |range| is in text-space (0 corresponds to |GetLayoutText()[0]|).
+// Returned value is in run-space (0 corresponds to the first glyph in the run).
+ui::Range CharRangeToGlyphRange(const internal::TextRun& run,
+ const ui::Range& range) {
+ DCHECK(run.range.Contains(range));
+ DCHECK(!range.is_reversed());
+ DCHECK(!range.is_empty());
+ const ui::Range run_range = ui::Range(range.start() - run.range.start(),
+ range.end() - run.range.start());
+ ui::Range result;
+ if (run.script_analysis.fRTL) {
+ result = ui::Range(run.logical_clusters[run_range.end() - 1],
+ run_range.start() > 0 ? run.logical_clusters[run_range.start() - 1]
+ : run.glyph_count);
+ } else {
+ result = ui::Range(run.logical_clusters[run_range.start()],
+ run_range.end() < run.range.length() ?
+ run.logical_clusters[run_range.end()] : run.glyph_count);
+ }
+ DCHECK(!result.is_reversed());
+ DCHECK(ui::Range(0, run.glyph_count).Contains(result));
+ return result;
+}
+
+} // namespace
+
+namespace internal {
+
+TextRun::TextRun()
+ : font_style(0),
+ strike(false),
+ diagonal_strike(false),
+ underline(false),
+ width(0),
+ preceding_run_widths(0),
+ glyph_count(0),
+ script_cache(NULL) {
+ memset(&script_analysis, 0, sizeof(script_analysis));
+ memset(&abc_widths, 0, sizeof(abc_widths));
+}
+
+TextRun::~TextRun() {
+ ScriptFreeCache(&script_cache);
+}
+
+// Returns the X coordinate of the leading or |trailing| edge of the glyph
+// starting at |index|, relative to the left of the text (not the view).
+int GetGlyphXBoundary(const internal::TextRun* run,
+ size_t index,
+ bool trailing) {
+ DCHECK_GE(index, run->range.start());
+ DCHECK_LT(index, run->range.end() + (trailing ? 0 : 1));
+ int x = 0;
+ HRESULT hr = ScriptCPtoX(
+ index - run->range.start(),
+ trailing,
+ run->range.length(),
+ run->glyph_count,
+ run->logical_clusters.get(),
+ run->visible_attributes.get(),
+ run->advance_widths.get(),
+ &run->script_analysis,
+ &x);
+ DCHECK(SUCCEEDED(hr));
+ return run->preceding_run_widths + x;
+}
+
+} // namespace internal
+
+// static
+HDC RenderTextWin::cached_hdc_ = NULL;
+
+// static
+std::map<std::string, Font> RenderTextWin::successful_substitute_fonts_;
+
+RenderTextWin::RenderTextWin()
+ : RenderText(),
+ common_baseline_(0),
+ needs_layout_(false) {
+ set_truncate_length(kMaxUniscribeTextLength);
+
+ memset(&script_control_, 0, sizeof(script_control_));
+ memset(&script_state_, 0, sizeof(script_state_));
+
+ MoveCursorTo(EdgeSelectionModel(CURSOR_LEFT));
+}
+
+RenderTextWin::~RenderTextWin() {
+}
+
+Size RenderTextWin::GetStringSize() {
+ EnsureLayout();
+ return string_size_;
+}
+
+int RenderTextWin::GetBaseline() {
+ EnsureLayout();
+ return common_baseline_;
+}
+
+SelectionModel RenderTextWin::FindCursorPosition(const Point& point) {
+ if (text().empty())
+ return SelectionModel();
+
+ EnsureLayout();
+ // Find the run that contains the point and adjust the argument location.
+ int x = ToTextPoint(point).x();
+ size_t run_index = GetRunContainingXCoord(x);
+ if (run_index >= runs_.size())
+ return EdgeSelectionModel((x < 0) ? CURSOR_LEFT : CURSOR_RIGHT);
+ internal::TextRun* run = runs_[run_index];
+
+ int position = 0, trailing = 0;
+ HRESULT hr = ScriptXtoCP(x - run->preceding_run_widths,
+ run->range.length(),
+ run->glyph_count,
+ run->logical_clusters.get(),
+ run->visible_attributes.get(),
+ run->advance_widths.get(),
+ &(run->script_analysis),
+ &position,
+ &trailing);
+ DCHECK(SUCCEEDED(hr));
+ DCHECK_GE(trailing, 0);
+ position += run->range.start();
+ const size_t cursor = LayoutIndexToTextIndex(position + trailing);
+ DCHECK_LE(cursor, text().length());
+ return SelectionModel(cursor, trailing ? CURSOR_BACKWARD : CURSOR_FORWARD);
+}
+
+std::vector<RenderText::FontSpan> RenderTextWin::GetFontSpansForTesting() {
+ EnsureLayout();
+
+ std::vector<RenderText::FontSpan> spans;
+ for (size_t i = 0; i < runs_.size(); ++i) {
+ spans.push_back(RenderText::FontSpan(runs_[i]->font,
+ ui::Range(LayoutIndexToTextIndex(runs_[i]->range.start()),
+ LayoutIndexToTextIndex(runs_[i]->range.end()))));
+ }
+
+ return spans;
+}
+
+SelectionModel RenderTextWin::AdjacentCharSelectionModel(
+ const SelectionModel& selection,
+ VisualCursorDirection direction) {
+ DCHECK(!needs_layout_);
+ internal::TextRun* run;
+ size_t run_index = GetRunContainingCaret(selection);
+ if (run_index >= runs_.size()) {
+ // The cursor is not in any run: we're at the visual and logical edge.
+ SelectionModel edge = EdgeSelectionModel(direction);
+ if (edge.caret_pos() == selection.caret_pos())
+ return edge;
+ int visual_index = (direction == CURSOR_RIGHT) ? 0 : runs_.size() - 1;
+ run = runs_[visual_to_logical_[visual_index]];
+ } else {
+ // If the cursor is moving within the current run, just move it by one
+ // grapheme in the appropriate direction.
+ run = runs_[run_index];
+ size_t caret = selection.caret_pos();
+ bool forward_motion =
+ run->script_analysis.fRTL == (direction == CURSOR_LEFT);
+ if (forward_motion) {
+ if (caret < LayoutIndexToTextIndex(run->range.end())) {
+ caret = IndexOfAdjacentGrapheme(caret, CURSOR_FORWARD);
+ return SelectionModel(caret, CURSOR_BACKWARD);
+ }
+ } else {
+ if (caret > LayoutIndexToTextIndex(run->range.start())) {
+ caret = IndexOfAdjacentGrapheme(caret, CURSOR_BACKWARD);
+ return SelectionModel(caret, CURSOR_FORWARD);
+ }
+ }
+ // The cursor is at the edge of a run; move to the visually adjacent run.
+ int visual_index = logical_to_visual_[run_index];
+ visual_index += (direction == CURSOR_LEFT) ? -1 : 1;
+ if (visual_index < 0 || visual_index >= static_cast<int>(runs_.size()))
+ return EdgeSelectionModel(direction);
+ run = runs_[visual_to_logical_[visual_index]];
+ }
+ bool forward_motion = run->script_analysis.fRTL == (direction == CURSOR_LEFT);
+ return forward_motion ? FirstSelectionModelInsideRun(run) :
+ LastSelectionModelInsideRun(run);
+}
+
+// TODO(msw): Implement word breaking for Windows.
+SelectionModel RenderTextWin::AdjacentWordSelectionModel(
+ const SelectionModel& selection,
+ VisualCursorDirection direction) {
+ if (obscured())
+ return EdgeSelectionModel(direction);
+
+ base::i18n::BreakIterator iter(text(), base::i18n::BreakIterator::BREAK_WORD);
+ bool success = iter.Init();
+ DCHECK(success);
+ if (!success)
+ return selection;
+
+ size_t pos;
+ if (direction == CURSOR_RIGHT) {
+ pos = std::min(selection.caret_pos() + 1, text().length());
+ while (iter.Advance()) {
+ pos = iter.pos();
+ if (iter.IsWord() && pos > selection.caret_pos())
+ break;
+ }
+ } else { // direction == CURSOR_LEFT
+ // Notes: We always iterate words from the beginning.
+ // This is probably fast enough for our usage, but we may
+ // want to modify WordIterator so that it can start from the
+ // middle of string and advance backwards.
+ pos = std::max<int>(selection.caret_pos() - 1, 0);
+ while (iter.Advance()) {
+ if (iter.IsWord()) {
+ size_t begin = iter.pos() - iter.GetString().length();
+ if (begin == selection.caret_pos()) {
+ // The cursor is at the beginning of a word.
+ // Move to previous word.
+ break;
+ } else if (iter.pos() >= selection.caret_pos()) {
+ // The cursor is in the middle or at the end of a word.
+ // Move to the top of current word.
+ pos = begin;
+ break;
+ } else {
+ pos = iter.pos() - iter.GetString().length();
+ }
+ }
+ }
+ }
+ return SelectionModel(pos, CURSOR_FORWARD);
+}
+
+ui::Range RenderTextWin::GetGlyphBounds(size_t index) {
+ const size_t run_index =
+ GetRunContainingCaret(SelectionModel(index, CURSOR_FORWARD));
+ // Return edge bounds if the index is invalid or beyond the layout text size.
+ if (run_index >= runs_.size())
+ return ui::Range(string_size_.width());
+ internal::TextRun* run = runs_[run_index];
+ const size_t layout_index = TextIndexToLayoutIndex(index);
+ return ui::Range(GetGlyphXBoundary(run, layout_index, false),
+ GetGlyphXBoundary(run, layout_index, true));
+}
+
+std::vector<Rect> RenderTextWin::GetSubstringBounds(const ui::Range& range) {
+ DCHECK(!needs_layout_);
+ DCHECK(ui::Range(0, text().length()).Contains(range));
+ ui::Range layout_range(TextIndexToLayoutIndex(range.start()),
+ TextIndexToLayoutIndex(range.end()));
+ DCHECK(ui::Range(0, GetLayoutText().length()).Contains(layout_range));
+
+ std::vector<Rect> bounds;
+ if (layout_range.is_empty())
+ return bounds;
+
+ // Add a Rect for each run/selection intersection.
+ // TODO(msw): The bounds should probably not always be leading the range ends.
+ for (size_t i = 0; i < runs_.size(); ++i) {
+ const internal::TextRun* run = runs_[visual_to_logical_[i]];
+ ui::Range intersection = run->range.Intersect(layout_range);
+ if (intersection.IsValid()) {
+ DCHECK(!intersection.is_reversed());
+ ui::Range range_x(GetGlyphXBoundary(run, intersection.start(), false),
+ GetGlyphXBoundary(run, intersection.end(), false));
+ Rect rect(range_x.GetMin(), 0, range_x.length(), run->font.GetHeight());
+ rect.set_origin(ToViewPoint(rect.origin()));
+ // Union this with the last rect if they're adjacent.
+ if (!bounds.empty() && rect.SharesEdgeWith(bounds.back())) {
+ rect.Union(bounds.back());
+ bounds.pop_back();
+ }
+ bounds.push_back(rect);
+ }
+ }
+ return bounds;
+}
+
+size_t RenderTextWin::TextIndexToLayoutIndex(size_t index) const {
+ DCHECK_LE(index, text().length());
+ ptrdiff_t i = obscured() ? ui::UTF16IndexToOffset(text(), 0, index) : index;
+ CHECK_GE(i, 0);
+ // Clamp layout indices to the length of the text actually used for layout.
+ return std::min<size_t>(GetLayoutText().length(), i);
+}
+
+size_t RenderTextWin::LayoutIndexToTextIndex(size_t index) const {
+ if (!obscured())
+ return index;
+
+ DCHECK_LE(index, GetLayoutText().length());
+ const size_t text_index = ui::UTF16OffsetToIndex(text(), 0, index);
+ DCHECK_LE(text_index, text().length());
+ return text_index;
+}
+
+bool RenderTextWin::IsCursorablePosition(size_t position) {
+ if (position == 0 || position == text().length())
+ return true;
+ EnsureLayout();
+
+ // Check that the index is at a valid code point (not mid-surrgate-pair),
+ // that it is not truncated from layout text (its glyph is shown on screen),
+ // and that its glyph has distinct bounds (not mid-multi-character-grapheme).
+ // An example of a multi-character-grapheme that is not a surrogate-pair is:
+ // \x0915\x093f - (ki) - one of many Devanagari biconsonantal conjuncts.
+ return ui::IsValidCodePointIndex(text(), position) &&
+ position < LayoutIndexToTextIndex(GetLayoutText().length()) &&
+ GetGlyphBounds(position) != GetGlyphBounds(position - 1);
+}
+
+void RenderTextWin::ResetLayout() {
+ // Layout is performed lazily as needed for drawing/metrics.
+ needs_layout_ = true;
+}
+
+void RenderTextWin::EnsureLayout() {
+ if (!needs_layout_)
+ return;
+ // TODO(msw): Skip complex processing if ScriptIsComplex returns false.
+ ItemizeLogicalText();
+ if (!runs_.empty())
+ LayoutVisualText();
+ needs_layout_ = false;
+}
+
+void RenderTextWin::DrawVisualText(Canvas* canvas) {
+ DCHECK(!needs_layout_);
+
+ // Skia will draw glyphs with respect to the baseline.
+ Vector2d offset(GetTextOffset() + Vector2d(0, common_baseline_));
+
+ SkScalar x = SkIntToScalar(offset.x());
+ SkScalar y = SkIntToScalar(offset.y());
+
+ std::vector<SkPoint> pos;
+
+ internal::SkiaTextRenderer renderer(canvas);
+ ApplyFadeEffects(&renderer);
+ ApplyTextShadows(&renderer);
+
+ bool smoothing_enabled;
+ bool cleartype_enabled;
+ GetCachedFontSmoothingSettings(&smoothing_enabled, &cleartype_enabled);
+ // Note that |cleartype_enabled| corresponds to Skia's |enable_lcd_text|.
+ renderer.SetFontSmoothingSettings(
+ smoothing_enabled, cleartype_enabled && !background_is_transparent());
+
+ ApplyCompositionAndSelectionStyles();
+
+ for (size_t i = 0; i < runs_.size(); ++i) {
+ // Get the run specified by the visual-to-logical map.
+ internal::TextRun* run = runs_[visual_to_logical_[i]];
+
+ // Skip painting empty runs and runs outside the display rect area.
+ if ((run->glyph_count == 0) || (x >= display_rect().right()) ||
+ (x + run->width <= display_rect().x())) {
+ x += run->width;
+ continue;
+ }
+
+ // Based on WebCore::skiaDrawText. |pos| contains the positions of glyphs.
+ // An extra terminal |pos| entry is added to simplify width calculations.
+ pos.resize(run->glyph_count + 1);
+ SkScalar glyph_x = x;
+ for (int glyph = 0; glyph < run->glyph_count; glyph++) {
+ pos[glyph].set(glyph_x + run->offsets[glyph].du,
+ y + run->offsets[glyph].dv);
+ glyph_x += SkIntToScalar(run->advance_widths[glyph]);
+ }
+ pos.back().set(glyph_x, y);
+
+ renderer.SetTextSize(run->font.GetFontSize());
+ renderer.SetFontFamilyWithStyle(run->font.GetFontName(), run->font_style);
+
+ for (BreakList<SkColor>::const_iterator it =
+ colors().GetBreak(run->range.start());
+ it != colors().breaks().end() && it->first < run->range.end();
+ ++it) {
+ const ui::Range glyph_range = CharRangeToGlyphRange(*run,
+ colors().GetRange(it).Intersect(run->range));
+ if (glyph_range.is_empty())
+ continue;
+ renderer.SetForegroundColor(it->second);
+ renderer.DrawPosText(&pos[glyph_range.start()],
+ &run->glyphs[glyph_range.start()],
+ glyph_range.length());
+ const SkScalar width = pos[glyph_range.end()].x() -
+ pos[glyph_range.start()].x();
+ renderer.DrawDecorations(pos[glyph_range.start()].x(), y,
+ SkScalarCeilToInt(width), run->underline,
+ run->strike, run->diagonal_strike);
+ }
+
+ DCHECK_EQ(glyph_x - x, run->width);
+ x = glyph_x;
+ }
+
+ UndoCompositionAndSelectionStyles();
+}
+
+void RenderTextWin::ItemizeLogicalText() {
+ runs_.clear();
+ // Make |string_size_|'s height and |common_baseline_| tall enough to draw
+ // often-used characters which are rendered with fonts in the font list.
+ string_size_ = Size(0, font_list().GetHeight());
+ common_baseline_ = font_list().GetBaseline();
+
+ // Set Uniscribe's base text direction.
+ script_state_.uBidiLevel =
+ (GetTextDirection() == base::i18n::RIGHT_TO_LEFT) ? 1 : 0;
+
+ if (text().empty())
+ return;
+
+ HRESULT hr = E_OUTOFMEMORY;
+ int script_items_count = 0;
+ std::vector<SCRIPT_ITEM> script_items;
+ const size_t layout_text_length = GetLayoutText().length();
+ // Ensure that |kMaxRuns| is attempted and the loop terminates afterward.
+ for (size_t runs = kGuessRuns; hr == E_OUTOFMEMORY && runs <= kMaxRuns;
+ runs = std::max(runs + 1, std::min(runs * 2, kMaxRuns))) {
+ // Derive the array of Uniscribe script items from the logical text.
+ // ScriptItemize always adds a terminal array item so that the length of
+ // the last item can be derived from the terminal SCRIPT_ITEM::iCharPos.
+ script_items.resize(runs);
+ hr = ScriptItemize(GetLayoutText().c_str(), layout_text_length,
+ runs - 1, &script_control_, &script_state_,
+ &script_items[0], &script_items_count);
+ }
+ DCHECK(SUCCEEDED(hr));
+ if (!SUCCEEDED(hr) || script_items_count <= 0)
+ return;
+
+ // Temporarily apply composition underlines and selection colors.
+ ApplyCompositionAndSelectionStyles();
+
+ // Build the list of runs from the script items and ranged styles. Use an
+ // empty color BreakList to avoid breaking runs at color boundaries.
+ BreakList<SkColor> empty_colors;
+ empty_colors.SetMax(text().length());
+ internal::StyleIterator style(empty_colors, styles());
+ SCRIPT_ITEM* script_item = &script_items[0];
+ const size_t max_run_length = kMaxGlyphs / 2;
+ for (size_t run_break = 0; run_break < layout_text_length;) {
+ internal::TextRun* run = new internal::TextRun();
+ run->range.set_start(run_break);
+ run->font = GetPrimaryFont();
+ run->font_style = (style.style(BOLD) ? Font::BOLD : 0) |
+ (style.style(ITALIC) ? Font::ITALIC : 0);
+ DeriveFontIfNecessary(run->font.GetFontSize(), run->font.GetHeight(),
+ run->font_style, &run->font);
+ run->strike = style.style(STRIKE);
+ run->diagonal_strike = style.style(DIAGONAL_STRIKE);
+ run->underline = style.style(UNDERLINE);
+ run->script_analysis = script_item->a;
+
+ // Find the next break and advance the iterators as needed.
+ const size_t script_item_break = (script_item + 1)->iCharPos;
+ run_break = std::min(script_item_break,
+ TextIndexToLayoutIndex(style.GetRange().end()));
+ // Clamp run lengths to avoid exceeding the maximum supported glyph count.
+ if ((run_break - run->range.start()) > max_run_length)
+ run_break = run->range.start() + max_run_length;
+ style.UpdatePosition(LayoutIndexToTextIndex(run_break));
+ if (script_item_break == run_break)
+ script_item++;
+ run->range.set_end(run_break);
+ runs_.push_back(run);
+ }
+
+ // Undo the temporarily applied composition underlines and selection colors.
+ UndoCompositionAndSelectionStyles();
+}
+
+void RenderTextWin::LayoutVisualText() {
+ DCHECK(!runs_.empty());
+
+ if (!cached_hdc_)
+ cached_hdc_ = CreateCompatibleDC(NULL);
+
+ HRESULT hr = E_FAIL;
+ // Ensure ascent and descent are not smaller than ones of the font list.
+ // Keep them tall enough to draw often-used characters.
+ // For example, if a text field contains a Japanese character, which is
+ // smaller than Latin ones, and then later a Latin one is inserted, this
+ // ensures that the text baseline does not shift.
+ int ascent = font_list().GetBaseline();
+ int descent = font_list().GetHeight() - font_list().GetBaseline();
+ for (size_t i = 0; i < runs_.size(); ++i) {
+ internal::TextRun* run = runs_[i];
+ LayoutTextRun(run);
+
+ ascent = std::max(ascent, run->font.GetBaseline());
+ descent = std::max(descent,
+ run->font.GetHeight() - run->font.GetBaseline());
+
+ if (run->glyph_count > 0) {
+ run->advance_widths.reset(new int[run->glyph_count]);
+ run->offsets.reset(new GOFFSET[run->glyph_count]);
+ hr = ScriptPlace(cached_hdc_,
+ &run->script_cache,
+ run->glyphs.get(),
+ run->glyph_count,
+ run->visible_attributes.get(),
+ &(run->script_analysis),
+ run->advance_widths.get(),
+ run->offsets.get(),
+ &(run->abc_widths));
+ DCHECK(SUCCEEDED(hr));
+ }
+ }
+ string_size_.set_height(ascent + descent);
+ common_baseline_ = ascent;
+
+ // Build the array of bidirectional embedding levels.
+ scoped_ptr<BYTE[]> levels(new BYTE[runs_.size()]);
+ for (size_t i = 0; i < runs_.size(); ++i)
+ levels[i] = runs_[i]->script_analysis.s.uBidiLevel;
+
+ // Get the maps between visual and logical run indices.
+ visual_to_logical_.reset(new int[runs_.size()]);
+ logical_to_visual_.reset(new int[runs_.size()]);
+ hr = ScriptLayout(runs_.size(),
+ levels.get(),
+ visual_to_logical_.get(),
+ logical_to_visual_.get());
+ DCHECK(SUCCEEDED(hr));
+
+ // Precalculate run width information.
+ size_t preceding_run_widths = 0;
+ for (size_t i = 0; i < runs_.size(); ++i) {
+ internal::TextRun* run = runs_[visual_to_logical_[i]];
+ run->preceding_run_widths = preceding_run_widths;
+ const ABC& abc = run->abc_widths;
+ run->width = abc.abcA + abc.abcB + abc.abcC;
+ preceding_run_widths += run->width;
+ }
+ string_size_.set_width(preceding_run_widths);
+}
+
+void RenderTextWin::LayoutTextRun(internal::TextRun* run) {
+ const size_t run_length = run->range.length();
+ const wchar_t* run_text = &(GetLayoutText()[run->range.start()]);
+ Font original_font = run->font;
+ LinkedFontsIterator fonts(original_font);
+ bool tried_cached_font = false;
+ bool tried_fallback = false;
+ // Keep track of the font that is able to display the greatest number of
+ // characters for which ScriptShape() returned S_OK. This font will be used
+ // in the case where no font is able to display the entire run.
+ int best_partial_font_missing_char_count = INT_MAX;
+ Font best_partial_font = original_font;
+ bool using_best_partial_font = false;
+ Font current_font;
+
+ run->logical_clusters.reset(new WORD[run_length]);
+ while (fonts.NextFont(&current_font)) {
+ HRESULT hr = ShapeTextRunWithFont(run, current_font);
+
+ bool glyphs_missing = false;
+ if (hr == USP_E_SCRIPT_NOT_IN_FONT) {
+ glyphs_missing = true;
+ } else if (hr == S_OK) {
+ // If |hr| is S_OK, there could still be missing glyphs in the output.
+ // http://msdn.microsoft.com/en-us/library/windows/desktop/dd368564.aspx
+ const int missing_count = CountCharsWithMissingGlyphs(run);
+ // Track the font that produced the least missing glyphs.
+ if (missing_count < best_partial_font_missing_char_count) {
+ best_partial_font_missing_char_count = missing_count;
+ best_partial_font = run->font;
+ }
+ glyphs_missing = (missing_count != 0);
+ } else {
+ NOTREACHED() << hr;
+ }
+
+ // Use the font if it had glyphs for all characters.
+ if (!glyphs_missing) {
+ // Save the successful fallback font that was chosen.
+ if (tried_fallback)
+ successful_substitute_fonts_[original_font.GetFontName()] = run->font;
+ return;
+ }
+
+ // First, try the cached font from previous runs, if any.
+ if (!tried_cached_font) {
+ tried_cached_font = true;
+
+ std::map<std::string, Font>::const_iterator it =
+ successful_substitute_fonts_.find(original_font.GetFontName());
+ if (it != successful_substitute_fonts_.end()) {
+ fonts.SetNextFont(it->second);
+ continue;
+ }
+ }
+
+ // If there are missing glyphs, first try finding a fallback font using a
+ // meta file, if it hasn't yet been attempted for this run.
+ // TODO(msw|asvitkine): Support RenderText's font_list()?
+ if (!tried_fallback) {
+ tried_fallback = true;
+
+ Font fallback_font;
+ if (ChooseFallbackFont(cached_hdc_, run->font, run_text, run_length,
+ &fallback_font)) {
+ fonts.SetNextFont(fallback_font);
+ continue;
+ }
+ }
+ }
+
+ // If a font was able to partially display the run, use that now.
+ if (best_partial_font_missing_char_count < static_cast<int>(run_length)) {
+ // Re-shape the run only if |best_partial_font| differs from the last font.
+ if (best_partial_font.GetNativeFont() != run->font.GetNativeFont())
+ ShapeTextRunWithFont(run, best_partial_font);
+ return;
+ }
+
+ // If no font was able to partially display the run, replace all glyphs
+ // with |wgDefault| from the original font to ensure to they don't hold
+ // garbage values.
+ // First, clear the cache and select the original font on the HDC.
+ ScriptFreeCache(&run->script_cache);
+ run->font = original_font;
+ SelectObject(cached_hdc_, run->font.GetNativeFont());
+
+ // Now, get the font's properties.
+ SCRIPT_FONTPROPERTIES properties;
+ memset(&properties, 0, sizeof(properties));
+ properties.cBytes = sizeof(properties);
+ HRESULT hr = ScriptGetFontProperties(cached_hdc_, &run->script_cache,
+ &properties);
+
+ // The initial values for the "missing" glyph and the space glyph are taken
+ // from the recommendations section of the OpenType spec:
+ // https://www.microsoft.com/typography/otspec/recom.htm
+ WORD missing_glyph = 0;
+ WORD space_glyph = 3;
+ if (hr == S_OK) {
+ missing_glyph = properties.wgDefault;
+ space_glyph = properties.wgBlank;
+ }
+
+ // Finally, initialize |glyph_count|, |glyphs|, |visible_attributes| and
+ // |logical_clusters| on the run (since they may not have been set yet).
+ run->glyph_count = run_length;
+ memset(run->visible_attributes.get(), 0,
+ run->glyph_count * sizeof(SCRIPT_VISATTR));
+ for (int i = 0; i < run->glyph_count; ++i)
+ run->glyphs[i] = IsWhitespace(run_text[i]) ? space_glyph : missing_glyph;
+ for (size_t i = 0; i < run_length; ++i) {
+ run->logical_clusters[i] = run->script_analysis.fRTL ?
+ run_length - 1 - i : i;
+ }
+
+ // TODO(msw): Don't use SCRIPT_UNDEFINED. Apparently Uniscribe can
+ // crash on certain surrogate pairs with SCRIPT_UNDEFINED.
+ // See https://bugzilla.mozilla.org/show_bug.cgi?id=341500
+ // And http://maxradi.us/documents/uniscribe/
+ run->script_analysis.eScript = SCRIPT_UNDEFINED;
+}
+
+HRESULT RenderTextWin::ShapeTextRunWithFont(internal::TextRun* run,
+ const Font& font) {
+ // Update the run's font only if necessary. If the two fonts wrap the same
+ // PlatformFontWin object, their native fonts will have the same value.
+ if (run->font.GetNativeFont() != font.GetNativeFont()) {
+ const int font_size = run->font.GetFontSize();
+ const int font_height = run->font.GetHeight();
+ run->font = font;
+ DeriveFontIfNecessary(font_size, font_height, run->font_style, &run->font);
+ ScriptFreeCache(&run->script_cache);
+ }
+
+ // Select the font desired for glyph generation.
+ SelectObject(cached_hdc_, run->font.GetNativeFont());
+
+ HRESULT hr = E_OUTOFMEMORY;
+ const size_t run_length = run->range.length();
+ const wchar_t* run_text = &(GetLayoutText()[run->range.start()]);
+ // Guess the expected number of glyphs from the length of the run.
+ // MSDN suggests this at http://msdn.microsoft.com/en-us/library/dd368564.aspx
+ size_t max_glyphs = static_cast<size_t>(1.5 * run_length + 16);
+ while (hr == E_OUTOFMEMORY && max_glyphs <= kMaxGlyphs) {
+ run->glyph_count = 0;
+ run->glyphs.reset(new WORD[max_glyphs]);
+ run->visible_attributes.reset(new SCRIPT_VISATTR[max_glyphs]);
+ hr = ScriptShape(cached_hdc_, &run->script_cache, run_text, run_length,
+ max_glyphs, &run->script_analysis, run->glyphs.get(),
+ run->logical_clusters.get(), run->visible_attributes.get(),
+ &run->glyph_count);
+ // Ensure that |kMaxGlyphs| is attempted and the loop terminates afterward.
+ max_glyphs = std::max(max_glyphs + 1, std::min(max_glyphs * 2, kMaxGlyphs));
+ }
+ return hr;
+}
+
+int RenderTextWin::CountCharsWithMissingGlyphs(internal::TextRun* run) const {
+ int chars_not_missing_glyphs = 0;
+ SCRIPT_FONTPROPERTIES properties;
+ memset(&properties, 0, sizeof(properties));
+ properties.cBytes = sizeof(properties);
+ ScriptGetFontProperties(cached_hdc_, &run->script_cache, &properties);
+
+ const wchar_t* run_text = &(GetLayoutText()[run->range.start()]);
+ for (size_t char_index = 0; char_index < run->range.length(); ++char_index) {
+ const int glyph_index = run->logical_clusters[char_index];
+ DCHECK_GE(glyph_index, 0);
+ DCHECK_LT(glyph_index, run->glyph_count);
+
+ if (run->glyphs[glyph_index] == properties.wgDefault)
+ continue;
+
+ // Windows Vista sometimes returns glyphs equal to wgBlank (instead of
+ // wgDefault), with fZeroWidth set. Treat such cases as having missing
+ // glyphs if the corresponding character is not whitespace.
+ // See: http://crbug.com/125629
+ if (run->glyphs[glyph_index] == properties.wgBlank &&
+ run->visible_attributes[glyph_index].fZeroWidth &&
+ !IsWhitespace(run_text[char_index]) &&
+ !IsUnicodeBidiControlCharacter(run_text[char_index])) {
+ continue;
+ }
+
+ ++chars_not_missing_glyphs;
+ }
+
+ DCHECK_LE(chars_not_missing_glyphs, static_cast<int>(run->range.length()));
+ return run->range.length() - chars_not_missing_glyphs;
+}
+
+size_t RenderTextWin::GetRunContainingCaret(const SelectionModel& caret) const {
+ DCHECK(!needs_layout_);
+ size_t layout_position = TextIndexToLayoutIndex(caret.caret_pos());
+ LogicalCursorDirection affinity = caret.caret_affinity();
+ for (size_t run = 0; run < runs_.size(); ++run)
+ if (RangeContainsCaret(runs_[run]->range, layout_position, affinity))
+ return run;
+ return runs_.size();
+}
+
+size_t RenderTextWin::GetRunContainingXCoord(int x) const {
+ DCHECK(!needs_layout_);
+ // Find the text run containing the argument point (assumed already offset).
+ for (size_t run = 0; run < runs_.size(); ++run) {
+ if ((runs_[run]->preceding_run_widths <= x) &&
+ ((runs_[run]->preceding_run_widths + runs_[run]->width) > x))
+ return run;
+ }
+ return runs_.size();
+}
+
+SelectionModel RenderTextWin::FirstSelectionModelInsideRun(
+ const internal::TextRun* run) {
+ size_t position = LayoutIndexToTextIndex(run->range.start());
+ position = IndexOfAdjacentGrapheme(position, CURSOR_FORWARD);
+ return SelectionModel(position, CURSOR_BACKWARD);
+}
+
+SelectionModel RenderTextWin::LastSelectionModelInsideRun(
+ const internal::TextRun* run) {
+ size_t position = LayoutIndexToTextIndex(run->range.end());
+ position = IndexOfAdjacentGrapheme(position, CURSOR_BACKWARD);
+ return SelectionModel(position, CURSOR_FORWARD);
+}
+
+RenderText* RenderText::CreateInstance() {
+ return new RenderTextWin;
+}
+
+} // namespace gfx
diff --git a/chromium/ui/gfx/render_text_win.h b/chromium/ui/gfx/render_text_win.h
new file mode 100644
index 00000000000..620ef092dff
--- /dev/null
+++ b/chromium/ui/gfx/render_text_win.h
@@ -0,0 +1,140 @@
+// Copyright (c) 2012 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_RENDER_TEXT_WIN_H_
+#define UI_GFX_RENDER_TEXT_WIN_H_
+
+#include <usp10.h>
+
+#include <map>
+#include <string>
+#include <vector>
+
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/scoped_vector.h"
+#include "ui/gfx/render_text.h"
+
+namespace gfx {
+
+namespace internal {
+
+struct TextRun {
+ TextRun();
+ ~TextRun();
+
+ ui::Range range;
+ Font font;
+ // A gfx::Font::FontStyle flag to specify bold and italic styles.
+ // Supersedes |font.GetFontStyle()|. Stored separately to avoid calling
+ // |font.DeriveFont()|, which is expensive on Windows.
+ int font_style;
+
+ bool strike;
+ bool diagonal_strike;
+ bool underline;
+
+ int width;
+ // The cumulative widths of preceding runs.
+ int preceding_run_widths;
+
+ SCRIPT_ANALYSIS script_analysis;
+
+ scoped_ptr<WORD[]> glyphs;
+ scoped_ptr<WORD[]> logical_clusters;
+ scoped_ptr<SCRIPT_VISATTR[]> visible_attributes;
+ int glyph_count;
+
+ scoped_ptr<int[]> advance_widths;
+ scoped_ptr<GOFFSET[]> offsets;
+ ABC abc_widths;
+ SCRIPT_CACHE script_cache;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(TextRun);
+};
+
+} // namespace internal
+
+// RenderTextWin is the Windows implementation of RenderText using Uniscribe.
+class RenderTextWin : public RenderText {
+ public:
+ RenderTextWin();
+ virtual ~RenderTextWin();
+
+ // Overridden from RenderText:
+ virtual Size GetStringSize() OVERRIDE;
+ virtual int GetBaseline() OVERRIDE;
+ virtual SelectionModel FindCursorPosition(const Point& point) OVERRIDE;
+ virtual std::vector<FontSpan> GetFontSpansForTesting() OVERRIDE;
+
+ protected:
+ // Overridden from RenderText:
+ virtual SelectionModel AdjacentCharSelectionModel(
+ const SelectionModel& selection,
+ VisualCursorDirection direction) OVERRIDE;
+ virtual SelectionModel AdjacentWordSelectionModel(
+ const SelectionModel& selection,
+ VisualCursorDirection direction) OVERRIDE;
+ virtual ui::Range GetGlyphBounds(size_t index) OVERRIDE;
+ virtual std::vector<Rect> GetSubstringBounds(const ui::Range& range) OVERRIDE;
+ virtual size_t TextIndexToLayoutIndex(size_t index) const OVERRIDE;
+ virtual size_t LayoutIndexToTextIndex(size_t index) const OVERRIDE;
+ virtual bool IsCursorablePosition(size_t position) OVERRIDE;
+ virtual void ResetLayout() OVERRIDE;
+ virtual void EnsureLayout() OVERRIDE;
+ virtual void DrawVisualText(Canvas* canvas) OVERRIDE;
+
+ private:
+ FRIEND_TEST_ALL_PREFIXES(RenderTextTest, Win_LogicalClusters);
+
+ void ItemizeLogicalText();
+ void LayoutVisualText();
+ void LayoutTextRun(internal::TextRun* run);
+
+ // Helper function that calls |ScriptShape()| on the run, which has logic to
+ // handle E_OUTOFMEMORY return codes.
+ HRESULT ShapeTextRunWithFont(internal::TextRun* run, const Font& font);
+
+ // Returns the number of characters in |run| that have missing glyphs.
+ int CountCharsWithMissingGlyphs(internal::TextRun* run) const;
+
+ // Return the run index that contains the argument; or the length of the
+ // |runs_| vector if argument exceeds the text length or width.
+ size_t GetRunContainingCaret(const SelectionModel& caret) const;
+ size_t GetRunContainingXCoord(int x) const;
+
+ // Given a |run|, returns the SelectionModel that contains the logical first
+ // or last caret position inside (not at a boundary of) the run.
+ // The returned value represents a cursor/caret position without a selection.
+ SelectionModel FirstSelectionModelInsideRun(const internal::TextRun* run);
+ SelectionModel LastSelectionModelInsideRun(const internal::TextRun* run);
+
+ // Cached HDC for performing Uniscribe API calls.
+ static HDC cached_hdc_;
+
+ // Cached map from font name to the last successful substitute font used.
+ // TODO(asvitkine): Move the caching logic to font_fallback_win.cc.
+ static std::map<std::string, Font> successful_substitute_fonts_;
+
+ SCRIPT_CONTROL script_control_;
+ SCRIPT_STATE script_state_;
+
+ ScopedVector<internal::TextRun> runs_;
+ Size string_size_;
+
+ // A common vertical baseline for all the text runs. This is computed as the
+ // largest baseline over all the runs' fonts.
+ int common_baseline_;
+
+ scoped_ptr<int[]> visual_to_logical_;
+ scoped_ptr<int[]> logical_to_visual_;
+
+ bool needs_layout_;
+
+ DISALLOW_COPY_AND_ASSIGN(RenderTextWin);
+};
+
+} // namespace gfx
+
+#endif // UI_GFX_RENDER_TEXT_WIN_H_
diff --git a/chromium/ui/gfx/safe_integer_conversions.h b/chromium/ui/gfx/safe_integer_conversions.h
new file mode 100644
index 00000000000..edd8a4cafee
--- /dev/null
+++ b/chromium/ui/gfx/safe_integer_conversions.h
@@ -0,0 +1,54 @@
+// Copyright (c) 2012 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_SAFE_INTEGER_CONVERSIONS_H_
+#define UI_GFX_SAFE_INTEGER_CONVERSIONS_H_
+
+#include <cmath>
+#include <limits>
+
+#include "ui/base/ui_export.h"
+
+namespace gfx {
+
+inline int ClampToInt(float value) {
+ if (value != value)
+ return 0; // no int NaN.
+ if (value >= std::numeric_limits<int>::max())
+ return std::numeric_limits<int>::max();
+ if (value <= std::numeric_limits<int>::min())
+ return std::numeric_limits<int>::min();
+ return static_cast<int>(value);
+}
+
+inline int ToFlooredInt(float value) {
+ return ClampToInt(std::floor(value));
+}
+
+inline int ToCeiledInt(float value) {
+ return ClampToInt(std::ceil(value));
+}
+
+inline int ToRoundedInt(float value) {
+ float rounded;
+ if (value >= 0.0f)
+ rounded = std::floor(value + 0.5f);
+ else
+ rounded = std::ceil(value - 0.5f);
+ return ClampToInt(rounded);
+}
+
+inline bool IsExpressibleAsInt(float value) {
+ if (value != value)
+ return false; // no int NaN.
+ if (value > std::numeric_limits<int>::max())
+ return false;
+ if (value < std::numeric_limits<int>::min())
+ return false;
+ return true;
+}
+
+} // namespace gfx
+
+#endif // UI_GFX_SAFE_INTEGER_CONVERSIONS_H_
diff --git a/chromium/ui/gfx/safe_integer_conversions_unittest.cc b/chromium/ui/gfx/safe_integer_conversions_unittest.cc
new file mode 100644
index 00000000000..1268f8bbf32
--- /dev/null
+++ b/chromium/ui/gfx/safe_integer_conversions_unittest.cc
@@ -0,0 +1,109 @@
+// Copyright (c) 2012 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 "ui/gfx/safe_integer_conversions.h"
+
+#include <limits>
+
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace gfx {
+
+TEST(SafeIntegerConversions, ClampToInt) {
+ EXPECT_EQ(0, ClampToInt(std::numeric_limits<float>::quiet_NaN()));
+
+ float max = std::numeric_limits<int>::max();
+ float min = std::numeric_limits<int>::min();
+ float infinity = std::numeric_limits<float>::infinity();
+
+ int int_max = std::numeric_limits<int>::max();
+ int int_min = std::numeric_limits<int>::min();
+
+ EXPECT_EQ(int_max, ClampToInt(infinity));
+ EXPECT_EQ(int_max, ClampToInt(max));
+ EXPECT_EQ(int_max, ClampToInt(max + 100));
+
+ EXPECT_EQ(-100, ClampToInt(-100.5f));
+ EXPECT_EQ(0, ClampToInt(0));
+ EXPECT_EQ(100, ClampToInt(100.5f));
+
+ EXPECT_EQ(int_min, ClampToInt(-infinity));
+ EXPECT_EQ(int_min, ClampToInt(min));
+ EXPECT_EQ(int_min, ClampToInt(min - 100));
+}
+
+TEST(SafeIntegerConversions, ToFlooredInt) {
+ EXPECT_EQ(0, ToFlooredInt(std::numeric_limits<float>::quiet_NaN()));
+
+ float max = std::numeric_limits<int>::max();
+ float min = std::numeric_limits<int>::min();
+ float infinity = std::numeric_limits<float>::infinity();
+
+ int int_max = std::numeric_limits<int>::max();
+ int int_min = std::numeric_limits<int>::min();
+
+ EXPECT_EQ(int_max, ToFlooredInt(infinity));
+ EXPECT_EQ(int_max, ToFlooredInt(max));
+ EXPECT_EQ(int_max, ToFlooredInt(max + 100));
+
+ EXPECT_EQ(-101, ToFlooredInt(-100.5f));
+ EXPECT_EQ(0, ToFlooredInt(0));
+ EXPECT_EQ(100, ToFlooredInt(100.5f));
+
+ EXPECT_EQ(int_min, ToFlooredInt(-infinity));
+ EXPECT_EQ(int_min, ToFlooredInt(min));
+ EXPECT_EQ(int_min, ToFlooredInt(min - 100));
+}
+
+TEST(SafeIntegerConversions, ToCeiledInt) {
+ EXPECT_EQ(0, ToCeiledInt(std::numeric_limits<float>::quiet_NaN()));
+
+ float max = std::numeric_limits<int>::max();
+ float min = std::numeric_limits<int>::min();
+ float infinity = std::numeric_limits<float>::infinity();
+
+ int int_max = std::numeric_limits<int>::max();
+ int int_min = std::numeric_limits<int>::min();
+
+ EXPECT_EQ(int_max, ToCeiledInt(infinity));
+ EXPECT_EQ(int_max, ToCeiledInt(max));
+ EXPECT_EQ(int_max, ToCeiledInt(max + 100));
+
+ EXPECT_EQ(-100, ToCeiledInt(-100.5f));
+ EXPECT_EQ(0, ToCeiledInt(0));
+ EXPECT_EQ(101, ToCeiledInt(100.5f));
+
+ EXPECT_EQ(int_min, ToCeiledInt(-infinity));
+ EXPECT_EQ(int_min, ToCeiledInt(min));
+ EXPECT_EQ(int_min, ToCeiledInt(min - 100));
+}
+
+TEST(SafeIntegerConversions, ToRoundedInt) {
+ EXPECT_EQ(0, ToRoundedInt(std::numeric_limits<float>::quiet_NaN()));
+
+ float max = std::numeric_limits<int>::max();
+ float min = std::numeric_limits<int>::min();
+ float infinity = std::numeric_limits<float>::infinity();
+
+ int int_max = std::numeric_limits<int>::max();
+ int int_min = std::numeric_limits<int>::min();
+
+ EXPECT_EQ(int_max, ToRoundedInt(infinity));
+ EXPECT_EQ(int_max, ToRoundedInt(max));
+ EXPECT_EQ(int_max, ToRoundedInt(max + 100));
+
+ EXPECT_EQ(-100, ToRoundedInt(-100.1f));
+ EXPECT_EQ(-101, ToRoundedInt(-100.5f));
+ EXPECT_EQ(-101, ToRoundedInt(-100.9f));
+ EXPECT_EQ(0, ToRoundedInt(0));
+ EXPECT_EQ(100, ToRoundedInt(100.1f));
+ EXPECT_EQ(101, ToRoundedInt(100.5f));
+ EXPECT_EQ(101, ToRoundedInt(100.9f));
+
+ EXPECT_EQ(int_min, ToRoundedInt(-infinity));
+ EXPECT_EQ(int_min, ToRoundedInt(min));
+ EXPECT_EQ(int_min, ToRoundedInt(min - 100));
+}
+
+} // namespace gfx
diff --git a/chromium/ui/gfx/scoped_cg_context_save_gstate_mac.h b/chromium/ui/gfx/scoped_cg_context_save_gstate_mac.h
new file mode 100644
index 00000000000..b22782d680d
--- /dev/null
+++ b/chromium/ui/gfx/scoped_cg_context_save_gstate_mac.h
@@ -0,0 +1,30 @@
+// 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_SCOPED_CG_CONTEXT_SAVE_GSTATE_MAC_H_
+#define UI_GFX_SCOPED_CG_CONTEXT_SAVE_GSTATE_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_SAVE_GSTATE_MAC_H_
diff --git a/chromium/ui/gfx/scoped_ns_graphics_context_save_gstate_mac.h b/chromium/ui/gfx/scoped_ns_graphics_context_save_gstate_mac.h
new file mode 100644
index 00000000000..f4b11ad228e
--- /dev/null
+++ b/chromium/ui/gfx/scoped_ns_graphics_context_save_gstate_mac.h
@@ -0,0 +1,33 @@
+// 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_SCOPED_NS_GRAPHICS_CONTEXT_SAVE_GSTATE_MAC_H_
+#define UI_GFX_SCOPED_NS_GRAPHICS_CONTEXT_SAVE_GSTATE_MAC_H_
+
+#include "base/basictypes.h"
+#include "ui/base/ui_export.h"
+
+#if defined(__OBJC__)
+@class NSGraphicsContext;
+#else
+class NSGraphicsContext;
+#endif
+
+namespace gfx {
+
+// A class to save/restore the state of the current context.
+class UI_EXPORT ScopedNSGraphicsContextSaveGState {
+ public:
+ ScopedNSGraphicsContextSaveGState();
+ ~ScopedNSGraphicsContextSaveGState();
+
+ private:
+ NSGraphicsContext* context_; // weak
+
+ DISALLOW_COPY_AND_ASSIGN(ScopedNSGraphicsContextSaveGState);
+};
+
+} // namespace gfx
+
+#endif // UI_GFX_SCOPED_NS_GRAPHICS_CONTEXT_SAVE_GSTATE_MAC_H_
diff --git a/chromium/ui/gfx/scoped_ns_graphics_context_save_gstate_mac.mm b/chromium/ui/gfx/scoped_ns_graphics_context_save_gstate_mac.mm
new file mode 100644
index 00000000000..6b19d8cf85e
--- /dev/null
+++ b/chromium/ui/gfx/scoped_ns_graphics_context_save_gstate_mac.mm
@@ -0,0 +1,23 @@
+// 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 "ui/gfx/scoped_ns_graphics_context_save_gstate_mac.h"
+
+#import <AppKit/AppKit.h>
+
+#include "base/logging.h"
+
+namespace gfx {
+
+ScopedNSGraphicsContextSaveGState::ScopedNSGraphicsContextSaveGState()
+ : context_([NSGraphicsContext currentContext]) {
+ [NSGraphicsContext saveGraphicsState];
+}
+
+ScopedNSGraphicsContextSaveGState::~ScopedNSGraphicsContextSaveGState() {
+ [NSGraphicsContext restoreGraphicsState];
+ DCHECK_EQ(context_, [NSGraphicsContext currentContext]);
+}
+
+} // namespace gfx
diff --git a/chromium/ui/gfx/scoped_sk_region.h b/chromium/ui/gfx/scoped_sk_region.h
new file mode 100644
index 00000000000..077b7492295
--- /dev/null
+++ b/chromium/ui/gfx/scoped_sk_region.h
@@ -0,0 +1,45 @@
+// Copyright (c) 2012 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_SK_REGION_H_
+#define UI_GFX_SCOPED_SK_REGION_H_
+
+#include "third_party/skia/include/core/SkRegion.h"
+
+namespace gfx {
+
+// Wraps an SkRegion.
+class ScopedSkRegion {
+ public:
+ ScopedSkRegion() : region_(NULL) {}
+ explicit ScopedSkRegion(SkRegion* region) : region_(region) {}
+
+ ~ScopedSkRegion() {
+ delete region_;
+ }
+
+ void Set(SkRegion* region) {
+ delete region_;
+ region_ = region;
+ }
+
+ SkRegion* Get() {
+ return region_;
+ }
+
+ SkRegion* release() {
+ SkRegion* region = region_;
+ region_ = NULL;
+ return region;
+ }
+
+ private:
+ SkRegion* region_;
+
+ DISALLOW_COPY_AND_ASSIGN(ScopedSkRegion);
+};
+
+} // namespace gfx
+
+#endif // UI_GFX_SCOPED_SK_REGION_H_
diff --git a/chromium/ui/gfx/scoped_ui_graphics_push_context_ios.h b/chromium/ui/gfx/scoped_ui_graphics_push_context_ios.h
new file mode 100644
index 00000000000..0e5455db04e
--- /dev/null
+++ b/chromium/ui/gfx/scoped_ui_graphics_push_context_ios.h
@@ -0,0 +1,27 @@
+// Copyright (c) 2012 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_UI_GRAPHICS_PUSH_CONTEXT_IOS_H_
+#define UI_GFX_SCOPED_UI_GRAPHICS_PUSH_CONTEXT_IOS_H_
+
+#import <QuartzCore/QuartzCore.h>
+
+#include "base/basictypes.h"
+
+namespace gfx {
+
+class ScopedUIGraphicsPushContext {
+ public:
+ explicit ScopedUIGraphicsPushContext(CGContextRef context);
+ ~ScopedUIGraphicsPushContext();
+
+ private:
+ CGContextRef context_;
+
+ DISALLOW_COPY_AND_ASSIGN(ScopedUIGraphicsPushContext);
+};
+
+} // namespace gfx
+
+#endif // UI_GFX_SCOPED_UI_GRAPHICS_PUSH_CONTEXT_IOS_H_
diff --git a/chromium/ui/gfx/scoped_ui_graphics_push_context_ios.mm b/chromium/ui/gfx/scoped_ui_graphics_push_context_ios.mm
new file mode 100644
index 00000000000..93cfde57d50
--- /dev/null
+++ b/chromium/ui/gfx/scoped_ui_graphics_push_context_ios.mm
@@ -0,0 +1,23 @@
+// Copyright (c) 2012 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 "ui/gfx/scoped_ui_graphics_push_context_ios.h"
+
+#import <UIKit/UIKit.h>
+
+#include "base/logging.h"
+
+namespace gfx {
+
+ScopedUIGraphicsPushContext::ScopedUIGraphicsPushContext(CGContextRef context)
+ : context_(context) {
+ UIGraphicsPushContext(context_);
+}
+
+ScopedUIGraphicsPushContext::~ScopedUIGraphicsPushContext() {
+ DCHECK_EQ(context_, UIGraphicsGetCurrentContext());
+ UIGraphicsPopContext();
+}
+
+} // namespace gfx
diff --git a/chromium/ui/gfx/screen.cc b/chromium/ui/gfx/screen.cc
new file mode 100644
index 00000000000..06ec78ec885
--- /dev/null
+++ b/chromium/ui/gfx/screen.cc
@@ -0,0 +1,58 @@
+// Copyright 2012 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/logging.h"
+#include "ui/gfx/screen.h"
+#include "ui/gfx/screen_type_delegate.h"
+
+namespace gfx {
+
+namespace {
+
+Screen* g_screen_[SCREEN_TYPE_LAST + 1];
+ScreenTypeDelegate* g_screen_type_delegate_ = NULL;
+
+} // namespace
+
+Screen::Screen() {
+}
+
+Screen::~Screen() {
+}
+
+// static
+Screen* Screen::GetScreenFor(NativeView view) {
+ ScreenType type = SCREEN_TYPE_NATIVE;
+ if (g_screen_type_delegate_)
+ type = g_screen_type_delegate_->GetScreenTypeForNativeView(view);
+ if (type == SCREEN_TYPE_NATIVE)
+ return GetNativeScreen();
+ DCHECK(g_screen_[type]);
+ return g_screen_[type];
+}
+
+// static
+void Screen::SetScreenInstance(ScreenType type, Screen* instance) {
+ DCHECK_LE(type, SCREEN_TYPE_LAST);
+ g_screen_[type] = instance;
+}
+
+// static
+Screen* Screen::GetScreenByType(ScreenType type) {
+ return g_screen_[type];
+}
+
+// static
+void Screen::SetScreenTypeDelegate(ScreenTypeDelegate* delegate) {
+ g_screen_type_delegate_ = delegate;
+}
+
+// static
+Screen* Screen::GetNativeScreen() {
+ if (!g_screen_[SCREEN_TYPE_NATIVE])
+ g_screen_[SCREEN_TYPE_NATIVE] = CreateNativeScreen();
+ return g_screen_[SCREEN_TYPE_NATIVE];
+}
+
+} // namespace gfx
diff --git a/chromium/ui/gfx/screen.h b/chromium/ui/gfx/screen.h
new file mode 100644
index 00000000000..9410d028889
--- /dev/null
+++ b/chromium/ui/gfx/screen.h
@@ -0,0 +1,86 @@
+// Copyright (c) 2012 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_SCREEN_H_
+#define UI_GFX_SCREEN_H_
+
+#include "base/basictypes.h"
+#include "ui/base/ui_export.h"
+#include "ui/gfx/display.h"
+#include "ui/gfx/native_widget_types.h"
+#include "ui/gfx/point.h"
+#include "ui/gfx/screen_type_delegate.h"
+
+namespace gfx {
+class DisplayObserver;
+class Rect;
+
+// A utility class for getting various info about screen size, displays,
+// cursor position, etc.
+class UI_EXPORT Screen {
+ public:
+ // Retrieves the Screen that the specified NativeView belongs to. A value of
+ // NULL is treated as |SCREEN_TYPE_NATIVE|.
+ static Screen* GetScreenFor(NativeView view);
+
+ // Returns the SCREEN_TYPE_NATIVE Screen. This should be used with caution,
+ // as it is likely to be incorrect for code that runs on Windows.
+ static Screen* GetNativeScreen();
+
+ // Sets the global screen for a particular screen type. Only the _NATIVE
+ // ScreenType must be provided.
+ static void SetScreenInstance(ScreenType type, Screen* instance);
+
+ // Returns the global screen for a particular type. Types other than _NATIVE
+ // may be NULL.
+ static Screen* GetScreenByType(ScreenType type);
+
+ // Sets the global ScreenTypeDelegate. May be left unset if the platform
+ // uses only the _NATIVE ScreenType.
+ static void SetScreenTypeDelegate(ScreenTypeDelegate* delegate);
+
+ Screen();
+ virtual ~Screen();
+
+ // Returns true if DIP is enabled.
+ virtual bool IsDIPEnabled() = 0;
+
+ // Returns the current absolute position of the mouse pointer.
+ virtual gfx::Point GetCursorScreenPoint() = 0;
+
+ // Returns the window under the cursor.
+ virtual gfx::NativeWindow GetWindowAtCursorScreenPoint() = 0;
+
+ // Returns the number of displays.
+ // Mirrored displays are excluded; this method is intended to return the
+ // number of distinct, usable displays.
+ virtual int GetNumDisplays() = 0;
+
+ // Returns the display nearest the specified window.
+ virtual gfx::Display GetDisplayNearestWindow(NativeView view) const = 0;
+
+ // Returns the the display nearest the specified point.
+ virtual gfx::Display GetDisplayNearestPoint(
+ const gfx::Point& point) const = 0;
+
+ // Returns the display that most closely intersects the provided bounds.
+ virtual gfx::Display GetDisplayMatching(
+ const gfx::Rect& match_rect) const = 0;
+
+ // Returns the primary display.
+ virtual gfx::Display GetPrimaryDisplay() const = 0;
+
+ // Adds/Removes display observers.
+ virtual void AddObserver(DisplayObserver* observer) = 0;
+ virtual void RemoveObserver(DisplayObserver* observer) = 0;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(Screen);
+};
+
+Screen* CreateNativeScreen();
+
+} // namespace gfx
+
+#endif // UI_GFX_SCREEN_H_
diff --git a/chromium/ui/gfx/screen_android.cc b/chromium/ui/gfx/screen_android.cc
new file mode 100644
index 00000000000..6c4d63325d6
--- /dev/null
+++ b/chromium/ui/gfx/screen_android.cc
@@ -0,0 +1,76 @@
+// Copyright (c) 2012 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 "ui/gfx/screen.h"
+
+#include "base/logging.h"
+#include "ui/gfx/android/device_display_info.h"
+#include "ui/gfx/display.h"
+#include "ui/gfx/size_conversions.h"
+
+namespace gfx {
+
+class ScreenAndroid : public Screen {
+ public:
+ ScreenAndroid() {}
+
+ virtual bool IsDIPEnabled() OVERRIDE { return true; }
+
+ virtual gfx::Point GetCursorScreenPoint() OVERRIDE { return gfx::Point(); }
+
+ virtual gfx::NativeWindow GetWindowAtCursorScreenPoint() OVERRIDE {
+ NOTIMPLEMENTED();
+ return NULL;
+ }
+
+ virtual gfx::Display GetPrimaryDisplay() const OVERRIDE {
+ gfx::DeviceDisplayInfo device_info;
+ const float device_scale_factor = device_info.GetDIPScale();
+ const gfx::Rect bounds_in_pixels =
+ gfx::Rect(
+ device_info.GetDisplayWidth(),
+ device_info.GetDisplayHeight());
+ const gfx::Rect bounds_in_dip =
+ gfx::Rect(gfx::ToCeiledSize(gfx::ScaleSize(
+ bounds_in_pixels.size(), 1.0f / device_scale_factor)));
+ gfx::Display display(0, bounds_in_dip);
+ if (!gfx::Display::HasForceDeviceScaleFactor())
+ display.set_device_scale_factor(device_scale_factor);
+ return display;
+ }
+
+ virtual gfx::Display GetDisplayNearestWindow(
+ gfx::NativeView view) const OVERRIDE {
+ return GetPrimaryDisplay();
+ }
+
+ virtual gfx::Display GetDisplayNearestPoint(
+ const gfx::Point& point) const OVERRIDE {
+ return GetPrimaryDisplay();
+ }
+
+ virtual int GetNumDisplays() OVERRIDE { return 1; }
+
+ virtual gfx::Display GetDisplayMatching(
+ const gfx::Rect& match_rect) const OVERRIDE {
+ return GetPrimaryDisplay();
+ }
+
+ virtual void AddObserver(DisplayObserver* observer) OVERRIDE {
+ // no display change on Android.
+ }
+
+ virtual void RemoveObserver(DisplayObserver* observer) OVERRIDE {
+ // no display change on Android.
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(ScreenAndroid);
+};
+
+Screen* CreateNativeScreen() {
+ return new ScreenAndroid;
+}
+
+} // namespace gfx
diff --git a/chromium/ui/gfx/screen_aura.cc b/chromium/ui/gfx/screen_aura.cc
new file mode 100644
index 00000000000..7a1effa0e8c
--- /dev/null
+++ b/chromium/ui/gfx/screen_aura.cc
@@ -0,0 +1,16 @@
+// Copyright (c) 2012 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 "ui/gfx/screen.h"
+
+#include "base/logging.h"
+
+namespace gfx {
+
+Screen* CreateNativeScreen() {
+ NOTREACHED() << "Implementation should be installed at higher level.";
+ return NULL;
+}
+
+} // namespace gfx
diff --git a/chromium/ui/gfx/screen_gtk.cc b/chromium/ui/gfx/screen_gtk.cc
new file mode 100644
index 00000000000..8ef803dca1f
--- /dev/null
+++ b/chromium/ui/gfx/screen_gtk.cc
@@ -0,0 +1,188 @@
+// Copyright (c) 2012 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 "ui/gfx/screen.h"
+
+#include <gdk/gdkx.h>
+#include <gtk/gtk.h>
+
+#include "base/logging.h"
+#include "ui/gfx/display.h"
+
+namespace {
+
+bool GetScreenWorkArea(gfx::Rect* out_rect) {
+ gboolean ok;
+ guchar* raw_data = NULL;
+ gint data_len = 0;
+ ok = gdk_property_get(gdk_get_default_root_window(), // a gdk window
+ gdk_atom_intern("_NET_WORKAREA", FALSE), // property
+ gdk_atom_intern("CARDINAL", FALSE), // property type
+ 0, // byte offset into property
+ 0xff, // property length to retrieve
+ false, // delete property after retrieval?
+ NULL, // returned property type
+ NULL, // returned data format
+ &data_len, // returned data len
+ &raw_data); // returned data
+ if (!ok)
+ return false;
+
+ // We expect to get four longs back: x, y, width, height.
+ if (data_len < static_cast<gint>(4 * sizeof(glong))) {
+ NOTREACHED();
+ g_free(raw_data);
+ return false;
+ }
+
+ glong* data = reinterpret_cast<glong*>(raw_data);
+ gint x = data[0];
+ gint y = data[1];
+ gint width = data[2];
+ gint height = data[3];
+ g_free(raw_data);
+
+ out_rect->SetRect(x, y, width, height);
+ return true;
+}
+
+gfx::Rect NativePrimaryMonitorBounds() {
+ GdkScreen* screen = gdk_screen_get_default();
+ GdkRectangle rect;
+ gdk_screen_get_monitor_geometry(screen, 0, &rect);
+ return gfx::Rect(rect);
+}
+
+gfx::Rect GetMonitorAreaNearestWindow(gfx::NativeView view) {
+ GdkScreen* screen = gdk_screen_get_default();
+ gint monitor_num = 0;
+ if (view && GTK_IS_WINDOW(view)) {
+ GtkWidget* top_level = gtk_widget_get_toplevel(view);
+ DCHECK(GTK_IS_WINDOW(top_level));
+ GtkWindow* window = GTK_WINDOW(top_level);
+ screen = gtk_window_get_screen(window);
+ monitor_num = gdk_screen_get_monitor_at_window(
+ screen,
+ gtk_widget_get_window(top_level));
+ }
+ GdkRectangle bounds;
+ gdk_screen_get_monitor_geometry(screen, monitor_num, &bounds);
+ return gfx::Rect(bounds);
+}
+
+class ScreenGtk : public gfx::Screen {
+ public:
+ ScreenGtk() {
+ }
+
+ virtual ~ScreenGtk() {
+ }
+
+ virtual bool IsDIPEnabled() OVERRIDE {
+ return false;
+ }
+
+ virtual gfx::Point GetCursorScreenPoint() OVERRIDE {
+ gint x, y;
+ gdk_display_get_pointer(gdk_display_get_default(), NULL, &x, &y, NULL);
+ return gfx::Point(x, y);
+ }
+
+ // Returns the window under the cursor.
+ virtual gfx::NativeWindow GetWindowAtCursorScreenPoint() OVERRIDE {
+ GdkWindow* window = gdk_window_at_pointer(NULL, NULL);
+ if (!window)
+ return NULL;
+
+ gpointer data = NULL;
+ gdk_window_get_user_data(window, &data);
+ GtkWidget* widget = reinterpret_cast<GtkWidget*>(data);
+ if (!widget)
+ return NULL;
+ widget = gtk_widget_get_toplevel(widget);
+ return GTK_IS_WINDOW(widget) ? GTK_WINDOW(widget) : NULL;
+ }
+
+ // Returns the number of displays.
+ // Mirrored displays are excluded; this method is intended to return the
+ // number of distinct, usable displays.
+ virtual int GetNumDisplays() OVERRIDE {
+ // This query is kinda bogus for Linux -- do we want number of X screens?
+ // The number of monitors Xinerama has? We'll just use whatever GDK uses.
+ GdkScreen* screen = gdk_screen_get_default();
+ return gdk_screen_get_n_monitors(screen);
+ }
+
+ // Returns the display nearest the specified window.
+ virtual gfx::Display GetDisplayNearestWindow(
+ gfx::NativeView view) const OVERRIDE {
+ gfx::Rect bounds = GetMonitorAreaNearestWindow(view);
+ // Do not use the _NET_WORKAREA here, this is supposed to be an area on a
+ // specific monitor, and _NET_WORKAREA is a hint from the WM that
+ // generally spans across all monitors. This would make the work area
+ // larger than the monitor.
+ // TODO(danakj) This is a work-around as there is no standard way to get
+ // this area, but it is a rect that we should be computing. The standard
+ // means to compute this rect would be to watch all windows with
+ // _NET_WM_STRUT(_PARTIAL) hints, and subtract their space from the
+ // physical area of the display to construct a work area.
+ // TODO(oshima): Implement ID and Observer.
+ return gfx::Display(0, bounds);
+ }
+
+ // Returns the the display nearest the specified point.
+ virtual gfx::Display GetDisplayNearestPoint(
+ const gfx::Point& point) const OVERRIDE {
+ GdkScreen* screen = gdk_screen_get_default();
+ gint monitor = gdk_screen_get_monitor_at_point(
+ screen, point.x(), point.y());
+ GdkRectangle bounds;
+ gdk_screen_get_monitor_geometry(screen, monitor, &bounds);
+ // TODO(oshima): Implement ID and Observer.
+ return gfx::Display(0, gfx::Rect(bounds));
+ }
+
+ // Returns the display that most closely intersects the provided bounds.
+ virtual gfx::Display GetDisplayMatching(
+ const gfx::Rect& match_rect) const OVERRIDE {
+ // TODO(thestig) Implement multi-monitor support.
+ return GetPrimaryDisplay();
+ }
+
+ // Returns the primary display.
+ virtual gfx::Display GetPrimaryDisplay() const OVERRIDE {
+ gfx::Rect bounds = NativePrimaryMonitorBounds();
+ // TODO(oshima): Implement ID and Observer.
+ gfx::Display display(0, bounds);
+ gfx::Rect rect;
+ if (GetScreenWorkArea(&rect)) {
+ display.set_work_area(gfx::IntersectRects(rect, bounds));
+ } else {
+ // Return the best we've got.
+ display.set_work_area(bounds);
+ }
+ return display;
+ }
+
+ virtual void AddObserver(gfx::DisplayObserver* observer) OVERRIDE {
+ // TODO(oshima): crbug.com/122863.
+ }
+
+ virtual void RemoveObserver(gfx::DisplayObserver* observer) OVERRIDE {
+ // TODO(oshima): crbug.com/122863.
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(ScreenGtk);
+};
+
+} // namespace
+
+namespace gfx {
+
+Screen* CreateNativeScreen() {
+ return new ScreenGtk;
+}
+
+} // namespace gfx
diff --git a/chromium/ui/gfx/screen_ios.mm b/chromium/ui/gfx/screen_ios.mm
new file mode 100644
index 00000000000..dfb0d1b62bb
--- /dev/null
+++ b/chromium/ui/gfx/screen_ios.mm
@@ -0,0 +1,84 @@
+// Copyright (c) 2012 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 "ui/gfx/screen.h"
+
+#import <UIKit/UIKit.h>
+
+#include "base/logging.h"
+#include "ui/gfx/display.h"
+
+namespace {
+
+class ScreenIos : public gfx::Screen {
+ virtual bool IsDIPEnabled() OVERRIDE {
+ return true;
+ }
+
+ virtual gfx::Point GetCursorScreenPoint() OVERRIDE {
+ NOTIMPLEMENTED();
+ return gfx::Point(0, 0);
+ }
+
+ virtual gfx::NativeWindow GetWindowAtCursorScreenPoint() OVERRIDE {
+ NOTIMPLEMENTED();
+ return gfx::NativeWindow();
+ }
+
+ virtual int GetNumDisplays() OVERRIDE {
+#if TARGET_IPHONE_SIMULATOR
+ // UIScreen does not reliably return correct results on the simulator.
+ return 1;
+#else
+ return [[UIScreen screens] count];
+#endif
+ }
+
+ // Returns the display nearest the specified window.
+ virtual gfx::Display GetDisplayNearestWindow(
+ gfx::NativeView view) const OVERRIDE {
+ NOTIMPLEMENTED();
+ return gfx::Display();
+ }
+
+ // Returns the the display nearest the specified point.
+ virtual gfx::Display GetDisplayNearestPoint(
+ const gfx::Point& point) const OVERRIDE {
+ NOTIMPLEMENTED();
+ return gfx::Display();
+ }
+
+ // Returns the display that most closely intersects the provided bounds.
+ virtual gfx::Display GetDisplayMatching(
+ const gfx::Rect& match_rect) const OVERRIDE {
+ NOTIMPLEMENTED();
+ return gfx::Display();
+ }
+
+ // Returns the primary display.
+ virtual gfx::Display GetPrimaryDisplay() const OVERRIDE {
+ UIScreen* mainScreen = [[UIScreen screens] objectAtIndex:0];
+ gfx::Display display(0, gfx::Rect(mainScreen.bounds));
+ display.set_device_scale_factor([mainScreen scale]);
+ return display;
+ }
+
+ virtual void AddObserver(gfx::DisplayObserver* observer) OVERRIDE {
+ // no display change on iOS.
+ }
+
+ virtual void RemoveObserver(gfx::DisplayObserver* observer) OVERRIDE {
+ // no display change on iOS.
+ }
+};
+
+} // namespace
+
+namespace gfx {
+
+Screen* CreateNativeScreen() {
+ return new ScreenIos;
+}
+
+} // namespace gfx
diff --git a/chromium/ui/gfx/screen_mac.mm b/chromium/ui/gfx/screen_mac.mm
new file mode 100644
index 00000000000..d9e1f0a71c9
--- /dev/null
+++ b/chromium/ui/gfx/screen_mac.mm
@@ -0,0 +1,185 @@
+// Copyright (c) 2012 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 "ui/gfx/screen.h"
+
+#import <ApplicationServices/ApplicationServices.h>
+#import <Cocoa/Cocoa.h>
+
+#include "base/logging.h"
+#include "base/mac/sdk_forward_declarations.h"
+#include "ui/gfx/display.h"
+
+namespace {
+
+gfx::Rect ConvertCoordinateSystem(NSRect ns_rect) {
+ // Primary monitor is defined as the monitor with the menubar,
+ // which is always at index 0.
+ NSScreen* primary_screen = [[NSScreen screens] objectAtIndex:0];
+ float primary_screen_height = [primary_screen frame].size.height;
+ gfx::Rect rect(NSRectToCGRect(ns_rect));
+ rect.set_y(primary_screen_height - rect.y() - rect.height());
+ return rect;
+}
+
+NSScreen* GetMatchingScreen(const gfx::Rect& match_rect) {
+ // Default to the monitor with the current keyboard focus, in case
+ // |match_rect| is not on any screen at all.
+ NSScreen* max_screen = [NSScreen mainScreen];
+ int max_area = 0;
+
+ for (NSScreen* screen in [NSScreen screens]) {
+ gfx::Rect monitor_area = ConvertCoordinateSystem([screen frame]);
+ gfx::Rect intersection = gfx::IntersectRects(monitor_area, match_rect);
+ int area = intersection.width() * intersection.height();
+ if (area > max_area) {
+ max_area = area;
+ max_screen = screen;
+ }
+ }
+
+ return max_screen;
+}
+
+gfx::Display GetDisplayForScreen(NSScreen* screen, bool is_primary) {
+ NSRect frame = [screen frame];
+ // TODO(oshima): Implement ID and Observer.
+ gfx::Display display(0, gfx::Rect(NSRectToCGRect(frame)));
+
+ NSRect visible_frame = [screen visibleFrame];
+
+ // Convert work area's coordinate systems.
+ if (is_primary) {
+ gfx::Rect work_area = gfx::Rect(NSRectToCGRect(visible_frame));
+ work_area.set_y(frame.size.height - visible_frame.origin.y -
+ visible_frame.size.height);
+ display.set_work_area(work_area);
+ } else {
+ display.set_bounds(ConvertCoordinateSystem(frame));
+ display.set_work_area(ConvertCoordinateSystem(visible_frame));
+ }
+ CGFloat scale;
+ if ([screen respondsToSelector:@selector(backingScaleFactor)])
+ scale = [screen backingScaleFactor];
+ else
+ scale = [screen userSpaceScaleFactor];
+ display.set_device_scale_factor(scale);
+ return display;
+}
+
+class ScreenMac : public gfx::Screen {
+ public:
+ ScreenMac() {}
+
+ virtual bool IsDIPEnabled() OVERRIDE {
+ return true;
+ }
+
+ virtual gfx::Point GetCursorScreenPoint() OVERRIDE {
+ NSPoint mouseLocation = [NSEvent mouseLocation];
+ // Flip coordinates to gfx (0,0 in top-left corner) using primary screen.
+ NSScreen* screen = [[NSScreen screens] objectAtIndex:0];
+ mouseLocation.y = NSMaxY([screen frame]) - mouseLocation.y;
+ return gfx::Point(mouseLocation.x, mouseLocation.y);
+ }
+
+ virtual gfx::NativeWindow GetWindowAtCursorScreenPoint() OVERRIDE {
+ NOTIMPLEMENTED();
+ return gfx::NativeWindow();
+ }
+
+ virtual int GetNumDisplays() OVERRIDE {
+ // Don't just return the number of online displays. It includes displays
+ // that mirror other displays, which are not desired in the count. It's
+ // tempting to use the count returned by CGGetActiveDisplayList, but active
+ // displays exclude sleeping displays, and those are desired in the count.
+
+ // It would be ridiculous to have this many displays connected, but
+ // CGDirectDisplayID is just an integer, so supporting up to this many
+ // doesn't hurt.
+ CGDirectDisplayID online_displays[128];
+ CGDisplayCount online_display_count = 0;
+ if (CGGetOnlineDisplayList(arraysize(online_displays),
+ online_displays,
+ &online_display_count) != kCGErrorSuccess) {
+ // 1 is a reasonable assumption.
+ return 1;
+ }
+
+ int display_count = 0;
+ for (CGDisplayCount online_display_index = 0;
+ online_display_index < online_display_count;
+ ++online_display_index) {
+ CGDirectDisplayID online_display = online_displays[online_display_index];
+ if (CGDisplayMirrorsDisplay(online_display) == kCGNullDirectDisplay) {
+ // If this display doesn't mirror any other, include it in the count.
+ // The primary display in a mirrored set will be counted, but those that
+ // mirror it will not be.
+ ++display_count;
+ }
+ }
+
+ return display_count;
+ }
+
+ virtual gfx::Display GetDisplayNearestWindow(
+ gfx::NativeView view) const OVERRIDE {
+ NSWindow* window = [view window];
+ if (!window)
+ return GetPrimaryDisplay();
+ NSScreen* match_screen = [window screen];
+ return GetDisplayForScreen(match_screen, false /* may not be primary */);
+ }
+
+ virtual gfx::Display GetDisplayNearestPoint(
+ const gfx::Point& point) const OVERRIDE {
+ NSPoint ns_point = NSPointFromCGPoint(point.ToCGPoint());
+
+ NSArray* screens = [NSScreen screens];
+ NSScreen* primary = [screens objectAtIndex:0];
+ ns_point.y = NSMaxY([primary frame]) - ns_point.y;
+ for (NSScreen* screen in screens) {
+ if (NSMouseInRect(ns_point, [screen frame], NO))
+ return GetDisplayForScreen(screen, screen == primary);
+ }
+ return GetPrimaryDisplay();
+ }
+
+ // Returns the display that most closely intersects the provided bounds.
+ virtual gfx::Display GetDisplayMatching(
+ const gfx::Rect& match_rect) const OVERRIDE {
+ NSScreen* match_screen = GetMatchingScreen(match_rect);
+ return GetDisplayForScreen(match_screen, false /* may not be primary */);
+ }
+
+ // Returns the primary display.
+ virtual gfx::Display GetPrimaryDisplay() const OVERRIDE {
+ // Primary display is defined as the display with the menubar,
+ // which is always at index 0.
+ NSScreen* primary = [[NSScreen screens] objectAtIndex:0];
+ gfx::Display display = GetDisplayForScreen(primary, true /* primary */);
+ return display;
+ }
+
+ virtual void AddObserver(gfx::DisplayObserver* observer) OVERRIDE {
+ // TODO(oshima): crbug.com/122863.
+ }
+
+ virtual void RemoveObserver(gfx::DisplayObserver* observer) OVERRIDE {
+ // TODO(oshima): crbug.com/122863.
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(ScreenMac);
+};
+
+} // namespace
+
+namespace gfx {
+
+Screen* CreateNativeScreen() {
+ return new ScreenMac;
+}
+
+}
diff --git a/chromium/ui/gfx/screen_type_delegate.h b/chromium/ui/gfx/screen_type_delegate.h
new file mode 100644
index 00000000000..12e8d90939d
--- /dev/null
+++ b/chromium/ui/gfx/screen_type_delegate.h
@@ -0,0 +1,32 @@
+// Copyright 2012 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_SCREEN_TYPE_DELEGATE_H_
+#define UI_GFX_SCREEN_TYPE_DELEGATE_H_
+
+#include "ui/gfx/native_widget_types.h"
+
+namespace gfx {
+
+enum UI_EXPORT ScreenType {
+ SCREEN_TYPE_NATIVE = 0,
+#if defined(OS_CHROMEOS)
+ SCREEN_TYPE_ALTERNATE = SCREEN_TYPE_NATIVE,
+#else
+ SCREEN_TYPE_ALTERNATE,
+#endif
+ SCREEN_TYPE_LAST = SCREEN_TYPE_ALTERNATE,
+};
+
+class UI_EXPORT ScreenTypeDelegate {
+ public:
+ virtual ~ScreenTypeDelegate() {}
+
+ // Determines which ScreenType a given |view| belongs to.
+ virtual ScreenType GetScreenTypeForNativeView(NativeView view) = 0;
+};
+
+} // namespace gfx
+
+#endif // UI_GFX_SCREEN_TYPE_DELEGATE_H_
diff --git a/chromium/ui/gfx/screen_unittest.cc b/chromium/ui/gfx/screen_unittest.cc
new file mode 100644
index 00000000000..db5a03ffec9
--- /dev/null
+++ b/chromium/ui/gfx/screen_unittest.cc
@@ -0,0 +1,24 @@
+// Copyright (c) 2012 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 "ui/gfx/screen.h"
+
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace {
+
+TEST(ScreenTest, GetPrimaryDisplaySize) {
+ // We aren't actually testing that it's correct, just that it's sane.
+ const gfx::Size size =
+ gfx::Screen::GetNativeScreen()->GetPrimaryDisplay().size();
+ EXPECT_GE(size.width(), 1);
+ EXPECT_GE(size.height(), 1);
+}
+
+TEST(ScreenTest, GetNumDisplays) {
+ // We aren't actually testing that it's correct, just that it's sane.
+ EXPECT_GE(gfx::Screen::GetNativeScreen()->GetNumDisplays(), 1);
+}
+
+} // namespace
diff --git a/chromium/ui/gfx/screen_win.cc b/chromium/ui/gfx/screen_win.cc
new file mode 100644
index 00000000000..06c14bd1732
--- /dev/null
+++ b/chromium/ui/gfx/screen_win.cc
@@ -0,0 +1,139 @@
+// Copyright (c) 2012 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 "ui/gfx/screen_win.h"
+
+#include <windows.h>
+
+#include "base/logging.h"
+#include "ui/base/win/dpi.h"
+#include "ui/gfx/display.h"
+
+namespace {
+
+MONITORINFO GetMonitorInfoForMonitor(HMONITOR monitor) {
+ MONITORINFO monitor_info = { 0 };
+ monitor_info.cbSize = sizeof(monitor_info);
+ GetMonitorInfo(monitor, &monitor_info);
+ return monitor_info;
+}
+
+gfx::Display GetDisplay(MONITORINFO& monitor_info) {
+ // TODO(oshima): Implement ID and Observer.
+ gfx::Rect bounds = gfx::Rect(monitor_info.rcMonitor);
+ gfx::Display display(0, bounds);
+ display.set_work_area(gfx::Rect(monitor_info.rcWork));
+ display.SetScaleAndBounds(ui::win::GetDeviceScaleFactor(), bounds);
+ return display;
+}
+
+} // namespace
+
+namespace gfx {
+
+ScreenWin::ScreenWin() {
+}
+
+ScreenWin::~ScreenWin() {
+}
+
+bool ScreenWin::IsDIPEnabled() {
+ return ui::IsInHighDPIMode();
+}
+
+gfx::Point ScreenWin::GetCursorScreenPoint() {
+ POINT pt;
+ GetCursorPos(&pt);
+ return gfx::Point(pt);
+}
+
+gfx::NativeWindow ScreenWin::GetWindowAtCursorScreenPoint() {
+ POINT location;
+ HWND window_hwnd = GetCursorPos(&location) ? WindowFromPoint(location) : NULL;
+ return GetNativeWindowFromHWND(window_hwnd);
+}
+
+int ScreenWin::GetNumDisplays() {
+ return GetSystemMetrics(SM_CMONITORS);
+}
+
+gfx::Display ScreenWin::GetDisplayNearestWindow(gfx::NativeView window) const {
+ HWND window_hwnd = GetHWNDFromNativeView(window);
+ if (!window_hwnd) {
+ // When |window| isn't rooted to a display, we should just return the
+ // default display so we get some correct display information like the
+ // scaling factor.
+ return GetPrimaryDisplay();
+ }
+
+ MONITORINFO monitor_info;
+ monitor_info.cbSize = sizeof(monitor_info);
+ GetMonitorInfo(MonitorFromWindow(window_hwnd, MONITOR_DEFAULTTONEAREST),
+ &monitor_info);
+ return GetDisplay(monitor_info);
+}
+
+gfx::Display ScreenWin::GetDisplayNearestPoint(const gfx::Point& point) const {
+ POINT initial_loc = { point.x(), point.y() };
+ HMONITOR monitor = MonitorFromPoint(initial_loc, MONITOR_DEFAULTTONEAREST);
+ MONITORINFO mi = {0};
+ mi.cbSize = sizeof(mi);
+ if (monitor && GetMonitorInfo(monitor, &mi))
+ return GetDisplay(mi);
+ return gfx::Display();
+}
+
+gfx::Display ScreenWin::GetDisplayMatching(const gfx::Rect& match_rect) const {
+ RECT other_bounds_rect = match_rect.ToRECT();
+ MONITORINFO monitor_info = GetMonitorInfoForMonitor(MonitorFromRect(
+ &other_bounds_rect, MONITOR_DEFAULTTONEAREST));
+ return GetDisplay(monitor_info);
+}
+
+gfx::Display ScreenWin::GetPrimaryDisplay() const {
+ MONITORINFO mi = GetMonitorInfoForMonitor(
+ MonitorFromWindow(NULL, MONITOR_DEFAULTTOPRIMARY));
+ gfx::Display display = GetDisplay(mi);
+ // TODO(kevers|girard): Test if these checks can be reintroduced for high-DIP
+ // once more of the app is DIP-aware.
+ if (!ui::IsInHighDPIMode()) {
+ DCHECK_EQ(GetSystemMetrics(SM_CXSCREEN), display.size().width());
+ DCHECK_EQ(GetSystemMetrics(SM_CYSCREEN), display.size().height());
+ }
+ return display;
+}
+
+void ScreenWin::AddObserver(DisplayObserver* observer) {
+ // TODO(oshima): crbug.com/122863.
+}
+
+void ScreenWin::RemoveObserver(DisplayObserver* observer) {
+ // TODO(oshima): crbug.com/122863.
+}
+
+HWND ScreenWin::GetHWNDFromNativeView(NativeView window) const {
+#if defined(USE_AURA)
+ NOTREACHED();
+ return NULL;
+#else
+ return window;
+#endif // USE_AURA
+}
+
+NativeWindow ScreenWin::GetNativeWindowFromHWND(HWND hwnd) const {
+#if defined(USE_AURA)
+ NOTREACHED();
+ return NULL;
+#else
+ return hwnd;
+#endif // USE_AURA
+}
+
+#if !defined(USE_AURA)
+Screen* CreateNativeScreen() {
+ return new ScreenWin;
+}
+#endif // !USE_AURA
+
+} // namespace gfx
diff --git a/chromium/ui/gfx/screen_win.h b/chromium/ui/gfx/screen_win.h
new file mode 100644
index 00000000000..f6b7d40ae8d
--- /dev/null
+++ b/chromium/ui/gfx/screen_win.h
@@ -0,0 +1,47 @@
+// Copyright (c) 2012 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_SCREEN_WIN_H_
+#define UI_GFX_SCREEN_WIN_H_
+
+#include "base/compiler_specific.h"
+#include "ui/base/ui_export.h"
+#include "ui/gfx/screen.h"
+
+namespace gfx {
+
+class UI_EXPORT ScreenWin : public gfx::Screen {
+ public:
+ ScreenWin();
+ virtual ~ScreenWin();
+
+ protected:
+ // Overridden from gfx::Screen:
+ virtual bool IsDIPEnabled() OVERRIDE;
+ virtual gfx::Point GetCursorScreenPoint() OVERRIDE;
+ virtual gfx::NativeWindow GetWindowAtCursorScreenPoint() OVERRIDE;
+ virtual int GetNumDisplays() OVERRIDE;
+ virtual gfx::Display GetDisplayNearestWindow(
+ gfx::NativeView window) const OVERRIDE;
+ virtual gfx::Display GetDisplayNearestPoint(
+ const gfx::Point& point) const OVERRIDE;
+ virtual gfx::Display GetDisplayMatching(
+ const gfx::Rect& match_rect) const OVERRIDE;
+ virtual gfx::Display GetPrimaryDisplay() const OVERRIDE;
+ virtual void AddObserver(DisplayObserver* observer) OVERRIDE;
+ virtual void RemoveObserver(DisplayObserver* observer) OVERRIDE;
+
+ // Returns the HWND associated with the NativeView.
+ virtual HWND GetHWNDFromNativeView(NativeView window) const;
+
+ // Returns the NativeView associated with the HWND.
+ virtual NativeWindow GetNativeWindowFromHWND(HWND hwnd) const;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(ScreenWin);
+};
+
+} // namespace gfx
+
+#endif // UI_GFX_SCREEN_WIN_H_
diff --git a/chromium/ui/gfx/scrollbar_size.cc b/chromium/ui/gfx/scrollbar_size.cc
new file mode 100644
index 00000000000..d58fd95532a
--- /dev/null
+++ b/chromium/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 "ui/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/chromium/ui/gfx/scrollbar_size.h b/chromium/ui/gfx/scrollbar_size.h
new file mode 100644
index 00000000000..43f1ca5ee5e
--- /dev/null
+++ b/chromium/ui/gfx/scrollbar_size.h
@@ -0,0 +1,19 @@
+// 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_SCROLLBAR_SIZE_H_
+#define UI_GFX_SCROLLBAR_SIZE_H_
+
+#include "ui/base/ui_export.h"
+
+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().
+UI_EXPORT int scrollbar_size();
+
+} // namespace gfx
+
+#endif // UI_GFX_SCROLLBAR_SIZE_H_
diff --git a/chromium/ui/gfx/selection_model.cc b/chromium/ui/gfx/selection_model.cc
new file mode 100644
index 00000000000..c3a31dae107
--- /dev/null
+++ b/chromium/ui/gfx/selection_model.cc
@@ -0,0 +1,37 @@
+// Copyright (c) 2012 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 "ui/gfx/selection_model.h"
+
+#include "base/format_macros.h"
+#include "base/strings/stringprintf.h"
+
+namespace gfx {
+
+SelectionModel::SelectionModel()
+ : selection_(0), caret_affinity_(CURSOR_BACKWARD) {}
+
+SelectionModel::SelectionModel(size_t position, LogicalCursorDirection affinity)
+ : selection_(position), caret_affinity_(affinity) {}
+
+SelectionModel::SelectionModel(ui::Range selection,
+ LogicalCursorDirection affinity)
+ : selection_(selection), caret_affinity_(affinity) {}
+
+bool SelectionModel::operator==(const SelectionModel& sel) const {
+ return selection_ == sel.selection() &&
+ caret_affinity_ == sel.caret_affinity();
+}
+
+std::string SelectionModel::ToString() const {
+ std::string str = "{";
+ if (selection().is_empty())
+ base::StringAppendF(&str, "%" PRIuS, caret_pos());
+ else
+ str += selection().ToString();
+ const bool backward = caret_affinity() == CURSOR_BACKWARD;
+ return str + (backward ? ",BACKWARD}" : ",FORWARD}");
+}
+
+} // namespace gfx
diff --git a/chromium/ui/gfx/selection_model.h b/chromium/ui/gfx/selection_model.h
new file mode 100644
index 00000000000..b99b383714b
--- /dev/null
+++ b/chromium/ui/gfx/selection_model.h
@@ -0,0 +1,113 @@
+// Copyright (c) 2012 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_SELECTION_MODEL_H_
+#define UI_GFX_SELECTION_MODEL_H_
+
+#include <string>
+
+#include "ui/base/range/range.h"
+#include "ui/base/ui_export.h"
+
+namespace gfx {
+
+// VisualCursorDirection and LogicalCursorDirection represent directions of
+// motion of the cursor in BiDi text. The combinations that make sense are:
+//
+// base::i18n::TextDirection VisualCursorDirection LogicalCursorDirection
+// LEFT_TO_RIGHT CURSOR_LEFT CURSOR_BACKWARD
+// LEFT_TO_RIGHT CURSOR_RIGHT CURSOR_FORWARD
+// RIGHT_TO_LEFT CURSOR_RIGHT CURSOR_BACKWARD
+// RIGHT_TO_LEFT CURSOR_LEFT CURSOR_FORWARD
+enum VisualCursorDirection {
+ CURSOR_LEFT,
+ CURSOR_RIGHT
+};
+enum LogicalCursorDirection {
+ CURSOR_BACKWARD,
+ CURSOR_FORWARD
+};
+
+// TODO(xji): publish bidi-editing guide line and replace the place holder.
+// SelectionModel is used to represent the logical selection and visual
+// position of cursor.
+//
+// For bi-directional text, the mapping between visual position and logical
+// position is not one-to-one. For example, logical text "abcDEF" where capital
+// letters stand for Hebrew, the visual display is "abcFED". According to the
+// bidi editing guide (http://bidi-editing-guideline):
+// 1. If pointing to the right half of the cell of a LTR character, the current
+// position must be set after this character and the caret must be displayed
+// after this character.
+// 2. If pointing to the right half of the cell of a RTL character, the current
+// position must be set before this character and the caret must be displayed
+// before this character.
+//
+// Pointing to the right half of 'c' and pointing to the right half of 'D' both
+// set the logical cursor position to 3. But the cursor displayed visually at
+// different places:
+// Pointing to the right half of 'c' displays the cursor right of 'c' as
+// "abc|FED".
+// Pointing to the right half of 'D' displays the cursor right of 'D' as
+// "abcFED|".
+// So, besides the logical selection start point and end point, we need extra
+// information to specify to which character the visual cursor is bound. This
+// is given by a "caret affinity" which is either CURSOR_BACKWARD (indicating
+// the trailing half of the 'c' in this case) or CURSOR_FORWARD (indicating
+// the leading half of the 'D').
+class UI_EXPORT SelectionModel {
+ public:
+ // Create a default SelectionModel to be overwritten later.
+ SelectionModel();
+ // Create a SelectionModel representing a caret |position| without a
+ // selection. The |affinity| is meaningful only when the caret is positioned
+ // between bidi runs that are not visually contiguous: in that case, it
+ // indicates the run to which the caret is attached for display purposes.
+ SelectionModel(size_t position, LogicalCursorDirection affinity);
+ // Create a SelectionModel representing a selection (which may be empty).
+ // The caret position is the end of the range.
+ SelectionModel(ui::Range selection, LogicalCursorDirection affinity);
+
+ const ui::Range& selection() const { return selection_; }
+ size_t caret_pos() const { return selection_.end(); }
+ LogicalCursorDirection caret_affinity() const { return caret_affinity_; }
+
+ bool operator==(const SelectionModel& sel) const;
+ bool operator!=(const SelectionModel& sel) const { return !(*this == sel); }
+
+ std::string ToString() const;
+
+ private:
+ friend class RenderText;
+
+ // TODO(benrg): Generally the selection start should not be changed without
+ // considering the effect on the caret affinity. This setter is exposed only
+ // to RenderText to discourage misuse, and should probably be removed.
+ void set_selection_start(size_t pos) { selection_.set_start(pos); }
+
+ // Logical selection. The logical caret position is the end of the selection.
+ ui::Range selection_;
+
+ // The logical direction from the caret position (selection_.end()) to the
+ // character it is attached to for display purposes. This matters only when
+ // the surrounding characters are not visually contiguous, which happens only
+ // in bidi text (and only at bidi run boundaries). The text is treated as
+ // though it was surrounded on both sides by runs in the dominant text
+ // direction. For example, supposing the dominant direction is LTR and the
+ // logical text is "abcDEF", where DEF is right-to-left text, the visual
+ // cursor will display as follows:
+ // caret position CURSOR_BACKWARD affinity CURSOR_FORWARD affinity
+ // 0 |abcFED |abcFED
+ // 1 a|bcFED a|bcFED
+ // 2 ab|cFED ab|cFED
+ // 3 abc|FED abcFED|
+ // 4 abcFE|D abcFE|D
+ // 5 abcF|ED abcF|ED
+ // 6 abc|FED abcFED|
+ LogicalCursorDirection caret_affinity_;
+};
+
+} // namespace gfx
+
+#endif // UI_GFX_SELECTION_MODEL_H_
diff --git a/chromium/ui/gfx/shadow_value.cc b/chromium/ui/gfx/shadow_value.cc
new file mode 100644
index 00000000000..4b24af15178
--- /dev/null
+++ b/chromium/ui/gfx/shadow_value.cc
@@ -0,0 +1,70 @@
+// Copyright (c) 2012 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 "ui/gfx/shadow_value.h"
+
+#include <algorithm>
+
+#include "base/strings/stringprintf.h"
+#include "ui/gfx/insets.h"
+#include "ui/gfx/point_conversions.h"
+
+namespace gfx {
+
+ShadowValue::ShadowValue()
+ : blur_(0),
+ color_(0) {
+}
+
+ShadowValue::ShadowValue(const gfx::Point& offset,
+ double blur,
+ SkColor color)
+ : offset_(offset),
+ blur_(blur),
+ color_(color) {
+}
+
+ShadowValue::~ShadowValue() {
+}
+
+ShadowValue ShadowValue::Scale(float scale) const {
+ gfx::Point scaled_offset =
+ gfx::ToFlooredPoint(gfx::ScalePoint(offset_, scale));
+ return ShadowValue(scaled_offset, blur_ * scale, color_);
+}
+
+std::string ShadowValue::ToString() const {
+ return base::StringPrintf(
+ "(%d,%d),%.2f,rgba(%d,%d,%d,%d)",
+ offset_.x(), offset_.y(),
+ blur_,
+ SkColorGetR(color_),
+ SkColorGetG(color_),
+ SkColorGetB(color_),
+ SkColorGetA(color_));
+}
+
+// static
+Insets ShadowValue::GetMargin(const ShadowValues& shadows) {
+ int left = 0;
+ int top = 0;
+ int right = 0;
+ int bottom = 0;
+
+ for (size_t i = 0; i < shadows.size(); ++i) {
+ const ShadowValue& shadow = shadows[i];
+
+ // Add 0.5 to round up to the next integer.
+ int blur = static_cast<int>(shadow.blur() / 2 + 0.5);
+
+ left = std::max(left, blur - shadow.x());
+ top = std::max(top, blur - shadow.y());
+ right = std::max(right, blur + shadow.x());
+ bottom = std::max(bottom, blur + shadow.y());
+ }
+
+ return Insets(-top, -left, -bottom, -right);
+}
+
+} // namespace gfx
diff --git a/chromium/ui/gfx/shadow_value.h b/chromium/ui/gfx/shadow_value.h
new file mode 100644
index 00000000000..d07ac94043b
--- /dev/null
+++ b/chromium/ui/gfx/shadow_value.h
@@ -0,0 +1,62 @@
+// Copyright (c) 2012 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_SHADOW_VALUE_H_
+#define UI_GFX_SHADOW_VALUE_H_
+
+#include <string>
+#include <vector>
+
+#include "third_party/skia/include/core/SkColor.h"
+#include "ui/base/ui_export.h"
+#include "ui/gfx/point.h"
+
+namespace gfx {
+
+class Insets;
+
+class ShadowValue;
+typedef std::vector<ShadowValue> ShadowValues;
+
+// ShadowValue encapsulates parameters needed to define a shadow, including the
+// shadow's offset, blur amount and color.
+class UI_EXPORT ShadowValue {
+ public:
+ ShadowValue();
+ ShadowValue(const gfx::Point& offset, double blur, SkColor color);
+ ~ShadowValue();
+
+ int x() const { return offset_.x(); }
+ int y() const { return offset_.y(); }
+ const gfx::Point& offset() const { return offset_; }
+ double blur() const { return blur_; }
+ SkColor color() const { return color_; }
+
+ ShadowValue Scale(float scale) const;
+
+ std::string ToString() const;
+
+ // Gets margin space needed for shadows. Note that values in returned Insets
+ // are negative because shadow margins are outside a boundary.
+ static Insets GetMargin(const ShadowValues& shadows);
+
+ private:
+ gfx::Point offset_;
+
+ // Blur amount of the shadow in pixels. If underlying implementation supports
+ // (e.g. Skia), it can have fraction part such as 0.5 pixel. The value
+ // defines a range from full shadow color at the start point inside the
+ // shadow to fully transparent at the end point outside it. The range is
+ // perpendicular to and centered on the shadow edge. For example, a blur
+ // amount of 4.0 means to have a blurry shadow edge of 4 pixels that
+ // transitions from full shadow color to fully transparent and with 2 pixels
+ // inside the shadow and 2 pixels goes beyond the edge.
+ double blur_;
+
+ SkColor color_;
+};
+
+} // namespace gfx
+
+#endif // UI_GFX_SHADOW_VALUE_H_
diff --git a/chromium/ui/gfx/shadow_value_unittest.cc b/chromium/ui/gfx/shadow_value_unittest.cc
new file mode 100644
index 00000000000..57515dc7075
--- /dev/null
+++ b/chromium/ui/gfx/shadow_value_unittest.cc
@@ -0,0 +1,63 @@
+// Copyright (c) 2012 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 "testing/gtest/include/gtest/gtest.h"
+#include "ui/gfx/insets.h"
+#include "ui/gfx/shadow_value.h"
+
+namespace gfx {
+
+TEST(ShadowValueTest, GetMargin) {
+ const struct TestCase {
+ Insets expected_margin;
+ size_t shadow_count;
+ ShadowValue shadows[2];
+ } kTestCases[] = {
+ {
+ Insets(), 0, {},
+ },
+ {
+ Insets(-2, -2, -2, -2),
+ 1,
+ { ShadowValue(gfx::Point(0, 0), 4, 0), },
+ },
+ {
+ Insets(0, -1, -4, -3),
+ 1,
+ { ShadowValue(gfx::Point(1, 2), 4, 0), },
+ },
+ {
+ Insets(-4, -3, 0, -1),
+ 1,
+ { ShadowValue(gfx::Point(-1, -2), 4, 0), },
+ },
+ {
+ Insets(0, -1, -5, -4),
+ 2,
+ {
+ ShadowValue(gfx::Point(1, 2), 4, 0),
+ ShadowValue(gfx::Point(2, 3), 4, 0),
+ },
+ },
+ {
+ Insets(-4, -3, -5, -4),
+ 2,
+ {
+ ShadowValue(gfx::Point(-1, -2), 4, 0),
+ ShadowValue(gfx::Point(2, 3), 4, 0),
+ },
+ },
+ };
+
+ for (size_t i = 0; i < ARRAYSIZE_UNSAFE(kTestCases); ++i) {
+ Insets margin = ShadowValue::GetMargin(
+ ShadowValues(kTestCases[i].shadows,
+ kTestCases[i].shadows + kTestCases[i].shadow_count));
+
+ EXPECT_EQ(kTestCases[i].expected_margin, margin) << " i=" << i;
+ }
+}
+
+} // namespace gfx
diff --git a/chromium/ui/gfx/size.cc b/chromium/ui/gfx/size.cc
new file mode 100644
index 00000000000..aa003e8c6df
--- /dev/null
+++ b/chromium/ui/gfx/size.cc
@@ -0,0 +1,46 @@
+// Copyright (c) 2012 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 "ui/gfx/size.h"
+
+#if defined(OS_WIN)
+#include <windows.h>
+#endif
+
+#include "base/strings/stringprintf.h"
+
+namespace gfx {
+
+template class SizeBase<Size, int>;
+
+#if defined(OS_MACOSX)
+Size::Size(const CGSize& s)
+ : SizeBase<Size, int>(s.width, 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
+
+std::string Size::ToString() const {
+ return base::StringPrintf("%dx%d", width(), height());
+}
+
+} // namespace gfx
diff --git a/chromium/ui/gfx/size.h b/chromium/ui/gfx/size.h
new file mode 100644
index 00000000000..f6152a3aa12
--- /dev/null
+++ b/chromium/ui/gfx/size.h
@@ -0,0 +1,67 @@
+// Copyright (c) 2012 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_
+
+#include <string>
+
+#include "base/compiler_specific.h"
+#include "ui/base/ui_export.h"
+#include "ui/gfx/size_base.h"
+#include "ui/gfx/size_f.h"
+
+#if defined(OS_WIN)
+typedef struct tagSIZE SIZE;
+#elif defined(OS_IOS)
+#include <CoreGraphics/CoreGraphics.h>
+#elif defined(OS_MACOSX)
+#include <ApplicationServices/ApplicationServices.h>
+#endif
+
+namespace gfx {
+
+// A size has width and height values.
+class UI_EXPORT Size : public SizeBase<Size, int> {
+ public:
+ Size() : SizeBase<Size, int>(0, 0) {}
+ Size(int width, int height) : SizeBase<Size, int>(width, height) {}
+#if defined(OS_MACOSX)
+ explicit Size(const CGSize& s);
+#endif
+
+ ~Size() {}
+
+#if defined(OS_MACOSX)
+ Size& operator=(const CGSize& s);
+#endif
+
+#if defined(OS_WIN)
+ SIZE ToSIZE() const;
+#elif defined(OS_MACOSX)
+ CGSize ToCGSize() const;
+#endif
+
+ operator SizeF() const {
+ return SizeF(width(), height());
+ }
+
+ std::string ToString() const;
+};
+
+inline bool operator==(const Size& lhs, const Size& rhs) {
+ return lhs.width() == rhs.width() && lhs.height() == rhs.height();
+}
+
+inline bool operator!=(const Size& lhs, const Size& rhs) {
+ return !(lhs == rhs);
+}
+
+#if !defined(COMPILER_MSVC)
+extern template class SizeBase<Size, int>;
+#endif
+
+} // namespace gfx
+
+#endif // UI_GFX_SIZE_H_
diff --git a/chromium/ui/gfx/size_base.h b/chromium/ui/gfx/size_base.h
new file mode 100644
index 00000000000..6f23fa3cedb
--- /dev/null
+++ b/chromium/ui/gfx/size_base.h
@@ -0,0 +1,69 @@
+// Copyright (c) 2012 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_BASE_H_
+#define UI_GFX_SIZE_BASE_H_
+
+#include "ui/base/ui_export.h"
+
+namespace gfx {
+
+// A size has width and height values.
+template<typename Class, typename Type>
+class UI_EXPORT SizeBase {
+ public:
+ Type width() const { return width_; }
+ Type height() const { return height_; }
+
+ Type GetArea() const { return width_ * height_; }
+
+ void SetSize(Type width, Type height) {
+ set_width(width);
+ set_height(height);
+ }
+
+ void Enlarge(Type width, Type height) {
+ set_width(width_ + width);
+ set_height(height_ + height);
+ }
+
+ void set_width(Type width) {
+ width_ = width < 0 ? 0 : width;
+ }
+ void set_height(Type height) {
+ height_ = height < 0 ? 0 : height;
+ }
+
+ void SetToMin(const Class& other) {
+ width_ = width_ <= other.width_ ? width_ : other.width_;
+ height_ = height_ <= other.height_ ? height_ : other.height_;
+ }
+
+ void SetToMax(const Class& other) {
+ width_ = width_ >= other.width_ ? width_ : other.width_;
+ height_ = height_ >= other.height_ ? height_ : other.height_;
+ }
+
+ bool IsEmpty() const {
+ return (width_ == 0) || (height_ == 0);
+ }
+
+ protected:
+ SizeBase(Type width, Type height)
+ : width_(width < 0 ? 0 : width),
+ height_(height < 0 ? 0 : height) {
+ }
+
+ // Destructor is intentionally made non virtual and protected.
+ // Do not make this public.
+ ~SizeBase() {}
+
+ private:
+ Type width_;
+ Type height_;
+};
+
+} // namespace gfx
+
+#endif // UI_GFX_SIZE_BASE_H_
diff --git a/chromium/ui/gfx/size_conversions.cc b/chromium/ui/gfx/size_conversions.cc
new file mode 100644
index 00000000000..eacbeb4fc8d
--- /dev/null
+++ b/chromium/ui/gfx/size_conversions.cc
@@ -0,0 +1,30 @@
+// Copyright (c) 2012 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 "ui/gfx/size_conversions.h"
+
+#include "ui/gfx/safe_integer_conversions.h"
+
+namespace gfx {
+
+Size ToFlooredSize(const SizeF& size) {
+ int w = ToFlooredInt(size.width());
+ int h = ToFlooredInt(size.height());
+ return Size(w, h);
+}
+
+Size ToCeiledSize(const SizeF& size) {
+ int w = ToCeiledInt(size.width());
+ int h = ToCeiledInt(size.height());
+ return Size(w, h);
+}
+
+Size ToRoundedSize(const SizeF& size) {
+ int w = ToRoundedInt(size.width());
+ int h = ToRoundedInt(size.height());
+ return Size(w, h);
+}
+
+} // namespace gfx
+
diff --git a/chromium/ui/gfx/size_conversions.h b/chromium/ui/gfx/size_conversions.h
new file mode 100644
index 00000000000..f642c4cc7cf
--- /dev/null
+++ b/chromium/ui/gfx/size_conversions.h
@@ -0,0 +1,24 @@
+// Copyright (c) 2012 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_CONVERSIONS_H_
+#define UI_GFX_SIZE_CONVERSIONS_H_
+
+#include "ui/gfx/size.h"
+#include "ui/gfx/size_f.h"
+
+namespace gfx {
+
+// Returns a Size with each component from the input SizeF floored.
+UI_EXPORT Size ToFlooredSize(const SizeF& size);
+
+// Returns a Size with each component from the input SizeF ceiled.
+UI_EXPORT Size ToCeiledSize(const SizeF& size);
+
+// Returns a Size with each component from the input SizeF rounded.
+UI_EXPORT Size ToRoundedSize(const SizeF& size);
+
+} // namespace gfx
+
+#endif // UI_GFX_SIZE_CONVERSIONS_H_
diff --git a/chromium/ui/gfx/size_f.cc b/chromium/ui/gfx/size_f.cc
new file mode 100644
index 00000000000..6eba8849b22
--- /dev/null
+++ b/chromium/ui/gfx/size_f.cc
@@ -0,0 +1,23 @@
+// Copyright (c) 2012 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 "ui/gfx/size_f.h"
+
+#include "base/strings/stringprintf.h"
+
+namespace gfx {
+
+template class SizeBase<SizeF, float>;
+
+std::string SizeF::ToString() const {
+ return base::StringPrintf("%fx%f", width(), height());
+}
+
+SizeF ScaleSize(const SizeF& s, float x_scale, float y_scale) {
+ SizeF scaled_s(s);
+ scaled_s.Scale(x_scale, y_scale);
+ return scaled_s;
+}
+
+} // namespace gfx
diff --git a/chromium/ui/gfx/size_f.h b/chromium/ui/gfx/size_f.h
new file mode 100644
index 00000000000..a38d3f6caff
--- /dev/null
+++ b/chromium/ui/gfx/size_f.h
@@ -0,0 +1,54 @@
+// Copyright (c) 2012 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_F_H_
+#define UI_GFX_SIZE_F_H_
+
+#include <string>
+
+#include "base/compiler_specific.h"
+#include "ui/base/ui_export.h"
+#include "ui/gfx/size_base.h"
+
+namespace gfx {
+
+// A floating version of gfx::Size.
+class UI_EXPORT SizeF : public SizeBase<SizeF, float> {
+ public:
+ SizeF() : SizeBase<SizeF, float>(0, 0) {}
+ SizeF(float width, float height) : SizeBase<SizeF, float>(width, height) {}
+ ~SizeF() {}
+
+ void Scale(float scale) {
+ Scale(scale, scale);
+ }
+
+ void Scale(float x_scale, float y_scale) {
+ SetSize(width() * x_scale, height() * y_scale);
+ }
+
+ std::string ToString() const;
+};
+
+inline bool operator==(const SizeF& lhs, const SizeF& rhs) {
+ return lhs.width() == rhs.width() && lhs.height() == rhs.height();
+}
+
+inline bool operator!=(const SizeF& lhs, const SizeF& rhs) {
+ return !(lhs == rhs);
+}
+
+UI_EXPORT SizeF ScaleSize(const SizeF& p, float x_scale, float y_scale);
+
+inline SizeF ScaleSize(const SizeF& p, float scale) {
+ return ScaleSize(p, scale, scale);
+}
+
+#if !defined(COMPILER_MSVC)
+extern template class SizeBase<SizeF, float>;
+#endif
+
+} // namespace gfx
+
+#endif // UI_GFX_SIZE_F_H_
diff --git a/chromium/ui/gfx/size_unittest.cc b/chromium/ui/gfx/size_unittest.cc
new file mode 100644
index 00000000000..9f109b3f04b
--- /dev/null
+++ b/chromium/ui/gfx/size_unittest.cc
@@ -0,0 +1,128 @@
+// Copyright (c) 2012 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 "ui/gfx/size_base.h"
+
+#include "testing/gtest/include/gtest/gtest.h"
+#include "ui/gfx/size.h"
+#include "ui/gfx/size_conversions.h"
+#include "ui/gfx/size_f.h"
+
+namespace gfx {
+
+namespace {
+
+int TestSizeF(const SizeF& s) {
+ return s.width();
+}
+
+} // namespace
+
+TEST(SizeTest, ToSizeF) {
+ // Check that implicit conversion from integer to float compiles.
+ Size a(10, 20);
+ float width = TestSizeF(a);
+ EXPECT_EQ(width, a.width());
+
+ SizeF b(10, 20);
+
+ EXPECT_EQ(a, b);
+ EXPECT_EQ(b, a);
+}
+
+TEST(SizeTest, ToFlooredSize) {
+ EXPECT_EQ(Size(0, 0), ToFlooredSize(SizeF(0, 0)));
+ EXPECT_EQ(Size(0, 0), ToFlooredSize(SizeF(0.0001f, 0.0001f)));
+ EXPECT_EQ(Size(0, 0), ToFlooredSize(SizeF(0.4999f, 0.4999f)));
+ EXPECT_EQ(Size(0, 0), ToFlooredSize(SizeF(0.5f, 0.5f)));
+ EXPECT_EQ(Size(0, 0), ToFlooredSize(SizeF(0.9999f, 0.9999f)));
+
+ EXPECT_EQ(Size(10, 10), ToFlooredSize(SizeF(10, 10)));
+ EXPECT_EQ(Size(10, 10), ToFlooredSize(SizeF(10.0001f, 10.0001f)));
+ EXPECT_EQ(Size(10, 10), ToFlooredSize(SizeF(10.4999f, 10.4999f)));
+ EXPECT_EQ(Size(10, 10), ToFlooredSize(SizeF(10.5f, 10.5f)));
+ EXPECT_EQ(Size(10, 10), ToFlooredSize(SizeF(10.9999f, 10.9999f)));
+}
+
+TEST(SizeTest, ToCeiledSize) {
+ EXPECT_EQ(Size(0, 0), ToCeiledSize(SizeF(0, 0)));
+ EXPECT_EQ(Size(1, 1), ToCeiledSize(SizeF(0.0001f, 0.0001f)));
+ EXPECT_EQ(Size(1, 1), ToCeiledSize(SizeF(0.4999f, 0.4999f)));
+ EXPECT_EQ(Size(1, 1), ToCeiledSize(SizeF(0.5f, 0.5f)));
+ EXPECT_EQ(Size(1, 1), ToCeiledSize(SizeF(0.9999f, 0.9999f)));
+
+ EXPECT_EQ(Size(10, 10), ToCeiledSize(SizeF(10, 10)));
+ EXPECT_EQ(Size(11, 11), ToCeiledSize(SizeF(10.0001f, 10.0001f)));
+ EXPECT_EQ(Size(11, 11), ToCeiledSize(SizeF(10.4999f, 10.4999f)));
+ EXPECT_EQ(Size(11, 11), ToCeiledSize(SizeF(10.5f, 10.5f)));
+ EXPECT_EQ(Size(11, 11), ToCeiledSize(SizeF(10.9999f, 10.9999f)));
+}
+
+TEST(SizeTest, ToRoundedSize) {
+ EXPECT_EQ(Size(0, 0), ToRoundedSize(SizeF(0, 0)));
+ EXPECT_EQ(Size(0, 0), ToRoundedSize(SizeF(0.0001f, 0.0001f)));
+ EXPECT_EQ(Size(0, 0), ToRoundedSize(SizeF(0.4999f, 0.4999f)));
+ EXPECT_EQ(Size(1, 1), ToRoundedSize(SizeF(0.5f, 0.5f)));
+ EXPECT_EQ(Size(1, 1), ToRoundedSize(SizeF(0.9999f, 0.9999f)));
+
+ EXPECT_EQ(Size(10, 10), ToRoundedSize(SizeF(10, 10)));
+ EXPECT_EQ(Size(10, 10), ToRoundedSize(SizeF(10.0001f, 10.0001f)));
+ EXPECT_EQ(Size(10, 10), ToRoundedSize(SizeF(10.4999f, 10.4999f)));
+ EXPECT_EQ(Size(11, 11), ToRoundedSize(SizeF(10.5f, 10.5f)));
+ EXPECT_EQ(Size(11, 11), ToRoundedSize(SizeF(10.9999f, 10.9999f)));
+}
+
+TEST(SizeTest, ClampSize) {
+ Size a;
+
+ a = Size(3, 5);
+ EXPECT_EQ(Size(3, 5).ToString(), a.ToString());
+ a.SetToMax(Size(2, 4));
+ EXPECT_EQ(Size(3, 5).ToString(), a.ToString());
+ a.SetToMax(Size(3, 5));
+ EXPECT_EQ(Size(3, 5).ToString(), a.ToString());
+ a.SetToMax(Size(4, 2));
+ EXPECT_EQ(Size(4, 5).ToString(), a.ToString());
+ a.SetToMax(Size(8, 10));
+ EXPECT_EQ(Size(8, 10).ToString(), a.ToString());
+
+ a.SetToMin(Size(9, 11));
+ EXPECT_EQ(Size(8, 10).ToString(), a.ToString());
+ a.SetToMin(Size(8, 10));
+ EXPECT_EQ(Size(8, 10).ToString(), a.ToString());
+ a.SetToMin(Size(11, 9));
+ EXPECT_EQ(Size(8, 9).ToString(), a.ToString());
+ a.SetToMin(Size(7, 11));
+ EXPECT_EQ(Size(7, 9).ToString(), a.ToString());
+ a.SetToMin(Size(3, 5));
+ EXPECT_EQ(Size(3, 5).ToString(), a.ToString());
+}
+
+TEST(SizeTest, ClampSizeF) {
+ SizeF a;
+
+ a = SizeF(3.5f, 5.5f);
+ EXPECT_EQ(SizeF(3.5f, 5.5f).ToString(), a.ToString());
+ a.SetToMax(SizeF(2.5f, 4.5f));
+ EXPECT_EQ(SizeF(3.5f, 5.5f).ToString(), a.ToString());
+ a.SetToMax(SizeF(3.5f, 5.5f));
+ EXPECT_EQ(SizeF(3.5f, 5.5f).ToString(), a.ToString());
+ a.SetToMax(SizeF(4.5f, 2.5f));
+ EXPECT_EQ(SizeF(4.5f, 5.5f).ToString(), a.ToString());
+ a.SetToMax(SizeF(8.5f, 10.5f));
+ EXPECT_EQ(SizeF(8.5f, 10.5f).ToString(), a.ToString());
+
+ a.SetToMin(SizeF(9.5f, 11.5f));
+ EXPECT_EQ(SizeF(8.5f, 10.5f).ToString(), a.ToString());
+ a.SetToMin(SizeF(8.5f, 10.5f));
+ EXPECT_EQ(SizeF(8.5f, 10.5f).ToString(), a.ToString());
+ a.SetToMin(SizeF(11.5f, 9.5f));
+ EXPECT_EQ(SizeF(8.5f, 9.5f).ToString(), a.ToString());
+ a.SetToMin(SizeF(7.5f, 11.5f));
+ EXPECT_EQ(SizeF(7.5f, 9.5f).ToString(), a.ToString());
+ a.SetToMin(SizeF(3.5f, 5.5f));
+ EXPECT_EQ(SizeF(3.5f, 5.5f).ToString(), a.ToString());
+}
+
+} // namespace gfx
diff --git a/chromium/ui/gfx/skbitmap_operations.cc b/chromium/ui/gfx/skbitmap_operations.cc
new file mode 100644
index 00000000000..bcc27238d57
--- /dev/null
+++ b/chromium/ui/gfx/skbitmap_operations.cc
@@ -0,0 +1,851 @@
+// Copyright (c) 2012 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 "ui/gfx/skbitmap_operations.h"
+
+#include <algorithm>
+#include <string.h>
+
+#include "base/logging.h"
+#include "skia/ext/refptr.h"
+#include "third_party/skia/include/core/SkBitmap.h"
+#include "third_party/skia/include/core/SkCanvas.h"
+#include "third_party/skia/include/core/SkColorFilter.h"
+#include "third_party/skia/include/core/SkColorPriv.h"
+#include "third_party/skia/include/core/SkUnPreMultiply.h"
+#include "third_party/skia/include/effects/SkBlurImageFilter.h"
+#include "ui/gfx/insets.h"
+#include "ui/gfx/point.h"
+#include "ui/gfx/size.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]);
+ SkColor alpha_pixel = SkUnPreMultiply::PMColorToColor(alpha_row[x]);
+ int alpha = SkAlphaMul(SkColorGetA(rgb_pixel),
+ SkAlpha255To256(SkColorGetA(alpha_pixel)));
+ int alpha_256 = SkAlpha255To256(alpha);
+ dst_row[x] = SkColorSetARGB(alpha,
+ SkAlphaMul(SkColorGetR(rgb_pixel), alpha_256),
+ SkAlphaMul(SkColorGetG(rgb_pixel), alpha_256),
+ SkAlphaMul(SkColorGetB(rgb_pixel),
+ alpha_256));
+ }
+ }
+
+ 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)(const 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(const 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(const 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(const 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(const 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(const 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(const 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(const 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,
+ const 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.config() == 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);
+
+ const int resultLastX = result.width() - 1;
+ const int srcLastX = bitmap.width() - 1;
+
+ for (int dest_y = 0; dest_y < result.height(); ++dest_y) {
+ const int src_y = dest_y << 1;
+ const SkPMColor* SK_RESTRICT cur_src0 = bitmap.getAddr32(0, src_y);
+ const SkPMColor* SK_RESTRICT cur_src1 = cur_src0;
+ if (src_y + 1 < bitmap.height())
+ cur_src1 = bitmap.getAddr32(0, src_y + 1);
+
+ SkPMColor* SK_RESTRICT cur_dst = result.getAddr32(0, dest_y);
+
+ for (int dest_x = 0; dest_x <= resultLastX; ++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 bump_x = (dest_x << 1) < srcLastX;
+ SkPMColor tmp, ag, rb;
+
+ // Top left pixel of the 2x2 block.
+ tmp = cur_src0[0];
+ ag = (tmp >> 8) & 0xFF00FF;
+ rb = tmp & 0xFF00FF;
+
+ // Top right pixel of the 2x2 block.
+ tmp = cur_src0[bump_x];
+ ag += (tmp >> 8) & 0xFF00FF;
+ rb += tmp & 0xFF00FF;
+
+ // Bottom left pixel of the 2x2 block.
+ tmp = cur_src1[0];
+ ag += (tmp >> 8) & 0xFF00FF;
+ rb += tmp & 0xFF00FF;
+
+ // Bottom right pixel of the 2x2 block.
+ tmp = cur_src1[bump_x];
+ 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.
+ *cur_dst++ = ((rb >> 2) & 0xFF00FF) | ((ag << 6) & 0xFF00FF00);
+
+ cur_src0 += 2;
+ cur_src1 += 2;
+ }
+ }
+
+ 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::CreateTransposedBitmap(const SkBitmap& image) {
+ DCHECK(image.config() == SkBitmap::kARGB_8888_Config);
+
+ SkBitmap transposed;
+ transposed.setConfig(
+ SkBitmap::kARGB_8888_Config, image.height(), image.width(), 0);
+ transposed.allocPixels();
+
+ SkAutoLockPixels lock_image(image);
+ SkAutoLockPixels lock_transposed(transposed);
+
+ 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;
+}
+
+// static
+SkBitmap SkBitmapOperations::CreateColorMask(const SkBitmap& bitmap,
+ SkColor c) {
+ DCHECK(bitmap.config() == SkBitmap::kARGB_8888_Config);
+
+ SkBitmap color_mask;
+ color_mask.setConfig(SkBitmap::kARGB_8888_Config,
+ bitmap.width(), bitmap.height());
+ color_mask.allocPixels();
+ color_mask.eraseARGB(0, 0, 0, 0);
+
+ SkCanvas canvas(color_mask);
+
+ skia::RefPtr<SkColorFilter> color_filter = skia::AdoptRef(
+ SkColorFilter::CreateModeFilter(c, SkXfermode::kSrcIn_Mode));
+ SkPaint paint;
+ paint.setColorFilter(color_filter.get());
+ canvas.drawBitmap(bitmap, SkIntToScalar(0), SkIntToScalar(0), &paint);
+ return color_mask;
+}
+
+// static
+SkBitmap SkBitmapOperations::CreateDropShadow(
+ const SkBitmap& bitmap,
+ const gfx::ShadowValues& shadows) {
+ DCHECK(bitmap.config() == SkBitmap::kARGB_8888_Config);
+
+ // Shadow margin insets are negative values because they grow outside.
+ // Negate them here as grow direction is not important and only pixel value
+ // is of interest here.
+ gfx::Insets shadow_margin = -gfx::ShadowValue::GetMargin(shadows);
+
+ SkBitmap image_with_shadow;
+ image_with_shadow.setConfig(SkBitmap::kARGB_8888_Config,
+ bitmap.width() + shadow_margin.width(),
+ bitmap.height() + shadow_margin.height());
+ image_with_shadow.allocPixels();
+ image_with_shadow.eraseARGB(0, 0, 0, 0);
+
+ SkCanvas canvas(image_with_shadow);
+ canvas.translate(SkIntToScalar(shadow_margin.left()),
+ SkIntToScalar(shadow_margin.top()));
+
+ SkPaint paint;
+ for (size_t i = 0; i < shadows.size(); ++i) {
+ const gfx::ShadowValue& shadow = shadows[i];
+ SkBitmap shadow_image = SkBitmapOperations::CreateColorMask(bitmap,
+ shadow.color());
+
+ skia::RefPtr<SkBlurImageFilter> filter =
+ skia::AdoptRef(new SkBlurImageFilter(SkDoubleToScalar(shadow.blur()),
+ SkDoubleToScalar(shadow.blur())));
+ paint.setImageFilter(filter.get());
+
+ canvas.saveLayer(0, &paint);
+ canvas.drawBitmap(shadow_image,
+ SkIntToScalar(shadow.x()),
+ SkIntToScalar(shadow.y()));
+ canvas.restore();
+ }
+
+ canvas.drawBitmap(bitmap, SkIntToScalar(0), SkIntToScalar(0));
+ return image_with_shadow;
+}
+
+// static
+SkBitmap SkBitmapOperations::Rotate(const SkBitmap& source,
+ RotationAmount rotation) {
+ SkBitmap result;
+ SkScalar angle = SkFloatToScalar(0.0f);
+
+ switch (rotation) {
+ case ROTATION_90_CW:
+ angle = SkFloatToScalar(90.0f);
+ result.setConfig(
+ SkBitmap::kARGB_8888_Config, source.height(), source.width());
+ break;
+ case ROTATION_180_CW:
+ angle = SkFloatToScalar(180.0f);
+ result.setConfig(
+ SkBitmap::kARGB_8888_Config, source.width(), source.height());
+ break;
+ case ROTATION_270_CW:
+ angle = SkFloatToScalar(270.0f);
+ result.setConfig(
+ SkBitmap::kARGB_8888_Config, source.height(), source.width());
+ break;
+ }
+ result.allocPixels();
+ SkCanvas canvas(result);
+ canvas.clear(SkColorSetARGB(0, 0, 0, 0));
+
+ canvas.translate(SkFloatToScalar(result.width() * 0.5f),
+ SkFloatToScalar(result.height() * 0.5f));
+ canvas.rotate(angle);
+ canvas.translate(-SkFloatToScalar(source.width() * 0.5f),
+ -SkFloatToScalar(source.height() * 0.5f));
+ canvas.drawBitmap(source, 0, 0);
+ canvas.flush();
+
+ return result;
+}
diff --git a/chromium/ui/gfx/skbitmap_operations.h b/chromium/ui/gfx/skbitmap_operations.h
new file mode 100644
index 00000000000..eb41d0f7090
--- /dev/null
+++ b/chromium/ui/gfx/skbitmap_operations.h
@@ -0,0 +1,130 @@
+// Copyright (c) 2012 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_
+
+#include "base/gtest_prod_util.h"
+#include "ui/base/ui_export.h"
+#include "ui/gfx/color_utils.h"
+#include "ui/gfx/shadow_value.h"
+
+namespace gfx {
+class Point;
+class Size;
+}
+
+class SkBitmap;
+
+class UI_EXPORT SkBitmapOperations {
+ public:
+ // Enum for use in rotating images (must be in 90 degree increments),
+ // see: Rotate.
+ enum RotationAmount {
+ ROTATION_90_CW,
+ ROTATION_180_CW,
+ ROTATION_270_CW,
+ };
+
+ // 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,
+ const 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 CreateTransposedBitmap(const SkBitmap& bitmap);
+
+ // Create a bitmap by combining alpha channel of |bitmap| and color |c|.
+ // The image must use the kARGB_8888_Config config.
+ static SkBitmap CreateColorMask(const SkBitmap& bitmap, SkColor c);
+
+ // Create a bitmap with drop shadow added to |bitmap|. |shadows| defines
+ // the shadows to add. The created bitmap would be padded to have enough space
+ // for shadows and have original bitmap in the center. The image must use the
+ // kARGB_8888_Config config.
+ static SkBitmap CreateDropShadow(const SkBitmap& bitmap,
+ const gfx::ShadowValues& shadows);
+
+ // Rotates the given source bitmap clockwise by the requested amount.
+ static SkBitmap Rotate(const SkBitmap& source, RotationAmount rotation);
+
+ 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/chromium/ui/gfx/skbitmap_operations_unittest.cc b/chromium/ui/gfx/skbitmap_operations_unittest.cc
new file mode 100644
index 00000000000..bfb208cae2a
--- /dev/null
+++ b/chromium/ui/gfx/skbitmap_operations_unittest.cc
@@ -0,0 +1,583 @@
+// Copyright (c) 2012 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 "ui/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/SkCanvas.h"
+#include "third_party/skia/include/core/SkColorPriv.h"
+#include "third_party/skia/include/core/SkRect.h"
+#include "third_party/skia/include/core/SkRegion.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),
+ SkAlpha255To256(SkColorGetA(alpha_pixel)));
+ int alpha_value_256 = SkAlpha255To256(alpha_value);
+ SkColor expected_pixel = SkColorSetARGB(
+ alpha_value,
+ SkAlphaMul(SkColorGetR(src_pixel), alpha_value_256),
+ SkAlphaMul(SkColorGetG(src_pixel), alpha_value_256),
+ SkAlphaMul(SkColorGetB(src_pixel), alpha_value_256));
+
+ EXPECT_EQ(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.getColor(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();
+
+ // Set PMColors into the bitmap
+ *input.getAddr32(0, 0) = SkPackARGB32NoCheck(0x80, 0x00, 0x00, 0x00);
+ *input.getAddr32(1, 0) = SkPackARGB32NoCheck(0x80, 0x80, 0x80, 0x80);
+ *input.getAddr32(0, 1) = SkPackARGB32NoCheck(0xFF, 0x00, 0xCC, 0x88);
+ *input.getAddr32(1, 1) = SkPackARGB32NoCheck(0x00, 0x00, 0xCC, 0x88);
+
+ 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, CreateTransposedBitmap) {
+ 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::CreateTransposedBitmap(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));
+ }
+ }
+}
+
+// Check that Rotate provides the desired results
+TEST(SkBitmapOperationsTest, RotateImage) {
+ const int src_w = 6, src_h = 4;
+ SkBitmap src;
+ // Create a simple 4 color bitmap:
+ // RRRBBB
+ // RRRBBB
+ // GGGYYY
+ // GGGYYY
+ src.setConfig(SkBitmap::kARGB_8888_Config, src_w, src_h);
+ src.allocPixels();
+
+ SkCanvas canvas(src);
+ src.eraseARGB(0, 0, 0, 0);
+ SkRegion region;
+
+ region.setRect(0, 0, src_w / 2, src_h / 2);
+ canvas.setClipRegion(region);
+ // This region is a semi-transparent red to test non-opaque pixels.
+ canvas.drawColor(0x1FFF0000, SkXfermode::kSrc_Mode);
+ region.setRect(src_w / 2, 0, src_w, src_h / 2);
+ canvas.setClipRegion(region);
+ canvas.drawColor(SK_ColorBLUE, SkXfermode::kSrc_Mode);
+ region.setRect(0, src_h / 2, src_w / 2, src_h);
+ canvas.setClipRegion(region);
+ canvas.drawColor(SK_ColorGREEN, SkXfermode::kSrc_Mode);
+ region.setRect(src_w / 2, src_h / 2, src_w, src_h);
+ canvas.setClipRegion(region);
+ canvas.drawColor(SK_ColorYELLOW, SkXfermode::kSrc_Mode);
+ canvas.flush();
+
+ SkBitmap rotate90, rotate180, rotate270;
+ rotate90 = SkBitmapOperations::Rotate(src,
+ SkBitmapOperations::ROTATION_90_CW);
+ rotate180 = SkBitmapOperations::Rotate(src,
+ SkBitmapOperations::ROTATION_180_CW);
+ rotate270 = SkBitmapOperations::Rotate(src,
+ SkBitmapOperations::ROTATION_270_CW);
+
+ ASSERT_EQ(rotate90.width(), src.height());
+ ASSERT_EQ(rotate90.height(), src.width());
+ ASSERT_EQ(rotate180.width(), src.width());
+ ASSERT_EQ(rotate180.height(), src.height());
+ ASSERT_EQ(rotate270.width(), src.height());
+ ASSERT_EQ(rotate270.height(), src.width());
+
+ SkAutoLockPixels lock_src(src);
+ SkAutoLockPixels lock_90(rotate90);
+ SkAutoLockPixels lock_180(rotate180);
+ SkAutoLockPixels lock_270(rotate270);
+
+ for (int x=0; x < src_w; ++x) {
+ for (int y=0; y < src_h; ++y) {
+ ASSERT_EQ(*src.getAddr32(x,y), *rotate90.getAddr32(src_h - (y+1),x));
+ ASSERT_EQ(*src.getAddr32(x,y), *rotate270.getAddr32(y, src_w - (x+1)));
+ ASSERT_EQ(*src.getAddr32(x,y),
+ *rotate180.getAddr32(src_w - (x+1), src_h - (y+1)));
+ }
+ }
+}
diff --git a/chromium/ui/gfx/skia_util.cc b/chromium/ui/gfx/skia_util.cc
new file mode 100644
index 00000000000..50092ab13b6
--- /dev/null
+++ b/chromium/ui/gfx/skia_util.cc
@@ -0,0 +1,184 @@
+// Copyright (c) 2012 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 "ui/gfx/skia_util.h"
+
+#include "third_party/skia/include/core/SkBitmap.h"
+#include "third_party/skia/include/core/SkColorFilter.h"
+#include "third_party/skia/include/core/SkColorPriv.h"
+#include "third_party/skia/include/core/SkUnPreMultiply.h"
+#include "third_party/skia/include/effects/SkBlurMaskFilter.h"
+#include "third_party/skia/include/effects/SkGradientShader.h"
+#include "third_party/skia/include/effects/SkLayerDrawLooper.h"
+#include "ui/gfx/image/image_skia_rep.h"
+#include "ui/gfx/rect.h"
+#include "ui/gfx/rect_f.h"
+#include "ui/gfx/shadow_value.h"
+#include "ui/gfx/transform.h"
+
+namespace gfx {
+
+SkRect RectToSkRect(const Rect& rect) {
+ SkRect r;
+ r.iset(rect.x(), rect.y(), rect.right(), rect.bottom());
+ return r;
+}
+
+SkIRect RectToSkIRect(const Rect& rect) {
+ return SkIRect::MakeXYWH(rect.x(), rect.y(), rect.width(), rect.height());
+}
+
+Rect SkIRectToRect(const SkIRect& rect) {
+ return Rect(rect.x(), rect.y(), rect.width(), rect.height());
+}
+
+SkRect RectFToSkRect(const RectF& rect) {
+ return SkRect::MakeXYWH(SkFloatToScalar(rect.x()),
+ SkFloatToScalar(rect.y()),
+ SkFloatToScalar(rect.width()),
+ SkFloatToScalar(rect.height()));
+}
+
+RectF SkRectToRectF(const SkRect& rect) {
+ return RectF(SkScalarToFloat(rect.x()),
+ SkScalarToFloat(rect.y()),
+ SkScalarToFloat(rect.width()),
+ SkScalarToFloat(rect.height()));
+}
+
+void TransformToFlattenedSkMatrix(const gfx::Transform& transform,
+ SkMatrix* flattened) {
+ // Convert from 4x4 to 3x3 by dropping the third row and column.
+ flattened->set(0, SkDoubleToScalar(transform.matrix().getDouble(0, 0)));
+ flattened->set(1, SkDoubleToScalar(transform.matrix().getDouble(0, 1)));
+ flattened->set(2, SkDoubleToScalar(transform.matrix().getDouble(0, 3)));
+ flattened->set(3, SkDoubleToScalar(transform.matrix().getDouble(1, 0)));
+ flattened->set(4, SkDoubleToScalar(transform.matrix().getDouble(1, 1)));
+ flattened->set(5, SkDoubleToScalar(transform.matrix().getDouble(1, 3)));
+ flattened->set(6, SkDoubleToScalar(transform.matrix().getDouble(3, 0)));
+ flattened->set(7, SkDoubleToScalar(transform.matrix().getDouble(3, 1)));
+ flattened->set(8, SkDoubleToScalar(transform.matrix().getDouble(3, 3)));
+}
+
+skia::RefPtr<SkShader> CreateImageRepShader(const gfx::ImageSkiaRep& image_rep,
+ SkShader::TileMode tile_mode,
+ const SkMatrix& local_matrix) {
+ skia::RefPtr<SkShader> shader = skia::AdoptRef(SkShader::CreateBitmapShader(
+ image_rep.sk_bitmap(), tile_mode, tile_mode));
+ SkScalar scale_x = local_matrix.getScaleX();
+ SkScalar scale_y = local_matrix.getScaleY();
+ SkScalar bitmap_scale = SkFloatToScalar(image_rep.GetScale());
+
+ // Unscale matrix by |bitmap_scale| such that the bitmap is drawn at the
+ // correct density.
+ // Convert skew and translation to pixel coordinates.
+ // Thus, for |bitmap_scale| = 2:
+ // x scale = 2, x translation = 1 DIP,
+ // should be converted to
+ // x scale = 1, x translation = 2 pixels.
+ SkMatrix shader_scale = local_matrix;
+ shader_scale.preScale(bitmap_scale, bitmap_scale);
+ shader_scale.setScaleX(SkScalarDiv(scale_x, bitmap_scale));
+ shader_scale.setScaleY(SkScalarDiv(scale_y, bitmap_scale));
+
+ shader->setLocalMatrix(shader_scale);
+ return shader;
+}
+
+skia::RefPtr<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].iset(0, start_point);
+ grad_points[1].iset(0, end_point);
+
+ return skia::AdoptRef(SkGradientShader::CreateLinear(
+ grad_points, grad_colors, NULL, 2, SkShader::kRepeat_TileMode));
+}
+
+skia::RefPtr<SkDrawLooper> CreateShadowDrawLooper(
+ const std::vector<ShadowValue>& shadows) {
+ if (shadows.empty())
+ return skia::RefPtr<SkDrawLooper>();
+
+ skia::RefPtr<SkLayerDrawLooper> looper =
+ skia::AdoptRef(new SkLayerDrawLooper);
+
+ looper->addLayer(); // top layer of the original.
+
+ SkLayerDrawLooper::LayerInfo layer_info;
+ layer_info.fPaintBits |= SkLayerDrawLooper::kMaskFilter_Bit;
+ layer_info.fPaintBits |= SkLayerDrawLooper::kColorFilter_Bit;
+ layer_info.fColorMode = SkXfermode::kSrc_Mode;
+
+ for (size_t i = 0; i < shadows.size(); ++i) {
+ const ShadowValue& shadow = shadows[i];
+
+ layer_info.fOffset.set(SkIntToScalar(shadow.x()),
+ SkIntToScalar(shadow.y()));
+
+ // SkBlurMaskFilter's blur radius defines the range to extend the blur from
+ // original mask, which is half of blur amount as defined in ShadowValue.
+ skia::RefPtr<SkMaskFilter> blur_mask = skia::AdoptRef(
+ SkBlurMaskFilter::Create(SkDoubleToScalar(shadow.blur() / 2),
+ SkBlurMaskFilter::kNormal_BlurStyle,
+ SkBlurMaskFilter::kHighQuality_BlurFlag));
+ skia::RefPtr<SkColorFilter> color_filter = skia::AdoptRef(
+ SkColorFilter::CreateModeFilter(shadow.color(),
+ SkXfermode::kSrcIn_Mode));
+
+ SkPaint* paint = looper->addLayer(layer_info);
+ paint->setMaskFilter(blur_mask.get());
+ paint->setColorFilter(color_filter.get());
+ }
+
+ return looper;
+}
+
+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()));
+}
+
+void ConvertSkiaToRGBA(const unsigned char* skia,
+ int pixel_width,
+ unsigned char* rgba) {
+ 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 gfx
diff --git a/chromium/ui/gfx/skia_util.h b/chromium/ui/gfx/skia_util.h
new file mode 100644
index 00000000000..4a2cfcf2035
--- /dev/null
+++ b/chromium/ui/gfx/skia_util.h
@@ -0,0 +1,74 @@
+// Copyright (c) 2012 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_
+
+#include <string>
+#include <vector>
+
+#include "skia/ext/refptr.h"
+#include "third_party/skia/include/core/SkColor.h"
+#include "third_party/skia/include/core/SkRect.h"
+#include "third_party/skia/include/core/SkShader.h"
+#include "ui/base/ui_export.h"
+
+class SkBitmap;
+class SkDrawLooper;
+
+namespace gfx {
+
+class ImageSkiaRep;
+class Rect;
+class RectF;
+class ShadowValue;
+class Transform;
+
+// Convert between Skia and gfx rect types.
+UI_EXPORT SkRect RectToSkRect(const Rect& rect);
+UI_EXPORT SkIRect RectToSkIRect(const Rect& rect);
+UI_EXPORT Rect SkIRectToRect(const SkIRect& rect);
+UI_EXPORT SkRect RectFToSkRect(const RectF& rect);
+UI_EXPORT RectF SkRectToRectF(const SkRect& rect);
+
+UI_EXPORT void TransformToFlattenedSkMatrix(const gfx::Transform& transform,
+ SkMatrix* flattened);
+
+// Creates a bitmap shader for the image rep with the image rep's scale factor.
+// Sets the created shader's local matrix such that it displays the image rep at
+// the correct scale factor.
+// The shader's local matrix should not be changed after the shader is created.
+// TODO(pkotwicz): Allow shader's local matrix to be changed after the shader
+// is created.
+//
+UI_EXPORT skia::RefPtr<SkShader> CreateImageRepShader(
+ const gfx::ImageSkiaRep& image_rep,
+ SkShader::TileMode tile_mode,
+ const SkMatrix& local_matrix);
+
+// Creates a vertical gradient shader. The caller owns the shader.
+// Example usage to avoid leaks:
+UI_EXPORT skia::RefPtr<SkShader> CreateGradientShader(int start_point,
+ int end_point,
+ SkColor start_color,
+ SkColor end_color);
+
+// Creates a draw looper to generate |shadows|. The caller owns the draw looper.
+// NULL is returned if |shadows| is empty since no draw looper is needed in
+// this case.
+UI_EXPORT skia::RefPtr<SkDrawLooper> CreateShadowDrawLooper(
+ const std::vector<ShadowValue>& shadows);
+
+// Returns true if the two bitmaps contain the same pixels.
+UI_EXPORT bool BitmapsAreEqual(const SkBitmap& bitmap1,
+ const SkBitmap& bitmap2);
+
+// Converts Skia ARGB format pixels in |skia| to RGBA.
+UI_EXPORT void ConvertSkiaToRGBA(const unsigned char* skia,
+ int pixel_width,
+ unsigned char* rgba);
+
+} // namespace gfx
+
+#endif // UI_GFX_SKIA_UTIL_H_
diff --git a/chromium/ui/gfx/skia_utils_gtk.cc b/chromium/ui/gfx/skia_utils_gtk.cc
new file mode 100644
index 00000000000..f7f3a0aaf2b
--- /dev/null
+++ b/chromium/ui/gfx/skia_utils_gtk.cc
@@ -0,0 +1,32 @@
+// Copyright (c) 2012 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 "ui/gfx/skia_utils_gtk.h"
+
+#include <gdk/gdk.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,
+ static_cast<guint16>(SkColorGetR(color) * kSkiaToGDKMultiplier),
+ static_cast<guint16>(SkColorGetG(color) * kSkiaToGDKMultiplier),
+ static_cast<guint16>(SkColorGetB(color) * kSkiaToGDKMultiplier)
+ };
+ return gdk_color;
+}
+
+} // namespace gfx
diff --git a/chromium/ui/gfx/skia_utils_gtk.h b/chromium/ui/gfx/skia_utils_gtk.h
new file mode 100644
index 00000000000..6d56e0fab8a
--- /dev/null
+++ b/chromium/ui/gfx/skia_utils_gtk.h
@@ -0,0 +1,23 @@
+// 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_SKIA_UTILS_GTK_H_
+#define UI_GFX_SKIA_UTILS_GTK_H_
+
+#include "third_party/skia/include/core/SkColor.h"
+#include "ui/base/ui_export.h"
+
+typedef struct _GdkColor GdkColor;
+
+namespace gfx {
+
+// Converts GdkColors to the ARGB layout Skia expects.
+UI_EXPORT SkColor GdkColorToSkColor(GdkColor color);
+
+// Converts ARGB to GdkColor.
+UI_EXPORT GdkColor SkColorToGdkColor(SkColor color);
+
+} // namespace gfx
+
+#endif // UI_GFX_SKIA_UTILS_GTK_H_
diff --git a/chromium/ui/gfx/switches.cc b/chromium/ui/gfx/switches.cc
new file mode 100644
index 00000000000..55452c3c7c7
--- /dev/null
+++ b/chromium/ui/gfx/switches.cc
@@ -0,0 +1,21 @@
+// Copyright 2012 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 "ui/gfx/switches.h"
+
+namespace gfx {
+namespace switches {
+
+// Let text glyphs have X-positions that aren't snapped to the pixel grid in
+// the browser UI.
+const char kEnableBrowserTextSubpixelPositioning[] =
+ "enable-browser-text-subpixel-positioning";
+
+// Enable text glyphs to have X-positions that aren't snapped to the pixel grid
+// in webkit renderers.
+const char kEnableWebkitTextSubpixelPositioning[] =
+ "enable-webkit-text-subpixel-positioning";
+
+} // namespace switches
+} // namespace gfx
diff --git a/chromium/ui/gfx/switches.h b/chromium/ui/gfx/switches.h
new file mode 100644
index 00000000000..3bae4521f7d
--- /dev/null
+++ b/chromium/ui/gfx/switches.h
@@ -0,0 +1,19 @@
+// Copyright 2012 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_SWITCHES_H_
+#define UI_GFX_SWITCHES_H_
+
+#include "ui/base/ui_export.h"
+
+namespace gfx {
+namespace switches {
+
+UI_EXPORT extern const char kEnableBrowserTextSubpixelPositioning[];
+UI_EXPORT extern const char kEnableWebkitTextSubpixelPositioning[];
+
+} // namespace switches
+} // namespace gfx
+
+#endif // UI_GFX_SWITCHES_H_
diff --git a/chromium/ui/gfx/sys_color_change_listener.cc b/chromium/ui/gfx/sys_color_change_listener.cc
new file mode 100644
index 00000000000..7a9fc84478f
--- /dev/null
+++ b/chromium/ui/gfx/sys_color_change_listener.cc
@@ -0,0 +1,119 @@
+// Copyright (c) 2012 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 "ui/gfx/color_utils.h"
+#include "ui/gfx/sys_color_change_listener.h"
+
+#if defined(OS_WIN)
+#include <windows.h>
+#endif
+
+#include "base/basictypes.h"
+#include "base/memory/singleton.h"
+#include "base/observer_list.h"
+#if defined(OS_WIN)
+#include "ui/base/win/singleton_hwnd.h"
+#endif
+
+namespace gfx {
+
+namespace {
+
+bool g_is_inverted_color_scheme = false;
+bool g_is_inverted_color_scheme_initialized = false;
+
+void UpdateInvertedColorScheme() {
+#if defined(OS_WIN)
+ int foreground_luminance = color_utils::GetLuminanceForColor(
+ color_utils::GetSysSkColor(COLOR_WINDOWTEXT));
+ int background_luminance = color_utils::GetLuminanceForColor(
+ color_utils::GetSysSkColor(COLOR_WINDOW));
+ HIGHCONTRAST high_contrast = {0};
+ high_contrast.cbSize = sizeof(HIGHCONTRAST);
+ g_is_inverted_color_scheme =
+ SystemParametersInfo(SPI_GETHIGHCONTRAST, 0, &high_contrast, 0) &&
+ ((high_contrast.dwFlags & HCF_HIGHCONTRASTON) != 0) &&
+ foreground_luminance > background_luminance;
+ g_is_inverted_color_scheme_initialized = true;
+#endif
+}
+
+} // namespace
+
+bool IsInvertedColorScheme() {
+ if (!g_is_inverted_color_scheme_initialized)
+ UpdateInvertedColorScheme();
+ return g_is_inverted_color_scheme;
+}
+
+#if defined(OS_WIN)
+class SysColorChangeObserver : public ui::SingletonHwnd::Observer {
+ public:
+ static SysColorChangeObserver* GetInstance();
+
+ void AddListener(SysColorChangeListener* listener);
+ void RemoveListener(SysColorChangeListener* listener);
+
+ private:
+ friend struct DefaultSingletonTraits<SysColorChangeObserver>;
+
+ SysColorChangeObserver();
+ virtual ~SysColorChangeObserver();
+
+ virtual void OnWndProc(HWND hwnd,
+ UINT message,
+ WPARAM wparam,
+ LPARAM lparam) OVERRIDE;
+
+ ObserverList<SysColorChangeListener> listeners_;
+};
+
+// static
+SysColorChangeObserver* SysColorChangeObserver::GetInstance() {
+ return Singleton<SysColorChangeObserver>::get();
+}
+
+SysColorChangeObserver::SysColorChangeObserver() {
+ ui::SingletonHwnd::GetInstance()->AddObserver(this);
+}
+
+SysColorChangeObserver::~SysColorChangeObserver() {
+ ui::SingletonHwnd::GetInstance()->RemoveObserver(this);
+}
+
+void SysColorChangeObserver::AddListener(SysColorChangeListener* listener) {
+ listeners_.AddObserver(listener);
+}
+
+void SysColorChangeObserver::RemoveListener(SysColorChangeListener* listener) {
+ listeners_.RemoveObserver(listener);
+}
+
+void SysColorChangeObserver::OnWndProc(HWND hwnd,
+ UINT message,
+ WPARAM wparam,
+ LPARAM lparam) {
+ if (message == WM_SYSCOLORCHANGE ||
+ (message == WM_SETTINGCHANGE && wparam == SPI_SETHIGHCONTRAST)) {
+ UpdateInvertedColorScheme();
+ FOR_EACH_OBSERVER(SysColorChangeListener, listeners_, OnSysColorChange());
+ }
+}
+#endif
+
+ScopedSysColorChangeListener::ScopedSysColorChangeListener(
+ SysColorChangeListener* listener)
+ : listener_(listener) {
+#if defined(OS_WIN)
+ SysColorChangeObserver::GetInstance()->AddListener(listener_);
+#endif
+}
+
+ScopedSysColorChangeListener::~ScopedSysColorChangeListener() {
+#if defined(OS_WIN)
+ SysColorChangeObserver::GetInstance()->RemoveListener(listener_);
+#endif
+}
+
+} // namespace gfx
diff --git a/chromium/ui/gfx/sys_color_change_listener.h b/chromium/ui/gfx/sys_color_change_listener.h
new file mode 100644
index 00000000000..2d196630b5e
--- /dev/null
+++ b/chromium/ui/gfx/sys_color_change_listener.h
@@ -0,0 +1,44 @@
+// Copyright (c) 2012 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_SYS_COLOR_CHANGE_LISTENER_H_
+#define UI_GFX_SYS_COLOR_CHANGE_LISTENER_H_
+
+#include "base/basictypes.h"
+#include "third_party/skia/include/core/SkColor.h"
+#include "ui/base/ui_export.h"
+
+namespace gfx {
+
+// Returns true only if Chrome should use an inverted color scheme - which is
+// only true if the system has high-contrast mode enabled and and is using a
+// light-on-dark color scheme. To be notified when this status changes, use
+// ScopedSysColorChangeListener, below.
+UI_EXPORT bool IsInvertedColorScheme();
+
+// Interface for classes that want to listen to system color changes.
+class UI_EXPORT SysColorChangeListener {
+ public:
+ virtual void OnSysColorChange() = 0;
+
+ protected:
+ virtual ~SysColorChangeListener() {}
+};
+
+// Create an instance of this class in any object that wants to listen
+// for system color changes.
+class UI_EXPORT ScopedSysColorChangeListener {
+ public:
+ explicit ScopedSysColorChangeListener(SysColorChangeListener* listener);
+ ~ScopedSysColorChangeListener();
+
+ private:
+ SysColorChangeListener* listener_;
+
+ DISALLOW_COPY_AND_ASSIGN(ScopedSysColorChangeListener);
+};
+
+} // namespace gfx;
+
+#endif // UI_GFX_SYS_COLOR_CHANGE_LISTENER_H_
diff --git a/chromium/ui/gfx/text_constants.h b/chromium/ui/gfx/text_constants.h
new file mode 100644
index 00000000000..cafeab4669b
--- /dev/null
+++ b/chromium/ui/gfx/text_constants.h
@@ -0,0 +1,66 @@
+// Copyright (c) 2012 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_TEXT_CONSTANTS_H_
+#define UI_GFX_TEXT_CONSTANTS_H_
+
+namespace gfx {
+
+// TODO(msw): Distinguish between logical character stops and glyph stops?
+// TODO(msw): Merge with base::i18n::BreakIterator::BreakType.
+enum BreakType {
+ // Stop cursor movement on neighboring characters.
+ CHARACTER_BREAK = 0,
+ // Stop cursor movement on nearest word boundaries.
+ WORD_BREAK,
+ // Stop cursor movement on line ends as shown on screen.
+ LINE_BREAK,
+};
+
+// Horizontal text alignment modes.
+enum HorizontalAlignment {
+ // Align the text's left edge with that of its display area.
+ ALIGN_LEFT = 0,
+ // Align the text's center with that of its display area.
+ ALIGN_CENTER,
+ // Align the text's right edge with that of its display area.
+ ALIGN_RIGHT,
+};
+
+// Vertical text alignment modes.
+enum VerticalAlignment {
+ // Align the text's top edge with that of its display area.
+ ALIGN_TOP = 0,
+ // Align the text's center with that of its display area.
+ ALIGN_VCENTER,
+ // Align the text's bottom edge with that of its display area.
+ ALIGN_BOTTOM,
+};
+
+// The directionality modes used to determine the base text direction.
+enum DirectionalityMode {
+ // Use the first strong character's direction.
+ DIRECTIONALITY_FROM_TEXT = 0,
+ // Use the UI locale's text reading direction.
+ DIRECTIONALITY_FROM_UI,
+ // Use LTR regardless of content or UI locale.
+ DIRECTIONALITY_FORCE_LTR,
+ // Use RTL regardless of content or UI locale.
+ DIRECTIONALITY_FORCE_RTL,
+};
+
+// Text styles and adornments.
+// TODO(msw): Merge with gfx::Font::FontStyle.
+enum TextStyle {
+ BOLD = 0,
+ ITALIC,
+ STRIKE,
+ DIAGONAL_STRIKE,
+ UNDERLINE,
+ NUM_TEXT_STYLES,
+};
+
+} // namespace gfx
+
+#endif // UI_GFX_TEXT_CONSTANTS_H_
diff --git a/chromium/ui/gfx/text_utils.cc b/chromium/ui/gfx/text_utils.cc
new file mode 100644
index 00000000000..a31ef3d3cc9
--- /dev/null
+++ b/chromium/ui/gfx/text_utils.cc
@@ -0,0 +1,49 @@
+// Copyright 2012 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 "ui/gfx/text_utils.h"
+
+#include "base/i18n/char_iterator.h"
+
+namespace gfx {
+
+base::string16 RemoveAcceleratorChar(const base::string16& s,
+ base::char16 accelerator_char,
+ int* accelerated_char_pos,
+ int* accelerated_char_span) {
+ bool escaped = false;
+ ptrdiff_t last_char_pos = -1;
+ int last_char_span = 0;
+ base::i18n::UTF16CharIterator chars(&s);
+ base::string16 accelerator_removed;
+
+ accelerator_removed.reserve(s.size());
+ while (!chars.end()) {
+ int32 c = chars.get();
+ int array_pos = chars.array_pos();
+ chars.Advance();
+
+ if (c != accelerator_char || escaped) {
+ int span = chars.array_pos() - array_pos;
+ if (escaped && c != accelerator_char) {
+ last_char_pos = accelerator_removed.size();
+ last_char_span = span;
+ }
+ for (int i = 0; i < span; i++)
+ accelerator_removed.push_back(s[array_pos + i]);
+ escaped = false;
+ } else {
+ escaped = true;
+ }
+ }
+
+ if (accelerated_char_pos)
+ *accelerated_char_pos = last_char_pos;
+ if (accelerated_char_span)
+ *accelerated_char_span = last_char_span;
+
+ return accelerator_removed;
+}
+
+} // namespace gfx
diff --git a/chromium/ui/gfx/text_utils.h b/chromium/ui/gfx/text_utils.h
new file mode 100644
index 00000000000..42bce439731
--- /dev/null
+++ b/chromium/ui/gfx/text_utils.h
@@ -0,0 +1,25 @@
+// Copyright 2012 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_TEXT_UTILS_H_
+#define UI_GFX_TEXT_UTILS_H_
+
+#include "base/strings/string16.h"
+#include "ui/base/ui_export.h"
+
+namespace gfx {
+
+// Strip the accelerator char (typically '&') from a menu string. A double
+// accelerator char ('&&') will be converted to a single char. The out params
+// |accelerated_char_pos| and |accelerated_char_span| will be set to the index
+// and span of the last accelerated character, respectively, or -1 and 0 if
+// there was none.
+UI_EXPORT base::string16 RemoveAcceleratorChar(const base::string16& s,
+ base::char16 accelerator_char,
+ int* accelerated_char_pos,
+ int* accelerated_char_span);
+
+} // namespace gfx
+
+#endif // UI_GFX_TEXT_UTILS_H_
diff --git a/chromium/ui/gfx/text_utils_unittest.cc b/chromium/ui/gfx/text_utils_unittest.cc
new file mode 100644
index 00000000000..1090b388b04
--- /dev/null
+++ b/chromium/ui/gfx/text_utils_unittest.cc
@@ -0,0 +1,62 @@
+// 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 "ui/gfx/text_utils.h"
+
+#include "base/strings/utf_string_conversions.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace gfx {
+namespace {
+
+const char16 kAcceleratorChar = '&';
+
+TEST(TextUtilsTest, RemoveAcceleratorChar) {
+ struct TestData {
+ const char* input;
+ int accelerated_char_pos;
+ int accelerated_char_span;
+ const char* output;
+ } cases[] = {
+ { "", -1, 0, "" },
+ { "&", -1, 0, "" },
+ { "no accelerator", -1, 0, "no accelerator" },
+ { "&one accelerator", 0, 1, "one accelerator" },
+ { "one &accelerator", 4, 1, "one accelerator" },
+ { "one_accelerator&", -1, 0, "one_accelerator" },
+ { "&two &accelerators", 4, 1, "two accelerators" },
+ { "two &accelerators&", 4, 1, "two accelerators" },
+ { "two& &accelerators", 4, 1, "two accelerators" },
+ { "&&escaping", -1, 0, "&escaping" },
+ { "escap&&ing", -1, 0, "escap&ing" },
+ { "escaping&&", -1, 0, "escaping&" },
+ { "&mix&&ed", 0, 1, "mix&ed" },
+ { "&&m&ix&&e&d&", 6, 1, "&mix&ed" },
+ { "&&m&&ix&ed&&", 5, 1, "&m&ixed&" },
+ { "&m&&ix&ed&&", 4, 1, "m&ixed&" },
+ // U+1D49C MATHEMATICAL SCRIPT CAPITAL A, which occupies two |char16|'s.
+ { "&\xF0\x9D\x92\x9C", 0, 2, "\xF0\x9D\x92\x9C" },
+ { "Test&\xF0\x9D\x92\x9Cing", 4, 2, "Test\xF0\x9D\x92\x9Cing" },
+ { "Test\xF0\x9D\x92\x9C&ing", 6, 1, "Test\xF0\x9D\x92\x9Cing" },
+ { "Test&\xF0\x9D\x92\x9C&ing", 6, 1, "Test\xF0\x9D\x92\x9Cing" },
+ { "Test&\xF0\x9D\x92\x9C&&ing", 4, 2, "Test\xF0\x9D\x92\x9C&ing" },
+ { "Test&\xF0\x9D\x92\x9C&\xF0\x9D\x92\x9Cing", 6, 2,
+ "Test\xF0\x9D\x92\x9C\xF0\x9D\x92\x9Cing" },
+ };
+
+ for (size_t i = 0; i < ARRAYSIZE_UNSAFE(cases); ++i) {
+ int accelerated_char_pos;
+ int accelerated_char_span;
+ base::string16 result = RemoveAcceleratorChar(UTF8ToUTF16(cases[i].input),
+ kAcceleratorChar,
+ &accelerated_char_pos,
+ &accelerated_char_span);
+ EXPECT_EQ(result, UTF8ToUTF16(cases[i].output));
+ EXPECT_EQ(accelerated_char_pos, cases[i].accelerated_char_pos);
+ EXPECT_EQ(accelerated_char_span, cases[i].accelerated_char_span);
+ }
+}
+
+} // namespace
+} // namespace gfx
diff --git a/chromium/ui/gfx/transform.cc b/chromium/ui/gfx/transform.cc
new file mode 100644
index 00000000000..3e94f44e21c
--- /dev/null
+++ b/chromium/ui/gfx/transform.cc
@@ -0,0 +1,507 @@
+// Copyright (c) 2012 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.
+
+// MSVC++ requires this to be set before any other includes to get M_PI.
+#define _USE_MATH_DEFINES
+
+#include "ui/gfx/transform.h"
+
+#include <cmath>
+
+#include "base/logging.h"
+#include "base/strings/stringprintf.h"
+#include "ui/gfx/point.h"
+#include "ui/gfx/point3_f.h"
+#include "ui/gfx/rect.h"
+#include "ui/gfx/safe_integer_conversions.h"
+#include "ui/gfx/skia_util.h"
+#include "ui/gfx/transform_util.h"
+#include "ui/gfx/vector3d_f.h"
+
+namespace gfx {
+
+namespace {
+
+// Taken from SkMatrix44.
+const double kEpsilon = 1e-8;
+
+double TanDegrees(double degrees) {
+ double radians = degrees * M_PI / 180;
+ return std::tan(radians);
+}
+
+} // namespace
+
+Transform::Transform(
+ double col1row1, double col2row1, double col3row1, double col4row1,
+ double col1row2, double col2row2, double col3row2, double col4row2,
+ double col1row3, double col2row3, double col3row3, double col4row3,
+ double col1row4, double col2row4, double col3row4, double col4row4)
+ : matrix_(SkMatrix44::kUninitialized_Constructor)
+{
+ matrix_.setDouble(0, 0, col1row1);
+ matrix_.setDouble(1, 0, col1row2);
+ matrix_.setDouble(2, 0, col1row3);
+ matrix_.setDouble(3, 0, col1row4);
+
+ matrix_.setDouble(0, 1, col2row1);
+ matrix_.setDouble(1, 1, col2row2);
+ matrix_.setDouble(2, 1, col2row3);
+ matrix_.setDouble(3, 1, col2row4);
+
+ matrix_.setDouble(0, 2, col3row1);
+ matrix_.setDouble(1, 2, col3row2);
+ matrix_.setDouble(2, 2, col3row3);
+ matrix_.setDouble(3, 2, col3row4);
+
+ matrix_.setDouble(0, 3, col4row1);
+ matrix_.setDouble(1, 3, col4row2);
+ matrix_.setDouble(2, 3, col4row3);
+ matrix_.setDouble(3, 3, col4row4);
+}
+
+Transform::Transform(
+ double col1row1, double col2row1,
+ double col1row2, double col2row2,
+ double x_translation, double y_translation)
+ : matrix_(SkMatrix44::kIdentity_Constructor)
+{
+ matrix_.setDouble(0, 0, col1row1);
+ matrix_.setDouble(1, 0, col1row2);
+ matrix_.setDouble(0, 1, col2row1);
+ matrix_.setDouble(1, 1, col2row2);
+ matrix_.setDouble(0, 3, x_translation);
+ matrix_.setDouble(1, 3, y_translation);
+}
+
+void Transform::RotateAboutXAxis(double degrees) {
+ double radians = degrees * M_PI / 180;
+ double cosTheta = std::cos(radians);
+ double sinTheta = std::sin(radians);
+ if (matrix_.isIdentity()) {
+ matrix_.set3x3(1, 0, 0,
+ 0, cosTheta, sinTheta,
+ 0, -sinTheta, cosTheta);
+ } else {
+ SkMatrix44 rot(SkMatrix44::kUninitialized_Constructor);
+ rot.set3x3(1, 0, 0,
+ 0, cosTheta, sinTheta,
+ 0, -sinTheta, cosTheta);
+ matrix_.preConcat(rot);
+ }
+}
+
+void Transform::RotateAboutYAxis(double degrees) {
+ double radians = degrees * M_PI / 180;
+ double cosTheta = std::cos(radians);
+ double sinTheta = std::sin(radians);
+ if (matrix_.isIdentity()) {
+ // Note carefully the placement of the -sinTheta for rotation about
+ // y-axis is different than rotation about x-axis or z-axis.
+ matrix_.set3x3(cosTheta, 0, -sinTheta,
+ 0, 1, 0,
+ sinTheta, 0, cosTheta);
+ } else {
+ SkMatrix44 rot(SkMatrix44::kUninitialized_Constructor);
+ rot.set3x3(cosTheta, 0, -sinTheta,
+ 0, 1, 0,
+ sinTheta, 0, cosTheta);
+ matrix_.preConcat(rot);
+ }
+}
+
+void Transform::RotateAboutZAxis(double degrees) {
+ double radians = degrees * M_PI / 180;
+ double cosTheta = std::cos(radians);
+ double sinTheta = std::sin(radians);
+ if (matrix_.isIdentity()) {
+ matrix_.set3x3(cosTheta, sinTheta, 0,
+ -sinTheta, cosTheta, 0,
+ 0, 0, 1);
+ } else {
+ SkMatrix44 rot(SkMatrix44::kUninitialized_Constructor);
+ rot.set3x3(cosTheta, sinTheta, 0,
+ -sinTheta, cosTheta, 0,
+ 0, 0, 1);
+ matrix_.preConcat(rot);
+ }
+}
+
+void Transform::RotateAbout(const Vector3dF& axis, double degrees) {
+ if (matrix_.isIdentity()) {
+ matrix_.setRotateDegreesAbout(SkDoubleToMScalar(axis.x()),
+ SkDoubleToMScalar(axis.y()),
+ SkDoubleToMScalar(axis.z()),
+ SkDoubleToMScalar(degrees));
+ } else {
+ SkMatrix44 rot(SkMatrix44::kUninitialized_Constructor);
+ rot.setRotateDegreesAbout(SkDoubleToMScalar(axis.x()),
+ SkDoubleToMScalar(axis.y()),
+ SkDoubleToMScalar(axis.z()),
+ SkDoubleToMScalar(degrees));
+ matrix_.preConcat(rot);
+ }
+}
+
+void Transform::Scale(double x, double y) {
+ matrix_.preScale(SkDoubleToMScalar(x), SkDoubleToMScalar(y), 1);
+}
+
+void Transform::Scale3d(double x, double y, double z) {
+ matrix_.preScale(SkDoubleToMScalar(x),
+ SkDoubleToMScalar(y),
+ SkDoubleToMScalar(z));
+}
+
+void Transform::Translate(double x, double y) {
+ matrix_.preTranslate(SkDoubleToMScalar(x), SkDoubleToMScalar(y), 0);
+}
+
+void Transform::Translate3d(double x, double y, double z) {
+ matrix_.preTranslate(SkDoubleToMScalar(x),
+ SkDoubleToMScalar(y),
+ SkDoubleToMScalar(z));
+}
+
+void Transform::SkewX(double angle_x) {
+ if (matrix_.isIdentity())
+ matrix_.setDouble(0, 1, TanDegrees(angle_x));
+ else {
+ SkMatrix44 skew(SkMatrix44::kIdentity_Constructor);
+ skew.setDouble(0, 1, TanDegrees(angle_x));
+ matrix_.preConcat(skew);
+ }
+}
+
+void Transform::SkewY(double angle_y) {
+ if (matrix_.isIdentity())
+ matrix_.setDouble(1, 0, TanDegrees(angle_y));
+ else {
+ SkMatrix44 skew(SkMatrix44::kIdentity_Constructor);
+ skew.setDouble(1, 0, TanDegrees(angle_y));
+ matrix_.preConcat(skew);
+ }
+}
+
+void Transform::ApplyPerspectiveDepth(double depth) {
+ if (depth == 0)
+ return;
+ if (matrix_.isIdentity())
+ matrix_.setDouble(3, 2, -1.0 / depth);
+ else {
+ SkMatrix44 m(SkMatrix44::kIdentity_Constructor);
+ m.setDouble(3, 2, -1.0 / depth);
+ matrix_.preConcat(m);
+ }
+}
+
+void Transform::PreconcatTransform(const Transform& transform) {
+ matrix_.preConcat(transform.matrix_);
+}
+
+void Transform::ConcatTransform(const Transform& transform) {
+ matrix_.postConcat(transform.matrix_);
+}
+
+bool Transform::IsIdentityOrIntegerTranslation() const {
+ if (!IsIdentityOrTranslation())
+ return false;
+
+ bool no_fractional_translation =
+ static_cast<int>(matrix_.getDouble(0, 3)) == matrix_.getDouble(0, 3) &&
+ static_cast<int>(matrix_.getDouble(1, 3)) == matrix_.getDouble(1, 3) &&
+ static_cast<int>(matrix_.getDouble(2, 3)) == matrix_.getDouble(2, 3);
+
+ return no_fractional_translation;
+}
+
+bool Transform::IsBackFaceVisible() const {
+ // Compute whether a layer with a forward-facing normal of (0, 0, 1, 0)
+ // would have its back face visible after applying the transform.
+ if (matrix_.isIdentity())
+ return false;
+
+ // This is done by transforming the normal and seeing if the resulting z
+ // value is positive or negative. However, note that transforming a normal
+ // actually requires using the inverse-transpose of the original transform.
+ //
+ // We can avoid inverting and transposing the matrix since we know we want
+ // to transform only the specific normal vector (0, 0, 1, 0). In this case,
+ // we only need the 3rd row, 3rd column of the inverse-transpose. We can
+ // calculate only the 3rd row 3rd column element of the inverse, skipping
+ // everything else.
+ //
+ // For more information, refer to:
+ // http://en.wikipedia.org/wiki/Invertible_matrix#Analytic_solution
+ //
+
+ double determinant = matrix_.determinant();
+
+ // If matrix was not invertible, then just assume back face is not visible.
+ if (std::abs(determinant) <= kEpsilon)
+ return false;
+
+ // Compute the cofactor of the 3rd row, 3rd column.
+ double cofactor_part_1 =
+ matrix_.getDouble(0, 0) *
+ matrix_.getDouble(1, 1) *
+ matrix_.getDouble(3, 3);
+
+ double cofactor_part_2 =
+ matrix_.getDouble(0, 1) *
+ matrix_.getDouble(1, 3) *
+ matrix_.getDouble(3, 0);
+
+ double cofactor_part_3 =
+ matrix_.getDouble(0, 3) *
+ matrix_.getDouble(1, 0) *
+ matrix_.getDouble(3, 1);
+
+ double cofactor_part_4 =
+ matrix_.getDouble(0, 0) *
+ matrix_.getDouble(1, 3) *
+ matrix_.getDouble(3, 1);
+
+ double cofactor_part_5 =
+ matrix_.getDouble(0, 1) *
+ matrix_.getDouble(1, 0) *
+ matrix_.getDouble(3, 3);
+
+ double cofactor_part_6 =
+ matrix_.getDouble(0, 3) *
+ matrix_.getDouble(1, 1) *
+ matrix_.getDouble(3, 0);
+
+ double cofactor33 =
+ cofactor_part_1 +
+ cofactor_part_2 +
+ cofactor_part_3 -
+ cofactor_part_4 -
+ cofactor_part_5 -
+ cofactor_part_6;
+
+ // Technically the transformed z component is cofactor33 / determinant. But
+ // we can avoid the costly division because we only care about the resulting
+ // +/- sign; we can check this equivalently by multiplication.
+ return cofactor33 * determinant < 0;
+}
+
+bool Transform::GetInverse(Transform* transform) const {
+ if (!matrix_.invert(&transform->matrix_)) {
+ // Initialize the return value to identity if this matrix turned
+ // out to be un-invertible.
+ transform->MakeIdentity();
+ return false;
+ }
+
+ return true;
+}
+
+bool Transform::Preserves2dAxisAlignment() const {
+ // Check whether an axis aligned 2-dimensional rect would remain axis-aligned
+ // after being transformed by this matrix (and implicitly projected by
+ // dropping any non-zero z-values).
+ //
+ // The 4th column can be ignored because translations don't affect axis
+ // alignment. The 3rd column can be ignored because we are assuming 2d
+ // inputs, where z-values will be zero. The 3rd row can also be ignored
+ // because we are assuming 2d outputs, and any resulting z-value is dropped
+ // anyway. For the inner 2x2 portion, the only effects that keep a rect axis
+ // aligned are (1) swapping axes and (2) scaling axes. This can be checked by
+ // verifying only 1 element of every column and row is non-zero. Degenerate
+ // cases that project the x or y dimension to zero are considered to preserve
+ // axis alignment.
+ //
+ // If the matrix does have perspective component that is affected by x or y
+ // values: The current implementation conservatively assumes that axis
+ // alignment is not preserved.
+
+ bool has_x_or_y_perspective = matrix_.getDouble(3, 0) != 0 ||
+ matrix_.getDouble(3, 1) != 0;
+
+ int num_non_zero_in_row_0 = 0;
+ int num_non_zero_in_row_1 = 0;
+ int num_non_zero_in_col_0 = 0;
+ int num_non_zero_in_col_1 = 0;
+
+ if (std::abs(matrix_.getDouble(0, 0)) > kEpsilon) {
+ num_non_zero_in_row_0++;
+ num_non_zero_in_col_0++;
+ }
+
+ if (std::abs(matrix_.getDouble(0, 1)) > kEpsilon) {
+ num_non_zero_in_row_0++;
+ num_non_zero_in_col_1++;
+ }
+
+ if (std::abs(matrix_.getDouble(1, 0)) > kEpsilon) {
+ num_non_zero_in_row_1++;
+ num_non_zero_in_col_0++;
+ }
+
+ if (std::abs(matrix_.getDouble(1, 1)) > kEpsilon) {
+ num_non_zero_in_row_1++;
+ num_non_zero_in_col_1++;
+ }
+
+ return
+ num_non_zero_in_row_0 <= 1 &&
+ num_non_zero_in_row_1 <= 1 &&
+ num_non_zero_in_col_0 <= 1 &&
+ num_non_zero_in_col_1 <= 1 &&
+ !has_x_or_y_perspective;
+}
+
+void Transform::Transpose() {
+ matrix_.transpose();
+}
+
+void Transform::FlattenTo2d() {
+ matrix_.setDouble(2, 0, 0.0);
+ matrix_.setDouble(2, 1, 0.0);
+ matrix_.setDouble(0, 2, 0.0);
+ matrix_.setDouble(1, 2, 0.0);
+ matrix_.setDouble(2, 2, 1.0);
+ matrix_.setDouble(3, 2, 0.0);
+ matrix_.setDouble(2, 3, 0.0);
+}
+
+Vector2dF Transform::To2dTranslation() const {
+ DCHECK(IsIdentityOrTranslation());
+ // Ensure that this translation is truly 2d.
+ const double translate_z = matrix_.getDouble(2, 3);
+ DCHECK_EQ(0.0, translate_z);
+ return gfx::Vector2dF(matrix_.getDouble(0, 3), matrix_.getDouble(1, 3));
+}
+
+void Transform::TransformPoint(Point& point) const {
+ TransformPointInternal(matrix_, point);
+}
+
+void Transform::TransformPoint(Point3F& point) const {
+ TransformPointInternal(matrix_, point);
+}
+
+bool Transform::TransformPointReverse(Point& point) const {
+ // TODO(sad): Try to avoid trying to invert the matrix.
+ SkMatrix44 inverse(SkMatrix44::kUninitialized_Constructor);
+ if (!matrix_.invert(&inverse))
+ return false;
+
+ TransformPointInternal(inverse, point);
+ return true;
+}
+
+bool Transform::TransformPointReverse(Point3F& point) const {
+ // TODO(sad): Try to avoid trying to invert the matrix.
+ SkMatrix44 inverse(SkMatrix44::kUninitialized_Constructor);
+ if (!matrix_.invert(&inverse))
+ return false;
+
+ TransformPointInternal(inverse, point);
+ return true;
+}
+
+void Transform::TransformRect(RectF* rect) const {
+ if (matrix_.isIdentity())
+ return;
+
+ SkRect src = RectFToSkRect(*rect);
+ const SkMatrix& matrix = matrix_;
+ matrix.mapRect(&src);
+ *rect = SkRectToRectF(src);
+}
+
+bool Transform::TransformRectReverse(RectF* rect) const {
+ if (matrix_.isIdentity())
+ return true;
+
+ SkMatrix44 inverse(SkMatrix44::kUninitialized_Constructor);
+ if (!matrix_.invert(&inverse))
+ return false;
+
+ const SkMatrix& matrix = inverse;
+ SkRect src = RectFToSkRect(*rect);
+ matrix.mapRect(&src);
+ *rect = SkRectToRectF(src);
+ return true;
+}
+
+bool Transform::Blend(const Transform& from, double progress) {
+ DecomposedTransform to_decomp;
+ DecomposedTransform from_decomp;
+ if (!DecomposeTransform(&to_decomp, *this) ||
+ !DecomposeTransform(&from_decomp, from))
+ return false;
+
+ if (!BlendDecomposedTransforms(&to_decomp, to_decomp, from_decomp, progress))
+ return false;
+
+ matrix_ = ComposeTransform(to_decomp).matrix();
+ return true;
+}
+
+void Transform::TransformPointInternal(const SkMatrix44& xform,
+ Point3F& point) const {
+ if (xform.isIdentity())
+ return;
+
+ SkMScalar p[4] = {
+ SkDoubleToMScalar(point.x()),
+ SkDoubleToMScalar(point.y()),
+ SkDoubleToMScalar(point.z()),
+ SkDoubleToMScalar(1)
+ };
+
+ xform.mapMScalars(p);
+
+ if (p[3] != 1 && abs(p[3]) > 0) {
+ point.SetPoint(p[0] / p[3], p[1] / p[3], p[2]/ p[3]);
+ } else {
+ point.SetPoint(p[0], p[1], p[2]);
+ }
+}
+
+void Transform::TransformPointInternal(const SkMatrix44& xform,
+ Point& point) const {
+ if (xform.isIdentity())
+ return;
+
+ SkMScalar p[4] = {
+ SkDoubleToMScalar(point.x()),
+ SkDoubleToMScalar(point.y()),
+ SkDoubleToMScalar(0),
+ SkDoubleToMScalar(1)
+ };
+
+ xform.mapMScalars(p);
+
+ point.SetPoint(ToRoundedInt(p[0]), ToRoundedInt(p[1]));
+}
+
+std::string Transform::ToString() const {
+ return base::StringPrintf(
+ "[ %+0.4f %+0.4f %+0.4f %+0.4f \n"
+ " %+0.4f %+0.4f %+0.4f %+0.4f \n"
+ " %+0.4f %+0.4f %+0.4f %+0.4f \n"
+ " %+0.4f %+0.4f %+0.4f %+0.4f ]\n",
+ matrix_.getDouble(0, 0),
+ matrix_.getDouble(0, 1),
+ matrix_.getDouble(0, 2),
+ matrix_.getDouble(0, 3),
+ matrix_.getDouble(1, 0),
+ matrix_.getDouble(1, 1),
+ matrix_.getDouble(1, 2),
+ matrix_.getDouble(1, 3),
+ matrix_.getDouble(2, 0),
+ matrix_.getDouble(2, 1),
+ matrix_.getDouble(2, 2),
+ matrix_.getDouble(2, 3),
+ matrix_.getDouble(3, 0),
+ matrix_.getDouble(3, 1),
+ matrix_.getDouble(3, 2),
+ matrix_.getDouble(3, 3));
+}
+
+} // namespace gfx
diff --git a/chromium/ui/gfx/transform.h b/chromium/ui/gfx/transform.h
new file mode 100644
index 00000000000..7a7543a3a51
--- /dev/null
+++ b/chromium/ui/gfx/transform.h
@@ -0,0 +1,238 @@
+// Copyright (c) 2012 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_TRANSFORM_H_
+#define UI_GFX_TRANSFORM_H_
+
+#include <string>
+
+#include "base/compiler_specific.h"
+#include "third_party/skia/include/utils/SkMatrix44.h"
+#include "ui/base/ui_export.h"
+#include "ui/gfx/vector2d_f.h"
+
+namespace gfx {
+
+class RectF;
+class Point;
+class Point3F;
+class Vector3dF;
+
+// 4x4 transformation matrix. Transform is cheap and explicitly allows
+// copy/assign.
+class UI_EXPORT Transform {
+ public:
+
+ enum SkipInitialization {
+ kSkipInitialization
+ };
+
+ Transform() : matrix_(SkMatrix44::kIdentity_Constructor) {}
+
+ // Skips initializing this matrix to avoid overhead, when we know it will be
+ // initialized before use.
+ Transform(SkipInitialization)
+ : matrix_(SkMatrix44::kUninitialized_Constructor) {}
+ Transform(const Transform& rhs) : matrix_(rhs.matrix_) {}
+ // Initialize with the concatenation of lhs * rhs.
+ Transform(const Transform& lhs, const Transform& rhs)
+ : matrix_(lhs.matrix_, rhs.matrix_) {}
+ // Constructs a transform from explicit 16 matrix elements. Elements
+ // should be given in row-major order.
+ Transform(double col1row1, double col2row1, double col3row1, double col4row1,
+ double col1row2, double col2row2, double col3row2, double col4row2,
+ double col1row3, double col2row3, double col3row3, double col4row3,
+ double col1row4, double col2row4, double col3row4, double col4row4);
+ // Constructs a transform from explicit 2d elements. All other matrix
+ // elements remain the same as the corresponding elements of an identity
+ // matrix.
+ Transform(double col1row1, double col2row1,
+ double col1row2, double col2row2,
+ double x_translation, double y_translation);
+ ~Transform() {}
+
+ bool operator==(const Transform& rhs) const { return matrix_ == rhs.matrix_; }
+ bool operator!=(const Transform& rhs) const { return matrix_ != rhs.matrix_; }
+
+ // Resets this transform to the identity transform.
+ void MakeIdentity() { matrix_.setIdentity(); }
+
+ // Applies the current transformation on a 2d rotation and assigns the result
+ // to |this|.
+ void Rotate(double degrees) { RotateAboutZAxis(degrees); }
+
+ // Applies the current transformation on an axis-angle rotation and assigns
+ // the result to |this|.
+ void RotateAboutXAxis(double degrees);
+ void RotateAboutYAxis(double degrees);
+ void RotateAboutZAxis(double degrees);
+ void RotateAbout(const Vector3dF& axis, double degrees);
+
+ // Applies the current transformation on a scaling and assigns the result
+ // to |this|.
+ void Scale(double x, double y);
+ void Scale3d(double x, double y, double z);
+
+ // Applies the current transformation on a translation and assigns the result
+ // to |this|.
+ void Translate(double x, double y);
+ void Translate3d(double x, double y, double z);
+
+ // Applies the current transformation on a skew and assigns the result
+ // to |this|.
+ void SkewX(double angle_x);
+ void SkewY(double angle_y);
+
+ // Applies the current transformation on a perspective transform and assigns
+ // the result to |this|.
+ void ApplyPerspectiveDepth(double depth);
+
+ // Applies a transformation on the current transformation
+ // (i.e. 'this = this * transform;').
+ void PreconcatTransform(const Transform& transform);
+
+ // Applies a transformation on the current transformation
+ // (i.e. 'this = transform * this;').
+ void ConcatTransform(const Transform& transform);
+
+ // Returns true if this is the identity matrix.
+ bool IsIdentity() const { return matrix_.isIdentity(); }
+
+ // Returns true if the matrix is either identity or pure translation.
+ bool IsIdentityOrTranslation() const {
+ return !(matrix_.getType() & ~SkMatrix44::kTranslate_Mask);
+ }
+
+ // Returns true if the matrix is either a positive scale and/or a translation.
+ bool IsPositiveScaleOrTranslation() const {
+ if (!IsScaleOrTranslation())
+ return false;
+ return matrix_.getDouble(0, 0) > 0.0 &&
+ matrix_.getDouble(1, 1) > 0.0 &&
+ matrix_.getDouble(2, 2) > 0.0;
+ }
+
+ // Returns true if the matrix is either identity or pure, non-fractional
+ // translation.
+ bool IsIdentityOrIntegerTranslation() const;
+
+ // Returns true if the matrix is has only scaling and translation components.
+ bool IsScaleOrTranslation() const {
+ int mask = SkMatrix44::kScale_Mask | SkMatrix44::kTranslate_Mask;
+ return (matrix_.getType() & ~mask) == 0;
+ }
+
+ // Returns true if axis-aligned 2d rects will remain axis-aligned after being
+ // transformed by this matrix.
+ bool Preserves2dAxisAlignment() const;
+
+ // Returns true if the matrix has any perspective component that would
+ // change the w-component of a homogeneous point.
+ bool HasPerspective() const {
+ return (matrix_.getType() & SkMatrix44::kPerspective_Mask) != 0;
+ }
+
+ // Returns true if this transform is non-singular.
+ bool IsInvertible() const { return matrix_.invert(NULL); }
+
+ // Returns true if a layer with a forward-facing normal of (0, 0, 1) would
+ // have its back side facing frontwards after applying the transform.
+ bool IsBackFaceVisible() const;
+
+ // Inverts the transform which is passed in. Returns true if successful.
+ bool GetInverse(Transform* transform) const WARN_UNUSED_RESULT;
+
+ // Transposes this transform in place.
+ void Transpose();
+
+ // Set 3rd row and 3rd colum to (0, 0, 1, 0). Note that this flattening
+ // operation is not quite the same as an orthographic projection and is
+ // technically not a linear operation.
+ //
+ // One useful interpretation of doing this operation:
+ // - For x and y values, the new transform behaves effectively like an
+ // orthographic projection was added to the matrix sequence.
+ // - For z values, the new transform overrides any effect that the transform
+ // had on z, and instead it preserves the z value for any points that are
+ // transformed.
+ // - Because of linearity of transforms, this flattened transform also
+ // preserves the effect that any subsequent (multiplied from the right)
+ // transforms would have on z values.
+ //
+ void FlattenTo2d();
+
+ // Returns the translation components of the matrix. It is an error to call
+ // this function if the transform does not represent only a 2d translation.
+ Vector2dF To2dTranslation() const;
+
+ // Applies the transformation on the point. Returns true if the point is
+ // transformed successfully.
+ void TransformPoint(Point3F& point) const;
+
+ // Applies the transformation on the point. Returns true if the point is
+ // transformed successfully. Rounds the result to the nearest point.
+ void TransformPoint(Point& point) const;
+
+ // Applies the reverse transformation on the point. Returns true if the
+ // transformation can be inverted.
+ bool TransformPointReverse(Point3F& point) const;
+
+ // Applies the reverse transformation on the point. Returns true if the
+ // transformation can be inverted. Rounds the result to the nearest point.
+ bool TransformPointReverse(Point& point) const;
+
+ // Applies transformation on the rectangle. Returns true if the transformed
+ // rectangle was axis aligned. If it returns false, rect will be the
+ // smallest axis aligned bounding box containing the transformed rect.
+ void TransformRect(RectF* rect) const;
+
+ // Applies the reverse transformation on the rectangle. Returns true if
+ // the transformed rectangle was axis aligned. If it returns false,
+ // rect will be the smallest axis aligned bounding box containing the
+ // transformed rect.
+ bool TransformRectReverse(RectF* rect) const;
+
+ // Decomposes |this| and |from|, interpolates the decomposed values, and
+ // sets |this| to the reconstituted result. Returns false if either matrix
+ // can't be decomposed. Uses routines described in this spec:
+ // http://www.w3.org/TR/css3-3d-transforms/.
+ //
+ // Note: this call is expensive since we need to decompose the transform. If
+ // you're going to be calling this rapidly (e.g., in an animation) you should
+ // decompose once using gfx::DecomposeTransforms and reuse your
+ // DecomposedTransform.
+ bool Blend(const Transform& from, double progress);
+
+ // Returns |this| * |other|.
+ Transform operator*(const Transform& other) const {
+ return Transform(*this, other);
+ }
+
+ // Sets |this| = |this| * |other|
+ Transform& operator*=(const Transform& other) {
+ PreconcatTransform(other);
+ return *this;
+ }
+
+ // Returns the underlying matrix.
+ const SkMatrix44& matrix() const { return matrix_; }
+ SkMatrix44& matrix() { return matrix_; }
+
+ std::string ToString() const;
+
+ private:
+ void TransformPointInternal(const SkMatrix44& xform,
+ Point& point) const;
+
+ void TransformPointInternal(const SkMatrix44& xform,
+ Point3F& point) const;
+
+ SkMatrix44 matrix_;
+
+ // copy/assign are allowed.
+};
+
+} // namespace gfx
+
+#endif // UI_GFX_TRANSFORM_H_
diff --git a/chromium/ui/gfx/transform_unittest.cc b/chromium/ui/gfx/transform_unittest.cc
new file mode 100644
index 00000000000..95ffa1fe3d7
--- /dev/null
+++ b/chromium/ui/gfx/transform_unittest.cc
@@ -0,0 +1,2509 @@
+// 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.
+
+// MSVC++ requires this to be set before any other includes to get M_PI.
+#define _USE_MATH_DEFINES
+
+#include "ui/gfx/transform.h"
+
+#include <cmath>
+#include <ostream>
+#include <limits>
+
+#include "base/basictypes.h"
+#include "base/logging.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "ui/gfx/point.h"
+#include "ui/gfx/point3_f.h"
+#include "ui/gfx/quad_f.h"
+#include "ui/gfx/transform_util.h"
+#include "ui/gfx/vector3d_f.h"
+
+namespace gfx {
+
+namespace {
+
+#define EXPECT_ROW1_EQ(a, b, c, d, transform) \
+ EXPECT_FLOAT_EQ((a), (transform).matrix().get(0, 0)); \
+ EXPECT_FLOAT_EQ((b), (transform).matrix().get(0, 1)); \
+ EXPECT_FLOAT_EQ((c), (transform).matrix().get(0, 2)); \
+ EXPECT_FLOAT_EQ((d), (transform).matrix().get(0, 3));
+
+#define EXPECT_ROW2_EQ(a, b, c, d, transform) \
+ EXPECT_FLOAT_EQ((a), (transform).matrix().get(1, 0)); \
+ EXPECT_FLOAT_EQ((b), (transform).matrix().get(1, 1)); \
+ EXPECT_FLOAT_EQ((c), (transform).matrix().get(1, 2)); \
+ EXPECT_FLOAT_EQ((d), (transform).matrix().get(1, 3));
+
+#define EXPECT_ROW3_EQ(a, b, c, d, transform) \
+ EXPECT_FLOAT_EQ((a), (transform).matrix().get(2, 0)); \
+ EXPECT_FLOAT_EQ((b), (transform).matrix().get(2, 1)); \
+ EXPECT_FLOAT_EQ((c), (transform).matrix().get(2, 2)); \
+ EXPECT_FLOAT_EQ((d), (transform).matrix().get(2, 3));
+
+#define EXPECT_ROW4_EQ(a, b, c, d, transform) \
+ EXPECT_FLOAT_EQ((a), (transform).matrix().get(3, 0)); \
+ EXPECT_FLOAT_EQ((b), (transform).matrix().get(3, 1)); \
+ EXPECT_FLOAT_EQ((c), (transform).matrix().get(3, 2)); \
+ EXPECT_FLOAT_EQ((d), (transform).matrix().get(3, 3)); \
+
+// Checking float values for equality close to zero is not robust using
+// EXPECT_FLOAT_EQ (see gtest documentation). So, to verify rotation matrices,
+// we must use a looser absolute error threshold in some places.
+#define EXPECT_ROW1_NEAR(a, b, c, d, transform, errorThreshold) \
+ EXPECT_NEAR((a), (transform).matrix().get(0, 0), (errorThreshold)); \
+ EXPECT_NEAR((b), (transform).matrix().get(0, 1), (errorThreshold)); \
+ EXPECT_NEAR((c), (transform).matrix().get(0, 2), (errorThreshold)); \
+ EXPECT_NEAR((d), (transform).matrix().get(0, 3), (errorThreshold));
+
+#define EXPECT_ROW2_NEAR(a, b, c, d, transform, errorThreshold) \
+ EXPECT_NEAR((a), (transform).matrix().get(1, 0), (errorThreshold)); \
+ EXPECT_NEAR((b), (transform).matrix().get(1, 1), (errorThreshold)); \
+ EXPECT_NEAR((c), (transform).matrix().get(1, 2), (errorThreshold)); \
+ EXPECT_NEAR((d), (transform).matrix().get(1, 3), (errorThreshold));
+
+#define EXPECT_ROW3_NEAR(a, b, c, d, transform, errorThreshold) \
+ EXPECT_NEAR((a), (transform).matrix().get(2, 0), (errorThreshold)); \
+ EXPECT_NEAR((b), (transform).matrix().get(2, 1), (errorThreshold)); \
+ EXPECT_NEAR((c), (transform).matrix().get(2, 2), (errorThreshold)); \
+ EXPECT_NEAR((d), (transform).matrix().get(2, 3), (errorThreshold));
+
+bool PointsAreNearlyEqual(const Point3F& lhs,
+ const Point3F& rhs) {
+ float epsilon = 0.0001f;
+ return lhs.SquaredDistanceTo(rhs) < epsilon;
+}
+
+bool MatricesAreNearlyEqual(const Transform& lhs,
+ const Transform& rhs) {
+ float epsilon = 0.0001f;
+ for (int row = 0; row < 4; ++row) {
+ for (int col = 0; col < 4; ++col) {
+ if (std::abs(lhs.matrix().get(row, col) -
+ rhs.matrix().get(row, col)) > epsilon)
+ return false;
+ }
+ }
+ return true;
+}
+
+void InitializeTestMatrix(Transform* transform) {
+ SkMatrix44& matrix = transform->matrix();
+ matrix.setDouble(0, 0, 10.0);
+ matrix.setDouble(1, 0, 11.0);
+ matrix.setDouble(2, 0, 12.0);
+ matrix.setDouble(3, 0, 13.0);
+ matrix.setDouble(0, 1, 14.0);
+ matrix.setDouble(1, 1, 15.0);
+ matrix.setDouble(2, 1, 16.0);
+ matrix.setDouble(3, 1, 17.0);
+ matrix.setDouble(0, 2, 18.0);
+ matrix.setDouble(1, 2, 19.0);
+ matrix.setDouble(2, 2, 20.0);
+ matrix.setDouble(3, 2, 21.0);
+ matrix.setDouble(0, 3, 22.0);
+ matrix.setDouble(1, 3, 23.0);
+ matrix.setDouble(2, 3, 24.0);
+ matrix.setDouble(3, 3, 25.0);
+
+ // Sanity check
+ EXPECT_ROW1_EQ(10.0f, 14.0f, 18.0f, 22.0f, (*transform));
+ EXPECT_ROW2_EQ(11.0f, 15.0f, 19.0f, 23.0f, (*transform));
+ EXPECT_ROW3_EQ(12.0f, 16.0f, 20.0f, 24.0f, (*transform));
+ EXPECT_ROW4_EQ(13.0f, 17.0f, 21.0f, 25.0f, (*transform));
+}
+
+void InitializeTestMatrix2(Transform* transform) {
+ SkMatrix44& matrix = transform->matrix();
+ matrix.setDouble(0, 0, 30.0);
+ matrix.setDouble(1, 0, 31.0);
+ matrix.setDouble(2, 0, 32.0);
+ matrix.setDouble(3, 0, 33.0);
+ matrix.setDouble(0, 1, 34.0);
+ matrix.setDouble(1, 1, 35.0);
+ matrix.setDouble(2, 1, 36.0);
+ matrix.setDouble(3, 1, 37.0);
+ matrix.setDouble(0, 2, 38.0);
+ matrix.setDouble(1, 2, 39.0);
+ matrix.setDouble(2, 2, 40.0);
+ matrix.setDouble(3, 2, 41.0);
+ matrix.setDouble(0, 3, 42.0);
+ matrix.setDouble(1, 3, 43.0);
+ matrix.setDouble(2, 3, 44.0);
+ matrix.setDouble(3, 3, 45.0);
+
+ // Sanity check
+ EXPECT_ROW1_EQ(30.0f, 34.0f, 38.0f, 42.0f, (*transform));
+ EXPECT_ROW2_EQ(31.0f, 35.0f, 39.0f, 43.0f, (*transform));
+ EXPECT_ROW3_EQ(32.0f, 36.0f, 40.0f, 44.0f, (*transform));
+ EXPECT_ROW4_EQ(33.0f, 37.0f, 41.0f, 45.0f, (*transform));
+}
+
+#ifdef SK_MSCALAR_IS_DOUBLE
+#define ERROR_THRESHOLD 1e-14
+#else
+#define ERROR_THRESHOLD 1e-7
+#endif
+#define LOOSE_ERROR_THRESHOLD 1e-7
+
+TEST(XFormTest, Equality) {
+ Transform lhs, rhs, interpolated;
+ rhs.matrix().set3x3(1, 2, 3,
+ 4, 5, 6,
+ 7, 8, 9);
+ interpolated = lhs;
+ for (int i = 0; i <= 100; ++i) {
+ for (int row = 0; row < 4; ++row) {
+ for (int col = 0; col < 4; ++col) {
+ float a = lhs.matrix().get(row, col);
+ float b = rhs.matrix().get(row, col);
+ float t = i / 100.0f;
+ interpolated.matrix().set(row, col, a + (b - a) * t);
+ }
+ }
+ if (i == 100) {
+ EXPECT_TRUE(rhs == interpolated);
+ } else {
+ EXPECT_TRUE(rhs != interpolated);
+ }
+ }
+ lhs = Transform();
+ rhs = Transform();
+ for (int i = 1; i < 100; ++i) {
+ lhs.MakeIdentity();
+ rhs.MakeIdentity();
+ lhs.Translate(i, i);
+ rhs.Translate(-i, -i);
+ EXPECT_TRUE(lhs != rhs);
+ rhs.Translate(2*i, 2*i);
+ EXPECT_TRUE(lhs == rhs);
+ }
+}
+
+TEST(XFormTest, ConcatTranslate) {
+ static const struct TestCase {
+ int x1;
+ int y1;
+ float tx;
+ float ty;
+ int x2;
+ int y2;
+ } test_cases[] = {
+ { 0, 0, 10.0f, 20.0f, 10, 20 },
+ { 0, 0, -10.0f, -20.0f, 0, 0 },
+ { 0, 0, -10.0f, -20.0f, -10, -20 },
+ { 0, 0,
+ std::numeric_limits<float>::quiet_NaN(),
+ std::numeric_limits<float>::quiet_NaN(),
+ 10, 20 },
+ };
+
+ Transform xform;
+ for (size_t i = 0; i < ARRAYSIZE_UNSAFE(test_cases); ++i) {
+ const TestCase& value = test_cases[i];
+ Transform translation;
+ translation.Translate(value.tx, value.ty);
+ xform = translation * xform;
+ Point3F p1(value.x1, value.y1, 0);
+ Point3F p2(value.x2, value.y2, 0);
+ xform.TransformPoint(p1);
+ if (value.tx == value.tx &&
+ value.ty == value.ty) {
+ EXPECT_TRUE(PointsAreNearlyEqual(p1, p2));
+ }
+ }
+}
+
+TEST(XFormTest, ConcatScale) {
+ static const struct TestCase {
+ int before;
+ float scale;
+ int after;
+ } test_cases[] = {
+ { 1, 10.0f, 10 },
+ { 1, .1f, 1 },
+ { 1, 100.0f, 100 },
+ { 1, -1.0f, -100 },
+ { 1, std::numeric_limits<float>::quiet_NaN(), 1 }
+ };
+
+ Transform xform;
+ for (size_t i = 0; i < ARRAYSIZE_UNSAFE(test_cases); ++i) {
+ const TestCase& value = test_cases[i];
+ Transform scale;
+ scale.Scale(value.scale, value.scale);
+ xform = scale * xform;
+ Point3F p1(value.before, value.before, 0);
+ Point3F p2(value.after, value.after, 0);
+ xform.TransformPoint(p1);
+ if (value.scale == value.scale) {
+ EXPECT_TRUE(PointsAreNearlyEqual(p1, p2));
+ }
+ }
+}
+
+TEST(XFormTest, ConcatRotate) {
+ static const struct TestCase {
+ int x1;
+ int y1;
+ float degrees;
+ int x2;
+ int y2;
+ } test_cases[] = {
+ { 1, 0, 90.0f, 0, 1 },
+ { 1, 0, -90.0f, 1, 0 },
+ { 1, 0, 90.0f, 0, 1 },
+ { 1, 0, 360.0f, 0, 1 },
+ { 1, 0, 0.0f, 0, 1 },
+ { 1, 0, std::numeric_limits<float>::quiet_NaN(), 1, 0 }
+ };
+
+ Transform xform;
+ for (size_t i = 0; i < ARRAYSIZE_UNSAFE(test_cases); ++i) {
+ const TestCase& value = test_cases[i];
+ Transform rotation;
+ rotation.Rotate(value.degrees);
+ xform = rotation * xform;
+ Point3F p1(value.x1, value.y1, 0);
+ Point3F p2(value.x2, value.y2, 0);
+ xform.TransformPoint(p1);
+ if (value.degrees == value.degrees) {
+ EXPECT_TRUE(PointsAreNearlyEqual(p1, p2));
+ }
+ }
+}
+
+TEST(XFormTest, SetTranslate) {
+ static const struct TestCase {
+ int x1; int y1;
+ float tx; float ty;
+ int x2; int y2;
+ } test_cases[] = {
+ { 0, 0, 10.0f, 20.0f, 10, 20 },
+ { 10, 20, 10.0f, 20.0f, 20, 40 },
+ { 10, 20, 0.0f, 0.0f, 10, 20 },
+ { 0, 0,
+ std::numeric_limits<float>::quiet_NaN(),
+ std::numeric_limits<float>::quiet_NaN(),
+ 0, 0 }
+ };
+
+ for (size_t i = 0; i < ARRAYSIZE_UNSAFE(test_cases); ++i) {
+ const TestCase& value = test_cases[i];
+ for (int k = 0; k < 3; ++k) {
+ Point3F p0, p1, p2;
+ Transform xform;
+ switch (k) {
+ case 0:
+ p1.SetPoint(value.x1, 0, 0);
+ p2.SetPoint(value.x2, 0, 0);
+ xform.Translate(value.tx, 0.0);
+ break;
+ case 1:
+ p1.SetPoint(0, value.y1, 0);
+ p2.SetPoint(0, value.y2, 0);
+ xform.Translate(0.0, value.ty);
+ break;
+ case 2:
+ p1.SetPoint(value.x1, value.y1, 0);
+ p2.SetPoint(value.x2, value.y2, 0);
+ xform.Translate(value.tx, value.ty);
+ break;
+ }
+ p0 = p1;
+ xform.TransformPoint(p1);
+ if (value.tx == value.tx &&
+ value.ty == value.ty) {
+ EXPECT_TRUE(PointsAreNearlyEqual(p1, p2));
+ xform.TransformPointReverse(p1);
+ EXPECT_TRUE(PointsAreNearlyEqual(p1, p0));
+ }
+ }
+ }
+}
+
+TEST(XFormTest, SetScale) {
+ static const struct TestCase {
+ int before;
+ float s;
+ int after;
+ } test_cases[] = {
+ { 1, 10.0f, 10 },
+ { 1, 1.0f, 1 },
+ { 1, 0.0f, 0 },
+ { 0, 10.0f, 0 },
+ { 1, std::numeric_limits<float>::quiet_NaN(), 0 },
+ };
+
+ for (size_t i = 0; i < ARRAYSIZE_UNSAFE(test_cases); ++i) {
+ const TestCase& value = test_cases[i];
+ for (int k = 0; k < 3; ++k) {
+ Point3F p0, p1, p2;
+ Transform xform;
+ switch (k) {
+ case 0:
+ p1.SetPoint(value.before, 0, 0);
+ p2.SetPoint(value.after, 0, 0);
+ xform.Scale(value.s, 1.0);
+ break;
+ case 1:
+ p1.SetPoint(0, value.before, 0);
+ p2.SetPoint(0, value.after, 0);
+ xform.Scale(1.0, value.s);
+ break;
+ case 2:
+ p1.SetPoint(value.before, value.before, 0);
+ p2.SetPoint(value.after, value.after, 0);
+ xform.Scale(value.s, value.s);
+ break;
+ }
+ p0 = p1;
+ xform.TransformPoint(p1);
+ if (value.s == value.s) {
+ EXPECT_TRUE(PointsAreNearlyEqual(p1, p2));
+ if (value.s != 0.0f) {
+ xform.TransformPointReverse(p1);
+ EXPECT_TRUE(PointsAreNearlyEqual(p1, p0));
+ }
+ }
+ }
+ }
+}
+
+TEST(XFormTest, SetRotate) {
+ static const struct SetRotateCase {
+ int x;
+ int y;
+ float degree;
+ int xprime;
+ int yprime;
+ } set_rotate_cases[] = {
+ { 100, 0, 90.0f, 0, 100 },
+ { 0, 0, 90.0f, 0, 0 },
+ { 0, 100, 90.0f, -100, 0 },
+ { 0, 1, -90.0f, 1, 0 },
+ { 100, 0, 0.0f, 100, 0 },
+ { 0, 0, 0.0f, 0, 0 },
+ { 0, 0, std::numeric_limits<float>::quiet_NaN(), 0, 0 },
+ { 100, 0, 360.0f, 100, 0 }
+ };
+
+ for (size_t i = 0; i < ARRAYSIZE_UNSAFE(set_rotate_cases); ++i) {
+ const SetRotateCase& value = set_rotate_cases[i];
+ Point3F p0;
+ Point3F p1(value.x, value.y, 0);
+ Point3F p2(value.xprime, value.yprime, 0);
+ p0 = p1;
+ Transform xform;
+ xform.Rotate(value.degree);
+ // just want to make sure that we don't crash in the case of NaN.
+ if (value.degree == value.degree) {
+ xform.TransformPoint(p1);
+ EXPECT_TRUE(PointsAreNearlyEqual(p1, p2));
+ xform.TransformPointReverse(p1);
+ EXPECT_TRUE(PointsAreNearlyEqual(p1, p0));
+ }
+ }
+}
+
+// 2D tests
+TEST(XFormTest, ConcatTranslate2D) {
+ static const struct TestCase {
+ int x1;
+ int y1;
+ float tx;
+ float ty;
+ int x2;
+ int y2;
+ } test_cases[] = {
+ { 0, 0, 10.0f, 20.0f, 10, 20},
+ { 0, 0, -10.0f, -20.0f, 0, 0},
+ { 0, 0, -10.0f, -20.0f, -10, -20},
+ { 0, 0,
+ std::numeric_limits<float>::quiet_NaN(),
+ std::numeric_limits<float>::quiet_NaN(),
+ 10, 20},
+ };
+
+ Transform xform;
+ for (size_t i = 0; i < ARRAYSIZE_UNSAFE(test_cases); ++i) {
+ const TestCase& value = test_cases[i];
+ Transform translation;
+ translation.Translate(value.tx, value.ty);
+ xform = translation * xform;
+ Point p1(value.x1, value.y1);
+ Point p2(value.x2, value.y2);
+ xform.TransformPoint(p1);
+ if (value.tx == value.tx &&
+ value.ty == value.ty) {
+ EXPECT_EQ(p1.x(), p2.x());
+ EXPECT_EQ(p1.y(), p2.y());
+ }
+ }
+}
+
+TEST(XFormTest, ConcatScale2D) {
+ static const struct TestCase {
+ int before;
+ float scale;
+ int after;
+ } test_cases[] = {
+ { 1, 10.0f, 10},
+ { 1, .1f, 1},
+ { 1, 100.0f, 100},
+ { 1, -1.0f, -100},
+ { 1, std::numeric_limits<float>::quiet_NaN(), 1}
+ };
+
+ Transform xform;
+ for (size_t i = 0; i < ARRAYSIZE_UNSAFE(test_cases); ++i) {
+ const TestCase& value = test_cases[i];
+ Transform scale;
+ scale.Scale(value.scale, value.scale);
+ xform = scale * xform;
+ Point p1(value.before, value.before);
+ Point p2(value.after, value.after);
+ xform.TransformPoint(p1);
+ if (value.scale == value.scale) {
+ EXPECT_EQ(p1.x(), p2.x());
+ EXPECT_EQ(p1.y(), p2.y());
+ }
+ }
+}
+
+TEST(XFormTest, ConcatRotate2D) {
+ static const struct TestCase {
+ int x1;
+ int y1;
+ float degrees;
+ int x2;
+ int y2;
+ } test_cases[] = {
+ { 1, 0, 90.0f, 0, 1},
+ { 1, 0, -90.0f, 1, 0},
+ { 1, 0, 90.0f, 0, 1},
+ { 1, 0, 360.0f, 0, 1},
+ { 1, 0, 0.0f, 0, 1},
+ { 1, 0, std::numeric_limits<float>::quiet_NaN(), 1, 0}
+ };
+
+ Transform xform;
+ for (size_t i = 0; i < ARRAYSIZE_UNSAFE(test_cases); ++i) {
+ const TestCase& value = test_cases[i];
+ Transform rotation;
+ rotation.Rotate(value.degrees);
+ xform = rotation * xform;
+ Point p1(value.x1, value.y1);
+ Point p2(value.x2, value.y2);
+ xform.TransformPoint(p1);
+ if (value.degrees == value.degrees) {
+ EXPECT_EQ(p1.x(), p2.x());
+ EXPECT_EQ(p1.y(), p2.y());
+ }
+ }
+}
+
+TEST(XFormTest, SetTranslate2D) {
+ static const struct TestCase {
+ int x1; int y1;
+ float tx; float ty;
+ int x2; int y2;
+ } test_cases[] = {
+ { 0, 0, 10.0f, 20.0f, 10, 20},
+ { 10, 20, 10.0f, 20.0f, 20, 40},
+ { 10, 20, 0.0f, 0.0f, 10, 20},
+ { 0, 0,
+ std::numeric_limits<float>::quiet_NaN(),
+ std::numeric_limits<float>::quiet_NaN(),
+ 0, 0}
+ };
+
+ for (size_t i = 0; i < ARRAYSIZE_UNSAFE(test_cases); ++i) {
+ const TestCase& value = test_cases[i];
+ for (int j = -1; j < 2; ++j) {
+ for (int k = 0; k < 3; ++k) {
+ float epsilon = 0.0001f;
+ Point p0, p1, p2;
+ Transform xform;
+ switch (k) {
+ case 0:
+ p1.SetPoint(value.x1, 0);
+ p2.SetPoint(value.x2, 0);
+ xform.Translate(value.tx + j * epsilon, 0.0);
+ break;
+ case 1:
+ p1.SetPoint(0, value.y1);
+ p2.SetPoint(0, value.y2);
+ xform.Translate(0.0, value.ty + j * epsilon);
+ break;
+ case 2:
+ p1.SetPoint(value.x1, value.y1);
+ p2.SetPoint(value.x2, value.y2);
+ xform.Translate(value.tx + j * epsilon,
+ value.ty + j * epsilon);
+ break;
+ }
+ p0 = p1;
+ xform.TransformPoint(p1);
+ if (value.tx == value.tx &&
+ value.ty == value.ty) {
+ EXPECT_EQ(p1.x(), p2.x());
+ EXPECT_EQ(p1.y(), p2.y());
+ xform.TransformPointReverse(p1);
+ EXPECT_EQ(p1.x(), p0.x());
+ EXPECT_EQ(p1.y(), p0.y());
+ }
+ }
+ }
+ }
+}
+
+TEST(XFormTest, SetScale2D) {
+ static const struct TestCase {
+ int before;
+ float s;
+ int after;
+ } test_cases[] = {
+ { 1, 10.0f, 10},
+ { 1, 1.0f, 1},
+ { 1, 0.0f, 0},
+ { 0, 10.0f, 0},
+ { 1, std::numeric_limits<float>::quiet_NaN(), 0},
+ };
+
+ for (size_t i = 0; i < ARRAYSIZE_UNSAFE(test_cases); ++i) {
+ const TestCase& value = test_cases[i];
+ for (int j = -1; j < 2; ++j) {
+ for (int k = 0; k < 3; ++k) {
+ float epsilon = 0.0001f;
+ Point p0, p1, p2;
+ Transform xform;
+ switch (k) {
+ case 0:
+ p1.SetPoint(value.before, 0);
+ p2.SetPoint(value.after, 0);
+ xform.Scale(value.s + j * epsilon, 1.0);
+ break;
+ case 1:
+ p1.SetPoint(0, value.before);
+ p2.SetPoint(0, value.after);
+ xform.Scale(1.0, value.s + j * epsilon);
+ break;
+ case 2:
+ p1.SetPoint(value.before,
+ value.before);
+ p2.SetPoint(value.after,
+ value.after);
+ xform.Scale(value.s + j * epsilon,
+ value.s + j * epsilon);
+ break;
+ }
+ p0 = p1;
+ xform.TransformPoint(p1);
+ if (value.s == value.s) {
+ EXPECT_EQ(p1.x(), p2.x());
+ EXPECT_EQ(p1.y(), p2.y());
+ if (value.s != 0.0f) {
+ xform.TransformPointReverse(p1);
+ EXPECT_EQ(p1.x(), p0.x());
+ EXPECT_EQ(p1.y(), p0.y());
+ }
+ }
+ }
+ }
+ }
+}
+
+TEST(XFormTest, SetRotate2D) {
+ static const struct SetRotateCase {
+ int x;
+ int y;
+ float degree;
+ int xprime;
+ int yprime;
+ } set_rotate_cases[] = {
+ { 100, 0, 90.0f, 0, 100},
+ { 0, 0, 90.0f, 0, 0},
+ { 0, 100, 90.0f, -100, 0},
+ { 0, 1, -90.0f, 1, 0},
+ { 100, 0, 0.0f, 100, 0},
+ { 0, 0, 0.0f, 0, 0},
+ { 0, 0, std::numeric_limits<float>::quiet_NaN(), 0, 0},
+ { 100, 0, 360.0f, 100, 0}
+ };
+
+ for (size_t i = 0; i < ARRAYSIZE_UNSAFE(set_rotate_cases); ++i) {
+ const SetRotateCase& value = set_rotate_cases[i];
+ for (int j = 1; j >= -1; --j) {
+ float epsilon = 0.1f;
+ Point pt(value.x, value.y);
+ Transform xform;
+ // should be invariant to small floating point errors.
+ xform.Rotate(value.degree + j * epsilon);
+ // just want to make sure that we don't crash in the case of NaN.
+ if (value.degree == value.degree) {
+ xform.TransformPoint(pt);
+ EXPECT_EQ(value.xprime, pt.x());
+ EXPECT_EQ(value.yprime, pt.y());
+ xform.TransformPointReverse(pt);
+ EXPECT_EQ(pt.x(), value.x);
+ EXPECT_EQ(pt.y(), value.y);
+ }
+ }
+ }
+}
+
+TEST(XFormTest, BlendTranslate) {
+ Transform from;
+ for (int i = -5; i < 15; ++i) {
+ Transform to;
+ to.Translate3d(1, 1, 1);
+ double t = i / 9.0;
+ EXPECT_TRUE(to.Blend(from, t));
+ EXPECT_FLOAT_EQ(t, to.matrix().get(0, 3));
+ EXPECT_FLOAT_EQ(t, to.matrix().get(1, 3));
+ EXPECT_FLOAT_EQ(t, to.matrix().get(2, 3));
+ }
+}
+
+TEST(XFormTest, BlendRotate) {
+ Vector3dF axes[] = {
+ Vector3dF(1, 0, 0),
+ Vector3dF(0, 1, 0),
+ Vector3dF(0, 0, 1),
+ Vector3dF(1, 1, 1)
+ };
+ Transform from;
+ for (size_t index = 0; index < ARRAYSIZE_UNSAFE(axes); ++index) {
+ for (int i = -5; i < 15; ++i) {
+ Transform to;
+ to.RotateAbout(axes[index], 90);
+ double t = i / 9.0;
+ EXPECT_TRUE(to.Blend(from, t));
+
+ Transform expected;
+ expected.RotateAbout(axes[index], 90 * t);
+
+ EXPECT_TRUE(MatricesAreNearlyEqual(expected, to));
+ }
+ }
+}
+
+TEST(XFormTest, BlendRotateFollowsShortestPath) {
+ // Verify that we interpolate along the shortest path regardless of whether
+ // this path crosses the 180-degree point.
+ Vector3dF axes[] = {
+ Vector3dF(1, 0, 0),
+ Vector3dF(0, 1, 0),
+ Vector3dF(0, 0, 1),
+ Vector3dF(1, 1, 1)
+ };
+ for (size_t index = 0; index < ARRAYSIZE_UNSAFE(axes); ++index) {
+ for (int i = -5; i < 15; ++i) {
+ Transform from1;
+ from1.RotateAbout(axes[index], 130.0);
+ Transform to1;
+ to1.RotateAbout(axes[index], 175.0);
+
+ Transform from2;
+ from2.RotateAbout(axes[index], 140.0);
+ Transform to2;
+ to2.RotateAbout(axes[index], 185.0);
+
+ double t = i / 9.0;
+ EXPECT_TRUE(to1.Blend(from1, t));
+ EXPECT_TRUE(to2.Blend(from2, t));
+
+ Transform expected1;
+ expected1.RotateAbout(axes[index], 130.0 + 45.0 * t);
+
+ Transform expected2;
+ expected2.RotateAbout(axes[index], 140.0 + 45.0 * t);
+
+ EXPECT_TRUE(MatricesAreNearlyEqual(expected1, to1));
+ EXPECT_TRUE(MatricesAreNearlyEqual(expected2, to2));
+ }
+ }
+}
+
+TEST(XFormTest, CanBlend180DegreeRotation) {
+ Vector3dF axes[] = {
+ Vector3dF(1, 0, 0),
+ Vector3dF(0, 1, 0),
+ Vector3dF(0, 0, 1),
+ Vector3dF(1, 1, 1)
+ };
+ Transform from;
+ for (size_t index = 0; index < ARRAYSIZE_UNSAFE(axes); ++index) {
+ for (int i = -5; i < 15; ++i) {
+ Transform to;
+ to.RotateAbout(axes[index], 180);
+ double t = i / 9.0;
+ EXPECT_TRUE(to.Blend(from, t));
+
+ Transform expected;
+ expected.RotateAbout(axes[index], 180 * t);
+
+ EXPECT_TRUE(MatricesAreNearlyEqual(expected, to));
+ }
+ }
+}
+
+TEST(XFormTest, BlendScale) {
+ Transform from;
+ for (int i = -5; i < 15; ++i) {
+ Transform to;
+ to.Scale3d(5, 4, 3);
+ double t = i / 9.0;
+ EXPECT_TRUE(to.Blend(from, t));
+ EXPECT_FLOAT_EQ(t * 4 + 1, to.matrix().get(0, 0));
+ EXPECT_FLOAT_EQ(t * 3 + 1, to.matrix().get(1, 1));
+ EXPECT_FLOAT_EQ(t * 2 + 1, to.matrix().get(2, 2));
+ }
+}
+
+TEST(XFormTest, BlendSkew) {
+ Transform from;
+ for (int i = 0; i < 2; ++i) {
+ Transform to;
+ to.SkewX(20);
+ to.SkewY(10);
+ double t = i;
+ Transform expected;
+ expected.SkewX(t * 20);
+ expected.SkewY(t * 10);
+ EXPECT_TRUE(to.Blend(from, t));
+ EXPECT_TRUE(MatricesAreNearlyEqual(expected, to));
+ }
+}
+
+TEST(XFormTest, ExtrapolateSkew) {
+ Transform from;
+ for (int i = -1; i < 2; ++i) {
+ Transform to;
+ to.SkewX(20);
+ double t = i;
+ Transform expected;
+ expected.SkewX(t * 20);
+ EXPECT_TRUE(to.Blend(from, t));
+ EXPECT_TRUE(MatricesAreNearlyEqual(expected, to));
+ }
+}
+
+TEST(XFormTest, BlendPerspective) {
+ Transform from;
+ from.ApplyPerspectiveDepth(200);
+ for (int i = -1; i < 3; ++i) {
+ Transform to;
+ to.ApplyPerspectiveDepth(800);
+ double t = i;
+ double depth = 1.0 / ((1.0 / 200) * (1.0 - t) + (1.0 / 800) * t);
+ Transform expected;
+ expected.ApplyPerspectiveDepth(depth);
+ EXPECT_TRUE(to.Blend(from, t));
+ EXPECT_TRUE(MatricesAreNearlyEqual(expected, to));
+ }
+}
+
+TEST(XFormTest, BlendIdentity) {
+ Transform from;
+ Transform to;
+ EXPECT_TRUE(to.Blend(from, 0.5));
+ EXPECT_EQ(to, from);
+}
+
+TEST(XFormTest, CannotBlendSingularMatrix) {
+ Transform from;
+ Transform to;
+ to.matrix().set(1, 1, SkDoubleToMScalar(0));
+ EXPECT_FALSE(to.Blend(from, 0.5));
+}
+
+TEST(XFormTest, VerifyBlendForTranslation) {
+ Transform from;
+ from.Translate3d(100.0, 200.0, 100.0);
+
+ Transform to;
+
+ to.Translate3d(200.0, 100.0, 300.0);
+ to.Blend(from, 0.0);
+ EXPECT_EQ(from, to);
+
+ to = Transform();
+ to.Translate3d(200.0, 100.0, 300.0);
+ to.Blend(from, 0.25);
+ EXPECT_ROW1_EQ(1.0f, 0.0f, 0.0f, 125.0f, to);
+ EXPECT_ROW2_EQ(0.0f, 1.0f, 0.0f, 175.0f, to);
+ EXPECT_ROW3_EQ(0.0f, 0.0f, 1.0f, 150.0f, to);
+ EXPECT_ROW4_EQ(0.0f, 0.0f, 0.0f, 1.0f, to);
+
+ to = Transform();
+ to.Translate3d(200.0, 100.0, 300.0);
+ to.Blend(from, 0.5);
+ EXPECT_ROW1_EQ(1.0f, 0.0f, 0.0f, 150.0f, to);
+ EXPECT_ROW2_EQ(0.0f, 1.0f, 0.0f, 150.0f, to);
+ EXPECT_ROW3_EQ(0.0f, 0.0f, 1.0f, 200.0f, to);
+ EXPECT_ROW4_EQ(0.0f, 0.0f, 0.0f, 1.0f, to);
+
+ to = Transform();
+ to.Translate3d(200.0, 100.0, 300.0);
+ to.Blend(from, 1.0);
+ EXPECT_ROW1_EQ(1.0f, 0.0f, 0.0f, 200.0f, to);
+ EXPECT_ROW2_EQ(0.0f, 1.0f, 0.0f, 100.0f, to);
+ EXPECT_ROW3_EQ(0.0f, 0.0f, 1.0f, 300.0f, to);
+ EXPECT_ROW4_EQ(0.0f, 0.0f, 0.0f, 1.0f, to);
+}
+
+TEST(XFormTest, VerifyBlendForScale) {
+ Transform from;
+ from.Scale3d(100.0, 200.0, 100.0);
+
+ Transform to;
+
+ to.Scale3d(200.0, 100.0, 300.0);
+ to.Blend(from, 0.0);
+ EXPECT_EQ(from, to);
+
+ to = Transform();
+ to.Scale3d(200.0, 100.0, 300.0);
+ to.Blend(from, 0.25);
+ EXPECT_ROW1_EQ(125.0f, 0.0f, 0.0f, 0.0f, to);
+ EXPECT_ROW2_EQ(0.0f, 175.0f, 0.0f, 0.0f, to);
+ EXPECT_ROW3_EQ(0.0f, 0.0f, 150.0f, 0.0f, to);
+ EXPECT_ROW4_EQ(0.0f, 0.0f, 0.0f, 1.0f, to);
+
+ to = Transform();
+ to.Scale3d(200.0, 100.0, 300.0);
+ to.Blend(from, 0.5);
+ EXPECT_ROW1_EQ(150.0f, 0.0f, 0.0f, 0.0f, to);
+ EXPECT_ROW2_EQ(0.0f, 150.0f, 0.0f, 0.0f, to);
+ EXPECT_ROW3_EQ(0.0f, 0.0f, 200.0f, 0.0f, to);
+ EXPECT_ROW4_EQ(0.0f, 0.0f, 0.0f, 1.0f, to);
+
+ to = Transform();
+ to.Scale3d(200.0, 100.0, 300.0);
+ to.Blend(from, 1.0);
+ EXPECT_ROW1_EQ(200.0f, 0.0f, 0.0f, 0.0f, to);
+ EXPECT_ROW2_EQ(0.0f, 100.0f, 0.0f, 0.0f, to);
+ EXPECT_ROW3_EQ(0.0f, 0.0f, 300.0f, 0.0f, to);
+ EXPECT_ROW4_EQ(0.0f, 0.0f, 0.0f, 1.0f, to);
+}
+
+TEST(XFormTest, VerifyBlendForSkewX) {
+ Transform from;
+ from.SkewX(0.0);
+
+ Transform to;
+
+ to.SkewX(45.0);
+ to.Blend(from, 0.0);
+ EXPECT_EQ(from, to);
+
+ to = Transform();
+ to.SkewX(45.0);
+ to.Blend(from, 0.5);
+ EXPECT_ROW1_EQ(1.0f, 0.5f, 0.0f, 0.0f, to);
+ EXPECT_ROW2_EQ(0.0f, 1.0f, 0.0f, 0.0f, to);
+ EXPECT_ROW3_EQ(0.0f, 0.0f, 1.0f, 0.0f, to);
+ EXPECT_ROW4_EQ(0.0f, 0.0f, 0.0f, 1.0f, to);
+
+ to = Transform();
+ to.SkewX(45.0);
+ to.Blend(from, 0.25);
+ EXPECT_ROW1_EQ(1.0f, 0.25f, 0.0f, 0.0f, to);
+ EXPECT_ROW2_EQ(0.0f, 1.0f, 0.0f, 0.0f, to);
+ EXPECT_ROW3_EQ(0.0f, 0.0f, 1.0f, 0.0f, to);
+ EXPECT_ROW4_EQ(0.0f, 0.0f, 0.0f, 1.0f, to);
+
+ to = Transform();
+ to.SkewX(45.0);
+ to.Blend(from, 1.0);
+ EXPECT_ROW1_EQ(1.0f, 1.0f, 0.0f, 0.0f, to);
+ EXPECT_ROW2_EQ(0.0f, 1.0f, 0.0f, 0.0f, to);
+ EXPECT_ROW3_EQ(0.0f, 0.0f, 1.0f, 0.0f, to);
+ EXPECT_ROW4_EQ(0.0f, 0.0f, 0.0f, 1.0f, to);
+}
+
+TEST(XFormTest, VerifyBlendForSkewY) {
+ // NOTE CAREFULLY: Decomposition of skew and rotation terms of the matrix
+ // is inherently underconstrained, and so it does not always compute the
+ // originally intended skew parameters. The current implementation uses QR
+ // decomposition, which decomposes the shear into a rotation + non-uniform
+ // scale.
+ //
+ // It is unlikely that the decomposition implementation will need to change
+ // very often, so to get any test coverage, the compromise is to verify the
+ // exact matrix that the.Blend() operation produces.
+ //
+ // This problem also potentially exists for skewX, but the current QR
+ // decomposition implementation just happens to decompose those test
+ // matrices intuitively.
+ //
+ // Unfortunately, this case suffers from uncomfortably large precision
+ // error.
+
+ Transform from;
+ from.SkewY(0.0);
+
+ Transform to;
+
+ to.SkewY(45.0);
+ to.Blend(from, 0.0);
+ EXPECT_EQ(from, to);
+
+ to = Transform();
+ to.SkewY(45.0);
+ to.Blend(from, 0.25);
+ EXPECT_ROW1_NEAR(1.0823489449280947471976333,
+ 0.0464370719145053845178239,
+ 0.0,
+ 0.0,
+ to,
+ LOOSE_ERROR_THRESHOLD);
+ EXPECT_ROW2_NEAR(0.2152925909665224513123150,
+ 0.9541702441750861130032035,
+ 0.0,
+ 0.0,
+ to,
+ LOOSE_ERROR_THRESHOLD);
+ EXPECT_ROW3_EQ(0.0f, 0.0f, 1.0f, 0.0f, to);
+ EXPECT_ROW4_EQ(0.0f, 0.0f, 0.0f, 1.0f, to);
+
+ to = Transform();
+ to.SkewY(45.0);
+ to.Blend(from, 0.5);
+ EXPECT_ROW1_NEAR(1.1152212925809066312865525,
+ 0.0676495144007326631996335,
+ 0.0,
+ 0.0,
+ to,
+ LOOSE_ERROR_THRESHOLD);
+ EXPECT_ROW2_NEAR(0.4619397844342648662419037,
+ 0.9519009045724774464858342,
+ 0.0,
+ 0.0,
+ to,
+ LOOSE_ERROR_THRESHOLD);
+ EXPECT_ROW3_EQ(0.0f, 0.0f, 1.0f, 0.0f, to);
+ EXPECT_ROW4_EQ(0.0f, 0.0f, 0.0f, 1.0f, to);
+
+ to = Transform();
+ to.SkewY(45.0);
+ to.Blend(from, 1.0);
+ EXPECT_ROW1_NEAR(1.0, 0.0, 0.0, 0.0, to, LOOSE_ERROR_THRESHOLD);
+ EXPECT_ROW2_NEAR(1.0, 1.0, 0.0, 0.0, to, LOOSE_ERROR_THRESHOLD);
+ EXPECT_ROW3_EQ(0.0f, 0.0f, 1.0f, 0.0f, to);
+ EXPECT_ROW4_EQ(0.0f, 0.0f, 0.0f, 1.0f, to);
+}
+
+TEST(XFormTest, VerifyBlendForRotationAboutX) {
+ // Even though.Blending uses quaternions, axis-aligned rotations should.
+ // Blend the same with quaternions or Euler angles. So we can test
+ // rotation.Blending by comparing against manually specified matrices from
+ // Euler angles.
+
+ Transform from;
+ from.RotateAbout(Vector3dF(1.0, 0.0, 0.0), 0.0);
+
+ Transform to;
+
+ to.RotateAbout(Vector3dF(1.0, 0.0, 0.0), 90.0);
+ to.Blend(from, 0.0);
+ EXPECT_EQ(from, to);
+
+ double expectedRotationAngle = 22.5 * M_PI / 180.0;
+ to = Transform();
+ to.RotateAbout(Vector3dF(1.0, 0.0, 0.0), 90.0);
+ to.Blend(from, 0.25);
+ EXPECT_ROW1_NEAR(1.0, 0.0, 0.0, 0.0, to, ERROR_THRESHOLD);
+ EXPECT_ROW2_NEAR(0.0,
+ std::cos(expectedRotationAngle),
+ -std::sin(expectedRotationAngle),
+ 0.0,
+ to,
+ ERROR_THRESHOLD);
+ EXPECT_ROW3_NEAR(0.0,
+ std::sin(expectedRotationAngle),
+ std::cos(expectedRotationAngle),
+ 0.0,
+ to,
+ ERROR_THRESHOLD);
+ EXPECT_ROW4_EQ(0.0f, 0.0f, 0.0f, 1.0f, to);
+
+ expectedRotationAngle = 45.0 * M_PI / 180.0;
+ to = Transform();
+ to.RotateAbout(Vector3dF(1.0, 0.0, 0.0), 90.0);
+ to.Blend(from, 0.5);
+ EXPECT_ROW1_NEAR(1.0, 0.0, 0.0, 0.0, to, ERROR_THRESHOLD);
+ EXPECT_ROW2_NEAR(0.0,
+ std::cos(expectedRotationAngle),
+ -std::sin(expectedRotationAngle),
+ 0.0,
+ to,
+ ERROR_THRESHOLD);
+ EXPECT_ROW3_NEAR(0.0,
+ std::sin(expectedRotationAngle),
+ std::cos(expectedRotationAngle),
+ 0.0,
+ to,
+ ERROR_THRESHOLD);
+ EXPECT_ROW4_EQ(0.0f, 0.0f, 0.0f, 1.0f, to);
+
+ to = Transform();
+ to.RotateAbout(Vector3dF(1.0, 0.0, 0.0), 90.0);
+ to.Blend(from, 1.0);
+ EXPECT_ROW1_NEAR(1.0, 0.0, 0.0, 0.0, to, ERROR_THRESHOLD);
+ EXPECT_ROW2_NEAR(0.0, 0.0, -1.0, 0.0, to, ERROR_THRESHOLD);
+ EXPECT_ROW3_NEAR(0.0, 1.0, 0.0, 0.0, to, ERROR_THRESHOLD);
+ EXPECT_ROW4_EQ(0.0f, 0.0f, 0.0f, 1.0f, to);
+}
+
+TEST(XFormTest, VerifyBlendForRotationAboutY) {
+ Transform from;
+ from.RotateAbout(Vector3dF(0.0, 1.0, 0.0), 0.0);
+
+ Transform to;
+
+ to.RotateAbout(Vector3dF(0.0, 1.0, 0.0), 90.0);
+ to.Blend(from, 0.0);
+ EXPECT_EQ(from, to);
+
+ double expectedRotationAngle = 22.5 * M_PI / 180.0;
+ to = Transform();
+ to.RotateAbout(Vector3dF(0.0, 1.0, 0.0), 90.0);
+ to.Blend(from, 0.25);
+ EXPECT_ROW1_NEAR(std::cos(expectedRotationAngle),
+ 0.0,
+ std::sin(expectedRotationAngle),
+ 0.0,
+ to,
+ ERROR_THRESHOLD);
+ EXPECT_ROW2_NEAR(0.0, 1.0, 0.0, 0.0, to, ERROR_THRESHOLD);
+ EXPECT_ROW3_NEAR(-std::sin(expectedRotationAngle),
+ 0.0,
+ std::cos(expectedRotationAngle),
+ 0.0,
+ to,
+ ERROR_THRESHOLD);
+ EXPECT_ROW4_EQ(0.0f, 0.0f, 0.0f, 1.0f, to);
+
+ expectedRotationAngle = 45.0 * M_PI / 180.0;
+ to = Transform();
+ to.RotateAbout(Vector3dF(0.0, 1.0, 0.0), 90.0);
+ to.Blend(from, 0.5);
+ EXPECT_ROW1_NEAR(std::cos(expectedRotationAngle),
+ 0.0,
+ std::sin(expectedRotationAngle),
+ 0.0,
+ to,
+ ERROR_THRESHOLD);
+ EXPECT_ROW2_NEAR(0.0, 1.0, 0.0, 0.0, to, ERROR_THRESHOLD);
+ EXPECT_ROW3_NEAR(-std::sin(expectedRotationAngle),
+ 0.0,
+ std::cos(expectedRotationAngle),
+ 0.0,
+ to,
+ ERROR_THRESHOLD);
+ EXPECT_ROW4_EQ(0.0f, 0.0f, 0.0f, 1.0f, to);
+
+ to = Transform();
+ to.RotateAbout(Vector3dF(0.0, 1.0, 0.0), 90.0);
+ to.Blend(from, 1.0);
+ EXPECT_ROW1_NEAR(0.0, 0.0, 1.0, 0.0, to, ERROR_THRESHOLD);
+ EXPECT_ROW2_NEAR(0.0, 1.0, 0.0, 0.0, to, ERROR_THRESHOLD);
+ EXPECT_ROW3_NEAR(-1.0, 0.0, 0.0, 0.0, to, ERROR_THRESHOLD);
+ EXPECT_ROW4_EQ(0.0f, 0.0f, 0.0f, 1.0f, to);
+}
+
+TEST(XFormTest, VerifyBlendForRotationAboutZ) {
+ Transform from;
+ from.RotateAbout(Vector3dF(0.0, 0.0, 1.0), 0.0);
+
+ Transform to;
+
+ to.RotateAbout(Vector3dF(0.0, 0.0, 1.0), 90.0);
+ to.Blend(from, 0.0);
+ EXPECT_EQ(from, to);
+
+ double expectedRotationAngle = 22.5 * M_PI / 180.0;
+ to = Transform();
+ to.RotateAbout(Vector3dF(0.0, 0.0, 1.0), 90.0);
+ to.Blend(from, 0.25);
+ EXPECT_ROW1_NEAR(std::cos(expectedRotationAngle),
+ -std::sin(expectedRotationAngle),
+ 0.0,
+ 0.0,
+ to,
+ ERROR_THRESHOLD);
+ EXPECT_ROW2_NEAR(std::sin(expectedRotationAngle),
+ std::cos(expectedRotationAngle),
+ 0.0,
+ 0.0,
+ to,
+ ERROR_THRESHOLD);
+ EXPECT_ROW3_NEAR(0.0, 0.0, 1.0, 0.0, to, ERROR_THRESHOLD);
+ EXPECT_ROW4_EQ(0.0f, 0.0f, 0.0f, 1.0f, to);
+
+ expectedRotationAngle = 45.0 * M_PI / 180.0;
+ to = Transform();
+ to.RotateAbout(Vector3dF(0.0, 0.0, 1.0), 90.0);
+ to.Blend(from, 0.5);
+ EXPECT_ROW1_NEAR(std::cos(expectedRotationAngle),
+ -std::sin(expectedRotationAngle),
+ 0.0,
+ 0.0,
+ to,
+ ERROR_THRESHOLD);
+ EXPECT_ROW2_NEAR(std::sin(expectedRotationAngle),
+ std::cos(expectedRotationAngle),
+ 0.0,
+ 0.0,
+ to,
+ ERROR_THRESHOLD);
+ EXPECT_ROW3_NEAR(0.0, 0.0, 1.0, 0.0, to, ERROR_THRESHOLD);
+ EXPECT_ROW4_EQ(0.0f, 0.0f, 0.0f, 1.0f, to);
+
+ to = Transform();
+ to.RotateAbout(Vector3dF(0.0, 0.0, 1.0), 90.0);
+ to.Blend(from, 1.0);
+ EXPECT_ROW1_NEAR(0.0, -1.0, 0.0, 0.0, to, ERROR_THRESHOLD);
+ EXPECT_ROW2_NEAR(1.0, 0.0, 0.0, 0.0, to, ERROR_THRESHOLD);
+ EXPECT_ROW3_NEAR(0.0, 0.0, 1.0, 0.0, to, ERROR_THRESHOLD);
+ EXPECT_ROW4_EQ(0.0f, 0.0f, 0.0f, 1.0f, to);
+}
+
+TEST(XFormTest, VerifyBlendForCompositeTransform) {
+ // Verify that the.Blending was done with a decomposition in correct order
+ // by blending a composite transform. Using matrix x vector notation
+ // (Ax = b, where x is column vector), the ordering should be:
+ // perspective * translation * rotation * skew * scale
+ //
+ // It is not as important (or meaningful) to check intermediate
+ // interpolations; order of operations will be tested well enough by the
+ // end cases that are easier to specify.
+
+ Transform from;
+ Transform to;
+
+ Transform expectedEndOfAnimation;
+ expectedEndOfAnimation.ApplyPerspectiveDepth(1.0);
+ expectedEndOfAnimation.Translate3d(10.0, 20.0, 30.0);
+ expectedEndOfAnimation.RotateAbout(Vector3dF(0.0, 0.0, 1.0), 25.0);
+ expectedEndOfAnimation.SkewY(45.0);
+ expectedEndOfAnimation.Scale3d(6.0, 7.0, 8.0);
+
+ to = expectedEndOfAnimation;
+ to.Blend(from, 0.0);
+ EXPECT_EQ(from, to);
+
+ to = expectedEndOfAnimation;
+ // We short circuit if blend is >= 1, so to check the numerics, we will
+ // check that we get close to what we expect when we're nearly done
+ // interpolating.
+ to.Blend(from, .99999);
+
+ // Recomposing the matrix results in a normalized matrix, so to verify we
+ // need to normalize the expectedEndOfAnimation before comparing elements.
+ // Normalizing means dividing everything by expectedEndOfAnimation.m44().
+ Transform normalizedExpectedEndOfAnimation = expectedEndOfAnimation;
+ Transform normalizationMatrix;
+ normalizationMatrix.matrix().set(
+ 0.0,
+ 0.0,
+ SkDoubleToMScalar(1 / expectedEndOfAnimation.matrix().get(3.0, 3.0)));
+ normalizationMatrix.matrix().set(
+ 1.0,
+ 1.0,
+ SkDoubleToMScalar(1 / expectedEndOfAnimation.matrix().get(3.0, 3.0)));
+ normalizationMatrix.matrix().set(
+ 2.0,
+ 2.0,
+ SkDoubleToMScalar(1 / expectedEndOfAnimation.matrix().get(3.0, 3.0)));
+ normalizationMatrix.matrix().set(
+ 3.0,
+ 3.0,
+ SkDoubleToMScalar(1 / expectedEndOfAnimation.matrix().get(3.0, 3.0)));
+ normalizedExpectedEndOfAnimation.PreconcatTransform(normalizationMatrix);
+
+ EXPECT_TRUE(MatricesAreNearlyEqual(normalizedExpectedEndOfAnimation, to));
+}
+
+TEST(XFormTest, DecomposedTransformCtor) {
+ DecomposedTransform decomp;
+ for (int i = 0; i < 3; ++i) {
+ EXPECT_EQ(0.0, decomp.translate[i]);
+ EXPECT_EQ(1.0, decomp.scale[i]);
+ EXPECT_EQ(0.0, decomp.skew[i]);
+ EXPECT_EQ(0.0, decomp.quaternion[i]);
+ EXPECT_EQ(0.0, decomp.perspective[i]);
+ }
+ EXPECT_EQ(1.0, decomp.quaternion[3]);
+ EXPECT_EQ(1.0, decomp.perspective[3]);
+ Transform identity;
+ Transform composed = ComposeTransform(decomp);
+ EXPECT_TRUE(MatricesAreNearlyEqual(identity, composed));
+}
+
+TEST(XFormTest, FactorTRS) {
+ for (int degrees = 0; degrees < 180; ++degrees) {
+ // build a transformation matrix.
+ gfx::Transform transform;
+ transform.Translate(degrees * 2, -degrees * 3);
+ transform.Rotate(degrees);
+ transform.Scale(degrees + 1, 2 * degrees + 1);
+
+ // factor the matrix
+ DecomposedTransform decomp;
+ bool success = DecomposeTransform(&decomp, transform);
+ EXPECT_TRUE(success);
+ EXPECT_FLOAT_EQ(decomp.translate[0], degrees * 2);
+ EXPECT_FLOAT_EQ(decomp.translate[1], -degrees * 3);
+ double rotation = std::acos(decomp.quaternion[3]) * 360.0 / M_PI;
+ while (rotation < 0.0)
+ rotation += 360.0;
+ while (rotation > 360.0)
+ rotation -= 360.0;
+ EXPECT_FLOAT_EQ(rotation, degrees);
+ EXPECT_FLOAT_EQ(decomp.scale[0], degrees + 1);
+ EXPECT_FLOAT_EQ(decomp.scale[1], 2 * degrees + 1);
+ }
+}
+
+TEST(XFormTest, IntegerTranslation) {
+ gfx::Transform transform;
+ EXPECT_TRUE(transform.IsIdentityOrIntegerTranslation());
+
+ transform.Translate3d(1, 2, 3);
+ EXPECT_TRUE(transform.IsIdentityOrIntegerTranslation());
+
+ transform.MakeIdentity();
+ transform.Translate3d(-1, -2, -3);
+ EXPECT_TRUE(transform.IsIdentityOrIntegerTranslation());
+
+ transform.MakeIdentity();
+ transform.Translate3d(4.5, 0, 0);
+ EXPECT_FALSE(transform.IsIdentityOrIntegerTranslation());
+
+ transform.MakeIdentity();
+ transform.Translate3d(0, -6.7, 0);
+ EXPECT_FALSE(transform.IsIdentityOrIntegerTranslation());
+
+ transform.MakeIdentity();
+ transform.Translate3d(0, 0, 8.9);
+ EXPECT_FALSE(transform.IsIdentityOrIntegerTranslation());
+}
+
+TEST(XFormTest, verifyMatrixInversion) {
+ {
+ // Invert a translation
+ gfx::Transform translation;
+ translation.Translate3d(2.0, 3.0, 4.0);
+ EXPECT_TRUE(translation.IsInvertible());
+
+ gfx::Transform inverse_translation;
+ bool is_invertible = translation.GetInverse(&inverse_translation);
+ EXPECT_TRUE(is_invertible);
+ EXPECT_ROW1_EQ(1.0f, 0.0f, 0.0f, -2.0f, inverse_translation);
+ EXPECT_ROW2_EQ(0.0f, 1.0f, 0.0f, -3.0f, inverse_translation);
+ EXPECT_ROW3_EQ(0.0f, 0.0f, 1.0f, -4.0f, inverse_translation);
+ EXPECT_ROW4_EQ(0.0f, 0.0f, 0.0f, 1.0f, inverse_translation);
+ }
+
+ {
+ // Invert a non-uniform scale
+ gfx::Transform scale;
+ scale.Scale3d(4.0, 10.0, 100.0);
+ EXPECT_TRUE(scale.IsInvertible());
+
+ gfx::Transform inverse_scale;
+ bool is_invertible = scale.GetInverse(&inverse_scale);
+ EXPECT_TRUE(is_invertible);
+ EXPECT_ROW1_EQ(0.25f, 0.0f, 0.0f, 0.0f, inverse_scale);
+ EXPECT_ROW2_EQ(0.0f, 0.1f, 0.0f, 0.0f, inverse_scale);
+ EXPECT_ROW3_EQ(0.0f, 0.0f, 0.01f, 0.0f, inverse_scale);
+ EXPECT_ROW4_EQ(0.0f, 0.0f, 0.0f, 1.0f, inverse_scale);
+ }
+
+ {
+ // Try to invert a matrix that is not invertible.
+ // The inverse() function should reset the output matrix to identity.
+ gfx::Transform uninvertible;
+ uninvertible.matrix().setDouble(0, 0, 0.0);
+ uninvertible.matrix().setDouble(1, 1, 0.0);
+ uninvertible.matrix().setDouble(2, 2, 0.0);
+ uninvertible.matrix().setDouble(3, 3, 0.0);
+ EXPECT_FALSE(uninvertible.IsInvertible());
+
+ gfx::Transform inverse_of_uninvertible;
+
+ // Add a scale just to more easily ensure that inverse_of_uninvertible is
+ // reset to identity.
+ inverse_of_uninvertible.Scale3d(4.0, 10.0, 100.0);
+
+ bool is_invertible = uninvertible.GetInverse(&inverse_of_uninvertible);
+ EXPECT_FALSE(is_invertible);
+ EXPECT_TRUE(inverse_of_uninvertible.IsIdentity());
+ EXPECT_ROW1_EQ(1.0f, 0.0f, 0.0f, 0.0f, inverse_of_uninvertible);
+ EXPECT_ROW2_EQ(0.0f, 1.0f, 0.0f, 0.0f, inverse_of_uninvertible);
+ EXPECT_ROW3_EQ(0.0f, 0.0f, 1.0f, 0.0f, inverse_of_uninvertible);
+ EXPECT_ROW4_EQ(0.0f, 0.0f, 0.0f, 1.0f, inverse_of_uninvertible);
+ }
+}
+
+TEST(XFormTest, verifyBackfaceVisibilityBasicCases) {
+ Transform transform;
+
+ transform.MakeIdentity();
+ EXPECT_FALSE(transform.IsBackFaceVisible());
+
+ transform.MakeIdentity();
+ transform.RotateAboutYAxis(80.0);
+ EXPECT_FALSE(transform.IsBackFaceVisible());
+
+ transform.MakeIdentity();
+ transform.RotateAboutYAxis(100.0);
+ EXPECT_TRUE(transform.IsBackFaceVisible());
+
+ // Edge case, 90 degree rotation should return false.
+ transform.MakeIdentity();
+ transform.RotateAboutYAxis(90.0);
+ EXPECT_FALSE(transform.IsBackFaceVisible());
+}
+
+TEST(XFormTest, verifyBackfaceVisibilityForPerspective) {
+ Transform layer_space_to_projection_plane;
+
+ // This tests if IsBackFaceVisible works properly under perspective
+ // transforms. Specifically, layers that may have their back face visible in
+ // orthographic projection, may not actually have back face visible under
+ // perspective projection.
+
+ // Case 1: Layer is rotated by slightly more than 90 degrees, at the center
+ // of the prespective projection. In this case, the layer's back-side
+ // is visible to the camera.
+ layer_space_to_projection_plane.MakeIdentity();
+ layer_space_to_projection_plane.ApplyPerspectiveDepth(1.0);
+ layer_space_to_projection_plane.Translate3d(0.0, 0.0, 0.0);
+ layer_space_to_projection_plane.RotateAboutYAxis(100.0);
+ EXPECT_TRUE(layer_space_to_projection_plane.IsBackFaceVisible());
+
+ // Case 2: Layer is rotated by slightly more than 90 degrees, but shifted off
+ // to the side of the camera. Because of the wide field-of-view, the
+ // layer's front side is still visible.
+ //
+ // |<-- front side of layer is visible to camera
+ // \ | /
+ // \ | /
+ // \| /
+ // | /
+ // |\ /<-- camera field of view
+ // | \ /
+ // back side of layer -->| \ /
+ // \./ <-- camera origin
+ //
+ layer_space_to_projection_plane.MakeIdentity();
+ layer_space_to_projection_plane.ApplyPerspectiveDepth(1.0);
+ layer_space_to_projection_plane.Translate3d(-10.0, 0.0, 0.0);
+ layer_space_to_projection_plane.RotateAboutYAxis(100.0);
+ EXPECT_FALSE(layer_space_to_projection_plane.IsBackFaceVisible());
+
+ // Case 3: Additionally rotating the layer by 180 degrees should of course
+ // show the opposite result of case 2.
+ layer_space_to_projection_plane.RotateAboutYAxis(180.0);
+ EXPECT_TRUE(layer_space_to_projection_plane.IsBackFaceVisible());
+}
+
+TEST(XFormTest, verifyDefaultConstructorCreatesIdentityMatrix) {
+ Transform A;
+ EXPECT_ROW1_EQ(1.0f, 0.0f, 0.0f, 0.0f, A);
+ EXPECT_ROW2_EQ(0.0f, 1.0f, 0.0f, 0.0f, A);
+ EXPECT_ROW3_EQ(0.0f, 0.0f, 1.0f, 0.0f, A);
+ EXPECT_ROW4_EQ(0.0f, 0.0f, 0.0f, 1.0f, A);
+ EXPECT_TRUE(A.IsIdentity());
+}
+
+TEST(XFormTest, verifyCopyConstructor) {
+ Transform A;
+ InitializeTestMatrix(&A);
+
+ // Copy constructor should produce exact same elements as matrix A.
+ Transform B(A);
+ EXPECT_ROW1_EQ(10.0f, 14.0f, 18.0f, 22.0f, B);
+ EXPECT_ROW2_EQ(11.0f, 15.0f, 19.0f, 23.0f, B);
+ EXPECT_ROW3_EQ(12.0f, 16.0f, 20.0f, 24.0f, B);
+ EXPECT_ROW4_EQ(13.0f, 17.0f, 21.0f, 25.0f, B);
+}
+
+TEST(XFormTest, verifyConstructorFor16Elements) {
+ Transform transform(1.0, 2.0, 3.0, 4.0,
+ 5.0, 6.0, 7.0, 8.0,
+ 9.0, 10.0, 11.0, 12.0,
+ 13.0, 14.0, 15.0, 16.0);
+
+ EXPECT_ROW1_EQ(1.0f, 2.0f, 3.0f, 4.0f, transform);
+ EXPECT_ROW2_EQ(5.0f, 6.0f, 7.0f, 8.0f, transform);
+ EXPECT_ROW3_EQ(9.0f, 10.0f, 11.0f, 12.0f, transform);
+ EXPECT_ROW4_EQ(13.0f, 14.0f, 15.0f, 16.0f, transform);
+}
+
+TEST(XFormTest, verifyConstructorFor2dElements) {
+ Transform transform(1.0, 2.0, 3.0, 4.0, 5.0, 6.0);
+
+ EXPECT_ROW1_EQ(1.0f, 2.0f, 0.0f, 5.0f, transform);
+ EXPECT_ROW2_EQ(3.0f, 4.0f, 0.0f, 6.0f, transform);
+ EXPECT_ROW3_EQ(0.0f, 0.0f, 1.0f, 0.0f, transform);
+ EXPECT_ROW4_EQ(0.0f, 0.0f, 0.0f, 1.0f, transform);
+}
+
+
+TEST(XFormTest, verifyAssignmentOperator) {
+ Transform A;
+ InitializeTestMatrix(&A);
+ Transform B;
+ InitializeTestMatrix2(&B);
+ Transform C;
+ InitializeTestMatrix2(&C);
+ C = B = A;
+
+ // Both B and C should now have been re-assigned to the value of A.
+ EXPECT_ROW1_EQ(10.0f, 14.0f, 18.0f, 22.0f, B);
+ EXPECT_ROW2_EQ(11.0f, 15.0f, 19.0f, 23.0f, B);
+ EXPECT_ROW3_EQ(12.0f, 16.0f, 20.0f, 24.0f, B);
+ EXPECT_ROW4_EQ(13.0f, 17.0f, 21.0f, 25.0f, B);
+
+ EXPECT_ROW1_EQ(10.0f, 14.0f, 18.0f, 22.0f, C);
+ EXPECT_ROW2_EQ(11.0f, 15.0f, 19.0f, 23.0f, C);
+ EXPECT_ROW3_EQ(12.0f, 16.0f, 20.0f, 24.0f, C);
+ EXPECT_ROW4_EQ(13.0f, 17.0f, 21.0f, 25.0f, C);
+}
+
+TEST(XFormTest, verifyEqualsBooleanOperator) {
+ Transform A;
+ InitializeTestMatrix(&A);
+
+ Transform B;
+ InitializeTestMatrix(&B);
+ EXPECT_TRUE(A == B);
+
+ // Modifying multiple elements should cause equals operator to return false.
+ Transform C;
+ InitializeTestMatrix2(&C);
+ EXPECT_FALSE(A == C);
+
+ // Modifying any one individual element should cause equals operator to
+ // return false.
+ Transform D;
+ D = A;
+ D.matrix().setDouble(0, 0, 0.0);
+ EXPECT_FALSE(A == D);
+
+ D = A;
+ D.matrix().setDouble(1, 0, 0.0);
+ EXPECT_FALSE(A == D);
+
+ D = A;
+ D.matrix().setDouble(2, 0, 0.0);
+ EXPECT_FALSE(A == D);
+
+ D = A;
+ D.matrix().setDouble(3, 0, 0.0);
+ EXPECT_FALSE(A == D);
+
+ D = A;
+ D.matrix().setDouble(0, 1, 0.0);
+ EXPECT_FALSE(A == D);
+
+ D = A;
+ D.matrix().setDouble(1, 1, 0.0);
+ EXPECT_FALSE(A == D);
+
+ D = A;
+ D.matrix().setDouble(2, 1, 0.0);
+ EXPECT_FALSE(A == D);
+
+ D = A;
+ D.matrix().setDouble(3, 1, 0.0);
+ EXPECT_FALSE(A == D);
+
+ D = A;
+ D.matrix().setDouble(0, 2, 0.0);
+ EXPECT_FALSE(A == D);
+
+ D = A;
+ D.matrix().setDouble(1, 2, 0.0);
+ EXPECT_FALSE(A == D);
+
+ D = A;
+ D.matrix().setDouble(2, 2, 0.0);
+ EXPECT_FALSE(A == D);
+
+ D = A;
+ D.matrix().setDouble(3, 2, 0.0);
+ EXPECT_FALSE(A == D);
+
+ D = A;
+ D.matrix().setDouble(0, 3, 0.0);
+ EXPECT_FALSE(A == D);
+
+ D = A;
+ D.matrix().setDouble(1, 3, 0.0);
+ EXPECT_FALSE(A == D);
+
+ D = A;
+ D.matrix().setDouble(2, 3, 0.0);
+ EXPECT_FALSE(A == D);
+
+ D = A;
+ D.matrix().setDouble(3, 3, 0.0);
+ EXPECT_FALSE(A == D);
+}
+
+TEST(XFormTest, verifyMultiplyOperator) {
+ Transform A;
+ InitializeTestMatrix(&A);
+
+ Transform B;
+ InitializeTestMatrix2(&B);
+
+ Transform C = A * B;
+ EXPECT_ROW1_EQ(2036.0f, 2292.0f, 2548.0f, 2804.0f, C);
+ EXPECT_ROW2_EQ(2162.0f, 2434.0f, 2706.0f, 2978.0f, C);
+ EXPECT_ROW3_EQ(2288.0f, 2576.0f, 2864.0f, 3152.0f, C);
+ EXPECT_ROW4_EQ(2414.0f, 2718.0f, 3022.0f, 3326.0f, C);
+
+ // Just an additional sanity check; matrix multiplication is not commutative.
+ EXPECT_FALSE(A * B == B * A);
+}
+
+TEST(XFormTest, verifyMultiplyAndAssignOperator) {
+ Transform A;
+ InitializeTestMatrix(&A);
+
+ Transform B;
+ InitializeTestMatrix2(&B);
+
+ A *= B;
+ EXPECT_ROW1_EQ(2036.0f, 2292.0f, 2548.0f, 2804.0f, A);
+ EXPECT_ROW2_EQ(2162.0f, 2434.0f, 2706.0f, 2978.0f, A);
+ EXPECT_ROW3_EQ(2288.0f, 2576.0f, 2864.0f, 3152.0f, A);
+ EXPECT_ROW4_EQ(2414.0f, 2718.0f, 3022.0f, 3326.0f, A);
+
+ // Just an additional sanity check; matrix multiplication is not commutative.
+ Transform C = A;
+ C *= B;
+ Transform D = B;
+ D *= A;
+ EXPECT_FALSE(C == D);
+}
+
+TEST(XFormTest, verifyMatrixMultiplication) {
+ Transform A;
+ InitializeTestMatrix(&A);
+
+ Transform B;
+ InitializeTestMatrix2(&B);
+
+ A.PreconcatTransform(B);
+ EXPECT_ROW1_EQ(2036.0f, 2292.0f, 2548.0f, 2804.0f, A);
+ EXPECT_ROW2_EQ(2162.0f, 2434.0f, 2706.0f, 2978.0f, A);
+ EXPECT_ROW3_EQ(2288.0f, 2576.0f, 2864.0f, 3152.0f, A);
+ EXPECT_ROW4_EQ(2414.0f, 2718.0f, 3022.0f, 3326.0f, A);
+}
+
+TEST(XFormTest, verifyMakeIdentiy) {
+ Transform A;
+ InitializeTestMatrix(&A);
+ A.MakeIdentity();
+ EXPECT_ROW1_EQ(1.0f, 0.0f, 0.0f, 0.0f, A);
+ EXPECT_ROW2_EQ(0.0f, 1.0f, 0.0f, 0.0f, A);
+ EXPECT_ROW3_EQ(0.0f, 0.0f, 1.0f, 0.0f, A);
+ EXPECT_ROW4_EQ(0.0f, 0.0f, 0.0f, 1.0f, A);
+ EXPECT_TRUE(A.IsIdentity());
+}
+
+TEST(XFormTest, verifyTranslate) {
+ Transform A;
+ A.Translate(2.0, 3.0);
+ EXPECT_ROW1_EQ(1.0f, 0.0f, 0.0f, 2.0f, A);
+ EXPECT_ROW2_EQ(0.0f, 1.0f, 0.0f, 3.0f, A);
+ EXPECT_ROW3_EQ(0.0f, 0.0f, 1.0f, 0.0f, A);
+ EXPECT_ROW4_EQ(0.0f, 0.0f, 0.0f, 1.0f, A);
+
+ // Verify that Translate() post-multiplies the existing matrix.
+ A.MakeIdentity();
+ A.Scale(5.0, 5.0);
+ A.Translate(2.0, 3.0);
+ EXPECT_ROW1_EQ(5.0f, 0.0f, 0.0f, 10.0f, A);
+ EXPECT_ROW2_EQ(0.0f, 5.0f, 0.0f, 15.0f, A);
+ EXPECT_ROW3_EQ(0.0f, 0.0f, 1.0f, 0.0f, A);
+ EXPECT_ROW4_EQ(0.0f, 0.0f, 0.0f, 1.0f, A);
+}
+
+TEST(XFormTest, verifyTranslate3d) {
+ Transform A;
+ A.Translate3d(2.0, 3.0, 4.0);
+ EXPECT_ROW1_EQ(1.0f, 0.0f, 0.0f, 2.0f, A);
+ EXPECT_ROW2_EQ(0.0f, 1.0f, 0.0f, 3.0f, A);
+ EXPECT_ROW3_EQ(0.0f, 0.0f, 1.0f, 4.0f, A);
+ EXPECT_ROW4_EQ(0.0f, 0.0f, 0.0f, 1.0f, A);
+
+ // Verify that Translate3d() post-multiplies the existing matrix.
+ A.MakeIdentity();
+ A.Scale3d(6.0, 7.0, 8.0);
+ A.Translate3d(2.0, 3.0, 4.0);
+ EXPECT_ROW1_EQ(6.0f, 0.0f, 0.0f, 12.0f, A);
+ EXPECT_ROW2_EQ(0.0f, 7.0f, 0.0f, 21.0f, A);
+ EXPECT_ROW3_EQ(0.0f, 0.0f, 8.0f, 32.0f, A);
+ EXPECT_ROW4_EQ(0.0f, 0.0f, 0.0f, 1.0f, A);
+}
+
+TEST(XFormTest, verifyScale) {
+ Transform A;
+ A.Scale(6.0, 7.0);
+ EXPECT_ROW1_EQ(6.0f, 0.0f, 0.0f, 0.0f, A);
+ EXPECT_ROW2_EQ(0.0f, 7.0f, 0.0f, 0.0f, A);
+ EXPECT_ROW3_EQ(0.0f, 0.0f, 1.0f, 0.0f, A);
+ EXPECT_ROW4_EQ(0.0f, 0.0f, 0.0f, 1.0f, A);
+
+ // Verify that Scale() post-multiplies the existing matrix.
+ A.MakeIdentity();
+ A.Translate3d(2.0, 3.0, 4.0);
+ A.Scale(6.0, 7.0);
+ EXPECT_ROW1_EQ(6.0f, 0.0f, 0.0f, 2.0f, A);
+ EXPECT_ROW2_EQ(0.0f, 7.0f, 0.0f, 3.0f, A);
+ EXPECT_ROW3_EQ(0.0f, 0.0f, 1.0f, 4.0f, A);
+ EXPECT_ROW4_EQ(0.0f, 0.0f, 0.0f, 1.0f, A);
+}
+
+TEST(XFormTest, verifyScale3d) {
+ Transform A;
+ A.Scale3d(6.0, 7.0, 8.0);
+ EXPECT_ROW1_EQ(6.0f, 0.0f, 0.0f, 0.0f, A);
+ EXPECT_ROW2_EQ(0.0f, 7.0f, 0.0f, 0.0f, A);
+ EXPECT_ROW3_EQ(0.0f, 0.0f, 8.0f, 0.0f, A);
+ EXPECT_ROW4_EQ(0.0f, 0.0f, 0.0f, 1.0f, A);
+
+ // Verify that scale3d() post-multiplies the existing matrix.
+ A.MakeIdentity();
+ A.Translate3d(2.0, 3.0, 4.0);
+ A.Scale3d(6.0, 7.0, 8.0);
+ EXPECT_ROW1_EQ(6.0f, 0.0f, 0.0f, 2.0f, A);
+ EXPECT_ROW2_EQ(0.0f, 7.0f, 0.0f, 3.0f, A);
+ EXPECT_ROW3_EQ(0.0f, 0.0f, 8.0f, 4.0f, A);
+ EXPECT_ROW4_EQ(0.0f, 0.0f, 0.0f, 1.0f, A);
+}
+
+TEST(XFormTest, verifyRotate) {
+ Transform A;
+ A.Rotate(90.0);
+ EXPECT_ROW1_NEAR(0.0, -1.0, 0.0, 0.0, A, ERROR_THRESHOLD);
+ EXPECT_ROW2_NEAR(1.0, 0.0, 0.0, 0.0, A, ERROR_THRESHOLD);
+ EXPECT_ROW3_EQ(0.0f, 0.0f, 1.0f, 0.0f, A);
+ EXPECT_ROW4_EQ(0.0f, 0.0f, 0.0f, 1.0f, A);
+
+ // Verify that Rotate() post-multiplies the existing matrix.
+ A.MakeIdentity();
+ A.Scale3d(6.0, 7.0, 8.0);
+ A.Rotate(90.0);
+ EXPECT_ROW1_NEAR(0.0, -6.0, 0.0, 0.0, A, ERROR_THRESHOLD);
+ EXPECT_ROW2_NEAR(7.0, 0.0, 0.0, 0.0, A, ERROR_THRESHOLD);
+ EXPECT_ROW3_EQ(0.0f, 0.0f, 8.0f, 0.0f, A);
+ EXPECT_ROW4_EQ(0.0f, 0.0f, 0.0f, 1.0f, A);
+}
+
+TEST(XFormTest, verifyRotateAboutXAxis) {
+ Transform A;
+ double sin45 = 0.5 * sqrt(2.0);
+ double cos45 = sin45;
+
+ A.MakeIdentity();
+ A.RotateAboutXAxis(90.0);
+ EXPECT_ROW1_EQ(1.0f, 0.0f, 0.0f, 0.0f, A);
+ EXPECT_ROW2_NEAR(0.0, 0.0, -1.0, 0.0, A, ERROR_THRESHOLD);
+ EXPECT_ROW3_NEAR(0.0, 1.0, 0.0, 0.0, A, ERROR_THRESHOLD);
+ EXPECT_ROW4_EQ(0.0f, 0.0f, 0.0f, 1.0f, A);
+
+ A.MakeIdentity();
+ A.RotateAboutXAxis(45.0);
+ EXPECT_ROW1_EQ(1.0f, 0.0f, 0.0f, 0.0f, A);
+ EXPECT_ROW2_NEAR(0.0, cos45, -sin45, 0.0, A, ERROR_THRESHOLD);
+ EXPECT_ROW3_NEAR(0.0, sin45, cos45, 0.0, A, ERROR_THRESHOLD);
+ EXPECT_ROW4_EQ(0.0f, 0.0f, 0.0f, 1.0f, A);
+
+ // Verify that RotateAboutXAxis(angle) post-multiplies the existing matrix.
+ A.MakeIdentity();
+ A.Scale3d(6.0, 7.0, 8.0);
+ A.RotateAboutXAxis(90.0);
+ EXPECT_ROW1_NEAR(6.0, 0.0, 0.0, 0.0, A, ERROR_THRESHOLD);
+ EXPECT_ROW2_NEAR(0.0, 0.0, -7.0, 0.0, A, ERROR_THRESHOLD);
+ EXPECT_ROW3_NEAR(0.0, 8.0, 0.0, 0.0, A, ERROR_THRESHOLD);
+ EXPECT_ROW4_EQ(0.0f, 0.0f, 0.0f, 1.0f, A);
+}
+
+TEST(XFormTest, verifyRotateAboutYAxis) {
+ Transform A;
+ double sin45 = 0.5 * sqrt(2.0);
+ double cos45 = sin45;
+
+ // Note carefully, the expected pattern is inverted compared to rotating
+ // about x axis or z axis.
+ A.MakeIdentity();
+ A.RotateAboutYAxis(90.0);
+ EXPECT_ROW1_NEAR(0.0, 0.0, 1.0, 0.0, A, ERROR_THRESHOLD);
+ EXPECT_ROW2_EQ(0.0f, 1.0f, 0.0f, 0.0f, A);
+ EXPECT_ROW3_NEAR(-1.0, 0.0, 0.0, 0.0, A, ERROR_THRESHOLD);
+ EXPECT_ROW4_EQ(0.0f, 0.0f, 0.0f, 1.0f, A);
+
+ A.MakeIdentity();
+ A.RotateAboutYAxis(45.0);
+ EXPECT_ROW1_NEAR(cos45, 0.0, sin45, 0.0, A, ERROR_THRESHOLD);
+ EXPECT_ROW2_EQ(0.0f, 1.0f, 0.0f, 0.0f, A);
+ EXPECT_ROW3_NEAR(-sin45, 0.0, cos45, 0.0, A, ERROR_THRESHOLD);
+ EXPECT_ROW4_EQ(0.0f, 0.0f, 0.0f, 1.0f, A);
+
+ // Verify that RotateAboutYAxis(angle) post-multiplies the existing matrix.
+ A.MakeIdentity();
+ A.Scale3d(6.0, 7.0, 8.0);
+ A.RotateAboutYAxis(90.0);
+ EXPECT_ROW1_NEAR(0.0, 0.0, 6.0, 0.0, A, ERROR_THRESHOLD);
+ EXPECT_ROW2_NEAR(0.0, 7.0, 0.0, 0.0, A, ERROR_THRESHOLD);
+ EXPECT_ROW3_NEAR(-8.0, 0.0, 0.0, 0.0, A, ERROR_THRESHOLD);
+ EXPECT_ROW4_EQ(0.0f, 0.0f, 0.0f, 1.0f, A);
+}
+
+TEST(XFormTest, verifyRotateAboutZAxis) {
+ Transform A;
+ double sin45 = 0.5 * sqrt(2.0);
+ double cos45 = sin45;
+
+ A.MakeIdentity();
+ A.RotateAboutZAxis(90.0);
+ EXPECT_ROW1_NEAR(0.0, -1.0, 0.0, 0.0, A, ERROR_THRESHOLD);
+ EXPECT_ROW2_NEAR(1.0, 0.0, 0.0, 0.0, A, ERROR_THRESHOLD);
+ EXPECT_ROW3_EQ(0.0f, 0.0f, 1.0f, 0.0f, A);
+ EXPECT_ROW4_EQ(0.0f, 0.0f, 0.0f, 1.0f, A);
+
+ A.MakeIdentity();
+ A.RotateAboutZAxis(45.0);
+ EXPECT_ROW1_NEAR(cos45, -sin45, 0.0, 0.0, A, ERROR_THRESHOLD);
+ EXPECT_ROW2_NEAR(sin45, cos45, 0.0, 0.0, A, ERROR_THRESHOLD);
+ EXPECT_ROW3_EQ(0.0f, 0.0f, 1.0f, 0.0f, A);
+ EXPECT_ROW4_EQ(0.0f, 0.0f, 0.0f, 1.0f, A);
+
+ // Verify that RotateAboutZAxis(angle) post-multiplies the existing matrix.
+ A.MakeIdentity();
+ A.Scale3d(6.0, 7.0, 8.0);
+ A.RotateAboutZAxis(90.0);
+ EXPECT_ROW1_NEAR(0.0, -6.0, 0.0, 0.0, A, ERROR_THRESHOLD);
+ EXPECT_ROW2_NEAR(7.0, 0.0, 0.0, 0.0, A, ERROR_THRESHOLD);
+ EXPECT_ROW3_EQ(0.0f, 0.0f, 8.0f, 0.0f, A);
+ EXPECT_ROW4_EQ(0.0f, 0.0f, 0.0f, 1.0f, A);
+}
+
+TEST(XFormTest, verifyRotateAboutForAlignedAxes) {
+ Transform A;
+
+ // Check rotation about z-axis
+ A.MakeIdentity();
+ A.RotateAbout(Vector3dF(0.0, 0.0, 1.0), 90.0);
+ EXPECT_ROW1_NEAR(0.0, -1.0, 0.0, 0.0, A, ERROR_THRESHOLD);
+ EXPECT_ROW2_NEAR(1.0, 0.0, 0.0, 0.0, A, ERROR_THRESHOLD);
+ EXPECT_ROW3_EQ(0.0f, 0.0f, 1.0f, 0.0f, A);
+ EXPECT_ROW4_EQ(0.0f, 0.0f, 0.0f, 1.0f, A);
+
+ // Check rotation about x-axis
+ A.MakeIdentity();
+ A.RotateAbout(Vector3dF(1.0, 0.0, 0.0), 90.0);
+ EXPECT_ROW1_EQ(1.0f, 0.0f, 0.0f, 0.0f, A);
+ EXPECT_ROW2_NEAR(0.0, 0.0, -1.0, 0.0, A, ERROR_THRESHOLD);
+ EXPECT_ROW3_NEAR(0.0, 1.0, 0.0, 0.0, A, ERROR_THRESHOLD);
+ EXPECT_ROW4_EQ(0.0f, 0.0f, 0.0f, 1.0f, A);
+
+ // Check rotation about y-axis. Note carefully, the expected pattern is
+ // inverted compared to rotating about x axis or z axis.
+ A.MakeIdentity();
+ A.RotateAbout(Vector3dF(0.0, 1.0, 0.0), 90.0);
+ EXPECT_ROW1_NEAR(0.0, 0.0, 1.0, 0.0, A, ERROR_THRESHOLD);
+ EXPECT_ROW2_EQ(0.0f, 1.0f, 0.0f, 0.0f, A);
+ EXPECT_ROW3_NEAR(-1.0, 0.0, 0.0, 0.0, A, ERROR_THRESHOLD);
+ EXPECT_ROW4_EQ(0.0f, 0.0f, 0.0f, 1.0f, A);
+
+ // Verify that rotate3d(axis, angle) post-multiplies the existing matrix.
+ A.MakeIdentity();
+ A.Scale3d(6.0, 7.0, 8.0);
+ A.RotateAboutZAxis(90.0);
+ EXPECT_ROW1_NEAR(0.0, -6.0, 0.0, 0.0, A, ERROR_THRESHOLD);
+ EXPECT_ROW2_NEAR(7.0, 0.0, 0.0, 0.0, A, ERROR_THRESHOLD);
+ EXPECT_ROW3_EQ(0.0f, 0.0f, 8.0f, 0.0f, A);
+ EXPECT_ROW4_EQ(0.0f, 0.0f, 0.0f, 1.0f, A);
+}
+
+TEST(XFormTest, verifyRotateAboutForArbitraryAxis) {
+ // Check rotation about an arbitrary non-axis-aligned vector.
+ Transform A;
+ A.RotateAbout(Vector3dF(1.0, 1.0, 1.0), 90.0);
+ EXPECT_ROW1_NEAR(0.3333333333333334258519187,
+ -0.2440169358562924717404030,
+ 0.9106836025229592124219380,
+ 0.0, A, ERROR_THRESHOLD);
+ EXPECT_ROW2_NEAR(0.9106836025229592124219380,
+ 0.3333333333333334258519187,
+ -0.2440169358562924717404030,
+ 0.0, A, ERROR_THRESHOLD);
+ EXPECT_ROW3_NEAR(-0.2440169358562924717404030,
+ 0.9106836025229592124219380,
+ 0.3333333333333334258519187,
+ 0.0, A, ERROR_THRESHOLD);
+ EXPECT_ROW4_EQ(0.0f, 0.0f, 0.0f, 1.0f, A);
+}
+
+TEST(XFormTest, verifyRotateAboutForDegenerateAxis) {
+ // Check rotation about a degenerate zero vector.
+ // It is expected to skip applying the rotation.
+ Transform A;
+
+ A.RotateAbout(Vector3dF(0.0, 0.0, 0.0), 45.0);
+ // Verify that A remains unchanged.
+ EXPECT_TRUE(A.IsIdentity());
+
+ InitializeTestMatrix(&A);
+ A.RotateAbout(Vector3dF(0.0, 0.0, 0.0), 35.0);
+
+ // Verify that A remains unchanged.
+ EXPECT_ROW1_EQ(10.0f, 14.0f, 18.0f, 22.0f, A);
+ EXPECT_ROW2_EQ(11.0f, 15.0f, 19.0f, 23.0f, A);
+ EXPECT_ROW3_EQ(12.0f, 16.0f, 20.0f, 24.0f, A);
+ EXPECT_ROW4_EQ(13.0f, 17.0f, 21.0f, 25.0f, A);
+}
+
+TEST(XFormTest, verifySkewX) {
+ Transform A;
+ A.SkewX(45.0);
+ EXPECT_ROW1_EQ(1.0f, 1.0f, 0.0f, 0.0f, A);
+ EXPECT_ROW2_EQ(0.0f, 1.0f, 0.0f, 0.0f, A);
+ EXPECT_ROW3_EQ(0.0f, 0.0f, 1.0f, 0.0f, A);
+ EXPECT_ROW4_EQ(0.0f, 0.0f, 0.0f, 1.0f, A);
+
+ // Verify that skewX() post-multiplies the existing matrix. Row 1, column 2,
+ // would incorrectly have value "7" if the matrix is pre-multiplied instead
+ // of post-multiplied.
+ A.MakeIdentity();
+ A.Scale3d(6.0, 7.0, 8.0);
+ A.SkewX(45.0);
+ EXPECT_ROW1_EQ(6.0f, 6.0f, 0.0f, 0.0f, A);
+ EXPECT_ROW2_EQ(0.0f, 7.0f, 0.0f, 0.0f, A);
+ EXPECT_ROW3_EQ(0.0f, 0.0f, 8.0f, 0.0f, A);
+ EXPECT_ROW4_EQ(0.0f, 0.0f, 0.0f, 1.0f, A);
+}
+
+TEST(XFormTest, verifySkewY) {
+ Transform A;
+ A.SkewY(45.0);
+ EXPECT_ROW1_EQ(1.0f, 0.0f, 0.0f, 0.0f, A);
+ EXPECT_ROW2_EQ(1.0f, 1.0f, 0.0f, 0.0f, A);
+ EXPECT_ROW3_EQ(0.0f, 0.0f, 1.0f, 0.0f, A);
+ EXPECT_ROW4_EQ(0.0f, 0.0f, 0.0f, 1.0f, A);
+
+ // Verify that skewY() post-multiplies the existing matrix. Row 2, column 1 ,
+ // would incorrectly have value "6" if the matrix is pre-multiplied instead
+ // of post-multiplied.
+ A.MakeIdentity();
+ A.Scale3d(6.0, 7.0, 8.0);
+ A.SkewY(45.0);
+ EXPECT_ROW1_EQ(6.0f, 0.0f, 0.0f, 0.0f, A);
+ EXPECT_ROW2_EQ(7.0f, 7.0f, 0.0f, 0.0f, A);
+ EXPECT_ROW3_EQ(0.0f, 0.0f, 8.0f, 0.0f, A);
+ EXPECT_ROW4_EQ(0.0f, 0.0f, 0.0f, 1.0f, A);
+}
+
+TEST(XFormTest, verifyPerspectiveDepth) {
+ Transform A;
+ A.ApplyPerspectiveDepth(1.0);
+ EXPECT_ROW1_EQ(1.0f, 0.0f, 0.0f, 0.0f, A);
+ EXPECT_ROW2_EQ(0.0f, 1.0f, 0.0f, 0.0f, A);
+ EXPECT_ROW3_EQ(0.0f, 0.0f, 1.0f, 0.0f, A);
+ EXPECT_ROW4_EQ(0.0f, 0.0f, -1.0f, 1.0f, A);
+
+ // Verify that PerspectiveDepth() post-multiplies the existing matrix.
+ A.MakeIdentity();
+ A.Translate3d(2.0, 3.0, 4.0);
+ A.ApplyPerspectiveDepth(1.0);
+ EXPECT_ROW1_EQ(1.0f, 0.0f, -2.0f, 2.0f, A);
+ EXPECT_ROW2_EQ(0.0f, 1.0f, -3.0f, 3.0f, A);
+ EXPECT_ROW3_EQ(0.0f, 0.0f, -3.0f, 4.0f, A);
+ EXPECT_ROW4_EQ(0.0f, 0.0f, -1.0f, 1.0f, A);
+}
+
+TEST(XFormTest, verifyHasPerspective) {
+ Transform A;
+ A.ApplyPerspectiveDepth(1.0);
+ EXPECT_TRUE(A.HasPerspective());
+
+ A.MakeIdentity();
+ A.ApplyPerspectiveDepth(0.0);
+ EXPECT_FALSE(A.HasPerspective());
+
+ A.MakeIdentity();
+ A.matrix().setDouble(3, 0, -1.0);
+ EXPECT_TRUE(A.HasPerspective());
+
+ A.MakeIdentity();
+ A.matrix().setDouble(3, 1, -1.0);
+ EXPECT_TRUE(A.HasPerspective());
+
+ A.MakeIdentity();
+ A.matrix().setDouble(3, 2, -0.3);
+ EXPECT_TRUE(A.HasPerspective());
+
+ A.MakeIdentity();
+ A.matrix().setDouble(3, 3, 0.5);
+ EXPECT_TRUE(A.HasPerspective());
+
+ A.MakeIdentity();
+ A.matrix().setDouble(3, 3, 0.0);
+ EXPECT_TRUE(A.HasPerspective());
+}
+
+TEST(XFormTest, verifyIsInvertible) {
+ Transform A;
+
+ // Translations, rotations, scales, skews and arbitrary combinations of them
+ // are invertible.
+ A.MakeIdentity();
+ EXPECT_TRUE(A.IsInvertible());
+
+ A.MakeIdentity();
+ A.Translate3d(2.0, 3.0, 4.0);
+ EXPECT_TRUE(A.IsInvertible());
+
+ A.MakeIdentity();
+ A.Scale3d(6.0, 7.0, 8.0);
+ EXPECT_TRUE(A.IsInvertible());
+
+ A.MakeIdentity();
+ A.RotateAboutXAxis(10.0);
+ A.RotateAboutYAxis(20.0);
+ A.RotateAboutZAxis(30.0);
+ EXPECT_TRUE(A.IsInvertible());
+
+ A.MakeIdentity();
+ A.SkewX(45.0);
+ EXPECT_TRUE(A.IsInvertible());
+
+ // A perspective matrix (projection plane at z=0) is invertible. The
+ // intuitive explanation is that perspective is eqivalent to a skew of the
+ // w-axis; skews are invertible.
+ A.MakeIdentity();
+ A.ApplyPerspectiveDepth(1.0);
+ EXPECT_TRUE(A.IsInvertible());
+
+ // A "pure" perspective matrix derived by similar triangles, with m44() set
+ // to zero (i.e. camera positioned at the origin), is not invertible.
+ A.MakeIdentity();
+ A.ApplyPerspectiveDepth(1.0);
+ A.matrix().setDouble(3, 3, 0.0);
+ EXPECT_FALSE(A.IsInvertible());
+
+ // Adding more to a non-invertible matrix will not make it invertible in the
+ // general case.
+ A.MakeIdentity();
+ A.ApplyPerspectiveDepth(1.0);
+ A.matrix().setDouble(3, 3, 0.0);
+ A.Scale3d(6.0, 7.0, 8.0);
+ A.RotateAboutXAxis(10.0);
+ A.RotateAboutYAxis(20.0);
+ A.RotateAboutZAxis(30.0);
+ A.Translate3d(6.0, 7.0, 8.0);
+ EXPECT_FALSE(A.IsInvertible());
+
+ // A degenerate matrix of all zeros is not invertible.
+ A.MakeIdentity();
+ A.matrix().setDouble(0, 0, 0.0);
+ A.matrix().setDouble(1, 1, 0.0);
+ A.matrix().setDouble(2, 2, 0.0);
+ A.matrix().setDouble(3, 3, 0.0);
+ EXPECT_FALSE(A.IsInvertible());
+}
+
+TEST(XFormTest, verifyIsIdentity) {
+ Transform A;
+
+ InitializeTestMatrix(&A);
+ EXPECT_FALSE(A.IsIdentity());
+
+ A.MakeIdentity();
+ EXPECT_TRUE(A.IsIdentity());
+
+ // Modifying any one individual element should cause the matrix to no longer
+ // be identity.
+ A.MakeIdentity();
+ A.matrix().setDouble(0, 0, 2.0);
+ EXPECT_FALSE(A.IsIdentity());
+
+ A.MakeIdentity();
+ A.matrix().setDouble(1, 0, 2.0);
+ EXPECT_FALSE(A.IsIdentity());
+
+ A.MakeIdentity();
+ A.matrix().setDouble(2, 0, 2.0);
+ EXPECT_FALSE(A.IsIdentity());
+
+ A.MakeIdentity();
+ A.matrix().setDouble(3, 0, 2.0);
+ EXPECT_FALSE(A.IsIdentity());
+
+ A.MakeIdentity();
+ A.matrix().setDouble(0, 1, 2.0);
+ EXPECT_FALSE(A.IsIdentity());
+
+ A.MakeIdentity();
+ A.matrix().setDouble(1, 1, 2.0);
+ EXPECT_FALSE(A.IsIdentity());
+
+ A.MakeIdentity();
+ A.matrix().setDouble(2, 1, 2.0);
+ EXPECT_FALSE(A.IsIdentity());
+
+ A.MakeIdentity();
+ A.matrix().setDouble(3, 1, 2.0);
+ EXPECT_FALSE(A.IsIdentity());
+
+ A.MakeIdentity();
+ A.matrix().setDouble(0, 2, 2.0);
+ EXPECT_FALSE(A.IsIdentity());
+
+ A.MakeIdentity();
+ A.matrix().setDouble(1, 2, 2.0);
+ EXPECT_FALSE(A.IsIdentity());
+
+ A.MakeIdentity();
+ A.matrix().setDouble(2, 2, 2.0);
+ EXPECT_FALSE(A.IsIdentity());
+
+ A.MakeIdentity();
+ A.matrix().setDouble(3, 2, 2.0);
+ EXPECT_FALSE(A.IsIdentity());
+
+ A.MakeIdentity();
+ A.matrix().setDouble(0, 3, 2.0);
+ EXPECT_FALSE(A.IsIdentity());
+
+ A.MakeIdentity();
+ A.matrix().setDouble(1, 3, 2.0);
+ EXPECT_FALSE(A.IsIdentity());
+
+ A.MakeIdentity();
+ A.matrix().setDouble(2, 3, 2.0);
+ EXPECT_FALSE(A.IsIdentity());
+
+ A.MakeIdentity();
+ A.matrix().setDouble(3, 3, 2.0);
+ EXPECT_FALSE(A.IsIdentity());
+}
+
+TEST(XFormTest, verifyIsIdentityOrTranslation) {
+ Transform A;
+
+ InitializeTestMatrix(&A);
+ EXPECT_FALSE(A.IsIdentityOrTranslation());
+
+ A.MakeIdentity();
+ EXPECT_TRUE(A.IsIdentityOrTranslation());
+
+ // Modifying any non-translation components should cause
+ // IsIdentityOrTranslation() to return false. NOTE: (0, 3), (1, 3), and
+ // (2, 3) are the translation components, so modifying them should still
+ // return true.
+ A.MakeIdentity();
+ A.matrix().setDouble(0, 0, 2.0);
+ EXPECT_FALSE(A.IsIdentityOrTranslation());
+
+ A.MakeIdentity();
+ A.matrix().setDouble(1, 0, 2.0);
+ EXPECT_FALSE(A.IsIdentityOrTranslation());
+
+ A.MakeIdentity();
+ A.matrix().setDouble(2, 0, 2.0);
+ EXPECT_FALSE(A.IsIdentityOrTranslation());
+
+ A.MakeIdentity();
+ A.matrix().setDouble(3, 0, 2.0);
+ EXPECT_FALSE(A.IsIdentityOrTranslation());
+
+ A.MakeIdentity();
+ A.matrix().setDouble(0, 1, 2.0);
+ EXPECT_FALSE(A.IsIdentityOrTranslation());
+
+ A.MakeIdentity();
+ A.matrix().setDouble(1, 1, 2.0);
+ EXPECT_FALSE(A.IsIdentityOrTranslation());
+
+ A.MakeIdentity();
+ A.matrix().setDouble(2, 1, 2.0);
+ EXPECT_FALSE(A.IsIdentityOrTranslation());
+
+ A.MakeIdentity();
+ A.matrix().setDouble(3, 1, 2.0);
+ EXPECT_FALSE(A.IsIdentityOrTranslation());
+
+ A.MakeIdentity();
+ A.matrix().setDouble(0, 2, 2.0);
+ EXPECT_FALSE(A.IsIdentityOrTranslation());
+
+ A.MakeIdentity();
+ A.matrix().setDouble(1, 2, 2.0);
+ EXPECT_FALSE(A.IsIdentityOrTranslation());
+
+ A.MakeIdentity();
+ A.matrix().setDouble(2, 2, 2.0);
+ EXPECT_FALSE(A.IsIdentityOrTranslation());
+
+ A.MakeIdentity();
+ A.matrix().setDouble(3, 2, 2.0);
+ EXPECT_FALSE(A.IsIdentityOrTranslation());
+
+ // Note carefully - expecting true here.
+ A.MakeIdentity();
+ A.matrix().setDouble(0, 3, 2.0);
+ EXPECT_TRUE(A.IsIdentityOrTranslation());
+
+ // Note carefully - expecting true here.
+ A.MakeIdentity();
+ A.matrix().setDouble(1, 3, 2.0);
+ EXPECT_TRUE(A.IsIdentityOrTranslation());
+
+ // Note carefully - expecting true here.
+ A.MakeIdentity();
+ A.matrix().setDouble(2, 3, 2.0);
+ EXPECT_TRUE(A.IsIdentityOrTranslation());
+
+ A.MakeIdentity();
+ A.matrix().setDouble(3, 3, 2.0);
+ EXPECT_FALSE(A.IsIdentityOrTranslation());
+}
+
+TEST(XFormTest, verifyIsScaleOrTranslation) {
+ Transform A;
+
+ InitializeTestMatrix(&A);
+ EXPECT_FALSE(A.IsScaleOrTranslation());
+
+ A.MakeIdentity();
+ EXPECT_TRUE(A.IsScaleOrTranslation());
+
+ // Modifying any non-scale or non-translation components should cause
+ // IsScaleOrTranslation() to return false. (0, 0), (1, 1), (2, 2), (0, 3),
+ // (1, 3), and (2, 3) are the scale and translation components, so
+ // modifying them should still return true.
+
+ // Note carefully - expecting true here.
+ A.MakeIdentity();
+ A.matrix().setDouble(0, 0, 2.0);
+ EXPECT_TRUE(A.IsScaleOrTranslation());
+
+ A.MakeIdentity();
+ A.matrix().setDouble(1, 0, 2.0);
+ EXPECT_FALSE(A.IsScaleOrTranslation());
+
+ A.MakeIdentity();
+ A.matrix().setDouble(2, 0, 2.0);
+ EXPECT_FALSE(A.IsScaleOrTranslation());
+
+ A.MakeIdentity();
+ A.matrix().setDouble(3, 0, 2.0);
+ EXPECT_FALSE(A.IsScaleOrTranslation());
+
+ A.MakeIdentity();
+ A.matrix().setDouble(0, 1, 2.0);
+ EXPECT_FALSE(A.IsScaleOrTranslation());
+
+ // Note carefully - expecting true here.
+ A.MakeIdentity();
+ A.matrix().setDouble(1, 1, 2.0);
+ EXPECT_TRUE(A.IsScaleOrTranslation());
+
+ A.MakeIdentity();
+ A.matrix().setDouble(2, 1, 2.0);
+ EXPECT_FALSE(A.IsScaleOrTranslation());
+
+ A.MakeIdentity();
+ A.matrix().setDouble(3, 1, 2.0);
+ EXPECT_FALSE(A.IsScaleOrTranslation());
+
+ A.MakeIdentity();
+ A.matrix().setDouble(0, 2, 2.0);
+ EXPECT_FALSE(A.IsScaleOrTranslation());
+
+ A.MakeIdentity();
+ A.matrix().setDouble(1, 2, 2.0);
+ EXPECT_FALSE(A.IsScaleOrTranslation());
+
+ // Note carefully - expecting true here.
+ A.MakeIdentity();
+ A.matrix().setDouble(2, 2, 2.0);
+ EXPECT_TRUE(A.IsScaleOrTranslation());
+
+ A.MakeIdentity();
+ A.matrix().setDouble(3, 2, 2.0);
+ EXPECT_FALSE(A.IsScaleOrTranslation());
+
+ // Note carefully - expecting true here.
+ A.MakeIdentity();
+ A.matrix().setDouble(0, 3, 2.0);
+ EXPECT_TRUE(A.IsScaleOrTranslation());
+
+ // Note carefully - expecting true here.
+ A.MakeIdentity();
+ A.matrix().setDouble(1, 3, 2.0);
+ EXPECT_TRUE(A.IsScaleOrTranslation());
+
+ // Note carefully - expecting true here.
+ A.MakeIdentity();
+ A.matrix().setDouble(2, 3, 2.0);
+ EXPECT_TRUE(A.IsScaleOrTranslation());
+
+ A.MakeIdentity();
+ A.matrix().setDouble(3, 3, 2.0);
+ EXPECT_FALSE(A.IsScaleOrTranslation());
+}
+
+TEST(XFormTest, verifyFlattenTo2d) {
+ Transform A;
+ InitializeTestMatrix(&A);
+
+ A.FlattenTo2d();
+ EXPECT_ROW1_EQ(10.0f, 14.0f, 0.0f, 22.0f, A);
+ EXPECT_ROW2_EQ(11.0f, 15.0f, 0.0f, 23.0f, A);
+ EXPECT_ROW3_EQ(0.0f, 0.0f, 1.0f, 0.0f, A);
+ EXPECT_ROW4_EQ(13.0f, 17.0f, 0.0f, 25.0f, A);
+}
+
+// Another implementation of Preserves2dAxisAlignment that isn't as fast,
+// good for testing the faster implementation.
+static bool EmpiricallyPreserves2dAxisAlignment(const Transform& transform) {
+ Point3F p1(5.0f, 5.0f, 0.0f);
+ Point3F p2(10.0f, 5.0f, 0.0f);
+ Point3F p3(10.0f, 20.0f, 0.0f);
+ Point3F p4(5.0f, 20.0f, 0.0f);
+
+ QuadF test_quad(PointF(p1.x(), p1.y()),
+ PointF(p2.x(), p2.y()),
+ PointF(p3.x(), p3.y()),
+ PointF(p4.x(), p4.y()));
+ EXPECT_TRUE(test_quad.IsRectilinear());
+
+ transform.TransformPoint(p1);
+ transform.TransformPoint(p2);
+ transform.TransformPoint(p3);
+ transform.TransformPoint(p4);
+
+ QuadF transformedQuad(PointF(p1.x(), p1.y()),
+ PointF(p2.x(), p2.y()),
+ PointF(p3.x(), p3.y()),
+ PointF(p4.x(), p4.y()));
+ return transformedQuad.IsRectilinear();
+}
+
+TEST(XFormTest, Preserves2dAxisAlignment) {
+ static const struct TestCase {
+ double a; // row 1, column 1
+ double b; // row 1, column 2
+ double c; // row 2, column 1
+ double d; // row 2, column 2
+ bool expected;
+ } test_cases[] = {
+ { 3.0, 0.0,
+ 0.0, 4.0, true }, // basic case
+ { 0.0, 4.0,
+ 3.0, 0.0, true }, // rotate by 90
+ { 0.0, 0.0,
+ 0.0, 4.0, true }, // degenerate x
+ { 3.0, 0.0,
+ 0.0, 0.0, true }, // degenerate y
+ { 0.0, 0.0,
+ 3.0, 0.0, true }, // degenerate x + rotate by 90
+ { 0.0, 4.0,
+ 0.0, 0.0, true }, // degenerate y + rotate by 90
+ { 3.0, 4.0,
+ 0.0, 0.0, false },
+ { 0.0, 0.0,
+ 3.0, 4.0, false },
+ { 0.0, 3.0,
+ 0.0, 4.0, false },
+ { 3.0, 0.0,
+ 4.0, 0.0, false },
+ { 3.0, 4.0,
+ 5.0, 0.0, false },
+ { 3.0, 4.0,
+ 0.0, 5.0, false },
+ { 3.0, 0.0,
+ 4.0, 5.0, false },
+ { 0.0, 3.0,
+ 4.0, 5.0, false },
+ { 2.0, 3.0,
+ 4.0, 5.0, false },
+ };
+
+ Transform transform;
+ for (size_t i = 0; i < ARRAYSIZE_UNSAFE(test_cases); ++i) {
+ const TestCase& value = test_cases[i];
+ transform.MakeIdentity();
+ transform.matrix().setDouble(0, 0, value.a);
+ transform.matrix().setDouble(0, 1, value.b);
+ transform.matrix().setDouble(1, 0, value.c);
+ transform.matrix().setDouble(1, 1, value.d);
+
+ if (value.expected) {
+ EXPECT_TRUE(EmpiricallyPreserves2dAxisAlignment(transform));
+ EXPECT_TRUE(transform.Preserves2dAxisAlignment());
+ } else {
+ EXPECT_FALSE(EmpiricallyPreserves2dAxisAlignment(transform));
+ EXPECT_FALSE(transform.Preserves2dAxisAlignment());
+ }
+ }
+
+ // Try the same test cases again, but this time make sure that other matrix
+ // elements (except perspective) have entries, to test that they are ignored.
+ for (size_t i = 0; i < ARRAYSIZE_UNSAFE(test_cases); ++i) {
+ const TestCase& value = test_cases[i];
+ transform.MakeIdentity();
+ transform.matrix().setDouble(0, 0, value.a);
+ transform.matrix().setDouble(0, 1, value.b);
+ transform.matrix().setDouble(1, 0, value.c);
+ transform.matrix().setDouble(1, 1, value.d);
+
+ transform.matrix().setDouble(0, 2, 1.0);
+ transform.matrix().setDouble(0, 3, 2.0);
+ transform.matrix().setDouble(1, 2, 3.0);
+ transform.matrix().setDouble(1, 3, 4.0);
+ transform.matrix().setDouble(2, 0, 5.0);
+ transform.matrix().setDouble(2, 1, 6.0);
+ transform.matrix().setDouble(2, 2, 7.0);
+ transform.matrix().setDouble(2, 3, 8.0);
+
+ if (value.expected) {
+ EXPECT_TRUE(EmpiricallyPreserves2dAxisAlignment(transform));
+ EXPECT_TRUE(transform.Preserves2dAxisAlignment());
+ } else {
+ EXPECT_FALSE(EmpiricallyPreserves2dAxisAlignment(transform));
+ EXPECT_FALSE(transform.Preserves2dAxisAlignment());
+ }
+ }
+
+ // Try the same test cases again, but this time add perspective which is
+ // always assumed to not-preserve axis alignment.
+ for (size_t i = 0; i < ARRAYSIZE_UNSAFE(test_cases); ++i) {
+ const TestCase& value = test_cases[i];
+ transform.MakeIdentity();
+ transform.matrix().setDouble(0, 0, value.a);
+ transform.matrix().setDouble(0, 1, value.b);
+ transform.matrix().setDouble(1, 0, value.c);
+ transform.matrix().setDouble(1, 1, value.d);
+
+ transform.matrix().setDouble(0, 2, 1.0);
+ transform.matrix().setDouble(0, 3, 2.0);
+ transform.matrix().setDouble(1, 2, 3.0);
+ transform.matrix().setDouble(1, 3, 4.0);
+ transform.matrix().setDouble(2, 0, 5.0);
+ transform.matrix().setDouble(2, 1, 6.0);
+ transform.matrix().setDouble(2, 2, 7.0);
+ transform.matrix().setDouble(2, 3, 8.0);
+ transform.matrix().setDouble(3, 0, 9.0);
+ transform.matrix().setDouble(3, 1, 10.0);
+ transform.matrix().setDouble(3, 2, 11.0);
+ transform.matrix().setDouble(3, 3, 12.0);
+
+ EXPECT_FALSE(EmpiricallyPreserves2dAxisAlignment(transform));
+ EXPECT_FALSE(transform.Preserves2dAxisAlignment());
+ }
+
+ // Try a few more practical situations to check precision
+ transform.MakeIdentity();
+ transform.RotateAboutZAxis(90.0);
+ EXPECT_TRUE(EmpiricallyPreserves2dAxisAlignment(transform));
+ EXPECT_TRUE(transform.Preserves2dAxisAlignment());
+
+ transform.MakeIdentity();
+ transform.RotateAboutZAxis(180.0);
+ EXPECT_TRUE(EmpiricallyPreserves2dAxisAlignment(transform));
+ EXPECT_TRUE(transform.Preserves2dAxisAlignment());
+
+ transform.MakeIdentity();
+ transform.RotateAboutZAxis(270.0);
+ EXPECT_TRUE(EmpiricallyPreserves2dAxisAlignment(transform));
+ EXPECT_TRUE(transform.Preserves2dAxisAlignment());
+
+ transform.MakeIdentity();
+ transform.RotateAboutYAxis(90.0);
+ EXPECT_TRUE(EmpiricallyPreserves2dAxisAlignment(transform));
+ EXPECT_TRUE(transform.Preserves2dAxisAlignment());
+
+ transform.MakeIdentity();
+ transform.RotateAboutXAxis(90.0);
+ EXPECT_TRUE(EmpiricallyPreserves2dAxisAlignment(transform));
+ EXPECT_TRUE(transform.Preserves2dAxisAlignment());
+
+ transform.MakeIdentity();
+ transform.RotateAboutZAxis(90.0);
+ transform.RotateAboutYAxis(90.0);
+ EXPECT_TRUE(EmpiricallyPreserves2dAxisAlignment(transform));
+ EXPECT_TRUE(transform.Preserves2dAxisAlignment());
+
+ transform.MakeIdentity();
+ transform.RotateAboutZAxis(90.0);
+ transform.RotateAboutXAxis(90.0);
+ EXPECT_TRUE(EmpiricallyPreserves2dAxisAlignment(transform));
+ EXPECT_TRUE(transform.Preserves2dAxisAlignment());
+
+ transform.MakeIdentity();
+ transform.RotateAboutYAxis(90.0);
+ transform.RotateAboutZAxis(90.0);
+ EXPECT_TRUE(EmpiricallyPreserves2dAxisAlignment(transform));
+ EXPECT_TRUE(transform.Preserves2dAxisAlignment());
+
+ transform.MakeIdentity();
+ transform.RotateAboutZAxis(45.0);
+ EXPECT_FALSE(EmpiricallyPreserves2dAxisAlignment(transform));
+ EXPECT_FALSE(transform.Preserves2dAxisAlignment());
+
+ // 3-d case; In 2d after an orthographic projection, this case does
+ // preserve 2d axis alignment. But in 3d, it does not preserve axis
+ // alignment.
+ transform.MakeIdentity();
+ transform.RotateAboutYAxis(45.0);
+ EXPECT_TRUE(EmpiricallyPreserves2dAxisAlignment(transform));
+ EXPECT_TRUE(transform.Preserves2dAxisAlignment());
+
+ transform.MakeIdentity();
+ transform.RotateAboutXAxis(45.0);
+ EXPECT_TRUE(EmpiricallyPreserves2dAxisAlignment(transform));
+ EXPECT_TRUE(transform.Preserves2dAxisAlignment());
+
+ // Perspective cases.
+ transform.MakeIdentity();
+ transform.ApplyPerspectiveDepth(10.0);
+ transform.RotateAboutYAxis(45.0);
+ EXPECT_FALSE(EmpiricallyPreserves2dAxisAlignment(transform));
+ EXPECT_FALSE(transform.Preserves2dAxisAlignment());
+
+ transform.MakeIdentity();
+ transform.ApplyPerspectiveDepth(10.0);
+ transform.RotateAboutZAxis(90.0);
+ EXPECT_TRUE(EmpiricallyPreserves2dAxisAlignment(transform));
+ EXPECT_TRUE(transform.Preserves2dAxisAlignment());
+}
+
+TEST(XFormTest, To2dTranslation) {
+ Vector2dF translation(3.f, 7.f);
+ Transform transform;
+ transform.Translate(translation.x(), translation.y() + 1);
+ EXPECT_NE(translation.ToString(), transform.To2dTranslation().ToString());
+ transform.MakeIdentity();
+ transform.Translate(translation.x(), translation.y());
+ EXPECT_EQ(translation.ToString(), transform.To2dTranslation().ToString());
+}
+
+} // namespace
+
+} // namespace gfx
diff --git a/chromium/ui/gfx/transform_util.cc b/chromium/ui/gfx/transform_util.cc
new file mode 100644
index 00000000000..90c8b56e554
--- /dev/null
+++ b/chromium/ui/gfx/transform_util.cc
@@ -0,0 +1,322 @@
+// Copyright (c) 2012 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 "ui/gfx/transform_util.h"
+
+#include <cmath>
+
+#include "ui/gfx/point.h"
+
+namespace gfx {
+
+namespace {
+
+double Length3(double v[3]) {
+ return std::sqrt(v[0] * v[0] + v[1] * v[1] + v[2] * v[2]);
+}
+
+void Scale3(double v[3], double scale) {
+ for (int i = 0; i < 3; ++i)
+ v[i] *= scale;
+}
+
+template <int n>
+double Dot(const double* a, const double* b) {
+ double toReturn = 0;
+ for (int i = 0; i < n; ++i)
+ toReturn += a[i] * b[i];
+ return toReturn;
+}
+
+template <int n>
+void Combine(double* out,
+ const double* a,
+ const double* b,
+ double scale_a,
+ double scale_b) {
+ for (int i = 0; i < n; ++i)
+ out[i] = a[i] * scale_a + b[i] * scale_b;
+}
+
+void Cross3(double out[3], double a[3], double b[3]) {
+ double x = a[1] * b[2] - a[2] * b[1];
+ double y = a[2] * b[0] - a[0] * b[2];
+ double z = a[0] * b[1] - a[1] * b[0];
+ out[0] = x;
+ out[1] = y;
+ out[2] = z;
+}
+
+// Taken from http://www.w3.org/TR/css3-transforms/.
+bool Slerp(double out[4],
+ const double q1[4],
+ const double q2[4],
+ double progress) {
+ double product = Dot<4>(q1, q2);
+
+ // Clamp product to -1.0 <= product <= 1.0.
+ product = std::min(std::max(product, -1.0), 1.0);
+
+ // Interpolate angles along the shortest path. For example, to interpolate
+ // between a 175 degree angle and a 185 degree angle, interpolate along the
+ // 10 degree path from 175 to 185, rather than along the 350 degree path in
+ // the opposite direction. This matches WebKit's implementation but not
+ // the current W3C spec. Fixing the spec to match this approach is discussed
+ // at:
+ // http://lists.w3.org/Archives/Public/www-style/2013May/0131.html
+ double scale1 = 1.0;
+ if (product < 0) {
+ product = -product;
+ scale1 = -1.0;
+ }
+
+ const double epsilon = 1e-5;
+ if (std::abs(product - 1.0) < epsilon) {
+ for (int i = 0; i < 4; ++i)
+ out[i] = q1[i];
+ return true;
+ }
+
+ double denom = std::sqrt(1 - product * product);
+ double theta = std::acos(product);
+ double w = std::sin(progress * theta) * (1 / denom);
+
+ scale1 *= std::cos(progress * theta) - product * w;
+ double scale2 = w;
+ Combine<4>(out, q1, q2, scale1, scale2);
+
+ return true;
+}
+
+// Returns false if the matrix cannot be normalized.
+bool Normalize(SkMatrix44& m) {
+ if (m.getDouble(3, 3) == 0.0)
+ // Cannot normalize.
+ return false;
+
+ double scale = 1.0 / m.getDouble(3, 3);
+ for (int i = 0; i < 4; i++)
+ for (int j = 0; j < 4; j++)
+ m.setDouble(i, j, m.getDouble(i, j) * scale);
+
+ return true;
+}
+
+} // namespace
+
+Transform GetScaleTransform(const Point& anchor, float scale) {
+ Transform transform;
+ transform.Translate(anchor.x() * (1 - scale),
+ anchor.y() * (1 - scale));
+ transform.Scale(scale, scale);
+ return transform;
+}
+
+DecomposedTransform::DecomposedTransform() {
+ translate[0] = translate[1] = translate[2] = 0.0;
+ scale[0] = scale[1] = scale[2] = 1.0;
+ skew[0] = skew[1] = skew[2] = 0.0;
+ perspective[0] = perspective[1] = perspective[2] = 0.0;
+ quaternion[0] = quaternion[1] = quaternion[2] = 0.0;
+ perspective[3] = quaternion[3] = 1.0;
+}
+
+bool BlendDecomposedTransforms(DecomposedTransform* out,
+ const DecomposedTransform& to,
+ const DecomposedTransform& from,
+ double progress) {
+ double scalea = progress;
+ double scaleb = 1.0 - progress;
+ Combine<3>(out->translate, to.translate, from.translate, scalea, scaleb);
+ Combine<3>(out->scale, to.scale, from.scale, scalea, scaleb);
+ Combine<3>(out->skew, to.skew, from.skew, scalea, scaleb);
+ Combine<4>(
+ out->perspective, to.perspective, from.perspective, scalea, scaleb);
+ return Slerp(out->quaternion, from.quaternion, to.quaternion, progress);
+}
+
+// Taken from http://www.w3.org/TR/css3-transforms/.
+bool DecomposeTransform(DecomposedTransform* decomp,
+ const Transform& transform) {
+ if (!decomp)
+ return false;
+
+ // We'll operate on a copy of the matrix.
+ SkMatrix44 matrix = transform.matrix();
+
+ // If we cannot normalize the matrix, then bail early as we cannot decompose.
+ if (!Normalize(matrix))
+ return false;
+
+ SkMatrix44 perspectiveMatrix = matrix;
+
+ for (int i = 0; i < 3; ++i)
+ perspectiveMatrix.setDouble(3, i, 0.0);
+
+ perspectiveMatrix.setDouble(3, 3, 1.0);
+
+ // If the perspective matrix is not invertible, we are also unable to
+ // decompose, so we'll bail early. Constant taken from SkMatrix44::invert.
+ if (std::abs(perspectiveMatrix.determinant()) < 1e-8)
+ return false;
+
+ if (matrix.getDouble(3, 0) != 0.0 ||
+ matrix.getDouble(3, 1) != 0.0 ||
+ matrix.getDouble(3, 2) != 0.0) {
+ // rhs is the right hand side of the equation.
+ SkMScalar rhs[4] = {
+ matrix.get(3, 0),
+ matrix.get(3, 1),
+ matrix.get(3, 2),
+ matrix.get(3, 3)
+ };
+
+ // Solve the equation by inverting perspectiveMatrix and multiplying
+ // rhs by the inverse.
+ SkMatrix44 inversePerspectiveMatrix(SkMatrix44::kUninitialized_Constructor);
+ if (!perspectiveMatrix.invert(&inversePerspectiveMatrix))
+ return false;
+
+ SkMatrix44 transposedInversePerspectiveMatrix =
+ inversePerspectiveMatrix;
+
+ transposedInversePerspectiveMatrix.transpose();
+ transposedInversePerspectiveMatrix.mapMScalars(rhs);
+
+ for (int i = 0; i < 4; ++i)
+ decomp->perspective[i] = rhs[i];
+
+ } else {
+ // No perspective.
+ for (int i = 0; i < 3; ++i)
+ decomp->perspective[i] = 0.0;
+ decomp->perspective[3] = 1.0;
+ }
+
+ for (int i = 0; i < 3; i++)
+ decomp->translate[i] = matrix.getDouble(i, 3);
+
+ double row[3][3];
+ for (int i = 0; i < 3; i++)
+ for (int j = 0; j < 3; ++j)
+ row[i][j] = matrix.getDouble(j, i);
+
+ // Compute X scale factor and normalize first row.
+ decomp->scale[0] = Length3(row[0]);
+ if (decomp->scale[0] != 0.0)
+ Scale3(row[0], 1.0 / decomp->scale[0]);
+
+ // Compute XY shear factor and make 2nd row orthogonal to 1st.
+ decomp->skew[0] = Dot<3>(row[0], row[1]);
+ Combine<3>(row[1], row[1], row[0], 1.0, -decomp->skew[0]);
+
+ // Now, compute Y scale and normalize 2nd row.
+ decomp->scale[1] = Length3(row[1]);
+ if (decomp->scale[1] != 0.0)
+ Scale3(row[1], 1.0 / decomp->scale[1]);
+
+ decomp->skew[0] /= decomp->scale[1];
+
+ // Compute XZ and YZ shears, orthogonalize 3rd row
+ decomp->skew[1] = Dot<3>(row[0], row[2]);
+ Combine<3>(row[2], row[2], row[0], 1.0, -decomp->skew[1]);
+ decomp->skew[2] = Dot<3>(row[1], row[2]);
+ Combine<3>(row[2], row[2], row[1], 1.0, -decomp->skew[2]);
+
+ // Next, get Z scale and normalize 3rd row.
+ decomp->scale[2] = Length3(row[2]);
+ if (decomp->scale[2] != 0.0)
+ Scale3(row[2], 1.0 / decomp->scale[2]);
+
+ decomp->skew[1] /= decomp->scale[2];
+ decomp->skew[2] /= decomp->scale[2];
+
+ // At this point, the matrix (in rows) is orthonormal.
+ // Check for a coordinate system flip. If the determinant
+ // is -1, then negate the matrix and the scaling factors.
+ double pdum3[3];
+ Cross3(pdum3, row[1], row[2]);
+ if (Dot<3>(row[0], pdum3) < 0) {
+ for (int i = 0; i < 3; i++) {
+ decomp->scale[i] *= -1.0;
+ for (int j = 0; j < 3; ++j)
+ row[i][j] *= -1.0;
+ }
+ }
+
+ decomp->quaternion[0] =
+ 0.5 * std::sqrt(std::max(1.0 + row[0][0] - row[1][1] - row[2][2], 0.0));
+ decomp->quaternion[1] =
+ 0.5 * std::sqrt(std::max(1.0 - row[0][0] + row[1][1] - row[2][2], 0.0));
+ decomp->quaternion[2] =
+ 0.5 * std::sqrt(std::max(1.0 - row[0][0] - row[1][1] + row[2][2], 0.0));
+ decomp->quaternion[3] =
+ 0.5 * std::sqrt(std::max(1.0 + row[0][0] + row[1][1] + row[2][2], 0.0));
+
+ if (row[2][1] > row[1][2])
+ decomp->quaternion[0] = -decomp->quaternion[0];
+ if (row[0][2] > row[2][0])
+ decomp->quaternion[1] = -decomp->quaternion[1];
+ if (row[1][0] > row[0][1])
+ decomp->quaternion[2] = -decomp->quaternion[2];
+
+ return true;
+}
+
+// Taken from http://www.w3.org/TR/css3-transforms/.
+Transform ComposeTransform(const DecomposedTransform& decomp) {
+ SkMatrix44 matrix(SkMatrix44::kIdentity_Constructor);
+ for (int i = 0; i < 4; i++)
+ matrix.setDouble(3, i, decomp.perspective[i]);
+
+ matrix.preTranslate(SkDoubleToMScalar(decomp.translate[0]),
+ SkDoubleToMScalar(decomp.translate[1]),
+ SkDoubleToMScalar(decomp.translate[2]));
+
+ double x = decomp.quaternion[0];
+ double y = decomp.quaternion[1];
+ double z = decomp.quaternion[2];
+ double w = decomp.quaternion[3];
+
+ SkMatrix44 rotation_matrix(SkMatrix44::kUninitialized_Constructor);
+ rotation_matrix.set3x3(1.0 - 2.0 * (y * y + z * z),
+ 2.0 * (x * y + z * w),
+ 2.0 * (x * z - y * w),
+ 2.0 * (x * y - z * w),
+ 1.0 - 2.0 * (x * x + z * z),
+ 2.0 * (y * z + x * w),
+ 2.0 * (x * z + y * w),
+ 2.0 * (y * z - x * w),
+ 1.0 - 2.0 * (x * x + y * y));
+
+ matrix.preConcat(rotation_matrix);
+
+ SkMatrix44 temp(SkMatrix44::kIdentity_Constructor);
+ if (decomp.skew[2]) {
+ temp.setDouble(1, 2, decomp.skew[2]);
+ matrix.preConcat(temp);
+ }
+
+ if (decomp.skew[1]) {
+ temp.setDouble(1, 2, 0);
+ temp.setDouble(0, 2, decomp.skew[1]);
+ matrix.preConcat(temp);
+ }
+
+ if (decomp.skew[0]) {
+ temp.setDouble(0, 2, 0);
+ temp.setDouble(0, 1, decomp.skew[0]);
+ matrix.preConcat(temp);
+ }
+
+ matrix.preScale(SkDoubleToMScalar(decomp.scale[0]),
+ SkDoubleToMScalar(decomp.scale[1]),
+ SkDoubleToMScalar(decomp.scale[2]));
+
+ Transform to_return;
+ to_return.matrix() = matrix;
+ return to_return;
+}
+
+} // namespace ui
diff --git a/chromium/ui/gfx/transform_util.h b/chromium/ui/gfx/transform_util.h
new file mode 100644
index 00000000000..b6da7b0782d
--- /dev/null
+++ b/chromium/ui/gfx/transform_util.h
@@ -0,0 +1,56 @@
+// Copyright (c) 2012 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_TRANSFORM_UTIL_H_
+#define UI_GFX_TRANSFORM_UTIL_H_
+
+#include "ui/base/ui_export.h"
+#include "ui/gfx/transform.h"
+
+namespace gfx {
+
+class Point;
+
+// Returns a scale transform at |anchor| point.
+UI_EXPORT Transform GetScaleTransform(const Point& anchor, float scale);
+
+// Contains the components of a factored transform. These components may be
+// blended and recomposed.
+struct UI_EXPORT DecomposedTransform {
+ // The default constructor initializes the components in such a way that
+ // if used with Compose below, will produce the identity transform.
+ DecomposedTransform();
+
+ double translate[3];
+ double scale[3];
+ double skew[3];
+ double perspective[4];
+ double quaternion[4];
+
+ // Copy and assign are allowed.
+};
+
+// Interpolates the decomposed components |to| with |from| using the
+// routines described in http://www.w3.org/TR/css3-3d-transform/.
+// |progress| is in the range [0, 1] (0 leaves |out| unchanged, and 1
+// assigns |from| to |out|).
+UI_EXPORT bool BlendDecomposedTransforms(DecomposedTransform* out,
+ const DecomposedTransform& to,
+ const DecomposedTransform& from,
+ double progress);
+
+// Decomposes this transform into its translation, scale, skew, perspective,
+// and rotation components following the routines detailed in this spec:
+// http://www.w3.org/TR/css3-3d-transforms/.
+UI_EXPORT bool DecomposeTransform(DecomposedTransform* out,
+ const Transform& transform);
+
+// Composes a transform from the given translation, scale, skew, prespective,
+// and rotation components following the routines detailed in this spec:
+// http://www.w3.org/TR/css3-3d-transforms/.
+UI_EXPORT Transform ComposeTransform(const DecomposedTransform& decomp);
+
+} // namespace gfx
+
+#endif // UI_GFX_TRANSFORM_UTIL_H_
diff --git a/chromium/ui/gfx/transform_util_unittest.cc b/chromium/ui/gfx/transform_util_unittest.cc
new file mode 100644
index 00000000000..01ddbe538d0
--- /dev/null
+++ b/chromium/ui/gfx/transform_util_unittest.cc
@@ -0,0 +1,34 @@
+// Copyright (c) 2012 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 "ui/gfx/transform_util.h"
+
+#include "testing/gtest/include/gtest/gtest.h"
+#include "ui/gfx/point.h"
+
+namespace gfx {
+namespace {
+
+TEST(TransformUtilTest, GetScaleTransform) {
+ const Point kAnchor(20, 40);
+ const float kScale = 0.5f;
+
+ Transform scale = GetScaleTransform(kAnchor, kScale);
+
+ const int kOffset = 10;
+ for (int sign_x = -1; sign_x <= 1; ++sign_x) {
+ for (int sign_y = -1; sign_y <= 1; ++sign_y) {
+ Point test(kAnchor.x() + sign_x * kOffset,
+ kAnchor.y() + sign_y * kOffset);
+ scale.TransformPoint(test);
+
+ EXPECT_EQ(Point(kAnchor.x() + sign_x * kOffset * kScale,
+ kAnchor.y() + sign_y * kOffset * kScale),
+ test);
+ }
+ }
+}
+
+} // namespace
+} // namespace gfx
diff --git a/chromium/ui/gfx/vector2d.cc b/chromium/ui/gfx/vector2d.cc
new file mode 100644
index 00000000000..9e685e45d1d
--- /dev/null
+++ b/chromium/ui/gfx/vector2d.cc
@@ -0,0 +1,39 @@
+// Copyright (c) 2012 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 "ui/gfx/vector2d.h"
+
+#include <cmath>
+
+#include "base/strings/stringprintf.h"
+
+namespace gfx {
+
+bool Vector2d::IsZero() const {
+ return x_ == 0 && y_ == 0;
+}
+
+void Vector2d::Add(const Vector2d& other) {
+ x_ += other.x_;
+ y_ += other.y_;
+}
+
+void Vector2d::Subtract(const Vector2d& other) {
+ x_ -= other.x_;
+ y_ -= other.y_;
+}
+
+int64 Vector2d::LengthSquared() const {
+ return static_cast<int64>(x_) * x_ + static_cast<int64>(y_) * y_;
+}
+
+float Vector2d::Length() const {
+ return static_cast<float>(std::sqrt(static_cast<double>(LengthSquared())));
+}
+
+std::string Vector2d::ToString() const {
+ return base::StringPrintf("[%d %d]", x_, y_);
+}
+
+} // namespace gfx
diff --git a/chromium/ui/gfx/vector2d.h b/chromium/ui/gfx/vector2d.h
new file mode 100644
index 00000000000..69f6b400ce3
--- /dev/null
+++ b/chromium/ui/gfx/vector2d.h
@@ -0,0 +1,91 @@
+// Copyright (c) 2012 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 vector class. This class is used to indicate a
+// distance in two dimensions between two points. Subtracting two points should
+// produce a vector, and adding a vector to a point produces the point at the
+// vector's distance from the original point.
+
+#ifndef UI_GFX_VECTOR2D_H_
+#define UI_GFX_VECTOR2D_H_
+
+#include <string>
+
+#include "base/basictypes.h"
+#include "ui/base/ui_export.h"
+#include "ui/gfx/vector2d_f.h"
+
+namespace gfx {
+
+class UI_EXPORT Vector2d {
+ public:
+ Vector2d() : x_(0), y_(0) {}
+ Vector2d(int x, int y) : x_(x), y_(y) {}
+
+ int x() const { return x_; }
+ void set_x(int x) { x_ = x; }
+
+ int y() const { return y_; }
+ void set_y(int y) { y_ = y; }
+
+ // True if both components of the vector are 0.
+ bool IsZero() const;
+
+ // Add the components of the |other| vector to the current vector.
+ void Add(const Vector2d& other);
+ // Subtract the components of the |other| vector from the current vector.
+ void Subtract(const Vector2d& other);
+
+ void operator+=(const Vector2d& other) { Add(other); }
+ void operator-=(const Vector2d& other) { Subtract(other); }
+
+ void SetToMin(const Vector2d& other) {
+ x_ = x_ <= other.x_ ? x_ : other.x_;
+ y_ = y_ <= other.y_ ? y_ : other.y_;
+ }
+
+ void SetToMax(const Vector2d& other) {
+ x_ = x_ >= other.x_ ? x_ : other.x_;
+ y_ = y_ >= other.y_ ? y_ : other.y_;
+ }
+
+ // Gives the square of the diagonal length of the vector. Since this is
+ // cheaper to compute than Length(), it is useful when you want to compare
+ // relative lengths of different vectors without needing the actual lengths.
+ int64 LengthSquared() const;
+ // Gives the diagonal length of the vector.
+ float Length() const;
+
+ std::string ToString() const;
+
+ operator Vector2dF() const { return Vector2dF(x_, y_); }
+
+ private:
+ int x_;
+ int y_;
+};
+
+inline bool operator==(const Vector2d& lhs, const Vector2d& rhs) {
+ return lhs.x() == rhs.x() && lhs.y() == rhs.y();
+}
+
+inline Vector2d operator-(const Vector2d& v) {
+ return Vector2d(-v.x(), -v.y());
+}
+
+inline Vector2d operator+(const Vector2d& lhs, const Vector2d& rhs) {
+ Vector2d result = lhs;
+ result.Add(rhs);
+ return result;
+}
+
+inline Vector2d operator-(const Vector2d& lhs, const Vector2d& rhs) {
+ Vector2d result = lhs;
+ result.Add(-rhs);
+ return result;
+}
+
+} // namespace gfx
+
+#endif // UI_GFX_VECTOR2D_H_
diff --git a/chromium/ui/gfx/vector2d_conversions.cc b/chromium/ui/gfx/vector2d_conversions.cc
new file mode 100644
index 00000000000..457e9f708a2
--- /dev/null
+++ b/chromium/ui/gfx/vector2d_conversions.cc
@@ -0,0 +1,30 @@
+// Copyright (c) 2012 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 "ui/gfx/vector2d_conversions.h"
+
+#include "ui/gfx/safe_integer_conversions.h"
+
+namespace gfx {
+
+Vector2d ToFlooredVector2d(const Vector2dF& vector2d) {
+ int x = ToFlooredInt(vector2d.x());
+ int y = ToFlooredInt(vector2d.y());
+ return Vector2d(x, y);
+}
+
+Vector2d ToCeiledVector2d(const Vector2dF& vector2d) {
+ int x = ToCeiledInt(vector2d.x());
+ int y = ToCeiledInt(vector2d.y());
+ return Vector2d(x, y);
+}
+
+Vector2d ToRoundedVector2d(const Vector2dF& vector2d) {
+ int x = ToRoundedInt(vector2d.x());
+ int y = ToRoundedInt(vector2d.y());
+ return Vector2d(x, y);
+}
+
+} // namespace gfx
+
diff --git a/chromium/ui/gfx/vector2d_conversions.h b/chromium/ui/gfx/vector2d_conversions.h
new file mode 100644
index 00000000000..051092e78fe
--- /dev/null
+++ b/chromium/ui/gfx/vector2d_conversions.h
@@ -0,0 +1,24 @@
+// Copyright (c) 2012 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_VECTOR2D_CONVERSIONS_H_
+#define UI_GFX_VECTOR2D_CONVERSIONS_H_
+
+#include "ui/gfx/vector2d.h"
+#include "ui/gfx/vector2d_f.h"
+
+namespace gfx {
+
+// Returns a Vector2d with each component from the input Vector2dF floored.
+UI_EXPORT Vector2d ToFlooredVector2d(const Vector2dF& vector2d);
+
+// Returns a Vector2d with each component from the input Vector2dF ceiled.
+UI_EXPORT Vector2d ToCeiledVector2d(const Vector2dF& vector2d);
+
+// Returns a Vector2d with each component from the input Vector2dF rounded.
+UI_EXPORT Vector2d ToRoundedVector2d(const Vector2dF& vector2d);
+
+} // namespace gfx
+
+#endif // UI_GFX_VECTOR2D_CONVERSIONS_H_
diff --git a/chromium/ui/gfx/vector2d_f.cc b/chromium/ui/gfx/vector2d_f.cc
new file mode 100644
index 00000000000..2ad267074b3
--- /dev/null
+++ b/chromium/ui/gfx/vector2d_f.cc
@@ -0,0 +1,60 @@
+// Copyright (c) 2012 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 "ui/gfx/vector2d_f.h"
+
+#include <cmath>
+
+#include "base/strings/stringprintf.h"
+
+namespace gfx {
+
+std::string Vector2dF::ToString() const {
+ return base::StringPrintf("[%f %f]", x_, y_);
+}
+
+bool Vector2dF::IsZero() const {
+ return x_ == 0 && y_ == 0;
+}
+
+void Vector2dF::Add(const Vector2dF& other) {
+ x_ += other.x_;
+ y_ += other.y_;
+}
+
+void Vector2dF::Subtract(const Vector2dF& other) {
+ x_ -= other.x_;
+ y_ -= other.y_;
+}
+
+double Vector2dF::LengthSquared() const {
+ return static_cast<double>(x_) * x_ + static_cast<double>(y_) * y_;
+}
+
+float Vector2dF::Length() const {
+ return static_cast<float>(std::sqrt(LengthSquared()));
+}
+
+void Vector2dF::Scale(float x_scale, float y_scale) {
+ x_ *= x_scale;
+ y_ *= y_scale;
+}
+
+double CrossProduct(const Vector2dF& lhs, const Vector2dF& rhs) {
+ return static_cast<double>(lhs.x()) * rhs.y() -
+ static_cast<double>(lhs.y()) * rhs.x();
+}
+
+double DotProduct(const Vector2dF& lhs, const Vector2dF& rhs) {
+ return static_cast<double>(lhs.x()) * rhs.x() +
+ static_cast<double>(lhs.y()) * rhs.y();
+}
+
+Vector2dF ScaleVector2d(const Vector2dF& v, float x_scale, float y_scale) {
+ Vector2dF scaled_v(v);
+ scaled_v.Scale(x_scale, y_scale);
+ return scaled_v;
+}
+
+} // namespace gfx
diff --git a/chromium/ui/gfx/vector2d_f.h b/chromium/ui/gfx/vector2d_f.h
new file mode 100644
index 00000000000..4faf28ae226
--- /dev/null
+++ b/chromium/ui/gfx/vector2d_f.h
@@ -0,0 +1,112 @@
+// Copyright (c) 2012 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 float vector class. This class is used to indicate a
+// distance in two dimensions between two points. Subtracting two points should
+// produce a vector, and adding a vector to a point produces the point at the
+// vector's distance from the original point.
+
+#ifndef UI_GFX_VECTOR2D_F_H_
+#define UI_GFX_VECTOR2D_F_H_
+
+#include <string>
+
+#include "ui/base/ui_export.h"
+
+namespace gfx {
+
+class UI_EXPORT Vector2dF {
+ public:
+ Vector2dF() : x_(0), y_(0) {}
+ Vector2dF(float x, float y) : x_(x), y_(y) {}
+
+ float x() const { return x_; }
+ void set_x(float x) { x_ = x; }
+
+ float y() const { return y_; }
+ void set_y(float y) { y_ = y; }
+
+ // True if both components of the vector are 0.
+ bool IsZero() const;
+
+ // Add the components of the |other| vector to the current vector.
+ void Add(const Vector2dF& other);
+ // Subtract the components of the |other| vector from the current vector.
+ void Subtract(const Vector2dF& other);
+
+ void operator+=(const Vector2dF& other) { Add(other); }
+ void operator-=(const Vector2dF& other) { Subtract(other); }
+
+ void SetToMin(const Vector2dF& other) {
+ x_ = x_ <= other.x_ ? x_ : other.x_;
+ y_ = y_ <= other.y_ ? y_ : other.y_;
+ }
+
+ void SetToMax(const Vector2dF& other) {
+ x_ = x_ >= other.x_ ? x_ : other.x_;
+ y_ = y_ >= other.y_ ? y_ : other.y_;
+ }
+
+ // Gives the square of the diagonal length of the vector.
+ double LengthSquared() const;
+ // Gives the diagonal length of the vector.
+ float Length() const;
+
+ // Scale the x and y components of the vector by |scale|.
+ void Scale(float scale) { Scale(scale, scale); }
+ // Scale the x and y components of the vector by |x_scale| and |y_scale|
+ // respectively.
+ void Scale(float x_scale, float y_scale);
+
+ std::string ToString() const;
+
+ private:
+ float x_;
+ float y_;
+};
+
+inline bool operator==(const Vector2dF& lhs, const Vector2dF& rhs) {
+ return lhs.x() == rhs.x() && lhs.y() == rhs.y();
+}
+
+inline bool operator!=(const Vector2dF& lhs, const Vector2dF& rhs) {
+ return !(lhs == rhs);
+}
+
+inline Vector2dF operator-(const Vector2dF& v) {
+ return Vector2dF(-v.x(), -v.y());
+}
+
+inline Vector2dF operator+(const Vector2dF& lhs, const Vector2dF& rhs) {
+ Vector2dF result = lhs;
+ result.Add(rhs);
+ return result;
+}
+
+inline Vector2dF operator-(const Vector2dF& lhs, const Vector2dF& rhs) {
+ Vector2dF result = lhs;
+ result.Add(-rhs);
+ return result;
+}
+
+// Return the cross product of two vectors.
+UI_EXPORT double CrossProduct(const Vector2dF& lhs, const Vector2dF& rhs);
+
+// Return the dot product of two vectors.
+UI_EXPORT double DotProduct(const Vector2dF& lhs, const Vector2dF& rhs);
+
+// Return a vector that is |v| scaled by the given scale factors along each
+// axis.
+UI_EXPORT Vector2dF ScaleVector2d(const Vector2dF& v,
+ float x_scale,
+ float y_scale);
+
+// Return a vector that is |v| scaled by the given scale factor.
+inline Vector2dF ScaleVector2d(const Vector2dF& v, float scale) {
+ return ScaleVector2d(v, scale, scale);
+}
+
+} // namespace gfx
+
+#endif // UI_GFX_VECTOR2D_F_H_
diff --git a/chromium/ui/gfx/vector2d_unittest.cc b/chromium/ui/gfx/vector2d_unittest.cc
new file mode 100644
index 00000000000..5d9e21ea66d
--- /dev/null
+++ b/chromium/ui/gfx/vector2d_unittest.cc
@@ -0,0 +1,250 @@
+// Copyright (c) 2012 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 <cmath>
+#include <limits>
+
+#include "base/basictypes.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "ui/gfx/vector2d.h"
+#include "ui/gfx/vector2d_f.h"
+
+namespace gfx {
+
+TEST(Vector2dTest, ConversionToFloat) {
+ Vector2d i(3, 4);
+ Vector2dF f = i;
+ EXPECT_EQ(i, f);
+}
+
+TEST(Vector2dTest, IsZero) {
+ Vector2d int_zero(0, 0);
+ Vector2d int_nonzero(2, -2);
+ Vector2dF float_zero(0, 0);
+ Vector2dF float_nonzero(0.1f, -0.1f);
+
+ EXPECT_TRUE(int_zero.IsZero());
+ EXPECT_FALSE(int_nonzero.IsZero());
+ EXPECT_TRUE(float_zero.IsZero());
+ EXPECT_FALSE(float_nonzero.IsZero());
+}
+
+TEST(Vector2dTest, Add) {
+ Vector2d i1(3, 5);
+ Vector2d i2(4, -1);
+
+ const struct {
+ Vector2d expected;
+ Vector2d actual;
+ } int_tests[] = {
+ { Vector2d(3, 5), i1 + Vector2d() },
+ { Vector2d(3 + 4, 5 - 1), i1 + i2 },
+ { Vector2d(3 - 4, 5 + 1), i1 - i2 }
+ };
+
+ for (size_t i = 0; i < ARRAYSIZE_UNSAFE(int_tests); ++i)
+ EXPECT_EQ(int_tests[i].expected.ToString(),
+ int_tests[i].actual.ToString());
+
+ Vector2dF f1(3.1f, 5.1f);
+ Vector2dF f2(4.3f, -1.3f);
+
+ const struct {
+ Vector2dF expected;
+ Vector2dF actual;
+ } float_tests[] = {
+ { Vector2dF(3.1F, 5.1F), f1 + Vector2d() },
+ { Vector2dF(3.1F, 5.1F), f1 + Vector2dF() },
+ { Vector2dF(3.1f + 4.3f, 5.1f - 1.3f), f1 + f2 },
+ { Vector2dF(3.1f - 4.3f, 5.1f + 1.3f), f1 - f2 }
+ };
+
+ for (size_t i = 0; i < ARRAYSIZE_UNSAFE(float_tests); ++i)
+ EXPECT_EQ(float_tests[i].expected.ToString(),
+ float_tests[i].actual.ToString());
+}
+
+TEST(Vector2dTest, Negative) {
+ const struct {
+ Vector2d expected;
+ Vector2d actual;
+ } int_tests[] = {
+ { Vector2d(0, 0), -Vector2d(0, 0) },
+ { Vector2d(-3, -3), -Vector2d(3, 3) },
+ { Vector2d(3, 3), -Vector2d(-3, -3) },
+ { Vector2d(-3, 3), -Vector2d(3, -3) },
+ { Vector2d(3, -3), -Vector2d(-3, 3) }
+ };
+
+ for (size_t i = 0; i < ARRAYSIZE_UNSAFE(int_tests); ++i)
+ EXPECT_EQ(int_tests[i].expected.ToString(),
+ int_tests[i].actual.ToString());
+
+ const struct {
+ Vector2dF expected;
+ Vector2dF actual;
+ } float_tests[] = {
+ { Vector2dF(0, 0), -Vector2d(0, 0) },
+ { Vector2dF(-0.3f, -0.3f), -Vector2dF(0.3f, 0.3f) },
+ { Vector2dF(0.3f, 0.3f), -Vector2dF(-0.3f, -0.3f) },
+ { Vector2dF(-0.3f, 0.3f), -Vector2dF(0.3f, -0.3f) },
+ { Vector2dF(0.3f, -0.3f), -Vector2dF(-0.3f, 0.3f) }
+ };
+
+ for (size_t i = 0; i < ARRAYSIZE_UNSAFE(float_tests); ++i)
+ EXPECT_EQ(float_tests[i].expected.ToString(),
+ float_tests[i].actual.ToString());
+}
+
+TEST(Vector2dTest, Scale) {
+ float double_values[][4] = {
+ { 4.5f, 1.2f, 3.3f, 5.6f },
+ { 4.5f, -1.2f, 3.3f, 5.6f },
+ { 4.5f, 1.2f, 3.3f, -5.6f },
+ { 4.5f, 1.2f, -3.3f, -5.6f },
+ { -4.5f, 1.2f, 3.3f, 5.6f },
+ { -4.5f, 1.2f, 0, 5.6f },
+ { -4.5f, 1.2f, 3.3f, 0 },
+ { 4.5f, 0, 3.3f, 5.6f },
+ { 0, 1.2f, 3.3f, 5.6f }
+ };
+
+ for (size_t i = 0; i < ARRAYSIZE_UNSAFE(double_values); ++i) {
+ Vector2dF v(double_values[i][0], double_values[i][1]);
+ v.Scale(double_values[i][2], double_values[i][3]);
+ EXPECT_EQ(v.x(), double_values[i][0] * double_values[i][2]);
+ EXPECT_EQ(v.y(), double_values[i][1] * double_values[i][3]);
+
+ Vector2dF v2 = ScaleVector2d(
+ gfx::Vector2dF(double_values[i][0], double_values[i][1]),
+ double_values[i][2], double_values[i][3]);
+ EXPECT_EQ(double_values[i][0] * double_values[i][2], v2.x());
+ EXPECT_EQ(double_values[i][1] * double_values[i][3], v2.y());
+ }
+
+ float single_values[][3] = {
+ { 4.5f, 1.2f, 3.3f },
+ { 4.5f, -1.2f, 3.3f },
+ { 4.5f, 1.2f, 3.3f },
+ { 4.5f, 1.2f, -3.3f },
+ { -4.5f, 1.2f, 3.3f },
+ { -4.5f, 1.2f, 0 },
+ { -4.5f, 1.2f, 3.3f },
+ { 4.5f, 0, 3.3f },
+ { 0, 1.2f, 3.3f }
+ };
+
+ for (size_t i = 0; i < ARRAYSIZE_UNSAFE(single_values); ++i) {
+ Vector2dF v(single_values[i][0], single_values[i][1]);
+ v.Scale(single_values[i][2]);
+ EXPECT_EQ(v.x(), single_values[i][0] * single_values[i][2]);
+ EXPECT_EQ(v.y(), single_values[i][1] * single_values[i][2]);
+
+ Vector2dF v2 = ScaleVector2d(
+ gfx::Vector2dF(double_values[i][0], double_values[i][1]),
+ double_values[i][2]);
+ EXPECT_EQ(single_values[i][0] * single_values[i][2], v2.x());
+ EXPECT_EQ(single_values[i][1] * single_values[i][2], v2.y());
+ }
+}
+
+TEST(Vector2dTest, Length) {
+ int int_values[][2] = {
+ { 0, 0 },
+ { 10, 20 },
+ { 20, 10 },
+ { -10, -20 },
+ { -20, 10 },
+ { 10, -20 },
+ };
+
+ for (size_t i = 0; i < ARRAYSIZE_UNSAFE(int_values); ++i) {
+ int v0 = int_values[i][0];
+ int v1 = int_values[i][1];
+ double length_squared =
+ static_cast<double>(v0) * v0 + static_cast<double>(v1) * v1;
+ double length = std::sqrt(length_squared);
+ Vector2d vector(v0, v1);
+ EXPECT_EQ(static_cast<float>(length_squared), vector.LengthSquared());
+ EXPECT_EQ(static_cast<float>(length), vector.Length());
+ }
+
+ float float_values[][2] = {
+ { 0, 0 },
+ { 10.5f, 20.5f },
+ { 20.5f, 10.5f },
+ { -10.5f, -20.5f },
+ { -20.5f, 10.5f },
+ { 10.5f, -20.5f },
+ // A large vector that fails if the Length function doesn't use
+ // double precision internally.
+ { 1236278317862780234892374893213178027.12122348904204230f,
+ 335890352589839028212313231225425134332.38123f },
+ };
+
+ for (size_t i = 0; i < ARRAYSIZE_UNSAFE(float_values); ++i) {
+ double v0 = float_values[i][0];
+ double v1 = float_values[i][1];
+ double length_squared =
+ static_cast<double>(v0) * v0 + static_cast<double>(v1) * v1;
+ double length = std::sqrt(length_squared);
+ Vector2dF vector(v0, v1);
+ EXPECT_DOUBLE_EQ(length_squared, vector.LengthSquared());
+ EXPECT_FLOAT_EQ(static_cast<float>(length), vector.Length());
+ }
+}
+
+TEST(Vector2dTest, ClampVector2d) {
+ Vector2d a;
+
+ a = Vector2d(3, 5);
+ EXPECT_EQ(Vector2d(3, 5).ToString(), a.ToString());
+ a.SetToMax(Vector2d(2, 4));
+ EXPECT_EQ(Vector2d(3, 5).ToString(), a.ToString());
+ a.SetToMax(Vector2d(3, 5));
+ EXPECT_EQ(Vector2d(3, 5).ToString(), a.ToString());
+ a.SetToMax(Vector2d(4, 2));
+ EXPECT_EQ(Vector2d(4, 5).ToString(), a.ToString());
+ a.SetToMax(Vector2d(8, 10));
+ EXPECT_EQ(Vector2d(8, 10).ToString(), a.ToString());
+
+ a.SetToMin(Vector2d(9, 11));
+ EXPECT_EQ(Vector2d(8, 10).ToString(), a.ToString());
+ a.SetToMin(Vector2d(8, 10));
+ EXPECT_EQ(Vector2d(8, 10).ToString(), a.ToString());
+ a.SetToMin(Vector2d(11, 9));
+ EXPECT_EQ(Vector2d(8, 9).ToString(), a.ToString());
+ a.SetToMin(Vector2d(7, 11));
+ EXPECT_EQ(Vector2d(7, 9).ToString(), a.ToString());
+ a.SetToMin(Vector2d(3, 5));
+ EXPECT_EQ(Vector2d(3, 5).ToString(), a.ToString());
+}
+
+TEST(Vector2dTest, ClampVector2dF) {
+ Vector2dF a;
+
+ a = Vector2dF(3.5f, 5.5f);
+ EXPECT_EQ(Vector2dF(3.5f, 5.5f).ToString(), a.ToString());
+ a.SetToMax(Vector2dF(2.5f, 4.5f));
+ EXPECT_EQ(Vector2dF(3.5f, 5.5f).ToString(), a.ToString());
+ a.SetToMax(Vector2dF(3.5f, 5.5f));
+ EXPECT_EQ(Vector2dF(3.5f, 5.5f).ToString(), a.ToString());
+ a.SetToMax(Vector2dF(4.5f, 2.5f));
+ EXPECT_EQ(Vector2dF(4.5f, 5.5f).ToString(), a.ToString());
+ a.SetToMax(Vector2dF(8.5f, 10.5f));
+ EXPECT_EQ(Vector2dF(8.5f, 10.5f).ToString(), a.ToString());
+
+ a.SetToMin(Vector2dF(9.5f, 11.5f));
+ EXPECT_EQ(Vector2dF(8.5f, 10.5f).ToString(), a.ToString());
+ a.SetToMin(Vector2dF(8.5f, 10.5f));
+ EXPECT_EQ(Vector2dF(8.5f, 10.5f).ToString(), a.ToString());
+ a.SetToMin(Vector2dF(11.5f, 9.5f));
+ EXPECT_EQ(Vector2dF(8.5f, 9.5f).ToString(), a.ToString());
+ a.SetToMin(Vector2dF(7.5f, 11.5f));
+ EXPECT_EQ(Vector2dF(7.5f, 9.5f).ToString(), a.ToString());
+ a.SetToMin(Vector2dF(3.5f, 5.5f));
+ EXPECT_EQ(Vector2dF(3.5f, 5.5f).ToString(), a.ToString());
+}
+
+} // namespace gfx
diff --git a/chromium/ui/gfx/vector3d_f.cc b/chromium/ui/gfx/vector3d_f.cc
new file mode 100644
index 00000000000..233e0542673
--- /dev/null
+++ b/chromium/ui/gfx/vector3d_f.cc
@@ -0,0 +1,88 @@
+// Copyright (c) 2012 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 "ui/gfx/vector3d_f.h"
+
+#include <cmath>
+
+#include "base/strings/stringprintf.h"
+
+namespace gfx {
+
+Vector3dF::Vector3dF()
+ : x_(0),
+ y_(0),
+ z_(0) {
+}
+
+Vector3dF::Vector3dF(float x, float y, float z)
+ : x_(x),
+ y_(y),
+ z_(z) {
+}
+
+Vector3dF::Vector3dF(const Vector2dF& other)
+ : x_(other.x()),
+ y_(other.y()),
+ z_(0) {
+}
+
+std::string Vector3dF::ToString() const {
+ return base::StringPrintf("[%f %f %f]", x_, y_, z_);
+}
+
+bool Vector3dF::IsZero() const {
+ return x_ == 0 && y_ == 0 && z_ == 0;
+}
+
+void Vector3dF::Add(const Vector3dF& other) {
+ x_ += other.x_;
+ y_ += other.y_;
+ z_ += other.z_;
+}
+
+void Vector3dF::Subtract(const Vector3dF& other) {
+ x_ -= other.x_;
+ y_ -= other.y_;
+ z_ -= other.z_;
+}
+
+double Vector3dF::LengthSquared() const {
+ return static_cast<double>(x_) * x_ + static_cast<double>(y_) * y_ +
+ static_cast<double>(z_) * z_;
+}
+
+float Vector3dF::Length() const {
+ return static_cast<float>(std::sqrt(LengthSquared()));
+}
+
+void Vector3dF::Scale(float x_scale, float y_scale, float z_scale) {
+ x_ *= x_scale;
+ y_ *= y_scale;
+ z_ *= z_scale;
+}
+
+void Vector3dF::Cross(const Vector3dF& other) {
+ float x = y_ * other.z() - z_ * other.y();
+ float y = z_ * other.x() - x_ * other.z();
+ float z = x_ * other.y() - y_ * other.x();
+ x_ = x;
+ y_ = y;
+ z_ = z;
+}
+
+float DotProduct(const Vector3dF& lhs, const Vector3dF& rhs) {
+ return lhs.x() * rhs.x() + lhs.y() * rhs.y() + lhs.z() * rhs.z();
+}
+
+Vector3dF ScaleVector3d(const Vector3dF& v,
+ float x_scale,
+ float y_scale,
+ float z_scale) {
+ Vector3dF scaled_v(v);
+ scaled_v.Scale(x_scale, y_scale, z_scale);
+ return scaled_v;
+}
+
+} // namespace gfx
diff --git a/chromium/ui/gfx/vector3d_f.h b/chromium/ui/gfx/vector3d_f.h
new file mode 100644
index 00000000000..17ad332f3aa
--- /dev/null
+++ b/chromium/ui/gfx/vector3d_f.h
@@ -0,0 +1,124 @@
+// Copyright (c) 2012 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 float vector class. This class is used to indicate a
+// distance in two dimensions between two points. Subtracting two points should
+// produce a vector, and adding a vector to a point produces the point at the
+// vector's distance from the original point.
+
+#ifndef UI_GFX_VECTOR3D_F_H_
+#define UI_GFX_VECTOR3D_F_H_
+
+#include <string>
+
+#include "ui/base/ui_export.h"
+#include "ui/gfx/vector2d_f.h"
+
+namespace gfx {
+
+class UI_EXPORT Vector3dF {
+ public:
+ Vector3dF();
+ Vector3dF(float x, float y, float z);
+
+ explicit Vector3dF(const Vector2dF& other);
+
+ float x() const { return x_; }
+ void set_x(float x) { x_ = x; }
+
+ float y() const { return y_; }
+ void set_y(float y) { y_ = y; }
+
+ float z() const { return z_; }
+ void set_z(float z) { z_ = z; }
+
+ // True if all components of the vector are 0.
+ bool IsZero() const;
+
+ // Add the components of the |other| vector to the current vector.
+ void Add(const Vector3dF& other);
+ // Subtract the components of the |other| vector from the current vector.
+ void Subtract(const Vector3dF& other);
+
+ void operator+=(const Vector3dF& other) { Add(other); }
+ void operator-=(const Vector3dF& other) { Subtract(other); }
+
+ void SetToMin(const Vector3dF& other) {
+ x_ = x_ <= other.x_ ? x_ : other.x_;
+ y_ = y_ <= other.y_ ? y_ : other.y_;
+ z_ = z_ <= other.z_ ? z_ : other.z_;
+ }
+
+ void SetToMax(const Vector3dF& other) {
+ x_ = x_ >= other.x_ ? x_ : other.x_;
+ y_ = y_ >= other.y_ ? y_ : other.y_;
+ z_ = z_ >= other.z_ ? z_ : other.z_;
+ }
+
+ // Gives the square of the diagonal length of the vector.
+ double LengthSquared() const;
+ // Gives the diagonal length of the vector.
+ float Length() const;
+
+ // Scale all components of the vector by |scale|.
+ void Scale(float scale) { Scale(scale, scale, scale); }
+ // Scale the each component of the vector by the given scale factors.
+ void Scale(float x_scale, float y_scale, float z_scale);
+
+ // Take the cross product of this vector with |other| and become the result.
+ void Cross(const Vector3dF& other);
+
+ std::string ToString() const;
+
+ private:
+ float x_;
+ float y_;
+ float z_;
+};
+
+inline bool operator==(const Vector3dF& lhs, const Vector3dF& rhs) {
+ return lhs.x() == rhs.x() && lhs.y() == rhs.y() && lhs.z() == rhs.z();
+}
+
+inline Vector3dF operator-(const Vector3dF& v) {
+ return Vector3dF(-v.x(), -v.y(), -v.z());
+}
+
+inline Vector3dF operator+(const Vector3dF& lhs, const Vector3dF& rhs) {
+ Vector3dF result = lhs;
+ result.Add(rhs);
+ return result;
+}
+
+inline Vector3dF operator-(const Vector3dF& lhs, const Vector3dF& rhs) {
+ Vector3dF result = lhs;
+ result.Add(-rhs);
+ return result;
+}
+
+// Return the cross product of two vectors.
+inline Vector3dF CrossProduct(const Vector3dF& lhs, const Vector3dF& rhs) {
+ Vector3dF result = lhs;
+ result.Cross(rhs);
+ return result;
+}
+
+// Return the dot product of two vectors.
+UI_EXPORT float DotProduct(const Vector3dF& lhs, const Vector3dF& rhs);
+
+// Return a vector that is |v| scaled by the given scale factors along each
+// axis.
+UI_EXPORT Vector3dF ScaleVector3d(const Vector3dF& v,
+ float x_scale,
+ float y_scale,
+ float z_scale);
+
+// Return a vector that is |v| scaled by the given scale factor.
+inline Vector3dF ScaleVector3d(const Vector3dF& v, float scale) {
+ return ScaleVector3d(v, scale, scale, scale);
+}
+
+} // namespace gfx
+
+#endif // UI_GFX_VECTOR3D_F_H_
diff --git a/chromium/ui/gfx/vector3d_unittest.cc b/chromium/ui/gfx/vector3d_unittest.cc
new file mode 100644
index 00000000000..5b3bd7e91bf
--- /dev/null
+++ b/chromium/ui/gfx/vector3d_unittest.cc
@@ -0,0 +1,264 @@
+// Copyright (c) 2012 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 <cmath>
+#include <limits>
+
+#include "base/basictypes.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "ui/gfx/vector3d_f.h"
+
+namespace gfx {
+
+TEST(Vector3dTest, IsZero) {
+ gfx::Vector3dF float_zero(0, 0, 0);
+ gfx::Vector3dF float_nonzero(0.1f, -0.1f, 0.1f);
+
+ EXPECT_TRUE(float_zero.IsZero());
+ EXPECT_FALSE(float_nonzero.IsZero());
+}
+
+TEST(Vector3dTest, Add) {
+ gfx::Vector3dF f1(3.1f, 5.1f, 2.7f);
+ gfx::Vector3dF f2(4.3f, -1.3f, 8.1f);
+
+ const struct {
+ gfx::Vector3dF expected;
+ gfx::Vector3dF actual;
+ } float_tests[] = {
+ { gfx::Vector3dF(3.1F, 5.1F, 2.7f), f1 + gfx::Vector3dF() },
+ { gfx::Vector3dF(3.1f + 4.3f, 5.1f - 1.3f, 2.7f + 8.1f), f1 + f2 },
+ { gfx::Vector3dF(3.1f - 4.3f, 5.1f + 1.3f, 2.7f - 8.1f), f1 - f2 }
+ };
+
+ for (size_t i = 0; i < ARRAYSIZE_UNSAFE(float_tests); ++i)
+ EXPECT_EQ(float_tests[i].expected.ToString(),
+ float_tests[i].actual.ToString());
+}
+
+TEST(Vector3dTest, Negative) {
+ const struct {
+ gfx::Vector3dF expected;
+ gfx::Vector3dF actual;
+ } float_tests[] = {
+ { gfx::Vector3dF(-0.0f, -0.0f, -0.0f), -gfx::Vector3dF(0, 0, 0) },
+ { gfx::Vector3dF(-0.3f, -0.3f, -0.3f), -gfx::Vector3dF(0.3f, 0.3f, 0.3f) },
+ { gfx::Vector3dF(0.3f, 0.3f, 0.3f), -gfx::Vector3dF(-0.3f, -0.3f, -0.3f) },
+ { gfx::Vector3dF(-0.3f, 0.3f, -0.3f), -gfx::Vector3dF(0.3f, -0.3f, 0.3f) },
+ { gfx::Vector3dF(0.3f, -0.3f, -0.3f), -gfx::Vector3dF(-0.3f, 0.3f, 0.3f) },
+ { gfx::Vector3dF(-0.3f, -0.3f, 0.3f), -gfx::Vector3dF(0.3f, 0.3f, -0.3f) }
+ };
+
+ for (size_t i = 0; i < ARRAYSIZE_UNSAFE(float_tests); ++i)
+ EXPECT_EQ(float_tests[i].expected.ToString(),
+ float_tests[i].actual.ToString());
+}
+
+TEST(Vector3dTest, Scale) {
+ float triple_values[][6] = {
+ { 4.5f, 1.2f, 1.8f, 3.3f, 5.6f, 4.2f },
+ { 4.5f, -1.2f, -1.8f, 3.3f, 5.6f, 4.2f },
+ { 4.5f, 1.2f, -1.8f, 3.3f, 5.6f, 4.2f },
+ { 4.5f, -1.2f -1.8f, 3.3f, 5.6f, 4.2f },
+
+ { 4.5f, 1.2f, 1.8f, 3.3f, -5.6f, -4.2f },
+ { 4.5f, 1.2f, 1.8f, -3.3f, -5.6f, -4.2f },
+ { 4.5f, 1.2f, -1.8f, 3.3f, -5.6f, -4.2f },
+ { 4.5f, 1.2f, -1.8f, -3.3f, -5.6f, -4.2f },
+
+ { -4.5f, 1.2f, 1.8f, 3.3f, 5.6f, 4.2f },
+ { -4.5f, 1.2f, 1.8f, 0, 5.6f, 4.2f },
+ { -4.5f, 1.2f, -1.8f, 3.3f, 5.6f, 4.2f },
+ { -4.5f, 1.2f, -1.8f, 0, 5.6f, 4.2f },
+
+ { -4.5f, 1.2f, 1.8f, 3.3f, 0, 4.2f },
+ { 4.5f, 0, 1.8f, 3.3f, 5.6f, 4.2f },
+ { -4.5f, 1.2f, -1.8f, 3.3f, 0, 4.2f },
+ { 4.5f, 0, -1.8f, 3.3f, 5.6f, 4.2f },
+ { -4.5f, 1.2f, 1.8f, 3.3f, 5.6f, 0 },
+ { -4.5f, 1.2f, -1.8f, 3.3f, 5.6f, 0 },
+
+ { 0, 1.2f, 0, 3.3f, 5.6f, 4.2f },
+ { 0, 1.2f, 1.8f, 3.3f, 5.6f, 4.2f }
+ };
+
+ for (size_t i = 0; i < ARRAYSIZE_UNSAFE(triple_values); ++i) {
+ gfx::Vector3dF v(triple_values[i][0],
+ triple_values[i][1],
+ triple_values[i][2]);
+ v.Scale(triple_values[i][3], triple_values[i][4], triple_values[i][5]);
+ EXPECT_EQ(triple_values[i][0] * triple_values[i][3], v.x());
+ EXPECT_EQ(triple_values[i][1] * triple_values[i][4], v.y());
+ EXPECT_EQ(triple_values[i][2] * triple_values[i][5], v.z());
+
+ Vector3dF v2 = ScaleVector3d(
+ gfx::Vector3dF(triple_values[i][0],
+ triple_values[i][1],
+ triple_values[i][2]),
+ triple_values[i][3], triple_values[i][4], triple_values[i][5]);
+ EXPECT_EQ(triple_values[i][0] * triple_values[i][3], v2.x());
+ EXPECT_EQ(triple_values[i][1] * triple_values[i][4], v2.y());
+ EXPECT_EQ(triple_values[i][2] * triple_values[i][5], v2.z());
+ }
+
+ float single_values[][4] = {
+ { 4.5f, 1.2f, 1.8f, 3.3f },
+ { 4.5f, -1.2f, 1.8f, 3.3f },
+ { 4.5f, 1.2f, -1.8f, 3.3f },
+ { 4.5f, -1.2f, -1.8f, 3.3f },
+ { -4.5f, 1.2f, 3.3f },
+ { -4.5f, 1.2f, 0 },
+ { -4.5f, 1.2f, 1.8f, 3.3f },
+ { -4.5f, 1.2f, 1.8f, 0 },
+ { 4.5f, 0, 1.8f, 3.3f },
+ { 0, 1.2f, 1.8f, 3.3f },
+ { 4.5f, 0, 1.8f, 3.3f },
+ { 0, 1.2f, 1.8f, 3.3f },
+ { 4.5f, 1.2f, 0, 3.3f },
+ { 4.5f, 1.2f, 0, 3.3f }
+ };
+
+ for (size_t i = 0; i < ARRAYSIZE_UNSAFE(single_values); ++i) {
+ gfx::Vector3dF v(single_values[i][0],
+ single_values[i][1],
+ single_values[i][2]);
+ v.Scale(single_values[i][3]);
+ EXPECT_EQ(single_values[i][0] * single_values[i][3], v.x());
+ EXPECT_EQ(single_values[i][1] * single_values[i][3], v.y());
+ EXPECT_EQ(single_values[i][2] * single_values[i][3], v.z());
+
+ Vector3dF v2 = ScaleVector3d(
+ gfx::Vector3dF(single_values[i][0],
+ single_values[i][1],
+ single_values[i][2]),
+ single_values[i][3]);
+ EXPECT_EQ(single_values[i][0] * single_values[i][3], v2.x());
+ EXPECT_EQ(single_values[i][1] * single_values[i][3], v2.y());
+ EXPECT_EQ(single_values[i][2] * single_values[i][3], v2.z());
+ }
+}
+
+TEST(Vector3dTest, Length) {
+ float float_values[][3] = {
+ { 0, 0, 0 },
+ { 10.5f, 20.5f, 8.5f },
+ { 20.5f, 10.5f, 8.5f },
+ { 8.5f, 20.5f, 10.5f },
+ { 10.5f, 8.5f, 20.5f },
+ { -10.5f, -20.5f, -8.5f },
+ { -20.5f, 10.5f, -8.5f },
+ { -8.5f, -20.5f, -10.5f },
+ { -10.5f, -8.5f, -20.5f },
+ { 10.5f, -20.5f, 8.5f },
+ { -10.5f, 20.5f, 8.5f },
+ { 10.5f, -20.5f, -8.5f },
+ { -10.5f, 20.5f, -8.5f },
+ // A large vector that fails if the Length function doesn't use
+ // double precision internally.
+ { 1236278317862780234892374893213178027.12122348904204230f,
+ 335890352589839028212313231225425134332.38123f,
+ 27861786423846742743236423478236784678.236713617231f }
+ };
+
+ for (size_t i = 0; i < ARRAYSIZE_UNSAFE(float_values); ++i) {
+ double v0 = float_values[i][0];
+ double v1 = float_values[i][1];
+ double v2 = float_values[i][2];
+ double length_squared =
+ static_cast<double>(v0) * v0 +
+ static_cast<double>(v1) * v1 +
+ static_cast<double>(v2) * v2;
+ double length = std::sqrt(length_squared);
+ gfx::Vector3dF vector(v0, v1, v2);
+ EXPECT_DOUBLE_EQ(length_squared, vector.LengthSquared());
+ EXPECT_FLOAT_EQ(static_cast<float>(length), vector.Length());
+ }
+}
+
+TEST(Vector3dTest, DotProduct) {
+ const struct {
+ float expected;
+ gfx::Vector3dF input1;
+ gfx::Vector3dF input2;
+ } tests[] = {
+ { 0, gfx::Vector3dF(1, 0, 0), gfx::Vector3dF(0, 1, 1) },
+ { 0, gfx::Vector3dF(0, 1, 0), gfx::Vector3dF(1, 0, 1) },
+ { 0, gfx::Vector3dF(0, 0, 1), gfx::Vector3dF(1, 1, 0) },
+
+ { 3, gfx::Vector3dF(1, 1, 1), gfx::Vector3dF(1, 1, 1) },
+
+ { 1.2f, gfx::Vector3dF(1.2f, -1.2f, 1.2f), gfx::Vector3dF(1, 1, 1) },
+ { 1.2f, gfx::Vector3dF(1, 1, 1), gfx::Vector3dF(1.2f, -1.2f, 1.2f) },
+
+ { 38.72f,
+ gfx::Vector3dF(1.1f, 2.2f, 3.3f), gfx::Vector3dF(4.4f, 5.5f, 6.6f) }
+ };
+
+ for (size_t i = 0; i < ARRAYSIZE_UNSAFE(tests); ++i) {
+ float actual = gfx::DotProduct(tests[i].input1, tests[i].input2);
+ EXPECT_EQ(tests[i].expected, actual);
+ }
+}
+
+TEST(Vector3dTest, CrossProduct) {
+ const struct {
+ gfx::Vector3dF expected;
+ gfx::Vector3dF input1;
+ gfx::Vector3dF input2;
+ } tests[] = {
+ { Vector3dF(), Vector3dF(), Vector3dF(1, 1, 1) },
+ { Vector3dF(), Vector3dF(1, 1, 1), Vector3dF() },
+ { Vector3dF(), Vector3dF(1, 1, 1), Vector3dF(1, 1, 1) },
+ { Vector3dF(),
+ Vector3dF(1.6f, 10.6f, -10.6f),
+ Vector3dF(1.6f, 10.6f, -10.6f) },
+
+ { Vector3dF(1, -1, 0), Vector3dF(1, 1, 1), Vector3dF(0, 0, 1) },
+ { Vector3dF(-1, 0, 1), Vector3dF(1, 1, 1), Vector3dF(0, 1, 0) },
+ { Vector3dF(0, 1, -1), Vector3dF(1, 1, 1), Vector3dF(1, 0, 0) },
+
+ { Vector3dF(-1, 1, 0), Vector3dF(0, 0, 1), Vector3dF(1, 1, 1) },
+ { Vector3dF(1, 0, -1), Vector3dF(0, 1, 0), Vector3dF(1, 1, 1) },
+ { Vector3dF(0, -1, 1), Vector3dF(1, 0, 0), Vector3dF(1, 1, 1) }
+ };
+
+ for (size_t i = 0; i < ARRAYSIZE_UNSAFE(tests); ++i) {
+ Vector3dF actual = gfx::CrossProduct(tests[i].input1, tests[i].input2);
+ EXPECT_EQ(tests[i].expected.ToString(), actual.ToString());
+ }
+}
+
+TEST(Vector3dFTest, ClampVector3dF) {
+ Vector3dF a;
+
+ a = Vector3dF(3.5f, 5.5f, 7.5f);
+ EXPECT_EQ(Vector3dF(3.5f, 5.5f, 7.5f).ToString(), a.ToString());
+ a.SetToMax(Vector3dF(2, 4.5f, 6.5f));
+ EXPECT_EQ(Vector3dF(3.5f, 5.5f, 7.5f).ToString(), a.ToString());
+ a.SetToMax(Vector3dF(3.5f, 5.5f, 7.5f));
+ EXPECT_EQ(Vector3dF(3.5f, 5.5f, 7.5f).ToString(), a.ToString());
+ a.SetToMax(Vector3dF(4.5f, 2, 6.5f));
+ EXPECT_EQ(Vector3dF(4.5f, 5.5f, 7.5f).ToString(), a.ToString());
+ a.SetToMax(Vector3dF(3.5f, 6.5f, 6.5f));
+ EXPECT_EQ(Vector3dF(4.5f, 6.5f, 7.5f).ToString(), a.ToString());
+ a.SetToMax(Vector3dF(3.5f, 5.5f, 8.5f));
+ EXPECT_EQ(Vector3dF(4.5f, 6.5f, 8.5f).ToString(), a.ToString());
+ a.SetToMax(Vector3dF(8.5f, 10.5f, 12.5f));
+ EXPECT_EQ(Vector3dF(8.5f, 10.5f, 12.5f).ToString(), a.ToString());
+
+ a.SetToMin(Vector3dF(9.5f, 11.5f, 13.5f));
+ EXPECT_EQ(Vector3dF(8.5f, 10.5f, 12.5f).ToString(), a.ToString());
+ a.SetToMin(Vector3dF(8.5f, 10.5f, 12.5f));
+ EXPECT_EQ(Vector3dF(8.5f, 10.5f, 12.5f).ToString(), a.ToString());
+ a.SetToMin(Vector3dF(7.5f, 11.5f, 13.5f));
+ EXPECT_EQ(Vector3dF(7.5f, 10.5f, 12.5f).ToString(), a.ToString());
+ a.SetToMin(Vector3dF(9.5f, 9.5f, 13.5f));
+ EXPECT_EQ(Vector3dF(7.5f, 9.5f, 12.5f).ToString(), a.ToString());
+ a.SetToMin(Vector3dF(9.5f, 11.5f, 11.5f));
+ EXPECT_EQ(Vector3dF(7.5f, 9.5f, 11.5f).ToString(), a.ToString());
+ a.SetToMin(Vector3dF(3.5f, 5.5f, 7.5f));
+ EXPECT_EQ(Vector3dF(3.5f, 5.5f, 7.5f).ToString(), a.ToString());
+}
+
+} // namespace gfx