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/skia | |
download | qtwebengine-chromium-679147eead574d186ebf3069647b4c23e8ccace6.tar.gz |
Initial import.
Diffstat (limited to 'chromium/skia')
164 files changed, 16247 insertions, 0 deletions
diff --git a/chromium/skia/OWNERS b/chromium/skia/OWNERS new file mode 100644 index 00000000000..fe892fd24cf --- /dev/null +++ b/chromium/skia/OWNERS @@ -0,0 +1,21 @@ +alokp@chromium.org +borenet@google.com +bsalomon@google.com +bungeman@google.com +djsollen@google.com +edisonn@google.com +epoger@google.com +fmalita@google.com +junov@chromium.org +jvanverth@google.com +reed@google.com +rmistry@google.com +robertphillips@google.com +scroggo@google.com +senorblanco@chromium.org +sugoi@google.com +thakis@chromium.org +tomhudson@google.com +vandebo@chromium.org + + diff --git a/chromium/skia/README.chromium b/chromium/skia/README.chromium new file mode 100644 index 00000000000..76a92840d71 --- /dev/null +++ b/chromium/skia/README.chromium @@ -0,0 +1,24 @@ +This is a copy of the Skia source tree. In the original repository, the include +directories and the "corecg" directories are separated out. On top of + libs/graphics -> skia +we have the following mappings from source repository to our tree: + include/corecg -> skia/include/corecg + include/graphics -> skia/include + libs/corecg -> skia/corecg + +platform/* are our own files that provide extra functionality we need our +Skia to implement. + +DO NOT CHANGE THE SKIA FILES IN OUR TREE. These will be overwritten when we +sync to newer versions of Skia. The exception is platform/ + +THE EXCEPTION IS include/corecg/SkUserConfig.h which are the application's +definition of its options and environment. This file must be manually merged +with any changes in the Skia tree so that our options are preserved and we +also pick up any important changes they make. + + -- brettw@google.com, 28 December 2006 + +Patches we are tracking locally (until Skia is fixed upstream): +fix_for_1186198.diff -- eseidel, 6/4/08, BUG=1186198 +linux_patch.diff diff --git a/chromium/skia/config/SkUserConfig.h b/chromium/skia/config/SkUserConfig.h new file mode 100644 index 00000000000..a0cb64f941c --- /dev/null +++ b/chromium/skia/config/SkUserConfig.h @@ -0,0 +1,244 @@ +/* + * Copyright (C) 2006 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef SkUserConfig_DEFINED +#define SkUserConfig_DEFINED + +/* SkTypes.h, the root of the public header files, does the following trick: + + #include <SkPreConfig.h> + #include <SkUserConfig.h> + #include <SkPostConfig.h> + + SkPreConfig.h runs first, and it is responsible for initializing certain + skia defines. + + SkPostConfig.h runs last, and its job is to just check that the final + defines are consistent (i.e. that we don't have mutually conflicting + defines). + + SkUserConfig.h (this file) runs in the middle. It gets to change or augment + the list of flags initially set in preconfig, and then postconfig checks + that everything still makes sense. + + Below are optional defines that add, subtract, or change default behavior + in Skia. Your port can locally edit this file to enable/disable flags as + you choose, or these can be delared on your command line (i.e. -Dfoo). + + By default, this include file will always default to having all of the flags + commented out, so including it will have no effect. +*/ + +/////////////////////////////////////////////////////////////////////////////// + +/* Scalars (the fractional value type in skia) can be implemented either as + floats or 16.16 integers (fixed). Exactly one of these two symbols must be + defined. +*/ +//#define SK_SCALAR_IS_FLOAT +//#define SK_SCALAR_IS_FIXED + + +/* Somewhat independent of how SkScalar is implemented, Skia also wants to know + if it can use floats at all. Naturally, if SK_SCALAR_IS_FLOAT is defined, + then so muse SK_CAN_USE_FLOAT, but if scalars are fixed, SK_CAN_USE_FLOAT + can go either way. + */ +//#define SK_CAN_USE_FLOAT + +/* For some performance-critical scalar operations, skia will optionally work + around the standard float operators if it knows that the CPU does not have + native support for floats. If your environment uses software floating point, + define this flag. + */ +//#define SK_SOFTWARE_FLOAT + + +/* Skia has lots of debug-only code. Often this is just null checks or other + parameter checking, but sometimes it can be quite intrusive (e.g. check that + each 32bit pixel is in premultiplied form). This code can be very useful + during development, but will slow things down in a shipping product. + + By default, these mutually exclusive flags are defined in SkPreConfig.h, + based on the presence or absence of NDEBUG, but that decision can be changed + here. + */ +//#define SK_DEBUG +//#define SK_RELEASE + + +/* If, in debugging mode, Skia needs to stop (presumably to invoke a debugger) + it will call SK_CRASH(). If this is not defined it, it is defined in + SkPostConfig.h to write to an illegal address + */ +//#define SK_CRASH() *(int *)(uintptr_t)0 = 0 + + +/* preconfig will have attempted to determine the endianness of the system, + but you can change these mutually exclusive flags here. + */ +//#define SK_CPU_BENDIAN +//#define SK_CPU_LENDIAN + + +/* Some compilers don't support long long for 64bit integers. If yours does + not, define this to the appropriate type. + */ +//#define SkLONGLONG int64_t + + +/* Some envorinments do not suport writable globals (eek!). If yours does not, + define this flag. + */ +//#define SK_USE_RUNTIME_GLOBALS + +/* If zlib is available and you want to support the flate compression + algorithm (used in PDF generation), define SK_ZLIB_INCLUDE to be the + include path. + */ +//#define SK_ZLIB_INCLUDE <zlib.h> +#define SK_ZLIB_INCLUDE "third_party/zlib/zlib.h" + +/* Define this to allow PDF scalars above 32k. The PDF/A spec doesn't allow + them, but modern PDF interpreters should handle them just fine. + */ +//#define SK_ALLOW_LARGE_PDF_SCALARS + +/* Define this to provide font subsetter for font subsetting when generating + PDF documents. + */ +#define SK_SFNTLY_SUBSETTER \ + "third_party/sfntly/cpp/src/sample/chromium/font_subsetter.h" + +/* Define this to remove dimension checks on bitmaps. Not all blits will be + correct yet, so this is mostly for debugging the implementation. + */ +//#define SK_ALLOW_OVER_32K_BITMAPS + + +/* To write debug messages to a console, skia will call SkDebugf(...) following + printf conventions (e.g. const char* format, ...). If you want to redirect + this to something other than printf, define yours here + */ +//#define SkDebugf(...) MyFunction(__VA_ARGS__) + + +/* If SK_DEBUG is defined, then you can optionally define SK_SUPPORT_UNITTEST + which will run additional self-tests at startup. These can take a long time, + so this flag is optional. + */ +#ifdef SK_DEBUG +#define SK_SUPPORT_UNITTEST +#endif + +/* If your system embeds skia and has complex event logging, define this + symbol to name a file that maps the following macros to your system's + equivalents: + SK_TRACE_EVENT0(event) + SK_TRACE_EVENT1(event, name1, value1) + SK_TRACE_EVENT2(event, name1, value1, name2, value2) + src/utils/SkDebugTrace.h has a trivial implementation that writes to + the debug output stream. If SK_USER_TRACE_INCLUDE_FILE is not defined, + SkTrace.h will define the above three macros to do nothing. +*/ +#undef SK_USER_TRACE_INCLUDE_FILE + +// ===== Begin Chrome-specific definitions ===== + +#define SK_SCALAR_IS_FLOAT +#undef SK_SCALAR_IS_FIXED + +#define SK_MSCALAR_IS_DOUBLE +#undef SK_MSCALAR_IS_FLOAT + +#define GR_MAX_OFFSCREEN_AA_DIM 512 + +// Log the file and line number for assertions. +#define SkDebugf(...) SkDebugf_FileLine(__FILE__, __LINE__, false, __VA_ARGS__) +SK_API void SkDebugf_FileLine(const char* file, int line, bool fatal, + const char* format, ...); + +// Marking the debug print as "fatal" will cause a debug break, so we don't need +// a separate crash call here. +#define SK_DEBUGBREAK(cond) do { if (!(cond)) { \ + SkDebugf_FileLine(__FILE__, __LINE__, true, \ + "%s:%d: failed assertion \"%s\"\n", \ + __FILE__, __LINE__, #cond); } } while (false) + +#if !defined(ANDROID) // On Android, we use the skia default settings. +#define SK_A32_SHIFT 24 +#define SK_R32_SHIFT 16 +#define SK_G32_SHIFT 8 +#define SK_B32_SHIFT 0 +#endif + +#if defined(SK_BUILD_FOR_WIN32) + +#define SK_BUILD_FOR_WIN + +// VC8 doesn't support stdint.h, so we define those types here. +#define SK_IGNORE_STDINT_DOT_H +typedef signed char int8_t; +typedef unsigned char uint8_t; +typedef short int16_t; +typedef unsigned short uint16_t; +typedef int int32_t; +typedef unsigned uint32_t; + +// VC doesn't support __restrict__, so make it a NOP. +#undef SK_RESTRICT +#define SK_RESTRICT + +// Skia uses this deprecated bzero function to fill zeros into a string. +#define bzero(str, len) memset(str, 0, len) + +#elif defined(SK_BUILD_FOR_MAC) + +#define SK_CPU_LENDIAN +#undef SK_CPU_BENDIAN + +#elif defined(SK_BUILD_FOR_UNIX) + +// Prefer FreeType's emboldening algorithm to Skia's +// TODO: skia used to just use hairline, but has improved since then, so +// we should revisit this choice... +#define SK_USE_FREETYPE_EMBOLDEN + +#ifdef SK_CPU_BENDIAN +// Above we set the order for ARGB channels in registers. I suspect that, on +// big endian machines, you can keep this the same and everything will work. +// The in-memory order will be different, of course, but as long as everything +// is reading memory as words rather than bytes, it will all work. However, if +// you find that colours are messed up I thought that I would leave a helpful +// locator for you. Also see the comments in +// base/gfx/bitmap_platform_device_linux.h +#error Read the comment at this location +#endif + +#endif + +// The default crash macro writes to badbeef which can cause some strange +// problems. Instead, pipe this through to the logging function as a fatal +// assertion. +#define SK_CRASH() SkDebugf_FileLine(__FILE__, __LINE__, true, "SK_CRASH") + +// Uncomment the following line to forward skia trace events to Chrome +// tracing. +// #define SK_USER_TRACE_INCLUDE_FILE "skia/ext/skia_trace_shim.h" + +// ===== End Chrome-specific definitions ===== + +#endif diff --git a/chromium/skia/ext/SkMemory_new_handler.cpp b/chromium/skia/ext/SkMemory_new_handler.cpp new file mode 100644 index 00000000000..dbbc4944512 --- /dev/null +++ b/chromium/skia/ext/SkMemory_new_handler.cpp @@ -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 <stdio.h> +#include <stdlib.h> +#include <new> + +#include "base/process/memory.h" + +#include "third_party/skia/include/core/SkTypes.h" +#include "third_party/skia/include/core/SkThread.h" + +// This implementation of sk_malloc_flags() and friends is identical +// to SkMemory_malloc.c, except that it disables the CRT's new_handler +// during malloc(), when SK_MALLOC_THROW is not set (ie., when +// sk_malloc_flags() would not abort on NULL). + +SK_DECLARE_STATIC_MUTEX(gSkNewHandlerMutex); + +void sk_throw() { + SkASSERT(!"sk_throw"); + abort(); +} + +void sk_out_of_memory(void) { + SkASSERT(!"sk_out_of_memory"); + abort(); +} + +void* sk_malloc_throw(size_t size) { + return sk_malloc_flags(size, SK_MALLOC_THROW); +} + +void* sk_realloc_throw(void* addr, size_t size) { + void* p = realloc(addr, size); + if (size == 0) { + return p; + } + if (p == NULL) { + sk_throw(); + } + return p; +} + +void sk_free(void* p) { + if (p) { + free(p); + } +} + +void* sk_malloc_flags(size_t size, unsigned flags) { + void* p; +#if defined(ANDROID) + // Android doesn't have std::set_new_handler. + p = malloc(size); +#else + if (!(flags & SK_MALLOC_THROW)) { +#if defined(OS_MACOSX) && !defined(OS_IOS) + p = base::UncheckedMalloc(size); +#else + SkAutoMutexAcquire lock(gSkNewHandlerMutex); + std::new_handler old_handler = std::set_new_handler(NULL); + p = malloc(size); + std::set_new_handler(old_handler); +#endif + } else { + p = malloc(size); + } +#endif + if (p == NULL) { + if (flags & SK_MALLOC_THROW) { + sk_throw(); + } + } + return p; +} diff --git a/chromium/skia/ext/SkThread_chrome.cc b/chromium/skia/ext/SkThread_chrome.cc new file mode 100644 index 00000000000..f379bebee71 --- /dev/null +++ b/chromium/skia/ext/SkThread_chrome.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 "third_party/skia/include/core/SkThread.h" + +#include <new> + +#include "base/atomicops.h" +#include "base/basictypes.h" +#include "base/logging.h" +#include "base/synchronization/lock.h" + +/** Adds one to the int specified by the address (in a thread-safe manner), and + returns the previous value. + No additional memory barrier is required. + This must act as a compiler barrier. +*/ +int32_t sk_atomic_inc(int32_t* addr) { + // sk_atomic_inc is expected to return the old value, + // Barrier_AtomicIncrement returns the new value. + return base::subtle::NoBarrier_AtomicIncrement(addr, 1) - 1; +} + +/* Subtracts one from the int specified by the address (in a thread-safe + manner), and returns the previous value. + Expected to act as a release (SL/S) memory barrier and a compiler barrier. +*/ +int32_t sk_atomic_dec(int32_t* addr) { + // sk_atomic_dec is expected to return the old value, + // Barrier_AtomicIncrement returns the new value. + return base::subtle::Barrier_AtomicIncrement(addr, -1) + 1; +} +/** If sk_atomic_dec does not act as an aquire (L/SL) barrier, this is expected + to act as an aquire (L/SL) memory barrier and as a compiler barrier. +*/ +void sk_membar_aquire__after_atomic_dec() { } + +/** Adds one to the int specified by the address iff the int specified by the + address is not zero (in a thread-safe manner), and returns the previous + value. + No additional memory barrier is required. + This must act as a compiler barrier. +*/ +int32_t sk_atomic_conditional_inc(int32_t* addr) { + int32_t value = *addr; + + while (true) { + if (value == 0) { + return 0; + } + + int32_t before; + before = base::subtle::Acquire_CompareAndSwap(addr, value, value + 1); + + if (before == value) { + return value; + } else { + value = before; + } + } +} +/** If sk_atomic_conditional_inc does not act as an aquire (L/SL) barrier, this + is expected to act as an aquire (L/SL) memory barrier and as a compiler + barrier. +*/ +void sk_membar_aquire__after_atomic_conditional_inc() { } + +SkMutex::SkMutex() { + COMPILE_ASSERT(sizeof(base::Lock) <= sizeof(fStorage), Lock_is_too_big_for_SkMutex); + base::Lock* lock = reinterpret_cast<base::Lock*>(fStorage); + new(lock) base::Lock(); +} + +SkMutex::~SkMutex() { + base::Lock* lock = reinterpret_cast<base::Lock*>(fStorage); + lock->~Lock(); +} + +void SkMutex::acquire() { + base::Lock* lock = reinterpret_cast<base::Lock*>(fStorage); + lock->Acquire(); +} + +void SkMutex::release() { + base::Lock* lock = reinterpret_cast<base::Lock*>(fStorage); + lock->Release(); +} diff --git a/chromium/skia/ext/SkTypeface_fake.cpp b/chromium/skia/ext/SkTypeface_fake.cpp new file mode 100644 index 00000000000..dea36a1ff45 --- /dev/null +++ b/chromium/skia/ext/SkTypeface_fake.cpp @@ -0,0 +1,21 @@ +// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "SkTypeface.h" + +// ===== Begin Chrome-specific definitions ===== + +uint32_t SkTypeface::UniqueID(const SkTypeface* face) +{ + return 0; +} + +void SkTypeface::serialize(SkWStream* stream) const { +} + +SkTypeface* SkTypeface::Deserialize(SkStream* stream) { + return NULL; +} + +// ===== End Chrome-specific definitions ===== diff --git a/chromium/skia/ext/analysis_canvas.cc b/chromium/skia/ext/analysis_canvas.cc new file mode 100644 index 00000000000..a2d397c89db --- /dev/null +++ b/chromium/skia/ext/analysis_canvas.cc @@ -0,0 +1,416 @@ +// 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 "base/debug/trace_event.h" +#include "base/logging.h" +#include "skia/ext/analysis_canvas.h" +#include "third_party/skia/include/core/SkDevice.h" +#include "third_party/skia/include/core/SkDraw.h" +#include "third_party/skia/include/core/SkRRect.h" +#include "third_party/skia/include/core/SkShader.h" +#include "third_party/skia/src/core/SkRasterClip.h" +#include "ui/gfx/rect_conversions.h" + +namespace { + +const int kNoLayer = -1; + +bool IsSolidColorPaint(const SkPaint& paint) { + SkXfermode::Mode xfermode; + + // getXfermode can return a NULL, but that is handled + // gracefully by AsMode (NULL turns into kSrcOver mode). + SkXfermode::AsMode(paint.getXfermode(), &xfermode); + + // Paint is solid color if the following holds: + // - Alpha is 1.0, style is fill, and there are no special effects + // - Xfer mode is either kSrc or kSrcOver (kSrcOver is equivalent + // to kSrc if source alpha is 1.0, which is already checked). + return (paint.getAlpha() == 255 && + !paint.getShader() && + !paint.getLooper() && + !paint.getMaskFilter() && + !paint.getColorFilter() && + paint.getStyle() == SkPaint::kFill_Style && + (xfermode == SkXfermode::kSrc_Mode || + xfermode == SkXfermode::kSrcOver_Mode)); +} + +bool IsFullQuad(const SkDraw& draw, + const SkRect& canvas_rect, + const SkRect& drawn_rect) { + + // If the transform results in a non-axis aligned + // rect, then be conservative and return false. + if (!draw.fMatrix->rectStaysRect()) + return false; + + SkRect draw_bitmap_rect; + draw.fBitmap->getBounds(&draw_bitmap_rect); + SkRect clip_rect = SkRect::Make(draw.fRC->getBounds()); + SkRect device_rect; + draw.fMatrix->mapRect(&device_rect, drawn_rect); + + // The drawn rect covers the full canvas, if the following conditions hold: + // - Clip rect is an actual rectangle. + // - The rect we're drawing (post-transform) contains the clip rect. + // That is, all of clip rect will be colored by the rect. + // - Clip rect contains the canvas rect. + // That is, we're not clipping to a portion of this canvas. + // - The bitmap into which the draw call happens is at least as + // big as the canvas rect + return draw.fRC->isRect() && + device_rect.contains(clip_rect) && + clip_rect.contains(canvas_rect) && + draw_bitmap_rect.contains(canvas_rect); +} + +} // namespace + +namespace skia { + +AnalysisDevice::AnalysisDevice(const SkBitmap& bitmap) + : INHERITED(bitmap), + is_forced_not_solid_(false), + is_forced_not_transparent_(false), + is_solid_color_(true), + is_transparent_(true), + has_text_(false) {} + +AnalysisDevice::~AnalysisDevice() {} + +bool AnalysisDevice::GetColorIfSolid(SkColor* color) const { + if (is_transparent_) { + *color = SK_ColorTRANSPARENT; + return true; + } + if (is_solid_color_) { + *color = color_; + return true; + } + return false; +} + +bool AnalysisDevice::HasText() const { + return has_text_; +} + +void AnalysisDevice::SetForceNotSolid(bool flag) { + is_forced_not_solid_ = flag; + if (is_forced_not_solid_) + is_solid_color_ = false; +} + +void AnalysisDevice::SetForceNotTransparent(bool flag) { + is_forced_not_transparent_ = flag; + if (is_forced_not_transparent_) + is_transparent_ = false; +} + +void AnalysisDevice::clear(SkColor color) { + is_transparent_ = (!is_forced_not_transparent_ && SkColorGetA(color) == 0); + has_text_ = false; + + if (!is_forced_not_solid_ && SkColorGetA(color) == 255) { + is_solid_color_ = true; + color_ = color; + } else { + is_solid_color_ = false; + } +} + +void AnalysisDevice::drawPaint(const SkDraw& draw, const SkPaint& paint) { + is_solid_color_ = false; + is_transparent_ = false; +} + +void AnalysisDevice::drawPoints(const SkDraw& draw, + SkCanvas::PointMode mode, + size_t count, + const SkPoint points[], + const SkPaint& paint) { + is_solid_color_ = false; + is_transparent_ = false; +} + +void AnalysisDevice::drawRect(const SkDraw& draw, + const SkRect& rect, + const SkPaint& paint) { + bool does_cover_canvas = + IsFullQuad(draw, SkRect::MakeWH(width(), height()), rect); + + SkXfermode::Mode xfermode; + SkXfermode::AsMode(paint.getXfermode(), &xfermode); + + // This canvas will become transparent if the following holds: + // - The quad is a full tile quad + // - We're not in "forced not transparent" mode + // - Transfer mode is clear (0 color, 0 alpha) + // + // If the paint alpha is not 0, or if the transfrer mode is + // not src, then this canvas will not be transparent. + // + // In all other cases, we keep the current transparent value + if (does_cover_canvas && + !is_forced_not_transparent_ && + xfermode == SkXfermode::kClear_Mode) { + is_transparent_ = true; + has_text_ = false; + } else if (paint.getAlpha() != 0 || xfermode != SkXfermode::kSrc_Mode) { + is_transparent_ = false; + } + + // This bitmap is solid if and only if the following holds. + // Note that this might be overly conservative: + // - We're not in "forced not solid" mode + // - Paint is solid color + // - The quad is a full tile quad + if (!is_forced_not_solid_ && IsSolidColorPaint(paint) && does_cover_canvas) { + is_solid_color_ = true; + color_ = paint.getColor(); + has_text_ = false; + } else { + is_solid_color_ = false; + } +} + +void AnalysisDevice::drawOval(const SkDraw& draw, + const SkRect& oval, + const SkPaint& paint) { + is_solid_color_ = false; + is_transparent_ = false; +} + +void AnalysisDevice::drawPath(const SkDraw& draw, + const SkPath& path, + const SkPaint& paint, + const SkMatrix* pre_path_matrix, + bool path_is_mutable) { + is_solid_color_ = false; + is_transparent_ = false; +} + +void AnalysisDevice::drawBitmap(const SkDraw& draw, + const SkBitmap& bitmap, + const SkMatrix& matrix, + const SkPaint& paint) { + is_solid_color_ = false; + is_transparent_ = false; +} + +void AnalysisDevice::drawSprite(const SkDraw& draw, + const SkBitmap& bitmap, + int x, + int y, + const SkPaint& paint) { + is_solid_color_ = false; + is_transparent_ = false; +} + +void AnalysisDevice::drawBitmapRect(const SkDraw& draw, + const SkBitmap& bitmap, + const SkRect* src_or_null, + const SkRect& dst, + const SkPaint& paint) { + // Call drawRect to determine transparency, + // but reset solid color to false. + drawRect(draw, dst, paint); + is_solid_color_ = false; +} + +void AnalysisDevice::drawText(const SkDraw& draw, + const void* text, + size_t len, + SkScalar x, + SkScalar y, + const SkPaint& paint) { + is_solid_color_ = false; + is_transparent_ = false; + has_text_ = true; +} + +void AnalysisDevice::drawPosText(const SkDraw& draw, + const void* text, + size_t len, + const SkScalar pos[], + SkScalar const_y, + int scalars_per_pos, + const SkPaint& paint) { + is_solid_color_ = false; + is_transparent_ = false; + has_text_ = true; +} + +void AnalysisDevice::drawTextOnPath(const SkDraw& draw, + const void* text, + size_t len, + const SkPath& path, + const SkMatrix* matrix, + const SkPaint& paint) { + is_solid_color_ = false; + is_transparent_ = false; + has_text_ = true; +} + +#ifdef SK_BUILD_FOR_ANDROID +void AnalysisDevice::drawPosTextOnPath(const SkDraw& draw, + const void* text, + size_t len, + const SkPoint pos[], + const SkPaint& paint, + const SkPath& path, + const SkMatrix* matrix) { + is_solid_color_ = false; + is_transparent_ = false; + has_text_ = true; +} +#endif + +void AnalysisDevice::drawVertices(const SkDraw& draw, + SkCanvas::VertexMode, + int vertex_count, + const SkPoint verts[], + const SkPoint texs[], + const SkColor colors[], + SkXfermode* xmode, + const uint16_t indices[], + int index_count, + const SkPaint& paint) { + is_solid_color_ = false; + is_transparent_ = false; +} + +void AnalysisDevice::drawDevice(const SkDraw& draw, + SkDevice* device, + int x, + int y, + const SkPaint& paint) { + is_solid_color_ = false; + is_transparent_ = false; +} + +AnalysisCanvas::AnalysisCanvas(AnalysisDevice* device) + : INHERITED(device), + saved_stack_size_(0), + force_not_solid_stack_level_(kNoLayer), + force_not_transparent_stack_level_(kNoLayer) {} + +AnalysisCanvas::~AnalysisCanvas() {} + +bool AnalysisCanvas::GetColorIfSolid(SkColor* color) const { + return (static_cast<AnalysisDevice*>(getDevice()))->GetColorIfSolid(color); +} + +bool AnalysisCanvas::HasText() const { + return (static_cast<AnalysisDevice*>(getDevice()))->HasText(); +} + +bool AnalysisCanvas::abortDrawing() { + // Early out as soon as we have detected that the tile has text. + return HasText(); +} + +bool AnalysisCanvas::clipRect(const SkRect& rect, SkRegion::Op op, bool do_aa) { + return INHERITED::clipRect(rect, op, do_aa); +} + +bool AnalysisCanvas::clipPath(const SkPath& path, SkRegion::Op op, bool do_aa) { + // clipPaths can make our calls to IsFullQuad invalid (ie have false + // positives). As a precaution, force the setting to be non-solid + // and non-transparent until we pop this + if (force_not_solid_stack_level_ == kNoLayer) { + force_not_solid_stack_level_ = saved_stack_size_; + (static_cast<AnalysisDevice*>(getDevice()))->SetForceNotSolid(true); + } + if (force_not_transparent_stack_level_ == kNoLayer) { + force_not_transparent_stack_level_ = saved_stack_size_; + (static_cast<AnalysisDevice*>(getDevice()))->SetForceNotTransparent(true); + } + + return INHERITED::clipRect(path.getBounds(), op, do_aa); +} + +bool AnalysisCanvas::clipRRect(const SkRRect& rrect, + SkRegion::Op op, + bool do_aa) { + // clipRRect can make our calls to IsFullQuad invalid (ie have false + // positives). As a precaution, force the setting to be non-solid + // and non-transparent until we pop this + if (force_not_solid_stack_level_ == kNoLayer) { + force_not_solid_stack_level_ = saved_stack_size_; + (static_cast<AnalysisDevice*>(getDevice()))->SetForceNotSolid(true); + } + if (force_not_transparent_stack_level_ == kNoLayer) { + force_not_transparent_stack_level_ = saved_stack_size_; + (static_cast<AnalysisDevice*>(getDevice()))->SetForceNotTransparent(true); + } + + return INHERITED::clipRect(rrect.getBounds(), op, do_aa); +} + +int AnalysisCanvas::save(SkCanvas::SaveFlags flags) { + ++saved_stack_size_; + return INHERITED::save(flags); +} + +int AnalysisCanvas::saveLayer(const SkRect* bounds, + const SkPaint* paint, + SkCanvas::SaveFlags flags) { + ++saved_stack_size_; + + // If after we draw to the saved layer, we have to blend with the current + // layer, then we can conservatively say that the canvas will not be of + // solid color. + if ((paint && !IsSolidColorPaint(*paint)) || + (bounds && !bounds->contains(SkRect::MakeWH(getDevice()->width(), + getDevice()->height())))) { + if (force_not_solid_stack_level_ == kNoLayer) { + force_not_solid_stack_level_ = saved_stack_size_; + (static_cast<AnalysisDevice*>(getDevice()))->SetForceNotSolid(true); + } + } + + // If after we draw to the save layer, we have to blend with the current + // layer using any part of the current layer's alpha, then we can + // conservatively say that the canvas will not be transparent. + SkXfermode::Mode xfermode = SkXfermode::kSrc_Mode; + if (paint) + SkXfermode::AsMode(paint->getXfermode(), &xfermode); + if (xfermode != SkXfermode::kSrc_Mode) { + if (force_not_transparent_stack_level_ == kNoLayer) { + force_not_transparent_stack_level_ = saved_stack_size_; + (static_cast<AnalysisDevice*>(getDevice()))->SetForceNotTransparent(true); + } + } + + // Actually saving a layer here could cause a new bitmap to be created + // and real rendering to occur. + int count = INHERITED::save(flags); + if (bounds) { + INHERITED::clipRectBounds(bounds, flags, NULL); + } + return count; +} + +void AnalysisCanvas::restore() { + INHERITED::restore(); + + DCHECK(saved_stack_size_); + if (saved_stack_size_) { + --saved_stack_size_; + if (saved_stack_size_ < force_not_solid_stack_level_) { + (static_cast<AnalysisDevice*>(getDevice()))->SetForceNotSolid(false); + force_not_solid_stack_level_ = kNoLayer; + } + if (saved_stack_size_ < force_not_transparent_stack_level_) { + (static_cast<AnalysisDevice*>(getDevice()))->SetForceNotTransparent( + false); + force_not_transparent_stack_level_ = kNoLayer; + } + } +} + +} // namespace skia + + diff --git a/chromium/skia/ext/analysis_canvas.h b/chromium/skia/ext/analysis_canvas.h new file mode 100644 index 00000000000..5db1540e6af --- /dev/null +++ b/chromium/skia/ext/analysis_canvas.h @@ -0,0 +1,164 @@ +// 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 SKIA_EXT_ANALYSIS_CANVAS_H_ +#define SKIA_EXT_ANALYSIS_CANVAS_H_ + +#include "base/compiler_specific.h" +#include "third_party/skia/include/core/SkCanvas.h" +#include "third_party/skia/include/core/SkDevice.h" +#include "third_party/skia/include/core/SkPicture.h" + +namespace skia { + +class AnalysisDevice; + +// Does not render anything, but gathers statistics about a region +// (specified as a clip rectangle) of an SkPicture as the picture is +// played back through it. +// To use: create a SkBitmap with kNo_Config, create an AnalysisDevice +// using that bitmap, and create an AnalysisCanvas using the device. +// Play a picture into the canvas, and then check result. +class SK_API AnalysisCanvas : public SkCanvas, public SkDrawPictureCallback { + public: + AnalysisCanvas(AnalysisDevice*); + virtual ~AnalysisCanvas(); + + // Returns true when a SkColor can be used to represent result. + bool GetColorIfSolid(SkColor* color) const; + bool HasText() const; + + // SkDrawPictureCallback override. + virtual bool abortDrawing() OVERRIDE; + + // SkCanvas overrides. + virtual bool clipRect(const SkRect& rect, + SkRegion::Op op = SkRegion::kIntersect_Op, + bool do_anti_alias = false) OVERRIDE; + virtual bool clipPath(const SkPath& path, + SkRegion::Op op = SkRegion::kIntersect_Op, + bool do_anti_alias = false) OVERRIDE; + virtual bool clipRRect(const SkRRect& rrect, + SkRegion::Op op = SkRegion::kIntersect_Op, + bool do_anti_alias = false) OVERRIDE; + + virtual int saveLayer(const SkRect* bounds, + const SkPaint* paint, + SkCanvas::SaveFlags flags) OVERRIDE; + virtual int save(SaveFlags flags = kMatrixClip_SaveFlag) OVERRIDE; + + virtual void restore() OVERRIDE; + + private: + typedef SkCanvas INHERITED; + + int saved_stack_size_; + int force_not_solid_stack_level_; + int force_not_transparent_stack_level_; +}; + +class SK_API AnalysisDevice : public SkDevice { + public: + AnalysisDevice(const SkBitmap& bitmap); + virtual ~AnalysisDevice(); + + bool GetColorIfSolid(SkColor* color) const; + bool HasText() const; + + void SetForceNotSolid(bool flag); + void SetForceNotTransparent(bool flag); + + protected: + // SkDevice overrides. + virtual void clear(SkColor color) OVERRIDE; + virtual void drawPaint(const SkDraw& draw, const SkPaint& paint) OVERRIDE; + virtual void drawPoints(const SkDraw& draw, + SkCanvas::PointMode mode, + size_t count, + const SkPoint points[], + const SkPaint& paint) OVERRIDE; + virtual void drawRect(const SkDraw& draw, + const SkRect& rect, + const SkPaint& paint) OVERRIDE; + virtual void drawOval(const SkDraw& draw, + const SkRect& oval, + const SkPaint& paint) OVERRIDE; + virtual void drawPath(const SkDraw& draw, + const SkPath& path, + const SkPaint& paint, + const SkMatrix* pre_path_matrix = NULL, + bool path_is_mutable = false) OVERRIDE; + virtual void drawBitmap(const SkDraw& draw, + const SkBitmap& bitmap, + const SkMatrix& matrix, + const SkPaint& paint) OVERRIDE; + virtual void drawSprite(const SkDraw& draw, + const SkBitmap& bitmap, + int x, + int y, + const SkPaint& paint) OVERRIDE; + virtual void drawBitmapRect(const SkDraw& draw, + const SkBitmap& bitmap, + const SkRect* src_or_null, + const SkRect& dst, + const SkPaint& paint) OVERRIDE; + virtual void drawText(const SkDraw& draw, + const void* text, + size_t len, + SkScalar x, + SkScalar y, + const SkPaint& paint) OVERRIDE; + virtual void drawPosText(const SkDraw& draw, + const void* text, + size_t len, + const SkScalar pos[], + SkScalar const_y, + int scalars_per_pos, + const SkPaint& paint) OVERRIDE; + virtual void drawTextOnPath(const SkDraw& draw, + const void* text, + size_t len, + const SkPath& path, + const SkMatrix* matrix, + const SkPaint& paint) OVERRIDE; +#ifdef SK_BUILD_FOR_ANDROID + virtual void drawPosTextOnPath(const SkDraw& draw, + const void* text, + size_t len, + const SkPoint pos[], + const SkPaint& paint, + const SkPath& path, + const SkMatrix* matrix) OVERRIDE; +#endif + virtual void drawVertices(const SkDraw& draw, + SkCanvas::VertexMode vertex_mode, + int vertex_count, + const SkPoint verts[], + const SkPoint texs[], + const SkColor colors[], + SkXfermode* xmode, + const uint16_t indices[], + int index_count, + const SkPaint& paint) OVERRIDE; + virtual void drawDevice(const SkDraw& draw, + SkDevice* device, + int x, + int y, + const SkPaint& paint) OVERRIDE; + + private: + typedef SkDevice INHERITED; + + bool is_forced_not_solid_; + bool is_forced_not_transparent_; + bool is_solid_color_; + SkColor color_; + bool is_transparent_; + bool has_text_; +}; + +} // namespace skia + +#endif // SKIA_EXT_ANALYSIS_CANVAS_H_ + diff --git a/chromium/skia/ext/analysis_canvas_unittest.cc b/chromium/skia/ext/analysis_canvas_unittest.cc new file mode 100644 index 00000000000..0b6103106ad --- /dev/null +++ b/chromium/skia/ext/analysis_canvas_unittest.cc @@ -0,0 +1,406 @@ +// 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/compiler_specific.h" +#include "skia/ext/analysis_canvas.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "third_party/skia/include/core/SkShader.h" + +namespace { + +void SolidColorFill(skia::AnalysisCanvas& canvas) { + canvas.clear(SkColorSetARGB(255, 255, 255, 255)); +} + +void TransparentFill(skia::AnalysisCanvas& canvas) { + canvas.clear(SkColorSetARGB(0, 0, 0, 0)); +} + +} // namespace +namespace skia { + +TEST(AnalysisCanvasTest, EmptyCanvas) { + SkBitmap emptyBitmap; + emptyBitmap.setConfig(SkBitmap::kNo_Config, 255, 255); + skia::AnalysisDevice device(emptyBitmap); + skia::AnalysisCanvas canvas(&device); + + SkColor color; + EXPECT_TRUE(canvas.GetColorIfSolid(&color)); + EXPECT_EQ(color, SkColorSetARGB(0, 0, 0, 0)); +} + +TEST(AnalysisCanvasTest, ClearCanvas) { + SkBitmap emptyBitmap; + emptyBitmap.setConfig(SkBitmap::kNo_Config, 255, 255); + skia::AnalysisDevice device(emptyBitmap); + skia::AnalysisCanvas canvas(&device); + + // Transparent color + SkColor color = SkColorSetARGB(0, 12, 34, 56); + canvas.clear(color); + + SkColor outputColor; + EXPECT_TRUE(canvas.GetColorIfSolid(&outputColor)); + EXPECT_EQ(static_cast<SkColor>(SK_ColorTRANSPARENT), outputColor); + + // Solid color + color = SkColorSetARGB(255, 65, 43, 21); + canvas.clear(color); + + EXPECT_TRUE(canvas.GetColorIfSolid(&outputColor)); + EXPECT_NE(static_cast<SkColor>(SK_ColorTRANSPARENT), outputColor); + EXPECT_EQ(outputColor, color); + + // Translucent color + color = SkColorSetARGB(128, 11, 22, 33); + canvas.clear(color); + + EXPECT_FALSE(canvas.GetColorIfSolid(&outputColor)); + + // Test helper methods + SolidColorFill(canvas); + EXPECT_TRUE(canvas.GetColorIfSolid(&outputColor)); + EXPECT_NE(static_cast<SkColor>(SK_ColorTRANSPARENT), outputColor); + + TransparentFill(canvas); + EXPECT_TRUE(canvas.GetColorIfSolid(&outputColor)); + EXPECT_EQ(static_cast<SkColor>(SK_ColorTRANSPARENT), outputColor); +} + +TEST(AnalysisCanvasTest, ComplexActions) { + SkBitmap emptyBitmap; + emptyBitmap.setConfig(SkBitmap::kNo_Config, 255, 255); + skia::AnalysisDevice device(emptyBitmap); + skia::AnalysisCanvas canvas(&device); + + // Draw paint test. + SkColor color = SkColorSetARGB(255, 11, 22, 33); + SkPaint paint; + paint.setColor(color); + + canvas.drawPaint(paint); + + SkColor outputColor; + //TODO(vmpstr): This should return true. (crbug.com/180597) + EXPECT_FALSE(canvas.GetColorIfSolid(&outputColor)); + + // Draw points test. + SkPoint points[4] = { + SkPoint::Make(0, 0), + SkPoint::Make(255, 0), + SkPoint::Make(255, 255), + SkPoint::Make(0, 255) + }; + + SolidColorFill(canvas); + canvas.drawPoints(SkCanvas::kLines_PointMode, 4, points, paint); + + EXPECT_FALSE(canvas.GetColorIfSolid(&outputColor)); + + // Draw oval test. + SolidColorFill(canvas); + canvas.drawOval(SkRect::MakeWH(255, 255), paint); + + EXPECT_FALSE(canvas.GetColorIfSolid(&outputColor)); + + // Draw bitmap test. + SolidColorFill(canvas); + SkBitmap secondBitmap; + secondBitmap.setConfig(SkBitmap::kNo_Config, 255, 255); + canvas.drawBitmap(secondBitmap, 0, 0); + + EXPECT_FALSE(canvas.GetColorIfSolid(&outputColor)); +} + +TEST(AnalysisCanvasTest, SimpleDrawRect) { + SkBitmap emptyBitmap; + emptyBitmap.setConfig(SkBitmap::kNo_Config, 255, 255); + skia::AnalysisDevice device(emptyBitmap); + skia::AnalysisCanvas canvas(&device); + + SkColor color = SkColorSetARGB(255, 11, 22, 33); + SkPaint paint; + paint.setColor(color); + canvas.clipRect(SkRect::MakeWH(255, 255)); + canvas.drawRect(SkRect::MakeWH(255, 255), paint); + + SkColor outputColor; + EXPECT_TRUE(canvas.GetColorIfSolid(&outputColor)); + EXPECT_NE(static_cast<SkColor>(SK_ColorTRANSPARENT), outputColor); + EXPECT_EQ(color, outputColor); + + color = SkColorSetARGB(255, 22, 33, 44); + paint.setColor(color); + canvas.translate(-128, -128); + canvas.drawRect(SkRect::MakeWH(382, 382), paint); + + EXPECT_FALSE(canvas.GetColorIfSolid(&outputColor)); + + color = SkColorSetARGB(255, 33, 44, 55); + paint.setColor(color); + canvas.drawRect(SkRect::MakeWH(383, 383), paint); + + EXPECT_TRUE(canvas.GetColorIfSolid(&outputColor)); + EXPECT_NE(static_cast<SkColor>(SK_ColorTRANSPARENT), outputColor); + EXPECT_EQ(color, outputColor); + + color = SkColorSetARGB(0, 0, 0, 0); + paint.setColor(color); + canvas.drawRect(SkRect::MakeWH(383, 383), paint); + + EXPECT_TRUE(canvas.GetColorIfSolid(&outputColor)); + EXPECT_NE(static_cast<SkColor>(SK_ColorTRANSPARENT), outputColor); + EXPECT_EQ(outputColor, SkColorSetARGB(255, 33, 44, 55)); + + color = SkColorSetARGB(128, 128, 128, 128); + paint.setColor(color); + canvas.drawRect(SkRect::MakeWH(383, 383), paint); + + EXPECT_FALSE(canvas.GetColorIfSolid(&outputColor)); + + paint.setXfermodeMode(SkXfermode::kClear_Mode); + canvas.drawRect(SkRect::MakeWH(382, 382), paint); + + EXPECT_FALSE(canvas.GetColorIfSolid(&outputColor)); + + canvas.drawRect(SkRect::MakeWH(383, 383), paint); + + EXPECT_TRUE(canvas.GetColorIfSolid(&outputColor)); + EXPECT_EQ(static_cast<SkColor>(SK_ColorTRANSPARENT), outputColor); + + canvas.translate(128, 128); + color = SkColorSetARGB(255, 11, 22, 33); + paint.setColor(color); + paint.setXfermodeMode(SkXfermode::kSrcOver_Mode); + canvas.drawRect(SkRect::MakeWH(255, 255), paint); + + EXPECT_TRUE(canvas.GetColorIfSolid(&outputColor)); + EXPECT_NE(static_cast<SkColor>(SK_ColorTRANSPARENT), outputColor); + EXPECT_EQ(color, outputColor); + + canvas.rotate(50); + canvas.drawRect(SkRect::MakeWH(255, 255), paint); + + EXPECT_FALSE(canvas.GetColorIfSolid(&outputColor)); +} + +TEST(AnalysisCanvasTest, ClipPath) { + SkBitmap emptyBitmap; + emptyBitmap.setConfig(SkBitmap::kNo_Config, 255, 255); + skia::AnalysisDevice device(emptyBitmap); + skia::AnalysisCanvas canvas(&device); + + SkPath path; + path.moveTo(0, 0); + path.lineTo(255, 0); + path.lineTo(255, 255); + path.lineTo(0, 255); + + SkColor outputColor; + SolidColorFill(canvas); + canvas.clipPath(path); + EXPECT_FALSE(canvas.GetColorIfSolid(&outputColor)); + + canvas.save(); + EXPECT_FALSE(canvas.GetColorIfSolid(&outputColor)); + + canvas.clipPath(path); + EXPECT_FALSE(canvas.GetColorIfSolid(&outputColor)); + + canvas.restore(); + EXPECT_FALSE(canvas.GetColorIfSolid(&outputColor)); + + SolidColorFill(canvas); + EXPECT_FALSE(canvas.GetColorIfSolid(&outputColor)); +} + +TEST(AnalysisCanvasTest, SaveLayerRestore) { + SkBitmap emptyBitmap; + emptyBitmap.setConfig(SkBitmap::kNo_Config, 255, 255); + skia::AnalysisDevice device(emptyBitmap); + skia::AnalysisCanvas canvas(&device); + + SkColor outputColor; + SolidColorFill(canvas); + EXPECT_TRUE(canvas.GetColorIfSolid(&outputColor)); + + SkRect bounds = SkRect::MakeWH(255, 255); + SkPaint paint; + paint.setColor(SkColorSetARGB(255, 255, 255, 255)); + paint.setXfermodeMode(SkXfermode::kSrcOver_Mode); + + // This should force non-transparency + canvas.saveLayer(&bounds, &paint, SkCanvas::kMatrix_SaveFlag); + EXPECT_TRUE(canvas.GetColorIfSolid(&outputColor)); + EXPECT_NE(static_cast<SkColor>(SK_ColorTRANSPARENT), outputColor); + + TransparentFill(canvas); + EXPECT_FALSE(canvas.GetColorIfSolid(&outputColor)); + + SolidColorFill(canvas); + EXPECT_TRUE(canvas.GetColorIfSolid(&outputColor)); + EXPECT_NE(static_cast<SkColor>(SK_ColorTRANSPARENT), outputColor); + + paint.setXfermodeMode(SkXfermode::kDst_Mode); + + // This should force non-solid color + canvas.saveLayer(&bounds, &paint, SkCanvas::kMatrix_SaveFlag); + EXPECT_FALSE(canvas.GetColorIfSolid(&outputColor)); + + TransparentFill(canvas); + EXPECT_FALSE(canvas.GetColorIfSolid(&outputColor)); + + SolidColorFill(canvas); + EXPECT_FALSE(canvas.GetColorIfSolid(&outputColor)); + + canvas.restore(); + EXPECT_FALSE(canvas.GetColorIfSolid(&outputColor)); + + TransparentFill(canvas); + EXPECT_FALSE(canvas.GetColorIfSolid(&outputColor)); + + SolidColorFill(canvas); + EXPECT_TRUE(canvas.GetColorIfSolid(&outputColor)); + EXPECT_NE(static_cast<SkColor>(SK_ColorTRANSPARENT), outputColor); + + canvas.restore(); + EXPECT_TRUE(canvas.GetColorIfSolid(&outputColor)); + EXPECT_NE(static_cast<SkColor>(SK_ColorTRANSPARENT), outputColor); + + TransparentFill(canvas); + EXPECT_TRUE(canvas.GetColorIfSolid(&outputColor)); + EXPECT_EQ(static_cast<SkColor>(SK_ColorTRANSPARENT), outputColor); + + SolidColorFill(canvas); + EXPECT_TRUE(canvas.GetColorIfSolid(&outputColor)); + EXPECT_NE(static_cast<SkColor>(SK_ColorTRANSPARENT), outputColor); +} + +TEST(AnalysisCanvasTest, HasText) { + int width = 200; + int height = 100; + + SkBitmap bitmap; + bitmap.setConfig(SkBitmap::kNo_Config, width, height); + + const char* text = "A"; + size_t byteLength = 1; + + SkPoint point = SkPoint::Make(SkIntToScalar(25), SkIntToScalar(25)); + SkPath path; + path.moveTo(point); + path.lineTo(SkIntToScalar(75), SkIntToScalar(75)); + + SkPaint paint; + paint.setColor(SK_ColorGRAY); + paint.setTextSize(SkIntToScalar(10)); + + { + skia::AnalysisDevice device(bitmap); + skia::AnalysisCanvas canvas(&device); + // Test after initialization. + EXPECT_FALSE(canvas.HasText()); + // Test drawing anything other than text. + canvas.drawRect(SkRect::MakeWH(width/2, height), paint); + EXPECT_FALSE(canvas.HasText()); + } + { + // Test SkCanvas::drawText. + skia::AnalysisDevice device(bitmap); + skia::AnalysisCanvas canvas(&device); + canvas.drawText(text, byteLength, point.fX, point.fY, paint); + EXPECT_TRUE(canvas.HasText()); + } + { + // Test SkCanvas::drawPosText. + skia::AnalysisDevice device(bitmap); + skia::AnalysisCanvas canvas(&device); + canvas.drawPosText(text, byteLength, &point, paint); + EXPECT_TRUE(canvas.HasText()); + } + { + // Test SkCanvas::drawPosTextH. + skia::AnalysisDevice device(bitmap); + skia::AnalysisCanvas canvas(&device); + canvas.drawPosTextH(text, byteLength, &point.fX, point.fY, paint); + EXPECT_TRUE(canvas.HasText()); + } + { + // Test SkCanvas::drawTextOnPathHV. + skia::AnalysisDevice device(bitmap); + skia::AnalysisCanvas canvas(&device); + canvas.drawTextOnPathHV(text, byteLength, path, point.fX, point.fY, paint); + EXPECT_TRUE(canvas.HasText()); + } + { + // Test SkCanvas::drawTextOnPath. + skia::AnalysisDevice device(bitmap); + skia::AnalysisCanvas canvas(&device); + canvas.drawTextOnPath(text, byteLength, path, NULL, paint); + EXPECT_TRUE(canvas.HasText()); + } + { + // Text under opaque rect. + skia::AnalysisDevice device(bitmap); + skia::AnalysisCanvas canvas(&device); + canvas.drawText(text, byteLength, point.fX, point.fY, paint); + EXPECT_TRUE(canvas.HasText()); + canvas.drawRect(SkRect::MakeWH(width, height), paint); + EXPECT_FALSE(canvas.HasText()); + } + { + // Text under translucent rect. + skia::AnalysisDevice device(bitmap); + skia::AnalysisCanvas canvas(&device); + canvas.drawText(text, byteLength, point.fX, point.fY, paint); + EXPECT_TRUE(canvas.HasText()); + SkPaint translucentPaint; + translucentPaint.setColor(0x88FFFFFF); + canvas.drawRect(SkRect::MakeWH(width, height), translucentPaint); + EXPECT_TRUE(canvas.HasText()); + } + { + // Text under rect in clear mode. + skia::AnalysisDevice device(bitmap); + skia::AnalysisCanvas canvas(&device); + canvas.drawText(text, byteLength, point.fX, point.fY, paint); + EXPECT_TRUE(canvas.HasText()); + SkPaint clearModePaint; + clearModePaint.setXfermodeMode(SkXfermode::kClear_Mode); + canvas.drawRect(SkRect::MakeWH(width, height), clearModePaint); + EXPECT_FALSE(canvas.HasText()); + } + { + // Clear. + skia::AnalysisDevice device(bitmap); + skia::AnalysisCanvas canvas(&device); + canvas.drawText(text, byteLength, point.fX, point.fY, paint); + EXPECT_TRUE(canvas.HasText()); + canvas.clear(SK_ColorGRAY); + EXPECT_FALSE(canvas.HasText()); + } + { + // Text inside clip region. + skia::AnalysisDevice device(bitmap); + skia::AnalysisCanvas canvas(&device); + canvas.clipRect(SkRect::MakeWH(100, 100)); + canvas.drawText(text, byteLength, point.fX, point.fY, paint); + EXPECT_TRUE(canvas.HasText()); + } + { + // Text outside clip region. + skia::AnalysisDevice device(bitmap); + skia::AnalysisCanvas canvas(&device); + canvas.clipRect(SkRect::MakeXYWH(100, 0, 100, 100)); + canvas.drawText(text, byteLength, point.fX, point.fY, paint); + // Analysis device does not do any clipping. + // So even when text is outside the clip region, + // it is marked as having the text. + // TODO(alokp): We may be able to do some trivial rejection. + EXPECT_TRUE(canvas.HasText()); + } +} + +} // namespace skia diff --git a/chromium/skia/ext/benchmarking_canvas.cc b/chromium/skia/ext/benchmarking_canvas.cc new file mode 100644 index 00000000000..d83252b4c55 --- /dev/null +++ b/chromium/skia/ext/benchmarking_canvas.cc @@ -0,0 +1,238 @@ +// 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 "base/containers/hash_tables.h" +#include "base/logging.h" +#include "base/time/time.h" +#include "skia/ext/benchmarking_canvas.h" +#include "third_party/skia/include/core/SkDevice.h" +#include "third_party/skia/include/utils/SkProxyCanvas.h" + +namespace skia { + +class AutoStamper { +public: + AutoStamper(TimingCanvas* timing_canvas); + ~AutoStamper(); + +private: + TimingCanvas* timing_canvas_; + base::TimeTicks start_ticks_; +}; + +class TimingCanvas : public SkProxyCanvas { +public: + TimingCanvas(int width, int height, const BenchmarkingCanvas* track_canvas) + : tracking_canvas_(track_canvas) { + skia::RefPtr<SkDevice> device = skia::AdoptRef( + SkNEW_ARGS(SkDevice, (SkBitmap::kARGB_8888_Config, width, height))); + canvas_ = skia::AdoptRef(SkNEW_ARGS(SkCanvas, (device.get()))); + + setProxy(canvas_.get()); + } + + virtual ~TimingCanvas() { + } + + double GetTime(size_t index) { + TimingsMap::const_iterator timing_info = timings_map_.find(index); + return timing_info != timings_map_.end() + ? timing_info->second.InMillisecondsF() + : 0.0; + } + + // SkCanvas overrides. + virtual int save(SaveFlags flags = kMatrixClip_SaveFlag) OVERRIDE { + AutoStamper stamper(this); + return SkProxyCanvas::save(flags); + } + + virtual int saveLayer(const SkRect* bounds, const SkPaint* paint, + SaveFlags flags = kARGB_ClipLayer_SaveFlag) OVERRIDE { + AutoStamper stamper(this); + return SkProxyCanvas::saveLayer(bounds, paint, flags); + } + + virtual void restore() OVERRIDE { + AutoStamper stamper(this); + SkProxyCanvas::restore(); + } + + virtual bool clipRect(const SkRect& rect, SkRegion::Op op, + bool doAa) OVERRIDE { + AutoStamper stamper(this); + return SkProxyCanvas::clipRect(rect, op, doAa); + } + + virtual bool clipRRect(const SkRRect& rrect, SkRegion::Op op, + bool doAa) OVERRIDE { + AutoStamper stamper(this); + return SkProxyCanvas::clipRRect(rrect, op, doAa); + } + + virtual bool clipPath(const SkPath& path, SkRegion::Op op, + bool doAa) OVERRIDE { + AutoStamper stamper(this); + return SkProxyCanvas::clipPath(path, op, doAa); + } + + virtual bool clipRegion(const SkRegion& region, + SkRegion::Op op = SkRegion::kIntersect_Op) OVERRIDE { + AutoStamper stamper(this); + return SkProxyCanvas::clipRegion(region, op); + } + + virtual void drawPaint(const SkPaint& paint) OVERRIDE { + AutoStamper stamper(this); + SkProxyCanvas::drawPaint(paint); + } + + virtual void drawPoints(PointMode mode, size_t count, const SkPoint pts[], + const SkPaint& paint) OVERRIDE { + AutoStamper stamper(this); + SkProxyCanvas::drawPoints(mode, count, pts, paint); + } + + virtual void drawOval(const SkRect& rect, const SkPaint& paint) OVERRIDE { + AutoStamper stamper(this); + SkProxyCanvas::drawOval(rect, paint); + } + + virtual void drawRect(const SkRect& rect, const SkPaint& paint) OVERRIDE { + AutoStamper stamper(this); + SkProxyCanvas::drawRect(rect, paint); + } + + virtual void drawRRect(const SkRRect& rrect, const SkPaint& paint) OVERRIDE { + AutoStamper stamper(this); + SkProxyCanvas::drawRRect(rrect, paint); + } + + virtual void drawPath(const SkPath& path, const SkPaint& paint) OVERRIDE { + AutoStamper stamper(this); + SkProxyCanvas::drawPath(path, paint); + } + + virtual void drawBitmap(const SkBitmap& bitmap, SkScalar left, SkScalar top, + const SkPaint* paint = NULL) OVERRIDE { + AutoStamper stamper(this); + SkProxyCanvas::drawBitmap(bitmap, left, top, paint); + } + + virtual void drawBitmapRectToRect(const SkBitmap& bitmap, const SkRect* src, + const SkRect& dst, + const SkPaint* paint = NULL) OVERRIDE { + AutoStamper stamper(this); + SkProxyCanvas::drawBitmapRectToRect(bitmap, src, dst, paint); + } + + virtual void drawBitmapMatrix(const SkBitmap& bitmap, const SkMatrix& m, + const SkPaint* paint = NULL) OVERRIDE { + AutoStamper stamper(this); + SkProxyCanvas::drawBitmapMatrix(bitmap, m, paint); + } + + virtual void drawSprite(const SkBitmap& bitmap, int left, int top, + const SkPaint* paint = NULL) OVERRIDE { + AutoStamper stamper(this); + SkProxyCanvas::drawSprite(bitmap, left, top, paint); + } + + virtual void drawText(const void* text, size_t byteLength, SkScalar x, + SkScalar y, const SkPaint& paint) OVERRIDE { + AutoStamper stamper(this); + SkProxyCanvas::drawText(text, byteLength, x, y, paint); + } + + virtual void drawPosText(const void* text, size_t byteLength, + const SkPoint pos[], + const SkPaint& paint) OVERRIDE { + AutoStamper stamper(this); + SkProxyCanvas::drawPosText(text, byteLength, pos, paint); + } + + virtual void drawPosTextH(const void* text, size_t byteLength, + const SkScalar xpos[], SkScalar constY, + const SkPaint& paint) OVERRIDE { + AutoStamper stamper(this); + SkProxyCanvas::drawPosTextH(text, byteLength, xpos, constY, paint); + } + + virtual void drawTextOnPath(const void* text, size_t byteLength, + const SkPath& path, const SkMatrix* matrix, + const SkPaint& paint) OVERRIDE { + AutoStamper stamper(this); + SkProxyCanvas::drawTextOnPath(text, byteLength, path, matrix, paint); + } + + virtual void drawPicture(SkPicture& picture) OVERRIDE { + AutoStamper stamper(this); + SkProxyCanvas::drawPicture(picture); + } + + virtual void drawVertices(VertexMode vmode, int vertexCount, + const SkPoint vertices[], const SkPoint texs[], + const SkColor colors[], SkXfermode* xmode, + const uint16_t indices[], int indexCount, + const SkPaint& paint) OVERRIDE { + AutoStamper stamper(this); + SkProxyCanvas::drawVertices(vmode, vertexCount, vertices, texs, colors, + xmode, indices, indexCount, paint); + } + + virtual void drawData(const void* data, size_t length) OVERRIDE { + AutoStamper stamper(this); + SkProxyCanvas::drawData(data, length); + } + +private: + typedef base::hash_map<size_t, base::TimeDelta> TimingsMap; + TimingsMap timings_map_; + + skia::RefPtr<SkCanvas> canvas_; + + friend class AutoStamper; + const BenchmarkingCanvas* tracking_canvas_; +}; + +AutoStamper::AutoStamper(TimingCanvas *timing_canvas) + : timing_canvas_(timing_canvas) { + start_ticks_ = base::TimeTicks::HighResNow(); +} + +AutoStamper::~AutoStamper() { + base::TimeDelta delta = base::TimeTicks::HighResNow() - start_ticks_; + int command_index = timing_canvas_->tracking_canvas_->CommandCount() - 1; + DCHECK_GE(command_index, 0); + timing_canvas_->timings_map_[command_index] = delta; +} + +BenchmarkingCanvas::BenchmarkingCanvas(int width, int height) + : SkNWayCanvas(width, height) { + debug_canvas_ = skia::AdoptRef(SkNEW_ARGS(SkDebugCanvas, (width, height))); + timing_canvas_ = skia::AdoptRef(SkNEW_ARGS(TimingCanvas, (width, height, this))); + + addCanvas(debug_canvas_.get()); + addCanvas(timing_canvas_.get()); +} + +BenchmarkingCanvas::~BenchmarkingCanvas() { + removeAll(); +} + +size_t BenchmarkingCanvas::CommandCount() const { + return debug_canvas_->getSize(); +} + +SkDrawCommand* BenchmarkingCanvas::GetCommand(size_t index) { + DCHECK_LT(index, static_cast<size_t>(debug_canvas_->getSize())); + return debug_canvas_->getDrawCommandAt(index); +} + +double BenchmarkingCanvas::GetTime(size_t index) { + DCHECK_LT(index, static_cast<size_t>(debug_canvas_->getSize())); + return timing_canvas_->GetTime(index); +} + +} // namespace skia diff --git a/chromium/skia/ext/benchmarking_canvas.h b/chromium/skia/ext/benchmarking_canvas.h new file mode 100644 index 00000000000..7ef82049b2c --- /dev/null +++ b/chromium/skia/ext/benchmarking_canvas.h @@ -0,0 +1,50 @@ +// 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 SKIA_EXT_BENCHMARKING_CANVAS_H_ +#define SKIA_EXT_BENCHMARKING_CANVAS_H_ + +#include "base/compiler_specific.h" +#include "skia/ext/refptr.h" +#include "third_party/skia/include/utils/SkNWayCanvas.h" +#include "third_party/skia/src/utils/debugger/SkDebugCanvas.h" + +namespace skia { + +class TimingCanvas; + +class SK_API BenchmarkingCanvas : public SkNWayCanvas { +public: + BenchmarkingCanvas(int width, int height); + virtual ~BenchmarkingCanvas(); + + // Returns the number of draw commands executed on this canvas. + size_t CommandCount() const; + + // Get draw command info for a given index. + SkDrawCommand* GetCommand(size_t index); + + // Return the recorded render time (milliseconds) for a draw command index. + double GetTime(size_t index); + +private: + // In order to avoid introducing a Skia version dependency, this + // implementation dispatches draw commands in lock-step to two distinct + // canvases: + // * a SkDebugCanvas used for gathering command info and tracking + // the current command index + // * a SkiaTimingCanvas used for measuring raster paint times (and relying + // on the former for tracking the current command index). + // + // This way, if the SkCanvas API is extended, we don't need to worry about + // updating content::SkiaTimingCanvas to accurately override all new methods + // (to avoid timing info indices from getting out of sync), as SkDebugCanvas + // already does that for us. + + skia::RefPtr<SkDebugCanvas> debug_canvas_; + skia::RefPtr<TimingCanvas> timing_canvas_; +}; + +} +#endif // SKIA_EXT_BENCHMARKING_CANVAS_H diff --git a/chromium/skia/ext/bitmap_platform_device.h b/chromium/skia/ext/bitmap_platform_device.h new file mode 100644 index 00000000000..56e41763f8d --- /dev/null +++ b/chromium/skia/ext/bitmap_platform_device.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 SKIA_EXT_BITMAP_PLATFORM_DEVICE_H_ +#define SKIA_EXT_BITMAP_PLATFORM_DEVICE_H_ + +// This file provides an easy way to include the appropriate +// BitmapPlatformDevice header file for your platform. + +#if defined(WIN32) +#include "skia/ext/bitmap_platform_device_win.h" +#elif defined(__APPLE__) +#include "skia/ext/bitmap_platform_device_mac.h" +#elif defined(ANDROID) +#include "skia/ext/bitmap_platform_device_android.h" +#elif defined(__linux__) || defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__sun) +#include "skia/ext/bitmap_platform_device_linux.h" +#endif + +namespace skia { + // Returns true if it is unsafe to attempt to allocate an offscreen buffer + // given these dimensions. + inline bool RasterDeviceTooBigToAllocate(int width, int height) { + +#ifndef SKIA_EXT_RASTER_DEVICE_ALLOCATION_MAX +#define SKIA_EXT_RASTER_DEVICE_ALLOCATION_MAX (2 * 256 * 1024 * 1024) +#endif + + int bytesPerPixel = 4; + int64_t bytes = (int64_t)width * height * bytesPerPixel; + return bytes > SKIA_EXT_RASTER_DEVICE_ALLOCATION_MAX; + } +} + +#endif // SKIA_EXT_BITMAP_PLATFORM_DEVICE_H_ diff --git a/chromium/skia/ext/bitmap_platform_device_android.cc b/chromium/skia/ext/bitmap_platform_device_android.cc new file mode 100644 index 00000000000..8a29586abcb --- /dev/null +++ b/chromium/skia/ext/bitmap_platform_device_android.cc @@ -0,0 +1,99 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "skia/ext/bitmap_platform_device_android.h" +#include "skia/ext/platform_canvas.h" + +namespace skia { + +BitmapPlatformDevice* BitmapPlatformDevice::Create(int width, int height, + bool is_opaque) { + SkBitmap bitmap; + bitmap.setConfig(SkBitmap::kARGB_8888_Config, width, height); + if (bitmap.allocPixels()) { + bitmap.setIsOpaque(is_opaque); + // Follow the logic in SkCanvas::createDevice(), initialize the bitmap if it + // is not opaque. + if (!is_opaque) + bitmap.eraseARGB(0, 0, 0, 0); + return new BitmapPlatformDevice(bitmap); + } + return NULL; +} + +BitmapPlatformDevice* BitmapPlatformDevice::CreateAndClear(int width, + int height, + bool is_opaque) { + BitmapPlatformDevice* device = Create(width, height, is_opaque); + if (!is_opaque) + device->accessBitmap(true).eraseARGB(0, 0, 0, 0); + return device; +} + +BitmapPlatformDevice* BitmapPlatformDevice::Create(int width, int height, + bool is_opaque, + uint8_t* data) { + SkBitmap bitmap; + bitmap.setConfig(SkBitmap::kARGB_8888_Config, width, height); + if (data) + bitmap.setPixels(data); + else if (!bitmap.allocPixels()) + return NULL; + + bitmap.setIsOpaque(is_opaque); + return new BitmapPlatformDevice(bitmap); +} + +BitmapPlatformDevice::BitmapPlatformDevice(const SkBitmap& bitmap) + : SkDevice(bitmap) { + SetPlatformDevice(this, this); +} + +BitmapPlatformDevice::~BitmapPlatformDevice() { +} + +SkDevice* BitmapPlatformDevice::onCreateCompatibleDevice( + SkBitmap::Config config, int width, int height, bool isOpaque, + Usage /*usage*/) { + SkASSERT(config == SkBitmap::kARGB_8888_Config); + return BitmapPlatformDevice::Create(width, height, isOpaque); +} + +PlatformSurface BitmapPlatformDevice::BeginPlatformPaint() { + // TODO(zhenghao): What should we return? The ptr to the address of the + // pixels? Maybe this won't be called at all. + return accessBitmap(true).getPixels(); +} + +void BitmapPlatformDevice::DrawToNativeContext( + PlatformSurface surface, int x, int y, const PlatformRect* src_rect) { + // Should never be called on Android. + SkASSERT(false); +} + +// PlatformCanvas impl + +SkCanvas* CreatePlatformCanvas(int width, int height, bool is_opaque, + uint8_t* data, OnFailureType failureType) { + skia::RefPtr<SkDevice> dev = skia::AdoptRef( + BitmapPlatformDevice::Create(width, height, is_opaque, data)); + return CreateCanvas(dev, failureType); +} + +// Port of PlatformBitmap to android +PlatformBitmap::~PlatformBitmap() { + // Nothing to do. +} + +bool PlatformBitmap::Allocate(int width, int height, bool is_opaque) { + bitmap_.setConfig(SkBitmap::kARGB_8888_Config, width, height); + if (!bitmap_.allocPixels()) + return false; + + bitmap_.setIsOpaque(is_opaque); + surface_ = bitmap_.getPixels(); + return true; +} + +} // namespace skia diff --git a/chromium/skia/ext/bitmap_platform_device_android.h b/chromium/skia/ext/bitmap_platform_device_android.h new file mode 100644 index 00000000000..b5755cb4b0b --- /dev/null +++ b/chromium/skia/ext/bitmap_platform_device_android.h @@ -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. + +#ifndef SKIA_EXT_BITMAP_PLATFORM_DEVICE_ANDROID_H_ +#define SKIA_EXT_BITMAP_PLATFORM_DEVICE_ANDROID_H_ + +#include "base/memory/ref_counted.h" +#include "base/compiler_specific.h" +#include "skia/ext/platform_device.h" + +namespace skia { + +// ----------------------------------------------------------------------------- +// For now we just use SkBitmap for SkDevice +// +// This is all quite ok for test_shell. In the future we will want to use +// shared memory between the renderer and the main process at least. In this +// case we'll probably create the buffer from a precreated region of memory. +// ----------------------------------------------------------------------------- +class BitmapPlatformDevice : public SkDevice, public PlatformDevice { + public: + // Construct a BitmapPlatformDevice. |is_opaque| should be set if the caller + // knows the bitmap will be completely opaque and allows some optimizations. + // The bitmap is not initialized. + static BitmapPlatformDevice* Create(int width, int height, bool is_opaque); + + // Construct a BitmapPlatformDevice, as above. + // If |is_opaque| is false, the bitmap is initialized to 0. + static BitmapPlatformDevice* CreateAndClear(int width, int height, + bool is_opaque); + + // This doesn't take ownership of |data|. If |data| is null, the bitmap + // is not initialized to 0. + static BitmapPlatformDevice* Create(int width, int height, bool is_opaque, + uint8_t* data); + + // Create a BitmapPlatformDevice from an already constructed bitmap; + // you should probably be using Create(). This may become private later if + // we ever have to share state between some native drawing UI and Skia, like + // the Windows and Mac versions of this class do. + explicit BitmapPlatformDevice(const SkBitmap& other); + virtual ~BitmapPlatformDevice(); + + virtual PlatformSurface BeginPlatformPaint() OVERRIDE; + virtual void DrawToNativeContext(PlatformSurface surface, int x, int y, + const PlatformRect* src_rect) OVERRIDE; + + protected: + virtual SkDevice* onCreateCompatibleDevice(SkBitmap::Config, int width, + int height, bool isOpaque, + Usage usage) OVERRIDE; + + private: + DISALLOW_COPY_AND_ASSIGN(BitmapPlatformDevice); +}; + +} // namespace skia + +#endif // SKIA_EXT_BITMAP_PLATFORM_DEVICE_ANDROID_H_ diff --git a/chromium/skia/ext/bitmap_platform_device_data.h b/chromium/skia/ext/bitmap_platform_device_data.h new file mode 100644 index 00000000000..81e81ed79ff --- /dev/null +++ b/chromium/skia/ext/bitmap_platform_device_data.h @@ -0,0 +1,102 @@ +// 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 SKIA_EXT_BITMAP_PLATFORM_DEVICE_DATA_H_ +#define SKIA_EXT_BITMAP_PLATFORM_DEVICE_DATA_H_ + +#include "skia/ext/bitmap_platform_device.h" + +namespace skia { + +class BitmapPlatformDevice::BitmapPlatformDeviceData : +#if defined(WIN32) || defined(__APPLE__) + public SkRefCnt { +#elif defined(__linux__) || defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__sun) + // These objects are reference counted and own a Cairo surface. The surface + // is the backing store for a Skia bitmap and we reference count it so that + // we can copy BitmapPlatformDevice objects without having to copy all the + // image data. + public base::RefCounted<BitmapPlatformDeviceData> { +#endif + + public: +#if defined(WIN32) + typedef HBITMAP PlatformContext; +#elif defined(__APPLE__) + typedef CGContextRef PlatformContext; +#elif defined(__linux__) || defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__sun) + typedef cairo_t* PlatformContext; +#endif + +#if defined(WIN32) || defined(__APPLE__) + explicit BitmapPlatformDeviceData(PlatformContext bitmap); +#elif defined(__linux__) || defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__sun) + explicit BitmapPlatformDeviceData(cairo_surface_t* surface); +#endif + +#if defined(WIN32) + // Create/destroy hdc_, which is the memory DC for our bitmap data. + HDC GetBitmapDC(); + void ReleaseBitmapDC(); + bool IsBitmapDCCreated() const; +#endif + +#if defined(__APPLE__) + void ReleaseBitmapContext(); +#endif // defined(__APPLE__) + + // Sets the transform and clip operations. This will not update the CGContext, + // but will mark the config as dirty. The next call of LoadConfig will + // pick up these changes. + void SetMatrixClip(const SkMatrix& transform, const SkRegion& region); + + // Loads the current transform and clip into the context. Can be called even + // when |bitmap_context_| is NULL (will be a NOP). + void LoadConfig(); + + const SkMatrix& transform() const { + return transform_; + } + + PlatformContext bitmap_context() { + return bitmap_context_; + } + + private: +#if defined(__linux__) || defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__sun) + friend class base::RefCounted<BitmapPlatformDeviceData>; +#endif + virtual ~BitmapPlatformDeviceData(); + + // Lazily-created graphics context used to draw into the bitmap. + PlatformContext bitmap_context_; + +#if defined(WIN32) + // Lazily-created DC used to draw into the bitmap, see GetBitmapDC(). + HDC hdc_; +#elif defined(__linux__) || defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__sun) + cairo_surface_t *const surface_; +#endif + + // True when there is a transform or clip that has not been set to the + // context. The context is retrieved for every text operation, and the + // transform and clip do not change as much. We can save time by not loading + // the clip and transform for every one. + bool config_dirty_; + + // Translation assigned to the context: we need to keep track of this + // separately so it can be updated even if the context isn't created yet. + SkMatrix transform_; + + // The current clipping + SkRegion clip_region_; + + // Disallow copy & assign. + BitmapPlatformDeviceData(const BitmapPlatformDeviceData&); + BitmapPlatformDeviceData& operator=(const BitmapPlatformDeviceData&); +}; + +} // namespace skia + +#endif // SKIA_EXT_BITMAP_PLATFORM_DEVICE_DATA_H_ diff --git a/chromium/skia/ext/bitmap_platform_device_linux.cc b/chromium/skia/ext/bitmap_platform_device_linux.cc new file mode 100644 index 00000000000..c9a02148d8e --- /dev/null +++ b/chromium/skia/ext/bitmap_platform_device_linux.cc @@ -0,0 +1,219 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "skia/ext/bitmap_platform_device_linux.h" +#include "skia/ext/bitmap_platform_device_data.h" +#include "skia/ext/platform_canvas.h" + +#if defined(OS_OPENBSD) +#include <cairo.h> +#else +#include <cairo/cairo.h> +#endif + +namespace skia { + +namespace { + +void LoadMatrixToContext(cairo_t* context, const SkMatrix& matrix) { + cairo_matrix_t cairo_matrix; + cairo_matrix_init(&cairo_matrix, + SkScalarToFloat(matrix.getScaleX()), + SkScalarToFloat(matrix.getSkewY()), + SkScalarToFloat(matrix.getSkewX()), + SkScalarToFloat(matrix.getScaleY()), + SkScalarToFloat(matrix.getTranslateX()), + SkScalarToFloat(matrix.getTranslateY())); + cairo_set_matrix(context, &cairo_matrix); +} + +void LoadClipToContext(cairo_t* context, const SkRegion& clip) { + cairo_reset_clip(context); + + // TODO(brettw) support non-rect clips. + SkIRect bounding = clip.getBounds(); + cairo_rectangle(context, bounding.fLeft, bounding.fTop, + bounding.fRight - bounding.fLeft, + bounding.fBottom - bounding.fTop); + cairo_clip(context); +} + +} // namespace + +BitmapPlatformDevice::BitmapPlatformDeviceData::BitmapPlatformDeviceData( + cairo_surface_t* surface) + : surface_(surface), + config_dirty_(true), + transform_(SkMatrix::I()) { // Want to load the config next time. + bitmap_context_ = cairo_create(surface); +} + +BitmapPlatformDevice::BitmapPlatformDeviceData::~BitmapPlatformDeviceData() { + cairo_destroy(bitmap_context_); + cairo_surface_destroy(surface_); +} + +void BitmapPlatformDevice::BitmapPlatformDeviceData::SetMatrixClip( + const SkMatrix& transform, + const SkRegion& region) { + transform_ = transform; + clip_region_ = region; + config_dirty_ = true; +} + +void BitmapPlatformDevice::BitmapPlatformDeviceData::LoadConfig() { + if (!config_dirty_ || !bitmap_context_) + return; // Nothing to do. + config_dirty_ = false; + + // Load the identity matrix since this is what our clip is relative to. + cairo_matrix_t cairo_matrix; + cairo_matrix_init_identity(&cairo_matrix); + cairo_set_matrix(bitmap_context_, &cairo_matrix); + + LoadClipToContext(bitmap_context_, clip_region_); + LoadMatrixToContext(bitmap_context_, transform_); +} + +// We use this static factory function instead of the regular constructor so +// that we can create the pixel data before calling the constructor. This is +// required so that we can call the base class' constructor with the pixel +// data. +BitmapPlatformDevice* BitmapPlatformDevice::Create(int width, int height, + bool is_opaque, + cairo_surface_t* surface) { + if (cairo_surface_status(surface) != CAIRO_STATUS_SUCCESS) { + cairo_surface_destroy(surface); + return NULL; + } + SkBitmap bitmap; + bitmap.setConfig(SkBitmap::kARGB_8888_Config, width, height, + cairo_image_surface_get_stride(surface)); + bitmap.setPixels(cairo_image_surface_get_data(surface)); + bitmap.setIsOpaque(is_opaque); + + // The device object will take ownership of the graphics context. + return new BitmapPlatformDevice + (bitmap, new BitmapPlatformDeviceData(surface)); +} + +BitmapPlatformDevice* BitmapPlatformDevice::Create(int width, int height, + bool is_opaque) { + // This initializes the bitmap to all zeros. + cairo_surface_t* surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, + width, height); + + BitmapPlatformDevice* device = Create(width, height, is_opaque, surface); + +#ifndef NDEBUG + if (device && is_opaque) // Fill with bright bluish green + device->eraseColor(SkColorSetARGB(255, 0, 255, 128)); +#endif + + return device; +} + +BitmapPlatformDevice* BitmapPlatformDevice::CreateAndClear(int width, + int height, + bool is_opaque) { + // The Linux port always constructs initialized bitmaps, so there is no extra + // work to perform here. + return Create(width, height, is_opaque); +} + +BitmapPlatformDevice* BitmapPlatformDevice::Create(int width, int height, + bool is_opaque, + uint8_t* data) { + cairo_surface_t* surface = cairo_image_surface_create_for_data( + data, CAIRO_FORMAT_ARGB32, width, height, + cairo_format_stride_for_width(CAIRO_FORMAT_ARGB32, width)); + + return Create(width, height, is_opaque, surface); +} + +// The device will own the bitmap, which corresponds to also owning the pixel +// data. Therefore, we do not transfer ownership to the SkDevice's bitmap. +BitmapPlatformDevice::BitmapPlatformDevice( + const SkBitmap& bitmap, + BitmapPlatformDeviceData* data) + : SkDevice(bitmap), + data_(data) { + SetPlatformDevice(this, this); +} + +BitmapPlatformDevice::~BitmapPlatformDevice() { +} + +SkDevice* BitmapPlatformDevice::onCreateCompatibleDevice( + SkBitmap::Config config, int width, int height, bool isOpaque, + Usage /*usage*/) { + SkASSERT(config == SkBitmap::kARGB_8888_Config); + return BitmapPlatformDevice::Create(width, height, isOpaque); +} + +cairo_t* BitmapPlatformDevice::BeginPlatformPaint() { + data_->LoadConfig(); + cairo_t* cairo = data_->bitmap_context(); + cairo_surface_t* surface = cairo_get_target(cairo); + // Tell cairo to flush anything it has pending. + cairo_surface_flush(surface); + // Tell Cairo that we (probably) modified (actually, will modify) its pixel + // buffer directly. + cairo_surface_mark_dirty(surface); + return cairo; +} + +void BitmapPlatformDevice::DrawToNativeContext( + PlatformSurface surface, int x, int y, const PlatformRect* src_rect) { + // Should never be called on Linux. + SkASSERT(false); +} + +void BitmapPlatformDevice::setMatrixClip(const SkMatrix& transform, + const SkRegion& region, + const SkClipStack&) { + data_->SetMatrixClip(transform, region); +} + +// PlatformCanvas impl + +SkCanvas* CreatePlatformCanvas(int width, int height, bool is_opaque, + uint8_t* data, OnFailureType failureType) { + skia::RefPtr<SkDevice> dev = skia::AdoptRef( + BitmapPlatformDevice::Create(width, height, is_opaque, data)); + return CreateCanvas(dev, failureType); +} + +// Port of PlatformBitmap to linux +PlatformBitmap::~PlatformBitmap() { + cairo_destroy(surface_); +} + +bool PlatformBitmap::Allocate(int width, int height, bool is_opaque) { + // The SkBitmap allocates and owns the bitmap memory; PlatformBitmap owns the + // cairo drawing context tied to the bitmap. The SkBitmap's pixelRef can + // outlive the PlatformBitmap if additional copies are made. + int stride = cairo_format_stride_for_width(CAIRO_FORMAT_ARGB32, width); + bitmap_.setConfig(SkBitmap::kARGB_8888_Config, width, height, stride); + if (!bitmap_.allocPixels()) // Using the default allocator. + return false; + bitmap_.setIsOpaque(is_opaque); + + cairo_surface_t* surf = cairo_image_surface_create_for_data( + reinterpret_cast<unsigned char*>(bitmap_.getPixels()), + CAIRO_FORMAT_ARGB32, + width, + height, + stride); + if (cairo_surface_status(surf) != CAIRO_STATUS_SUCCESS) { + cairo_surface_destroy(surf); + return false; + } + + surface_ = cairo_create(surf); + cairo_surface_destroy(surf); + return true; +} + +} // namespace skia diff --git a/chromium/skia/ext/bitmap_platform_device_linux.h b/chromium/skia/ext/bitmap_platform_device_linux.h new file mode 100644 index 00000000000..e1f9a75c850 --- /dev/null +++ b/chromium/skia/ext/bitmap_platform_device_linux.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 SKIA_EXT_BITMAP_PLATFORM_DEVICE_LINUX_H_ +#define SKIA_EXT_BITMAP_PLATFORM_DEVICE_LINUX_H_ + +#include "base/basictypes.h" +#include "base/compiler_specific.h" +#include "base/memory/ref_counted.h" +#include "skia/ext/platform_device.h" + +typedef struct _cairo_surface cairo_surface_t; + +// ----------------------------------------------------------------------------- +// Image byte ordering on Linux: +// +// Pixels are packed into 32-bit words these days. Even for 24-bit images, +// often 8-bits will be left unused for alignment reasons. Thus, when you see +// ARGB as the byte order you have to wonder if that's in memory order or +// little-endian order. Here I'll write A.R.G.B to specifiy the memory order. +// +// GdkPixbuf's provide a nice backing store and defaults to R.G.B.A order. +// They'll do the needed byte swapping to match the X server when drawn. +// +// Skia can be controled in skia/include/corecg/SkUserConfig.h (see bits about +// SK_R32_SHIFT). For Linux we define it to be ARGB in registers. For little +// endian machines that means B.G.R.A in memory. +// +// The image loaders are controlled in +// webkit/port/platform/image-decoders/ImageDecoder.h (see setRGBA). These are +// also configured for ARGB in registers. +// +// Cairo's only 32-bit mode is ARGB in registers. +// +// X servers commonly have a 32-bit visual with xRGB in registers (since they +// typically don't do alpha blending of drawables at the user level. Composite +// extensions aside.) +// +// We don't use GdkPixbuf because its byte order differs from the rest. Most +// importantly, it differs from Cairo which, being a system library, is +// something that we can't easily change. +// ----------------------------------------------------------------------------- + +namespace skia { + +// ----------------------------------------------------------------------------- +// This is the Linux bitmap backing for Skia. We create a Cairo image surface +// to store the backing buffer. This buffer is BGRA in memory (on little-endian +// machines). +// +// For now we are also using Cairo to paint to the Drawables so we provide an +// accessor for getting the surface. +// +// This is all quite ok for test_shell. In the future we will want to use +// shared memory between the renderer and the main process at least. In this +// case we'll probably create the buffer from a precreated region of memory. +// ----------------------------------------------------------------------------- +class BitmapPlatformDevice : public SkDevice, public PlatformDevice { + // A reference counted cairo surface + class BitmapPlatformDeviceData; + + public: + // Create a BitmapPlatformDeviceLinux from an already constructed bitmap; + // you should probably be using Create(). This may become private later if + // we ever have to share state between some native drawing UI and Skia, like + // the Windows and Mac versions of this class do. + // + // This object takes ownership of @data. + BitmapPlatformDevice(const SkBitmap& other, BitmapPlatformDeviceData* data); + virtual ~BitmapPlatformDevice(); + + // Constructs a device with size |width| * |height| with contents initialized + // to zero. |is_opaque| should be set if the caller knows the bitmap will be + // completely opaque and allows some optimizations. + static BitmapPlatformDevice* Create(int width, int height, bool is_opaque); + + // Performs the same construction as Create. + // Other ports require a separate construction routine because Create does not + // initialize the bitmap to 0. + static BitmapPlatformDevice* CreateAndClear(int width, int height, + bool is_opaque); + + // This doesn't take ownership of |data|. If |data| is NULL, the contents + // of the device are initialized to 0. + static BitmapPlatformDevice* Create(int width, int height, bool is_opaque, + uint8_t* data); + + // Overridden from SkDevice: + virtual void setMatrixClip(const SkMatrix& transform, const SkRegion& region, + const SkClipStack&) OVERRIDE; + + // Overridden from PlatformDevice: + virtual cairo_t* BeginPlatformPaint() OVERRIDE; + virtual void DrawToNativeContext(PlatformSurface surface, int x, int y, + const PlatformRect* src_rect) OVERRIDE; + + protected: + virtual SkDevice* onCreateCompatibleDevice(SkBitmap::Config, int width, + int height, bool isOpaque, + Usage usage) OVERRIDE; + + private: + static BitmapPlatformDevice* Create(int width, int height, bool is_opaque, + cairo_surface_t* surface); + + scoped_refptr<BitmapPlatformDeviceData> data_; + + DISALLOW_COPY_AND_ASSIGN(BitmapPlatformDevice); +}; + +} // namespace skia + +#endif // SKIA_EXT_BITMAP_PLATFORM_DEVICE_LINUX_H_ diff --git a/chromium/skia/ext/bitmap_platform_device_mac.cc b/chromium/skia/ext/bitmap_platform_device_mac.cc new file mode 100644 index 00000000000..b7b05e50a3c --- /dev/null +++ b/chromium/skia/ext/bitmap_platform_device_mac.cc @@ -0,0 +1,303 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "skia/ext/bitmap_platform_device_mac.h" + +#import <ApplicationServices/ApplicationServices.h> +#include <time.h> + +#include "base/mac/mac_util.h" +#include "base/memory/ref_counted.h" +#include "skia/ext/bitmap_platform_device_data.h" +#include "skia/ext/platform_canvas.h" +#include "skia/ext/skia_utils_mac.h" +#include "third_party/skia/include/core/SkMatrix.h" +#include "third_party/skia/include/core/SkRegion.h" +#include "third_party/skia/include/core/SkTypes.h" +#include "third_party/skia/include/core/SkUtils.h" + +namespace skia { + +namespace { + +static CGContextRef CGContextForData(void* data, int width, int height) { +#define HAS_ARGB_SHIFTS(a, r, g, b) \ + (SK_A32_SHIFT == (a) && SK_R32_SHIFT == (r) \ + && SK_G32_SHIFT == (g) && SK_B32_SHIFT == (b)) +#if defined(SK_CPU_LENDIAN) && HAS_ARGB_SHIFTS(24, 16, 8, 0) + // Allocate a bitmap context with 4 components per pixel (BGRA). Apple + // recommends these flags for improved CG performance. + + // CGBitmapContextCreate returns NULL if width/height are 0. However, our + // callers expect to get a canvas back (which they later resize/reallocate) + // so we pin the dimensions here. + width = SkMax32(1, width); + height = SkMax32(1, height); + CGContextRef context = + CGBitmapContextCreate(data, width, height, 8, width * 4, + base::mac::GetSystemColorSpace(), + kCGImageAlphaPremultipliedFirst | + kCGBitmapByteOrder32Host); +#else +#error We require that Skia's and CoreGraphics's recommended \ + image memory layout match. +#endif +#undef HAS_ARGB_SHIFTS + + if (!context) + return NULL; + + // Change the coordinate system to match WebCore's + CGContextTranslateCTM(context, 0, height); + CGContextScaleCTM(context, 1.0, -1.0); + + return context; +} + +} // namespace + +BitmapPlatformDevice::BitmapPlatformDeviceData::BitmapPlatformDeviceData( + CGContextRef bitmap) + : bitmap_context_(bitmap), + config_dirty_(true), // Want to load the config next time. + transform_(SkMatrix::I()) { + SkASSERT(bitmap_context_); + // Initialize the clip region to the entire bitmap. + + SkIRect rect; + rect.set(0, 0, + CGBitmapContextGetWidth(bitmap_context_), + CGBitmapContextGetHeight(bitmap_context_)); + clip_region_ = SkRegion(rect); + CGContextRetain(bitmap_context_); + // We must save the state once so that we can use the restore/save trick + // in LoadConfig(). + CGContextSaveGState(bitmap_context_); +} + +BitmapPlatformDevice::BitmapPlatformDeviceData::~BitmapPlatformDeviceData() { + if (bitmap_context_) + CGContextRelease(bitmap_context_); +} + +void BitmapPlatformDevice::BitmapPlatformDeviceData::ReleaseBitmapContext() { + SkASSERT(bitmap_context_); + CGContextRelease(bitmap_context_); + bitmap_context_ = NULL; +} + +void BitmapPlatformDevice::BitmapPlatformDeviceData::SetMatrixClip( + const SkMatrix& transform, + const SkRegion& region) { + transform_ = transform; + clip_region_ = region; + config_dirty_ = true; +} + +void BitmapPlatformDevice::BitmapPlatformDeviceData::LoadConfig() { + if (!config_dirty_ || !bitmap_context_) + return; // Nothing to do. + config_dirty_ = false; + + // We must restore and then save the state of the graphics context since the + // calls to Load the clipping region to the context are strictly cummulative, + // i.e., you can't replace a clip rect, other than with a save/restore. + // But this implies that no other changes to the state are done elsewhere. + // If we ever get to need to change this, then we must replace the clip rect + // calls in LoadClippingRegionToCGContext() with an image mask instead. + CGContextRestoreGState(bitmap_context_); + CGContextSaveGState(bitmap_context_); + LoadTransformToCGContext(bitmap_context_, transform_); + LoadClippingRegionToCGContext(bitmap_context_, clip_region_, transform_); +} + + +// We use this static factory function instead of the regular constructor so +// that we can create the pixel data before calling the constructor. This is +// required so that we can call the base class' constructor with the pixel +// data. +BitmapPlatformDevice* BitmapPlatformDevice::Create(CGContextRef context, + int width, + int height, + bool is_opaque) { + if (RasterDeviceTooBigToAllocate(width, height)) + return NULL; + + SkBitmap bitmap; + bitmap.setConfig(SkBitmap::kARGB_8888_Config, width, height); + if (bitmap.allocPixels() != true) + return NULL; + + void* data = NULL; + if (context) { + data = CGBitmapContextGetData(context); + bitmap.setPixels(data); + } else { + data = bitmap.getPixels(); + } + + bitmap.setIsOpaque(is_opaque); + + // If we were given data, then don't clobber it! +#ifndef NDEBUG + if (!context && is_opaque) { + // To aid in finding bugs, we set the background color to something + // obviously wrong so it will be noticable when it is not cleared + bitmap.eraseARGB(255, 0, 255, 128); // bright bluish green + } +#endif + + if (!context) { + context = CGContextForData(data, width, height); + if (!context) + return NULL; + } else + CGContextRetain(context); + + BitmapPlatformDevice* rv = new BitmapPlatformDevice( + skia::AdoptRef(new BitmapPlatformDeviceData(context)), bitmap); + + // The device object took ownership of the graphics context with its own + // CGContextRetain call. + CGContextRelease(context); + + return rv; +} + +BitmapPlatformDevice* BitmapPlatformDevice::CreateAndClear(int width, + int height, + bool is_opaque) { + BitmapPlatformDevice* device = Create(NULL, width, height, is_opaque); + if (!is_opaque) + device->accessBitmap(true).eraseARGB(0, 0, 0, 0); + return device; +} + +BitmapPlatformDevice* BitmapPlatformDevice::CreateWithData(uint8_t* data, + int width, + int height, + bool is_opaque) { + CGContextRef context = NULL; + if (data) + context = CGContextForData(data, width, height); + + BitmapPlatformDevice* rv = Create(context, width, height, is_opaque); + + // The device object took ownership of the graphics context with its own + // CGContextRetain call. + if (context) + CGContextRelease(context); + + return rv; +} + +// The device will own the bitmap, which corresponds to also owning the pixel +// data. Therefore, we do not transfer ownership to the SkDevice's bitmap. +BitmapPlatformDevice::BitmapPlatformDevice( + const skia::RefPtr<BitmapPlatformDeviceData>& data, const SkBitmap& bitmap) + : SkDevice(bitmap), + data_(data) { + SetPlatformDevice(this, this); +} + +BitmapPlatformDevice::~BitmapPlatformDevice() { +} + +CGContextRef BitmapPlatformDevice::GetBitmapContext() { + data_->LoadConfig(); + return data_->bitmap_context(); +} + +void BitmapPlatformDevice::setMatrixClip(const SkMatrix& transform, + const SkRegion& region, + const SkClipStack&) { + data_->SetMatrixClip(transform, region); +} + +void BitmapPlatformDevice::DrawToNativeContext(CGContextRef context, int x, + int y, const CGRect* src_rect) { + bool created_dc = false; + if (!data_->bitmap_context()) { + created_dc = true; + GetBitmapContext(); + } + + // this should not make a copy of the bits, since we're not doing + // anything to trigger copy on write + CGImageRef image = CGBitmapContextCreateImage(data_->bitmap_context()); + CGRect bounds; + bounds.origin.x = x; + bounds.origin.y = y; + if (src_rect) { + bounds.size.width = src_rect->size.width; + bounds.size.height = src_rect->size.height; + CGImageRef sub_image = CGImageCreateWithImageInRect(image, *src_rect); + CGContextDrawImage(context, bounds, sub_image); + CGImageRelease(sub_image); + } else { + bounds.size.width = width(); + bounds.size.height = height(); + CGContextDrawImage(context, bounds, image); + } + CGImageRelease(image); + + if (created_dc) + data_->ReleaseBitmapContext(); +} + +const SkBitmap& BitmapPlatformDevice::onAccessBitmap(SkBitmap* bitmap) { + // Not needed in CoreGraphics + return *bitmap; +} + +SkDevice* BitmapPlatformDevice::onCreateCompatibleDevice( + SkBitmap::Config config, int width, int height, bool isOpaque, + Usage /*usage*/) { + SkASSERT(config == SkBitmap::kARGB_8888_Config); + SkDevice* bitmap_device = BitmapPlatformDevice::CreateAndClear(width, height, + isOpaque); + return bitmap_device; +} + +// PlatformCanvas impl + +SkCanvas* CreatePlatformCanvas(CGContextRef ctx, int width, int height, + bool is_opaque, OnFailureType failureType) { + skia::RefPtr<SkDevice> dev = skia::AdoptRef( + BitmapPlatformDevice::Create(ctx, width, height, is_opaque)); + return CreateCanvas(dev, failureType); +} + +SkCanvas* CreatePlatformCanvas(int width, int height, bool is_opaque, + uint8_t* data, OnFailureType failureType) { + skia::RefPtr<SkDevice> dev = skia::AdoptRef( + BitmapPlatformDevice::CreateWithData(data, width, height, is_opaque)); + return CreateCanvas(dev, failureType); +} + +// Port of PlatformBitmap to mac + +PlatformBitmap::~PlatformBitmap() { + if (surface_) + CGContextRelease(surface_); +} + +bool PlatformBitmap::Allocate(int width, int height, bool is_opaque) { + if (RasterDeviceTooBigToAllocate(width, height)) + return false; + + bitmap_.setConfig(SkBitmap::kARGB_8888_Config, width, height, width * 4); + if (!bitmap_.allocPixels()) + return false; + + if (!is_opaque) + bitmap_.eraseColor(0); + bitmap_.setIsOpaque(is_opaque); + + surface_ = CGContextForData(bitmap_.getPixels(), bitmap_.width(), + bitmap_.height()); + return true; +} + +} // namespace skia diff --git a/chromium/skia/ext/bitmap_platform_device_mac.h b/chromium/skia/ext/bitmap_platform_device_mac.h new file mode 100644 index 00000000000..a1c5894086e --- /dev/null +++ b/chromium/skia/ext/bitmap_platform_device_mac.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 SKIA_EXT_BITMAP_PLATFORM_DEVICE_MAC_H_ +#define SKIA_EXT_BITMAP_PLATFORM_DEVICE_MAC_H_ + +#include "base/basictypes.h" +#include "base/compiler_specific.h" +#include "skia/ext/platform_device.h" +#include "skia/ext/refptr.h" + +namespace skia { + +// A device is basically a wrapper around SkBitmap that provides a surface for +// SkCanvas to draw into. Our device provides a surface CoreGraphics can also +// write to. BitmapPlatformDevice creates a bitmap using +// CGCreateBitmapContext() in a format that Skia supports and can then use this +// to draw text into, etc. This pixel data is provided to the bitmap that the +// device contains so that it can be shared. +// +// The device owns the pixel data, when the device goes away, the pixel data +// also becomes invalid. THIS IS DIFFERENT THAN NORMAL SKIA which uses +// reference counting for the pixel data. In normal Skia, you could assign +// another bitmap to this device's bitmap and everything will work properly. +// For us, that other bitmap will become invalid as soon as the device becomes +// invalid, which may lead to subtle bugs. Therefore, DO NOT ASSIGN THE +// DEVICE'S PIXEL DATA TO ANOTHER BITMAP, make sure you copy instead. +class SK_API BitmapPlatformDevice : public SkDevice, public PlatformDevice { + public: + // Creates a BitmapPlatformDevice instance. |is_opaque| should be set if the + // caller knows the bitmap will be completely opaque and allows some + // optimizations. + // |context| may be NULL. If |context| is NULL, then the bitmap backing store + // is not initialized. + static BitmapPlatformDevice* Create(CGContextRef context, + int width, int height, + bool is_opaque); + + // Creates a BitmapPlatformDevice instance. If |is_opaque| is false, + // then the bitmap is initialzed to 0. + static BitmapPlatformDevice* CreateAndClear(int width, int height, + bool is_opaque); + + // Creates a context for |data| and calls Create. + // If |data| is NULL, then the bitmap backing store is not initialized. + static BitmapPlatformDevice* CreateWithData(uint8_t* data, + int width, int height, + bool is_opaque); + + virtual ~BitmapPlatformDevice(); + + // PlatformDevice overrides + virtual CGContextRef GetBitmapContext() OVERRIDE; + virtual void DrawToNativeContext(CGContextRef context, int x, int y, + const CGRect* src_rect) OVERRIDE; + + // SkDevice overrides + virtual void setMatrixClip(const SkMatrix& transform, const SkRegion& region, + const SkClipStack&) OVERRIDE; + + protected: + // Reference counted data that can be shared between multiple devices. This + // allows copy constructors and operator= for devices to work properly. The + // bitmaps used by the base device class are already refcounted and copyable. + class BitmapPlatformDeviceData; + + BitmapPlatformDevice(const skia::RefPtr<BitmapPlatformDeviceData>& data, + const SkBitmap& bitmap); + + // Flushes the CoreGraphics context so that the pixel data can be accessed + // directly by Skia. Overridden from SkDevice, this is called when Skia + // starts accessing pixel data. + virtual const SkBitmap& onAccessBitmap(SkBitmap*) OVERRIDE; + + virtual SkDevice* onCreateCompatibleDevice(SkBitmap::Config, int width, + int height, bool isOpaque, + Usage usage) OVERRIDE; + + // Data associated with this device, guaranteed non-null. + skia::RefPtr<BitmapPlatformDeviceData> data_; + + DISALLOW_COPY_AND_ASSIGN(BitmapPlatformDevice); +}; + +} // namespace skia + +#endif // SKIA_EXT_BITMAP_PLATFORM_DEVICE_MAC_H_ diff --git a/chromium/skia/ext/bitmap_platform_device_mac_unittest.cc b/chromium/skia/ext/bitmap_platform_device_mac_unittest.cc new file mode 100644 index 00000000000..7265bc4cfb3 --- /dev/null +++ b/chromium/skia/ext/bitmap_platform_device_mac_unittest.cc @@ -0,0 +1,69 @@ +// 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 "skia/ext/bitmap_platform_device_mac.h" + +#include "base/memory/scoped_ptr.h" +#include "skia/ext/skia_utils_mac.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "third_party/skia/include/core/SkMatrix.h" +#include "third_party/skia/include/core/SkRegion.h" +#include "third_party/skia/include/core/SkClipStack.h" + +namespace skia { + +const int kWidth = 400; +const int kHeight = 300; + +class BitmapPlatformDeviceMacTest : public testing::Test { + public: + BitmapPlatformDeviceMacTest() { + bitmap_.reset(BitmapPlatformDevice::Create( + NULL, kWidth, kHeight, /*is_opaque=*/true)); + } + + scoped_ptr<BitmapPlatformDevice> bitmap_; +}; + +TEST_F(BitmapPlatformDeviceMacTest, ClipRectTransformWithTranslate) { + SkMatrix transform; + transform.setTranslate(50, 140); + + SkClipStack ignore; + SkRegion clip_region; + SkIRect rect; + rect.set(0, 0, kWidth, kHeight); + clip_region.setRect(rect); + bitmap_->setMatrixClip(transform, clip_region, ignore); + + CGContextRef context = bitmap_->GetBitmapContext(); + SkRect clip_rect = gfx::CGRectToSkRect(CGContextGetClipBoundingBox(context)); + transform.mapRect(&clip_rect); + EXPECT_EQ(0, clip_rect.fLeft); + EXPECT_EQ(0, clip_rect.fTop); + EXPECT_EQ(kWidth, clip_rect.width()); + EXPECT_EQ(kHeight, clip_rect.height()); +} + +TEST_F(BitmapPlatformDeviceMacTest, ClipRectTransformWithScale) { + SkMatrix transform; + transform.setScale(0.5, 0.5); + + SkClipStack unused; + SkRegion clip_region; + SkIRect rect; + rect.set(0, 0, kWidth, kHeight); + clip_region.setRect(rect); + bitmap_->setMatrixClip(transform, clip_region, unused); + + CGContextRef context = bitmap_->GetBitmapContext(); + SkRect clip_rect = gfx::CGRectToSkRect(CGContextGetClipBoundingBox(context)); + transform.mapRect(&clip_rect); + EXPECT_EQ(0, clip_rect.fLeft); + EXPECT_EQ(0, clip_rect.fTop); + EXPECT_EQ(kWidth, clip_rect.width()); + EXPECT_EQ(kHeight, clip_rect.height()); +} + +} // namespace skia diff --git a/chromium/skia/ext/bitmap_platform_device_win.cc b/chromium/skia/ext/bitmap_platform_device_win.cc new file mode 100644 index 00000000000..201866aa17e --- /dev/null +++ b/chromium/skia/ext/bitmap_platform_device_win.cc @@ -0,0 +1,356 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include <windows.h> +#include <psapi.h> + +#include "skia/ext/bitmap_platform_device_win.h" +#include "skia/ext/bitmap_platform_device_data.h" +#include "skia/ext/platform_canvas.h" +#include "third_party/skia/include/core/SkMatrix.h" +#include "third_party/skia/include/core/SkRefCnt.h" +#include "third_party/skia/include/core/SkRegion.h" +#include "third_party/skia/include/core/SkUtils.h" + +namespace { + +// PlatformBitmapPixelRef is an SkPixelRef that, on Windows, is backed by an +// HBITMAP. +class SK_API PlatformBitmapPixelRef : public SkPixelRef { + public: + PlatformBitmapPixelRef(HBITMAP bitmap_handle, void* pixels); + virtual ~PlatformBitmapPixelRef(); + + SK_DECLARE_UNFLATTENABLE_OBJECT(); + + protected: + virtual void* onLockPixels(SkColorTable**) SK_OVERRIDE; + virtual void onUnlockPixels() SK_OVERRIDE; + + private: + HBITMAP bitmap_handle_; + void* pixels_; +}; + +HBITMAP CreateHBitmap(int width, int height, bool is_opaque, + HANDLE shared_section, void** data) { + // CreateDIBSection appears to get unhappy if we create an empty bitmap, so + // just create a minimal bitmap + if ((width == 0) || (height == 0)) { + width = 1; + height = 1; + } + + BITMAPINFOHEADER hdr = {0}; + hdr.biSize = sizeof(BITMAPINFOHEADER); + hdr.biWidth = width; + hdr.biHeight = -height; // minus means top-down bitmap + hdr.biPlanes = 1; + hdr.biBitCount = 32; + hdr.biCompression = BI_RGB; // no compression + hdr.biSizeImage = 0; + hdr.biXPelsPerMeter = 1; + hdr.biYPelsPerMeter = 1; + hdr.biClrUsed = 0; + hdr.biClrImportant = 0; + + HBITMAP hbitmap = CreateDIBSection(NULL, reinterpret_cast<BITMAPINFO*>(&hdr), + 0, data, shared_section, 0); + return hbitmap; +} + +PlatformBitmapPixelRef::PlatformBitmapPixelRef(HBITMAP bitmap_handle, + void* pixels) + : bitmap_handle_(bitmap_handle), + pixels_(pixels) { + setPreLocked(pixels, NULL); +} + +PlatformBitmapPixelRef::~PlatformBitmapPixelRef() { + if (bitmap_handle_) + DeleteObject(bitmap_handle_); +} + +void* PlatformBitmapPixelRef::onLockPixels(SkColorTable** color_table) { + *color_table = NULL; + return pixels_; +} + +void PlatformBitmapPixelRef::onUnlockPixels() { + // Nothing to do. + return; +} + +} // namespace + +namespace skia { + +BitmapPlatformDevice::BitmapPlatformDeviceData::BitmapPlatformDeviceData( + HBITMAP hbitmap) + : bitmap_context_(hbitmap), + hdc_(NULL), + config_dirty_(true), // Want to load the config next time. + transform_(SkMatrix::I()) { + // Initialize the clip region to the entire bitmap. + BITMAP bitmap_data; + if (GetObject(bitmap_context_, sizeof(BITMAP), &bitmap_data)) { + SkIRect rect; + rect.set(0, 0, bitmap_data.bmWidth, bitmap_data.bmHeight); + clip_region_ = SkRegion(rect); + } +} + +BitmapPlatformDevice::BitmapPlatformDeviceData::~BitmapPlatformDeviceData() { + if (hdc_) + ReleaseBitmapDC(); + + // this will free the bitmap data as well as the bitmap handle + DeleteObject(bitmap_context_); +} + +HDC BitmapPlatformDevice::BitmapPlatformDeviceData::GetBitmapDC() { + if (!hdc_) { + hdc_ = CreateCompatibleDC(NULL); + InitializeDC(hdc_); + HGDIOBJ old_bitmap = SelectObject(hdc_, bitmap_context_); + // When the memory DC is created, its display surface is exactly one + // monochrome pixel wide and one monochrome pixel high. Since we select our + // own bitmap, we must delete the previous one. + DeleteObject(old_bitmap); + } + + LoadConfig(); + return hdc_; +} + +void BitmapPlatformDevice::BitmapPlatformDeviceData::ReleaseBitmapDC() { + SkASSERT(hdc_); + DeleteDC(hdc_); + hdc_ = NULL; +} + +bool BitmapPlatformDevice::BitmapPlatformDeviceData::IsBitmapDCCreated() + const { + return hdc_ != NULL; +} + + +void BitmapPlatformDevice::BitmapPlatformDeviceData::SetMatrixClip( + const SkMatrix& transform, + const SkRegion& region) { + transform_ = transform; + clip_region_ = region; + config_dirty_ = true; +} + +void BitmapPlatformDevice::BitmapPlatformDeviceData::LoadConfig() { + if (!config_dirty_ || !hdc_) + return; // Nothing to do. + config_dirty_ = false; + + // Transform. + LoadTransformToDC(hdc_, transform_); + LoadClippingRegionToDC(hdc_, clip_region_, transform_); +} + +// We use this static factory function instead of the regular constructor so +// that we can create the pixel data before calling the constructor. This is +// required so that we can call the base class' constructor with the pixel +// data. +BitmapPlatformDevice* BitmapPlatformDevice::Create( + int width, + int height, + bool is_opaque, + HANDLE shared_section) { + + void* data; + HBITMAP hbitmap = CreateHBitmap(width, height, is_opaque, shared_section, + &data); + if (!hbitmap) + return NULL; + + SkBitmap bitmap; + bitmap.setConfig(SkBitmap::kARGB_8888_Config, width, height); + bitmap.setPixels(data); + bitmap.setIsOpaque(is_opaque); + +#ifndef NDEBUG + // If we were given data, then don't clobber it! + if (!shared_section && is_opaque) + // To aid in finding bugs, we set the background color to something + // obviously wrong so it will be noticable when it is not cleared + bitmap.eraseARGB(255, 0, 255, 128); // bright bluish green +#endif + + // The device object will take ownership of the HBITMAP. The initial refcount + // of the data object will be 1, which is what the constructor expects. + return new BitmapPlatformDevice( + skia::AdoptRef(new BitmapPlatformDeviceData(hbitmap)), bitmap); +} + +// static +BitmapPlatformDevice* BitmapPlatformDevice::Create(int width, int height, + bool is_opaque) { + return Create(width, height, is_opaque, NULL); +} + +// static +BitmapPlatformDevice* BitmapPlatformDevice::CreateAndClear(int width, + int height, + bool is_opaque) { + BitmapPlatformDevice* device = BitmapPlatformDevice::Create(width, height, + is_opaque); + if (device && !is_opaque) + device->accessBitmap(true).eraseARGB(0, 0, 0, 0); + return device; +} + +// The device will own the HBITMAP, which corresponds to also owning the pixel +// data. Therefore, we do not transfer ownership to the SkDevice's bitmap. +BitmapPlatformDevice::BitmapPlatformDevice( + const skia::RefPtr<BitmapPlatformDeviceData>& data, + const SkBitmap& bitmap) + : SkDevice(bitmap), + data_(data) { + // The data object is already ref'ed for us by create(). + SkDEBUGCODE(begin_paint_count_ = 0); + SetPlatformDevice(this, this); +} + +BitmapPlatformDevice::~BitmapPlatformDevice() { + SkASSERT(begin_paint_count_ == 0); +} + +HDC BitmapPlatformDevice::BeginPlatformPaint() { + SkDEBUGCODE(begin_paint_count_++); + return data_->GetBitmapDC(); +} + +void BitmapPlatformDevice::EndPlatformPaint() { + SkASSERT(begin_paint_count_--); + PlatformDevice::EndPlatformPaint(); +} + +void BitmapPlatformDevice::setMatrixClip(const SkMatrix& transform, + const SkRegion& region, + const SkClipStack&) { + data_->SetMatrixClip(transform, region); +} + +void BitmapPlatformDevice::DrawToNativeContext(HDC dc, int x, int y, + const RECT* src_rect) { + bool created_dc = !data_->IsBitmapDCCreated(); + HDC source_dc = BeginPlatformPaint(); + + RECT temp_rect; + if (!src_rect) { + temp_rect.left = 0; + temp_rect.right = width(); + temp_rect.top = 0; + temp_rect.bottom = height(); + src_rect = &temp_rect; + } + + int copy_width = src_rect->right - src_rect->left; + int copy_height = src_rect->bottom - src_rect->top; + + // We need to reset the translation for our bitmap or (0,0) won't be in the + // upper left anymore + SkMatrix identity; + identity.reset(); + + LoadTransformToDC(source_dc, identity); + if (isOpaque()) { + BitBlt(dc, + x, + y, + copy_width, + copy_height, + source_dc, + src_rect->left, + src_rect->top, + SRCCOPY); + } else { + SkASSERT(copy_width != 0 && copy_height != 0); + BLENDFUNCTION blend_function = {AC_SRC_OVER, 0, 255, AC_SRC_ALPHA}; + GdiAlphaBlend(dc, + x, + y, + copy_width, + copy_height, + source_dc, + src_rect->left, + src_rect->top, + copy_width, + copy_height, + blend_function); + } + LoadTransformToDC(source_dc, data_->transform()); + + EndPlatformPaint(); + if (created_dc) + data_->ReleaseBitmapDC(); +} + +const SkBitmap& BitmapPlatformDevice::onAccessBitmap(SkBitmap* bitmap) { + // FIXME(brettw) OPTIMIZATION: We should only flush if we know a GDI + // operation has occurred on our DC. + if (data_->IsBitmapDCCreated()) + GdiFlush(); + return *bitmap; +} + +SkDevice* BitmapPlatformDevice::onCreateCompatibleDevice( + SkBitmap::Config config, int width, int height, bool isOpaque, Usage) { + SkASSERT(config == SkBitmap::kARGB_8888_Config); + return BitmapPlatformDevice::CreateAndClear(width, height, isOpaque); +} + +// PlatformCanvas impl + +SkCanvas* CreatePlatformCanvas(int width, + int height, + bool is_opaque, + HANDLE shared_section, + OnFailureType failureType) { + skia::RefPtr<SkDevice> dev = skia::AdoptRef( + BitmapPlatformDevice::Create(width, height, is_opaque, shared_section)); + return CreateCanvas(dev, failureType); +} + +// Port of PlatformBitmap to win + +PlatformBitmap::~PlatformBitmap() { + if (surface_) { + if (platform_extra_) + SelectObject(surface_, reinterpret_cast<HGDIOBJ>(platform_extra_)); + DeleteDC(surface_); + } +} + +bool PlatformBitmap::Allocate(int width, int height, bool is_opaque) { + void* data; + HBITMAP hbitmap = CreateHBitmap(width, height, is_opaque, 0, &data); + if (!hbitmap) + return false; + + surface_ = CreateCompatibleDC(NULL); + InitializeDC(surface_); + // When the memory DC is created, its display surface is exactly one + // monochrome pixel wide and one monochrome pixel high. Save this object + // off, we'll restore it just before deleting the memory DC. + HGDIOBJ stock_bitmap = SelectObject(surface_, hbitmap); + platform_extra_ = reinterpret_cast<intptr_t>(stock_bitmap); + + bitmap_.setConfig(SkBitmap::kARGB_8888_Config, width, height); + // PlatformBitmapPixelRef takes ownership of |hbitmap|. + bitmap_.setPixelRef( + skia::AdoptRef(new PlatformBitmapPixelRef(hbitmap, data)).get()); + bitmap_.setIsOpaque(is_opaque); + bitmap_.lockPixels(); + + return true; +} + +} // namespace skia diff --git a/chromium/skia/ext/bitmap_platform_device_win.h b/chromium/skia/ext/bitmap_platform_device_win.h new file mode 100644 index 00000000000..c896c0aaae7 --- /dev/null +++ b/chromium/skia/ext/bitmap_platform_device_win.h @@ -0,0 +1,99 @@ +// Copyright (c) 2012 The Chromium 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 SKIA_EXT_BITMAP_PLATFORM_DEVICE_WIN_H_ +#define SKIA_EXT_BITMAP_PLATFORM_DEVICE_WIN_H_ + +#include "base/basictypes.h" +#include "base/compiler_specific.h" +#include "skia/ext/platform_device.h" +#include "skia/ext/refptr.h" + +namespace skia { + +// A device is basically a wrapper around SkBitmap that provides a surface for +// SkCanvas to draw into. Our device provides a surface Windows can also write +// to. BitmapPlatformDevice creates a bitmap using CreateDIBSection() in a +// format that Skia supports and can then use this to draw ClearType into, etc. +// This pixel data is provided to the bitmap that the device contains so that it +// can be shared. +// +// The device owns the pixel data, when the device goes away, the pixel data +// also becomes invalid. THIS IS DIFFERENT THAN NORMAL SKIA which uses +// reference counting for the pixel data. In normal Skia, you could assign +// another bitmap to this device's bitmap and everything will work properly. +// For us, that other bitmap will become invalid as soon as the device becomes +// invalid, which may lead to subtle bugs. Therefore, DO NOT ASSIGN THE +// DEVICE'S PIXEL DATA TO ANOTHER BITMAP, make sure you copy instead. +class SK_API BitmapPlatformDevice : public SkDevice, public PlatformDevice { + public: + // Factory function. is_opaque should be set if the caller knows the bitmap + // will be completely opaque and allows some optimizations. + // + // The |shared_section| parameter is optional (pass NULL for default + // behavior). If |shared_section| is non-null, then it must be a handle to a + // file-mapping object returned by CreateFileMapping. See CreateDIBSection + // for details. If |shared_section| is null, the bitmap backing store is not + // initialized. + static BitmapPlatformDevice* Create(int width, int height, + bool is_opaque, HANDLE shared_section); + + // Create a BitmapPlatformDevice with no shared section. The bitmap is not + // initialized to 0. + static BitmapPlatformDevice* Create(int width, int height, bool is_opaque); + + // Creates a BitmapPlatformDevice instance respecting the parameters as above. + // If |is_opaque| is false, then the bitmap is initialzed to 0. + static BitmapPlatformDevice* CreateAndClear(int width, int height, + bool is_opaque); + + virtual ~BitmapPlatformDevice(); + + // PlatformDevice overrides + // Retrieves the bitmap DC, which is the memory DC for our bitmap data. The + // bitmap DC is lazy created. + virtual PlatformSurface BeginPlatformPaint() OVERRIDE; + virtual void EndPlatformPaint() OVERRIDE; + + virtual void DrawToNativeContext(HDC dc, int x, int y, + const RECT* src_rect) OVERRIDE; + + // Loads the given transform and clipping region into the HDC. This is + // overridden from SkDevice. + virtual void setMatrixClip(const SkMatrix& transform, const SkRegion& region, + const SkClipStack&) OVERRIDE; + + protected: + // Flushes the Windows device context so that the pixel data can be accessed + // directly by Skia. Overridden from SkDevice, this is called when Skia + // starts accessing pixel data. + virtual const SkBitmap& onAccessBitmap(SkBitmap* bitmap) OVERRIDE; + + virtual SkDevice* onCreateCompatibleDevice(SkBitmap::Config, int width, + int height, bool isOpaque, + Usage usage) OVERRIDE; + + private: + // Reference counted data that can be shared between multiple devices. This + // allows copy constructors and operator= for devices to work properly. The + // bitmaps used by the base device class are already refcounted and copyable. + class BitmapPlatformDeviceData; + + // Private constructor. + BitmapPlatformDevice(const skia::RefPtr<BitmapPlatformDeviceData>& data, + const SkBitmap& bitmap); + + // Data associated with this device, guaranteed non-null. + skia::RefPtr<BitmapPlatformDeviceData> data_; + +#ifdef SK_DEBUG + int begin_paint_count_; +#endif + + DISALLOW_COPY_AND_ASSIGN(BitmapPlatformDevice); +}; + +} // namespace skia + +#endif // SKIA_EXT_BITMAP_PLATFORM_DEVICE_WIN_H_ diff --git a/chromium/skia/ext/convolver.cc b/chromium/skia/ext/convolver.cc new file mode 100644 index 00000000000..4b40ffd2cea --- /dev/null +++ b/chromium/skia/ext/convolver.cc @@ -0,0 +1,716 @@ +// 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 <algorithm> + +#include "base/logging.h" +#include "skia/ext/convolver.h" +#include "skia/ext/convolver_SSE2.h" +#include "skia/ext/convolver_mips_dspr2.h" +#include "third_party/skia/include/core/SkSize.h" +#include "third_party/skia/include/core/SkTypes.h" + +namespace skia { + +namespace { + +// Converts the argument to an 8-bit unsigned value by clamping to the range +// 0-255. +inline unsigned char ClampTo8(int a) { + if (static_cast<unsigned>(a) < 256) + return a; // Avoid the extra check in the common case. + if (a < 0) + return 0; + return 255; +} + +// Takes the value produced by accumulating element-wise product of image with +// a kernel and brings it back into range. +// All of the filter scaling factors are in fixed point with kShiftBits bits of +// fractional part. +inline unsigned char BringBackTo8(int a, bool take_absolute) { + a >>= ConvolutionFilter1D::kShiftBits; + if (take_absolute) + a = std::abs(a); + return ClampTo8(a); +} + +// Stores a list of rows in a circular buffer. The usage is you write into it +// by calling AdvanceRow. It will keep track of which row in the buffer it +// should use next, and the total number of rows added. +class CircularRowBuffer { + public: + // The number of pixels in each row is given in |source_row_pixel_width|. + // The maximum number of rows needed in the buffer is |max_y_filter_size| + // (we only need to store enough rows for the biggest filter). + // + // We use the |first_input_row| to compute the coordinates of all of the + // following rows returned by Advance(). + CircularRowBuffer(int dest_row_pixel_width, int max_y_filter_size, + int first_input_row) + : row_byte_width_(dest_row_pixel_width * 4), + num_rows_(max_y_filter_size), + next_row_(0), + next_row_coordinate_(first_input_row) { + buffer_.resize(row_byte_width_ * max_y_filter_size); + row_addresses_.resize(num_rows_); + } + + // Moves to the next row in the buffer, returning a pointer to the beginning + // of it. + unsigned char* AdvanceRow() { + unsigned char* row = &buffer_[next_row_ * row_byte_width_]; + next_row_coordinate_++; + + // Set the pointer to the next row to use, wrapping around if necessary. + next_row_++; + if (next_row_ == num_rows_) + next_row_ = 0; + return row; + } + + // Returns a pointer to an "unrolled" array of rows. These rows will start + // at the y coordinate placed into |*first_row_index| and will continue in + // order for the maximum number of rows in this circular buffer. + // + // The |first_row_index_| may be negative. This means the circular buffer + // starts before the top of the image (it hasn't been filled yet). + unsigned char* const* GetRowAddresses(int* first_row_index) { + // Example for a 4-element circular buffer holding coords 6-9. + // Row 0 Coord 8 + // Row 1 Coord 9 + // Row 2 Coord 6 <- next_row_ = 2, next_row_coordinate_ = 10. + // Row 3 Coord 7 + // + // The "next" row is also the first (lowest) coordinate. This computation + // may yield a negative value, but that's OK, the math will work out + // since the user of this buffer will compute the offset relative + // to the first_row_index and the negative rows will never be used. + *first_row_index = next_row_coordinate_ - num_rows_; + + int cur_row = next_row_; + for (int i = 0; i < num_rows_; i++) { + row_addresses_[i] = &buffer_[cur_row * row_byte_width_]; + + // Advance to the next row, wrapping if necessary. + cur_row++; + if (cur_row == num_rows_) + cur_row = 0; + } + return &row_addresses_[0]; + } + + private: + // The buffer storing the rows. They are packed, each one row_byte_width_. + std::vector<unsigned char> buffer_; + + // Number of bytes per row in the |buffer_|. + int row_byte_width_; + + // The number of rows available in the buffer. + int num_rows_; + + // The next row index we should write into. This wraps around as the + // circular buffer is used. + int next_row_; + + // The y coordinate of the |next_row_|. This is incremented each time a + // new row is appended and does not wrap. + int next_row_coordinate_; + + // Buffer used by GetRowAddresses(). + std::vector<unsigned char*> row_addresses_; +}; + +// Convolves horizontally along a single row. The row data is given in +// |src_data| and continues for the num_values() of the filter. +template<bool has_alpha> +void ConvolveHorizontally(const unsigned char* src_data, + const ConvolutionFilter1D& filter, + unsigned char* out_row) { + // Loop over each pixel on this row in the output image. + int num_values = filter.num_values(); + for (int out_x = 0; out_x < num_values; out_x++) { + // Get the filter that determines the current output pixel. + int filter_offset, filter_length; + const ConvolutionFilter1D::Fixed* filter_values = + filter.FilterForValue(out_x, &filter_offset, &filter_length); + + // Compute the first pixel in this row that the filter affects. It will + // touch |filter_length| pixels (4 bytes each) after this. + const unsigned char* row_to_filter = &src_data[filter_offset * 4]; + + // Apply the filter to the row to get the destination pixel in |accum|. + int accum[4] = {0}; + for (int filter_x = 0; filter_x < filter_length; filter_x++) { + ConvolutionFilter1D::Fixed cur_filter = filter_values[filter_x]; + accum[0] += cur_filter * row_to_filter[filter_x * 4 + 0]; + accum[1] += cur_filter * row_to_filter[filter_x * 4 + 1]; + accum[2] += cur_filter * row_to_filter[filter_x * 4 + 2]; + if (has_alpha) + accum[3] += cur_filter * row_to_filter[filter_x * 4 + 3]; + } + + // Bring this value back in range. All of the filter scaling factors + // are in fixed point with kShiftBits bits of fractional part. + accum[0] >>= ConvolutionFilter1D::kShiftBits; + accum[1] >>= ConvolutionFilter1D::kShiftBits; + accum[2] >>= ConvolutionFilter1D::kShiftBits; + if (has_alpha) + accum[3] >>= ConvolutionFilter1D::kShiftBits; + + // Store the new pixel. + out_row[out_x * 4 + 0] = ClampTo8(accum[0]); + out_row[out_x * 4 + 1] = ClampTo8(accum[1]); + out_row[out_x * 4 + 2] = ClampTo8(accum[2]); + if (has_alpha) + out_row[out_x * 4 + 3] = ClampTo8(accum[3]); + } +} + +// Does vertical convolution to produce one output row. The filter values and +// length are given in the first two parameters. These are applied to each +// of the rows pointed to in the |source_data_rows| array, with each row +// being |pixel_width| wide. +// +// The output must have room for |pixel_width * 4| bytes. +template<bool has_alpha> +void ConvolveVertically(const ConvolutionFilter1D::Fixed* filter_values, + int filter_length, + unsigned char* const* source_data_rows, + int pixel_width, + unsigned char* out_row) { + // We go through each column in the output and do a vertical convolution, + // generating one output pixel each time. + for (int out_x = 0; out_x < pixel_width; out_x++) { + // Compute the number of bytes over in each row that the current column + // we're convolving starts at. The pixel will cover the next 4 bytes. + int byte_offset = out_x * 4; + + // Apply the filter to one column of pixels. + int accum[4] = {0}; + for (int filter_y = 0; filter_y < filter_length; filter_y++) { + ConvolutionFilter1D::Fixed cur_filter = filter_values[filter_y]; + accum[0] += cur_filter * source_data_rows[filter_y][byte_offset + 0]; + accum[1] += cur_filter * source_data_rows[filter_y][byte_offset + 1]; + accum[2] += cur_filter * source_data_rows[filter_y][byte_offset + 2]; + if (has_alpha) + accum[3] += cur_filter * source_data_rows[filter_y][byte_offset + 3]; + } + + // Bring this value back in range. All of the filter scaling factors + // are in fixed point with kShiftBits bits of precision. + accum[0] >>= ConvolutionFilter1D::kShiftBits; + accum[1] >>= ConvolutionFilter1D::kShiftBits; + accum[2] >>= ConvolutionFilter1D::kShiftBits; + if (has_alpha) + accum[3] >>= ConvolutionFilter1D::kShiftBits; + + // Store the new pixel. + out_row[byte_offset + 0] = ClampTo8(accum[0]); + out_row[byte_offset + 1] = ClampTo8(accum[1]); + out_row[byte_offset + 2] = ClampTo8(accum[2]); + if (has_alpha) { + unsigned char alpha = ClampTo8(accum[3]); + + // Make sure the alpha channel doesn't come out smaller than any of the + // color channels. We use premultipled alpha channels, so this should + // never happen, but rounding errors will cause this from time to time. + // These "impossible" colors will cause overflows (and hence random pixel + // values) when the resulting bitmap is drawn to the screen. + // + // We only need to do this when generating the final output row (here). + int max_color_channel = std::max(out_row[byte_offset + 0], + std::max(out_row[byte_offset + 1], out_row[byte_offset + 2])); + if (alpha < max_color_channel) + out_row[byte_offset + 3] = max_color_channel; + else + out_row[byte_offset + 3] = alpha; + } else { + // No alpha channel, the image is opaque. + out_row[byte_offset + 3] = 0xff; + } + } +} + +void ConvolveVertically(const ConvolutionFilter1D::Fixed* filter_values, + int filter_length, + unsigned char* const* source_data_rows, + int pixel_width, + unsigned char* out_row, + bool source_has_alpha) { + if (source_has_alpha) { + ConvolveVertically<true>(filter_values, filter_length, + source_data_rows, + pixel_width, + out_row); + } else { + ConvolveVertically<false>(filter_values, filter_length, + source_data_rows, + pixel_width, + out_row); + } +} + +} // namespace + +// ConvolutionFilter1D --------------------------------------------------------- + +ConvolutionFilter1D::ConvolutionFilter1D() + : max_filter_(0) { +} + +ConvolutionFilter1D::~ConvolutionFilter1D() { +} + +void ConvolutionFilter1D::AddFilter(int filter_offset, + const float* filter_values, + int filter_length) { + SkASSERT(filter_length > 0); + + std::vector<Fixed> fixed_values; + fixed_values.reserve(filter_length); + + for (int i = 0; i < filter_length; ++i) + fixed_values.push_back(FloatToFixed(filter_values[i])); + + AddFilter(filter_offset, &fixed_values[0], filter_length); +} + +void ConvolutionFilter1D::AddFilter(int filter_offset, + const Fixed* filter_values, + int filter_length) { + // It is common for leading/trailing filter values to be zeros. In such + // cases it is beneficial to only store the central factors. + // For a scaling to 1/4th in each dimension using a Lanczos-2 filter on + // a 1080p image this optimization gives a ~10% speed improvement. + int filter_size = filter_length; + int first_non_zero = 0; + while (first_non_zero < filter_length && filter_values[first_non_zero] == 0) + first_non_zero++; + + if (first_non_zero < filter_length) { + // Here we have at least one non-zero factor. + int last_non_zero = filter_length - 1; + while (last_non_zero >= 0 && filter_values[last_non_zero] == 0) + last_non_zero--; + + filter_offset += first_non_zero; + filter_length = last_non_zero + 1 - first_non_zero; + SkASSERT(filter_length > 0); + + for (int i = first_non_zero; i <= last_non_zero; i++) + filter_values_.push_back(filter_values[i]); + } else { + // Here all the factors were zeroes. + filter_length = 0; + } + + FilterInstance instance; + + // We pushed filter_length elements onto filter_values_ + instance.data_location = (static_cast<int>(filter_values_.size()) - + filter_length); + instance.offset = filter_offset; + instance.trimmed_length = filter_length; + instance.length = filter_size; + filters_.push_back(instance); + + max_filter_ = std::max(max_filter_, filter_length); +} + +const ConvolutionFilter1D::Fixed* ConvolutionFilter1D::GetSingleFilter( + int* specified_filter_length, + int* filter_offset, + int* filter_length) const { + const FilterInstance& filter = filters_[0]; + *filter_offset = filter.offset; + *filter_length = filter.trimmed_length; + *specified_filter_length = filter.length; + if (filter.trimmed_length == 0) + return NULL; + + return &filter_values_[filter.data_location]; +} + +typedef void (*ConvolveVertically_pointer)( + const ConvolutionFilter1D::Fixed* filter_values, + int filter_length, + unsigned char* const* source_data_rows, + int pixel_width, + unsigned char* out_row, + bool has_alpha); +typedef void (*Convolve4RowsHorizontally_pointer)( + const unsigned char* src_data[4], + const ConvolutionFilter1D& filter, + unsigned char* out_row[4]); +typedef void (*ConvolveHorizontally_pointer)( + const unsigned char* src_data, + const ConvolutionFilter1D& filter, + unsigned char* out_row, + bool has_alpha); + +struct ConvolveProcs { + // This is how many extra pixels may be read by the + // conolve*horizontally functions. + int extra_horizontal_reads; + ConvolveVertically_pointer convolve_vertically; + Convolve4RowsHorizontally_pointer convolve_4rows_horizontally; + ConvolveHorizontally_pointer convolve_horizontally; +}; + +void SetupSIMD(ConvolveProcs *procs) { +#ifdef SIMD_SSE2 + base::CPU cpu; + if (cpu.has_sse2()) { + procs->extra_horizontal_reads = 3; + procs->convolve_vertically = &ConvolveVertically_SSE2; + procs->convolve_4rows_horizontally = &Convolve4RowsHorizontally_SSE2; + procs->convolve_horizontally = &ConvolveHorizontally_SSE2; + } +#elif defined SIMD_MIPS_DSPR2 + procs->extra_horizontal_reads = 3; + procs->convolve_vertically = &ConvolveVertically_mips_dspr2; + procs->convolve_horizontally = &ConvolveHorizontally_mips_dspr2; +#endif +} + +void BGRAConvolve2D(const unsigned char* source_data, + int source_byte_row_stride, + bool source_has_alpha, + const ConvolutionFilter1D& filter_x, + const ConvolutionFilter1D& filter_y, + int output_byte_row_stride, + unsigned char* output, + bool use_simd_if_possible) { + ConvolveProcs simd; + simd.extra_horizontal_reads = 0; + simd.convolve_vertically = NULL; + simd.convolve_4rows_horizontally = NULL; + simd.convolve_horizontally = NULL; + if (use_simd_if_possible) { + SetupSIMD(&simd); + } + + int max_y_filter_size = filter_y.max_filter(); + + // The next row in the input that we will generate a horizontally + // convolved row for. If the filter doesn't start at the beginning of the + // image (this is the case when we are only resizing a subset), then we + // don't want to generate any output rows before that. Compute the starting + // row for convolution as the first pixel for the first vertical filter. + int filter_offset, filter_length; + const ConvolutionFilter1D::Fixed* filter_values = + filter_y.FilterForValue(0, &filter_offset, &filter_length); + int next_x_row = filter_offset; + + // We loop over each row in the input doing a horizontal convolution. This + // will result in a horizontally convolved image. We write the results into + // a circular buffer of convolved rows and do vertical convolution as rows + // are available. This prevents us from having to store the entire + // intermediate image and helps cache coherency. + // We will need four extra rows to allow horizontal convolution could be done + // simultaneously. We also padding each row in row buffer to be aligned-up to + // 16 bytes. + // TODO(jiesun): We do not use aligned load from row buffer in vertical + // convolution pass yet. Somehow Windows does not like it. + int row_buffer_width = (filter_x.num_values() + 15) & ~0xF; + int row_buffer_height = max_y_filter_size + + (simd.convolve_4rows_horizontally ? 4 : 0); + CircularRowBuffer row_buffer(row_buffer_width, + row_buffer_height, + filter_offset); + + // Loop over every possible output row, processing just enough horizontal + // convolutions to run each subsequent vertical convolution. + SkASSERT(output_byte_row_stride >= filter_x.num_values() * 4); + int num_output_rows = filter_y.num_values(); + + // We need to check which is the last line to convolve before we advance 4 + // lines in one iteration. + int last_filter_offset, last_filter_length; + + // SSE2 can access up to 3 extra pixels past the end of the + // buffer. At the bottom of the image, we have to be careful + // not to access data past the end of the buffer. Normally + // we fall back to the C++ implementation for the last row. + // If the last row is less than 3 pixels wide, we may have to fall + // back to the C++ version for more rows. Compute how many + // rows we need to avoid the SSE implementation for here. + filter_x.FilterForValue(filter_x.num_values() - 1, &last_filter_offset, + &last_filter_length); + int avoid_simd_rows = 1 + simd.extra_horizontal_reads / + (last_filter_offset + last_filter_length); + + filter_y.FilterForValue(num_output_rows - 1, &last_filter_offset, + &last_filter_length); + + for (int out_y = 0; out_y < num_output_rows; out_y++) { + filter_values = filter_y.FilterForValue(out_y, + &filter_offset, &filter_length); + + // Generate output rows until we have enough to run the current filter. + while (next_x_row < filter_offset + filter_length) { + if (simd.convolve_4rows_horizontally && + next_x_row + 3 < last_filter_offset + last_filter_length - + avoid_simd_rows) { + const unsigned char* src[4]; + unsigned char* out_row[4]; + for (int i = 0; i < 4; ++i) { + src[i] = &source_data[(next_x_row + i) * source_byte_row_stride]; + out_row[i] = row_buffer.AdvanceRow(); + } + simd.convolve_4rows_horizontally(src, filter_x, out_row); + next_x_row += 4; + } else { + // Check if we need to avoid SSE2 for this row. + if (simd.convolve_horizontally && + next_x_row < last_filter_offset + last_filter_length - + avoid_simd_rows) { + simd.convolve_horizontally( + &source_data[next_x_row * source_byte_row_stride], + filter_x, row_buffer.AdvanceRow(), source_has_alpha); + } else { + if (source_has_alpha) { + ConvolveHorizontally<true>( + &source_data[next_x_row * source_byte_row_stride], + filter_x, row_buffer.AdvanceRow()); + } else { + ConvolveHorizontally<false>( + &source_data[next_x_row * source_byte_row_stride], + filter_x, row_buffer.AdvanceRow()); + } + } + next_x_row++; + } + } + + // Compute where in the output image this row of final data will go. + unsigned char* cur_output_row = &output[out_y * output_byte_row_stride]; + + // Get the list of rows that the circular buffer has, in order. + int first_row_in_circular_buffer; + unsigned char* const* rows_to_convolve = + row_buffer.GetRowAddresses(&first_row_in_circular_buffer); + + // Now compute the start of the subset of those rows that the filter + // needs. + unsigned char* const* first_row_for_filter = + &rows_to_convolve[filter_offset - first_row_in_circular_buffer]; + + if (simd.convolve_vertically) { + simd.convolve_vertically(filter_values, filter_length, + first_row_for_filter, + filter_x.num_values(), cur_output_row, + source_has_alpha); + } else { + ConvolveVertically(filter_values, filter_length, + first_row_for_filter, + filter_x.num_values(), cur_output_row, + source_has_alpha); + } + } +} + +void SingleChannelConvolveX1D(const unsigned char* source_data, + int source_byte_row_stride, + int input_channel_index, + int input_channel_count, + const ConvolutionFilter1D& filter, + const SkISize& image_size, + unsigned char* output, + int output_byte_row_stride, + int output_channel_index, + int output_channel_count, + bool absolute_values) { + int filter_offset, filter_length, filter_size; + // Very much unlike BGRAConvolve2D, here we expect to have the same filter + // for all pixels. + const ConvolutionFilter1D::Fixed* filter_values = + filter.GetSingleFilter(&filter_size, &filter_offset, &filter_length); + + if (filter_values == NULL || image_size.width() < filter_size) { + NOTREACHED(); + return; + } + + int centrepoint = filter_length / 2; + if (filter_size - filter_offset != 2 * filter_offset) { + // This means the original filter was not symmetrical AND + // got clipped from one side more than from the other. + centrepoint = filter_size / 2 - filter_offset; + } + + const unsigned char* source_data_row = source_data; + unsigned char* output_row = output; + + for (int r = 0; r < image_size.height(); ++r) { + unsigned char* target_byte = output_row + output_channel_index; + // Process the lead part, padding image to the left with the first pixel. + int c = 0; + for (; c < centrepoint; ++c, target_byte += output_channel_count) { + int accval = 0; + int i = 0; + int pixel_byte_index = input_channel_index; + for (; i < centrepoint - c; ++i) // Padding part. + accval += filter_values[i] * source_data_row[pixel_byte_index]; + + for (; i < filter_length; ++i, pixel_byte_index += input_channel_count) + accval += filter_values[i] * source_data_row[pixel_byte_index]; + + *target_byte = BringBackTo8(accval, absolute_values); + } + + // Now for the main event. + for (; c < image_size.width() - centrepoint; + ++c, target_byte += output_channel_count) { + int accval = 0; + int pixel_byte_index = (c - centrepoint) * input_channel_count + + input_channel_index; + + for (int i = 0; i < filter_length; + ++i, pixel_byte_index += input_channel_count) { + accval += filter_values[i] * source_data_row[pixel_byte_index]; + } + + *target_byte = BringBackTo8(accval, absolute_values); + } + + for (; c < image_size.width(); ++c, target_byte += output_channel_count) { + int accval = 0; + int overlap_taps = image_size.width() - c + centrepoint; + int pixel_byte_index = (c - centrepoint) * input_channel_count + + input_channel_index; + int i = 0; + for (; i < overlap_taps - 1; ++i, pixel_byte_index += input_channel_count) + accval += filter_values[i] * source_data_row[pixel_byte_index]; + + for (; i < filter_length; ++i) + accval += filter_values[i] * source_data_row[pixel_byte_index]; + + *target_byte = BringBackTo8(accval, absolute_values); + } + + source_data_row += source_byte_row_stride; + output_row += output_byte_row_stride; + } +} + +void SingleChannelConvolveY1D(const unsigned char* source_data, + int source_byte_row_stride, + int input_channel_index, + int input_channel_count, + const ConvolutionFilter1D& filter, + const SkISize& image_size, + unsigned char* output, + int output_byte_row_stride, + int output_channel_index, + int output_channel_count, + bool absolute_values) { + int filter_offset, filter_length, filter_size; + // Very much unlike BGRAConvolve2D, here we expect to have the same filter + // for all pixels. + const ConvolutionFilter1D::Fixed* filter_values = + filter.GetSingleFilter(&filter_size, &filter_offset, &filter_length); + + if (filter_values == NULL || image_size.height() < filter_size) { + NOTREACHED(); + return; + } + + int centrepoint = filter_length / 2; + if (filter_size - filter_offset != 2 * filter_offset) { + // This means the original filter was not symmetrical AND + // got clipped from one side more than from the other. + centrepoint = filter_size / 2 - filter_offset; + } + + for (int c = 0; c < image_size.width(); ++c) { + unsigned char* target_byte = output + c * output_channel_count + + output_channel_index; + int r = 0; + + for (; r < centrepoint; ++r, target_byte += output_byte_row_stride) { + int accval = 0; + int i = 0; + int pixel_byte_index = c * input_channel_count + input_channel_index; + + for (; i < centrepoint - r; ++i) // Padding part. + accval += filter_values[i] * source_data[pixel_byte_index]; + + for (; i < filter_length; ++i, pixel_byte_index += source_byte_row_stride) + accval += filter_values[i] * source_data[pixel_byte_index]; + + *target_byte = BringBackTo8(accval, absolute_values); + } + + for (; r < image_size.height() - centrepoint; + ++r, target_byte += output_byte_row_stride) { + int accval = 0; + int pixel_byte_index = (r - centrepoint) * source_byte_row_stride + + c * input_channel_count + input_channel_index; + for (int i = 0; i < filter_length; + ++i, pixel_byte_index += source_byte_row_stride) { + accval += filter_values[i] * source_data[pixel_byte_index]; + } + + *target_byte = BringBackTo8(accval, absolute_values); + } + + for (; r < image_size.height(); + ++r, target_byte += output_byte_row_stride) { + int accval = 0; + int overlap_taps = image_size.height() - r + centrepoint; + int pixel_byte_index = (r - centrepoint) * source_byte_row_stride + + c * input_channel_count + input_channel_index; + int i = 0; + for (; i < overlap_taps - 1; + ++i, pixel_byte_index += source_byte_row_stride) { + accval += filter_values[i] * source_data[pixel_byte_index]; + } + + for (; i < filter_length; ++i) + accval += filter_values[i] * source_data[pixel_byte_index]; + + *target_byte = BringBackTo8(accval, absolute_values); + } + } +} + +void SetUpGaussianConvolutionKernel(ConvolutionFilter1D* filter, + float kernel_sigma, + bool derivative) { + DCHECK(filter != NULL); + DCHECK_GT(kernel_sigma, 0.0); + const int tail_length = static_cast<int>(4.0f * kernel_sigma + 0.5f); + const int kernel_size = tail_length * 2 + 1; + const float sigmasq = kernel_sigma * kernel_sigma; + std::vector<float> kernel_weights(kernel_size, 0.0); + float kernel_sum = 1.0f; + + kernel_weights[tail_length] = 1.0f; + + for (int ii = 1; ii <= tail_length; ++ii) { + float v = std::exp(-0.5f * ii * ii / sigmasq); + kernel_weights[tail_length + ii] = v; + kernel_weights[tail_length - ii] = v; + kernel_sum += 2.0f * v; + } + + for (int i = 0; i < kernel_size; ++i) + kernel_weights[i] /= kernel_sum; + + if (derivative) { + kernel_weights[tail_length] = 0.0; + for (int ii = 1; ii <= tail_length; ++ii) { + float v = sigmasq * kernel_weights[tail_length + ii] / ii; + kernel_weights[tail_length + ii] = v; + kernel_weights[tail_length - ii] = -v; + } + } + + filter->AddFilter(0, &kernel_weights[0], kernel_weights.size()); +} + +} // namespace skia diff --git a/chromium/skia/ext/convolver.h b/chromium/skia/ext/convolver.h new file mode 100644 index 00000000000..dd99a7272f9 --- /dev/null +++ b/chromium/skia/ext/convolver.h @@ -0,0 +1,235 @@ +// Copyright (c) 2012 The Chromium 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 SKIA_EXT_CONVOLVER_H_ +#define SKIA_EXT_CONVOLVER_H_ + +#include <cmath> +#include <vector> + +#include "base/basictypes.h" +#include "base/cpu.h" +#include "third_party/skia/include/core/SkSize.h" +#include "third_party/skia/include/core/SkTypes.h" + +// We can build SSE2 optimized versions for all x86 CPUs +// except when building for the IOS emulator. +#if defined(ARCH_CPU_X86_FAMILY) && !defined(OS_IOS) +#define SIMD_SSE2 1 +#define SIMD_PADDING 8 // 8 * int16 +#endif + +#if defined (ARCH_CPU_MIPS_FAMILY) && \ + defined(__mips_dsp) && (__mips_dsp_rev >= 2) +#define SIMD_MIPS_DSPR2 1 +#endif +// avoid confusion with Mac OS X's math library (Carbon) +#if defined(__APPLE__) +#undef FloatToFixed +#undef FixedToFloat +#endif + +namespace skia { + +// Represents a filter in one dimension. Each output pixel has one entry in this +// object for the filter values contributing to it. You build up the filter +// list by calling AddFilter for each output pixel (in order). +// +// We do 2-dimensional convolution by first convolving each row by one +// ConvolutionFilter1D, then convolving each column by another one. +// +// Entries are stored in fixed point, shifted left by kShiftBits. +class ConvolutionFilter1D { + public: + typedef short Fixed; + + // The number of bits that fixed point values are shifted by. + enum { kShiftBits = 14 }; + + SK_API ConvolutionFilter1D(); + SK_API ~ConvolutionFilter1D(); + + // Convert between floating point and our fixed point representation. + static Fixed FloatToFixed(float f) { + return static_cast<Fixed>(f * (1 << kShiftBits)); + } + static unsigned char FixedToChar(Fixed x) { + return static_cast<unsigned char>(x >> kShiftBits); + } + static float FixedToFloat(Fixed x) { + // The cast relies on Fixed being a short, implying that on + // the platforms we care about all (16) bits will fit into + // the mantissa of a (32-bit) float. + COMPILE_ASSERT(sizeof(Fixed) == 2, fixed_type_should_fit_in_float_mantissa); + float raw = static_cast<float>(x); + return ldexpf(raw, -kShiftBits); + } + + // Returns the maximum pixel span of a filter. + int max_filter() const { return max_filter_; } + + // Returns the number of filters in this filter. This is the dimension of the + // output image. + int num_values() const { return static_cast<int>(filters_.size()); } + + // Appends the given list of scaling values for generating a given output + // pixel. |filter_offset| is the distance from the edge of the image to where + // the scaling factors start. The scaling factors apply to the source pixels + // starting from this position, and going for the next |filter_length| pixels. + // + // You will probably want to make sure your input is normalized (that is, + // all entries in |filter_values| sub to one) to prevent affecting the overall + // brighness of the image. + // + // The filter_length must be > 0. + // + // This version will automatically convert your input to fixed point. + SK_API void AddFilter(int filter_offset, + const float* filter_values, + int filter_length); + + // Same as the above version, but the input is already fixed point. + void AddFilter(int filter_offset, + const Fixed* filter_values, + int filter_length); + + // Retrieves a filter for the given |value_offset|, a position in the output + // image in the direction we're convolving. The offset and length of the + // filter values are put into the corresponding out arguments (see AddFilter + // above for what these mean), and a pointer to the first scaling factor is + // returned. There will be |filter_length| values in this array. + inline const Fixed* FilterForValue(int value_offset, + int* filter_offset, + int* filter_length) const { + const FilterInstance& filter = filters_[value_offset]; + *filter_offset = filter.offset; + *filter_length = filter.trimmed_length; + if (filter.trimmed_length == 0) { + return NULL; + } + return &filter_values_[filter.data_location]; + } + + // Retrieves the filter for the offset 0, presumed to be the one and only. + // The offset and length of the filter values are put into the corresponding + // out arguments (see AddFilter). Note that |filter_legth| and + // |specified_filter_length| may be different if leading/trailing zeros of the + // original floating point form were clipped. + // There will be |filter_length| values in the return array. + // Returns NULL if the filter is 0-length (for instance when all floating + // point values passed to AddFilter were clipped to 0). + SK_API const Fixed* GetSingleFilter(int* specified_filter_length, + int* filter_offset, + int* filter_length) const; + + inline void PaddingForSIMD() { + // Padding |padding_count| of more dummy coefficients after the coefficients + // of last filter to prevent SIMD instructions which load 8 or 16 bytes + // together to access invalid memory areas. We are not trying to align the + // coefficients right now due to the opaqueness of <vector> implementation. + // This has to be done after all |AddFilter| calls. +#ifdef SIMD_PADDING + for (int i = 0; i < SIMD_PADDING; ++i) + filter_values_.push_back(static_cast<Fixed>(0)); +#endif + } + + private: + struct FilterInstance { + // Offset within filter_values for this instance of the filter. + int data_location; + + // Distance from the left of the filter to the center. IN PIXELS + int offset; + + // Number of values in this filter instance. + int trimmed_length; + + // Filter length as specified. Note that this may be different from + // 'trimmed_length' if leading/trailing zeros of the original floating + // point form were clipped differently on each tail. + int length; + }; + + // Stores the information for each filter added to this class. + std::vector<FilterInstance> filters_; + + // We store all the filter values in this flat list, indexed by + // |FilterInstance.data_location| to avoid the mallocs required for storing + // each one separately. + std::vector<Fixed> filter_values_; + + // The maximum size of any filter we've added. + int max_filter_; +}; + +// Does a two-dimensional convolution on the given source image. +// +// It is assumed the source pixel offsets referenced in the input filters +// reference only valid pixels, so the source image size is not required. Each +// row of the source image starts |source_byte_row_stride| after the previous +// one (this allows you to have rows with some padding at the end). +// +// The result will be put into the given output buffer. The destination image +// size will be xfilter.num_values() * yfilter.num_values() pixels. It will be +// in rows of exactly xfilter.num_values() * 4 bytes. +// +// |source_has_alpha| is a hint that allows us to avoid doing computations on +// the alpha channel if the image is opaque. If you don't know, set this to +// true and it will work properly, but setting this to false will be a few +// percent faster if you know the image is opaque. +// +// The layout in memory is assumed to be 4-bytes per pixel in B-G-R-A order +// (this is ARGB when loaded into 32-bit words on a little-endian machine). +SK_API void BGRAConvolve2D(const unsigned char* source_data, + int source_byte_row_stride, + bool source_has_alpha, + const ConvolutionFilter1D& xfilter, + const ConvolutionFilter1D& yfilter, + int output_byte_row_stride, + unsigned char* output, + bool use_simd_if_possible); + +// Does a 1D convolution of the given source image along the X dimension on +// a single channel of the bitmap. +// +// The function uses the same convolution kernel for each pixel. That kernel +// must be added to |filter| at offset 0. This is a most straightforward +// implementation of convolution, intended chiefly for development purposes. +SK_API void SingleChannelConvolveX1D(const unsigned char* source_data, + int source_byte_row_stride, + int input_channel_index, + int input_channel_count, + const ConvolutionFilter1D& filter, + const SkISize& image_size, + unsigned char* output, + int output_byte_row_stride, + int output_channel_index, + int output_channel_count, + bool absolute_values); + +// Does a 1D convolution of the given source image along the Y dimension on +// a single channel of the bitmap. +SK_API void SingleChannelConvolveY1D(const unsigned char* source_data, + int source_byte_row_stride, + int input_channel_index, + int input_channel_count, + const ConvolutionFilter1D& filter, + const SkISize& image_size, + unsigned char* output, + int output_byte_row_stride, + int output_channel_index, + int output_channel_count, + bool absolute_values); + +// Set up the |filter| instance with a gaussian kernel. |kernel_sigma| is the +// parameter of gaussian. If |derivative| is true, the kernel will be that of +// the first derivative. Intended for use with the two routines above. +SK_API void SetUpGaussianConvolutionKernel(ConvolutionFilter1D* filter, + float kernel_sigma, + bool derivative); + +} // namespace skia + +#endif // SKIA_EXT_CONVOLVER_H_ diff --git a/chromium/skia/ext/convolver_SSE2.cc b/chromium/skia/ext/convolver_SSE2.cc new file mode 100644 index 00000000000..a77a1f45c41 --- /dev/null +++ b/chromium/skia/ext/convolver_SSE2.cc @@ -0,0 +1,457 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include <algorithm> + +#include "skia/ext/convolver.h" +#include "skia/ext/convolver_SSE2.h" +#include "third_party/skia/include/core/SkTypes.h" + +#include <emmintrin.h> // ARCH_CPU_X86_FAMILY was defined in build/config.h + +namespace skia { + +// Convolves horizontally along a single row. The row data is given in +// |src_data| and continues for the num_values() of the filter. +void ConvolveHorizontally_SSE2(const unsigned char* src_data, + const ConvolutionFilter1D& filter, + unsigned char* out_row, + bool /*has_alpha*/) { + int num_values = filter.num_values(); + + int filter_offset, filter_length; + __m128i zero = _mm_setzero_si128(); + __m128i mask[4]; + // |mask| will be used to decimate all extra filter coefficients that are + // loaded by SIMD when |filter_length| is not divisible by 4. + // mask[0] is not used in following algorithm. + mask[1] = _mm_set_epi16(0, 0, 0, 0, 0, 0, 0, -1); + mask[2] = _mm_set_epi16(0, 0, 0, 0, 0, 0, -1, -1); + mask[3] = _mm_set_epi16(0, 0, 0, 0, 0, -1, -1, -1); + + // Output one pixel each iteration, calculating all channels (RGBA) together. + for (int out_x = 0; out_x < num_values; out_x++) { + const ConvolutionFilter1D::Fixed* filter_values = + filter.FilterForValue(out_x, &filter_offset, &filter_length); + + __m128i accum = _mm_setzero_si128(); + + // Compute the first pixel in this row that the filter affects. It will + // touch |filter_length| pixels (4 bytes each) after this. + const __m128i* row_to_filter = + reinterpret_cast<const __m128i*>(&src_data[filter_offset << 2]); + + // We will load and accumulate with four coefficients per iteration. + for (int filter_x = 0; filter_x < filter_length >> 2; filter_x++) { + + // Load 4 coefficients => duplicate 1st and 2nd of them for all channels. + __m128i coeff, coeff16; + // [16] xx xx xx xx c3 c2 c1 c0 + coeff = _mm_loadl_epi64(reinterpret_cast<const __m128i*>(filter_values)); + // [16] xx xx xx xx c1 c1 c0 c0 + coeff16 = _mm_shufflelo_epi16(coeff, _MM_SHUFFLE(1, 1, 0, 0)); + // [16] c1 c1 c1 c1 c0 c0 c0 c0 + coeff16 = _mm_unpacklo_epi16(coeff16, coeff16); + + // Load four pixels => unpack the first two pixels to 16 bits => + // multiply with coefficients => accumulate the convolution result. + // [8] a3 b3 g3 r3 a2 b2 g2 r2 a1 b1 g1 r1 a0 b0 g0 r0 + __m128i src8 = _mm_loadu_si128(row_to_filter); + // [16] a1 b1 g1 r1 a0 b0 g0 r0 + __m128i src16 = _mm_unpacklo_epi8(src8, zero); + __m128i mul_hi = _mm_mulhi_epi16(src16, coeff16); + __m128i mul_lo = _mm_mullo_epi16(src16, coeff16); + // [32] a0*c0 b0*c0 g0*c0 r0*c0 + __m128i t = _mm_unpacklo_epi16(mul_lo, mul_hi); + accum = _mm_add_epi32(accum, t); + // [32] a1*c1 b1*c1 g1*c1 r1*c1 + t = _mm_unpackhi_epi16(mul_lo, mul_hi); + accum = _mm_add_epi32(accum, t); + + // Duplicate 3rd and 4th coefficients for all channels => + // unpack the 3rd and 4th pixels to 16 bits => multiply with coefficients + // => accumulate the convolution results. + // [16] xx xx xx xx c3 c3 c2 c2 + coeff16 = _mm_shufflelo_epi16(coeff, _MM_SHUFFLE(3, 3, 2, 2)); + // [16] c3 c3 c3 c3 c2 c2 c2 c2 + coeff16 = _mm_unpacklo_epi16(coeff16, coeff16); + // [16] a3 g3 b3 r3 a2 g2 b2 r2 + src16 = _mm_unpackhi_epi8(src8, zero); + mul_hi = _mm_mulhi_epi16(src16, coeff16); + mul_lo = _mm_mullo_epi16(src16, coeff16); + // [32] a2*c2 b2*c2 g2*c2 r2*c2 + t = _mm_unpacklo_epi16(mul_lo, mul_hi); + accum = _mm_add_epi32(accum, t); + // [32] a3*c3 b3*c3 g3*c3 r3*c3 + t = _mm_unpackhi_epi16(mul_lo, mul_hi); + accum = _mm_add_epi32(accum, t); + + // Advance the pixel and coefficients pointers. + row_to_filter += 1; + filter_values += 4; + } + + // When |filter_length| is not divisible by 4, we need to decimate some of + // the filter coefficient that was loaded incorrectly to zero; Other than + // that the algorithm is same with above, exceot that the 4th pixel will be + // always absent. + int r = filter_length&3; + if (r) { + // Note: filter_values must be padded to align_up(filter_offset, 8). + __m128i coeff, coeff16; + coeff = _mm_loadl_epi64(reinterpret_cast<const __m128i*>(filter_values)); + // Mask out extra filter taps. + coeff = _mm_and_si128(coeff, mask[r]); + coeff16 = _mm_shufflelo_epi16(coeff, _MM_SHUFFLE(1, 1, 0, 0)); + coeff16 = _mm_unpacklo_epi16(coeff16, coeff16); + + // Note: line buffer must be padded to align_up(filter_offset, 16). + // We resolve this by use C-version for the last horizontal line. + __m128i src8 = _mm_loadu_si128(row_to_filter); + __m128i src16 = _mm_unpacklo_epi8(src8, zero); + __m128i mul_hi = _mm_mulhi_epi16(src16, coeff16); + __m128i mul_lo = _mm_mullo_epi16(src16, coeff16); + __m128i t = _mm_unpacklo_epi16(mul_lo, mul_hi); + accum = _mm_add_epi32(accum, t); + t = _mm_unpackhi_epi16(mul_lo, mul_hi); + accum = _mm_add_epi32(accum, t); + + src16 = _mm_unpackhi_epi8(src8, zero); + coeff16 = _mm_shufflelo_epi16(coeff, _MM_SHUFFLE(3, 3, 2, 2)); + coeff16 = _mm_unpacklo_epi16(coeff16, coeff16); + mul_hi = _mm_mulhi_epi16(src16, coeff16); + mul_lo = _mm_mullo_epi16(src16, coeff16); + t = _mm_unpacklo_epi16(mul_lo, mul_hi); + accum = _mm_add_epi32(accum, t); + } + + // Shift right for fixed point implementation. + accum = _mm_srai_epi32(accum, ConvolutionFilter1D::kShiftBits); + + // Packing 32 bits |accum| to 16 bits per channel (signed saturation). + accum = _mm_packs_epi32(accum, zero); + // Packing 16 bits |accum| to 8 bits per channel (unsigned saturation). + accum = _mm_packus_epi16(accum, zero); + + // Store the pixel value of 32 bits. + *(reinterpret_cast<int*>(out_row)) = _mm_cvtsi128_si32(accum); + out_row += 4; + } +} + +// Convolves horizontally along four rows. The row data is given in +// |src_data| and continues for the num_values() of the filter. +// The algorithm is almost same as |ConvolveHorizontally_SSE2|. Please +// refer to that function for detailed comments. +void Convolve4RowsHorizontally_SSE2(const unsigned char* src_data[4], + const ConvolutionFilter1D& filter, + unsigned char* out_row[4]) { + int num_values = filter.num_values(); + + int filter_offset, filter_length; + __m128i zero = _mm_setzero_si128(); + __m128i mask[4]; + // |mask| will be used to decimate all extra filter coefficients that are + // loaded by SIMD when |filter_length| is not divisible by 4. + // mask[0] is not used in following algorithm. + mask[1] = _mm_set_epi16(0, 0, 0, 0, 0, 0, 0, -1); + mask[2] = _mm_set_epi16(0, 0, 0, 0, 0, 0, -1, -1); + mask[3] = _mm_set_epi16(0, 0, 0, 0, 0, -1, -1, -1); + + // Output one pixel each iteration, calculating all channels (RGBA) together. + for (int out_x = 0; out_x < num_values; out_x++) { + const ConvolutionFilter1D::Fixed* filter_values = + filter.FilterForValue(out_x, &filter_offset, &filter_length); + + // four pixels in a column per iteration. + __m128i accum0 = _mm_setzero_si128(); + __m128i accum1 = _mm_setzero_si128(); + __m128i accum2 = _mm_setzero_si128(); + __m128i accum3 = _mm_setzero_si128(); + int start = (filter_offset<<2); + // We will load and accumulate with four coefficients per iteration. + for (int filter_x = 0; filter_x < (filter_length >> 2); filter_x++) { + __m128i coeff, coeff16lo, coeff16hi; + // [16] xx xx xx xx c3 c2 c1 c0 + coeff = _mm_loadl_epi64(reinterpret_cast<const __m128i*>(filter_values)); + // [16] xx xx xx xx c1 c1 c0 c0 + coeff16lo = _mm_shufflelo_epi16(coeff, _MM_SHUFFLE(1, 1, 0, 0)); + // [16] c1 c1 c1 c1 c0 c0 c0 c0 + coeff16lo = _mm_unpacklo_epi16(coeff16lo, coeff16lo); + // [16] xx xx xx xx c3 c3 c2 c2 + coeff16hi = _mm_shufflelo_epi16(coeff, _MM_SHUFFLE(3, 3, 2, 2)); + // [16] c3 c3 c3 c3 c2 c2 c2 c2 + coeff16hi = _mm_unpacklo_epi16(coeff16hi, coeff16hi); + + __m128i src8, src16, mul_hi, mul_lo, t; + +#define ITERATION(src, accum) \ + src8 = _mm_loadu_si128(reinterpret_cast<const __m128i*>(src)); \ + src16 = _mm_unpacklo_epi8(src8, zero); \ + mul_hi = _mm_mulhi_epi16(src16, coeff16lo); \ + mul_lo = _mm_mullo_epi16(src16, coeff16lo); \ + t = _mm_unpacklo_epi16(mul_lo, mul_hi); \ + accum = _mm_add_epi32(accum, t); \ + t = _mm_unpackhi_epi16(mul_lo, mul_hi); \ + accum = _mm_add_epi32(accum, t); \ + src16 = _mm_unpackhi_epi8(src8, zero); \ + mul_hi = _mm_mulhi_epi16(src16, coeff16hi); \ + mul_lo = _mm_mullo_epi16(src16, coeff16hi); \ + t = _mm_unpacklo_epi16(mul_lo, mul_hi); \ + accum = _mm_add_epi32(accum, t); \ + t = _mm_unpackhi_epi16(mul_lo, mul_hi); \ + accum = _mm_add_epi32(accum, t) + + ITERATION(src_data[0] + start, accum0); + ITERATION(src_data[1] + start, accum1); + ITERATION(src_data[2] + start, accum2); + ITERATION(src_data[3] + start, accum3); + + start += 16; + filter_values += 4; + } + + int r = filter_length & 3; + if (r) { + // Note: filter_values must be padded to align_up(filter_offset, 8); + __m128i coeff; + coeff = _mm_loadl_epi64(reinterpret_cast<const __m128i*>(filter_values)); + // Mask out extra filter taps. + coeff = _mm_and_si128(coeff, mask[r]); + + __m128i coeff16lo = _mm_shufflelo_epi16(coeff, _MM_SHUFFLE(1, 1, 0, 0)); + /* c1 c1 c1 c1 c0 c0 c0 c0 */ + coeff16lo = _mm_unpacklo_epi16(coeff16lo, coeff16lo); + __m128i coeff16hi = _mm_shufflelo_epi16(coeff, _MM_SHUFFLE(3, 3, 2, 2)); + coeff16hi = _mm_unpacklo_epi16(coeff16hi, coeff16hi); + + __m128i src8, src16, mul_hi, mul_lo, t; + + ITERATION(src_data[0] + start, accum0); + ITERATION(src_data[1] + start, accum1); + ITERATION(src_data[2] + start, accum2); + ITERATION(src_data[3] + start, accum3); + } + + accum0 = _mm_srai_epi32(accum0, ConvolutionFilter1D::kShiftBits); + accum0 = _mm_packs_epi32(accum0, zero); + accum0 = _mm_packus_epi16(accum0, zero); + accum1 = _mm_srai_epi32(accum1, ConvolutionFilter1D::kShiftBits); + accum1 = _mm_packs_epi32(accum1, zero); + accum1 = _mm_packus_epi16(accum1, zero); + accum2 = _mm_srai_epi32(accum2, ConvolutionFilter1D::kShiftBits); + accum2 = _mm_packs_epi32(accum2, zero); + accum2 = _mm_packus_epi16(accum2, zero); + accum3 = _mm_srai_epi32(accum3, ConvolutionFilter1D::kShiftBits); + accum3 = _mm_packs_epi32(accum3, zero); + accum3 = _mm_packus_epi16(accum3, zero); + + *(reinterpret_cast<int*>(out_row[0])) = _mm_cvtsi128_si32(accum0); + *(reinterpret_cast<int*>(out_row[1])) = _mm_cvtsi128_si32(accum1); + *(reinterpret_cast<int*>(out_row[2])) = _mm_cvtsi128_si32(accum2); + *(reinterpret_cast<int*>(out_row[3])) = _mm_cvtsi128_si32(accum3); + + out_row[0] += 4; + out_row[1] += 4; + out_row[2] += 4; + out_row[3] += 4; + } +} + +// Does vertical convolution to produce one output row. The filter values and +// length are given in the first two parameters. These are applied to each +// of the rows pointed to in the |source_data_rows| array, with each row +// being |pixel_width| wide. +// +// The output must have room for |pixel_width * 4| bytes. +template<bool has_alpha> +void ConvolveVertically_SSE2(const ConvolutionFilter1D::Fixed* filter_values, + int filter_length, + unsigned char* const* source_data_rows, + int pixel_width, + unsigned char* out_row) { + int width = pixel_width & ~3; + + __m128i zero = _mm_setzero_si128(); + __m128i accum0, accum1, accum2, accum3, coeff16; + const __m128i* src; + // Output four pixels per iteration (16 bytes). + for (int out_x = 0; out_x < width; out_x += 4) { + + // Accumulated result for each pixel. 32 bits per RGBA channel. + accum0 = _mm_setzero_si128(); + accum1 = _mm_setzero_si128(); + accum2 = _mm_setzero_si128(); + accum3 = _mm_setzero_si128(); + + // Convolve with one filter coefficient per iteration. + for (int filter_y = 0; filter_y < filter_length; filter_y++) { + + // Duplicate the filter coefficient 8 times. + // [16] cj cj cj cj cj cj cj cj + coeff16 = _mm_set1_epi16(filter_values[filter_y]); + + // Load four pixels (16 bytes) together. + // [8] a3 b3 g3 r3 a2 b2 g2 r2 a1 b1 g1 r1 a0 b0 g0 r0 + src = reinterpret_cast<const __m128i*>( + &source_data_rows[filter_y][out_x << 2]); + __m128i src8 = _mm_loadu_si128(src); + + // Unpack 1st and 2nd pixels from 8 bits to 16 bits for each channels => + // multiply with current coefficient => accumulate the result. + // [16] a1 b1 g1 r1 a0 b0 g0 r0 + __m128i src16 = _mm_unpacklo_epi8(src8, zero); + __m128i mul_hi = _mm_mulhi_epi16(src16, coeff16); + __m128i mul_lo = _mm_mullo_epi16(src16, coeff16); + // [32] a0 b0 g0 r0 + __m128i t = _mm_unpacklo_epi16(mul_lo, mul_hi); + accum0 = _mm_add_epi32(accum0, t); + // [32] a1 b1 g1 r1 + t = _mm_unpackhi_epi16(mul_lo, mul_hi); + accum1 = _mm_add_epi32(accum1, t); + + // Unpack 3rd and 4th pixels from 8 bits to 16 bits for each channels => + // multiply with current coefficient => accumulate the result. + // [16] a3 b3 g3 r3 a2 b2 g2 r2 + src16 = _mm_unpackhi_epi8(src8, zero); + mul_hi = _mm_mulhi_epi16(src16, coeff16); + mul_lo = _mm_mullo_epi16(src16, coeff16); + // [32] a2 b2 g2 r2 + t = _mm_unpacklo_epi16(mul_lo, mul_hi); + accum2 = _mm_add_epi32(accum2, t); + // [32] a3 b3 g3 r3 + t = _mm_unpackhi_epi16(mul_lo, mul_hi); + accum3 = _mm_add_epi32(accum3, t); + } + + // Shift right for fixed point implementation. + accum0 = _mm_srai_epi32(accum0, ConvolutionFilter1D::kShiftBits); + accum1 = _mm_srai_epi32(accum1, ConvolutionFilter1D::kShiftBits); + accum2 = _mm_srai_epi32(accum2, ConvolutionFilter1D::kShiftBits); + accum3 = _mm_srai_epi32(accum3, ConvolutionFilter1D::kShiftBits); + + // Packing 32 bits |accum| to 16 bits per channel (signed saturation). + // [16] a1 b1 g1 r1 a0 b0 g0 r0 + accum0 = _mm_packs_epi32(accum0, accum1); + // [16] a3 b3 g3 r3 a2 b2 g2 r2 + accum2 = _mm_packs_epi32(accum2, accum3); + + // Packing 16 bits |accum| to 8 bits per channel (unsigned saturation). + // [8] a3 b3 g3 r3 a2 b2 g2 r2 a1 b1 g1 r1 a0 b0 g0 r0 + accum0 = _mm_packus_epi16(accum0, accum2); + + if (has_alpha) { + // Compute the max(ri, gi, bi) for each pixel. + // [8] xx a3 b3 g3 xx a2 b2 g2 xx a1 b1 g1 xx a0 b0 g0 + __m128i a = _mm_srli_epi32(accum0, 8); + // [8] xx xx xx max3 xx xx xx max2 xx xx xx max1 xx xx xx max0 + __m128i b = _mm_max_epu8(a, accum0); // Max of r and g. + // [8] xx xx a3 b3 xx xx a2 b2 xx xx a1 b1 xx xx a0 b0 + a = _mm_srli_epi32(accum0, 16); + // [8] xx xx xx max3 xx xx xx max2 xx xx xx max1 xx xx xx max0 + b = _mm_max_epu8(a, b); // Max of r and g and b. + // [8] max3 00 00 00 max2 00 00 00 max1 00 00 00 max0 00 00 00 + b = _mm_slli_epi32(b, 24); + + // Make sure the value of alpha channel is always larger than maximum + // value of color channels. + accum0 = _mm_max_epu8(b, accum0); + } else { + // Set value of alpha channels to 0xFF. + __m128i mask = _mm_set1_epi32(0xff000000); + accum0 = _mm_or_si128(accum0, mask); + } + + // Store the convolution result (16 bytes) and advance the pixel pointers. + _mm_storeu_si128(reinterpret_cast<__m128i*>(out_row), accum0); + out_row += 16; + } + + // When the width of the output is not divisible by 4, We need to save one + // pixel (4 bytes) each time. And also the fourth pixel is always absent. + if (pixel_width & 3) { + accum0 = _mm_setzero_si128(); + accum1 = _mm_setzero_si128(); + accum2 = _mm_setzero_si128(); + for (int filter_y = 0; filter_y < filter_length; ++filter_y) { + coeff16 = _mm_set1_epi16(filter_values[filter_y]); + // [8] a3 b3 g3 r3 a2 b2 g2 r2 a1 b1 g1 r1 a0 b0 g0 r0 + src = reinterpret_cast<const __m128i*>( + &source_data_rows[filter_y][width<<2]); + __m128i src8 = _mm_loadu_si128(src); + // [16] a1 b1 g1 r1 a0 b0 g0 r0 + __m128i src16 = _mm_unpacklo_epi8(src8, zero); + __m128i mul_hi = _mm_mulhi_epi16(src16, coeff16); + __m128i mul_lo = _mm_mullo_epi16(src16, coeff16); + // [32] a0 b0 g0 r0 + __m128i t = _mm_unpacklo_epi16(mul_lo, mul_hi); + accum0 = _mm_add_epi32(accum0, t); + // [32] a1 b1 g1 r1 + t = _mm_unpackhi_epi16(mul_lo, mul_hi); + accum1 = _mm_add_epi32(accum1, t); + // [16] a3 b3 g3 r3 a2 b2 g2 r2 + src16 = _mm_unpackhi_epi8(src8, zero); + mul_hi = _mm_mulhi_epi16(src16, coeff16); + mul_lo = _mm_mullo_epi16(src16, coeff16); + // [32] a2 b2 g2 r2 + t = _mm_unpacklo_epi16(mul_lo, mul_hi); + accum2 = _mm_add_epi32(accum2, t); + } + + accum0 = _mm_srai_epi32(accum0, ConvolutionFilter1D::kShiftBits); + accum1 = _mm_srai_epi32(accum1, ConvolutionFilter1D::kShiftBits); + accum2 = _mm_srai_epi32(accum2, ConvolutionFilter1D::kShiftBits); + // [16] a1 b1 g1 r1 a0 b0 g0 r0 + accum0 = _mm_packs_epi32(accum0, accum1); + // [16] a3 b3 g3 r3 a2 b2 g2 r2 + accum2 = _mm_packs_epi32(accum2, zero); + // [8] a3 b3 g3 r3 a2 b2 g2 r2 a1 b1 g1 r1 a0 b0 g0 r0 + accum0 = _mm_packus_epi16(accum0, accum2); + if (has_alpha) { + // [8] xx a3 b3 g3 xx a2 b2 g2 xx a1 b1 g1 xx a0 b0 g0 + __m128i a = _mm_srli_epi32(accum0, 8); + // [8] xx xx xx max3 xx xx xx max2 xx xx xx max1 xx xx xx max0 + __m128i b = _mm_max_epu8(a, accum0); // Max of r and g. + // [8] xx xx a3 b3 xx xx a2 b2 xx xx a1 b1 xx xx a0 b0 + a = _mm_srli_epi32(accum0, 16); + // [8] xx xx xx max3 xx xx xx max2 xx xx xx max1 xx xx xx max0 + b = _mm_max_epu8(a, b); // Max of r and g and b. + // [8] max3 00 00 00 max2 00 00 00 max1 00 00 00 max0 00 00 00 + b = _mm_slli_epi32(b, 24); + accum0 = _mm_max_epu8(b, accum0); + } else { + __m128i mask = _mm_set1_epi32(0xff000000); + accum0 = _mm_or_si128(accum0, mask); + } + + for (int out_x = width; out_x < pixel_width; out_x++) { + *(reinterpret_cast<int*>(out_row)) = _mm_cvtsi128_si32(accum0); + accum0 = _mm_srli_si128(accum0, 4); + out_row += 4; + } + } +} + +void ConvolveVertically_SSE2(const ConvolutionFilter1D::Fixed* filter_values, + int filter_length, + unsigned char* const* source_data_rows, + int pixel_width, + unsigned char* out_row, + bool has_alpha) { + if (has_alpha) { + ConvolveVertically_SSE2<true>(filter_values, + filter_length, + source_data_rows, + pixel_width, + out_row); + } else { + ConvolveVertically_SSE2<false>(filter_values, + filter_length, + source_data_rows, + pixel_width, + out_row); + } +} + +} // namespace skia diff --git a/chromium/skia/ext/convolver_SSE2.h b/chromium/skia/ext/convolver_SSE2.h new file mode 100644 index 00000000000..cf604067e92 --- /dev/null +++ b/chromium/skia/ext/convolver_SSE2.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 SKIA_EXT_CONVOLVER_SSE2_H_ +#define SKIA_EXT_CONVOLVER_SSE2_H_ + +#include "skia/ext/convolver.h" + +namespace skia { + +void ConvolveVertically_SSE2(const ConvolutionFilter1D::Fixed* filter_values, + int filter_length, + unsigned char* const* source_data_rows, + int pixel_width, + unsigned char* out_row, + bool has_alpha); +void Convolve4RowsHorizontally_SSE2(const unsigned char* src_data[4], + const ConvolutionFilter1D& filter, + unsigned char* out_row[4]); +void ConvolveHorizontally_SSE2(const unsigned char* src_data, + const ConvolutionFilter1D& filter, + unsigned char* out_row, + bool has_alpha); +} // namespace skia + +#endif // SKIA_EXT_CONVOLVER_SSE2_H_ diff --git a/chromium/skia/ext/convolver_mips_dspr2.cc b/chromium/skia/ext/convolver_mips_dspr2.cc new file mode 100644 index 00000000000..955abef7a52 --- /dev/null +++ b/chromium/skia/ext/convolver_mips_dspr2.cc @@ -0,0 +1,478 @@ +// 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 <algorithm> +#include "skia/ext/convolver.h" +#include "skia/ext/convolver_mips_dspr2.h" +#include "third_party/skia/include/core/SkTypes.h" + +namespace skia { +// Convolves horizontally along a single row. The row data is given in +// |src_data| and continues for the num_values() of the filter. +void ConvolveHorizontally_mips_dspr2(const unsigned char* src_data, + const ConvolutionFilter1D& filter, + unsigned char* out_row, + bool has_alpha) { +#if SIMD_MIPS_DSPR2 + int row_to_filter = 0; + int num_values = filter.num_values(); + if (has_alpha) { + for (int out_x = 0; out_x < num_values; out_x++) { + // Get the filter that determines the current output pixel. + int filter_offset, filter_length; + const ConvolutionFilter1D::Fixed* filter_values = + filter.FilterForValue(out_x, &filter_offset, &filter_length); + int filter_x = 0; + + __asm__ __volatile__ ( + ".set push \n" + ".set noreorder \n" + + "beqz %[filter_len], 3f \n" + " sll $t0, %[filter_offset], 2 \n" + "addu %[rtf], %[src_data], $t0 \n" + "mtlo $0, $ac0 \n" + "mtlo $0, $ac1 \n" + "mtlo $0, $ac2 \n" + "mtlo $0, $ac3 \n" + "srl $t7, %[filter_len], 2 \n" + "beqz $t7, 2f \n" + " li %[fx], 0 \n" + + "11: \n" + "addu $t4, %[filter_val], %[fx] \n" + "sll $t5, %[fx], 1 \n" + "ulw $t6, 0($t4) \n" // t6 = |cur[1]|cur[0]| + "ulw $t8, 4($t4) \n" // t8 = |cur[3]|cur[2]| + "addu $t0, %[rtf], $t5 \n" + "lw $t1, 0($t0) \n" // t1 = |a0|b0|g0|r0| + "lw $t2, 4($t0) \n" // t2 = |a1|b1|g1|r1| + "lw $t3, 8($t0) \n" // t3 = |a2|b2|g2|r2| + "lw $t4, 12($t0) \n" // t4 = |a3|b3|g3|r3| + "precrq.qb.ph $t0, $t2, $t1 \n" // t0 = |a1|g1|a0|g0| + "precr.qb.ph $t5, $t2, $t1 \n" // t5 = |b1|r1|b0|r0| + "preceu.ph.qbla $t1, $t0 \n" // t1 = |0|a1|0|a0| + "preceu.ph.qbra $t2, $t0 \n" // t2 = |0|g1|0|g0| + "preceu.ph.qbla $t0, $t5 \n" // t0 = |0|b1|0|b0| + "preceu.ph.qbra $t5, $t5 \n" // t5 = |0|r1|0|r0| + "dpa.w.ph $ac0, $t1, $t6 \n" // ac0+(cur*a1)+(cur*a0) + "dpa.w.ph $ac1, $t0, $t6 \n" // ac1+(cur*b1)+(cur*b0) + "dpa.w.ph $ac2, $t2, $t6 \n" // ac2+(cur*g1)+(cur*g0) + "dpa.w.ph $ac3, $t5, $t6 \n" // ac3+(cur*r1)+(cur*r0) + "precrq.qb.ph $t0, $t4, $t3 \n" // t0 = |a3|g3|a2|g2| + "precr.qb.ph $t5, $t4, $t3 \n" // t5 = |b3|r3|b2|r2| + "preceu.ph.qbla $t1, $t0 \n" // t1 = |0|a3|0|a2| + "preceu.ph.qbra $t2, $t0 \n" // t2 = |0|g3|0|g2| + "preceu.ph.qbla $t0, $t5 \n" // t0 = |0|b3|0|b2| + "preceu.ph.qbra $t5, $t5 \n" // t5 = |0|r3|0|r2| + "dpa.w.ph $ac0, $t1, $t8 \n" // ac0+(cur*a3)+(cur*a2) + "dpa.w.ph $ac1, $t0, $t8 \n" // ac1+(cur*b3)+(cur*b2) + "dpa.w.ph $ac2, $t2, $t8 \n" // ac2+(cur*g3)+(cur*g2) + "dpa.w.ph $ac3, $t5, $t8 \n" // ac3+(cur*r3)+(cur*r2) + "addiu $t7, $t7, -1 \n" + "bgtz $t7, 11b \n" + " addiu %[fx], %[fx], 8 \n" + + "2: \n" + "andi $t7, %[filter_len], 0x3 \n" // residual + "beqz $t7, 3f \n" + " nop \n" + + "21: \n" + "sll $t1, %[fx], 1 \n" + "addu $t2, %[filter_val], %[fx] \n" + "addu $t0, %[rtf], $t1 \n" + "lh $t6, 0($t2) \n" // t6 = filter_val[fx] + "lbu $t1, 0($t0) \n" // t1 = row[fx * 4 + 0] + "lbu $t2, 1($t0) \n" // t2 = row[fx * 4 + 1] + "lbu $t3, 2($t0) \n" // t3 = row[fx * 4 + 2] + "lbu $t4, 3($t0) \n" // t4 = row[fx * 4 + 2] + "maddu $ac3, $t6, $t1 \n" + "maddu $ac2, $t6, $t2 \n" + "maddu $ac1, $t6, $t3 \n" + "maddu $ac0, $t6, $t4 \n" + "addiu $t7, $t7, -1 \n" + "bgtz $t7, 21b \n" + " addiu %[fx], %[fx], 2 \n" + + "3: \n" + "extrv.w $t0, $ac0, %[kShiftBits] \n" // a >> kShiftBits + "extrv.w $t1, $ac1, %[kShiftBits] \n" // b >> kShiftBits + "extrv.w $t2, $ac2, %[kShiftBits] \n" // g >> kShiftBits + "extrv.w $t3, $ac3, %[kShiftBits] \n" // r >> kShiftBits + "sll $t5, %[out_x], 2 \n" + "repl.ph $t6, 128 \n" // t6 = | 128 | 128 | + "addu $t5, %[out_row], $t5 \n" + "append $t2, $t3, 16 \n" + "append $t0, $t1, 16 \n" + "subu.ph $t1, $t0, $t6 \n" + "shll_s.ph $t1, $t1, 8 \n" + "shra.ph $t1, $t1, 8 \n" + "addu.ph $t1, $t1, $t6 \n" + "subu.ph $t3, $t2, $t6 \n" + "shll_s.ph $t3, $t3, 8 \n" + "shra.ph $t3, $t3, 8 \n" + "addu.ph $t3, $t3, $t6 \n" + "precr.qb.ph $t0, $t1, $t3 \n" + "usw $t0, 0($t5) \n" + + ".set pop \n" + : [fx] "+r" (filter_x), [out_x] "+r" (out_x), [out_row] "+r" (out_row), + [rtf] "+r" (row_to_filter) + : [filter_val] "r" (filter_values), [filter_len] "r" (filter_length), + [kShiftBits] "r" (ConvolutionFilter1D::kShiftBits), + [filter_offset] "r" (filter_offset), [src_data] "r" (src_data) + : "lo", "hi", "$ac1lo", "$ac1hi", "$ac2lo", "$ac2hi", "$ac3lo", "$ac3hi", + "t0", "t1", "t2", "t3", "t4", "t5", "t6", "t7", "t8" + ); + } + } else { + for (int out_x = 0; out_x < num_values; out_x++) { + // Get the filter that determines the current output pixel. + int filter_offset, filter_length; + const ConvolutionFilter1D::Fixed* filter_values = + filter.FilterForValue(out_x, &filter_offset, &filter_length); + int filter_x = 0; + __asm__ __volatile__ ( + ".set push \n" + ".set noreorder \n" + + "beqz %[filter_len], 3f \n" + " sll $t0, %[filter_offset], 2 \n" + "addu %[rtf], %[src_data], $t0 \n" + "mtlo $0, $ac1 \n" + "mtlo $0, $ac2 \n" + "mtlo $0, $ac3 \n" + "srl $t7, %[filter_len], 2 \n" + "beqz $t7, 2f \n" + " li %[fx], 0 \n" + + "11: \n" + "addu $t4, %[filter_val], %[fx] \n" + "sll $t5, %[fx], 1 \n" + "ulw $t6, 0($t4) \n" // t6 = |cur[1]|cur[0]| + "ulw $t8, 4($t4) \n" // t8 = |cur[3]|cur[2]| + "addu $t0, %[rtf], $t5 \n" + "lw $t1, 0($t0) \n" // t1 = |a0|b0|g0|r0| + "lw $t2, 4($t0) \n" // t2 = |a1|b1|g1|r1| + "lw $t3, 8($t0) \n" // t3 = |a2|b2|g2|r2| + "lw $t4, 12($t0) \n" // t4 = |a3|b3|g3|r3| + "precrq.qb.ph $t0, $t2, $t1 \n" // t0 = |a1|g1|a0|g0| + "precr.qb.ph $t5, $t2, $t1 \n" // t5 = |b1|r1|b0|r0| + "preceu.ph.qbra $t2, $t0 \n" // t2 = |0|g1|0|g0| + "preceu.ph.qbla $t0, $t5 \n" // t0 = |0|b1|0|b0| + "preceu.ph.qbra $t5, $t5 \n" // t5 = |0|r1|0|r0| + "dpa.w.ph $ac1, $t0, $t6 \n" // ac1+(cur*b1)+(cur*b0) + "dpa.w.ph $ac2, $t2, $t6 \n" // ac2+(cur*g1)+(cur*g0) + "dpa.w.ph $ac3, $t5, $t6 \n" // ac3+(cur*r1)+(cur*r0) + "precrq.qb.ph $t0, $t4, $t3 \n" // t0 = |a3|g3|a2|g2| + "precr.qb.ph $t5, $t4, $t3 \n" // t5 = |b3|r3|b2|r2| + "preceu.ph.qbra $t2, $t0 \n" // t2 = |0|g3|0|g2| + "preceu.ph.qbla $t0, $t5 \n" // t0 = |0|b3|0|b2| + "preceu.ph.qbra $t5, $t5 \n" // t5 = |0|r3|0|r2| + "dpa.w.ph $ac1, $t0, $t8 \n" // ac1+(cur*b3)+(cur*b2) + "dpa.w.ph $ac2, $t2, $t8 \n" // ac2+(cur*g3)+(cur*g2) + "dpa.w.ph $ac3, $t5, $t8 \n" // ac3+(cur*r3)+(cur*r2) + "addiu $t7, $t7, -1 \n" + "bgtz $t7, 11b \n" + " addiu %[fx], %[fx], 8 \n" + + "2: \n" + "andi $t7, %[filter_len], 0x3 \n" // residual + "beqz $t7, 3f \n" + " nop \n" + + "21: \n" + "sll $t1, %[fx], 1 \n" + "addu $t2, %[filter_val], %[fx] \n" + "addu $t0, %[rtf], $t1 \n" + "lh $t6, 0($t2) \n" // t6 = filter_val[fx] + "lbu $t1, 0($t0) \n" // t1 = row[fx * 4 + 0] + "lbu $t2, 1($t0) \n" // t2 = row[fx * 4 + 1] + "lbu $t3, 2($t0) \n" // t3 = row[fx * 4 + 2] + "maddu $ac3, $t6, $t1 \n" + "maddu $ac2, $t6, $t2 \n" + "maddu $ac1, $t6, $t3 \n" + "addiu $t7, $t7, -1 \n" + "bgtz $t7, 21b \n" + " addiu %[fx], %[fx], 2 \n" + + "3: \n" + "extrv.w $t1, $ac1, %[kShiftBits] \n" // b >> kShiftBits + "extrv.w $t2, $ac2, %[kShiftBits] \n" // g >> kShiftBits + "extrv.w $t3, $ac3, %[kShiftBits] \n" // r >> kShiftBits + "repl.ph $t6, 128 \n" // t6 = | 128 | 128 | + "sll $t8, %[out_x], 2 \n" + "addu $t8, %[out_row], $t8 \n" + "append $t2, $t3, 16 \n" + "andi $t1, 0xFFFF \n" + "subu.ph $t5, $t1, $t6 \n" + "shll_s.ph $t5, $t5, 8 \n" + "shra.ph $t5, $t5, 8 \n" + "addu.ph $t5, $t5, $t6 \n" + "subu.ph $t4, $t2, $t6 \n" + "shll_s.ph $t4, $t4, 8 \n" + "shra.ph $t4, $t4, 8 \n" + "addu.ph $t4, $t4, $t6 \n" + "precr.qb.ph $t0, $t5, $t4 \n" + "usw $t0, 0($t8) \n" + + ".set pop \n" + : [fx] "+r" (filter_x), [out_x] "+r" (out_x), [out_row] "+r" (out_row), + [rtf] "+r" (row_to_filter) + : [filter_val] "r" (filter_values), [filter_len] "r" (filter_length), + [kShiftBits] "r" (ConvolutionFilter1D::kShiftBits), + [filter_offset] "r" (filter_offset), [src_data] "r" (src_data) + : "lo", "hi", "$ac1lo", "$ac1hi", "$ac2lo", "$ac2hi", "$ac3lo", "$ac3hi", + "t0", "t1", "t2", "t3", "t4", "t5", "t6", "t7", "t8" + ); + } + } +#endif +} +void ConvolveVertically_mips_dspr2(const ConvolutionFilter1D::Fixed* filter_val, + int filter_length, + unsigned char* const* source_data_rows, + int pixel_width, + unsigned char* out_row, + bool has_alpha) { +#if SIMD_MIPS_DSPR2 + // We go through each column in the output and do a vertical convolution, + // generating one output pixel each time. + int byte_offset; + int cnt; + int filter_y; + if (has_alpha) { + for (int out_x = 0; out_x < pixel_width; out_x++) { + __asm__ __volatile__ ( + ".set push \n" + ".set noreorder \n" + + "beqz %[filter_len], 3f \n" + " sll %[offset], %[out_x], 2 \n" + "mtlo $0, $ac0 \n" + "mtlo $0, $ac1 \n" + "mtlo $0, $ac2 \n" + "mtlo $0, $ac3 \n" + "srl %[cnt], %[filter_len], 2 \n" + "beqz %[cnt], 2f \n" + " li %[fy], 0 \n" + + "11: \n" + "sll $t1, %[fy], 1 \n" + "addu $t0, %[src_data_rows], $t1 \n" + "lw $t1, 0($t0) \n" + "lw $t2, 4($t0) \n" + "lw $t3, 8($t0) \n" + "lw $t4, 12($t0) \n" + "addu $t1, $t1, %[offset] \n" + "addu $t2, $t2, %[offset] \n" + "addu $t3, $t3, %[offset] \n" + "addu $t4, $t4, %[offset] \n" + "lw $t1, 0($t1) \n" // t1 = |a0|b0|g0|r0| + "lw $t2, 0($t2) \n" // t2 = |a1|b1|g1|r1| + "lw $t3, 0($t3) \n" // t3 = |a0|b0|g0|r0| + "lw $t4, 0($t4) \n" // t4 = |a1|b1|g1|r1| + "precrq.qb.ph $t5, $t2, $t1 \n" // t5 = |a1|g1|a0|g0| + "precr.qb.ph $t6, $t2, $t1 \n" // t6 = |b1|r1|b0|r0| + "preceu.ph.qbla $t0, $t5 \n" // t0 = |0|a1|0|a0| + "preceu.ph.qbra $t1, $t5 \n" // t1 = |0|g1|0|g0| + "preceu.ph.qbla $t2, $t6 \n" // t2 = |0|b1|0|b0| + "preceu.ph.qbra $t5, $t6 \n" // t5 = |0|r1|0|r0| + "addu $t6, %[filter_val], %[fy] \n" + "ulw $t7, 0($t6) \n" // t7 = |cur_1|cur_0| + "ulw $t6, 4($t6) \n" // t6 = |cur_3|cur_2| + "dpa.w.ph $ac0, $t5, $t7 \n" // (cur*r1)+(cur*r0) + "dpa.w.ph $ac1, $t1, $t7 \n" // (cur*g1)+(cur*g0) + "dpa.w.ph $ac2, $t2, $t7 \n" // (cur*b1)+(cur*b0) + "dpa.w.ph $ac3, $t0, $t7 \n" // (cur*a1)+(cur*a0) + "precrq.qb.ph $t5, $t4, $t3 \n" // t5 = |a3|g3|a2|g2| + "precr.qb.ph $t7, $t4, $t3 \n" // t7 = |b3|r3|b2|r2| + "preceu.ph.qbla $t0, $t5 \n" // t0 = |0|a3|0|a2| + "preceu.ph.qbra $t1, $t5 \n" // t1 = |0|g3|0|g2| + "preceu.ph.qbla $t2, $t7 \n" // t2 = |0|b3|0|b2| + "preceu.ph.qbra $t5, $t7 \n" // t5 = |0|r3|0|r2| + "dpa.w.ph $ac0, $t5, $t6 \n" // (cur*r3)+(cur*r2) + "dpa.w.ph $ac1, $t1, $t6 \n" // (cur*g3)+(cur*g2) + "dpa.w.ph $ac2, $t2, $t6 \n" // (cur*b3)+(cur*b2) + "dpa.w.ph $ac3, $t0, $t6 \n" // (cur*a3)+(cur*a2) + "addiu %[cnt], %[cnt], -1 \n" + "bgtz %[cnt], 11b \n" + " addiu %[fy], %[fy], 8 \n" + + "2: \n" + "andi %[cnt], %[filter_len], 0x3 \n" // residual + "beqz %[cnt], 3f \n" + " nop \n" + + "21: \n" + "addu $t0, %[filter_val], %[fy] \n" + "lh $t4, 0($t0) \n" // t4=filter_val[fx] + "sll $t1, %[fy], 1 \n" + "addu $t0, %[src_data_rows], $t1 \n" + "lw $t1, 0($t0) \n" + "addu $t0, $t1, %[offset] \n" + "lbu $t1, 0($t0) \n" // t1 = row[fx*4 + 0] + "lbu $t2, 1($t0) \n" // t2 = row[fx*4 + 1] + "lbu $t3, 2($t0) \n" // t3 = row[fx*4 + 2] + "lbu $t0, 3($t0) \n" // t4 = row[fx*4 + 2] + "maddu $ac0, $t4, $t1 \n" + "maddu $ac1, $t4, $t2 \n" + "maddu $ac2, $t4, $t3 \n" + "maddu $ac3, $t4, $t0 \n" + "addiu %[cnt], %[cnt], -1 \n" + "bgtz %[cnt], 21b \n" + " addiu %[fy], %[fy], 2 \n" + + "3: \n" + "extrv.w $t3, $ac0, %[kShiftBits] \n" // a >> kShiftBits + "extrv.w $t2, $ac1, %[kShiftBits] \n" // b >> kShiftBits + "extrv.w $t1, $ac2, %[kShiftBits] \n" // g >> kShiftBits + "extrv.w $t0, $ac3, %[kShiftBits] \n" // r >> kShiftBits + "repl.ph $t4, 128 \n" // t4 = | 128 | 128 | + "addu $t5, %[out_row], %[offset] \n" + "append $t2, $t3, 16 \n" // t2 = |0|g|0|r| + "append $t0, $t1, 16 \n" // t0 = |0|a|0|b| + "subu.ph $t1, $t0, $t4 \n" + "shll_s.ph $t1, $t1, 8 \n" + "shra.ph $t1, $t1, 8 \n" + "addu.ph $t1, $t1, $t4 \n" // Clamp(a)|Clamp(b) + "subu.ph $t2, $t2, $t4 \n" + "shll_s.ph $t2, $t2, 8 \n" + "shra.ph $t2, $t2, 8 \n" + "addu.ph $t2, $t2, $t4 \n" // Clamp(g)|Clamp(r) + "andi $t3, $t1, 0xFF \n" // t3 = ClampTo8(b) + "cmp.lt.ph $t3, $t2 \n" // cmp b, g, r + "pick.ph $t0, $t2, $t3 \n" + "andi $t3, $t0, 0xFF \n" + "srl $t4, $t0, 16 \n" + "cmp.lt.ph $t3, $t4 \n" + "pick.ph $t0, $t4, $t3 \n" // t0 = max_color_ch + "srl $t3, $t1, 16 \n" // t1 = ClampTo8(a) + "cmp.lt.ph $t3, $t0 \n" + "pick.ph $t0, $t0, $t3 \n" + "ins $t1, $t0, 16, 8 \n" + "precr.qb.ph $t0, $t1, $t2 \n" // t0 = |a|b|g|r| + "usw $t0, 0($t5) \n" + + ".set pop \n" + : [filter_val] "+r" (filter_val), [filter_len] "+r" (filter_length), + [offset] "+r" (byte_offset), [fy] "+r" (filter_y), [cnt] "+r" (cnt), + [out_x] "+r" (out_x), [pixel_width] "+r" (pixel_width) + : [src_data_rows] "r" (source_data_rows), [out_row] "r" (out_row), + [kShiftBits] "r" (ConvolutionFilter1D::kShiftBits) + : "lo", "hi", "$ac1lo", "$ac1hi", "$ac2lo", "$ac2hi", "$ac3lo", "$ac3hi", + "t0", "t1", "t2", "t3", "t4", "t5", "t6","t7", "memory" + ); + } + } else { + for (int out_x = 0; out_x < pixel_width; out_x++) { + __asm__ __volatile__ ( + ".set push \n" + ".set noreorder \n" + + "beqz %[filter_len], 3f \n" + " sll %[offset], %[out_x], 2 \n" + "mtlo $0, $ac0 \n" + "mtlo $0, $ac1 \n" + "mtlo $0, $ac2 \n" + "srl %[cnt], %[filter_len], 2 \n" + "beqz %[cnt], 2f \n" + " li %[fy], 0 \n" + + "11: \n" + "sll $t1, %[fy], 1 \n" + "addu $t0, %[src_data_rows], $t1 \n" + "lw $t1, 0($t0) \n" + "lw $t2, 4($t0) \n" + "lw $t3, 8($t0) \n" + "lw $t4, 12($t0) \n" + "addu $t1, $t1, %[offset] \n" + "addu $t2, $t2, %[offset] \n" + "addu $t3, $t3, %[offset] \n" + "addu $t4, $t4, %[offset] \n" + "lw $t1, 0($t1) \n" // t1 = |a0|b0|g0|r0| + "lw $t2, 0($t2) \n" // t2 = |a1|b1|g1|r1| + "lw $t3, 0($t3) \n" // t3 = |a0|b0|g0|r0| + "lw $t4, 0($t4) \n" // t4 = |a1|b1|g1|r1| + "precrq.qb.ph $t5, $t2, $t1 \n" // t5 = |a1|g1|a0|g0| + "precr.qb.ph $t6, $t2, $t1 \n" // t6 = |b1|r1|b0|r0| + "preceu.ph.qbra $t1, $t5 \n" // t1 = |0|g1|0|g0| + "preceu.ph.qbla $t2, $t6 \n" // t2 = |0|b1|0|b0| + "preceu.ph.qbra $t5, $t6 \n" // t5 = |0|r1|0|r0| + "addu $t6, %[filter_val], %[fy] \n" + "ulw $t0, 0($t6) \n" // t0 = |cur_1|cur_0| + "ulw $t6, 4($t6) \n" // t6 = |cur_1|cur_0| + "dpa.w.ph $ac0, $t5, $t0 \n" // (cur*r1)+(cur*r0) + "dpa.w.ph $ac1, $t1, $t0 \n" // (cur*g1)+(cur*g0) + "dpa.w.ph $ac2, $t2, $t0 \n" // (cur*b1)+(cur*b0) + "precrq.qb.ph $t5, $t4, $t3 \n" // t5 = |a3|g3|a2|g2| + "precr.qb.ph $t0, $t4, $t3 \n" // t0 = |b3|r3|b2|r2| + "preceu.ph.qbra $t1, $t5 \n" // t1 = |0|g3|0|g2| + "preceu.ph.qbla $t2, $t0 \n" // t2 = |0|b3|0|b2| + "preceu.ph.qbra $t5, $t0 \n" // t5 = |0|r3|0|r2| + "dpa.w.ph $ac0, $t5, $t6 \n" // (cur*r1)+(cur*r0) + "dpa.w.ph $ac1, $t1, $t6 \n" // (cur*g1)+(cur*g0) + "dpa.w.ph $ac2, $t2, $t6 \n" // (cur*b1)+(cur*b0) + "addiu %[cnt], %[cnt], -1 \n" + "bgtz %[cnt], 11b \n" + " addiu %[fy], %[fy], 8 \n" + + "2: \n" + "andi %[cnt], %[filter_len], 0x3 \n" // residual + "beqz %[cnt], 3f \n" + " nop \n" + + "21: \n" + "addu $t0, %[filter_val], %[fy] \n" + "lh $t4, 0($t0) \n" // filter_val[fx] + "sll $t1, %[fy], 1 \n" + "addu $t0, %[src_data_rows], $t1 \n" + "lw $t1, 0($t0) \n" + "addu $t0, $t1, %[offset] \n" + "lbu $t1, 0($t0) \n" // t1 = row[fx*4 + 0] + "lbu $t2, 1($t0) \n" // t2 = row[fx*4 + 1] + "lbu $t3, 2($t0) \n" // t3 = row[fx*4 + 2] + "maddu $ac0, $t4, $t1 \n" + "maddu $ac1, $t4, $t2 \n" + "maddu $ac2, $t4, $t3 \n" + "addiu %[cnt], %[cnt], -1 \n" + "bgtz %[cnt], 21b \n" + " addiu %[fy], %[fy], 2 \n" + + "3: \n" + "extrv.w $t3, $ac0, %[kShiftBits] \n" // r >> kShiftBits + "extrv.w $t2, $ac1, %[kShiftBits] \n" // g >> kShiftBits + "extrv.w $t1, $ac2, %[kShiftBits] \n" // b >> kShiftBits + "repl.ph $t6, 128 \n" // t6 = | 128 | 128 | + "addu $t5, %[out_row], %[offset] \n" + "append $t2, $t3, 16 \n" // t2 = |0|g|0|r| + "andi $t1, $t1, 0xFFFF \n" + "subu.ph $t1, $t1, $t6 \n" + "shll_s.ph $t1, $t1, 8 \n" + "shra.ph $t1, $t1, 8 \n" + "addu.ph $t1, $t1, $t6 \n" // Clamp(a)|Clamp(b) + "subu.ph $t2, $t2, $t6 \n" + "shll_s.ph $t2, $t2, 8 \n" + "shra.ph $t2, $t2, 8 \n" + "addu.ph $t2, $t2, $t6 \n" // Clamp(g)|Clamp(r) + "li $t0, 0xFF \n" + "ins $t1, $t0, 16, 8 \n" + "precr.qb.ph $t0, $t1, $t2 \n" // t0 = |a|b|g|r| + "usw $t0, 0($t5) \n" + + ".set pop \n" + : [filter_val] "+r" (filter_val), [filter_len] "+r" (filter_length), + [offset] "+r" (byte_offset), [fy] "+r" (filter_y), [cnt] "+r" (cnt), + [out_x] "+r" (out_x), [pixel_width] "+r" (pixel_width) + : [src_data_rows] "r" (source_data_rows), [out_row] "r" (out_row), + [kShiftBits] "r" (ConvolutionFilter1D::kShiftBits) + : "lo", "hi", "$ac1lo", "$ac1hi", "$ac2lo", "$ac2hi", "$ac3lo", "$ac3hi", + "t0", "t1", "t2", "t3", "t4", "t5", "t6", "memory" + ); + } + } +#endif +} +} // namespace skia diff --git a/chromium/skia/ext/convolver_mips_dspr2.h b/chromium/skia/ext/convolver_mips_dspr2.h new file mode 100644 index 00000000000..a5e59f935b9 --- /dev/null +++ b/chromium/skia/ext/convolver_mips_dspr2.h @@ -0,0 +1,25 @@ +// 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 SKIA_EXT_CONVOLVER_MIPS_DSPR2_H_ +#define SKIA_EXT_CONVOLVER_MIPS_DSPR2_H_ + +#include "skia/ext/convolver.h" + +namespace skia { + +void ConvolveVertically_mips_dspr2(const ConvolutionFilter1D::Fixed* filter_val, + int filter_length, + unsigned char* const* source_data_rows, + int pixel_width, + unsigned char* out_row, + bool has_alpha); + +void ConvolveHorizontally_mips_dspr2(const unsigned char* src_data, + const ConvolutionFilter1D& filter, + unsigned char* out_row, + bool has_alpha); +} // namespace skia + +#endif // SKIA_EXT_CONVOLVER_MIPS_DSPR2_H_ diff --git a/chromium/skia/ext/convolver_unittest.cc b/chromium/skia/ext/convolver_unittest.cc new file mode 100644 index 00000000000..8d0c852a16d --- /dev/null +++ b/chromium/skia/ext/convolver_unittest.cc @@ -0,0 +1,535 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include <string.h> +#include <time.h> +#include <algorithm> +#include <numeric> +#include <vector> + +#include "base/basictypes.h" +#include "base/logging.h" +#include "base/time/time.h" +#include "skia/ext/convolver.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "third_party/skia/include/core/SkBitmap.h" +#include "third_party/skia/include/core/SkColorPriv.h" +#include "third_party/skia/include/core/SkRect.h" +#include "third_party/skia/include/core/SkTypes.h" + +namespace skia { + +namespace { + +// Fills the given filter with impulse functions for the range 0->num_entries. +void FillImpulseFilter(int num_entries, ConvolutionFilter1D* filter) { + float one = 1.0f; + for (int i = 0; i < num_entries; i++) + filter->AddFilter(i, &one, 1); +} + +// Filters the given input with the impulse function, and verifies that it +// does not change. +void TestImpulseConvolution(const unsigned char* data, int width, int height) { + int byte_count = width * height * 4; + + ConvolutionFilter1D filter_x; + FillImpulseFilter(width, &filter_x); + + ConvolutionFilter1D filter_y; + FillImpulseFilter(height, &filter_y); + + std::vector<unsigned char> output; + output.resize(byte_count); + BGRAConvolve2D(data, width * 4, true, filter_x, filter_y, + filter_x.num_values() * 4, &output[0], false); + + // Output should exactly match input. + EXPECT_EQ(0, memcmp(data, &output[0], byte_count)); +} + +// Fills the destination filter with a box filter averaging every two pixels +// to produce the output. +void FillBoxFilter(int size, ConvolutionFilter1D* filter) { + const float box[2] = { 0.5, 0.5 }; + for (int i = 0; i < size; i++) + filter->AddFilter(i * 2, box, 2); +} + +} // namespace + +// Tests that each pixel, when set and run through the impulse filter, does +// not change. +TEST(Convolver, Impulse) { + // We pick an "odd" size that is not likely to fit on any boundaries so that + // we can see if all the widths and paddings are handled properly. + int width = 15; + int height = 31; + int byte_count = width * height * 4; + std::vector<unsigned char> input; + input.resize(byte_count); + + unsigned char* input_ptr = &input[0]; + for (int y = 0; y < height; y++) { + for (int x = 0; x < width; x++) { + for (int channel = 0; channel < 3; channel++) { + memset(input_ptr, 0, byte_count); + input_ptr[(y * width + x) * 4 + channel] = 0xff; + // Always set the alpha channel or it will attempt to "fix" it for us. + input_ptr[(y * width + x) * 4 + 3] = 0xff; + TestImpulseConvolution(input_ptr, width, height); + } + } + } +} + +// Tests that using a box filter to halve an image results in every square of 4 +// pixels in the original get averaged to a pixel in the output. +TEST(Convolver, Halve) { + static const int kSize = 16; + + int src_width = kSize; + int src_height = kSize; + int src_row_stride = src_width * 4; + int src_byte_count = src_row_stride * src_height; + std::vector<unsigned char> input; + input.resize(src_byte_count); + + int dest_width = src_width / 2; + int dest_height = src_height / 2; + int dest_byte_count = dest_width * dest_height * 4; + std::vector<unsigned char> output; + output.resize(dest_byte_count); + + // First fill the array with a bunch of random data. + srand(static_cast<unsigned>(time(NULL))); + for (int i = 0; i < src_byte_count; i++) + input[i] = rand() * 255 / RAND_MAX; + + // Compute the filters. + ConvolutionFilter1D filter_x, filter_y; + FillBoxFilter(dest_width, &filter_x); + FillBoxFilter(dest_height, &filter_y); + + // Do the convolution. + BGRAConvolve2D(&input[0], src_width, true, filter_x, filter_y, + filter_x.num_values() * 4, &output[0], false); + + // Compute the expected results and check, allowing for a small difference + // to account for rounding errors. + for (int y = 0; y < dest_height; y++) { + for (int x = 0; x < dest_width; x++) { + for (int channel = 0; channel < 4; channel++) { + int src_offset = (y * 2 * src_row_stride + x * 2 * 4) + channel; + int value = input[src_offset] + // Top left source pixel. + input[src_offset + 4] + // Top right source pixel. + input[src_offset + src_row_stride] + // Lower left. + input[src_offset + src_row_stride + 4]; // Lower right. + value /= 4; // Average. + int difference = value - output[(y * dest_width + x) * 4 + channel]; + EXPECT_TRUE(difference >= -1 || difference <= 1); + } + } + } +} + +// Tests the optimization in Convolver1D::AddFilter that avoids storing +// leading/trailing zeroes. +TEST(Convolver, AddFilter) { + skia::ConvolutionFilter1D filter; + + const skia::ConvolutionFilter1D::Fixed* values = NULL; + int filter_offset = 0; + int filter_length = 0; + + // An all-zero filter is handled correctly, all factors ignored + static const float factors1[] = { 0.0f, 0.0f, 0.0f }; + filter.AddFilter(11, factors1, arraysize(factors1)); + ASSERT_EQ(0, filter.max_filter()); + ASSERT_EQ(1, filter.num_values()); + + values = filter.FilterForValue(0, &filter_offset, &filter_length); + ASSERT_TRUE(values == NULL); // No values => NULL. + ASSERT_EQ(11, filter_offset); // Same as input offset. + ASSERT_EQ(0, filter_length); // But no factors since all are zeroes. + + // Zeroes on the left are ignored + static const float factors2[] = { 0.0f, 1.0f, 1.0f, 1.0f, 1.0f }; + filter.AddFilter(22, factors2, arraysize(factors2)); + ASSERT_EQ(4, filter.max_filter()); + ASSERT_EQ(2, filter.num_values()); + + values = filter.FilterForValue(1, &filter_offset, &filter_length); + ASSERT_TRUE(values != NULL); + ASSERT_EQ(23, filter_offset); // 22 plus 1 leading zero + ASSERT_EQ(4, filter_length); // 5 - 1 leading zero + + // Zeroes on the right are ignored + static const float factors3[] = { 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 0.0f, 0.0f }; + filter.AddFilter(33, factors3, arraysize(factors3)); + ASSERT_EQ(5, filter.max_filter()); + ASSERT_EQ(3, filter.num_values()); + + values = filter.FilterForValue(2, &filter_offset, &filter_length); + ASSERT_TRUE(values != NULL); + ASSERT_EQ(33, filter_offset); // 33, same as input due to no leading zero + ASSERT_EQ(5, filter_length); // 7 - 2 trailing zeroes + + // Zeroes in leading & trailing positions + static const float factors4[] = { 0.0f, 0.0f, 1.0f, 1.0f, 1.0f, 0.0f, 0.0f }; + filter.AddFilter(44, factors4, arraysize(factors4)); + ASSERT_EQ(5, filter.max_filter()); // No change from existing value. + ASSERT_EQ(4, filter.num_values()); + + values = filter.FilterForValue(3, &filter_offset, &filter_length); + ASSERT_TRUE(values != NULL); + ASSERT_EQ(46, filter_offset); // 44 plus 2 leading zeroes + ASSERT_EQ(3, filter_length); // 7 - (2 leading + 2 trailing) zeroes + + // Zeroes surrounded by non-zero values are ignored + static const float factors5[] = { 0.0f, 0.0f, + 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, + 0.0f }; + filter.AddFilter(55, factors5, arraysize(factors5)); + ASSERT_EQ(6, filter.max_filter()); + ASSERT_EQ(5, filter.num_values()); + + values = filter.FilterForValue(4, &filter_offset, &filter_length); + ASSERT_TRUE(values != NULL); + ASSERT_EQ(57, filter_offset); // 55 plus 2 leading zeroes + ASSERT_EQ(6, filter_length); // 9 - (2 leading + 1 trailing) zeroes + + // All-zero filters after the first one also work + static const float factors6[] = { 0.0f }; + filter.AddFilter(66, factors6, arraysize(factors6)); + ASSERT_EQ(6, filter.max_filter()); + ASSERT_EQ(6, filter.num_values()); + + values = filter.FilterForValue(5, &filter_offset, &filter_length); + ASSERT_TRUE(values == NULL); // filter_length == 0 => values is NULL + ASSERT_EQ(66, filter_offset); // value passed in + ASSERT_EQ(0, filter_length); +} + +#if defined(THREAD_SANITIZER) +// Times out under ThreadSanitizer, http://crbug.com/134400. +#define MAYBE_SIMDVerification DISABLED_SIMDVerification +#else +#define MAYBE_SIMDVerification SIMDVerification +#endif +TEST(Convolver, MAYBE_SIMDVerification) { + int source_sizes[][2] = { + {1,1}, {1,2}, {1,3}, {1,4}, {1,5}, + {2,1}, {2,2}, {2,3}, {2,4}, {2,5}, + {3,1}, {3,2}, {3,3}, {3,4}, {3,5}, + {4,1}, {4,2}, {4,3}, {4,4}, {4,5}, + {1920, 1080}, + {720, 480}, + {1377, 523}, + {325, 241} }; + int dest_sizes[][2] = { {1280, 1024}, {480, 270}, {177, 123} }; + float filter[] = { 0.05f, -0.15f, 0.6f, 0.6f, -0.15f, 0.05f }; + + srand(static_cast<unsigned int>(time(0))); + + // Loop over some specific source and destination dimensions. + for (unsigned int i = 0; i < arraysize(source_sizes); ++i) { + unsigned int source_width = source_sizes[i][0]; + unsigned int source_height = source_sizes[i][1]; + for (unsigned int j = 0; j < arraysize(dest_sizes); ++j) { + unsigned int dest_width = dest_sizes[j][0]; + unsigned int dest_height = dest_sizes[j][1]; + + // Preparing convolve coefficients. + ConvolutionFilter1D x_filter, y_filter; + for (unsigned int p = 0; p < dest_width; ++p) { + unsigned int offset = source_width * p / dest_width; + EXPECT_LT(offset, source_width); + x_filter.AddFilter(offset, filter, + std::min<int>(arraysize(filter), + source_width - offset)); + } + x_filter.PaddingForSIMD(); + for (unsigned int p = 0; p < dest_height; ++p) { + unsigned int offset = source_height * p / dest_height; + y_filter.AddFilter(offset, filter, + std::min<int>(arraysize(filter), + source_height - offset)); + } + y_filter.PaddingForSIMD(); + + // Allocate input and output skia bitmap. + SkBitmap source, result_c, result_sse; + source.setConfig(SkBitmap::kARGB_8888_Config, + source_width, source_height); + source.allocPixels(); + result_c.setConfig(SkBitmap::kARGB_8888_Config, + dest_width, dest_height); + result_c.allocPixels(); + result_sse.setConfig(SkBitmap::kARGB_8888_Config, + dest_width, dest_height); + result_sse.allocPixels(); + + // Randomize source bitmap for testing. + unsigned char* src_ptr = static_cast<unsigned char*>(source.getPixels()); + for (int y = 0; y < source.height(); y++) { + for (unsigned int x = 0; x < source.rowBytes(); x++) + src_ptr[x] = rand() % 255; + src_ptr += source.rowBytes(); + } + + // Test both cases with different has_alpha. + for (int alpha = 0; alpha < 2; alpha++) { + // Convolve using C code. + base::TimeTicks resize_start; + base::TimeDelta delta_c, delta_sse; + unsigned char* r1 = static_cast<unsigned char*>(result_c.getPixels()); + unsigned char* r2 = static_cast<unsigned char*>(result_sse.getPixels()); + + resize_start = base::TimeTicks::Now(); + BGRAConvolve2D(static_cast<const uint8*>(source.getPixels()), + static_cast<int>(source.rowBytes()), + (alpha != 0), x_filter, y_filter, + static_cast<int>(result_c.rowBytes()), r1, false); + delta_c = base::TimeTicks::Now() - resize_start; + + resize_start = base::TimeTicks::Now(); + // Convolve using SSE2 code + BGRAConvolve2D(static_cast<const uint8*>(source.getPixels()), + static_cast<int>(source.rowBytes()), + (alpha != 0), x_filter, y_filter, + static_cast<int>(result_sse.rowBytes()), r2, true); + delta_sse = base::TimeTicks::Now() - resize_start; + + // Unfortunately I could not enable the performance check now. + // Most bots use debug version, and there are great difference between + // the code generation for intrinsic, etc. In release version speed + // difference was 150%-200% depend on alpha channel presence; + // while in debug version speed difference was 96%-120%. + // TODO(jiesun): optimize further until we could enable this for + // debug version too. + // EXPECT_LE(delta_sse, delta_c); + + int64 c_us = delta_c.InMicroseconds(); + int64 sse_us = delta_sse.InMicroseconds(); + VLOG(1) << "from:" << source_width << "x" << source_height + << " to:" << dest_width << "x" << dest_height + << (alpha ? " with alpha" : " w/o alpha"); + VLOG(1) << "c:" << c_us << " sse:" << sse_us; + VLOG(1) << "ratio:" << static_cast<float>(c_us) / sse_us; + + // Comparing result. + for (unsigned int i = 0; i < dest_height; i++) { + for (unsigned int x = 0; x < dest_width * 4; x++) { // RGBA always. + EXPECT_EQ(r1[x], r2[x]); + } + r1 += result_c.rowBytes(); + r2 += result_sse.rowBytes(); + } + } + } + } +} + +TEST(Convolver, SeparableSingleConvolution) { + static const int kImgWidth = 1024; + static const int kImgHeight = 1024; + static const int kChannelCount = 3; + static const int kStrideSlack = 22; + ConvolutionFilter1D filter; + const float box[5] = { 0.2f, 0.2f, 0.2f, 0.2f, 0.2f }; + filter.AddFilter(0, box, 5); + + // Allocate a source image and set to 0. + const int src_row_stride = kImgWidth * kChannelCount + kStrideSlack; + int src_byte_count = src_row_stride * kImgHeight; + std::vector<unsigned char> input; + const int signal_x = kImgWidth / 2; + const int signal_y = kImgHeight / 2; + input.resize(src_byte_count, 0); + // The image has a single impulse pixel in channel 1, smack in the middle. + const int non_zero_pixel_index = + signal_y * src_row_stride + signal_x * kChannelCount + 1; + input[non_zero_pixel_index] = 255; + + // Destination will be a single channel image with stide matching width. + const int dest_row_stride = kImgWidth; + const int dest_byte_count = dest_row_stride * kImgHeight; + std::vector<unsigned char> output; + output.resize(dest_byte_count); + + // Apply convolution in X. + SingleChannelConvolveX1D(&input[0], src_row_stride, 1, kChannelCount, + filter, SkISize::Make(kImgWidth, kImgHeight), + &output[0], dest_row_stride, 0, 1, false); + for (int x = signal_x - 2; x <= signal_x + 2; ++x) + EXPECT_GT(output[signal_y * dest_row_stride + x], 0); + + EXPECT_EQ(output[signal_y * dest_row_stride + signal_x - 3], 0); + EXPECT_EQ(output[signal_y * dest_row_stride + signal_x + 3], 0); + + // Apply convolution in Y. + SingleChannelConvolveY1D(&input[0], src_row_stride, 1, kChannelCount, + filter, SkISize::Make(kImgWidth, kImgHeight), + &output[0], dest_row_stride, 0, 1, false); + for (int y = signal_y - 2; y <= signal_y + 2; ++y) + EXPECT_GT(output[y * dest_row_stride + signal_x], 0); + + EXPECT_EQ(output[(signal_y - 3) * dest_row_stride + signal_x], 0); + EXPECT_EQ(output[(signal_y + 3) * dest_row_stride + signal_x], 0); + + EXPECT_EQ(output[signal_y * dest_row_stride + signal_x - 1], 0); + EXPECT_EQ(output[signal_y * dest_row_stride + signal_x + 1], 0); + + // The main point of calling this is to invoke the routine on input without + // padding. + std::vector<unsigned char> output2; + output2.resize(dest_byte_count); + SingleChannelConvolveX1D(&output[0], dest_row_stride, 0, 1, + filter, SkISize::Make(kImgWidth, kImgHeight), + &output2[0], dest_row_stride, 0, 1, false); + // This should be a result of 2D convolution. + for (int x = signal_x - 2; x <= signal_x + 2; ++x) { + for (int y = signal_y - 2; y <= signal_y + 2; ++y) + EXPECT_GT(output2[y * dest_row_stride + x], 0); + } + EXPECT_EQ(output2[0], 0); + EXPECT_EQ(output2[dest_row_stride - 1], 0); + EXPECT_EQ(output2[dest_byte_count - 1], 0); +} + +TEST(Convolver, SeparableSingleConvolutionEdges) { + // The purpose of this test is to check if the implementation treats correctly + // edges of the image. + static const int kImgWidth = 600; + static const int kImgHeight = 800; + static const int kChannelCount = 3; + static const int kStrideSlack = 22; + static const int kChannel = 1; + ConvolutionFilter1D filter; + const float box[5] = { 0.2f, 0.2f, 0.2f, 0.2f, 0.2f }; + filter.AddFilter(0, box, 5); + + // Allocate a source image and set to 0. + int src_row_stride = kImgWidth * kChannelCount + kStrideSlack; + int src_byte_count = src_row_stride * kImgHeight; + std::vector<unsigned char> input(src_byte_count); + + // Draw a frame around the image. + for (int i = 0; i < src_byte_count; ++i) { + int row = i / src_row_stride; + int col = i % src_row_stride / kChannelCount; + int channel = i % src_row_stride % kChannelCount; + if (channel != kChannel || col > kImgWidth) { + input[i] = 255; + } else if (row == 0 || col == 0 || + col == kImgWidth - 1 || row == kImgHeight - 1) { + input[i] = 100; + } else if (row == 1 || col == 1 || + col == kImgWidth - 2 || row == kImgHeight - 2) { + input[i] = 200; + } else { + input[i] = 0; + } + } + + // Destination will be a single channel image with stide matching width. + int dest_row_stride = kImgWidth; + int dest_byte_count = dest_row_stride * kImgHeight; + std::vector<unsigned char> output; + output.resize(dest_byte_count); + + // Apply convolution in X. + SingleChannelConvolveX1D(&input[0], src_row_stride, 1, kChannelCount, + filter, SkISize::Make(kImgWidth, kImgHeight), + &output[0], dest_row_stride, 0, 1, false); + + // Sadly, comparison is not as simple as retaining all values. + int invalid_values = 0; + const unsigned char first_value = output[0]; + EXPECT_NEAR(first_value, 100, 1); + for (int i = 0; i < dest_row_stride; ++i) { + if (output[i] != first_value) + ++invalid_values; + } + EXPECT_EQ(0, invalid_values); + + int test_row = 22; + EXPECT_NEAR(output[test_row * dest_row_stride], 100, 1); + EXPECT_NEAR(output[test_row * dest_row_stride + 1], 80, 1); + EXPECT_NEAR(output[test_row * dest_row_stride + 2], 60, 1); + EXPECT_NEAR(output[test_row * dest_row_stride + 3], 40, 1); + EXPECT_NEAR(output[(test_row + 1) * dest_row_stride - 1], 100, 1); + EXPECT_NEAR(output[(test_row + 1) * dest_row_stride - 2], 80, 1); + EXPECT_NEAR(output[(test_row + 1) * dest_row_stride - 3], 60, 1); + EXPECT_NEAR(output[(test_row + 1) * dest_row_stride - 4], 40, 1); + + SingleChannelConvolveY1D(&input[0], src_row_stride, 1, kChannelCount, + filter, SkISize::Make(kImgWidth, kImgHeight), + &output[0], dest_row_stride, 0, 1, false); + + int test_column = 42; + EXPECT_NEAR(output[test_column], 100, 1); + EXPECT_NEAR(output[test_column + dest_row_stride], 80, 1); + EXPECT_NEAR(output[test_column + dest_row_stride * 2], 60, 1); + EXPECT_NEAR(output[test_column + dest_row_stride * 3], 40, 1); + + EXPECT_NEAR(output[test_column + dest_row_stride * (kImgHeight - 1)], 100, 1); + EXPECT_NEAR(output[test_column + dest_row_stride * (kImgHeight - 2)], 80, 1); + EXPECT_NEAR(output[test_column + dest_row_stride * (kImgHeight - 3)], 60, 1); + EXPECT_NEAR(output[test_column + dest_row_stride * (kImgHeight - 4)], 40, 1); +} + +TEST(Convolver, SetUpGaussianConvolutionFilter) { + ConvolutionFilter1D smoothing_filter; + ConvolutionFilter1D gradient_filter; + SetUpGaussianConvolutionKernel(&smoothing_filter, 4.5f, false); + SetUpGaussianConvolutionKernel(&gradient_filter, 3.0f, true); + + int specified_filter_length; + int filter_offset; + int filter_length; + + const ConvolutionFilter1D::Fixed* smoothing_kernel = + smoothing_filter.GetSingleFilter( + &specified_filter_length, &filter_offset, &filter_length); + EXPECT_TRUE(smoothing_kernel); + std::vector<float> fp_smoothing_kernel(filter_length); + std::transform(smoothing_kernel, + smoothing_kernel + filter_length, + fp_smoothing_kernel.begin(), + ConvolutionFilter1D::FixedToFloat); + // Should sum-up to 1 (nearly), and all values whould be in ]0, 1[. + EXPECT_NEAR(std::accumulate( + fp_smoothing_kernel.begin(), fp_smoothing_kernel.end(), 0.0f), + 1.0f, 0.01f); + EXPECT_GT(*std::min_element(fp_smoothing_kernel.begin(), + fp_smoothing_kernel.end()), 0.0f); + EXPECT_LT(*std::max_element(fp_smoothing_kernel.begin(), + fp_smoothing_kernel.end()), 1.0f); + + const ConvolutionFilter1D::Fixed* gradient_kernel = + gradient_filter.GetSingleFilter( + &specified_filter_length, &filter_offset, &filter_length); + EXPECT_TRUE(gradient_kernel); + std::vector<float> fp_gradient_kernel(filter_length); + std::transform(gradient_kernel, + gradient_kernel + filter_length, + fp_gradient_kernel.begin(), + ConvolutionFilter1D::FixedToFloat); + // Should sum-up to 0, and all values whould be in ]-1.5, 1.5[. + EXPECT_NEAR(std::accumulate( + fp_gradient_kernel.begin(), fp_gradient_kernel.end(), 0.0f), + 0.0f, 0.01f); + EXPECT_GT(*std::min_element(fp_gradient_kernel.begin(), + fp_gradient_kernel.end()), -1.5f); + EXPECT_LT(*std::min_element(fp_gradient_kernel.begin(), + fp_gradient_kernel.end()), 0.0f); + EXPECT_LT(*std::max_element(fp_gradient_kernel.begin(), + fp_gradient_kernel.end()), 1.5f); + EXPECT_GT(*std::max_element(fp_gradient_kernel.begin(), + fp_gradient_kernel.end()), 0.0f); +} + +} // namespace skia diff --git a/chromium/skia/ext/data/vectorcanvastest/basicdrawing/00_pc_clean.png b/chromium/skia/ext/data/vectorcanvastest/basicdrawing/00_pc_clean.png Binary files differnew file mode 100644 index 00000000000..a5435f278bc --- /dev/null +++ b/chromium/skia/ext/data/vectorcanvastest/basicdrawing/00_pc_clean.png diff --git a/chromium/skia/ext/data/vectorcanvastest/basicdrawing/00_vc_clean.png b/chromium/skia/ext/data/vectorcanvastest/basicdrawing/00_vc_clean.png Binary files differnew file mode 100644 index 00000000000..a5435f278bc --- /dev/null +++ b/chromium/skia/ext/data/vectorcanvastest/basicdrawing/00_vc_clean.png diff --git a/chromium/skia/ext/data/vectorcanvastest/basicdrawing/01_pc_drawargb.png b/chromium/skia/ext/data/vectorcanvastest/basicdrawing/01_pc_drawargb.png Binary files differnew file mode 100644 index 00000000000..a5435f278bc --- /dev/null +++ b/chromium/skia/ext/data/vectorcanvastest/basicdrawing/01_pc_drawargb.png diff --git a/chromium/skia/ext/data/vectorcanvastest/basicdrawing/01_vc_drawargb.png b/chromium/skia/ext/data/vectorcanvastest/basicdrawing/01_vc_drawargb.png Binary files differnew file mode 100644 index 00000000000..a5435f278bc --- /dev/null +++ b/chromium/skia/ext/data/vectorcanvastest/basicdrawing/01_vc_drawargb.png diff --git a/chromium/skia/ext/data/vectorcanvastest/basicdrawing/02_pc_drawline_black.png b/chromium/skia/ext/data/vectorcanvastest/basicdrawing/02_pc_drawline_black.png Binary files differnew file mode 100644 index 00000000000..c21fdf1c577 --- /dev/null +++ b/chromium/skia/ext/data/vectorcanvastest/basicdrawing/02_pc_drawline_black.png diff --git a/chromium/skia/ext/data/vectorcanvastest/basicdrawing/02_vc_drawline_black.png b/chromium/skia/ext/data/vectorcanvastest/basicdrawing/02_vc_drawline_black.png Binary files differnew file mode 100644 index 00000000000..c21fdf1c577 --- /dev/null +++ b/chromium/skia/ext/data/vectorcanvastest/basicdrawing/02_vc_drawline_black.png diff --git a/chromium/skia/ext/data/vectorcanvastest/basicdrawing/03_pc_drawrect_green.png b/chromium/skia/ext/data/vectorcanvastest/basicdrawing/03_pc_drawrect_green.png Binary files differnew file mode 100644 index 00000000000..dfc46a80d8a --- /dev/null +++ b/chromium/skia/ext/data/vectorcanvastest/basicdrawing/03_pc_drawrect_green.png diff --git a/chromium/skia/ext/data/vectorcanvastest/basicdrawing/03_vc_drawrect_green.png b/chromium/skia/ext/data/vectorcanvastest/basicdrawing/03_vc_drawrect_green.png Binary files differnew file mode 100644 index 00000000000..dfc46a80d8a --- /dev/null +++ b/chromium/skia/ext/data/vectorcanvastest/basicdrawing/03_vc_drawrect_green.png diff --git a/chromium/skia/ext/data/vectorcanvastest/basicdrawing/04_pc_drawrect_noop.png b/chromium/skia/ext/data/vectorcanvastest/basicdrawing/04_pc_drawrect_noop.png Binary files differnew file mode 100644 index 00000000000..dfc46a80d8a --- /dev/null +++ b/chromium/skia/ext/data/vectorcanvastest/basicdrawing/04_pc_drawrect_noop.png diff --git a/chromium/skia/ext/data/vectorcanvastest/basicdrawing/04_vc_drawrect_noop.png b/chromium/skia/ext/data/vectorcanvastest/basicdrawing/04_vc_drawrect_noop.png Binary files differnew file mode 100644 index 00000000000..dfc46a80d8a --- /dev/null +++ b/chromium/skia/ext/data/vectorcanvastest/basicdrawing/04_vc_drawrect_noop.png diff --git a/chromium/skia/ext/data/vectorcanvastest/basicdrawing/05_pc_drawrect_noop.png b/chromium/skia/ext/data/vectorcanvastest/basicdrawing/05_pc_drawrect_noop.png Binary files differnew file mode 100644 index 00000000000..69cc6dce635 --- /dev/null +++ b/chromium/skia/ext/data/vectorcanvastest/basicdrawing/05_pc_drawrect_noop.png diff --git a/chromium/skia/ext/data/vectorcanvastest/basicdrawing/05_vc_drawrect_noop.png b/chromium/skia/ext/data/vectorcanvastest/basicdrawing/05_vc_drawrect_noop.png Binary files differnew file mode 100644 index 00000000000..69cc6dce635 --- /dev/null +++ b/chromium/skia/ext/data/vectorcanvastest/basicdrawing/05_vc_drawrect_noop.png diff --git a/chromium/skia/ext/data/vectorcanvastest/basicdrawing/06_pc_drawpaint_black.png b/chromium/skia/ext/data/vectorcanvastest/basicdrawing/06_pc_drawpaint_black.png Binary files differnew file mode 100644 index 00000000000..9cbff6e164f --- /dev/null +++ b/chromium/skia/ext/data/vectorcanvastest/basicdrawing/06_pc_drawpaint_black.png diff --git a/chromium/skia/ext/data/vectorcanvastest/basicdrawing/06_vc_drawpaint_black.png b/chromium/skia/ext/data/vectorcanvastest/basicdrawing/06_vc_drawpaint_black.png Binary files differnew file mode 100644 index 00000000000..9cbff6e164f --- /dev/null +++ b/chromium/skia/ext/data/vectorcanvastest/basicdrawing/06_vc_drawpaint_black.png diff --git a/chromium/skia/ext/data/vectorcanvastest/basicdrawing/07_pc_drawline_left_to_right.png b/chromium/skia/ext/data/vectorcanvastest/basicdrawing/07_pc_drawline_left_to_right.png Binary files differnew file mode 100644 index 00000000000..bbdfc36cb56 --- /dev/null +++ b/chromium/skia/ext/data/vectorcanvastest/basicdrawing/07_pc_drawline_left_to_right.png diff --git a/chromium/skia/ext/data/vectorcanvastest/basicdrawing/07_vc_drawline_left_to_right.png b/chromium/skia/ext/data/vectorcanvastest/basicdrawing/07_vc_drawline_left_to_right.png Binary files differnew file mode 100644 index 00000000000..bbdfc36cb56 --- /dev/null +++ b/chromium/skia/ext/data/vectorcanvastest/basicdrawing/07_vc_drawline_left_to_right.png diff --git a/chromium/skia/ext/data/vectorcanvastest/basicdrawing/08_pc_drawline_red.png b/chromium/skia/ext/data/vectorcanvastest/basicdrawing/08_pc_drawline_red.png Binary files differnew file mode 100644 index 00000000000..9dc35f051e9 --- /dev/null +++ b/chromium/skia/ext/data/vectorcanvastest/basicdrawing/08_pc_drawline_red.png diff --git a/chromium/skia/ext/data/vectorcanvastest/basicdrawing/08_vc_drawline_red.png b/chromium/skia/ext/data/vectorcanvastest/basicdrawing/08_vc_drawline_red.png Binary files differnew file mode 100644 index 00000000000..9dc35f051e9 --- /dev/null +++ b/chromium/skia/ext/data/vectorcanvastest/basicdrawing/08_vc_drawline_red.png diff --git a/chromium/skia/ext/data/vectorcanvastest/bitmaps/00_pc_opaque.png b/chromium/skia/ext/data/vectorcanvastest/bitmaps/00_pc_opaque.png Binary files differnew file mode 100644 index 00000000000..812b1ca293a --- /dev/null +++ b/chromium/skia/ext/data/vectorcanvastest/bitmaps/00_pc_opaque.png diff --git a/chromium/skia/ext/data/vectorcanvastest/bitmaps/00_vc_opaque.png b/chromium/skia/ext/data/vectorcanvastest/bitmaps/00_vc_opaque.png Binary files differnew file mode 100644 index 00000000000..812b1ca293a --- /dev/null +++ b/chromium/skia/ext/data/vectorcanvastest/bitmaps/00_vc_opaque.png diff --git a/chromium/skia/ext/data/vectorcanvastest/bitmaps/01_pc_alpha.png b/chromium/skia/ext/data/vectorcanvastest/bitmaps/01_pc_alpha.png Binary files differnew file mode 100644 index 00000000000..1d1342bbaa8 --- /dev/null +++ b/chromium/skia/ext/data/vectorcanvastest/bitmaps/01_pc_alpha.png diff --git a/chromium/skia/ext/data/vectorcanvastest/bitmaps/01_vc_alpha.png b/chromium/skia/ext/data/vectorcanvastest/bitmaps/01_vc_alpha.png Binary files differnew file mode 100644 index 00000000000..1d1342bbaa8 --- /dev/null +++ b/chromium/skia/ext/data/vectorcanvastest/bitmaps/01_vc_alpha.png diff --git a/chromium/skia/ext/data/vectorcanvastest/bitmaps/bitmap_alpha.png b/chromium/skia/ext/data/vectorcanvastest/bitmaps/bitmap_alpha.png Binary files differnew file mode 100644 index 00000000000..a19d09d06c7 --- /dev/null +++ b/chromium/skia/ext/data/vectorcanvastest/bitmaps/bitmap_alpha.png diff --git a/chromium/skia/ext/data/vectorcanvastest/bitmaps/bitmap_opaque.png b/chromium/skia/ext/data/vectorcanvastest/bitmaps/bitmap_opaque.png Binary files differnew file mode 100644 index 00000000000..3560d270f59 --- /dev/null +++ b/chromium/skia/ext/data/vectorcanvastest/bitmaps/bitmap_opaque.png diff --git a/chromium/skia/ext/data/vectorcanvastest/circles/00_pc_circle_stroke.png b/chromium/skia/ext/data/vectorcanvastest/circles/00_pc_circle_stroke.png Binary files differnew file mode 100644 index 00000000000..896631b3231 --- /dev/null +++ b/chromium/skia/ext/data/vectorcanvastest/circles/00_pc_circle_stroke.png diff --git a/chromium/skia/ext/data/vectorcanvastest/circles/00_vc_circle_stroke.png b/chromium/skia/ext/data/vectorcanvastest/circles/00_vc_circle_stroke.png Binary files differnew file mode 100644 index 00000000000..c265be341fe --- /dev/null +++ b/chromium/skia/ext/data/vectorcanvastest/circles/00_vc_circle_stroke.png diff --git a/chromium/skia/ext/data/vectorcanvastest/circles/01_pc_circle_fill.png b/chromium/skia/ext/data/vectorcanvastest/circles/01_pc_circle_fill.png Binary files differnew file mode 100644 index 00000000000..92b647d046a --- /dev/null +++ b/chromium/skia/ext/data/vectorcanvastest/circles/01_pc_circle_fill.png diff --git a/chromium/skia/ext/data/vectorcanvastest/circles/01_vc_circle_fill.png b/chromium/skia/ext/data/vectorcanvastest/circles/01_vc_circle_fill.png Binary files differnew file mode 100644 index 00000000000..f5270cea5a7 --- /dev/null +++ b/chromium/skia/ext/data/vectorcanvastest/circles/01_vc_circle_fill.png diff --git a/chromium/skia/ext/data/vectorcanvastest/circles/02_pc_circle_over_strike.png b/chromium/skia/ext/data/vectorcanvastest/circles/02_pc_circle_over_strike.png Binary files differnew file mode 100644 index 00000000000..64ae06ab9d8 --- /dev/null +++ b/chromium/skia/ext/data/vectorcanvastest/circles/02_pc_circle_over_strike.png diff --git a/chromium/skia/ext/data/vectorcanvastest/circles/02_vc_circle_over_strike.png b/chromium/skia/ext/data/vectorcanvastest/circles/02_vc_circle_over_strike.png Binary files differnew file mode 100644 index 00000000000..4d3d1b069ee --- /dev/null +++ b/chromium/skia/ext/data/vectorcanvastest/circles/02_vc_circle_over_strike.png diff --git a/chromium/skia/ext/data/vectorcanvastest/circles/03_pc_circle_stroke_and_fill.png b/chromium/skia/ext/data/vectorcanvastest/circles/03_pc_circle_stroke_and_fill.png Binary files differnew file mode 100644 index 00000000000..6aeeb49b083 --- /dev/null +++ b/chromium/skia/ext/data/vectorcanvastest/circles/03_pc_circle_stroke_and_fill.png diff --git a/chromium/skia/ext/data/vectorcanvastest/circles/03_vc_circle_stroke_and_fill.png b/chromium/skia/ext/data/vectorcanvastest/circles/03_vc_circle_stroke_and_fill.png Binary files differnew file mode 100644 index 00000000000..f073a3e8c8d --- /dev/null +++ b/chromium/skia/ext/data/vectorcanvastest/circles/03_vc_circle_stroke_and_fill.png diff --git a/chromium/skia/ext/data/vectorcanvastest/circles/04_pc_mixed_stroke.png b/chromium/skia/ext/data/vectorcanvastest/circles/04_pc_mixed_stroke.png Binary files differnew file mode 100644 index 00000000000..e4a044fa26b --- /dev/null +++ b/chromium/skia/ext/data/vectorcanvastest/circles/04_pc_mixed_stroke.png diff --git a/chromium/skia/ext/data/vectorcanvastest/circles/04_vc_mixed_stroke.png b/chromium/skia/ext/data/vectorcanvastest/circles/04_vc_mixed_stroke.png Binary files differnew file mode 100644 index 00000000000..efd9e3abdfb --- /dev/null +++ b/chromium/skia/ext/data/vectorcanvastest/circles/04_vc_mixed_stroke.png diff --git a/chromium/skia/ext/data/vectorcanvastest/clippingclean/00_pc_clipped.png b/chromium/skia/ext/data/vectorcanvastest/clippingclean/00_pc_clipped.png Binary files differnew file mode 100644 index 00000000000..14ff949d624 --- /dev/null +++ b/chromium/skia/ext/data/vectorcanvastest/clippingclean/00_pc_clipped.png diff --git a/chromium/skia/ext/data/vectorcanvastest/clippingclean/00_vc_clipped.png b/chromium/skia/ext/data/vectorcanvastest/clippingclean/00_vc_clipped.png Binary files differnew file mode 100644 index 00000000000..14ff949d624 --- /dev/null +++ b/chromium/skia/ext/data/vectorcanvastest/clippingclean/00_vc_clipped.png diff --git a/chromium/skia/ext/data/vectorcanvastest/clippingclean/01_pc_unclipped.png b/chromium/skia/ext/data/vectorcanvastest/clippingclean/01_pc_unclipped.png Binary files differnew file mode 100644 index 00000000000..436f9a5b7c3 --- /dev/null +++ b/chromium/skia/ext/data/vectorcanvastest/clippingclean/01_pc_unclipped.png diff --git a/chromium/skia/ext/data/vectorcanvastest/clippingclean/01_vc_unclipped.png b/chromium/skia/ext/data/vectorcanvastest/clippingclean/01_vc_unclipped.png Binary files differnew file mode 100644 index 00000000000..436f9a5b7c3 --- /dev/null +++ b/chromium/skia/ext/data/vectorcanvastest/clippingclean/01_vc_unclipped.png diff --git a/chromium/skia/ext/data/vectorcanvastest/clippingcombined/00_pc_combined.png b/chromium/skia/ext/data/vectorcanvastest/clippingcombined/00_pc_combined.png Binary files differnew file mode 100644 index 00000000000..14ff949d624 --- /dev/null +++ b/chromium/skia/ext/data/vectorcanvastest/clippingcombined/00_pc_combined.png diff --git a/chromium/skia/ext/data/vectorcanvastest/clippingcombined/00_vc_combined.png b/chromium/skia/ext/data/vectorcanvastest/clippingcombined/00_vc_combined.png Binary files differnew file mode 100644 index 00000000000..14ff949d624 --- /dev/null +++ b/chromium/skia/ext/data/vectorcanvastest/clippingcombined/00_vc_combined.png diff --git a/chromium/skia/ext/data/vectorcanvastest/clippingintersect/00_pc_intersect.png b/chromium/skia/ext/data/vectorcanvastest/clippingintersect/00_pc_intersect.png Binary files differnew file mode 100644 index 00000000000..1285dac0dba --- /dev/null +++ b/chromium/skia/ext/data/vectorcanvastest/clippingintersect/00_pc_intersect.png diff --git a/chromium/skia/ext/data/vectorcanvastest/clippingintersect/00_vc_intersect.png b/chromium/skia/ext/data/vectorcanvastest/clippingintersect/00_vc_intersect.png Binary files differnew file mode 100644 index 00000000000..1285dac0dba --- /dev/null +++ b/chromium/skia/ext/data/vectorcanvastest/clippingintersect/00_vc_intersect.png diff --git a/chromium/skia/ext/data/vectorcanvastest/clippingpath/00_pc_path.png b/chromium/skia/ext/data/vectorcanvastest/clippingpath/00_pc_path.png Binary files differnew file mode 100644 index 00000000000..8807649bb93 --- /dev/null +++ b/chromium/skia/ext/data/vectorcanvastest/clippingpath/00_pc_path.png diff --git a/chromium/skia/ext/data/vectorcanvastest/clippingpath/00_vc_path.png b/chromium/skia/ext/data/vectorcanvastest/clippingpath/00_vc_path.png Binary files differnew file mode 100644 index 00000000000..8807649bb93 --- /dev/null +++ b/chromium/skia/ext/data/vectorcanvastest/clippingpath/00_vc_path.png diff --git a/chromium/skia/ext/data/vectorcanvastest/clippingrect/00_pc_rect.png b/chromium/skia/ext/data/vectorcanvastest/clippingrect/00_pc_rect.png Binary files differnew file mode 100644 index 00000000000..9c365e13dd5 --- /dev/null +++ b/chromium/skia/ext/data/vectorcanvastest/clippingrect/00_pc_rect.png diff --git a/chromium/skia/ext/data/vectorcanvastest/clippingrect/00_vc_rect.png b/chromium/skia/ext/data/vectorcanvastest/clippingrect/00_vc_rect.png Binary files differnew file mode 100644 index 00000000000..9c365e13dd5 --- /dev/null +++ b/chromium/skia/ext/data/vectorcanvastest/clippingrect/00_vc_rect.png diff --git a/chromium/skia/ext/data/vectorcanvastest/diagonallines/00_pc_nw-se.png b/chromium/skia/ext/data/vectorcanvastest/diagonallines/00_pc_nw-se.png Binary files differnew file mode 100644 index 00000000000..5736c3503ad --- /dev/null +++ b/chromium/skia/ext/data/vectorcanvastest/diagonallines/00_pc_nw-se.png diff --git a/chromium/skia/ext/data/vectorcanvastest/diagonallines/00_vc_nw-se.png b/chromium/skia/ext/data/vectorcanvastest/diagonallines/00_vc_nw-se.png Binary files differnew file mode 100644 index 00000000000..5736c3503ad --- /dev/null +++ b/chromium/skia/ext/data/vectorcanvastest/diagonallines/00_vc_nw-se.png diff --git a/chromium/skia/ext/data/vectorcanvastest/diagonallines/01_pc_sw-ne.png b/chromium/skia/ext/data/vectorcanvastest/diagonallines/01_pc_sw-ne.png Binary files differnew file mode 100644 index 00000000000..bfffd8a52a6 --- /dev/null +++ b/chromium/skia/ext/data/vectorcanvastest/diagonallines/01_pc_sw-ne.png diff --git a/chromium/skia/ext/data/vectorcanvastest/diagonallines/01_vc_sw-ne.png b/chromium/skia/ext/data/vectorcanvastest/diagonallines/01_vc_sw-ne.png Binary files differnew file mode 100644 index 00000000000..ae6b7537fc1 --- /dev/null +++ b/chromium/skia/ext/data/vectorcanvastest/diagonallines/01_vc_sw-ne.png diff --git a/chromium/skia/ext/data/vectorcanvastest/diagonallines/02_pc_ne-sw.png b/chromium/skia/ext/data/vectorcanvastest/diagonallines/02_pc_ne-sw.png Binary files differnew file mode 100644 index 00000000000..75acdad042a --- /dev/null +++ b/chromium/skia/ext/data/vectorcanvastest/diagonallines/02_pc_ne-sw.png diff --git a/chromium/skia/ext/data/vectorcanvastest/diagonallines/02_vc_ne-sw.png b/chromium/skia/ext/data/vectorcanvastest/diagonallines/02_vc_ne-sw.png Binary files differnew file mode 100644 index 00000000000..86a67992afe --- /dev/null +++ b/chromium/skia/ext/data/vectorcanvastest/diagonallines/02_vc_ne-sw.png diff --git a/chromium/skia/ext/data/vectorcanvastest/diagonallines/03_pc_se-nw.png b/chromium/skia/ext/data/vectorcanvastest/diagonallines/03_pc_se-nw.png Binary files differnew file mode 100644 index 00000000000..50502cc1b8c --- /dev/null +++ b/chromium/skia/ext/data/vectorcanvastest/diagonallines/03_pc_se-nw.png diff --git a/chromium/skia/ext/data/vectorcanvastest/diagonallines/03_vc_se-nw.png b/chromium/skia/ext/data/vectorcanvastest/diagonallines/03_vc_se-nw.png Binary files differnew file mode 100644 index 00000000000..362f6e727e7 --- /dev/null +++ b/chromium/skia/ext/data/vectorcanvastest/diagonallines/03_vc_se-nw.png diff --git a/chromium/skia/ext/data/vectorcanvastest/lineorientation/00_pc_horizontal.png b/chromium/skia/ext/data/vectorcanvastest/lineorientation/00_pc_horizontal.png Binary files differnew file mode 100644 index 00000000000..7bcd99885a8 --- /dev/null +++ b/chromium/skia/ext/data/vectorcanvastest/lineorientation/00_pc_horizontal.png diff --git a/chromium/skia/ext/data/vectorcanvastest/lineorientation/00_vc_horizontal.png b/chromium/skia/ext/data/vectorcanvastest/lineorientation/00_vc_horizontal.png Binary files differnew file mode 100644 index 00000000000..46c9b0abe18 --- /dev/null +++ b/chromium/skia/ext/data/vectorcanvastest/lineorientation/00_vc_horizontal.png diff --git a/chromium/skia/ext/data/vectorcanvastest/lineorientation/01_pc_vertical.png b/chromium/skia/ext/data/vectorcanvastest/lineorientation/01_pc_vertical.png Binary files differnew file mode 100644 index 00000000000..09f41db50fa --- /dev/null +++ b/chromium/skia/ext/data/vectorcanvastest/lineorientation/01_pc_vertical.png diff --git a/chromium/skia/ext/data/vectorcanvastest/lineorientation/01_vc_vertical.png b/chromium/skia/ext/data/vectorcanvastest/lineorientation/01_vc_vertical.png Binary files differnew file mode 100644 index 00000000000..7f5f1f7f156 --- /dev/null +++ b/chromium/skia/ext/data/vectorcanvastest/lineorientation/01_vc_vertical.png diff --git a/chromium/skia/ext/data/vectorcanvastest/lineorientation/02_pc_horizontal_180.png b/chromium/skia/ext/data/vectorcanvastest/lineorientation/02_pc_horizontal_180.png Binary files differnew file mode 100644 index 00000000000..5966df640eb --- /dev/null +++ b/chromium/skia/ext/data/vectorcanvastest/lineorientation/02_pc_horizontal_180.png diff --git a/chromium/skia/ext/data/vectorcanvastest/lineorientation/02_vc_horizontal_180.png b/chromium/skia/ext/data/vectorcanvastest/lineorientation/02_vc_horizontal_180.png Binary files differnew file mode 100644 index 00000000000..e43a844985d --- /dev/null +++ b/chromium/skia/ext/data/vectorcanvastest/lineorientation/02_vc_horizontal_180.png diff --git a/chromium/skia/ext/data/vectorcanvastest/lineorientation/03_pc_vertical_180.png b/chromium/skia/ext/data/vectorcanvastest/lineorientation/03_pc_vertical_180.png Binary files differnew file mode 100644 index 00000000000..9ac482558f8 --- /dev/null +++ b/chromium/skia/ext/data/vectorcanvastest/lineorientation/03_pc_vertical_180.png diff --git a/chromium/skia/ext/data/vectorcanvastest/lineorientation/03_vc_vertical_180.png b/chromium/skia/ext/data/vectorcanvastest/lineorientation/03_vc_vertical_180.png Binary files differnew file mode 100644 index 00000000000..d9e033abf59 --- /dev/null +++ b/chromium/skia/ext/data/vectorcanvastest/lineorientation/03_vc_vertical_180.png diff --git a/chromium/skia/ext/data/vectorcanvastest/matrix/00_pc_translate1.png b/chromium/skia/ext/data/vectorcanvastest/matrix/00_pc_translate1.png Binary files differnew file mode 100644 index 00000000000..fe27cb31581 --- /dev/null +++ b/chromium/skia/ext/data/vectorcanvastest/matrix/00_pc_translate1.png diff --git a/chromium/skia/ext/data/vectorcanvastest/matrix/00_vc_translate1.png b/chromium/skia/ext/data/vectorcanvastest/matrix/00_vc_translate1.png Binary files differnew file mode 100644 index 00000000000..fe27cb31581 --- /dev/null +++ b/chromium/skia/ext/data/vectorcanvastest/matrix/00_vc_translate1.png diff --git a/chromium/skia/ext/data/vectorcanvastest/matrix/01_pc_translate2.png b/chromium/skia/ext/data/vectorcanvastest/matrix/01_pc_translate2.png Binary files differnew file mode 100644 index 00000000000..406bf5776e1 --- /dev/null +++ b/chromium/skia/ext/data/vectorcanvastest/matrix/01_pc_translate2.png diff --git a/chromium/skia/ext/data/vectorcanvastest/matrix/01_vc_translate2.png b/chromium/skia/ext/data/vectorcanvastest/matrix/01_vc_translate2.png Binary files differnew file mode 100644 index 00000000000..406bf5776e1 --- /dev/null +++ b/chromium/skia/ext/data/vectorcanvastest/matrix/01_vc_translate2.png diff --git a/chromium/skia/ext/data/vectorcanvastest/matrix/02_pc_scale.png b/chromium/skia/ext/data/vectorcanvastest/matrix/02_pc_scale.png Binary files differnew file mode 100644 index 00000000000..9e94fb0c6f7 --- /dev/null +++ b/chromium/skia/ext/data/vectorcanvastest/matrix/02_pc_scale.png diff --git a/chromium/skia/ext/data/vectorcanvastest/matrix/02_vc_scale.png b/chromium/skia/ext/data/vectorcanvastest/matrix/02_vc_scale.png Binary files differnew file mode 100644 index 00000000000..fde62aadcb6 --- /dev/null +++ b/chromium/skia/ext/data/vectorcanvastest/matrix/02_vc_scale.png diff --git a/chromium/skia/ext/data/vectorcanvastest/matrix/03_pc_rotate.png b/chromium/skia/ext/data/vectorcanvastest/matrix/03_pc_rotate.png Binary files differnew file mode 100644 index 00000000000..7a43a2ad665 --- /dev/null +++ b/chromium/skia/ext/data/vectorcanvastest/matrix/03_pc_rotate.png diff --git a/chromium/skia/ext/data/vectorcanvastest/matrix/03_vc_rotate.png b/chromium/skia/ext/data/vectorcanvastest/matrix/03_vc_rotate.png Binary files differnew file mode 100644 index 00000000000..7a22b7f14d7 --- /dev/null +++ b/chromium/skia/ext/data/vectorcanvastest/matrix/03_vc_rotate.png diff --git a/chromium/skia/ext/data/vectorcanvastest/patheffects/00_pc_dash_line.png b/chromium/skia/ext/data/vectorcanvastest/patheffects/00_pc_dash_line.png Binary files differnew file mode 100644 index 00000000000..e08d3e2a3f5 --- /dev/null +++ b/chromium/skia/ext/data/vectorcanvastest/patheffects/00_pc_dash_line.png diff --git a/chromium/skia/ext/data/vectorcanvastest/patheffects/00_vc_dash_line.png b/chromium/skia/ext/data/vectorcanvastest/patheffects/00_vc_dash_line.png Binary files differnew file mode 100644 index 00000000000..e08d3e2a3f5 --- /dev/null +++ b/chromium/skia/ext/data/vectorcanvastest/patheffects/00_vc_dash_line.png diff --git a/chromium/skia/ext/data/vectorcanvastest/patheffects/01_pc_dash_path.png b/chromium/skia/ext/data/vectorcanvastest/patheffects/01_pc_dash_path.png Binary files differnew file mode 100644 index 00000000000..3a301354219 --- /dev/null +++ b/chromium/skia/ext/data/vectorcanvastest/patheffects/01_pc_dash_path.png diff --git a/chromium/skia/ext/data/vectorcanvastest/patheffects/01_vc_dash_path.png b/chromium/skia/ext/data/vectorcanvastest/patheffects/01_vc_dash_path.png Binary files differnew file mode 100644 index 00000000000..7868b9a4ed5 --- /dev/null +++ b/chromium/skia/ext/data/vectorcanvastest/patheffects/01_vc_dash_path.png diff --git a/chromium/skia/ext/data/vectorcanvastest/patheffects/02_pc_dash_rect.png b/chromium/skia/ext/data/vectorcanvastest/patheffects/02_pc_dash_rect.png Binary files differnew file mode 100644 index 00000000000..04f2cebae2c --- /dev/null +++ b/chromium/skia/ext/data/vectorcanvastest/patheffects/02_pc_dash_rect.png diff --git a/chromium/skia/ext/data/vectorcanvastest/patheffects/02_vc_dash_rect.png b/chromium/skia/ext/data/vectorcanvastest/patheffects/02_vc_dash_rect.png Binary files differnew file mode 100644 index 00000000000..5344eee2e86 --- /dev/null +++ b/chromium/skia/ext/data/vectorcanvastest/patheffects/02_vc_dash_rect.png diff --git a/chromium/skia/ext/data/vectorcanvastest/patheffects/03_pc_circle.png b/chromium/skia/ext/data/vectorcanvastest/patheffects/03_pc_circle.png Binary files differnew file mode 100644 index 00000000000..4c267ef765d --- /dev/null +++ b/chromium/skia/ext/data/vectorcanvastest/patheffects/03_pc_circle.png diff --git a/chromium/skia/ext/data/vectorcanvastest/patheffects/03_vc_circle.png b/chromium/skia/ext/data/vectorcanvastest/patheffects/03_vc_circle.png Binary files differnew file mode 100644 index 00000000000..46ac35d05ba --- /dev/null +++ b/chromium/skia/ext/data/vectorcanvastest/patheffects/03_vc_circle.png diff --git a/chromium/skia/ext/data/vectorcanvastest/pathorientation/00_pc_drawpath_ltr.png b/chromium/skia/ext/data/vectorcanvastest/pathorientation/00_pc_drawpath_ltr.png Binary files differnew file mode 100644 index 00000000000..cefbf8700b3 --- /dev/null +++ b/chromium/skia/ext/data/vectorcanvastest/pathorientation/00_pc_drawpath_ltr.png diff --git a/chromium/skia/ext/data/vectorcanvastest/pathorientation/00_vc_drawpath_ltr.png b/chromium/skia/ext/data/vectorcanvastest/pathorientation/00_vc_drawpath_ltr.png Binary files differnew file mode 100644 index 00000000000..cefbf8700b3 --- /dev/null +++ b/chromium/skia/ext/data/vectorcanvastest/pathorientation/00_vc_drawpath_ltr.png diff --git a/chromium/skia/ext/data/vectorcanvastest/pathorientation/01_pc_drawpath_rtl.png b/chromium/skia/ext/data/vectorcanvastest/pathorientation/01_pc_drawpath_rtl.png Binary files differnew file mode 100644 index 00000000000..7bcd99885a8 --- /dev/null +++ b/chromium/skia/ext/data/vectorcanvastest/pathorientation/01_pc_drawpath_rtl.png diff --git a/chromium/skia/ext/data/vectorcanvastest/pathorientation/01_vc_drawpath_rtl.png b/chromium/skia/ext/data/vectorcanvastest/pathorientation/01_vc_drawpath_rtl.png Binary files differnew file mode 100644 index 00000000000..46c9b0abe18 --- /dev/null +++ b/chromium/skia/ext/data/vectorcanvastest/pathorientation/01_vc_drawpath_rtl.png diff --git a/chromium/skia/ext/google_logging.cc b/chromium/skia/ext/google_logging.cc new file mode 100644 index 00000000000..673e23ad00c --- /dev/null +++ b/chromium/skia/ext/google_logging.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. + +// This file provides integration with Google-style "base/logging.h" assertions +// for Skia SkASSERT. If you don't want this, you can link with another file +// that provides integration with the logging of your choice. + +#include "base/logging.h" +#include "base/strings/stringprintf.h" +#include "third_party/skia/include/core/SkTypes.h" + +void SkDebugf_FileLine(const char* file, int line, bool fatal, + const char* format, ...) { + va_list ap; + va_start(ap, format); + + std::string msg; + base::StringAppendV(&msg, format, ap); + va_end(ap); + + logging::LogMessage(file, line, + fatal ? logging::LOG_FATAL : logging::LOG_INFO).stream() + << msg; +} diff --git a/chromium/skia/ext/image_operations.cc b/chromium/skia/ext/image_operations.cc new file mode 100644 index 00000000000..c61da0cfa53 --- /dev/null +++ b/chromium/skia/ext/image_operations.cc @@ -0,0 +1,544 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#define _USE_MATH_DEFINES +#include <algorithm> +#include <cmath> +#include <limits> + +#include "skia/ext/image_operations.h" + +// TODO(pkasting): skia/ext should not depend on base/! +#include "base/containers/stack_container.h" +#include "base/debug/trace_event.h" +#include "base/logging.h" +#include "base/metrics/histogram.h" +#include "base/time/time.h" +#include "build/build_config.h" +#include "skia/ext/convolver.h" +#include "third_party/skia/include/core/SkColorPriv.h" +#include "third_party/skia/include/core/SkFontHost.h" +#include "third_party/skia/include/core/SkRect.h" + +namespace skia { + +namespace { + +// Returns the ceiling/floor as an integer. +inline int CeilInt(float val) { + return static_cast<int>(ceil(val)); +} +inline int FloorInt(float val) { + return static_cast<int>(floor(val)); +} + +// Filter function computation ------------------------------------------------- + +// Evaluates the box filter, which goes from -0.5 to +0.5. +float EvalBox(float x) { + return (x >= -0.5f && x < 0.5f) ? 1.0f : 0.0f; +} + +// Evaluates the Lanczos filter of the given filter size window for the given +// position. +// +// |filter_size| is the width of the filter (the "window"), outside of which +// the value of the function is 0. Inside of the window, the value is the +// normalized sinc function: +// lanczos(x) = sinc(x) * sinc(x / filter_size); +// where +// sinc(x) = sin(pi*x) / (pi*x); +float EvalLanczos(int filter_size, float x) { + if (x <= -filter_size || x >= filter_size) + return 0.0f; // Outside of the window. + if (x > -std::numeric_limits<float>::epsilon() && + x < std::numeric_limits<float>::epsilon()) + return 1.0f; // Special case the discontinuity at the origin. + float xpi = x * static_cast<float>(M_PI); + return (sin(xpi) / xpi) * // sinc(x) + sin(xpi / filter_size) / (xpi / filter_size); // sinc(x/filter_size) +} + +// Evaluates the Hamming filter of the given filter size window for the given +// position. +// +// The filter covers [-filter_size, +filter_size]. Outside of this window +// the value of the function is 0. Inside of the window, the value is sinus +// cardinal multiplied by a recentered Hamming function. The traditional +// Hamming formula for a window of size N and n ranging in [0, N-1] is: +// hamming(n) = 0.54 - 0.46 * cos(2 * pi * n / (N-1))) +// In our case we want the function centered for x == 0 and at its minimum +// on both ends of the window (x == +/- filter_size), hence the adjusted +// formula: +// hamming(x) = (0.54 - +// 0.46 * cos(2 * pi * (x - filter_size)/ (2 * filter_size))) +// = 0.54 - 0.46 * cos(pi * x / filter_size - pi) +// = 0.54 + 0.46 * cos(pi * x / filter_size) +float EvalHamming(int filter_size, float x) { + if (x <= -filter_size || x >= filter_size) + return 0.0f; // Outside of the window. + if (x > -std::numeric_limits<float>::epsilon() && + x < std::numeric_limits<float>::epsilon()) + return 1.0f; // Special case the sinc discontinuity at the origin. + const float xpi = x * static_cast<float>(M_PI); + + return ((sin(xpi) / xpi) * // sinc(x) + (0.54f + 0.46f * cos(xpi / filter_size))); // hamming(x) +} + +// ResizeFilter ---------------------------------------------------------------- + +// Encapsulates computation and storage of the filters required for one complete +// resize operation. +class ResizeFilter { + public: + ResizeFilter(ImageOperations::ResizeMethod method, + int src_full_width, int src_full_height, + int dest_width, int dest_height, + const SkIRect& dest_subset); + + // Returns the filled filter values. + const ConvolutionFilter1D& x_filter() { return x_filter_; } + const ConvolutionFilter1D& y_filter() { return y_filter_; } + + private: + // Returns the number of pixels that the filer spans, in filter space (the + // destination image). + float GetFilterSupport(float scale) { + switch (method_) { + case ImageOperations::RESIZE_BOX: + // The box filter just scales with the image scaling. + return 0.5f; // Only want one side of the filter = /2. + case ImageOperations::RESIZE_HAMMING1: + // The Hamming filter takes as much space in the source image in + // each direction as the size of the window = 1 for Hamming1. + return 1.0f; + case ImageOperations::RESIZE_LANCZOS2: + // The Lanczos filter takes as much space in the source image in + // each direction as the size of the window = 2 for Lanczos2. + return 2.0f; + case ImageOperations::RESIZE_LANCZOS3: + // The Lanczos filter takes as much space in the source image in + // each direction as the size of the window = 3 for Lanczos3. + return 3.0f; + default: + NOTREACHED(); + return 1.0f; + } + } + + // Computes one set of filters either horizontally or vertically. The caller + // will specify the "min" and "max" rather than the bottom/top and + // right/bottom so that the same code can be re-used in each dimension. + // + // |src_depend_lo| and |src_depend_size| gives the range for the source + // depend rectangle (horizontally or vertically at the caller's discretion + // -- see above for what this means). + // + // Likewise, the range of destination values to compute and the scale factor + // for the transform is also specified. + void ComputeFilters(int src_size, + int dest_subset_lo, int dest_subset_size, + float scale, + ConvolutionFilter1D* output); + + // Computes the filter value given the coordinate in filter space. + inline float ComputeFilter(float pos) { + switch (method_) { + case ImageOperations::RESIZE_BOX: + return EvalBox(pos); + case ImageOperations::RESIZE_HAMMING1: + return EvalHamming(1, pos); + case ImageOperations::RESIZE_LANCZOS2: + return EvalLanczos(2, pos); + case ImageOperations::RESIZE_LANCZOS3: + return EvalLanczos(3, pos); + default: + NOTREACHED(); + return 0; + } + } + + ImageOperations::ResizeMethod method_; + + // Size of the filter support on one side only in the destination space. + // See GetFilterSupport. + float x_filter_support_; + float y_filter_support_; + + // Subset of scaled destination bitmap to compute. + SkIRect out_bounds_; + + ConvolutionFilter1D x_filter_; + ConvolutionFilter1D y_filter_; + + DISALLOW_COPY_AND_ASSIGN(ResizeFilter); +}; + +ResizeFilter::ResizeFilter(ImageOperations::ResizeMethod method, + int src_full_width, int src_full_height, + int dest_width, int dest_height, + const SkIRect& dest_subset) + : method_(method), + out_bounds_(dest_subset) { + // method_ will only ever refer to an "algorithm method". + SkASSERT((ImageOperations::RESIZE_FIRST_ALGORITHM_METHOD <= method) && + (method <= ImageOperations::RESIZE_LAST_ALGORITHM_METHOD)); + + float scale_x = static_cast<float>(dest_width) / + static_cast<float>(src_full_width); + float scale_y = static_cast<float>(dest_height) / + static_cast<float>(src_full_height); + + ComputeFilters(src_full_width, dest_subset.fLeft, dest_subset.width(), + scale_x, &x_filter_); + ComputeFilters(src_full_height, dest_subset.fTop, dest_subset.height(), + scale_y, &y_filter_); +} + +// TODO(egouriou): Take advantage of periods in the convolution. +// Practical resizing filters are periodic outside of the border area. +// For Lanczos, a scaling by a (reduced) factor of p/q (q pixels in the +// source become p pixels in the destination) will have a period of p. +// A nice consequence is a period of 1 when downscaling by an integral +// factor. Downscaling from typical display resolutions is also bound +// to produce interesting periods as those are chosen to have multiple +// small factors. +// Small periods reduce computational load and improve cache usage if +// the coefficients can be shared. For periods of 1 we can consider +// loading the factors only once outside the borders. +void ResizeFilter::ComputeFilters(int src_size, + int dest_subset_lo, int dest_subset_size, + float scale, + ConvolutionFilter1D* output) { + int dest_subset_hi = dest_subset_lo + dest_subset_size; // [lo, hi) + + // When we're doing a magnification, the scale will be larger than one. This + // means the destination pixels are much smaller than the source pixels, and + // that the range covered by the filter won't necessarily cover any source + // pixel boundaries. Therefore, we use these clamped values (max of 1) for + // some computations. + float clamped_scale = std::min(1.0f, scale); + + // This is how many source pixels from the center we need to count + // to support the filtering function. + float src_support = GetFilterSupport(clamped_scale) / clamped_scale; + + // Speed up the divisions below by turning them into multiplies. + float inv_scale = 1.0f / scale; + + base::StackVector<float, 64> filter_values; + base::StackVector<int16, 64> fixed_filter_values; + + // Loop over all pixels in the output range. We will generate one set of + // filter values for each one. Those values will tell us how to blend the + // source pixels to compute the destination pixel. + for (int dest_subset_i = dest_subset_lo; dest_subset_i < dest_subset_hi; + dest_subset_i++) { + // Reset the arrays. We don't declare them inside so they can re-use the + // same malloc-ed buffer. + filter_values->clear(); + fixed_filter_values->clear(); + + // This is the pixel in the source directly under the pixel in the dest. + // Note that we base computations on the "center" of the pixels. To see + // why, observe that the destination pixel at coordinates (0, 0) in a 5.0x + // downscale should "cover" the pixels around the pixel with *its center* + // at coordinates (2.5, 2.5) in the source, not those around (0, 0). + // Hence we need to scale coordinates (0.5, 0.5), not (0, 0). + float src_pixel = (static_cast<float>(dest_subset_i) + 0.5f) * inv_scale; + + // Compute the (inclusive) range of source pixels the filter covers. + int src_begin = std::max(0, FloorInt(src_pixel - src_support)); + int src_end = std::min(src_size - 1, CeilInt(src_pixel + src_support)); + + // Compute the unnormalized filter value at each location of the source + // it covers. + float filter_sum = 0.0f; // Sub of the filter values for normalizing. + for (int cur_filter_pixel = src_begin; cur_filter_pixel <= src_end; + cur_filter_pixel++) { + // Distance from the center of the filter, this is the filter coordinate + // in source space. We also need to consider the center of the pixel + // when comparing distance against 'src_pixel'. In the 5x downscale + // example used above the distance from the center of the filter to + // the pixel with coordinates (2, 2) should be 0, because its center + // is at (2.5, 2.5). + float src_filter_dist = + ((static_cast<float>(cur_filter_pixel) + 0.5f) - src_pixel); + + // Since the filter really exists in dest space, map it there. + float dest_filter_dist = src_filter_dist * clamped_scale; + + // Compute the filter value at that location. + float filter_value = ComputeFilter(dest_filter_dist); + filter_values->push_back(filter_value); + + filter_sum += filter_value; + } + DCHECK(!filter_values->empty()) << "We should always get a filter!"; + + // The filter must be normalized so that we don't affect the brightness of + // the image. Convert to normalized fixed point. + int16 fixed_sum = 0; + for (size_t i = 0; i < filter_values->size(); i++) { + int16 cur_fixed = output->FloatToFixed(filter_values[i] / filter_sum); + fixed_sum += cur_fixed; + fixed_filter_values->push_back(cur_fixed); + } + + // The conversion to fixed point will leave some rounding errors, which + // we add back in to avoid affecting the brightness of the image. We + // arbitrarily add this to the center of the filter array (this won't always + // be the center of the filter function since it could get clipped on the + // edges, but it doesn't matter enough to worry about that case). + int16 leftovers = output->FloatToFixed(1.0f) - fixed_sum; + fixed_filter_values[fixed_filter_values->size() / 2] += leftovers; + + // Now it's ready to go. + output->AddFilter(src_begin, &fixed_filter_values[0], + static_cast<int>(fixed_filter_values->size())); + } + + output->PaddingForSIMD(); +} + +ImageOperations::ResizeMethod ResizeMethodToAlgorithmMethod( + ImageOperations::ResizeMethod method) { + // Convert any "Quality Method" into an "Algorithm Method" + if (method >= ImageOperations::RESIZE_FIRST_ALGORITHM_METHOD && + method <= ImageOperations::RESIZE_LAST_ALGORITHM_METHOD) { + return method; + } + // The call to ImageOperationsGtv::Resize() above took care of + // GPU-acceleration in the cases where it is possible. So now we just + // pick the appropriate software method for each resize quality. + switch (method) { + // Users of RESIZE_GOOD are willing to trade a lot of quality to + // get speed, allowing the use of linear resampling to get hardware + // acceleration (SRB). Hence any of our "good" software filters + // will be acceptable, and we use the fastest one, Hamming-1. + case ImageOperations::RESIZE_GOOD: + // Users of RESIZE_BETTER are willing to trade some quality in order + // to improve performance, but are guaranteed not to devolve to a linear + // resampling. In visual tests we see that Hamming-1 is not as good as + // Lanczos-2, however it is about 40% faster and Lanczos-2 itself is + // about 30% faster than Lanczos-3. The use of Hamming-1 has been deemed + // an acceptable trade-off between quality and speed. + case ImageOperations::RESIZE_BETTER: + return ImageOperations::RESIZE_HAMMING1; + default: + return ImageOperations::RESIZE_LANCZOS3; + } +} + +} // namespace + +// Resize ---------------------------------------------------------------------- + +// static +SkBitmap ImageOperations::Resize(const SkBitmap& source, + ResizeMethod method, + int dest_width, int dest_height, + const SkIRect& dest_subset, + SkBitmap::Allocator* allocator) { + if (method == ImageOperations::RESIZE_SUBPIXEL) { + return ResizeSubpixel(source, dest_width, dest_height, + dest_subset, allocator); + } else { + return ResizeBasic(source, method, dest_width, dest_height, dest_subset, + allocator); + } +} + +// static +SkBitmap ImageOperations::ResizeSubpixel(const SkBitmap& source, + int dest_width, int dest_height, + const SkIRect& dest_subset, + SkBitmap::Allocator* allocator) { + TRACE_EVENT2("skia", "ImageOperations::ResizeSubpixel", + "src_pixels", source.width()*source.height(), + "dst_pixels", dest_width*dest_height); + // Currently only works on Linux/BSD because these are the only platforms + // where SkFontHost::GetSubpixelOrder is defined. +#if defined(OS_LINUX) && !defined(GTV) + // Understand the display. + const SkFontHost::LCDOrder order = SkFontHost::GetSubpixelOrder(); + const SkFontHost::LCDOrientation orientation = + SkFontHost::GetSubpixelOrientation(); + + // Decide on which dimension, if any, to deploy subpixel rendering. + int w = 1; + int h = 1; + switch (orientation) { + case SkFontHost::kHorizontal_LCDOrientation: + w = dest_width < source.width() ? 3 : 1; + break; + case SkFontHost::kVertical_LCDOrientation: + h = dest_height < source.height() ? 3 : 1; + break; + } + + // Resize the image. + const int width = dest_width * w; + const int height = dest_height * h; + SkIRect subset = { dest_subset.fLeft, dest_subset.fTop, + dest_subset.fLeft + dest_subset.width() * w, + dest_subset.fTop + dest_subset.height() * h }; + SkBitmap img = ResizeBasic(source, ImageOperations::RESIZE_LANCZOS3, width, + height, subset, allocator); + const int row_words = img.rowBytes() / 4; + if (w == 1 && h == 1) + return img; + + // Render into subpixels. + SkBitmap result; + result.setConfig(SkBitmap::kARGB_8888_Config, dest_subset.width(), + dest_subset.height()); + result.allocPixels(allocator, NULL); + if (!result.readyToDraw()) + return img; + + SkAutoLockPixels locker(img); + if (!img.readyToDraw()) + return img; + + uint32* src_row = img.getAddr32(0, 0); + uint32* dst_row = result.getAddr32(0, 0); + for (int y = 0; y < dest_subset.height(); y++) { + uint32* src = src_row; + uint32* dst = dst_row; + for (int x = 0; x < dest_subset.width(); x++, src += w, dst++) { + uint8 r = 0, g = 0, b = 0, a = 0; + switch (order) { + case SkFontHost::kRGB_LCDOrder: + switch (orientation) { + case SkFontHost::kHorizontal_LCDOrientation: + r = SkGetPackedR32(src[0]); + g = SkGetPackedG32(src[1]); + b = SkGetPackedB32(src[2]); + a = SkGetPackedA32(src[1]); + break; + case SkFontHost::kVertical_LCDOrientation: + r = SkGetPackedR32(src[0 * row_words]); + g = SkGetPackedG32(src[1 * row_words]); + b = SkGetPackedB32(src[2 * row_words]); + a = SkGetPackedA32(src[1 * row_words]); + break; + } + break; + case SkFontHost::kBGR_LCDOrder: + switch (orientation) { + case SkFontHost::kHorizontal_LCDOrientation: + b = SkGetPackedB32(src[0]); + g = SkGetPackedG32(src[1]); + r = SkGetPackedR32(src[2]); + a = SkGetPackedA32(src[1]); + break; + case SkFontHost::kVertical_LCDOrientation: + b = SkGetPackedB32(src[0 * row_words]); + g = SkGetPackedG32(src[1 * row_words]); + r = SkGetPackedR32(src[2 * row_words]); + a = SkGetPackedA32(src[1 * row_words]); + break; + } + break; + case SkFontHost::kNONE_LCDOrder: + NOTREACHED(); + } + // Premultiplied alpha is very fragile. + a = a > r ? a : r; + a = a > g ? a : g; + a = a > b ? a : b; + *dst = SkPackARGB32(a, r, g, b); + } + src_row += h * row_words; + dst_row += result.rowBytes() / 4; + } + result.setIsOpaque(img.isOpaque()); + return result; +#else + return SkBitmap(); +#endif // OS_POSIX && !OS_MACOSX && !defined(OS_ANDROID) +} + +// static +SkBitmap ImageOperations::ResizeBasic(const SkBitmap& source, + ResizeMethod method, + int dest_width, int dest_height, + const SkIRect& dest_subset, + SkBitmap::Allocator* allocator) { + TRACE_EVENT2("skia", "ImageOperations::ResizeBasic", + "src_pixels", source.width()*source.height(), + "dst_pixels", dest_width*dest_height); + // Ensure that the ResizeMethod enumeration is sound. + SkASSERT(((RESIZE_FIRST_QUALITY_METHOD <= method) && + (method <= RESIZE_LAST_QUALITY_METHOD)) || + ((RESIZE_FIRST_ALGORITHM_METHOD <= method) && + (method <= RESIZE_LAST_ALGORITHM_METHOD))); + + // Time how long this takes to see if it's a problem for users. + base::TimeTicks resize_start = base::TimeTicks::Now(); + + SkIRect dest = { 0, 0, dest_width, dest_height }; + DCHECK(dest.contains(dest_subset)) << + "The supplied subset does not fall within the destination image."; + + // If the size of source or destination is 0, i.e. 0x0, 0xN or Nx0, just + // return empty. + if (source.width() < 1 || source.height() < 1 || + dest_width < 1 || dest_height < 1) + return SkBitmap(); + + method = ResizeMethodToAlgorithmMethod(method); + // Check that we deal with an "algorithm methods" from this point onward. + SkASSERT((ImageOperations::RESIZE_FIRST_ALGORITHM_METHOD <= method) && + (method <= ImageOperations::RESIZE_LAST_ALGORITHM_METHOD)); + + SkAutoLockPixels locker(source); + if (!source.readyToDraw() || source.config() != SkBitmap::kARGB_8888_Config) + return SkBitmap(); + + ResizeFilter filter(method, source.width(), source.height(), + dest_width, dest_height, dest_subset); + + // Get a source bitmap encompassing this touched area. We construct the + // offsets and row strides such that it looks like a new bitmap, while + // referring to the old data. + const uint8* source_subset = + reinterpret_cast<const uint8*>(source.getPixels()); + + // Convolve into the result. + SkBitmap result; + result.setConfig(SkBitmap::kARGB_8888_Config, + dest_subset.width(), dest_subset.height()); + result.allocPixels(allocator, NULL); + if (!result.readyToDraw()) + return SkBitmap(); + + BGRAConvolve2D(source_subset, static_cast<int>(source.rowBytes()), + !source.isOpaque(), filter.x_filter(), filter.y_filter(), + static_cast<int>(result.rowBytes()), + static_cast<unsigned char*>(result.getPixels()), + true); + + // Preserve the "opaque" flag for use as an optimization later. + result.setIsOpaque(source.isOpaque()); + + base::TimeDelta delta = base::TimeTicks::Now() - resize_start; + UMA_HISTOGRAM_TIMES("Image.ResampleMS", delta); + + return result; +} + +// static +SkBitmap ImageOperations::Resize(const SkBitmap& source, + ResizeMethod method, + int dest_width, int dest_height, + SkBitmap::Allocator* allocator) { + SkIRect dest_subset = { 0, 0, dest_width, dest_height }; + return Resize(source, method, dest_width, dest_height, dest_subset, + allocator); +} + +} // namespace skia diff --git a/chromium/skia/ext/image_operations.h b/chromium/skia/ext/image_operations.h new file mode 100644 index 00000000000..eff89123c6b --- /dev/null +++ b/chromium/skia/ext/image_operations.h @@ -0,0 +1,133 @@ +// 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 SKIA_EXT_IMAGE_OPERATIONS_H_ +#define SKIA_EXT_IMAGE_OPERATIONS_H_ + +#include "third_party/skia/include/core/SkBitmap.h" +#include "third_party/skia/include/core/SkTypes.h" + +struct SkIRect; + +namespace skia { + +class SK_API ImageOperations { + public: + enum ResizeMethod { + // + // Quality Methods + // + // Those enumeration values express a desired quality/speed tradeoff. + // They are translated into an algorithm-specific method that depends + // on the capabilities (CPU, GPU) of the underlying platform. + // It is possible for all three methods to be mapped to the same + // algorithm on a given platform. + + // Good quality resizing. Fastest resizing with acceptable visual quality. + // This is typically intended for use during interactive layouts + // where slower platforms may want to trade image quality for large + // increase in resizing performance. + // + // For example the resizing implementation may devolve to linear + // filtering if this enables GPU acceleration to be used. + // + // Note that the underlying resizing method may be determined + // on the fly based on the parameters for a given resize call. + // For example an implementation using a GPU-based linear filter + // in the common case may still use a higher-quality software-based + // filter in cases where using the GPU would actually be slower - due + // to too much latency - or impossible - due to image format or size + // constraints. + RESIZE_GOOD, + + // Medium quality resizing. Close to high quality resizing (better + // than linear interpolation) with potentially some quality being + // traded-off for additional speed compared to RESIZE_BEST. + // + // This is intended, for example, for generation of large thumbnails + // (hundreds of pixels in each dimension) from large sources, where + // a linear filter would produce too many artifacts but where + // a RESIZE_HIGH might be too costly time-wise. + RESIZE_BETTER, + + // High quality resizing. The algorithm is picked to favor image quality. + RESIZE_BEST, + + // + // Algorithm-specific enumerations + // + + // Box filter. This is a weighted average of all of the pixels touching + // the destination pixel. For enlargement, this is nearest neighbor. + // + // You probably don't want this, it is here for testing since it is easy to + // compute. Use RESIZE_LANCZOS3 instead. + RESIZE_BOX, + + // 1-cycle Hamming filter. This is tall is the middle and falls off towards + // the window edges but without going to 0. This is about 40% faster than + // a 2-cycle Lanczos. + RESIZE_HAMMING1, + + // 2-cycle Lanczos filter. This is tall in the middle, goes negative on + // each side, then returns to zero. Does not provide as good a frequency + // response as a 3-cycle Lanczos but is roughly 30% faster. + RESIZE_LANCZOS2, + + // 3-cycle Lanczos filter. This is tall in the middle, goes negative on + // each side, then oscillates 2 more times. It gives nice sharp edges. + RESIZE_LANCZOS3, + + // Lanczos filter + subpixel interpolation. If subpixel rendering is not + // appropriate we automatically fall back to Lanczos. + RESIZE_SUBPIXEL, + + // enum aliases for first and last methods by algorithm or by quality. + RESIZE_FIRST_QUALITY_METHOD = RESIZE_GOOD, + RESIZE_LAST_QUALITY_METHOD = RESIZE_BEST, + RESIZE_FIRST_ALGORITHM_METHOD = RESIZE_BOX, + RESIZE_LAST_ALGORITHM_METHOD = RESIZE_SUBPIXEL, + }; + + // Resizes the given source bitmap using the specified resize method, so that + // the entire image is (dest_size) big. The dest_subset is the rectangle in + // this destination image that should actually be returned. + // + // The output image will be (dest_subset.width(), dest_subset.height()). This + // will save work if you do not need the entire bitmap. + // + // The destination subset must be smaller than the destination image. + static SkBitmap Resize(const SkBitmap& source, + ResizeMethod method, + int dest_width, int dest_height, + const SkIRect& dest_subset, + SkBitmap::Allocator* allocator = NULL); + + // Alternate version for resizing and returning the entire bitmap rather than + // a subset. + static SkBitmap Resize(const SkBitmap& source, + ResizeMethod method, + int dest_width, int dest_height, + SkBitmap::Allocator* allocator = NULL); + + private: + ImageOperations(); // Class for scoping only. + + // Supports all methods except RESIZE_SUBPIXEL. + static SkBitmap ResizeBasic(const SkBitmap& source, + ResizeMethod method, + int dest_width, int dest_height, + const SkIRect& dest_subset, + SkBitmap::Allocator* allocator = NULL); + + // Subpixel renderer. + static SkBitmap ResizeSubpixel(const SkBitmap& source, + int dest_width, int dest_height, + const SkIRect& dest_subset, + SkBitmap::Allocator* allocator = NULL); +}; + +} // namespace skia + +#endif // SKIA_EXT_IMAGE_OPERATIONS_H_ diff --git a/chromium/skia/ext/image_operations_bench.cc b/chromium/skia/ext/image_operations_bench.cc new file mode 100644 index 00000000000..11263f3d788 --- /dev/null +++ b/chromium/skia/ext/image_operations_bench.cc @@ -0,0 +1,293 @@ +// 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. + +// This small program is used to measure the performance of the various +// resize algorithms offered by the ImageOperations::Resize function. +// It will generate an empty source bitmap, and rescale it to specified +// dimensions. It will repeat this operation multiple time to get more accurate +// average throughput. Because it uses elapsed time to do its math, it is only +// accurate on an idle system (but that approach was deemed more accurate +// than the use of the times() call. +// To present a single number in MB/s, it calculates the 'speed' by taking +// source surface + destination surface and dividing by the elapsed time. +// This number is somewhat reasonable way to measure this, given our current +// implementation which somewhat scales this way. + +#include <stdio.h> + +#include "base/basictypes.h" +#include "base/command_line.h" +#include "base/format_macros.h" +#include "base/strings/string_number_conversions.h" +#include "base/strings/string_split.h" +#include "base/strings/string_util.h" +#include "base/strings/utf_string_conversions.h" +#include "base/time/time.h" +#include "skia/ext/image_operations.h" +#include "third_party/skia/include/core/SkBitmap.h" +#include "third_party/skia/include/core/SkRect.h" + +namespace { + +struct StringMethodPair { + const char* name; + skia::ImageOperations::ResizeMethod method; +}; +#define ADD_METHOD(x) { #x, skia::ImageOperations::RESIZE_##x } +const StringMethodPair resize_methods[] = { + ADD_METHOD(GOOD), + ADD_METHOD(BETTER), + ADD_METHOD(BEST), + ADD_METHOD(BOX), + ADD_METHOD(HAMMING1), + ADD_METHOD(LANCZOS2), + ADD_METHOD(LANCZOS3), + ADD_METHOD(SUBPIXEL) +}; + +// converts a string into one of the image operation method to resize. +// Returns true on success, false otherwise. +bool StringToMethod(const std::string& arg, + skia::ImageOperations::ResizeMethod* method) { + for (size_t i = 0; i < arraysize(resize_methods); ++i) { + if (base::strcasecmp(arg.c_str(), resize_methods[i].name) == 0) { + *method = resize_methods[i].method; + return true; + } + } + return false; +} + +const char* MethodToString(skia::ImageOperations::ResizeMethod method) { + for (size_t i = 0; i < arraysize(resize_methods); ++i) { + if (method == resize_methods[i].method) { + return resize_methods[i].name; + } + } + return "unknown"; +} + +// Prints all supported resize methods +void PrintMethods() { + bool print_comma = false; + for (size_t i = 0; i < arraysize(resize_methods); ++i) { + if (print_comma) { + printf(","); + } else { + print_comma = true; + } + printf(" %s", resize_methods[i].name); + } +} + +// Returns the number of bytes that the bitmap has. This number is different +// from what SkBitmap::getSize() returns since it does not take into account +// the stride. The difference between the stride and the width can be large +// because of the alignment constraints on bitmaps created for SRB scaling +// (32 pixels) as seen on GTV platforms. Using this metric instead of the +// getSize seemed to be a more accurate representation of the work done (even +// though in terms of memory bandwidth that might be similar because of the +// cache line size). +int GetBitmapSize(const SkBitmap* bitmap) { + return bitmap->height() * bitmap->bytesPerPixel() * bitmap->width(); +} + +// Simple class to represent dimensions of a bitmap (width, height). +class Dimensions { + public: + Dimensions() + : width_(0), + height_(0) {} + + void set(int w, int h) { + width_ = w; + height_ = h; + } + + int width() const { + return width_; + } + + int height() const { + return height_; + } + + bool IsValid() const { + return (width_ > 0 && height_ > 0); + } + + // On failure, will set its state in such a way that IsValid will return + // false. + void FromString(const std::string& arg) { + std::vector<std::string> strings; + base::SplitString(std::string(arg), 'x', &strings); + if (strings.size() != 2 || + base::StringToInt(strings[0], &width_) == false || + base::StringToInt(strings[1], &height_) == false) { + width_ = -1; // force the dimension object to be invalid. + } + } + private: + int width_; + int height_; +}; + +// main class used for the benchmarking. +class Benchmark { + public: + static const int kDefaultNumberIterations; + static const skia::ImageOperations::ResizeMethod kDefaultResizeMethod; + + Benchmark() + : num_iterations_(kDefaultNumberIterations), + method_(kDefaultResizeMethod) {} + + // Returns true if command line parsing was successful, false otherwise. + bool ParseArgs(const CommandLine* command_line); + + // Returns true if successful, false otherwise. + bool Run() const; + + static void Usage(); + private: + int num_iterations_; + skia::ImageOperations::ResizeMethod method_; + Dimensions source_; + Dimensions dest_; +}; + +// static +const int Benchmark::kDefaultNumberIterations = 1024; +const skia::ImageOperations::ResizeMethod Benchmark::kDefaultResizeMethod = + skia::ImageOperations::RESIZE_LANCZOS3; + +// argument management +void Benchmark::Usage() { + printf("image_operations_bench -source wxh -destination wxh " + "[-iterations i] [-method m] [-help]\n" + " -source wxh: specify source width and height\n" + " -destination wxh: specify destination width and height\n" + " -iter i: perform i iterations (default:%d)\n" + " -method m: use method m (default:%s), which can be:", + Benchmark::kDefaultNumberIterations, + MethodToString(Benchmark::kDefaultResizeMethod)); + PrintMethods(); + printf("\n -help: prints this help and exits\n"); +} + +bool Benchmark::ParseArgs(const CommandLine* command_line) { + const CommandLine::SwitchMap& switches = command_line->GetSwitches(); + bool fNeedHelp = false; + + for (CommandLine::SwitchMap::const_iterator iter = switches.begin(); + iter != switches.end(); + ++iter) { + const std::string& s = iter->first; + std::string value; +#if defined(OS_WIN) + value = WideToUTF8(iter->second); +#else + value = iter->second; +#endif + if (s == "source") { + source_.FromString(value); + } else if (s == "destination") { + dest_.FromString(value); + } else if (s == "iterations") { + if (base::StringToInt(value, &num_iterations_) == false) { + fNeedHelp = true; + } + } else if (s == "method") { + if (!StringToMethod(value, &method_)) { + printf("Invalid method '%s' specified\n", value.c_str()); + fNeedHelp = true; + } + } else { + fNeedHelp = true; + } + } + + if (num_iterations_ <= 0) { + printf("Invalid number of iterations: %d\n", num_iterations_); + fNeedHelp = true; + } + if (!source_.IsValid()) { + printf("Invalid source dimensions specified\n"); + fNeedHelp = true; + } + if (!dest_.IsValid()) { + printf("Invalid dest dimensions specified\n"); + fNeedHelp = true; + } + if (fNeedHelp == true) { + return false; + } + return true; +} + +// actual benchmark. +bool Benchmark::Run() const { + SkBitmap source; + source.setConfig(SkBitmap::kARGB_8888_Config, + source_.width(), source_.height()); + source.allocPixels(); + source.eraseARGB(0, 0, 0, 0); + + SkBitmap dest; + + const base::TimeTicks start = base::TimeTicks::Now(); + + for (int i = 0; i < num_iterations_; ++i) { + dest = skia::ImageOperations::Resize(source, + method_, + dest_.width(), dest_.height()); + } + + const int64 elapsed_us = (base::TimeTicks::Now() - start).InMicroseconds(); + + const uint64 num_bytes = static_cast<uint64>(num_iterations_) * + (GetBitmapSize(&source) + GetBitmapSize(&dest)); + + printf("%" PRIu64 " MB/s,\telapsed = %" PRIu64 " source=%d dest=%d\n", + static_cast<uint64>(elapsed_us == 0 ? 0 : num_bytes / elapsed_us), + static_cast<uint64>(elapsed_us), + GetBitmapSize(&source), GetBitmapSize(&dest)); + + return true; +} + +// A small class to automatically call Reset on the global command line to +// avoid nasty valgrind complaints for the leak of the global command line. +class CommandLineAutoReset { + public: + CommandLineAutoReset(int argc, char** argv) { + CommandLine::Init(argc, argv); + } + ~CommandLineAutoReset() { + CommandLine::Reset(); + } + + const CommandLine* Get() const { + return CommandLine::ForCurrentProcess(); + } +}; + +} // namespace + +int main(int argc, char** argv) { + Benchmark bench; + CommandLineAutoReset command_line(argc, argv); + + if (!bench.ParseArgs(command_line.Get())) { + Benchmark::Usage(); + return 1; + } + + if (!bench.Run()) { + printf("Failed to run benchmark\n"); + return 1; + } + + return 0; +} diff --git a/chromium/skia/ext/image_operations_unittest.cc b/chromium/skia/ext/image_operations_unittest.cc new file mode 100644 index 00000000000..c7069e28172 --- /dev/null +++ b/chromium/skia/ext/image_operations_unittest.cc @@ -0,0 +1,731 @@ +// Copyright (c) 2012 The Chromium Authors. 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 <iomanip> +#include <vector> + +#include "base/basictypes.h" +#include "base/compiler_specific.h" +#include "base/file_util.h" +#include "base/strings/string_util.h" +#include "skia/ext/image_operations.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "third_party/skia/include/core/SkBitmap.h" +#include "third_party/skia/include/core/SkRect.h" +#include "ui/gfx/codec/png_codec.h" +#include "ui/gfx/size.h" + +namespace { + +// Computes the average pixel value for the given range, inclusive. +uint32_t AveragePixel(const SkBitmap& bmp, + int x_min, int x_max, + int y_min, int y_max) { + float accum[4] = {0, 0, 0, 0}; + int count = 0; + for (int y = y_min; y <= y_max; y++) { + for (int x = x_min; x <= x_max; x++) { + uint32_t cur = *bmp.getAddr32(x, y); + accum[0] += SkColorGetB(cur); + accum[1] += SkColorGetG(cur); + accum[2] += SkColorGetR(cur); + accum[3] += SkColorGetA(cur); + count++; + } + } + + return SkColorSetARGB(static_cast<unsigned char>(accum[3] / count), + static_cast<unsigned char>(accum[2] / count), + static_cast<unsigned char>(accum[1] / count), + static_cast<unsigned char>(accum[0] / count)); +} + +// Computes the average pixel (/color) value for the given colors. +SkColor AveragePixel(const SkColor colors[], size_t color_count) { + float accum[4] = { 0.0f, 0.0f, 0.0f, 0.0f }; + for (size_t i = 0; i < color_count; ++i) { + const SkColor cur = colors[i]; + accum[0] += static_cast<float>(SkColorGetA(cur)); + accum[1] += static_cast<float>(SkColorGetR(cur)); + accum[2] += static_cast<float>(SkColorGetG(cur)); + accum[3] += static_cast<float>(SkColorGetB(cur)); + } + const SkColor average_color = + SkColorSetARGB(static_cast<uint8_t>(accum[0] / color_count), + static_cast<uint8_t>(accum[1] / color_count), + static_cast<uint8_t>(accum[2] / color_count), + static_cast<uint8_t>(accum[3] / color_count)); + return average_color; +} + +void PrintPixel(const SkBitmap& bmp, + int x_min, int x_max, + int y_min, int y_max) { + char str[128]; + + for (int y = y_min; y <= y_max; ++y) { + for (int x = x_min; x <= x_max; ++x) { + const uint32_t cur = *bmp.getAddr32(x, y); + base::snprintf(str, sizeof(str), "bmp[%d,%d] = %08X", x, y, cur); + ADD_FAILURE() << str; + } + } +} + +// Returns the euclidian distance between two RGBA colors interpreted +// as 4-components vectors. +// +// Notes: +// - This is a really poor definition of color distance. Yet it +// is "good enough" for our uses here. +// - More realistic measures like the various Delta E formulas defined +// by CIE are way more complex and themselves require the RGBA to +// to transformed into CIELAB (typically via sRGB first). +// - The static_cast<int> below are needed to avoid interpreting "negative" +// differences as huge positive values. +float ColorsEuclidianDistance(const SkColor a, const SkColor b) { + int b_int_diff = static_cast<int>(SkColorGetB(a) - SkColorGetB(b)); + int g_int_diff = static_cast<int>(SkColorGetG(a) - SkColorGetG(b)); + int r_int_diff = static_cast<int>(SkColorGetR(a) - SkColorGetR(b)); + int a_int_diff = static_cast<int>(SkColorGetA(a) - SkColorGetA(b)); + + float b_float_diff = static_cast<float>(b_int_diff); + float g_float_diff = static_cast<float>(g_int_diff); + float r_float_diff = static_cast<float>(r_int_diff); + float a_float_diff = static_cast<float>(a_int_diff); + + return sqrtf((b_float_diff * b_float_diff) + (g_float_diff * g_float_diff) + + (r_float_diff * r_float_diff) + (a_float_diff * a_float_diff)); +} + +// Returns true if each channel of the given two colors are "close." This is +// used for comparing colors where rounding errors may cause off-by-one. +bool ColorsClose(uint32_t a, uint32_t b) { + return abs(static_cast<int>(SkColorGetB(a) - SkColorGetB(b))) < 2 && + abs(static_cast<int>(SkColorGetG(a) - SkColorGetG(b))) < 2 && + abs(static_cast<int>(SkColorGetR(a) - SkColorGetR(b))) < 2 && + abs(static_cast<int>(SkColorGetA(a) - SkColorGetA(b))) < 2; +} + +void FillDataToBitmap(int w, int h, SkBitmap* bmp) { + bmp->setConfig(SkBitmap::kARGB_8888_Config, w, h); + bmp->allocPixels(); + + for (int y = 0; y < h; ++y) { + for (int x = 0; x < w; ++x) { + const uint8_t component = static_cast<uint8_t>(y * w + x); + const SkColor pixel = SkColorSetARGB(component, component, + component, component); + *bmp->getAddr32(x, y) = pixel; + } + } +} + +// Draws a horizontal and vertical grid into the w x h bitmap passed in. +// Each line in the grid is drawn with a width of "grid_width" pixels, +// and those lines repeat every "grid_pitch" pixels. The top left pixel (0, 0) +// is considered to be part of a grid line. +// The pixels that fall on a line are colored with "grid_color", while those +// outside of the lines are colored in "background_color". +// Note that grid_with can be greather than or equal to grid_pitch, in which +// case the resulting bitmap will be a solid color "grid_color". +void DrawGridToBitmap(int w, int h, + SkColor background_color, SkColor grid_color, + int grid_pitch, int grid_width, + SkBitmap* bmp) { + ASSERT_GT(grid_pitch, 0); + ASSERT_GT(grid_width, 0); + ASSERT_NE(background_color, grid_color); + + bmp->setConfig(SkBitmap::kARGB_8888_Config, w, h); + bmp->allocPixels(); + + for (int y = 0; y < h; ++y) { + bool y_on_grid = ((y % grid_pitch) < grid_width); + + for (int x = 0; x < w; ++x) { + bool on_grid = (y_on_grid || ((x % grid_pitch) < grid_width)); + + *bmp->getAddr32(x, y) = (on_grid ? grid_color : background_color); + } + } +} + +// Draws a checkerboard pattern into the w x h bitmap passed in. +// Each rectangle is rect_w in width, rect_h in height. +// The colors alternate between color1 and color2, color1 being used +// in the rectangle at the top left corner. +void DrawCheckerToBitmap(int w, int h, + SkColor color1, SkColor color2, + int rect_w, int rect_h, + SkBitmap* bmp) { + ASSERT_GT(rect_w, 0); + ASSERT_GT(rect_h, 0); + ASSERT_NE(color1, color2); + + bmp->setConfig(SkBitmap::kARGB_8888_Config, w, h); + bmp->allocPixels(); + + for (int y = 0; y < h; ++y) { + bool y_bit = (((y / rect_h) & 0x1) == 0); + + for (int x = 0; x < w; ++x) { + bool x_bit = (((x / rect_w) & 0x1) == 0); + + bool use_color2 = (x_bit != y_bit); // xor + + *bmp->getAddr32(x, y) = (use_color2 ? color2 : color1); + } + } +} + +// DEBUG_BITMAP_GENERATION (0 or 1) controls whether the routines +// to save the test bitmaps are present. By default the test just fails +// without reading/writing files but it is then convenient to have +// a simple way to make the failing tests write out the input/output images +// to check them visually. +#define DEBUG_BITMAP_GENERATION (0) + +#if DEBUG_BITMAP_GENERATION +void SaveBitmapToPNG(const SkBitmap& bmp, const char* path) { + SkAutoLockPixels lock(bmp); + std::vector<unsigned char> png; + gfx::PNGCodec::ColorFormat color_format = gfx::PNGCodec::FORMAT_RGBA; + if (!gfx::PNGCodec::Encode( + reinterpret_cast<const unsigned char*>(bmp.getPixels()), + color_format, gfx::Size(bmp.width(), bmp.height()), + static_cast<int>(bmp.rowBytes()), + false, std::vector<gfx::PNGCodec::Comment>(), &png)) { + FAIL() << "Failed to encode image"; + } + + const base::FilePath fpath(path); + const int num_written = + file_util::WriteFile(fpath, reinterpret_cast<const char*>(&png[0]), + png.size()); + if (num_written != static_cast<int>(png.size())) { + FAIL() << "Failed to write dest \"" << path << '"'; + } +} +#endif // #if DEBUG_BITMAP_GENERATION + +void CheckResampleToSame(skia::ImageOperations::ResizeMethod method) { + // Make our source bitmap. + const int src_w = 16, src_h = 34; + SkBitmap src; + FillDataToBitmap(src_w, src_h, &src); + + // Do a resize of the full bitmap to the same size. The lanczos filter is good + // enough that we should get exactly the same image for output. + SkBitmap results = skia::ImageOperations::Resize(src, method, src_w, src_h); + ASSERT_EQ(src_w, results.width()); + ASSERT_EQ(src_h, results.height()); + + SkAutoLockPixels src_lock(src); + SkAutoLockPixels results_lock(results); + for (int y = 0; y < src_h; y++) { + for (int x = 0; x < src_w; x++) { + EXPECT_EQ(*src.getAddr32(x, y), *results.getAddr32(x, y)); + } + } +} + +// Types defined outside of the ResizeShouldAverageColors test to allow +// use of the arraysize() macro. +// +// 'max_color_distance_override' is used in a max() call together with +// the value of 'max_color_distance' defined in a TestedPixel instance. +// Hence a value of 0.0 in 'max_color_distance_override' means +// "use the pixel-specific value" and larger values can be used to allow +// worse computation errors than provided in a TestedPixel instance. +struct TestedResizeMethod { + skia::ImageOperations::ResizeMethod method; + const char* name; + float max_color_distance_override; +}; + +struct TestedPixel { + int x; + int y; + float max_color_distance; + const char* name; +}; + +// Helper function used by the test "ResizeShouldAverageColors" below. +// Note that ASSERT_EQ does a "return;" on failure, hence we can't have +// a "bool" return value to reflect success. Hence "all_pixels_pass" +void CheckResizeMethodShouldAverageGrid( + const SkBitmap& src, + const TestedResizeMethod& tested_method, + int dest_w, int dest_h, SkColor average_color, + bool* method_passed) { + *method_passed = false; + + const TestedPixel tested_pixels[] = { + // Corners + { 0, 0, 2.3f, "Top left corner" }, + { 0, dest_h - 1, 2.3f, "Bottom left corner" }, + { dest_w - 1, 0, 2.3f, "Top right corner" }, + { dest_w - 1, dest_h - 1, 2.3f, "Bottom right corner" }, + // Middle points of each side + { dest_w / 2, 0, 1.0f, "Top middle" }, + { dest_w / 2, dest_h - 1, 1.0f, "Bottom middle" }, + { 0, dest_h / 2, 1.0f, "Left middle" }, + { dest_w - 1, dest_h / 2, 1.0f, "Right middle" }, + // Center + { dest_w / 2, dest_h / 2, 1.0f, "Center" } + }; + + // Resize the src + const skia::ImageOperations::ResizeMethod method = tested_method.method; + + SkBitmap dest = skia::ImageOperations::Resize(src, method, dest_w, dest_h); + ASSERT_EQ(dest_w, dest.width()); + ASSERT_EQ(dest_h, dest.height()); + + // Check that pixels match the expected average. + float max_observed_distance = 0.0f; + bool all_pixels_ok = true; + + SkAutoLockPixels dest_lock(dest); + + for (size_t pixel_index = 0; + pixel_index < arraysize(tested_pixels); + ++pixel_index) { + const TestedPixel& tested_pixel = tested_pixels[pixel_index]; + + const int x = tested_pixel.x; + const int y = tested_pixel.y; + const float max_allowed_distance = + std::max(tested_pixel.max_color_distance, + tested_method.max_color_distance_override); + + const SkColor actual_color = *dest.getAddr32(x, y); + + // Check that the pixels away from the border region are very close + // to the expected average color + float distance = ColorsEuclidianDistance(average_color, actual_color); + + EXPECT_LE(distance, max_allowed_distance) + << "Resizing method: " << tested_method.name + << ", pixel tested: " << tested_pixel.name + << "(" << x << ", " << y << ")" + << std::hex << std::showbase + << ", expected (avg) hex: " << average_color + << ", actual hex: " << actual_color; + + if (distance > max_allowed_distance) { + all_pixels_ok = false; + } + if (distance > max_observed_distance) { + max_observed_distance = distance; + } + } + + if (!all_pixels_ok) { + ADD_FAILURE() << "Maximum observed color distance for method " + << tested_method.name << ": " << max_observed_distance; + +#if DEBUG_BITMAP_GENERATION + char path[128]; + base::snprintf(path, sizeof(path), + "/tmp/ResizeShouldAverageColors_%s_dest.png", + tested_method.name); + SaveBitmapToPNG(dest, path); +#endif // #if DEBUG_BITMAP_GENERATION + } + + *method_passed = all_pixels_ok; +} + + +} // namespace + +// Helper tests that saves bitmaps to PNGs in /tmp/ to visually check +// that the bitmap generation functions work as expected. +// Those tests are not enabled by default as verification is done +// manually/visually, however it is convenient to leave the functions +// in place. +#if 0 && DEBUG_BITMAP_GENERATION +TEST(ImageOperations, GenerateGradientBitmap) { + // Make our source bitmap. + const int src_w = 640, src_h = 480; + SkBitmap src; + FillDataToBitmap(src_w, src_h, &src); + + SaveBitmapToPNG(src, "/tmp/gradient_640x480.png"); +} + +TEST(ImageOperations, GenerateGridBitmap) { + const int src_w = 640, src_h = 480, src_grid_pitch = 10, src_grid_width = 4; + const SkColor grid_color = SK_ColorRED, background_color = SK_ColorBLUE; + SkBitmap src; + DrawGridToBitmap(src_w, src_h, + background_color, grid_color, + src_grid_pitch, src_grid_width, + &src); + + SaveBitmapToPNG(src, "/tmp/grid_640x408_10_4_red_blue.png"); +} + +TEST(ImageOperations, GenerateCheckerBitmap) { + const int src_w = 640, src_h = 480, rect_w = 10, rect_h = 4; + const SkColor color1 = SK_ColorRED, color2 = SK_ColorBLUE; + SkBitmap src; + DrawCheckerToBitmap(src_w, src_h, color1, color2, rect_w, rect_h, &src); + + SaveBitmapToPNG(src, "/tmp/checker_640x408_10_4_red_blue.png"); +} +#endif // #if ... && DEBUG_BITMAP_GENERATION + +// Makes the bitmap 50% the size as the original using a box filter. This is +// an easy operation that we can check the results for manually. +TEST(ImageOperations, Halve) { + // Make our source bitmap. + int src_w = 30, src_h = 38; + SkBitmap src; + FillDataToBitmap(src_w, src_h, &src); + + // Do a halving of the full bitmap. + SkBitmap actual_results = skia::ImageOperations::Resize( + src, skia::ImageOperations::RESIZE_BOX, src_w / 2, src_h / 2); + ASSERT_EQ(src_w / 2, actual_results.width()); + ASSERT_EQ(src_h / 2, actual_results.height()); + + // Compute the expected values & compare. + SkAutoLockPixels lock(actual_results); + for (int y = 0; y < actual_results.height(); y++) { + for (int x = 0; x < actual_results.width(); x++) { + // Note that those expressions take into account the "half-pixel" + // offset that comes into play due to considering the coordinates + // of the center of the pixels. So x * 2 is a simplification + // of ((x+0.5) * 2 - 1) and (x * 2 + 1) is really (x + 0.5) * 2. + int first_x = x * 2; + int last_x = std::min(src_w - 1, x * 2 + 1); + + int first_y = y * 2; + int last_y = std::min(src_h - 1, y * 2 + 1); + + const uint32_t expected_color = AveragePixel(src, + first_x, last_x, + first_y, last_y); + const uint32_t actual_color = *actual_results.getAddr32(x, y); + const bool close = ColorsClose(expected_color, actual_color); + EXPECT_TRUE(close); + if (!close) { + char str[128]; + base::snprintf(str, sizeof(str), + "exp[%d,%d] = %08X, actual[%d,%d] = %08X", + x, y, expected_color, x, y, actual_color); + ADD_FAILURE() << str; + PrintPixel(src, first_x, last_x, first_y, last_y); + } + } + } +} + +TEST(ImageOperations, HalveSubset) { + // Make our source bitmap. + int src_w = 16, src_h = 34; + SkBitmap src; + FillDataToBitmap(src_w, src_h, &src); + + // Do a halving of the full bitmap. + SkBitmap full_results = skia::ImageOperations::Resize( + src, skia::ImageOperations::RESIZE_BOX, src_w / 2, src_h / 2); + ASSERT_EQ(src_w / 2, full_results.width()); + ASSERT_EQ(src_h / 2, full_results.height()); + + // Now do a halving of a a subset, recall the destination subset is in the + // destination coordinate system (max = half of the original image size). + SkIRect subset_rect = { 2, 3, 3, 6 }; + SkBitmap subset_results = skia::ImageOperations::Resize( + src, skia::ImageOperations::RESIZE_BOX, + src_w / 2, src_h / 2, subset_rect); + ASSERT_EQ(subset_rect.width(), subset_results.width()); + ASSERT_EQ(subset_rect.height(), subset_results.height()); + + // The computed subset and the corresponding subset of the original image + // should be the same. + SkAutoLockPixels full_lock(full_results); + SkAutoLockPixels subset_lock(subset_results); + for (int y = 0; y < subset_rect.height(); y++) { + for (int x = 0; x < subset_rect.width(); x++) { + ASSERT_EQ( + *full_results.getAddr32(x + subset_rect.fLeft, y + subset_rect.fTop), + *subset_results.getAddr32(x, y)); + } + } +} + +TEST(ImageOperations, InvalidParams) { + // Make our source bitmap. + SkBitmap src; + src.setConfig(SkBitmap::kA8_Config, 16, 34); + src.allocPixels(); + + // Scale it, don't die. + SkBitmap full_results = skia::ImageOperations::Resize( + src, skia::ImageOperations::RESIZE_BOX, 10, 20); +} + +// Resamples an image to the same image, it should give the same result. +TEST(ImageOperations, ResampleToSameHamming1) { + CheckResampleToSame(skia::ImageOperations::RESIZE_HAMMING1); +} + +TEST(ImageOperations, ResampleToSameLanczos2) { + CheckResampleToSame(skia::ImageOperations::RESIZE_LANCZOS2); +} + +TEST(ImageOperations, ResampleToSameLanczos3) { + CheckResampleToSame(skia::ImageOperations::RESIZE_LANCZOS3); +} + +// Check that all Good/Better/Best, Box, Lanczos2 and Lanczos3 generate purple +// when resizing a 4x8 red/blue checker pattern by 1/16x1/16. +TEST(ImageOperations, ResizeShouldAverageColors) { + // Make our source bitmap. + const int src_w = 640, src_h = 480, checker_rect_w = 4, checker_rect_h = 8; + const SkColor checker_color1 = SK_ColorRED, checker_color2 = SK_ColorBLUE; + + const int dest_w = src_w / (4 * checker_rect_w); + const int dest_h = src_h / (2 * checker_rect_h); + + // Compute the expected (average) color + const SkColor colors[] = { checker_color1, checker_color2 }; + const SkColor average_color = AveragePixel(colors, arraysize(colors)); + + // RESIZE_SUBPIXEL is only supported on Linux/non-GTV platforms. + static const TestedResizeMethod tested_methods[] = { + { skia::ImageOperations::RESIZE_GOOD, "GOOD", 0.0f }, + { skia::ImageOperations::RESIZE_BETTER, "BETTER", 0.0f }, + { skia::ImageOperations::RESIZE_BEST, "BEST", 0.0f }, + { skia::ImageOperations::RESIZE_BOX, "BOX", 0.0f }, + { skia::ImageOperations::RESIZE_HAMMING1, "HAMMING1", 0.0f }, + { skia::ImageOperations::RESIZE_LANCZOS2, "LANCZOS2", 0.0f }, + { skia::ImageOperations::RESIZE_LANCZOS3, "LANCZOS3", 0.0f }, +#if defined(OS_LINUX) && !defined(GTV) + // SUBPIXEL has slightly worse performance than the other filters: + // 6.324 Bottom left/right corners + // 5.099 Top left/right corners + // 2.828 Bottom middle + // 1.414 Top/Left/Right middle, center + // + // This is expected since, in order to judge RESIZE_SUBPIXEL accurately, + // we'd need to compute distances for each sub-pixel, and potentially + // tweak the test parameters so that expectations were realistic when + // looking at sub-pixels in isolation. + // + // Rather than going to these lengths, we added the "max_distance_override" + // field in TestedResizeMethod, intended for RESIZE_SUBPIXEL. It allows + // us to to enable its testing without having to lower the success criteria + // for the other methods. This procedure is distateful but defining + // a distance limit for each tested pixel for each method was judged to add + // unneeded complexity. + { skia::ImageOperations::RESIZE_SUBPIXEL, "SUBPIXEL", 6.4f }, +#endif + }; + + // Create our source bitmap. + SkBitmap src; + DrawCheckerToBitmap(src_w, src_h, + checker_color1, checker_color2, + checker_rect_w, checker_rect_h, + &src); + + // For each method, downscale by 16 in each dimension, + // and check each tested pixel against the expected average color. + bool all_methods_ok ALLOW_UNUSED = true; + + for (size_t method_index = 0; + method_index < arraysize(tested_methods); + ++method_index) { + bool pass = true; + CheckResizeMethodShouldAverageGrid(src, + tested_methods[method_index], + dest_w, dest_h, average_color, + &pass); + if (!pass) { + all_methods_ok = false; + } + } + +#if DEBUG_BITMAP_GENERATION + if (!all_methods_ok) { + SaveBitmapToPNG(src, "/tmp/ResizeShouldAverageColors_src.png"); + } +#endif // #if DEBUG_BITMAP_GENERATION +} + + +// Check that Lanczos2 and Lanczos3 thumbnails produce similar results +TEST(ImageOperations, CompareLanczosMethods) { + const int src_w = 640, src_h = 480, src_grid_pitch = 8, src_grid_width = 4; + + const int dest_w = src_w / 4; + const int dest_h = src_h / 4; + + // 5.0f is the maximum distance we see in this test given the current + // parameters. The value is very ad-hoc and the parameters of the scaling + // were picked to produce a small value. So this test is very much about + // revealing egregious regression rather than doing a good job at checking + // the math behind the filters. + // TODO(evannier): because of the half pixel error mentioned inside + // image_operations.cc, this distance is much larger than it should be. + // This should read: + // const float max_color_distance = 5.0f; + const float max_color_distance = 12.1f; + + // Make our source bitmap. + SkColor grid_color = SK_ColorRED, background_color = SK_ColorBLUE; + SkBitmap src; + DrawGridToBitmap(src_w, src_h, + background_color, grid_color, + src_grid_pitch, src_grid_width, + &src); + + // Resize the src using both methods. + SkBitmap dest_l2 = skia::ImageOperations::Resize( + src, + skia::ImageOperations::RESIZE_LANCZOS2, + dest_w, dest_h); + ASSERT_EQ(dest_w, dest_l2.width()); + ASSERT_EQ(dest_h, dest_l2.height()); + + SkBitmap dest_l3 = skia::ImageOperations::Resize( + src, + skia::ImageOperations::RESIZE_LANCZOS3, + dest_w, dest_h); + ASSERT_EQ(dest_w, dest_l3.width()); + ASSERT_EQ(dest_h, dest_l3.height()); + + // Compare the pixels produced by both methods. + float max_observed_distance = 0.0f; + bool all_pixels_ok = true; + + SkAutoLockPixels l2_lock(dest_l2); + SkAutoLockPixels l3_lock(dest_l3); + for (int y = 0; y < dest_h; ++y) { + for (int x = 0; x < dest_w; ++x) { + const SkColor color_lanczos2 = *dest_l2.getAddr32(x, y); + const SkColor color_lanczos3 = *dest_l3.getAddr32(x, y); + + float distance = ColorsEuclidianDistance(color_lanczos2, color_lanczos3); + + EXPECT_LE(distance, max_color_distance) + << "pixel tested: (" << x << ", " << y + << std::hex << std::showbase + << "), lanczos2 hex: " << color_lanczos2 + << ", lanczos3 hex: " << color_lanczos3 + << std::setprecision(2) + << ", distance: " << distance; + + if (distance > max_color_distance) { + all_pixels_ok = false; + } + if (distance > max_observed_distance) { + max_observed_distance = distance; + } + } + } + + if (!all_pixels_ok) { + ADD_FAILURE() << "Maximum observed color distance: " + << max_observed_distance; + +#if DEBUG_BITMAP_GENERATION + SaveBitmapToPNG(src, "/tmp/CompareLanczosMethods_source.png"); + SaveBitmapToPNG(dest_l2, "/tmp/CompareLanczosMethods_lanczos2.png"); + SaveBitmapToPNG(dest_l3, "/tmp/CompareLanczosMethods_lanczos3.png"); +#endif // #if DEBUG_BITMAP_GENERATION + } +} + +#ifndef M_PI +// No M_PI in math.h on windows? No problem. +#define M_PI 3.14159265358979323846 +#endif + +static double sinc(double x) { + if (x == 0.0) return 1.0; + x *= M_PI; + return sin(x) / x; +} + +static double lanczos3(double offset) { + if (fabs(offset) >= 3) return 0.0; + return sinc(offset) * sinc(offset / 3.0); +} + +TEST(ImageOperations, ScaleUp) { + const int src_w = 3; + const int src_h = 3; + const int dst_w = 9; + const int dst_h = 9; + SkBitmap src; + src.setConfig(SkBitmap::kARGB_8888_Config, src_w, src_h); + src.allocPixels(); + + for (int src_y = 0; src_y < src_h; ++src_y) { + for (int src_x = 0; src_x < src_w; ++src_x) { + *src.getAddr32(src_x, src_y) = SkColorSetARGBInline(255, + 10 + src_x * 100, + 10 + src_y * 100, + 0); + } + } + + SkBitmap dst = skia::ImageOperations::Resize( + src, + skia::ImageOperations::RESIZE_LANCZOS3, + dst_w, dst_h); + SkAutoLockPixels dst_lock(dst); + for (int dst_y = 0; dst_y < dst_h; ++dst_y) { + for (int dst_x = 0; dst_x < dst_w; ++dst_x) { + float dst_x_in_src = (dst_x + 0.5) * src_w / dst_w; + float dst_y_in_src = (dst_y + 0.5) * src_h / dst_h; + float a = 0.0f; + float r = 0.0f; + float g = 0.0f; + float b = 0.0f; + float sum = 0.0f; + for (int src_y = 0; src_y < src_h; ++src_y) { + for (int src_x = 0; src_x < src_w; ++src_x) { + double coeff = + lanczos3(src_x + 0.5 - dst_x_in_src) * + lanczos3(src_y + 0.5 - dst_y_in_src); + sum += coeff; + SkColor tmp = *src.getAddr32(src_x, src_y); + a += coeff * SkColorGetA(tmp); + r += coeff * SkColorGetR(tmp); + g += coeff * SkColorGetG(tmp); + b += coeff * SkColorGetB(tmp); + } + } + a /= sum; + r /= sum; + g /= sum; + b /= sum; + if (a < 0.0f) a = 0.0f; + if (r < 0.0f) r = 0.0f; + if (g < 0.0f) g = 0.0f; + if (b < 0.0f) b = 0.0f; + if (a > 255.0f) a = 255.0f; + if (r > 255.0f) r = 255.0f; + if (g > 255.0f) g = 255.0f; + if (b > 255.0f) b = 255.0f; + SkColor dst_color = *dst.getAddr32(dst_x, dst_y); + EXPECT_LE(fabs(SkColorGetA(dst_color) - a), 1.5f); + EXPECT_LE(fabs(SkColorGetR(dst_color) - r), 1.5f); + EXPECT_LE(fabs(SkColorGetG(dst_color) - g), 1.5f); + EXPECT_LE(fabs(SkColorGetB(dst_color) - b), 1.5f); + if (HasFailure()) { + return; + } + } + } +} diff --git a/chromium/skia/ext/lazy_pixel_ref.cc b/chromium/skia/ext/lazy_pixel_ref.cc new file mode 100644 index 00000000000..784fae47419 --- /dev/null +++ b/chromium/skia/ext/lazy_pixel_ref.cc @@ -0,0 +1,15 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "skia/ext/lazy_pixel_ref.h" + +namespace skia { + +LazyPixelRef::LazyPixelRef() : SkPixelRef(0) { +} + +LazyPixelRef::~LazyPixelRef() { +} + +} // namespace skia diff --git a/chromium/skia/ext/lazy_pixel_ref.h b/chromium/skia/ext/lazy_pixel_ref.h new file mode 100644 index 00000000000..fff4c385add --- /dev/null +++ b/chromium/skia/ext/lazy_pixel_ref.h @@ -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. + +#ifndef SKIA_EXT_LAZY_PIXEL_REF_H_ +#define SKIA_EXT_LAZY_PIXEL_REF_H_ + +#include "third_party/skia/include/core/SkPixelRef.h" +#include "third_party/skia/include/core/SkRect.h" + +namespace skia { + +// This class extends SkPixelRef to facilitate lazy image decoding on the impl +// thread. +class SK_API LazyPixelRef : public SkPixelRef { + public: + LazyPixelRef(); + virtual ~LazyPixelRef(); + + struct PrepareParams { + // Clipping rect for this pixel ref. + SkIRect clip_rect; + }; + + // Request the ImageDecodingStore to prepare image decoding for the + // given clipping rect. Returns true is succeeded, or false otherwise. + virtual bool PrepareToDecode(const PrepareParams& params) = 0; + + // Returns true if this pixel ref is already in the ImageDecodingStore's + // cache, false otherwise. Much cheaper than PrepareToDecode(). + virtual bool MaybeDecoded() = 0; + + // Start decoding the image. + virtual void Decode() = 0; +}; + +} // namespace skia + +#endif // SKIA_EXT_LAZY_PIXEL_REF_H_ diff --git a/chromium/skia/ext/lazy_pixel_ref_utils.cc b/chromium/skia/ext/lazy_pixel_ref_utils.cc new file mode 100644 index 00000000000..67371c9035e --- /dev/null +++ b/chromium/skia/ext/lazy_pixel_ref_utils.cc @@ -0,0 +1,409 @@ +// 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 "skia/ext/lazy_pixel_ref_utils.h" + +#include "skia/ext/lazy_pixel_ref.h" +#include "third_party/skia/include/core/SkCanvas.h" +#include "third_party/skia/include/core/SkData.h" +#include "third_party/skia/include/core/SkDevice.h" +#include "third_party/skia/include/core/SkDraw.h" +#include "third_party/skia/include/core/SkPixelRef.h" +#include "third_party/skia/include/core/SkRRect.h" +#include "third_party/skia/include/core/SkRect.h" +#include "third_party/skia/include/core/SkShader.h" +#include "third_party/skia/src/core/SkRasterClip.h" + +namespace skia { + +namespace { + +// URI label for a lazily decoded SkPixelRef. +const char kLabelLazyDecoded[] = "lazy"; + +class LazyPixelRefSet { + public: + LazyPixelRefSet( + std::vector<LazyPixelRefUtils::PositionLazyPixelRef>* pixel_refs) + : pixel_refs_(pixel_refs) {} + + void Add(SkPixelRef* pixel_ref, const SkRect& rect) { + // Only save lazy pixel refs. + if (pixel_ref->getURI() && + !strcmp(pixel_ref->getURI(), kLabelLazyDecoded)) { + LazyPixelRefUtils::PositionLazyPixelRef position_pixel_ref; + position_pixel_ref.lazy_pixel_ref = + static_cast<skia::LazyPixelRef*>(pixel_ref); + position_pixel_ref.pixel_ref_rect = rect; + pixel_refs_->push_back(position_pixel_ref); + } + } + + private: + std::vector<LazyPixelRefUtils::PositionLazyPixelRef>* pixel_refs_; +}; + +class GatherPixelRefDevice : public SkDevice { + public: + GatherPixelRefDevice(const SkBitmap& bm, LazyPixelRefSet* lazy_pixel_ref_set) + : SkDevice(bm), lazy_pixel_ref_set_(lazy_pixel_ref_set) {} + + virtual void clear(SkColor color) SK_OVERRIDE {} + virtual void writePixels(const SkBitmap& bitmap, + int x, + int y, + SkCanvas::Config8888 config8888) SK_OVERRIDE {} + + virtual void drawPaint(const SkDraw& draw, const SkPaint& paint) SK_OVERRIDE { + SkBitmap bitmap; + if (GetBitmapFromPaint(paint, &bitmap)) { + SkRect clip_rect = SkRect::Make(draw.fRC->getBounds()); + SkRect canvas_rect = SkRect::MakeWH(width(), height()); + SkRect paint_rect = SkRect::MakeEmpty(); + paint_rect.intersect(canvas_rect, clip_rect); + + AddBitmap(bitmap, paint_rect); + } + } + + virtual void drawPoints(const SkDraw& draw, + SkCanvas::PointMode mode, + size_t count, + const SkPoint points[], + const SkPaint& paint) SK_OVERRIDE { + SkBitmap bitmap; + if (!GetBitmapFromPaint(paint, &bitmap)) + return; + + if (count == 0) + return; + + SkPoint min_point = points[0]; + SkPoint max_point = points[0]; + for (size_t i = 1; i < count; ++i) { + const SkPoint& point = points[i]; + min_point.set(std::min(min_point.x(), point.x()), + std::min(min_point.y(), point.y())); + max_point.set(std::max(max_point.x(), point.x()), + std::max(max_point.y(), point.y())); + } + + SkRect bounds = SkRect::MakeLTRB( + min_point.x(), min_point.y(), max_point.x(), max_point.y()); + + GatherPixelRefDevice::drawRect(draw, bounds, paint); + } + virtual void drawRect(const SkDraw& draw, + const SkRect& rect, + const SkPaint& paint) SK_OVERRIDE { + SkBitmap bitmap; + if (GetBitmapFromPaint(paint, &bitmap)) { + SkRect mapped_rect; + draw.fMatrix->mapRect(&mapped_rect, rect); + mapped_rect.intersect(SkRect::Make(draw.fRC->getBounds())); + AddBitmap(bitmap, mapped_rect); + } + } + virtual void drawOval(const SkDraw& draw, + const SkRect& rect, + const SkPaint& paint) SK_OVERRIDE { + GatherPixelRefDevice::drawRect(draw, rect, paint); + } + virtual void drawRRect(const SkDraw& draw, + const SkRRect& rect, + const SkPaint& paint) SK_OVERRIDE { + GatherPixelRefDevice::drawRect(draw, rect.rect(), paint); + } + virtual void drawPath(const SkDraw& draw, + const SkPath& path, + const SkPaint& paint, + const SkMatrix* pre_path_matrix, + bool path_is_mutable) SK_OVERRIDE { + SkBitmap bitmap; + if (!GetBitmapFromPaint(paint, &bitmap)) + return; + + SkRect path_bounds = path.getBounds(); + SkRect final_rect; + if (pre_path_matrix != NULL) + pre_path_matrix->mapRect(&final_rect, path_bounds); + else + final_rect = path_bounds; + + GatherPixelRefDevice::drawRect(draw, final_rect, paint); + } + virtual void drawBitmap(const SkDraw& draw, + const SkBitmap& bitmap, + const SkMatrix& matrix, + const SkPaint& paint) SK_OVERRIDE { + SkMatrix total_matrix; + total_matrix.setConcat(*draw.fMatrix, matrix); + + SkRect bitmap_rect = SkRect::MakeWH(bitmap.width(), bitmap.height()); + SkRect mapped_rect; + total_matrix.mapRect(&mapped_rect, bitmap_rect); + AddBitmap(bitmap, mapped_rect); + + SkBitmap paint_bitmap; + if (GetBitmapFromPaint(paint, &paint_bitmap)) + AddBitmap(paint_bitmap, mapped_rect); + } + virtual void drawBitmapRect(const SkDraw& draw, + const SkBitmap& bitmap, + const SkRect* src_or_null, + const SkRect& dst, + const SkPaint& paint) SK_OVERRIDE { + SkRect bitmap_rect = SkRect::MakeWH(bitmap.width(), bitmap.height()); + SkMatrix matrix; + matrix.setRectToRect(bitmap_rect, dst, SkMatrix::kFill_ScaleToFit); + GatherPixelRefDevice::drawBitmap(draw, bitmap, matrix, paint); + } + virtual void drawSprite(const SkDraw& draw, + const SkBitmap& bitmap, + int x, + int y, + const SkPaint& paint) SK_OVERRIDE { + // Sprites aren't affected by current matrix, so we can't reuse drawRect. + SkMatrix matrix; + matrix.setTranslate(x, y); + + SkRect bitmap_rect = SkRect::MakeWH(bitmap.width(), bitmap.height()); + SkRect mapped_rect; + matrix.mapRect(&mapped_rect, bitmap_rect); + + AddBitmap(bitmap, mapped_rect); + SkBitmap paint_bitmap; + if (GetBitmapFromPaint(paint, &paint_bitmap)) + AddBitmap(paint_bitmap, mapped_rect); + } + virtual void drawText(const SkDraw& draw, + const void* text, + size_t len, + SkScalar x, + SkScalar y, + const SkPaint& paint) SK_OVERRIDE { + SkBitmap bitmap; + if (!GetBitmapFromPaint(paint, &bitmap)) + return; + + // Math is borrowed from SkBBoxRecord + SkRect bounds; + paint.measureText(text, len, &bounds); + SkPaint::FontMetrics metrics; + paint.getFontMetrics(&metrics); + + if (paint.isVerticalText()) { + SkScalar h = bounds.fBottom - bounds.fTop; + if (paint.getTextAlign() == SkPaint::kCenter_Align) { + bounds.fTop -= h / 2; + bounds.fBottom -= h / 2; + } + bounds.fBottom += metrics.fBottom; + bounds.fTop += metrics.fTop; + } else { + SkScalar w = bounds.fRight - bounds.fLeft; + if (paint.getTextAlign() == SkPaint::kCenter_Align) { + bounds.fLeft -= w / 2; + bounds.fRight -= w / 2; + } else if (paint.getTextAlign() == SkPaint::kRight_Align) { + bounds.fLeft -= w; + bounds.fRight -= w; + } + bounds.fTop = metrics.fTop; + bounds.fBottom = metrics.fBottom; + } + + SkScalar pad = (metrics.fBottom - metrics.fTop) / 2; + bounds.fLeft -= pad; + bounds.fRight += pad; + bounds.fLeft += x; + bounds.fRight += x; + bounds.fTop += y; + bounds.fBottom += y; + + GatherPixelRefDevice::drawRect(draw, bounds, paint); + } + virtual void drawPosText(const SkDraw& draw, + const void* text, + size_t len, + const SkScalar pos[], + SkScalar const_y, + int scalars_per_pos, + const SkPaint& paint) SK_OVERRIDE { + SkBitmap bitmap; + if (!GetBitmapFromPaint(paint, &bitmap)) + return; + + if (len == 0) + return; + + // Similar to SkDraw asserts. + SkASSERT(scalars_per_pos == 1 || scalars_per_pos == 2); + + SkPoint min_point; + SkPoint max_point; + if (scalars_per_pos == 1) { + min_point.set(pos[0], const_y); + max_point.set(pos[0], const_y); + } else if (scalars_per_pos == 2) { + min_point.set(pos[0], const_y + pos[1]); + max_point.set(pos[0], const_y + pos[1]); + } + + for (size_t i = 0; i < len; ++i) { + SkScalar x = pos[i * scalars_per_pos]; + SkScalar y = const_y; + if (scalars_per_pos == 2) + y += pos[i * scalars_per_pos + 1]; + + min_point.set(std::min(x, min_point.x()), std::min(y, min_point.y())); + max_point.set(std::max(x, max_point.x()), std::max(y, max_point.y())); + } + + SkRect bounds = SkRect::MakeLTRB( + min_point.x(), min_point.y(), max_point.x(), max_point.y()); + + // Math is borrowed from SkBBoxRecord + SkPaint::FontMetrics metrics; + paint.getFontMetrics(&metrics); + + bounds.fTop += metrics.fTop; + bounds.fBottom += metrics.fBottom; + + SkScalar pad = (metrics.fTop - metrics.fBottom) / 2; + bounds.fLeft += pad; + bounds.fRight -= pad; + + GatherPixelRefDevice::drawRect(draw, bounds, paint); + } + virtual void drawTextOnPath(const SkDraw& draw, + const void* text, + size_t len, + const SkPath& path, + const SkMatrix* matrix, + const SkPaint& paint) SK_OVERRIDE { + SkBitmap bitmap; + if (!GetBitmapFromPaint(paint, &bitmap)) + return; + + // Math is borrowed from SkBBoxRecord + SkRect bounds = path.getBounds(); + SkPaint::FontMetrics metrics; + paint.getFontMetrics(&metrics); + + SkScalar pad = metrics.fTop; + bounds.fLeft += pad; + bounds.fRight -= pad; + bounds.fTop += pad; + bounds.fBottom -= pad; + + GatherPixelRefDevice::drawRect(draw, bounds, paint); + } + virtual void drawVertices(const SkDraw& draw, + SkCanvas::VertexMode, + int vertex_count, + const SkPoint verts[], + const SkPoint texs[], + const SkColor colors[], + SkXfermode* xmode, + const uint16_t indices[], + int index_count, + const SkPaint& paint) SK_OVERRIDE { + GatherPixelRefDevice::drawPoints( + draw, SkCanvas::kPolygon_PointMode, vertex_count, verts, paint); + } + virtual void drawDevice(const SkDraw&, + SkDevice*, + int x, + int y, + const SkPaint&) SK_OVERRIDE {} + + protected: + virtual bool onReadPixels(const SkBitmap& bitmap, + int x, + int y, + SkCanvas::Config8888 config8888) SK_OVERRIDE { + return false; + } + + private: + LazyPixelRefSet* lazy_pixel_ref_set_; + + void AddBitmap(const SkBitmap& bm, const SkRect& rect) { + lazy_pixel_ref_set_->Add(bm.pixelRef(), rect); + } + + bool GetBitmapFromPaint(const SkPaint& paint, SkBitmap* bm) { + SkShader* shader = paint.getShader(); + if (shader) { + // Check whether the shader is a gradient in order to prevent generation + // of bitmaps from gradient shaders, which implement asABitmap. + if (SkShader::kNone_GradientType == shader->asAGradient(NULL)) + return shader->asABitmap(bm, NULL, NULL); + } + return false; + } +}; + +class NoSaveLayerCanvas : public SkCanvas { + public: + NoSaveLayerCanvas(SkDevice* device) : INHERITED(device) {} + + // Turn saveLayer() into save() for speed, should not affect correctness. + virtual int saveLayer(const SkRect* bounds, + const SkPaint* paint, + SaveFlags flags) SK_OVERRIDE { + + // Like SkPictureRecord, we don't want to create layers, but we do need + // to respect the save and (possibly) its rect-clip. + int count = this->INHERITED::save(flags); + if (bounds) { + this->INHERITED::clipRectBounds(bounds, flags, NULL); + } + return count; + } + + // Disable aa for speed. + virtual bool clipRect(const SkRect& rect, SkRegion::Op op, bool doAA) + SK_OVERRIDE { + return this->INHERITED::clipRect(rect, op, false); + } + + virtual bool clipPath(const SkPath& path, SkRegion::Op op, bool doAA) + SK_OVERRIDE { + return this->updateClipConservativelyUsingBounds( + path.getBounds(), op, path.isInverseFillType()); + } + virtual bool clipRRect(const SkRRect& rrect, SkRegion::Op op, bool doAA) + SK_OVERRIDE { + return this->updateClipConservativelyUsingBounds( + rrect.getBounds(), op, false); + } + + private: + typedef SkCanvas INHERITED; +}; + +} // namespace + +void LazyPixelRefUtils::GatherPixelRefs( + SkPicture* picture, + std::vector<PositionLazyPixelRef>* lazy_pixel_refs) { + lazy_pixel_refs->clear(); + LazyPixelRefSet pixel_ref_set(lazy_pixel_refs); + + SkBitmap empty_bitmap; + empty_bitmap.setConfig( + SkBitmap::kNo_Config, picture->width(), picture->height()); + + GatherPixelRefDevice device(empty_bitmap, &pixel_ref_set); + NoSaveLayerCanvas canvas(&device); + + canvas.clipRect(SkRect::MakeWH(picture->width(), picture->height()), + SkRegion::kIntersect_Op, + false); + canvas.drawPicture(*picture); +} + +} // namespace skia diff --git a/chromium/skia/ext/lazy_pixel_ref_utils.h b/chromium/skia/ext/lazy_pixel_ref_utils.h new file mode 100644 index 00000000000..33fe039e1ac --- /dev/null +++ b/chromium/skia/ext/lazy_pixel_ref_utils.h @@ -0,0 +1,33 @@ +// 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 SKIA_EXT_LAZY_PIXEL_REF_UTILS_H_ +#define SKIA_EXT_LAZY_PIXEL_REF_UTILS_H_ + +#include <vector> + +#include "SkPicture.h" +#include "SkRect.h" + +namespace skia { + +class LazyPixelRef; +class SK_API LazyPixelRefUtils { + public: + + struct PositionLazyPixelRef { + skia::LazyPixelRef* lazy_pixel_ref; + SkRect pixel_ref_rect; + }; + + static void GatherPixelRefs( + SkPicture* picture, + std::vector<PositionLazyPixelRef>* lazy_pixel_refs); +}; + +typedef std::vector<LazyPixelRefUtils::PositionLazyPixelRef> LazyPixelRefList; + +} // namespace skia + +#endif diff --git a/chromium/skia/ext/lazy_pixel_ref_utils_unittest.cc b/chromium/skia/ext/lazy_pixel_ref_utils_unittest.cc new file mode 100644 index 00000000000..a9f60a23ab1 --- /dev/null +++ b/chromium/skia/ext/lazy_pixel_ref_utils_unittest.cc @@ -0,0 +1,735 @@ +// 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/compiler_specific.h" +#include "base/memory/scoped_ptr.h" +#include "cc/test/geometry_test_utils.h" +#include "skia/ext/lazy_pixel_ref.h" +#include "skia/ext/lazy_pixel_ref_utils.h" +#include "skia/ext/refptr.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/SkFlattenableBuffers.h" +#include "third_party/skia/include/core/SkPoint.h" +#include "third_party/skia/include/core/SkShader.h" +#include "third_party/skia/src/core/SkOrderedReadBuffer.h" +#include "ui/gfx/rect.h" +#include "ui/gfx/skia_util.h" + +namespace skia { + +namespace { + +void CreateBitmap(gfx::Size size, const char* uri, SkBitmap* bitmap); + +class TestPixelRef : public SkPixelRef { + public: + TestPixelRef(int width, int height); + virtual ~TestPixelRef(); + + virtual SkFlattenable::Factory getFactory() OVERRIDE; + virtual void* onLockPixels(SkColorTable** color_table) OVERRIDE; + virtual void onUnlockPixels() OVERRIDE {} + virtual SkPixelRef* deepCopy(SkBitmap::Config config, const SkIRect* subset) + OVERRIDE; + + private: + scoped_ptr<char[]> pixels_; +}; + +class TestLazyPixelRef : public skia::LazyPixelRef { + public: + TestLazyPixelRef(int width, int height); + virtual ~TestLazyPixelRef(); + + virtual SkFlattenable::Factory getFactory() OVERRIDE; + virtual void* onLockPixels(SkColorTable** color_table) OVERRIDE; + virtual void onUnlockPixels() OVERRIDE {} + virtual bool PrepareToDecode(const PrepareParams& params) OVERRIDE; + virtual bool MaybeDecoded() OVERRIDE; + virtual SkPixelRef* deepCopy(SkBitmap::Config config, const SkIRect* subset) + OVERRIDE; + virtual void Decode() OVERRIDE {} + + private: + scoped_ptr<char[]> pixels_; +}; + +class TestLazyShader : public SkShader { + public: + TestLazyShader() { CreateBitmap(gfx::Size(50, 50), "lazy", &bitmap_); } + + TestLazyShader(SkFlattenableReadBuffer& flattenable_buffer) { + SkOrderedReadBuffer& buffer = + static_cast<SkOrderedReadBuffer&>(flattenable_buffer); + SkReader32* reader = buffer.getReader32(); + + reader->skip(-4); + uint32_t toSkip = reader->readU32(); + reader->skip(toSkip); + + CreateBitmap(gfx::Size(50, 50), "lazy", &bitmap_); + } + + virtual SkShader::BitmapType asABitmap(SkBitmap* bitmap, + SkMatrix* matrix, + TileMode xy[2]) const OVERRIDE { + if (bitmap) + *bitmap = bitmap_; + return SkShader::kDefault_BitmapType; + } + + // Pure virtual implementaiton. + virtual void shadeSpan(int x, int y, SkPMColor[], int count) OVERRIDE {} + SK_DECLARE_PUBLIC_FLATTENABLE_DESERIALIZATION_PROCS(TestLazyShader); + + private: + SkBitmap bitmap_; +}; + +TestPixelRef::TestPixelRef(int width, int height) + : pixels_(new char[4 * width * height]) {} + +TestPixelRef::~TestPixelRef() {} + +SkFlattenable::Factory TestPixelRef::getFactory() { return NULL; } + +void* TestPixelRef::onLockPixels(SkColorTable** color_table) { + return pixels_.get(); +} + +SkPixelRef* TestPixelRef::deepCopy(SkBitmap::Config config, + const SkIRect* subset) { + this->ref(); + return this; +} + +TestLazyPixelRef::TestLazyPixelRef(int width, int height) + : pixels_(new char[4 * width * height]) {} + +TestLazyPixelRef::~TestLazyPixelRef() {} + +SkFlattenable::Factory TestLazyPixelRef::getFactory() { return NULL; } + +void* TestLazyPixelRef::onLockPixels(SkColorTable** color_table) { + return pixels_.get(); +} + +bool TestLazyPixelRef::PrepareToDecode(const PrepareParams& params) { + return true; +} + +bool TestLazyPixelRef::MaybeDecoded() { + return true; +} + +SkPixelRef* TestLazyPixelRef::deepCopy(SkBitmap::Config config, + const SkIRect* subset) { + this->ref(); + return this; +} + +void CreateBitmap(gfx::Size size, const char* uri, SkBitmap* bitmap) { + skia::RefPtr<TestLazyPixelRef> lazy_pixel_ref = + skia::AdoptRef(new TestLazyPixelRef(size.width(), size.height())); + lazy_pixel_ref->setURI(uri); + + bitmap->setConfig(SkBitmap::kARGB_8888_Config, size.width(), size.height()); + bitmap->setPixelRef(lazy_pixel_ref.get()); +} + +SkCanvas* StartRecording(SkPicture* picture, gfx::Rect layer_rect) { + SkCanvas* canvas = picture->beginRecording( + layer_rect.width(), + layer_rect.height(), + SkPicture::kUsePathBoundsForClip_RecordingFlag | + SkPicture::kOptimizeForClippedPlayback_RecordingFlag); + + canvas->save(); + canvas->translate(-layer_rect.x(), -layer_rect.y()); + canvas->clipRect(SkRect::MakeXYWH( + layer_rect.x(), layer_rect.y(), layer_rect.width(), layer_rect.height())); + + return canvas; +} + +void StopRecording(SkPicture* picture, SkCanvas* canvas) { + canvas->restore(); + picture->endRecording(); +} + +} // namespace + +TEST(LazyPixelRefUtilsTest, DrawPaint) { + gfx::Rect layer_rect(0, 0, 256, 256); + + skia::RefPtr<SkPicture> picture = skia::AdoptRef(new SkPicture); + SkCanvas* canvas = StartRecording(picture.get(), layer_rect); + + TestLazyShader first_shader; + SkPaint first_paint; + first_paint.setShader(&first_shader); + + TestLazyShader second_shader; + SkPaint second_paint; + second_paint.setShader(&second_shader); + + TestLazyShader third_shader; + SkPaint third_paint; + third_paint.setShader(&third_shader); + + canvas->drawPaint(first_paint); + canvas->clipRect(SkRect::MakeXYWH(34, 45, 56, 67)); + canvas->drawPaint(second_paint); + // Total clip is now (34, 45, 56, 55) + canvas->clipRect(SkRect::MakeWH(100, 100)); + canvas->drawPaint(third_paint); + + StopRecording(picture.get(), canvas); + + std::vector<skia::LazyPixelRefUtils::PositionLazyPixelRef> pixel_refs; + skia::LazyPixelRefUtils::GatherPixelRefs(picture.get(), &pixel_refs); + + EXPECT_EQ(3u, pixel_refs.size()); + EXPECT_FLOAT_RECT_EQ(gfx::RectF(0, 0, 256, 256), + gfx::SkRectToRectF(pixel_refs[0].pixel_ref_rect)); + EXPECT_FLOAT_RECT_EQ(gfx::RectF(34, 45, 56, 67), + gfx::SkRectToRectF(pixel_refs[1].pixel_ref_rect)); + EXPECT_FLOAT_RECT_EQ(gfx::RectF(34, 45, 56, 55), + gfx::SkRectToRectF(pixel_refs[2].pixel_ref_rect)); +} + +TEST(LazyPixelRefUtilsTest, DrawPoints) { + gfx::Rect layer_rect(0, 0, 256, 256); + + skia::RefPtr<SkPicture> picture = skia::AdoptRef(new SkPicture); + SkCanvas* canvas = StartRecording(picture.get(), layer_rect); + + TestLazyShader first_shader; + SkPaint first_paint; + first_paint.setShader(&first_shader); + + TestLazyShader second_shader; + SkPaint second_paint; + second_paint.setShader(&second_shader); + + TestLazyShader third_shader; + SkPaint third_paint; + third_paint.setShader(&third_shader); + + SkPoint points[3]; + points[0].set(10, 10); + points[1].set(100, 20); + points[2].set(50, 100); + // (10, 10, 90, 90). + canvas->drawPoints(SkCanvas::kPolygon_PointMode, 3, points, first_paint); + + canvas->save(); + + canvas->clipRect(SkRect::MakeWH(50, 50)); + // (10, 10, 40, 40). + canvas->drawPoints(SkCanvas::kPolygon_PointMode, 3, points, second_paint); + + canvas->restore(); + + points[0].set(50, 55); + points[1].set(50, 55); + points[2].set(200, 200); + // (50, 55, 150, 145). + canvas->drawPoints(SkCanvas::kPolygon_PointMode, 3, points, third_paint); + + StopRecording(picture.get(), canvas); + + std::vector<skia::LazyPixelRefUtils::PositionLazyPixelRef> pixel_refs; + skia::LazyPixelRefUtils::GatherPixelRefs(picture.get(), &pixel_refs); + + EXPECT_EQ(3u, pixel_refs.size()); + EXPECT_FLOAT_RECT_EQ(gfx::RectF(10, 10, 90, 90), + gfx::SkRectToRectF(pixel_refs[0].pixel_ref_rect)); + EXPECT_FLOAT_RECT_EQ(gfx::RectF(10, 10, 40, 40), + gfx::SkRectToRectF(pixel_refs[1].pixel_ref_rect)); + EXPECT_FLOAT_RECT_EQ(gfx::RectF(50, 55, 150, 145), + gfx::SkRectToRectF(pixel_refs[2].pixel_ref_rect)); +} + +TEST(LazyPixelRefUtilsTest, DrawRect) { + gfx::Rect layer_rect(0, 0, 256, 256); + + skia::RefPtr<SkPicture> picture = skia::AdoptRef(new SkPicture); + SkCanvas* canvas = StartRecording(picture.get(), layer_rect); + + TestLazyShader first_shader; + SkPaint first_paint; + first_paint.setShader(&first_shader); + + TestLazyShader second_shader; + SkPaint second_paint; + second_paint.setShader(&second_shader); + + TestLazyShader third_shader; + SkPaint third_paint; + third_paint.setShader(&third_shader); + + // (10, 20, 30, 40). + canvas->drawRect(SkRect::MakeXYWH(10, 20, 30, 40), first_paint); + + canvas->save(); + + canvas->translate(5, 17); + // (5, 50, 25, 35) + canvas->drawRect(SkRect::MakeXYWH(0, 33, 25, 35), second_paint); + + canvas->restore(); + + canvas->clipRect(SkRect::MakeXYWH(50, 50, 50, 50)); + canvas->translate(20, 20); + // (50, 50, 50, 50) + canvas->drawRect(SkRect::MakeXYWH(0, 0, 100, 100), third_paint); + + std::vector<skia::LazyPixelRefUtils::PositionLazyPixelRef> pixel_refs; + skia::LazyPixelRefUtils::GatherPixelRefs(picture.get(), &pixel_refs); + + EXPECT_EQ(3u, pixel_refs.size()); + EXPECT_FLOAT_RECT_EQ(gfx::RectF(10, 20, 30, 40), + gfx::SkRectToRectF(pixel_refs[0].pixel_ref_rect)); + EXPECT_FLOAT_RECT_EQ(gfx::RectF(5, 50, 25, 35), + gfx::SkRectToRectF(pixel_refs[1].pixel_ref_rect)); + EXPECT_FLOAT_RECT_EQ(gfx::RectF(50, 50, 50, 50), + gfx::SkRectToRectF(pixel_refs[2].pixel_ref_rect)); +} + +TEST(LazyPixelRefUtilsTest, DrawRRect) { + gfx::Rect layer_rect(0, 0, 256, 256); + + skia::RefPtr<SkPicture> picture = skia::AdoptRef(new SkPicture); + SkCanvas* canvas = StartRecording(picture.get(), layer_rect); + + TestLazyShader first_shader; + SkPaint first_paint; + first_paint.setShader(&first_shader); + + TestLazyShader second_shader; + SkPaint second_paint; + second_paint.setShader(&second_shader); + + TestLazyShader third_shader; + SkPaint third_paint; + third_paint.setShader(&third_shader); + + SkRRect rrect; + rrect.setRect(SkRect::MakeXYWH(10, 20, 30, 40)); + + // (10, 20, 30, 40). + canvas->drawRRect(rrect, first_paint); + + canvas->save(); + + canvas->translate(5, 17); + rrect.setRect(SkRect::MakeXYWH(0, 33, 25, 35)); + // (5, 50, 25, 35) + canvas->drawRRect(rrect, second_paint); + + canvas->restore(); + + canvas->clipRect(SkRect::MakeXYWH(50, 50, 50, 50)); + canvas->translate(20, 20); + rrect.setRect(SkRect::MakeXYWH(0, 0, 100, 100)); + // (50, 50, 50, 50) + canvas->drawRRect(rrect, third_paint); + + std::vector<skia::LazyPixelRefUtils::PositionLazyPixelRef> pixel_refs; + skia::LazyPixelRefUtils::GatherPixelRefs(picture.get(), &pixel_refs); + + EXPECT_EQ(3u, pixel_refs.size()); + EXPECT_FLOAT_RECT_EQ(gfx::RectF(10, 20, 30, 40), + gfx::SkRectToRectF(pixel_refs[0].pixel_ref_rect)); + EXPECT_FLOAT_RECT_EQ(gfx::RectF(5, 50, 25, 35), + gfx::SkRectToRectF(pixel_refs[1].pixel_ref_rect)); + EXPECT_FLOAT_RECT_EQ(gfx::RectF(50, 50, 50, 50), + gfx::SkRectToRectF(pixel_refs[2].pixel_ref_rect)); +} + +TEST(LazyPixelRefUtilsTest, DrawOval) { + gfx::Rect layer_rect(0, 0, 256, 256); + + skia::RefPtr<SkPicture> picture = skia::AdoptRef(new SkPicture); + SkCanvas* canvas = StartRecording(picture.get(), layer_rect); + + TestLazyShader first_shader; + SkPaint first_paint; + first_paint.setShader(&first_shader); + + TestLazyShader second_shader; + SkPaint second_paint; + second_paint.setShader(&second_shader); + + TestLazyShader third_shader; + SkPaint third_paint; + third_paint.setShader(&third_shader); + + canvas->save(); + + canvas->scale(2, 0.5); + // (20, 10, 60, 20). + canvas->drawOval(SkRect::MakeXYWH(10, 20, 30, 40), first_paint); + + canvas->restore(); + canvas->save(); + + canvas->translate(1, 2); + // (1, 35, 25, 35) + canvas->drawRect(SkRect::MakeXYWH(0, 33, 25, 35), second_paint); + + canvas->restore(); + + canvas->clipRect(SkRect::MakeXYWH(50, 50, 50, 50)); + canvas->translate(20, 20); + // (50, 50, 50, 50) + canvas->drawRect(SkRect::MakeXYWH(0, 0, 100, 100), third_paint); + + std::vector<skia::LazyPixelRefUtils::PositionLazyPixelRef> pixel_refs; + skia::LazyPixelRefUtils::GatherPixelRefs(picture.get(), &pixel_refs); + + EXPECT_EQ(3u, pixel_refs.size()); + EXPECT_FLOAT_RECT_EQ(gfx::RectF(20, 10, 60, 20), + gfx::SkRectToRectF(pixel_refs[0].pixel_ref_rect)); + EXPECT_FLOAT_RECT_EQ(gfx::RectF(1, 35, 25, 35), + gfx::SkRectToRectF(pixel_refs[1].pixel_ref_rect)); + EXPECT_FLOAT_RECT_EQ(gfx::RectF(50, 50, 50, 50), + gfx::SkRectToRectF(pixel_refs[2].pixel_ref_rect)); +} + +TEST(LazyPixelRefUtilsTest, DrawPath) { + gfx::Rect layer_rect(0, 0, 256, 256); + + skia::RefPtr<SkPicture> picture = skia::AdoptRef(new SkPicture); + SkCanvas* canvas = StartRecording(picture.get(), layer_rect); + + TestLazyShader first_shader; + SkPaint first_paint; + first_paint.setShader(&first_shader); + + TestLazyShader second_shader; + SkPaint second_paint; + second_paint.setShader(&second_shader); + + SkPath path; + path.moveTo(12, 13); + path.lineTo(50, 50); + path.lineTo(22, 101); + + // (12, 13, 38, 88). + canvas->drawPath(path, first_paint); + + canvas->save(); + canvas->clipRect(SkRect::MakeWH(50, 50)); + + // (12, 13, 38, 37). + canvas->drawPath(path, second_paint); + + canvas->restore(); + + StopRecording(picture.get(), canvas); + + std::vector<skia::LazyPixelRefUtils::PositionLazyPixelRef> pixel_refs; + skia::LazyPixelRefUtils::GatherPixelRefs(picture.get(), &pixel_refs); + + EXPECT_EQ(2u, pixel_refs.size()); + EXPECT_FLOAT_RECT_EQ(gfx::RectF(12, 13, 38, 88), + gfx::SkRectToRectF(pixel_refs[0].pixel_ref_rect)); + EXPECT_FLOAT_RECT_EQ(gfx::RectF(12, 13, 38, 37), + gfx::SkRectToRectF(pixel_refs[1].pixel_ref_rect)); +} + +TEST(LazyPixelRefUtilsTest, DrawBitmap) { + gfx::Rect layer_rect(0, 0, 256, 256); + + skia::RefPtr<SkPicture> picture = skia::AdoptRef(new SkPicture); + SkCanvas* canvas = StartRecording(picture.get(), layer_rect); + + SkBitmap first; + CreateBitmap(gfx::Size(50, 50), "lazy", &first); + SkBitmap second; + CreateBitmap(gfx::Size(50, 50), "lazy", &second); + SkBitmap third; + CreateBitmap(gfx::Size(50, 50), "lazy", &third); + SkBitmap fourth; + CreateBitmap(gfx::Size(50, 1), "lazy", &fourth); + SkBitmap fifth; + CreateBitmap(gfx::Size(10, 10), "lazy", &fifth); + + canvas->save(); + + // At (0, 0). + canvas->drawBitmap(first, 0, 0); + canvas->translate(25, 0); + // At (25, 0). + canvas->drawBitmap(second, 0, 0); + canvas->translate(0, 50); + // At (50, 50). + canvas->drawBitmap(third, 25, 0); + + canvas->restore(); + canvas->save(); + + canvas->rotate(90); + // At (0, 0), rotated 90 degrees + canvas->drawBitmap(fourth, 0, 0); + + canvas->restore(); + + canvas->scale(5, 6); + // At (0, 0), scaled by 5 and 6 + canvas->drawBitmap(fifth, 0, 0); + + StopRecording(picture.get(), canvas); + + std::vector<skia::LazyPixelRefUtils::PositionLazyPixelRef> pixel_refs; + skia::LazyPixelRefUtils::GatherPixelRefs(picture.get(), &pixel_refs); + + EXPECT_EQ(5u, pixel_refs.size()); + EXPECT_FLOAT_RECT_EQ(gfx::RectF(0, 0, 50, 50), + gfx::SkRectToRectF(pixel_refs[0].pixel_ref_rect)); + EXPECT_FLOAT_RECT_EQ(gfx::RectF(25, 0, 50, 50), + gfx::SkRectToRectF(pixel_refs[1].pixel_ref_rect)); + EXPECT_FLOAT_RECT_EQ(gfx::RectF(50, 50, 50, 50), + gfx::SkRectToRectF(pixel_refs[2].pixel_ref_rect)); + EXPECT_FLOAT_RECT_EQ(gfx::RectF(-1, 0, 1, 50), + gfx::SkRectToRectF(pixel_refs[3].pixel_ref_rect)); + EXPECT_FLOAT_RECT_EQ(gfx::RectF(0, 0, 50, 60), + gfx::SkRectToRectF(pixel_refs[4].pixel_ref_rect)); + +} + +TEST(LazyPixelRefUtilsTest, DrawBitmapRect) { + gfx::Rect layer_rect(0, 0, 256, 256); + + skia::RefPtr<SkPicture> picture = skia::AdoptRef(new SkPicture); + SkCanvas* canvas = StartRecording(picture.get(), layer_rect); + + SkBitmap first; + CreateBitmap(gfx::Size(50, 50), "lazy", &first); + SkBitmap second; + CreateBitmap(gfx::Size(50, 50), "lazy", &second); + SkBitmap third; + CreateBitmap(gfx::Size(50, 50), "lazy", &third); + + TestLazyShader first_shader; + SkPaint first_paint; + first_paint.setShader(&first_shader); + + SkPaint non_lazy_paint; + + canvas->save(); + + // (0, 0, 100, 100). + canvas->drawBitmapRect(first, SkRect::MakeWH(100, 100), &non_lazy_paint); + canvas->translate(25, 0); + // (75, 50, 10, 10). + canvas->drawBitmapRect( + second, SkRect::MakeXYWH(50, 50, 10, 10), &non_lazy_paint); + canvas->translate(5, 50); + // (0, 30, 100, 100). One from bitmap, one from paint. + canvas->drawBitmapRect( + third, SkRect::MakeXYWH(-30, -20, 100, 100), &first_paint); + + canvas->restore(); + + StopRecording(picture.get(), canvas); + + std::vector<skia::LazyPixelRefUtils::PositionLazyPixelRef> pixel_refs; + skia::LazyPixelRefUtils::GatherPixelRefs(picture.get(), &pixel_refs); + + EXPECT_EQ(4u, pixel_refs.size()); + EXPECT_FLOAT_RECT_EQ(gfx::RectF(0, 0, 100, 100), + gfx::SkRectToRectF(pixel_refs[0].pixel_ref_rect)); + EXPECT_FLOAT_RECT_EQ(gfx::RectF(75, 50, 10, 10), + gfx::SkRectToRectF(pixel_refs[1].pixel_ref_rect)); + EXPECT_FLOAT_RECT_EQ(gfx::RectF(0, 30, 100, 100), + gfx::SkRectToRectF(pixel_refs[2].pixel_ref_rect)); + EXPECT_FLOAT_RECT_EQ(gfx::RectF(0, 30, 100, 100), + gfx::SkRectToRectF(pixel_refs[3].pixel_ref_rect)); +} + +TEST(LazyPixelRefUtilsTest, DrawSprite) { + gfx::Rect layer_rect(0, 0, 256, 256); + + skia::RefPtr<SkPicture> picture = skia::AdoptRef(new SkPicture); + SkCanvas* canvas = StartRecording(picture.get(), layer_rect); + + SkBitmap first; + CreateBitmap(gfx::Size(50, 50), "lazy", &first); + SkBitmap second; + CreateBitmap(gfx::Size(50, 50), "lazy", &second); + SkBitmap third; + CreateBitmap(gfx::Size(50, 50), "lazy", &third); + SkBitmap fourth; + CreateBitmap(gfx::Size(50, 50), "lazy", &fourth); + SkBitmap fifth; + CreateBitmap(gfx::Size(50, 50), "lazy", &fifth); + + canvas->save(); + + // Sprites aren't affected by the current matrix. + + // (0, 0, 50, 50). + canvas->drawSprite(first, 0, 0); + canvas->translate(25, 0); + // (10, 0, 50, 50). + canvas->drawSprite(second, 10, 0); + canvas->translate(0, 50); + // (25, 0, 50, 50). + canvas->drawSprite(third, 25, 0); + + canvas->restore(); + canvas->save(); + + canvas->rotate(90); + // (0, 0, 50, 50). + canvas->drawSprite(fourth, 0, 0); + + canvas->restore(); + + TestLazyShader first_shader; + SkPaint first_paint; + first_paint.setShader(&first_shader); + + canvas->scale(5, 6); + // (100, 100, 50, 50). + canvas->drawSprite(fifth, 100, 100, &first_paint); + + StopRecording(picture.get(), canvas); + + std::vector<skia::LazyPixelRefUtils::PositionLazyPixelRef> pixel_refs; + skia::LazyPixelRefUtils::GatherPixelRefs(picture.get(), &pixel_refs); + + EXPECT_EQ(6u, pixel_refs.size()); + EXPECT_FLOAT_RECT_EQ(gfx::RectF(0, 0, 50, 50), + gfx::SkRectToRectF(pixel_refs[0].pixel_ref_rect)); + EXPECT_FLOAT_RECT_EQ(gfx::RectF(10, 0, 50, 50), + gfx::SkRectToRectF(pixel_refs[1].pixel_ref_rect)); + EXPECT_FLOAT_RECT_EQ(gfx::RectF(25, 0, 50, 50), + gfx::SkRectToRectF(pixel_refs[2].pixel_ref_rect)); + EXPECT_FLOAT_RECT_EQ(gfx::RectF(0, 0, 50, 50), + gfx::SkRectToRectF(pixel_refs[3].pixel_ref_rect)); + EXPECT_FLOAT_RECT_EQ(gfx::RectF(100, 100, 50, 50), + gfx::SkRectToRectF(pixel_refs[4].pixel_ref_rect)); + EXPECT_FLOAT_RECT_EQ(gfx::RectF(100, 100, 50, 50), + gfx::SkRectToRectF(pixel_refs[5].pixel_ref_rect)); +} + +TEST(LazyPixelRefUtilsTest, DrawText) { + gfx::Rect layer_rect(0, 0, 256, 256); + + skia::RefPtr<SkPicture> picture = skia::AdoptRef(new SkPicture); + SkCanvas* canvas = StartRecording(picture.get(), layer_rect); + + TestLazyShader first_shader; + SkPaint first_paint; + first_paint.setShader(&first_shader); + + SkPoint points[4]; + points[0].set(10, 50); + points[1].set(20, 50); + points[2].set(30, 50); + points[3].set(40, 50); + + SkPath path; + path.moveTo(10, 50); + path.lineTo(20, 50); + path.lineTo(30, 50); + path.lineTo(40, 50); + path.lineTo(50, 50); + + canvas->drawText("text", 4, 50, 50, first_paint); + canvas->drawPosText("text", 4, points, first_paint); + canvas->drawTextOnPath("text", 4, path, NULL, first_paint); + + std::vector<skia::LazyPixelRefUtils::PositionLazyPixelRef> pixel_refs; + skia::LazyPixelRefUtils::GatherPixelRefs(picture.get(), &pixel_refs); + + EXPECT_EQ(3u, pixel_refs.size()); +} + +TEST(LazyPixelRefUtilsTest, DrawVertices) { + gfx::Rect layer_rect(0, 0, 256, 256); + + skia::RefPtr<SkPicture> picture = skia::AdoptRef(new SkPicture); + SkCanvas* canvas = StartRecording(picture.get(), layer_rect); + + TestLazyShader first_shader; + SkPaint first_paint; + first_paint.setShader(&first_shader); + + TestLazyShader second_shader; + SkPaint second_paint; + second_paint.setShader(&second_shader); + + TestLazyShader third_shader; + SkPaint third_paint; + third_paint.setShader(&third_shader); + + SkPoint points[3]; + SkColor colors[3]; + uint16_t indecies[3] = {0, 1, 2}; + points[0].set(10, 10); + points[1].set(100, 20); + points[2].set(50, 100); + // (10, 10, 90, 90). + canvas->drawVertices(SkCanvas::kTriangles_VertexMode, + 3, + points, + points, + colors, + NULL, + indecies, + 3, + first_paint); + + canvas->save(); + + canvas->clipRect(SkRect::MakeWH(50, 50)); + // (10, 10, 40, 40). + canvas->drawVertices(SkCanvas::kTriangles_VertexMode, + 3, + points, + points, + colors, + NULL, + indecies, + 3, + second_paint); + + canvas->restore(); + + points[0].set(50, 55); + points[1].set(50, 55); + points[2].set(200, 200); + // (50, 55, 150, 145). + canvas->drawVertices(SkCanvas::kTriangles_VertexMode, + 3, + points, + points, + colors, + NULL, + indecies, + 3, + third_paint); + + StopRecording(picture.get(), canvas); + + std::vector<skia::LazyPixelRefUtils::PositionLazyPixelRef> pixel_refs; + skia::LazyPixelRefUtils::GatherPixelRefs(picture.get(), &pixel_refs); + + EXPECT_EQ(3u, pixel_refs.size()); + EXPECT_FLOAT_RECT_EQ(gfx::RectF(10, 10, 90, 90), + gfx::SkRectToRectF(pixel_refs[0].pixel_ref_rect)); + EXPECT_FLOAT_RECT_EQ(gfx::RectF(10, 10, 40, 40), + gfx::SkRectToRectF(pixel_refs[1].pixel_ref_rect)); + EXPECT_FLOAT_RECT_EQ(gfx::RectF(50, 55, 150, 145), + gfx::SkRectToRectF(pixel_refs[2].pixel_ref_rect)); +} + +} // namespace skia diff --git a/chromium/skia/ext/paint_simplifier.cc b/chromium/skia/ext/paint_simplifier.cc new file mode 100644 index 00000000000..6b697662458 --- /dev/null +++ b/chromium/skia/ext/paint_simplifier.cc @@ -0,0 +1,41 @@ +// 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 "skia/ext/paint_simplifier.h" +#include "third_party/skia/include/core/SkPaint.h" + +namespace skia { + +PaintSimplifier::PaintSimplifier() + : INHERITED() { + +} + +PaintSimplifier::~PaintSimplifier() { + +} + +bool PaintSimplifier::filter(SkPaint* paint, Type type) { + + // Preserve a modicum of text quality; black & white text is + // just too blocky, even during a fling. + if (type != kText_Type) { + paint->setAntiAlias(false); + } + paint->setSubpixelText(false); + paint->setLCDRenderText(false); + + paint->setFilterBitmap(false); + paint->setMaskFilter(NULL); + + // Uncomment this line to shade simplified tiles pink during debugging. + //paint->setColor(SkColorSetRGB(255, 127, 127)); + + return true; +} + + +} // namespace skia + + diff --git a/chromium/skia/ext/paint_simplifier.h b/chromium/skia/ext/paint_simplifier.h new file mode 100644 index 00000000000..0eff833727d --- /dev/null +++ b/chromium/skia/ext/paint_simplifier.h @@ -0,0 +1,33 @@ +// 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 SKIA_EXT_PAINT_SIMPLIFIER_H +#define SKIA_EXT_PAINT_SIMPLIFIER_H + +#include "base/values.h" +#include "third_party/skia/include/core/SkDrawFilter.h" + +class SkPaint; + +namespace skia { + +/* + When installed on a SkCanvas, reduces the quality of all draws + to that canvas. This improves rasterization speed during flings. + We turn off blurs, filters, and antialiasing *except for* text. +*/ +class SK_API PaintSimplifier : public SkDrawFilter { + public: + PaintSimplifier(); + virtual ~PaintSimplifier(); + virtual bool filter(SkPaint*, Type) OVERRIDE; + + private: + typedef SkDrawFilter INHERITED; +}; + +} // namespace skia + +#endif // SKIA_EXT_PAINT_SIMPLIFIER_H + diff --git a/chromium/skia/ext/platform_canvas.cc b/chromium/skia/ext/platform_canvas.cc new file mode 100644 index 00000000000..6a2548100bc --- /dev/null +++ b/chromium/skia/ext/platform_canvas.cc @@ -0,0 +1,78 @@ +// 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 "skia/ext/platform_canvas.h" + +#include "skia/ext/bitmap_platform_device.h" +#include "third_party/skia/include/core/SkTypes.h" + +namespace skia { + +SkDevice* GetTopDevice(const SkCanvas& canvas) { + return canvas.getTopDevice(true); +} + +bool SupportsPlatformPaint(const SkCanvas* canvas) { + PlatformDevice* platform_device = GetPlatformDevice(GetTopDevice(*canvas)); + return platform_device && platform_device->SupportsPlatformPaint(); +} + +PlatformSurface BeginPlatformPaint(SkCanvas* canvas) { + PlatformDevice* platform_device = GetPlatformDevice(GetTopDevice(*canvas)); + if (platform_device) + return platform_device->BeginPlatformPaint(); + + return 0; +} + +void EndPlatformPaint(SkCanvas* canvas) { + PlatformDevice* platform_device = GetPlatformDevice(GetTopDevice(*canvas)); + if (platform_device) + platform_device->EndPlatformPaint(); +} + +void DrawToNativeContext(SkCanvas* canvas, PlatformSurface context, int x, + int y, const PlatformRect* src_rect) { + PlatformDevice* platform_device = GetPlatformDevice(GetTopDevice(*canvas)); + if (platform_device) + platform_device->DrawToNativeContext(context, x, y, src_rect); +} + +static SkPMColor MakeOpaqueXfermodeProc(SkPMColor src, SkPMColor dst) { + return dst | (0xFF << SK_A32_SHIFT); +} + +void MakeOpaque(SkCanvas* canvas, int x, int y, int width, int height) { + if (width <= 0 || height <= 0) + return; + + SkRect rect; + rect.setXYWH(SkIntToScalar(x), SkIntToScalar(y), + SkIntToScalar(width), SkIntToScalar(height)); + SkPaint paint; + // so we don't draw anything on a device that ignores xfermodes + paint.setColor(0); + // install our custom mode + skia::RefPtr<SkProcXfermode> xfermode = + skia::AdoptRef(new SkProcXfermode(MakeOpaqueXfermodeProc)); + paint.setXfermode(xfermode.get()); + canvas->drawRect(rect, paint); +} + +size_t PlatformCanvasStrideForWidth(unsigned width) { + return 4 * width; +} + +SkCanvas* CreateCanvas(const skia::RefPtr<SkDevice>& device, OnFailureType failureType) { + if (!device) { + if (CRASH_ON_FAILURE == failureType) + SK_CRASH(); + return NULL; + } + return new SkCanvas(device.get()); +} + +PlatformBitmap::PlatformBitmap() : surface_(0), platform_extra_(0) {} + +} // namespace skia diff --git a/chromium/skia/ext/platform_canvas.h b/chromium/skia/ext/platform_canvas.h new file mode 100644 index 00000000000..9e2bc823794 --- /dev/null +++ b/chromium/skia/ext/platform_canvas.h @@ -0,0 +1,188 @@ +// 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 SKIA_EXT_PLATFORM_CANVAS_H_ +#define SKIA_EXT_PLATFORM_CANVAS_H_ + +// The platform-specific device will include the necessary platform headers +// to get the surface type. +#include "base/basictypes.h" +#include "skia/ext/platform_device.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/SkPixelRef.h" + +namespace skia { + +typedef SkCanvas PlatformCanvas; + +// +// Note about error handling. +// +// Creating a canvas can fail at times, most often because we fail to allocate +// the backing-store (pixels). This can be from out-of-memory, or something +// more opaque, like GDI or cairo reported a failure. +// +// To allow the caller to handle the failure, every Create... factory takes an +// enum as its last parameter. The default value is kCrashOnFailure. If the +// caller passes kReturnNullOnFailure, then the caller is responsible to check +// the return result. +// +enum OnFailureType { + CRASH_ON_FAILURE, + RETURN_NULL_ON_FAILURE +}; + +#if defined(WIN32) + // The shared_section parameter is passed to gfx::PlatformDevice::create. + // See it for details. + SK_API SkCanvas* CreatePlatformCanvas(int width, + int height, + bool is_opaque, + HANDLE shared_section, + OnFailureType failure_type); +#elif defined(__APPLE__) + SK_API SkCanvas* CreatePlatformCanvas(CGContextRef context, + int width, + int height, + bool is_opaque, + OnFailureType failure_type); + + SK_API SkCanvas* CreatePlatformCanvas(int width, + int height, + bool is_opaque, + uint8_t* context, + OnFailureType failure_type); +#elif defined(__linux__) || defined(__FreeBSD__) || defined(__OpenBSD__) || \ + defined(__sun) || defined(ANDROID) + // Linux --------------------------------------------------------------------- + + // Construct a canvas from the given memory region. The memory is not cleared + // first. @data must be, at least, @height * StrideForWidth(@width) bytes. + SK_API SkCanvas* CreatePlatformCanvas(int width, + int height, + bool is_opaque, + uint8_t* data, + OnFailureType failure_type); +#endif + +static inline SkCanvas* CreatePlatformCanvas(int width, + int height, + bool is_opaque) { + return CreatePlatformCanvas(width, height, is_opaque, 0, CRASH_ON_FAILURE); +} + +SK_API SkCanvas* CreateCanvas(const skia::RefPtr<SkDevice>& device, + OnFailureType failure_type); + +static inline SkCanvas* CreateBitmapCanvas(int width, + int height, + bool is_opaque) { + return CreatePlatformCanvas(width, height, is_opaque, 0, CRASH_ON_FAILURE); +} + +static inline SkCanvas* TryCreateBitmapCanvas(int width, + int height, + bool is_opaque) { + return CreatePlatformCanvas(width, height, is_opaque, 0, + RETURN_NULL_ON_FAILURE); +} + +// Return the stride (length of a line in bytes) for the given width. Because +// we use 32-bits per pixel, this will be roughly 4*width. However, for +// alignment reasons we may wish to increase that. +SK_API size_t PlatformCanvasStrideForWidth(unsigned width); + +// Returns the SkDevice pointer of the topmost rect with a non-empty +// clip. In practice, this is usually either the top layer or nothing, since +// we usually set the clip to new layers when we make them. +// +// This may return NULL, so callers need to check. +// +// This is different than SkCanvas' getDevice, because that returns the +// bottommost device. +// +// Danger: the resulting device should not be saved. It will be invalidated +// by the next call to save() or restore(). +SK_API SkDevice* GetTopDevice(const SkCanvas& canvas); + +// Returns true if native platform routines can be used to draw on the +// given canvas. If this function returns false, BeginPlatformPaint will +// return NULL PlatformSurface. +SK_API bool SupportsPlatformPaint(const SkCanvas* canvas); + +// Draws into the a native platform surface, |context|. Forwards to +// DrawToNativeContext on a PlatformDevice instance bound to the top device. +// If no PlatformDevice instance is bound, is a no-operation. +SK_API void DrawToNativeContext(SkCanvas* canvas, + PlatformSurface context, + int x, + int y, + const PlatformRect* src_rect); + +// Sets the opacity of each pixel in the specified region to be opaque. +SK_API void MakeOpaque(SkCanvas* canvas, int x, int y, int width, int height); + +// These calls should surround calls to platform drawing routines, the +// surface returned here can be used with the native platform routines. +// +// Call EndPlatformPaint when you are done and want to use skia operations +// after calling the platform-specific BeginPlatformPaint; this will +// synchronize the bitmap to OS if necessary. +SK_API PlatformSurface BeginPlatformPaint(SkCanvas* canvas); +SK_API void EndPlatformPaint(SkCanvas* canvas); + +// Helper class for pairing calls to BeginPlatformPaint and EndPlatformPaint. +// Upon construction invokes BeginPlatformPaint, and upon destruction invokes +// EndPlatformPaint. +class SK_API ScopedPlatformPaint { + public: + explicit ScopedPlatformPaint(SkCanvas* canvas) : canvas_(canvas) { + platform_surface_ = BeginPlatformPaint(canvas); + } + ~ScopedPlatformPaint() { EndPlatformPaint(canvas_); } + + // Returns the PlatformSurface to use for native platform drawing calls. + PlatformSurface GetPlatformSurface() { return platform_surface_; } + private: + SkCanvas* canvas_; + PlatformSurface platform_surface_; + + // Disallow copy and assign + ScopedPlatformPaint(const ScopedPlatformPaint&); + ScopedPlatformPaint& operator=(const ScopedPlatformPaint&); +}; + +// PlatformBitmap holds a PlatformSurface that can also be used as an SkBitmap. +class SK_API PlatformBitmap { + public: + PlatformBitmap(); + ~PlatformBitmap(); + + // Returns true if the bitmap was able to allocate its surface. + bool Allocate(int width, int height, bool is_opaque); + + // Returns the platform surface, or 0 if Allocate() did not return true. + PlatformSurface GetSurface() { return surface_; } + + // Return the skia bitmap, which will be empty if Allocate() did not + // return true. + // + // The resulting SkBitmap holds a refcount on the underlying platform surface, + // so the surface will remain allocated so long as the SkBitmap or its copies + // stay around. + const SkBitmap& GetBitmap() { return bitmap_; } + + private: + SkBitmap bitmap_; + PlatformSurface surface_; // initialized to 0 + intptr_t platform_extra_; // platform specific, initialized to 0 + + DISALLOW_COPY_AND_ASSIGN(PlatformBitmap); +}; + +} // namespace skia + +#endif // SKIA_EXT_PLATFORM_CANVAS_H_ diff --git a/chromium/skia/ext/platform_canvas_unittest.cc b/chromium/skia/ext/platform_canvas_unittest.cc new file mode 100644 index 00000000000..7595bcaca2b --- /dev/null +++ b/chromium/skia/ext/platform_canvas_unittest.cc @@ -0,0 +1,459 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// TODO(awalker): clean up the const/non-const reference handling in this test + +#include "build/build_config.h" + +#if defined(OS_MACOSX) +#import <ApplicationServices/ApplicationServices.h> +#endif + +#if !defined(OS_WIN) +#include <unistd.h> +#endif + +#include "base/memory/scoped_ptr.h" +#include "skia/ext/platform_canvas.h" +#include "skia/ext/platform_device.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "third_party/skia/include/core/SkBitmap.h" +#include "third_party/skia/include/core/SkColor.h" +#include "third_party/skia/include/core/SkPixelRef.h" + +namespace skia { + +namespace { + +// Return true if the canvas is filled to canvas_color, and contains a single +// rectangle filled to rect_color. This function ignores the alpha channel, +// since Windows will sometimes clear the alpha channel when drawing, and we +// will fix that up later in cases it's necessary. +bool VerifyRect(const PlatformCanvas& canvas, + uint32_t canvas_color, uint32_t rect_color, + int x, int y, int w, int h) { + SkDevice* device = skia::GetTopDevice(canvas); + const SkBitmap& bitmap = device->accessBitmap(false); + SkAutoLockPixels lock(bitmap); + + // For masking out the alpha values. + uint32_t alpha_mask = 0xFF << SK_A32_SHIFT; + + for (int cur_y = 0; cur_y < bitmap.height(); cur_y++) { + for (int cur_x = 0; cur_x < bitmap.width(); cur_x++) { + if (cur_x >= x && cur_x < x + w && + cur_y >= y && cur_y < y + h) { + // Inside the square should be rect_color + if ((*bitmap.getAddr32(cur_x, cur_y) | alpha_mask) != + (rect_color | alpha_mask)) + return false; + } else { + // Outside the square should be canvas_color + if ((*bitmap.getAddr32(cur_x, cur_y) | alpha_mask) != + (canvas_color | alpha_mask)) + return false; + } + } + } + return true; +} + +#if !defined(OS_MACOSX) +bool IsOfColor(const SkBitmap& bitmap, int x, int y, uint32_t color) { + // For masking out the alpha values. + static uint32_t alpha_mask = 0xFF << SK_A32_SHIFT; + return (*bitmap.getAddr32(x, y) | alpha_mask) == (color | alpha_mask); +} + +// Return true if canvas has something that passes for a rounded-corner +// rectangle. Basically, we're just checking to make sure that the pixels in the +// middle are of rect_color and pixels in the corners are of canvas_color. +bool VerifyRoundedRect(const PlatformCanvas& canvas, + uint32_t canvas_color, uint32_t rect_color, + int x, int y, int w, int h) { + SkDevice* device = skia::GetTopDevice(canvas); + const SkBitmap& bitmap = device->accessBitmap(false); + SkAutoLockPixels lock(bitmap); + + // Check corner points first. They should be of canvas_color. + if (!IsOfColor(bitmap, x, y, canvas_color)) return false; + if (!IsOfColor(bitmap, x + w, y, canvas_color)) return false; + if (!IsOfColor(bitmap, x, y + h, canvas_color)) return false; + if (!IsOfColor(bitmap, x + w, y, canvas_color)) return false; + + // Check middle points. They should be of rect_color. + if (!IsOfColor(bitmap, (x + w / 2), y, rect_color)) return false; + if (!IsOfColor(bitmap, x, (y + h / 2), rect_color)) return false; + if (!IsOfColor(bitmap, x + w, (y + h / 2), rect_color)) return false; + if (!IsOfColor(bitmap, (x + w / 2), y + h, rect_color)) return false; + + return true; +} +#endif + +// Checks whether there is a white canvas with a black square at the given +// location in pixels (not in the canvas coordinate system). +bool VerifyBlackRect(const PlatformCanvas& canvas, int x, int y, int w, int h) { + return VerifyRect(canvas, SK_ColorWHITE, SK_ColorBLACK, x, y, w, h); +} + +// Check that every pixel in the canvas is a single color. +bool VerifyCanvasColor(const PlatformCanvas& canvas, uint32_t canvas_color) { + return VerifyRect(canvas, canvas_color, 0, 0, 0, 0, 0); +} + +#if defined(OS_WIN) +void DrawNativeRect(PlatformCanvas& canvas, int x, int y, int w, int h) { + skia::ScopedPlatformPaint scoped_platform_paint(&canvas); + HDC dc = scoped_platform_paint.GetPlatformSurface(); + + RECT inner_rc; + inner_rc.left = x; + inner_rc.top = y; + inner_rc.right = x + w; + inner_rc.bottom = y + h; + FillRect(dc, &inner_rc, reinterpret_cast<HBRUSH>(GetStockObject(BLACK_BRUSH))); +} +#elif defined(OS_MACOSX) +void DrawNativeRect(PlatformCanvas& canvas, int x, int y, int w, int h) { + skia::ScopedPlatformPaint scoped_platform_paint(&canvas); + CGContextRef context = scoped_platform_paint.GetPlatformSurface(); + + CGRect inner_rc = CGRectMake(x, y, w, h); + // RGBA opaque black + CGColorRef black = CGColorCreateGenericRGB(0.0, 0.0, 0.0, 1.0); + CGContextSetFillColorWithColor(context, black); + CGColorRelease(black); + CGContextFillRect(context, inner_rc); +} +#else +void DrawNativeRect(PlatformCanvas& canvas, int x, int y, int w, int h) { + notImplemented(); +} +#endif + +// Clips the contents of the canvas to the given rectangle. This will be +// intersected with any existing clip. +void AddClip(PlatformCanvas& canvas, int x, int y, int w, int h) { + SkRect rect; + rect.set(SkIntToScalar(x), SkIntToScalar(y), + SkIntToScalar(x + w), SkIntToScalar(y + h)); + canvas.clipRect(rect); +} + +class LayerSaver { + public: + LayerSaver(PlatformCanvas& canvas, int x, int y, int w, int h) + : canvas_(canvas), + x_(x), + y_(y), + w_(w), + h_(h) { + SkRect bounds; + bounds.set(SkIntToScalar(x_), SkIntToScalar(y_), + SkIntToScalar(right()), SkIntToScalar(bottom())); + canvas_.saveLayer(&bounds, NULL); + canvas.clear(SkColorSetARGB(0, 0, 0, 0)); + } + + ~LayerSaver() { + canvas_.restore(); + } + + int x() const { return x_; } + int y() const { return y_; } + int w() const { return w_; } + int h() const { return h_; } + + // Returns the EXCLUSIVE far bounds of the layer. + int right() const { return x_ + w_; } + int bottom() const { return y_ + h_; } + + private: + PlatformCanvas& canvas_; + int x_, y_, w_, h_; +}; + +// Size used for making layers in many of the below tests. +const int kLayerX = 2; +const int kLayerY = 3; +const int kLayerW = 9; +const int kLayerH = 7; + +// Size used by some tests to draw a rectangle inside the layer. +const int kInnerX = 4; +const int kInnerY = 5; +const int kInnerW = 2; +const int kInnerH = 3; + +// Radius used by some tests to draw a rounded-corner rectangle. +const SkScalar kRadius = 2.0; + +} + +// This just checks that our checking code is working properly, it just uses +// regular skia primitives. +TEST(PlatformCanvas, SkLayer) { + // Create the canvas initialized to opaque white. + RefPtr<SkCanvas> canvas = AdoptRef(CreatePlatformCanvas(16, 16, true)); + canvas->drawColor(SK_ColorWHITE); + + // Make a layer and fill it completely to make sure that the bounds are + // correct. + { + LayerSaver layer(*canvas, kLayerX, kLayerY, kLayerW, kLayerH); + canvas->drawColor(SK_ColorBLACK); + } + EXPECT_TRUE(VerifyBlackRect(*canvas, kLayerX, kLayerY, kLayerW, kLayerH)); +} + +#if !defined(USE_AURA) // http://crbug.com/154358 + +// Test native clipping. +TEST(PlatformCanvas, ClipRegion) { + // Initialize a white canvas + RefPtr<SkCanvas> canvas = AdoptRef(CreatePlatformCanvas(16, 16, true)); + canvas->drawColor(SK_ColorWHITE); + EXPECT_TRUE(VerifyCanvasColor(*canvas, SK_ColorWHITE)); + + // Test that initially the canvas has no clip region, by filling it + // with a black rectangle. + // Note: Don't use LayerSaver, since internally it sets a clip region. + DrawNativeRect(*canvas, 0, 0, 16, 16); + EXPECT_TRUE(VerifyCanvasColor(*canvas, SK_ColorBLACK)); + + // Test that intersecting disjoint clip rectangles sets an empty clip region + canvas->drawColor(SK_ColorWHITE); + EXPECT_TRUE(VerifyCanvasColor(*canvas, SK_ColorWHITE)); + { + LayerSaver layer(*canvas, 0, 0, 16, 16); + AddClip(*canvas, 2, 3, 4, 5); + AddClip(*canvas, 4, 9, 10, 10); + DrawNativeRect(*canvas, 0, 0, 16, 16); + } + EXPECT_TRUE(VerifyCanvasColor(*canvas, SK_ColorWHITE)); +} + +#endif // !defined(USE_AURA) + +// Test the layers get filled properly by native rendering. +TEST(PlatformCanvas, FillLayer) { + // Create the canvas initialized to opaque white. + RefPtr<SkCanvas> canvas = AdoptRef(CreatePlatformCanvas(16, 16, true)); + + // Make a layer and fill it completely to make sure that the bounds are + // correct. + canvas->drawColor(SK_ColorWHITE); + { + LayerSaver layer(*canvas, kLayerX, kLayerY, kLayerW, kLayerH); + DrawNativeRect(*canvas, 0, 0, 100, 100); +#if defined(OS_WIN) + MakeOpaque(canvas.get(), 0, 0, 100, 100); +#endif + } + EXPECT_TRUE(VerifyBlackRect(*canvas, kLayerX, kLayerY, kLayerW, kLayerH)); + + // Make a layer and fill it partially to make sure the translation is correct. + canvas->drawColor(SK_ColorWHITE); + { + LayerSaver layer(*canvas, kLayerX, kLayerY, kLayerW, kLayerH); + DrawNativeRect(*canvas, kInnerX, kInnerY, kInnerW, kInnerH); +#if defined(OS_WIN) + MakeOpaque(canvas.get(), kInnerX, kInnerY, kInnerW, kInnerH); +#endif + } + EXPECT_TRUE(VerifyBlackRect(*canvas, kInnerX, kInnerY, kInnerW, kInnerH)); + + // Add a clip on the layer and fill to make sure clip is correct. + canvas->drawColor(SK_ColorWHITE); + { + LayerSaver layer(*canvas, kLayerX, kLayerY, kLayerW, kLayerH); + canvas->save(); + AddClip(*canvas, kInnerX, kInnerY, kInnerW, kInnerH); + DrawNativeRect(*canvas, 0, 0, 100, 100); +#if defined(OS_WIN) + MakeOpaque(canvas.get(), kInnerX, kInnerY, kInnerW, kInnerH); +#endif + canvas->restore(); + } + EXPECT_TRUE(VerifyBlackRect(*canvas, kInnerX, kInnerY, kInnerW, kInnerH)); + + // Add a clip and then make the layer to make sure the clip is correct. + canvas->drawColor(SK_ColorWHITE); + canvas->save(); + AddClip(*canvas, kInnerX, kInnerY, kInnerW, kInnerH); + { + LayerSaver layer(*canvas, kLayerX, kLayerY, kLayerW, kLayerH); + DrawNativeRect(*canvas, 0, 0, 100, 100); +#if defined(OS_WIN) + MakeOpaque(canvas.get(), 0, 0, 100, 100); +#endif + } + canvas->restore(); + EXPECT_TRUE(VerifyBlackRect(*canvas, kInnerX, kInnerY, kInnerW, kInnerH)); +} + +#if !defined(USE_AURA) // http://crbug.com/154358 + +// Test that translation + make layer works properly. +TEST(PlatformCanvas, TranslateLayer) { + // Create the canvas initialized to opaque white. + RefPtr<SkCanvas> canvas = AdoptRef(CreatePlatformCanvas(16, 16, true)); + + // Make a layer and fill it completely to make sure that the bounds are + // correct. + canvas->drawColor(SK_ColorWHITE); + canvas->save(); + canvas->translate(1, 1); + { + LayerSaver layer(*canvas, kLayerX, kLayerY, kLayerW, kLayerH); + DrawNativeRect(*canvas, 0, 0, 100, 100); +#if defined(OS_WIN) + MakeOpaque(canvas.get(), 0, 0, 100, 100); +#endif + } + canvas->restore(); + EXPECT_TRUE(VerifyBlackRect(*canvas, kLayerX + 1, kLayerY + 1, + kLayerW, kLayerH)); + + // Translate then make the layer. + canvas->drawColor(SK_ColorWHITE); + canvas->save(); + canvas->translate(1, 1); + { + LayerSaver layer(*canvas, kLayerX, kLayerY, kLayerW, kLayerH); + DrawNativeRect(*canvas, kInnerX, kInnerY, kInnerW, kInnerH); +#if defined(OS_WIN) + MakeOpaque(canvas.get(), kInnerX, kInnerY, kInnerW, kInnerH); +#endif + } + canvas->restore(); + EXPECT_TRUE(VerifyBlackRect(*canvas, kInnerX + 1, kInnerY + 1, + kInnerW, kInnerH)); + + // Make the layer then translate. + canvas->drawColor(SK_ColorWHITE); + canvas->save(); + { + LayerSaver layer(*canvas, kLayerX, kLayerY, kLayerW, kLayerH); + canvas->translate(1, 1); + DrawNativeRect(*canvas, kInnerX, kInnerY, kInnerW, kInnerH); +#if defined(OS_WIN) + MakeOpaque(canvas.get(), kInnerX, kInnerY, kInnerW, kInnerH); +#endif + } + canvas->restore(); + EXPECT_TRUE(VerifyBlackRect(*canvas, kInnerX + 1, kInnerY + 1, + kInnerW, kInnerH)); + + // Translate both before and after, and have a clip. + canvas->drawColor(SK_ColorWHITE); + canvas->save(); + canvas->translate(1, 1); + { + LayerSaver layer(*canvas, kLayerX, kLayerY, kLayerW, kLayerH); + canvas->drawColor(SK_ColorWHITE); + canvas->translate(1, 1); + AddClip(*canvas, kInnerX + 1, kInnerY + 1, kInnerW - 1, kInnerH - 1); + DrawNativeRect(*canvas, 0, 0, 100, 100); +#if defined(OS_WIN) + MakeOpaque(canvas.get(), kLayerX, kLayerY, kLayerW, kLayerH); +#endif + } + canvas->restore(); + EXPECT_TRUE(VerifyBlackRect(*canvas, kInnerX + 3, kInnerY + 3, + kInnerW - 1, kInnerH - 1)); + +// TODO(dglazkov): Figure out why this fails on Mac (antialiased clipping?), +// modify test and remove this guard. +#if !defined(OS_MACOSX) + // Translate both before and after, and have a path clip. + canvas->drawColor(SK_ColorWHITE); + canvas->save(); + canvas->translate(1, 1); + { + LayerSaver layer(*canvas, kLayerX, kLayerY, kLayerW, kLayerH); + canvas->drawColor(SK_ColorWHITE); + canvas->translate(1, 1); + + SkPath path; + SkRect rect; + rect.iset(kInnerX - 1, kInnerY - 1, + kInnerX + kInnerW, kInnerY + kInnerH); + path.addRoundRect(rect, kRadius, kRadius); + canvas->clipPath(path); + + DrawNativeRect(*canvas, 0, 0, 100, 100); +#if defined(OS_WIN) + MakeOpaque(canvas.get(), kLayerX, kLayerY, kLayerW, kLayerH); +#endif + } + canvas->restore(); + EXPECT_TRUE(VerifyRoundedRect(*canvas, SK_ColorWHITE, SK_ColorBLACK, + kInnerX + 1, kInnerY + 1, kInnerW, kInnerH)); +#endif +} + +#endif // #if !defined(USE_AURA) + +TEST(PlatformBitmapTest, PlatformBitmap) { + const int kWidth = 400; + const int kHeight = 300; + scoped_ptr<PlatformBitmap> platform_bitmap(new PlatformBitmap); + + EXPECT_TRUE(0 == platform_bitmap->GetSurface()); + EXPECT_TRUE(platform_bitmap->GetBitmap().empty()); + EXPECT_TRUE(platform_bitmap->GetBitmap().isNull()); + + EXPECT_TRUE(platform_bitmap->Allocate(kWidth, kHeight, /*is_opaque=*/false)); + + EXPECT_TRUE(0 != platform_bitmap->GetSurface()); + EXPECT_FALSE(platform_bitmap->GetBitmap().empty()); + EXPECT_FALSE(platform_bitmap->GetBitmap().isNull()); + EXPECT_EQ(kWidth, platform_bitmap->GetBitmap().width()); + EXPECT_EQ(kHeight, platform_bitmap->GetBitmap().height()); + EXPECT_LE(static_cast<size_t>(platform_bitmap->GetBitmap().width()*4), + platform_bitmap->GetBitmap().rowBytes()); + EXPECT_EQ(SkBitmap::kARGB_8888_Config, // Same for all platforms. + platform_bitmap->GetBitmap().config()); + EXPECT_TRUE(platform_bitmap->GetBitmap().lockPixelsAreWritable()); + EXPECT_TRUE(platform_bitmap->GetBitmap().pixelRef()->isLocked()); + EXPECT_EQ(1, platform_bitmap->GetBitmap().pixelRef()->getRefCnt()); + + *(platform_bitmap->GetBitmap().getAddr32(10, 20)) = 0xDEED1020; + *(platform_bitmap->GetBitmap().getAddr32(20, 30)) = 0xDEED2030; + + SkBitmap sk_bitmap = platform_bitmap->GetBitmap(); + sk_bitmap.lockPixels(); + + EXPECT_EQ(2, platform_bitmap->GetBitmap().pixelRef()->getRefCnt()); + EXPECT_EQ(2, sk_bitmap.pixelRef()->getRefCnt()); + + EXPECT_EQ(0xDEED1020, *sk_bitmap.getAddr32(10, 20)); + EXPECT_EQ(0xDEED2030, *sk_bitmap.getAddr32(20, 30)); + + *(platform_bitmap->GetBitmap().getAddr32(30, 40)) = 0xDEED3040; + + // The SkBitmaps derived from a PlatformBitmap must be capable of outliving + // the PlatformBitmap. + platform_bitmap.reset(); + + EXPECT_EQ(1, sk_bitmap.pixelRef()->getRefCnt()); + + EXPECT_EQ(0xDEED1020, *sk_bitmap.getAddr32(10, 20)); + EXPECT_EQ(0xDEED2030, *sk_bitmap.getAddr32(20, 30)); + EXPECT_EQ(0xDEED3040, *sk_bitmap.getAddr32(30, 40)); + sk_bitmap.unlockPixels(); + + EXPECT_EQ(NULL, sk_bitmap.getPixels()); + + sk_bitmap.lockPixels(); + EXPECT_EQ(0xDEED1020, *sk_bitmap.getAddr32(10, 20)); + EXPECT_EQ(0xDEED2030, *sk_bitmap.getAddr32(20, 30)); + EXPECT_EQ(0xDEED3040, *sk_bitmap.getAddr32(30, 40)); + sk_bitmap.unlockPixels(); +} + + +} // namespace skia diff --git a/chromium/skia/ext/platform_device.cc b/chromium/skia/ext/platform_device.cc new file mode 100644 index 00000000000..c7f156516f9 --- /dev/null +++ b/chromium/skia/ext/platform_device.cc @@ -0,0 +1,80 @@ +// 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/logging.h" +#include "skia/ext/platform_device.h" + +#include "third_party/skia/include/core/SkMetaData.h" + +namespace skia { + +namespace { + +const char* kDevicePlatformBehaviour = "CrDevicePlatformBehaviour"; +const char* kDraftModeKey = "CrDraftMode"; + +#if defined(OS_MACOSX) || defined(OS_WIN) +const char* kIsPreviewMetafileKey = "CrIsPreviewMetafile"; +#endif + +void SetBoolMetaData(const SkCanvas& canvas, const char* key, bool value) { + SkMetaData& meta = skia::getMetaData(canvas); + meta.setBool(key, value); +} + +bool GetBoolMetaData(const SkCanvas& canvas, const char* key) { + bool value; + SkMetaData& meta = skia::getMetaData(canvas); + if (!meta.findBool(key, &value)) + value = false; + return value; +} + +} // namespace + +void SetPlatformDevice(SkDevice* device, PlatformDevice* platform_behaviour) { + SkMetaData& meta_data = device->getMetaData(); + meta_data.setPtr(kDevicePlatformBehaviour, platform_behaviour); +} + +PlatformDevice* GetPlatformDevice(SkDevice* device) { + if (device) { + SkMetaData& meta_data = device->getMetaData(); + PlatformDevice* device_behaviour = NULL; + if (meta_data.findPtr(kDevicePlatformBehaviour, + reinterpret_cast<void**>(&device_behaviour))) + return device_behaviour; + } + return NULL; +} + +SkMetaData& getMetaData(const SkCanvas& canvas) { + SkDevice* device = canvas.getDevice(); + DCHECK(device != NULL); + return device->getMetaData(); +} + +void SetIsDraftMode(const SkCanvas& canvas, bool draft_mode) { + SetBoolMetaData(canvas, kDraftModeKey, draft_mode); +} + +bool IsDraftMode(const SkCanvas& canvas) { + return GetBoolMetaData(canvas, kDraftModeKey); +} + +#if defined(OS_MACOSX) || defined(OS_WIN) +void SetIsPreviewMetafile(const SkCanvas& canvas, bool is_preview) { + SetBoolMetaData(canvas, kIsPreviewMetafileKey, is_preview); +} + +bool IsPreviewMetafile(const SkCanvas& canvas) { + return GetBoolMetaData(canvas, kIsPreviewMetafileKey); +} +#endif + +bool PlatformDevice::SupportsPlatformPaint() { + return true; +} + +} // namespace skia diff --git a/chromium/skia/ext/platform_device.h b/chromium/skia/ext/platform_device.h new file mode 100644 index 00000000000..4ac3aee5657 --- /dev/null +++ b/chromium/skia/ext/platform_device.h @@ -0,0 +1,173 @@ +// Copyright (c) 2012 The Chromium 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 SKIA_EXT_PLATFORM_DEVICE_H_ +#define SKIA_EXT_PLATFORM_DEVICE_H_ + +#include "build/build_config.h" + +#if defined(OS_WIN) +#include <windows.h> +#include <vector> +#endif + +#include "third_party/skia/include/core/SkColor.h" +#include "third_party/skia/include/core/SkDevice.h" +#include "third_party/skia/include/core/SkPreConfig.h" + +class SkMatrix; +class SkMetaData; +class SkPath; +class SkRegion; + +#if defined(OS_LINUX) || defined(OS_OPENBSD) || defined(OS_FREEBSD) \ + || defined(OS_SOLARIS) +typedef struct _cairo cairo_t; +typedef struct _cairo_rectangle cairo_rectangle_t; +#elif defined(OS_MACOSX) +typedef struct CGContext* CGContextRef; +typedef struct CGRect CGRect; +#endif + +namespace skia { + +class PlatformDevice; + +#if defined(OS_WIN) +typedef HDC PlatformSurface; +typedef RECT PlatformRect; +#elif defined(ANDROID) +typedef void* PlatformSurface; +typedef SkIRect* PlatformRect; +#elif defined(OS_LINUX) || defined(OS_OPENBSD) || defined(OS_FREEBSD) \ + || defined(OS_SOLARIS) +typedef cairo_t* PlatformSurface; +typedef cairo_rectangle_t PlatformRect; +#elif defined(OS_MACOSX) +typedef CGContextRef PlatformSurface; +typedef CGRect PlatformRect; +#endif + +// The following routines provide accessor points for the functionality +// exported by the various PlatformDevice ports. The PlatformDevice, and +// BitmapPlatformDevice classes inherit directly from SkDevice, which is no +// longer a supported usage-pattern for skia. In preparation of the removal of +// these classes, all calls to PlatformDevice::* should be routed through these +// helper functions. + +// Bind a PlatformDevice instance, |platform_device| to |device|. Subsequent +// calls to the functions exported below will forward the request to the +// corresponding method on the bound PlatformDevice instance. If no +// PlatformDevice has been bound to the SkDevice passed, then the routines are +// NOPS. +SK_API void SetPlatformDevice(SkDevice* device, + PlatformDevice* platform_device); +SK_API PlatformDevice* GetPlatformDevice(SkDevice* device); + + +#if defined(OS_WIN) +// Initializes the default settings and colors in a device context. +SK_API void InitializeDC(HDC context); +#elif defined(OS_MACOSX) +// Returns the CGContext that backing the SkDevice. Forwards to the bound +// PlatformDevice. Returns NULL if no PlatformDevice is bound. +SK_API CGContextRef GetBitmapContext(SkDevice* device); +#endif + +// Following routines are used in print preview workflow to mark the draft mode +// metafile and preview metafile. +SK_API SkMetaData& getMetaData(const SkCanvas& canvas); +SK_API void SetIsDraftMode(const SkCanvas& canvas, bool draft_mode); +SK_API bool IsDraftMode(const SkCanvas& canvas); + +#if defined(OS_MACOSX) || defined(OS_WIN) +SK_API void SetIsPreviewMetafile(const SkCanvas& canvas, bool is_preview); +SK_API bool IsPreviewMetafile(const SkCanvas& canvas); +#endif + +// A SkDevice is basically a wrapper around SkBitmap that provides a surface for +// SkCanvas to draw into. PlatformDevice provides a surface Windows can also +// write to. It also provides functionality to play well with GDI drawing +// functions. This class is abstract and must be subclassed. It provides the +// basic interface to implement it either with or without a bitmap backend. +// +// PlatformDevice provides an interface which sub-classes of SkDevice can also +// provide to allow for drawing by the native platform into the device. +class SK_API PlatformDevice { + public: + virtual ~PlatformDevice() {} + +#if defined(OS_MACOSX) + // The CGContext that corresponds to the bitmap, used for CoreGraphics + // operations drawing into the bitmap. This is possibly heavyweight, so it + // should exist only during one pass of rendering. + virtual CGContextRef GetBitmapContext() = 0; +#endif + + // The DC that corresponds to the bitmap, used for GDI operations drawing + // into the bitmap. This is possibly heavyweight, so it should be existant + // only during one pass of rendering. + virtual PlatformSurface BeginPlatformPaint(); + + // Finish a previous call to beginPlatformPaint. + virtual void EndPlatformPaint(); + + // Draws to the given screen DC, if the bitmap DC doesn't exist, this will + // temporarily create it. However, if you have created the bitmap DC, it will + // be more efficient if you don't free it until after this call so it doesn't + // have to be created twice. If src_rect is null, then the entirety of the + // source device will be copied. + virtual void DrawToNativeContext(PlatformSurface surface, int x, int y, + const PlatformRect* src_rect) = 0; + + // Returns true if GDI operations can be used for drawing into the bitmap. + virtual bool SupportsPlatformPaint(); + +#if defined(OS_WIN) + // Loads a SkPath into the GDI context. The path can there after be used for + // clipping or as a stroke. Returns false if the path failed to be loaded. + static bool LoadPathToDC(HDC context, const SkPath& path); + + // Loads a SkRegion into the GDI context. + static void LoadClippingRegionToDC(HDC context, const SkRegion& region, + const SkMatrix& transformation); +#elif defined(OS_MACOSX) + // Loads a SkPath into the CG context. The path can there after be used for + // clipping or as a stroke. + static void LoadPathToCGContext(CGContextRef context, const SkPath& path); + + // Initializes the default settings and colors in a device context. + static void InitializeCGContext(CGContextRef context); + + // Loads a SkRegion into the CG context. + static void LoadClippingRegionToCGContext(CGContextRef context, + const SkRegion& region, + const SkMatrix& transformation); +#endif + + protected: +#if defined(OS_WIN) + // Arrays must be inside structures. + struct CubicPoints { + SkPoint p[4]; + }; + typedef std::vector<CubicPoints> CubicPath; + typedef std::vector<CubicPath> CubicPaths; + + // Loads the specified Skia transform into the device context, excluding + // perspective (which GDI doesn't support). + static void LoadTransformToDC(HDC dc, const SkMatrix& matrix); + + // Transforms SkPath's paths into a series of cubic path. + static bool SkPathToCubicPaths(CubicPaths* paths, const SkPath& skpath); +#elif defined(OS_MACOSX) + // Loads the specified Skia transform into the device context + static void LoadTransformToCGContext(CGContextRef context, + const SkMatrix& matrix); +#endif +}; + +} // namespace skia + +#endif // SKIA_EXT_PLATFORM_DEVICE_H_ diff --git a/chromium/skia/ext/platform_device_linux.cc b/chromium/skia/ext/platform_device_linux.cc new file mode 100644 index 00000000000..f72e6148c4e --- /dev/null +++ b/chromium/skia/ext/platform_device_linux.cc @@ -0,0 +1,17 @@ +// 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 "skia/ext/platform_device.h" + +namespace skia { + +PlatformSurface PlatformDevice::BeginPlatformPaint() { + return NULL; +} + +void PlatformDevice::EndPlatformPaint() { + // We don't need to do anything on Linux here. +} + +} // namespace skia diff --git a/chromium/skia/ext/platform_device_mac.cc b/chromium/skia/ext/platform_device_mac.cc new file mode 100644 index 00000000000..6ff017d5cd7 --- /dev/null +++ b/chromium/skia/ext/platform_device_mac.cc @@ -0,0 +1,155 @@ +// 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 "skia/ext/platform_device.h" +#include "skia/ext/bitmap_platform_device.h" + +#import <ApplicationServices/ApplicationServices.h> +#include "skia/ext/skia_utils_mac.h" +#include "third_party/skia/include/core/SkMatrix.h" +#include "third_party/skia/include/core/SkPath.h" +#include "third_party/skia/include/core/SkTypes.h" +#include "third_party/skia/include/core/SkUtils.h" + +namespace skia { + +CGContextRef GetBitmapContext(SkDevice* device) { + PlatformDevice* platform_device = GetPlatformDevice(device); + if (platform_device) + return platform_device->GetBitmapContext(); + + return NULL; +} + +CGContextRef PlatformDevice::BeginPlatformPaint() { + return GetBitmapContext(); +} + +void PlatformDevice::EndPlatformPaint() { + // Flushing will be done in onAccessBitmap. +} + +// Set up the CGContextRef for peaceful coexistence with Skia +void PlatformDevice::InitializeCGContext(CGContextRef context) { + // CG defaults to the same settings as Skia +} + +// static +void PlatformDevice::LoadPathToCGContext(CGContextRef context, + const SkPath& path) { + // instead of a persistent attribute of the context, CG specifies the fill + // type per call, so we just have to load up the geometry. + CGContextBeginPath(context); + + SkPoint points[4] = { {0, 0}, {0, 0}, {0, 0}, {0, 0} }; + SkPath::Iter iter(path, false); + for (SkPath::Verb verb = iter.next(points); verb != SkPath::kDone_Verb; + verb = iter.next(points)) { + switch (verb) { + case SkPath::kMove_Verb: { // iter.next returns 1 point + CGContextMoveToPoint(context, points[0].fX, points[0].fY); + break; + } + case SkPath::kLine_Verb: { // iter.next returns 2 points + CGContextAddLineToPoint(context, points[1].fX, points[1].fY); + break; + } + case SkPath::kQuad_Verb: { // iter.next returns 3 points + CGContextAddQuadCurveToPoint(context, points[1].fX, points[1].fY, + points[2].fX, points[2].fY); + break; + } + case SkPath::kCubic_Verb: { // iter.next returns 4 points + CGContextAddCurveToPoint(context, points[1].fX, points[1].fY, + points[2].fX, points[2].fY, + points[3].fX, points[3].fY); + break; + } + case SkPath::kClose_Verb: { // iter.next returns 1 point (the last point) + break; + } + case SkPath::kDone_Verb: // iter.next returns 0 points + default: { + SkASSERT(false); + break; + } + } + } + CGContextClosePath(context); +} + +// static +void PlatformDevice::LoadTransformToCGContext(CGContextRef context, + const SkMatrix& matrix) { + // CoreGraphics can concatenate transforms, but not reset the current one. + // So in order to get the required behavior here, we need to first make + // the current transformation matrix identity and only then load the new one. + + // Reset matrix to identity. + CGAffineTransform orig_cg_matrix = CGContextGetCTM(context); + CGAffineTransform orig_cg_matrix_inv = CGAffineTransformInvert( + orig_cg_matrix); + CGContextConcatCTM(context, orig_cg_matrix_inv); + + // assert that we have indeed returned to the identity Matrix. + SkASSERT(CGAffineTransformIsIdentity(CGContextGetCTM(context))); + + // Convert xform to CG-land. + // Our coordinate system is flipped to match WebKit's so we need to modify + // the xform to match that. + SkMatrix transformed_matrix = matrix; + SkScalar sy = matrix.getScaleY() * (SkScalar)-1; + transformed_matrix.setScaleY(sy); + size_t height = CGBitmapContextGetHeight(context); + SkScalar ty = -matrix.getTranslateY(); // y axis is flipped. + transformed_matrix.setTranslateY(ty + (SkScalar)height); + + CGAffineTransform cg_matrix = gfx::SkMatrixToCGAffineTransform( + transformed_matrix); + + // Load final transform into context. + CGContextConcatCTM(context, cg_matrix); +} + +// static +void PlatformDevice::LoadClippingRegionToCGContext( + CGContextRef context, + const SkRegion& region, + const SkMatrix& transformation) { + if (region.isEmpty()) { + // region can be empty, in which case everything will be clipped. + SkRect rect; + rect.setEmpty(); + CGContextClipToRect(context, gfx::SkRectToCGRect(rect)); + } else if (region.isRect()) { + // CoreGraphics applies the current transform to clip rects, which is + // unwanted. Inverse-transform the rect before sending it to CG. This only + // works for translations and scaling, but not for rotations (but the + // viewport is never rotated anyway). + SkMatrix t; + bool did_invert = transformation.invert(&t); + if (!did_invert) + t.reset(); + // Do the transformation. + SkRect rect; + rect.set(region.getBounds()); + t.mapRect(&rect); + SkIRect irect; + rect.round(&irect); + CGContextClipToRect(context, gfx::SkIRectToCGRect(irect)); + } else { + // It is complex. + SkPath path; + region.getBoundaryPath(&path); + // Clip. Note that windows clipping regions are not affected by the + // transform so apply it manually. + path.transform(transformation); + // TODO(playmobil): Implement. + SkASSERT(false); + // LoadPathToDC(context, path); + // hrgn = PathToRegion(context); + } +} + +} // namespace skia diff --git a/chromium/skia/ext/platform_device_win.cc b/chromium/skia/ext/platform_device_win.cc new file mode 100644 index 00000000000..f44e66eb43d --- /dev/null +++ b/chromium/skia/ext/platform_device_win.cc @@ -0,0 +1,237 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "skia/ext/platform_device.h" + +#include "skia/ext/skia_utils_win.h" +#include "third_party/skia/include/core/SkMatrix.h" +#include "third_party/skia/include/core/SkPath.h" +#include "third_party/skia/include/core/SkRegion.h" +#include "third_party/skia/include/core/SkUtils.h" + +namespace skia { + +void InitializeDC(HDC context) { + // Enables world transformation. + // If the GM_ADVANCED graphics mode is set, GDI always draws arcs in the + // counterclockwise direction in logical space. This is equivalent to the + // statement that, in the GM_ADVANCED graphics mode, both arc control points + // and arcs themselves fully respect the device context's world-to-device + // transformation. + BOOL res = SetGraphicsMode(context, GM_ADVANCED); + SkASSERT(res != 0); + + // Enables dithering. + res = SetStretchBltMode(context, HALFTONE); + SkASSERT(res != 0); + // As per SetStretchBltMode() documentation, SetBrushOrgEx() must be called + // right after. + res = SetBrushOrgEx(context, 0, 0, NULL); + SkASSERT(res != 0); + + // Sets up default orientation. + res = SetArcDirection(context, AD_CLOCKWISE); + SkASSERT(res != 0); + + // Sets up default colors. + res = SetBkColor(context, RGB(255, 255, 255)); + SkASSERT(res != CLR_INVALID); + res = SetTextColor(context, RGB(0, 0, 0)); + SkASSERT(res != CLR_INVALID); + res = SetDCBrushColor(context, RGB(255, 255, 255)); + SkASSERT(res != CLR_INVALID); + res = SetDCPenColor(context, RGB(0, 0, 0)); + SkASSERT(res != CLR_INVALID); + + // Sets up default transparency. + res = SetBkMode(context, OPAQUE); + SkASSERT(res != 0); + res = SetROP2(context, R2_COPYPEN); + SkASSERT(res != 0); +} + +PlatformSurface PlatformDevice::BeginPlatformPaint() { + return 0; +} + +void PlatformDevice::EndPlatformPaint() { + // We don't clear the DC here since it will be likely to be used again. + // Flushing will be done in onAccessBitmap. +} + +void PlatformDevice::DrawToNativeContext(PlatformSurface surface, int x, int y, + const PlatformRect* src_rect) { +} + +// static +bool PlatformDevice::LoadPathToDC(HDC context, const SkPath& path) { + switch (path.getFillType()) { + case SkPath::kWinding_FillType: { + int res = SetPolyFillMode(context, WINDING); + SkASSERT(res != 0); + break; + } + case SkPath::kEvenOdd_FillType: { + int res = SetPolyFillMode(context, ALTERNATE); + SkASSERT(res != 0); + break; + } + default: { + SkASSERT(false); + break; + } + } + BOOL res = BeginPath(context); + if (!res) { + return false; + } + + CubicPaths paths; + if (!SkPathToCubicPaths(&paths, path)) + return false; + + std::vector<POINT> points; + for (CubicPaths::const_iterator path(paths.begin()); path != paths.end(); + ++path) { + if (!path->size()) + continue; + points.resize(0); + points.reserve(path->size() * 3 / 4 + 1); + points.push_back(SkPointToPOINT(path->front().p[0])); + for (CubicPath::const_iterator point(path->begin()); point != path->end(); + ++point) { + // Never add point->p[0] + points.push_back(SkPointToPOINT(point->p[1])); + points.push_back(SkPointToPOINT(point->p[2])); + points.push_back(SkPointToPOINT(point->p[3])); + } + SkASSERT((points.size() - 1) % 3 == 0); + // This is slightly inefficient since all straight line and quadratic lines + // are "upgraded" to a cubic line. + // TODO(maruel): http://b/1147346 We should use + // PolyDraw/PolyBezier/Polyline whenever possible. + res = PolyBezier(context, &points.front(), + static_cast<DWORD>(points.size())); + SkASSERT(res != 0); + if (res == 0) + break; + } + if (res == 0) { + // Make sure the path is discarded. + AbortPath(context); + } else { + res = EndPath(context); + SkASSERT(res != 0); + } + return true; +} + +// static +void PlatformDevice::LoadTransformToDC(HDC dc, const SkMatrix& matrix) { + XFORM xf; + xf.eM11 = matrix[SkMatrix::kMScaleX]; + xf.eM21 = matrix[SkMatrix::kMSkewX]; + xf.eDx = matrix[SkMatrix::kMTransX]; + xf.eM12 = matrix[SkMatrix::kMSkewY]; + xf.eM22 = matrix[SkMatrix::kMScaleY]; + xf.eDy = matrix[SkMatrix::kMTransY]; + SetWorldTransform(dc, &xf); +} + +// static +bool PlatformDevice::SkPathToCubicPaths(CubicPaths* paths, + const SkPath& skpath) { + paths->clear(); + CubicPath* current_path = NULL; + SkPoint current_points[4]; + CubicPoints points_to_add; + SkPath::Iter iter(skpath, false); + for (SkPath::Verb verb = iter.next(current_points); + verb != SkPath::kDone_Verb; + verb = iter.next(current_points)) { + switch (verb) { + case SkPath::kMove_Verb: { // iter.next returns 1 point + // Ignores it since the point is copied in the next operation. See + // SkPath::Iter::next() for reference. + paths->push_back(CubicPath()); + current_path = &paths->back(); + // Skip point addition. + continue; + } + case SkPath::kLine_Verb: { // iter.next returns 2 points + points_to_add.p[0] = current_points[0]; + points_to_add.p[1] = current_points[0]; + points_to_add.p[2] = current_points[1]; + points_to_add.p[3] = current_points[1]; + break; + } + case SkPath::kQuad_Verb: { // iter.next returns 3 points + points_to_add.p[0] = current_points[0]; + points_to_add.p[1] = current_points[1]; + points_to_add.p[2] = current_points[2]; + points_to_add.p[3] = current_points[2]; + break; + } + case SkPath::kCubic_Verb: { // iter.next returns 4 points + points_to_add.p[0] = current_points[0]; + points_to_add.p[1] = current_points[1]; + points_to_add.p[2] = current_points[2]; + points_to_add.p[3] = current_points[3]; + break; + } + case SkPath::kClose_Verb: { // iter.next returns 1 point (the last point) + paths->push_back(CubicPath()); + current_path = &paths->back(); + continue; + } + default: { + current_path = NULL; + // Will return false. + break; + } + } + SkASSERT(current_path); + if (!current_path) { + paths->clear(); + return false; + } + current_path->push_back(points_to_add); + } + return true; +} + +// static +void PlatformDevice::LoadClippingRegionToDC(HDC context, + const SkRegion& region, + const SkMatrix& transformation) { + HRGN hrgn; + if (region.isEmpty()) { + // region can be empty, in which case everything will be clipped. + hrgn = CreateRectRgn(0, 0, 0, 0); + } else if (region.isRect()) { + // We don't apply transformation, because the translation is already applied + // to the region. + hrgn = CreateRectRgnIndirect(&SkIRectToRECT(region.getBounds())); + } else { + // It is complex. + SkPath path; + region.getBoundaryPath(&path); + // Clip. Note that windows clipping regions are not affected by the + // transform so apply it manually. + // Since the transform is given as the original translation of canvas, we + // should apply it in reverse. + SkMatrix t(transformation); + t.setTranslateX(-t.getTranslateX()); + t.setTranslateY(-t.getTranslateY()); + path.transform(t); + LoadPathToDC(context, path); + hrgn = PathToRegion(context); + } + int result = SelectClipRgn(context, hrgn); + SkASSERT(result != ERROR); + result = DeleteObject(hrgn); + SkASSERT(result != 0); +} + +} // namespace skia diff --git a/chromium/skia/ext/recursive_gaussian_convolution.cc b/chromium/skia/ext/recursive_gaussian_convolution.cc new file mode 100644 index 00000000000..195fca8deae --- /dev/null +++ b/chromium/skia/ext/recursive_gaussian_convolution.cc @@ -0,0 +1,270 @@ +// 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 <algorithm> +#include <cmath> +#include <vector> + +#include "base/logging.h" +#include "skia/ext/recursive_gaussian_convolution.h" + +namespace skia { + +namespace { + +// Takes the value produced by accumulating element-wise product of image with +// a kernel and brings it back into range. +// All of the filter scaling factors are in fixed point with kShiftBits bits of +// fractional part. +template<bool take_absolute> +inline unsigned char FloatTo8(float f) { + int a = static_cast<int>(f + 0.5f); + if (take_absolute) + a = std::abs(a); + else if (a < 0) + return 0; + if (a < 256) + return a; + return 255; +} + +template<RecursiveFilter::Order order> +inline float ForwardFilter(float in_n_1, + float in_n, + float in_n1, + const std::vector<float>& w, + int n, + const float* b) { + switch (order) { + case RecursiveFilter::FUNCTION: + return b[0] * in_n + b[1] * w[n-1] + b[2] * w[n-2] + b[3] * w[n-3]; + case RecursiveFilter::FIRST_DERIVATIVE: + return b[0] * 0.5f * (in_n1 - in_n_1) + + b[1] * w[n-1] + b[2] * w[n-2] + b[3] * w[n-3]; + case RecursiveFilter::SECOND_DERIVATIVE: + return b[0] * (in_n - in_n_1) + + b[1] * w[n-1] + b[2] * w[n-2] + b[3] * w[n-3]; + } + + NOTREACHED(); + return 0.0f; +} + +template<RecursiveFilter::Order order> +inline float BackwardFilter(const std::vector<float>& out, + int n, + float w_n, + float w_n1, + const float* b) { + switch (order) { + case RecursiveFilter::FUNCTION: + case RecursiveFilter::FIRST_DERIVATIVE: + return b[0] * w_n + + b[1] * out[n + 1] + b[2] * out[n + 2] + b[3] * out[n + 3]; + case RecursiveFilter::SECOND_DERIVATIVE: + return b[0] * (w_n1 - w_n) + + b[1] * out[n + 1] + b[2] * out[n + 2] + b[3] * out[n + 3]; + } + NOTREACHED(); + return 0.0f; +} + +template<RecursiveFilter::Order order, bool absolute_values> +unsigned char SingleChannelRecursiveFilter( + const unsigned char* const source_data, + int source_pixel_stride, + int source_row_stride, + int row_width, + int row_count, + unsigned char* const output, + int output_pixel_stride, + int output_row_stride, + const float* b) { + const int intermediate_buffer_size = row_width + 6; + std::vector<float> w(intermediate_buffer_size); + const unsigned char* in = source_data; + unsigned char* out = output; + unsigned char max_output = 0; + for (int r = 0; r < row_count; + ++r, in += source_row_stride, out += output_row_stride) { + // Compute forward filter. + // First initialize start of the w (temporary) vector. + if (order == RecursiveFilter::FUNCTION) + w[0] = w[1] = w[2] = in[0]; + else + w[0] = w[1] = w[2] = 0.0f; + // Note that special-casing of w[3] is needed because of derivatives. + w[3] = ForwardFilter<order>( + in[0], in[0], in[source_pixel_stride], w, 3, b); + int n = 4; + int c = 1; + int byte_index = source_pixel_stride; + for (; c < row_width - 1; ++c, ++n, byte_index += source_pixel_stride) { + w[n] = ForwardFilter<order>(in[byte_index - source_pixel_stride], + in[byte_index], + in[byte_index + source_pixel_stride], + w, n, b); + } + + // The value of w corresponding to the last image pixel needs to be computed + // separately, again because of derivatives. + w[n] = ForwardFilter<order>(in[byte_index - source_pixel_stride], + in[byte_index], + in[byte_index], + w, n, b); + // Now three trailing bytes set to the same value as current w[n]. + w[n + 1] = w[n]; + w[n + 2] = w[n]; + w[n + 3] = w[n]; + + // Now apply the back filter. + float w_n1 = w[n + 1]; + int output_index = (row_width - 1) * output_pixel_stride; + for (; c >= 0; output_index -= output_pixel_stride, --c, --n) { + float w_n = BackwardFilter<order>(w, n, w[n], w_n1, b); + w_n1 = w[n]; + w[n] = w_n; + out[output_index] = FloatTo8<absolute_values>(w_n); + max_output = std::max(max_output, out[output_index]); + } + } + return max_output; +} + +unsigned char SingleChannelRecursiveFilter( + const unsigned char* const source_data, + int source_pixel_stride, + int source_row_stride, + int row_width, + int row_count, + unsigned char* const output, + int output_pixel_stride, + int output_row_stride, + const float* b, + RecursiveFilter::Order order, + bool absolute_values) { + if (absolute_values) { + switch (order) { + case RecursiveFilter::FUNCTION: + return SingleChannelRecursiveFilter<RecursiveFilter::FUNCTION, true>( + source_data, source_pixel_stride, source_row_stride, + row_width, row_count, + output, output_pixel_stride, output_row_stride, b); + case RecursiveFilter::FIRST_DERIVATIVE: + return SingleChannelRecursiveFilter< + RecursiveFilter::FIRST_DERIVATIVE, true>( + source_data, source_pixel_stride, source_row_stride, + row_width, row_count, + output, output_pixel_stride, output_row_stride, b); + case RecursiveFilter::SECOND_DERIVATIVE: + return SingleChannelRecursiveFilter< + RecursiveFilter::SECOND_DERIVATIVE, true>( + source_data, source_pixel_stride, source_row_stride, + row_width, row_count, + output, output_pixel_stride, output_row_stride, b); + } + } else { + switch (order) { + case RecursiveFilter::FUNCTION: + return SingleChannelRecursiveFilter<RecursiveFilter::FUNCTION, false>( + source_data, source_pixel_stride, source_row_stride, + row_width, row_count, + output, output_pixel_stride, output_row_stride, b); + case RecursiveFilter::FIRST_DERIVATIVE: + return SingleChannelRecursiveFilter< + RecursiveFilter::FIRST_DERIVATIVE, false>( + source_data, source_pixel_stride, source_row_stride, + row_width, row_count, + output, output_pixel_stride, output_row_stride, b); + case RecursiveFilter::SECOND_DERIVATIVE: + return SingleChannelRecursiveFilter< + RecursiveFilter::SECOND_DERIVATIVE, false>( + source_data, source_pixel_stride, source_row_stride, + row_width, row_count, + output, output_pixel_stride, output_row_stride, b); + } + } + + NOTREACHED(); + return 0; +} + +} + +float RecursiveFilter::qFromSigma(float sigma) { + DCHECK_GE(sigma, 0.5f); + if (sigma <= 2.5f) + return 3.97156f - 4.14554f * std::sqrt(1.0f - 0.26891f * sigma); + return 0.98711f * sigma - 0.96330f; +} + +void RecursiveFilter::computeCoefficients(float q, float b[4]) { + b[0] = 1.57825f + 2.44413f * q + 1.4281f * q * q + 0.422205f * q * q * q; + b[1] = 2.4413f * q + 2.85619f * q * q + 1.26661f * q * q * q; + b[2] = - 1.4281f * q * q - 1.26661f * q * q * q; + b[3] = 0.422205f * q * q * q; + + // The above is exactly like in the paper. To cut down on computations, + // we can fix up these numbers a bit now. + float b_norm = 1.0f - (b[1] + b[2] + b[3]) / b[0]; + b[1] /= b[0]; + b[2] /= b[0]; + b[3] /= b[0]; + b[0] = b_norm; +} + +RecursiveFilter::RecursiveFilter(float sigma, Order order) + : order_(order), q_(qFromSigma(sigma)) { + computeCoefficients(q_, b_); +} + +unsigned char SingleChannelRecursiveGaussianX(const unsigned char* source_data, + int source_byte_row_stride, + int input_channel_index, + int input_channel_count, + const RecursiveFilter& filter, + const SkISize& image_size, + unsigned char* output, + int output_byte_row_stride, + int output_channel_index, + int output_channel_count, + bool absolute_values) { + return SingleChannelRecursiveFilter(source_data + input_channel_index, + input_channel_count, + source_byte_row_stride, + image_size.width(), + image_size.height(), + output + output_channel_index, + output_channel_count, + output_byte_row_stride, + filter.b(), + filter.order(), + absolute_values); +} + +unsigned char SingleChannelRecursiveGaussianY(const unsigned char* source_data, + int source_byte_row_stride, + int input_channel_index, + int input_channel_count, + const RecursiveFilter& filter, + const SkISize& image_size, + unsigned char* output, + int output_byte_row_stride, + int output_channel_index, + int output_channel_count, + bool absolute_values) { + return SingleChannelRecursiveFilter(source_data + input_channel_index, + source_byte_row_stride, + input_channel_count, + image_size.height(), + image_size.width(), + output + output_channel_index, + output_byte_row_stride, + output_channel_count, + filter.b(), + filter.order(), + absolute_values); +} + +} // namespace skia diff --git a/chromium/skia/ext/recursive_gaussian_convolution.h b/chromium/skia/ext/recursive_gaussian_convolution.h new file mode 100644 index 00000000000..a4843292e1a --- /dev/null +++ b/chromium/skia/ext/recursive_gaussian_convolution.h @@ -0,0 +1,71 @@ +// 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 SKIA_EXT_RECURSIVE_GAUSSIAN_CONVOLUTION_H_ +#define SKIA_EXT_RECURSIVE_GAUSSIAN_CONVOLUTION_H_ + +#include "skia/ext/convolver.h" +#include "third_party/skia/include/core/SkSize.h" +#include "third_party/skia/include/core/SkTypes.h" + +namespace skia { + +// RecursiveFilter, paired with SingleChannelRecursiveGaussianX and +// SingleChannelRecursiveGaussianY routines below implement recursive filters as +// described in 'Recursive implementation of the Gaussian filter' (Young, Vliet) +// (1995). Single-letter variable names mirror exactly the usage in the paper to +// ease reading and analysis. +class RecursiveFilter { + public: + enum Order { + FUNCTION, + FIRST_DERIVATIVE, + SECOND_DERIVATIVE + }; + + static float qFromSigma(float sigma); + static void computeCoefficients(float q, float b[4]); + SK_API RecursiveFilter(float sigma, Order order); + + Order order() const { return order_; } + const float* b() const { return b_; } + + private: + Order order_; + float q_; + float b_[4]; +}; + +// Applies a gaussian recursive filter given as |filter| to a single channel at +// |input_channel_index| to image given in |source_data| along X axis. +// The output is placed into |output| into channel |output_channel_index|. +SK_API unsigned char SingleChannelRecursiveGaussianX( + const unsigned char* source_data, + int source_byte_row_stride, + int input_channel_index, + int input_channel_count, + const RecursiveFilter& filter, + const SkISize& image_size, + unsigned char* output, + int output_byte_row_stride, + int output_channel_index, + int output_channel_count, + bool absolute_values); + +// Applies a gaussian recursive filter along Y axis. +SK_API unsigned char SingleChannelRecursiveGaussianY( + const unsigned char* source_data, + int source_byte_row_stride, + int input_channel_index, + int input_channel_count, + const RecursiveFilter& filter, + const SkISize& image_size, + unsigned char* output, + int output_byte_row_stride, + int output_channel_index, + int output_channel_count, + bool absolute_values); +} // namespace skia + +#endif // SKIA_EXT_RECURSIVE_GAUSSIAN_CONVOLUTION_H_ diff --git a/chromium/skia/ext/recursive_gaussian_convolution_unittest.cc b/chromium/skia/ext/recursive_gaussian_convolution_unittest.cc new file mode 100644 index 00000000000..3f00bac68b0 --- /dev/null +++ b/chromium/skia/ext/recursive_gaussian_convolution_unittest.cc @@ -0,0 +1,395 @@ +// 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 <functional> +#include <numeric> +#include <vector> + +#include "base/basictypes.h" +#include "base/file_util.h" +#include "base/files/file_path.h" +#include "base/logging.h" +#include "base/time/time.h" +#include "skia/ext/convolver.h" +#include "skia/ext/recursive_gaussian_convolution.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "third_party/skia/include/core/SkPoint.h" +#include "third_party/skia/include/core/SkRect.h" + +namespace { + +int ComputeRowStride(int width, int channel_count, int stride_slack) { + return width * channel_count + stride_slack; +} + +SkIPoint MakeImpulseImage(std::vector<unsigned char>* image, + int width, + int height, + int channel_index, + int channel_count, + int stride_slack) { + const int src_row_stride = ComputeRowStride( + width, channel_count, stride_slack); + const int src_byte_count = src_row_stride * height; + const int signal_x = width / 2; + const int signal_y = height / 2; + + image->resize(src_byte_count, 0); + const int non_zero_pixel_index = + signal_y * src_row_stride + signal_x * channel_count + channel_index; + (*image)[non_zero_pixel_index] = 255; + return SkIPoint::Make(signal_x, signal_y); +} + +SkIRect MakeBoxImage(std::vector<unsigned char>* image, + int width, + int height, + int channel_index, + int channel_count, + int stride_slack, + int box_width, + int box_height, + unsigned char value) { + const int src_row_stride = ComputeRowStride( + width, channel_count, stride_slack); + const int src_byte_count = src_row_stride * height; + const SkIRect box = SkIRect::MakeXYWH((width - box_width) / 2, + (height - box_height) / 2, + box_width, box_height); + + image->resize(src_byte_count, 0); + for (int y = box.top(); y < box.bottom(); ++y) { + for (int x = box.left(); x < box.right(); ++x) + (*image)[y * src_row_stride + x * channel_count + channel_index] = value; + } + + return box; +} + +int ComputeBoxSum(const std::vector<unsigned char>& image, + const SkIRect& box, + int image_width) { + // Compute the sum of all pixels in the box. Assume byte stride 1 and row + // stride same as image_width. + int sum = 0; + for (int y = box.top(); y < box.bottom(); ++y) { + for (int x = box.left(); x < box.right(); ++x) + sum += image[y * image_width + x]; + } + + return sum; +} + +} // namespace + +namespace skia { + +TEST(RecursiveGaussian, SmoothingMethodComparison) { + static const int kImgWidth = 512; + static const int kImgHeight = 220; + static const int kChannelIndex = 3; + static const int kChannelCount = 3; + static const int kStrideSlack = 22; + + std::vector<unsigned char> input; + SkISize image_size = SkISize::Make(kImgWidth, kImgHeight); + MakeImpulseImage( + &input, kImgWidth, kImgHeight, kChannelIndex, kChannelCount, + kStrideSlack); + + // Destination will be a single channel image with stide matching width. + const int dest_row_stride = kImgWidth; + const int dest_byte_count = dest_row_stride * kImgHeight; + std::vector<unsigned char> intermediate(dest_byte_count); + std::vector<unsigned char> intermediate2(dest_byte_count); + std::vector<unsigned char> control(dest_byte_count); + std::vector<unsigned char> output(dest_byte_count); + + const int src_row_stride = ComputeRowStride( + kImgWidth, kChannelCount, kStrideSlack); + + const float kernel_sigma = 2.5f; + ConvolutionFilter1D filter; + SetUpGaussianConvolutionKernel(&filter, kernel_sigma, false); + // Process the control image. + SingleChannelConvolveX1D(&input[0], src_row_stride, + kChannelIndex, kChannelCount, + filter, image_size, + &intermediate[0], dest_row_stride, 0, 1, false); + SingleChannelConvolveY1D(&intermediate[0], dest_row_stride, 0, 1, + filter, image_size, + &control[0], dest_row_stride, 0, 1, false); + + // Now try the same using the other method. + RecursiveFilter recursive_filter(kernel_sigma, RecursiveFilter::FUNCTION); + SingleChannelRecursiveGaussianY(&input[0], src_row_stride, + kChannelIndex, kChannelCount, + recursive_filter, image_size, + &intermediate2[0], dest_row_stride, + 0, 1, false); + SingleChannelRecursiveGaussianX(&intermediate2[0], dest_row_stride, 0, 1, + recursive_filter, image_size, + &output[0], dest_row_stride, 0, 1, false); + + // We cannot expect the results to be really the same. In particular, + // the standard implementation is computed in completely fixed-point, while + // recursive is done in floating point and squeezed back into char*. On top + // of that, its characteristics are a bit different (consult the paper). + EXPECT_NEAR(std::accumulate(intermediate.begin(), intermediate.end(), 0), + std::accumulate(intermediate2.begin(), intermediate2.end(), 0), + 50); + int difference_count = 0; + std::vector<unsigned char>::const_iterator i1, i2; + for (i1 = control.begin(), i2 = output.begin(); + i1 != control.end(); ++i1, ++i2) { + if ((*i1 != 0) != (*i2 != 0)) + difference_count++; + } + + EXPECT_LE(difference_count, 44); // 44 is 2 * PI * r (r == 7, spot size). +} + +TEST(RecursiveGaussian, SmoothingImpulse) { + static const int kImgWidth = 200; + static const int kImgHeight = 300; + static const int kChannelIndex = 3; + static const int kChannelCount = 3; + static const int kStrideSlack = 22; + + std::vector<unsigned char> input; + SkISize image_size = SkISize::Make(kImgWidth, kImgHeight); + const SkIPoint centre_point = MakeImpulseImage( + &input, kImgWidth, kImgHeight, kChannelIndex, kChannelCount, + kStrideSlack); + + // Destination will be a single channel image with stide matching width. + const int dest_row_stride = kImgWidth; + const int dest_byte_count = dest_row_stride * kImgHeight; + std::vector<unsigned char> intermediate(dest_byte_count); + std::vector<unsigned char> output(dest_byte_count); + + const int src_row_stride = ComputeRowStride( + kImgWidth, kChannelCount, kStrideSlack); + + const float kernel_sigma = 5.0f; + RecursiveFilter recursive_filter(kernel_sigma, RecursiveFilter::FUNCTION); + SingleChannelRecursiveGaussianY(&input[0], src_row_stride, + kChannelIndex, kChannelCount, + recursive_filter, image_size, + &intermediate[0], dest_row_stride, + 0, 1, false); + SingleChannelRecursiveGaussianX(&intermediate[0], dest_row_stride, 0, 1, + recursive_filter, image_size, + &output[0], dest_row_stride, 0, 1, false); + + // Check we got the expected impulse response. + const int cx = centre_point.x(); + const int cy = centre_point.y(); + unsigned char value_x = output[dest_row_stride * cy + cx]; + unsigned char value_y = value_x; + EXPECT_GT(value_x, 0); + for (int offset = 0; + offset < std::min(kImgWidth, kImgHeight) && (value_y > 0 || value_x > 0); + ++offset) { + // Symmetricity and monotonicity along X. + EXPECT_EQ(output[dest_row_stride * cy + cx - offset], + output[dest_row_stride * cy + cx + offset]); + EXPECT_LE(output[dest_row_stride * cy + cx - offset], value_x); + value_x = output[dest_row_stride * cy + cx - offset]; + + // Symmetricity and monotonicity along Y. + EXPECT_EQ(output[dest_row_stride * (cy - offset) + cx], + output[dest_row_stride * (cy + offset) + cx]); + EXPECT_LE(output[dest_row_stride * (cy - offset) + cx], value_y); + value_y = output[dest_row_stride * (cy - offset) + cx]; + + // Symmetricity along X/Y (not really assured, but should be close). + EXPECT_NEAR(value_x, value_y, 1); + } + + // Smooth the inverse now. + std::vector<unsigned char> output2(dest_byte_count); + std::transform(input.begin(), input.end(), input.begin(), + std::bind1st(std::minus<unsigned char>(), 255U)); + SingleChannelRecursiveGaussianY(&input[0], src_row_stride, + kChannelIndex, kChannelCount, + recursive_filter, image_size, + &intermediate[0], dest_row_stride, + 0, 1, false); + SingleChannelRecursiveGaussianX(&intermediate[0], dest_row_stride, 0, 1, + recursive_filter, image_size, + &output2[0], dest_row_stride, 0, 1, false); + // The image should be the reverse of output, but permitting for rounding + // we will only claim that wherever output is 0, output2 should be 255. + // There still can be differences at the edges of the object. + std::vector<unsigned char>::const_iterator i1, i2; + int difference_count = 0; + for (i1 = output.begin(), i2 = output2.begin(); + i1 != output.end(); ++i1, ++i2) { + // The line below checks (*i1 == 0 <==> *i2 == 255). + if ((*i1 != 0 && *i2 == 255) && ! (*i1 == 0 && *i2 != 255)) + ++difference_count; + } + EXPECT_LE(difference_count, 8); +} + +TEST(RecursiveGaussian, FirstDerivative) { + static const int kImgWidth = 512; + static const int kImgHeight = 1024; + static const int kChannelIndex = 2; + static const int kChannelCount = 4; + static const int kStrideSlack = 22; + static const int kBoxSize = 400; + + std::vector<unsigned char> input; + const SkISize image_size = SkISize::Make(kImgWidth, kImgHeight); + const SkIRect box = MakeBoxImage( + &input, kImgWidth, kImgHeight, kChannelIndex, kChannelCount, + kStrideSlack, kBoxSize, kBoxSize, 200); + + // Destination will be a single channel image with stide matching width. + const int dest_row_stride = kImgWidth; + const int dest_byte_count = dest_row_stride * kImgHeight; + std::vector<unsigned char> output_x(dest_byte_count); + std::vector<unsigned char> output_y(dest_byte_count); + std::vector<unsigned char> output(dest_byte_count); + + const int src_row_stride = ComputeRowStride( + kImgWidth, kChannelCount, kStrideSlack); + + const float kernel_sigma = 3.0f; + const int spread = 4 * kernel_sigma; + RecursiveFilter recursive_filter(kernel_sigma, + RecursiveFilter::FIRST_DERIVATIVE); + SingleChannelRecursiveGaussianX(&input[0], src_row_stride, + kChannelIndex, kChannelCount, + recursive_filter, image_size, + &output_x[0], dest_row_stride, + 0, 1, true); + SingleChannelRecursiveGaussianY(&input[0], src_row_stride, + kChannelIndex, kChannelCount, + recursive_filter, image_size, + &output_y[0], dest_row_stride, + 0, 1, true); + + // In test code we can assume adding the two up should do fine. + std::vector<unsigned char>::const_iterator ix, iy; + std::vector<unsigned char>::iterator target; + for (target = output.begin(), ix = output_x.begin(), iy = output_y.begin(); + target < output.end(); ++target, ++ix, ++iy) { + *target = *ix + *iy; + } + + SkIRect inflated_rect(box); + inflated_rect.outset(spread, spread); + SkIRect deflated_rect(box); + deflated_rect.inset(spread, spread); + + int image_total = ComputeBoxSum(output, + SkIRect::MakeWH(kImgWidth, kImgHeight), + kImgWidth); + int box_inflated = ComputeBoxSum(output, inflated_rect, kImgWidth); + int box_deflated = ComputeBoxSum(output, deflated_rect, kImgWidth); + EXPECT_EQ(box_deflated, 0); + EXPECT_EQ(image_total, box_inflated); + + // Try inverted image. Behaviour should be very similar (modulo rounding). + std::transform(input.begin(), input.end(), input.begin(), + std::bind1st(std::minus<unsigned char>(), 255U)); + SingleChannelRecursiveGaussianX(&input[0], src_row_stride, + kChannelIndex, kChannelCount, + recursive_filter, image_size, + &output_x[0], dest_row_stride, + 0, 1, true); + SingleChannelRecursiveGaussianY(&input[0], src_row_stride, + kChannelIndex, kChannelCount, + recursive_filter, image_size, + &output_y[0], dest_row_stride, + 0, 1, true); + + for (target = output.begin(), ix = output_x.begin(), iy = output_y.begin(); + target < output.end(); ++target, ++ix, ++iy) { + *target = *ix + *iy; + } + + image_total = ComputeBoxSum(output, + SkIRect::MakeWH(kImgWidth, kImgHeight), + kImgWidth); + box_inflated = ComputeBoxSum(output, inflated_rect, kImgWidth); + box_deflated = ComputeBoxSum(output, deflated_rect, kImgWidth); + + EXPECT_EQ(box_deflated, 0); + EXPECT_EQ(image_total, box_inflated); +} + +TEST(RecursiveGaussian, SecondDerivative) { + static const int kImgWidth = 700; + static const int kImgHeight = 500; + static const int kChannelIndex = 0; + static const int kChannelCount = 2; + static const int kStrideSlack = 42; + static const int kBoxSize = 200; + + std::vector<unsigned char> input; + SkISize image_size = SkISize::Make(kImgWidth, kImgHeight); + const SkIRect box = MakeBoxImage( + &input, kImgWidth, kImgHeight, kChannelIndex, kChannelCount, + kStrideSlack, kBoxSize, kBoxSize, 200); + + // Destination will be a single channel image with stide matching width. + const int dest_row_stride = kImgWidth; + const int dest_byte_count = dest_row_stride * kImgHeight; + std::vector<unsigned char> output_x(dest_byte_count); + std::vector<unsigned char> output_y(dest_byte_count); + std::vector<unsigned char> output(dest_byte_count); + + const int src_row_stride = ComputeRowStride( + kImgWidth, kChannelCount, kStrideSlack); + + const float kernel_sigma = 5.0f; + const int spread = 8 * kernel_sigma; + RecursiveFilter recursive_filter(kernel_sigma, + RecursiveFilter::SECOND_DERIVATIVE); + SingleChannelRecursiveGaussianX(&input[0], src_row_stride, + kChannelIndex, kChannelCount, + recursive_filter, image_size, + &output_x[0], dest_row_stride, + 0, 1, true); + SingleChannelRecursiveGaussianY(&input[0], src_row_stride, + kChannelIndex, kChannelCount, + recursive_filter, image_size, + &output_y[0], dest_row_stride, + 0, 1, true); + + // In test code we can assume adding the two up should do fine. + std::vector<unsigned char>::const_iterator ix, iy; + std::vector<unsigned char>::iterator target; + for (target = output.begin(),ix = output_x.begin(), iy = output_y.begin(); + target < output.end(); ++target, ++ix, ++iy) { + *target = *ix + *iy; + } + + int image_total = ComputeBoxSum(output, + SkIRect::MakeWH(kImgWidth, kImgHeight), + kImgWidth); + int box_inflated = ComputeBoxSum(output, + SkIRect::MakeLTRB(box.left() - spread, + box.top() - spread, + box.right() + spread, + box.bottom() + spread), + kImgWidth); + int box_deflated = ComputeBoxSum(output, + SkIRect::MakeLTRB(box.left() + spread, + box.top() + spread, + box.right() - spread, + box.bottom() - spread), + kImgWidth); + // Since second derivative is not really used and implemented mostly + // for the sake of completeness, we do not verify the detail (that dip + // in the middle). But it is there. + EXPECT_EQ(box_deflated, 0); + EXPECT_EQ(image_total, box_inflated); +} + +} // namespace skia diff --git a/chromium/skia/ext/refptr.h b/chromium/skia/ext/refptr.h new file mode 100644 index 00000000000..a3900f61ba4 --- /dev/null +++ b/chromium/skia/ext/refptr.h @@ -0,0 +1,118 @@ +// 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 SKIA_EXT_REFPTR_H_ +#define SKIA_EXT_REFPTR_H_ + +#include "third_party/skia/include/core/SkRefCnt.h" + +namespace skia { + +// When creating/receiving a ref-counted pointer from Skia, wrap that pointer in +// this class to avoid dealing with the ref-counting and prevent leaks/crashes +// due to ref-counting bugs. +// +// Example of creating a new SkShader* and setting it on a SkPaint: +// skia::RefPtr<SkShader> shader = skia::AdoptRef(SkGradientShader::Create()); +// paint.setShader(shader.get()); +// +// When passing around a ref-counted pointer to methods outside of Skia, always +// pass around the skia::RefPtr instead of the raw pointer. An example method +// that takes a SkShader* parameter and saves the SkShader* in the class. +// void AMethodThatSavesAShader(const skia::RefPtr<SkShader>& shader) { +// member_refptr_ = shader; +// } +// skia::RefPtr<SkShader> member_refptr_; +// +// When returning a ref-counted pointer, also return the skia::RefPtr instead. +// An example method that creates an SkShader* and returns it: +// skia::RefPtr<SkShader> MakeAShader() { +// return skia::AdoptRef(SkGradientShader::Create()); +// } +// +// To take a scoped reference to an object whose references are all owned +// by other objects (i.e. does not have one that needs to be adopted) use the +// skia::SharePtr helper: +// +// skia::RefPtr<SkShader> shader = skia::SharePtr(paint.getShader()); +// +// Never call ref() or unref() on the underlying ref-counted pointer. If you +// AdoptRef() the raw pointer immediately into a skia::RefPtr and always work +// with skia::RefPtr instances instead, the ref-counting will be taken care of +// for you. +template<typename T> +class RefPtr { + public: + RefPtr() : ptr_(NULL) {} + + RefPtr(const RefPtr& other) + : ptr_(other.get()) { + SkSafeRef(ptr_); + } + + template<typename U> + RefPtr(const RefPtr<U>& other) + : ptr_(other.get()) { + SkSafeRef(ptr_); + } + + ~RefPtr() { + clear(); + } + + RefPtr& operator=(const RefPtr& other) { + SkRefCnt_SafeAssign(ptr_, other.get()); + return *this; + } + + template<typename U> + RefPtr& operator=(const RefPtr<U>& other) { + SkRefCnt_SafeAssign(ptr_, other.get()); + return *this; + } + + void clear() { + T* to_unref = ptr_; + ptr_ = NULL; + SkSafeUnref(to_unref); + } + + T* get() const { return ptr_; } + T& operator*() const { return *ptr_; } + T* operator->() const { return ptr_; } + + typedef T* RefPtr::*unspecified_bool_type; + operator unspecified_bool_type() const { + return ptr_ ? &RefPtr::ptr_ : NULL; + } + + private: + T* ptr_; + + // This function cannot be public because Skia starts its ref-counted + // objects at refcnt=1. This makes it impossible to differentiate + // between a newly created object (that doesn't need to be ref'd) or an + // already existing object with one owner (that does need to be ref'd so that + // this RefPtr can also manage its lifetime). + explicit RefPtr(T* ptr) : ptr_(ptr) {} + + template<typename U> + friend RefPtr<U> AdoptRef(U* ptr); + + template<typename U> + friend RefPtr<U> SharePtr(U* ptr); +}; + +// For objects that have an unowned reference (such as newly created objects). +template<typename T> +RefPtr<T> AdoptRef(T* ptr) { return RefPtr<T>(ptr); } + +// For objects that are already owned. This doesn't take ownership of existing +// references and adds a new one. +template<typename T> +RefPtr<T> SharePtr(T* ptr) { return RefPtr<T>(SkSafeRef(ptr)); } + +} // namespace skia + +#endif // SKIA_EXT_REFPTR_H_ diff --git a/chromium/skia/ext/refptr_unittest.cc b/chromium/skia/ext/refptr_unittest.cc new file mode 100644 index 00000000000..1d63ed1b1e9 --- /dev/null +++ b/chromium/skia/ext/refptr_unittest.cc @@ -0,0 +1,125 @@ +// 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 "skia/ext/refptr.h" + +#include "testing/gtest/include/gtest/gtest.h" + +namespace skia { +namespace { + +TEST(RefPtrTest, ReferenceCounting) { + SkRefCnt* ref = new SkRefCnt(); + EXPECT_EQ(1, ref->getRefCnt()); + + { + // Adopt the reference from the caller on creation. + RefPtr<SkRefCnt> refptr1 = AdoptRef(ref); + EXPECT_EQ(1, ref->getRefCnt()); + EXPECT_EQ(1, refptr1->getRefCnt()); + + EXPECT_EQ(ref, &*refptr1); + EXPECT_EQ(ref, refptr1.get()); + + { + // Take a second reference for the second instance. + RefPtr<SkRefCnt> refptr2(refptr1); + EXPECT_EQ(2, ref->getRefCnt()); + + RefPtr<SkRefCnt> refptr3; + EXPECT_FALSE(refptr3); + + // Take a third reference for the third instance. + refptr3 = refptr1; + EXPECT_EQ(3, ref->getRefCnt()); + + // Same object, so should have the same refcount. + refptr2 = refptr3; + EXPECT_EQ(3, ref->getRefCnt()); + + // Drop the object from refptr2, so it should lose its reference. + EXPECT_TRUE(refptr2); + refptr2.clear(); + EXPECT_EQ(2, ref->getRefCnt()); + + EXPECT_FALSE(refptr2); + EXPECT_EQ(NULL, refptr2.get()); + + EXPECT_TRUE(refptr3); + EXPECT_EQ(2, refptr3->getRefCnt()); + EXPECT_EQ(ref, &*refptr3); + EXPECT_EQ(ref, refptr3.get()); + } + + // Drop a reference when the third object is destroyed. + EXPECT_EQ(1, ref->getRefCnt()); + } +} + +TEST(RefPtrTest, Construct) { + SkRefCnt* ref = new SkRefCnt(); + EXPECT_EQ(1, ref->getRefCnt()); + + // Adopt the reference from the caller on creation. + RefPtr<SkRefCnt> refptr1(AdoptRef(ref)); + EXPECT_EQ(1, ref->getRefCnt()); + EXPECT_EQ(1, refptr1->getRefCnt()); + + EXPECT_EQ(ref, &*refptr1); + EXPECT_EQ(ref, refptr1.get()); + + RefPtr<SkRefCnt> refptr2(refptr1); + EXPECT_EQ(2, ref->getRefCnt()); +} + +TEST(RefPtrTest, DeclareAndAssign) { + SkRefCnt* ref = new SkRefCnt(); + EXPECT_EQ(1, ref->getRefCnt()); + + // Adopt the reference from the caller on creation. + RefPtr<SkRefCnt> refptr1 = AdoptRef(ref); + EXPECT_EQ(1, ref->getRefCnt()); + EXPECT_EQ(1, refptr1->getRefCnt()); + + EXPECT_EQ(ref, &*refptr1); + EXPECT_EQ(ref, refptr1.get()); + + RefPtr<SkRefCnt> refptr2 = refptr1; + EXPECT_EQ(2, ref->getRefCnt()); +} + +TEST(RefPtrTest, Assign) { + SkRefCnt* ref = new SkRefCnt(); + EXPECT_EQ(1, ref->getRefCnt()); + + // Adopt the reference from the caller on creation. + RefPtr<SkRefCnt> refptr1; + refptr1 = AdoptRef(ref); + EXPECT_EQ(1, ref->getRefCnt()); + EXPECT_EQ(1, refptr1->getRefCnt()); + + EXPECT_EQ(ref, &*refptr1); + EXPECT_EQ(ref, refptr1.get()); + + RefPtr<SkRefCnt> refptr2; + refptr2 = refptr1; + EXPECT_EQ(2, ref->getRefCnt()); +} + +class Subclass : public SkRefCnt {}; + +TEST(RefPtrTest, Upcast) { + RefPtr<Subclass> child = AdoptRef(new Subclass()); + EXPECT_EQ(1, child->getRefCnt()); + + RefPtr<SkRefCnt> parent = child; + EXPECT_TRUE(child); + EXPECT_TRUE(parent); + + EXPECT_EQ(2, child->getRefCnt()); + EXPECT_EQ(2, parent->getRefCnt()); +} + +} // namespace +} // namespace skia diff --git a/chromium/skia/ext/skia_trace_shim.h b/chromium/skia/ext/skia_trace_shim.h new file mode 100644 index 00000000000..62bdecc4ce7 --- /dev/null +++ b/chromium/skia/ext/skia_trace_shim.h @@ -0,0 +1,17 @@ +// Copyright (c) 2012 The Chromium 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 SKIA_EXT_SKIA_TRACE_SHIM_H_ +#define SKIA_EXT_SKIA_TRACE_SHIM_H_ + +#include "base/debug/trace_event.h" + +#define SK_TRACE_EVENT0(name) \ + TRACE_EVENT0("skia", name) +#define SK_TRACE_EVENT1(name, arg1_name, arg1_val) \ + TRACE_EVENT1("skia", name, arg1_name, arg1_val) +#define SK_TRACE_EVENT2(name, arg1_name, arg1_val, arg2_name, arg2_val) \ + TRACE_EVENT1("skia", name, arg1_name, arg1_val, arg2_name, arg2_val) + +#endif diff --git a/chromium/skia/ext/skia_utils_base.cc b/chromium/skia/ext/skia_utils_base.cc new file mode 100644 index 00000000000..07c06bb60d9 --- /dev/null +++ b/chromium/skia/ext/skia_utils_base.cc @@ -0,0 +1,53 @@ +// 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 "skia/ext/skia_utils_base.h" + +namespace skia { + +bool ReadSkString(const Pickle& pickle, PickleIterator* iter, SkString* str) { + int reply_length; + const char* reply_text; + + if (!pickle.ReadData(iter, &reply_text, &reply_length)) + return false; + + if (str) + str->set(reply_text, reply_length); + return true; +} + +bool ReadSkFontIdentity(const Pickle& pickle, PickleIterator* iter, + SkFontConfigInterface::FontIdentity* identity) { + uint32_t reply_id; + uint32_t reply_ttcIndex; + int reply_length; + const char* reply_text; + + if (!pickle.ReadUInt32(iter, &reply_id) || + !pickle.ReadUInt32(iter, &reply_ttcIndex) || + !pickle.ReadData(iter, &reply_text, &reply_length)) + return false; + + if (identity) { + identity->fID = reply_id; + identity->fTTCIndex = reply_ttcIndex; + identity->fString.set(reply_text, reply_length); + } + return true; +} + +bool WriteSkString(Pickle* pickle, const SkString& str) { + return pickle->WriteData(str.c_str(), str.size()); +} + +bool WriteSkFontIdentity(Pickle* pickle, + const SkFontConfigInterface::FontIdentity& identity) { + return pickle->WriteUInt32(identity.fID) && + pickle->WriteUInt32(identity.fTTCIndex) && + WriteSkString(pickle, identity.fString); +} + +} // namespace skia + diff --git a/chromium/skia/ext/skia_utils_base.h b/chromium/skia/ext/skia_utils_base.h new file mode 100644 index 00000000000..cfddd31b6f6 --- /dev/null +++ b/chromium/skia/ext/skia_utils_base.h @@ -0,0 +1,33 @@ +// 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 SKIA_EXT_SKIA_UTILS_BASE_H_ +#define SKIA_EXT_SKIA_UTILS_BASE_H_ + +#include "base/pickle.h" +#include "third_party/skia/include/ports/SkFontConfigInterface.h" + +namespace skia { + +// Return true if the pickle/iterator contains a string. If so, and if str +// is not null, copy that string into str. +SK_API bool ReadSkString(const Pickle& pickle, PickleIterator* iter, + SkString* str); + +// Return true if the pickle/iterator contains a FontIdentity. If so, and if +// identity is not null, copy it into identity. +SK_API bool ReadSkFontIdentity(const Pickle& pickle, PickleIterator* iter, + SkFontConfigInterface::FontIdentity* identity); + +// Return true if str can be written into the request pickle. +SK_API bool WriteSkString(Pickle* pickle, const SkString& str); + +// Return true if identity can be written into the request pickle. +SK_API bool WriteSkFontIdentity(Pickle* pickle, + const SkFontConfigInterface::FontIdentity& identity); + +} // namespace skia + +#endif // SKIA_EXT_SKIA_UTILS_BASE_H_ + diff --git a/chromium/skia/ext/skia_utils_ios.h b/chromium/skia/ext/skia_utils_ios.h new file mode 100644 index 00000000000..6a854ce6aff --- /dev/null +++ b/chromium/skia/ext/skia_utils_ios.h @@ -0,0 +1,40 @@ +// 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 SKIA_EXT_SKIA_UTILS_IOS_H_ +#define SKIA_EXT_SKIA_UTILS_IOS_H_ + +#include <CoreGraphics/CoreGraphics.h> +#include <vector> + +#include "third_party/skia/include/core/SkBitmap.h" + +#ifdef __OBJC__ +@class UIImage; +@class NSData; +#else +class UIImage; +class NSData; +#endif + +namespace gfx { + +// Draws a CGImage into an SkBitmap of the given size. +SK_API SkBitmap CGImageToSkBitmap(CGImageRef image, + CGSize size, + bool is_opaque); + +// Given an SkBitmap and a color space, return an autoreleased UIImage. +SK_API UIImage* SkBitmapToUIImageWithColorSpace(const SkBitmap& skia_bitmap, + CGFloat scale, + CGColorSpaceRef color_space); + +// Decodes all image representations inside the data into a vector of SkBitmaps. +// Returns a vector of all the successfully decoded representations or an empty +// vector if none can be decoded. +SK_API std::vector<SkBitmap> ImageDataToSkBitmaps(NSData* image_data); + +} // namespace gfx + +#endif // SKIA_EXT_SKIA_UTILS_IOS_H_ diff --git a/chromium/skia/ext/skia_utils_ios.mm b/chromium/skia/ext/skia_utils_ios.mm new file mode 100644 index 00000000000..80c50fdec21 --- /dev/null +++ b/chromium/skia/ext/skia_utils_ios.mm @@ -0,0 +1,103 @@ +// 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 "skia/ext/skia_utils_ios.h" + +#import <ImageIO/ImageIO.h> +#import <UIKit/UIKit.h> + +#include "base/logging.h" +#include "base/mac/scoped_cftyperef.h" +#include "third_party/skia/include/utils/mac/SkCGUtils.h" + +namespace gfx { + +SkBitmap CGImageToSkBitmap(CGImageRef image, CGSize size, bool is_opaque) { + SkBitmap bitmap; + if (!image) + return bitmap; + + bitmap.setConfig(SkBitmap::kARGB_8888_Config, size.width, size.height); + if (!bitmap.allocPixels()) + return bitmap; + + bitmap.setIsOpaque(is_opaque); + void* data = bitmap.getPixels(); + + // Allocate a bitmap context with 4 components per pixel (BGRA). Apple + // recommends these flags for improved CG performance. +#define HAS_ARGB_SHIFTS(a, r, g, b) \ + (SK_A32_SHIFT == (a) && SK_R32_SHIFT == (r) \ + && SK_G32_SHIFT == (g) && SK_B32_SHIFT == (b)) +#if defined(SK_CPU_LENDIAN) && HAS_ARGB_SHIFTS(24, 16, 8, 0) + base::ScopedCFTypeRef<CGColorSpaceRef> color_space( + CGColorSpaceCreateDeviceRGB()); + base::ScopedCFTypeRef<CGContextRef> context(CGBitmapContextCreate( + data, + size.width, + size.height, + 8, + size.width * 4, + color_space, + kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Host)); +#else +#error We require that Skia's and CoreGraphics's recommended \ + image memory layout match. +#endif +#undef HAS_ARGB_SHIFTS + + DCHECK(context); + if (!context) + return bitmap; + + CGRect imageRect = CGRectMake(0.0, 0.0, size.width, size.height); + CGContextSetBlendMode(context, kCGBlendModeCopy); + CGContextDrawImage(context, imageRect, image); + + return bitmap; +} + +UIImage* SkBitmapToUIImageWithColorSpace(const SkBitmap& skia_bitmap, + CGFloat scale, + CGColorSpaceRef color_space) { + if (skia_bitmap.isNull()) + return nil; + + // First convert SkBitmap to CGImageRef. + base::ScopedCFTypeRef<CGImageRef> cg_image( + SkCreateCGImageRefWithColorspace(skia_bitmap, color_space)); + + // Now convert to UIImage. + return [UIImage imageWithCGImage:cg_image.get() + scale:scale + orientation:UIImageOrientationUp]; +} + +std::vector<SkBitmap> ImageDataToSkBitmaps(NSData* image_data) { + DCHECK(image_data); + base::ScopedCFTypeRef<CFDictionaryRef> empty_dictionary( + CFDictionaryCreate(NULL, NULL, NULL, 0, NULL, NULL)); + std::vector<SkBitmap> frames; + + base::ScopedCFTypeRef<CGImageSourceRef> source( + CGImageSourceCreateWithData((CFDataRef)image_data, empty_dictionary)); + + size_t count = CGImageSourceGetCount(source); + for (size_t index = 0; index < count; ++index) { + base::ScopedCFTypeRef<CGImageRef> cg_image( + CGImageSourceCreateImageAtIndex(source, index, empty_dictionary)); + + CGSize size = CGSizeMake(CGImageGetWidth(cg_image), + CGImageGetHeight(cg_image)); + const SkBitmap bitmap = CGImageToSkBitmap(cg_image, size, false); + if (!bitmap.empty()) + frames.push_back(bitmap); + } + + DLOG_IF(WARNING, frames.size() != count) << "Only decoded " << frames.size() + << " frames for " << count << " expected."; + return frames; +} + +} // namespace gfx diff --git a/chromium/skia/ext/skia_utils_ios_unittest.mm b/chromium/skia/ext/skia_utils_ios_unittest.mm new file mode 100644 index 00000000000..c26e688291b --- /dev/null +++ b/chromium/skia/ext/skia_utils_ios_unittest.mm @@ -0,0 +1,157 @@ +// Copyright (c) 2012 The Chromium 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 <UIKit/UIKit.h> + +#include "base/base64.h" +#include "skia/ext/skia_utils_ios.mm" +#include "testing/gtest/include/gtest/gtest.h" + +namespace { + +typedef testing::Test SkiaUtilsIosTest; + +// This is a base64 encoded version of google.com ico file. generated by +// curl http://www.google.com/favicon.ico | base64 -b 76 +const char kIcoEncodedData[] = + "AAABAAIAEBAAAAEAIABoBAAAJgAAACAgAAABACAAqBAAAI4EAAAoAAAAEAAAACAAAAABACAAAAAA" + "AAAEAAASCwAAEgsAAAAAAAAAAAAA9IVCSvSFQuf0hUL/9IVC//SFQv/0hUL/9IVC//SFQv/0hUL/" + "9IVC//SFQv/0hUL/9IVC//SFQv/0hULk9IVCSvSFQub0hUL/9IVC//SFQv/0hUL/9IVC//SFQv/0" + "hUL/9IVC//SFQv/0hUL/9IVC//SFQv/0hUL/9IVC//SFQuf0hUL/9IVC//SFQv/0hUL/9Y1O//rI" + "q//+7+f//eXX//vUvf/7z7X/96Fu//SFQv/0hUL/9IVC//SFQv/0hUL/9IVC//SFQv/0hUL/9IVC" + "//vYwv/97OH/9ZRZ//SFQv/0hUL/9IhG//zbx//3om7/9IVC//SFQv/0hUL/9IVC//SFQv/0hUL/" + "9IVC//SFQv/97uX/+buW//SFQv/0hUL/9IVC//SFQv/5upT/+9O6//SFQv/0hUL/9IVC//SFQv/0" + "hUL/9IVC//SFQv/0hUL/+b6b//zezP/0iEf/9IVC//SFQv/1klf//ezh//vPtP/0hUL/9IVC//SF" + "Qv/0hUL/9IVC//SFQv/0hUL/9IVC//SFQv/3qXr/+siq//m8lv/5wqD//vTu//3t4//1klb/9IVC" + "//SFQv/0hUL/9IVC//SFQv/0hUL/9IVC//SFQv/0hUL/9IVC//SFQv/0h0b//vbx//zi0//1j1H/" + "9IVC//SFQv/0hUL/9IVC//SFQv/0hUL/9IVC//SFQv/0hUL/9IVC//SFQv/2nmn/+bmS/////v/4" + "sIX/9IVC//SFQv/0hUL/9IVC//SFQv/0hUL/9IVC//SFQv/0hUL/9IVC//SFQv/5uJH///v5//eo" + "ef/1jU//+82y//afav/0hUL/9IVC//SFQv/0hUL/9IVC//SFQv/0hUL/9IVC//SFQv/0hUL//vXw" + "//vOs//0hUL/9IVC//ekcf/96+D/9IVC//SFQv/0hUL/9IVC//SFQv/0hUL/9IVC//SFQv/0hUL/" + "9IVC//728v/4sIX/9IVC//SFQv/4s4n///v4//SFQv/0hUL/9IVC//SFQv/0hUL/9IVC//SFQv/0" + "hUL/9IVC//SFQv/6yKn/+byX//SFQv/0hkT//eTV//vWv//0hUL/9IVC//SFQv/0hUL/9IVC//SF" + "Qv/0hUL/9IVC//SFQv/0hUL/9IZE//m6lP/5u5b//OHQ///+/f/6y6//96d3//SFQv/0hUL/9IVC" + "//SFQv/0hULm9IVC//SFQv/0hUL/9IVC//SFQv/0hUL/9IVC//SFQv/0hUL/9IVC//SFQv/0hUL/" + "9IVC//SFQv/0hULm9IVCSfSFQub0hUL/9IVC//SFQv/0hUL/9IVC//SFQv/0hUL/9IVC//SFQv/0" + "hUL/9IVC//SFQv/0hULm9IVCSQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoAAAAIAAAAEAAAAABACAAAAAAAAAQAAASCwAAEgsA" + "AAAAAAAAAAAA9IVCAPSFQif0hUKt9IVC8vSFQv/0hUL/9IVC//SFQv/0hUL/9IVC//SFQv/0hUL/" + "9IVC//SFQv/0hUL/9IVC//SFQv/0hUL/9IVC//SFQv/0hUL/9IVC//SFQv/0hUL/9IVC//SFQv/0" + "hUL/9IVC//SFQvL0hUKt9IVCJ/SFQgD0hUIo9IVC7/SFQv/0hUL/9IVC//SFQv/0hUL/9IVC//SF" + "Qv/0hUL/9IVC//SFQv/0hUL/9IVC//SFQv/0hUL/9IVC//SFQv/0hUL/9IVC//SFQv/0hUL/9IVC" + "//SFQv/0hUL/9IVC//SFQv/0hUL/9IVC//SFQv/0hULv9IVCKPSFQq30hUL/9IVC//SFQv/0hUL/" + "9IVC//SFQv/0hUL/9IVC//SFQv/0hUL/9IVC//SFQv/0hUL/9IVC//SFQv/0hUL/9IVC//SFQv/0" + "hUL/9IVC//SFQv/0hUL/9IVC//SFQv/0hUL/9IVC//SFQv/0hUL/9IVC//SFQv/0hUKt9IVC8fSF" + "Qv/0hUL/9IVC//SFQv/0hUL/9IVC//SFQv/0hUL/9IVC//SFQv/0hUL/9IVC//SFQv/0hUL/9IVC" + "//SFQv/0hUL/9IVC//SFQv/0hUL/9IVC//SFQv/0hUL/9IVC//SFQv/0hUL/9IVC//SFQv/0hUL/" + "9IVC//SFQvP0hUL/9IVC//SFQv/0hUL/9IVC//SFQv/0hUL/9IVC//SFQv/0hUL/9YtL//i2jv/8" + "28f//vLr///7+P///Pv//vTu//3n2v/6zbH/96Nw//SFQ//0hUL/9IVC//SFQv/0hUL/9IVC//SF" + "Qv/0hUL/9IVC//SFQv/0hUL/9IVC//SFQv/0hUL/9IVC//SFQv/0hUL/9IVC//SFQv/0hUL/9IVC" + "//ekcv/+8+z////////////+9fD/+9K5//m9mf/4to7/+buV//vSuf/++PT//OPT//aYYP/0hUL/" + "9IVC//SFQv/0hUL/9IVC//SFQv/0hUL/9IVC//SFQv/0hUL/9IVC//SFQv/0hUL/9IVC//SFQv/0" + "hUL/9IVC//SFQv/2l13///r3/////////fv/+b2Z//SIRv/0hUL/9IVC//SFQv/0hUL/9IVC//WN" + "T//84M///vXv//aZYf/0hUL/9IVC//SFQv/0hUL/9IVC//SFQv/0hUL/9IVC//SFQv/0hUL/9IVC" + "//SFQv/0hUL/9IVC//SFQv/0hUL/9IVC//vPtP////////////i0i//0hUL/9IVC//SFQv/0hUL/" + "9IVC//SFQv/0hUL/9IVC//WQUv///Pr//OPU//SFQv/0hUL/9IVC//SFQv/0hUL/9IVC//SFQv/0" + "hUL/9IVC//SFQv/0hUL/9IVC//SFQv/0hUL/9IVC//SFQv/0hUL//eTV///////+9O7/9IVD//SF" + "Qv/0hUL/9IVC//SFQv/0hUL/9IVC//SFQv/0hUL/9IVC//3m2P//////9ppi//SFQv/0hUL/9IVC" + "//SFQv/0hUL/9IVC//SFQv/0hUL/9IVC//SFQv/0hUL/9IVC//SFQv/0hUL/9IVC//SFQv/718H/" + "//////3s4f/0hUL/9IVC//SFQv/0hUL/9IVC//SFQv/0hUL/9IVC//SFQv/0hUL//vDn///////4" + "soj/9IVC//SFQv/0hUL/9IVC//SFQv/0hUL/9IVC//SFQv/0hUL/9IVC//SFQv/0hUL/9IVC//SF" + "Qv/0hUL/9IVC//erff////////38//WTWP/0hUL/9IVC//SFQv/0hUL/9IVC//SFQv/0hUL/9IVC" + "//iziv////////////iwhf/0hUL/9IVC//SFQv/0hUL/9IVC//SFQv/0hUL/9IVC//SFQv/0hUL/" + "9IVC//SFQv/0hUL/9IVC//SFQv/0hUL/9IVC//rMsP///////eXW//WSVv/0hUL/9IVC//SFQv/0" + "hUL/9IVC//SFQv/4sYb///z7/////////Pv/9ZFV//SFQv/0hUL/9IVC//SFQv/0hUL/9IVC//SF" + "Qv/0hUL/9IVC//SFQv/0hUL/9IVC//SFQv/0hUL/9IVC//SFQv/0hUL/9IVC//ixhv/+8Of//vn1" + "//rMsP/4rH//9plh//WQUv/1j1L/+s2x//////////////////m9mf/0hUL/9IVC//SFQv/0hUL/" + "9IVC//SFQv/0hUL/9IVC//SFQv/0hUL/9IVC//SFQv/0hUL/9IVC//SFQv/0hUL/9IVC//SFQv/0" + "hUL/9IVC//SGQ//2nmn/+buW//vNsv/82sb//e3j/////////////////////v/5wZ//9IVC//SF" + "Qv/0hUL/9IVC//SFQv/0hUL/9IVC//SFQv/0hUL/9IVC//SFQv/0hUL/9IVC//SFQv/0hUL/9IVC" + "//SFQv/0hUL/9IVC//SFQv/0hUL/9IVC//SFQv/0hUL/9IVC//SFQv/83Mj////////////++fb/" + "+K+C//SFQv/0hUL/9IVC//SFQv/0hUL/9IVC//SFQv/0hUL/9IVC//SFQv/0hUL/9IVC//SFQv/0" + "hUL/9IVC//SFQv/0hUL/9IVC//SFQv/0hUL/9IVC//SFQv/0hUL/9IVC//SFQv/0hUL/9ZRZ////" + "/////////vTt//aaYv/0hUL/9IVC//SFQv/0hUL/9IVC//SFQv/0hUL/9IVC//SFQv/0hUL/9IVC" + "//SFQv/0hUL/9IVC//SFQv/0hUL/9IVC//SFQv/0hUL/9IVC//SFQv/0hUL/9IVC//SFQv/0hUL/" + "9IVC//SFQv/1lFr////////////6xqf/9IVC//SFQv/0hUL/9IVC//SFQv/0hUL/9IVC//SFQv/0" + "hUL/9IVC//SFQv/0hUL/9IVC//SFQv/0hUL/9IVC//SFQv/0hUL/9IVC//SFQv/0hUL/9IVC//SF" + "Qv/0hUL/9IVC//ehbf/70bj//end//3o2////v3///////3l1//0iEb/9IVC//SFQv/0hUL/9IVC" + "//SFQv/0hUL/9IVC//SFQv/0hUL/9IVC//SFQv/0hUL/9IVC//SFQv/0hUL/9IVC//SFQv/0hUL/" + "9IVC//SFQv/0hUL/9IVC//SFQv/5wqD////////////96t7/96Z2//WOUP/2nWf//NvH//zcyP/1" + "i0z/9IVC//SFQv/0hUL/9IVC//SFQv/0hUL/9IVC//SFQv/0hUL/9IVC//SFQv/0hUL/9IVC//SF" + "Qv/0hUL/9IVC//SFQv/0hUL/9IVC//SFQv/0hUL/96l6/////////////vLr//WPUf/0hUL/9IVC" + "//SFQv/0h0b//end//3k1f/0iUn/9IVC//SFQv/0hUL/9IVC//SFQv/0hUL/9IVC//SFQv/0hUL/" + "9IVC//SFQv/0hUL/9IVC//SFQv/0hUL/9IVC//SFQv/0hUL/9IVC//SFQv/8387////////////4" + "sYf/9IVC//SFQv/0hUL/9IVC//SFQv/6w6L///////nBn//0hUL/9IVC//SFQv/0hUL/9IVC//SF" + "Qv/0hUL/9IVC//SFQv/0hUL/9IVC//SFQv/0hUL/9IVC//SFQv/0hUL/9IVC//SFQv/0hUL/9IVC" + "///69////////vj1//SIR//0hUL/9IVC//SFQv/0hUL/9IVC//m+mv///////e3j//SFQv/0hUL/" + "9IVC//SFQv/0hUL/9IVC//SFQv/0hUL/9IVC//SFQv/0hUL/9IVC//SFQv/0hUL/9IVC//SFQv/0" + "hUL/9IVC//SFQv/0hUL///r3///////8387/9IVC//SFQv/0hUL/9IVC//SFQv/0hUL/+syw////" + "///++fb/9IVC//SFQv/0hUL/9IVC//SFQv/0hUL/9IVC//SFQv/0hUL/9IVC//SFQv/0hUL/9IVC" + "//SFQv/0hUL/9IVC//SFQv/0hUL/9IVC//SFQv/95NX///////vUvP/0hUL/9IVC//SFQv/0hUL/" + "9IVC//SFQv/97OH///////7y6//0hUL/9IVC//SFQv/0hUL/9IVC//SFQv/0hUL/9IVC//SFQv/0" + "hUL/9IVC//SFQv/0hUL/9IVC//SFQv/0hUL/9IVC//SFQv/0hUL/9IVC//i2jv///////N/O//SF" + "Qv/0hUL/9IVC//SFQv/0hUL/96Nx////////////+s2x//SFQv/0hUL/9IVC//SFQv/0hUL/9IVC" + "//SFQv/0hUL/9IVC//SFQv/0hUL/9IVC//SFQv/0hUL/9IVC//SFQv/0hUL/9IVC//SFQv/0hUL/" + "9IdF//zh0P//+/j/9ZJW//SFQv/0hUL/9IVC//SKSv/96t7///////738v/1k1f/9IVC//SFQv/0" + "hUL/9IVC//SFQv/0hUL/9IVC//SFQv/0hUL/9IVC//SFQv/0hUL/9IVC//SFQv/0hUL/9IVC//SF" + "Qv/0hUL/9IVC//SFQv/0hUL/9YxN//vUvf/96+D/96Z0//WNT//3om///ebY/////////Pv/+LKI" + "//WVW//0h0X/9IVC//SFQv/0hUL/9IVC//SFQv/0hUL/9IVC//SFQv/0hUL/9IVC//SFQv/0hUL/" + "9IVC//SFQv/0hUL/9IVC//SFQv/0hUL/9IVC//SFQv/0hUL/9IVC//agbP/7zbL//enc//749P//" + "//////////////////////////3r4P/3p3f/9IVC//SFQv/0hUL/9IVC//SFQv/0hUL/9IVC//SF" + "Qv/0hULx9IVC//SFQv/0hUL/9IVC//SFQv/0hUL/9IVC//SFQv/0hUL/9IVC//SFQv/0hUL/9IVC" + "//SFQv/0hUL/9IVC//SFQv/0hUL/9IVC//SFQv/0hUL/9IVC//SFQv/0hUL/9IVC//SFQv/0hUL/" + "9IVC//SFQv/0hUL/9IVC8/SFQq30hUL/9IVC//SFQv/0hUL/9IVC//SFQv/0hUL/9IVC//SFQv/0" + "hUL/9IVC//SFQv/0hUL/9IVC//SFQv/0hUL/9IVC//SFQv/0hUL/9IVC//SFQv/0hUL/9IVC//SF" + "Qv/0hUL/9IVC//SFQv/0hUL/9IVC//SFQv/0hUKt9IVCJ/SFQu/0hUL/9IVC//SFQv/0hUL/9IVC" + "//SFQv/0hUL/9IVC//SFQv/0hUL/9IVC//SFQv/0hUL/9IVC//SFQv/0hUL/9IVC//SFQv/0hUL/" + "9IVC//SFQv/0hUL/9IVC//SFQv/0hUL/9IVC//SFQv/0hUL/9IVC7/SFQif0hUIA9IVCJfSFQq30" + "hULx9IVC//SFQv/0hUL/9IVC//SFQv/0hUL/9IVC//SFQv/0hUL/9IVC//SFQv/0hUL/9IVC//SF" + "Qv/0hUL/9IVC//SFQv/0hUL/9IVC//SFQv/0hUL/9IVC//SFQv/0hUL/9IVC8fSFQq30hUIl9IVC" + "AIAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAACAAAAB"; + +NSData* IcoData() { + std::string output; + EXPECT_TRUE(base::Base64Decode(kIcoEncodedData, &output)); + return [NSData dataWithBytes:output.data() length:output.length()]; +} + +NSData* InvalidData(NSUInteger length) { + NSMutableData* data=[NSMutableData dataWithLength:length]; + memset([data mutableBytes], 0xFF, length); + return data; +} + +TEST_F(SkiaUtilsIosTest, ImageDataToSkBitmaps) { + std::vector<SkBitmap> bitmaps(gfx::ImageDataToSkBitmaps(IcoData())); + + EXPECT_EQ(2UL, bitmaps.size()); + EXPECT_EQ(32, bitmaps[0].width()); + EXPECT_EQ(32, bitmaps[0].height()); + EXPECT_EQ(16, bitmaps[1].width()); + EXPECT_EQ(16, bitmaps[1].height()); +} + +TEST_F(SkiaUtilsIosTest, InvalidDataFailure) { + std::vector<SkBitmap> bitmaps1(gfx::ImageDataToSkBitmaps(InvalidData(1))); + EXPECT_EQ(0UL, bitmaps1.size()); + std::vector<SkBitmap> bitmaps2(gfx::ImageDataToSkBitmaps(InvalidData(10))); + EXPECT_EQ(0UL, bitmaps2.size()); + std::vector<SkBitmap> bitmaps3(gfx::ImageDataToSkBitmaps(InvalidData(100))); + EXPECT_EQ(0UL, bitmaps3.size()); + std::vector<SkBitmap> bitmaps4(gfx::ImageDataToSkBitmaps(InvalidData(1000))); + EXPECT_EQ(0UL, bitmaps4.size()); + std::vector<SkBitmap> bitmaps5(gfx::ImageDataToSkBitmaps(InvalidData(5000))); + EXPECT_EQ(0UL, bitmaps5.size()); +} + +TEST_F(SkiaUtilsIosTest, EmptyDataFailure) { + std::vector<SkBitmap> bitmaps(gfx::ImageDataToSkBitmaps([NSData data])); + + EXPECT_EQ(0UL, bitmaps.size()); +} + +} // namespace + diff --git a/chromium/skia/ext/skia_utils_mac.h b/chromium/skia/ext/skia_utils_mac.h new file mode 100644 index 00000000000..e6f7b35bbc2 --- /dev/null +++ b/chromium/skia/ext/skia_utils_mac.h @@ -0,0 +1,122 @@ +// Copyright (c) 2012 The Chromium 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 SKIA_EXT_SKIA_UTILS_MAC_H_ +#define SKIA_EXT_SKIA_UTILS_MAC_H_ + +#include <ApplicationServices/ApplicationServices.h> +#include <vector> + +#include "third_party/skia/include/core/SkBitmap.h" +#include "third_party/skia/include/core/SkColor.h" + +struct SkIRect; +struct SkPoint; +struct SkRect; +class SkCanvas; +class SkMatrix; +#ifdef __LP64__ +typedef CGSize NSSize; +#else +typedef struct _NSSize NSSize; +#endif + +#ifdef __OBJC__ +@class NSBitmapImageRep; +@class NSImage; +@class NSImageRep; +@class NSColor; +#else +class NSBitmapImageRep; +class NSImage; +class NSImageRep; +class NSColor; +#endif + +namespace gfx { + +// Converts a Skia point to a CoreGraphics CGPoint. +// Both use same in-memory format. +inline const CGPoint& SkPointToCGPoint(const SkPoint& point) { + return reinterpret_cast<const CGPoint&>(point); +} + +// Converts a CoreGraphics point to a Skia CGPoint. +// Both use same in-memory format. +inline const SkPoint& CGPointToSkPoint(const CGPoint& point) { + return reinterpret_cast<const SkPoint&>(point); +} + +// Matrix converters. +SK_API CGAffineTransform SkMatrixToCGAffineTransform(const SkMatrix& matrix); + +// Rectangle converters. +SK_API SkRect CGRectToSkRect(const CGRect& rect); + +// Converts a Skia rect to a CoreGraphics CGRect. +CGRect SkIRectToCGRect(const SkIRect& rect); +CGRect SkRectToCGRect(const SkRect& rect); + +// Converts CGColorRef to the ARGB layout Skia expects. +SK_API SkColor CGColorRefToSkColor(CGColorRef color); + +// Converts ARGB to CGColorRef. +SK_API CGColorRef CGColorCreateFromSkColor(SkColor color); + +// Converts NSColor to ARGB. Returns raw rgb values and does no colorspace +// conversion. Only valid for colors in calibrated and device color spaces. +SK_API SkColor NSDeviceColorToSkColor(NSColor* color); + +// Converts ARGB in the specified color space to NSColor. +// Prefer sRGB over calibrated colors. +SK_API NSColor* SkColorToCalibratedNSColor(SkColor color); +SK_API NSColor* SkColorToDeviceNSColor(SkColor color); +SK_API NSColor* SkColorToSRGBNSColor(SkColor color); + +// Converts a CGImage to a SkBitmap. +SK_API SkBitmap CGImageToSkBitmap(CGImageRef image); + +// Draws an NSImage with a given size into a SkBitmap. +SK_API SkBitmap NSImageToSkBitmap(NSImage* image, NSSize size, bool is_opaque); + +// Draws an NSImageRep with a given size into a SkBitmap. +SK_API SkBitmap NSImageRepToSkBitmap( + NSImageRep* image, NSSize size, bool is_opaque); + +// Given an SkBitmap, return an autoreleased NSBitmapImageRep in the generic +// color space. +SK_API NSBitmapImageRep* SkBitmapToNSBitmapImageRep(const SkBitmap& image); + +SK_API NSBitmapImageRep* SkBitmapToNSBitmapImageRepWithColorSpace( + const SkBitmap& skiaBitmap, + CGColorSpaceRef colorSpace); + +// Given an SkBitmap and a color space, return an autoreleased NSImage. +SK_API NSImage* SkBitmapToNSImageWithColorSpace(const SkBitmap& icon, + CGColorSpaceRef colorSpace); + +// Given an SkBitmap, return an autoreleased NSImage in the generic color space. +// DEPRECATED, use SkBitmapToNSImageWithColorSpace() instead. +// TODO(thakis): Remove this -- http://crbug.com/69432 +SK_API NSImage* SkBitmapToNSImage(const SkBitmap& icon); + +// Converts a SkCanvas temporarily to a CGContext +class SK_API SkiaBitLocker { + public: + explicit SkiaBitLocker(SkCanvas* canvas); + ~SkiaBitLocker(); + CGContextRef cgContext(); + + private: + void releaseIfNeeded(); + SkCanvas* canvas_; + CGContextRef cgContext_; + SkBitmap bitmap_; + bool useDeviceBits_; +}; + + +} // namespace gfx + +#endif // SKIA_EXT_SKIA_UTILS_MAC_H_ diff --git a/chromium/skia/ext/skia_utils_mac.mm b/chromium/skia/ext/skia_utils_mac.mm new file mode 100644 index 00000000000..213bb00e498 --- /dev/null +++ b/chromium/skia/ext/skia_utils_mac.mm @@ -0,0 +1,429 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "skia/ext/skia_utils_mac.h" + +#import <AppKit/AppKit.h> + +#include "base/logging.h" +#include "base/mac/scoped_cftyperef.h" +#include "base/mac/scoped_nsobject.h" +#include "base/memory/scoped_ptr.h" +#include "skia/ext/bitmap_platform_device_mac.h" +#include "third_party/skia/include/core/SkRegion.h" +#include "third_party/skia/include/utils/mac/SkCGUtils.h" + +namespace { + +// Draws an NSImage or an NSImageRep with a given size into a SkBitmap. +SkBitmap NSImageOrNSImageRepToSkBitmap( + NSImage* image, + NSImageRep* image_rep, + NSSize size, + bool is_opaque) { + // Only image or image_rep should be provided, not both. + DCHECK((image != 0) ^ (image_rep != 0)); + + SkBitmap bitmap; + bitmap.setConfig(SkBitmap::kARGB_8888_Config, size.width, size.height); + if (!bitmap.allocPixels()) + return bitmap; // Return |bitmap| which should respond true to isNull(). + + bitmap.setIsOpaque(is_opaque); + + base::ScopedCFTypeRef<CGColorSpaceRef> color_space( + CGColorSpaceCreateWithName(kCGColorSpaceGenericRGB)); + void* data = bitmap.getPixels(); + + // Allocate a bitmap context with 4 components per pixel (BGRA). Apple + // recommends these flags for improved CG performance. +#define HAS_ARGB_SHIFTS(a, r, g, b) \ + (SK_A32_SHIFT == (a) && SK_R32_SHIFT == (r) \ + && SK_G32_SHIFT == (g) && SK_B32_SHIFT == (b)) +#if defined(SK_CPU_LENDIAN) && HAS_ARGB_SHIFTS(24, 16, 8, 0) + base::ScopedCFTypeRef<CGContextRef> context(CGBitmapContextCreate( + data, + size.width, + size.height, + 8, + size.width * 4, + color_space, + kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Host)); +#else +#error We require that Skia's and CoreGraphics's recommended \ + image memory layout match. +#endif +#undef HAS_ARGB_SHIFTS + + // Something went really wrong. Best guess is that the bitmap data is invalid. + DCHECK(context); + + [NSGraphicsContext saveGraphicsState]; + + NSGraphicsContext* context_cocoa = + [NSGraphicsContext graphicsContextWithGraphicsPort:context flipped:NO]; + [NSGraphicsContext setCurrentContext:context_cocoa]; + + NSRect drawRect = NSMakeRect(0, 0, size.width, size.height); + if (image) { + [image drawInRect:drawRect + fromRect:NSZeroRect + operation:NSCompositeCopy + fraction:1.0]; + } else { + [image_rep drawInRect:drawRect + fromRect:NSZeroRect + operation:NSCompositeCopy + fraction:1.0 + respectFlipped:NO + hints:nil]; + } + + [NSGraphicsContext restoreGraphicsState]; + + return bitmap; +} + +} // namespace + +namespace gfx { + +CGAffineTransform SkMatrixToCGAffineTransform(const SkMatrix& matrix) { + // CGAffineTransforms don't support perspective transforms, so make sure + // we don't get those. + DCHECK(matrix[SkMatrix::kMPersp0] == 0.0f); + DCHECK(matrix[SkMatrix::kMPersp1] == 0.0f); + DCHECK(matrix[SkMatrix::kMPersp2] == 1.0f); + + return CGAffineTransformMake(matrix[SkMatrix::kMScaleX], + matrix[SkMatrix::kMSkewY], + matrix[SkMatrix::kMSkewX], + matrix[SkMatrix::kMScaleY], + matrix[SkMatrix::kMTransX], + matrix[SkMatrix::kMTransY]); +} + +SkRect CGRectToSkRect(const CGRect& rect) { + SkRect sk_rect = { + rect.origin.x, rect.origin.y, CGRectGetMaxX(rect), CGRectGetMaxY(rect) + }; + return sk_rect; +} + +CGRect SkIRectToCGRect(const SkIRect& rect) { + CGRect cg_rect = { + { rect.fLeft, rect.fTop }, + { rect.fRight - rect.fLeft, rect.fBottom - rect.fTop } + }; + return cg_rect; +} + +CGRect SkRectToCGRect(const SkRect& rect) { + CGRect cg_rect = { + { rect.fLeft, rect.fTop }, + { rect.fRight - rect.fLeft, rect.fBottom - rect.fTop } + }; + return cg_rect; +} + +// Converts CGColorRef to the ARGB layout Skia expects. +SkColor CGColorRefToSkColor(CGColorRef color) { + DCHECK(CGColorGetNumberOfComponents(color) == 4); + const CGFloat* components = CGColorGetComponents(color); + return SkColorSetARGB(SkScalarRound(255.0 * components[3]), // alpha + SkScalarRound(255.0 * components[0]), // red + SkScalarRound(255.0 * components[1]), // green + SkScalarRound(255.0 * components[2])); // blue +} + +// Converts ARGB to CGColorRef. +CGColorRef CGColorCreateFromSkColor(SkColor color) { + return CGColorCreateGenericRGB(SkColorGetR(color) / 255.0, + SkColorGetG(color) / 255.0, + SkColorGetB(color) / 255.0, + SkColorGetA(color) / 255.0); +} + +// Converts NSColor to ARGB +SkColor NSDeviceColorToSkColor(NSColor* color) { + DCHECK([color colorSpace] == [NSColorSpace genericRGBColorSpace] || + [color colorSpace] == [NSColorSpace deviceRGBColorSpace]); + CGFloat red, green, blue, alpha; + color = [color colorUsingColorSpace:[NSColorSpace deviceRGBColorSpace]]; + [color getRed:&red green:&green blue:&blue alpha:&alpha]; + return SkColorSetARGB(SkScalarRound(255.0 * alpha), + SkScalarRound(255.0 * red), + SkScalarRound(255.0 * green), + SkScalarRound(255.0 * blue)); +} + +// Converts ARGB to NSColor. +NSColor* SkColorToCalibratedNSColor(SkColor color) { + return [NSColor colorWithCalibratedRed:SkColorGetR(color) / 255.0 + green:SkColorGetG(color) / 255.0 + blue:SkColorGetB(color) / 255.0 + alpha:SkColorGetA(color) / 255.0]; +} + +NSColor* SkColorToDeviceNSColor(SkColor color) { + return [NSColor colorWithDeviceRed:SkColorGetR(color) / 255.0 + green:SkColorGetG(color) / 255.0 + blue:SkColorGetB(color) / 255.0 + alpha:SkColorGetA(color) / 255.0]; +} + +NSColor* SkColorToSRGBNSColor(SkColor color) { + const CGFloat components[] = { + SkColorGetR(color) / 255.0, + SkColorGetG(color) / 255.0, + SkColorGetB(color) / 255.0, + SkColorGetA(color) / 255.0 + }; + return [NSColor colorWithColorSpace:[NSColorSpace sRGBColorSpace] + components:components + count:4]; +} + +SkBitmap CGImageToSkBitmap(CGImageRef image) { + if (!image) + return SkBitmap(); + + int width = CGImageGetWidth(image); + int height = CGImageGetHeight(image); + + scoped_ptr<SkDevice> device( + skia::BitmapPlatformDevice::Create(NULL, width, height, false)); + + CGContextRef context = skia::GetBitmapContext(device.get()); + + // We need to invert the y-axis of the canvas so that Core Graphics drawing + // happens right-side up. Skia has an upper-left origin and CG has a lower- + // left one. + CGContextScaleCTM(context, 1.0, -1.0); + CGContextTranslateCTM(context, 0, -height); + + // We want to copy transparent pixels from |image|, instead of blending it + // onto uninitialized pixels. + CGContextSetBlendMode(context, kCGBlendModeCopy); + + CGRect rect = CGRectMake(0, 0, width, height); + CGContextDrawImage(context, rect, image); + + // Because |device| will be cleaned up and will take its pixels with it, we + // copy it to the stack and return it. + SkBitmap bitmap = device->accessBitmap(false); + + return bitmap; +} + +SkBitmap NSImageToSkBitmap(NSImage* image, NSSize size, bool is_opaque) { + return NSImageOrNSImageRepToSkBitmap(image, nil, size, is_opaque); +} + +SkBitmap NSImageRepToSkBitmap( + NSImageRep* image_rep, NSSize size, bool is_opaque) { + return NSImageOrNSImageRepToSkBitmap(nil, image_rep, size, is_opaque); +} + +NSBitmapImageRep* SkBitmapToNSBitmapImageRep(const SkBitmap& skiaBitmap) { + base::ScopedCFTypeRef<CGColorSpaceRef> color_space( + CGColorSpaceCreateWithName(kCGColorSpaceGenericRGB)); + return SkBitmapToNSBitmapImageRepWithColorSpace(skiaBitmap, color_space); +} + +NSBitmapImageRep* SkBitmapToNSBitmapImageRepWithColorSpace( + const SkBitmap& skiaBitmap, + CGColorSpaceRef colorSpace) { + // First convert SkBitmap to CGImageRef. + base::ScopedCFTypeRef<CGImageRef> cgimage( + SkCreateCGImageRefWithColorspace(skiaBitmap, colorSpace)); + + // Now convert to NSBitmapImageRep. + base::scoped_nsobject<NSBitmapImageRep> bitmap( + [[NSBitmapImageRep alloc] initWithCGImage:cgimage]); + return [bitmap.release() autorelease]; +} + +NSImage* SkBitmapToNSImageWithColorSpace(const SkBitmap& skiaBitmap, + CGColorSpaceRef colorSpace) { + if (skiaBitmap.isNull()) + return nil; + + base::scoped_nsobject<NSImage> image([[NSImage alloc] init]); + [image addRepresentation: + SkBitmapToNSBitmapImageRepWithColorSpace(skiaBitmap, colorSpace)]; + [image setSize:NSMakeSize(skiaBitmap.width(), skiaBitmap.height())]; + return [image.release() autorelease]; +} + +NSImage* SkBitmapToNSImage(const SkBitmap& skiaBitmap) { + base::ScopedCFTypeRef<CGColorSpaceRef> colorSpace( + CGColorSpaceCreateWithName(kCGColorSpaceGenericRGB)); + return SkBitmapToNSImageWithColorSpace(skiaBitmap, colorSpace.get()); +} + +SkiaBitLocker::SkiaBitLocker(SkCanvas* canvas) + : canvas_(canvas), + cgContext_(0) { +} + +SkiaBitLocker::~SkiaBitLocker() { + releaseIfNeeded(); +} + +// This must be called to balance calls to cgContext +void SkiaBitLocker::releaseIfNeeded() { + if (!cgContext_) + return; + if (useDeviceBits_) { + bitmap_.unlockPixels(); + } else { + // Find the bits that were drawn to. + SkAutoLockPixels lockedPixels(bitmap_); + const uint32_t* pixelBase + = reinterpret_cast<uint32_t*>(bitmap_.getPixels()); + int rowPixels = bitmap_.rowBytesAsPixels(); + int width = bitmap_.width(); + int height = bitmap_.height(); + SkIRect bounds; + bounds.fTop = 0; + int x; + int y = -1; + const uint32_t* pixels = pixelBase; + while (++y < height) { + for (x = 0; x < width; ++x) { + if (pixels[x]) { + bounds.fTop = y; + goto foundTop; + } + } + pixels += rowPixels; + } +foundTop: + bounds.fBottom = height; + y = height; + pixels = pixelBase + rowPixels * (y - 1); + while (--y > bounds.fTop) { + for (x = 0; x < width; ++x) { + if (pixels[x]) { + bounds.fBottom = y + 1; + goto foundBottom; + } + } + pixels -= rowPixels; + } +foundBottom: + bounds.fLeft = 0; + x = -1; + while (++x < width) { + pixels = pixelBase + rowPixels * bounds.fTop; + for (y = bounds.fTop; y < bounds.fBottom; ++y) { + if (pixels[x]) { + bounds.fLeft = x; + goto foundLeft; + } + pixels += rowPixels; + } + } +foundLeft: + bounds.fRight = width; + x = width; + while (--x > bounds.fLeft) { + pixels = pixelBase + rowPixels * bounds.fTop; + for (y = bounds.fTop; y < bounds.fBottom; ++y) { + if (pixels[x]) { + bounds.fRight = x + 1; + goto foundRight; + } + pixels += rowPixels; + } + } +foundRight: + SkBitmap subset; + if (!bitmap_.extractSubset(&subset, bounds)) { + return; + } + // Neutralize the global matrix by concatenating the inverse. In the + // future, Skia may provide some mechanism to set the device portion of + // the matrix to identity without clobbering any hosting matrix (e.g., the + // picture's matrix). + const SkMatrix& skMatrix = canvas_->getTotalMatrix(); + SkMatrix inverse; + if (!skMatrix.invert(&inverse)) + return; + canvas_->save(); + canvas_->concat(inverse); + canvas_->drawBitmap(subset, bounds.fLeft, bounds.fTop); + canvas_->restore(); + } + CGContextRelease(cgContext_); + cgContext_ = 0; +} + +CGContextRef SkiaBitLocker::cgContext() { + SkDevice* device = canvas_->getTopDevice(); + DCHECK(device); + if (!device) + return 0; + releaseIfNeeded(); // This flushes any prior bitmap use + const SkBitmap& deviceBits = device->accessBitmap(true); + useDeviceBits_ = deviceBits.getPixels(); + if (useDeviceBits_) { + bitmap_ = deviceBits; + bitmap_.lockPixels(); + } else { + bitmap_.setConfig( + SkBitmap::kARGB_8888_Config, deviceBits.width(), deviceBits.height()); + bitmap_.allocPixels(); + bitmap_.eraseColor(0); + } + base::ScopedCFTypeRef<CGColorSpaceRef> colorSpace( + CGColorSpaceCreateDeviceRGB()); + cgContext_ = CGBitmapContextCreate(bitmap_.getPixels(), bitmap_.width(), + bitmap_.height(), 8, bitmap_.rowBytes(), colorSpace, + kCGBitmapByteOrder32Host | kCGImageAlphaPremultipliedFirst); + + // Apply device matrix. + CGAffineTransform contentsTransform = CGAffineTransformMakeScale(1, -1); + contentsTransform = CGAffineTransformTranslate(contentsTransform, 0, + -device->height()); + CGContextConcatCTM(cgContext_, contentsTransform); + + const SkIPoint& pt = device->getOrigin(); + // Skip applying the clip when not writing directly to device. + // They're applied in the offscreen case when the bitmap is drawn. + if (useDeviceBits_) { + // Apply clip in device coordinates. + CGMutablePathRef clipPath = CGPathCreateMutable(); + const SkRegion& clipRgn = canvas_->getTotalClip(); + if (clipRgn.isEmpty()) { + // CoreGraphics does not consider a newly created path to be empty. + // Explicitly set it to empty so the subsequent drawing is clipped out. + // It would be better to make the CGContext hidden if there was a CG + // call that does that. + CGPathAddRect(clipPath, 0, CGRectMake(0, 0, 0, 0)); + } + SkRegion::Iterator iter(clipRgn); + const SkIPoint& pt = device->getOrigin(); + for (; !iter.done(); iter.next()) { + SkIRect skRect = iter.rect(); + skRect.offset(-pt); + CGRect cgRect = SkIRectToCGRect(skRect); + CGPathAddRect(clipPath, 0, cgRect); + } + CGContextAddPath(cgContext_, clipPath); + CGContextClip(cgContext_); + CGPathRelease(clipPath); + } + + // Apply content matrix. + SkMatrix skMatrix = canvas_->getTotalMatrix(); + skMatrix.postTranslate(-SkIntToScalar(pt.fX), -SkIntToScalar(pt.fY)); + CGAffineTransform affine = SkMatrixToCGAffineTransform(skMatrix); + CGContextConcatCTM(cgContext_, affine); + + return cgContext_; +} + +} // namespace gfx diff --git a/chromium/skia/ext/skia_utils_mac_unittest.mm b/chromium/skia/ext/skia_utils_mac_unittest.mm new file mode 100644 index 00000000000..51b957eb384 --- /dev/null +++ b/chromium/skia/ext/skia_utils_mac_unittest.mm @@ -0,0 +1,246 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "skia/ext/skia_utils_mac.mm" + +#include "base/mac/scoped_nsobject.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace { + +class SkiaUtilsMacTest : public testing::Test { + public: + // Creates a red or blue bitmap. + SkBitmap CreateSkBitmap(int width, int height, bool isred, bool tfbit); + + // Creates a red or blue image. + NSImage* CreateNSImage(int width, int height, bool isred); + + // Checks that the given bitmap rep is actually red or blue. + void TestImageRep(NSBitmapImageRep* imageRep, bool isred); + + // Checks that the given bitmap is actually red or blue. + void TestSkBitmap(const SkBitmap& bitmap, bool isred); + + enum BitLockerTest { + TestIdentity = 0, + TestTranslate = 1, + TestClip = 2, + TestXClip = TestTranslate | TestClip, + TestNoBits = 4, + TestTranslateNoBits = TestTranslate | TestNoBits, + TestClipNoBits = TestClip | TestNoBits, + TestXClipNoBits = TestXClip | TestNoBits, + }; + void RunBitLockerTest(BitLockerTest test); + + // If not red, is blue. + // If not tfbit (twenty-four-bit), is 444. + void ShapeHelper(int width, int height, bool isred, bool tfbit); +}; + +SkBitmap SkiaUtilsMacTest::CreateSkBitmap(int width, int height, + bool isred, bool tfbit) { + SkBitmap bitmap; + + if (tfbit) + bitmap.setConfig(SkBitmap::kARGB_8888_Config, width, height); + else + bitmap.setConfig(SkBitmap::kARGB_4444_Config, width, height); + bitmap.allocPixels(); + + if (isred) + bitmap.eraseRGB(0xff, 0, 0); + else + bitmap.eraseRGB(0, 0, 0xff); + + return bitmap; +} + +NSImage* SkiaUtilsMacTest::CreateNSImage(int width, int height, bool isred) { + base::scoped_nsobject<NSImage> image( + [[NSImage alloc] initWithSize:NSMakeSize(width, height)]); + [image lockFocus]; + if (isred) + [[NSColor colorWithDeviceRed:1.0 green:0.0 blue:0.0 alpha:1.0] set]; + else + [[NSColor colorWithDeviceRed:0.0 green:0.0 blue:1.0 alpha:1.0] set]; + NSRectFill(NSMakeRect(0, 0, width, height)); + [image unlockFocus]; + return [image.release() autorelease]; +} + +void SkiaUtilsMacTest::TestImageRep(NSBitmapImageRep* imageRep, bool isred) { + // Get the color of a pixel and make sure it looks fine + int x = [imageRep size].width > 17 ? 17 : 0; + int y = [imageRep size].height > 17 ? 17 : 0; + NSColor* color = [imageRep colorAtX:x y:y]; + CGFloat red = 0, green = 0, blue = 0, alpha = 0; + + // SkBitmapToNSImage returns a bitmap in the calibrated color space (sRGB), + // while NSReadPixel returns a color in the device color space. Convert back + // to the calibrated color space before testing. + color = [color colorUsingColorSpaceName:NSCalibratedRGBColorSpace]; + + [color getRed:&red green:&green blue:&blue alpha:&alpha]; + + // Be tolerant of floating point rounding and lossy color space conversions. + if (isred) { + EXPECT_GT(red, 0.95); + EXPECT_LT(blue, 0.05); + } else { + EXPECT_LT(red, 0.05); + EXPECT_GT(blue, 0.95); + } + EXPECT_LT(green, 0.05); + EXPECT_GT(alpha, 0.95); +} + +void SkiaUtilsMacTest::TestSkBitmap(const SkBitmap& bitmap, bool isred) { + int x = bitmap.width() > 17 ? 17 : 0; + int y = bitmap.height() > 17 ? 17 : 0; + SkColor color = bitmap.getColor(x, y); + + // Be tolerant of lossy color space conversions. + // TODO(sail): Fix color space conversion issues, http://crbug.com/79946 + if (isred) { + EXPECT_GT(SkColorGetR(color), 245u); + EXPECT_LT(SkColorGetB(color), 10u); + } else { + EXPECT_LT(SkColorGetR(color), 10u); + EXPECT_GT(SkColorGetB(color), 245u); + } + EXPECT_LT(SkColorGetG(color), 10u); + EXPECT_GT(SkColorGetA(color), 245u); +} + +// setBitmapDevice has been deprecated/removed. Is this test still useful? +void SkiaUtilsMacTest::RunBitLockerTest(BitLockerTest test) { + const unsigned width = 2; + const unsigned height = 2; + const unsigned storageSize = width * height; + const unsigned original[] = {0xFF333333, 0xFF666666, 0xFF999999, 0xFFCCCCCC}; + EXPECT_EQ(storageSize, sizeof(original) / sizeof(original[0])); + unsigned bits[storageSize]; + memcpy(bits, original, sizeof(original)); + SkBitmap bitmap; + bitmap.setConfig(SkBitmap::kARGB_8888_Config, width, height); + bitmap.setPixels(bits); + + SkCanvas canvas(bitmap); + if (test & TestTranslate) + canvas.translate(width / 2, 0); + if (test & TestClip) { + SkRect clipRect = {0, height / 2, width, height}; + canvas.clipRect(clipRect); + } + { + gfx::SkiaBitLocker bitLocker(&canvas); + CGContextRef cgContext = bitLocker.cgContext(); + CGColorRef testColor = CGColorGetConstantColor(kCGColorWhite); + CGContextSetFillColorWithColor(cgContext, testColor); + CGRect cgRect = {{0, 0}, {width, height}}; + CGContextFillRect(cgContext, cgRect); + if (test & TestNoBits) { + if (test & TestClip) { + SkRect clipRect = {0, height / 2, width, height}; + canvas.clipRect(clipRect); + } + } + } + const unsigned results[][storageSize] = { + {0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF}, // identity + {0xFF333333, 0xFFFFFFFF, 0xFF999999, 0xFFFFFFFF}, // translate + {0xFF333333, 0xFF666666, 0xFFFFFFFF, 0xFFFFFFFF}, // clip + {0xFF333333, 0xFF666666, 0xFF999999, 0xFFFFFFFF} // translate | clip + }; + for (unsigned index = 0; index < storageSize; index++) + EXPECT_EQ(results[test & ~TestNoBits][index], bits[index]); +} + +void SkiaUtilsMacTest::ShapeHelper(int width, int height, + bool isred, bool tfbit) { + SkBitmap thing(CreateSkBitmap(width, height, isred, tfbit)); + + // Confirm size + NSImage* image = gfx::SkBitmapToNSImage(thing); + EXPECT_DOUBLE_EQ([image size].width, (double)width); + EXPECT_DOUBLE_EQ([image size].height, (double)height); + + EXPECT_TRUE([[image representations] count] == 1); + EXPECT_TRUE([[[image representations] lastObject] + isKindOfClass:[NSBitmapImageRep class]]); + TestImageRep([[image representations] lastObject], isred); +} + +TEST_F(SkiaUtilsMacTest, BitmapToNSImage_RedSquare64x64) { + ShapeHelper(64, 64, true, true); +} + +TEST_F(SkiaUtilsMacTest, BitmapToNSImage_BlueRectangle199x19) { + ShapeHelper(199, 19, false, true); +} + +TEST_F(SkiaUtilsMacTest, BitmapToNSImage_BlueRectangle444) { + ShapeHelper(200, 200, false, false); +} + +TEST_F(SkiaUtilsMacTest, BitmapToNSBitmapImageRep_BlueRectangle20x30) { + int width = 20; + int height = 30; + + SkBitmap bitmap(CreateSkBitmap(width, height, false, true)); + NSBitmapImageRep* imageRep = gfx::SkBitmapToNSBitmapImageRep(bitmap); + + EXPECT_DOUBLE_EQ(width, [imageRep size].width); + EXPECT_DOUBLE_EQ(height, [imageRep size].height); + TestImageRep(imageRep, false); +} + +TEST_F(SkiaUtilsMacTest, NSImageRepToSkBitmap) { + int width = 10; + int height = 15; + bool isred = true; + + NSImage* image = CreateNSImage(width, height, isred); + EXPECT_EQ(1u, [[image representations] count]); + NSBitmapImageRep* imageRep = [[image representations] lastObject]; + SkBitmap bitmap(gfx::NSImageRepToSkBitmap(imageRep, [image size], false)); + TestSkBitmap(bitmap, isred); +} + +TEST_F(SkiaUtilsMacTest, BitLocker_Identity) { + RunBitLockerTest(SkiaUtilsMacTest::TestIdentity); +} + +TEST_F(SkiaUtilsMacTest, BitLocker_Translate) { + RunBitLockerTest(SkiaUtilsMacTest::TestTranslate); +} + +TEST_F(SkiaUtilsMacTest, BitLocker_Clip) { + RunBitLockerTest(SkiaUtilsMacTest::TestClip); +} + +TEST_F(SkiaUtilsMacTest, BitLocker_XClip) { + RunBitLockerTest(SkiaUtilsMacTest::TestXClip); +} + +TEST_F(SkiaUtilsMacTest, BitLocker_NoBits) { + RunBitLockerTest(SkiaUtilsMacTest::TestNoBits); +} + +TEST_F(SkiaUtilsMacTest, BitLocker_TranslateNoBits) { + RunBitLockerTest(SkiaUtilsMacTest::TestTranslateNoBits); +} + +TEST_F(SkiaUtilsMacTest, BitLocker_ClipNoBits) { + RunBitLockerTest(SkiaUtilsMacTest::TestClipNoBits); +} + +TEST_F(SkiaUtilsMacTest, BitLocker_XClipNoBits) { + RunBitLockerTest(SkiaUtilsMacTest::TestXClipNoBits); +} + +} // namespace + diff --git a/chromium/skia/ext/skia_utils_win.cc b/chromium/skia/ext/skia_utils_win.cc new file mode 100644 index 00000000000..8b72b986b58 --- /dev/null +++ b/chromium/skia/ext/skia_utils_win.cc @@ -0,0 +1,66 @@ +// 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 "skia/ext/skia_utils_win.h" + +#include <windows.h> + +#include "third_party/skia/include/core/SkRect.h" +#include "third_party/skia/include/effects/SkGradientShader.h" + +namespace { + +template <bool> +struct CompileAssert { +}; + +#undef COMPILE_ASSERT +#define COMPILE_ASSERT(expr, msg) \ + typedef CompileAssert<(bool(expr))> msg[bool(expr) ? 1 : -1] + +COMPILE_ASSERT(SK_OFFSETOF(RECT, left) == SK_OFFSETOF(SkIRect, fLeft), o1); +COMPILE_ASSERT(SK_OFFSETOF(RECT, top) == SK_OFFSETOF(SkIRect, fTop), o2); +COMPILE_ASSERT(SK_OFFSETOF(RECT, right) == SK_OFFSETOF(SkIRect, fRight), o3); +COMPILE_ASSERT(SK_OFFSETOF(RECT, bottom) == SK_OFFSETOF(SkIRect, fBottom), o4); +COMPILE_ASSERT(sizeof(RECT().left) == sizeof(SkIRect().fLeft), o5); +COMPILE_ASSERT(sizeof(RECT().top) == sizeof(SkIRect().fTop), o6); +COMPILE_ASSERT(sizeof(RECT().right) == sizeof(SkIRect().fRight), o7); +COMPILE_ASSERT(sizeof(RECT().bottom) == sizeof(SkIRect().fBottom), o8); +COMPILE_ASSERT(sizeof(RECT) == sizeof(SkIRect), o9); + +} // namespace + +namespace skia { + +POINT SkPointToPOINT(const SkPoint& point) { + POINT win_point = { SkScalarRound(point.fX), SkScalarRound(point.fY) }; + return win_point; +} + +SkRect RECTToSkRect(const RECT& rect) { + SkRect sk_rect = { SkIntToScalar(rect.left), SkIntToScalar(rect.top), + SkIntToScalar(rect.right), SkIntToScalar(rect.bottom) }; + return sk_rect; +} + +SkColor COLORREFToSkColor(COLORREF color) { +#ifndef _MSC_VER + return SkColorSetRGB(GetRValue(color), GetGValue(color), GetBValue(color)); +#else + // ARGB = 0xFF000000 | ((0BGR -> RGB0) >> 8) + return 0xFF000000u | (_byteswap_ulong(color) >> 8); +#endif +} + +COLORREF SkColorToCOLORREF(SkColor color) { +#ifndef _MSC_VER + return RGB(SkColorGetR(color), SkColorGetG(color), SkColorGetB(color)); +#else + // 0BGR = ((ARGB -> BGRA) >> 8) + return (_byteswap_ulong(color) >> 8); +#endif +} + +} // namespace skia + diff --git a/chromium/skia/ext/skia_utils_win.h b/chromium/skia/ext/skia_utils_win.h new file mode 100644 index 00000000000..98e1b999be7 --- /dev/null +++ b/chromium/skia/ext/skia_utils_win.h @@ -0,0 +1,49 @@ +// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SKIA_EXT_SKIA_UTILS_WIN_H_ +#define SKIA_EXT_SKIA_UTILS_WIN_H_ + +#include "third_party/skia/include/core/SkColor.h" + +struct SkIRect; +struct SkPoint; +struct SkRect; +typedef unsigned long DWORD; +typedef DWORD COLORREF; +typedef struct tagPOINT POINT; +typedef struct tagRECT RECT; + +namespace skia { + +// Converts a Skia point to a Windows POINT. +POINT SkPointToPOINT(const SkPoint& point); + +// Converts a Windows RECT to a Skia rect. +SkRect RECTToSkRect(const RECT& rect); + +// Converts a Windows RECT to a Skia rect. +// Both use same in-memory format. Verified by COMPILE_ASSERT() in +// skia_utils.cc. +inline const SkIRect& RECTToSkIRect(const RECT& rect) { + return reinterpret_cast<const SkIRect&>(rect); +} + +// Converts a Skia rect to a Windows RECT. +// Both use same in-memory format. Verified by COMPILE_ASSERT() in +// skia_utils.cc. +inline const RECT& SkIRectToRECT(const SkIRect& rect) { + return reinterpret_cast<const RECT&>(rect); +} + +// Converts COLORREFs (0BGR) to the ARGB layout Skia expects. +SK_API SkColor COLORREFToSkColor(COLORREF color); + +// Converts ARGB to COLORREFs (0BGR). +SK_API COLORREF SkColorToCOLORREF(SkColor color); + +} // namespace skia + +#endif // SKIA_EXT_SKIA_UTILS_WIN_H_ + diff --git a/chromium/skia/ext/vector_canvas.cc b/chromium/skia/ext/vector_canvas.cc new file mode 100644 index 00000000000..9de6b3d0c59 --- /dev/null +++ b/chromium/skia/ext/vector_canvas.cc @@ -0,0 +1,38 @@ +// 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 "skia/ext/vector_canvas.h" +#include "third_party/skia/include/core/SkDevice.h" + +namespace skia { + +VectorCanvas::VectorCanvas(SkDevice* device) + : PlatformCanvas(device) { +} + +VectorCanvas::~VectorCanvas() { +} + +SkBounder* VectorCanvas::setBounder(SkBounder* bounder) { + if (!IsTopDeviceVectorial()) + return PlatformCanvas::setBounder(bounder); + + // This function isn't used in the code. Verify this assumption. + SkASSERT(false); + return NULL; +} + +SkDrawFilter* VectorCanvas::setDrawFilter(SkDrawFilter* filter) { + // This function isn't used in the code. Verify this assumption. + SkASSERT(false); + return NULL; +} + +bool VectorCanvas::IsTopDeviceVectorial() const { + SkDevice* device = GetTopDevice(*this); + return device->getDeviceCapabilities() & SkDevice::kVector_Capability; +} + +} // namespace skia + diff --git a/chromium/skia/ext/vector_canvas.h b/chromium/skia/ext/vector_canvas.h new file mode 100644 index 00000000000..e7a67fce9d2 --- /dev/null +++ b/chromium/skia/ext/vector_canvas.h @@ -0,0 +1,40 @@ +// 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 SKIA_EXT_VECTOR_CANVAS_H_ +#define SKIA_EXT_VECTOR_CANVAS_H_ + +#include "base/compiler_specific.h" +#include "skia/ext/platform_canvas.h" + +class SkDevice; + +namespace skia { + +// This class is a specialization of the regular PlatformCanvas. It is designed +// to work with a VectorDevice to manage platform-specific drawing. It allows +// using both Skia operations and platform-specific operations. It *doesn't* +// support reading back from the bitmap backstore since it is not used. +class SK_API VectorCanvas : public PlatformCanvas { + public: + // Ownership of |device| is transfered to VectorCanvas. + explicit VectorCanvas(SkDevice* device); + virtual ~VectorCanvas(); + + virtual SkBounder* setBounder(SkBounder* bounder) OVERRIDE; + virtual SkDrawFilter* setDrawFilter(SkDrawFilter* filter) OVERRIDE; + + private: + // Returns true if the top device is vector based and not bitmap based. + bool IsTopDeviceVectorial() const; + + // Copy & assign are not supported. + VectorCanvas(const VectorCanvas&); + const VectorCanvas& operator=(const VectorCanvas&); +}; + +} // namespace skia + +#endif // SKIA_EXT_VECTOR_CANVAS_H_ + diff --git a/chromium/skia/ext/vector_canvas_unittest.cc b/chromium/skia/ext/vector_canvas_unittest.cc new file mode 100644 index 00000000000..a087aedf5d2 --- /dev/null +++ b/chromium/skia/ext/vector_canvas_unittest.cc @@ -0,0 +1,970 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "build/build_config.h" + +#if !defined(OS_WIN) +#include <unistd.h> +#endif + +#include "base/command_line.h" +#include "base/file_util.h" +#include "base/path_service.h" +#include "base/strings/string_util.h" +#include "base/strings/stringprintf.h" +#include "base/strings/utf_string_conversions.h" +#include "skia/ext/vector_canvas.h" +#include "skia/ext/vector_platform_device_emf_win.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "third_party/skia/include/effects/SkDashPathEffect.h" +#include "ui/gfx/codec/png_codec.h" +#include "ui/gfx/size.h" + +namespace skia { + +namespace { + +const char kGenerateSwitch[] = "vector-canvas-generate"; + +// Lightweight HDC management. +class Context { + public: + Context() : context_(CreateCompatibleDC(NULL)) { + EXPECT_TRUE(context_); + } + ~Context() { + DeleteDC(context_); + } + + HDC context() const { return context_; } + + private: + HDC context_; + + DISALLOW_COPY_AND_ASSIGN(Context); +}; + +// Lightweight HBITMAP management. +class Bitmap { + public: + Bitmap(const Context& context, int x, int y) { + BITMAPINFOHEADER hdr; + hdr.biSize = sizeof(BITMAPINFOHEADER); + hdr.biWidth = x; + hdr.biHeight = -y; // Minus means top-down bitmap. + hdr.biPlanes = 1; + hdr.biBitCount = 32; + hdr.biCompression = BI_RGB; // No compression. + hdr.biSizeImage = 0; + hdr.biXPelsPerMeter = 1; + hdr.biYPelsPerMeter = 1; + hdr.biClrUsed = 0; + hdr.biClrImportant = 0; + bitmap_ = CreateDIBSection(context.context(), + reinterpret_cast<BITMAPINFO*>(&hdr), 0, + &data_, NULL, 0); + EXPECT_TRUE(bitmap_); + EXPECT_TRUE(SelectObject(context.context(), bitmap_)); + } + ~Bitmap() { + EXPECT_TRUE(DeleteObject(bitmap_)); + } + + private: + HBITMAP bitmap_; + + void* data_; + + DISALLOW_COPY_AND_ASSIGN(Bitmap); +}; + +// Lightweight raw-bitmap management. The image, once initialized, is immuable. +// It is mainly used for comparison. +class Image { + public: + // Creates the image from the given filename on disk. + explicit Image(const base::FilePath& filename) : ignore_alpha_(true) { + std::string compressed; + file_util::ReadFileToString(filename, &compressed); + EXPECT_TRUE(compressed.size()); + + SkBitmap bitmap; + EXPECT_TRUE(gfx::PNGCodec::Decode( + reinterpret_cast<const unsigned char*>(compressed.data()), + compressed.size(), &bitmap)); + SetSkBitmap(bitmap); + } + + // Loads the image from a canvas. + Image(skia::PlatformCanvas& canvas) : ignore_alpha_(true) { + // Use a different way to access the bitmap. The normal way would be to + // query the SkBitmap. + skia::ScopedPlatformPaint scoped_platform_paint(&canvas); + HDC context = scoped_platform_paint.GetPlatformSurface(); + HGDIOBJ bitmap = GetCurrentObject(context, OBJ_BITMAP); + EXPECT_TRUE(bitmap != NULL); + // Initialize the clip region to the entire bitmap. + BITMAP bitmap_data; + EXPECT_EQ(GetObject(bitmap, sizeof(BITMAP), &bitmap_data), sizeof(BITMAP)); + width_ = bitmap_data.bmWidth; + height_ = bitmap_data.bmHeight; + row_length_ = bitmap_data.bmWidthBytes; + size_t size = row_length_ * height_; + data_.resize(size); + memcpy(&*data_.begin(), bitmap_data.bmBits, size); + } + + // Loads the image from a canvas. + Image(const SkBitmap& bitmap) : ignore_alpha_(true) { + SetSkBitmap(bitmap); + } + + int width() const { return width_; } + int height() const { return height_; } + int row_length() const { return row_length_; } + + // Save the image to a png file. Used to create the initial test files. + void SaveToFile(const base::FilePath& filename) { + std::vector<unsigned char> compressed; + ASSERT_TRUE(gfx::PNGCodec::Encode(&*data_.begin(), + gfx::PNGCodec::FORMAT_BGRA, + gfx::Size(width_, height_), + row_length_, + true, + std::vector<gfx::PNGCodec::Comment>(), + &compressed)); + ASSERT_TRUE(compressed.size()); + FILE* f = file_util::OpenFile(filename, "wb"); + ASSERT_TRUE(f); + ASSERT_EQ(fwrite(&*compressed.begin(), 1, compressed.size(), f), + compressed.size()); + file_util::CloseFile(f); + } + + // Returns the percentage of the image that is different from the other, + // between 0 and 100. + double PercentageDifferent(const Image& rhs) const { + if (width_ != rhs.width_ || + height_ != rhs.height_ || + row_length_ != rhs.row_length_ || + width_ == 0 || + height_ == 0) { + return 100.; // When of different size or empty, they are 100% different. + } + // Compute pixels different in the overlap + int pixels_different = 0; + for (int y = 0; y < height_; ++y) { + for (int x = 0; x < width_; ++x) { + uint32_t lhs_pixel = pixel_at(x, y); + uint32_t rhs_pixel = rhs.pixel_at(x, y); + if (lhs_pixel != rhs_pixel) + ++pixels_different; + } + } + + // Like the WebKit ImageDiff tool, we define percentage different in terms + // of the size of the 'actual' bitmap. + double total_pixels = static_cast<double>(width_) * + static_cast<double>(height_); + return static_cast<double>(pixels_different) / total_pixels * 100.; + } + + // Returns the 0x0RGB or 0xARGB value of the pixel at the given location, + // depending on ignore_alpha_. + uint32 pixel_at(int x, int y) const { + EXPECT_TRUE(x >= 0 && x < width_); + EXPECT_TRUE(y >= 0 && y < height_); + const uint32* data = reinterpret_cast<const uint32*>(&*data_.begin()); + const uint32* data_row = data + y * row_length_ / sizeof(uint32); + if (ignore_alpha_) + return data_row[x] & 0xFFFFFF; // Strip out A. + else + return data_row[x]; + } + + protected: + void SetSkBitmap(const SkBitmap& bitmap) { + SkAutoLockPixels lock(bitmap); + width_ = bitmap.width(); + height_ = bitmap.height(); + row_length_ = static_cast<int>(bitmap.rowBytes()); + size_t size = row_length_ * height_; + data_.resize(size); + memcpy(&*data_.begin(), bitmap.getAddr(0, 0), size); + } + + private: + // Pixel dimensions of the image. + int width_; + int height_; + + // Length of a line in bytes. + int row_length_; + + // Actual bitmap data in arrays of RGBAs (so when loaded as uint32, it's + // 0xABGR). + std::vector<unsigned char> data_; + + // Flag to signal if the comparison functions should ignore the alpha channel. + const bool ignore_alpha_; + + DISALLOW_COPY_AND_ASSIGN(Image); +}; + +// Base for tests. Capability to process an image. +class ImageTest : public testing::Test { + public: + // In what state is the test running. + enum ProcessAction { + GENERATE, + COMPARE, + NOOP, + }; + + ImageTest(ProcessAction default_action) + : action_(default_action) { + } + + protected: + virtual void SetUp() { + const testing::TestInfo& test_info = + *testing::UnitTest::GetInstance()->current_test_info(); + PathService::Get(base::DIR_SOURCE_ROOT, &test_dir_); + test_dir_ = test_dir_.AppendASCII("skia"). + AppendASCII("ext"). + AppendASCII("data"). + AppendASCII(test_info.test_case_name()). + AppendASCII(test_info.name()); + + // Hack for a quick lowercase. We assume all the tests names are ASCII. + base::FilePath::StringType tmp(test_dir_.value()); + for (size_t i = 0; i < tmp.size(); ++i) + tmp[i] = base::ToLowerASCII(tmp[i]); + test_dir_ = base::FilePath(tmp); + + if (action_ == GENERATE) { + // Make sure the directory exist. + file_util::CreateDirectory(test_dir_); + } + } + + // Returns the fully qualified path of a data file. + base::FilePath test_file(const base::FilePath::StringType& filename) const { + // Hack for a quick lowercase. We assume all the test data file names are + // ASCII. +#if defined(OS_WIN) + std::string tmp = WideToASCII(filename); +#else + std::string tmp(filename); +#endif + for (size_t i = 0; i < tmp.size(); ++i) + tmp[i] = base::ToLowerASCII(tmp[i]); + + return test_dir_.AppendASCII(tmp); + } + + // Compares or saves the bitmap currently loaded in the context, depending on + // kGenerating value. Returns 0 on success or any positive value between ]0, + // 100] on failure. The return value is the percentage of difference between + // the image in the file and the image in the canvas. + double ProcessCanvas(skia::PlatformCanvas& canvas, + base::FilePath::StringType filename) const { + filename = filename + FILE_PATH_LITERAL(".png"); + switch (action_) { + case GENERATE: + SaveImage(canvas, filename); + return 0.; + case COMPARE: + return CompareImage(canvas, filename); + case NOOP: + return 0; + default: + // Invalid state, returns that the image is 100 different. + return 100.; + } + } + + // Compares the bitmap currently loaded in the context with the file. Returns + // the percentage of pixel difference between both images, between 0 and 100. + double CompareImage(skia::PlatformCanvas& canvas, + const base::FilePath::StringType& filename) const { + Image image1(canvas); + Image image2(test_file(filename)); + double diff = image1.PercentageDifferent(image2); + return diff; + } + + // Saves the bitmap currently loaded in the context into the file. + void SaveImage(skia::PlatformCanvas& canvas, + const base::FilePath::StringType& filename) const { + Image(canvas).SaveToFile(test_file(filename)); + } + + ProcessAction action_; + + // Path to directory used to contain the test data. + base::FilePath test_dir_; + + DISALLOW_COPY_AND_ASSIGN(ImageTest); +}; + +// Premultiply the Alpha channel on the R, B and G channels. +void Premultiply(SkBitmap bitmap) { + SkAutoLockPixels lock(bitmap); + for (int x = 0; x < bitmap.width(); ++x) { + for (int y = 0; y < bitmap.height(); ++y) { + uint32_t* pixel_addr = bitmap.getAddr32(x, y); + uint32_t color = *pixel_addr; + BYTE alpha = SkColorGetA(color); + if (!alpha) { + *pixel_addr = 0; + } else { + BYTE alpha_offset = alpha / 2; + *pixel_addr = SkColorSetARGB( + SkColorGetA(color), + (SkColorGetR(color) * 255 + alpha_offset) / alpha, + (SkColorGetG(color) * 255 + alpha_offset) / alpha, + (SkColorGetB(color) * 255 + alpha_offset) / alpha); + } + } + } +} + +void LoadPngFileToSkBitmap(const base::FilePath& filename, + SkBitmap* bitmap, + bool is_opaque) { + std::string compressed; + file_util::ReadFileToString(base::MakeAbsoluteFilePath(filename), + &compressed); + ASSERT_TRUE(compressed.size()); + + ASSERT_TRUE(gfx::PNGCodec::Decode( + reinterpret_cast<const unsigned char*>(compressed.data()), + compressed.size(), bitmap)); + + EXPECT_EQ(is_opaque, bitmap->isOpaque()); + Premultiply(*bitmap); +} + +} // namespace + +// Streams an image. +inline std::ostream& operator<<(std::ostream& out, const Image& image) { + return out << "Image(" << image.width() << ", " + << image.height() << ", " << image.row_length() << ")"; +} + +// Runs simultaneously the same drawing commands on VectorCanvas and +// PlatformCanvas and compare the results. +class VectorCanvasTest : public ImageTest { + public: + typedef ImageTest parent; + + VectorCanvasTest() : parent(CurrentMode()), compare_canvas_(true) { + } + + protected: + virtual void SetUp() { + parent::SetUp(); + Init(100); + number_ = 0; + } + + virtual void TearDown() { + delete pcanvas_; + pcanvas_ = NULL; + + delete vcanvas_; + vcanvas_ = NULL; + + delete bitmap_; + bitmap_ = NULL; + + delete context_; + context_ = NULL; + + parent::TearDown(); + } + + void Init(int size) { + size_ = size; + context_ = new Context(); + bitmap_ = new Bitmap(*context_, size_, size_); + vcanvas_ = new VectorCanvas( + VectorPlatformDeviceEmf::CreateDevice( + size_, size_, true, context_->context())); + pcanvas_ = CreatePlatformCanvas(size_, size_, false); + + // Clear white. + vcanvas_->drawARGB(255, 255, 255, 255, SkXfermode::kSrc_Mode); + pcanvas_->drawARGB(255, 255, 255, 255, SkXfermode::kSrc_Mode); + } + + // Compares both canvas and returns the pixel difference in percentage between + // both images. 0 on success and ]0, 100] on failure. + double ProcessImage(const base::FilePath::StringType& filename) { + std::wstring number(base::StringPrintf(L"%02d_", number_++)); + double diff1 = parent::ProcessCanvas(*vcanvas_, number + L"vc_" + filename); + double diff2 = parent::ProcessCanvas(*pcanvas_, number + L"pc_" + filename); + if (!compare_canvas_) + return std::max(diff1, diff2); + + Image image1(*vcanvas_); + Image image2(*pcanvas_); + double diff = image1.PercentageDifferent(image2); + return std::max(std::max(diff1, diff2), diff); + } + + // Returns COMPARE, which is the default. If kGenerateSwitch command + // line argument is used to start this process, GENERATE is returned instead. + static ProcessAction CurrentMode() { + return CommandLine::ForCurrentProcess()->HasSwitch(kGenerateSwitch) ? + GENERATE : COMPARE; + } + + // Length in x and y of the square canvas. + int size_; + + // Current image number in the current test. Used to number of test files. + int number_; + + // A temporary HDC to draw into. + Context* context_; + + // Bitmap created inside context_. + Bitmap* bitmap_; + + // Vector based canvas. + VectorCanvas* vcanvas_; + + // Pixel based canvas. + PlatformCanvas* pcanvas_; + + // When true (default), vcanvas_ and pcanvas_ contents are compared and + // verified to be identical. + bool compare_canvas_; +}; + + +//////////////////////////////////////////////////////////////////////////////// +// Actual tests + +#if !defined(USE_AURA) // http://crbug.com/154358 + +TEST_F(VectorCanvasTest, BasicDrawing) { + EXPECT_EQ(Image(*vcanvas_).PercentageDifferent(Image(*pcanvas_)), 0.) + << L"clean"; + EXPECT_EQ(0., ProcessImage(FILE_PATH_LITERAL("clean"))); + + // Clear white. + { + vcanvas_->drawARGB(255, 255, 255, 255, SkXfermode::kSrc_Mode); + pcanvas_->drawARGB(255, 255, 255, 255, SkXfermode::kSrc_Mode); + } + EXPECT_EQ(0., ProcessImage(FILE_PATH_LITERAL("drawARGB"))); + + // Diagonal line top-left to bottom-right. + { + SkPaint paint; + // Default color is black. + vcanvas_->drawLine(10, 10, 90, 90, paint); + pcanvas_->drawLine(10, 10, 90, 90, paint); + } + EXPECT_EQ(0., ProcessImage(FILE_PATH_LITERAL("drawLine_black"))); + + // Rect. + { + SkPaint paint; + paint.setColor(SK_ColorGREEN); + vcanvas_->drawRectCoords(25, 25, 75, 75, paint); + pcanvas_->drawRectCoords(25, 25, 75, 75, paint); + } + EXPECT_EQ(0., ProcessImage(FILE_PATH_LITERAL("drawRect_green"))); + + // A single-point rect doesn't leave any mark. + { + SkPaint paint; + paint.setColor(SK_ColorBLUE); + vcanvas_->drawRectCoords(5, 5, 5, 5, paint); + pcanvas_->drawRectCoords(5, 5, 5, 5, paint); + } + EXPECT_EQ(0., ProcessImage(FILE_PATH_LITERAL("drawRect_noop"))); + + // Rect. + { + SkPaint paint; + paint.setColor(SK_ColorBLUE); + vcanvas_->drawRectCoords(75, 50, 80, 55, paint); + pcanvas_->drawRectCoords(75, 50, 80, 55, paint); + } + EXPECT_EQ(0., ProcessImage(FILE_PATH_LITERAL("drawRect_noop"))); + + // Empty again + { + vcanvas_->drawPaint(SkPaint()); + pcanvas_->drawPaint(SkPaint()); + } + EXPECT_EQ(0., ProcessImage(FILE_PATH_LITERAL("drawPaint_black"))); + + // Horizontal line left to right. + { + SkPaint paint; + paint.setColor(SK_ColorRED); + vcanvas_->drawLine(10, 20, 90, 20, paint); + pcanvas_->drawLine(10, 20, 90, 20, paint); + } + EXPECT_EQ(0., ProcessImage(FILE_PATH_LITERAL("drawLine_left_to_right"))); + + // Vertical line downward. + { + SkPaint paint; + paint.setColor(SK_ColorRED); + vcanvas_->drawLine(30, 10, 30, 90, paint); + pcanvas_->drawLine(30, 10, 30, 90, paint); + } + EXPECT_EQ(0., ProcessImage(FILE_PATH_LITERAL("drawLine_red"))); +} + +TEST_F(VectorCanvasTest, Circles) { + // There is NO WAY to make them agree. At least verify that the output doesn't + // change across versions. This test is disabled. See bug 1060231. + compare_canvas_ = false; + + // Stroked Circle. + { + SkPaint paint; + SkPath path; + path.addCircle(50, 75, 10); + paint.setStyle(SkPaint::kStroke_Style); + paint.setColor(SK_ColorMAGENTA); + vcanvas_->drawPath(path, paint); + pcanvas_->drawPath(path, paint); + } + EXPECT_EQ(0., ProcessImage(FILE_PATH_LITERAL("circle_stroke"))); + + // Filled Circle. + { + SkPaint paint; + SkPath path; + path.addCircle(50, 25, 10); + paint.setStyle(SkPaint::kFill_Style); + vcanvas_->drawPath(path, paint); + pcanvas_->drawPath(path, paint); + } + EXPECT_EQ(0., ProcessImage(FILE_PATH_LITERAL("circle_fill"))); + + // Stroked Circle over. + { + SkPaint paint; + SkPath path; + path.addCircle(50, 25, 10); + paint.setStyle(SkPaint::kStroke_Style); + paint.setColor(SK_ColorBLUE); + vcanvas_->drawPath(path, paint); + pcanvas_->drawPath(path, paint); + } + EXPECT_EQ(0., ProcessImage(FILE_PATH_LITERAL("circle_over_strike"))); + + // Stroke and Fill Circle. + { + SkPaint paint; + SkPath path; + path.addCircle(12, 50, 10); + paint.setStyle(SkPaint::kStrokeAndFill_Style); + paint.setColor(SK_ColorRED); + vcanvas_->drawPath(path, paint); + pcanvas_->drawPath(path, paint); + } + EXPECT_EQ(0., ProcessImage(FILE_PATH_LITERAL("circle_stroke_and_fill"))); + + // Line + Quad + Cubic. + { + SkPaint paint; + SkPath path; + paint.setStyle(SkPaint::kStroke_Style); + paint.setColor(SK_ColorGREEN); + path.moveTo(1, 1); + path.lineTo(60, 40); + path.lineTo(80, 80); + path.quadTo(20, 50, 10, 90); + path.quadTo(50, 20, 90, 10); + path.cubicTo(20, 40, 50, 50, 10, 10); + path.cubicTo(30, 20, 50, 50, 90, 10); + path.addRect(90, 90, 95, 96); + vcanvas_->drawPath(path, paint); + pcanvas_->drawPath(path, paint); + } + EXPECT_EQ(0., ProcessImage(FILE_PATH_LITERAL("mixed_stroke"))); +} + +TEST_F(VectorCanvasTest, LineOrientation) { + // There is NO WAY to make them agree. At least verify that the output doesn't + // change across versions. This test is disabled. See bug 1060231. + compare_canvas_ = false; + + // Horizontal lines. + { + SkPaint paint; + paint.setColor(SK_ColorRED); + // Left to right. + vcanvas_->drawLine(10, 20, 90, 20, paint); + pcanvas_->drawLine(10, 20, 90, 20, paint); + // Right to left. + vcanvas_->drawLine(90, 30, 10, 30, paint); + pcanvas_->drawLine(90, 30, 10, 30, paint); + } + EXPECT_EQ(0., ProcessImage(FILE_PATH_LITERAL("horizontal"))); + + // Vertical lines. + { + SkPaint paint; + paint.setColor(SK_ColorRED); + // Top down. + vcanvas_->drawLine(20, 10, 20, 90, paint); + pcanvas_->drawLine(20, 10, 20, 90, paint); + // Bottom up. + vcanvas_->drawLine(30, 90, 30, 10, paint); + pcanvas_->drawLine(30, 90, 30, 10, paint); + } + EXPECT_EQ(0., ProcessImage(FILE_PATH_LITERAL("vertical"))); + + // Try again with a 180 degres rotation. + vcanvas_->rotate(180); + pcanvas_->rotate(180); + + // Horizontal lines (rotated). + { + SkPaint paint; + paint.setColor(SK_ColorRED); + vcanvas_->drawLine(-10, -25, -90, -25, paint); + pcanvas_->drawLine(-10, -25, -90, -25, paint); + vcanvas_->drawLine(-90, -35, -10, -35, paint); + pcanvas_->drawLine(-90, -35, -10, -35, paint); + } + EXPECT_EQ(0., ProcessImage(FILE_PATH_LITERAL("horizontal_180"))); + + // Vertical lines (rotated). + { + SkPaint paint; + paint.setColor(SK_ColorRED); + vcanvas_->drawLine(-25, -10, -25, -90, paint); + pcanvas_->drawLine(-25, -10, -25, -90, paint); + vcanvas_->drawLine(-35, -90, -35, -10, paint); + pcanvas_->drawLine(-35, -90, -35, -10, paint); + } + EXPECT_EQ(0., ProcessImage(FILE_PATH_LITERAL("vertical_180"))); +} + +TEST_F(VectorCanvasTest, PathOrientation) { + // There is NO WAY to make them agree. At least verify that the output doesn't + // change across versions. This test is disabled. See bug 1060231. + compare_canvas_ = false; + + // Horizontal lines. + { + SkPaint paint; + paint.setStyle(SkPaint::kStroke_Style); + paint.setColor(SK_ColorRED); + SkPath path; + SkPoint start; + start.set(10, 20); + SkPoint end; + end.set(90, 20); + path.moveTo(start); + path.lineTo(end); + vcanvas_->drawPath(path, paint); + pcanvas_->drawPath(path, paint); + } + EXPECT_EQ(0., ProcessImage(FILE_PATH_LITERAL("drawPath_ltr"))); + + // Horizontal lines. + { + SkPaint paint; + paint.setStyle(SkPaint::kStroke_Style); + paint.setColor(SK_ColorRED); + SkPath path; + SkPoint start; + start.set(90, 30); + SkPoint end; + end.set(10, 30); + path.moveTo(start); + path.lineTo(end); + vcanvas_->drawPath(path, paint); + pcanvas_->drawPath(path, paint); + } + EXPECT_EQ(0., ProcessImage(FILE_PATH_LITERAL("drawPath_rtl"))); +} + +TEST_F(VectorCanvasTest, DiagonalLines) { + SkPaint paint; + paint.setColor(SK_ColorRED); + + vcanvas_->drawLine(10, 10, 90, 90, paint); + pcanvas_->drawLine(10, 10, 90, 90, paint); + EXPECT_EQ(0., ProcessImage(FILE_PATH_LITERAL("nw-se"))); + + // Starting here, there is NO WAY to make them agree. At least verify that the + // output doesn't change across versions. This test is disabled. See bug + // 1060231. + compare_canvas_ = false; + + vcanvas_->drawLine(10, 95, 90, 15, paint); + pcanvas_->drawLine(10, 95, 90, 15, paint); + EXPECT_EQ(0., ProcessImage(FILE_PATH_LITERAL("sw-ne"))); + + vcanvas_->drawLine(90, 10, 10, 90, paint); + pcanvas_->drawLine(90, 10, 10, 90, paint); + EXPECT_EQ(0., ProcessImage(FILE_PATH_LITERAL("ne-sw"))); + + vcanvas_->drawLine(95, 90, 15, 10, paint); + pcanvas_->drawLine(95, 90, 15, 10, paint); + EXPECT_EQ(0., ProcessImage(FILE_PATH_LITERAL("se-nw"))); +} + +#if defined(OS_WIN) +#define MAYBE_PathEffects DISABLED_PathEffects +#else +#define MAYBE_PathEffects PathEffects +#endif +TEST_F(VectorCanvasTest, MAYBE_PathEffects) { + { + SkPaint paint; + SkScalar intervals[] = { 1, 1 }; + skia::RefPtr<SkPathEffect> effect = skia::AdoptRef( + new SkDashPathEffect(intervals, arraysize(intervals), 0)); + paint.setPathEffect(effect.get()); + paint.setColor(SK_ColorMAGENTA); + paint.setStyle(SkPaint::kStroke_Style); + + vcanvas_->drawLine(10, 10, 90, 10, paint); + pcanvas_->drawLine(10, 10, 90, 10, paint); + } + EXPECT_EQ(0., ProcessImage(FILE_PATH_LITERAL("dash_line"))); + + + // Starting here, there is NO WAY to make them agree. At least verify that the + // output doesn't change across versions. This test is disabled. See bug + // 1060231. + compare_canvas_ = false; + + { + SkPaint paint; + SkScalar intervals[] = { 3, 5 }; + skia::RefPtr<SkPathEffect> effect = skia::AdoptRef( + new SkDashPathEffect(intervals, arraysize(intervals), 0)); + paint.setPathEffect(effect.get()); + paint.setColor(SK_ColorMAGENTA); + paint.setStyle(SkPaint::kStroke_Style); + + SkPath path; + path.moveTo(10, 15); + path.lineTo(90, 15); + path.lineTo(90, 90); + vcanvas_->drawPath(path, paint); + pcanvas_->drawPath(path, paint); + } + EXPECT_EQ(0., ProcessImage(FILE_PATH_LITERAL("dash_path"))); + + { + SkPaint paint; + SkScalar intervals[] = { 2, 1 }; + skia::RefPtr<SkPathEffect> effect = skia::AdoptRef( + new SkDashPathEffect(intervals, arraysize(intervals), 0)); + paint.setPathEffect(effect.get()); + paint.setColor(SK_ColorMAGENTA); + paint.setStyle(SkPaint::kStroke_Style); + + vcanvas_->drawRectCoords(20, 20, 30, 30, paint); + pcanvas_->drawRectCoords(20, 20, 30, 30, paint); + } + EXPECT_EQ(0., ProcessImage(FILE_PATH_LITERAL("dash_rect"))); + + // This thing looks like it has been drawn by a 3 years old kid. I haven't + // filed a bug on this since I guess nobody is expecting this to look nice. + { + SkPaint paint; + SkScalar intervals[] = { 1, 1 }; + skia::RefPtr<SkPathEffect> effect = skia::AdoptRef( + new SkDashPathEffect(intervals, arraysize(intervals), 0)); + paint.setPathEffect(effect.get()); + paint.setColor(SK_ColorMAGENTA); + paint.setStyle(SkPaint::kStroke_Style); + + SkPath path; + path.addCircle(50, 75, 10); + vcanvas_->drawPath(path, paint); + pcanvas_->drawPath(path, paint); + EXPECT_EQ(0., ProcessImage(FILE_PATH_LITERAL("circle"))); + } +} + +TEST_F(VectorCanvasTest, Bitmaps) { + { + SkBitmap bitmap; + LoadPngFileToSkBitmap(test_file(L"bitmap_opaque.png"), &bitmap, true); + vcanvas_->drawBitmap(bitmap, 13, 3, NULL); + pcanvas_->drawBitmap(bitmap, 13, 3, NULL); + EXPECT_EQ(0., ProcessImage(FILE_PATH_LITERAL("opaque"))); + } + + { + SkBitmap bitmap; + LoadPngFileToSkBitmap(test_file(L"bitmap_alpha.png"), &bitmap, false); + vcanvas_->drawBitmap(bitmap, 5, 15, NULL); + pcanvas_->drawBitmap(bitmap, 5, 15, NULL); + EXPECT_EQ(0., ProcessImage(FILE_PATH_LITERAL("alpha"))); + } +} + +TEST_F(VectorCanvasTest, ClippingRect) { + SkBitmap bitmap; + LoadPngFileToSkBitmap(test_file(L"..\\bitmaps\\bitmap_opaque.png"), &bitmap, + true); + SkRect rect; + rect.fLeft = 2; + rect.fTop = 2; + rect.fRight = 30.5f; + rect.fBottom = 30.5f; + vcanvas_->clipRect(rect); + pcanvas_->clipRect(rect); + + vcanvas_->drawBitmap(bitmap, 13, 3, NULL); + pcanvas_->drawBitmap(bitmap, 13, 3, NULL); + EXPECT_EQ(0., ProcessImage(FILE_PATH_LITERAL("rect"))); +} + +TEST_F(VectorCanvasTest, ClippingPath) { + SkBitmap bitmap; + LoadPngFileToSkBitmap(test_file(L"..\\bitmaps\\bitmap_opaque.png"), &bitmap, + true); + SkPath path; + path.addCircle(20, 20, 10); + vcanvas_->clipPath(path); + pcanvas_->clipPath(path); + + vcanvas_->drawBitmap(bitmap, 14, 3, NULL); + pcanvas_->drawBitmap(bitmap, 14, 3, NULL); + EXPECT_EQ(0., ProcessImage(FILE_PATH_LITERAL("path"))); +} + +TEST_F(VectorCanvasTest, ClippingCombined) { + SkBitmap bitmap; + LoadPngFileToSkBitmap(test_file(L"..\\bitmaps\\bitmap_opaque.png"), &bitmap, + true); + + SkRect rect; + rect.fLeft = 2; + rect.fTop = 2; + rect.fRight = 30.5f; + rect.fBottom = 30.5f; + vcanvas_->clipRect(rect); + pcanvas_->clipRect(rect); + SkPath path; + path.addCircle(20, 20, 10); + vcanvas_->clipPath(path, SkRegion::kUnion_Op); + pcanvas_->clipPath(path, SkRegion::kUnion_Op); + + vcanvas_->drawBitmap(bitmap, 15, 3, NULL); + pcanvas_->drawBitmap(bitmap, 15, 3, NULL); + EXPECT_EQ(0., ProcessImage(FILE_PATH_LITERAL("combined"))); +} + +TEST_F(VectorCanvasTest, ClippingIntersect) { + SkBitmap bitmap; + LoadPngFileToSkBitmap(test_file(L"..\\bitmaps\\bitmap_opaque.png"), &bitmap, + true); + + SkRect rect; + rect.fLeft = 2; + rect.fTop = 2; + rect.fRight = 30.5f; + rect.fBottom = 30.5f; + vcanvas_->clipRect(rect); + pcanvas_->clipRect(rect); + SkPath path; + path.addCircle(23, 23, 15); + vcanvas_->clipPath(path); + pcanvas_->clipPath(path); + + vcanvas_->drawBitmap(bitmap, 15, 3, NULL); + pcanvas_->drawBitmap(bitmap, 15, 3, NULL); + EXPECT_EQ(0., ProcessImage(FILE_PATH_LITERAL("intersect"))); +} + +TEST_F(VectorCanvasTest, ClippingClean) { + SkBitmap bitmap; + LoadPngFileToSkBitmap(test_file(L"..\\bitmaps\\bitmap_opaque.png"), &bitmap, + true); + { + SkAutoCanvasRestore acrv(vcanvas_, true); + SkAutoCanvasRestore acrp(pcanvas_, true); + SkRect rect; + rect.fLeft = 2; + rect.fTop = 2; + rect.fRight = 30.5f; + rect.fBottom = 30.5f; + vcanvas_->clipRect(rect); + pcanvas_->clipRect(rect); + + vcanvas_->drawBitmap(bitmap, 15, 3, NULL); + pcanvas_->drawBitmap(bitmap, 15, 3, NULL); + EXPECT_EQ(0., ProcessImage(FILE_PATH_LITERAL("clipped"))); + } + { + // Verify that the clipping region has been fixed back. + vcanvas_->drawBitmap(bitmap, 55, 3, NULL); + pcanvas_->drawBitmap(bitmap, 55, 3, NULL); + EXPECT_EQ(0., ProcessImage(FILE_PATH_LITERAL("unclipped"))); + } +} + +// See http://crbug.com/26938 +TEST_F(VectorCanvasTest, DISABLED_Matrix) { + SkBitmap bitmap; + LoadPngFileToSkBitmap(test_file(L"..\\bitmaps\\bitmap_opaque.png"), &bitmap, + true); + { + vcanvas_->translate(15, 3); + pcanvas_->translate(15, 3); + vcanvas_->drawBitmap(bitmap, 0, 0, NULL); + pcanvas_->drawBitmap(bitmap, 0, 0, NULL); + EXPECT_EQ(0., ProcessImage(FILE_PATH_LITERAL("translate1"))); + } + { + vcanvas_->translate(-30, -23); + pcanvas_->translate(-30, -23); + vcanvas_->drawBitmap(bitmap, 0, 0, NULL); + pcanvas_->drawBitmap(bitmap, 0, 0, NULL); + EXPECT_EQ(0., ProcessImage(FILE_PATH_LITERAL("translate2"))); + } + vcanvas_->resetMatrix(); + pcanvas_->resetMatrix(); + + // For scaling and rotation, they use a different algorithm (nearest + // neighborhood vs smoothing). At least verify that the output doesn't change + // across versions. + compare_canvas_ = false; + + { + vcanvas_->scale(SkDoubleToScalar(1.9), SkDoubleToScalar(1.5)); + pcanvas_->scale(SkDoubleToScalar(1.9), SkDoubleToScalar(1.5)); + vcanvas_->drawBitmap(bitmap, 1, 1, NULL); + pcanvas_->drawBitmap(bitmap, 1, 1, NULL); + EXPECT_EQ(0., ProcessImage(FILE_PATH_LITERAL("scale"))); + } + vcanvas_->resetMatrix(); + pcanvas_->resetMatrix(); + + { + vcanvas_->rotate(67); + pcanvas_->rotate(67); + vcanvas_->drawBitmap(bitmap, 20, -50, NULL); + pcanvas_->drawBitmap(bitmap, 20, -50, NULL); + EXPECT_EQ(0., ProcessImage(FILE_PATH_LITERAL("rotate"))); + } +} + +#endif // !defined(USE_AURA) + +} // namespace skia diff --git a/chromium/skia/ext/vector_platform_device_emf_win.cc b/chromium/skia/ext/vector_platform_device_emf_win.cc new file mode 100644 index 00000000000..958ff8f4094 --- /dev/null +++ b/chromium/skia/ext/vector_platform_device_emf_win.cc @@ -0,0 +1,986 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "skia/ext/vector_platform_device_emf_win.h" + +#include <windows.h> + +#include "base/logging.h" +#include "base/strings/string16.h" +#include "skia/ext/bitmap_platform_device.h" +#include "skia/ext/skia_utils_win.h" +#include "third_party/skia/include/core/SkFontHost.h" +#include "third_party/skia/include/core/SkPathEffect.h" +#include "third_party/skia/include/core/SkTemplates.h" +#include "third_party/skia/include/core/SkUtils.h" +#include "third_party/skia/include/ports/SkTypeface_win.h" + +namespace skia { + +#define CHECK_FOR_NODRAW_ANNOTATION(paint) \ + do { if (paint.isNoDrawAnnotation()) { return; } } while (0) + +// static +SkDevice* VectorPlatformDeviceEmf::CreateDevice( + int width, int height, bool is_opaque, HANDLE shared_section) { + if (!is_opaque) { + // TODO(maruel): http://crbug.com/18382 When restoring a semi-transparent + // layer, i.e. merging it, we need to rasterize it because GDI doesn't + // support transparency except for AlphaBlend(). Right now, a + // BitmapPlatformDevice is created when VectorCanvas think a saveLayers() + // call is being done. The way to save a layer would be to create an + // EMF-based VectorDevice and have this device registers the drawing. When + // playing back the device into a bitmap, do it at the printer's dpi instead + // of the layout's dpi (which is much lower). + return BitmapPlatformDevice::Create(width, height, is_opaque, + shared_section); + } + + // TODO(maruel): http://crbug.com/18383 Look if it would be worth to + // increase the resolution by ~10x (any worthy factor) to increase the + // rendering precision (think about printing) while using a relatively + // low dpi. This happens because we receive float as input but the GDI + // functions works with integers. The idea is to premultiply the matrix + // with this factor and multiply each SkScalar that are passed to + // SkScalarRound(value) as SkScalarRound(value * 10). Safari is already + // doing the same for text rendering. + SkASSERT(shared_section); + SkDevice* device = VectorPlatformDeviceEmf::create( + reinterpret_cast<HDC>(shared_section), width, height); + return device; +} + +static void FillBitmapInfoHeader(int width, int height, BITMAPINFOHEADER* hdr) { + hdr->biSize = sizeof(BITMAPINFOHEADER); + hdr->biWidth = width; + hdr->biHeight = -height; // Minus means top-down bitmap. + hdr->biPlanes = 1; + hdr->biBitCount = 32; + hdr->biCompression = BI_RGB; // no compression + hdr->biSizeImage = 0; + hdr->biXPelsPerMeter = 1; + hdr->biYPelsPerMeter = 1; + hdr->biClrUsed = 0; + hdr->biClrImportant = 0; +} + +SkDevice* VectorPlatformDeviceEmf::create(HDC dc, int width, int height) { + InitializeDC(dc); + + // Link the SkBitmap to the current selected bitmap in the device context. + SkBitmap bitmap; + HGDIOBJ selected_bitmap = GetCurrentObject(dc, OBJ_BITMAP); + bool succeeded = false; + if (selected_bitmap != NULL) { + BITMAP bitmap_data; + if (GetObject(selected_bitmap, sizeof(BITMAP), &bitmap_data) == + sizeof(BITMAP)) { + // The context has a bitmap attached. Attach our SkBitmap to it. + // Warning: If the bitmap gets unselected from the HDC, + // VectorPlatformDeviceEmf has no way to detect this, so the HBITMAP + // could be released while SkBitmap still has a reference to it. Be + // cautious. + if (width == bitmap_data.bmWidth && + height == bitmap_data.bmHeight) { + bitmap.setConfig(SkBitmap::kARGB_8888_Config, + bitmap_data.bmWidth, + bitmap_data.bmHeight, + bitmap_data.bmWidthBytes); + bitmap.setPixels(bitmap_data.bmBits); + succeeded = true; + } + } + } + + if (!succeeded) + bitmap.setConfig(SkBitmap::kARGB_8888_Config, width, height); + + return new VectorPlatformDeviceEmf(dc, bitmap); +} + +VectorPlatformDeviceEmf::VectorPlatformDeviceEmf(HDC dc, const SkBitmap& bitmap) + : SkDevice(bitmap), + hdc_(dc), + previous_brush_(NULL), + previous_pen_(NULL) { + transform_.reset(); + SetPlatformDevice(this, this); +} + +VectorPlatformDeviceEmf::~VectorPlatformDeviceEmf() { + SkASSERT(previous_brush_ == NULL); + SkASSERT(previous_pen_ == NULL); +} + +HDC VectorPlatformDeviceEmf::BeginPlatformPaint() { + return hdc_; +} + +uint32_t VectorPlatformDeviceEmf::getDeviceCapabilities() { + return SkDevice::getDeviceCapabilities() | kVector_Capability; +} + +void VectorPlatformDeviceEmf::drawPaint(const SkDraw& draw, + const SkPaint& paint) { + // TODO(maruel): Bypass the current transformation matrix. + SkRect rect; + rect.fLeft = 0; + rect.fTop = 0; + rect.fRight = SkIntToScalar(width() + 1); + rect.fBottom = SkIntToScalar(height() + 1); + drawRect(draw, rect, paint); +} + +void VectorPlatformDeviceEmf::drawPoints(const SkDraw& draw, + SkCanvas::PointMode mode, + size_t count, + const SkPoint pts[], + const SkPaint& paint) { + if (!count) + return; + + if (mode == SkCanvas::kPoints_PointMode) { + SkASSERT(false); + return; + } + + SkPaint tmp_paint(paint); + tmp_paint.setStyle(SkPaint::kStroke_Style); + + // Draw a path instead. + SkPath path; + switch (mode) { + case SkCanvas::kLines_PointMode: + if (count % 2) { + SkASSERT(false); + return; + } + for (size_t i = 0; i < count / 2; ++i) { + path.moveTo(pts[2 * i]); + path.lineTo(pts[2 * i + 1]); + } + break; + case SkCanvas::kPolygon_PointMode: + path.moveTo(pts[0]); + for (size_t i = 1; i < count; ++i) { + path.lineTo(pts[i]); + } + break; + default: + SkASSERT(false); + return; + } + // Draw the calculated path. + drawPath(draw, path, tmp_paint); +} + +void VectorPlatformDeviceEmf::drawRect(const SkDraw& draw, + const SkRect& rect, + const SkPaint& paint) { + CHECK_FOR_NODRAW_ANNOTATION(paint); + if (paint.getPathEffect()) { + // Draw a path instead. + SkPath path_orginal; + path_orginal.addRect(rect); + + // Apply the path effect to the rect. + SkPath path_modified; + paint.getFillPath(path_orginal, &path_modified); + + // Removes the path effect from the temporary SkPaint object. + SkPaint paint_no_effet(paint); + paint_no_effet.setPathEffect(NULL); + + // Draw the calculated path. + drawPath(draw, path_modified, paint_no_effet); + return; + } + + if (!ApplyPaint(paint)) { + return; + } + HDC dc = BeginPlatformPaint(); + if (!Rectangle(dc, SkScalarRound(rect.fLeft), + SkScalarRound(rect.fTop), + SkScalarRound(rect.fRight), + SkScalarRound(rect.fBottom))) { + SkASSERT(false); + } + EndPlatformPaint(); + Cleanup(); +} + +void VectorPlatformDeviceEmf::drawPath(const SkDraw& draw, + const SkPath& path, + const SkPaint& paint, + const SkMatrix* prePathMatrix, + bool pathIsMutable) { + CHECK_FOR_NODRAW_ANNOTATION(paint); + if (paint.getPathEffect()) { + // Apply the path effect forehand. + SkPath path_modified; + paint.getFillPath(path, &path_modified); + + // Removes the path effect from the temporary SkPaint object. + SkPaint paint_no_effet(paint); + paint_no_effet.setPathEffect(NULL); + + // Draw the calculated path. + drawPath(draw, path_modified, paint_no_effet); + return; + } + + if (!ApplyPaint(paint)) { + return; + } + HDC dc = BeginPlatformPaint(); + if (PlatformDevice::LoadPathToDC(dc, path)) { + switch (paint.getStyle()) { + case SkPaint::kFill_Style: { + BOOL res = StrokeAndFillPath(dc); + SkASSERT(res != 0); + break; + } + case SkPaint::kStroke_Style: { + BOOL res = StrokePath(dc); + SkASSERT(res != 0); + break; + } + case SkPaint::kStrokeAndFill_Style: { + BOOL res = StrokeAndFillPath(dc); + SkASSERT(res != 0); + break; + } + default: + SkASSERT(false); + break; + } + } + EndPlatformPaint(); + Cleanup(); +} + +void VectorPlatformDeviceEmf::drawBitmapRect(const SkDraw& draw, + const SkBitmap& bitmap, + const SkRect* src, + const SkRect& dst, + const SkPaint& paint) { + SkMatrix matrix; + SkRect bitmapBounds, tmpSrc, tmpDst; + SkBitmap tmpBitmap; + + bitmapBounds.isetWH(bitmap.width(), bitmap.height()); + + // Compute matrix from the two rectangles + if (src) { + tmpSrc = *src; + } else { + tmpSrc = bitmapBounds; + } + matrix.setRectToRect(tmpSrc, dst, SkMatrix::kFill_ScaleToFit); + + const SkBitmap* bitmapPtr = &bitmap; + + // clip the tmpSrc to the bounds of the bitmap, and recompute dstRect if + // needed (if the src was clipped). No check needed if src==null. + if (src) { + if (!bitmapBounds.contains(*src)) { + if (!tmpSrc.intersect(bitmapBounds)) { + return; // nothing to draw + } + // recompute dst, based on the smaller tmpSrc + matrix.mapRect(&tmpDst, tmpSrc); + } + + // since we may need to clamp to the borders of the src rect within + // the bitmap, we extract a subset. + // TODO: make sure this is handled in drawrect and remove it from here. + SkIRect srcIR; + tmpSrc.roundOut(&srcIR); + if (!bitmap.extractSubset(&tmpBitmap, srcIR)) { + return; + } + bitmapPtr = &tmpBitmap; + + // Since we did an extract, we need to adjust the matrix accordingly + SkScalar dx = 0, dy = 0; + if (srcIR.fLeft > 0) { + dx = SkIntToScalar(srcIR.fLeft); + } + if (srcIR.fTop > 0) { + dy = SkIntToScalar(srcIR.fTop); + } + if (dx || dy) { + matrix.preTranslate(dx, dy); + } + } + this->drawBitmap(draw, *bitmapPtr, matrix, paint); +} + +void VectorPlatformDeviceEmf::drawBitmap(const SkDraw& draw, + const SkBitmap& bitmap, + const SkMatrix& matrix, + const SkPaint& paint) { + // Load the temporary matrix. This is what will translate, rotate and resize + // the bitmap. + SkMatrix actual_transform(transform_); + actual_transform.preConcat(matrix); + LoadTransformToDC(hdc_, actual_transform); + + InternalDrawBitmap(bitmap, 0, 0, paint); + + // Restore the original matrix. + LoadTransformToDC(hdc_, transform_); +} + +void VectorPlatformDeviceEmf::drawSprite(const SkDraw& draw, + const SkBitmap& bitmap, + int x, int y, + const SkPaint& paint) { + SkMatrix identity; + identity.reset(); + LoadTransformToDC(hdc_, identity); + + InternalDrawBitmap(bitmap, x, y, paint); + + // Restore the original matrix. + LoadTransformToDC(hdc_, transform_); +} + +///////////////////////////////////////////////////////////////////////// + +static bool gdiCanHandleText(const SkPaint& paint) { + return !paint.getShader() && + !paint.getPathEffect() && + (SkPaint::kFill_Style == paint.getStyle()) && + (255 == paint.getAlpha()); +} + +class SkGDIFontSetup { + public: + SkGDIFontSetup() : + fHDC(NULL), + fNewFont(NULL), + fSavedFont(NULL), + fSavedTextColor(0), + fUseGDI(false) { + SkDEBUGCODE(fUseGDIHasBeenCalled = false;) + } + ~SkGDIFontSetup(); + + // can only be called once + bool useGDI(HDC hdc, const SkPaint&); + + private: + HDC fHDC; + HFONT fNewFont; + HFONT fSavedFont; + COLORREF fSavedTextColor; + bool fUseGDI; + SkDEBUGCODE(bool fUseGDIHasBeenCalled;) +}; + +bool SkGDIFontSetup::useGDI(HDC hdc, const SkPaint& paint) { + SkASSERT(!fUseGDIHasBeenCalled); + SkDEBUGCODE(fUseGDIHasBeenCalled = true;) + + fUseGDI = gdiCanHandleText(paint); + if (fUseGDI) { + fSavedTextColor = GetTextColor(hdc); + SetTextColor(hdc, skia::SkColorToCOLORREF(paint.getColor())); + + LOGFONT lf; + SkLOGFONTFromTypeface(paint.getTypeface(), &lf); + lf.lfHeight = -SkScalarRound(paint.getTextSize()); + fNewFont = CreateFontIndirect(&lf); + fSavedFont = (HFONT)::SelectObject(hdc, fNewFont); + fHDC = hdc; + } + return fUseGDI; +} + +SkGDIFontSetup::~SkGDIFontSetup() { + if (fUseGDI) { + ::SelectObject(fHDC, fSavedFont); + ::DeleteObject(fNewFont); + SetTextColor(fHDC, fSavedTextColor); + } +} + +static SkScalar getAscent(const SkPaint& paint) { + SkPaint::FontMetrics fm; + paint.getFontMetrics(&fm); + return fm.fAscent; +} + +// return the options int for ExtTextOut. Only valid if the paint's text +// encoding is not UTF8 (in which case ExtTextOut can't be used). +static UINT getTextOutOptions(const SkPaint& paint) { + if (SkPaint::kGlyphID_TextEncoding == paint.getTextEncoding()) { + return ETO_GLYPH_INDEX; + } else { + SkASSERT(SkPaint::kUTF16_TextEncoding == paint.getTextEncoding()); + return 0; + } +} + +static SkiaEnsureTypefaceCharactersAccessible + g_skia_ensure_typeface_characters_accessible = NULL; + +SK_API void SetSkiaEnsureTypefaceCharactersAccessible( + SkiaEnsureTypefaceCharactersAccessible func) { + // This function is supposed to be called once in process life time. + SkASSERT(g_skia_ensure_typeface_characters_accessible == NULL); + g_skia_ensure_typeface_characters_accessible = func; +} + +void EnsureTypefaceCharactersAccessible( + const SkTypeface& typeface, const wchar_t* text, unsigned int text_length) { + LOGFONT lf; + SkLOGFONTFromTypeface(&typeface, &lf); + g_skia_ensure_typeface_characters_accessible(lf, text, text_length); +} + +bool EnsureExtTextOut(HDC hdc, int x, int y, UINT options, const RECT * lprect, + LPCWSTR text, unsigned int characters, const int * lpDx, + SkTypeface* const typeface) { + bool success = ExtTextOut(hdc, x, y, options, lprect, text, characters, lpDx); + if (!success) { + if (typeface) { + EnsureTypefaceCharactersAccessible(*typeface, + text, + characters); + success = ExtTextOut(hdc, x, y, options, lprect, text, characters, lpDx); + if (!success) { + LOGFONT lf; + SkLOGFONTFromTypeface(typeface, &lf); + VLOG(1) << "SkFontHost::EnsureTypefaceCharactersAccessible FAILED for " + << " FaceName = " << lf.lfFaceName + << " and characters: " << string16(text, characters); + } + } else { + VLOG(1) << "ExtTextOut FAILED for default FaceName " + << " and characters: " << string16(text, characters); + } + } + return success; +} + +void VectorPlatformDeviceEmf::drawText(const SkDraw& draw, + const void* text, + size_t byteLength, + SkScalar x, + SkScalar y, + const SkPaint& paint) { + SkGDIFontSetup setup; + bool useDrawPath = true; + + if (SkPaint::kUTF8_TextEncoding != paint.getTextEncoding() + && setup.useGDI(hdc_, paint)) { + UINT options = getTextOutOptions(paint); + UINT count = byteLength >> 1; + useDrawPath = !EnsureExtTextOut(hdc_, SkScalarRound(x), + SkScalarRound(y + getAscent(paint)), options, 0, + reinterpret_cast<const wchar_t*>(text), count, NULL, + paint.getTypeface()); + } + + if (useDrawPath) { + SkPath path; + paint.getTextPath(text, byteLength, x, y, &path); + drawPath(draw, path, paint); + } +} + +static size_t size_utf8(const char* text) { + return SkUTF8_CountUTF8Bytes(text); +} + +static size_t size_utf16(const char* text) { + uint16_t c = *reinterpret_cast<const uint16_t*>(text); + return SkUTF16_IsHighSurrogate(c) ? 4 : 2; +} + +static size_t size_glyphid(const char* text) { + return 2; +} + +void VectorPlatformDeviceEmf::drawPosText(const SkDraw& draw, + const void* text, + size_t len, + const SkScalar pos[], + SkScalar constY, + int scalarsPerPos, + const SkPaint& paint) { + SkGDIFontSetup setup; + bool useDrawText = true; + + if (2 == scalarsPerPos + && SkPaint::kUTF8_TextEncoding != paint.getTextEncoding() + && setup.useGDI(hdc_, paint)) { + int startX = SkScalarRound(pos[0]); + int startY = SkScalarRound(pos[1] + getAscent(paint)); + const int count = len >> 1; + SkAutoSTMalloc<64, INT> storage(count); + INT* advances = storage.get(); + for (int i = 0; i < count - 1; ++i) { + advances[i] = SkScalarRound(pos[2] - pos[0]); + pos += 2; + } + useDrawText = !EnsureExtTextOut(hdc_, startX, startY, + getTextOutOptions(paint), 0, reinterpret_cast<const wchar_t*>(text), + count, advances, paint.getTypeface()); + } + + if (useDrawText) { + size_t (*bytesPerCodePoint)(const char*); + switch (paint.getTextEncoding()) { + case SkPaint::kUTF8_TextEncoding: + bytesPerCodePoint = size_utf8; + break; + case SkPaint::kUTF16_TextEncoding: + bytesPerCodePoint = size_utf16; + break; + default: + SkASSERT(SkPaint::kGlyphID_TextEncoding == paint.getTextEncoding()); + bytesPerCodePoint = size_glyphid; + break; + } + + const char* curr = reinterpret_cast<const char*>(text); + const char* stop = curr + len; + while (curr < stop) { + SkScalar y = (1 == scalarsPerPos) ? constY : pos[1]; + size_t bytes = bytesPerCodePoint(curr); + drawText(draw, curr, bytes, pos[0], y, paint); + curr += bytes; + pos += scalarsPerPos; + } + } +} + +void VectorPlatformDeviceEmf::drawTextOnPath(const SkDraw& draw, + const void* text, + size_t len, + const SkPath& path, + const SkMatrix* matrix, + const SkPaint& paint) { + // This function isn't used in the code. Verify this assumption. + SkASSERT(false); +} + +void VectorPlatformDeviceEmf::drawVertices(const SkDraw& draw, + SkCanvas::VertexMode vmode, + int vertexCount, + const SkPoint vertices[], + const SkPoint texs[], + const SkColor colors[], + SkXfermode* xmode, + const uint16_t indices[], + int indexCount, + const SkPaint& paint) { + // This function isn't used in the code. Verify this assumption. + SkASSERT(false); +} + +void VectorPlatformDeviceEmf::drawDevice(const SkDraw& draw, + SkDevice* device, + int x, + int y, + const SkPaint& paint) { + // TODO(maruel): http://b/1183870 Playback the EMF buffer at printer's dpi if + // it is a vectorial device. + drawSprite(draw, device->accessBitmap(false), x, y, paint); +} + +bool VectorPlatformDeviceEmf::ApplyPaint(const SkPaint& paint) { + // Note: The goal here is to transfert the SkPaint's state to the HDC's state. + // This function does not execute the SkPaint drawing commands. These should + // be executed in drawPaint(). + + SkPaint::Style style = paint.getStyle(); + if (!paint.getAlpha()) + style = (SkPaint::Style) SkPaint::kStyleCount; + + switch (style) { + case SkPaint::kFill_Style: + if (!CreateBrush(true, paint) || + !CreatePen(false, paint)) + return false; + break; + case SkPaint::kStroke_Style: + if (!CreateBrush(false, paint) || + !CreatePen(true, paint)) + return false; + break; + case SkPaint::kStrokeAndFill_Style: + if (!CreateBrush(true, paint) || + !CreatePen(true, paint)) + return false; + break; + default: + if (!CreateBrush(false, paint) || + !CreatePen(false, paint)) + return false; + break; + } + + /* + getFlags(); + isAntiAlias(); + isDither() + isLinearText() + isSubpixelText() + isUnderlineText() + isStrikeThruText() + isFakeBoldText() + isDevKernText() + isFilterBitmap() + + // Skia's text is not used. This should be fixed. + getTextAlign() + getTextScaleX() + getTextSkewX() + getTextEncoding() + getFontMetrics() + getFontSpacing() + */ + + // BUG 1094907: Implement shaders. Shaders currently in use: + // SkShader::CreateBitmapShader + // SkGradientShader::CreateRadial + // SkGradientShader::CreateLinear + // SkASSERT(!paint.getShader()); + + // http://b/1106647 Implement loopers and mask filter. Looper currently in + // use: + // SkBlurDrawLooper is used for shadows. + // SkASSERT(!paint.getLooper()); + // SkASSERT(!paint.getMaskFilter()); + + // http://b/1165900 Implement xfermode. + // SkASSERT(!paint.getXfermode()); + + // The path effect should be processed before arriving here. + SkASSERT(!paint.getPathEffect()); + + // This isn't used in the code. Verify this assumption. + SkASSERT(!paint.getRasterizer()); + // Reuse code to load Win32 Fonts. + return true; +} + +void VectorPlatformDeviceEmf::setMatrixClip(const SkMatrix& transform, + const SkRegion& region, + const SkClipStack&) { + transform_ = transform; + LoadTransformToDC(hdc_, transform_); + clip_region_ = region; + if (!clip_region_.isEmpty()) + LoadClipRegion(); +} + +void VectorPlatformDeviceEmf::DrawToNativeContext(HDC dc, int x, int y, + const RECT* src_rect) { + SkASSERT(false); +} + +void VectorPlatformDeviceEmf::LoadClipRegion() { + SkMatrix t; + t.reset(); + LoadClippingRegionToDC(hdc_, clip_region_, t); +} + +SkDevice* VectorPlatformDeviceEmf::onCreateCompatibleDevice( + SkBitmap::Config config, int width, int height, bool isOpaque, + Usage /*usage*/) { + SkASSERT(config == SkBitmap::kARGB_8888_Config); + return VectorPlatformDeviceEmf::CreateDevice(width, height, isOpaque, NULL); +} + +bool VectorPlatformDeviceEmf::CreateBrush(bool use_brush, COLORREF color) { + SkASSERT(previous_brush_ == NULL); + // We can't use SetDCBrushColor() or DC_BRUSH when drawing to a EMF buffer. + // SetDCBrushColor() calls are not recorded at all and DC_BRUSH will use + // WHITE_BRUSH instead. + + if (!use_brush) { + // Set the transparency. + if (0 == SetBkMode(hdc_, TRANSPARENT)) { + SkASSERT(false); + return false; + } + + // Select the NULL brush. + previous_brush_ = SelectObject(GetStockObject(NULL_BRUSH)); + return previous_brush_ != NULL; + } + + // Set the opacity. + if (0 == SetBkMode(hdc_, OPAQUE)) { + SkASSERT(false); + return false; + } + + // Create and select the brush. + previous_brush_ = SelectObject(CreateSolidBrush(color)); + return previous_brush_ != NULL; +} + +bool VectorPlatformDeviceEmf::CreatePen(bool use_pen, + COLORREF color, + int stroke_width, + float stroke_miter, + DWORD pen_style) { + SkASSERT(previous_pen_ == NULL); + // We can't use SetDCPenColor() or DC_PEN when drawing to a EMF buffer. + // SetDCPenColor() calls are not recorded at all and DC_PEN will use BLACK_PEN + // instead. + + // No pen case + if (!use_pen) { + previous_pen_ = SelectObject(GetStockObject(NULL_PEN)); + return previous_pen_ != NULL; + } + + // Use the stock pen if the stroke width is 0. + if (stroke_width == 0) { + // Create a pen with the right color. + previous_pen_ = SelectObject(::CreatePen(PS_SOLID, 0, color)); + return previous_pen_ != NULL; + } + + // Load a custom pen. + LOGBRUSH brush; + brush.lbStyle = BS_SOLID; + brush.lbColor = color; + brush.lbHatch = 0; + HPEN pen = ExtCreatePen(pen_style, stroke_width, &brush, 0, NULL); + SkASSERT(pen != NULL); + previous_pen_ = SelectObject(pen); + if (previous_pen_ == NULL) + return false; + + if (!SetMiterLimit(hdc_, stroke_miter, NULL)) { + SkASSERT(false); + return false; + } + return true; +} + +void VectorPlatformDeviceEmf::Cleanup() { + if (previous_brush_) { + HGDIOBJ result = SelectObject(previous_brush_); + previous_brush_ = NULL; + if (result) { + BOOL res = DeleteObject(result); + SkASSERT(res != 0); + } + } + if (previous_pen_) { + HGDIOBJ result = SelectObject(previous_pen_); + previous_pen_ = NULL; + if (result) { + BOOL res = DeleteObject(result); + SkASSERT(res != 0); + } + } + // Remove any loaded path from the context. + AbortPath(hdc_); +} + +HGDIOBJ VectorPlatformDeviceEmf::SelectObject(HGDIOBJ object) { + HGDIOBJ result = ::SelectObject(hdc_, object); + SkASSERT(result != HGDI_ERROR); + if (result == HGDI_ERROR) + return NULL; + return result; +} + +bool VectorPlatformDeviceEmf::CreateBrush(bool use_brush, + const SkPaint& paint) { + // Make sure that for transparent color, no brush is used. + if (paint.getAlpha() == 0) { + use_brush = false; + } + + return CreateBrush(use_brush, SkColorToCOLORREF(paint.getColor())); +} + +bool VectorPlatformDeviceEmf::CreatePen(bool use_pen, const SkPaint& paint) { + // Make sure that for transparent color, no pen is used. + if (paint.getAlpha() == 0) { + use_pen = false; + } + + DWORD pen_style = PS_GEOMETRIC | PS_SOLID; + switch (paint.getStrokeJoin()) { + case SkPaint::kMiter_Join: + // Connects path segments with a sharp join. + pen_style |= PS_JOIN_MITER; + break; + case SkPaint::kRound_Join: + // Connects path segments with a round join. + pen_style |= PS_JOIN_ROUND; + break; + case SkPaint::kBevel_Join: + // Connects path segments with a flat bevel join. + pen_style |= PS_JOIN_BEVEL; + break; + default: + SkASSERT(false); + break; + } + switch (paint.getStrokeCap()) { + case SkPaint::kButt_Cap: + // Begin/end contours with no extension. + pen_style |= PS_ENDCAP_FLAT; + break; + case SkPaint::kRound_Cap: + // Begin/end contours with a semi-circle extension. + pen_style |= PS_ENDCAP_ROUND; + break; + case SkPaint::kSquare_Cap: + // Begin/end contours with a half square extension. + pen_style |= PS_ENDCAP_SQUARE; + break; + default: + SkASSERT(false); + break; + } + + return CreatePen(use_pen, + SkColorToCOLORREF(paint.getColor()), + SkScalarRound(paint.getStrokeWidth()), + paint.getStrokeMiter(), + pen_style); +} + +void VectorPlatformDeviceEmf::InternalDrawBitmap(const SkBitmap& bitmap, + int x, int y, + const SkPaint& paint) { + unsigned char alpha = paint.getAlpha(); + if (alpha == 0) + return; + + bool is_translucent; + if (alpha != 255) { + // ApplyPaint expect an opaque color. + SkPaint tmp_paint(paint); + tmp_paint.setAlpha(255); + if (!ApplyPaint(tmp_paint)) + return; + is_translucent = true; + } else { + if (!ApplyPaint(paint)) + return; + is_translucent = false; + } + int src_size_x = bitmap.width(); + int src_size_y = bitmap.height(); + if (!src_size_x || !src_size_y) + return; + + // Create a BMP v4 header that we can serialize. We use the shared "V3" + // fillter to fill the stardard items, then add in the "V4" stuff we want. + BITMAPV4HEADER bitmap_header; + memset(&bitmap_header, 0, sizeof(BITMAPV4HEADER)); + FillBitmapInfoHeader(src_size_x, src_size_y, + reinterpret_cast<BITMAPINFOHEADER*>(&bitmap_header)); + bitmap_header.bV4Size = sizeof(BITMAPV4HEADER); + bitmap_header.bV4RedMask = 0x00ff0000; + bitmap_header.bV4GreenMask = 0x0000ff00; + bitmap_header.bV4BlueMask = 0x000000ff; + bitmap_header.bV4AlphaMask = 0xff000000; + + SkAutoLockPixels lock(bitmap); + SkASSERT(bitmap.config() == SkBitmap::kARGB_8888_Config); + const uint32_t* pixels = static_cast<const uint32_t*>(bitmap.getPixels()); + if (pixels == NULL) { + SkASSERT(false); + return; + } + + if (!is_translucent) { + int row_length = bitmap.rowBytesAsPixels(); + // There is no quick way to determine if an image is opaque. + for (int y2 = 0; y2 < src_size_y; ++y2) { + for (int x2 = 0; x2 < src_size_x; ++x2) { + if (SkColorGetA(pixels[(y2 * row_length) + x2]) != 255) { + is_translucent = true; + y2 = src_size_y; + break; + } + } + } + } + + HDC dc = BeginPlatformPaint(); + BITMAPINFOHEADER hdr; + FillBitmapInfoHeader(src_size_x, src_size_y, &hdr); + if (is_translucent) { + // The image must be loaded as a bitmap inside a device context. + HDC bitmap_dc = ::CreateCompatibleDC(dc); + void* bits = NULL; + HBITMAP hbitmap = ::CreateDIBSection( + bitmap_dc, reinterpret_cast<const BITMAPINFO*>(&hdr), + DIB_RGB_COLORS, &bits, NULL, 0); + + // static cast to a char so we can do byte ptr arithmatic to + // get the offset. + unsigned char* dest_buffer = static_cast<unsigned char *>(bits); + + // We will copy row by row to avoid having to worry about + // the row strides being different. + const int dest_row_size = hdr.biBitCount / 8 * hdr.biWidth; + for (int row = 0; row < bitmap.height(); ++row) { + int dest_offset = row * dest_row_size; + // pixels_offset in terms of pixel count. + int src_offset = row * bitmap.rowBytesAsPixels(); + memcpy(dest_buffer + dest_offset, pixels + src_offset, dest_row_size); + } + SkASSERT(hbitmap); + HGDIOBJ old_bitmap = ::SelectObject(bitmap_dc, hbitmap); + + // After some analysis of IE7's behavior, this is the thing to do. I was + // sure IE7 was doing so kind of bitmasking due to the way translucent image + // where renderered but after some windbg tracing, it is being done by the + // printer driver after all (mostly HP printers). IE7 always use AlphaBlend + // for bitmasked images. The trick seems to switch the stretching mode in + // what the driver expects. + DWORD previous_mode = GetStretchBltMode(dc); + BOOL result = SetStretchBltMode(dc, COLORONCOLOR); + SkASSERT(result); + // Note that this function expect premultiplied colors (!) + BLENDFUNCTION blend_function = {AC_SRC_OVER, 0, alpha, AC_SRC_ALPHA}; + result = GdiAlphaBlend(dc, + x, y, // Destination origin. + src_size_x, src_size_y, // Destination size. + bitmap_dc, + 0, 0, // Source origin. + src_size_x, src_size_y, // Source size. + blend_function); + SkASSERT(result); + result = SetStretchBltMode(dc, previous_mode); + SkASSERT(result); + + ::SelectObject(bitmap_dc, static_cast<HBITMAP>(old_bitmap)); + DeleteObject(hbitmap); + DeleteDC(bitmap_dc); + } else { + int nCopied = StretchDIBits(dc, + x, y, // Destination origin. + src_size_x, src_size_y, + 0, 0, // Source origin. + src_size_x, src_size_y, // Source size. + pixels, + reinterpret_cast<const BITMAPINFO*>(&hdr), + DIB_RGB_COLORS, + SRCCOPY); + } + EndPlatformPaint(); + Cleanup(); +} + +} // namespace skia diff --git a/chromium/skia/ext/vector_platform_device_emf_win.h b/chromium/skia/ext/vector_platform_device_emf_win.h new file mode 100644 index 00000000000..c0deeeceaeb --- /dev/null +++ b/chromium/skia/ext/vector_platform_device_emf_win.h @@ -0,0 +1,140 @@ +// 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 SKIA_EXT_VECTOR_PLATFORM_DEVICE_EMF_WIN_H_ +#define SKIA_EXT_VECTOR_PLATFORM_DEVICE_EMF_WIN_H_ + +#include "base/basictypes.h" +#include "base/compiler_specific.h" +#include "skia/ext/platform_device.h" +#include "third_party/skia/include/core/SkMatrix.h" +#include "third_party/skia/include/core/SkRegion.h" + +namespace skia { + +// A device is basically a wrapper around SkBitmap that provides a surface for +// SkCanvas to draw into. This specific device is not not backed by a surface +// and is thus unreadable. This is because the backend is completely vectorial. +// This device is a simple wrapper over a Windows device context (HDC) handle. +class VectorPlatformDeviceEmf : public SkDevice, public PlatformDevice { + public: + SK_API static SkDevice* CreateDevice(int width, int height, bool isOpaque, + HANDLE shared_section); + + // Factory function. The DC is kept as the output context. + static SkDevice* create(HDC dc, int width, int height); + + VectorPlatformDeviceEmf(HDC dc, const SkBitmap& bitmap); + virtual ~VectorPlatformDeviceEmf(); + + // PlatformDevice methods + virtual PlatformSurface BeginPlatformPaint() OVERRIDE; + virtual void DrawToNativeContext(HDC dc, int x, int y, + const RECT* src_rect) OVERRIDE; + // SkDevice methods. + virtual uint32_t getDeviceCapabilities(); + virtual void drawPaint(const SkDraw& draw, const SkPaint& paint) OVERRIDE; + virtual void drawPoints(const SkDraw& draw, SkCanvas::PointMode mode, + size_t count, const SkPoint[], + const SkPaint& paint) OVERRIDE; + virtual void drawRect(const SkDraw& draw, const SkRect& r, + const SkPaint& paint) OVERRIDE; + virtual void drawPath(const SkDraw& draw, const SkPath& path, + const SkPaint& paint, + const SkMatrix* prePathMatrix = NULL, + bool pathIsMutable = false) OVERRIDE; + virtual void drawBitmapRect(const SkDraw& draw, const SkBitmap& bitmap, + const SkRect* src, const SkRect& dst, + const SkPaint& paint) SK_OVERRIDE; + virtual void drawBitmap(const SkDraw& draw, const SkBitmap& bitmap, + const SkMatrix& matrix, + const SkPaint& paint) OVERRIDE; + virtual void drawSprite(const SkDraw& draw, const SkBitmap& bitmap, + int x, int y, const SkPaint& paint) OVERRIDE; + virtual void drawText(const SkDraw& draw, const void* text, size_t len, + SkScalar x, SkScalar y, const SkPaint& paint) OVERRIDE; + virtual void drawPosText(const SkDraw& draw, const void* text, size_t len, + const SkScalar pos[], SkScalar constY, + int scalarsPerPos, const SkPaint& paint) OVERRIDE; + virtual void drawTextOnPath(const SkDraw& draw, const void* text, size_t len, + const SkPath& path, const SkMatrix* matrix, + const SkPaint& paint) OVERRIDE; + virtual void drawVertices(const SkDraw& draw, SkCanvas::VertexMode, + int vertexCount, + const SkPoint verts[], const SkPoint texs[], + const SkColor colors[], SkXfermode* xmode, + const uint16_t indices[], int indexCount, + const SkPaint& paint) OVERRIDE; + virtual void drawDevice(const SkDraw& draw, SkDevice*, int x, int y, + const SkPaint&) OVERRIDE; + + virtual void setMatrixClip(const SkMatrix& transform, const SkRegion& region, + const SkClipStack&) OVERRIDE; + + void LoadClipRegion(); + + protected: + virtual SkDevice* onCreateCompatibleDevice(SkBitmap::Config, int width, + int height, bool isOpaque, + Usage usage) OVERRIDE; + + private: + // Applies the SkPaint's painting properties in the current GDI context, if + // possible. If GDI can't support all paint's properties, returns false. It + // doesn't execute the "commands" in SkPaint. + bool ApplyPaint(const SkPaint& paint); + + // Selects a new object in the device context. It can be a pen, a brush, a + // clipping region, a bitmap or a font. Returns the old selected object. + HGDIOBJ SelectObject(HGDIOBJ object); + + // Creates a brush according to SkPaint's properties. + bool CreateBrush(bool use_brush, const SkPaint& paint); + + // Creates a pen according to SkPaint's properties. + bool CreatePen(bool use_pen, const SkPaint& paint); + + // Restores back the previous objects (pen, brush, etc) after a paint command. + void Cleanup(); + + // Creates a brush according to SkPaint's properties. + bool CreateBrush(bool use_brush, COLORREF color); + + // Creates a pen according to SkPaint's properties. + bool CreatePen(bool use_pen, COLORREF color, int stroke_width, + float stroke_miter, DWORD pen_style); + + // Draws a bitmap in the the device, using the currently loaded matrix. + void InternalDrawBitmap(const SkBitmap& bitmap, int x, int y, + const SkPaint& paint); + + // The Windows Device Context handle. It is the backend used with GDI drawing. + // This backend is write-only and vectorial. + HDC hdc_; + + // Translation assigned to the DC: we need to keep track of this separately + // so it can be updated even if the DC isn't created yet. + SkMatrix transform_; + + // The current clipping + SkRegion clip_region_; + + // Previously selected brush before the current drawing. + HGDIOBJ previous_brush_; + + // Previously selected pen before the current drawing. + HGDIOBJ previous_pen_; + + DISALLOW_COPY_AND_ASSIGN(VectorPlatformDeviceEmf); +}; + +typedef void (*SkiaEnsureTypefaceCharactersAccessible) + (const LOGFONT& font, const wchar_t* text, unsigned int text_length); + +SK_API void SetSkiaEnsureTypefaceCharactersAccessible( + SkiaEnsureTypefaceCharactersAccessible func); + +} // namespace skia + +#endif // SKIA_EXT_VECTOR_PLATFORM_DEVICE_EMF_WIN_H_ diff --git a/chromium/skia/ext/vector_platform_device_skia.cc b/chromium/skia/ext/vector_platform_device_skia.cc new file mode 100644 index 00000000000..d8d3084afcb --- /dev/null +++ b/chromium/skia/ext/vector_platform_device_skia.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 "skia/ext/vector_platform_device_skia.h" + +#include "skia/ext/bitmap_platform_device.h" +#include "third_party/skia/include/core/SkClipStack.h" +#include "third_party/skia/include/core/SkDraw.h" +#include "third_party/skia/include/core/SkRect.h" +#include "third_party/skia/include/core/SkRegion.h" +#include "third_party/skia/include/core/SkScalar.h" + +namespace skia { + +static inline SkBitmap makeABitmap(int width, int height) { + SkBitmap bitmap; + bitmap.setConfig(SkBitmap::kNo_Config, width, height); + return bitmap; +} + +VectorPlatformDeviceSkia::VectorPlatformDeviceSkia( + const SkISize& pageSize, + const SkISize& contentSize, + const SkMatrix& initialTransform) + : SkPDFDevice(pageSize, contentSize, initialTransform) { + SetPlatformDevice(this, this); +} + +VectorPlatformDeviceSkia::~VectorPlatformDeviceSkia() { +} + +bool VectorPlatformDeviceSkia::SupportsPlatformPaint() { + return false; +} + +PlatformSurface VectorPlatformDeviceSkia::BeginPlatformPaint() { + // Even when drawing a vector representation of the page, we have to + // provide a raster surface for plugins to render into - they don't have + // a vector interface. Therefore we create a BitmapPlatformDevice here + // and return the context from it, then layer on the raster data as an + // image in EndPlatformPaint. + DCHECK(raster_surface_ == NULL); + raster_surface_ = skia::AdoptRef( + BitmapPlatformDevice::CreateAndClear(width(), height(), false)); + return raster_surface_->BeginPlatformPaint(); +} + +void VectorPlatformDeviceSkia::EndPlatformPaint() { + DCHECK(raster_surface_ != NULL); + SkPaint paint; + // SkPDFDevice checks the passed SkDraw for an empty clip (only). Fake + // it out by setting a non-empty clip. + SkDraw draw; + SkRegion clip(SkIRect::MakeWH(width(), height())); + draw.fClip=&clip; + drawSprite(draw, raster_surface_->accessBitmap(false), 0, 0, paint); + // BitmapPlatformDevice matches begin and end calls. + raster_surface_->EndPlatformPaint(); + raster_surface_.clear(); +} + +#if defined(OS_WIN) +void VectorPlatformDeviceSkia::DrawToNativeContext(HDC dc, + int x, + int y, + const RECT* src_rect) { + SkASSERT(false); +} +#elif defined(OS_MACOSX) +void VectorPlatformDeviceSkia::DrawToNativeContext(CGContext* context, int x, + int y, const CGRect* src_rect) { + SkASSERT(false); +} + +CGContextRef VectorPlatformDeviceSkia::GetBitmapContext() { + SkASSERT(false); + return NULL; +} +#elif defined(OS_LINUX) || defined(OS_ANDROID) || defined(OS_OPENBSD) +void VectorPlatformDeviceSkia::DrawToNativeContext( + PlatformSurface surface, int x, int y, const PlatformRect* src_rect) { + // Should never be called on Linux. + SkASSERT(false); +} +#endif + +} // namespace skia diff --git a/chromium/skia/ext/vector_platform_device_skia.h b/chromium/skia/ext/vector_platform_device_skia.h new file mode 100644 index 00000000000..4a6d2d92921 --- /dev/null +++ b/chromium/skia/ext/vector_platform_device_skia.h @@ -0,0 +1,60 @@ +// 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 SKIA_EXT_VECTOR_PLATFORM_DEVICE_SKIA_H_ +#define SKIA_EXT_VECTOR_PLATFORM_DEVICE_SKIA_H_ + +#include "base/basictypes.h" +#include "base/compiler_specific.h" +#include "base/logging.h" +#include "skia/ext/platform_device.h" +#include "skia/ext/refptr.h" +#include "third_party/skia/include/core/SkTScopedPtr.h" +#include "third_party/skia/include/pdf/SkPDFDevice.h" + +class SkMatrix; + +namespace skia { + +class BitmapPlatformDevice; + +class VectorPlatformDeviceSkia : public SkPDFDevice, public PlatformDevice { + public: + SK_API VectorPlatformDeviceSkia(const SkISize& pageSize, + const SkISize& contentSize, + const SkMatrix& initialTransform); + virtual ~VectorPlatformDeviceSkia(); + + // PlatformDevice methods. + virtual bool SupportsPlatformPaint() OVERRIDE; + + virtual PlatformSurface BeginPlatformPaint() OVERRIDE; + virtual void EndPlatformPaint() OVERRIDE; +#if defined(OS_WIN) + virtual void DrawToNativeContext(HDC dc, + int x, + int y, + const RECT* src_rect) OVERRIDE; +#elif defined(OS_MACOSX) + virtual void DrawToNativeContext(CGContext* context, + int x, + int y, + const CGRect* src_rect) OVERRIDE; + virtual CGContextRef GetBitmapContext() OVERRIDE; +#elif defined(OS_LINUX) || defined(OS_ANDROID) || defined(OS_OPENBSD) + virtual void DrawToNativeContext(PlatformSurface surface, + int x, + int y, + const PlatformRect* src_rect) OVERRIDE; +#endif + + private: + skia::RefPtr<BitmapPlatformDevice> raster_surface_; + + DISALLOW_COPY_AND_ASSIGN(VectorPlatformDeviceSkia); +}; + +} // namespace skia + +#endif // SKIA_EXT_VECTOR_PLATFORM_DEVICE_SKIA_H_ diff --git a/chromium/skia/fix_for_1186198.diff b/chromium/skia/fix_for_1186198.diff new file mode 100644 index 00000000000..6158f594a25 --- /dev/null +++ b/chromium/skia/fix_for_1186198.diff @@ -0,0 +1,38 @@ +Index: sgl/SkEdge.cpp
+===================================================================
+--- sgl/SkEdge.cpp (revision 42965)
++++ sgl/SkEdge.cpp (working copy)
+@@ -17,6 +17,7 @@
+ + #include "SkEdge.h" + #include "SkFDot6.h" ++#include <limits> + + /* + In setLine, setQuadratic, setCubic, the first thing we do is to convert +@@ -76,8 +77,23 @@
+ + fX = SkFDot6ToFixed(x0 + SkFixedMul(slope, (32 - y0) & 63)); // + SK_Fixed1/2 + fDX = slope; +- fFirstY = SkToS16(top); +- fLastY = SkToS16(bot - 1); ++ fFirstY = (int16_t)(top); // inlined skToS16() ++ if (top != (long)fFirstY) { ++ if (fFirstY < top) { ++ fFirstY = std::numeric_limits<int16_t>::max(); ++ } else { ++ fFirstY = std::numeric_limits<int16_t>::min(); ++ } ++ fX -= fDX * (top - (long)fFirstY); ++ } ++ fLastY = (int16_t)(bot - 1); // inlined SkToS16() ++ if (bot-1 != (long)fLastY) { ++ if (fLastY < bot-1) { ++ fLastY = std::numeric_limits<int16_t>::max(); ++ } else { ++ fLastY = std::numeric_limits<int16_t>::min(); ++ } ++ } + fCurveCount = 0; + fWinding = SkToS8(winding); + fCurveShift = 0; diff --git a/chromium/skia/skia.gyp b/chromium/skia/skia.gyp new file mode 100644 index 00000000000..d1a227557c6 --- /dev/null +++ b/chromium/skia/skia.gyp @@ -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. + +{ + 'conditions': [ + # In component mode (shared_lib), we build all of skia as a single DLL. + # However, in the static mode, we need to build skia as multiple targets + # in order to support the use case where a platform (e.g. Android) may + # already have a copy of skia as a system library. + ['component=="static_library" and use_system_skia==0', { + 'targets': [ + { + 'target_name': 'skia_library', + 'type': 'static_library', + 'includes': [ + 'skia_library.gypi', + 'skia_common.gypi', + ], + }, + ], + }], + ['component=="static_library" and use_system_skia==1', { + 'targets': [ + { + 'target_name': 'skia_library', + 'type': 'none', + 'includes': ['skia_system.gypi'], + }, + ], + }], + ['component=="static_library"', { + 'targets': [ + { + 'target_name': 'skia', + 'type': 'none', + 'dependencies': [ + 'skia_library', + 'skia_chrome', + ], + 'export_dependent_settings': [ + 'skia_library', + 'skia_chrome', + ], + }, + { + 'target_name': 'skia_chrome', + 'type': 'static_library', + 'includes': [ + 'skia_chrome.gypi', + 'skia_common.gypi', + ], + }, + ], + }, + { # component != static_library + 'targets': [ + { + 'target_name': 'skia', + 'type': 'shared_library', + 'includes': [ + 'skia_library.gypi', + 'skia_chrome.gypi', + 'skia_common.gypi', + ], + 'defines': [ + 'SKIA_DLL', + 'GR_DLL=1', + 'GR_IMPLEMENTATION=1', + 'SKIA_IMPLEMENTATION=1', + ], + 'direct_dependent_settings': { + 'defines': [ + 'SKIA_DLL', + 'GR_DLL=1', + ], + }, + }, + { + 'target_name': 'skia_library', + 'type': 'none', + }, + { + 'target_name': 'skia_chrome', + 'type': 'none', + }, + ], + }], + ], + + # targets that are not dependent upon the component type + 'targets': [ + { + 'target_name': 'skia_chrome_opts', + 'type': 'static_library', + 'include_dirs': [ + '..', + 'config', + '../third_party/skia/include/config', + '../third_party/skia/include/core', + ], + 'conditions': [ + [ 'os_posix == 1 and OS != "mac" and OS != "android" and \ + target_arch != "arm" and target_arch != "mipsel"', { + 'cflags': [ + '-msse2', + ], + }], + [ 'target_arch != "arm" and target_arch != "mipsel"', { + 'sources': [ + 'ext/convolver_SSE2.cc', + ], + }], + [ 'target_arch == "mipsel"',{ + 'cflags': [ + '-fomit-frame-pointer', + ], + 'sources': [ + 'ext/convolver_mips_dspr2.cc', + ], + }], + ], + }, + { + 'target_name': 'image_operations_bench', + 'type': 'executable', + 'dependencies': [ + '../base/base.gyp:base', + 'skia', + ], + 'include_dirs': [ + '..', + ], + 'sources': [ + 'ext/image_operations_bench.cc', + ], + }, + ], +} diff --git a/chromium/skia/skia_Prefix.pch b/chromium/skia/skia_Prefix.pch new file mode 100644 index 00000000000..5541dea0637 --- /dev/null +++ b/chromium/skia/skia_Prefix.pch @@ -0,0 +1,12 @@ +// Copyright (c) 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. + +// Prefix header for all source files in the 'Skia' framework. + +#ifdef __OBJC__ +#import <Cocoa/Cocoa.h> +#endif + +// Include the Skia prefix file. +#include "precompiled.cc" diff --git a/chromium/skia/skia_chrome.gypi b/chromium/skia/skia_chrome.gypi new file mode 100644 index 00000000000..ecd9b4735c2 --- /dev/null +++ b/chromium/skia/skia_chrome.gypi @@ -0,0 +1,132 @@ +# 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. + + +# This gypi file contains all the Chrome-specific enhancements to Skia. +# In component mode (shared_lib) it is folded into a single shared library with +# the Skia files but in all other cases it is a separate library. +{ + 'dependencies': [ + 'skia_library', + 'skia_chrome_opts', + '../base/base.gyp:base', + '../base/third_party/dynamic_annotations/dynamic_annotations.gyp:dynamic_annotations', + ], + + 'direct_dependent_settings': { + 'include_dirs': [ + 'ext', + ], + }, + + 'include_dirs': [ + '..', + ], + + 'sources': [ + 'ext/analysis_canvas.cc', + 'ext/analysis_canvas.h', + 'ext/benchmarking_canvas.cc', + 'ext/benchmarking_canvas.h', + 'ext/bitmap_platform_device.h', + 'ext/bitmap_platform_device_android.cc', + 'ext/bitmap_platform_device_android.h', + 'ext/bitmap_platform_device_data.h', + 'ext/bitmap_platform_device_linux.cc', + 'ext/bitmap_platform_device_linux.h', + 'ext/bitmap_platform_device_mac.cc', + 'ext/bitmap_platform_device_mac.h', + 'ext/bitmap_platform_device_win.cc', + 'ext/bitmap_platform_device_win.h', + 'ext/convolver.cc', + 'ext/convolver.h', + 'ext/google_logging.cc', + 'ext/image_operations.cc', + 'ext/image_operations.h', + 'ext/lazy_pixel_ref.cc', + 'ext/lazy_pixel_ref.h', + 'ext/lazy_pixel_ref_utils.cc', + 'ext/lazy_pixel_ref_utils.h', + 'ext/SkThread_chrome.cc', + 'ext/paint_simplifier.cc', + 'ext/paint_simplifier.h', + 'ext/platform_canvas.cc', + 'ext/platform_canvas.h', + 'ext/platform_device.cc', + 'ext/platform_device.h', + 'ext/platform_device_linux.cc', + 'ext/platform_device_mac.cc', + 'ext/platform_device_win.cc', + 'ext/recursive_gaussian_convolution.cc', + 'ext/recursive_gaussian_convolution.h', + 'ext/refptr.h', + 'ext/SkMemory_new_handler.cpp', + 'ext/skia_trace_shim.h', + 'ext/skia_utils_base.cc', + 'ext/skia_utils_base.h', + 'ext/skia_utils_ios.mm', + 'ext/skia_utils_ios.h', + 'ext/skia_utils_mac.mm', + 'ext/skia_utils_mac.h', + 'ext/skia_utils_win.cc', + 'ext/skia_utils_win.h', + 'ext/vector_canvas.cc', + 'ext/vector_canvas.h', + 'ext/vector_platform_device_emf_win.cc', + 'ext/vector_platform_device_emf_win.h', + 'ext/vector_platform_device_skia.cc', + 'ext/vector_platform_device_skia.h', + ], + + 'conditions': [ + # For POSIX platforms, prefer the Mutex implementation provided by Skia + # since it does not generate static initializers. + # TODO: should check if SK_USE_POSIX_THREADS is defined instead + [ 'OS == "android" or OS == "linux" or OS == "mac" or OS == "ios"', { + 'sources!': [ + 'ext/SkThread_chrome.cc', + ], + }], + [ 'OS == "android"', { + 'sources!': [ + 'ext/vector_platform_device_skia.cc', + ], + }], + ['OS == "ios"', { + 'sources/': [ + ['exclude', '^ext/vector_platform_device_skia\\.'], + ], + 'dependencies!': [ + 'skia_chrome_opts', + ], + }], + [ 'OS == "win"', { + 'sources!': [ + 'ext/SkThread_chrome.cc', + ], + }], + # TODO(scottmg): http://crbug.com/177306 + ['clang==1', { + 'xcode_settings': { + 'WARNING_CFLAGS!': [ + # Don't warn about string->bool used in asserts. + '-Wstring-conversion', + ], + }, + 'cflags!': [ + '-Wstring-conversion', + ], + }], + ], + + 'target_conditions': [ + # Pull in specific linux files for android (which have been filtered out + # by file name rules). + [ 'OS == "android"', { + 'sources/': [ + ['include', 'ext/platform_device_linux\\.cc$'], + ], + }], + ], +} diff --git a/chromium/skia/skia_common.gypi b/chromium/skia/skia_common.gypi new file mode 100644 index 00000000000..616ae5bb31b --- /dev/null +++ b/chromium/skia/skia_common.gypi @@ -0,0 +1,33 @@ +# 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. + +# This gypi file handles the removal of platform-specific files from the +# Skia build. +{ + 'conditions': [ + [ 'OS != "android"', { + 'sources/': [ + ['exclude', '_android\\.(cc|cpp)$'], + ], + }], + [ 'OS != "ios"', { + 'sources/': [ + ['exclude', '_ios\\.(cc|cpp|mm?)$'], + ], + }], + [ 'OS != "mac"', { + 'sources/': [ + ['exclude', '_mac\\.(cc|cpp|mm?)$'], + ], + }], + [ 'OS != "win"', { + 'sources/': [ ['exclude', '_win\\.(cc|cpp)$'] ], + }], + [ 'use_glib == 0', { + 'sources/': [ ['exclude', '_linux\\.(cc|cpp)$'] ], + }], + ], + + 'msvs_disabled_warnings': [4244, 4267, 4341, 4345, 4390, 4554, 4748, 4800], +} diff --git a/chromium/skia/skia_library.gypi b/chromium/skia/skia_library.gypi new file mode 100644 index 00000000000..29005533614 --- /dev/null +++ b/chromium/skia/skia_library.gypi @@ -0,0 +1,472 @@ +# 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. + + +# This gypi file contains the Skia library. +# In component mode (shared_lib) it is folded into a single shared library with +# the Chrome-specific enhancements but in all other cases it is a separate lib. +{ + 'dependencies': [ + 'skia_library_opts.gyp:skia_opts', + '../third_party/zlib/zlib.gyp:zlib', + ], + + 'variables': { + 'variables': { + 'conditions': [ + ['OS== "ios"', { + 'skia_support_gpu': 0, + }, { + 'skia_support_gpu': 1, + }], + ['OS=="ios" or OS=="android"', { + 'skia_support_pdf': 0, + }, { + 'skia_support_pdf': 1, + }], + ], + }, + 'skia_support_gpu': '<(skia_support_gpu)', + 'skia_support_pdf': '<(skia_support_pdf)', + + # These two set the paths so we can include skia/gyp/core.gypi + 'skia_src_path': '../third_party/skia/src', + 'skia_include_path': '../third_party/skia/include', + + # This list will contain all defines that also need to be exported to + # dependent components. + 'skia_export_defines': [ + 'SK_ENABLE_INST_COUNT=0', + 'SK_SUPPORT_GPU=<(skia_support_gpu)', + 'GR_GL_CUSTOM_SETUP_HEADER="GrGLConfig_chrome.h"', + ], + + 'default_font_cache_limit': '(20*1024*1024)', + + 'conditions': [ + ['OS== "android"', { + # Android devices are typically more memory constrained, so + # use a smaller glyph cache. + 'default_font_cache_limit': '(8*1024*1024)', + 'skia_export_defines': [ + 'SK_BUILD_FOR_ANDROID', + 'USE_CHROMIUM_SKIA', + ], + }], + ], + }, + + 'includes': [ + '../third_party/skia/gyp/core.gypi', + '../third_party/skia/gyp/effects.gypi', + ], + + 'sources': [ + # this should likely be moved into src/utils in skia + '../third_party/skia/src/core/SkFlate.cpp', + # We don't want to add this to Skia's core.gypi since it is + # Android only. Include it here and remove it for everyone + # but Android later. + '../third_party/skia/src/core/SkPaintOptionsAndroid.cpp', + + '../third_party/skia/src/ports/SkImageDecoder_empty.cpp', + '../third_party/skia/src/images/SkScaledBitmapSampler.cpp', + '../third_party/skia/src/images/SkScaledBitmapSampler.h', + + '../third_party/skia/src/opts/opts_check_SSE2.cpp', + + '../third_party/skia/src/pdf/SkPDFCatalog.cpp', + '../third_party/skia/src/pdf/SkPDFCatalog.h', + '../third_party/skia/src/pdf/SkPDFDevice.cpp', + '../third_party/skia/src/pdf/SkPDFDocument.cpp', + '../third_party/skia/src/pdf/SkPDFFont.cpp', + '../third_party/skia/src/pdf/SkPDFFont.h', + '../third_party/skia/src/pdf/SkPDFFormXObject.cpp', + '../third_party/skia/src/pdf/SkPDFFormXObject.h', + '../third_party/skia/src/pdf/SkPDFGraphicState.cpp', + '../third_party/skia/src/pdf/SkPDFGraphicState.h', + '../third_party/skia/src/pdf/SkPDFImage.cpp', + '../third_party/skia/src/pdf/SkPDFImage.h', + '../third_party/skia/src/pdf/SkPDFImageStream.cpp', + '../third_party/skia/src/pdf/SkPDFImageStream.h', + '../third_party/skia/src/pdf/SkPDFPage.cpp', + '../third_party/skia/src/pdf/SkPDFPage.h', + '../third_party/skia/src/pdf/SkPDFResourceDict.cpp', + '../third_party/skia/src/pdf/SkPDFResourceDict.h', + '../third_party/skia/src/pdf/SkPDFShader.cpp', + '../third_party/skia/src/pdf/SkPDFShader.h', + '../third_party/skia/src/pdf/SkPDFStream.cpp', + '../third_party/skia/src/pdf/SkPDFStream.h', + '../third_party/skia/src/pdf/SkPDFTypes.cpp', + '../third_party/skia/src/pdf/SkPDFTypes.h', + '../third_party/skia/src/pdf/SkPDFUtils.cpp', + '../third_party/skia/src/pdf/SkPDFUtils.h', + + '../third_party/skia/src/ports/SkPurgeableMemoryBlock_none.cpp', + + '../third_party/skia/src/ports/SkFontConfigInterface_android.cpp', + '../third_party/skia/src/ports/SkFontConfigInterface_direct.cpp', + + '../third_party/skia/src/fonts/SkFontMgr_fontconfig.cpp', + '../third_party/skia/src/ports/SkFontHost_fontconfig.cpp', + + '../third_party/skia/src/ports/SkFontHost_FreeType.cpp', + '../third_party/skia/src/ports/SkFontHost_FreeType_common.cpp', + '../third_party/skia/src/ports/SkFontHost_FreeType_common.h', + '../third_party/skia/src/ports/SkFontConfigParser_android.cpp', + '../third_party/skia/src/ports/SkFontHost_mac.cpp', + '../third_party/skia/src/ports/SkFontHost_win.cpp', + '../third_party/skia/src/ports/SkGlobalInitialization_chromium.cpp', + '../third_party/skia/src/ports/SkOSFile_posix.cpp', + '../third_party/skia/src/ports/SkOSFile_stdio.cpp', + '../third_party/skia/src/ports/SkOSFile_win.cpp', + '../third_party/skia/src/ports/SkThread_pthread.cpp', + '../third_party/skia/src/ports/SkThread_win.cpp', + '../third_party/skia/src/ports/SkTime_Unix.cpp', + '../third_party/skia/src/ports/SkTLS_pthread.cpp', + '../third_party/skia/src/ports/SkTLS_win.cpp', + + '../third_party/skia/src/sfnt/SkOTTable_name.cpp', + '../third_party/skia/src/sfnt/SkOTTable_name.h', + '../third_party/skia/src/sfnt/SkOTUtils.cpp', + '../third_party/skia/src/sfnt/SkOTUtils.h', + + '../third_party/skia/include/utils/mac/SkCGUtils.h', + '../third_party/skia/include/utils/SkDeferredCanvas.h', + '../third_party/skia/include/utils/SkMatrix44.h', + '../third_party/skia/src/utils/debugger/SkDebugCanvas.cpp', + '../third_party/skia/src/utils/debugger/SkDebugCanvas.h', + '../third_party/skia/src/utils/debugger/SkDrawCommand.cpp', + '../third_party/skia/src/utils/debugger/SkDrawCommand.h', + '../third_party/skia/src/utils/debugger/SkObjectParser.cpp', + '../third_party/skia/src/utils/debugger/SkObjectParser.h', + '../third_party/skia/src/utils/mac/SkCreateCGImageRef.cpp', + '../third_party/skia/src/utils/SkBase64.cpp', + '../third_party/skia/src/utils/SkBase64.h', + '../third_party/skia/src/utils/SkBitSet.cpp', + '../third_party/skia/src/utils/SkBitSet.h', + '../third_party/skia/src/utils/SkDeferredCanvas.cpp', + '../third_party/skia/src/utils/SkMatrix44.cpp', + '../third_party/skia/src/utils/SkNullCanvas.cpp', + '../third_party/skia/include/utils/SkNWayCanvas.h', + '../third_party/skia/src/utils/SkNWayCanvas.cpp', + '../third_party/skia/src/utils/SkPictureUtils.cpp', + '../third_party/skia/src/utils/SkProxyCanvas.cpp', + '../third_party/skia/src/utils/SkRTConf.cpp', + '../third_party/skia/include/utils/SkRTConf.h', + '../third_party/skia/include/pdf/SkPDFDevice.h', + '../third_party/skia/include/pdf/SkPDFDocument.h', + + '../third_party/skia/include/ports/SkTypeface_win.h', + + '../third_party/skia/include/images/SkImageRef.h', + '../third_party/skia/include/images/SkImageRef_GlobalPool.h', + '../third_party/skia/include/images/SkMovie.h', + '../third_party/skia/include/images/SkPageFlipper.h', + + '../third_party/skia/include/utils/SkNullCanvas.h', + '../third_party/skia/include/utils/SkPictureUtils.h', + '../third_party/skia/include/utils/SkProxyCanvas.h', + ], + 'include_dirs': [ + '..', + 'config', + '../third_party/skia/include/config', + '../third_party/skia/include/core', + '../third_party/skia/include/effects', + '../third_party/skia/include/images', + '../third_party/skia/include/lazy', + '../third_party/skia/include/pathops', + '../third_party/skia/include/pdf', + '../third_party/skia/include/pipe', + '../third_party/skia/include/ports', + '../third_party/skia/include/utils', + '../third_party/skia/src/core', + '../third_party/skia/src/image', + '../third_party/skia/src/ports', + '../third_party/skia/src/sfnt', + '../third_party/skia/src/utils', + '../third_party/skia/src/lazy', + ], + 'conditions': [ + ['skia_support_gpu != 0', { + 'includes': [ + '../third_party/skia/gyp/gpu.gypi', + ], + 'sources': [ + '<@(skgpu_sources)', + ], + 'include_dirs': [ + '../third_party/skia/include/gpu', + '../third_party/skia/src/gpu', + ], + }], + ['skia_support_pdf == 0', { + 'sources/': [ + ['exclude', '../third_party/skia/src/pdf/'] + ], + }], + ['skia_support_pdf == 1', { + 'dependencies': [ + '../third_party/sfntly/sfntly.gyp:sfntly', + ], + }], + + #Settings for text blitting, chosen to approximate the system browser. + [ 'OS == "linux"', { + 'defines': [ + 'SK_GAMMA_EXPONENT=1.2', + 'SK_GAMMA_CONTRAST=0.2', + ], + }], + ['OS == "android"', { + 'defines': [ + 'SK_GAMMA_APPLY_TO_A8', + 'SK_GAMMA_EXPONENT=1.4', + 'SK_GAMMA_CONTRAST=0.0', + ], + }], + ['OS == "win"', { + 'defines': [ + 'SK_GAMMA_SRGB', + 'SK_GAMMA_CONTRAST=0.5', + ], + }], + ['OS == "mac"', { + 'defines': [ + 'SK_GAMMA_SRGB', + 'SK_GAMMA_CONTRAST=0.0', + ], + }], + + # For POSIX platforms, prefer the Mutex implementation provided by Skia + # since it does not generate static initializers. + [ 'OS == "android" or OS == "linux" or OS == "mac" or OS == "ios"', { + 'defines+': [ + 'SK_USE_POSIX_THREADS', + ], + 'direct_dependent_settings': { + 'defines': [ + 'SK_USE_POSIX_THREADS', + ], + }, + }], + + [ 'OS != "android"', { + 'sources!': [ + '../third_party/skia/src/core/SkPaintOptionsAndroid.cpp', + ], + }], + [ 'OS != "ios"', { + 'dependencies': [ + '../third_party/WebKit/public/blink_skia_config.gyp:blink_skia_config', + ], + 'export_dependent_settings': [ + '../third_party/WebKit/public/blink_skia_config.gyp:blink_skia_config', + ], + }], + [ 'OS != "mac"', { + 'sources/': [ + ['exclude', '/mac/'] + ], + }], + [ 'target_arch == "arm" and arm_version >= 7 and arm_neon == 1', { + 'defines': [ + '__ARM_HAVE_NEON', + ], + }], + [ 'target_arch == "arm" and arm_version >= 7 and arm_neon_optional == 1', { + 'defines': [ + '__ARM_HAVE_OPTIONAL_NEON_SUPPORT', + ], + }], + [ 'OS == "android" and target_arch == "arm"', { + 'sources': [ + '../third_party/skia/src/core/SkUtilsArm.cpp', + ], + 'includes': [ + '../build/android/cpufeatures.gypi', + ], + }], + [ 'target_arch == "arm" or target_arch == "mipsel"', { + 'sources!': [ + '../third_party/skia/src/opts/opts_check_SSE2.cpp' + ], + }], + [ 'use_glib == 1', { + 'dependencies': [ + '../build/linux/system.gyp:fontconfig', + '../build/linux/system.gyp:freetype2', + '../build/linux/system.gyp:pangocairo', + '../third_party/icu/icu.gyp:icuuc', + ], + 'cflags': [ + '-Wno-unused', + '-Wno-unused-function', + ], + }], + [ 'use_glib == 0', { + 'sources!': [ + '../third_party/skia/src/ports/SkFontConfigInterface_direct.cpp', + '../third_party/skia/src/fonts/SkFontMgr_fontconfig.cpp', + ], + }], + [ 'use_glib == 0 and OS != "android"', { + 'sources!': [ + '../third_party/skia/src/ports/SkFontHost_FreeType.cpp', + '../third_party/skia/src/ports/SkFontHost_FreeType_common.cpp', + '../third_party/skia/src/ports/SkFontHost_fontconfig.cpp', + + ], + }], + [ 'OS == "android"', { + 'dependencies': [ + '../third_party/expat/expat.gyp:expat', + '../third_party/freetype/freetype.gyp:ft2', + ], + # This exports a hard dependency because it needs to run its + # symlink action in order to expose the skia header files. + 'hard_dependency': 1, + 'include_dirs': [ + '../third_party/expat/files/lib', + ], + }], + [ 'OS == "ios"', { + 'defines': [ + 'SK_BUILD_FOR_IOS', + 'SK_USE_MAC_CORE_TEXT', + ], + 'include_dirs': [ + '../third_party/skia/include/utils/ios', + '../third_party/skia/include/utils/mac', + ], + 'link_settings': { + 'libraries': [ + '$(SDKROOT)/System/Library/Frameworks/ImageIO.framework', + ], + }, + 'sources': [ + # This file is used on both iOS and Mac, so it should be removed + # from the ios and mac conditions and moved into the main sources + # list. + '../third_party/skia/src/utils/mac/SkStream_mac.cpp', + ], + 'sources/': [ + ['exclude', 'opts_check_SSE2\\.cpp$'], + ], + + # The main skia_opts target does not currently work on iOS because the + # target architecture on iOS is determined at compile time rather than + # gyp time (simulator builds are x86, device builds are arm). As a + # temporary measure, this is a separate opts target for iOS-only, using + # the _none.cpp files to avoid architecture-dependent implementations. + 'dependencies': [ + 'skia_library_opts.gyp:skia_opts_none', + ], + 'dependencies!': [ + 'skia_library_opts.gyp:skia_opts', + ], + }], + [ 'OS == "mac"', { + 'defines': [ + 'SK_BUILD_FOR_MAC', + 'SK_USE_MAC_CORE_TEXT', + ], + 'direct_dependent_settings': { + 'include_dirs': [ + '../third_party/skia/include/utils/mac', + ], + }, + 'include_dirs': [ + '../third_party/skia/include/utils/mac', + ], + 'link_settings': { + 'libraries': [ + '$(SDKROOT)/System/Library/Frameworks/AppKit.framework', + ], + }, + 'sources': [ + '../third_party/skia/src/utils/mac/SkStream_mac.cpp', + ], + }], + [ 'OS == "win"', { + 'sources!': [ + '../third_party/skia/src/ports/SkOSFile_posix.cpp', + '../third_party/skia/src/ports/SkThread_pthread.cpp', + '../third_party/skia/src/ports/SkTime_Unix.cpp', + '../third_party/skia/src/ports/SkTLS_pthread.cpp', + ], + }], + # TODO(scottmg): http://crbug.com/177306 + ['clang==1', { + 'xcode_settings': { + 'WARNING_CFLAGS!': [ + # Don't warn about string->bool used in asserts. + '-Wstring-conversion', + ], + }, + 'cflags!': [ + '-Wstring-conversion', + ], + }], + ], + 'target_conditions': [ + # Pull in specific Mac files for iOS (which have been filtered out + # by file name rules). + [ 'OS == "ios"', { + 'sources/': [ + ['include', 'SkFontHost_mac\\.cpp$',], + ['include', 'SkStream_mac\\.cpp$',], + ['include', 'SkCreateCGImageRef\\.cpp$',], + ], + }], + ], + + 'defines': [ + '<@(skia_export_defines)', + + # this flag can be removed entirely once this has baked for a while + 'SK_ALLOW_OVER_32K_BITMAPS', + + # skia uses static initializers to initialize the serialization logic + # of its "pictures" library. This is currently not used in chrome; if + # it ever gets used the processes that use it need to call + # SkGraphics::Init(). + 'SK_ALLOW_STATIC_GLOBAL_INITIALIZERS=0', + + # Disable this check because it is too strict for some Chromium-specific + # subclasses of SkPixelRef. See bug: crbug.com/171776. + 'SK_DISABLE_PIXELREF_LOCKCOUNT_BALANCE_CHECK', + + 'IGNORE_ROT_AA_RECT_OPT', + + 'SKIA_IGNORE_GPU_MIPMAPS', + + 'SK_GDI_ALWAYS_USE_TEXTMETRICS_FOR_FONT_METRICS', + + 'SK_DEFAULT_FONT_CACHE_LIMIT=<(default_font_cache_limit)', + ], + + 'direct_dependent_settings': { + 'include_dirs': [ + #temporary until we can hide SkFontHost + '../third_party/skia/src/core', + + 'config', + '../third_party/skia/include/config', + '../third_party/skia/include/core', + '../third_party/skia/include/effects', + '../third_party/skia/include/pdf', + '../third_party/skia/include/gpu', + '../third_party/skia/include/lazy', + '../third_party/skia/include/pathops', + '../third_party/skia/include/pipe', + '../third_party/skia/include/ports', + '../third_party/skia/include/utils', + ], + 'defines': [ + '<@(skia_export_defines)', + ], + }, +} diff --git a/chromium/skia/skia_library_opts.gyp b/chromium/skia/skia_library_opts.gyp new file mode 100644 index 00000000000..c87b9821bf8 --- /dev/null +++ b/chromium/skia/skia_library_opts.gyp @@ -0,0 +1,242 @@ +# 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. + + +# This gyp file contains the platform-specific optimizations for Skia +{ + 'targets': [ + # Due to an unfortunate intersection of lameness between gcc and gyp, + # we have to build the *_SSE2.cpp files in a separate target. The + # gcc lameness is that, in order to compile SSE2 intrinsics code, it + # must be passed the -msse2 flag. However, with this flag, it may + # emit SSE2 instructions even for scalar code, such as the CPUID + # test used to test for the presence of SSE2. So that, and all other + # code must be compiled *without* -msse2. The gyp lameness is that it + # does not allow file-specific CFLAGS, so we must create this extra + # target for those files to be compiled with -msse2. + # + # This is actually only a problem on 32-bit Linux (all Intel Macs have + # SSE2, Linux x86_64 has SSE2 by definition, and MSC will happily emit + # SSE2 from instrinsics, which generating plain ol' 386 for everything + # else). However, to keep the .gyp file simple and avoid platform-specific + # build breakage, we do this on all platforms. + + # For about the same reason, we need to compile the ARM opts files + # separately as well. + { + 'target_name': 'skia_opts', + 'type': 'static_library', + 'include_dirs': [ + 'config', + '../third_party/skia/include/config', + '../third_party/skia/include/core', + '../third_party/skia/src/core', + '../third_party/skia/src/opts', + ], + 'conditions': [ + [ 'os_posix == 1 and OS != "mac" and OS != "android" and \ + target_arch != "arm" and target_arch != "mipsel"', { + 'cflags': [ + '-msse2', + ], + }], + [ 'target_arch != "arm" and target_arch != "mipsel"', { + 'sources': [ + '../third_party/skia/src/opts/SkBitmapProcState_opts_SSE2.cpp', + '../third_party/skia/src/opts/SkBlitRect_opts_SSE2.cpp', + '../third_party/skia/src/opts/SkBlitRow_opts_SSE2.cpp', + '../third_party/skia/src/opts/SkUtils_opts_SSE2.cpp', + '../third_party/skia/src/opts/SkBitmapFilter_opts_SSE2.cpp', + ], + 'dependencies': [ + 'skia_opts_ssse3', + ], + }], + [ 'target_arch == "arm"', { + 'conditions': [ + [ 'arm_version >= 7 and arm_neon == 1', { + 'defines': [ + '__ARM_HAVE_NEON', + ], + }], + [ 'arm_version >= 7 and arm_neon_optional == 1', { + 'defines': [ + '__ARM_HAVE_OPTIONAL_NEON_SUPPORT', + ], + }], + [ 'arm_version >= 7 and (arm_neon == 1 or arm_neon_optional == 1)', { + 'cflags': [ + # The neon assembly contains conditional instructions which + # aren't enclosed in an IT block. The assembler complains + # without this option. + # See #86592. + '-Wa,-mimplicit-it=always', + ], + 'dependencies': [ + 'skia_opts_neon', + ] + }], + ], + # The assembly uses the frame pointer register (r7 in Thumb/r11 in + # ARM), the compiler doesn't like that. Explicitly remove the + # -fno-omit-frame-pointer flag for Android, as that gets added to all + # targets via common.gypi. + 'cflags!': [ + '-fno-omit-frame-pointer', + '-marm', + '-mapcs-frame', + ], + 'cflags': [ + '-fomit-frame-pointer', + ], + 'sources': [ + '../third_party/skia/src/opts/SkBitmapProcState_opts_arm.cpp', + ], + }], + [ 'target_arch == "arm" and (arm_version < 7 or (arm_neon == 0 and arm_neon_optional == 1))', { + 'sources': [ + '../third_party/skia/src/opts/memset.arm.S', + ], + }], + [ 'target_arch == "arm" and arm_version < 6', { + 'sources': [ + '../third_party/skia/src/opts/SkBlitRow_opts_none.cpp', + '../third_party/skia/src/opts/SkUtils_opts_none.cpp', + ], + }], + [ 'target_arch == "arm" and arm_version >= 6', { + 'sources': [ + '../third_party/skia/src/opts/SkBlitRow_opts_arm.cpp', + '../third_party/skia/src/opts/SkBlitRow_opts_arm.h', + '../third_party/skia/src/opts/opts_check_arm.cpp', + ], + }], + [ 'target_arch == "mipsel"',{ + 'cflags': [ + '-fomit-frame-pointer', + ], + 'sources': [ + '../third_party/skia/src/opts/SkBitmapProcState_opts_none.cpp', + '../third_party/skia/src/opts/SkBlitRow_opts_none.cpp', + '../third_party/skia/src/opts/SkUtils_opts_none.cpp', + ], + }], + ], + }, + # For the same lame reasons as what is done for skia_opts, we have to + # create another target specifically for SSSE3 code as we would not want + # to compile the SSE2 code with -mssse3 which would potentially allow + # gcc to generate SSSE3 code. + { + 'target_name': 'skia_opts_ssse3', + 'type': 'static_library', + 'include_dirs': [ + '..', + 'config', + '../third_party/skia/include/config', + '../third_party/skia/include/core', + '../third_party/skia/src/core', + ], + 'conditions': [ + [ 'OS in ["linux", "freebsd", "openbsd", "solaris", "android"]', { + 'cflags': [ + '-mssse3', + ], + }], + [ 'OS == "mac"', { + 'xcode_settings': { + 'GCC_ENABLE_SUPPLEMENTAL_SSE3_INSTRUCTIONS': 'YES', + }, + }], + [ 'OS == "win"', { + 'include_dirs': [ + 'config/win', + ], + 'direct_dependent_settings': { + 'include_dirs': [ + 'config/win', + ], + }, + }], + [ 'target_arch != "arm" and target_arch != "mipsel"', { + 'sources': [ + '../third_party/skia/src/opts/SkBitmapProcState_opts_SSSE3.cpp', + ], + }], + ], + }, + { + 'target_name': 'skia_opts_none', + 'type': 'static_library', + 'include_dirs': [ + '..', + 'config', + '../third_party/skia/include/config', + '../third_party/skia/include/core', + '../third_party/skia/src/core', + ], + 'sources': [ + '../third_party/skia/src/opts/SkBitmapProcState_opts_none.cpp', + '../third_party/skia/src/opts/SkBlitRow_opts_none.cpp', + '../third_party/skia/src/opts/SkUtils_opts_none.cpp', + ], + }, + ], + 'conditions': [ + # NEON code must be compiled with -mfpu=neon which also affects scalar + # code. To support dynamic NEON code paths, we need to build all + # NEON-specific sources in a separate static library. The situation + # is very similar to the SSSE3 one. + ['target_arch == "arm" and (arm_neon == 1 or arm_neon_optional == 1)', { + 'targets': [ + { + 'target_name': 'skia_opts_neon', + 'type': 'static_library', + 'include_dirs': [ + '..', + 'config', + '../third_party/skia/include/core', + '../third_party/skia/src/core', + '../third_party/skia/src/opts', + ], + 'cflags!': [ + '-fno-omit-frame-pointer', + '-mfpu=vfp', # remove them all, just in case. + '-mfpu=vfpv3', + '-mfpu=vfpv3-d16', + ], + 'cflags': [ + '-mfpu=neon', + '-fomit-frame-pointer', + ], + 'ldflags': [ + '-march=armv7-a', + '-Wl,--fix-cortex-a8', + ], + 'sources': [ + '../third_party/skia/src/opts/memset16_neon.S', + '../third_party/skia/src/opts/memset32_neon.S', + '../third_party/skia/src/opts/SkBitmapProcState_arm_neon.cpp', + '../third_party/skia/src/opts/SkBitmapProcState_matrixProcs_neon.cpp', + '../third_party/skia/src/opts/SkBitmapProcState_matrix_clamp_neon.h', + '../third_party/skia/src/opts/SkBitmapProcState_matrix_repeat_neon.h', + '../third_party/skia/src/opts/SkBlitRow_opts_arm_neon.cpp', + ], + 'conditions': [ + ['arm_neon == 1', { + 'defines': [ + '__ARM_HAVE_NEON', + ], + }], + ['arm_neon_optional == 1', { + 'defines': [ + '__ARM_HAVE_OPTIONAL_NEON_SUPPORT', + ], + }], + ], + }, + ], + }], + ], +} diff --git a/chromium/skia/skia_system.gypi b/chromium/skia/skia_system.gypi new file mode 100644 index 00000000000..b05623c0500 --- /dev/null +++ b/chromium/skia/skia_system.gypi @@ -0,0 +1,24 @@ +# 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. + + +# This gypi file contains the shim header generation and other settings to use +# the system version of skia on Android. +{ + 'direct_dependent_settings': { + # This makes the Android build system set the include path appropriately. + 'libraries': [ '-lskia' ], + }, + 'link_settings': { + # This actually causes the final binary to be linked against skia. + 'libraries': [ '-lskia' ], + }, + 'variables': { + 'headers_root_path': '../third_party/skia/include', + }, + 'includes': [ + '../third_party/skia/gyp/public_headers.gypi', + '../build/shim_headers.gypi', + ], +} diff --git a/chromium/skia/skia_test_expectations.txt b/chromium/skia/skia_test_expectations.txt new file mode 100644 index 00000000000..83f6ac54e64 --- /dev/null +++ b/chromium/skia/skia_test_expectations.txt @@ -0,0 +1,70 @@ +# TEMPORARY overrides of +# src/third_party/WebKit/LayoutTests/platform/chromium/test_expectations.txt +# that are associated with changes to the Skia code. +# +# GUIDELINES: +# - This file should be empty most of the time. +# - Expectations should only be added TEMPORARILY, as a step towards +# rebaselining layout test results. If any one expectation remains in here +# for more than a week or two, then we are probably doing something wrong. +# - Expectations from this file should NOT be rolled into any other +# test_expectations file. If there is a test that we expect to fail +# indefinitely, then we should add that test to the roach motel that is +# src/third_party/WebKit/LayoutTests/platform/chromium/test_expectations.txt +# - Tests listed in this file should NOT be rebaselined by WebKit Gardeners, +# unless they have made arrangements with Skia developers. +# +# For more information, see https://bugs.webkit.org/show_bug.cgi?id=86749 +# or email skia-dev@google.com . +# +# INSTRUCTIONS: +# If you are rolling Skia's DEPS within Chrome, and trybot results indicate +# that the DEPS roll would break some webkit layout_tests, please follow +# these steps: +# +# 1. Confirm that those layout_test failures are "reasonable"-- Are they +# actually improvements, not regressions? Or maybe they are very minor +# differences that go along with a performance improvement? +# If not, please fix Skia rather than rolling in the version that will +# regress the webkit layout_tests. +# +# 2. File a bug to yourself to track the rebaselining of results caused by +# your Skia DEPS roll. +# +# 3. Add one or more lines to this file, in the same syntax used in the main +# test_expectations file, to mark those tests as expected-to-fail. +# Add this file to your DEPS roll CL. +# +# 4. Run your DEPS roll CL through the trybots again, and confirm your CL does +# not cause any layout tests to fail. (If there are still failures as a +# result of your CL, you probably didn't add the test expectations correctly.) +# +# 5. Commit your DEPS roll CL, and keep an eye on the waterfall bots to make +# sure nothing goes red. +# +# 6. Make sure to rebaseline the layout tests as soon as possible! The longer +# we leave overrides in this file, the harder it will be to rebaseline those +# tests (because other rendering changes might creep in). +# +# START OVERRIDES HERE + +# Image scaling difference due to ongoing Skia image resizing changes +crbug.com/263331 virtual/deferred/fast/images/webp-color-profile-lossy.html [ ImageOnlyFailure ] + +# With Skia r10399 Skia's Windows text metrics have been improved +crbug.com/265448 [ Win ] svg/batik/text/textEffect3.svg [ ImageOnlyFailure ] +crbug.com/265448 [ Win ] svg/custom/use-referencing-nonexisting-symbol.svg [ ImageOnlyFailure ] +crbug.com/265448 [ Win ] svg/transforms/animated-path-inside-transformed-html.xhtml [ ImageOnlyFailure ] +crbug.com/265448 [ Win ] svg/transforms/text-with-pattern-inside-transformed-html.xhtml [ ImageOnlyFailure ] +crbug.com/265448 [ Win ] svg/transforms/text-with-pattern-with-svg-transform.svg [ ImageOnlyFailure ] + +# With skia r10444 some imperceptible differences on the edges of blurs were caused +crbug.com/266315 fast/box-shadow/box-shadow-clipped-slices.html [ ImageOnlyFailure ] +crbug.com/266315 fast/repaint/box-shadow-h.html [ ImageOnlyFailure ] +crbug.com/266315 fast/repaint/box-shadow-v.html [ ImageOnlyFailure ] +crbug.com/266315 fast/repaint/shadow-multiple-horizontal.html [ ImageOnlyFailure ] +crbug.com/266315 fast/repaint/shadow-multiple-strict-horizontal.html [ ImageOnlyFailure ] +crbug.com/266315 fast/repaint/shadow-multiple-strict-vertical.html [ ImageOnlyFailure ] +crbug.com/266315 fast/repaint/shadow-multiple-vertical.html [ ImageOnlyFailure ] + +# END OVERRIDES HERE (this line ensures that the file is newline-terminated) diff --git a/chromium/skia/tile_patch.diff b/chromium/skia/tile_patch.diff new file mode 100644 index 00000000000..78118416b47 --- /dev/null +++ b/chromium/skia/tile_patch.diff @@ -0,0 +1,284 @@ +Index: sgl/SkBitmapProcState.h
+===================================================================
+--- sgl/SkBitmapProcState.h (revision 42716)
++++ sgl/SkBitmapProcState.h (working copy)
+@@ -39,8 +39,9 @@
+ int count, + uint16_t colors[]); + +- typedef U16CPU (*FixedTileProc)(SkFixed); // returns 0..0xFFFF +- ++ typedef SkFixed (*FixedTileProc)(SkFixed, int); ++ typedef int (*IntTileProc)(int, int); ++ + MatrixProc fMatrixProc; // chooseProcs + SampleProc32 fSampleProc32; // chooseProcs + SampleProc16 fSampleProc16; // chooseProcs +@@ -48,6 +49,8 @@
+ SkMatrix fUnitInvMatrix; // chooseProcs + FixedTileProc fTileProcX; // chooseProcs + FixedTileProc fTileProcY; // chooseProcs ++ IntTileProc iTileProcX; // chooseProcs ++ IntTileProc iTileProcY; // chooseProcs + SkFixed fFilterOneX; + SkFixed fFilterOneY; + +Index: sgl/SkBitmapProcState.cpp
+===================================================================
+--- sgl/SkBitmapProcState.cpp (revision 42716)
++++ sgl/SkBitmapProcState.cpp (working copy)
+@@ -296,8 +296,9 @@
+ } + const SkMatrix* m; + +- if (SkShader::kClamp_TileMode == fTileModeX && +- SkShader::kClamp_TileMode == fTileModeY) { ++ if (inv.getType() <= SkMatrix::kTranslate_Mask || ++ (SkShader::kClamp_TileMode == fTileModeX && ++ SkShader::kClamp_TileMode == fTileModeY)) { + m = &inv; + } else { + fUnitInvMatrix = inv; +@@ -330,6 +331,16 @@
+ fInvMatrix = m; + fInvProc = m->getMapXYProc(); + fInvType = m->getType(); ++ if (fInvType <= SkMatrix::kTranslate_Mask && ++ inv.getType() > SkMatrix::kTranslate_Mask) { ++ SkASSERT(inv.getType() <= ++ (SkMatrix::kTranslate_Mask | SkMatrix::kScale_Mask)); ++ // It is possible that by the calculation of fUnitInvMatrix, we have ++ // eliminated the scale transformation of the matrix (e.g., if inv^(-1) ++ // scales fOrigBitmap into an 1X1 rect). We add the scale flag back so ++ // that we don't make wrong choice in chooseMatrixProc(). ++ fInvType |= SkMatrix::kScale_Mask; ++ } + fInvSx = SkScalarToFixed(m->getScaleX()); + fInvSy = SkScalarToFixed(m->getScaleY()); + fInvKy = SkScalarToFixed(m->getSkewY()); +Index: sgl/SkBitmapProcState_matrix.h
+===================================================================
+--- sgl/SkBitmapProcState_matrix.h (revision 42716)
++++ sgl/SkBitmapProcState_matrix.h (working copy)
+@@ -1,4 +1,5 @@
+ ++#define TRANSLATE_NOFILTER_NAME MAKENAME(_nofilter_translate) + #define SCALE_NOFILTER_NAME MAKENAME(_nofilter_scale) + #define SCALE_FILTER_NAME MAKENAME(_filter_scale) + #define AFFINE_NOFILTER_NAME MAKENAME(_nofilter_affine) +@@ -17,6 +18,38 @@
+ #define PREAMBLE_ARG_Y + #endif + ++#ifndef PREAMBLE_TRANS ++ #define PREAMBLE_TRANS(state) ++#endif ++ ++static void TRANSLATE_NOFILTER_NAME(const SkBitmapProcState& s, ++ uint32_t xy[], int count, int x, int y) ++{ ++ SkASSERT((s.fInvType & ~SkMatrix::kTranslate_Mask) == 0); ++ ++ PREAMBLE_TRANS(s); ++ ++ x += SkScalarFloor(s.fInvMatrix->getTranslateX()); ++ y += SkScalarFloor(s.fInvMatrix->getTranslateY()); ++ ++ *xy++ = (uint32_t)TILEY_TRANS(y, (s.fBitmap->height() - 1)); ++ ++ int maxX = s.fBitmap->width() - 1; ++ int i; ++ uint16_t* xx = (uint16_t*)xy; ++ for (i = (count >> 2); i > 0; --i) ++ { ++ *xx++ = (uint16_t)TILEX_TRANS(x, maxX); x++; ++ *xx++ = (uint16_t)TILEX_TRANS(x, maxX); x++; ++ *xx++ = (uint16_t)TILEX_TRANS(x, maxX); x++; ++ *xx++ = (uint16_t)TILEX_TRANS(x, maxX); x++; ++ } ++ for (i = (count & 3); i > 0; --i) ++ { ++ *xx++ = (uint16_t)TILEX_TRANS(x, maxX); x++; ++ } ++} ++ + static void SCALE_NOFILTER_NAME(const SkBitmapProcState& s, + uint32_t xy[], int count, int x, int y) { + SkASSERT((s.fInvType & ~(SkMatrix::kTranslate_Mask | +@@ -206,9 +239,9 @@
+ unsigned maxY = s.fBitmap->height() - 1; + + do { +- *xy++ = PACK_FILTER_Y_NAME(fy, maxY, oneX PREAMBLE_ARG_Y); ++ *xy++ = PACK_FILTER_Y_NAME(fy, maxY, oneY PREAMBLE_ARG_Y); + fy += dy; +- *xy++ = PACK_FILTER_X_NAME(fx, maxX, oneY PREAMBLE_ARG_X); ++ *xy++ = PACK_FILTER_X_NAME(fx, maxX, oneX PREAMBLE_ARG_X); + fx += dx; + } while (--count != 0); + } +@@ -241,6 +274,9 @@
+ } + + static SkBitmapProcState::MatrixProc MAKENAME(_Procs)[] = { ++ TRANSLATE_NOFILTER_NAME, ++ TRANSLATE_NOFILTER_NAME, // No need to do filtering if the matrix is no ++ // more complex than identity/translate. + SCALE_NOFILTER_NAME, + SCALE_FILTER_NAME, + AFFINE_NOFILTER_NAME, +@@ -255,7 +291,10 @@
+ #ifdef CHECK_FOR_DECAL + #undef CHECK_FOR_DECAL + #endif +- ++#undef TILEX_TRANS ++#undef TILEY_TRANS ++ ++#undef TRANSLATE_NOFILTER_NAME + #undef SCALE_NOFILTER_NAME + #undef SCALE_FILTER_NAME + #undef AFFINE_NOFILTER_NAME +@@ -268,6 +307,7 @@
+ #undef PREAMBLE_PARAM_Y + #undef PREAMBLE_ARG_X + #undef PREAMBLE_ARG_Y ++#undef PREAMBLE_TRANS + + #undef TILEX_LOW_BITS + #undef TILEY_LOW_BITS +Index: sgl/SkBitmapProcState_matrixProcs.cpp
+===================================================================
+--- sgl/SkBitmapProcState_matrixProcs.cpp (revision 42716)
++++ sgl/SkBitmapProcState_matrixProcs.cpp (working copy)
+@@ -28,6 +28,8 @@
+ #define TILEX_LOW_BITS(fx, max) (((fx) >> 12) & 0xF) + #define TILEY_LOW_BITS(fy, max) (((fy) >> 12) & 0xF) + #define CHECK_FOR_DECAL ++#define TILEX_TRANS(x, max) SkClampMax(x, max) ++#define TILEY_TRANS(y, max) SkClampMax(y, max) + #include "SkBitmapProcState_matrix.h" + + #define MAKENAME(suffix) RepeatX_RepeatY ## suffix +@@ -35,6 +37,9 @@
+ #define TILEY_PROCF(fy, max) (((fy) & 0xFFFF) * ((max) + 1) >> 16) + #define TILEX_LOW_BITS(fx, max) ((((fx) & 0xFFFF) * ((max) + 1) >> 12) & 0xF) + #define TILEY_LOW_BITS(fy, max) ((((fy) & 0xFFFF) * ((max) + 1) >> 12) & 0xF) ++#define REAL_MOD(val, modulus) (((val)%(modulus)) + (modulus)*( (val)<0 )) ++#define TILEX_TRANS(x, max) (REAL_MOD((x), ((max) + 1))) ++#define TILEY_TRANS(y, max) (REAL_MOD((y), ((max) + 1))) + #include "SkBitmapProcState_matrix.h" + + #define MAKENAME(suffix) GeneralXY ## suffix +@@ -44,13 +49,17 @@
+ #define PREAMBLE_PARAM_Y , SkBitmapProcState::FixedTileProc tileProcY + #define PREAMBLE_ARG_X , tileProcX + #define PREAMBLE_ARG_Y , tileProcY +-#define TILEX_PROCF(fx, max) (tileProcX(fx) * ((max) + 1) >> 16) +-#define TILEY_PROCF(fy, max) (tileProcY(fy) * ((max) + 1) >> 16) +-#define TILEX_LOW_BITS(fx, max) ((tileProcX(fx) * ((max) + 1) >> 12) & 0xF) +-#define TILEY_LOW_BITS(fy, max) ((tileProcY(fy) * ((max) + 1) >> 12) & 0xF) ++#define TILEX_PROCF(fx, max) (tileProcX(fx, max) >> 16) ++#define TILEY_PROCF(fy, max) (tileProcY(fy, max) >> 16) ++#define TILEX_LOW_BITS(fx, max) ((tileProcX(fx, max) >> 14) & 0x3) ++#define TILEY_LOW_BITS(fy, max) ((tileProcY(fy, max) >> 14) & 0x3) ++#define PREAMBLE_TRANS(state) SkBitmapProcState::IntTileProc tileProcX = (state).iTileProcX; \ ++ SkBitmapProcState::IntTileProc tileProcY = (state).iTileProcY ++#define TILEX_TRANS(x, max) tileProcX(x, max) ++#define TILEY_TRANS(y, max) tileProcY(y, max) + #include "SkBitmapProcState_matrix.h" + +-static inline U16CPU fixed_clamp(SkFixed x) ++static inline SkFixed fixed_clamp(SkFixed x, int max) + { + #ifdef SK_CPU_HAS_CONDITIONAL_INSTR + if (x >> 16) +@@ -66,19 +75,20 @@
+ x = 0xFFFF; + } + #endif +- return x; ++ return x * (max + 1); + } + +-static inline U16CPU fixed_repeat(SkFixed x) ++static inline SkFixed fixed_repeat(SkFixed x, int max) + { +- return x & 0xFFFF; ++ return (x & 0xFFFF) * (max + 1); + } + +-static inline U16CPU fixed_mirror(SkFixed x) ++static inline SkFixed fixed_mirror(SkFixed x, int max) + { + SkFixed s = x << 15 >> 31; + // s is FFFFFFFF if we're on an odd interval, or 0 if an even interval +- return (x ^ s) & 0xFFFF; ++ x = ((x ^ s) & 0xFFFF) * (max + 1); ++ return s ? (x ^ 0xFFFF) : x; + } + + static SkBitmapProcState::FixedTileProc choose_tile_proc(unsigned m) +@@ -90,15 +100,52 @@
+ SkASSERT(SkShader::kMirror_TileMode == m); + return fixed_mirror; + } ++ ++static inline int int_clamp(int x, int max) ++{ ++ SkASSERT(max >= 0); ++ ++ return SkClampMax(x, max); ++} + ++static inline int int_repeat(int x, int max) ++{ ++ SkASSERT(max >= 0); ++ ++ return x % (max + 1); ++} ++ ++static inline int int_mirror(int x, int max) ++{ ++ SkASSERT(max >= 0); ++ ++ int dx = x % (max + 1); ++ if (dx < 0) ++ dx = -dx - 1; ++ ++ return (x / (max + 1) % 2) ? max - dx : dx; ++} ++ ++static SkBitmapProcState::IntTileProc choose_int_tile_proc(unsigned m) ++{ ++ if (SkShader::kClamp_TileMode == m) ++ return int_clamp; ++ if (SkShader::kRepeat_TileMode == m) ++ return int_repeat; ++ SkASSERT(SkShader::kMirror_TileMode == m); ++ return int_mirror; ++} ++ + SkBitmapProcState::MatrixProc SkBitmapProcState::chooseMatrixProc() + { + int index = 0; + if (fDoFilter) + index = 1; + if (fInvType & SkMatrix::kPerspective_Mask) ++ index |= 6; ++ else if (fInvType & SkMatrix::kAffine_Mask) + index |= 4; +- else if (fInvType & SkMatrix::kAffine_Mask) ++ else if (fInvType & SkMatrix::kScale_Mask) + index |= 2; + + if (SkShader::kClamp_TileMode == fTileModeX && +@@ -123,6 +170,8 @@
+ // only general needs these procs + fTileProcX = choose_tile_proc(fTileModeX); + fTileProcY = choose_tile_proc(fTileModeY); ++ iTileProcX = choose_int_tile_proc(fTileModeX); ++ iTileProcY = choose_int_tile_proc(fTileModeY); + return GeneralXY_Procs[index]; + } + |