diff options
author | Torne (Richard Coles) <torne@google.com> | 2012-11-14 11:43:16 +0000 |
---|---|---|
committer | Torne (Richard Coles) <torne@google.com> | 2012-11-14 11:43:16 +0000 |
commit | 5821806d5e7f356e8fa4b058a389a808ea183019 (patch) | |
tree | e19f4793aac92e2c0d9a01087019a60d6657d838 /skia | |
parent | 8e79a8efe247f109aafd917a69e8a392961b3687 (diff) | |
download | chromium_org-5821806d5e7f356e8fa4b058a389a808ea183019.tar.gz |
Merge from Chromium at DEPS revision r167172
This commit was generated by merge_to_master.py.
Change-Id: Ib8d56fd5ae39a2d7e8c91dcd76cc6d13f25f2aab
Diffstat (limited to 'skia')
148 files changed, 12654 insertions, 0 deletions
diff --git a/skia/OWNERS b/skia/OWNERS new file mode 100644 index 0000000000..e4b8e1d2b4 --- /dev/null +++ b/skia/OWNERS @@ -0,0 +1,6 @@ +alokp@chromium.org +reed@google.com +vandebo@chromium.org +senorblanco@chromium.org +thakis@chromium.org +twiz@chromium.org diff --git a/skia/README.chromium b/skia/README.chromium new file mode 100644 index 0000000000..76a92840d7 --- /dev/null +++ b/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/skia/config/SkUserConfig.h b/skia/config/SkUserConfig.h new file mode 100644 index 0000000000..0b3f3c7f73 --- /dev/null +++ b/skia/config/SkUserConfig.h @@ -0,0 +1,252 @@ +/* + * 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> +#if defined(USE_SYSTEM_ZLIB) +#define SK_ZLIB_INCLUDE <zlib.h> +#else +#define SK_ZLIB_INCLUDE "third_party/zlib/zlib.h" +#endif + +/* 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 + +/* If this is not defined, skia dithers gradients. Turning this on will make + gradients look better, but might have a performance impact. When it's turned + on, several webkit pixel tests will need to be rebaselined, too. + http://crbug.com/41756 + */ +#define SK_DISABLE_DITHER_32BIT_GRADIENT + +// ===== Begin Chrome-specific definitions ===== + +#define SK_SCALAR_IS_FLOAT +#undef SK_SCALAR_IS_FIXED + +#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/skia/ext/SkFontHost_fontconfig.cpp b/skia/ext/SkFontHost_fontconfig.cpp new file mode 100644 index 0000000000..757672fe1f --- /dev/null +++ b/skia/ext/SkFontHost_fontconfig.cpp @@ -0,0 +1,369 @@ +/* libs/graphics/ports/SkFontHost_fontconfig.cpp +** +** Copyright 2008, Google Inc. +** +** 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. +*/ + +// ----------------------------------------------------------------------------- +// This file provides implementations of the font resolution members of +// SkFontHost by using the fontconfig[1] library. Fontconfig is usually found +// on Linux systems and handles configuration, parsing and caching issues +// involved with enumerating and matching fonts. +// +// [1] http://fontconfig.org +// ----------------------------------------------------------------------------- + +#include <map> +#include <string> + +#include <sys/mman.h> +#include <sys/stat.h> +#include <unistd.h> + +#include "base/compiler_specific.h" +#include "third_party/skia/src/ports/SkFontDescriptor.h" +#include "SkFontHost.h" +#include "SkStream.h" +#include "SkFontHost_fontconfig_control.h" +#include "SkFontHost_fontconfig_impl.h" +#include "SkFontHost_fontconfig_direct.h" + +static FontConfigInterface* global_fc_impl = NULL; + +void SkiaFontConfigUseDirectImplementation() { + if (global_fc_impl) + delete global_fc_impl; + global_fc_impl = new FontConfigDirect; +} + +void SkiaFontConfigSetImplementation(FontConfigInterface* font_config) { + if (global_fc_impl) + delete global_fc_impl; + global_fc_impl = font_config; +} + +static FontConfigInterface* GetFcImpl() { + if (!global_fc_impl) + global_fc_impl = new FontConfigDirect; + return global_fc_impl; +} + +SK_DECLARE_STATIC_MUTEX(global_remote_font_map_lock); +static std::map<uint32_t, std::pair<uint8_t*, size_t> >* global_remote_fonts; + +// Initialize the map declared above. Note that its corresponding mutex must be +// locked before calling this function. +static void AllocateGlobalRemoteFontsMapOnce() { + if (!global_remote_fonts) { + global_remote_fonts = + new std::map<uint32_t, std::pair<uint8_t*, size_t> >(); + } +} + +static unsigned global_next_remote_font_id; + +// This is the maximum size of the font cache. +static const unsigned kFontCacheMemoryBudget = 2 * 1024 * 1024; // 2MB + +// UniqueIds are encoded as (filefaceid << 8) | style +// For system fonts, filefaceid = (fileid << 4) | face_index. +// For remote fonts, filefaceid = fileid. + +static unsigned UniqueIdToFileFaceId(unsigned uniqueid) +{ + return uniqueid >> 8; +} + +static SkTypeface::Style UniqueIdToStyle(unsigned uniqueid) +{ + return static_cast<SkTypeface::Style>(uniqueid & 0xff); +} + +static unsigned FileFaceIdAndStyleToUniqueId(unsigned filefaceid, + SkTypeface::Style style) +{ + SkASSERT((style & 0xff) == style); + return (filefaceid << 8) | static_cast<int>(style); +} + +static const unsigned kRemoteFontMask = 0x00800000u; + +static bool IsRemoteFont(unsigned filefaceid) +{ + return filefaceid & kRemoteFontMask; +} + +class FontConfigTypeface : public SkTypeface { +public: + FontConfigTypeface(Style style, uint32_t id) + : SkTypeface(style, id) + { } + + virtual ~FontConfigTypeface() + { + const uint32_t id = uniqueID(); + if (IsRemoteFont(UniqueIdToFileFaceId(id))) { + SkAutoMutexAcquire ac(global_remote_font_map_lock); + AllocateGlobalRemoteFontsMapOnce(); + std::map<uint32_t, std::pair<uint8_t*, size_t> >::iterator iter + = global_remote_fonts->find(id); + if (iter != global_remote_fonts->end()) { + sk_free(iter->second.first); // remove the font on memory. + global_remote_fonts->erase(iter); + } + } + } +}; + +// static +SkTypeface* SkFontHost::CreateTypeface(const SkTypeface* familyFace, + const char familyName[], + SkTypeface::Style style) +{ + std::string resolved_family_name; + + if (familyFace) { + // Given the fileid we can ask fontconfig for the familyname of the + // font. + const unsigned filefaceid = UniqueIdToFileFaceId(familyFace->uniqueID()); + if (!GetFcImpl()->Match(&resolved_family_name, NULL, + true /* filefaceid valid */, filefaceid, "", + NULL, 0, NULL, NULL)) { + return NULL; + } + } else if (familyName) { + resolved_family_name = familyName; + } + + bool bold = style & SkTypeface::kBold; + bool italic = style & SkTypeface::kItalic; + unsigned filefaceid; + if (!GetFcImpl()->Match(NULL, &filefaceid, + false, -1, /* no filefaceid */ + resolved_family_name, NULL, 0, + &bold, &italic)) { + return NULL; + } + const SkTypeface::Style resulting_style = static_cast<SkTypeface::Style>( + (bold ? SkTypeface::kBold : 0) | + (italic ? SkTypeface::kItalic : 0)); + + const unsigned id = FileFaceIdAndStyleToUniqueId(filefaceid, + resulting_style); + SkTypeface* typeface = SkNEW_ARGS(FontConfigTypeface, (resulting_style, id)); + return typeface; +} + +// static +SkTypeface* SkFontHost::CreateTypefaceFromStream(SkStream* stream) +{ + if (!stream) + return NULL; + + const size_t length = stream->read(0, 0); + if (!length) + return NULL; + if (length >= 1024 * 1024 * 1024) + return NULL; // don't accept too large fonts (>= 1GB) for safety. + + uint8_t* font = (uint8_t*)sk_malloc_throw(length); + if (stream->read(font, length) != length) { + sk_free(font); + return NULL; + } + + SkTypeface::Style style = static_cast<SkTypeface::Style>(0); + unsigned id = 0; + { + SkAutoMutexAcquire ac(global_remote_font_map_lock); + AllocateGlobalRemoteFontsMapOnce(); + id = FileFaceIdAndStyleToUniqueId( + global_next_remote_font_id | kRemoteFontMask, style); + + if (++global_next_remote_font_id >= kRemoteFontMask) + global_next_remote_font_id = 0; + + if (!global_remote_fonts->insert( + std::make_pair(id, std::make_pair(font, length))).second) { + sk_free(font); + return NULL; + } + } + + SkTypeface* typeface = SkNEW_ARGS(FontConfigTypeface, (style, id)); + return typeface; +} + +// static +SkTypeface* SkFontHost::CreateTypefaceFromFile(const char path[]) +{ + SkASSERT(!"SkFontHost::CreateTypefaceFromFile unimplemented"); + return NULL; +} + +uint32_t SkFontHost::NextLogicalFont(SkFontID curr, SkFontID orig) { + // We don't handle font fallback, WebKit does. + return 0; +} + +/////////////////////////////////////////////////////////////////////////////// + +// Serialize, Deserialize need to be compatible across platforms, hence the use +// of SkFontDescriptor. + +void SkFontHost::Serialize(const SkTypeface* face, SkWStream* stream) { + SkFontDescriptor desc(face->style()); + + std::string resolved_family_name; + + const unsigned filefaceid = UniqueIdToFileFaceId(face->uniqueID()); + if (GetFcImpl()->Match(&resolved_family_name, NULL, + true /* filefaceid valid */, filefaceid, "", NULL, 0, NULL, NULL)) + desc.setFamilyName(resolved_family_name.c_str()); + else + desc.setFamilyName("sans-serif"); + + // would also like other names (see SkFontDescriptor.h) + + desc.serialize(stream); + + // by convention, we also write out the actual sfnt data, preceeded by + // a packed-length. For now we skip that, so we just write the zero. + stream->writePackedUInt(0); +} + +SkTypeface* SkFontHost::Deserialize(SkStream* stream) { + SkFontDescriptor desc(stream); + + // by convention, Serialize will have also written the actual sfnt data. + // for now, we just want to skip it. + size_t size = stream->readPackedUInt(); + stream->skip(size); + + return SkFontHost::CreateTypeface(NULL, desc.getFamilyName(), + desc.getStyle()); +} + +/////////////////////////////////////////////////////////////////////////////// + +class SkFileDescriptorStream : public SkStream { + public: + SkFileDescriptorStream(int fd) { + memory_ = NULL; + offset_ = 0; + + // this ensures that if we fail in the constructor, we will safely + // ignore all subsequent calls to read() because we will always trim + // the requested size down to 0 + length_ = 0; + + struct stat st; + if (fstat(fd, &st)) + return; + + void* memory = mmap(NULL, st.st_size, PROT_READ, MAP_SHARED, fd, 0); + close(fd); + if (memory == MAP_FAILED) + return; + + memory_ = reinterpret_cast<uint8_t*>(memory); + length_ = st.st_size; + } + + virtual ~SkFileDescriptorStream() { + munmap(const_cast<uint8_t*>(memory_), length_); + } + + virtual bool rewind() OVERRIDE { + offset_ = 0; + return true; + } + + // SkStream implementation. + virtual size_t read(void* buffer, size_t size) OVERRIDE { + if (!buffer && !size) { + // This is request for the length of the stream. + return length_; + } + + size_t remaining = length_ - offset_; + if (size > remaining) + size = remaining; + if (buffer) + memcpy(buffer, memory_ + offset_, size); + + offset_ += size; + return size; + } + + virtual const void* getMemoryBase() OVERRIDE { + return memory_; + } + + private: + const uint8_t* memory_; + size_t offset_, length_; +}; + +/////////////////////////////////////////////////////////////////////////////// + +// static +SkStream* SkFontHost::OpenStream(uint32_t id) +{ + const unsigned filefaceid = UniqueIdToFileFaceId(id); + + if (IsRemoteFont(filefaceid)) { + // remote font + SkAutoMutexAcquire ac(global_remote_font_map_lock); + AllocateGlobalRemoteFontsMapOnce(); + std::map<uint32_t, std::pair<uint8_t*, size_t> >::const_iterator iter + = global_remote_fonts->find(id); + if (iter == global_remote_fonts->end()) + return NULL; + return SkNEW_ARGS( + SkMemoryStream, (iter->second.first, iter->second.second)); + } + + // system font + const int fd = GetFcImpl()->Open(filefaceid); + if (fd < 0) + return NULL; + + return SkNEW_ARGS(SkFileDescriptorStream, (fd)); +} + +// static +size_t SkFontHost::GetFileName(SkFontID fontID, char path[], size_t length, + int32_t* index) { + const unsigned filefaceid = UniqueIdToFileFaceId(fontID); + + if (IsRemoteFont(filefaceid)) + return 0; + + if (index) { + *index = filefaceid & 0xfu; + // 1 is a bogus return value. + // We had better change the signature of this function in Skia + // to return bool to indicate success/failure and have another + // out param for fileName length. + if (!path) + return 1; + } + + if (path) + SkASSERT(!"SkFontHost::GetFileName does not support the font path " + "retrieval."); + + return 0; +} diff --git a/skia/ext/SkFontHost_fontconfig_control.h b/skia/ext/SkFontHost_fontconfig_control.h new file mode 100644 index 0000000000..336634b012 --- /dev/null +++ b/skia/ext/SkFontHost_fontconfig_control.h @@ -0,0 +1,38 @@ +/* libs/graphics/ports/SkFontHost_fontconfig_control.h +** +** Copyright 2009, Google Inc. +** +** 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 FontConfigControl_DEFINED +#define FontConfigControl_DEFINED + +#include "SkPreConfig.h" + +class FontConfigInterface; + +// Sets the Skia FontHost to use the direct (non-IPC, requires filesystem +// access) FontConfig implementation. Any previously-set FontConfigInterface +// will be freed. +SK_API void SkiaFontConfigUseDirectImplementation(); + +// Sets the Skia FontHost to use the given implementation of FontConfig. This +// is normally used to configure the IPC-based implementation to get out of +// the sandbox. +// +// Ownership of the given pointer is transferred, and any previously-set +// FontConfigInterface will be freed. +SK_API void SkiaFontConfigSetImplementation(FontConfigInterface* font_config); + +#endif // FontConfigControl_DEFINED diff --git a/skia/ext/SkFontHost_fontconfig_direct.cpp b/skia/ext/SkFontHost_fontconfig_direct.cpp new file mode 100644 index 0000000000..fd0e24353a --- /dev/null +++ b/skia/ext/SkFontHost_fontconfig_direct.cpp @@ -0,0 +1,478 @@ +/* libs/graphics/ports/SkFontHost_fontconfig_direct.cpp +** +** Copyright 2009, Google Inc. +** +** 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. +*/ + +#include "skia/ext/SkFontHost_fontconfig_direct.h" + +#include <unistd.h> +#include <fcntl.h> + +#include <fontconfig/fontconfig.h> + +#include "third_party/skia/include/core/SkTypeface.h" +#include "third_party/skia/include/core/SkUtils.h" + +namespace { + +// Equivalence classes, used to match the Liberation and other fonts +// with their metric-compatible replacements. See the discussion in +// GetFontEquivClass(). +enum FontEquivClass +{ + OTHER, + SANS, + SERIF, + MONO, + SYMBOL, + PGOTHIC, + GOTHIC, + PMINCHO, + MINCHO, + SIMSUN, + NSIMSUN, + SIMHEI, +}; + +// Match the font name against a whilelist of fonts, returning the equivalence +// class. +FontEquivClass GetFontEquivClass(const char* fontname) +{ + // It would be nice for fontconfig to tell us whether a given suggested + // replacement is a "strong" match (that is, an equivalent font) or + // a "weak" match (that is, fontconfig's next-best attempt at finding a + // substitute). However, I played around with the fontconfig API for + // a good few hours and could not make it reveal this information. + // + // So instead, we hardcode. Initially this function emulated + // /etc/fonts/conf.d/30-metric-aliases.conf + // from my Ubuntu system, but we're better off being very conservative. + + // Arimo, Tinos and Cousine are a set of fonts metric-compatible with + // Arial, Times New Roman and Courier New with a character repertoire + // much larger than Liberation. Note that Cousine is metrically + // compatible with Courier New, but the former is sans-serif while + // the latter is serif. + + + struct FontEquivMap { + FontEquivClass clazz; + const char name[40]; + }; + + static const FontEquivMap kFontEquivMap[] = { + { SANS, "Arial" }, + { SANS, "Arimo" }, + { SANS, "Liberation Sans" }, + + { SERIF, "Times New Roman" }, + { SERIF, "Tinos" }, + { SERIF, "Liberation Serif" }, + + { MONO, "Courier New" }, + { MONO, "Cousine" }, + { MONO, "Liberation Mono" }, + + { SYMBOL, "Symbol" }, + { SYMBOL, "Symbol Neu" }, + + // MS Pゴシック + { PGOTHIC, "MS PGothic" }, + { PGOTHIC, "\xef\xbc\xad\xef\xbc\xb3 \xef\xbc\xb0" + "\xe3\x82\xb4\xe3\x82\xb7\xe3\x83\x83\xe3\x82\xaf" }, + { PGOTHIC, "IPAPGothic" }, + { PGOTHIC, "MotoyaG04Gothic" }, + + // MS ゴシック + { GOTHIC, "MS Gothic" }, + { GOTHIC, "\xef\xbc\xad\xef\xbc\xb3 " + "\xe3\x82\xb4\xe3\x82\xb7\xe3\x83\x83\xe3\x82\xaf" }, + { GOTHIC, "IPAGothic" }, + { GOTHIC, "MotoyaG04GothicMono" }, + + // MS P明朝 + { PMINCHO, "MS PMincho" }, + { PMINCHO, "\xef\xbc\xad\xef\xbc\xb3 \xef\xbc\xb0" + "\xe6\x98\x8e\xe6\x9c\x9d"}, + { PMINCHO, "IPAPMincho" }, + { PMINCHO, "MotoyaG04Mincho" }, + + // MS 明朝 + { MINCHO, "MS Mincho" }, + { MINCHO, "\xef\xbc\xad\xef\xbc\xb3 \xe6\x98\x8e\xe6\x9c\x9d" }, + { MINCHO, "IPAMincho" }, + { MINCHO, "MotoyaG04MinchoMono" }, + + // 宋体 + { SIMSUN, "Simsun" }, + { SIMSUN, "\xe5\xae\x8b\xe4\xbd\x93" }, + { SIMSUN, "Song ASC" }, + + // 新宋体 + { NSIMSUN, "NSimsun" }, + { NSIMSUN, "\xe6\x96\xb0\xe5\xae\x8b\xe4\xbd\x93" }, + { NSIMSUN, "N Song ASC" }, + + // 黑体 + { SIMHEI, "Simhei" }, + { SIMHEI, "\xe9\xbb\x91\xe4\xbd\x93" }, + { SIMHEI, "MYingHeiGB18030" }, + { SIMHEI, "MYingHeiB5HK" }, + }; + + static const size_t kFontCount = + sizeof(kFontEquivMap)/sizeof(kFontEquivMap[0]); + + // TODO(jungshik): If this loop turns out to be hot, turn + // the array to a static (hash)map to speed it up. + for (size_t i = 0; i < kFontCount; ++i) { + if (strcasecmp(kFontEquivMap[i].name, fontname) == 0) + return kFontEquivMap[i].clazz; + } + return OTHER; +} + + +// Return true if |font_a| and |font_b| are visually and at the metrics +// level interchangeable. +bool IsMetricCompatibleReplacement(const char* font_a, const char* font_b) +{ + FontEquivClass class_a = GetFontEquivClass(font_a); + FontEquivClass class_b = GetFontEquivClass(font_b); + + return class_a != OTHER && class_a == class_b; +} + +inline unsigned FileFaceIdToFileId(unsigned filefaceid) +{ + return filefaceid >> 4; +} + +inline unsigned FileIdAndFaceIndexToFileFaceId(unsigned fileid, int face_index) +{ + SkASSERT((face_index & 0xfu) == face_index); + return (fileid << 4) | face_index; +} + +// Normally we only return exactly the font asked for. In last-resort +// cases, the request either doesn't specify a font or is one of the +// basic font names like "Sans", "Serif" or "Monospace". This function +// tells you whether a given request is for such a fallback. +bool IsFallbackFontAllowed(const std::string& family) { + const char* family_cstr = family.c_str(); + return family.empty() || + strcasecmp(family_cstr, "sans") == 0 || + strcasecmp(family_cstr, "serif") == 0 || + strcasecmp(family_cstr, "monospace") == 0; +} + +// Find matching font from |font_set| for the given font family. +FcPattern* MatchFont(FcFontSet* font_set, + FcChar8* post_config_family, + const std::string& family) { + // Older versions of fontconfig have a bug where they cannot select + // only scalable fonts so we have to manually filter the results. + FcPattern* match = NULL; + for (int i = 0; i < font_set->nfont; ++i) { + FcPattern* current = font_set->fonts[i]; + FcBool is_scalable; + + if (FcPatternGetBool(current, FC_SCALABLE, 0, + &is_scalable) != FcResultMatch || + !is_scalable) { + continue; + } + + // fontconfig can also return fonts which are unreadable + FcChar8* c_filename; + if (FcPatternGetString(current, FC_FILE, 0, &c_filename) != FcResultMatch) + continue; + + if (access(reinterpret_cast<char*>(c_filename), R_OK) != 0) + continue; + + match = current; + break; + } + + if (match && !IsFallbackFontAllowed(family)) { + bool acceptable_substitute = false; + for (int id = 0; id < 255; ++id) { + FcChar8* post_match_family; + if (FcPatternGetString(match, FC_FAMILY, id, &post_match_family) != + FcResultMatch) + break; + acceptable_substitute = + (strcasecmp(reinterpret_cast<char*>(post_config_family), + reinterpret_cast<char*>(post_match_family)) == 0 || + // Workaround for Issue 12530: + // requested family: "Bitstream Vera Sans" + // post_config_family: "Arial" + // post_match_family: "Bitstream Vera Sans" + // -> We should treat this case as a good match. + strcasecmp(family.c_str(), + reinterpret_cast<char*>(post_match_family)) == 0) || + IsMetricCompatibleReplacement(family.c_str(), + reinterpret_cast<char*>(post_match_family)); + if (acceptable_substitute) + break; + } + if (!acceptable_substitute) + return NULL; + } + + return match; +} + +// Retrieves |is_bold|, |is_italic| and |font_family| properties from |font|. +bool GetFontProperties(FcPattern* font, + std::string* font_family, + bool* is_bold, + bool* is_italic) { + FcChar8* c_family; + if (FcPatternGetString(font, FC_FAMILY, 0, &c_family)) + return false; + + int resulting_bold; + if (FcPatternGetInteger(font, FC_WEIGHT, 0, &resulting_bold)) + resulting_bold = FC_WEIGHT_NORMAL; + + int resulting_italic; + if (FcPatternGetInteger(font, FC_SLANT, 0, &resulting_italic)) + resulting_italic = FC_SLANT_ROMAN; + + // If we ask for an italic font, fontconfig might take a roman font and set + // the undocumented property FC_MATRIX to a skew matrix. It'll then say + // that the font is italic or oblique. So, if we see a matrix, we don't + // believe that it's italic. + FcValue matrix; + const bool have_matrix = FcPatternGet(font, FC_MATRIX, 0, &matrix) == 0; + + // If we ask for an italic font, fontconfig might take a roman font and set + // FC_EMBOLDEN. + FcValue embolden; + const bool have_embolden = + FcPatternGet(font, FC_EMBOLDEN, 0, &embolden) == 0; + + *is_bold = resulting_bold > FC_WEIGHT_MEDIUM && !have_embolden; + *is_italic = resulting_italic > FC_SLANT_ROMAN && !have_matrix; + *font_family = reinterpret_cast<char*>(c_family); + + return true; +} + +} // anonymous namespace + +FontConfigDirect::FontConfigDirect() + : next_file_id_(0) { + FcInit(); +} + +FontConfigDirect::~FontConfigDirect() { +} + +bool FontConfigDirect::Match(std::string* result_family, + unsigned* result_filefaceid, + bool filefaceid_valid, unsigned filefaceid, + const std::string& family, + const void* data, size_t characters_bytes, + bool* is_bold, bool* is_italic) { + if (family.length() > kMaxFontFamilyLength) + return false; + + SkAutoMutexAcquire ac(mutex_); + + // Given |family|, |is_bold| and |is_italic| but not |data|, the result will + // be a function of these three parameters, and thus eligible for caching. + // This is the fast path for |SkTypeface::CreateFromName()|. + bool eligible_for_cache = !family.empty() && is_bold && is_italic && !data; + if (eligible_for_cache) { + int style = (*is_bold ? SkTypeface::kBold : 0 ) | + (*is_italic ? SkTypeface::kItalic : 0); + FontMatchKey key = FontMatchKey(family, style); + const std::map<FontMatchKey, FontMatch>::const_iterator i = + font_match_cache_.find(key); + if (i != font_match_cache_.end()) { + *is_bold = i->second.is_bold; + *is_italic = i->second.is_italic; + if (result_family) + *result_family = i->second.family; + if (result_filefaceid) + *result_filefaceid = i->second.filefaceid; + return true; + } + } + + FcPattern* pattern = FcPatternCreate(); + + if (filefaceid_valid) { + const std::map<unsigned, std::string>::const_iterator + i = fileid_to_filename_.find(FileFaceIdToFileId(filefaceid)); + if (i == fileid_to_filename_.end()) { + FcPatternDestroy(pattern); + return false; + } + int face_index = filefaceid & 0xfu; + FcPatternAddString(pattern, FC_FILE, + reinterpret_cast<const FcChar8*>(i->second.c_str())); + // face_index is added only when family is empty because it is not + // necessary to uniquiely identify a font if both file and + // family are given. + if (family.empty()) + FcPatternAddInteger(pattern, FC_INDEX, face_index); + } + if (!family.empty()) { + FcPatternAddString(pattern, FC_FAMILY, (FcChar8*) family.c_str()); + } + + FcCharSet* charset = NULL; + if (data) { + charset = FcCharSetCreate(); + const uint16_t* chars = (const uint16_t*)data; + const uint16_t* stop = chars + characters_bytes/2; + while (chars < stop) { + FcCharSetAddChar(charset, SkUTF16_NextUnichar(&chars)); + } + FcPatternAddCharSet(pattern, FC_CHARSET, charset); + FcCharSetDestroy(charset); // pattern now owns it. + } + + FcPatternAddInteger(pattern, FC_WEIGHT, + is_bold && *is_bold ? FC_WEIGHT_BOLD + : FC_WEIGHT_NORMAL); + FcPatternAddInteger(pattern, FC_SLANT, + is_italic && *is_italic ? FC_SLANT_ITALIC + : FC_SLANT_ROMAN); + FcPatternAddBool(pattern, FC_SCALABLE, FcTrue); + + FcConfigSubstitute(NULL, pattern, FcMatchPattern); + FcDefaultSubstitute(pattern); + + // Font matching: + // CSS often specifies a fallback list of families: + // font-family: a, b, c, serif; + // However, fontconfig will always do its best to find *a* font when asked + // for something so we need a way to tell if the match which it has found is + // "good enough" for us. Otherwise, we can return NULL which gets piped up + // and lets WebKit know to try the next CSS family name. However, fontconfig + // configs allow substitutions (mapping "Arial -> Helvetica" etc) and we + // wish to support that. + // + // Thus, if a specific family is requested we set @family_requested. Then we + // record two strings: the family name after config processing and the + // family name after resolving. If the two are equal, it's a good match. + // + // So consider the case where a user has mapped Arial to Helvetica in their + // config. + // requested family: "Arial" + // post_config_family: "Helvetica" + // post_match_family: "Helvetica" + // -> good match + // + // and for a missing font: + // requested family: "Monaco" + // post_config_family: "Monaco" + // post_match_family: "Times New Roman" + // -> BAD match + // + // However, we special-case fallback fonts; see IsFallbackFontAllowed(). + FcChar8* post_config_family; + FcPatternGetString(pattern, FC_FAMILY, 0, &post_config_family); + + FcResult result; + FcFontSet* font_set = FcFontSort(0, pattern, 0, 0, &result); + if (!font_set) { + FcPatternDestroy(pattern); + return false; + } + + FcPattern* match = MatchFont(font_set, post_config_family, family); + if (!match) { + FcPatternDestroy(pattern); + FcFontSetDestroy(font_set); + return false; + } + + FcPatternDestroy(pattern); + + FcChar8* c_filename; + if (FcPatternGetString(match, FC_FILE, 0, &c_filename) != FcResultMatch) { + FcFontSetDestroy(font_set); + return false; + } + int face_index; + if (FcPatternGetInteger(match, FC_INDEX, 0, &face_index) != FcResultMatch) { + FcFontSetDestroy(font_set); + return false; + } + + FontMatch font_match; + if (filefaceid_valid) { + font_match.filefaceid = filefaceid; + } else { + unsigned out_fileid; + const std::string filename(reinterpret_cast<char*>(c_filename)); + const std::map<std::string, unsigned>::const_iterator + i = filename_to_fileid_.find(filename); + if (i == filename_to_fileid_.end()) { + out_fileid = next_file_id_++; + filename_to_fileid_[filename] = out_fileid; + fileid_to_filename_[out_fileid] = filename; + } else { + out_fileid = i->second; + } + // fileid stored in filename_to_fileid_ and fileid_to_filename_ is + // unique only up to the font file. We have to encode face_index for + // the out param. + font_match.filefaceid = + FileIdAndFaceIndexToFileFaceId(out_fileid, face_index); + } + + bool success = GetFontProperties(match, + &font_match.family, + &font_match.is_bold, + &font_match.is_italic); + FcFontSetDestroy(font_set); + + if (success) { + // If eligible, cache the result of the matching. + if (eligible_for_cache) { + int style = (*is_bold ? SkTypeface::kBold : 0 ) | + (*is_italic ? SkTypeface::kItalic : 0); + font_match_cache_[FontMatchKey(family, style)] = font_match; + } + + if (result_family) + *result_family = font_match.family; + if (result_filefaceid) + *result_filefaceid = font_match.filefaceid; + if (is_bold) + *is_bold = font_match.is_bold; + if (is_italic) + *is_italic = font_match.is_italic; + } + + return success; +} + +int FontConfigDirect::Open(unsigned filefaceid) { + SkAutoMutexAcquire ac(mutex_); + const std::map<unsigned, std::string>::const_iterator + i = fileid_to_filename_.find(FileFaceIdToFileId(filefaceid)); + if (i == fileid_to_filename_.end()) + return -1; + + return open(i->second.c_str(), O_RDONLY); +} diff --git a/skia/ext/SkFontHost_fontconfig_direct.h b/skia/ext/SkFontHost_fontconfig_direct.h new file mode 100644 index 0000000000..ae97f3d105 --- /dev/null +++ b/skia/ext/SkFontHost_fontconfig_direct.h @@ -0,0 +1,59 @@ +/* libs/graphics/ports/SkFontHost_fontconfig_direct.h +** +** Copyright 2009, Google Inc. +** +** 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 FontConfigDirect_DEFINED +#define FontConfigDirect_DEFINED + +#include <map> +#include <string> + +#include "SkThread.h" +#include "SkFontHost_fontconfig_impl.h" + +class SK_API FontConfigDirect : public FontConfigInterface { + public: + FontConfigDirect(); + virtual ~FontConfigDirect(); + + // FontConfigInterface implementation. Thread safe. + virtual bool Match(std::string* result_family, unsigned* result_filefaceid, + bool filefaceid_valid, unsigned filefaceid, + const std::string& family, + const void* characters, size_t characters_bytes, + bool* is_bold, bool* is_italic) SK_OVERRIDE; + virtual int Open(unsigned filefaceid) SK_OVERRIDE; + + private: + SkMutex mutex_; + // fileid stored in two maps below are unique per font file. + std::map<unsigned, std::string> fileid_to_filename_; + std::map<std::string, unsigned> filename_to_fileid_; + + // Cache of |family,style| to |FontMatch| to minimize querying FontConfig. + typedef std::pair<std::string, int> FontMatchKey; + struct FontMatch { + std::string family; + bool is_bold; + bool is_italic; + unsigned filefaceid; + }; + std::map<FontMatchKey, FontMatch> font_match_cache_; + + unsigned next_file_id_; +}; + +#endif // FontConfigDirect_DEFINED diff --git a/skia/ext/SkFontHost_fontconfig_impl.h b/skia/ext/SkFontHost_fontconfig_impl.h new file mode 100644 index 0000000000..d6f8179640 --- /dev/null +++ b/skia/ext/SkFontHost_fontconfig_impl.h @@ -0,0 +1,73 @@ +/* libs/graphics/ports/SkFontHost_fontconfig_impl.h +** +** Copyright 2009, Google Inc. +** +** 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. +*/ + +/* The SkFontHost_fontconfig code requires an implementation of an abstact + * fontconfig interface. We do this because sometimes fontconfig is not + * directly availible and this provides an ability to change the fontconfig + * implementation at run-time. + */ + +#ifndef FontConfigInterface_DEFINED +#define FontConfigInterface_DEFINED + +#include <string> + +class FontConfigInterface { + public: + virtual ~FontConfigInterface() { } + + /** Performs config match + * + * @param result_family (optional, set to NULL to ignore, output) + * on success, set to the resulting family name. + * @param result_filefaceid (optional, set to NULL to ignore, output) + * on success, set to the resulting fileface id. + * @param filefaceid_valid if true, then |filefaceid| is valid + * @param filefaceid the filefaceid (as returned by this function) + * which we are trying to match. + * @param family (optional) the family of the font that we are trying to + * match. If the length of the |family| is greater then + * kMaxFontFamilyLength, this function should immediately return false. + * @param characters (optional) UTF-16 characters the font must cover. + * @param characters_bytes (optional) number of bytes in |characters| + * @param is_bold (optional, set to NULL to ignore, in/out) + * @param is_italic (optional, set to NULL to ignore, in/out) + * @return true iff successful. + * Note that |filefaceid| uniquely identifies <font file, face_index) : + * system font: filefaceid = + * (fileid(unique per font file) << 4 | face_index) + * remote font: filefaceid = fileid + */ + virtual bool Match( + std::string* result_family, + unsigned* result_filefaceid, + bool filefaceid_valid, + unsigned filefaceid, + const std::string& family, + const void* characters, + size_t characters_bytes, + bool* is_bold, + bool* is_italic) = 0; + + /** Open a font file given the filefaceid as returned by Match. + */ + virtual int Open(unsigned filefaceid) = 0; + + static const unsigned kMaxFontFamilyLength = 2048; +}; + +#endif // FontConfigInterface_DEFINED diff --git a/skia/ext/SkMemory_new_handler.cpp b/skia/ext/SkMemory_new_handler.cpp new file mode 100644 index 0000000000..1799618efa --- /dev/null +++ b/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_util.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/skia/ext/SkThread_chrome.cc b/skia/ext/SkThread_chrome.cc new file mode 100644 index 0000000000..4904f2da47 --- /dev/null +++ b/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::Aquire_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/skia/ext/SkTypeface_fake.cpp b/skia/ext/SkTypeface_fake.cpp new file mode 100644 index 0000000000..dea36a1ff4 --- /dev/null +++ b/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/skia/ext/bitmap_platform_device.h b/skia/ext/bitmap_platform_device.h new file mode 100644 index 0000000000..56e41763f8 --- /dev/null +++ b/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/skia/ext/bitmap_platform_device_android.cc b/skia/ext/bitmap_platform_device_android.cc new file mode 100644 index 0000000000..6a71fb22ba --- /dev/null +++ b/skia/ext/bitmap_platform_device_android.cc @@ -0,0 +1,89 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#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); +} + +// Port of PlatformBitmap to android + +PlatformBitmap::~PlatformBitmap() {} + +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/skia/ext/bitmap_platform_device_android.h b/skia/ext/bitmap_platform_device_android.h new file mode 100644 index 0000000000..b5755cb4b0 --- /dev/null +++ b/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/skia/ext/bitmap_platform_device_data.h b/skia/ext/bitmap_platform_device_data.h new file mode 100644 index 0000000000..81e81ed79f --- /dev/null +++ b/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/skia/ext/bitmap_platform_device_linux.cc b/skia/ext/bitmap_platform_device_linux.cc new file mode 100644 index 0000000000..63b44a6f07 --- /dev/null +++ b/skia/ext/bitmap_platform_device_linux.cc @@ -0,0 +1,203 @@ +// Copyright (c) 2012 The Chromium Authors. 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 (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); +} + +// Port of PlatformBitmap to linux + +PlatformBitmap::~PlatformBitmap() { + cairo_destroy(surface_); +} + +bool PlatformBitmap::Allocate(int width, int height, bool is_opaque) { + cairo_surface_t* surf = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, + width, height); + if (cairo_surface_status(surf) != CAIRO_STATUS_SUCCESS) { + cairo_surface_destroy(surf); + return false; + } + + bitmap_.setConfig(SkBitmap::kARGB_8888_Config, width, height, + cairo_image_surface_get_stride(surf)); + bitmap_.setPixels(cairo_image_surface_get_data(surf)); + bitmap_.setIsOpaque(is_opaque); + + surface_ = cairo_create(surf); + cairo_surface_destroy(surf); + return true; +} + +} // namespace skia diff --git a/skia/ext/bitmap_platform_device_linux.h b/skia/ext/bitmap_platform_device_linux.h new file mode 100644 index 0000000000..e1f9a75c85 --- /dev/null +++ b/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/skia/ext/bitmap_platform_device_mac.cc b/skia/ext/bitmap_platform_device_mac.cc new file mode 100644 index 0000000000..d8f16a2d28 --- /dev/null +++ b/skia/ext/bitmap_platform_device_mac.cc @@ -0,0 +1,282 @@ +// Copyright (c) 2012 The Chromium Authors. 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. + 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( + 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( + BitmapPlatformDeviceData* data, const SkBitmap& bitmap) + : SkDevice(bitmap), + data_(data) { + SetPlatformDevice(this, this); +} + +BitmapPlatformDevice::~BitmapPlatformDevice() { + data_->unref(); +} + +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; +} + +// 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/skia/ext/bitmap_platform_device_mac.h b/skia/ext/bitmap_platform_device_mac.h new file mode 100644 index 0000000000..0d00a0abb5 --- /dev/null +++ b/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" + +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(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. We hold a reference + // to this object. + BitmapPlatformDeviceData* data_; + + DISALLOW_COPY_AND_ASSIGN(BitmapPlatformDevice); +}; + +} // namespace skia + +#endif // SKIA_EXT_BITMAP_PLATFORM_DEVICE_MAC_H_ diff --git a/skia/ext/bitmap_platform_device_mac_unittest.cc b/skia/ext/bitmap_platform_device_mac_unittest.cc new file mode 100644 index 0000000000..7265bc4cfb --- /dev/null +++ b/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/skia/ext/bitmap_platform_device_win.cc b/skia/ext/bitmap_platform_device_win.cc new file mode 100644 index 0000000000..dc21673f8c --- /dev/null +++ b/skia/ext/bitmap_platform_device_win.cc @@ -0,0 +1,299 @@ +// Copyright (c) 2012 The Chromium Authors. 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" + +static HBITMAP CreateHBitmap(int width, int height, bool is_opaque, + HANDLE shared_section, SkBitmap* output) { + // 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; + + void* data = NULL; + HBITMAP hbitmap = CreateDIBSection(NULL, reinterpret_cast<BITMAPINFO*>(&hdr), + 0, &data, shared_section, 0); + if (hbitmap) { + output->setConfig(SkBitmap::kARGB_8888_Config, width, height); + output->setPixels(data); + output->setIsOpaque(is_opaque); + } + return hbitmap; +} + +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) { + SkBitmap bitmap; + + HBITMAP hbitmap = CreateHBitmap(width, height, is_opaque, shared_section, + &bitmap); + if (!hbitmap) + return NULL; + +#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(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( + 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); + data_->unref(); +} + +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 /*usage*/) { + SkASSERT(config == SkBitmap::kARGB_8888_Config); + SkDevice* bitmap_device = BitmapPlatformDevice::CreateAndClear(width, height, + isOpaque); + return bitmap_device; +} + +// Port of PlatformBitmap to win + +PlatformBitmap::~PlatformBitmap() { + if (surface_) + DeleteDC(surface_); + + HBITMAP hbitmap = (HBITMAP)platform_extra_; + if (hbitmap) + DeleteObject(hbitmap); +} + +bool PlatformBitmap::Allocate(int width, int height, bool is_opaque) { + HBITMAP hbitmap = CreateHBitmap(width, height, is_opaque, 0, &bitmap_); + if (!hbitmap) + return false; + + surface_ = CreateCompatibleDC(NULL); + InitializeDC(surface_); + HGDIOBJ old_bitmap = SelectObject(surface_, hbitmap); + // When the memory DC is created, its display surface is exactly one + // monochrome pixel wide and one monochrome pixel high. Since we select our + // own bitmap, we must delete the previous one. + DeleteObject(old_bitmap); + // remember the hbitmap, so we can free it in our destructor + platform_extra_ = (intptr_t)hbitmap; + return true; +} + +} // namespace skia diff --git a/skia/ext/bitmap_platform_device_win.h b/skia/ext/bitmap_platform_device_win.h new file mode 100644 index 0000000000..745ac10d68 --- /dev/null +++ b/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" + +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. The data should already be ref'ed for us. + BitmapPlatformDevice(BitmapPlatformDeviceData* data, + const SkBitmap& bitmap); + + // Data associated with this device, guaranteed non-null. We hold a reference + // to this object. + 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/skia/ext/convolver.cc b/skia/ext/convolver.cc new file mode 100644 index 0000000000..ee9d056fa4 --- /dev/null +++ b/skia/ext/convolver.cc @@ -0,0 +1,860 @@ +// 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 "third_party/skia/include/core/SkTypes.h" + +#if defined(SIMD_SSE2) +#include <emmintrin.h> // ARCH_CPU_X86_FAMILY was defined in build/config.h +#endif + +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; +} + +// 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; + } + } +} + + +// 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) { +#if defined(SIMD_SSE2) + 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; + } +#endif +} + +// 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 ConvolveHorizontally4_SSE2(const unsigned char* src_data[4], + const ConvolutionFilter1D& filter, + unsigned char* out_row[4]) { +#if defined(SIMD_SSE2) + 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; + } +#endif +} + +// 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) { +#if defined(SIMD_SSE2) + 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; + } + } +#endif +} + +} // 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 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.length = filter_length; + filters_.push_back(instance); + + max_filter_ = std::max(max_filter_, filter_length); +} + +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_sse2) { +#if !defined(SIMD_SSE2) + // Even we have runtime support for SSE2 instructions, since the binary + // was not built with SSE2 support, we had to fallback to C version. + use_sse2 = false; +#endif + + 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 + (use_sse2 ? 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; + 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. + if (use_sse2) { + while (next_x_row < filter_offset + filter_length) { + if (next_x_row + 3 < last_filter_offset + last_filter_length - 1) { + 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(); + } + ConvolveHorizontally4_SSE2(src, filter_x, out_row); + next_x_row += 4; + } else { + // For the last row, SSE2 load possibly to access data beyond the + // image area. therefore we use C version here. + if (next_x_row == last_filter_offset + last_filter_length - 1) { + 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()); + } + } else { + ConvolveHorizontally_SSE2( + &source_data[next_x_row * source_byte_row_stride], + filter_x, row_buffer.AdvanceRow()); + } + next_x_row++; + } + } + } else { + while (next_x_row < filter_offset + filter_length) { + if (source_has_alpha) { + ConvolveHorizontally<true>( + &source_data[next_x_row * source_byte_row_stride], + filter_x, row_buffer.AdvanceRow()); + } else { + ConvolveHorizontally<false>( + &source_data[next_x_row * source_byte_row_stride], + filter_x, row_buffer.AdvanceRow()); + } + next_x_row++; + } + } + + // Compute where in the output image this row of final data will go. + 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 (source_has_alpha) { + if (use_sse2) { + ConvolveVertically_SSE2<true>(filter_values, filter_length, + first_row_for_filter, + filter_x.num_values(), cur_output_row); + } else { + ConvolveVertically<true>(filter_values, filter_length, + first_row_for_filter, + filter_x.num_values(), cur_output_row); + } + } else { + if (use_sse2) { + ConvolveVertically_SSE2<false>(filter_values, filter_length, + first_row_for_filter, + filter_x.num_values(), cur_output_row); + } else { + ConvolveVertically<false>(filter_values, filter_length, + first_row_for_filter, + filter_x.num_values(), cur_output_row); + } + } + } +} + +} // namespace skia diff --git a/skia/ext/convolver.h b/skia/ext/convolver.h new file mode 100644 index 0000000000..14974e5293 --- /dev/null +++ b/skia/ext/convolver.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_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/SkTypes.h" + +#if defined(ARCH_CPU_X86_FAMILY) +#if defined(__x86_64__) || defined(_M_X64) || defined(__SSE2__) || _M_IX86_FP==2 +// This is where we had compiler support for SSE2 instructions. +// FIXME: Known buggy, so disabling for M22. +// #define SIMD_SSE2 1 +#endif +#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.length; + if (filter.length == 0) { + return NULL; + } + return &filter_values_[filter.data_location]; + } + + + inline void PaddingForSIMD(int padding_count) { + // 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. + for (int i = 0; i < padding_count; ++i) + filter_values_.push_back(static_cast<Fixed>(0)); + } + + private: + struct FilterInstance { + // Offset within filter_values for this instance of the filter. + int data_location; + + // Distance from the left of the filter to the center. IN PIXELS + int offset; + + // Number of values in this filter instance. + int length; + }; + + // Stores the information for each filter added to this class. + std::vector<FilterInstance> filters_; + + // We store all the filter values in this flat list, indexed by + // |FilterInstance.data_location| to avoid the mallocs required for storing + // each one separately. + std::vector<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_sse2); +} // namespace skia + +#endif // SKIA_EXT_CONVOLVER_H_ diff --git a/skia/ext/convolver_unittest.cc b/skia/ext/convolver_unittest.cc new file mode 100644 index 0000000000..f61b685daf --- /dev/null +++ b/skia/ext/convolver_unittest.cc @@ -0,0 +1,321 @@ +// Copyright (c) 2012 The Chromium Authors. 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 <vector> + +#include "base/basictypes.h" +#include "base/logging.h" +#include "base/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); +} + +TEST(Convolver, SIMDVerification) { +#if defined(SIMD_SSE2) + base::CPU cpu; + if (!cpu.has_sse2()) return; + + int source_sizes[][2] = { {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 = source_sizes[j][0]; + unsigned int dest_height = source_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; + if (offset > source_width - arraysize(filter)) + offset = source_width - arraysize(filter); + x_filter.AddFilter(offset, filter, arraysize(filter)); + } + for (unsigned int p = 0; p < dest_height; ++p) { + unsigned int offset = source_height * p / dest_height; + if (offset > source_height - arraysize(filter)) + offset = source_height - arraysize(filter); + y_filter.AddFilter(offset, filter, arraysize(filter)); + } + + // 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 (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(); + } + } + } + } +#endif +} + +} // namespace skia diff --git a/skia/ext/data/vectorcanvastest/basicdrawing/00_pc_clean.png b/skia/ext/data/vectorcanvastest/basicdrawing/00_pc_clean.png Binary files differnew file mode 100644 index 0000000000..a5435f278b --- /dev/null +++ b/skia/ext/data/vectorcanvastest/basicdrawing/00_pc_clean.png diff --git a/skia/ext/data/vectorcanvastest/basicdrawing/00_vc_clean.png b/skia/ext/data/vectorcanvastest/basicdrawing/00_vc_clean.png Binary files differnew file mode 100644 index 0000000000..a5435f278b --- /dev/null +++ b/skia/ext/data/vectorcanvastest/basicdrawing/00_vc_clean.png diff --git a/skia/ext/data/vectorcanvastest/basicdrawing/01_pc_drawargb.png b/skia/ext/data/vectorcanvastest/basicdrawing/01_pc_drawargb.png Binary files differnew file mode 100644 index 0000000000..a5435f278b --- /dev/null +++ b/skia/ext/data/vectorcanvastest/basicdrawing/01_pc_drawargb.png diff --git a/skia/ext/data/vectorcanvastest/basicdrawing/01_vc_drawargb.png b/skia/ext/data/vectorcanvastest/basicdrawing/01_vc_drawargb.png Binary files differnew file mode 100644 index 0000000000..a5435f278b --- /dev/null +++ b/skia/ext/data/vectorcanvastest/basicdrawing/01_vc_drawargb.png diff --git a/skia/ext/data/vectorcanvastest/basicdrawing/02_pc_drawline_black.png b/skia/ext/data/vectorcanvastest/basicdrawing/02_pc_drawline_black.png Binary files differnew file mode 100644 index 0000000000..c21fdf1c57 --- /dev/null +++ b/skia/ext/data/vectorcanvastest/basicdrawing/02_pc_drawline_black.png diff --git a/skia/ext/data/vectorcanvastest/basicdrawing/02_vc_drawline_black.png b/skia/ext/data/vectorcanvastest/basicdrawing/02_vc_drawline_black.png Binary files differnew file mode 100644 index 0000000000..c21fdf1c57 --- /dev/null +++ b/skia/ext/data/vectorcanvastest/basicdrawing/02_vc_drawline_black.png diff --git a/skia/ext/data/vectorcanvastest/basicdrawing/03_pc_drawrect_green.png b/skia/ext/data/vectorcanvastest/basicdrawing/03_pc_drawrect_green.png Binary files differnew file mode 100644 index 0000000000..dfc46a80d8 --- /dev/null +++ b/skia/ext/data/vectorcanvastest/basicdrawing/03_pc_drawrect_green.png diff --git a/skia/ext/data/vectorcanvastest/basicdrawing/03_vc_drawrect_green.png b/skia/ext/data/vectorcanvastest/basicdrawing/03_vc_drawrect_green.png Binary files differnew file mode 100644 index 0000000000..dfc46a80d8 --- /dev/null +++ b/skia/ext/data/vectorcanvastest/basicdrawing/03_vc_drawrect_green.png diff --git a/skia/ext/data/vectorcanvastest/basicdrawing/04_pc_drawrect_noop.png b/skia/ext/data/vectorcanvastest/basicdrawing/04_pc_drawrect_noop.png Binary files differnew file mode 100644 index 0000000000..dfc46a80d8 --- /dev/null +++ b/skia/ext/data/vectorcanvastest/basicdrawing/04_pc_drawrect_noop.png diff --git a/skia/ext/data/vectorcanvastest/basicdrawing/04_vc_drawrect_noop.png b/skia/ext/data/vectorcanvastest/basicdrawing/04_vc_drawrect_noop.png Binary files differnew file mode 100644 index 0000000000..dfc46a80d8 --- /dev/null +++ b/skia/ext/data/vectorcanvastest/basicdrawing/04_vc_drawrect_noop.png diff --git a/skia/ext/data/vectorcanvastest/basicdrawing/05_pc_drawrect_noop.png b/skia/ext/data/vectorcanvastest/basicdrawing/05_pc_drawrect_noop.png Binary files differnew file mode 100644 index 0000000000..69cc6dce63 --- /dev/null +++ b/skia/ext/data/vectorcanvastest/basicdrawing/05_pc_drawrect_noop.png diff --git a/skia/ext/data/vectorcanvastest/basicdrawing/05_vc_drawrect_noop.png b/skia/ext/data/vectorcanvastest/basicdrawing/05_vc_drawrect_noop.png Binary files differnew file mode 100644 index 0000000000..69cc6dce63 --- /dev/null +++ b/skia/ext/data/vectorcanvastest/basicdrawing/05_vc_drawrect_noop.png diff --git a/skia/ext/data/vectorcanvastest/basicdrawing/06_pc_drawpaint_black.png b/skia/ext/data/vectorcanvastest/basicdrawing/06_pc_drawpaint_black.png Binary files differnew file mode 100644 index 0000000000..9cbff6e164 --- /dev/null +++ b/skia/ext/data/vectorcanvastest/basicdrawing/06_pc_drawpaint_black.png diff --git a/skia/ext/data/vectorcanvastest/basicdrawing/06_vc_drawpaint_black.png b/skia/ext/data/vectorcanvastest/basicdrawing/06_vc_drawpaint_black.png Binary files differnew file mode 100644 index 0000000000..9cbff6e164 --- /dev/null +++ b/skia/ext/data/vectorcanvastest/basicdrawing/06_vc_drawpaint_black.png diff --git a/skia/ext/data/vectorcanvastest/basicdrawing/07_pc_drawline_left_to_right.png b/skia/ext/data/vectorcanvastest/basicdrawing/07_pc_drawline_left_to_right.png Binary files differnew file mode 100644 index 0000000000..bbdfc36cb5 --- /dev/null +++ b/skia/ext/data/vectorcanvastest/basicdrawing/07_pc_drawline_left_to_right.png diff --git a/skia/ext/data/vectorcanvastest/basicdrawing/07_vc_drawline_left_to_right.png b/skia/ext/data/vectorcanvastest/basicdrawing/07_vc_drawline_left_to_right.png Binary files differnew file mode 100644 index 0000000000..bbdfc36cb5 --- /dev/null +++ b/skia/ext/data/vectorcanvastest/basicdrawing/07_vc_drawline_left_to_right.png diff --git a/skia/ext/data/vectorcanvastest/basicdrawing/08_pc_drawline_red.png b/skia/ext/data/vectorcanvastest/basicdrawing/08_pc_drawline_red.png Binary files differnew file mode 100644 index 0000000000..9dc35f051e --- /dev/null +++ b/skia/ext/data/vectorcanvastest/basicdrawing/08_pc_drawline_red.png diff --git a/skia/ext/data/vectorcanvastest/basicdrawing/08_vc_drawline_red.png b/skia/ext/data/vectorcanvastest/basicdrawing/08_vc_drawline_red.png Binary files differnew file mode 100644 index 0000000000..9dc35f051e --- /dev/null +++ b/skia/ext/data/vectorcanvastest/basicdrawing/08_vc_drawline_red.png diff --git a/skia/ext/data/vectorcanvastest/bitmaps/00_pc_opaque.png b/skia/ext/data/vectorcanvastest/bitmaps/00_pc_opaque.png Binary files differnew file mode 100644 index 0000000000..812b1ca293 --- /dev/null +++ b/skia/ext/data/vectorcanvastest/bitmaps/00_pc_opaque.png diff --git a/skia/ext/data/vectorcanvastest/bitmaps/00_vc_opaque.png b/skia/ext/data/vectorcanvastest/bitmaps/00_vc_opaque.png Binary files differnew file mode 100644 index 0000000000..812b1ca293 --- /dev/null +++ b/skia/ext/data/vectorcanvastest/bitmaps/00_vc_opaque.png diff --git a/skia/ext/data/vectorcanvastest/bitmaps/01_pc_alpha.png b/skia/ext/data/vectorcanvastest/bitmaps/01_pc_alpha.png Binary files differnew file mode 100644 index 0000000000..1d1342bbaa --- /dev/null +++ b/skia/ext/data/vectorcanvastest/bitmaps/01_pc_alpha.png diff --git a/skia/ext/data/vectorcanvastest/bitmaps/01_vc_alpha.png b/skia/ext/data/vectorcanvastest/bitmaps/01_vc_alpha.png Binary files differnew file mode 100644 index 0000000000..1d1342bbaa --- /dev/null +++ b/skia/ext/data/vectorcanvastest/bitmaps/01_vc_alpha.png diff --git a/skia/ext/data/vectorcanvastest/bitmaps/bitmap_alpha.png b/skia/ext/data/vectorcanvastest/bitmaps/bitmap_alpha.png Binary files differnew file mode 100644 index 0000000000..a19d09d06c --- /dev/null +++ b/skia/ext/data/vectorcanvastest/bitmaps/bitmap_alpha.png diff --git a/skia/ext/data/vectorcanvastest/bitmaps/bitmap_opaque.png b/skia/ext/data/vectorcanvastest/bitmaps/bitmap_opaque.png Binary files differnew file mode 100644 index 0000000000..3560d270f5 --- /dev/null +++ b/skia/ext/data/vectorcanvastest/bitmaps/bitmap_opaque.png diff --git a/skia/ext/data/vectorcanvastest/circles/00_pc_circle_stroke.png b/skia/ext/data/vectorcanvastest/circles/00_pc_circle_stroke.png Binary files differnew file mode 100644 index 0000000000..896631b323 --- /dev/null +++ b/skia/ext/data/vectorcanvastest/circles/00_pc_circle_stroke.png diff --git a/skia/ext/data/vectorcanvastest/circles/00_vc_circle_stroke.png b/skia/ext/data/vectorcanvastest/circles/00_vc_circle_stroke.png Binary files differnew file mode 100644 index 0000000000..c265be341f --- /dev/null +++ b/skia/ext/data/vectorcanvastest/circles/00_vc_circle_stroke.png diff --git a/skia/ext/data/vectorcanvastest/circles/01_pc_circle_fill.png b/skia/ext/data/vectorcanvastest/circles/01_pc_circle_fill.png Binary files differnew file mode 100644 index 0000000000..92b647d046 --- /dev/null +++ b/skia/ext/data/vectorcanvastest/circles/01_pc_circle_fill.png diff --git a/skia/ext/data/vectorcanvastest/circles/01_vc_circle_fill.png b/skia/ext/data/vectorcanvastest/circles/01_vc_circle_fill.png Binary files differnew file mode 100644 index 0000000000..f5270cea5a --- /dev/null +++ b/skia/ext/data/vectorcanvastest/circles/01_vc_circle_fill.png diff --git a/skia/ext/data/vectorcanvastest/circles/02_pc_circle_over_strike.png b/skia/ext/data/vectorcanvastest/circles/02_pc_circle_over_strike.png Binary files differnew file mode 100644 index 0000000000..64ae06ab9d --- /dev/null +++ b/skia/ext/data/vectorcanvastest/circles/02_pc_circle_over_strike.png diff --git a/skia/ext/data/vectorcanvastest/circles/02_vc_circle_over_strike.png b/skia/ext/data/vectorcanvastest/circles/02_vc_circle_over_strike.png Binary files differnew file mode 100644 index 0000000000..4d3d1b069e --- /dev/null +++ b/skia/ext/data/vectorcanvastest/circles/02_vc_circle_over_strike.png diff --git a/skia/ext/data/vectorcanvastest/circles/03_pc_circle_stroke_and_fill.png b/skia/ext/data/vectorcanvastest/circles/03_pc_circle_stroke_and_fill.png Binary files differnew file mode 100644 index 0000000000..6aeeb49b08 --- /dev/null +++ b/skia/ext/data/vectorcanvastest/circles/03_pc_circle_stroke_and_fill.png diff --git a/skia/ext/data/vectorcanvastest/circles/03_vc_circle_stroke_and_fill.png b/skia/ext/data/vectorcanvastest/circles/03_vc_circle_stroke_and_fill.png Binary files differnew file mode 100644 index 0000000000..f073a3e8c8 --- /dev/null +++ b/skia/ext/data/vectorcanvastest/circles/03_vc_circle_stroke_and_fill.png diff --git a/skia/ext/data/vectorcanvastest/circles/04_pc_mixed_stroke.png b/skia/ext/data/vectorcanvastest/circles/04_pc_mixed_stroke.png Binary files differnew file mode 100644 index 0000000000..e4a044fa26 --- /dev/null +++ b/skia/ext/data/vectorcanvastest/circles/04_pc_mixed_stroke.png diff --git a/skia/ext/data/vectorcanvastest/circles/04_vc_mixed_stroke.png b/skia/ext/data/vectorcanvastest/circles/04_vc_mixed_stroke.png Binary files differnew file mode 100644 index 0000000000..efd9e3abdf --- /dev/null +++ b/skia/ext/data/vectorcanvastest/circles/04_vc_mixed_stroke.png diff --git a/skia/ext/data/vectorcanvastest/clippingclean/00_pc_clipped.png b/skia/ext/data/vectorcanvastest/clippingclean/00_pc_clipped.png Binary files differnew file mode 100644 index 0000000000..14ff949d62 --- /dev/null +++ b/skia/ext/data/vectorcanvastest/clippingclean/00_pc_clipped.png diff --git a/skia/ext/data/vectorcanvastest/clippingclean/00_vc_clipped.png b/skia/ext/data/vectorcanvastest/clippingclean/00_vc_clipped.png Binary files differnew file mode 100644 index 0000000000..14ff949d62 --- /dev/null +++ b/skia/ext/data/vectorcanvastest/clippingclean/00_vc_clipped.png diff --git a/skia/ext/data/vectorcanvastest/clippingclean/01_pc_unclipped.png b/skia/ext/data/vectorcanvastest/clippingclean/01_pc_unclipped.png Binary files differnew file mode 100644 index 0000000000..436f9a5b7c --- /dev/null +++ b/skia/ext/data/vectorcanvastest/clippingclean/01_pc_unclipped.png diff --git a/skia/ext/data/vectorcanvastest/clippingclean/01_vc_unclipped.png b/skia/ext/data/vectorcanvastest/clippingclean/01_vc_unclipped.png Binary files differnew file mode 100644 index 0000000000..436f9a5b7c --- /dev/null +++ b/skia/ext/data/vectorcanvastest/clippingclean/01_vc_unclipped.png diff --git a/skia/ext/data/vectorcanvastest/clippingcombined/00_pc_combined.png b/skia/ext/data/vectorcanvastest/clippingcombined/00_pc_combined.png Binary files differnew file mode 100644 index 0000000000..14ff949d62 --- /dev/null +++ b/skia/ext/data/vectorcanvastest/clippingcombined/00_pc_combined.png diff --git a/skia/ext/data/vectorcanvastest/clippingcombined/00_vc_combined.png b/skia/ext/data/vectorcanvastest/clippingcombined/00_vc_combined.png Binary files differnew file mode 100644 index 0000000000..14ff949d62 --- /dev/null +++ b/skia/ext/data/vectorcanvastest/clippingcombined/00_vc_combined.png diff --git a/skia/ext/data/vectorcanvastest/clippingintersect/00_pc_intersect.png b/skia/ext/data/vectorcanvastest/clippingintersect/00_pc_intersect.png Binary files differnew file mode 100644 index 0000000000..1285dac0db --- /dev/null +++ b/skia/ext/data/vectorcanvastest/clippingintersect/00_pc_intersect.png diff --git a/skia/ext/data/vectorcanvastest/clippingintersect/00_vc_intersect.png b/skia/ext/data/vectorcanvastest/clippingintersect/00_vc_intersect.png Binary files differnew file mode 100644 index 0000000000..1285dac0db --- /dev/null +++ b/skia/ext/data/vectorcanvastest/clippingintersect/00_vc_intersect.png diff --git a/skia/ext/data/vectorcanvastest/clippingpath/00_pc_path.png b/skia/ext/data/vectorcanvastest/clippingpath/00_pc_path.png Binary files differnew file mode 100644 index 0000000000..8807649bb9 --- /dev/null +++ b/skia/ext/data/vectorcanvastest/clippingpath/00_pc_path.png diff --git a/skia/ext/data/vectorcanvastest/clippingpath/00_vc_path.png b/skia/ext/data/vectorcanvastest/clippingpath/00_vc_path.png Binary files differnew file mode 100644 index 0000000000..8807649bb9 --- /dev/null +++ b/skia/ext/data/vectorcanvastest/clippingpath/00_vc_path.png diff --git a/skia/ext/data/vectorcanvastest/clippingrect/00_pc_rect.png b/skia/ext/data/vectorcanvastest/clippingrect/00_pc_rect.png Binary files differnew file mode 100644 index 0000000000..9c365e13dd --- /dev/null +++ b/skia/ext/data/vectorcanvastest/clippingrect/00_pc_rect.png diff --git a/skia/ext/data/vectorcanvastest/clippingrect/00_vc_rect.png b/skia/ext/data/vectorcanvastest/clippingrect/00_vc_rect.png Binary files differnew file mode 100644 index 0000000000..9c365e13dd --- /dev/null +++ b/skia/ext/data/vectorcanvastest/clippingrect/00_vc_rect.png diff --git a/skia/ext/data/vectorcanvastest/diagonallines/00_pc_nw-se.png b/skia/ext/data/vectorcanvastest/diagonallines/00_pc_nw-se.png Binary files differnew file mode 100644 index 0000000000..5736c3503a --- /dev/null +++ b/skia/ext/data/vectorcanvastest/diagonallines/00_pc_nw-se.png diff --git a/skia/ext/data/vectorcanvastest/diagonallines/00_vc_nw-se.png b/skia/ext/data/vectorcanvastest/diagonallines/00_vc_nw-se.png Binary files differnew file mode 100644 index 0000000000..5736c3503a --- /dev/null +++ b/skia/ext/data/vectorcanvastest/diagonallines/00_vc_nw-se.png diff --git a/skia/ext/data/vectorcanvastest/diagonallines/01_pc_sw-ne.png b/skia/ext/data/vectorcanvastest/diagonallines/01_pc_sw-ne.png Binary files differnew file mode 100644 index 0000000000..bfffd8a52a --- /dev/null +++ b/skia/ext/data/vectorcanvastest/diagonallines/01_pc_sw-ne.png diff --git a/skia/ext/data/vectorcanvastest/diagonallines/01_vc_sw-ne.png b/skia/ext/data/vectorcanvastest/diagonallines/01_vc_sw-ne.png Binary files differnew file mode 100644 index 0000000000..ae6b7537fc --- /dev/null +++ b/skia/ext/data/vectorcanvastest/diagonallines/01_vc_sw-ne.png diff --git a/skia/ext/data/vectorcanvastest/diagonallines/02_pc_ne-sw.png b/skia/ext/data/vectorcanvastest/diagonallines/02_pc_ne-sw.png Binary files differnew file mode 100644 index 0000000000..75acdad042 --- /dev/null +++ b/skia/ext/data/vectorcanvastest/diagonallines/02_pc_ne-sw.png diff --git a/skia/ext/data/vectorcanvastest/diagonallines/02_vc_ne-sw.png b/skia/ext/data/vectorcanvastest/diagonallines/02_vc_ne-sw.png Binary files differnew file mode 100644 index 0000000000..86a67992af --- /dev/null +++ b/skia/ext/data/vectorcanvastest/diagonallines/02_vc_ne-sw.png diff --git a/skia/ext/data/vectorcanvastest/diagonallines/03_pc_se-nw.png b/skia/ext/data/vectorcanvastest/diagonallines/03_pc_se-nw.png Binary files differnew file mode 100644 index 0000000000..50502cc1b8 --- /dev/null +++ b/skia/ext/data/vectorcanvastest/diagonallines/03_pc_se-nw.png diff --git a/skia/ext/data/vectorcanvastest/diagonallines/03_vc_se-nw.png b/skia/ext/data/vectorcanvastest/diagonallines/03_vc_se-nw.png Binary files differnew file mode 100644 index 0000000000..362f6e727e --- /dev/null +++ b/skia/ext/data/vectorcanvastest/diagonallines/03_vc_se-nw.png diff --git a/skia/ext/data/vectorcanvastest/lineorientation/00_pc_horizontal.png b/skia/ext/data/vectorcanvastest/lineorientation/00_pc_horizontal.png Binary files differnew file mode 100644 index 0000000000..7bcd99885a --- /dev/null +++ b/skia/ext/data/vectorcanvastest/lineorientation/00_pc_horizontal.png diff --git a/skia/ext/data/vectorcanvastest/lineorientation/00_vc_horizontal.png b/skia/ext/data/vectorcanvastest/lineorientation/00_vc_horizontal.png Binary files differnew file mode 100644 index 0000000000..46c9b0abe1 --- /dev/null +++ b/skia/ext/data/vectorcanvastest/lineorientation/00_vc_horizontal.png diff --git a/skia/ext/data/vectorcanvastest/lineorientation/01_pc_vertical.png b/skia/ext/data/vectorcanvastest/lineorientation/01_pc_vertical.png Binary files differnew file mode 100644 index 0000000000..09f41db50f --- /dev/null +++ b/skia/ext/data/vectorcanvastest/lineorientation/01_pc_vertical.png diff --git a/skia/ext/data/vectorcanvastest/lineorientation/01_vc_vertical.png b/skia/ext/data/vectorcanvastest/lineorientation/01_vc_vertical.png Binary files differnew file mode 100644 index 0000000000..7f5f1f7f15 --- /dev/null +++ b/skia/ext/data/vectorcanvastest/lineorientation/01_vc_vertical.png diff --git a/skia/ext/data/vectorcanvastest/lineorientation/02_pc_horizontal_180.png b/skia/ext/data/vectorcanvastest/lineorientation/02_pc_horizontal_180.png Binary files differnew file mode 100644 index 0000000000..5966df640e --- /dev/null +++ b/skia/ext/data/vectorcanvastest/lineorientation/02_pc_horizontal_180.png diff --git a/skia/ext/data/vectorcanvastest/lineorientation/02_vc_horizontal_180.png b/skia/ext/data/vectorcanvastest/lineorientation/02_vc_horizontal_180.png Binary files differnew file mode 100644 index 0000000000..e43a844985 --- /dev/null +++ b/skia/ext/data/vectorcanvastest/lineorientation/02_vc_horizontal_180.png diff --git a/skia/ext/data/vectorcanvastest/lineorientation/03_pc_vertical_180.png b/skia/ext/data/vectorcanvastest/lineorientation/03_pc_vertical_180.png Binary files differnew file mode 100644 index 0000000000..9ac482558f --- /dev/null +++ b/skia/ext/data/vectorcanvastest/lineorientation/03_pc_vertical_180.png diff --git a/skia/ext/data/vectorcanvastest/lineorientation/03_vc_vertical_180.png b/skia/ext/data/vectorcanvastest/lineorientation/03_vc_vertical_180.png Binary files differnew file mode 100644 index 0000000000..d9e033abf5 --- /dev/null +++ b/skia/ext/data/vectorcanvastest/lineorientation/03_vc_vertical_180.png diff --git a/skia/ext/data/vectorcanvastest/matrix/00_pc_translate1.png b/skia/ext/data/vectorcanvastest/matrix/00_pc_translate1.png Binary files differnew file mode 100644 index 0000000000..fe27cb3158 --- /dev/null +++ b/skia/ext/data/vectorcanvastest/matrix/00_pc_translate1.png diff --git a/skia/ext/data/vectorcanvastest/matrix/00_vc_translate1.png b/skia/ext/data/vectorcanvastest/matrix/00_vc_translate1.png Binary files differnew file mode 100644 index 0000000000..fe27cb3158 --- /dev/null +++ b/skia/ext/data/vectorcanvastest/matrix/00_vc_translate1.png diff --git a/skia/ext/data/vectorcanvastest/matrix/01_pc_translate2.png b/skia/ext/data/vectorcanvastest/matrix/01_pc_translate2.png Binary files differnew file mode 100644 index 0000000000..406bf5776e --- /dev/null +++ b/skia/ext/data/vectorcanvastest/matrix/01_pc_translate2.png diff --git a/skia/ext/data/vectorcanvastest/matrix/01_vc_translate2.png b/skia/ext/data/vectorcanvastest/matrix/01_vc_translate2.png Binary files differnew file mode 100644 index 0000000000..406bf5776e --- /dev/null +++ b/skia/ext/data/vectorcanvastest/matrix/01_vc_translate2.png diff --git a/skia/ext/data/vectorcanvastest/matrix/02_pc_scale.png b/skia/ext/data/vectorcanvastest/matrix/02_pc_scale.png Binary files differnew file mode 100644 index 0000000000..9e94fb0c6f --- /dev/null +++ b/skia/ext/data/vectorcanvastest/matrix/02_pc_scale.png diff --git a/skia/ext/data/vectorcanvastest/matrix/02_vc_scale.png b/skia/ext/data/vectorcanvastest/matrix/02_vc_scale.png Binary files differnew file mode 100644 index 0000000000..fde62aadcb --- /dev/null +++ b/skia/ext/data/vectorcanvastest/matrix/02_vc_scale.png diff --git a/skia/ext/data/vectorcanvastest/matrix/03_pc_rotate.png b/skia/ext/data/vectorcanvastest/matrix/03_pc_rotate.png Binary files differnew file mode 100644 index 0000000000..7a43a2ad66 --- /dev/null +++ b/skia/ext/data/vectorcanvastest/matrix/03_pc_rotate.png diff --git a/skia/ext/data/vectorcanvastest/matrix/03_vc_rotate.png b/skia/ext/data/vectorcanvastest/matrix/03_vc_rotate.png Binary files differnew file mode 100644 index 0000000000..7a22b7f14d --- /dev/null +++ b/skia/ext/data/vectorcanvastest/matrix/03_vc_rotate.png diff --git a/skia/ext/data/vectorcanvastest/patheffects/00_pc_dash_line.png b/skia/ext/data/vectorcanvastest/patheffects/00_pc_dash_line.png Binary files differnew file mode 100644 index 0000000000..e08d3e2a3f --- /dev/null +++ b/skia/ext/data/vectorcanvastest/patheffects/00_pc_dash_line.png diff --git a/skia/ext/data/vectorcanvastest/patheffects/00_vc_dash_line.png b/skia/ext/data/vectorcanvastest/patheffects/00_vc_dash_line.png Binary files differnew file mode 100644 index 0000000000..e08d3e2a3f --- /dev/null +++ b/skia/ext/data/vectorcanvastest/patheffects/00_vc_dash_line.png diff --git a/skia/ext/data/vectorcanvastest/patheffects/01_pc_dash_path.png b/skia/ext/data/vectorcanvastest/patheffects/01_pc_dash_path.png Binary files differnew file mode 100644 index 0000000000..3a30135421 --- /dev/null +++ b/skia/ext/data/vectorcanvastest/patheffects/01_pc_dash_path.png diff --git a/skia/ext/data/vectorcanvastest/patheffects/01_vc_dash_path.png b/skia/ext/data/vectorcanvastest/patheffects/01_vc_dash_path.png Binary files differnew file mode 100644 index 0000000000..7868b9a4ed --- /dev/null +++ b/skia/ext/data/vectorcanvastest/patheffects/01_vc_dash_path.png diff --git a/skia/ext/data/vectorcanvastest/patheffects/02_pc_dash_rect.png b/skia/ext/data/vectorcanvastest/patheffects/02_pc_dash_rect.png Binary files differnew file mode 100644 index 0000000000..04f2cebae2 --- /dev/null +++ b/skia/ext/data/vectorcanvastest/patheffects/02_pc_dash_rect.png diff --git a/skia/ext/data/vectorcanvastest/patheffects/02_vc_dash_rect.png b/skia/ext/data/vectorcanvastest/patheffects/02_vc_dash_rect.png Binary files differnew file mode 100644 index 0000000000..5344eee2e8 --- /dev/null +++ b/skia/ext/data/vectorcanvastest/patheffects/02_vc_dash_rect.png diff --git a/skia/ext/data/vectorcanvastest/patheffects/03_pc_circle.png b/skia/ext/data/vectorcanvastest/patheffects/03_pc_circle.png Binary files differnew file mode 100644 index 0000000000..4c267ef765 --- /dev/null +++ b/skia/ext/data/vectorcanvastest/patheffects/03_pc_circle.png diff --git a/skia/ext/data/vectorcanvastest/patheffects/03_vc_circle.png b/skia/ext/data/vectorcanvastest/patheffects/03_vc_circle.png Binary files differnew file mode 100644 index 0000000000..46ac35d05b --- /dev/null +++ b/skia/ext/data/vectorcanvastest/patheffects/03_vc_circle.png diff --git a/skia/ext/data/vectorcanvastest/pathorientation/00_pc_drawpath_ltr.png b/skia/ext/data/vectorcanvastest/pathorientation/00_pc_drawpath_ltr.png Binary files differnew file mode 100644 index 0000000000..cefbf8700b --- /dev/null +++ b/skia/ext/data/vectorcanvastest/pathorientation/00_pc_drawpath_ltr.png diff --git a/skia/ext/data/vectorcanvastest/pathorientation/00_vc_drawpath_ltr.png b/skia/ext/data/vectorcanvastest/pathorientation/00_vc_drawpath_ltr.png Binary files differnew file mode 100644 index 0000000000..cefbf8700b --- /dev/null +++ b/skia/ext/data/vectorcanvastest/pathorientation/00_vc_drawpath_ltr.png diff --git a/skia/ext/data/vectorcanvastest/pathorientation/01_pc_drawpath_rtl.png b/skia/ext/data/vectorcanvastest/pathorientation/01_pc_drawpath_rtl.png Binary files differnew file mode 100644 index 0000000000..7bcd99885a --- /dev/null +++ b/skia/ext/data/vectorcanvastest/pathorientation/01_pc_drawpath_rtl.png diff --git a/skia/ext/data/vectorcanvastest/pathorientation/01_vc_drawpath_rtl.png b/skia/ext/data/vectorcanvastest/pathorientation/01_vc_drawpath_rtl.png Binary files differnew file mode 100644 index 0000000000..46c9b0abe1 --- /dev/null +++ b/skia/ext/data/vectorcanvastest/pathorientation/01_vc_drawpath_rtl.png diff --git a/skia/ext/google_logging.cc b/skia/ext/google_logging.cc new file mode 100644 index 0000000000..5e8ffe1d14 --- /dev/null +++ b/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/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/skia/ext/image_operations.cc b/skia/ext/image_operations.cc new file mode 100644 index 0000000000..b048f3064a --- /dev/null +++ b/skia/ext/image_operations.cc @@ -0,0 +1,541 @@ +// Copyright (c) 2012 The Chromium 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.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/SkBitmap.h" +#include "third_party/skia/include/core/SkRect.h" +#include "third_party/skia/include/core/SkFontHost.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, float src_support, + 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); + + x_filter_support_ = GetFilterSupport(scale_x); + y_filter_support_ = GetFilterSupport(scale_y); + + // Support of the filter in source space. + float src_x_support = x_filter_support_ / scale_x; + float src_y_support = y_filter_support_ / scale_y; + + ComputeFilters(src_full_width, dest_subset.fLeft, dest_subset.width(), + scale_x, src_x_support, &x_filter_); + ComputeFilters(src_full_height, dest_subset.fTop, dest_subset.height(), + scale_y, src_y_support, &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, float src_support, + 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); + + // 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(8); +} + +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) { + if (method == ImageOperations::RESIZE_SUBPIXEL) + return ResizeSubpixel(source, dest_width, dest_height, dest_subset); + else + return ResizeBasic(source, method, dest_width, dest_height, dest_subset); +} + +// static +SkBitmap ImageOperations::ResizeSubpixel(const SkBitmap& source, + int dest_width, int dest_height, + const SkIRect& dest_subset) { + 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); + 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(); + 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) { + 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()) + 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. + base::CPU cpu; + SkBitmap result; + result.setConfig(SkBitmap::kARGB_8888_Config, + dest_subset.width(), dest_subset.height()); + result.allocPixels(); + 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()), + cpu.has_sse2()); + + // 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) { + SkIRect dest_subset = { 0, 0, dest_width, dest_height }; + return Resize(source, method, dest_width, dest_height, dest_subset); +} + +} // namespace skia diff --git a/skia/ext/image_operations.h b/skia/ext/image_operations.h new file mode 100644 index 0000000000..20e6a08dcc --- /dev/null +++ b/skia/ext/image_operations.h @@ -0,0 +1,129 @@ +// 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/SkTypes.h" + +class SkBitmap; +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); + + // 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); + + 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); + + // Subpixel renderer. + static SkBitmap ResizeSubpixel(const SkBitmap& source, + int dest_width, int dest_height, + const SkIRect& dest_subset); +}; + +} // namespace skia + +#endif // SKIA_EXT_IMAGE_OPERATIONS_H_ diff --git a/skia/ext/image_operations_bench.cc b/skia/ext/image_operations_bench.cc new file mode 100644 index 0000000000..f590458ea7 --- /dev/null +++ b/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/string_number_conversions.h" +#include "base/string_split.h" +#include "base/string_util.h" +#include "base/time.h" +#include "base/utf_string_conversions.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/skia/ext/image_operations_unittest.cc b/skia/ext/image_operations_unittest.cc new file mode 100644 index 0000000000..23353faacc --- /dev/null +++ b/skia/ext/image_operations_unittest.cc @@ -0,0 +1,634 @@ +// Copyright (c) 2012 The Chromium Authors. 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 <iomanip> +#include <vector> + +#include "base/basictypes.h" +#include "base/compiler_specific.h" +#include "base/file_util.h" +#include "base/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 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)); + } + } +} + +// 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 + } +} diff --git a/skia/ext/platform_canvas.cc b/skia/ext/platform_canvas.cc new file mode 100644 index 0000000000..11b1f5e9d5 --- /dev/null +++ b/skia/ext/platform_canvas.cc @@ -0,0 +1,92 @@ +// 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 { + +PlatformCanvas::PlatformCanvas() {} + +// static +size_t PlatformCanvas::StrideForWidth(unsigned width) { + return 4 * width; +} + +bool PlatformCanvas::initializeWithDevice(SkDevice* device) { + if (!device) + return false; + + setDevice(device); + device->unref(); // Was created with refcount 1, and setDevice also refs. + return true; +} + +SkCanvas* CreateBitmapCanvas(int width, int height, bool is_opaque) { + return new PlatformCanvas(width, height, is_opaque); +} + +SkCanvas* TryCreateBitmapCanvas(int width, int height, bool is_opaque) { + PlatformCanvas* canvas = new PlatformCanvas(); + if (!canvas->initialize(width, height, is_opaque)) { + delete canvas; + canvas = NULL; + } + return canvas; +} + +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 + paint.setXfermode(new SkProcXfermode(MakeOpaqueXfermodeProc))->unref(); + canvas->drawRect(rect, paint); +} + +PlatformBitmap::PlatformBitmap() : surface_(0), platform_extra_(0) {} + +} // namespace skia diff --git a/skia/ext/platform_canvas.h b/skia/ext/platform_canvas.h new file mode 100644 index 0000000000..f20d62e27e --- /dev/null +++ b/skia/ext/platform_canvas.h @@ -0,0 +1,180 @@ +// 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 "third_party/skia/include/core/SkCanvas.h" + +namespace skia { + +// This class is a specialization of the regular SkCanvas that is designed to +// work with a PlatformDevice to manage platform-specific drawing. It allows +// using both Skia operations and platform-specific operations. +class SK_API PlatformCanvas : public SkCanvas { + public: + // If you use the version with no arguments, you MUST call initialize() + PlatformCanvas(); + // Set is_opaque if you are going to erase the bitmap and not use + // transparency: this will enable some optimizations. + PlatformCanvas(int width, int height, bool is_opaque); + +#if defined(WIN32) + // The shared_section parameter is passed to gfx::PlatformDevice::create. + // See it for details. + PlatformCanvas(int width, int height, bool is_opaque, HANDLE shared_section); +#elif defined(__APPLE__) + PlatformCanvas(int width, int height, bool is_opaque, + CGContextRef context); + PlatformCanvas(int width, int height, bool is_opaque, uint8_t* context); +#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. + PlatformCanvas(int width, int height, bool is_opaque, uint8_t* data); +#endif + + virtual ~PlatformCanvas(); + +#if defined(WIN32) + // For two-part init, call if you use the no-argument constructor above. Note + // that we want this to optionally match the Linux initialize if you only + // pass 3 arguments, hence the evil default argument. + bool initialize(int width, int height, bool is_opaque, + HANDLE shared_section = NULL); +#elif defined(__APPLE__) + // For two-part init, call if you use the no-argument constructor above + bool initialize(CGContextRef context, int width, int height, bool is_opaque); + bool initialize(int width, int height, bool is_opaque, uint8_t* data = NULL); + +#elif defined(__linux__) || defined(__FreeBSD__) || defined(__OpenBSD__) || \ + defined(__sun) || defined(ANDROID) + // For two-part init, call if you use the no-argument constructor above + bool initialize(int width, int height, bool is_opaque, uint8_t* data = NULL); +#endif + + // Shared -------------------------------------------------------------------- + + // 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. + static size_t StrideForWidth(unsigned width); + + // Allow callers to see the non-virtual function even though we have an + // override of a virtual one. + // FIXME(brettw) is this necessary? + using SkCanvas::clipRect; + + private: + // Helper method used internally by the initialize() methods. + bool initializeWithDevice(SkDevice* device); + + // Disallow copy and assign + PlatformCanvas(const PlatformCanvas&); + PlatformCanvas& operator=(const PlatformCanvas&); +}; + +// 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. +// +// If there is no layer that is not all clipped out, this will return a +// dummy device so callers do not have to check. If you are concerned about +// performance, check the clip before doing any painting. +// +// This is different than SkCanvas' getDevice, because that returns the +// bottommost device. +// +// Danger: the resulting device should not be saved. It will be invalidated +// by the next call to save() or restore(). +SK_API SkDevice* GetTopDevice(const SkCanvas& canvas); + +// Creates a canvas with raster bitmap backing. +// Set is_opaque if you are going to erase the bitmap and not use +// transparency: this will enable some optimizations. +SK_API SkCanvas* CreateBitmapCanvas(int width, int height, bool is_opaque); + +// Non-crashing version of CreateBitmapCanvas +// returns NULL if allocation fails for any reason. +// Use this instead of CreateBitmapCanvas in places that are likely to +// attempt to allocate very large canvases (therefore likely to fail), +// and where it is possible to recover gracefully from the failed allocation. +SK_API SkCanvas* TryCreateBitmapCanvas(int width, int height, bool is_opaque); + +// 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&); +}; + +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. + const SkBitmap& GetBitmap() { return bitmap_; } + + private: + SkBitmap bitmap_; + PlatformSurface surface_; // initialized to 0 + intptr_t platform_extra_; // initialized to 0, specific to each platform + + DISALLOW_COPY_AND_ASSIGN(PlatformBitmap); +}; + +} // namespace skia + +#endif // SKIA_EXT_PLATFORM_CANVAS_H_ diff --git a/skia/ext/platform_canvas_linux.cc b/skia/ext/platform_canvas_linux.cc new file mode 100644 index 0000000000..9912c16e08 --- /dev/null +++ b/skia/ext/platform_canvas_linux.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/platform_canvas.h" + +#include "base/debug/trace_event.h" +#include "skia/ext/bitmap_platform_device.h" +#include "skia/ext/platform_device.h" +#include "third_party/skia/include/core/SkTypes.h" + +namespace skia { + +PlatformCanvas::PlatformCanvas(int width, int height, bool is_opaque) { + TRACE_EVENT2("skia", "PlatformCanvas::PlatformCanvas", + "width", width, "height", height); + if (!initialize(width, height, is_opaque)) + SK_CRASH(); +} + +PlatformCanvas::PlatformCanvas(int width, int height, bool is_opaque, + uint8_t* data) { + TRACE_EVENT2("skia", "PlatformCanvas::PlatformCanvas", + "width", width, "height", height); + if (!initialize(width, height, is_opaque, data)) + SK_CRASH(); +} + +PlatformCanvas::~PlatformCanvas() { +} + +bool PlatformCanvas::initialize(int width, int height, bool is_opaque, + uint8_t* data) { + return initializeWithDevice(BitmapPlatformDevice::Create( + width, height, is_opaque, data)); +} + +} // namespace skia diff --git a/skia/ext/platform_canvas_mac.cc b/skia/ext/platform_canvas_mac.cc new file mode 100644 index 0000000000..d1667c2e05 --- /dev/null +++ b/skia/ext/platform_canvas_mac.cc @@ -0,0 +1,56 @@ +// 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 "base/debug/trace_event.h" +#include "skia/ext/bitmap_platform_device.h" +#include "third_party/skia/include/core/SkTypes.h" + +namespace skia { + +PlatformCanvas::PlatformCanvas(int width, int height, bool is_opaque) { + TRACE_EVENT2("skia", "PlatformCanvas::PlatformCanvas", + "width", width, "height", height); + initialize(width, height, is_opaque); +} + +PlatformCanvas::PlatformCanvas(int width, + int height, + bool is_opaque, + CGContextRef context) { + TRACE_EVENT2("skia", "PlatformCanvas::PlatformCanvas", + "width", width, "height", height); + initialize(context, width, height, is_opaque); +} + +PlatformCanvas::PlatformCanvas(int width, + int height, + bool is_opaque, + uint8_t* data) { + TRACE_EVENT2("skia", "PlatformCanvas::PlatformCanvas", + "width", width, "height", height); + initialize(width, height, is_opaque, data); +} + +PlatformCanvas::~PlatformCanvas() { +} + +bool PlatformCanvas::initialize(int width, + int height, + bool is_opaque, + uint8_t* data) { + return initializeWithDevice(BitmapPlatformDevice::CreateWithData( + data, width, height, is_opaque)); +} + +bool PlatformCanvas::initialize(CGContextRef context, + int width, + int height, + bool is_opaque) { + return initializeWithDevice(BitmapPlatformDevice::Create( + context, width, height, is_opaque)); +} + +} // namespace skia diff --git a/skia/ext/platform_canvas_skia.cc b/skia/ext/platform_canvas_skia.cc new file mode 100644 index 0000000000..9cd51aca41 --- /dev/null +++ b/skia/ext/platform_canvas_skia.cc @@ -0,0 +1,113 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "skia/ext/platform_canvas.h" + +#include "base/debug/trace_event.h" +#include "skia/ext/bitmap_platform_device.h" + +// TODO(reveman): a lot of unnecessary duplication of code from +// platform_canvas_[win|linux|mac].cc in here. Need to refactor +// PlatformCanvas to avoid this: +// http://code.google.com/p/chromium/issues/detail?id=119555 + +namespace skia { + +PlatformCanvas::PlatformCanvas(int width, int height, bool is_opaque) { + TRACE_EVENT2("skia", "PlatformCanvas::PlatformCanvas", + "width", width, "height", height); + if (!initialize(width, height, is_opaque)) + SK_CRASH(); +} + +#if defined(WIN32) +PlatformCanvas::PlatformCanvas(int width, + int height, + bool is_opaque, + HANDLE shared_section) { + TRACE_EVENT2("skia", "PlatformCanvas::PlatformCanvas", + "width", width, "height", height); + if (!initialize(width, height, is_opaque, shared_section)) + SK_CRASH(); +} +#elif defined(__APPLE__) +PlatformCanvas::PlatformCanvas(int width, int height, bool is_opaque, + uint8_t* data) { + TRACE_EVENT2("skia", "PlatformCanvas::PlatformCanvas", + "width", width, "height", height); + if (!initialize(width, height, is_opaque, data)) + SK_CRASH(); +} +PlatformCanvas::PlatformCanvas(int width, + int height, + bool is_opaque, + CGContextRef context) { + TRACE_EVENT2("skia", "PlatformCanvas::PlatformCanvas", + "width", width, "height", height); + if (!initialize(context, width, height, is_opaque)) + SK_CRASH(); +} +#elif defined(__linux__) || defined(__FreeBSD__) || defined(__OpenBSD__) || \ + defined(__sun) || defined(ANDROID) +PlatformCanvas::PlatformCanvas(int width, int height, bool is_opaque, + uint8_t* data) { + TRACE_EVENT2("skia", "PlatformCanvas::PlatformCanvas", + "width", width, "height", height); + if (!initialize(width, height, is_opaque, data)) + SK_CRASH(); +} +#endif + +PlatformCanvas::~PlatformCanvas() { +} + +#if defined(WIN32) +bool PlatformCanvas::initialize(int width, + int height, + bool is_opaque, + HANDLE shared_section) { + // Use platform specific device for shared_section. + if (shared_section) + return initializeWithDevice(BitmapPlatformDevice::Create( + width, height, is_opaque, shared_section)); + + return initializeWithDevice(new SkDevice( + SkBitmap::kARGB_8888_Config, width, height, is_opaque)); +} +#elif defined(__APPLE__) +bool PlatformCanvas::initialize(int width, + int height, + bool is_opaque, + uint8_t* data) { + // Use platform specific device for data. + if (data) + return initializeWithDevice(BitmapPlatformDevice::CreateWithData( + data, width, height, is_opaque)); + + return initializeWithDevice(new SkDevice( + SkBitmap::kARGB_8888_Config, width, height, is_opaque)); +} + +bool PlatformCanvas::initialize(CGContextRef context, + int width, + int height, + bool is_opaque) { + return initializeWithDevice(BitmapPlatformDevice::Create( + context, width, height, is_opaque)); +} +#elif defined(__linux__) || defined(__FreeBSD__) || defined(__OpenBSD__) || \ + defined(__sun) || defined(ANDROID) +bool PlatformCanvas::initialize(int width, int height, bool is_opaque, + uint8_t* data) { + // Use platform specific device for data. + if (data) + return initializeWithDevice(BitmapPlatformDevice::Create( + width, height, is_opaque, data)); + + return initializeWithDevice(new SkDevice( + SkBitmap::kARGB_8888_Config, width, height, is_opaque)); +} +#endif + +} // namespace skia diff --git a/skia/ext/platform_canvas_unittest.cc b/skia/ext/platform_canvas_unittest.cc new file mode 100644 index 0000000000..5679721977 --- /dev/null +++ b/skia/ext/platform_canvas_unittest.cc @@ -0,0 +1,398 @@ +// Copyright (c) 2012 The Chromium 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 "skia/ext/platform_canvas.h" +#include "skia/ext/platform_device.h" +#include "testing/gtest/include/gtest/gtest.h" + +#include "SkColor.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. + PlatformCanvas canvas(16, 16, true); + canvas.drawColor(SK_ColorWHITE); + + // Make a layer and fill it completely to make sure that the bounds are + // correct. + { + LayerSaver layer(canvas, kLayerX, kLayerY, kLayerW, kLayerH); + canvas.drawColor(SK_ColorBLACK); + } + EXPECT_TRUE(VerifyBlackRect(canvas, kLayerX, kLayerY, kLayerW, kLayerH)); +} + +#if !defined(USE_AURA) // http://crbug.com/154358 + +// Test native clipping. +TEST(PlatformCanvas, ClipRegion) { + // Initialize a white canvas + PlatformCanvas canvas(16, 16, true); + canvas.drawColor(SK_ColorWHITE); + EXPECT_TRUE(VerifyCanvasColor(canvas, SK_ColorWHITE)); + + // Test that initially the canvas has no clip region, by filling it + // with a black rectangle. + // Note: Don't use LayerSaver, since internally it sets a clip region. + 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. + PlatformCanvas canvas(16, 16, true); + + // Make a layer and fill it completely to make sure that the bounds are + // correct. + canvas.drawColor(SK_ColorWHITE); + { + LayerSaver layer(canvas, kLayerX, kLayerY, kLayerW, kLayerH); + DrawNativeRect(canvas, 0, 0, 100, 100); +#if defined(OS_WIN) + MakeOpaque(&canvas, 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, 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, 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, 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. + PlatformCanvas canvas(16, 16, true); + + // Make a layer and fill it completely to make sure that the bounds are + // correct. + canvas.drawColor(SK_ColorWHITE); + canvas.save(); + canvas.translate(1, 1); + { + LayerSaver layer(canvas, kLayerX, kLayerY, kLayerW, kLayerH); + DrawNativeRect(canvas, 0, 0, 100, 100); +#if defined(OS_WIN) + MakeOpaque(&canvas, 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, 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, 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, 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, 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) + +} // namespace skia diff --git a/skia/ext/platform_canvas_win.cc b/skia/ext/platform_canvas_win.cc new file mode 100644 index 0000000000..7b9a787298 --- /dev/null +++ b/skia/ext/platform_canvas_win.cc @@ -0,0 +1,40 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include <windows.h> +#include <psapi.h> + +#include "base/debug/trace_event.h" +#include "skia/ext/bitmap_platform_device_win.h" +#include "skia/ext/platform_canvas.h" + +namespace skia { + +PlatformCanvas::PlatformCanvas(int width, int height, bool is_opaque) { + TRACE_EVENT2("skia", "PlatformCanvas::PlatformCanvas", + "width", width, "height", height); + initialize(width, height, is_opaque, NULL); +} + +PlatformCanvas::PlatformCanvas(int width, + int height, + bool is_opaque, + HANDLE shared_section) { + TRACE_EVENT2("skia", "PlatformCanvas::PlatformCanvas", + "width", width, "height", height); + initialize(width, height, is_opaque, shared_section); +} + +PlatformCanvas::~PlatformCanvas() { +} + +bool PlatformCanvas::initialize(int width, + int height, + bool is_opaque, + HANDLE shared_section) { + return initializeWithDevice(BitmapPlatformDevice::Create( + width, height, is_opaque, shared_section)); +} + +} // namespace skia diff --git a/skia/ext/platform_device.cc b/skia/ext/platform_device.cc new file mode 100644 index 0000000000..83a191862d --- /dev/null +++ b/skia/ext/platform_device.cc @@ -0,0 +1,79 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#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) { + 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/skia/ext/platform_device.h b/skia/ext/platform_device.h new file mode 100644 index 0000000000..69efb3f723 --- /dev/null +++ b/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_SUN) +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_SUN) +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/skia/ext/platform_device_linux.cc b/skia/ext/platform_device_linux.cc new file mode 100644 index 0000000000..f72e6148c4 --- /dev/null +++ b/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/skia/ext/platform_device_mac.cc b/skia/ext/platform_device_mac.cc new file mode 100644 index 0000000000..6ff017d5cd --- /dev/null +++ b/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/skia/ext/platform_device_win.cc b/skia/ext/platform_device_win.cc new file mode 100644 index 0000000000..f44e66eb43 --- /dev/null +++ b/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/skia/ext/skia_sandbox_support_win.cc b/skia/ext/skia_sandbox_support_win.cc new file mode 100644 index 0000000000..13efb80fdb --- /dev/null +++ b/skia/ext/skia_sandbox_support_win.cc @@ -0,0 +1,24 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "skia_sandbox_support_win.h" +#include "SkFontHost.h" +#include "SkTypeface_win.h" + +static SkiaEnsureTypefaceAccessible g_skia_ensure_typeface_accessible = NULL; + +SK_API void SetSkiaEnsureTypefaceAccessible(SkiaEnsureTypefaceAccessible func) { + // This function is supposed to be called once in process life time. + SkASSERT(g_skia_ensure_typeface_accessible == NULL); + g_skia_ensure_typeface_accessible = func; +} + +// static +void SkFontHost::EnsureTypefaceAccessible(const SkTypeface& typeface) { + if (g_skia_ensure_typeface_accessible) { + LOGFONT lf; + SkLOGFONTFromTypeface(&typeface, &lf); + g_skia_ensure_typeface_accessible(lf); + } +} diff --git a/skia/ext/skia_sandbox_support_win.h b/skia/ext/skia_sandbox_support_win.h new file mode 100644 index 0000000000..47615bf443 --- /dev/null +++ b/skia/ext/skia_sandbox_support_win.h @@ -0,0 +1,16 @@ +// 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_SKIA_SANDBOX_SUPPORT_WIN_H_ +#define SKIA_EXT_SKIA_SANDBOX_SUPPORT_WIN_H_ + +#include <windows.h> + +#include "SkPreConfig.h" + +typedef void (*SkiaEnsureTypefaceAccessible)(const LOGFONT& font); + +SK_API void SetSkiaEnsureTypefaceAccessible(SkiaEnsureTypefaceAccessible func); + +#endif // SKIA_EXT_SKIA_SANDBOX_SUPPORT_WIN_H_ diff --git a/skia/ext/skia_trace_shim.h b/skia/ext/skia_trace_shim.h new file mode 100644 index 0000000000..62bdecc4ce --- /dev/null +++ b/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/skia/ext/skia_utils_ios.h b/skia/ext/skia_utils_ios.h new file mode 100644 index 0000000000..f213cf0f79 --- /dev/null +++ b/skia/ext/skia_utils_ios.h @@ -0,0 +1,33 @@ +// 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; +#else +class UIImage; +#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); + +} // namespace gfx + +#endif // SKIA_EXT_SKIA_UTILS_IOS_H_ diff --git a/skia/ext/skia_utils_ios.mm b/skia/ext/skia_utils_ios.mm new file mode 100644 index 0000000000..fba12df795 --- /dev/null +++ b/skia/ext/skia_utils_ios.mm @@ -0,0 +1,72 @@ +// 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 <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::mac::ScopedCFTypeRef<CGColorSpaceRef> color_space( + CGColorSpaceCreateDeviceRGB()); + base::mac::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); + 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::mac::ScopedCFTypeRef<CGImageRef> cg_image( + SkCreateCGImageRefWithColorspace(skia_bitmap, color_space)); + + // Now convert to UIImage. + return [UIImage imageWithCGImage:cg_image.get() + scale:scale + orientation:UIImageOrientationUp]; +} + +} // namespace gfx diff --git a/skia/ext/skia_utils_mac.h b/skia/ext/skia_utils_mac.h new file mode 100644 index 0000000000..ad5fc93d70 --- /dev/null +++ b/skia/ext/skia_utils_mac.h @@ -0,0 +1,120 @@ +// Copyright (c) 2012 The Chromium 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. +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 to NSColor. +SK_API NSColor* SkColorToCalibratedNSColor(SkColor color); +SK_API NSColor* SkColorToDeviceNSColor(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/skia/ext/skia_utils_mac.mm b/skia/ext/skia_utils_mac.mm new file mode 100644 index 0000000000..e95635b1b8 --- /dev/null +++ b/skia/ext/skia_utils_mac.mm @@ -0,0 +1,418 @@ +// Copyright (c) 2012 The Chromium Authors. 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/memory/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::mac::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::mac::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]; +} + +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::mac::ScopedCFTypeRef<CGColorSpaceRef> color_space( + CGColorSpaceCreateWithName(kCGColorSpaceGenericRGB)); + return SkBitmapToNSBitmapImageRepWithColorSpace(skiaBitmap, color_space); +} + +NSBitmapImageRep* SkBitmapToNSBitmapImageRepWithColorSpace( + const SkBitmap& skiaBitmap, + CGColorSpaceRef colorSpace) { + // First convert SkBitmap to CGImageRef. + base::mac::ScopedCFTypeRef<CGImageRef> cgimage( + SkCreateCGImageRefWithColorspace(skiaBitmap, colorSpace)); + + // Now convert to NSBitmapImageRep. + scoped_nsobject<NSBitmapImageRep> bitmap( + [[NSBitmapImageRep alloc] initWithCGImage:cgimage]); + return [bitmap.release() autorelease]; +} + +NSImage* SkBitmapToNSImageWithColorSpace(const SkBitmap& skiaBitmap, + CGColorSpaceRef colorSpace) { + if (skiaBitmap.isNull()) + return nil; + + // First convert SkBitmap to CGImageRef. + base::mac::ScopedCFTypeRef<CGImageRef> cgimage( + SkCreateCGImageRefWithColorspace(skiaBitmap, colorSpace)); + + // Now convert to NSImage. + scoped_nsobject<NSBitmapImageRep> bitmap( + [[NSBitmapImageRep alloc] initWithCGImage:cgimage]); + scoped_nsobject<NSImage> image([[NSImage alloc] init]); + [image addRepresentation:bitmap]; + [image setSize:NSMakeSize(skiaBitmap.width(), skiaBitmap.height())]; + return [image.release() autorelease]; +} + +NSImage* SkBitmapToNSImage(const SkBitmap& skiaBitmap) { + base::mac::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); + } + cgContext_ = CGBitmapContextCreate(bitmap_.getPixels(), bitmap_.width(), + bitmap_.height(), 8, bitmap_.rowBytes(), CGColorSpaceCreateDeviceRGB(), + 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/skia/ext/skia_utils_mac_unittest.mm b/skia/ext/skia_utils_mac_unittest.mm new file mode 100644 index 0000000000..353c52a380 --- /dev/null +++ b/skia/ext/skia_utils_mac_unittest.mm @@ -0,0 +1,244 @@ +// Copyright (c) 2012 The Chromium Authors. 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 "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) { + 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/skia/ext/skia_utils_win.cc b/skia/ext/skia_utils_win.cc new file mode 100644 index 0000000000..4988a3a8c8 --- /dev/null +++ b/skia/ext/skia_utils_win.cc @@ -0,0 +1,70 @@ +// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "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) { + // Currently, Alpha is always 255 or the color is 0 so there is no need to + // demultiply the channels. If this DCHECK() is ever hit, the full + // (SkColorGetX(color) * 255 / a) will have to be added in the conversion. + SkASSERT((0xFF == SkColorGetA(color)) || (0 == color)); +#ifndef _MSC_VER + return RGB(SkColorGetR(color), SkColorGetG(color), SkColorGetB(color)); +#else + // 0BGR = ((ARGB -> BGRA) >> 8) + return (_byteswap_ulong(color) >> 8); +#endif +} + +} // namespace skia + diff --git a/skia/ext/skia_utils_win.h b/skia/ext/skia_utils_win.h new file mode 100644 index 0000000000..98e1b999be --- /dev/null +++ b/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/skia/ext/vector_canvas.cc b/skia/ext/vector_canvas.cc new file mode 100644 index 0000000000..96c23e92c3 --- /dev/null +++ b/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) { + setDevice(device)->unref(); // Created with refcount 1, and setDevice refs. +} + +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/skia/ext/vector_canvas.h b/skia/ext/vector_canvas.h new file mode 100644 index 0000000000..e7a67fce9d --- /dev/null +++ b/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/skia/ext/vector_canvas_unittest.cc b/skia/ext/vector_canvas_unittest.cc new file mode 100644 index 0000000000..d5f7f41932 --- /dev/null +++ b/skia/ext/vector_canvas_unittest.cc @@ -0,0 +1,968 @@ +// Copyright (c) 2012 The Chromium Authors. 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/string_util.h" +#include "base/stringprintf.h" +#include "base/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 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 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. + FilePath::StringType tmp(test_dir_.value()); + for (size_t i = 0; i < tmp.size(); ++i) + tmp[i] = base::ToLowerASCII(tmp[i]); + test_dir_ = FilePath(tmp); + + if (action_ == GENERATE) { + // Make sure the directory exist. + file_util::CreateDirectory(test_dir_); + } + } + + // Returns the fully qualified path of a data file. + FilePath test_file(const 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, + 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 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 FilePath::StringType& filename) const { + Image(canvas).SaveToFile(test_file(filename)); + } + + ProcessAction action_; + + // Path to directory used to contain the test data. + 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 FilePath& filename, + SkBitmap* bitmap, + bool is_opaque) { + std::string compressed; + file_util::ReadFileToString(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_ = new PlatformCanvas(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 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 }; + SkPathEffect* effect = new SkDashPathEffect(intervals, arraysize(intervals), + 0); + paint.setPathEffect(effect)->unref(); + paint.setColor(SK_ColorMAGENTA); + paint.setStyle(SkPaint::kStroke_Style); + + vcanvas_->drawLine(10, 10, 90, 10, paint); + pcanvas_->drawLine(10, 10, 90, 10, paint); + } + EXPECT_EQ(0., ProcessImage(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 }; + SkPathEffect* effect = new SkDashPathEffect(intervals, arraysize(intervals), + 0); + paint.setPathEffect(effect)->unref(); + paint.setColor(SK_ColorMAGENTA); + paint.setStyle(SkPaint::kStroke_Style); + + SkPath path; + path.moveTo(10, 15); + path.lineTo(90, 15); + path.lineTo(90, 90); + vcanvas_->drawPath(path, paint); + pcanvas_->drawPath(path, paint); + } + EXPECT_EQ(0., ProcessImage(FILE_PATH_LITERAL("dash_path"))); + + { + SkPaint paint; + SkScalar intervals[] = { 2, 1 }; + SkPathEffect* effect = new SkDashPathEffect(intervals, arraysize(intervals), + 0); + paint.setPathEffect(effect)->unref(); + paint.setColor(SK_ColorMAGENTA); + paint.setStyle(SkPaint::kStroke_Style); + + vcanvas_->drawRectCoords(20, 20, 30, 30, paint); + pcanvas_->drawRectCoords(20, 20, 30, 30, paint); + } + EXPECT_EQ(0., ProcessImage(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 }; + SkPathEffect* effect = new SkDashPathEffect(intervals, arraysize(intervals), + 0); + paint.setPathEffect(effect)->unref(); + paint.setColor(SK_ColorMAGENTA); + paint.setStyle(SkPaint::kStroke_Style); + + SkPath path; + path.addCircle(50, 75, 10); + vcanvas_->drawPath(path, paint); + pcanvas_->drawPath(path, paint); + EXPECT_EQ(0., ProcessImage(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/skia/ext/vector_platform_device_emf_win.cc b/skia/ext/vector_platform_device_emf_win.cc new file mode 100644 index 0000000000..73be49119f --- /dev/null +++ b/skia/ext/vector_platform_device_emf_win.cc @@ -0,0 +1,874 @@ +// Copyright (c) 2012 The Chromium Authors. 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 "skia/ext/vector_platform_device_emf_win.h" + +#include "skia/ext/bitmap_platform_device.h" +#include "skia/ext/skia_utils_win.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); + SkSafeUnref(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); + SkSafeUnref(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::drawBitmap(const SkDraw& draw, + const SkBitmap& bitmap, + const SkIRect* srcRectOrNull, + 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; + } +} + +void VectorPlatformDeviceEmf::drawText(const SkDraw& draw, + const void* text, + size_t byteLength, + SkScalar x, + SkScalar y, + const SkPaint& paint) { + SkGDIFontSetup setup; + if (SkPaint::kUTF8_TextEncoding != paint.getTextEncoding() + && setup.useGDI(hdc_, paint)) { + UINT options = getTextOutOptions(paint); + UINT count = byteLength >> 1; + ExtTextOut(hdc_, SkScalarRound(x), SkScalarRound(y + getAscent(paint)), + options, 0, reinterpret_cast<const wchar_t*>(text), count, NULL); + } else { + 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; + 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; + } + ExtTextOut(hdc_, startX, startY, getTextOutOptions(paint), 0, + reinterpret_cast<const wchar_t*>(text), count, advances); + } else { + 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::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/skia/ext/vector_platform_device_emf_win.h b/skia/ext/vector_platform_device_emf_win.h new file mode 100644 index 0000000000..6d84d3a8be --- /dev/null +++ b/skia/ext/vector_platform_device_emf_win.h @@ -0,0 +1,132 @@ +// 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 drawBitmap(const SkDraw& draw, const SkBitmap& bitmap, + const SkIRect* srcRectOrNull, + 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); +}; + +} // namespace skia + +#endif // SKIA_EXT_VECTOR_PLATFORM_DEVICE_EMF_WIN_H_ diff --git a/skia/ext/vector_platform_device_skia.cc b/skia/ext/vector_platform_device_skia.cc new file mode 100644 index 0000000000..14376222b4 --- /dev/null +++ b/skia/ext/vector_platform_device_skia.cc @@ -0,0 +1,89 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#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_ = BitmapPlatformDevice::CreateAndClear(width(), height(), + false); + raster_surface_->unref(); // SkRefPtr and create both took a reference. + 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_ = NULL; +} + +#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/skia/ext/vector_platform_device_skia.h b/skia/ext/vector_platform_device_skia.h new file mode 100644 index 0000000000..0575563f98 --- /dev/null +++ b/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 "third_party/skia/include/core/SkRefCnt.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: + SkRefPtr<BitmapPlatformDevice> raster_surface_; + + DISALLOW_COPY_AND_ASSIGN(VectorPlatformDeviceSkia); +}; + +} // namespace skia + +#endif // SKIA_EXT_VECTOR_PLATFORM_DEVICE_SKIA_H_ diff --git a/skia/fix_for_1186198.diff b/skia/fix_for_1186198.diff new file mode 100644 index 0000000000..6158f594a2 --- /dev/null +++ b/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/skia/skia.gyp b/skia/skia.gyp new file mode 100644 index 0000000000..37d6c2e497 --- /dev/null +++ b/skia/skia.gyp @@ -0,0 +1,853 @@ +# Copyright (c) 2012 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +{ + 'targets': [ + { + 'target_name': 'skia', + 'type': '<(component)', + 'variables': { + 'conditions': [ + ['OS== "ios"', { + 'skia_support_gpu': 0, + }, { + 'skia_support_gpu': 1, + }], + + ['inside_chromium_build==0', { + 'webkit_src_dir': '<(DEPTH)/../../..', + },{ + 'webkit_src_dir': '<(DEPTH)/third_party/WebKit', + }], + ], + + 'optimize': 'max', + + # 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', + }, + + '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', + + '../third_party/skia/src/images/bmpdecoderhelper.cpp', + '../third_party/skia/src/images/bmpdecoderhelper.h', + #'../third_party/skia/src/images/SkFDStream.cpp', + #'../third_party/skia/src/images/SkFlipPixelRef.cpp', + '../third_party/skia/src/images/SkImageDecoder.cpp', + '../third_party/skia/src/images/SkImageDecoder_Factory.cpp', + #'../third_party/skia/src/images/SkImageDecoder_fpdfemb.cpp', + #'../third_party/skia/src/images/SkImageDecoder_libbmp.cpp', + #'../third_party/skia/src/images/SkImageDecoder_libgif.cpp', + #'../third_party/skia/src/images/SkImageDecoder_libico.cpp', + #'../third_party/skia/src/images/SkImageDecoder_libjpeg.cpp', + #'../third_party/skia/src/images/SkImageDecoder_libpng.cpp', + #'../third_party/skia/src/images/SkImageDecoder_libpvjpeg.cpp', + #'../third_party/skia/src/images/SkImageDecoder_wbmp.cpp', + #'../third_party/skia/src/images/SkImageEncoder.cpp', + #'../third_party/skia/src/images/SkImageEncoder_Factory.cpp', + #'../third_party/skia/src/images/SkImageRef.cpp', + #'../third_party/skia/src/images/SkImageRefPool.cpp', + #'../third_party/skia/src/images/SkImageRefPool.h', + #'../third_party/skia/src/images/SkImageRef_GlobalPool.cpp', + #'../third_party/skia/src/images/SkMovie.cpp', + #'../third_party/skia/src/images/SkMovie_gif.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/SkPDFPage.cpp', + '../third_party/skia/src/pdf/SkPDFPage.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/FontHostConfiguration_android.cpp', + '../third_party/skia/src/ports/SkFontDescriptor.cpp', + '../third_party/skia/src/ports/SkFontDescriptor.h', + #'../third_party/skia/src/ports/SkFontHost_FONTPATH.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/SkFontHost_android.cpp', + #'../third_party/skia/src/ports/SkFontHost_ascender.cpp', + '../third_party/skia/src/ports/SkFontHost_tables.cpp', + #'../third_party/skia/src/ports/SkFontHost_linux.cpp', + '../third_party/skia/src/ports/SkFontHost_mac.cpp', + #'../third_party/skia/src/ports/SkFontHost_none.cpp', + '../third_party/skia/src/ports/SkFontHost_sandbox_none.cpp', + '../third_party/skia/src/ports/SkFontHost_win.cpp', + '../third_party/skia/src/ports/SkGlobalInitialization_chromium.cpp', + #'../third_party/skia/src/ports/SkImageDecoder_CG.cpp', + #'../third_party/skia/src/ports/SkImageDecoder_empty.cpp', + #'../third_party/skia/src/ports/SkImageRef_ashmem.cpp', + #'../third_party/skia/src/ports/SkImageRef_ashmem.h', + #'../third_party/skia/src/ports/SkOSEvent_android.cpp', + #'../third_party/skia/src/ports/SkOSEvent_dummy.cpp', + '../third_party/skia/src/ports/SkOSFile_stdio.cpp', + #'../third_party/skia/src/ports/SkThread_none.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/SkXMLParser_empty.cpp', + #'../third_party/skia/src/ports/SkXMLParser_expat.cpp', + #'../third_party/skia/src/ports/SkXMLParser_tinyxml.cpp', + #'../third_party/skia/src/ports/SkXMLPullParser_expat.cpp', + + '../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/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/include/pdf/SkPDFDevice.h', + '../third_party/skia/include/pdf/SkPDFDocument.h', + + '../third_party/skia/include/ports/SkTypeface_win.h', + + '../third_party/skia/include/images/SkFlipPixelRef.h', + '../third_party/skia/include/images/SkImageDecoder.h', + '../third_party/skia/include/images/SkImageEncoder.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', + + '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/SkThread_chrome.cc', + 'ext/platform_canvas.cc', + 'ext/platform_canvas.h', + 'ext/platform_canvas_linux.cc', + 'ext/platform_canvas_mac.cc', + 'ext/platform_canvas_skia.cc', + 'ext/platform_canvas_win.cc', + 'ext/platform_device.cc', + 'ext/platform_device.h', + 'ext/platform_device_linux.cc', + 'ext/platform_device_mac.cc', + 'ext/platform_device_win.cc', + 'ext/SkMemory_new_handler.cpp', + 'ext/skia_sandbox_support_win.h', + 'ext/skia_sandbox_support_win.cc', + 'ext/skia_trace_shim.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', + ], + '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/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/sfnt', + '../third_party/skia/src/utils', + ], + 'msvs_disabled_warnings': [4244, 4267, 4341, 4345, 4390, 4554, 4748, 4800], + 'defines': [ + #'SK_GAMMA_SRGB', + #'SK_GAMMA_APPLY_TO_A8', + 'SK_BUILD_NO_IMAGE_ENCODE', + 'GR_GL_CUSTOM_SETUP_HEADER="GrGLConfig_chrome.h"', + 'GR_STATIC_RECT_VB=1', + 'GR_AGGRESSIVE_SHADER_OPTS=1', + 'SK_DISABLE_FAST_AA_STROKE_RECT', + 'SK_DEFERRED_CANVAS_USES_GPIPE=1', + + # 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', + + # Temporarily keep old int-srcrect behavior, until we determine if + # the few failures are a bug or not. + 'SK_SUPPORT_INT_SRCRECT_DRAWBITMAPRECT', + ], + 'sources!': [ + '../third_party/skia/include/core/SkTypes.h', + ], + 'conditions': [ + ['skia_support_gpu != 0', { + 'includes': [ + '../third_party/skia/gyp/gpu.gypi', + ], + 'sources': [ + '<@(gr_sources)', + '<@(skgr_sources)', + ], + 'include_dirs': [ + '../third_party/skia/include/gpu', + '../third_party/skia/include/gpu/gl', + '../third_party/skia/src/gpu', + ], + }, { # skia_support_gpu == 0 + 'defines': [ + 'SK_SUPPORT_GPU=0', + ], + }], + ['release_valgrind_build == 1', { + 'defines': [ + 'SK_DEBUG_PATH_REF=1', + ], + 'direct_dependent_settings': { + 'defines': [ + 'SK_DEBUG_PATH_REF=1', + ], + }, + }], + #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" or 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', + ], + }, + 'sources!': [ + 'ext/SkThread_chrome.cc', + ], + }], + [ 'OS != "android"', { + 'sources/': [ + ['exclude', '_android\\.(cc|cpp)$'], + ], + 'defines': [ + 'SK_DEFAULT_FONT_CACHE_LIMIT=(20*1024*1024)', + ], + }], + [ 'OS != "ios"', { + 'sources/': [ + ['exclude', '_ios\\.(cc|cpp|mm?)$'], + ], + 'dependencies': [ + '<(webkit_src_dir)/Source/WebKit/chromium/skia_webkit.gyp:skia_webkit', + ], + }], + [ 'OS != "mac"', { + 'sources/': [ + ['exclude', '_mac\\.(cc|cpp|mm?)$'], + ['exclude', '/mac/'] + ], + }], + [ 'OS != "win"', { + 'sources/': [ ['exclude', '_win\\.(cc|cpp)$'] ], + }], + [ 'chromeos == 1', { + 'defines': [ + # Temporarily use SkPaint to keep a scale factor needed for correct + # font rendering in high DPI mode. + # See https://codereview.appspot.com/6495089/ + 'SK_SUPPORT_HINTING_SCALE_FACTOR', + ], + }], + [ 'armv7 == 1', { + 'defines': [ + '__ARM_ARCH__=7', + ], + }], + [ 'armv7 == 1 and arm_neon == 1', { + 'defines': [ + '__ARM_HAVE_NEON', + ], + }], + [ '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/harfbuzz/harfbuzz.gyp:harfbuzz', + '../third_party/icu/icu.gyp:icuuc', + ], + 'cflags': [ + '-Wno-unused', + '-Wno-unused-function', + ], + 'sources': [ + 'ext/SkFontHost_fontconfig.cpp', + 'ext/SkFontHost_fontconfig_direct.cpp', + ], + 'defines': [ +# 'SK_USE_COLOR_LUMINANCE', + ], + }], + [ 'use_glib == 0 and OS != "android"', { + 'sources/': [ ['exclude', '_linux\\.(cc|cpp)$'] ], + 'sources!': [ + '../third_party/skia/src/ports/SkFontHost_FreeType.cpp', + '../third_party/skia/src/ports/SkFontHost_FreeType_common.cpp', + ], + }], + [ 'use_aura == 1 and use_canvas_skia == 1', { + 'sources/': [ + ['exclude', 'ext/platform_canvas_mac\\.cc$'], + ['exclude', 'ext/platform_canvas_linux\\.cc$'], + ['exclude', 'ext/platform_canvas_win\\.cc$'], + ], + }, { # use_aura == 0 and use_canvas_skia == 1 + 'sources/': [ ['exclude', 'ext/platform_canvas_skia\\.cc$'] ], + }], + [ 'toolkit_uses_gtk == 1', { + 'dependencies': [ + '../build/linux/system.gyp:gdk', + ], + }, { # toolkit_uses_gtk == 0 + 'sources/': [ ['exclude', '_gtk\\.(cc|cpp)$'] ], + }], + [ 'OS == "android"', { + 'sources/': [ + ['exclude', '_linux\\.(cc|cpp)$'], + ], + 'conditions': [ + [ '_toolset == "target"', { + 'defines': [ + 'HAVE_PTHREADS', + 'OS_ANDROID', + 'SK_BUILD_FOR_ANDROID_NDK', + # Android devices are typically more memory constrained, so + # use a smaller glyph cache. + 'SK_DEFAULT_FONT_CACHE_LIMIT=(8*1024*1024)', + 'USE_CHROMIUM_SKIA', + ], + 'dependencies': [ + '../third_party/expat/expat.gyp:expat', + '../third_party/freetype/freetype.gyp:ft2', + '../third_party/harfbuzz/harfbuzz.gyp:harfbuzz', + 'skia_opts' + ], + 'dependencies!': [ + # Android doesn't use Skia's PDF generation, which is what uses + # sfntly. + '../third_party/sfntly/sfntly.gyp:sfntly', + ], + # 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', + ], + 'sources/': [ + ['include', 'ext/platform_device_linux\\.cc$'], + ['include', 'ext/platform_canvas_linux\\.cc$'], + ['exclude', '../third_party/skia/src/pdf/'], + ], + 'sources!': [ + 'ext/vector_platform_device_skia.cc', + ], + 'export_dependent_settings': [ + '../third_party/harfbuzz/harfbuzz.gyp:harfbuzz', + ], + }], + [ '_toolset == "target" and android_build_type == 0', { + 'defines': [ + 'HAVE_ENDIAN_H', + ], + }], + [ '_toolset=="host" and host_os=="linux"', { + 'sources': [ + 'ext/platform_device_linux.cc', + 'ext/platform_canvas_linux.cc', + ], + }], + ], + }], + [ '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', + ], + 'dependencies': [ + 'skia_opts_ios', + ], + 'dependencies!': [ + 'skia_opts', + '../third_party/sfntly/sfntly.gyp:sfntly', + ], + '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', '/pdf/'], + ['exclude', '^ext/vector_platform_device_skia\\.'], + ['exclude', 'opts_check_SSE2\\.cpp$'], + ['exclude', 'SkFontHost_tables\\.cpp$',], + ], + }], + [ 'OS == "mac"', { + 'defines': [ + 'SK_BUILD_FOR_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', + ], + 'sources!': [ + # The mac's fonthost implements the table methods natively, + # so no need for these generic versions. + '../third_party/skia/src/ports/SkFontHost_tables.cpp', + ], + 'conditions': [ + [ 'use_skia == 0', { + 'sources/': [ + ['exclude', '/pdf/'], + ['exclude', 'ext/vector_platform_device_skia\\.(cc|h)'], + ], + }, + { # use_skia + 'defines': [ + 'SK_USE_MAC_CORE_TEXT', +# 'SK_USE_COLOR_LUMINANCE', + ], + }], + ], + }], + [ 'OS == "win"', { + 'sources!': [ + '../third_party/skia/src/core/SkMMapStream.cpp', + '../third_party/skia/src/ports/SkFontHost_sandbox_none.cpp', + '../third_party/skia/src/ports/SkThread_pthread.cpp', + '../third_party/skia/src/ports/SkTime_Unix.cpp', + 'ext/SkThread_chrome.cc', + ], + 'include_dirs': [ + 'config/win', + ], + 'direct_dependent_settings': { + 'include_dirs': [ + 'config/win', + ], + }, + }], + ['component=="shared_library"', { + 'defines': [ + 'GR_DLL=1', + 'GR_IMPLEMENTATION=1', + 'SKIA_DLL', + 'SKIA_IMPLEMENTATION=1', + ], + 'dependencies': [ + '../base/base.gyp:base', + ], + 'direct_dependent_settings': { + 'defines': [ + 'GR_DLL', + 'SKIA_DLL', + ], + }, + }], + ], + 'dependencies': [ + 'skia_opts', + '../base/third_party/dynamic_annotations/dynamic_annotations.gyp:dynamic_annotations', + '../third_party/sfntly/sfntly.gyp:sfntly', + '../third_party/zlib/zlib.gyp:zlib', + ], + 'direct_dependent_settings': { + 'include_dirs': [ + 'config', + + #temporary until we can hide SkFontHost + '../third_party/skia/src/core', + + '../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/gpu/gl', + '../third_party/skia/include/pipe', + '../third_party/skia/include/ports', + '../third_party/skia/include/utils', + 'ext', + ], + 'defines': [ + 'SK_BUILD_NO_IMAGE_ENCODE', + 'SK_DEFERRED_CANVAS_USES_GPIPE=1', + 'GR_GL_CUSTOM_SETUP_HEADER="GrGLConfig_chrome.h"', + 'GR_AGGRESSIVE_SHADER_OPTS=1', + ], + 'conditions': [ + [ 'chromeos == 1', { + 'defines': [ + 'SK_SUPPORT_HINTING_SCALE_FACTOR', + ], + }], + ['OS=="android"', { + 'dependencies!': [ + 'skia_opts', + '../third_party/zlib/zlib.gyp:zlib', + ], + 'defines': [ + # Don't use non-NDK available stuff. + 'SK_BUILD_FOR_ANDROID_NDK', + ], + 'conditions': [ + [ '_toolset == "target" and android_build_type == 0', { + 'defines': [ + 'HAVE_ENDIAN_H', + ], + }], + ], + }], + ['OS=="mac"', { + 'include_dirs': [ + '../third_party/skia/include/utils/mac', + ], + }], + ], + }, + '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$',], + ], + }], + ], + }, + + # 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', + 'variables': { + 'optimize': 'max', + }, + '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/utils', + '../third_party/skia/src/core', + ], + 'conditions': [ + [ 'os_posix == 1 and OS != "mac" and OS != "android" and \ + target_arch != "arm" and target_arch != "mipsel"', { + 'cflags': [ + '-msse2', + ], + }], + [ 'OS == "android"', { + 'defines': [ + 'SK_BUILD_FOR_ANDROID_NDK', + ], + }], + [ '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', + ], + 'conditions': [ + # x86 Android doesn't support SSSE3 instructions. + [ 'OS != "android"', { + 'dependencies': [ + 'skia_opts_ssse3', + ], + }], + ], + }], + [ 'target_arch == "arm"', { + 'conditions': [ + [ 'armv7 == 1', { + 'defines': [ + '__ARM_ARCH__=7', + ], + }], + [ 'armv7 == 1 and arm_neon == 1', { + 'defines': [ + '__ARM_HAVE_NEON', + ], + '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', + ], + }], + ], + # 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', + ], + 'cflags': [ + '-fomit-frame-pointer', + ], + 'sources': [ + '../third_party/skia/src/opts/SkBitmapProcState_opts_arm.cpp', + ], + }], + [ 'armv7 == 1 and arm_neon == 0', { + 'sources': [ + '../third_party/skia/src/opts/memset.arm.S', + ], + }], + [ 'armv7 == 1 and arm_neon == 1', { + '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', + ], + }], + [ 'target_arch == "arm" and armv7 == 0', { + 'sources': [ + '../third_party/skia/src/opts/SkBlitRow_opts_none.cpp', + '../third_party/skia/src/opts/SkUtils_opts_none.cpp', + ], + }], + [ 'target_arch == "arm" and armv7 == 1', { + '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', + 'variables': { + 'optimize': 'max', + }, + '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"]', { + '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"', { + 'sources': [ + '../third_party/skia/src/opts/SkBitmapProcState_opts_SSSE3.cpp', + ], + }], + ], + }, + { + 'target_name': 'image_operations_bench', + 'type': 'executable', + 'dependencies': [ + '../base/base.gyp:base', + 'skia', + ], + 'include_dirs': [ + '..', + ], + 'sources': [ + 'ext/image_operations_bench.cc', + ], + }, + ], + 'conditions': [ + ['OS=="ios"', { + 'targets': [ + # 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. + { + 'target_name': 'skia_opts_ios', + 'type': 'static_library', + '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/utils', + '../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', + ], + }, + ], + }], + ], +} diff --git a/skia/skia.target.mk b/skia/skia.target.mk new file mode 100644 index 0000000000..45a7acf15c --- /dev/null +++ b/skia/skia.target.mk @@ -0,0 +1,490 @@ +# This file is generated by gyp; do not edit. + +include $(CLEAR_VARS) + +LOCAL_MODULE_CLASS := STATIC_LIBRARIES +LOCAL_MODULE := skia_skia_gyp +LOCAL_MODULE_SUFFIX := .a +LOCAL_MODULE_TAGS := optional +gyp_intermediate_dir := $(call local-intermediates-dir) +gyp_shared_intermediate_dir := $(call intermediates-dir-for,GYP,shared) + +# Make sure our deps are built first. +GYP_TARGET_DEPENDENCIES := \ + $(call intermediates-dir-for,GYP,third_party_WebKit_Source_WebKit_chromium_skia_webkit_gyp)/skia_webkit.stamp \ + $(call intermediates-dir-for,GYP,third_party_expat_expat_gyp)/expat.stamp + +GYP_GENERATED_OUTPUTS := + +# Make sure our deps and generated files are built first. +LOCAL_ADDITIONAL_DEPENDENCIES := $(GYP_TARGET_DEPENDENCIES) $(GYP_GENERATED_OUTPUTS) + +$(gyp_intermediate_dir)/bitmap_platform_device_android.cpp: $(LOCAL_PATH)/skia/ext/bitmap_platform_device_android.cc + mkdir -p $(@D); cp $< $@ +$(gyp_intermediate_dir)/convolver.cpp: $(LOCAL_PATH)/skia/ext/convolver.cc + mkdir -p $(@D); cp $< $@ +$(gyp_intermediate_dir)/google_logging.cpp: $(LOCAL_PATH)/skia/ext/google_logging.cc + mkdir -p $(@D); cp $< $@ +$(gyp_intermediate_dir)/image_operations.cpp: $(LOCAL_PATH)/skia/ext/image_operations.cc + mkdir -p $(@D); cp $< $@ +$(gyp_intermediate_dir)/platform_canvas.cpp: $(LOCAL_PATH)/skia/ext/platform_canvas.cc + mkdir -p $(@D); cp $< $@ +$(gyp_intermediate_dir)/platform_canvas_linux.cpp: $(LOCAL_PATH)/skia/ext/platform_canvas_linux.cc + mkdir -p $(@D); cp $< $@ +$(gyp_intermediate_dir)/platform_device.cpp: $(LOCAL_PATH)/skia/ext/platform_device.cc + mkdir -p $(@D); cp $< $@ +$(gyp_intermediate_dir)/platform_device_linux.cpp: $(LOCAL_PATH)/skia/ext/platform_device_linux.cc + mkdir -p $(@D); cp $< $@ +$(gyp_intermediate_dir)/vector_canvas.cpp: $(LOCAL_PATH)/skia/ext/vector_canvas.cc + mkdir -p $(@D); cp $< $@ +LOCAL_GENERATED_SOURCES := \ + $(gyp_intermediate_dir)/bitmap_platform_device_android.cpp \ + $(gyp_intermediate_dir)/convolver.cpp \ + $(gyp_intermediate_dir)/google_logging.cpp \ + $(gyp_intermediate_dir)/image_operations.cpp \ + $(gyp_intermediate_dir)/platform_canvas.cpp \ + $(gyp_intermediate_dir)/platform_canvas_linux.cpp \ + $(gyp_intermediate_dir)/platform_device.cpp \ + $(gyp_intermediate_dir)/platform_device_linux.cpp \ + $(gyp_intermediate_dir)/vector_canvas.cpp + +GYP_COPIED_SOURCE_ORIGIN_DIRS := \ + $(LOCAL_PATH)/skia/ext + +LOCAL_SRC_FILES := \ + third_party/skia/src/core/SkFlate.cpp \ + third_party/skia/src/images/bmpdecoderhelper.cpp \ + third_party/skia/src/images/SkImageDecoder.cpp \ + third_party/skia/src/images/SkImageDecoder_Factory.cpp \ + third_party/skia/src/images/SkScaledBitmapSampler.cpp \ + third_party/skia/src/ports/FontHostConfiguration_android.cpp \ + third_party/skia/src/ports/SkFontDescriptor.cpp \ + third_party/skia/src/ports/SkFontHost_FreeType.cpp \ + third_party/skia/src/ports/SkFontHost_FreeType_common.cpp \ + third_party/skia/src/ports/SkFontHost_android.cpp \ + third_party/skia/src/ports/SkFontHost_tables.cpp \ + third_party/skia/src/ports/SkFontHost_sandbox_none.cpp \ + third_party/skia/src/ports/SkGlobalInitialization_chromium.cpp \ + third_party/skia/src/ports/SkOSFile_stdio.cpp \ + third_party/skia/src/ports/SkThread_pthread.cpp \ + third_party/skia/src/ports/SkTime_Unix.cpp \ + third_party/skia/src/sfnt/SkOTUtils.cpp \ + third_party/skia/src/utils/SkBase64.cpp \ + third_party/skia/src/utils/SkBitSet.cpp \ + third_party/skia/src/utils/SkDeferredCanvas.cpp \ + third_party/skia/src/utils/SkMatrix44.cpp \ + third_party/skia/src/utils/SkNullCanvas.cpp \ + third_party/skia/src/utils/SkNWayCanvas.cpp \ + skia/ext/SkMemory_new_handler.cpp \ + third_party/skia/src/core/Sk64.cpp \ + third_party/skia/src/core/SkAAClip.cpp \ + third_party/skia/src/core/SkAnnotation.cpp \ + third_party/skia/src/core/SkAdvancedTypefaceMetrics.cpp \ + third_party/skia/src/core/SkAlphaRuns.cpp \ + third_party/skia/src/core/SkBBoxHierarchy.cpp \ + third_party/skia/src/core/SkBBoxRecord.cpp \ + third_party/skia/src/core/SkBBoxHierarchyRecord.cpp \ + third_party/skia/src/core/SkBitmap.cpp \ + third_party/skia/src/core/SkBitmapHeap.cpp \ + third_party/skia/src/core/SkBitmapProcShader.cpp \ + third_party/skia/src/core/SkBitmapProcState.cpp \ + third_party/skia/src/core/SkBitmapProcState_matrixProcs.cpp \ + third_party/skia/src/core/SkBitmapSampler.cpp \ + third_party/skia/src/core/SkBitmap_scroll.cpp \ + third_party/skia/src/core/SkBlitMask_D32.cpp \ + third_party/skia/src/core/SkBlitRow_D16.cpp \ + third_party/skia/src/core/SkBlitRow_D32.cpp \ + third_party/skia/src/core/SkBlitRow_D4444.cpp \ + third_party/skia/src/core/SkBlitter.cpp \ + third_party/skia/src/core/SkBlitter_4444.cpp \ + third_party/skia/src/core/SkBlitter_A1.cpp \ + third_party/skia/src/core/SkBlitter_A8.cpp \ + third_party/skia/src/core/SkBlitter_ARGB32.cpp \ + third_party/skia/src/core/SkBlitter_RGB16.cpp \ + third_party/skia/src/core/SkBlitter_Sprite.cpp \ + third_party/skia/src/core/SkBuffer.cpp \ + third_party/skia/src/core/SkCanvas.cpp \ + third_party/skia/src/core/SkChunkAlloc.cpp \ + third_party/skia/src/core/SkClipStack.cpp \ + third_party/skia/src/core/SkColor.cpp \ + third_party/skia/src/core/SkColorFilter.cpp \ + third_party/skia/src/core/SkColorTable.cpp \ + third_party/skia/src/core/SkComposeShader.cpp \ + third_party/skia/src/core/SkConcaveToTriangles.cpp \ + third_party/skia/src/core/SkConfig8888.cpp \ + third_party/skia/src/core/SkCordic.cpp \ + third_party/skia/src/core/SkCubicClipper.cpp \ + third_party/skia/src/core/SkData.cpp \ + third_party/skia/src/core/SkDebug.cpp \ + third_party/skia/src/core/SkDeque.cpp \ + third_party/skia/src/core/SkDevice.cpp \ + third_party/skia/src/core/SkDeviceProfile.cpp \ + third_party/skia/src/core/SkDither.cpp \ + third_party/skia/src/core/SkDraw.cpp \ + third_party/skia/src/core/SkEdgeBuilder.cpp \ + third_party/skia/src/core/SkEdgeClipper.cpp \ + third_party/skia/src/core/SkEdge.cpp \ + third_party/skia/src/core/SkFilterProc.cpp \ + third_party/skia/src/core/SkFlattenable.cpp \ + third_party/skia/src/core/SkFlattenableBuffers.cpp \ + third_party/skia/src/core/SkFloat.cpp \ + third_party/skia/src/core/SkFloatBits.cpp \ + third_party/skia/src/core/SkFontHost.cpp \ + third_party/skia/src/core/SkGeometry.cpp \ + third_party/skia/src/core/SkGlyphCache.cpp \ + third_party/skia/src/core/SkGraphics.cpp \ + third_party/skia/src/core/SkInstCnt.cpp \ + third_party/skia/src/core/SkImageFilter.cpp \ + third_party/skia/src/core/SkLineClipper.cpp \ + third_party/skia/src/core/SkMallocPixelRef.cpp \ + third_party/skia/src/core/SkMask.cpp \ + third_party/skia/src/core/SkMaskFilter.cpp \ + third_party/skia/src/core/SkMaskGamma.cpp \ + third_party/skia/src/core/SkMath.cpp \ + third_party/skia/src/core/SkMatrix.cpp \ + third_party/skia/src/core/SkMetaData.cpp \ + third_party/skia/src/core/SkMMapStream.cpp \ + third_party/skia/src/core/SkOrderedReadBuffer.cpp \ + third_party/skia/src/core/SkOrderedWriteBuffer.cpp \ + third_party/skia/src/core/SkPackBits.cpp \ + third_party/skia/src/core/SkPaint.cpp \ + third_party/skia/src/core/SkPath.cpp \ + third_party/skia/src/core/SkPathEffect.cpp \ + third_party/skia/src/core/SkPathHeap.cpp \ + third_party/skia/src/core/SkPathMeasure.cpp \ + third_party/skia/src/core/SkPicture.cpp \ + third_party/skia/src/core/SkPictureFlat.cpp \ + third_party/skia/src/core/SkPicturePlayback.cpp \ + third_party/skia/src/core/SkPictureRecord.cpp \ + third_party/skia/src/core/SkPictureStateTree.cpp \ + third_party/skia/src/core/SkPixelRef.cpp \ + third_party/skia/src/core/SkPoint.cpp \ + third_party/skia/src/core/SkProcSpriteBlitter.cpp \ + third_party/skia/src/core/SkPtrRecorder.cpp \ + third_party/skia/src/core/SkQuadClipper.cpp \ + third_party/skia/src/core/SkRasterClip.cpp \ + third_party/skia/src/core/SkRasterizer.cpp \ + third_party/skia/src/core/SkRect.cpp \ + third_party/skia/src/core/SkRefCnt.cpp \ + third_party/skia/src/core/SkRefDict.cpp \ + third_party/skia/src/core/SkRegion.cpp \ + third_party/skia/src/core/SkRegion_path.cpp \ + third_party/skia/src/core/SkRTree.cpp \ + third_party/skia/src/core/SkScalar.cpp \ + third_party/skia/src/core/SkScalerContext.cpp \ + third_party/skia/src/core/SkScan.cpp \ + third_party/skia/src/core/SkScan_AntiPath.cpp \ + third_party/skia/src/core/SkScan_Antihair.cpp \ + third_party/skia/src/core/SkScan_Hairline.cpp \ + third_party/skia/src/core/SkScan_Path.cpp \ + third_party/skia/src/core/SkShader.cpp \ + third_party/skia/src/core/SkSpriteBlitter_ARGB32.cpp \ + third_party/skia/src/core/SkSpriteBlitter_RGB16.cpp \ + third_party/skia/src/core/SkStream.cpp \ + third_party/skia/src/core/SkString.cpp \ + third_party/skia/src/core/SkStroke.cpp \ + third_party/skia/src/core/SkStrokerPriv.cpp \ + third_party/skia/src/core/SkTileGrid.cpp \ + third_party/skia/src/core/SkTLS.cpp \ + third_party/skia/src/core/SkTSearch.cpp \ + third_party/skia/src/core/SkTypeface.cpp \ + third_party/skia/src/core/SkTypefaceCache.cpp \ + third_party/skia/src/core/SkUnPreMultiply.cpp \ + third_party/skia/src/core/SkUtils.cpp \ + third_party/skia/src/core/SkWriter32.cpp \ + third_party/skia/src/core/SkXfermode.cpp \ + third_party/skia/src/image/SkDataPixelRef.cpp \ + third_party/skia/src/image/SkImage.cpp \ + third_party/skia/src/image/SkImagePriv.cpp \ + third_party/skia/src/image/SkImage_Codec.cpp \ + third_party/skia/src/image/SkImage_Picture.cpp \ + third_party/skia/src/image/SkImage_Raster.cpp \ + third_party/skia/src/image/SkSurface.cpp \ + third_party/skia/src/image/SkSurface_Picture.cpp \ + third_party/skia/src/image/SkSurface_Raster.cpp \ + third_party/skia/src/pipe/SkGPipeRead.cpp \ + third_party/skia/src/pipe/SkGPipeWrite.cpp \ + third_party/skia/src/effects/Sk1DPathEffect.cpp \ + third_party/skia/src/effects/Sk2DPathEffect.cpp \ + third_party/skia/src/effects/SkAvoidXfermode.cpp \ + third_party/skia/src/effects/SkArithmeticMode.cpp \ + third_party/skia/src/effects/SkBitmapSource.cpp \ + third_party/skia/src/effects/SkBlendImageFilter.cpp \ + third_party/skia/src/effects/SkBlurDrawLooper.cpp \ + third_party/skia/src/effects/SkBlurMask.cpp \ + third_party/skia/src/effects/SkBlurImageFilter.cpp \ + third_party/skia/src/effects/SkBlurMaskFilter.cpp \ + third_party/skia/src/effects/SkColorFilters.cpp \ + third_party/skia/src/effects/SkColorFilterImageFilter.cpp \ + third_party/skia/src/effects/SkColorMatrix.cpp \ + third_party/skia/src/effects/SkColorMatrixFilter.cpp \ + third_party/skia/src/effects/SkCornerPathEffect.cpp \ + third_party/skia/src/effects/SkDashPathEffect.cpp \ + third_party/skia/src/effects/SkDiscretePathEffect.cpp \ + third_party/skia/src/effects/SkEmbossMask.cpp \ + third_party/skia/src/effects/SkEmbossMaskFilter.cpp \ + third_party/skia/src/effects/SkKernel33MaskFilter.cpp \ + third_party/skia/src/effects/SkLayerDrawLooper.cpp \ + third_party/skia/src/effects/SkLayerRasterizer.cpp \ + third_party/skia/src/effects/SkLightingImageFilter.cpp \ + third_party/skia/src/effects/SkOffsetImageFilter.cpp \ + third_party/skia/src/effects/SkMatrixConvolutionImageFilter.cpp \ + third_party/skia/src/effects/SkMorphologyImageFilter.cpp \ + third_party/skia/src/effects/SkPaintFlagsDrawFilter.cpp \ + third_party/skia/src/effects/SkPixelXorXfermode.cpp \ + third_party/skia/src/effects/SkPorterDuff.cpp \ + third_party/skia/src/effects/SkSingleInputImageFilter.cpp \ + third_party/skia/src/effects/SkStippleMaskFilter.cpp \ + third_party/skia/src/effects/SkTableColorFilter.cpp \ + third_party/skia/src/effects/SkTableMaskFilter.cpp \ + third_party/skia/src/effects/SkTestImageFilters.cpp \ + third_party/skia/src/effects/SkTransparentShader.cpp \ + third_party/skia/src/effects/SkMagnifierImageFilter.cpp \ + third_party/skia/src/effects/gradients/SkBitmapCache.cpp \ + third_party/skia/src/effects/gradients/SkClampRange.cpp \ + third_party/skia/src/effects/gradients/SkGradientShader.cpp \ + third_party/skia/src/effects/gradients/SkLinearGradient.cpp \ + third_party/skia/src/effects/gradients/SkRadialGradient.cpp \ + third_party/skia/src/effects/gradients/SkTwoPointRadialGradient.cpp \ + third_party/skia/src/effects/gradients/SkTwoPointConicalGradient.cpp \ + third_party/skia/src/effects/gradients/SkSweepGradient.cpp \ + third_party/skia/src/gpu/GrAAHairLinePathRenderer.cpp \ + third_party/skia/src/gpu/GrAAConvexPathRenderer.cpp \ + third_party/skia/src/gpu/GrAARectRenderer.cpp \ + third_party/skia/src/gpu/GrAddPathRenderers_default.cpp \ + third_party/skia/src/gpu/GrAllocPool.cpp \ + third_party/skia/src/gpu/GrAtlas.cpp \ + third_party/skia/src/gpu/GrBufferAllocPool.cpp \ + third_party/skia/src/gpu/GrCacheID.cpp \ + third_party/skia/src/gpu/GrClipData.cpp \ + third_party/skia/src/gpu/GrContext.cpp \ + third_party/skia/src/gpu/GrDefaultPathRenderer.cpp \ + third_party/skia/src/gpu/GrDrawState.cpp \ + third_party/skia/src/gpu/GrDrawTarget.cpp \ + third_party/skia/src/gpu/GrEffect.cpp \ + third_party/skia/src/gpu/GrGeometryBuffer.cpp \ + third_party/skia/src/gpu/GrClipMaskCache.cpp \ + third_party/skia/src/gpu/GrClipMaskManager.cpp \ + third_party/skia/src/gpu/GrGpu.cpp \ + third_party/skia/src/gpu/GrGpuFactory.cpp \ + third_party/skia/src/gpu/GrInOrderDrawBuffer.cpp \ + third_party/skia/src/gpu/GrMemory.cpp \ + third_party/skia/src/gpu/GrMemoryPool.cpp \ + third_party/skia/src/gpu/GrPath.cpp \ + third_party/skia/src/gpu/GrPathRendererChain.cpp \ + third_party/skia/src/gpu/GrPathRenderer.cpp \ + third_party/skia/src/gpu/GrPathUtils.cpp \ + third_party/skia/src/gpu/GrRectanizer.cpp \ + third_party/skia/src/gpu/GrRenderTarget.cpp \ + third_party/skia/src/gpu/GrResource.cpp \ + third_party/skia/src/gpu/GrResourceCache.cpp \ + third_party/skia/src/gpu/GrStencil.cpp \ + third_party/skia/src/gpu/GrStencilAndCoverPathRenderer.cpp \ + third_party/skia/src/gpu/GrStencilBuffer.cpp \ + third_party/skia/src/gpu/GrSWMaskHelper.cpp \ + third_party/skia/src/gpu/GrSoftwarePathRenderer.cpp \ + third_party/skia/src/gpu/GrSurface.cpp \ + third_party/skia/src/gpu/GrTextContext.cpp \ + third_party/skia/src/gpu/GrTextStrike.cpp \ + third_party/skia/src/gpu/GrTexture.cpp \ + third_party/skia/src/gpu/GrTextureAccess.cpp \ + third_party/skia/src/gpu/gr_unittests.cpp \ + third_party/skia/src/gpu/effects/GrTextureStripAtlas.cpp \ + third_party/skia/src/gpu/effects/GrConfigConversionEffect.cpp \ + third_party/skia/src/gpu/effects/GrConvolutionEffect.cpp \ + third_party/skia/src/gpu/effects/GrSingleTextureEffect.cpp \ + third_party/skia/src/gpu/effects/GrTextureDomainEffect.cpp \ + third_party/skia/src/gpu/gl/GrGLCaps.cpp \ + third_party/skia/src/gpu/gl/GrGLContextInfo.cpp \ + third_party/skia/src/gpu/gl/GrGLCreateNativeInterface_none.cpp \ + third_party/skia/src/gpu/gl/GrGLDefaultInterface_none.cpp \ + third_party/skia/src/gpu/gl/GrGLEffect.cpp \ + third_party/skia/src/gpu/gl/GrGLEffectMatrix.cpp \ + third_party/skia/src/gpu/gl/GrGLIndexBuffer.cpp \ + third_party/skia/src/gpu/gl/GrGLInterface.cpp \ + third_party/skia/src/gpu/gl/GrGLPath.cpp \ + third_party/skia/src/gpu/gl/GrGLProgram.cpp \ + third_party/skia/src/gpu/gl/GrGLRenderTarget.cpp \ + third_party/skia/src/gpu/gl/GrGLShaderBuilder.cpp \ + third_party/skia/src/gpu/gl/GrGLSL.cpp \ + third_party/skia/src/gpu/gl/GrGLStencilBuffer.cpp \ + third_party/skia/src/gpu/gl/GrGLTexture.cpp \ + third_party/skia/src/gpu/gl/GrGLUtil.cpp \ + third_party/skia/src/gpu/gl/GrGLUniformManager.cpp \ + third_party/skia/src/gpu/gl/GrGLVertexBuffer.cpp \ + third_party/skia/src/gpu/gl/GrGpuGL.cpp \ + third_party/skia/src/gpu/gl/GrGpuGL_program.cpp \ + third_party/skia/src/gpu/SkGpuDevice.cpp \ + third_party/skia/src/gpu/SkGr.cpp \ + third_party/skia/src/gpu/SkGrFontScaler.cpp \ + third_party/skia/src/gpu/SkGrPixelRef.cpp \ + third_party/skia/src/gpu/SkGrTexturePixelRef.cpp \ + third_party/skia/src/image/SkImage_Gpu.cpp \ + third_party/skia/src/image/SkSurface_Gpu.cpp \ + third_party/skia/src/gpu/gl/SkGLContext.cpp + + +# Flags passed to both C and C++ files. +MY_CFLAGS := \ + -fno-exceptions \ + -fno-strict-aliasing \ + -Wno-unused-parameter \ + -Wno-missing-field-initializers \ + -fvisibility=hidden \ + -pipe \ + -fPIC \ + -Wno-format \ + -mthumb \ + -march=armv7-a \ + -mtune=cortex-a8 \ + -mfloat-abi=softfp \ + -mfpu=vfpv3-d16 \ + -fno-tree-sra \ + -fuse-ld=gold \ + -Wno-psabi \ + -mthumb-interwork \ + -ffunction-sections \ + -funwind-tables \ + -g \ + -fstack-protector \ + -fno-short-enums \ + -finline-limit=64 \ + -Wa,--noexecstack \ + -Wno-error=extra \ + -Wno-error=ignored-qualifiers \ + -Wno-error=type-limits \ + -Wno-error=non-virtual-dtor \ + -Wno-error=sign-promo \ + -Wno-error=address \ + -Wno-error=format-security \ + -Wno-error=non-virtual-dtor \ + -Wno-error=return-type \ + -Wno-error=sequence-point \ + -Os \ + -g \ + -fomit-frame-pointer \ + -fdata-sections \ + -ffunction-sections + +MY_CFLAGS_C := + +MY_DEFS := \ + '-D_FILE_OFFSET_BITS=64' \ + '-DNO_TCMALLOC' \ + '-DDISABLE_NACL' \ + '-DCHROMIUM_BUILD' \ + '-DUSE_LIBJPEG_TURBO=1' \ + '-DUSE_PROPRIETARY_CODECS' \ + '-DENABLE_PEPPER_THREADING' \ + '-DENABLE_GPU=1' \ + '-DUSE_OPENSSL=1' \ + '-DENABLE_EGLIMAGE=1' \ + '-DUSE_SKIA=1' \ + '-DSK_USE_POSIX_THREADS' \ + '-DSK_BUILD_NO_IMAGE_ENCODE' \ + '-DGR_GL_CUSTOM_SETUP_HEADER="GrGLConfig_chrome.h"' \ + '-DGR_STATIC_RECT_VB=1' \ + '-DGR_AGGRESSIVE_SHADER_OPTS=1' \ + '-DSK_DISABLE_FAST_AA_STROKE_RECT' \ + '-DSK_DEFERRED_CANVAS_USES_GPIPE=1' \ + '-DSK_ALLOW_OVER_32K_BITMAPS' \ + '-DSK_ALLOW_STATIC_GLOBAL_INITIALIZERS=0' \ + '-DSK_SUPPORT_INT_SRCRECT_DRAWBITMAPRECT' \ + '-DSK_GAMMA_SRGB' \ + '-DSK_GAMMA_CONTRAST=0.5' \ + '-D__ARM_ARCH__=7' \ + '-DHAVE_PTHREADS' \ + '-DOS_ANDROID' \ + '-DSK_BUILD_FOR_ANDROID_NDK' \ + '-DSK_DEFAULT_FONT_CACHE_LIMIT=(8*1024*1024)' \ + '-DUSE_CHROMIUM_SKIA' \ + '-DSK_SIMPLE_TWOCOLOR_VERTICAL_GRADIENTS' \ + '-DSK_DRAW_POS_TEXT_IGNORE_SUBPIXEL_LEFT_ALIGN_FIX' \ + '-DSK_USE_LEGACY_AA_COVERAGE' \ + '-DANDROID' \ + '-D__GNU_SOURCE=1' \ + '-DUSE_STLPORT=1' \ + '-D_STLP_USE_PTR_SPECIALIZATIONS=1' \ + '-DCHROME_SYMBOLS_ID=""' \ + '-DANDROID_UPSTREAM_BRINGUP=1' \ + '-DDYNAMIC_ANNOTATIONS_ENABLED=1' \ + '-DWTF_USE_DYNAMIC_ANNOTATIONS=1' \ + '-D_DEBUG' + +LOCAL_CFLAGS := $(MY_CFLAGS_C) $(MY_CFLAGS) $(MY_DEFS) + +# Include paths placed before CFLAGS/CPPFLAGS +LOCAL_C_INCLUDES := \ + $(LOCAL_PATH) \ + $(LOCAL_PATH)/skia/config \ + $(LOCAL_PATH)/third_party/skia/include/config \ + $(LOCAL_PATH)/third_party/skia/include/core \ + $(LOCAL_PATH)/third_party/skia/include/effects \ + $(LOCAL_PATH)/third_party/skia/include/images \ + $(LOCAL_PATH)/third_party/skia/include/pdf \ + $(LOCAL_PATH)/third_party/skia/include/pipe \ + $(LOCAL_PATH)/third_party/skia/include/ports \ + $(LOCAL_PATH)/third_party/skia/include/utils \ + $(LOCAL_PATH)/third_party/skia/src/core \ + $(LOCAL_PATH)/third_party/skia/src/image \ + $(LOCAL_PATH)/third_party/skia/src/sfnt \ + $(LOCAL_PATH)/third_party/skia/src/utils \ + $(LOCAL_PATH)/third_party/skia/include/gpu \ + $(LOCAL_PATH)/third_party/skia/include/gpu/gl \ + $(LOCAL_PATH)/third_party/skia/src/gpu \ + $(LOCAL_PATH)/third_party/expat/files/lib \ + $(LOCAL_PATH)/third_party/zlib \ + $(GYP_ABS_ANDROID_TOP_DIR)/external/expat/lib \ + $(LOCAL_PATH)/third_party/freetype/include \ + $(LOCAL_PATH)/third_party/harfbuzz/contrib \ + $(LOCAL_PATH)/third_party/harfbuzz/src \ + $(GYP_ABS_ANDROID_TOP_DIR)/frameworks/wilhelm/include \ + $(GYP_ABS_ANDROID_TOP_DIR)/bionic \ + $(GYP_ABS_ANDROID_TOP_DIR)/external/stlport/stlport + +LOCAL_C_INCLUDES := $(GYP_COPIED_SOURCE_ORIGIN_DIRS) $(LOCAL_C_INCLUDES) + +# Flags passed to only C++ (and not C) files. +LOCAL_CPPFLAGS := \ + -fno-rtti \ + -fno-threadsafe-statics \ + -fvisibility-inlines-hidden \ + -Wno-deprecated \ + -Wno-abi \ + -Wno-error=c++0x-compat + +### Rules for final target. + +LOCAL_LDFLAGS := \ + -Wl,-z,noexecstack \ + -fPIC \ + -Wl,-z,relro \ + -Wl,-z,now \ + -fuse-ld=gold \ + -nostdlib \ + -Wl,--no-undefined \ + -Wl,--exclude-libs=ALL \ + -Wl,--icf=safe \ + -Wl,-O1 \ + -Wl,--as-needed \ + -Wl,--gc-sections + + +LOCAL_STATIC_LIBRARIES := + +# Enable grouping to fix circular references +LOCAL_GROUP_STATIC_LIBRARIES := true + +LOCAL_SHARED_LIBRARIES := \ + libstlport \ + libdl + +# Add target alias to "gyp_all_modules" target. +.PHONY: gyp_all_modules +gyp_all_modules: skia_skia_gyp + +# Alias gyp target name. +.PHONY: skia +skia: skia_skia_gyp + +include $(BUILD_STATIC_LIBRARY) diff --git a/skia/skia_Prefix.pch b/skia/skia_Prefix.pch new file mode 100644 index 0000000000..5541dea063 --- /dev/null +++ b/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/skia/skia_opts.target.mk b/skia/skia_opts.target.mk new file mode 100644 index 0000000000..ecd474d9a6 --- /dev/null +++ b/skia/skia_opts.target.mk @@ -0,0 +1,161 @@ +# This file is generated by gyp; do not edit. + +include $(CLEAR_VARS) + +LOCAL_MODULE_CLASS := STATIC_LIBRARIES +LOCAL_MODULE := skia_skia_opts_gyp +LOCAL_MODULE_SUFFIX := .a +LOCAL_MODULE_TAGS := optional +gyp_intermediate_dir := $(call local-intermediates-dir) +gyp_shared_intermediate_dir := $(call intermediates-dir-for,GYP,shared) + +# Make sure our deps are built first. +GYP_TARGET_DEPENDENCIES := + +GYP_GENERATED_OUTPUTS := + +# Make sure our deps and generated files are built first. +LOCAL_ADDITIONAL_DEPENDENCIES := $(GYP_TARGET_DEPENDENCIES) $(GYP_GENERATED_OUTPUTS) + +LOCAL_GENERATED_SOURCES := + +GYP_COPIED_SOURCE_ORIGIN_DIRS := + +LOCAL_SRC_FILES := \ + third_party/skia/src/opts/SkBitmapProcState_opts_arm.cpp \ + third_party/skia/src/opts/memset.arm.S \ + third_party/skia/src/opts/SkBlitRow_opts_arm.cpp \ + third_party/skia/src/opts/opts_check_arm.cpp + + +# Flags passed to both C and C++ files. +MY_CFLAGS := \ + -fno-exceptions \ + -fno-strict-aliasing \ + -Wno-unused-parameter \ + -Wno-missing-field-initializers \ + -fvisibility=hidden \ + -pipe \ + -fPIC \ + -fomit-frame-pointer \ + -Wno-format \ + -mthumb \ + -march=armv7-a \ + -mtune=cortex-a8 \ + -mfloat-abi=softfp \ + -mfpu=vfpv3-d16 \ + -fno-tree-sra \ + -fuse-ld=gold \ + -Wno-psabi \ + -mthumb-interwork \ + -ffunction-sections \ + -funwind-tables \ + -g \ + -fstack-protector \ + -fno-short-enums \ + -finline-limit=64 \ + -Wa,--noexecstack \ + -Wno-error=extra \ + -Wno-error=ignored-qualifiers \ + -Wno-error=type-limits \ + -Wno-error=non-virtual-dtor \ + -Wno-error=sign-promo \ + -Wno-error=address \ + -Wno-error=format-security \ + -Wno-error=non-virtual-dtor \ + -Wno-error=return-type \ + -Wno-error=sequence-point \ + -Os \ + -g \ + -fomit-frame-pointer \ + -fdata-sections \ + -ffunction-sections + +MY_CFLAGS_C := + +MY_DEFS := \ + '-D_FILE_OFFSET_BITS=64' \ + '-DNO_TCMALLOC' \ + '-DDISABLE_NACL' \ + '-DCHROMIUM_BUILD' \ + '-DUSE_LIBJPEG_TURBO=1' \ + '-DUSE_PROPRIETARY_CODECS' \ + '-DENABLE_PEPPER_THREADING' \ + '-DENABLE_GPU=1' \ + '-DUSE_OPENSSL=1' \ + '-DENABLE_EGLIMAGE=1' \ + '-DUSE_SKIA=1' \ + '-DSK_BUILD_FOR_ANDROID_NDK' \ + '-D__ARM_ARCH__=7' \ + '-DANDROID' \ + '-D__GNU_SOURCE=1' \ + '-DUSE_STLPORT=1' \ + '-D_STLP_USE_PTR_SPECIALIZATIONS=1' \ + '-DCHROME_SYMBOLS_ID=""' \ + '-DANDROID_UPSTREAM_BRINGUP=1' \ + '-DDYNAMIC_ANNOTATIONS_ENABLED=1' \ + '-DWTF_USE_DYNAMIC_ANNOTATIONS=1' \ + '-D_DEBUG' + +LOCAL_CFLAGS := $(MY_CFLAGS_C) $(MY_CFLAGS) $(MY_DEFS) + +# Include paths placed before CFLAGS/CPPFLAGS +LOCAL_C_INCLUDES := \ + $(LOCAL_PATH) \ + $(LOCAL_PATH)/skia/config \ + $(LOCAL_PATH)/third_party/skia/include/config \ + $(LOCAL_PATH)/third_party/skia/include/core \ + $(LOCAL_PATH)/third_party/skia/include/effects \ + $(LOCAL_PATH)/third_party/skia/include/images \ + $(LOCAL_PATH)/third_party/skia/include/utils \ + $(LOCAL_PATH)/third_party/skia/src/core \ + $(GYP_ABS_ANDROID_TOP_DIR)/frameworks/wilhelm/include \ + $(GYP_ABS_ANDROID_TOP_DIR)/bionic \ + $(GYP_ABS_ANDROID_TOP_DIR)/external/stlport/stlport + +LOCAL_C_INCLUDES := $(GYP_COPIED_SOURCE_ORIGIN_DIRS) $(LOCAL_C_INCLUDES) + +# Flags passed to only C++ (and not C) files. +LOCAL_CPPFLAGS := \ + -fno-rtti \ + -fno-threadsafe-statics \ + -fvisibility-inlines-hidden \ + -Wno-deprecated \ + -Wno-abi \ + -Wno-error=c++0x-compat + +### Rules for final target. + +LOCAL_LDFLAGS := \ + -Wl,-z,noexecstack \ + -fPIC \ + -Wl,-z,relro \ + -Wl,-z,now \ + -fuse-ld=gold \ + -nostdlib \ + -Wl,--no-undefined \ + -Wl,--exclude-libs=ALL \ + -Wl,--icf=safe \ + -Wl,-O1 \ + -Wl,--as-needed \ + -Wl,--gc-sections + + +LOCAL_STATIC_LIBRARIES := + +# Enable grouping to fix circular references +LOCAL_GROUP_STATIC_LIBRARIES := true + +LOCAL_SHARED_LIBRARIES := \ + libstlport \ + libdl + +# Add target alias to "gyp_all_modules" target. +.PHONY: gyp_all_modules +gyp_all_modules: skia_skia_opts_gyp + +# Alias gyp target name. +.PHONY: skia_opts +skia_opts: skia_skia_opts_gyp + +include $(BUILD_STATIC_LIBRARY) diff --git a/skia/skia_test_expectations.txt b/skia/skia_test_expectations.txt new file mode 100644 index 0000000000..d72efc6db4 --- /dev/null +++ b/skia/skia_test_expectations.txt @@ -0,0 +1,72 @@ +# 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 + +# bungeman@ will rebaseline this test +crbug.com/155867 platform/chromium-linux/fast/text/chromium-linux-fontconfig-renderstyle.html [ Failure ImageOnlyFailure ] + +# To be rebaselined after skia.gyp undefines SK_DISABLE_EXTRACTSUBSET_OPAQUE_FIX +# +crbug.com/156097 fast/backgrounds/repeat/negative-offset-repeat-transformed.html [ ImageOnlyFailure ] +crbug.com/156097 fast/borders/border-image-rotate-transform.html [ ImageOnlyFailure ] +crbug.com/156097 svg/custom/clip-mask-negative-scale.svg [ ImageOnlyFailure ] +crbug.com/156097 svg/custom/focus-ring.svg [ ImageOnlyFailure ] +crbug.com/156097 svg/transforms/animated-path-inside-transformed-html.xhtml [ ImageOnlyFailure ] +crbug.com/156097 fast/forms/select-style.html [ ImageOnlyFailure ] + +# To be rebaselined by reed@. http://code.google.com/p/skia/source/detail?r=6220 +# removed double drawing of clipped hairlines at edges. + +crbug.com/148638 fast/repaint/list-marker.html [ Failure ImageOnlyFailure ] +crbug.com/148638 svg/hixie/perf/001.xml [ Failure ImageOnlyFailure ] +crbug.com/148638 svg/hixie/perf/002.xml [ Failure ImageOnlyFailure ] +crbug.com/148638 svg/transforms/text-with-pattern-inside-transformed-html.xhtml [ Failure ImageOnlyFailure ] +crbug.com/148638 svg/zoom/page/zoom-img-preserveAspectRatio-support-1.html [ Failure ImageOnlyFailure ] + +# END OVERRIDES HERE (this line ensures that the file is newline-terminated) diff --git a/skia/tile_patch.diff b/skia/tile_patch.diff new file mode 100644 index 0000000000..78118416b4 --- /dev/null +++ b/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]; + } + |