diff --git a/data/copy-dpf-stuff b/data/copy-dpf-stuff index 26523f9c4..4521ebd6c 100755 --- a/data/copy-dpf-stuff +++ b/data/copy-dpf-stuff @@ -2,39 +2,61 @@ set -e -cp -v /home/falktx/FOSS/GIT-mine/DISTRHO/DPF/dgl/*.hpp /home/falktx/FOSS/GIT-mine/falkTX/Carla/source/modules/dgl/ -cp -r -v /home/falktx/FOSS/GIT-mine/DISTRHO/DPF/dgl/src /home/falktx/FOSS/GIT-mine/falkTX/Carla/source/modules/dgl/ -cp -r -v /home/falktx/FOSS/GIT-mine/DISTRHO/DPF/distrho/* /home/falktx/FOSS/GIT-mine/falkTX/Carla/source/modules/distrho/ || true +cp -v /Shared/Personal/FOSS/GIT/DISTRHO/DPF/dgl/*.hpp /Shared/Personal/FOSS/GIT/falkTX/Carla/source/modules/dgl/ +cp -r -v /Shared/Personal/FOSS/GIT/DISTRHO/DPF/dgl/src/*.cpp /Shared/Personal/FOSS/GIT/falkTX/Carla/source/modules/dgl/src/ +cp -r -v /Shared/Personal/FOSS/GIT/DISTRHO/DPF/dgl/src/*.hpp /Shared/Personal/FOSS/GIT/falkTX/Carla/source/modules/dgl/src/ +cp -r -v /Shared/Personal/FOSS/GIT/DISTRHO/DPF/distrho/*.cpp /Shared/Personal/FOSS/GIT/falkTX/Carla/source/modules/distrho/ +cp -r -v /Shared/Personal/FOSS/GIT/DISTRHO/DPF/distrho/*.hpp /Shared/Personal/FOSS/GIT/falkTX/Carla/source/modules/distrho/ +cp -r -v /Shared/Personal/FOSS/GIT/DISTRHO/DPF/distrho/*.h /Shared/Personal/FOSS/GIT/falkTX/Carla/source/modules/distrho/ -rm -r /home/falktx/FOSS/GIT-mine/falkTX/Carla/source/modules/dgl/src/resources -rm -r /home/falktx/FOSS/GIT-mine/falkTX/Carla/source/modules/dgl/src/sofd -rm /home/falktx/FOSS/GIT-mine/falkTX/Carla/source/modules/dgl/src/Resources.* +cp -r -v /Shared/Personal/FOSS/GIT/DISTRHO/DPF/dgl/src/pugl-upstream/include /Shared/Personal/FOSS/GIT/falkTX/Carla/source/modules/dgl/src/pugl-upstream/ +cp -r -v /Shared/Personal/FOSS/GIT/DISTRHO/DPF/dgl/src/pugl-upstream/src /Shared/Personal/FOSS/GIT/falkTX/Carla/source/modules/dgl/src/pugl-upstream/ +cp -r -v /Shared/Personal/FOSS/GIT/DISTRHO/DPF/dgl/src/pugl-upstream/AUTHORS /Shared/Personal/FOSS/GIT/falkTX/Carla/source/modules/dgl/src/pugl-upstream/ +cp -r -v /Shared/Personal/FOSS/GIT/DISTRHO/DPF/dgl/src/pugl-upstream/COPYING /Shared/Personal/FOSS/GIT/falkTX/Carla/source/modules/dgl/src/pugl-upstream/ -rm /home/falktx/FOSS/GIT-mine/falkTX/Carla/source/modules/distrho/src/DistrhoPlugin{Jack,LADSPA+DSSI,LV2,LV2export,VST}.cpp -rm /home/falktx/FOSS/GIT-mine/falkTX/Carla/source/modules/distrho/src/DistrhoUI{DSSI,LV2}.cpp +rm /Shared/Personal/FOSS/GIT/falkTX/Carla/source/modules/dgl/Cairo.hpp +rm /Shared/Personal/FOSS/GIT/falkTX/Carla/source/modules/dgl/Vulkan.hpp +rm /Shared/Personal/FOSS/GIT/falkTX/Carla/source/modules/dgl/src/Cairo.cpp +rm /Shared/Personal/FOSS/GIT/falkTX/Carla/source/modules/dgl/src/Vulkan.cpp +rm /Shared/Personal/FOSS/GIT/falkTX/Carla/source/modules/dgl/src/Resources.{cpp,hpp} -# cp -v /home/falktx/FOSS/GIT-mine/DISTRHO/DISTRHO_mini-series/plugins/3BandEQ/{*.cpp,*.hpp,*.h} /home/falktx/FOSS/GIT-mine/falkTX/Carla/source/modules/native-plugins/3bandeq/ -# cp -v /home/falktx/FOSS/GIT-mine/DISTRHO/DISTRHO_mini-series/plugins/3BandSplitter/{*.cpp,*.hpp,*.h} /home/falktx/FOSS/GIT-mine/falkTX/Carla/source/modules/native-plugins/3bandsplitter/ -# cp -v /home/falktx/FOSS/GIT-mine/DISTRHO/DISTRHO_mini-series/plugins/PingPongPan/{*.cpp,*.hpp,*.h} /home/falktx/FOSS/GIT-mine/falkTX/Carla/source/modules/native-plugins/pingpongpan/ +# rm /Shared/Personal/FOSS/GIT/falkTX/Carla/source/modules/distrho/*.cpp +rm /Shared/Personal/FOSS/GIT/falkTX/Carla/source/modules/distrho/*.mm +rm -r /Shared/Personal/FOSS/GIT/falkTX/Carla/source/modules/distrho/src/jackbridge +rm -r /Shared/Personal/FOSS/GIT/falkTX/Carla/source/modules/distrho/src/travesty +rm -r /Shared/Personal/FOSS/GIT/falkTX/Carla/source/modules/distrho/src/vst +rm /Shared/Personal/FOSS/GIT/falkTX/Carla/source/modules/distrho/DistrhoInfo.hpp +rm /Shared/Personal/FOSS/GIT/falkTX/Carla/source/modules/distrho/src/DistrhoPlugin{JACK,LADSPA+DSSI,LV2,LV2export,VST2,VST3}.cpp +rm /Shared/Personal/FOSS/GIT/falkTX/Carla/source/modules/distrho/src/DistrhoPluginVST3.hpp +rm /Shared/Personal/FOSS/GIT/falkTX/Carla/source/modules/distrho/src/DistrhoUI{DSSI,LV2,VST3}.cpp +rm -r /Shared/Personal/FOSS/GIT/falkTX/Carla/source/modules/distrho/extra/sofd +rm /Shared/Personal/FOSS/GIT/falkTX/Carla/source/modules/distrho/extra/FileBrowserDialog.{cpp,hpp} +rm /Shared/Personal/FOSS/GIT/falkTX/Carla/source/modules/distrho/extra/LibraryUtils.hpp +rm /Shared/Personal/FOSS/GIT/falkTX/Carla/source/modules/distrho/extra/RingBuffer.hpp +rm /Shared/Personal/FOSS/GIT/falkTX/Carla/source/modules/distrho/extra/ScopedSafeLocale.hpp + +# cp -v /Shared/Personal/FOSS/GIT/DISTRHO/DISTRHO_mini-series/plugins/3BandEQ/{*.cpp,*.hpp,*.h} /Shared/Personal/FOSS/GIT/falkTX/Carla/source/modules/native-plugins/3bandeq/ +# cp -v /Shared/Personal/FOSS/GIT/DISTRHO/DISTRHO_mini-series/plugins/3BandSplitter/{*.cpp,*.hpp,*.h} /Shared/Personal/FOSS/GIT/falkTX/Carla/source/modules/native-plugins/3bandsplitter/ +# cp -v /Shared/Personal/FOSS/GIT/DISTRHO/DISTRHO_mini-series/plugins/PingPongPan/{*.cpp,*.hpp,*.h} /Shared/Personal/FOSS/GIT/falkTX/Carla/source/modules/native-plugins/pingpongpan/ # -# cp -v /home/falktx/FOSS/GIT-mine/DISTRHO/DISTRHO_nekobi/plugins/Nekobi/{*.cpp,*.hpp,*.h} /home/falktx/FOSS/GIT-mine/falkTX/Carla/source/modules/native-plugins/nekobi/ -# cp -v /home/falktx/FOSS/GIT-mine/DISTRHO/DISTRHO_nekobi/plugins/Nekobi/nekobee-src/{*.c,*.h} /home/falktx/FOSS/GIT-mine/falkTX/Carla/source/modules/native-plugins/nekobi/nekobee-src/ +# cp -v /Shared/Personal/FOSS/GIT/DISTRHO/DISTRHO_nekobi/plugins/Nekobi/{*.cpp,*.hpp,*.h} /Shared/Personal/FOSS/GIT/falkTX/Carla/source/modules/native-plugins/nekobi/ +# cp -v /Shared/Personal/FOSS/GIT/DISTRHO/DISTRHO_nekobi/plugins/Nekobi/nekobee-src/{*.c,*.h} /Shared/Personal/FOSS/GIT/falkTX/Carla/source/modules/native-plugins/nekobi/nekobee-src/ # -# cp -v /home/falktx/FOSS/GIT-mine/DISTRHO/DISTRHO_prom/plugins/ProM/{*.cpp,*.hpp,*.h} /home/falktx/FOSS/GIT-mine/falkTX/Carla/source/modules/native-plugins/prom/ +# cp -v /Shared/Personal/FOSS/GIT/DISTRHO/DISTRHO_prom/plugins/ProM/{*.cpp,*.hpp,*.h} /Shared/Personal/FOSS/GIT/falkTX/Carla/source/modules/native-plugins/prom/ # -# cp -v /home/falktx/FOSS/GIT-mine/DISTRHO/JuicePlugins/plugins/GrooveJuice/{*.cpp,*.hpp,*.hxx,*.h} /home/falktx/FOSS/GIT-mine/falkTX/Carla/source/modules/native-plugins/groovejuice/ -# cp -v /home/falktx/FOSS/GIT-mine/DISTRHO/JuicePlugins/plugins/PowerJuice/{*.cpp,*.hpp,*.h} /home/falktx/FOSS/GIT-mine/falkTX/Carla/source/modules/native-plugins/powerjuice/ -# # cp -v /home/falktx/FOSS/GIT-mine/DISTRHO/JuicePlugins/plugins/PowerJuiceX2/{*.cpp,*.hpp,*.hxx,*.h} /home/falktx/FOSS/GIT-mine/falkTX/Carla/source/modules/native-plugins/powerjuicex2/ -# cp -v /home/falktx/FOSS/GIT-mine/DISTRHO/JuicePlugins/plugins/SegmentJuice/{*.cpp,*.hpp,*.hxx,*.h} /home/falktx/FOSS/GIT-mine/falkTX/Carla/source/modules/native-plugins/segmentjuice/ -# # cp -v /home/falktx/FOSS/GIT-mine/DISTRHO/JuicePlugins/plugins/StutterJuice/{*.cpp,*.hpp,*.hxx,*.h} /home/falktx/FOSS/GIT-mine/falkTX/Carla/source/modules/native-plugins/stutterjuice/ -# # cp -v /home/falktx/FOSS/GIT-mine/DISTRHO/JuicePlugins/plugins/TriggerJuice/{*.cpp,*.hpp,*.hxx,*.h} /home/falktx/FOSS/GIT-mine/falkTX/Carla/source/modules/native-plugins/triggerjuice/ -# cp -v /home/falktx/FOSS/GIT-mine/DISTRHO/JuicePlugins/plugins/VectorJuice/{*.cpp,*.hpp,*.h} /home/falktx/FOSS/GIT-mine/falkTX/Carla/source/modules/native-plugins/vectorjuice/ -# cp -v /home/falktx/FOSS/GIT-mine/DISTRHO/JuicePlugins/plugins/WobbleJuice/{*.cpp,*.hpp,*.hxx,*.h} /home/falktx/FOSS/GIT-mine/falkTX/Carla/source/modules/native-plugins/wobblejuice/ +# cp -v /Shared/Personal/FOSS/GIT/DISTRHO/JuicePlugins/plugins/GrooveJuice/{*.cpp,*.hpp,*.hxx,*.h} /Shared/Personal/FOSS/GIT/falkTX/Carla/source/modules/native-plugins/groovejuice/ +# cp -v /Shared/Personal/FOSS/GIT/DISTRHO/JuicePlugins/plugins/PowerJuice/{*.cpp,*.hpp,*.h} /Shared/Personal/FOSS/GIT/falkTX/Carla/source/modules/native-plugins/powerjuice/ +# # cp -v /Shared/Personal/FOSS/GIT/DISTRHO/JuicePlugins/plugins/PowerJuiceX2/{*.cpp,*.hpp,*.hxx,*.h} /Shared/Personal/FOSS/GIT/falkTX/Carla/source/modules/native-plugins/powerjuicex2/ +# cp -v /Shared/Personal/FOSS/GIT/DISTRHO/JuicePlugins/plugins/SegmentJuice/{*.cpp,*.hpp,*.hxx,*.h} /Shared/Personal/FOSS/GIT/falkTX/Carla/source/modules/native-plugins/segmentjuice/ +# # cp -v /Shared/Personal/FOSS/GIT/DISTRHO/JuicePlugins/plugins/StutterJuice/{*.cpp,*.hpp,*.hxx,*.h} /Shared/Personal/FOSS/GIT/falkTX/Carla/source/modules/native-plugins/stutterjuice/ +# # cp -v /Shared/Personal/FOSS/GIT/DISTRHO/JuicePlugins/plugins/TriggerJuice/{*.cpp,*.hpp,*.hxx,*.h} /Shared/Personal/FOSS/GIT/falkTX/Carla/source/modules/native-plugins/triggerjuice/ +# cp -v /Shared/Personal/FOSS/GIT/DISTRHO/JuicePlugins/plugins/VectorJuice/{*.cpp,*.hpp,*.h} /Shared/Personal/FOSS/GIT/falkTX/Carla/source/modules/native-plugins/vectorjuice/ +# cp -v /Shared/Personal/FOSS/GIT/DISTRHO/JuicePlugins/plugins/WobbleJuice/{*.cpp,*.hpp,*.hxx,*.h} /Shared/Personal/FOSS/GIT/falkTX/Carla/source/modules/native-plugins/wobblejuice/ # -# cp -v /home/falktx/FOSS/GIT-mine/zam-plugins-DPF/plugins/ZamComp/{*.cpp,*.hpp,*.h} /home/falktx/FOSS/GIT-mine/falkTX/Carla/source/modules/native-plugins/zamcomp/ -# cp -v /home/falktx/FOSS/GIT-mine/zam-plugins-DPF/plugins/ZamCompX2/{*.cpp,*.hpp,*.h} /home/falktx/FOSS/GIT-mine/falkTX/Carla/source/modules/native-plugins/zamcompx2/ -# cp -v /home/falktx/FOSS/GIT-mine/zam-plugins-DPF/plugins/ZamEQ2/{*.cpp,*.hpp,*.h} /home/falktx/FOSS/GIT-mine/falkTX/Carla/source/modules/native-plugins/zameq2/ -# cp -v /home/falktx/FOSS/GIT-mine/zam-plugins-DPF/plugins/ZamSynth/{*.cpp,*.hpp,*.h} /home/falktx/FOSS/GIT-mine/falkTX/Carla/source/modules/native-plugins/zamsynth/ -# cp -v /home/falktx/FOSS/GIT-mine/zam-plugins-DPF/plugins/ZamTube/{*.cpp,*.hpp,*.h} /home/falktx/FOSS/GIT-mine/falkTX/Carla/source/modules/native-plugins/zamtube/ -# cp -v /home/falktx/FOSS/GIT-mine/zam-plugins-DPF/plugins/ZaMultiComp/{*.cpp,*.hpp,*.h} /home/falktx/FOSS/GIT-mine/falkTX/Carla/source/modules/native-plugins/zamulticomp/ -# cp -v /home/falktx/FOSS/GIT-mine/zam-plugins-DPF/plugins/ZaMultiCompX2/{*.cpp,*.hpp,*.h} /home/falktx/FOSS/GIT-mine/falkTX/Carla/source/modules/native-plugins/zamulticompx2/ +# cp -v /Shared/Personal/FOSS/GIT/zam-plugins-DPF/plugins/ZamComp/{*.cpp,*.hpp,*.h} /Shared/Personal/FOSS/GIT/falkTX/Carla/source/modules/native-plugins/zamcomp/ +# cp -v /Shared/Personal/FOSS/GIT/zam-plugins-DPF/plugins/ZamCompX2/{*.cpp,*.hpp,*.h} /Shared/Personal/FOSS/GIT/falkTX/Carla/source/modules/native-plugins/zamcompx2/ +# cp -v /Shared/Personal/FOSS/GIT/zam-plugins-DPF/plugins/ZamEQ2/{*.cpp,*.hpp,*.h} /Shared/Personal/FOSS/GIT/falkTX/Carla/source/modules/native-plugins/zameq2/ +# cp -v /Shared/Personal/FOSS/GIT/zam-plugins-DPF/plugins/ZamSynth/{*.cpp,*.hpp,*.h} /Shared/Personal/FOSS/GIT/falkTX/Carla/source/modules/native-plugins/zamsynth/ +# cp -v /Shared/Personal/FOSS/GIT/zam-plugins-DPF/plugins/ZamTube/{*.cpp,*.hpp,*.h} /Shared/Personal/FOSS/GIT/falkTX/Carla/source/modules/native-plugins/zamtube/ +# cp -v /Shared/Personal/FOSS/GIT/zam-plugins-DPF/plugins/ZaMultiComp/{*.cpp,*.hpp,*.h} /Shared/Personal/FOSS/GIT/falkTX/Carla/source/modules/native-plugins/zamulticomp/ +# cp -v /Shared/Personal/FOSS/GIT/zam-plugins-DPF/plugins/ZaMultiCompX2/{*.cpp,*.hpp,*.h} /Shared/Personal/FOSS/GIT/falkTX/Carla/source/modules/native-plugins/zamulticompx2/ diff --git a/source/Makefile.mk b/source/Makefile.mk index 6824ddc97..4c73b9211 100644 --- a/source/Makefile.mk +++ b/source/Makefile.mk @@ -178,7 +178,11 @@ endif ifeq ($(HAVE_DGL),true) BASE_FLAGS += -DHAVE_DGL -BASE_FLAGS += -DDGL_NAMESPACE=CarlaDGL -DDGL_FILE_BROWSER_DISABLED -DDGL_NO_SHARED_RESOURCES +BASE_FLAGS += -DDGL_NAMESPACE=CarlaDGL +BASE_FLAGS += -DDGL_OPENGL +BASE_FLAGS += -DDGL_FILE_BROWSER_DISABLED +BASE_FLAGS += -DDGL_NO_SHARED_RESOURCES +BASE_FLAGS += -DDONT_SET_USING_DGL_NAMESPACE endif ifeq ($(HAVE_FLUIDSYNTH),true) @@ -218,6 +222,10 @@ endif ifeq ($(USING_JUCE),true) BASE_FLAGS += -DUSING_JUCE +BASE_FLAGS += -DJUCE_APP_CONFIG_HEADER='"AppConfig.h"' +ifeq ($(WIN32),true) +BASE_FLAGS += -D_WIN32_WINNT=0x0600 +endif endif ifeq ($(USING_JUCE_AUDIO_DEVICES),true) @@ -232,16 +240,6 @@ ifeq ($(STATIC_PLUGIN_TARGET),true) BASE_FLAGS += -DSTATIC_PLUGIN_TARGET endif -# --------------------------------------------------------------------------------------------------------------------- -# Custom build flags for JUCE - -ifeq ($(USING_JUCE),true) -BUILD_CXX_FLAGS += -DJUCE_APP_CONFIG_HEADER='"AppConfig.h"' -ifeq ($(WIN32),true) -BASE_FLAGS += -D_WIN32_WINNT=0x0600 -endif -endif - # --------------------------------------------------------------------------------------------------------------------- # Allow custom namespace diff --git a/source/modules/dgl/Application.hpp b/source/modules/dgl/Application.hpp index 1b868c792..28404e394 100644 --- a/source/modules/dgl/Application.hpp +++ b/source/modules/dgl/Application.hpp @@ -1,6 +1,6 @@ /* * DISTRHO Plugin Framework (DPF) - * Copyright (C) 2012-2016 Filipe Coelho + * Copyright (C) 2012-2021 Filipe Coelho * * Permission to use, copy, modify, and/or distribute this software for any purpose with * or without fee is hereby granted, provided that the above copyright notice and this @@ -21,30 +21,26 @@ START_NAMESPACE_DGL -// ----------------------------------------------------------------------- -// Forward class names - -class Window; - -// ----------------------------------------------------------------------- +// -------------------------------------------------------------------------------------------------------------------- /** Base DGL Application class. One application instance is required for creating a window. - There's no single/global application instance in DGL, and multiple - windows can share the same app instance. + There's no single/global application instance in DGL, and multiple windows can share the same app instance. + + In standalone mode an application will automatically quit its event-loop when all its windows are closed. - In standalone mode an application will automatically quit its - event-loop when all its windows are closed. + Unless stated otherwise, functions within this class are not thread-safe. */ -class Application +class DISTRHO_API Application { public: /** Constructor. */ - Application(); + // NOTE: the default value is not yet passed, so we catch where we use this + Application(bool isStandalone = true); /** Destructor. @@ -62,29 +58,71 @@ public: idle() is called at regular intervals. @note This function is meant for standalones only, *never* call this from plugins. */ - void exec(unsigned int idleTime = 10); + void exec(uint idleTimeInMs = 30); /** Quit the application. This stops the event-loop and closes all Windows. + This function is thread-safe. */ void quit(); /** Check if the application is about to quit. Returning true means there's no event-loop running at the moment (or it's just about to stop). + This function is thread-safe. + */ + bool isQuitting() const noexcept; + + /** + Check if the application is standalone, otherwise running as a module or plugin. + This function is thread-safe. + */ + bool isStandalone() const noexcept; + + /** + Return the time in seconds. + + This is a monotonically increasing clock with high resolution.@n + The returned time is only useful to compare against other times returned by this function, + its absolute value has no meaning. + */ + double getTime() const; + + /** + Add a callback function to be triggered on every idle cycle. + You can add more than one, and remove them at anytime with removeIdleCallback(). + Idle callbacks trigger right after OS event handling and Window idle events (within the same cycle). + There are no guarantees in terms of timing, use Window::addIdleCallback for time-relative callbacks. + */ + void addIdleCallback(IdleCallback* callback); + + /** + Remove an idle callback previously added via addIdleCallback(). + */ + void removeIdleCallback(IdleCallback* callback); + + /** + Set the class name of the application. + + This is a stable identifier for the application, used as the window class/instance name on X11 and Windows. + It is not displayed to the user, but can be used in scripts and by window managers, + so it should be the same for every instance of the application, but different from other applications. + + Plugins created with DPF have their class name automatically set based on DGL_NAMESPACE and plugin name. */ - bool isQuiting() const noexcept; + void setClassName(const char* name); private: struct PrivateData; PrivateData* const pData; + friend class PluginApplication; friend class Window; DISTRHO_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(Application) }; -// ----------------------------------------------------------------------- +// -------------------------------------------------------------------------------------------------------------------- END_NAMESPACE_DGL diff --git a/source/modules/dgl/Base.hpp b/source/modules/dgl/Base.hpp index f100c2be6..bb0538c9c 100644 --- a/source/modules/dgl/Base.hpp +++ b/source/modules/dgl/Base.hpp @@ -1,6 +1,6 @@ /* * DISTRHO Plugin Framework (DPF) - * Copyright (C) 2012-2016 Filipe Coelho + * Copyright (C) 2012-2021 Filipe Coelho * * Permission to use, copy, modify, and/or distribute this software for any purpose with * or without fee is hereby granted, provided that the above copyright notice and this @@ -20,7 +20,7 @@ #include "../distrho/extra/LeakDetector.hpp" #include "../distrho/extra/ScopedPointer.hpp" -// ----------------------------------------------------------------------- +// -------------------------------------------------------------------------------------------------------------------- // Define namespace #ifndef DGL_NAMESPACE @@ -31,124 +31,48 @@ #define END_NAMESPACE_DGL } #define USE_NAMESPACE_DGL using namespace DGL_NAMESPACE; -#ifdef DISTRHO_OS_WINDOWS -// ----------------------------------------------------------------------- -// Fix OpenGL includes for Windows, based on glfw code - -#ifndef APIENTRY -# define APIENTRY __stdcall -# define DGL_APIENTRY_DEFINED -#endif // APIENTRY - -/* We need WINGDIAPI defined */ -#ifndef WINGDIAPI -# if defined(_MSC_VER) || defined(__BORLANDC__) || defined(__POCC__) -# define WINGDIAPI __declspec(dllimport) -# elif defined(__LCC__) -# define WINGDIAPI __stdcall -# else -# define WINGDIAPI extern -# endif -# define DGL_WINGDIAPI_DEFINED -#endif // WINGDIAPI - -/* Some files also need CALLBACK defined */ -#ifndef CALLBACK -# if defined(_MSC_VER) -# if (defined(_M_MRX000) || defined(_M_IX86) || defined(_M_ALPHA) || defined(_M_PPC)) && !defined(MIDL_PASS) -# define CALLBACK __stdcall -# else -# define CALLBACK -# endif -# else -# define CALLBACK __stdcall -# endif -# define DGL_CALLBACK_DEFINED -#endif // CALLBACK - -/* Most GL/glu.h variants on Windows need wchar_t */ -#include - -#endif // DISTRHO_OS_WINDOWS - -// ----------------------------------------------------------------------- -// OpenGL includes - -#ifdef DISTRHO_OS_MAC -# include -#else -# ifndef DISTRHO_OS_WINDOWS -# define GL_GLEXT_PROTOTYPES -# endif -# include -# include -#endif - -// ----------------------------------------------------------------------- -// Missing OpenGL defines - -#if defined(GL_BGR_EXT) && ! defined(GL_BGR) -# define GL_BGR GL_BGR_EXT -#endif - -#if defined(GL_BGRA_EXT) && ! defined(GL_BGRA) -# define GL_BGRA GL_BGRA_EXT -#endif - -#ifndef GL_CLAMP_TO_BORDER -# define GL_CLAMP_TO_BORDER 0x812D -#endif - -#ifdef DISTRHO_OS_WINDOWS -// ----------------------------------------------------------------------- -// Fix OpenGL includes for Windows, based on glfw code - -#ifdef DGL_APIENTRY_DEFINED -# undef APIENTRY -# undef DGL_APIENTRY_DEFINED -#endif - -#ifdef DGL_WINGDIAPI_DEFINED -# undef WINGDIAPI -# undef DGL_WINGDIAPI_DEFINED -#endif - -#ifdef DGL_CALLBACK_DEFINED -# undef CALLBACK -# undef DGL_CALLBACK_DEFINED -#endif - -#endif // DISTRHO_OS_WINDOWS - START_NAMESPACE_DGL -// ----------------------------------------------------------------------- +// -------------------------------------------------------------------------------------------------------------------- // Base DGL enums -/** - Convenience symbols for ASCII control characters. - */ -enum Char { - kCharBackspace = 0x08, - kCharEscape = 0x1B, - kCharDelete = 0x7F -}; - /** Keyboard modifier flags. */ enum Modifier { - kModifierShift = 1 << 0, /**< Shift key */ - kModifierControl = 1 << 1, /**< Control key */ - kModifierAlt = 1 << 2, /**< Alt/Option key */ - kModifierSuper = 1 << 3 /**< Mod4/Command/Windows key */ + kModifierShift = 1u << 0u, ///< Shift key + kModifierControl = 1u << 1u, ///< Control key + kModifierAlt = 1u << 2u, ///< Alt/Option key + kModifierSuper = 1u << 3u ///< Mod4/Command/Windows key }; /** - Special (non-Unicode) keyboard keys. + Keyboard key codepoints. + + All keys are identified by a Unicode code point in Widget::KeyboardEvent::key. + This enumeration defines constants for special keys that do not have a standard + code point, and some convenience constants for control characters. + Note that all keys are handled in the same way, this enumeration is just for + convenience when writing hard-coded key bindings. + + Keys that do not have a standard code point use values in the Private Use + Area in the Basic Multilingual Plane (`U+E000` to `U+F8FF`). + Applications must take care to not interpret these values beyond key detection, + the mapping used here is arbitrary and specific to DPF. */ enum Key { - kKeyF1 = 1, + // Convenience symbols for ASCII control characters + kKeyBackspace = 0x08, + kKeyEscape = 0x1B, + kKeyDelete = 0x7F, + + // Backwards compatibility with old DPF + kCharBackspace DISTRHO_DEPRECATED_BY("kKeyBackspace") = kKeyBackspace, + kCharEscape DISTRHO_DEPRECATED_BY("kKeyEscape") = kKeyEscape, + kCharDelete DISTRHO_DEPRECATED_BY("kKeyDelete") = kKeyDelete, + + // Unicode Private Use Area + kKeyF1 = 0xE000, kKeyF2, kKeyF3, kKeyF4, @@ -170,25 +94,92 @@ enum Key { kKeyEnd, kKeyInsert, kKeyShift, + kKeyShiftL = kKeyShift, + kKeyShiftR, kKeyControl, + kKeyControlL = kKeyControl, + kKeyControlR, kKeyAlt, - kKeySuper + kKeyAltL = kKeyAlt, + kKeyAltR, + kKeySuper, + kKeySuperL = kKeySuper, + kKeySuperR, + kKeyMenu, + kKeyCapsLock, + kKeyScrollLock, + kKeyNumLock, + kKeyPrintScreen, + kKeyPause +}; + +/** + Common flags for all events. + */ +enum EventFlag { + kFlagSendEvent = 1, ///< Event is synthetic + kFlagIsHint = 2 ///< Event is a hint (not direct user input) +}; + +/** + Reason for a crossing event. + */ +enum CrossingMode { + kCrossingNormal, ///< Crossing due to pointer motion + kCrossingGrab, ///< Crossing due to a grab + kCrossingUngrab ///< Crossing due to a grab release }; -// ----------------------------------------------------------------------- +/** + A mouse cursor type. + + This is a portable subset of mouse cursors that exist on X11, MacOS, and Windows. +*/ +enum MouseCursor { + kMouseCursorArrow, ///< Default pointing arrow + kMouseCursorCaret, ///< Caret (I-Beam) for text entry + kMouseCursorCrosshair, ///< Cross-hair + kMouseCursorHand, ///< Hand with a pointing finger + kMouseCursorNotAllowed, ///< Operation not allowed + kMouseCursorLeftRight, ///< Left/right arrow for horizontal resize + kMouseCursorUpDown, ///< Up/down arrow for vertical resize + kMouseCursorDiagonal, ///< Top-left to bottom-right arrow for diagonal resize + kMouseCursorAntiDiagonal ///< Bottom-left to top-right arrow for diagonal resize +}; + +/** + Scroll direction. + + Describes the direction of a scroll event along with whether the scroll is a "smooth" scroll. + The discrete directions are for devices like mouse wheels with constrained axes, + while a smooth scroll is for those with arbitrary scroll direction freedom, like some touchpads. +*/ +enum ScrollDirection { + kScrollUp, ///< Scroll up + kScrollDown, ///< Scroll down + kScrollLeft, ///< Scroll left + kScrollRight, ///< Scroll right + kScrollSmooth ///< Smooth scroll in any direction +}; + +// -------------------------------------------------------------------------------------------------------------------- // Base DGL classes +/** + Graphics context, definition depends on build type. + */ +struct GraphicsContext {}; + /** Idle callback. */ -class IdleCallback +struct IdleCallback { -public: virtual ~IdleCallback() {} virtual void idleCallback() = 0; }; -// ----------------------------------------------------------------------- +// -------------------------------------------------------------------------------------------------------------------- END_NAMESPACE_DGL @@ -198,6 +189,6 @@ END_NAMESPACE_DGL using namespace DGL_NAMESPACE; #endif -// ----------------------------------------------------------------------- +// -------------------------------------------------------------------------------------------------------------------- #endif // DGL_BASE_HPP_INCLUDED diff --git a/source/modules/dgl/Color.hpp b/source/modules/dgl/Color.hpp index 2fc8c64c2..fbce9cc2c 100644 --- a/source/modules/dgl/Color.hpp +++ b/source/modules/dgl/Color.hpp @@ -1,6 +1,6 @@ /* * DISTRHO Plugin Framework (DPF) - * Copyright (C) 2012-2016 Filipe Coelho + * Copyright (C) 2012-2021 Filipe Coelho * * Permission to use, copy, modify, and/or distribute this software for any purpose with * or without fee is hereby granted, provided that the above copyright notice and this @@ -23,7 +23,7 @@ struct NVGcolor; START_NAMESPACE_DGL -// ----------------------------------------------------------------------- +// -------------------------------------------------------------------------------------------------------------------- /** A color made from red, green, blue and alpha floating-point values in [0..1] range. @@ -44,13 +44,13 @@ struct Color { /** Create a color from red, green, blue and alpha numeric values. - Values must be in [0..255] range. + All values except alpha must be in [0..255] range, with alpha in [0..1] range. */ - Color(int red, int green, int blue, int alpha = 255) noexcept; + Color(int red, int green, int blue, float alpha = 1.0f) noexcept; /** Create a color from red, green, blue and alpha floating-point values. - Values must in [0..1] range. + All values must in [0..1] range. */ Color(float red, float green, float blue, float alpha = 1.0f) noexcept; @@ -65,6 +65,11 @@ struct Color { */ Color(const Color& color1, const Color& color2, float u) noexcept; + /** + Create a new color based on this one but with a different alpha value. + */ + Color withAlpha(float alpha) noexcept; + /** Create a color specified by hue, saturation and lightness. Values must in [0..1] range. @@ -74,7 +79,7 @@ struct Color { /** Create a color from a HTML string like "#333" or "#112233". */ - static Color fromHTML(const char* rgb, float alpha = 1.0f); + static Color fromHTML(const char* rgb, float alpha = 1.0f) noexcept; /** Linearly interpolate this color against another. @@ -83,7 +88,7 @@ struct Color { /** Check if this color matches another. - @note Comparison is forced within 8-bit color values. + @note Comparison is done within 8-bit color space. */ bool isEqual(const Color& color, bool withAlpha = true) noexcept; bool isNotEqual(const Color& color, bool withAlpha = true) noexcept; @@ -95,6 +100,11 @@ struct Color { */ void fixBounds() noexcept; + /** + Set this color for use in the next drawing operation for the provided context. + */ + void setFor(const GraphicsContext& context, bool includeAlpha = false); + /** @internal Needed for NanoVG compatibility. @@ -103,7 +113,7 @@ struct Color { operator NVGcolor() const noexcept; }; -// ----------------------------------------------------------------------- +// -------------------------------------------------------------------------------------------------------------------- END_NAMESPACE_DGL diff --git a/source/modules/dgl/EventHandlers.hpp b/source/modules/dgl/EventHandlers.hpp new file mode 100644 index 000000000..a460440ed --- /dev/null +++ b/source/modules/dgl/EventHandlers.hpp @@ -0,0 +1,174 @@ +/* + * DISTRHO Plugin Framework (DPF) + * Copyright (C) 2012-2021 Filipe Coelho + * + * Permission to use, copy, modify, and/or distribute this software for any purpose with + * or without fee is hereby granted, provided that the above copyright notice and this + * permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD + * TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN + * NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER + * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef DGL_EVENT_HANDLERS_HPP_INCLUDED +#define DGL_EVENT_HANDLERS_HPP_INCLUDED + +#include "Widget.hpp" + +START_NAMESPACE_DGL + +/* NOTE none of these classes get assigned to a widget automatically + Manual plugging into Widget events is needed, like so: + + ``` + bool onMouse(const MouseEvent& ev) override + { + return ButtonEventHandler::mouseEvent(ev); + } + ``` +*/ + +// -------------------------------------------------------------------------------------------------------------------- + +class ButtonEventHandler +{ +public: + enum State { + kButtonStateDefault = 0x0, + kButtonStateHover = 0x1, + kButtonStateActive = 0x2, + kButtonStateActiveHover = kButtonStateActive|kButtonStateHover + }; + + class Callback + { + public: + virtual ~Callback() {} + virtual void buttonClicked(SubWidget* widget, int button) = 0; + }; + + explicit ButtonEventHandler(SubWidget* self); + virtual ~ButtonEventHandler(); + + bool isActive() noexcept; + void setActive(bool active, bool sendCallback) noexcept; + + bool isChecked() const noexcept; + void setChecked(bool checked, bool sendCallback) noexcept; + + bool isCheckable() const noexcept; + void setCheckable(bool checkable) noexcept; + + Point getLastClickPosition() const noexcept; + Point getLastMotionPosition() const noexcept; + + void setCallback(Callback* callback) noexcept; + + bool mouseEvent(const Widget::MouseEvent& ev); + bool motionEvent(const Widget::MotionEvent& ev); + +protected: + State getState() const noexcept; + void clearState() noexcept; + + virtual void stateChanged(State state, State oldState); + + void setInternalCallback(Callback* callback) noexcept; + void triggerUserCallback(SubWidget* widget, int button); + +private: + struct PrivateData; + PrivateData* const pData; + + DISTRHO_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(ButtonEventHandler) +}; + +// -------------------------------------------------------------------------------------------------------------------- + +class KnobEventHandler +{ +public: + enum Orientation { + Horizontal, + Vertical + }; + + // NOTE hover not implemented yet + enum State { + kKnobStateDefault = 0x0, + kKnobStateHover = 0x1, + kKnobStateDragging = 0x2, + kKnobStateDraggingHover = kKnobStateDragging|kKnobStateHover + }; + + class Callback + { + public: + virtual ~Callback() {} + virtual void knobDragStarted(SubWidget* widget) = 0; + virtual void knobDragFinished(SubWidget* widget) = 0; + virtual void knobValueChanged(SubWidget* widget, float value) = 0; + }; + + explicit KnobEventHandler(SubWidget* self); + explicit KnobEventHandler(SubWidget* self, const KnobEventHandler& other); + KnobEventHandler& operator=(const KnobEventHandler& other); + virtual ~KnobEventHandler(); + + // returns raw value, is assumed to be scaled if using log + float getValue() const noexcept; + + // NOTE: value is assumed to be scaled if using log + virtual bool setValue(float value, bool sendCallback = false) noexcept; + + // returns 0-1 ranged value, already with log scale as needed + float getNormalizedValue() const noexcept; + + // NOTE: value is assumed to be scaled if using log + void setDefault(float def) noexcept; + + // NOTE: value is assumed to be scaled if using log + void setRange(float min, float max) noexcept; + + void setStep(float step) noexcept; + + void setUsingLogScale(bool yesNo) noexcept; + + Orientation getOrientation() const noexcept; + void setOrientation(const Orientation orientation) noexcept; + + void setCallback(Callback* callback) noexcept; + + bool mouseEvent(const Widget::MouseEvent& ev); + bool motionEvent(const Widget::MotionEvent& ev); + bool scrollEvent(const Widget::ScrollEvent& ev); + +protected: + State getState() const noexcept; + +private: + struct PrivateData; + PrivateData* const pData; + + /* not for use */ +#ifdef DISTRHO_PROPER_CPP11_SUPPORT + KnobEventHandler(KnobEventHandler& other) = delete; + KnobEventHandler(const KnobEventHandler& other) = delete; +#else + KnobEventHandler(KnobEventHandler& other); + KnobEventHandler(const KnobEventHandler& other); +#endif + + DISTRHO_LEAK_DETECTOR(KnobEventHandler) +}; + +// -------------------------------------------------------------------------------------------------------------------- + +END_NAMESPACE_DGL + +#endif // DGL_EVENT_HANDLERS_HPP_INCLUDED + diff --git a/source/modules/dgl/Geometry.hpp b/source/modules/dgl/Geometry.hpp index 3dbf70640..9ac9483be 100644 --- a/source/modules/dgl/Geometry.hpp +++ b/source/modules/dgl/Geometry.hpp @@ -1,6 +1,6 @@ /* * DISTRHO Plugin Framework (DPF) - * Copyright (C) 2012-2016 Filipe Coelho + * Copyright (C) 2012-2021 Filipe Coelho * * Permission to use, copy, modify, and/or distribute this software for any purpose with * or without fee is hereby granted, provided that the above copyright notice and this @@ -21,7 +21,7 @@ START_NAMESPACE_DGL -// ----------------------------------------------------------------------- +// -------------------------------------------------------------------------------------------------------------------- // Forward class names template class Line; @@ -29,7 +29,7 @@ template class Circle; template class Triangle; template class Rectangle; -// ----------------------------------------------------------------------- +// -------------------------------------------------------------------------------------------------------------------- /** DGL Point class. @@ -114,14 +114,14 @@ public: bool operator!=(const Point& pos) const noexcept; private: - T fX, fY; + T x, y; template friend class Line; template friend class Circle; template friend class Triangle; template friend class Rectangle; }; -// ----------------------------------------------------------------------- +// -------------------------------------------------------------------------------------------------------------------- /** DGL Size class. @@ -195,7 +195,7 @@ public: /** Return true if size is not null (0x0). - A non-null size is still invalid if its width or height is negative. + A non-null size is still invalid if its width or height are negative. */ bool isNotNull() const noexcept; @@ -210,6 +210,8 @@ public: */ bool isInvalid() const noexcept; + Size toInt() const noexcept; + Size operator+(const Size& size) noexcept; Size operator-(const Size& size) noexcept; Size& operator=(const Size& size) noexcept; @@ -217,6 +219,8 @@ public: Size& operator-=(const Size& size) noexcept; Size& operator*=(double m) noexcept; Size& operator/=(double d) noexcept; + Size operator*(double m) const noexcept; + Size operator/(double m) const noexcept; bool operator==(const Size& size) const noexcept; bool operator!=(const Size& size) const noexcept; @@ -346,11 +350,6 @@ public: */ void moveBy(const Point& pos) noexcept; - /** - Draw this line using the current OpenGL state. - */ - void draw(); - /** Return true if line is null (start and end pos are equal). */ @@ -361,12 +360,28 @@ public: */ bool isNotNull() const noexcept; +#ifndef DPF_TEST_POINT_CPP + /** + Draw this line using the provided graphics context, optionally specifying line width. + */ + void draw(const GraphicsContext& context, T width = 1); +#endif + Line& operator=(const Line& line) noexcept; bool operator==(const Line& line) const noexcept; bool operator!=(const Line& line) const noexcept; +#ifndef DPF_TEST_POINT_CPP + /** + Draw this line using the current OpenGL state.@n + DEPRECATED Please use draw(const GraphicsContext&) instead. + */ + DISTRHO_DEPRECATED_BY("draw(const GraphicsContext&)") + void draw(); +#endif + private: - Point fPosStart, fPosEnd; + Point posStart, posEnd; }; // ----------------------------------------------------------------------- @@ -461,19 +476,35 @@ public: void setNumSegments(const uint num); /** - Draw this circle using the current OpenGL state. + Draw this circle using the provided graphics context. */ - void draw(); + void draw(const GraphicsContext& context); /** - Draw lines (outline of this circle) using the current OpenGL state. + Draw lines (outline of this circle) using the provided graphics context, optionally specifying line width. */ - void drawOutline(); + void drawOutline(const GraphicsContext& context, T lineWidth = 1); Circle& operator=(const Circle& cir) noexcept; bool operator==(const Circle& cir) const noexcept; bool operator!=(const Circle& cir) const noexcept; +#ifndef DPF_TEST_POINT_CPP + /** + Draw this circle using the current OpenGL state.@n + DEPRECATED Please use draw(const GraphicsContext&) instead. + */ + DISTRHO_DEPRECATED_BY("draw(const GraphicsContext&)") + void draw(); + + /** + Draw lines (outline of this circle) using the current OpenGL state.@n + DEPRECATED Please use drawOutline(const GraphicsContext&,T) instead. + */ + DISTRHO_DEPRECATED_BY("drawOutline(const GraphicsContext&)") + void drawOutline(); +#endif + private: Point fPos; float fSize; @@ -481,8 +512,6 @@ private: // cached values float fTheta, fCos, fSin; - - void _draw(const bool outline); }; // ----------------------------------------------------------------------- @@ -516,16 +545,6 @@ public: */ Triangle(const Triangle& tri) noexcept; - /** - Draw this triangle using the current OpenGL state. - */ - void draw(); - - /** - Draw lines (outline of this triangle) using the current OpenGL state. - */ - void drawOutline(); - /** Return true if triangle is null (all its points are equal). An null triangle is also invalid. @@ -549,14 +568,38 @@ public: */ bool isInvalid() const noexcept; + /** + Draw this triangle using the provided graphics context. + */ + void draw(const GraphicsContext& context); + + /** + Draw lines (outline of this triangle) using the provided graphics context, optionally specifying line width. + */ + void drawOutline(const GraphicsContext& context, T lineWidth = 1); + Triangle& operator=(const Triangle& tri) noexcept; bool operator==(const Triangle& tri) const noexcept; bool operator!=(const Triangle& tri) const noexcept; -private: - Point fPos1, fPos2, fPos3; +#ifndef DPF_TEST_POINT_CPP + /** + Draw this triangle using the current OpenGL state.@n + DEPRECATED Please use draw(const GraphicsContext&) instead. + */ + DISTRHO_DEPRECATED_BY("draw(const GraphicsContext&)") + void draw(); - void _draw(const bool outline); + /** + Draw lines (outline of this triangle) using the current OpenGL state.@n + DEPRECATED Please use drawOutline(const GraphicsContext&,T) instead. + */ + DISTRHO_DEPRECATED_BY("drawOutline(const GraphicsContext&)") + void drawOutline(); +#endif + +private: + Point pos1, pos2, pos3; }; // ----------------------------------------------------------------------- @@ -710,6 +753,17 @@ public: */ bool contains(const Point& pos) const noexcept; + /** + Check if this rectangle contains the point @a pos affected by a custom scale. + */ + bool containsAfterScaling(const Point& pos, double scaling) const noexcept; + + /** + Check if this rectangle contains the point @a pos of another type. + */ + template + bool contains(const Point& pos) const noexcept; + /** Check if this rectangle contains X. */ @@ -721,14 +775,37 @@ public: bool containsY(const T& y) const noexcept; /** - Draw this rectangle using the current OpenGL state. + Return true if size is null (0x0). + An null size is also invalid. + */ + bool isNull() const noexcept; + + /** + Return true if size is not null (0x0). + A non-null size is still invalid if its width or height are negative. */ - void draw(); + bool isNotNull() const noexcept; /** - Draw lines (outline of this rectangle) using the current OpenGL state. + Return true if size is valid (width and height are higher than zero). */ - void drawOutline(); + bool isValid() const noexcept; + + /** + Return true if size is invalid (width or height are lower or equal to zero). + An invalid size might not be null under some circumstances. + */ + bool isInvalid() const noexcept; + + /** + Draw this rectangle using the provided graphics context. + */ + void draw(const GraphicsContext& context); + + /** + Draw lines (outline of this rectangle) using the provided graphics context, optionally specifying line width. + */ + void drawOutline(const GraphicsContext& context, T lineWidth = 1); Rectangle& operator=(const Rectangle& rect) noexcept; Rectangle& operator*=(double m) noexcept; @@ -736,11 +813,23 @@ public: bool operator==(const Rectangle& size) const noexcept; bool operator!=(const Rectangle& size) const noexcept; -private: - Point fPos; - Size fSize; + /** + Draw this rectangle using the current OpenGL state.@n + DEPRECATED Please use draw(const GraphicsContext&) instead. + */ + DISTRHO_DEPRECATED_BY("draw(const GraphicsContext&)") + void draw(); - void _draw(const bool outline); + /** + Draw lines (outline of this rectangle) using the current OpenGL state.@n + DEPRECATED Please use drawOutline(const GraphicsContext&,T) instead. + */ + DISTRHO_DEPRECATED_BY("drawOutline(const GraphicsContext&)") + void drawOutline(); + +private: + Point pos; + Size size; }; // ----------------------------------------------------------------------- diff --git a/source/modules/dgl/Image.hpp b/source/modules/dgl/Image.hpp index 3df5bd84a..2e5153615 100644 --- a/source/modules/dgl/Image.hpp +++ b/source/modules/dgl/Image.hpp @@ -1,6 +1,6 @@ /* * DISTRHO Plugin Framework (DPF) - * Copyright (C) 2012-2016 Filipe Coelho + * Copyright (C) 2012-2021 Filipe Coelho * * Permission to use, copy, modify, and/or distribute this software for any purpose with * or without fee is hereby granted, provided that the above copyright notice and this @@ -17,131 +17,20 @@ #ifndef DGL_IMAGE_HPP_INCLUDED #define DGL_IMAGE_HPP_INCLUDED -#include "Geometry.hpp" +#ifdef DGL_CAIRO +#include "Cairo.hpp" +#else +#include "OpenGL.hpp" +#endif START_NAMESPACE_DGL -// ----------------------------------------------------------------------- - -/** - Base DGL Image class. - - This is an Image class that handles raw image data in pixels. - You can init the image data on the contructor or later on by calling loadFromMemory(). - - To generate raw data useful for this class see the utils/png2rgba.py script. - Be careful when using a PNG without alpha channel, for those the format is 'GL_BGR' - instead of the default 'GL_BGRA'. - - Images are drawn on screen via 2D textures. - */ -class Image -{ -public: - /** - Constructor for a null Image. - */ - Image(); - - /** - Constructor using raw image data. - @note @a rawData must remain valid for the lifetime of this Image. - */ - Image(const char* const rawData, const uint width, const uint height, const GLenum format = GL_BGRA, const GLenum type = GL_UNSIGNED_BYTE); - - /** - Constructor using raw image data. - @note @a rawData must remain valid for the lifetime of this Image. - */ - Image(const char* const rawData, const Size& size, const GLenum format = GL_BGRA, const GLenum type = GL_UNSIGNED_BYTE); - - /** - Constructor using another image data. - */ - Image(const Image& image); - - /** - Destructor. - */ - ~Image(); - - /** - Load image data from memory. - @note @a rawData must remain valid for the lifetime of this Image. - */ - void loadFromMemory(const char* const rawData, const uint width, const uint height, const GLenum format = GL_BGRA, const GLenum type = GL_UNSIGNED_BYTE) noexcept; - - /** - Load image data from memory. - @note @a rawData must remain valid for the lifetime of this Image. - */ - void loadFromMemory(const char* const rawData, const Size& size, const GLenum format = GL_BGRA, const GLenum type = GL_UNSIGNED_BYTE) noexcept; - - /** - Check if this image is valid. - */ - bool isValid() const noexcept; - - /** - Get width. - */ - uint getWidth() const noexcept; - - /** - Get height. - */ - uint getHeight() const noexcept; - - /** - Get size. - */ - const Size& getSize() const noexcept; - - /** - Get the raw image data. - */ - const char* getRawData() const noexcept; - - /** - Get the image format. - */ - GLenum getFormat() const noexcept; - - /** - Get the image type. - */ - GLenum getType() const noexcept; - - /** - Draw this image at (0, 0) point. - */ - void draw(); - - /** - Draw this image at (x, y) point. - */ - void drawAt(const int x, const int y); - - /** - Draw this image at position @a pos. - */ - void drawAt(const Point& pos); - - Image& operator=(const Image& image) noexcept; - bool operator==(const Image& image) const noexcept; - bool operator!=(const Image& image) const noexcept; - -private: - const char* fRawData; - Size fSize; - GLenum fFormat; - GLenum fType; - GLuint fTextureId; - bool fIsReady; -}; - -// ----------------------------------------------------------------------- +#ifdef DGL_CAIRO +typedef CairoImage Image; +#else +typedef OpenGLImage Image; +#endif END_NAMESPACE_DGL -#endif // DGL_IMAGE_HPP_INCLUDED +#endif diff --git a/source/modules/dgl/ImageBase.hpp b/source/modules/dgl/ImageBase.hpp new file mode 100644 index 000000000..e7e84792d --- /dev/null +++ b/source/modules/dgl/ImageBase.hpp @@ -0,0 +1,156 @@ +/* + * DISTRHO Plugin Framework (DPF) + * Copyright (C) 2012-2021 Filipe Coelho + * + * Permission to use, copy, modify, and/or distribute this software for any purpose with + * or without fee is hereby granted, provided that the above copyright notice and this + * permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD + * TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN + * NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER + * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef DGL_IMAGE_BASE_HPP_INCLUDED +#define DGL_IMAGE_BASE_HPP_INCLUDED + +#include "Geometry.hpp" + +START_NAMESPACE_DGL + +// -------------------------------------------------------------------------------------------------------------------- + +enum ImageFormat { + kImageFormatNull, + kImageFormatGrayscale, + kImageFormatBGR, + kImageFormatBGRA, + kImageFormatRGB, + kImageFormatRGBA, +}; + +/** + Base DGL Image class. + + This is an Image class that handles raw image data in pixels. + It is an abstract class that provides the common methods to build on top. + Cairo and OpenGL Image classes are based upon this one. + + @see CairoImage, OpenGLImage + */ +class ImageBase +{ +protected: + /** + Constructor for a null Image. + */ + ImageBase(); + + /** + Constructor using raw image data. + @note @a rawData must remain valid for the lifetime of this Image. + */ + ImageBase(const char* rawData, uint width, uint height, ImageFormat format); + + /** + Constructor using raw image data. + @note @a rawData must remain valid for the lifetime of this Image. + */ + ImageBase(const char* rawData, const Size& size, ImageFormat format); + + /** + Constructor using another image data. + */ + ImageBase(const ImageBase& image); + +public: + /** + Destructor. + */ + virtual ~ImageBase(); + + /** + Check if this image is valid. + */ + bool isValid() const noexcept; + + /** + Check if this image is not valid. + */ + bool isInvalid() const noexcept; + + /** + Get width. + */ + uint getWidth() const noexcept; + + /** + Get height. + */ + uint getHeight() const noexcept; + + /** + Get size. + */ + const Size& getSize() const noexcept; + + /** + Get the raw image data. + */ + const char* getRawData() const noexcept; + + /** + Get the image format. + */ + ImageFormat getFormat() const noexcept; + + /** + Load image data from memory. + @note @a rawData must remain valid for the lifetime of this Image. + */ + void loadFromMemory(const char* rawData, uint width, uint height, ImageFormat format = kImageFormatBGRA) noexcept; + + /** + Load image data from memory. + @note @a rawData must remain valid for the lifetime of this Image. + */ + virtual void loadFromMemory(const char* rawData, + const Size& size, + ImageFormat format = kImageFormatBGRA) noexcept; + + /** + Draw this image at (0, 0) point using the current OpenGL context. + */ + void draw(const GraphicsContext& context); + + /** + Draw this image at (x, y) point using the current OpenGL context. + */ + void drawAt(const GraphicsContext& context, int x, int y); + + /** + Draw this image at position @a pos using the current OpenGL context. + */ + virtual void drawAt(const GraphicsContext& context, const Point& pos) = 0; + + /** + TODO document this. + */ + ImageBase& operator=(const ImageBase& image) noexcept; + bool operator==(const ImageBase& image) const noexcept; + bool operator!=(const ImageBase& image) const noexcept; + +protected: + const char* rawData; + Size size; + ImageFormat format; +}; + +// -------------------------------------------------------------------------------------------------------------------- + +END_NAMESPACE_DGL + +#endif // DGL_IMAGE_HPP_INCLUDED diff --git a/source/modules/dgl/ImageBaseWidgets.hpp b/source/modules/dgl/ImageBaseWidgets.hpp new file mode 100644 index 000000000..1a4fbbea9 --- /dev/null +++ b/source/modules/dgl/ImageBaseWidgets.hpp @@ -0,0 +1,257 @@ +/* + * DISTRHO Plugin Framework (DPF) + * Copyright (C) 2012-2021 Filipe Coelho + * + * Permission to use, copy, modify, and/or distribute this software for any purpose with + * or without fee is hereby granted, provided that the above copyright notice and this + * permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD + * TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN + * NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER + * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef DGL_IMAGE_BASE_WIDGETS_HPP_INCLUDED +#define DGL_IMAGE_BASE_WIDGETS_HPP_INCLUDED + +#include "EventHandlers.hpp" +#include "StandaloneWindow.hpp" +#include "SubWidget.hpp" + +START_NAMESPACE_DGL + +// -------------------------------------------------------------------------------------------------------------------- + +/** + DGL Image About Window class. + + This is a Window attached (transient) to another Window that simply shows an Image as its content. + It is typically used for "about this project" style pop-up Windows. + + Pressing 'Esc' or clicking anywhere on the window will automatically close it. + + @see CairoImageAboutWindow, OpenGLImageAboutWindow, Window::runAsModal(bool) + */ +template +class ImageBaseAboutWindow : public StandaloneWindow +{ +public: + /** + Constructor taking an existing Window as the parent transient window and an optional image. + If @a image is valid, the about window size will match the image size. + */ + explicit ImageBaseAboutWindow(Window& transientParentWindow, const ImageType& image = ImageType()); + + /** + Constructor taking a top-level-widget's Window as the parent transient window and an optional image. + If @a image is valid, the about window size will match the image size. + */ + explicit ImageBaseAboutWindow(TopLevelWidget* topLevelWidget, const ImageType& image = ImageType()); + + /** + Set a new image to use as background for this window. + Window size will adjust to match the image size. + */ + void setImage(const ImageType& image); + +protected: + void onDisplay() override; + bool onKeyboard(const KeyboardEvent&) override; + bool onMouse(const MouseEvent&) override; + +private: + ImageType img; + + DISTRHO_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(ImageBaseAboutWindow) +}; + +// -------------------------------------------------------------------------------------------------------------------- + +/** + DGL Image Button class. + + This is a typical button, where the drawing comes from a pregenerated set of images. + The button can be under "normal", "hover" and "down" states, with one separate image possible for each. + + The event logic for this button comes from the ButtonEventHandler class. + + @see CairoImageButton, OpenGLImageButton + */ +template +class ImageBaseButton : public SubWidget, + public ButtonEventHandler +{ +public: + class Callback + { + public: + virtual ~Callback() {} + virtual void imageButtonClicked(ImageBaseButton* imageButton, int button) = 0; + }; + + explicit ImageBaseButton(Widget* parentWidget, const ImageType& image); + explicit ImageBaseButton(Widget* parentWidget, const ImageType& imageNormal, const ImageType& imageDown); + explicit ImageBaseButton(Widget* parentWidget, const ImageType& imageNormal, const ImageType& imageHover, const ImageType& imageDown); + + ~ImageBaseButton() override; + + void setCallback(Callback* callback) noexcept; + +protected: + void onDisplay() override; + bool onMouse(const MouseEvent&) override; + bool onMotion(const MotionEvent&) override; + +private: + struct PrivateData; + PrivateData* const pData; + + DISTRHO_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(ImageBaseButton) +}; + +// -------------------------------------------------------------------------------------------------------------------- + +/** + DGL Image Knob class. + + This is a typical knob/dial, where the drawing comes from a pregenerated image "filmstrip". + The knob's "filmstrip" image can be either horizontal or vertical, + with the number of steps automatically based on the largest value (ie, horizontal if width>height, vertical if height>width). + There are no different images for "hover" or "down" states. + + The event logic for this knob comes from the KnobEventHandler class. + + @see CairoImageKnob, OpenGLImageKnob + */ +template +class ImageBaseKnob : public SubWidget, + public KnobEventHandler +{ +public: + class Callback + { + public: + virtual ~Callback() {} + virtual void imageKnobDragStarted(ImageBaseKnob* imageKnob) = 0; + virtual void imageKnobDragFinished(ImageBaseKnob* imageKnob) = 0; + virtual void imageKnobValueChanged(ImageBaseKnob* imageKnob, float value) = 0; + }; + + explicit ImageBaseKnob(Widget* parentWidget, const ImageType& image, Orientation orientation = Vertical) noexcept; + explicit ImageBaseKnob(const ImageBaseKnob& imageKnob); + ImageBaseKnob& operator=(const ImageBaseKnob& imageKnob); + ~ImageBaseKnob() override; + + void setCallback(Callback* callback) noexcept; + void setImageLayerCount(uint count) noexcept; + void setRotationAngle(int angle); + bool setValue(float value, bool sendCallback = false) noexcept override; + +protected: + void onDisplay() override; + bool onMouse(const MouseEvent&) override; + bool onMotion(const MotionEvent&) override; + bool onScroll(const ScrollEvent&) override; + +private: + struct PrivateData; + PrivateData* const pData; + + DISTRHO_LEAK_DETECTOR(ImageBaseKnob) +}; + +// -------------------------------------------------------------------------------------------------------------------- + +// note set range and step before setting the value + +template +class ImageBaseSlider : public SubWidget +{ +public: + class Callback + { + public: + virtual ~Callback() {} + virtual void imageSliderDragStarted(ImageBaseSlider* imageSlider) = 0; + virtual void imageSliderDragFinished(ImageBaseSlider* imageSlider) = 0; + virtual void imageSliderValueChanged(ImageBaseSlider* imageSlider, float value) = 0; + }; + + explicit ImageBaseSlider(Widget* parentWidget, const ImageType& image) noexcept; + ~ImageBaseSlider() override; + + float getValue() const noexcept; + void setValue(float value, bool sendCallback = false) noexcept; + void setDefault(float def) noexcept; + + void setStartPos(const Point& startPos) noexcept; + void setStartPos(int x, int y) noexcept; + void setEndPos(const Point& endPos) noexcept; + void setEndPos(int x, int y) noexcept; + + void setInverted(bool inverted) noexcept; + void setRange(float min, float max) noexcept; + void setStep(float step) noexcept; + + void setCallback(Callback* callback) noexcept; + +protected: + void onDisplay() override; + bool onMouse(const MouseEvent&) override; + bool onMotion(const MotionEvent&) override; + +private: + struct PrivateData; + PrivateData* const pData; + + // these should not be used + void setAbsoluteX(int) const noexcept {} + void setAbsoluteY(int) const noexcept {} + void setAbsolutePos(int, int) const noexcept {} + void setAbsolutePos(const Point&) const noexcept {} + + DISTRHO_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(ImageBaseSlider) +}; + +// -------------------------------------------------------------------------------------------------------------------- + +template +class ImageBaseSwitch : public SubWidget +{ +public: + class Callback + { + public: + virtual ~Callback() {} + virtual void imageSwitchClicked(ImageBaseSwitch* imageSwitch, bool down) = 0; + }; + + explicit ImageBaseSwitch(Widget* parentWidget, const ImageType& imageNormal, const ImageType& imageDown) noexcept; + explicit ImageBaseSwitch(const ImageBaseSwitch& imageSwitch) noexcept; + ImageBaseSwitch& operator=(const ImageBaseSwitch& imageSwitch) noexcept; + ~ImageBaseSwitch() override; + + bool isDown() const noexcept; + void setDown(bool down) noexcept; + + void setCallback(Callback* callback) noexcept; + +protected: + void onDisplay() override; + bool onMouse(const MouseEvent&) override; + +private: + struct PrivateData; + PrivateData* const pData; + + DISTRHO_LEAK_DETECTOR(ImageBaseSwitch) +}; + +// -------------------------------------------------------------------------------------------------------------------- + +END_NAMESPACE_DGL + +#endif // DGL_IMAGE_BASE_WIDGETS_HPP_INCLUDED diff --git a/source/modules/dgl/ImageWidgets.hpp b/source/modules/dgl/ImageWidgets.hpp index aafe19622..992b6e37b 100644 --- a/source/modules/dgl/ImageWidgets.hpp +++ b/source/modules/dgl/ImageWidgets.hpp @@ -1,6 +1,6 @@ /* * DISTRHO Plugin Framework (DPF) - * Copyright (C) 2012-2016 Filipe Coelho + * Copyright (C) 2012-2021 Filipe Coelho * * Permission to use, copy, modify, and/or distribute this software for any purpose with * or without fee is hereby granted, provided that the above copyright notice and this @@ -18,253 +18,23 @@ #define DGL_IMAGE_WIDGETS_HPP_INCLUDED #include "Image.hpp" -#include "Widget.hpp" -#include "Window.hpp" +#include "ImageBaseWidgets.hpp" START_NAMESPACE_DGL -// ----------------------------------------------------------------------- - -class ImageAboutWindow : public Window, - public Widget -{ -public: - explicit ImageAboutWindow(Window& parent, const Image& image = Image()); - explicit ImageAboutWindow(Widget* widget, const Image& image = Image()); - - void setImage(const Image& image); - -protected: - void onDisplay() override; - bool onKeyboard(const KeyboardEvent&) override; - bool onMouse(const MouseEvent&) override; - void onReshape(uint width, uint height) override; - -private: - Image fImgBackground; - - DISTRHO_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(ImageAboutWindow) -}; - -// ----------------------------------------------------------------------- - -class ImageButton : public Widget -{ -public: - class Callback - { - public: - virtual ~Callback() {} - virtual void imageButtonClicked(ImageButton* imageButton, int button) = 0; - }; - - explicit ImageButton(Window& parent, const Image& image); - explicit ImageButton(Window& parent, const Image& imageNormal, const Image& imageDown); - explicit ImageButton(Window& parent, const Image& imageNormal, const Image& imageHover, const Image& imageDown); - - explicit ImageButton(Widget* widget, const Image& image); - explicit ImageButton(Widget* widget, const Image& imageNormal, const Image& imageDown); - explicit ImageButton(Widget* widget, const Image& imageNormal, const Image& imageHover, const Image& imageDown); - - ~ImageButton() override; - - void setCallback(Callback* callback) noexcept; - -protected: - void onDisplay() override; - bool onMouse(const MouseEvent&) override; - bool onMotion(const MotionEvent&) override; - -private: - struct PrivateData; - PrivateData* const pData; - - DISTRHO_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(ImageButton) -}; - -// ----------------------------------------------------------------------- - -class ImageKnob : public Widget -{ -public: - enum Orientation { - Horizontal, - Vertical - }; - - class Callback - { - public: - virtual ~Callback() {} - virtual void imageKnobDragStarted(ImageKnob* imageKnob) = 0; - virtual void imageKnobDragFinished(ImageKnob* imageKnob) = 0; - virtual void imageKnobValueChanged(ImageKnob* imageKnob, float value) = 0; - }; - - explicit ImageKnob(Window& parent, const Image& image, Orientation orientation = Vertical) noexcept; - explicit ImageKnob(Widget* widget, const Image& image, Orientation orientation = Vertical) noexcept; - explicit ImageKnob(const ImageKnob& imageKnob); - ImageKnob& operator=(const ImageKnob& imageKnob); - ~ImageKnob() override; - - float getValue() const noexcept; - - void setDefault(float def) noexcept; - void setRange(float min, float max) noexcept; - void setStep(float step) noexcept; - void setValue(float value, bool sendCallback = false) noexcept; - void setUsingLogScale(bool yesNo) noexcept; - - void setCallback(Callback* callback) noexcept; - void setOrientation(Orientation orientation) noexcept; - void setRotationAngle(int angle); - - void setImageLayerCount(uint count) noexcept; - -protected: - void onDisplay() override; - bool onMouse(const MouseEvent&) override; - bool onMotion(const MotionEvent&) override; - bool onScroll(const ScrollEvent&) override; - -private: - Image fImage; - float fMinimum; - float fMaximum; - float fStep; - float fValue; - float fValueDef; - float fValueTmp; - bool fUsingDefault; - bool fUsingLog; - Orientation fOrientation; - - int fRotationAngle; - bool fDragging; - int fLastX; - int fLastY; - - Callback* fCallback; - - bool fIsImgVertical; - uint fImgLayerWidth; - uint fImgLayerHeight; - uint fImgLayerCount; - bool fIsReady; - GLuint fTextureId; - - float _logscale(float value) const; - float _invlogscale(float value) const; - - DISTRHO_LEAK_DETECTOR(ImageKnob) -}; - -// ----------------------------------------------------------------------- - -// note set range and step before setting the value - -class ImageSlider : public Widget -{ -public: - class Callback - { - public: - virtual ~Callback() {} - virtual void imageSliderDragStarted(ImageSlider* imageSlider) = 0; - virtual void imageSliderDragFinished(ImageSlider* imageSlider) = 0; - virtual void imageSliderValueChanged(ImageSlider* imageSlider, float value) = 0; - }; - - explicit ImageSlider(Window& parent, const Image& image) noexcept; - explicit ImageSlider(Widget* widget, const Image& image) noexcept; - - float getValue() const noexcept; - void setValue(float value, bool sendCallback = false) noexcept; - - void setStartPos(const Point& startPos) noexcept; - void setStartPos(int x, int y) noexcept; - void setEndPos(const Point& endPos) noexcept; - void setEndPos(int x, int y) noexcept; - - void setInverted(bool inverted) noexcept; - void setRange(float min, float max) noexcept; - void setStep(float step) noexcept; - - void setCallback(Callback* callback) noexcept; - -protected: - void onDisplay() override; - bool onMouse(const MouseEvent&) override; - bool onMotion(const MotionEvent&) override; - -private: - Image fImage; - float fMinimum; - float fMaximum; - float fStep; - float fValue; - float fValueTmp; - - bool fDragging; - bool fInverted; - bool fValueIsSet; - int fStartedX; - int fStartedY; - - Callback* fCallback; - - Point fStartPos; - Point fEndPos; - Rectangle fSliderArea; - - void _recheckArea() noexcept; - - // these should not be used - void setAbsoluteX(int) const noexcept {} - void setAbsoluteY(int) const noexcept {} - void setAbsolutePos(int, int) const noexcept {} - void setAbsolutePos(const Point&) const noexcept {} - - DISTRHO_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(ImageSlider) -}; - -// ----------------------------------------------------------------------- - -class ImageSwitch : public Widget -{ -public: - class Callback - { - public: - virtual ~Callback() {} - virtual void imageSwitchClicked(ImageSwitch* imageButton, bool down) = 0; - }; - - explicit ImageSwitch(Window& parent, const Image& imageNormal, const Image& imageDown) noexcept; - explicit ImageSwitch(Widget* widget, const Image& imageNormal, const Image& imageDown) noexcept; - explicit ImageSwitch(const ImageSwitch& imageSwitch) noexcept; - ImageSwitch& operator=(const ImageSwitch& imageSwitch) noexcept; - - bool isDown() const noexcept; - void setDown(bool down) noexcept; - - void setCallback(Callback* callback) noexcept; - -protected: - void onDisplay() override; - bool onMouse(const MouseEvent&) override; - -private: - Image fImageNormal; - Image fImageDown; - bool fIsDown; - - Callback* fCallback; - - DISTRHO_LEAK_DETECTOR(ImageSwitch) -}; - -// ----------------------------------------------------------------------- +#ifdef DGL_CAIRO +typedef CairoImageAboutWindow ImageAboutWindow; +typedef CairoImageButton ImageButton; +typedef CairoImageKnob ImageKnob; +typedef CairoImageSlider ImageSlider; +typedef CairoImageSwitch ImageSwitch; +#else +typedef OpenGLImageAboutWindow ImageAboutWindow; +typedef OpenGLImageButton ImageButton; +typedef OpenGLImageKnob ImageKnob; +typedef OpenGLImageSlider ImageSlider; +typedef OpenGLImageSwitch ImageSwitch; +#endif END_NAMESPACE_DGL diff --git a/source/modules/dgl/Makefile b/source/modules/dgl/Makefile index 9500381d1..552f3444f 100644 --- a/source/modules/dgl/Makefile +++ b/source/modules/dgl/Makefile @@ -11,6 +11,7 @@ include ../Makefile.mk # --------------------------------------------------------------------------------------------------------------------- BUILD_CXX_FLAGS += $(DGL_FLAGS) -Isrc +BUILD_CXX_FLAGS += -Isrc/pugl-upstream/include ifneq ($(MACOS),true) WINE_FLAGS = -I/usr/include/wine/wine/windows @@ -22,27 +23,47 @@ endif OBJS = \ $(OBJDIR)/Application.cpp.o \ + $(OBJDIR)/ApplicationPrivateData.cpp.o \ $(OBJDIR)/Color.cpp.o \ + $(OBJDIR)/EventHandlers.cpp.o \ $(OBJDIR)/Geometry.cpp.o \ - $(OBJDIR)/Image.cpp.o \ - $(OBJDIR)/ImageWidgets.cpp.o \ + $(OBJDIR)/ImageBase.cpp.o \ + $(OBJDIR)/ImageBaseWidgets.cpp.o \ $(OBJDIR)/NanoVG.cpp.o \ - $(OBJDIR)/Widget.cpp.o + $(OBJDIR)/OpenGL.cpp.o \ + $(OBJDIR)/SubWidget.cpp.o \ + $(OBJDIR)/SubWidgetPrivateData.cpp.o \ + $(OBJDIR)/TopLevelWidget.cpp.o \ + $(OBJDIR)/TopLevelWidgetPrivateData.cpp.o \ + $(OBJDIR)/Widget.cpp.o \ + $(OBJDIR)/WidgetPrivateData.cpp.o \ + $(OBJDIR)/Window.cpp.o \ + $(OBJDIR)/WindowPrivateData.cpp.o ifeq ($(MACOS),true) -OBJS += $(OBJDIR)/Window.mm.o +OBJS += $(OBJDIR)/pugl.mm.o else -OBJS += $(OBJDIR)/Window.cpp.o +OBJS += $(OBJDIR)/pugl.cpp.o endif OBJS_wine = \ + $(OBJDIR)/pugl.cpp-wine.o \ $(OBJDIR)/Application.cpp-wine.o \ + $(OBJDIR)/ApplicationPrivateData.cpp-wine.o \ $(OBJDIR)/Color.cpp-wine.o \ + $(OBJDIR)/EventHandlers.cpp-wine.o \ $(OBJDIR)/Geometry.cpp-wine.o \ - $(OBJDIR)/Image.cpp-wine.o \ - $(OBJDIR)/ImageWidgets.cpp-wine.o \ + $(OBJDIR)/ImageBase.cpp-wine.o \ + $(OBJDIR)/ImageBaseWidgets.cpp-wine.o \ + $(OBJDIR)/OpenGL.cpp-wine.o \ + $(OBJDIR)/SubWidget.cpp-wine.o \ + $(OBJDIR)/SubWidgetPrivateData.cpp-wine.o \ + $(OBJDIR)/TopLevelWidget.cpp-wine.o \ + $(OBJDIR)/TopLevelWidgetPrivateData.cpp-wine.o \ $(OBJDIR)/Widget.cpp-wine.o \ - $(OBJDIR)/Window.cpp-wine.o + $(OBJDIR)/WidgetPrivateData.cpp-wine.o \ + $(OBJDIR)/Window.cpp-wine.o \ + $(OBJDIR)/WindowPrivateData.cpp-wine.o # --------------------------------------------------------------------------------------------------------------------- diff --git a/source/modules/dgl/NanoVG.hpp b/source/modules/dgl/NanoVG.hpp index e94da1059..e18074f54 100644 --- a/source/modules/dgl/NanoVG.hpp +++ b/source/modules/dgl/NanoVG.hpp @@ -1,6 +1,6 @@ /* * DISTRHO Plugin Framework (DPF) - * Copyright (C) 2012-2016 Filipe Coelho + * Copyright (C) 2012-2021 Filipe Coelho * * Permission to use, copy, modify, and/or distribute this software for any purpose with * or without fee is hereby granted, provided that the above copyright notice and this @@ -18,7 +18,15 @@ #define DGL_NANO_WIDGET_HPP_INCLUDED #include "Color.hpp" -#include "Widget.hpp" +#include "OpenGL.hpp" +#include "SubWidget.hpp" +#include "TopLevelWidget.hpp" +#include "StandaloneWindow.hpp" + +#ifdef _MSC_VER +# pragma warning(push) +# pragma warning(disable:4661) /* instantiated template classes whose methods are defined elsewhere */ +#endif #ifndef DGL_NO_SHARED_RESOURCES # define NANOVG_DEJAVU_SANS_TTF "__dpf_dejavusans_ttf__" @@ -32,9 +40,17 @@ START_NAMESPACE_DGL // ----------------------------------------------------------------------- // Forward class names -class BlendishWidget; class NanoVG; +// ----------------------------------------------------------------------- +// Helper methods + +/** + Create a NanoVG context using the DPF-provided NanoVG library. + On Windows this will load a few extra OpenGL functions required for NanoVG to work. + */ +NVGcontext* nvgCreateGL(int flags); + // ----------------------------------------------------------------------- // NanoImage @@ -304,7 +320,9 @@ public: /** Constructor reusing a NanoVG context, used for subwidgets. */ + /* NanoVG(NanoWidget* groupWidget); + */ /** Destructor. @@ -435,6 +453,11 @@ public: */ void globalAlpha(float alpha); + /** + Sets the color tint applied to all rendered shapes. + */ + void globalTint(Color tint); + /* -------------------------------------------------------------------- * Transforms */ @@ -578,12 +601,26 @@ public: NanoImage::Handle createImageFromMemory(uchar* data, uint dataSize, int imageFlags); /** - Creates image from specified image data. + Creates image from specified raw format image data. + */ + NanoImage::Handle createImageFromRawMemory(uint w, uint h, const uchar* data, + ImageFlags imageFlags, ImageFormat format); + + /** + Creates image from specified raw format image data. + Overloaded function for convenience. + @see ImageFlags + */ + NanoImage::Handle createImageFromRawMemory(uint w, uint h, const uchar* data, + int imageFlags, ImageFormat format); + + /** + Creates image from specified RGBA image data. */ NanoImage::Handle createImageFromRGBA(uint w, uint h, const uchar* data, ImageFlags imageFlags); /** - Creates image from specified image data. + Creates image from specified RGBA image data. Overloaded function for convenience. @see ImageFlags */ @@ -850,14 +887,13 @@ public: /** Load DPF's internal shared resources for this NanoVG class. */ - virtual void loadSharedResources(); + virtual bool loadSharedResources(); #endif private: NVGcontext* const fContext; bool fInFrame; bool fIsSubWidget; - friend class BlendishWidget; DISTRHO_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(NanoVG) }; @@ -872,30 +908,39 @@ private: The drawing function onDisplay() is implemented internally but a new onNanoDisplay() needs to be overridden instead. */ -class NanoWidget : public Widget, - public NanoVG +template +class NanoBaseWidget : public BaseWidget, + public NanoVG { public: /** - Constructor. + Constructor for a NanoSubWidget. + @see CreateFlags + */ + explicit NanoBaseWidget(Widget* parentGroupWidget, int flags = CREATE_ANTIALIAS); + + /** + Constructor for a NanoTopLevelWidget. @see CreateFlags */ - explicit NanoWidget(Window& parent, int flags = CREATE_ANTIALIAS); + explicit NanoBaseWidget(Window& windowToMapTo, int flags = CREATE_ANTIALIAS); /** - Constructor for a subwidget. + Constructor for a NanoStandaloneWindow without transient parent window. + @see CreateFlags */ - explicit NanoWidget(Widget* groupWidget, int flags = CREATE_ANTIALIAS); + explicit NanoBaseWidget(Application& app, int flags = CREATE_ANTIALIAS); /** - Constructor for a subwidget, reusing a NanoVG context. + Constructor for a NanoStandaloneWindow with transient parent window. + @see CreateFlags */ - explicit NanoWidget(NanoWidget* groupWidget); + explicit NanoBaseWidget(Application& app, Window& transientParentWindow, int flags = CREATE_ANTIALIAS); /** Destructor. */ - virtual ~NanoWidget(); + virtual ~NanoBaseWidget() {} protected: /** @@ -905,14 +950,17 @@ protected: virtual void onNanoDisplay() = 0; private: - struct PrivateData; - PrivateData* const nData; - /** Widget display function. Implemented internally to wrap begin/endFrame() automatically. */ - void onDisplay() override; + inline void onDisplay() override + { + // NOTE maybe should use BaseWidget::getWindow().getScaleFactor() as 3rd arg ? + NanoVG::beginFrame(BaseWidget::getWidth(), BaseWidget::getHeight()); + onNanoDisplay(); + NanoVG::endFrame(); + } // these should not be used void beginFrame(uint,uint) {} @@ -921,11 +969,22 @@ private: void cancelFrame() {} void endFrame() {} - DISTRHO_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(NanoWidget) + DISTRHO_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(NanoBaseWidget) }; +typedef NanoBaseWidget NanoSubWidget; +typedef NanoBaseWidget NanoTopLevelWidget; +typedef NanoBaseWidget NanoStandaloneWindow; + +DISTRHO_DEPRECATED_BY("NanoSubWidget") +typedef NanoSubWidget NanoWidget; + // ----------------------------------------------------------------------- END_NAMESPACE_DGL +#ifdef _MSC_VER +# pragma warning(pop) +#endif + #endif // DGL_NANO_WIDGET_HPP_INCLUDED diff --git a/source/modules/dgl/OpenGL.hpp b/source/modules/dgl/OpenGL.hpp new file mode 100644 index 000000000..629d831f9 --- /dev/null +++ b/source/modules/dgl/OpenGL.hpp @@ -0,0 +1,308 @@ +/* + * DISTRHO Plugin Framework (DPF) + * Copyright (C) 2012-2021 Filipe Coelho + * + * Permission to use, copy, modify, and/or distribute this software for any purpose with + * or without fee is hereby granted, provided that the above copyright notice and this + * permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD + * TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN + * NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER + * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef DGL_OPENGL_HPP_INCLUDED +#define DGL_OPENGL_HPP_INCLUDED + +#include "ImageBase.hpp" +#include "ImageBaseWidgets.hpp" + +// ----------------------------------------------------------------------- +// Fix OpenGL includes for Windows, based on glfw code (part 1) + +#undef DGL_CALLBACK_DEFINED +#undef DGL_WINGDIAPI_DEFINED + +#ifdef DISTRHO_OS_WINDOWS + +#ifndef APIENTRY +# define APIENTRY __stdcall +#endif // APIENTRY + +/* We need WINGDIAPI defined */ +#ifndef WINGDIAPI +# if defined(_MSC_VER) || defined(__BORLANDC__) || defined(__POCC__) +# define WINGDIAPI __declspec(dllimport) +# elif defined(__LCC__) +# define WINGDIAPI __stdcall +# else +# define WINGDIAPI extern +# endif +# define DGL_WINGDIAPI_DEFINED +#endif // WINGDIAPI + +/* Some files also need CALLBACK defined */ +#ifndef CALLBACK +# if defined(_MSC_VER) +# if (defined(_M_MRX000) || defined(_M_IX86) || defined(_M_ALPHA) || defined(_M_PPC)) && !defined(MIDL_PASS) +# define CALLBACK __stdcall +# else +# define CALLBACK +# endif +# else +# define CALLBACK __stdcall +# endif +# define DGL_CALLBACK_DEFINED +#endif // CALLBACK + +/* Most GL/glu.h variants on Windows need wchar_t */ +#include + +#endif // DISTRHO_OS_WINDOWS + +// ----------------------------------------------------------------------- +// OpenGL includes + +#ifdef DISTRHO_OS_MAC +# ifdef DGL_USE_OPENGL3 +# include +# include +# else +# include +# endif +#else +# ifndef DISTRHO_OS_WINDOWS +# define GL_GLEXT_PROTOTYPES +# endif +# ifndef __GLEW_H__ +# include +# include +# endif +#endif + +// ----------------------------------------------------------------------- +// Missing OpenGL defines + +#if defined(GL_BGR_EXT) && !defined(GL_BGR) +# define GL_BGR GL_BGR_EXT +#endif + +#if defined(GL_BGRA_EXT) && !defined(GL_BGRA) +# define GL_BGRA GL_BGRA_EXT +#endif + +#ifndef GL_CLAMP_TO_BORDER +# define GL_CLAMP_TO_BORDER 0x812D +#endif + +// ----------------------------------------------------------------------- +// Fix OpenGL includes for Windows, based on glfw code (part 2) + +#ifdef DGL_CALLBACK_DEFINED +# undef CALLBACK +# undef DGL_CALLBACK_DEFINED +#endif + +#ifdef DGL_WINGDIAPI_DEFINED +# undef WINGDIAPI +# undef DGL_WINGDIAPI_DEFINED +#endif + +START_NAMESPACE_DGL + +// ----------------------------------------------------------------------- + +/** + OpenGL Graphics context. + */ +struct OpenGLGraphicsContext : GraphicsContext +{ +}; + +// ----------------------------------------------------------------------- + +static inline +ImageFormat asDISTRHOImageFormat(const GLenum format) +{ + switch (format) + { +#ifdef DGL_USE_OPENGL3 + case GL_RED: +#else + case GL_LUMINANCE: +#endif + return kImageFormatGrayscale; + case GL_BGR: + return kImageFormatBGR; + case GL_BGRA: + return kImageFormatBGRA; + case GL_RGB: + return kImageFormatRGB; + case GL_RGBA: + return kImageFormatRGBA; + } + + return kImageFormatNull; +} + +static inline +GLenum asOpenGLImageFormat(const ImageFormat format) +{ + switch (format) + { + case kImageFormatNull: + break; + case kImageFormatGrayscale: +#ifdef DGL_USE_OPENGL3 + return GL_RED; +#else + return GL_LUMINANCE; +#endif + case kImageFormatBGR: + return GL_BGR; + case kImageFormatBGRA: + return GL_BGRA; + case kImageFormatRGB: + return GL_RGB; + case kImageFormatRGBA: + return GL_RGBA; + } + + return 0x0; +} + +// ----------------------------------------------------------------------- + +/** + OpenGL Image class. + + This is an Image class that handles raw image data in pixels. + You can init the image data on the contructor or later on by calling loadFromMemory(). + + To generate raw data useful for this class see the utils/png2rgba.py script. + Be careful when using a PNG without alpha channel, for those the format is 'GL_BGR' + instead of the default 'GL_BGRA'. + + Images are drawn on screen via 2D textures. + */ +class OpenGLImage : public ImageBase +{ +public: + /** + Constructor for a null Image. + */ + OpenGLImage(); + + /** + Constructor using raw image data. + @note @a rawData must remain valid for the lifetime of this Image. + */ + OpenGLImage(const char* rawData, uint width, uint height, ImageFormat format = kImageFormatBGRA); + + /** + Constructor using raw image data. + @note @a rawData must remain valid for the lifetime of this Image. + */ + OpenGLImage(const char* rawData, const Size& size, ImageFormat format = kImageFormatBGRA); + + /** + Constructor using another image data. + */ + OpenGLImage(const OpenGLImage& image); + + /** + Destructor. + */ + ~OpenGLImage() override; + + /** + Load image data from memory. + @note @a rawData must remain valid for the lifetime of this Image. + */ + void loadFromMemory(const char* rawData, + const Size& size, + ImageFormat format = kImageFormatBGRA) noexcept override; + + /** + Draw this image at position @a pos using the graphics context @a context. + */ + void drawAt(const GraphicsContext& context, const Point& pos) override; + + /** + TODO document this. + */ + OpenGLImage& operator=(const OpenGLImage& image) noexcept; + + // FIXME this should not be needed + inline void loadFromMemory(const char* rdata, uint w, uint h, ImageFormat fmt = kImageFormatBGRA) + { loadFromMemory(rdata, Size(w, h), fmt); }; + inline void draw(const GraphicsContext& context) + { drawAt(context, Point(0, 0)); }; + inline void drawAt(const GraphicsContext& context, int x, int y) + { drawAt(context, Point(x, y)); }; + + /** + Constructor using raw image data, specifying an OpenGL image format. + @note @a rawData must remain valid for the lifetime of this Image. + DEPRECATED This constructor uses OpenGL image format instead of DISTRHO one. + */ + DISTRHO_DEPRECATED_BY("OpenGLImage(const char*, uint, uint, ImageFormat)") + explicit OpenGLImage(const char* rawData, uint width, uint height, GLenum glFormat); + + /** + Constructor using raw image data, specifying an OpenGL image format. + @note @a rawData must remain valid for the lifetime of this Image. + DEPRECATED This constructor uses OpenGL image format instead of DISTRHO one. + */ + DISTRHO_DEPRECATED_BY("OpenGLImage(const char*, const Size&, ImageFormat)") + explicit OpenGLImage(const char* rawData, const Size& size, GLenum glFormat); + + /** + Draw this image at (0, 0) point using the current OpenGL context. + DEPRECATED This function does not take into consideration the current graphics context and only works in OpenGL. + */ + DISTRHO_DEPRECATED_BY("draw(const GraphicsContext&)") + void draw(); + + /** + Draw this image at (x, y) point using the current OpenGL context. + DEPRECATED This function does not take into consideration the current graphics context and only works in OpenGL. + */ + DISTRHO_DEPRECATED_BY("drawAt(const GraphicsContext&, int, int)") + void drawAt(int x, int y); + + /** + Draw this image at position @a pos using the current OpenGL context. + DEPRECATED This function does not take into consideration the current graphics context and only works in OpenGL. + */ + DISTRHO_DEPRECATED_BY("drawAt(const GraphicsContext&, const Point&)") + void drawAt(const Point& pos); + + /** + Get the image type. + DEPRECATED Type is always assumed to be GL_UNSIGNED_BYTE. + */ + DISTRHO_DEPRECATED + GLenum getType() const noexcept { return GL_UNSIGNED_BYTE; } + +private: + GLuint textureId; + bool setupCalled; +}; + +// ----------------------------------------------------------------------- + +typedef ImageBaseAboutWindow OpenGLImageAboutWindow; +typedef ImageBaseButton OpenGLImageButton; +typedef ImageBaseKnob OpenGLImageKnob; +typedef ImageBaseSlider OpenGLImageSlider; +typedef ImageBaseSwitch OpenGLImageSwitch; + +// ----------------------------------------------------------------------- + +END_NAMESPACE_DGL + +#endif diff --git a/source/modules/dgl/StandaloneWindow.hpp b/source/modules/dgl/StandaloneWindow.hpp index 01ffa7dbd..673a85e66 100644 --- a/source/modules/dgl/StandaloneWindow.hpp +++ b/source/modules/dgl/StandaloneWindow.hpp @@ -1,6 +1,6 @@ /* * DISTRHO Plugin Framework (DPF) - * Copyright (C) 2012-2016 Filipe Coelho + * Copyright (C) 2012-2021 Filipe Coelho * * Permission to use, copy, modify, and/or distribute this software for any purpose with * or without fee is hereby granted, provided that the above copyright notice and this @@ -17,39 +17,69 @@ #ifndef DGL_STANDALONE_WINDOW_HPP_INCLUDED #define DGL_STANDALONE_WINDOW_HPP_INCLUDED -#include "Application.hpp" -#include "Widget.hpp" +#include "TopLevelWidget.hpp" #include "Window.hpp" START_NAMESPACE_DGL // ----------------------------------------------------------------------- -class StandaloneWindow : public Application, - public Window +class StandaloneWindow : public Window, + public TopLevelWidget { public: /** - Constructor. + Constructor without parent. */ - StandaloneWindow(); + StandaloneWindow(Application& app) + : Window(app), + TopLevelWidget((Window&)*this), + sgc((Window&)*this) {} /** - Show window and execute application. + Constructor with a transient parent window, typically used to run as modal. */ - void exec(); + StandaloneWindow(Application& app, Window& transientParentWindow) + : Window(app, transientParentWindow), + TopLevelWidget((Window&)*this), + sgc((Window&)*this, transientParentWindow) {} -private: - Widget* fWidget; - - /** @internal */ - void onReshape(uint width, uint height) override; + /** + Clear current graphics context. + Must be called at the end of your StandaloneWindow constructor. + */ + void done() + { + sgc.done(); + } - /** @internal */ - void _addWidget(Widget* widget) override; + /** + Overloaded functions to ensure they apply to the Window class. + */ + bool isVisible() const noexcept { return Window::isVisible(); } + void setVisible(bool yesNo) { Window::setVisible(yesNo); } + void hide() { Window::hide(); } + void show() { Window::show(); } + uint getWidth() const noexcept { return Window::getWidth(); } + uint getHeight() const noexcept { return Window::getHeight(); } + const Size getSize() const noexcept { return Window::getSize(); } + void repaint() noexcept { Window::repaint(); } + void setWidth(uint width) { Window::setWidth(width); } + void setHeight(uint height) { Window::setHeight(height); } + void setSize(uint width, uint height) { Window::setSize(width, height); } + void setSize(const Size& size) { Window::setSize(size); } + bool addIdleCallback(IdleCallback* callback, uint timerFrequencyInMs = 0) + { return Window::addIdleCallback(callback, timerFrequencyInMs); } + bool removeIdleCallback(IdleCallback* callback) { return Window::removeIdleCallback(callback); } + Application& getApp() const noexcept { return Window::getApp(); } + const GraphicsContext& getGraphicsContext() const noexcept { return Window::getGraphicsContext(); } + double getScaleFactor() const noexcept { return Window::getScaleFactor(); } + void setGeometryConstraints(uint minimumWidth, uint minimumHeight, + bool keepAspectRatio = false, bool automaticallyScale = false) + { Window::setGeometryConstraints(minimumWidth, minimumHeight, keepAspectRatio, automaticallyScale); } - /** @internal */ - void _removeWidget(Widget* widget) override; +private: + ScopedGraphicsContext sgc; DISTRHO_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(StandaloneWindow) }; diff --git a/source/modules/dgl/SubWidget.hpp b/source/modules/dgl/SubWidget.hpp new file mode 100644 index 000000000..eab84b743 --- /dev/null +++ b/source/modules/dgl/SubWidget.hpp @@ -0,0 +1,179 @@ +/* + * DISTRHO Plugin Framework (DPF) + * Copyright (C) 2012-2021 Filipe Coelho + * + * Permission to use, copy, modify, and/or distribute this software for any purpose with + * or without fee is hereby granted, provided that the above copyright notice and this + * permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD + * TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN + * NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER + * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef DGL_SUBWIDGET_HPP_INCLUDED +#define DGL_SUBWIDGET_HPP_INCLUDED + +#include "Widget.hpp" + +START_NAMESPACE_DGL + +// -------------------------------------------------------------------------------------------------------------------- + +/** + Sub-Widget class. + + This class is the main entry point for creating any reusable widgets from within DGL. + It can be freely positioned from within a parent widget, thus being named subwidget. + + Many subwidgets can share the same parent, and subwidgets themselves can also have its own subwidgets. + It is subwidgets all the way down. + + TODO check absolute vs relative position and see what makes more sense. + + @see CairoSubWidget + */ +class SubWidget : public Widget +{ +public: + /** + Constructor. + */ + explicit SubWidget(Widget* parentWidget); + + /** + Destructor. + */ + virtual ~SubWidget(); + + /** + Check if this widget contains the point defined by @a x and @a y. + */ + // TODO rename as containsRelativePos + template + bool contains(T x, T y) const noexcept; + + /** + Check if this widget contains the point @a pos. + */ + // TODO rename as containsRelativePos + template + bool contains(const Point& pos) const noexcept; + + /** + Get absolute X. + */ + int getAbsoluteX() const noexcept; + + /** + Get absolute Y. + */ + int getAbsoluteY() const noexcept; + + /** + Get absolute position. + */ + Point getAbsolutePos() const noexcept; + + /** + Get absolute area of this subwidget. + This is the same as `Rectangle(getAbsolutePos(), getSize());` + @see getConstrainedAbsoluteArea() + */ + Rectangle getAbsoluteArea() const noexcept; + + /** + Get absolute area of this subwidget, with special consideration for not allowing negative values. + @see getAbsoluteArea() + */ + Rectangle getConstrainedAbsoluteArea() const noexcept; + + /** + Set absolute X. + */ + void setAbsoluteX(int x) noexcept; + + /** + Set absolute Y. + */ + void setAbsoluteY(int y) noexcept; + + /** + Set absolute position using @a x and @a y values. + */ + void setAbsolutePos(int x, int y) noexcept; + + /** + Set absolute position. + */ + void setAbsolutePos(const Point& pos) noexcept; + + /** + Get the margin currently in use for widget coordinates. + By default this value is (0,0). + */ + Point getMargin() const noexcept; + + /** + Set a margin to be used for widget coordinates using @a x and @a y values. + */ + void setMargin(int x, int y) noexcept; + + /** + Set a margin to be used for widget coordinates. + */ + void setMargin(const Point& offset) noexcept; + + /** + Get parent Widget, as passed in the constructor. + */ + Widget* getParentWidget() const noexcept; + + /** + Request repaint of this subwidget's area to the window this widget belongs to. + */ + void repaint() noexcept override; + + /** + Bring this widget to the "front" of the parent widget. + Makes the widget behave as if it was the last to be registered on the parent widget, thus being "in front". + */ + virtual void toFront(); + + /** + Indicate that this subwidget will draw out of bounds, and thus needs the entire viewport available for drawing. + */ + void setNeedsFullViewportDrawing(bool needsFullViewportForDrawing = true); + + /** + Indicate that this subwidget will always draw at its own internal size and needs scaling to fit target size. + */ + void setNeedsViewportScaling(bool needsViewportScaling = true, double autoScaleFactor = 0.0); + + /** + Indicate that this subwidget should not be drawn on screen, typically because it is managed by something else. + */ + void setSkipDrawing(bool skipDrawing = true); + +protected: + /** + A function called when the subwidget's absolute position is changed. + */ + virtual void onPositionChanged(const PositionChangedEvent&); + +private: + struct PrivateData; + PrivateData* const pData; + friend class Widget; + DISTRHO_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(SubWidget) +}; + +// -------------------------------------------------------------------------------------------------------------------- + +END_NAMESPACE_DGL + +#endif // DGL_SUBWIDGET_HPP_INCLUDED + diff --git a/source/modules/dgl/TopLevelWidget.hpp b/source/modules/dgl/TopLevelWidget.hpp new file mode 100644 index 000000000..5e53784e1 --- /dev/null +++ b/source/modules/dgl/TopLevelWidget.hpp @@ -0,0 +1,146 @@ +/* + * DISTRHO Plugin Framework (DPF) + * Copyright (C) 2012-2021 Filipe Coelho + * + * Permission to use, copy, modify, and/or distribute this software for any purpose with + * or without fee is hereby granted, provided that the above copyright notice and this + * permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD + * TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN + * NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER + * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef DGL_TOP_LEVEL_WIDGET_HPP_INCLUDED +#define DGL_TOP_LEVEL_WIDGET_HPP_INCLUDED + +#include "Widget.hpp" + +#ifdef DISTRHO_DEFINES_H_INCLUDED +START_NAMESPACE_DISTRHO +class UI; +END_NAMESPACE_DISTRHO +#endif + +START_NAMESPACE_DGL + +class Window; + +// ----------------------------------------------------------------------- + +/** + Top-Level Widget class. + + This is the only Widget class that is allowed to be used directly on a Window. + + This widget takes the full size of the Window it is mapped to. + Sub-widgets can be added on top of this top-level widget, by creating them with this class as parent. + Doing so allows for custom position and sizes. + + This class is used as the type for DPF Plugin UIs. + So anything that a plugin UI might need that does not belong in a simple Widget will go here. + */ +class TopLevelWidget : public Widget +{ +public: + /** + Constructor. + */ + explicit TopLevelWidget(Window& windowToMapTo); + + /** + Destructor. + */ + virtual ~TopLevelWidget(); + + /** + Get the application associated with this top-level widget's window. + */ + Application& getApp() const noexcept; + + /** + Get the window associated with this top-level widget. + */ + Window& getWindow() const noexcept; + + /** + Set width of this widget's window. + @note This will not change the widget's size right away, but be pending on the OS resizing the window + */ + void setWidth(uint width); + + /** + Set height of this widget's window. + @note This will not change the widget's size right away, but be pending on the OS resizing the window + */ + void setHeight(uint height); + + /** + Set size of this widget's window, using @a width and @a height values. + @note This will not change the widget's size right away, but be pending on the OS resizing the window + */ + void setSize(uint width, uint height); + + /** + Set size of this widget's window. + @note This will not change the widget's size right away, but be pending on the OS resizing the window + */ + void setSize(const Size& size); + + /** + TODO document this. + */ + void repaint() noexcept override; + + /** + TODO document this. + */ + void repaint(const Rectangle& rect) noexcept; + + // TODO group stuff after here, convenience functions present in Window class + bool setClipboard(const char* mimeType, const void* data, size_t dataSize); + const void* getClipboard(const char*& mimeType, size_t& dataSize); + bool setCursor(MouseCursor cursor); + bool addIdleCallback(IdleCallback* callback, uint timerFrequencyInMs = 0); + bool removeIdleCallback(IdleCallback* callback); + double getScaleFactor() const noexcept; + void setGeometryConstraints(uint minimumWidth, + uint minimumHeight, + bool keepAspectRatio = false, + bool automaticallyScale = false, + bool resizeNowIfAutoScaling = true); + + DISTRHO_DEPRECATED_BY("getApp()") + Application& getParentApp() const noexcept { return getApp(); } + + DISTRHO_DEPRECATED_BY("getWindow()") + Window& getParentWindow() const noexcept { return getWindow(); } + +protected: + bool onKeyboard(const KeyboardEvent&) override; + bool onCharacterInput(const CharacterInputEvent&) override; + bool onMouse(const MouseEvent&) override; + bool onMotion(const MotionEvent&) override; + bool onScroll(const ScrollEvent&) override; + +private: + struct PrivateData; + PrivateData* const pData; + friend class Window; +#ifdef DISTRHO_DEFINES_H_INCLUDED + friend class DISTRHO_NAMESPACE::UI; +#endif + /** @internal */ + virtual void requestSizeChange(uint width, uint height); + + DISTRHO_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(TopLevelWidget) +}; + +// ----------------------------------------------------------------------- + +END_NAMESPACE_DGL + +#endif // DGL_TOP_LEVEL_WIDGET_HPP_INCLUDED diff --git a/source/modules/dgl/Widget.hpp b/source/modules/dgl/Widget.hpp index 6497a5f4f..463f7c9f5 100644 --- a/source/modules/dgl/Widget.hpp +++ b/source/modules/dgl/Widget.hpp @@ -1,6 +1,6 @@ /* * DISTRHO Plugin Framework (DPF) - * Copyright (C) 2012-2016 Filipe Coelho + * Copyright (C) 2012-2021 Filipe Coelho * * Permission to use, copy, modify, and/or distribute this software for any purpose with * or without fee is hereby granted, provided that the above copyright notice and this @@ -19,91 +19,102 @@ #include "Geometry.hpp" -#include +START_NAMESPACE_DGL -// ----------------------------------------------------------------------- +// -------------------------------------------------------------------------------------------------------------------- // Forward class names -#ifdef DISTRHO_DEFINES_H_INCLUDED -START_NAMESPACE_DISTRHO -class UI; -END_NAMESPACE_DISTRHO -#endif - -START_NAMESPACE_DGL - class Application; -class ImageSlider; -class NanoWidget; +class SubWidget; +class TopLevelWidget; class Window; -class StandaloneWindow; -// ----------------------------------------------------------------------- +// -------------------------------------------------------------------------------------------------------------------- /** Base DGL Widget class. This is the base Widget class, from which all widgets are built. - All widgets have a parent Window where they'll be drawn. - This parent is never changed during the widget lifetime. + All widgets have a parent widget where they'll be drawn, this can be the top-level widget or a group widget. + This parent is never changed during a widget's lifetime. - Widgets receive events in relative coordinates. - (0, 0) means its top-left position. + Widgets receive events in relative coordinates. (0, 0) means its top-left position. - Windows paint widgets in the order they are constructed. - Early widgets are drawn first, at the bottom, then newer ones on top. - Events are sent in the inverse order so that the top-most widget gets + The top-level widget will draw subwidgets in the order they are constructed. + Early subwidgets are drawn first, at the bottom, then newer ones on top. + Events are sent in the inverse order so that the top-most widgets get a chance to catch the event and stop its propagation. - All widget event callbacks do nothing by default. + All widget event callbacks do nothing by default and onDisplay MUST be reimplemented by subclasses. + + @note It is not possible to subclass this Widget class directly, you must use SubWidget or TopLevelWidget instead. */ class Widget { public: /** Base event data. - @a mod The currently active keyboard modifiers, @see Modifier. - @a time The timestamp (if any). + These are the fields present on all Widget events. + + @a mod Currently active keyboard modifiers, @see Modifier. + @a mod Event flags, @see EventFlag. + @a time Event timestamp (if any). */ struct BaseEvent { - uint mod; - uint32_t time; + uint mod; + uint flags; + uint time; - /** Constuctor */ - BaseEvent() noexcept : mod(0x0), time(0) {} + /** Constructor */ + BaseEvent() noexcept : mod(0x0), flags(0x0), time(0) {} /** Destuctor */ virtual ~BaseEvent() noexcept {} }; /** Keyboard event. - @a press True if the key was pressed, false if released. - @a key Unicode point of the key pressed. + + This event represents low-level key presses and releases. + This can be used for "direct" keyboard handing like key bindings, but must not be interpreted as text input. + + Keys are represented portably as Unicode code points, using the "natural" code point for the key. + The @a key field is the code for the pressed key, without any modifiers applied. + For example, a press or release of the 'A' key will have `key` 97 ('a') + regardless of whether shift or control are being held. + + Alternatively, the raw @a keycode can be used to work directly with physical keys, + but note that this value is not portable and differs between platforms and hardware. + + @a press True if the key was pressed, false if released. + @a key Unicode point of the key pressed. + @a keycode Raw keycode. @see onKeyboard */ struct KeyboardEvent : BaseEvent { bool press; uint key; + uint keycode; - /** Constuctor */ + /** Constructor */ KeyboardEvent() noexcept : BaseEvent(), press(false), - key(0) {} + key(0), + keycode(0) {} }; /** Special keyboard event. - @a press True if the key was pressed, false if released. - @a key The key pressed. - @see onSpecial + + DEPRECATED This used to be part of DPF due to pugl, but now deprecated and simply non-functional. + All events go through KeyboardEvent or CharacterInputEvent, use those instead. */ - struct SpecialEvent : BaseEvent { + struct DISTRHO_DEPRECATED_BY("KeyboardEvent") SpecialEvent : BaseEvent { bool press; Key key; - /** Constuctor */ + /** Constructor */ SpecialEvent() noexcept : BaseEvent(), press(false), @@ -111,54 +122,107 @@ public: }; /** - Mouse event. - @a button The button number (1 = left, 2 = middle, 3 = right). - @a press True if the button was pressed, false if released. - @a pos The widget-relative coordinates of the pointer. + Character input event. + + This event represents text input, usually as the result of a key press. + The text is given both as a Unicode character code and a UTF-8 string. + + Note that this event is generated by the platform's input system, + so there is not necessarily a direct correspondence between text events and physical key presses. + For example, with some input methods a sequence of several key presses will generate a single character. + + @a keycode Raw key code. + @a character Unicode character code. + @a string UTF-8 string. + @see onCharacterInput + */ + struct CharacterInputEvent : BaseEvent { + uint keycode; + uint character; + char string[8]; + + /** Constructor */ + CharacterInputEvent() noexcept + : BaseEvent(), + keycode(0), + character(0), +#ifdef DISTRHO_PROPER_CPP11_SUPPORT + string{'\0','\0','\0','\0','\0','\0','\0','\0'} {} +#else + string() { std::memset(string, 0, sizeof(string)); } +#endif + }; + + /** + Mouse press or release event. + + @a button The button number starting from 1 (1 = left, 2 = middle, 3 = right). + @a press True if the button was pressed, false if released. + @a pos The widget-relative coordinates of the pointer. + @a absolutePos The absolute coordinates of the pointer. @see onMouse */ struct MouseEvent : BaseEvent { - int button; + uint button; bool press; - Point pos; + Point pos; + Point absolutePos; - /** Constuctor */ + /** Constructor */ MouseEvent() noexcept : BaseEvent(), button(0), press(false), - pos(0, 0) {} + pos(0.0, 0.0), + absolutePos(0.0, 0.0) {} }; /** Mouse motion event. - @a pos The widget-relative coordinates of the pointer. + + @a pos The widget-relative coordinates of the pointer. + @a absolutePos The absolute coordinates of the pointer. @see onMotion */ struct MotionEvent : BaseEvent { - Point pos; + Point pos; + Point absolutePos; - /** Constuctor */ + /** Constructor */ MotionEvent() noexcept : BaseEvent(), - pos(0, 0) {} + pos(0.0, 0.0), + absolutePos(0.0, 0.0) {} }; /** Mouse scroll event. - @a pos The widget-relative coordinates of the pointer. - @a delta The scroll distance. + + The scroll distance is expressed in "lines", + an arbitrary unit that corresponds to a single tick of a detented mouse wheel. + For example, `delta.y` = 1.0 scrolls 1 line up. + Some systems and devices support finer resolution and/or higher values for fast scrolls, + so programs should handle any value gracefully. + + @a pos The widget-relative coordinates of the pointer. + @a absolutePos The absolute coordinates of the pointer. + @a delta The scroll distance. + @a direction The direction of the scroll or "smooth". @see onScroll */ struct ScrollEvent : BaseEvent { - Point pos; - Point delta; + Point pos; + Point absolutePos; + Point delta; + ScrollDirection direction; - /** Constuctor */ + /** Constructor */ ScrollEvent() noexcept : BaseEvent(), - pos(0, 0), - delta(0.0f, 0.0f) {} + pos(0.0, 0.0), + absolutePos(0.0, 0.0), + delta(0.0, 0.0), + direction(kScrollSmooth) {} }; /** @@ -171,22 +235,40 @@ public: Size size; Size oldSize; - /** Constuctor */ + /** Constructor */ ResizeEvent() noexcept : size(0, 0), oldSize(0, 0) {} }; /** - Constructor. + Widget position changed event. + @a pos The new absolute position of the widget. + @a oldPos The previous absolute position of the widget. + @see onPositionChanged */ - explicit Widget(Window& parent); + struct PositionChangedEvent { + Point pos; + Point oldPos; + /** Constructor */ + PositionChangedEvent() noexcept + : pos(0, 0), + oldPos(0, 0) {} + }; + +private: /** - Constructor for a subwidget. + Private constructor, reserved for TopLevelWidget class. */ - explicit Widget(Widget* groupWidget); + explicit Widget(TopLevelWidget* topLevelWidget); + /** + Private constructor, reserved for SubWidget class. + */ + explicit Widget(Widget* widgetToGroupTo); + +public: /** Destructor. */ @@ -199,9 +281,9 @@ public: bool isVisible() const noexcept; /** - Set widget visible (or not) according to @a yesNo. + Set widget visible (or not) according to @a visible. */ - void setVisible(bool yesNo); + void setVisible(bool visible); /** Show widget. @@ -228,7 +310,7 @@ public: /** Get size. */ - const Size& getSize() const noexcept; + const Size getSize() const noexcept; /** Set width. @@ -251,81 +333,58 @@ public: void setSize(const Size& size) noexcept; /** - Get absolute X. - */ - int getAbsoluteX() const noexcept; - - /** - Get absolute Y. - */ - int getAbsoluteY() const noexcept; - - /** - Get absolute position. - */ - const Point& getAbsolutePos() const noexcept; - - /** - Set absolute X. - */ - void setAbsoluteX(int x) noexcept; - - /** - Set absolute Y. - */ - void setAbsoluteY(int y) noexcept; - - /** - Set absolute position using @a x and @a y values. + Get the Id associated with this widget. + @see setId */ - void setAbsolutePos(int x, int y) noexcept; + uint getId() const noexcept; /** - Set absolute position. + Set an Id to be associated with this widget. + @see getId */ - void setAbsolutePos(const Point& pos) noexcept; + void setId(uint id) noexcept; /** - Get this widget's window application. - Same as calling getParentWindow().getApp(). + Get the application associated with this widget's window. + This is the same as calling `getTopLevelWidget()->getApp()`. */ - Application& getParentApp() const noexcept; + Application& getApp() const noexcept; /** - Get parent window, as passed in the constructor. + Get the window associated with this widget. + This is the same as calling `getTopLevelWidget()->getWindow()`. */ - Window& getParentWindow() const noexcept; + Window& getWindow() const noexcept; /** - Check if this widget contains the point defined by @a x and @a y. + Get the graphics context associated with this widget's window. + GraphicsContext is an empty struct and needs to be casted into a different type in order to be usable, + for example GraphicsContext. + @see CairoSubWidget, CairoTopLevelWidget */ - bool contains(int x, int y) const noexcept; + const GraphicsContext& getGraphicsContext() const noexcept; /** - Check if this widget contains the point @a pos. + Get top-level widget, as passed directly in the constructor + or going up the chain of group widgets until it finds the top-level one. */ - bool contains(const Point& pos) const noexcept; + TopLevelWidget* getTopLevelWidget() const noexcept; /** - Tell this widget's window to repaint itself. + Request repaint of this widget's area to the window this widget belongs to. + On the raw Widget class this function does nothing. */ - void repaint() noexcept; + virtual void repaint() noexcept; - /** - Get the Id associated with this widget. - @see setId - */ - uint getId() const noexcept; + DISTRHO_DEPRECATED_BY("getApp()") + Application& getParentApp() const noexcept { return getApp(); } - /** - Set an Id to be associated with this widget. - @see getId - */ - void setId(uint id) noexcept; + DISTRHO_DEPRECATED_BY("getWindow()") + Window& getParentWindow() const noexcept { return getWindow(); } protected: /** - A function called to draw the view contents with OpenGL. + A function called to draw the widget contents. */ virtual void onDisplay() = 0; @@ -336,10 +395,10 @@ protected: virtual bool onKeyboard(const KeyboardEvent&); /** - A function called when a special key is pressed or released. + A function called when an UTF-8 character is received. @return True to stop event propagation, false otherwise. */ - virtual bool onSpecial(const SpecialEvent&); + virtual bool onCharacterInput(const CharacterInputEvent&); /** A function called when a mouse button is pressed or released. @@ -364,25 +423,34 @@ protected: */ virtual void onResize(const ResizeEvent&); + /** + A function called when a special key is pressed or released. + DEPRECATED use onKeyboard or onCharacterInput + */ +#if defined(__clang__) +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wdeprecated-declarations" +#elif defined(__GNUC__) && (__GNUC__ * 100 + __GNUC_MINOR__) >= 460 +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wdeprecated-declarations" +#endif + virtual bool onSpecial(const SpecialEvent&) { return false; } +#if defined(__clang__) +# pragma clang diagnostic pop +#elif defined(__GNUC__) && (__GNUC__ * 100 + __GNUC_MINOR__) >= 460 +# pragma GCC diagnostic pop +#endif + private: struct PrivateData; PrivateData* const pData; - - /** @internal */ - explicit Widget(Widget* groupWidget, bool addToSubWidgets); - - friend class ImageSlider; - friend class NanoWidget; - friend class Window; - friend class StandaloneWindow; -#ifdef DISTRHO_DEFINES_H_INCLUDED - friend class DISTRHO_NAMESPACE::UI; -#endif + friend class SubWidget; + friend class TopLevelWidget; DISTRHO_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(Widget) }; -// ----------------------------------------------------------------------- +// -------------------------------------------------------------------------------------------------------------------- END_NAMESPACE_DGL diff --git a/source/modules/dgl/Window.hpp b/source/modules/dgl/Window.hpp index 182ae9728..51a7d2a39 100644 --- a/source/modules/dgl/Window.hpp +++ b/source/modules/dgl/Window.hpp @@ -1,6 +1,6 @@ /* * DISTRHO Plugin Framework (DPF) - * Copyright (C) 2012-2016 Filipe Coelho + * Copyright (C) 2012-2021 Filipe Coelho * * Permission to use, copy, modify, and/or distribute this software for any purpose with * or without fee is hereby granted, provided that the above copyright notice and this @@ -19,128 +19,498 @@ #include "Geometry.hpp" -#ifdef DISTRHO_DEFINES_H_INCLUDED -START_NAMESPACE_DISTRHO -class UIExporter; -END_NAMESPACE_DISTRHO +#ifndef DGL_FILE_BROWSER_DISABLED +# include "../distrho/extra/FileBrowserDialog.hpp" #endif START_NAMESPACE_DGL +class Application; +class PluginWindow; +class TopLevelWidget; + // ----------------------------------------------------------------------- -class Application; -class Widget; -class StandaloneWindow; +/** + DGL Window class. + + This is the where all OS-related events initially happen, before being propagated to any widgets. + + A Window MUST have an Application instance tied to it. + It is not possible to swap Application instances from within the lifetime of a Window. + But it is possible to completely change the Widgets that a Window contains during its lifetime. + + Typically the event handling functions as following: + Application -> Window -> Top-Level-Widget -> SubWidgets -class Window + Please note that, unlike many other graphical toolkits out there, + DGL makes a clear distinction between a Window and a Widget. + You cannot directly draw in a Window, you need to create a Widget for that. + + Also, a Window MUST have a single top-level Widget. + The Window will take care of global screen positioning and resizing, everything else is sent for widgets to handle. + + ... + */ +class DISTRHO_API Window { + struct PrivateData; + public: #ifndef DGL_FILE_BROWSER_DISABLED + typedef DISTRHO_NAMESPACE::FileBrowserHandle FileBrowserHandle; + typedef DISTRHO_NAMESPACE::FileBrowserOptions FileBrowserOptions; +#endif + /** - File browser options. - */ - struct FileBrowserOptions { - const char* startDir; - const char* title; - uint width; - uint height; - - /** - File browser buttons. - - 0 means hidden. - 1 means visible and unchecked. - 2 means visible and checked. - */ - struct Buttons { - uint listAllFiles; - uint showHidden; - uint showPlaces; - - /** Constuctor for default values */ - Buttons() - : listAllFiles(2), - showHidden(1), - showPlaces(1) {} - } buttons; - - /** Constuctor for default values */ - FileBrowserOptions() - : startDir(nullptr), - title(nullptr), - width(0), - height(0), - buttons() {} + Window graphics context as a scoped struct. + This class gives graphics context drawing time to a window's widgets. + Typically used for allowing OpenGL drawing operations during a window + widget constructor. + + Unless you are subclassing the Window or StandaloneWindow classes, you do not need to care. + In such cases you will need to use this struct as a way to get a valid OpenGL context. + For example in a standalone application: + ``` + int main() + { + Application app; + Window win(app); + ScopedPointer widget; + { + const Window::ScopedGraphicsContext sgc(win); + widget = new MyCustomTopLevelWidget(win); + } + app.exec(); + return 0; + } + ``` + + This struct is necessary because we cannot automatically make the window leave the OpenGL context in custom code. + And we must always cleanly enter and leave the OpenGL context. + So in order to avoid messing up the global host context, this class is used around widget creation. + */ + struct ScopedGraphicsContext + { + /** Constructor that will make the @a window graphics context the current one */ + explicit ScopedGraphicsContext(Window& window); + + /** Overloaded constructor, gives back context to its transient parent when done */ + explicit ScopedGraphicsContext(Window& window, Window& transientParentWindow); + + /** Desstructor for clearing current context, if not done yet */ + ~ScopedGraphicsContext(); + + /** Early context clearing, useful for standalone windows not created by you. */ + void done(); + + DISTRHO_DECLARE_NON_COPYABLE(ScopedGraphicsContext) + DISTRHO_PREVENT_HEAP_ALLOCATION + + private: + Window& window; + Window::PrivateData* ppData; + bool active; }; -#endif // DGL_FILE_BROWSER_DISABLED + /** + Constructor for a regular, standalone window. + */ explicit Window(Application& app); - explicit Window(Application& app, Window& parent); - explicit Window(Application& app, intptr_t parentId); + + /** + Constructor for a modal window, by having another window as its transient parent. + The Application instance must be the same between the 2 windows. + */ + explicit Window(Application& app, Window& transientParentWindow); + + /** + Constructor for an embed Window without known size, + typically used in modules or plugins that run inside another host. + */ + explicit Window(Application& app, + uintptr_t parentWindowHandle, + double scaleFactor, + bool resizable); + + /** + Constructor for an embed Window with known size, + typically used in modules or plugins that run inside another host. + */ + explicit Window(Application& app, + uintptr_t parentWindowHandle, + uint width, + uint height, + double scaleFactor, + bool resizable); + + /** + Destructor. + */ virtual ~Window(); + /** + Whether this Window is embed into another (usually not DGL-controlled) Window. + */ + bool isEmbed() const noexcept; + + /** + Check if this window is visible / mapped. + Invisible windows do not receive events except resize. + @see setVisible(bool) + */ + bool isVisible() const noexcept; + + /** + Set window visible (or not) according to @a visible. + Only valid for standalones, embed windows are always visible. + @see isVisible(), hide(), show() + */ + void setVisible(bool visible); + + /** + Show window. + This is the same as calling setVisible(true). + @see isVisible(), setVisible(bool) + */ void show(); + + /** + Hide window. + This is the same as calling setVisible(false). + @see isVisible(), setVisible(bool) + */ void hide(); + + /** + Hide window and notify application of a window close event. + The application event-loop will stop when all windows have been closed. + For standalone windows only, has no effect if window is embed. + @see isEmbed() + + @note It is possible to hide the window while not stopping the event-loop. + A closed window is always hidden, but the reverse is not always true. + */ void close(); - void exec(bool lockWait = false); - void focus(); - void repaint() noexcept; + /** + Check if this window is resizable (by the user or window manager). + @see setResizable + */ + bool isResizable() const noexcept; -#ifndef DGL_FILE_BROWSER_DISABLED - bool openFileBrowser(const FileBrowserOptions& options); -#endif + /** + Set window as resizable (by the user or window manager). + It is always possible to resize a window programmatically, which is not the same as the user being allowed to it. + @note This function does nothing for plugins, where the resizable state is set via macro. + @see DISTRHO_UI_USER_RESIZABLE + */ + void setResizable(bool resizable); - bool isVisible() const noexcept; - void setVisible(bool yesNo); + /** + Get X offset, typically 0. + */ + int getOffsetX() const noexcept; - bool isResizable() const noexcept; - void setResizable(bool yesNo); + /** + Get Y offset, typically 0. + */ + int getOffsetY() const noexcept; + + /** + Get offset. + */ + Point getOffset() const noexcept; + + /** + Set X offset. + */ + void setOffsetX(int x); + /** + Set Y offset. + */ + void setOffsetY(int y); + + /** + Set offset using @a x and @a y values. + */ + void setOffset(int x, int y); + + /** + Set offset. + */ + void setOffset(const Point& offset); + + /** + Get width. + */ uint getWidth() const noexcept; + + /** + Get height. + */ uint getHeight() const noexcept; + + /** + Get size. + */ Size getSize() const noexcept; + + /** + Set width. + */ + void setWidth(uint width); + + /** + Set height. + */ + void setHeight(uint height); + + /** + Set size using @a width and @a height values. + */ void setSize(uint width, uint height); - void setSize(Size size); + /** + Set size. + */ + void setSize(const Size& size); + + /** + Get the title of the window previously set with setTitle(). + */ const char* getTitle() const noexcept; + + /** + Set the title of the window, typically displayed in the title bar or in window switchers. + + This only makes sense for non-embedded windows. + */ void setTitle(const char* title); - void setTransientWinId(uintptr_t winId); + /** + Check if key repeat events are ignored. + */ + bool isIgnoringKeyRepeat() const noexcept; + + /** + Set to ignore (or not) key repeat events according to @a ignore. + */ + void setIgnoringKeyRepeat(bool ignore) noexcept; + + /** + Set the clipboard contents. + + This sets the system clipboard contents, + which can be retrieved with getClipboard() or pasted into other applications. + + If using a string, the use of a null terminator is required (and must be part of dataSize).@n + The MIME type of the data "text/plain" is assumed if null is used. + */ + bool setClipboard(const char* mimeType, const void* data, size_t dataSize); + + /** + Get the clipboard contents. + + This gets the system clipboard contents, + which may have been set with setClipboard() or copied from another application. + + returns the clipboard contents, or null. + */ + const void* getClipboard(const char*& mimeType, size_t& dataSize); + + /** + Set the mouse cursor. + + This changes the system cursor that is displayed when the pointer is inside the window. + May fail if setting the cursor is not supported on this system, + for example if compiled on X11 without Xcursor support. + */ + bool setCursor(MouseCursor cursor); + + /** + Add a callback function to be triggered on every idle cycle or on a specific timer frequency. + You can add more than one, and remove them at anytime with removeIdleCallback(). + This can be used to perform some action at a regular interval with relatively low frequency. + If providing a timer frequency, there are a few things to note: + 1. There is a platform-specific limit to the number of supported timers, and overhead associated with each, + so you should create only a few timers and perform several tasks in one if necessary. + 2. This timer frequency is not guaranteed to have a resolution better than 10ms + (the maximum timer resolution on Windows) and may be rounded up if it is too short. + On X11 and MacOS, a resolution of about 1ms can usually be relied on. + */ + bool addIdleCallback(IdleCallback* callback, uint timerFrequencyInMs = 0); + + /** + Remove an idle callback previously added via addIdleCallback(). + */ + bool removeIdleCallback(IdleCallback* callback); + + /** + Get the application associated with this window. + */ Application& getApp() const noexcept; - intptr_t getWindowId() const noexcept; - void addIdleCallback(IdleCallback* const callback); - void removeIdleCallback(IdleCallback* const callback); + /** + Get the graphics context associated with this window. + GraphicsContext is an empty struct and needs to be casted into a different type in order to be usable, + for example GraphicsContext. + @see CairoSubWidget, CairoTopLevelWidget + */ + const GraphicsContext& getGraphicsContext() const noexcept; + + /** + Get the "native" window handle. + Returned value depends on the platform: + - HaikuOS: This is a pointer to a `BView`. + - MacOS: This is a pointer to an `NSView*`. + - Windows: This is a `HWND`. + - Everything else: This is an [X11] `Window`. + */ + uintptr_t getNativeWindowHandle() const noexcept; + + /** + Get the scale factor requested for this window. + This is purely informational, and up to developers to choose what to do with it. + + If you do not want to deal with this yourself, + consider using setGeometryConstraints() where you can specify to automatically scale the window contents. + @see setGeometryConstraints + */ + double getScaleFactor() const noexcept; + + /** + Grab the keyboard input focus. + */ + void focus(); + +#ifndef DGL_FILE_BROWSER_DISABLED + /** + Open a file browser dialog with this window as transient parent. + A few options can be specified to setup the dialog. + + If a path is selected, onFileSelected() will be called with the user chosen path. + If the user cancels or does not pick a file, onFileSelected() will be called with nullptr as filename. + + This function does not block the event loop. + */ + bool openFileBrowser(const FileBrowserOptions& options = FileBrowserOptions()); +#endif + + /** + Request repaint of this window, for the entire area. + */ + void repaint() noexcept; + + /** + Request partial repaint of this window, with bounds according to @a rect. + */ + void repaint(const Rectangle& rect) noexcept; + + /** + Render this window's content into a picture file, specified by @a filename. + Window must be visible and on screen. + Written picture format is PPM. + */ + void renderToPicture(const char* filename); + + /** + Run this window as a modal, blocking input events from the parent. + Only valid for windows that have been created with another window as parent (as passed in the constructor). + Can optionally block-wait, but such option is only available if the application is running as standalone. + */ + void runAsModal(bool blockWait = false); + + /** + Get the geometry constraints set for the Window. + @see setGeometryConstraints + */ + Size getGeometryConstraints(bool& keepAspectRatio); + + /** + Set geometry constraints for the Window when resized by the user, and optionally scale contents automatically. + */ + void setGeometryConstraints(uint minimumWidth, + uint minimumHeight, + bool keepAspectRatio = false, + bool automaticallyScale = false, + bool resizeNowIfAutoScaling = true); + + /** DEPRECATED Use isIgnoringKeyRepeat(). */ + DISTRHO_DEPRECATED_BY("isIgnoringKeyRepeat()") + inline bool getIgnoringKeyRepeat() const noexcept { return isIgnoringKeyRepeat(); } + + /** DEPRECATED Use getScaleFactor(). */ + DISTRHO_DEPRECATED_BY("getScaleFactor()") + inline double getScaling() const noexcept { return getScaleFactor(); } + + /** DEPRECATED Use runAsModal(bool). */ + DISTRHO_DEPRECATED_BY("runAsModal(bool)") + inline void exec(bool blockWait = false) { runAsModal(blockWait); } protected: - virtual void onDisplayBefore(); - virtual void onDisplayAfter(); + /** + A function called when the window is attempted to be closed. + Returning true closes the window, which is the default behaviour. + Override this method and return false to prevent the window from being closed by the user. + + This method is not used for embed windows, and not even made available in DISTRHO_NAMESPACE::UI. + For embed windows, closing is handled by the host/parent process and we have no control over it. + As such, a close action on embed windows will always succeed and cannot be cancelled. + + NOTE: This currently does not work under macOS. + */ + virtual bool onClose(); + + /** + A function called when the window gains or loses the keyboard focus. + The default implementation does nothing. + */ + virtual void onFocus(bool focus, CrossingMode mode); + + /** + A function called when the window is resized. + If there is a top-level widget associated with this window, its size will be set right after this function. + The default implementation sets up drawing context where necessary. + */ virtual void onReshape(uint width, uint height); - virtual void onClose(); + + /** + A function called when scale factor requested for this window changes. + The default implementation does nothing. + WARNING function needs a proper name + */ + virtual void onScaleFactorChanged(double scaleFactor); #ifndef DGL_FILE_BROWSER_DISABLED - virtual void fileBrowserSelected(const char* filename); + /** + A function called when a path is selected by the user, as triggered by openFileBrowser(). + This action happens after the user confirms the action, so the file browser dialog will be closed at this point. + The default implementation does nothing. + */ + virtual void onFileSelected(const char* filename); + + /** DEPRECATED Use onFileSelected(). */ + DISTRHO_DEPRECATED_BY("onFileSelected(const char*)") + inline virtual void fileBrowserSelected(const char* filename) { return onFileSelected(filename); } #endif private: - struct PrivateData; PrivateData* const pData; friend class Application; - friend class Widget; - friend class StandaloneWindow; -#ifdef DISTRHO_DEFINES_H_INCLUDED - friend class DISTRHO_NAMESPACE::UIExporter; -#endif - - virtual void _addWidget(Widget* const widget); - virtual void _removeWidget(Widget* const widget); - void _idle(); + friend class PluginWindow; + friend class TopLevelWidget; - bool handlePluginKeyboard(const bool press, const uint key); - bool handlePluginSpecial(const bool press, const Key key); + /** @internal */ + explicit Window(Application& app, + uintptr_t parentWindowHandle, + uint width, + uint height, + double scaleFactor, + bool resizable, + bool isVST3, + bool doPostInit); DISTRHO_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(Window) }; diff --git a/source/modules/dgl/src/Application.cpp b/source/modules/dgl/src/Application.cpp index 6c10f26a7..5d6529a93 100644 --- a/source/modules/dgl/src/Application.cpp +++ b/source/modules/dgl/src/Application.cpp @@ -1,6 +1,6 @@ /* * DISTRHO Plugin Framework (DPF) - * Copyright (C) 2012-2016 Filipe Coelho + * Copyright (C) 2012-2021 Filipe Coelho * * Permission to use, copy, modify, and/or distribute this software for any purpose with * or without fee is hereby granted, provided that the above copyright notice and this @@ -14,19 +14,14 @@ * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ -#ifdef __WINE__ -# include "winsock2.h" -#endif - #include "ApplicationPrivateData.hpp" -#include "../Window.hpp" START_NAMESPACE_DGL -// ----------------------------------------------------------------------- +// -------------------------------------------------------------------------------------------------------------------- -Application::Application() - : pData(new PrivateData()) {} +Application::Application(const bool isStandalone) + : pData(new PrivateData(isStandalone)) {} Application::~Application() { @@ -35,44 +30,56 @@ Application::~Application() void Application::idle() { - for (std::list::iterator it = pData->windows.begin(), ite = pData->windows.end(); it != ite; ++it) - { - Window* const window(*it); - window->_idle(); - } - - for (std::list::iterator it = pData->idleCallbacks.begin(), ite = pData->idleCallbacks.end(); it != ite; ++it) - { - IdleCallback* const idleCallback(*it); - idleCallback->idleCallback(); - } + pData->idle(0); } -void Application::exec(unsigned int idleTime) +void Application::exec(const uint idleTimeInMs) { - for (; pData->doLoop;) - { - idle(); - d_msleep(idleTime); - } + DISTRHO_SAFE_ASSERT_RETURN(pData->isStandalone,); + + while (! pData->isQuitting) + pData->idle(idleTimeInMs); } void Application::quit() { - pData->doLoop = false; + pData->quit(); +} + +bool Application::isQuitting() const noexcept +{ + return pData->isQuitting || pData->isQuittingInNextCycle; +} + +bool Application::isStandalone() const noexcept +{ + return pData->isStandalone; +} + +double Application::getTime() const +{ + return pData->getTime(); +} + +void Application::addIdleCallback(IdleCallback* const callback) +{ + DISTRHO_SAFE_ASSERT_RETURN(callback != nullptr,) + + pData->idleCallbacks.push_back(callback); +} + +void Application::removeIdleCallback(IdleCallback* const callback) +{ + DISTRHO_SAFE_ASSERT_RETURN(callback != nullptr,) - for (std::list::reverse_iterator rit = pData->windows.rbegin(), rite = pData->windows.rend(); rit != rite; ++rit) - { - Window* const window(*rit); - window->close(); - } + pData->idleCallbacks.remove(callback); } -bool Application::isQuiting() const noexcept +void Application::setClassName(const char* const name) { - return !pData->doLoop; + pData->setClassName(name); } -// ----------------------------------------------------------------------- +// -------------------------------------------------------------------------------------------------------------------- END_NAMESPACE_DGL diff --git a/source/modules/dgl/src/ApplicationPrivateData.cpp b/source/modules/dgl/src/ApplicationPrivateData.cpp new file mode 100644 index 000000000..f54535fc1 --- /dev/null +++ b/source/modules/dgl/src/ApplicationPrivateData.cpp @@ -0,0 +1,172 @@ +/* + * DISTRHO Plugin Framework (DPF) + * Copyright (C) 2012-2021 Filipe Coelho + * + * Permission to use, copy, modify, and/or distribute this software for any purpose with + * or without fee is hereby granted, provided that the above copyright notice and this + * permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD + * TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN + * NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER + * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "ApplicationPrivateData.hpp" +#include "../Window.hpp" + +#include "pugl.hpp" + +#include + +START_NAMESPACE_DGL + +typedef std::list::reverse_iterator WindowListReverseIterator; + +static d_ThreadHandle getCurrentThreadHandle() noexcept +{ +#ifdef DISTRHO_OS_WINDOWS + return GetCurrentThread(); +#else + return pthread_self(); +#endif +} + +static bool isThisTheMainThread(const d_ThreadHandle mainThreadHandle) noexcept +{ +#ifdef DISTRHO_OS_WINDOWS + return GetCurrentThread() == mainThreadHandle; // IsGUIThread ? +#else + return pthread_equal(getCurrentThreadHandle(), mainThreadHandle) != 0; +#endif +} + +// -------------------------------------------------------------------------------------------------------------------- + +Application::PrivateData::PrivateData(const bool standalone) + : world(puglNewWorld(standalone ? PUGL_PROGRAM : PUGL_MODULE, + standalone ? PUGL_WORLD_THREADS : 0x0)), + isStandalone(standalone), + isQuitting(false), + isQuittingInNextCycle(false), + isStarting(true), + visibleWindows(0), + mainThreadHandle(getCurrentThreadHandle()), + windows(), + idleCallbacks() +{ + DISTRHO_SAFE_ASSERT_RETURN(world != nullptr,); + + puglSetWorldHandle(world, this); + puglSetClassName(world, DISTRHO_MACRO_AS_STRING(DGL_NAMESPACE)); + +#ifdef DISTRHO_OS_MAC + if (standalone) + puglMacOSActivateApp(); +#endif +} + +Application::PrivateData::~PrivateData() +{ + DISTRHO_SAFE_ASSERT(isStarting || isQuitting); + DISTRHO_SAFE_ASSERT(visibleWindows == 0); + + windows.clear(); + idleCallbacks.clear(); + + if (world != nullptr) + puglFreeWorld(world); +} + +// -------------------------------------------------------------------------------------------------------------------- + +void Application::PrivateData::oneWindowShown() noexcept +{ + if (++visibleWindows == 1) + { + isQuitting = false; + isStarting = false; + } +} + +void Application::PrivateData::oneWindowClosed() noexcept +{ + DISTRHO_SAFE_ASSERT_RETURN(visibleWindows != 0,); + + if (--visibleWindows == 0) + isQuitting = true; +} + +// -------------------------------------------------------------------------------------------------------------------- + +void Application::PrivateData::idle(const uint timeoutInMs) +{ + if (isQuittingInNextCycle) + { + quit(); + isQuittingInNextCycle = false; + } + + if (world != nullptr) + { + const double timeoutInSeconds = timeoutInMs != 0 + ? static_cast(timeoutInMs) / 1000.0 + : 0.0; + + puglUpdate(world, timeoutInSeconds); + } + + triggerIdleCallbacks(); +} + +void Application::PrivateData::triggerIdleCallbacks() +{ + for (std::list::iterator it = idleCallbacks.begin(), ite = idleCallbacks.end(); it != ite; ++it) + { + IdleCallback* const idleCallback(*it); + idleCallback->idleCallback(); + } +} + +void Application::PrivateData::quit() +{ + if (! isThisTheMainThread(mainThreadHandle)) + { + if (! isQuittingInNextCycle) + { + isQuittingInNextCycle = true; + return; + } + } + + isQuitting = true; + +#ifndef DPF_TEST_APPLICATION_CPP + for (WindowListReverseIterator rit = windows.rbegin(), rite = windows.rend(); rit != rite; ++rit) + { + DGL_NAMESPACE::Window* const window(*rit); + window->close(); + } +#endif +} + +double Application::PrivateData::getTime() const +{ + DISTRHO_SAFE_ASSERT_RETURN(world != nullptr, 0.0); + + return puglGetTime(world); +} + +void Application::PrivateData::setClassName(const char* const name) +{ + DISTRHO_SAFE_ASSERT_RETURN(world != nullptr,); + DISTRHO_SAFE_ASSERT_RETURN(name != nullptr && name[0] != '\0',); + + puglSetClassName(world, name); +} + +// -------------------------------------------------------------------------------------------------------------------- + +END_NAMESPACE_DGL diff --git a/source/modules/dgl/src/ApplicationPrivateData.hpp b/source/modules/dgl/src/ApplicationPrivateData.hpp index e95d44a7e..0e33c9851 100644 --- a/source/modules/dgl/src/ApplicationPrivateData.hpp +++ b/source/modules/dgl/src/ApplicationPrivateData.hpp @@ -1,6 +1,6 @@ /* * DISTRHO Plugin Framework (DPF) - * Copyright (C) 2012-2016 Filipe Coelho + * Copyright (C) 2012-2021 Filipe Coelho * * Permission to use, copy, modify, and/or distribute this software for any purpose with * or without fee is hereby granted, provided that the above copyright notice and this @@ -18,53 +18,98 @@ #define DGL_APP_PRIVATE_DATA_HPP_INCLUDED #include "../Application.hpp" -#include "../../distrho/extra/Sleep.hpp" #include +#ifdef DISTRHO_OS_WINDOWS +# ifndef NOMINMAX +# define NOMINMAX +# endif +# include +# include +typedef HANDLE d_ThreadHandle; +#else +# include +typedef pthread_t d_ThreadHandle; +#endif + +#ifdef DISTRHO_OS_MAC +typedef struct PuglWorldImpl PuglWorld; +#endif + START_NAMESPACE_DGL -// ----------------------------------------------------------------------- +class Window; + +#ifndef DISTRHO_OS_MAC +typedef struct PuglWorldImpl PuglWorld; +#endif + +// -------------------------------------------------------------------------------------------------------------------- struct Application::PrivateData { - bool doLoop; + /** Pugl world instance. */ + PuglWorld* const world; + + /** Whether the application is running as standalone, otherwise it is part of a plugin. */ + const bool isStandalone; + + /** Whether the applicating is about to quit, or already stopped. Defaults to false. */ + bool isQuitting; + + /** Helper for safely close everything from main thread. */ + bool isQuittingInNextCycle; + + /** Whether the applicating is starting up, that is, no windows have been made visible yet. Defaults to true. */ + bool isStarting; + + /** Counter of visible windows, only used in standalone mode. + If 0->1, application is starting. If 1->0, application is quitting/stopping. */ uint visibleWindows; - std::list windows; - std::list idleCallbacks; - - PrivateData() - : doLoop(true), - visibleWindows(0), - windows(), - idleCallbacks() {} - - ~PrivateData() - { - DISTRHO_SAFE_ASSERT(! doLoop); - DISTRHO_SAFE_ASSERT(visibleWindows == 0); - - windows.clear(); - idleCallbacks.clear(); - } - - void oneShown() noexcept - { - if (++visibleWindows == 1) - doLoop = true; - } - - void oneHidden() noexcept - { - DISTRHO_SAFE_ASSERT_RETURN(visibleWindows > 0,); - - if (--visibleWindows == 0) - doLoop = false; - } - - DISTRHO_DECLARE_NON_COPY_STRUCT(PrivateData) + + /** Handle that identifies the main thread. Used to check if calls belong to current thread or not. */ + d_ThreadHandle mainThreadHandle; + + /** List of windows for this application. Only used during `close`. */ + std::list windows; + + /** List of idle callbacks for this application. */ + std::list idleCallbacks; + + /** Constructor and destructor */ + explicit PrivateData(bool standalone); + ~PrivateData(); + + /** Flag one window as shown, which increments @a visibleWindows. + Sets @a isQuitting and @a isStarting as false if this is the first window. + For standalone mode only. */ + void oneWindowShown() noexcept; + + /** Flag one window as closed, which decrements @a visibleWindows. + Sets @a isQuitting as true if this is the last window. + For standalone mode only. */ + void oneWindowClosed() noexcept; + + /** Run Pugl world update for @a timeoutInMs, and then each idle callback in order of registration. */ + void idle(uint timeoutInMs); + + /** Run each idle callback without updating pugl world. */ + void triggerIdleCallbacks(); + + /** Set flag indicating application is quitting, and close all windows in reverse order of registration. + For standalone mode only. */ + void quit(); + + /** Get time via pugl */ + double getTime() const; + + /** Set pugl world class name. */ + void setClassName(const char* name); + + DISTRHO_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(PrivateData) }; -// ----------------------------------------------------------------------- +// -------------------------------------------------------------------------------------------------------------------- END_NAMESPACE_DGL diff --git a/source/modules/dgl/src/Color.cpp b/source/modules/dgl/src/Color.cpp index 2ea9819f6..10382fbd1 100644 --- a/source/modules/dgl/src/Color.cpp +++ b/source/modules/dgl/src/Color.cpp @@ -1,6 +1,6 @@ /* * DISTRHO Plugin Framework (DPF) - * Copyright (C) 2012-2016 Filipe Coelho + * Copyright (C) 2012-2021 Filipe Coelho * * Permission to use, copy, modify, and/or distribute this software for any purpose with * or without fee is hereby granted, provided that the above copyright notice and this @@ -16,12 +16,23 @@ #include "../Color.hpp" -#include "nanovg/nanovg.h" - START_NAMESPACE_DGL // ----------------------------------------------------------------------- +static float computeHue(float h, float m1, float m2) +{ + if (h < 0) h += 1; + if (h > 1) h -= 1; + if (h < 1.0f/6.0f) + return m1 + (m2 - m1) * h * 6.0f; + if (h < 3.0f/6.0f) + return m2; + if (h < 4.0f/6.0f) + return m1 + (m2 - m1) * (2.0f/3.0f - h) * 6.0f; + return m1; +} + static void fixRange(float& value) { /**/ if (value < 0.0f) @@ -46,27 +57,27 @@ static uchar getFixedRange2(const float& value) return 0; if (value2 >= 255.0f) return 255; - return static_cast(value2); + return static_cast(value2 + 0.5f); } // ----------------------------------------------------------------------- Color::Color() noexcept - : red(1.0f), - green(1.0f), - blue(1.0f), + : red(0.0f), + green(0.0f), + blue(0.0f), alpha(1.0f) {} -Color::Color(int r, int g, int b, int a) noexcept +Color::Color(const int r, const int g, const int b, const float a) noexcept : red(static_cast(r)/255.0f), green(static_cast(g)/255.0f), blue(static_cast(b)/255.0f), - alpha(static_cast(a)/255.0f) + alpha(a) { fixBounds(); } -Color::Color(float r, float g, float b, float a) noexcept +Color::Color(const float r, const float g, const float b, const float a) noexcept : red(r), green(g), blue(b), @@ -94,7 +105,7 @@ Color& Color::operator=(const Color& color) noexcept return *this; } -Color::Color(const Color& color1, const Color& color2, float u) noexcept +Color::Color(const Color& color1, const Color& color2, const float u) noexcept : red(color1.red), green(color1.green), blue(color1.blue), @@ -103,70 +114,91 @@ Color::Color(const Color& color1, const Color& color2, float u) noexcept interpolate(color2, u); } +Color Color::withAlpha(const float alpha2) noexcept +{ + Color color(*this); + color.alpha = alpha2; + return color; +} + Color Color::fromHSL(float hue, float saturation, float lightness, float alpha) { - return nvgHSLA(hue, saturation, lightness, static_cast(getFixedRange(alpha)*255.0f)); + float m1, m2; + Color col; + hue = fmodf(hue, 1.0f); + if (hue < 0.0f) hue += 1.0f; + fixRange(saturation); + fixRange(lightness); + m2 = lightness <= 0.5f ? (lightness * (1 + saturation)) : (lightness + saturation - lightness * saturation); + m1 = 2 * lightness - m2; + col.red = computeHue(hue + 1.0f/3.0f, m1, m2); + col.green = computeHue(hue, m1, m2); + col.blue = computeHue(hue - 1.0f/3.0f, m1, m2); + col.alpha = alpha; + col.fixBounds(); + return col; } -Color Color::fromHTML(const char* rgb, float alpha) +Color Color::fromHTML(const char* rgb, const float alpha) noexcept { Color fallback; DISTRHO_SAFE_ASSERT_RETURN(rgb != nullptr && rgb[0] != '\0', fallback); - if (rgb[0] == '#') ++rgb; + if (rgb[0] == '#') + ++rgb; DISTRHO_SAFE_ASSERT_RETURN(rgb[0] != '\0', fallback); - std::size_t rgblen(std::strlen(rgb)); + std::size_t rgblen = std::strlen(rgb); DISTRHO_SAFE_ASSERT_RETURN(rgblen == 3 || rgblen == 6, fallback); - char rgbtmp[3] = { '\0', '\0', '\0' }; + char rgbtmp[5] = { '0', 'x', '\0', '\0', '\0' }; int r, g, b; if (rgblen == 3) { - rgbtmp[0] = rgb[0]; - r = static_cast(std::strtol(rgbtmp, nullptr, 16)); + rgbtmp[2] = rgb[0]; + r = static_cast(std::strtol(rgbtmp, nullptr, 16)) * 17; - rgbtmp[0] = rgb[1]; - g = static_cast(std::strtol(rgbtmp, nullptr, 16)); + rgbtmp[2] = rgb[1]; + g = static_cast(std::strtol(rgbtmp, nullptr, 16)) * 17; - rgbtmp[0] = rgb[2]; - b = static_cast(std::strtol(rgbtmp, nullptr, 16)); + rgbtmp[2] = rgb[2]; + b = static_cast(std::strtol(rgbtmp, nullptr, 16)) * 17; } else { - rgbtmp[0] = rgb[0]; - rgbtmp[1] = rgb[1]; + rgbtmp[2] = rgb[0]; + rgbtmp[3] = rgb[1]; r = static_cast(std::strtol(rgbtmp, nullptr, 16)); - rgbtmp[0] = rgb[2]; - rgbtmp[1] = rgb[3]; + rgbtmp[2] = rgb[2]; + rgbtmp[3] = rgb[3]; g = static_cast(std::strtol(rgbtmp, nullptr, 16)); - rgbtmp[0] = rgb[4]; - rgbtmp[1] = rgb[5]; + rgbtmp[2] = rgb[4]; + rgbtmp[3] = rgb[5]; b = static_cast(std::strtol(rgbtmp, nullptr, 16)); } - return Color(r, g, b, static_cast(getFixedRange(alpha)*255.0f)); + return Color(r, g, b, alpha); } void Color::interpolate(const Color& other, float u) noexcept { fixRange(u); - const float oneMinusU(1.0f - u); + const float oneMinusU = 1.0f - u; - red = red * oneMinusU + other.red * u; - green = green * oneMinusU + other.green * u; - blue = blue * oneMinusU + other.blue * u; - alpha = alpha * oneMinusU + other.alpha * u; + red = (red * oneMinusU) + (other.red * u); + green = (green * oneMinusU) + (other.green * u); + blue = (blue * oneMinusU) + (other.blue * u); + alpha = (alpha * oneMinusU) + (other.alpha * u); fixBounds(); } // ----------------------------------------------------------------------- -bool Color::isEqual(const Color& color, bool withAlpha) noexcept +bool Color::isEqual(const Color& color, const bool withAlpha) noexcept { const uchar r1 = getFixedRange2(rgba[0]); const uchar g1 = getFixedRange2(rgba[1]); @@ -184,7 +216,7 @@ bool Color::isEqual(const Color& color, bool withAlpha) noexcept return (r1 == r2 && g1 == g2 && b1 == b2); } -bool Color::isNotEqual(const Color& color, bool withAlpha) noexcept +bool Color::isNotEqual(const Color& color, const bool withAlpha) noexcept { const uchar r1 = getFixedRange2(rgba[0]); const uchar g1 = getFixedRange2(rgba[1]); @@ -224,22 +256,4 @@ void Color::fixBounds() noexcept // ----------------------------------------------------------------------- -Color::Color(const NVGcolor& c) noexcept - : red(c.r), green(c.g), blue(c.b), alpha(c.a) -{ - fixBounds(); -} - -Color::operator NVGcolor() const noexcept -{ - NVGcolor nc; - nc.r = red; - nc.g = green; - nc.b = blue; - nc.a = alpha; - return nc; -} - -// ----------------------------------------------------------------------- - END_NAMESPACE_DGL diff --git a/source/modules/dgl/src/Common.hpp b/source/modules/dgl/src/Common.hpp deleted file mode 100644 index fd1070a09..000000000 --- a/source/modules/dgl/src/Common.hpp +++ /dev/null @@ -1,124 +0,0 @@ -/* - * DISTRHO Plugin Framework (DPF) - * Copyright (C) 2012-2016 Filipe Coelho - * - * Permission to use, copy, modify, and/or distribute this software for any purpose with - * or without fee is hereby granted, provided that the above copyright notice and this - * permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD - * TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN - * NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL - * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER - * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN - * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#ifndef DGL_COMMON_HPP_INCLUDED -#define DGL_COMMON_HPP_INCLUDED - -#include "../ImageWidgets.hpp" - -START_NAMESPACE_DGL - -// ----------------------------------------------------------------------- - -struct ButtonImpl { - enum State { - kStateNormal = 0, - kStateHover, - kStateDown - }; - - int button; - int state; - Widget* self; - - ImageButton::Callback* callback_img; - - ButtonImpl(Widget* const s) noexcept - : button(-1), - state(kStateNormal), - self(s), - callback_img(nullptr) {} - - bool onMouse(const Widget::MouseEvent& ev) - { - // button was released, handle it now - if (button != -1 && ! ev.press) - { - DISTRHO_SAFE_ASSERT(state == kStateDown); - - // release button - const int button2 = button; - button = -1; - - // cursor was moved outside the button bounds, ignore click - if (! self->contains(ev.pos)) - { - state = kStateNormal; - self->repaint(); - return true; - } - - // still on bounds, register click - state = kStateHover; - self->repaint(); - - if (callback_img != nullptr) - callback_img->imageButtonClicked((ImageButton*)self, button2); - - return true; - } - - // button was pressed, wait for release - if (ev.press && self->contains(ev.pos)) - { - button = ev.button; - state = kStateDown; - self->repaint(); - return true; - } - - return false; - } - - bool onMotion(const Widget::MotionEvent& ev) - { - // keep pressed - if (button != -1) - return true; - - if (self->contains(ev.pos)) - { - // check if entering hover - if (state == kStateNormal) - { - state = kStateHover; - self->repaint(); - return true; - } - } - else - { - // check if exiting hover - if (state == kStateHover) - { - state = kStateNormal; - self->repaint(); - return true; - } - } - - return false; - } - - DISTRHO_PREVENT_HEAP_ALLOCATION - DISTRHO_DECLARE_NON_COPY_STRUCT(ButtonImpl) -}; - -// ----------------------------------------------------------------------- - -END_NAMESPACE_DGL - -#endif // DGL_APP_PRIVATE_DATA_HPP_INCLUDED diff --git a/source/modules/dgl/src/EventHandlers.cpp b/source/modules/dgl/src/EventHandlers.cpp new file mode 100644 index 000000000..64f81ca35 --- /dev/null +++ b/source/modules/dgl/src/EventHandlers.cpp @@ -0,0 +1,632 @@ +/* + * DISTRHO Plugin Framework (DPF) + * Copyright (C) 2012-2021 Filipe Coelho + * + * Permission to use, copy, modify, and/or distribute this software for any purpose with + * or without fee is hereby granted, provided that the above copyright notice and this + * permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD + * TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN + * NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER + * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "../EventHandlers.hpp" +#include "../SubWidget.hpp" + +START_NAMESPACE_DGL + +// -------------------------------------------------------------------------------------------------------------------- + +struct ButtonEventHandler::PrivateData { + ButtonEventHandler* const self; + SubWidget* const widget; + ButtonEventHandler::Callback* internalCallback; + ButtonEventHandler::Callback* userCallback; + + int button; + int state; + bool checkable; + bool checked; + + Point lastClickPos; + Point lastMotionPos; + + PrivateData(ButtonEventHandler* const s, SubWidget* const w) + : self(s), + widget(w), + internalCallback(nullptr), + userCallback(nullptr), + button(-1), + state(kButtonStateDefault), + checkable(false), + checked(false), + lastClickPos(0, 0), + lastMotionPos(0, 0) {} + + bool mouseEvent(const Widget::MouseEvent& ev) + { + lastClickPos = ev.pos; + + // button was released, handle it now + if (button != -1 && ! ev.press) + { + DISTRHO_SAFE_ASSERT(state & kButtonStateActive); + + // release button + const int button2 = button; + button = -1; + + const int state2 = state; + state &= ~kButtonStateActive; + + self->stateChanged(static_cast(state), static_cast(state2)); + widget->repaint(); + + // cursor was moved outside the button bounds, ignore click + if (! widget->contains(ev.pos)) + return true; + + // still on bounds, register click + if (checkable) + checked = !checked; + + if (internalCallback != nullptr) + internalCallback->buttonClicked(widget, button2); + else if (userCallback != nullptr) + userCallback->buttonClicked(widget, button2); + + return true; + } + + // button was pressed, wait for release + if (ev.press && widget->contains(ev.pos)) + { + const int state2 = state; + button = static_cast(ev.button); + state |= kButtonStateActive; + self->stateChanged(static_cast(state), static_cast(state2)); + widget->repaint(); + return true; + } + + return false; + } + + bool motionEvent(const Widget::MotionEvent& ev) + { + // keep pressed + if (button != -1) + { + lastMotionPos = ev.pos; + return true; + } + + bool ret = false; + + if (widget->contains(ev.pos)) + { + // check if entering hover + if ((state & kButtonStateHover) == 0x0) + { + const int state2 = state; + state |= kButtonStateHover; + ret = widget->contains(lastMotionPos); + self->stateChanged(static_cast(state), static_cast(state2)); + widget->repaint(); + } + } + else + { + // check if exiting hover + if (state & kButtonStateHover) + { + const int state2 = state; + state &= ~kButtonStateHover; + ret = widget->contains(lastMotionPos); + self->stateChanged(static_cast(state), static_cast(state2)); + widget->repaint(); + } + } + + lastMotionPos = ev.pos; + return ret; + } + + void setActive(const bool active2, const bool sendCallback) noexcept + { + const bool active = state & kButtonStateActive; + if (active == active2) + return; + + state |= kButtonStateActive; + widget->repaint(); + + if (sendCallback) + { + if (internalCallback != nullptr) + internalCallback->buttonClicked(widget, -1); + else if (userCallback != nullptr) + userCallback->buttonClicked(widget, -1); + } + } + + void setChecked(const bool checked2, const bool sendCallback) noexcept + { + if (checked == checked2) + return; + + checked = checked2; + widget->repaint(); + + if (sendCallback) + { + if (internalCallback != nullptr) + internalCallback->buttonClicked(widget, -1); + else if (userCallback != nullptr) + userCallback->buttonClicked(widget, -1); + } + } + + DISTRHO_DECLARE_NON_COPYABLE(PrivateData) +}; + +// -------------------------------------------------------------------------------------------------------------------- + +ButtonEventHandler::ButtonEventHandler(SubWidget* const self) + : pData(new PrivateData(this, self)) {} + +ButtonEventHandler::~ButtonEventHandler() +{ + delete pData; +} + +bool ButtonEventHandler::isActive() noexcept +{ + return pData->state & kButtonStateActive; +} + +void ButtonEventHandler::setActive(const bool active, const bool sendCallback) noexcept +{ + pData->setActive(active, sendCallback); +} + +bool ButtonEventHandler::isChecked() const noexcept +{ + return pData->checked; +} + +void ButtonEventHandler::setChecked(const bool checked, const bool sendCallback) noexcept +{ + pData->setChecked(checked, sendCallback); +} + +bool ButtonEventHandler::isCheckable() const noexcept +{ + return pData->checkable; +} + +void ButtonEventHandler::setCheckable(const bool checkable) noexcept +{ + if (pData->checkable == checkable) + return; + + pData->checkable = checkable; +} + +Point ButtonEventHandler::getLastClickPosition() const noexcept +{ + return pData->lastClickPos; +} + +Point ButtonEventHandler::getLastMotionPosition() const noexcept +{ + return pData->lastMotionPos; +} + +void ButtonEventHandler::setCallback(Callback* const callback) noexcept +{ + pData->userCallback = callback; +} + +bool ButtonEventHandler::mouseEvent(const Widget::MouseEvent& ev) +{ + return pData->mouseEvent(ev); +} + +bool ButtonEventHandler::motionEvent(const Widget::MotionEvent& ev) +{ + return pData->motionEvent(ev); +} + +ButtonEventHandler::State ButtonEventHandler::getState() const noexcept +{ + return static_cast(pData->state); +} + +void ButtonEventHandler::clearState() noexcept +{ + pData->state = kButtonStateDefault; +} + +void ButtonEventHandler::stateChanged(State, State) +{ +} + +void ButtonEventHandler::setInternalCallback(Callback* const callback) noexcept +{ + pData->internalCallback = callback; +} + +void ButtonEventHandler::triggerUserCallback(SubWidget* const widget, const int button) +{ + if (pData->userCallback != nullptr) + pData->userCallback->buttonClicked(widget, button); +} + +// -------------------------------------------------------------------------------------------------------------------- + +struct KnobEventHandler::PrivateData { + KnobEventHandler* const self; + SubWidget* const widget; + KnobEventHandler::Callback* callback; + + float minimum; + float maximum; + float step; + float value; + float valueDef; + float valueTmp; + bool usingDefault; + bool usingLog; + Orientation orientation; + int state; + + double lastX; + double lastY; + + PrivateData(KnobEventHandler* const s, SubWidget* const w) + : self(s), + widget(w), + callback(nullptr), + minimum(0.0f), + maximum(1.0f), + step(0.0f), + value(0.5f), + valueDef(value), + valueTmp(value), + usingDefault(false), + usingLog(false), + orientation(Vertical), + state(kKnobStateDefault), + lastX(0.0), + lastY(0.0) {} + + PrivateData(KnobEventHandler* const s, SubWidget* const w, PrivateData* const other) + : self(s), + widget(w), + callback(other->callback), + minimum(other->minimum), + maximum(other->maximum), + step(other->step), + value(other->value), + valueDef(other->valueDef), + valueTmp(value), + usingDefault(other->usingDefault), + usingLog(other->usingLog), + orientation(other->orientation), + state(kKnobStateDefault), + lastX(0.0), + lastY(0.0) {} + + void assignFrom(PrivateData* const other) + { + callback = other->callback; + minimum = other->minimum; + maximum = other->maximum; + step = other->step; + value = other->value; + valueDef = other->valueDef; + valueTmp = value; + usingDefault = other->usingDefault; + usingLog = other->usingLog; + orientation = other->orientation; + state = kKnobStateDefault; + lastX = 0.0; + lastY = 0.0; + } + + inline float logscale(const float v) const + { + const float b = std::log(maximum/minimum)/(maximum-minimum); + const float a = maximum/std::exp(maximum*b); + return a * std::exp(b*v); + } + + inline float invlogscale(const float v) const + { + const float b = std::log(maximum/minimum)/(maximum-minimum); + const float a = maximum/std::exp(maximum*b); + return std::log(v/a)/b; + } + + bool mouseEvent(const Widget::MouseEvent& ev) + { + if (ev.button != 1) + return false; + + if (ev.press) + { + if (! widget->contains(ev.pos)) + return false; + + if ((ev.mod & kModifierShift) != 0 && usingDefault) + { + setValue(valueDef, true); + valueTmp = value; + return true; + } + + state |= kKnobStateDragging; + lastX = ev.pos.getX(); + lastY = ev.pos.getY(); + widget->repaint(); + + if (callback != nullptr) + callback->knobDragStarted(widget); + + return true; + } + else if (state & kKnobStateDragging) + { + state &= ~kKnobStateDragging; + widget->repaint(); + + if (callback != nullptr) + callback->knobDragFinished(widget); + + return true; + } + + return false; + } + + bool motionEvent(const Widget::MotionEvent& ev) + { + if ((state & kKnobStateDragging) == 0x0) + return false; + + bool doVal = false; + float d, value2 = 0.0f; + + if (orientation == Horizontal) + { + if (const double movX = ev.pos.getX() - lastX) + { + d = (ev.mod & kModifierControl) ? 2000.0f : 200.0f; + value2 = (usingLog ? invlogscale(valueTmp) : valueTmp) + (float(maximum - minimum) / d * float(movX)); + doVal = true; + } + } + else if (orientation == Vertical) + { + if (const double movY = lastY - ev.pos.getY()) + { + d = (ev.mod & kModifierControl) ? 2000.0f : 200.0f; + value2 = (usingLog ? invlogscale(valueTmp) : valueTmp) + (float(maximum - minimum) / d * float(movY)); + doVal = true; + } + } + + if (! doVal) + return false; + + if (usingLog) + value2 = logscale(value2); + + if (value2 < minimum) + { + valueTmp = value2 = minimum; + } + else if (value2 > maximum) + { + valueTmp = value2 = maximum; + } + else + { + valueTmp = value2; + + if (d_isNotZero(step)) + { + const float rest = std::fmod(value2, step); + value2 -= rest + (rest > step/2.0f ? step : 0.0f); + } + } + + setValue(value2, true); + + lastX = ev.pos.getX(); + lastY = ev.pos.getY(); + + return true; + } + + bool scrollEvent(const Widget::ScrollEvent& ev) + { + if (! widget->contains(ev.pos)) + return false; + + const float dir = (ev.delta.getY() > 0.f) ? 1.f : -1.f; + const float d = (ev.mod & kModifierControl) ? 2000.0f : 200.0f; + float value2 = (usingLog ? invlogscale(valueTmp) : valueTmp) + + ((maximum - minimum) / d * 10.f * dir); + + if (usingLog) + value2 = logscale(value2); + + if (value2 < minimum) + { + valueTmp = value2 = minimum; + } + else if (value2 > maximum) + { + valueTmp = value2 = maximum; + } + else + { + valueTmp = value2; + + if (d_isNotZero(step)) + { + const float rest = std::fmod(value2, step); + value2 = value2 - rest + (rest > step/2.0f ? step : 0.0f); + } + } + + setValue(value2, true); + return true; + } + + float getNormalizedValue() const noexcept + { + const float diff = maximum - minimum; + return ((usingLog ? invlogscale(value) : value) - minimum) / diff; + } + + void setRange(const float min, const float max) noexcept + { + DISTRHO_SAFE_ASSERT_RETURN(max > min,); + + if (value < min) + { + valueTmp = value = min; + widget->repaint(); + } + else if (value > max) + { + valueTmp = value = max; + widget->repaint(); + } + + minimum = min; + maximum = max; + } + + bool setValue(const float value2, const bool sendCallback) + { + if (d_isEqual(value, value2)) + return false; + + valueTmp = value = value2; + widget->repaint(); + + if (sendCallback && callback != nullptr) + { + try { + callback->knobValueChanged(widget, value); + } DISTRHO_SAFE_EXCEPTION("KnobEventHandler::setValue"); + } + + return true; + } +}; + +// -------------------------------------------------------------------------------------------------------------------- + +KnobEventHandler::KnobEventHandler(SubWidget* const self) + : pData(new PrivateData(this, self)) {} + +KnobEventHandler::KnobEventHandler(SubWidget* const self, const KnobEventHandler& other) + : pData(new PrivateData(this, self, other.pData)) {} + +KnobEventHandler& KnobEventHandler::operator=(const KnobEventHandler& other) +{ + pData->assignFrom(other.pData); + return *this; +} + +KnobEventHandler::~KnobEventHandler() +{ + delete pData; +} + +float KnobEventHandler::getValue() const noexcept +{ + return pData->value; +} + +bool KnobEventHandler::setValue(const float value, const bool sendCallback) noexcept +{ + return pData->setValue(value, sendCallback); +} + +float KnobEventHandler::getNormalizedValue() const noexcept +{ + return pData->getNormalizedValue(); +} + +void KnobEventHandler::setDefault(const float def) noexcept +{ + pData->valueDef = def; + pData->usingDefault = true; +} + +void KnobEventHandler::setRange(const float min, const float max) noexcept +{ + pData->setRange(min, max); +} + +void KnobEventHandler::setStep(const float step) noexcept +{ + pData->step = step; +} + +void KnobEventHandler::setUsingLogScale(const bool yesNo) noexcept +{ + pData->usingLog = yesNo; +} + +KnobEventHandler::Orientation KnobEventHandler::getOrientation() const noexcept +{ + return pData->orientation; +} + +void KnobEventHandler::setOrientation(const Orientation orientation) noexcept +{ + if (pData->orientation == orientation) + return; + + pData->orientation = orientation; +} + +void KnobEventHandler::setCallback(Callback* const callback) noexcept +{ + pData->callback = callback; +} + +bool KnobEventHandler::mouseEvent(const Widget::MouseEvent& ev) +{ + return pData->mouseEvent(ev); +} + +bool KnobEventHandler::motionEvent(const Widget::MotionEvent& ev) +{ + return pData->motionEvent(ev); +} + +bool KnobEventHandler::scrollEvent(const Widget::ScrollEvent& ev) +{ + return pData->scrollEvent(ev); +} + +KnobEventHandler::State KnobEventHandler::getState() const noexcept +{ + return static_cast(pData->state); +} + +// -------------------------------------------------------------------------------------------------------------------- + +END_NAMESPACE_DGL diff --git a/source/modules/dgl/src/Geometry.cpp b/source/modules/dgl/src/Geometry.cpp index 7ea3af348..eb8df6e57 100644 --- a/source/modules/dgl/src/Geometry.cpp +++ b/source/modules/dgl/src/Geometry.cpp @@ -1,6 +1,6 @@ /* * DISTRHO Plugin Framework (DPF) - * Copyright (C) 2012-2016 Filipe Coelho + * Copyright (C) 2012-2021 Filipe Coelho * * Permission to use, copy, modify, and/or distribute this software for any purpose with * or without fee is hereby granted, provided that the above copyright notice and this @@ -14,6 +14,13 @@ * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ +#ifdef _MSC_VER +# pragma warning(disable:4661) /* instantiated template classes whose methods are defined elsewhere */ +#elif defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6)) +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wconversion" +#endif + #include "../Geometry.hpp" #include @@ -27,129 +34,129 @@ static const float M_2PIf = 3.14159265358979323846f*2.0f; template Point::Point() noexcept - : fX(0), - fY(0) {} + : x(0), + y(0) {} template -Point::Point(const T& x, const T& y) noexcept - : fX(x), - fY(y) {} +Point::Point(const T& x2, const T& y2) noexcept + : x(x2), + y(y2) {} template Point::Point(const Point& pos) noexcept - : fX(pos.fX), - fY(pos.fY) {} + : x(pos.x), + y(pos.y) {} template const T& Point::getX() const noexcept { - return fX; + return x; } template const T& Point::getY() const noexcept { - return fY; + return y; } template -void Point::setX(const T& x) noexcept +void Point::setX(const T& x2) noexcept { - fX = x; + x = x2; } template -void Point::setY(const T& y) noexcept +void Point::setY(const T& y2) noexcept { - fY = y; + y = y2; } template -void Point::setPos(const T& x, const T& y) noexcept +void Point::setPos(const T& x2, const T& y2) noexcept { - fX = x; - fY = y; + x = x2; + y = y2; } template void Point::setPos(const Point& pos) noexcept { - fX = pos.fX; - fY = pos.fY; + x = pos.x; + y = pos.y; } template -void Point::moveBy(const T& x, const T& y) noexcept +void Point::moveBy(const T& x2, const T& y2) noexcept { - fX = static_cast(fX+x); - fY = static_cast(fY+y); + x = static_cast(x+x2); + y = static_cast(y+y2); } template void Point::moveBy(const Point& pos) noexcept { - fX = static_cast(fX+pos.fX); - fY = static_cast(fY+pos.fY); + x = static_cast(x+pos.x); + y = static_cast(y+pos.y); } template bool Point::isZero() const noexcept { - return fX == 0 && fY == 0; + return x == 0 && y == 0; } template bool Point::isNotZero() const noexcept { - return fX != 0 || fY != 0; + return x != 0 || y != 0; } template Point Point::operator+(const Point& pos) noexcept { - return Point(fX+pos.fX, fY+pos.fY); + return Point(x+pos.x, y+pos.y); } template Point Point::operator-(const Point& pos) noexcept { - return Point(fX-pos.fX, fY-pos.fY); + return Point(x-pos.x, y-pos.y); } template Point& Point::operator=(const Point& pos) noexcept { - fX = pos.fX; - fY = pos.fY; + x = pos.x; + y = pos.y; return *this; } template Point& Point::operator+=(const Point& pos) noexcept { - fX = static_cast(fX+pos.fX); - fY = static_cast(fY+pos.fY); + x = static_cast(x+pos.x); + y = static_cast(y+pos.y); return *this; } template Point& Point::operator-=(const Point& pos) noexcept { - fX = static_cast(fX-pos.fX); - fY = static_cast(fY-pos.fY); + x = static_cast(x-pos.x); + y = static_cast(y-pos.y); return *this; } template bool Point::operator==(const Point& pos) const noexcept { - return (fX == pos.fX && fY == pos.fY); + return (x == pos.x && y == pos.y); } template bool Point::operator!=(const Point& pos) const noexcept { - return (fX != pos.fX || fY != pos.fY); + return (x != pos.x || y != pos.y); } // ----------------------------------------------------------------------- @@ -237,7 +244,7 @@ bool Size::isNotNull() const noexcept template bool Size::isValid() const noexcept { - return fWidth > 1 && fHeight > 1; + return fWidth > 0 && fHeight > 0; } template @@ -246,6 +253,27 @@ bool Size::isInvalid() const noexcept return fWidth <= 0 || fHeight <= 0; } +template +Size Size::toInt() const noexcept +{ + return Size(static_cast(fWidth), + static_cast(fHeight)); +} + +template<> +Size Size::toInt() const noexcept +{ + return Size(static_cast(fWidth + 0.5), + static_cast(fHeight + 0.5)); +} + +template<> +Size Size::toInt() const noexcept +{ + return Size(static_cast(fWidth + 0.5f), + static_cast(fHeight + 0.5f)); +} + template Size Size::operator+(const Size& size) noexcept { @@ -298,6 +326,22 @@ Size& Size::operator/=(double d) noexcept return *this; } +template +Size Size::operator*(const double m) const noexcept +{ + Size size(fWidth, fHeight); + size *= m; + return size; +} + +template +Size Size::operator/(const double m) const noexcept +{ + Size size(fWidth, fHeight); + size /= m; + return size; +} + template bool Size::operator==(const Size& size) const noexcept { @@ -315,177 +359,162 @@ bool Size::operator!=(const Size& size) const noexcept template Line::Line() noexcept - : fPosStart(0, 0), - fPosEnd(0, 0) {} + : posStart(0, 0), + posEnd(0, 0) {} template Line::Line(const T& startX, const T& startY, const T& endX, const T& endY) noexcept - : fPosStart(startX, startY), - fPosEnd(endX, endY) {} + : posStart(startX, startY), + posEnd(endX, endY) {} template Line::Line(const T& startX, const T& startY, const Point& endPos) noexcept - : fPosStart(startX, startY), - fPosEnd(endPos) {} + : posStart(startX, startY), + posEnd(endPos) {} template Line::Line(const Point& startPos, const T& endX, const T& endY) noexcept - : fPosStart(startPos), - fPosEnd(endX, endY) {} + : posStart(startPos), + posEnd(endX, endY) {} template Line::Line(const Point& startPos, const Point& endPos) noexcept - : fPosStart(startPos), - fPosEnd(endPos) {} + : posStart(startPos), + posEnd(endPos) {} template Line::Line(const Line& line) noexcept - : fPosStart(line.fPosStart), - fPosEnd(line.fPosEnd) {} + : posStart(line.posStart), + posEnd(line.posEnd) {} template const T& Line::getStartX() const noexcept { - return fPosStart.fX; + return posStart.x; } template const T& Line::getStartY() const noexcept { - return fPosStart.fY; + return posStart.y; } template const T& Line::getEndX() const noexcept { - return fPosEnd.fX; + return posEnd.x; } template const T& Line::getEndY() const noexcept { - return fPosEnd.fY; + return posEnd.y; } template const Point& Line::getStartPos() const noexcept { - return fPosStart; + return posStart; } template const Point& Line::getEndPos() const noexcept { - return fPosEnd; + return posEnd; } template void Line::setStartX(const T& x) noexcept { - fPosStart.fX = x; + posStart.x = x; } template void Line::setStartY(const T& y) noexcept { - fPosStart.fY = y; + posStart.y = y; } template void Line::setStartPos(const T& x, const T& y) noexcept { - fPosStart = Point(x, y); + posStart = Point(x, y); } template void Line::setStartPos(const Point& pos) noexcept { - fPosStart = pos; + posStart = pos; } template void Line::setEndX(const T& x) noexcept { - fPosEnd.fX = x; + posEnd.x = x; } template void Line::setEndY(const T& y) noexcept { - fPosEnd.fY = y; + posEnd.y = y; } template void Line::setEndPos(const T& x, const T& y) noexcept { - fPosEnd = Point(x, y); + posEnd = Point(x, y); } template void Line::setEndPos(const Point& pos) noexcept { - fPosEnd = pos; + posEnd = pos; } template void Line::moveBy(const T& x, const T& y) noexcept { - fPosStart.moveBy(x, y); - fPosEnd.moveBy(x, y); + posStart.moveBy(x, y); + posEnd.moveBy(x, y); } template void Line::moveBy(const Point& pos) noexcept { - fPosStart.moveBy(pos); - fPosEnd.moveBy(pos); -} - -template -void Line::draw() -{ - DISTRHO_SAFE_ASSERT_RETURN(fPosStart != fPosEnd,); - - glBegin(GL_LINES); - - { - glVertex2d(fPosStart.fX, fPosStart.fY); - glVertex2d(fPosEnd.fX, fPosEnd.fY); - } - - glEnd(); + posStart.moveBy(pos); + posEnd.moveBy(pos); } template bool Line::isNull() const noexcept { - return fPosStart == fPosEnd; + return posStart == posEnd; } template bool Line::isNotNull() const noexcept { - return fPosStart != fPosEnd; + return posStart != posEnd; } template Line& Line::operator=(const Line& line) noexcept { - fPosStart = line.fPosStart; - fPosEnd = line.fPosEnd; + posStart = line.posStart; + posEnd = line.posEnd; return *this; } template bool Line::operator==(const Line& line) const noexcept { - return (fPosStart == line.fPosStart && fPosEnd == line.fPosEnd); + return (posStart == line.posStart && posEnd == line.posEnd); } template bool Line::operator!=(const Line& line) const noexcept { - return (fPosStart != line.fPosStart || fPosEnd != line.fPosEnd); + return (posStart != line.posStart || posEnd != line.posEnd); } // ----------------------------------------------------------------------- @@ -539,13 +568,13 @@ Circle::Circle(const Circle& cir) noexcept template const T& Circle::getX() const noexcept { - return fPos.fX; + return fPos.x; } template const T& Circle::getY() const noexcept { - return fPos.fY; + return fPos.y; } template @@ -557,20 +586,20 @@ const Point& Circle::getPos() const noexcept template void Circle::setX(const T& x) noexcept { - fPos.fX = x; + fPos.x = x; } template void Circle::setY(const T& y) noexcept { - fPos.fY = y; + fPos.y = y; } template void Circle::setPos(const T& x, const T& y) noexcept { - fPos.fX = x; - fPos.fY = y; + fPos.x = x; + fPos.y = y; } template @@ -614,18 +643,6 @@ void Circle::setNumSegments(const uint num) fSin = std::sin(fTheta); } -template -void Circle::draw() -{ - _draw(false); -} - -template -void Circle::drawOutline() -{ - _draw(true); -} - template Circle& Circle::operator=(const Circle& cir) noexcept { @@ -650,125 +667,76 @@ bool Circle::operator!=(const Circle& cir) const noexcept return (fPos != cir.fPos || d_isNotEqual(fSize, cir.fSize) || fNumSegments != cir.fNumSegments); } -template -void Circle::_draw(const bool outline) -{ - DISTRHO_SAFE_ASSERT_RETURN(fNumSegments >= 3 && fSize > 0.0f,); - - double t, x = fSize, y = 0.0; - - glBegin(outline ? GL_LINE_LOOP : GL_POLYGON); - - for (uint i=0; i Triangle::Triangle() noexcept - : fPos1(0, 0), - fPos2(0, 0), - fPos3(0, 0) {} + : pos1(0, 0), + pos2(0, 0), + pos3(0, 0) {} template Triangle::Triangle(const T& x1, const T& y1, const T& x2, const T& y2, const T& x3, const T& y3) noexcept - : fPos1(x1, y1), - fPos2(x2, y2), - fPos3(x3, y3) {} + : pos1(x1, y1), + pos2(x2, y2), + pos3(x3, y3) {} template -Triangle::Triangle(const Point& pos1, const Point& pos2, const Point& pos3) noexcept - : fPos1(pos1), - fPos2(pos2), - fPos3(pos3) {} +Triangle::Triangle(const Point& p1, const Point& p2, const Point& p3) noexcept + : pos1(p1), + pos2(p2), + pos3(p3) {} template Triangle::Triangle(const Triangle& tri) noexcept - : fPos1(tri.fPos1), - fPos2(tri.fPos2), - fPos3(tri.fPos3) {} - -template -void Triangle::draw() -{ - _draw(false); -} - -template -void Triangle::drawOutline() -{ - _draw(true); -} + : pos1(tri.pos1), + pos2(tri.pos2), + pos3(tri.pos3) {} template bool Triangle::isNull() const noexcept { - return fPos1 == fPos2 && fPos1 == fPos3; + return pos1 == pos2 && pos1 == pos3; } template bool Triangle::isNotNull() const noexcept { - return fPos1 != fPos2 || fPos1 != fPos3; + return pos1 != pos2 || pos1 != pos3; } template bool Triangle::isValid() const noexcept { - return fPos1 != fPos2 && fPos1 != fPos3; + return pos1 != pos2 && pos1 != pos3; } template bool Triangle::isInvalid() const noexcept { - return fPos1 == fPos2 || fPos1 == fPos3; + return pos1 == pos2 || pos1 == pos3; } template Triangle& Triangle::operator=(const Triangle& tri) noexcept { - fPos1 = tri.fPos1; - fPos2 = tri.fPos2; - fPos3 = tri.fPos3; + pos1 = tri.pos1; + pos2 = tri.pos2; + pos3 = tri.pos3; return *this; } template bool Triangle::operator==(const Triangle& tri) const noexcept { - return (fPos1 == tri.fPos1 && fPos2 == tri.fPos2 && fPos3 == tri.fPos3); + return (pos1 == tri.pos1 && pos2 == tri.pos2 && pos3 == tri.pos3); } template bool Triangle::operator!=(const Triangle& tri) const noexcept { - return (fPos1 != tri.fPos1 || fPos2 != tri.fPos2 || fPos3 != tri.fPos3); -} - -template -void Triangle::_draw(const bool outline) -{ - DISTRHO_SAFE_ASSERT_RETURN(fPos1 != fPos2 && fPos1 != fPos3,); - - glBegin(outline ? GL_LINE_LOOP : GL_TRIANGLES); - - { - glVertex2d(fPos1.fX, fPos1.fY); - glVertex2d(fPos2.fX, fPos2.fY); - glVertex2d(fPos3.fX, fPos3.fY); - } - - glEnd(); + return (pos1 != tri.pos1 || pos2 != tri.pos2 || pos3 != tri.pos3); } // ----------------------------------------------------------------------- @@ -776,250 +744,263 @@ void Triangle::_draw(const bool outline) template Rectangle::Rectangle() noexcept - : fPos(0, 0), - fSize(0, 0) {} + : pos(0, 0), + size(0, 0) {} template -Rectangle::Rectangle(const T& x, const T& y, const T& width, const T& height) noexcept - : fPos(x, y), - fSize(width, height) {} +Rectangle::Rectangle(const T& x, const T& y, const T& w, const T& h) noexcept + : pos(x, y), + size(w, h) {} template -Rectangle::Rectangle(const T& x, const T& y, const Size& size) noexcept - : fPos(x, y), - fSize(size) {} +Rectangle::Rectangle(const T& x, const T& y, const Size& s) noexcept + : pos(x, y), + size(s) {} template -Rectangle::Rectangle(const Point& pos, const T& width, const T& height) noexcept - : fPos(pos), - fSize(width, height) {} +Rectangle::Rectangle(const Point& p, const T& w, const T& h) noexcept + : pos(p), + size(w, h) {} template -Rectangle::Rectangle(const Point& pos, const Size& size) noexcept - : fPos(pos), - fSize(size) {} +Rectangle::Rectangle(const Point& p, const Size& s) noexcept + : pos(p), + size(s) {} template Rectangle::Rectangle(const Rectangle& rect) noexcept - : fPos(rect.fPos), - fSize(rect.fSize) {} + : pos(rect.pos), + size(rect.size) {} template const T& Rectangle::getX() const noexcept { - return fPos.fX; + return pos.x; } template const T& Rectangle::getY() const noexcept { - return fPos.fY; + return pos.y; } template const T& Rectangle::getWidth() const noexcept { - return fSize.fWidth; + return size.fWidth; } template const T& Rectangle::getHeight() const noexcept { - return fSize.fHeight; + return size.fHeight; } template const Point& Rectangle::getPos() const noexcept { - return fPos; + return pos; } template const Size& Rectangle::getSize() const noexcept { - return fSize; + return size; } template void Rectangle::setX(const T& x) noexcept { - fPos.fX = x; + pos.x = x; } template void Rectangle::setY(const T& y) noexcept { - fPos.fY = y; + pos.y = y; } template void Rectangle::setPos(const T& x, const T& y) noexcept { - fPos.fX = x; - fPos.fY = y; + pos.x = x; + pos.y = y; } template -void Rectangle::setPos(const Point& pos) noexcept +void Rectangle::setPos(const Point& pos2) noexcept { - fPos = pos; + pos = pos2; } template void Rectangle::moveBy(const T& x, const T& y) noexcept { - fPos.moveBy(x, y); + pos.moveBy(x, y); } template -void Rectangle::moveBy(const Point& pos) noexcept +void Rectangle::moveBy(const Point& pos2) noexcept { - fPos.moveBy(pos); + pos.moveBy(pos2); } template void Rectangle::setWidth(const T& width) noexcept { - fSize.fWidth = width; + size.fWidth = width; } template void Rectangle::setHeight(const T& height) noexcept { - fSize.fHeight = height; + size.fHeight = height; } template void Rectangle::setSize(const T& width, const T& height) noexcept { - fSize.fWidth = width; - fSize.fHeight = height; + size.fWidth = width; + size.fHeight = height; } template -void Rectangle::setSize(const Size& size) noexcept +void Rectangle::setSize(const Size& size2) noexcept { - fSize = size; + size = size2; } template void Rectangle::growBy(double multiplier) noexcept { - fSize.growBy(multiplier); + size.growBy(multiplier); } template void Rectangle::shrinkBy(double divider) noexcept { - fSize.shrinkBy(divider); + size.shrinkBy(divider); } template -void Rectangle::setRectangle(const Point& pos, const Size& size) noexcept +void Rectangle::setRectangle(const Point& pos2, const Size& size2) noexcept { - fPos = pos; - fSize = size; + pos = pos2; + size = size2; } template void Rectangle::setRectangle(const Rectangle& rect) noexcept { - fPos = rect.fPos; - fSize = rect.fSize; + pos = rect.pos; + size = rect.size; } template bool Rectangle::contains(const T& x, const T& y) const noexcept { - return (x >= fPos.fX && y >= fPos.fY && x <= fPos.fX+fSize.fWidth && y <= fPos.fY+fSize.fHeight); + return (x >= pos.x && y >= pos.y && x <= pos.x+size.fWidth && y <= pos.y+size.fHeight); +} + +template +bool Rectangle::contains(const Point& p) const noexcept +{ + return contains(p.x, p.y); +} + +template +template +bool Rectangle::contains(const Point& p) const noexcept +{ + return (p.x >= pos.x && p.y >= pos.y && p.x <= pos.x+size.fWidth && p.y <= pos.y+size.fHeight); +} + +template<> template<> +bool Rectangle::contains(const Point& p) const noexcept +{ + return (p.x >= pos.x && p.y >= pos.y && p.x <= pos.x+size.fWidth && p.y <= pos.y+size.fHeight); +} + +template<> template<> +bool Rectangle::contains(const Point& p) const noexcept +{ + return (p.x >= pos.x && p.y >= pos.y && p.x <= pos.x+size.fWidth && p.y <= pos.y+size.fHeight); } template -bool Rectangle::contains(const Point& pos) const noexcept +bool Rectangle::containsAfterScaling(const Point& p, const double scaling) const noexcept { - return contains(pos.fX, pos.fY); + return (p.x >= pos.x && p.y >= pos.y && p.x/scaling <= pos.x+size.fWidth && p.y/scaling <= pos.y+size.fHeight); } template bool Rectangle::containsX(const T& x) const noexcept { - return (x >= fPos.fX && x <= fPos.fX + fSize.fWidth); + return (x >= pos.x && x <= pos.x + size.fWidth); } template bool Rectangle::containsY(const T& y) const noexcept { - return (y >= fPos.fY && y <= fPos.fY + fSize.fHeight); + return (y >= pos.y && y <= pos.y + size.fHeight); } template -void Rectangle::draw() +bool Rectangle::isNull() const noexcept { - _draw(false); + return size.isNull(); } template -void Rectangle::drawOutline() +bool Rectangle::isNotNull() const noexcept { - _draw(true); + return size.isNotNull(); +} + +template +bool Rectangle::isValid() const noexcept +{ + return size.isValid(); +} + +template +bool Rectangle::isInvalid() const noexcept +{ + return size.isInvalid(); } template Rectangle& Rectangle::operator=(const Rectangle& rect) noexcept { - fPos = rect.fPos; - fSize = rect.fSize; + pos = rect.pos; + size = rect.size; return *this; } template Rectangle& Rectangle::operator*=(double m) noexcept { - fSize *= m; + size *= m; return *this; } template Rectangle& Rectangle::operator/=(double d) noexcept { - fSize /= d; + size /= d; return *this; } template bool Rectangle::operator==(const Rectangle& rect) const noexcept { - return (fPos == rect.fPos && fSize == rect.fSize); + return (pos == rect.pos && size == rect.size); } template bool Rectangle::operator!=(const Rectangle& rect) const noexcept { - return (fPos != rect.fPos || fSize != rect.fSize); -} - -template -void Rectangle::_draw(const bool outline) -{ - DISTRHO_SAFE_ASSERT_RETURN(fSize.isValid(),); - - glBegin(outline ? GL_LINE_LOOP : GL_QUADS); - - { - glTexCoord2f(0.0f, 0.0f); - glVertex2d(fPos.fX, fPos.fY); - - glTexCoord2f(1.0f, 0.0f); - glVertex2d(fPos.fX+fSize.fWidth, fPos.fY); - - glTexCoord2f(1.0f, 1.0f); - glVertex2d(fPos.fX+fSize.fWidth, fPos.fY+fSize.fHeight); - - glTexCoord2f(0.0f, 1.0f); - glVertex2d(fPos.fX, fPos.fY+fSize.fHeight); - } - - glEnd(); + return (pos != rect.pos || size != rect.size); } // ----------------------------------------------------------------------- diff --git a/source/modules/dgl/src/Image.cpp b/source/modules/dgl/src/Image.cpp deleted file mode 100644 index b79b8d445..000000000 --- a/source/modules/dgl/src/Image.cpp +++ /dev/null @@ -1,194 +0,0 @@ -/* - * DISTRHO Plugin Framework (DPF) - * Copyright (C) 2012-2016 Filipe Coelho - * - * Permission to use, copy, modify, and/or distribute this software for any purpose with - * or without fee is hereby granted, provided that the above copyright notice and this - * permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD - * TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN - * NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL - * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER - * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN - * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#include "../Image.hpp" - -START_NAMESPACE_DGL - -// ----------------------------------------------------------------------- - -Image::Image() - : fRawData(nullptr), - fSize(0, 0), - fFormat(0), - fType(0), - fTextureId(0), - fIsReady(false) -{ - glGenTextures(1, &fTextureId); -} - -Image::Image(const char* const rawData, const uint width, const uint height, const GLenum format, const GLenum type) - : fRawData(rawData), - fSize(width, height), - fFormat(format), - fType(type), - fTextureId(0), - fIsReady(false) -{ - glGenTextures(1, &fTextureId); -} - -Image::Image(const char* const rawData, const Size& size, const GLenum format, const GLenum type) - : fRawData(rawData), - fSize(size), - fFormat(format), - fType(type), - fTextureId(0), - fIsReady(false) -{ - glGenTextures(1, &fTextureId); -} - -Image::Image(const Image& image) - : fRawData(image.fRawData), - fSize(image.fSize), - fFormat(image.fFormat), - fType(image.fType), - fTextureId(0), - fIsReady(false) -{ - glGenTextures(1, &fTextureId); -} - -Image::~Image() -{ - if (fTextureId != 0) - { -#ifndef DISTRHO_OS_MAC // FIXME - glDeleteTextures(1, &fTextureId); -#endif - fTextureId = 0; - } -} - -void Image::loadFromMemory(const char* const rawData, const uint width, const uint height, const GLenum format, const GLenum type) noexcept -{ - loadFromMemory(rawData, Size(width, height), format, type); -} - -void Image::loadFromMemory(const char* const rawData, const Size& size, const GLenum format, const GLenum type) noexcept -{ - fRawData = rawData; - fSize = size; - fFormat = format; - fType = type; - fIsReady = false; -} - -bool Image::isValid() const noexcept -{ - return (fRawData != nullptr && fSize.getWidth() > 0 && fSize.getHeight() > 0); -} - -uint Image::getWidth() const noexcept -{ - return fSize.getWidth(); -} - -uint Image::getHeight() const noexcept -{ - return fSize.getHeight(); -} - -const Size& Image::getSize() const noexcept -{ - return fSize; -} - -const char* Image::getRawData() const noexcept -{ - return fRawData; -} - -GLenum Image::getFormat() const noexcept -{ - return fFormat; -} - -GLenum Image::getType() const noexcept -{ - return fType; -} - -void Image::draw() -{ - drawAt(0, 0); -} - -void Image::drawAt(const int x, const int y) -{ - drawAt(Point(x, y)); -} - -void Image::drawAt(const Point& pos) -{ - if (fTextureId == 0 || ! isValid()) - return; - - glEnable(GL_TEXTURE_2D); - glBindTexture(GL_TEXTURE_2D, fTextureId); - - if (! fIsReady) - { - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER); - - static const float trans[] = { 0.0f, 0.0f, 0.0f, 0.0f }; - glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, trans); - - glPixelStorei(GL_PACK_ALIGNMENT, 1); - glPixelStorei(GL_UNPACK_ALIGNMENT, 1); - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, - static_cast(fSize.getWidth()), static_cast(fSize.getHeight()), 0, - fFormat, fType, fRawData); - - fIsReady = true; - } - - Rectangle(pos, static_cast(fSize.getWidth()), static_cast(fSize.getHeight())).draw(); - - glBindTexture(GL_TEXTURE_2D, 0); - glDisable(GL_TEXTURE_2D); -} - -// ----------------------------------------------------------------------- - -Image& Image::operator=(const Image& image) noexcept -{ - fRawData = image.fRawData; - fSize = image.fSize; - fFormat = image.fFormat; - fType = image.fType; - fIsReady = false; - return *this; -} - -bool Image::operator==(const Image& image) const noexcept -{ - return (fRawData == image.fRawData && fSize == image.fSize); -} - -bool Image::operator!=(const Image& image) const noexcept -{ - return !operator==(image); -} - -// ----------------------------------------------------------------------- - -END_NAMESPACE_DGL diff --git a/source/modules/dgl/src/ImageBase.cpp b/source/modules/dgl/src/ImageBase.cpp new file mode 100644 index 000000000..229523bf3 --- /dev/null +++ b/source/modules/dgl/src/ImageBase.cpp @@ -0,0 +1,132 @@ +/* + * DISTRHO Plugin Framework (DPF) + * Copyright (C) 2012-2021 Filipe Coelho + * + * Permission to use, copy, modify, and/or distribute this software for any purpose with + * or without fee is hereby granted, provided that the above copyright notice and this + * permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD + * TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN + * NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER + * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "../ImageBase.hpp" + +START_NAMESPACE_DGL + +// -------------------------------------------------------------------------------------------------------------------- +// protected constructors + +ImageBase::ImageBase() + : rawData(nullptr), + size(0, 0), + format(kImageFormatNull) {} + +ImageBase::ImageBase(const char* const rdata, const uint width, const uint height, const ImageFormat fmt) + : rawData(rdata), + size(width, height), + format(fmt) {} + +ImageBase::ImageBase(const char* const rdata, const Size& s, const ImageFormat fmt) + : rawData(rdata), + size(s), + format(fmt) {} + +ImageBase::ImageBase(const ImageBase& image) + : rawData(image.rawData), + size(image.size), + format(image.format) {} + +// -------------------------------------------------------------------------------------------------------------------- +// public methods + +ImageBase::~ImageBase() {} + +bool ImageBase::isValid() const noexcept +{ + return (rawData != nullptr && size.isValid()); +} + +bool ImageBase::isInvalid() const noexcept +{ + return (rawData == nullptr || size.isInvalid()); +} + +uint ImageBase::getWidth() const noexcept +{ + return size.getWidth(); +} + +uint ImageBase::getHeight() const noexcept +{ + return size.getHeight(); +} + +const Size& ImageBase::getSize() const noexcept +{ + return size; +} + +const char* ImageBase::getRawData() const noexcept +{ + return rawData; +} + +ImageFormat ImageBase::getFormat() const noexcept +{ + return format; +} + +void ImageBase::loadFromMemory(const char* const rdata, + const uint width, + const uint height, + const ImageFormat fmt) noexcept +{ + loadFromMemory(rdata, Size(width, height), fmt); +} + +void ImageBase::loadFromMemory(const char* const rdata, const Size& s, const ImageFormat fmt) noexcept +{ + rawData = rdata; + size = s; + format = fmt; +} + +void ImageBase::draw(const GraphicsContext& context) +{ + drawAt(context, Point(0, 0)); +} + +void ImageBase::drawAt(const GraphicsContext& context, const int x, const int y) +{ + drawAt(context, Point(x, y)); +} + +// -------------------------------------------------------------------------------------------------------------------- +// public operators + +ImageBase& ImageBase::operator=(const ImageBase& image) noexcept +{ + rawData = image.rawData; + size = image.size; + format = image.format; + return *this; +} + +bool ImageBase::operator==(const ImageBase& image) const noexcept +{ + return (rawData == image.rawData && size == image.size && format == image.format); +} + +bool ImageBase::operator!=(const ImageBase& image) const noexcept +{ + return !operator==(image); +} + +// -------------------------------------------------------------------------------------------------------------------- + +END_NAMESPACE_DGL diff --git a/source/modules/dgl/src/ImageBaseWidgets.cpp b/source/modules/dgl/src/ImageBaseWidgets.cpp new file mode 100644 index 000000000..bb16f0cee --- /dev/null +++ b/source/modules/dgl/src/ImageBaseWidgets.cpp @@ -0,0 +1,928 @@ +/* + * DISTRHO Plugin Framework (DPF) + * Copyright (C) 2012-2021 Filipe Coelho + * + * Permission to use, copy, modify, and/or distribute this software for any purpose with + * or without fee is hereby granted, provided that the above copyright notice and this + * permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD + * TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN + * NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER + * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "../ImageBaseWidgets.hpp" +#include "../Color.hpp" + +START_NAMESPACE_DGL + +// -------------------------------------------------------------------------------------------------------------------- + +template +ImageBaseAboutWindow::ImageBaseAboutWindow(Window& transientParentWindow, const ImageType& image) + : StandaloneWindow(transientParentWindow.getApp(), transientParentWindow), + img(image) +{ + setResizable(false); + setTitle("About"); + + if (image.isValid()) + { + setSize(image.getSize()); + setGeometryConstraints(image.getWidth(), image.getHeight(), true, true); + } + + done(); +} + +template +ImageBaseAboutWindow::ImageBaseAboutWindow(TopLevelWidget* const topLevelWidget, const ImageType& image) + : StandaloneWindow(topLevelWidget->getApp(), topLevelWidget->getWindow()), + img(image) +{ + setResizable(false); + setTitle("About"); + + if (image.isValid()) + { + setSize(image.getSize()); + setGeometryConstraints(image.getWidth(), image.getHeight(), true, true); + } + + done(); +} + +template +void ImageBaseAboutWindow::setImage(const ImageType& image) +{ + if (img == image) + return; + + img = image; + + if (image.isInvalid()) + return; + + setSize(image.getSize()); + setGeometryConstraints(image.getWidth(), image.getHeight(), true, true); +} + +template +void ImageBaseAboutWindow::onDisplay() +{ + img.draw(getGraphicsContext()); +} + +template +bool ImageBaseAboutWindow::onKeyboard(const KeyboardEvent& ev) +{ + if (ev.press && ev.key == kKeyEscape) + { + close(); + return true; + } + + return false; +} + +template +bool ImageBaseAboutWindow::onMouse(const MouseEvent& ev) +{ + if (ev.press) + { + close(); + return true; + } + + return false; +} + +// -------------------------------------------------------------------------------------------------------------------- + +template +struct ImageBaseButton::PrivateData : public ButtonEventHandler::Callback { + ImageBaseButton::Callback* callback; + ImageType imageNormal; + ImageType imageHover; + ImageType imageDown; + + PrivateData(const ImageType& normal, const ImageType& hover, const ImageType& down) + : callback(nullptr), + imageNormal(normal), + imageHover(hover), + imageDown(down) {} + + void buttonClicked(SubWidget* widget, int button) override + { + if (callback != nullptr) + if (ImageBaseButton* const imageButton = dynamic_cast(widget)) + callback->imageButtonClicked(imageButton, button); + } + + DISTRHO_DECLARE_NON_COPYABLE(PrivateData) +}; + +// -------------------------------------------------------------------------------------------------------------------- + +template +ImageBaseButton::ImageBaseButton(Widget* const parentWidget, const ImageType& image) + : SubWidget(parentWidget), + ButtonEventHandler(this), + pData(new PrivateData(image, image, image)) +{ + ButtonEventHandler::setCallback(pData); + setSize(image.getSize()); +} + +template +ImageBaseButton::ImageBaseButton(Widget* const parentWidget, const ImageType& imageNormal, const ImageType& imageDown) + : SubWidget(parentWidget), + ButtonEventHandler(this), + pData(new PrivateData(imageNormal, imageNormal, imageDown)) +{ + DISTRHO_SAFE_ASSERT(imageNormal.getSize() == imageDown.getSize()); + + ButtonEventHandler::setCallback(pData); + setSize(imageNormal.getSize()); +} + +template +ImageBaseButton::ImageBaseButton(Widget* const parentWidget, const ImageType& imageNormal, const ImageType& imageHover, const ImageType& imageDown) + : SubWidget(parentWidget), + ButtonEventHandler(this), + pData(new PrivateData(imageNormal, imageHover, imageDown)) +{ + DISTRHO_SAFE_ASSERT(imageNormal.getSize() == imageHover.getSize() && imageHover.getSize() == imageDown.getSize()); + + ButtonEventHandler::setCallback(pData); + setSize(imageNormal.getSize()); +} + +template +ImageBaseButton::~ImageBaseButton() +{ + delete pData; +} + +template +void ImageBaseButton::setCallback(Callback* callback) noexcept +{ + pData->callback = callback; +} + +template +void ImageBaseButton::onDisplay() +{ + const GraphicsContext& context(getGraphicsContext()); + + const State state = ButtonEventHandler::getState(); + + if (state & kButtonStateActive) + pData->imageDown.draw(context); + else if (state & kButtonStateHover) + pData->imageHover.draw(context); + else + pData->imageNormal.draw(context); +} + +template +bool ImageBaseButton::onMouse(const MouseEvent& ev) +{ + if (SubWidget::onMouse(ev)) + return true; + return ButtonEventHandler::mouseEvent(ev); +} + +template +bool ImageBaseButton::onMotion(const MotionEvent& ev) +{ + if (SubWidget::onMotion(ev)) + return true; + return ButtonEventHandler::motionEvent(ev); +} + +// -------------------------------------------------------------------------------------------------------------------- + +template +struct ImageBaseKnob::PrivateData : public KnobEventHandler::Callback { + ImageBaseKnob::Callback* callback; + ImageType image; + + int rotationAngle; + + bool alwaysRepaint; + bool isImgVertical; + uint imgLayerWidth; + uint imgLayerHeight; + uint imgLayerCount; + bool isReady; + + union { + uint glTextureId; + void* cairoSurface; + }; + + explicit PrivateData(const ImageType& img) + : callback(nullptr), + image(img), + rotationAngle(0), + alwaysRepaint(false), + isImgVertical(img.getHeight() > img.getWidth()), + imgLayerWidth(isImgVertical ? img.getWidth() : img.getHeight()), + imgLayerHeight(imgLayerWidth), + imgLayerCount(isImgVertical ? img.getHeight()/imgLayerHeight : img.getWidth()/imgLayerWidth), + isReady(false) + { + init(); + } + + explicit PrivateData(PrivateData* const other) + : callback(other->callback), + image(other->image), + rotationAngle(other->rotationAngle), + alwaysRepaint(other->alwaysRepaint), + isImgVertical(other->isImgVertical), + imgLayerWidth(other->imgLayerWidth), + imgLayerHeight(other->imgLayerHeight), + imgLayerCount(other->imgLayerCount), + isReady(false) + { + init(); + } + + void assignFrom(PrivateData* const other) + { + cleanup(); + image = other->image; + rotationAngle = other->rotationAngle; + callback = other->callback; + alwaysRepaint = other->alwaysRepaint; + isImgVertical = other->isImgVertical; + imgLayerWidth = other->imgLayerWidth; + imgLayerHeight = other->imgLayerHeight; + imgLayerCount = other->imgLayerCount; + isReady = false; + init(); + } + + ~PrivateData() + { + cleanup(); + } + + void knobDragStarted(SubWidget* const widget) override + { + if (callback != nullptr) + if (ImageBaseKnob* const imageKnob = dynamic_cast(widget)) + callback->imageKnobDragStarted(imageKnob); + } + + void knobDragFinished(SubWidget* const widget) override + { + if (callback != nullptr) + if (ImageBaseKnob* const imageKnob = dynamic_cast(widget)) + callback->imageKnobDragFinished(imageKnob); + } + + void knobValueChanged(SubWidget* const widget, const float value) override + { + if (rotationAngle == 0 || alwaysRepaint) + isReady = false; + + if (callback != nullptr) + if (ImageBaseKnob* const imageKnob = dynamic_cast(widget)) + callback->imageKnobValueChanged(imageKnob, value); + } + + // implemented independently per graphics backend + void init(); + void cleanup(); + + DISTRHO_DECLARE_NON_COPYABLE(PrivateData) +}; + +// -------------------------------------------------------------------------------------------------------------------- + +template +ImageBaseKnob::ImageBaseKnob(Widget* const parentWidget, + const ImageType& image, + const Orientation orientation) noexcept + : SubWidget(parentWidget), + KnobEventHandler(this), + pData(new PrivateData(image)) +{ + KnobEventHandler::setCallback(pData); + setOrientation(orientation); + setSize(pData->imgLayerWidth, pData->imgLayerHeight); +} + +template +ImageBaseKnob::ImageBaseKnob(const ImageBaseKnob& imageKnob) + : SubWidget(imageKnob.getParentWidget()), + KnobEventHandler(this, imageKnob), + pData(new PrivateData(imageKnob.pData)) +{ + KnobEventHandler::setCallback(pData); + setOrientation(imageKnob.getOrientation()); + setSize(pData->imgLayerWidth, pData->imgLayerHeight); +} + +template +ImageBaseKnob& ImageBaseKnob::operator=(const ImageBaseKnob& imageKnob) +{ + KnobEventHandler::operator=(imageKnob); + pData->assignFrom(imageKnob.pData); + setSize(pData->imgLayerWidth, pData->imgLayerHeight); + return *this; +} + +template +ImageBaseKnob::~ImageBaseKnob() +{ + delete pData; +} + +template +void ImageBaseKnob::setCallback(Callback* callback) noexcept +{ + pData->callback = callback; +} + +template +void ImageBaseKnob::setImageLayerCount(uint count) noexcept +{ + DISTRHO_SAFE_ASSERT_RETURN(count > 1,); + + pData->imgLayerCount = count; + + if (pData->isImgVertical) + pData->imgLayerHeight = pData->image.getHeight()/count; + else + pData->imgLayerWidth = pData->image.getWidth()/count; + + setSize(pData->imgLayerWidth, pData->imgLayerHeight); +} + +template +void ImageBaseKnob::setRotationAngle(int angle) +{ + if (pData->rotationAngle == angle) + return; + + pData->rotationAngle = angle; + pData->isReady = false; +} + +template +bool ImageBaseKnob::setValue(float value, bool sendCallback) noexcept +{ + if (KnobEventHandler::setValue(value, sendCallback)) + { + if (pData->rotationAngle == 0 || pData->alwaysRepaint) + pData->isReady = false; + + return true; + } + + return false; +} + +template +bool ImageBaseKnob::onMouse(const MouseEvent& ev) +{ + if (SubWidget::onMouse(ev)) + return true; + return KnobEventHandler::mouseEvent(ev); +} + +template +bool ImageBaseKnob::onMotion(const MotionEvent& ev) +{ + if (SubWidget::onMotion(ev)) + return true; + return KnobEventHandler::motionEvent(ev); +} + +template +bool ImageBaseKnob::onScroll(const ScrollEvent& ev) +{ + if (SubWidget::onScroll(ev)) + return true; + return KnobEventHandler::scrollEvent(ev); +} + +// -------------------------------------------------------------------------------------------------------------------- + +template +struct ImageBaseSlider::PrivateData { + ImageType image; + float minimum; + float maximum; + float step; + float value; + float valueDef; + float valueTmp; + bool usingDefault; + + bool dragging; + bool inverted; + bool valueIsSet; + double startedX; + double startedY; + + Callback* callback; + + Point startPos; + Point endPos; + Rectangle sliderArea; + + PrivateData(const ImageType& img) + : image(img), + minimum(0.0f), + maximum(1.0f), + step(0.0f), + value(0.5f), + valueDef(value), + valueTmp(value), + usingDefault(false), + dragging(false), + inverted(false), + valueIsSet(false), + startedX(0.0), + startedY(0.0), + callback(nullptr), + startPos(), + endPos(), + sliderArea() {} + + void recheckArea() noexcept + { + if (startPos.getY() == endPos.getY()) + { + // horizontal + sliderArea = Rectangle(startPos.getX(), + startPos.getY(), + endPos.getX() + static_cast(image.getWidth()) - startPos.getX(), + static_cast(image.getHeight())); + } + else + { + // vertical + sliderArea = Rectangle(startPos.getX(), + startPos.getY(), + static_cast(image.getWidth()), + endPos.getY() + static_cast(image.getHeight()) - startPos.getY()); + } + } + + DISTRHO_DECLARE_NON_COPYABLE(PrivateData) +}; + +// -------------------------------------------------------------------------------------------------------------------- + +template +ImageBaseSlider::ImageBaseSlider(Widget* const parentWidget, const ImageType& image) noexcept + : SubWidget(parentWidget), + pData(new PrivateData(image)) +{ + setNeedsFullViewportDrawing(); +} + +template +ImageBaseSlider::~ImageBaseSlider() +{ + delete pData; +} + +template +float ImageBaseSlider::getValue() const noexcept +{ + return pData->value; +} + +template +void ImageBaseSlider::setValue(float value, bool sendCallback) noexcept +{ + if (! pData->valueIsSet) + pData->valueIsSet = true; + + if (d_isEqual(pData->value, value)) + return; + + pData->value = value; + + if (d_isZero(pData->step)) + pData->valueTmp = value; + + repaint(); + + if (sendCallback && pData->callback != nullptr) + { + try { + pData->callback->imageSliderValueChanged(this, pData->value); + } DISTRHO_SAFE_EXCEPTION("ImageBaseSlider::setValue"); + } +} + +template +void ImageBaseSlider::setStartPos(const Point& startPos) noexcept +{ + pData->startPos = startPos; + pData->recheckArea(); +} + +template +void ImageBaseSlider::setStartPos(int x, int y) noexcept +{ + setStartPos(Point(x, y)); +} + +template +void ImageBaseSlider::setEndPos(const Point& endPos) noexcept +{ + pData->endPos = endPos; + pData->recheckArea(); +} + +template +void ImageBaseSlider::setEndPos(int x, int y) noexcept +{ + setEndPos(Point(x, y)); +} + +template +void ImageBaseSlider::setInverted(bool inverted) noexcept +{ + if (pData->inverted == inverted) + return; + + pData->inverted = inverted; + repaint(); +} + +template +void ImageBaseSlider::setDefault(float value) noexcept +{ + pData->valueDef = value; + pData->usingDefault = true; +} + +template +void ImageBaseSlider::setRange(float min, float max) noexcept +{ + pData->minimum = min; + pData->maximum = max; + + if (pData->value < min) + { + pData->value = min; + repaint(); + + if (pData->callback != nullptr && pData->valueIsSet) + { + try { + pData->callback->imageSliderValueChanged(this, pData->value); + } DISTRHO_SAFE_EXCEPTION("ImageBaseSlider::setRange < min"); + } + } + else if (pData->value > max) + { + pData->value = max; + repaint(); + + if (pData->callback != nullptr && pData->valueIsSet) + { + try { + pData->callback->imageSliderValueChanged(this, pData->value); + } DISTRHO_SAFE_EXCEPTION("ImageBaseSlider::setRange > max"); + } + } +} + +template +void ImageBaseSlider::setStep(float step) noexcept +{ + pData->step = step; +} + +template +void ImageBaseSlider::setCallback(Callback* callback) noexcept +{ + pData->callback = callback; +} + +template +void ImageBaseSlider::onDisplay() +{ + const GraphicsContext& context(getGraphicsContext()); + +#if 0 // DEBUG, paints slider area + Color(1.0f, 1.0f, 1.0f, 0.5f).setFor(context, true); + Rectangle(pData->sliderArea.getX(), + pData->sliderArea.getY(), + pData->sliderArea.getX()+pData->sliderArea.getWidth(), + pData->sliderArea.getY()+pData->sliderArea.getHeight()).draw(context); + Color(1.0f, 1.0f, 1.0f, 1.0f).setFor(context, true); +#endif + + const float normValue = (pData->value - pData->minimum) / (pData->maximum - pData->minimum); + + int x, y; + + if (pData->startPos.getY() == pData->endPos.getY()) + { + // horizontal + if (pData->inverted) + x = pData->endPos.getX() - static_cast(normValue*static_cast(pData->endPos.getX()-pData->startPos.getX())); + else + x = pData->startPos.getX() + static_cast(normValue*static_cast(pData->endPos.getX()-pData->startPos.getX())); + + y = pData->startPos.getY(); + } + else + { + // vertical + x = pData->startPos.getX(); + + if (pData->inverted) + y = pData->endPos.getY() - static_cast(normValue*static_cast(pData->endPos.getY()-pData->startPos.getY())); + else + y = pData->startPos.getY() + static_cast(normValue*static_cast(pData->endPos.getY()-pData->startPos.getY())); + } + + pData->image.drawAt(context, x, y); +} + +template +bool ImageBaseSlider::onMouse(const MouseEvent& ev) +{ + if (ev.button != 1) + return false; + + if (ev.press) + { + if (! pData->sliderArea.contains(ev.pos)) + return false; + + if ((ev.mod & kModifierShift) != 0 && pData->usingDefault) + { + setValue(pData->valueDef, true); + pData->valueTmp = pData->value; + return true; + } + + float vper; + const double x = ev.pos.getX(); + const double y = ev.pos.getY(); + + if (pData->startPos.getY() == pData->endPos.getY()) + { + // horizontal + vper = float(x - pData->sliderArea.getX()) / float(pData->sliderArea.getWidth()); + } + else + { + // vertical + vper = float(y - pData->sliderArea.getY()) / float(pData->sliderArea.getHeight()); + } + + float value; + + if (pData->inverted) + value = pData->maximum - vper * (pData->maximum - pData->minimum); + else + value = pData->minimum + vper * (pData->maximum - pData->minimum); + + if (value < pData->minimum) + { + pData->valueTmp = value = pData->minimum; + } + else if (value > pData->maximum) + { + pData->valueTmp = value = pData->maximum; + } + else if (d_isNotZero(pData->step)) + { + pData->valueTmp = value; + const float rest = std::fmod(value, pData->step); + value = value - rest + (rest > pData->step/2.0f ? pData->step : 0.0f); + } + + pData->dragging = true; + pData->startedX = x; + pData->startedY = y; + + if (pData->callback != nullptr) + pData->callback->imageSliderDragStarted(this); + + setValue(value, true); + + return true; + } + else if (pData->dragging) + { + if (pData->callback != nullptr) + pData->callback->imageSliderDragFinished(this); + + pData->dragging = false; + return true; + } + + return false; +} + +template +bool ImageBaseSlider::onMotion(const MotionEvent& ev) +{ + if (! pData->dragging) + return false; + + const bool horizontal = pData->startPos.getY() == pData->endPos.getY(); + const double x = ev.pos.getX(); + const double y = ev.pos.getY(); + + if ((horizontal && pData->sliderArea.containsX(x)) || (pData->sliderArea.containsY(y) && ! horizontal)) + { + float vper; + + if (horizontal) + { + // horizontal + vper = float(x - pData->sliderArea.getX()) / float(pData->sliderArea.getWidth()); + } + else + { + // vertical + vper = float(y - pData->sliderArea.getY()) / float(pData->sliderArea.getHeight()); + } + + float value; + + if (pData->inverted) + value = pData->maximum - vper * (pData->maximum - pData->minimum); + else + value = pData->minimum + vper * (pData->maximum - pData->minimum); + + if (value < pData->minimum) + { + pData->valueTmp = value = pData->minimum; + } + else if (value > pData->maximum) + { + pData->valueTmp = value = pData->maximum; + } + else if (d_isNotZero(pData->step)) + { + pData->valueTmp = value; + const float rest = std::fmod(value, pData->step); + value = value - rest + (rest > pData->step/2.0f ? pData->step : 0.0f); + } + + setValue(value, true); + } + else if (horizontal) + { + if (x < pData->sliderArea.getX()) + setValue(pData->inverted ? pData->maximum : pData->minimum, true); + else + setValue(pData->inverted ? pData->minimum : pData->maximum, true); + } + else + { + if (y < pData->sliderArea.getY()) + setValue(pData->inverted ? pData->maximum : pData->minimum, true); + else + setValue(pData->inverted ? pData->minimum : pData->maximum, true); + } + + return true; +} + +// -------------------------------------------------------------------------------------------------------------------- + +template +struct ImageBaseSwitch::PrivateData { + ImageType imageNormal; + ImageType imageDown; + bool isDown; + Callback* callback; + + PrivateData(const ImageType& normal, const ImageType& down) + : imageNormal(normal), + imageDown(down), + isDown(false), + callback(nullptr) + { + DISTRHO_SAFE_ASSERT(imageNormal.getSize() == imageDown.getSize()); + } + + PrivateData(PrivateData* const other) + : imageNormal(other->imageNormal), + imageDown(other->imageDown), + isDown(other->isDown), + callback(other->callback) + { + DISTRHO_SAFE_ASSERT(imageNormal.getSize() == imageDown.getSize()); + } + + void assignFrom(PrivateData* const other) + { + imageNormal = other->imageNormal; + imageDown = other->imageDown; + isDown = other->isDown; + callback = other->callback; + DISTRHO_SAFE_ASSERT(imageNormal.getSize() == imageDown.getSize()); + } + + DISTRHO_DECLARE_NON_COPYABLE(PrivateData) +}; + +// -------------------------------------------------------------------------------------------------------------------- + +template +ImageBaseSwitch::ImageBaseSwitch(Widget* const parentWidget, const ImageType& imageNormal, const ImageType& imageDown) noexcept + : SubWidget(parentWidget), + pData(new PrivateData(imageNormal, imageDown)) +{ + setSize(imageNormal.getSize()); +} + +template +ImageBaseSwitch::ImageBaseSwitch(const ImageBaseSwitch& imageSwitch) noexcept + : SubWidget(imageSwitch.getParentWidget()), + pData(new PrivateData(imageSwitch.pData)) +{ + setSize(pData->imageNormal.getSize()); +} + +template +ImageBaseSwitch& ImageBaseSwitch::operator=(const ImageBaseSwitch& imageSwitch) noexcept +{ + pData->assignFrom(imageSwitch.pData); + setSize(pData->imageNormal.getSize()); + return *this; +} + +template +ImageBaseSwitch::~ImageBaseSwitch() +{ + delete pData; +} + +template +bool ImageBaseSwitch::isDown() const noexcept +{ + return pData->isDown; +} + +template +void ImageBaseSwitch::setDown(const bool down) noexcept +{ + if (pData->isDown == down) + return; + + pData->isDown = down; + repaint(); +} + +template +void ImageBaseSwitch::setCallback(Callback* const callback) noexcept +{ + pData->callback = callback; +} + +template +void ImageBaseSwitch::onDisplay() +{ + const GraphicsContext& context(getGraphicsContext()); + + if (pData->isDown) + pData->imageDown.draw(context); + else + pData->imageNormal.draw(context); +} + +template +bool ImageBaseSwitch::onMouse(const MouseEvent& ev) +{ + if (ev.press && contains(ev.pos)) + { + pData->isDown = !pData->isDown; + + repaint(); + + if (pData->callback != nullptr) + pData->callback->imageSwitchClicked(this, pData->isDown); + + return true; + } + + return false; +} + +// -------------------------------------------------------------------------------------------------------------------- + +END_NAMESPACE_DGL diff --git a/source/modules/dgl/src/ImageWidgets.cpp b/source/modules/dgl/src/ImageWidgets.cpp deleted file mode 100644 index 9b4625c1a..000000000 --- a/source/modules/dgl/src/ImageWidgets.cpp +++ /dev/null @@ -1,1077 +0,0 @@ -/* - * DISTRHO Plugin Framework (DPF) - * Copyright (C) 2012-2016 Filipe Coelho - * - * Permission to use, copy, modify, and/or distribute this software for any purpose with - * or without fee is hereby granted, provided that the above copyright notice and this - * permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD - * TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN - * NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL - * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER - * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN - * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#include "Common.hpp" -#include "WidgetPrivateData.hpp" - -START_NAMESPACE_DGL - -// ----------------------------------------------------------------------- - -ImageAboutWindow::ImageAboutWindow(Window& parent, const Image& image) - : Window(parent.getApp(), parent), - Widget((Window&)*this), - fImgBackground(image) -{ - Window::setResizable(false); - Window::setSize(image.getSize()); - Window::setTitle("About"); -} - -ImageAboutWindow::ImageAboutWindow(Widget* widget, const Image& image) - : Window(widget->getParentApp(), widget->getParentWindow()), - Widget((Window&)*this), - fImgBackground(image) -{ - Window::setResizable(false); - Window::setSize(image.getSize()); - Window::setTitle("About"); -} - -void ImageAboutWindow::setImage(const Image& image) -{ - if (fImgBackground == image) - return; - - fImgBackground = image; - Window::setSize(image.getSize()); -} - -void ImageAboutWindow::onDisplay() -{ - fImgBackground.draw(); -} - -bool ImageAboutWindow::onKeyboard(const KeyboardEvent& ev) -{ - if (ev.press && ev.key == kCharEscape) - { - Window::close(); - return true; - } - - return false; -} - -bool ImageAboutWindow::onMouse(const MouseEvent& ev) -{ - if (ev.press) - { - Window::close(); - return true; - } - - return false; -} - -void ImageAboutWindow::onReshape(uint width, uint height) -{ - Widget::setSize(width, height); - Window::onReshape(width, height); -} - -// ----------------------------------------------------------------------- - -struct ImageButton::PrivateData { - ButtonImpl impl; - Image imageNormal; - Image imageHover; - Image imageDown; - - PrivateData(Widget* const s, const Image& normal, const Image& hover, const Image& down) - : impl(s), - imageNormal(normal), - imageHover(hover), - imageDown(down) {} - - DISTRHO_DECLARE_NON_COPY_STRUCT(PrivateData) -}; - -// ----------------------------------------------------------------------- - -ImageButton::ImageButton(Window& parent, const Image& image) - : Widget(parent), - pData(new PrivateData(this, image, image, image)) -{ - setSize(image.getSize()); -} - -ImageButton::ImageButton(Window& parent, const Image& imageNormal, const Image& imageDown) - : Widget(parent), - pData(new PrivateData(this, imageNormal, imageNormal, imageDown)) -{ - DISTRHO_SAFE_ASSERT(imageNormal.getSize() == imageDown.getSize()); - - setSize(imageNormal.getSize()); -} - -ImageButton::ImageButton(Window& parent, const Image& imageNormal, const Image& imageHover, const Image& imageDown) - : Widget(parent), - pData(new PrivateData(this, imageNormal, imageHover, imageDown)) -{ - DISTRHO_SAFE_ASSERT(imageNormal.getSize() == imageHover.getSize() && imageHover.getSize() == imageDown.getSize()); - - setSize(imageNormal.getSize()); -} - -ImageButton::ImageButton(Widget* widget, const Image& image) - : Widget(widget->getParentWindow()), - pData(new PrivateData(this, image, image, image)) -{ - setSize(image.getSize()); -} - -ImageButton::ImageButton(Widget* widget, const Image& imageNormal, const Image& imageDown) - : Widget(widget->getParentWindow()), - pData(new PrivateData(this, imageNormal, imageNormal, imageDown)) -{ - DISTRHO_SAFE_ASSERT(imageNormal.getSize() == imageDown.getSize()); - - setSize(imageNormal.getSize()); -} - -ImageButton::ImageButton(Widget* widget, const Image& imageNormal, const Image& imageHover, const Image& imageDown) - : Widget(widget->getParentWindow()), - pData(new PrivateData(this, imageNormal, imageHover, imageDown)) -{ - DISTRHO_SAFE_ASSERT(imageNormal.getSize() == imageHover.getSize() && imageHover.getSize() == imageDown.getSize()); - - setSize(imageNormal.getSize()); -} - -ImageButton::~ImageButton() -{ - delete pData; -} - -void ImageButton::setCallback(Callback* callback) noexcept -{ - pData->impl.callback_img = callback; -} - -void ImageButton::onDisplay() -{ - switch (pData->impl.state) - { - case ButtonImpl::kStateDown: - pData->imageDown.draw(); - break; - case ButtonImpl::kStateHover: - pData->imageHover.draw(); - break; - default: - pData->imageNormal.draw(); - break; - } -} - -bool ImageButton::onMouse(const MouseEvent& ev) -{ - return pData->impl.onMouse(ev); -} - -bool ImageButton::onMotion(const MotionEvent& ev) -{ - return pData->impl.onMotion(ev); -} - -// ----------------------------------------------------------------------- - -ImageKnob::ImageKnob(Window& parent, const Image& image, Orientation orientation) noexcept - : Widget(parent), - fImage(image), - fMinimum(0.0f), - fMaximum(1.0f), - fStep(0.0f), - fValue(0.5f), - fValueDef(fValue), - fValueTmp(fValue), - fUsingDefault(false), - fUsingLog(false), - fOrientation(orientation), - fRotationAngle(0), - fDragging(false), - fLastX(0), - fLastY(0), - fCallback(nullptr), - fIsImgVertical(image.getHeight() > image.getWidth()), - fImgLayerWidth(fIsImgVertical ? image.getWidth() : image.getHeight()), - fImgLayerHeight(fImgLayerWidth), - fImgLayerCount(fIsImgVertical ? image.getHeight()/fImgLayerHeight : image.getWidth()/fImgLayerWidth), - fIsReady(false), - fTextureId(0) -{ - glGenTextures(1, &fTextureId); - setSize(fImgLayerWidth, fImgLayerHeight); -} - -ImageKnob::ImageKnob(Widget* widget, const Image& image, Orientation orientation) noexcept - : Widget(widget->getParentWindow()), - fImage(image), - fMinimum(0.0f), - fMaximum(1.0f), - fStep(0.0f), - fValue(0.5f), - fValueDef(fValue), - fValueTmp(fValue), - fUsingDefault(false), - fUsingLog(false), - fOrientation(orientation), - fRotationAngle(0), - fDragging(false), - fLastX(0), - fLastY(0), - fCallback(nullptr), - fIsImgVertical(image.getHeight() > image.getWidth()), - fImgLayerWidth(fIsImgVertical ? image.getWidth() : image.getHeight()), - fImgLayerHeight(fImgLayerWidth), - fImgLayerCount(fIsImgVertical ? image.getHeight()/fImgLayerHeight : image.getWidth()/fImgLayerWidth), - fIsReady(false), - fTextureId(0) -{ - glGenTextures(1, &fTextureId); - setSize(fImgLayerWidth, fImgLayerHeight); -} - -ImageKnob::ImageKnob(const ImageKnob& imageKnob) - : Widget(imageKnob.getParentWindow()), - fImage(imageKnob.fImage), - fMinimum(imageKnob.fMinimum), - fMaximum(imageKnob.fMaximum), - fStep(imageKnob.fStep), - fValue(imageKnob.fValue), - fValueDef(imageKnob.fValueDef), - fValueTmp(fValue), - fUsingDefault(imageKnob.fUsingDefault), - fUsingLog(imageKnob.fUsingLog), - fOrientation(imageKnob.fOrientation), - fRotationAngle(imageKnob.fRotationAngle), - fDragging(false), - fLastX(0), - fLastY(0), - fCallback(imageKnob.fCallback), - fIsImgVertical(imageKnob.fIsImgVertical), - fImgLayerWidth(imageKnob.fImgLayerWidth), - fImgLayerHeight(imageKnob.fImgLayerHeight), - fImgLayerCount(imageKnob.fImgLayerCount), - fIsReady(false), - fTextureId(0) -{ - glGenTextures(1, &fTextureId); - setSize(fImgLayerWidth, fImgLayerHeight); -} - -ImageKnob& ImageKnob::operator=(const ImageKnob& imageKnob) -{ - fImage = imageKnob.fImage; - fMinimum = imageKnob.fMinimum; - fMaximum = imageKnob.fMaximum; - fStep = imageKnob.fStep; - fValue = imageKnob.fValue; - fValueDef = imageKnob.fValueDef; - fValueTmp = fValue; - fUsingDefault = imageKnob.fUsingDefault; - fUsingLog = imageKnob.fUsingLog; - fOrientation = imageKnob.fOrientation; - fRotationAngle = imageKnob.fRotationAngle; - fDragging = false; - fLastX = 0; - fLastY = 0; - fCallback = imageKnob.fCallback; - fIsImgVertical = imageKnob.fIsImgVertical; - fImgLayerWidth = imageKnob.fImgLayerWidth; - fImgLayerHeight = imageKnob.fImgLayerHeight; - fImgLayerCount = imageKnob.fImgLayerCount; - fIsReady = false; - - if (fTextureId != 0) - { - glDeleteTextures(1, &fTextureId); - fTextureId = 0; - } - - glGenTextures(1, &fTextureId); - setSize(fImgLayerWidth, fImgLayerHeight); - - return *this; -} - -ImageKnob::~ImageKnob() -{ - if (fTextureId != 0) - { - glDeleteTextures(1, &fTextureId); - fTextureId = 0; - } -} - -float ImageKnob::getValue() const noexcept -{ - return fValue; -} - -// NOTE: value is assumed to be scaled if using log -void ImageKnob::setDefault(float value) noexcept -{ - fValueDef = value; - fUsingDefault = true; -} - -void ImageKnob::setRange(float min, float max) noexcept -{ - DISTRHO_SAFE_ASSERT_RETURN(max > min,); - - if (fValue < min) - { - fValue = min; - repaint(); - - if (fCallback != nullptr) - { - try { - fCallback->imageKnobValueChanged(this, fValue); - } DISTRHO_SAFE_EXCEPTION("ImageKnob::setRange < min"); - } - } - else if (fValue > max) - { - fValue = max; - repaint(); - - if (fCallback != nullptr) - { - try { - fCallback->imageKnobValueChanged(this, fValue); - } DISTRHO_SAFE_EXCEPTION("ImageKnob::setRange > max"); - } - } - - fMinimum = min; - fMaximum = max; -} - -void ImageKnob::setStep(float step) noexcept -{ - fStep = step; -} - -// NOTE: value is assumed to be scaled if using log -void ImageKnob::setValue(float value, bool sendCallback) noexcept -{ - if (d_isEqual(fValue, value)) - return; - - fValue = value; - - if (d_isZero(fStep)) - fValueTmp = value; - - if (fRotationAngle == 0) - fIsReady = false; - - repaint(); - - if (sendCallback && fCallback != nullptr) - { - try { - fCallback->imageKnobValueChanged(this, fValue); - } DISTRHO_SAFE_EXCEPTION("ImageKnob::setValue"); - } -} - -void ImageKnob::setUsingLogScale(bool yesNo) noexcept -{ - fUsingLog = yesNo; -} - -void ImageKnob::setCallback(Callback* callback) noexcept -{ - fCallback = callback; -} - -void ImageKnob::setOrientation(Orientation orientation) noexcept -{ - if (fOrientation == orientation) - return; - - fOrientation = orientation; -} - -void ImageKnob::setRotationAngle(int angle) -{ - if (fRotationAngle == angle) - return; - - fRotationAngle = angle; - fIsReady = false; -} - -void ImageKnob::setImageLayerCount(uint count) noexcept -{ - DISTRHO_SAFE_ASSERT_RETURN(count > 1,); - - fImgLayerCount = count; - - if (fIsImgVertical) - fImgLayerHeight = fImage.getHeight()/count; - else - fImgLayerWidth = fImage.getWidth()/count; - - setSize(fImgLayerWidth, fImgLayerHeight); -} - -void ImageKnob::onDisplay() -{ - const float normValue = ((fUsingLog ? _invlogscale(fValue) : fValue) - fMinimum) / (fMaximum - fMinimum); - - glEnable(GL_TEXTURE_2D); - glBindTexture(GL_TEXTURE_2D, fTextureId); - - if (! fIsReady) - { - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER); - - static const float trans[] = { 0.0f, 0.0f, 0.0f, 0.0f }; - glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, trans); - - glPixelStorei(GL_PACK_ALIGNMENT, 1); - glPixelStorei(GL_UNPACK_ALIGNMENT, 1); - - uint imageDataOffset = 0; - - if (fRotationAngle == 0) - { - DISTRHO_SAFE_ASSERT_RETURN(fImgLayerCount > 0,); - DISTRHO_SAFE_ASSERT_RETURN(normValue >= 0.0f,); - - const uint& v1(fIsImgVertical ? fImgLayerWidth : fImgLayerHeight); - const uint& v2(fIsImgVertical ? fImgLayerHeight : fImgLayerWidth); - - const uint layerDataSize = v1 * v2 * ((fImage.getFormat() == GL_BGRA || fImage.getFormat() == GL_RGBA) ? 4 : 3); - /* */ imageDataOffset = layerDataSize * uint(normValue * float(fImgLayerCount-1)); - } - - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, - static_cast(getWidth()), static_cast(getHeight()), 0, - fImage.getFormat(), fImage.getType(), fImage.getRawData() + imageDataOffset); - - fIsReady = true; - } - - const int w = static_cast(getWidth()); - const int h = static_cast(getHeight()); - - if (fRotationAngle != 0) - { - glPushMatrix(); - - const int w2 = w/2; - const int h2 = h/2; - - glTranslatef(static_cast(w2), static_cast(h2), 0.0f); - glRotatef(normValue*static_cast(fRotationAngle), 0.0f, 0.0f, 1.0f); - - Rectangle(-w2, -h2, w, h).draw(); - - glPopMatrix(); - } - else - { - Rectangle(0, 0, w, h).draw(); - } - - glBindTexture(GL_TEXTURE_2D, 0); - glDisable(GL_TEXTURE_2D); -} - -bool ImageKnob::onMouse(const MouseEvent& ev) -{ - if (ev.button != 1) - return false; - - if (ev.press) - { - if (! contains(ev.pos)) - return false; - - if ((ev.mod & kModifierShift) != 0 && fUsingDefault) - { - setValue(fValueDef, true); - fValueTmp = fValue; - return true; - } - - fDragging = true; - fLastX = ev.pos.getX(); - fLastY = ev.pos.getY(); - - if (fCallback != nullptr) - fCallback->imageKnobDragStarted(this); - - return true; - } - else if (fDragging) - { - if (fCallback != nullptr) - fCallback->imageKnobDragFinished(this); - - fDragging = false; - return true; - } - - return false; -} - -bool ImageKnob::onMotion(const MotionEvent& ev) -{ - if (! fDragging) - return false; - - bool doVal = false; - float d, value = 0.0f; - - if (fOrientation == ImageKnob::Horizontal) - { - if (const int movX = ev.pos.getX() - fLastX) - { - d = (ev.mod & kModifierControl) ? 2000.0f : 200.0f; - value = (fUsingLog ? _invlogscale(fValueTmp) : fValueTmp) + (float(fMaximum - fMinimum) / d * float(movX)); - doVal = true; - } - } - else if (fOrientation == ImageKnob::Vertical) - { - if (const int movY = fLastY - ev.pos.getY()) - { - d = (ev.mod & kModifierControl) ? 2000.0f : 200.0f; - value = (fUsingLog ? _invlogscale(fValueTmp) : fValueTmp) + (float(fMaximum - fMinimum) / d * float(movY)); - doVal = true; - } - } - - if (! doVal) - return false; - - if (fUsingLog) - value = _logscale(value); - - if (value < fMinimum) - { - fValueTmp = value = fMinimum; - } - else if (value > fMaximum) - { - fValueTmp = value = fMaximum; - } - else if (d_isNotZero(fStep)) - { - fValueTmp = value; - const float rest = std::fmod(value, fStep); - value = value - rest + (rest > fStep/2.0f ? fStep : 0.0f); - } - - setValue(value, true); - - fLastX = ev.pos.getX(); - fLastY = ev.pos.getY(); - - return true; -} - -bool ImageKnob::onScroll(const ScrollEvent& ev) -{ - if (! contains(ev.pos)) - return false; - - const float d = (ev.mod & kModifierControl) ? 2000.0f : 200.0f; - float value = (fUsingLog ? _invlogscale(fValueTmp) : fValueTmp) + (float(fMaximum - fMinimum) / d * 10.f * ev.delta.getY()); - - if (fUsingLog) - value = _logscale(value); - - if (value < fMinimum) - { - fValueTmp = value = fMinimum; - } - else if (value > fMaximum) - { - fValueTmp = value = fMaximum; - } - else if (d_isNotZero(fStep)) - { - fValueTmp = value; - const float rest = std::fmod(value, fStep); - value = value - rest + (rest > fStep/2.0f ? fStep : 0.0f); - } - - setValue(value, true); - return true; -} - -// ----------------------------------------------------------------------- - -float ImageKnob::_logscale(float value) const -{ - const float b = std::log(fMaximum/fMinimum)/(fMaximum-fMinimum); - const float a = fMaximum/std::exp(fMaximum*b); - return a * std::exp(b*value); -} - -float ImageKnob::_invlogscale(float value) const -{ - const float b = std::log(fMaximum/fMinimum)/(fMaximum-fMinimum); - const float a = fMaximum/std::exp(fMaximum*b); - return std::log(value/a)/b; -} - -// ----------------------------------------------------------------------- - -ImageSlider::ImageSlider(Window& parent, const Image& image) noexcept - : Widget(parent), - fImage(image), - fMinimum(0.0f), - fMaximum(1.0f), - fStep(0.0f), - fValue(0.5f), - fValueTmp(fValue), - fDragging(false), - fInverted(false), - fValueIsSet(false), - fStartedX(0), - fStartedY(0), - fCallback(nullptr), - fStartPos(), - fEndPos(), - fSliderArea() -{ - pData->needsFullViewport = true; -} - -ImageSlider::ImageSlider(Widget* widget, const Image& image) noexcept - : Widget(widget->getParentWindow()), - fImage(image), - fMinimum(0.0f), - fMaximum(1.0f), - fStep(0.0f), - fValue(0.5f), - fValueTmp(fValue), - fDragging(false), - fInverted(false), - fValueIsSet(false), - fStartedX(0), - fStartedY(0), - fCallback(nullptr), - fStartPos(), - fEndPos(), - fSliderArea() -{ - pData->needsFullViewport = true; -} - -float ImageSlider::getValue() const noexcept -{ - return fValue; -} - -void ImageSlider::setValue(float value, bool sendCallback) noexcept -{ - if (! fValueIsSet) - fValueIsSet = true; - - if (d_isEqual(fValue, value)) - return; - - fValue = value; - - if (d_isZero(fStep)) - fValueTmp = value; - - repaint(); - - if (sendCallback && fCallback != nullptr) - { - try { - fCallback->imageSliderValueChanged(this, fValue); - } DISTRHO_SAFE_EXCEPTION("ImageSlider::setValue"); - } -} - -void ImageSlider::setStartPos(const Point& startPos) noexcept -{ - fStartPos = startPos; - _recheckArea(); -} - -void ImageSlider::setStartPos(int x, int y) noexcept -{ - setStartPos(Point(x, y)); -} - -void ImageSlider::setEndPos(const Point& endPos) noexcept -{ - fEndPos = endPos; - _recheckArea(); -} - -void ImageSlider::setEndPos(int x, int y) noexcept -{ - setEndPos(Point(x, y)); -} - -void ImageSlider::setInverted(bool inverted) noexcept -{ - if (fInverted == inverted) - return; - - fInverted = inverted; - repaint(); -} - -void ImageSlider::setRange(float min, float max) noexcept -{ - fMinimum = min; - fMaximum = max; - - if (fValue < min) - { - fValue = min; - repaint(); - - if (fCallback != nullptr && fValueIsSet) - { - try { - fCallback->imageSliderValueChanged(this, fValue); - } DISTRHO_SAFE_EXCEPTION("ImageSlider::setRange < min"); - } - } - else if (fValue > max) - { - fValue = max; - repaint(); - - if (fCallback != nullptr && fValueIsSet) - { - try { - fCallback->imageSliderValueChanged(this, fValue); - } DISTRHO_SAFE_EXCEPTION("ImageSlider::setRange > max"); - } - } -} - -void ImageSlider::setStep(float step) noexcept -{ - fStep = step; -} - -void ImageSlider::setCallback(Callback* callback) noexcept -{ - fCallback = callback; -} - -void ImageSlider::onDisplay() -{ -#if 0 // DEBUG, paints slider area - glColor3f(0.4f, 0.5f, 0.1f); - glRecti(fSliderArea.getX(), fSliderArea.getY(), fSliderArea.getX()+fSliderArea.getWidth(), fSliderArea.getY()+fSliderArea.getHeight()); - glColor4f(1.0f, 1.0f, 1.0f, 1.0f); -#endif - - const float normValue = (fValue - fMinimum) / (fMaximum - fMinimum); - - int x, y; - - if (fStartPos.getY() == fEndPos.getY()) - { - // horizontal - if (fInverted) - x = fEndPos.getX() - static_cast(normValue*static_cast(fEndPos.getX()-fStartPos.getX())); - else - x = fStartPos.getX() + static_cast(normValue*static_cast(fEndPos.getX()-fStartPos.getX())); - - y = fStartPos.getY(); - } - else - { - // vertical - x = fStartPos.getX(); - - if (fInverted) - y = fEndPos.getY() - static_cast(normValue*static_cast(fEndPos.getY()-fStartPos.getY())); - else - y = fStartPos.getY() + static_cast(normValue*static_cast(fEndPos.getY()-fStartPos.getY())); - } - - fImage.drawAt(x, y); -} - -bool ImageSlider::onMouse(const MouseEvent& ev) -{ - if (ev.button != 1) - return false; - - if (ev.press) - { - if (! fSliderArea.contains(ev.pos)) - return false; - - float vper; - const int x = ev.pos.getX(); - const int y = ev.pos.getY(); - - if (fStartPos.getY() == fEndPos.getY()) - { - // horizontal - vper = float(x - fSliderArea.getX()) / float(fSliderArea.getWidth()); - } - else - { - // vertical - vper = float(y - fSliderArea.getY()) / float(fSliderArea.getHeight()); - } - - float value; - - if (fInverted) - value = fMaximum - vper * (fMaximum - fMinimum); - else - value = fMinimum + vper * (fMaximum - fMinimum); - - if (value < fMinimum) - { - fValueTmp = value = fMinimum; - } - else if (value > fMaximum) - { - fValueTmp = value = fMaximum; - } - else if (d_isNotZero(fStep)) - { - fValueTmp = value; - const float rest = std::fmod(value, fStep); - value = value - rest + (rest > fStep/2.0f ? fStep : 0.0f); - } - - fDragging = true; - fStartedX = x; - fStartedY = y; - - if (fCallback != nullptr) - fCallback->imageSliderDragStarted(this); - - setValue(value, true); - - return true; - } - else if (fDragging) - { - if (fCallback != nullptr) - fCallback->imageSliderDragFinished(this); - - fDragging = false; - return true; - } - - return false; -} - -bool ImageSlider::onMotion(const MotionEvent& ev) -{ - if (! fDragging) - return false; - - const bool horizontal = fStartPos.getY() == fEndPos.getY(); - const int x = ev.pos.getX(); - const int y = ev.pos.getY(); - - if ((horizontal && fSliderArea.containsX(x)) || (fSliderArea.containsY(y) && ! horizontal)) - { - float vper; - - if (horizontal) - { - // horizontal - vper = float(x - fSliderArea.getX()) / float(fSliderArea.getWidth()); - } - else - { - // vertical - vper = float(y - fSliderArea.getY()) / float(fSliderArea.getHeight()); - } - - float value; - - if (fInverted) - value = fMaximum - vper * (fMaximum - fMinimum); - else - value = fMinimum + vper * (fMaximum - fMinimum); - - if (value < fMinimum) - { - fValueTmp = value = fMinimum; - } - else if (value > fMaximum) - { - fValueTmp = value = fMaximum; - } - else if (d_isNotZero(fStep)) - { - fValueTmp = value; - const float rest = std::fmod(value, fStep); - value = value - rest + (rest > fStep/2.0f ? fStep : 0.0f); - } - - setValue(value, true); - } - else if (horizontal) - { - if (x < fSliderArea.getX()) - setValue(fInverted ? fMaximum : fMinimum, true); - else - setValue(fInverted ? fMinimum : fMaximum, true); - } - else - { - if (y < fSliderArea.getY()) - setValue(fInverted ? fMaximum : fMinimum, true); - else - setValue(fInverted ? fMinimum : fMaximum, true); - } - - return true; -} - -void ImageSlider::_recheckArea() noexcept -{ - if (fStartPos.getY() == fEndPos.getY()) - { - // horizontal - fSliderArea = Rectangle(fStartPos.getX(), - fStartPos.getY(), - fEndPos.getX() + static_cast(fImage.getWidth()) - fStartPos.getX(), - static_cast(fImage.getHeight())); - } - else - { - // vertical - fSliderArea = Rectangle(fStartPos.getX(), - fStartPos.getY(), - static_cast(fImage.getWidth()), - fEndPos.getY() + static_cast(fImage.getHeight()) - fStartPos.getY()); - } -} - -// ----------------------------------------------------------------------- - -ImageSwitch::ImageSwitch(Window& parent, const Image& imageNormal, const Image& imageDown) noexcept - : Widget(parent), - fImageNormal(imageNormal), - fImageDown(imageDown), - fIsDown(false), - fCallback(nullptr) -{ - DISTRHO_SAFE_ASSERT(fImageNormal.getSize() == fImageDown.getSize()); - - setSize(fImageNormal.getSize()); -} - -ImageSwitch::ImageSwitch(Widget* widget, const Image& imageNormal, const Image& imageDown) noexcept - : Widget(widget->getParentWindow()), - fImageNormal(imageNormal), - fImageDown(imageDown), - fIsDown(false), - fCallback(nullptr) -{ - DISTRHO_SAFE_ASSERT(fImageNormal.getSize() == fImageDown.getSize()); - - setSize(fImageNormal.getSize()); -} - -ImageSwitch::ImageSwitch(const ImageSwitch& imageSwitch) noexcept - : Widget(imageSwitch.getParentWindow()), - fImageNormal(imageSwitch.fImageNormal), - fImageDown(imageSwitch.fImageDown), - fIsDown(imageSwitch.fIsDown), - fCallback(imageSwitch.fCallback) -{ - DISTRHO_SAFE_ASSERT(fImageNormal.getSize() == fImageDown.getSize()); - - setSize(fImageNormal.getSize()); -} - -ImageSwitch& ImageSwitch::operator=(const ImageSwitch& imageSwitch) noexcept -{ - fImageNormal = imageSwitch.fImageNormal; - fImageDown = imageSwitch.fImageDown; - fIsDown = imageSwitch.fIsDown; - fCallback = imageSwitch.fCallback; - - DISTRHO_SAFE_ASSERT(fImageNormal.getSize() == fImageDown.getSize()); - - setSize(fImageNormal.getSize()); - - return *this; -} - -bool ImageSwitch::isDown() const noexcept -{ - return fIsDown; -} - -void ImageSwitch::setDown(bool down) noexcept -{ - if (fIsDown == down) - return; - - fIsDown = down; - repaint(); -} - -void ImageSwitch::setCallback(Callback* callback) noexcept -{ - fCallback = callback; -} - -void ImageSwitch::onDisplay() -{ - if (fIsDown) - fImageDown.draw(); - else - fImageNormal.draw(); -} - -bool ImageSwitch::onMouse(const MouseEvent& ev) -{ - if (ev.press && contains(ev.pos)) - { - fIsDown = !fIsDown; - - repaint(); - - if (fCallback != nullptr) - fCallback->imageSwitchClicked(this, fIsDown); - - return true; - } - - return false; -} - -// ----------------------------------------------------------------------- - -END_NAMESPACE_DGL diff --git a/source/modules/dgl/src/NanoVG.cpp b/source/modules/dgl/src/NanoVG.cpp index 3ef7b35dc..cb78fade9 100644 --- a/source/modules/dgl/src/NanoVG.cpp +++ b/source/modules/dgl/src/NanoVG.cpp @@ -1,6 +1,6 @@ /* * DISTRHO Plugin Framework (DPF) - * Copyright (C) 2012-2018 Filipe Coelho + * Copyright (C) 2012-2021 Filipe Coelho * * Permission to use, copy, modify, and/or distribute this software for any purpose with * or without fee is hereby granted, provided that the above copyright notice and this @@ -15,7 +15,7 @@ */ #include "../NanoVG.hpp" -#include "WidgetPrivateData.hpp" +#include "SubWidgetPrivateData.hpp" #ifndef DGL_NO_SHARED_RESOURCES # include "Resources.hpp" @@ -53,6 +53,28 @@ DGL_EXT(PFNGLUNIFORM2FVPROC, glUniform2fv) DGL_EXT(PFNGLUNIFORM4FVPROC, glUniform4fv) DGL_EXT(PFNGLUSEPROGRAMPROC, glUseProgram) DGL_EXT(PFNGLVERTEXATTRIBPOINTERPROC, glVertexAttribPointer) +DGL_EXT(PFNGLBLENDFUNCSEPARATEPROC, glBlendFuncSeparate) +# ifdef DGL_USE_NANOVG_FBO +DGL_EXT(PFNGLBINDFRAMEBUFFERPROC, glBindFramebuffer) +DGL_EXT(PFNGLBINDRENDERBUFFERPROC, glBindRenderbuffer) +DGL_EXT(PFNGLCHECKFRAMEBUFFERSTATUSPROC, glCheckFramebufferStatus) +DGL_EXT(PFNGLDELETEFRAMEBUFFERSPROC, glDeleteFramebuffers) +DGL_EXT(PFNGLDELETERENDERBUFFERSPROC, glDeleteRenderbuffers) +DGL_EXT(PFNGLFRAMEBUFFERTEXTURE2DPROC, glFramebufferTexture2D) +DGL_EXT(PFNGLFRAMEBUFFERRENDERBUFFERPROC, glFramebufferRenderbuffer) +DGL_EXT(PFNGLGENFRAMEBUFFERSPROC, glGenFramebuffers) +DGL_EXT(PFNGLGENRENDERBUFFERSPROC, glGenRenderbuffers) +DGL_EXT(PFNGLRENDERBUFFERSTORAGEPROC, glRenderbufferStorage) +# endif +# ifdef DGL_USE_OPENGL3 +DGL_EXT(PFNGLBINDBUFFERRANGEPROC, glBindBufferRange) +DGL_EXT(PFNGLBINDVERTEXARRAYPROC, glBindVertexArray) +DGL_EXT(PFNGLDELETEVERTEXARRAYSPROC, glDeleteVertexArrays) +DGL_EXT(PFNGLGENERATEMIPMAPPROC, glGenerateMipmap) +DGL_EXT(PFNGLGETUNIFORMBLOCKINDEXPROC, glGetUniformBlockIndex) +DGL_EXT(PFNGLGENVERTEXARRAYSPROC, glGenVertexArrays) +DGL_EXT(PFNGLUNIFORMBLOCKBINDINGPROC, glUniformBlockBinding) +# endif # undef DGL_EXT #endif @@ -60,32 +82,61 @@ DGL_EXT(PFNGLVERTEXATTRIBPOINTERPROC, glVertexAttribPointer) // Include NanoVG OpenGL implementation //#define STB_IMAGE_STATIC -#define NANOVG_GL2_IMPLEMENTATION +#ifdef DGL_USE_OPENGL3 +# define NANOVG_GL3_IMPLEMENTATION +#else +# define NANOVG_GL2_IMPLEMENTATION +#endif + +#if defined(DISTRHO_OS_MAC) && defined(NANOVG_GL2_IMPLEMENTATION) +# define glBindVertexArray glBindVertexArrayAPPLE +# define glDeleteVertexArrays glDeleteVertexArraysAPPLE +# define glGenVertexArrays glGenVertexArraysAPPLE +#endif + #include "nanovg/nanovg_gl.h" +#ifdef DGL_USE_NANOVG_FBO +# define NANOVG_FBO_VALID 1 +# include "nanovg/nanovg_gl_utils.h" +#endif + #if defined(NANOVG_GL2) -# define nvgCreateGL nvgCreateGL2 +# define nvgCreateGLfn nvgCreateGL2 # define nvgDeleteGL nvgDeleteGL2 +# define nvglCreateImageFromHandle nvglCreateImageFromHandleGL2 +# define nvglImageHandle nvglImageHandleGL2 #elif defined(NANOVG_GL3) -# define nvgCreateGL nvgCreateGL3 +# define nvgCreateGLfn nvgCreateGL3 # define nvgDeleteGL nvgDeleteGL3 +# define nvglCreateImageFromHandle nvglCreateImageFromHandleGL3 +# define nvglImageHandle nvglImageHandleGL3 #elif defined(NANOVG_GLES2) -# define nvgCreateGL nvgCreateGLES2 +# define nvgCreateGLfn nvgCreateGLES2 # define nvgDeleteGL nvgDeleteGLES2 +# define nvglCreateImageFromHandle nvglCreateImageFromHandleGLES2 +# define nvglImageHandle nvglImageHandleGLES2 #elif defined(NANOVG_GLES3) -# define nvgCreateGL nvgCreateGLES3 +# define nvgCreateGLfn nvgCreateGLES3 # define nvgDeleteGL nvgDeleteGLES3 +# define nvglCreateImageFromHandle nvglCreateImageFromHandleGLES3 +# define nvglImageHandle nvglImageHandleGLES3 #endif -static NVGcontext* nvgCreateGL_helper(int flags) +// ----------------------------------------------------------------------- + +START_NAMESPACE_DGL + +NVGcontext* nvgCreateGL(int flags) { #if defined(DISTRHO_OS_WINDOWS) +# if defined(__GNUC__) && (__GNUC__ >= 9) +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wcast-function-type" +# endif static bool needsInit = true; - if (needsInit) - { - needsInit = false; # define DGL_EXT(PROC, func) \ - func = (PROC) wglGetProcAddress ( #func ); \ + if (needsInit) func = (PROC) wglGetProcAddress ( #func ); \ DISTRHO_SAFE_ASSERT_RETURN(func != nullptr, nullptr); DGL_EXT(PFNGLACTIVETEXTUREPROC, glActiveTexture) DGL_EXT(PFNGLATTACHSHADERPROC, glAttachShader) @@ -114,15 +165,55 @@ DGL_EXT(PFNGLUNIFORM2FVPROC, glUniform2fv) DGL_EXT(PFNGLUNIFORM4FVPROC, glUniform4fv) DGL_EXT(PFNGLUSEPROGRAMPROC, glUseProgram) DGL_EXT(PFNGLVERTEXATTRIBPOINTERPROC, glVertexAttribPointer) +DGL_EXT(PFNGLBLENDFUNCSEPARATEPROC, glBlendFuncSeparate) +# ifdef DGL_USE_NANOVG_FBO +DGL_EXT(PFNGLBINDFRAMEBUFFERPROC, glBindFramebuffer) +DGL_EXT(PFNGLBINDRENDERBUFFERPROC, glBindRenderbuffer) +DGL_EXT(PFNGLCHECKFRAMEBUFFERSTATUSPROC, glCheckFramebufferStatus) +DGL_EXT(PFNGLDELETEFRAMEBUFFERSPROC, glDeleteFramebuffers) +DGL_EXT(PFNGLDELETERENDERBUFFERSPROC, glDeleteRenderbuffers) +DGL_EXT(PFNGLFRAMEBUFFERTEXTURE2DPROC, glFramebufferTexture2D) +DGL_EXT(PFNGLFRAMEBUFFERRENDERBUFFERPROC, glFramebufferRenderbuffer) +DGL_EXT(PFNGLGENFRAMEBUFFERSPROC, glGenFramebuffers) +DGL_EXT(PFNGLGENRENDERBUFFERSPROC, glGenRenderbuffers) +DGL_EXT(PFNGLRENDERBUFFERSTORAGEPROC, glRenderbufferStorage) +# endif +# ifdef DGL_USE_OPENGL3 +DGL_EXT(PFNGLBINDBUFFERRANGEPROC, glBindBufferRange) +DGL_EXT(PFNGLBINDVERTEXARRAYPROC, glBindVertexArray) +DGL_EXT(PFNGLDELETEVERTEXARRAYSPROC, glDeleteVertexArrays) +DGL_EXT(PFNGLGENERATEMIPMAPPROC, glGenerateMipmap) +DGL_EXT(PFNGLGETUNIFORMBLOCKINDEXPROC, glGetUniformBlockIndex) +DGL_EXT(PFNGLGENVERTEXARRAYSPROC, glGenVertexArrays) +DGL_EXT(PFNGLUNIFORMBLOCKBINDINGPROC, glUniformBlockBinding) +# endif # undef DGL_EXT - } + needsInit = false; +# if defined(__GNUC__) && (__GNUC__ >= 9) +# pragma GCC diagnostic pop +# endif #endif - return nvgCreateGL(flags); + return nvgCreateGLfn(flags); } // ----------------------------------------------------------------------- +// DGL Color class conversion -START_NAMESPACE_DGL +Color::Color(const NVGcolor& c) noexcept + : red(c.r), green(c.g), blue(c.b), alpha(c.a) +{ + fixBounds(); +} + +Color::operator NVGcolor() const noexcept +{ + NVGcolor nc; + nc.r = red; + nc.g = green; + nc.b = blue; + nc.a = alpha; + return nc; +} // ----------------------------------------------------------------------- // NanoImage @@ -153,6 +244,7 @@ NanoImage& NanoImage::operator=(const Handle& handle) fHandle.context = handle.context; fHandle.imageId = handle.imageId; + _updateSize(); return *this; } @@ -220,15 +312,10 @@ NanoVG::Paint::operator NVGpaint() const noexcept // NanoVG NanoVG::NanoVG(int flags) - : fContext(nvgCreateGL_helper(flags)), + : fContext(nvgCreateGL(flags)), fInFrame(false), fIsSubWidget(false) {} -NanoVG::NanoVG(NanoWidget* groupWidget) - : fContext(groupWidget->fContext), - fInFrame(false), - fIsSubWidget(true) {} - NanoVG::~NanoVG() { DISTRHO_SAFE_ASSERT(! fInFrame); @@ -241,26 +328,28 @@ NanoVG::~NanoVG() void NanoVG::beginFrame(const uint width, const uint height, const float scaleFactor) { - if (fContext == nullptr) return; DISTRHO_SAFE_ASSERT_RETURN(scaleFactor > 0.0f,); DISTRHO_SAFE_ASSERT_RETURN(! fInFrame,); - fInFrame = true; - nvgBeginFrame(fContext, static_cast(width), static_cast(height), scaleFactor); + + if (fContext != nullptr) + nvgBeginFrame(fContext, static_cast(width), static_cast(height), scaleFactor); } void NanoVG::beginFrame(Widget* const widget) { DISTRHO_SAFE_ASSERT_RETURN(widget != nullptr,); DISTRHO_SAFE_ASSERT_RETURN(! fInFrame,); - fInFrame = true; if (fContext == nullptr) return; - Window& window(widget->getParentWindow()); - nvgBeginFrame(fContext, static_cast(window.getWidth()), static_cast(window.getHeight()), 1.0f); + if (TopLevelWidget* const tlw = widget->getTopLevelWidget()) + nvgBeginFrame(fContext, + static_cast(tlw->getWidth()), + static_cast(tlw->getHeight()), + tlw->getScaleFactor()); } void NanoVG::cancelFrame() @@ -424,6 +513,12 @@ void NanoVG::globalAlpha(float alpha) nvgGlobalAlpha(fContext, alpha); } +void NanoVG::globalTint(Color tint) +{ + if (fContext != nullptr) + nvgGlobalTint(fContext, tint); +} + // ----------------------------------------------------------------------- // Transforms @@ -447,10 +542,8 @@ void NanoVG::translate(float x, float y) void NanoVG::rotate(float angle) { - if (fContext == nullptr) return; - DISTRHO_SAFE_ASSERT_RETURN(angle > 0.0f,); - - nvgRotate(fContext, angle); + if (fContext != nullptr) + nvgRotate(fContext, angle); } void NanoVG::skewX(float angle) @@ -574,6 +667,45 @@ NanoImage::Handle NanoVG::createImageFromMemory(uchar* data, uint dataSize, int return NanoImage::Handle(fContext, nvgCreateImageMem(fContext, imageFlags, data,static_cast(dataSize))); } +NanoImage::Handle NanoVG::createImageFromRawMemory(uint w, uint h, const uchar* data, + ImageFlags imageFlags, ImageFormat format) +{ + return createImageFromRawMemory(w, h, data, static_cast(imageFlags), format); +} + +NanoImage::Handle NanoVG::createImageFromRawMemory(uint w, uint h, const uchar* data, + int imageFlags, ImageFormat format) +{ + if (fContext == nullptr) return NanoImage::Handle(); + DISTRHO_SAFE_ASSERT_RETURN(data != nullptr, NanoImage::Handle()); + + NVGtexture nvgformat; + switch (format) + { + case kImageFormatGrayscale: + nvgformat = NVG_TEXTURE_ALPHA; + break; + case kImageFormatBGR: + nvgformat = NVG_TEXTURE_BGR; + break; + case kImageFormatBGRA: + nvgformat = NVG_TEXTURE_BGRA; + break; + case kImageFormatRGB: + nvgformat = NVG_TEXTURE_RGB; + break; + case kImageFormatRGBA: + nvgformat = NVG_TEXTURE_RGBA; + break; + default: + return NanoImage::Handle(); + } + + return NanoImage::Handle(fContext, nvgCreateImageRaw(fContext, + static_cast(w), + static_cast(h), imageFlags, nvgformat, data)); +} + NanoImage::Handle NanoVG::createImageFromRGBA(uint w, uint h, const uchar* data, ImageFlags imageFlags) { return createImageFromRGBA(w, h, data, static_cast(imageFlags)); @@ -589,12 +721,14 @@ NanoImage::Handle NanoVG::createImageFromRGBA(uint w, uint h, const uchar* data, static_cast(h), imageFlags, data)); } -NanoImage::Handle NanoVG::createImageFromTextureHandle(GLuint textureId, uint w, uint h, ImageFlags imageFlags, bool deleteTexture) +NanoImage::Handle NanoVG::createImageFromTextureHandle(GLuint textureId, uint w, uint h, + ImageFlags imageFlags, bool deleteTexture) { return createImageFromTextureHandle(textureId, w, h, static_cast(imageFlags), deleteTexture); } -NanoImage::Handle NanoVG::createImageFromTextureHandle(GLuint textureId, uint w, uint h, int imageFlags, bool deleteTexture) +NanoImage::Handle NanoVG::createImageFromTextureHandle(GLuint textureId, uint w, uint h, + int imageFlags, bool deleteTexture) { if (fContext == nullptr) return NanoImage::Handle(); DISTRHO_SAFE_ASSERT_RETURN(textureId != 0, NanoImage::Handle()); @@ -758,26 +892,26 @@ void NanoVG::stroke() NanoVG::FontId NanoVG::createFontFromFile(const char* name, const char* filename) { - if (fContext == nullptr) return -1; DISTRHO_SAFE_ASSERT_RETURN(name != nullptr && name[0] != '\0', -1); DISTRHO_SAFE_ASSERT_RETURN(filename != nullptr && filename[0] != '\0', -1); + DISTRHO_SAFE_ASSERT_RETURN(fContext != nullptr, -1); return nvgCreateFont(fContext, name, filename); } NanoVG::FontId NanoVG::createFontFromMemory(const char* name, const uchar* data, uint dataSize, bool freeData) { - if (fContext == nullptr) return -1; DISTRHO_SAFE_ASSERT_RETURN(name != nullptr && name[0] != '\0', -1); DISTRHO_SAFE_ASSERT_RETURN(data != nullptr, -1); + DISTRHO_SAFE_ASSERT_RETURN(fContext != nullptr, -1); return nvgCreateFontMem(fContext, name, const_cast(data), static_cast(dataSize), freeData); } NanoVG::FontId NanoVG::findFont(const char* name) { - if (fContext == nullptr) return -1; DISTRHO_SAFE_ASSERT_RETURN(name != nullptr && name[0] != '\0', -1); + DISTRHO_SAFE_ASSERT_RETURN(fContext != nullptr, -1); return nvgFindFont(fContext, name); } @@ -899,77 +1033,56 @@ int NanoVG::textBreakLines(const char* string, const char* end, float breakRowWi } #ifndef DGL_NO_SHARED_RESOURCES -void NanoVG::loadSharedResources() +bool NanoVG::loadSharedResources() { + if (fContext == nullptr) return false; + if (nvgFindFont(fContext, NANOVG_DEJAVU_SANS_TTF) >= 0) - return; + return true; using namespace dpf_resources; - nvgCreateFontMem(fContext, NANOVG_DEJAVU_SANS_TTF, (const uchar*)dejavusans_ttf, dejavusans_ttf_size, 0); + return nvgCreateFontMem(fContext, NANOVG_DEJAVU_SANS_TTF, (uchar*)dejavusans_ttf, dejavusans_ttf_size, 0) >= 0; } #endif // ----------------------------------------------------------------------- +// NanoSubWidget -struct NanoWidget::PrivateData { - NanoWidget* const self; - std::vector subWidgets; - - PrivateData(NanoWidget* const s) - : self(s), - subWidgets() {} - - ~PrivateData() - { - subWidgets.clear(); - } -}; - -NanoWidget::NanoWidget(Window& parent, int flags) - : Widget(parent), - NanoVG(flags), - nData(new PrivateData(this)) +template <> +NanoBaseWidget::NanoBaseWidget(Widget* const parent, int flags) + : SubWidget(parent), + NanoVG(flags) { - pData->needsScaling = true; + setNeedsViewportScaling(); } -NanoWidget::NanoWidget(Widget* groupWidget, int flags) - : Widget(groupWidget, true), - NanoVG(flags), - nData(new PrivateData(this)) -{ - pData->needsScaling = true; -} +template class NanoBaseWidget; -NanoWidget::NanoWidget(NanoWidget* groupWidget) - : Widget(groupWidget, false), - NanoVG(groupWidget), - nData(new PrivateData(this)) -{ - pData->needsScaling = true; - pData->skipDisplay = true; - groupWidget->nData->subWidgets.push_back(this); -} +// ----------------------------------------------------------------------- +// NanoTopLevelWidget -NanoWidget::~NanoWidget() -{ - delete nData; -} +template <> +NanoBaseWidget::NanoBaseWidget(Window& windowToMapTo, int flags) + : TopLevelWidget(windowToMapTo), + NanoVG(flags) {} -void NanoWidget::onDisplay() -{ - NanoVG::beginFrame(getWidth(), getHeight()); - onNanoDisplay(); +template class NanoBaseWidget; - for (std::vector::iterator it = nData->subWidgets.begin(); it != nData->subWidgets.end(); ++it) - { - NanoWidget* const widget(*it); - widget->onNanoDisplay(); - } +// ----------------------------------------------------------------------- +// NanoStandaloneWindow - NanoVG::endFrame(); -} +template <> +NanoBaseWidget::NanoBaseWidget(Application& app, int flags) + : StandaloneWindow(app), + NanoVG(flags) {} + +template <> +NanoBaseWidget::NanoBaseWidget(Application& app, Window& parentWindow, int flags) + : StandaloneWindow(app, parentWindow), + NanoVG(flags) {} + +template class NanoBaseWidget; // ----------------------------------------------------------------------- diff --git a/source/modules/dgl/src/OpenGL.cpp b/source/modules/dgl/src/OpenGL.cpp new file mode 100644 index 000000000..0992cc32c --- /dev/null +++ b/source/modules/dgl/src/OpenGL.cpp @@ -0,0 +1,796 @@ +/* + * DISTRHO Plugin Framework (DPF) + * Copyright (C) 2012-2021 Filipe Coelho + * + * Permission to use, copy, modify, and/or distribute this software for any purpose with + * or without fee is hereby granted, provided that the above copyright notice and this + * permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD + * TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN + * NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER + * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifdef _MSC_VER +// instantiated template classes whose methods are defined elsewhere +# pragma warning(disable:4661) +#endif + +#include "../OpenGL.hpp" +#include "../Color.hpp" +#include "../ImageWidgets.hpp" + +#include "SubWidgetPrivateData.hpp" +#include "TopLevelWidgetPrivateData.hpp" +#include "WidgetPrivateData.hpp" +#include "WindowPrivateData.hpp" + +// templated classes +#include "ImageBaseWidgets.cpp" + +START_NAMESPACE_DGL + +// ----------------------------------------------------------------------- + +#ifdef DGL_USE_OPENGL3 +static void notImplemented(const char* const name) +{ + d_stderr2("OpenGL3 function not implemented: %s", name); +} +#endif + +// ----------------------------------------------------------------------- +// Color + +void Color::setFor(const GraphicsContext&, const bool includeAlpha) +{ +#ifndef DGL_USE_OPENGL3 + if (includeAlpha) + glColor4f(red, green, blue, alpha); + else + glColor3f(red, green, blue); +#else + notImplemented("Color::setFor"); + // unused + (void)includeAlpha; +#endif +} + +// ----------------------------------------------------------------------- +// Line + +#ifndef DGL_USE_OPENGL3 +template +static void drawLine(const Point& posStart, const Point& posEnd) +{ + DISTRHO_SAFE_ASSERT_RETURN(posStart != posEnd,); + + glBegin(GL_LINES); + + { + glVertex2d(posStart.getX(), posStart.getY()); + glVertex2d(posEnd.getX(), posEnd.getY()); + } + + glEnd(); +} +#endif + +template +void Line::draw(const GraphicsContext&, const T width) +{ +#ifndef DGL_USE_OPENGL3 + DISTRHO_SAFE_ASSERT_RETURN(width != 0,); + + glLineWidth(static_cast(width)); + drawLine(posStart, posEnd); +#else + notImplemented("Line::draw"); +#endif +} + +// deprecated calls +template +void Line::draw() +{ +#ifndef DGL_USE_OPENGL3 + drawLine(posStart, posEnd); +#else + notImplemented("Line::draw"); +#endif +} + +template class Line; +template class Line; +template class Line; +template class Line; +template class Line; +template class Line; + +// ----------------------------------------------------------------------- +// Circle + +#ifndef DGL_USE_OPENGL3 +template +static void drawCircle(const Point& pos, + const uint numSegments, + const float size, + const float sin, + const float cos, + const bool outline) +{ + DISTRHO_SAFE_ASSERT_RETURN(numSegments >= 3 && size > 0.0f,); + + const T origx = pos.getX(); + const T origy = pos.getY(); + double t, x = size, y = 0.0; + + glBegin(outline ? GL_LINE_LOOP : GL_POLYGON); + + for (uint i=0; i +void Circle::draw(const GraphicsContext&) +{ +#ifndef DGL_USE_OPENGL3 + drawCircle(fPos, fNumSegments, fSize, fSin, fCos, false); +#else + notImplemented("Circle::draw"); +#endif +} + +template +void Circle::drawOutline(const GraphicsContext&, const T lineWidth) +{ + DISTRHO_SAFE_ASSERT_RETURN(lineWidth != 0,); + + glLineWidth(static_cast(lineWidth)); +#ifndef DGL_USE_OPENGL3 + drawCircle(fPos, fNumSegments, fSize, fSin, fCos, true); +#else + notImplemented("Circle::drawOutline"); +#endif +} + +// deprecated calls +template +void Circle::draw() +{ +#ifndef DGL_USE_OPENGL3 + drawCircle(fPos, fNumSegments, fSize, fSin, fCos, false); +#else + notImplemented("Circle::draw"); +#endif +} + +template +void Circle::drawOutline() +{ +#ifndef DGL_USE_OPENGL3 + drawCircle(fPos, fNumSegments, fSize, fSin, fCos, true); +#else + notImplemented("Circle::drawOutline"); +#endif +} + +template class Circle; +template class Circle; +template class Circle; +template class Circle; +template class Circle; +template class Circle; + +// ----------------------------------------------------------------------- +// Triangle + +#ifndef DGL_USE_OPENGL3 +template +static void drawTriangle(const Point& pos1, + const Point& pos2, + const Point& pos3, + const bool outline) +{ + DISTRHO_SAFE_ASSERT_RETURN(pos1 != pos2 && pos1 != pos3,); + + glBegin(outline ? GL_LINE_LOOP : GL_TRIANGLES); + + { + glVertex2d(pos1.getX(), pos1.getY()); + glVertex2d(pos2.getX(), pos2.getY()); + glVertex2d(pos3.getX(), pos3.getY()); + } + + glEnd(); +} +#endif + +template +void Triangle::draw(const GraphicsContext&) +{ +#ifndef DGL_USE_OPENGL3 + drawTriangle(pos1, pos2, pos3, false); +#else + notImplemented("Triangle::draw"); +#endif +} + +template +void Triangle::drawOutline(const GraphicsContext&, const T lineWidth) +{ + DISTRHO_SAFE_ASSERT_RETURN(lineWidth != 0,); + + glLineWidth(static_cast(lineWidth)); +#ifndef DGL_USE_OPENGL3 + drawTriangle(pos1, pos2, pos3, true); +#else + notImplemented("Triangle::drawOutline"); +#endif +} + +// deprecated calls +template +void Triangle::draw() +{ +#ifndef DGL_USE_OPENGL3 + drawTriangle(pos1, pos2, pos3, false); +#else + notImplemented("Triangle::draw"); +#endif +} + +template +void Triangle::drawOutline() +{ +#ifndef DGL_USE_OPENGL3 + drawTriangle(pos1, pos2, pos3, true); +#else + notImplemented("Triangle::drawOutline"); +#endif +} + +template class Triangle; +template class Triangle; +template class Triangle; +template class Triangle; +template class Triangle; +template class Triangle; + +// ----------------------------------------------------------------------- +// Rectangle + +#ifndef DGL_USE_OPENGL3 +template +static void drawRectangle(const Rectangle& rect, const bool outline) +{ + DISTRHO_SAFE_ASSERT_RETURN(rect.isValid(),); + + glBegin(outline ? GL_LINE_LOOP : GL_QUADS); + + { + const T x = rect.getX(); + const T y = rect.getY(); + const T w = rect.getWidth(); + const T h = rect.getHeight(); + + glTexCoord2f(0.0f, 0.0f); + glVertex2d(x, y); + + glTexCoord2f(1.0f, 0.0f); + glVertex2d(x+w, y); + + glTexCoord2f(1.0f, 1.0f); + glVertex2d(x+w, y+h); + + glTexCoord2f(0.0f, 1.0f); + glVertex2d(x, y+h); + } + + glEnd(); +} +#endif + +template +void Rectangle::draw(const GraphicsContext&) +{ +#ifndef DGL_USE_OPENGL3 + drawRectangle(*this, false); +#else + notImplemented("Rectangle::draw"); +#endif +} + +template +void Rectangle::drawOutline(const GraphicsContext&, const T lineWidth) +{ + DISTRHO_SAFE_ASSERT_RETURN(lineWidth != 0,); + + glLineWidth(static_cast(lineWidth)); +#ifndef DGL_USE_OPENGL3 + drawRectangle(*this, true); +#else + notImplemented("Rectangle::drawOutline"); +#endif +} + +// deprecated calls +template +void Rectangle::draw() +{ +#ifndef DGL_USE_OPENGL3 + drawRectangle(*this, false); +#else + notImplemented("Rectangle::draw"); +#endif +} + +template +void Rectangle::drawOutline() +{ +#ifndef DGL_USE_OPENGL3 + drawRectangle(*this, true); +#else + notImplemented("Rectangle::drawOutline"); +#endif +} + +template class Rectangle; +template class Rectangle; +template class Rectangle; +template class Rectangle; +template class Rectangle; +template class Rectangle; + +// ----------------------------------------------------------------------- +// OpenGLImage + +static void setupOpenGLImage(const OpenGLImage& image, GLuint textureId) +{ + DISTRHO_SAFE_ASSERT_RETURN(image.isValid(),); + + glEnable(GL_TEXTURE_2D); + glBindTexture(GL_TEXTURE_2D, textureId); + + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER); + + static const float trans[] = { 0.0f, 0.0f, 0.0f, 0.0f }; + glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, trans); + + glPixelStorei(GL_PACK_ALIGNMENT, 1); + glPixelStorei(GL_UNPACK_ALIGNMENT, 1); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, + static_cast(image.getWidth()), + static_cast(image.getHeight()), + 0, + asOpenGLImageFormat(image.getFormat()), GL_UNSIGNED_BYTE, image.getRawData()); + + glBindTexture(GL_TEXTURE_2D, 0); + glDisable(GL_TEXTURE_2D); +} + +static void drawOpenGLImage(const OpenGLImage& image, const Point& pos, const GLuint textureId, bool& setupCalled) +{ + if (textureId == 0 || image.isInvalid()) + return; + + if (! setupCalled) + { + setupOpenGLImage(image, textureId); + setupCalled = true; + } + +#ifndef DGL_USE_OPENGL3 + glColor4f(1.0f, 1.0f, 1.0f, 1.0f); +#endif + + glEnable(GL_TEXTURE_2D); + glBindTexture(GL_TEXTURE_2D, textureId); + +#ifndef DGL_USE_OPENGL3 + glBegin(GL_QUADS); + + { + const int x = pos.getX(); + const int y = pos.getY(); + const int w = static_cast(image.getWidth()); + const int h = static_cast(image.getHeight()); + + glTexCoord2f(0.0f, 0.0f); + glVertex2d(x, y); + + glTexCoord2f(1.0f, 0.0f); + glVertex2d(x+w, y); + + glTexCoord2f(1.0f, 1.0f); + glVertex2d(x+w, y+h); + + glTexCoord2f(0.0f, 1.0f); + glVertex2d(x, y+h); + } + + glEnd(); +#endif + + glBindTexture(GL_TEXTURE_2D, 0); + glDisable(GL_TEXTURE_2D); +} + +OpenGLImage::OpenGLImage() + : ImageBase(), + textureId(0), + setupCalled(false) +{ + glGenTextures(1, &textureId); + DISTRHO_SAFE_ASSERT(textureId != 0); +} + +OpenGLImage::OpenGLImage(const char* const rdata, const uint w, const uint h, const ImageFormat fmt) + : ImageBase(rdata, w, h, fmt), + textureId(0), + setupCalled(false) +{ + glGenTextures(1, &textureId); + DISTRHO_SAFE_ASSERT(textureId != 0); +} + +OpenGLImage::OpenGLImage(const char* const rdata, const Size& s, const ImageFormat fmt) + : ImageBase(rdata, s, fmt), + textureId(0), + setupCalled(false) +{ + glGenTextures(1, &textureId); + DISTRHO_SAFE_ASSERT(textureId != 0); +} + +OpenGLImage::OpenGLImage(const OpenGLImage& image) + : ImageBase(image), + textureId(0), + setupCalled(false) +{ + glGenTextures(1, &textureId); + DISTRHO_SAFE_ASSERT(textureId != 0); +} + +OpenGLImage::~OpenGLImage() +{ + if (textureId != 0) + glDeleteTextures(1, &textureId); +} + +void OpenGLImage::loadFromMemory(const char* const rdata, const Size& s, const ImageFormat fmt) noexcept +{ + setupCalled = false; + ImageBase::loadFromMemory(rdata, s, fmt); +} + +void OpenGLImage::drawAt(const GraphicsContext&, const Point& pos) +{ + drawOpenGLImage(*this, pos, textureId, setupCalled); +} + +OpenGLImage& OpenGLImage::operator=(const OpenGLImage& image) noexcept +{ + rawData = image.rawData; + size = image.size; + format = image.format; + setupCalled = false; + return *this; +} + +// deprecated calls +OpenGLImage::OpenGLImage(const char* const rdata, const uint w, const uint h, const GLenum fmt) + : ImageBase(rdata, w, h, asDISTRHOImageFormat(fmt)), + textureId(0), + setupCalled(false) +{ + glGenTextures(1, &textureId); + DISTRHO_SAFE_ASSERT(textureId != 0); +} + +OpenGLImage::OpenGLImage(const char* const rdata, const Size& s, const GLenum fmt) + : ImageBase(rdata, s, asDISTRHOImageFormat(fmt)), + textureId(0), + setupCalled(false) +{ + glGenTextures(1, &textureId); + DISTRHO_SAFE_ASSERT(textureId != 0); +} + +void OpenGLImage::draw() +{ + drawOpenGLImage(*this, Point(0, 0), textureId, setupCalled); +} + +void OpenGLImage::drawAt(const int x, const int y) +{ + drawOpenGLImage(*this, Point(x, y), textureId, setupCalled); +} + +void OpenGLImage::drawAt(const Point& pos) +{ + drawOpenGLImage(*this, pos, textureId, setupCalled); +} + +// ----------------------------------------------------------------------- +// ImageBaseAboutWindow + +#if 0 +template <> +void ImageBaseAboutWindow::onDisplay() +{ + const GraphicsContext& context(getGraphicsContext()); + img.draw(context); +} +#endif + +template class ImageBaseAboutWindow; + +// ----------------------------------------------------------------------- +// ImageBaseButton + +template class ImageBaseButton; + +// ----------------------------------------------------------------------- +// ImageBaseKnob + +template <> +void ImageBaseKnob::PrivateData::init() +{ + glTextureId = 0; + glGenTextures(1, &glTextureId); +} + +template <> +void ImageBaseKnob::PrivateData::cleanup() +{ + if (glTextureId == 0) + return; + + glDeleteTextures(1, &glTextureId); + glTextureId = 0; +} + +template <> +void ImageBaseKnob::onDisplay() +{ + const GraphicsContext& context(getGraphicsContext()); + const float normValue = getNormalizedValue(); + + glEnable(GL_TEXTURE_2D); + glBindTexture(GL_TEXTURE_2D, pData->glTextureId); + + if (! pData->isReady) + { + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER); + + static const float trans[] = { 0.0f, 0.0f, 0.0f, 0.0f }; + glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, trans); + + glPixelStorei(GL_PACK_ALIGNMENT, 1); + glPixelStorei(GL_UNPACK_ALIGNMENT, 1); + + uint imageDataOffset = 0; + + if (pData->rotationAngle == 0) + { + DISTRHO_SAFE_ASSERT_RETURN(pData->imgLayerCount > 0,); + DISTRHO_SAFE_ASSERT_RETURN(normValue >= 0.0f,); + + const uint& v1(pData->isImgVertical ? pData->imgLayerWidth : pData->imgLayerHeight); + const uint& v2(pData->isImgVertical ? pData->imgLayerHeight : pData->imgLayerWidth); + + // TODO kImageFormatGreyscale + const uint layerDataSize = v1 * v2 * ((pData->image.getFormat() == kImageFormatBGRA || + pData->image.getFormat() == kImageFormatRGBA) ? 4 : 3); + /* */ imageDataOffset = layerDataSize * uint(normValue * float(pData->imgLayerCount-1)); + } + + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, + static_cast(getWidth()), static_cast(getHeight()), 0, + asOpenGLImageFormat(pData->image.getFormat()), GL_UNSIGNED_BYTE, pData->image.getRawData() + imageDataOffset); + + pData->isReady = true; + } + + const int w = static_cast(getWidth()); + const int h = static_cast(getHeight()); + + if (pData->rotationAngle != 0) + { +#ifndef DGL_USE_OPENGL3 + glPushMatrix(); +#endif + + const int w2 = w/2; + const int h2 = h/2; + +#ifndef DGL_USE_OPENGL3 + glTranslatef(static_cast(w2), static_cast(h2), 0.0f); + glRotatef(normValue*static_cast(pData->rotationAngle), 0.0f, 0.0f, 1.0f); +#endif + + Rectangle(-w2, -h2, w, h).draw(context); + +#ifndef DGL_USE_OPENGL3 + glPopMatrix(); +#endif + } + else + { + Rectangle(0, 0, w, h).draw(context); + } + + glBindTexture(GL_TEXTURE_2D, 0); + glDisable(GL_TEXTURE_2D); +} + +template class ImageBaseKnob; + +// ----------------------------------------------------------------------- +// ImageBaseSlider + +template class ImageBaseSlider; + +// ----------------------------------------------------------------------- +// ImageBaseSwitch + +template class ImageBaseSwitch; + +// ----------------------------------------------------------------------- + +void SubWidget::PrivateData::display(const uint width, const uint height, const double autoScaleFactor) +{ + if (skipDrawing) + return; + + bool needsDisableScissor = false; + + if (needsViewportScaling) + { + // limit viewport to widget bounds + const int x = absolutePos.getX(); + const int w = static_cast(self->getWidth()); + const int h = static_cast(self->getHeight()); + + if (viewportScaleFactor != 0.0 && viewportScaleFactor != 1.0) + { + glViewport(x, + -static_cast(height * viewportScaleFactor - height + absolutePos.getY() + 0.5), + static_cast(width * viewportScaleFactor + 0.5), + static_cast(height * viewportScaleFactor + 0.5)); + } + else + { + const int y = static_cast(height - self->getHeight()) - absolutePos.getY(); + glViewport(x, y, w, h); + } + } + else if (needsFullViewportForDrawing || (absolutePos.isZero() && self->getSize() == Size(width, height))) + { + // full viewport size + glViewport(0, + -static_cast(height * autoScaleFactor - height + 0.5), + static_cast(width * autoScaleFactor + 0.5), + static_cast(height * autoScaleFactor + 0.5)); + } + else + { + // set viewport pos + glViewport(static_cast(absolutePos.getX() * autoScaleFactor + 0.5), + -static_cast(std::round((height * autoScaleFactor - height) + + (absolutePos.getY() * autoScaleFactor))), + static_cast(std::round(width * autoScaleFactor)), + static_cast(std::round(height * autoScaleFactor))); + + // then cut the outer bounds + glScissor(static_cast(absolutePos.getX() * autoScaleFactor + 0.5), + static_cast(height - std::round((static_cast(self->getHeight()) + absolutePos.getY()) + * autoScaleFactor)), + static_cast(std::round(self->getWidth() * autoScaleFactor)), + static_cast(std::round(self->getHeight() * autoScaleFactor))); + + glEnable(GL_SCISSOR_TEST); + needsDisableScissor = true; + } + + // display widget + self->onDisplay(); + + if (needsDisableScissor) + glDisable(GL_SCISSOR_TEST); + + selfw->pData->displaySubWidgets(width, height, autoScaleFactor); +} + +// ----------------------------------------------------------------------- + +void TopLevelWidget::PrivateData::display() +{ + if (! selfw->pData->visible) + return; + + const Size size(window.getSize()); + const uint width = size.getWidth(); + const uint height = size.getHeight(); + + const double autoScaleFactor = window.pData->autoScaleFactor; + + // full viewport size + if (window.pData->autoScaling) + { + glViewport(0, + -static_cast(height * autoScaleFactor - height + 0.5), + static_cast(width * autoScaleFactor + 0.5), + static_cast(height * autoScaleFactor + 0.5)); + } + else + { + glViewport(0, 0, static_cast(width), static_cast(height)); + } + + // main widget drawing + self->onDisplay(); + + // now draw subwidgets if there are any + selfw->pData->displaySubWidgets(width, height, autoScaleFactor); +} + +// ----------------------------------------------------------------------- + +void Window::PrivateData::renderToPicture(const char* const filename, + const GraphicsContext&, + const uint width, + const uint height) +{ + FILE* const f = fopen(filename, "w"); + DISTRHO_SAFE_ASSERT_RETURN(f != nullptr,); + + GLubyte* const pixels = new GLubyte[width * height * 3 * sizeof(GLubyte)]; + + glFlush(); + glReadPixels(0, 0, static_cast(width), static_cast(height), GL_RGB, GL_UNSIGNED_BYTE, pixels); + + fprintf(f, "P3\n%d %d\n255\n", width, height); + for (uint y = 0; y < height; y++) + { + for (uint i, x = 0; x < width; x++) + { + i = 3 * ((height - y - 1) * width + x); + fprintf(f, "%3d %3d %3d ", pixels[i], pixels[i+1], pixels[i+2]); + } + fprintf(f, "\n"); + } + + delete[] pixels; + fclose(f); +} + +// ----------------------------------------------------------------------- + +const GraphicsContext& Window::PrivateData::getGraphicsContext() const noexcept +{ + return (const GraphicsContext&)graphicsContext; +} + +// ----------------------------------------------------------------------- + +END_NAMESPACE_DGL diff --git a/source/modules/dgl/src/SubWidget.cpp b/source/modules/dgl/src/SubWidget.cpp new file mode 100644 index 000000000..d0f389170 --- /dev/null +++ b/source/modules/dgl/src/SubWidget.cpp @@ -0,0 +1,189 @@ +/* + * DISTRHO Plugin Framework (DPF) + * Copyright (C) 2012-2021 Filipe Coelho + * + * Permission to use, copy, modify, and/or distribute this software for any purpose with + * or without fee is hereby granted, provided that the above copyright notice and this + * permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD + * TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN + * NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER + * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "SubWidgetPrivateData.hpp" +#include "WidgetPrivateData.hpp" +#include "../TopLevelWidget.hpp" + +START_NAMESPACE_DGL + +// -------------------------------------------------------------------------------------------------------------------- + +SubWidget::SubWidget(Widget* const parentWidget) + : Widget(parentWidget), + pData(new PrivateData(this, parentWidget)) {} + +SubWidget::~SubWidget() +{ + delete pData; +} + +template +bool SubWidget::contains(const T x, const T y) const noexcept +{ + return Rectangle(0, 0, + static_cast(getWidth()), + static_cast(getHeight())).contains(x, y); +} + +template +bool SubWidget::contains(const Point& pos) const noexcept +{ + return contains(pos.getX(), pos.getY()); +} + +int SubWidget::getAbsoluteX() const noexcept +{ + return pData->absolutePos.getX(); +} + +int SubWidget::getAbsoluteY() const noexcept +{ + return pData->absolutePos.getY(); +} + +Point SubWidget::getAbsolutePos() const noexcept +{ + return pData->absolutePos; +} + +Rectangle SubWidget::getAbsoluteArea() const noexcept +{ + return Rectangle(getAbsolutePos(), getSize().toInt()); +} + +Rectangle SubWidget::getConstrainedAbsoluteArea() const noexcept +{ + const int x = getAbsoluteX(); + const int y = getAbsoluteY(); + + if (x >= 0 && y >= 0) + return Rectangle(x, y, getSize()); + + const int xOffset = std::min(0, x); + const int yOffset = std::min(0, y); + const int width = std::max(0, static_cast(getWidth()) + xOffset); + const int height = std::max(0, static_cast(getHeight()) + yOffset); + + return Rectangle(0, 0, static_cast(width), static_cast(height)); +} + +void SubWidget::setAbsoluteX(const int x) noexcept +{ + setAbsolutePos(Point(x, getAbsoluteY())); +} + +void SubWidget::setAbsoluteY(const int y) noexcept +{ + setAbsolutePos(Point(getAbsoluteX(), y)); +} + +void SubWidget::setAbsolutePos(const int x, const int y) noexcept +{ + setAbsolutePos(Point(x, y)); +} + +void SubWidget::setAbsolutePos(const Point& pos) noexcept +{ + if (pData->absolutePos == pos) + return; + + PositionChangedEvent ev; + ev.oldPos = pData->absolutePos; + ev.pos = pos; + + pData->absolutePos = pos; + onPositionChanged(ev); + + repaint(); +} + +Point SubWidget::getMargin() const noexcept +{ + return pData->margin; +} + +void SubWidget::setMargin(const int x, const int y) noexcept +{ + pData->margin = Point(x, y); +} + +void SubWidget::setMargin(const Point& offset) noexcept +{ + pData->margin = offset; +} + +Widget* SubWidget::getParentWidget() const noexcept +{ + return pData->parentWidget; +} + +void SubWidget::repaint() noexcept +{ + if (! isVisible()) + return; + + if (TopLevelWidget* const topw = getTopLevelWidget()) + { + if (pData->needsFullViewportForDrawing) + topw->repaint(); + else + topw->repaint(getConstrainedAbsoluteArea()); + } +} + +void SubWidget::toFront() +{ + std::list& subwidgets(pData->parentWidget->pData->subWidgets); + + subwidgets.remove(this); + subwidgets.push_back(this); +} + +void SubWidget::setNeedsFullViewportDrawing(const bool needsFullViewportForDrawing) +{ + pData->needsFullViewportForDrawing = needsFullViewportForDrawing; +} + +void SubWidget::setNeedsViewportScaling(const bool needsViewportScaling, const double autoScaleFactor) +{ + pData->needsViewportScaling = needsViewportScaling; + pData->viewportScaleFactor = autoScaleFactor; +} + +void SubWidget::setSkipDrawing(const bool skipDrawing) +{ + pData->skipDrawing = skipDrawing; +} + +void SubWidget::onPositionChanged(const PositionChangedEvent&) +{ +} + +// -------------------------------------------------------------------------------------------------------------------- +// Possible template data types + +template<> +bool SubWidget::contains(const Point& pos) const noexcept +{ + return contains(pos.getX(), pos.getY()); +} + +// float, int, uint, short, ushort + +// -------------------------------------------------------------------------------------------------------------------- + +END_NAMESPACE_DGL diff --git a/source/modules/dgl/src/SubWidgetPrivateData.cpp b/source/modules/dgl/src/SubWidgetPrivateData.cpp new file mode 100644 index 000000000..a0ce404cd --- /dev/null +++ b/source/modules/dgl/src/SubWidgetPrivateData.cpp @@ -0,0 +1,45 @@ +/* + * DISTRHO Plugin Framework (DPF) + * Copyright (C) 2012-2021 Filipe Coelho + * + * Permission to use, copy, modify, and/or distribute this software for any purpose with + * or without fee is hereby granted, provided that the above copyright notice and this + * permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD + * TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN + * NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER + * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "SubWidgetPrivateData.hpp" +#include "WidgetPrivateData.hpp" + +START_NAMESPACE_DGL + +// -------------------------------------------------------------------------------------------------------------------- + +SubWidget::PrivateData::PrivateData(SubWidget* const s, Widget* const pw) + : self(s), + selfw((Widget*)s), + parentWidget(pw), + absolutePos(), + margin(), + needsFullViewportForDrawing(false), + needsViewportScaling(false), + skipDrawing(false), + viewportScaleFactor(0.0) +{ + parentWidget->pData->subWidgets.push_back(self); +} + +SubWidget::PrivateData::~PrivateData() +{ + parentWidget->pData->subWidgets.remove(self); +} + +// -------------------------------------------------------------------------------------------------------------------- + +END_NAMESPACE_DGL diff --git a/source/modules/dgl/src/SubWidgetPrivateData.hpp b/source/modules/dgl/src/SubWidgetPrivateData.hpp new file mode 100644 index 000000000..a06f59d16 --- /dev/null +++ b/source/modules/dgl/src/SubWidgetPrivateData.hpp @@ -0,0 +1,50 @@ +/* + * DISTRHO Plugin Framework (DPF) + * Copyright (C) 2012-2021 Filipe Coelho + * + * Permission to use, copy, modify, and/or distribute this software for any purpose with + * or without fee is hereby granted, provided that the above copyright notice and this + * permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD + * TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN + * NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER + * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef DGL_SUBWIDGET_PRIVATE_DATA_HPP_INCLUDED +#define DGL_SUBWIDGET_PRIVATE_DATA_HPP_INCLUDED + +#include "../SubWidget.hpp" + +START_NAMESPACE_DGL + +// -------------------------------------------------------------------------------------------------------------------- + +struct SubWidget::PrivateData { + SubWidget* const self; + Widget* const selfw; + Widget* const parentWidget; + Point absolutePos; + Point margin; + bool needsFullViewportForDrawing; // needed for widgets drawing out of bounds + bool needsViewportScaling; // needed for NanoVG + bool skipDrawing; // for context reuse in NanoVG based guis + double viewportScaleFactor; // auto-scaling for NanoVG + + explicit PrivateData(SubWidget* const s, Widget* const pw); + ~PrivateData(); + + // NOTE display function is different depending on build type, must call displaySubWidgets at the end + void display(uint width, uint height, double autoScaleFactor); + + DISTRHO_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(PrivateData) +}; + +// -------------------------------------------------------------------------------------------------------------------- + +END_NAMESPACE_DGL + +#endif // DGL_SUBWIDGET_PRIVATE_DATA_HPP_INCLUDED diff --git a/source/modules/dgl/src/TopLevelWidget.cpp b/source/modules/dgl/src/TopLevelWidget.cpp new file mode 100644 index 000000000..f577a1c9c --- /dev/null +++ b/source/modules/dgl/src/TopLevelWidget.cpp @@ -0,0 +1,151 @@ +/* + * DISTRHO Plugin Framework (DPF) + * Copyright (C) 2012-2021 Filipe Coelho + * + * Permission to use, copy, modify, and/or distribute this software for any purpose with + * or without fee is hereby granted, provided that the above copyright notice and this + * permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD + * TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN + * NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER + * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "TopLevelWidgetPrivateData.hpp" +#include "../Window.hpp" + +START_NAMESPACE_DGL + +// -------------------------------------------------------------------------------------------------------------------- + +TopLevelWidget::TopLevelWidget(Window& windowToMapTo) + : Widget(this), + pData(new PrivateData(this, windowToMapTo)) {} + +TopLevelWidget::~TopLevelWidget() +{ + delete pData; +} + +Application& TopLevelWidget::getApp() const noexcept +{ + return pData->window.getApp(); +} + +Window& TopLevelWidget::getWindow() const noexcept +{ + return pData->window; +} + +void TopLevelWidget::setWidth(const uint width) +{ + pData->window.setWidth(width); +} + +void TopLevelWidget::setHeight(const uint height) +{ + pData->window.setHeight(height); +} + +void TopLevelWidget::setSize(const uint width, const uint height) +{ + pData->window.setSize(width, height); +} + +void TopLevelWidget::setSize(const Size& size) +{ + pData->window.setSize(size); +} + +bool TopLevelWidget::setClipboard(const char* const mimeType, const void* const data, const size_t dataSize) +{ + return pData->window.setClipboard(mimeType, data, dataSize); +} + +const void* TopLevelWidget::getClipboard(const char*& mimeType, size_t& dataSize) +{ + return pData->window.getClipboard(mimeType, dataSize); +} + +bool TopLevelWidget::setCursor(const MouseCursor cursor) +{ + return pData->window.setCursor(cursor); +} + +bool TopLevelWidget::addIdleCallback(IdleCallback* const callback, const uint timerFrequencyInMs) +{ + return pData->window.addIdleCallback(callback, timerFrequencyInMs); +} + +bool TopLevelWidget::removeIdleCallback(IdleCallback* const callback) +{ + return pData->window.removeIdleCallback(callback); +} + +double TopLevelWidget::getScaleFactor() const noexcept +{ + return pData->window.getScaleFactor(); +} + +void TopLevelWidget::repaint() noexcept +{ + pData->window.repaint(); +} + +void TopLevelWidget::repaint(const Rectangle& rect) noexcept +{ + pData->window.repaint(rect); +} + +void TopLevelWidget::setGeometryConstraints(const uint minimumWidth, + const uint minimumHeight, + const bool keepAspectRatio, + const bool automaticallyScale, + const bool resizeNowIfAutoScaling) +{ + pData->window.setGeometryConstraints(minimumWidth, + minimumHeight, + keepAspectRatio, + automaticallyScale, + resizeNowIfAutoScaling); +} + +// -------------------------------------------------------------------------------------------------------------------- + +bool TopLevelWidget::onKeyboard(const KeyboardEvent& ev) +{ + return pData->keyboardEvent(ev); +} + +bool TopLevelWidget::onCharacterInput(const CharacterInputEvent& ev) +{ + return pData->characterInputEvent(ev); +} + +bool TopLevelWidget::onMouse(const MouseEvent& ev) +{ + return pData->mouseEvent(ev); +} + +bool TopLevelWidget::onMotion(const MotionEvent& ev) +{ + return pData->motionEvent(ev); +} + +bool TopLevelWidget::onScroll(const ScrollEvent& ev) +{ + return pData->scrollEvent(ev); +} + +// -------------------------------------------------------------------------------------------------------------------- + +void TopLevelWidget::requestSizeChange(uint, uint) +{ +} + +// -------------------------------------------------------------------------------------------------------------------- + +END_NAMESPACE_DGL diff --git a/source/modules/dgl/src/TopLevelWidgetPrivateData.cpp b/source/modules/dgl/src/TopLevelWidgetPrivateData.cpp new file mode 100644 index 000000000..fc862250d --- /dev/null +++ b/source/modules/dgl/src/TopLevelWidgetPrivateData.cpp @@ -0,0 +1,134 @@ +/* + * DISTRHO Plugin Framework (DPF) + * Copyright (C) 2012-2021 Filipe Coelho + * + * Permission to use, copy, modify, and/or distribute this software for any purpose with + * or without fee is hereby granted, provided that the above copyright notice and this + * permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD + * TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN + * NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER + * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "TopLevelWidgetPrivateData.hpp" +#include "WidgetPrivateData.hpp" +#include "WindowPrivateData.hpp" +#include "pugl.hpp" + +START_NAMESPACE_DGL + +// ----------------------------------------------------------------------- + +TopLevelWidget::PrivateData::PrivateData(TopLevelWidget* const s, Window& w) + : self(s), + selfw(s), + window(w) +{ + window.pData->topLevelWidgets.push_back(self); +} + +TopLevelWidget::PrivateData::~PrivateData() +{ + window.pData->topLevelWidgets.remove(self); +} + +bool TopLevelWidget::PrivateData::keyboardEvent(const KeyboardEvent& ev) +{ + // ignore event if we are not visible + if (! selfw->pData->visible) + return false; + + // propagate event to all subwidgets recursively + return selfw->pData->giveKeyboardEventForSubWidgets(ev); +} + +bool TopLevelWidget::PrivateData::characterInputEvent(const CharacterInputEvent& ev) +{ + // ignore event if we are not visible + if (! selfw->pData->visible) + return false; + + // propagate event to all subwidgets recursively + return selfw->pData->giveCharacterInputEventForSubWidgets(ev); +} + +bool TopLevelWidget::PrivateData::mouseEvent(const MouseEvent& ev) +{ + // ignore event if we are not visible + if (! selfw->pData->visible) + return false; + + MouseEvent rev = ev; + + if (window.pData->autoScaling) + { + const double autoScaleFactor = window.pData->autoScaleFactor; + + rev.pos.setX(ev.pos.getX() / autoScaleFactor); + rev.pos.setY(ev.pos.getY() / autoScaleFactor); + rev.absolutePos.setX(ev.absolutePos.getX() / autoScaleFactor); + rev.absolutePos.setY(ev.absolutePos.getY() / autoScaleFactor); + } + + // propagate event to all subwidgets recursively + return selfw->pData->giveMouseEventForSubWidgets(rev); +} + +bool TopLevelWidget::PrivateData::motionEvent(const MotionEvent& ev) +{ + // ignore event if we are not visible + if (! selfw->pData->visible) + return false; + + MotionEvent rev = ev; + + if (window.pData->autoScaling) + { + const double autoScaleFactor = window.pData->autoScaleFactor; + + rev.pos.setX(ev.pos.getX() / autoScaleFactor); + rev.pos.setY(ev.pos.getY() / autoScaleFactor); + rev.absolutePos.setX(ev.absolutePos.getX() / autoScaleFactor); + rev.absolutePos.setY(ev.absolutePos.getY() / autoScaleFactor); + } + + // propagate event to all subwidgets recursively + return selfw->pData->giveMotionEventForSubWidgets(rev); +} + +bool TopLevelWidget::PrivateData::scrollEvent(const ScrollEvent& ev) +{ + // ignore event if we are not visible + if (! selfw->pData->visible) + return false; + + ScrollEvent rev = ev; + + if (window.pData->autoScaling) + { + const double autoScaleFactor = window.pData->autoScaleFactor; + + rev.pos.setX(ev.pos.getX() / autoScaleFactor); + rev.pos.setY(ev.pos.getY() / autoScaleFactor); + rev.absolutePos.setX(ev.absolutePos.getX() / autoScaleFactor); + rev.absolutePos.setY(ev.absolutePos.getY() / autoScaleFactor); + rev.delta.setX(ev.delta.getX() / autoScaleFactor); + rev.delta.setY(ev.delta.getY() / autoScaleFactor); + } + + // propagate event to all subwidgets recursively + return selfw->pData->giveScrollEventForSubWidgets(rev); +} + +void TopLevelWidget::PrivateData::fallbackOnResize() +{ + puglFallbackOnResize(window.pData->view); +} + +// ----------------------------------------------------------------------- + +END_NAMESPACE_DGL diff --git a/source/modules/dgl/src/TopLevelWidgetPrivateData.hpp b/source/modules/dgl/src/TopLevelWidgetPrivateData.hpp new file mode 100644 index 000000000..fb4e77b2f --- /dev/null +++ b/source/modules/dgl/src/TopLevelWidgetPrivateData.hpp @@ -0,0 +1,50 @@ +/* + * DISTRHO Plugin Framework (DPF) + * Copyright (C) 2012-2021 Filipe Coelho + * + * Permission to use, copy, modify, and/or distribute this software for any purpose with + * or without fee is hereby granted, provided that the above copyright notice and this + * permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD + * TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN + * NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER + * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef DGL_TOP_LEVEL_WIDGET_PRIVATE_DATA_HPP_INCLUDED +#define DGL_TOP_LEVEL_WIDGET_PRIVATE_DATA_HPP_INCLUDED + +#include "../TopLevelWidget.hpp" + +#include + +START_NAMESPACE_DGL + +// ----------------------------------------------------------------------- + +struct TopLevelWidget::PrivateData { + TopLevelWidget* const self; + Widget* const selfw; + Window& window; + + explicit PrivateData(TopLevelWidget* self, Window& window); + ~PrivateData(); + void display(); + bool keyboardEvent(const KeyboardEvent& ev); + bool characterInputEvent(const CharacterInputEvent& ev); + bool mouseEvent(const MouseEvent& ev); + bool motionEvent(const MotionEvent& ev); + bool scrollEvent(const ScrollEvent& ev); + void fallbackOnResize(); + + DISTRHO_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(PrivateData) +}; + +// ----------------------------------------------------------------------- + +END_NAMESPACE_DGL + +#endif // DGL_TOP_LEVEL_WIDGET_PRIVATE_DATA_HPP_INCLUDED diff --git a/source/modules/dgl/src/Widget.cpp b/source/modules/dgl/src/Widget.cpp index b94d5dd9d..8aaa90d5d 100644 --- a/source/modules/dgl/src/Widget.cpp +++ b/source/modules/dgl/src/Widget.cpp @@ -1,6 +1,6 @@ /* * DISTRHO Plugin Framework (DPF) - * Copyright (C) 2012-2016 Filipe Coelho + * Copyright (C) 2012-2021 Filipe Coelho * * Permission to use, copy, modify, and/or distribute this software for any purpose with * or without fee is hereby granted, provided that the above copyright notice and this @@ -15,33 +15,22 @@ */ #include "WidgetPrivateData.hpp" +#include "../TopLevelWidget.hpp" +#include "../Window.hpp" START_NAMESPACE_DGL -// ----------------------------------------------------------------------- +// -------------------------------------------------------------------------------------------------------------------- // Widget -Widget::Widget(Window& parent) - : pData(new PrivateData(this, parent, nullptr, false)) -{ - parent._addWidget(this); -} +Widget::Widget(TopLevelWidget* const topLevelWidget) + : pData(new PrivateData(this, topLevelWidget)) {} -Widget::Widget(Widget* groupWidget) - : pData(new PrivateData(this, groupWidget->getParentWindow(), groupWidget, true)) -{ - pData->parent._addWidget(this); -} - -Widget::Widget(Widget* groupWidget, bool addToSubWidgets) - : pData(new PrivateData(this, groupWidget->getParentWindow(), groupWidget, addToSubWidgets)) -{ - pData->parent._addWidget(this); -} +Widget::Widget(Widget* const parentWidget) + : pData(new PrivateData(this, parentWidget)) {} Widget::~Widget() { - pData->parent._removeWidget(this); delete pData; } @@ -50,13 +39,15 @@ bool Widget::isVisible() const noexcept return pData->visible; } -void Widget::setVisible(bool yesNo) +void Widget::setVisible(bool visible) { - if (pData->visible == yesNo) + if (pData->visible == visible) return; - pData->visible = yesNo; - pData->parent.repaint(); + pData->visible = visible; + repaint(); + + // FIXME check case of hiding a previously visible widget, does it trigger a repaint? } void Widget::show() @@ -79,7 +70,7 @@ uint Widget::getHeight() const noexcept return pData->size.getHeight(); } -const Size& Widget::getSize() const noexcept +const Size Widget::getSize() const noexcept { return pData->size; } @@ -96,7 +87,7 @@ void Widget::setWidth(uint width) noexcept pData->size.setWidth(width); onResize(ev); - pData->parent.repaint(); + repaint(); } void Widget::setHeight(uint height) noexcept @@ -111,7 +102,7 @@ void Widget::setHeight(uint height) noexcept pData->size.setHeight(height); onResize(ev); - pData->parent.repaint(); + repaint(); } void Widget::setSize(uint width, uint height) noexcept @@ -131,79 +122,34 @@ void Widget::setSize(const Size& size) noexcept pData->size = size; onResize(ev); - pData->parent.repaint(); -} - -int Widget::getAbsoluteX() const noexcept -{ - return pData->absolutePos.getX(); -} - -int Widget::getAbsoluteY() const noexcept -{ - return pData->absolutePos.getY(); -} - -const Point& Widget::getAbsolutePos() const noexcept -{ - return pData->absolutePos; -} - -void Widget::setAbsoluteX(int x) noexcept -{ - if (pData->absolutePos.getX() == x) - return; - - pData->absolutePos.setX(x); - pData->parent.repaint(); -} - -void Widget::setAbsoluteY(int y) noexcept -{ - if (pData->absolutePos.getY() == y) - return; - - pData->absolutePos.setY(y); - pData->parent.repaint(); -} - -void Widget::setAbsolutePos(int x, int y) noexcept -{ - setAbsolutePos(Point(x, y)); -} - -void Widget::setAbsolutePos(const Point& pos) noexcept -{ - if (pData->absolutePos == pos) - return; - - pData->absolutePos = pos; - pData->parent.repaint(); + repaint(); } -Application& Widget::getParentApp() const noexcept +Application& Widget::getApp() const noexcept { - return pData->parent.getApp(); + DISTRHO_SAFE_ASSERT(pData->topLevelWidget != nullptr); + return pData->topLevelWidget->getApp(); } -Window& Widget::getParentWindow() const noexcept +Window& Widget::getWindow() const noexcept { - return pData->parent; + DISTRHO_SAFE_ASSERT(pData->topLevelWidget != nullptr); + return pData->topLevelWidget->getWindow(); } -bool Widget::contains(int x, int y) const noexcept +const GraphicsContext& Widget::getGraphicsContext() const noexcept { - return (x >= 0 && y >= 0 && static_cast(x) < pData->size.getWidth() && static_cast(y) < pData->size.getHeight()); + DISTRHO_SAFE_ASSERT(pData->topLevelWidget != nullptr); + return pData->topLevelWidget->getWindow().getGraphicsContext(); } -bool Widget::contains(const Point& pos) const noexcept +TopLevelWidget* Widget::getTopLevelWidget() const noexcept { - return contains(pos.getX(), pos.getY()); + return pData->topLevelWidget; } void Widget::repaint() noexcept { - pData->parent.repaint(); } uint Widget::getId() const noexcept @@ -216,35 +162,38 @@ void Widget::setId(uint id) noexcept pData->id = id; } -bool Widget::onKeyboard(const KeyboardEvent&) +bool Widget::onKeyboard(const KeyboardEvent& ev) { - return false; + return pData->giveKeyboardEventForSubWidgets(ev); } -bool Widget::onSpecial(const SpecialEvent&) +bool Widget::onCharacterInput(const CharacterInputEvent& ev) { - return false; + return pData->giveCharacterInputEventForSubWidgets(ev); } -bool Widget::onMouse(const MouseEvent&) +bool Widget::onMouse(const MouseEvent& ev) { - return false; + MouseEvent rev = ev; + return pData->giveMouseEventForSubWidgets(rev); } -bool Widget::onMotion(const MotionEvent&) +bool Widget::onMotion(const MotionEvent& ev) { - return false; + MotionEvent rev = ev; + return pData->giveMotionEventForSubWidgets(rev); } -bool Widget::onScroll(const ScrollEvent&) +bool Widget::onScroll(const ScrollEvent& ev) { - return false; + ScrollEvent rev = ev; + return pData->giveScrollEventForSubWidgets(rev); } void Widget::onResize(const ResizeEvent&) { } -// ----------------------------------------------------------------------- +// -------------------------------------------------------------------------------------------------------------------- END_NAMESPACE_DGL diff --git a/source/modules/dgl/src/WidgetPrivateData.cpp b/source/modules/dgl/src/WidgetPrivateData.cpp new file mode 100644 index 000000000..59b87901c --- /dev/null +++ b/source/modules/dgl/src/WidgetPrivateData.cpp @@ -0,0 +1,229 @@ +/* + * DISTRHO Plugin Framework (DPF) + * Copyright (C) 2012-2021 Filipe Coelho + * + * Permission to use, copy, modify, and/or distribute this software for any purpose with + * or without fee is hereby granted, provided that the above copyright notice and this + * permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD + * TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN + * NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER + * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "WidgetPrivateData.hpp" +#include "SubWidgetPrivateData.hpp" +#include "../TopLevelWidget.hpp" + +START_NAMESPACE_DGL + +#define FOR_EACH_SUBWIDGET(it) \ + for (std::list::iterator it = subWidgets.begin(); it != subWidgets.end(); ++it) + +#define FOR_EACH_SUBWIDGET_INV(rit) \ + for (std::list::reverse_iterator rit = subWidgets.rbegin(); rit != subWidgets.rend(); ++rit) + +// ----------------------------------------------------------------------- + +Widget::PrivateData::PrivateData(Widget* const s, TopLevelWidget* const tlw) + : self(s), + topLevelWidget(tlw), + parentWidget(nullptr), + id(0), + needsScaling(false), + visible(true), + size(0, 0), + subWidgets() {} + +Widget::PrivateData::PrivateData(Widget* const s, Widget* const pw) + : self(s), + topLevelWidget(findTopLevelWidget(pw)), + parentWidget(pw), + id(0), + needsScaling(false), + visible(true), + size(0, 0), + subWidgets() {} + +Widget::PrivateData::~PrivateData() +{ + subWidgets.clear(); +} + +void Widget::PrivateData::displaySubWidgets(const uint width, const uint height, const double autoScaleFactor) +{ + if (subWidgets.size() == 0) + return; + + for (std::list::iterator it = subWidgets.begin(); it != subWidgets.end(); ++it) + { + SubWidget* const subwidget(*it); + + if (subwidget->isVisible()) + subwidget->pData->display(width, height, autoScaleFactor); + } +} + +// ----------------------------------------------------------------------- + +bool Widget::PrivateData::giveKeyboardEventForSubWidgets(const KeyboardEvent& ev) +{ + if (! visible) + return false; + if (subWidgets.size() == 0) + return false; + + FOR_EACH_SUBWIDGET_INV(rit) + { + SubWidget* const widget(*rit); + + if (widget->isVisible() && widget->onKeyboard(ev)) + return true; + } + + return false; +} + +bool Widget::PrivateData::giveCharacterInputEventForSubWidgets(const CharacterInputEvent& ev) +{ + if (! visible) + return false; + if (subWidgets.size() == 0) + return false; + + FOR_EACH_SUBWIDGET_INV(rit) + { + SubWidget* const widget(*rit); + + if (widget->isVisible() && widget->onCharacterInput(ev)) + return true; + } + + return false; +} + +bool Widget::PrivateData::giveMouseEventForSubWidgets(MouseEvent& ev) +{ + if (! visible) + return false; + if (subWidgets.size() == 0) + return false; + + const double x = ev.absolutePos.getX(); + const double y = ev.absolutePos.getY(); + + if (SubWidget* const selfw = dynamic_cast(self)) + { + if (selfw->pData->needsViewportScaling) + { + ev.absolutePos.setX(x - selfw->getAbsoluteX() + selfw->getMargin().getX()); + ev.absolutePos.setY(y - selfw->getAbsoluteY() + selfw->getMargin().getY()); + } + } + + FOR_EACH_SUBWIDGET_INV(rit) + { + SubWidget* const widget(*rit); + + if (! widget->isVisible()) + continue; + + ev.pos = Point(x - widget->getAbsoluteX() + widget->getMargin().getX(), + y - widget->getAbsoluteY() + widget->getMargin().getY()); + + if (widget->onMouse(ev)) + return true; + } + + return false; +} + +bool Widget::PrivateData::giveMotionEventForSubWidgets(MotionEvent& ev) +{ + if (! visible) + return false; + if (subWidgets.size() == 0) + return false; + + const double x = ev.absolutePos.getX(); + const double y = ev.absolutePos.getY(); + + if (SubWidget* const selfw = dynamic_cast(self)) + { + if (selfw->pData->needsViewportScaling) + { + ev.absolutePos.setX(x - selfw->getAbsoluteX() + selfw->getMargin().getX()); + ev.absolutePos.setY(y - selfw->getAbsoluteY() + selfw->getMargin().getY()); + } + } + + FOR_EACH_SUBWIDGET_INV(rit) + { + SubWidget* const widget(*rit); + + if (! widget->isVisible()) + continue; + + ev.pos = Point(x - widget->getAbsoluteX() + widget->getMargin().getX(), + y - widget->getAbsoluteY() + widget->getMargin().getY()); + + if (widget->onMotion(ev)) + return true; + } + + return false; +} + +bool Widget::PrivateData::giveScrollEventForSubWidgets(ScrollEvent& ev) +{ + if (! visible) + return false; + if (subWidgets.size() == 0) + return false; + + const double x = ev.absolutePos.getX(); + const double y = ev.absolutePos.getY(); + + if (SubWidget* const selfw = dynamic_cast(self)) + { + if (selfw->pData->needsViewportScaling) + { + ev.absolutePos.setX(x - selfw->getAbsoluteX() + selfw->getMargin().getX()); + ev.absolutePos.setY(y - selfw->getAbsoluteY() + selfw->getMargin().getY()); + } + } + + FOR_EACH_SUBWIDGET_INV(rit) + { + SubWidget* const widget(*rit); + + if (! widget->isVisible()) + continue; + + ev.pos = Point(x - widget->getAbsoluteX() + widget->getMargin().getX(), + y - widget->getAbsoluteY() + widget->getMargin().getY()); + + if (widget->onScroll(ev)) + return true; + } + + return false; +} + +// ----------------------------------------------------------------------- + +TopLevelWidget* Widget::PrivateData::findTopLevelWidget(Widget* const pw) +{ + if (pw->pData->topLevelWidget != nullptr) + return pw->pData->topLevelWidget; + if (pw->pData->parentWidget != nullptr) + return findTopLevelWidget(pw->pData->parentWidget); + return nullptr; +} + +// ----------------------------------------------------------------------- + +END_NAMESPACE_DGL diff --git a/source/modules/dgl/src/WidgetPrivateData.hpp b/source/modules/dgl/src/WidgetPrivateData.hpp index bf836b7ba..15b84097a 100644 --- a/source/modules/dgl/src/WidgetPrivateData.hpp +++ b/source/modules/dgl/src/WidgetPrivateData.hpp @@ -1,6 +1,6 @@ /* * DISTRHO Plugin Framework (DPF) - * Copyright (C) 2012-2018 Filipe Coelho + * Copyright (C) 2012-2021 Filipe Coelho * * Permission to use, copy, modify, and/or distribute this software for any purpose with * or without fee is hereby granted, provided that the above copyright notice and this @@ -18,119 +18,43 @@ #define DGL_WIDGET_PRIVATE_DATA_HPP_INCLUDED #include "../Widget.hpp" -#include "../Window.hpp" -#include +#include START_NAMESPACE_DGL -// ----------------------------------------------------------------------- +// -------------------------------------------------------------------------------------------------------------------- struct Widget::PrivateData { Widget* const self; - Window& parent; - Point absolutePos; - Size size; - std::vector subWidgets; - + TopLevelWidget* const topLevelWidget; + Widget* const parentWidget; uint id; - bool needsFullViewport; bool needsScaling; - bool skipDisplay; bool visible; + Size size; + std::list subWidgets; - PrivateData(Widget* const s, Window& p, Widget* groupWidget, bool addToSubWidgets) - : self(s), - parent(p), - absolutePos(0, 0), - size(0, 0), - subWidgets(), - id(0), - needsFullViewport(false), - needsScaling(false), - skipDisplay(false), - visible(true) - { - if (addToSubWidgets && groupWidget != nullptr) - { - skipDisplay = true; - groupWidget->pData->subWidgets.push_back(self); - } - } - - ~PrivateData() - { - subWidgets.clear(); - } - - void display(const uint width, const uint height, const bool renderingSubWidget) - { - if ((skipDisplay && ! renderingSubWidget) || size.isInvalid() || ! visible) - return; - - bool needsDisableScissor = false; - - // reset color - glColor4f(1.0f, 1.0f, 1.0f, 1.0f); - - if (needsFullViewport || (absolutePos.isZero() && size == Size(width, height))) - { - // full viewport size - glViewport(0, 0, static_cast(width), static_cast(height)); - } - else if (needsScaling) - { - // limit viewport to widget bounds - glViewport(absolutePos.getX(), - static_cast(height - self->getHeight()) - absolutePos.getY(), - static_cast(self->getWidth()), - static_cast(self->getHeight())); - } - else - { - // only set viewport pos - glViewport(absolutePos.getX(), - /*static_cast(height - self->getHeight())*/ - absolutePos.getY(), - static_cast(width), - static_cast(height)); - - // then cut the outer bounds - glScissor(absolutePos.getX(), - static_cast(height - self->getHeight()) - absolutePos.getY(), - static_cast(self->getWidth()), - static_cast(self->getHeight())); - - glEnable(GL_SCISSOR_TEST); - needsDisableScissor = true; - } - - // display widget - self->onDisplay(); - - if (needsDisableScissor) - { - glDisable(GL_SCISSOR_TEST); - needsDisableScissor = false; - } + // called via TopLevelWidget + explicit PrivateData(Widget* const s, TopLevelWidget* const tlw); + // called via SubWidget + explicit PrivateData(Widget* const s, Widget* const pw); + ~PrivateData(); - displaySubWidgets(width, height); - } + void displaySubWidgets(uint width, uint height, double autoScaleFactor); - void displaySubWidgets(const uint width, const uint height) - { - for (std::vector::iterator it = subWidgets.begin(); it != subWidgets.end(); ++it) - { - Widget* const widget(*it); - DISTRHO_SAFE_ASSERT_CONTINUE(widget->pData != this); + bool giveKeyboardEventForSubWidgets(const KeyboardEvent& ev); + bool giveCharacterInputEventForSubWidgets(const CharacterInputEvent& ev); + bool giveMouseEventForSubWidgets(MouseEvent& ev); + bool giveMotionEventForSubWidgets(MotionEvent& ev); + bool giveScrollEventForSubWidgets(ScrollEvent& ev); - widget->pData->display(width, height, true); - } - } + static TopLevelWidget* findTopLevelWidget(Widget* const w); - DISTRHO_DECLARE_NON_COPY_STRUCT(PrivateData) + DISTRHO_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(PrivateData) }; -// ----------------------------------------------------------------------- +// -------------------------------------------------------------------------------------------------------------------- END_NAMESPACE_DGL diff --git a/source/modules/dgl/src/Window.cpp b/source/modules/dgl/src/Window.cpp index c03ee758f..e77790e08 100644 --- a/source/modules/dgl/src/Window.cpp +++ b/source/modules/dgl/src/Window.cpp @@ -1,6 +1,6 @@ /* * DISTRHO Plugin Framework (DPF) - * Copyright (C) 2012-2018 Filipe Coelho + * Copyright (C) 2012-2021 Filipe Coelho * * Permission to use, copy, modify, and/or distribute this software for any purpose with * or without fee is hereby granted, provided that the above copyright notice and this @@ -14,1401 +14,492 @@ * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ -// we need this for now -//#define PUGL_GRAB_FOCUS 1 +#include "WindowPrivateData.hpp" +#include "../TopLevelWidget.hpp" -#include "../Base.hpp" - -#undef PUGL_HAVE_CAIRO -#undef PUGL_HAVE_GL -#define PUGL_HAVE_GL 1 - -#include "pugl/pugl.h" - -#if defined(__GNUC__) && (__GNUC__ >= 7) -# pragma GCC diagnostic push -# pragma GCC diagnostic ignored "-Wimplicit-fallthrough" -#endif - -#if defined(DISTRHO_OS_WINDOWS) -# include "pugl/pugl_win.cpp" -# undef max -# undef min -#elif defined(DISTRHO_OS_MAC) -# define PuglWindow DISTRHO_JOIN_MACRO(PuglWindow, DGL_NAMESPACE) -# define PuglOpenGLView DISTRHO_JOIN_MACRO(PuglOpenGLView, DGL_NAMESPACE) -# include "pugl/pugl_osx.m" -#else -# include -# include -extern "C" { -# include "pugl/pugl_x11.c" -} -#endif - -#if defined(__GNUC__) && (__GNUC__ >= 7) -# pragma GCC diagnostic pop -#endif - -#include "ApplicationPrivateData.hpp" -#include "WidgetPrivateData.hpp" -#include "../StandaloneWindow.hpp" -#include "../../distrho/extra/String.hpp" - -#define FOR_EACH_WIDGET(it) \ - for (std::list::iterator it = fWidgets.begin(); it != fWidgets.end(); ++it) - -#define FOR_EACH_WIDGET_INV(rit) \ - for (std::list::reverse_iterator rit = fWidgets.rbegin(); rit != fWidgets.rend(); ++rit) - -#ifdef DEBUG -# define DBG(msg) std::fprintf(stderr, "%s", msg); -# define DBGp(...) std::fprintf(stderr, __VA_ARGS__); -# define DBGF std::fflush(stderr); -#else -# define DBG(msg) -# define DBGp(...) -# define DBGF -#endif +#include "pugl.hpp" START_NAMESPACE_DGL // ----------------------------------------------------------------------- -// Window Private - -struct Window::PrivateData { - PrivateData(Application& app, Window* const self) - : fApp(app), - fSelf(self), - fView(puglInit()), - fFirstInit(true), - fVisible(false), - fResizable(true), - fUsingEmbed(false), - fWidth(1), - fHeight(1), - fTitle(nullptr), - fWidgets(), - fModal(), -#if defined(DISTRHO_OS_WINDOWS) - hwnd(nullptr), - hwndParent(nullptr) -#elif defined(DISTRHO_OS_MAC) - fNeedsIdle(true), - mView(nullptr), - mWindow(nullptr), - mParentWindow(nullptr) -#else - xDisplay(nullptr), - xWindow(0) -#endif - { - DBG("Creating window without parent..."); DBGF; - init(); - } - - PrivateData(Application& app, Window* const self, Window& parent) - : fApp(app), - fSelf(self), - fView(puglInit()), - fFirstInit(true), - fVisible(false), - fResizable(true), - fUsingEmbed(false), - fWidth(1), - fHeight(1), - fTitle(nullptr), - fWidgets(), - fModal(parent.pData), -#if defined(DISTRHO_OS_WINDOWS) - hwnd(nullptr), - hwndParent(nullptr) -#elif defined(DISTRHO_OS_MAC) - fNeedsIdle(false), - mView(nullptr), - mWindow(nullptr), - mParentWindow(nullptr) -#else - xDisplay(nullptr), - xWindow(0) -#endif - { - DBG("Creating window with parent..."); DBGF; - init(); - - const PuglInternals* const parentImpl(parent.pData->fView->impl); - - // NOTE: almost a 1:1 copy of setTransientWinId() -#if defined(DISTRHO_OS_WINDOWS) - hwndParent = parentImpl->hwnd; - SetWindowLongPtr(hwnd, GWLP_HWNDPARENT, (LONG_PTR)hwndParent); -#elif defined(DISTRHO_OS_MAC) - mParentWindow = parentImpl->window; -#else - XSetTransientForHint(xDisplay, xWindow, parentImpl->win); -#endif - } - - PrivateData(Application& app, Window* const self, const intptr_t parentId) - : fApp(app), - fSelf(self), - fView(puglInit()), - fFirstInit(true), - fVisible(parentId != 0), - fResizable(parentId == 0), - fUsingEmbed(parentId != 0), - fWidth(1), - fHeight(1), - fTitle(nullptr), - fWidgets(), - fModal(), -#if defined(DISTRHO_OS_WINDOWS) - hwnd(nullptr), - hwndParent(nullptr) -#elif defined(DISTRHO_OS_MAC) - fNeedsIdle(parentId == 0), - mView(nullptr), - mWindow(nullptr), - mParentWindow(nullptr) -#else - xDisplay(nullptr), - xWindow(0) -#endif - { - if (fUsingEmbed) - { - DBG("Creating embedded window..."); DBGF; - puglInitWindowParent(fView, parentId); - } - else - { - DBG("Creating window without parent..."); DBGF; - } - - init(); - - if (fUsingEmbed) - { - DBG("NOTE: Embed window is always visible and non-resizable\n"); - puglShowWindow(fView); - fApp.pData->oneShown(); - fFirstInit = false; - } - } - - void init() - { - if (fSelf == nullptr || fView == nullptr) - { - DBG("Failed!\n"); - return; - } - - puglInitContextType(fView, PUGL_GL); - puglInitUserResizable(fView, fResizable); - puglInitWindowSize(fView, static_cast(fWidth), static_cast(fHeight)); - - puglSetHandle(fView, this); - puglSetDisplayFunc(fView, onDisplayCallback); - puglSetKeyboardFunc(fView, onKeyboardCallback); - puglSetMotionFunc(fView, onMotionCallback); - puglSetMouseFunc(fView, onMouseCallback); - puglSetScrollFunc(fView, onScrollCallback); - puglSetSpecialFunc(fView, onSpecialCallback); - puglSetReshapeFunc(fView, onReshapeCallback); - puglSetCloseFunc(fView, onCloseCallback); -#ifndef DGL_FILE_BROWSER_DISABLED - puglSetFileSelectedFunc(fView, fileBrowserSelectedCallback); -#endif - - puglCreateWindow(fView, nullptr); - - PuglInternals* impl = fView->impl; -#if defined(DISTRHO_OS_WINDOWS) - hwnd = impl->hwnd; - DISTRHO_SAFE_ASSERT(hwnd != 0); -#elif defined(DISTRHO_OS_MAC) - mView = impl->glview; - mWindow = impl->window; - DISTRHO_SAFE_ASSERT(mView != nullptr); - if (fUsingEmbed) { - DISTRHO_SAFE_ASSERT(mWindow == nullptr); - } else { - DISTRHO_SAFE_ASSERT(mWindow != nullptr); - } -#else - xDisplay = impl->display; - xWindow = impl->win; - DISTRHO_SAFE_ASSERT(xWindow != 0); - - if (! fUsingEmbed) - { - const pid_t pid = getpid(); - const Atom _nwp = XInternAtom(xDisplay, "_NET_WM_PID", False); - XChangeProperty(xDisplay, xWindow, _nwp, XA_CARDINAL, 32, PropModeReplace, (const uchar*)&pid, 1); - - const Atom _wt = XInternAtom(xDisplay, "_NET_WM_WINDOW_TYPE", False); - - // Setting the window to both dialog and normal will produce a decorated floating dialog - // Order is important: DIALOG needs to come before NORMAL - const Atom _wts[2] = { - XInternAtom(xDisplay, "_NET_WM_WINDOW_TYPE_DIALOG", False), - XInternAtom(xDisplay, "_NET_WM_WINDOW_TYPE_NORMAL", False) - }; - XChangeProperty(xDisplay, xWindow, _wt, XA_ATOM, 32, PropModeReplace, (const uchar*)&_wts, 2); - - } -#endif - puglEnterContext(fView); - - fApp.pData->windows.push_back(fSelf); - - DBG("Success!\n"); - } - - ~PrivateData() - { - DBG("Destroying window..."); DBGF; - - if (fModal.enabled) - { - exec_fini(); - close(); - } - - fWidgets.clear(); - - if (fUsingEmbed) - { - puglHideWindow(fView); - fApp.pData->oneHidden(); - } - - if (fSelf != nullptr) - { - fApp.pData->windows.remove(fSelf); - fSelf = nullptr; - } - - if (fView != nullptr) - { - puglDestroy(fView); - fView = nullptr; - } - - if (fTitle != nullptr) - { - std::free(fTitle); - fTitle = nullptr; - } - -#if defined(DISTRHO_OS_WINDOWS) - hwnd = 0; -#elif defined(DISTRHO_OS_MAC) - mView = nullptr; - mWindow = nullptr; -#else - xDisplay = nullptr; - xWindow = 0; -#endif - - DBG("Success!\n"); - } - - // ------------------------------------------------------------------- - - void close() - { - DBG("Window close\n"); +// ScopedGraphicsContext - if (fUsingEmbed) - return; +Window::ScopedGraphicsContext::ScopedGraphicsContext(Window& win) + : window(win), + ppData(nullptr), + active(puglBackendEnter(window.pData->view)) {} - setVisible(false); +Window::ScopedGraphicsContext::ScopedGraphicsContext(Window& win, Window& transientWin) + : window(win), + ppData(transientWin.pData), + active(false) +{ + puglBackendLeave(ppData->view); + active = puglBackendEnter(window.pData->view); +} - if (! fFirstInit) - { - fApp.pData->oneHidden(); - fFirstInit = true; - } - } +Window::ScopedGraphicsContext::~ScopedGraphicsContext() +{ + done(); +} - void exec(const bool lockWait) +void Window::ScopedGraphicsContext::done() +{ + if (active) { - DBG("Window exec\n"); - exec_init(); - - if (lockWait) - { - for (; fVisible && fModal.enabled;) - { - idle(); - d_msleep(10); - } - - exec_fini(); - } - else - { - idle(); - } + puglBackendLeave(window.pData->view); + active = false; } - // ------------------------------------------------------------------- - - void exec_init() + if (ppData != nullptr) { - DBG("Window modal loop starting..."); DBGF; - DISTRHO_SAFE_ASSERT_RETURN(fModal.parent != nullptr, setVisible(true)); - - fModal.enabled = true; - fModal.parent->fModal.childFocus = this; - - fModal.parent->setVisible(true); - setVisible(true); - - DBG("Ok\n"); + puglBackendEnter(ppData->view); + ppData = nullptr; } +} - void exec_fini() - { - DBG("Window modal loop stopping..."); DBGF; - fModal.enabled = false; - - if (fModal.parent != nullptr) - { - fModal.parent->fModal.childFocus = nullptr; - - // the mouse position probably changed since the modal appeared, - // so send a mouse motion event to the modal's parent window -#if defined(DISTRHO_OS_WINDOWS) - // TODO -#elif defined(DISTRHO_OS_MAC) - // TODO -#else - int i, wx, wy; - uint u; - ::Window w; - if (XQueryPointer(fModal.parent->xDisplay, fModal.parent->xWindow, &w, &w, &i, &i, &wx, &wy, &u) == True) - fModal.parent->onPuglMotion(wx, wy); -#endif - } - - DBG("Ok\n"); - } - - // ------------------------------------------------------------------- - - void focus() - { - DBG("Window focus\n"); -#if defined(DISTRHO_OS_WINDOWS) - SetForegroundWindow(hwnd); - SetActiveWindow(hwnd); - SetFocus(hwnd); -#elif defined(DISTRHO_OS_MAC) - if (mWindow != nullptr) - [mWindow makeKeyWindow]; -#else - XRaiseWindow(xDisplay, xWindow); - XSetInputFocus(xDisplay, xWindow, RevertToPointerRoot, CurrentTime); - XFlush(xDisplay); -#endif - } +// ----------------------------------------------------------------------- +// Window - // ------------------------------------------------------------------- +Window::Window(Application& app) + : pData(new PrivateData(app, this)) +{ + pData->initPost(); +} - void setVisible(const bool yesNo) - { - if (fVisible == yesNo) - { - DBG("Window setVisible matches current state, ignoring request\n"); - return; - } - if (fUsingEmbed) - { - DBG("Window setVisible cannot be called when embedded\n"); - return; - } +Window::Window(Application& app, Window& transientParentWindow) + : pData(new PrivateData(app, this, transientParentWindow.pData)) +{ + pData->initPost(); +} - DBG("Window setVisible called\n"); +Window::Window(Application& app, + const uintptr_t parentWindowHandle, + const double scaleFactor, + const bool resizable) + : pData(new PrivateData(app, this, parentWindowHandle, scaleFactor, resizable)) +{ + pData->initPost(); +} - fVisible = yesNo; +Window::Window(Application& app, + const uintptr_t parentWindowHandle, + const uint width, + const uint height, + const double scaleFactor, + const bool resizable) + : pData(new PrivateData(app, this, parentWindowHandle, width, height, scaleFactor, resizable, false)) +{ + pData->initPost(); +} - if (yesNo && fFirstInit) - setSize(fWidth, fHeight, true); +Window::Window(Application& app, + const uintptr_t parentWindowHandle, + const uint width, + const uint height, + const double scaleFactor, + const bool resizable, + const bool isVST3, + const bool doPostInit) + : pData(new PrivateData(app, this, parentWindowHandle, width, height, scaleFactor, resizable, isVST3)) +{ + if (doPostInit) + pData->initPost(); +} -#if defined(DISTRHO_OS_WINDOWS) - if (yesNo) - { - if (fFirstInit) - { - RECT rectChild, rectParent; - - if (hwndParent != nullptr && - GetWindowRect(hwnd, &rectChild) && - GetWindowRect(hwndParent, &rectParent)) - { - SetWindowPos(hwnd, hwndParent, - rectParent.left + (rectChild.right-rectChild.left)/2, - rectParent.top + (rectChild.bottom-rectChild.top)/2, - 0, 0, SWP_SHOWWINDOW|SWP_NOSIZE); - } - else - { - ShowWindow(hwnd, SW_SHOWNORMAL); - } - } - else - { - ShowWindow(hwnd, SW_RESTORE); - } - } - else - { - ShowWindow(hwnd, SW_HIDE); - } +Window::~Window() +{ + delete pData; +} - UpdateWindow(hwnd); -#elif defined(DISTRHO_OS_MAC) - if (yesNo) - { - if (mWindow != nullptr) - { - if (mParentWindow != nullptr) - [mParentWindow addChildWindow:mWindow - ordered:NSWindowAbove]; +bool Window::isEmbed() const noexcept +{ + return pData->isEmbed; +} - [mWindow setIsVisible:YES]; - } - else - { - [mView setHidden:NO]; - } - } - else - { - if (mWindow != nullptr) - { - if (mParentWindow != nullptr) - [mParentWindow removeChildWindow:mWindow]; +bool Window::isVisible() const noexcept +{ + return pData->isVisible; +} - [mWindow setIsVisible:NO]; - } - else - { - [mView setHidden:YES]; - } - } -#else - if (yesNo) - XMapRaised(xDisplay, xWindow); - else - XUnmapWindow(xDisplay, xWindow); +void Window::setVisible(const bool visible) +{ + if (visible) + pData->show(); + else + pData->hide(); +} - XFlush(xDisplay); -#endif +void Window::show() +{ + pData->show(); +} - if (yesNo) - { - if (fFirstInit) - { - fApp.pData->oneShown(); - fFirstInit = false; - } - } - else if (fModal.enabled) - exec_fini(); - } +void Window::hide() +{ + pData->hide(); +} - // ------------------------------------------------------------------- +void Window::close() +{ + pData->close(); +} - void setResizable(const bool yesNo) - { - if (fResizable == yesNo) - { - DBG("Window setResizable matches current state, ignoring request\n"); - return; - } - if (fUsingEmbed) - { - DBG("Window setResizable cannot be called when embedded\n"); - return; - } +bool Window::isResizable() const noexcept +{ + return puglGetViewHint(pData->view, PUGL_RESIZABLE) == PUGL_TRUE; +} - DBG("Window setResizable called\n"); +void Window::setResizable(const bool resizable) +{ + pData->setResizable(resizable); +} - fResizable = yesNo; +int Window::getOffsetX() const noexcept +{ + DISTRHO_SAFE_ASSERT_RETURN(pData->view != nullptr, 0); -#if defined(DISTRHO_OS_WINDOWS) - const int winFlags = fResizable ? GetWindowLong(hwnd, GWL_STYLE) | WS_SIZEBOX - : GetWindowLong(hwnd, GWL_STYLE) & ~WS_SIZEBOX; - SetWindowLong(hwnd, GWL_STYLE, winFlags); -#elif defined(DISTRHO_OS_MAC) - const uint flags(yesNo ? (NSViewWidthSizable|NSViewHeightSizable) : 0x0); - [mView setAutoresizingMask:flags]; -#endif + return puglGetFrame(pData->view).x; +} - setSize(fWidth, fHeight, true); - } +int Window::getOffsetY() const noexcept +{ + DISTRHO_SAFE_ASSERT_RETURN(pData->view != nullptr, 0); - // ------------------------------------------------------------------- + return puglGetFrame(pData->view).y; +} - void setSize(uint width, uint height, const bool forced = false) - { - if (width <= 1 || height <= 1) - { - DBGp("Window setSize called with invalid value(s) %i %i, ignoring request\n", width, height); - return; - } +Point Window::getOffset() const noexcept +{ + DISTRHO_SAFE_ASSERT_RETURN(pData->view != nullptr, Point()); - if (fWidth == width && fHeight == height && ! forced) - { - DBGp("Window setSize matches current size, ignoring request (%i %i)\n", width, height); - return; - } + const PuglRect rect = puglGetFrame(pData->view); + return Point(rect.x, rect.y); +} - fWidth = width; - fHeight = height; +void Window::setOffsetX(const int x) +{ + setOffset(x, getOffsetY()); +} - DBGp("Window setSize called %s, size %i %i, resizable %s\n", forced ? "(forced)" : "(not forced)", width, height, fResizable?"true":"false"); +void Window::setOffsetY(const int y) +{ + setOffset(getOffsetX(), y); +} -#if defined(DISTRHO_OS_WINDOWS) - const int winFlags = WS_POPUPWINDOW | WS_CAPTION | (fResizable ? WS_SIZEBOX : 0x0); - RECT wr = { 0, 0, static_cast(width), static_cast(height) }; - AdjustWindowRectEx(&wr, fUsingEmbed ? WS_CHILD : winFlags, FALSE, WS_EX_TOPMOST); +void Window::setOffset(const int x, const int y) +{ + puglSetWindowOffset(pData->view, x, y); +} - SetWindowPos(hwnd, 0, 0, 0, wr.right-wr.left, wr.bottom-wr.top, - SWP_NOACTIVATE|SWP_NOMOVE|SWP_NOOWNERZORDER|SWP_NOZORDER); +void Window::setOffset(const Point& offset) +{ + setOffset(offset.getX(), offset.getY()); +} - if (! forced) - UpdateWindow(hwnd); -#elif defined(DISTRHO_OS_MAC) - [mView setFrame:NSMakeRect(0, 0, width, height)]; +uint Window::getWidth() const noexcept +{ + DISTRHO_SAFE_ASSERT_RETURN(pData->view != nullptr, 0); - if (mWindow != nullptr) - { - const NSSize size = NSMakeSize(width, height); - [mWindow setContentSize:size]; + const double width = puglGetFrame(pData->view).width; + DISTRHO_SAFE_ASSERT_RETURN(width >= 0.0, 0); + return static_cast(width + 0.5); +} - if (fResizable) - { - [mWindow setContentMinSize:NSMakeSize(1, 1)]; - [mWindow setContentMaxSize:NSMakeSize(99999, 99999)]; - [[mWindow standardWindowButton:NSWindowZoomButton] setHidden:NO]; - } - else - { - [mWindow setContentMinSize:size]; - [mWindow setContentMaxSize:size]; - [[mWindow standardWindowButton:NSWindowZoomButton] setHidden:YES]; - } - } -#else - XResizeWindow(xDisplay, xWindow, width, height); +uint Window::getHeight() const noexcept +{ + DISTRHO_SAFE_ASSERT_RETURN(pData->view != nullptr, 0); - if (! fResizable) - { - XSizeHints sizeHints; - memset(&sizeHints, 0, sizeof(sizeHints)); - - sizeHints.flags = PSize|PMinSize|PMaxSize; - sizeHints.width = static_cast(width); - sizeHints.height = static_cast(height); - sizeHints.min_width = static_cast(width); - sizeHints.min_height = static_cast(height); - sizeHints.max_width = static_cast(width); - sizeHints.max_height = static_cast(height); - - XSetNormalHints(xDisplay, xWindow, &sizeHints); - } + const double height = puglGetFrame(pData->view).height; + DISTRHO_SAFE_ASSERT_RETURN(height >= 0.0, 0); + return static_cast(height + 0.5); +} - if (! forced) - XFlush(xDisplay); -#endif +Size Window::getSize() const noexcept +{ + DISTRHO_SAFE_ASSERT_RETURN(pData->view != nullptr, Size()); - puglPostRedisplay(fView); - } + const PuglRect rect = puglGetFrame(pData->view); + DISTRHO_SAFE_ASSERT_RETURN(rect.width >= 0.0, Size()); + DISTRHO_SAFE_ASSERT_RETURN(rect.height >= 0.0, Size()); + return Size(static_cast(rect.width + 0.5), + static_cast(rect.height + 0.5)); +} - // ------------------------------------------------------------------- +void Window::setWidth(const uint width) +{ + setSize(width, getHeight()); +} - const char* getTitle() const noexcept - { - static const char* const kFallback = ""; +void Window::setHeight(const uint height) +{ + setSize(getWidth(), height); +} - return fTitle != nullptr ? fTitle : kFallback; - } +void Window::setSize(uint width, uint height) +{ + DISTRHO_SAFE_ASSERT_UINT2_RETURN(width > 1 && height > 1, width, height,); - void setTitle(const char* const title) + if (pData->isEmbed) { - DBGp("Window setTitle \"%s\"\n", title); - - if (fTitle != nullptr) - std::free(fTitle); + const double scaleFactor = pData->scaleFactor; + uint minWidth = pData->minWidth; + uint minHeight = pData->minHeight; - fTitle = strdup(title); - -#if defined(DISTRHO_OS_WINDOWS) - SetWindowTextA(hwnd, title); -#elif defined(DISTRHO_OS_MAC) - if (mWindow != nullptr) + if (pData->autoScaling && scaleFactor != 1.0) { - NSString* titleString = [[NSString alloc] - initWithBytes:title - length:strlen(title) - encoding:NSUTF8StringEncoding]; - - [mWindow setTitle:titleString]; + minWidth *= scaleFactor; + minHeight *= scaleFactor; } -#else - XStoreName(xDisplay, xWindow, title); -#endif - } - void setTransientWinId(const uintptr_t winId) - { - DISTRHO_SAFE_ASSERT_RETURN(winId != 0,); - -#if defined(DISTRHO_OS_WINDOWS) - hwndParent = (HWND)winId; - SetWindowLongPtr(hwnd, GWLP_HWNDPARENT, (LONG_PTR)winId); -#elif defined(DISTRHO_OS_MAC) - NSWindow* const parentWindow = [NSApp windowWithWindowNumber:winId]; - DISTRHO_SAFE_ASSERT_RETURN(parentWindow != nullptr,); - - [parentWindow addChildWindow:mWindow - ordered:NSWindowAbove]; -#else - XSetTransientForHint(xDisplay, xWindow, static_cast< ::Window>(winId)); -#endif - } - - // ------------------------------------------------------------------- + // handle geometry constraints here + if (width < minWidth) + width = minWidth; - void addWidget(Widget* const widget) - { - fWidgets.push_back(widget); - } + if (height < minHeight) + height = minHeight; - void removeWidget(Widget* const widget) - { - fWidgets.remove(widget); - } - - void idle() - { - puglProcessEvents(fView); - -#ifdef DISTRHO_OS_MAC - if (fNeedsIdle) + if (pData->keepAspectRatio) { - NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init]; - NSEvent* event; + const double ratio = static_cast(pData->minWidth) + / static_cast(pData->minHeight); + const double reqRatio = static_cast(width) + / static_cast(height); - for (;;) + if (d_isNotEqual(ratio, reqRatio)) { - event = [NSApp - nextEventMatchingMask:NSAnyEventMask - untilDate:[NSDate distantPast] - inMode:NSDefaultRunLoopMode - dequeue:YES]; - - if (event == nil) - break; - - [NSApp sendEvent: event]; + // fix width + if (reqRatio > ratio) + width = static_cast(height * ratio + 0.5); + // fix height + else + height = static_cast(static_cast(width) / ratio + 0.5); } - - [pool release]; } -#endif - - if (fModal.enabled && fModal.parent != nullptr) - fModal.parent->idle(); - } - - // ------------------------------------------------------------------- - - void onPuglDisplay() - { - fSelf->onDisplayBefore(); - - FOR_EACH_WIDGET(it) - { - Widget* const widget(*it); - widget->pData->display(fWidth, fHeight, false); - } - - fSelf->onDisplayAfter(); - } - - int onPuglKeyboard(const bool press, const uint key) - { - DBGp("PUGL: onKeyboard : %i %i\n", press, key); - - if (fModal.childFocus != nullptr) - { - fModal.childFocus->focus(); - return 0; - } - - Widget::KeyboardEvent ev; - ev.press = press; - ev.key = key; - ev.mod = static_cast(puglGetModifiers(fView)); - ev.time = puglGetEventTimestamp(fView); - - FOR_EACH_WIDGET_INV(rit) - { - Widget* const widget(*rit); - - if (widget->isVisible() && widget->onKeyboard(ev)) - return 0; - } - - return 1; - } - - int onPuglSpecial(const bool press, const Key key) - { - DBGp("PUGL: onSpecial : %i %i\n", press, key); - - if (fModal.childFocus != nullptr) - { - fModal.childFocus->focus(); - return 0; - } - - Widget::SpecialEvent ev; - ev.press = press; - ev.key = key; - ev.mod = static_cast(puglGetModifiers(fView)); - ev.time = puglGetEventTimestamp(fView); - - FOR_EACH_WIDGET_INV(rit) - { - Widget* const widget(*rit); - - if (widget->isVisible() && widget->onSpecial(ev)) - return 0; - } - - return 1; - } - - void onPuglMouse(const int button, const bool press, const int x, const int y) - { - DBGp("PUGL: onMouse : %i %i %i %i\n", button, press, x, y); - - // FIXME - pugl sends 2 of these for each window on init, don't ask me why. we'll ignore it - if (press && button == 0 && x == 0 && y == 0) return; - - if (fModal.childFocus != nullptr) - return fModal.childFocus->focus(); - - Widget::MouseEvent ev; - ev.button = button; - ev.press = press; - ev.mod = static_cast(puglGetModifiers(fView)); - ev.time = puglGetEventTimestamp(fView); - - FOR_EACH_WIDGET_INV(rit) - { - Widget* const widget(*rit); - - ev.pos = Point(x-widget->getAbsoluteX(), y-widget->getAbsoluteY()); - - if (widget->isVisible() && widget->onMouse(ev)) - break; - } - } - - void onPuglMotion(const int x, const int y) - { - DBGp("PUGL: onMotion : %i %i\n", x, y); - - if (fModal.childFocus != nullptr) - return; - - Widget::MotionEvent ev; - ev.mod = static_cast(puglGetModifiers(fView)); - ev.time = puglGetEventTimestamp(fView); - - FOR_EACH_WIDGET_INV(rit) - { - Widget* const widget(*rit); - - ev.pos = Point(x-widget->getAbsoluteX(), y-widget->getAbsoluteY()); - - if (widget->isVisible() && widget->onMotion(ev)) - break; - } - } - - void onPuglScroll(const int x, const int y, const float dx, const float dy) - { - DBGp("PUGL: onScroll : %i %i %f %f\n", x, y, dx, dy); - - if (fModal.childFocus != nullptr) - return; - - Widget::ScrollEvent ev; - ev.delta = Point(dx, dy); - ev.mod = static_cast(puglGetModifiers(fView)); - ev.time = puglGetEventTimestamp(fView); - - FOR_EACH_WIDGET_INV(rit) - { - Widget* const widget(*rit); - - ev.pos = Point(x-widget->getAbsoluteX(), y-widget->getAbsoluteY()); - - if (widget->isVisible() && widget->onScroll(ev)) - break; - } - } - - void onPuglReshape(const int width, const int height) - { - DBGp("PUGL: onReshape : %i %i\n", width, height); - - if (width <= 1 && height <= 1) - return; - - fWidth = static_cast(width); - fHeight = static_cast(height); - - fSelf->onReshape(fWidth, fHeight); - - FOR_EACH_WIDGET(it) - { - Widget* const widget(*it); - - if (widget->pData->needsFullViewport) - widget->setSize(fWidth, fHeight); - } - } - - void onPuglClose() - { - DBG("PUGL: onClose\n"); - - if (fModal.enabled) - exec_fini(); - - fSelf->onClose(); - - if (fModal.childFocus != nullptr) - fModal.childFocus->fSelf->onClose(); - - close(); - } - - // ------------------------------------------------------------------- - - bool handlePluginKeyboard(const bool press, const uint key) - { - DBGp("PUGL: handlePluginKeyboard : %i %i\n", press, key); - - if (fModal.childFocus != nullptr) - { - fModal.childFocus->focus(); - return true; - } - - Widget::KeyboardEvent ev; - ev.press = press; - ev.key = key; - ev.mod = static_cast(fView->mods); - ev.time = 0; - - if ((ev.mod & kModifierShift) != 0 && ev.key >= 'a' && ev.key <= 'z') - ev.key -= 'a' - 'A'; // a-z -> A-Z - - FOR_EACH_WIDGET_INV(rit) - { - Widget* const widget(*rit); - - if (widget->isVisible() && widget->onKeyboard(ev)) - return true; - } - - return false; - } - - bool handlePluginSpecial(const bool press, const Key key) - { - DBGp("PUGL: handlePluginSpecial : %i %i\n", press, key); - - if (fModal.childFocus != nullptr) - { - fModal.childFocus->focus(); - return true; - } - - int mods = 0x0; - - switch (key) - { - case kKeyShift: - mods |= kModifierShift; - break; - case kKeyControl: - mods |= kModifierControl; - break; - case kKeyAlt: - mods |= kModifierAlt; - break; - default: - break; - } - - if (mods != 0x0) - { - if (press) - fView->mods |= mods; - else - fView->mods &= ~(mods); - } - - Widget::SpecialEvent ev; - ev.press = press; - ev.key = key; - ev.mod = static_cast(fView->mods); - ev.time = 0; - - FOR_EACH_WIDGET_INV(rit) - { - Widget* const widget(*rit); - - if (widget->isVisible() && widget->onSpecial(ev)) - return true; - } - - return false; - } - - // ------------------------------------------------------------------- - - Application& fApp; - Window* fSelf; - PuglView* fView; - - bool fFirstInit; - bool fVisible; - bool fResizable; - bool fUsingEmbed; - uint fWidth; - uint fHeight; - char* fTitle; - std::list fWidgets; - - struct Modal { - bool enabled; - PrivateData* parent; - PrivateData* childFocus; - - Modal() - : enabled(false), - parent(nullptr), - childFocus(nullptr) {} - - Modal(PrivateData* const p) - : enabled(false), - parent(p), - childFocus(nullptr) {} - - ~Modal() - { - DISTRHO_SAFE_ASSERT(! enabled); - DISTRHO_SAFE_ASSERT(childFocus == nullptr); - } - - DISTRHO_DECLARE_NON_COPY_STRUCT(Modal) - } fModal; - -#if defined(DISTRHO_OS_WINDOWS) - HWND hwnd; - HWND hwndParent; -#elif defined(DISTRHO_OS_MAC) - bool fNeedsIdle; - PuglOpenGLView* mView; - id mWindow; - id mParentWindow; -#else - Display* xDisplay; - ::Window xWindow; -#endif - - // ------------------------------------------------------------------- - // Callbacks - - #define handlePtr ((PrivateData*)puglGetHandle(view)) - - static void onDisplayCallback(PuglView* view) - { - handlePtr->onPuglDisplay(); - } - - static int onKeyboardCallback(PuglView* view, bool press, uint32_t key) - { - return handlePtr->onPuglKeyboard(press, key); - } - - static int onSpecialCallback(PuglView* view, bool press, PuglKey key) - { - return handlePtr->onPuglSpecial(press, static_cast(key)); - } - - static void onMouseCallback(PuglView* view, int button, bool press, int x, int y) - { - handlePtr->onPuglMouse(button, press, x, y); } - static void onMotionCallback(PuglView* view, int x, int y) + if (pData->usesSizeRequest) { - handlePtr->onPuglMotion(x, y); - } - - static void onScrollCallback(PuglView* view, int x, int y, float dx, float dy) - { - handlePtr->onPuglScroll(x, y, dx, dy); - } + DISTRHO_SAFE_ASSERT_RETURN(pData->topLevelWidgets.size() != 0,); - static void onReshapeCallback(PuglView* view, int width, int height) - { - handlePtr->onPuglReshape(width, height); - } + TopLevelWidget* const topLevelWidget = pData->topLevelWidgets.front(); + DISTRHO_SAFE_ASSERT_RETURN(topLevelWidget != nullptr,); - static void onCloseCallback(PuglView* view) - { - handlePtr->onPuglClose(); + topLevelWidget->requestSizeChange(width, height); } - -#ifndef DGL_FILE_BROWSER_DISABLED - static void fileBrowserSelectedCallback(PuglView* view, const char* filename) + else { - handlePtr->fSelf->fileBrowserSelected(filename); + puglSetWindowSize(pData->view, width, height); } -#endif - - #undef handlePtr - - DISTRHO_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(PrivateData) -}; - -// ----------------------------------------------------------------------- -// Window - -Window::Window(Application& app) - : pData(new PrivateData(app, this)) {} - -Window::Window(Application& app, Window& parent) - : pData(new PrivateData(app, this, parent)) {} - -Window::Window(Application& app, intptr_t parentId) - : pData(new PrivateData(app, this, parentId)) {} - -Window::~Window() -{ - delete pData; } -void Window::show() +void Window::setSize(const Size& size) { - pData->setVisible(true); + setSize(size.getWidth(), size.getHeight()); } -void Window::hide() +const char* Window::getTitle() const noexcept { - pData->setVisible(false); + return puglGetWindowTitle(pData->view); } -void Window::close() +void Window::setTitle(const char* const title) { - pData->close(); + if (pData->view != nullptr) + puglSetWindowTitle(pData->view, title); } -void Window::exec(bool lockWait) +bool Window::isIgnoringKeyRepeat() const noexcept { - pData->exec(lockWait); + return puglGetViewHint(pData->view, PUGL_IGNORE_KEY_REPEAT) == PUGL_TRUE; } -void Window::focus() +void Window::setIgnoringKeyRepeat(const bool ignore) noexcept { - pData->focus(); + puglSetViewHint(pData->view, PUGL_IGNORE_KEY_REPEAT, ignore); } -void Window::repaint() noexcept +bool Window::setClipboard(const char* const mimeType, const void* const data, const size_t dataSize) { - puglPostRedisplay(pData->fView); + return puglSetClipboard(pData->view, mimeType, data, dataSize) == PUGL_SUCCESS; } -// static int fib_filter_filename_filter(const char* const name) -// { -// return 1; -// (void)name; -// } - -#ifndef DGL_FILE_BROWSER_DISABLED -bool Window::openFileBrowser(const FileBrowserOptions& options) +const void* Window::getClipboard(const char*& mimeType, size_t& dataSize) { -# ifdef SOFD_HAVE_X11 - using DISTRHO_NAMESPACE::String; - - // -------------------------------------------------------------------------- - // configure start dir - - // TODO: get abspath if needed - // TODO: cross-platform - - String startDir(options.startDir); - - if (startDir.isEmpty()) - { - if (char* const dir_name = get_current_dir_name()) - { - startDir = dir_name; - std::free(dir_name); - } - } - - DISTRHO_SAFE_ASSERT_RETURN(startDir.isNotEmpty(), false); - - if (! startDir.endsWith('/')) - startDir += "/"; - - DISTRHO_SAFE_ASSERT_RETURN(x_fib_configure(0, startDir) == 0, false); - - // -------------------------------------------------------------------------- - // configure title - - String title(options.title); - - if (title.isEmpty()) - { - title = pData->getTitle(); - - if (title.isEmpty()) - title = "FileBrowser"; - } - - DISTRHO_SAFE_ASSERT_RETURN(x_fib_configure(1, title) == 0, false); - - // -------------------------------------------------------------------------- - // configure filters - - x_fib_cfg_filter_callback(nullptr); //fib_filter_filename_filter); - - // -------------------------------------------------------------------------- - // configure buttons - - x_fib_cfg_buttons(3, options.buttons.listAllFiles-1); - x_fib_cfg_buttons(1, options.buttons.showHidden-1); - x_fib_cfg_buttons(2, options.buttons.showPlaces-1); - - // -------------------------------------------------------------------------- - // show - - return (x_fib_show(pData->xDisplay, pData->xWindow, /*options.width*/0, /*options.height*/0) == 0); -# else - // not implemented - return false; - - // unused - (void)options; -# endif + DISTRHO_SAFE_ASSERT_RETURN(!pData->ignoreEvents, nullptr); + pData->ignoreEvents = true; + const void* const clipboard = puglGetClipboard(pData->view, &mimeType, &dataSize); + pData->ignoreEvents = false; + return clipboard; } -#endif -bool Window::isVisible() const noexcept +bool Window::setCursor(const MouseCursor cursor) { - return pData->fVisible; + return puglSetCursor(pData->view, static_cast(cursor)) == PUGL_SUCCESS; } -void Window::setVisible(bool yesNo) +bool Window::addIdleCallback(IdleCallback* const callback, const uint timerFrequencyInMs) { - pData->setVisible(yesNo); -} + DISTRHO_SAFE_ASSERT_RETURN(callback != nullptr, false) -bool Window::isResizable() const noexcept -{ - return pData->fResizable; + return pData->addIdleCallback(callback, timerFrequencyInMs); } -void Window::setResizable(bool yesNo) +bool Window::removeIdleCallback(IdleCallback* const callback) { - pData->setResizable(yesNo); -} + DISTRHO_SAFE_ASSERT_RETURN(callback != nullptr, false) -uint Window::getWidth() const noexcept -{ - return pData->fWidth; + return pData->removeIdleCallback(callback); } -uint Window::getHeight() const noexcept +Application& Window::getApp() const noexcept { - return pData->fHeight; + return pData->app; } -Size Window::getSize() const noexcept +#ifndef DPF_TEST_WINDOW_CPP +const GraphicsContext& Window::getGraphicsContext() const noexcept { - return Size(pData->fWidth, pData->fHeight); + return pData->getGraphicsContext(); } +#endif -void Window::setSize(uint width, uint height) +uintptr_t Window::getNativeWindowHandle() const noexcept { - pData->setSize(width, height); + return puglGetNativeWindow(pData->view); } -void Window::setSize(Size size) +double Window::getScaleFactor() const noexcept { - pData->setSize(size.getWidth(), size.getHeight()); + return pData->scaleFactor; } -const char* Window::getTitle() const noexcept +void Window::focus() { - return pData->getTitle(); + pData->focus(); } -void Window::setTitle(const char* title) +#ifndef DGL_FILE_BROWSER_DISABLED +bool Window::openFileBrowser(const FileBrowserOptions& options) { - pData->setTitle(title); + return pData->openFileBrowser(options); } +#endif -void Window::setTransientWinId(uintptr_t winId) +void Window::repaint() noexcept { - pData->setTransientWinId(winId); -} + if (pData->view == nullptr) + return; -Application& Window::getApp() const noexcept -{ - return pData->fApp; + puglPostRedisplay(pData->view); } -intptr_t Window::getWindowId() const noexcept +void Window::repaint(const Rectangle& rect) noexcept { - return puglGetNativeWindow(pData->fView); -} + if (pData->view == nullptr) + return; -void Window::_addWidget(Widget* const widget) -{ - pData->addWidget(widget); + PuglRect prect = { + static_cast(rect.getX()), + static_cast(rect.getY()), + static_cast(rect.getWidth()), + static_cast(rect.getHeight()), + }; + if (pData->autoScaling) + { + const double autoScaleFactor = pData->autoScaleFactor; + + prect.x *= autoScaleFactor; + prect.y *= autoScaleFactor; + prect.width *= autoScaleFactor; + prect.height *= autoScaleFactor; + } + puglPostRedisplayRect(pData->view, prect); } -void Window::_removeWidget(Widget* const widget) +void Window::renderToPicture(const char* const filename) { - pData->removeWidget(widget); + pData->filenameToRenderInto = strdup(filename); } -void Window::_idle() +void Window::runAsModal(bool blockWait) { - pData->idle(); + pData->runAsModal(blockWait); } -// ----------------------------------------------------------------------- - -void Window::addIdleCallback(IdleCallback* const callback) +Size Window::getGeometryConstraints(bool& keepAspectRatio) { - DISTRHO_SAFE_ASSERT_RETURN(callback != nullptr,) - - pData->fApp.pData->idleCallbacks.push_back(callback); + keepAspectRatio = pData->keepAspectRatio; + return Size(pData->minWidth, pData->minHeight); } -void Window::removeIdleCallback(IdleCallback* const callback) +void Window::setGeometryConstraints(uint minimumWidth, + uint minimumHeight, + const bool keepAspectRatio, + const bool automaticallyScale, + const bool resizeNowIfAutoScaling) { - DISTRHO_SAFE_ASSERT_RETURN(callback != nullptr,) + DISTRHO_SAFE_ASSERT_RETURN(minimumWidth > 0,); + DISTRHO_SAFE_ASSERT_RETURN(minimumHeight > 0,); - pData->fApp.pData->idleCallbacks.remove(callback); -} + pData->minWidth = minimumWidth; + pData->minHeight = minimumHeight; + pData->autoScaling = automaticallyScale; + pData->keepAspectRatio = keepAspectRatio; -// ----------------------------------------------------------------------- + if (pData->view == nullptr) + return; -void Window::onDisplayBefore() -{ - glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); - glLoadIdentity(); -} + const double scaleFactor = pData->scaleFactor; -void Window::onDisplayAfter() -{ -} + if (automaticallyScale && scaleFactor != 1.0) + { + minimumWidth *= scaleFactor; + minimumHeight *= scaleFactor; + } -void Window::onReshape(uint width, uint height) -{ - glEnable(GL_BLEND); - glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); - glMatrixMode(GL_PROJECTION); - glLoadIdentity(); - glOrtho(0.0, static_cast(width), static_cast(height), 0.0, 0.0, 1.0); - glViewport(0, 0, static_cast(width), static_cast(height)); - glMatrixMode(GL_MODELVIEW); - glLoadIdentity(); -} + puglSetGeometryConstraints(pData->view, minimumWidth, minimumHeight, keepAspectRatio); -void Window::onClose() -{ -} + if (scaleFactor != 1.0 && automaticallyScale && resizeNowIfAutoScaling) + { + const Size size(getSize()); -#ifndef DGL_FILE_BROWSER_DISABLED -void Window::fileBrowserSelected(const char*) -{ + setSize(static_cast(size.getWidth() * scaleFactor + 0.5), + static_cast(size.getHeight() * scaleFactor + 0.5)); + } } -#endif -bool Window::handlePluginKeyboard(const bool press, const uint key) +bool Window::onClose() { - return pData->handlePluginKeyboard(press, key); + return true; } -bool Window::handlePluginSpecial(const bool press, const Key key) +void Window::onFocus(bool, CrossingMode) { - return pData->handlePluginSpecial(press, key); } -// ----------------------------------------------------------------------- - -StandaloneWindow::StandaloneWindow() - : Application(), - Window((Application&)*this), - fWidget(nullptr) {} - -void StandaloneWindow::exec() +void Window::onReshape(uint, uint) { - Window::show(); - Application::exec(); + puglFallbackOnResize(pData->view); } -void StandaloneWindow::onReshape(uint width, uint height) +void Window::onScaleFactorChanged(double) { - if (fWidget != nullptr) - fWidget->setSize(width, height); - Window::onReshape(width, height); } -void StandaloneWindow::_addWidget(Widget* widget) +#ifndef DGL_FILE_BROWSER_DISABLED +void Window::onFileSelected(const char*) { - if (fWidget == nullptr) - { - fWidget = widget; - fWidget->pData->needsFullViewport = true; - } - Window::_addWidget(widget); } +#endif -void StandaloneWindow::_removeWidget(Widget* widget) +#if 0 +void Window::setTransientWinId(const uintptr_t winId) { - if (fWidget == widget) - { - fWidget->pData->needsFullViewport = false; - fWidget = nullptr; - } - Window::_removeWidget(widget); + puglSetTransientFor(pData->view, winId); } +#endif // ----------------------------------------------------------------------- END_NAMESPACE_DGL - -#undef DBG -#undef DBGF diff --git a/source/modules/dgl/src/WindowPrivateData.cpp b/source/modules/dgl/src/WindowPrivateData.cpp new file mode 100644 index 000000000..0228551c0 --- /dev/null +++ b/source/modules/dgl/src/WindowPrivateData.cpp @@ -0,0 +1,1079 @@ +/* + * DISTRHO Plugin Framework (DPF) + * Copyright (C) 2012-2021 Filipe Coelho + * + * Permission to use, copy, modify, and/or distribute this software for any purpose with + * or without fee is hereby granted, provided that the above copyright notice and this + * permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD + * TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN + * NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER + * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "WindowPrivateData.hpp" +#include "TopLevelWidgetPrivateData.hpp" + +#include "pugl.hpp" + +// #define DGL_DEBUG_EVENTS + +#if defined(DEBUG) && defined(DGL_DEBUG_EVENTS) +# ifdef DISTRHO_PROPER_CPP11_SUPPORT +# include +# else +# include +# endif +#endif + +START_NAMESPACE_DGL + +#if defined(DEBUG) && defined(DGL_DEBUG_EVENTS) +# define DGL_DBG(msg) std::fprintf(stderr, "%s", msg); +# define DGL_DBGp(...) std::fprintf(stderr, __VA_ARGS__); +# define DGL_DBGF std::fflush(stderr); +#else +# define DGL_DBG(msg) +# define DGL_DBGp(...) +# define DGL_DBGF +#endif + +#define DEFAULT_WIDTH 640 +#define DEFAULT_HEIGHT 480 + +#define FOR_EACH_TOP_LEVEL_WIDGET(it) \ + for (std::list::iterator it = topLevelWidgets.begin(); it != topLevelWidgets.end(); ++it) + +#define FOR_EACH_TOP_LEVEL_WIDGET_INV(rit) \ + for (std::list::reverse_iterator rit = topLevelWidgets.rbegin(); rit != topLevelWidgets.rend(); ++rit) + +// ----------------------------------------------------------------------- + +static double getDesktopScaleFactor(const PuglView* const view) +{ + // allow custom scale for testing + if (const char* const scale = getenv("DPF_SCALE_FACTOR")) + return std::max(1.0, std::atof(scale)); + + if (view != nullptr) + return puglGetDesktopScaleFactor(view); + + return 1.0; +} + +// ----------------------------------------------------------------------- + +Window::PrivateData::PrivateData(Application& a, Window* const s) + : app(a), + appData(a.pData), + self(s), + view(puglNewView(appData->world)), + transientParentView(nullptr), + topLevelWidgets(), + isClosed(true), + isVisible(false), + isEmbed(false), + usesSizeRequest(false), + scaleFactor(getDesktopScaleFactor(view)), + autoScaling(false), + autoScaleFactor(1.0), + minWidth(0), + minHeight(0), + keepAspectRatio(false), + ignoreIdleCallbacks(false), + ignoreEvents(false), + filenameToRenderInto(nullptr), +#ifndef DGL_FILE_BROWSER_DISABLED + fileBrowserHandle(nullptr), +#endif + modal() +{ + initPre(DEFAULT_WIDTH, DEFAULT_HEIGHT, false); +} + +Window::PrivateData::PrivateData(Application& a, Window* const s, PrivateData* const ppData) + : app(a), + appData(a.pData), + self(s), + view(puglNewView(appData->world)), + transientParentView(ppData->view), + topLevelWidgets(), + isClosed(true), + isVisible(false), + isEmbed(false), + usesSizeRequest(false), + scaleFactor(ppData->scaleFactor), + autoScaling(false), + autoScaleFactor(1.0), + minWidth(0), + minHeight(0), + keepAspectRatio(false), + ignoreIdleCallbacks(false), + ignoreEvents(false), + filenameToRenderInto(nullptr), +#ifndef DGL_FILE_BROWSER_DISABLED + fileBrowserHandle(nullptr), +#endif + modal(ppData) +{ + puglSetTransientFor(view, puglGetNativeWindow(transientParentView)); + + initPre(DEFAULT_WIDTH, DEFAULT_HEIGHT, false); +} + +Window::PrivateData::PrivateData(Application& a, Window* const s, + const uintptr_t parentWindowHandle, + const double scale, const bool resizable) + : app(a), + appData(a.pData), + self(s), + view(puglNewView(appData->world)), + transientParentView(nullptr), + topLevelWidgets(), + isClosed(parentWindowHandle == 0), + isVisible(parentWindowHandle != 0), + isEmbed(parentWindowHandle != 0), + usesSizeRequest(false), + scaleFactor(scale != 0.0 ? scale : getDesktopScaleFactor(view)), + autoScaling(false), + autoScaleFactor(1.0), + minWidth(0), + minHeight(0), + keepAspectRatio(false), + ignoreIdleCallbacks(false), + ignoreEvents(false), + filenameToRenderInto(nullptr), +#ifndef DGL_FILE_BROWSER_DISABLED + fileBrowserHandle(nullptr), +#endif + modal() +{ + if (isEmbed) + puglSetParentWindow(view, parentWindowHandle); + + initPre(DEFAULT_WIDTH, DEFAULT_HEIGHT, resizable); +} + +Window::PrivateData::PrivateData(Application& a, Window* const s, + const uintptr_t parentWindowHandle, + const uint width, const uint height, + const double scale, const bool resizable, const bool isVST3) + : app(a), + appData(a.pData), + self(s), + view(appData->world != nullptr ? puglNewView(appData->world) : nullptr), + transientParentView(nullptr), + topLevelWidgets(), + isClosed(parentWindowHandle == 0), + isVisible(parentWindowHandle != 0 && view != nullptr), + isEmbed(parentWindowHandle != 0), + usesSizeRequest(isVST3), + scaleFactor(scale != 0.0 ? scale : getDesktopScaleFactor(view)), + autoScaling(false), + autoScaleFactor(1.0), + minWidth(0), + minHeight(0), + keepAspectRatio(false), + ignoreIdleCallbacks(false), + ignoreEvents(false), + filenameToRenderInto(nullptr), +#ifndef DGL_FILE_BROWSER_DISABLED + fileBrowserHandle(nullptr), +#endif + modal() +{ + if (isEmbed) + puglSetParentWindow(view, parentWindowHandle); + + initPre(width != 0 ? width : DEFAULT_WIDTH, height != 0 ? height : DEFAULT_HEIGHT, resizable); +} + +Window::PrivateData::~PrivateData() +{ + appData->idleCallbacks.remove(this); + appData->windows.remove(self); + std::free(filenameToRenderInto); + + if (view == nullptr) + return; + + if (isEmbed) + { +#ifndef DGL_FILE_BROWSER_DISABLED + if (fileBrowserHandle != nullptr) + fileBrowserClose(fileBrowserHandle); +#endif + puglHide(view); + appData->oneWindowClosed(); + isClosed = true; + isVisible = false; + } + + puglFreeView(view); +} + +// ----------------------------------------------------------------------- + +void Window::PrivateData::initPre(const uint width, const uint height, const bool resizable) +{ + appData->windows.push_back(self); + appData->idleCallbacks.push_back(this); + memset(graphicsContext, 0, sizeof(graphicsContext)); + + if (view == nullptr) + { + d_stderr2("Failed to create Pugl view, everything will fail!"); + return; + } + + puglSetMatchingBackendForCurrentBuild(view); + + puglClearMinSize(view); + puglSetWindowSize(view, width, height); + + puglSetHandle(view, this); + puglSetViewHint(view, PUGL_RESIZABLE, resizable ? PUGL_TRUE : PUGL_FALSE); + puglSetViewHint(view, PUGL_IGNORE_KEY_REPEAT, PUGL_FALSE); +#if DGL_USE_RGBA + puglSetViewHint(view, PUGL_DEPTH_BITS, 24); +#else + puglSetViewHint(view, PUGL_DEPTH_BITS, 16); +#endif + puglSetViewHint(view, PUGL_STENCIL_BITS, 8); +#ifdef DGL_USE_OPENGL3 + puglSetViewHint(view, PUGL_USE_COMPAT_PROFILE, PUGL_FALSE); + puglSetViewHint(view, PUGL_CONTEXT_VERSION_MAJOR, 3); +#endif + // PUGL_SAMPLES ?? + puglSetEventFunc(view, puglEventCallback); +} + +bool Window::PrivateData::initPost() +{ + if (view == nullptr) + return false; + + // create view now, as a few methods we allow devs to use require it + if (puglRealize(view) != PUGL_SUCCESS) + { + view = nullptr; + d_stderr2("Failed to realize Pugl view, everything will fail!"); + return false; + } + + if (isEmbed) + { + appData->oneWindowShown(); + puglShow(view); + } + + return true; +} + +// ----------------------------------------------------------------------- + +void Window::PrivateData::close() +{ + DGL_DBG("Window close\n"); + // DGL_DBGp("Window close DBG %i %i %p\n", isEmbed, isClosed, appData); + + if (isEmbed || isClosed) + return; + + isClosed = true; + hide(); + appData->oneWindowClosed(); +} + +// ----------------------------------------------------------------------- + +void Window::PrivateData::show() +{ + if (isVisible) + { + DGL_DBG("Window show matches current visible state, ignoring request\n"); + return; + } + if (isEmbed) + { + DGL_DBG("Window show cannot be called when embedded\n"); + return; + } + + DGL_DBG("Window show called\n"); + + if (view == nullptr) + return; + + if (isClosed) + { + isClosed = false; + appData->oneWindowShown(); + + // FIXME + PuglRect rect = puglGetFrame(view); + puglSetWindowSize(view, static_cast(rect.width), static_cast(rect.height)); + +#if defined(DISTRHO_OS_WINDOWS) + puglWin32ShowCentered(view); +#elif defined(DISTRHO_OS_MAC) + puglMacOSShowCentered(view); +#else + puglShow(view); +#endif + } + else + { +#ifdef DISTRHO_OS_WINDOWS + puglWin32RestoreWindow(view); +#else + puglShow(view); +#endif + } + + isVisible = true; +} + +void Window::PrivateData::hide() +{ + if (isEmbed) + { + DGL_DBG("Window hide cannot be called when embedded\n"); + return; + } + if (! isVisible) + { + DGL_DBG("Window hide matches current visible state, ignoring request\n"); + return; + } + + DGL_DBG("Window hide called\n"); + + if (modal.enabled) + stopModal(); + +#ifndef DGL_FILE_BROWSER_DISABLED + if (fileBrowserHandle != nullptr) + { + fileBrowserClose(fileBrowserHandle); + fileBrowserHandle = nullptr; + } +#endif + + puglHide(view); + + isVisible = false; +} + +// ----------------------------------------------------------------------- + +void Window::PrivateData::focus() +{ + if (view == nullptr) + return; + + if (! isEmbed) + puglRaiseWindow(view); + +#if defined(HAVE_X11) && !defined(DISTRHO_OS_MAC) && !defined(DISTRHO_OS_WINDOWS) + puglX11GrabFocus(view); +#else + puglGrabFocus(view); +#endif +} + +// ----------------------------------------------------------------------- + +void Window::PrivateData::setResizable(const bool resizable) +{ + DISTRHO_SAFE_ASSERT_RETURN(! isEmbed,); + + DGL_DBG("Window setResizable called\n"); + + puglSetViewHint(view, PUGL_RESIZABLE, resizable ? PUGL_TRUE : PUGL_FALSE); +#ifdef DISTRHO_OS_WINDOWS + puglWin32SetWindowResizable(view, resizable); +#endif +} + +// ----------------------------------------------------------------------- + +void Window::PrivateData::idleCallback() +{ +#ifndef DGL_FILE_BROWSER_DISABLED + if (fileBrowserHandle != nullptr && fileBrowserIdle(fileBrowserHandle)) + { + self->onFileSelected(fileBrowserGetPath(fileBrowserHandle)); + fileBrowserClose(fileBrowserHandle); + fileBrowserHandle = nullptr; + } +#endif +} + +// ----------------------------------------------------------------------- +// idle callback stuff + +bool Window::PrivateData::addIdleCallback(IdleCallback* const callback, const uint timerFrequencyInMs) +{ + if (ignoreIdleCallbacks) + return false; + + if (timerFrequencyInMs == 0) + { + appData->idleCallbacks.push_back(callback); + return true; + } + + return puglStartTimer(view, (uintptr_t)callback, static_cast(timerFrequencyInMs) / 1000.0) == PUGL_SUCCESS; +} + +bool Window::PrivateData::removeIdleCallback(IdleCallback* const callback) +{ + if (ignoreIdleCallbacks) + return false; + + if (std::find(appData->idleCallbacks.begin(), + appData->idleCallbacks.end(), callback) != appData->idleCallbacks.end()) + { + appData->idleCallbacks.remove(callback); + return true; + } + + return puglStopTimer(view, (uintptr_t)callback) == PUGL_SUCCESS; +} + +#ifndef DGL_FILE_BROWSER_DISABLED +// ----------------------------------------------------------------------- +// file handling + +bool Window::PrivateData::openFileBrowser(const FileBrowserOptions& options) +{ + if (fileBrowserHandle != nullptr) + fileBrowserClose(fileBrowserHandle); + + FileBrowserOptions options2 = options; + + if (options2.title == nullptr) + options2.title = puglGetWindowTitle(view); + + fileBrowserHandle = fileBrowserCreate(isEmbed, + puglGetNativeWindow(view), + autoScaling ? autoScaleFactor : scaleFactor, + options2); + + return fileBrowserHandle != nullptr; +} +#endif // ! DGL_FILE_BROWSER_DISABLED + +// ----------------------------------------------------------------------- +// modal handling + +void Window::PrivateData::startModal() +{ + DGL_DBG("Window modal loop starting..."); DGL_DBGF; + DISTRHO_SAFE_ASSERT_RETURN(modal.parent != nullptr, show()); + + // activate modal mode for this window + modal.enabled = true; + + // make parent give focus to us + modal.parent->modal.child = this; + + // make sure both parent and ourselves are visible + modal.parent->show(); + show(); + +#ifdef DISTRHO_OS_MAC + puglMacOSAddChildWindow(modal.parent->view, view); +#endif + + DGL_DBG("Ok\n"); +} + +void Window::PrivateData::stopModal() +{ + DGL_DBG("Window modal loop stopping..."); DGL_DBGF; + + // deactivate modal mode + modal.enabled = false; + + // safety checks, make sure we have a parent and we are currently active as the child to give focus to + if (modal.parent == nullptr) + return; + if (modal.parent->modal.child != this) + return; + +#ifdef DISTRHO_OS_MAC + puglMacOSRemoveChildWindow(modal.parent->view, view); +#endif + + // stop parent from giving focus to us, so it behaves like normal + modal.parent->modal.child = nullptr; + + // refocus main window after closing child + if (! modal.parent->isClosed) + { + const Widget::MotionEvent ev; + modal.parent->onPuglMotion(ev); + modal.parent->focus(); + } + + DGL_DBG("Ok\n"); +} + +void Window::PrivateData::runAsModal(const bool blockWait) +{ + DGL_DBGp("Window::PrivateData::runAsModal %i\n", blockWait); + startModal(); + + if (blockWait) + { + DISTRHO_SAFE_ASSERT_RETURN(appData->isStandalone,); + + while (isVisible && modal.enabled) + appData->idle(10); + + stopModal(); + } + else + { + appData->idle(0); + } +} + +// ----------------------------------------------------------------------- +// pugl events + +void Window::PrivateData::onPuglConfigure(const double width, const double height) +{ + DISTRHO_SAFE_ASSERT_INT2_RETURN(width > 1 && height > 1, width, height,); + + DGL_DBGp("PUGL: onReshape : %f %f\n", width, height); + + if (autoScaling) + { + const double scaleHorizontal = width / static_cast(minWidth); + const double scaleVertical = height / static_cast(minHeight); + autoScaleFactor = scaleHorizontal < scaleVertical ? scaleHorizontal : scaleVertical; + } + + const uint uwidth = static_cast(width + 0.5); + const uint uheight = static_cast(height + 0.5); + + self->onReshape(uwidth, uheight); + +#ifndef DPF_TEST_WINDOW_CPP + FOR_EACH_TOP_LEVEL_WIDGET(it) + { + TopLevelWidget* const widget(*it); + + /* Some special care here, we call Widget::setSize instead of the TopLevelWidget one. + * This is because we want TopLevelWidget::setSize to handle both window and widget size, + * but we dont want to change window size here, because we are the window.. + * So we just call the Widget specific method manually. + * + * Alternatively, we could expose a resize function on the pData, like done with the display function. + * But there is nothing extra we need to do in there, so this works fine. + */ + ((Widget*)widget)->setSize(uwidth, uheight); + } +#endif + + // always repaint after a resize + puglPostRedisplay(view); +} + +void Window::PrivateData::onPuglExpose() +{ + DGL_DBGp("PUGL: onPuglExpose\n"); + + puglOnDisplayPrepare(view); + +#ifndef DPF_TEST_WINDOW_CPP + FOR_EACH_TOP_LEVEL_WIDGET(it) + { + TopLevelWidget* const widget(*it); + + if (widget->isVisible()) + widget->pData->display(); + } + + if (char* const filename = filenameToRenderInto) + { + const PuglRect rect = puglGetFrame(view); + filenameToRenderInto = nullptr; + renderToPicture(filename, getGraphicsContext(), static_cast(rect.width), static_cast(rect.height)); + std::free(filename); + } +#endif +} + +void Window::PrivateData::onPuglClose() +{ + DGL_DBG("PUGL: onClose\n"); + +#ifndef DISTRHO_OS_MAC + // if we are running as standalone we can prevent closing in certain conditions + if (appData->isStandalone) + { + // a child window is active, gives focus to it + if (modal.child != nullptr) + return modal.child->focus(); + + // ask window if we should close + if (! self->onClose()) + return; + } +#endif + + if (modal.enabled) + stopModal(); + + if (modal.child != nullptr) + { + modal.child->close(); + modal.child = nullptr; + } + + close(); +} + +void Window::PrivateData::onPuglFocus(const bool focus, const CrossingMode mode) +{ + DGL_DBGp("onPuglFocus : %i %i | %i\n", focus, mode, isClosed); + + if (isClosed) + return; + + if (modal.child != nullptr) + return modal.child->focus(); + + self->onFocus(focus, mode); +} + +void Window::PrivateData::onPuglKey(const Widget::KeyboardEvent& ev) +{ + DGL_DBGp("onPuglKey : %i %u %u\n", ev.press, ev.key, ev.keycode); + + if (modal.child != nullptr) + return modal.child->focus(); + +#ifndef DPF_TEST_WINDOW_CPP + FOR_EACH_TOP_LEVEL_WIDGET_INV(rit) + { + TopLevelWidget* const widget(*rit); + + if (widget->isVisible() && widget->onKeyboard(ev)) + break; + } +#endif +} + +void Window::PrivateData::onPuglText(const Widget::CharacterInputEvent& ev) +{ + DGL_DBGp("onPuglText : %u %u %s\n", ev.keycode, ev.character, ev.string); + + if (modal.child != nullptr) + return modal.child->focus(); + +#ifndef DPF_TEST_WINDOW_CPP + FOR_EACH_TOP_LEVEL_WIDGET_INV(rit) + { + TopLevelWidget* const widget(*rit); + + if (widget->isVisible() && widget->onCharacterInput(ev)) + break; + } +#endif +} + +void Window::PrivateData::onPuglMouse(const Widget::MouseEvent& ev) +{ + DGL_DBGp("onPuglMouse : %i %i %f %f\n", ev.button, ev.press, ev.pos.getX(), ev.pos.getY()); + + if (modal.child != nullptr) + return modal.child->focus(); + +#ifndef DPF_TEST_WINDOW_CPP + FOR_EACH_TOP_LEVEL_WIDGET_INV(rit) + { + TopLevelWidget* const widget(*rit); + + if (widget->isVisible() && widget->onMouse(ev)) + break; + } +#endif +} + +void Window::PrivateData::onPuglMotion(const Widget::MotionEvent& ev) +{ + DGL_DBGp("onPuglMotion : %f %f\n", ev.pos.getX(), ev.pos.getY()); + + if (modal.child != nullptr) + return modal.child->focus(); + +#ifndef DPF_TEST_WINDOW_CPP + FOR_EACH_TOP_LEVEL_WIDGET_INV(rit) + { + TopLevelWidget* const widget(*rit); + + if (widget->isVisible() && widget->onMotion(ev)) + break; + } +#endif +} + +void Window::PrivateData::onPuglScroll(const Widget::ScrollEvent& ev) +{ + DGL_DBGp("onPuglScroll : %f %f %f %f\n", ev.pos.getX(), ev.pos.getY(), ev.delta.getX(), ev.delta.getY()); + + if (modal.child != nullptr) + return modal.child->focus(); + +#ifndef DPF_TEST_WINDOW_CPP + FOR_EACH_TOP_LEVEL_WIDGET_INV(rit) + { + TopLevelWidget* const widget(*rit); + + if (widget->isVisible() && widget->onScroll(ev)) + break; + } +#endif +} + +#if defined(DEBUG) && defined(DGL_DEBUG_EVENTS) +static int printEvent(const PuglEvent* event, const char* prefix, const bool verbose); +#endif + +PuglStatus Window::PrivateData::puglEventCallback(PuglView* const view, const PuglEvent* const event) +{ + Window::PrivateData* const pData = (Window::PrivateData*)puglGetHandle(view); +#if defined(DEBUG) && defined(DGL_DEBUG_EVENTS) + if (event->type != PUGL_TIMER) { + printEvent(event, "pugl event: ", true); + } +#endif + + switch (event->type) + { + ///< No event + case PUGL_NOTHING: + break; + + ///< View created, a #PuglEventCreate + case PUGL_CREATE: +#if defined(HAVE_X11) && !defined(DISTRHO_OS_MAC) && !defined(DISTRHO_OS_WINDOWS) + if (! pData->isEmbed) + puglX11SetWindowTypeAndPID(view, pData->appData->isStandalone); +#endif + break; + + ///< View destroyed, a #PuglEventDestroy + case PUGL_DESTROY: + break; + + ///< View moved/resized, a #PuglEventConfigure + case PUGL_CONFIGURE: + // unused x, y (double) + pData->onPuglConfigure(event->configure.width, event->configure.height); + break; + + ///< View made visible, a #PuglEventMap + case PUGL_MAP: + break; + + ///< View made invisible, a #PuglEventUnmap + case PUGL_UNMAP: + break; + + ///< View ready to draw, a #PuglEventUpdate + case PUGL_UPDATE: + break; + + ///< View must be drawn, a #PuglEventExpose + case PUGL_EXPOSE: + if (pData->ignoreEvents) + break; + // unused x, y, width, height (double) + pData->onPuglExpose(); + break; + + ///< View will be closed, a #PuglEventClose + case PUGL_CLOSE: + pData->onPuglClose(); + break; + + ///< Keyboard focus entered view, a #PuglEventFocus + case PUGL_FOCUS_IN: + ///< Keyboard focus left view, a #PuglEventFocus + case PUGL_FOCUS_OUT: + if (pData->ignoreEvents) + break; + pData->onPuglFocus(event->type == PUGL_FOCUS_IN, + static_cast(event->focus.mode)); + break; + + ///< Key pressed, a #PuglEventKey + case PUGL_KEY_PRESS: + ///< Key released, a #PuglEventKey + case PUGL_KEY_RELEASE: + { + if (pData->ignoreEvents) + break; + // unused x, y, xRoot, yRoot (double) + Widget::KeyboardEvent ev; + ev.mod = event->key.state; + ev.flags = event->key.flags; + ev.time = static_cast(event->key.time * 1000.0 + 0.5); + ev.press = event->type == PUGL_KEY_PRESS; + ev.key = event->key.key; + ev.keycode = event->key.keycode; + + // keyboard events must always be lowercase + if (ev.key >= 'A' && ev.key <= 'Z') + { + ev.key += 'a' - 'A'; // A-Z -> a-z + ev.mod |= kModifierShift; + } + + pData->onPuglKey(ev); + break; + } + + ///< Character entered, a #PuglEventText + case PUGL_TEXT: + { + if (pData->ignoreEvents) + break; + // unused x, y, xRoot, yRoot (double) + Widget::CharacterInputEvent ev; + ev.mod = event->text.state; + ev.flags = event->text.flags; + ev.time = static_cast(event->text.time * 1000.0 + 0.5); + ev.keycode = event->text.keycode; + ev.character = event->text.character; + std::strncpy(ev.string, event->text.string, sizeof(ev.string)); + pData->onPuglText(ev); + break; + } + + ///< Pointer entered view, a #PuglEventCrossing + case PUGL_POINTER_IN: + break; + ///< Pointer left view, a #PuglEventCrossing + case PUGL_POINTER_OUT: + break; + + ///< Mouse button pressed, a #PuglEventButton + case PUGL_BUTTON_PRESS: + ///< Mouse button released, a #PuglEventButton + case PUGL_BUTTON_RELEASE: + { + if (pData->ignoreEvents) + break; + Widget::MouseEvent ev; + ev.mod = event->button.state; + ev.flags = event->button.flags; + ev.time = static_cast(event->button.time * 1000.0 + 0.5); + ev.button = event->button.button; + ev.press = event->type == PUGL_BUTTON_PRESS; + ev.pos = Point(event->button.x, event->button.y); + ev.absolutePos = ev.pos; + pData->onPuglMouse(ev); + break; + } + + ///< Pointer moved, a #PuglEventMotion + case PUGL_MOTION: + { + if (pData->ignoreEvents) + break; + Widget::MotionEvent ev; + ev.mod = event->motion.state; + ev.flags = event->motion.flags; + ev.time = static_cast(event->motion.time * 1000.0 + 0.5); + ev.pos = Point(event->motion.x, event->motion.y); + ev.absolutePos = ev.pos; + pData->onPuglMotion(ev); + break; + } + + ///< Scrolled, a #PuglEventScroll + case PUGL_SCROLL: + { + if (pData->ignoreEvents) + break; + Widget::ScrollEvent ev; + ev.mod = event->scroll.state; + ev.flags = event->scroll.flags; + ev.time = static_cast(event->scroll.time * 1000.0 + 0.5); + ev.pos = Point(event->scroll.x, event->scroll.y); + ev.delta = Point(event->scroll.dx, event->scroll.dy); + ev.direction = static_cast(event->scroll.direction); + ev.absolutePos = ev.pos; + pData->onPuglScroll(ev); + break; + } + + ///< Custom client message, a #PuglEventClient + case PUGL_CLIENT: + break; + + ///< Timer triggered, a #PuglEventTimer + case PUGL_TIMER: + if (pData->ignoreEvents) + break; + if (IdleCallback* const idleCallback = reinterpret_cast(event->timer.id)) + idleCallback->idleCallback(); + break; + + ///< Recursive loop entered, a #PuglEventLoopEnter + case PUGL_LOOP_ENTER: + break; + + ///< Recursive loop left, a #PuglEventLoopLeave + case PUGL_LOOP_LEAVE: + break; + } + + return PUGL_SUCCESS; +} + +// ----------------------------------------------------------------------- + +#if defined(DEBUG) && defined(DGL_DEBUG_EVENTS) +static int printModifiers(const uint32_t mods) +{ + return fprintf(stderr, "Modifiers:%s%s%s%s\n", + (mods & PUGL_MOD_SHIFT) ? " Shift" : "", + (mods & PUGL_MOD_CTRL) ? " Ctrl" : "", + (mods & PUGL_MOD_ALT) ? " Alt" : "", + (mods & PUGL_MOD_SUPER) ? " Super" : ""); +} + +static int printEvent(const PuglEvent* event, const char* prefix, const bool verbose) +{ +#define FFMT "%6.1f" +#define PFMT FFMT " " FFMT +#define PRINT(fmt, ...) fprintf(stderr, fmt, __VA_ARGS__) + + switch (event->type) { + case PUGL_NOTHING: + return 0; + case PUGL_KEY_PRESS: + return PRINT("%sKey press code %3u key U+%04X\n", + prefix, + event->key.keycode, + event->key.key); + case PUGL_KEY_RELEASE: + return PRINT("%sKey release code %3u key U+%04X\n", + prefix, + event->key.keycode, + event->key.key); + case PUGL_TEXT: + return PRINT("%sText entry code %3u char U+%04X (%s)\n", + prefix, + event->text.keycode, + event->text.character, + event->text.string); + case PUGL_BUTTON_PRESS: + case PUGL_BUTTON_RELEASE: + return (PRINT("%sMouse %u %s at " PFMT " ", + prefix, + event->button.button, + (event->type == PUGL_BUTTON_PRESS) ? "down" : "up ", + event->button.x, + event->button.y) + + printModifiers(event->scroll.state)); + case PUGL_SCROLL: + return (PRINT("%sScroll %5.1f %5.1f at " PFMT " ", + prefix, + event->scroll.dx, + event->scroll.dy, + event->scroll.x, + event->scroll.y) + + printModifiers(event->scroll.state)); + case PUGL_POINTER_IN: + return PRINT("%sMouse enter at " PFMT "\n", + prefix, + event->crossing.x, + event->crossing.y); + case PUGL_POINTER_OUT: + return PRINT("%sMouse leave at " PFMT "\n", + prefix, + event->crossing.x, + event->crossing.y); + case PUGL_FOCUS_IN: + return PRINT("%sFocus in %i\n", + prefix, + event->focus.mode); + case PUGL_FOCUS_OUT: + return PRINT("%sFocus out %i\n", + prefix, + event->focus.mode); + case PUGL_CLIENT: + return PRINT("%sClient %" PRIXPTR " %" PRIXPTR "\n", + prefix, + event->client.data1, + event->client.data2); + case PUGL_TIMER: + return PRINT("%sTimer %" PRIuPTR "\n", prefix, event->timer.id); + default: + break; + } + + if (verbose) { + switch (event->type) { + case PUGL_CREATE: + return fprintf(stderr, "%sCreate\n", prefix); + case PUGL_DESTROY: + return fprintf(stderr, "%sDestroy\n", prefix); + case PUGL_MAP: + return fprintf(stderr, "%sMap\n", prefix); + case PUGL_UNMAP: + return fprintf(stderr, "%sUnmap\n", prefix); + case PUGL_UPDATE: + return 0; // fprintf(stderr, "%sUpdate\n", prefix); + case PUGL_CONFIGURE: + return PRINT("%sConfigure " PFMT " " PFMT "\n", + prefix, + event->configure.x, + event->configure.y, + event->configure.width, + event->configure.height); + case PUGL_EXPOSE: + return PRINT("%sExpose " PFMT " " PFMT "\n", + prefix, + event->expose.x, + event->expose.y, + event->expose.width, + event->expose.height); + case PUGL_CLOSE: + return PRINT("%sClose\n", prefix); + case PUGL_MOTION: + return PRINT("%sMouse motion at " PFMT "\n", + prefix, + event->motion.x, + event->motion.y); + default: + return PRINT("%sUnknown event type %d\n", prefix, (int)event->type); + } + } + +#undef PRINT +#undef PFMT +#undef FFMT + + return 0; +} +#endif + +#undef DGL_DBG +#undef DGL_DBGF + +// ----------------------------------------------------------------------- + +END_NAMESPACE_DGL diff --git a/source/modules/dgl/src/WindowPrivateData.hpp b/source/modules/dgl/src/WindowPrivateData.hpp new file mode 100644 index 000000000..36d2ce43d --- /dev/null +++ b/source/modules/dgl/src/WindowPrivateData.hpp @@ -0,0 +1,199 @@ +/* + * DISTRHO Plugin Framework (DPF) + * Copyright (C) 2012-2021 Filipe Coelho + * + * Permission to use, copy, modify, and/or distribute this software for any purpose with + * or without fee is hereby granted, provided that the above copyright notice and this + * permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD + * TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN + * NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER + * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef DGL_WINDOW_PRIVATE_DATA_HPP_INCLUDED +#define DGL_WINDOW_PRIVATE_DATA_HPP_INCLUDED + +#include "../Window.hpp" +#include "../Widget.hpp" +#include "ApplicationPrivateData.hpp" + +#include "pugl.hpp" + +#include + +START_NAMESPACE_DGL + +class TopLevelWidget; + +// ----------------------------------------------------------------------- + +struct Window::PrivateData : IdleCallback { + /** Reference to the DGL Application class this (private data) window associates with. */ + Application& app; + + /** Direct access to the DGL Application private data where we registers ourselves in. */ + Application::PrivateData* const appData; + + /** Pointer to the the DGL Window class that this private data belongs to. */ + Window* const self; + + /** Pugl view instance. */ + PuglView* view; + + /** Pugl view instance of the transient parent window. */ + PuglView* const transientParentView; + + /** Reserved space for graphics context. */ + mutable uint8_t graphicsContext[sizeof(void*)]; + + /** The top-level widgets associated with this Window. */ + std::list topLevelWidgets; + + /** Whether this Window is closed (not visible or counted in the Application it is tied to). + Defaults to true unless embed (embed windows are never closed). */ + bool isClosed; + + /** Whether this Window is currently visible/mapped. Defaults to false. */ + bool isVisible; + + /** Whether this Window is embed into another (usually not DGL-controlled) Window. */ + const bool isEmbed; + + /** Whether to ignore resize requests and feed them into the host instead. used for VST3 */ + const bool usesSizeRequest; + + /** Scale factor to report to widgets on request, purely informational. */ + double scaleFactor; + + /** Automatic scaling to apply on widgets, implemented internally. */ + bool autoScaling; + double autoScaleFactor; + + /** Pugl geometry constraints access. */ + uint minWidth, minHeight; + bool keepAspectRatio; + + /** Whether to ignore idle callback requests, useful for temporary windows. */ + bool ignoreIdleCallbacks; + + /** Whether to ignore pugl events (except create and destroy), used for puglGetClipboard. */ + bool ignoreEvents; + + /** Render to a picture file when non-null, automatically free+unset after saving. */ + char* filenameToRenderInto; + +#ifndef DGL_FILE_BROWSER_DISABLED + /** Handle for file browser dialog operations. */ + FileBrowserHandle fileBrowserHandle; +#endif + + /** Modal window setup. */ + struct Modal { + PrivateData* parent; // parent of this window (so we can become modal) + PrivateData* child; // child window to give focus to when modal mode is enabled + bool enabled; // wherever modal mode is enabled (only possible if parent != null) + + /** Constructor for a non-modal window. */ + Modal() noexcept + : parent(nullptr), + child(nullptr), + enabled(false) {} + + /** Constructor for a modal window (with a parent). */ + Modal(PrivateData* const p) noexcept + : parent(p), + child(nullptr), + enabled(false) {} + + /** Destructor. */ + ~Modal() noexcept + { + DISTRHO_SAFE_ASSERT(! enabled); + } + + DISTRHO_DECLARE_NON_COPYABLE(Modal) + DISTRHO_PREVENT_HEAP_ALLOCATION + } modal; + + /** Constructor for a regular, standalone window. */ + explicit PrivateData(Application& app, Window* self); + + /** Constructor for a modal window. */ + explicit PrivateData(Application& app, Window* self, PrivateData* ppData); + + /** Constructor for an embed Window, with a few extra hints from the host side. */ + explicit PrivateData(Application& app, Window* self, uintptr_t parentWindowHandle, double scaling, bool resizable); + + /** Constructor for an embed Window, with a few extra hints from the host side. */ + explicit PrivateData(Application& app, Window* self, uintptr_t parentWindowHandle, + uint width, uint height, double scaling, bool resizable, bool isVST3); + + /** Destructor. */ + ~PrivateData() override; + + /** Helper initialization function called at the end of all this class constructors. */ + void initPre(uint width, uint height, bool resizable); + /** Helper initialization function called on the Window constructor after we are done. */ + bool initPost(); + + /** Hide window and notify application of a window close event. + * Does nothing if window is embed (that is, not standalone). + * The application event-loop will stop when all windows have been closed. + * + * @note It is possible to hide the window while not stopping the event-loop. + * A closed window is always hidden, but the reverse is not always true. + */ + void close(); + + void show(); + void hide(); + + void focus(); + + void setResizable(bool resizable); + + const GraphicsContext& getGraphicsContext() const noexcept; + + // idle callback stuff + void idleCallback() override; + bool addIdleCallback(IdleCallback* callback, uint timerFrequencyInMs); + bool removeIdleCallback(IdleCallback* callback); + +#ifndef DGL_FILE_BROWSER_DISABLED + // file handling + bool openFileBrowser(const FileBrowserOptions& options); +#endif + + static void renderToPicture(const char* filename, const GraphicsContext& context, uint width, uint height); + + // modal handling + void startModal(); + void stopModal(); + void runAsModal(bool blockWait); + + // pugl events + void onPuglConfigure(double width, double height); + void onPuglExpose(); + void onPuglClose(); + void onPuglFocus(bool focus, CrossingMode mode); + void onPuglKey(const Widget::KeyboardEvent& ev); + void onPuglText(const Widget::CharacterInputEvent& ev); + void onPuglMouse(const Widget::MouseEvent& ev); + void onPuglMotion(const Widget::MotionEvent& ev); + void onPuglScroll(const Widget::ScrollEvent& ev); + + // Pugl event handling entry point + static PuglStatus puglEventCallback(PuglView* view, const PuglEvent* event); + + DISTRHO_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(PrivateData) +}; + +// ----------------------------------------------------------------------- + +END_NAMESPACE_DGL + +#endif // DGL_WINDOW_PRIVATE_DATA_HPP_INCLUDED diff --git a/source/modules/dgl/src/nanovg/fontstash.h b/source/modules/dgl/src/nanovg/fontstash.h index 5fa867553..37ecafc0f 100644 --- a/source/modules/dgl/src/nanovg/fontstash.h +++ b/source/modules/dgl/src/nanovg/fontstash.h @@ -38,10 +38,15 @@ enum FONSalign { FONS_ALIGN_BASELINE = 1<<6, // Default }; +enum FONSglyphBitmap { + FONS_GLYPH_BITMAP_OPTIONAL = 1, + FONS_GLYPH_BITMAP_REQUIRED = 2, +}; + enum FONSerrorCode { // Font atlas is full. FONS_ATLAS_FULL = 1, - // Scratch memory used to render glyphs is full, requested size reported in 'val', you may need to bump up FONS_SCRATCH_BUF_SIZE. + // Scratch memory used to render glyphs is full, requested size reported in 'val', you may need to bump up FONS_SCRATCH_BUF_SIZE. FONS_SCRATCH_FULL = 2, // Calls to fonsPushState has created too large stack, if you need deep state stack bump up FONS_MAX_STATES. FONS_STATES_OVERFLOW = 3, @@ -78,6 +83,7 @@ struct FONStextIter { const char* next; const char* end; unsigned int utf8state; + int bitmapOption; }; typedef struct FONStextIter FONStextIter; @@ -90,14 +96,14 @@ void fonsDeleteInternal(FONScontext* s); void fonsSetErrorCallback(FONScontext* s, void (*callback)(void* uptr, int error, int val), void* uptr); // Returns current atlas size. void fonsGetAtlasSize(FONScontext* s, int* width, int* height); -// Expands the atlas size. +// Expands the atlas size. int fonsExpandAtlas(FONScontext* s, int width, int height); // Resets the whole stash. int fonsResetAtlas(FONScontext* stash, int width, int height); // Add fonts -int fonsAddFont(FONScontext* s, const char* name, const char* path); -int fonsAddFontMem(FONScontext* s, const char* name, const unsigned char* data, int ndata, int freeData); +int fonsAddFont(FONScontext* s, const char* name, const char* path, int fontIndex); +int fonsAddFontMem(FONScontext* s, const char* name, unsigned char* data, int ndata, int freeData, int fontIndex); int fonsGetFontByName(FONScontext* s, const char* name); // State handling @@ -122,7 +128,7 @@ void fonsLineBounds(FONScontext* s, float y, float* miny, float* maxy); void fonsVertMetrics(FONScontext* s, float* ascender, float* descender, float* lineh); // Text iterator -int fonsTextIterInit(FONScontext* stash, FONStextIter* iter, float x, float y, const char* str, const char* end); +int fonsTextIterInit(FONScontext* stash, FONStextIter* iter, float x, float y, const char* str, const char* end, int bitmapOption); int fonsTextIterNext(FONScontext* stash, FONStextIter* iter, struct FONSquad* quad); // Pull texture changes @@ -151,91 +157,6 @@ struct FONSttFontImpl { }; typedef struct FONSttFontImpl FONSttFontImpl; -static FT_Library ftLibrary; - -int fons__tt_init(FONScontext *context) -{ - FT_Error ftError; - FONS_NOTUSED(context); - ftError = FT_Init_FreeType(&ftLibrary); - return ftError == 0; -} - -int fons__tt_loadFont(FONScontext *context, FONSttFontImpl *font, const unsigned char *data, int dataSize) -{ - FT_Error ftError; - FONS_NOTUSED(context); - - //font->font.userdata = stash; - ftError = FT_New_Memory_Face(ftLibrary, (const FT_Byte*)data, dataSize, 0, &font->font); - return ftError == 0; -} - -void fons__tt_getFontVMetrics(FONSttFontImpl *font, int *ascent, int *descent, int *lineGap) -{ - *ascent = font->font->ascender; - *descent = font->font->descender; - *lineGap = font->font->height - (*ascent - *descent); -} - -float fons__tt_getPixelHeightScale(FONSttFontImpl *font, float size) -{ - return size / (font->font->ascender - font->font->descender); -} - -int fons__tt_getGlyphIndex(FONSttFontImpl *font, int codepoint) -{ - return FT_Get_Char_Index(font->font, codepoint); -} - -int fons__tt_buildGlyphBitmap(FONSttFontImpl *font, int glyph, float size, float scale, - int *advance, int *lsb, int *x0, int *y0, int *x1, int *y1) -{ - FT_Error ftError; - FT_GlyphSlot ftGlyph; - FONS_NOTUSED(scale); - - ftError = FT_Set_Pixel_Sizes(font->font, 0, (FT_UInt)(size * (float)font->font->units_per_EM / (float)(font->font->ascender - font->font->descender))); - if (ftError) return 0; - ftError = FT_Load_Glyph(font->font, glyph, FT_LOAD_RENDER); - if (ftError) return 0; - ftError = FT_Get_Advance(font->font, glyph, FT_LOAD_NO_SCALE, (FT_Fixed*)advance); - if (ftError) return 0; - ftGlyph = font->font->glyph; - *lsb = ftGlyph->metrics.horiBearingX; - *x0 = ftGlyph->bitmap_left; - *x1 = *x0 + ftGlyph->bitmap.width; - *y0 = -ftGlyph->bitmap_top; - *y1 = *y0 + ftGlyph->bitmap.rows; - return 1; -} - -void fons__tt_renderGlyphBitmap(FONSttFontImpl *font, unsigned char *output, int outWidth, int outHeight, int outStride, - float scaleX, float scaleY, int glyph) -{ - FT_GlyphSlot ftGlyph = font->font->glyph; - int ftGlyphOffset = 0; - int x, y; - FONS_NOTUSED(outWidth); - FONS_NOTUSED(outHeight); - FONS_NOTUSED(scaleX); - FONS_NOTUSED(scaleY); - FONS_NOTUSED(glyph); // glyph has already been loaded by fons__tt_buildGlyphBitmap - - for ( y = 0; y < ftGlyph->bitmap.rows; y++ ) { - for ( x = 0; x < ftGlyph->bitmap.width; x++ ) { - output[(y * outStride) + x] = ftGlyph->bitmap.buffer[ftGlyphOffset++]; - } - } -} - -int fons__tt_getGlyphKernAdvance(FONSttFontImpl *font, int glyph1, int glyph2) -{ - FT_Vector ftKerning; - FT_Get_Kerning(font->font, glyph1, glyph2, FT_KERNING_DEFAULT, &ftKerning); - return ftKerning.x; -} - #else #define STB_TRUETYPE_IMPLEMENTATION @@ -250,61 +171,10 @@ struct FONSttFontImpl { }; typedef struct FONSttFontImpl FONSttFontImpl; -int fons__tt_init(FONScontext *context) -{ - FONS_NOTUSED(context); - return 1; -} - -int fons__tt_loadFont(FONScontext *context, FONSttFontImpl *font, const unsigned char *data, int dataSize) -{ - int stbError; - FONS_NOTUSED(dataSize); - - font->font.userdata = context; - stbError = stbtt_InitFont(&font->font, data, 0); - return stbError; -} - -void fons__tt_getFontVMetrics(FONSttFontImpl *font, int *ascent, int *descent, int *lineGap) -{ - stbtt_GetFontVMetrics(&font->font, ascent, descent, lineGap); -} - -float fons__tt_getPixelHeightScale(FONSttFontImpl *font, float size) -{ - return stbtt_ScaleForPixelHeight(&font->font, size); -} - -int fons__tt_getGlyphIndex(FONSttFontImpl *font, int codepoint) -{ - return stbtt_FindGlyphIndex(&font->font, codepoint); -} - -int fons__tt_buildGlyphBitmap(FONSttFontImpl *font, int glyph, float size, float scale, - int *advance, int *lsb, int *x0, int *y0, int *x1, int *y1) -{ - FONS_NOTUSED(size); - stbtt_GetGlyphHMetrics(&font->font, glyph, advance, lsb); - stbtt_GetGlyphBitmapBox(&font->font, glyph, scale, scale, x0, y0, x1, y1); - return 1; -} - -void fons__tt_renderGlyphBitmap(FONSttFontImpl *font, unsigned char *output, int outWidth, int outHeight, int outStride, - float scaleX, float scaleY, int glyph) -{ - stbtt_MakeGlyphBitmap(&font->font, output, outWidth, outHeight, outStride, scaleX, scaleY, glyph); -} - -int fons__tt_getGlyphKernAdvance(FONSttFontImpl *font, int glyph1, int glyph2) -{ - return stbtt_GetGlyphKernAdvance(&font->font, glyph1, glyph2); -} - #endif #ifndef FONS_SCRATCH_BUF_SIZE -# define FONS_SCRATCH_BUF_SIZE 16000 +# define FONS_SCRATCH_BUF_SIZE 96000 #endif #ifndef FONS_HASH_LUT_SIZE # define FONS_HASH_LUT_SIZE 256 @@ -324,6 +194,9 @@ int fons__tt_getGlyphKernAdvance(FONSttFontImpl *font, int glyph1, int glyph2) #ifndef FONS_MAX_STATES # define FONS_MAX_STATES 20 #endif +#ifndef FONS_MAX_FALLBACKS +# define FONS_MAX_FALLBACKS 20 +#endif static unsigned int fons__hashint(unsigned int a) { @@ -361,7 +234,7 @@ struct FONSfont { FONSttFontImpl font; char name[64]; - const unsigned char* data; + unsigned char* data; int dataSize; unsigned char freeData; float ascender; @@ -371,6 +244,8 @@ struct FONSfont int cglyphs; int nglyphs; int lut[FONS_HASH_LUT_SIZE]; + int fallbacks[FONS_MAX_FALLBACKS]; + int nfallbacks; }; typedef struct FONSfont FONSfont; @@ -419,8 +294,191 @@ struct FONScontext int nstates; void (*handleError)(void* uptr, int error, int val); void* errorUptr; +#ifdef FONS_USE_FREETYPE + FT_Library ftLibrary; +#endif }; +#ifdef FONS_USE_FREETYPE + +int fons__tt_init(FONScontext *context) +{ + FT_Error ftError; + ftError = FT_Init_FreeType(&context->ftLibrary); + return ftError == 0; +} + +int fons__tt_done(FONScontext *context) +{ + FT_Error ftError; + ftError = FT_Done_FreeType(context->ftLibrary); + return ftError == 0; +} + +int fons__tt_loadFont(FONScontext *context, FONSttFontImpl *font, unsigned char *data, int dataSize, int fontIndex) +{ + FT_Error ftError; + + //font->font.userdata = stash; + ftError = FT_New_Memory_Face(context->ftLibrary, (const FT_Byte*)data, dataSize, fontIndex, &font->font); + return ftError == 0; +} + +void fons__tt_getFontVMetrics(FONSttFontImpl *font, int *ascent, int *descent, int *lineGap) +{ + *ascent = font->font->ascender; + *descent = font->font->descender; + *lineGap = font->font->height - (*ascent - *descent); +} + +float fons__tt_getPixelHeightScale(FONSttFontImpl *font, float size) +{ +#if 1 + // Note(DPF) maintain pixel-based units for compat after nanovg update + return size / (font->font->ascender - font->font->descender); +#else + return size / font->font->units_per_EM; +#endif +} + +int fons__tt_getGlyphIndex(FONSttFontImpl *font, int codepoint) +{ + return FT_Get_Char_Index(font->font, codepoint); +} + +int fons__tt_buildGlyphBitmap(FONSttFontImpl *font, int glyph, float size, float scale, + int *advance, int *lsb, int *x0, int *y0, int *x1, int *y1) +{ + FT_Error ftError; + FT_GlyphSlot ftGlyph; + FT_Fixed advFixed; + FONS_NOTUSED(scale); + +#if 1 + // Note(DPF) maintain pixel-based units for compat after nanovg update + ftError = FT_Set_Pixel_Sizes(font->font, 0, (FT_UInt)(size * (float)font->font->units_per_EM / (float)(font->font->ascender - font->font->descender))); +#else + ftError = FT_Set_Pixel_Sizes(font->font, 0, size); +#endif + if (ftError) return 0; +#if 1 + // Note(DPF) maintain pixel-based units for compat after nanovg update + ftError = FT_Load_Glyph(font->font, glyph, FT_LOAD_RENDER); +#else + ftError = FT_Load_Glyph(font->font, glyph, FT_LOAD_RENDER | FT_LOAD_FORCE_AUTOHINT); +#endif + if (ftError) return 0; + ftError = FT_Get_Advance(font->font, glyph, FT_LOAD_NO_SCALE, &advFixed); + if (ftError) return 0; + ftGlyph = font->font->glyph; + *advance = (int)advFixed; + *lsb = (int)ftGlyph->metrics.horiBearingX; + *x0 = ftGlyph->bitmap_left; + *x1 = *x0 + ftGlyph->bitmap.width; + *y0 = -ftGlyph->bitmap_top; + *y1 = *y0 + ftGlyph->bitmap.rows; + return 1; +} + +void fons__tt_renderGlyphBitmap(FONSttFontImpl *font, unsigned char *output, int outWidth, int outHeight, int outStride, + float scaleX, float scaleY, int glyph) +{ + FT_GlyphSlot ftGlyph = font->font->glyph; + int ftGlyphOffset = 0; + unsigned int x, y; + FONS_NOTUSED(outWidth); + FONS_NOTUSED(outHeight); + FONS_NOTUSED(scaleX); + FONS_NOTUSED(scaleY); + FONS_NOTUSED(glyph); // glyph has already been loaded by fons__tt_buildGlyphBitmap + + for ( y = 0; y < ftGlyph->bitmap.rows; y++ ) { + for ( x = 0; x < ftGlyph->bitmap.width; x++ ) { + output[(y * outStride) + x] = ftGlyph->bitmap.buffer[ftGlyphOffset++]; + } + } +} + +int fons__tt_getGlyphKernAdvance(FONSttFontImpl *font, int glyph1, int glyph2) +{ + FT_Vector ftKerning; + FT_Get_Kerning(font->font, glyph1, glyph2, FT_KERNING_DEFAULT, &ftKerning); + return (int)((ftKerning.x + 32) >> 6); // Round up and convert to integer +} + +#else + +int fons__tt_init(FONScontext *context) +{ + FONS_NOTUSED(context); + return 1; +} + +int fons__tt_done(FONScontext *context) +{ + FONS_NOTUSED(context); + return 1; +} + +int fons__tt_loadFont(FONScontext *context, FONSttFontImpl *font, unsigned char *data, int dataSize, int fontIndex) +{ + int offset, stbError; + FONS_NOTUSED(dataSize); + + font->font.userdata = context; + offset = stbtt_GetFontOffsetForIndex(data, fontIndex); + if (offset == -1) { + stbError = 0; + } else { + stbError = stbtt_InitFont(&font->font, data, offset); + } + return stbError; +} + +void fons__tt_getFontVMetrics(FONSttFontImpl *font, int *ascent, int *descent, int *lineGap) +{ + stbtt_GetFontVMetrics(&font->font, ascent, descent, lineGap); +} + +float fons__tt_getPixelHeightScale(FONSttFontImpl *font, float size) +{ +#if 1 + // Note(DPF) maintain pixel-based units for compat after nanovg update + return stbtt_ScaleForPixelHeight(&font->font, size); +#else + return stbtt_ScaleForMappingEmToPixels(&font->font, size); +#endif +} + +int fons__tt_getGlyphIndex(FONSttFontImpl *font, int codepoint) +{ + return stbtt_FindGlyphIndex(&font->font, codepoint); +} + +int fons__tt_buildGlyphBitmap(FONSttFontImpl *font, int glyph, float size, float scale, + int *advance, int *lsb, int *x0, int *y0, int *x1, int *y1) +{ + FONS_NOTUSED(size); + stbtt_GetGlyphHMetrics(&font->font, glyph, advance, lsb); + stbtt_GetGlyphBitmapBox(&font->font, glyph, scale, scale, x0, y0, x1, y1); + return 1; +} + +void fons__tt_renderGlyphBitmap(FONSttFontImpl *font, unsigned char *output, int outWidth, int outHeight, int outStride, + float scaleX, float scaleY, int glyph) +{ + stbtt_MakeGlyphBitmap(&font->font, output, outWidth, outHeight, outStride, scaleX, scaleY, glyph); +} + +int fons__tt_getGlyphKernAdvance(FONSttFontImpl *font, int glyph1, int glyph2) +{ + return stbtt_GetGlyphKernAdvance(&font->font, glyph1, glyph2); +} + +#endif + +#ifdef STB_TRUETYPE_IMPLEMENTATION + static void* fons__tmpalloc(size_t size, void* up) { unsigned char* ptr; @@ -446,6 +504,8 @@ static void fons__tmpfree(void* ptr, void* up) // empty } +#endif // STB_TRUETYPE_IMPLEMENTATION + // Copyright (c) 2008-2010 Bjoern Hoehrmann // See http://bjoern.hoehrmann.de/utf-8/decoder/dfa/ for details. @@ -751,6 +811,27 @@ static FONSstate* fons__getState(FONScontext* stash) return &stash->states[stash->nstates-1]; } +int fonsAddFallbackFont(FONScontext* stash, int base, int fallback) +{ + FONSfont* baseFont = stash->fonts[base]; + if (baseFont->nfallbacks < FONS_MAX_FALLBACKS) { + baseFont->fallbacks[baseFont->nfallbacks++] = fallback; + return 1; + } + return 0; +} + +void fonsResetFallbackFont(FONScontext* stash, int base) +{ + int i; + + FONSfont* baseFont = stash->fonts[base]; + baseFont->nfallbacks = 0; + baseFont->nglyphs = 0; + for (i = 0; i < FONS_HASH_LUT_SIZE; i++) + baseFont->lut[i] = -1; +} + void fonsSetSize(FONScontext* stash, float size) { fons__getState(stash)->size = size; @@ -818,7 +899,7 @@ static void fons__freeFont(FONSfont* font) { if (font == NULL) return; if (font->glyphs) free(font->glyphs); - if (font->freeData && font->data) free((void*)font->data); + if (font->freeData && font->data) free(font->data); free(font); } @@ -830,6 +911,8 @@ static int fons__allocFont(FONScontext* stash) stash->fonts = (FONSfont**)realloc(stash->fonts, sizeof(FONSfont*) * stash->cfonts); if (stash->fonts == NULL) return -1; + for (int i=stash->nfonts; icfonts; ++i) + stash->fonts[i] = NULL; } font = (FONSfont*)malloc(sizeof(FONSfont)); if (font == NULL) goto error; @@ -849,12 +932,12 @@ error: return FONS_INVALID; } -int fonsAddFont(FONScontext* stash, const char* name, const char* path) +int fonsAddFont(FONScontext* stash, const char* name, const char* path, int fontIndex) { FILE* fp = 0; int dataSize = 0; + size_t readed; unsigned char* data = NULL; - size_t ignore; // Read in the font data. fp = fopen(path, "rb"); @@ -864,28 +947,30 @@ int fonsAddFont(FONScontext* stash, const char* name, const char* path) fseek(fp,0,SEEK_SET); data = (unsigned char*)malloc(dataSize); if (data == NULL) goto error; - ignore = fread(data, 1, dataSize, fp); + readed = fread(data, 1, dataSize, fp); fclose(fp); fp = 0; + if (readed != (size_t)dataSize) goto error; - return fonsAddFontMem(stash, name, data, dataSize, 1); + return fonsAddFontMem(stash, name, data, dataSize, 1, fontIndex); error: if (data) free(data); if (fp) fclose(fp); return FONS_INVALID; - - FONS_NOTUSED(ignore); } -int fonsAddFontMem(FONScontext* stash, const char* name, const unsigned char* data, int dataSize, int freeData) +int fonsAddFontMem(FONScontext* stash, const char* name, unsigned char* data, int dataSize, int freeData, int fontIndex) { int i, ascent, descent, fh, lineGap; FONSfont* font; int idx = fons__allocFont(stash); if (idx == FONS_INVALID) + { + if (freeData && data) free(data); return FONS_INVALID; + } font = stash->fonts[idx]; @@ -903,15 +988,16 @@ int fonsAddFontMem(FONScontext* stash, const char* name, const unsigned char* da // Init font stash->nscratch = 0; - if (!fons__tt_loadFont(stash, &font->font, data, dataSize)) goto error; + if (!fons__tt_loadFont(stash, &font->font, data, dataSize, fontIndex)) goto error; // Store normalized line height. The real line height is got // by multiplying the lineh by font size. fons__tt_getFontVMetrics( &font->font, &ascent, &descent, &lineGap); + ascent += lineGap; fh = ascent - descent; font->ascender = (float)ascent / (float)fh; font->descender = (float)descent / (float)fh; - font->lineh = (float)(fh + lineGap) / (float)fh; + font->lineh = font->ascender - font->descender; return idx; @@ -938,6 +1024,8 @@ static FONSglyph* fons__allocGlyph(FONSfont* font) font->cglyphs = font->cglyphs == 0 ? 8 : font->cglyphs * 2; font->glyphs = (FONSglyph*)realloc(font->glyphs, sizeof(FONSglyph) * font->cglyphs); if (font->glyphs == NULL) return NULL; + for (int i=font->nglyphs; icglyphs; ++i) + memset(&font->glyphs[i], 0, sizeof(*font->glyphs)); } font->nglyphs++; return &font->glyphs[font->nglyphs-1]; @@ -1010,7 +1098,7 @@ static void fons__blur(FONScontext* stash, unsigned char* dst, int w, int h, int } static FONSglyph* fons__getGlyph(FONScontext* stash, FONSfont* font, unsigned int codepoint, - short isize, short iblur) + short isize, short iblur, int bitmapOption) { int i, g, advance, lsb, x0, y0, x1, y1, gw, gh, gx, gy, x, y; float scale; @@ -1020,6 +1108,7 @@ static FONSglyph* fons__getGlyph(FONScontext* stash, FONSfont* font, unsigned in int pad, added; unsigned char* bdst; unsigned char* dst; + FONSfont* renderFont = font; if (isize < 2) return NULL; if (iblur > 20) iblur = 20; @@ -1032,32 +1121,66 @@ static FONSglyph* fons__getGlyph(FONScontext* stash, FONSfont* font, unsigned in h = fons__hashint(codepoint) & (FONS_HASH_LUT_SIZE-1); i = font->lut[h]; while (i != -1) { - if (font->glyphs[i].codepoint == codepoint && font->glyphs[i].size == isize && font->glyphs[i].blur == iblur) - return &font->glyphs[i]; + if (font->glyphs[i].codepoint == codepoint && font->glyphs[i].size == isize && font->glyphs[i].blur == iblur) { + glyph = &font->glyphs[i]; + if (bitmapOption == FONS_GLYPH_BITMAP_OPTIONAL || (glyph->x0 >= 0 && glyph->y0 >= 0)) { + return glyph; + } + // At this point, glyph exists but the bitmap data is not yet created. + break; + } i = font->glyphs[i].next; } - // Could not find glyph, create it. - scale = fons__tt_getPixelHeightScale(&font->font, size); + // Create a new glyph or rasterize bitmap data for a cached glyph. g = fons__tt_getGlyphIndex(&font->font, codepoint); - fons__tt_buildGlyphBitmap(&font->font, g, size, scale, &advance, &lsb, &x0, &y0, &x1, &y1); + // Try to find the glyph in fallback fonts. + if (g == 0) { + for (i = 0; i < font->nfallbacks; ++i) { + FONSfont* fallbackFont = stash->fonts[font->fallbacks[i]]; + int fallbackIndex = fons__tt_getGlyphIndex(&fallbackFont->font, codepoint); + if (fallbackIndex != 0) { + g = fallbackIndex; + renderFont = fallbackFont; + break; + } + } + // It is possible that we did not find a fallback glyph. + // In that case the glyph index 'g' is 0, and we'll proceed below and cache empty glyph. + } + scale = fons__tt_getPixelHeightScale(&renderFont->font, size); + fons__tt_buildGlyphBitmap(&renderFont->font, g, size, scale, &advance, &lsb, &x0, &y0, &x1, &y1); gw = x1-x0 + pad*2; gh = y1-y0 + pad*2; - // Find free spot for the rect in the atlas - added = fons__atlasAddRect(stash->atlas, gw, gh, &gx, &gy); - if (added == 0 && stash->handleError != NULL) { - // Atlas is full, let the user to resize the atlas (or not), and try again. - stash->handleError(stash->errorUptr, FONS_ATLAS_FULL, 0); + // Determines the spot to draw glyph in the atlas. + if (bitmapOption == FONS_GLYPH_BITMAP_REQUIRED) { + // Find free spot for the rect in the atlas added = fons__atlasAddRect(stash->atlas, gw, gh, &gx, &gy); + if (added == 0 && stash->handleError != NULL) { + // Atlas is full, let the user to resize the atlas (or not), and try again. + stash->handleError(stash->errorUptr, FONS_ATLAS_FULL, 0); + added = fons__atlasAddRect(stash->atlas, gw, gh, &gx, &gy); + } + if (added == 0) return NULL; + } else { + // Negative coordinate indicates there is no bitmap data created. + gx = -1; + gy = -1; } - if (added == 0) return NULL; // Init glyph. - glyph = fons__allocGlyph(font); - glyph->codepoint = codepoint; - glyph->size = isize; - glyph->blur = iblur; + if (glyph == NULL) { + glyph = fons__allocGlyph(font); + glyph->codepoint = codepoint; + glyph->size = isize; + glyph->blur = iblur; + glyph->next = 0; + + // Insert char to hash lookup. + glyph->next = font->lut[h]; + font->lut[h] = font->nglyphs-1; + } glyph->index = g; glyph->x0 = (short)gx; glyph->y0 = (short)gy; @@ -1066,15 +1189,14 @@ static FONSglyph* fons__getGlyph(FONScontext* stash, FONSfont* font, unsigned in glyph->xadv = (short)(scale * advance * 10.0f); glyph->xoff = (short)(x0 - pad); glyph->yoff = (short)(y0 - pad); - glyph->next = 0; - // Insert char to hash lookup. - glyph->next = font->lut[h]; - font->lut[h] = font->nglyphs-1; + if (bitmapOption == FONS_GLYPH_BITMAP_OPTIONAL) { + return glyph; + } // Rasterize dst = &stash->texData[(glyph->x0+pad) + (glyph->y0+pad) * stash->params.width]; - fons__tt_renderGlyphBitmap(&font->font, dst, gw-pad*2,gh-pad*2, stash->params.width, scale,scale, g); + fons__tt_renderGlyphBitmap(&renderFont->font, dst, gw-pad*2,gh-pad*2, stash->params.width, scale, scale, g); // Make sure there is one pixel empty border. dst = &stash->texData[glyph->x0 + glyph->y0 * stash->params.width]; @@ -1101,7 +1223,7 @@ static FONSglyph* fons__getGlyph(FONScontext* stash, FONSfont* font, unsigned in if (iblur > 0) { stash->nscratch = 0; bdst = &stash->texData[glyph->x0 + glyph->y0 * stash->params.width]; - fons__blur(stash, bdst, gw,gh, stash->params.width, iblur); + fons__blur(stash, bdst, gw, gh, stash->params.width, iblur); } stash->dirtyRect[0] = fons__mini(stash->dirtyRect[0], glyph->x0); @@ -1134,8 +1256,8 @@ static void fons__getQuad(FONScontext* stash, FONSfont* font, y1 = (float)(glyph->y1-1); if (stash->params.flags & FONS_ZERO_TOPLEFT) { - rx = (float)(int)(*x + xoff); - ry = (float)(int)(*y + yoff); + rx = floorf(*x + xoff); + ry = floorf(*y + yoff); q->x0 = rx; q->y0 = ry; @@ -1147,8 +1269,8 @@ static void fons__getQuad(FONScontext* stash, FONSfont* font, q->s1 = x1 * stash->itw; q->t1 = y1 * stash->ith; } else { - rx = (float)(int)(*x + xoff); - ry = (float)(int)(*y - yoff); + rx = floorf(*x + xoff); + ry = floorf(*y - yoff); q->x0 = rx; q->y0 = ry; @@ -1226,7 +1348,7 @@ float fonsDrawText(FONScontext* stash, const char* str, const char* end) { FONSstate* state = fons__getState(stash); - unsigned int codepoint = 0; + unsigned int codepoint; unsigned int utf8state = 0; FONSglyph* glyph = NULL; FONSquad q; @@ -1263,7 +1385,7 @@ float fonsDrawText(FONScontext* stash, for (; str != end; ++str) { if (fons__decutf8(&utf8state, &codepoint, *(const unsigned char*)str)) continue; - glyph = fons__getGlyph(stash, font, codepoint, isize, iblur); + glyph = fons__getGlyph(stash, font, codepoint, isize, iblur, FONS_GLYPH_BITMAP_REQUIRED); if (glyph != NULL) { fons__getQuad(stash, font, prevGlyphIndex, glyph, scale, state->spacing, &x, &y, &q); @@ -1286,7 +1408,7 @@ float fonsDrawText(FONScontext* stash, } int fonsTextIterInit(FONScontext* stash, FONStextIter* iter, - float x, float y, const char* str, const char* end) + float x, float y, const char* str, const char* end, int bitmapOption) { FONSstate* state = fons__getState(stash); float width; @@ -1326,6 +1448,7 @@ int fonsTextIterInit(FONScontext* stash, FONStextIter* iter, iter->end = end; iter->codepoint = 0; iter->prevGlyphIndex = -1; + iter->bitmapOption = bitmapOption; return 1; } @@ -1346,7 +1469,8 @@ int fonsTextIterNext(FONScontext* stash, FONStextIter* iter, FONSquad* quad) // Get glyph and quad iter->x = iter->nextx; iter->y = iter->nexty; - glyph = fons__getGlyph(stash, iter->font, iter->codepoint, iter->isize, iter->iblur); + glyph = fons__getGlyph(stash, iter->font, iter->codepoint, iter->isize, iter->iblur, iter->bitmapOption); + // If the iterator was initialized with FONS_GLYPH_BITMAP_OPTIONAL, then the UV coordinates of the quad will be invalid. if (glyph != NULL) fons__getQuad(stash, iter->font, iter->prevGlyphIndex, glyph, iter->scale, iter->spacing, &iter->nextx, &iter->nexty, quad); iter->prevGlyphIndex = glyph != NULL ? glyph->index : -1; @@ -1406,12 +1530,12 @@ void fonsDrawDebug(FONScontext* stash, float x, float y) } float fonsTextBounds(FONScontext* stash, - float x, float y, + float x, float y, const char* str, const char* end, float* bounds) { FONSstate* state = fons__getState(stash); - unsigned int codepoint = 0; + unsigned int codepoint; unsigned int utf8state = 0; FONSquad q; FONSglyph* glyph = NULL; @@ -1443,7 +1567,7 @@ float fonsTextBounds(FONScontext* stash, for (; str != end; ++str) { if (fons__decutf8(&utf8state, &codepoint, *(const unsigned char*)str)) continue; - glyph = fons__getGlyph(stash, font, codepoint, isize, iblur); + glyph = fons__getGlyph(stash, font, codepoint, isize, iblur, FONS_GLYPH_BITMAP_OPTIONAL); if (glyph != NULL) { fons__getQuad(stash, font, prevGlyphIndex, glyph, scale, state->spacing, &x, &y, &q); if (q.x0 < minx) minx = q.x0; @@ -1567,6 +1691,7 @@ void fonsDeleteInternal(FONScontext* stash) if (stash->fonts) free(stash->fonts); if (stash->texData) free(stash->texData); if (stash->scratch) free(stash->scratch); + fons__tt_done(stash); free(stash); } @@ -1594,7 +1719,7 @@ int fonsExpandAtlas(FONScontext* stash, int width, int height) height = fons__maxi(height, stash->params.height); if (width == stash->params.width && height == stash->params.height) - return 1; + return 1; // Flush pending glyphs. fons__flush(stash); diff --git a/source/modules/dgl/src/nanovg/nanovg.c b/source/modules/dgl/src/nanovg/nanovg.c index 315be3517..0c75b17fe 100644 --- a/source/modules/dgl/src/nanovg/nanovg.c +++ b/source/modules/dgl/src/nanovg/nanovg.c @@ -16,13 +16,29 @@ // 3. This notice may not be removed or altered from any source distribution. // +#include #include #include +#include + #include "nanovg.h" #define FONTSTASH_IMPLEMENTATION #include "fontstash.h" + +#ifndef NVG_NO_STB #define STB_IMAGE_IMPLEMENTATION #include "stb_image.h" +#endif + +#ifdef NVG_DISABLE_SKIPPING_WHITESPACE +#define NVG_SKIPPED_CHAR NVG_SPACE +#else +#define NVG_SKIPPED_CHAR NVG_CHAR +#endif + +#ifndef NVG_FONT_TEXTURE_FLAGS +#define NVG_FONT_TEXTURE_FLAGS 0 +#endif #ifdef _MSC_VER #pragma warning(disable: 4100) // unreferenced formal parameter @@ -63,13 +79,15 @@ enum NVGpointFlags }; struct NVGstate { + NVGcompositeOperationState compositeOperation; + int shapeAntiAlias; NVGpaint fill; NVGpaint stroke; float strokeWidth; float miterLimit; int lineJoin; int lineCap; - float alpha; + NVGcolor tint; float xform[6]; NVGscissor scissor; float fontSize; @@ -104,6 +122,14 @@ struct NVGpathCache { }; typedef struct NVGpathCache NVGpathCache; +struct NVGfontContext { // Fontstash context plus font images; shared between shared NanoVG contexts. + int refCount; + struct FONScontext* fs; + int fontImages[NVG_MAX_FONTIMAGES]; + int fontImageIdx; +}; +typedef struct NVGfontContext NVGfontContext; + struct NVGcontext { NVGparams params; float* commands; @@ -117,9 +143,7 @@ struct NVGcontext { float distTol; float fringeWidth; float devicePxRatio; - struct FONScontext* fs; - int fontImages[NVG_MAX_FONTIMAGES]; - int fontImageIdx; + NVGfontContext* fontContext; int drawCallCount; int fillTriCount; int strokeTriCount; @@ -200,7 +224,85 @@ static void nvg__setDevicePixelRatio(NVGcontext* ctx, float ratio) ctx->devicePxRatio = ratio; } -NVGcontext* nvgCreateInternal(NVGparams* params) +static NVGcompositeOperationState nvg__compositeOperationState(int op) +{ + int sfactor, dfactor; + + if (op == NVG_SOURCE_OVER) + { + sfactor = NVG_ONE; + dfactor = NVG_ONE_MINUS_SRC_ALPHA; + } + else if (op == NVG_SOURCE_IN) + { + sfactor = NVG_DST_ALPHA; + dfactor = NVG_ZERO; + } + else if (op == NVG_SOURCE_OUT) + { + sfactor = NVG_ONE_MINUS_DST_ALPHA; + dfactor = NVG_ZERO; + } + else if (op == NVG_ATOP) + { + sfactor = NVG_DST_ALPHA; + dfactor = NVG_ONE_MINUS_SRC_ALPHA; + } + else if (op == NVG_DESTINATION_OVER) + { + sfactor = NVG_ONE_MINUS_DST_ALPHA; + dfactor = NVG_ONE; + } + else if (op == NVG_DESTINATION_IN) + { + sfactor = NVG_ZERO; + dfactor = NVG_SRC_ALPHA; + } + else if (op == NVG_DESTINATION_OUT) + { + sfactor = NVG_ZERO; + dfactor = NVG_ONE_MINUS_SRC_ALPHA; + } + else if (op == NVG_DESTINATION_ATOP) + { + sfactor = NVG_ONE_MINUS_DST_ALPHA; + dfactor = NVG_SRC_ALPHA; + } + else if (op == NVG_LIGHTER) + { + sfactor = NVG_ONE; + dfactor = NVG_ONE; + } + else if (op == NVG_COPY) + { + sfactor = NVG_ONE; + dfactor = NVG_ZERO; + } + else if (op == NVG_XOR) + { + sfactor = NVG_ONE_MINUS_DST_ALPHA; + dfactor = NVG_ONE_MINUS_SRC_ALPHA; + } + else + { + sfactor = NVG_ONE; + dfactor = NVG_ZERO; + } + + NVGcompositeOperationState state; + state.srcRGB = sfactor; + state.dstRGB = dfactor; + state.srcAlpha = sfactor; + state.dstAlpha = dfactor; + return state; +} + +static NVGstate* nvg__getState(NVGcontext* ctx) +{ + return &ctx->states[ctx->nstates-1]; +} + +NVGcontext* nvgCreateInternal(NVGparams* params, NVGcontext* other) // Share the fonts and images of 'other' if it's non-NULL. { FONSparams fontParams; NVGcontext* ctx = (NVGcontext*)malloc(sizeof(NVGcontext)); @@ -209,8 +311,16 @@ NVGcontext* nvgCreateInternal(NVGparams* params) memset(ctx, 0, sizeof(NVGcontext)); ctx->params = *params; - for (i = 0; i < NVG_MAX_FONTIMAGES; i++) - ctx->fontImages[i] = 0; + if (other) { + ctx->fontContext = other->fontContext; + ctx->fontContext->refCount++; + } else { + ctx->fontContext = (NVGfontContext*)malloc(sizeof(NVGfontContext)); + if (ctx->fontContext == NULL) goto error; + for (i = 0; i < NVG_MAX_FONTIMAGES; i++) + ctx->fontContext->fontImages[i] = 0; + ctx->fontContext->refCount = 1; + } ctx->commands = (float*)malloc(sizeof(float)*NVG_INIT_COMMANDS_SIZE); if (!ctx->commands) goto error; @@ -225,25 +335,32 @@ NVGcontext* nvgCreateInternal(NVGparams* params) nvg__setDevicePixelRatio(ctx, 1.0f); - if (ctx->params.renderCreate(ctx->params.userPtr) == 0) goto error; + if (ctx->params.renderCreate(ctx->params.userPtr, other ? other->params.userPtr : NULL) == 0) goto error; // Init font rendering - memset(&fontParams, 0, sizeof(fontParams)); - fontParams.width = NVG_INIT_FONTIMAGE_SIZE; - fontParams.height = NVG_INIT_FONTIMAGE_SIZE; - fontParams.flags = FONS_ZERO_TOPLEFT; - fontParams.renderCreate = NULL; - fontParams.renderUpdate = NULL; - fontParams.renderDraw = NULL; - fontParams.renderDelete = NULL; - fontParams.userPtr = NULL; - ctx->fs = fonsCreateInternal(&fontParams); - if (ctx->fs == NULL) goto error; - - // Create font texture - ctx->fontImages[0] = ctx->params.renderCreateTexture(ctx->params.userPtr, NVG_TEXTURE_ALPHA, fontParams.width, fontParams.height, 0, NULL); - if (ctx->fontImages[0] == 0) goto error; - ctx->fontImageIdx = 0; + if (!other) { + memset(&fontParams, 0, sizeof(fontParams)); + fontParams.width = NVG_INIT_FONTIMAGE_SIZE; + fontParams.height = NVG_INIT_FONTIMAGE_SIZE; + fontParams.flags = FONS_ZERO_TOPLEFT; + fontParams.renderCreate = NULL; + fontParams.renderUpdate = NULL; + fontParams.renderDraw = NULL; + fontParams.renderDelete = NULL; + fontParams.userPtr = NULL; + ctx->fontContext->fs = fonsCreateInternal(&fontParams); + if (ctx->fontContext->fs == NULL) goto error; + + // Create font texture + ctx->fontContext->fontImages[0] = ctx->params.renderCreateTexture(ctx->params.userPtr, + NVG_TEXTURE_ALPHA, + fontParams.width, + fontParams.height, + NVG_FONT_TEXTURE_FLAGS, + NULL); + if (ctx->fontContext->fontImages[0] == 0) goto error; + ctx->fontContext->fontImageIdx = 0; + } return ctx; @@ -264,14 +381,18 @@ void nvgDeleteInternal(NVGcontext* ctx) if (ctx->commands != NULL) free(ctx->commands); if (ctx->cache != NULL) nvg__deletePathCache(ctx->cache); - if (ctx->fs) - fonsDeleteInternal(ctx->fs); + if (ctx->fontContext != NULL && --ctx->fontContext->refCount == 0) { + if (ctx->fontContext->fs) + fonsDeleteInternal(ctx->fontContext->fs); - for (i = 0; i < NVG_MAX_FONTIMAGES; i++) { - if (ctx->fontImages[i] != 0) { - nvgDeleteImage(ctx, ctx->fontImages[i]); - ctx->fontImages[i] = 0; + for (i = 0; i < NVG_MAX_FONTIMAGES; i++) { + if (ctx->fontContext->fontImages[i] != 0) { + nvgDeleteImage(ctx, ctx->fontContext->fontImages[i]); + ctx->fontContext->fontImages[i] = 0; + } } + + free(ctx->fontContext); } if (ctx->params.renderDelete != NULL) @@ -280,7 +401,7 @@ void nvgDeleteInternal(NVGcontext* ctx) free(ctx); } -void nvgBeginFrame(NVGcontext* ctx, int windowWidth, int windowHeight, float devicePixelRatio) +void nvgBeginFrame(NVGcontext* ctx, float windowWidth, float windowHeight, float devicePixelRatio) { /* printf("Tris: draws:%d fill:%d stroke:%d text:%d TOT:%d\n", ctx->drawCallCount, ctx->fillTriCount, ctx->strokeTriCount, ctx->textTriCount, @@ -291,8 +412,8 @@ void nvgBeginFrame(NVGcontext* ctx, int windowWidth, int windowHeight, float dev nvgReset(ctx); nvg__setDevicePixelRatio(ctx, devicePixelRatio); - - ctx->params.renderViewport(ctx->params.userPtr, windowWidth, windowHeight); + + ctx->params.renderViewport(ctx->params.userPtr, windowWidth, windowHeight, devicePixelRatio); ctx->drawCallCount = 0; ctx->fillTriCount = 0; @@ -308,30 +429,30 @@ void nvgCancelFrame(NVGcontext* ctx) void nvgEndFrame(NVGcontext* ctx) { ctx->params.renderFlush(ctx->params.userPtr); - if (ctx->fontImageIdx != 0) { - int fontImage = ctx->fontImages[ctx->fontImageIdx]; + if (ctx->fontContext->fontImageIdx != 0) { + int fontImage = ctx->fontContext->fontImages[ctx->fontContext->fontImageIdx]; int i, j, iw, ih; // delete images that smaller than current one if (fontImage == 0) return; nvgImageSize(ctx, fontImage, &iw, &ih); - for (i = j = 0; i < ctx->fontImageIdx; i++) { - if (ctx->fontImages[i] != 0) { + for (i = j = 0; i < ctx->fontContext->fontImageIdx; i++) { + if (ctx->fontContext->fontImages[i] != 0) { int nw, nh; - nvgImageSize(ctx, ctx->fontImages[i], &nw, &nh); + nvgImageSize(ctx, ctx->fontContext->fontImages[i], &nw, &nh); if (nw < iw || nh < ih) - nvgDeleteImage(ctx, ctx->fontImages[i]); + nvgDeleteImage(ctx, ctx->fontContext->fontImages[i]); else - ctx->fontImages[j++] = ctx->fontImages[i]; + ctx->fontContext->fontImages[j++] = ctx->fontContext->fontImages[i]; } } // make current font image to first - ctx->fontImages[j++] = ctx->fontImages[0]; - ctx->fontImages[0] = fontImage; - ctx->fontImageIdx = 0; + ctx->fontContext->fontImages[j++] = ctx->fontContext->fontImages[0]; + ctx->fontContext->fontImages[0] = fontImage; + ctx->fontContext->fontImageIdx = 0; // clear all images after j for (i = j; i < NVG_MAX_FONTIMAGES; i++) - ctx->fontImages[i] = 0; + ctx->fontContext->fontImages[i] = 0; } } @@ -383,7 +504,7 @@ NVGcolor nvgLerpRGBA(NVGcolor c0, NVGcolor c1, float u) { int i; float oneminu; - NVGcolor cint; + NVGcolor cint = {{{0}}}; u = nvg__clampf(u, 0.0f, 1.0f); oneminu = 1.0f - u; @@ -391,7 +512,7 @@ NVGcolor nvgLerpRGBA(NVGcolor c0, NVGcolor c1, float u) { cint.rgba[i] = c0.rgba[i] * oneminu + c1.rgba[i] * u; } - + return cint; } @@ -430,12 +551,6 @@ NVGcolor nvgHSLA(float h, float s, float l, unsigned char a) return col; } - -static NVGstate* nvg__getState(NVGcontext* ctx) -{ - return &ctx->states[ctx->nstates-1]; -} - void nvgTransformIdentity(float* t) { t[0] = 1.0f; t[1] = 0.0f; @@ -568,11 +683,13 @@ void nvgReset(NVGcontext* ctx) nvg__setPaintColor(&state->fill, nvgRGBA(255,255,255,255)); nvg__setPaintColor(&state->stroke, nvgRGBA(0,0,0,255)); + state->compositeOperation = nvg__compositeOperationState(NVG_SOURCE_OVER); + state->shapeAntiAlias = 1; state->strokeWidth = 1.0f; state->miterLimit = 10.0f; state->lineCap = NVG_BUTT; state->lineJoin = NVG_MITER; - state->alpha = 1.0f; + state->tint = nvgRGBAf(1, 1, 1, 1); nvgTransformIdentity(state->xform); state->scissor.extent[0] = -1.0f; @@ -587,6 +704,12 @@ void nvgReset(NVGcontext* ctx) } // State setting +void nvgShapeAntiAlias(NVGcontext* ctx, int enabled) +{ + NVGstate* state = nvg__getState(ctx); + state->shapeAntiAlias = enabled; +} + void nvgStrokeWidth(NVGcontext* ctx, float width) { NVGstate* state = nvg__getState(ctx); @@ -614,7 +737,33 @@ void nvgLineJoin(NVGcontext* ctx, int join) void nvgGlobalAlpha(NVGcontext* ctx, float alpha) { NVGstate* state = nvg__getState(ctx); - state->alpha = alpha; + state->tint.a = alpha; +} + +void nvgGlobalTint(NVGcontext* ctx, NVGcolor tint) +{ + NVGstate* state = nvg__getState(ctx); + state->tint = tint; +} + +NVGcolor nvgGetGlobalTint(NVGcontext* ctx) +{ + NVGstate* state = nvg__getState(ctx); + return state->tint; +} + +void nvgAlpha(NVGcontext* ctx, float alpha) +{ + NVGstate* state = nvg__getState(ctx); + state->tint.a *= alpha; +} + +void nvgTint(NVGcontext* ctx, NVGcolor tint) +{ + NVGstate* state = nvg__getState(ctx); + int i; + for (i = 0; i < 4; i++) + state->tint.rgba[i] *= tint.rgba[i]; } void nvgTransform(NVGcontext* ctx, float a, float b, float c, float d, float e, float f) @@ -703,6 +852,7 @@ void nvgFillPaint(NVGcontext* ctx, NVGpaint paint) nvgTransformMultiply(state->fill.xform, state->xform); } +#ifndef NVG_NO_STB int nvgCreateImage(NVGcontext* ctx, const char* filename, int imageFlags) { int w, h, n, image; @@ -719,7 +869,7 @@ int nvgCreateImage(NVGcontext* ctx, const char* filename, int imageFlags) return image; } -int nvgCreateImageMem(NVGcontext* ctx, int imageFlags, const unsigned char* data, int ndata) +int nvgCreateImageMem(NVGcontext* ctx, int imageFlags, unsigned char* data, int ndata) { int w, h, n, image; unsigned char* img = stbi_load_from_memory(data, ndata, &w, &h, &n, 4); @@ -731,10 +881,16 @@ int nvgCreateImageMem(NVGcontext* ctx, int imageFlags, const unsigned char* data stbi_image_free(img); return image; } +#endif + +int nvgCreateImageRaw(NVGcontext* ctx, int w, int h, int imageFlags, NVGtexture format, const unsigned char* data) +{ + return ctx->params.renderCreateTexture(ctx->params.userPtr, format, w, h, imageFlags, data); +} int nvgCreateImageRGBA(NVGcontext* ctx, int w, int h, int imageFlags, const unsigned char* data) { - return ctx->params.renderCreateTexture(ctx->params.userPtr, NVG_TEXTURE_RGBA, w, h, imageFlags, data); + return nvgCreateImageRaw(ctx, w, h, imageFlags, NVG_TEXTURE_RGBA, data); } void nvgUpdateImage(NVGcontext* ctx, int image, const unsigned char* data) @@ -913,7 +1069,7 @@ void nvgIntersectScissor(NVGcontext* ctx, float x, float y, float w, float h) } // Transform the current scissor rect into current transform space. - // If there is difference in rotation, this will be approximation. + // If there is difference in rotation, this will be approximation. memcpy(pxform, state->scissor.xform, sizeof(float)*6); ex = state->scissor.extent[0]; ey = state->scissor.extent[1]; @@ -936,6 +1092,30 @@ void nvgResetScissor(NVGcontext* ctx) state->scissor.extent[1] = -1.0f; } +// Global composite operation. +void nvgGlobalCompositeOperation(NVGcontext* ctx, int op) +{ + NVGstate* state = nvg__getState(ctx); + state->compositeOperation = nvg__compositeOperationState(op); +} + +void nvgGlobalCompositeBlendFunc(NVGcontext* ctx, int sfactor, int dfactor) +{ + nvgGlobalCompositeBlendFuncSeparate(ctx, sfactor, dfactor, sfactor, dfactor); +} + +void nvgGlobalCompositeBlendFuncSeparate(NVGcontext* ctx, int srcRGB, int dstRGB, int srcAlpha, int dstAlpha) +{ + NVGcompositeOperationState op; + op.srcRGB = srcRGB; + op.dstRGB = dstRGB; + op.srcAlpha = srcAlpha; + op.dstAlpha = dstAlpha; + + NVGstate* state = nvg__getState(ctx); + state->compositeOperation = op; +} + static int nvg__ptEquals(float x1, float y1, float x2, float y2, float tol) { float dx = x2 - x1; @@ -1173,7 +1353,7 @@ static void nvg__tesselateBezier(NVGcontext* ctx, { float x12,y12,x23,y23,x34,y34,x123,y123,x234,y234,x1234,y1234; float dx,dy,d2,d3; - + if (level > 10) return; x12 = (x1+x2)*0.5f; @@ -1205,8 +1385,8 @@ static void nvg__tesselateBezier(NVGcontext* ctx, x1234 = (x123+x234)*0.5f; y1234 = (y123+y234)*0.5f; - nvg__tesselateBezier(ctx, x1,y1, x12,y12, x123,y123, x1234,y1234, level+1, 0); - nvg__tesselateBezier(ctx, x1234,y1234, x234,y234, x34,y34, x4,y4, level+1, type); + nvg__tesselateBezier(ctx, x1,y1, x12,y12, x123,y123, x1234,y1234, level+1, 0); + nvg__tesselateBezier(ctx, x1234,y1234, x234,y234, x34,y34, x4,y4, level+1, type); } static void nvg__flattenPaths(NVGcontext* ctx) @@ -1331,7 +1511,8 @@ static void nvg__chooseBevel(int bevel, NVGpoint* p0, NVGpoint* p1, float w, } static NVGvertex* nvg__roundJoin(NVGvertex* dst, NVGpoint* p0, NVGpoint* p1, - float lw, float rw, float lu, float ru, int ncap, float fringe) + float lw, float rw, float lu, float ru, int ncap, + float fringe) { int i, n; float dlx0 = p0->dy; @@ -1464,36 +1645,39 @@ static NVGvertex* nvg__bevelJoin(NVGvertex* dst, NVGpoint* p0, NVGpoint* p1, } static NVGvertex* nvg__buttCapStart(NVGvertex* dst, NVGpoint* p, - float dx, float dy, float w, float d, float aa) + float dx, float dy, float w, float d, + float aa, float u0, float u1) { float px = p->x - dx*d; float py = p->y - dy*d; float dlx = dy; float dly = -dx; - nvg__vset(dst, px + dlx*w - dx*aa, py + dly*w - dy*aa, 0,0); dst++; - nvg__vset(dst, px - dlx*w - dx*aa, py - dly*w - dy*aa, 1,0); dst++; - nvg__vset(dst, px + dlx*w, py + dly*w, 0,1); dst++; - nvg__vset(dst, px - dlx*w, py - dly*w, 1,1); dst++; + nvg__vset(dst, px + dlx*w - dx*aa, py + dly*w - dy*aa, u0,0); dst++; + nvg__vset(dst, px - dlx*w - dx*aa, py - dly*w - dy*aa, u1,0); dst++; + nvg__vset(dst, px + dlx*w, py + dly*w, u0,1); dst++; + nvg__vset(dst, px - dlx*w, py - dly*w, u1,1); dst++; return dst; } static NVGvertex* nvg__buttCapEnd(NVGvertex* dst, NVGpoint* p, - float dx, float dy, float w, float d, float aa) + float dx, float dy, float w, float d, + float aa, float u0, float u1) { float px = p->x + dx*d; float py = p->y + dy*d; float dlx = dy; float dly = -dx; - nvg__vset(dst, px + dlx*w, py + dly*w, 0,1); dst++; - nvg__vset(dst, px - dlx*w, py - dly*w, 1,1); dst++; - nvg__vset(dst, px + dlx*w + dx*aa, py + dly*w + dy*aa, 0,0); dst++; - nvg__vset(dst, px - dlx*w + dx*aa, py - dly*w + dy*aa, 1,0); dst++; + nvg__vset(dst, px + dlx*w, py + dly*w, u0,1); dst++; + nvg__vset(dst, px - dlx*w, py - dly*w, u1,1); dst++; + nvg__vset(dst, px + dlx*w + dx*aa, py + dly*w + dy*aa, u0,0); dst++; + nvg__vset(dst, px - dlx*w + dx*aa, py - dly*w + dy*aa, u1,0); dst++; return dst; } static NVGvertex* nvg__roundCapStart(NVGvertex* dst, NVGpoint* p, - float dx, float dy, float w, int ncap, float aa) + float dx, float dy, float w, int ncap, + float aa, float u0, float u1) { int i; float px = p->x; @@ -1504,16 +1688,17 @@ static NVGvertex* nvg__roundCapStart(NVGvertex* dst, NVGpoint* p, for (i = 0; i < ncap; i++) { float a = i/(float)(ncap-1)*NVG_PI; float ax = cosf(a) * w, ay = sinf(a) * w; - nvg__vset(dst, px - dlx*ax - dx*ay, py - dly*ax - dy*ay, 0,1); dst++; + nvg__vset(dst, px - dlx*ax - dx*ay, py - dly*ax - dy*ay, u0,1); dst++; nvg__vset(dst, px, py, 0.5f,1); dst++; } - nvg__vset(dst, px + dlx*w, py + dly*w, 0,1); dst++; - nvg__vset(dst, px - dlx*w, py - dly*w, 1,1); dst++; + nvg__vset(dst, px + dlx*w, py + dly*w, u0,1); dst++; + nvg__vset(dst, px - dlx*w, py - dly*w, u1,1); dst++; return dst; } static NVGvertex* nvg__roundCapEnd(NVGvertex* dst, NVGpoint* p, - float dx, float dy, float w, int ncap, float aa) + float dx, float dy, float w, int ncap, + float aa, float u0, float u1) { int i; float px = p->x; @@ -1521,13 +1706,13 @@ static NVGvertex* nvg__roundCapEnd(NVGvertex* dst, NVGpoint* p, float dlx = dy; float dly = -dx; NVG_NOTUSED(aa); - nvg__vset(dst, px + dlx*w, py + dly*w, 0,1); dst++; - nvg__vset(dst, px - dlx*w, py - dly*w, 1,1); dst++; + nvg__vset(dst, px + dlx*w, py + dly*w, u0,1); dst++; + nvg__vset(dst, px - dlx*w, py - dly*w, u1,1); dst++; for (i = 0; i < ncap; i++) { float a = i/(float)(ncap-1)*NVG_PI; float ax = cosf(a) * w, ay = sinf(a) * w; nvg__vset(dst, px, py, 0.5f,1); dst++; - nvg__vset(dst, px - dlx*ax + dx*ay, py - dly*ax + dy*ay, 0,1); dst++; + nvg__vset(dst, px - dlx*ax + dx*ay, py - dly*ax + dy*ay, u0,1); dst++; } return dst; } @@ -1603,15 +1788,24 @@ static void nvg__calculateJoins(NVGcontext* ctx, float w, int lineJoin, float mi } -static int nvg__expandStroke(NVGcontext* ctx, float w, int lineCap, int lineJoin, float miterLimit) -{ +static int nvg__expandStroke(NVGcontext* ctx, float w, float fringe, int lineCap, int lineJoin, float miterLimit) +{ NVGpathCache* cache = ctx->cache; NVGvertex* verts; NVGvertex* dst; int cverts, i, j; - float aa = ctx->fringeWidth; + float aa = fringe;//ctx->fringeWidth; + float u0 = 0.0f, u1 = 1.0f; int ncap = nvg__curveDivs(w, NVG_PI, ctx->tessTol); // Calculate divisions per half circle. + w += aa * 0.5f; + + // Disable the gradient used for antialiasing when antialiasing is not used. + if (aa == 0.0f) { + u0 = 0.5f; + u1 = 0.5f; + } + nvg__calculateJoins(ctx, w, lineJoin, miterLimit); // Calculate max vertex usage. @@ -1672,42 +1866,42 @@ static int nvg__expandStroke(NVGcontext* ctx, float w, int lineCap, int lineJoin dy = p1->y - p0->y; nvg__normalize(&dx, &dy); if (lineCap == NVG_BUTT) - dst = nvg__buttCapStart(dst, p0, dx, dy, w, -aa*0.5f, aa); + dst = nvg__buttCapStart(dst, p0, dx, dy, w, -aa*0.5f, aa, u0, u1); else if (lineCap == NVG_BUTT || lineCap == NVG_SQUARE) - dst = nvg__buttCapStart(dst, p0, dx, dy, w, w-aa, aa); + dst = nvg__buttCapStart(dst, p0, dx, dy, w, w-aa, aa, u0, u1); else if (lineCap == NVG_ROUND) - dst = nvg__roundCapStart(dst, p0, dx, dy, w, ncap, aa); + dst = nvg__roundCapStart(dst, p0, dx, dy, w, ncap, aa, u0, u1); } for (j = s; j < e; ++j) { if ((p1->flags & (NVG_PT_BEVEL | NVG_PR_INNERBEVEL)) != 0) { if (lineJoin == NVG_ROUND) { - dst = nvg__roundJoin(dst, p0, p1, w, w, 0, 1, ncap, aa); + dst = nvg__roundJoin(dst, p0, p1, w, w, u0, u1, ncap, aa); } else { - dst = nvg__bevelJoin(dst, p0, p1, w, w, 0, 1, aa); + dst = nvg__bevelJoin(dst, p0, p1, w, w, u0, u1, aa); } } else { - nvg__vset(dst, p1->x + (p1->dmx * w), p1->y + (p1->dmy * w), 0,1); dst++; - nvg__vset(dst, p1->x - (p1->dmx * w), p1->y - (p1->dmy * w), 1,1); dst++; + nvg__vset(dst, p1->x + (p1->dmx * w), p1->y + (p1->dmy * w), u0,1); dst++; + nvg__vset(dst, p1->x - (p1->dmx * w), p1->y - (p1->dmy * w), u1,1); dst++; } p0 = p1++; } if (loop) { // Loop it - nvg__vset(dst, verts[0].x, verts[0].y, 0,1); dst++; - nvg__vset(dst, verts[1].x, verts[1].y, 1,1); dst++; + nvg__vset(dst, verts[0].x, verts[0].y, u0,1); dst++; + nvg__vset(dst, verts[1].x, verts[1].y, u1,1); dst++; } else { // Add cap dx = p1->x - p0->x; dy = p1->y - p0->y; nvg__normalize(&dx, &dy); if (lineCap == NVG_BUTT) - dst = nvg__buttCapEnd(dst, p1, dx, dy, w, -aa*0.5f, aa); + dst = nvg__buttCapEnd(dst, p1, dx, dy, w, -aa*0.5f, aa, u0, u1); else if (lineCap == NVG_BUTT || lineCap == NVG_SQUARE) - dst = nvg__buttCapEnd(dst, p1, dx, dy, w, w-aa, aa); + dst = nvg__buttCapEnd(dst, p1, dx, dy, w, w-aa, aa, u0, u1); else if (lineCap == NVG_ROUND) - dst = nvg__roundCapEnd(dst, p1, dx, dy, w, ncap, aa); + dst = nvg__roundCapEnd(dst, p1, dx, dy, w, ncap, aa, u0, u1); } path->nstroke = (int)(dst - verts); @@ -1868,7 +2062,7 @@ void nvgQuadTo(NVGcontext* ctx, float cx, float cy, float x, float y) { float x0 = ctx->commandx; float y0 = ctx->commandy; - float vals[] = { NVG_BEZIERTO, + float vals[] = { NVG_BEZIERTO, x0 + 2.0f/3.0f*(cx - x0), y0 + 2.0f/3.0f*(cy - y0), x + 2.0f/3.0f*(cx - x), y + 2.0f/3.0f*(cy - y), x, y }; @@ -1950,7 +2144,7 @@ void nvgArc(NVGcontext* ctx, float cx, float cy, float r, float a0, float a1, in float px = 0, py = 0, ptanx = 0, ptany = 0; float vals[3 + 5*7 + 100]; int i, ndivs, nvals; - int move = ctx->ncommands > 0 ? NVG_LINETO : NVG_MOVETO; + int move = ctx->ncommands > 0 ? NVG_LINETO : NVG_MOVETO; // Clamp angles da = a1 - a0; @@ -2022,22 +2216,31 @@ void nvgRect(NVGcontext* ctx, float x, float y, float w, float h) void nvgRoundedRect(NVGcontext* ctx, float x, float y, float w, float h, float r) { - if (r < 0.1f) { - nvgRect(ctx, x,y,w,h); + nvgRoundedRectVarying(ctx, x, y, w, h, r, r, r, r); +} + +void nvgRoundedRectVarying(NVGcontext* ctx, float x, float y, float w, float h, float radTopLeft, float radTopRight, float radBottomRight, float radBottomLeft) +{ + if(radTopLeft < 0.1f && radTopRight < 0.1f && radBottomRight < 0.1f && radBottomLeft < 0.1f) { + nvgRect(ctx, x, y, w, h); return; - } - else { - float rx = nvg__minf(r, nvg__absf(w)*0.5f) * nvg__signf(w), ry = nvg__minf(r, nvg__absf(h)*0.5f) * nvg__signf(h); + } else { + float halfw = nvg__absf(w)*0.5f; + float halfh = nvg__absf(h)*0.5f; + float rxBL = nvg__minf(radBottomLeft, halfw) * nvg__signf(w), ryBL = nvg__minf(radBottomLeft, halfh) * nvg__signf(h); + float rxBR = nvg__minf(radBottomRight, halfw) * nvg__signf(w), ryBR = nvg__minf(radBottomRight, halfh) * nvg__signf(h); + float rxTR = nvg__minf(radTopRight, halfw) * nvg__signf(w), ryTR = nvg__minf(radTopRight, halfh) * nvg__signf(h); + float rxTL = nvg__minf(radTopLeft, halfw) * nvg__signf(w), ryTL = nvg__minf(radTopLeft, halfh) * nvg__signf(h); float vals[] = { - NVG_MOVETO, x, y+ry, - NVG_LINETO, x, y+h-ry, - NVG_BEZIERTO, x, y+h-ry*(1-NVG_KAPPA90), x+rx*(1-NVG_KAPPA90), y+h, x+rx, y+h, - NVG_LINETO, x+w-rx, y+h, - NVG_BEZIERTO, x+w-rx*(1-NVG_KAPPA90), y+h, x+w, y+h-ry*(1-NVG_KAPPA90), x+w, y+h-ry, - NVG_LINETO, x+w, y+ry, - NVG_BEZIERTO, x+w, y+ry*(1-NVG_KAPPA90), x+w-rx*(1-NVG_KAPPA90), y, x+w-rx, y, - NVG_LINETO, x+rx, y, - NVG_BEZIERTO, x+rx*(1-NVG_KAPPA90), y, x, y+ry*(1-NVG_KAPPA90), x, y+ry, + NVG_MOVETO, x, y + ryTL, + NVG_LINETO, x, y + h - ryBL, + NVG_BEZIERTO, x, y + h - ryBL*(1 - NVG_KAPPA90), x + rxBL*(1 - NVG_KAPPA90), y + h, x + rxBL, y + h, + NVG_LINETO, x + w - rxBR, y + h, + NVG_BEZIERTO, x + w - rxBR*(1 - NVG_KAPPA90), y + h, x + w, y + h - ryBR*(1 - NVG_KAPPA90), x + w, y + h - ryBR, + NVG_LINETO, x + w, y + ryTR, + NVG_BEZIERTO, x + w, y + ryTR*(1 - NVG_KAPPA90), x + w - rxTR*(1 - NVG_KAPPA90), y, x + w - rxTR, y, + NVG_LINETO, x + rxTL, y, + NVG_BEZIERTO, x + rxTL*(1 - NVG_KAPPA90), y, x, y + ryTL*(1 - NVG_KAPPA90), x, y + ryTL, NVG_CLOSE }; nvg__appendCommands(ctx, vals, NVG_COUNTOF(vals)); @@ -2092,16 +2295,18 @@ void nvgFill(NVGcontext* ctx) int i; nvg__flattenPaths(ctx); - if (ctx->params.edgeAntiAlias) + if (ctx->params.edgeAntiAlias && state->shapeAntiAlias) nvg__expandFill(ctx, ctx->fringeWidth, NVG_MITER, 2.4f); else nvg__expandFill(ctx, 0.0f, NVG_MITER, 2.4f); - // Apply global alpha - fillPaint.innerColor.a *= state->alpha; - fillPaint.outerColor.a *= state->alpha; + // Apply global tint + for (i = 0; i < 4; i++) { + fillPaint.innerColor.rgba[i] *= state->tint.rgba[i]; + fillPaint.outerColor.rgba[i] *= state->tint.rgba[i]; + } - ctx->params.renderFill(ctx->params.userPtr, &fillPaint, &state->scissor, ctx->fringeWidth, + ctx->params.renderFill(ctx->params.userPtr, &fillPaint, state->compositeOperation, &state->scissor, ctx->fringeWidth, ctx->cache->bounds, ctx->cache->paths, ctx->cache->npaths); // Count triangles @@ -2122,6 +2327,7 @@ void nvgStroke(NVGcontext* ctx) const NVGpath* path; int i; + if (strokeWidth < ctx->fringeWidth) { // If the stroke width is less than pixel size, use alpha to emulate coverage. // Since coverage is area, scale by alpha*alpha. @@ -2131,18 +2337,20 @@ void nvgStroke(NVGcontext* ctx) strokeWidth = ctx->fringeWidth; } - // Apply global alpha - strokePaint.innerColor.a *= state->alpha; - strokePaint.outerColor.a *= state->alpha; + // Apply global tint + for (i = 0; i < 4; i++) { + strokePaint.innerColor.rgba[i] *= state->tint.rgba[i]; + strokePaint.outerColor.rgba[i] *= state->tint.rgba[i]; + } nvg__flattenPaths(ctx); - if (ctx->params.edgeAntiAlias) - nvg__expandStroke(ctx, strokeWidth*0.5f + ctx->fringeWidth*0.5f, state->lineCap, state->lineJoin, state->miterLimit); + if (ctx->params.edgeAntiAlias && state->shapeAntiAlias) + nvg__expandStroke(ctx, strokeWidth*0.5f, ctx->fringeWidth, state->lineCap, state->lineJoin, state->miterLimit); else - nvg__expandStroke(ctx, strokeWidth*0.5f, state->lineCap, state->lineJoin, state->miterLimit); + nvg__expandStroke(ctx, strokeWidth*0.5f, 0.0f, state->lineCap, state->lineJoin, state->miterLimit); - ctx->params.renderStroke(ctx->params.userPtr, &strokePaint, &state->scissor, ctx->fringeWidth, + ctx->params.renderStroke(ctx->params.userPtr, &strokePaint, state->compositeOperation, &state->scissor, ctx->fringeWidth, strokeWidth, ctx->cache->paths, ctx->cache->npaths); // Count triangles @@ -2154,20 +2362,52 @@ void nvgStroke(NVGcontext* ctx) } // Add fonts -int nvgCreateFont(NVGcontext* ctx, const char* name, const char* path) +int nvgCreateFont(NVGcontext* ctx, const char* name, const char* filename) +{ + return fonsAddFont(ctx->fontContext->fs, name, filename, 0); +} + +int nvgCreateFontAtIndex(NVGcontext* ctx, const char* name, const char* filename, const int fontIndex) +{ + return fonsAddFont(ctx->fontContext->fs, name, filename, fontIndex); +} + +int nvgCreateFontMem(NVGcontext* ctx, const char* name, unsigned char* data, int ndata, int freeData) { - return fonsAddFont(ctx->fs, name, path); + return fonsAddFontMem(ctx->fontContext->fs, name, data, ndata, freeData, 0); } -int nvgCreateFontMem(NVGcontext* ctx, const char* name, const unsigned char* data, int ndata, int freeData) +int nvgCreateFontMemAtIndex(NVGcontext* ctx, const char* name, unsigned char* data, int ndata, int freeData, const int fontIndex) { - return fonsAddFontMem(ctx->fs, name, data, ndata, freeData); + return fonsAddFontMem(ctx->fontContext->fs, name, data, ndata, freeData, fontIndex); } int nvgFindFont(NVGcontext* ctx, const char* name) { if (name == NULL) return -1; - return fonsGetFontByName(ctx->fs, name); + return fonsGetFontByName(ctx->fontContext->fs, name); +} + + +int nvgAddFallbackFontId(NVGcontext* ctx, int baseFont, int fallbackFont) +{ + if(baseFont == -1 || fallbackFont == -1) return 0; + return fonsAddFallbackFont(ctx->fontContext->fs, baseFont, fallbackFont); +} + +int nvgAddFallbackFont(NVGcontext* ctx, const char* baseFont, const char* fallbackFont) +{ + return nvgAddFallbackFontId(ctx, nvgFindFont(ctx, baseFont), nvgFindFont(ctx, fallbackFont)); +} + +void nvgResetFallbackFontsId(NVGcontext* ctx, int baseFont) +{ + fonsResetFallbackFont(ctx->fontContext->fs, baseFont); +} + +void nvgResetFallbackFonts(NVGcontext* ctx, const char* baseFont) +{ + nvgResetFallbackFontsId(ctx, nvgFindFont(ctx, baseFont)); } // State setting @@ -2210,7 +2450,7 @@ void nvgFontFaceId(NVGcontext* ctx, int font) void nvgFontFace(NVGcontext* ctx, const char* font) { NVGstate* state = nvg__getState(ctx); - state->fontId = fonsGetFontByName(ctx->fs, font); + state->fontId = fonsGetFontByName(ctx->fontContext->fs, font); } static float nvg__quantize(float a, float d) @@ -2227,12 +2467,12 @@ static void nvg__flushTextTexture(NVGcontext* ctx) { int dirty[4]; - if (fonsValidateTexture(ctx->fs, dirty)) { - int fontImage = ctx->fontImages[ctx->fontImageIdx]; + if (fonsValidateTexture(ctx->fontContext->fs, dirty)) { + int fontImage = ctx->fontContext->fontImages[ctx->fontContext->fontImageIdx]; // Update texture if (fontImage != 0) { int iw, ih; - const unsigned char* data = fonsGetTextureData(ctx->fs, &iw, &ih); + const unsigned char* data = fonsGetTextureData(ctx->fontContext->fs, &iw, &ih); int x = dirty[0]; int y = dirty[1]; int w = dirty[2] - dirty[0]; @@ -2246,44 +2486,55 @@ static int nvg__allocTextAtlas(NVGcontext* ctx) { int iw, ih; nvg__flushTextTexture(ctx); - if (ctx->fontImageIdx >= NVG_MAX_FONTIMAGES-1) + if (ctx->fontContext->fontImageIdx >= NVG_MAX_FONTIMAGES-1) return 0; // if next fontImage already have a texture - if (ctx->fontImages[ctx->fontImageIdx+1] != 0) - nvgImageSize(ctx, ctx->fontImages[ctx->fontImageIdx+1], &iw, &ih); + if (ctx->fontContext->fontImages[ctx->fontContext->fontImageIdx+1] != 0) + nvgImageSize(ctx, ctx->fontContext->fontImages[ctx->fontContext->fontImageIdx+1], &iw, &ih); else { // calculate the new font image size and create it. - nvgImageSize(ctx, ctx->fontImages[ctx->fontImageIdx], &iw, &ih); + nvgImageSize(ctx, ctx->fontContext->fontImages[ctx->fontContext->fontImageIdx], &iw, &ih); if (iw > ih) ih *= 2; else iw *= 2; if (iw > NVG_MAX_FONTIMAGE_SIZE || ih > NVG_MAX_FONTIMAGE_SIZE) iw = ih = NVG_MAX_FONTIMAGE_SIZE; - ctx->fontImages[ctx->fontImageIdx+1] = ctx->params.renderCreateTexture(ctx->params.userPtr, NVG_TEXTURE_ALPHA, iw, ih, 0, NULL); + ctx->fontContext->fontImages[ctx->fontContext->fontImageIdx+1] + = ctx->params.renderCreateTexture(ctx->params.userPtr, + NVG_TEXTURE_ALPHA, iw, ih, NVG_FONT_TEXTURE_FLAGS, NULL); } - ++ctx->fontImageIdx; - fonsResetAtlas(ctx->fs, iw, ih); + ++ctx->fontContext->fontImageIdx; + fonsResetAtlas(ctx->fontContext->fs, iw, ih); return 1; } static void nvg__renderText(NVGcontext* ctx, NVGvertex* verts, int nverts) { + int i; NVGstate* state = nvg__getState(ctx); NVGpaint paint = state->fill; // Render triangles. - paint.image = ctx->fontImages[ctx->fontImageIdx]; + paint.image = ctx->fontContext->fontImages[ctx->fontContext->fontImageIdx]; - // Apply global alpha - paint.innerColor.a *= state->alpha; - paint.outerColor.a *= state->alpha; + // Apply global tint + for (i = 0; i < 4; i++) { + paint.innerColor.rgba[i] *= state->tint.rgba[i]; + paint.outerColor.rgba[i] *= state->tint.rgba[i]; + } - ctx->params.renderTriangles(ctx->params.userPtr, &paint, &state->scissor, verts, nverts); + ctx->params.renderTriangles(ctx->params.userPtr, &paint, state->compositeOperation, &state->scissor, verts, nverts, ctx->fringeWidth); ctx->drawCallCount++; ctx->textTriCount += nverts/3; } +static int nvg__isTransformFlipped(const float *xform) +{ + float det = xform[0] * xform[3] - xform[2] * xform[1]; + return( det < 0); +} + float nvgText(NVGcontext* ctx, float x, float y, const char* string, const char* end) { NVGstate* state = nvg__getState(ctx); @@ -2294,39 +2545,46 @@ float nvgText(NVGcontext* ctx, float x, float y, const char* string, const char* float invscale = 1.0f / scale; int cverts = 0; int nverts = 0; + int isFlipped = nvg__isTransformFlipped(state->xform); if (end == NULL) end = string + strlen(string); if (state->fontId == FONS_INVALID) return x; - fonsSetSize(ctx->fs, state->fontSize*scale); - fonsSetSpacing(ctx->fs, state->letterSpacing*scale); - fonsSetBlur(ctx->fs, state->fontBlur*scale); - fonsSetAlign(ctx->fs, state->textAlign); - fonsSetFont(ctx->fs, state->fontId); + fonsSetSize(ctx->fontContext->fs, state->fontSize*scale); + fonsSetSpacing(ctx->fontContext->fs, state->letterSpacing*scale); + fonsSetBlur(ctx->fontContext->fs, state->fontBlur*scale); + fonsSetAlign(ctx->fontContext->fs, state->textAlign); + fonsSetFont(ctx->fontContext->fs, state->fontId); cverts = nvg__maxi(2, (int)(end - string)) * 6; // conservative estimate. verts = nvg__allocTempVerts(ctx, cverts); if (verts == NULL) return x; - fonsTextIterInit(ctx->fs, &iter, x*scale, y*scale, string, end); + fonsTextIterInit(ctx->fontContext->fs, &iter, x*scale, y*scale, string, end, FONS_GLYPH_BITMAP_REQUIRED); prevIter = iter; - while (fonsTextIterNext(ctx->fs, &iter, &q)) { + while (fonsTextIterNext(ctx->fontContext->fs, &iter, &q)) { float c[4*2]; if (iter.prevGlyphIndex == -1) { // can not retrieve glyph? - if (!nvg__allocTextAtlas(ctx)) - break; // no memory :( if (nverts != 0) { nvg__renderText(ctx, verts, nverts); nverts = 0; } + if (!nvg__allocTextAtlas(ctx)) + break; // no memory :( iter = prevIter; - fonsTextIterNext(ctx->fs, &iter, &q); // try again + fonsTextIterNext(ctx->fontContext->fs, &iter, &q); // try again if (iter.prevGlyphIndex == -1) // still can not find glyph? break; } prevIter = iter; + if(isFlipped) { + float tmp; + + tmp = q.y0; q.y0 = q.y1; q.y1 = tmp; + tmp = q.t0; q.t0 = q.t1; q.t1 = tmp; + } // Transform corners. nvgTransformPoint(&c[0],&c[1], state->xform, q.x0*invscale, q.y0*invscale); nvgTransformPoint(&c[2],&c[3], state->xform, q.x1*invscale, q.y0*invscale); @@ -2343,12 +2601,12 @@ float nvgText(NVGcontext* ctx, float x, float y, const char* string, const char* } } - // TODO: add back-end bit to do this just once per frame. + // TODO: add back-end bit to do this just once per frame. nvg__flushTextTexture(ctx); nvg__renderText(ctx, verts, nverts); - return iter.x; + return iter.nextx / scale; } void nvgTextBox(NVGcontext* ctx, float x, float y, float breakRowWidth, const char* string, const char* end) @@ -2401,18 +2659,18 @@ int nvgTextGlyphPositions(NVGcontext* ctx, float x, float y, const char* string, if (string == end) return 0; - fonsSetSize(ctx->fs, state->fontSize*scale); - fonsSetSpacing(ctx->fs, state->letterSpacing*scale); - fonsSetBlur(ctx->fs, state->fontBlur*scale); - fonsSetAlign(ctx->fs, state->textAlign); - fonsSetFont(ctx->fs, state->fontId); + fonsSetSize(ctx->fontContext->fs, state->fontSize*scale); + fonsSetSpacing(ctx->fontContext->fs, state->letterSpacing*scale); + fonsSetBlur(ctx->fontContext->fs, state->fontBlur*scale); + fonsSetAlign(ctx->fontContext->fs, state->textAlign); + fonsSetFont(ctx->fontContext->fs, state->fontId); - fonsTextIterInit(ctx->fs, &iter, x*scale, y*scale, string, end); + fonsTextIterInit(ctx->fontContext->fs, &iter, x*scale, y*scale, string, end, FONS_GLYPH_BITMAP_OPTIONAL); prevIter = iter; - while (fonsTextIterNext(ctx->fs, &iter, &q)) { + while (fonsTextIterNext(ctx->fontContext->fs, &iter, &q)) { if (iter.prevGlyphIndex < 0 && nvg__allocTextAtlas(ctx)) { // can not retrieve glyph? iter = prevIter; - fonsTextIterNext(ctx->fs, &iter, &q); // try again + fonsTextIterNext(ctx->fontContext->fs, &iter, &q); // try again } prevIter = iter; positions[npos].str = iter.str; @@ -2431,6 +2689,7 @@ enum NVGcodepointType { NVG_SPACE, NVG_NEWLINE, NVG_CHAR, + NVG_CJK_CHAR, }; int nvgTextBreakLines(NVGcontext* ctx, const char* string, const char* end, float breakRowWidth, NVGtextRow* rows, int maxRows) @@ -2464,20 +2723,20 @@ int nvgTextBreakLines(NVGcontext* ctx, const char* string, const char* end, floa if (string == end) return 0; - fonsSetSize(ctx->fs, state->fontSize*scale); - fonsSetSpacing(ctx->fs, state->letterSpacing*scale); - fonsSetBlur(ctx->fs, state->fontBlur*scale); - fonsSetAlign(ctx->fs, state->textAlign); - fonsSetFont(ctx->fs, state->fontId); + fonsSetSize(ctx->fontContext->fs, state->fontSize*scale); + fonsSetSpacing(ctx->fontContext->fs, state->letterSpacing*scale); + fonsSetBlur(ctx->fontContext->fs, state->fontBlur*scale); + fonsSetAlign(ctx->fontContext->fs, state->textAlign); + fonsSetFont(ctx->fontContext->fs, state->fontId); breakRowWidth *= scale; - fonsTextIterInit(ctx->fs, &iter, 0, 0, string, end); + fonsTextIterInit(ctx->fontContext->fs, &iter, 0, 0, string, end, FONS_GLYPH_BITMAP_OPTIONAL); prevIter = iter; - while (fonsTextIterNext(ctx->fs, &iter, &q)) { + while (fonsTextIterNext(ctx->fontContext->fs, &iter, &q)) { if (iter.prevGlyphIndex < 0 && nvg__allocTextAtlas(ctx)) { // can not retrieve glyph? iter = prevIter; - fonsTextIterNext(ctx->fs, &iter, &q); // try again + fonsTextIterNext(ctx->fontContext->fs, &iter, &q); // try again } prevIter = iter; switch (iter.codepoint) { @@ -2498,7 +2757,15 @@ int nvgTextBreakLines(NVGcontext* ctx, const char* string, const char* end, floa type = NVG_NEWLINE; break; default: - type = NVG_CHAR; + if ((iter.codepoint >= 0x4E00 && iter.codepoint <= 0x9FFF) || + (iter.codepoint >= 0x3000 && iter.codepoint <= 0x30FF) || + (iter.codepoint >= 0xFF00 && iter.codepoint <= 0xFFEF) || + (iter.codepoint >= 0x1100 && iter.codepoint <= 0x11FF) || + (iter.codepoint >= 0x3130 && iter.codepoint <= 0x318F) || + (iter.codepoint >= 0xAC00 && iter.codepoint <= 0xD7AF)) + type = NVG_CJK_CHAR; + else + type = NVG_CHAR; break; } @@ -2525,12 +2792,12 @@ int nvgTextBreakLines(NVGcontext* ctx, const char* string, const char* end, floa } else { if (rowStart == NULL) { // Skip white space until the beginning of the line - if (type == NVG_CHAR) { + if (type == NVG_CHAR || type == NVG_CJK_CHAR || type == NVG_SKIPPED_CHAR) { // The current char is the row so far rowStartX = iter.x; rowStart = iter.str; rowEnd = iter.next; - rowWidth = iter.nextx - rowStartX; // q.x1 - rowStartX; + rowWidth = iter.nextx - rowStartX; rowMinX = q.x0 - rowStartX; rowMaxX = q.x1 - rowStartX; wordStart = iter.str; @@ -2545,26 +2812,26 @@ int nvgTextBreakLines(NVGcontext* ctx, const char* string, const char* end, floa float nextWidth = iter.nextx - rowStartX; // track last non-white space character - if (type == NVG_CHAR) { + if (type == NVG_CHAR || type == NVG_CJK_CHAR || type == NVG_SKIPPED_CHAR) { rowEnd = iter.next; rowWidth = iter.nextx - rowStartX; rowMaxX = q.x1 - rowStartX; } // track last end of a word - if (ptype == NVG_CHAR && type == NVG_SPACE) { + if (((ptype == NVG_CHAR || ptype == NVG_CJK_CHAR) && type == NVG_SPACE) || type == NVG_CJK_CHAR) { breakEnd = iter.str; breakWidth = rowWidth; breakMaxX = rowMaxX; } // track last beginning of a word - if (ptype == NVG_SPACE && type == NVG_CHAR) { + if ((ptype == NVG_SPACE && (type == NVG_CHAR || type == NVG_CJK_CHAR)) || type == NVG_CJK_CHAR) { wordStart = iter.str; wordStartX = iter.x; - wordMinX = q.x0 - rowStartX; + wordMinX = q.x0; } // Break to new line when a character is beyond break width. - if (type == NVG_CHAR && nextWidth > breakRowWidth) { + if ((type == NVG_CHAR || type == NVG_CJK_CHAR) && nextWidth > breakRowWidth) { // The run length is too long, need to break to new line. if (breakEnd == rowStart) { // The current word is longer than the row length, just break it from here. @@ -2597,13 +2864,13 @@ int nvgTextBreakLines(NVGcontext* ctx, const char* string, const char* end, floa nrows++; if (nrows >= maxRows) return nrows; + // Update row rowStartX = wordStartX; rowStart = wordStart; rowEnd = iter.next; rowWidth = iter.nextx - rowStartX; - rowMinX = wordMinX; + rowMinX = wordMinX - rowStartX; rowMaxX = q.x1 - rowStartX; - // No change to the word start } // Set null break point breakEnd = rowStart; @@ -2640,16 +2907,16 @@ float nvgTextBounds(NVGcontext* ctx, float x, float y, const char* string, const if (state->fontId == FONS_INVALID) return 0; - fonsSetSize(ctx->fs, state->fontSize*scale); - fonsSetSpacing(ctx->fs, state->letterSpacing*scale); - fonsSetBlur(ctx->fs, state->fontBlur*scale); - fonsSetAlign(ctx->fs, state->textAlign); - fonsSetFont(ctx->fs, state->fontId); + fonsSetSize(ctx->fontContext->fs, state->fontSize*scale); + fonsSetSpacing(ctx->fontContext->fs, state->letterSpacing*scale); + fonsSetBlur(ctx->fontContext->fs, state->fontBlur*scale); + fonsSetAlign(ctx->fontContext->fs, state->textAlign); + fonsSetFont(ctx->fontContext->fs, state->fontId); - width = fonsTextBounds(ctx->fs, x*scale, y*scale, string, end, bounds); + width = fonsTextBounds(ctx->fontContext->fs, x*scale, y*scale, string, end, bounds); if (bounds != NULL) { // Use line bounds for height. - fonsLineBounds(ctx->fs, y*scale, &bounds[1], &bounds[3]); + fonsLineBounds(ctx->fontContext->fs, y*scale, &bounds[1], &bounds[3]); bounds[0] *= invscale; bounds[1] *= invscale; bounds[2] *= invscale; @@ -2684,12 +2951,12 @@ void nvgTextBoxBounds(NVGcontext* ctx, float x, float y, float breakRowWidth, co minx = maxx = x; miny = maxy = y; - fonsSetSize(ctx->fs, state->fontSize*scale); - fonsSetSpacing(ctx->fs, state->letterSpacing*scale); - fonsSetBlur(ctx->fs, state->fontBlur*scale); - fonsSetAlign(ctx->fs, state->textAlign); - fonsSetFont(ctx->fs, state->fontId); - fonsLineBounds(ctx->fs, 0, &rminy, &rmaxy); + fonsSetSize(ctx->fontContext->fs, state->fontSize*scale); + fonsSetSpacing(ctx->fontContext->fs, state->letterSpacing*scale); + fonsSetBlur(ctx->fontContext->fs, state->fontBlur*scale); + fonsSetAlign(ctx->fontContext->fs, state->textAlign); + fonsSetFont(ctx->fontContext->fs, state->fontId); + fonsLineBounds(ctx->fontContext->fs, 0, &rminy, &rmaxy); rminy *= invscale; rmaxy *= invscale; @@ -2735,13 +3002,13 @@ void nvgTextMetrics(NVGcontext* ctx, float* ascender, float* descender, float* l if (state->fontId == FONS_INVALID) return; - fonsSetSize(ctx->fs, state->fontSize*scale); - fonsSetSpacing(ctx->fs, state->letterSpacing*scale); - fonsSetBlur(ctx->fs, state->fontBlur*scale); - fonsSetAlign(ctx->fs, state->textAlign); - fonsSetFont(ctx->fs, state->fontId); + fonsSetSize(ctx->fontContext->fs, state->fontSize*scale); + fonsSetSpacing(ctx->fontContext->fs, state->letterSpacing*scale); + fonsSetBlur(ctx->fontContext->fs, state->fontBlur*scale); + fonsSetAlign(ctx->fontContext->fs, state->textAlign); + fonsSetFont(ctx->fontContext->fs, state->fontId); - fonsVertMetrics(ctx->fs, ascender, descender, lineh); + fonsVertMetrics(ctx->fontContext->fs, ascender, descender, lineh); if (ascender != NULL) *ascender *= invscale; if (descender != NULL) diff --git a/source/modules/dgl/src/nanovg/nanovg.h b/source/modules/dgl/src/nanovg/nanovg.h index 5b27c9b76..89b4dd809 100644 --- a/source/modules/dgl/src/nanovg/nanovg.h +++ b/source/modules/dgl/src/nanovg/nanovg.h @@ -79,10 +79,46 @@ enum NVGalign { // Vertical align NVG_ALIGN_TOP = 1<<3, // Align text vertically to top. NVG_ALIGN_MIDDLE = 1<<4, // Align text vertically to middle. - NVG_ALIGN_BOTTOM = 1<<5, // Align text vertically to bottom. - NVG_ALIGN_BASELINE = 1<<6, // Default, align text vertically to baseline. + NVG_ALIGN_BOTTOM = 1<<5, // Align text vertically to bottom. + NVG_ALIGN_BASELINE = 1<<6, // Default, align text vertically to baseline. }; +enum NVGblendFactor { + NVG_ZERO = 1<<0, + NVG_ONE = 1<<1, + NVG_SRC_COLOR = 1<<2, + NVG_ONE_MINUS_SRC_COLOR = 1<<3, + NVG_DST_COLOR = 1<<4, + NVG_ONE_MINUS_DST_COLOR = 1<<5, + NVG_SRC_ALPHA = 1<<6, + NVG_ONE_MINUS_SRC_ALPHA = 1<<7, + NVG_DST_ALPHA = 1<<8, + NVG_ONE_MINUS_DST_ALPHA = 1<<9, + NVG_SRC_ALPHA_SATURATE = 1<<10, +}; + +enum NVGcompositeOperation { + NVG_SOURCE_OVER, + NVG_SOURCE_IN, + NVG_SOURCE_OUT, + NVG_ATOP, + NVG_DESTINATION_OVER, + NVG_DESTINATION_IN, + NVG_DESTINATION_OUT, + NVG_DESTINATION_ATOP, + NVG_LIGHTER, + NVG_COPY, + NVG_XOR, +}; + +struct NVGcompositeOperationState { + int srcRGB; + int dstRGB; + int srcAlpha; + int dstAlpha; +}; +typedef struct NVGcompositeOperationState NVGcompositeOperationState; + struct NVGglyphPosition { const char* str; // Position of the glyph in the input string. float x; // The x-coordinate of the logical glyph position. @@ -100,11 +136,20 @@ struct NVGtextRow { typedef struct NVGtextRow NVGtextRow; enum NVGimageFlags { - NVG_IMAGE_GENERATE_MIPMAPS = 1<<0, // Generate mipmaps during creation of the image. + NVG_IMAGE_GENERATE_MIPMAPS = 1<<0, // Generate mipmaps during creation of the image. NVG_IMAGE_REPEATX = 1<<1, // Repeat image in X direction. NVG_IMAGE_REPEATY = 1<<2, // Repeat image in Y direction. NVG_IMAGE_FLIPY = 1<<3, // Flips (inverses) image in Y direction when rendered. - NVG_IMAGE_PREMULTIPLIED = 1<<4, // Image data has premultiplied alpha. + NVG_IMAGE_PREMULTIPLIED = 1<<4, // Image data has premultiplied alpha. + NVG_IMAGE_NEAREST = 1<<5, // Image interpolation is Nearest instead Linear +}; + +enum NVGtexture { + NVG_TEXTURE_ALPHA, + NVG_TEXTURE_BGR, + NVG_TEXTURE_BGRA, + NVG_TEXTURE_RGB, + NVG_TEXTURE_RGBA, }; // Begin drawing a new frame @@ -115,7 +160,7 @@ enum NVGimageFlags { // For example, GLFW returns two dimension for an opened window: window size and // frame buffer size. In that case you would set windowWidth/Height to the window size // devicePixelRatio to: frameBufferWidth / windowWidth. -void nvgBeginFrame(NVGcontext* ctx, int windowWidth, int windowHeight, float devicePixelRatio); +void nvgBeginFrame(NVGcontext* ctx, float windowWidth, float windowHeight, float devicePixelRatio); // Cancels drawing the current frame. void nvgCancelFrame(NVGcontext* ctx); @@ -123,6 +168,22 @@ void nvgCancelFrame(NVGcontext* ctx); // Ends drawing flushing remaining render state. void nvgEndFrame(NVGcontext* ctx); +// +// Composite operation +// +// The composite operations in NanoVG are modeled after HTML Canvas API, and +// the blend func is based on OpenGL (see corresponding manuals for more info). +// The colors in the blending state have premultiplied alpha. + +// Sets the composite operation. The op parameter should be one of NVGcompositeOperation. +void nvgGlobalCompositeOperation(NVGcontext* ctx, int op); + +// Sets the composite operation with custom pixel arithmetic. The parameters should be one of NVGblendFactor. +void nvgGlobalCompositeBlendFunc(NVGcontext* ctx, int sfactor, int dfactor); + +// Sets the composite operation with custom pixel arithmetic for RGB and alpha components separately. The parameters should be one of NVGblendFactor. +void nvgGlobalCompositeBlendFuncSeparate(NVGcontext* ctx, int srcRGB, int dstRGB, int srcAlpha, int dstAlpha); + // // Color utils // @@ -183,7 +244,10 @@ void nvgReset(NVGcontext* ctx); // Solid color is simply defined as a color value, different kinds of paints can be created // using nvgLinearGradient(), nvgBoxGradient(), nvgRadialGradient() and nvgImagePattern(). // -// Current render style can be saved and restored using nvgSave() and nvgRestore(). +// Current render style can be saved and restored using nvgSave() and nvgRestore(). + +// Sets whether to draw antialias for nvgStroke() and nvgFill(). It's enabled by default. +void nvgShapeAntiAlias(NVGcontext* ctx, int enabled); // Sets current stroke style to a solid color. void nvgStrokeColor(NVGcontext* ctx, NVGcolor color); @@ -215,6 +279,10 @@ void nvgLineJoin(NVGcontext* ctx, int join); // Sets the transparency applied to all rendered shapes. // Already transparent paths will get proportionally more transparent as well. void nvgGlobalAlpha(NVGcontext* ctx, float alpha); +void nvgGlobalTint(NVGcontext* ctx, NVGcolor tint); +NVGcolor nvgGetGlobalTint(NVGcontext* ctx); +void nvgAlpha(NVGcontext* ctx, float alpha); +void nvgTint(NVGcontext* ctx, NVGcolor tint); // // Transforms @@ -231,7 +299,7 @@ void nvgGlobalAlpha(NVGcontext* ctx, float alpha); // Apart from nvgResetTransform(), each transformation function first creates // specific transformation matrix and pre-multiplies the current transformation by it. // -// Current coordinate system (transformation) can be saved and restored using nvgSave() and nvgRestore(). +// Current coordinate system (transformation) can be saved and restored using nvgSave() and nvgRestore(). // Resets current transform to a identity matrix. void nvgResetTransform(NVGcontext* ctx); @@ -317,7 +385,11 @@ int nvgCreateImage(NVGcontext* ctx, const char* filename, int imageFlags); // Creates image by loading it from the specified chunk of memory. // Returns handle to the image. -int nvgCreateImageMem(NVGcontext* ctx, int imageFlags, const unsigned char* data, int ndata); +int nvgCreateImageMem(NVGcontext* ctx, int imageFlags, unsigned char* data, int ndata); + +// Creates image from specified image data and texture format. +// Returns handle to the image. +int nvgCreateImageRaw(NVGcontext* ctx, int w, int h, int imageFlags, enum NVGtexture format, const unsigned char* data); // Creates image from specified image data. // Returns handle to the image. @@ -358,7 +430,7 @@ NVGpaint nvgBoxGradient(NVGcontext* ctx, float x, float y, float w, float h, NVGpaint nvgRadialGradient(NVGcontext* ctx, float cx, float cy, float inr, float outr, NVGcolor icol, NVGcolor ocol); -// Creates and returns an image patter. Parameters (ox,oy) specify the left-top location of the image pattern, +// Creates and returns an image pattern. Parameters (ox,oy) specify the left-top location of the image pattern, // (ex,ey) the size of one image, angle rotation around the top-left corner, image is handle to the image to render. // The gradient is transformed by the current transform when it is passed to nvgFillPaint() or nvgStrokePaint(). NVGpaint nvgImagePattern(NVGcontext* ctx, float ox, float oy, float ex, float ey, @@ -368,7 +440,7 @@ NVGpaint nvgImagePattern(NVGcontext* ctx, float ox, float oy, float ex, float ey // Scissoring // // Scissoring allows you to clip the rendering into a rectangle. This is useful for various -// user interface cases like rendering a text edit or a timeline. +// user interface cases like rendering a text edit or a timeline. // Sets the current scissor rectangle. // The scissor rectangle is transformed by the current transform. @@ -423,7 +495,7 @@ void nvgArcTo(NVGcontext* ctx, float x1, float y1, float x2, float y2, float rad // Closes current sub-path with a line segment. void nvgClosePath(NVGcontext* ctx); -// Sets the current sub-path winding, see NVGwinding and NVGsolidity. +// Sets the current sub-path winding, see NVGwinding and NVGsolidity. void nvgPathWinding(NVGcontext* ctx, int dir); // Creates new circle arc shaped sub-path. The arc center is at cx,cy, the arc radius is r, @@ -437,10 +509,13 @@ void nvgRect(NVGcontext* ctx, float x, float y, float w, float h); // Creates new rounded rectangle shaped sub-path. void nvgRoundedRect(NVGcontext* ctx, float x, float y, float w, float h, float r); +// Creates new rounded rectangle shaped sub-path with varying radii for each corner. +void nvgRoundedRectVarying(NVGcontext* ctx, float x, float y, float w, float h, float radTopLeft, float radTopRight, float radBottomRight, float radBottomLeft); + // Creates new ellipse shaped sub-path. void nvgEllipse(NVGcontext* ctx, float cx, float cy, float rx, float ry); -// Creates new circle shaped sub-path. +// Creates new circle shaped sub-path. void nvgCircle(NVGcontext* ctx, float cx, float cy, float r); // Fills the current path with current fill style. @@ -487,13 +562,31 @@ void nvgStroke(NVGcontext* ctx); // Returns handle to the font. int nvgCreateFont(NVGcontext* ctx, const char* name, const char* filename); -// Creates image by loading it from the specified memory chunk. +// fontIndex specifies which font face to load from a .ttf/.ttc file. +int nvgCreateFontAtIndex(NVGcontext* ctx, const char* name, const char* filename, const int fontIndex); + +// Creates font by loading it from the specified memory chunk. // Returns handle to the font. -int nvgCreateFontMem(NVGcontext* ctx, const char* name, const unsigned char* data, int ndata, int freeData); +int nvgCreateFontMem(NVGcontext* ctx, const char* name, unsigned char* data, int ndata, int freeData); + +// fontIndex specifies which font face to load from a .ttf/.ttc file. +int nvgCreateFontMemAtIndex(NVGcontext* ctx, const char* name, unsigned char* data, int ndata, int freeData, const int fontIndex); // Finds a loaded font of specified name, and returns handle to it, or -1 if the font is not found. int nvgFindFont(NVGcontext* ctx, const char* name); +// Adds a fallback font by handle. +int nvgAddFallbackFontId(NVGcontext* ctx, int baseFont, int fallbackFont); + +// Adds a fallback font by name. +int nvgAddFallbackFont(NVGcontext* ctx, const char* baseFont, const char* fallbackFont); + +// Resets fallback fonts by handle. +void nvgResetFallbackFontsId(NVGcontext* ctx, int baseFont); + +// Resets fallback fonts by name. +void nvgResetFallbackFonts(NVGcontext* ctx, const char* baseFont); + // Sets the font size of current text style. void nvgFontSize(NVGcontext* ctx, float size); @@ -503,7 +596,7 @@ void nvgFontBlur(NVGcontext* ctx, float blur); // Sets the letter spacing of current text style. void nvgTextLetterSpacing(NVGcontext* ctx, float spacing); -// Sets the proportional line height of current text style. The line height is specified as multiple of font size. +// Sets the proportional line height of current text style. The line height is specified as multiple of font size. void nvgTextLineHeight(NVGcontext* ctx, float lineHeight); // Sets the text align of current text style, see NVGalign for options. @@ -550,11 +643,6 @@ int nvgTextBreakLines(NVGcontext* ctx, const char* string, const char* end, floa // // Internal Render API // -enum NVGtexture { - NVG_TEXTURE_ALPHA = 0x01, - NVG_TEXTURE_RGBA = 0x02, -}; - struct NVGscissor { float xform[6]; float extent[2]; @@ -583,23 +671,23 @@ typedef struct NVGpath NVGpath; struct NVGparams { void* userPtr; int edgeAntiAlias; - int (*renderCreate)(void* uptr); + int (*renderCreate)(void* uptr, void* otherUptr); int (*renderCreateTexture)(void* uptr, int type, int w, int h, int imageFlags, const unsigned char* data); int (*renderDeleteTexture)(void* uptr, int image); int (*renderUpdateTexture)(void* uptr, int image, int x, int y, int w, int h, const unsigned char* data); int (*renderGetTextureSize)(void* uptr, int image, int* w, int* h); - void (*renderViewport)(void* uptr, int width, int height); + void (*renderViewport)(void* uptr, float width, float height, float devicePixelRatio); void (*renderCancel)(void* uptr); void (*renderFlush)(void* uptr); - void (*renderFill)(void* uptr, NVGpaint* paint, NVGscissor* scissor, float fringe, const float* bounds, const NVGpath* paths, int npaths); - void (*renderStroke)(void* uptr, NVGpaint* paint, NVGscissor* scissor, float fringe, float strokeWidth, const NVGpath* paths, int npaths); - void (*renderTriangles)(void* uptr, NVGpaint* paint, NVGscissor* scissor, const NVGvertex* verts, int nverts); + void (*renderFill)(void* uptr, NVGpaint* paint, NVGcompositeOperationState compositeOperation, NVGscissor* scissor, float fringe, const float* bounds, const NVGpath* paths, int npaths); + void (*renderStroke)(void* uptr, NVGpaint* paint, NVGcompositeOperationState compositeOperation, NVGscissor* scissor, float fringe, float strokeWidth, const NVGpath* paths, int npaths); + void (*renderTriangles)(void* uptr, NVGpaint* paint, NVGcompositeOperationState compositeOperation, NVGscissor* scissor, const NVGvertex* verts, int nverts, float fringe); void (*renderDelete)(void* uptr); }; typedef struct NVGparams NVGparams; // Constructor and destructor, called by the render back-end. -NVGcontext* nvgCreateInternal(NVGparams* params); +NVGcontext* nvgCreateInternal(NVGparams* params, NVGcontext* other); void nvgDeleteInternal(NVGcontext* ctx); NVGparams* nvgInternalParams(NVGcontext* ctx); diff --git a/source/modules/dgl/src/nanovg/nanovg_gl.h b/source/modules/dgl/src/nanovg/nanovg_gl.h index 0b3c4c9a8..dc541ff24 100644 --- a/source/modules/dgl/src/nanovg/nanovg_gl.h +++ b/source/modules/dgl/src/nanovg/nanovg_gl.h @@ -57,29 +57,45 @@ enum NVGcreateFlags { #if defined NANOVG_GL2 NVGcontext* nvgCreateGL2(int flags); +NVGcontext* nvgCreateSharedGL2(NVGcontext* other, int flags); void nvgDeleteGL2(NVGcontext* ctx); +int nvglCreateImageFromHandleGL2(NVGcontext* ctx, GLuint textureId, int w, int h, int flags); +GLuint nvglImageHandleGL2(NVGcontext* ctx, int image); + #endif #if defined NANOVG_GL3 NVGcontext* nvgCreateGL3(int flags); +NVGcontext* nvgCreateSharedGL3(NVGcontext* other, int flags); void nvgDeleteGL3(NVGcontext* ctx); +int nvglCreateImageFromHandleGL3(NVGcontext* ctx, GLuint textureId, int w, int h, int flags); +GLuint nvglImageHandleGL3(NVGcontext* ctx, int image); + #endif #if defined NANOVG_GLES2 NVGcontext* nvgCreateGLES2(int flags); +NVGcontext* nvgCreateSharedGLES2(NVGcontext* other, int flags); void nvgDeleteGLES2(NVGcontext* ctx); +int nvglCreateImageFromHandleGLES2(NVGcontext* ctx, GLuint textureId, int w, int h, int flags); +GLuint nvglImageHandleGLES2(NVGcontext* ctx, int image); + #endif #if defined NANOVG_GLES3 NVGcontext* nvgCreateGLES3(int flags); +NVGcontext* nvgCreateSharedGLES3(NVGcontext* other, int flags); void nvgDeleteGLES3(NVGcontext* ctx); +int nvglCreateImageFromHandleGLES3(NVGcontext* ctx, GLuint textureId, int w, int h, int flags); +GLuint nvglImageHandleGLES3(NVGcontext* ctx, int image); + #endif // These are additional flags on top of NVGimageFlags. @@ -87,10 +103,6 @@ enum NVGimageFlagsGL { NVG_IMAGE_NODELETE = 1<<16, // Do not delete GL texture handle. }; -int nvglCreateImageFromHandle(NVGcontext* ctx, GLuint textureId, int w, int h, int flags); -GLuint nvglImageHandle(NVGcontext* ctx, int image); - - #ifdef __cplusplus } #endif @@ -142,6 +154,15 @@ struct GLNVGtexture { }; typedef struct GLNVGtexture GLNVGtexture; +struct GLNVGblend +{ + GLenum srcRGB; + GLenum dstRGB; + GLenum srcAlpha; + GLenum dstAlpha; +}; +typedef struct GLNVGblend GLNVGblend; + enum GLNVGcallType { GLNVG_NONE = 0, GLNVG_FILL, @@ -158,6 +179,7 @@ struct GLNVGcall { int triangleOffset; int triangleCount; int uniformOffset; + GLNVGblend blendFunc; }; typedef struct GLNVGcall GLNVGcall; @@ -210,13 +232,19 @@ struct GLNVGfragUniforms { }; typedef struct GLNVGfragUniforms GLNVGfragUniforms; -struct GLNVGcontext { - GLNVGshader shader; +struct GLNVGtextureContext { // Textures; shared between shared NanoVG contexts. + int refCount; GLNVGtexture* textures; - float view[2]; int ntextures; int ctextures; int textureId; +}; +typedef struct GLNVGtextureContext GLNVGtextureContext; + +struct GLNVGcontext { + GLNVGshader shader; + GLNVGtextureContext* textureContext; + float view[2]; GLuint vertBuf; #if defined NANOVG_GL3 GLuint vertArr; @@ -248,7 +276,10 @@ struct GLNVGcontext { GLenum stencilFunc; GLint stencilFuncRef; GLuint stencilFuncMask; + GLNVGblend blendFunc; #endif + + int dummyTex; }; typedef struct GLNVGcontext GLNVGcontext; @@ -298,7 +329,7 @@ static void glnvg__stencilFunc(GLNVGcontext* gl, GLenum func, GLint ref, GLuint if ((gl->stencilFunc != func) || (gl->stencilFuncRef != ref) || (gl->stencilFuncMask != mask)) { - + gl->stencilFunc = func; gl->stencilFuncRef = ref; gl->stencilFuncMask = mask; @@ -308,53 +339,68 @@ static void glnvg__stencilFunc(GLNVGcontext* gl, GLenum func, GLint ref, GLuint glStencilFunc(func, ref, mask); #endif } +static void glnvg__blendFuncSeparate(GLNVGcontext* gl, const GLNVGblend* blend) +{ +#if NANOVG_GL_USE_STATE_FILTER + if ((gl->blendFunc.srcRGB != blend->srcRGB) || + (gl->blendFunc.dstRGB != blend->dstRGB) || + (gl->blendFunc.srcAlpha != blend->srcAlpha) || + (gl->blendFunc.dstAlpha != blend->dstAlpha)) { + + gl->blendFunc = *blend; + glBlendFuncSeparate(blend->srcRGB, blend->dstRGB, blend->srcAlpha,blend->dstAlpha); + } +#else + glBlendFuncSeparate(blend->srcRGB, blend->dstRGB, blend->srcAlpha,blend->dstAlpha); +#endif +} static GLNVGtexture* glnvg__allocTexture(GLNVGcontext* gl) { GLNVGtexture* tex = NULL; int i; - for (i = 0; i < gl->ntextures; i++) { - if (gl->textures[i].id == 0) { - tex = &gl->textures[i]; + for (i = 0; i < gl->textureContext->ntextures; i++) { + if (gl->textureContext->textures[i].id == 0) { + tex = &gl->textureContext->textures[i]; break; } } if (tex == NULL) { - if (gl->ntextures+1 > gl->ctextures) { + if (gl->textureContext->ntextures+1 > gl->textureContext->ctextures) { GLNVGtexture* textures; - int ctextures = glnvg__maxi(gl->ntextures+1, 4) + gl->ctextures/2; // 1.5x Overallocate - textures = (GLNVGtexture*)realloc(gl->textures, sizeof(GLNVGtexture)*ctextures); + int ctextures = glnvg__maxi(gl->textureContext->ntextures+1, 4) + gl->textureContext->ctextures/2; // 1.5x Overallocate + textures = (GLNVGtexture*)realloc(gl->textureContext->textures, sizeof(GLNVGtexture)*ctextures); if (textures == NULL) return NULL; - gl->textures = textures; - gl->ctextures = ctextures; + gl->textureContext->textures = textures; + gl->textureContext->ctextures = ctextures; } - tex = &gl->textures[gl->ntextures++]; + tex = &gl->textureContext->textures[gl->textureContext->ntextures++]; } - + memset(tex, 0, sizeof(*tex)); - tex->id = ++gl->textureId; - + tex->id = ++gl->textureContext->textureId; + return tex; } static GLNVGtexture* glnvg__findTexture(GLNVGcontext* gl, int id) { int i; - for (i = 0; i < gl->ntextures; i++) - if (gl->textures[i].id == id) - return &gl->textures[i]; + for (i = 0; i < gl->textureContext->ntextures; i++) + if (gl->textureContext->textures[i].id == id) + return &gl->textureContext->textures[i]; return NULL; } static int glnvg__deleteTexture(GLNVGcontext* gl, int id) { int i; - for (i = 0; i < gl->ntextures; i++) { - if (gl->textures[i].id == id) { - if (gl->textures[i].tex != 0 && (gl->textures[i].flags & NVG_IMAGE_NODELETE) == 0) - glDeleteTextures(1, &gl->textures[i].tex); - memset(&gl->textures[i], 0, sizeof(gl->textures[i])); + for (i = 0; i < gl->textureContext->ntextures; i++) { + if (gl->textureContext->textures[i].id == id) { + if (gl->textureContext->textures[i].tex != 0 && (gl->textureContext->textures[i].flags & NVG_IMAGE_NODELETE) == 0) + glDeleteTextures(1, &gl->textureContext->textures[i].tex); + memset(&gl->textureContext->textures[i], 0, sizeof(gl->textureContext->textures[i])); return 1; } } @@ -363,8 +409,8 @@ static int glnvg__deleteTexture(GLNVGcontext* gl, int id) static void glnvg__dumpShaderError(GLuint shader, const char* name, const char* type) { - char str[512+1]; - int len = 0; + GLchar str[512+1]; + GLsizei len = 0; glGetShaderInfoLog(shader, 512, &len, str); if (len > 512) len = 512; str[len] = '\0'; @@ -373,8 +419,8 @@ static void glnvg__dumpShaderError(GLuint shader, const char* name, const char* static void glnvg__dumpProgramError(GLuint prog, const char* name) { - char str[512+1]; - int len = 0; + GLchar str[512+1]; + GLsizei len = 0; glGetProgramInfoLog(prog, 512, &len, str); if (len > 512) len = 512; str[len] = '\0'; @@ -466,9 +512,22 @@ static void glnvg__getUniforms(GLNVGshader* shader) #endif } -static int glnvg__renderCreate(void* uptr) +static int glnvg__renderCreateTexture(void* uptr, int type, int w, int h, int imageFlags, const unsigned char* data); + +static int glnvg__renderCreate(void* uptr, void* otherUptr) // Share the textures of GLNVGcontext 'otherUptr' if it's non-NULL. { GLNVGcontext* gl = (GLNVGcontext*)uptr; + + if (otherUptr) { + GLNVGcontext* other = (GLNVGcontext*)otherUptr; + gl->textureContext = other->textureContext; + gl->textureContext->refCount++; + } else { + gl->textureContext = (GLNVGtextureContext*)malloc(sizeof(GLNVGtextureContext)); + memset(gl->textureContext, 0, sizeof(GLNVGtextureContext)); + gl->textureContext->refCount = 1; + } + int align = 4; // TODO: mediump float may not be enough for GLES2 in iOS. @@ -514,7 +573,7 @@ static int glnvg__renderCreate(void* uptr) " gl_Position = vec4(2.0*vertex.x/viewSize.x - 1.0, 1.0 - 2.0*vertex.y/viewSize.y, 0, 1);\n" "}\n"; - static const char* fillFragShader = + static const char* fillFragShader = "#ifdef GL_ES\n" "#if defined(GL_FRAGMENT_PRECISION_HIGH) || defined(NANOVG_GL3)\n" " precision highp float;\n" @@ -592,6 +651,7 @@ static int glnvg__renderCreate(void* uptr) " float scissor = scissorMask(fpos);\n" "#ifdef EDGE_AA\n" " float strokeAlpha = strokeMask();\n" + " if (strokeAlpha < strokeThr) discard;\n" "#else\n" " float strokeAlpha = 1.0;\n" "#endif\n" @@ -631,9 +691,6 @@ static int glnvg__renderCreate(void* uptr) " color *= scissor;\n" " result = color * innerCol;\n" " }\n" - "#ifdef EDGE_AA\n" - " if (strokeAlpha < strokeThr) discard;\n" - "#endif\n" "#ifdef NANOVG_GL3\n" " outColor = result;\n" "#else\n" @@ -663,11 +720,15 @@ static int glnvg__renderCreate(void* uptr) #if NANOVG_GL_USE_UNIFORMBUFFER // Create UBOs glUniformBlockBinding(gl->shader.prog, gl->shader.loc[GLNVG_LOC_FRAG], GLNVG_FRAG_BINDING); - glGenBuffers(1, &gl->fragBuf); + glGenBuffers(1, &gl->fragBuf); glGetIntegerv(GL_UNIFORM_BUFFER_OFFSET_ALIGNMENT, &align); #endif gl->fragSize = sizeof(GLNVGfragUniforms) + align - sizeof(GLNVGfragUniforms) % align; + // Some platforms does not allow to have samples to unset textures. + // Create empty one which is bound when there's no texture specified. + gl->dummyTex = glnvg__renderCreateTexture(gl, NVG_TEXTURE_ALPHA, 1, 1, 0, NULL); + glnvg__checkError(gl, "create done"); glFinish(); @@ -690,7 +751,7 @@ static int glnvg__renderCreateTexture(void* uptr, int type, int w, int h, int im printf("Repeat X/Y is not supported for non power-of-two textures (%d x %d)\n", w, h); imageFlags &= ~(NVG_IMAGE_REPEATX | NVG_IMAGE_REPEATY); } - // No mips. + // No mips. if (imageFlags & NVG_IMAGE_GENERATE_MIPMAPS) { printf("Mip-maps is not support for non power-of-two textures (%d x %d)\n", w, h); imageFlags &= ~NVG_IMAGE_GENERATE_MIPMAPS; @@ -719,23 +780,50 @@ static int glnvg__renderCreateTexture(void* uptr, int type, int w, int h, int im } #endif - if (type == NVG_TEXTURE_RGBA) + switch (type) + { + case NVG_TEXTURE_BGR: + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, w, h, 0, GL_BGR, GL_UNSIGNED_BYTE, data); + break; + case NVG_TEXTURE_BGRA: + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, w, h, 0, GL_BGRA, GL_UNSIGNED_BYTE, data); + break; + case NVG_TEXTURE_RGB: + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, w, h, 0, GL_RGB, GL_UNSIGNED_BYTE, data); + break; + case NVG_TEXTURE_RGBA: glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, data); - else -#if defined(NANOVG_GLES2) + break; + default: +#if defined(NANOVG_GLES2) || defined (NANOVG_GL2) glTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE, w, h, 0, GL_LUMINANCE, GL_UNSIGNED_BYTE, data); #elif defined(NANOVG_GLES3) glTexImage2D(GL_TEXTURE_2D, 0, GL_R8, w, h, 0, GL_RED, GL_UNSIGNED_BYTE, data); #else glTexImage2D(GL_TEXTURE_2D, 0, GL_RED, w, h, 0, GL_RED, GL_UNSIGNED_BYTE, data); #endif + break; + } if (imageFlags & NVG_IMAGE_GENERATE_MIPMAPS) { - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); + if (imageFlags & NVG_IMAGE_NEAREST) { + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST_MIPMAP_NEAREST); + } else { + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); + } } else { - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + if (imageFlags & NVG_IMAGE_NEAREST) { + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + } else { + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + } + } + + if (imageFlags & NVG_IMAGE_NEAREST) { + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + } else { + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); } - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); if (imageFlags & NVG_IMAGE_REPEATX) glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); @@ -790,22 +878,50 @@ static int glnvg__renderUpdateTexture(void* uptr, int image, int x, int y, int w glPixelStorei(GL_UNPACK_SKIP_ROWS, y); #else // No support for all of skip, need to update a whole row at a time. - if (tex->type == NVG_TEXTURE_RGBA) + switch (tex->type) + { + case NVG_TEXTURE_BGR: + data += y*tex->width*3; + break; + case NVG_TEXTURE_BGRA: data += y*tex->width*4; - else + break; + case NVG_TEXTURE_RGB: + data += y*tex->width*3; + break; + case NVG_TEXTURE_RGBA: + data += y*tex->width*4; + break; + default: data += y*tex->width; + break; + } x = 0; w = tex->width; #endif - if (tex->type == NVG_TEXTURE_RGBA) + switch (tex->type) + { + case NVG_TEXTURE_BGR: + glTexSubImage2D(GL_TEXTURE_2D, 0, x,y, w,h, GL_BGR, GL_UNSIGNED_BYTE, data); + break; + case NVG_TEXTURE_BGRA: + glTexSubImage2D(GL_TEXTURE_2D, 0, x,y, w,h, GL_BGRA, GL_UNSIGNED_BYTE, data); + break; + case NVG_TEXTURE_RGB: + glTexSubImage2D(GL_TEXTURE_2D, 0, x,y, w,h, GL_RGB, GL_UNSIGNED_BYTE, data); + break; + case NVG_TEXTURE_RGBA: glTexSubImage2D(GL_TEXTURE_2D, 0, x,y, w,h, GL_RGBA, GL_UNSIGNED_BYTE, data); - else -#ifdef NANOVG_GLES2 + break; + default: +#if defined(NANOVG_GLES2) || defined(NANOVG_GL2) glTexSubImage2D(GL_TEXTURE_2D, 0, x,y, w,h, GL_LUMINANCE, GL_UNSIGNED_BYTE, data); #else glTexSubImage2D(GL_TEXTURE_2D, 0, x,y, w,h, GL_RED, GL_UNSIGNED_BYTE, data); #endif + break; + } glPixelStorei(GL_UNPACK_ALIGNMENT, 4); #ifndef NANOVG_GLES2 @@ -887,19 +1003,46 @@ static int glnvg__convertPaint(GLNVGcontext* gl, GLNVGfragUniforms* frag, NVGpai tex = glnvg__findTexture(gl, paint->image); if (tex == NULL) return 0; if ((tex->flags & NVG_IMAGE_FLIPY) != 0) { - float flipped[6]; - nvgTransformScale(flipped, 1.0f, -1.0f); - nvgTransformMultiply(flipped, paint->xform); - nvgTransformInverse(invxform, flipped); + float m1[6], m2[6]; + nvgTransformTranslate(m1, 0.0f, frag->extent[1] * 0.5f); + nvgTransformMultiply(m1, paint->xform); + nvgTransformScale(m2, 1.0f, -1.0f); + nvgTransformMultiply(m2, m1); + nvgTransformTranslate(m1, 0.0f, -frag->extent[1] * 0.5f); + nvgTransformMultiply(m1, m2); + nvgTransformInverse(invxform, m1); } else { nvgTransformInverse(invxform, paint->xform); } frag->type = NSVG_SHADER_FILLIMG; - if (tex->type == NVG_TEXTURE_RGBA) + #if NANOVG_GL_USE_UNIFORMBUFFER + switch (tex->type) + { + case NVG_TEXTURE_BGR: + case NVG_TEXTURE_BGRA: + case NVG_TEXTURE_RGB: + case NVG_TEXTURE_RGBA: frag->texType = (tex->flags & NVG_IMAGE_PREMULTIPLIED) ? 0 : 1; - else + break; + default: frag->texType = 2; + break; + } + #else + switch (tex->type) + { + case NVG_TEXTURE_BGR: + case NVG_TEXTURE_BGRA: + case NVG_TEXTURE_RGB: + case NVG_TEXTURE_RGBA: + frag->texType = (tex->flags & NVG_IMAGE_PREMULTIPLIED) ? 0.0f : 1.0f; + break; + default: + frag->texType = 2.0f; + break; + } + #endif // printf("frag->texType = %d\n", frag->texType); } else { frag->type = NSVG_SHADER_FILLGRAD; @@ -917,6 +1060,7 @@ static GLNVGfragUniforms* nvg__fragUniformPtr(GLNVGcontext* gl, int i); static void glnvg__setUniforms(GLNVGcontext* gl, int uniformOffset, int image) { + GLNVGtexture* tex = NULL; #if NANOVG_GL_USE_UNIFORMBUFFER glBindBufferRange(GL_UNIFORM_BUFFER, GLNVG_FRAG_BINDING, gl->fragBuf, uniformOffset, sizeof(GLNVGfragUniforms)); #else @@ -925,19 +1069,22 @@ static void glnvg__setUniforms(GLNVGcontext* gl, int uniformOffset, int image) #endif if (image != 0) { - GLNVGtexture* tex = glnvg__findTexture(gl, image); - glnvg__bindTexture(gl, tex != NULL ? tex->tex : 0); - glnvg__checkError(gl, "tex paint tex"); - } else { - glnvg__bindTexture(gl, 0); + tex = glnvg__findTexture(gl, image); + } + // If no image is set, use empty texture + if (tex == NULL) { + tex = glnvg__findTexture(gl, gl->dummyTex); } + glnvg__bindTexture(gl, tex != NULL ? tex->tex : 0); + glnvg__checkError(gl, "tex paint tex"); } -static void glnvg__renderViewport(void* uptr, int width, int height) +static void glnvg__renderViewport(void* uptr, float width, float height, float devicePixelRatio) { + NVG_NOTUSED(devicePixelRatio); GLNVGcontext* gl = (GLNVGcontext*)uptr; - gl->view[0] = (float)width; - gl->view[1] = (float)height; + gl->view[0] = width; + gl->view[1] = height; } static void glnvg__fill(GLNVGcontext* gl, GLNVGcall* call) @@ -979,7 +1126,7 @@ static void glnvg__fill(GLNVGcontext* gl, GLNVGcall* call) // Draw fill glnvg__stencilFunc(gl, GL_NOTEQUAL, 0x0, 0xff); glStencilOp(GL_ZERO, GL_ZERO, GL_ZERO); - glDrawArrays(GL_TRIANGLES, call->triangleOffset, call->triangleCount); + glDrawArrays(GL_TRIANGLE_STRIP, call->triangleOffset, call->triangleCount); glDisable(GL_STENCIL_TEST); } @@ -992,12 +1139,12 @@ static void glnvg__convexFill(GLNVGcontext* gl, GLNVGcall* call) glnvg__setUniforms(gl, call->uniformOffset, call->image); glnvg__checkError(gl, "convex fill"); - for (i = 0; i < npaths; i++) + for (i = 0; i < npaths; i++) { glDrawArrays(GL_TRIANGLE_FAN, paths[i].fillOffset, paths[i].fillCount); - if (gl->flags & NVG_ANTIALIAS) { // Draw fringes - for (i = 0; i < npaths; i++) + if (paths[i].strokeCount > 0) { glDrawArrays(GL_TRIANGLE_STRIP, paths[i].strokeOffset, paths[i].strokeCount); + } } } @@ -1026,7 +1173,7 @@ static void glnvg__stroke(GLNVGcontext* gl, GLNVGcall* call) for (i = 0; i < npaths; i++) glDrawArrays(GL_TRIANGLE_STRIP, paths[i].strokeOffset, paths[i].strokeCount); - // Clear stencil buffer. + // Clear stencil buffer. glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE); glnvg__stencilFunc(gl, GL_ALWAYS, 0x0, 0xff); glStencilOp(GL_ZERO, GL_ZERO, GL_ZERO); @@ -1064,6 +1211,50 @@ static void glnvg__renderCancel(void* uptr) { gl->nuniforms = 0; } +static GLenum glnvg_convertBlendFuncFactor(int factor) +{ + if (factor == NVG_ZERO) + return GL_ZERO; + if (factor == NVG_ONE) + return GL_ONE; + if (factor == NVG_SRC_COLOR) + return GL_SRC_COLOR; + if (factor == NVG_ONE_MINUS_SRC_COLOR) + return GL_ONE_MINUS_SRC_COLOR; + if (factor == NVG_DST_COLOR) + return GL_DST_COLOR; + if (factor == NVG_ONE_MINUS_DST_COLOR) + return GL_ONE_MINUS_DST_COLOR; + if (factor == NVG_SRC_ALPHA) + return GL_SRC_ALPHA; + if (factor == NVG_ONE_MINUS_SRC_ALPHA) + return GL_ONE_MINUS_SRC_ALPHA; + if (factor == NVG_DST_ALPHA) + return GL_DST_ALPHA; + if (factor == NVG_ONE_MINUS_DST_ALPHA) + return GL_ONE_MINUS_DST_ALPHA; + if (factor == NVG_SRC_ALPHA_SATURATE) + return GL_SRC_ALPHA_SATURATE; + return GL_INVALID_ENUM; +} + +static GLNVGblend glnvg__blendCompositeOperation(NVGcompositeOperationState op) +{ + GLNVGblend blend; + blend.srcRGB = glnvg_convertBlendFuncFactor(op.srcRGB); + blend.dstRGB = glnvg_convertBlendFuncFactor(op.dstRGB); + blend.srcAlpha = glnvg_convertBlendFuncFactor(op.srcAlpha); + blend.dstAlpha = glnvg_convertBlendFuncFactor(op.dstAlpha); + if (blend.srcRGB == GL_INVALID_ENUM || blend.dstRGB == GL_INVALID_ENUM || blend.srcAlpha == GL_INVALID_ENUM || blend.dstAlpha == GL_INVALID_ENUM) + { + blend.srcRGB = GL_ONE; + blend.dstRGB = GL_ONE_MINUS_SRC_ALPHA; + blend.srcAlpha = GL_ONE; + blend.dstAlpha = GL_ONE_MINUS_SRC_ALPHA; + } + return blend; +} + static void glnvg__renderFlush(void* uptr) { GLNVGcontext* gl = (GLNVGcontext*)uptr; @@ -1074,7 +1265,6 @@ static void glnvg__renderFlush(void* uptr) // Setup require GL state. glUseProgram(gl->shader.prog); - glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); glEnable(GL_CULL_FACE); glCullFace(GL_BACK); glFrontFace(GL_CCW); @@ -1093,6 +1283,10 @@ static void glnvg__renderFlush(void* uptr) gl->stencilFunc = GL_ALWAYS; gl->stencilFuncRef = 0; gl->stencilFuncMask = 0xffffffff; + gl->blendFunc.srcRGB = GL_INVALID_ENUM; + gl->blendFunc.srcAlpha = GL_INVALID_ENUM; + gl->blendFunc.dstRGB = GL_INVALID_ENUM; + gl->blendFunc.dstAlpha = GL_INVALID_ENUM; #endif #if NANOVG_GL_USE_UNIFORMBUFFER @@ -1122,6 +1316,7 @@ static void glnvg__renderFlush(void* uptr) for (i = 0; i < gl->ncalls; i++) { GLNVGcall* call = &gl->calls[i]; + glnvg__blendFuncSeparate(gl,&call->blendFunc); if (call->type == GLNVG_FILL) glnvg__fill(gl, call); else if (call->type == GLNVG_CONVEXFILL) @@ -1136,9 +1331,9 @@ static void glnvg__renderFlush(void* uptr) glDisableVertexAttribArray(1); #if defined NANOVG_GL3 glBindVertexArray(0); -#endif +#endif glDisable(GL_CULL_FACE); - glBindBuffer(GL_ARRAY_BUFFER, 0); + glBindBuffer(GL_ARRAY_BUFFER, 0); glUseProgram(0); glnvg__bindTexture(gl, 0); } @@ -1237,7 +1432,7 @@ static void glnvg__vset(NVGvertex* vtx, float x, float y, float u, float v) vtx->v = v; } -static void glnvg__renderFill(void* uptr, NVGpaint* paint, NVGscissor* scissor, float fringe, +static void glnvg__renderFill(void* uptr, NVGpaint* paint, NVGcompositeOperationState compositeOperation, NVGscissor* scissor, float fringe, const float* bounds, const NVGpath* paths, int npaths) { GLNVGcontext* gl = (GLNVGcontext*)uptr; @@ -1249,16 +1444,21 @@ static void glnvg__renderFill(void* uptr, NVGpaint* paint, NVGscissor* scissor, if (call == NULL) return; call->type = GLNVG_FILL; + call->triangleCount = 4; call->pathOffset = glnvg__allocPaths(gl, npaths); if (call->pathOffset == -1) goto error; call->pathCount = npaths; call->image = paint->image; + call->blendFunc = glnvg__blendCompositeOperation(compositeOperation); if (npaths == 1 && paths[0].convex) + { call->type = GLNVG_CONVEXFILL; + call->triangleCount = 0; // Bounding box fill quad not needed for convex fill + } // Allocate vertices for all the paths. - maxverts = glnvg__maxVertCount(paths, npaths) + 6; + maxverts = glnvg__maxVertCount(paths, npaths) + call->triangleCount; offset = glnvg__allocVerts(gl, maxverts); if (offset == -1) goto error; @@ -1280,20 +1480,16 @@ static void glnvg__renderFill(void* uptr, NVGpaint* paint, NVGscissor* scissor, } } - // Quad - call->triangleOffset = offset; - call->triangleCount = 6; - quad = &gl->verts[call->triangleOffset]; - glnvg__vset(&quad[0], bounds[0], bounds[3], 0.5f, 1.0f); - glnvg__vset(&quad[1], bounds[2], bounds[3], 0.5f, 1.0f); - glnvg__vset(&quad[2], bounds[2], bounds[1], 0.5f, 1.0f); - - glnvg__vset(&quad[3], bounds[0], bounds[3], 0.5f, 1.0f); - glnvg__vset(&quad[4], bounds[2], bounds[1], 0.5f, 1.0f); - glnvg__vset(&quad[5], bounds[0], bounds[1], 0.5f, 1.0f); - // Setup uniforms for draw calls if (call->type == GLNVG_FILL) { + // Quad + call->triangleOffset = offset; + quad = &gl->verts[call->triangleOffset]; + glnvg__vset(&quad[0], bounds[2], bounds[3], 0.5f, 1.0f); + glnvg__vset(&quad[1], bounds[2], bounds[1], 0.5f, 1.0f); + glnvg__vset(&quad[2], bounds[0], bounds[3], 0.5f, 1.0f); + glnvg__vset(&quad[3], bounds[0], bounds[1], 0.5f, 1.0f); + call->uniformOffset = glnvg__allocFragUniforms(gl, 2); if (call->uniformOffset == -1) goto error; // Simple shader for stencil @@ -1318,7 +1514,7 @@ error: if (gl->ncalls > 0) gl->ncalls--; } -static void glnvg__renderStroke(void* uptr, NVGpaint* paint, NVGscissor* scissor, float fringe, +static void glnvg__renderStroke(void* uptr, NVGpaint* paint, NVGcompositeOperationState compositeOperation, NVGscissor* scissor, float fringe, float strokeWidth, const NVGpath* paths, int npaths) { GLNVGcontext* gl = (GLNVGcontext*)uptr; @@ -1332,6 +1528,7 @@ static void glnvg__renderStroke(void* uptr, NVGpaint* paint, NVGscissor* scissor if (call->pathOffset == -1) goto error; call->pathCount = npaths; call->image = paint->image; + call->blendFunc = glnvg__blendCompositeOperation(compositeOperation); // Allocate vertices for all the paths. maxverts = glnvg__maxVertCount(paths, npaths); @@ -1373,8 +1570,8 @@ error: if (gl->ncalls > 0) gl->ncalls--; } -static void glnvg__renderTriangles(void* uptr, NVGpaint* paint, NVGscissor* scissor, - const NVGvertex* verts, int nverts) +static void glnvg__renderTriangles(void* uptr, NVGpaint* paint, NVGcompositeOperationState compositeOperation, NVGscissor* scissor, + const NVGvertex* verts, int nverts, float fringe) { GLNVGcontext* gl = (GLNVGcontext*)uptr; GLNVGcall* call = glnvg__allocCall(gl); @@ -1384,6 +1581,7 @@ static void glnvg__renderTriangles(void* uptr, NVGpaint* paint, NVGscissor* scis call->type = GLNVG_TRIANGLES; call->image = paint->image; + call->blendFunc = glnvg__blendCompositeOperation(compositeOperation); // Allocate vertices for all the paths. call->triangleOffset = glnvg__allocVerts(gl, nverts); @@ -1396,7 +1594,7 @@ static void glnvg__renderTriangles(void* uptr, NVGpaint* paint, NVGscissor* scis call->uniformOffset = glnvg__allocFragUniforms(gl, 1); if (call->uniformOffset == -1) goto error; frag = nvg__fragUniformPtr(gl, call->uniformOffset); - glnvg__convertPaint(gl, frag, paint, scissor, 1.0f, 1.0f, -1.0f); + glnvg__convertPaint(gl, frag, paint, scissor, 1.0f, fringe, -1.0f); frag->type = NSVG_SHADER_IMG; return; @@ -1426,11 +1624,14 @@ static void glnvg__renderDelete(void* uptr) if (gl->vertBuf != 0) glDeleteBuffers(1, &gl->vertBuf); - for (i = 0; i < gl->ntextures; i++) { - if (gl->textures[i].tex != 0 && (gl->textures[i].flags & NVG_IMAGE_NODELETE) == 0) - glDeleteTextures(1, &gl->textures[i].tex); + if (gl->textureContext != NULL && --gl->textureContext->refCount == 0) { + for (i = 0; i < gl->textureContext->ntextures; i++) { + if (gl->textureContext->textures[i].tex != 0 && (gl->textureContext->textures[i].flags & NVG_IMAGE_NODELETE) == 0) + glDeleteTextures(1, &gl->textureContext->textures[i].tex); + } + free(gl->textureContext->textures); + free(gl->textureContext); } - free(gl->textures); free(gl->paths); free(gl->verts); @@ -1450,6 +1651,28 @@ NVGcontext* nvgCreateGLES2(int flags) #elif defined NANOVG_GLES3 NVGcontext* nvgCreateGLES3(int flags) #endif +{ +#if defined NANOVG_GL2 + return nvgCreateSharedGL2(NULL, flags); +#elif defined NANOVG_GL3 + return nvgCreateSharedGL3(NULL, flags); +#elif defined NANOVG_GLES2 + return nvgCreateSharedGLES2(NULL, flags); +#elif defined NANOVG_GLES3 + return nvgCreateSharedGLES3(NULL, flags); +#endif +} + +// Share the fonts and textures of 'other' if it's non-NULL. +#if defined NANOVG_GL2 +NVGcontext* nvgCreateSharedGL2(NVGcontext* other, int flags) +#elif defined NANOVG_GL3 +NVGcontext* nvgCreateSharedGL3(NVGcontext* other, int flags) +#elif defined NANOVG_GLES2 +NVGcontext* nvgCreateSharedGLES2(NVGcontext* other, int flags) +#elif defined NANOVG_GLES3 +NVGcontext* nvgCreateSharedGLES3(NVGcontext* other, int flags) +#endif { NVGparams params; NVGcontext* ctx = NULL; @@ -1475,7 +1698,7 @@ NVGcontext* nvgCreateGLES3(int flags) gl->flags = flags; - ctx = nvgCreateInternal(¶ms); + ctx = nvgCreateInternal(¶ms, other); if (ctx == NULL) goto error; return ctx; @@ -1499,7 +1722,15 @@ void nvgDeleteGLES3(NVGcontext* ctx) nvgDeleteInternal(ctx); } -int nvglCreateImageFromHandle(NVGcontext* ctx, GLuint textureId, int w, int h, int imageFlags) +#if defined NANOVG_GL2 +int nvglCreateImageFromHandleGL2(NVGcontext* ctx, GLuint textureId, int w, int h, int imageFlags) +#elif defined NANOVG_GL3 +int nvglCreateImageFromHandleGL3(NVGcontext* ctx, GLuint textureId, int w, int h, int imageFlags) +#elif defined NANOVG_GLES2 +int nvglCreateImageFromHandleGLES2(NVGcontext* ctx, GLuint textureId, int w, int h, int imageFlags) +#elif defined NANOVG_GLES3 +int nvglCreateImageFromHandleGLES3(NVGcontext* ctx, GLuint textureId, int w, int h, int imageFlags) +#endif { GLNVGcontext* gl = (GLNVGcontext*)nvgInternalParams(ctx)->userPtr; GLNVGtexture* tex = glnvg__allocTexture(gl); @@ -1515,7 +1746,15 @@ int nvglCreateImageFromHandle(NVGcontext* ctx, GLuint textureId, int w, int h, i return tex->id; } -GLuint nvglImageHandle(NVGcontext* ctx, int image) +#if defined NANOVG_GL2 +GLuint nvglImageHandleGL2(NVGcontext* ctx, int image) +#elif defined NANOVG_GL3 +GLuint nvglImageHandleGL3(NVGcontext* ctx, int image) +#elif defined NANOVG_GLES2 +GLuint nvglImageHandleGLES2(NVGcontext* ctx, int image) +#elif defined NANOVG_GLES3 +GLuint nvglImageHandleGLES3(NVGcontext* ctx, int image) +#endif { GLNVGcontext* gl = (GLNVGcontext*)nvgInternalParams(ctx)->userPtr; GLNVGtexture* tex = glnvg__findTexture(gl, image); diff --git a/source/modules/dgl/src/nanovg/nanovg_gl_utils.h b/source/modules/dgl/src/nanovg/nanovg_gl_utils.h index 89ae4811d..f7384d803 100644 --- a/source/modules/dgl/src/nanovg/nanovg_gl_utils.h +++ b/source/modules/dgl/src/nanovg/nanovg_gl_utils.h @@ -30,7 +30,7 @@ typedef struct NVGLUframebuffer NVGLUframebuffer; // Helper function to create GL frame buffer to render to. void nvgluBindFramebuffer(NVGLUframebuffer* fb); NVGLUframebuffer* nvgluCreateFramebuffer(NVGcontext* ctx, int w, int h, int imageFlags); -void nvgluDeleteFramebuffer(NVGcontext* ctx, NVGLUframebuffer* fb); +void nvgluDeleteFramebuffer(NVGLUframebuffer* fb); #endif // NANOVG_GL_UTILS_H @@ -64,7 +64,18 @@ NVGLUframebuffer* nvgluCreateFramebuffer(NVGcontext* ctx, int w, int h, int imag memset(fb, 0, sizeof(NVGLUframebuffer)); fb->image = nvgCreateImageRGBA(ctx, w, h, imageFlags | NVG_IMAGE_FLIPY | NVG_IMAGE_PREMULTIPLIED, NULL); - fb->texture = nvglImageHandle(ctx, fb->image); + +#if defined NANOVG_GL2 + fb->texture = nvglImageHandleGL2(ctx, fb->image); +#elif defined NANOVG_GL3 + fb->texture = nvglImageHandleGL3(ctx, fb->image); +#elif defined NANOVG_GLES2 + fb->texture = nvglImageHandleGLES2(ctx, fb->image); +#elif defined NANOVG_GLES3 + fb->texture = nvglImageHandleGLES3(ctx, fb->image); +#endif + + fb->ctx = ctx; // frame buffer object glGenFramebuffers(1, &fb->fbo); @@ -79,7 +90,18 @@ NVGLUframebuffer* nvgluCreateFramebuffer(NVGcontext* ctx, int w, int h, int imag glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, fb->texture, 0); glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_RENDERBUFFER, fb->rbo); - if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) goto error; + if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) { +#ifdef GL_DEPTH24_STENCIL8 + // If GL_STENCIL_INDEX8 is not supported, try GL_DEPTH24_STENCIL8 as a fallback. + // Some graphics cards require a depth buffer along with a stencil. + glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, w, h); + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, fb->texture, 0); + glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_RENDERBUFFER, fb->rbo); + + if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) +#endif // GL_DEPTH24_STENCIL8 + goto error; + } glBindFramebuffer(GL_FRAMEBUFFER, defaultFBO); glBindRenderbuffer(GL_RENDERBUFFER, defaultRBO); @@ -87,7 +109,7 @@ NVGLUframebuffer* nvgluCreateFramebuffer(NVGcontext* ctx, int w, int h, int imag error: glBindFramebuffer(GL_FRAMEBUFFER, defaultFBO); glBindRenderbuffer(GL_RENDERBUFFER, defaultRBO); - nvgluDeleteFramebuffer(ctx, fb); + nvgluDeleteFramebuffer(fb); return NULL; #else NVG_NOTUSED(ctx); @@ -108,7 +130,7 @@ void nvgluBindFramebuffer(NVGLUframebuffer* fb) #endif } -void nvgluDeleteFramebuffer(NVGcontext* ctx, NVGLUframebuffer* fb) +void nvgluDeleteFramebuffer(NVGLUframebuffer* fb) { #ifdef NANOVG_FBO_VALID if (fb == NULL) return; @@ -117,14 +139,14 @@ void nvgluDeleteFramebuffer(NVGcontext* ctx, NVGLUframebuffer* fb) if (fb->rbo != 0) glDeleteRenderbuffers(1, &fb->rbo); if (fb->image >= 0) - nvgDeleteImage(ctx, fb->image); + nvgDeleteImage(fb->ctx, fb->image); + fb->ctx = NULL; fb->fbo = 0; fb->rbo = 0; fb->texture = 0; fb->image = -1; free(fb); #else - NVG_NOTUSED(ctx); NVG_NOTUSED(fb); #endif } diff --git a/source/modules/dgl/src/nanovg/stb_image.h b/source/modules/dgl/src/nanovg/stb_image.h index c180e3def..e06f7a1d7 100644 --- a/source/modules/dgl/src/nanovg/stb_image.h +++ b/source/modules/dgl/src/nanovg/stb_image.h @@ -1,87 +1,236 @@ -/* stb_image - v1.39 - public domain JPEG/PNG reader - http://nothings.org/stb_image.c - when you control the images you're loading +/* stb_image - v2.10 - public domain image loader - http://nothings.org/stb_image.h no warranty implied; use at your own risk Do this: #define STB_IMAGE_IMPLEMENTATION before you include this file in *one* C or C++ file to create the implementation. + // i.e. it should look like this: + #include ... + #include ... + #include ... + #define STB_IMAGE_IMPLEMENTATION + #include "stb_image.h" + + You can #define STBI_ASSERT(x) before the #include to avoid using assert.h. + And #define STBI_MALLOC, STBI_REALLOC, and STBI_FREE to avoid using malloc,realloc,free + + QUICK NOTES: Primarily of interest to game developers and other people who can avoid problematic images and only need the trivial interface - JPEG baseline (no JPEG progressive) - PNG 8-bit-per-channel only + JPEG baseline & progressive (12 bpc/arithmetic not supported, same as stock IJG lib) + PNG 1/2/4/8-bit-per-channel (16 bpc not supported) TGA (not sure what subset, if a subset) BMP non-1bpp, non-RLE - PSD (composited view only, no extra channels) + PSD (composited view only, no extra channels, 8/16 bit-per-channel) GIF (*comp always reports as 4-channel) HDR (radiance rgbE format) PIC (Softimage PIC) - - - stbi__jpeg_huff_decode from memory or through FILE (define STBI_NO_STDIO to remove code) - - stbi__jpeg_huff_decode from arbitrary I/O callbacks - - overridable dequantizing-IDCT, YCbCr-to-RGB conversion (define STBI_SIMD) - - Latest revisions: - 1.39 (2014-06-15) TGA optimization fix, multiple BMP fixes - 1.38 (2014-06-06) suppress MSVC run-time warnings, fix accidental rename of 'skip' - 1.37 (2014-06-04) remove duplicate typedef - 1.36 (2014-06-03) converted to header file, allow reading incorrect iphoned-images without iphone flag - 1.35 (2014-05-27) warnings, bugfixes, TGA optimization, etc - 1.34 (unknown ) warning fix - 1.33 (2011-07-14) minor fixes suggested by Dave Moore + PNM (PPM and PGM binary only) + + Animated GIF still needs a proper API, but here's one way to do it: + http://gist.github.com/urraka/685d9a6340b26b830d49 + + - decode from memory or through FILE (define STBI_NO_STDIO to remove code) + - decode from arbitrary I/O callbacks + - SIMD acceleration on x86/x64 (SSE2) and ARM (NEON) + + Full documentation under "DOCUMENTATION" below. + + + Revision 2.00 release notes: + + - Progressive JPEG is now supported. + + - PPM and PGM binary formats are now supported, thanks to Ken Miller. + + - x86 platforms now make use of SSE2 SIMD instructions for + JPEG decoding, and ARM platforms can use NEON SIMD if requested. + This work was done by Fabian "ryg" Giesen. SSE2 is used by + default, but NEON must be enabled explicitly; see docs. + + With other JPEG optimizations included in this version, we see + 2x speedup on a JPEG on an x86 machine, and a 1.5x speedup + on a JPEG on an ARM machine, relative to previous versions of this + library. The same results will not obtain for all JPGs and for all + x86/ARM machines. (Note that progressive JPEGs are significantly + slower to decode than regular JPEGs.) This doesn't mean that this + is the fastest JPEG decoder in the land; rather, it brings it + closer to parity with standard libraries. If you want the fastest + decode, look elsewhere. (See "Philosophy" section of docs below.) + + See final bullet items below for more info on SIMD. + + - Added STBI_MALLOC, STBI_REALLOC, and STBI_FREE macros for replacing + the memory allocator. Unlike other STBI libraries, these macros don't + support a context parameter, so if you need to pass a context in to + the allocator, you'll have to store it in a global or a thread-local + variable. + + - Split existing STBI_NO_HDR flag into two flags, STBI_NO_HDR and + STBI_NO_LINEAR. + STBI_NO_HDR: suppress implementation of .hdr reader format + STBI_NO_LINEAR: suppress high-dynamic-range light-linear float API + + - You can suppress implementation of any of the decoders to reduce + your code footprint by #defining one or more of the following + symbols before creating the implementation. + + STBI_NO_JPEG + STBI_NO_PNG + STBI_NO_BMP + STBI_NO_PSD + STBI_NO_TGA + STBI_NO_GIF + STBI_NO_HDR + STBI_NO_PIC + STBI_NO_PNM (.ppm and .pgm) + + - You can request *only* certain decoders and suppress all other ones + (this will be more forward-compatible, as addition of new decoders + doesn't require you to disable them explicitly): + + STBI_ONLY_JPEG + STBI_ONLY_PNG + STBI_ONLY_BMP + STBI_ONLY_PSD + STBI_ONLY_TGA + STBI_ONLY_GIF + STBI_ONLY_HDR + STBI_ONLY_PIC + STBI_ONLY_PNM (.ppm and .pgm) + + Note that you can define multiples of these, and you will get all + of them ("only x" and "only y" is interpreted to mean "only x&y"). + + - If you use STBI_NO_PNG (or _ONLY_ without PNG), and you still + want the zlib decoder to be available, #define STBI_SUPPORT_ZLIB + + - Compilation of all SIMD code can be suppressed with + #define STBI_NO_SIMD + It should not be necessary to disable SIMD unless you have issues + compiling (e.g. using an x86 compiler which doesn't support SSE + intrinsics or that doesn't support the method used to detect + SSE2 support at run-time), and even those can be reported as + bugs so I can refine the built-in compile-time checking to be + smarter. + + - The old STBI_SIMD system which allowed installing a user-defined + IDCT etc. has been removed. If you need this, don't upgrade. My + assumption is that almost nobody was doing this, and those who + were will find the built-in SIMD more satisfactory anyway. + + - RGB values computed for JPEG images are slightly different from + previous versions of stb_image. (This is due to using less + integer precision in SIMD.) The C code has been adjusted so + that the same RGB values will be computed regardless of whether + SIMD support is available, so your app should always produce + consistent results. But these results are slightly different from + previous versions. (Specifically, about 3% of available YCbCr values + will compute different RGB results from pre-1.49 versions by +-1; + most of the deviating values are one smaller in the G channel.) + + - If you must produce consistent results with previous versions of + stb_image, #define STBI_JPEG_OLD and you will get the same results + you used to; however, you will not get the SIMD speedups for + the YCbCr-to-RGB conversion step (although you should still see + significant JPEG speedup from the other changes). + + Please note that STBI_JPEG_OLD is a temporary feature; it will be + removed in future versions of the library. It is only intended for + near-term back-compatibility use. + + + Latest revision history: + 2.10 (2016-01-22) avoid warning introduced in 2.09 + 2.09 (2016-01-16) 16-bit TGA; comments in PNM files; STBI_REALLOC_SIZED + 2.08 (2015-09-13) fix to 2.07 cleanup, reading RGB PSD as RGBA + 2.07 (2015-09-13) partial animated GIF support + limited 16-bit PSD support + minor bugs, code cleanup, and compiler warnings + 2.06 (2015-04-19) fix bug where PSD returns wrong '*comp' value + 2.05 (2015-04-19) fix bug in progressive JPEG handling, fix warning + 2.04 (2015-04-15) try to re-enable SIMD on MinGW 64-bit + 2.03 (2015-04-12) additional corruption checking + stbi_set_flip_vertically_on_load + fix NEON support; fix mingw support + 2.02 (2015-01-19) fix incorrect assert, fix warning + 2.01 (2015-01-17) fix various warnings + 2.00b (2014-12-25) fix STBI_MALLOC in progressive JPEG + 2.00 (2014-12-25) optimize JPEG, including x86 SSE2 & ARM NEON SIMD + progressive JPEG + PGM/PPM support + STBI_MALLOC,STBI_REALLOC,STBI_FREE + STBI_NO_*, STBI_ONLY_* + GIF bugfix + 1.48 (2014-12-14) fix incorrectly-named assert() + 1.47 (2014-12-14) 1/2/4-bit PNG support (both grayscale and paletted) + optimize PNG + fix bug in interlaced PNG with user-specified channel count See end of file for full revision history. - TODO: - stbi_info support for BMP,PSD,HDR,PIC - ============================ Contributors ========================= - - Image formats Bug fixes & warning fixes - Sean Barrett (jpeg, png, bmp) Marc LeBlanc - Nicolas Schulz (hdr, psd) Christpher Lloyd - Jonathan Dummer (tga) Dave Moore - Jean-Marc Lienher (gif) Won Chun - Tom Seddon (pic) the Horde3D community - Thatcher Ulrich (psd) Janez Zemva - Jonathan Blow - Laurent Gomila - Extensions, features Aruelien Pocheville - Jetro Lauha (stbi_info) Ryamond Barbiero - James "moose2000" Brown (iPhone PNG) David Woo - Ben "Disch" Wenger (io callbacks) Roy Eltham - Martin "SpartanJ" Golini Luke Graham - Thomas Ruf - John Bartholomew - Optimizations & bugfixes Ken Hamada - Fabian "ryg" Giesen Cort Stratton - Arseny Kapoulkine Blazej Dariusz Roszkowski - Thibault Reuille - If your name should be here but Paul Du Bois - isn't, let Sean know. Guillaume George - Jerry Jansson - Hayaki Saito + + Image formats Extensions, features + Sean Barrett (jpeg, png, bmp) Jetro Lauha (stbi_info) + Nicolas Schulz (hdr, psd) Martin "SpartanJ" Golini (stbi_info) + Jonathan Dummer (tga) James "moose2000" Brown (iPhone PNG) + Jean-Marc Lienher (gif) Ben "Disch" Wenger (io callbacks) + Tom Seddon (pic) Omar Cornut (1/2/4-bit PNG) + Thatcher Ulrich (psd) Nicolas Guillemot (vertical flip) + Ken Miller (pgm, ppm) Richard Mitton (16-bit PSD) + urraka@github (animated gif) Junggon Kim (PNM comments) + Daniel Gibson (16-bit TGA) + + Optimizations & bugfixes + Fabian "ryg" Giesen + Arseny Kapoulkine + + Bug & warning fixes + Marc LeBlanc David Woo Guillaume George Martins Mozeiko + Christpher Lloyd Martin Golini Jerry Jansson Joseph Thomson + Dave Moore Roy Eltham Hayaki Saito Phil Jordan + Won Chun Luke Graham Johan Duparc Nathan Reed + the Horde3D community Thomas Ruf Ronny Chevalier Nick Verigakis + Janez Zemva John Bartholomew Michal Cichon svdijk@github + Jonathan Blow Ken Hamada Tero Hanninen Baldur Karlsson + Laurent Gomila Cort Stratton Sergio Gonzalez romigrou@github + Aruelien Pocheville Thibault Reuille Cass Everitt + Ryamond Barbiero Paul Du Bois Engin Manap + Blazej Dariusz Roszkowski + Michaelangel007@github + + +LICENSE + +This software is in the public domain. Where that dedication is not +recognized, you are granted a perpetual, irrevocable license to copy, +distribute, and modify this file as you see fit. + */ #ifndef STBI_INCLUDE_STB_IMAGE_H #define STBI_INCLUDE_STB_IMAGE_H +// DOCUMENTATION +// // Limitations: -// - no jpeg progressive support -// - non-HDR formats support 8-bit samples only (jpeg, png) -// - no delayed line count (jpeg) -- IJG doesn't support either +// - no 16-bit-per-channel PNG +// - no 12-bit-per-channel JPEG +// - no JPEGs with arithmetic coding // - no 1-bit BMP // - GIF always returns *comp=4 // -// Basic usage (see HDR discussion below): +// Basic usage (see HDR discussion below for HDR usage): // int x,y,n; // unsigned char *data = stbi_load(filename, &x, &y, &n, 0); -// // ... process data if not NULL ... +// // ... process data if not NULL ... // // ... x = width, y = height, n = # 8-bit components per pixel ... // // ... replace '0' with '1'..'4' to force that many components per pixel // // ... but 'n' will always be the number that it would have been if you said 0 @@ -94,14 +243,16 @@ // int req_comp -- if non-zero, # of image components requested in result // // The return value from an image loader is an 'unsigned char *' which points -// to the pixel data. The pixel data consists of *y scanlines of *x pixels, +// to the pixel data, or NULL on an allocation failure or if the image is +// corrupt or invalid. The pixel data consists of *y scanlines of *x pixels, // with each pixel consisting of N interleaved 8-bit components; the first // pixel pointed to is top-left-most in the image. There is no padding between // image scanlines or between pixels, regardless of format. The number of // components N is 'req_comp' if req_comp is non-zero, or *comp otherwise. // If req_comp is non-zero, *comp has the number of components that _would_ // have been output otherwise. E.g. if you set req_comp to 4, you will always -// get RGBA output, but you can check *comp to easily see if it's opaque. +// get RGBA output, but you can check *comp to see if it's trivially opaque +// because e.g. there were only 3 channels in the source image. // // An output image with N components has the following components interleaved // in this order in each pixel: @@ -123,18 +274,66 @@ // // =========================================================================== // -// iPhone PNG support: +// Philosophy // -// By default we convert iphone-formatted PNGs back to RGB; nominally they -// would silently load as BGR, except the existing code should have just -// failed on such iPhone PNGs. But you can disable this conversion by -// by calling stbi_convert_iphone_png_to_rgb(0), in which case -// you will always just get the native iphone "format" through. +// stb libraries are designed with the following priorities: // -// Call stbi_set_unpremultiply_on_load(1) as well to force a divide per -// pixel to remove any premultiplied alpha *only* if the image file explicitly -// says there's premultiplied data (currently only happens in iPhone images, -// and only if iPhone convert-to-rgb processing is on). +// 1. easy to use +// 2. easy to maintain +// 3. good performance +// +// Sometimes I let "good performance" creep up in priority over "easy to maintain", +// and for best performance I may provide less-easy-to-use APIs that give higher +// performance, in addition to the easy to use ones. Nevertheless, it's important +// to keep in mind that from the standpoint of you, a client of this library, +// all you care about is #1 and #3, and stb libraries do not emphasize #3 above all. +// +// Some secondary priorities arise directly from the first two, some of which +// make more explicit reasons why performance can't be emphasized. +// +// - Portable ("ease of use") +// - Small footprint ("easy to maintain") +// - No dependencies ("ease of use") +// +// =========================================================================== +// +// I/O callbacks +// +// I/O callbacks allow you to read from arbitrary sources, like packaged +// files or some other source. Data read from callbacks are processed +// through a small internal buffer (currently 128 bytes) to try to reduce +// overhead. +// +// The three functions you must define are "read" (reads some bytes of data), +// "skip" (skips some bytes of data), "eof" (reports if the stream is at the end). +// +// =========================================================================== +// +// SIMD support +// +// The JPEG decoder will try to automatically use SIMD kernels on x86 when +// supported by the compiler. For ARM Neon support, you must explicitly +// request it. +// +// (The old do-it-yourself SIMD API is no longer supported in the current +// code.) +// +// On x86, SSE2 will automatically be used when available based on a run-time +// test; if not, the generic C versions are used as a fall-back. On ARM targets, +// the typical path is to have separate builds for NEON and non-NEON devices +// (at least this is true for iOS and Android). Therefore, the NEON support is +// toggled by a build flag: define STBI_NEON to get NEON loops. +// +// The output of the JPEG decoder is slightly different from versions where +// SIMD support was introduced (that is, for versions before 1.49). The +// difference is only +-1 in the 8-bit RGB channels, and only on a small +// fraction of pixels. You can force the pre-1.49 behavior by defining +// STBI_JPEG_OLD, but this will disable some of the SIMD decoding path +// and hence cost some performance. +// +// If for some reason you do not want to use any of SIMD code, or if +// you have issues compiling it, you can disable it entirely by +// defining STBI_NO_SIMD. // // =========================================================================== // @@ -157,7 +356,7 @@ // (linear) floats to preserve the full dynamic range: // // float *data = stbi_loadf(filename, &x, &y, &n, 0); -// +// // If you load LDR images through this interface, those images will // be promoted to floating point values, run through the inverse of // constants corresponding to the above: @@ -174,24 +373,22 @@ // // =========================================================================== // -// I/O callbacks +// iPhone PNG support: // -// I/O callbacks allow you to read from arbitrary sources, like packaged -// files or some other source. Data read from callbacks are processed -// through a small internal buffer (currently 128 bytes) to try to reduce -// overhead. +// By default we convert iphone-formatted PNGs back to RGB, even though +// they are internally encoded differently. You can disable this conversion +// by by calling stbi_convert_iphone_png_to_rgb(0), in which case +// you will always just get the native iphone "format" through (which +// is BGR stored in RGB). +// +// Call stbi_set_unpremultiply_on_load(1) as well to force a divide per +// pixel to remove any premultiplied alpha *only* if the image file explicitly +// says there's premultiplied data (currently only happens in iPhone images, +// and only if iPhone convert-to-rgb processing is on). // -// The three functions you must define are "read" (reads some bytes of data), -// "skip" (skips some bytes of data), "eof" (reports if the stream is at the end). #ifndef STBI_NO_STDIO - -#if defined(_MSC_VER) && _MSC_VER >= 1400 -#define _CRT_SECURE_NO_WARNINGS // suppress warnings about fopen() -#pragma warning(push) -#pragma warning(disable:4996) // suppress even more warnings about fopen() -#endif #include #endif // STBI_NO_STDIO @@ -228,41 +425,43 @@ extern "C" { // load image by filename, open file, or memory buffer // -STBIDEF stbi_uc *stbi_load_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *comp, int req_comp); - -#ifndef STBI_NO_STDIO -STBIDEF stbi_uc *stbi_load (char const *filename, int *x, int *y, int *comp, int req_comp); -STBIDEF stbi_uc *stbi_load_from_file (FILE *f, int *x, int *y, int *comp, int req_comp); -// for stbi_load_from_file, file pointer is left pointing immediately after image -#endif - typedef struct { - int (*read) (void *user,char *data,int size); // fill 'data' with 'size' bytes. return number of bytes actually read + int (*read) (void *user,char *data,int size); // fill 'data' with 'size' bytes. return number of bytes actually read void (*skip) (void *user,int n); // skip the next 'n' bytes, or 'unget' the last -n bytes if negative int (*eof) (void *user); // returns nonzero if we are at end of file/data } stbi_io_callbacks; -STBIDEF stbi_uc *stbi_load_from_callbacks (stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *comp, int req_comp); +STBIDEF stbi_uc *stbi_load (char const *filename, int *x, int *y, int *comp, int req_comp); +STBIDEF stbi_uc *stbi_load_from_memory (stbi_uc const *buffer, int len , int *x, int *y, int *comp, int req_comp); +STBIDEF stbi_uc *stbi_load_from_callbacks(stbi_io_callbacks const *clbk , void *user, int *x, int *y, int *comp, int req_comp); -#ifndef STBI_NO_HDR - STBIDEF float *stbi_loadf_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *comp, int req_comp); +#ifndef STBI_NO_STDIO +STBIDEF stbi_uc *stbi_load_from_file (FILE *f, int *x, int *y, int *comp, int req_comp); +// for stbi_load_from_file, file pointer is left pointing immediately after image +#endif + +#ifndef STBI_NO_LINEAR + STBIDEF float *stbi_loadf (char const *filename, int *x, int *y, int *comp, int req_comp); + STBIDEF float *stbi_loadf_from_memory (stbi_uc const *buffer, int len, int *x, int *y, int *comp, int req_comp); + STBIDEF float *stbi_loadf_from_callbacks (stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *comp, int req_comp); #ifndef STBI_NO_STDIO - STBIDEF float *stbi_loadf (char const *filename, int *x, int *y, int *comp, int req_comp); STBIDEF float *stbi_loadf_from_file (FILE *f, int *x, int *y, int *comp, int req_comp); #endif - - STBIDEF float *stbi_loadf_from_callbacks (stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *comp, int req_comp); +#endif +#ifndef STBI_NO_HDR STBIDEF void stbi_hdr_to_ldr_gamma(float gamma); STBIDEF void stbi_hdr_to_ldr_scale(float scale); +#endif // STBI_NO_HDR +#ifndef STBI_NO_LINEAR STBIDEF void stbi_ldr_to_hdr_gamma(float gamma); STBIDEF void stbi_ldr_to_hdr_scale(float scale); -#endif // STBI_NO_HDR +#endif // STBI_NO_LINEAR -// stbi_is_hdr is always defined +// stbi_is_hdr is always defined, but always returns false if STBI_NO_HDR STBIDEF int stbi_is_hdr_from_callbacks(stbi_io_callbacks const *clbk, void *user); STBIDEF int stbi_is_hdr_from_memory(stbi_uc const *buffer, int len); #ifndef STBI_NO_STDIO @@ -273,7 +472,7 @@ STBIDEF int stbi_is_hdr_from_file(FILE *f); // get a VERY brief reason for failure // NOT THREADSAFE -STBIDEF const char *stbi_failure_reason (void); +STBIDEF const char *stbi_failure_reason (void); // free the loaded image -- this is just free() STBIDEF void stbi_image_free (void *retval_from_stbi_load); @@ -299,6 +498,8 @@ STBIDEF void stbi_set_unpremultiply_on_load(int flag_true_if_should_unpremultipl // or just pass them through "as-is" STBIDEF void stbi_convert_iphone_png_to_rgb(int flag_true_if_should_convert); +// flip the image vertically, so the first pixel in the output array is the bottom left +STBIDEF void stbi_set_flip_vertically_on_load(int flag_true_if_should_flip); // ZLIB client - used by PNG, available for other purposes @@ -311,26 +512,6 @@ STBIDEF char *stbi_zlib_decode_noheader_malloc(const char *buffer, int len, int STBIDEF int stbi_zlib_decode_noheader_buffer(char *obuffer, int olen, const char *ibuffer, int ilen); -// define faster low-level operations (typically SIMD support) -#ifdef STBI_SIMD -typedef void (*stbi_idct_8x8)(stbi_uc *out, int out_stride, short data[64], unsigned short *dequantize); -// compute an integer IDCT on "input" -// input[x] = data[x] * dequantize[x] -// write results to 'out': 64 samples, each run of 8 spaced by 'out_stride' -// CLAMP results to 0..255 -typedef void (*stbi_YCbCr_to_RGB_run)(stbi_uc *output, stbi_uc const *y, stbi_uc const *cb, stbi_uc const *cr, int count, int step); -// compute a conversion from YCbCr to RGB -// 'count' pixels -// write pixels to 'output'; each pixel is 'step' bytes (either 3 or 4; if 4, write '255' as 4th), order R,G,B -// y: Y input channel -// cb: Cb input channel; scale/biased to be 0..255 -// cr: Cr input channel; scale/biased to be 0..255 - -STBIDEF void stbi_install_idct(stbi_idct_8x8 func); -STBIDEF void stbi_install_YCbCr_to_RGB(stbi_YCbCr_to_RGB_run func); -#endif // STBI_SIMD - - #ifdef __cplusplus } #endif @@ -342,19 +523,62 @@ STBIDEF void stbi_install_YCbCr_to_RGB(stbi_YCbCr_to_RGB_run func); #ifdef STB_IMAGE_IMPLEMENTATION -#ifndef STBI_NO_HDR +#if defined(STBI_ONLY_JPEG) || defined(STBI_ONLY_PNG) || defined(STBI_ONLY_BMP) \ + || defined(STBI_ONLY_TGA) || defined(STBI_ONLY_GIF) || defined(STBI_ONLY_PSD) \ + || defined(STBI_ONLY_HDR) || defined(STBI_ONLY_PIC) || defined(STBI_ONLY_PNM) \ + || defined(STBI_ONLY_ZLIB) + #ifndef STBI_ONLY_JPEG + #define STBI_NO_JPEG + #endif + #ifndef STBI_ONLY_PNG + #define STBI_NO_PNG + #endif + #ifndef STBI_ONLY_BMP + #define STBI_NO_BMP + #endif + #ifndef STBI_ONLY_PSD + #define STBI_NO_PSD + #endif + #ifndef STBI_ONLY_TGA + #define STBI_NO_TGA + #endif + #ifndef STBI_ONLY_GIF + #define STBI_NO_GIF + #endif + #ifndef STBI_ONLY_HDR + #define STBI_NO_HDR + #endif + #ifndef STBI_ONLY_PIC + #define STBI_NO_PIC + #endif + #ifndef STBI_ONLY_PNM + #define STBI_NO_PNM + #endif +#endif + +#if defined(STBI_NO_PNG) && !defined(STBI_SUPPORT_ZLIB) && !defined(STBI_NO_ZLIB) +#define STBI_NO_ZLIB +#endif + + +#include +#include // ptrdiff_t on osx +#include +#include + +#if !defined(STBI_NO_LINEAR) || !defined(STBI_NO_HDR) #include // ldexp -#include // strcmp, strtok #endif #ifndef STBI_NO_STDIO #include #endif -#include -#include + +#ifndef STBI_ASSERT #include -#include -#include // ptrdiff_t on osx +#define STBI_ASSERT(x) assert(x) +#endif + #ifndef _MSC_VER #ifdef __cplusplus @@ -399,6 +623,121 @@ typedef unsigned char validate_uint32[sizeof(stbi__uint32)==4 ? 1 : -1]; #define stbi_lrot(x,y) (((x) << (y)) | ((x) >> (32 - (y)))) #endif +#if defined(STBI_MALLOC) && defined(STBI_FREE) && (defined(STBI_REALLOC) || defined(STBI_REALLOC_SIZED)) +// ok +#elif !defined(STBI_MALLOC) && !defined(STBI_FREE) && !defined(STBI_REALLOC) && !defined(STBI_REALLOC_SIZED) +// ok +#else +#error "Must define all or none of STBI_MALLOC, STBI_FREE, and STBI_REALLOC (or STBI_REALLOC_SIZED)." +#endif + +#ifndef STBI_MALLOC +#define STBI_MALLOC(sz) malloc(sz) +#define STBI_REALLOC(p,newsz) realloc(p,newsz) +#define STBI_FREE(p) free(p) +#endif + +#ifndef STBI_REALLOC_SIZED +#define STBI_REALLOC_SIZED(p,oldsz,newsz) STBI_REALLOC(p,newsz) +#endif + +// x86/x64 detection +#if defined(__x86_64__) || defined(_M_X64) +#define STBI__X64_TARGET +#elif defined(__i386) || defined(_M_IX86) +#define STBI__X86_TARGET +#endif + +#if defined(__GNUC__) && (defined(STBI__X86_TARGET) || defined(STBI__X64_TARGET)) && !defined(__SSE2__) && !defined(STBI_NO_SIMD) +// NOTE: not clear do we actually need this for the 64-bit path? +// gcc doesn't support sse2 intrinsics unless you compile with -msse2, +// (but compiling with -msse2 allows the compiler to use SSE2 everywhere; +// this is just broken and gcc are jerks for not fixing it properly +// http://www.virtualdub.org/blog/pivot/entry.php?id=363 ) +#define STBI_NO_SIMD +#endif + +#if defined(__MINGW32__) && defined(STBI__X86_TARGET) && !defined(STBI_MINGW_ENABLE_SSE2) && !defined(STBI_NO_SIMD) +// Note that __MINGW32__ doesn't actually mean 32-bit, so we have to avoid STBI__X64_TARGET +// +// 32-bit MinGW wants ESP to be 16-byte aligned, but this is not in the +// Windows ABI and VC++ as well as Windows DLLs don't maintain that invariant. +// As a result, enabling SSE2 on 32-bit MinGW is dangerous when not +// simultaneously enabling "-mstackrealign". +// +// See https://github.com/nothings/stb/issues/81 for more information. +// +// So default to no SSE2 on 32-bit MinGW. If you've read this far and added +// -mstackrealign to your build settings, feel free to #define STBI_MINGW_ENABLE_SSE2. +#define STBI_NO_SIMD +#endif + +#if !defined(STBI_NO_SIMD) && defined(STBI__X86_TARGET) +#define STBI_SSE2 +#include + +#ifdef _MSC_VER + +#if _MSC_VER >= 1400 // not VC6 +#include // __cpuid +static int stbi__cpuid3(void) +{ + int info[4]; + __cpuid(info,1); + return info[3]; +} +#else +static int stbi__cpuid3(void) +{ + int res; + __asm { + mov eax,1 + cpuid + mov res,edx + } + return res; +} +#endif + +#define STBI_SIMD_ALIGN(type, name) __declspec(align(16)) type name + +static int stbi__sse2_available() +{ + int info3 = stbi__cpuid3(); + return ((info3 >> 26) & 1) != 0; +} +#else // assume GCC-style if not VC++ +#define STBI_SIMD_ALIGN(type, name) type name __attribute__((aligned(16))) + +static int stbi__sse2_available() +{ +#if defined(__GNUC__) && (__GNUC__ * 100 + __GNUC_MINOR__) >= 408 // GCC 4.8 or later + // GCC 4.8+ has a nice way to do this + return __builtin_cpu_supports("sse2"); +#else + // portable way to do this, preferably without using GCC inline ASM? + // just bail for now. + return 0; +#endif +} +#endif +#endif + +// ARM NEON +#if defined(STBI_NO_SIMD) && defined(STBI_NEON) +#undef STBI_NEON +#endif + +#ifdef STBI_NEON +#include +// assume GCC or Clang on ARM targets +#define STBI_SIMD_ALIGN(type, name) type name __attribute__((aligned(16))) +#endif + +#ifndef STBI_SIMD_ALIGN +#define STBI_SIMD_ALIGN(type, name) type name +#endif + /////////////////////////////////////////////// // // stbi__context struct and start_xxx functions @@ -409,7 +748,7 @@ typedef struct { stbi__uint32 img_x, img_y; int img_n, img_out_n; - + stbi_io_callbacks io; void *io_user_data; @@ -418,19 +757,19 @@ typedef struct stbi_uc buffer_start[128]; stbi_uc *img_buffer, *img_buffer_end; - stbi_uc *img_buffer_original; + stbi_uc *img_buffer_original, *img_buffer_original_end; } stbi__context; static void stbi__refill_buffer(stbi__context *s); -// initialize a memory-stbi__jpeg_huff_decode context +// initialize a memory-decode context static void stbi__start_mem(stbi__context *s, stbi_uc const *buffer, int len) { s->io.read = NULL; s->read_from_callbacks = 0; s->img_buffer = s->img_buffer_original = (stbi_uc *) buffer; - s->img_buffer_end = (stbi_uc *) buffer+len; + s->img_buffer_end = s->img_buffer_original_end = (stbi_uc *) buffer+len; } // initialize a callback-based context @@ -442,6 +781,7 @@ static void stbi__start_callbacks(stbi__context *s, stbi_io_callbacks *c, void * s->read_from_callbacks = 1; s->img_buffer_original = s->buffer_start; stbi__refill_buffer(s); + s->img_buffer_original_end = s->img_buffer_end; } #ifndef STBI_NO_STDIO @@ -483,31 +823,62 @@ static void stbi__rewind(stbi__context *s) // but we just rewind to the beginning of the initial buffer, because // we only use it after doing 'test', which only ever looks at at most 92 bytes s->img_buffer = s->img_buffer_original; + s->img_buffer_end = s->img_buffer_original_end; } +#ifndef STBI_NO_JPEG static int stbi__jpeg_test(stbi__context *s); static stbi_uc *stbi__jpeg_load(stbi__context *s, int *x, int *y, int *comp, int req_comp); static int stbi__jpeg_info(stbi__context *s, int *x, int *y, int *comp); +#endif + +#ifndef STBI_NO_PNG static int stbi__png_test(stbi__context *s); static stbi_uc *stbi__png_load(stbi__context *s, int *x, int *y, int *comp, int req_comp); static int stbi__png_info(stbi__context *s, int *x, int *y, int *comp); +#endif + +#ifndef STBI_NO_BMP static int stbi__bmp_test(stbi__context *s); static stbi_uc *stbi__bmp_load(stbi__context *s, int *x, int *y, int *comp, int req_comp); +static int stbi__bmp_info(stbi__context *s, int *x, int *y, int *comp); +#endif + +#ifndef STBI_NO_TGA static int stbi__tga_test(stbi__context *s); static stbi_uc *stbi__tga_load(stbi__context *s, int *x, int *y, int *comp, int req_comp); static int stbi__tga_info(stbi__context *s, int *x, int *y, int *comp); +#endif + +#ifndef STBI_NO_PSD static int stbi__psd_test(stbi__context *s); static stbi_uc *stbi__psd_load(stbi__context *s, int *x, int *y, int *comp, int req_comp); +static int stbi__psd_info(stbi__context *s, int *x, int *y, int *comp); +#endif + #ifndef STBI_NO_HDR static int stbi__hdr_test(stbi__context *s); static float *stbi__hdr_load(stbi__context *s, int *x, int *y, int *comp, int req_comp); +static int stbi__hdr_info(stbi__context *s, int *x, int *y, int *comp); #endif + +#ifndef STBI_NO_PIC static int stbi__pic_test(stbi__context *s); static stbi_uc *stbi__pic_load(stbi__context *s, int *x, int *y, int *comp, int req_comp); +static int stbi__pic_info(stbi__context *s, int *x, int *y, int *comp); +#endif + +#ifndef STBI_NO_GIF static int stbi__gif_test(stbi__context *s); static stbi_uc *stbi__gif_load(stbi__context *s, int *x, int *y, int *comp, int req_comp); static int stbi__gif_info(stbi__context *s, int *x, int *y, int *comp); +#endif +#ifndef STBI_NO_PNM +static int stbi__pnm_test(stbi__context *s); +static stbi_uc *stbi__pnm_load(stbi__context *s, int *x, int *y, int *comp, int req_comp); +static int stbi__pnm_info(stbi__context *s, int *x, int *y, int *comp); +#endif // this is not threadsafe static const char *stbi__g_failure_reason; @@ -523,6 +894,11 @@ static int stbi__err(const char *str) return 0; } +static void *stbi__malloc(size_t size) +{ + return STBI_MALLOC(size); +} + // stbi__err - error // stbi__errpf - error returning pointer to float // stbi__errpuc - error returning pointer to unsigned char @@ -535,27 +911,52 @@ static int stbi__err(const char *str) #define stbi__err(x,y) stbi__err(x) #endif -#define stbi__errpf(x,y) ((float *) (stbi__err(x,y)?NULL:NULL)) -#define stbi__errpuc(x,y) ((unsigned char *) (stbi__err(x,y)?NULL:NULL)) +#define stbi__errpf(x,y) ((float *)(size_t) (stbi__err(x,y)?NULL:NULL)) +#define stbi__errpuc(x,y) ((unsigned char *)(size_t) (stbi__err(x,y)?NULL:NULL)) STBIDEF void stbi_image_free(void *retval_from_stbi_load) { - free(retval_from_stbi_load); + STBI_FREE(retval_from_stbi_load); } -#ifndef STBI_NO_HDR +#ifndef STBI_NO_LINEAR static float *stbi__ldr_to_hdr(stbi_uc *data, int x, int y, int comp); +#endif + +#ifndef STBI_NO_HDR static stbi_uc *stbi__hdr_to_ldr(float *data, int x, int y, int comp); #endif -static unsigned char *stbi_load_main(stbi__context *s, int *x, int *y, int *comp, int req_comp) +static int stbi__vertically_flip_on_load = 0; + +STBIDEF void stbi_set_flip_vertically_on_load(int flag_true_if_should_flip) +{ + stbi__vertically_flip_on_load = flag_true_if_should_flip; +} + +static unsigned char *stbi__load_main(stbi__context *s, int *x, int *y, int *comp, int req_comp) { + #ifndef STBI_NO_JPEG if (stbi__jpeg_test(s)) return stbi__jpeg_load(s,x,y,comp,req_comp); + #endif + #ifndef STBI_NO_PNG if (stbi__png_test(s)) return stbi__png_load(s,x,y,comp,req_comp); + #endif + #ifndef STBI_NO_BMP if (stbi__bmp_test(s)) return stbi__bmp_load(s,x,y,comp,req_comp); + #endif + #ifndef STBI_NO_GIF if (stbi__gif_test(s)) return stbi__gif_load(s,x,y,comp,req_comp); + #endif + #ifndef STBI_NO_PSD if (stbi__psd_test(s)) return stbi__psd_load(s,x,y,comp,req_comp); + #endif + #ifndef STBI_NO_PIC if (stbi__pic_test(s)) return stbi__pic_load(s,x,y,comp,req_comp); + #endif + #ifndef STBI_NO_PNM + if (stbi__pnm_test(s)) return stbi__pnm_load(s,x,y,comp,req_comp); + #endif #ifndef STBI_NO_HDR if (stbi__hdr_test(s)) { @@ -564,16 +965,81 @@ static unsigned char *stbi_load_main(stbi__context *s, int *x, int *y, int *comp } #endif + #ifndef STBI_NO_TGA // test tga last because it's a crappy test! if (stbi__tga_test(s)) return stbi__tga_load(s,x,y,comp,req_comp); + #endif + return stbi__errpuc("unknown image type", "Image not of any known type, or corrupt"); } +static unsigned char *stbi__load_flip(stbi__context *s, int *x, int *y, int *comp, int req_comp) +{ + unsigned char *result = stbi__load_main(s, x, y, comp, req_comp); + + if (stbi__vertically_flip_on_load && result != NULL) { + int w = *x, h = *y; + int depth = req_comp ? req_comp : *comp; + int row,col,z; + stbi_uc temp; + + // @OPTIMIZE: use a bigger temp buffer and memcpy multiple pixels at once + for (row = 0; row < (h>>1); row++) { + for (col = 0; col < w; col++) { + for (z = 0; z < depth; z++) { + temp = result[(row * w + col) * depth + z]; + result[(row * w + col) * depth + z] = result[((h - row - 1) * w + col) * depth + z]; + result[((h - row - 1) * w + col) * depth + z] = temp; + } + } + } + } + + return result; +} + +#ifndef STBI_NO_HDR +static void stbi__float_postprocess(float *result, int *x, int *y, int *comp, int req_comp) +{ + if (stbi__vertically_flip_on_load && result != NULL) { + int w = *x, h = *y; + int depth = req_comp ? req_comp : *comp; + int row,col,z; + float temp; + + // @OPTIMIZE: use a bigger temp buffer and memcpy multiple pixels at once + for (row = 0; row < (h>>1); row++) { + for (col = 0; col < w; col++) { + for (z = 0; z < depth; z++) { + temp = result[(row * w + col) * depth + z]; + result[(row * w + col) * depth + z] = result[((h - row - 1) * w + col) * depth + z]; + result[((h - row - 1) * w + col) * depth + z] = temp; + } + } + } + } +} +#endif + #ifndef STBI_NO_STDIO -STBIDEF unsigned char *stbi_load(char const *filename, int *x, int *y, int *comp, int req_comp) + +static FILE *stbi__fopen(char const *filename, char const *mode) { - FILE *f = fopen(filename, "rb"); + FILE *f; +#if defined(_MSC_VER) && _MSC_VER >= 1400 + if (0 != fopen_s(&f, filename, mode)) + f=0; +#else + f = fopen(filename, mode); +#endif + return f; +} + + +STBIDEF stbi_uc *stbi_load(char const *filename, int *x, int *y, int *comp, int req_comp) +{ + FILE *f = stbi__fopen(filename, "rb"); unsigned char *result; if (!f) return stbi__errpuc("can't fopen", "Unable to open file"); result = stbi_load_from_file(f,x,y,comp,req_comp); @@ -581,12 +1047,12 @@ STBIDEF unsigned char *stbi_load(char const *filename, int *x, int *y, int *comp return result; } -STBIDEF unsigned char *stbi_load_from_file(FILE *f, int *x, int *y, int *comp, int req_comp) +STBIDEF stbi_uc *stbi_load_from_file(FILE *f, int *x, int *y, int *comp, int req_comp) { unsigned char *result; stbi__context s; stbi__start_file(&s,f); - result = stbi_load_main(&s,x,y,comp,req_comp); + result = stbi__load_flip(&s,x,y,comp,req_comp); if (result) { // need to 'unget' all the characters in the IO buffer fseek(f, - (int) (s.img_buffer_end - s.img_buffer), SEEK_CUR); @@ -595,75 +1061,78 @@ STBIDEF unsigned char *stbi_load_from_file(FILE *f, int *x, int *y, int *comp, i } #endif //!STBI_NO_STDIO -STBIDEF unsigned char *stbi_load_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *comp, int req_comp) +STBIDEF stbi_uc *stbi_load_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *comp, int req_comp) { stbi__context s; stbi__start_mem(&s,buffer,len); - return stbi_load_main(&s,x,y,comp,req_comp); + return stbi__load_flip(&s,x,y,comp,req_comp); } -unsigned char *stbi_load_from_callbacks(stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *comp, int req_comp) +STBIDEF stbi_uc *stbi_load_from_callbacks(stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *comp, int req_comp) { stbi__context s; stbi__start_callbacks(&s, (stbi_io_callbacks *) clbk, user); - return stbi_load_main(&s,x,y,comp,req_comp); + return stbi__load_flip(&s,x,y,comp,req_comp); } -#ifndef STBI_NO_HDR - -float *stbi_loadf_main(stbi__context *s, int *x, int *y, int *comp, int req_comp) +#ifndef STBI_NO_LINEAR +static float *stbi__loadf_main(stbi__context *s, int *x, int *y, int *comp, int req_comp) { unsigned char *data; #ifndef STBI_NO_HDR - if (stbi__hdr_test(s)) - return stbi__hdr_load(s,x,y,comp,req_comp); + if (stbi__hdr_test(s)) { + float *hdr_data = stbi__hdr_load(s,x,y,comp,req_comp); + if (hdr_data) + stbi__float_postprocess(hdr_data,x,y,comp,req_comp); + return hdr_data; + } #endif - data = stbi_load_main(s, x, y, comp, req_comp); + data = stbi__load_flip(s, x, y, comp, req_comp); if (data) return stbi__ldr_to_hdr(data, *x, *y, req_comp ? req_comp : *comp); return stbi__errpf("unknown image type", "Image not of any known type, or corrupt"); } -float *stbi_loadf_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *comp, int req_comp) +STBIDEF float *stbi_loadf_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *comp, int req_comp) { stbi__context s; stbi__start_mem(&s,buffer,len); - return stbi_loadf_main(&s,x,y,comp,req_comp); + return stbi__loadf_main(&s,x,y,comp,req_comp); } -float *stbi_loadf_from_callbacks(stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *comp, int req_comp) +STBIDEF float *stbi_loadf_from_callbacks(stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *comp, int req_comp) { stbi__context s; stbi__start_callbacks(&s, (stbi_io_callbacks *) clbk, user); - return stbi_loadf_main(&s,x,y,comp,req_comp); + return stbi__loadf_main(&s,x,y,comp,req_comp); } #ifndef STBI_NO_STDIO -float *stbi_loadf(char const *filename, int *x, int *y, int *comp, int req_comp) +STBIDEF float *stbi_loadf(char const *filename, int *x, int *y, int *comp, int req_comp) { - FILE *f = fopen(filename, "rb"); float *result; + FILE *f = stbi__fopen(filename, "rb"); if (!f) return stbi__errpf("can't fopen", "Unable to open file"); result = stbi_loadf_from_file(f,x,y,comp,req_comp); fclose(f); return result; } -float *stbi_loadf_from_file(FILE *f, int *x, int *y, int *comp, int req_comp) +STBIDEF float *stbi_loadf_from_file(FILE *f, int *x, int *y, int *comp, int req_comp) { stbi__context s; stbi__start_file(&s,f); - return stbi_loadf_main(&s,x,y,comp,req_comp); + return stbi__loadf_main(&s,x,y,comp,req_comp); } #endif // !STBI_NO_STDIO -#endif // !STBI_NO_HDR +#endif // !STBI_NO_LINEAR -// these is-hdr-or-not is defined independent of whether STBI_NO_HDR is -// defined, for API simplicity; if STBI_NO_HDR is defined, it always +// these is-hdr-or-not is defined independent of whether STBI_NO_LINEAR is +// defined, for API simplicity; if STBI_NO_LINEAR is defined, it always // reports false! -int stbi_is_hdr_from_memory(stbi_uc const *buffer, int len) +STBIDEF int stbi_is_hdr_from_memory(stbi_uc const *buffer, int len) { #ifndef STBI_NO_HDR stbi__context s; @@ -679,7 +1148,7 @@ int stbi_is_hdr_from_memory(stbi_uc const *buffer, int len) #ifndef STBI_NO_STDIO STBIDEF int stbi_is_hdr (char const *filename) { - FILE *f = fopen(filename, "rb"); + FILE *f = stbi__fopen(filename, "rb"); int result=0; if (f) { result = stbi_is_hdr_from_file(f); @@ -695,6 +1164,7 @@ STBIDEF int stbi_is_hdr_from_file(FILE *f) stbi__start_file(&s,f); return stbi__hdr_test(&s); #else + STBI_NOTUSED(f); return 0; #endif } @@ -707,21 +1177,24 @@ STBIDEF int stbi_is_hdr_from_callbacks(stbi_io_callbacks const *clbk, void stbi__start_callbacks(&s, (stbi_io_callbacks *) clbk, user); return stbi__hdr_test(&s); #else + STBI_NOTUSED(clbk); + STBI_NOTUSED(user); return 0; #endif } -#ifndef STBI_NO_HDR -static float stbi__h2l_gamma_i=1.0f/2.2f, stbi__h2l_scale_i=1.0f; +#ifndef STBI_NO_LINEAR static float stbi__l2h_gamma=2.2f, stbi__l2h_scale=1.0f; -void stbi_hdr_to_ldr_gamma(float gamma) { stbi__h2l_gamma_i = 1/gamma; } -void stbi_hdr_to_ldr_scale(float scale) { stbi__h2l_scale_i = 1/scale; } - -void stbi_ldr_to_hdr_gamma(float gamma) { stbi__l2h_gamma = gamma; } -void stbi_ldr_to_hdr_scale(float scale) { stbi__l2h_scale = scale; } +STBIDEF void stbi_ldr_to_hdr_gamma(float gamma) { stbi__l2h_gamma = gamma; } +STBIDEF void stbi_ldr_to_hdr_scale(float scale) { stbi__l2h_scale = scale; } #endif +static float stbi__h2l_gamma_i=1.0f/2.2f, stbi__h2l_scale_i=1.0f; + +STBIDEF void stbi_hdr_to_ldr_gamma(float gamma) { stbi__h2l_gamma_i = 1/gamma; } +STBIDEF void stbi_hdr_to_ldr_scale(float scale) { stbi__h2l_scale_i = 1/scale; } + ////////////////////////////////////////////////////////////////////////////// // @@ -730,9 +1203,9 @@ void stbi_ldr_to_hdr_scale(float scale) { stbi__l2h_scale = scale; } enum { - SCAN_load=0, - SCAN_type, - SCAN_header + STBI__SCAN_load=0, + STBI__SCAN_type, + STBI__SCAN_header }; static void stbi__refill_buffer(stbi__context *s) @@ -740,7 +1213,7 @@ static void stbi__refill_buffer(stbi__context *s) int n = (s->io.read)(s->io_user_data,(char*)s->buffer_start,s->buflen); if (n == 0) { // at end of file, treat same as if from memory, but need to handle case - // where s->img_buffer isn't pointing to safe memory, stbi__err.g. 0-byte file + // where s->img_buffer isn't pointing to safe memory, e.g. 0-byte file s->read_from_callbacks = 0; s->img_buffer = s->buffer_start; s->img_buffer_end = s->buffer_start+1; @@ -771,11 +1244,15 @@ stbi_inline static int stbi__at_eof(stbi__context *s) if (s->read_from_callbacks == 0) return 1; } - return s->img_buffer >= s->img_buffer_end; + return s->img_buffer >= s->img_buffer_end; } static void stbi__skip(stbi__context *s, int n) { + if (n < 0) { + s->img_buffer = s->img_buffer_end; + return; + } if (s->io.read) { int blen = (int) (s->img_buffer_end - s->img_buffer); if (blen < n) { @@ -795,7 +1272,7 @@ static int stbi__getn(stbi__context *s, stbi_uc *buffer, int n) int res, count; memcpy(buffer, s->img_buffer, blen); - + count = (s->io.read)(s->io_user_data, (char*) buffer + blen, n - blen); res = (count == (n-blen)); s->img_buffer = s->img_buffer_end; @@ -823,22 +1300,31 @@ static stbi__uint32 stbi__get32be(stbi__context *s) return (z << 16) + stbi__get16be(s); } +#if defined(STBI_NO_BMP) && defined(STBI_NO_TGA) && defined(STBI_NO_GIF) +// nothing +#else static int stbi__get16le(stbi__context *s) { int z = stbi__get8(s); return z + (stbi__get8(s) << 8); } +#endif +#ifndef STBI_NO_BMP static stbi__uint32 stbi__get32le(stbi__context *s) { stbi__uint32 z = stbi__get16le(s); return z + (stbi__get16le(s) << 16); } +#endif + +#define STBI__BYTECAST(x) ((stbi_uc) ((x) & 255)) // truncate int to byte without warnings + ////////////////////////////////////////////////////////////////////////////// // // generic converter from built-in img_n to req_comp -// individual types do this automatically as much as possible (stbi__err.g. jpeg +// individual types do this automatically as much as possible (e.g. jpeg // does all cases internally since it needs to colorspace convert anyway, // and it never has alpha, so very few cases ). png can automatically // interleave an alpha=255 channel, but falls back to this for other cases @@ -857,11 +1343,11 @@ static unsigned char *stbi__convert_format(unsigned char *data, int img_n, int r unsigned char *good; if (req_comp == img_n) return data; - assert(req_comp >= 1 && req_comp <= 4); + STBI_ASSERT(req_comp >= 1 && req_comp <= 4); - good = (unsigned char *) malloc(req_comp * x * y); + good = (unsigned char *) stbi__malloc(req_comp * x * y); if (good == NULL) { - free(data); + STBI_FREE(data); return stbi__errpuc("outofmem", "Out of memory"); } @@ -886,21 +1372,21 @@ static unsigned char *stbi__convert_format(unsigned char *data, int img_n, int r CASE(4,1) dest[0]=stbi__compute_y(src[0],src[1],src[2]); break; CASE(4,2) dest[0]=stbi__compute_y(src[0],src[1],src[2]), dest[1] = src[3]; break; CASE(4,3) dest[0]=src[0],dest[1]=src[1],dest[2]=src[2]; break; - default: assert(0); + default: STBI_ASSERT(0); } #undef CASE } - free(data); + STBI_FREE(data); return good; } -#ifndef STBI_NO_HDR +#ifndef STBI_NO_LINEAR static float *stbi__ldr_to_hdr(stbi_uc *data, int x, int y, int comp) { int i,k,n; - float *output = (float *) malloc(x * y * comp * sizeof(float)); - if (output == NULL) { free(data); return stbi__errpf("outofmem", "Out of memory"); } + float *output = (float *) stbi__malloc(x * y * comp * sizeof(float)); + if (output == NULL) { STBI_FREE(data); return stbi__errpf("outofmem", "Out of memory"); } // compute number of non-alpha components if (comp & 1) n = comp; else n = comp-1; for (i=0; i < x*y; ++i) { @@ -909,16 +1395,18 @@ static float *stbi__ldr_to_hdr(stbi_uc *data, int x, int y, int comp) } if (k < comp) output[i*comp + k] = data[i*comp+k]/255.0f; } - free(data); + STBI_FREE(data); return output; } +#endif +#ifndef STBI_NO_HDR #define stbi__float2int(x) ((int) (x)) static stbi_uc *stbi__hdr_to_ldr(float *data, int x, int y, int comp) { int i,k,n; - stbi_uc *output = (stbi_uc *) malloc(x * y * comp); - if (output == NULL) { free(data); return stbi__errpuc("outofmem", "Out of memory"); } + stbi_uc *output = (stbi_uc *) stbi__malloc(x * y * comp); + if (output == NULL) { STBI_FREE(data); return stbi__errpuc("outofmem", "Out of memory"); } // compute number of non-alpha components if (comp & 1) n = comp; else n = comp-1; for (i=0; i < x*y; ++i) { @@ -935,17 +1423,16 @@ static stbi_uc *stbi__hdr_to_ldr(float *data, int x, int y, int comp) output[i*comp + k] = (stbi_uc) stbi__float2int(z); } } - free(data); + STBI_FREE(data); return output; } #endif ////////////////////////////////////////////////////////////////////////////// // -// "baseline" JPEG/JFIF decoder (not actually fully baseline implementation) +// "baseline" JPEG/JFIF decoder // // simple implementation -// - channel subsampling of at most 2 in each dimension // - doesn't support delayed output of y-dimension // - simple interface (only one output format: 8-bit interleaved RGB) // - doesn't try to recover corrupt jpegs @@ -959,13 +1446,10 @@ static stbi_uc *stbi__hdr_to_ldr(float *data, int x, int y, int comp) // - quality integer IDCT derived from IJG's 'slow' // performance // - fast huffman; reasonable integer IDCT +// - some SIMD kernels for common paths on targets with SSE2/NEON // - uses a lot of intermediate memory, could cache poorly -// - load http://nothings.org/remote/anemones.jpg 3 times on 2.8Ghz P4 -// stb_jpeg: 1.34 seconds (MSVC6, default release build) -// stb_jpeg: 1.06 seconds (MSVC6, processor = Pentium Pro) -// IJL11.dll: 1.08 seconds (compiled by intel) -// IJG 1998: 0.98 seconds (MSVC6, makefile provided by IJG) -// IJG 1998: 0.95 seconds (MSVC6, makefile + proc=PPro) + +#ifndef STBI_NO_JPEG // huffman decoding acceleration #define FAST_BITS 9 // larger handles more cases; smaller stomps less cache @@ -983,13 +1467,11 @@ typedef struct typedef struct { - #ifdef STBI_SIMD - unsigned short dequant2[4][64]; - #endif stbi__context *s; stbi__huffman huff_dc[4]; stbi__huffman huff_ac[4]; stbi_uc dequant[4][64]; + stbi__int16 fast_ac[4][1 << FAST_BITS]; // sizes for components, interleaved MCUs int img_h_max, img_v_max; @@ -1007,17 +1489,31 @@ typedef struct int x,y,w2,h2; stbi_uc *data; - void *raw_data; + void *raw_data, *raw_coeff; stbi_uc *linebuf; + short *coeff; // progressive only + int coeff_w, coeff_h; // number of 8x8 coefficient blocks } img_comp[4]; - stbi__uint32 code_buffer; // jpeg entropy-coded buffer + stbi__uint32 code_buffer; // jpeg entropy-coded buffer int code_bits; // number of valid bits unsigned char marker; // marker seen while filling entropy buffer int nomore; // flag if we saw a marker so must stop + int progressive; + int spec_start; + int spec_end; + int succ_high; + int succ_low; + int eob_run; + int scan_n, order[4]; int restart_interval, todo; + +// kernels + void (*idct_block_kernel)(stbi_uc *out, int out_stride, short data[64]); + void (*YCbCr_to_RGB_kernel)(stbi_uc *out, const stbi_uc *y, const stbi_uc *pcb, const stbi_uc *pcr, int count, int step); + stbi_uc *(*resample_row_hv_2_kernel)(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs); } stbi__jpeg; static int stbi__build_huffman(stbi__huffman *h, int *count) @@ -1061,6 +1557,33 @@ static int stbi__build_huffman(stbi__huffman *h, int *count) return 1; } +// build a table that decodes both magnitude and value of small ACs in +// one go. +static void stbi__build_fast_ac(stbi__int16 *fast_ac, stbi__huffman *h) +{ + int i; + for (i=0; i < (1 << FAST_BITS); ++i) { + stbi_uc fast = h->fast[i]; + fast_ac[i] = 0; + if (fast < 255) { + int rs = h->values[fast]; + int run = (rs >> 4) & 15; + int magbits = rs & 15; + int len = h->size[fast]; + + if (magbits && len + magbits <= FAST_BITS) { + // magnitude code followed by receive_extend code + int k = ((i << len) & ((1 << FAST_BITS) - 1)) >> (FAST_BITS - magbits); + int m = 1 << (magbits - 1); + if (k < m) k += (-1 << magbits) + 1; + // if the result is small enough, we can fit it in fast_ac table + if (k >= -128 && k <= 127) + fast_ac[i] = (stbi__int16) ((k << 8) + (run << 4) + (len + magbits)); + } + } + } +} + static void stbi__grow_buffer_unsafe(stbi__jpeg *j) { do { @@ -1081,7 +1604,7 @@ static void stbi__grow_buffer_unsafe(stbi__jpeg *j) // (1 << n) - 1 static stbi__uint32 stbi__bmask[17]={0,1,3,7,15,31,63,127,255,511,1023,2047,4095,8191,16383,32767,65535}; -// stbi__jpeg_huff_decode a jpeg huffman value from the bitstream +// decode a jpeg huffman value from the bitstream stbi_inline static int stbi__jpeg_huff_decode(stbi__jpeg *j, stbi__huffman *h) { unsigned int temp; @@ -1123,7 +1646,7 @@ stbi_inline static int stbi__jpeg_huff_decode(stbi__jpeg *j, stbi__huffman *h) // convert the huffman code to the symbol id c = ((j->code_buffer >> (32 - k)) & stbi__bmask[k]) + h->delta[k]; - assert((((j->code_buffer) >> (32 - h->size[c])) & stbi__bmask[h->size[c]]) == h->code[c]); + STBI_ASSERT((((j->code_buffer) >> (32 - h->size[c])) & stbi__bmask[h->size[c]]) == h->code[c]); // convert the id to a symbol j->code_bits -= k; @@ -1131,31 +1654,46 @@ stbi_inline static int stbi__jpeg_huff_decode(stbi__jpeg *j, stbi__huffman *h) return h->values[c]; } +// bias[n] = (-1<code_bits < n) stbi__grow_buffer_unsafe(j); - #if 1 + sgn = (stbi__int32)j->code_buffer >> 31; // sign bit is always in MSB k = stbi_lrot(j->code_buffer, n); + STBI_ASSERT(n >= 0 && n < (int) (sizeof(stbi__bmask)/sizeof(*stbi__bmask))); j->code_buffer = k & ~stbi__bmask[n]; k &= stbi__bmask[n]; j->code_bits -= n; - #else - k = (j->code_buffer >> (32 - n)) & stbi__bmask[n]; + return k + (stbi__jbias[n] & ~sgn); +} + +// get some unsigned bits +stbi_inline static int stbi__jpeg_get_bits(stbi__jpeg *j, int n) +{ + unsigned int k; + if (j->code_bits < n) stbi__grow_buffer_unsafe(j); + k = stbi_lrot(j->code_buffer, n); + j->code_buffer = k & ~stbi__bmask[n]; + k &= stbi__bmask[n]; j->code_bits -= n; - j->code_buffer <<= n; - #endif - // the following test is probably a random branch that won't - // predict well. I tried to table accelerate it but failed. - // maybe it's compiling as a conditional move? - if (k < m) - return (-1 << n) + k + 1; - else - return k; + return k; +} + +stbi_inline static int stbi__jpeg_get_bit(stbi__jpeg *j) +{ + unsigned int k; + if (j->code_bits < 1) stbi__grow_buffer_unsafe(j); + k = j->code_buffer; + j->code_buffer <<= 1; + --j->code_bits; + return k & 0x80000000; } // given a value that's at position X in the zigzag stream, @@ -1175,11 +1713,14 @@ static stbi_uc stbi__jpeg_dezigzag[64+15] = 63, 63, 63, 63, 63, 63, 63 }; -// stbi__jpeg_huff_decode one 64-entry block-- -static int stbi__jpeg_decode_block(stbi__jpeg *j, short data[64], stbi__huffman *hdc, stbi__huffman *hac, int b) +// decode one 64-entry block-- +static int stbi__jpeg_decode_block(stbi__jpeg *j, short data[64], stbi__huffman *hdc, stbi__huffman *hac, stbi__int16 *fac, int b, stbi_uc *dequant) { int diff,dc,k; - int t = stbi__jpeg_huff_decode(j, hdc); + int t; + + if (j->code_bits < 16) stbi__grow_buffer_unsafe(j); + t = stbi__jpeg_huff_decode(j, hdc); if (t < 0) return stbi__err("bad huffman code","Corrupt JPEG"); // 0 all the ac values now so we can do it 32-bits at a time @@ -1188,28 +1729,189 @@ static int stbi__jpeg_decode_block(stbi__jpeg *j, short data[64], stbi__huffman diff = t ? stbi__extend_receive(j, t) : 0; dc = j->img_comp[b].dc_pred + diff; j->img_comp[b].dc_pred = dc; - data[0] = (short) dc; + data[0] = (short) (dc * dequant[0]); - // stbi__jpeg_huff_decode AC components, see JPEG spec + // decode AC components, see JPEG spec k = 1; do { - int r,s; - int rs = stbi__jpeg_huff_decode(j, hac); - if (rs < 0) return stbi__err("bad huffman code","Corrupt JPEG"); - s = rs & 15; - r = rs >> 4; - if (s == 0) { - if (rs != 0xf0) break; // end block - k += 16; + unsigned int zig; + int c,r,s; + if (j->code_bits < 16) stbi__grow_buffer_unsafe(j); + c = (j->code_buffer >> (32 - FAST_BITS)) & ((1 << FAST_BITS)-1); + r = fac[c]; + if (r) { // fast-AC path + k += (r >> 4) & 15; // run + s = r & 15; // combined length + j->code_buffer <<= s; + j->code_bits -= s; + // decode into unzigzag'd location + zig = stbi__jpeg_dezigzag[k++]; + data[zig] = (short) ((r >> 8) * dequant[zig]); } else { - k += r; - // stbi__jpeg_huff_decode into unzigzag'd location - data[stbi__jpeg_dezigzag[k++]] = (short) stbi__extend_receive(j,s); + int rs = stbi__jpeg_huff_decode(j, hac); + if (rs < 0) return stbi__err("bad huffman code","Corrupt JPEG"); + s = rs & 15; + r = rs >> 4; + if (s == 0) { + if (rs != 0xf0) break; // end block + k += 16; + } else { + k += r; + // decode into unzigzag'd location + zig = stbi__jpeg_dezigzag[k++]; + data[zig] = (short) (stbi__extend_receive(j,s) * dequant[zig]); + } } } while (k < 64); return 1; } +static int stbi__jpeg_decode_block_prog_dc(stbi__jpeg *j, short data[64], stbi__huffman *hdc, int b) +{ + int diff,dc; + int t; + if (j->spec_end != 0) return stbi__err("can't merge dc and ac", "Corrupt JPEG"); + + if (j->code_bits < 16) stbi__grow_buffer_unsafe(j); + + if (j->succ_high == 0) { + // first scan for DC coefficient, must be first + memset(data,0,64*sizeof(data[0])); // 0 all the ac values now + t = stbi__jpeg_huff_decode(j, hdc); + diff = t ? stbi__extend_receive(j, t) : 0; + + dc = j->img_comp[b].dc_pred + diff; + j->img_comp[b].dc_pred = dc; + data[0] = (short) (dc << j->succ_low); + } else { + // refinement scan for DC coefficient + if (stbi__jpeg_get_bit(j)) + data[0] += (short) (1 << j->succ_low); + } + return 1; +} + +// @OPTIMIZE: store non-zigzagged during the decode passes, +// and only de-zigzag when dequantizing +static int stbi__jpeg_decode_block_prog_ac(stbi__jpeg *j, short data[64], stbi__huffman *hac, stbi__int16 *fac) +{ + int k; + if (j->spec_start == 0) return stbi__err("can't merge dc and ac", "Corrupt JPEG"); + + if (j->succ_high == 0) { + int shift = j->succ_low; + + if (j->eob_run) { + --j->eob_run; + return 1; + } + + k = j->spec_start; + do { + unsigned int zig; + int c,r,s; + if (j->code_bits < 16) stbi__grow_buffer_unsafe(j); + c = (j->code_buffer >> (32 - FAST_BITS)) & ((1 << FAST_BITS)-1); + r = fac[c]; + if (r) { // fast-AC path + k += (r >> 4) & 15; // run + s = r & 15; // combined length + j->code_buffer <<= s; + j->code_bits -= s; + zig = stbi__jpeg_dezigzag[k++]; + data[zig] = (short) ((r >> 8) << shift); + } else { + int rs = stbi__jpeg_huff_decode(j, hac); + if (rs < 0) return stbi__err("bad huffman code","Corrupt JPEG"); + s = rs & 15; + r = rs >> 4; + if (s == 0) { + if (r < 15) { + j->eob_run = (1 << r); + if (r) + j->eob_run += stbi__jpeg_get_bits(j, r); + --j->eob_run; + break; + } + k += 16; + } else { + k += r; + zig = stbi__jpeg_dezigzag[k++]; + data[zig] = (short) (stbi__extend_receive(j,s) << shift); + } + } + } while (k <= j->spec_end); + } else { + // refinement scan for these AC coefficients + + short bit = (short) (1 << j->succ_low); + + if (j->eob_run) { + --j->eob_run; + for (k = j->spec_start; k <= j->spec_end; ++k) { + short *p = &data[stbi__jpeg_dezigzag[k]]; + if (*p != 0) + if (stbi__jpeg_get_bit(j)) + if ((*p & bit)==0) { + if (*p > 0) + *p += bit; + else + *p -= bit; + } + } + } else { + k = j->spec_start; + do { + int r,s; + int rs = stbi__jpeg_huff_decode(j, hac); // @OPTIMIZE see if we can use the fast path here, advance-by-r is so slow, eh + if (rs < 0) return stbi__err("bad huffman code","Corrupt JPEG"); + s = rs & 15; + r = rs >> 4; + if (s == 0) { + if (r < 15) { + j->eob_run = (1 << r) - 1; + if (r) + j->eob_run += stbi__jpeg_get_bits(j, r); + r = 64; // force end of block + } else { + // r=15 s=0 should write 16 0s, so we just do + // a run of 15 0s and then write s (which is 0), + // so we don't have to do anything special here + } + } else { + if (s != 1) return stbi__err("bad huffman code", "Corrupt JPEG"); + // sign bit + if (stbi__jpeg_get_bit(j)) + s = bit; + else + s = -bit; + } + + // advance by r + while (k <= j->spec_end) { + short *p = &data[stbi__jpeg_dezigzag[k++]]; + if (*p != 0) { + if (stbi__jpeg_get_bit(j)) + if ((*p & bit)==0) { + if (*p > 0) + *p += bit; + else + *p -= bit; + } + } else { + if (r == 0) { + *p = (short) s; + break; + } + --r; + } + } + } while (k <= j->spec_end); + } + } + return 1; +} + // take a -128..127 value and stbi__clamp it and convert to 0..255 stbi_inline static stbi_uc stbi__clamp(int x) { @@ -1221,21 +1923,21 @@ stbi_inline static stbi_uc stbi__clamp(int x) return (stbi_uc) x; } -#define stbi__f2f(x) (int) (((x) * 4096 + 0.5)) +#define stbi__f2f(x) ((int) (((x) * 4096 + 0.5))) #define stbi__fsh(x) ((x) << 12) // derived from jidctint -- DCT_ISLOW -#define STBI__IDCT_1D(s0,s1,s2,s3,s4,s5,s6,s7) \ +#define STBI__IDCT_1D(s0,s1,s2,s3,s4,s5,s6,s7) \ int t0,t1,t2,t3,p1,p2,p3,p4,p5,x0,x1,x2,x3; \ p2 = s2; \ p3 = s6; \ - p1 = (p2+p3) * stbi__f2f(0.5411961f); \ - t2 = p1 + p3*stbi__f2f(-1.847759065f); \ - t3 = p1 + p2*stbi__f2f( 0.765366865f); \ + p1 = (p2+p3) * stbi__f2f(0.5411961f); \ + t2 = p1 + p3*stbi__f2f(-1.847759065f); \ + t3 = p1 + p2*stbi__f2f( 0.765366865f); \ p2 = s0; \ p3 = s4; \ - t0 = stbi__fsh(p2+p3); \ - t1 = stbi__fsh(p2-p3); \ + t0 = stbi__fsh(p2+p3); \ + t1 = stbi__fsh(p2-p3); \ x0 = t0+t3; \ x3 = t0-t3; \ x1 = t1+t2; \ @@ -1248,36 +1950,28 @@ stbi_inline static stbi_uc stbi__clamp(int x) p4 = t1+t3; \ p1 = t0+t3; \ p2 = t1+t2; \ - p5 = (p3+p4)*stbi__f2f( 1.175875602f); \ - t0 = t0*stbi__f2f( 0.298631336f); \ - t1 = t1*stbi__f2f( 2.053119869f); \ - t2 = t2*stbi__f2f( 3.072711026f); \ - t3 = t3*stbi__f2f( 1.501321110f); \ - p1 = p5 + p1*stbi__f2f(-0.899976223f); \ - p2 = p5 + p2*stbi__f2f(-2.562915447f); \ - p3 = p3*stbi__f2f(-1.961570560f); \ - p4 = p4*stbi__f2f(-0.390180644f); \ + p5 = (p3+p4)*stbi__f2f( 1.175875602f); \ + t0 = t0*stbi__f2f( 0.298631336f); \ + t1 = t1*stbi__f2f( 2.053119869f); \ + t2 = t2*stbi__f2f( 3.072711026f); \ + t3 = t3*stbi__f2f( 1.501321110f); \ + p1 = p5 + p1*stbi__f2f(-0.899976223f); \ + p2 = p5 + p2*stbi__f2f(-2.562915447f); \ + p3 = p3*stbi__f2f(-1.961570560f); \ + p4 = p4*stbi__f2f(-0.390180644f); \ t3 += p1+p4; \ t2 += p2+p3; \ t1 += p2+p4; \ t0 += p1+p3; -#ifdef STBI_SIMD -typedef unsigned short stbi_dequantize_t; -#else -typedef stbi_uc stbi_dequantize_t; -#endif - -// .344 seconds on 3*anemones.jpg -static void stbi__idct_block(stbi_uc *out, int out_stride, short data[64], stbi_dequantize_t *dequantize) +static void stbi__idct_block(stbi_uc *out, int out_stride, short data[64]) { int i,val[64],*v=val; - stbi_dequantize_t *dq = dequantize; stbi_uc *o; short *d = data; // columns - for (i=0; i < 8; ++i,++d,++dq, ++v) { + for (i=0; i < 8; ++i,++d, ++v) { // if all zeroes, shortcut -- this avoids dequantizing 0s and IDCTing if (d[ 8]==0 && d[16]==0 && d[24]==0 && d[32]==0 && d[40]==0 && d[48]==0 && d[56]==0) { @@ -1285,11 +1979,10 @@ static void stbi__idct_block(stbi_uc *out, int out_stride, short data[64], stbi_ // (1|2|3|4|5|6|7)==0 0 seconds // all separate -0.047 seconds // 1 && 2|3 && 4|5 && 6|7: -0.047 seconds - int dcterm = d[0] * dq[0] << 2; + int dcterm = d[0] << 2; v[0] = v[8] = v[16] = v[24] = v[32] = v[40] = v[48] = v[56] = dcterm; } else { - STBI__IDCT_1D(d[ 0]*dq[ 0],d[ 8]*dq[ 8],d[16]*dq[16],d[24]*dq[24], - d[32]*dq[32],d[40]*dq[40],d[48]*dq[48],d[56]*dq[56]) + STBI__IDCT_1D(d[ 0],d[ 8],d[16],d[24],d[32],d[40],d[48],d[56]) // constants scaled things up by 1<<12; let's bring them back // down, but keep 2 extra bits of precision x0 += 512; x1 += 512; x2 += 512; x3 += 512; @@ -1330,14 +2023,394 @@ static void stbi__idct_block(stbi_uc *out, int out_stride, short data[64], stbi_ } } -#ifdef STBI_SIMD -static stbi_idct_8x8 stbi__idct_installed = stbi__idct_block; +#ifdef STBI_SSE2 +// sse2 integer IDCT. not the fastest possible implementation but it +// produces bit-identical results to the generic C version so it's +// fully "transparent". +static void stbi__idct_simd(stbi_uc *out, int out_stride, short data[64]) +{ + // This is constructed to match our regular (generic) integer IDCT exactly. + __m128i row0, row1, row2, row3, row4, row5, row6, row7; + __m128i tmp; + + // dot product constant: even elems=x, odd elems=y + #define dct_const(x,y) _mm_setr_epi16((x),(y),(x),(y),(x),(y),(x),(y)) + + // out(0) = c0[even]*x + c0[odd]*y (c0, x, y 16-bit, out 32-bit) + // out(1) = c1[even]*x + c1[odd]*y + #define dct_rot(out0,out1, x,y,c0,c1) \ + __m128i c0##lo = _mm_unpacklo_epi16((x),(y)); \ + __m128i c0##hi = _mm_unpackhi_epi16((x),(y)); \ + __m128i out0##_l = _mm_madd_epi16(c0##lo, c0); \ + __m128i out0##_h = _mm_madd_epi16(c0##hi, c0); \ + __m128i out1##_l = _mm_madd_epi16(c0##lo, c1); \ + __m128i out1##_h = _mm_madd_epi16(c0##hi, c1) + + // out = in << 12 (in 16-bit, out 32-bit) + #define dct_widen(out, in) \ + __m128i out##_l = _mm_srai_epi32(_mm_unpacklo_epi16(_mm_setzero_si128(), (in)), 4); \ + __m128i out##_h = _mm_srai_epi32(_mm_unpackhi_epi16(_mm_setzero_si128(), (in)), 4) + + // wide add + #define dct_wadd(out, a, b) \ + __m128i out##_l = _mm_add_epi32(a##_l, b##_l); \ + __m128i out##_h = _mm_add_epi32(a##_h, b##_h) + + // wide sub + #define dct_wsub(out, a, b) \ + __m128i out##_l = _mm_sub_epi32(a##_l, b##_l); \ + __m128i out##_h = _mm_sub_epi32(a##_h, b##_h) + + // butterfly a/b, add bias, then shift by "s" and pack + #define dct_bfly32o(out0, out1, a,b,bias,s) \ + { \ + __m128i abiased_l = _mm_add_epi32(a##_l, bias); \ + __m128i abiased_h = _mm_add_epi32(a##_h, bias); \ + dct_wadd(sum, abiased, b); \ + dct_wsub(dif, abiased, b); \ + out0 = _mm_packs_epi32(_mm_srai_epi32(sum_l, s), _mm_srai_epi32(sum_h, s)); \ + out1 = _mm_packs_epi32(_mm_srai_epi32(dif_l, s), _mm_srai_epi32(dif_h, s)); \ + } -STBIDEF void stbi_install_idct(stbi_idct_8x8 func) -{ - stbi__idct_installed = func; + // 8-bit interleave step (for transposes) + #define dct_interleave8(a, b) \ + tmp = a; \ + a = _mm_unpacklo_epi8(a, b); \ + b = _mm_unpackhi_epi8(tmp, b) + + // 16-bit interleave step (for transposes) + #define dct_interleave16(a, b) \ + tmp = a; \ + a = _mm_unpacklo_epi16(a, b); \ + b = _mm_unpackhi_epi16(tmp, b) + + #define dct_pass(bias,shift) \ + { \ + /* even part */ \ + dct_rot(t2e,t3e, row2,row6, rot0_0,rot0_1); \ + __m128i sum04 = _mm_add_epi16(row0, row4); \ + __m128i dif04 = _mm_sub_epi16(row0, row4); \ + dct_widen(t0e, sum04); \ + dct_widen(t1e, dif04); \ + dct_wadd(x0, t0e, t3e); \ + dct_wsub(x3, t0e, t3e); \ + dct_wadd(x1, t1e, t2e); \ + dct_wsub(x2, t1e, t2e); \ + /* odd part */ \ + dct_rot(y0o,y2o, row7,row3, rot2_0,rot2_1); \ + dct_rot(y1o,y3o, row5,row1, rot3_0,rot3_1); \ + __m128i sum17 = _mm_add_epi16(row1, row7); \ + __m128i sum35 = _mm_add_epi16(row3, row5); \ + dct_rot(y4o,y5o, sum17,sum35, rot1_0,rot1_1); \ + dct_wadd(x4, y0o, y4o); \ + dct_wadd(x5, y1o, y5o); \ + dct_wadd(x6, y2o, y5o); \ + dct_wadd(x7, y3o, y4o); \ + dct_bfly32o(row0,row7, x0,x7,bias,shift); \ + dct_bfly32o(row1,row6, x1,x6,bias,shift); \ + dct_bfly32o(row2,row5, x2,x5,bias,shift); \ + dct_bfly32o(row3,row4, x3,x4,bias,shift); \ + } + + __m128i rot0_0 = dct_const(stbi__f2f(0.5411961f), stbi__f2f(0.5411961f) + stbi__f2f(-1.847759065f)); + __m128i rot0_1 = dct_const(stbi__f2f(0.5411961f) + stbi__f2f( 0.765366865f), stbi__f2f(0.5411961f)); + __m128i rot1_0 = dct_const(stbi__f2f(1.175875602f) + stbi__f2f(-0.899976223f), stbi__f2f(1.175875602f)); + __m128i rot1_1 = dct_const(stbi__f2f(1.175875602f), stbi__f2f(1.175875602f) + stbi__f2f(-2.562915447f)); + __m128i rot2_0 = dct_const(stbi__f2f(-1.961570560f) + stbi__f2f( 0.298631336f), stbi__f2f(-1.961570560f)); + __m128i rot2_1 = dct_const(stbi__f2f(-1.961570560f), stbi__f2f(-1.961570560f) + stbi__f2f( 3.072711026f)); + __m128i rot3_0 = dct_const(stbi__f2f(-0.390180644f) + stbi__f2f( 2.053119869f), stbi__f2f(-0.390180644f)); + __m128i rot3_1 = dct_const(stbi__f2f(-0.390180644f), stbi__f2f(-0.390180644f) + stbi__f2f( 1.501321110f)); + + // rounding biases in column/row passes, see stbi__idct_block for explanation. + __m128i bias_0 = _mm_set1_epi32(512); + __m128i bias_1 = _mm_set1_epi32(65536 + (128<<17)); + + // load + row0 = _mm_load_si128((const __m128i *) (data + 0*8)); + row1 = _mm_load_si128((const __m128i *) (data + 1*8)); + row2 = _mm_load_si128((const __m128i *) (data + 2*8)); + row3 = _mm_load_si128((const __m128i *) (data + 3*8)); + row4 = _mm_load_si128((const __m128i *) (data + 4*8)); + row5 = _mm_load_si128((const __m128i *) (data + 5*8)); + row6 = _mm_load_si128((const __m128i *) (data + 6*8)); + row7 = _mm_load_si128((const __m128i *) (data + 7*8)); + + // column pass + dct_pass(bias_0, 10); + + { + // 16bit 8x8 transpose pass 1 + dct_interleave16(row0, row4); + dct_interleave16(row1, row5); + dct_interleave16(row2, row6); + dct_interleave16(row3, row7); + + // transpose pass 2 + dct_interleave16(row0, row2); + dct_interleave16(row1, row3); + dct_interleave16(row4, row6); + dct_interleave16(row5, row7); + + // transpose pass 3 + dct_interleave16(row0, row1); + dct_interleave16(row2, row3); + dct_interleave16(row4, row5); + dct_interleave16(row6, row7); + } + + // row pass + dct_pass(bias_1, 17); + + { + // pack + __m128i p0 = _mm_packus_epi16(row0, row1); // a0a1a2a3...a7b0b1b2b3...b7 + __m128i p1 = _mm_packus_epi16(row2, row3); + __m128i p2 = _mm_packus_epi16(row4, row5); + __m128i p3 = _mm_packus_epi16(row6, row7); + + // 8bit 8x8 transpose pass 1 + dct_interleave8(p0, p2); // a0e0a1e1... + dct_interleave8(p1, p3); // c0g0c1g1... + + // transpose pass 2 + dct_interleave8(p0, p1); // a0c0e0g0... + dct_interleave8(p2, p3); // b0d0f0h0... + + // transpose pass 3 + dct_interleave8(p0, p2); // a0b0c0d0... + dct_interleave8(p1, p3); // a4b4c4d4... + + // store + _mm_storel_epi64((__m128i *) out, p0); out += out_stride; + _mm_storel_epi64((__m128i *) out, _mm_shuffle_epi32(p0, 0x4e)); out += out_stride; + _mm_storel_epi64((__m128i *) out, p2); out += out_stride; + _mm_storel_epi64((__m128i *) out, _mm_shuffle_epi32(p2, 0x4e)); out += out_stride; + _mm_storel_epi64((__m128i *) out, p1); out += out_stride; + _mm_storel_epi64((__m128i *) out, _mm_shuffle_epi32(p1, 0x4e)); out += out_stride; + _mm_storel_epi64((__m128i *) out, p3); out += out_stride; + _mm_storel_epi64((__m128i *) out, _mm_shuffle_epi32(p3, 0x4e)); + } + +#undef dct_const +#undef dct_rot +#undef dct_widen +#undef dct_wadd +#undef dct_wsub +#undef dct_bfly32o +#undef dct_interleave8 +#undef dct_interleave16 +#undef dct_pass +} + +#endif // STBI_SSE2 + +#ifdef STBI_NEON + +// NEON integer IDCT. should produce bit-identical +// results to the generic C version. +static void stbi__idct_simd(stbi_uc *out, int out_stride, short data[64]) +{ + int16x8_t row0, row1, row2, row3, row4, row5, row6, row7; + + int16x4_t rot0_0 = vdup_n_s16(stbi__f2f(0.5411961f)); + int16x4_t rot0_1 = vdup_n_s16(stbi__f2f(-1.847759065f)); + int16x4_t rot0_2 = vdup_n_s16(stbi__f2f( 0.765366865f)); + int16x4_t rot1_0 = vdup_n_s16(stbi__f2f( 1.175875602f)); + int16x4_t rot1_1 = vdup_n_s16(stbi__f2f(-0.899976223f)); + int16x4_t rot1_2 = vdup_n_s16(stbi__f2f(-2.562915447f)); + int16x4_t rot2_0 = vdup_n_s16(stbi__f2f(-1.961570560f)); + int16x4_t rot2_1 = vdup_n_s16(stbi__f2f(-0.390180644f)); + int16x4_t rot3_0 = vdup_n_s16(stbi__f2f( 0.298631336f)); + int16x4_t rot3_1 = vdup_n_s16(stbi__f2f( 2.053119869f)); + int16x4_t rot3_2 = vdup_n_s16(stbi__f2f( 3.072711026f)); + int16x4_t rot3_3 = vdup_n_s16(stbi__f2f( 1.501321110f)); + +#define dct_long_mul(out, inq, coeff) \ + int32x4_t out##_l = vmull_s16(vget_low_s16(inq), coeff); \ + int32x4_t out##_h = vmull_s16(vget_high_s16(inq), coeff) + +#define dct_long_mac(out, acc, inq, coeff) \ + int32x4_t out##_l = vmlal_s16(acc##_l, vget_low_s16(inq), coeff); \ + int32x4_t out##_h = vmlal_s16(acc##_h, vget_high_s16(inq), coeff) + +#define dct_widen(out, inq) \ + int32x4_t out##_l = vshll_n_s16(vget_low_s16(inq), 12); \ + int32x4_t out##_h = vshll_n_s16(vget_high_s16(inq), 12) + +// wide add +#define dct_wadd(out, a, b) \ + int32x4_t out##_l = vaddq_s32(a##_l, b##_l); \ + int32x4_t out##_h = vaddq_s32(a##_h, b##_h) + +// wide sub +#define dct_wsub(out, a, b) \ + int32x4_t out##_l = vsubq_s32(a##_l, b##_l); \ + int32x4_t out##_h = vsubq_s32(a##_h, b##_h) + +// butterfly a/b, then shift using "shiftop" by "s" and pack +#define dct_bfly32o(out0,out1, a,b,shiftop,s) \ + { \ + dct_wadd(sum, a, b); \ + dct_wsub(dif, a, b); \ + out0 = vcombine_s16(shiftop(sum_l, s), shiftop(sum_h, s)); \ + out1 = vcombine_s16(shiftop(dif_l, s), shiftop(dif_h, s)); \ + } + +#define dct_pass(shiftop, shift) \ + { \ + /* even part */ \ + int16x8_t sum26 = vaddq_s16(row2, row6); \ + dct_long_mul(p1e, sum26, rot0_0); \ + dct_long_mac(t2e, p1e, row6, rot0_1); \ + dct_long_mac(t3e, p1e, row2, rot0_2); \ + int16x8_t sum04 = vaddq_s16(row0, row4); \ + int16x8_t dif04 = vsubq_s16(row0, row4); \ + dct_widen(t0e, sum04); \ + dct_widen(t1e, dif04); \ + dct_wadd(x0, t0e, t3e); \ + dct_wsub(x3, t0e, t3e); \ + dct_wadd(x1, t1e, t2e); \ + dct_wsub(x2, t1e, t2e); \ + /* odd part */ \ + int16x8_t sum15 = vaddq_s16(row1, row5); \ + int16x8_t sum17 = vaddq_s16(row1, row7); \ + int16x8_t sum35 = vaddq_s16(row3, row5); \ + int16x8_t sum37 = vaddq_s16(row3, row7); \ + int16x8_t sumodd = vaddq_s16(sum17, sum35); \ + dct_long_mul(p5o, sumodd, rot1_0); \ + dct_long_mac(p1o, p5o, sum17, rot1_1); \ + dct_long_mac(p2o, p5o, sum35, rot1_2); \ + dct_long_mul(p3o, sum37, rot2_0); \ + dct_long_mul(p4o, sum15, rot2_1); \ + dct_wadd(sump13o, p1o, p3o); \ + dct_wadd(sump24o, p2o, p4o); \ + dct_wadd(sump23o, p2o, p3o); \ + dct_wadd(sump14o, p1o, p4o); \ + dct_long_mac(x4, sump13o, row7, rot3_0); \ + dct_long_mac(x5, sump24o, row5, rot3_1); \ + dct_long_mac(x6, sump23o, row3, rot3_2); \ + dct_long_mac(x7, sump14o, row1, rot3_3); \ + dct_bfly32o(row0,row7, x0,x7,shiftop,shift); \ + dct_bfly32o(row1,row6, x1,x6,shiftop,shift); \ + dct_bfly32o(row2,row5, x2,x5,shiftop,shift); \ + dct_bfly32o(row3,row4, x3,x4,shiftop,shift); \ + } + + // load + row0 = vld1q_s16(data + 0*8); + row1 = vld1q_s16(data + 1*8); + row2 = vld1q_s16(data + 2*8); + row3 = vld1q_s16(data + 3*8); + row4 = vld1q_s16(data + 4*8); + row5 = vld1q_s16(data + 5*8); + row6 = vld1q_s16(data + 6*8); + row7 = vld1q_s16(data + 7*8); + + // add DC bias + row0 = vaddq_s16(row0, vsetq_lane_s16(1024, vdupq_n_s16(0), 0)); + + // column pass + dct_pass(vrshrn_n_s32, 10); + + // 16bit 8x8 transpose + { +// these three map to a single VTRN.16, VTRN.32, and VSWP, respectively. +// whether compilers actually get this is another story, sadly. +#define dct_trn16(x, y) { int16x8x2_t t = vtrnq_s16(x, y); x = t.val[0]; y = t.val[1]; } +#define dct_trn32(x, y) { int32x4x2_t t = vtrnq_s32(vreinterpretq_s32_s16(x), vreinterpretq_s32_s16(y)); x = vreinterpretq_s16_s32(t.val[0]); y = vreinterpretq_s16_s32(t.val[1]); } +#define dct_trn64(x, y) { int16x8_t x0 = x; int16x8_t y0 = y; x = vcombine_s16(vget_low_s16(x0), vget_low_s16(y0)); y = vcombine_s16(vget_high_s16(x0), vget_high_s16(y0)); } + + // pass 1 + dct_trn16(row0, row1); // a0b0a2b2a4b4a6b6 + dct_trn16(row2, row3); + dct_trn16(row4, row5); + dct_trn16(row6, row7); + + // pass 2 + dct_trn32(row0, row2); // a0b0c0d0a4b4c4d4 + dct_trn32(row1, row3); + dct_trn32(row4, row6); + dct_trn32(row5, row7); + + // pass 3 + dct_trn64(row0, row4); // a0b0c0d0e0f0g0h0 + dct_trn64(row1, row5); + dct_trn64(row2, row6); + dct_trn64(row3, row7); + +#undef dct_trn16 +#undef dct_trn32 +#undef dct_trn64 + } + + // row pass + // vrshrn_n_s32 only supports shifts up to 16, we need + // 17. so do a non-rounding shift of 16 first then follow + // up with a rounding shift by 1. + dct_pass(vshrn_n_s32, 16); + + { + // pack and round + uint8x8_t p0 = vqrshrun_n_s16(row0, 1); + uint8x8_t p1 = vqrshrun_n_s16(row1, 1); + uint8x8_t p2 = vqrshrun_n_s16(row2, 1); + uint8x8_t p3 = vqrshrun_n_s16(row3, 1); + uint8x8_t p4 = vqrshrun_n_s16(row4, 1); + uint8x8_t p5 = vqrshrun_n_s16(row5, 1); + uint8x8_t p6 = vqrshrun_n_s16(row6, 1); + uint8x8_t p7 = vqrshrun_n_s16(row7, 1); + + // again, these can translate into one instruction, but often don't. +#define dct_trn8_8(x, y) { uint8x8x2_t t = vtrn_u8(x, y); x = t.val[0]; y = t.val[1]; } +#define dct_trn8_16(x, y) { uint16x4x2_t t = vtrn_u16(vreinterpret_u16_u8(x), vreinterpret_u16_u8(y)); x = vreinterpret_u8_u16(t.val[0]); y = vreinterpret_u8_u16(t.val[1]); } +#define dct_trn8_32(x, y) { uint32x2x2_t t = vtrn_u32(vreinterpret_u32_u8(x), vreinterpret_u32_u8(y)); x = vreinterpret_u8_u32(t.val[0]); y = vreinterpret_u8_u32(t.val[1]); } + + // sadly can't use interleaved stores here since we only write + // 8 bytes to each scan line! + + // 8x8 8-bit transpose pass 1 + dct_trn8_8(p0, p1); + dct_trn8_8(p2, p3); + dct_trn8_8(p4, p5); + dct_trn8_8(p6, p7); + + // pass 2 + dct_trn8_16(p0, p2); + dct_trn8_16(p1, p3); + dct_trn8_16(p4, p6); + dct_trn8_16(p5, p7); + + // pass 3 + dct_trn8_32(p0, p4); + dct_trn8_32(p1, p5); + dct_trn8_32(p2, p6); + dct_trn8_32(p3, p7); + + // store + vst1_u8(out, p0); out += out_stride; + vst1_u8(out, p1); out += out_stride; + vst1_u8(out, p2); out += out_stride; + vst1_u8(out, p3); out += out_stride; + vst1_u8(out, p4); out += out_stride; + vst1_u8(out, p5); out += out_stride; + vst1_u8(out, p6); out += out_stride; + vst1_u8(out, p7); + +#undef dct_trn8_8 +#undef dct_trn8_16 +#undef dct_trn8_32 + } + +#undef dct_long_mul +#undef dct_long_mac +#undef dct_widen +#undef dct_wadd +#undef dct_wsub +#undef dct_bfly32o +#undef dct_pass } -#endif + +#endif // STBI_NEON #define STBI__MARKER_none 0xff // if there's a pending marker from the entropy stream, return that @@ -1368,6 +2441,7 @@ static void stbi__jpeg_reset(stbi__jpeg *j) j->img_comp[0].dc_pred = j->img_comp[1].dc_pred = j->img_comp[2].dc_pred = 0; j->marker = STBI__MARKER_none; j->todo = j->restart_interval ? j->restart_interval : 0x7fffffff; + j->eob_run = 0; // no more than 1<<31 MCUs if no restart_interal? that's plenty safe, // since we don't even allow 1<<30 pixels } @@ -1375,73 +2449,151 @@ static void stbi__jpeg_reset(stbi__jpeg *j) static int stbi__parse_entropy_coded_data(stbi__jpeg *z) { stbi__jpeg_reset(z); - if (z->scan_n == 1) { - int i,j; - #ifdef STBI_SIMD - __declspec(align(16)) - #endif - short data[64]; - int n = z->order[0]; - // non-interleaved data, we just need to process one block at a time, - // in trivial scanline order - // number of blocks to do just depends on how many actual "pixels" this - // component has, independent of interleaved MCU blocking and such - int w = (z->img_comp[n].x+7) >> 3; - int h = (z->img_comp[n].y+7) >> 3; - for (j=0; j < h; ++j) { - for (i=0; i < w; ++i) { - if (!stbi__jpeg_decode_block(z, data, z->huff_dc+z->img_comp[n].hd, z->huff_ac+z->img_comp[n].ha, n)) return 0; - #ifdef STBI_SIMD - stbi__idct_installed(z->img_comp[n].data+z->img_comp[n].w2*j*8+i*8, z->img_comp[n].w2, data, z->dequant2[z->img_comp[n].tq]); - #else - stbi__idct_block(z->img_comp[n].data+z->img_comp[n].w2*j*8+i*8, z->img_comp[n].w2, data, z->dequant[z->img_comp[n].tq]); - #endif - // every data block is an MCU, so countdown the restart interval - if (--z->todo <= 0) { - if (z->code_bits < 24) stbi__grow_buffer_unsafe(z); - // if it's NOT a restart, then just bail, so we get corrupt data - // rather than no data - if (!STBI__RESTART(z->marker)) return 1; - stbi__jpeg_reset(z); + if (!z->progressive) { + if (z->scan_n == 1) { + int i,j; + STBI_SIMD_ALIGN(short, data[64]); + int n = z->order[0]; + // non-interleaved data, we just need to process one block at a time, + // in trivial scanline order + // number of blocks to do just depends on how many actual "pixels" this + // component has, independent of interleaved MCU blocking and such + int w = (z->img_comp[n].x+7) >> 3; + int h = (z->img_comp[n].y+7) >> 3; + for (j=0; j < h; ++j) { + for (i=0; i < w; ++i) { + int ha = z->img_comp[n].ha; + if (!stbi__jpeg_decode_block(z, data, z->huff_dc+z->img_comp[n].hd, z->huff_ac+ha, z->fast_ac[ha], n, z->dequant[z->img_comp[n].tq])) return 0; + z->idct_block_kernel(z->img_comp[n].data+z->img_comp[n].w2*j*8+i*8, z->img_comp[n].w2, data); + // every data block is an MCU, so countdown the restart interval + if (--z->todo <= 0) { + if (z->code_bits < 24) stbi__grow_buffer_unsafe(z); + // if it's NOT a restart, then just bail, so we get corrupt data + // rather than no data + if (!STBI__RESTART(z->marker)) return 1; + stbi__jpeg_reset(z); + } } } + return 1; + } else { // interleaved + int i,j,k,x,y; + STBI_SIMD_ALIGN(short, data[64]); + for (j=0; j < z->img_mcu_y; ++j) { + for (i=0; i < z->img_mcu_x; ++i) { + // scan an interleaved mcu... process scan_n components in order + for (k=0; k < z->scan_n; ++k) { + int n = z->order[k]; + // scan out an mcu's worth of this component; that's just determined + // by the basic H and V specified for the component + for (y=0; y < z->img_comp[n].v; ++y) { + for (x=0; x < z->img_comp[n].h; ++x) { + int x2 = (i*z->img_comp[n].h + x)*8; + int y2 = (j*z->img_comp[n].v + y)*8; + int ha = z->img_comp[n].ha; + if (!stbi__jpeg_decode_block(z, data, z->huff_dc+z->img_comp[n].hd, z->huff_ac+ha, z->fast_ac[ha], n, z->dequant[z->img_comp[n].tq])) return 0; + z->idct_block_kernel(z->img_comp[n].data+z->img_comp[n].w2*y2+x2, z->img_comp[n].w2, data); + } + } + } + // after all interleaved components, that's an interleaved MCU, + // so now count down the restart interval + if (--z->todo <= 0) { + if (z->code_bits < 24) stbi__grow_buffer_unsafe(z); + if (!STBI__RESTART(z->marker)) return 1; + stbi__jpeg_reset(z); + } + } + } + return 1; } - } else { // interleaved! - int i,j,k,x,y; - short data[64]; - for (j=0; j < z->img_mcu_y; ++j) { - for (i=0; i < z->img_mcu_x; ++i) { - // scan an interleaved mcu... process scan_n components in order - for (k=0; k < z->scan_n; ++k) { - int n = z->order[k]; - // scan out an mcu's worth of this component; that's just determined - // by the basic H and V specified for the component - for (y=0; y < z->img_comp[n].v; ++y) { - for (x=0; x < z->img_comp[n].h; ++x) { - int x2 = (i*z->img_comp[n].h + x)*8; - int y2 = (j*z->img_comp[n].v + y)*8; - if (!stbi__jpeg_decode_block(z, data, z->huff_dc+z->img_comp[n].hd, z->huff_ac+z->img_comp[n].ha, n)) return 0; - #ifdef STBI_SIMD - stbi__idct_installed(z->img_comp[n].data+z->img_comp[n].w2*y2+x2, z->img_comp[n].w2, data, z->dequant2[z->img_comp[n].tq]); - #else - stbi__idct_block(z->img_comp[n].data+z->img_comp[n].w2*y2+x2, z->img_comp[n].w2, data, z->dequant[z->img_comp[n].tq]); - #endif + } else { + if (z->scan_n == 1) { + int i,j; + int n = z->order[0]; + // non-interleaved data, we just need to process one block at a time, + // in trivial scanline order + // number of blocks to do just depends on how many actual "pixels" this + // component has, independent of interleaved MCU blocking and such + int w = (z->img_comp[n].x+7) >> 3; + int h = (z->img_comp[n].y+7) >> 3; + for (j=0; j < h; ++j) { + for (i=0; i < w; ++i) { + short *data = z->img_comp[n].coeff + 64 * (i + j * z->img_comp[n].coeff_w); + if (z->spec_start == 0) { + if (!stbi__jpeg_decode_block_prog_dc(z, data, &z->huff_dc[z->img_comp[n].hd], n)) + return 0; + } else { + int ha = z->img_comp[n].ha; + if (!stbi__jpeg_decode_block_prog_ac(z, data, &z->huff_ac[ha], z->fast_ac[ha])) + return 0; + } + // every data block is an MCU, so countdown the restart interval + if (--z->todo <= 0) { + if (z->code_bits < 24) stbi__grow_buffer_unsafe(z); + if (!STBI__RESTART(z->marker)) return 1; + stbi__jpeg_reset(z); + } + } + } + return 1; + } else { // interleaved + int i,j,k,x,y; + for (j=0; j < z->img_mcu_y; ++j) { + for (i=0; i < z->img_mcu_x; ++i) { + // scan an interleaved mcu... process scan_n components in order + for (k=0; k < z->scan_n; ++k) { + int n = z->order[k]; + // scan out an mcu's worth of this component; that's just determined + // by the basic H and V specified for the component + for (y=0; y < z->img_comp[n].v; ++y) { + for (x=0; x < z->img_comp[n].h; ++x) { + int x2 = (i*z->img_comp[n].h + x); + int y2 = (j*z->img_comp[n].v + y); + short *data = z->img_comp[n].coeff + 64 * (x2 + y2 * z->img_comp[n].coeff_w); + if (!stbi__jpeg_decode_block_prog_dc(z, data, &z->huff_dc[z->img_comp[n].hd], n)) + return 0; + } } } + // after all interleaved components, that's an interleaved MCU, + // so now count down the restart interval + if (--z->todo <= 0) { + if (z->code_bits < 24) stbi__grow_buffer_unsafe(z); + if (!STBI__RESTART(z->marker)) return 1; + stbi__jpeg_reset(z); + } } - // after all interleaved components, that's an interleaved MCU, - // so now count down the restart interval - if (--z->todo <= 0) { - if (z->code_bits < 24) stbi__grow_buffer_unsafe(z); - // if it's NOT a restart, then just bail, so we get corrupt data - // rather than no data - if (!STBI__RESTART(z->marker)) return 1; - stbi__jpeg_reset(z); + } + return 1; + } + } +} + +static void stbi__jpeg_dequantize(short *data, stbi_uc *dequant) +{ + int i; + for (i=0; i < 64; ++i) + data[i] *= dequant[i]; +} + +static void stbi__jpeg_finish(stbi__jpeg *z) +{ + if (z->progressive) { + // dequantize and idct the data + int i,j,n; + for (n=0; n < z->s->img_n; ++n) { + int w = (z->img_comp[n].x+7) >> 3; + int h = (z->img_comp[n].y+7) >> 3; + for (j=0; j < h; ++j) { + for (i=0; i < w; ++i) { + short *data = z->img_comp[n].coeff + 64 * (i + j * z->img_comp[n].coeff_w); + stbi__jpeg_dequantize(data, z->dequant[z->img_comp[n].tq]); + z->idct_block_kernel(z->img_comp[n].data+z->img_comp[n].w2*j*8+i*8, z->img_comp[n].w2, data); } } } } - return 1; } static int stbi__process_marker(stbi__jpeg *z, int m) @@ -1451,9 +2603,6 @@ static int stbi__process_marker(stbi__jpeg *z, int m) case STBI__MARKER_none: // no marker found return stbi__err("expected marker","Corrupt JPEG"); - case 0xC2: // stbi__SOF - progressive - return stbi__err("progressive jpeg","JPEG format not supported (progressive)"); - case 0xDD: // DRI - specify restart interval if (stbi__get16be(z->s) != 4) return stbi__err("bad DRI len","Corrupt JPEG"); z->restart_interval = stbi__get16be(z->s); @@ -1469,10 +2618,6 @@ static int stbi__process_marker(stbi__jpeg *z, int m) if (t > 3) return stbi__err("bad DQT table","Corrupt JPEG"); for (i=0; i < 64; ++i) z->dequant[t][stbi__jpeg_dezigzag[i]] = stbi__get8(z->s); - #ifdef STBI_SIMD - for (i=0; i < 64; ++i) - z->dequant2[t][i] = z->dequant[t][i]; - #endif L -= 65; } return L==0; @@ -1500,6 +2645,8 @@ static int stbi__process_marker(stbi__jpeg *z, int m) } for (i=0; i < n; ++i) v[i] = stbi__get8(z->s); + if (tc != 0) + stbi__build_fast_ac(z->fast_ac[th], z->huff_ac + th); L -= n; } return L==0; @@ -1512,28 +2659,42 @@ static int stbi__process_marker(stbi__jpeg *z, int m) return 0; } -// after we see stbi__SOS +// after we see SOS static int stbi__process_scan_header(stbi__jpeg *z) { int i; int Ls = stbi__get16be(z->s); z->scan_n = stbi__get8(z->s); - if (z->scan_n < 1 || z->scan_n > 4 || z->scan_n > (int) z->s->img_n) return stbi__err("bad stbi__SOS component count","Corrupt JPEG"); - if (Ls != 6+2*z->scan_n) return stbi__err("bad stbi__SOS len","Corrupt JPEG"); + if (z->scan_n < 1 || z->scan_n > 4 || z->scan_n > (int) z->s->img_n) return stbi__err("bad SOS component count","Corrupt JPEG"); + if (Ls != 6+2*z->scan_n) return stbi__err("bad SOS len","Corrupt JPEG"); for (i=0; i < z->scan_n; ++i) { int id = stbi__get8(z->s), which; int q = stbi__get8(z->s); for (which = 0; which < z->s->img_n; ++which) if (z->img_comp[which].id == id) break; - if (which == z->s->img_n) return 0; + if (which == z->s->img_n) return 0; // no match z->img_comp[which].hd = q >> 4; if (z->img_comp[which].hd > 3) return stbi__err("bad DC huff","Corrupt JPEG"); z->img_comp[which].ha = q & 15; if (z->img_comp[which].ha > 3) return stbi__err("bad AC huff","Corrupt JPEG"); z->order[i] = which; } - if (stbi__get8(z->s) != 0) return stbi__err("bad stbi__SOS","Corrupt JPEG"); - stbi__get8(z->s); // should be 63, but might be 0 - if (stbi__get8(z->s) != 0) return stbi__err("bad stbi__SOS","Corrupt JPEG"); + + { + int aa; + z->spec_start = stbi__get8(z->s); + z->spec_end = stbi__get8(z->s); // should be 63, but might be 0 + aa = stbi__get8(z->s); + z->succ_high = (aa >> 4); + z->succ_low = (aa & 15); + if (z->progressive) { + if (z->spec_start > 63 || z->spec_end > 63 || z->spec_start > z->spec_end || z->succ_high > 13 || z->succ_low > 13) + return stbi__err("bad SOS", "Corrupt JPEG"); + } else { + if (z->spec_start != 0) return stbi__err("bad SOS","Corrupt JPEG"); + if (z->succ_high != 0 || z->succ_low != 0) return stbi__err("bad SOS","Corrupt JPEG"); + z->spec_end = 63; + } + } return 1; } @@ -1542,8 +2703,8 @@ static int stbi__process_frame_header(stbi__jpeg *z, int scan) { stbi__context *s = z->s; int Lf,p,i,q, h_max=1,v_max=1,c; - Lf = stbi__get16be(s); if (Lf < 11) return stbi__err("bad stbi__SOF len","Corrupt JPEG"); // JPEG - p = stbi__get8(s); if (p != 8) return stbi__err("only 8-bit","JPEG format not supported: 8-bit only"); // JPEG baseline + Lf = stbi__get16be(s); if (Lf < 11) return stbi__err("bad SOF len","Corrupt JPEG"); // JPEG + p = stbi__get8(s); if (p != 8) return stbi__err("only 8-bit","JPEG format not supported: 8-bit only"); // JPEG baseline s->img_y = stbi__get16be(s); if (s->img_y == 0) return stbi__err("no header height", "JPEG format not supported: delayed height"); // Legal, but we don't handle it--but neither does IJG s->img_x = stbi__get16be(s); if (s->img_x == 0) return stbi__err("0 width","Corrupt JPEG"); // JPEG requires c = stbi__get8(s); @@ -1554,7 +2715,7 @@ static int stbi__process_frame_header(stbi__jpeg *z, int scan) z->img_comp[i].linebuf = NULL; } - if (Lf != 8+3*s->img_n) return stbi__err("bad stbi__SOF len","Corrupt JPEG"); + if (Lf != 8+3*s->img_n) return stbi__err("bad SOF len","Corrupt JPEG"); for (i=0; i < s->img_n; ++i) { z->img_comp[i].id = stbi__get8(s); @@ -1567,9 +2728,9 @@ static int stbi__process_frame_header(stbi__jpeg *z, int scan) z->img_comp[i].tq = stbi__get8(s); if (z->img_comp[i].tq > 3) return stbi__err("bad TQ","Corrupt JPEG"); } - if (scan != SCAN_load) return 1; + if (scan != STBI__SCAN_load) return 1; - if ((1 << 30) / s->img_x / s->img_n < s->img_y) return stbi__err("too large", "Image too large to stbi__jpeg_huff_decode"); + if ((1 << 30) / s->img_x / s->img_n < s->img_y) return stbi__err("too large", "Image too large to decode"); for (i=0; i < s->img_n; ++i) { if (z->img_comp[i].h > h_max) h_max = z->img_comp[i].h; @@ -1585,64 +2746,82 @@ static int stbi__process_frame_header(stbi__jpeg *z, int scan) z->img_mcu_y = (s->img_y + z->img_mcu_h-1) / z->img_mcu_h; for (i=0; i < s->img_n; ++i) { - // number of effective pixels (stbi__err.g. for non-interleaved MCU) + // number of effective pixels (e.g. for non-interleaved MCU) z->img_comp[i].x = (s->img_x * z->img_comp[i].h + h_max-1) / h_max; z->img_comp[i].y = (s->img_y * z->img_comp[i].v + v_max-1) / v_max; - // to simplify generation, we'll allocate enough memory to stbi__jpeg_huff_decode + // to simplify generation, we'll allocate enough memory to decode // the bogus oversized data from using interleaved MCUs and their - // big blocks (stbi__err.g. a 16x16 iMCU on an image of width 33); we won't + // big blocks (e.g. a 16x16 iMCU on an image of width 33); we won't // discard the extra data until colorspace conversion z->img_comp[i].w2 = z->img_mcu_x * z->img_comp[i].h * 8; z->img_comp[i].h2 = z->img_mcu_y * z->img_comp[i].v * 8; - z->img_comp[i].raw_data = malloc(z->img_comp[i].w2 * z->img_comp[i].h2+15); + z->img_comp[i].raw_data = stbi__malloc(z->img_comp[i].w2 * z->img_comp[i].h2+15); + if (z->img_comp[i].raw_data == NULL) { for(--i; i >= 0; --i) { - free(z->img_comp[i].raw_data); - z->img_comp[i].data = NULL; + STBI_FREE(z->img_comp[i].raw_data); + z->img_comp[i].raw_data = NULL; } return stbi__err("outofmem", "Out of memory"); } - // align blocks for installable-idct using mmx/sse + // align blocks for idct using mmx/sse z->img_comp[i].data = (stbi_uc*) (((size_t) z->img_comp[i].raw_data + 15) & ~15); z->img_comp[i].linebuf = NULL; + if (z->progressive) { + z->img_comp[i].coeff_w = (z->img_comp[i].w2 + 7) >> 3; + z->img_comp[i].coeff_h = (z->img_comp[i].h2 + 7) >> 3; + z->img_comp[i].raw_coeff = STBI_MALLOC(z->img_comp[i].coeff_w * z->img_comp[i].coeff_h * 64 * sizeof(short) + 15); + z->img_comp[i].coeff = (short*) (((size_t) z->img_comp[i].raw_coeff + 15) & ~15); + } else { + z->img_comp[i].coeff = 0; + z->img_comp[i].raw_coeff = 0; + } } return 1; } -// use comparisons since in some cases we handle more than one case (stbi__err.g. stbi__SOF) +// use comparisons since in some cases we handle more than one case (e.g. SOF) #define stbi__DNL(x) ((x) == 0xdc) #define stbi__SOI(x) ((x) == 0xd8) #define stbi__EOI(x) ((x) == 0xd9) -#define stbi__SOF(x) ((x) == 0xc0 || (x) == 0xc1) +#define stbi__SOF(x) ((x) == 0xc0 || (x) == 0xc1 || (x) == 0xc2) #define stbi__SOS(x) ((x) == 0xda) -static int decode_jpeg_header(stbi__jpeg *z, int scan) +#define stbi__SOF_progressive(x) ((x) == 0xc2) + +static int stbi__decode_jpeg_header(stbi__jpeg *z, int scan) { int m; z->marker = STBI__MARKER_none; // initialize cached marker to empty m = stbi__get_marker(z); - if (!stbi__SOI(m)) return stbi__err("no stbi__SOI","Corrupt JPEG"); - if (scan == SCAN_type) return 1; + if (!stbi__SOI(m)) return stbi__err("no SOI","Corrupt JPEG"); + if (scan == STBI__SCAN_type) return 1; m = stbi__get_marker(z); while (!stbi__SOF(m)) { if (!stbi__process_marker(z,m)) return 0; m = stbi__get_marker(z); while (m == STBI__MARKER_none) { // some files have extra padding after their blocks, so ok, we'll scan - if (stbi__at_eof(z->s)) return stbi__err("no stbi__SOF", "Corrupt JPEG"); + if (stbi__at_eof(z->s)) return stbi__err("no SOF", "Corrupt JPEG"); m = stbi__get_marker(z); } } + z->progressive = stbi__SOF_progressive(m); if (!stbi__process_frame_header(z, scan)) return 0; return 1; } -static int decode_jpeg_image(stbi__jpeg *j) +// decode image to YCbCr format +static int stbi__decode_jpeg_image(stbi__jpeg *j) { int m; + for (m = 0; m < 4; m++) { + j->img_comp[m].raw_data = NULL; + j->img_comp[m].raw_coeff = NULL; + } j->restart_interval = 0; - if (!decode_jpeg_header(j, SCAN_load)) return 0; + if (!stbi__decode_jpeg_header(j, STBI__SCAN_load)) return 0; m = stbi__get_marker(j); while (!stbi__EOI(m)) { if (stbi__SOS(m)) { @@ -1656,7 +2835,7 @@ static int decode_jpeg_image(stbi__jpeg *j) j->marker = stbi__get8(j->s); break; } else if (x != 0) { - return 0; + return stbi__err("junk before marker", "Corrupt JPEG"); } } // if we reach eof without hitting a marker, stbi__get_marker() below will fail and we'll eventually return 0 @@ -1666,6 +2845,8 @@ static int decode_jpeg_image(stbi__jpeg *j) } m = stbi__get_marker(j); } + if (j->progressive) + stbi__jpeg_finish(j); return 1; } @@ -1749,6 +2930,123 @@ static stbi_uc *stbi__resample_row_hv_2(stbi_uc *out, stbi_uc *in_near, stbi_uc return out; } +#if defined(STBI_SSE2) || defined(STBI_NEON) +static stbi_uc *stbi__resample_row_hv_2_simd(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs) +{ + // need to generate 2x2 samples for every one in input + int i=0,t0,t1; + + if (w == 1) { + out[0] = out[1] = stbi__div4(3*in_near[0] + in_far[0] + 2); + return out; + } + + t1 = 3*in_near[0] + in_far[0]; + // process groups of 8 pixels for as long as we can. + // note we can't handle the last pixel in a row in this loop + // because we need to handle the filter boundary conditions. + for (; i < ((w-1) & ~7); i += 8) { +#if defined(STBI_SSE2) + // load and perform the vertical filtering pass + // this uses 3*x + y = 4*x + (y - x) + __m128i zero = _mm_setzero_si128(); + __m128i farb = _mm_loadl_epi64((__m128i *) (in_far + i)); + __m128i nearb = _mm_loadl_epi64((__m128i *) (in_near + i)); + __m128i farw = _mm_unpacklo_epi8(farb, zero); + __m128i nearw = _mm_unpacklo_epi8(nearb, zero); + __m128i diff = _mm_sub_epi16(farw, nearw); + __m128i nears = _mm_slli_epi16(nearw, 2); + __m128i curr = _mm_add_epi16(nears, diff); // current row + + // horizontal filter works the same based on shifted vers of current + // row. "prev" is current row shifted right by 1 pixel; we need to + // insert the previous pixel value (from t1). + // "next" is current row shifted left by 1 pixel, with first pixel + // of next block of 8 pixels added in. + __m128i prv0 = _mm_slli_si128(curr, 2); + __m128i nxt0 = _mm_srli_si128(curr, 2); + __m128i prev = _mm_insert_epi16(prv0, t1, 0); + __m128i next = _mm_insert_epi16(nxt0, 3*in_near[i+8] + in_far[i+8], 7); + + // horizontal filter, polyphase implementation since it's convenient: + // even pixels = 3*cur + prev = cur*4 + (prev - cur) + // odd pixels = 3*cur + next = cur*4 + (next - cur) + // note the shared term. + __m128i bias = _mm_set1_epi16(8); + __m128i curs = _mm_slli_epi16(curr, 2); + __m128i prvd = _mm_sub_epi16(prev, curr); + __m128i nxtd = _mm_sub_epi16(next, curr); + __m128i curb = _mm_add_epi16(curs, bias); + __m128i even = _mm_add_epi16(prvd, curb); + __m128i odd = _mm_add_epi16(nxtd, curb); + + // interleave even and odd pixels, then undo scaling. + __m128i int0 = _mm_unpacklo_epi16(even, odd); + __m128i int1 = _mm_unpackhi_epi16(even, odd); + __m128i de0 = _mm_srli_epi16(int0, 4); + __m128i de1 = _mm_srli_epi16(int1, 4); + + // pack and write output + __m128i outv = _mm_packus_epi16(de0, de1); + _mm_storeu_si128((__m128i *) (out + i*2), outv); +#elif defined(STBI_NEON) + // load and perform the vertical filtering pass + // this uses 3*x + y = 4*x + (y - x) + uint8x8_t farb = vld1_u8(in_far + i); + uint8x8_t nearb = vld1_u8(in_near + i); + int16x8_t diff = vreinterpretq_s16_u16(vsubl_u8(farb, nearb)); + int16x8_t nears = vreinterpretq_s16_u16(vshll_n_u8(nearb, 2)); + int16x8_t curr = vaddq_s16(nears, diff); // current row + + // horizontal filter works the same based on shifted vers of current + // row. "prev" is current row shifted right by 1 pixel; we need to + // insert the previous pixel value (from t1). + // "next" is current row shifted left by 1 pixel, with first pixel + // of next block of 8 pixels added in. + int16x8_t prv0 = vextq_s16(curr, curr, 7); + int16x8_t nxt0 = vextq_s16(curr, curr, 1); + int16x8_t prev = vsetq_lane_s16(t1, prv0, 0); + int16x8_t next = vsetq_lane_s16(3*in_near[i+8] + in_far[i+8], nxt0, 7); + + // horizontal filter, polyphase implementation since it's convenient: + // even pixels = 3*cur + prev = cur*4 + (prev - cur) + // odd pixels = 3*cur + next = cur*4 + (next - cur) + // note the shared term. + int16x8_t curs = vshlq_n_s16(curr, 2); + int16x8_t prvd = vsubq_s16(prev, curr); + int16x8_t nxtd = vsubq_s16(next, curr); + int16x8_t even = vaddq_s16(curs, prvd); + int16x8_t odd = vaddq_s16(curs, nxtd); + + // undo scaling and round, then store with even/odd phases interleaved + uint8x8x2_t o; + o.val[0] = vqrshrun_n_s16(even, 4); + o.val[1] = vqrshrun_n_s16(odd, 4); + vst2_u8(out + i*2, o); +#endif + + // "previous" value for next iter + t1 = 3*in_near[i+7] + in_far[i+7]; + } + + t0 = t1; + t1 = 3*in_near[i] + in_far[i]; + out[i*2] = stbi__div16(3*t1 + t0 + 8); + + for (++i; i < w; ++i) { + t0 = t1; + t1 = 3*in_near[i]+in_far[i]; + out[i*2-1] = stbi__div16(3*t0 + t1 + 8); + out[i*2 ] = stbi__div16(3*t1 + t0 + 8); + } + out[w*2-1] = stbi__div4(t1+2); + + STBI_NOTUSED(hs); + + return out; +} +#endif + static stbi_uc *stbi__resample_row_generic(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs) { // resample with nearest-neighbor @@ -1760,10 +3058,10 @@ static stbi_uc *stbi__resample_row_generic(stbi_uc *out, stbi_uc *in_near, stbi_ return out; } +#ifdef STBI_JPEG_OLD +// this is the same YCbCr-to-RGB calculation that stb_image has used +// historically before the algorithm changes in 1.49 #define float2fixed(x) ((int) ((x) * 65536 + 0.5)) - -// 0.38 seconds on 3*anemones.jpg (0.25 with processor = Pro) -// VC6 without processor=Pro is generating multiple LEAs per multiply! static void stbi__YCbCr_to_RGB_row(stbi_uc *out, const stbi_uc *y, const stbi_uc *pcb, const stbi_uc *pcr, int count, int step) { int i; @@ -1788,28 +3086,214 @@ static void stbi__YCbCr_to_RGB_row(stbi_uc *out, const stbi_uc *y, const stbi_uc out += step; } } +#else +// this is a reduced-precision calculation of YCbCr-to-RGB introduced +// to make sure the code produces the same results in both SIMD and scalar +#define float2fixed(x) (((int) ((x) * 4096.0f + 0.5f)) << 8) +static void stbi__YCbCr_to_RGB_row(stbi_uc *out, const stbi_uc *y, const stbi_uc *pcb, const stbi_uc *pcr, int count, int step) +{ + int i; + for (i=0; i < count; ++i) { + int y_fixed = (y[i] << 20) + (1<<19); // rounding + int r,g,b; + int cr = pcr[i] - 128; + int cb = pcb[i] - 128; + r = y_fixed + cr* float2fixed(1.40200f); + g = y_fixed + (cr*-float2fixed(0.71414f)) + ((cb*-float2fixed(0.34414f)) & 0xffff0000); + b = y_fixed + cb* float2fixed(1.77200f); + r >>= 20; + g >>= 20; + b >>= 20; + if ((unsigned) r > 255) { if (r < 0) r = 0; else r = 255; } + if ((unsigned) g > 255) { if (g < 0) g = 0; else g = 255; } + if ((unsigned) b > 255) { if (b < 0) b = 0; else b = 255; } + out[0] = (stbi_uc)r; + out[1] = (stbi_uc)g; + out[2] = (stbi_uc)b; + out[3] = 255; + out += step; + } +} +#endif -#ifdef STBI_SIMD -static stbi_YCbCr_to_RGB_run stbi__YCbCr_installed = stbi__YCbCr_to_RGB_row; +#if defined(STBI_SSE2) || defined(STBI_NEON) +static void stbi__YCbCr_to_RGB_simd(stbi_uc *out, stbi_uc const *y, stbi_uc const *pcb, stbi_uc const *pcr, int count, int step) +{ + int i = 0; + +#ifdef STBI_SSE2 + // step == 3 is pretty ugly on the final interleave, and i'm not convinced + // it's useful in practice (you wouldn't use it for textures, for example). + // so just accelerate step == 4 case. + if (step == 4) { + // this is a fairly straightforward implementation and not super-optimized. + __m128i signflip = _mm_set1_epi8(-0x80); + __m128i cr_const0 = _mm_set1_epi16( (short) ( 1.40200f*4096.0f+0.5f)); + __m128i cr_const1 = _mm_set1_epi16( - (short) ( 0.71414f*4096.0f+0.5f)); + __m128i cb_const0 = _mm_set1_epi16( - (short) ( 0.34414f*4096.0f+0.5f)); + __m128i cb_const1 = _mm_set1_epi16( (short) ( 1.77200f*4096.0f+0.5f)); + __m128i y_bias = _mm_set1_epi8((char) (unsigned char) 128); + __m128i xw = _mm_set1_epi16(255); // alpha channel + + for (; i+7 < count; i += 8) { + // load + __m128i y_bytes = _mm_loadl_epi64((__m128i *) (y+i)); + __m128i cr_bytes = _mm_loadl_epi64((__m128i *) (pcr+i)); + __m128i cb_bytes = _mm_loadl_epi64((__m128i *) (pcb+i)); + __m128i cr_biased = _mm_xor_si128(cr_bytes, signflip); // -128 + __m128i cb_biased = _mm_xor_si128(cb_bytes, signflip); // -128 + + // unpack to short (and left-shift cr, cb by 8) + __m128i yw = _mm_unpacklo_epi8(y_bias, y_bytes); + __m128i crw = _mm_unpacklo_epi8(_mm_setzero_si128(), cr_biased); + __m128i cbw = _mm_unpacklo_epi8(_mm_setzero_si128(), cb_biased); + + // color transform + __m128i yws = _mm_srli_epi16(yw, 4); + __m128i cr0 = _mm_mulhi_epi16(cr_const0, crw); + __m128i cb0 = _mm_mulhi_epi16(cb_const0, cbw); + __m128i cb1 = _mm_mulhi_epi16(cbw, cb_const1); + __m128i cr1 = _mm_mulhi_epi16(crw, cr_const1); + __m128i rws = _mm_add_epi16(cr0, yws); + __m128i gwt = _mm_add_epi16(cb0, yws); + __m128i bws = _mm_add_epi16(yws, cb1); + __m128i gws = _mm_add_epi16(gwt, cr1); + + // descale + __m128i rw = _mm_srai_epi16(rws, 4); + __m128i bw = _mm_srai_epi16(bws, 4); + __m128i gw = _mm_srai_epi16(gws, 4); + + // back to byte, set up for transpose + __m128i brb = _mm_packus_epi16(rw, bw); + __m128i gxb = _mm_packus_epi16(gw, xw); + + // transpose to interleave channels + __m128i t0 = _mm_unpacklo_epi8(brb, gxb); + __m128i t1 = _mm_unpackhi_epi8(brb, gxb); + __m128i o0 = _mm_unpacklo_epi16(t0, t1); + __m128i o1 = _mm_unpackhi_epi16(t0, t1); + + // store + _mm_storeu_si128((__m128i *) (out + 0), o0); + _mm_storeu_si128((__m128i *) (out + 16), o1); + out += 32; + } + } +#endif -STBIDEF void stbi_install_YCbCr_to_RGB(stbi_YCbCr_to_RGB_run func) -{ - stbi__YCbCr_installed = func; +#ifdef STBI_NEON + // in this version, step=3 support would be easy to add. but is there demand? + if (step == 4) { + // this is a fairly straightforward implementation and not super-optimized. + uint8x8_t signflip = vdup_n_u8(0x80); + int16x8_t cr_const0 = vdupq_n_s16( (short) ( 1.40200f*4096.0f+0.5f)); + int16x8_t cr_const1 = vdupq_n_s16( - (short) ( 0.71414f*4096.0f+0.5f)); + int16x8_t cb_const0 = vdupq_n_s16( - (short) ( 0.34414f*4096.0f+0.5f)); + int16x8_t cb_const1 = vdupq_n_s16( (short) ( 1.77200f*4096.0f+0.5f)); + + for (; i+7 < count; i += 8) { + // load + uint8x8_t y_bytes = vld1_u8(y + i); + uint8x8_t cr_bytes = vld1_u8(pcr + i); + uint8x8_t cb_bytes = vld1_u8(pcb + i); + int8x8_t cr_biased = vreinterpret_s8_u8(vsub_u8(cr_bytes, signflip)); + int8x8_t cb_biased = vreinterpret_s8_u8(vsub_u8(cb_bytes, signflip)); + + // expand to s16 + int16x8_t yws = vreinterpretq_s16_u16(vshll_n_u8(y_bytes, 4)); + int16x8_t crw = vshll_n_s8(cr_biased, 7); + int16x8_t cbw = vshll_n_s8(cb_biased, 7); + + // color transform + int16x8_t cr0 = vqdmulhq_s16(crw, cr_const0); + int16x8_t cb0 = vqdmulhq_s16(cbw, cb_const0); + int16x8_t cr1 = vqdmulhq_s16(crw, cr_const1); + int16x8_t cb1 = vqdmulhq_s16(cbw, cb_const1); + int16x8_t rws = vaddq_s16(yws, cr0); + int16x8_t gws = vaddq_s16(vaddq_s16(yws, cb0), cr1); + int16x8_t bws = vaddq_s16(yws, cb1); + + // undo scaling, round, convert to byte + uint8x8x4_t o; + o.val[0] = vqrshrun_n_s16(rws, 4); + o.val[1] = vqrshrun_n_s16(gws, 4); + o.val[2] = vqrshrun_n_s16(bws, 4); + o.val[3] = vdup_n_u8(255); + + // store, interleaving r/g/b/a + vst4_u8(out, o); + out += 8*4; + } + } +#endif + + for (; i < count; ++i) { + int y_fixed = (y[i] << 20) + (1<<19); // rounding + int r,g,b; + int cr = pcr[i] - 128; + int cb = pcb[i] - 128; + r = y_fixed + cr* float2fixed(1.40200f); + g = y_fixed + cr*-float2fixed(0.71414f) + ((cb*-float2fixed(0.34414f)) & 0xffff0000); + b = y_fixed + cb* float2fixed(1.77200f); + r >>= 20; + g >>= 20; + b >>= 20; + if ((unsigned) r > 255) { if (r < 0) r = 0; else r = 255; } + if ((unsigned) g > 255) { if (g < 0) g = 0; else g = 255; } + if ((unsigned) b > 255) { if (b < 0) b = 0; else b = 255; } + out[0] = (stbi_uc)r; + out[1] = (stbi_uc)g; + out[2] = (stbi_uc)b; + out[3] = 255; + out += step; + } } #endif +// set up the kernels +static void stbi__setup_jpeg(stbi__jpeg *j) +{ + j->idct_block_kernel = stbi__idct_block; + j->YCbCr_to_RGB_kernel = stbi__YCbCr_to_RGB_row; + j->resample_row_hv_2_kernel = stbi__resample_row_hv_2; + +#ifdef STBI_SSE2 + if (stbi__sse2_available()) { + j->idct_block_kernel = stbi__idct_simd; + #ifndef STBI_JPEG_OLD + j->YCbCr_to_RGB_kernel = stbi__YCbCr_to_RGB_simd; + #endif + j->resample_row_hv_2_kernel = stbi__resample_row_hv_2_simd; + } +#endif + +#ifdef STBI_NEON + j->idct_block_kernel = stbi__idct_simd; + #ifndef STBI_JPEG_OLD + j->YCbCr_to_RGB_kernel = stbi__YCbCr_to_RGB_simd; + #endif + j->resample_row_hv_2_kernel = stbi__resample_row_hv_2_simd; +#endif +} // clean up the temporary component buffers static void stbi__cleanup_jpeg(stbi__jpeg *j) { int i; for (i=0; i < j->s->img_n; ++i) { - if (j->img_comp[i].data) { - free(j->img_comp[i].raw_data); + if (j->img_comp[i].raw_data) { + STBI_FREE(j->img_comp[i].raw_data); + j->img_comp[i].raw_data = NULL; j->img_comp[i].data = NULL; } + if (j->img_comp[i].raw_coeff) { + STBI_FREE(j->img_comp[i].raw_coeff); + j->img_comp[i].raw_coeff = 0; + j->img_comp[i].coeff = 0; + } if (j->img_comp[i].linebuf) { - free(j->img_comp[i].linebuf); + STBI_FREE(j->img_comp[i].linebuf); j->img_comp[i].linebuf = NULL; } } @@ -1820,7 +3304,7 @@ typedef struct resample_row_func resample; stbi_uc *line0,*line1; int hs,vs; // expansion factor in each axis - int w_lores; // horizontal pixels pre-expansion + int w_lores; // horizontal pixels pre-expansion int ystep; // how far through vertical expansion we are int ypos; // which pre-expansion row we're on } stbi__resample; @@ -1828,12 +3312,13 @@ typedef struct static stbi_uc *load_jpeg_image(stbi__jpeg *z, int *out_x, int *out_y, int *comp, int req_comp) { int n, decode_n; + z->s->img_n = 0; // make stbi__cleanup_jpeg safe + // validate req_comp if (req_comp < 0 || req_comp > 4) return stbi__errpuc("bad req_comp", "Internal error"); - z->s->img_n = 0; - // load a jpeg image from whichever source - if (!decode_jpeg_image(z)) { stbi__cleanup_jpeg(z); return NULL; } + // load a jpeg image from whichever source, but leave in YCbCr format + if (!stbi__decode_jpeg_image(z)) { stbi__cleanup_jpeg(z); return NULL; } // determine actual number of components to generate n = req_comp ? req_comp : z->s->img_n; @@ -1857,7 +3342,7 @@ static stbi_uc *load_jpeg_image(stbi__jpeg *z, int *out_x, int *out_y, int *comp // allocate line buffer big enough for upsampling off the edges // with upsample factor of 4 - z->img_comp[k].linebuf = (stbi_uc *) malloc(z->s->img_x + 3); + z->img_comp[k].linebuf = (stbi_uc *) stbi__malloc(z->s->img_x + 3); if (!z->img_comp[k].linebuf) { stbi__cleanup_jpeg(z); return stbi__errpuc("outofmem", "Out of memory"); } r->hs = z->img_h_max / z->img_comp[k].h; @@ -1870,12 +3355,12 @@ static stbi_uc *load_jpeg_image(stbi__jpeg *z, int *out_x, int *out_y, int *comp if (r->hs == 1 && r->vs == 1) r->resample = resample_row_1; else if (r->hs == 1 && r->vs == 2) r->resample = stbi__resample_row_v_2; else if (r->hs == 2 && r->vs == 1) r->resample = stbi__resample_row_h_2; - else if (r->hs == 2 && r->vs == 2) r->resample = stbi__resample_row_hv_2; + else if (r->hs == 2 && r->vs == 2) r->resample = z->resample_row_hv_2_kernel; else r->resample = stbi__resample_row_generic; } // can't error after this so, this is safe - output = (stbi_uc *) malloc(n * z->s->img_x * z->s->img_y + 1); + output = (stbi_uc *) stbi__malloc(n * z->s->img_x * z->s->img_y + 1); if (!output) { stbi__cleanup_jpeg(z); return stbi__errpuc("outofmem", "Out of memory"); } // now go ahead and resample @@ -1898,11 +3383,7 @@ static stbi_uc *load_jpeg_image(stbi__jpeg *z, int *out_x, int *out_y, int *comp if (n >= 3) { stbi_uc *y = coutput[0]; if (z->s->img_n == 3) { - #ifdef STBI_SIMD - stbi__YCbCr_installed(out, y, coutput[1], coutput[2], z->s->img_x, n); - #else - stbi__YCbCr_to_RGB_row(out, y, coutput[1], coutput[2], z->s->img_x, n); - #endif + z->YCbCr_to_RGB_kernel(out, y, coutput[1], coutput[2], z->s->img_x, n); } else for (i=0; i < z->s->img_x; ++i) { out[0] = out[1] = out[2] = y[i]; @@ -1929,6 +3410,7 @@ static unsigned char *stbi__jpeg_load(stbi__context *s, int *x, int *y, int *com { stbi__jpeg j; j.s = s; + stbi__setup_jpeg(&j); return load_jpeg_image(&j, x,y,comp,req_comp); } @@ -1937,14 +3419,15 @@ static int stbi__jpeg_test(stbi__context *s) int r; stbi__jpeg j; j.s = s; - r = decode_jpeg_header(&j, SCAN_type); + stbi__setup_jpeg(&j); + r = stbi__decode_jpeg_header(&j, STBI__SCAN_type); stbi__rewind(s); return r; } static int stbi__jpeg_info_raw(stbi__jpeg *j, int *x, int *y, int *comp) { - if (!decode_jpeg_header(j, SCAN_header)) { + if (!stbi__decode_jpeg_header(j, STBI__SCAN_header)) { stbi__rewind( j->s ); return 0; } @@ -1960,14 +3443,17 @@ static int stbi__jpeg_info(stbi__context *s, int *x, int *y, int *comp) j.s = s; return stbi__jpeg_info_raw(&j, x, y, comp); } +#endif -// public domain zlib stbi__jpeg_huff_decode v0.2 Sean Barrett 2006-11-18 +// public domain zlib decode v0.2 Sean Barrett 2006-11-18 // simple implementation // - all input must be provided in an upfront buffer // - all output is written to a single output buffer (can malloc/realloc) // performance // - fast huffman +#ifndef STBI_NO_ZLIB + // fast-way is faster to check than jpeg huffman, but slow way is slower #define STBI__ZFAST_BITS 9 // accelerate all cases in default tables #define STBI__ZFAST_MASK ((1 << STBI__ZFAST_BITS) - 1) @@ -1981,7 +3467,7 @@ typedef struct int maxcode[17]; stbi__uint16 firstsymbol[16]; stbi_uc size[288]; - stbi__uint16 value[288]; + stbi__uint16 value[288]; } stbi__zhuffman; stbi_inline static int stbi__bitreverse16(int n) @@ -1995,9 +3481,9 @@ stbi_inline static int stbi__bitreverse16(int n) stbi_inline static int stbi__bit_reverse(int v, int bits) { - assert(bits <= 16); + STBI_ASSERT(bits <= 16); // to bit reverse n bits, reverse 16 and shift - // stbi__err.g. 11 bits, bit reverse and shift away 5 + // e.g. 11 bits, bit reverse and shift away 5 return stbi__bitreverse16(v) >> (16-bits); } @@ -2008,12 +3494,13 @@ static int stbi__zbuild_huffman(stbi__zhuffman *z, stbi_uc *sizelist, int num) // DEFLATE spec for generating codes memset(sizes, 0, sizeof(sizes)); - memset(z->fast, 255, sizeof(z->fast)); - for (i=0; i < num; ++i) + memset(z->fast, 0, sizeof(z->fast)); + for (i=0; i < num; ++i) ++sizes[sizelist[i]]; sizes[0] = 0; for (i=1; i < 16; ++i) - assert(sizes[i] <= (1 << i)); + if (sizes[i] > (1 << i)) + return stbi__err("bad sizes", "Corrupt PNG"); code = 0; for (i=1; i < 16; ++i) { next_code[i] = code; @@ -2021,7 +3508,7 @@ static int stbi__zbuild_huffman(stbi__zhuffman *z, stbi_uc *sizelist, int num) z->firstsymbol[i] = (stbi__uint16) k; code = (code + sizes[i]); if (sizes[i]) - if (code-1 >= (1 << i)) return stbi__err("bad codelengths","Corrupt JPEG"); + if (code-1 >= (1 << i)) return stbi__err("bad codelengths","Corrupt PNG"); z->maxcode[i] = code << (16-i); // preshift for inner loop code <<= 1; k += sizes[i]; @@ -2031,13 +3518,14 @@ static int stbi__zbuild_huffman(stbi__zhuffman *z, stbi_uc *sizelist, int num) int s = sizelist[i]; if (s) { int c = next_code[s] - z->firstcode[s] + z->firstsymbol[s]; + stbi__uint16 fastv = (stbi__uint16) ((s << 9) | i); z->size [c] = (stbi_uc ) s; z->value[c] = (stbi__uint16) i; if (s <= STBI__ZFAST_BITS) { - int k = stbi__bit_reverse(next_code[s],s); - while (k < (1 << STBI__ZFAST_BITS)) { - z->fast[k] = (stbi__uint16) c; - k += (1 << s); + int j = stbi__bit_reverse(next_code[s],s); + while (j < (1 << STBI__ZFAST_BITS)) { + z->fast[j] = fastv; + j += (1 << s); } } ++next_code[s]; @@ -2075,8 +3563,8 @@ stbi_inline static stbi_uc stbi__zget8(stbi__zbuf *z) static void stbi__fill_bits(stbi__zbuf *z) { do { - assert(z->code_buffer < (1U << z->num_bits)); - z->code_buffer |= stbi__zget8(z) << z->num_bits; + STBI_ASSERT(z->code_buffer < (1U << z->num_bits)); + z->code_buffer |= (unsigned int) stbi__zget8(z) << z->num_bits; z->num_bits += 8; } while (z->num_bits <= 24); } @@ -2088,21 +3576,12 @@ stbi_inline static unsigned int stbi__zreceive(stbi__zbuf *z, int n) k = z->code_buffer & ((1 << n) - 1); z->code_buffer >>= n; z->num_bits -= n; - return k; + return k; } -stbi_inline static int stbi__zhuffman_decode(stbi__zbuf *a, stbi__zhuffman *z) +static int stbi__zhuffman_decode_slowpath(stbi__zbuf *a, stbi__zhuffman *z) { int b,s,k; - if (a->num_bits < 16) stbi__fill_bits(a); - b = z->fast[a->code_buffer & STBI__ZFAST_MASK]; - if (b < 0xffff) { - s = z->size[b]; - a->code_buffer >>= s; - a->num_bits -= s; - return z->value[b]; - } - // not resolved by fast table, so compute it the slow way // use jpeg approach, which requires MSbits at top k = stbi__bit_reverse(a->code_buffer, 16); @@ -2112,22 +3591,38 @@ stbi_inline static int stbi__zhuffman_decode(stbi__zbuf *a, stbi__zhuffman *z) if (s == 16) return -1; // invalid code! // code size is s, so: b = (k >> (16-s)) - z->firstcode[s] + z->firstsymbol[s]; - assert(z->size[b] == s); + STBI_ASSERT(z->size[b] == s); a->code_buffer >>= s; a->num_bits -= s; return z->value[b]; } -static int stbi__zexpand(stbi__zbuf *z, int n) // need to make room for n bytes +stbi_inline static int stbi__zhuffman_decode(stbi__zbuf *a, stbi__zhuffman *z) +{ + int b,s; + if (a->num_bits < 16) stbi__fill_bits(a); + b = z->fast[a->code_buffer & STBI__ZFAST_MASK]; + if (b) { + s = b >> 9; + a->code_buffer >>= s; + a->num_bits -= s; + return b & 511; + } + return stbi__zhuffman_decode_slowpath(a, z); +} + +static int stbi__zexpand(stbi__zbuf *z, char *zout, int n) // need to make room for n bytes { char *q; - int cur, limit; + int cur, limit, old_limit; + z->zout = zout; if (!z->z_expandable) return stbi__err("output buffer limit","Corrupt PNG"); cur = (int) (z->zout - z->zout_start); - limit = (int) (z->zout_end - z->zout_start); + limit = old_limit = (int) (z->zout_end - z->zout_start); while (cur + n > limit) limit *= 2; - q = (char *) realloc(z->zout_start, limit); + q = (char *) STBI_REALLOC_SIZED(z->zout_start, old_limit, limit); + STBI_NOTUSED(old_limit); if (q == NULL) return stbi__err("outofmem", "Out of memory"); z->zout_start = q; z->zout = q + cur; @@ -2140,7 +3635,7 @@ static int stbi__zlength_base[31] = { 15,17,19,23,27,31,35,43,51,59, 67,83,99,115,131,163,195,227,258,0,0 }; -static int stbi__zlength_extra[31]= +static int stbi__zlength_extra[31]= { 0,0,0,0,0,0,0,0,1,1,1,1,2,2,2,2,3,3,3,3,4,4,4,4,5,5,5,5,0,0,0 }; static int stbi__zdist_base[32] = { 1,2,3,4,5,7,9,13,17,25,33,49,65,97,129,193, @@ -2151,16 +3646,23 @@ static int stbi__zdist_extra[32] = static int stbi__parse_huffman_block(stbi__zbuf *a) { + char *zout = a->zout; for(;;) { int z = stbi__zhuffman_decode(a, &a->z_length); if (z < 256) { if (z < 0) return stbi__err("bad huffman code","Corrupt PNG"); // error in huffman codes - if (a->zout >= a->zout_end) if (!stbi__zexpand(a, 1)) return 0; - *a->zout++ = (char) z; + if (zout >= a->zout_end) { + if (!stbi__zexpand(a, zout, 1)) return 0; + zout = a->zout; + } + *zout++ = (char) z; } else { stbi_uc *p; int len,dist; - if (z == 256) return 1; + if (z == 256) { + a->zout = zout; + return 1; + } z -= 257; len = stbi__zlength_base[z]; if (stbi__zlength_extra[z]) len += stbi__zreceive(a, stbi__zlength_extra[z]); @@ -2168,11 +3670,18 @@ static int stbi__parse_huffman_block(stbi__zbuf *a) if (z < 0) return stbi__err("bad huffman code","Corrupt PNG"); dist = stbi__zdist_base[z]; if (stbi__zdist_extra[z]) dist += stbi__zreceive(a, stbi__zdist_extra[z]); - if (a->zout - a->zout_start < dist) return stbi__err("bad dist","Corrupt PNG"); - if (a->zout + len > a->zout_end) if (!stbi__zexpand(a, len)) return 0; - p = (stbi_uc *) (a->zout - dist); - while (len--) - *a->zout++ = *p++; + if (zout - a->zout_start < dist) return stbi__err("bad dist","Corrupt PNG"); + if (zout + len > a->zout_end) { + if (!stbi__zexpand(a, zout, len)) return 0; + zout = a->zout; + } + p = (stbi_uc *) (zout - dist); + if (dist == 1) { // run of one byte; common in images. + stbi_uc v = *p; + if (len) { do *zout++ = v; while (--len); } + } else { + if (len) { do *zout++ = *p++; while (--len); } + } } } } @@ -2199,7 +3708,7 @@ static int stbi__compute_huffman_codes(stbi__zbuf *a) n = 0; while (n < hlit + hdist) { int c = stbi__zhuffman_decode(a, &z_codelength); - assert(c >= 0 && c < 19); + if (c < 0 || c >= 19) return stbi__err("bad codelengths", "Corrupt PNG"); if (c < 16) lencodes[n++] = (stbi_uc) c; else if (c == 16) { @@ -2211,7 +3720,7 @@ static int stbi__compute_huffman_codes(stbi__zbuf *a) memset(lencodes+n, 0, c); n += c; } else { - assert(c == 18); + STBI_ASSERT(c == 18); c = stbi__zreceive(a,7)+11; memset(lencodes+n, 0, c); n += c; @@ -2236,7 +3745,7 @@ static int stbi__parse_uncomperssed_block(stbi__zbuf *a) a->code_buffer >>= 8; a->num_bits -= 8; } - assert(a->num_bits == 0); + STBI_ASSERT(a->num_bits == 0); // now fill header the normal way while (k < 4) header[k++] = stbi__zget8(a); @@ -2245,7 +3754,7 @@ static int stbi__parse_uncomperssed_block(stbi__zbuf *a) if (nlen != (len ^ 0xffff)) return stbi__err("zlib corrupt","Corrupt PNG"); if (a->zbuffer + len > a->zbuffer_end) return stbi__err("read past buffer","Corrupt PNG"); if (a->zout + len > a->zout_end) - if (!stbi__zexpand(a, len)) return 0; + if (!stbi__zexpand(a, a->zout, len)) return 0; memcpy(a->zout, a->zbuffer, len); a->zbuffer += len; a->zout += len; @@ -2280,13 +3789,13 @@ static void stbi__init_zdefaults(void) static int stbi__parse_zlib(stbi__zbuf *a, int parse_header) { - int final_, type; + int final, type; if (parse_header) if (!stbi__parse_zlib_header(a)) return 0; a->num_bits = 0; a->code_buffer = 0; do { - final_ = stbi__zreceive(a,1); + final = stbi__zreceive(a,1); type = stbi__zreceive(a,2); if (type == 0) { if (!stbi__parse_uncomperssed_block(a)) return 0; @@ -2303,7 +3812,7 @@ static int stbi__parse_zlib(stbi__zbuf *a, int parse_header) } if (!stbi__parse_huffman_block(a)) return 0; } - } while (!final_); + } while (!final); return 1; } @@ -2320,7 +3829,7 @@ static int stbi__do_zlib(stbi__zbuf *a, char *obuf, int olen, int exp, int parse STBIDEF char *stbi_zlib_decode_malloc_guesssize(const char *buffer, int len, int initial_size, int *outlen) { stbi__zbuf a; - char *p = (char *) malloc(initial_size); + char *p = (char *) stbi__malloc(initial_size); if (p == NULL) return NULL; a.zbuffer = (stbi_uc *) buffer; a.zbuffer_end = (stbi_uc *) buffer + len; @@ -2328,7 +3837,7 @@ STBIDEF char *stbi_zlib_decode_malloc_guesssize(const char *buffer, int len, int if (outlen) *outlen = (int) (a.zout - a.zout_start); return a.zout_start; } else { - free(a.zout_start); + STBI_FREE(a.zout_start); return NULL; } } @@ -2341,7 +3850,7 @@ STBIDEF char *stbi_zlib_decode_malloc(char const *buffer, int len, int *outlen) STBIDEF char *stbi_zlib_decode_malloc_guesssize_headerflag(const char *buffer, int len, int initial_size, int *outlen, int parse_header) { stbi__zbuf a; - char *p = (char *) malloc(initial_size); + char *p = (char *) stbi__malloc(initial_size); if (p == NULL) return NULL; a.zbuffer = (stbi_uc *) buffer; a.zbuffer_end = (stbi_uc *) buffer + len; @@ -2349,7 +3858,7 @@ STBIDEF char *stbi_zlib_decode_malloc_guesssize_headerflag(const char *buffer, i if (outlen) *outlen = (int) (a.zout - a.zout_start); return a.zout_start; } else { - free(a.zout_start); + STBI_FREE(a.zout_start); return NULL; } } @@ -2368,7 +3877,7 @@ STBIDEF int stbi_zlib_decode_buffer(char *obuffer, int olen, char const *ibuffer STBIDEF char *stbi_zlib_decode_noheader_malloc(char const *buffer, int len, int *outlen) { stbi__zbuf a; - char *p = (char *) malloc(16384); + char *p = (char *) stbi__malloc(16384); if (p == NULL) return NULL; a.zbuffer = (stbi_uc *) buffer; a.zbuffer_end = (stbi_uc *) buffer+len; @@ -2376,7 +3885,7 @@ STBIDEF char *stbi_zlib_decode_noheader_malloc(char const *buffer, int len, int if (outlen) *outlen = (int) (a.zout - a.zout_start); return a.zout_start; } else { - free(a.zout_start); + STBI_FREE(a.zout_start); return NULL; } } @@ -2391,6 +3900,7 @@ STBIDEF int stbi_zlib_decode_noheader_buffer(char *obuffer, int olen, const char else return -1; } +#endif // public domain "baseline" PNG decoder v0.10 Sean Barrett 2006-11-18 // simple implementation @@ -2402,15 +3912,13 @@ STBIDEF int stbi_zlib_decode_noheader_buffer(char *obuffer, int olen, const char // performance // - uses stb_zlib, a PD zlib implementation with fast huffman decoding - +#ifndef STBI_NO_PNG typedef struct { stbi__uint32 length; stbi__uint32 type; } stbi__pngchunk; -#define PNG_TYPE(a,b,c,d) (((a) << 24) + ((b) << 16) + ((c) << 8) + (d)) - static stbi__pngchunk stbi__get_chunk_header(stbi__context *s) { stbi__pngchunk c; @@ -2436,13 +3944,23 @@ typedef struct enum { - STBI__F_none=0, STBI__F_sub=1, STBI__F_up=2, STBI__F_avg=3, STBI__F_paeth=4, - STBI__F_avg_first, STBI__F_paeth_first + STBI__F_none=0, + STBI__F_sub=1, + STBI__F_up=2, + STBI__F_avg=3, + STBI__F_paeth=4, + // synthetic filters used for first scanline to avoid needing a dummy row of 0s + STBI__F_avg_first, + STBI__F_paeth_first }; static stbi_uc first_row_filter[5] = { - STBI__F_none, STBI__F_sub, STBI__F_none, STBI__F_avg_first, STBI__F_paeth_first + STBI__F_none, + STBI__F_sub, + STBI__F_none, + STBI__F_avg_first, + STBI__F_paeth_first }; static int stbi__paeth(int a, int b, int c) @@ -2456,32 +3974,50 @@ static int stbi__paeth(int a, int b, int c) return c; } -#define STBI__BYTECAST(x) ((stbi_uc) ((x) & 255)) // truncate int to byte without warnings +static stbi_uc stbi__depth_scale_table[9] = { 0, 0xff, 0x55, 0, 0x11, 0,0,0, 0x01 }; // create the png data from post-deflated data -static int stbi__create_png_image_raw(stbi__png *a, stbi_uc *raw, stbi__uint32 raw_len, int out_n, stbi__uint32 x, stbi__uint32 y) +static int stbi__create_png_image_raw(stbi__png *a, stbi_uc *raw, stbi__uint32 raw_len, int out_n, stbi__uint32 x, stbi__uint32 y, int depth, int color) { stbi__context *s = a->s; stbi__uint32 i,j,stride = x*out_n; + stbi__uint32 img_len, img_width_bytes; int k; int img_n = s->img_n; // copy it into a local for later - assert(out_n == s->img_n || out_n == s->img_n+1); - a->out = (stbi_uc *) malloc(x * y * out_n); + + STBI_ASSERT(out_n == s->img_n || out_n == s->img_n+1); + a->out = (stbi_uc *) stbi__malloc(x * y * out_n); // extra bytes to write off the end into if (!a->out) return stbi__err("outofmem", "Out of memory"); + + img_width_bytes = (((img_n * x * depth) + 7) >> 3); + img_len = (img_width_bytes + 1) * y; if (s->img_x == x && s->img_y == y) { - if (raw_len != (img_n * x + 1) * y) return stbi__err("not enough pixels","Corrupt PNG"); + if (raw_len != img_len) return stbi__err("not enough pixels","Corrupt PNG"); } else { // interlaced: - if (raw_len < (img_n * x + 1) * y) return stbi__err("not enough pixels","Corrupt PNG"); + if (raw_len < img_len) return stbi__err("not enough pixels","Corrupt PNG"); } + for (j=0; j < y; ++j) { stbi_uc *cur = a->out + stride*j; stbi_uc *prior = cur - stride; int filter = *raw++; - if (filter > 4) return stbi__err("invalid filter","Corrupt PNG"); + int filter_bytes = img_n; + int width = x; + if (filter > 4) + return stbi__err("invalid filter","Corrupt PNG"); + + if (depth < 8) { + STBI_ASSERT(img_width_bytes <= x); + cur += x*out_n - img_width_bytes; // store output to the rightmost img_len bytes, so we can decode in place + filter_bytes = 1; + width = img_width_bytes; + } + // if first row, use special filter that doesn't sample previous row if (j == 0) filter = first_row_filter[filter]; - // handle first pixel explicitly - for (k=0; k < img_n; ++k) { + + // handle first byte explicitly + for (k=0; k < filter_bytes; ++k) { switch (filter) { case STBI__F_none : cur[k] = raw[k]; break; case STBI__F_sub : cur[k] = raw[k]; break; @@ -2492,28 +4028,39 @@ static int stbi__create_png_image_raw(stbi__png *a, stbi_uc *raw, stbi__uint32 r case STBI__F_paeth_first: cur[k] = raw[k]; break; } } - if (img_n != out_n) cur[img_n] = 255; - raw += img_n; - cur += out_n; - prior += out_n; + + if (depth == 8) { + if (img_n != out_n) + cur[img_n] = 255; // first pixel + raw += img_n; + cur += out_n; + prior += out_n; + } else { + raw += 1; + cur += 1; + prior += 1; + } + // this is a little gross, so that we don't switch per-pixel or per-component - if (img_n == out_n) { + if (depth < 8 || img_n == out_n) { + int nk = (width - 1)*img_n; #define CASE(f) \ case f: \ - for (i=x-1; i >= 1; --i, raw+=img_n,cur+=img_n,prior+=img_n) \ - for (k=0; k < img_n; ++k) + for (k=0; k < nk; ++k) switch (filter) { - CASE(STBI__F_none) cur[k] = raw[k]; break; - CASE(STBI__F_sub) cur[k] = STBI__BYTECAST(raw[k] + cur[k-img_n]); break; + // "none" filter turns into a memcpy here; make that explicit. + case STBI__F_none: memcpy(cur, raw, nk); break; + CASE(STBI__F_sub) cur[k] = STBI__BYTECAST(raw[k] + cur[k-filter_bytes]); break; CASE(STBI__F_up) cur[k] = STBI__BYTECAST(raw[k] + prior[k]); break; - CASE(STBI__F_avg) cur[k] = STBI__BYTECAST(raw[k] + ((prior[k] + cur[k-img_n])>>1)); break; - CASE(STBI__F_paeth) cur[k] = STBI__BYTECAST(raw[k] + stbi__paeth(cur[k-img_n],prior[k],prior[k-img_n])); break; - CASE(STBI__F_avg_first) cur[k] = STBI__BYTECAST(raw[k] + (cur[k-img_n] >> 1)); break; - CASE(STBI__F_paeth_first) cur[k] = STBI__BYTECAST(raw[k] + stbi__paeth(cur[k-img_n],0,0)); break; + CASE(STBI__F_avg) cur[k] = STBI__BYTECAST(raw[k] + ((prior[k] + cur[k-filter_bytes])>>1)); break; + CASE(STBI__F_paeth) cur[k] = STBI__BYTECAST(raw[k] + stbi__paeth(cur[k-filter_bytes],prior[k],prior[k-filter_bytes])); break; + CASE(STBI__F_avg_first) cur[k] = STBI__BYTECAST(raw[k] + (cur[k-filter_bytes] >> 1)); break; + CASE(STBI__F_paeth_first) cur[k] = STBI__BYTECAST(raw[k] + stbi__paeth(cur[k-filter_bytes],0,0)); break; } #undef CASE + raw += nk; } else { - assert(img_n+1 == out_n); + STBI_ASSERT(img_n+1 == out_n); #define CASE(f) \ case f: \ for (i=x-1; i >= 1; --i, cur[img_n]=255,raw+=img_n,cur+=out_n,prior+=out_n) \ @@ -2530,18 +4077,93 @@ static int stbi__create_png_image_raw(stbi__png *a, stbi_uc *raw, stbi__uint32 r #undef CASE } } + + // we make a separate pass to expand bits to pixels; for performance, + // this could run two scanlines behind the above code, so it won't + // intefere with filtering but will still be in the cache. + if (depth < 8) { + for (j=0; j < y; ++j) { + stbi_uc *cur = a->out + stride*j; + stbi_uc *in = a->out + stride*j + x*out_n - img_width_bytes; + // unpack 1/2/4-bit into a 8-bit buffer. allows us to keep the common 8-bit path optimal at minimal cost for 1/2/4-bit + // png guarante byte alignment, if width is not multiple of 8/4/2 we'll decode dummy trailing data that will be skipped in the later loop + stbi_uc scale = (color == 0) ? stbi__depth_scale_table[depth] : 1; // scale grayscale values to 0..255 range + + // note that the final byte might overshoot and write more data than desired. + // we can allocate enough data that this never writes out of memory, but it + // could also overwrite the next scanline. can it overwrite non-empty data + // on the next scanline? yes, consider 1-pixel-wide scanlines with 1-bit-per-pixel. + // so we need to explicitly clamp the final ones + + if (depth == 4) { + for (k=x*img_n; k >= 2; k-=2, ++in) { + *cur++ = scale * ((*in >> 4) ); + *cur++ = scale * ((*in ) & 0x0f); + } + if (k > 0) *cur++ = scale * ((*in >> 4) ); + } else if (depth == 2) { + for (k=x*img_n; k >= 4; k-=4, ++in) { + *cur++ = scale * ((*in >> 6) ); + *cur++ = scale * ((*in >> 4) & 0x03); + *cur++ = scale * ((*in >> 2) & 0x03); + *cur++ = scale * ((*in ) & 0x03); + } + if (k > 0) *cur++ = scale * ((*in >> 6) ); + if (k > 1) *cur++ = scale * ((*in >> 4) & 0x03); + if (k > 2) *cur++ = scale * ((*in >> 2) & 0x03); + } else if (depth == 1) { + for (k=x*img_n; k >= 8; k-=8, ++in) { + *cur++ = scale * ((*in >> 7) ); + *cur++ = scale * ((*in >> 6) & 0x01); + *cur++ = scale * ((*in >> 5) & 0x01); + *cur++ = scale * ((*in >> 4) & 0x01); + *cur++ = scale * ((*in >> 3) & 0x01); + *cur++ = scale * ((*in >> 2) & 0x01); + *cur++ = scale * ((*in >> 1) & 0x01); + *cur++ = scale * ((*in ) & 0x01); + } + if (k > 0) *cur++ = scale * ((*in >> 7) ); + if (k > 1) *cur++ = scale * ((*in >> 6) & 0x01); + if (k > 2) *cur++ = scale * ((*in >> 5) & 0x01); + if (k > 3) *cur++ = scale * ((*in >> 4) & 0x01); + if (k > 4) *cur++ = scale * ((*in >> 3) & 0x01); + if (k > 5) *cur++ = scale * ((*in >> 2) & 0x01); + if (k > 6) *cur++ = scale * ((*in >> 1) & 0x01); + } + if (img_n != out_n) { + int q; + // insert alpha = 255 + cur = a->out + stride*j; + if (img_n == 1) { + for (q=x-1; q >= 0; --q) { + cur[q*2+1] = 255; + cur[q*2+0] = cur[q]; + } + } else { + STBI_ASSERT(img_n == 3); + for (q=x-1; q >= 0; --q) { + cur[q*4+3] = 255; + cur[q*4+2] = cur[q*3+2]; + cur[q*4+1] = cur[q*3+1]; + cur[q*4+0] = cur[q*3+0]; + } + } + } + } + } + return 1; } -static int stbi__create_png_image(stbi__png *a, stbi_uc *raw, stbi__uint32 raw_len, int out_n, int interlaced) +static int stbi__create_png_image(stbi__png *a, stbi_uc *image_data, stbi__uint32 image_data_len, int out_n, int depth, int color, int interlaced) { - stbi_uc *final_; + stbi_uc *final; int p; if (!interlaced) - return stbi__create_png_image_raw(a, raw, raw_len, out_n, a->s->img_x, a->s->img_y); + return stbi__create_png_image_raw(a, image_data, image_data_len, out_n, a->s->img_x, a->s->img_y, depth, color); // de-interlacing - final_ = (stbi_uc *) malloc(a->s->img_x * a->s->img_y * out_n); + final = (stbi_uc *) stbi__malloc(a->s->img_x * a->s->img_y * out_n); for (p=0; p < 7; ++p) { int xorig[] = { 0,4,0,2,0,1,0 }; int yorig[] = { 0,0,4,0,2,0,1 }; @@ -2552,20 +4174,25 @@ static int stbi__create_png_image(stbi__png *a, stbi_uc *raw, stbi__uint32 raw_l x = (a->s->img_x - xorig[p] + xspc[p]-1) / xspc[p]; y = (a->s->img_y - yorig[p] + yspc[p]-1) / yspc[p]; if (x && y) { - if (!stbi__create_png_image_raw(a, raw, raw_len, out_n, x, y)) { - free(final_); + stbi__uint32 img_len = ((((a->s->img_n * x * depth) + 7) >> 3) + 1) * y; + if (!stbi__create_png_image_raw(a, image_data, image_data_len, out_n, x, y, depth, color)) { + STBI_FREE(final); return 0; } - for (j=0; j < y; ++j) - for (i=0; i < x; ++i) - memcpy(final_ + (j*yspc[p]+yorig[p])*a->s->img_x*out_n + (i*xspc[p]+xorig[p])*out_n, + for (j=0; j < y; ++j) { + for (i=0; i < x; ++i) { + int out_y = j*yspc[p]+yorig[p]; + int out_x = i*xspc[p]+xorig[p]; + memcpy(final + out_y*a->s->img_x*out_n + out_x*out_n, a->out + (j*x+i)*out_n, out_n); - free(a->out); - raw += (x*out_n+1)*y; - raw_len -= (x*out_n+1)*y; + } + } + STBI_FREE(a->out); + image_data += img_len; + image_data_len -= img_len; } } - a->out = final_; + a->out = final; return 1; } @@ -2578,7 +4205,7 @@ static int stbi__compute_transparency(stbi__png *z, stbi_uc tc[3], int out_n) // compute color-based transparency, assuming we've // already got 255 as the alpha value in the output - assert(out_n == 2 || out_n == 4); + STBI_ASSERT(out_n == 2 || out_n == 4); if (out_n == 2) { for (i=0; i < pixel_count; ++i) { @@ -2600,7 +4227,7 @@ static int stbi__expand_png_palette(stbi__png *a, stbi_uc *palette, int len, int stbi__uint32 i, pixel_count = a->s->img_x * a->s->img_y; stbi_uc *p, *temp_out, *orig = a->out; - p = (stbi_uc *) malloc(pixel_count * pal_img_n); + p = (stbi_uc *) stbi__malloc(pixel_count * pal_img_n); if (p == NULL) return stbi__err("outofmem", "Out of memory"); // between here and free(out) below, exitting would leak @@ -2624,7 +4251,7 @@ static int stbi__expand_png_palette(stbi__png *a, stbi_uc *palette, int len, int p += 4; } } - free(a->out); + STBI_FREE(a->out); a->out = temp_out; STBI_NOTUSED(len); @@ -2659,7 +4286,7 @@ static void stbi__de_iphone(stbi__png *z) p += 3; } } else { - assert(s->img_out_n == 4); + STBI_ASSERT(s->img_out_n == 4); if (stbi__unpremultiply_on_load) { // convert bgr to rgb and unpremultiply for (i=0; i < pixel_count; ++i) { @@ -2672,7 +4299,7 @@ static void stbi__de_iphone(stbi__png *z) } else { p[0] = p[2]; p[2] = t; - } + } p += 4; } } else { @@ -2687,12 +4314,14 @@ static void stbi__de_iphone(stbi__png *z) } } +#define STBI__PNG_TYPE(a,b,c,d) (((a) << 24) + ((b) << 16) + ((c) << 8) + (d)) + static int stbi__parse_png_file(stbi__png *z, int scan, int req_comp) { stbi_uc palette[1024], pal_img_n=0; stbi_uc has_trans=0, tc[3]; stbi__uint32 ioff=0, idata_limit=0, i, pal_len=0; - int first=1,k,interlace=0, is_iphone=0; + int first=1,k,interlace=0, color=0, depth=0, is_iphone=0; stbi__context *s = z->s; z->expanded = NULL; @@ -2701,23 +4330,23 @@ static int stbi__parse_png_file(stbi__png *z, int scan, int req_comp) if (!stbi__check_png_header(s)) return 0; - if (scan == SCAN_type) return 1; + if (scan == STBI__SCAN_type) return 1; for (;;) { stbi__pngchunk c = stbi__get_chunk_header(s); switch (c.type) { - case PNG_TYPE('C','g','B','I'): + case STBI__PNG_TYPE('C','g','B','I'): is_iphone = 1; stbi__skip(s, c.length); break; - case PNG_TYPE('I','H','D','R'): { - int depth,color,comp,filter; + case STBI__PNG_TYPE('I','H','D','R'): { + int comp,filter; if (!first) return stbi__err("multiple IHDR","Corrupt PNG"); first = 0; if (c.length != 13) return stbi__err("bad IHDR len","Corrupt PNG"); s->img_x = stbi__get32be(s); if (s->img_x > (1 << 24)) return stbi__err("too large","Very large image (corrupt?)"); s->img_y = stbi__get32be(s); if (s->img_y > (1 << 24)) return stbi__err("too large","Very large image (corrupt?)"); - depth = stbi__get8(s); if (depth != 8) return stbi__err("8bit only","PNG not supported: 8-bit only"); + depth = stbi__get8(s); if (depth != 1 && depth != 2 && depth != 4 && depth != 8) return stbi__err("1/2/4/8-bit only","PNG not supported: 1/2/4/8-bit only"); color = stbi__get8(s); if (color > 6) return stbi__err("bad ctype","Corrupt PNG"); if (color == 3) pal_img_n = 3; else if (color & 1) return stbi__err("bad ctype","Corrupt PNG"); comp = stbi__get8(s); if (comp) return stbi__err("bad comp method","Corrupt PNG"); @@ -2726,8 +4355,8 @@ static int stbi__parse_png_file(stbi__png *z, int scan, int req_comp) if (!s->img_x || !s->img_y) return stbi__err("0-pixel image","Corrupt PNG"); if (!pal_img_n) { s->img_n = (color & 2 ? 3 : 1) + (color & 4 ? 1 : 0); - if ((1 << 30) / s->img_x / s->img_n < s->img_y) return stbi__err("too large", "Image too large to stbi__jpeg_huff_decode"); - if (scan == SCAN_header) return 1; + if ((1 << 30) / s->img_x / s->img_n < s->img_y) return stbi__err("too large", "Image too large to decode"); + if (scan == STBI__SCAN_header) return 1; } else { // if paletted, then pal_n is our final components, and // img_n is # components to decompress/filter. @@ -2738,7 +4367,7 @@ static int stbi__parse_png_file(stbi__png *z, int scan, int req_comp) break; } - case PNG_TYPE('P','L','T','E'): { + case STBI__PNG_TYPE('P','L','T','E'): { if (first) return stbi__err("first not IHDR", "Corrupt PNG"); if (c.length > 256*3) return stbi__err("invalid PLTE","Corrupt PNG"); pal_len = c.length / 3; @@ -2752,11 +4381,11 @@ static int stbi__parse_png_file(stbi__png *z, int scan, int req_comp) break; } - case PNG_TYPE('t','R','N','S'): { + case STBI__PNG_TYPE('t','R','N','S'): { if (first) return stbi__err("first not IHDR", "Corrupt PNG"); if (z->idata) return stbi__err("tRNS after IDAT","Corrupt PNG"); if (pal_img_n) { - if (scan == SCAN_header) { s->img_n = 4; return 1; } + if (scan == STBI__SCAN_header) { s->img_n = 4; return 1; } if (pal_len == 0) return stbi__err("tRNS before PLTE","Corrupt PNG"); if (c.length > pal_len) return stbi__err("bad tRNS len","Corrupt PNG"); pal_img_n = 4; @@ -2767,21 +4396,24 @@ static int stbi__parse_png_file(stbi__png *z, int scan, int req_comp) if (c.length != (stbi__uint32) s->img_n*2) return stbi__err("bad tRNS len","Corrupt PNG"); has_trans = 1; for (k=0; k < s->img_n; ++k) - tc[k] = (stbi_uc) (stbi__get16be(s) & 255); // non 8-bit images will be larger + tc[k] = (stbi_uc) (stbi__get16be(s) & 255) * stbi__depth_scale_table[depth]; // non 8-bit images will be larger } break; } - case PNG_TYPE('I','D','A','T'): { + case STBI__PNG_TYPE('I','D','A','T'): { if (first) return stbi__err("first not IHDR", "Corrupt PNG"); if (pal_img_n && !pal_len) return stbi__err("no PLTE","Corrupt PNG"); - if (scan == SCAN_header) { s->img_n = pal_img_n; return 1; } + if (scan == STBI__SCAN_header) { s->img_n = pal_img_n; return 1; } + if ((int)(ioff + c.length) < (int)ioff) return 0; if (ioff + c.length > idata_limit) { + stbi__uint32 idata_limit_old = idata_limit; stbi_uc *p; if (idata_limit == 0) idata_limit = c.length > 4096 ? c.length : 4096; while (ioff + c.length > idata_limit) idata_limit *= 2; - p = (stbi_uc *) realloc(z->idata, idata_limit); if (p == NULL) return stbi__err("outofmem", "Out of memory"); + STBI_NOTUSED(idata_limit_old); + p = (stbi_uc *) STBI_REALLOC_SIZED(z->idata, idata_limit_old, idata_limit); if (p == NULL) return stbi__err("outofmem", "Out of memory"); z->idata = p; } if (!stbi__getn(s, z->idata+ioff,c.length)) return stbi__err("outofdata","Corrupt PNG"); @@ -2789,19 +4421,22 @@ static int stbi__parse_png_file(stbi__png *z, int scan, int req_comp) break; } - case PNG_TYPE('I','E','N','D'): { - stbi__uint32 raw_len; + case STBI__PNG_TYPE('I','E','N','D'): { + stbi__uint32 raw_len, bpl; if (first) return stbi__err("first not IHDR", "Corrupt PNG"); - if (scan != SCAN_load) return 1; + if (scan != STBI__SCAN_load) return 1; if (z->idata == NULL) return stbi__err("no IDAT","Corrupt PNG"); - z->expanded = (stbi_uc *) stbi_zlib_decode_malloc_guesssize_headerflag((char *) z->idata, ioff, 16384, (int *) &raw_len, !is_iphone); + // initial guess for decoded data size to avoid unnecessary reallocs + bpl = (s->img_x * depth + 7) / 8; // bytes per line, per component + raw_len = bpl * s->img_y * s->img_n /* pixels */ + s->img_y /* filter mode per row */; + z->expanded = (stbi_uc *) stbi_zlib_decode_malloc_guesssize_headerflag((char *) z->idata, ioff, raw_len, (int *) &raw_len, !is_iphone); if (z->expanded == NULL) return 0; // zlib should set error - free(z->idata); z->idata = NULL; + STBI_FREE(z->idata); z->idata = NULL; if ((req_comp == s->img_n+1 && req_comp != 3 && !pal_img_n) || has_trans) s->img_out_n = s->img_n+1; else s->img_out_n = s->img_n; - if (!stbi__create_png_image(z, z->expanded, raw_len, s->img_out_n, interlace)) return 0; + if (!stbi__create_png_image(z, z->expanded, raw_len, s->img_out_n, depth, color, interlace)) return 0; if (has_trans) if (!stbi__compute_transparency(z, tc, s->img_out_n)) return 0; if (is_iphone && stbi__de_iphone_flag && s->img_out_n > 2) @@ -2814,7 +4449,7 @@ static int stbi__parse_png_file(stbi__png *z, int scan, int req_comp) if (!stbi__expand_png_palette(z, palette, pal_len, s->img_out_n)) return 0; } - free(z->expanded); z->expanded = NULL; + STBI_FREE(z->expanded); z->expanded = NULL; return 1; } @@ -2844,7 +4479,7 @@ static unsigned char *stbi__do_png(stbi__png *p, int *x, int *y, int *n, int req { unsigned char *result=NULL; if (req_comp < 0 || req_comp > 4) return stbi__errpuc("bad req_comp", "Internal error"); - if (stbi__parse_png_file(p, SCAN_load, req_comp)) { + if (stbi__parse_png_file(p, STBI__SCAN_load, req_comp)) { result = p->out; p->out = NULL; if (req_comp && req_comp != p->s->img_out_n) { @@ -2854,11 +4489,11 @@ static unsigned char *stbi__do_png(stbi__png *p, int *x, int *y, int *n, int req } *x = p->s->img_x; *y = p->s->img_y; - if (n) *n = p->s->img_n; + if (n) *n = p->s->img_out_n; } - free(p->out); p->out = NULL; - free(p->expanded); p->expanded = NULL; - free(p->idata); p->idata = NULL; + STBI_FREE(p->out); p->out = NULL; + STBI_FREE(p->expanded); p->expanded = NULL; + STBI_FREE(p->idata); p->idata = NULL; return result; } @@ -2880,7 +4515,7 @@ static int stbi__png_test(stbi__context *s) static int stbi__png_info_raw(stbi__png *p, int *x, int *y, int *comp) { - if (!stbi__parse_png_file(p, SCAN_header, 0)) { + if (!stbi__parse_png_file(p, STBI__SCAN_header, 0)) { stbi__rewind( p->s ); return 0; } @@ -2896,8 +4531,11 @@ static int stbi__png_info(stbi__context *s, int *x, int *y, int *comp) p.s = s; return stbi__png_info_raw(&p, x, y, comp); } +#endif // Microsoft/Windows BMP image + +#ifndef STBI_NO_BMP static int stbi__bmp_test_raw(stbi__context *s) { int r; @@ -2961,19 +4599,22 @@ static int stbi__shiftsigned(int v, int shift, int bits) return result; } -static stbi_uc *stbi__bmp_load(stbi__context *s, int *x, int *y, int *comp, int req_comp) +typedef struct { - stbi_uc *out; - unsigned int mr=0,mg=0,mb=0,ma=0, fake_a=0; - stbi_uc pal[256][4]; - int psize=0,i,j,compress=0,width; - int bpp, flip_vertically, pad, target, offset, hsz; + int bpp, offset, hsz; + unsigned int mr,mg,mb,ma, all_a; +} stbi__bmp_data; + +static void *stbi__bmp_parse_header(stbi__context *s, stbi__bmp_data *info) +{ + int hsz; if (stbi__get8(s) != 'B' || stbi__get8(s) != 'M') return stbi__errpuc("not BMP", "Corrupt BMP"); stbi__get32le(s); // discard filesize stbi__get16le(s); // discard reserved stbi__get16le(s); // discard reserved - offset = stbi__get32le(s); - hsz = stbi__get32le(s); + info->offset = stbi__get32le(s); + info->hsz = hsz = stbi__get32le(s); + if (hsz != 12 && hsz != 40 && hsz != 56 && hsz != 108 && hsz != 124) return stbi__errpuc("unknown BMP", "BMP type not supported: unknown"); if (hsz == 12) { s->img_x = stbi__get16le(s); @@ -2983,15 +4624,10 @@ static stbi_uc *stbi__bmp_load(stbi__context *s, int *x, int *y, int *comp, int s->img_y = stbi__get32le(s); } if (stbi__get16le(s) != 1) return stbi__errpuc("bad BMP", "bad BMP"); - bpp = stbi__get16le(s); - if (bpp == 1) return stbi__errpuc("monochrome", "BMP type not supported: 1-bit"); - flip_vertically = ((int) s->img_y) > 0; - s->img_y = abs((int) s->img_y); - if (hsz == 12) { - if (bpp < 24) - psize = (offset - 14 - 24) / 3; - } else { - compress = stbi__get32le(s); + info->bpp = stbi__get16le(s); + if (info->bpp == 1) return stbi__errpuc("monochrome", "BMP type not supported: 1-bit"); + if (hsz != 12) { + int compress = stbi__get32le(s); if (compress == 1 || compress == 2) return stbi__errpuc("BMP RLE", "BMP type not supported: RLE"); stbi__get32le(s); // discard sizeof stbi__get32le(s); // discard hres @@ -3005,27 +4641,26 @@ static stbi_uc *stbi__bmp_load(stbi__context *s, int *x, int *y, int *comp, int stbi__get32le(s); stbi__get32le(s); } - if (bpp == 16 || bpp == 32) { - mr = mg = mb = 0; + if (info->bpp == 16 || info->bpp == 32) { + info->mr = info->mg = info->mb = 0; if (compress == 0) { - if (bpp == 32) { - mr = 0xffu << 16; - mg = 0xffu << 8; - mb = 0xffu << 0; - ma = 0xffu << 24; - fake_a = 1; // @TODO: check for cases like alpha value is all 0 and switch it to 255 - STBI_NOTUSED(fake_a); + if (info->bpp == 32) { + info->mr = 0xffu << 16; + info->mg = 0xffu << 8; + info->mb = 0xffu << 0; + info->ma = 0xffu << 24; + info->all_a = 0; // if all_a is 0 at end, then we loaded alpha channel but it was all 0 } else { - mr = 31u << 10; - mg = 31u << 5; - mb = 31u << 0; + info->mr = 31u << 10; + info->mg = 31u << 5; + info->mb = 31u << 0; } } else if (compress == 3) { - mr = stbi__get32le(s); - mg = stbi__get32le(s); - mb = stbi__get32le(s); + info->mr = stbi__get32le(s); + info->mg = stbi__get32le(s); + info->mb = stbi__get32le(s); // not documented, but generated by photoshop and handled by mspaint - if (mr == mg && mg == mb) { + if (info->mr == info->mg && info->mg == info->mb) { // ?!?!? return stbi__errpuc("bad BMP", "bad BMP"); } @@ -3033,11 +4668,13 @@ static stbi_uc *stbi__bmp_load(stbi__context *s, int *x, int *y, int *comp, int return stbi__errpuc("bad BMP", "bad BMP"); } } else { - assert(hsz == 108 || hsz == 124); - mr = stbi__get32le(s); - mg = stbi__get32le(s); - mb = stbi__get32le(s); - ma = stbi__get32le(s); + int i; + if (hsz != 108 && hsz != 124) + return stbi__errpuc("bad BMP", "bad BMP"); + info->mr = stbi__get32le(s); + info->mg = stbi__get32le(s); + info->mb = stbi__get32le(s); + info->ma = stbi__get32le(s); stbi__get32le(s); // discard color space for (i=0; i < 12; ++i) stbi__get32le(s); // discard color space parameters @@ -3048,35 +4685,68 @@ static stbi_uc *stbi__bmp_load(stbi__context *s, int *x, int *y, int *comp, int stbi__get32le(s); // discard reserved } } - if (bpp < 16) - psize = (offset - 14 - hsz) >> 2; } + return (void *) 1; +} + + +static stbi_uc *stbi__bmp_load(stbi__context *s, int *x, int *y, int *comp, int req_comp) +{ + stbi_uc *out; + unsigned int mr=0,mg=0,mb=0,ma=0, all_a; + stbi_uc pal[256][4]; + int psize=0,i,j,width; + int flip_vertically, pad, target; + stbi__bmp_data info; + + info.all_a = 255; + if (stbi__bmp_parse_header(s, &info) == NULL) + return NULL; // error code already set + + flip_vertically = ((int) s->img_y) > 0; + s->img_y = abs((int) s->img_y); + + mr = info.mr; + mg = info.mg; + mb = info.mb; + ma = info.ma; + all_a = info.all_a; + + if (info.hsz == 12) { + if (info.bpp < 24) + psize = (info.offset - 14 - 24) / 3; + } else { + if (info.bpp < 16) + psize = (info.offset - 14 - info.hsz) >> 2; + } + s->img_n = ma ? 4 : 3; - if (req_comp && req_comp >= 3) // we can directly stbi__jpeg_huff_decode 3 or 4 + if (req_comp && req_comp >= 3) // we can directly decode 3 or 4 target = req_comp; else target = s->img_n; // if they want monochrome, we'll post-convert - out = (stbi_uc *) malloc(target * s->img_x * s->img_y); + + out = (stbi_uc *) stbi__malloc(target * s->img_x * s->img_y); if (!out) return stbi__errpuc("outofmem", "Out of memory"); - if (bpp < 16) { + if (info.bpp < 16) { int z=0; - if (psize == 0 || psize > 256) { free(out); return stbi__errpuc("invalid", "Corrupt BMP"); } + if (psize == 0 || psize > 256) { STBI_FREE(out); return stbi__errpuc("invalid", "Corrupt BMP"); } for (i=0; i < psize; ++i) { pal[i][2] = stbi__get8(s); pal[i][1] = stbi__get8(s); pal[i][0] = stbi__get8(s); - if (hsz != 12) stbi__get8(s); + if (info.hsz != 12) stbi__get8(s); pal[i][3] = 255; } - stbi__skip(s, offset - 14 - hsz - psize * (hsz == 12 ? 3 : 4)); - if (bpp == 4) width = (s->img_x + 1) >> 1; - else if (bpp == 8) width = s->img_x; - else { free(out); return stbi__errpuc("bad bpp", "Corrupt BMP"); } + stbi__skip(s, info.offset - 14 - info.hsz - psize * (info.hsz == 12 ? 3 : 4)); + if (info.bpp == 4) width = (s->img_x + 1) >> 1; + else if (info.bpp == 8) width = s->img_x; + else { STBI_FREE(out); return stbi__errpuc("bad bpp", "Corrupt BMP"); } pad = (-width)&3; for (j=0; j < (int) s->img_y; ++j) { for (i=0; i < (int) s->img_x; i += 2) { int v=stbi__get8(s),v2=0; - if (bpp == 4) { + if (info.bpp == 4) { v2 = v & 15; v >>= 4; } @@ -3085,7 +4755,7 @@ static stbi_uc *stbi__bmp_load(stbi__context *s, int *x, int *y, int *comp, int out[z++] = pal[v][2]; if (target == 4) out[z++] = 255; if (i+1 == (int) s->img_x) break; - v = (bpp == 8) ? stbi__get8(s) : v2; + v = (info.bpp == 8) ? stbi__get8(s) : v2; out[z++] = pal[v][0]; out[z++] = pal[v][1]; out[z++] = pal[v][2]; @@ -3097,19 +4767,19 @@ static stbi_uc *stbi__bmp_load(stbi__context *s, int *x, int *y, int *comp, int int rshift=0,gshift=0,bshift=0,ashift=0,rcount=0,gcount=0,bcount=0,acount=0; int z = 0; int easy=0; - stbi__skip(s, offset - 14 - hsz); - if (bpp == 24) width = 3 * s->img_x; - else if (bpp == 16) width = 2*s->img_x; + stbi__skip(s, info.offset - 14 - info.hsz); + if (info.bpp == 24) width = 3 * s->img_x; + else if (info.bpp == 16) width = 2*s->img_x; else /* bpp = 32 and pad = 0 */ width=0; pad = (-width) & 3; - if (bpp == 24) { + if (info.bpp == 24) { easy = 1; - } else if (bpp == 32) { + } else if (info.bpp == 32) { if (mb == 0xff && mg == 0xff00 && mr == 0x00ff0000 && ma == 0xff000000) easy = 2; } if (!easy) { - if (!mr || !mg || !mb) { free(out); return stbi__errpuc("bad masks", "Corrupt BMP"); } + if (!mr || !mg || !mb) { STBI_FREE(out); return stbi__errpuc("bad masks", "Corrupt BMP"); } // right shift amt to put high bit in position #7 rshift = stbi__high_bit(mr)-7; rcount = stbi__bitcount(mr); gshift = stbi__high_bit(mg)-7; gcount = stbi__bitcount(mg); @@ -3125,22 +4795,31 @@ static stbi_uc *stbi__bmp_load(stbi__context *s, int *x, int *y, int *comp, int out[z+0] = stbi__get8(s); z += 3; a = (easy == 2 ? stbi__get8(s) : 255); + all_a |= a; if (target == 4) out[z++] = a; } } else { + int bpp = info.bpp; for (i=0; i < (int) s->img_x; ++i) { - stbi__uint32 v = (stbi__uint32) (bpp == 16 ? stbi__get16le(s) : stbi__get32le(s)); + stbi__uint32 v = (bpp == 16 ? (stbi__uint32) stbi__get16le(s) : stbi__get32le(s)); int a; out[z++] = STBI__BYTECAST(stbi__shiftsigned(v & mr, rshift, rcount)); out[z++] = STBI__BYTECAST(stbi__shiftsigned(v & mg, gshift, gcount)); out[z++] = STBI__BYTECAST(stbi__shiftsigned(v & mb, bshift, bcount)); a = (ma ? stbi__shiftsigned(v & ma, ashift, acount) : 255); - if (target == 4) out[z++] = STBI__BYTECAST(a); + all_a |= a; + if (target == 4) out[z++] = STBI__BYTECAST(a); } } stbi__skip(s, pad); } } + + // if alpha channel is all 0s, replace with all 255s + if (target == 4 && all_a == 0) + for (i=4*s->img_x*s->img_y-1; i >= 0; i -= 4) + out[i] = 255; + if (flip_vertically) { stbi_uc t; for (j=0; j < (int) s->img_y>>1; ++j) { @@ -3162,24 +4841,60 @@ static stbi_uc *stbi__bmp_load(stbi__context *s, int *x, int *y, int *comp, int if (comp) *comp = s->img_n; return out; } +#endif // Targa Truevision - TGA // by Jonathan Dummer +#ifndef STBI_NO_TGA +// returns STBI_rgb or whatever, 0 on error +static int stbi__tga_get_comp(int bits_per_pixel, int is_grey, int* is_rgb16) +{ + // only RGB or RGBA (incl. 16bit) or grey allowed + if(is_rgb16) *is_rgb16 = 0; + switch(bits_per_pixel) { + case 8: return STBI_grey; + case 16: if(is_grey) return STBI_grey_alpha; + // else: fall-through + case 15: if(is_rgb16) *is_rgb16 = 1; + return STBI_rgb; + case 24: // fall-through + case 32: return bits_per_pixel/8; + default: return 0; + } +} static int stbi__tga_info(stbi__context *s, int *x, int *y, int *comp) { - int tga_w, tga_h, tga_comp; - int sz; + int tga_w, tga_h, tga_comp, tga_image_type, tga_bits_per_pixel, tga_colormap_bpp; + int sz, tga_colormap_type; stbi__get8(s); // discard Offset - sz = stbi__get8(s); // color type - if( sz > 1 ) { + tga_colormap_type = stbi__get8(s); // colormap type + if( tga_colormap_type > 1 ) { stbi__rewind(s); return 0; // only RGB or indexed allowed } - sz = stbi__get8(s); // image type - // only RGB or grey allowed, +/- RLE - if ((sz != 1) && (sz != 2) && (sz != 3) && (sz != 9) && (sz != 10) && (sz != 11)) return 0; - stbi__skip(s,9); + tga_image_type = stbi__get8(s); // image type + if ( tga_colormap_type == 1 ) { // colormapped (paletted) image + if (tga_image_type != 1 && tga_image_type != 9) { + stbi__rewind(s); + return 0; + } + stbi__skip(s,4); // skip index of first colormap entry and number of entries + sz = stbi__get8(s); // check bits per palette color entry + if ( (sz != 8) && (sz != 15) && (sz != 16) && (sz != 24) && (sz != 32) ) { + stbi__rewind(s); + return 0; + } + stbi__skip(s,4); // skip image x and y origin + tga_colormap_bpp = sz; + } else { // "normal" image w/o colormap - only RGB or grey allowed, +/- RLE + if ( (tga_image_type != 2) && (tga_image_type != 3) && (tga_image_type != 10) && (tga_image_type != 11) ) { + stbi__rewind(s); + return 0; // only RGB or grey allowed, +/- RLE + } + stbi__skip(s,9); // skip colormap specification and image x/y origin + tga_colormap_bpp = 0; + } tga_w = stbi__get16le(s); if( tga_w < 1 ) { stbi__rewind(s); @@ -3190,44 +4905,80 @@ static int stbi__tga_info(stbi__context *s, int *x, int *y, int *comp) stbi__rewind(s); return 0; // test height } - sz = stbi__get8(s); // bits per pixel - // only RGB or RGBA or grey allowed - if ((sz != 8) && (sz != 16) && (sz != 24) && (sz != 32)) { - stbi__rewind(s); - return 0; + tga_bits_per_pixel = stbi__get8(s); // bits per pixel + stbi__get8(s); // ignore alpha bits + if (tga_colormap_bpp != 0) { + if((tga_bits_per_pixel != 8) && (tga_bits_per_pixel != 16)) { + // when using a colormap, tga_bits_per_pixel is the size of the indexes + // I don't think anything but 8 or 16bit indexes makes sense + stbi__rewind(s); + return 0; + } + tga_comp = stbi__tga_get_comp(tga_colormap_bpp, 0, NULL); + } else { + tga_comp = stbi__tga_get_comp(tga_bits_per_pixel, (tga_image_type == 3) || (tga_image_type == 11), NULL); + } + if(!tga_comp) { + stbi__rewind(s); + return 0; } - tga_comp = sz; if (x) *x = tga_w; if (y) *y = tga_h; - if (comp) *comp = tga_comp / 8; + if (comp) *comp = tga_comp; return 1; // seems to have passed everything } static int stbi__tga_test(stbi__context *s) { - int res; - int sz; + int res = 0; + int sz, tga_color_type; stbi__get8(s); // discard Offset - sz = stbi__get8(s); // color type - if ( sz > 1 ) return 0; // only RGB or indexed allowed + tga_color_type = stbi__get8(s); // color type + if ( tga_color_type > 1 ) goto errorEnd; // only RGB or indexed allowed sz = stbi__get8(s); // image type - if ( (sz != 1) && (sz != 2) && (sz != 3) && (sz != 9) && (sz != 10) && (sz != 11) ) return 0; // only RGB or grey allowed, +/- RLE - stbi__get16be(s); // discard palette start - stbi__get16be(s); // discard palette length - stbi__get8(s); // discard bits per palette color entry - stbi__get16be(s); // discard x origin - stbi__get16be(s); // discard y origin - if ( stbi__get16be(s) < 1 ) return 0; // test width - if ( stbi__get16be(s) < 1 ) return 0; // test height + if ( tga_color_type == 1 ) { // colormapped (paletted) image + if (sz != 1 && sz != 9) goto errorEnd; // colortype 1 demands image type 1 or 9 + stbi__skip(s,4); // skip index of first colormap entry and number of entries + sz = stbi__get8(s); // check bits per palette color entry + if ( (sz != 8) && (sz != 15) && (sz != 16) && (sz != 24) && (sz != 32) ) goto errorEnd; + stbi__skip(s,4); // skip image x and y origin + } else { // "normal" image w/o colormap + if ( (sz != 2) && (sz != 3) && (sz != 10) && (sz != 11) ) goto errorEnd; // only RGB or grey allowed, +/- RLE + stbi__skip(s,9); // skip colormap specification and image x/y origin + } + if ( stbi__get16le(s) < 1 ) goto errorEnd; // test width + if ( stbi__get16le(s) < 1 ) goto errorEnd; // test height sz = stbi__get8(s); // bits per pixel - if ( (sz != 8) && (sz != 16) && (sz != 24) && (sz != 32) ) - res = 0; - else - res = 1; + if ( (tga_color_type == 1) && (sz != 8) && (sz != 16) ) goto errorEnd; // for colormapped images, bpp is size of an index + if ( (sz != 8) && (sz != 15) && (sz != 16) && (sz != 24) && (sz != 32) ) goto errorEnd; + + res = 1; // if we got this far, everything's good and we can return 1 instead of 0 + +errorEnd: stbi__rewind(s); return res; } +// read 16bit value and convert to 24bit RGB +void stbi__tga_read_rgb16(stbi__context *s, stbi_uc* out) +{ + stbi__uint16 px = stbi__get16le(s); + stbi__uint16 fiveBitMask = 31; + // we have 3 channels with 5bits each + int r = (px >> 10) & fiveBitMask; + int g = (px >> 5) & fiveBitMask; + int b = px & fiveBitMask; + // Note that this saves the data in RGB(A) order, so it doesn't need to be swapped later + out[0] = (r * 255)/31; + out[1] = (g * 255)/31; + out[2] = (b * 255)/31; + + // some people claim that the most significant bit might be used for alpha + // (possibly if an alpha-bit is set in the "image descriptor byte") + // but that only made 16bit test images completely translucent.. + // so let's treat all 15 and 16bit TGAs as RGB with no alpha. +} + static stbi_uc *stbi__tga_load(stbi__context *s, int *x, int *y, int *comp, int req_comp) { // read in the TGA header stuff @@ -3243,8 +4994,9 @@ static stbi_uc *stbi__tga_load(stbi__context *s, int *x, int *y, int *comp, int int tga_width = stbi__get16le(s); int tga_height = stbi__get16le(s); int tga_bits_per_pixel = stbi__get8(s); - int tga_comp = tga_bits_per_pixel / 8; + int tga_comp, tga_rgb16=0; int tga_inverted = stbi__get8(s); + // int tga_alpha_bits = tga_inverted & 15; // the 4 lowest bits - unused (useless?) // image data unsigned char *tga_data; unsigned char *tga_palette = NULL; @@ -3260,41 +5012,30 @@ static stbi_uc *stbi__tga_load(stbi__context *s, int *x, int *y, int *comp, int tga_image_type -= 8; tga_is_RLE = 1; } - /* int tga_alpha_bits = tga_inverted & 15; */ tga_inverted = 1 - ((tga_inverted >> 5) & 1); - // error check - if ( //(tga_indexed) || - (tga_width < 1) || (tga_height < 1) || - (tga_image_type < 1) || (tga_image_type > 3) || - ((tga_bits_per_pixel != 8) && (tga_bits_per_pixel != 16) && - (tga_bits_per_pixel != 24) && (tga_bits_per_pixel != 32)) - ) - { - return NULL; // we don't report this as a bad TGA because we don't even know if it's TGA - } - // If I'm paletted, then I'll use the number of bits from the palette - if ( tga_indexed ) - { - tga_comp = tga_palette_bits / 8; - } + if ( tga_indexed ) tga_comp = stbi__tga_get_comp(tga_palette_bits, 0, &tga_rgb16); + else tga_comp = stbi__tga_get_comp(tga_bits_per_pixel, (tga_image_type == 3), &tga_rgb16); + + if(!tga_comp) // shouldn't really happen, stbi__tga_test() should have ensured basic consistency + return stbi__errpuc("bad format", "Can't find out TGA pixelformat"); // tga info *x = tga_width; *y = tga_height; if (comp) *comp = tga_comp; - tga_data = (unsigned char*)malloc( tga_width * tga_height * tga_comp ); + tga_data = (unsigned char*)stbi__malloc( (size_t)tga_width * tga_height * tga_comp ); if (!tga_data) return stbi__errpuc("outofmem", "Out of memory"); // skip to the data's starting position (offset usually = 0) stbi__skip(s, tga_offset ); - if ( !tga_indexed && !tga_is_RLE) { + if ( !tga_indexed && !tga_is_RLE && !tga_rgb16 ) { for (i=0; i < tga_height; ++i) { - int y = tga_inverted ? tga_height -i - 1 : i; - stbi_uc *tga_row = tga_data + y*tga_width*tga_comp; + int row = tga_inverted ? tga_height -i - 1 : i; + stbi_uc *tga_row = tga_data + row*tga_width*tga_comp; stbi__getn(s, tga_row, tga_width * tga_comp); } } else { @@ -3304,15 +5045,22 @@ static stbi_uc *stbi__tga_load(stbi__context *s, int *x, int *y, int *comp, int // any data to skip? (offset usually = 0) stbi__skip(s, tga_palette_start ); // load the palette - tga_palette = (unsigned char*)malloc( tga_palette_len * tga_palette_bits / 8 ); + tga_palette = (unsigned char*)stbi__malloc( tga_palette_len * tga_comp ); if (!tga_palette) { - free(tga_data); + STBI_FREE(tga_data); return stbi__errpuc("outofmem", "Out of memory"); } - if (!stbi__getn(s, tga_palette, tga_palette_len * tga_palette_bits / 8 )) { - free(tga_data); - free(tga_palette); - return stbi__errpuc("bad palette", "Corrupt TGA"); + if (tga_rgb16) { + stbi_uc *pal_entry = tga_palette; + STBI_ASSERT(tga_comp == STBI_rgb); + for (i=0; i < tga_palette_len; ++i) { + stbi__tga_read_rgb16(s, pal_entry); + pal_entry += tga_comp; + } + } else if (!stbi__getn(s, tga_palette, tga_palette_len * tga_comp)) { + STBI_FREE(tga_data); + STBI_FREE(tga_palette); + return stbi__errpuc("bad palette", "Corrupt TGA"); } } // load the data @@ -3342,23 +5090,22 @@ static stbi_uc *stbi__tga_load(stbi__context *s, int *x, int *y, int *comp, int // load however much data we did have if ( tga_indexed ) { - // read in 1 byte, then perform the lookup - int pal_idx = stbi__get8(s); - if ( pal_idx >= tga_palette_len ) - { - // invalid index + // read in index, then perform the lookup + int pal_idx = (tga_bits_per_pixel == 8) ? stbi__get8(s) : stbi__get16le(s); + if ( pal_idx >= tga_palette_len ) { + // invalid index pal_idx = 0; } - pal_idx *= tga_bits_per_pixel / 8; - for (j = 0; j*8 < tga_bits_per_pixel; ++j) - { + pal_idx *= tga_comp; + for (j = 0; j < tga_comp; ++j) { raw_data[j] = tga_palette[pal_idx+j]; } - } else - { + } else if(tga_rgb16) { + STBI_ASSERT(tga_comp == STBI_rgb); + stbi__tga_read_rgb16(s, raw_data); + } else { // read in the data raw - for (j = 0; j*8 < tga_bits_per_pixel; ++j) - { + for (j = 0; j < tga_comp; ++j) { raw_data[j] = stbi__get8(s); } } @@ -3393,12 +5140,12 @@ static stbi_uc *stbi__tga_load(stbi__context *s, int *x, int *y, int *comp, int // clear my palette, if I had one if ( tga_palette != NULL ) { - free( tga_palette ); + STBI_FREE( tga_palette ); } } - // swap RGB - if (tga_comp >= 3) + // swap RGB - if the source data was RGB16, it already is in the right order + if (tga_comp >= 3 && !tga_rgb16) { unsigned char* tga_pixel = tga_data; for (i=0; i < tga_width * tga_height; ++i) @@ -3421,10 +5168,12 @@ static stbi_uc *stbi__tga_load(stbi__context *s, int *x, int *y, int *comp, int // OK, done return tga_data; } +#endif // ************************************************************************************************* // Photoshop PSD loader -- PD by Thatcher Ulrich, integration by Nicolas Schulz, tweaked by STB +#ifndef STBI_NO_PSD static int stbi__psd_test(stbi__context *s) { int r = (stbi__get32be(s) == 0x38425053); @@ -3437,6 +5186,7 @@ static stbi_uc *stbi__psd_load(stbi__context *s, int *x, int *y, int *comp, int int pixelCount; int channelCount, compression; int channel, i, count, len; + int bitdepth; int w,h; stbi_uc *out; @@ -3459,10 +5209,11 @@ static stbi_uc *stbi__psd_load(stbi__context *s, int *x, int *y, int *comp, int // Read the rows and columns of the image. h = stbi__get32be(s); w = stbi__get32be(s); - + // Make sure the depth is 8 bits. - if (stbi__get16be(s) != 8) - return stbi__errpuc("unsupported bit depth", "PSD bit depth is not 8 bit"); + bitdepth = stbi__get16be(s); + if (bitdepth != 8 && bitdepth != 16) + return stbi__errpuc("unsupported bit depth", "PSD bit depth is not 8 or 16 bit"); // Make sure the color mode is RGB. // Valid options are: @@ -3495,13 +5246,13 @@ static stbi_uc *stbi__psd_load(stbi__context *s, int *x, int *y, int *comp, int return stbi__errpuc("bad compression", "PSD has an unknown compression format"); // Create the destination image. - out = (stbi_uc *) malloc(4 * w*h); + out = (stbi_uc *) stbi__malloc(4 * w*h); if (!out) return stbi__errpuc("outofmem", "Out of memory"); pixelCount = w*h; // Initialize the data to zero. //memset( out, 0, pixelCount * 4 ); - + // Finally, the image data. if (compression) { // RLE as used by .PSD and .TIFF @@ -3519,11 +5270,12 @@ static stbi_uc *stbi__psd_load(stbi__context *s, int *x, int *y, int *comp, int // Read the RLE data by channel. for (channel = 0; channel < 4; channel++) { stbi_uc *p; - + p = out+channel; if (channel >= channelCount) { // Fill this channel with default data. - for (i = 0; i < pixelCount; i++) *p = (channel == 3 ? 255 : 0), p += 4; + for (i = 0; i < pixelCount; i++, p += 4) + *p = (channel == 3 ? 255 : 0); } else { // Read the RLE data. count = 0; @@ -3557,23 +5309,30 @@ static stbi_uc *stbi__psd_load(stbi__context *s, int *x, int *y, int *comp, int } } } - + } else { // We're at the raw image data. It's each channel in order (Red, Green, Blue, Alpha, ...) // where each channel consists of an 8-bit value for each pixel in the image. - + // Read the data by channel. for (channel = 0; channel < 4; channel++) { stbi_uc *p; - + p = out + channel; - if (channel > channelCount) { + if (channel >= channelCount) { // Fill this channel with default data. - for (i = 0; i < pixelCount; i++) *p = channel == 3 ? 255 : 0, p += 4; + stbi_uc val = channel == 3 ? 255 : 0; + for (i = 0; i < pixelCount; i++, p += 4) + *p = val; } else { // Read the data. - for (i = 0; i < pixelCount; i++) - *p = stbi__get8(s), p += 4; + if (bitdepth == 16) { + for (i = 0; i < pixelCount; i++, p += 4) + *p = (stbi_uc) (stbi__get16be(s) >> 8); + } else { + for (i = 0; i < pixelCount; i++, p += 4) + *p = stbi__get8(s); + } } } } @@ -3583,12 +5342,13 @@ static stbi_uc *stbi__psd_load(stbi__context *s, int *x, int *y, int *comp, int if (out == NULL) return out; // stbi__convert_format frees input on failure } - if (comp) *comp = channelCount; + if (comp) *comp = 4; *y = h; *x = w; - + return out; } +#endif // ************************************************************************************************* // Softimage PIC loader @@ -3597,6 +5357,7 @@ static stbi_uc *stbi__psd_load(stbi__context *s, int *x, int *y, int *comp, int // See http://softimage.wiki.softimage.com/index.php/INFO:_PIC_file_format // See http://ozviz.wasp.uwa.edu.au/~pbourke/dataformats/softimagepic/ +#ifndef STBI_NO_PIC static int stbi__pic_is4(stbi__context *s,const char *str) { int i; @@ -3729,7 +5490,6 @@ static stbi_uc *stbi__pic_load_core(stbi__context *s,int width,int height,int *c if (count >= 128) { // Repeated stbi_uc value[4]; - int i; if (count==128) count = stbi__get16be(s); @@ -3773,18 +5533,18 @@ static stbi_uc *stbi__pic_load(stbi__context *s,int *px,int *py,int *comp,int re x = stbi__get16be(s); y = stbi__get16be(s); if (stbi__at_eof(s)) return stbi__errpuc("bad file","file too short (pic header)"); - if ((1 << 28) / x < y) return stbi__errpuc("too large", "Image too large to stbi__jpeg_huff_decode"); + if ((1 << 28) / x < y) return stbi__errpuc("too large", "Image too large to decode"); stbi__get32be(s); //skip `ratio' stbi__get16be(s); //skip `fields' stbi__get16be(s); //skip `pad' // intermediate buffer is RGBA - result = (stbi_uc *) malloc(x*y*4); + result = (stbi_uc *) stbi__malloc(x*y*4); memset(result, 0xff, x*y*4); if (!stbi__pic_load_core(s,x,y,comp, result)) { - free(result); + STBI_FREE(result); result=0; } *px = x; @@ -3801,10 +5561,13 @@ static int stbi__pic_test(stbi__context *s) stbi__rewind(s); return r; } +#endif // ************************************************************************************************* // GIF loader -- public domain by Jean-Marc Lienher -- simplified/shrunk by stb -typedef struct + +#ifndef STBI_NO_GIF +typedef struct { stbi__int16 prefix; stbi_uc first; @@ -3814,8 +5577,8 @@ typedef struct typedef struct { int w,h; - stbi_uc *out; // output buffer (always 4 components) - int flags, bgindex, ratio, transparent, eflags; + stbi_uc *out, *old_out; // output buffer (always 4 components) + int flags, bgindex, ratio, transparent, eflags, delay; stbi_uc pal[256][4]; stbi_uc lpal[256][4]; stbi__gif_lzw codes[4096]; @@ -3852,8 +5615,8 @@ static void stbi__gif_parse_colortable(stbi__context *s, stbi_uc pal[256][4], in pal[i][2] = stbi__get8(s); pal[i][1] = stbi__get8(s); pal[i][0] = stbi__get8(s); - pal[i][3] = transp ? 0 : 255; - } + pal[i][3] = transp == i ? 0 : 255; + } } static int stbi__gif_header(stbi__context *s, stbi__gif *g, int *comp, int is_info) @@ -3864,8 +5627,8 @@ static int stbi__gif_header(stbi__context *s, stbi__gif *g, int *comp, int is_in version = stbi__get8(s); if (version != '7' && version != '9') return stbi__err("not GIF", "Corrupt GIF"); - if (stbi__get8(s) != 'a') return stbi__err("not GIF", "Corrupt GIF"); - + if (stbi__get8(s) != 'a') return stbi__err("not GIF", "Corrupt GIF"); + stbi__g_failure_reason = ""; g->w = stbi__get16le(s); g->h = stbi__get16le(s); @@ -3886,7 +5649,7 @@ static int stbi__gif_header(stbi__context *s, stbi__gif *g, int *comp, int is_in static int stbi__gif_info_raw(stbi__context *s, int *x, int *y, int *comp) { - stbi__gif g; + stbi__gif g; if (!stbi__gif_header(s, &g, comp, 1)) { stbi__rewind( s ); return 0; @@ -3900,13 +5663,13 @@ static void stbi__out_gif_code(stbi__gif *g, stbi__uint16 code) { stbi_uc *p, *c; - // recurse to stbi__jpeg_huff_decode the prefixes, since the linked-list is backwards, + // recurse to decode the prefixes, since the linked-list is backwards, // and working backwards through an interleaved image would be nasty if (g->codes[code].prefix >= 0) stbi__out_gif_code(g, g->codes[code].prefix); if (g->cur_y >= g->max_y) return; - + p = &g->out[g->cur_x + g->cur_y]; c = &g->color_table[g->codes[code].suffix * 4]; @@ -3933,22 +5696,23 @@ static void stbi__out_gif_code(stbi__gif *g, stbi__uint16 code) static stbi_uc *stbi__process_gif_raster(stbi__context *s, stbi__gif *g) { stbi_uc lzw_cs; - stbi__int32 len, code; + stbi__int32 len, init_code; stbi__uint32 first; stbi__int32 codesize, codemask, avail, oldcode, bits, valid_bits, clear; stbi__gif_lzw *p; lzw_cs = stbi__get8(s); + if (lzw_cs > 12) return NULL; clear = 1 << lzw_cs; first = 1; codesize = lzw_cs + 1; codemask = (1 << codesize) - 1; bits = 0; valid_bits = 0; - for (code = 0; code < clear; code++) { - g->codes[code].prefix = -1; - g->codes[code].first = (stbi_uc) code; - g->codes[code].suffix = (stbi_uc) code; + for (init_code = 0; init_code < clear; init_code++) { + g->codes[init_code].prefix = -1; + g->codes[init_code].first = (stbi_uc) init_code; + g->codes[init_code].suffix = (stbi_uc) init_code; } // support no starting clear code @@ -3960,7 +5724,7 @@ static stbi_uc *stbi__process_gif_raster(stbi__context *s, stbi__gif *g) if (valid_bits < codesize) { if (len == 0) { len = stbi__get8(s); // start new block - if (len == 0) + if (len == 0) return g->out; } --len; @@ -4005,21 +5769,22 @@ static stbi_uc *stbi__process_gif_raster(stbi__context *s, stbi__gif *g) } else { return stbi__errpuc("illegal code in raster", "Corrupt GIF"); } - } + } } } -static void stbi__fill_gif_background(stbi__gif *g) +static void stbi__fill_gif_background(stbi__gif *g, int x0, int y0, int x1, int y1) { - int i; + int x, y; stbi_uc *c = g->pal[g->bgindex]; - // @OPTIMIZE: write a dword at a time - for (i = 0; i < g->w * g->h * 4; i += 4) { - stbi_uc *p = &g->out[i]; - p[0] = c[2]; - p[1] = c[1]; - p[2] = c[0]; - p[3] = c[3]; + for (y = y0; y < y1; y += 4 * g->w) { + for (x = x0; x < x1; x += 4) { + stbi_uc *p = &g->out[y + x]; + p[0] = c[2]; + p[1] = c[1]; + p[2] = c[0]; + p[3] = 0; + } } } @@ -4027,27 +5792,40 @@ static void stbi__fill_gif_background(stbi__gif *g) static stbi_uc *stbi__gif_load_next(stbi__context *s, stbi__gif *g, int *comp, int req_comp) { int i; - stbi_uc *old_out = 0; + stbi_uc *prev_out = 0; - if (g->out == 0) { - if (!stbi__gif_header(s, g, comp,0)) return 0; // stbi__g_failure_reason set by stbi__gif_header - g->out = (stbi_uc *) malloc(4 * g->w * g->h); - if (g->out == 0) return stbi__errpuc("outofmem", "Out of memory"); - stbi__fill_gif_background(g); - } else { - // animated-gif-only path - if (((g->eflags & 0x1C) >> 2) == 3) { - old_out = g->out; - g->out = (stbi_uc *) malloc(4 * g->w * g->h); - if (g->out == 0) return stbi__errpuc("outofmem", "Out of memory"); - memcpy(g->out, old_out, g->w*g->h*4); - } + if (g->out == 0 && !stbi__gif_header(s, g, comp,0)) + return 0; // stbi__g_failure_reason set by stbi__gif_header + + prev_out = g->out; + g->out = (stbi_uc *) stbi__malloc(4 * g->w * g->h); + if (g->out == 0) return stbi__errpuc("outofmem", "Out of memory"); + + switch ((g->eflags & 0x1C) >> 2) { + case 0: // unspecified (also always used on 1st frame) + stbi__fill_gif_background(g, 0, 0, 4 * g->w, 4 * g->w * g->h); + break; + case 1: // do not dispose + if (prev_out) memcpy(g->out, prev_out, 4 * g->w * g->h); + g->old_out = prev_out; + break; + case 2: // dispose to background + if (prev_out) memcpy(g->out, prev_out, 4 * g->w * g->h); + stbi__fill_gif_background(g, g->start_x, g->start_y, g->max_x, g->max_y); + break; + case 3: // dispose to previous + if (g->old_out) { + for (i = g->start_y; i < g->max_y; i += 4 * g->w) + memcpy(&g->out[i + g->start_x], &g->old_out[i + g->start_x], g->max_x - g->start_x); + } + break; } - + for (;;) { switch (stbi__get8(s)) { case 0x2C: /* Image Descriptor */ { + int prev_trans = -1; stbi__int32 x, y, w, h; stbi_uc *o; @@ -4078,21 +5856,22 @@ static stbi_uc *stbi__gif_load_next(stbi__context *s, stbi__gif *g, int *comp, i if (g->lflags & 0x80) { stbi__gif_parse_colortable(s,g->lpal, 2 << (g->lflags & 7), g->eflags & 0x01 ? g->transparent : -1); - g->color_table = (stbi_uc *) g->lpal; + g->color_table = (stbi_uc *) g->lpal; } else if (g->flags & 0x80) { - for (i=0; i < 256; ++i) // @OPTIMIZE: stbi__jpeg_reset only the previous transparent - g->pal[i][3] = 255; - if (g->transparent >= 0 && (g->eflags & 0x01)) + if (g->transparent >= 0 && (g->eflags & 0x01)) { + prev_trans = g->pal[g->transparent][3]; g->pal[g->transparent][3] = 0; + } g->color_table = (stbi_uc *) g->pal; } else return stbi__errpuc("missing color table", "Corrupt GIF"); - + o = stbi__process_gif_raster(s, g); if (o == NULL) return NULL; - if (req_comp && req_comp != 4) - o = stbi__convert_format(o, 4, req_comp, g->w, g->h); + if (prev_trans != -1) + g->pal[g->transparent][3] = (stbi_uc) prev_trans; + return o; } @@ -4103,7 +5882,7 @@ static stbi_uc *stbi__gif_load_next(stbi__context *s, stbi__gif *g, int *comp, i len = stbi__get8(s); if (len == 4) { g->eflags = stbi__get8(s); - stbi__get16le(s); // delay + g->delay = stbi__get16le(s); g->transparent = stbi__get8(s); } else { stbi__skip(s, len); @@ -4116,26 +5895,32 @@ static stbi_uc *stbi__gif_load_next(stbi__context *s, stbi__gif *g, int *comp, i } case 0x3B: // gif stream termination code - return (stbi_uc *) 1; + return (stbi_uc *) s; // using '1' causes warning on some compilers default: return stbi__errpuc("unknown code", "Corrupt GIF"); } } + + STBI_NOTUSED(req_comp); } static stbi_uc *stbi__gif_load(stbi__context *s, int *x, int *y, int *comp, int req_comp) { stbi_uc *u = 0; stbi__gif g; - memset(&g, 0, sizeof(stbi__gif)); + memset(&g, 0, sizeof(g)); u = stbi__gif_load_next(s, &g, comp, req_comp); - if (u == (void *) 1) u = 0; // end of animated gif marker + if (u == (stbi_uc *) s) u = 0; // end of animated gif marker if (u) { *x = g.w; *y = g.h; + if (req_comp && req_comp != 4) + u = stbi__convert_format(u, 4, req_comp, g.w, g.h); } + else if (g.out) + STBI_FREE(g.out); return u; } @@ -4144,7 +5929,7 @@ static int stbi__gif_info(stbi__context *s, int *x, int *y, int *comp) { return stbi__gif_info_raw(s,x,y,comp); } - +#endif // ************************************************************************************************* // Radiance RGBE HDR loader @@ -4233,7 +6018,7 @@ static float *stbi__hdr_load(stbi__context *s, int *x, int *y, int *comp, int re // Check identifier if (strcmp(stbi__hdr_gettoken(s,buffer), "#?RADIANCE") != 0) return stbi__errpf("not HDR", "Corrupt HDR image"); - + // Parse header for(;;) { token = stbi__hdr_gettoken(s,buffer); @@ -4261,7 +6046,7 @@ static float *stbi__hdr_load(stbi__context *s, int *x, int *y, int *comp, int re if (req_comp == 0) req_comp = 3; // Read data - hdr_data = (float *) malloc(height * width * req_comp * sizeof(float)); + hdr_data = (float *) stbi__malloc(height * width * req_comp * sizeof(float)); // Load image data // image data is stored as some number of sca @@ -4294,14 +6079,14 @@ static float *stbi__hdr_load(stbi__context *s, int *x, int *y, int *comp, int re stbi__hdr_convert(hdr_data, rgbe, req_comp); i = 1; j = 0; - free(scanline); + STBI_FREE(scanline); goto main_decode_loop; // yes, this makes no sense } len <<= 8; len |= stbi__get8(s); - if (len != width) { free(hdr_data); free(scanline); return stbi__errpf("invalid decoded scanline length", "corrupt HDR"); } - if (scanline == NULL) scanline = (stbi_uc *) malloc(width * 4); - + if (len != width) { STBI_FREE(hdr_data); STBI_FREE(scanline); return stbi__errpf("invalid decoded scanline length", "corrupt HDR"); } + if (scanline == NULL) scanline = (stbi_uc *) stbi__malloc(width * 4); + for (k = 0; k < 4; ++k) { i = 0; while (i < width) { @@ -4322,7 +6107,7 @@ static float *stbi__hdr_load(stbi__context *s, int *x, int *y, int *comp, int re for (i=0; i < width; ++i) stbi__hdr_convert(hdr_data+(j*width + i)*req_comp, scanline + i*4, req_comp); } - free(scanline); + STBI_FREE(scanline); } return hdr_data; @@ -4334,7 +6119,7 @@ static int stbi__hdr_info(stbi__context *s, int *x, int *y, int *comp) char *token; int valid = 0; - if (strcmp(stbi__hdr_gettoken(s,buffer), "#?RADIANCE") != 0) { + if (stbi__hdr_test(s) == 0) { stbi__rewind( s ); return 0; } @@ -4368,34 +6153,25 @@ static int stbi__hdr_info(stbi__context *s, int *x, int *y, int *comp) } #endif // STBI_NO_HDR +#ifndef STBI_NO_BMP static int stbi__bmp_info(stbi__context *s, int *x, int *y, int *comp) { - int hsz; - if (stbi__get8(s) != 'B' || stbi__get8(s) != 'M') { - stbi__rewind( s ); - return 0; - } - stbi__skip(s,12); - hsz = stbi__get32le(s); - if (hsz != 12 && hsz != 40 && hsz != 56 && hsz != 108 && hsz != 124) { - stbi__rewind( s ); - return 0; - } - if (hsz == 12) { - *x = stbi__get16le(s); - *y = stbi__get16le(s); - } else { - *x = stbi__get32le(s); - *y = stbi__get32le(s); - } - if (stbi__get16le(s) != 1) { - stbi__rewind( s ); - return 0; - } - *comp = stbi__get16le(s) / 8; + void *p; + stbi__bmp_data info; + + info.all_a = 255; + p = stbi__bmp_parse_header(s, &info); + stbi__rewind( s ); + if (p == NULL) + return 0; + *x = s->img_x; + *y = s->img_y; + *comp = info.ma ? 4 : 3; return 1; } +#endif +#ifndef STBI_NO_PSD static int stbi__psd_info(stbi__context *s, int *x, int *y, int *comp) { int channelCount; @@ -4426,20 +6202,30 @@ static int stbi__psd_info(stbi__context *s, int *x, int *y, int *comp) *comp = 4; return 1; } +#endif +#ifndef STBI_NO_PIC static int stbi__pic_info(stbi__context *s, int *x, int *y, int *comp) { int act_comp=0,num_packets=0,chained; stbi__pic_packet packets[10]; - stbi__skip(s, 92); + if (!stbi__pic_is4(s,"\x53\x80\xF6\x34")) { + stbi__rewind(s); + return 0; + } + + stbi__skip(s, 88); *x = stbi__get16be(s); *y = stbi__get16be(s); - if (stbi__at_eof(s)) return 0; + if (stbi__at_eof(s)) { + stbi__rewind( s); + return 0; + } if ( (*x) != 0 && (1 << 28) / (*x) < (*y)) { - stbi__rewind( s ); - return 0; + stbi__rewind( s ); + return 0; } stbi__skip(s, 8); @@ -4471,35 +6257,171 @@ static int stbi__pic_info(stbi__context *s, int *x, int *y, int *comp) return 1; } +#endif + +// ************************************************************************************************* +// Portable Gray Map and Portable Pixel Map loader +// by Ken Miller +// +// PGM: http://netpbm.sourceforge.net/doc/pgm.html +// PPM: http://netpbm.sourceforge.net/doc/ppm.html +// +// Known limitations: +// Does not support comments in the header section +// Does not support ASCII image data (formats P2 and P3) +// Does not support 16-bit-per-channel + +#ifndef STBI_NO_PNM + +static int stbi__pnm_test(stbi__context *s) +{ + char p, t; + p = (char) stbi__get8(s); + t = (char) stbi__get8(s); + if (p != 'P' || (t != '5' && t != '6')) { + stbi__rewind( s ); + return 0; + } + return 1; +} + +static stbi_uc *stbi__pnm_load(stbi__context *s, int *x, int *y, int *comp, int req_comp) +{ + stbi_uc *out; + if (!stbi__pnm_info(s, (int *)&s->img_x, (int *)&s->img_y, (int *)&s->img_n)) + return 0; + *x = s->img_x; + *y = s->img_y; + *comp = s->img_n; + + out = (stbi_uc *) stbi__malloc(s->img_n * s->img_x * s->img_y); + if (!out) return stbi__errpuc("outofmem", "Out of memory"); + stbi__getn(s, out, s->img_n * s->img_x * s->img_y); + + if (req_comp && req_comp != s->img_n) { + out = stbi__convert_format(out, s->img_n, req_comp, s->img_x, s->img_y); + if (out == NULL) return out; // stbi__convert_format frees input on failure + } + return out; +} + +static int stbi__pnm_isspace(char c) +{ + return c == ' ' || c == '\t' || c == '\n' || c == '\v' || c == '\f' || c == '\r'; +} + +static void stbi__pnm_skip_whitespace(stbi__context *s, char *c) +{ + for (;;) { + while (!stbi__at_eof(s) && stbi__pnm_isspace(*c)) + *c = (char) stbi__get8(s); + + if (stbi__at_eof(s) || *c != '#') + break; + + while (!stbi__at_eof(s) && *c != '\n' && *c != '\r' ) + *c = (char) stbi__get8(s); + } +} + +static int stbi__pnm_isdigit(char c) +{ + return c >= '0' && c <= '9'; +} + +static int stbi__pnm_getinteger(stbi__context *s, char *c) +{ + int value = 0; + + while (!stbi__at_eof(s) && stbi__pnm_isdigit(*c)) { + value = value*10 + (*c - '0'); + *c = (char) stbi__get8(s); + } + + return value; +} + +static int stbi__pnm_info(stbi__context *s, int *x, int *y, int *comp) +{ + int maxv; + char c, p, t; + + stbi__rewind( s ); + + // Get identifier + p = (char) stbi__get8(s); + t = (char) stbi__get8(s); + if (p != 'P' || (t != '5' && t != '6')) { + stbi__rewind( s ); + return 0; + } + + *comp = (t == '6') ? 3 : 1; // '5' is 1-component .pgm; '6' is 3-component .ppm + + c = (char) stbi__get8(s); + stbi__pnm_skip_whitespace(s, &c); + + *x = stbi__pnm_getinteger(s, &c); // read width + stbi__pnm_skip_whitespace(s, &c); + + *y = stbi__pnm_getinteger(s, &c); // read height + stbi__pnm_skip_whitespace(s, &c); + + maxv = stbi__pnm_getinteger(s, &c); // read max value + + if (maxv > 255) + return stbi__err("max value > 255", "PPM image not 8-bit"); + else + return 1; +} +#endif static int stbi__info_main(stbi__context *s, int *x, int *y, int *comp) { - if (stbi__jpeg_info(s, x, y, comp)) - return 1; - if (stbi__png_info(s, x, y, comp)) - return 1; - if (stbi__gif_info(s, x, y, comp)) - return 1; - if (stbi__bmp_info(s, x, y, comp)) - return 1; - if (stbi__psd_info(s, x, y, comp)) - return 1; - if (stbi__pic_info(s, x, y, comp)) - return 1; + #ifndef STBI_NO_JPEG + if (stbi__jpeg_info(s, x, y, comp)) return 1; + #endif + + #ifndef STBI_NO_PNG + if (stbi__png_info(s, x, y, comp)) return 1; + #endif + + #ifndef STBI_NO_GIF + if (stbi__gif_info(s, x, y, comp)) return 1; + #endif + + #ifndef STBI_NO_BMP + if (stbi__bmp_info(s, x, y, comp)) return 1; + #endif + + #ifndef STBI_NO_PSD + if (stbi__psd_info(s, x, y, comp)) return 1; + #endif + + #ifndef STBI_NO_PIC + if (stbi__pic_info(s, x, y, comp)) return 1; + #endif + + #ifndef STBI_NO_PNM + if (stbi__pnm_info(s, x, y, comp)) return 1; + #endif + #ifndef STBI_NO_HDR - if (stbi__hdr_info(s, x, y, comp)) - return 1; + if (stbi__hdr_info(s, x, y, comp)) return 1; #endif + // test tga last because it's a crappy test! + #ifndef STBI_NO_TGA if (stbi__tga_info(s, x, y, comp)) return 1; + #endif return stbi__err("unknown image type", "Image not of any known type, or corrupt"); } #ifndef STBI_NO_STDIO STBIDEF int stbi_info(char const *filename, int *x, int *y, int *comp) { - FILE *f = fopen(filename, "rb"); + FILE *f = stbi__fopen(filename, "rb"); int result; if (!f) return stbi__err("can't fopen", "Unable to open file"); result = stbi_info_from_file(f, x, y, comp); @@ -4535,114 +6457,158 @@ STBIDEF int stbi_info_from_callbacks(stbi_io_callbacks const *c, void *user, int #endif // STB_IMAGE_IMPLEMENTATION -#if !defined(STBI_NO_STDIO) && defined(_MSC_VER) && _MSC_VER >= 1400 -#pragma warning(pop) -#endif - - /* revision history: - 1.39 (2014-06-15) - fix to TGA optimization when req_comp != number of components in TGA; - fix to GIF loading because BMP wasn't rewinding (whoops, no GIFs in my test suite) - add support for BMP version 5 (more ignored fields) - 1.38 (2014-06-06) - suppress MSVC warnings on integer casts truncating values - fix accidental rename of 'skip' field of I/O - 1.37 (2014-06-04) - remove duplicate typedef - 1.36 (2014-06-03) - convert to header file single-file library - if de-iphone isn't set, load iphone images color-swapped instead of returning NULL - 1.35 (2014-05-27) - various warnings - fix broken STBI_SIMD path - fix bug where stbi_load_from_file no longer left file pointer in correct place - fix broken non-easy path for 32-bit BMP (possibly never used) - TGA optimization by Arseny Kapoulkine - 1.34 (unknown) - use STBI_NOTUSED in stbi__resample_row_generic(), fix one more leak in tga failure case - 1.33 (2011-07-14) - make stbi_is_hdr work in STBI_NO_HDR (as specified), minor compiler-friendly improvements - 1.32 (2011-07-13) - support for "info" function for all supported filetypes (SpartanJ) - 1.31 (2011-06-20) - a few more leak fixes, bug in PNG handling (SpartanJ) - 1.30 (2011-06-11) - added ability to load files via callbacks to accomidate custom input streams (Ben Wenger) - removed deprecated format-specific test/load functions - removed support for installable file formats (stbi_loader) -- would have been broken for IO callbacks anyway - error cases in bmp and tga give messages and don't leak (Raymond Barbiero, grisha) - fix inefficiency in decoding 32-bit BMP (David Woo) - 1.29 (2010-08-16) - various warning fixes from Aurelien Pocheville - 1.28 (2010-08-01) - fix bug in GIF palette transparency (SpartanJ) - 1.27 (2010-08-01) - cast-to-stbi_uc to fix warnings - 1.26 (2010-07-24) - fix bug in file buffering for PNG reported by SpartanJ - 1.25 (2010-07-17) - refix trans_data warning (Won Chun) - 1.24 (2010-07-12) - perf improvements reading from files on platforms with lock-heavy fgetc() - minor perf improvements for jpeg - deprecated type-specific functions so we'll get feedback if they're needed - attempt to fix trans_data warning (Won Chun) - 1.23 fixed bug in iPhone support - 1.22 (2010-07-10) - removed image *writing* support - stbi_info support from Jetro Lauha - GIF support from Jean-Marc Lienher - iPhone PNG-extensions from James Brown - warning-fixes from Nicolas Schulz and Janez Zemva (i.stbi__err. Janez (U+017D)emva) - 1.21 fix use of 'stbi_uc' in header (reported by jon blow) - 1.20 added support for Softimage PIC, by Tom Seddon - 1.19 bug in interlaced PNG corruption check (found by ryg) - 1.18 2008-08-02 - fix a threading bug (local mutable static) - 1.17 support interlaced PNG - 1.16 major bugfix - stbi__convert_format converted one too many pixels - 1.15 initialize some fields for thread safety - 1.14 fix threadsafe conversion bug - header-file-only version (#define STBI_HEADER_FILE_ONLY before including) - 1.13 threadsafe - 1.12 const qualifiers in the API - 1.11 Support installable IDCT, colorspace conversion routines - 1.10 Fixes for 64-bit (don't use "unsigned long") - optimized upsampling by Fabian "ryg" Giesen - 1.09 Fix format-conversion for PSD code (bad global variables!) - 1.08 Thatcher Ulrich's PSD code integrated by Nicolas Schulz - 1.07 attempt to fix C++ warning/errors again - 1.06 attempt to fix C++ warning/errors again - 1.05 fix TGA loading to return correct *comp and use good luminance calc - 1.04 default float alpha is 1, not 255; use 'void *' for stbi_image_free - 1.03 bugfixes to STBI_NO_STDIO, STBI_NO_HDR - 1.02 support for (subset of) HDR files, float interface for preferred access to them - 1.01 fix bug: possible bug in handling right-side up bmps... not sure - fix bug: the stbi__bmp_load() and stbi__tga_load() functions didn't work at all - 1.00 interface to zlib that skips zlib header - 0.99 correct handling of alpha in palette - 0.98 TGA loader by lonesock; dynamically add loaders (untested) - 0.97 jpeg errors on too large a file; also catch another malloc failure - 0.96 fix detection of invalid v value - particleman@mollyrocket forum - 0.95 during header scan, seek to markers in case of padding - 0.94 STBI_NO_STDIO to disable stdio usage; rename all #defines the same - 0.93 handle jpegtran output; verbose errors - 0.92 read 4,8,16,24,32-bit BMP files of several formats - 0.91 output 24-bit Windows 3.0 BMP files - 0.90 fix a few more warnings; bump version number to approach 1.0 - 0.61 bugfixes due to Marc LeBlanc, Christopher Lloyd - 0.60 fix compiling as c++ - 0.59 fix warnings: merge Dave Moore's -Wall fixes - 0.58 fix bug: zlib uncompressed mode len/nlen was wrong endian - 0.57 fix bug: jpg last huffman symbol before marker was >9 bits but less than 16 available - 0.56 fix bug: zlib uncompressed mode len vs. nlen - 0.55 fix bug: restart_interval not initialized to 0 - 0.54 allow NULL for 'int *comp' - 0.53 fix bug in png 3->4; speedup png decoding - 0.52 png handles req_comp=3,4 directly; minor cleanup; jpeg comments - 0.51 obey req_comp requests, 1-component jpegs return as 1-component, - on 'test' only check type, not whether we support this variant - 0.50 first released version + 2.10 (2016-01-22) avoid warning introduced in 2.09 by STBI_REALLOC_SIZED + 2.09 (2016-01-16) allow comments in PNM files + 16-bit-per-pixel TGA (not bit-per-component) + info() for TGA could break due to .hdr handling + info() for BMP to shares code instead of sloppy parse + can use STBI_REALLOC_SIZED if allocator doesn't support realloc + code cleanup + 2.08 (2015-09-13) fix to 2.07 cleanup, reading RGB PSD as RGBA + 2.07 (2015-09-13) fix compiler warnings + partial animated GIF support + limited 16-bpc PSD support + #ifdef unused functions + bug with < 92 byte PIC,PNM,HDR,TGA + 2.06 (2015-04-19) fix bug where PSD returns wrong '*comp' value + 2.05 (2015-04-19) fix bug in progressive JPEG handling, fix warning + 2.04 (2015-04-15) try to re-enable SIMD on MinGW 64-bit + 2.03 (2015-04-12) extra corruption checking (mmozeiko) + stbi_set_flip_vertically_on_load (nguillemot) + fix NEON support; fix mingw support + 2.02 (2015-01-19) fix incorrect assert, fix warning + 2.01 (2015-01-17) fix various warnings; suppress SIMD on gcc 32-bit without -msse2 + 2.00b (2014-12-25) fix STBI_MALLOC in progressive JPEG + 2.00 (2014-12-25) optimize JPG, including x86 SSE2 & NEON SIMD (ryg) + progressive JPEG (stb) + PGM/PPM support (Ken Miller) + STBI_MALLOC,STBI_REALLOC,STBI_FREE + GIF bugfix -- seemingly never worked + STBI_NO_*, STBI_ONLY_* + 1.48 (2014-12-14) fix incorrectly-named assert() + 1.47 (2014-12-14) 1/2/4-bit PNG support, both direct and paletted (Omar Cornut & stb) + optimize PNG (ryg) + fix bug in interlaced PNG with user-specified channel count (stb) + 1.46 (2014-08-26) + fix broken tRNS chunk (colorkey-style transparency) in non-paletted PNG + 1.45 (2014-08-16) + fix MSVC-ARM internal compiler error by wrapping malloc + 1.44 (2014-08-07) + various warning fixes from Ronny Chevalier + 1.43 (2014-07-15) + fix MSVC-only compiler problem in code changed in 1.42 + 1.42 (2014-07-09) + don't define _CRT_SECURE_NO_WARNINGS (affects user code) + fixes to stbi__cleanup_jpeg path + added STBI_ASSERT to avoid requiring assert.h + 1.41 (2014-06-25) + fix search&replace from 1.36 that messed up comments/error messages + 1.40 (2014-06-22) + fix gcc struct-initialization warning + 1.39 (2014-06-15) + fix to TGA optimization when req_comp != number of components in TGA; + fix to GIF loading because BMP wasn't rewinding (whoops, no GIFs in my test suite) + add support for BMP version 5 (more ignored fields) + 1.38 (2014-06-06) + suppress MSVC warnings on integer casts truncating values + fix accidental rename of 'skip' field of I/O + 1.37 (2014-06-04) + remove duplicate typedef + 1.36 (2014-06-03) + convert to header file single-file library + if de-iphone isn't set, load iphone images color-swapped instead of returning NULL + 1.35 (2014-05-27) + various warnings + fix broken STBI_SIMD path + fix bug where stbi_load_from_file no longer left file pointer in correct place + fix broken non-easy path for 32-bit BMP (possibly never used) + TGA optimization by Arseny Kapoulkine + 1.34 (unknown) + use STBI_NOTUSED in stbi__resample_row_generic(), fix one more leak in tga failure case + 1.33 (2011-07-14) + make stbi_is_hdr work in STBI_NO_HDR (as specified), minor compiler-friendly improvements + 1.32 (2011-07-13) + support for "info" function for all supported filetypes (SpartanJ) + 1.31 (2011-06-20) + a few more leak fixes, bug in PNG handling (SpartanJ) + 1.30 (2011-06-11) + added ability to load files via callbacks to accomidate custom input streams (Ben Wenger) + removed deprecated format-specific test/load functions + removed support for installable file formats (stbi_loader) -- would have been broken for IO callbacks anyway + error cases in bmp and tga give messages and don't leak (Raymond Barbiero, grisha) + fix inefficiency in decoding 32-bit BMP (David Woo) + 1.29 (2010-08-16) + various warning fixes from Aurelien Pocheville + 1.28 (2010-08-01) + fix bug in GIF palette transparency (SpartanJ) + 1.27 (2010-08-01) + cast-to-stbi_uc to fix warnings + 1.26 (2010-07-24) + fix bug in file buffering for PNG reported by SpartanJ + 1.25 (2010-07-17) + refix trans_data warning (Won Chun) + 1.24 (2010-07-12) + perf improvements reading from files on platforms with lock-heavy fgetc() + minor perf improvements for jpeg + deprecated type-specific functions so we'll get feedback if they're needed + attempt to fix trans_data warning (Won Chun) + 1.23 fixed bug in iPhone support + 1.22 (2010-07-10) + removed image *writing* support + stbi_info support from Jetro Lauha + GIF support from Jean-Marc Lienher + iPhone PNG-extensions from James Brown + warning-fixes from Nicolas Schulz and Janez Zemva (i.stbi__err. Janez (U+017D)emva) + 1.21 fix use of 'stbi_uc' in header (reported by jon blow) + 1.20 added support for Softimage PIC, by Tom Seddon + 1.19 bug in interlaced PNG corruption check (found by ryg) + 1.18 (2008-08-02) + fix a threading bug (local mutable static) + 1.17 support interlaced PNG + 1.16 major bugfix - stbi__convert_format converted one too many pixels + 1.15 initialize some fields for thread safety + 1.14 fix threadsafe conversion bug + header-file-only version (#define STBI_HEADER_FILE_ONLY before including) + 1.13 threadsafe + 1.12 const qualifiers in the API + 1.11 Support installable IDCT, colorspace conversion routines + 1.10 Fixes for 64-bit (don't use "unsigned long") + optimized upsampling by Fabian "ryg" Giesen + 1.09 Fix format-conversion for PSD code (bad global variables!) + 1.08 Thatcher Ulrich's PSD code integrated by Nicolas Schulz + 1.07 attempt to fix C++ warning/errors again + 1.06 attempt to fix C++ warning/errors again + 1.05 fix TGA loading to return correct *comp and use good luminance calc + 1.04 default float alpha is 1, not 255; use 'void *' for stbi_image_free + 1.03 bugfixes to STBI_NO_STDIO, STBI_NO_HDR + 1.02 support for (subset of) HDR files, float interface for preferred access to them + 1.01 fix bug: possible bug in handling right-side up bmps... not sure + fix bug: the stbi__bmp_load() and stbi__tga_load() functions didn't work at all + 1.00 interface to zlib that skips zlib header + 0.99 correct handling of alpha in palette + 0.98 TGA loader by lonesock; dynamically add loaders (untested) + 0.97 jpeg errors on too large a file; also catch another malloc failure + 0.96 fix detection of invalid v value - particleman@mollyrocket forum + 0.95 during header scan, seek to markers in case of padding + 0.94 STBI_NO_STDIO to disable stdio usage; rename all #defines the same + 0.93 handle jpegtran output; verbose errors + 0.92 read 4,8,16,24,32-bit BMP files of several formats + 0.91 output 24-bit Windows 3.0 BMP files + 0.90 fix a few more warnings; bump version number to approach 1.0 + 0.61 bugfixes due to Marc LeBlanc, Christopher Lloyd + 0.60 fix compiling as c++ + 0.59 fix warnings: merge Dave Moore's -Wall fixes + 0.58 fix bug: zlib uncompressed mode len/nlen was wrong endian + 0.57 fix bug: jpg last huffman symbol before marker was >9 bits but less than 16 available + 0.56 fix bug: zlib uncompressed mode len vs. nlen + 0.55 fix bug: restart_interval not initialized to 0 + 0.54 allow NULL for 'int *comp' + 0.53 fix bug in png 3->4; speedup png decoding + 0.52 png handles req_comp=3,4 directly; minor cleanup; jpeg comments + 0.51 obey req_comp requests, 1-component jpegs return as 1-component, + on 'test' only check type, not whether we support this variant + 0.50 (2006-11-19) + first released version */ diff --git a/source/modules/dgl/src/nanovg/stb_truetype.h b/source/modules/dgl/src/nanovg/stb_truetype.h index 570f236c0..f6ab5b01f 100644 --- a/source/modules/dgl/src/nanovg/stb_truetype.h +++ b/source/modules/dgl/src/nanovg/stb_truetype.h @@ -1,2081 +1,5011 @@ -// stb_truetype.h - v0.8 - public domain -// authored from 2009-2013 by Sean Barrett / RAD Game Tools -// -// This library processes TrueType files: -// parse files -// extract glyph metrics -// extract glyph shapes -// render glyphs to one-channel bitmaps with antialiasing (box filter) -// -// Todo: -// non-MS cmaps -// crashproof on bad data -// hinting? (no longer patented) -// cleartype-style AA? -// optimize: use simple memory allocator for intermediates -// optimize: build edge-list directly from curves -// optimize: rasterize directly from curves? -// -// ADDITIONAL CONTRIBUTORS -// -// Mikko Mononen: compound shape support, more cmap formats -// Tor Andersson: kerning, subpixel rendering -// -// Bug/warning reports: -// "Zer" on mollyrocket (with fix) -// Cass Everitt -// stoiko (Haemimont Games) -// Brian Hook -// Walter van Niftrik -// David Gow -// David Given -// Ivan-Assen Ivanov -// Anthony Pesch -// -// VERSION HISTORY -// -// 0.8 (2014-05-25) fix a few more warnings -// 0.7 (2013-09-25) bugfix: subpixel glyph bug fixed in 0.5 had come back -// 0.6c (2012-07-24) improve documentation -// 0.6b (2012-07-20) fix a few more warnings -// 0.6 (2012-07-17) fix warnings; added stbtt_ScaleForMappingEmToPixels, -// stbtt_GetFontBoundingBox, stbtt_IsGlyphEmpty -// 0.5 (2011-12-09) bugfixes: -// subpixel glyph renderer computed wrong bounding box -// first vertex of shape can be off-curve (FreeSans) -// 0.4b (2011-12-03) fixed an error in the font baking example -// 0.4 (2011-12-01) kerning, subpixel rendering (tor) -// bugfixes for: -// codepoint-to-glyph conversion using table fmt=12 -// codepoint-to-glyph conversion using table fmt=4 -// stbtt_GetBakedQuad with non-square texture (Zer) -// updated Hello World! sample to use kerning and subpixel -// fixed some warnings -// 0.3 (2009-06-24) cmap fmt=12, compound shapes (MM) -// userdata, malloc-from-userdata, non-zero fill (STB) -// 0.2 (2009-03-11) Fix unsigned/signed char warnings -// 0.1 (2009-03-09) First public release -// -// LICENSE -// -// This software is in the public domain. Where that dedication is not -// recognized, you are granted a perpetual, irrevokable license to copy -// and modify this file as you see fit. -// -// USAGE -// -// Include this file in whatever places neeed to refer to it. In ONE C/C++ -// file, write: -// #define STB_TRUETYPE_IMPLEMENTATION -// before the #include of this file. This expands out the actual -// implementation into that C/C++ file. -// -// Simple 3D API (don't ship this, but it's fine for tools and quick start, -// and you can cut and paste from it to move to more advanced) -// stbtt_BakeFontBitmap() -- bake a font to a bitmap for use as texture -// stbtt_GetBakedQuad() -- compute quad to draw for a given char -// -// "Load" a font file from a memory buffer (you have to keep the buffer loaded) -// stbtt_InitFont() -// stbtt_GetFontOffsetForIndex() -- use for TTC font collections -// -// Render a unicode codepoint to a bitmap -// stbtt_GetCodepointBitmap() -- allocates and returns a bitmap -// stbtt_MakeCodepointBitmap() -- renders into bitmap you provide -// stbtt_GetCodepointBitmapBox() -- how big the bitmap must be -// -// Character advance/positioning -// stbtt_GetCodepointHMetrics() -// stbtt_GetFontVMetrics() -// stbtt_GetCodepointKernAdvance() -// -// ADDITIONAL DOCUMENTATION -// -// Immediately after this block comment are a series of sample programs. -// -// After the sample programs is the "header file" section. This section -// includes documentation for each API function. -// -// Some important concepts to understand to use this library: -// -// Codepoint -// Characters are defined by unicode codepoints, e.g. 65 is -// uppercase A, 231 is lowercase c with a cedilla, 0x7e30 is -// the hiragana for "ma". -// -// Glyph -// A visual character shape (every codepoint is rendered as -// some glyph) -// -// Glyph index -// A font-specific integer ID representing a glyph -// -// Baseline -// Glyph shapes are defined relative to a baseline, which is the -// bottom of uppercase characters. Characters extend both above -// and below the baseline. -// -// Current Point -// As you draw text to the screen, you keep track of a "current point" -// which is the origin of each character. The current point's vertical -// position is the baseline. Even "baked fonts" use this model. -// -// Vertical Font Metrics -// The vertical qualities of the font, used to vertically position -// and space the characters. See docs for stbtt_GetFontVMetrics. -// -// Font Size in Pixels or Points -// The preferred interface for specifying font sizes in stb_truetype -// is to specify how tall the font's vertical extent should be in pixels. -// If that sounds good enough, skip the next paragraph. -// -// Most font APIs instead use "points", which are a common typographic -// measurement for describing font size, defined as 72 points per inch. -// stb_truetype provides a point API for compatibility. However, true -// "per inch" conventions don't make much sense on computer displays -// since they different monitors have different number of pixels per -// inch. For example, Windows traditionally uses a convention that -// there are 96 pixels per inch, thus making 'inch' measurements have -// nothing to do with inches, and thus effectively defining a point to -// be 1.333 pixels. Additionally, the TrueType font data provides -// an explicit scale factor to scale a given font's glyphs to points, -// but the author has observed that this scale factor is often wrong -// for non-commercial fonts, thus making fonts scaled in points -// according to the TrueType spec incoherently sized in practice. -// -// ADVANCED USAGE -// -// Quality: -// -// - Use the functions with Subpixel at the end to allow your characters -// to have subpixel positioning. Since the font is anti-aliased, not -// hinted, this is very import for quality. (This is not possible with -// baked fonts.) -// -// - Kerning is now supported, and if you're supporting subpixel rendering -// then kerning is worth using to give your text a polished look. -// -// Performance: -// -// - Convert Unicode codepoints to glyph indexes and operate on the glyphs; -// if you don't do this, stb_truetype is forced to do the conversion on -// every call. -// -// - There are a lot of memory allocations. We should modify it to take -// a temp buffer and allocate from the temp buffer (without freeing), -// should help performance a lot. -// -// NOTES -// -// The system uses the raw data found in the .ttf file without changing it -// and without building auxiliary data structures. This is a bit inefficient -// on little-endian systems (the data is big-endian), but assuming you're -// caching the bitmaps or glyph shapes this shouldn't be a big deal. -// -// It appears to be very hard to programmatically determine what font a -// given file is in a general way. I provide an API for this, but I don't -// recommend it. -// -// -// SOURCE STATISTICS (based on v0.6c, 2050 LOC) -// -// Documentation & header file 520 LOC \___ 660 LOC documentation -// Sample code 140 LOC / -// Truetype parsing 620 LOC ---- 620 LOC TrueType -// Software rasterization 240 LOC \ . -// Curve tesselation 120 LOC \__ 550 LOC Bitmap creation -// Bitmap management 100 LOC / -// Baked bitmap interface 70 LOC / -// Font name matching & access 150 LOC ---- 150 -// C runtime library abstraction 60 LOC ---- 60 - - -////////////////////////////////////////////////////////////////////////////// -////////////////////////////////////////////////////////////////////////////// -//// -//// SAMPLE PROGRAMS -//// -// -// Incomplete text-in-3d-api example, which draws quads properly aligned to be lossless -// -#if 0 -#define STB_TRUETYPE_IMPLEMENTATION // force following include to generate implementation -#include "stb_truetype.h" - -char ttf_buffer[1<<20]; -unsigned char temp_bitmap[512*512]; - -stbtt_bakedchar cdata[96]; // ASCII 32..126 is 95 glyphs -GLstbtt_uint ftex; - -void my_stbtt_initfont(void) -{ - fread(ttf_buffer, 1, 1<<20, fopen("c:/windows/fonts/times.ttf", "rb")); - stbtt_BakeFontBitmap(data,0, 32.0, temp_bitmap,512,512, 32,96, cdata); // no guarantee this fits! - // can free ttf_buffer at this point - glGenTextures(1, &ftex); - glBindTexture(GL_TEXTURE_2D, ftex); - glTexImage2D(GL_TEXTURE_2D, 0, GL_ALPHA, 512,512, 0, GL_ALPHA, GL_UNSIGNED_BYTE, temp_bitmap); - // can free temp_bitmap at this point - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); -} - -void my_stbtt_print(float x, float y, char *text) -{ - // assume orthographic projection with units = screen pixels, origin at top left - glBindTexture(GL_TEXTURE_2D, ftex); - glBegin(GL_QUADS); - while (*text) { - if (*text >= 32 && *text < 128) { - stbtt_aligned_quad q; - stbtt_GetBakedQuad(cdata, 512,512, *text-32, &x,&y,&q,1);//1=opengl & d3d10+,0=d3d9 - glTexCoord2f(q.s0,q.t1); glVertex2f(q.x0,q.y0); - glTexCoord2f(q.s1,q.t1); glVertex2f(q.x1,q.y0); - glTexCoord2f(q.s1,q.t0); glVertex2f(q.x1,q.y1); - glTexCoord2f(q.s0,q.t0); glVertex2f(q.x0,q.y1); - } - ++text; - } - glEnd(); -} -#endif -// -// -////////////////////////////////////////////////////////////////////////////// -// -// Complete program (this compiles): get a single bitmap, print as ASCII art -// -#if 0 -#include -#define STB_TRUETYPE_IMPLEMENTATION // force following include to generate implementation -#include "stb_truetype.h" - -char ttf_buffer[1<<25]; - -int main(int argc, char **argv) -{ - stbtt_fontinfo font; - unsigned char *bitmap; - int w,h,i,j,c = (argc > 1 ? atoi(argv[1]) : 'a'), s = (argc > 2 ? atoi(argv[2]) : 20); - - fread(ttf_buffer, 1, 1<<25, fopen(argc > 3 ? argv[3] : "c:/windows/fonts/arialbd.ttf", "rb")); - - stbtt_InitFont(&font, ttf_buffer, stbtt_GetFontOffsetForIndex(ttf_buffer,0)); - bitmap = stbtt_GetCodepointBitmap(&font, 0,stbtt_ScaleForPixelHeight(&font, s), c, &w, &h, 0,0); - - for (j=0; j < h; ++j) { - for (i=0; i < w; ++i) - putchar(" .:ioVM@"[bitmap[j*w+i]>>5]); - putchar('\n'); - } - return 0; -} -#endif -// -// Output: -// -// .ii. -// @@@@@@. -// V@Mio@@o -// :i. V@V -// :oM@@M -// :@@@MM@M -// @@o o@M -// :@@. M@M -// @@@o@@@@ -// :M@@V:@@. -// -////////////////////////////////////////////////////////////////////////////// -// -// Complete program: print "Hello World!" banner, with bugs -// -#if 0 -char buffer[24<<20]; -unsigned char screen[20][79]; - -int main(int arg, char **argv) -{ - stbtt_fontinfo font; - int i,j,ascent,baseline,ch=0; - float scale, xpos=2; // leave a little padding in case the character extends left - char *text = "Heljo World!"; - - fread(buffer, 1, 1000000, fopen("c:/windows/fonts/arialbd.ttf", "rb")); - stbtt_InitFont(&font, buffer, 0); - - scale = stbtt_ScaleForPixelHeight(&font, 15); - stbtt_GetFontVMetrics(&font, &ascent,0,0); - baseline = (int) (ascent*scale); - - while (text[ch]) { - int advance,lsb,x0,y0,x1,y1; - float x_shift = xpos - (float) floor(xpos); - stbtt_GetCodepointHMetrics(&font, text[ch], &advance, &lsb); - stbtt_GetCodepointBitmapBoxSubpixel(&font, text[ch], scale,scale,x_shift,0, &x0,&y0,&x1,&y1); - stbtt_MakeCodepointBitmapSubpixel(&font, &screen[baseline + y0][(int) xpos + x0], x1-x0,y1-y0, 79, scale,scale,x_shift,0, text[ch]); - // note that this stomps the old data, so where character boxes overlap (e.g. 'lj') it's wrong - // because this API is really for baking character bitmaps into textures. if you want to render - // a sequence of characters, you really need to render each bitmap to a temp buffer, then - // "alpha blend" that into the working buffer - xpos += (advance * scale); - if (text[ch+1]) - xpos += scale*stbtt_GetCodepointKernAdvance(&font, text[ch],text[ch+1]); - ++ch; - } - - for (j=0; j < 20; ++j) { - for (i=0; i < 78; ++i) - putchar(" .:ioVM@"[screen[j][i]>>5]); - putchar('\n'); - } - - return 0; -} -#endif - - -////////////////////////////////////////////////////////////////////////////// -////////////////////////////////////////////////////////////////////////////// -//// -//// INTEGRATION WITH YOUR CODEBASE -//// -//// The following sections allow you to supply alternate definitions -//// of C library functions used by stb_truetype. - -#ifdef STB_TRUETYPE_IMPLEMENTATION - // #define your own (u)stbtt_int8/16/32 before including to override this - #ifndef stbtt_uint8 - typedef unsigned char stbtt_uint8; - typedef signed char stbtt_int8; - typedef unsigned short stbtt_uint16; - typedef signed short stbtt_int16; - typedef unsigned int stbtt_uint32; - typedef signed int stbtt_int32; - #endif - - typedef char stbtt__check_size32[sizeof(stbtt_int32)==4 ? 1 : -1]; - typedef char stbtt__check_size16[sizeof(stbtt_int16)==2 ? 1 : -1]; - - // #define your own STBTT_sort() to override this to avoid qsort - #ifndef STBTT_sort - #include - #define STBTT_sort(data,num_items,item_size,compare_func) qsort(data,num_items,item_size,compare_func) - #endif - - // #define your own STBTT_ifloor/STBTT_iceil() to avoid math.h - #ifndef STBTT_ifloor - #include - #define STBTT_ifloor(x) ((int) floor(x)) - #define STBTT_iceil(x) ((int) ceil(x)) - #endif - - #ifndef STBTT_sqrt - #include - #define STBTT_sqrt(x) sqrt(x) - #endif - - // #define your own functions "STBTT_malloc" / "STBTT_free" to avoid malloc.h - #ifndef STBTT_malloc - #include - #define STBTT_malloc(x,u) ((void)(u),malloc(x)) - #define STBTT_free(x,u) free(x) - #endif - - #ifndef STBTT_assert - #include - #define STBTT_assert(x) assert(x) - #endif - - #ifndef STBTT_strlen - #include - #define STBTT_strlen(x) strlen(x) - #endif - - #ifndef STBTT_memcpy - #include - #define STBTT_memcpy memcpy - #define STBTT_memset memset - #endif -#endif - -/////////////////////////////////////////////////////////////////////////////// -/////////////////////////////////////////////////////////////////////////////// -//// -//// INTERFACE -//// -//// - -#ifndef __STB_INCLUDE_STB_TRUETYPE_H__ -#define __STB_INCLUDE_STB_TRUETYPE_H__ - -#ifdef __cplusplus -extern "C" { -#endif - -////////////////////////////////////////////////////////////////////////////// -// -// TEXTURE BAKING API -// -// If you use this API, you only have to call two functions ever. -// - -typedef struct -{ - unsigned short x0,y0,x1,y1; // coordinates of bbox in bitmap - float xoff,yoff,xadvance; -} stbtt_bakedchar; - -extern int stbtt_BakeFontBitmap(const unsigned char *data, int offset, // font location (use offset=0 for plain .ttf) - float pixel_height, // height of font in pixels - unsigned char *pixels, int pw, int ph, // bitmap to be filled in - int first_char, int num_chars, // characters to bake - stbtt_bakedchar *chardata); // you allocate this, it's num_chars long -// if return is positive, the first unused row of the bitmap -// if return is negative, returns the negative of the number of characters that fit -// if return is 0, no characters fit and no rows were used -// This uses a very crappy packing. - -typedef struct -{ - float x0,y0,s0,t0; // top-left - float x1,y1,s1,t1; // bottom-right -} stbtt_aligned_quad; - -extern void stbtt_GetBakedQuad(stbtt_bakedchar *chardata, int pw, int ph, // same data as above - int char_index, // character to display - float *xpos, float *ypos, // pointers to current position in screen pixel space - stbtt_aligned_quad *q, // output: quad to draw - int opengl_fillrule); // true if opengl fill rule; false if DX9 or earlier -// Call GetBakedQuad with char_index = 'character - first_char', and it -// creates the quad you need to draw and advances the current position. -// -// The coordinate system used assumes y increases downwards. -// -// Characters will extend both above and below the current position; -// see discussion of "BASELINE" above. -// -// It's inefficient; you might want to c&p it and optimize it. - - -////////////////////////////////////////////////////////////////////////////// -// -// FONT LOADING -// -// - -extern int stbtt_GetFontOffsetForIndex(const unsigned char *data, int index); -// Each .ttf/.ttc file may have more than one font. Each font has a sequential -// index number starting from 0. Call this function to get the font offset for -// a given index; it returns -1 if the index is out of range. A regular .ttf -// file will only define one font and it always be at offset 0, so it will -// return '0' for index 0, and -1 for all other indices. You can just skip -// this step if you know it's that kind of font. - - -// The following structure is defined publically so you can declare one on -// the stack or as a global or etc, but you should treat it as opaque. -typedef struct stbtt_fontinfo -{ - void * userdata; - unsigned char * data; // pointer to .ttf file - int fontstart; // offset of start of font - - int numGlyphs; // number of glyphs, needed for range checking - - int loca,head,glyf,hhea,hmtx,kern; // table locations as offset from start of .ttf - int index_map; // a cmap mapping for our chosen character encoding - int indexToLocFormat; // format needed to map from glyph index to glyph -} stbtt_fontinfo; - -extern int stbtt_InitFont(stbtt_fontinfo *info, const unsigned char *data, int offset); -// Given an offset into the file that defines a font, this function builds -// the necessary cached info for the rest of the system. You must allocate -// the stbtt_fontinfo yourself, and stbtt_InitFont will fill it out. You don't -// need to do anything special to free it, because the contents are pure -// value data with no additional data structures. Returns 0 on failure. - - -////////////////////////////////////////////////////////////////////////////// -// -// CHARACTER TO GLYPH-INDEX CONVERSIOn - -int stbtt_FindGlyphIndex(const stbtt_fontinfo *info, int unicode_codepoint); -// If you're going to perform multiple operations on the same character -// and you want a speed-up, call this function with the character you're -// going to process, then use glyph-based functions instead of the -// codepoint-based functions. - - -////////////////////////////////////////////////////////////////////////////// -// -// CHARACTER PROPERTIES -// - -extern float stbtt_ScaleForPixelHeight(const stbtt_fontinfo *info, float pixels); -// computes a scale factor to produce a font whose "height" is 'pixels' tall. -// Height is measured as the distance from the highest ascender to the lowest -// descender; in other words, it's equivalent to calling stbtt_GetFontVMetrics -// and computing: -// scale = pixels / (ascent - descent) -// so if you prefer to measure height by the ascent only, use a similar calculation. - -extern float stbtt_ScaleForMappingEmToPixels(const stbtt_fontinfo *info, float pixels); -// computes a scale factor to produce a font whose EM size is mapped to -// 'pixels' tall. This is probably what traditional APIs compute, but -// I'm not positive. - -extern void stbtt_GetFontVMetrics(const stbtt_fontinfo *info, int *ascent, int *descent, int *lineGap); -// ascent is the coordinate above the baseline the font extends; descent -// is the coordinate below the baseline the font extends (i.e. it is typically negative) -// lineGap is the spacing between one row's descent and the next row's ascent... -// so you should advance the vertical position by "*ascent - *descent + *lineGap" -// these are expressed in unscaled coordinates, so you must multiply by -// the scale factor for a given size - -extern void stbtt_GetFontBoundingBox(const stbtt_fontinfo *info, int *x0, int *y0, int *x1, int *y1); -// the bounding box around all possible characters - -extern void stbtt_GetCodepointHMetrics(const stbtt_fontinfo *info, int codepoint, int *advanceWidth, int *leftSideBearing); -// leftSideBearing is the offset from the current horizontal position to the left edge of the character -// advanceWidth is the offset from the current horizontal position to the next horizontal position -// these are expressed in unscaled coordinates - -extern int stbtt_GetCodepointKernAdvance(const stbtt_fontinfo *info, int ch1, int ch2); -// an additional amount to add to the 'advance' value between ch1 and ch2 - -extern int stbtt_GetCodepointBox(const stbtt_fontinfo *info, int codepoint, int *x0, int *y0, int *x1, int *y1); -// Gets the bounding box of the visible part of the glyph, in unscaled coordinates - -extern void stbtt_GetGlyphHMetrics(const stbtt_fontinfo *info, int glyph_index, int *advanceWidth, int *leftSideBearing); -extern int stbtt_GetGlyphKernAdvance(const stbtt_fontinfo *info, int glyph1, int glyph2); -extern int stbtt_GetGlyphBox(const stbtt_fontinfo *info, int glyph_index, int *x0, int *y0, int *x1, int *y1); -// as above, but takes one or more glyph indices for greater efficiency - - -////////////////////////////////////////////////////////////////////////////// -// -// GLYPH SHAPES (you probably don't need these, but they have to go before -// the bitmaps for C declaration-order reasons) -// - -#ifndef STBTT_vmove // you can predefine these to use different values (but why?) - enum { - STBTT_vmove=1, - STBTT_vline, - STBTT_vcurve - }; -#endif - -#ifndef stbtt_vertex // you can predefine this to use different values - // (we share this with other code at RAD) - #define stbtt_vertex_type short // can't use stbtt_int16 because that's not visible in the header file - typedef struct - { - stbtt_vertex_type x,y,cx,cy; - unsigned char type,padding; - } stbtt_vertex; -#endif - -extern int stbtt_IsGlyphEmpty(const stbtt_fontinfo *info, int glyph_index); -// returns non-zero if nothing is drawn for this glyph - -extern int stbtt_GetCodepointShape(const stbtt_fontinfo *info, int unicode_codepoint, stbtt_vertex **vertices); -extern int stbtt_GetGlyphShape(const stbtt_fontinfo *info, int glyph_index, stbtt_vertex **vertices); -// returns # of vertices and fills *vertices with the pointer to them -// these are expressed in "unscaled" coordinates -// -// The shape is a series of countours. Each one starts with -// a STBTT_moveto, then consists of a series of mixed -// STBTT_lineto and STBTT_curveto segments. A lineto -// draws a line from previous endpoint to its x,y; a curveto -// draws a quadratic bezier from previous endpoint to -// its x,y, using cx,cy as the bezier control point. - -extern void stbtt_FreeShape(const stbtt_fontinfo *info, stbtt_vertex *vertices); -// frees the data allocated above - -////////////////////////////////////////////////////////////////////////////// -// -// BITMAP RENDERING -// - -extern void stbtt_FreeBitmap(unsigned char *bitmap, void *userdata); -// frees the bitmap allocated below - -extern unsigned char *stbtt_GetCodepointBitmap(const stbtt_fontinfo *info, float scale_x, float scale_y, int codepoint, int *width, int *height, int *xoff, int *yoff); -// allocates a large-enough single-channel 8bpp bitmap and renders the -// specified character/glyph at the specified scale into it, with -// antialiasing. 0 is no coverage (transparent), 255 is fully covered (opaque). -// *width & *height are filled out with the width & height of the bitmap, -// which is stored left-to-right, top-to-bottom. -// -// xoff/yoff are the offset it pixel space from the glyph origin to the top-left of the bitmap - -extern unsigned char *stbtt_GetCodepointBitmapSubpixel(const stbtt_fontinfo *info, float scale_x, float scale_y, float shift_x, float shift_y, int codepoint, int *width, int *height, int *xoff, int *yoff); -// the same as stbtt_GetCodepoitnBitmap, but you can specify a subpixel -// shift for the character - -extern void stbtt_MakeCodepointBitmap(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, int codepoint); -// the same as stbtt_GetCodepointBitmap, but you pass in storage for the bitmap -// in the form of 'output', with row spacing of 'out_stride' bytes. the bitmap -// is clipped to out_w/out_h bytes. Call stbtt_GetCodepointBitmapBox to get the -// width and height and positioning info for it first. - -extern void stbtt_MakeCodepointBitmapSubpixel(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int codepoint); -// same as stbtt_MakeCodepointBitmap, but you can specify a subpixel -// shift for the character - -extern void stbtt_GetCodepointBitmapBox(const stbtt_fontinfo *font, int codepoint, float scale_x, float scale_y, int *ix0, int *iy0, int *ix1, int *iy1); -// get the bbox of the bitmap centered around the glyph origin; so the -// bitmap width is ix1-ix0, height is iy1-iy0, and location to place -// the bitmap top left is (leftSideBearing*scale,iy0). -// (Note that the bitmap uses y-increases-down, but the shape uses -// y-increases-up, so CodepointBitmapBox and CodepointBox are inverted.) - -extern void stbtt_GetCodepointBitmapBoxSubpixel(const stbtt_fontinfo *font, int codepoint, float scale_x, float scale_y, float shift_x, float shift_y, int *ix0, int *iy0, int *ix1, int *iy1); -// same as stbtt_GetCodepointBitmapBox, but you can specify a subpixel -// shift for the character - -// the following functions are equivalent to the above functions, but operate -// on glyph indices instead of Unicode codepoints (for efficiency) -extern unsigned char *stbtt_GetGlyphBitmap(const stbtt_fontinfo *info, float scale_x, float scale_y, int glyph, int *width, int *height, int *xoff, int *yoff); -extern unsigned char *stbtt_GetGlyphBitmapSubpixel(const stbtt_fontinfo *info, float scale_x, float scale_y, float shift_x, float shift_y, int glyph, int *width, int *height, int *xoff, int *yoff); -extern void stbtt_MakeGlyphBitmap(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, int glyph); -extern void stbtt_MakeGlyphBitmapSubpixel(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int glyph); -extern void stbtt_GetGlyphBitmapBox(const stbtt_fontinfo *font, int glyph, float scale_x, float scale_y, int *ix0, int *iy0, int *ix1, int *iy1); -extern void stbtt_GetGlyphBitmapBoxSubpixel(const stbtt_fontinfo *font, int glyph, float scale_x, float scale_y,float shift_x, float shift_y, int *ix0, int *iy0, int *ix1, int *iy1); - - -// @TODO: don't expose this structure -typedef struct -{ - int w,h,stride; - unsigned char *pixels; -} stbtt__bitmap; - -extern void stbtt_Rasterize(stbtt__bitmap *result, float flatness_in_pixels, stbtt_vertex *vertices, int num_verts, float scale_x, float scale_y, float shift_x, float shift_y, int x_off, int y_off, int invert, void *userdata); - -////////////////////////////////////////////////////////////////////////////// -// -// Finding the right font... -// -// You should really just solve this offline, keep your own tables -// of what font is what, and don't try to get it out of the .ttf file. -// That's because getting it out of the .ttf file is really hard, because -// the names in the file can appear in many possible encodings, in many -// possible languages, and e.g. if you need a case-insensitive comparison, -// the details of that depend on the encoding & language in a complex way -// (actually underspecified in truetype, but also gigantic). -// -// But you can use the provided functions in two possible ways: -// stbtt_FindMatchingFont() will use *case-sensitive* comparisons on -// unicode-encoded names to try to find the font you want; -// you can run this before calling stbtt_InitFont() -// -// stbtt_GetFontNameString() lets you get any of the various strings -// from the file yourself and do your own comparisons on them. -// You have to have called stbtt_InitFont() first. - - -extern int stbtt_FindMatchingFont(const unsigned char *fontdata, const char *name, int flags); -// returns the offset (not index) of the font that matches, or -1 if none -// if you use STBTT_MACSTYLE_DONTCARE, use a font name like "Arial Bold". -// if you use any other flag, use a font name like "Arial"; this checks -// the 'macStyle' header field; i don't know if fonts set this consistently -#define STBTT_MACSTYLE_DONTCARE 0 -#define STBTT_MACSTYLE_BOLD 1 -#define STBTT_MACSTYLE_ITALIC 2 -#define STBTT_MACSTYLE_UNDERSCORE 4 -#define STBTT_MACSTYLE_NONE 8 // <= not same as 0, this makes us check the bitfield is 0 - -extern int stbtt_CompareUTF8toUTF16_bigendian(const char *s1, int len1, const char *s2, int len2); -// returns 1/0 whether the first string interpreted as utf8 is identical to -// the second string interpreted as big-endian utf16... useful for strings from next func - -extern const char *stbtt_GetFontNameString(const stbtt_fontinfo *font, int *length, int platformID, int encodingID, int languageID, int nameID); -// returns the string (which may be big-endian double byte, e.g. for unicode) -// and puts the length in bytes in *length. -// -// some of the values for the IDs are below; for more see the truetype spec: -// http://developer.apple.com/textfonts/TTRefMan/RM06/Chap6name.html -// http://www.microsoft.com/typography/otspec/name.htm - -enum { // platformID - STBTT_PLATFORM_ID_UNICODE =0, - STBTT_PLATFORM_ID_MAC =1, - STBTT_PLATFORM_ID_ISO =2, - STBTT_PLATFORM_ID_MICROSOFT =3 -}; - -enum { // encodingID for STBTT_PLATFORM_ID_UNICODE - STBTT_UNICODE_EID_UNICODE_1_0 =0, - STBTT_UNICODE_EID_UNICODE_1_1 =1, - STBTT_UNICODE_EID_ISO_10646 =2, - STBTT_UNICODE_EID_UNICODE_2_0_BMP=3, - STBTT_UNICODE_EID_UNICODE_2_0_FULL=4 -}; - -enum { // encodingID for STBTT_PLATFORM_ID_MICROSOFT - STBTT_MS_EID_SYMBOL =0, - STBTT_MS_EID_UNICODE_BMP =1, - STBTT_MS_EID_SHIFTJIS =2, - STBTT_MS_EID_UNICODE_FULL =10 -}; - -enum { // encodingID for STBTT_PLATFORM_ID_MAC; same as Script Manager codes - STBTT_MAC_EID_ROMAN =0, STBTT_MAC_EID_ARABIC =4, - STBTT_MAC_EID_JAPANESE =1, STBTT_MAC_EID_HEBREW =5, - STBTT_MAC_EID_CHINESE_TRAD =2, STBTT_MAC_EID_GREEK =6, - STBTT_MAC_EID_KOREAN =3, STBTT_MAC_EID_RUSSIAN =7 -}; - -enum { // languageID for STBTT_PLATFORM_ID_MICROSOFT; same as LCID... - // problematic because there are e.g. 16 english LCIDs and 16 arabic LCIDs - STBTT_MS_LANG_ENGLISH =0x0409, STBTT_MS_LANG_ITALIAN =0x0410, - STBTT_MS_LANG_CHINESE =0x0804, STBTT_MS_LANG_JAPANESE =0x0411, - STBTT_MS_LANG_DUTCH =0x0413, STBTT_MS_LANG_KOREAN =0x0412, - STBTT_MS_LANG_FRENCH =0x040c, STBTT_MS_LANG_RUSSIAN =0x0419, - STBTT_MS_LANG_GERMAN =0x0407, STBTT_MS_LANG_SPANISH =0x0409, - STBTT_MS_LANG_HEBREW =0x040d, STBTT_MS_LANG_SWEDISH =0x041D -}; - -enum { // languageID for STBTT_PLATFORM_ID_MAC - STBTT_MAC_LANG_ENGLISH =0 , STBTT_MAC_LANG_JAPANESE =11, - STBTT_MAC_LANG_ARABIC =12, STBTT_MAC_LANG_KOREAN =23, - STBTT_MAC_LANG_DUTCH =4 , STBTT_MAC_LANG_RUSSIAN =32, - STBTT_MAC_LANG_FRENCH =1 , STBTT_MAC_LANG_SPANISH =6 , - STBTT_MAC_LANG_GERMAN =2 , STBTT_MAC_LANG_SWEDISH =5 , - STBTT_MAC_LANG_HEBREW =10, STBTT_MAC_LANG_CHINESE_SIMPLIFIED =33, - STBTT_MAC_LANG_ITALIAN =3 , STBTT_MAC_LANG_CHINESE_TRAD =19 -}; - -#ifdef __cplusplus -} -#endif - -#endif // __STB_INCLUDE_STB_TRUETYPE_H__ - -/////////////////////////////////////////////////////////////////////////////// -/////////////////////////////////////////////////////////////////////////////// -//// -//// IMPLEMENTATION -//// -//// - -#ifdef STB_TRUETYPE_IMPLEMENTATION - -////////////////////////////////////////////////////////////////////////// -// -// accessors to parse data from file -// - -// on platforms that don't allow misaligned reads, if we want to allow -// truetype fonts that aren't padded to alignment, define ALLOW_UNALIGNED_TRUETYPE - -#define ttBYTE(p) (* (stbtt_uint8 *) (p)) -#define ttCHAR(p) (* (stbtt_int8 *) (p)) -#define ttFixed(p) ttLONG(p) - -#if defined(STB_TRUETYPE_BIGENDIAN) && !defined(ALLOW_UNALIGNED_TRUETYPE) - - #define ttUSHORT(p) (* (stbtt_uint16 *) (p)) - #define ttSHORT(p) (* (stbtt_int16 *) (p)) - #define ttULONG(p) (* (stbtt_uint32 *) (p)) - #define ttLONG(p) (* (stbtt_int32 *) (p)) - -#else - - stbtt_uint16 ttUSHORT(const stbtt_uint8 *p) { return p[0]*256 + p[1]; } - stbtt_int16 ttSHORT(const stbtt_uint8 *p) { return p[0]*256 + p[1]; } - stbtt_uint32 ttULONG(const stbtt_uint8 *p) { return (p[0]<<24) + (p[1]<<16) + (p[2]<<8) + p[3]; } - stbtt_int32 ttLONG(const stbtt_uint8 *p) { return (p[0]<<24) + (p[1]<<16) + (p[2]<<8) + p[3]; } - -#endif - -#define stbtt_tag4(p,c0,c1,c2,c3) ((p)[0] == (c0) && (p)[1] == (c1) && (p)[2] == (c2) && (p)[3] == (c3)) -#define stbtt_tag(p,str) stbtt_tag4(p,str[0],str[1],str[2],str[3]) - -static int stbtt__isfont(const stbtt_uint8 *font) -{ - // check the version number - if (stbtt_tag4(font, '1',0,0,0)) return 1; // TrueType 1 - if (stbtt_tag(font, "typ1")) return 1; // TrueType with type 1 font -- we don't support this! - if (stbtt_tag(font, "OTTO")) return 1; // OpenType with CFF - if (stbtt_tag4(font, 0,1,0,0)) return 1; // OpenType 1.0 - return 0; -} - -// @OPTIMIZE: binary search -static stbtt_uint32 stbtt__find_table(stbtt_uint8 *data, stbtt_uint32 fontstart, const char *tag) -{ - stbtt_int32 num_tables = ttUSHORT(data+fontstart+4); - stbtt_uint32 tabledir = fontstart + 12; - stbtt_int32 i; - for (i=0; i < num_tables; ++i) { - stbtt_uint32 loc = tabledir + 16*i; - if (stbtt_tag(data+loc+0, tag)) - return ttULONG(data+loc+8); - } - return 0; -} - -int stbtt_GetFontOffsetForIndex(const unsigned char *font_collection, int index) -{ - // if it's just a font, there's only one valid index - if (stbtt__isfont(font_collection)) - return index == 0 ? 0 : -1; - - // check if it's a TTC - if (stbtt_tag(font_collection, "ttcf")) { - // version 1? - if (ttULONG(font_collection+4) == 0x00010000 || ttULONG(font_collection+4) == 0x00020000) { - stbtt_int32 n = ttLONG(font_collection+8); - if (index >= n) - return -1; - return ttULONG(font_collection+12+index*14); - } - } - return -1; -} - -int stbtt_InitFont(stbtt_fontinfo *info, const unsigned char *data2, int fontstart) -{ - stbtt_uint8 *data = (stbtt_uint8 *) data2; - stbtt_uint32 cmap, t; - stbtt_int32 i,numTables; - - info->data = data; - info->fontstart = fontstart; - - cmap = stbtt__find_table(data, fontstart, "cmap"); // required - info->loca = stbtt__find_table(data, fontstart, "loca"); // required - info->head = stbtt__find_table(data, fontstart, "head"); // required - info->glyf = stbtt__find_table(data, fontstart, "glyf"); // required - info->hhea = stbtt__find_table(data, fontstart, "hhea"); // required - info->hmtx = stbtt__find_table(data, fontstart, "hmtx"); // required - info->kern = stbtt__find_table(data, fontstart, "kern"); // not required - if (!cmap || !info->loca || !info->head || !info->glyf || !info->hhea || !info->hmtx) - return 0; - - t = stbtt__find_table(data, fontstart, "maxp"); - if (t) - info->numGlyphs = ttUSHORT(data+t+4); - else - info->numGlyphs = 0xffff; - - // find a cmap encoding table we understand *now* to avoid searching - // later. (todo: could make this installable) - // the same regardless of glyph. - numTables = ttUSHORT(data + cmap + 2); - info->index_map = 0; - for (i=0; i < numTables; ++i) { - stbtt_uint32 encoding_record = cmap + 4 + 8 * i; - // find an encoding we understand: - switch(ttUSHORT(data+encoding_record)) { - case STBTT_PLATFORM_ID_MICROSOFT: - switch (ttUSHORT(data+encoding_record+2)) { - case STBTT_MS_EID_UNICODE_BMP: - case STBTT_MS_EID_UNICODE_FULL: - // MS/Unicode - info->index_map = cmap + ttULONG(data+encoding_record+4); - break; - } - break; - } - } - if (info->index_map == 0) - return 0; - - info->indexToLocFormat = ttUSHORT(data+info->head + 50); - return 1; -} - -int stbtt_FindGlyphIndex(const stbtt_fontinfo *info, int unicode_codepoint) -{ - stbtt_uint8 *data = info->data; - stbtt_uint32 index_map = info->index_map; - - stbtt_uint16 format = ttUSHORT(data + index_map + 0); - if (format == 0) { // apple byte encoding - stbtt_int32 bytes = ttUSHORT(data + index_map + 2); - if (unicode_codepoint < bytes-6) - return ttBYTE(data + index_map + 6 + unicode_codepoint); - return 0; - } else if (format == 6) { - stbtt_uint32 first = ttUSHORT(data + index_map + 6); - stbtt_uint32 count = ttUSHORT(data + index_map + 8); - if ((stbtt_uint32) unicode_codepoint >= first && (stbtt_uint32) unicode_codepoint < first+count) - return ttUSHORT(data + index_map + 10 + (unicode_codepoint - first)*2); - return 0; - } else if (format == 2) { - STBTT_assert(0); // @TODO: high-byte mapping for japanese/chinese/korean - return 0; - } else if (format == 4) { // standard mapping for windows fonts: binary search collection of ranges - stbtt_uint16 segcount = ttUSHORT(data+index_map+6) >> 1; - stbtt_uint16 searchRange = ttUSHORT(data+index_map+8) >> 1; - stbtt_uint16 entrySelector = ttUSHORT(data+index_map+10); - stbtt_uint16 rangeShift = ttUSHORT(data+index_map+12) >> 1; - stbtt_uint16 item, offset, start, end; - - // do a binary search of the segments - stbtt_uint32 endCount = index_map + 14; - stbtt_uint32 search = endCount; - - if (unicode_codepoint > 0xffff) - return 0; - - // they lie from endCount .. endCount + segCount - // but searchRange is the nearest power of two, so... - if (unicode_codepoint >= ttUSHORT(data + search + rangeShift*2)) - search += rangeShift*2; - - // now decrement to bias correctly to find smallest - search -= 2; - while (entrySelector) { - searchRange >>= 1; - start = ttUSHORT(data + search + searchRange*2 + segcount*2 + 2); - end = ttUSHORT(data + search + searchRange*2); - if (unicode_codepoint > end) - search += searchRange*2; - --entrySelector; - } - search += 2; - - item = (stbtt_uint16) ((search - endCount) >> 1); - - STBTT_assert(unicode_codepoint <= ttUSHORT(data + endCount + 2*item)); - start = ttUSHORT(data + index_map + 14 + segcount*2 + 2 + 2*item); - end = ttUSHORT(data + index_map + 14 + 2 + 2*item); - if (unicode_codepoint < start) - return 0; - - offset = ttUSHORT(data + index_map + 14 + segcount*6 + 2 + 2*item); - if (offset == 0) - return (stbtt_uint16) (unicode_codepoint + ttSHORT(data + index_map + 14 + segcount*4 + 2 + 2*item)); - - return ttUSHORT(data + offset + (unicode_codepoint-start)*2 + index_map + 14 + segcount*6 + 2 + 2*item); - } else if (format == 12 || format == 13) { - stbtt_uint32 ngroups = ttULONG(data+index_map+12); - stbtt_int32 low,high; - low = 0; high = (stbtt_int32)ngroups; - // Binary search the right group. - while (low < high) { - stbtt_int32 mid = low + ((high-low) >> 1); // rounds down, so low <= mid < high - stbtt_uint32 start_char = ttULONG(data+index_map+16+mid*12); - stbtt_uint32 end_char = ttULONG(data+index_map+16+mid*12+4); - if ((stbtt_uint32) unicode_codepoint < start_char) - high = mid; - else if ((stbtt_uint32) unicode_codepoint > end_char) - low = mid+1; - else { - stbtt_uint32 start_glyph = ttULONG(data+index_map+16+mid*12+8); - if (format == 12) - return start_glyph + unicode_codepoint-start_char; - else // format == 13 - return start_glyph; - } - } - return 0; // not found - } - // @TODO - STBTT_assert(0); - return 0; -} - -int stbtt_GetCodepointShape(const stbtt_fontinfo *info, int unicode_codepoint, stbtt_vertex **vertices) -{ - return stbtt_GetGlyphShape(info, stbtt_FindGlyphIndex(info, unicode_codepoint), vertices); -} - -static void stbtt_setvertex(stbtt_vertex *v, stbtt_uint8 type, stbtt_int32 x, stbtt_int32 y, stbtt_int32 cx, stbtt_int32 cy) -{ - v->type = type; - v->x = (stbtt_int16) x; - v->y = (stbtt_int16) y; - v->cx = (stbtt_int16) cx; - v->cy = (stbtt_int16) cy; -} - -static int stbtt__GetGlyfOffset(const stbtt_fontinfo *info, int glyph_index) -{ - int g1,g2; - - if (glyph_index >= info->numGlyphs) return -1; // glyph index out of range - if (info->indexToLocFormat >= 2) return -1; // unknown index->glyph map format - - if (info->indexToLocFormat == 0) { - g1 = info->glyf + ttUSHORT(info->data + info->loca + glyph_index * 2) * 2; - g2 = info->glyf + ttUSHORT(info->data + info->loca + glyph_index * 2 + 2) * 2; - } else { - g1 = info->glyf + ttULONG (info->data + info->loca + glyph_index * 4); - g2 = info->glyf + ttULONG (info->data + info->loca + glyph_index * 4 + 4); - } - - return g1==g2 ? -1 : g1; // if length is 0, return -1 -} - -int stbtt_GetGlyphBox(const stbtt_fontinfo *info, int glyph_index, int *x0, int *y0, int *x1, int *y1) -{ - int g = stbtt__GetGlyfOffset(info, glyph_index); - if (g < 0) return 0; - - if (x0) *x0 = ttSHORT(info->data + g + 2); - if (y0) *y0 = ttSHORT(info->data + g + 4); - if (x1) *x1 = ttSHORT(info->data + g + 6); - if (y1) *y1 = ttSHORT(info->data + g + 8); - return 1; -} - -int stbtt_GetCodepointBox(const stbtt_fontinfo *info, int codepoint, int *x0, int *y0, int *x1, int *y1) -{ - return stbtt_GetGlyphBox(info, stbtt_FindGlyphIndex(info,codepoint), x0,y0,x1,y1); -} - -int stbtt_IsGlyphEmpty(const stbtt_fontinfo *info, int glyph_index) -{ - stbtt_int16 numberOfContours; - int g = stbtt__GetGlyfOffset(info, glyph_index); - if (g < 0) return 1; - numberOfContours = ttSHORT(info->data + g); - return numberOfContours == 0; -} - -static int stbtt__close_shape(stbtt_vertex *vertices, int num_vertices, int was_off, int start_off, - stbtt_int32 sx, stbtt_int32 sy, stbtt_int32 scx, stbtt_int32 scy, stbtt_int32 cx, stbtt_int32 cy) -{ - if (start_off) { - if (was_off) - stbtt_setvertex(&vertices[num_vertices++], STBTT_vcurve, (cx+scx)>>1, (cy+scy)>>1, cx,cy); - stbtt_setvertex(&vertices[num_vertices++], STBTT_vcurve, sx,sy,scx,scy); - } else { - if (was_off) - stbtt_setvertex(&vertices[num_vertices++], STBTT_vcurve,sx,sy,cx,cy); - else - stbtt_setvertex(&vertices[num_vertices++], STBTT_vline,sx,sy,0,0); - } - return num_vertices; -} - -int stbtt_GetGlyphShape(const stbtt_fontinfo *info, int glyph_index, stbtt_vertex **pvertices) -{ - stbtt_int16 numberOfContours; - stbtt_uint8 *endPtsOfContours; - stbtt_uint8 *data = info->data; - stbtt_vertex *vertices=0; - int num_vertices=0; - int g = stbtt__GetGlyfOffset(info, glyph_index); - - *pvertices = NULL; - - if (g < 0) return 0; - - numberOfContours = ttSHORT(data + g); - - if (numberOfContours > 0) { - stbtt_uint8 flags=0,flagcount; - stbtt_int32 ins, i,j=0,m,n, next_move, was_off=0, off, start_off=0; - stbtt_int32 x,y,cx,cy,sx,sy, scx,scy; - stbtt_uint8 *points; - endPtsOfContours = (data + g + 10); - ins = ttUSHORT(data + g + 10 + numberOfContours * 2); - points = data + g + 10 + numberOfContours * 2 + 2 + ins; - - n = 1+ttUSHORT(endPtsOfContours + numberOfContours*2-2); - - m = n + 2*numberOfContours; // a loose bound on how many vertices we might need - vertices = (stbtt_vertex *) STBTT_malloc(m * sizeof(vertices[0]), info->userdata); - if (vertices == 0) - return 0; - - next_move = 0; - flagcount=0; - - // in first pass, we load uninterpreted data into the allocated array - // above, shifted to the end of the array so we won't overwrite it when - // we create our final data starting from the front - - off = m - n; // starting offset for uninterpreted data, regardless of how m ends up being calculated - - // first load flags - - for (i=0; i < n; ++i) { - if (flagcount == 0) { - flags = *points++; - if (flags & 8) - flagcount = *points++; - } else - --flagcount; - vertices[off+i].type = flags; - } - - // now load x coordinates - x=0; - for (i=0; i < n; ++i) { - flags = vertices[off+i].type; - if (flags & 2) { - stbtt_int16 dx = *points++; - x += (flags & 16) ? dx : -dx; // ??? - } else { - if (!(flags & 16)) { - x = x + (stbtt_int16) (points[0]*256 + points[1]); - points += 2; - } - } - vertices[off+i].x = (stbtt_int16) x; - } - - // now load y coordinates - y=0; - for (i=0; i < n; ++i) { - flags = vertices[off+i].type; - if (flags & 4) { - stbtt_int16 dy = *points++; - y += (flags & 32) ? dy : -dy; // ??? - } else { - if (!(flags & 32)) { - y = y + (stbtt_int16) (points[0]*256 + points[1]); - points += 2; - } - } - vertices[off+i].y = (stbtt_int16) y; - } - - // now convert them to our format - num_vertices=0; - sx = sy = cx = cy = scx = scy = 0; - for (i=0; i < n; ++i) { - flags = vertices[off+i].type; - x = (stbtt_int16) vertices[off+i].x; - y = (stbtt_int16) vertices[off+i].y; - - if (next_move == i) { - if (i != 0) - num_vertices = stbtt__close_shape(vertices, num_vertices, was_off, start_off, sx,sy,scx,scy,cx,cy); - - // now start the new one - start_off = !(flags & 1); - if (start_off) { - // if we start off with an off-curve point, then when we need to find a point on the curve - // where we can start, and we need to save some state for when we wraparound. - scx = x; - scy = y; - if (!(vertices[off+i+1].type & 1)) { - // next point is also a curve point, so interpolate an on-point curve - sx = (x + (stbtt_int32) vertices[off+i+1].x) >> 1; - sy = (y + (stbtt_int32) vertices[off+i+1].y) >> 1; - } else { - // otherwise just use the next point as our start point - sx = (stbtt_int32) vertices[off+i+1].x; - sy = (stbtt_int32) vertices[off+i+1].y; - ++i; // we're using point i+1 as the starting point, so skip it - } - } else { - sx = x; - sy = y; - } - stbtt_setvertex(&vertices[num_vertices++], STBTT_vmove,sx,sy,0,0); - was_off = 0; - next_move = 1 + ttUSHORT(endPtsOfContours+j*2); - ++j; - } else { - if (!(flags & 1)) { // if it's a curve - if (was_off) // two off-curve control points in a row means interpolate an on-curve midpoint - stbtt_setvertex(&vertices[num_vertices++], STBTT_vcurve, (cx+x)>>1, (cy+y)>>1, cx, cy); - cx = x; - cy = y; - was_off = 1; - } else { - if (was_off) - stbtt_setvertex(&vertices[num_vertices++], STBTT_vcurve, x,y, cx, cy); - else - stbtt_setvertex(&vertices[num_vertices++], STBTT_vline, x,y,0,0); - was_off = 0; - } - } - } - num_vertices = stbtt__close_shape(vertices, num_vertices, was_off, start_off, sx,sy,scx,scy,cx,cy); - } else if (numberOfContours == -1) { - // Compound shapes. - int more = 1; - stbtt_uint8 *comp = data + g + 10; - num_vertices = 0; - vertices = 0; - while (more) { - stbtt_uint16 flags, gidx; - int comp_num_verts = 0, i; - stbtt_vertex *comp_verts = 0, *tmp = 0; - float mtx[6] = {1,0,0,1,0,0}, m, n; - - flags = ttSHORT(comp); comp+=2; - gidx = ttSHORT(comp); comp+=2; - - if (flags & 2) { // XY values - if (flags & 1) { // shorts - mtx[4] = ttSHORT(comp); comp+=2; - mtx[5] = ttSHORT(comp); comp+=2; - } else { - mtx[4] = ttCHAR(comp); comp+=1; - mtx[5] = ttCHAR(comp); comp+=1; - } - } - else { - // @TODO handle matching point - STBTT_assert(0); - } - if (flags & (1<<3)) { // WE_HAVE_A_SCALE - mtx[0] = mtx[3] = ttSHORT(comp)/16384.0f; comp+=2; - mtx[1] = mtx[2] = 0; - } else if (flags & (1<<6)) { // WE_HAVE_AN_X_AND_YSCALE - mtx[0] = ttSHORT(comp)/16384.0f; comp+=2; - mtx[1] = mtx[2] = 0; - mtx[3] = ttSHORT(comp)/16384.0f; comp+=2; - } else if (flags & (1<<7)) { // WE_HAVE_A_TWO_BY_TWO - mtx[0] = ttSHORT(comp)/16384.0f; comp+=2; - mtx[1] = ttSHORT(comp)/16384.0f; comp+=2; - mtx[2] = ttSHORT(comp)/16384.0f; comp+=2; - mtx[3] = ttSHORT(comp)/16384.0f; comp+=2; - } - - // Find transformation scales. - m = (float) STBTT_sqrt(mtx[0]*mtx[0] + mtx[1]*mtx[1]); - n = (float) STBTT_sqrt(mtx[2]*mtx[2] + mtx[3]*mtx[3]); - - // Get indexed glyph. - comp_num_verts = stbtt_GetGlyphShape(info, gidx, &comp_verts); - if (comp_num_verts > 0) { - // Transform vertices. - for (i = 0; i < comp_num_verts; ++i) { - stbtt_vertex* v = &comp_verts[i]; - stbtt_vertex_type x,y; - x=v->x; y=v->y; - v->x = (stbtt_vertex_type)(m * (mtx[0]*x + mtx[2]*y + mtx[4])); - v->y = (stbtt_vertex_type)(n * (mtx[1]*x + mtx[3]*y + mtx[5])); - x=v->cx; y=v->cy; - v->cx = (stbtt_vertex_type)(m * (mtx[0]*x + mtx[2]*y + mtx[4])); - v->cy = (stbtt_vertex_type)(n * (mtx[1]*x + mtx[3]*y + mtx[5])); - } - // Append vertices. - tmp = (stbtt_vertex*)STBTT_malloc((num_vertices+comp_num_verts)*sizeof(stbtt_vertex), info->userdata); - if (!tmp) { - if (vertices) STBTT_free(vertices, info->userdata); - if (comp_verts) STBTT_free(comp_verts, info->userdata); - return 0; - } - if (num_vertices > 0) STBTT_memcpy(tmp, vertices, num_vertices*sizeof(stbtt_vertex)); - STBTT_memcpy(tmp+num_vertices, comp_verts, comp_num_verts*sizeof(stbtt_vertex)); - if (vertices) STBTT_free(vertices, info->userdata); - vertices = tmp; - STBTT_free(comp_verts, info->userdata); - num_vertices += comp_num_verts; - } - // More components ? - more = flags & (1<<5); - } - } else if (numberOfContours < 0) { - // @TODO other compound variations? - STBTT_assert(0); - } else { - // numberOfCounters == 0, do nothing - } - - *pvertices = vertices; - return num_vertices; -} - -void stbtt_GetGlyphHMetrics(const stbtt_fontinfo *info, int glyph_index, int *advanceWidth, int *leftSideBearing) -{ - stbtt_uint16 numOfLongHorMetrics = ttUSHORT(info->data+info->hhea + 34); - if (glyph_index < numOfLongHorMetrics) { - if (advanceWidth) *advanceWidth = ttSHORT(info->data + info->hmtx + 4*glyph_index); - if (leftSideBearing) *leftSideBearing = ttSHORT(info->data + info->hmtx + 4*glyph_index + 2); - } else { - if (advanceWidth) *advanceWidth = ttSHORT(info->data + info->hmtx + 4*(numOfLongHorMetrics-1)); - if (leftSideBearing) *leftSideBearing = ttSHORT(info->data + info->hmtx + 4*numOfLongHorMetrics + 2*(glyph_index - numOfLongHorMetrics)); - } -} - -int stbtt_GetGlyphKernAdvance(const stbtt_fontinfo *info, int glyph1, int glyph2) -{ - stbtt_uint8 *data = info->data + info->kern; - stbtt_uint32 needle, straw; - int l, r, m; - - // we only look at the first table. it must be 'horizontal' and format 0. - if (!info->kern) - return 0; - if (ttUSHORT(data+2) < 1) // number of tables, need at least 1 - return 0; - if (ttUSHORT(data+8) != 1) // horizontal flag must be set in format - return 0; - - l = 0; - r = ttUSHORT(data+10) - 1; - needle = glyph1 << 16 | glyph2; - while (l <= r) { - m = (l + r) >> 1; - straw = ttULONG(data+18+(m*6)); // note: unaligned read - if (needle < straw) - r = m - 1; - else if (needle > straw) - l = m + 1; - else - return ttSHORT(data+22+(m*6)); - } - return 0; -} - -int stbtt_GetCodepointKernAdvance(const stbtt_fontinfo *info, int ch1, int ch2) -{ - if (!info->kern) // if no kerning table, don't waste time looking up both codepoint->glyphs - return 0; - return stbtt_GetGlyphKernAdvance(info, stbtt_FindGlyphIndex(info,ch1), stbtt_FindGlyphIndex(info,ch2)); -} - -void stbtt_GetCodepointHMetrics(const stbtt_fontinfo *info, int codepoint, int *advanceWidth, int *leftSideBearing) -{ - stbtt_GetGlyphHMetrics(info, stbtt_FindGlyphIndex(info,codepoint), advanceWidth, leftSideBearing); -} - -void stbtt_GetFontVMetrics(const stbtt_fontinfo *info, int *ascent, int *descent, int *lineGap) -{ - if (ascent ) *ascent = ttSHORT(info->data+info->hhea + 4); - if (descent) *descent = ttSHORT(info->data+info->hhea + 6); - if (lineGap) *lineGap = ttSHORT(info->data+info->hhea + 8); -} - -void stbtt_GetFontBoundingBox(const stbtt_fontinfo *info, int *x0, int *y0, int *x1, int *y1) -{ - *x0 = ttSHORT(info->data + info->head + 36); - *y0 = ttSHORT(info->data + info->head + 38); - *x1 = ttSHORT(info->data + info->head + 40); - *y1 = ttSHORT(info->data + info->head + 42); -} - -float stbtt_ScaleForPixelHeight(const stbtt_fontinfo *info, float height) -{ - int fheight = ttSHORT(info->data + info->hhea + 4) - ttSHORT(info->data + info->hhea + 6); - return (float) height / fheight; -} - -float stbtt_ScaleForMappingEmToPixels(const stbtt_fontinfo *info, float pixels) -{ - int unitsPerEm = ttUSHORT(info->data + info->head + 18); - return pixels / unitsPerEm; -} - -void stbtt_FreeShape(const stbtt_fontinfo *info, stbtt_vertex *v) -{ - STBTT_free(v, info->userdata); -} - -////////////////////////////////////////////////////////////////////////////// -// -// antialiasing software rasterizer -// - -void stbtt_GetGlyphBitmapBoxSubpixel(const stbtt_fontinfo *font, int glyph, float scale_x, float scale_y,float shift_x, float shift_y, int *ix0, int *iy0, int *ix1, int *iy1) -{ - int x0,y0,x1,y1; - if (!stbtt_GetGlyphBox(font, glyph, &x0,&y0,&x1,&y1)) - x0=y0=x1=y1=0; // e.g. space character - // now move to integral bboxes (treating pixels as little squares, what pixels get touched)? - if (ix0) *ix0 = STBTT_ifloor(x0 * scale_x + shift_x); - if (iy0) *iy0 = -STBTT_iceil (y1 * scale_y + shift_y); - if (ix1) *ix1 = STBTT_iceil (x1 * scale_x + shift_x); - if (iy1) *iy1 = -STBTT_ifloor(y0 * scale_y + shift_y); -} -void stbtt_GetGlyphBitmapBox(const stbtt_fontinfo *font, int glyph, float scale_x, float scale_y, int *ix0, int *iy0, int *ix1, int *iy1) -{ - stbtt_GetGlyphBitmapBoxSubpixel(font, glyph, scale_x, scale_y,0.0f,0.0f, ix0, iy0, ix1, iy1); -} - -void stbtt_GetCodepointBitmapBoxSubpixel(const stbtt_fontinfo *font, int codepoint, float scale_x, float scale_y, float shift_x, float shift_y, int *ix0, int *iy0, int *ix1, int *iy1) -{ - stbtt_GetGlyphBitmapBoxSubpixel(font, stbtt_FindGlyphIndex(font,codepoint), scale_x, scale_y,shift_x,shift_y, ix0,iy0,ix1,iy1); -} - -void stbtt_GetCodepointBitmapBox(const stbtt_fontinfo *font, int codepoint, float scale_x, float scale_y, int *ix0, int *iy0, int *ix1, int *iy1) -{ - stbtt_GetCodepointBitmapBoxSubpixel(font, codepoint, scale_x, scale_y,0.0f,0.0f, ix0,iy0,ix1,iy1); -} - -typedef struct stbtt__edge { - float x0,y0, x1,y1; - int invert; -} stbtt__edge; - -typedef struct stbtt__active_edge -{ - int x,dx; - float ey; - struct stbtt__active_edge *next; - int valid; -} stbtt__active_edge; - -#define FIXSHIFT 10 -#define FIX (1 << FIXSHIFT) -#define FIXMASK (FIX-1) - -static stbtt__active_edge *new_active(stbtt__edge *e, int off_x, float start_point, void *userdata) -{ - stbtt__active_edge *z = (stbtt__active_edge *) STBTT_malloc(sizeof(*z), userdata); // @TODO: make a pool of these!!! - float dxdy = (e->x1 - e->x0) / (e->y1 - e->y0); - STBTT_assert(e->y0 <= start_point); - if (!z) return z; - // round dx down to avoid going too far - if (dxdy < 0) - z->dx = -STBTT_ifloor(FIX * -dxdy); - else - z->dx = STBTT_ifloor(FIX * dxdy); - z->x = STBTT_ifloor(FIX * (e->x0 + dxdy * (start_point - e->y0))); - z->x -= off_x * FIX; - z->ey = e->y1; - z->next = 0; - z->valid = e->invert ? 1 : -1; - return z; -} - -// note: this routine clips fills that extend off the edges... ideally this -// wouldn't happen, but it could happen if the truetype glyph bounding boxes -// are wrong, or if the user supplies a too-small bitmap -static void stbtt__fill_active_edges(unsigned char *scanline, int len, stbtt__active_edge *e, int max_weight) -{ - // non-zero winding fill - int x0=0, w=0; - - while (e) { - if (w == 0) { - // if we're currently at zero, we need to record the edge start point - x0 = e->x; w += e->valid; - } else { - int x1 = e->x; w += e->valid; - // if we went to zero, we need to draw - if (w == 0) { - int i = x0 >> FIXSHIFT; - int j = x1 >> FIXSHIFT; - - if (i < len && j >= 0) { - if (i == j) { - // x0,x1 are the same pixel, so compute combined coverage - scanline[i] = scanline[i] + (stbtt_uint8) ((x1 - x0) * max_weight >> FIXSHIFT); - } else { - if (i >= 0) // add antialiasing for x0 - scanline[i] = scanline[i] + (stbtt_uint8) (((FIX - (x0 & FIXMASK)) * max_weight) >> FIXSHIFT); - else - i = -1; // clip - - if (j < len) // add antialiasing for x1 - scanline[j] = scanline[j] + (stbtt_uint8) (((x1 & FIXMASK) * max_weight) >> FIXSHIFT); - else - j = len; // clip - - for (++i; i < j; ++i) // fill pixels between x0 and x1 - scanline[i] = scanline[i] + (stbtt_uint8) max_weight; - } - } - } - } - - e = e->next; - } -} - -static void stbtt__rasterize_sorted_edges(stbtt__bitmap *result, stbtt__edge *e, int n, int vsubsample, int off_x, int off_y, void *userdata) -{ - stbtt__active_edge *active = NULL; - int y,j=0; - int max_weight = (255 / vsubsample); // weight per vertical scanline - int s; // vertical subsample index - unsigned char scanline_data[512], *scanline; - - if (result->w > 512) - scanline = (unsigned char *) STBTT_malloc(result->w, userdata); - else - scanline = scanline_data; - - y = off_y * vsubsample; - e[n].y0 = (off_y + result->h) * (float) vsubsample + 1; - - while (j < result->h) { - STBTT_memset(scanline, 0, result->w); - for (s=0; s < vsubsample; ++s) { - // find center of pixel for this scanline - float scan_y = y + 0.5f; - stbtt__active_edge **step = &active; - - // update all active edges; - // remove all active edges that terminate before the center of this scanline - while (*step) { - stbtt__active_edge * z = *step; - if (z->ey <= scan_y) { - *step = z->next; // delete from list - STBTT_assert(z->valid); - z->valid = 0; - STBTT_free(z, userdata); - } else { - z->x += z->dx; // advance to position for current scanline - step = &((*step)->next); // advance through list - } - } - - // resort the list if needed - for(;;) { - int changed=0; - step = &active; - while (*step && (*step)->next) { - if ((*step)->x > (*step)->next->x) { - stbtt__active_edge *t = *step; - stbtt__active_edge *q = t->next; - - t->next = q->next; - q->next = t; - *step = q; - changed = 1; - } - step = &(*step)->next; - } - if (!changed) break; - } - - // insert all edges that start before the center of this scanline -- omit ones that also end on this scanline - while (e->y0 <= scan_y) { - if (e->y1 > scan_y) { - stbtt__active_edge *z = new_active(e, off_x, scan_y, userdata); - // find insertion point - if (active == NULL) - active = z; - else if (z->x < active->x) { - // insert at front - z->next = active; - active = z; - } else { - // find thing to insert AFTER - stbtt__active_edge *p = active; - while (p->next && p->next->x < z->x) - p = p->next; - // at this point, p->next->x is NOT < z->x - z->next = p->next; - p->next = z; - } - } - ++e; - } - - // now process all active edges in XOR fashion - if (active) - stbtt__fill_active_edges(scanline, result->w, active, max_weight); - - ++y; - } - STBTT_memcpy(result->pixels + j * result->stride, scanline, result->w); - ++j; - } - - while (active) { - stbtt__active_edge *z = active; - active = active->next; - STBTT_free(z, userdata); - } - - if (scanline != scanline_data) - STBTT_free(scanline, userdata); -} - -static int stbtt__edge_compare(const void *p, const void *q) -{ - stbtt__edge *a = (stbtt__edge *) p; - stbtt__edge *b = (stbtt__edge *) q; - - if (a->y0 < b->y0) return -1; - if (a->y0 > b->y0) return 1; - return 0; -} - -typedef struct -{ - float x,y; -} stbtt__point; - -static void stbtt__rasterize(stbtt__bitmap *result, stbtt__point *pts, int *wcount, int windings, float scale_x, float scale_y, float shift_x, float shift_y, int off_x, int off_y, int invert, void *userdata) -{ - float y_scale_inv = invert ? -scale_y : scale_y; - stbtt__edge *e; - int n,i,j,k,m; - int vsubsample = result->h < 8 ? 15 : 5; - // vsubsample should divide 255 evenly; otherwise we won't reach full opacity - - // now we have to blow out the windings into explicit edge lists - n = 0; - for (i=0; i < windings; ++i) - n += wcount[i]; - - e = (stbtt__edge *) STBTT_malloc(sizeof(*e) * (n+1), userdata); // add an extra one as a sentinel - if (e == 0) return; - n = 0; - - m=0; - for (i=0; i < windings; ++i) { - stbtt__point *p = pts + m; - m += wcount[i]; - j = wcount[i]-1; - for (k=0; k < wcount[i]; j=k++) { - int a=k,b=j; - // skip the edge if horizontal - if (p[j].y == p[k].y) - continue; - // add edge from j to k to the list - e[n].invert = 0; - if (invert ? p[j].y > p[k].y : p[j].y < p[k].y) { - e[n].invert = 1; - a=j,b=k; - } - e[n].x0 = p[a].x * scale_x + shift_x; - e[n].y0 = p[a].y * y_scale_inv * vsubsample + shift_y; - e[n].x1 = p[b].x * scale_x + shift_x; - e[n].y1 = p[b].y * y_scale_inv * vsubsample + shift_y; - ++n; - } - } - - // now sort the edges by their highest point (should snap to integer, and then by x) - STBTT_sort(e, n, sizeof(e[0]), stbtt__edge_compare); - - // now, traverse the scanlines and find the intersections on each scanline, use xor winding rule - stbtt__rasterize_sorted_edges(result, e, n, vsubsample, off_x, off_y, userdata); - - STBTT_free(e, userdata); -} - -static void stbtt__add_point(stbtt__point *points, int n, float x, float y) -{ - if (!points) return; // during first pass, it's unallocated - points[n].x = x; - points[n].y = y; -} - -// tesselate until threshhold p is happy... @TODO warped to compensate for non-linear stretching -static int stbtt__tesselate_curve(stbtt__point *points, int *num_points, float x0, float y0, float x1, float y1, float x2, float y2, float objspace_flatness_squared, int n) -{ - // midpoint - float mx = (x0 + 2*x1 + x2)/4; - float my = (y0 + 2*y1 + y2)/4; - // versus directly drawn line - float dx = (x0+x2)/2 - mx; - float dy = (y0+y2)/2 - my; - if (n > 16) // 65536 segments on one curve better be enough! - return 1; - if (dx*dx+dy*dy > objspace_flatness_squared) { // half-pixel error allowed... need to be smaller if AA - stbtt__tesselate_curve(points, num_points, x0,y0, (x0+x1)/2.0f,(y0+y1)/2.0f, mx,my, objspace_flatness_squared,n+1); - stbtt__tesselate_curve(points, num_points, mx,my, (x1+x2)/2.0f,(y1+y2)/2.0f, x2,y2, objspace_flatness_squared,n+1); - } else { - stbtt__add_point(points, *num_points,x2,y2); - *num_points = *num_points+1; - } - return 1; -} - -// returns number of contours -stbtt__point *stbtt_FlattenCurves(stbtt_vertex *vertices, int num_verts, float objspace_flatness, int **contour_lengths, int *num_contours, void *userdata) -{ - stbtt__point *points=0; - int num_points=0; - - float objspace_flatness_squared = objspace_flatness * objspace_flatness; - int i,n=0,start=0, pass; - - // count how many "moves" there are to get the contour count - for (i=0; i < num_verts; ++i) - if (vertices[i].type == STBTT_vmove) - ++n; - - *num_contours = n; - if (n == 0) return 0; - - *contour_lengths = (int *) STBTT_malloc(sizeof(**contour_lengths) * n, userdata); - - if (*contour_lengths == 0) { - *num_contours = 0; - return 0; - } - - // make two passes through the points so we don't need to realloc - for (pass=0; pass < 2; ++pass) { - float x=0,y=0; - if (pass == 1) { - points = (stbtt__point *) STBTT_malloc(num_points * sizeof(points[0]), userdata); - if (points == NULL) goto error; - } - num_points = 0; - n= -1; - for (i=0; i < num_verts; ++i) { - switch (vertices[i].type) { - case STBTT_vmove: - // start the next contour - if (n >= 0) - (*contour_lengths)[n] = num_points - start; - ++n; - start = num_points; - - x = vertices[i].x, y = vertices[i].y; - stbtt__add_point(points, num_points++, x,y); - break; - case STBTT_vline: - x = vertices[i].x, y = vertices[i].y; - stbtt__add_point(points, num_points++, x, y); - break; - case STBTT_vcurve: - stbtt__tesselate_curve(points, &num_points, x,y, - vertices[i].cx, vertices[i].cy, - vertices[i].x, vertices[i].y, - objspace_flatness_squared, 0); - x = vertices[i].x, y = vertices[i].y; - break; - } - } - (*contour_lengths)[n] = num_points - start; - } - - return points; -error: - STBTT_free(points, userdata); - STBTT_free(*contour_lengths, userdata); - *contour_lengths = 0; - *num_contours = 0; - return NULL; -} - -void stbtt_Rasterize(stbtt__bitmap *result, float flatness_in_pixels, stbtt_vertex *vertices, int num_verts, float scale_x, float scale_y, float shift_x, float shift_y, int x_off, int y_off, int invert, void *userdata) -{ - float scale = scale_x > scale_y ? scale_y : scale_x; - int winding_count, *winding_lengths; - stbtt__point *windings = stbtt_FlattenCurves(vertices, num_verts, flatness_in_pixels / scale, &winding_lengths, &winding_count, userdata); - if (windings) { - stbtt__rasterize(result, windings, winding_lengths, winding_count, scale_x, scale_y, shift_x, shift_y, x_off, y_off, invert, userdata); - STBTT_free(winding_lengths, userdata); - STBTT_free(windings, userdata); - } -} - -void stbtt_FreeBitmap(unsigned char *bitmap, void *userdata) -{ - STBTT_free(bitmap, userdata); -} - -unsigned char *stbtt_GetGlyphBitmapSubpixel(const stbtt_fontinfo *info, float scale_x, float scale_y, float shift_x, float shift_y, int glyph, int *width, int *height, int *xoff, int *yoff) -{ - int ix0,iy0,ix1,iy1; - stbtt__bitmap gbm; - stbtt_vertex *vertices; - int num_verts = stbtt_GetGlyphShape(info, glyph, &vertices); - - if (scale_x == 0) scale_x = scale_y; - if (scale_y == 0) { - if (scale_x == 0) return NULL; - scale_y = scale_x; - } - - stbtt_GetGlyphBitmapBoxSubpixel(info, glyph, scale_x, scale_y, shift_x, shift_y, &ix0,&iy0,&ix1,&iy1); - - // now we get the size - gbm.w = (ix1 - ix0); - gbm.h = (iy1 - iy0); - gbm.pixels = NULL; // in case we error - - if (width ) *width = gbm.w; - if (height) *height = gbm.h; - if (xoff ) *xoff = ix0; - if (yoff ) *yoff = iy0; - - if (gbm.w && gbm.h) { - gbm.pixels = (unsigned char *) STBTT_malloc(gbm.w * gbm.h, info->userdata); - if (gbm.pixels) { - gbm.stride = gbm.w; - - stbtt_Rasterize(&gbm, 0.35f, vertices, num_verts, scale_x, scale_y, shift_x, shift_y, ix0, iy0, 1, info->userdata); - } - } - STBTT_free(vertices, info->userdata); - return gbm.pixels; -} - -unsigned char *stbtt_GetGlyphBitmap(const stbtt_fontinfo *info, float scale_x, float scale_y, int glyph, int *width, int *height, int *xoff, int *yoff) -{ - return stbtt_GetGlyphBitmapSubpixel(info, scale_x, scale_y, 0.0f, 0.0f, glyph, width, height, xoff, yoff); -} - -void stbtt_MakeGlyphBitmapSubpixel(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int glyph) -{ - int ix0,iy0; - stbtt_vertex *vertices; - int num_verts = stbtt_GetGlyphShape(info, glyph, &vertices); - stbtt__bitmap gbm; - - stbtt_GetGlyphBitmapBoxSubpixel(info, glyph, scale_x, scale_y, shift_x, shift_y, &ix0,&iy0,0,0); - gbm.pixels = output; - gbm.w = out_w; - gbm.h = out_h; - gbm.stride = out_stride; - - if (gbm.w && gbm.h) - stbtt_Rasterize(&gbm, 0.35f, vertices, num_verts, scale_x, scale_y, shift_x, shift_y, ix0,iy0, 1, info->userdata); - - STBTT_free(vertices, info->userdata); -} - -void stbtt_MakeGlyphBitmap(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, int glyph) -{ - stbtt_MakeGlyphBitmapSubpixel(info, output, out_w, out_h, out_stride, scale_x, scale_y, 0.0f,0.0f, glyph); -} - -unsigned char *stbtt_GetCodepointBitmapSubpixel(const stbtt_fontinfo *info, float scale_x, float scale_y, float shift_x, float shift_y, int codepoint, int *width, int *height, int *xoff, int *yoff) -{ - return stbtt_GetGlyphBitmapSubpixel(info, scale_x, scale_y,shift_x,shift_y, stbtt_FindGlyphIndex(info,codepoint), width,height,xoff,yoff); -} - -void stbtt_MakeCodepointBitmapSubpixel(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int codepoint) -{ - stbtt_MakeGlyphBitmapSubpixel(info, output, out_w, out_h, out_stride, scale_x, scale_y, shift_x, shift_y, stbtt_FindGlyphIndex(info,codepoint)); -} - -unsigned char *stbtt_GetCodepointBitmap(const stbtt_fontinfo *info, float scale_x, float scale_y, int codepoint, int *width, int *height, int *xoff, int *yoff) -{ - return stbtt_GetCodepointBitmapSubpixel(info, scale_x, scale_y, 0.0f,0.0f, codepoint, width,height,xoff,yoff); -} - -void stbtt_MakeCodepointBitmap(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, int codepoint) -{ - stbtt_MakeCodepointBitmapSubpixel(info, output, out_w, out_h, out_stride, scale_x, scale_y, 0.0f,0.0f, codepoint); -} - -////////////////////////////////////////////////////////////////////////////// -// -// bitmap baking -// -// This is SUPER-CRAPPY packing to keep source code small - -extern int stbtt_BakeFontBitmap(const unsigned char *data, int offset, // font location (use offset=0 for plain .ttf) - float pixel_height, // height of font in pixels - unsigned char *pixels, int pw, int ph, // bitmap to be filled in - int first_char, int num_chars, // characters to bake - stbtt_bakedchar *chardata) -{ - float scale; - int x,y,bottom_y, i; - stbtt_fontinfo f; - stbtt_InitFont(&f, data, offset); - STBTT_memset(pixels, 0, pw*ph); // background of 0 around pixels - x=y=1; - bottom_y = 1; - - scale = stbtt_ScaleForPixelHeight(&f, pixel_height); - - for (i=0; i < num_chars; ++i) { - int advance, lsb, x0,y0,x1,y1,gw,gh; - int g = stbtt_FindGlyphIndex(&f, first_char + i); - stbtt_GetGlyphHMetrics(&f, g, &advance, &lsb); - stbtt_GetGlyphBitmapBox(&f, g, scale,scale, &x0,&y0,&x1,&y1); - gw = x1-x0; - gh = y1-y0; - if (x + gw + 1 >= pw) - y = bottom_y, x = 1; // advance to next row - if (y + gh + 1 >= ph) // check if it fits vertically AFTER potentially moving to next row - return -i; - STBTT_assert(x+gw < pw); - STBTT_assert(y+gh < ph); - stbtt_MakeGlyphBitmap(&f, pixels+x+y*pw, gw,gh,pw, scale,scale, g); - chardata[i].x0 = (stbtt_int16) x; - chardata[i].y0 = (stbtt_int16) y; - chardata[i].x1 = (stbtt_int16) (x + gw); - chardata[i].y1 = (stbtt_int16) (y + gh); - chardata[i].xadvance = scale * advance; - chardata[i].xoff = (float) x0; - chardata[i].yoff = (float) y0; - x = x + gw + 2; - if (y+gh+2 > bottom_y) - bottom_y = y+gh+2; - } - return bottom_y; -} - -void stbtt_GetBakedQuad(stbtt_bakedchar *chardata, int pw, int ph, int char_index, float *xpos, float *ypos, stbtt_aligned_quad *q, int opengl_fillrule) -{ - float d3d_bias = opengl_fillrule ? 0 : -0.5f; - float ipw = 1.0f / pw, iph = 1.0f / ph; - stbtt_bakedchar *b = chardata + char_index; - int round_x = STBTT_ifloor((*xpos + b->xoff) + 0.5); - int round_y = STBTT_ifloor((*ypos + b->yoff) + 0.5); - - q->x0 = round_x + d3d_bias; - q->y0 = round_y + d3d_bias; - q->x1 = round_x + b->x1 - b->x0 + d3d_bias; - q->y1 = round_y + b->y1 - b->y0 + d3d_bias; - - q->s0 = b->x0 * ipw; - q->t0 = b->y0 * iph; - q->s1 = b->x1 * ipw; - q->t1 = b->y1 * iph; - - *xpos += b->xadvance; -} - -////////////////////////////////////////////////////////////////////////////// -// -// font name matching -- recommended not to use this -// - -// check if a utf8 string contains a prefix which is the utf16 string; if so return length of matching utf8 string -static stbtt_int32 stbtt__CompareUTF8toUTF16_bigendian_prefix(const stbtt_uint8 *s1, stbtt_int32 len1, const stbtt_uint8 *s2, stbtt_int32 len2) -{ - stbtt_int32 i=0; - - // convert utf16 to utf8 and compare the results while converting - while (len2) { - stbtt_uint16 ch = s2[0]*256 + s2[1]; - if (ch < 0x80) { - if (i >= len1) return -1; - if (s1[i++] != ch) return -1; - } else if (ch < 0x800) { - if (i+1 >= len1) return -1; - if (s1[i++] != 0xc0 + (ch >> 6)) return -1; - if (s1[i++] != 0x80 + (ch & 0x3f)) return -1; - } else if (ch >= 0xd800 && ch < 0xdc00) { - stbtt_uint32 c; - stbtt_uint16 ch2 = s2[2]*256 + s2[3]; - if (i+3 >= len1) return -1; - c = ((ch - 0xd800) << 10) + (ch2 - 0xdc00) + 0x10000; - if (s1[i++] != 0xf0 + (c >> 18)) return -1; - if (s1[i++] != 0x80 + ((c >> 12) & 0x3f)) return -1; - if (s1[i++] != 0x80 + ((c >> 6) & 0x3f)) return -1; - if (s1[i++] != 0x80 + ((c ) & 0x3f)) return -1; - s2 += 2; // plus another 2 below - len2 -= 2; - } else if (ch >= 0xdc00 && ch < 0xe000) { - return -1; - } else { - if (i+2 >= len1) return -1; - if (s1[i++] != 0xe0 + (ch >> 12)) return -1; - if (s1[i++] != 0x80 + ((ch >> 6) & 0x3f)) return -1; - if (s1[i++] != 0x80 + ((ch ) & 0x3f)) return -1; - } - s2 += 2; - len2 -= 2; - } - return i; -} - -int stbtt_CompareUTF8toUTF16_bigendian(const char *s1, int len1, const char *s2, int len2) -{ - return len1 == stbtt__CompareUTF8toUTF16_bigendian_prefix((const stbtt_uint8*) s1, len1, (const stbtt_uint8*) s2, len2); -} - -// returns results in whatever encoding you request... but note that 2-byte encodings -// will be BIG-ENDIAN... use stbtt_CompareUTF8toUTF16_bigendian() to compare -const char *stbtt_GetFontNameString(const stbtt_fontinfo *font, int *length, int platformID, int encodingID, int languageID, int nameID) -{ - stbtt_int32 i,count,stringOffset; - stbtt_uint8 *fc = font->data; - stbtt_uint32 offset = font->fontstart; - stbtt_uint32 nm = stbtt__find_table(fc, offset, "name"); - if (!nm) return NULL; - - count = ttUSHORT(fc+nm+2); - stringOffset = nm + ttUSHORT(fc+nm+4); - for (i=0; i < count; ++i) { - stbtt_uint32 loc = nm + 6 + 12 * i; - if (platformID == ttUSHORT(fc+loc+0) && encodingID == ttUSHORT(fc+loc+2) - && languageID == ttUSHORT(fc+loc+4) && nameID == ttUSHORT(fc+loc+6)) { - *length = ttUSHORT(fc+loc+8); - return (const char *) (fc+stringOffset+ttUSHORT(fc+loc+10)); - } - } - return NULL; -} - -static int stbtt__matchpair(stbtt_uint8 *fc, stbtt_uint32 nm, stbtt_uint8 *name, stbtt_int32 nlen, stbtt_int32 target_id, stbtt_int32 next_id) -{ - stbtt_int32 i; - stbtt_int32 count = ttUSHORT(fc+nm+2); - stbtt_int32 stringOffset = nm + ttUSHORT(fc+nm+4); - - for (i=0; i < count; ++i) { - stbtt_uint32 loc = nm + 6 + 12 * i; - stbtt_int32 id = ttUSHORT(fc+loc+6); - if (id == target_id) { - // find the encoding - stbtt_int32 platform = ttUSHORT(fc+loc+0), encoding = ttUSHORT(fc+loc+2), language = ttUSHORT(fc+loc+4); - - // is this a Unicode encoding? - if (platform == 0 || (platform == 3 && encoding == 1) || (platform == 3 && encoding == 10)) { - stbtt_int32 slen = ttUSHORT(fc+loc+8); - stbtt_int32 off = ttUSHORT(fc+loc+10); - - // check if there's a prefix match - stbtt_int32 matchlen = stbtt__CompareUTF8toUTF16_bigendian_prefix(name, nlen, fc+stringOffset+off,slen); - if (matchlen >= 0) { - // check for target_id+1 immediately following, with same encoding & language - if (i+1 < count && ttUSHORT(fc+loc+12+6) == next_id && ttUSHORT(fc+loc+12) == platform && ttUSHORT(fc+loc+12+2) == encoding && ttUSHORT(fc+loc+12+4) == language) { - slen = ttUSHORT(fc+loc+12+8); - off = ttUSHORT(fc+loc+12+10); - if (slen == 0) { - if (matchlen == nlen) - return 1; - } else if (matchlen < nlen && name[matchlen] == ' ') { - ++matchlen; - if (stbtt_CompareUTF8toUTF16_bigendian((char*) (name+matchlen), nlen-matchlen, (char*)(fc+stringOffset+off),slen)) - return 1; - } - } else { - // if nothing immediately following - if (matchlen == nlen) - return 1; - } - } - } - - // @TODO handle other encodings - } - } - return 0; -} - -static int stbtt__matches(stbtt_uint8 *fc, stbtt_uint32 offset, stbtt_uint8 *name, stbtt_int32 flags) -{ - stbtt_int32 nlen = (stbtt_int32) STBTT_strlen((char *) name); - stbtt_uint32 nm,hd; - if (!stbtt__isfont(fc+offset)) return 0; - - // check italics/bold/underline flags in macStyle... - if (flags) { - hd = stbtt__find_table(fc, offset, "head"); - if ((ttUSHORT(fc+hd+44) & 7) != (flags & 7)) return 0; - } - - nm = stbtt__find_table(fc, offset, "name"); - if (!nm) return 0; - - if (flags) { - // if we checked the macStyle flags, then just check the family and ignore the subfamily - if (stbtt__matchpair(fc, nm, name, nlen, 16, -1)) return 1; - if (stbtt__matchpair(fc, nm, name, nlen, 1, -1)) return 1; - if (stbtt__matchpair(fc, nm, name, nlen, 3, -1)) return 1; - } else { - if (stbtt__matchpair(fc, nm, name, nlen, 16, 17)) return 1; - if (stbtt__matchpair(fc, nm, name, nlen, 1, 2)) return 1; - if (stbtt__matchpair(fc, nm, name, nlen, 3, -1)) return 1; - } - - return 0; -} - -int stbtt_FindMatchingFont(const unsigned char *font_collection, const char *name_utf8, stbtt_int32 flags) -{ - stbtt_int32 i; - for (i=0;;++i) { - stbtt_int32 off = stbtt_GetFontOffsetForIndex(font_collection, i); - if (off < 0) return off; - if (stbtt__matches((stbtt_uint8 *) font_collection, off, (stbtt_uint8*) name_utf8, flags)) - return off; - } -} - -#endif // STB_TRUETYPE_IMPLEMENTATION +// stb_truetype.h - v1.24 - public domain +// authored from 2009-2020 by Sean Barrett / RAD Game Tools +// +// ======================================================================= +// +// NO SECURITY GUARANTEE -- DO NOT USE THIS ON UNTRUSTED FONT FILES +// +// This library does no range checking of the offsets found in the file, +// meaning an attacker can use it to read arbitrary memory. +// +// ======================================================================= +// +// This library processes TrueType files: +// parse files +// extract glyph metrics +// extract glyph shapes +// render glyphs to one-channel bitmaps with antialiasing (box filter) +// render glyphs to one-channel SDF bitmaps (signed-distance field/function) +// +// Todo: +// non-MS cmaps +// crashproof on bad data +// hinting? (no longer patented) +// cleartype-style AA? +// optimize: use simple memory allocator for intermediates +// optimize: build edge-list directly from curves +// optimize: rasterize directly from curves? +// +// ADDITIONAL CONTRIBUTORS +// +// Mikko Mononen: compound shape support, more cmap formats +// Tor Andersson: kerning, subpixel rendering +// Dougall Johnson: OpenType / Type 2 font handling +// Daniel Ribeiro Maciel: basic GPOS-based kerning +// +// Misc other: +// Ryan Gordon +// Simon Glass +// github:IntellectualKitty +// Imanol Celaya +// Daniel Ribeiro Maciel +// +// Bug/warning reports/fixes: +// "Zer" on mollyrocket Fabian "ryg" Giesen github:NiLuJe +// Cass Everitt Martins Mozeiko github:aloucks +// stoiko (Haemimont Games) Cap Petschulat github:oyvindjam +// Brian Hook Omar Cornut github:vassvik +// Walter van Niftrik Ryan Griege +// David Gow Peter LaValle +// David Given Sergey Popov +// Ivan-Assen Ivanov Giumo X. Clanjor +// Anthony Pesch Higor Euripedes +// Johan Duparc Thomas Fields +// Hou Qiming Derek Vinyard +// Rob Loach Cort Stratton +// Kenney Phillis Jr. Brian Costabile +// Ken Voskuil (kaesve) +// +// VERSION HISTORY +// +// 1.24 (2020-02-05) fix warning +// 1.23 (2020-02-02) query SVG data for glyphs; query whole kerning table (but only kern not GPOS) +// 1.22 (2019-08-11) minimize missing-glyph duplication; fix kerning if both 'GPOS' and 'kern' are defined +// 1.21 (2019-02-25) fix warning +// 1.20 (2019-02-07) PackFontRange skips missing codepoints; GetScaleFontVMetrics() +// 1.19 (2018-02-11) GPOS kerning, STBTT_fmod +// 1.18 (2018-01-29) add missing function +// 1.17 (2017-07-23) make more arguments const; doc fix +// 1.16 (2017-07-12) SDF support +// 1.15 (2017-03-03) make more arguments const +// 1.14 (2017-01-16) num-fonts-in-TTC function +// 1.13 (2017-01-02) support OpenType fonts, certain Apple fonts +// 1.12 (2016-10-25) suppress warnings about casting away const with -Wcast-qual +// 1.11 (2016-04-02) fix unused-variable warning +// 1.10 (2016-04-02) user-defined fabs(); rare memory leak; remove duplicate typedef +// 1.09 (2016-01-16) warning fix; avoid crash on outofmem; use allocation userdata properly +// 1.08 (2015-09-13) document stbtt_Rasterize(); fixes for vertical & horizontal edges +// 1.07 (2015-08-01) allow PackFontRanges to accept arrays of sparse codepoints; +// variant PackFontRanges to pack and render in separate phases; +// fix stbtt_GetFontOFfsetForIndex (never worked for non-0 input?); +// fixed an assert() bug in the new rasterizer +// replace assert() with STBTT_assert() in new rasterizer +// +// Full history can be found at the end of this file. +// +// LICENSE +// +// See end of file for license information. +// +// USAGE +// +// Include this file in whatever places need to refer to it. In ONE C/C++ +// file, write: +// #define STB_TRUETYPE_IMPLEMENTATION +// before the #include of this file. This expands out the actual +// implementation into that C/C++ file. +// +// To make the implementation private to the file that generates the implementation, +// #define STBTT_STATIC +// +// Simple 3D API (don't ship this, but it's fine for tools and quick start) +// stbtt_BakeFontBitmap() -- bake a font to a bitmap for use as texture +// stbtt_GetBakedQuad() -- compute quad to draw for a given char +// +// Improved 3D API (more shippable): +// #include "stb_rect_pack.h" -- optional, but you really want it +// stbtt_PackBegin() +// stbtt_PackSetOversampling() -- for improved quality on small fonts +// stbtt_PackFontRanges() -- pack and renders +// stbtt_PackEnd() +// stbtt_GetPackedQuad() +// +// "Load" a font file from a memory buffer (you have to keep the buffer loaded) +// stbtt_InitFont() +// stbtt_GetFontOffsetForIndex() -- indexing for TTC font collections +// stbtt_GetNumberOfFonts() -- number of fonts for TTC font collections +// +// Render a unicode codepoint to a bitmap +// stbtt_GetCodepointBitmap() -- allocates and returns a bitmap +// stbtt_MakeCodepointBitmap() -- renders into bitmap you provide +// stbtt_GetCodepointBitmapBox() -- how big the bitmap must be +// +// Character advance/positioning +// stbtt_GetCodepointHMetrics() +// stbtt_GetFontVMetrics() +// stbtt_GetFontVMetricsOS2() +// stbtt_GetCodepointKernAdvance() +// +// Starting with version 1.06, the rasterizer was replaced with a new, +// faster and generally-more-precise rasterizer. The new rasterizer more +// accurately measures pixel coverage for anti-aliasing, except in the case +// where multiple shapes overlap, in which case it overestimates the AA pixel +// coverage. Thus, anti-aliasing of intersecting shapes may look wrong. If +// this turns out to be a problem, you can re-enable the old rasterizer with +// #define STBTT_RASTERIZER_VERSION 1 +// which will incur about a 15% speed hit. +// +// ADDITIONAL DOCUMENTATION +// +// Immediately after this block comment are a series of sample programs. +// +// After the sample programs is the "header file" section. This section +// includes documentation for each API function. +// +// Some important concepts to understand to use this library: +// +// Codepoint +// Characters are defined by unicode codepoints, e.g. 65 is +// uppercase A, 231 is lowercase c with a cedilla, 0x7e30 is +// the hiragana for "ma". +// +// Glyph +// A visual character shape (every codepoint is rendered as +// some glyph) +// +// Glyph index +// A font-specific integer ID representing a glyph +// +// Baseline +// Glyph shapes are defined relative to a baseline, which is the +// bottom of uppercase characters. Characters extend both above +// and below the baseline. +// +// Current Point +// As you draw text to the screen, you keep track of a "current point" +// which is the origin of each character. The current point's vertical +// position is the baseline. Even "baked fonts" use this model. +// +// Vertical Font Metrics +// The vertical qualities of the font, used to vertically position +// and space the characters. See docs for stbtt_GetFontVMetrics. +// +// Font Size in Pixels or Points +// The preferred interface for specifying font sizes in stb_truetype +// is to specify how tall the font's vertical extent should be in pixels. +// If that sounds good enough, skip the next paragraph. +// +// Most font APIs instead use "points", which are a common typographic +// measurement for describing font size, defined as 72 points per inch. +// stb_truetype provides a point API for compatibility. However, true +// "per inch" conventions don't make much sense on computer displays +// since different monitors have different number of pixels per +// inch. For example, Windows traditionally uses a convention that +// there are 96 pixels per inch, thus making 'inch' measurements have +// nothing to do with inches, and thus effectively defining a point to +// be 1.333 pixels. Additionally, the TrueType font data provides +// an explicit scale factor to scale a given font's glyphs to points, +// but the author has observed that this scale factor is often wrong +// for non-commercial fonts, thus making fonts scaled in points +// according to the TrueType spec incoherently sized in practice. +// +// DETAILED USAGE: +// +// Scale: +// Select how high you want the font to be, in points or pixels. +// Call ScaleForPixelHeight or ScaleForMappingEmToPixels to compute +// a scale factor SF that will be used by all other functions. +// +// Baseline: +// You need to select a y-coordinate that is the baseline of where +// your text will appear. Call GetFontBoundingBox to get the baseline-relative +// bounding box for all characters. SF*-y0 will be the distance in pixels +// that the worst-case character could extend above the baseline, so if +// you want the top edge of characters to appear at the top of the +// screen where y=0, then you would set the baseline to SF*-y0. +// +// Current point: +// Set the current point where the first character will appear. The +// first character could extend left of the current point; this is font +// dependent. You can either choose a current point that is the leftmost +// point and hope, or add some padding, or check the bounding box or +// left-side-bearing of the first character to be displayed and set +// the current point based on that. +// +// Displaying a character: +// Compute the bounding box of the character. It will contain signed values +// relative to . I.e. if it returns x0,y0,x1,y1, +// then the character should be displayed in the rectangle from +// to = 32 && *text < 128) { + stbtt_aligned_quad q; + stbtt_GetBakedQuad(cdata, 512,512, *text-32, &x,&y,&q,1);//1=opengl & d3d10+,0=d3d9 + glTexCoord2f(q.s0,q.t1); glVertex2f(q.x0,q.y0); + glTexCoord2f(q.s1,q.t1); glVertex2f(q.x1,q.y0); + glTexCoord2f(q.s1,q.t0); glVertex2f(q.x1,q.y1); + glTexCoord2f(q.s0,q.t0); glVertex2f(q.x0,q.y1); + } + ++text; + } + glEnd(); +} +#endif +// +// +////////////////////////////////////////////////////////////////////////////// +// +// Complete program (this compiles): get a single bitmap, print as ASCII art +// +#if 0 +#include +#define STB_TRUETYPE_IMPLEMENTATION // force following include to generate implementation +#include "stb_truetype.h" + +char ttf_buffer[1<<25]; + +int main(int argc, char **argv) +{ + stbtt_fontinfo font; + unsigned char *bitmap; + int w,h,i,j,c = (argc > 1 ? atoi(argv[1]) : 'a'), s = (argc > 2 ? atoi(argv[2]) : 20); + + fread(ttf_buffer, 1, 1<<25, fopen(argc > 3 ? argv[3] : "c:/windows/fonts/arialbd.ttf", "rb")); + + stbtt_InitFont(&font, ttf_buffer, stbtt_GetFontOffsetForIndex(ttf_buffer,0)); + bitmap = stbtt_GetCodepointBitmap(&font, 0,stbtt_ScaleForPixelHeight(&font, s), c, &w, &h, 0,0); + + for (j=0; j < h; ++j) { + for (i=0; i < w; ++i) + putchar(" .:ioVM@"[bitmap[j*w+i]>>5]); + putchar('\n'); + } + return 0; +} +#endif +// +// Output: +// +// .ii. +// @@@@@@. +// V@Mio@@o +// :i. V@V +// :oM@@M +// :@@@MM@M +// @@o o@M +// :@@. M@M +// @@@o@@@@ +// :M@@V:@@. +// +////////////////////////////////////////////////////////////////////////////// +// +// Complete program: print "Hello World!" banner, with bugs +// +#if 0 +char buffer[24<<20]; +unsigned char screen[20][79]; + +int main(int arg, char **argv) +{ + stbtt_fontinfo font; + int i,j,ascent,baseline,ch=0; + float scale, xpos=2; // leave a little padding in case the character extends left + char *text = "Heljo World!"; // intentionally misspelled to show 'lj' brokenness + + fread(buffer, 1, 1000000, fopen("c:/windows/fonts/arialbd.ttf", "rb")); + stbtt_InitFont(&font, buffer, 0); + + scale = stbtt_ScaleForPixelHeight(&font, 15); + stbtt_GetFontVMetrics(&font, &ascent,0,0); + baseline = (int) (ascent*scale); + + while (text[ch]) { + int advance,lsb,x0,y0,x1,y1; + float x_shift = xpos - (float) floor(xpos); + stbtt_GetCodepointHMetrics(&font, text[ch], &advance, &lsb); + stbtt_GetCodepointBitmapBoxSubpixel(&font, text[ch], scale,scale,x_shift,0, &x0,&y0,&x1,&y1); + stbtt_MakeCodepointBitmapSubpixel(&font, &screen[baseline + y0][(int) xpos + x0], x1-x0,y1-y0, 79, scale,scale,x_shift,0, text[ch]); + // note that this stomps the old data, so where character boxes overlap (e.g. 'lj') it's wrong + // because this API is really for baking character bitmaps into textures. if you want to render + // a sequence of characters, you really need to render each bitmap to a temp buffer, then + // "alpha blend" that into the working buffer + xpos += (advance * scale); + if (text[ch+1]) + xpos += scale*stbtt_GetCodepointKernAdvance(&font, text[ch],text[ch+1]); + ++ch; + } + + for (j=0; j < 20; ++j) { + for (i=0; i < 78; ++i) + putchar(" .:ioVM@"[screen[j][i]>>5]); + putchar('\n'); + } + + return 0; +} +#endif + + +////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////////// +//// +//// INTEGRATION WITH YOUR CODEBASE +//// +//// The following sections allow you to supply alternate definitions +//// of C library functions used by stb_truetype, e.g. if you don't +//// link with the C runtime library. + +#ifdef STB_TRUETYPE_IMPLEMENTATION + // #define your own (u)stbtt_int8/16/32 before including to override this + #ifndef stbtt_uint8 + typedef unsigned char stbtt_uint8; + typedef signed char stbtt_int8; + typedef unsigned short stbtt_uint16; + typedef signed short stbtt_int16; + typedef unsigned int stbtt_uint32; + typedef signed int stbtt_int32; + #endif + + typedef char stbtt__check_size32[sizeof(stbtt_int32)==4 ? 1 : -1]; + typedef char stbtt__check_size16[sizeof(stbtt_int16)==2 ? 1 : -1]; + + // e.g. #define your own STBTT_ifloor/STBTT_iceil() to avoid math.h + #ifndef STBTT_ifloor + #include + #define STBTT_ifloor(x) ((int) floor(x)) + #define STBTT_iceil(x) ((int) ceil(x)) + #endif + + #ifndef STBTT_sqrt + #include + #define STBTT_sqrt(x) sqrt(x) + #define STBTT_pow(x,y) pow(x,y) + #endif + + #ifndef STBTT_fmod + #include + #define STBTT_fmod(x,y) fmod(x,y) + #endif + + #ifndef STBTT_cos + #include + #define STBTT_cos(x) cos(x) + #define STBTT_acos(x) acos(x) + #endif + + #ifndef STBTT_fabs + #include + #define STBTT_fabs(x) fabs(x) + #endif + + // #define your own functions "STBTT_malloc" / "STBTT_free" to avoid malloc.h + #ifndef STBTT_malloc + #include + #define STBTT_malloc(x,u) ((void)(u),malloc(x)) + #define STBTT_free(x,u) ((void)(u),free(x)) + #endif + + #ifndef STBTT_assert + #include + #define STBTT_assert(x) assert(x) + #endif + + #ifndef STBTT_strlen + #include + #define STBTT_strlen(x) strlen(x) + #endif + + #ifndef STBTT_memcpy + #include + #define STBTT_memcpy memcpy + #define STBTT_memset memset + #endif +#endif + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +//// +//// INTERFACE +//// +//// + +#ifndef __STB_INCLUDE_STB_TRUETYPE_H__ +#define __STB_INCLUDE_STB_TRUETYPE_H__ + +#ifdef STBTT_STATIC +#define STBTT_DEF static +#else +#define STBTT_DEF extern +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +// private structure +typedef struct +{ + unsigned char *data; + int cursor; + int size; +} stbtt__buf; + +////////////////////////////////////////////////////////////////////////////// +// +// TEXTURE BAKING API +// +// If you use this API, you only have to call two functions ever. +// + +typedef struct +{ + unsigned short x0,y0,x1,y1; // coordinates of bbox in bitmap + float xoff,yoff,xadvance; +} stbtt_bakedchar; + +STBTT_DEF int stbtt_BakeFontBitmap(const unsigned char *data, int offset, // font location (use offset=0 for plain .ttf) + float pixel_height, // height of font in pixels + unsigned char *pixels, int pw, int ph, // bitmap to be filled in + int first_char, int num_chars, // characters to bake + stbtt_bakedchar *chardata); // you allocate this, it's num_chars long +// if return is positive, the first unused row of the bitmap +// if return is negative, returns the negative of the number of characters that fit +// if return is 0, no characters fit and no rows were used +// This uses a very crappy packing. + +typedef struct +{ + float x0,y0,s0,t0; // top-left + float x1,y1,s1,t1; // bottom-right +} stbtt_aligned_quad; + +STBTT_DEF void stbtt_GetBakedQuad(const stbtt_bakedchar *chardata, int pw, int ph, // same data as above + int char_index, // character to display + float *xpos, float *ypos, // pointers to current position in screen pixel space + stbtt_aligned_quad *q, // output: quad to draw + int opengl_fillrule); // true if opengl fill rule; false if DX9 or earlier +// Call GetBakedQuad with char_index = 'character - first_char', and it +// creates the quad you need to draw and advances the current position. +// +// The coordinate system used assumes y increases downwards. +// +// Characters will extend both above and below the current position; +// see discussion of "BASELINE" above. +// +// It's inefficient; you might want to c&p it and optimize it. + +STBTT_DEF void stbtt_GetScaledFontVMetrics(const unsigned char *fontdata, int index, float size, float *ascent, float *descent, float *lineGap); +// Query the font vertical metrics without having to create a font first. + + +////////////////////////////////////////////////////////////////////////////// +// +// NEW TEXTURE BAKING API +// +// This provides options for packing multiple fonts into one atlas, not +// perfectly but better than nothing. + +typedef struct +{ + unsigned short x0,y0,x1,y1; // coordinates of bbox in bitmap + float xoff,yoff,xadvance; + float xoff2,yoff2; +} stbtt_packedchar; + +typedef struct stbtt_pack_context stbtt_pack_context; +typedef struct stbtt_fontinfo stbtt_fontinfo; +#ifndef STB_RECT_PACK_VERSION +typedef struct stbrp_rect stbrp_rect; +#endif + +STBTT_DEF int stbtt_PackBegin(stbtt_pack_context *spc, unsigned char *pixels, int width, int height, int stride_in_bytes, int padding, void *alloc_context); +// Initializes a packing context stored in the passed-in stbtt_pack_context. +// Future calls using this context will pack characters into the bitmap passed +// in here: a 1-channel bitmap that is width * height. stride_in_bytes is +// the distance from one row to the next (or 0 to mean they are packed tightly +// together). "padding" is the amount of padding to leave between each +// character (normally you want '1' for bitmaps you'll use as textures with +// bilinear filtering). +// +// Returns 0 on failure, 1 on success. + +STBTT_DEF void stbtt_PackEnd (stbtt_pack_context *spc); +// Cleans up the packing context and frees all memory. + +#define STBTT_POINT_SIZE(x) (-(x)) + +STBTT_DEF int stbtt_PackFontRange(stbtt_pack_context *spc, const unsigned char *fontdata, int font_index, float font_size, + int first_unicode_char_in_range, int num_chars_in_range, stbtt_packedchar *chardata_for_range); +// Creates character bitmaps from the font_index'th font found in fontdata (use +// font_index=0 if you don't know what that is). It creates num_chars_in_range +// bitmaps for characters with unicode values starting at first_unicode_char_in_range +// and increasing. Data for how to render them is stored in chardata_for_range; +// pass these to stbtt_GetPackedQuad to get back renderable quads. +// +// font_size is the full height of the character from ascender to descender, +// as computed by stbtt_ScaleForPixelHeight. To use a point size as computed +// by stbtt_ScaleForMappingEmToPixels, wrap the point size in STBTT_POINT_SIZE() +// and pass that result as 'font_size': +// ..., 20 , ... // font max minus min y is 20 pixels tall +// ..., STBTT_POINT_SIZE(20), ... // 'M' is 20 pixels tall + +typedef struct +{ + float font_size; + int first_unicode_codepoint_in_range; // if non-zero, then the chars are continuous, and this is the first codepoint + int *array_of_unicode_codepoints; // if non-zero, then this is an array of unicode codepoints + int num_chars; + stbtt_packedchar *chardata_for_range; // output + unsigned char h_oversample, v_oversample; // don't set these, they're used internally +} stbtt_pack_range; + +STBTT_DEF int stbtt_PackFontRanges(stbtt_pack_context *spc, const unsigned char *fontdata, int font_index, stbtt_pack_range *ranges, int num_ranges); +// Creates character bitmaps from multiple ranges of characters stored in +// ranges. This will usually create a better-packed bitmap than multiple +// calls to stbtt_PackFontRange. Note that you can call this multiple +// times within a single PackBegin/PackEnd. + +STBTT_DEF void stbtt_PackSetOversampling(stbtt_pack_context *spc, unsigned int h_oversample, unsigned int v_oversample); +// Oversampling a font increases the quality by allowing higher-quality subpixel +// positioning, and is especially valuable at smaller text sizes. +// +// This function sets the amount of oversampling for all following calls to +// stbtt_PackFontRange(s) or stbtt_PackFontRangesGatherRects for a given +// pack context. The default (no oversampling) is achieved by h_oversample=1 +// and v_oversample=1. The total number of pixels required is +// h_oversample*v_oversample larger than the default; for example, 2x2 +// oversampling requires 4x the storage of 1x1. For best results, render +// oversampled textures with bilinear filtering. Look at the readme in +// stb/tests/oversample for information about oversampled fonts +// +// To use with PackFontRangesGather etc., you must set it before calls +// call to PackFontRangesGatherRects. + +STBTT_DEF void stbtt_PackSetSkipMissingCodepoints(stbtt_pack_context *spc, int skip); +// If skip != 0, this tells stb_truetype to skip any codepoints for which +// there is no corresponding glyph. If skip=0, which is the default, then +// codepoints without a glyph recived the font's "missing character" glyph, +// typically an empty box by convention. + +STBTT_DEF void stbtt_GetPackedQuad(const stbtt_packedchar *chardata, int pw, int ph, // same data as above + int char_index, // character to display + float *xpos, float *ypos, // pointers to current position in screen pixel space + stbtt_aligned_quad *q, // output: quad to draw + int align_to_integer); + +STBTT_DEF int stbtt_PackFontRangesGatherRects(stbtt_pack_context *spc, const stbtt_fontinfo *info, stbtt_pack_range *ranges, int num_ranges, stbrp_rect *rects); +STBTT_DEF void stbtt_PackFontRangesPackRects(stbtt_pack_context *spc, stbrp_rect *rects, int num_rects); +STBTT_DEF int stbtt_PackFontRangesRenderIntoRects(stbtt_pack_context *spc, const stbtt_fontinfo *info, stbtt_pack_range *ranges, int num_ranges, stbrp_rect *rects); +// Calling these functions in sequence is roughly equivalent to calling +// stbtt_PackFontRanges(). If you more control over the packing of multiple +// fonts, or if you want to pack custom data into a font texture, take a look +// at the source to of stbtt_PackFontRanges() and create a custom version +// using these functions, e.g. call GatherRects multiple times, +// building up a single array of rects, then call PackRects once, +// then call RenderIntoRects repeatedly. This may result in a +// better packing than calling PackFontRanges multiple times +// (or it may not). + +// this is an opaque structure that you shouldn't mess with which holds +// all the context needed from PackBegin to PackEnd. +struct stbtt_pack_context { + void *user_allocator_context; + void *pack_info; + int width; + int height; + int stride_in_bytes; + int padding; + int skip_missing; + unsigned int h_oversample, v_oversample; + unsigned char *pixels; + void *nodes; +}; + +////////////////////////////////////////////////////////////////////////////// +// +// FONT LOADING +// +// + +STBTT_DEF int stbtt_GetNumberOfFonts(const unsigned char *data); +// This function will determine the number of fonts in a font file. TrueType +// collection (.ttc) files may contain multiple fonts, while TrueType font +// (.ttf) files only contain one font. The number of fonts can be used for +// indexing with the previous function where the index is between zero and one +// less than the total fonts. If an error occurs, -1 is returned. + +STBTT_DEF int stbtt_GetFontOffsetForIndex(const unsigned char *data, int index); +// Each .ttf/.ttc file may have more than one font. Each font has a sequential +// index number starting from 0. Call this function to get the font offset for +// a given index; it returns -1 if the index is out of range. A regular .ttf +// file will only define one font and it always be at offset 0, so it will +// return '0' for index 0, and -1 for all other indices. + +// The following structure is defined publicly so you can declare one on +// the stack or as a global or etc, but you should treat it as opaque. +struct stbtt_fontinfo +{ + void * userdata; + unsigned char * data; // pointer to .ttf file + int fontstart; // offset of start of font + + int numGlyphs; // number of glyphs, needed for range checking + + int loca,head,glyf,hhea,hmtx,kern,gpos,svg; // table locations as offset from start of .ttf + int index_map; // a cmap mapping for our chosen character encoding + int indexToLocFormat; // format needed to map from glyph index to glyph + + stbtt__buf cff; // cff font data + stbtt__buf charstrings; // the charstring index + stbtt__buf gsubrs; // global charstring subroutines index + stbtt__buf subrs; // private charstring subroutines index + stbtt__buf fontdicts; // array of font dicts + stbtt__buf fdselect; // map from glyph to fontdict +}; + +STBTT_DEF int stbtt_InitFont(stbtt_fontinfo *info, const unsigned char *data, int offset); +// Given an offset into the file that defines a font, this function builds +// the necessary cached info for the rest of the system. You must allocate +// the stbtt_fontinfo yourself, and stbtt_InitFont will fill it out. You don't +// need to do anything special to free it, because the contents are pure +// value data with no additional data structures. Returns 0 on failure. + + +////////////////////////////////////////////////////////////////////////////// +// +// CHARACTER TO GLYPH-INDEX CONVERSIOn + +STBTT_DEF int stbtt_FindGlyphIndex(const stbtt_fontinfo *info, int unicode_codepoint); +// If you're going to perform multiple operations on the same character +// and you want a speed-up, call this function with the character you're +// going to process, then use glyph-based functions instead of the +// codepoint-based functions. +// Returns 0 if the character codepoint is not defined in the font. + + +////////////////////////////////////////////////////////////////////////////// +// +// CHARACTER PROPERTIES +// + +STBTT_DEF float stbtt_ScaleForPixelHeight(const stbtt_fontinfo *info, float pixels); +// computes a scale factor to produce a font whose "height" is 'pixels' tall. +// Height is measured as the distance from the highest ascender to the lowest +// descender; in other words, it's equivalent to calling stbtt_GetFontVMetrics +// and computing: +// scale = pixels / (ascent - descent) +// so if you prefer to measure height by the ascent only, use a similar calculation. + +STBTT_DEF float stbtt_ScaleForMappingEmToPixels(const stbtt_fontinfo *info, float pixels); +// computes a scale factor to produce a font whose EM size is mapped to +// 'pixels' tall. This is probably what traditional APIs compute, but +// I'm not positive. + +STBTT_DEF void stbtt_GetFontVMetrics(const stbtt_fontinfo *info, int *ascent, int *descent, int *lineGap); +// ascent is the coordinate above the baseline the font extends; descent +// is the coordinate below the baseline the font extends (i.e. it is typically negative) +// lineGap is the spacing between one row's descent and the next row's ascent... +// so you should advance the vertical position by "*ascent - *descent + *lineGap" +// these are expressed in unscaled coordinates, so you must multiply by +// the scale factor for a given size + +STBTT_DEF int stbtt_GetFontVMetricsOS2(const stbtt_fontinfo *info, int *typoAscent, int *typoDescent, int *typoLineGap); +// analogous to GetFontVMetrics, but returns the "typographic" values from the OS/2 +// table (specific to MS/Windows TTF files). +// +// Returns 1 on success (table present), 0 on failure. + +STBTT_DEF void stbtt_GetFontBoundingBox(const stbtt_fontinfo *info, int *x0, int *y0, int *x1, int *y1); +// the bounding box around all possible characters + +STBTT_DEF void stbtt_GetCodepointHMetrics(const stbtt_fontinfo *info, int codepoint, int *advanceWidth, int *leftSideBearing); +// leftSideBearing is the offset from the current horizontal position to the left edge of the character +// advanceWidth is the offset from the current horizontal position to the next horizontal position +// these are expressed in unscaled coordinates + +STBTT_DEF int stbtt_GetCodepointKernAdvance(const stbtt_fontinfo *info, int ch1, int ch2); +// an additional amount to add to the 'advance' value between ch1 and ch2 + +STBTT_DEF int stbtt_GetCodepointBox(const stbtt_fontinfo *info, int codepoint, int *x0, int *y0, int *x1, int *y1); +// Gets the bounding box of the visible part of the glyph, in unscaled coordinates + +STBTT_DEF void stbtt_GetGlyphHMetrics(const stbtt_fontinfo *info, int glyph_index, int *advanceWidth, int *leftSideBearing); +STBTT_DEF int stbtt_GetGlyphKernAdvance(const stbtt_fontinfo *info, int glyph1, int glyph2); +STBTT_DEF int stbtt_GetGlyphBox(const stbtt_fontinfo *info, int glyph_index, int *x0, int *y0, int *x1, int *y1); +// as above, but takes one or more glyph indices for greater efficiency + +typedef struct stbtt_kerningentry +{ + int glyph1; // use stbtt_FindGlyphIndex + int glyph2; + int advance; +} stbtt_kerningentry; + +STBTT_DEF int stbtt_GetKerningTableLength(const stbtt_fontinfo *info); +STBTT_DEF int stbtt_GetKerningTable(const stbtt_fontinfo *info, stbtt_kerningentry* table, int table_length); +// Retrieves a complete list of all of the kerning pairs provided by the font +// stbtt_GetKerningTable never writes more than table_length entries and returns how many entries it did write. +// The table will be sorted by (a.glyph1 == b.glyph1)?(a.glyph2 < b.glyph2):(a.glyph1 < b.glyph1) + +////////////////////////////////////////////////////////////////////////////// +// +// GLYPH SHAPES (you probably don't need these, but they have to go before +// the bitmaps for C declaration-order reasons) +// + +#ifndef STBTT_vmove // you can predefine these to use different values (but why?) + enum { + STBTT_vmove=1, + STBTT_vline, + STBTT_vcurve, + STBTT_vcubic + }; +#endif + +#ifndef stbtt_vertex // you can predefine this to use different values + // (we share this with other code at RAD) + #define stbtt_vertex_type short // can't use stbtt_int16 because that's not visible in the header file + typedef struct + { + stbtt_vertex_type x,y,cx,cy,cx1,cy1; + unsigned char type,padding; + } stbtt_vertex; +#endif + +STBTT_DEF int stbtt_IsGlyphEmpty(const stbtt_fontinfo *info, int glyph_index); +// returns non-zero if nothing is drawn for this glyph + +STBTT_DEF int stbtt_GetCodepointShape(const stbtt_fontinfo *info, int unicode_codepoint, stbtt_vertex **vertices); +STBTT_DEF int stbtt_GetGlyphShape(const stbtt_fontinfo *info, int glyph_index, stbtt_vertex **vertices); +// returns # of vertices and fills *vertices with the pointer to them +// these are expressed in "unscaled" coordinates +// +// The shape is a series of contours. Each one starts with +// a STBTT_moveto, then consists of a series of mixed +// STBTT_lineto and STBTT_curveto segments. A lineto +// draws a line from previous endpoint to its x,y; a curveto +// draws a quadratic bezier from previous endpoint to +// its x,y, using cx,cy as the bezier control point. + +STBTT_DEF void stbtt_FreeShape(const stbtt_fontinfo *info, stbtt_vertex *vertices); +// frees the data allocated above + +STBTT_DEF int stbtt_GetCodepointSVG(const stbtt_fontinfo *info, int unicode_codepoint, const char **svg); +STBTT_DEF int stbtt_GetGlyphSVG(const stbtt_fontinfo *info, int gl, const char **svg); +// fills svg with the character's SVG data. +// returns data size or 0 if SVG not found. + +////////////////////////////////////////////////////////////////////////////// +// +// BITMAP RENDERING +// + +STBTT_DEF void stbtt_FreeBitmap(unsigned char *bitmap, void *userdata); +// frees the bitmap allocated below + +STBTT_DEF unsigned char *stbtt_GetCodepointBitmap(const stbtt_fontinfo *info, float scale_x, float scale_y, int codepoint, int *width, int *height, int *xoff, int *yoff); +// allocates a large-enough single-channel 8bpp bitmap and renders the +// specified character/glyph at the specified scale into it, with +// antialiasing. 0 is no coverage (transparent), 255 is fully covered (opaque). +// *width & *height are filled out with the width & height of the bitmap, +// which is stored left-to-right, top-to-bottom. +// +// xoff/yoff are the offset it pixel space from the glyph origin to the top-left of the bitmap + +STBTT_DEF unsigned char *stbtt_GetCodepointBitmapSubpixel(const stbtt_fontinfo *info, float scale_x, float scale_y, float shift_x, float shift_y, int codepoint, int *width, int *height, int *xoff, int *yoff); +// the same as stbtt_GetCodepoitnBitmap, but you can specify a subpixel +// shift for the character + +STBTT_DEF void stbtt_MakeCodepointBitmap(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, int codepoint); +// the same as stbtt_GetCodepointBitmap, but you pass in storage for the bitmap +// in the form of 'output', with row spacing of 'out_stride' bytes. the bitmap +// is clipped to out_w/out_h bytes. Call stbtt_GetCodepointBitmapBox to get the +// width and height and positioning info for it first. + +STBTT_DEF void stbtt_MakeCodepointBitmapSubpixel(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int codepoint); +// same as stbtt_MakeCodepointBitmap, but you can specify a subpixel +// shift for the character + +STBTT_DEF void stbtt_MakeCodepointBitmapSubpixelPrefilter(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int oversample_x, int oversample_y, float *sub_x, float *sub_y, int codepoint); +// same as stbtt_MakeCodepointBitmapSubpixel, but prefiltering +// is performed (see stbtt_PackSetOversampling) + +STBTT_DEF void stbtt_GetCodepointBitmapBox(const stbtt_fontinfo *font, int codepoint, float scale_x, float scale_y, int *ix0, int *iy0, int *ix1, int *iy1); +// get the bbox of the bitmap centered around the glyph origin; so the +// bitmap width is ix1-ix0, height is iy1-iy0, and location to place +// the bitmap top left is (leftSideBearing*scale,iy0). +// (Note that the bitmap uses y-increases-down, but the shape uses +// y-increases-up, so CodepointBitmapBox and CodepointBox are inverted.) + +STBTT_DEF void stbtt_GetCodepointBitmapBoxSubpixel(const stbtt_fontinfo *font, int codepoint, float scale_x, float scale_y, float shift_x, float shift_y, int *ix0, int *iy0, int *ix1, int *iy1); +// same as stbtt_GetCodepointBitmapBox, but you can specify a subpixel +// shift for the character + +// the following functions are equivalent to the above functions, but operate +// on glyph indices instead of Unicode codepoints (for efficiency) +STBTT_DEF unsigned char *stbtt_GetGlyphBitmap(const stbtt_fontinfo *info, float scale_x, float scale_y, int glyph, int *width, int *height, int *xoff, int *yoff); +STBTT_DEF unsigned char *stbtt_GetGlyphBitmapSubpixel(const stbtt_fontinfo *info, float scale_x, float scale_y, float shift_x, float shift_y, int glyph, int *width, int *height, int *xoff, int *yoff); +STBTT_DEF void stbtt_MakeGlyphBitmap(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, int glyph); +STBTT_DEF void stbtt_MakeGlyphBitmapSubpixel(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int glyph); +STBTT_DEF void stbtt_MakeGlyphBitmapSubpixelPrefilter(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int oversample_x, int oversample_y, float *sub_x, float *sub_y, int glyph); +STBTT_DEF void stbtt_GetGlyphBitmapBox(const stbtt_fontinfo *font, int glyph, float scale_x, float scale_y, int *ix0, int *iy0, int *ix1, int *iy1); +STBTT_DEF void stbtt_GetGlyphBitmapBoxSubpixel(const stbtt_fontinfo *font, int glyph, float scale_x, float scale_y,float shift_x, float shift_y, int *ix0, int *iy0, int *ix1, int *iy1); + + +// @TODO: don't expose this structure +typedef struct +{ + int w,h,stride; + unsigned char *pixels; +} stbtt__bitmap; + +// rasterize a shape with quadratic beziers into a bitmap +STBTT_DEF void stbtt_Rasterize(stbtt__bitmap *result, // 1-channel bitmap to draw into + float flatness_in_pixels, // allowable error of curve in pixels + stbtt_vertex *vertices, // array of vertices defining shape + int num_verts, // number of vertices in above array + float scale_x, float scale_y, // scale applied to input vertices + float shift_x, float shift_y, // translation applied to input vertices + int x_off, int y_off, // another translation applied to input + int invert, // if non-zero, vertically flip shape + void *userdata); // context for to STBTT_MALLOC + +////////////////////////////////////////////////////////////////////////////// +// +// Signed Distance Function (or Field) rendering + +STBTT_DEF void stbtt_FreeSDF(unsigned char *bitmap, void *userdata); +// frees the SDF bitmap allocated below + +STBTT_DEF unsigned char * stbtt_GetGlyphSDF(const stbtt_fontinfo *info, float scale, int glyph, int padding, unsigned char onedge_value, float pixel_dist_scale, int *width, int *height, int *xoff, int *yoff); +STBTT_DEF unsigned char * stbtt_GetCodepointSDF(const stbtt_fontinfo *info, float scale, int codepoint, int padding, unsigned char onedge_value, float pixel_dist_scale, int *width, int *height, int *xoff, int *yoff); +// These functions compute a discretized SDF field for a single character, suitable for storing +// in a single-channel texture, sampling with bilinear filtering, and testing against +// larger than some threshold to produce scalable fonts. +// info -- the font +// scale -- controls the size of the resulting SDF bitmap, same as it would be creating a regular bitmap +// glyph/codepoint -- the character to generate the SDF for +// padding -- extra "pixels" around the character which are filled with the distance to the character (not 0), +// which allows effects like bit outlines +// onedge_value -- value 0-255 to test the SDF against to reconstruct the character (i.e. the isocontour of the character) +// pixel_dist_scale -- what value the SDF should increase by when moving one SDF "pixel" away from the edge (on the 0..255 scale) +// if positive, > onedge_value is inside; if negative, < onedge_value is inside +// width,height -- output height & width of the SDF bitmap (including padding) +// xoff,yoff -- output origin of the character +// return value -- a 2D array of bytes 0..255, width*height in size +// +// pixel_dist_scale & onedge_value are a scale & bias that allows you to make +// optimal use of the limited 0..255 for your application, trading off precision +// and special effects. SDF values outside the range 0..255 are clamped to 0..255. +// +// Example: +// scale = stbtt_ScaleForPixelHeight(22) +// padding = 5 +// onedge_value = 180 +// pixel_dist_scale = 180/5.0 = 36.0 +// +// This will create an SDF bitmap in which the character is about 22 pixels +// high but the whole bitmap is about 22+5+5=32 pixels high. To produce a filled +// shape, sample the SDF at each pixel and fill the pixel if the SDF value +// is greater than or equal to 180/255. (You'll actually want to antialias, +// which is beyond the scope of this example.) Additionally, you can compute +// offset outlines (e.g. to stroke the character border inside & outside, +// or only outside). For example, to fill outside the character up to 3 SDF +// pixels, you would compare against (180-36.0*3)/255 = 72/255. The above +// choice of variables maps a range from 5 pixels outside the shape to +// 2 pixels inside the shape to 0..255; this is intended primarily for apply +// outside effects only (the interior range is needed to allow proper +// antialiasing of the font at *smaller* sizes) +// +// The function computes the SDF analytically at each SDF pixel, not by e.g. +// building a higher-res bitmap and approximating it. In theory the quality +// should be as high as possible for an SDF of this size & representation, but +// unclear if this is true in practice (perhaps building a higher-res bitmap +// and computing from that can allow drop-out prevention). +// +// The algorithm has not been optimized at all, so expect it to be slow +// if computing lots of characters or very large sizes. + + + +////////////////////////////////////////////////////////////////////////////// +// +// Finding the right font... +// +// You should really just solve this offline, keep your own tables +// of what font is what, and don't try to get it out of the .ttf file. +// That's because getting it out of the .ttf file is really hard, because +// the names in the file can appear in many possible encodings, in many +// possible languages, and e.g. if you need a case-insensitive comparison, +// the details of that depend on the encoding & language in a complex way +// (actually underspecified in truetype, but also gigantic). +// +// But you can use the provided functions in two possible ways: +// stbtt_FindMatchingFont() will use *case-sensitive* comparisons on +// unicode-encoded names to try to find the font you want; +// you can run this before calling stbtt_InitFont() +// +// stbtt_GetFontNameString() lets you get any of the various strings +// from the file yourself and do your own comparisons on them. +// You have to have called stbtt_InitFont() first. + + +STBTT_DEF int stbtt_FindMatchingFont(const unsigned char *fontdata, const char *name, int flags); +// returns the offset (not index) of the font that matches, or -1 if none +// if you use STBTT_MACSTYLE_DONTCARE, use a font name like "Arial Bold". +// if you use any other flag, use a font name like "Arial"; this checks +// the 'macStyle' header field; i don't know if fonts set this consistently +#define STBTT_MACSTYLE_DONTCARE 0 +#define STBTT_MACSTYLE_BOLD 1 +#define STBTT_MACSTYLE_ITALIC 2 +#define STBTT_MACSTYLE_UNDERSCORE 4 +#define STBTT_MACSTYLE_NONE 8 // <= not same as 0, this makes us check the bitfield is 0 + +STBTT_DEF int stbtt_CompareUTF8toUTF16_bigendian(const char *s1, int len1, const char *s2, int len2); +// returns 1/0 whether the first string interpreted as utf8 is identical to +// the second string interpreted as big-endian utf16... useful for strings from next func + +STBTT_DEF const char *stbtt_GetFontNameString(const stbtt_fontinfo *font, int *length, int platformID, int encodingID, int languageID, int nameID); +// returns the string (which may be big-endian double byte, e.g. for unicode) +// and puts the length in bytes in *length. +// +// some of the values for the IDs are below; for more see the truetype spec: +// http://developer.apple.com/textfonts/TTRefMan/RM06/Chap6name.html +// http://www.microsoft.com/typography/otspec/name.htm + +enum { // platformID + STBTT_PLATFORM_ID_UNICODE =0, + STBTT_PLATFORM_ID_MAC =1, + STBTT_PLATFORM_ID_ISO =2, + STBTT_PLATFORM_ID_MICROSOFT =3 +}; + +enum { // encodingID for STBTT_PLATFORM_ID_UNICODE + STBTT_UNICODE_EID_UNICODE_1_0 =0, + STBTT_UNICODE_EID_UNICODE_1_1 =1, + STBTT_UNICODE_EID_ISO_10646 =2, + STBTT_UNICODE_EID_UNICODE_2_0_BMP=3, + STBTT_UNICODE_EID_UNICODE_2_0_FULL=4 +}; + +enum { // encodingID for STBTT_PLATFORM_ID_MICROSOFT + STBTT_MS_EID_SYMBOL =0, + STBTT_MS_EID_UNICODE_BMP =1, + STBTT_MS_EID_SHIFTJIS =2, + STBTT_MS_EID_UNICODE_FULL =10 +}; + +enum { // encodingID for STBTT_PLATFORM_ID_MAC; same as Script Manager codes + STBTT_MAC_EID_ROMAN =0, STBTT_MAC_EID_ARABIC =4, + STBTT_MAC_EID_JAPANESE =1, STBTT_MAC_EID_HEBREW =5, + STBTT_MAC_EID_CHINESE_TRAD =2, STBTT_MAC_EID_GREEK =6, + STBTT_MAC_EID_KOREAN =3, STBTT_MAC_EID_RUSSIAN =7 +}; + +enum { // languageID for STBTT_PLATFORM_ID_MICROSOFT; same as LCID... + // problematic because there are e.g. 16 english LCIDs and 16 arabic LCIDs + STBTT_MS_LANG_ENGLISH =0x0409, STBTT_MS_LANG_ITALIAN =0x0410, + STBTT_MS_LANG_CHINESE =0x0804, STBTT_MS_LANG_JAPANESE =0x0411, + STBTT_MS_LANG_DUTCH =0x0413, STBTT_MS_LANG_KOREAN =0x0412, + STBTT_MS_LANG_FRENCH =0x040c, STBTT_MS_LANG_RUSSIAN =0x0419, + STBTT_MS_LANG_GERMAN =0x0407, STBTT_MS_LANG_SPANISH =0x0409, + STBTT_MS_LANG_HEBREW =0x040d, STBTT_MS_LANG_SWEDISH =0x041D +}; + +enum { // languageID for STBTT_PLATFORM_ID_MAC + STBTT_MAC_LANG_ENGLISH =0 , STBTT_MAC_LANG_JAPANESE =11, + STBTT_MAC_LANG_ARABIC =12, STBTT_MAC_LANG_KOREAN =23, + STBTT_MAC_LANG_DUTCH =4 , STBTT_MAC_LANG_RUSSIAN =32, + STBTT_MAC_LANG_FRENCH =1 , STBTT_MAC_LANG_SPANISH =6 , + STBTT_MAC_LANG_GERMAN =2 , STBTT_MAC_LANG_SWEDISH =5 , + STBTT_MAC_LANG_HEBREW =10, STBTT_MAC_LANG_CHINESE_SIMPLIFIED =33, + STBTT_MAC_LANG_ITALIAN =3 , STBTT_MAC_LANG_CHINESE_TRAD =19 +}; + +#ifdef __cplusplus +} +#endif + +#endif // __STB_INCLUDE_STB_TRUETYPE_H__ + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +//// +//// IMPLEMENTATION +//// +//// + +#ifdef STB_TRUETYPE_IMPLEMENTATION + +#ifndef STBTT_MAX_OVERSAMPLE +#define STBTT_MAX_OVERSAMPLE 8 +#endif + +#if STBTT_MAX_OVERSAMPLE > 255 +#error "STBTT_MAX_OVERSAMPLE cannot be > 255" +#endif + +typedef int stbtt__test_oversample_pow2[(STBTT_MAX_OVERSAMPLE & (STBTT_MAX_OVERSAMPLE-1)) == 0 ? 1 : -1]; + +#ifndef STBTT_RASTERIZER_VERSION +#define STBTT_RASTERIZER_VERSION 2 +#endif + +#ifdef _MSC_VER +#define STBTT__NOTUSED(v) (void)(v) +#else +#define STBTT__NOTUSED(v) (void)sizeof(v) +#endif + +////////////////////////////////////////////////////////////////////////// +// +// stbtt__buf helpers to parse data from file +// + +static stbtt_uint8 stbtt__buf_get8(stbtt__buf *b) +{ + if (b->cursor >= b->size) + return 0; + return b->data[b->cursor++]; +} + +static stbtt_uint8 stbtt__buf_peek8(stbtt__buf *b) +{ + if (b->cursor >= b->size) + return 0; + return b->data[b->cursor]; +} + +static void stbtt__buf_seek(stbtt__buf *b, int o) +{ + STBTT_assert(!(o > b->size || o < 0)); + b->cursor = (o > b->size || o < 0) ? b->size : o; +} + +static void stbtt__buf_skip(stbtt__buf *b, int o) +{ + stbtt__buf_seek(b, b->cursor + o); +} + +static stbtt_uint32 stbtt__buf_get(stbtt__buf *b, int n) +{ + stbtt_uint32 v = 0; + int i; + STBTT_assert(n >= 1 && n <= 4); + for (i = 0; i < n; i++) + v = (v << 8) | stbtt__buf_get8(b); + return v; +} + +static stbtt__buf stbtt__new_buf(const void *p, size_t size) +{ + stbtt__buf r; + STBTT_assert(size < 0x40000000); + r.data = (stbtt_uint8*) p; + r.size = (int) size; + r.cursor = 0; + return r; +} + +#define stbtt__buf_get16(b) stbtt__buf_get((b), 2) +#define stbtt__buf_get32(b) stbtt__buf_get((b), 4) + +static stbtt__buf stbtt__buf_range(const stbtt__buf *b, int o, int s) +{ + stbtt__buf r = stbtt__new_buf(NULL, 0); + if (o < 0 || s < 0 || o > b->size || s > b->size - o) return r; + r.data = b->data + o; + r.size = s; + return r; +} + +static stbtt__buf stbtt__cff_get_index(stbtt__buf *b) +{ + int count, start, offsize; + start = b->cursor; + count = stbtt__buf_get16(b); + if (count) { + offsize = stbtt__buf_get8(b); + STBTT_assert(offsize >= 1 && offsize <= 4); + stbtt__buf_skip(b, offsize * count); + stbtt__buf_skip(b, stbtt__buf_get(b, offsize) - 1); + } + return stbtt__buf_range(b, start, b->cursor - start); +} + +static stbtt_uint32 stbtt__cff_int(stbtt__buf *b) +{ + int b0 = stbtt__buf_get8(b); + if (b0 >= 32 && b0 <= 246) return b0 - 139; + else if (b0 >= 247 && b0 <= 250) return (b0 - 247)*256 + stbtt__buf_get8(b) + 108; + else if (b0 >= 251 && b0 <= 254) return -(b0 - 251)*256 - stbtt__buf_get8(b) - 108; + else if (b0 == 28) return stbtt__buf_get16(b); + else if (b0 == 29) return stbtt__buf_get32(b); + STBTT_assert(0); + return 0; +} + +static void stbtt__cff_skip_operand(stbtt__buf *b) { + int v, b0 = stbtt__buf_peek8(b); + STBTT_assert(b0 >= 28); + if (b0 == 30) { + stbtt__buf_skip(b, 1); + while (b->cursor < b->size) { + v = stbtt__buf_get8(b); + if ((v & 0xF) == 0xF || (v >> 4) == 0xF) + break; + } + } else { + stbtt__cff_int(b); + } +} + +static stbtt__buf stbtt__dict_get(stbtt__buf *b, int key) +{ + stbtt__buf_seek(b, 0); + while (b->cursor < b->size) { + int start = b->cursor, end, op; + while (stbtt__buf_peek8(b) >= 28) + stbtt__cff_skip_operand(b); + end = b->cursor; + op = stbtt__buf_get8(b); + if (op == 12) op = stbtt__buf_get8(b) | 0x100; + if (op == key) return stbtt__buf_range(b, start, end-start); + } + return stbtt__buf_range(b, 0, 0); +} + +static void stbtt__dict_get_ints(stbtt__buf *b, int key, int outcount, stbtt_uint32 *out) +{ + int i; + stbtt__buf operands = stbtt__dict_get(b, key); + for (i = 0; i < outcount && operands.cursor < operands.size; i++) + out[i] = stbtt__cff_int(&operands); +} + +static int stbtt__cff_index_count(stbtt__buf *b) +{ + stbtt__buf_seek(b, 0); + return stbtt__buf_get16(b); +} + +static stbtt__buf stbtt__cff_index_get(stbtt__buf b, int i) +{ + int count, offsize, start, end; + stbtt__buf_seek(&b, 0); + count = stbtt__buf_get16(&b); + offsize = stbtt__buf_get8(&b); + STBTT_assert(i >= 0 && i < count); + STBTT_assert(offsize >= 1 && offsize <= 4); + stbtt__buf_skip(&b, i*offsize); + start = stbtt__buf_get(&b, offsize); + end = stbtt__buf_get(&b, offsize); + return stbtt__buf_range(&b, 2+(count+1)*offsize+start, end - start); +} + +////////////////////////////////////////////////////////////////////////// +// +// accessors to parse data from file +// + +// on platforms that don't allow misaligned reads, if we want to allow +// truetype fonts that aren't padded to alignment, define ALLOW_UNALIGNED_TRUETYPE + +#define ttBYTE(p) (* (stbtt_uint8 *) (p)) +#define ttCHAR(p) (* (stbtt_int8 *) (p)) +#define ttFixed(p) ttLONG(p) + +static stbtt_uint16 ttUSHORT(stbtt_uint8 *p) { return p[0]*256 + p[1]; } +static stbtt_int16 ttSHORT(stbtt_uint8 *p) { return p[0]*256 + p[1]; } +static stbtt_uint32 ttULONG(stbtt_uint8 *p) { return (p[0]<<24) + (p[1]<<16) + (p[2]<<8) + p[3]; } +static stbtt_int32 ttLONG(stbtt_uint8 *p) { return (p[0]<<24) + (p[1]<<16) + (p[2]<<8) + p[3]; } + +#define stbtt_tag4(p,c0,c1,c2,c3) ((p)[0] == (c0) && (p)[1] == (c1) && (p)[2] == (c2) && (p)[3] == (c3)) +#define stbtt_tag(p,str) stbtt_tag4(p,str[0],str[1],str[2],str[3]) + +static int stbtt__isfont(stbtt_uint8 *font) +{ + // check the version number + if (stbtt_tag4(font, '1',0,0,0)) return 1; // TrueType 1 + if (stbtt_tag(font, "typ1")) return 1; // TrueType with type 1 font -- we don't support this! + if (stbtt_tag(font, "OTTO")) return 1; // OpenType with CFF + if (stbtt_tag4(font, 0,1,0,0)) return 1; // OpenType 1.0 + if (stbtt_tag(font, "true")) return 1; // Apple specification for TrueType fonts + return 0; +} + +// @OPTIMIZE: binary search +static stbtt_uint32 stbtt__find_table(stbtt_uint8 *data, stbtt_uint32 fontstart, const char *tag) +{ + stbtt_int32 num_tables = ttUSHORT(data+fontstart+4); + stbtt_uint32 tabledir = fontstart + 12; + stbtt_int32 i; + for (i=0; i < num_tables; ++i) { + stbtt_uint32 loc = tabledir + 16*i; + if (stbtt_tag(data+loc+0, tag)) + return ttULONG(data+loc+8); + } + return 0; +} + +static int stbtt_GetFontOffsetForIndex_internal(unsigned char *font_collection, int index) +{ + // if it's just a font, there's only one valid index + if (stbtt__isfont(font_collection)) + return index == 0 ? 0 : -1; + + // check if it's a TTC + if (stbtt_tag(font_collection, "ttcf")) { + // version 1? + if (ttULONG(font_collection+4) == 0x00010000 || ttULONG(font_collection+4) == 0x00020000) { + stbtt_int32 n = ttLONG(font_collection+8); + if (index >= n) + return -1; + return ttULONG(font_collection+12+index*4); + } + } + return -1; +} + +static int stbtt_GetNumberOfFonts_internal(unsigned char *font_collection) +{ + // if it's just a font, there's only one valid font + if (stbtt__isfont(font_collection)) + return 1; + + // check if it's a TTC + if (stbtt_tag(font_collection, "ttcf")) { + // version 1? + if (ttULONG(font_collection+4) == 0x00010000 || ttULONG(font_collection+4) == 0x00020000) { + return ttLONG(font_collection+8); + } + } + return 0; +} + +static stbtt__buf stbtt__get_subrs(stbtt__buf cff, stbtt__buf fontdict) +{ + stbtt_uint32 subrsoff = 0, private_loc[2] = { 0, 0 }; + stbtt__buf pdict; + stbtt__dict_get_ints(&fontdict, 18, 2, private_loc); + if (!private_loc[1] || !private_loc[0]) return stbtt__new_buf(NULL, 0); + pdict = stbtt__buf_range(&cff, private_loc[1], private_loc[0]); + stbtt__dict_get_ints(&pdict, 19, 1, &subrsoff); + if (!subrsoff) return stbtt__new_buf(NULL, 0); + stbtt__buf_seek(&cff, private_loc[1]+subrsoff); + return stbtt__cff_get_index(&cff); +} + +// since most people won't use this, find this table the first time it's needed +static int stbtt__get_svg(stbtt_fontinfo *info) +{ + stbtt_uint32 t; + if (info->svg < 0) { + t = stbtt__find_table(info->data, info->fontstart, "SVG "); + if (t) { + stbtt_uint32 offset = ttULONG(info->data + t + 2); + info->svg = t + offset; + } else { + info->svg = 0; + } + } + return info->svg; +} + +static int stbtt_InitFont_internal(stbtt_fontinfo *info, unsigned char *data, int fontstart) +{ + stbtt_uint32 cmap, t; + stbtt_int32 i,numTables; + + info->data = data; + info->fontstart = fontstart; + info->cff = stbtt__new_buf(NULL, 0); + + cmap = stbtt__find_table(data, fontstart, "cmap"); // required + info->loca = stbtt__find_table(data, fontstart, "loca"); // required + info->head = stbtt__find_table(data, fontstart, "head"); // required + info->glyf = stbtt__find_table(data, fontstart, "glyf"); // required + info->hhea = stbtt__find_table(data, fontstart, "hhea"); // required + info->hmtx = stbtt__find_table(data, fontstart, "hmtx"); // required + info->kern = stbtt__find_table(data, fontstart, "kern"); // not required + info->gpos = stbtt__find_table(data, fontstart, "GPOS"); // not required + + if (!cmap || !info->head || !info->hhea || !info->hmtx) + return 0; + if (info->glyf) { + // required for truetype + if (!info->loca) return 0; + } else { + // initialization for CFF / Type2 fonts (OTF) + stbtt__buf b, topdict, topdictidx; + stbtt_uint32 cstype = 2, charstrings = 0, fdarrayoff = 0, fdselectoff = 0; + stbtt_uint32 cff; + + cff = stbtt__find_table(data, fontstart, "CFF "); + if (!cff) return 0; + + info->fontdicts = stbtt__new_buf(NULL, 0); + info->fdselect = stbtt__new_buf(NULL, 0); + + // @TODO this should use size from table (not 512MB) + info->cff = stbtt__new_buf(data+cff, 512*1024*1024); + b = info->cff; + + // read the header + stbtt__buf_skip(&b, 2); + stbtt__buf_seek(&b, stbtt__buf_get8(&b)); // hdrsize + + // @TODO the name INDEX could list multiple fonts, + // but we just use the first one. + stbtt__cff_get_index(&b); // name INDEX + topdictidx = stbtt__cff_get_index(&b); + topdict = stbtt__cff_index_get(topdictidx, 0); + stbtt__cff_get_index(&b); // string INDEX + info->gsubrs = stbtt__cff_get_index(&b); + + stbtt__dict_get_ints(&topdict, 17, 1, &charstrings); + stbtt__dict_get_ints(&topdict, 0x100 | 6, 1, &cstype); + stbtt__dict_get_ints(&topdict, 0x100 | 36, 1, &fdarrayoff); + stbtt__dict_get_ints(&topdict, 0x100 | 37, 1, &fdselectoff); + info->subrs = stbtt__get_subrs(b, topdict); + + // we only support Type 2 charstrings + if (cstype != 2) return 0; + if (charstrings == 0) return 0; + + if (fdarrayoff) { + // looks like a CID font + if (!fdselectoff) return 0; + stbtt__buf_seek(&b, fdarrayoff); + info->fontdicts = stbtt__cff_get_index(&b); + info->fdselect = stbtt__buf_range(&b, fdselectoff, b.size-fdselectoff); + } + + stbtt__buf_seek(&b, charstrings); + info->charstrings = stbtt__cff_get_index(&b); + } + + t = stbtt__find_table(data, fontstart, "maxp"); + if (t) + info->numGlyphs = ttUSHORT(data+t+4); + else + info->numGlyphs = 0xffff; + + info->svg = -1; + + // find a cmap encoding table we understand *now* to avoid searching + // later. (todo: could make this installable) + // the same regardless of glyph. + numTables = ttUSHORT(data + cmap + 2); + info->index_map = 0; + for (i=0; i < numTables; ++i) { + stbtt_uint32 encoding_record = cmap + 4 + 8 * i; + // find an encoding we understand: + switch(ttUSHORT(data+encoding_record)) { + case STBTT_PLATFORM_ID_MICROSOFT: + switch (ttUSHORT(data+encoding_record+2)) { + case STBTT_MS_EID_UNICODE_BMP: + case STBTT_MS_EID_UNICODE_FULL: + // MS/Unicode + info->index_map = cmap + ttULONG(data+encoding_record+4); + break; + } + break; + case STBTT_PLATFORM_ID_UNICODE: + // Mac/iOS has these + // all the encodingIDs are unicode, so we don't bother to check it + info->index_map = cmap + ttULONG(data+encoding_record+4); + break; + } + } + if (info->index_map == 0) + return 0; + + info->indexToLocFormat = ttUSHORT(data+info->head + 50); + return 1; +} + +STBTT_DEF int stbtt_FindGlyphIndex(const stbtt_fontinfo *info, int unicode_codepoint) +{ + stbtt_uint8 *data = info->data; + stbtt_uint32 index_map = info->index_map; + + stbtt_uint16 format = ttUSHORT(data + index_map + 0); + if (format == 0) { // apple byte encoding + stbtt_int32 bytes = ttUSHORT(data + index_map + 2); + if (unicode_codepoint < bytes-6) + return ttBYTE(data + index_map + 6 + unicode_codepoint); + return 0; + } else if (format == 6) { + stbtt_uint32 first = ttUSHORT(data + index_map + 6); + stbtt_uint32 count = ttUSHORT(data + index_map + 8); + if ((stbtt_uint32) unicode_codepoint >= first && (stbtt_uint32) unicode_codepoint < first+count) + return ttUSHORT(data + index_map + 10 + (unicode_codepoint - first)*2); + return 0; + } else if (format == 2) { + STBTT_assert(0); // @TODO: high-byte mapping for japanese/chinese/korean + return 0; + } else if (format == 4) { // standard mapping for windows fonts: binary search collection of ranges + stbtt_uint16 segcount = ttUSHORT(data+index_map+6) >> 1; + stbtt_uint16 searchRange = ttUSHORT(data+index_map+8) >> 1; + stbtt_uint16 entrySelector = ttUSHORT(data+index_map+10); + stbtt_uint16 rangeShift = ttUSHORT(data+index_map+12) >> 1; + + // do a binary search of the segments + stbtt_uint32 endCount = index_map + 14; + stbtt_uint32 search = endCount; + + if (unicode_codepoint > 0xffff) + return 0; + + // they lie from endCount .. endCount + segCount + // but searchRange is the nearest power of two, so... + if (unicode_codepoint >= ttUSHORT(data + search + rangeShift*2)) + search += rangeShift*2; + + // now decrement to bias correctly to find smallest + search -= 2; + while (entrySelector) { + stbtt_uint16 end; + searchRange >>= 1; + end = ttUSHORT(data + search + searchRange*2); + if (unicode_codepoint > end) + search += searchRange*2; + --entrySelector; + } + search += 2; + + { + stbtt_uint16 offset, start; + stbtt_uint16 item = (stbtt_uint16) ((search - endCount) >> 1); + + STBTT_assert(unicode_codepoint <= ttUSHORT(data + endCount + 2*item)); + start = ttUSHORT(data + index_map + 14 + segcount*2 + 2 + 2*item); + if (unicode_codepoint < start) + return 0; + + offset = ttUSHORT(data + index_map + 14 + segcount*6 + 2 + 2*item); + if (offset == 0) + return (stbtt_uint16) (unicode_codepoint + ttSHORT(data + index_map + 14 + segcount*4 + 2 + 2*item)); + + return ttUSHORT(data + offset + (unicode_codepoint-start)*2 + index_map + 14 + segcount*6 + 2 + 2*item); + } + } else if (format == 12 || format == 13) { + stbtt_uint32 ngroups = ttULONG(data+index_map+12); + stbtt_int32 low,high; + low = 0; high = (stbtt_int32)ngroups; + // Binary search the right group. + while (low < high) { + stbtt_int32 mid = low + ((high-low) >> 1); // rounds down, so low <= mid < high + stbtt_uint32 start_char = ttULONG(data+index_map+16+mid*12); + stbtt_uint32 end_char = ttULONG(data+index_map+16+mid*12+4); + if ((stbtt_uint32) unicode_codepoint < start_char) + high = mid; + else if ((stbtt_uint32) unicode_codepoint > end_char) + low = mid+1; + else { + stbtt_uint32 start_glyph = ttULONG(data+index_map+16+mid*12+8); + if (format == 12) + return start_glyph + unicode_codepoint-start_char; + else // format == 13 + return start_glyph; + } + } + return 0; // not found + } + // @TODO + STBTT_assert(0); + return 0; +} + +STBTT_DEF int stbtt_GetCodepointShape(const stbtt_fontinfo *info, int unicode_codepoint, stbtt_vertex **vertices) +{ + return stbtt_GetGlyphShape(info, stbtt_FindGlyphIndex(info, unicode_codepoint), vertices); +} + +static void stbtt_setvertex(stbtt_vertex *v, stbtt_uint8 type, stbtt_int32 x, stbtt_int32 y, stbtt_int32 cx, stbtt_int32 cy) +{ + v->type = type; + v->x = (stbtt_int16) x; + v->y = (stbtt_int16) y; + v->cx = (stbtt_int16) cx; + v->cy = (stbtt_int16) cy; +} + +static int stbtt__GetGlyfOffset(const stbtt_fontinfo *info, int glyph_index) +{ + int g1,g2; + + STBTT_assert(!info->cff.size); + + if (glyph_index >= info->numGlyphs) return -1; // glyph index out of range + if (info->indexToLocFormat >= 2) return -1; // unknown index->glyph map format + + if (info->indexToLocFormat == 0) { + g1 = info->glyf + ttUSHORT(info->data + info->loca + glyph_index * 2) * 2; + g2 = info->glyf + ttUSHORT(info->data + info->loca + glyph_index * 2 + 2) * 2; + } else { + g1 = info->glyf + ttULONG (info->data + info->loca + glyph_index * 4); + g2 = info->glyf + ttULONG (info->data + info->loca + glyph_index * 4 + 4); + } + + return g1==g2 ? -1 : g1; // if length is 0, return -1 +} + +static int stbtt__GetGlyphInfoT2(const stbtt_fontinfo *info, int glyph_index, int *x0, int *y0, int *x1, int *y1); + +STBTT_DEF int stbtt_GetGlyphBox(const stbtt_fontinfo *info, int glyph_index, int *x0, int *y0, int *x1, int *y1) +{ + if (info->cff.size) { + stbtt__GetGlyphInfoT2(info, glyph_index, x0, y0, x1, y1); + } else { + int g = stbtt__GetGlyfOffset(info, glyph_index); + if (g < 0) return 0; + + if (x0) *x0 = ttSHORT(info->data + g + 2); + if (y0) *y0 = ttSHORT(info->data + g + 4); + if (x1) *x1 = ttSHORT(info->data + g + 6); + if (y1) *y1 = ttSHORT(info->data + g + 8); + } + return 1; +} + +STBTT_DEF int stbtt_GetCodepointBox(const stbtt_fontinfo *info, int codepoint, int *x0, int *y0, int *x1, int *y1) +{ + return stbtt_GetGlyphBox(info, stbtt_FindGlyphIndex(info,codepoint), x0,y0,x1,y1); +} + +STBTT_DEF int stbtt_IsGlyphEmpty(const stbtt_fontinfo *info, int glyph_index) +{ + stbtt_int16 numberOfContours; + int g; + if (info->cff.size) + return stbtt__GetGlyphInfoT2(info, glyph_index, NULL, NULL, NULL, NULL) == 0; + g = stbtt__GetGlyfOffset(info, glyph_index); + if (g < 0) return 1; + numberOfContours = ttSHORT(info->data + g); + return numberOfContours == 0; +} + +static int stbtt__close_shape(stbtt_vertex *vertices, int num_vertices, int was_off, int start_off, + stbtt_int32 sx, stbtt_int32 sy, stbtt_int32 scx, stbtt_int32 scy, stbtt_int32 cx, stbtt_int32 cy) +{ + if (start_off) { + if (was_off) + stbtt_setvertex(&vertices[num_vertices++], STBTT_vcurve, (cx+scx)>>1, (cy+scy)>>1, cx,cy); + stbtt_setvertex(&vertices[num_vertices++], STBTT_vcurve, sx,sy,scx,scy); + } else { + if (was_off) + stbtt_setvertex(&vertices[num_vertices++], STBTT_vcurve,sx,sy,cx,cy); + else + stbtt_setvertex(&vertices[num_vertices++], STBTT_vline,sx,sy,0,0); + } + return num_vertices; +} + +static int stbtt__GetGlyphShapeTT(const stbtt_fontinfo *info, int glyph_index, stbtt_vertex **pvertices) +{ + stbtt_int16 numberOfContours; + stbtt_uint8 *endPtsOfContours; + stbtt_uint8 *data = info->data; + stbtt_vertex *vertices=0; + int num_vertices=0; + int g = stbtt__GetGlyfOffset(info, glyph_index); + + *pvertices = NULL; + + if (g < 0) return 0; + + numberOfContours = ttSHORT(data + g); + + if (numberOfContours > 0) { + stbtt_uint8 flags=0,flagcount; + stbtt_int32 ins, i,j=0,m,n, next_move, was_off=0, off, start_off=0; + stbtt_int32 x,y,cx,cy,sx,sy, scx,scy; + stbtt_uint8 *points; + endPtsOfContours = (data + g + 10); + ins = ttUSHORT(data + g + 10 + numberOfContours * 2); + points = data + g + 10 + numberOfContours * 2 + 2 + ins; + + n = 1+ttUSHORT(endPtsOfContours + numberOfContours*2-2); + + m = n + 2*numberOfContours; // a loose bound on how many vertices we might need + vertices = (stbtt_vertex *) STBTT_malloc(m * sizeof(vertices[0]), info->userdata); + if (vertices == 0) + return 0; + + next_move = 0; + flagcount=0; + + // in first pass, we load uninterpreted data into the allocated array + // above, shifted to the end of the array so we won't overwrite it when + // we create our final data starting from the front + + off = m - n; // starting offset for uninterpreted data, regardless of how m ends up being calculated + + // first load flags + + for (i=0; i < n; ++i) { + if (flagcount == 0) { + flags = *points++; + if (flags & 8) + flagcount = *points++; + } else + --flagcount; + vertices[off+i].type = flags; + } + + // now load x coordinates + x=0; + for (i=0; i < n; ++i) { + flags = vertices[off+i].type; + if (flags & 2) { + stbtt_int16 dx = *points++; + x += (flags & 16) ? dx : -dx; // ??? + } else { + if (!(flags & 16)) { + x = x + (stbtt_int16) (points[0]*256 + points[1]); + points += 2; + } + } + vertices[off+i].x = (stbtt_int16) x; + } + + // now load y coordinates + y=0; + for (i=0; i < n; ++i) { + flags = vertices[off+i].type; + if (flags & 4) { + stbtt_int16 dy = *points++; + y += (flags & 32) ? dy : -dy; // ??? + } else { + if (!(flags & 32)) { + y = y + (stbtt_int16) (points[0]*256 + points[1]); + points += 2; + } + } + vertices[off+i].y = (stbtt_int16) y; + } + + // now convert them to our format + num_vertices=0; + sx = sy = cx = cy = scx = scy = 0; + for (i=0; i < n; ++i) { + flags = vertices[off+i].type; + x = (stbtt_int16) vertices[off+i].x; + y = (stbtt_int16) vertices[off+i].y; + + if (next_move == i) { + if (i != 0) + num_vertices = stbtt__close_shape(vertices, num_vertices, was_off, start_off, sx,sy,scx,scy,cx,cy); + + // now start the new one + start_off = !(flags & 1); + if (start_off) { + // if we start off with an off-curve point, then when we need to find a point on the curve + // where we can start, and we need to save some state for when we wraparound. + scx = x; + scy = y; + if (!(vertices[off+i+1].type & 1)) { + // next point is also a curve point, so interpolate an on-point curve + sx = (x + (stbtt_int32) vertices[off+i+1].x) >> 1; + sy = (y + (stbtt_int32) vertices[off+i+1].y) >> 1; + } else { + // otherwise just use the next point as our start point + sx = (stbtt_int32) vertices[off+i+1].x; + sy = (stbtt_int32) vertices[off+i+1].y; + ++i; // we're using point i+1 as the starting point, so skip it + } + } else { + sx = x; + sy = y; + } + stbtt_setvertex(&vertices[num_vertices++], STBTT_vmove,sx,sy,0,0); + was_off = 0; + next_move = 1 + ttUSHORT(endPtsOfContours+j*2); + ++j; + } else { + if (!(flags & 1)) { // if it's a curve + if (was_off) // two off-curve control points in a row means interpolate an on-curve midpoint + stbtt_setvertex(&vertices[num_vertices++], STBTT_vcurve, (cx+x)>>1, (cy+y)>>1, cx, cy); + cx = x; + cy = y; + was_off = 1; + } else { + if (was_off) + stbtt_setvertex(&vertices[num_vertices++], STBTT_vcurve, x,y, cx, cy); + else + stbtt_setvertex(&vertices[num_vertices++], STBTT_vline, x,y,0,0); + was_off = 0; + } + } + } + num_vertices = stbtt__close_shape(vertices, num_vertices, was_off, start_off, sx,sy,scx,scy,cx,cy); + } else if (numberOfContours < 0) { + // Compound shapes. + int more = 1; + stbtt_uint8 *comp = data + g + 10; + num_vertices = 0; + vertices = 0; + while (more) { + stbtt_uint16 flags, gidx; + int comp_num_verts = 0, i; + stbtt_vertex *comp_verts = 0, *tmp = 0; + float mtx[6] = {1,0,0,1,0,0}, m, n; + + flags = ttSHORT(comp); comp+=2; + gidx = ttSHORT(comp); comp+=2; + + if (flags & 2) { // XY values + if (flags & 1) { // shorts + mtx[4] = ttSHORT(comp); comp+=2; + mtx[5] = ttSHORT(comp); comp+=2; + } else { + mtx[4] = ttCHAR(comp); comp+=1; + mtx[5] = ttCHAR(comp); comp+=1; + } + } + else { + // @TODO handle matching point + STBTT_assert(0); + } + if (flags & (1<<3)) { // WE_HAVE_A_SCALE + mtx[0] = mtx[3] = ttSHORT(comp)/16384.0f; comp+=2; + mtx[1] = mtx[2] = 0; + } else if (flags & (1<<6)) { // WE_HAVE_AN_X_AND_YSCALE + mtx[0] = ttSHORT(comp)/16384.0f; comp+=2; + mtx[1] = mtx[2] = 0; + mtx[3] = ttSHORT(comp)/16384.0f; comp+=2; + } else if (flags & (1<<7)) { // WE_HAVE_A_TWO_BY_TWO + mtx[0] = ttSHORT(comp)/16384.0f; comp+=2; + mtx[1] = ttSHORT(comp)/16384.0f; comp+=2; + mtx[2] = ttSHORT(comp)/16384.0f; comp+=2; + mtx[3] = ttSHORT(comp)/16384.0f; comp+=2; + } + + // Find transformation scales. + m = (float) STBTT_sqrt(mtx[0]*mtx[0] + mtx[1]*mtx[1]); + n = (float) STBTT_sqrt(mtx[2]*mtx[2] + mtx[3]*mtx[3]); + + // Get indexed glyph. + comp_num_verts = stbtt_GetGlyphShape(info, gidx, &comp_verts); + if (comp_num_verts > 0) { + // Transform vertices. + for (i = 0; i < comp_num_verts; ++i) { + stbtt_vertex* v = &comp_verts[i]; + stbtt_vertex_type x,y; + x=v->x; y=v->y; + v->x = (stbtt_vertex_type)(m * (mtx[0]*x + mtx[2]*y + mtx[4])); + v->y = (stbtt_vertex_type)(n * (mtx[1]*x + mtx[3]*y + mtx[5])); + x=v->cx; y=v->cy; + v->cx = (stbtt_vertex_type)(m * (mtx[0]*x + mtx[2]*y + mtx[4])); + v->cy = (stbtt_vertex_type)(n * (mtx[1]*x + mtx[3]*y + mtx[5])); + } + // Append vertices. + tmp = (stbtt_vertex*)STBTT_malloc((num_vertices+comp_num_verts)*sizeof(stbtt_vertex), info->userdata); + if (!tmp) { + if (vertices) STBTT_free(vertices, info->userdata); + if (comp_verts) STBTT_free(comp_verts, info->userdata); + return 0; + } + if (num_vertices > 0) STBTT_memcpy(tmp, vertices, num_vertices*sizeof(stbtt_vertex)); + STBTT_memcpy(tmp+num_vertices, comp_verts, comp_num_verts*sizeof(stbtt_vertex)); + if (vertices) STBTT_free(vertices, info->userdata); + vertices = tmp; + STBTT_free(comp_verts, info->userdata); + num_vertices += comp_num_verts; + } + // More components ? + more = flags & (1<<5); + } + } else { + // numberOfCounters == 0, do nothing + } + + *pvertices = vertices; + return num_vertices; +} + +typedef struct +{ + int bounds; + int started; + float first_x, first_y; + float x, y; + stbtt_int32 min_x, max_x, min_y, max_y; + + stbtt_vertex *pvertices; + int num_vertices; +} stbtt__csctx; + +#define STBTT__CSCTX_INIT(bounds) {bounds,0, 0,0, 0,0, 0,0,0,0, NULL, 0} + +static void stbtt__track_vertex(stbtt__csctx *c, stbtt_int32 x, stbtt_int32 y) +{ + if (x > c->max_x || !c->started) c->max_x = x; + if (y > c->max_y || !c->started) c->max_y = y; + if (x < c->min_x || !c->started) c->min_x = x; + if (y < c->min_y || !c->started) c->min_y = y; + c->started = 1; +} + +static void stbtt__csctx_v(stbtt__csctx *c, stbtt_uint8 type, stbtt_int32 x, stbtt_int32 y, stbtt_int32 cx, stbtt_int32 cy, stbtt_int32 cx1, stbtt_int32 cy1) +{ + if (c->bounds) { + stbtt__track_vertex(c, x, y); + if (type == STBTT_vcubic) { + stbtt__track_vertex(c, cx, cy); + stbtt__track_vertex(c, cx1, cy1); + } + } else { + stbtt_setvertex(&c->pvertices[c->num_vertices], type, x, y, cx, cy); + c->pvertices[c->num_vertices].cx1 = (stbtt_int16) cx1; + c->pvertices[c->num_vertices].cy1 = (stbtt_int16) cy1; + } + c->num_vertices++; +} + +static void stbtt__csctx_close_shape(stbtt__csctx *ctx) +{ + if (ctx->first_x != ctx->x || ctx->first_y != ctx->y) + stbtt__csctx_v(ctx, STBTT_vline, (int)ctx->first_x, (int)ctx->first_y, 0, 0, 0, 0); +} + +static void stbtt__csctx_rmove_to(stbtt__csctx *ctx, float dx, float dy) +{ + stbtt__csctx_close_shape(ctx); + ctx->first_x = ctx->x = ctx->x + dx; + ctx->first_y = ctx->y = ctx->y + dy; + stbtt__csctx_v(ctx, STBTT_vmove, (int)ctx->x, (int)ctx->y, 0, 0, 0, 0); +} + +static void stbtt__csctx_rline_to(stbtt__csctx *ctx, float dx, float dy) +{ + ctx->x += dx; + ctx->y += dy; + stbtt__csctx_v(ctx, STBTT_vline, (int)ctx->x, (int)ctx->y, 0, 0, 0, 0); +} + +static void stbtt__csctx_rccurve_to(stbtt__csctx *ctx, float dx1, float dy1, float dx2, float dy2, float dx3, float dy3) +{ + float cx1 = ctx->x + dx1; + float cy1 = ctx->y + dy1; + float cx2 = cx1 + dx2; + float cy2 = cy1 + dy2; + ctx->x = cx2 + dx3; + ctx->y = cy2 + dy3; + stbtt__csctx_v(ctx, STBTT_vcubic, (int)ctx->x, (int)ctx->y, (int)cx1, (int)cy1, (int)cx2, (int)cy2); +} + +static stbtt__buf stbtt__get_subr(stbtt__buf idx, int n) +{ + int count = stbtt__cff_index_count(&idx); + int bias = 107; + if (count >= 33900) + bias = 32768; + else if (count >= 1240) + bias = 1131; + n += bias; + if (n < 0 || n >= count) + return stbtt__new_buf(NULL, 0); + return stbtt__cff_index_get(idx, n); +} + +static stbtt__buf stbtt__cid_get_glyph_subrs(const stbtt_fontinfo *info, int glyph_index) +{ + stbtt__buf fdselect = info->fdselect; + int nranges, start, end, v, fmt, fdselector = -1, i; + + stbtt__buf_seek(&fdselect, 0); + fmt = stbtt__buf_get8(&fdselect); + if (fmt == 0) { + // untested + stbtt__buf_skip(&fdselect, glyph_index); + fdselector = stbtt__buf_get8(&fdselect); + } else if (fmt == 3) { + nranges = stbtt__buf_get16(&fdselect); + start = stbtt__buf_get16(&fdselect); + for (i = 0; i < nranges; i++) { + v = stbtt__buf_get8(&fdselect); + end = stbtt__buf_get16(&fdselect); + if (glyph_index >= start && glyph_index < end) { + fdselector = v; + break; + } + start = end; + } + } + if (fdselector == -1) stbtt__new_buf(NULL, 0); + return stbtt__get_subrs(info->cff, stbtt__cff_index_get(info->fontdicts, fdselector)); +} + +static int stbtt__run_charstring(const stbtt_fontinfo *info, int glyph_index, stbtt__csctx *c) +{ + int in_header = 1, maskbits = 0, subr_stack_height = 0, sp = 0, v, i, b0; + int has_subrs = 0, clear_stack; + float s[48]; + stbtt__buf subr_stack[10], subrs = info->subrs, b; + float f; + +#define STBTT__CSERR(s) (0) + + // this currently ignores the initial width value, which isn't needed if we have hmtx + b = stbtt__cff_index_get(info->charstrings, glyph_index); + while (b.cursor < b.size) { + i = 0; + clear_stack = 1; + b0 = stbtt__buf_get8(&b); + switch (b0) { + // @TODO implement hinting + case 0x13: // hintmask + case 0x14: // cntrmask + if (in_header) + maskbits += (sp / 2); // implicit "vstem" + in_header = 0; + stbtt__buf_skip(&b, (maskbits + 7) / 8); + break; + + case 0x01: // hstem + case 0x03: // vstem + case 0x12: // hstemhm + case 0x17: // vstemhm + maskbits += (sp / 2); + break; + + case 0x15: // rmoveto + in_header = 0; + if (sp < 2) return STBTT__CSERR("rmoveto stack"); + stbtt__csctx_rmove_to(c, s[sp-2], s[sp-1]); + break; + case 0x04: // vmoveto + in_header = 0; + if (sp < 1) return STBTT__CSERR("vmoveto stack"); + stbtt__csctx_rmove_to(c, 0, s[sp-1]); + break; + case 0x16: // hmoveto + in_header = 0; + if (sp < 1) return STBTT__CSERR("hmoveto stack"); + stbtt__csctx_rmove_to(c, s[sp-1], 0); + break; + + case 0x05: // rlineto + if (sp < 2) return STBTT__CSERR("rlineto stack"); + for (; i + 1 < sp; i += 2) + stbtt__csctx_rline_to(c, s[i], s[i+1]); + break; + + // hlineto/vlineto and vhcurveto/hvcurveto alternate horizontal and vertical + // starting from a different place. + + case 0x07: // vlineto + if (sp < 1) return STBTT__CSERR("vlineto stack"); + goto vlineto; + case 0x06: // hlineto + if (sp < 1) return STBTT__CSERR("hlineto stack"); + for (;;) { + if (i >= sp) break; + stbtt__csctx_rline_to(c, s[i], 0); + i++; + vlineto: + if (i >= sp) break; + stbtt__csctx_rline_to(c, 0, s[i]); + i++; + } + break; + + case 0x1F: // hvcurveto + if (sp < 4) return STBTT__CSERR("hvcurveto stack"); + goto hvcurveto; + case 0x1E: // vhcurveto + if (sp < 4) return STBTT__CSERR("vhcurveto stack"); + for (;;) { + if (i + 3 >= sp) break; + stbtt__csctx_rccurve_to(c, 0, s[i], s[i+1], s[i+2], s[i+3], (sp - i == 5) ? s[i + 4] : 0.0f); + i += 4; + hvcurveto: + if (i + 3 >= sp) break; + stbtt__csctx_rccurve_to(c, s[i], 0, s[i+1], s[i+2], (sp - i == 5) ? s[i+4] : 0.0f, s[i+3]); + i += 4; + } + break; + + case 0x08: // rrcurveto + if (sp < 6) return STBTT__CSERR("rcurveline stack"); + for (; i + 5 < sp; i += 6) + stbtt__csctx_rccurve_to(c, s[i], s[i+1], s[i+2], s[i+3], s[i+4], s[i+5]); + break; + + case 0x18: // rcurveline + if (sp < 8) return STBTT__CSERR("rcurveline stack"); + for (; i + 5 < sp - 2; i += 6) + stbtt__csctx_rccurve_to(c, s[i], s[i+1], s[i+2], s[i+3], s[i+4], s[i+5]); + if (i + 1 >= sp) return STBTT__CSERR("rcurveline stack"); + stbtt__csctx_rline_to(c, s[i], s[i+1]); + break; + + case 0x19: // rlinecurve + if (sp < 8) return STBTT__CSERR("rlinecurve stack"); + for (; i + 1 < sp - 6; i += 2) + stbtt__csctx_rline_to(c, s[i], s[i+1]); + if (i + 5 >= sp) return STBTT__CSERR("rlinecurve stack"); + stbtt__csctx_rccurve_to(c, s[i], s[i+1], s[i+2], s[i+3], s[i+4], s[i+5]); + break; + + case 0x1A: // vvcurveto + case 0x1B: // hhcurveto + if (sp < 4) return STBTT__CSERR("(vv|hh)curveto stack"); + f = 0.0; + if (sp & 1) { f = s[i]; i++; } + for (; i + 3 < sp; i += 4) { + if (b0 == 0x1B) + stbtt__csctx_rccurve_to(c, s[i], f, s[i+1], s[i+2], s[i+3], 0.0); + else + stbtt__csctx_rccurve_to(c, f, s[i], s[i+1], s[i+2], 0.0, s[i+3]); + f = 0.0; + } + break; + + case 0x0A: // callsubr + if (!has_subrs) { + if (info->fdselect.size) + subrs = stbtt__cid_get_glyph_subrs(info, glyph_index); + has_subrs = 1; + } + // fallthrough + case 0x1D: // callgsubr + if (sp < 1) return STBTT__CSERR("call(g|)subr stack"); + v = (int) s[--sp]; + if (subr_stack_height >= 10) return STBTT__CSERR("recursion limit"); + subr_stack[subr_stack_height++] = b; + b = stbtt__get_subr(b0 == 0x0A ? subrs : info->gsubrs, v); + if (b.size == 0) return STBTT__CSERR("subr not found"); + b.cursor = 0; + clear_stack = 0; + break; + + case 0x0B: // return + if (subr_stack_height <= 0) return STBTT__CSERR("return outside subr"); + b = subr_stack[--subr_stack_height]; + clear_stack = 0; + break; + + case 0x0E: // endchar + stbtt__csctx_close_shape(c); + return 1; + + case 0x0C: { // two-byte escape + float dx1, dx2, dx3, dx4, dx5, dx6, dy1, dy2, dy3, dy4, dy5, dy6; + float dx, dy; + int b1 = stbtt__buf_get8(&b); + switch (b1) { + // @TODO These "flex" implementations ignore the flex-depth and resolution, + // and always draw beziers. + case 0x22: // hflex + if (sp < 7) return STBTT__CSERR("hflex stack"); + dx1 = s[0]; + dx2 = s[1]; + dy2 = s[2]; + dx3 = s[3]; + dx4 = s[4]; + dx5 = s[5]; + dx6 = s[6]; + stbtt__csctx_rccurve_to(c, dx1, 0, dx2, dy2, dx3, 0); + stbtt__csctx_rccurve_to(c, dx4, 0, dx5, -dy2, dx6, 0); + break; + + case 0x23: // flex + if (sp < 13) return STBTT__CSERR("flex stack"); + dx1 = s[0]; + dy1 = s[1]; + dx2 = s[2]; + dy2 = s[3]; + dx3 = s[4]; + dy3 = s[5]; + dx4 = s[6]; + dy4 = s[7]; + dx5 = s[8]; + dy5 = s[9]; + dx6 = s[10]; + dy6 = s[11]; + //fd is s[12] + stbtt__csctx_rccurve_to(c, dx1, dy1, dx2, dy2, dx3, dy3); + stbtt__csctx_rccurve_to(c, dx4, dy4, dx5, dy5, dx6, dy6); + break; + + case 0x24: // hflex1 + if (sp < 9) return STBTT__CSERR("hflex1 stack"); + dx1 = s[0]; + dy1 = s[1]; + dx2 = s[2]; + dy2 = s[3]; + dx3 = s[4]; + dx4 = s[5]; + dx5 = s[6]; + dy5 = s[7]; + dx6 = s[8]; + stbtt__csctx_rccurve_to(c, dx1, dy1, dx2, dy2, dx3, 0); + stbtt__csctx_rccurve_to(c, dx4, 0, dx5, dy5, dx6, -(dy1+dy2+dy5)); + break; + + case 0x25: // flex1 + if (sp < 11) return STBTT__CSERR("flex1 stack"); + dx1 = s[0]; + dy1 = s[1]; + dx2 = s[2]; + dy2 = s[3]; + dx3 = s[4]; + dy3 = s[5]; + dx4 = s[6]; + dy4 = s[7]; + dx5 = s[8]; + dy5 = s[9]; + dx6 = dy6 = s[10]; + dx = dx1+dx2+dx3+dx4+dx5; + dy = dy1+dy2+dy3+dy4+dy5; + if (STBTT_fabs(dx) > STBTT_fabs(dy)) + dy6 = -dy; + else + dx6 = -dx; + stbtt__csctx_rccurve_to(c, dx1, dy1, dx2, dy2, dx3, dy3); + stbtt__csctx_rccurve_to(c, dx4, dy4, dx5, dy5, dx6, dy6); + break; + + default: + return STBTT__CSERR("unimplemented"); + } + } break; + + default: + if (b0 != 255 && b0 != 28 && (b0 < 32 || b0 > 254)) + return STBTT__CSERR("reserved operator"); + + // push immediate + if (b0 == 255) { + f = (float)(stbtt_int32)stbtt__buf_get32(&b) / 0x10000; + } else { + stbtt__buf_skip(&b, -1); + f = (float)(stbtt_int16)stbtt__cff_int(&b); + } + if (sp >= 48) return STBTT__CSERR("push stack overflow"); + s[sp++] = f; + clear_stack = 0; + break; + } + if (clear_stack) sp = 0; + } + return STBTT__CSERR("no endchar"); + +#undef STBTT__CSERR +} + +static int stbtt__GetGlyphShapeT2(const stbtt_fontinfo *info, int glyph_index, stbtt_vertex **pvertices) +{ + // runs the charstring twice, once to count and once to output (to avoid realloc) + stbtt__csctx count_ctx = STBTT__CSCTX_INIT(1); + stbtt__csctx output_ctx = STBTT__CSCTX_INIT(0); + if (stbtt__run_charstring(info, glyph_index, &count_ctx)) { + *pvertices = (stbtt_vertex*)STBTT_malloc(count_ctx.num_vertices*sizeof(stbtt_vertex), info->userdata); + output_ctx.pvertices = *pvertices; + if (stbtt__run_charstring(info, glyph_index, &output_ctx)) { + STBTT_assert(output_ctx.num_vertices == count_ctx.num_vertices); + return output_ctx.num_vertices; + } + } + *pvertices = NULL; + return 0; +} + +static int stbtt__GetGlyphInfoT2(const stbtt_fontinfo *info, int glyph_index, int *x0, int *y0, int *x1, int *y1) +{ + stbtt__csctx c = STBTT__CSCTX_INIT(1); + int r = stbtt__run_charstring(info, glyph_index, &c); + if (x0) *x0 = r ? c.min_x : 0; + if (y0) *y0 = r ? c.min_y : 0; + if (x1) *x1 = r ? c.max_x : 0; + if (y1) *y1 = r ? c.max_y : 0; + return r ? c.num_vertices : 0; +} + +STBTT_DEF int stbtt_GetGlyphShape(const stbtt_fontinfo *info, int glyph_index, stbtt_vertex **pvertices) +{ + if (!info->cff.size) + return stbtt__GetGlyphShapeTT(info, glyph_index, pvertices); + else + return stbtt__GetGlyphShapeT2(info, glyph_index, pvertices); +} + +STBTT_DEF void stbtt_GetGlyphHMetrics(const stbtt_fontinfo *info, int glyph_index, int *advanceWidth, int *leftSideBearing) +{ + stbtt_uint16 numOfLongHorMetrics = ttUSHORT(info->data+info->hhea + 34); + if (glyph_index < numOfLongHorMetrics) { + if (advanceWidth) *advanceWidth = ttSHORT(info->data + info->hmtx + 4*glyph_index); + if (leftSideBearing) *leftSideBearing = ttSHORT(info->data + info->hmtx + 4*glyph_index + 2); + } else { + if (advanceWidth) *advanceWidth = ttSHORT(info->data + info->hmtx + 4*(numOfLongHorMetrics-1)); + if (leftSideBearing) *leftSideBearing = ttSHORT(info->data + info->hmtx + 4*numOfLongHorMetrics + 2*(glyph_index - numOfLongHorMetrics)); + } +} + +STBTT_DEF int stbtt_GetKerningTableLength(const stbtt_fontinfo *info) +{ + stbtt_uint8 *data = info->data + info->kern; + + // we only look at the first table. it must be 'horizontal' and format 0. + if (!info->kern) + return 0; + if (ttUSHORT(data+2) < 1) // number of tables, need at least 1 + return 0; + if (ttUSHORT(data+8) != 1) // horizontal flag must be set in format + return 0; + + return ttUSHORT(data+10); +} + +STBTT_DEF int stbtt_GetKerningTable(const stbtt_fontinfo *info, stbtt_kerningentry* table, int table_length) +{ + stbtt_uint8 *data = info->data + info->kern; + int k, length; + + // we only look at the first table. it must be 'horizontal' and format 0. + if (!info->kern) + return 0; + if (ttUSHORT(data+2) < 1) // number of tables, need at least 1 + return 0; + if (ttUSHORT(data+8) != 1) // horizontal flag must be set in format + return 0; + + length = ttUSHORT(data+10); + if (table_length < length) + length = table_length; + + for (k = 0; k < length; k++) + { + table[k].glyph1 = ttUSHORT(data+18+(k*6)); + table[k].glyph2 = ttUSHORT(data+20+(k*6)); + table[k].advance = ttSHORT(data+22+(k*6)); + } + + return length; +} + +static int stbtt__GetGlyphKernInfoAdvance(const stbtt_fontinfo *info, int glyph1, int glyph2) +{ + stbtt_uint8 *data = info->data + info->kern; + stbtt_uint32 needle, straw; + int l, r, m; + + // we only look at the first table. it must be 'horizontal' and format 0. + if (!info->kern) + return 0; + if (ttUSHORT(data+2) < 1) // number of tables, need at least 1 + return 0; + if (ttUSHORT(data+8) != 1) // horizontal flag must be set in format + return 0; + + l = 0; + r = ttUSHORT(data+10) - 1; + needle = glyph1 << 16 | glyph2; + while (l <= r) { + m = (l + r) >> 1; + straw = ttULONG(data+18+(m*6)); // note: unaligned read + if (needle < straw) + r = m - 1; + else if (needle > straw) + l = m + 1; + else + return ttSHORT(data+22+(m*6)); + } + return 0; +} + +static stbtt_int32 stbtt__GetCoverageIndex(stbtt_uint8 *coverageTable, int glyph) +{ + stbtt_uint16 coverageFormat = ttUSHORT(coverageTable); + switch(coverageFormat) { + case 1: { + stbtt_uint16 glyphCount = ttUSHORT(coverageTable + 2); + + // Binary search. + stbtt_int32 l=0, r=glyphCount-1, m; + int straw, needle=glyph; + while (l <= r) { + stbtt_uint8 *glyphArray = coverageTable + 4; + stbtt_uint16 glyphID; + m = (l + r) >> 1; + glyphID = ttUSHORT(glyphArray + 2 * m); + straw = glyphID; + if (needle < straw) + r = m - 1; + else if (needle > straw) + l = m + 1; + else { + return m; + } + } + } break; + + case 2: { + stbtt_uint16 rangeCount = ttUSHORT(coverageTable + 2); + stbtt_uint8 *rangeArray = coverageTable + 4; + + // Binary search. + stbtt_int32 l=0, r=rangeCount-1, m; + int strawStart, strawEnd, needle=glyph; + while (l <= r) { + stbtt_uint8 *rangeRecord; + m = (l + r) >> 1; + rangeRecord = rangeArray + 6 * m; + strawStart = ttUSHORT(rangeRecord); + strawEnd = ttUSHORT(rangeRecord + 2); + if (needle < strawStart) + r = m - 1; + else if (needle > strawEnd) + l = m + 1; + else { + stbtt_uint16 startCoverageIndex = ttUSHORT(rangeRecord + 4); + return startCoverageIndex + glyph - strawStart; + } + } + } break; + + default: { + // There are no other cases. + STBTT_assert(0); + } break; + } + + return -1; +} + +static stbtt_int32 stbtt__GetGlyphClass(stbtt_uint8 *classDefTable, int glyph) +{ + stbtt_uint16 classDefFormat = ttUSHORT(classDefTable); + switch(classDefFormat) + { + case 1: { + stbtt_uint16 startGlyphID = ttUSHORT(classDefTable + 2); + stbtt_uint16 glyphCount = ttUSHORT(classDefTable + 4); + stbtt_uint8 *classDef1ValueArray = classDefTable + 6; + + if (glyph >= startGlyphID && glyph < startGlyphID + glyphCount) + return (stbtt_int32)ttUSHORT(classDef1ValueArray + 2 * (glyph - startGlyphID)); + + classDefTable = classDef1ValueArray + 2 * glyphCount; + } break; + + case 2: { + stbtt_uint16 classRangeCount = ttUSHORT(classDefTable + 2); + stbtt_uint8 *classRangeRecords = classDefTable + 4; + + // Binary search. + stbtt_int32 l=0, r=classRangeCount-1, m; + int strawStart, strawEnd, needle=glyph; + while (l <= r) { + stbtt_uint8 *classRangeRecord; + m = (l + r) >> 1; + classRangeRecord = classRangeRecords + 6 * m; + strawStart = ttUSHORT(classRangeRecord); + strawEnd = ttUSHORT(classRangeRecord + 2); + if (needle < strawStart) + r = m - 1; + else if (needle > strawEnd) + l = m + 1; + else + return (stbtt_int32)ttUSHORT(classRangeRecord + 4); + } + + classDefTable = classRangeRecords + 6 * classRangeCount; + } break; + + default: { + // There are no other cases. + STBTT_assert(0); + } break; + } + + return -1; +} + +// Define to STBTT_assert(x) if you want to break on unimplemented formats. +#define STBTT_GPOS_TODO_assert(x) + +static stbtt_int32 stbtt__GetGlyphGPOSInfoAdvance(const stbtt_fontinfo *info, int glyph1, int glyph2) +{ + stbtt_uint16 lookupListOffset; + stbtt_uint8 *lookupList; + stbtt_uint16 lookupCount; + stbtt_uint8 *data; + stbtt_int32 i; + + if (!info->gpos) return 0; + + data = info->data + info->gpos; + + if (ttUSHORT(data+0) != 1) return 0; // Major version 1 + if (ttUSHORT(data+2) != 0) return 0; // Minor version 0 + + lookupListOffset = ttUSHORT(data+8); + lookupList = data + lookupListOffset; + lookupCount = ttUSHORT(lookupList); + + for (i=0; i> 1; + pairValue = pairValueArray + (2 + valueRecordPairSizeInBytes) * m; + secondGlyph = ttUSHORT(pairValue); + straw = secondGlyph; + if (needle < straw) + r = m - 1; + else if (needle > straw) + l = m + 1; + else { + stbtt_int16 xAdvance = ttSHORT(pairValue + 2); + return xAdvance; + } + } + } break; + + case 2: { + stbtt_uint16 valueFormat1 = ttUSHORT(table + 4); + stbtt_uint16 valueFormat2 = ttUSHORT(table + 6); + + stbtt_uint16 classDef1Offset = ttUSHORT(table + 8); + stbtt_uint16 classDef2Offset = ttUSHORT(table + 10); + int glyph1class = stbtt__GetGlyphClass(table + classDef1Offset, glyph1); + int glyph2class = stbtt__GetGlyphClass(table + classDef2Offset, glyph2); + + stbtt_uint16 class1Count = ttUSHORT(table + 12); + stbtt_uint16 class2Count = ttUSHORT(table + 14); + STBTT_assert(glyph1class < class1Count); + STBTT_assert(glyph2class < class2Count); + + // TODO: Support more formats. + STBTT_GPOS_TODO_assert(valueFormat1 == 4); + if (valueFormat1 != 4) return 0; + STBTT_GPOS_TODO_assert(valueFormat2 == 0); + if (valueFormat2 != 0) return 0; + + if (glyph1class >= 0 && glyph1class < class1Count && glyph2class >= 0 && glyph2class < class2Count) { + stbtt_uint8 *class1Records = table + 16; + stbtt_uint8 *class2Records = class1Records + 2 * (glyph1class * class2Count); + stbtt_int16 xAdvance = ttSHORT(class2Records + 2 * glyph2class); + return xAdvance; + } + } break; + + default: { + // There are no other cases. + STBTT_assert(0); + break; + }; + } + } + break; + }; + + default: + // TODO: Implement other stuff. + break; + } + } + + return 0; +} + +STBTT_DEF int stbtt_GetGlyphKernAdvance(const stbtt_fontinfo *info, int g1, int g2) +{ + int xAdvance = 0; + + if (info->gpos) + xAdvance += stbtt__GetGlyphGPOSInfoAdvance(info, g1, g2); + else if (info->kern) + xAdvance += stbtt__GetGlyphKernInfoAdvance(info, g1, g2); + + return xAdvance; +} + +STBTT_DEF int stbtt_GetCodepointKernAdvance(const stbtt_fontinfo *info, int ch1, int ch2) +{ + if (!info->kern && !info->gpos) // if no kerning table, don't waste time looking up both codepoint->glyphs + return 0; + return stbtt_GetGlyphKernAdvance(info, stbtt_FindGlyphIndex(info,ch1), stbtt_FindGlyphIndex(info,ch2)); +} + +STBTT_DEF void stbtt_GetCodepointHMetrics(const stbtt_fontinfo *info, int codepoint, int *advanceWidth, int *leftSideBearing) +{ + stbtt_GetGlyphHMetrics(info, stbtt_FindGlyphIndex(info,codepoint), advanceWidth, leftSideBearing); +} + +STBTT_DEF void stbtt_GetFontVMetrics(const stbtt_fontinfo *info, int *ascent, int *descent, int *lineGap) +{ + if (ascent ) *ascent = ttSHORT(info->data+info->hhea + 4); + if (descent) *descent = ttSHORT(info->data+info->hhea + 6); + if (lineGap) *lineGap = ttSHORT(info->data+info->hhea + 8); +} + +STBTT_DEF int stbtt_GetFontVMetricsOS2(const stbtt_fontinfo *info, int *typoAscent, int *typoDescent, int *typoLineGap) +{ + int tab = stbtt__find_table(info->data, info->fontstart, "OS/2"); + if (!tab) + return 0; + if (typoAscent ) *typoAscent = ttSHORT(info->data+tab + 68); + if (typoDescent) *typoDescent = ttSHORT(info->data+tab + 70); + if (typoLineGap) *typoLineGap = ttSHORT(info->data+tab + 72); + return 1; +} + +STBTT_DEF void stbtt_GetFontBoundingBox(const stbtt_fontinfo *info, int *x0, int *y0, int *x1, int *y1) +{ + *x0 = ttSHORT(info->data + info->head + 36); + *y0 = ttSHORT(info->data + info->head + 38); + *x1 = ttSHORT(info->data + info->head + 40); + *y1 = ttSHORT(info->data + info->head + 42); +} + +STBTT_DEF float stbtt_ScaleForPixelHeight(const stbtt_fontinfo *info, float height) +{ + int fheight = ttSHORT(info->data + info->hhea + 4) - ttSHORT(info->data + info->hhea + 6); + return (float) height / fheight; +} + +STBTT_DEF float stbtt_ScaleForMappingEmToPixels(const stbtt_fontinfo *info, float pixels) +{ + int unitsPerEm = ttUSHORT(info->data + info->head + 18); + return pixels / unitsPerEm; +} + +STBTT_DEF void stbtt_FreeShape(const stbtt_fontinfo *info, stbtt_vertex *v) +{ + STBTT_free(v, info->userdata); +} + +STBTT_DEF stbtt_uint8 *stbtt_FindSVGDoc(const stbtt_fontinfo *info, int gl) +{ + int i; + stbtt_uint8 *data = info->data; + stbtt_uint8 *svg_doc_list = data + stbtt__get_svg((stbtt_fontinfo *) info); + + int numEntries = ttUSHORT(svg_doc_list); + stbtt_uint8 *svg_docs = svg_doc_list + 2; + + for(i=0; i= ttUSHORT(svg_doc)) && (gl <= ttUSHORT(svg_doc + 2))) + return svg_doc; + } + return 0; +} + +STBTT_DEF int stbtt_GetGlyphSVG(const stbtt_fontinfo *info, int gl, const char **svg) +{ + stbtt_uint8 *data = info->data; + stbtt_uint8 *svg_doc; + + if (info->svg == 0) + return 0; + + svg_doc = stbtt_FindSVGDoc(info, gl); + if (svg_doc != NULL) { + *svg = (char *) data + info->svg + ttULONG(svg_doc + 4); + return ttULONG(svg_doc + 8); + } else { + return 0; + } +} + +STBTT_DEF int stbtt_GetCodepointSVG(const stbtt_fontinfo *info, int unicode_codepoint, const char **svg) +{ + return stbtt_GetGlyphSVG(info, stbtt_FindGlyphIndex(info, unicode_codepoint), svg); +} + +////////////////////////////////////////////////////////////////////////////// +// +// antialiasing software rasterizer +// + +STBTT_DEF void stbtt_GetGlyphBitmapBoxSubpixel(const stbtt_fontinfo *font, int glyph, float scale_x, float scale_y,float shift_x, float shift_y, int *ix0, int *iy0, int *ix1, int *iy1) +{ + int x0=0,y0=0,x1,y1; // =0 suppresses compiler warning + if (!stbtt_GetGlyphBox(font, glyph, &x0,&y0,&x1,&y1)) { + // e.g. space character + if (ix0) *ix0 = 0; + if (iy0) *iy0 = 0; + if (ix1) *ix1 = 0; + if (iy1) *iy1 = 0; + } else { + // move to integral bboxes (treating pixels as little squares, what pixels get touched)? + if (ix0) *ix0 = STBTT_ifloor( x0 * scale_x + shift_x); + if (iy0) *iy0 = STBTT_ifloor(-y1 * scale_y + shift_y); + if (ix1) *ix1 = STBTT_iceil ( x1 * scale_x + shift_x); + if (iy1) *iy1 = STBTT_iceil (-y0 * scale_y + shift_y); + } +} + +STBTT_DEF void stbtt_GetGlyphBitmapBox(const stbtt_fontinfo *font, int glyph, float scale_x, float scale_y, int *ix0, int *iy0, int *ix1, int *iy1) +{ + stbtt_GetGlyphBitmapBoxSubpixel(font, glyph, scale_x, scale_y,0.0f,0.0f, ix0, iy0, ix1, iy1); +} + +STBTT_DEF void stbtt_GetCodepointBitmapBoxSubpixel(const stbtt_fontinfo *font, int codepoint, float scale_x, float scale_y, float shift_x, float shift_y, int *ix0, int *iy0, int *ix1, int *iy1) +{ + stbtt_GetGlyphBitmapBoxSubpixel(font, stbtt_FindGlyphIndex(font,codepoint), scale_x, scale_y,shift_x,shift_y, ix0,iy0,ix1,iy1); +} + +STBTT_DEF void stbtt_GetCodepointBitmapBox(const stbtt_fontinfo *font, int codepoint, float scale_x, float scale_y, int *ix0, int *iy0, int *ix1, int *iy1) +{ + stbtt_GetCodepointBitmapBoxSubpixel(font, codepoint, scale_x, scale_y,0.0f,0.0f, ix0,iy0,ix1,iy1); +} + +////////////////////////////////////////////////////////////////////////////// +// +// Rasterizer + +typedef struct stbtt__hheap_chunk +{ + struct stbtt__hheap_chunk *next; +} stbtt__hheap_chunk; + +typedef struct stbtt__hheap +{ + struct stbtt__hheap_chunk *head; + void *first_free; + int num_remaining_in_head_chunk; +} stbtt__hheap; + +static void *stbtt__hheap_alloc(stbtt__hheap *hh, size_t size, void *userdata) +{ + if (hh->first_free) { + void *p = hh->first_free; + hh->first_free = * (void **) p; + return p; + } else { + if (hh->num_remaining_in_head_chunk == 0) { + int count = (size < 32 ? 2000 : size < 128 ? 800 : 100); + stbtt__hheap_chunk *c = (stbtt__hheap_chunk *) STBTT_malloc(sizeof(stbtt__hheap_chunk) + size * count, userdata); + if (c == NULL) + return NULL; + c->next = hh->head; + hh->head = c; + hh->num_remaining_in_head_chunk = count; + } + --hh->num_remaining_in_head_chunk; + return (char *) (hh->head) + sizeof(stbtt__hheap_chunk) + size * hh->num_remaining_in_head_chunk; + } +} + +static void stbtt__hheap_free(stbtt__hheap *hh, void *p) +{ + *(void **) p = hh->first_free; + hh->first_free = p; +} + +static void stbtt__hheap_cleanup(stbtt__hheap *hh, void *userdata) +{ + stbtt__hheap_chunk *c = hh->head; + while (c) { + stbtt__hheap_chunk *n = c->next; + STBTT_free(c, userdata); + c = n; + } +} + +typedef struct stbtt__edge { + float x0,y0, x1,y1; + int invert; +} stbtt__edge; + + +typedef struct stbtt__active_edge +{ + struct stbtt__active_edge *next; + #if STBTT_RASTERIZER_VERSION==1 + int x,dx; + float ey; + int direction; + #elif STBTT_RASTERIZER_VERSION==2 + float fx,fdx,fdy; + float direction; + float sy; + float ey; + #else + #error "Unrecognized value of STBTT_RASTERIZER_VERSION" + #endif +} stbtt__active_edge; + +#if STBTT_RASTERIZER_VERSION == 1 +#define STBTT_FIXSHIFT 10 +#define STBTT_FIX (1 << STBTT_FIXSHIFT) +#define STBTT_FIXMASK (STBTT_FIX-1) + +static stbtt__active_edge *stbtt__new_active(stbtt__hheap *hh, stbtt__edge *e, int off_x, float start_point, void *userdata) +{ + stbtt__active_edge *z = (stbtt__active_edge *) stbtt__hheap_alloc(hh, sizeof(*z), userdata); + float dxdy = (e->x1 - e->x0) / (e->y1 - e->y0); + STBTT_assert(z != NULL); + if (!z) return z; + + // round dx down to avoid overshooting + if (dxdy < 0) + z->dx = -STBTT_ifloor(STBTT_FIX * -dxdy); + else + z->dx = STBTT_ifloor(STBTT_FIX * dxdy); + + z->x = STBTT_ifloor(STBTT_FIX * e->x0 + z->dx * (start_point - e->y0)); // use z->dx so when we offset later it's by the same amount + z->x -= off_x * STBTT_FIX; + + z->ey = e->y1; + z->next = 0; + z->direction = e->invert ? 1 : -1; + return z; +} +#elif STBTT_RASTERIZER_VERSION == 2 +static stbtt__active_edge *stbtt__new_active(stbtt__hheap *hh, stbtt__edge *e, int off_x, float start_point, void *userdata) +{ + stbtt__active_edge *z = (stbtt__active_edge *) stbtt__hheap_alloc(hh, sizeof(*z), userdata); + float dxdy = (e->x1 - e->x0) / (e->y1 - e->y0); + STBTT_assert(z != NULL); + //STBTT_assert(e->y0 <= start_point); + if (!z) return z; + z->fdx = dxdy; + z->fdy = dxdy != 0.0f ? (1.0f/dxdy) : 0.0f; + z->fx = e->x0 + dxdy * (start_point - e->y0); + z->fx -= off_x; + z->direction = e->invert ? 1.0f : -1.0f; + z->sy = e->y0; + z->ey = e->y1; + z->next = 0; + return z; +} +#else +#error "Unrecognized value of STBTT_RASTERIZER_VERSION" +#endif + +#if STBTT_RASTERIZER_VERSION == 1 +// note: this routine clips fills that extend off the edges... ideally this +// wouldn't happen, but it could happen if the truetype glyph bounding boxes +// are wrong, or if the user supplies a too-small bitmap +static void stbtt__fill_active_edges(unsigned char *scanline, int len, stbtt__active_edge *e, int max_weight) +{ + // non-zero winding fill + int x0=0, w=0; + + while (e) { + if (w == 0) { + // if we're currently at zero, we need to record the edge start point + x0 = e->x; w += e->direction; + } else { + int x1 = e->x; w += e->direction; + // if we went to zero, we need to draw + if (w == 0) { + int i = x0 >> STBTT_FIXSHIFT; + int j = x1 >> STBTT_FIXSHIFT; + + if (i < len && j >= 0) { + if (i == j) { + // x0,x1 are the same pixel, so compute combined coverage + scanline[i] = scanline[i] + (stbtt_uint8) ((x1 - x0) * max_weight >> STBTT_FIXSHIFT); + } else { + if (i >= 0) // add antialiasing for x0 + scanline[i] = scanline[i] + (stbtt_uint8) (((STBTT_FIX - (x0 & STBTT_FIXMASK)) * max_weight) >> STBTT_FIXSHIFT); + else + i = -1; // clip + + if (j < len) // add antialiasing for x1 + scanline[j] = scanline[j] + (stbtt_uint8) (((x1 & STBTT_FIXMASK) * max_weight) >> STBTT_FIXSHIFT); + else + j = len; // clip + + for (++i; i < j; ++i) // fill pixels between x0 and x1 + scanline[i] = scanline[i] + (stbtt_uint8) max_weight; + } + } + } + } + + e = e->next; + } +} + +static void stbtt__rasterize_sorted_edges(stbtt__bitmap *result, stbtt__edge *e, int n, int vsubsample, int off_x, int off_y, void *userdata) +{ + stbtt__hheap hh = { 0, 0, 0 }; + stbtt__active_edge *active = NULL; + int y,j=0; + int max_weight = (255 / vsubsample); // weight per vertical scanline + int s; // vertical subsample index + unsigned char scanline_data[512], *scanline; + + if (result->w > 512) + scanline = (unsigned char *) STBTT_malloc(result->w, userdata); + else + scanline = scanline_data; + + y = off_y * vsubsample; + e[n].y0 = (off_y + result->h) * (float) vsubsample + 1; + + while (j < result->h) { + STBTT_memset(scanline, 0, result->w); + for (s=0; s < vsubsample; ++s) { + // find center of pixel for this scanline + float scan_y = y + 0.5f; + stbtt__active_edge **step = &active; + + // update all active edges; + // remove all active edges that terminate before the center of this scanline + while (*step) { + stbtt__active_edge * z = *step; + if (z->ey <= scan_y) { + *step = z->next; // delete from list + STBTT_assert(z->direction); + z->direction = 0; + stbtt__hheap_free(&hh, z); + } else { + z->x += z->dx; // advance to position for current scanline + step = &((*step)->next); // advance through list + } + } + + // resort the list if needed + for(;;) { + int changed=0; + step = &active; + while (*step && (*step)->next) { + if ((*step)->x > (*step)->next->x) { + stbtt__active_edge *t = *step; + stbtt__active_edge *q = t->next; + + t->next = q->next; + q->next = t; + *step = q; + changed = 1; + } + step = &(*step)->next; + } + if (!changed) break; + } + + // insert all edges that start before the center of this scanline -- omit ones that also end on this scanline + while (e->y0 <= scan_y) { + if (e->y1 > scan_y) { + stbtt__active_edge *z = stbtt__new_active(&hh, e, off_x, scan_y, userdata); + if (z != NULL) { + // find insertion point + if (active == NULL) + active = z; + else if (z->x < active->x) { + // insert at front + z->next = active; + active = z; + } else { + // find thing to insert AFTER + stbtt__active_edge *p = active; + while (p->next && p->next->x < z->x) + p = p->next; + // at this point, p->next->x is NOT < z->x + z->next = p->next; + p->next = z; + } + } + } + ++e; + } + + // now process all active edges in XOR fashion + if (active) + stbtt__fill_active_edges(scanline, result->w, active, max_weight); + + ++y; + } + STBTT_memcpy(result->pixels + j * result->stride, scanline, result->w); + ++j; + } + + stbtt__hheap_cleanup(&hh, userdata); + + if (scanline != scanline_data) + STBTT_free(scanline, userdata); +} + +#elif STBTT_RASTERIZER_VERSION == 2 + +// the edge passed in here does not cross the vertical line at x or the vertical line at x+1 +// (i.e. it has already been clipped to those) +static void stbtt__handle_clipped_edge(float *scanline, int x, stbtt__active_edge *e, float x0, float y0, float x1, float y1) +{ + if (y0 == y1) return; + STBTT_assert(y0 < y1); + STBTT_assert(e->sy <= e->ey); + if (y0 > e->ey) return; + if (y1 < e->sy) return; + if (y0 < e->sy) { + x0 += (x1-x0) * (e->sy - y0) / (y1-y0); + y0 = e->sy; + } + if (y1 > e->ey) { + x1 += (x1-x0) * (e->ey - y1) / (y1-y0); + y1 = e->ey; + } + + if (x0 == x) + STBTT_assert(x1 <= x+1); + else if (x0 == x+1) + STBTT_assert(x1 >= x); + else if (x0 <= x) + STBTT_assert(x1 <= x); + else if (x0 >= x+1) + STBTT_assert(x1 >= x+1); + else + STBTT_assert(x1 >= x && x1 <= x+1); + + if (x0 <= x && x1 <= x) + scanline[x] += e->direction * (y1-y0); + else if (x0 >= x+1 && x1 >= x+1) + ; + else { + STBTT_assert(x0 >= x && x0 <= x+1 && x1 >= x && x1 <= x+1); + scanline[x] += e->direction * (y1-y0) * (1-((x0-x)+(x1-x))/2); // coverage = 1 - average x position + } +} + +static void stbtt__fill_active_edges_new(float *scanline, float *scanline_fill, int len, stbtt__active_edge *e, float y_top) +{ + float y_bottom = y_top+1; + + while (e) { + // brute force every pixel + + // compute intersection points with top & bottom + STBTT_assert(e->ey >= y_top); + + if (e->fdx == 0) { + float x0 = e->fx; + if (x0 < len) { + if (x0 >= 0) { + stbtt__handle_clipped_edge(scanline,(int) x0,e, x0,y_top, x0,y_bottom); + stbtt__handle_clipped_edge(scanline_fill-1,(int) x0+1,e, x0,y_top, x0,y_bottom); + } else { + stbtt__handle_clipped_edge(scanline_fill-1,0,e, x0,y_top, x0,y_bottom); + } + } + } else { + float x0 = e->fx; + float dx = e->fdx; + float xb = x0 + dx; + float x_top, x_bottom; + float sy0,sy1; + float dy = e->fdy; + STBTT_assert(e->sy <= y_bottom && e->ey >= y_top); + + // compute endpoints of line segment clipped to this scanline (if the + // line segment starts on this scanline. x0 is the intersection of the + // line with y_top, but that may be off the line segment. + if (e->sy > y_top) { + x_top = x0 + dx * (e->sy - y_top); + sy0 = e->sy; + } else { + x_top = x0; + sy0 = y_top; + } + if (e->ey < y_bottom) { + x_bottom = x0 + dx * (e->ey - y_top); + sy1 = e->ey; + } else { + x_bottom = xb; + sy1 = y_bottom; + } + + if (x_top >= 0 && x_bottom >= 0 && x_top < len && x_bottom < len) { + // from here on, we don't have to range check x values + + if ((int) x_top == (int) x_bottom) { + float height; + // simple case, only spans one pixel + int x = (int) x_top; + height = sy1 - sy0; + STBTT_assert(x >= 0 && x < len); + scanline[x] += e->direction * (1-((x_top - x) + (x_bottom-x))/2) * height; + scanline_fill[x] += e->direction * height; // everything right of this pixel is filled + } else { + int x,x1,x2; + float y_crossing, step, sign, area; + // covers 2+ pixels + if (x_top > x_bottom) { + // flip scanline vertically; signed area is the same + float t; + sy0 = y_bottom - (sy0 - y_top); + sy1 = y_bottom - (sy1 - y_top); + t = sy0, sy0 = sy1, sy1 = t; + t = x_bottom, x_bottom = x_top, x_top = t; + dx = -dx; + dy = -dy; + t = x0, x0 = xb, xb = t; + } + + x1 = (int) x_top; + x2 = (int) x_bottom; + // compute intersection with y axis at x1+1 + y_crossing = (x1+1 - x0) * dy + y_top; + + sign = e->direction; + // area of the rectangle covered from y0..y_crossing + area = sign * (y_crossing-sy0); + // area of the triangle (x_top,y0), (x+1,y0), (x+1,y_crossing) + scanline[x1] += area * (1-((x_top - x1)+(x1+1-x1))/2); + + step = sign * dy; + for (x = x1+1; x < x2; ++x) { + scanline[x] += area + step/2; + area += step; + } + y_crossing += dy * (x2 - (x1+1)); + + STBTT_assert(STBTT_fabs(area) <= 1.01f); + + scanline[x2] += area + sign * (1-((x2-x2)+(x_bottom-x2))/2) * (sy1-y_crossing); + + scanline_fill[x2] += sign * (sy1-sy0); + } + } else { + // if edge goes outside of box we're drawing, we require + // clipping logic. since this does not match the intended use + // of this library, we use a different, very slow brute + // force implementation + int x; + for (x=0; x < len; ++x) { + // cases: + // + // there can be up to two intersections with the pixel. any intersection + // with left or right edges can be handled by splitting into two (or three) + // regions. intersections with top & bottom do not necessitate case-wise logic. + // + // the old way of doing this found the intersections with the left & right edges, + // then used some simple logic to produce up to three segments in sorted order + // from top-to-bottom. however, this had a problem: if an x edge was epsilon + // across the x border, then the corresponding y position might not be distinct + // from the other y segment, and it might ignored as an empty segment. to avoid + // that, we need to explicitly produce segments based on x positions. + + // rename variables to clearly-defined pairs + float y0 = y_top; + float x1 = (float) (x); + float x2 = (float) (x+1); + float x3 = xb; + float y3 = y_bottom; + + // x = e->x + e->dx * (y-y_top) + // (y-y_top) = (x - e->x) / e->dx + // y = (x - e->x) / e->dx + y_top + float y1 = (x - x0) / dx + y_top; + float y2 = (x+1 - x0) / dx + y_top; + + if (x0 < x1 && x3 > x2) { // three segments descending down-right + stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x1,y1); + stbtt__handle_clipped_edge(scanline,x,e, x1,y1, x2,y2); + stbtt__handle_clipped_edge(scanline,x,e, x2,y2, x3,y3); + } else if (x3 < x1 && x0 > x2) { // three segments descending down-left + stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x2,y2); + stbtt__handle_clipped_edge(scanline,x,e, x2,y2, x1,y1); + stbtt__handle_clipped_edge(scanline,x,e, x1,y1, x3,y3); + } else if (x0 < x1 && x3 > x1) { // two segments across x, down-right + stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x1,y1); + stbtt__handle_clipped_edge(scanline,x,e, x1,y1, x3,y3); + } else if (x3 < x1 && x0 > x1) { // two segments across x, down-left + stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x1,y1); + stbtt__handle_clipped_edge(scanline,x,e, x1,y1, x3,y3); + } else if (x0 < x2 && x3 > x2) { // two segments across x+1, down-right + stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x2,y2); + stbtt__handle_clipped_edge(scanline,x,e, x2,y2, x3,y3); + } else if (x3 < x2 && x0 > x2) { // two segments across x+1, down-left + stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x2,y2); + stbtt__handle_clipped_edge(scanline,x,e, x2,y2, x3,y3); + } else { // one segment + stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x3,y3); + } + } + } + } + e = e->next; + } +} + +// directly AA rasterize edges w/o supersampling +static void stbtt__rasterize_sorted_edges(stbtt__bitmap *result, stbtt__edge *e, int n, int vsubsample, int off_x, int off_y, void *userdata) +{ + stbtt__hheap hh = { 0, 0, 0 }; + stbtt__active_edge *active = NULL; + int y,j=0, i; + float scanline_data[129], *scanline, *scanline2; + + STBTT__NOTUSED(vsubsample); + + if (result->w > 64) + scanline = (float *) STBTT_malloc((result->w*2+1) * sizeof(float), userdata); + else + scanline = scanline_data; + + scanline2 = scanline + result->w; + + y = off_y; + e[n].y0 = (float) (off_y + result->h) + 1; + + while (j < result->h) { + // find center of pixel for this scanline + float scan_y_top = y + 0.0f; + float scan_y_bottom = y + 1.0f; + stbtt__active_edge **step = &active; + + STBTT_memset(scanline , 0, result->w*sizeof(scanline[0])); + STBTT_memset(scanline2, 0, (result->w+1)*sizeof(scanline[0])); + + // update all active edges; + // remove all active edges that terminate before the top of this scanline + while (*step) { + stbtt__active_edge * z = *step; + if (z->ey <= scan_y_top) { + *step = z->next; // delete from list + STBTT_assert(z->direction); + z->direction = 0; + stbtt__hheap_free(&hh, z); + } else { + step = &((*step)->next); // advance through list + } + } + + // insert all edges that start before the bottom of this scanline + while (e->y0 <= scan_y_bottom) { + if (e->y0 != e->y1) { + stbtt__active_edge *z = stbtt__new_active(&hh, e, off_x, scan_y_top, userdata); + if (z != NULL) { + if (j == 0 && off_y != 0) { + if (z->ey < scan_y_top) { + // this can happen due to subpixel positioning and some kind of fp rounding error i think + z->ey = scan_y_top; + } + } + STBTT_assert(z->ey >= scan_y_top); // if we get really unlucky a tiny bit of an edge can be out of bounds + // insert at front + z->next = active; + active = z; + } + } + ++e; + } + + // now process all active edges + if (active) + stbtt__fill_active_edges_new(scanline, scanline2+1, result->w, active, scan_y_top); + + { + float sum = 0; + for (i=0; i < result->w; ++i) { + float k; + int m; + sum += scanline2[i]; + k = scanline[i] + sum; + k = (float) STBTT_fabs(k)*255 + 0.5f; + m = (int) k; + if (m > 255) m = 255; + result->pixels[j*result->stride + i] = (unsigned char) m; + } + } + // advance all the edges + step = &active; + while (*step) { + stbtt__active_edge *z = *step; + z->fx += z->fdx; // advance to position for current scanline + step = &((*step)->next); // advance through list + } + + ++y; + ++j; + } + + stbtt__hheap_cleanup(&hh, userdata); + + if (scanline != scanline_data) + STBTT_free(scanline, userdata); +} +#else +#error "Unrecognized value of STBTT_RASTERIZER_VERSION" +#endif + +#define STBTT__COMPARE(a,b) ((a)->y0 < (b)->y0) + +static void stbtt__sort_edges_ins_sort(stbtt__edge *p, int n) +{ + int i,j; + for (i=1; i < n; ++i) { + stbtt__edge t = p[i], *a = &t; + j = i; + while (j > 0) { + stbtt__edge *b = &p[j-1]; + int c = STBTT__COMPARE(a,b); + if (!c) break; + p[j] = p[j-1]; + --j; + } + if (i != j) + p[j] = t; + } +} + +static void stbtt__sort_edges_quicksort(stbtt__edge *p, int n) +{ + /* threshold for transitioning to insertion sort */ + while (n > 12) { + stbtt__edge t; + int c01,c12,c,m,i,j; + + /* compute median of three */ + m = n >> 1; + c01 = STBTT__COMPARE(&p[0],&p[m]); + c12 = STBTT__COMPARE(&p[m],&p[n-1]); + /* if 0 >= mid >= end, or 0 < mid < end, then use mid */ + if (c01 != c12) { + /* otherwise, we'll need to swap something else to middle */ + int z; + c = STBTT__COMPARE(&p[0],&p[n-1]); + /* 0>mid && midn => n; 0 0 */ + /* 0n: 0>n => 0; 0 n */ + z = (c == c12) ? 0 : n-1; + t = p[z]; + p[z] = p[m]; + p[m] = t; + } + /* now p[m] is the median-of-three */ + /* swap it to the beginning so it won't move around */ + t = p[0]; + p[0] = p[m]; + p[m] = t; + + /* partition loop */ + i=1; + j=n-1; + for(;;) { + /* handling of equality is crucial here */ + /* for sentinels & efficiency with duplicates */ + for (;;++i) { + if (!STBTT__COMPARE(&p[i], &p[0])) break; + } + for (;;--j) { + if (!STBTT__COMPARE(&p[0], &p[j])) break; + } + /* make sure we haven't crossed */ + if (i >= j) break; + t = p[i]; + p[i] = p[j]; + p[j] = t; + + ++i; + --j; + } + /* recurse on smaller side, iterate on larger */ + if (j < (n-i)) { + stbtt__sort_edges_quicksort(p,j); + p = p+i; + n = n-i; + } else { + stbtt__sort_edges_quicksort(p+i, n-i); + n = j; + } + } +} + +static void stbtt__sort_edges(stbtt__edge *p, int n) +{ + stbtt__sort_edges_quicksort(p, n); + stbtt__sort_edges_ins_sort(p, n); +} + +typedef struct +{ + float x,y; +} stbtt__point; + +static void stbtt__rasterize(stbtt__bitmap *result, stbtt__point *pts, int *wcount, int windings, float scale_x, float scale_y, float shift_x, float shift_y, int off_x, int off_y, int invert, void *userdata) +{ + float y_scale_inv = invert ? -scale_y : scale_y; + stbtt__edge *e; + int n,i,j,k,m; +#if STBTT_RASTERIZER_VERSION == 1 + int vsubsample = result->h < 8 ? 15 : 5; +#elif STBTT_RASTERIZER_VERSION == 2 + int vsubsample = 1; +#else + #error "Unrecognized value of STBTT_RASTERIZER_VERSION" +#endif + // vsubsample should divide 255 evenly; otherwise we won't reach full opacity + + // now we have to blow out the windings into explicit edge lists + n = 0; + for (i=0; i < windings; ++i) + n += wcount[i]; + + e = (stbtt__edge *) STBTT_malloc(sizeof(*e) * (n+1), userdata); // add an extra one as a sentinel + if (e == 0) return; + n = 0; + + m=0; + for (i=0; i < windings; ++i) { + stbtt__point *p = pts + m; + m += wcount[i]; + j = wcount[i]-1; + for (k=0; k < wcount[i]; j=k++) { + int a=k,b=j; + // skip the edge if horizontal + if (p[j].y == p[k].y) + continue; + // add edge from j to k to the list + e[n].invert = 0; + if (invert ? p[j].y > p[k].y : p[j].y < p[k].y) { + e[n].invert = 1; + a=j,b=k; + } + e[n].x0 = p[a].x * scale_x + shift_x; + e[n].y0 = (p[a].y * y_scale_inv + shift_y) * vsubsample; + e[n].x1 = p[b].x * scale_x + shift_x; + e[n].y1 = (p[b].y * y_scale_inv + shift_y) * vsubsample; + ++n; + } + } + + // now sort the edges by their highest point (should snap to integer, and then by x) + //STBTT_sort(e, n, sizeof(e[0]), stbtt__edge_compare); + stbtt__sort_edges(e, n); + + // now, traverse the scanlines and find the intersections on each scanline, use xor winding rule + stbtt__rasterize_sorted_edges(result, e, n, vsubsample, off_x, off_y, userdata); + + STBTT_free(e, userdata); +} + +static void stbtt__add_point(stbtt__point *points, int n, float x, float y) +{ + if (!points) return; // during first pass, it's unallocated + points[n].x = x; + points[n].y = y; +} + +// tessellate until threshold p is happy... @TODO warped to compensate for non-linear stretching +static int stbtt__tesselate_curve(stbtt__point *points, int *num_points, float x0, float y0, float x1, float y1, float x2, float y2, float objspace_flatness_squared, int n) +{ + // midpoint + float mx = (x0 + 2*x1 + x2)/4; + float my = (y0 + 2*y1 + y2)/4; + // versus directly drawn line + float dx = (x0+x2)/2 - mx; + float dy = (y0+y2)/2 - my; + if (n > 16) // 65536 segments on one curve better be enough! + return 1; + if (dx*dx+dy*dy > objspace_flatness_squared) { // half-pixel error allowed... need to be smaller if AA + stbtt__tesselate_curve(points, num_points, x0,y0, (x0+x1)/2.0f,(y0+y1)/2.0f, mx,my, objspace_flatness_squared,n+1); + stbtt__tesselate_curve(points, num_points, mx,my, (x1+x2)/2.0f,(y1+y2)/2.0f, x2,y2, objspace_flatness_squared,n+1); + } else { + stbtt__add_point(points, *num_points,x2,y2); + *num_points = *num_points+1; + } + return 1; +} + +static void stbtt__tesselate_cubic(stbtt__point *points, int *num_points, float x0, float y0, float x1, float y1, float x2, float y2, float x3, float y3, float objspace_flatness_squared, int n) +{ + // @TODO this "flatness" calculation is just made-up nonsense that seems to work well enough + float dx0 = x1-x0; + float dy0 = y1-y0; + float dx1 = x2-x1; + float dy1 = y2-y1; + float dx2 = x3-x2; + float dy2 = y3-y2; + float dx = x3-x0; + float dy = y3-y0; + float longlen = (float) (STBTT_sqrt(dx0*dx0+dy0*dy0)+STBTT_sqrt(dx1*dx1+dy1*dy1)+STBTT_sqrt(dx2*dx2+dy2*dy2)); + float shortlen = (float) STBTT_sqrt(dx*dx+dy*dy); + float flatness_squared = longlen*longlen-shortlen*shortlen; + + if (n > 16) // 65536 segments on one curve better be enough! + return; + + if (flatness_squared > objspace_flatness_squared) { + float x01 = (x0+x1)/2; + float y01 = (y0+y1)/2; + float x12 = (x1+x2)/2; + float y12 = (y1+y2)/2; + float x23 = (x2+x3)/2; + float y23 = (y2+y3)/2; + + float xa = (x01+x12)/2; + float ya = (y01+y12)/2; + float xb = (x12+x23)/2; + float yb = (y12+y23)/2; + + float mx = (xa+xb)/2; + float my = (ya+yb)/2; + + stbtt__tesselate_cubic(points, num_points, x0,y0, x01,y01, xa,ya, mx,my, objspace_flatness_squared,n+1); + stbtt__tesselate_cubic(points, num_points, mx,my, xb,yb, x23,y23, x3,y3, objspace_flatness_squared,n+1); + } else { + stbtt__add_point(points, *num_points,x3,y3); + *num_points = *num_points+1; + } +} + +// returns number of contours +static stbtt__point *stbtt_FlattenCurves(stbtt_vertex *vertices, int num_verts, float objspace_flatness, int **contour_lengths, int *num_contours, void *userdata) +{ + stbtt__point *points=0; + int num_points=0; + + float objspace_flatness_squared = objspace_flatness * objspace_flatness; + int i,n=0,start=0, pass; + + // count how many "moves" there are to get the contour count + for (i=0; i < num_verts; ++i) + if (vertices[i].type == STBTT_vmove) + ++n; + + *num_contours = n; + if (n == 0) return 0; + + *contour_lengths = (int *) STBTT_malloc(sizeof(**contour_lengths) * n, userdata); + + if (*contour_lengths == 0) { + *num_contours = 0; + return 0; + } + + // make two passes through the points so we don't need to realloc + for (pass=0; pass < 2; ++pass) { + float x=0,y=0; + if (pass == 1) { + points = (stbtt__point *) STBTT_malloc(num_points * sizeof(points[0]), userdata); + if (points == NULL) goto error; + } + num_points = 0; + n= -1; + for (i=0; i < num_verts; ++i) { + switch (vertices[i].type) { + case STBTT_vmove: + // start the next contour + if (n >= 0) + (*contour_lengths)[n] = num_points - start; + ++n; + start = num_points; + + x = vertices[i].x, y = vertices[i].y; + stbtt__add_point(points, num_points++, x,y); + break; + case STBTT_vline: + x = vertices[i].x, y = vertices[i].y; + stbtt__add_point(points, num_points++, x, y); + break; + case STBTT_vcurve: + stbtt__tesselate_curve(points, &num_points, x,y, + vertices[i].cx, vertices[i].cy, + vertices[i].x, vertices[i].y, + objspace_flatness_squared, 0); + x = vertices[i].x, y = vertices[i].y; + break; + case STBTT_vcubic: + stbtt__tesselate_cubic(points, &num_points, x,y, + vertices[i].cx, vertices[i].cy, + vertices[i].cx1, vertices[i].cy1, + vertices[i].x, vertices[i].y, + objspace_flatness_squared, 0); + x = vertices[i].x, y = vertices[i].y; + break; + } + } + (*contour_lengths)[n] = num_points - start; + } + + return points; +error: + STBTT_free(points, userdata); + STBTT_free(*contour_lengths, userdata); + *contour_lengths = 0; + *num_contours = 0; + return NULL; +} + +STBTT_DEF void stbtt_Rasterize(stbtt__bitmap *result, float flatness_in_pixels, stbtt_vertex *vertices, int num_verts, float scale_x, float scale_y, float shift_x, float shift_y, int x_off, int y_off, int invert, void *userdata) +{ + float scale = scale_x > scale_y ? scale_y : scale_x; + int winding_count = 0; + int *winding_lengths = NULL; + stbtt__point *windings = stbtt_FlattenCurves(vertices, num_verts, flatness_in_pixels / scale, &winding_lengths, &winding_count, userdata); + if (windings) { + stbtt__rasterize(result, windings, winding_lengths, winding_count, scale_x, scale_y, shift_x, shift_y, x_off, y_off, invert, userdata); + STBTT_free(winding_lengths, userdata); + STBTT_free(windings, userdata); + } +} + +STBTT_DEF void stbtt_FreeBitmap(unsigned char *bitmap, void *userdata) +{ + STBTT_free(bitmap, userdata); +} + +STBTT_DEF unsigned char *stbtt_GetGlyphBitmapSubpixel(const stbtt_fontinfo *info, float scale_x, float scale_y, float shift_x, float shift_y, int glyph, int *width, int *height, int *xoff, int *yoff) +{ + int ix0,iy0,ix1,iy1; + stbtt__bitmap gbm; + stbtt_vertex *vertices; + int num_verts = stbtt_GetGlyphShape(info, glyph, &vertices); + + if (scale_x == 0) scale_x = scale_y; + if (scale_y == 0) { + if (scale_x == 0) { + STBTT_free(vertices, info->userdata); + return NULL; + } + scale_y = scale_x; + } + + stbtt_GetGlyphBitmapBoxSubpixel(info, glyph, scale_x, scale_y, shift_x, shift_y, &ix0,&iy0,&ix1,&iy1); + + // now we get the size + gbm.w = (ix1 - ix0); + gbm.h = (iy1 - iy0); + gbm.pixels = NULL; // in case we error + + if (width ) *width = gbm.w; + if (height) *height = gbm.h; + if (xoff ) *xoff = ix0; + if (yoff ) *yoff = iy0; + + if (gbm.w && gbm.h) { + gbm.pixels = (unsigned char *) STBTT_malloc(gbm.w * gbm.h, info->userdata); + if (gbm.pixels) { + gbm.stride = gbm.w; + + stbtt_Rasterize(&gbm, 0.35f, vertices, num_verts, scale_x, scale_y, shift_x, shift_y, ix0, iy0, 1, info->userdata); + } + } + STBTT_free(vertices, info->userdata); + return gbm.pixels; +} + +STBTT_DEF unsigned char *stbtt_GetGlyphBitmap(const stbtt_fontinfo *info, float scale_x, float scale_y, int glyph, int *width, int *height, int *xoff, int *yoff) +{ + return stbtt_GetGlyphBitmapSubpixel(info, scale_x, scale_y, 0.0f, 0.0f, glyph, width, height, xoff, yoff); +} + +STBTT_DEF void stbtt_MakeGlyphBitmapSubpixel(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int glyph) +{ + int ix0,iy0; + stbtt_vertex *vertices; + int num_verts = stbtt_GetGlyphShape(info, glyph, &vertices); + stbtt__bitmap gbm; + + stbtt_GetGlyphBitmapBoxSubpixel(info, glyph, scale_x, scale_y, shift_x, shift_y, &ix0,&iy0,0,0); + gbm.pixels = output; + gbm.w = out_w; + gbm.h = out_h; + gbm.stride = out_stride; + + if (gbm.w && gbm.h) + stbtt_Rasterize(&gbm, 0.35f, vertices, num_verts, scale_x, scale_y, shift_x, shift_y, ix0,iy0, 1, info->userdata); + + STBTT_free(vertices, info->userdata); +} + +STBTT_DEF void stbtt_MakeGlyphBitmap(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, int glyph) +{ + stbtt_MakeGlyphBitmapSubpixel(info, output, out_w, out_h, out_stride, scale_x, scale_y, 0.0f,0.0f, glyph); +} + +STBTT_DEF unsigned char *stbtt_GetCodepointBitmapSubpixel(const stbtt_fontinfo *info, float scale_x, float scale_y, float shift_x, float shift_y, int codepoint, int *width, int *height, int *xoff, int *yoff) +{ + return stbtt_GetGlyphBitmapSubpixel(info, scale_x, scale_y,shift_x,shift_y, stbtt_FindGlyphIndex(info,codepoint), width,height,xoff,yoff); +} + +STBTT_DEF void stbtt_MakeCodepointBitmapSubpixelPrefilter(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int oversample_x, int oversample_y, float *sub_x, float *sub_y, int codepoint) +{ + stbtt_MakeGlyphBitmapSubpixelPrefilter(info, output, out_w, out_h, out_stride, scale_x, scale_y, shift_x, shift_y, oversample_x, oversample_y, sub_x, sub_y, stbtt_FindGlyphIndex(info,codepoint)); +} + +STBTT_DEF void stbtt_MakeCodepointBitmapSubpixel(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int codepoint) +{ + stbtt_MakeGlyphBitmapSubpixel(info, output, out_w, out_h, out_stride, scale_x, scale_y, shift_x, shift_y, stbtt_FindGlyphIndex(info,codepoint)); +} + +STBTT_DEF unsigned char *stbtt_GetCodepointBitmap(const stbtt_fontinfo *info, float scale_x, float scale_y, int codepoint, int *width, int *height, int *xoff, int *yoff) +{ + return stbtt_GetCodepointBitmapSubpixel(info, scale_x, scale_y, 0.0f,0.0f, codepoint, width,height,xoff,yoff); +} + +STBTT_DEF void stbtt_MakeCodepointBitmap(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, int codepoint) +{ + stbtt_MakeCodepointBitmapSubpixel(info, output, out_w, out_h, out_stride, scale_x, scale_y, 0.0f,0.0f, codepoint); +} + +////////////////////////////////////////////////////////////////////////////// +// +// bitmap baking +// +// This is SUPER-CRAPPY packing to keep source code small + +static int stbtt_BakeFontBitmap_internal(unsigned char *data, int offset, // font location (use offset=0 for plain .ttf) + float pixel_height, // height of font in pixels + unsigned char *pixels, int pw, int ph, // bitmap to be filled in + int first_char, int num_chars, // characters to bake + stbtt_bakedchar *chardata) +{ + float scale; + int x,y,bottom_y, i; + stbtt_fontinfo f; + f.userdata = NULL; + if (!stbtt_InitFont(&f, data, offset)) + return -1; + STBTT_memset(pixels, 0, pw*ph); // background of 0 around pixels + x=y=1; + bottom_y = 1; + + scale = stbtt_ScaleForPixelHeight(&f, pixel_height); + + for (i=0; i < num_chars; ++i) { + int advance, lsb, x0,y0,x1,y1,gw,gh; + int g = stbtt_FindGlyphIndex(&f, first_char + i); + stbtt_GetGlyphHMetrics(&f, g, &advance, &lsb); + stbtt_GetGlyphBitmapBox(&f, g, scale,scale, &x0,&y0,&x1,&y1); + gw = x1-x0; + gh = y1-y0; + if (x + gw + 1 >= pw) + y = bottom_y, x = 1; // advance to next row + if (y + gh + 1 >= ph) // check if it fits vertically AFTER potentially moving to next row + return -i; + STBTT_assert(x+gw < pw); + STBTT_assert(y+gh < ph); + stbtt_MakeGlyphBitmap(&f, pixels+x+y*pw, gw,gh,pw, scale,scale, g); + chardata[i].x0 = (stbtt_int16) x; + chardata[i].y0 = (stbtt_int16) y; + chardata[i].x1 = (stbtt_int16) (x + gw); + chardata[i].y1 = (stbtt_int16) (y + gh); + chardata[i].xadvance = scale * advance; + chardata[i].xoff = (float) x0; + chardata[i].yoff = (float) y0; + x = x + gw + 1; + if (y+gh+1 > bottom_y) + bottom_y = y+gh+1; + } + return bottom_y; +} + +STBTT_DEF void stbtt_GetBakedQuad(const stbtt_bakedchar *chardata, int pw, int ph, int char_index, float *xpos, float *ypos, stbtt_aligned_quad *q, int opengl_fillrule) +{ + float d3d_bias = opengl_fillrule ? 0 : -0.5f; + float ipw = 1.0f / pw, iph = 1.0f / ph; + const stbtt_bakedchar *b = chardata + char_index; + int round_x = STBTT_ifloor((*xpos + b->xoff) + 0.5f); + int round_y = STBTT_ifloor((*ypos + b->yoff) + 0.5f); + + q->x0 = round_x + d3d_bias; + q->y0 = round_y + d3d_bias; + q->x1 = round_x + b->x1 - b->x0 + d3d_bias; + q->y1 = round_y + b->y1 - b->y0 + d3d_bias; + + q->s0 = b->x0 * ipw; + q->t0 = b->y0 * iph; + q->s1 = b->x1 * ipw; + q->t1 = b->y1 * iph; + + *xpos += b->xadvance; +} + +////////////////////////////////////////////////////////////////////////////// +// +// rectangle packing replacement routines if you don't have stb_rect_pack.h +// + +#ifndef STB_RECT_PACK_VERSION + +typedef int stbrp_coord; + +//////////////////////////////////////////////////////////////////////////////////// +// // +// // +// COMPILER WARNING ?!?!? // +// // +// // +// if you get a compile warning due to these symbols being defined more than // +// once, move #include "stb_rect_pack.h" before #include "stb_truetype.h" // +// // +//////////////////////////////////////////////////////////////////////////////////// + +typedef struct +{ + int width,height; + int x,y,bottom_y; +} stbrp_context; + +typedef struct +{ + unsigned char x; +} stbrp_node; + +struct stbrp_rect +{ + stbrp_coord x,y; + int id,w,h,was_packed; +}; + +static void stbrp_init_target(stbrp_context *con, int pw, int ph, stbrp_node *nodes, int num_nodes) +{ + con->width = pw; + con->height = ph; + con->x = 0; + con->y = 0; + con->bottom_y = 0; + STBTT__NOTUSED(nodes); + STBTT__NOTUSED(num_nodes); +} + +static void stbrp_pack_rects(stbrp_context *con, stbrp_rect *rects, int num_rects) +{ + int i; + for (i=0; i < num_rects; ++i) { + if (con->x + rects[i].w > con->width) { + con->x = 0; + con->y = con->bottom_y; + } + if (con->y + rects[i].h > con->height) + break; + rects[i].x = con->x; + rects[i].y = con->y; + rects[i].was_packed = 1; + con->x += rects[i].w; + if (con->y + rects[i].h > con->bottom_y) + con->bottom_y = con->y + rects[i].h; + } + for ( ; i < num_rects; ++i) + rects[i].was_packed = 0; +} +#endif + +////////////////////////////////////////////////////////////////////////////// +// +// bitmap baking +// +// This is SUPER-AWESOME (tm Ryan Gordon) packing using stb_rect_pack.h. If +// stb_rect_pack.h isn't available, it uses the BakeFontBitmap strategy. + +STBTT_DEF int stbtt_PackBegin(stbtt_pack_context *spc, unsigned char *pixels, int pw, int ph, int stride_in_bytes, int padding, void *alloc_context) +{ + stbrp_context *context = (stbrp_context *) STBTT_malloc(sizeof(*context) ,alloc_context); + int num_nodes = pw - padding; + stbrp_node *nodes = (stbrp_node *) STBTT_malloc(sizeof(*nodes ) * num_nodes,alloc_context); + + if (context == NULL || nodes == NULL) { + if (context != NULL) STBTT_free(context, alloc_context); + if (nodes != NULL) STBTT_free(nodes , alloc_context); + return 0; + } + + spc->user_allocator_context = alloc_context; + spc->width = pw; + spc->height = ph; + spc->pixels = pixels; + spc->pack_info = context; + spc->nodes = nodes; + spc->padding = padding; + spc->stride_in_bytes = stride_in_bytes != 0 ? stride_in_bytes : pw; + spc->h_oversample = 1; + spc->v_oversample = 1; + spc->skip_missing = 0; + + stbrp_init_target(context, pw-padding, ph-padding, nodes, num_nodes); + + if (pixels) + STBTT_memset(pixels, 0, pw*ph); // background of 0 around pixels + + return 1; +} + +STBTT_DEF void stbtt_PackEnd (stbtt_pack_context *spc) +{ + STBTT_free(spc->nodes , spc->user_allocator_context); + STBTT_free(spc->pack_info, spc->user_allocator_context); +} + +STBTT_DEF void stbtt_PackSetOversampling(stbtt_pack_context *spc, unsigned int h_oversample, unsigned int v_oversample) +{ + STBTT_assert(h_oversample <= STBTT_MAX_OVERSAMPLE); + STBTT_assert(v_oversample <= STBTT_MAX_OVERSAMPLE); + if (h_oversample <= STBTT_MAX_OVERSAMPLE) + spc->h_oversample = h_oversample; + if (v_oversample <= STBTT_MAX_OVERSAMPLE) + spc->v_oversample = v_oversample; +} + +STBTT_DEF void stbtt_PackSetSkipMissingCodepoints(stbtt_pack_context *spc, int skip) +{ + spc->skip_missing = skip; +} + +#define STBTT__OVER_MASK (STBTT_MAX_OVERSAMPLE-1) + +static void stbtt__h_prefilter(unsigned char *pixels, int w, int h, int stride_in_bytes, unsigned int kernel_width) +{ + unsigned char buffer[STBTT_MAX_OVERSAMPLE]; + int safe_w = w - kernel_width; + int j; + STBTT_memset(buffer, 0, STBTT_MAX_OVERSAMPLE); // suppress bogus warning from VS2013 -analyze + for (j=0; j < h; ++j) { + int i; + unsigned int total; + STBTT_memset(buffer, 0, kernel_width); + + total = 0; + + // make kernel_width a constant in common cases so compiler can optimize out the divide + switch (kernel_width) { + case 2: + for (i=0; i <= safe_w; ++i) { + total += pixels[i] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i]; + pixels[i] = (unsigned char) (total / 2); + } + break; + case 3: + for (i=0; i <= safe_w; ++i) { + total += pixels[i] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i]; + pixels[i] = (unsigned char) (total / 3); + } + break; + case 4: + for (i=0; i <= safe_w; ++i) { + total += pixels[i] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i]; + pixels[i] = (unsigned char) (total / 4); + } + break; + case 5: + for (i=0; i <= safe_w; ++i) { + total += pixels[i] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i]; + pixels[i] = (unsigned char) (total / 5); + } + break; + default: + for (i=0; i <= safe_w; ++i) { + total += pixels[i] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i]; + pixels[i] = (unsigned char) (total / kernel_width); + } + break; + } + + for (; i < w; ++i) { + STBTT_assert(pixels[i] == 0); + total -= buffer[i & STBTT__OVER_MASK]; + pixels[i] = (unsigned char) (total / kernel_width); + } + + pixels += stride_in_bytes; + } +} + +static void stbtt__v_prefilter(unsigned char *pixels, int w, int h, int stride_in_bytes, unsigned int kernel_width) +{ + unsigned char buffer[STBTT_MAX_OVERSAMPLE]; + int safe_h = h - kernel_width; + int j; + STBTT_memset(buffer, 0, STBTT_MAX_OVERSAMPLE); // suppress bogus warning from VS2013 -analyze + for (j=0; j < w; ++j) { + int i; + unsigned int total; + STBTT_memset(buffer, 0, kernel_width); + + total = 0; + + // make kernel_width a constant in common cases so compiler can optimize out the divide + switch (kernel_width) { + case 2: + for (i=0; i <= safe_h; ++i) { + total += pixels[i*stride_in_bytes] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i*stride_in_bytes]; + pixels[i*stride_in_bytes] = (unsigned char) (total / 2); + } + break; + case 3: + for (i=0; i <= safe_h; ++i) { + total += pixels[i*stride_in_bytes] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i*stride_in_bytes]; + pixels[i*stride_in_bytes] = (unsigned char) (total / 3); + } + break; + case 4: + for (i=0; i <= safe_h; ++i) { + total += pixels[i*stride_in_bytes] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i*stride_in_bytes]; + pixels[i*stride_in_bytes] = (unsigned char) (total / 4); + } + break; + case 5: + for (i=0; i <= safe_h; ++i) { + total += pixels[i*stride_in_bytes] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i*stride_in_bytes]; + pixels[i*stride_in_bytes] = (unsigned char) (total / 5); + } + break; + default: + for (i=0; i <= safe_h; ++i) { + total += pixels[i*stride_in_bytes] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i*stride_in_bytes]; + pixels[i*stride_in_bytes] = (unsigned char) (total / kernel_width); + } + break; + } + + for (; i < h; ++i) { + STBTT_assert(pixels[i*stride_in_bytes] == 0); + total -= buffer[i & STBTT__OVER_MASK]; + pixels[i*stride_in_bytes] = (unsigned char) (total / kernel_width); + } + + pixels += 1; + } +} + +static float stbtt__oversample_shift(int oversample) +{ + if (!oversample) + return 0.0f; + + // The prefilter is a box filter of width "oversample", + // which shifts phase by (oversample - 1)/2 pixels in + // oversampled space. We want to shift in the opposite + // direction to counter this. + return (float)-(oversample - 1) / (2.0f * (float)oversample); +} + +// rects array must be big enough to accommodate all characters in the given ranges +STBTT_DEF int stbtt_PackFontRangesGatherRects(stbtt_pack_context *spc, const stbtt_fontinfo *info, stbtt_pack_range *ranges, int num_ranges, stbrp_rect *rects) +{ + int i,j,k; + int missing_glyph_added = 0; + + k=0; + for (i=0; i < num_ranges; ++i) { + float fh = ranges[i].font_size; + float scale = fh > 0 ? stbtt_ScaleForPixelHeight(info, fh) : stbtt_ScaleForMappingEmToPixels(info, -fh); + ranges[i].h_oversample = (unsigned char) spc->h_oversample; + ranges[i].v_oversample = (unsigned char) spc->v_oversample; + for (j=0; j < ranges[i].num_chars; ++j) { + int x0,y0,x1,y1; + int codepoint = ranges[i].array_of_unicode_codepoints == NULL ? ranges[i].first_unicode_codepoint_in_range + j : ranges[i].array_of_unicode_codepoints[j]; + int glyph = stbtt_FindGlyphIndex(info, codepoint); + if (glyph == 0 && (spc->skip_missing || missing_glyph_added)) { + rects[k].w = rects[k].h = 0; + } else { + stbtt_GetGlyphBitmapBoxSubpixel(info,glyph, + scale * spc->h_oversample, + scale * spc->v_oversample, + 0,0, + &x0,&y0,&x1,&y1); + rects[k].w = (stbrp_coord) (x1-x0 + spc->padding + spc->h_oversample-1); + rects[k].h = (stbrp_coord) (y1-y0 + spc->padding + spc->v_oversample-1); + if (glyph == 0) + missing_glyph_added = 1; + } + ++k; + } + } + + return k; +} + +STBTT_DEF void stbtt_MakeGlyphBitmapSubpixelPrefilter(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int prefilter_x, int prefilter_y, float *sub_x, float *sub_y, int glyph) +{ + stbtt_MakeGlyphBitmapSubpixel(info, + output, + out_w - (prefilter_x - 1), + out_h - (prefilter_y - 1), + out_stride, + scale_x, + scale_y, + shift_x, + shift_y, + glyph); + + if (prefilter_x > 1) + stbtt__h_prefilter(output, out_w, out_h, out_stride, prefilter_x); + + if (prefilter_y > 1) + stbtt__v_prefilter(output, out_w, out_h, out_stride, prefilter_y); + + *sub_x = stbtt__oversample_shift(prefilter_x); + *sub_y = stbtt__oversample_shift(prefilter_y); +} + +// rects array must be big enough to accommodate all characters in the given ranges +STBTT_DEF int stbtt_PackFontRangesRenderIntoRects(stbtt_pack_context *spc, const stbtt_fontinfo *info, stbtt_pack_range *ranges, int num_ranges, stbrp_rect *rects) +{ + int i,j,k, missing_glyph = -1, return_value = 1; + + // save current values + int old_h_over = spc->h_oversample; + int old_v_over = spc->v_oversample; + + k = 0; + for (i=0; i < num_ranges; ++i) { + float fh = ranges[i].font_size; + float scale = fh > 0 ? stbtt_ScaleForPixelHeight(info, fh) : stbtt_ScaleForMappingEmToPixels(info, -fh); + float recip_h,recip_v,sub_x,sub_y; + spc->h_oversample = ranges[i].h_oversample; + spc->v_oversample = ranges[i].v_oversample; + recip_h = 1.0f / spc->h_oversample; + recip_v = 1.0f / spc->v_oversample; + sub_x = stbtt__oversample_shift(spc->h_oversample); + sub_y = stbtt__oversample_shift(spc->v_oversample); + for (j=0; j < ranges[i].num_chars; ++j) { + stbrp_rect *r = &rects[k]; + if (r->was_packed && r->w != 0 && r->h != 0) { + stbtt_packedchar *bc = &ranges[i].chardata_for_range[j]; + int advance, lsb, x0,y0,x1,y1; + int codepoint = ranges[i].array_of_unicode_codepoints == NULL ? ranges[i].first_unicode_codepoint_in_range + j : ranges[i].array_of_unicode_codepoints[j]; + int glyph = stbtt_FindGlyphIndex(info, codepoint); + stbrp_coord pad = (stbrp_coord) spc->padding; + + // pad on left and top + r->x += pad; + r->y += pad; + r->w -= pad; + r->h -= pad; + stbtt_GetGlyphHMetrics(info, glyph, &advance, &lsb); + stbtt_GetGlyphBitmapBox(info, glyph, + scale * spc->h_oversample, + scale * spc->v_oversample, + &x0,&y0,&x1,&y1); + stbtt_MakeGlyphBitmapSubpixel(info, + spc->pixels + r->x + r->y*spc->stride_in_bytes, + r->w - spc->h_oversample+1, + r->h - spc->v_oversample+1, + spc->stride_in_bytes, + scale * spc->h_oversample, + scale * spc->v_oversample, + 0,0, + glyph); + + if (spc->h_oversample > 1) + stbtt__h_prefilter(spc->pixels + r->x + r->y*spc->stride_in_bytes, + r->w, r->h, spc->stride_in_bytes, + spc->h_oversample); + + if (spc->v_oversample > 1) + stbtt__v_prefilter(spc->pixels + r->x + r->y*spc->stride_in_bytes, + r->w, r->h, spc->stride_in_bytes, + spc->v_oversample); + + bc->x0 = (stbtt_int16) r->x; + bc->y0 = (stbtt_int16) r->y; + bc->x1 = (stbtt_int16) (r->x + r->w); + bc->y1 = (stbtt_int16) (r->y + r->h); + bc->xadvance = scale * advance; + bc->xoff = (float) x0 * recip_h + sub_x; + bc->yoff = (float) y0 * recip_v + sub_y; + bc->xoff2 = (x0 + r->w) * recip_h + sub_x; + bc->yoff2 = (y0 + r->h) * recip_v + sub_y; + + if (glyph == 0) + missing_glyph = j; + } else if (spc->skip_missing) { + return_value = 0; + } else if (r->was_packed && r->w == 0 && r->h == 0 && missing_glyph >= 0) { + ranges[i].chardata_for_range[j] = ranges[i].chardata_for_range[missing_glyph]; + } else { + return_value = 0; // if any fail, report failure + } + + ++k; + } + } + + // restore original values + spc->h_oversample = old_h_over; + spc->v_oversample = old_v_over; + + return return_value; +} + +STBTT_DEF void stbtt_PackFontRangesPackRects(stbtt_pack_context *spc, stbrp_rect *rects, int num_rects) +{ + stbrp_pack_rects((stbrp_context *) spc->pack_info, rects, num_rects); +} + +STBTT_DEF int stbtt_PackFontRanges(stbtt_pack_context *spc, const unsigned char *fontdata, int font_index, stbtt_pack_range *ranges, int num_ranges) +{ + stbtt_fontinfo info; + int i,j,n, return_value = 1; + //stbrp_context *context = (stbrp_context *) spc->pack_info; + stbrp_rect *rects; + + // flag all characters as NOT packed + for (i=0; i < num_ranges; ++i) + for (j=0; j < ranges[i].num_chars; ++j) + ranges[i].chardata_for_range[j].x0 = + ranges[i].chardata_for_range[j].y0 = + ranges[i].chardata_for_range[j].x1 = + ranges[i].chardata_for_range[j].y1 = 0; + + n = 0; + for (i=0; i < num_ranges; ++i) + n += ranges[i].num_chars; + + rects = (stbrp_rect *) STBTT_malloc(sizeof(*rects) * n, spc->user_allocator_context); + if (rects == NULL) + return 0; + + info.userdata = spc->user_allocator_context; + stbtt_InitFont(&info, fontdata, stbtt_GetFontOffsetForIndex(fontdata,font_index)); + + n = stbtt_PackFontRangesGatherRects(spc, &info, ranges, num_ranges, rects); + + stbtt_PackFontRangesPackRects(spc, rects, n); + + return_value = stbtt_PackFontRangesRenderIntoRects(spc, &info, ranges, num_ranges, rects); + + STBTT_free(rects, spc->user_allocator_context); + return return_value; +} + +STBTT_DEF int stbtt_PackFontRange(stbtt_pack_context *spc, const unsigned char *fontdata, int font_index, float font_size, + int first_unicode_codepoint_in_range, int num_chars_in_range, stbtt_packedchar *chardata_for_range) +{ + stbtt_pack_range range; + range.first_unicode_codepoint_in_range = first_unicode_codepoint_in_range; + range.array_of_unicode_codepoints = NULL; + range.num_chars = num_chars_in_range; + range.chardata_for_range = chardata_for_range; + range.font_size = font_size; + return stbtt_PackFontRanges(spc, fontdata, font_index, &range, 1); +} + +STBTT_DEF void stbtt_GetScaledFontVMetrics(const unsigned char *fontdata, int index, float size, float *ascent, float *descent, float *lineGap) +{ + int i_ascent, i_descent, i_lineGap; + float scale; + stbtt_fontinfo info; + stbtt_InitFont(&info, fontdata, stbtt_GetFontOffsetForIndex(fontdata, index)); + scale = size > 0 ? stbtt_ScaleForPixelHeight(&info, size) : stbtt_ScaleForMappingEmToPixels(&info, -size); + stbtt_GetFontVMetrics(&info, &i_ascent, &i_descent, &i_lineGap); + *ascent = (float) i_ascent * scale; + *descent = (float) i_descent * scale; + *lineGap = (float) i_lineGap * scale; +} + +STBTT_DEF void stbtt_GetPackedQuad(const stbtt_packedchar *chardata, int pw, int ph, int char_index, float *xpos, float *ypos, stbtt_aligned_quad *q, int align_to_integer) +{ + float ipw = 1.0f / pw, iph = 1.0f / ph; + const stbtt_packedchar *b = chardata + char_index; + + if (align_to_integer) { + float x = (float) STBTT_ifloor((*xpos + b->xoff) + 0.5f); + float y = (float) STBTT_ifloor((*ypos + b->yoff) + 0.5f); + q->x0 = x; + q->y0 = y; + q->x1 = x + b->xoff2 - b->xoff; + q->y1 = y + b->yoff2 - b->yoff; + } else { + q->x0 = *xpos + b->xoff; + q->y0 = *ypos + b->yoff; + q->x1 = *xpos + b->xoff2; + q->y1 = *ypos + b->yoff2; + } + + q->s0 = b->x0 * ipw; + q->t0 = b->y0 * iph; + q->s1 = b->x1 * ipw; + q->t1 = b->y1 * iph; + + *xpos += b->xadvance; +} + +////////////////////////////////////////////////////////////////////////////// +// +// sdf computation +// + +#define STBTT_min(a,b) ((a) < (b) ? (a) : (b)) +#define STBTT_max(a,b) ((a) < (b) ? (b) : (a)) + +static int stbtt__ray_intersect_bezier(float orig[2], float ray[2], float q0[2], float q1[2], float q2[2], float hits[2][2]) +{ + float q0perp = q0[1]*ray[0] - q0[0]*ray[1]; + float q1perp = q1[1]*ray[0] - q1[0]*ray[1]; + float q2perp = q2[1]*ray[0] - q2[0]*ray[1]; + float roperp = orig[1]*ray[0] - orig[0]*ray[1]; + + float a = q0perp - 2*q1perp + q2perp; + float b = q1perp - q0perp; + float c = q0perp - roperp; + + float s0 = 0., s1 = 0.; + int num_s = 0; + + if (a != 0.0) { + float discr = b*b - a*c; + if (discr > 0.0) { + float rcpna = -1 / a; + float d = (float) STBTT_sqrt(discr); + s0 = (b+d) * rcpna; + s1 = (b-d) * rcpna; + if (s0 >= 0.0 && s0 <= 1.0) + num_s = 1; + if (d > 0.0 && s1 >= 0.0 && s1 <= 1.0) { + if (num_s == 0) s0 = s1; + ++num_s; + } + } + } else { + // 2*b*s + c = 0 + // s = -c / (2*b) + s0 = c / (-2 * b); + if (s0 >= 0.0 && s0 <= 1.0) + num_s = 1; + } + + if (num_s == 0) + return 0; + else { + float rcp_len2 = 1 / (ray[0]*ray[0] + ray[1]*ray[1]); + float rayn_x = ray[0] * rcp_len2, rayn_y = ray[1] * rcp_len2; + + float q0d = q0[0]*rayn_x + q0[1]*rayn_y; + float q1d = q1[0]*rayn_x + q1[1]*rayn_y; + float q2d = q2[0]*rayn_x + q2[1]*rayn_y; + float rod = orig[0]*rayn_x + orig[1]*rayn_y; + + float q10d = q1d - q0d; + float q20d = q2d - q0d; + float q0rd = q0d - rod; + + hits[0][0] = q0rd + s0*(2.0f - 2.0f*s0)*q10d + s0*s0*q20d; + hits[0][1] = a*s0+b; + + if (num_s > 1) { + hits[1][0] = q0rd + s1*(2.0f - 2.0f*s1)*q10d + s1*s1*q20d; + hits[1][1] = a*s1+b; + return 2; + } else { + return 1; + } + } +} + +static int equal(float *a, float *b) +{ + return (a[0] == b[0] && a[1] == b[1]); +} + +static int stbtt__compute_crossings_x(float x, float y, int nverts, stbtt_vertex *verts) +{ + int i; + float orig[2], ray[2] = { 1, 0 }; + float y_frac; + int winding = 0; + + orig[0] = x; + orig[1] = y; + + // make sure y never passes through a vertex of the shape + y_frac = (float) STBTT_fmod(y, 1.0f); + if (y_frac < 0.01f) + y += 0.01f; + else if (y_frac > 0.99f) + y -= 0.01f; + orig[1] = y; + + // test a ray from (-infinity,y) to (x,y) + for (i=0; i < nverts; ++i) { + if (verts[i].type == STBTT_vline) { + int x0 = (int) verts[i-1].x, y0 = (int) verts[i-1].y; + int x1 = (int) verts[i ].x, y1 = (int) verts[i ].y; + if (y > STBTT_min(y0,y1) && y < STBTT_max(y0,y1) && x > STBTT_min(x0,x1)) { + float x_inter = (y - y0) / (y1 - y0) * (x1-x0) + x0; + if (x_inter < x) + winding += (y0 < y1) ? 1 : -1; + } + } + if (verts[i].type == STBTT_vcurve) { + int x0 = (int) verts[i-1].x , y0 = (int) verts[i-1].y ; + int x1 = (int) verts[i ].cx, y1 = (int) verts[i ].cy; + int x2 = (int) verts[i ].x , y2 = (int) verts[i ].y ; + int ax = STBTT_min(x0,STBTT_min(x1,x2)), ay = STBTT_min(y0,STBTT_min(y1,y2)); + int by = STBTT_max(y0,STBTT_max(y1,y2)); + if (y > ay && y < by && x > ax) { + float q0[2],q1[2],q2[2]; + float hits[2][2]; + q0[0] = (float)x0; + q0[1] = (float)y0; + q1[0] = (float)x1; + q1[1] = (float)y1; + q2[0] = (float)x2; + q2[1] = (float)y2; + if (equal(q0,q1) || equal(q1,q2)) { + x0 = (int)verts[i-1].x; + y0 = (int)verts[i-1].y; + x1 = (int)verts[i ].x; + y1 = (int)verts[i ].y; + if (y > STBTT_min(y0,y1) && y < STBTT_max(y0,y1) && x > STBTT_min(x0,x1)) { + float x_inter = (y - y0) / (y1 - y0) * (x1-x0) + x0; + if (x_inter < x) + winding += (y0 < y1) ? 1 : -1; + } + } else { + int num_hits = stbtt__ray_intersect_bezier(orig, ray, q0, q1, q2, hits); + if (num_hits >= 1) + if (hits[0][0] < 0) + winding += (hits[0][1] < 0 ? -1 : 1); + if (num_hits >= 2) + if (hits[1][0] < 0) + winding += (hits[1][1] < 0 ? -1 : 1); + } + } + } + } + return winding; +} + +static float stbtt__cuberoot( float x ) +{ + if (x<0) + return -(float) STBTT_pow(-x,1.0f/3.0f); + else + return (float) STBTT_pow( x,1.0f/3.0f); +} + +// x^3 + c*x^2 + b*x + a = 0 +static int stbtt__solve_cubic(float a, float b, float c, float* r) +{ + float s = -a / 3; + float p = b - a*a / 3; + float q = a * (2*a*a - 9*b) / 27 + c; + float p3 = p*p*p; + float d = q*q + 4*p3 / 27; + if (d >= 0) { + float z = (float) STBTT_sqrt(d); + float u = (-q + z) / 2; + float v = (-q - z) / 2; + u = stbtt__cuberoot(u); + v = stbtt__cuberoot(v); + r[0] = s + u + v; + return 1; + } else { + float u = (float) STBTT_sqrt(-p/3); + float v = (float) STBTT_acos(-STBTT_sqrt(-27/p3) * q / 2) / 3; // p3 must be negative, since d is negative + float m = (float) STBTT_cos(v); + float n = (float) STBTT_cos(v-3.141592/2)*1.732050808f; + r[0] = s + u * 2 * m; + r[1] = s - u * (m + n); + r[2] = s - u * (m - n); + + //STBTT_assert( STBTT_fabs(((r[0]+a)*r[0]+b)*r[0]+c) < 0.05f); // these asserts may not be safe at all scales, though they're in bezier t parameter units so maybe? + //STBTT_assert( STBTT_fabs(((r[1]+a)*r[1]+b)*r[1]+c) < 0.05f); + //STBTT_assert( STBTT_fabs(((r[2]+a)*r[2]+b)*r[2]+c) < 0.05f); + return 3; + } +} + +STBTT_DEF unsigned char * stbtt_GetGlyphSDF(const stbtt_fontinfo *info, float scale, int glyph, int padding, unsigned char onedge_value, float pixel_dist_scale, int *width, int *height, int *xoff, int *yoff) +{ + float scale_x = scale, scale_y = scale; + int ix0,iy0,ix1,iy1; + int w,h; + unsigned char *data; + + if (scale == 0) return NULL; + + stbtt_GetGlyphBitmapBoxSubpixel(info, glyph, scale, scale, 0.0f,0.0f, &ix0,&iy0,&ix1,&iy1); + + // if empty, return NULL + if (ix0 == ix1 || iy0 == iy1) + return NULL; + + ix0 -= padding; + iy0 -= padding; + ix1 += padding; + iy1 += padding; + + w = (ix1 - ix0); + h = (iy1 - iy0); + + if (width ) *width = w; + if (height) *height = h; + if (xoff ) *xoff = ix0; + if (yoff ) *yoff = iy0; + + // invert for y-downwards bitmaps + scale_y = -scale_y; + + { + int x,y,i,j; + float *precompute; + stbtt_vertex *verts; + int num_verts = stbtt_GetGlyphShape(info, glyph, &verts); + data = (unsigned char *) STBTT_malloc(w * h, info->userdata); + precompute = (float *) STBTT_malloc(num_verts * sizeof(float), info->userdata); + + for (i=0,j=num_verts-1; i < num_verts; j=i++) { + if (verts[i].type == STBTT_vline) { + float x0 = verts[i].x*scale_x, y0 = verts[i].y*scale_y; + float x1 = verts[j].x*scale_x, y1 = verts[j].y*scale_y; + float dist = (float) STBTT_sqrt((x1-x0)*(x1-x0) + (y1-y0)*(y1-y0)); + precompute[i] = (dist == 0) ? 0.0f : 1.0f / dist; + } else if (verts[i].type == STBTT_vcurve) { + float x2 = verts[j].x *scale_x, y2 = verts[j].y *scale_y; + float x1 = verts[i].cx*scale_x, y1 = verts[i].cy*scale_y; + float x0 = verts[i].x *scale_x, y0 = verts[i].y *scale_y; + float bx = x0 - 2*x1 + x2, by = y0 - 2*y1 + y2; + float len2 = bx*bx + by*by; + if (len2 != 0.0f) + precompute[i] = 1.0f / (bx*bx + by*by); + else + precompute[i] = 0.0f; + } else + precompute[i] = 0.0f; + } + + for (y=iy0; y < iy1; ++y) { + for (x=ix0; x < ix1; ++x) { + float val; + float min_dist = 999999.0f; + float sx = (float) x + 0.5f; + float sy = (float) y + 0.5f; + float x_gspace = (sx / scale_x); + float y_gspace = (sy / scale_y); + + int winding = stbtt__compute_crossings_x(x_gspace, y_gspace, num_verts, verts); // @OPTIMIZE: this could just be a rasterization, but needs to be line vs. non-tesselated curves so a new path + + for (i=0; i < num_verts; ++i) { + float x0 = verts[i].x*scale_x, y0 = verts[i].y*scale_y; + + // check against every point here rather than inside line/curve primitives -- @TODO: wrong if multiple 'moves' in a row produce a garbage point, and given culling, probably more efficient to do within line/curve + float dist2 = (x0-sx)*(x0-sx) + (y0-sy)*(y0-sy); + if (dist2 < min_dist*min_dist) + min_dist = (float) STBTT_sqrt(dist2); + + if (verts[i].type == STBTT_vline) { + float x1 = verts[i-1].x*scale_x, y1 = verts[i-1].y*scale_y; + + // coarse culling against bbox + //if (sx > STBTT_min(x0,x1)-min_dist && sx < STBTT_max(x0,x1)+min_dist && + // sy > STBTT_min(y0,y1)-min_dist && sy < STBTT_max(y0,y1)+min_dist) + float dist = (float) STBTT_fabs((x1-x0)*(y0-sy) - (y1-y0)*(x0-sx)) * precompute[i]; + STBTT_assert(i != 0); + if (dist < min_dist) { + // check position along line + // x' = x0 + t*(x1-x0), y' = y0 + t*(y1-y0) + // minimize (x'-sx)*(x'-sx)+(y'-sy)*(y'-sy) + float dx = x1-x0, dy = y1-y0; + float px = x0-sx, py = y0-sy; + // minimize (px+t*dx)^2 + (py+t*dy)^2 = px*px + 2*px*dx*t + t^2*dx*dx + py*py + 2*py*dy*t + t^2*dy*dy + // derivative: 2*px*dx + 2*py*dy + (2*dx*dx+2*dy*dy)*t, set to 0 and solve + float t = -(px*dx + py*dy) / (dx*dx + dy*dy); + if (t >= 0.0f && t <= 1.0f) + min_dist = dist; + } + } else if (verts[i].type == STBTT_vcurve) { + float x2 = verts[i-1].x *scale_x, y2 = verts[i-1].y *scale_y; + float x1 = verts[i ].cx*scale_x, y1 = verts[i ].cy*scale_y; + float box_x0 = STBTT_min(STBTT_min(x0,x1),x2); + float box_y0 = STBTT_min(STBTT_min(y0,y1),y2); + float box_x1 = STBTT_max(STBTT_max(x0,x1),x2); + float box_y1 = STBTT_max(STBTT_max(y0,y1),y2); + // coarse culling against bbox to avoid computing cubic unnecessarily + if (sx > box_x0-min_dist && sx < box_x1+min_dist && sy > box_y0-min_dist && sy < box_y1+min_dist) { + int num=0; + float ax = x1-x0, ay = y1-y0; + float bx = x0 - 2*x1 + x2, by = y0 - 2*y1 + y2; + float mx = x0 - sx, my = y0 - sy; + float res[3],px,py,t,it; + float a_inv = precompute[i]; + if (a_inv == 0.0) { // if a_inv is 0, it's 2nd degree so use quadratic formula + float a = 3*(ax*bx + ay*by); + float b = 2*(ax*ax + ay*ay) + (mx*bx+my*by); + float c = mx*ax+my*ay; + if (a == 0.0) { // if a is 0, it's linear + if (b != 0.0) { + res[num++] = -c/b; + } + } else { + float discriminant = b*b - 4*a*c; + if (discriminant < 0) + num = 0; + else { + float root = (float) STBTT_sqrt(discriminant); + res[0] = (-b - root)/(2*a); + res[1] = (-b + root)/(2*a); + num = 2; // don't bother distinguishing 1-solution case, as code below will still work + } + } + } else { + float b = 3*(ax*bx + ay*by) * a_inv; // could precompute this as it doesn't depend on sample point + float c = (2*(ax*ax + ay*ay) + (mx*bx+my*by)) * a_inv; + float d = (mx*ax+my*ay) * a_inv; + num = stbtt__solve_cubic(b, c, d, res); + } + if (num >= 1 && res[0] >= 0.0f && res[0] <= 1.0f) { + t = res[0], it = 1.0f - t; + px = it*it*x0 + 2*t*it*x1 + t*t*x2; + py = it*it*y0 + 2*t*it*y1 + t*t*y2; + dist2 = (px-sx)*(px-sx) + (py-sy)*(py-sy); + if (dist2 < min_dist * min_dist) + min_dist = (float) STBTT_sqrt(dist2); + } + if (num >= 2 && res[1] >= 0.0f && res[1] <= 1.0f) { + t = res[1], it = 1.0f - t; + px = it*it*x0 + 2*t*it*x1 + t*t*x2; + py = it*it*y0 + 2*t*it*y1 + t*t*y2; + dist2 = (px-sx)*(px-sx) + (py-sy)*(py-sy); + if (dist2 < min_dist * min_dist) + min_dist = (float) STBTT_sqrt(dist2); + } + if (num >= 3 && res[2] >= 0.0f && res[2] <= 1.0f) { + t = res[2], it = 1.0f - t; + px = it*it*x0 + 2*t*it*x1 + t*t*x2; + py = it*it*y0 + 2*t*it*y1 + t*t*y2; + dist2 = (px-sx)*(px-sx) + (py-sy)*(py-sy); + if (dist2 < min_dist * min_dist) + min_dist = (float) STBTT_sqrt(dist2); + } + } + } + } + if (winding == 0) + min_dist = -min_dist; // if outside the shape, value is negative + val = onedge_value + pixel_dist_scale * min_dist; + if (val < 0) + val = 0; + else if (val > 255) + val = 255; + data[(y-iy0)*w+(x-ix0)] = (unsigned char) val; + } + } + STBTT_free(precompute, info->userdata); + STBTT_free(verts, info->userdata); + } + return data; +} + +STBTT_DEF unsigned char * stbtt_GetCodepointSDF(const stbtt_fontinfo *info, float scale, int codepoint, int padding, unsigned char onedge_value, float pixel_dist_scale, int *width, int *height, int *xoff, int *yoff) +{ + return stbtt_GetGlyphSDF(info, scale, stbtt_FindGlyphIndex(info, codepoint), padding, onedge_value, pixel_dist_scale, width, height, xoff, yoff); +} + +STBTT_DEF void stbtt_FreeSDF(unsigned char *bitmap, void *userdata) +{ + STBTT_free(bitmap, userdata); +} + +////////////////////////////////////////////////////////////////////////////// +// +// font name matching -- recommended not to use this +// + +// check if a utf8 string contains a prefix which is the utf16 string; if so return length of matching utf8 string +static stbtt_int32 stbtt__CompareUTF8toUTF16_bigendian_prefix(stbtt_uint8 *s1, stbtt_int32 len1, stbtt_uint8 *s2, stbtt_int32 len2) +{ + stbtt_int32 i=0; + + // convert utf16 to utf8 and compare the results while converting + while (len2) { + stbtt_uint16 ch = s2[0]*256 + s2[1]; + if (ch < 0x80) { + if (i >= len1) return -1; + if (s1[i++] != ch) return -1; + } else if (ch < 0x800) { + if (i+1 >= len1) return -1; + if (s1[i++] != 0xc0 + (ch >> 6)) return -1; + if (s1[i++] != 0x80 + (ch & 0x3f)) return -1; + } else if (ch >= 0xd800 && ch < 0xdc00) { + stbtt_uint32 c; + stbtt_uint16 ch2 = s2[2]*256 + s2[3]; + if (i+3 >= len1) return -1; + c = ((ch - 0xd800) << 10) + (ch2 - 0xdc00) + 0x10000; + if (s1[i++] != 0xf0 + (c >> 18)) return -1; + if (s1[i++] != 0x80 + ((c >> 12) & 0x3f)) return -1; + if (s1[i++] != 0x80 + ((c >> 6) & 0x3f)) return -1; + if (s1[i++] != 0x80 + ((c ) & 0x3f)) return -1; + s2 += 2; // plus another 2 below + len2 -= 2; + } else if (ch >= 0xdc00 && ch < 0xe000) { + return -1; + } else { + if (i+2 >= len1) return -1; + if (s1[i++] != 0xe0 + (ch >> 12)) return -1; + if (s1[i++] != 0x80 + ((ch >> 6) & 0x3f)) return -1; + if (s1[i++] != 0x80 + ((ch ) & 0x3f)) return -1; + } + s2 += 2; + len2 -= 2; + } + return i; +} + +static int stbtt_CompareUTF8toUTF16_bigendian_internal(char *s1, int len1, char *s2, int len2) +{ + return len1 == stbtt__CompareUTF8toUTF16_bigendian_prefix((stbtt_uint8*) s1, len1, (stbtt_uint8*) s2, len2); +} + +// returns results in whatever encoding you request... but note that 2-byte encodings +// will be BIG-ENDIAN... use stbtt_CompareUTF8toUTF16_bigendian() to compare +STBTT_DEF const char *stbtt_GetFontNameString(const stbtt_fontinfo *font, int *length, int platformID, int encodingID, int languageID, int nameID) +{ + stbtt_int32 i,count,stringOffset; + stbtt_uint8 *fc = font->data; + stbtt_uint32 offset = font->fontstart; + stbtt_uint32 nm = stbtt__find_table(fc, offset, "name"); + if (!nm) return NULL; + + count = ttUSHORT(fc+nm+2); + stringOffset = nm + ttUSHORT(fc+nm+4); + for (i=0; i < count; ++i) { + stbtt_uint32 loc = nm + 6 + 12 * i; + if (platformID == ttUSHORT(fc+loc+0) && encodingID == ttUSHORT(fc+loc+2) + && languageID == ttUSHORT(fc+loc+4) && nameID == ttUSHORT(fc+loc+6)) { + *length = ttUSHORT(fc+loc+8); + return (const char *) (fc+stringOffset+ttUSHORT(fc+loc+10)); + } + } + return NULL; +} + +static int stbtt__matchpair(stbtt_uint8 *fc, stbtt_uint32 nm, stbtt_uint8 *name, stbtt_int32 nlen, stbtt_int32 target_id, stbtt_int32 next_id) +{ + stbtt_int32 i; + stbtt_int32 count = ttUSHORT(fc+nm+2); + stbtt_int32 stringOffset = nm + ttUSHORT(fc+nm+4); + + for (i=0; i < count; ++i) { + stbtt_uint32 loc = nm + 6 + 12 * i; + stbtt_int32 id = ttUSHORT(fc+loc+6); + if (id == target_id) { + // find the encoding + stbtt_int32 platform = ttUSHORT(fc+loc+0), encoding = ttUSHORT(fc+loc+2), language = ttUSHORT(fc+loc+4); + + // is this a Unicode encoding? + if (platform == 0 || (platform == 3 && encoding == 1) || (platform == 3 && encoding == 10)) { + stbtt_int32 slen = ttUSHORT(fc+loc+8); + stbtt_int32 off = ttUSHORT(fc+loc+10); + + // check if there's a prefix match + stbtt_int32 matchlen = stbtt__CompareUTF8toUTF16_bigendian_prefix(name, nlen, fc+stringOffset+off,slen); + if (matchlen >= 0) { + // check for target_id+1 immediately following, with same encoding & language + if (i+1 < count && ttUSHORT(fc+loc+12+6) == next_id && ttUSHORT(fc+loc+12) == platform && ttUSHORT(fc+loc+12+2) == encoding && ttUSHORT(fc+loc+12+4) == language) { + slen = ttUSHORT(fc+loc+12+8); + off = ttUSHORT(fc+loc+12+10); + if (slen == 0) { + if (matchlen == nlen) + return 1; + } else if (matchlen < nlen && name[matchlen] == ' ') { + ++matchlen; + if (stbtt_CompareUTF8toUTF16_bigendian_internal((char*) (name+matchlen), nlen-matchlen, (char*)(fc+stringOffset+off),slen)) + return 1; + } + } else { + // if nothing immediately following + if (matchlen == nlen) + return 1; + } + } + } + + // @TODO handle other encodings + } + } + return 0; +} + +static int stbtt__matches(stbtt_uint8 *fc, stbtt_uint32 offset, stbtt_uint8 *name, stbtt_int32 flags) +{ + stbtt_int32 nlen = (stbtt_int32) STBTT_strlen((char *) name); + stbtt_uint32 nm,hd; + if (!stbtt__isfont(fc+offset)) return 0; + + // check italics/bold/underline flags in macStyle... + if (flags) { + hd = stbtt__find_table(fc, offset, "head"); + if ((ttUSHORT(fc+hd+44) & 7) != (flags & 7)) return 0; + } + + nm = stbtt__find_table(fc, offset, "name"); + if (!nm) return 0; + + if (flags) { + // if we checked the macStyle flags, then just check the family and ignore the subfamily + if (stbtt__matchpair(fc, nm, name, nlen, 16, -1)) return 1; + if (stbtt__matchpair(fc, nm, name, nlen, 1, -1)) return 1; + if (stbtt__matchpair(fc, nm, name, nlen, 3, -1)) return 1; + } else { + if (stbtt__matchpair(fc, nm, name, nlen, 16, 17)) return 1; + if (stbtt__matchpair(fc, nm, name, nlen, 1, 2)) return 1; + if (stbtt__matchpair(fc, nm, name, nlen, 3, -1)) return 1; + } + + return 0; +} + +static int stbtt_FindMatchingFont_internal(unsigned char *font_collection, char *name_utf8, stbtt_int32 flags) +{ + stbtt_int32 i; + for (i=0;;++i) { + stbtt_int32 off = stbtt_GetFontOffsetForIndex(font_collection, i); + if (off < 0) return off; + if (stbtt__matches((stbtt_uint8 *) font_collection, off, (stbtt_uint8*) name_utf8, flags)) + return off; + } +} + +#if defined(__GNUC__) || defined(__clang__) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wcast-qual" +#endif + +STBTT_DEF int stbtt_BakeFontBitmap(const unsigned char *data, int offset, + float pixel_height, unsigned char *pixels, int pw, int ph, + int first_char, int num_chars, stbtt_bakedchar *chardata) +{ + return stbtt_BakeFontBitmap_internal((unsigned char *) data, offset, pixel_height, pixels, pw, ph, first_char, num_chars, chardata); +} + +STBTT_DEF int stbtt_GetFontOffsetForIndex(const unsigned char *data, int index) +{ + return stbtt_GetFontOffsetForIndex_internal((unsigned char *) data, index); +} + +STBTT_DEF int stbtt_GetNumberOfFonts(const unsigned char *data) +{ + return stbtt_GetNumberOfFonts_internal((unsigned char *) data); +} + +STBTT_DEF int stbtt_InitFont(stbtt_fontinfo *info, const unsigned char *data, int offset) +{ + return stbtt_InitFont_internal(info, (unsigned char *) data, offset); +} + +STBTT_DEF int stbtt_FindMatchingFont(const unsigned char *fontdata, const char *name, int flags) +{ + return stbtt_FindMatchingFont_internal((unsigned char *) fontdata, (char *) name, flags); +} + +STBTT_DEF int stbtt_CompareUTF8toUTF16_bigendian(const char *s1, int len1, const char *s2, int len2) +{ + return stbtt_CompareUTF8toUTF16_bigendian_internal((char *) s1, len1, (char *) s2, len2); +} + +#if defined(__GNUC__) || defined(__clang__) +#pragma GCC diagnostic pop +#endif + +#endif // STB_TRUETYPE_IMPLEMENTATION + + +// FULL VERSION HISTORY +// +// 1.19 (2018-02-11) OpenType GPOS kerning (horizontal only), STBTT_fmod +// 1.18 (2018-01-29) add missing function +// 1.17 (2017-07-23) make more arguments const; doc fix +// 1.16 (2017-07-12) SDF support +// 1.15 (2017-03-03) make more arguments const +// 1.14 (2017-01-16) num-fonts-in-TTC function +// 1.13 (2017-01-02) support OpenType fonts, certain Apple fonts +// 1.12 (2016-10-25) suppress warnings about casting away const with -Wcast-qual +// 1.11 (2016-04-02) fix unused-variable warning +// 1.10 (2016-04-02) allow user-defined fabs() replacement +// fix memory leak if fontsize=0.0 +// fix warning from duplicate typedef +// 1.09 (2016-01-16) warning fix; avoid crash on outofmem; use alloc userdata for PackFontRanges +// 1.08 (2015-09-13) document stbtt_Rasterize(); fixes for vertical & horizontal edges +// 1.07 (2015-08-01) allow PackFontRanges to accept arrays of sparse codepoints; +// allow PackFontRanges to pack and render in separate phases; +// fix stbtt_GetFontOFfsetForIndex (never worked for non-0 input?); +// fixed an assert() bug in the new rasterizer +// replace assert() with STBTT_assert() in new rasterizer +// 1.06 (2015-07-14) performance improvements (~35% faster on x86 and x64 on test machine) +// also more precise AA rasterizer, except if shapes overlap +// remove need for STBTT_sort +// 1.05 (2015-04-15) fix misplaced definitions for STBTT_STATIC +// 1.04 (2015-04-15) typo in example +// 1.03 (2015-04-12) STBTT_STATIC, fix memory leak in new packing, various fixes +// 1.02 (2014-12-10) fix various warnings & compile issues w/ stb_rect_pack, C++ +// 1.01 (2014-12-08) fix subpixel position when oversampling to exactly match +// non-oversampled; STBTT_POINT_SIZE for packed case only +// 1.00 (2014-12-06) add new PackBegin etc. API, w/ support for oversampling +// 0.99 (2014-09-18) fix multiple bugs with subpixel rendering (ryg) +// 0.9 (2014-08-07) support certain mac/iOS fonts without an MS platformID +// 0.8b (2014-07-07) fix a warning +// 0.8 (2014-05-25) fix a few more warnings +// 0.7 (2013-09-25) bugfix: subpixel glyph bug fixed in 0.5 had come back +// 0.6c (2012-07-24) improve documentation +// 0.6b (2012-07-20) fix a few more warnings +// 0.6 (2012-07-17) fix warnings; added stbtt_ScaleForMappingEmToPixels, +// stbtt_GetFontBoundingBox, stbtt_IsGlyphEmpty +// 0.5 (2011-12-09) bugfixes: +// subpixel glyph renderer computed wrong bounding box +// first vertex of shape can be off-curve (FreeSans) +// 0.4b (2011-12-03) fixed an error in the font baking example +// 0.4 (2011-12-01) kerning, subpixel rendering (tor) +// bugfixes for: +// codepoint-to-glyph conversion using table fmt=12 +// codepoint-to-glyph conversion using table fmt=4 +// stbtt_GetBakedQuad with non-square texture (Zer) +// updated Hello World! sample to use kerning and subpixel +// fixed some warnings +// 0.3 (2009-06-24) cmap fmt=12, compound shapes (MM) +// userdata, malloc-from-userdata, non-zero fill (stb) +// 0.2 (2009-03-11) Fix unsigned/signed char warnings +// 0.1 (2009-03-09) First public release +// + +/* +------------------------------------------------------------------------------ +This software is available under 2 licenses -- choose whichever you prefer. +------------------------------------------------------------------------------ +ALTERNATIVE A - MIT License +Copyright (c) 2017 Sean Barrett +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +------------------------------------------------------------------------------ +ALTERNATIVE B - Public Domain (www.unlicense.org) +This is free and unencumbered software released into the public domain. +Anyone is free to copy, modify, publish, use, compile, sell, or distribute this +software, either in source code form or as a compiled binary, for any purpose, +commercial or non-commercial, and by any means. +In jurisdictions that recognize copyright laws, the author or authors of this +software dedicate any and all copyright interest in the software to the public +domain. We make this dedication for the benefit of the public at large and to +the detriment of our heirs and successors. We intend this dedication to be an +overt act of relinquishment in perpetuity of all present and future rights to +this software under copyright law. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +------------------------------------------------------------------------------ +*/ \ No newline at end of file diff --git a/source/modules/dgl/src/pugl-upstream b/source/modules/dgl/src/pugl-upstream new file mode 160000 index 000000000..b1d9703ec --- /dev/null +++ b/source/modules/dgl/src/pugl-upstream @@ -0,0 +1 @@ +Subproject commit b1d9703ecbdb0a033fe0b9acdf58b90f7d81a8e5 diff --git a/source/modules/dgl/src/pugl.cpp b/source/modules/dgl/src/pugl.cpp new file mode 100644 index 000000000..bddec423a --- /dev/null +++ b/source/modules/dgl/src/pugl.cpp @@ -0,0 +1,675 @@ +/* + * DISTRHO Plugin Framework (DPF) + * Copyright (C) 2012-2021 Filipe Coelho + * + * Permission to use, copy, modify, and/or distribute this software for any purpose with + * or without fee is hereby granted, provided that the above copyright notice and this + * permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD + * TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN + * NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER + * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "pugl.hpp" + +/* we will include all header files used in pugl in their C++ friendly form, then pugl stuff in custom namespace */ +#include +#include +#include +#include +#include + +#if defined(DISTRHO_OS_MAC) +# import +# include +# include +# ifdef DGL_CAIRO +# include +# include +# endif +# ifdef DGL_OPENGL +# include +# endif +# ifdef DGL_VULKAN +# import +# include +# include +# endif +#elif defined(DISTRHO_OS_WINDOWS) +# include +# include +# include +# include +# ifdef DGL_CAIRO +# include +# include +# endif +# ifdef DGL_OPENGL +# include +# endif +# ifdef DGL_VULKAN +# include +# include +# endif +#else +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# ifdef HAVE_XCURSOR +# include +# include +# endif +# ifdef HAVE_XRANDR +# include +# endif +# ifdef HAVE_XSYNC +# include +# include +# endif +# ifdef DGL_CAIRO +# include +# include +# endif +# ifdef DGL_OPENGL +# include +# include +# endif +# ifdef DGL_VULKAN +# include +# include +# endif +#endif + +#ifndef DGL_FILE_BROWSER_DISABLED +# ifdef DISTRHO_OS_MAC +# import "../../distrho/extra/FileBrowserDialog.cpp" +# else +# include "../../distrho/extra/FileBrowserDialog.cpp" +# endif +#endif + +#ifndef DISTRHO_OS_MAC +START_NAMESPACE_DGL +#endif + +// -------------------------------------------------------------------------------------------------------------------- + +#if defined(DISTRHO_OS_MAC) +# ifndef DISTRHO_MACOS_NAMESPACE_MACRO +# define DISTRHO_MACOS_NAMESPACE_MACRO_HELPER(NS, SEP, INTERFACE) NS ## SEP ## INTERFACE +# define DISTRHO_MACOS_NAMESPACE_MACRO(NS, INTERFACE) DISTRHO_MACOS_NAMESPACE_MACRO_HELPER(NS, _, INTERFACE) +# define PuglStubView DISTRHO_MACOS_NAMESPACE_MACRO(DGL_NAMESPACE, PuglStubView) +# define PuglWrapperView DISTRHO_MACOS_NAMESPACE_MACRO(DGL_NAMESPACE, PuglWrapperView) +# define PuglWindow DISTRHO_MACOS_NAMESPACE_MACRO(DGL_NAMESPACE, PuglWindow) +# endif +# ifndef __MAC_10_9 +# define NSModalResponseOK NSOKButton +# endif +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wdeprecated-declarations" +# import "pugl-upstream/src/mac.m" +# import "pugl-upstream/src/mac_stub.m" +# ifdef DGL_CAIRO +# import "pugl-upstream/src/mac_cairo.m" +# endif +# ifdef DGL_OPENGL +# import "pugl-upstream/src/mac_gl.m" +# endif +# ifdef DGL_VULKAN +# import "pugl-upstream/src/mac_vulkan.m" +# endif +# pragma clang diagnostic pop +#elif defined(DISTRHO_OS_WINDOWS) +# include "pugl-upstream/src/win.c" +# include "pugl-upstream/src/win_stub.c" +# ifdef DGL_CAIRO +# include "pugl-upstream/src/win_cairo.c" +# endif +# ifdef DGL_OPENGL +# include "pugl-upstream/src/win_gl.c" +# endif +# ifdef DGL_VULKAN +# include "pugl-upstream/src/win_vulkan.c" +# endif +#else +# include "pugl-upstream/src/x11.c" +# include "pugl-upstream/src/x11_stub.c" +# ifdef DGL_CAIRO +# include "pugl-upstream/src/x11_cairo.c" +# endif +# ifdef DGL_OPENGL +# include "pugl-upstream/src/x11_gl.c" +# endif +# ifdef DGL_VULKAN +# include "pugl-upstream/src/x11_vulkan.c" +# endif +#endif + +#include "pugl-upstream/src/implementation.c" + +// -------------------------------------------------------------------------------------------------------------------- +// expose backend enter + +bool puglBackendEnter(PuglView* const view) +{ + return view->backend->enter(view, nullptr) == PUGL_SUCCESS; +} + +// -------------------------------------------------------------------------------------------------------------------- +// expose backend leave + +void puglBackendLeave(PuglView* const view) +{ + view->backend->leave(view, nullptr); +} + +// -------------------------------------------------------------------------------------------------------------------- +// clear minimum size to 0 + +void puglClearMinSize(PuglView* const view) +{ + view->minWidth = 0; + view->minHeight = 0; +} + +// -------------------------------------------------------------------------------------------------------------------- +// missing in pugl, directly returns transient parent + +PuglNativeView puglGetTransientParent(const PuglView* const view) +{ + return view->transientParent; +} + +// -------------------------------------------------------------------------------------------------------------------- +// missing in pugl, directly returns title char* pointer + +const char* puglGetWindowTitle(const PuglView* const view) +{ + return view->title; +} + +// -------------------------------------------------------------------------------------------------------------------- +// get global scale factor + +double puglGetDesktopScaleFactor(const PuglView* const view) +{ +#if defined(DISTRHO_OS_MAC) + if (NSWindow* const window = view->impl->window ? view->impl->window + : [view->impl->wrapperView window]) + return [window screen].backingScaleFactor; + return [NSScreen mainScreen].backingScaleFactor; +#elif defined(DISTRHO_OS_WINDOWS) + if (const HMODULE Shcore = LoadLibraryA("Shcore.dll")) + { + typedef HRESULT(WINAPI* PFN_GetProcessDpiAwareness)(HANDLE, DWORD*); + typedef HRESULT(WINAPI* PFN_GetScaleFactorForMonitor)(HMONITOR, DWORD*); + +# if defined(__GNUC__) && (__GNUC__ >= 9) +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wcast-function-type" +# endif + const PFN_GetProcessDpiAwareness GetProcessDpiAwareness + = (PFN_GetProcessDpiAwareness)GetProcAddress(Shcore, "GetProcessDpiAwareness"); + const PFN_GetScaleFactorForMonitor GetScaleFactorForMonitor + = (PFN_GetScaleFactorForMonitor)GetProcAddress(Shcore, "GetScaleFactorForMonitor"); +# if defined(__GNUC__) && (__GNUC__ >= 9) +# pragma GCC diagnostic pop +# endif + + DWORD dpiAware = 0; + if (GetProcessDpiAwareness && GetScaleFactorForMonitor + && GetProcessDpiAwareness(NULL, &dpiAware) == 0 && dpiAware != 0) + { + const HMONITOR hMon = MonitorFromWindow(view->impl->hwnd, MONITOR_DEFAULTTOPRIMARY); + + DWORD scaleFactor = 0; + if (GetScaleFactorForMonitor(hMon, &scaleFactor) == 0 && scaleFactor != 0) + { + FreeLibrary(Shcore); + return static_cast(scaleFactor) / 100.0; + } + } + + FreeLibrary(Shcore); + } +#elif defined(HAVE_X11) + XrmInitialize(); + + if (char* const rms = XResourceManagerString(view->world->impl->display)) + { + if (const XrmDatabase sdb = XrmGetStringDatabase(rms)) + { + char* type = nullptr; + XrmValue ret; + + if (XrmGetResource(sdb, "Xft.dpi", "String", &type, &ret) + && ret.addr != nullptr + && type != nullptr + && std::strncmp("String", type, 6) == 0) + { + if (const double dpi = std::atof(ret.addr)) + return dpi / 96; + } + } + } +#else + // unused + (void)view; +#endif + + return 1.0; +} + +// -------------------------------------------------------------------------------------------------------------------- +// bring view window into the foreground, aka "raise" window + +void puglRaiseWindow(PuglView* const view) +{ +#if defined(DISTRHO_OS_MAC) + if (NSWindow* const window = view->impl->window ? view->impl->window + : [view->impl->wrapperView window]) + [window orderFrontRegardless]; +#elif defined(DISTRHO_OS_WINDOWS) + SetForegroundWindow(view->impl->hwnd); + SetActiveWindow(view->impl->hwnd); +#else + XRaiseWindow(view->impl->display, view->impl->win); +#endif +} + +// -------------------------------------------------------------------------------------------------------------------- +// set backend that matches current build + +void puglSetMatchingBackendForCurrentBuild(PuglView* const view) +{ +#ifdef DGL_CAIRO + puglSetBackend(view, puglCairoBackend()); +#endif +#ifdef DGL_OPENGL + puglSetBackend(view, puglGlBackend()); +#endif +#ifdef DGL_VULKAN + puglSetBackend(view, puglVulkanBackend()); +#endif + if (view->backend == nullptr) + puglSetBackend(view, puglStubBackend()); +} + +// -------------------------------------------------------------------------------------------------------------------- +// Combine puglSetMinSize and puglSetAspectRatio + +PuglStatus puglSetGeometryConstraints(PuglView* const view, const uint width, const uint height, const bool aspect) +{ + view->minWidth = (int)width; + view->minHeight = (int)height; + + if (aspect) { + view->minAspectX = (int)width; + view->minAspectY = (int)height; + view->maxAspectX = (int)width; + view->maxAspectY = (int)height; + } + +#if defined(DISTRHO_OS_MAC) + puglSetMinSize(view, width, height); + + if (aspect) { + puglSetAspectRatio(view, width, height, width, height); + } +#elif defined(DISTRHO_OS_WINDOWS) + // nothing +#else + if (const PuglStatus status = updateSizeHints(view)) + return status; + + XFlush(view->impl->display); +#endif + + return PUGL_SUCCESS; +} + +// -------------------------------------------------------------------------------------------------------------------- +// set window offset without changing size + +PuglStatus puglSetWindowOffset(PuglView* const view, const int x, const int y) +{ + // TODO custom setFrame version + PuglRect rect = puglGetFrame(view); + rect.x = x; + rect.y = y; + return puglSetFrame(view, rect); +} + +// -------------------------------------------------------------------------------------------------------------------- +// set window size with default size and without changing frame x/y position + +PuglStatus puglSetWindowSize(PuglView* const view, const uint width, const uint height) +{ + view->defaultWidth = width; + view->defaultHeight = height; + view->frame.width = width; + view->frame.height = height; + +#if defined(DISTRHO_OS_MAC) + // replace setFrame with setFrameSize + PuglInternals* const impl = view->impl; + + const PuglRect frame = view->frame; + const NSRect framePx = rectToNsRect(frame); + const NSRect framePt = nsRectToPoints(view, framePx); + + if (impl->window) + { + // Resize window to fit new content rect + const NSRect screenPt = rectToScreen(viewScreen(view), framePt); + const NSRect winFrame = [impl->window frameRectForContentRect:screenPt]; + + [impl->window setFrame:winFrame display:NO]; + } + + // Resize views + const NSSize sizePx = NSMakeSize(frame.width, frame.height); + const NSSize sizePt = [impl->drawView convertSizeFromBacking:sizePx]; + + [impl->wrapperView setFrameSize:(impl->window ? sizePt : framePt.size)]; + [impl->drawView setFrameSize:sizePt]; +#elif defined(DISTRHO_OS_WINDOWS) + // matches upstream pugl, except we add SWP_NOMOVE flag + if (view->impl->hwnd) + { + const PuglRect frame = view->frame; + + RECT rect = { (long)frame.x, + (long)frame.y, + (long)frame.x + (long)frame.width, + (long)frame.y + (long)frame.height }; + + AdjustWindowRectEx(&rect, puglWinGetWindowFlags(view), FALSE, puglWinGetWindowExFlags(view)); + + if (SetWindowPos(view->impl->hwnd, + HWND_TOP, + rect.left, + rect.top, + rect.right - rect.left, + rect.bottom - rect.top, + SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOOWNERZORDER | SWP_NOZORDER)) + { + // make sure to return context back to ourselves + view->backend->enter(view, nullptr); + return PUGL_SUCCESS; + } + + return PUGL_UNKNOWN_ERROR; + } +#else + // matches upstream pugl, except we use XResizeWindow instead of XMoveResizeWindow + if (view->impl->win) + { + Display* const display = view->impl->display; + + if (! XResizeWindow(display, view->impl->win, width, height)) + return PUGL_UNKNOWN_ERROR; + + if (const PuglStatus status = updateSizeHints(view)) + return status; + + XFlush(display); + } +#endif + + return PUGL_SUCCESS; +} + +// -------------------------------------------------------------------------------------------------------------------- +// DGL specific, build-specific drawing prepare + +void puglOnDisplayPrepare(PuglView*) +{ +#ifdef DGL_OPENGL + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + glLoadIdentity(); +#endif +} + +// -------------------------------------------------------------------------------------------------------------------- +// DGL specific, build-specific fallback resize + +void puglFallbackOnResize(PuglView* const view) +{ +#ifdef DGL_OPENGL + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + glMatrixMode(GL_PROJECTION); + glLoadIdentity(); + glOrtho(0.0, static_cast(view->frame.width), static_cast(view->frame.height), 0.0, 0.0, 1.0); + glViewport(0, 0, static_cast(view->frame.width), static_cast(view->frame.height)); + glMatrixMode(GL_MODELVIEW); + glLoadIdentity(); +#endif +} + +#if defined(DISTRHO_OS_MAC) + +// -------------------------------------------------------------------------------------------------------------------- +// macOS specific, allow standalone window to gain focus + +void puglMacOSActivateApp() +{ + [NSApp setActivationPolicy:NSApplicationActivationPolicyRegular]; + [NSApp activateIgnoringOtherApps:YES]; +} + +// -------------------------------------------------------------------------------------------------------------------- +// macOS specific, add another view's window as child + +PuglStatus +puglMacOSAddChildWindow(PuglView* const view, PuglView* const child) +{ + if (NSWindow* const viewWindow = view->impl->window ? view->impl->window + : [view->impl->wrapperView window]) + { + if (NSWindow* const childWindow = child->impl->window ? child->impl->window + : [child->impl->wrapperView window]) + { + [viewWindow addChildWindow:childWindow ordered:NSWindowAbove]; + return PUGL_SUCCESS; + } + } + + return PUGL_FAILURE; +} + +// -------------------------------------------------------------------------------------------------------------------- +// macOS specific, remove another view's window as child + +PuglStatus +puglMacOSRemoveChildWindow(PuglView* const view, PuglView* const child) +{ + if (NSWindow* const viewWindow = view->impl->window ? view->impl->window + : [view->impl->wrapperView window]) + { + if (NSWindow* const childWindow = child->impl->window ? child->impl->window + : [child->impl->wrapperView window]) + { + [viewWindow removeChildWindow:childWindow]; + return PUGL_SUCCESS; + } + } + + return PUGL_FAILURE; +} + +// -------------------------------------------------------------------------------------------------------------------- +// macOS specific, center view based on parent coordinates (if there is one) + +void puglMacOSShowCentered(PuglView* const view) +{ + if (puglShow(view) != PUGL_SUCCESS) + return; + + if (view->transientParent != 0) + { + NSWindow* const transientWindow = [(NSView*)view->transientParent window]; + DISTRHO_SAFE_ASSERT_RETURN(transientWindow != nullptr,); + + const NSRect ourFrame = [view->impl->window frame]; + const NSRect transientFrame = [transientWindow frame]; + + const int x = transientFrame.origin.x + transientFrame.size.width / 2 - ourFrame.size.width / 2; + const int y = transientFrame.origin.y + transientFrame.size.height / 2 + ourFrame.size.height / 2; + + [view->impl->window setFrameTopLeftPoint:NSMakePoint(x, y)]; + } + else + { + [view->impl->window center]; + } +} + +// -------------------------------------------------------------------------------------------------------------------- + +#elif defined(DISTRHO_OS_WINDOWS) + +// -------------------------------------------------------------------------------------------------------------------- +// win32 specific, call ShowWindow with SW_RESTORE + +void puglWin32RestoreWindow(PuglView* const view) +{ + PuglInternals* impl = view->impl; + DISTRHO_SAFE_ASSERT_RETURN(impl->hwnd != nullptr,); + + ShowWindow(impl->hwnd, SW_RESTORE); + SetFocus(impl->hwnd); +} + +// -------------------------------------------------------------------------------------------------------------------- +// win32 specific, center view based on parent coordinates (if there is one) + +void puglWin32ShowCentered(PuglView* const view) +{ + PuglInternals* impl = view->impl; + DISTRHO_SAFE_ASSERT_RETURN(impl->hwnd != nullptr,); + + RECT rectChild, rectParent; + + if (view->transientParent != 0 && + GetWindowRect(impl->hwnd, &rectChild) && + GetWindowRect((HWND)view->transientParent, &rectParent)) + { + SetWindowPos(impl->hwnd, (HWND)view->transientParent, + rectParent.left + (rectChild.right-rectChild.left)/2, + rectParent.top + (rectChild.bottom-rectChild.top)/2, + 0, 0, SWP_SHOWWINDOW|SWP_NOSIZE); + } + else + { +#ifdef DGL_WINDOWS_ICON_ID + WNDCLASSEX wClass; + std::memset(&wClass, 0, sizeof(wClass)); + + const HINSTANCE hInstance = GetModuleHandle(nullptr); + + if (GetClassInfoEx(hInstance, view->world->className, &wClass)) + wClass.hIcon = LoadIcon(nullptr, MAKEINTRESOURCE(DGL_WINDOWS_ICON_ID)); + + SetClassLongPtr(impl->hwnd, GCLP_HICON, (LONG_PTR) LoadIcon(hInstance, MAKEINTRESOURCE(DGL_WINDOWS_ICON_ID))); +#endif + + MONITORINFO mInfo; + std::memset(&mInfo, 0, sizeof(mInfo)); + mInfo.cbSize = sizeof(mInfo); + + if (GetMonitorInfo(MonitorFromWindow(impl->hwnd, MONITOR_DEFAULTTOPRIMARY), &mInfo)) + SetWindowPos(impl->hwnd, + HWND_TOP, + mInfo.rcWork.left + (mInfo.rcWork.right - view->frame.width) / 2, + mInfo.rcWork.top + (mInfo.rcWork.bottom - view->frame.height) / 2, + 0, 0, SWP_SHOWWINDOW|SWP_NOSIZE); + else + ShowWindow(impl->hwnd, SW_NORMAL); + } + + SetFocus(impl->hwnd); +} + +// -------------------------------------------------------------------------------------------------------------------- +// win32 specific, set or unset WS_SIZEBOX style flag + +void puglWin32SetWindowResizable(PuglView* const view, const bool resizable) +{ + PuglInternals* impl = view->impl; + DISTRHO_SAFE_ASSERT_RETURN(impl->hwnd != nullptr,); + + const int winFlags = resizable ? GetWindowLong(impl->hwnd, GWL_STYLE) | WS_SIZEBOX + : GetWindowLong(impl->hwnd, GWL_STYLE) & ~WS_SIZEBOX; + SetWindowLong(impl->hwnd, GWL_STYLE, winFlags); +} + +// -------------------------------------------------------------------------------------------------------------------- + +#elif defined(HAVE_X11) + +// -------------------------------------------------------------------------------------------------------------------- +// X11 specific, safer way to grab focus + +PuglStatus puglX11GrabFocus(const PuglView* const view) +{ + const PuglInternals* const impl = view->impl; + + XWindowAttributes wa; + std::memset(&wa, 0, sizeof(wa)); + + DISTRHO_SAFE_ASSERT_RETURN(XGetWindowAttributes(impl->display, impl->win, &wa), PUGL_UNKNOWN_ERROR); + + if (wa.map_state == IsViewable) + { + XRaiseWindow(impl->display, impl->win); + XSetInputFocus(impl->display, impl->win, RevertToPointerRoot, CurrentTime); + XSync(impl->display, False); + } + + return PUGL_SUCCESS; +} + +// -------------------------------------------------------------------------------------------------------------------- +// X11 specific, set dialog window type and pid hints + +void puglX11SetWindowTypeAndPID(const PuglView* const view, const bool isStandalone) +{ + const PuglInternals* const impl = view->impl; + + const pid_t pid = getpid(); + const Atom _nwp = XInternAtom(impl->display, "_NET_WM_PID", False); + XChangeProperty(impl->display, impl->win, _nwp, XA_CARDINAL, 32, PropModeReplace, (const uchar*)&pid, 1); + + const Atom _wt = XInternAtom(impl->display, "_NET_WM_WINDOW_TYPE", False); + + Atom _wts[2]; + int numAtoms = 0; + + if (! isStandalone) + _wts[numAtoms++] = XInternAtom(impl->display, "_NET_WM_WINDOW_TYPE_DIALOG", False); + + _wts[numAtoms++] = XInternAtom(impl->display, "_NET_WM_WINDOW_TYPE_NORMAL", False); + + XChangeProperty(impl->display, impl->win, _wt, XA_ATOM, 32, PropModeReplace, (const uchar*)&_wts, numAtoms); +} + +// -------------------------------------------------------------------------------------------------------------------- + +#endif // HAVE_X11 + +#ifndef DISTRHO_OS_MAC +END_NAMESPACE_DGL +#endif diff --git a/source/modules/dgl/src/pugl.hpp b/source/modules/dgl/src/pugl.hpp new file mode 100644 index 000000000..fbdeb1107 --- /dev/null +++ b/source/modules/dgl/src/pugl.hpp @@ -0,0 +1,154 @@ +/* + * DISTRHO Plugin Framework (DPF) + * Copyright (C) 2012-2021 Filipe Coelho + * + * Permission to use, copy, modify, and/or distribute this software for any purpose with + * or without fee is hereby granted, provided that the above copyright notice and this + * permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD + * TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN + * NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER + * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef DGL_PUGL_HPP_INCLUDED +#define DGL_PUGL_HPP_INCLUDED + +#include "../Base.hpp" + +/* we will include all header files used in pugl in their C++ friendly form, then pugl stuff in custom namespace */ +#include +#ifdef DISTRHO_PROPER_CPP11_SUPPORT +# include +# include +#else +# include +# include +#endif + +#define PUGL_API +#define PUGL_DISABLE_DEPRECATED +#define PUGL_NO_INCLUDE_GLU_H + +// -------------------------------------------------------------------------------------------------------------------- + +#ifndef DISTRHO_OS_MAC +START_NAMESPACE_DGL +#else +USE_NAMESPACE_DGL +#endif + +#include "pugl-upstream/include/pugl/pugl.h" + +// -------------------------------------------------------------------------------------------------------------------- + +PUGL_BEGIN_DECLS + +// expose backend enter +PUGL_API bool +puglBackendEnter(PuglView* view); + +// expose backend leave +PUGL_API void +puglBackendLeave(PuglView* view); + +// clear minimum size to 0 +PUGL_API void +puglClearMinSize(PuglView* view); + +// missing in pugl, directly returns transient parent +PUGL_API PuglNativeView +puglGetTransientParent(const PuglView* view); + +// missing in pugl, directly returns title char* pointer +PUGL_API const char* +puglGetWindowTitle(const PuglView* view); + +// get global scale factor +PUGL_API double +puglGetDesktopScaleFactor(const PuglView* view); + +// bring view window into the foreground, aka "raise" window +PUGL_API void +puglRaiseWindow(PuglView* view); + +// DGL specific, assigns backend that matches current DGL build +PUGL_API void +puglSetMatchingBackendForCurrentBuild(PuglView* view); + +// Combine puglSetMinSize and puglSetAspectRatio +PUGL_API PuglStatus +puglSetGeometryConstraints(PuglView* view, uint width, uint height, bool aspect); + +// set window offset without changing size +PUGL_API PuglStatus +puglSetWindowOffset(PuglView* view, int x, int y); + +// set window size with default size and without changing frame x/y position +PUGL_API PuglStatus +puglSetWindowSize(PuglView* view, uint width, uint height); + +// DGL specific, build-specific drawing prepare +PUGL_API void +puglOnDisplayPrepare(PuglView* view); + +// DGL specific, build-specific fallback resize +PUGL_API void +puglFallbackOnResize(PuglView* view); + +#if defined(DISTRHO_OS_MAC) + +// macOS specific, allow standalone window to gain focus +PUGL_API void +puglMacOSActivateApp(); + +// macOS specific, add another view's window as child +PUGL_API PuglStatus +puglMacOSAddChildWindow(PuglView* view, PuglView* child); + +// macOS specific, remove another view's window as child +PUGL_API PuglStatus +puglMacOSRemoveChildWindow(PuglView* view, PuglView* child); + +// macOS specific, center view based on parent coordinates (if there is one) +PUGL_API void +puglMacOSShowCentered(PuglView* view); + +#elif defined(DISTRHO_OS_WINDOWS) + +// win32 specific, call ShowWindow with SW_RESTORE +PUGL_API void +puglWin32RestoreWindow(PuglView* view); + +// win32 specific, center view based on parent coordinates (if there is one) +PUGL_API void +puglWin32ShowCentered(PuglView* view); + +// win32 specific, set or unset WS_SIZEBOX style flag +PUGL_API void +puglWin32SetWindowResizable(PuglView* view, bool resizable); + +#elif defined(HAVE_X11) + +// X11 specific, safer way to grab focus +PUGL_API PuglStatus +puglX11GrabFocus(const PuglView* view); + +// X11 specific, set dialog window type and pid hints +PUGL_API void +puglX11SetWindowTypeAndPID(const PuglView* view, bool isStandalone); + +#endif + +PUGL_END_DECLS + +// -------------------------------------------------------------------------------------------------------------------- + +#ifndef DISTRHO_OS_MAC +END_NAMESPACE_DGL +#endif + +#endif // DGL_PUGL_HPP_INCLUDED diff --git a/source/modules/dgl/src/pugl.mm b/source/modules/dgl/src/pugl.mm new file mode 120000 index 000000000..8ea748274 --- /dev/null +++ b/source/modules/dgl/src/pugl.mm @@ -0,0 +1 @@ +pugl.cpp \ No newline at end of file diff --git a/source/modules/dgl/src/pugl/common.h b/source/modules/dgl/src/pugl/common.h deleted file mode 100644 index afda61e0f..000000000 --- a/source/modules/dgl/src/pugl/common.h +++ /dev/null @@ -1,121 +0,0 @@ -/* - Copyright 2014 David Robillard - - Permission to use, copy, modify, and/or distribute this software for any - purpose with or without fee is hereby granted, provided that the above - copyright notice and this permission notice appear in all copies. - - THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -*/ - -#ifndef PUGL_COMMON_H_INCLUDED -#define PUGL_COMMON_H_INCLUDED - -#ifdef __cplusplus -extern "C" { -#endif - -/** - @addtogroup pugl - @{ -*/ - -/** - A Pugl view. -*/ -typedef struct PuglViewImpl PuglView; - -/** - A native window handle. - - On X11, this is a Window. - On OSX, this is an NSView*. - On Windows, this is a HWND. -*/ -typedef intptr_t PuglNativeWindow; - -/** - Handle for opaque user data. -*/ -typedef void* PuglHandle; - -/** - Return status code. -*/ -typedef enum { - PUGL_SUCCESS = 0 -} PuglStatus; - -/** - Drawing context type. -*/ -typedef enum { - PUGL_GL, - PUGL_CAIRO -} PuglContextType; - -/** - Convenience symbols for ASCII control characters. -*/ -typedef enum { - PUGL_CHAR_BACKSPACE = 0x08, - PUGL_CHAR_ESCAPE = 0x1B, - PUGL_CHAR_DELETE = 0x7F -} PuglChar; - -/** - Keyboard modifier flags. -*/ -typedef enum { - PUGL_MOD_SHIFT = 1 << 0, /**< Shift key */ - PUGL_MOD_CTRL = 1 << 1, /**< Control key */ - PUGL_MOD_ALT = 1 << 2, /**< Alt/Option key */ - PUGL_MOD_SUPER = 1 << 3 /**< Mod4/Command/Windows key */ -} PuglMod; - -/** - Special (non-Unicode) keyboard keys. -*/ -typedef enum { - PUGL_KEY_F1 = 1, - PUGL_KEY_F2, - PUGL_KEY_F3, - PUGL_KEY_F4, - PUGL_KEY_F5, - PUGL_KEY_F6, - PUGL_KEY_F7, - PUGL_KEY_F8, - PUGL_KEY_F9, - PUGL_KEY_F10, - PUGL_KEY_F11, - PUGL_KEY_F12, - PUGL_KEY_LEFT, - PUGL_KEY_UP, - PUGL_KEY_RIGHT, - PUGL_KEY_DOWN, - PUGL_KEY_PAGE_UP, - PUGL_KEY_PAGE_DOWN, - PUGL_KEY_HOME, - PUGL_KEY_END, - PUGL_KEY_INSERT, - PUGL_KEY_SHIFT, - PUGL_KEY_CTRL, - PUGL_KEY_ALT, - PUGL_KEY_SUPER -} PuglKey; - -/** - @} -*/ - -#ifdef __cplusplus -} /* extern "C" */ -#endif - -#endif /* PUGL_COMMON_H_INCLUDED */ diff --git a/source/modules/dgl/src/pugl/event.h b/source/modules/dgl/src/pugl/event.h deleted file mode 100644 index 7314e48a0..000000000 --- a/source/modules/dgl/src/pugl/event.h +++ /dev/null @@ -1,41 +0,0 @@ -/* - Copyright 2014 David Robillard - - Permission to use, copy, modify, and/or distribute this software for any - purpose with or without fee is hereby granted, provided that the above - copyright notice and this permission notice appear in all copies. - - THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -*/ - -#ifndef PUGL_EVENT_H_INCLUDED -#define PUGL_EVENT_H_INCLUDED - -#ifdef __cplusplus -extern "C" { -#else -# include -#endif - -#include "pugl/common.h" - -/** - @addtogroup pugl - @{ -*/ - -/** - @} -*/ - -#ifdef __cplusplus -} /* extern "C" */ -#endif - -#endif /* PUGL_EVENT_H_INCLUDED */ diff --git a/source/modules/dgl/src/pugl/gl.h b/source/modules/dgl/src/pugl/gl.h deleted file mode 100644 index 9a6aeefe1..000000000 --- a/source/modules/dgl/src/pugl/gl.h +++ /dev/null @@ -1,32 +0,0 @@ -/* - Copyright 2012-2014 David Robillard - - Permission to use, copy, modify, and/or distribute this software for any - purpose with or without fee is hereby granted, provided that the above - copyright notice and this permission notice appear in all copies. - - THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -*/ - -/** - @file gl.h Portable header wrapper for gl.h. - - Unfortunately, GL includes vary across platforms so this header allows for - pure portable programs. -*/ - -#ifdef __APPLE__ -# include "OpenGL/gl.h" -#else -# ifdef _WIN32 -# include /* Broken Windows GL headers require this */ -# endif -# include "GL/gl.h" -#endif - diff --git a/source/modules/dgl/src/pugl/glu.h b/source/modules/dgl/src/pugl/glu.h deleted file mode 100644 index 4be79c707..000000000 --- a/source/modules/dgl/src/pugl/glu.h +++ /dev/null @@ -1,32 +0,0 @@ -/* - Copyright 2012-2014 David Robillard - - Permission to use, copy, modify, and/or distribute this software for any - purpose with or without fee is hereby granted, provided that the above - copyright notice and this permission notice appear in all copies. - - THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -*/ - -/** - @file glu.h Portable header wrapper for glu.h. - - Unfortunately, GL includes vary across platforms so this header allows for - pure portable programs. -*/ - -#ifdef __APPLE__ -# include "OpenGL/glu.h" -#else -# ifdef _WIN32 -# include /* Broken Windows GL headers require this */ -# endif -# include "GL/glu.h" -#endif - diff --git a/source/modules/dgl/src/pugl/pugl.h b/source/modules/dgl/src/pugl/pugl.h deleted file mode 100644 index 42518e16b..000000000 --- a/source/modules/dgl/src/pugl/pugl.h +++ /dev/null @@ -1,385 +0,0 @@ -/* - Copyright 2012-2014 David Robillard - - Permission to use, copy, modify, and/or distribute this software for any - purpose with or without fee is hereby granted, provided that the above - copyright notice and this permission notice appear in all copies. - - THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -*/ - -/** - @file pugl.h API for Pugl, a minimal portable API for OpenGL. -*/ - -#ifndef PUGL_H_INCLUDED -#define PUGL_H_INCLUDED - -#include - -#include "pugl/common.h" -#include "pugl/event.h" - -#ifdef PUGL_SHARED -# ifdef _WIN32 -# define PUGL_LIB_IMPORT __declspec(dllimport) -# define PUGL_LIB_EXPORT __declspec(dllexport) -# else -# define PUGL_LIB_IMPORT __attribute__((visibility("default"))) -# define PUGL_LIB_EXPORT __attribute__((visibility("default"))) -# endif -# ifdef PUGL_INTERNAL -# define PUGL_API PUGL_LIB_EXPORT -# else -# define PUGL_API PUGL_LIB_IMPORT -# endif -#else -# ifdef _WIN32 -# define PUGL_API -# else -# define PUGL_API __attribute__((visibility("hidden"))) -# endif -#endif - -#ifdef __cplusplus -extern "C" { -#else -# include -#endif - -/** - @defgroup pugl Pugl - A minimal portable API for OpenGL. - @{ -*/ - -/** - A function called when the window is closed. -*/ -typedef void (*PuglCloseFunc)(PuglView* view); - -/** - A function called to draw the view contents with OpenGL. -*/ -typedef void (*PuglDisplayFunc)(PuglView* view); - -/** - A function called when a key is pressed or released. - @param view The view the event occured in. - @param press True if the key was pressed, false if released. - @param key Unicode point of the key pressed. - @return 0 if event was handled, otherwise send event to parent window. -*/ -typedef int (*PuglKeyboardFunc)(PuglView* view, bool press, uint32_t key); - -/** - A function called when the pointer moves. - @param view The view the event occured in. - @param x The window-relative x coordinate of the pointer. - @param y The window-relative y coordinate of the pointer. -*/ -typedef void (*PuglMotionFunc)(PuglView* view, int x, int y); - -/** - A function called when a mouse button is pressed or released. - @param view The view the event occured in. - @param button The button number (1 = left, 2 = middle, 3 = right). - @param press True if the key was pressed, false if released. - @param x The window-relative x coordinate of the pointer. - @param y The window-relative y coordinate of the pointer. -*/ -typedef void (*PuglMouseFunc)( - PuglView* view, int button, bool press, int x, int y); - -/** - A function called when the view is resized. - @param view The view being resized. - @param width The new view width. - @param height The new view height. -*/ -typedef void (*PuglReshapeFunc)(PuglView* view, int width, int height); - -/** - A function called on scrolling (e.g. mouse wheel or track pad). - - The distances used here are in "lines", a single tick of a clicking mouse - wheel. For example, @p dy = 1.0 scrolls 1 line up. Some systems and - devices support finer resolution and/or higher values for fast scrolls, - so programs should handle any value gracefully. - - @param view The view being scrolled. - @param x The window-relative x coordinate of the pointer. - @param y The window-relative y coordinate of the pointer. - @param dx The scroll x distance. - @param dx The scroll y distance. -*/ -typedef void (*PuglScrollFunc)(PuglView* view, int x, int y, float dx, float dy); - -/** - A function called when a special key is pressed or released. - - This callback allows the use of keys that do not have unicode points. - Note that some are non-printable keys. - - @param view The view the event occured in. - @param press True if the key was pressed, false if released. - @param key The key pressed. - @return 0 if event was handled, otherwise send event to parent window. -*/ -typedef int (*PuglSpecialFunc)(PuglView* view, bool press, PuglKey key); - -/** - A function called when a filename is selected via file-browser. - - @param view The view the event occured in. - @param filename The selected file name or NULL if the dialog was canceled. -*/ -typedef void (*PuglFileSelectedFunc)(PuglView* view, const char* filename); - -/** - @name Initialization - Configuration functions which must be called before creating a window. - @{ -*/ - -/** - Create a Pugl context. - - To create a window, call the various puglInit* functions as necessary, then - call puglCreateWindow(). -*/ -PUGL_API PuglView* -puglInit(void); - -/** - Set the parent window before creating a window (for embedding). -*/ -PUGL_API void -puglInitWindowParent(PuglView* view, PuglNativeWindow parent); - -/** - Set the window size before creating a window. -*/ -PUGL_API void -puglInitWindowSize(PuglView* view, int width, int height); - -/** - Set the minimum window size before creating a window. -*/ -PUGL_API void -puglInitWindowMinSize(PuglView* view, int width, int height); - -/** - Enable or disable resizing before creating a window. -*/ -PUGL_API void -puglInitUserResizable(PuglView* view, bool resizable); - -/** - Set transient parent before creating a window. - - On X11, parent_id must be a Window. - On OSX, parent_id must be an NSView*. -*/ -PUGL_API void -puglInitTransientFor(PuglView* view, uintptr_t parent); - -/** - Set the context type before creating a window. -*/ -PUGL_API void -puglInitContextType(PuglView* view, PuglContextType type); - -/** - @} -*/ - -/** - @name Windows - Window management functions. - @{ -*/ - -/** - Create a window with the settings given by the various puglInit functions. - - @return 1 (pugl does not currently support multiple windows). -*/ -PUGL_API int -puglCreateWindow(PuglView* view, const char* title); - -/** - Show the current window. -*/ -PUGL_API void -puglShowWindow(PuglView* view); - -/** - Hide the current window. -*/ -PUGL_API void -puglHideWindow(PuglView* view); - -/** - Return the native window handle. -*/ -PUGL_API PuglNativeWindow -puglGetNativeWindow(PuglView* view); - -/** - @} -*/ - -/** - Set the handle to be passed to all callbacks. - - This is generally a pointer to a struct which contains all necessary state. - Everything needed in callbacks should be here, not in static variables. - - Note the lack of this facility makes GLUT unsuitable for plugins or - non-trivial programs; this mistake is largely why Pugl exists. -*/ -PUGL_API void -puglSetHandle(PuglView* view, PuglHandle handle); - -/** - Get the handle to be passed to all callbacks. -*/ -PUGL_API PuglHandle -puglGetHandle(PuglView* view); - -/** - Get the drawing context. - - For PUGL_GL contexts, this is unused and returns NULL. - For PUGL_CAIRO contexts, this returns a pointer to a cairo_t. -*/ -PUGL_API void* -puglGetContext(PuglView* view); - -/** - Return the timestamp (if any) of the currently-processing event. -*/ -PUGL_API uint32_t -puglGetEventTimestamp(PuglView* view); - -/** - Get the currently active modifiers (PuglMod flags). - - This should only be called from an event handler. -*/ -PUGL_API int -puglGetModifiers(PuglView* view); - -/** - Ignore synthetic repeated key events. -*/ -PUGL_API void -puglIgnoreKeyRepeat(PuglView* view, bool ignore); - -/** - @name Event Callbacks - Functions to set event callbacks for handling user input. - @{ -*/ - -/** - Set the function to call when the window is closed. -*/ -PUGL_API void -puglSetCloseFunc(PuglView* view, PuglCloseFunc closeFunc); - -/** - Set the display function which should draw the UI using GL. -*/ -PUGL_API void -puglSetDisplayFunc(PuglView* view, PuglDisplayFunc displayFunc); - -/** - Set the function to call on keyboard events. -*/ -PUGL_API void -puglSetKeyboardFunc(PuglView* view, PuglKeyboardFunc keyboardFunc); - -/** - Set the function to call on mouse motion. -*/ -PUGL_API void -puglSetMotionFunc(PuglView* view, PuglMotionFunc motionFunc); - -/** - Set the function to call on mouse button events. -*/ -PUGL_API void -puglSetMouseFunc(PuglView* view, PuglMouseFunc mouseFunc); - -/** - Set the function to call on scroll events. -*/ -PUGL_API void -puglSetScrollFunc(PuglView* view, PuglScrollFunc scrollFunc); - -/** - Set the function to call on special events. -*/ -PUGL_API void -puglSetSpecialFunc(PuglView* view, PuglSpecialFunc specialFunc); - -/** - Set the function to call when the window size changes. -*/ -PUGL_API void -puglSetReshapeFunc(PuglView* view, PuglReshapeFunc reshapeFunc); - -/** - Set the function to call on file-browser selections. -*/ -PUGL_API void -puglSetFileSelectedFunc(PuglView* view, PuglFileSelectedFunc fileSelectedFunc); - -/** - @} -*/ - -/** - Grab the input focus. -*/ -PUGL_API void -puglGrabFocus(PuglView* view); - -/** - Process all pending window events. - - This handles input events as well as rendering, so it should be called - regularly and rapidly enough to keep the UI responsive. -*/ -PUGL_API PuglStatus -puglProcessEvents(PuglView* view); - -/** - Request a redisplay on the next call to puglProcessEvents(). -*/ -PUGL_API void -puglPostRedisplay(PuglView* view); - -/** - Destroy a GL window. -*/ -PUGL_API void -puglDestroy(PuglView* view); - -/** - @} -*/ - -#ifdef __cplusplus -} /* extern "C" */ -#endif - -#endif /* PUGL_H_INCLUDED */ diff --git a/source/modules/dgl/src/pugl/pugl_internal.h b/source/modules/dgl/src/pugl/pugl_internal.h deleted file mode 100644 index 3ca59ca7c..000000000 --- a/source/modules/dgl/src/pugl/pugl_internal.h +++ /dev/null @@ -1,352 +0,0 @@ -/* - Copyright 2012-2014 David Robillard - - Permission to use, copy, modify, and/or distribute this software for any - purpose with or without fee is hereby granted, provided that the above - copyright notice and this permission notice appear in all copies. - - THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -*/ - -/** - @file pugl_internal.h Private platform-independent definitions. - - Note this file contains function definitions, so it must be compiled into - the final binary exactly once. Each platform specific implementation file - including it once should achieve this. - - If you are copying the pugl code into your source tree, the following - symbols can be defined to tweak pugl behaviour: - - PUGL_HAVE_CAIRO: Include Cairo support code. - PUGL_HAVE_GL: Include OpenGL support code. - PUGL_GRAB_FOCUS: Work around reparent keyboard issues by grabbing focus. - PUGL_VERBOSE: Print GL information to console. -*/ - -#include "pugl/pugl.h" -#include "pugl/event.h" - -#ifdef PUGL_VERBOSE -# include -# define PUGL_LOG(str) fprintf(stderr, "pugl: " str) -# define PUGL_LOGF(fmt, ...) fprintf(stderr, "pugl: " fmt, __VA_ARGS__) -#else -# define PUGL_LOG(str) -# define PUGL_LOGF(fmt, ...) -#endif - -typedef struct PuglInternalsImpl PuglInternals; - -struct PuglViewImpl { - PuglHandle handle; - PuglCloseFunc closeFunc; - PuglDisplayFunc displayFunc; - PuglKeyboardFunc keyboardFunc; - PuglMotionFunc motionFunc; - PuglMouseFunc mouseFunc; - PuglReshapeFunc reshapeFunc; - PuglScrollFunc scrollFunc; - PuglSpecialFunc specialFunc; - PuglFileSelectedFunc fileSelectedFunc; - - PuglInternals* impl; - - PuglNativeWindow parent; - PuglContextType ctx_type; - uintptr_t transient_parent; - - int width; - int height; - int min_width; - int min_height; - int mods; - bool mouse_in_view; - bool ignoreKeyRepeat; - bool redisplay; - bool resizable; - uint32_t event_timestamp_ms; -}; - -PuglInternals* puglInitInternals(void); - -PuglView* -puglInit(void) -{ - PuglView* view = (PuglView*)calloc(1, sizeof(PuglView)); - if (!view) { - return NULL; - } - - PuglInternals* impl = puglInitInternals(); - if (!impl) { - free(view); - return NULL; - } - - view->impl = impl; - view->width = 640; - view->height = 480; - - return view; -} - -void -puglInitWindowSize(PuglView* view, int width, int height) -{ - view->width = width; - view->height = height; -} - -void -puglInitWindowMinSize(PuglView* view, int width, int height) -{ - view->min_width = width; - view->min_height = height; -} - -void -puglInitWindowParent(PuglView* view, PuglNativeWindow parent) -{ - view->parent = parent; -} - -void -puglInitUserResizable(PuglView* view, bool resizable) -{ - view->resizable = resizable; -} - -void -puglInitTransientFor(PuglView* view, uintptr_t parent) -{ - view->transient_parent = parent; -} - -void -puglInitContextType(PuglView* view, PuglContextType type) -{ - view->ctx_type = type; -} - -void -puglSetHandle(PuglView* view, PuglHandle handle) -{ - view->handle = handle; -} - -PuglHandle -puglGetHandle(PuglView* view) -{ - return view->handle; -} - -uint32_t -puglGetEventTimestamp(PuglView* view) -{ - return view->event_timestamp_ms; -} - -int -puglGetModifiers(PuglView* view) -{ - return view->mods; -} - -void -puglIgnoreKeyRepeat(PuglView* view, bool ignore) -{ - view->ignoreKeyRepeat = ignore; -} - -void -puglSetCloseFunc(PuglView* view, PuglCloseFunc closeFunc) -{ - view->closeFunc = closeFunc; -} - -void -puglSetDisplayFunc(PuglView* view, PuglDisplayFunc displayFunc) -{ - view->displayFunc = displayFunc; -} - -void -puglSetKeyboardFunc(PuglView* view, PuglKeyboardFunc keyboardFunc) -{ - view->keyboardFunc = keyboardFunc; -} - -void -puglSetMotionFunc(PuglView* view, PuglMotionFunc motionFunc) -{ - view->motionFunc = motionFunc; -} - -void -puglSetMouseFunc(PuglView* view, PuglMouseFunc mouseFunc) -{ - view->mouseFunc = mouseFunc; -} - -void -puglSetReshapeFunc(PuglView* view, PuglReshapeFunc reshapeFunc) -{ - view->reshapeFunc = reshapeFunc; -} - -void -puglSetScrollFunc(PuglView* view, PuglScrollFunc scrollFunc) -{ - view->scrollFunc = scrollFunc; -} - -void -puglSetSpecialFunc(PuglView* view, PuglSpecialFunc specialFunc) -{ - view->specialFunc = specialFunc; -} - -void -puglSetFileSelectedFunc(PuglView* view, PuglFileSelectedFunc fileSelectedFunc) -{ - view->fileSelectedFunc = fileSelectedFunc; -} - -void -puglEnterContext(PuglView* view); - -void -puglLeaveContext(PuglView* view, bool flush); - -#if 0 -/** Return the code point for buf, or the replacement character on error. */ -static uint32_t -puglDecodeUTF8(const uint8_t* buf) -{ -#define FAIL_IF(cond) { if (cond) return 0xFFFD; } - - /* http://en.wikipedia.org/wiki/UTF-8 */ - - if (buf[0] < 0x80) { - return buf[0]; - } else if (buf[0] < 0xC2) { - return 0xFFFD; - } else if (buf[0] < 0xE0) { - FAIL_IF((buf[1] & 0xC0) != 0x80); - return (buf[0] << 6) + buf[1] - 0x3080; - } else if (buf[0] < 0xF0) { - FAIL_IF((buf[1] & 0xC0) != 0x80); - FAIL_IF(buf[0] == 0xE0 && buf[1] < 0xA0); - FAIL_IF((buf[2] & 0xC0) != 0x80); - return (buf[0] << 12) + (buf[1] << 6) + buf[2] - 0xE2080; - } else if (buf[0] < 0xF5) { - FAIL_IF((buf[1] & 0xC0) != 0x80); - FAIL_IF(buf[0] == 0xF0 && buf[1] < 0x90); - FAIL_IF(buf[0] == 0xF4 && buf[1] >= 0x90); - FAIL_IF((buf[2] & 0xC0) != 0x80); - FAIL_IF((buf[3] & 0xC0) != 0x80); - return ((buf[0] << 18) + - (buf[1] << 12) + - (buf[2] << 6) + - buf[3] - 0x3C82080); - } - return 0xFFFD; -} -#endif - -static void -puglDefaultReshape(PuglView* view, int width, int height) -{ - glMatrixMode(GL_PROJECTION); - glLoadIdentity(); - glOrtho(0, width, height, 0, 0, 1); - glViewport(0, 0, width, height); - - glMatrixMode(GL_MODELVIEW); - glLoadIdentity(); - return; - - // unused - (void)view; -} - -#if 0 -static void -puglDispatchEvent(PuglView* view, const PuglEvent* event) -{ - if (view->eventFunc) { - view->eventFunc(view, event); - } - - switch (event->type) { - case PUGL_CONFIGURE: - puglEnterContext(view); - view->width = event->configure.width; - view->height = event->configure.height; - if (view->reshapeFunc) { - view->reshapeFunc(view, view->width, view->height); - } - puglLeaveContext(view, false); - break; - case PUGL_EXPOSE: - if (event->expose.count == 0) { - puglEnterContext(view); - if (view->displayFunc) { - view->displayFunc(view); - } - view->redisplay = false; - puglLeaveContext(view, true); - } - break; - case PUGL_MOTION_NOTIFY: - view->event_timestamp_ms = event->motion.time; - view->mods = event->motion.state; - if (view->motionFunc) { - view->motionFunc(view, event->motion.x, event->motion.y); - } - break; - case PUGL_SCROLL: - if (view->scrollFunc) { - view->scrollFunc(view, - event->scroll.x, event->scroll.y, - event->scroll.dx, event->scroll.dy); - } - break; - case PUGL_BUTTON_PRESS: - case PUGL_BUTTON_RELEASE: - view->event_timestamp_ms = event->button.time; - view->mods = event->button.state; - if (view->mouseFunc) { - view->mouseFunc(view, - event->button.button, - event->type == PUGL_BUTTON_PRESS, - event->button.x, - event->button.y); - } - break; - case PUGL_KEY_PRESS: - case PUGL_KEY_RELEASE: - view->event_timestamp_ms = event->key.time; - view->mods = event->key.state; - if (event->key.special && view->specialFunc) { - view->specialFunc(view, - event->type == PUGL_KEY_PRESS, - event->key.special); - } else if (event->key.character && view->keyboardFunc) { - view->keyboardFunc(view, - event->type == PUGL_KEY_PRESS, - event->key.character); - } - break; - default: - break; - } -} -#endif diff --git a/source/modules/dgl/src/pugl/pugl_osx.m b/source/modules/dgl/src/pugl/pugl_osx.m deleted file mode 100644 index 02c63012e..000000000 --- a/source/modules/dgl/src/pugl/pugl_osx.m +++ /dev/null @@ -1,576 +0,0 @@ -/* - Copyright 2012 David Robillard - - Permission to use, copy, modify, and/or distribute this software for any - purpose with or without fee is hereby granted, provided that the above - copyright notice and this permission notice appear in all copies. - - THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -*/ - -/** - @file pugl_osx.m OSX/Cocoa Pugl Implementation. -*/ - -#include - -#import - -#include "pugl_internal.h" - -@interface PuglWindow : NSWindow -{ -@public - PuglView* puglview; -} - -- (id) initWithContentRect:(NSRect)contentRect - styleMask:(unsigned int)aStyle - backing:(NSBackingStoreType)bufferingType - defer:(BOOL)flag; -- (void) setPuglview:(PuglView*)view; -- (BOOL) canBecomeKeyWindow; -- (BOOL) windowShouldClose:(id)sender; -@end - -@implementation PuglWindow - -- (id)initWithContentRect:(NSRect)contentRect - styleMask:(unsigned int)aStyle - backing:(NSBackingStoreType)bufferingType - defer:(BOOL)flag -{ - NSWindow* result = [super initWithContentRect:contentRect - styleMask:(NSClosableWindowMask | - NSTitledWindowMask | - NSResizableWindowMask) - backing:NSBackingStoreBuffered defer:NO]; - - [result setAcceptsMouseMovedEvents:YES]; - [result setLevel: CGShieldingWindowLevel() + 1]; - - return (PuglWindow*)result; - - // unused - (void)aStyle; (void)bufferingType; (void)flag; -} - -- (void)setPuglview:(PuglView*)view -{ - puglview = view; - [self setContentSize:NSMakeSize(view->width, view->height)]; -} - -- (BOOL)canBecomeKeyWindow -{ - return YES; -} - -- (BOOL)windowShouldClose:(id)sender -{ - if (puglview->closeFunc) - puglview->closeFunc(puglview); - return YES; - - // unused - (void)sender; -} - -@end - -static void -puglDisplay(PuglView* view) -{ - view->redisplay = false; - if (view->displayFunc) { - view->displayFunc(view); - } -} - -@interface PuglOpenGLView : NSOpenGLView -{ -@public - PuglView* puglview; - NSTrackingArea* trackingArea; - bool doubleBuffered; -} - -- (BOOL) acceptsFirstMouse:(NSEvent*)e; -- (BOOL) acceptsFirstResponder; -- (BOOL) isFlipped; -- (BOOL) isOpaque; -- (BOOL) preservesContentInLiveResize; -- (id) initWithFrame:(NSRect)frame; -- (void) reshape; -- (void) drawRect:(NSRect)r; -- (void) cursorUpdate:(NSEvent*)e; -- (void) updateTrackingAreas; -- (void) viewWillMoveToWindow:(NSWindow*)newWindow; -- (void) mouseMoved:(NSEvent*)event; -- (void) mouseDragged:(NSEvent*)event; -- (void) rightMouseDragged:(NSEvent*)event; -- (void) otherMouseDragged:(NSEvent*)event; -- (void) mouseDown:(NSEvent*)event; -- (void) rightMouseDown:(NSEvent*)event; -- (void) otherMouseDown:(NSEvent*)event; -- (void) mouseUp:(NSEvent*)event; -- (void) rightMouseUp:(NSEvent*)event; -- (void) otherMouseUp:(NSEvent*)event; -- (void) scrollWheel:(NSEvent*)event; -- (void) keyDown:(NSEvent*)event; -- (void) keyUp:(NSEvent*)event; -- (void) flagsChanged:(NSEvent*)event; - -@end - -@implementation PuglOpenGLView - -- (BOOL) acceptsFirstMouse:(NSEvent*)e -{ - return YES; - - // unused - (void)e; -} - -- (BOOL) acceptsFirstResponder -{ - return YES; -} - -- (BOOL) isFlipped -{ - return YES; -} - -- (BOOL) isOpaque -{ - return YES; -} - -- (BOOL) preservesContentInLiveResize -{ - return NO; -} - -- (id) initWithFrame:(NSRect)frame -{ - puglview = nil; - trackingArea = nil; - doubleBuffered = true; - - NSOpenGLPixelFormatAttribute pixelAttribs[] = { - NSOpenGLPFAColorSize, 24, - NSOpenGLPFAAlphaSize, 8, - NSOpenGLPFADepthSize, 16, - NSOpenGLPFADoubleBuffer, - NSOpenGLPFAAccelerated, - 0 - }; - - NSOpenGLPixelFormat* pixelFormat = [[NSOpenGLPixelFormat alloc] - initWithAttributes:pixelAttribs]; - - if (pixelFormat) { - self = [super initWithFrame:frame pixelFormat:pixelFormat]; - [pixelFormat release]; - printf("Is doubleBuffered? TRUE\n"); - } else { - self = [super initWithFrame:frame]; - doubleBuffered = false; - printf("Is doubleBuffered? FALSE\n"); - } - - if (self) { - GLint swapInterval = 1; - [[self openGLContext] setValues:&swapInterval forParameter:NSOpenGLCPSwapInterval]; - - [self reshape]; - } - - return self; -} - -- (void) reshape -{ - if (!puglview) { - /* NOTE: Apparently reshape gets called when the GC gets around to - deleting the view (?), so we must have reset puglview to NULL when - this comes around. - */ - return; - } - - [[self openGLContext] update]; - - NSRect bounds = [self bounds]; - int width = bounds.size.width; - int height = bounds.size.height; - - puglEnterContext(puglview); - - if (puglview->reshapeFunc) { - puglview->reshapeFunc(puglview, width, height); - } else { - puglDefaultReshape(puglview, width, height); - } - - puglLeaveContext(puglview, false); - - puglview->width = width; - puglview->height = height; -} - -- (void) drawRect:(NSRect)r -{ - puglEnterContext(puglview); - puglDisplay(puglview); - puglLeaveContext(puglview, true); - - // unused - return; (void)r; -} - -- (void) cursorUpdate:(NSEvent*)e -{ - [[NSCursor arrowCursor] set]; - - // unused - return; (void)e; -} - -- (void) updateTrackingAreas -{ - static const int opts = NSTrackingMouseEnteredAndExited - | NSTrackingMouseMoved - | NSTrackingEnabledDuringMouseDrag - | NSTrackingInVisibleRect - | NSTrackingActiveAlways - | NSTrackingCursorUpdate; - - if (trackingArea != nil) { - [self removeTrackingArea:trackingArea]; - [trackingArea release]; - } - - trackingArea = [[NSTrackingArea alloc] initWithRect:[self bounds] - options:opts - owner:self - userInfo:nil]; - [self addTrackingArea:trackingArea]; - [super updateTrackingAreas]; -} - -- (void) viewWillMoveToWindow:(NSWindow*)newWindow -{ - if (newWindow != nil) { - [newWindow setAcceptsMouseMovedEvents:YES]; - [newWindow makeFirstResponder:self]; - } - - [super viewWillMoveToWindow:newWindow]; -} - -static unsigned -getModifiers(PuglView* view, NSEvent* ev) -{ - const unsigned modifierFlags = [ev modifierFlags]; - - view->event_timestamp_ms = fmod([ev timestamp] * 1000.0, UINT32_MAX); - - unsigned mods = 0; - mods |= (modifierFlags & NSShiftKeyMask) ? PUGL_MOD_SHIFT : 0; - mods |= (modifierFlags & NSControlKeyMask) ? PUGL_MOD_CTRL : 0; - mods |= (modifierFlags & NSAlternateKeyMask) ? PUGL_MOD_ALT : 0; - mods |= (modifierFlags & NSCommandKeyMask) ? PUGL_MOD_SUPER : 0; - return mods; -} - -static int -getFixedAppKitButton(NSInteger button) -{ - switch (button) { - case 0: return 1; - case 1: return 3; - case 2: return 2; - default: return button; - } -} - -- (void) mouseMoved:(NSEvent*)event -{ - if (puglview->motionFunc) { - NSPoint loc = [self convertPoint:[event locationInWindow] fromView:nil]; - puglview->mods = getModifiers(puglview, event); - puglview->motionFunc(puglview, loc.x, loc.y); - } -} - -- (void) mouseDragged:(NSEvent*)event -{ - [self mouseMoved:event]; -} - -- (void) rightMouseDragged:(NSEvent*)event -{ - [self mouseDragged:event]; -} - -- (void) otherMouseDragged:(NSEvent*)event -{ - [self mouseDragged:event]; -} - -- (void) mouseDown:(NSEvent*)event -{ - if (puglview->mouseFunc) { - NSPoint loc = [self convertPoint:[event locationInWindow] fromView:nil]; - puglview->mods = getModifiers(puglview, event); - puglview->mouseFunc(puglview, getFixedAppKitButton([event buttonNumber]), true, loc.x, loc.y); - } -} - -- (void) rightMouseDown:(NSEvent*)event -{ - [self mouseDown:event]; -} - -- (void) otherMouseDown:(NSEvent*)event -{ - [self mouseDown:event]; -} - -- (void) mouseUp:(NSEvent*)event -{ - if (puglview->mouseFunc) { - NSPoint loc = [self convertPoint:[event locationInWindow] fromView:nil]; - puglview->mods = getModifiers(puglview, event); - puglview->mouseFunc(puglview, getFixedAppKitButton([event buttonNumber]), false, loc.x, loc.y); - } -} - -- (void) rightMouseUp:(NSEvent*)event -{ - [self mouseUp:event]; -} - -- (void) otherMouseUp:(NSEvent*)event -{ - [self mouseUp:event]; -} - -- (void) scrollWheel:(NSEvent*)event -{ - if (puglview->scrollFunc) { - NSPoint loc = [self convertPoint:[event locationInWindow] fromView:nil]; - puglview->mods = getModifiers(puglview, event); - puglview->scrollFunc(puglview, - loc.x, loc.y, - [event deltaX], [event deltaY]); - } -} - -- (void) keyDown:(NSEvent*)event -{ - if (puglview->keyboardFunc && !(puglview->ignoreKeyRepeat && [event isARepeat])) { - NSString* chars = [event characters]; - puglview->mods = getModifiers(puglview, event); - puglview->keyboardFunc(puglview, true, [chars characterAtIndex:0]); - } -} - -- (void) keyUp:(NSEvent*)event -{ - if (puglview->keyboardFunc) { - NSString* chars = [event characters]; - puglview->mods = getModifiers(puglview, event); - puglview->keyboardFunc(puglview, false, [chars characterAtIndex:0]); - } -} - -- (void) flagsChanged:(NSEvent*)event -{ - if (puglview->specialFunc) { - const unsigned mods = getModifiers(puglview, event); - if ((mods & PUGL_MOD_SHIFT) != (puglview->mods & PUGL_MOD_SHIFT)) { - puglview->specialFunc(puglview, mods & PUGL_MOD_SHIFT, PUGL_KEY_SHIFT); - } else if ((mods & PUGL_MOD_CTRL) != (puglview->mods & PUGL_MOD_CTRL)) { - puglview->specialFunc(puglview, mods & PUGL_MOD_CTRL, PUGL_KEY_CTRL); - } else if ((mods & PUGL_MOD_ALT) != (puglview->mods & PUGL_MOD_ALT)) { - puglview->specialFunc(puglview, mods & PUGL_MOD_ALT, PUGL_KEY_ALT); - } else if ((mods & PUGL_MOD_SUPER) != (puglview->mods & PUGL_MOD_SUPER)) { - puglview->specialFunc(puglview, mods & PUGL_MOD_SUPER, PUGL_KEY_SUPER); - } - puglview->mods = mods; - } -} - -@end - -struct PuglInternalsImpl { - PuglOpenGLView* glview; - id window; -}; - -PuglInternals* -puglInitInternals() -{ - return (PuglInternals*)calloc(1, sizeof(PuglInternals)); -} - -void -puglEnterContext(PuglView* view) -{ -#ifdef PUGL_HAVE_GL - if (view->ctx_type == PUGL_GL) { - [[view->impl->glview openGLContext] makeCurrentContext]; - } -#endif -} - -void -puglLeaveContext(PuglView* view, bool flush) -{ -#ifdef PUGL_HAVE_GL - if (view->ctx_type == PUGL_GL && flush) { - if (view->impl->glview->doubleBuffered) { - [[view->impl->glview openGLContext] flushBuffer]; - } else { - glFlush(); - } - //[NSOpenGLContext clearCurrentContext]; - } -#endif -} - -int -puglCreateWindow(PuglView* view, const char* title) -{ - PuglInternals* impl = view->impl; - - [NSAutoreleasePool new]; - [NSApplication sharedApplication]; - - impl->glview = [PuglOpenGLView new]; - - if (!impl->glview) { - return 1; - } - - impl->glview->puglview = view; - - if (view->resizable) { - [impl->glview setAutoresizingMask:NSViewWidthSizable|NSViewHeightSizable]; - } - - if (view->parent) { - [impl->glview retain]; - NSView* pview = (NSView*)view->parent; - [pview addSubview:impl->glview]; - return 0; - } - - id window = [[PuglWindow new]retain]; - - if (title) { - NSString* titleString = [[NSString alloc] - initWithBytes:title - length:strlen(title) - encoding:NSUTF8StringEncoding]; - - [window setTitle:titleString]; - } - - [window setPuglview:view]; - [window setContentView:impl->glview]; - [window makeFirstResponder:impl->glview]; - [window makeKeyAndOrderFront:window]; - - // wait for first puglShowWindow - [window setIsVisible:NO]; - - [NSApp activateIgnoringOtherApps:YES]; - [window center]; - - impl->window = window; - - return 0; -} - -void -puglShowWindow(PuglView* view) -{ - PuglInternals* impl = view->impl; - - if (impl->window) { - [impl->window setIsVisible:YES]; - } else { - [view->impl->glview setHidden:NO]; - } -} - -void -puglHideWindow(PuglView* view) -{ - PuglInternals* impl = view->impl; - - if (impl->window) { - [impl->window setIsVisible:NO]; - } else { - [impl->glview setHidden:YES]; - } -} - -void -puglDestroy(PuglView* view) -{ - view->impl->glview->puglview = NULL; - - if (view->impl->window) { - [view->impl->window close]; - [view->impl->glview release]; - [view->impl->window release]; - } else { - [view->impl->glview release]; - } - - free(view->impl); - free(view); -} - -PuglStatus -puglProcessEvents(PuglView* view) -{ - return PUGL_SUCCESS; - - // unused - (void)view; -} - -void -puglPostRedisplay(PuglView* view) -{ - view->redisplay = true; - [view->impl->glview setNeedsDisplay:YES]; -} - -PuglNativeWindow -puglGetNativeWindow(PuglView* view) -{ - return (PuglNativeWindow)view->impl->glview; -} - -void* -puglGetContext(PuglView* view) -{ - return NULL; - - // unused - (void)view; -} diff --git a/source/modules/dgl/src/pugl/pugl_win.cpp b/source/modules/dgl/src/pugl/pugl_win.cpp deleted file mode 100644 index c87af8578..000000000 --- a/source/modules/dgl/src/pugl/pugl_win.cpp +++ /dev/null @@ -1,489 +0,0 @@ -/* - Copyright 2012-2014 David Robillard - - Permission to use, copy, modify, and/or distribute this software for any - purpose with or without fee is hereby granted, provided that the above - copyright notice and this permission notice appear in all copies. - - THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -*/ - -/** - @file pugl_win.cpp Windows/WGL Pugl Implementation. -*/ - -#include -#include -#include -#include - -#include -#include -#include - -#include "pugl/pugl_internal.h" - -#ifndef WM_MOUSEWHEEL -# define WM_MOUSEWHEEL 0x020A -#endif -#ifndef WM_MOUSEHWHEEL -# define WM_MOUSEHWHEEL 0x020E -#endif -#ifndef WHEEL_DELTA -# define WHEEL_DELTA 120 -#endif -#ifndef GWLP_USERDATA -# define GWLP_USERDATA (-21) -#endif - -#define PUGL_LOCAL_CLOSE_MSG (WM_USER + 50) - -HINSTANCE hInstance = NULL; - -struct PuglInternalsImpl { - HWND hwnd; - HDC hdc; - HGLRC hglrc; - WNDCLASS wc; -}; - -LRESULT CALLBACK -wndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam); - -#if 0 -extern "C" { -BOOL WINAPI -DllMain(HINSTANCE hInst, DWORD, LPVOID) -{ - hInstance = hInst; - return 1; -} -} // extern "C" -#endif - -PuglInternals* -puglInitInternals() -{ - return (PuglInternals*)calloc(1, sizeof(PuglInternals)); -} - -void -puglEnterContext(PuglView* view) -{ -#ifdef PUGL_HAVE_GL - if (view->ctx_type == PUGL_GL) { - wglMakeCurrent(view->impl->hdc, view->impl->hglrc); - } -#endif -} - -void -puglLeaveContext(PuglView* view, bool flush) -{ -#ifdef PUGL_HAVE_GL - if (view->ctx_type == PUGL_GL) { - if (flush) { - glFlush(); - SwapBuffers(view->impl->hdc); - } - wglMakeCurrent(NULL, NULL); - } -#endif -} - -int -puglCreateWindow(PuglView* view, const char* title) -{ - PuglInternals* impl = view->impl; - - if (!title) { - title = "Window"; - } - - // FIXME: This is nasty, and pugl should not have static anything. - // Should class be a parameter? Does this make sense on other platforms? - static int wc_count = 0; - char classNameBuf[256]; - std::srand((std::time(NULL))); -#ifdef __WINE__ - std::snprintf(classNameBuf, sizeof(classNameBuf), "%s_%d-%d", title, std::rand(), ++wc_count); -#else - _snprintf(classNameBuf, sizeof(classNameBuf), "%s_%d-%d", title, std::rand(), ++wc_count); -#endif - classNameBuf[sizeof(classNameBuf)-1] = '\0'; - - impl->wc.style = CS_OWNDC; - impl->wc.lpfnWndProc = wndProc; - impl->wc.cbClsExtra = 0; - impl->wc.cbWndExtra = 0; - impl->wc.hInstance = hInstance; - impl->wc.hIcon = LoadIcon(hInstance, IDI_APPLICATION); - impl->wc.hCursor = LoadCursor(hInstance, IDC_ARROW); - impl->wc.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH); - impl->wc.lpszMenuName = NULL; - impl->wc.lpszClassName = strdup(classNameBuf); - - if (!RegisterClass(&impl->wc)) { - free((void*)impl->wc.lpszClassName); - free(impl); - free(view); - return 1; - } - - int winFlags = WS_POPUPWINDOW | WS_CAPTION; - if (view->resizable) { - winFlags |= WS_SIZEBOX; - if (view->min_width > 0 && view->min_height > 0) { - // Adjust the minimum window size to accomodate requested view size - RECT mr = { 0, 0, view->min_width, view->min_height }; - AdjustWindowRectEx(&mr, view->parent ? WS_CHILD : winFlags, FALSE, WS_EX_TOPMOST); - view->min_width = mr.right - mr.left; - view->min_height = mr.bottom - mr.top; - } - } - - // Adjust the window size to accomodate requested view size - RECT wr = { 0, 0, view->width, view->height }; - AdjustWindowRectEx(&wr, view->parent ? WS_CHILD : winFlags, FALSE, WS_EX_TOPMOST); - - impl->hwnd = CreateWindowEx( - WS_EX_TOPMOST, - classNameBuf, title, - view->parent ? (WS_CHILD | WS_VISIBLE) : winFlags, - CW_USEDEFAULT, CW_USEDEFAULT, wr.right-wr.left, wr.bottom-wr.top, - (HWND)view->parent, NULL, hInstance, NULL); - - if (!impl->hwnd) { - UnregisterClass(impl->wc.lpszClassName, NULL); - free((void*)impl->wc.lpszClassName); - free(impl); - free(view); - return 1; - } - - SetWindowLongPtr(impl->hwnd, GWLP_USERDATA, (LONG_PTR)view); - - impl->hdc = GetDC(impl->hwnd); - - PIXELFORMATDESCRIPTOR pfd; - ZeroMemory(&pfd, sizeof(pfd)); - pfd.nSize = sizeof(pfd); - pfd.nVersion = 1; - pfd.dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER; - pfd.iPixelType = PFD_TYPE_RGBA; - pfd.cColorBits = 24; - pfd.cDepthBits = 16; - pfd.iLayerType = PFD_MAIN_PLANE; - - int format = ChoosePixelFormat(impl->hdc, &pfd); - SetPixelFormat(impl->hdc, format, &pfd); - - impl->hglrc = wglCreateContext(impl->hdc); - if (!impl->hglrc) { - ReleaseDC (impl->hwnd, impl->hdc); - DestroyWindow (impl->hwnd); - UnregisterClass (impl->wc.lpszClassName, NULL); - free((void*)impl->wc.lpszClassName); - free(impl); - free(view); - return 1; - } - - return PUGL_SUCCESS; -} - -void -puglShowWindow(PuglView* view) -{ - ShowWindow(view->impl->hwnd, SW_SHOWNORMAL); -} - -void -puglHideWindow(PuglView* view) -{ - ShowWindow(view->impl->hwnd, SW_HIDE); -} - -void -puglDestroy(PuglView* view) -{ - wglMakeCurrent(NULL, NULL); - wglDeleteContext(view->impl->hglrc); - ReleaseDC(view->impl->hwnd, view->impl->hdc); - DestroyWindow(view->impl->hwnd); - UnregisterClass(view->impl->wc.lpszClassName, NULL); - free((void*)view->impl->wc.lpszClassName); - free(view->impl); - free(view); -} - -static void -puglReshape(PuglView* view, int width, int height) -{ - puglEnterContext(view); - - if (view->reshapeFunc) { - view->reshapeFunc(view, width, height); - } else { - puglDefaultReshape(view, width, height); - } - - view->width = width; - view->height = height; -} - -static void -puglDisplay(PuglView* view) -{ - puglEnterContext(view); - - view->redisplay = false; - if (view->displayFunc) { - view->displayFunc(view); - } - - puglLeaveContext(view, true); -} - -static PuglKey -keySymToSpecial(int sym) -{ - switch (sym) { - case VK_F1: return PUGL_KEY_F1; - case VK_F2: return PUGL_KEY_F2; - case VK_F3: return PUGL_KEY_F3; - case VK_F4: return PUGL_KEY_F4; - case VK_F5: return PUGL_KEY_F5; - case VK_F6: return PUGL_KEY_F6; - case VK_F7: return PUGL_KEY_F7; - case VK_F8: return PUGL_KEY_F8; - case VK_F9: return PUGL_KEY_F9; - case VK_F10: return PUGL_KEY_F10; - case VK_F11: return PUGL_KEY_F11; - case VK_F12: return PUGL_KEY_F12; - case VK_LEFT: return PUGL_KEY_LEFT; - case VK_UP: return PUGL_KEY_UP; - case VK_RIGHT: return PUGL_KEY_RIGHT; - case VK_DOWN: return PUGL_KEY_DOWN; - case VK_PRIOR: return PUGL_KEY_PAGE_UP; - case VK_NEXT: return PUGL_KEY_PAGE_DOWN; - case VK_HOME: return PUGL_KEY_HOME; - case VK_END: return PUGL_KEY_END; - case VK_INSERT: return PUGL_KEY_INSERT; - case VK_SHIFT: return PUGL_KEY_SHIFT; - case VK_CONTROL: return PUGL_KEY_CTRL; - case VK_MENU: return PUGL_KEY_ALT; - case VK_LWIN: return PUGL_KEY_SUPER; - case VK_RWIN: return PUGL_KEY_SUPER; - } - return (PuglKey)0; -} - -static void -processMouseEvent(PuglView* view, int button, bool press, LPARAM lParam) -{ - view->event_timestamp_ms = GetMessageTime(); - if (press) { - SetCapture(view->impl->hwnd); - } else { - ReleaseCapture(); - } - - if (view->mouseFunc) { - view->mouseFunc(view, button, press, - GET_X_LPARAM(lParam), - GET_Y_LPARAM(lParam)); - } -} - -static void -setModifiers(PuglView* view) -{ - view->mods = 0; - view->mods |= (GetKeyState(VK_SHIFT) < 0) ? PUGL_MOD_SHIFT : 0; - view->mods |= (GetKeyState(VK_CONTROL) < 0) ? PUGL_MOD_CTRL : 0; - view->mods |= (GetKeyState(VK_MENU) < 0) ? PUGL_MOD_ALT : 0; - view->mods |= (GetKeyState(VK_LWIN) < 0) ? PUGL_MOD_SUPER : 0; - view->mods |= (GetKeyState(VK_RWIN) < 0) ? PUGL_MOD_SUPER : 0; -} - -static LRESULT -handleMessage(PuglView* view, UINT message, WPARAM wParam, LPARAM lParam) -{ - PAINTSTRUCT ps; - PuglKey key; - RECT rect; - MINMAXINFO* mmi; - - setModifiers(view); - switch (message) { - case WM_CREATE: - case WM_SHOWWINDOW: - case WM_SIZE: - GetClientRect(view->impl->hwnd, &rect); - puglReshape(view, rect.right, rect.bottom); - view->width = rect.right; - view->height = rect.bottom; - break; - case WM_GETMINMAXINFO: - mmi = (MINMAXINFO*)lParam; - mmi->ptMinTrackSize.x = view->min_width; - mmi->ptMinTrackSize.y = view->min_height; - break; - case WM_PAINT: - BeginPaint(view->impl->hwnd, &ps); - puglDisplay(view); - EndPaint(view->impl->hwnd, &ps); - break; - case WM_MOUSEMOVE: - if (view->motionFunc) { - view->event_timestamp_ms = GetMessageTime(); - view->motionFunc(view, GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam)); - } - break; - case WM_LBUTTONDOWN: - processMouseEvent(view, 1, true, lParam); - break; - case WM_MBUTTONDOWN: - processMouseEvent(view, 2, true, lParam); - break; - case WM_RBUTTONDOWN: - processMouseEvent(view, 3, true, lParam); - break; - case WM_LBUTTONUP: - processMouseEvent(view, 1, false, lParam); - break; - case WM_MBUTTONUP: - processMouseEvent(view, 2, false, lParam); - break; - case WM_RBUTTONUP: - processMouseEvent(view, 3, false, lParam); - break; - case WM_MOUSEWHEEL: - if (view->scrollFunc) { - view->event_timestamp_ms = GetMessageTime(); - POINT pt = { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) }; - ScreenToClient(view->impl->hwnd, &pt); - view->scrollFunc( - view, pt.x, pt.y, - 0.0f, GET_WHEEL_DELTA_WPARAM(wParam) / (float)WHEEL_DELTA); - } - break; - case WM_MOUSEHWHEEL: - if (view->scrollFunc) { - view->event_timestamp_ms = GetMessageTime(); - POINT pt = { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) }; - ScreenToClient(view->impl->hwnd, &pt); - view->scrollFunc( - view, pt.x, pt.y, - GET_WHEEL_DELTA_WPARAM(wParam) / (float)WHEEL_DELTA, 0.0f); - } - break; - case WM_KEYDOWN: - if (view->ignoreKeyRepeat && (lParam & (1 << 30))) { - break; - } // else nobreak - case WM_KEYUP: - view->event_timestamp_ms = GetMessageTime(); - if ((key = keySymToSpecial(wParam))) { - if (view->specialFunc) { - view->specialFunc(view, message == WM_KEYDOWN, key); - } - } else if (view->keyboardFunc) { - static BYTE kbs[256]; - if (GetKeyboardState(kbs) != FALSE) { - char lb[2]; - UINT scanCode = (lParam >> 8) & 0xFFFFFF00; - if ( 1 == ToAscii(wParam, scanCode, kbs, (LPWORD)lb, 0)) { - view->keyboardFunc(view, message == WM_KEYDOWN, (char)lb[0]); - } - } - } - break; - case WM_QUIT: - case PUGL_LOCAL_CLOSE_MSG: - if (view->closeFunc) { - view->closeFunc(view); - view->redisplay = false; - } - break; - default: - return DefWindowProc( - view->impl->hwnd, message, wParam, lParam); - } - - return 0; -} - -void -puglGrabFocus(PuglView* /*view*/) -{ - // TODO -} - -PuglStatus -puglProcessEvents(PuglView* view) -{ - MSG msg; - while (PeekMessage(&msg, view->impl->hwnd, 0, 0, PM_REMOVE)) { - handleMessage(view, msg.message, msg.wParam, msg.lParam); - } - - if (view->redisplay) { - InvalidateRect(view->impl->hwnd, NULL, FALSE); - } - - return PUGL_SUCCESS; -} - -LRESULT CALLBACK -wndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) -{ - PuglView* view = (PuglView*)GetWindowLongPtr(hwnd, GWLP_USERDATA); - - switch (message) { - case WM_CREATE: - PostMessage(hwnd, WM_SHOWWINDOW, TRUE, 0); - return 0; - case WM_CLOSE: - PostMessage(hwnd, PUGL_LOCAL_CLOSE_MSG, wParam, lParam); - return 0; - case WM_DESTROY: - return 0; - default: - if (view && hwnd == view->impl->hwnd) { - return handleMessage(view, message, wParam, lParam); - } else { - return DefWindowProc(hwnd, message, wParam, lParam); - } - } -} - -void -puglPostRedisplay(PuglView* view) -{ - view->redisplay = true; -} - -PuglNativeWindow -puglGetNativeWindow(PuglView* view) -{ - return (PuglNativeWindow)view->impl->hwnd; -} - -void* -puglGetContext(PuglView* /*view*/) -{ -#ifdef PUGL_HAVE_CAIRO - if (view->ctx_type == PUGL_CAIRO) { - // TODO - } -#endif - return NULL; -} diff --git a/source/modules/dgl/src/pugl/pugl_x11.c b/source/modules/dgl/src/pugl/pugl_x11.c deleted file mode 100644 index 224324886..000000000 --- a/source/modules/dgl/src/pugl/pugl_x11.c +++ /dev/null @@ -1,636 +0,0 @@ -/* - Copyright 2012-2014 David Robillard - Copyright 2013 Robin Gareus - Copyright 2011-2012 Ben Loftis, Harrison Consoles - - Permission to use, copy, modify, and/or distribute this software for any - purpose with or without fee is hereby granted, provided that the above - copyright notice and this permission notice appear in all copies. - - THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -*/ - -/** - @file pugl_x11.c X11 Pugl Implementation. -*/ - -#include -#include -#include - -#include -#include -#include -#include - -#ifdef PUGL_HAVE_GL -#include -#include -#endif - -#ifdef PUGL_HAVE_CAIRO -#include -#include -#endif - -#include "pugl/pugl_internal.h" - -#ifndef DGL_FILE_BROWSER_DISABLED -#define SOFD_HAVE_X11 -#include "../sofd/libsofd.h" -#include "../sofd/libsofd.c" -#endif - -struct PuglInternalsImpl { - Display* display; - int screen; - Window win; - XIM xim; - XIC xic; -#ifdef PUGL_HAVE_CAIRO - cairo_t* cr; - cairo_surface_t* surface; -#endif -#ifdef PUGL_HAVE_GL - GLXContext ctx; - Bool doubleBuffered; -#endif -}; - -PuglInternals* -puglInitInternals(void) -{ - return (PuglInternals*)calloc(1, sizeof(PuglInternals)); -} - -static XVisualInfo* -getVisual(PuglView* view) -{ - PuglInternals* const impl = view->impl; - XVisualInfo* vi = NULL; - -#ifdef PUGL_HAVE_GL - if (view->ctx_type == PUGL_GL) { - /** - Attributes for single-buffered RGBA with at least - 4 bits per color and a 16 bit depth buffer. - */ - int attrListSgl[] = { - GLX_RGBA, - GLX_RED_SIZE, 4, - GLX_GREEN_SIZE, 4, - GLX_BLUE_SIZE, 4, - GLX_DEPTH_SIZE, 16, - GLX_ARB_multisample, 1, - None - }; - - /** - Attributes for double-buffered RGBA with at least - 4 bits per color and a 16 bit depth buffer. - */ - int attrListDbl[] = { - GLX_RGBA, - GLX_DOUBLEBUFFER, - GLX_RED_SIZE, 4, - GLX_GREEN_SIZE, 4, - GLX_BLUE_SIZE, 4, - GLX_DEPTH_SIZE, 16, - GLX_ARB_multisample, 1, - None - }; - - /** - Attributes for double-buffered RGBA with multi-sampling - (antialiasing) - */ - int attrListDblMS[] = { - GLX_RGBA, - GLX_DOUBLEBUFFER, - GLX_RED_SIZE, 4, - GLX_GREEN_SIZE, 4, - GLX_BLUE_SIZE, 4, - GLX_ALPHA_SIZE, 4, - GLX_DEPTH_SIZE, 16, - GLX_SAMPLE_BUFFERS, 1, - GLX_SAMPLES, 4, - None - }; - - impl->doubleBuffered = True; - - vi = glXChooseVisual(impl->display, impl->screen, attrListDblMS); - - if (vi == NULL) { - vi = glXChooseVisual(impl->display, impl->screen, attrListDbl); - PUGL_LOG("multisampling (antialiasing) is not available\n"); - } - - if (vi == NULL) { - vi = glXChooseVisual(impl->display, impl->screen, attrListSgl); - impl->doubleBuffered = False; - PUGL_LOG("singlebuffered rendering will be used, no doublebuffering available\n"); - } - } -#endif -#ifdef PUGL_HAVE_CAIRO - if (view->ctx_type == PUGL_CAIRO) { - XVisualInfo pat; - int n; - pat.screen = impl->screen; - vi = XGetVisualInfo(impl->display, VisualScreenMask, &pat, &n); - } -#endif - - return vi; -} - -static bool -createContext(PuglView* view, XVisualInfo* vi) -{ - PuglInternals* const impl = view->impl; - -#ifdef PUGL_HAVE_GL - if (view->ctx_type == PUGL_GL) { - impl->ctx = glXCreateContext(impl->display, vi, 0, GL_TRUE); - return (impl->ctx != NULL); - } -#endif -#ifdef PUGL_HAVE_CAIRO - if (view->ctx_type == PUGL_CAIRO) { - impl->surface = cairo_xlib_surface_create( - impl->display, impl->win, vi->visual, view->width, view->height); - if (impl->surface == NULL) { - PUGL_LOG("failed to create cairo surface\n"); - return false; - } - impl->cr = cairo_create(impl->surface); - if (impl->cr == NULL) { - cairo_surface_destroy(impl->surface); - impl->surface = NULL; - PUGL_LOG("failed to create cairo context\n"); - return false; - } - return true; - } -#endif - - return false; -} - -static void -destroyContext(PuglView* view) -{ - PuglInternals* const impl = view->impl; - -#ifdef PUGL_HAVE_GL - if (view->ctx_type == PUGL_GL) { - glXDestroyContext(impl->display, impl->ctx); - impl->ctx = NULL; - } -#endif -#ifdef PUGL_HAVE_CAIRO - if (view->ctx_type == PUGL_CAIRO) { - cairo_destroy(impl->cr); - impl->cr = NULL; - - cairo_surface_destroy(impl->surface); - impl->surface = NULL; - } -#endif -} - -void -puglEnterContext(PuglView* view) -{ -#ifdef PUGL_HAVE_GL - if (view->ctx_type == PUGL_GL) { - glXMakeCurrent(view->impl->display, view->impl->win, view->impl->ctx); - } -#endif -} - -void -puglLeaveContext(PuglView* view, bool flush) -{ -#ifdef PUGL_HAVE_GL - if (view->ctx_type == PUGL_GL) { - if (flush) { - glFlush(); - if (view->impl->doubleBuffered) { - glXSwapBuffers(view->impl->display, view->impl->win); - } - } - glXMakeCurrent(view->impl->display, None, NULL); - } -#endif -} - -int -puglCreateWindow(PuglView* view, const char* title) -{ - PuglInternals* const impl = view->impl; - - impl->display = XOpenDisplay(NULL); - impl->screen = DefaultScreen(impl->display); - - XVisualInfo* const vi = getVisual(view); - if (!vi) { - XCloseDisplay(impl->display); - impl->display = NULL; - return 1; - } - -#ifdef PUGL_HAVE_GL - int glxMajor, glxMinor; - glXQueryVersion(impl->display, &glxMajor, &glxMinor); - PUGL_LOGF("GLX Version %d.%d\n", glxMajor, glxMinor); -#endif - - Window xParent = view->parent - ? (Window)view->parent - : RootWindow(impl->display, impl->screen); - - Colormap cmap = XCreateColormap( - impl->display, xParent, vi->visual, AllocNone); - - XSetWindowAttributes attr; - memset(&attr, 0, sizeof(XSetWindowAttributes)); - attr.background_pixel = BlackPixel(impl->display, impl->screen); - attr.border_pixel = BlackPixel(impl->display, impl->screen); - attr.colormap = cmap; - attr.event_mask = (ExposureMask | StructureNotifyMask | - EnterWindowMask | LeaveWindowMask | - KeyPressMask | KeyReleaseMask | - ButtonPressMask | ButtonReleaseMask | - PointerMotionMask | FocusChangeMask); - - impl->win = XCreateWindow( - impl->display, xParent, - 0, 0, view->width, view->height, 0, vi->depth, InputOutput, vi->visual, - CWBackPixel | CWBorderPixel | CWColormap | CWEventMask, &attr); - - if (!createContext(view, vi)) { - XDestroyWindow(impl->display, impl->win); - impl->win = 0; - - XCloseDisplay(impl->display); - impl->display = NULL; - - return 1; - } - - XSizeHints sizeHints; - memset(&sizeHints, 0, sizeof(sizeHints)); - if (!view->resizable) { - sizeHints.flags = PMinSize|PMaxSize; - sizeHints.min_width = view->width; - sizeHints.min_height = view->height; - sizeHints.max_width = view->width; - sizeHints.max_height = view->height; - XSetNormalHints(impl->display, impl->win, &sizeHints); - } else if (view->min_width > 0 && view->min_height > 0) { - sizeHints.flags = PMinSize; - sizeHints.min_width = view->min_width; - sizeHints.min_height = view->min_height; - XSetNormalHints(impl->display, impl->win, &sizeHints); - } - - if (title) { - XStoreName(impl->display, impl->win, title); - } - - if (!view->parent) { - Atom wmDelete = XInternAtom(impl->display, "WM_DELETE_WINDOW", True); - XSetWMProtocols(impl->display, impl->win, &wmDelete, 1); - } - - if (glXIsDirect(impl->display, impl->ctx)) { - PUGL_LOG("DRI enabled (to disable, set LIBGL_ALWAYS_INDIRECT=1\n"); - } else { - PUGL_LOG("No DRI available\n"); - } - - XFree(vi); - - return PUGL_SUCCESS; -} - -void -puglShowWindow(PuglView* view) -{ - XMapRaised(view->impl->display, view->impl->win); -} - -void -puglHideWindow(PuglView* view) -{ - XUnmapWindow(view->impl->display, view->impl->win); -} - -void -puglDestroy(PuglView* view) -{ - if (!view) { - return; - } - -#ifndef DGL_FILE_BROWSER_DISABLED - x_fib_close(view->impl->display); -#endif - - destroyContext(view); - XDestroyWindow(view->impl->display, view->impl->win); - XCloseDisplay(view->impl->display); - free(view->impl); - free(view); -} - -static void -puglReshape(PuglView* view, int width, int height) -{ - puglEnterContext(view); - - if (view->reshapeFunc) { - view->reshapeFunc(view, width, height); - } else { - puglDefaultReshape(view, width, height); - } - - puglLeaveContext(view, false); - - view->width = width; - view->height = height; -} - -static void -puglDisplay(PuglView* view) -{ - puglEnterContext(view); - - view->redisplay = false; - - if (view->displayFunc) { - view->displayFunc(view); - } - - puglLeaveContext(view, true); -} - -static PuglKey -keySymToSpecial(KeySym sym) -{ - switch (sym) { - case XK_F1: return PUGL_KEY_F1; - case XK_F2: return PUGL_KEY_F2; - case XK_F3: return PUGL_KEY_F3; - case XK_F4: return PUGL_KEY_F4; - case XK_F5: return PUGL_KEY_F5; - case XK_F6: return PUGL_KEY_F6; - case XK_F7: return PUGL_KEY_F7; - case XK_F8: return PUGL_KEY_F8; - case XK_F9: return PUGL_KEY_F9; - case XK_F10: return PUGL_KEY_F10; - case XK_F11: return PUGL_KEY_F11; - case XK_F12: return PUGL_KEY_F12; - case XK_Left: return PUGL_KEY_LEFT; - case XK_Up: return PUGL_KEY_UP; - case XK_Right: return PUGL_KEY_RIGHT; - case XK_Down: return PUGL_KEY_DOWN; - case XK_Page_Up: return PUGL_KEY_PAGE_UP; - case XK_Page_Down: return PUGL_KEY_PAGE_DOWN; - case XK_Home: return PUGL_KEY_HOME; - case XK_End: return PUGL_KEY_END; - case XK_Insert: return PUGL_KEY_INSERT; - case XK_Shift_L: return PUGL_KEY_SHIFT; - case XK_Shift_R: return PUGL_KEY_SHIFT; - case XK_Control_L: return PUGL_KEY_CTRL; - case XK_Control_R: return PUGL_KEY_CTRL; - case XK_Alt_L: return PUGL_KEY_ALT; - case XK_Alt_R: return PUGL_KEY_ALT; - case XK_Super_L: return PUGL_KEY_SUPER; - case XK_Super_R: return PUGL_KEY_SUPER; - } - return (PuglKey)0; -} - -static void -setModifiers(PuglView* view, unsigned xstate, unsigned xtime) -{ - view->event_timestamp_ms = xtime; - - view->mods = 0; - view->mods |= (xstate & ShiftMask) ? PUGL_MOD_SHIFT : 0; - view->mods |= (xstate & ControlMask) ? PUGL_MOD_CTRL : 0; - view->mods |= (xstate & Mod1Mask) ? PUGL_MOD_ALT : 0; - view->mods |= (xstate & Mod4Mask) ? PUGL_MOD_SUPER : 0; -} - -static void -dispatchKey(PuglView* view, XEvent* event, bool press) -{ - KeySym sym; - char str[5]; - PuglKey special; - const int n = XLookupString(&event->xkey, str, 4, &sym, NULL); - - if (sym == XK_Escape && view->closeFunc && !press && !view->parent) { - view->closeFunc(view); - view->redisplay = false; - return; - } - if (n == 0 && sym == 0) { - goto send_event; - return; - } - if (n > 1) { - fprintf(stderr, "warning: Unsupported multi-byte key %X\n", (int)sym); - goto send_event; - return; - } - - special = keySymToSpecial(sym); - if (special && view->specialFunc) { - if (view->specialFunc(view, press, special) == 0) { - return; - } - } else if (!special && view->keyboardFunc) { - if (view->keyboardFunc(view, press, str[0]) == 0) { - return; - } - } - -send_event: - if (view->parent != 0) { - event->xkey.time = 0; // purposefully set an invalid time, used for feedback detection on bad hosts - event->xany.window = view->parent; - XSendEvent(view->impl->display, view->parent, False, NoEventMask, event); - } -} - -PuglStatus -puglProcessEvents(PuglView* view) -{ - XEvent event; - while (XPending(view->impl->display) > 0) { - XNextEvent(view->impl->display, &event); - -#ifndef DGL_FILE_BROWSER_DISABLED - if (x_fib_handle_events(view->impl->display, &event)) { - const int status = x_fib_status(); - - if (status > 0) { - char* const filename = x_fib_filename(); - x_fib_close(view->impl->display); - if (view->fileSelectedFunc) { - view->fileSelectedFunc(view, filename); - } - free(filename); - } else if (status < 0) { - x_fib_close(view->impl->display); - if (view->fileSelectedFunc) { - view->fileSelectedFunc(view, NULL); - } - } - break; - } -#endif - - if (event.xany.window != view->impl->win && - (view->parent == 0 || event.xany.window != (Window)view->parent)) { - continue; - } - if ((event.type == KeyPress || event.type == KeyRelease) && event.xkey.time == 0) { - continue; - } - - switch (event.type) { - case MapNotify: - puglReshape(view, view->width, view->height); - break; - case ConfigureNotify: - if ((event.xconfigure.width != view->width) || - (event.xconfigure.height != view->height)) { - puglReshape(view, - event.xconfigure.width, - event.xconfigure.height); - } - break; - case Expose: - if (event.xexpose.count != 0) { - break; - } - puglDisplay(view); - break; - case MotionNotify: - setModifiers(view, event.xmotion.state, event.xmotion.time); - if (view->motionFunc) { - view->motionFunc(view, event.xmotion.x, event.xmotion.y); - } - break; - case ButtonPress: - setModifiers(view, event.xbutton.state, event.xbutton.time); - if (event.xbutton.button >= 4 && event.xbutton.button <= 7) { - if (view->scrollFunc) { - float dx = 0, dy = 0; - switch (event.xbutton.button) { - case 4: dy = 1.0f; break; - case 5: dy = -1.0f; break; - case 6: dx = -1.0f; break; - case 7: dx = 1.0f; break; - } - view->scrollFunc(view, event.xbutton.x, event.xbutton.y, dx, dy); - } - break; - } - // nobreak - case ButtonRelease: - setModifiers(view, event.xbutton.state, event.xbutton.time); - if (view->mouseFunc && - (event.xbutton.button < 4 || event.xbutton.button > 7)) { - view->mouseFunc(view, - event.xbutton.button, event.type == ButtonPress, - event.xbutton.x, event.xbutton.y); - } - break; - case KeyPress: - setModifiers(view, event.xkey.state, event.xkey.time); - dispatchKey(view, &event, true); - break; - case KeyRelease: { - setModifiers(view, event.xkey.state, event.xkey.time); - bool repeated = false; - if (view->ignoreKeyRepeat && - XEventsQueued(view->impl->display, QueuedAfterReading)) { - XEvent next; - XPeekEvent(view->impl->display, &next); - if (next.type == KeyPress && - next.xkey.time == event.xkey.time && - next.xkey.keycode == event.xkey.keycode) { - XNextEvent(view->impl->display, &event); - repeated = true; - } - } - if (!repeated) { - dispatchKey(view, &event, false); - } - } break; - case ClientMessage: { - char* type = XGetAtomName(view->impl->display, - event.xclient.message_type); - if (!strcmp(type, "WM_PROTOCOLS")) { - if (view->closeFunc) { - view->closeFunc(view); - view->redisplay = false; - } - } - XFree(type); - } break; -#ifdef PUGL_GRAB_FOCUS - case EnterNotify: - XSetInputFocus(view->impl->display, view->impl->win, RevertToPointerRoot, CurrentTime); - break; -#endif - default: - break; - } - } - - if (view->redisplay) { - puglDisplay(view); - } - - return PUGL_SUCCESS; -} - -void -puglPostRedisplay(PuglView* view) -{ - view->redisplay = true; -} - -PuglNativeWindow -puglGetNativeWindow(PuglView* view) -{ - return view->impl->win; -} - -void* -puglGetContext(PuglView* view) -{ -#ifdef PUGL_HAVE_CAIRO - if (view->ctx_type == PUGL_CAIRO) { - return view->impl->cr; - } -#endif - return NULL; - - // may be unused - (void)view; -} diff --git a/source/modules/distrho/DistrhoInfo.hpp b/source/modules/distrho/DistrhoInfo.hpp deleted file mode 100644 index 7a4661e4c..000000000 --- a/source/modules/distrho/DistrhoInfo.hpp +++ /dev/null @@ -1,570 +0,0 @@ -/* - * DISTRHO Plugin Framework (DPF) - * Copyright (C) 2012-2016 Filipe Coelho - * - * Permission to use, copy, modify, and/or distribute this software for any purpose with - * or without fee is hereby granted, provided that the above copyright notice and this - * permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD - * TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN - * NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL - * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER - * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN - * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#ifdef DOXYGEN - -#include "src/DistrhoDefines.h" - -START_NAMESPACE_DISTRHO - -/* ------------------------------------------------------------------------------------------------------------ - * Intro */ - -/** - @mainpage DISTRHO %Plugin Framework - - DISTRHO %Plugin Framework (or @b DPF for short) - is a plugin framework designed to make development of new plugins an easy and enjoyable task.@n - It allows developers to create plugins with custom UIs using a simple C++ API.@n - The framework facilitates exporting various different plugin formats from the same code-base. - - DPF can build for LADSPA, DSSI, LV2 and VST2 formats.@n - A JACK/Standalone mode is also available, allowing you to quickly test plugins. - - @section Macros - You start by creating a "DistrhoPluginInfo.h" file describing the plugin via macros, see @ref PluginMacros.@n - This file is included in the main DPF code to select which features to activate for each plugin format. - - For example, a plugin (with %UI) that use states will require LV2 hosts to support Atom and Worker extensions for - message passing from the %UI to the plugin.@n - If your plugin does not make use of states, the Worker extension is not set as a required feature. - - @section Plugin - The next step is to create your plugin code by subclassing DPF's Plugin class.@n - You need to pass the number of parameters in the constructor and also the number of programs and states, if any. - - Here's an example of an audio plugin that simply mutes the host output: - @code - class MutePlugin : public Plugin - { - public: - /** - Plugin class constructor. - */ - MutePlugin() - : Plugin(0, 0, 0) // 0 parameters, 0 programs and 0 states - { - } - - protected: - /* ---------------------------------------------------------------------------------------- - * Information */ - - /** - Get the plugin label. - This label is a short restricted name consisting of only _, a-z, A-Z and 0-9 characters. - */ - const char* getLabel() const override - { - return "Mute"; - } - - /** - Get the plugin author/maker. - */ - const char* getMaker() const override - { - return "DPF"; - } - - /** - Get the plugin license name (a single line of text). - For commercial plugins this should return some short copyright information. - */ - const char* getLicense() const override - { - return "MIT"; - } - - /** - Get the plugin version, in hexadecimal. - */ - uint32_t getVersion() const override - { - return d_version(1, 0, 0); - } - - /** - Get the plugin unique Id. - This value is used by LADSPA, DSSI and VST plugin formats. - */ - int64_t getUniqueId() const override - { - return d_cconst('M', 'u', 't', 'e'); - } - - /* ---------------------------------------------------------------------------------------- - * This example has no parameters, so skip parameter stuff */ - - void initParameter(uint32_t, Parameter&) override {} - float getParameterValue(uint32_t) const override { return 0.0f; } - void setParameterValue(uint32_t, float) override {} - - /* ---------------------------------------------------------------------------------------- - * Audio/MIDI Processing */ - - /** - Run/process function for plugins without MIDI input. - NOTE: Some parameters might be null if there are no audio inputs or outputs. - */ - void run(const float**, float** outputs, uint32_t frames) override - { - // get the left and right audio outputs - float* const outL = outputs[0]; - float* const outR = outputs[1]; - - // mute audio - std::memset(outL, 0, sizeof(float)*frames); - std::memset(outR, 0, sizeof(float)*frames); - } - - }; - @endcode - - See the Plugin class for more information and to understand what each function does. - - @section Parameters - A plugin is nothing without parameters.@n - In DPF parameters can be inputs or outputs.@n - They have hints to describe how they behave plus a name and a symbol identifying them.@n - Parameters also have 'ranges' – a minimum, maximum and default value. - - Input parameters are "read-only": the plugin can read them but not change them. - (the exception being when changing programs, more on that below)@n - It's the host responsibility to save, restore and set input parameters. - - Output parameters can be changed at anytime by the plugin.@n - The host will simply read their values and not change them. - - Here's an example of an audio plugin that has 1 input parameter: - @code - class GainPlugin : public Plugin - { - public: - /** - Plugin class constructor. - You must set all parameter values to their defaults, matching ParameterRanges::def. - */ - GainPlugin() - : Plugin(1, 0, 0), // 1 parameter, 0 programs and 0 states - fGain(1.0f) - { - } - - protected: - /* ---------------------------------------------------------------------------------------- - * Information */ - - const char* getLabel() const override - { - return "Gain"; - } - - const char* getMaker() const override - { - return "DPF"; - } - - const char* getLicense() const override - { - return "MIT"; - } - - uint32_t getVersion() const override - { - return d_version(1, 0, 0); - } - - int64_t getUniqueId() const override - { - return d_cconst('G', 'a', 'i', 'n'); - } - - /* ---------------------------------------------------------------------------------------- - * Init */ - - /** - Initialize a parameter. - This function will be called once, shortly after the plugin is created. - */ - void initParameter(uint32_t index, Parameter& parameter) override - { - // we only have one parameter so we can skip checking the index - - parameter.hints = kParameterIsAutomable; - parameter.name = "Gain"; - parameter.symbol = "gain"; - parameter.ranges.min = 0.0f; - parameter.ranges.max = 2.0f; - parameter.ranges.def = 1.0f; - } - - /* ---------------------------------------------------------------------------------------- - * Internal data */ - - /** - Get the current value of a parameter. - */ - float getParameterValue(uint32_t index) const override - { - // same as before, ignore index check - - return fGain; - } - - /** - Change a parameter value. - */ - void setParameterValue(uint32_t index, float value) override - { - // same as before, ignore index check - - fGain = value; - } - - /* ---------------------------------------------------------------------------------------- - * Audio/MIDI Processing */ - - void run(const float**, float** outputs, uint32_t frames) override - { - // get the mono input and output - const float* const in = inputs[0]; - /* */ float* const out = outputs[0]; - - // apply gain against all samples - for (uint32_t i=0; i < frames; ++i) - out[i] = in[i] * fGain; - } - - private: - float fGain; - }; - @endcode - - See the Parameter struct for more information about parameters. - - @section Programs - Programs in DPF refer to plugin-side presets (usually called "factory presets"), - an initial set of presets provided by plugin authors included in the actual plugin. - - To use programs you must first enable them by setting @ref DISTRHO_PLUGIN_WANT_PROGRAMS to 1 in your DistrhoPluginInfo.h file.@n - When enabled you'll need to override 2 new function in your plugin code, - Plugin::initProgramName(uint32_t, String&) and Plugin::loadProgram(uint32_t). - - Here's an example of a plugin with a "default" program: - @code - class PluginWithPresets : public Plugin - { - public: - PluginWithPresets() - : Plugin(2, 1, 0), // 2 parameters, 1 program and 0 states - fGainL(1.0f), - fGainR(1.0f), - { - } - - protected: - /* ---------------------------------------------------------------------------------------- - * Information */ - - const char* getLabel() const override - { - return "Prog"; - } - - const char* getMaker() const override - { - return "DPF"; - } - - const char* getLicense() const override - { - return "MIT"; - } - - uint32_t getVersion() const override - { - return d_version(1, 0, 0); - } - - int64_t getUniqueId() const override - { - return d_cconst('P', 'r', 'o', 'g'); - } - - /* ---------------------------------------------------------------------------------------- - * Init */ - - /** - Initialize a parameter. - This function will be called once, shortly after the plugin is created. - */ - void initParameter(uint32_t index, Parameter& parameter) override - { - parameter.hints = kParameterIsAutomable; - parameter.ranges.min = 0.0f; - parameter.ranges.max = 2.0f; - parameter.ranges.def = 1.0f; - - switch (index) - { - case 0; - parameter.name = "Gain Right"; - parameter.symbol = "gainR"; - break; - case 1; - parameter.name = "Gain Left"; - parameter.symbol = "gainL"; - break; - } - } - - /** - Set the name of the program @a index. - This function will be called once, shortly after the plugin is created. - */ - void initProgramName(uint32_t index, String& programName) - { - switch(index) - { - case 0: - programName = "Default"; - break; - } - } - - /* ---------------------------------------------------------------------------------------- - * Internal data */ - - /** - Get the current value of a parameter. - */ - float getParameterValue(uint32_t index) const override - { - switch (index) - { - case 0; - return fGainL; - case 1; - return fGainR; - } - } - - /** - Change a parameter value. - */ - void setParameterValue(uint32_t index, float value) override - { - switch (index) - { - case 0; - fGainL = value; - break; - case 1; - fGainR = value; - break; - } - } - - /** - Load a program. - */ - void loadProgram(uint32_t index) - { - switch(index) - { - case 0: - fGainL = 1.0f; - fGainR = 1.0f; - break; - } - } - - /* ---------------------------------------------------------------------------------------- - * Audio/MIDI Processing */ - - void run(const float**, float** outputs, uint32_t frames) override - { - // get the left and right audio buffers - const float* const inL = inputs[0]; - const float* const inR = inputs[0]; - /* */ float* const outL = outputs[0]; - /* */ float* const outR = outputs[0]; - - // apply gain against all samples - for (uint32_t i=0; i < frames; ++i) - { - outL[i] = inL[i] * fGainL; - outR[i] = inR[i] * fGainR; - } - } - - private: - float fGainL, fGainR; - }; - @endcode - - @section States - describe them - - @section MIDI - describe them - - @section Latency - describe it - - @section Time-Position - describe it - - @section UI - describe them -*/ - -/* ------------------------------------------------------------------------------------------------------------ - * Plugin Macros */ - -/** - @defgroup PluginMacros Plugin Macros - - C Macros that describe your plugin. (defined in the "DistrhoPluginInfo.h" file) - - With these macros you can tell the host what features your plugin requires.@n - Depending on which macros you enable, new functions will be available to call and/or override. - - All values are either integer or strings.@n - For boolean-like values 1 means 'on' and 0 means 'off'. - - The values defined in this group are for documentation purposes only.@n - All macros are disabled by default. - - Only 4 macros are required, they are: - - @ref DISTRHO_PLUGIN_NAME - - @ref DISTRHO_PLUGIN_NUM_INPUTS - - @ref DISTRHO_PLUGIN_NUM_OUTPUTS - - @ref DISTRHO_PLUGIN_URI - @{ - */ - -/** - The plugin name.@n - This is used to identify your plugin before a Plugin instance can be created. - @note This macro is required. - */ -#define DISTRHO_PLUGIN_NAME "Plugin Name" - -/** - Number of audio inputs the plugin has. - @note This macro is required. - */ -#define DISTRHO_PLUGIN_NUM_INPUTS 2 - -/** - Number of audio outputs the plugin has. - @note This macro is required. - */ -#define DISTRHO_PLUGIN_NUM_OUTPUTS 2 - -/** - The plugin URI when exporting in LV2 format. - @note This macro is required. - */ -#define DISTRHO_PLUGIN_URI "urn:distrho:name" - -/** - Wherever the plugin has a custom %UI. - @see DISTRHO_UI_USE_NANOVG - @see UI - */ -#define DISTRHO_PLUGIN_HAS_UI 1 - -/** - Wherever the plugin processing is realtime-safe.@n - TODO - list rtsafe requirements - */ -#define DISTRHO_PLUGIN_IS_RT_SAFE 1 - -/** - Wherever the plugin is a synth.@n - @ref DISTRHO_PLUGIN_WANT_MIDI_INPUT is automatically enabled when this is too. - @see DISTRHO_PLUGIN_WANT_MIDI_INPUT - */ -#define DISTRHO_PLUGIN_IS_SYNTH 1 - -/** - Enable direct access between the %UI and plugin code. - @see UI::getPluginInstancePointer() - @note DO NOT USE THIS UNLESS STRICTLY NECESSARY!! - Try to avoid it at all costs! - */ -#define DISTRHO_PLUGIN_WANT_DIRECT_ACCESS 0 - -/** - Wherever the plugin introduces latency during audio or midi processing. - @see Plugin::setLatency(uint32_t) - */ -#define DISTRHO_PLUGIN_WANT_LATENCY 1 - -/** - Wherever the plugin wants MIDI input.@n - This is automatically enabled if @ref DISTRHO_PLUGIN_IS_SYNTH is true. - */ -#define DISTRHO_PLUGIN_WANT_MIDI_INPUT 1 - -/** - Wherever the plugin wants MIDI output. - @see Plugin::writeMidiEvent(const MidiEvent&) - */ -#define DISTRHO_PLUGIN_WANT_MIDI_OUTPUT 1 - -/** - Wherever the plugin provides its own internal programs. - @see Plugin::initProgramName(uint32_t, String&) - @see Plugin::loadProgram(uint32_t) - */ -#define DISTRHO_PLUGIN_WANT_PROGRAMS 1 - -/** - Wherever the plugin uses internal non-parameter data. - @see Plugin::initState(uint32_t, String&, String&) - @see Plugin::setState(const char*, const char*) - */ -#define DISTRHO_PLUGIN_WANT_STATE 1 - -/** - Wherever the plugin wants time position information from the host. - @see Plugin::getTimePosition() - */ -#define DISTRHO_PLUGIN_WANT_TIMEPOS 1 - -/** - Wherever the %UI uses NanoVG for drawing instead of the default raw OpenGL calls.@n - When enabled your %UI instance will subclass @ref NanoWidget instead of @ref Widget. - */ -#define DISTRHO_UI_USE_NANOVG 1 - -/** - The %UI URI when exporting in LV2 format.@n - By default this is set to @ref DISTRHO_PLUGIN_URI with "#UI" as suffix. - */ -#define DISTRHO_UI_URI DISTRHO_PLUGIN_URI "#UI" - -/** @} */ - -// ----------------------------------------------------------------------------------------------------------- - -END_NAMESPACE_DISTRHO - -#endif // DOXYGEN diff --git a/source/modules/distrho/DistrhoPlugin.hpp b/source/modules/distrho/DistrhoPlugin.hpp index 4e246ac9d..9fdc86aa1 100644 --- a/source/modules/distrho/DistrhoPlugin.hpp +++ b/source/modules/distrho/DistrhoPlugin.hpp @@ -1,6 +1,6 @@ /* * DISTRHO Plugin Framework (DPF) - * Copyright (C) 2012-2018 Filipe Coelho + * Copyright (C) 2012-2022 Filipe Coelho * * Permission to use, copy, modify, and/or distribute this software for any purpose with * or without fee is hereby granted, provided that the above copyright notice and this @@ -35,15 +35,43 @@ START_NAMESPACE_DISTRHO */ /** - Audio port can be used as control voltage (LV2 only). + Audio port can be used as control voltage (LV2 and JACK standalone only). */ static const uint32_t kAudioPortIsCV = 0x1; /** - Audio port should be used as sidechan (LV2 only). + Audio port should be used as sidechan (LV2 and VST3 only). + This hint should not be used with CV style ports. + @note non-sidechain audio ports must exist in the plugin if this flag is set. */ static const uint32_t kAudioPortIsSidechain = 0x2; +/** + CV port has bipolar range (-1 to +1, or -5 to +5 if scaled). + This is merely a hint to tell the host what value range to expect. + */ +static const uint32_t kCVPortHasBipolarRange = 0x10; + +/** + CV port has negative unipolar range (-1 to 0, or -10 to 0 if scaled). + This is merely a hint to tell the host what value range to expect. + */ +static const uint32_t kCVPortHasNegativeUnipolarRange = 0x20; + +/** + CV port has positive unipolar range (0 to +1, or 0 to +10 if scaled). + This is merely a hint to tell the host what value range to expect. + */ +static const uint32_t kCVPortHasPositiveUnipolarRange = 0x40; + +/** + CV port has scaled range to match real values (-5 to +5v bipolar, +/-10 to 0v unipolar). + One other range flag is required if this flag is set. + + When enabled, this makes the port a mod:CVPort, compatible with the MOD Devices platform. + */ +static const uint32_t kCVPortHasScaledRange = 0x80; + /** @} */ /* ------------------------------------------------------------------------------------------------------------ @@ -58,10 +86,14 @@ static const uint32_t kAudioPortIsSidechain = 0x2; */ /** - Parameter is automable (real-time safe). + Parameter is automatable (real-time safe). @see Plugin::setParameterValue(uint32_t, float) */ -static const uint32_t kParameterIsAutomable = 0x01; +static const uint32_t kParameterIsAutomatable = 0x01; + +/** It was a typo, sorry.. */ +DISTRHO_DEPRECATED_BY("kParameterIsAutomatable") +static const uint32_t kParameterIsAutomable = kParameterIsAutomatable; /** Parameter value is boolean.@n @@ -83,9 +115,12 @@ static const uint32_t kParameterIsLogarithmic = 0x08; Parameter is of output type.@n When unset, parameter is assumed to be of input type. - Parameter inputs are changed by the host and must not be changed by the plugin.@n - The only exception being when changing programs, see Plugin::loadProgram().@n + Parameter inputs are changed by the host and typically should not be changed by the plugin.@n + One exception is when changing programs, see Plugin::loadProgram().@n + The other exception is with parameter change requests, see Plugin::requestParameterValueChange().@n Outputs are changed by the plugin and never modified by the host. + + If you are targetting VST2, make sure to order your parameters so that all inputs are before any outputs. */ static const uint32_t kParameterIsOutput = 0x10; @@ -100,6 +135,52 @@ static const uint32_t kParameterIsTrigger = 0x20 | kParameterIsBoolean; /** @} */ +/* ------------------------------------------------------------------------------------------------------------ + * State Hints */ + +/** + @defgroup StateHints State Hints + + Various state hints. + @see State::hints + @{ + */ + +/** + State is visible and readable by hosts that support string-type plugin parameters. + */ +static const uint32_t kStateIsHostReadable = 0x01; + +/** + State is writable by the host, allowing users to arbitrarily change the state.@n + For obvious reasons a writable state is also readable by the host. + */ +static const uint32_t kStateIsHostWritable = 0x02 | kStateIsHostReadable; + +/** + State is a filename path instead of a regular string.@n + The readable and writable hints are required for filenames to work, and thus are automatically set. + */ +static const uint32_t kStateIsFilenamePath = 0x04 | kStateIsHostWritable; + +/** + State is a base64 encoded string. + */ +static const uint32_t kStateIsBase64Blob = 0x08; + +/** + State is for Plugin/DSP side only, meaning there is never a need to notify the UI when it changes. + */ +static const uint32_t kStateIsOnlyForDSP = 0x10; + +/** + State is for UI side only.@n + If the DSP and UI are separate and the UI is not available, this property won't be saved. + */ +static const uint32_t kStateIsOnlyForUI = 0x20; + +/** @} */ + /* ------------------------------------------------------------------------------------------------------------ * Base Plugin structs */ @@ -108,8 +189,61 @@ static const uint32_t kParameterIsTrigger = 0x20 | kParameterIsBoolean; @{ */ +/** + Parameter designation.@n + Allows a parameter to be specially designated for a task, like bypass. + + Each designation is unique, there must be only one parameter that uses it.@n + The use of designated parameters is completely optional. + + @note Designated parameters have strict ranges. + @see ParameterRanges::adjustForDesignation() + */ +enum ParameterDesignation { + /** + Null or unset designation. + */ + kParameterDesignationNull = 0, + + /** + Bypass designation.@n + When on (> 0.5f), it means the plugin must run in a bypassed state. + */ + kParameterDesignationBypass = 1 +}; + +/** + Predefined Port Groups Ids. + + This enumeration provides a few commonly used groups for convenient use in plugins. + For preventing conflicts with user code, negative values are used here. + When rolling your own port groups, you MUST start their group ids from 0 and they MUST be sequential. + + @see PortGroup + */ +enum PredefinedPortGroupsIds { + /** + Null or unset port group. + */ + kPortGroupNone = (uint32_t)-1, + + /** + A single channel audio group. + */ + kPortGroupMono = (uint32_t)-2, + + /** + A 2-channel discrete stereo audio group, + where the 1st audio port is the left channel and the 2nd port is the right channel. + */ + kPortGroupStereo = (uint32_t)-3 +}; + /** Audio Port. + + Can be used as CV port by specifying kAudioPortIsCV in hints,@n + but this is only supported in LV2 and JACK standalone formats. */ struct AudioPort { /** @@ -134,35 +268,23 @@ struct AudioPort { String symbol; /** - Default constructor for a regular audio port. - */ - AudioPort() noexcept - : hints(0x0), - name(), - symbol() {} -}; - -/** - Parameter designation.@n - Allows a parameter to be specially designated for a task, like bypass. - - Each designation is unique, there must be only one parameter that uses it.@n - The use of designated parameters is completely optional. + The group id that this audio/cv port belongs to. + No group is assigned by default. - @note Designated parameters have strict ranges. - @see ParameterRanges::adjustForDesignation() - */ -enum ParameterDesignation { - /** - Null or unset designation. + You can use a group from PredefinedPortGroups or roll your own.@n + When rolling your own port groups, you MUST start their group ids from 0 and they MUST be sequential. + @see PortGroup, Plugin::initPortGroup */ - kParameterDesignationNull = 0, + uint32_t groupId; /** - Bypass designation.@n - When on (> 0.5f), it means the plugin must run in a bypassed state. + Default constructor for a regular audio port. */ - kParameterDesignationBypass = 1 + AudioPort() noexcept + : hints(0x0), + name(), + symbol(), + groupId(kPortGroupNone) {} }; /** @@ -189,7 +311,7 @@ struct ParameterRanges { float max; /** - Default constructor, using 0.0 as minimum, 1.0 as maximum and 0.0 as default. + Default constructor, using 0.0 as default, 0.0 as minimum, 1.0 as maximum. */ ParameterRanges() noexcept : def(0.0f), @@ -226,7 +348,7 @@ struct ParameterRanges { /** Get a fixed value within range. */ - const float& getFixedValue(const float& value) const noexcept + float getFixedValue(const float& value) const noexcept { if (value <= min) return min; @@ -333,9 +455,9 @@ struct ParameterEnumerationValues { /** Array of @ParameterEnumerationValue items.@n - This pointer must be null or have been allocated on the heap with `new`. + This pointer must be null or have been allocated on the heap with `new ParameterEnumerationValue[count]`. */ - const ParameterEnumerationValue* values; + ParameterEnumerationValue* values; /** Default constructor, for zero enumeration values. @@ -349,7 +471,7 @@ struct ParameterEnumerationValues { Constructor using custom values.@n The pointer to @values must have been allocated on the heap with `new`. */ - ParameterEnumerationValues(uint32_t c, bool r, const ParameterEnumerationValue* v) noexcept + ParameterEnumerationValues(uint32_t c, bool r, ParameterEnumerationValue* v) noexcept : count(c), restrictedMode(r), values(v) {} @@ -365,6 +487,8 @@ struct ParameterEnumerationValues { values = nullptr; } } + + DISTRHO_DECLARE_NON_COPYABLE(ParameterEnumerationValues) }; /** @@ -384,6 +508,13 @@ struct Parameter { */ String name; + /** + The short name of this parameter.@n + Used when displaying the parameter name in a very limited space. + @note This value is optional, the full name is used when the short one is missing. + */ + String shortName; + /** The symbol of this parameter.@n A parameter symbol is a short restricted name used as a machine and human readable identifier.@n @@ -399,6 +530,12 @@ struct Parameter { */ String unit; + /** + An extensive description/comment about the parameter. + @note This value is optional and only used for LV2. + */ + String description; + /** Ranges of this parameter.@n The ranges describe the default, minimum and maximum values. @@ -424,18 +561,30 @@ struct Parameter { */ uint8_t midiCC; + /** + The group id that this parameter belongs to. + No group is assigned by default. + + You can use a group from PredefinedPortGroups or roll your own.@n + When rolling your own port groups, you MUST start their group ids from 0 and they MUST be sequential. + @see PortGroup, Plugin::initPortGroup + */ + uint32_t groupId; + /** Default constructor for a null parameter. */ Parameter() noexcept : hints(0x0), name(), + shortName(), symbol(), unit(), ranges(), enumValues(), designation(kParameterDesignationNull), - midiCC(0) {} + midiCC(0), + groupId(kPortGroupNone) {} /** Constructor using custom values. @@ -443,12 +592,14 @@ struct Parameter { Parameter(uint32_t h, const char* n, const char* s, const char* u, float def, float min, float max) noexcept : hints(h), name(n), + shortName(), symbol(s), unit(u), ranges(def, min, max), enumValues(), designation(kParameterDesignationNull), - midiCC(0) {} + midiCC(0), + groupId(kPortGroupNone) {} /** Initialize a parameter for a specific designation. @@ -462,11 +613,13 @@ struct Parameter { case kParameterDesignationNull: break; case kParameterDesignationBypass: - hints = kParameterIsAutomable|kParameterIsBoolean|kParameterIsInteger; - name = "Bypass"; - symbol = "dpf_bypass"; - unit = ""; - midiCC = 0; + hints = kParameterIsAutomatable|kParameterIsBoolean|kParameterIsInteger; + name = "Bypass"; + shortName = "Bypass"; + symbol = "dpf_bypass"; + unit = ""; + midiCC = 0; + groupId = kPortGroupNone; ranges.def = 0.0f; ranges.min = 0.0f; ranges.max = 1.0f; @@ -475,6 +628,83 @@ struct Parameter { } }; +/** + Port Group.@n + Allows to group together audio/cv ports or parameters. + + Each unique group MUST have an unique symbol and a name. + A group can be applied to both inputs and outputs (at the same time). + The same group cannot be used in audio ports and parameters. + + An audio port group logically combines ports which should be considered part of the same stream.@n + For example, two audio ports in a group may form a stereo stream. + + A parameter group provides meta-data to the host to indicate that some parameters belong together. + + The use of port groups is completely optional. + + @see Plugin::initPortGroup, AudioPort::group, Parameter::group + */ +struct PortGroup { + /** + The name of this port group.@n + A port group name can contain any character, but hosts might have a hard time with non-ascii ones.@n + The name doesn't have to be unique within a plugin instance, but it's recommended. + */ + String name; + + /** + The symbol of this port group.@n + A port group symbol is a short restricted name used as a machine and human readable identifier.@n + The first character must be one of _, a-z or A-Z and subsequent characters can be from _, a-z, A-Z and 0-9. + @note Port group symbols MUST be unique within a plugin instance. + */ + String symbol; +}; + +/** + State. + + In DPF states refer to key:value string pairs, used to store arbitrary non-parameter data.@n + By default states are completely internal to the plugin and not visible by the host.@n + Flags can be set to allow hosts to see and/or change them. + + TODO API under construction + */ +struct State { + /** + Hints describing this state. + @note Changing these hints can break compatibility with previously saved data. + @see StateHints + */ + uint32_t hints; + + /** + The key or "symbol" of this state.@n + A state key is a short restricted name used as a machine and human readable identifier. + @note State keys MUST be unique within a plugin instance. + TODO define rules for allowed characters, must be usable as URI non-encoded parameters + */ + String key; + + /** + The default value of this state.@n + Can be left empty if considered a valid initial state. + */ + String defaultValue; + + /** + String representation of this state. + */ + String label; + + /** + An extensive description/comment about this state. + @note This value is optional and only used for LV2. + */ + String description; +}; + /** MIDI event. */ @@ -497,6 +727,9 @@ struct MidiEvent { /** MIDI data.@n If size > kDataSize, dataExt is used (otherwise null). + + When dataExt is used, the event holder is responsible for + keeping the pointer valid during the entirety of the run function. */ uint8_t data[kDataSize]; const uint8_t* dataExt; @@ -507,7 +740,7 @@ struct MidiEvent { The @a playing and @a frame values are always valid.@n BBT values are only valid when @a bbt.valid is true. - This struct is inspired by the JACK Transport API. + This struct is inspired by the [JACK Transport API](https://jackaudio.org/api/structjack__position__t.html). */ struct TimePosition { /** @@ -546,10 +779,11 @@ struct TimePosition { /** Current tick within beat.@n - Should always be > 0 and <= @a ticksPerBeat.@n + Should always be >= 0 and < @a ticksPerBeat.@n The first tick is tick '0'. + @note Fraction part of tick is only available on some plugin formats. */ - int32_t tick; + double tick; /** Number of ticks that have elapsed between frame 0 and the first beat of the current measure. @@ -567,7 +801,7 @@ struct TimePosition { float beatType; /** - Number of ticks within a bar.@n + Number of ticks within a beat.@n Usually a moderately large integer with many denominators, such as 1920.0. */ double ticksPerBeat; @@ -590,6 +824,22 @@ struct TimePosition { beatType(0.0f), ticksPerBeat(0.0), beatsPerMinute(0.0) {} + + /** + Reinitialize this position using the default null initialization. + */ + void clear() noexcept + { + valid = false; + bar = 0; + beat = 0; + tick = 0; + barStartTick = 0.0; + beatsPerBar = 0.0f; + beatType = 0.0f; + ticksPerBeat = 0.0; + beatsPerMinute = 0.0; + } } bbt; /** @@ -599,6 +849,16 @@ struct TimePosition { : playing(false), frame(0), bbt() {} + + /** + Reinitialize this position using the default null initialization. + */ + void clear() noexcept + { + playing = false; + frame = 0; + bbt.clear(); + } }; /** @} */ @@ -670,6 +930,22 @@ public: */ double getSampleRate() const noexcept; + /** + Get the bundle path where the plugin resides. + Can return null if the plugin is not available in a bundle (if it is a single binary). + @see getBinaryFilename + @see getResourcePath + */ + const char* getBundlePath() const noexcept; + + /** + Check if this plugin instance is a "dummy" one used for plugin meta-data/information export.@n + When true no processing will be done, the plugin is created only to extract information.@n + In DPF, LADSPA/DSSI, VST2 and VST3 formats create one global instance per plugin binary + while LV2 creates one when generating turtle meta-data. + */ + bool isDummyInstance() const noexcept; + #if DISTRHO_PLUGIN_WANT_TIMEPOS /** Get the current host transport time position.@n @@ -698,6 +974,37 @@ public: bool writeMidiEvent(const MidiEvent& midiEvent) noexcept; #endif +#if DISTRHO_PLUGIN_WANT_PARAMETER_VALUE_CHANGE_REQUEST + /** + Check if parameter value change requests will work with the current plugin host. + @note This function is only available if DISTRHO_PLUGIN_WANT_PARAMETER_VALUE_CHANGE_REQUEST is enabled. + @see requestParameterValueChange(uint32_t, float) + */ + bool canRequestParameterValueChanges() const noexcept; + + /** + Request a parameter value change from the host. + If successful, this function will automatically trigger a parameter update on the UI side as well. + This function can fail, for example if the host is busy with the parameter for read-only automation. + Some hosts simply do not have this functionality, which can be verified with canRequestParameterValueChanges(). + @note This function is only available if DISTRHO_PLUGIN_WANT_PARAMETER_VALUE_CHANGE_REQUEST is enabled. + */ + bool requestParameterValueChange(uint32_t index, float value) noexcept; +#endif + +#if DISTRHO_PLUGIN_WANT_STATE + /** + Set state value and notify the host about the change.@n + This function will call `setState()` and also trigger an update on the UI side as necessary.@n + It must not be called during run.@n + The state must be host readable. + @note this function does nothing on DSSI plugin format, as DSSI only supports UI->DSP messages. + + TODO API under construction + */ + bool updateStateValue(const char* key, const char* value) noexcept; +#endif + protected: /* -------------------------------------------------------------------------------------------------------- * Information */ @@ -763,7 +1070,14 @@ protected: Initialize the parameter @a index.@n This function will be called once, shortly after the plugin is created. */ - virtual void initParameter(uint32_t index, Parameter& parameter) = 0; + virtual void initParameter(uint32_t index, Parameter& parameter); + + /** + Initialize the port group @a groupId.@n + This function will be called once, + shortly after the plugin is created and all audio ports and parameters have been enumerated. + */ + virtual void initPortGroup(uint32_t groupId, PortGroup& portGroup); #if DISTRHO_PLUGIN_WANT_PROGRAMS /** @@ -776,11 +1090,17 @@ protected: #if DISTRHO_PLUGIN_WANT_STATE /** - Set the state key and default value of @a index.@n + Initialize the state @a index.@n This function will be called once, shortly after the plugin is created.@n Must be implemented by your plugin class only if DISTRHO_PLUGIN_WANT_STATE is enabled. */ - virtual void initState(uint32_t index, String& stateKey, String& defaultStateValue) = 0; + virtual void initState(uint32_t index, State& state); + + DISTRHO_DEPRECATED_BY("initState(uint32_t,State&)") + virtual void initState(uint32_t, String&, String&) {} + + DISTRHO_DEPRECATED_BY("initState(uint32_t,State&)") + virtual bool isStateFile(uint32_t) { return false; } #endif /* -------------------------------------------------------------------------------------------------------- @@ -790,15 +1110,15 @@ protected: Get the current value of a parameter.@n The host may call this function from any context, including realtime processing. */ - virtual float getParameterValue(uint32_t index) const = 0; + virtual float getParameterValue(uint32_t index) const; /** Change a parameter value.@n The host may call this function from any context, including realtime processing.@n - When a parameter is marked as automable, you must ensure no non-realtime operations are performed. + When a parameter is marked as automatable, you must ensure no non-realtime operations are performed. @note This function will only be called for parameter inputs. */ - virtual void setParameterValue(uint32_t index, float value) = 0; + virtual void setParameterValue(uint32_t index, float value); #if DISTRHO_PLUGIN_WANT_PROGRAMS /** @@ -806,7 +1126,7 @@ protected: The host may call this function from any context, including realtime processing.@n Must be implemented by your plugin class only if DISTRHO_PLUGIN_WANT_PROGRAMS is enabled. */ - virtual void loadProgram(uint32_t index) = 0; + virtual void loadProgram(uint32_t index); #endif #if DISTRHO_PLUGIN_WANT_FULL_STATE @@ -816,7 +1136,7 @@ protected: Must be implemented by your plugin class if DISTRHO_PLUGIN_WANT_FULL_STATE is enabled. @note The use of this function breaks compatibility with the DSSI format. */ - virtual String getState(const char* key) const = 0; + virtual String getState(const char* key) const; #endif #if DISTRHO_PLUGIN_WANT_STATE @@ -824,7 +1144,7 @@ protected: Change an internal state @a key to @a value.@n Must be implemented by your plugin class only if DISTRHO_PLUGIN_WANT_STATE is enabled. */ - virtual void setState(const char* key, const char* value) = 0; + virtual void setState(const char* key, const char* value); #endif /* -------------------------------------------------------------------------------------------------------- @@ -845,14 +1165,14 @@ protected: Run/process function for plugins with MIDI input. @note Some parameters might be null if there are no audio inputs/outputs or MIDI events. */ - virtual void run(const float* const* inputs, float** outputs, uint32_t frames, + virtual void run(const float** inputs, float** outputs, uint32_t frames, const MidiEvent* midiEvents, uint32_t midiEventCount) = 0; #else /** Run/process function for plugins without MIDI input. @note Some parameters might be null if there are no audio inputs or outputs. */ - virtual void run(const float* const* inputs, float** outputs, uint32_t frames) = 0; + virtual void run(const float** inputs, float** outputs, uint32_t frames) = 0; #endif /* -------------------------------------------------------------------------------------------------------- @@ -895,7 +1215,10 @@ private: */ /** - TODO. + Create an instance of the Plugin class.@n + This is the entry point for DPF plugins.@n + DPF will call this to either create an instance of your plugin for the host + or to fetch some initial information for internal caching. */ extern Plugin* createPlugin(); diff --git a/source/modules/distrho/DistrhoPluginMain.cpp b/source/modules/distrho/DistrhoPluginMain.cpp index ede06a44f..2676cca9a 100644 --- a/source/modules/distrho/DistrhoPluginMain.cpp +++ b/source/modules/distrho/DistrhoPluginMain.cpp @@ -1,6 +1,6 @@ /* * DISTRHO Plugin Framework (DPF) - * Copyright (C) 2012-2016 Filipe Coelho + * Copyright (C) 2012-2021 Filipe Coelho * * Permission to use, copy, modify, and/or distribute this software for any purpose with * or without fee is hereby granted, provided that the above copyright notice and this @@ -19,14 +19,30 @@ #if defined(DISTRHO_PLUGIN_TARGET_CARLA) # include "src/DistrhoPluginCarla.cpp" #elif defined(DISTRHO_PLUGIN_TARGET_JACK) -# include "src/DistrhoPluginJack.cpp" +# include "src/DistrhoPluginJACK.cpp" #elif (defined(DISTRHO_PLUGIN_TARGET_LADSPA) || defined(DISTRHO_PLUGIN_TARGET_DSSI)) # include "src/DistrhoPluginLADSPA+DSSI.cpp" #elif defined(DISTRHO_PLUGIN_TARGET_LV2) # include "src/DistrhoPluginLV2.cpp" # include "src/DistrhoPluginLV2export.cpp" -#elif defined(DISTRHO_PLUGIN_TARGET_VST) -# include "src/DistrhoPluginVST.cpp" +#elif defined(DISTRHO_PLUGIN_TARGET_VST2) +# include "src/DistrhoPluginVST2.cpp" +#elif defined(DISTRHO_PLUGIN_TARGET_VST3) +# include "src/DistrhoPluginVST3.cpp" +#elif defined(DISTRHO_PLUGIN_TARGET_SHARED) +DISTRHO_PLUGIN_EXPORT DISTRHO_NAMESPACE::Plugin* createSharedPlugin(); +DISTRHO_PLUGIN_EXPORT DISTRHO_NAMESPACE::Plugin* createSharedPlugin() { return DISTRHO_NAMESPACE::createPlugin(); } +#elif defined(DISTRHO_PLUGIN_TARGET_STATIC) +START_NAMESPACE_DISTRHO +Plugin* createStaticPlugin() { return createPlugin(); } +END_NAMESPACE_DISTRHO #else # error unsupported format #endif + +#if defined(DISTRHO_PLUGIN_TARGET_JACK) +# define DISTRHO_IS_STANDALONE 1 +#else +# define DISTRHO_IS_STANDALONE 0 +#endif +#include "src/DistrhoUtils.cpp" diff --git a/source/modules/distrho/DistrhoPluginUtils.hpp b/source/modules/distrho/DistrhoPluginUtils.hpp new file mode 100644 index 000000000..b9ed642e9 --- /dev/null +++ b/source/modules/distrho/DistrhoPluginUtils.hpp @@ -0,0 +1,215 @@ +/* + * DISTRHO Plugin Framework (DPF) + * Copyright (C) 2012-2021 Filipe Coelho + * + * Permission to use, copy, modify, and/or distribute this software for any purpose with + * or without fee is hereby granted, provided that the above copyright notice and this + * permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD + * TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN + * NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER + * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef DISTRHO_PLUGIN_UTILS_HPP_INCLUDED +#define DISTRHO_PLUGIN_UTILS_HPP_INCLUDED + +#include "DistrhoPlugin.hpp" + +START_NAMESPACE_DISTRHO + +/* ------------------------------------------------------------------------------------------------------------ + * Plugin related utilities */ + +/** + @defgroup PluginRelatedUtilities Plugin related utilities + + @{ + */ + +/** + Get the absolute filename of the plugin DSP/UI binary.@n + Under certain systems or plugin formats the binary will be inside the plugin bundle.@n + Also, in some formats or setups, the DSP and UI binaries are in different files. +*/ +const char* getBinaryFilename(); + +/** + Get a string representation of the current plugin format we are building against.@n + This can be "JACK/Standalone", "LADSPA", "DSSI", "LV2", "VST2" or "VST3".@n + This string is purely informational and must not be used to tweak plugin behaviour. + + @note DO NOT CHANGE PLUGIN BEHAVIOUR BASED ON PLUGIN FORMAT. +*/ +const char* getPluginFormatName() noexcept; + +/** + Get the path to where resources are stored within the plugin bundle.@n + Requires a valid plugin bundle path. + + Returns a path inside the bundle where the plugin is meant to store its resources in.@n + This path varies between systems and plugin formats, like so: + + - LV2: /resources (can be stored anywhere inside the bundle really, DPF just uses this one) + - VST2 macOS: /Contents/Resources + - VST2 non-macOS: /resources (see note) + + The other non-mentioned formats do not support bundles.@n + + @note For VST2 on non-macOS systems, this assumes you have your plugin inside a dedicated directory + rather than only shipping with the binary (e.g. /myplugin.dll) +*/ +const char* getResourcePath(const char* bundlePath) noexcept; + +/** @} */ + +/* ------------------------------------------------------------------------------------------------------------ + * Plugin helper classes */ + +/** + @defgroup PluginHelperClasses Plugin helper classes + + @{ + */ + +#if DISTRHO_PLUGIN_NUM_OUTPUTS > 0 +/** + Handy class to help keep audio buffer in sync with incoming MIDI events. + To use it, create a local variable (on the stack) and call nextEvent() until it returns false. + @code + for (AudioMidiSyncHelper amsh(outputs, frames, midiEvents, midiEventCount); amsh.nextEvent();) + { + float* const outL = amsh.outputs[0]; + float* const outR = amsh.outputs[1]; + + for (uint32_t i=0; i= totalFramesUsed, + firstEventFrame, totalFramesUsed, false); + + midiEventCount = 1; + while (midiEventCount < remainingMidiEventCount) + { + if (midiEvents[midiEventCount].frame == firstEventFrame) + ++midiEventCount; + else + break; + } + + frames = firstEventFrame - totalFramesUsed; + remainingFrames -= frames; + remainingMidiEventCount -= midiEventCount; + totalFramesUsed += frames; + return true; + } + +private: + /** @internal */ + uint32_t remainingFrames; + uint32_t remainingMidiEventCount; + uint32_t totalFramesUsed; +}; +#endif + +/** @} */ + +// ----------------------------------------------------------------------------------------------------------- + +END_NAMESPACE_DISTRHO + +#endif // DISTRHO_PLUGIN_UTILS_HPP_INCLUDED diff --git a/source/modules/distrho/DistrhoUI.hpp b/source/modules/distrho/DistrhoUI.hpp index a4ec9e610..e22a656b3 100644 --- a/source/modules/distrho/DistrhoUI.hpp +++ b/source/modules/distrho/DistrhoUI.hpp @@ -1,6 +1,6 @@ /* * DISTRHO Plugin Framework (DPF) - * Copyright (C) 2012-2016 Filipe Coelho + * Copyright (C) 2012-2021 Filipe Coelho * * Permission to use, copy, modify, and/or distribute this software for any purpose with * or without fee is hereby granted, provided that the above copyright notice and this @@ -20,17 +20,42 @@ #include "extra/LeakDetector.hpp" #include "src/DistrhoPluginChecks.h" -#ifndef HAVE_DGL +#ifdef DGL_CAIRO +# include "Cairo.hpp" +#endif +#ifdef DGL_OPENGL +# include "OpenGL.hpp" +#endif +#ifdef DGL_VULKAN +# include "Vulkan.hpp" +#endif + +#if DISTRHO_PLUGIN_HAS_EXTERNAL_UI +# include "../dgl/Base.hpp" # include "extra/ExternalWindow.hpp" typedef DISTRHO_NAMESPACE::ExternalWindow UIWidget; +#elif DISTRHO_UI_USE_CUSTOM +# include DISTRHO_UI_CUSTOM_INCLUDE_PATH +typedef DISTRHO_UI_CUSTOM_WIDGET_TYPE UIWidget; +#elif DISTRHO_UI_USE_CAIRO +# include "../dgl/Cairo.hpp" +typedef DGL_NAMESPACE::CairoTopLevelWidget UIWidget; #elif DISTRHO_UI_USE_NANOVG # include "../dgl/NanoVG.hpp" -typedef DGL_NAMESPACE::NanoWidget UIWidget; +typedef DGL_NAMESPACE::NanoTopLevelWidget UIWidget; #else -# include "../dgl/Widget.hpp" -typedef DGL_NAMESPACE::Widget UIWidget; +# include "../dgl/TopLevelWidget.hpp" +typedef DGL_NAMESPACE::TopLevelWidget UIWidget; #endif +#ifndef DGL_FILE_BROWSER_DISABLED +# include "extra/FileBrowserDialog.hpp" +#endif + +START_NAMESPACE_DGL +class PluginWindow; +END_NAMESPACE_DGL + START_NAMESPACE_DISTRHO /* ------------------------------------------------------------------------------------------------------------ @@ -53,8 +78,13 @@ public: /** UI class constructor. The UI should be initialized to a default state that matches the plugin side. + + When @a automaticallyScale is set to true, DPF will automatically scale up the UI + to fit the host/desktop scale factor.@n + It assumes aspect ratio is meant to be kept. + Manually call setGeometryConstraints instead if keeping UI aspect ratio is not required. */ - UI(uint width = 0, uint height = 0); + UI(uint width = 0, uint height = 0, bool automaticallyScaleAndSetAsMinimumSize = false); /** Destructor. @@ -64,21 +94,67 @@ public: /* -------------------------------------------------------------------------------------------------------- * Host state */ + /** + Check if this UI window is resizable (by the user or window manager). + There are situations where an UI supports resizing but the plugin host does not, so this could return false. + + You might want to add a resize handle for such cases, so the user is still allowed to resize the window. + (programatically resizing a window is always possible, but the same is not true for the window manager) + */ + bool isResizable() const noexcept; + + /** + Get the color used for UI background (i.e. window color) in RGBA format. + Returns 0 by default, in case of error or lack of host support. + + The following example code can be use to extract individual colors: + ``` + const int red = (bgColor >> 24) & 0xff; + const int green = (bgColor >> 16) & 0xff; + const int blue = (bgColor >> 8) & 0xff; + ``` + */ + uint getBackgroundColor() const noexcept; + + /** + Get the color used for UI foreground (i.e. text color) in RGBA format. + Returns 0xffffffff by default, in case of error or lack of host support. + + The following example code can be use to extract individual colors: + ``` + const int red = (fgColor >> 24) & 0xff; + const int green = (fgColor >> 16) & 0xff; + const int blue = (fgColor >> 8) & 0xff; + ``` + */ + uint getForegroundColor() const noexcept; + /** Get the current sample rate used in plugin processing. @see sampleRateChanged(double) */ double getSampleRate() const noexcept; + /** + Get the bundle path where the UI resides.@n + Can return null if the UI is not available in a bundle (if it is a single binary). + @see getBinaryFilename + */ + const char* getBundlePath() const noexcept; + /** editParameter. - @TODO Document this. + + Touch/pressed-down event. + Lets the host know the user is tweaking a parameter. + Required in some hosts to record automation. */ void editParameter(uint32_t index, bool started); /** setParameterValue. - @TODO Document this. + + Change a parameter value in the Plugin. */ void setParameterValue(uint32_t index, float value); @@ -88,17 +164,43 @@ public: @TODO Document this. */ void setState(const char* key, const char* value); + + /** + Request a new file from the host, matching the properties of a state key.@n + This will use the native host file browser if available, otherwise a DPF built-in file browser is used.@n + Response will be sent asynchronously to stateChanged, with the matching key and the new file as the value.@n + It is not possible to know if the action was cancelled by the user. + + @return Success if a file-browser was opened, otherwise false. + @note You cannot request more than one file at a time. + */ + bool requestStateFile(const char* key); #endif #if DISTRHO_PLUGIN_WANT_MIDI_INPUT /** - sendNote. - @TODO Document this. - @note Work in progress. Implemented for DSSI and LV2 formats. + Send a single MIDI note from the UI to the plugin DSP side.@n + A note with zero velocity will be sent as note-off (MIDI 0x80), otherwise note-on (MIDI 0x90). */ void sendNote(uint8_t channel, uint8_t note, uint8_t velocity); #endif +#ifndef DGL_FILE_BROWSER_DISABLED + /** + Open a file browser dialog with this window as transient parent.@n + A few options can be specified to setup the dialog. + + If a path is selected, onFileSelected() will be called with the user chosen path. + If the user cancels or does not pick a file, onFileSelected() will be called with nullptr as filename. + + This function does not block the event loop. + + @note This is exactly the same API as provided by the Window class, + but redeclared here so that non-embed/DGL based UIs can still use file browser related functions. + */ + bool openFileBrowser(const FileBrowserOptions& options = FileBrowserOptions()); +#endif + #if DISTRHO_PLUGIN_WANT_DIRECT_ACCESS /* -------------------------------------------------------------------------------------------------------- * Direct DSP access - DO NOT USE THIS UNLESS STRICTLY NECESSARY!! */ @@ -121,6 +223,13 @@ public: */ static const char* getNextBundlePath() noexcept; + /** + Get the scale factor that will be used for the next UI. + @note: This function is only valid during createUI(), + it will return 1.0 when called from anywhere else. + */ + static double getNextScaleFactor() noexcept; + # if DISTRHO_PLUGIN_HAS_EMBED_UI /** Get the Window Id that will be used for the next created window. @@ -166,36 +275,74 @@ protected: */ virtual void sampleRateChanged(double newSampleRate); -#ifdef HAVE_DGL /* -------------------------------------------------------------------------------------------------------- * UI Callbacks (optional) */ /** - uiIdle. - @TODO Document this. + UI idle function, called to give idle time to the plugin UI directly from the host. + This is called right after OS event handling and Window idle events (within the same cycle). + There are no guarantees in terms of timing. + @see addIdleCallback(IdleCallback*, uint). */ virtual void uiIdle() {} -#ifndef DGL_FILE_BROWSER_DISABLED /** - File browser selected function. - @see Window::fileBrowserSelected(const char*) + Window scale factor function, called when the scale factor changes. + This function is for plugin UIs to be able to override Window::onScaleFactorChanged(double). + + The default implementation does nothing. + WARNING function needs a proper name */ - virtual void uiFileBrowserSelected(const char* filename); -#endif + virtual void uiScaleFactorChanged(double scaleFactor); + +#if !DISTRHO_PLUGIN_HAS_EXTERNAL_UI + /** + Windows focus function, called when the window gains or loses the keyboard focus. + This function is for plugin UIs to be able to override Window::onFocus(bool, CrossingMode). + + The default implementation does nothing. + */ + virtual void uiFocus(bool focus, DGL_NAMESPACE::CrossingMode mode); /** - OpenGL window reshape function, called when parent window is resized. - You can reimplement this function for a custom OpenGL state. - @see Window::onReshape(uint,uint) + Window reshape function, called when the window is resized. + This function is for plugin UIs to be able to override Window::onReshape(uint, uint). + + The plugin UI size will be set right after this function. + The default implementation sets up the drawing context where necessary. + + You should almost never need to override this function. + The most common exception is custom OpenGL setup, but only really needed for custom OpenGL drawing code. */ virtual void uiReshape(uint width, uint height); +#endif // !DISTRHO_PLUGIN_HAS_EXTERNAL_UI + +#ifndef DGL_FILE_BROWSER_DISABLED + /** + Window file selected function, called when a path is selected by the user, as triggered by openFileBrowser(). + This function is for plugin UIs to be able to override Window::onFileSelected(const char*). + + This action happens after the user confirms the action, so the file browser dialog will be closed at this point. + The default implementation does nothing. + + If you need to use files as plugin state, please setup and use states with kStateIsFilenamePath instead. + */ + virtual void uiFileBrowserSelected(const char* filename); +#endif /* -------------------------------------------------------------------------------------------------------- * UI Resize Handling, internal */ +#if DISTRHO_PLUGIN_HAS_EXTERNAL_UI + /** + External Window resize function, called when the window is resized. + This is overriden here so the host knows when the UI is resized by you. + @see ExternalWindow::sizeChanged(uint,uint) + */ + void sizeChanged(uint width, uint height) override; +#else /** - OpenGL widget resize function, called when the widget is resized. + Widget resize function, called when the widget is resized. This is overriden here so the host knows when the UI is resized by you. @see Widget::onResize(const ResizeEvent&) */ @@ -206,16 +353,12 @@ protected: private: struct PrivateData; - PrivateData* const pData; + PrivateData* const uiData; + friend class DGL_NAMESPACE::PluginWindow; friend class UIExporter; - friend class UIExporterWindow; - -#ifdef HAVE_DGL - // these should not be used - void setAbsoluteX(int) const noexcept {} - void setAbsoluteY(int) const noexcept {} - void setAbsolutePos(int, int) const noexcept {} - void setAbsolutePos(const DGL_NAMESPACE::Point&) const noexcept {} +#if !DISTRHO_PLUGIN_HAS_EXTERNAL_UI + /** @internal */ + void requestSizeChange(uint width, uint height) override; #endif DISTRHO_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(UI) diff --git a/source/modules/distrho/DistrhoUIMain.cpp b/source/modules/distrho/DistrhoUIMain.cpp index f5c95db86..9b898bc7a 100644 --- a/source/modules/distrho/DistrhoUIMain.cpp +++ b/source/modules/distrho/DistrhoUIMain.cpp @@ -1,6 +1,6 @@ /* * DISTRHO Plugin Framework (DPF) - * Copyright (C) 2012-2016 Filipe Coelho + * Copyright (C) 2012-2022 Filipe Coelho * * Permission to use, copy, modify, and/or distribute this software for any purpose with * or without fee is hereby granted, provided that the above copyright notice and this @@ -24,8 +24,21 @@ # include "src/DistrhoUIDSSI.cpp" #elif defined(DISTRHO_PLUGIN_TARGET_LV2) # include "src/DistrhoUILV2.cpp" -#elif defined(DISTRHO_PLUGIN_TARGET_VST) +#elif defined(DISTRHO_PLUGIN_TARGET_VST2) +// nothing +#elif defined(DISTRHO_PLUGIN_TARGET_VST3) +# include "src/DistrhoUIVST3.cpp" +#elif defined(DISTRHO_PLUGIN_TARGET_SHARED) || defined(DISTRHO_PLUGIN_TARGET_STATIC) // nothing #else # error unsupported format #endif + +#if !DISTRHO_PLUGIN_WANT_DIRECT_ACCESS && !DISTRHO_PLUGIN_TARGET_JACK && !DISTRHO_PLUGIN_TARGET_VST2 && !DISTRHO_PLUGIN_TARGET_VST3 +# ifdef DISTRHO_PLUGIN_TARGET_DSSI +# define DISTRHO_IS_STANDALONE 1 +# else +# define DISTRHO_IS_STANDALONE 0 +# endif +# include "src/DistrhoUtils.cpp" +#endif diff --git a/source/modules/distrho/DistrhoUtils.hpp b/source/modules/distrho/DistrhoUtils.hpp index a0ada9cc5..a76948662 100644 --- a/source/modules/distrho/DistrhoUtils.hpp +++ b/source/modules/distrho/DistrhoUtils.hpp @@ -1,6 +1,6 @@ /* * DISTRHO Plugin Framework (DPF) - * Copyright (C) 2012-2018 Filipe Coelho + * Copyright (C) 2012-2021 Filipe Coelho * * Permission to use, copy, modify, and/or distribute this software for any purpose with * or without fee is hereby granted, provided that the above copyright notice and this @@ -25,10 +25,6 @@ #include #include - -#undef max -#undef min - #include #ifdef DISTRHO_PROPER_CPP11_SUPPORT @@ -37,7 +33,12 @@ # include #endif -#if defined(DISTRHO_OS_MAC) && ! defined(CARLA_OS_MAC) && ! defined(DISTRHO_PROPER_CPP11_SUPPORT) +#if defined(DISTRHO_OS_WINDOWS) && defined(_MSC_VER) +#include +typedef SSIZE_T ssize_t; +#endif + +#if ! defined(CARLA_MATH_UTILS_HPP_INCLUDED) && ! defined(DISTRHO_PROPER_CPP11_SUPPORT) namespace std { inline float fmin(float __x, float __y) { return __builtin_fminf(__x, __y); } @@ -54,39 +55,57 @@ inline float round(float __x) # define M_PI 3.14159265358979323846 #endif -// ----------------------------------------------------------------------- -// misc functions +#define DISTRHO_MACRO_AS_STRING_VALUE(MACRO) #MACRO +#define DISTRHO_MACRO_AS_STRING(MACRO) DISTRHO_MACRO_AS_STRING_VALUE(MACRO) -/* - * Return a 64-bit number from 4 8-bit numbers. +/* ------------------------------------------------------------------------------------------------------------ + * misc functions */ + +/** + @defgroup MiscellaneousFunctions Miscellaneous functions + + @{ */ -static inline + +/** + Return a 32-bit number from 4 8-bit numbers.@n + The return type is a int64_t for better compatibility with plugin formats that use such numbers. + */ +static inline constexpr int64_t d_cconst(const uint8_t a, const uint8_t b, const uint8_t c, const uint8_t d) noexcept { return (a << 24) | (b << 16) | (c << 8) | (d << 0); } -/* - * Return an hexadecimal representation of a MAJ.MIN.MICRO version number. +/** + Return an hexadecimal representation of a MAJ.MIN.MICRO version number. */ -static inline +static inline constexpr uint32_t d_version(const uint8_t major, const uint8_t minor, const uint8_t micro) noexcept { - return uint32_t(major << 16) | uint32_t(minor << 8) | uint32_t(micro << 0); + return uint32_t(major << 16) | uint32_t(minor << 8) | (micro << 0); } -/* - * Dummy function. +/** + Dummy, no-op function. */ static inline void d_pass() noexcept {} -// ----------------------------------------------------------------------- -// string print functions +/** @} */ -/* - * Print a string to stdout with newline (gray color). - * Does nothing if DEBUG is not defined. +/* ------------------------------------------------------------------------------------------------------------ + * string print functions */ + +/** + @defgroup StringPrintFunctions String print functions + + @{ + */ + +/** + Print a string to stdout with newline (gray color). + Does nothing if DEBUG is not defined. */ #ifndef DEBUG # define d_debug(...) @@ -95,64 +114,64 @@ static inline void d_debug(const char* const fmt, ...) noexcept { try { - ::va_list args; - ::va_start(args, fmt); + va_list args; + va_start(args, fmt); std::fprintf(stdout, "\x1b[30;1m"); std::vfprintf(stdout, fmt, args); std::fprintf(stdout, "\x1b[0m\n"); - ::va_end(args); + va_end(args); } catch (...) {} } #endif -/* - * Print a string to stdout with newline. +/** + Print a string to stdout with newline. */ static inline void d_stdout(const char* const fmt, ...) noexcept { try { - ::va_list args; - ::va_start(args, fmt); + va_list args; + va_start(args, fmt); std::vfprintf(stdout, fmt, args); std::fprintf(stdout, "\n"); - ::va_end(args); + va_end(args); } catch (...) {} } -/* - * Print a string to stderr with newline. +/** + Print a string to stderr with newline. */ static inline void d_stderr(const char* const fmt, ...) noexcept { try { - ::va_list args; - ::va_start(args, fmt); + va_list args; + va_start(args, fmt); std::vfprintf(stderr, fmt, args); std::fprintf(stderr, "\n"); - ::va_end(args); + va_end(args); } catch (...) {} } -/* - * Print a string to stderr with newline (red color). +/** + Print a string to stderr with newline (red color). */ static inline void d_stderr2(const char* const fmt, ...) noexcept { try { - ::va_list args; - ::va_start(args, fmt); + va_list args; + va_start(args, fmt); std::fprintf(stderr, "\x1b[31m"); std::vfprintf(stderr, fmt, args); std::fprintf(stderr, "\x1b[0m\n"); - ::va_end(args); + va_end(args); } catch (...) {} } -/* - * Print a safe assertion error message. +/** + Print a safe assertion error message. */ static inline void d_safe_assert(const char* const assertion, const char* const file, const int line) noexcept @@ -160,8 +179,58 @@ void d_safe_assert(const char* const assertion, const char* const file, const in d_stderr2("assertion failure: \"%s\" in file %s, line %i", assertion, file, line); } -/* - * Print a safe exception error message. +/** + Print a safe assertion error message, with 1 extra signed integer value. + */ +static inline +void d_safe_assert_int(const char* const assertion, const char* const file, + const int line, const int value) noexcept +{ + d_stderr2("assertion failure: \"%s\" in file %s, line %i, value %i", assertion, file, line, value); +} + +/** + Print a safe assertion error message, with 1 extra unsigned integer value. + */ +static inline +void d_safe_assert_uint(const char* const assertion, const char* const file, + const int line, const uint value) noexcept +{ + d_stderr2("assertion failure: \"%s\" in file %s, line %i, value %u", assertion, file, line, value); +} + +/** + Print a safe assertion error message, with 2 extra signed integer values. + */ +static inline +void d_safe_assert_int2(const char* const assertion, const char* const file, + const int line, const int v1, const int v2) noexcept +{ + d_stderr2("assertion failure: \"%s\" in file %s, line %i, v1 %i, v2 %i", assertion, file, line, v1, v2); +} + +/** + Print a safe assertion error message, with 2 extra unsigned integer values. + */ +static inline +void d_safe_assert_uint2(const char* const assertion, const char* const file, + const int line, const uint v1, const uint v2) noexcept +{ + d_stderr2("assertion failure: \"%s\" in file %s, line %i, v1 %u, v2 %u", assertion, file, line, v1, v2); +} + +/** + Print a safe assertion error message, with a custom error message. + */ +static inline +void d_custom_safe_assert(const char* const message, const char* const assertion, const char* const file, + const int line) noexcept +{ + d_stderr2("assertion failure: %s, condition \"%s\" in file %s, line %i", message, assertion, file, line); +} + +/** + Print a safe exception error message. */ static inline void d_safe_exception(const char* const exception, const char* const file, const int line) noexcept @@ -169,12 +238,20 @@ void d_safe_exception(const char* const exception, const char* const file, const d_stderr2("exception caught: \"%s\" in file %s, line %i", exception, file, line); } -// ----------------------------------------------------------------------- -// math functions +/** @} */ -/* - * Safely compare two floating point numbers. - * Returns true if they match. +/* ------------------------------------------------------------------------------------------------------------ + * math functions */ + +/** + @defgroup MathFunctions Math related functions + + @{ + */ + +/** + Safely compare two floating point numbers. + Returns true if they match. */ template static inline @@ -183,9 +260,9 @@ bool d_isEqual(const T& v1, const T& v2) return std::abs(v1-v2) < std::numeric_limits::epsilon(); } -/* - * Safely compare two floating point numbers. - * Returns true if they don't match. +/** + Safely compare two floating point numbers. + Returns true if they don't match. */ template static inline @@ -194,8 +271,8 @@ bool d_isNotEqual(const T& v1, const T& v2) return std::abs(v1-v2) >= std::numeric_limits::epsilon(); } -/* - * Safely check if a floating point number is zero. +/** + Safely check if a floating point number is zero. */ template static inline @@ -204,8 +281,8 @@ bool d_isZero(const T& value) return std::abs(value) < std::numeric_limits::epsilon(); } -/* - * Safely check if a floating point number is not zero. +/** + Safely check if a floating point number is not zero. */ template static inline @@ -214,8 +291,8 @@ bool d_isNotZero(const T& value) return std::abs(value) >= std::numeric_limits::epsilon(); } -/* - * Get next power of 2. +/** + Get next power of 2. */ static inline uint32_t d_nextPowerOf2(uint32_t size) noexcept @@ -232,6 +309,8 @@ uint32_t d_nextPowerOf2(uint32_t size) noexcept return ++size; } +/** @} */ + // ----------------------------------------------------------------------- #ifndef DONT_SET_USING_DISTRHO_NAMESPACE diff --git a/source/modules/distrho/extra/ExternalWindow.hpp b/source/modules/distrho/extra/ExternalWindow.hpp index 8ad2df0f2..1f1c360bb 100644 --- a/source/modules/distrho/extra/ExternalWindow.hpp +++ b/source/modules/distrho/extra/ExternalWindow.hpp @@ -1,6 +1,6 @@ /* * DISTRHO Plugin Framework (DPF) - * Copyright (C) 2012-2016 Filipe Coelho + * Copyright (C) 2012-2021 Filipe Coelho * * Permission to use, copy, modify, and/or distribute this software for any purpose with * or without fee is hereby granted, provided that the above copyright notice and this @@ -19,9 +19,7 @@ #include "String.hpp" -#ifdef DISTRHO_OS_WINDOWS -# error Unsupported platform! -#else +#ifndef DISTRHO_OS_WINDOWS # include # include # include @@ -33,136 +31,544 @@ START_NAMESPACE_DISTRHO // ----------------------------------------------------------------------- // ExternalWindow class +/** + External Window class. + + This is a standalone TopLevelWidget/Window-compatible class, but without any real event handling. + Being compatible with TopLevelWidget/Window, it allows to be used as DPF UI target. + + It can be used to embed non-DPF things or to run a tool in a new process as the "UI". + The uiIdle() function will be called at regular intervals to keep UI running. + There are helper methods in place to launch external tools and keep track of its running state. + + External windows can be setup to run in 3 different modes: + * Embed: + Embed into the host UI, even-loop driven by the host. + This is basically working as a regular plugin UI, as you typically expect them to. + The plugin side does not get control over showing, hiding or closing the window (as usual for plugins). + No restrictions on supported plugin format, everything should work. + Requires DISTRHO_PLUGIN_HAS_EMBED_UI to be set to 1. + + * Semi-external: + The UI is not embed into the host, but the even-loop is still driven by it. + In this mode the host does not have control over the UI except for showing, hiding and setting transient parent. + It is possible to close the window from the plugin, the host will be notified of such case. + Host regularly calls isQuitting() to check if the UI got closed by the user or plugin side. + This mode is only possible in LV2 plugin formats, using lv2ui:showInterface extension. + + * Standalone: + The UI is not embed into the host or uses its event-loop, basically running as standalone. + The host only has control over showing and hiding the window, nothing else. + The UI is still free to close itself at any point. + DPF will keep calling isRunning() to check if it should keep the event-loop running. + Only possible in JACK and DSSI targets, as the UIs are literally standalone applications there. + + Please note that for non-embed windows, you cannot show the window yourself. + The plugin window is only allowed to hide or close itself, a "show" action needs to come from the host. + + A few callbacks are provided so that implementations do not need to care about checking for state changes. + They are not called on construction, but will be everytime something changes either by the host or the window itself. + */ class ExternalWindow { -public: - ExternalWindow(const uint w = 1, const uint h = 1, const char* const t = "") - : width(w), - height(h), - title(t), - pid(0) {} + struct PrivateData; +public: + /** + Constructor. + */ + explicit ExternalWindow() + : pData() {} + + /** + Constructor for DPF internal use. + */ + explicit ExternalWindow(const PrivateData& data) + : pData(data) {} + + /** + Destructor. + */ virtual ~ExternalWindow() { - terminateAndWaitForProcess(); + DISTRHO_SAFE_ASSERT(!pData.visible); + } + + /* -------------------------------------------------------------------------------------------------------- + * ExternalWindow specific calls - Host side calls that you can reimplement for fine-grained funtionality */ + + /** + Check if main-loop is running. + This is used under standalone mode to check whether to keep things running. + Returning false from this function will stop the event-loop and close the window. + */ + virtual bool isRunning() const + { +#ifndef DISTRHO_OS_WINDOWS + if (ext.inUse) + return ext.isRunning(); +#endif + return isVisible(); + } + + /** + Check if we are about to close. + This is used when the event-loop is provided by the host to check if it should close the window. + It is also used in standalone mode right after isRunning() returns false to verify if window needs to be closed. + */ + virtual bool isQuitting() const + { +#ifndef DISTRHO_OS_WINDOWS + return ext.inUse ? ext.isQuitting : pData.isQuitting; +#else + return pData.isQuitting; +#endif } + /** + Get the "native" window handle. + This can be reimplemented in order to pass the native window to hosts that can use such informaton. + + Returned value type depends on the platform: + - HaikuOS: This is a pointer to a `BView`. + - MacOS: This is a pointer to an `NSView*`. + - Windows: This is a `HWND`. + - Everything else: This is an [X11] `Window`. + + @note Only available to override if DISTRHO_PLUGIN_HAS_EMBED_UI is set to 1. + */ + virtual uintptr_t getNativeWindowHandle() const noexcept + { + return 0; + } + + /** + Grab the keyboard input focus. + Typically you would setup OS-native methods to bring the window to front and give it focus. + Default implementation does nothing. + */ + virtual void focus() {} + + /* -------------------------------------------------------------------------------------------------------- + * TopLevelWidget-like calls - Information, can be called by either host or plugin */ + +#if DISTRHO_PLUGIN_HAS_EMBED_UI + /** + Whether this Window is embed into another (usually not DGL-controlled) Window. + */ + bool isEmbed() const noexcept + { + return pData.parentWindowHandle != 0; + } +#endif + + /** + Check if this window is visible. + @see setVisible(bool) + */ + bool isVisible() const noexcept + { + return pData.visible; + } + + /** + Whether this Window is running as standalone, that is, without being coupled to a host event-loop. + When in standalone mode, isRunning() is called to check if the event-loop should keep running. + */ + bool isStandalone() const noexcept + { + return pData.isStandalone; + } + + /** + Get width of this window. + Only relevant to hosts when the UI is embedded. + */ uint getWidth() const noexcept { - return width; + return pData.width; } + /** + Get height of this window. + Only relevant to hosts when the UI is embedded. + */ uint getHeight() const noexcept { - return height; + return pData.height; + } + + /** + Get the scale factor requested for this window. + This is purely informational, and up to developers to choose what to do with it. + */ + double getScaleFactor() const noexcept + { + return pData.scaleFactor; } + /** + Get the title of the window previously set with setTitle(). + This is typically displayed in the title bar or in window switchers. + */ const char* getTitle() const noexcept { - return title; + return pData.title; } - void setTitle(const char* const t) noexcept +#if DISTRHO_PLUGIN_HAS_EMBED_UI + /** + Get the "native" window handle that this window should embed itself into. + Returned value type depends on the platform: + - HaikuOS: This is a pointer to a `BView`. + - MacOS: This is a pointer to an `NSView*`. + - Windows: This is a `HWND`. + - Everything else: This is an [X11] `Window`. + */ + uintptr_t getParentWindowHandle() const noexcept { - title = t; + return pData.parentWindowHandle; } +#endif - bool isRunning() noexcept + /** + Get the transient window that we should attach ourselves to. + TODO what id? also NSView* on macOS, or NSWindow? + */ + uintptr_t getTransientWindowId() const noexcept { - if (pid <= 0) - return false; + return pData.transientWinId; + } - const pid_t p = ::waitpid(pid, nullptr, WNOHANG); + /* -------------------------------------------------------------------------------------------------------- + * TopLevelWidget-like calls - actions called by either host or plugin */ - if (p == pid || (p == -1 && errno == ECHILD)) - { - printf("NOTICE: Child process exited while idle\n"); - pid = 0; - return false; - } + /** + Hide window. + This is the same as calling setVisible(false). + Embed windows should never call this! + @see isVisible(), setVisible(bool) + */ + void hide() + { + setVisible(false); + } + + /** + Hide the UI and gracefully terminate. + Embed windows should never call this! + */ + virtual void close() + { + pData.isQuitting = true; + hide(); +#ifndef DISTRHO_OS_WINDOWS + if (ext.inUse) + terminateAndWaitForExternalProcess(); +#endif + } + + /** + Set width of this window. + Can trigger a sizeChanged callback. + Only relevant to hosts when the UI is embedded. + */ + void setWidth(uint width) + { + setSize(width, getHeight()); + } - return true; + /** + Set height of this window. + Can trigger a sizeChanged callback. + Only relevant to hosts when the UI is embedded. + */ + void setHeight(uint height) + { + setSize(getWidth(), height); + } + + /** + Set size of this window using @a width and @a height values. + Can trigger a sizeChanged callback. + Only relevant to hosts when the UI is embedded. + */ + void setSize(uint width, uint height) + { + DISTRHO_SAFE_ASSERT_UINT_RETURN(width > 1, width,); + DISTRHO_SAFE_ASSERT_UINT_RETURN(height > 1, height,); + + if (pData.width == width && pData.height == height) + return; + + pData.width = width; + pData.height = height; + sizeChanged(width, height); + } + + /** + Set the title of the window, typically displayed in the title bar or in window switchers. + Can trigger a titleChanged callback. + Only relevant to hosts when the UI is not embedded. + */ + void setTitle(const char* title) + { + if (pData.title == title) + return; + + pData.title = title; + titleChanged(title); + } + + /** + Set geometry constraints for the Window when resized by the user. + */ + void setGeometryConstraints(uint minimumWidth, uint minimumHeight, bool keepAspectRatio = false) + { + DISTRHO_SAFE_ASSERT_UINT_RETURN(minimumWidth > 0, minimumWidth,); + DISTRHO_SAFE_ASSERT_UINT_RETURN(minimumHeight > 0, minimumHeight,); + + pData.minWidth = minimumWidth; + pData.minHeight = minimumHeight; + pData.keepAspectRatio = keepAspectRatio; + } + + /* -------------------------------------------------------------------------------------------------------- + * TopLevelWidget-like calls - actions called by the host */ + + /** + Show window. + This is the same as calling setVisible(true). + @see isVisible(), setVisible(bool) + */ + void show() + { + setVisible(true); + } + + /** + Set window visible (or not) according to @a visible. + @see isVisible(), hide(), show() + */ + void setVisible(bool visible) + { + if (pData.visible == visible) + return; + + pData.visible = visible; + visibilityChanged(visible); + } + + /** + Called by the host to set the transient parent window that we should attach ourselves to. + TODO what id? also NSView* on macOS, or NSWindow? + */ + void setTransientWindowId(uintptr_t winId) + { + if (pData.transientWinId == winId) + return; + + pData.transientWinId = winId; + transientParentWindowChanged(winId); } protected: + /* -------------------------------------------------------------------------------------------------------- + * ExternalWindow special calls for running externals tools */ + bool startExternalProcess(const char* args[]) { - terminateAndWaitForProcess(); +#ifndef DISTRHO_OS_WINDOWS + ext.inUse = true; - pid = vfork(); + return ext.start(args); +#else + (void)args; + return false; // TODO +#endif + } - switch (pid) - { - case 0: - execvp(args[0], (char**)args); - _exit(1); - return false; + void terminateAndWaitForExternalProcess() + { +#ifndef DISTRHO_OS_WINDOWS + ext.isQuitting = true; + ext.terminateAndWait(); +#else + // TODO +#endif + } - case -1: - printf("Could not start external ui\n"); - return false; + /* -------------------------------------------------------------------------------------------------------- + * ExternalWindow specific callbacks */ - default: - return true; - } + /** + A callback for when the window size changes. + @note WIP this might need to get fed back into the host somehow. + */ + virtual void sizeChanged(uint /* width */, uint /* height */) + { + // unused, meant for custom implementations } -private: - uint width; - uint height; - String title; - pid_t pid; + /** + A callback for when the window title changes. + @note WIP this might need to get fed back into the host somehow. + */ + virtual void titleChanged(const char* /* title */) + { + // unused, meant for custom implementations + } - friend class UIExporter; + /** + A callback for when the window visibility changes. + @note WIP this might need to get fed back into the host somehow. + */ + virtual void visibilityChanged(bool /* visible */) + { + // unused, meant for custom implementations + } - void terminateAndWaitForProcess() + /** + A callback for when the transient parent window changes. + */ + virtual void transientParentWindowChanged(uintptr_t /* winId */) { - if (pid <= 0) - return; + // unused, meant for custom implementations + } + +private: + friend class PluginWindow; + friend class UI; - printf("Waiting for previous process to stop,,,\n"); +#ifndef DISTRHO_OS_WINDOWS + struct ExternalProcess { + bool inUse; + bool isQuitting; + mutable pid_t pid; - bool sendTerm = true; + ExternalProcess() + : inUse(false), + isQuitting(false), + pid(0) {} - for (pid_t p;;) + bool isRunning() const noexcept { - p = ::waitpid(pid, nullptr, WNOHANG); + if (pid <= 0) + return false; + + const pid_t p = ::waitpid(pid, nullptr, WNOHANG); - switch (p) + if (p == pid || (p == -1 && errno == ECHILD)) + { + d_stdout("NOTICE: Child process exited while idle"); + pid = 0; + return false; + } + + return true; + } + + bool start(const char* args[]) + { + terminateAndWait(); + + pid = vfork(); + + switch (pid) { case 0: - if (sendTerm) - { - sendTerm = false; - ::kill(pid, SIGTERM); - } - break; + execvp(args[0], (char**)args); + _exit(1); + return false; case -1: - if (errno == ECHILD) - { - printf("Done! (no such process)\n"); - pid = 0; - return; - } - break; + d_stderr("Could not start external ui"); + return false; default: - if (p == pid) + return true; + } + } + + void terminateAndWait() + { + if (pid <= 0) + return; + + d_stdout("Waiting for external process to stop,,,"); + + bool sendTerm = true; + + for (pid_t p;;) + { + p = ::waitpid(pid, nullptr, WNOHANG); + + switch (p) { - printf("Done! (clean wait)\n"); - pid = 0; - return; + case 0: + if (sendTerm) + { + sendTerm = false; + ::kill(pid, SIGTERM); + } + break; + + case -1: + if (errno == ECHILD) + { + d_stdout("Done! (no such process)"); + pid = 0; + return; + } + break; + + default: + if (p == pid) + { + d_stdout("Done! (clean wait)"); + pid = 0; + return; + } + break; } - break; - } - // 5 msec - usleep(5*1000); + // 5 msec + usleep(5*1000); + } } - } + } ext; +#endif - DISTRHO_DECLARE_NON_COPY_CLASS(ExternalWindow) + struct PrivateData { + uintptr_t parentWindowHandle; + uintptr_t transientWinId; + uint width; + uint height; + double scaleFactor; + String title; + uint minWidth; + uint minHeight; + bool keepAspectRatio; + bool isQuitting; + bool isStandalone; + bool visible; + + PrivateData() + : parentWindowHandle(0), + transientWinId(0), + width(1), + height(1), + scaleFactor(1.0), + title(), + minWidth(0), + minHeight(0), + keepAspectRatio(false), + isQuitting(false), + isStandalone(false), + visible(false) {} + } pData; + + DISTRHO_DECLARE_NON_COPYABLE(ExternalWindow) }; // ----------------------------------------------------------------------- diff --git a/source/modules/distrho/extra/LeakDetector.hpp b/source/modules/distrho/extra/LeakDetector.hpp index b146efc72..11ede473e 100644 --- a/source/modules/distrho/extra/LeakDetector.hpp +++ b/source/modules/distrho/extra/LeakDetector.hpp @@ -23,7 +23,25 @@ START_NAMESPACE_DISTRHO // ----------------------------------------------------------------------- // The following code was based from juce-core LeakDetector class -// Copyright (C) 2013 Raw Material Software Ltd. + +/** + Copyright (C) 2013 Raw Material Software Ltd. + + Permission is granted to use this software under the terms of the ISC license + http://www.isc.org/downloads/software-support-policy/isc-license/ + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH REGARD + TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND + FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, + OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF + USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER + TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE + OF THIS SOFTWARE. +*/ /** A good old-fashioned C macro concatenation helper. This combines two items (which may themselves be macros) into a single string, @@ -54,13 +72,13 @@ START_NAMESPACE_DISTRHO DISTRHO_NAMESPACE::LeakedObjectDetector DISTRHO_JOIN_MACRO(leakDetector_, ClassName); # define DISTRHO_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(ClassName) \ - DISTRHO_DECLARE_NON_COPY_CLASS(ClassName) \ + DISTRHO_DECLARE_NON_COPYABLE(ClassName) \ DISTRHO_LEAK_DETECTOR(ClassName) #else /** Don't use leak detection on release builds. */ # define DISTRHO_LEAK_DETECTOR(ClassName) # define DISTRHO_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(ClassName) \ - DISTRHO_DECLARE_NON_COPY_CLASS(ClassName) + DISTRHO_DECLARE_NON_COPYABLE(ClassName) #endif //============================================================================== diff --git a/source/modules/distrho/extra/Mutex.hpp b/source/modules/distrho/extra/Mutex.hpp index 2f80515a3..e3d5fba74 100644 --- a/source/modules/distrho/extra/Mutex.hpp +++ b/source/modules/distrho/extra/Mutex.hpp @@ -1,6 +1,6 @@ /* * DISTRHO Plugin Framework (DPF) - * Copyright (C) 2012-2016 Filipe Coelho + * Copyright (C) 2012-2021 Filipe Coelho * * Permission to use, copy, modify, and/or distribute this software for any purpose with * or without fee is hereby granted, provided that the above copyright notice and this @@ -20,6 +20,9 @@ #include "../DistrhoUtils.hpp" #ifdef DISTRHO_OS_WINDOWS +# ifndef NOMINMAX +# define NOMINMAX +# endif # include # include #endif @@ -39,7 +42,7 @@ public: /* * Constructor. */ - Mutex(bool inheritPriority = true) noexcept + Mutex(const bool inheritPriority = true) noexcept : fMutex() { pthread_mutexattr_t attr; @@ -61,9 +64,9 @@ public: /* * Lock the mutex. */ - void lock() const noexcept + bool lock() const noexcept { - pthread_mutex_lock(&fMutex); + return (pthread_mutex_lock(&fMutex) == 0); } /* @@ -86,8 +89,7 @@ public: private: mutable pthread_mutex_t fMutex; - DISTRHO_PREVENT_HEAP_ALLOCATION - DISTRHO_DECLARE_NON_COPY_CLASS(Mutex) + DISTRHO_DECLARE_NON_COPYABLE(Mutex) }; // ----------------------------------------------------------------------- @@ -133,12 +135,13 @@ public: /* * Lock the mutex. */ - void lock() const noexcept + bool lock() const noexcept { #ifdef DISTRHO_OS_WINDOWS EnterCriticalSection(&fSection); + return true; #else - pthread_mutex_lock(&fMutex); + return (pthread_mutex_lock(&fMutex) == 0); #endif } @@ -174,8 +177,7 @@ private: mutable pthread_mutex_t fMutex; #endif - DISTRHO_PREVENT_HEAP_ALLOCATION - DISTRHO_DECLARE_NON_COPY_CLASS(RecursiveMutex) + DISTRHO_DECLARE_NON_COPYABLE(RecursiveMutex) }; // ----------------------------------------------------------------------- @@ -256,7 +258,7 @@ private: volatile bool fTriggered; DISTRHO_PREVENT_HEAP_ALLOCATION - DISTRHO_DECLARE_NON_COPY_CLASS(Signal) + DISTRHO_DECLARE_NON_COPYABLE(Signal) }; // ----------------------------------------------------------------------- @@ -281,7 +283,7 @@ private: const Mutex& fMutex; DISTRHO_PREVENT_HEAP_ALLOCATION - DISTRHO_DECLARE_NON_COPY_CLASS(ScopeLocker) + DISTRHO_DECLARE_NON_COPYABLE(ScopeLocker) }; // ----------------------------------------------------------------------- @@ -295,6 +297,10 @@ public: : fMutex(mutex), fLocked(mutex.tryLock()) {} + ScopeTryLocker(const Mutex& mutex, const bool forceLock) noexcept + : fMutex(mutex), + fLocked(forceLock ? mutex.lock() : mutex.tryLock()) {} + ~ScopeTryLocker() noexcept { if (fLocked) @@ -316,7 +322,7 @@ private: const bool fLocked; DISTRHO_PREVENT_HEAP_ALLOCATION - DISTRHO_DECLARE_NON_COPY_CLASS(ScopeTryLocker) + DISTRHO_DECLARE_NON_COPYABLE(ScopeTryLocker) }; // ----------------------------------------------------------------------- @@ -341,7 +347,7 @@ private: const Mutex& fMutex; DISTRHO_PREVENT_HEAP_ALLOCATION - DISTRHO_DECLARE_NON_COPY_CLASS(ScopeUnlocker) + DISTRHO_DECLARE_NON_COPYABLE(ScopeUnlocker) }; // ----------------------------------------------------------------------- diff --git a/source/modules/distrho/extra/ScopedPointer.hpp b/source/modules/distrho/extra/ScopedPointer.hpp index b8efbf0a2..6349199ca 100644 --- a/source/modules/distrho/extra/ScopedPointer.hpp +++ b/source/modules/distrho/extra/ScopedPointer.hpp @@ -25,7 +25,25 @@ START_NAMESPACE_DISTRHO // ----------------------------------------------------------------------- // The following code was based from juce-core ScopedPointer class -// Copyright (C) 2013 Raw Material Software Ltd. + +/** + Copyright (C) 2013 Raw Material Software Ltd. + + Permission is granted to use this software under the terms of the ISC license + http://www.isc.org/downloads/software-support-policy/isc-license/ + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH REGARD + TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND + FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, + OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF + USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER + TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE + OF THIS SOFTWARE. +*/ //============================================================================== /** @@ -145,6 +163,9 @@ public: /** Returns the object that this ScopedPointer refers to. */ ObjectType* get() const noexcept { return object; } + /** Returns the object that this ScopedPointer refers to. */ + ObjectType& getObject() const noexcept { return *object; } + /** Returns the object that this ScopedPointer refers to. */ ObjectType& operator*() const noexcept { return *object; } diff --git a/source/modules/distrho/extra/Sleep.hpp b/source/modules/distrho/extra/Sleep.hpp index af54ba590..612a940aa 100644 --- a/source/modules/distrho/extra/Sleep.hpp +++ b/source/modules/distrho/extra/Sleep.hpp @@ -20,6 +20,9 @@ #include "../DistrhoUtils.hpp" #ifdef DISTRHO_OS_WINDOWS +# ifndef NOMINMAX +# define NOMINMAX +# endif # include # include #else diff --git a/source/modules/distrho/extra/String.hpp b/source/modules/distrho/extra/String.hpp index 650c5bbde..02383958c 100644 --- a/source/modules/distrho/extra/String.hpp +++ b/source/modules/distrho/extra/String.hpp @@ -1,6 +1,6 @@ /* * DISTRHO Plugin Framework (DPF) - * Copyright (C) 2012-2016 Filipe Coelho + * Copyright (C) 2012-2021 Filipe Coelho * * Permission to use, copy, modify, and/or distribute this software for any purpose with * or without fee is hereby granted, provided that the above copyright notice and this @@ -18,6 +18,7 @@ #define DISTRHO_STRING_HPP_INCLUDED #include "../DistrhoUtils.hpp" +#include "../extra/ScopedSafeLocale.hpp" #include @@ -37,14 +38,16 @@ public: */ explicit String() noexcept : fBuffer(_null()), - fBufferLen(0) {} + fBufferLen(0), + fBufferAlloc(false) {} /* * Simple character. */ explicit String(const char c) noexcept : fBuffer(_null()), - fBufferLen(0) + fBufferLen(0), + fBufferAlloc(false) { char ch[2]; ch[0] = c; @@ -56,20 +59,21 @@ public: /* * Simple char string. */ - explicit String(char* const strBuf, const bool copyData = true) noexcept + explicit String(char* const strBuf, const bool reallocData = true) noexcept : fBuffer(_null()), - fBufferLen(0) + fBufferLen(0), + fBufferAlloc(false) { - if (copyData || strBuf == nullptr) + if (reallocData || strBuf == nullptr) { _dup(strBuf); } else { - fBuffer = strBuf; - fBufferLen = std::strlen(strBuf); + fBuffer = strBuf; + fBufferLen = std::strlen(strBuf); + fBufferAlloc = true; } - } /* @@ -77,7 +81,8 @@ public: */ explicit String(const char* const strBuf) noexcept : fBuffer(_null()), - fBufferLen(0) + fBufferLen(0), + fBufferAlloc(false) { _dup(strBuf); } @@ -87,7 +92,8 @@ public: */ explicit String(const int value) noexcept : fBuffer(_null()), - fBufferLen(0) + fBufferLen(0), + fBufferAlloc(false) { char strBuf[0xff+1]; std::snprintf(strBuf, 0xff, "%d", value); @@ -101,7 +107,8 @@ public: */ explicit String(const unsigned int value, const bool hexadecimal = false) noexcept : fBuffer(_null()), - fBufferLen(0) + fBufferLen(0), + fBufferAlloc(false) { char strBuf[0xff+1]; std::snprintf(strBuf, 0xff, hexadecimal ? "0x%x" : "%u", value); @@ -115,7 +122,8 @@ public: */ explicit String(const long value) noexcept : fBuffer(_null()), - fBufferLen(0) + fBufferLen(0), + fBufferAlloc(false) { char strBuf[0xff+1]; std::snprintf(strBuf, 0xff, "%ld", value); @@ -129,7 +137,8 @@ public: */ explicit String(const unsigned long value, const bool hexadecimal = false) noexcept : fBuffer(_null()), - fBufferLen(0) + fBufferLen(0), + fBufferAlloc(false) { char strBuf[0xff+1]; std::snprintf(strBuf, 0xff, hexadecimal ? "0x%lx" : "%lu", value); @@ -143,7 +152,8 @@ public: */ explicit String(const long long value) noexcept : fBuffer(_null()), - fBufferLen(0) + fBufferLen(0), + fBufferAlloc(false) { char strBuf[0xff+1]; std::snprintf(strBuf, 0xff, "%lld", value); @@ -157,7 +167,8 @@ public: */ explicit String(const unsigned long long value, const bool hexadecimal = false) noexcept : fBuffer(_null()), - fBufferLen(0) + fBufferLen(0), + fBufferAlloc(false) { char strBuf[0xff+1]; std::snprintf(strBuf, 0xff, hexadecimal ? "0x%llx" : "%llu", value); @@ -171,10 +182,16 @@ public: */ explicit String(const float value) noexcept : fBuffer(_null()), - fBufferLen(0) + fBufferLen(0), + fBufferAlloc(false) { char strBuf[0xff+1]; - std::snprintf(strBuf, 0xff, "%f", value); + + { + const ScopedSafeLocale ssl; + std::snprintf(strBuf, 0xff, "%.12g", static_cast(value)); + } + strBuf[0xff] = '\0'; _dup(strBuf); @@ -185,10 +202,16 @@ public: */ explicit String(const double value) noexcept : fBuffer(_null()), - fBufferLen(0) + fBufferLen(0), + fBufferAlloc(false) { char strBuf[0xff+1]; - std::snprintf(strBuf, 0xff, "%g", value); + + { + const ScopedSafeLocale ssl; + std::snprintf(strBuf, 0xff, "%.24g", value); + } + strBuf[0xff] = '\0'; _dup(strBuf); @@ -202,7 +225,8 @@ public: */ String(const String& str) noexcept : fBuffer(_null()), - fBufferLen(0) + fBufferLen(0), + fBufferAlloc(false) { _dup(str.fBuffer); } @@ -217,13 +241,12 @@ public: { DISTRHO_SAFE_ASSERT_RETURN(fBuffer != nullptr,); - if (fBuffer == _null()) - return; - - std::free(fBuffer); + if (fBufferAlloc) + std::free(fBuffer); - fBuffer = nullptr; - fBufferLen = 0; + fBuffer = nullptr; + fBufferLen = 0; + fBufferAlloc = false; } // ------------------------------------------------------------------- @@ -253,6 +276,20 @@ public: return (fBufferLen != 0); } + /* + * Check if the string contains a specific character, case-sensitive. + */ + bool contains(const char c) const noexcept + { + for (std::size_t i=0; i= 0", __FILE__, __LINE__); + d_safe_assert_int("ret >= 0", __FILE__, __LINE__, int(ret)); if (found != nullptr) *found = false; @@ -479,7 +516,7 @@ public: */ String& replace(const char before, const char after) noexcept { - DISTRHO_SAFE_ASSERT_RETURN(before != '\0' && after != '\0', *this); + DISTRHO_SAFE_ASSERT_RETURN(before != '\0' /* && after != '\0' */, *this); for (std::size_t i=0; i < fBufferLen; ++i) { @@ -490,6 +527,29 @@ public: return *this; } + /* + * Remove all occurrences of character 'c', shifting and truncating the string as necessary. + */ + String& remove(const char c) noexcept + { + DISTRHO_SAFE_ASSERT_RETURN(c != '\0', *this); + + if (fBufferLen == 0) + return *this; + + for (std::size_t i=0; i < fBufferLen; ++i) + { + if (fBuffer[i] == c) + { + --fBufferLen; + std::memmove(fBuffer+i, fBuffer+i+1, fBufferLen-i); + } + } + + fBuffer[fBufferLen] = '\0'; + return *this; + } + /* * Truncate the string to size 'n'. */ @@ -498,9 +558,7 @@ public: if (n >= fBufferLen) return *this; - for (std::size_t i=n; i < fBufferLen; ++i) - fBuffer[i] = '\0'; - + fBuffer[n] = '\0'; fBufferLen = n; return *this; @@ -529,7 +587,7 @@ public: } /* - * Convert to all ascii characters to lowercase. + * Convert all ascii characters to lowercase. */ String& toLower() noexcept { @@ -545,7 +603,7 @@ public: } /* - * Convert to all ascii characters to uppercase. + * Convert all ascii characters to uppercase. */ String& toUpper() noexcept { @@ -560,6 +618,36 @@ public: return *this; } + /* + * Create a new string where all non-basic characters are converted to '_'. + * @see toBasic() + */ + String asBasic() const noexcept + { + String s(*this); + return s.toBasic(); + } + + /* + * Create a new string where all ascii characters are converted lowercase. + * @see toLower() + */ + String asLower() const noexcept + { + String s(*this); + return s.toLower(); + } + + /* + * Create a new string where all ascii characters are converted to uppercase. + * @see toUpper() + */ + String asUpper() const noexcept + { + String s(*this); + return s.toUpper(); + } + /* * Direct access to the string buffer (read-only). */ @@ -568,6 +656,20 @@ public: return fBuffer; } + /* + * Get and release the string buffer, while also clearing this string. + * This allows to keep a pointer to the buffer after this object is deleted. + * Result must be freed. + */ + char* getAndReleaseBuffer() noexcept + { + char* ret = fBufferLen > 0 ? fBuffer : nullptr; + fBuffer = _null(); + fBufferLen = 0; + fBufferAlloc = false; + return ret; + } + // ------------------------------------------------------------------- // base64 stuff, based on http://www.adp-gmbh.ch/cpp/common/base64.html // Copyright (C) 2004-2008 René Nyffenegger @@ -579,14 +681,18 @@ public: "abcdefghijklmnopqrstuvwxyz" "0123456789+/"; - const std::size_t kTmpBufSize = std::min(d_nextPowerOf2(dataSize/3), 65536U); +#ifndef _MSC_VER + const std::size_t kTmpBufSize = std::min(d_nextPowerOf2(static_cast(dataSize/3)), 65536U); +#else + constexpr std::size_t kTmpBufSize = 65536U; +#endif const uchar* bytesToEncode((const uchar*)data); uint i=0, j=0; uint charArray3[3], charArray4[4]; - char strBuf[kTmpBufSize+1]; + char strBuf[kTmpBufSize + 1]; strBuf[kTmpBufSize] = '\0'; std::size_t strBufIndex = 0; @@ -711,16 +817,26 @@ public: String& operator+=(const char* const strBuf) noexcept { - if (strBuf == nullptr) + if (strBuf == nullptr || strBuf[0] == '\0') return *this; - const std::size_t newBufSize = fBufferLen + std::strlen(strBuf) + 1; - char newBuf[newBufSize]; + const std::size_t strBufLen = std::strlen(strBuf); + + // for empty strings, we can just take the appended string as our entire data + if (isEmpty()) + { + _dup(strBuf, strBufLen); + return *this; + } + + // we have some data ourselves, reallocate to add the new stuff + char* const newBuf = (char*)realloc(fBuffer, fBufferLen + strBufLen + 1); + DISTRHO_SAFE_ASSERT_RETURN(newBuf != nullptr, *this); - std::strcpy(newBuf, fBuffer); - std::strcat(newBuf, strBuf); + std::memcpy(newBuf + fBufferLen, strBuf, strBufLen + 1); - _dup(newBuf, newBufSize-1); + fBuffer = newBuf; + fBufferLen += strBufLen; return *this; } @@ -732,15 +848,20 @@ public: String operator+(const char* const strBuf) noexcept { - const std::size_t newBufSize = fBufferLen + ((strBuf != nullptr) ? std::strlen(strBuf) : 0) + 1; - char newBuf[newBufSize]; + if (strBuf == nullptr || strBuf[0] == '\0') + return *this; + if (isEmpty()) + return String(strBuf); - std::strcpy(newBuf, fBuffer); + const std::size_t strBufLen = std::strlen(strBuf); + const std::size_t newBufSize = fBufferLen + strBufLen; + char* const newBuf = (char*)malloc(newBufSize + 1); + DISTRHO_SAFE_ASSERT_RETURN(newBuf != nullptr, String()); - if (strBuf != nullptr) - std::strcat(newBuf, strBuf); + std::memcpy(newBuf, fBuffer, fBufferLen); + std::memcpy(newBuf + fBufferLen, strBuf, strBufLen + 1); - return String(newBuf); + return String(newBuf, false); } String operator+(const String& str) noexcept @@ -748,11 +869,18 @@ public: return operator+(str.fBuffer); } + // needed for std::map compatibility + bool operator<(const String& str) const noexcept + { + return std::strcmp(fBuffer, str.fBuffer) < 0; + } + // ------------------------------------------------------------------- private: - char* fBuffer; // the actual string buffer - std::size_t fBufferLen; // string length + char* fBuffer; // the actual string buffer + std::size_t fBufferLen; // string length + bool fBufferAlloc; // wherever the buffer is allocated, not using _null() /* * Static null string. @@ -780,7 +908,7 @@ private: if (std::strcmp(fBuffer, strBuf) == 0) return; - if (fBuffer != _null()) + if (fBufferAlloc) std::free(fBuffer); fBufferLen = (size > 0) ? size : std::strlen(strBuf); @@ -788,28 +916,31 @@ private: if (fBuffer == nullptr) { - fBuffer = _null(); - fBufferLen = 0; + fBuffer = _null(); + fBufferLen = 0; + fBufferAlloc = false; return; } - std::strcpy(fBuffer, strBuf); + fBufferAlloc = true; + std::strcpy(fBuffer, strBuf); fBuffer[fBufferLen] = '\0'; } else { - DISTRHO_SAFE_ASSERT(size == 0); + DISTRHO_SAFE_ASSERT_UINT(size == 0, static_cast(size)); // don't recreate null string - if (fBuffer == _null()) + if (! fBufferAlloc) return; DISTRHO_SAFE_ASSERT(fBuffer != nullptr); std::free(fBuffer); - fBuffer = _null(); - fBufferLen = 0; + fBuffer = _null(); + fBufferLen = 0; + fBufferAlloc = false; } } @@ -821,27 +952,41 @@ private: static inline String operator+(const String& strBefore, const char* const strBufAfter) noexcept { - const char* const strBufBefore = strBefore.buffer(); - const std::size_t newBufSize = strBefore.length() + ((strBufAfter != nullptr) ? std::strlen(strBufAfter) : 0) + 1; - char newBuf[newBufSize]; + if (strBufAfter == nullptr || strBufAfter[0] == '\0') + return strBefore; + if (strBefore.isEmpty()) + return String(strBufAfter); - std::strcpy(newBuf, strBufBefore); - std::strcat(newBuf, strBufAfter); + const std::size_t strBeforeLen = strBefore.length(); + const std::size_t strBufAfterLen = std::strlen(strBufAfter); + const std::size_t newBufSize = strBeforeLen + strBufAfterLen; + char* const newBuf = (char*)malloc(newBufSize + 1); + DISTRHO_SAFE_ASSERT_RETURN(newBuf != nullptr, String()); - return String(newBuf); + std::memcpy(newBuf, strBefore.buffer(), strBeforeLen); + std::memcpy(newBuf + strBeforeLen, strBufAfter, strBufAfterLen + 1); + + return String(newBuf, false); } static inline String operator+(const char* const strBufBefore, const String& strAfter) noexcept { - const char* const strBufAfter = strAfter.buffer(); - const std::size_t newBufSize = ((strBufBefore != nullptr) ? std::strlen(strBufBefore) : 0) + strAfter.length() + 1; - char newBuf[newBufSize]; + if (strAfter.isEmpty()) + return String(strBufBefore); + if (strBufBefore == nullptr || strBufBefore[0] == '\0') + return strAfter; + + const std::size_t strBufBeforeLen = std::strlen(strBufBefore); + const std::size_t strAfterLen = strAfter.length(); + const std::size_t newBufSize = strBufBeforeLen + strAfterLen; + char* const newBuf = (char*)malloc(newBufSize + 1); + DISTRHO_SAFE_ASSERT_RETURN(newBuf != nullptr, String()); - std::strcpy(newBuf, strBufBefore); - std::strcat(newBuf, strBufAfter); + std::memcpy(newBuf, strBufBefore, strBufBeforeLen); + std::memcpy(newBuf + strBufBeforeLen, strAfter.buffer(), strAfterLen + 1); - return String(newBuf); + return String(newBuf, false); } // ----------------------------------------------------------------------- diff --git a/source/modules/distrho/extra/Thread.hpp b/source/modules/distrho/extra/Thread.hpp index b3d92c983..ec6f13d9f 100644 --- a/source/modules/distrho/extra/Thread.hpp +++ b/source/modules/distrho/extra/Thread.hpp @@ -1,6 +1,6 @@ /* * DISTRHO Plugin Framework (DPF) - * Copyright (C) 2012-2016 Filipe Coelho + * Copyright (C) 2012-2021 Filipe Coelho * * Permission to use, copy, modify, and/or distribute this software for any purpose with * or without fee is hereby granted, provided that the above copyright notice and this @@ -88,33 +88,70 @@ public: /* * Start the thread. */ - bool startThread() noexcept + bool startThread(const bool withRealtimePriority = false) noexcept { // check if already running DISTRHO_SAFE_ASSERT_RETURN(! isThreadRunning(), true); + pthread_t handle; + + pthread_attr_t attr; + pthread_attr_init(&attr); + + struct sched_param sched_param; + std::memset(&sched_param, 0, sizeof(sched_param)); + + if (withRealtimePriority) + { + sched_param.sched_priority = 80; + +#ifndef DISTRHO_OS_HAIKU + if (pthread_attr_setscope(&attr, PTHREAD_SCOPE_SYSTEM) == 0 && + pthread_attr_setinheritsched(&attr, PTHREAD_EXPLICIT_SCHED) == 0 && +# ifndef DISTRHO_OS_WINDOWS + (pthread_attr_setschedpolicy(&attr, SCHED_FIFO) == 0 || + pthread_attr_setschedpolicy(&attr, SCHED_RR) == 0) && +# endif + pthread_attr_setschedparam(&attr, &sched_param) == 0) + { + d_stdout("Thread setup with realtime priority successful"); + } + else +#endif + { + d_stdout("Thread setup with realtime priority failed, going with normal priority instead"); + pthread_attr_destroy(&attr); + pthread_attr_init(&attr); + } + } + const MutexLocker ml(fLock); fShouldExit = false; - pthread_t handle; + bool ok = pthread_create(&handle, &attr, _entryPoint, this) == 0; + pthread_attr_destroy(&attr); - if (pthread_create(&handle, nullptr, _entryPoint, this) == 0) - { + if (withRealtimePriority && !ok) + { + d_stdout("Thread with realtime priority failed on creation, going with normal priority instead"); + pthread_attr_init(&attr); + ok = pthread_create(&handle, &attr, _entryPoint, this) == 0; + pthread_attr_destroy(&attr); + } + + DISTRHO_SAFE_ASSERT_RETURN(ok, false); #ifdef PTW32_DLLPORT - DISTRHO_SAFE_ASSERT_RETURN(handle.p != nullptr, false); + DISTRHO_SAFE_ASSERT_RETURN(handle.p != nullptr, false); #else - DISTRHO_SAFE_ASSERT_RETURN(handle != 0, false); + DISTRHO_SAFE_ASSERT_RETURN(handle != 0, false); #endif - pthread_detach(handle); - _copyFrom(handle); + pthread_detach(handle); + _copyFrom(handle); - // wait for thread to start - fSignal.wait(); - return true; - } - - return false; + // wait for thread to start + fSignal.wait(); + return true; } /* @@ -161,10 +198,7 @@ public: _copyTo(threadId); _init(); - try { - pthread_cancel(threadId); - } DISTRHO_SAFE_EXCEPTION("pthread_cancel"); - + pthread_detach(threadId); return false; } } @@ -191,6 +225,14 @@ public: return fName; } + /* + * Returns the Id/handle of the thread. + */ + pthread_t getThreadId() const noexcept + { + return fHandle; + } + /* * Changes the name of the caller thread. */ @@ -201,7 +243,7 @@ public: #ifdef DISTRHO_OS_LINUX prctl(PR_SET_NAME, name, 0, 0, 0); #endif -#if defined(__GLIBC__) && (__GLIBC__ * 1000 + __GLIBC_MINOR__) >= 2012 +#if defined(__GLIBC__) && (__GLIBC__ * 1000 + __GLIBC_MINOR__) >= 2012 && !defined(DISTRHO_OS_GNU_HURD) pthread_setname_np(pthread_self(), name); #endif } @@ -259,7 +301,8 @@ private: */ void _runEntryPoint() noexcept { - setCurrentThreadName(fName); + if (fName.isNotEmpty()) + setCurrentThreadName(fName); // report ready fSignal.signal(); @@ -281,7 +324,7 @@ private: return nullptr; } - DISTRHO_DECLARE_NON_COPY_CLASS(Thread) + DISTRHO_DECLARE_NON_COPYABLE(Thread) }; // ----------------------------------------------------------------------- diff --git a/source/modules/distrho/src/DistrhoDefines.h b/source/modules/distrho/src/DistrhoDefines.h index c14cfb8a2..929593c2d 100644 --- a/source/modules/distrho/src/DistrhoDefines.h +++ b/source/modules/distrho/src/DistrhoDefines.h @@ -1,6 +1,6 @@ /* * DISTRHO Plugin Framework (DPF) - * Copyright (C) 2012-2016 Filipe Coelho + * Copyright (C) 2012-2021 Filipe Coelho * * Permission to use, copy, modify, and/or distribute this software for any purpose with * or without fee is hereby granted, provided that the above copyright notice and this @@ -27,10 +27,12 @@ /* Check OS */ #if defined(WIN32) || defined(_WIN32) || defined(__WIN32__) +# define DISTRHO_API # define DISTRHO_PLUGIN_EXPORT extern "C" __declspec (dllexport) # define DISTRHO_OS_WINDOWS 1 # define DISTRHO_DLL_EXTENSION "dll" #else +# define DISTRHO_API # define DISTRHO_PLUGIN_EXPORT extern "C" __attribute__ ((visibility("default"))) # if defined(__APPLE__) # define DISTRHO_OS_MAC 1 @@ -39,6 +41,10 @@ # define DISTRHO_OS_HAIKU 1 # elif defined(__linux__) || defined(__linux) # define DISTRHO_OS_LINUX 1 +# elif defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__) +# define DISTRHO_OS_BSD 1 +# elif defined(__GNU__) +# define DISTRHO_OS_GNU_HURD 1 # endif #endif @@ -51,26 +57,82 @@ # if HAVE_CPP11_SUPPORT # define DISTRHO_PROPER_CPP11_SUPPORT # endif -#elif __cplusplus >= 201103L || (defined(__GNUC__) && defined(__GXX_EXPERIMENTAL_CXX0X__) && (__GNUC__ * 100 + __GNUC_MINOR__) >= 405) || __has_extension(cxx_noexcept) +#elif __cplusplus >= 201103L || (defined(__GNUC__) && defined(__GXX_EXPERIMENTAL_CXX0X__) && (__GNUC__ * 100 + __GNUC_MINOR__) >= 405) || __has_extension(cxx_noexcept) || (defined(_MSC_VER) && _MSVC_LANG >= 201103L) # define DISTRHO_PROPER_CPP11_SUPPORT # if (defined(__GNUC__) && (__GNUC__ * 100 + __GNUC_MINOR__) < 407 && ! defined(__clang__)) || (defined(__clang__) && ! __has_extension(cxx_override_control)) -# define override // gcc4.7+ only -# define final // gcc4.7+ only +# define override /* gcc4.7+ only */ +# define final /* gcc4.7+ only */ # endif #endif #ifndef DISTRHO_PROPER_CPP11_SUPPORT +# define constexpr # define noexcept throw() # define override # define final # define nullptr NULL #endif +/* Define unlikely */ +#ifdef __GNUC__ +# define unlikely(x) __builtin_expect(x,0) +#else +# define unlikely(x) x +#endif + +/* Define DISTRHO_DEPRECATED */ +#if defined(__GNUC__) && (__GNUC__ * 100 + __GNUC_MINOR__) >= 480 +# define DISTRHO_DEPRECATED __attribute__((deprecated)) +#elif defined(_MSC_VER) +# define DISTRHO_DEPRECATED [[deprecated]] /* Note: __declspec(deprecated) it not applicable to enum members */ +#else +# define DISTRHO_DEPRECATED +#endif + +/* Define DISTRHO_DEPRECATED_BY */ +#if defined(__clang__) && defined(DISTRHO_PROPER_CPP11_SUPPORT) +# define DISTRHO_DEPRECATED_BY(other) __attribute__((deprecated("", other))) +#elif defined(__GNUC__) && (__GNUC__ * 100 + __GNUC_MINOR__) >= 480 +# define DISTRHO_DEPRECATED_BY(other) __attribute__((deprecated("Use " other))) +#else +# define DISTRHO_DEPRECATED_BY(other) DISTRHO_DEPRECATED +#endif + /* Define DISTRHO_SAFE_ASSERT* */ -#define DISTRHO_SAFE_ASSERT(cond) if (! (cond)) d_safe_assert(#cond, __FILE__, __LINE__); -#define DISTRHO_SAFE_ASSERT_BREAK(cond) if (! (cond)) { d_safe_assert(#cond, __FILE__, __LINE__); break; } -#define DISTRHO_SAFE_ASSERT_CONTINUE(cond) if (! (cond)) { d_safe_assert(#cond, __FILE__, __LINE__); continue; } -#define DISTRHO_SAFE_ASSERT_RETURN(cond, ret) if (! (cond)) { d_safe_assert(#cond, __FILE__, __LINE__); return ret; } +#define DISTRHO_SAFE_ASSERT(cond) if (unlikely(!(cond))) d_safe_assert (#cond, __FILE__, __LINE__); +#define DISTRHO_SAFE_ASSERT_INT(cond, value) if (unlikely(!(cond))) d_safe_assert_int (#cond, __FILE__, __LINE__, static_cast(value)); +#define DISTRHO_SAFE_ASSERT_INT2(cond, v1, v2) if (unlikely(!(cond))) d_safe_assert_int2 (#cond, __FILE__, __LINE__, static_cast(v1), static_cast(v2)); +#define DISTRHO_SAFE_ASSERT_UINT(cond, value) if (unlikely(!(cond))) d_safe_assert_uint (#cond, __FILE__, __LINE__, static_cast(value)); +#define DISTRHO_SAFE_ASSERT_UINT2(cond, v1, v2) if (unlikely(!(cond))) d_safe_assert_uint2(#cond, __FILE__, __LINE__, static_cast(v1), static_cast(v2)); + +#define DISTRHO_SAFE_ASSERT_BREAK(cond) if (unlikely(!(cond))) { d_safe_assert(#cond, __FILE__, __LINE__); break; } +#define DISTRHO_SAFE_ASSERT_CONTINUE(cond) if (unlikely(!(cond))) { d_safe_assert(#cond, __FILE__, __LINE__); continue; } +#define DISTRHO_SAFE_ASSERT_RETURN(cond, ret) if (unlikely(!(cond))) { d_safe_assert(#cond, __FILE__, __LINE__); return ret; } + +#define DISTRHO_CUSTOM_SAFE_ASSERT(msg, cond) if (unlikely(!(cond))) d_custom_safe_assert(msg, #cond, __FILE__, __LINE__); +#define DISTRHO_CUSTOM_SAFE_ASSERT_BREAK(msg, cond) if (unlikely(!(cond))) { d_custom_safe_assert(msg, #cond, __FILE__, __LINE__); break; } +#define DISTRHO_CUSTOM_SAFE_ASSERT_CONTINUE(msg, cond) if (unlikely(!(cond))) { d_custom_safe_assert(msg, #cond, __FILE__, __LINE__); continue; } +#define DISTRHO_CUSTOM_SAFE_ASSERT_RETURN(msg, cond, ret) if (unlikely(!(cond))) { d_custom_safe_assert(msg, #cond, __FILE__, __LINE__); return ret; } + +#define DISTRHO_CUSTOM_SAFE_ASSERT_ONCE_BREAK(msg, cond) if (unlikely(!(cond))) { static bool _p; if (!_p) { _p = true; d_custom_safe_assert(msg, #cond, __FILE__, __LINE__); } break; } +#define DISTRHO_CUSTOM_SAFE_ASSERT_ONCE_CONTINUE(msg, cond) if (unlikely(!(cond))) { static bool _p; if (!_p) { _p = true; d_custom_safe_assert(msg, #cond, __FILE__, __LINE__); } continue; } +#define DISTRHO_CUSTOM_SAFE_ASSERT_ONCE_RETURN(msg, cond, ret) if (unlikely(!(cond))) { static bool _p; if (!_p) { _p = true; d_custom_safe_assert(msg, #cond, __FILE__, __LINE__); } return ret; } + +#define DISTRHO_SAFE_ASSERT_INT_BREAK(cond, value) if (unlikely(!(cond))) { d_safe_assert_int(#cond, __FILE__, __LINE__, static_cast(value)); break; } +#define DISTRHO_SAFE_ASSERT_INT_CONTINUE(cond, value) if (unlikely(!(cond))) { d_safe_assert_int(#cond, __FILE__, __LINE__, static_cast(value)); continue; } +#define DISTRHO_SAFE_ASSERT_INT_RETURN(cond, value, ret) if (unlikely(!(cond))) { d_safe_assert_int(#cond, __FILE__, __LINE__, static_cast(value)); return ret; } + +#define DISTRHO_SAFE_ASSERT_INT2_BREAK(cond, v1, v2) if (unlikely(!(cond))) { d_safe_assert_int2(#cond, __FILE__, __LINE__, static_cast(v1), static_cast(v2)); break; } +#define DISTRHO_SAFE_ASSERT_INT2_CONTINUE(cond, v1, v2) if (unlikely(!(cond))) { d_safe_assert_int2(#cond, __FILE__, __LINE__, static_cast(v1), static_cast(v2)); continue; } +#define DISTRHO_SAFE_ASSERT_INT2_RETURN(cond, v1, v2, ret) if (unlikely(!(cond))) { d_safe_assert_int2(#cond, __FILE__, __LINE__, static_cast(v1), static_cast(v2)); return ret; } + +#define DISTRHO_SAFE_ASSERT_UINT_BREAK(cond, value) if (unlikely(!(cond))) { d_safe_assert_uint(#cond, __FILE__, __LINE__, static_cast(value)); break; } +#define DISTRHO_SAFE_ASSERT_UINT_CONTINUE(cond, value) if (unlikely(!(cond))) { d_safe_assert_uint(#cond, __FILE__, __LINE__, static_cast(value)); continue; } +#define DISTRHO_SAFE_ASSERT_UINT_RETURN(cond, value, ret) if (unlikely(!(cond))) { d_safe_assert_uint(#cond, __FILE__, __LINE__, static_cast(value)); return ret; } + +#define DISTRHO_SAFE_ASSERT_UINT2_BREAK(cond, v1, v2) if (unlikely(!(cond))) { d_safe_assert_uint2(#cond, __FILE__, __LINE__, static_cast(v1), static_cast(v2)); break; } +#define DISTRHO_SAFE_ASSERT_UINT2_CONTINUE(cond, v1, v2) if (unlikely(!(cond))) { d_safe_assert_uint2(#cond, __FILE__, __LINE__, static_cast(v1), static_cast(v2)); continue; } +#define DISTRHO_SAFE_ASSERT_UINT2_RETURN(cond, v1, v2, ret) if (unlikely(!(cond))) { d_safe_assert_uint2(#cond, __FILE__, __LINE__, static_cast(v1), static_cast(v2)); return ret; } /* Define DISTRHO_SAFE_EXCEPTION */ #define DISTRHO_SAFE_EXCEPTION(msg) catch(...) { d_safe_exception(msg, __FILE__, __LINE__); } @@ -78,34 +140,23 @@ #define DISTRHO_SAFE_EXCEPTION_CONTINUE(msg) catch(...) { d_safe_exception(msg, __FILE__, __LINE__); continue; } #define DISTRHO_SAFE_EXCEPTION_RETURN(msg, ret) catch(...) { d_safe_exception(msg, __FILE__, __LINE__); return ret; } -/* Define DISTRHO_DECLARE_NON_COPY_CLASS */ +/* Define DISTRHO_DECLARE_NON_COPYABLE */ #ifdef DISTRHO_PROPER_CPP11_SUPPORT -# define DISTRHO_DECLARE_NON_COPY_CLASS(ClassName) \ -private: \ - ClassName(ClassName&) = delete; \ - ClassName(const ClassName&) = delete; \ - ClassName& operator=(ClassName&) = delete ; \ +# define DISTRHO_DECLARE_NON_COPYABLE(ClassName) \ +private: \ + ClassName(ClassName&) = delete; \ + ClassName(const ClassName&) = delete; \ + ClassName& operator=(ClassName&) = delete; \ ClassName& operator=(const ClassName&) = delete; #else -# define DISTRHO_DECLARE_NON_COPY_CLASS(ClassName) \ -private: \ - ClassName(ClassName&); \ - ClassName(const ClassName&); \ - ClassName& operator=(ClassName&); \ +# define DISTRHO_DECLARE_NON_COPYABLE(ClassName) \ +private: \ + ClassName(ClassName&); \ + ClassName(const ClassName&); \ + ClassName& operator=(ClassName&); \ ClassName& operator=(const ClassName&); #endif -/* Define DISTRHO_DECLARE_NON_COPY_STRUCT */ -#ifdef DISTRHO_PROPER_CPP11_SUPPORT -# define DISTRHO_DECLARE_NON_COPY_STRUCT(StructName) \ - StructName(StructName&) = delete; \ - StructName(const StructName&) = delete; \ - StructName& operator=(StructName&) = delete; \ - StructName& operator=(const StructName&) = delete; -#else -# define DISTRHO_DECLARE_NON_COPY_STRUCT(StructName) -#endif - /* Define DISTRHO_PREVENT_HEAP_ALLOCATION */ #ifdef DISTRHO_PROPER_CPP11_SUPPORT # define DISTRHO_PREVENT_HEAP_ALLOCATION \ @@ -119,6 +170,17 @@ private: \ static void operator delete(void*); #endif +/* Define DISTRHO_PREVENT_VIRTUAL_HEAP_ALLOCATION */ +#ifdef DISTRHO_PROPER_CPP11_SUPPORT +# define DISTRHO_PREVENT_VIRTUAL_HEAP_ALLOCATION \ +private: \ + static void* operator new(std::size_t) = delete; +#else +# define DISTRHO_PREVENT_VIRTUAL_HEAP_ALLOCATION \ +private: \ + static void* operator new(std::size_t); +#endif + /* Define namespace */ #ifndef DISTRHO_NAMESPACE # define DISTRHO_NAMESPACE DISTRHO @@ -127,10 +189,37 @@ private: \ #define END_NAMESPACE_DISTRHO } #define USE_NAMESPACE_DISTRHO using namespace DISTRHO_NAMESPACE; +/* Define DISTRHO_OS_SEP and DISTRHO_OS_SPLIT */ +#ifdef DISTRHO_OS_WINDOWS +# define DISTRHO_OS_SEP '\\' +# define DISTRHO_OS_SEP_STR "\\" +# define DISTRHO_OS_SPLIT ';' +# define DISTRHO_OS_SPLIT_STR ";" +#else +# define DISTRHO_OS_SEP '/' +# define DISTRHO_OS_SEP_STR "/" +# define DISTRHO_OS_SPLIT ':' +# define DISTRHO_OS_SPLIT_STR ":" +#endif + +/* MSVC warnings */ +#ifdef _MSC_VER +# define strdup _strdup +# pragma warning(disable:4244) /* possible loss of data */ +#endif + +/* Useful macros */ +#define ARRAY_SIZE(ARRAY) sizeof(ARRAY)/sizeof(ARRAY[0]) + /* Useful typedefs */ typedef unsigned char uchar; typedef unsigned short int ushort; typedef unsigned int uint; typedef unsigned long int ulong; +typedef unsigned long long int ulonglong; + +/* Deprecated macros */ +#define DISTRHO_DECLARE_NON_COPY_CLASS(ClassName) DISTRHO_DECLARE_NON_COPYABLE(ClassName) +#define DISTRHO_DECLARE_NON_COPY_STRUCT(StructName) DISTRHO_DECLARE_NON_COPYABLE(StructName) #endif // DISTRHO_DEFINES_H_INCLUDED diff --git a/source/modules/distrho/src/DistrhoPlugin.cpp b/source/modules/distrho/src/DistrhoPlugin.cpp index 345987615..9c4c9b65a 100644 --- a/source/modules/distrho/src/DistrhoPlugin.cpp +++ b/source/modules/distrho/src/DistrhoPlugin.cpp @@ -1,6 +1,6 @@ /* * DISTRHO Plugin Framework (DPF) - * Copyright (C) 2012-2018 Filipe Coelho + * Copyright (C) 2012-2022 Filipe Coelho * * Permission to use, copy, modify, and/or distribute this software for any purpose with * or without fee is hereby granted, provided that the above copyright notice and this @@ -21,16 +21,20 @@ START_NAMESPACE_DISTRHO /* ------------------------------------------------------------------------------------------------------------ * Static data, see DistrhoPluginInternal.hpp */ -uint32_t d_lastBufferSize = 0; -double d_lastSampleRate = 0.0; +uint32_t d_nextBufferSize = 0; +double d_nextSampleRate = 0.0; +const char* d_nextBundlePath = nullptr; +bool d_nextPluginIsDummy = false; +bool d_nextCanRequestParameterValueChanges = false; /* ------------------------------------------------------------------------------------------------------------ * Static fallback data, see DistrhoPluginInternal.hpp */ const String PluginExporter::sFallbackString; -const AudioPort PluginExporter::sFallbackAudioPort; +/* */ AudioPortWithBusId PluginExporter::sFallbackAudioPort; const ParameterRanges PluginExporter::sFallbackRanges; const ParameterEnumerationValues PluginExporter::sFallbackEnumValues; +const PortGroupWithId PluginExporter::sFallbackPortGroup; /* ------------------------------------------------------------------------------------------------------------ * Plugin */ @@ -39,7 +43,13 @@ Plugin::Plugin(uint32_t parameterCount, uint32_t programCount, uint32_t stateCou : pData(new PrivateData()) { #if DISTRHO_PLUGIN_NUM_INPUTS+DISTRHO_PLUGIN_NUM_OUTPUTS > 0 - pData->audioPorts = new AudioPort[DISTRHO_PLUGIN_NUM_INPUTS+DISTRHO_PLUGIN_NUM_OUTPUTS]; + pData->audioPorts = new AudioPortWithBusId[DISTRHO_PLUGIN_NUM_INPUTS+DISTRHO_PLUGIN_NUM_OUTPUTS]; +#endif + +#ifdef DPF_ABORT_ON_ERROR +# define DPF_ABORT abort(); +#else +# define DPF_ABORT #endif if (parameterCount > 0) @@ -48,26 +58,29 @@ Plugin::Plugin(uint32_t parameterCount, uint32_t programCount, uint32_t stateCou pData->parameters = new Parameter[parameterCount]; } -#if DISTRHO_PLUGIN_WANT_PROGRAMS if (programCount > 0) { +#if DISTRHO_PLUGIN_WANT_PROGRAMS pData->programCount = programCount; pData->programNames = new String[programCount]; - } #else - DISTRHO_SAFE_ASSERT(programCount == 0); + d_stderr2("DPF warning: Plugins with programs must define `DISTRHO_PLUGIN_WANT_PROGRAMS` to 1"); + DPF_ABORT #endif + } -#if DISTRHO_PLUGIN_WANT_STATE if (stateCount > 0) { - pData->stateCount = stateCount; - pData->stateKeys = new String[stateCount]; - pData->stateDefValues = new String[stateCount]; - } +#if DISTRHO_PLUGIN_WANT_STATE + pData->stateCount = stateCount; + pData->states = new State[stateCount]; #else - DISTRHO_SAFE_ASSERT(stateCount == 0); + d_stderr2("DPF warning: Plugins with state must define `DISTRHO_PLUGIN_WANT_STATE` to 1"); + DPF_ABORT #endif + } + +#undef DPF_ABORT } Plugin::~Plugin() @@ -88,6 +101,16 @@ double Plugin::getSampleRate() const noexcept return pData->sampleRate; } +const char* Plugin::getBundlePath() const noexcept +{ + return pData->bundlePath; +} + +bool Plugin::isDummyInstance() const noexcept +{ + return pData->isDummy; +} + #if DISTRHO_PLUGIN_WANT_TIMEPOS const TimePosition& Plugin::getTimePosition() const noexcept { @@ -109,6 +132,25 @@ bool Plugin::writeMidiEvent(const MidiEvent& midiEvent) noexcept } #endif +#if DISTRHO_PLUGIN_WANT_PARAMETER_VALUE_CHANGE_REQUEST +bool Plugin::canRequestParameterValueChanges() const noexcept +{ + return pData->canRequestParameterValueChanges; +} + +bool Plugin::requestParameterValueChange(const uint32_t index, const float value) noexcept +{ + return pData->requestParameterValueChangeCallback(index, value); +} +#endif + +#if DISTRHO_PLUGIN_WANT_STATE +bool Plugin::updateStateValue(const char* const key, const char* const value) noexcept +{ + return pData->updateStateValueCallback(key, value); +} +#endif + /* ------------------------------------------------------------------------------------------------------------ * Init */ @@ -130,11 +172,69 @@ void Plugin::initAudioPort(bool input, uint32_t index, AudioPort& port) } } +void Plugin::initParameter(uint32_t, Parameter&) {} + +void Plugin::initPortGroup(const uint32_t groupId, PortGroup& portGroup) +{ + fillInPredefinedPortGroupData(groupId, portGroup); +} + +#if DISTRHO_PLUGIN_WANT_PROGRAMS +void Plugin::initProgramName(uint32_t, String&) {} +#endif + +#if DISTRHO_PLUGIN_WANT_STATE +void Plugin::initState(const uint32_t index, State& state) +{ + uint hints = 0x0; + String stateKey, defaultStateValue; + + #if defined(__clang__) + #pragma clang diagnostic push + #pragma clang diagnostic ignored "-Wdeprecated-declarations" + #elif defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6)) + #pragma GCC diagnostic push + #pragma GCC diagnostic ignored "-Wdeprecated-declarations" + #endif + initState(index, stateKey, defaultStateValue); + if (isStateFile(index)) + hints = kStateIsFilenamePath; + #if defined(__clang__) + #pragma clang diagnostic pop + #elif defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6)) + #pragma GCC diagnostic pop + #endif + + state.hints = hints; + state.key = stateKey; + state.label = stateKey; + state.defaultValue = defaultStateValue; +} +#endif + +/* ------------------------------------------------------------------------------------------------------------ + * Init */ + +float Plugin::getParameterValue(uint32_t) const { return 0.0f; } +void Plugin::setParameterValue(uint32_t, float) {} + +#if DISTRHO_PLUGIN_WANT_PROGRAMS +void Plugin::loadProgram(uint32_t) {} +#endif + +#if DISTRHO_PLUGIN_WANT_FULL_STATE +String Plugin::getState(const char*) const { return String(); } +#endif + +#if DISTRHO_PLUGIN_WANT_STATE +void Plugin::setState(const char*, const char*) {} +#endif + /* ------------------------------------------------------------------------------------------------------------ * Callbacks (optional) */ void Plugin::bufferSizeChanged(uint32_t) {} -void Plugin::sampleRateChanged(double) {} +void Plugin::sampleRateChanged(double) {} // ----------------------------------------------------------------------------------------------------------- diff --git a/source/modules/distrho/src/DistrhoPluginCarla.cpp b/source/modules/distrho/src/DistrhoPluginCarla.cpp index 611b55bb0..5f375f03a 100644 --- a/source/modules/distrho/src/DistrhoPluginCarla.cpp +++ b/source/modules/distrho/src/DistrhoPluginCarla.cpp @@ -1,6 +1,6 @@ /* * DISTRHO Plugin Framework (DPF) - * Copyright (C) 2012-2019 Filipe Coelho + * Copyright (C) 2012-2021 Filipe Coelho * * Permission to use, copy, modify, and/or distribute this software for any purpose with * or without fee is hereby granted, provided that the above copyright notice and this @@ -22,6 +22,10 @@ #include "CarlaNative.hpp" +// TODO +#undef DISTRHO_PLUGIN_WANT_PARAMETER_VALUE_CHANGE_REQUEST +#define DISTRHO_PLUGIN_WANT_PARAMETER_VALUE_CHANGE_REQUEST 0 + // ----------------------------------------------------------------------- START_NAMESPACE_DISTRHO @@ -29,6 +33,9 @@ START_NAMESPACE_DISTRHO #if ! DISTRHO_PLUGIN_WANT_MIDI_OUTPUT static const writeMidiFunc writeMidiCallback = nullptr; #endif +#if ! DISTRHO_PLUGIN_WANT_PARAMETER_VALUE_CHANGE_REQUEST +static const requestParameterValueChangeFunc requestParameterValueChangeCallback = nullptr; +#endif #if DISTRHO_PLUGIN_HAS_UI // ----------------------------------------------------------------------- @@ -98,13 +105,9 @@ public: // --------------------------------------------- protected: - void handleEditParameter(const uint32_t rindex, const bool touch) + void handleEditParameter(const uint32_t, const bool) { - fHost->dispatcher(fHost->handle, - NATIVE_HOST_OPCODE_UI_TOUCH_PARAMETER, - static_cast(rindex), - touch ? 1 : 0, - nullptr, 0.0f); + // TODO } void handleSetParameterValue(const uint32_t rindex, const float value) @@ -188,7 +191,7 @@ class PluginCarla : public NativePluginClass public: PluginCarla(const NativeHostDescriptor* const host) : NativePluginClass(host), - fPlugin(this, writeMidiCallback), + fPlugin(this, writeMidiCallback, requestParameterValueChangeCallback), fScalePointsCache(nullptr) { #if DISTRHO_PLUGIN_HAS_UI @@ -235,8 +238,8 @@ protected: int nativeParamHints = ::NATIVE_PARAMETER_IS_ENABLED; const uint32_t paramHints = fPlugin.getParameterHints(index); - if (paramHints & kParameterIsAutomable) - nativeParamHints |= ::NATIVE_PARAMETER_IS_AUTOMATABLE; + if (paramHints & kParameterIsAutomatable) + nativeParamHints |= ::NATIVE_PARAMETER_IS_AUTOMABLE; if (paramHints & kParameterIsBoolean) nativeParamHints |= ::NATIVE_PARAMETER_IS_BOOLEAN; if (paramHints & kParameterIsInteger) @@ -364,8 +367,7 @@ protected: } #if DISTRHO_PLUGIN_WANT_MIDI_INPUT - void process(const float* const* const inBuffer, float** const outBuffer, const uint32_t frames, - const NativeMidiEvent* const midiEvents, const uint32_t midiEventCount) override + void process(float** const inBuffer, float** const outBuffer, const uint32_t frames, const NativeMidiEvent* const midiEvents, const uint32_t midiEventCount) override { MidiEvent realMidiEvents[midiEventCount]; @@ -386,13 +388,12 @@ protected: realMidiEvent.dataExt = nullptr; } - fPlugin.run(inBuffer, outBuffer, frames, realMidiEvents, midiEventCount); + fPlugin.run(const_cast(inBuffer), outBuffer, frames, realMidiEvents, midiEventCount); } #else - void process(const float* const* const inBuffer, float** const outBuffer, const uint32_t frames, - const NativeMidiEvent* const, const uint32_t) override + void process(float** const inBuffer, float** const outBuffer, const uint32_t frames, const NativeMidiEvent* const, const uint32_t) override { - fPlugin.run(inBuffer, outBuffer, frames); + fPlugin.run(const_cast(inBuffer), outBuffer, frames); } #endif @@ -518,6 +519,19 @@ private: } #endif +#if DISTRHO_PLUGIN_WANT_PARAMETER_VALUE_CHANGE_REQUEST + bool requestParameterValueChange(const uint32_t index, const float value) + { + // TODO implementation + return false; + } + + static bool requestParameterValueChangeCallback(void* ptr, const uint32_t index, const float value) + { + return thisPtr->requestParameterValueChange(index, value); + } +#endif + CARLA_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(PluginCarla) // ------------------------------------------------------------------- diff --git a/source/modules/distrho/src/DistrhoPluginChecks.h b/source/modules/distrho/src/DistrhoPluginChecks.h index a3690b076..d3d4b1740 100644 --- a/source/modules/distrho/src/DistrhoPluginChecks.h +++ b/source/modules/distrho/src/DistrhoPluginChecks.h @@ -1,6 +1,6 @@ /* * DISTRHO Plugin Framework (DPF) - * Copyright (C) 2012-2016 Filipe Coelho + * Copyright (C) 2012-2019 Filipe Coelho * * Permission to use, copy, modify, and/or distribute this software for any purpose with * or without fee is hereby granted, provided that the above copyright notice and this @@ -69,6 +69,10 @@ # define DISTRHO_PLUGIN_WANT_MIDI_OUTPUT 0 #endif +#ifndef DISTRHO_PLUGIN_WANT_PARAMETER_VALUE_CHANGE_REQUEST +# define DISTRHO_PLUGIN_WANT_PARAMETER_VALUE_CHANGE_REQUEST 0 +#endif + #ifndef DISTRHO_PLUGIN_WANT_PROGRAMS # define DISTRHO_PLUGIN_WANT_PROGRAMS 0 #endif @@ -79,12 +83,17 @@ #ifndef DISTRHO_PLUGIN_WANT_FULL_STATE # define DISTRHO_PLUGIN_WANT_FULL_STATE 0 +# define DISTRHO_PLUGIN_WANT_FULL_STATE_WAS_NOT_SET #endif #ifndef DISTRHO_PLUGIN_WANT_TIMEPOS # define DISTRHO_PLUGIN_WANT_TIMEPOS 0 #endif +#ifndef DISTRHO_UI_USER_RESIZABLE +# define DISTRHO_UI_USER_RESIZABLE 0 +#endif + #ifndef DISTRHO_UI_USE_NANOVG # define DISTRHO_UI_USE_NANOVG 0 #endif @@ -93,7 +102,7 @@ // Define DISTRHO_PLUGIN_HAS_EMBED_UI if needed #ifndef DISTRHO_PLUGIN_HAS_EMBED_UI -# ifdef HAVE_DGL +# if (defined(DGL_CAIRO) && defined(HAVE_CAIRO)) || (defined(DGL_OPENGL) && defined(HAVE_OPENGL)) # define DISTRHO_PLUGIN_HAS_EMBED_UI 1 # else # define DISTRHO_PLUGIN_HAS_EMBED_UI 0 @@ -104,7 +113,7 @@ // Define DISTRHO_UI_URI if needed #ifndef DISTRHO_UI_URI -# define DISTRHO_UI_URI DISTRHO_PLUGIN_URI "#UI" +# define DISTRHO_UI_URI DISTRHO_PLUGIN_URI "#DPF_UI" #endif // ----------------------------------------------------------------------- @@ -123,10 +132,23 @@ # error Synths need MIDI input to work! #endif +// ----------------------------------------------------------------------- +// Enable state if plugin wants state files (deprecated) + +#ifdef DISTRHO_PLUGIN_WANT_STATEFILES +# warning DISTRHO_PLUGIN_WANT_STATEFILES is deprecated +# undef DISTRHO_PLUGIN_WANT_STATEFILES +# if ! DISTRHO_PLUGIN_WANT_STATE +# undef DISTRHO_PLUGIN_WANT_STATE +# define DISTRHO_PLUGIN_WANT_STATE 1 +# endif +#endif + // ----------------------------------------------------------------------- // Enable full state if plugin exports presets -#if DISTRHO_PLUGIN_WANT_PROGRAMS && DISTRHO_PLUGIN_WANT_STATE +#if DISTRHO_PLUGIN_WANT_PROGRAMS && DISTRHO_PLUGIN_WANT_STATE && defined(DISTRHO_PLUGIN_WANT_FULL_STATE_WAS_NOT_SET) +# warning Plugins with programs and state should implement full state API too # undef DISTRHO_PLUGIN_WANT_FULL_STATE # define DISTRHO_PLUGIN_WANT_FULL_STATE 1 #endif @@ -134,11 +156,23 @@ // ----------------------------------------------------------------------- // Disable UI if DGL or External UI is not available -#if DISTRHO_PLUGIN_HAS_UI && ! DISTRHO_PLUGIN_HAS_EXTERNAL_UI && ! defined(HAVE_DGL) +#if (defined(DGL_CAIRO) && ! defined(HAVE_CAIRO)) || (defined(DGL_OPENGL) && ! defined(HAVE_OPENGL)) +# undef DISTRHO_PLUGIN_HAS_EMBED_UI +# define DISTRHO_PLUGIN_HAS_EMBED_UI 0 +#endif + +#if DISTRHO_PLUGIN_HAS_UI && ! DISTRHO_PLUGIN_HAS_EMBED_UI && ! DISTRHO_PLUGIN_HAS_EXTERNAL_UI # undef DISTRHO_PLUGIN_HAS_UI # define DISTRHO_PLUGIN_HAS_UI 0 #endif +// ----------------------------------------------------------------------- +// Prevent users from messing about with DPF internals + +#ifdef DISTRHO_UI_IS_STANDALONE +# error DISTRHO_UI_IS_STANDALONE must not be defined +#endif + // ----------------------------------------------------------------------- #endif // DISTRHO_PLUGIN_CHECKS_H_INCLUDED diff --git a/source/modules/distrho/src/DistrhoPluginInternal.hpp b/source/modules/distrho/src/DistrhoPluginInternal.hpp index 177fc0966..00fed5d67 100644 --- a/source/modules/distrho/src/DistrhoPluginInternal.hpp +++ b/source/modules/distrho/src/DistrhoPluginInternal.hpp @@ -1,6 +1,6 @@ /* * DISTRHO Plugin Framework (DPF) - * Copyright (C) 2012-2018 Filipe Coelho + * Copyright (C) 2012-2021 Filipe Coelho * * Permission to use, copy, modify, and/or distribute this software for any purpose with * or without fee is hereby granted, provided that the above copyright notice and this @@ -19,6 +19,12 @@ #include "../DistrhoPlugin.hpp" +#ifdef DISTRHO_PLUGIN_TARGET_VST3 +# include "DistrhoPluginVST3.hpp" +#endif + +#include + START_NAMESPACE_DISTRHO // ----------------------------------------------------------------------- @@ -29,28 +35,76 @@ static const uint32_t kMaxMidiEvents = 512; // ----------------------------------------------------------------------- // Static data, see DistrhoPlugin.cpp -extern uint32_t d_lastBufferSize; -extern double d_lastSampleRate; +extern uint32_t d_nextBufferSize; +extern double d_nextSampleRate; +extern const char* d_nextBundlePath; +extern bool d_nextPluginIsDummy; +extern bool d_nextCanRequestParameterValueChanges; // ----------------------------------------------------------------------- // DSP callbacks typedef bool (*writeMidiFunc) (void* ptr, const MidiEvent& midiEvent); +typedef bool (*requestParameterValueChangeFunc) (void* ptr, uint32_t index, float value); +typedef bool (*updateStateValueFunc) (void* ptr, const char* key, const char* value); + +// ----------------------------------------------------------------------- +// Helpers + +struct AudioPortWithBusId : AudioPort { + uint32_t busId; + + AudioPortWithBusId() + : AudioPort(), + busId(0) {} +}; + +struct PortGroupWithId : PortGroup { + uint32_t groupId; + + PortGroupWithId() + : PortGroup(), + groupId(kPortGroupNone) {} +}; + +static void fillInPredefinedPortGroupData(const uint32_t groupId, PortGroup& portGroup) +{ + switch (groupId) + { + case kPortGroupNone: + portGroup.name.clear(); + portGroup.symbol.clear(); + break; + case kPortGroupMono: + portGroup.name = "Mono"; + portGroup.symbol = "dpf_mono"; + break; + case kPortGroupStereo: + portGroup.name = "Stereo"; + portGroup.symbol = "dpf_stereo"; + break; + } +} // ----------------------------------------------------------------------- // Plugin private data struct Plugin::PrivateData { + const bool canRequestParameterValueChanges; + const bool isDummy; bool isProcessing; #if DISTRHO_PLUGIN_NUM_INPUTS+DISTRHO_PLUGIN_NUM_OUTPUTS > 0 - AudioPort* audioPorts; + AudioPortWithBusId* audioPorts; #endif uint32_t parameterCount; uint32_t parameterOffset; Parameter* parameters; + uint32_t portGroupCount; + PortGroupWithId* portGroups; + #if DISTRHO_PLUGIN_WANT_PROGRAMS uint32_t programCount; String* programNames; @@ -58,8 +112,7 @@ struct Plugin::PrivateData { #if DISTRHO_PLUGIN_WANT_STATE uint32_t stateCount; - String* stateKeys; - String* stateDefValues; + State* states; #endif #if DISTRHO_PLUGIN_WANT_LATENCY @@ -73,34 +126,43 @@ struct Plugin::PrivateData { // Callbacks void* callbacksPtr; writeMidiFunc writeMidiCallbackFunc; + requestParameterValueChangeFunc requestParameterValueChangeCallbackFunc; + updateStateValueFunc updateStateValueCallbackFunc; uint32_t bufferSize; double sampleRate; + char* bundlePath; PrivateData() noexcept - : isProcessing(false), + : canRequestParameterValueChanges(d_nextCanRequestParameterValueChanges), + isDummy(d_nextPluginIsDummy), + isProcessing(false), #if DISTRHO_PLUGIN_NUM_INPUTS+DISTRHO_PLUGIN_NUM_OUTPUTS > 0 audioPorts(nullptr), #endif parameterCount(0), parameterOffset(0), parameters(nullptr), + portGroupCount(0), + portGroups(nullptr), #if DISTRHO_PLUGIN_WANT_PROGRAMS programCount(0), programNames(nullptr), #endif #if DISTRHO_PLUGIN_WANT_STATE stateCount(0), - stateKeys(nullptr), - stateDefValues(nullptr), + states(nullptr), #endif #if DISTRHO_PLUGIN_WANT_LATENCY latency(0), #endif callbacksPtr(nullptr), writeMidiCallbackFunc(nullptr), - bufferSize(d_lastBufferSize), - sampleRate(d_lastSampleRate) + requestParameterValueChangeCallbackFunc(nullptr), + updateStateValueCallbackFunc(nullptr), + bufferSize(d_nextBufferSize), + sampleRate(d_nextSampleRate), + bundlePath(d_nextBundlePath != nullptr ? strdup(d_nextBundlePath) : nullptr) { DISTRHO_SAFE_ASSERT(bufferSize != 0); DISTRHO_SAFE_ASSERT(d_isNotZero(sampleRate)); @@ -113,12 +175,16 @@ struct Plugin::PrivateData { #endif #ifdef DISTRHO_PLUGIN_TARGET_LV2 -# if (DISTRHO_PLUGIN_IS_SYNTH || DISTRHO_PLUGIN_WANT_TIMEPOS || DISTRHO_PLUGIN_WANT_STATE) +# if (DISTRHO_PLUGIN_WANT_MIDI_INPUT || DISTRHO_PLUGIN_WANT_STATE || DISTRHO_PLUGIN_WANT_TIMEPOS) parameterOffset += 1; -# if DISTRHO_PLUGIN_WANT_STATE +# endif +# if (DISTRHO_PLUGIN_WANT_MIDI_OUTPUT || DISTRHO_PLUGIN_WANT_STATE) parameterOffset += 1; -# endif # endif +#endif + +#ifdef DISTRHO_PLUGIN_TARGET_VST3 + parameterOffset += kVst3InternalParameterCount; #endif } @@ -138,6 +204,12 @@ struct Plugin::PrivateData { parameters = nullptr; } + if (portGroups != nullptr) + { + delete[] portGroups; + portGroups = nullptr; + } + #if DISTRHO_PLUGIN_WANT_PROGRAMS if (programNames != nullptr) { @@ -147,18 +219,18 @@ struct Plugin::PrivateData { #endif #if DISTRHO_PLUGIN_WANT_STATE - if (stateKeys != nullptr) + if (states != nullptr) { - delete[] stateKeys; - stateKeys = nullptr; + delete[] states; + states = nullptr; } +#endif - if (stateDefValues != nullptr) + if (bundlePath != nullptr) { - delete[] stateDefValues; - stateDefValues = nullptr; + std::free(bundlePath); + bundlePath = nullptr; } -#endif } #if DISTRHO_PLUGIN_WANT_MIDI_OUTPUT @@ -170,6 +242,27 @@ struct Plugin::PrivateData { return false; } #endif + +#if DISTRHO_PLUGIN_WANT_PARAMETER_VALUE_CHANGE_REQUEST + bool requestParameterValueChangeCallback(const uint32_t index, const float value) + { + if (requestParameterValueChangeCallbackFunc != nullptr) + return requestParameterValueChangeCallbackFunc(callbacksPtr, index, value); + + return false; + } +#endif + +#if DISTRHO_PLUGIN_WANT_STATE + bool updateStateValueCallback(const char* const key, const char* const value) + { + d_stdout("updateStateValueCallback %p", updateStateValueCallbackFunc); + if (updateStateValueCallbackFunc != nullptr) + return updateStateValueCallbackFunc(callbacksPtr, key, value); + + return false; + } +#endif }; // ----------------------------------------------------------------------- @@ -178,7 +271,10 @@ struct Plugin::PrivateData { class PluginExporter { public: - PluginExporter(void* const callbacksPtr, const writeMidiFunc writeMidiCall) + PluginExporter(void* const callbacksPtr, + const writeMidiFunc writeMidiCall, + const requestParameterValueChangeFunc requestParameterValueChangeCall, + const updateStateValueFunc updateStateValueCall) : fPlugin(createPlugin()), fData((fPlugin != nullptr) ? fPlugin->pData : nullptr), fIsActive(false) @@ -186,6 +282,79 @@ public: DISTRHO_SAFE_ASSERT_RETURN(fPlugin != nullptr,); DISTRHO_SAFE_ASSERT_RETURN(fData != nullptr,); +#if defined(DPF_RUNTIME_TESTING) && defined(__GNUC__) && !defined(__clang__) + /* Run-time testing build. + * Verify that virtual functions are overriden if parameters, programs or states are in use. + * This does not work on all compilers, but we use it purely as informational check anyway. */ + if (fData->parameterCount != 0) + { + if ((void*)(fPlugin->*(&Plugin::initParameter)) == (void*)&Plugin::initParameter) + { + d_stderr2("DPF warning: Plugins with parameters must implement `initParameter`"); + abort(); + } + if ((void*)(fPlugin->*(&Plugin::getParameterValue)) == (void*)&Plugin::getParameterValue) + { + d_stderr2("DPF warning: Plugins with parameters must implement `getParameterValue`"); + abort(); + } + if ((void*)(fPlugin->*(&Plugin::setParameterValue)) == (void*)&Plugin::setParameterValue) + { + d_stderr2("DPF warning: Plugins with parameters must implement `setParameterValue`"); + abort(); + } + } + +# if DISTRHO_PLUGIN_WANT_PROGRAMS + if (fData->programCount != 0) + { + if ((void*)(fPlugin->*(&Plugin::initProgramName)) == (void*)&Plugin::initProgramName) + { + d_stderr2("DPF warning: Plugins with programs must implement `initProgramName`"); + abort(); + } + if ((void*)(fPlugin->*(&Plugin::loadProgram)) == (void*)&Plugin::loadProgram) + { + d_stderr2("DPF warning: Plugins with programs must implement `loadProgram`"); + abort(); + } + } +# endif + +# if DISTRHO_PLUGIN_WANT_STATE + if (fData->stateCount != 0) + { + if ((void*)(fPlugin->*(&Plugin::initState)) == (void*)&Plugin::initState) + { + d_stderr2("DPF warning: Plugins with state must implement `initState`"); + abort(); + } + + if ((void*)(fPlugin->*(&Plugin::setState)) == (void*)&Plugin::setState) + { + d_stderr2("DPF warning: Plugins with state must implement `setState`"); + abort(); + } + } +# endif + +# if DISTRHO_PLUGIN_WANT_FULL_STATE + if (fData->stateCount != 0) + { + if ((void*)(fPlugin->*(&Plugin::getState)) == (void*)&Plugin::getState) + { + d_stderr2("DPF warning: Plugins with full state must implement `getState`"); + abort(); + } + } + else + { + d_stderr2("DPF warning: Plugins with full state must have at least 1 state"); + abort(); + } +# endif +#endif + #if DISTRHO_PLUGIN_NUM_INPUTS+DISTRHO_PLUGIN_NUM_OUTPUTS > 0 { uint32_t j=0; @@ -198,11 +367,42 @@ public: fPlugin->initAudioPort(false, i, fData->audioPorts[j]); # endif } -#endif +#endif // DISTRHO_PLUGIN_NUM_INPUTS+DISTRHO_PLUGIN_NUM_OUTPUTS > 0 for (uint32_t i=0, count=fData->parameterCount; i < count; ++i) fPlugin->initParameter(i, fData->parameters[i]); + { + std::set portGroupIndices; + +#if DISTRHO_PLUGIN_NUM_INPUTS+DISTRHO_PLUGIN_NUM_OUTPUTS > 0 + for (uint32_t i=0; i < DISTRHO_PLUGIN_NUM_INPUTS+DISTRHO_PLUGIN_NUM_OUTPUTS; ++i) + portGroupIndices.insert(fData->audioPorts[i].groupId); +#endif + for (uint32_t i=0, count=fData->parameterCount; i < count; ++i) + portGroupIndices.insert(fData->parameters[i].groupId); + + portGroupIndices.erase(kPortGroupNone); + + if (const uint32_t portGroupSize = static_cast(portGroupIndices.size())) + { + fData->portGroups = new PortGroupWithId[portGroupSize]; + fData->portGroupCount = portGroupSize; + + uint32_t index = 0; + for (std::set::iterator it = portGroupIndices.begin(); it != portGroupIndices.end(); ++it, ++index) + { + PortGroupWithId& portGroup(fData->portGroups[index]); + portGroup.groupId = *it; + + if (portGroup.groupId < portGroupSize) + fPlugin->initPortGroup(portGroup.groupId, portGroup); + else + fillInPredefinedPortGroupData(portGroup.groupId, portGroup); + } + } + } + #if DISTRHO_PLUGIN_WANT_PROGRAMS for (uint32_t i=0, count=fData->programCount; i < count; ++i) fPlugin->initProgramName(i, fData->programNames[i]); @@ -210,11 +410,13 @@ public: #if DISTRHO_PLUGIN_WANT_STATE for (uint32_t i=0, count=fData->stateCount; i < count; ++i) - fPlugin->initState(i, fData->stateKeys[i], fData->stateDefValues[i]); + fPlugin->initState(i, fData->states[i]); #endif - fData->callbacksPtr = callbacksPtr; + fData->callbacksPtr = callbacksPtr; fData->writeMidiCallbackFunc = writeMidiCall; + fData->requestParameterValueChangeCallbackFunc = requestParameterValueChangeCall; + fData->updateStateValueCallbackFunc = updateStateValueCall; } ~PluginExporter() @@ -297,7 +499,7 @@ public: #endif #if DISTRHO_PLUGIN_NUM_INPUTS+DISTRHO_PLUGIN_NUM_OUTPUTS > 0 - const AudioPort& getAudioPort(const bool input, const uint32_t index) const noexcept + AudioPortWithBusId& getAudioPort(const bool input, const uint32_t index) const noexcept { DISTRHO_SAFE_ASSERT_RETURN(fData != nullptr, sFallbackAudioPort); @@ -316,6 +518,11 @@ public: return fData->audioPorts[index + (input ? 0 : DISTRHO_PLUGIN_NUM_INPUTS)]; } + + uint32_t getAudioPortHints(const bool input, const uint32_t index) const noexcept + { + return getAudioPort(input, index).hints; + } #endif uint32_t getParameterCount() const noexcept @@ -356,6 +563,11 @@ public: return (getParameterHints(index) & kParameterIsOutput) != 0x0; } + bool isParameterTrigger(const uint32_t index) const noexcept + { + return (getParameterHints(index) & kParameterIsTrigger) == kParameterIsTrigger; + } + bool isParameterOutputOrTrigger(const uint32_t index) const noexcept { const uint32_t hints = getParameterHints(index); @@ -375,6 +587,13 @@ public: return fData->parameters[index].name; } + const String& getParameterShortName(const uint32_t index) const noexcept + { + DISTRHO_SAFE_ASSERT_RETURN(fData != nullptr && index < fData->parameterCount, sFallbackString); + + return fData->parameters[index].shortName; + } + const String& getParameterSymbol(const uint32_t index) const noexcept { DISTRHO_SAFE_ASSERT_RETURN(fData != nullptr && index < fData->parameterCount, sFallbackString); @@ -389,6 +608,13 @@ public: return fData->parameters[index].unit; } + const String& getParameterDescription(const uint32_t index) const noexcept + { + DISTRHO_SAFE_ASSERT_RETURN(fData != nullptr && index < fData->parameterCount, sFallbackString); + + return fData->parameters[index].description; + } + const ParameterEnumerationValues& getParameterEnumValues(const uint32_t index) const noexcept { DISTRHO_SAFE_ASSERT_RETURN(fData != nullptr && index < fData->parameterCount, sFallbackEnumValues); @@ -410,6 +636,21 @@ public: return fData->parameters[index].midiCC; } + uint32_t getParameterGroupId(const uint32_t index) const noexcept + { + DISTRHO_SAFE_ASSERT_RETURN(fData != nullptr && index < fData->parameterCount, kPortGroupNone); + + return fData->parameters[index].groupId; + } + + float getParameterDefault(const uint32_t index) const + { + DISTRHO_SAFE_ASSERT_RETURN(fPlugin != nullptr, 0.0f); + DISTRHO_SAFE_ASSERT_RETURN(fData != nullptr && index < fData->parameterCount, 0.0f); + + return fData->parameters[index].ranges.def; + } + float getParameterValue(const uint32_t index) const { DISTRHO_SAFE_ASSERT_RETURN(fPlugin != nullptr, 0.0f); @@ -426,6 +667,42 @@ public: fPlugin->setParameterValue(index, value); } + uint32_t getPortGroupCount() const noexcept + { + DISTRHO_SAFE_ASSERT_RETURN(fData != nullptr, 0); + + return fData->portGroupCount; + } + + const PortGroupWithId& getPortGroupById(const uint32_t groupId) const noexcept + { + DISTRHO_SAFE_ASSERT_RETURN(fData != nullptr && fData->portGroupCount != 0, sFallbackPortGroup); + + for (uint32_t i=0; i < fData->portGroupCount; ++i) + { + const PortGroupWithId& portGroup(fData->portGroups[i]); + + if (portGroup.groupId == groupId) + return portGroup; + } + + return sFallbackPortGroup; + } + + const PortGroupWithId& getPortGroupByIndex(const uint32_t index) const noexcept + { + DISTRHO_SAFE_ASSERT_RETURN(fData != nullptr && index < fData->portGroupCount, sFallbackPortGroup); + + return fData->portGroups[index]; + } + + const String& getPortGroupSymbolForId(const uint32_t groupId) const noexcept + { + DISTRHO_SAFE_ASSERT_RETURN(fData != nullptr, sFallbackString); + + return getPortGroupById(groupId).symbol; + } + #if DISTRHO_PLUGIN_WANT_PROGRAMS uint32_t getProgramCount() const noexcept { @@ -458,22 +735,43 @@ public: return fData->stateCount; } + uint32_t getStateHints(const uint32_t index) const noexcept + { + DISTRHO_SAFE_ASSERT_RETURN(fData != nullptr && index < fData->stateCount, 0x0); + + return fData->states[index].hints; + } + const String& getStateKey(const uint32_t index) const noexcept { DISTRHO_SAFE_ASSERT_RETURN(fData != nullptr && index < fData->stateCount, sFallbackString); - return fData->stateKeys[index]; + return fData->states[index].key; } const String& getStateDefaultValue(const uint32_t index) const noexcept { DISTRHO_SAFE_ASSERT_RETURN(fData != nullptr && index < fData->stateCount, sFallbackString); - return fData->stateDefValues[index]; + return fData->states[index].defaultValue; + } + + const String& getStateLabel(const uint32_t index) const noexcept + { + DISTRHO_SAFE_ASSERT_RETURN(fData != nullptr && index < fData->stateCount, sFallbackString); + + return fData->states[index].label; + } + + const String& getStateDescription(const uint32_t index) const noexcept + { + DISTRHO_SAFE_ASSERT_RETURN(fData != nullptr && index < fData->stateCount, sFallbackString); + + return fData->states[index].description; } # if DISTRHO_PLUGIN_WANT_FULL_STATE - String getState(const char* key) const + String getStateValue(const char* const key) const { DISTRHO_SAFE_ASSERT_RETURN(fData != nullptr, sFallbackString); DISTRHO_SAFE_ASSERT_RETURN(key != nullptr && key[0] != '\0', sFallbackString); @@ -498,7 +796,7 @@ public: for (uint32_t i=0; i < fData->stateCount; ++i) { - if (fData->stateKeys[i] == key) + if (fData->states[i].key == key) return true; } @@ -552,7 +850,7 @@ public: } #if DISTRHO_PLUGIN_WANT_MIDI_INPUT - void run(const float* const* const inputs, float** const outputs, const uint32_t frames, + void run(const float** const inputs, float** const outputs, const uint32_t frames, const MidiEvent* const midiEvents, const uint32_t midiEventCount) { DISTRHO_SAFE_ASSERT_RETURN(fData != nullptr,); @@ -569,7 +867,7 @@ public: fData->isProcessing = false; } #else - void run(const float* const* const inputs, float** const outputs, const uint32_t frames) + void run(const float** const inputs, float** const outputs, const uint32_t frames) { DISTRHO_SAFE_ASSERT_RETURN(fData != nullptr,); DISTRHO_SAFE_ASSERT_RETURN(fPlugin != nullptr,); @@ -650,12 +948,15 @@ private: // Static fallback data, see DistrhoPlugin.cpp static const String sFallbackString; - static const AudioPort sFallbackAudioPort; + static /* */ AudioPortWithBusId sFallbackAudioPort; static const ParameterRanges sFallbackRanges; static const ParameterEnumerationValues sFallbackEnumValues; + static const PortGroupWithId sFallbackPortGroup; DISTRHO_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(PluginExporter) +#ifndef DISTRHO_PLUGIN_TARGET_VST3 /* there is no way around this for VST3 */ DISTRHO_PREVENT_HEAP_ALLOCATION +#endif }; // ----------------------------------------------------------------------- diff --git a/source/modules/distrho/src/DistrhoUI.cpp b/source/modules/distrho/src/DistrhoUI.cpp index f180ee2e6..5e98c289b 100644 --- a/source/modules/distrho/src/DistrhoUI.cpp +++ b/source/modules/distrho/src/DistrhoUI.cpp @@ -1,6 +1,6 @@ /* * DISTRHO Plugin Framework (DPF) - * Copyright (C) 2012-2016 Filipe Coelho + * Copyright (C) 2012-2021 Filipe Coelho * * Permission to use, copy, modify, and/or distribute this software for any purpose with * or without fee is hereby granted, provided that the above copyright notice and this @@ -14,78 +14,280 @@ * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ -#include "DistrhoUIInternal.hpp" +#include "src/DistrhoPluginChecks.h" +#include "src/DistrhoDefines.h" -#ifdef HAVE_DGL -# include "src/WidgetPrivateData.hpp" +#if !defined(DGL_FILE_BROWSER_DISABLED) && !defined(DISTRHO_OS_MAC) +# define DISTRHO_PUGL_NAMESPACE_MACRO_HELPER(NS, SEP, FUNCTION) NS ## SEP ## FUNCTION +# define DISTRHO_PUGL_NAMESPACE_MACRO(NS, FUNCTION) DISTRHO_PUGL_NAMESPACE_MACRO_HELPER(NS, _, FUNCTION) +# define DISTRHO_FILE_BROWSER_DIALOG_EXTRA_NAMESPACE Plugin +# define x_fib_add_recent DISTRHO_PUGL_NAMESPACE_MACRO(Plugin, x_fib_add_recent) +# define x_fib_cfg_buttons DISTRHO_PUGL_NAMESPACE_MACRO(Plugin, x_fib_cfg_buttons) +# define x_fib_cfg_filter_callback DISTRHO_PUGL_NAMESPACE_MACRO(Plugin, x_fib_cfg_filter_callback) +# define x_fib_close DISTRHO_PUGL_NAMESPACE_MACRO(Plugin, x_fib_close) +# define x_fib_configure DISTRHO_PUGL_NAMESPACE_MACRO(Plugin, x_fib_configure) +# define x_fib_filename DISTRHO_PUGL_NAMESPACE_MACRO(Plugin, x_fib_filename) +# define x_fib_free_recent DISTRHO_PUGL_NAMESPACE_MACRO(Plugin, x_fib_free_recent) +# define x_fib_handle_events DISTRHO_PUGL_NAMESPACE_MACRO(Plugin, x_fib_handle_events) +# define x_fib_load_recent DISTRHO_PUGL_NAMESPACE_MACRO(Plugin, x_fib_load_recent) +# define x_fib_recent_at DISTRHO_PUGL_NAMESPACE_MACRO(Plugin, x_fib_recent_at) +# define x_fib_recent_count DISTRHO_PUGL_NAMESPACE_MACRO(Plugin, x_fib_recent_count) +# define x_fib_recent_file DISTRHO_PUGL_NAMESPACE_MACRO(Plugin, x_fib_recent_file) +# define x_fib_save_recent DISTRHO_PUGL_NAMESPACE_MACRO(Plugin, x_fib_save_recent) +# define x_fib_show DISTRHO_PUGL_NAMESPACE_MACRO(Plugin, x_fib_show) +# define x_fib_status DISTRHO_PUGL_NAMESPACE_MACRO(Plugin, x_fib_status) +# include "../extra/FileBrowserDialog.cpp" #endif +#if DISTRHO_PLUGIN_HAS_EXTERNAL_UI +# if defined(DISTRHO_OS_WINDOWS) +# include +# include +# elif defined(HAVE_X11) +# include +# endif +#else +# include "src/TopLevelWidgetPrivateData.hpp" +# include "src/WindowPrivateData.hpp" +#endif + +#include "DistrhoUIPrivateData.hpp" + START_NAMESPACE_DISTRHO /* ------------------------------------------------------------------------------------------------------------ * Static data, see DistrhoUIInternal.hpp */ -double d_lastUiSampleRate = 0.0; -void* d_lastUiDspPtr = nullptr; -#ifdef HAVE_DGL -Window* d_lastUiWindow = nullptr; +const char* g_nextBundlePath = nullptr; +#if DISTRHO_PLUGIN_HAS_EXTERNAL_UI +uintptr_t g_nextWindowId = 0; +double g_nextScaleFactor = 1.0; #endif -uintptr_t g_nextWindowId = 0; -const char* g_nextBundlePath = nullptr; +#if DISTRHO_PLUGIN_HAS_EXTERNAL_UI /* ------------------------------------------------------------------------------------------------------------ - * UI */ + * get global scale factor */ -#ifdef HAVE_DGL -UI::UI(uint width, uint height) - : UIWidget(*d_lastUiWindow), - pData(new PrivateData()) +#ifdef DISTRHO_OS_MAC +double getDesktopScaleFactor(uintptr_t parentWindowHandle); +#else +static double getDesktopScaleFactor(const uintptr_t parentWindowHandle) { - ((UIWidget*)this)->pData->needsFullViewport = false; + // allow custom scale for testing + if (const char* const scale = getenv("DPF_SCALE_FACTOR")) + return std::max(1.0, std::atof(scale)); + +#if defined(DISTRHO_OS_WINDOWS) + if (const HMODULE Shcore = LoadLibraryA("Shcore.dll")) + { + typedef HRESULT(WINAPI* PFN_GetProcessDpiAwareness)(HANDLE, DWORD*); + typedef HRESULT(WINAPI* PFN_GetScaleFactorForMonitor)(HMONITOR, DWORD*); + +# if defined(__GNUC__) && (__GNUC__ >= 9) +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wcast-function-type" +# endif + const PFN_GetProcessDpiAwareness GetProcessDpiAwareness + = (PFN_GetProcessDpiAwareness)GetProcAddress(Shcore, "GetProcessDpiAwareness"); + const PFN_GetScaleFactorForMonitor GetScaleFactorForMonitor + = (PFN_GetScaleFactorForMonitor)GetProcAddress(Shcore, "GetScaleFactorForMonitor"); +# if defined(__GNUC__) && (__GNUC__ >= 9) +# pragma GCC diagnostic pop +# endif + + DWORD dpiAware = 0; + if (GetProcessDpiAwareness && GetScaleFactorForMonitor + && GetProcessDpiAwareness(NULL, &dpiAware) == 0 && dpiAware != 0) + { + const HMONITOR hMon = parentWindowHandle != 0 + ? MonitorFromWindow((HWND)parentWindowHandle, MONITOR_DEFAULTTOPRIMARY) + : MonitorFromPoint(POINT{0,0}, MONITOR_DEFAULTTOPRIMARY); + + DWORD scaleFactor = 0; + if (GetScaleFactorForMonitor(hMon, &scaleFactor) == 0 && scaleFactor != 0) + { + FreeLibrary(Shcore); + return static_cast(scaleFactor) / 100.0; + } + } + + FreeLibrary(Shcore); + } +#elif defined(HAVE_X11) + ::Display* const display = XOpenDisplay(nullptr); + DISTRHO_SAFE_ASSERT_RETURN(display != nullptr, 1.0); + + XrmInitialize(); + + if (char* const rms = XResourceManagerString(display)) + { + if (const XrmDatabase sdb = XrmGetStringDatabase(rms)) + { + char* type = nullptr; + XrmValue ret; + + if (XrmGetResource(sdb, "Xft.dpi", "String", &type, &ret) + && ret.addr != nullptr + && type != nullptr + && std::strncmp("String", type, 6) == 0) + { + if (const double dpi = std::atof(ret.addr)) + { + XCloseDisplay(display); + return dpi / 96; + } + } + } + } - if (width > 0 && height > 0) - setSize(width, height); + XCloseDisplay(display); +#endif + + return 1.0; + + // might be unused + (void)parentWindowHandle; } +#endif // !DISTRHO_OS_MAC + +#endif + +/* ------------------------------------------------------------------------------------------------------------ + * UI::PrivateData special handling */ + +UI::PrivateData* UI::PrivateData::s_nextPrivateData = nullptr; + +#if DISTRHO_PLUGIN_HAS_EXTERNAL_UI +ExternalWindow::PrivateData #else -UI::UI(uint width, uint height) - : UIWidget(width, height), - pData(new PrivateData()) {} +PluginWindow& #endif +UI::PrivateData::createNextWindow(UI* const ui, const uint width, const uint height) +{ + UI::PrivateData* const pData = s_nextPrivateData; +#if DISTRHO_PLUGIN_HAS_EXTERNAL_UI + pData->window = new PluginWindow(ui, pData->app); + ExternalWindow::PrivateData ewData; + ewData.parentWindowHandle = pData->winId; + ewData.width = width; + ewData.height = height; + ewData.scaleFactor = pData->scaleFactor != 0.0 ? pData->scaleFactor : getDesktopScaleFactor(pData->winId); + ewData.title = DISTRHO_PLUGIN_NAME; + ewData.isStandalone = DISTRHO_UI_IS_STANDALONE; + return ewData; +#else + pData->window = new PluginWindow(ui, pData->app, pData->winId, width, height, pData->scaleFactor); + + // If there are no callbacks, this is most likely a temporary window, so ignore idle callbacks + if (pData->callbacksPtr == nullptr) + pData->window->setIgnoreIdleCallbacks(); + + return pData->window.getObject(); +#endif +} + +/* ------------------------------------------------------------------------------------------------------------ + * UI */ + +UI::UI(const uint width, const uint height, const bool automaticallyScaleAndSetAsMinimumSize) + : UIWidget(UI::PrivateData::createNextWindow(this, width, height)), + uiData(UI::PrivateData::s_nextPrivateData) +{ +#if !DISTRHO_PLUGIN_HAS_EXTERNAL_UI + if (width != 0 && height != 0) + { + Widget::setSize(width, height); + + if (automaticallyScaleAndSetAsMinimumSize) + setGeometryConstraints(width, height, true, true, true); + } +#else + // unused + (void)automaticallyScaleAndSetAsMinimumSize; +#endif +} UI::~UI() { - delete pData; } /* ------------------------------------------------------------------------------------------------------------ * Host state */ +bool UI::isResizable() const noexcept +{ +#if DISTRHO_UI_USER_RESIZABLE +# if DISTRHO_PLUGIN_HAS_EXTERNAL_UI + return true; +# else + return uiData->window->isResizable(); +# endif +#else + return false; +#endif +} + +uint UI::getBackgroundColor() const noexcept +{ + return uiData->bgColor; +} + +uint UI::getForegroundColor() const noexcept +{ + return uiData->fgColor; +} + double UI::getSampleRate() const noexcept { - return pData->sampleRate; + return uiData->sampleRate; +} + +const char* UI::getBundlePath() const noexcept +{ + return uiData->bundlePath; } void UI::editParameter(uint32_t index, bool started) { - pData->editParamCallback(index + pData->parameterOffset, started); + uiData->editParamCallback(index + uiData->parameterOffset, started); } void UI::setParameterValue(uint32_t index, float value) { - pData->setParamCallback(index + pData->parameterOffset, value); + uiData->setParamCallback(index + uiData->parameterOffset, value); } #if DISTRHO_PLUGIN_WANT_STATE void UI::setState(const char* key, const char* value) { - pData->setStateCallback(key, value); + uiData->setStateCallback(key, value); +} +#endif + +#if DISTRHO_PLUGIN_WANT_STATE +bool UI::requestStateFile(const char* key) +{ + return uiData->fileRequestCallback(key); } #endif #if DISTRHO_PLUGIN_WANT_MIDI_INPUT void UI::sendNote(uint8_t channel, uint8_t note, uint8_t velocity) { - pData->sendNoteCallback(channel, note, velocity); + uiData->sendNoteCallback(channel, note, velocity); +} +#endif + +#ifndef DGL_FILE_BROWSER_DISABLED +bool UI::openFileBrowser(const FileBrowserOptions& options) +{ +# if DISTRHO_PLUGIN_HAS_EXTERNAL_UI + // TODO + return false; + (void)options; +# else + return getWindow().openFileBrowser(options); +# endif } #endif @@ -95,60 +297,102 @@ void UI::sendNote(uint8_t channel, uint8_t note, uint8_t velocity) void* UI::getPluginInstancePointer() const noexcept { - return pData->dspPtr; + return uiData->dspPtr; } #endif #if DISTRHO_PLUGIN_HAS_EXTERNAL_UI /* ------------------------------------------------------------------------------------------------------------ - * External UI helpers */ + * External UI helpers (static calls) */ const char* UI::getNextBundlePath() noexcept { return g_nextBundlePath; } +double UI::getNextScaleFactor() noexcept +{ + return g_nextScaleFactor; +} + # if DISTRHO_PLUGIN_HAS_EMBED_UI uintptr_t UI::getNextWindowId() noexcept { return g_nextWindowId; } # endif -#endif +#endif // DISTRHO_PLUGIN_HAS_EXTERNAL_UI /* ------------------------------------------------------------------------------------------------------------ * DSP/Plugin Callbacks (optional) */ -void UI::sampleRateChanged(double) {} +void UI::sampleRateChanged(double) +{ +} -#ifdef HAVE_DGL /* ------------------------------------------------------------------------------------------------------------ * UI Callbacks (optional) */ -#ifndef DGL_FILE_BROWSER_DISABLED -void UI::uiFileBrowserSelected(const char*) +void UI::uiScaleFactorChanged(double) +{ +} + +#if !DISTRHO_PLUGIN_HAS_EXTERNAL_UI +void UI::uiFocus(bool, DGL_NAMESPACE::CrossingMode) { } -#endif -void UI::uiReshape(uint width, uint height) +void UI::uiReshape(uint, uint) { - glEnable(GL_BLEND); - glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); - glMatrixMode(GL_PROJECTION); - glLoadIdentity(); - glOrtho(0.0, static_cast(width), static_cast(height), 0.0, 0.0, 1.0); - glViewport(0, 0, static_cast(width), static_cast(height)); - glMatrixMode(GL_MODELVIEW); - glLoadIdentity(); + // NOTE this must be the same as Window::onReshape + pData->fallbackOnResize(); } +#endif // !DISTRHO_PLUGIN_HAS_EXTERNAL_UI + +#ifndef DGL_FILE_BROWSER_DISABLED +void UI::uiFileBrowserSelected(const char*) +{ +} +#endif /* ------------------------------------------------------------------------------------------------------------ * UI Resize Handling, internal */ +#if DISTRHO_PLUGIN_HAS_EXTERNAL_UI +void UI::sizeChanged(const uint width, const uint height) +{ + UIWidget::sizeChanged(width, height); + + uiData->setSizeCallback(width, height); +} +#else void UI::onResize(const ResizeEvent& ev) { - pData->setSizeCallback(ev.size.getWidth(), ev.size.getHeight()); + UIWidget::onResize(ev); + +#ifndef DISTRHO_PLUGIN_TARGET_VST3 + if (uiData->initializing) + return; + + const uint width = ev.size.getWidth(); + const uint height = ev.size.getHeight(); + uiData->setSizeCallback(width, height); +#endif +} + +// NOTE: only used for VST3 +void UI::requestSizeChange(const uint width, const uint height) +{ +# ifdef DISTRHO_PLUGIN_TARGET_VST3 + if (uiData->initializing) + uiData->window->setSizeForVST3(width, height); + else + uiData->setSizeCallback(width, height); +# else + // unused + (void)width; + (void)height; +# endif } #endif diff --git a/source/modules/distrho/src/DistrhoUIInternal.hpp b/source/modules/distrho/src/DistrhoUIInternal.hpp index 8a045a93e..fb36f6e3b 100644 --- a/source/modules/distrho/src/DistrhoUIInternal.hpp +++ b/source/modules/distrho/src/DistrhoUIInternal.hpp @@ -1,6 +1,6 @@ /* * DISTRHO Plugin Framework (DPF) - * Copyright (C) 2012-2018 Filipe Coelho + * Copyright (C) 2012-2021 Filipe Coelho * * Permission to use, copy, modify, and/or distribute this software for any purpose with * or without fee is hereby granted, provided that the above copyright notice and this @@ -17,462 +17,403 @@ #ifndef DISTRHO_UI_INTERNAL_HPP_INCLUDED #define DISTRHO_UI_INTERNAL_HPP_INCLUDED -#include "../DistrhoUI.hpp" - -#ifdef HAVE_DGL -# include "../../dgl/Application.hpp" -# include "../../dgl/Window.hpp" -using DGL_NAMESPACE::Application; -using DGL_NAMESPACE::IdleCallback; -using DGL_NAMESPACE::Window; -#endif +#include "DistrhoUIPrivateData.hpp" START_NAMESPACE_DISTRHO // ----------------------------------------------------------------------- // Static data, see DistrhoUI.cpp -extern double d_lastUiSampleRate; -extern void* d_lastUiDspPtr; -#ifdef HAVE_DGL -extern Window* d_lastUiWindow; -#endif -extern uintptr_t g_nextWindowId; extern const char* g_nextBundlePath; +#if DISTRHO_PLUGIN_HAS_EXTERNAL_UI +extern uintptr_t g_nextWindowId; +extern double g_nextScaleFactor; +#endif // ----------------------------------------------------------------------- -// UI callbacks +// UI exporter class -typedef void (*editParamFunc) (void* ptr, uint32_t rindex, bool started); -typedef void (*setParamFunc) (void* ptr, uint32_t rindex, float value); -typedef void (*setStateFunc) (void* ptr, const char* key, const char* value); -typedef void (*sendNoteFunc) (void* ptr, uint8_t channel, uint8_t note, uint8_t velo); -typedef void (*setSizeFunc) (void* ptr, uint width, uint height); +class UIExporter +{ + // ------------------------------------------------------------------- + // UI Widget and its private data -// ----------------------------------------------------------------------- -// UI private data - -struct UI::PrivateData { - // DSP - double sampleRate; - uint32_t parameterOffset; -#if DISTRHO_PLUGIN_WANT_DIRECT_ACCESS - void* dspPtr; -#endif + UI* ui; + UI::PrivateData* uiData; - // Callbacks - void* callbacksPtr; - editParamFunc editParamCallbackFunc; - setParamFunc setParamCallbackFunc; - setStateFunc setStateCallbackFunc; - sendNoteFunc sendNoteCallbackFunc; - setSizeFunc setSizeCallbackFunc; - - PrivateData() noexcept - : sampleRate(d_lastUiSampleRate), - parameterOffset(0), -#if DISTRHO_PLUGIN_WANT_DIRECT_ACCESS - dspPtr(d_lastUiDspPtr), -#endif - callbacksPtr(nullptr), - editParamCallbackFunc(nullptr), - setParamCallbackFunc(nullptr), - setStateCallbackFunc(nullptr), - sendNoteCallbackFunc(nullptr), - setSizeCallbackFunc(nullptr) - { - DISTRHO_SAFE_ASSERT(d_isNotZero(sampleRate)); - -#if defined(DISTRHO_PLUGIN_TARGET_DSSI) || defined(DISTRHO_PLUGIN_TARGET_LV2) - parameterOffset += DISTRHO_PLUGIN_NUM_INPUTS + DISTRHO_PLUGIN_NUM_OUTPUTS; -# if DISTRHO_PLUGIN_WANT_LATENCY - parameterOffset += 1; -# endif -#endif + // ------------------------------------------------------------------- -#ifdef DISTRHO_PLUGIN_TARGET_LV2 -# if (DISTRHO_PLUGIN_IS_SYNTH || DISTRHO_PLUGIN_WANT_TIMEPOS || DISTRHO_PLUGIN_WANT_STATE) - parameterOffset += 1; -# if DISTRHO_PLUGIN_WANT_STATE - parameterOffset += 1; -# endif -# endif +public: + UIExporter(void* const callbacksPtr, + const uintptr_t winId, + const double sampleRate, + const editParamFunc editParamCall, + const setParamFunc setParamCall, + const setStateFunc setStateCall, + const sendNoteFunc sendNoteCall, + const setSizeFunc setSizeCall, + const fileRequestFunc fileRequestCall, + const char* const bundlePath = nullptr, + void* const dspPtr = nullptr, + const double scaleFactor = 0.0, + const uint32_t bgColor = 0, + const uint32_t fgColor = 0xffffffff) + : ui(nullptr), + uiData(new UI::PrivateData()) + { + uiData->sampleRate = sampleRate; + uiData->bundlePath = bundlePath != nullptr ? strdup(bundlePath) : nullptr; + uiData->dspPtr = dspPtr; + + uiData->bgColor = bgColor; + uiData->fgColor = fgColor; + uiData->scaleFactor = scaleFactor; + uiData->winId = winId; + + uiData->callbacksPtr = callbacksPtr; + uiData->editParamCallbackFunc = editParamCall; + uiData->setParamCallbackFunc = setParamCall; + uiData->setStateCallbackFunc = setStateCall; + uiData->sendNoteCallbackFunc = sendNoteCall; + uiData->setSizeCallbackFunc = setSizeCall; + uiData->fileRequestCallbackFunc = fileRequestCall; + + g_nextBundlePath = bundlePath; +#if DISTRHO_PLUGIN_HAS_EXTERNAL_UI + g_nextWindowId = winId; + g_nextScaleFactor = scaleFactor; #endif - } + UI::PrivateData::s_nextPrivateData = uiData; - void editParamCallback(const uint32_t rindex, const bool started) - { - if (editParamCallbackFunc != nullptr) - editParamCallbackFunc(callbacksPtr, rindex, started); - } + UI* const uiPtr = createUI(); - void setParamCallback(const uint32_t rindex, const float value) - { - if (setParamCallbackFunc != nullptr) - setParamCallbackFunc(callbacksPtr, rindex, value); - } + g_nextBundlePath = nullptr; +#if DISTRHO_PLUGIN_HAS_EXTERNAL_UI + g_nextWindowId = 0; + g_nextScaleFactor = 0.0; +#else + // enter context called in the PluginWindow constructor, see DistrhoUIPrivateData.hpp + uiData->window->leaveContext(); +#endif + UI::PrivateData::s_nextPrivateData = nullptr; - void setStateCallback(const char* const key, const char* const value) - { - if (setStateCallbackFunc != nullptr) - setStateCallbackFunc(callbacksPtr, key, value); - } + DISTRHO_SAFE_ASSERT_RETURN(uiPtr != nullptr,); + ui = uiPtr; + uiData->initializing = false; - void sendNoteCallback(const uint8_t channel, const uint8_t note, const uint8_t velocity) - { - if (sendNoteCallbackFunc != nullptr) - sendNoteCallbackFunc(callbacksPtr, channel, note, velocity); +#if !DISTRHO_PLUGIN_HAS_EXTERNAL_UI + // unused + (void)bundlePath; +#endif } - void setSizeCallback(const uint width, const uint height) + ~UIExporter() { - if (setSizeCallbackFunc != nullptr) - setSizeCallbackFunc(callbacksPtr, width, height); + quit(); +#if !DISTRHO_PLUGIN_HAS_EXTERNAL_UI + uiData->window->enterContextForDeletion(); +#endif + delete ui; + delete uiData; } -}; -// ----------------------------------------------------------------------- -// Plugin Window, needed to take care of resize properly + // ------------------------------------------------------------------- -#ifdef HAVE_DGL -static inline -UI* createUiWrapper(void* const dspPtr, Window* const window) -{ - d_lastUiDspPtr = dspPtr; - d_lastUiWindow = window; - UI* const ret = createUI(); - d_lastUiDspPtr = nullptr; - d_lastUiWindow = nullptr; - return ret; -} - -class UIExporterWindow : public Window -{ -public: - UIExporterWindow(Application& app, const intptr_t winId, void* const dspPtr) - : Window(app, winId), - fUI(createUiWrapper(dspPtr, this)), - fIsReady(false) + uint getWidth() const noexcept { - DISTRHO_SAFE_ASSERT_RETURN(fUI != nullptr,); - - // set window size - setResizable(false); - setSize(fUI->getWidth(), fUI->getHeight()); + return uiData->window->getWidth(); } - ~UIExporterWindow() + uint getHeight() const noexcept { - delete fUI; + return uiData->window->getHeight(); } - UI* getUI() const noexcept + double getScaleFactor() const noexcept { - return fUI; + return uiData->window->getScaleFactor(); } - bool isReady() const noexcept + bool getGeometryConstraints(uint& minimumWidth, uint& minimumHeight, bool& keepAspectRatio) const noexcept { - return fIsReady; +#if DISTRHO_PLUGIN_HAS_EXTERNAL_UI + uiData->window->getGeometryConstraints(minimumWidth, minimumHeight, keepAspectRatio); +#else + const DGL_NAMESPACE::Size size(uiData->window->getGeometryConstraints(keepAspectRatio)); + minimumWidth = size.getWidth(); + minimumHeight = size.getHeight(); +#endif + return true; } -protected: - // custom window reshape - void onReshape(uint width, uint height) override + bool isResizable() const noexcept { - DISTRHO_SAFE_ASSERT_RETURN(fUI != nullptr,); - - fUI->uiReshape(width, height); - fIsReady = true; + return uiData->window->isResizable(); } -#ifndef DGL_FILE_BROWSER_DISABLED - // custom file-browser selected - void fileBrowserSelected(const char* filename) override + bool isVisible() const noexcept { - DISTRHO_SAFE_ASSERT_RETURN(fUI != nullptr,); - - fUI->uiFileBrowserSelected(filename); + return uiData->window->isVisible(); } -#endif - -private: - UI* const fUI; - bool fIsReady; -}; -#else -static inline -UI* createUiWrapper(void* const dspPtr, const uintptr_t winId, const char* const bundlePath) -{ - d_lastUiDspPtr = dspPtr; - g_nextWindowId = winId; - g_nextBundlePath = bundlePath; - UI* const ret = createUI(); - d_lastUiDspPtr = nullptr; - g_nextWindowId = 0; - g_nextBundlePath = nullptr; - return ret; -} -#endif -// ----------------------------------------------------------------------- -// UI exporter class - -class UIExporter -{ -public: - UIExporter(void* const callbacksPtr, - const intptr_t winId, - const editParamFunc editParamCall, - const setParamFunc setParamCall, - const setStateFunc setStateCall, - const sendNoteFunc sendNoteCall, - const setSizeFunc setSizeCall, - void* const dspPtr = nullptr, - const char* const bundlePath = nullptr) -#ifdef HAVE_DGL - : glApp(), - glWindow(glApp, winId, dspPtr), - fChangingSize(false), - fUI(glWindow.getUI()), -#else - : fUI(createUiWrapper(dspPtr, winId, bundlePath)), -#endif - fData((fUI != nullptr) ? fUI->pData : nullptr) + uintptr_t getNativeWindowHandle() const noexcept { - DISTRHO_SAFE_ASSERT_RETURN(fUI != nullptr,); - DISTRHO_SAFE_ASSERT_RETURN(fData != nullptr,); - - fData->callbacksPtr = callbacksPtr; - fData->editParamCallbackFunc = editParamCall; - fData->setParamCallbackFunc = setParamCall; - fData->setStateCallbackFunc = setStateCall; - fData->sendNoteCallbackFunc = sendNoteCall; - fData->setSizeCallbackFunc = setSizeCall; - -#ifdef HAVE_DGL - // unused - return; (void)bundlePath; -#endif + return uiData->window->getNativeWindowHandle(); } - // ------------------------------------------------------------------- - - uint getWidth() const noexcept + uint getBackgroundColor() const noexcept { -#ifdef HAVE_DGL - return glWindow.getWidth(); -#else - DISTRHO_SAFE_ASSERT_RETURN(fUI != nullptr, 1); - return fUI->getWidth(); -#endif - } + DISTRHO_SAFE_ASSERT_RETURN(uiData != nullptr, 0); - uint getHeight() const noexcept - { -#ifdef HAVE_DGL - return glWindow.getHeight(); -#else - DISTRHO_SAFE_ASSERT_RETURN(fUI != nullptr, 1); - return fUI->getHeight(); -#endif + return uiData->bgColor; } - bool isVisible() const noexcept + uint getForegroundColor() const noexcept { -#ifdef HAVE_DGL - return glWindow.isVisible(); -#else - DISTRHO_SAFE_ASSERT_RETURN(fUI != nullptr, false); - return fUI->isRunning(); -#endif - } + DISTRHO_SAFE_ASSERT_RETURN(uiData != nullptr, 0xffffffff); - // ------------------------------------------------------------------- - - intptr_t getWindowId() const noexcept - { -#ifdef HAVE_DGL - return glWindow.getWindowId(); -#else - return 0; -#endif + return uiData->fgColor; } // ------------------------------------------------------------------- uint32_t getParameterOffset() const noexcept { - DISTRHO_SAFE_ASSERT_RETURN(fData != nullptr, 0); + DISTRHO_SAFE_ASSERT_RETURN(uiData != nullptr, 0); - return fData->parameterOffset; + return uiData->parameterOffset; } // ------------------------------------------------------------------- void parameterChanged(const uint32_t index, const float value) { - DISTRHO_SAFE_ASSERT_RETURN(fUI != nullptr,); + DISTRHO_SAFE_ASSERT_RETURN(ui != nullptr,); - fUI->parameterChanged(index, value); + ui->parameterChanged(index, value); } #if DISTRHO_PLUGIN_WANT_PROGRAMS void programLoaded(const uint32_t index) { - DISTRHO_SAFE_ASSERT_RETURN(fUI != nullptr,); + DISTRHO_SAFE_ASSERT_RETURN(ui != nullptr,); - fUI->programLoaded(index); + ui->programLoaded(index); } #endif #if DISTRHO_PLUGIN_WANT_STATE void stateChanged(const char* const key, const char* const value) { - DISTRHO_SAFE_ASSERT_RETURN(fUI != nullptr,); + DISTRHO_SAFE_ASSERT_RETURN(ui != nullptr,); DISTRHO_SAFE_ASSERT_RETURN(key != nullptr && key[0] != '\0',); DISTRHO_SAFE_ASSERT_RETURN(value != nullptr,); - fUI->stateChanged(key, value); + ui->stateChanged(key, value); } #endif // ------------------------------------------------------------------- -#ifdef HAVE_DGL - void exec(IdleCallback* const cb) +#if DISTRHO_UI_IS_STANDALONE + void exec(DGL_NAMESPACE::IdleCallback* const cb) { DISTRHO_SAFE_ASSERT_RETURN(cb != nullptr,); - DISTRHO_SAFE_ASSERT_RETURN(fUI != nullptr,); - glWindow.addIdleCallback(cb); - glWindow.setVisible(true); - glApp.exec(); + uiData->window->show(); + uiData->window->focus(); + uiData->app.addIdleCallback(cb); + uiData->app.exec(); } void exec_idle() { - if (glWindow.isReady()) - fUI->uiIdle(); + DISTRHO_SAFE_ASSERT_RETURN(ui != nullptr, ); + + ui->uiIdle(); } -#endif - bool idle() + void showAndFocus() { - DISTRHO_SAFE_ASSERT_RETURN(fUI != nullptr, false); + uiData->window->show(); + uiData->window->focus(); + } +#endif -#ifdef HAVE_DGL - glApp.idle(); + bool plugin_idle() + { + DISTRHO_SAFE_ASSERT_RETURN(ui != nullptr, false); - if (glWindow.isReady()) - fUI->uiIdle(); + uiData->app.idle(); + ui->uiIdle(); + return ! uiData->app.isQuitting(); + } - return ! glApp.isQuiting(); -#else - return fUI->isRunning(); -#endif + void focus() + { + uiData->window->focus(); } void quit() { -#ifdef HAVE_DGL - glWindow.close(); - glApp.quit(); -#else - DISTRHO_SAFE_ASSERT_RETURN(fUI != nullptr,); - fUI->terminateAndWaitForProcess(); -#endif + uiData->window->close(); + uiData->app.quit(); } // ------------------------------------------------------------------- - void setWindowTitle(const char* const uiTitle) +#if defined(DISTRHO_PLUGIN_TARGET_VST3) && (defined(DISTRHO_OS_MAC) || defined(DISTRHO_OS_WINDOWS)) + void idleForVST3() { -#ifdef HAVE_DGL - glWindow.setTitle(uiTitle); -#else - DISTRHO_SAFE_ASSERT_RETURN(fUI != nullptr,); - fUI->setTitle(uiTitle); -#endif + DISTRHO_SAFE_ASSERT_RETURN(ui != nullptr,); + + uiData->app.triggerIdleCallbacks(); + ui->uiIdle(); } -#ifdef HAVE_DGL - void setWindowSize(const uint width, const uint height, const bool updateUI = false) +# if !DISTRHO_PLUGIN_HAS_EXTERNAL_UI + void addIdleCallbackForVST3(IdleCallback* const cb, const uint timerFrequencyInMs) { - DISTRHO_SAFE_ASSERT_RETURN(fUI != nullptr,); - DISTRHO_SAFE_ASSERT_RETURN(! fChangingSize,); + uiData->window->addIdleCallback(cb, timerFrequencyInMs); + } - fChangingSize = true; + void removeIdleCallbackForVST3(IdleCallback* const cb) + { + uiData->window->removeIdleCallback(cb); + } +# endif +#endif - if (updateUI) - fUI->setSize(width, height); + // ------------------------------------------------------------------- - glWindow.setSize(width, height); + void setWindowOffset(const int x, const int y) + { +#if DISTRHO_PLUGIN_HAS_EXTERNAL_UI + // TODO + (void)x; (void)y; +#else + uiData->window->setOffset(x, y); +#endif + } + +#ifdef DISTRHO_PLUGIN_TARGET_VST3 + void setWindowSizeForVST3(const uint width, const uint height) + { +# if DISTRHO_PLUGIN_HAS_EXTERNAL_UI + ui->setSize(width, height); +# else + uiData->window->setSizeForVST3(width, height); +# endif + } +#endif - fChangingSize = false; + void setWindowTitle(const char* const uiTitle) + { + uiData->window->setTitle(uiTitle); } void setWindowTransientWinId(const uintptr_t winId) { +#if DISTRHO_PLUGIN_HAS_EXTERNAL_UI + ui->setTransientWindowId(winId); +#elif 0 /* TODO */ glWindow.setTransientWinId(winId); +#else + (void)winId; +#endif } bool setWindowVisible(const bool yesNo) { - glWindow.setVisible(yesNo); + uiData->window->setVisible(yesNo); - return ! glApp.isQuiting(); + return ! uiData->app.isQuitting(); } - bool handlePluginKeyboard(const bool press, const uint key) +#if !DISTRHO_PLUGIN_HAS_EXTERNAL_UI + bool handlePluginKeyboardVST2(const bool press, const uint key, const uint16_t mods) { - return glWindow.handlePluginKeyboard(press, key); + DGL_NAMESPACE::Widget::KeyboardEvent ev; + ev.mod = mods; + ev.press = press; + ev.key = key; + + const bool ret = ui->onKeyboard(ev); + + if (! press) + return ret; + + DGL_NAMESPACE::Widget::CharacterInputEvent cev; + cev.mod = mods; + cev.character = key; + + // if shift modifier is on, convert a-z -> A-Z for character input + if (key >= 'a' && key <= 'z' && (mods & DGL_NAMESPACE::kModifierShift) != 0) + cev.character -= 'a' - 'A'; + + ui->onCharacterInput(cev); + return ret; } - bool handlePluginSpecial(const bool press, const Key key) + bool handlePluginKeyboardVST3(const bool press, const uint keychar, const uint keycode, const uint16_t mods) { - return glWindow.handlePluginKeyboard(press, key); + DGL_NAMESPACE::Widget::KeyboardEvent ev; + ev.mod = mods; + ev.press = press; + ev.key = keychar; + ev.keycode = keycode; + + const bool ret = ui->onKeyboard(ev); + + if (! press) + return ret; + + DGL_NAMESPACE::Widget::CharacterInputEvent cev; + cev.mod = mods; + cev.keycode = keycode; + cev.character = keychar; + + // if shift modifier is on, convert a-z -> A-Z for character input + if (keychar >= 'a' && keychar <= 'z' && (mods & DGL_NAMESPACE::kModifierShift) != 0) + cev.character -= 'a' - 'A'; + + ui->onCharacterInput(cev); + return ret; } -#else - void setWindowSize(const uint, const uint, const bool) {} - void setWindowTransientWinId(const uintptr_t) {} - bool setWindowVisible(const bool) { return true; } #endif // ------------------------------------------------------------------- - void setSampleRate(const double sampleRate, const bool doCallback = false) + void notifyScaleFactorChanged(const double scaleFactor) { - DISTRHO_SAFE_ASSERT_RETURN(fData != nullptr,); - DISTRHO_SAFE_ASSERT_RETURN(fUI != nullptr,); - DISTRHO_SAFE_ASSERT(sampleRate > 0.0); + DISTRHO_SAFE_ASSERT_RETURN(ui != nullptr,); - if (d_isEqual(fData->sampleRate, sampleRate)) - return; + ui->uiScaleFactorChanged(scaleFactor); + } - fData->sampleRate = sampleRate; +#if !DISTRHO_PLUGIN_HAS_EXTERNAL_UI + void notifyFocusChanged(const bool focus) + { + DISTRHO_SAFE_ASSERT_RETURN(ui != nullptr,); - if (doCallback) - fUI->sampleRateChanged(sampleRate); + ui->uiFocus(focus, DGL_NAMESPACE::kCrossingNormal); } +#endif -private: -#ifdef HAVE_DGL - // ------------------------------------------------------------------- - // DGL Application and Window for this widget - - Application glApp; - UIExporterWindow glWindow; + void setSampleRate(const double sampleRate, const bool doCallback = false) + { + DISTRHO_SAFE_ASSERT_RETURN(ui != nullptr,); + DISTRHO_SAFE_ASSERT_RETURN(uiData != nullptr,); + DISTRHO_SAFE_ASSERT(sampleRate > 0.0); - // prevent recursion - bool fChangingSize; -#endif + if (d_isEqual(uiData->sampleRate, sampleRate)) + return; - // ------------------------------------------------------------------- - // Widget and DistrhoUI data + uiData->sampleRate = sampleRate; - UI* const fUI; - UI::PrivateData* const fData; + if (doCallback) + ui->sampleRateChanged(sampleRate); + } DISTRHO_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(UIExporter) }; diff --git a/source/modules/distrho/src/DistrhoUIPrivateData.hpp b/source/modules/distrho/src/DistrhoUIPrivateData.hpp new file mode 100644 index 000000000..5d56586b6 --- /dev/null +++ b/source/modules/distrho/src/DistrhoUIPrivateData.hpp @@ -0,0 +1,500 @@ +/* + * DISTRHO Plugin Framework (DPF) + * Copyright (C) 2012-2021 Filipe Coelho + * + * Permission to use, copy, modify, and/or distribute this software for any purpose with + * or without fee is hereby granted, provided that the above copyright notice and this + * permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD + * TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN + * NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER + * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef DISTRHO_UI_PRIVATE_DATA_HPP_INCLUDED +#define DISTRHO_UI_PRIVATE_DATA_HPP_INCLUDED + +#include "../DistrhoUI.hpp" + +#ifdef DISTRHO_PLUGIN_TARGET_VST3 +# include "DistrhoPluginVST3.hpp" +#endif + +#if DISTRHO_PLUGIN_HAS_EXTERNAL_UI +# include "../extra/Sleep.hpp" +#else +# include "../../dgl/src/ApplicationPrivateData.hpp" +# include "../../dgl/src/WindowPrivateData.hpp" +# include "../../dgl/src/pugl.hpp" +#endif + +#if defined(DISTRHO_PLUGIN_TARGET_JACK) || defined(DISTRHO_PLUGIN_TARGET_DSSI) +# define DISTRHO_UI_IS_STANDALONE 1 +#else +# define DISTRHO_UI_IS_STANDALONE 0 +#endif + +#ifdef DISTRHO_PLUGIN_TARGET_VST3 +# define DISTRHO_UI_IS_VST3 1 +#else +# define DISTRHO_UI_IS_VST3 0 +#endif + +#ifdef DISTRHO_PLUGIN_TARGET_VST2 +# undef DISTRHO_UI_USER_RESIZABLE +# define DISTRHO_UI_USER_RESIZABLE 0 +#endif + +// ----------------------------------------------------------------------- + +#if DISTRHO_PLUGIN_HAS_EXTERNAL_UI +START_NAMESPACE_DISTRHO +#else +START_NAMESPACE_DGL +#endif + +// ----------------------------------------------------------------------- +// Plugin Application, will set class name based on plugin details + +#if DISTRHO_PLUGIN_HAS_EXTERNAL_UI +struct PluginApplication +{ + DGL_NAMESPACE::IdleCallback* idleCallback; + UI* ui; + + explicit PluginApplication() + : idleCallback(nullptr), + ui(nullptr) {} + + void addIdleCallback(DGL_NAMESPACE::IdleCallback* const cb) + { + DISTRHO_SAFE_ASSERT_RETURN(cb != nullptr,); + DISTRHO_SAFE_ASSERT_RETURN(idleCallback == nullptr,); + + idleCallback = cb; + } + + bool isQuitting() const noexcept + { + return ui->isQuitting(); + } + + bool isStandalone() const noexcept + { + return DISTRHO_UI_IS_STANDALONE; + } + + void exec() + { + while (ui->isRunning()) + { + d_msleep(30); + idleCallback->idleCallback(); + } + + if (! ui->isQuitting()) + ui->close(); + } + + // these are not needed + void idle() {} + void quit() {} + void triggerIdleCallbacks() {} + + DISTRHO_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(PluginApplication) +}; +#else +class PluginApplication : public Application +{ +public: + explicit PluginApplication() + : Application(DISTRHO_UI_IS_STANDALONE) + { + const char* const className = ( +#ifdef DISTRHO_PLUGIN_BRAND + DISTRHO_PLUGIN_BRAND +#else + DISTRHO_MACRO_AS_STRING(DISTRHO_NAMESPACE) +#endif + "-" DISTRHO_PLUGIN_NAME + ); + setClassName(className); + } + + void triggerIdleCallbacks() + { + pData->triggerIdleCallbacks(); + } + + DISTRHO_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(PluginApplication) +}; +#endif + +// ----------------------------------------------------------------------- +// Plugin Window, will pass some Window events to UI + +#if DISTRHO_PLUGIN_HAS_EXTERNAL_UI +class PluginWindow +{ + UI* const ui; + +public: + explicit PluginWindow(UI* const uiPtr, PluginApplication& app) + : ui(uiPtr) + { + app.ui = ui; + } + + // fetch cached data + uint getWidth() const noexcept { return ui->pData.width; } + uint getHeight() const noexcept { return ui->pData.height; } + double getScaleFactor() const noexcept { return ui->pData.scaleFactor; } + + // direct mappings + void close() { ui->close(); } + void focus() { ui->focus(); } + void show() { ui->show(); } + bool isResizable() const noexcept { return ui->isResizable(); } + bool isVisible() const noexcept { return ui->isVisible(); } + void setTitle(const char* const title) { ui->setTitle(title); } + void setVisible(const bool visible) { ui->setVisible(visible); } + uintptr_t getNativeWindowHandle() const noexcept { return ui->getNativeWindowHandle(); } + void getGeometryConstraints(uint& minimumWidth, uint& minimumHeight, bool& keepAspectRatio) const noexcept + { + minimumWidth = ui->pData.minWidth; + minimumHeight = ui->pData.minHeight; + keepAspectRatio = ui->pData.keepAspectRatio; + } + + DISTRHO_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(PluginWindow) +}; +#else // DISTRHO_PLUGIN_HAS_EXTERNAL_UI +class PluginWindow : public Window +{ + DISTRHO_NAMESPACE::UI* const ui; + bool initializing; + bool receivedReshapeDuringInit; + +public: + explicit PluginWindow(DISTRHO_NAMESPACE::UI* const uiPtr, + PluginApplication& app, + const uintptr_t parentWindowHandle, + const uint width, + const uint height, + const double scaleFactor) + : Window(app, parentWindowHandle, width, height, scaleFactor, + DISTRHO_UI_USER_RESIZABLE, DISTRHO_UI_IS_VST3, false), + ui(uiPtr), + initializing(true), + receivedReshapeDuringInit(false) + { + if (pData->view == nullptr) + return; + + // this is called just before creating UI, ensuring proper context to it + if (pData->initPost()) + puglBackendEnter(pData->view); + } + + ~PluginWindow() + { + if (pData->view != nullptr) + puglBackendLeave(pData->view); + } + + // called after creating UI, restoring proper context + void leaveContext() + { + if (pData->view == nullptr) + return; + + if (receivedReshapeDuringInit) + ui->uiReshape(getWidth(), getHeight()); + + initializing = false; + puglBackendLeave(pData->view); + } + + // used for temporary windows (VST2/3 get size without active/visible view) + void setIgnoreIdleCallbacks(const bool ignore = true) + { + pData->ignoreIdleCallbacks = ignore; + } + + // called right before deleting UI, ensuring correct context + void enterContextForDeletion() + { + if (pData->view != nullptr) + puglBackendEnter(pData->view); + } + + #ifdef DISTRHO_PLUGIN_TARGET_VST3 + void setSizeForVST3(const uint width, const uint height) + { + puglSetWindowSize(pData->view, width, height); + } + #endif + +protected: + void onFocus(const bool focus, const DGL_NAMESPACE::CrossingMode mode) override + { + DISTRHO_SAFE_ASSERT_RETURN(ui != nullptr,); + + if (initializing) + return; + + ui->uiFocus(focus, mode); + } + + void onReshape(const uint width, const uint height) override + { + DISTRHO_SAFE_ASSERT_RETURN(ui != nullptr,); + + if (initializing) + { + receivedReshapeDuringInit = true; + return; + } + + ui->uiReshape(width, height); + } + + void onScaleFactorChanged(const double scaleFactor) override + { + DISTRHO_SAFE_ASSERT_RETURN(ui != nullptr,); + + if (initializing) + return; + + ui->uiScaleFactorChanged(scaleFactor); + } + +# ifndef DGL_FILE_BROWSER_DISABLED + void onFileSelected(const char* filename) override; +# endif + + DISTRHO_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(PluginWindow) +}; +#endif // DISTRHO_PLUGIN_HAS_EXTERNAL_UI + +#if DISTRHO_PLUGIN_HAS_EXTERNAL_UI +END_NAMESPACE_DISTRHO +#else +END_NAMESPACE_DGL +#endif + +// ----------------------------------------------------------------------- + +START_NAMESPACE_DISTRHO + +#if !DISTRHO_PLUGIN_HAS_EXTERNAL_UI +using DGL_NAMESPACE::PluginApplication; +using DGL_NAMESPACE::PluginWindow; +#endif + +// ----------------------------------------------------------------------- +// UI callbacks + +typedef void (*editParamFunc) (void* ptr, uint32_t rindex, bool started); +typedef void (*setParamFunc) (void* ptr, uint32_t rindex, float value); +typedef void (*setStateFunc) (void* ptr, const char* key, const char* value); +typedef void (*sendNoteFunc) (void* ptr, uint8_t channel, uint8_t note, uint8_t velo); +typedef void (*setSizeFunc) (void* ptr, uint width, uint height); +typedef bool (*fileRequestFunc) (void* ptr, const char* key); + +// ----------------------------------------------------------------------- +// UI private data + +struct UI::PrivateData { + // DGL + PluginApplication app; + ScopedPointer window; + + // DSP + double sampleRate; + uint32_t parameterOffset; + void* dspPtr; + + // UI + uint bgColor; + uint fgColor; + double scaleFactor; + uintptr_t winId; +#if !DISTRHO_PLUGIN_HAS_EXTERNAL_UI && !defined(DGL_FILE_BROWSER_DISABLED) + char* uiStateFileKeyRequest; +#endif + char* bundlePath; + + // Ignore initial resize events while initializing + bool initializing; + + // Callbacks + void* callbacksPtr; + editParamFunc editParamCallbackFunc; + setParamFunc setParamCallbackFunc; + setStateFunc setStateCallbackFunc; + sendNoteFunc sendNoteCallbackFunc; + setSizeFunc setSizeCallbackFunc; + fileRequestFunc fileRequestCallbackFunc; + + PrivateData() noexcept + : app(), + window(nullptr), + sampleRate(0), + parameterOffset(0), + dspPtr(nullptr), + bgColor(0), + fgColor(0xffffffff), + scaleFactor(1.0), + winId(0), +#if !DISTRHO_PLUGIN_HAS_EXTERNAL_UI && !defined(DGL_FILE_BROWSER_DISABLED) + uiStateFileKeyRequest(nullptr), +#endif + bundlePath(nullptr), + initializing(true), + callbacksPtr(nullptr), + editParamCallbackFunc(nullptr), + setParamCallbackFunc(nullptr), + setStateCallbackFunc(nullptr), + sendNoteCallbackFunc(nullptr), + setSizeCallbackFunc(nullptr), + fileRequestCallbackFunc(nullptr) + { +#if defined(DISTRHO_PLUGIN_TARGET_DSSI) || defined(DISTRHO_PLUGIN_TARGET_LV2) + parameterOffset += DISTRHO_PLUGIN_NUM_INPUTS + DISTRHO_PLUGIN_NUM_OUTPUTS; +# if DISTRHO_PLUGIN_WANT_LATENCY + parameterOffset += 1; +# endif +#endif + +#ifdef DISTRHO_PLUGIN_TARGET_LV2 +# if (DISTRHO_PLUGIN_WANT_MIDI_INPUT || DISTRHO_PLUGIN_WANT_TIMEPOS || DISTRHO_PLUGIN_WANT_STATE) + parameterOffset += 1; +# endif +# if (DISTRHO_PLUGIN_WANT_MIDI_OUTPUT || DISTRHO_PLUGIN_WANT_STATE) + parameterOffset += 1; +# endif +#endif + +#ifdef DISTRHO_PLUGIN_TARGET_VST3 + parameterOffset += kVst3InternalParameterCount; +#endif + } + + ~PrivateData() noexcept + { +#if !DISTRHO_PLUGIN_HAS_EXTERNAL_UI && !defined(DGL_FILE_BROWSER_DISABLED) + std::free(uiStateFileKeyRequest); +#endif + std::free(bundlePath); + } + + void editParamCallback(const uint32_t rindex, const bool started) + { + if (editParamCallbackFunc != nullptr) + editParamCallbackFunc(callbacksPtr, rindex, started); + } + + void setParamCallback(const uint32_t rindex, const float value) + { + if (setParamCallbackFunc != nullptr) + setParamCallbackFunc(callbacksPtr, rindex, value); + } + + void setStateCallback(const char* const key, const char* const value) + { + if (setStateCallbackFunc != nullptr) + setStateCallbackFunc(callbacksPtr, key, value); + } + + void sendNoteCallback(const uint8_t channel, const uint8_t note, const uint8_t velocity) + { + if (sendNoteCallbackFunc != nullptr) + sendNoteCallbackFunc(callbacksPtr, channel, note, velocity); + } + + void setSizeCallback(const uint width, const uint height) + { + if (setSizeCallbackFunc != nullptr) + setSizeCallbackFunc(callbacksPtr, width, height); + } + + // implemented below, after PluginWindow + bool fileRequestCallback(const char* const key); + + static UI::PrivateData* s_nextPrivateData; +#if DISTRHO_PLUGIN_HAS_EXTERNAL_UI + static ExternalWindow::PrivateData createNextWindow(UI* ui, uint width, uint height); +#else + static PluginWindow& createNextWindow(UI* ui, uint width, uint height); +#endif +}; + +// ----------------------------------------------------------------------- +// UI private data fileRequestCallback, which requires PluginWindow definitions + +inline bool UI::PrivateData::fileRequestCallback(const char* const key) +{ + if (fileRequestCallbackFunc != nullptr) + return fileRequestCallbackFunc(callbacksPtr, key); + +#if DISTRHO_PLUGIN_WANT_STATE && !DISTRHO_PLUGIN_HAS_EXTERNAL_UI && !defined(DGL_FILE_BROWSER_DISABLED) + std::free(uiStateFileKeyRequest); + uiStateFileKeyRequest = strdup(key); + DISTRHO_SAFE_ASSERT_RETURN(uiStateFileKeyRequest != nullptr, false); + + char title[0xff]; + snprintf(title, sizeof(title)-1u, DISTRHO_PLUGIN_NAME ": %s", key); + title[sizeof(title)-1u] = '\0'; + + FileBrowserOptions opts; + opts.title = title; + return window->openFileBrowser(opts); +#endif + + return false; +} + +END_NAMESPACE_DISTRHO + +// ----------------------------------------------------------------------- +// PluginWindow onFileSelected that require UI::PrivateData definitions + +#if !DISTRHO_PLUGIN_HAS_EXTERNAL_UI && !defined(DGL_FILE_BROWSER_DISABLED) +START_NAMESPACE_DGL + +inline void PluginWindow::onFileSelected(const char* const filename) +{ + DISTRHO_SAFE_ASSERT_RETURN(ui != nullptr,); + + if (initializing) + return; + +# if DISTRHO_PLUGIN_WANT_STATE + if (char* const key = ui->uiData->uiStateFileKeyRequest) + { + ui->uiData->uiStateFileKeyRequest = nullptr; + if (filename != nullptr) + { + // notify DSP + ui->setState(key, filename); + // notify UI + ui->stateChanged(key, filename); + } + std::free(key); + return; + } +# endif + + ui->uiFileBrowserSelected(filename); +} + +END_NAMESPACE_DGL +#endif + +// ----------------------------------------------------------------------- + +#endif // DISTRHO_UI_PRIVATE_DATA_HPP_INCLUDED diff --git a/source/modules/distrho/src/DistrhoUtils.cpp b/source/modules/distrho/src/DistrhoUtils.cpp new file mode 100644 index 000000000..7aaed1a29 --- /dev/null +++ b/source/modules/distrho/src/DistrhoUtils.cpp @@ -0,0 +1,140 @@ +/* + * DISTRHO Plugin Framework (DPF) + * Copyright (C) 2012-2021 Filipe Coelho + * + * Permission to use, copy, modify, and/or distribute this software for any purpose with + * or without fee is hereby granted, provided that the above copyright notice and this + * permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD + * TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN + * NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER + * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef DISTRHO_IS_STANDALONE +# error Wrong build configuration +#endif + +#include "../extra/String.hpp" + +#ifdef DISTRHO_OS_WINDOWS +# include +#else +# ifndef STATIC_BUILD +# include +# endif +# include +# include +#endif + +#if defined(DISTRHO_OS_WINDOWS) && !DISTRHO_IS_STANDALONE +static HINSTANCE hInstance = nullptr; + +DISTRHO_PLUGIN_EXPORT +BOOL WINAPI DllMain(HINSTANCE hInst, DWORD reason, LPVOID) +{ + if (reason == DLL_PROCESS_ATTACH) + hInstance = hInst; + return 1; +} +#endif + +START_NAMESPACE_DISTRHO + +// ----------------------------------------------------------------------- + +const char* getBinaryFilename() +{ + static String filename; + + if (filename.isNotEmpty()) + return filename; + +#ifdef DISTRHO_OS_WINDOWS +# if DISTRHO_IS_STANDALONE + constexpr const HINSTANCE hInstance = nullptr; +# endif + CHAR filenameBuf[MAX_PATH]; + filenameBuf[0] = '\0'; + GetModuleFileNameA(hInstance, filenameBuf, sizeof(filenameBuf)); + filename = filenameBuf; +#elif !defined(STATIC_BUILD) + Dl_info info; + dladdr((void*)getBinaryFilename, &info); + char filenameBuf[PATH_MAX]; + filename = realpath(info.dli_fname, filenameBuf); +#endif + + return filename; +} + +const char* getPluginFormatName() noexcept +{ +#if defined(DISTRHO_PLUGIN_TARGET_CARLA) + return "Carla"; +#elif defined(DISTRHO_PLUGIN_TARGET_JACK) + return "JACK/Standalone"; +#elif defined(DISTRHO_PLUGIN_TARGET_LADSPA) + return "LADSPA"; +#elif defined(DISTRHO_PLUGIN_TARGET_DSSI) + return "DSSI"; +#elif defined(DISTRHO_PLUGIN_TARGET_LV2) + return "LV2"; +#elif defined(DISTRHO_PLUGIN_TARGET_VST2) + return "VST2"; +#elif defined(DISTRHO_PLUGIN_TARGET_VST3) + return "VST3"; +#else + return "Unknown"; +#endif +} + +const char* getResourcePath(const char* const bundlePath) noexcept +{ + DISTRHO_SAFE_ASSERT_RETURN(bundlePath != nullptr, nullptr); + +#if defined(DISTRHO_PLUGIN_TARGET_JACK) || defined(DISTRHO_PLUGIN_TARGET_VST2) + static String resourcePath; + + if (resourcePath.isEmpty()) + { + resourcePath = bundlePath; +# ifdef DISTRHO_OS_MAC + resourcePath += "/Contents/Resources"; +# else + resourcePath += DISTRHO_OS_SEP_STR "resources"; +# endif + } + + return resourcePath.buffer(); +#elif defined(DISTRHO_PLUGIN_TARGET_LV2) + static String resourcePath; + + if (resourcePath.isEmpty()) + { + resourcePath = bundlePath; + resourcePath += DISTRHO_OS_SEP_STR "resources"; + } + + return resourcePath.buffer(); +#elif defined(DISTRHO_PLUGIN_TARGET_VST3) + static String resourcePath; + + if (resourcePath.isEmpty()) + { + resourcePath = bundlePath; + resourcePath += "/Contents/Resources"; + } + + return resourcePath.buffer(); +#endif + + return nullptr; +} + +// ----------------------------------------------------------------------- + +END_NAMESPACE_DISTRHO diff --git a/source/modules/distrho/src/dssi b/source/modules/distrho/src/dssi deleted file mode 120000 index b9532b815..000000000 --- a/source/modules/distrho/src/dssi +++ /dev/null @@ -1 +0,0 @@ -../../../includes/dssi \ No newline at end of file diff --git a/source/modules/distrho/src/ladspa b/source/modules/distrho/src/ladspa deleted file mode 120000 index ab5d2dd19..000000000 --- a/source/modules/distrho/src/ladspa +++ /dev/null @@ -1 +0,0 @@ -../../../includes/ladspa \ No newline at end of file diff --git a/source/modules/distrho/src/lv2 b/source/modules/distrho/src/lv2 deleted file mode 120000 index 65c3c111d..000000000 --- a/source/modules/distrho/src/lv2 +++ /dev/null @@ -1 +0,0 @@ -../../../includes/lv2 \ No newline at end of file diff --git a/source/modules/distrho/src/vestige b/source/modules/distrho/src/vestige deleted file mode 120000 index e5010d964..000000000 --- a/source/modules/distrho/src/vestige +++ /dev/null @@ -1 +0,0 @@ -../../../includes/vestige \ No newline at end of file diff --git a/source/plugin/carla-vst-export.cpp b/source/plugin/carla-vst-export.cpp index b9d97cd8c..9403b8791 100644 --- a/source/plugin/carla-vst-export.cpp +++ b/source/plugin/carla-vst-export.cpp @@ -21,6 +21,7 @@ #include "ui_launcher_res.cpp" #include +#include #ifdef __WINE__ __cdecl static intptr_t cvst_dispatcherCallback(AEffect* effect, int32_t opcode, int32_t index, intptr_t value, void* ptr, float opt) @@ -130,9 +131,9 @@ intptr_t VSTAudioMaster(AEffect* effect, int32_t opcode, int32_t index, intptr_t bool isUsingUILauncher() { -#ifdef CARLA_OS_LINUX - return false; -#else +#if defined(CARLA_OS_MAC) || defined(CARLA_OS_WIN) return true; +#else + return false; #endif } diff --git a/source/plugin/carla-vst.hpp b/source/plugin/carla-vst.hpp index 2480c9094..7c5372bca 100644 --- a/source/plugin/carla-vst.hpp +++ b/source/plugin/carla-vst.hpp @@ -1,6 +1,6 @@ /* * Carla Native Plugins - * Copyright (C) 2013-2019 Filipe Coelho + * Copyright (C) 2013-2022 Filipe Coelho * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -32,7 +32,7 @@ struct VstObject { NativePlugin* plugin; }; -CarlaUILauncher* createUILauncher(intptr_t winId, const NativePluginDescriptor* d, NativePluginHandle h); +CarlaUILauncher* createUILauncher(uintptr_t winId, const NativePluginDescriptor* d, NativePluginHandle h); void idleUILauncher(CarlaUILauncher* ui); void destoryUILauncher(CarlaUILauncher* ui); diff --git a/source/plugin/ui_launcher.cpp b/source/plugin/ui_launcher.cpp index c762fd79c..8012816df 100644 --- a/source/plugin/ui_launcher.cpp +++ b/source/plugin/ui_launcher.cpp @@ -1,6 +1,6 @@ /* * Carla Native Plugin UI launcher - * Copyright (C) 2018 Filipe Coelho + * Copyright (C) 2018-2022 Filipe Coelho * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -15,8 +15,10 @@ * For a full copy of the GNU General Public License see the doc/GPL.txt file. */ -#include "dgl/Application.hpp" -#include "dgl/ImageWidgets.hpp" +#include "dgl/OpenGL.hpp" +#include "dgl/src/pugl.hpp" +#include "dgl/src/WindowPrivateData.hpp" + #include "CarlaNative.h" #include "ui_launcher_res.hpp" #include "CarlaDefines.h" @@ -25,24 +27,71 @@ START_NAMESPACE_DGL -class CarlaButtonWidget : public Widget, - private ImageButton::Callback +class PluginApplication : public Application +{ +public: + explicit PluginApplication() + : Application(false) + { + setClassName("CarlaPluginWrapper"); + } +}; + +class PluginWindow : public Window +{ +public: + explicit PluginWindow(PluginApplication& app, const uintptr_t winId) + : Window(app, winId, ui_launcher_res::carla_uiWidth, ui_launcher_res::carla_uiHeight, 0.0, false, false, false) + { + // this is called just before creating UI, ensuring proper context to it + if (pData->view != nullptr && pData->initPost()) + puglBackendEnter(pData->view); + } + + ~PluginWindow() + { + if (pData->view != nullptr) + puglBackendLeave(pData->view); + } + + // called right before deleting UI, ensuring correct context + void enterContextForDeletion() + { + if (pData->view != nullptr) + puglBackendEnter(pData->view); + } + + // called after creating UI, restoring proper context + void leaveContextAfterCreation() + { + if (pData->view != nullptr) + puglBackendLeave(pData->view); + } +}; + +class CarlaButtonWidget : public TopLevelWidget, + private OpenGLImageButton::Callback { public: - CarlaButtonWidget(Window& parent, const NativePluginDescriptor* const d, const NativePluginHandle h) - : Widget(parent), + explicit CarlaButtonWidget(PluginWindow& parent, const NativePluginDescriptor* const d, const NativePluginHandle h) + : TopLevelWidget(parent), startButtonImage(ui_launcher_res::carla_uiData, ui_launcher_res::carla_uiWidth, ui_launcher_res::carla_uiHeight, - GL_BGR), + kImageFormatBGR), startButton(this, startButtonImage), descriptor(d), - handle(h) + handle(h), + pluginWindow(parent) { startButton.setCallback(this); - setSize(startButtonImage.getSize()); - parent.setSize(startButtonImage.getSize()); + pluginWindow.leaveContextAfterCreation(); + } + + ~CarlaButtonWidget() override + { + pluginWindow.enterContextForDeletion(); } protected: @@ -50,7 +99,7 @@ protected: { } - void imageButtonClicked(ImageButton* imageButton, int) override + void imageButtonClicked(OpenGLImageButton* imageButton, int) override { if (imageButton != &startButton) return; @@ -60,10 +109,11 @@ protected: } private: - Image startButtonImage; - ImageButton startButton; + OpenGLImage startButtonImage; + OpenGLImageButton startButton; const NativePluginDescriptor* const descriptor; const NativePluginHandle handle; + PluginWindow& pluginWindow; CARLA_DECLARE_NON_COPY_CLASS(CarlaButtonWidget); }; @@ -72,18 +122,20 @@ END_NAMESPACE_DGL // -------------------------------------------------------------------------------------------------------------------- +USE_NAMESPACE_DGL + struct CarlaUILauncher { - DGL_NAMESPACE::Application app; - DGL_NAMESPACE::Window window; + PluginApplication app; + PluginWindow window; CarlaButtonWidget widget; - CarlaUILauncher(const intptr_t winId, const NativePluginDescriptor* const d, const NativePluginHandle h) + CarlaUILauncher(const uintptr_t winId, const NativePluginDescriptor* const d, const NativePluginHandle h) : app(), window(app, winId), widget(window, d, h) {} }; -CarlaUILauncher* createUILauncher(const intptr_t winId, +CarlaUILauncher* createUILauncher(const uintptr_t winId, const NativePluginDescriptor* const d, const NativePluginHandle h) {