diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index ef951e5c..1fc8b415 100755 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -63,9 +63,15 @@ function(add_example name) print_elapsed.cpp ) - set_property(TARGET ${name} PROPERTY - MSVC_RUNTIME_LIBRARY "MultiThreaded" - ) + if (CMAKE_BUILD_TYPE STREQUAL "Release" OR CMAKE_C_COMPILER_ID STREQUAL "Clang") + set_property(TARGET ${name} PROPERTY + MSVC_RUNTIME_LIBRARY "MultiThreaded" + ) + else() + set_property(TARGET ${name} PROPERTY + MSVC_RUNTIME_LIBRARY "MultiThreadedDebug" + ) + endif() target_link_options(${name} PRIVATE /SUBSYSTEM:WINDOWS /ENTRY:mainCRTStartup shcore.lib diff --git a/examples/host/linux/skia_app.cpp b/examples/host/linux/skia_app.cpp index cd1ac0c2..d70f530f 100644 --- a/examples/host/linux/skia_app.cpp +++ b/examples/host/linux/skia_app.cpp @@ -10,6 +10,7 @@ #include "GrDirectContext.h" #include "gl/GrGLInterface.h" +#include "gl/GrGLAssembleInterface.h" #include "SkImage.h" #include "SkColorSpace.h" #include "SkCanvas.h" @@ -43,15 +44,30 @@ namespace void realize(GtkGLArea* area, gpointer user_data) { - gtk_gl_area_make_current (area); - if (gtk_gl_area_get_error (area) != NULL) - return; + auto error = [](char const* msg) { throw std::runtime_error(msg); }; + + gtk_gl_area_make_current(area); + if (gtk_gl_area_get_error(area) != nullptr) + error("Error. gtk_gl_area_get_error failed"); view_state& state = *reinterpret_cast(user_data); glClearColor(state._bkd.red, state._bkd.green, state._bkd.blue, state._bkd.alpha); glClear(GL_COLOR_BUFFER_BIT); - state._xface = GrGLMakeNativeInterface(); - state._ctx = GrDirectContext::MakeGL(state._xface); + if (state._xface = GrGLMakeNativeInterface(); state._xface == nullptr) + { + //backup plan. see https://gist.github.com/ad8e/dd150b775ae6aa4d5cf1a092e4713add?permalink_comment_id=4680136#gistcomment-4680136 + state._xface = GrGLMakeAssembledInterface( + nullptr, (GrGLGetProc) * + [](void*, const char* p) -> void* + { + return (void*)glXGetProcAddress((const GLubyte*)p); + } + ); + if (state._xface == nullptr) + error("Error. GLMakeNativeInterface failed"); + } + if (state._ctx = GrDirectContext::MakeGL(state._xface); state._ctx == nullptr) + error("Error. GrDirectContext::MakeGL failed"); } gboolean render(GtkGLArea* area, GdkGLContext* context, gpointer user_data) @@ -89,8 +105,8 @@ namespace SkCanvas* gpu_canvas = state._surface->getCanvas(); gpu_canvas->save(); + gpu_canvas->scale(state._scale, state._scale); auto cnv = canvas{gpu_canvas}; - cnv.pre_scale(state._scale); draw(cnv); @@ -127,8 +143,10 @@ namespace g_signal_connect(window, "destroy", G_CALLBACK(close_window), user_data); + GtkWidget* widget = nullptr; // create a GtkGLArea instance - auto* gl_area = gtk_gl_area_new(); + GtkWidget* gl_area = gtk_gl_area_new(); + widget = gl_area; gtk_container_add(GTK_CONTAINER(window), gl_area); g_signal_connect(gl_area, "render", G_CALLBACK(render), user_data); @@ -140,8 +158,8 @@ namespace auto w = gtk_widget_get_window(GTK_WIDGET(window)); state._scale = gdk_window_get_scale_factor(w); - if (state._animate) - state._timer_id = g_timeout_add(1000 / 60, animate, gl_area); + if (widget && state._animate) + state._timer_id = g_timeout_add(1000 / 60, animate, widget); } } @@ -175,12 +193,22 @@ int run_app( state._bkd = background_color; auto* app = gtk_application_new("org.gtk-skia.example", G_APPLICATION_FLAGS_NONE); - g_signal_connect(app, "activate", G_CALLBACK(activate), &state); - int status = g_application_run(G_APPLICATION(app), argc, const_cast(argv)); + int status = 0; + + try + { + g_signal_connect(app, "activate", G_CALLBACK(activate), &state); + int status = g_application_run(G_APPLICATION(app), argc, const_cast(argv)); + } + catch (std::runtime_error const& e) + { + // GPU rendering not available + g_printerr(e.what()); + int status = 1; + } g_object_unref(app); return status; } - diff --git a/examples/host/macos/skia_app.mm b/examples/host/macos/skia_app.mm index e2625df9..22d3bf0c 100755 --- a/examples/host/macos/skia_app.mm +++ b/examples/host/macos/skia_app.mm @@ -127,8 +127,8 @@ - (void) drawRect : (NSRect) dirty { SkCanvas* gpu_canvas = surface->getCanvas(); gpu_canvas->save(); + gpu_canvas->scale(_scale, _scale); auto cnv = canvas{gpu_canvas}; - cnv.pre_scale(_scale); draw(cnv); diff --git a/examples/host/windows/skia_app.cpp b/examples/host/windows/skia_app.cpp index 0dc8da09..c9f5873f 100644 --- a/examples/host/windows/skia_app.cpp +++ b/examples/host/windows/skia_app.cpp @@ -185,8 +185,8 @@ void window::render() { SkCanvas* gpu_canvas = surface->getCanvas(); gpu_canvas->save(); + gpu_canvas->scale(_scale, _scale); auto cnv = canvas{gpu_canvas}; - cnv.pre_scale(_scale); draw(cnv); diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt index 79ac2a2c..bfd9d35f 100755 --- a/lib/CMakeLists.txt +++ b/lib/CMakeLists.txt @@ -33,9 +33,15 @@ if (WIN32) NOMINMAX _UNICODE ) - set_property(TARGET libunibreak PROPERTY - MSVC_RUNTIME_LIBRARY "MultiThreaded" - ) + if (CMAKE_BUILD_TYPE STREQUAL "Debug" OR CMAKE_C_COMPILER_ID STREQUAL "Clang") + set_property(TARGET libunibreak PROPERTY + MSVC_RUNTIME_LIBRARY "MultiThreadedDebug" + ) + else() + set_property(TARGET libunibreak PROPERTY + MSVC_RUNTIME_LIBRARY "MultiThreaded" + ) + endif() endif() if (ARTIST_SKIA) @@ -91,10 +97,19 @@ if (ARTIST_SKIA) if (CMAKE_C_COMPILER_ID STREQUAL "MSVC") set(ARTIST_BINARIES ${CMAKE_CURRENT_BINARY_DIR}/windows/msvc/x86_64/src/prebuilt_binaries) - set(ARTIST_BINARIES_URL https://github.com/cycfi/artist/raw/prebuilt_binaries_0.90/windows/skia/msvc/x86_64/x86_64.zip) - set(ARTIST_BINARIES_MD5 93b0a532f87c1068bc2adabf19da086d) - set(ARTIST_BINARIES_PREFIX windows/msvc/x86_64) + if (CMAKE_BUILD_TYPE STREQUAL "Release") + set(ARTIST_BINARIES_URL https://github.com/cycfi/artist/raw/prebuilt_binaries_0.90/windows/skia/msvc/x86_64/x86_64.zip) + set(ARTIST_BINARIES_MD5 93b0a532f87c1068bc2adabf19da086d) + set(ARTIST_BINARIES_PREFIX windows/msvc/x86_64) + else() + set(ARTIST_BINARIES_URL https://github.com/cycfi/artist/raw/prebuilt_binaries_0.90/windows/skia/msvc/x86_64-dbg/x86_64-dbg.zip) + set(ARTIST_BINARIES_MD5 b2e5f3927c3b6b3f41ca1c51c8b6a55f) + set(ARTIST_BINARIES_PREFIX windows/msvc/x86_64) + endif() elseif (CMAKE_C_COMPILER_ID STREQUAL "Clang") + if (CMAKE_BUILD_TYPE STREQUAL "Debug") + message(WARNING "Warning: There are no precompiled binaries built using MTd for Clang. Use MT.") + endif() set(ARTIST_BINARIES ${CMAKE_CURRENT_BINARY_DIR}/windows/clang/x86_64/src/prebuilt_binaries) set(ARTIST_BINARIES_URL https://github.com/cycfi/artist/raw/prebuilt_binaries_0.90/windows/skia/clang/x86_64/x86_64.zip) set(ARTIST_BINARIES_MD5 b2bc06c19cd61f93505b7e37eaf489db) @@ -227,6 +242,7 @@ if (ARTIST_SKIA) if (APPLE) set(ARTIST_SKIA_PLATFORM_WINDOW_CONTEXT external/skia/tools/sk_app/mac/GLWindowContext_mac.mm + external/skia/tools/sk_app/mac/RasterWindowContext_mac.mm ) set_source_files_properties(${ARTIST_SKIA_PLATFORM_WINDOW_CONTEXT} PROPERTIES COMPILE_FLAGS -fno-objc-arc @@ -234,6 +250,7 @@ if (ARTIST_SKIA) elseif (MSVC) set(ARTIST_SKIA_PLATFORM_WINDOW_CONTEXT external/skia/tools/sk_app/win/GLWindowContext_win.cpp + external/skia/tools/sk_app/win/RasterWindowContext_win.cpp ) endif() @@ -246,6 +263,8 @@ if (ARTIST_SKIA) impl/skia/detail/harfbuzz.cpp external/skia/tools/sk_app/GLWindowContext.cpp external/skia/tools/sk_app/WindowContext.cpp + external/skia/tools/ToolUtils.cpp + external/skia/tools/SkMetaData.cpp ${ARTIST_SKIA_PLATFORM_WINDOW_CONTEXT} ) endif() @@ -280,7 +299,7 @@ endif() add_dependencies(artist libunibreak) if (ARTIST_SKIA) - add_dependencies(artist prebuilt_binaries) + add_dependencies(artist prebuilt_binaries) endif() target_sources(artist @@ -376,9 +395,17 @@ elseif (WIN32) NOMINMAX _UNICODE ) - set_property(TARGET artist PROPERTY - MSVC_RUNTIME_LIBRARY "MultiThreaded" - ) + if (CMAKE_BUILD_TYPE STREQUAL "Release" OR CMAKE_C_COMPILER_ID STREQUAL "Clang") + set_property(TARGET artist PROPERTY + MSVC_RUNTIME_LIBRARY "MultiThreaded" + ) + else() + set_property(TARGET artist PROPERTY + MSVC_RUNTIME_LIBRARY "MultiThreadedDebug" + ) + endif() + + endif() if (CMAKE_BUILD_TYPE STREQUAL "Debug") diff --git a/lib/external/skia/include/utils/SkRandom.h b/lib/external/skia/include/utils/SkRandom.h new file mode 100644 index 00000000..ba40732b --- /dev/null +++ b/lib/external/skia/include/utils/SkRandom.h @@ -0,0 +1,169 @@ +/* + * Copyright 2006 The Android Open Source Project + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkRandom_DEFINED +#define SkRandom_DEFINED + +#include "include/core/SkScalar.h" +#include "include/private/SkFixed.h" +#include "include/private/SkFloatBits.h" + +/** \class SkRandom + + Utility class that implements pseudo random 32bit numbers using Marsaglia's + multiply-with-carry "mother of all" algorithm. Unlike rand(), this class holds + its own state, so that multiple instances can be used with no side-effects. + + Has a large period and all bits are well-randomized. + */ +class SkRandom { +public: + SkRandom() { init(0); } + SkRandom(uint32_t seed) { init(seed); } + SkRandom(const SkRandom& rand) : fK(rand.fK), fJ(rand.fJ) {} + + SkRandom& operator=(const SkRandom& rand) { + fK = rand.fK; + fJ = rand.fJ; + + return *this; + } + + /** Return the next pseudo random number as an unsigned 32bit value. + */ + uint32_t nextU() { + fK = kKMul*(fK & 0xffff) + (fK >> 16); + fJ = kJMul*(fJ & 0xffff) + (fJ >> 16); + return (((fK << 16) | (fK >> 16)) + fJ); + } + + /** Return the next pseudo random number as a signed 32bit value. + */ + int32_t nextS() { return (int32_t)this->nextU(); } + + /** + * Returns value [0...1) as an IEEE float + */ + float nextF() { + int floatint = 0x3f800000 | (int)(this->nextU() >> 9); + float f = SkBits2Float(floatint) - 1.0f; + return f; + } + + /** + * Returns value [min...max) as a float + */ + float nextRangeF(float min, float max) { + return min + this->nextF() * (max - min); + } + + /** Return the next pseudo random number, as an unsigned value of + at most bitCount bits. + @param bitCount The maximum number of bits to be returned + */ + uint32_t nextBits(unsigned bitCount) { + SkASSERT(bitCount > 0 && bitCount <= 32); + return this->nextU() >> (32 - bitCount); + } + + /** Return the next pseudo random unsigned number, mapped to lie within + [min, max] inclusive. + */ + uint32_t nextRangeU(uint32_t min, uint32_t max) { + SkASSERT(min <= max); + uint32_t range = max - min + 1; + if (0 == range) { + return this->nextU(); + } else { + return min + this->nextU() % range; + } + } + + /** Return the next pseudo random unsigned number, mapped to lie within + [0, count). + */ + uint32_t nextULessThan(uint32_t count) { + SkASSERT(count > 0); + return this->nextRangeU(0, count - 1); + } + + /** Return the next pseudo random number expressed as a SkScalar + in the range [0..SK_Scalar1). + */ + SkScalar nextUScalar1() { return SkFixedToScalar(this->nextUFixed1()); } + + /** Return the next pseudo random number expressed as a SkScalar + in the range [min..max). + */ + SkScalar nextRangeScalar(SkScalar min, SkScalar max) { + return this->nextUScalar1() * (max - min) + min; + } + + /** Return the next pseudo random number expressed as a SkScalar + in the range [-SK_Scalar1..SK_Scalar1). + */ + SkScalar nextSScalar1() { return SkFixedToScalar(this->nextSFixed1()); } + + /** Return the next pseudo random number as a bool. + */ + bool nextBool() { return this->nextU() >= 0x80000000; } + + /** A biased version of nextBool(). + */ + bool nextBiasedBool(SkScalar fractionTrue) { + SkASSERT(fractionTrue >= 0 && fractionTrue <= SK_Scalar1); + return this->nextUScalar1() <= fractionTrue; + } + + /** Reset the random object. + */ + void setSeed(uint32_t seed) { init(seed); } + +private: + // Initialize state variables with LCG. + // We must ensure that both J and K are non-zero, otherwise the + // multiply-with-carry step will forevermore return zero. + void init(uint32_t seed) { + fK = NextLCG(seed); + if (0 == fK) { + fK = NextLCG(fK); + } + fJ = NextLCG(fK); + if (0 == fJ) { + fJ = NextLCG(fJ); + } + SkASSERT(0 != fK && 0 != fJ); + } + static uint32_t NextLCG(uint32_t seed) { return kMul*seed + kAdd; } + + /** Return the next pseudo random number expressed as an unsigned SkFixed + in the range [0..SK_Fixed1). + */ + SkFixed nextUFixed1() { return this->nextU() >> 16; } + + /** Return the next pseudo random number expressed as a signed SkFixed + in the range [-SK_Fixed1..SK_Fixed1). + */ + SkFixed nextSFixed1() { return this->nextS() >> 15; } + + // See "Numerical Recipes in C", 1992 page 284 for these constants + // For the LCG that sets the initial state from a seed + enum { + kMul = 1664525, + kAdd = 1013904223 + }; + // Constants for the multiply-with-carry steps + enum { + kKMul = 30345, + kJMul = 18000, + }; + + uint32_t fK; + uint32_t fJ; +}; + +#endif diff --git a/lib/external/skia/tools/SkMetaData.cpp b/lib/external/skia/tools/SkMetaData.cpp new file mode 100644 index 00000000..99ab2ce2 --- /dev/null +++ b/lib/external/skia/tools/SkMetaData.cpp @@ -0,0 +1,252 @@ +/* + * Copyright 2006 The Android Open Source Project + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "tools/SkMetaData.h" + +#include "include/private/SkMalloc.h" +#include "include/private/SkTo.h" + +void SkMetaData::reset() +{ + Rec* rec = fRec; + while (rec) { + Rec* next = rec->fNext; + Rec::Free(rec); + rec = next; + } + fRec = nullptr; +} + +void SkMetaData::setS32(const char name[], int32_t value) +{ + (void)this->set(name, &value, sizeof(int32_t), kS32_Type, 1); +} + +void SkMetaData::setScalar(const char name[], SkScalar value) +{ + (void)this->set(name, &value, sizeof(SkScalar), kScalar_Type, 1); +} + +SkScalar* SkMetaData::setScalars(const char name[], int count, const SkScalar values[]) +{ + SkASSERT(count > 0); + if (count > 0) + return (SkScalar*)this->set(name, values, sizeof(SkScalar), kScalar_Type, count); + return nullptr; +} + +void SkMetaData::setPtr(const char name[], void* ptr) { + (void)this->set(name, &ptr, sizeof(void*), kPtr_Type, 1); +} + +void SkMetaData::setBool(const char name[], bool value) +{ + (void)this->set(name, &value, sizeof(bool), kBool_Type, 1); +} + +void* SkMetaData::set(const char name[], const void* data, size_t dataSize, Type type, int count) +{ + SkASSERT(name); + SkASSERT(dataSize); + SkASSERT(count > 0); + + FindResult result = this->findWithPrev(name, type); + + Rec* rec; + bool reuseRec = result.rec && + result.rec->fDataLen == dataSize && + result.rec->fDataCount == count; + if (reuseRec) { + rec = result.rec; + } else { + size_t len = strlen(name); + rec = Rec::Alloc(sizeof(Rec) + dataSize * count + len + 1); + rec->fType = SkToU8(type); + rec->fDataLen = SkToU8(dataSize); + rec->fDataCount = SkToU16(count); + + memcpy(rec->name(), name, len + 1); + } + if (data) { + memcpy(rec->data(), data, dataSize * count); + } + + if (reuseRec) { + // Do nothing, reused + } else if (result.rec) { + // Had one, but had to create a new one. Invalidates iterators. + // Delayed removal since name or data may have been in the result.rec. + this->remove(result); + if (result.prev) { + rec->fNext = result.prev->fNext; + result.prev->fNext = rec; + } + } else { + // Adding a new one, stick it at head. + rec->fNext = fRec; + fRec = rec; + } + return rec->data(); +} + +bool SkMetaData::findS32(const char name[], int32_t* value) const +{ + const Rec* rec = this->find(name, kS32_Type); + if (rec) + { + SkASSERT(rec->fDataCount == 1); + if (value) + *value = *(const int32_t*)rec->data(); + return true; + } + return false; +} + +bool SkMetaData::findScalar(const char name[], SkScalar* value) const +{ + const Rec* rec = this->find(name, kScalar_Type); + if (rec) + { + SkASSERT(rec->fDataCount == 1); + if (value) + *value = *(const SkScalar*)rec->data(); + return true; + } + return false; +} + +const SkScalar* SkMetaData::findScalars(const char name[], int* count, SkScalar values[]) const +{ + const Rec* rec = this->find(name, kScalar_Type); + if (rec) + { + if (count) + *count = rec->fDataCount; + if (values) + memcpy(values, rec->data(), rec->fDataCount * rec->fDataLen); + return (const SkScalar*)rec->data(); + } + return nullptr; +} + +bool SkMetaData::findPtr(const char name[], void** ptr) const { + const Rec* rec = this->find(name, kPtr_Type); + if (rec) { + SkASSERT(rec->fDataCount == 1); + void** found = (void**)rec->data(); + if (ptr) { + *ptr = *found; + } + return true; + } + return false; +} + +bool SkMetaData::findBool(const char name[], bool* value) const +{ + const Rec* rec = this->find(name, kBool_Type); + if (rec) + { + SkASSERT(rec->fDataCount == 1); + if (value) + *value = *(const bool*)rec->data(); + return true; + } + return false; +} + +SkMetaData::FindResult SkMetaData::findWithPrev(const char name[], Type type) const { + FindResult current { fRec, nullptr }; + while (current.rec) { + if (current.rec->fType == type && !strcmp(current.rec->name(), name)) + return current; + current.prev = current.rec; + current.rec = current.rec->fNext; + } + return current; +} + + +const SkMetaData::Rec* SkMetaData::find(const char name[], Type type) const { + return this->findWithPrev(name, type).rec; +} + +void SkMetaData::remove(FindResult result) { + SkASSERT(result.rec); + if (result.prev) { + result.prev->fNext = result.rec->fNext; + } else { + fRec = result.rec->fNext; + } + Rec::Free(result.rec); +} + +bool SkMetaData::remove(const char name[], Type type) { + FindResult result = this->findWithPrev(name, type); + if (!result.rec) { + return false; + } + this->remove(result); + return true; +} + +bool SkMetaData::removeS32(const char name[]) +{ + return this->remove(name, kS32_Type); +} + +bool SkMetaData::removeScalar(const char name[]) +{ + return this->remove(name, kScalar_Type); +} + +bool SkMetaData::removePtr(const char name[]) +{ + return this->remove(name, kPtr_Type); +} + +bool SkMetaData::removeBool(const char name[]) +{ + return this->remove(name, kBool_Type); +} + +/////////////////////////////////////////////////////////////////////////////// + +SkMetaData::Iter::Iter(const SkMetaData& metadata) { + fRec = metadata.fRec; +} + +void SkMetaData::Iter::reset(const SkMetaData& metadata) { + fRec = metadata.fRec; +} + +const char* SkMetaData::Iter::next(SkMetaData::Type* t, int* count) { + const char* name = nullptr; + + if (fRec) { + if (t) { + *t = (SkMetaData::Type)fRec->fType; + } + if (count) { + *count = fRec->fDataCount; + } + name = fRec->name(); + + fRec = fRec->fNext; + } + return name; +} + +/////////////////////////////////////////////////////////////////////////////// + +SkMetaData::Rec* SkMetaData::Rec::Alloc(size_t size) { + return (Rec*)sk_malloc_throw(size); +} + +void SkMetaData::Rec::Free(Rec* rec) { + sk_free(rec); +} diff --git a/lib/external/skia/tools/SkMetaData.h b/lib/external/skia/tools/SkMetaData.h new file mode 100644 index 00000000..b493252f --- /dev/null +++ b/lib/external/skia/tools/SkMetaData.h @@ -0,0 +1,123 @@ +/* + * Copyright 2006 The Android Open Source Project + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ +#ifndef SkMetaData_DEFINED +#define SkMetaData_DEFINED + +#include "include/core/SkScalar.h" + +/** A map from c-string keys to arrays of POD (int32_t, kScalar, void*, or bool) + values. +*/ +class SkMetaData { +public: + SkMetaData() {} + ~SkMetaData() { if (fRec) { this->reset(); } } + void reset(); + + bool findS32(const char name[], int32_t* value = nullptr) const; + bool findScalar(const char name[], SkScalar* value = nullptr) const; + const SkScalar* findScalars(const char name[], int* count, + SkScalar values[] = nullptr) const; + bool findPtr(const char name[], void** value = nullptr) const; + bool findBool(const char name[], bool* value = nullptr) const; + + bool hasS32(const char name[], int32_t value) const { + int32_t v; + return this->findS32(name, &v) && v == value; + } + bool hasScalar(const char name[], SkScalar value) const { + SkScalar v; + return this->findScalar(name, &v) && v == value; + } + bool hasPtr(const char name[], void* value) const { + void* v; + return this->findPtr(name, &v) && v == value; + } + bool hasBool(const char name[], bool value) const { + bool v; + return this->findBool(name, &v) && v == value; + } + + void setS32(const char name[], int32_t value); + void setScalar(const char name[], SkScalar value); + SkScalar* setScalars(const char name[], int count, const SkScalar values[] = nullptr); + void setPtr(const char name[], void* value); + void setBool(const char name[], bool value); + + bool removeS32(const char name[]); + bool removeScalar(const char name[]); + bool removePtr(const char name[]); + bool removeBool(const char name[]); + + enum Type { + kS32_Type, + kScalar_Type, + kPtr_Type, + kBool_Type, + + kTypeCount + }; + + struct Rec; + class Iter; + friend class Iter; + + class Iter { + public: + Iter() : fRec(nullptr) {} + Iter(const SkMetaData&); + + /** Reset the iterator, so that calling next() will return the first + data element. This is done implicitly in the constructor. + */ + void reset(const SkMetaData&); + + /** Each time next is called, it returns the name of the next data element, + or null when there are no more elements. If non-null is returned, then the + element's type is returned (if not null), and the number of data values + is returned in count (if not null). + */ + const char* next(Type*, int* count); + + private: + Rec* fRec; + }; + +public: + struct Rec { + Rec* fNext; + uint16_t fDataCount; // number of elements + uint8_t fDataLen; // sizeof a single element + uint8_t fType; + + const void* data() const { return (this + 1); } + void* data() { return (this + 1); } + const char* name() const { return (const char*)this->data() + fDataLen * fDataCount; } + char* name() { return (char*)this->data() + fDataLen * fDataCount; } + + static Rec* Alloc(size_t); + static void Free(Rec*); + }; + Rec* fRec = nullptr; + + const Rec* find(const char name[], Type) const; + void* set(const char name[], const void* data, size_t len, Type, int count); + bool remove(const char name[], Type); + + SkMetaData(const SkMetaData&) = delete; + SkMetaData& operator=(const SkMetaData&) = delete; + +private: + struct FindResult { + SkMetaData::Rec* rec; + SkMetaData::Rec* prev; + }; + FindResult findWithPrev(const char name[], Type type) const; + void remove(FindResult); +}; + +#endif diff --git a/lib/external/skia/tools/ToolUtils.cpp b/lib/external/skia/tools/ToolUtils.cpp new file mode 100644 index 00000000..28eb82d0 --- /dev/null +++ b/lib/external/skia/tools/ToolUtils.cpp @@ -0,0 +1,704 @@ +/* + * Copyright 2014 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "tools/ToolUtils.h" + +#include "include/core/SkBitmap.h" +#include "include/core/SkBlendMode.h" +#include "include/core/SkCanvas.h" +#include "include/core/SkColorPriv.h" +#include "include/core/SkImage.h" +#include "include/core/SkMatrix.h" +#include "include/core/SkPaint.h" +#include "include/core/SkPathBuilder.h" +#include "include/core/SkPicture.h" +#include "include/core/SkPixelRef.h" +#include "include/core/SkPixmap.h" +#include "include/core/SkPoint3.h" +#include "include/core/SkRRect.h" +#include "include/core/SkShader.h" +#include "include/core/SkSurface.h" +#include "include/core/SkTextBlob.h" +#include "include/ports/SkTypeface_win.h" +#include "include/private/SkColorData.h" +#include "include/private/SkFloatingPoint.h" +#include "src/core/SkFontPriv.h" + +#include +#include + +#ifdef SK_GRAPHITE_ENABLED +#include "include/gpu/graphite/ImageProvider.h" +#include +#endif + +#if defined(SK_ENABLE_SVG) +#include "modules/svg/include/SkSVGDOM.h" +#include "modules/svg/include/SkSVGNode.h" +#include "src/xml/SkDOM.h" +#endif + +#if SK_SUPPORT_GPU +#include "include/gpu/GrDirectContext.h" +#include "include/gpu/GrRecordingContext.h" +#include "src/gpu/ganesh/GrCaps.h" +#include "src/gpu/ganesh/GrDirectContextPriv.h" +#endif + +namespace ToolUtils { + +const char* alphatype_name(SkAlphaType at) { + switch (at) { + case kUnknown_SkAlphaType: return "Unknown"; + case kOpaque_SkAlphaType: return "Opaque"; + case kPremul_SkAlphaType: return "Premul"; + case kUnpremul_SkAlphaType: return "Unpremul"; + } + SkASSERT(false); + return "unexpected alphatype"; +} + +const char* colortype_name(SkColorType ct) { + switch (ct) { + case kUnknown_SkColorType: return "Unknown"; + case kAlpha_8_SkColorType: return "Alpha_8"; + case kA16_unorm_SkColorType: return "Alpha_16"; + case kA16_float_SkColorType: return "A16_float"; + case kRGB_565_SkColorType: return "RGB_565"; + case kARGB_4444_SkColorType: return "ARGB_4444"; + case kRGBA_8888_SkColorType: return "RGBA_8888"; + case kSRGBA_8888_SkColorType: return "SRGBA_8888"; + case kRGB_888x_SkColorType: return "RGB_888x"; + case kBGRA_8888_SkColorType: return "BGRA_8888"; + case kRGBA_1010102_SkColorType: return "RGBA_1010102"; + case kBGRA_1010102_SkColorType: return "BGRA_1010102"; + case kRGB_101010x_SkColorType: return "RGB_101010x"; + case kBGR_101010x_SkColorType: return "BGR_101010x"; + case kGray_8_SkColorType: return "Gray_8"; + case kRGBA_F16Norm_SkColorType: return "RGBA_F16Norm"; + case kRGBA_F16_SkColorType: return "RGBA_F16"; + case kRGBA_F32_SkColorType: return "RGBA_F32"; + case kR8G8_unorm_SkColorType: return "R8G8_unorm"; + case kR16G16_unorm_SkColorType: return "R16G16_unorm"; + case kR16G16_float_SkColorType: return "R16G16_float"; + case kR16G16B16A16_unorm_SkColorType: return "R16G16B16A16_unorm"; + case kR8_unorm_SkColorType: return "R8_unorm"; + } + SkASSERT(false); + return "unexpected colortype"; +} + +const char* colortype_depth(SkColorType ct) { + switch (ct) { + case kUnknown_SkColorType: return "Unknown"; + case kAlpha_8_SkColorType: return "A8"; + case kA16_unorm_SkColorType: return "A16"; + case kA16_float_SkColorType: return "AF16"; + case kRGB_565_SkColorType: return "565"; + case kARGB_4444_SkColorType: return "4444"; + case kRGBA_8888_SkColorType: return "8888"; + case kSRGBA_8888_SkColorType: return "8888"; + case kRGB_888x_SkColorType: return "888"; + case kBGRA_8888_SkColorType: return "8888"; + case kRGBA_1010102_SkColorType: return "1010102"; + case kBGRA_1010102_SkColorType: return "1010102"; + case kRGB_101010x_SkColorType: return "101010"; + case kBGR_101010x_SkColorType: return "101010"; + case kGray_8_SkColorType: return "G8"; + case kRGBA_F16Norm_SkColorType: return "F16Norm"; // TODO: "F16"? + case kRGBA_F16_SkColorType: return "F16"; + case kRGBA_F32_SkColorType: return "F32"; + case kR8G8_unorm_SkColorType: return "88"; + case kR16G16_unorm_SkColorType: return "1616"; + case kR16G16_float_SkColorType: return "F16F16"; + case kR16G16B16A16_unorm_SkColorType: return "16161616"; + case kR8_unorm_SkColorType: return "8"; + } + SkASSERT(false); + return "unexpected colortype"; +} + +const char* tilemode_name(SkTileMode mode) { + switch (mode) { + case SkTileMode::kClamp: return "clamp"; + case SkTileMode::kRepeat: return "repeat"; + case SkTileMode::kMirror: return "mirror"; + case SkTileMode::kDecal: return "decal"; + } + SkASSERT(false); + return "unexpected tilemode"; +} + +SkColor color_to_565(SkColor color) { + // Not a good idea to use this function for greyscale colors... + // it will add an obvious purple or green tint. + SkASSERT(SkColorGetR(color) != SkColorGetG(color) || SkColorGetR(color) != SkColorGetB(color) || + SkColorGetG(color) != SkColorGetB(color)); + + SkPMColor pmColor = SkPreMultiplyColor(color); + U16CPU color16 = SkPixel32ToPixel16(pmColor); + return SkPixel16ToColor(color16); +} + +sk_sp create_checkerboard_shader(SkColor c1, SkColor c2, int size) { + SkBitmap bm; + bm.allocPixels(SkImageInfo::MakeS32(2 * size, 2 * size, kPremul_SkAlphaType)); + bm.eraseColor(c1); + bm.eraseArea(SkIRect::MakeLTRB(0, 0, size, size), c2); + bm.eraseArea(SkIRect::MakeLTRB(size, size, 2 * size, 2 * size), c2); + return bm.makeShader(SkTileMode::kRepeat, SkTileMode::kRepeat, SkSamplingOptions()); +} + +SkBitmap create_checkerboard_bitmap(int w, int h, SkColor c1, SkColor c2, int checkSize) { + SkBitmap bitmap; + bitmap.allocPixels(SkImageInfo::MakeS32(w, h, kPremul_SkAlphaType)); + SkCanvas canvas(bitmap); + + ToolUtils::draw_checkerboard(&canvas, c1, c2, checkSize); + return bitmap; +} + +sk_sp create_checkerboard_image(int w, int h, SkColor c1, SkColor c2, int checkSize) { + auto surf = SkSurface::MakeRasterN32Premul(w, h); + ToolUtils::draw_checkerboard(surf->getCanvas(), c1, c2, checkSize); + return surf->makeImageSnapshot(); +} + +void draw_checkerboard(SkCanvas* canvas, SkColor c1, SkColor c2, int size) { + SkPaint paint; + paint.setShader(create_checkerboard_shader(c1, c2, size)); + paint.setBlendMode(SkBlendMode::kSrc); + canvas->drawPaint(paint); +} + +SkBitmap +create_string_bitmap(int w, int h, SkColor c, int x, int y, int textSize, const char* str) { + SkBitmap bitmap; + bitmap.allocN32Pixels(w, h); + SkCanvas canvas(bitmap); + + SkPaint paint; + paint.setColor(c); + + SkFont font(ToolUtils::create_portable_typeface(), textSize); + + canvas.clear(0x00000000); + canvas.drawSimpleText(str, + strlen(str), + SkTextEncoding::kUTF8, + SkIntToScalar(x), + SkIntToScalar(y), + font, + paint); + + // Tag data as sRGB (without doing any color space conversion). Color-space aware configs + // will process this correctly but legacy configs will render as if this returned N32. + SkBitmap result; + result.setInfo(SkImageInfo::MakeS32(w, h, kPremul_SkAlphaType)); + result.setPixelRef(sk_ref_sp(bitmap.pixelRef()), 0, 0); + return result; +} + +sk_sp create_string_image(int w, int h, SkColor c, int x, int y, int textSize, + const char* str) { + return create_string_bitmap(w, h, c, x, y, textSize, str).asImage(); +} + +void add_to_text_blob_w_len(SkTextBlobBuilder* builder, + const char* text, + size_t len, + SkTextEncoding encoding, + const SkFont& font, + SkScalar x, + SkScalar y) { + int count = font.countText(text, len, encoding); + if (count < 1) { + return; + } + auto run = builder->allocRun(font, count, x, y); + font.textToGlyphs(text, len, encoding, run.glyphs, count); +} + +void add_to_text_blob(SkTextBlobBuilder* builder, + const char* text, + const SkFont& font, + SkScalar x, + SkScalar y) { + add_to_text_blob_w_len(builder, text, strlen(text), SkTextEncoding::kUTF8, font, x, y); +} + +void get_text_path(const SkFont& font, + const void* text, + size_t length, + SkTextEncoding encoding, + SkPath* dst, + const SkPoint pos[]) { + SkAutoToGlyphs atg(font, text, length, encoding); + const int count = atg.count(); + SkAutoTArray computedPos; + if (pos == nullptr) { + computedPos.reset(count); + font.getPos(atg.glyphs(), count, &computedPos[0]); + pos = computedPos.get(); + } + + struct Rec { + SkPath* fDst; + const SkPoint* fPos; + } rec = {dst, pos}; + font.getPaths(atg.glyphs(), + atg.count(), + [](const SkPath* src, const SkMatrix& mx, void* ctx) { + Rec* rec = (Rec*)ctx; + if (src) { + SkMatrix tmp(mx); + tmp.postTranslate(rec->fPos->fX, rec->fPos->fY); + rec->fDst->addPath(*src, tmp); + } + rec->fPos += 1; + }, + &rec); +} + +SkPath make_star(const SkRect& bounds, int numPts, int step) { + SkASSERT(numPts != step); + SkPathBuilder builder; + builder.setFillType(SkPathFillType::kEvenOdd); + builder.moveTo(0, -1); + for (int i = 1; i < numPts; ++i) { + int idx = i * step % numPts; + SkScalar theta = idx * 2 * SK_ScalarPI / numPts + SK_ScalarPI / 2; + SkScalar x = SkScalarCos(theta); + SkScalar y = -SkScalarSin(theta); + builder.lineTo(x, y); + } + SkPath path = builder.detach(); + path.transform(SkMatrix::RectToRect(path.getBounds(), bounds)); + return path; +} + +static inline void norm_to_rgb(SkBitmap* bm, int x, int y, const SkVector3& norm) { + SkASSERT(SkScalarNearlyEqual(norm.length(), 1.0f)); + unsigned char r = static_cast((0.5f * norm.fX + 0.5f) * 255); + unsigned char g = static_cast((-0.5f * norm.fY + 0.5f) * 255); + unsigned char b = static_cast((0.5f * norm.fZ + 0.5f) * 255); + *bm->getAddr32(x, y) = SkPackARGB32(0xFF, r, g, b); +} + +void create_hemi_normal_map(SkBitmap* bm, const SkIRect& dst) { + const SkPoint center = + SkPoint::Make(dst.fLeft + (dst.width() / 2.0f), dst.fTop + (dst.height() / 2.0f)); + const SkPoint halfSize = SkPoint::Make(dst.width() / 2.0f, dst.height() / 2.0f); + + SkVector3 norm; + + for (int y = dst.fTop; y < dst.fBottom; ++y) { + for (int x = dst.fLeft; x < dst.fRight; ++x) { + norm.fX = (x + 0.5f - center.fX) / halfSize.fX; + norm.fY = (y + 0.5f - center.fY) / halfSize.fY; + + SkScalar tmp = norm.fX * norm.fX + norm.fY * norm.fY; + if (tmp >= 1.0f) { + norm.set(0.0f, 0.0f, 1.0f); + } else { + norm.fZ = sqrtf(1.0f - tmp); + } + + norm_to_rgb(bm, x, y, norm); + } + } +} + +void create_frustum_normal_map(SkBitmap* bm, const SkIRect& dst) { + const SkPoint center = + SkPoint::Make(dst.fLeft + (dst.width() / 2.0f), dst.fTop + (dst.height() / 2.0f)); + + SkIRect inner = dst; + inner.inset(dst.width() / 4, dst.height() / 4); + + SkPoint3 norm; + const SkPoint3 left = SkPoint3::Make(-SK_ScalarRoot2Over2, 0.0f, SK_ScalarRoot2Over2); + const SkPoint3 up = SkPoint3::Make(0.0f, -SK_ScalarRoot2Over2, SK_ScalarRoot2Over2); + const SkPoint3 right = SkPoint3::Make(SK_ScalarRoot2Over2, 0.0f, SK_ScalarRoot2Over2); + const SkPoint3 down = SkPoint3::Make(0.0f, SK_ScalarRoot2Over2, SK_ScalarRoot2Over2); + + for (int y = dst.fTop; y < dst.fBottom; ++y) { + for (int x = dst.fLeft; x < dst.fRight; ++x) { + if (inner.contains(x, y)) { + norm.set(0.0f, 0.0f, 1.0f); + } else { + SkScalar locX = x + 0.5f - center.fX; + SkScalar locY = y + 0.5f - center.fY; + + if (locX >= 0.0f) { + if (locY > 0.0f) { + norm = locX >= locY ? right : down; // LR corner + } else { + norm = locX > -locY ? right : up; // UR corner + } + } else { + if (locY > 0.0f) { + norm = -locX > locY ? left : down; // LL corner + } else { + norm = locX > locY ? up : left; // UL corner + } + } + } + + norm_to_rgb(bm, x, y, norm); + } + } +} + +void create_tetra_normal_map(SkBitmap* bm, const SkIRect& dst) { + const SkPoint center = + SkPoint::Make(dst.fLeft + (dst.width() / 2.0f), dst.fTop + (dst.height() / 2.0f)); + + static const SkScalar k1OverRoot3 = 0.5773502692f; + + SkPoint3 norm; + const SkPoint3 leftUp = SkPoint3::Make(-k1OverRoot3, -k1OverRoot3, k1OverRoot3); + const SkPoint3 rightUp = SkPoint3::Make(k1OverRoot3, -k1OverRoot3, k1OverRoot3); + const SkPoint3 down = SkPoint3::Make(0.0f, SK_ScalarRoot2Over2, SK_ScalarRoot2Over2); + + for (int y = dst.fTop; y < dst.fBottom; ++y) { + for (int x = dst.fLeft; x < dst.fRight; ++x) { + SkScalar locX = x + 0.5f - center.fX; + SkScalar locY = y + 0.5f - center.fY; + + if (locX >= 0.0f) { + if (locY > 0.0f) { + norm = locX >= locY ? rightUp : down; // LR corner + } else { + norm = rightUp; + } + } else { + if (locY > 0.0f) { + norm = -locX > locY ? leftUp : down; // LL corner + } else { + norm = leftUp; + } + } + + norm_to_rgb(bm, x, y, norm); + } + } +} + +bool copy_to(SkBitmap* dst, SkColorType dstColorType, const SkBitmap& src) { + SkPixmap srcPM; + if (!src.peekPixels(&srcPM)) { + return false; + } + + SkBitmap tmpDst; + SkImageInfo dstInfo = srcPM.info().makeColorType(dstColorType); + if (!tmpDst.setInfo(dstInfo)) { + return false; + } + + if (!tmpDst.tryAllocPixels()) { + return false; + } + + SkPixmap dstPM; + if (!tmpDst.peekPixels(&dstPM)) { + return false; + } + + if (!srcPM.readPixels(dstPM)) { + return false; + } + + dst->swap(tmpDst); + return true; +} + +void copy_to_g8(SkBitmap* dst, const SkBitmap& src) { + SkASSERT(kBGRA_8888_SkColorType == src.colorType() || + kRGBA_8888_SkColorType == src.colorType()); + + SkImageInfo grayInfo = src.info().makeColorType(kGray_8_SkColorType); + dst->allocPixels(grayInfo); + uint8_t* dst8 = (uint8_t*)dst->getPixels(); + const uint32_t* src32 = (const uint32_t*)src.getPixels(); + + const int w = src.width(); + const int h = src.height(); + const bool isBGRA = (kBGRA_8888_SkColorType == src.colorType()); + for (int y = 0; y < h; ++y) { + if (isBGRA) { + // BGRA + for (int x = 0; x < w; ++x) { + uint32_t s = src32[x]; + dst8[x] = SkComputeLuminance((s >> 16) & 0xFF, (s >> 8) & 0xFF, s & 0xFF); + } + } else { + // RGBA + for (int x = 0; x < w; ++x) { + uint32_t s = src32[x]; + dst8[x] = SkComputeLuminance(s & 0xFF, (s >> 8) & 0xFF, (s >> 16) & 0xFF); + } + } + src32 = (const uint32_t*)((const char*)src32 + src.rowBytes()); + dst8 += dst->rowBytes(); + } +} + +////////////////////////////////////////////////////////////////////////////////////////////// + +bool equal_pixels(const SkPixmap& a, const SkPixmap& b) { + if (a.width() != b.width() || a.height() != b.height() || a.colorType() != b.colorType()) { + return false; + } + + for (int y = 0; y < a.height(); ++y) { + const char* aptr = (const char*)a.addr(0, y); + const char* bptr = (const char*)b.addr(0, y); + if (0 != memcmp(aptr, bptr, a.width() * a.info().bytesPerPixel())) { + return false; + } + } + return true; +} + +bool equal_pixels(const SkBitmap& bm0, const SkBitmap& bm1) { + SkPixmap pm0, pm1; + return bm0.peekPixels(&pm0) && bm1.peekPixels(&pm1) && equal_pixels(pm0, pm1); +} + +bool equal_pixels(const SkImage* a, const SkImage* b) { + // ensure that peekPixels will succeed + auto imga = a->makeRasterImage(); + auto imgb = b->makeRasterImage(); + + SkPixmap pm0, pm1; + return imga->peekPixels(&pm0) && imgb->peekPixels(&pm1) && equal_pixels(pm0, pm1); +} + +sk_sp makeSurface(SkCanvas* canvas, + const SkImageInfo& info, + const SkSurfaceProps* props) { + auto surf = canvas->makeSurface(info, props); + if (!surf) { + surf = SkSurface::MakeRaster(info, props); + } + return surf; +} + +void sniff_paths(const char filepath[], std::function callback) { + SkFILEStream stream(filepath); + if (!stream.isValid()) { + SkDebugf("sniff_paths: invalid input file at \"%s\"\n", filepath); + return; + } + + class PathSniffer : public SkCanvas { + public: + PathSniffer(std::function callback) + : SkCanvas(4096, 4096, nullptr) + , fPathSniffCallback(callback) {} + private: + void onDrawPath(const SkPath& path, const SkPaint& paint) override { + fPathSniffCallback(this->getTotalMatrix(), path, paint); + } + std::function fPathSniffCallback; + }; + + PathSniffer pathSniffer(callback); + if (const char* ext = strrchr(filepath, '.'); ext && !strcmp(ext, ".svg")) { +#if defined(SK_ENABLE_SVG) + sk_sp svg = SkSVGDOM::MakeFromStream(stream); + if (!svg) { + SkDebugf("sniff_paths: couldn't load svg at \"%s\"\n", filepath); + return; + } + svg->setContainerSize(SkSize::Make(pathSniffer.getBaseLayerSize())); + svg->render(&pathSniffer); +#endif + } else { + sk_sp skp = SkPicture::MakeFromStream(&stream); + if (!skp) { + SkDebugf("sniff_paths: couldn't load skp at \"%s\"\n", filepath); + return; + } + skp->playback(&pathSniffer); + } +} + +#if SK_SUPPORT_GPU +sk_sp MakeTextureImage(SkCanvas* canvas, sk_sp orig) { + if (!orig) { + return nullptr; + } + + if (canvas->recordingContext() && canvas->recordingContext()->asDirectContext()) { + GrDirectContext* dContext = canvas->recordingContext()->asDirectContext(); + const GrCaps* caps = dContext->priv().caps(); + + if (orig->width() >= caps->maxTextureSize() || orig->height() >= caps->maxTextureSize()) { + // Ganesh is able to tile large SkImage draws. Always forcing SkImages to be uploaded + // prevents this feature from being tested by our tools. For now, leave excessively + // large SkImages as bitmaps. + return orig; + } + + return orig->makeTextureImage(dContext); + } +#if defined(SK_GRAPHITE_ENABLED) + else if (canvas->recorder()) { + return orig->makeTextureImage(canvas->recorder()); + } +#endif + + return orig; +} +#endif + +VariationSliders::VariationSliders(SkTypeface* typeface, + SkFontArguments::VariationPosition variationPosition) { + if (!typeface) { + return; + } + + int numAxes = typeface->getVariationDesignParameters(nullptr, 0); + if (numAxes < 0) { + return; + } + + std::unique_ptr copiedAxes = + std::make_unique(numAxes); + + numAxes = typeface->getVariationDesignParameters(copiedAxes.get(), numAxes); + if (numAxes < 0) { + return; + } + + auto argVariationPositionOrDefault = [&variationPosition](SkFourByteTag tag, + SkScalar defaultValue) -> SkScalar { + for (int i = 0; i < variationPosition.coordinateCount; ++i) { + if (variationPosition.coordinates[i].axis == tag) { + return variationPosition.coordinates[i].value; + } + } + return defaultValue; + }; + + fAxisSliders.resize(numAxes); + fCoords = std::make_unique(numAxes); + for (int i = 0; i < numAxes; ++i) { + fAxisSliders[i].axis = copiedAxes[i]; + fAxisSliders[i].current = + argVariationPositionOrDefault(copiedAxes[i].tag, copiedAxes[i].def); + fAxisSliders[i].name = tagToString(fAxisSliders[i].axis.tag); + fCoords[i] = { fAxisSliders[i].axis.tag, fAxisSliders[i].current }; + } +} + +/* static */ +SkString VariationSliders::tagToString(SkFourByteTag tag) { + char tagAsString[5]; + tagAsString[4] = 0; + tagAsString[0] = (char)(uint8_t)(tag >> 24); + tagAsString[1] = (char)(uint8_t)(tag >> 16); + tagAsString[2] = (char)(uint8_t)(tag >> 8); + tagAsString[3] = (char)(uint8_t)(tag >> 0); + return SkString(tagAsString); +} + +bool VariationSliders::writeControls(SkMetaData* controls) { + for (size_t i = 0; i < fAxisSliders.size(); ++i) { + SkScalar axisVars[kAxisVarsSize]; + + axisVars[0] = fAxisSliders[i].current; + axisVars[1] = fAxisSliders[i].axis.min; + axisVars[2] = fAxisSliders[i].axis.max; + controls->setScalars(fAxisSliders[i].name.c_str(), kAxisVarsSize, axisVars); + } + return true; +} + +void VariationSliders::readControls(const SkMetaData& controls, bool* changed) { + for (size_t i = 0; i < fAxisSliders.size(); ++i) { + SkScalar axisVars[kAxisVarsSize] = {0}; + int resultAxisVarsSize = 0; + SkASSERT_RELEASE(controls.findScalars( + tagToString(fAxisSliders[i].axis.tag).c_str(), &resultAxisVarsSize, axisVars)); + SkASSERT_RELEASE(resultAxisVarsSize == kAxisVarsSize); + if (changed) { + *changed |= fAxisSliders[i].current != axisVars[0]; + } + fAxisSliders[i].current = axisVars[0]; + fCoords[i] = { fAxisSliders[i].axis.tag, fAxisSliders[i].current }; + } +} + +SkSpan VariationSliders::getCoordinates() { + return SkSpan{fCoords.get(), + fAxisSliders.size()}; +} + +#ifdef SK_GRAPHITE_ENABLED + +// Currently, we give each new Recorder its own ImageProvider. This means we don't have to deal +// w/ any threading issues. +// TODO: We should probably have this class generate and report some cache stats +// TODO: Hook up to listener system? +// TODO: add testing of a single ImageProvider passed to multiple recorders +class TestingImageProvider : public skgpu::graphite::ImageProvider { +public: + ~TestingImageProvider() override {} + + sk_sp findOrCreate(skgpu::graphite::Recorder* recorder, + const SkImage* image, + SkImage::RequiredImageProperties requiredProps) override { + if (requiredProps.fMipmapped == skgpu::graphite::Mipmapped::kNo) { + // If no mipmaps are required, check to see if we have a mipmapped version anyway - + // since it can be used in that case. + // TODO: we could get fancy and, if ever a mipmapped key eclipsed a non-mipmapped + // key, we could remove the hidden non-mipmapped key/image from the cache. + uint64_t mipMappedKey = ((uint64_t)image->uniqueID() << 32) | 0x1; + auto result = fCache.find(mipMappedKey); + if (result != fCache.end()) { + return result->second; + } + } + + uint64_t key = ((uint64_t)image->uniqueID() << 32) | + (requiredProps.fMipmapped == skgpu::graphite::Mipmapped::kYes ? 0x1 : 0x0); + + auto result = fCache.find(key); + if (result != fCache.end()) { + return result->second; + } + + sk_sp newImage = image->makeTextureImage(recorder, requiredProps); + if (!newImage) { + return nullptr; + } + + auto [iter, success] = fCache.insert({ key, newImage }); + SkASSERT(success); + + return iter->second; + } + +private: + std::unordered_map> fCache; +}; + +skgpu::graphite::RecorderOptions CreateTestingRecorderOptions() { + skgpu::graphite::RecorderOptions options; + + options.fImageProvider.reset(new TestingImageProvider); + + return options; +} + +#endif // SK_GRAPHITE_ENABLED + +} // namespace ToolUtils diff --git a/lib/external/skia/tools/ToolUtils.h b/lib/external/skia/tools/ToolUtils.h new file mode 100644 index 00000000..a3fbc1a8 --- /dev/null +++ b/lib/external/skia/tools/ToolUtils.h @@ -0,0 +1,351 @@ +/* + * Copyright 2014 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef ToolUtils_DEFINED +#define ToolUtils_DEFINED + +#include "include/core/SkColor.h" +#include "include/core/SkData.h" +#include "include/core/SkEncodedImageFormat.h" +#include "include/core/SkFont.h" +#include "include/core/SkFontStyle.h" +#include "include/core/SkFontTypes.h" +#include "include/core/SkImageEncoder.h" +#include "include/core/SkImageInfo.h" +#include "include/core/SkPixmap.h" +#include "include/core/SkRect.h" +#include "include/core/SkRefCnt.h" +#include "include/core/SkScalar.h" +#include "include/core/SkSpan.h" +#include "include/core/SkStream.h" +#include "include/core/SkSurface.h" +#include "include/core/SkTypeface.h" +#include "include/core/SkTypes.h" +#include "include/private/SkTArray.h" +#include "include/private/SkTDArray.h" +#include "include/utils/SkRandom.h" +#include "src/core/SkTInternalLList.h" +#include "tools/SkMetaData.h" + +#ifdef SK_GRAPHITE_ENABLED +#include "include/gpu/graphite/Recorder.h" +#endif + +class SkBitmap; +class SkCanvas; +class SkFontStyle; +class SkImage; +class SkPath; +class SkPixmap; +class SkRRect; +class SkShader; +class SkSurface; +class SkSurfaceProps; +class SkTextBlobBuilder; +class SkTypeface; + +namespace ToolUtils { + +const char* alphatype_name (SkAlphaType); +const char* colortype_name (SkColorType); +const char* colortype_depth(SkColorType); // like colortype_name, but channel order agnostic +const char* tilemode_name(SkTileMode); + +/** + * Map opaque colors from 8888 to 565. + */ +SkColor color_to_565(SkColor color); + +/* Return a color emoji typeface with planets to scale if available. */ +sk_sp planet_typeface(); + +/** Return a color emoji typeface if available. */ +sk_sp emoji_typeface(); + +/** Sample text for the emoji_typeface font. */ +constexpr const char* emoji_sample_text() { + return "\xF0\x9F\x98\x80" + " " + "\xE2\x99\xA2"; // 😀 ♢ +} + +/** A simple SkUserTypeface for testing. */ +sk_sp sample_user_typeface(); + +/** + * Returns a platform-independent text renderer. + */ +sk_sp create_portable_typeface(const char* name, SkFontStyle style); + +static inline sk_sp create_portable_typeface() { + return create_portable_typeface(nullptr, SkFontStyle()); +} + +void get_text_path(const SkFont&, + const void* text, + size_t length, + SkTextEncoding, + SkPath*, + const SkPoint* positions = nullptr); + +/** + * Returns true iff all of the pixels between the two images are identical. + * + * If the configs differ, return false. + */ +bool equal_pixels(const SkPixmap&, const SkPixmap&); +bool equal_pixels(const SkBitmap&, const SkBitmap&); +bool equal_pixels(const SkImage* a, const SkImage* b); + +/** Returns a newly created CheckerboardShader. */ +sk_sp create_checkerboard_shader(SkColor c1, SkColor c2, int size); + +/** Draw a checkerboard pattern in the current canvas, restricted to + the current clip, using SkXfermode::kSrc_Mode. */ +void draw_checkerboard(SkCanvas* canvas, SkColor color1, SkColor color2, int checkSize); + +/** Make it easier to create a bitmap-based checkerboard */ +SkBitmap create_checkerboard_bitmap(int w, int h, SkColor c1, SkColor c2, int checkSize); + +sk_sp create_checkerboard_image(int w, int h, SkColor c1, SkColor c2, int checkSize); + +/** A default checkerboard. */ +inline void draw_checkerboard(SkCanvas* canvas) { + ToolUtils::draw_checkerboard(canvas, 0xFF999999, 0xFF666666, 8); +} + +SkBitmap create_string_bitmap(int w, int h, SkColor c, int x, int y, int textSize, const char* str); +sk_sp create_string_image(int w, int h, SkColor c, int x, int y, int textSize, const char* str); + +// If the canvas does't make a surface (e.g. recording), make a raster surface +sk_sp makeSurface(SkCanvas*, const SkImageInfo&, const SkSurfaceProps* = nullptr); + +// A helper for inserting a drawtext call into a SkTextBlobBuilder +void add_to_text_blob_w_len(SkTextBlobBuilder*, + const char* text, + size_t len, + SkTextEncoding, + const SkFont&, + SkScalar x, + SkScalar y); + +void add_to_text_blob(SkTextBlobBuilder*, const char* text, const SkFont&, SkScalar x, SkScalar y); + +// Constructs a star by walking a 'numPts'-sided regular polygon with even/odd fill: +// +// moveTo(pts[0]); +// lineTo(pts[step % numPts]); +// ... +// lineTo(pts[(step * (N - 1)) % numPts]); +// +// numPts=5, step=2 will produce a classic five-point star. +// +// numPts and step must be co-prime. +SkPath make_star(const SkRect& bounds, int numPts = 5, int step = 2); + +void create_hemi_normal_map(SkBitmap* bm, const SkIRect& dst); + +void create_frustum_normal_map(SkBitmap* bm, const SkIRect& dst); + +void create_tetra_normal_map(SkBitmap* bm, const SkIRect& dst); + +// A helper object to test the topological sorting code (TopoSortBench.cpp & TopoSortTest.cpp) +class TopoTestNode : public SkRefCnt { +public: + TopoTestNode(int id) : fID(id) {} + + void dependsOn(TopoTestNode* src) { *fDependencies.append() = src; } + void targets(uint32_t target) { *fTargets.append() = target; } + + int id() const { return fID; } + void reset() { + fOutputPos = 0; + fTempMark = false; + fWasOutput = false; + } + + uint32_t outputPos() const { + SkASSERT(fWasOutput); + return fOutputPos; + } + + // check that the topological sort is valid for this node + bool check() { + if (!fWasOutput) { + return false; + } + + for (int i = 0; i < fDependencies.count(); ++i) { + if (!fDependencies[i]->fWasOutput) { + return false; + } + // This node should've been output after all the nodes on which it depends + if (fOutputPos < fDependencies[i]->outputPos()) { + return false; + } + } + + return true; + } + + // The following 7 methods are needed by the topological sort + static void SetTempMark(TopoTestNode* node) { node->fTempMark = true; } + static void ResetTempMark(TopoTestNode* node) { node->fTempMark = false; } + static bool IsTempMarked(TopoTestNode* node) { return node->fTempMark; } + static void Output(TopoTestNode* node, uint32_t outputPos) { + SkASSERT(!node->fWasOutput); + node->fOutputPos = outputPos; + node->fWasOutput = true; + } + static bool WasOutput(TopoTestNode* node) { return node->fWasOutput; } + static uint32_t GetIndex(TopoTestNode* node) { return node->outputPos(); } + static int NumDependencies(TopoTestNode* node) { return node->fDependencies.count(); } + static TopoTestNode* Dependency(TopoTestNode* node, int index) { + return node->fDependencies[index]; + } + static int NumTargets(TopoTestNode* node) { return node->fTargets.count(); } + static uint32_t GetTarget(TopoTestNode* node, int i) { return node->fTargets[i]; } + static uint32_t GetID(TopoTestNode* node) { return node->id(); } + + // Helper functions for TopoSortBench & TopoSortTest + static void AllocNodes(SkTArray>* graph, int num) { + graph->reserve_back(num); + + for (int i = 0; i < num; ++i) { + graph->push_back(sk_sp(new TopoTestNode(i))); + } + } + +#ifdef SK_DEBUG + static void Print(const SkTArray& graph) { + for (int i = 0; i < graph.count(); ++i) { + SkDebugf("%d, ", graph[i]->id()); + } + SkDebugf("\n"); + } +#endif + + // randomize the array + static void Shuffle(SkSpan> graph, SkRandom* rand) { + for (int i = graph.size() - 1; i > 0; --i) { + int swap = rand->nextU() % (i + 1); + + graph[i].swap(graph[swap]); + } + } + + SK_DECLARE_INTERNAL_LLIST_INTERFACE(TopoTestNode); + +private: + int fID; + uint32_t fOutputPos = 0; + bool fTempMark = false; + bool fWasOutput = false; + + SkTDArray fDependencies; + SkTDArray fTargets; +}; + +template +inline bool EncodeImageToFile(const char* path, const T& src, SkEncodedImageFormat f, int q) { + SkFILEWStream file(path); + return file.isValid() && SkEncodeImage(&file, src, f, q); +} + +bool copy_to(SkBitmap* dst, SkColorType dstCT, const SkBitmap& src); +void copy_to_g8(SkBitmap* dst, const SkBitmap& src); + +class PixelIter { +public: + PixelIter(); + PixelIter(SkSurface* surf) { + SkPixmap pm; + if (!surf->peekPixels(&pm)) { + pm.reset(); + } + this->reset(pm); + } + + void reset(const SkPixmap& pm) { + fPM = pm; + fLoc = {-1, 0}; + } + + void* next(SkIPoint* loc = nullptr) { + if (!fPM.addr()) { + return nullptr; + } + fLoc.fX += 1; + if (fLoc.fX >= fPM.width()) { + fLoc.fX = 0; + if (++fLoc.fY >= fPM.height()) { + this->setDone(); + return nullptr; + } + } + if (loc) { + *loc = fLoc; + } + return fPM.writable_addr(fLoc.fX, fLoc.fY); + } + + void setDone() { fPM.reset(); } + +private: + SkPixmap fPM; + SkIPoint fLoc; +}; + +using PathSniffCallback = void(const SkMatrix&, const SkPath&, const SkPaint&); + +// Calls the provided PathSniffCallback for each path in the given file. +// Supported file formats are .svg and .skp. +void sniff_paths(const char filepath[], std::function); + +#if SK_SUPPORT_GPU +sk_sp MakeTextureImage(SkCanvas* canvas, sk_sp orig); +#endif + +// Initialised with a font, this class can be called to setup GM UI with sliders for font +// variations, and returns a set of variation coordinates that matches what the sliders in the UI +// are set to. Useful for testing variable font properties, see colrv1.cpp. +class VariationSliders { +public: + VariationSliders() {} + + VariationSliders(SkTypeface*, + SkFontArguments::VariationPosition variationPosition = {nullptr, 0}); + + bool writeControls(SkMetaData* controls); + + /* Scans controls for information about the variation axes that the user may have configured. + * Optionally pass in a boolean to receive information on whether the axes were updated. */ + void readControls(const SkMetaData& controls, bool* changed = nullptr); + + SkSpan getCoordinates(); + + static SkString tagToString(SkFourByteTag tag); + +private: + struct AxisSlider { + SkScalar current; + SkFontParameters::Variation::Axis axis; + SkString name; + }; + + std::vector fAxisSliders; + std::unique_ptr fCoords; + static constexpr size_t kAxisVarsSize = 3; +}; + +#ifdef SK_GRAPHITE_ENABLED +skgpu::graphite::RecorderOptions CreateTestingRecorderOptions(); +#endif + +} // namespace ToolUtils + +#endif // ToolUtils_DEFINED diff --git a/lib/impl/macos/quartz2d/canvas.mm b/lib/impl/macos/quartz2d/canvas.mm index 39b648f0..926a8133 100755 --- a/lib/impl/macos/quartz2d/canvas.mm +++ b/lib/impl/macos/quartz2d/canvas.mm @@ -233,20 +233,6 @@ void make_gradient(std::vector const& space, CGGradientRef& { } - void canvas::pre_scale(float sc) - { - // Quartz-2D does not need to be pre-scaled because it already does this - // when setting up its context. But see how we take the actual pre-scale - // in the constructor. - } - - float canvas::pre_scale() const - { - // Quartz-2D does not need to be pre-scaled because it already does this - // when setting up its context. - return 1.0; // Pre-scale is always 1.0 - } - void canvas::translate(point p) { CGContextTranslateCTM(CGContextRef(_context), p.x, p.y); diff --git a/lib/impl/skia/canvas.cpp b/lib/impl/skia/canvas.cpp index b0e1ddfb..72507d96 100755 --- a/lib/impl/skia/canvas.cpp +++ b/lib/impl/skia/canvas.cpp @@ -47,8 +47,8 @@ namespace cycfi::artist void save(); void restore(); - - float _pre_scale = 1.0; + affine_transform get_inv_affine() const; + void set_inv_affine(affine_transform xf); static SkPaint& get_fill_paint(canvas const& cnv); @@ -79,6 +79,7 @@ namespace cycfi::artist state_info_stack _stack; SkPaint _clear_paint; + affine_transform _inv_affine; }; canvas::canvas_state::canvas_state() @@ -135,25 +136,25 @@ namespace cycfi::artist return cnv._state->fill_paint(); } - canvas::canvas(canvas_impl* context_) - : _context{context_} - , _state{std::make_unique()} + affine_transform canvas::canvas_state::get_inv_affine() const { + return _inv_affine; } - canvas::~canvas() + void canvas::canvas_state::set_inv_affine(affine_transform xf) { + _inv_affine = xf; } - void canvas::pre_scale(float sc) + canvas::canvas(canvas_impl* context_) + : _context{context_} + , _state{std::make_unique()} { - scale(sc, sc); - _state->_pre_scale = sc; + _state->set_inv_affine(transform().invert()); } - float canvas::pre_scale() const + canvas::~canvas() { - return _state->_pre_scale; } void canvas::translate(point p) @@ -178,21 +179,30 @@ namespace cycfi::artist point canvas::device_to_user(point p) { - auto scale = _state->_pre_scale; - auto mat = _context->getTotalMatrix(); - (void) mat.invert(&mat); - SkPoint skp; - mat.mapXY(p.x * scale, p.y * scale, &skp); - return {skp.x(), skp.y()}; + // Get the current transform + auto af = transform(); + + // Undo the initial transform + auto xaf = af * _state->get_inv_affine(); + + // Map the point to the inverted `xaf` transform + auto up = xaf.invert().apply(p); + + return {float(up.x), float(up.y)}; } point canvas::user_to_device(point p) { - auto scale = _state->_pre_scale; - auto mat = _context->getTotalMatrix(); - SkPoint skp; - mat.mapXY(p.x, p.y, &skp); - return {skp.x() / scale, skp.y() / scale}; + // Get the current transform + auto af = transform(); + + // Undo the initial transform + auto xaf = af * _state->get_inv_affine(); + + // Map the point to the `xaf` transform + auto up = xaf.apply(p); + + return {float(up.x), float(up.y)}; } affine_transform canvas::transform() const @@ -392,11 +402,10 @@ namespace cycfi::artist void canvas::shadow_style(point offset, float blur, color c) { - constexpr auto blur_factor = 0.4f; - + constexpr auto blur_factor = 1.0f; auto matrix = _context->getTotalMatrix(); - auto scx = matrix.getScaleX() / _state->_pre_scale; - auto scy = matrix.getScaleY() / _state->_pre_scale; + float scx = matrix.getScaleX(); + float scy = matrix.getScaleY(); auto shadow = SkImageFilters::DropShadow( offset.x / scx diff --git a/lib/include/artist/canvas.hpp b/lib/include/artist/canvas.hpp index add56783..029bdbe2 100755 --- a/lib/include/artist/canvas.hpp +++ b/lib/include/artist/canvas.hpp @@ -47,9 +47,6 @@ namespace cycfi::artist explicit operator bool() const; bool operator!() const; - void pre_scale(float sc); - float pre_scale() const; - /////////////////////////////////////////////////////////////////////////////////// // Transforms void translate(point p); diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 989be507..3f9afb9b 100755 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -45,9 +45,15 @@ elseif (MSVC) artist_test main.cpp ) - set_property(TARGET artist_test PROPERTY - MSVC_RUNTIME_LIBRARY "MultiThreaded" - ) + if (CMAKE_BUILD_TYPE STREQUAL "Release" OR CMAKE_C_COMPILER_ID STREQUAL "Clang") + set_property(TARGET artist_test PROPERTY + MSVC_RUNTIME_LIBRARY "MultiThreaded" + ) + else() + set_property(TARGET artist_test PROPERTY + MSVC_RUNTIME_LIBRARY "MultiThreadedDebug" + ) + endif() elseif (UNIX)