diff options
author | Zeno Albisser <zeno.albisser@digia.com> | 2013-08-15 21:46:11 +0200 |
---|---|---|
committer | Zeno Albisser <zeno.albisser@digia.com> | 2013-08-15 21:46:11 +0200 |
commit | 679147eead574d186ebf3069647b4c23e8ccace6 (patch) | |
tree | fc247a0ac8ff119f7c8550879ebb6d3dd8d1ff69 /chromium/ui/gfx | |
download | qtwebengine-chromium-679147eead574d186ebf3069647b4c23e8ccace6.tar.gz |
Initial import.
Diffstat (limited to 'chromium/ui/gfx')
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", >k_antialias, + "gtk-xft-hinting", >k_hinting, + "gtk-xft-hintstyle", >k_hint_style, + "gtk-xft-rgba", >k_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(¤t_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 |