├── example ├── addons.make ├── bin │ └── data │ │ ├── testcard.png │ │ ├── ofxaddons_thumbnail.png │ │ ├── ofxaddons_thumbnail@2x.png │ │ ├── shaders │ │ └── ofxWarp │ │ │ ├── ControlPoint.frag │ │ │ ├── WarpBilinear.vert │ │ │ ├── WarpPerspective.vert │ │ │ ├── ControlPoint.vert │ │ │ ├── WarpPerspective.frag │ │ │ └── WarpBilinear.frag │ │ └── settings.json ├── imgui.ini ├── icon.rc ├── src │ ├── main.cpp │ ├── ofApp.h │ └── ofApp.cpp ├── example.sln ├── example.vcxproj.filters └── example.vcxproj ├── src ├── ofxWarp.h └── ofxWarp │ ├── WarpPerspective.h │ ├── WarpPerspectiveBilinear.h │ ├── Controller.h │ ├── WarpBilinear.h │ ├── WarpBase.h │ ├── WarpPerspectiveBilinear.cpp │ ├── WarpPerspective.cpp │ ├── Controller.cpp │ ├── WarpBase.cpp │ └── WarpBilinear.cpp ├── .appveyor.yml ├── LICENSE ├── .gitignore ├── README.md └── .travis.yml /example/addons.make: -------------------------------------------------------------------------------- 1 | ofxWarp 2 | -------------------------------------------------------------------------------- /example/bin/data/testcard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prisonerjohn/ofxWarp/HEAD/example/bin/data/testcard.png -------------------------------------------------------------------------------- /example/bin/data/ofxaddons_thumbnail.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prisonerjohn/ofxWarp/HEAD/example/bin/data/ofxaddons_thumbnail.png -------------------------------------------------------------------------------- /example/bin/data/ofxaddons_thumbnail@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prisonerjohn/ofxWarp/HEAD/example/bin/data/ofxaddons_thumbnail@2x.png -------------------------------------------------------------------------------- /example/imgui.ini: -------------------------------------------------------------------------------- 1 | [Debug] 2 | Pos=60,60 3 | Size=400,400 4 | Collapsed=0 5 | 6 | [App] 7 | Pos=10,10 8 | Size=305,465 9 | Collapsed=0 10 | 11 | [Preview] 12 | Pos=994,12 13 | Size=272,291 14 | Collapsed=0 15 | 16 | -------------------------------------------------------------------------------- /example/icon.rc: -------------------------------------------------------------------------------- 1 | // Icon Resource Definition 2 | #define MAIN_ICON 102 3 | 4 | #if defined(_DEBUG) 5 | MAIN_ICON ICON "icon_debug.ico" 6 | #else 7 | MAIN_ICON ICON "icon.ico" 8 | #endif 9 | -------------------------------------------------------------------------------- /example/src/main.cpp: -------------------------------------------------------------------------------- 1 | #include "ofMain.h" 2 | #include "ofApp.h" 3 | 4 | //======================================================================== 5 | int main() 6 | { 7 | ofGLFWWindowSettings settings; 8 | settings.setGLVersion(3, 2); 9 | settings.setSize(1280, 720); 10 | ofCreateWindow(settings); 11 | 12 | ofRunApp(new ofApp()); 13 | } 14 | -------------------------------------------------------------------------------- /example/bin/data/shaders/ofxWarp/ControlPoint.frag: -------------------------------------------------------------------------------- 1 | #version 150 2 | 3 | in vec2 vTexCoord; 4 | in vec4 vColor; 5 | 6 | out vec4 fragColor; 7 | 8 | void main(void) 9 | { 10 | vec2 uv = vTexCoord * 2.0 - 1.0; 11 | float d = dot(uv, uv); 12 | float rim = smoothstep(0.7, 0.8, d); 13 | rim += smoothstep(0.3, 0.4, d) - smoothstep(0.5, 0.6, d); 14 | rim += smoothstep(0.1, 0.0, d); 15 | fragColor = mix(vec4( 0.0, 0.0, 0.0, 0.25), vColor, rim); 16 | } 17 | -------------------------------------------------------------------------------- /example/bin/data/shaders/ofxWarp/WarpBilinear.vert: -------------------------------------------------------------------------------- 1 | #version 150 2 | 3 | // OF default uniforms and attributes 4 | uniform mat4 modelViewProjectionMatrix; 5 | uniform vec4 globalColor; 6 | 7 | in vec4 position; 8 | in vec2 texcoord; 9 | in vec4 color; 10 | 11 | // App uniforms and attributes 12 | out vec2 vTexCoord; 13 | out vec4 vColor; 14 | 15 | void main(void) 16 | { 17 | vTexCoord = texcoord; 18 | vColor = globalColor; 19 | 20 | gl_Position = modelViewProjectionMatrix * position; 21 | } 22 | -------------------------------------------------------------------------------- /example/bin/data/shaders/ofxWarp/WarpPerspective.vert: -------------------------------------------------------------------------------- 1 | #version 150 2 | 3 | // OF default uniforms and attributes 4 | uniform mat4 modelViewProjectionMatrix; 5 | uniform vec4 globalColor; 6 | 7 | in vec4 position; 8 | in vec2 texcoord; 9 | in vec4 color; 10 | 11 | // App uniforms and attributes 12 | out vec2 vTexCoord; 13 | out vec4 vColor; 14 | 15 | void main(void) 16 | { 17 | vTexCoord = texcoord; 18 | vColor = globalColor; 19 | 20 | gl_Position = modelViewProjectionMatrix * position; 21 | } 22 | -------------------------------------------------------------------------------- /src/ofxWarp.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "ofxWarp/Controller.h" 4 | #include "ofxWarp/WarpBase.h" 5 | #include "ofxWarp/WarpBilinear.h" 6 | #include "ofxWarp/WarpPerspective.h" 7 | #include "ofxWarp/WarpPerspectiveBilinear.h" 8 | 9 | typedef ofxWarp::Controller ofxWarpController; 10 | typedef ofxWarp::WarpBase ofxWarpBase; 11 | typedef ofxWarp::WarpBilinear ofxWarpBilinear; 12 | typedef ofxWarp::WarpPerspective ofxWarpPerspective; 13 | typedef ofxWarp::WarpPerspectiveBilinear ofxWarpPerspectiveBilinear; 14 | -------------------------------------------------------------------------------- /example/bin/data/shaders/ofxWarp/ControlPoint.vert: -------------------------------------------------------------------------------- 1 | #version 150 2 | 3 | // OF default uniforms and attributes 4 | uniform mat4 modelViewProjectionMatrix; 5 | uniform vec4 globalColor; 6 | 7 | in vec4 position; 8 | in vec2 texcoord; 9 | in vec4 color; 10 | 11 | // App uniforms and attributes 12 | in vec4 iPositionScale; 13 | in vec4 iColor; 14 | 15 | out vec2 vTexCoord; 16 | out vec4 vColor; 17 | 18 | void main(void) 19 | { 20 | vTexCoord = texcoord; 21 | vColor = globalColor * iColor; 22 | gl_Position = modelViewProjectionMatrix * vec4(position.xy * iPositionScale.z + iPositionScale.xy, position.zw); 23 | } 24 | -------------------------------------------------------------------------------- /example/src/ofApp.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "ofMain.h" 4 | #include "ofxWarp.h" 5 | 6 | class ofApp 7 | : public ofBaseApp 8 | { 9 | public: 10 | void setup(); 11 | void exit(); 12 | 13 | void update(); 14 | void draw(); 15 | 16 | void keyPressed(int key); 17 | void keyReleased(int key); 18 | void mouseMoved(int x, int y); 19 | void mouseDragged(int x, int y, int button); 20 | void mousePressed(int x, int y, int button); 21 | void mouseReleased(int x, int y, int button); 22 | void mouseEntered(int x, int y); 23 | void mouseExited(int x, int y); 24 | void windowResized(int w, int h); 25 | void dragEvent(ofDragInfo dragInfo); 26 | void gotMessage(ofMessage msg); 27 | 28 | bool useBeginEnd; 29 | ofxWarpController warpController; 30 | ofTexture texture; 31 | std::vector srcAreas; 32 | int areaMode; 33 | std::string areaName; 34 | }; 35 | -------------------------------------------------------------------------------- /.appveyor.yml: -------------------------------------------------------------------------------- 1 | version: 1.0.{build} 2 | image: Visual Studio 2017 3 | 4 | environment: 5 | global: 6 | APPVEYOR_OS_NAME: windows 7 | matrix: 8 | #MSYS2 Building 9 | - platform: x86 10 | BUILDER: MSYS2 11 | 12 | #VisualStudio Building 13 | - platform: x86 14 | BUILDER : VS 15 | BITS: 32 16 | - platform: x64 17 | BUILDER : VS 18 | BITS: 64 19 | 20 | configuration: Debug 21 | shallow_clone: true 22 | clone_depth: 10 23 | init: 24 | - set MSYS2_PATH=c:\msys64 25 | - set CHERE_INVOKING=1 26 | - if "%BUILDER%_%PLATFORM%"=="MSYS2_x86" set MSYSTEM=MINGW32 27 | - if "%BUILDER%_%PLATFORM%"=="MSYS2_x64" set MSYSTEM=MINGW64 28 | - if "%BUILDER%"=="VS" set PATH=C:\Program Files (x86)\MSBuild\15.0\Bin;%PATH% 29 | 30 | install: 31 | - cd .. 32 | - git clone --depth=1 --branch=master https://github.com/openframeworks/openFrameworks 33 | - call openFrameworks\scripts\ci\addons\install.cmd 34 | 35 | build_script: 36 | - cd %OF_PATH% 37 | - scripts\ci\addons\build.cmd 38 | 39 | 40 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Elie Zananiri 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /example/bin/data/shaders/ofxWarp/WarpPerspective.frag: -------------------------------------------------------------------------------- 1 | #version 150 2 | 3 | uniform sampler2D uTexture; 4 | uniform vec3 uLuminance; 5 | uniform vec3 uGamma; 6 | uniform vec4 uEdges; 7 | uniform vec4 uCorners; 8 | uniform float uExponent; 9 | 10 | in vec2 vTexCoord; 11 | in vec4 vColor; 12 | 13 | out vec4 fragColor; 14 | 15 | float map(in float value, in float inMin, in float inMax, in float outMin, in float outMax) 16 | { 17 | return outMin + (outMax - outMin) * (value - inMin) / (inMax - inMin); 18 | } 19 | 20 | void main(void) 21 | { 22 | vec4 texColor = texture(uTexture, vTexCoord); 23 | 24 | vec2 mapCoord = vec2(map(vTexCoord.x, uCorners.x, uCorners.z, 0.0, 1.0), map(vTexCoord.y, uCorners.y, uCorners.w, 0.0, 1.0)); 25 | 26 | float a = 1.0; 27 | if (uEdges.x > 0.0) a *= clamp(mapCoord.x / uEdges.x, 0.0, 1.0); 28 | if (uEdges.y > 0.0) a *= clamp(mapCoord.y / uEdges.y, 0.0, 1.0); 29 | if (uEdges.z > 0.0) a *= clamp((1.0 - mapCoord.x) / uEdges.z, 0.0, 1.0); 30 | if (uEdges.w > 0.0) a *= clamp((1.0 - mapCoord.y) / uEdges.w, 0.0, 1.0); 31 | 32 | const vec3 one = vec3(1.0); 33 | vec3 blend = (a < 0.5) ? (uLuminance * pow(2.0 * a, uExponent)) : one - (one - uLuminance) * pow(2.0 * (1.0 - a), uExponent); 34 | 35 | texColor.rgb *= pow(blend, one / uGamma); 36 | 37 | fragColor = texColor * vColor; 38 | } 39 | -------------------------------------------------------------------------------- /src/ofxWarp/WarpPerspective.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "WarpBase.h" 4 | 5 | namespace ofxWarp 6 | { 7 | class WarpPerspective 8 | : public WarpBase 9 | { 10 | public: 11 | WarpPerspective(); 12 | virtual ~WarpPerspective(); 13 | 14 | const glm::mat4 & getTransform(); 15 | const glm::mat4 & getTransformInverted(); 16 | 17 | //! reset control points to undistorted image 18 | virtual void reset(const glm::vec2 & scale = glm::vec2(1.0f), const glm::vec2 & offset = glm::vec2(0.0f)) override; 19 | //! setup the warp before drawing its contents 20 | virtual void begin() override; 21 | //! restore the warp after drawing 22 | virtual void end() override; 23 | 24 | virtual void rotateClockwise() override; 25 | virtual void rotateCounterclockwise() override; 26 | 27 | virtual void flipHorizontal() override; 28 | virtual void flipVertical() override; 29 | 30 | protected: 31 | //! draw a specific area of a warped texture to a specific region 32 | virtual void drawTexture(const ofTexture & texture, const ofRectangle & srcBounds, const ofRectangle & dstBounds) override; 33 | //! draw the warp's controls interface 34 | virtual void drawControls() override; 35 | 36 | glm::mat4 getPerspectiveTransform(const glm::vec2 src[4], const glm::vec2 dst[4]) const; 37 | void gaussianElimination(float * input, int n) const; 38 | 39 | protected: 40 | glm::vec2 srcPoints[4]; 41 | glm::vec2 dstPoints[4]; 42 | 43 | glm::mat4 transform; 44 | glm::mat4 transformInverted; 45 | 46 | ofShader shader; 47 | ofVboMesh quadMesh; 48 | }; 49 | } -------------------------------------------------------------------------------- /example/bin/data/shaders/ofxWarp/WarpBilinear.frag: -------------------------------------------------------------------------------- 1 | #version 150 2 | 3 | uniform sampler2D uTexture; 4 | uniform vec4 uExtends; 5 | uniform vec3 uLuminance; 6 | uniform vec3 uGamma; 7 | uniform vec4 uEdges; 8 | uniform vec4 uCorners; 9 | uniform float uExponent; 10 | uniform bool uEditing; 11 | 12 | in vec2 vTexCoord; 13 | in vec4 vColor; 14 | 15 | out vec4 fragColor; 16 | 17 | float map(in float value, in float inMin, in float inMax, in float outMin, in float outMax) 18 | { 19 | return outMin + (outMax - outMin) * (value - inMin) / (inMax - inMin); 20 | } 21 | 22 | float grid(in vec2 uv, in vec2 size) 23 | { 24 | vec2 coord = uv / size; 25 | vec2 grid = abs(fract(coord - 0.5) - 0.5) / (2.0 * fwidth(coord)); 26 | float line = min(grid.x, grid.y); 27 | return 1.0 - min(line, 1.0); 28 | } 29 | 30 | void main(void) 31 | { 32 | vec4 texColor = texture(uTexture, vTexCoord); 33 | 34 | vec2 mapCoord = vec2(map(vTexCoord.x, uCorners.x, uCorners.z, 0.0, 1.0), map(vTexCoord.y, uCorners.y, uCorners.w, 0.0, 1.0)); 35 | 36 | float a = 1.0; 37 | if (uEdges.x > 0.0) a *= clamp(mapCoord.x / uEdges.x, 0.0, 1.0); 38 | if (uEdges.y > 0.0) a *= clamp(mapCoord.y / uEdges.y, 0.0, 1.0); 39 | if (uEdges.z > 0.0) a *= clamp((1.0 - mapCoord.x) / uEdges.z, 0.0, 1.0); 40 | if (uEdges.w > 0.0) a *= clamp((1.0 - mapCoord.y) / uEdges.w, 0.0, 1.0); 41 | 42 | const vec3 one = vec3(1.0); 43 | vec3 blend = (a < 0.5) ? (uLuminance * pow(2.0 * a, uExponent)) : one - (one - uLuminance) * pow(2.0 * (1.0 - a), uExponent); 44 | 45 | texColor.rgb *= pow(blend, one / uGamma); 46 | 47 | if (uEditing) 48 | { 49 | float f = grid(mapCoord.xy * uExtends.xy, uExtends.zw); 50 | vec4 gridColor = vec4(1.0f); 51 | fragColor = mix(texColor * vColor, gridColor, f); 52 | } 53 | else 54 | { 55 | fragColor = texColor * vColor; 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.xcodeproj 2 | example*/bin/data 3 | example*/bin/libs/ 4 | example*/bin/* 5 | !example*/bin/data/.gitkeep 6 | !example*/bin/data/ 7 | 8 | 9 | ######################### 10 | # openFrameworks patterns 11 | ######################### 12 | 13 | # build files 14 | openFrameworks.a 15 | openFrameworksDebug.a 16 | openFrameworksUniversal.a 17 | libs/openFrameworksCompiled/lib/*/* 18 | !libs/openFrameworksCompiled/lib/*/.gitkeep 19 | 20 | # apothecary 21 | scripts/apothecary/build 22 | 23 | # rule to avoid non-official addons going into git 24 | # see addons/.gitignore 25 | addons/* 26 | 27 | # rule to avoid non-official apps going into git 28 | # see apps/.gitignore 29 | apps/* 30 | 31 | # also, see examples/.gitignore 32 | 33 | ######################### 34 | # general 35 | ######################### 36 | 37 | [Bb]uild/ 38 | [Oo]bj/ 39 | *.o 40 | [Dd]ebug*/ 41 | [Rr]elease*/ 42 | *.mode* 43 | *.app/ 44 | *.pyc 45 | .svn/ 46 | *.log 47 | *.cpp.eep 48 | *.cpp.elf 49 | *.cpp.hex 50 | 51 | ######################### 52 | # IDE 53 | ######################### 54 | 55 | # XCode 56 | *.pbxuser 57 | *.perspective 58 | *.perspectivev3 59 | *.mode1v3 60 | *.mode2v3 61 | # XCode 4 62 | xcuserdata 63 | *.xcworkspace 64 | 65 | # Code::Blocks 66 | *.depend 67 | *.layout 68 | 69 | # Visual Studio 70 | *.sdf 71 | *.opensdf 72 | *.suo 73 | *.pdb 74 | *.ilk 75 | *.aps 76 | ipch/ 77 | 78 | # Eclipse 79 | .metadata 80 | local.properties 81 | .externalToolBuilders 82 | 83 | ######################### 84 | # operating system 85 | ######################### 86 | 87 | # Linux 88 | *~ 89 | # KDE 90 | .directory 91 | .AppleDouble 92 | 93 | # OSX 94 | .DS_Store 95 | *.swp 96 | *~.nib 97 | # Thumbnails 98 | ._* 99 | 100 | # Windows 101 | # Windows image file caches 102 | Thumbs.db 103 | # Folder config file 104 | Desktop.ini 105 | 106 | # Android 107 | .csettings 108 | /libs/openFrameworksCompiled/project/android/paths.make 109 | 110 | ######################### 111 | # miscellaneous 112 | ######################### 113 | 114 | .mailmap 115 | *.opendb 116 | *.db 117 | -------------------------------------------------------------------------------- /example/example.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 14 4 | VisualStudioVersion = 14.0.24720.0 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "example", "example.vcxproj", "{7FD42DF7-442E-479A-BA76-D0022F99702A}" 7 | EndProject 8 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "openFrameworksLib", "..\..\..\libs\openFrameworksCompiled\project\vs\openFrameworksLib.vcxproj", "{5837595D-ACA9-485C-8E76-729040CE4B0B}" 9 | EndProject 10 | Global 11 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 12 | Debug|x64 = Debug|x64 13 | Debug|x86 = Debug|x86 14 | Release|x64 = Release|x64 15 | Release|x86 = Release|x86 16 | EndGlobalSection 17 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 18 | {7FD42DF7-442E-479A-BA76-D0022F99702A}.Debug|x64.ActiveCfg = Debug|x64 19 | {7FD42DF7-442E-479A-BA76-D0022F99702A}.Debug|x64.Build.0 = Debug|x64 20 | {7FD42DF7-442E-479A-BA76-D0022F99702A}.Debug|x86.ActiveCfg = Debug|Win32 21 | {7FD42DF7-442E-479A-BA76-D0022F99702A}.Debug|x86.Build.0 = Debug|Win32 22 | {7FD42DF7-442E-479A-BA76-D0022F99702A}.Release|x64.ActiveCfg = Release|x64 23 | {7FD42DF7-442E-479A-BA76-D0022F99702A}.Release|x64.Build.0 = Release|x64 24 | {7FD42DF7-442E-479A-BA76-D0022F99702A}.Release|x86.ActiveCfg = Release|Win32 25 | {7FD42DF7-442E-479A-BA76-D0022F99702A}.Release|x86.Build.0 = Release|Win32 26 | {5837595D-ACA9-485C-8E76-729040CE4B0B}.Debug|x64.ActiveCfg = Debug|x64 27 | {5837595D-ACA9-485C-8E76-729040CE4B0B}.Debug|x64.Build.0 = Debug|x64 28 | {5837595D-ACA9-485C-8E76-729040CE4B0B}.Debug|x86.ActiveCfg = Debug|Win32 29 | {5837595D-ACA9-485C-8E76-729040CE4B0B}.Debug|x86.Build.0 = Debug|Win32 30 | {5837595D-ACA9-485C-8E76-729040CE4B0B}.Release|x64.ActiveCfg = Release|x64 31 | {5837595D-ACA9-485C-8E76-729040CE4B0B}.Release|x64.Build.0 = Release|x64 32 | {5837595D-ACA9-485C-8E76-729040CE4B0B}.Release|x86.ActiveCfg = Release|Win32 33 | {5837595D-ACA9-485C-8E76-729040CE4B0B}.Release|x86.Build.0 = Release|Win32 34 | EndGlobalSection 35 | GlobalSection(SolutionProperties) = preSolution 36 | HideSolutionNode = FALSE 37 | EndGlobalSection 38 | EndGlobal 39 | -------------------------------------------------------------------------------- /src/ofxWarp/WarpPerspectiveBilinear.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "WarpBilinear.h" 4 | #include "WarpPerspective.h" 5 | 6 | namespace ofxWarp 7 | { 8 | class WarpPerspectiveBilinear 9 | : public WarpBilinear 10 | { 11 | public: 12 | WarpPerspectiveBilinear(const ofFbo::Settings & fboSettings = ofFbo::Settings()); 13 | virtual ~WarpPerspectiveBilinear(); 14 | 15 | virtual void serialize(nlohmann::json & json) override; 16 | virtual void deserialize(const nlohmann::json & json) override; 17 | 18 | virtual void setEditing(bool editing) override; 19 | 20 | virtual void setSize(float width, float height) override; 21 | 22 | //! reset control points to undistorted image 23 | virtual void reset(const glm::vec2 & scale = glm::vec2(1.0f), const glm::vec2 & offset = glm::vec2(0.0f)) override; 24 | 25 | //! return the coordinates of the specified control point 26 | virtual glm::vec2 getControlPoint(size_t index) const override; 27 | //! set the coordinates of the specified control point 28 | virtual void setControlPoint(size_t index, const glm::vec2 & pos) override; 29 | //! move the specified control point 30 | virtual void moveControlPoint(size_t index, const glm::vec2 & shift) override; 31 | //! select one of the control points 32 | virtual void selectControlPoint(size_t index) override; 33 | //! deselect the selected control point 34 | virtual void deselectControlPoint() override; 35 | 36 | virtual void rotateClockwise() override; 37 | virtual void rotateCounterclockwise() override; 38 | 39 | virtual bool handleCursorDown(const glm::vec2 & pos) override; 40 | virtual bool handleCursorDrag(const glm::vec2 & pos) override; 41 | 42 | virtual bool handleWindowResize(int width, int height) override; 43 | 44 | protected: 45 | //! draw a specific area of a warped texture to a specific region 46 | virtual void drawTexture(const ofTexture & texture, const ofRectangle & srcBounds, const ofRectangle & dstBounds) override; 47 | 48 | //! return whether or not the control point is one of the 4 corners and should be treated as a perspective control point 49 | bool isCorner(size_t index) const; 50 | //! convert the control point index to the appropriate perspective warp index 51 | size_t convertIndex(size_t index) const; 52 | 53 | protected: 54 | std::shared_ptr warpPerspective; 55 | }; 56 | } -------------------------------------------------------------------------------- /src/ofxWarp/Controller.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "ofEvents.h" 4 | #include "WarpBase.h" 5 | 6 | namespace ofxWarp 7 | { 8 | class Controller 9 | { 10 | public: 11 | Controller(); 12 | ~Controller(); 13 | 14 | //! write a settings json file 15 | bool saveSettings(const std::string & filePath); 16 | //! read a settings json file 17 | bool loadSettings(const std::string & filePath); 18 | 19 | //! serialize the list of warps to a json file 20 | void serialize(nlohmann::json & json); 21 | //! deserialize the list of warps from a json file 22 | void deserialize(const nlohmann::json & json); 23 | 24 | //! build and add a new warp of the specified type 25 | template 26 | inline std::shared_ptr buildWarp() 27 | { 28 | auto warp = std::make_shared(); 29 | this->warps.push_back(warp); 30 | 31 | return warp; 32 | } 33 | 34 | //! add the warp to the list 35 | bool addWarp(std::shared_ptr warp); 36 | //! remove the warp from the list 37 | bool removeWarp(std::shared_ptr warp); 38 | 39 | //! return the list of warps 40 | std::vector> & getWarps(); 41 | //! return the warp at the specified index 42 | std::shared_ptr getWarp(size_t index) const; 43 | //! return the number of warps 44 | size_t getNumWarps() const; 45 | 46 | //! handle mouseMoved events for multiple warps 47 | void onMouseMoved(ofMouseEventArgs & args); 48 | //! handle mousePressed events for multiple warps 49 | void onMousePressed(ofMouseEventArgs & args); 50 | //! handle mouseDragged events for multiple warps 51 | void onMouseDragged(ofMouseEventArgs & args); 52 | //! handle mouseReleased events for multiple warps 53 | void onMouseReleased(ofMouseEventArgs & args); 54 | 55 | //! handle keyPressed events for multiple warps 56 | void onKeyPressed(ofKeyEventArgs & args); 57 | //! handle keyReleased events for multiple warps 58 | void onKeyReleased(ofKeyEventArgs & args); 59 | 60 | //! handle windowResized events for multiple warps 61 | void onWindowResized(ofResizeEventArgs & args); 62 | 63 | protected: 64 | //! check all warps and select the closest control point 65 | void selectClosestControlPoint(const glm::vec2 & pos); 66 | 67 | protected: 68 | std::vector> warps; 69 | 70 | size_t focusedIndex; 71 | }; 72 | } 73 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## ofxWarp 2 | An openFrameworks port of the [Cinder-Warping](https://github.com/paulhoux/Cinder-Warping) block by @paulhoux. 3 | Enables you to easily create editable bi-linear and perspective warps, or a combination of the two. 4 | 5 | [![Build status](https://travis-ci.org/prisonerjohn/ofxWarp.svg?branch=master)](https://travis-ci.org/prisonerjohn/ofxWarp) 6 | [![Build status](https://ci.appveyor.com/api/projects/status/2f3a7456lo33gt9c/branch/master?svg=true)](https://ci.appveyor.com/project/prisonerjohn/ofxwarp/branch/master) 7 | 8 | #### From the [Cinder-Warping](https://github.com/paulhoux/Cinder-Warping) README: 9 | 10 | Image warping, as used in this addon, is the process of manipulating an image so that it can be projected onto flat or curved surfaces without distortion. There are three types of warps available: 11 | * **Perspective warp**: performs image correction for projecting onto flat surfaces in case the projector is not horizontally and/or vertically aligned with the wall. Use it to exactly project your content on a rectangular area on a wall or building, regardless of how your projector is setup. For best results, disable any keystone correction on the projector. Then simply drag the four corners of your content where you want them to be on the wall. 12 | * **Bilinear warp**: inferior to perspective warping on flat surfaces, but allows projecting onto curved surfaces. Simply add control points to the warp and drag them to where you want your content to be. 13 | * **Perspective-bilinear warp**: a combination of both techniques, where you first drag the four corners of your content to the desired location on the wall, then adjust for curvatures using the additional control points. If you (accidentally) move your projector later, all you need to do is adjust the four corners and the projection should perfectly fit on the curved wall again. 14 | 15 | #### Installation 16 | 17 | * Drop the addon folder into your `openFrameworks/addons` directory, and add to project as you would any other addon. 18 | * Copy the shaders found in the example to your project's `bin/data/shaders/ofxWarp` folder. 19 | 20 | #### Compatibility 21 | 22 | * openFrameworks 0.9 and up 23 | * OpenGL 3 and up (programmable pipeline) 24 | * The included shaders only work with normalized textures (`GL_TEXTURE_2D`) but can be easily modified to work with rectangle textures 25 | 26 | #### Controls 27 | You can use `ofxWarp::Controller` to adjust your warps: 28 | * `w` to toggle editing on all warps 29 | * Use mouse or cursor keys to move the currently selected control point 30 | * `TAB` to select the next control point 31 | * `-` or `+` to change brightness 32 | * `r` to reset the warp to its default settings 33 | * `F11` to flip content horizontally 34 | * `F12` to flip content vertically 35 | 36 | For Perspective warps only: 37 | * `F9` to rotate content counter-clockwise 38 | * `F10` to rotate content clockwise 39 | 40 | For Bilinear warps only: 41 | * `m` to toggle between linear and curved mapping 42 | * `F1` to reduce the number of horizontal control points 43 | * `F2` to increase the number of horizontal control points 44 | * `F3` to reduce the number of vertical control points 45 | * `F4` to increase the number of vertical control points 46 | * `F5` to decrease the mesh resolution 47 | * `F6` to increase the mesh resolution 48 | * `F7` to toggle adaptive mesh resolution 49 | -------------------------------------------------------------------------------- /example/example.vcxproj.filters: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | src 6 | 7 | 8 | src 9 | 10 | 11 | addons\ofxWarp\src\ofxWarp 12 | 13 | 14 | addons\ofxWarp\src\ofxWarp 15 | 16 | 17 | addons\ofxWarp\src\ofxWarp 18 | 19 | 20 | addons\ofxWarp\src\ofxWarp 21 | 22 | 23 | addons\ofxWarp\src\ofxWarp 24 | 25 | 26 | 27 | 28 | {d8376475-7454-4a24-b08a-aac121d3ad6f} 29 | 30 | 31 | {069f5eeb-250d-441f-ba6b-d63ccf99dc12} 32 | 33 | 34 | {83bcac6d-2f9c-4a89-83eb-ceac2443e7f2} 35 | 36 | 37 | {59c146a4-bf8d-4008-b606-008b94975639} 38 | 39 | 40 | {7aff29ff-cd85-4646-8ec2-b7c71b319a63} 41 | 42 | 43 | {c49ccda4-d910-4c7e-adc8-c264baf176a8} 44 | 45 | 46 | {773c4a4d-2c4f-4ffb-a9f5-686adc3044c7} 47 | 48 | 49 | 50 | 51 | src 52 | 53 | 54 | addons\ofxWarp\src 55 | 56 | 57 | addons\ofxWarp\src\ofxWarp 58 | 59 | 60 | addons\ofxWarp\src\ofxWarp 61 | 62 | 63 | addons\ofxWarp\src\ofxWarp 64 | 65 | 66 | addons\ofxWarp\src\ofxWarp 67 | 68 | 69 | addons\ofxWarp\src\ofxWarp 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | shaders\ofxWarp 78 | 79 | 80 | shaders\ofxWarp 81 | 82 | 83 | shaders\ofxWarp 84 | 85 | 86 | shaders\ofxWarp 87 | 88 | 89 | shaders\ofxWarp 90 | 91 | 92 | shaders\ofxWarp 93 | 94 | 95 | -------------------------------------------------------------------------------- /src/ofxWarp/WarpBilinear.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "ofFbo.h" 4 | #include "ofVbo.h" 5 | 6 | #include "WarpBase.h" 7 | 8 | namespace ofxWarp 9 | { 10 | class WarpBilinear 11 | : public WarpBase 12 | { 13 | public: 14 | WarpBilinear(const ofFbo::Settings & fboSettings = ofFbo::Settings()); 15 | virtual ~WarpBilinear(); 16 | 17 | virtual void serialize(nlohmann::json & json) override; 18 | virtual void deserialize(const nlohmann::json & json) override; 19 | 20 | virtual void setSize(float width, float height) override; 21 | 22 | void setFboSettings(const ofFbo::Settings & fboSettings); 23 | 24 | //! set whether the mesh is linear (or curved) 25 | void setLinear(bool linear); 26 | //! return whether the mesh is linear (or curved) 27 | bool getLinear() const; 28 | 29 | //! set whether the mesh resolution is adaptive to the window size 30 | void setAdaptive(bool adaptive); 31 | //! return whether the mesh resolution is adaptive to the window size 32 | bool getAdaptive() const; 33 | 34 | //! increase the mesh resolution 35 | void increaseResolution(); 36 | //! decrease the mesh resolution 37 | void decreaseResolution(); 38 | //! return the mesh resolution 39 | int getResolution() const; 40 | 41 | //! reset control points to undistorted image 42 | virtual void reset(const glm::vec2 & scale = glm::vec2(1.0f), const glm::vec2 & offset = glm::vec2(0.0f)) override; 43 | //! setup the warp before drawing its contents 44 | virtual void begin() override; 45 | //! restore the warp after drawing 46 | virtual void end() override; 47 | 48 | //! set the number of horizontal control points for this warp 49 | void setNumControlsX(int n); 50 | //! set the number of vertical control points for this warp 51 | void setNumControlsY(int n); 52 | 53 | void setCorners(float left, float top, float right, float bottom); 54 | 55 | virtual void rotateClockwise() override; 56 | virtual void rotateCounterclockwise() override; 57 | 58 | virtual void flipHorizontal() override; 59 | virtual void flipVertical() override; 60 | 61 | protected: 62 | //! draw a specific area of a warped texture to a specific region 63 | virtual void drawTexture(const ofTexture & texture, const ofRectangle & srcBounds, const ofRectangle & dstBounds) override; 64 | //! draw the warp's controls interface 65 | virtual void drawControls() override; 66 | 67 | //! set up the frame buffer 68 | void setupFbo(); 69 | //! set up the shader and vertex buffer 70 | void setupVbo(); 71 | //! set up the vbo mesh 72 | void setupMesh(int resolutionX = 36, int resolutionY = 36); 73 | //! update the vbo mesh based on the control points 74 | void updateMesh(); 75 | //! return the specified control point, values for col and row are clamped to prevent errors. 76 | glm::vec2 getPoint(int col, int row) const; 77 | //! perform fast Catmull-Rom interpolation, and return the interpolated value at t 78 | glm::vec2 cubicInterpolate(const std::vector & knots, float t) const; 79 | //! 80 | ofRectangle getMeshBounds() const; 81 | 82 | protected: 83 | ofFbo fbo; 84 | ofFbo::Settings fboSettings; 85 | ofVbo vbo; 86 | ofShader shader; 87 | 88 | //! linear or curved interpolation 89 | bool linear; 90 | 91 | bool adaptive; 92 | 93 | //! texture coordinates of corners 94 | glm::vec4 corners; 95 | 96 | //! detail of the generated mesh (multiples of 5 seem to work best) 97 | int resolution; 98 | 99 | //! number of horizontal quads 100 | int resolutionX; 101 | //! number of vertical quads 102 | int resolutionY; 103 | 104 | private: 105 | //! greatest common divisor using Euclidian algorithm (from: http://en.wikipedia.org/wiki/Greatest_common_divisor) 106 | inline int gcd(int a, int b) const 107 | { 108 | if (b == 0) 109 | { 110 | return a; 111 | } 112 | else 113 | { 114 | return gcd(b, a % b); 115 | } 116 | }; 117 | }; 118 | } 119 | -------------------------------------------------------------------------------- /example/bin/data/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "warps": [ 3 | { 4 | "blend": { 5 | "edges": "0, 0, 0.5, 0.5", 6 | "exponent": 2.0, 7 | "gamma": "1, 1, 1", 8 | "luminance": "0.5, 0.5, 0.5" 9 | }, 10 | "brightness": 1.0, 11 | "type": 2, 12 | "warp": { 13 | "columns": 2, 14 | "control points": [ 15 | "0.0890625, 0.0597222", 16 | "0.50625, 0.0402778", 17 | "0.611719, 0.486111", 18 | "0.0351563, 0.588889" 19 | ], 20 | "rows": 2 21 | } 22 | }, 23 | { 24 | "adaptive": true, 25 | "blend": { 26 | "edges": "0.5, 0, 0, 0.5", 27 | "exponent": 2.0, 28 | "gamma": "1, 1, 1", 29 | "luminance": "0.5, 0.5, 0.5" 30 | }, 31 | "brightness": 1.0, 32 | "linear": false, 33 | "resolution": 16, 34 | "type": 1, 35 | "warp": { 36 | "columns": 5, 37 | "control points": [ 38 | "0.48125, 0.0402778", 39 | "0.571875, 0.491667", 40 | "0.573438, 0.0392361", 41 | "0.671484, 0.480903", 42 | "0.717188, 0.129861", 43 | "0.757812, 0.497917", 44 | "0.824219, 0.0482639", 45 | "0.840234, 0.410764", 46 | "0.980469, 0.0527778", 47 | "0.929688, 0.459722" 48 | ], 49 | "rows": 2 50 | } 51 | }, 52 | { 53 | "adaptive": true, 54 | "blend": { 55 | "edges": "0.5, 0.5, 0, 0.5", 56 | "exponent": 2.0, 57 | "gamma": "1, 1, 1", 58 | "luminance": "0.5, 0.5, 0.5" 59 | }, 60 | "brightness": 1.0, 61 | "corners": [ 62 | "0.571095, 0.493055", 63 | "0.930469, 0.458333", 64 | "0.898438, 0.870833", 65 | "0.561719, 0.919444" 66 | ], 67 | "linear": false, 68 | "resolution": 16, 69 | "type": 3, 70 | "warp": { 71 | "columns": 5, 72 | "control points": [ 73 | "0, 0", 74 | "0.0630292, 0.500232", 75 | "0, 1", 76 | "0.259811, -0.0268803", 77 | "0.343242, 0.536344", 78 | "0.259619, 1.09856", 79 | "0.500002, 0.00670155", 80 | "0.577167, 0.598489", 81 | "0.513955, 1.19234", 82 | "0.732039, -0.136884", 83 | "0.82365, 0.508819", 84 | "0.781408, 1.15969", 85 | "1, 0", 86 | "1.08493, 0.500191", 87 | "1, 1" 88 | ], 89 | "rows": 3 90 | } 91 | }, 92 | { 93 | "adaptive": true, 94 | "blend": { 95 | "edges": "0, 0.5, 0.5, 0", 96 | "exponent": 2.0, 97 | "gamma": "1, 1, 1", 98 | "luminance": "0.5, 0.5, 0.5" 99 | }, 100 | "brightness": 1.0, 101 | "corners": [ 102 | "0.0351563, 0.580556", 103 | "0.572656, 0.490278", 104 | "0.567969, 0.920833", 105 | "0.0382813, 0.911111" 106 | ], 107 | "linear": false, 108 | "resolution": 16, 109 | "type": 3, 110 | "warp": { 111 | "columns": 2, 112 | "control points": [ 113 | "0, 0", 114 | "0.0784798, 0.507221", 115 | "0, 1", 116 | "1, 0", 117 | "1.03334, 0.507208", 118 | "1, 1" 119 | ], 120 | "rows": 3 121 | } 122 | } 123 | ] 124 | } -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | # This file allows testing your addon using travis CI servers to use it you'll need to 2 | # create an account in travis.org and enable your addon there. 3 | # 4 | # By default it will test linux 64bit and osx against the master and stable OF branches. 5 | # Other platforms can be enabled by uncommenting the corresponding sections. 6 | # 7 | # If any extra install is needed to use the addon it can be included in the corresponding 8 | # install script in: 9 | # 10 | # scripts/ci/$TARGET/install.sh 11 | # 12 | 13 | 14 | language: c++ 15 | compiler: gcc 16 | sudo: true 17 | matrix: 18 | include: 19 | # fully specify builds, include can't dynamically expand matrix entries 20 | # relative order of sudo and env is important so that addons: is recognized 21 | 22 | # Linux 64bit, OF master 23 | - os: linux 24 | dist: trusty 25 | sudo: required 26 | env: TARGET="linux64" OF_BRANCH="master" 27 | addons: 28 | apt: 29 | sources: 30 | - ubuntu-toolchain-r-test 31 | packages: 32 | - gcc-4.9 33 | - g++-4.9 34 | - gdb 35 | 36 | # Linux 64bit, OF stable: Not supported yet 37 | # - os: linux 38 | # dist: trusty 39 | # sudo: required 40 | # env: TARGET="linux64" OF_BRANCH="stable" 41 | # addons: 42 | # apt: 43 | # sources: 44 | # - ubuntu-toolchain-r-test 45 | # packages: 46 | # - gcc-4.9 47 | # - g++-4.9 48 | # - gdb 49 | 50 | # OSX, OF master 51 | - os: osx 52 | osx_image: xcode8 53 | compiler: clang 54 | env: TARGET="osx" OF_BRANCH="master" 55 | 56 | # OSX, OF stable: Not supported yet 57 | # - os: osx 58 | # osx_image: xcode8 59 | # compiler: clang 60 | # env: TARGET="osx" OF_BRANCH="stable" 61 | 62 | # Linux ARM6, OF master: Uncomment following lines to enable 63 | # - os: linux 64 | # sudo: required 65 | # dist: trusty 66 | # env: TARGET="linuxarmv6l" OF_BRANCH="master" 67 | 68 | 69 | # Linux ARM6, OF stable: Not supported yet 70 | # - os: linux 71 | # sudo: required 72 | # dist: trusty 73 | # env: TARGET="linuxarmv6l" OF_BRANCH="stable" 74 | 75 | # Linux ARM7, OF master: Uncomment following lines to enable 76 | # - os: linux 77 | # sudo: false 78 | # env: TARGET="linuxarmv7l" OF_BRANCH="master" 79 | # cache: 80 | # directories: 81 | # - ~/rpi2_toolchain 82 | # - ~/firmware-master 83 | # - ~/archlinux 84 | 85 | # Linux ARM7, OF stable: Not supported yet 86 | # - os: linux 87 | # sudo: false 88 | # env: TARGET="linuxarmv7l" OF_BRANCH="stable" 89 | # cache: 90 | # directories: 91 | # - ~/rpi2_toolchain 92 | # - ~/firmware-master 93 | # - ~/archlinux 94 | 95 | 96 | # Emscripten, OF master: Uncomment following lines to enable 97 | # - os: linux 98 | # sudo: false 99 | # env: TARGET="emscripten" OF_BRANCH="master" 100 | # addons: 101 | # apt: 102 | # sources: 103 | # - ubuntu-toolchain-r-test 104 | # packages: 105 | # - libstdc++6 106 | 107 | 108 | # Emscripten, OF stable: Not supported yet 109 | # - os: linux 110 | # sudo: false 111 | # env: TARGET="emscripten" OF_BRANCH="stable" 112 | # addons: 113 | # apt: 114 | # sources: 115 | # - ubuntu-toolchain-r-test 116 | # packages: 117 | # - libstdc++6 118 | 119 | 120 | # iOS, OF master: Not supported yet 121 | # - os: osx 122 | # osx_image: xcode8 123 | # compiler: clang 124 | # env: TARGET="ios" OF_BRANCH="master" 125 | 126 | 127 | # iOS, OF stable: Not supported yet 128 | # - os: osx 129 | # osx_image: xcode8 130 | # compiler: clang 131 | # env: TARGET="ios" OF_BRANCH="stable" 132 | 133 | 134 | # tvOS, OF master: Not supported yet 135 | # - os: osx 136 | # osx_image: xcode8 137 | # compiler: clang 138 | # env: TARGET="tvos" OF_BRANCH="master" 139 | 140 | 141 | # tvOS, OF stable: Not supported yet 142 | # - os: osx 143 | # osx_image: xcode8 144 | # compiler: clang 145 | # env: TARGET="tvos" OF_BRANCH="stable" 146 | 147 | 148 | # Android armv7, OF master: Uncomment following lines to enable 149 | # - os: linux 150 | # sudo: false 151 | # env: TARGET="android" OPT="armv7" OF_BRANCH="master" 152 | # cache: 153 | # directories: 154 | # - ~/android-ndk-r12b 155 | 156 | 157 | # Android armv7, OF stable: Not supported yet 158 | # - os: linux 159 | # sudo: false 160 | # env: TARGET="android" OPT="armv7" OF_BRANCH="stable" 161 | # cache: 162 | # directories: 163 | # - ~/android-ndk-r12b 164 | 165 | 166 | # Android x86, OF master: Uncomment following lines to enable 167 | # - os: linux 168 | # sudo: false 169 | # env: TARGET="android" OPT="x86" OF_BRANCH="master" 170 | # cache: 171 | # directories: 172 | # - ~/android-ndk-r12b 173 | 174 | 175 | # Android x86, OF stable: Not supported yet 176 | # - os: linux 177 | # sudo: false 178 | # env: TARGET="android" OPT="x86" OF_BRANCH="stable" 179 | # cache: 180 | # directories: 181 | # - ~/android-ndk-r12b 182 | 183 | 184 | # Exclude the default build that would otherwise be generated 185 | # see https://github.com/travis-ci/travis-ci/issues/1228 186 | exclude: 187 | - compiler: gcc 188 | 189 | install: 190 | - cd ~ 191 | - git clone --depth=1 --branch=$OF_BRANCH https://github.com/openframeworks/openFrameworks 192 | - cd openFrameworks 193 | - scripts/ci/addons/install.sh 194 | 195 | script: 196 | - scripts/ci/addons/build.sh 197 | 198 | git: 199 | depth: 10 200 | -------------------------------------------------------------------------------- /example/src/ofApp.cpp: -------------------------------------------------------------------------------- 1 | #include "ofApp.h" 2 | 3 | //-------------------------------------------------------------- 4 | void ofApp::setup() 5 | { 6 | ofSetLogLevel(OF_LOG_NOTICE); 7 | ofDisableArbTex(); 8 | ofBackground(ofColor::black); 9 | 10 | ofImage image; 11 | image.setUseTexture(false); 12 | if (!image.load("testcard.png")) 13 | { 14 | ofLogError("ofApp::setup") << "Could not load image!"; 15 | return; 16 | } 17 | 18 | this->texture.enableMipmap(); 19 | this->texture.loadData(image.getPixels()); 20 | 21 | // Load warp settings from file if one exists. 22 | this->warpController.loadSettings("settings.json"); 23 | if (this->warpController.getWarps().empty()) 24 | { 25 | // Otherwise create warps from scratch. 26 | std::shared_ptr warp; 27 | 28 | warp = this->warpController.buildWarp(); 29 | warp->setSize(this->texture.getWidth(), this->texture.getHeight()); 30 | warp->setEdges(glm::vec4(0.0f, 0.0f, 1.0f, 1.0f)); 31 | 32 | warp = this->warpController.buildWarp(); 33 | warp->setSize(this->texture.getWidth(), this->texture.getHeight()); 34 | warp->setEdges(glm::vec4(1.0f, 0.0f, 0.0f, 1.0f)); 35 | 36 | warp = this->warpController.buildWarp(); 37 | warp->setSize(this->texture.getWidth(), this->texture.getHeight()); 38 | warp->setEdges(glm::vec4(1.0f, 1.0f, 0.0f, 0.0f)); 39 | 40 | warp = this->warpController.buildWarp(); 41 | warp->setSize(this->texture.getWidth(), this->texture.getHeight()); 42 | warp->setEdges(glm::vec4(0.0f, 1.0f, 1.0f, 0.0f)); 43 | } 44 | 45 | this->srcAreas.resize(this->warpController.getNumWarps()); 46 | 47 | // Start with full area mode. 48 | this->areaMode = -1; 49 | this->keyPressed('a'); 50 | 51 | this->useBeginEnd = false; 52 | } 53 | 54 | //-------------------------------------------------------------- 55 | void ofApp::exit() 56 | { 57 | this->warpController.saveSettings("settings.json"); 58 | } 59 | 60 | //-------------------------------------------------------------- 61 | void ofApp::update() 62 | { 63 | ofSetWindowTitle(ofToString(ofGetFrameRate(), 2) + " FPS :: " + areaName + " :: " + (this->useBeginEnd ? "begin()/end()" : "draw()")); 64 | } 65 | 66 | //-------------------------------------------------------------- 67 | void ofApp::draw() 68 | { 69 | ofBackground(ofColor::black); 70 | 71 | if (this->texture.isAllocated()) 72 | { 73 | for (auto i = 0; i < this->warpController.getNumWarps(); ++i) 74 | { 75 | auto warp = this->warpController.getWarp(i); 76 | if (this->useBeginEnd) 77 | { 78 | warp->begin(); 79 | { 80 | auto bounds = warp->getBounds(); 81 | this->texture.drawSubsection(bounds.x, bounds.y, bounds.width, bounds.height, this->srcAreas[i].x, this->srcAreas[i].y, this->srcAreas[i].width, this->srcAreas[i].height); 82 | } 83 | warp->end(); 84 | } 85 | else 86 | { 87 | warp->draw(this->texture, this->srcAreas[i]); 88 | } 89 | } 90 | } 91 | 92 | std::ostringstream oss; 93 | oss << ofToString(ofGetFrameRate(), 2) << " fps" << endl; 94 | oss << "[a]rea mode: " << areaName << endl; 95 | oss << "[d]raw mode: " << (this->useBeginEnd ? "begin()/end()" : "draw()") << endl; 96 | oss << "[w]arp edit: " << (this->warpController.getWarp(0)->isEditing() ? "on" : "off"); 97 | ofSetColor(ofColor::white); 98 | ofDrawBitmapStringHighlight(oss.str(), 10, 20); 99 | } 100 | 101 | //-------------------------------------------------------------- 102 | void ofApp::keyPressed(int key) 103 | { 104 | if (key == 'f') 105 | { 106 | ofToggleFullscreen(); 107 | } 108 | else if (key == 'a') 109 | { 110 | this->areaMode = (this->areaMode + 1) % 3; 111 | if (this->areaMode == 0) 112 | { 113 | // Draw the full image for each warp. 114 | auto area = ofRectangle(0, 0, this->texture.getWidth(), this->texture.getHeight()); 115 | for (auto i = 0; i < this->warpController.getNumWarps(); ++i) 116 | { 117 | this->srcAreas[i] = area; 118 | } 119 | 120 | this->areaName = "full"; 121 | } 122 | else if (this->areaMode == 1) 123 | { 124 | // Draw a corner region of the image so that all warps make up the entire image. 125 | for (auto i = 0; i < this->warpController.getNumWarps(); ++i) 126 | { 127 | static const auto overlap = 10.0f; 128 | if (i == 0) 129 | { 130 | // Top-left. 131 | this->srcAreas[i] = ofRectangle(0, 0, this->texture.getWidth() * 0.5f + overlap, this->texture.getHeight() * 0.5f + overlap); 132 | } 133 | else if (i == 1) 134 | { 135 | // Top-right. 136 | this->srcAreas[i] = ofRectangle(this->texture.getWidth() * 0.5f - overlap, 0, this->texture.getWidth() * 0.5f + overlap, this->texture.getHeight() * 0.5f + overlap); 137 | } 138 | else if (i == 2) 139 | { 140 | // Bottom-right. 141 | this->srcAreas[i] = ofRectangle(this->texture.getWidth() * 0.5f - overlap, this->texture.getHeight() * 0.5f - overlap, this->texture.getWidth() * 0.5f + overlap, this->texture.getHeight() * 0.5f + overlap); 142 | } 143 | else 144 | { 145 | // Bottom-left. 146 | this->srcAreas[i] = ofRectangle(0, this->texture.getHeight() * 0.5f - overlap, this->texture.getWidth() * 0.5f + overlap, this->texture.getHeight() * 0.5f + overlap); 147 | } 148 | } 149 | 150 | this->areaName = "corners"; 151 | } 152 | else 153 | { 154 | // Draw a random region of the image for each warp. 155 | auto x1 = ofRandom(0, this->texture.getWidth() - 150); 156 | auto y1 = ofRandom(0, this->texture.getHeight() - 150); 157 | auto x2 = ofRandom(x1 + 150, this->texture.getWidth()); 158 | auto y2 = ofRandom(y1 + 150, this->texture.getHeight()); 159 | auto area = ofRectangle(x1, y1, x2 - x1, y2 - y1); 160 | for (auto i = 0; i < this->warpController.getNumWarps(); ++i) 161 | { 162 | this->srcAreas[i] = area; 163 | } 164 | 165 | this->areaName = "random"; 166 | } 167 | } 168 | else if (key == 'd') 169 | { 170 | this->useBeginEnd ^= 1; 171 | } 172 | } 173 | 174 | //-------------------------------------------------------------- 175 | void ofApp::keyReleased(int key){ 176 | 177 | } 178 | 179 | //-------------------------------------------------------------- 180 | void ofApp::mouseMoved(int x, int y){ 181 | 182 | } 183 | 184 | //-------------------------------------------------------------- 185 | void ofApp::mouseDragged(int x, int y, int button){ 186 | 187 | } 188 | 189 | //-------------------------------------------------------------- 190 | void ofApp::mousePressed(int x, int y, int button){ 191 | 192 | } 193 | 194 | //-------------------------------------------------------------- 195 | void ofApp::mouseReleased(int x, int y, int button){ 196 | 197 | } 198 | 199 | //-------------------------------------------------------------- 200 | void ofApp::mouseEntered(int x, int y){ 201 | 202 | } 203 | 204 | //-------------------------------------------------------------- 205 | void ofApp::mouseExited(int x, int y){ 206 | 207 | } 208 | 209 | //-------------------------------------------------------------- 210 | void ofApp::windowResized(int w, int h){ 211 | 212 | } 213 | 214 | //-------------------------------------------------------------- 215 | void ofApp::gotMessage(ofMessage msg){ 216 | 217 | } 218 | 219 | //-------------------------------------------------------------- 220 | void ofApp::dragEvent(ofDragInfo dragInfo){ 221 | 222 | } 223 | -------------------------------------------------------------------------------- /src/ofxWarp/WarpBase.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "ofColor.h" 4 | #include "ofJson.h" 5 | #include "ofParameter.h" 6 | #include "ofRectangle.h" 7 | #include "ofShader.h" 8 | #include "ofTexture.h" 9 | #include "ofVboMesh.h" 10 | #include "ofVectorMath.h" 11 | 12 | namespace ofxWarp 13 | { 14 | class WarpBase 15 | { 16 | public: 17 | typedef enum 18 | { 19 | TYPE_UNKNOWN, 20 | TYPE_BILINEAR, 21 | TYPE_PERSPECTIVE, 22 | TYPE_PERSPECTIVE_BILINEAR 23 | } Type; 24 | 25 | WarpBase(Type type = TYPE_UNKNOWN); 26 | virtual ~WarpBase(); 27 | 28 | //! returns the type of the warp 29 | Type getType() const; 30 | 31 | virtual void serialize(nlohmann::json & json); 32 | virtual void deserialize(const nlohmann::json & json); 33 | 34 | virtual void setEditing(bool editing); 35 | void toggleEditing(); 36 | bool isEditing() const; 37 | 38 | //! set the width of the content in pixels 39 | virtual void setWidth(float width); 40 | //! get the width of the content in pixels 41 | float getWidth() const; 42 | 43 | //! set the height of the content in pixels 44 | virtual void setHeight(float height); 45 | //! get the height of the content in pixels 46 | float getHeight() const; 47 | 48 | //! set the width and height of the content in pixels 49 | virtual void setSize(float width, float height); 50 | //! set the width and height of the content in pixels 51 | virtual void setSize(const glm::vec2 & size); 52 | //! get the width and height of the content in pixels 53 | glm::vec2 getSize() const; 54 | //! get the rectangle of the content in pixels 55 | ofRectangle getBounds() const; 56 | 57 | //! set the brightness value of the texture (values between 0 and 1) 58 | void setBrightness(float brightness); 59 | //! return the brightness value of the texture (values between 0 and 1) 60 | float getBrightness() const; 61 | 62 | //! set the luminance value for all color channels, used for edge blending (0.5 = linear) 63 | void setLuminance(float luminance); 64 | //! set the luminance value for the red, green and blue channels, used for edge blending (0.5 = linear) 65 | void setLuminance(float red, float green, float blue); 66 | //! set the luminance value for the red, green and blue channels, used for edge blending (0.5 = linear) 67 | void setLuminance(const glm::vec3 & rgb); 68 | //! returns the luminance value for the red, green and blue channels, used for edge blending (0.5 = linear) 69 | const glm::vec3 & getLuminance() const; 70 | 71 | //! set the gamma curve value for all color channels 72 | void setGamma(float gamma); 73 | //! set the gamma curve value for the red, green and blue channels 74 | void setGamma(float red, float green, float blue); 75 | //! set the gamma curve value for the red, green and blue channels 76 | void setGamma(const glm::vec3 & rgb); 77 | //! return the gamma curve value for the red, green and blue channels 78 | const glm::vec3 & getGamma() const; 79 | 80 | //! set the edge blending curve exponent (1.0 = linear, 2.0 = quadratic) 81 | void setExponent(float exponent); 82 | //! return the edge blending curve exponent (1.0 = linear, 2.0 = quadratic) 83 | float getExponent() const; 84 | 85 | //! set the edge blending area for the left, top, right and bottom edges (values between 0 and 1) 86 | void setEdges(float left, float top, float right, float bottom); 87 | //! set the edge blending area for the left, top, right and bottom edges (values between 0 and 1) 88 | void setEdges(const glm::vec4 & edges); 89 | //! return the edge blending area for the left, top, right and bottom edges (values between 0 and 1) 90 | glm::vec4 getEdges() const; 91 | 92 | //! reset control points to undistorted image 93 | virtual void reset(const glm::vec2 & scale = glm::vec2(1.0f), const glm::vec2 & offset = glm::vec2(0.0f)) = 0; 94 | //! setup the warp before drawing its contents 95 | virtual void begin() = 0; 96 | //! restore the warp after drawing 97 | virtual void end() = 0; 98 | 99 | //! draw a warped texture 100 | void draw(const ofTexture & texture); 101 | //! draw a specific area of a warped texture 102 | void draw(const ofTexture & texture, const ofRectangle & srcBounds); 103 | //! draw a specific area of a warped texture to a specific region 104 | void draw(const ofTexture & texture, const ofRectangle & srcBounds, const ofRectangle & dstBounds); 105 | 106 | //! adjust both the source and destination rectangles so that they are clipped against the warp's content 107 | bool clip(ofRectangle & srcBounds, ofRectangle & dstBounds) const; 108 | 109 | //! return the coordinates of the specified control point 110 | virtual glm::vec2 getControlPoint(size_t index) const; 111 | //! set the coordinates of the specified control point 112 | virtual void setControlPoint(size_t index, const glm::vec2 & pos); 113 | //! move the specified control point 114 | virtual void moveControlPoint(size_t index, const glm::vec2 & shift); 115 | //! get the number of control points 116 | virtual size_t getNumControlPoints() const; 117 | //! get the index of the currently selected control point 118 | virtual size_t getSelectedControlPoint() const; 119 | //! select one of the control points 120 | virtual void selectControlPoint(size_t index); 121 | //! deselect the selected control point 122 | virtual void deselectControlPoint(); 123 | //! return the index of the closest control point, as well as the distance in pixels 124 | virtual size_t findClosestControlPoint(const glm::vec2 & pos, float * distance) const; 125 | 126 | //! return the number of control points columns 127 | size_t getNumControlsX() const; 128 | //! return the number of control points rows 129 | size_t getNumControlsY() const; 130 | 131 | virtual void rotateClockwise() = 0; 132 | virtual void rotateCounterclockwise() = 0; 133 | 134 | virtual void flipHorizontal() = 0; 135 | virtual void flipVertical() = 0; 136 | 137 | virtual bool handleCursorDown(const glm::vec2 & pos); 138 | virtual bool handleCursorDrag(const glm::vec2 & pos); 139 | 140 | virtual bool handleWindowResize(int width, int height); 141 | 142 | static void setShaderPath(const std::filesystem::path shaderPath); 143 | 144 | protected: 145 | //! draw a specific area of a warped texture to a specific region 146 | virtual void drawTexture(const ofTexture & texture, const ofRectangle & srcBounds, const ofRectangle & dstBounds) = 0; 147 | //! draw the warp's controls interface 148 | virtual void drawControls() = 0; 149 | 150 | //! draw a control point in the preset color 151 | void queueControlPoint(const glm::vec2 & pos, bool selected = false, bool attached = false); 152 | //! draw a control point in the specified color 153 | void queueControlPoint(const glm::vec2 & pos, const ofFloatColor & color, float scale = 1.0f); 154 | 155 | //! setup the control points instanced vbo 156 | void setupControlPoints(); 157 | //! draw the control points 158 | void drawControlPoints(); 159 | 160 | protected: 161 | Type type; 162 | 163 | bool editing; 164 | bool dirty; 165 | 166 | float width; 167 | float height; 168 | 169 | glm::vec2 windowSize; 170 | 171 | float brightness; 172 | 173 | size_t numControlsX; 174 | size_t numControlsY; 175 | std::vector controlPoints; 176 | 177 | size_t selectedIndex; 178 | float selectedTime; 179 | glm::vec2 selectedOffset; 180 | 181 | glm::vec3 luminance; 182 | glm::vec3 gamma; 183 | float exponent; 184 | glm::vec4 edges; 185 | 186 | static const int MAX_NUM_CONTROL_POINTS = 1024; 187 | 188 | static std::filesystem::path shaderPath; 189 | 190 | private: 191 | typedef enum 192 | { 193 | INSTANCE_POS_SCALE_ATTRIBUTE = 5, 194 | INSTANCE_COLOR_ATTRIBUTE = 6 195 | } Attribute; 196 | 197 | typedef struct ControlData 198 | { 199 | glm::vec2 pos; 200 | float scale; 201 | float dummy; 202 | ofFloatColor color; 203 | 204 | ControlData() 205 | {} 206 | 207 | ControlData(const glm::vec2 & pos, const ofFloatColor & color, float scale) 208 | : pos(pos) 209 | , color(color) 210 | , scale(scale) 211 | {} 212 | } ControlData; 213 | 214 | std::vector controlData; 215 | ofVboMesh controlMesh; 216 | ofShader controlShader; 217 | }; 218 | } -------------------------------------------------------------------------------- /src/ofxWarp/WarpPerspectiveBilinear.cpp: -------------------------------------------------------------------------------- 1 | #include "WarpPerspectiveBilinear.h" 2 | 3 | #include "ofGraphics.h" 4 | 5 | namespace ofxWarp 6 | { 7 | //-------------------------------------------------------------- 8 | WarpPerspectiveBilinear::WarpPerspectiveBilinear(const ofFbo::Settings & fboSettings) 9 | : WarpBilinear(fboSettings) 10 | { 11 | this->type = TYPE_PERSPECTIVE_BILINEAR; 12 | 13 | // Create child WarpPerspective. 14 | this->warpPerspective = std::make_shared(); 15 | } 16 | 17 | //-------------------------------------------------------------- 18 | WarpPerspectiveBilinear::~WarpPerspectiveBilinear() 19 | {} 20 | 21 | //-------------------------------------------------------------- 22 | void WarpPerspectiveBilinear::serialize(nlohmann::json & json) 23 | { 24 | WarpBilinear::serialize(json); 25 | 26 | std::vector corners; 27 | for (auto i = 0; i < 4; ++i) 28 | { 29 | const auto corner = this->warpPerspective->getControlPoint(i); 30 | std::ostringstream oss; 31 | oss << corner; 32 | corners.push_back(oss.str()); 33 | } 34 | json["corners"] = corners; 35 | } 36 | 37 | //-------------------------------------------------------------- 38 | void WarpPerspectiveBilinear::deserialize(const nlohmann::json & json) 39 | { 40 | WarpBilinear::deserialize(json); 41 | 42 | auto i = 0; 43 | for (const auto & jsonPoint : json["corners"]) 44 | { 45 | glm::vec2 corner; 46 | std::istringstream iss; 47 | iss.str(jsonPoint); 48 | iss >> corner; 49 | this->warpPerspective->setControlPoint(i, corner); 50 | 51 | ++i; 52 | } 53 | } 54 | 55 | //-------------------------------------------------------------- 56 | void WarpPerspectiveBilinear::setEditing(bool editing) 57 | { 58 | WarpBase::setEditing(editing); 59 | this->warpPerspective->setEditing(this->editing); 60 | } 61 | 62 | //-------------------------------------------------------------- 63 | void WarpPerspectiveBilinear::setSize(float width, float height) 64 | { 65 | // Make content size compatible with WarpBilinear's windowSize. 66 | this->warpPerspective->setSize(this->windowSize); 67 | 68 | WarpBilinear::setSize(width, height); 69 | } 70 | 71 | //-------------------------------------------------------------- 72 | void WarpPerspectiveBilinear::reset(const glm::vec2 & scale, const glm::vec2 & offset) 73 | { 74 | this->warpPerspective->reset(scale, offset); 75 | 76 | WarpBilinear::reset(); 77 | } 78 | 79 | //-------------------------------------------------------------- 80 | glm::vec2 WarpPerspectiveBilinear::getControlPoint(size_t index) const 81 | { 82 | // Depending on index, return perspective or bilinear control point. 83 | if (this->isCorner(index)) 84 | { 85 | // Perspective: simply return one of the corners. 86 | return this->warpPerspective->getControlPoint(this->convertIndex(index)); 87 | } 88 | else 89 | { 90 | // Bilinear: transform control point from warped space to normalized screen space. 91 | auto cp = WarpBase::getControlPoint(index) * this->warpPerspective->getSize(); 92 | auto pt = this->warpPerspective->getTransform() * glm::vec4(cp.x, cp.y, 0.0f, 1.0f); 93 | 94 | if (pt.w != 0) pt.w = 1.0f / pt.w; 95 | pt *= pt.w; 96 | 97 | return glm::vec2(pt.x, pt.y) / this->windowSize; 98 | } 99 | } 100 | 101 | //-------------------------------------------------------------- 102 | void WarpPerspectiveBilinear::setControlPoint(size_t index, const glm::vec2 & pos) 103 | { 104 | // Depending on index, set perspective or bilinear control point. 105 | if (this->isCorner(index)) 106 | { 107 | // Perspective: simply set the control point. 108 | this->warpPerspective->setControlPoint(this->convertIndex(index), pos); 109 | } 110 | else 111 | { 112 | // Bilinear:: transform control point from normalized screen space to warped space. 113 | auto cp = pos * this->windowSize; 114 | auto pt = this->warpPerspective->getTransformInverted() * glm::vec4(cp.x, cp.y, 0.0f, 1.0f); 115 | 116 | if (pt.w != 0) pt.w = 1.0f / pt.w; 117 | pt *= pt.w; 118 | 119 | WarpBase::setControlPoint(index, glm::vec2(pt.x, pt.y) / this->warpPerspective->getSize()); 120 | } 121 | } 122 | 123 | //-------------------------------------------------------------- 124 | void WarpPerspectiveBilinear::moveControlPoint(size_t index, const glm::vec2 & shift) 125 | { 126 | // Depending on index, move perspective or bilinear control point. 127 | if (this->isCorner(index)) 128 | { 129 | // Perspective: simply move the control point. 130 | this->warpPerspective->moveControlPoint(this->convertIndex(index), shift); 131 | } 132 | else { 133 | // Bilinear: transform control point from normalized screen space to warped space. 134 | auto pt = getControlPoint(index); 135 | this->setControlPoint(index, pt + shift); 136 | } 137 | } 138 | 139 | //-------------------------------------------------------------- 140 | void WarpPerspectiveBilinear::selectControlPoint(size_t index) 141 | { 142 | // Depending on index, select perspective or bilinear control point. 143 | if (this->isCorner(index)) 144 | { 145 | this->warpPerspective->selectControlPoint(this->convertIndex(index)); 146 | } 147 | else 148 | { 149 | this->warpPerspective->deselectControlPoint(); 150 | } 151 | 152 | // Always select bilinear control point, which we use to keep track of editing. 153 | WarpBase::selectControlPoint(index); 154 | } 155 | 156 | //-------------------------------------------------------------- 157 | void WarpPerspectiveBilinear::deselectControlPoint() 158 | { 159 | this->warpPerspective->deselectControlPoint(); 160 | WarpBase::deselectControlPoint(); 161 | } 162 | 163 | //-------------------------------------------------------------- 164 | void WarpPerspectiveBilinear::rotateClockwise() 165 | { 166 | this->warpPerspective->rotateClockwise(); 167 | } 168 | 169 | //-------------------------------------------------------------- 170 | void WarpPerspectiveBilinear::rotateCounterclockwise() 171 | { 172 | this->warpPerspective->rotateCounterclockwise(); 173 | } 174 | 175 | //-------------------------------------------------------------- 176 | bool WarpPerspectiveBilinear::handleCursorDown(const glm::vec2 & pos) 177 | { 178 | if (!this->editing || this->selectedIndex >= this->controlPoints.size()) return false; 179 | 180 | // Depending on selected control point, let perspective or bilinear warp handle it. 181 | if (this->isCorner(this->selectedIndex)) 182 | { 183 | return this->warpPerspective->handleCursorDown(pos); 184 | } 185 | return WarpBase::handleCursorDown(pos); 186 | } 187 | 188 | //-------------------------------------------------------------- 189 | bool WarpPerspectiveBilinear::handleCursorDrag(const glm::vec2 & pos) 190 | { 191 | if (!this->editing || this->selectedIndex >= this->controlPoints.size()) return false; 192 | 193 | // Depending on selected control point, let perspective or bilinear warp handle it. 194 | if (this->isCorner(this->selectedIndex)) 195 | { 196 | return this->warpPerspective->handleCursorDrag(pos); 197 | } 198 | return WarpBase::handleCursorDrag(pos); 199 | } 200 | 201 | //-------------------------------------------------------------- 202 | bool WarpPerspectiveBilinear::handleWindowResize(int width, int height) 203 | { 204 | // Make content size compatible with WarpBilinear's windowSize. 205 | this->warpPerspective->setSize(width, height); 206 | 207 | auto handled = this->warpPerspective->handleWindowResize(width, height); 208 | handled |= WarpBilinear::handleWindowResize(width, height); 209 | 210 | return handled; 211 | } 212 | 213 | //-------------------------------------------------------------- 214 | void WarpPerspectiveBilinear::drawTexture(const ofTexture & texture, const ofRectangle & srcBounds, const ofRectangle & dstBounds) 215 | { 216 | ofPushMatrix(); 217 | { 218 | // Apply Perspective transform. 219 | ofMultMatrix(this->warpPerspective->getTransform()); 220 | 221 | // Draw Bilinear warp. 222 | WarpBilinear::drawTexture(texture, srcBounds, dstBounds); 223 | } 224 | ofPopMatrix(); 225 | } 226 | 227 | //-------------------------------------------------------------- 228 | bool WarpPerspectiveBilinear::isCorner(size_t index) const 229 | { 230 | auto numControls = (this->numControlsX * this->numControlsY); 231 | 232 | return (index == 0 || index == (numControls - this->numControlsY) || index == (numControls - 1) || index == (this->numControlsY - 1)); 233 | } 234 | 235 | //-------------------------------------------------------------- 236 | size_t WarpPerspectiveBilinear::convertIndex(size_t index) const 237 | { 238 | auto numControls = (this->numControlsX * this->numControlsY); 239 | 240 | if (index == 0) 241 | { 242 | return 0; 243 | } 244 | if (index == (numControls - this->numControlsY)) 245 | { 246 | return 2; 247 | } 248 | if (index == (numControls - 1)) 249 | { 250 | return 3; 251 | } 252 | if (index == (this->numControlsY - 1)) 253 | { 254 | return 1; 255 | } 256 | 257 | return index; 258 | } 259 | } -------------------------------------------------------------------------------- /src/ofxWarp/WarpPerspective.cpp: -------------------------------------------------------------------------------- 1 | #include "WarpPerspective.h" 2 | 3 | #include "ofGraphics.h" 4 | 5 | namespace ofxWarp 6 | { 7 | //-------------------------------------------------------------- 8 | WarpPerspective::WarpPerspective() 9 | : WarpBase(TYPE_PERSPECTIVE) 10 | { 11 | this->srcPoints[0] = glm::vec2(0.0f, 0.0f); 12 | this->srcPoints[1] = glm::vec2(this->width, 0.0f); 13 | this->srcPoints[2] = glm::vec2(this->width, this->height); 14 | this->srcPoints[3] = glm::vec2(0.0f, this->height); 15 | 16 | this->reset(); 17 | 18 | this->shader.load(WarpBase::shaderPath / "WarpPerspective"); 19 | } 20 | 21 | //-------------------------------------------------------------- 22 | WarpPerspective::~WarpPerspective() 23 | {} 24 | 25 | //-------------------------------------------------------------- 26 | const glm::mat4 & WarpPerspective::getTransform() 27 | { 28 | // Calculate warp matrix. 29 | if (this->dirty) { 30 | // Update source size. 31 | this->srcPoints[1].x = this->width; 32 | this->srcPoints[2].x = this->width; 33 | this->srcPoints[2].y = this->height; 34 | this->srcPoints[3].y = this->height; 35 | 36 | // Convert corners to actual destination pixels. 37 | for (int i = 0; i < 4; ++i) 38 | { 39 | this->dstPoints[i] = this->controlPoints[i] * this->windowSize; 40 | } 41 | 42 | // Calculate warp matrix. 43 | this->transform = getPerspectiveTransform(this->srcPoints, this->dstPoints); 44 | this->transformInverted = glm::inverse(this->transform); 45 | 46 | this->dirty = false; 47 | } 48 | 49 | return this->transform; 50 | } 51 | 52 | //-------------------------------------------------------------- 53 | const glm::mat4 & WarpPerspective::getTransformInverted() 54 | { 55 | if (this->dirty) 56 | { 57 | this->getTransform(); 58 | } 59 | 60 | return this->transformInverted; 61 | } 62 | 63 | //-------------------------------------------------------------- 64 | void WarpPerspective::reset(const glm::vec2 & scale, const glm::vec2 & offset) 65 | { 66 | this->controlPoints.clear(); 67 | 68 | this->controlPoints.push_back(glm::vec2(0.0f, 0.0f) * scale + offset); 69 | this->controlPoints.push_back(glm::vec2(1.0f, 0.0f) * scale + offset); 70 | this->controlPoints.push_back(glm::vec2(1.0f, 1.0f) * scale + offset); 71 | this->controlPoints.push_back(glm::vec2(0.0f, 1.0f) * scale + offset); 72 | 73 | this->dirty = true; 74 | } 75 | 76 | //-------------------------------------------------------------- 77 | void WarpPerspective::begin() 78 | { 79 | ofPushMatrix(); 80 | ofMultMatrix(this->getTransform()); 81 | } 82 | 83 | //-------------------------------------------------------------- 84 | void WarpPerspective::end() 85 | { 86 | ofPopMatrix(); 87 | 88 | this->drawControls(); 89 | } 90 | 91 | //-------------------------------------------------------------- 92 | void WarpPerspective::drawTexture(const ofTexture & texture, const ofRectangle & srcBounds, const ofRectangle & dstBounds) 93 | { 94 | // Clip against bounds. 95 | auto srcClip = srcBounds; 96 | auto dstClip = dstBounds; 97 | this->clip(srcClip, dstClip); 98 | 99 | // Set corner texture coordinates. 100 | glm::vec4 corners; 101 | if (texture.getTextureData().textureTarget == GL_TEXTURE_RECTANGLE_ARB) 102 | { 103 | if (texture.getTextureData().bFlipTexture) 104 | { 105 | corners = glm::vec4(srcClip.getMinX(), srcClip.getMaxY(), srcClip.getMaxX(), srcClip.getMinY()); 106 | } 107 | else 108 | { 109 | corners = glm::vec4(srcClip.getMinX(), srcClip.getMinY(), srcClip.getMaxX(), srcClip.getMaxY()); 110 | } 111 | } 112 | else 113 | { 114 | if (texture.getTextureData().bFlipTexture) 115 | { 116 | corners = glm::vec4(srcClip.getMinX() / texture.getWidth(), srcClip.getMaxY() / texture.getHeight(), srcClip.getMaxX() / texture.getWidth(), srcClip.getMinY() / texture.getHeight()); 117 | } 118 | else 119 | { 120 | corners = glm::vec4(srcClip.getMinX() / texture.getWidth(), srcClip.getMinY() / texture.getHeight(), srcClip.getMaxX() / texture.getWidth(), srcClip.getMaxY() / texture.getHeight()); 121 | } 122 | } 123 | 124 | ofPushMatrix(); 125 | { 126 | ofMultMatrix(this->getTransform()); 127 | 128 | auto currentColor = ofGetStyle().color; 129 | ofPushStyle(); 130 | { 131 | // Adjust brightness. 132 | if (this->brightness < 1.0f) 133 | { 134 | currentColor *= this->brightness; 135 | ofSetColor(currentColor); 136 | } 137 | 138 | // Draw texture. 139 | this->shader.begin(); 140 | { 141 | this->shader.setUniformTexture("uTexture", texture, 1); 142 | this->shader.setUniform3f("uLuminance", this->luminance); 143 | this->shader.setUniform3f("uGamma", this->gamma); 144 | this->shader.setUniform4f("uEdges", this->edges); 145 | this->shader.setUniform4f("uCorners", corners); 146 | this->shader.setUniform1f("uExponent", this->exponent); 147 | 148 | const auto mesh = texture.getMeshForSubsection(dstClip.x, dstClip.y, 0.0f, dstClip.width, dstClip.height, srcClip.x, srcClip.y, srcClip.width, srcClip.height, ofIsVFlipped(), OF_RECTMODE_CORNER); 149 | mesh.draw(); 150 | } 151 | this->shader.end(); 152 | } 153 | ofPopStyle(); 154 | 155 | if (this->editing) 156 | { 157 | // Draw grid lines. 158 | ofPushStyle(); 159 | { 160 | glHint(GL_LINE_SMOOTH_HINT, GL_NICEST); 161 | 162 | ofSetColor(ofColor::white); 163 | 164 | for (int i = 0; i <= 1; ++i) 165 | { 166 | float s = i / 1.0f; 167 | ofDrawLine(s * this->width, 0.0f, s * this->width, this->height); 168 | ofDrawLine(0.0f, s * this->height, this->width, s * this->height); 169 | } 170 | 171 | ofDrawLine(0.0f, 0.0f, this->width, this->height); 172 | ofDrawLine(this->width, 0.0f, 0.0f, this->height); 173 | } 174 | ofPopStyle(); 175 | } 176 | } 177 | ofPopMatrix(); 178 | } 179 | 180 | //-------------------------------------------------------------- 181 | void WarpPerspective::drawControls() 182 | { 183 | if (this->editing && this->selectedIndex < this->controlPoints.size()) 184 | { 185 | // Draw control points. 186 | for (auto i = 0; i < 4; ++i) 187 | { 188 | this->queueControlPoint(dstPoints[i], i == this->selectedIndex); 189 | } 190 | 191 | this->drawControlPoints(); 192 | } 193 | } 194 | 195 | //-------------------------------------------------------------- 196 | // Adapted from: http://forum.openframeworks.cc/t/quad-warping-homography-without-opencv/3121/19 197 | glm::mat4 WarpPerspective::getPerspectiveTransform(const glm::vec2 src[4], const glm::vec2 dst[4]) const 198 | { 199 | float p[8][9] = 200 | { 201 | { -src[0][0], -src[0][1], -1, 0, 0, 0, src[0][0] * dst[0][0], src[0][1] * dst[0][0], -dst[0][0] }, // h11 202 | { 0, 0, 0, -src[0][0], -src[0][1], -1, src[0][0] * dst[0][1], src[0][1] * dst[0][1], -dst[0][1] }, // h12 203 | { -src[1][0], -src[1][1], -1, 0, 0, 0, src[1][0] * dst[1][0], src[1][1] * dst[1][0], -dst[1][0] }, // h13 204 | { 0, 0, 0, -src[1][0], -src[1][1], -1, src[1][0] * dst[1][1], src[1][1] * dst[1][1], -dst[1][1] }, // h21 205 | { -src[2][0], -src[2][1], -1, 0, 0, 0, src[2][0] * dst[2][0], src[2][1] * dst[2][0], -dst[2][0] }, // h22 206 | { 0, 0, 0, -src[2][0], -src[2][1], -1, src[2][0] * dst[2][1], src[2][1] * dst[2][1], -dst[2][1] }, // h23 207 | { -src[3][0], -src[3][1], -1, 0, 0, 0, src[3][0] * dst[3][0], src[3][1] * dst[3][0], -dst[3][0] }, // h31 208 | { 0, 0, 0, -src[3][0], -src[3][1], -1, src[3][0] * dst[3][1], src[3][1] * dst[3][1], -dst[3][1] }, // h32 209 | }; 210 | 211 | this->gaussianElimination(&p[0][0], 9); 212 | 213 | return glm::mat4(p[0][8], p[3][8], 0, p[6][8], 214 | p[1][8], p[4][8], 0, p[7][8], 215 | 0, 0, 1, 0, 216 | p[2][8], p[5][8], 0, 1); 217 | } 218 | 219 | //-------------------------------------------------------------- 220 | void WarpPerspective::gaussianElimination(float * input, int n) const 221 | { 222 | auto i = 0; 223 | auto j = 0; 224 | auto m = n - 1; 225 | 226 | while (i < m && j < n) 227 | { 228 | auto iMax = i; 229 | for (auto k = i + 1; k < m; ++k) 230 | { 231 | if (fabs(input[k * n + j]) > fabs(input[iMax * n + j])) 232 | { 233 | iMax = k; 234 | } 235 | } 236 | 237 | if (input[iMax * n + j] != 0) 238 | { 239 | if (i != iMax) 240 | { 241 | for (auto k = 0; k < n; ++k) 242 | { 243 | auto ikIn = input[i * n + k]; 244 | input[i * n + k] = input[iMax * n + k]; 245 | input[iMax * n + k] = ikIn; 246 | } 247 | } 248 | 249 | float ijIn = input[i * n + j]; 250 | for (auto k = 0; k < n; ++k) 251 | { 252 | input[i * n + k] /= ijIn; 253 | } 254 | 255 | for (auto u = i + 1; u < m; ++u) 256 | { 257 | auto ujIn = input[u * n + j]; 258 | for (auto k = 0; k < n; ++k) 259 | { 260 | input[u * n + k] -= ujIn * input[i * n + k]; 261 | } 262 | } 263 | 264 | ++i; 265 | } 266 | ++j; 267 | } 268 | 269 | for (auto i = m - 2; i >= 0; --i) 270 | { 271 | for (auto j = i + 1; j < n - 1; ++j) 272 | { 273 | input[i * n + m] -= input[i * n + j] * input[j * n + m]; 274 | } 275 | } 276 | } 277 | 278 | //-------------------------------------------------------------- 279 | void WarpPerspective::rotateClockwise() 280 | { 281 | std::swap(this->controlPoints[3], this->controlPoints[0]); 282 | std::swap(this->controlPoints[0], this->controlPoints[1]); 283 | std::swap(this->controlPoints[1], this->controlPoints[2]); 284 | this->selectedIndex = (this->selectedIndex + 3) % 4; 285 | this->dirty = true; 286 | } 287 | 288 | //-------------------------------------------------------------- 289 | void WarpPerspective::rotateCounterclockwise() 290 | { 291 | std::swap(this->controlPoints[1], this->controlPoints[2]); 292 | std::swap(this->controlPoints[0], this->controlPoints[1]); 293 | std::swap(this->controlPoints[3], this->controlPoints[0]); 294 | this->selectedIndex = (this->selectedIndex + 1) % 4; 295 | this->dirty = true; 296 | } 297 | 298 | //-------------------------------------------------------------- 299 | void WarpPerspective::flipHorizontal() 300 | { 301 | std::swap(this->controlPoints[0], this->controlPoints[1]); 302 | std::swap(this->controlPoints[2], this->controlPoints[3]); 303 | if (this->selectedIndex % 2) 304 | { 305 | --this->selectedIndex; 306 | } 307 | else 308 | { 309 | ++this->selectedIndex; 310 | } 311 | this->dirty = true; 312 | } 313 | 314 | //-------------------------------------------------------------- 315 | void WarpPerspective::flipVertical() 316 | { 317 | std::swap(this->controlPoints[0], this->controlPoints[3]); 318 | std::swap(this->controlPoints[1], this->controlPoints[2]); 319 | this->selectedIndex = (this->controlPoints.size() - 1) - this->selectedIndex; 320 | this->dirty = true; 321 | } 322 | } -------------------------------------------------------------------------------- /example/example.vcxproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | Win32 7 | 8 | 9 | Debug 10 | x64 11 | 12 | 13 | Release 14 | Win32 15 | 16 | 17 | Release 18 | x64 19 | 20 | 21 | 22 | {7FD42DF7-442E-479A-BA76-D0022F99702A} 23 | Win32Proj 24 | example 25 | 26 | 27 | 28 | Application 29 | Unicode 30 | v141 31 | 32 | 33 | Application 34 | Unicode 35 | v141 36 | 37 | 38 | Application 39 | Unicode 40 | true 41 | v141 42 | 43 | 44 | Application 45 | Unicode 46 | true 47 | v141 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | bin\ 69 | obj\$(Configuration)\ 70 | $(ProjectName)_debug 71 | true 72 | true 73 | 74 | 75 | bin\ 76 | obj\$(Configuration)\ 77 | $(ProjectName)_debug 78 | true 79 | true 80 | 81 | 82 | bin\ 83 | obj\$(Configuration)\ 84 | false 85 | 86 | 87 | bin\ 88 | obj\$(Configuration)\ 89 | false 90 | 91 | 92 | 93 | Disabled 94 | EnableFastChecks 95 | %(PreprocessorDefinitions) 96 | MultiThreadedDebugDLL 97 | Level3 98 | ..\..\..\addons\ofxWarp\src;%(AdditionalIncludeDirectories) 99 | CompileAsCpp 100 | 101 | 102 | true 103 | Console 104 | false 105 | %(AdditionalLibraryDirectories) 106 | 107 | 108 | 109 | 110 | 111 | 112 | Disabled 113 | EnableFastChecks 114 | %(PreprocessorDefinitions) 115 | MultiThreadedDebugDLL 116 | Level3 117 | CompileAsCpp 118 | true 119 | ..\..\..\addons\ofxWarp\src;%(AdditionalIncludeDirectories) 120 | 121 | 122 | true 123 | Console 124 | false 125 | %(AdditionalLibraryDirectories) 126 | 127 | 128 | 129 | 130 | 131 | 132 | false 133 | %(PreprocessorDefinitions) 134 | MultiThreadedDLL 135 | Level3 136 | ..\..\..\addons\ofxWarp\src;%(AdditionalIncludeDirectories) 137 | CompileAsCpp 138 | true 139 | 140 | 141 | false 142 | false 143 | Console 144 | true 145 | true 146 | false 147 | %(AdditionalLibraryDirectories) 148 | 149 | 150 | 151 | 152 | 153 | 154 | false 155 | %(PreprocessorDefinitions) 156 | MultiThreadedDLL 157 | Level3 158 | ..\..\..\addons\ofxWarp\src;%(AdditionalIncludeDirectories) 159 | CompileAsCpp 160 | 161 | 162 | false 163 | false 164 | Console 165 | true 166 | true 167 | false 168 | %(AdditionalLibraryDirectories) 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | {5837595d-aca9-485c-8e76-729040ce4b0b} 194 | 195 | 196 | 197 | 198 | /D_DEBUG %(AdditionalOptions) 199 | /D_DEBUG %(AdditionalOptions) 200 | $(OF_ROOT)\libs\openFrameworksCompiled\project\vs 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | -------------------------------------------------------------------------------- /src/ofxWarp/Controller.cpp: -------------------------------------------------------------------------------- 1 | #include "Controller.h" 2 | 3 | #include "WarpBilinear.h" 4 | #include "WarpPerspective.h" 5 | #include "WarpPerspectiveBilinear.h" 6 | 7 | #include "GLFW/glfw3.h" 8 | 9 | namespace ofxWarp 10 | { 11 | //-------------------------------------------------------------- 12 | Controller::Controller() 13 | : focusedIndex(-1) 14 | { 15 | ofAddListener(ofEvents().mouseMoved, this, &Controller::onMouseMoved); 16 | ofAddListener(ofEvents().mousePressed, this, &Controller::onMousePressed); 17 | ofAddListener(ofEvents().mouseDragged, this, &Controller::onMouseDragged); 18 | ofAddListener(ofEvents().mouseReleased, this, &Controller::onMouseReleased); 19 | 20 | ofAddListener(ofEvents().keyPressed, this, &Controller::onKeyPressed); 21 | ofAddListener(ofEvents().keyReleased, this, &Controller::onKeyReleased); 22 | 23 | ofAddListener(ofEvents().windowResized, this, &Controller::onWindowResized); 24 | } 25 | 26 | //-------------------------------------------------------------- 27 | Controller::~Controller() 28 | { 29 | ofRemoveListener(ofEvents().mouseMoved, this, &Controller::onMouseMoved); 30 | ofRemoveListener(ofEvents().mousePressed, this, &Controller::onMousePressed); 31 | ofRemoveListener(ofEvents().mouseDragged, this, &Controller::onMouseDragged); 32 | ofRemoveListener(ofEvents().mouseReleased, this, &Controller::onMouseReleased); 33 | 34 | ofRemoveListener(ofEvents().keyPressed, this, &Controller::onKeyPressed); 35 | ofRemoveListener(ofEvents().keyReleased, this, &Controller::onKeyReleased); 36 | 37 | ofRemoveListener(ofEvents().windowResized, this, &Controller::onWindowResized); 38 | 39 | this->warps.clear(); 40 | } 41 | 42 | //-------------------------------------------------------------- 43 | bool Controller::saveSettings(const std::string & filePath) 44 | { 45 | nlohmann::json json; 46 | this->serialize(json); 47 | 48 | auto file = ofFile(filePath, ofFile::WriteOnly); 49 | file << json.dump(4); 50 | 51 | return true; 52 | } 53 | 54 | //-------------------------------------------------------------- 55 | bool Controller::loadSettings(const std::string & filePath) 56 | { 57 | auto file = ofFile(filePath, ofFile::ReadOnly); 58 | if (!file.exists()) 59 | { 60 | ofLogWarning("Warp::loadSettings") << "File not found at path " << filePath; 61 | return false; 62 | } 63 | 64 | nlohmann::json json; 65 | file >> json; 66 | 67 | this->deserialize(json); 68 | 69 | return true; 70 | } 71 | 72 | //-------------------------------------------------------------- 73 | void Controller::serialize(nlohmann::json & json) 74 | { 75 | std::vector jsonWarps; 76 | for (auto warp : this->warps) 77 | { 78 | nlohmann::json jsonWarp; 79 | warp->serialize(jsonWarp); 80 | jsonWarps.push_back(jsonWarp); 81 | } 82 | json["warps"] = jsonWarps; 83 | } 84 | 85 | //-------------------------------------------------------------- 86 | void Controller::deserialize(const nlohmann::json & json) 87 | { 88 | this->warps.clear(); 89 | for (auto & jsonWarp : json["warps"]) 90 | { 91 | std::shared_ptr warp; 92 | 93 | int typeAsInt = jsonWarp["type"]; 94 | WarpBase::Type type = (WarpBase::Type)typeAsInt; 95 | switch (type) 96 | { 97 | case WarpBase::TYPE_BILINEAR: 98 | warp = std::make_shared(); 99 | break; 100 | 101 | case WarpBase::TYPE_PERSPECTIVE: 102 | warp = std::make_shared(); 103 | break; 104 | 105 | case WarpBase::TYPE_PERSPECTIVE_BILINEAR: 106 | warp = std::make_shared(); 107 | break; 108 | 109 | default: 110 | ofLogWarning("Warp::loadSettings") << "Unrecognized Warp type " << type; 111 | } 112 | 113 | if (warp) 114 | { 115 | warp->deserialize(jsonWarp); 116 | this->warps.push_back(warp); 117 | } 118 | } 119 | } 120 | 121 | //-------------------------------------------------------------- 122 | bool Controller::addWarp(std::shared_ptr warp) 123 | { 124 | auto it = std::find(this->warps.begin(), this->warps.end(), warp); 125 | if (it == this->warps.end()) 126 | { 127 | this->warps.push_back(warp); 128 | return true; 129 | } 130 | 131 | return false; 132 | } 133 | 134 | //-------------------------------------------------------------- 135 | bool Controller::removeWarp(std::shared_ptr warp) 136 | { 137 | auto it = std::find(this->warps.begin(), this->warps.end(), warp); 138 | if (it != this->warps.end()) 139 | { 140 | this->warps.erase(it); 141 | return true; 142 | } 143 | 144 | return false; 145 | } 146 | 147 | //-------------------------------------------------------------- 148 | std::vector> & Controller::getWarps() 149 | { 150 | return this->warps; 151 | } 152 | 153 | //-------------------------------------------------------------- 154 | std::shared_ptr Controller::getWarp(size_t index) const 155 | { 156 | if (index < this->warps.size()) 157 | { 158 | return this->warps[index]; 159 | } 160 | return nullptr; 161 | } 162 | 163 | //-------------------------------------------------------------- 164 | size_t Controller::getNumWarps() const 165 | { 166 | return this->warps.size(); 167 | } 168 | 169 | //-------------------------------------------------------------- 170 | void Controller::selectClosestControlPoint(const glm::vec2 & pos) 171 | { 172 | size_t warpIdx = -1; 173 | size_t pointIdx = -1; 174 | auto distance = std::numeric_limits::max(); 175 | 176 | // Find warp and distance to closest control point. 177 | for (int i = this->warps.size() - 1; i >= 0; --i) 178 | { 179 | float candidate; 180 | auto idx = this->warps[i]->findClosestControlPoint(pos, &candidate); 181 | if (candidate < distance && this->warps[i]->isEditing()) 182 | { 183 | distance = candidate; 184 | pointIdx = idx; 185 | warpIdx = i; 186 | } 187 | } 188 | 189 | focusedIndex = warpIdx; 190 | 191 | // Select the closest control point and deselect all others. 192 | for (int i = this->warps.size() - 1; i >= 0; --i) 193 | { 194 | if (i == this->focusedIndex) 195 | { 196 | this->warps[i]->selectControlPoint(pointIdx); 197 | } 198 | else 199 | { 200 | this->warps[i]->deselectControlPoint(); 201 | } 202 | } 203 | } 204 | 205 | //-------------------------------------------------------------- 206 | void Controller::onMouseMoved(ofMouseEventArgs & args) 207 | { 208 | // Find and select closest control point. 209 | this->selectClosestControlPoint(args); 210 | } 211 | 212 | //-------------------------------------------------------------- 213 | void Controller::onMousePressed(ofMouseEventArgs & args) 214 | { 215 | // Find and select closest control point. 216 | this->selectClosestControlPoint(args); 217 | 218 | if (this->focusedIndex < this->warps.size()) 219 | { 220 | this->warps[this->focusedIndex]->handleCursorDown(args); 221 | } 222 | } 223 | 224 | //-------------------------------------------------------------- 225 | void Controller::onMouseDragged(ofMouseEventArgs & args) 226 | { 227 | if (this->focusedIndex < this->warps.size()) 228 | { 229 | this->warps[this->focusedIndex]->handleCursorDrag(args); 230 | } 231 | } 232 | 233 | //-------------------------------------------------------------- 234 | void Controller::onMouseReleased(ofMouseEventArgs & args) 235 | {} 236 | 237 | //-------------------------------------------------------------- 238 | void Controller::onKeyPressed(ofKeyEventArgs & args) 239 | { 240 | if (args.key == 'w') 241 | { 242 | for (auto warp : this->warps) 243 | { 244 | warp->toggleEditing(); 245 | } 246 | } 247 | else if (this->focusedIndex < this->warps.size()) 248 | { 249 | auto warp = this->warps[this->focusedIndex]; 250 | 251 | if (args.key == '-') 252 | { 253 | warp->setBrightness(MAX(0.0f, warp->getBrightness() - 0.01f)); 254 | } 255 | else if (args.key == '+') 256 | { 257 | warp->setBrightness(MIN(1.0f, warp->getBrightness() + 0.01f)); 258 | } 259 | else if (args.key == 'r') 260 | { 261 | warp->reset(); 262 | } 263 | else if (args.key == OF_KEY_TAB) 264 | { 265 | // Select the next of previous (+ SHIFT) control point. 266 | size_t nextIndex; 267 | auto selectedIndex = warp->getSelectedControlPoint(); 268 | if (ofGetKeyPressed(OF_KEY_SHIFT)) 269 | { 270 | if (selectedIndex == 0) 271 | { 272 | nextIndex = warp->getNumControlPoints() - 1; 273 | } 274 | else 275 | { 276 | nextIndex = selectedIndex - 1; 277 | } 278 | } 279 | else 280 | { 281 | nextIndex = (selectedIndex + 1) % warp->getNumControlPoints(); 282 | } 283 | warp->selectControlPoint(nextIndex); 284 | } 285 | else if (args.key == OF_KEY_UP || args.key == OF_KEY_DOWN || args.key == OF_KEY_LEFT || args.key == OF_KEY_RIGHT) 286 | { 287 | auto step = ofGetKeyPressed(OF_KEY_SHIFT) ? 10.0f : 0.5f; 288 | auto shift = glm::vec2(0.0f); 289 | if (args.key == OF_KEY_UP) 290 | { 291 | shift.y = -step / (float)ofGetHeight(); 292 | } 293 | else if (args.key == OF_KEY_DOWN) 294 | { 295 | shift.y = step / (float)ofGetHeight(); 296 | } 297 | else if (args.key == OF_KEY_LEFT) 298 | { 299 | shift.x = -step / (float)ofGetWidth(); 300 | } 301 | else 302 | { 303 | shift.x = step / (float)ofGetWidth(); 304 | } 305 | warp->moveControlPoint(warp->getSelectedControlPoint(), shift); 306 | } 307 | else if (args.key == OF_KEY_F9) 308 | { 309 | warp->rotateCounterclockwise(); 310 | } 311 | else if (args.key == OF_KEY_F10) 312 | { 313 | warp->rotateClockwise(); 314 | } 315 | else if (args.key == OF_KEY_F11) 316 | { 317 | warp->flipHorizontal(); 318 | } 319 | else if (args.key == OF_KEY_F12) 320 | { 321 | warp->flipVertical(); 322 | } 323 | else if (warp->getType() == WarpBase::TYPE_BILINEAR || warp->getType() == WarpBase::TYPE_PERSPECTIVE_BILINEAR) 324 | { 325 | // The rest of the controls only apply to Bilinear warps. 326 | auto warpBilinear = std::dynamic_pointer_cast(warp); 327 | if (warpBilinear) 328 | { 329 | if (args.key == OF_KEY_F1) 330 | { 331 | // Reduce the number of horizontal control points. 332 | if (ofGetKeyPressed(OF_KEY_SHIFT)) 333 | { 334 | warpBilinear->setNumControlsX(warpBilinear->getNumControlsX() - 1); 335 | } 336 | else 337 | { 338 | warpBilinear->setNumControlsX((warpBilinear->getNumControlsX() + 1) / 2); 339 | } 340 | } 341 | else if (args.key == OF_KEY_F2) 342 | { 343 | // Increase the number of horizontal control points. 344 | if (ofGetKeyPressed(OF_KEY_SHIFT)) 345 | { 346 | warpBilinear->setNumControlsX(warpBilinear->getNumControlsX() + 1); 347 | } 348 | else 349 | { 350 | warpBilinear->setNumControlsX(warpBilinear->getNumControlsX() * 2 - 1); 351 | } 352 | } 353 | else if (args.key == OF_KEY_F3) 354 | { 355 | // Reduce the number of vertical control points. 356 | if (ofGetKeyPressed(OF_KEY_SHIFT)) 357 | { 358 | warpBilinear->setNumControlsY(warpBilinear->getNumControlsY() - 1); 359 | } 360 | else 361 | { 362 | warpBilinear->setNumControlsY((warpBilinear->getNumControlsY() + 1) / 2); 363 | } 364 | } 365 | else if (args.key == OF_KEY_F4) 366 | { 367 | // Increase the number of vertical control points. 368 | if (ofGetKeyPressed(OF_KEY_SHIFT)) 369 | { 370 | warpBilinear->setNumControlsY(warpBilinear->getNumControlsY() + 1); 371 | } 372 | else 373 | { 374 | warpBilinear->setNumControlsY(warpBilinear->getNumControlsY() * 2 - 1); 375 | } 376 | } 377 | else if (args.key == OF_KEY_F5) 378 | { 379 | warpBilinear->decreaseResolution(); 380 | } 381 | else if (args.key == OF_KEY_F6) 382 | { 383 | warpBilinear->increaseResolution(); 384 | } 385 | else if (args.key == OF_KEY_F7) 386 | { 387 | warpBilinear->setAdaptive(!warpBilinear->getAdaptive()); 388 | } 389 | else if (args.key == 'm') 390 | { 391 | warpBilinear->setLinear(!warpBilinear->getLinear()); 392 | } 393 | } 394 | } 395 | } 396 | } 397 | 398 | //-------------------------------------------------------------- 399 | void Controller::onKeyReleased(ofKeyEventArgs & args) 400 | {} 401 | 402 | //-------------------------------------------------------------- 403 | void Controller::onWindowResized(ofResizeEventArgs & args) 404 | { 405 | for (auto warp : this->warps) 406 | { 407 | warp->handleWindowResize(args.width, args.height); 408 | } 409 | } 410 | } -------------------------------------------------------------------------------- /src/ofxWarp/WarpBase.cpp: -------------------------------------------------------------------------------- 1 | #include "WarpBase.h" 2 | 3 | #include "ofPolyline.h" 4 | 5 | namespace ofxWarp 6 | { 7 | //-------------------------------------------------------------- 8 | std::filesystem::path WarpBase::shaderPath = std::filesystem::path("shaders") / "ofxWarp"; 9 | 10 | //-------------------------------------------------------------- 11 | void WarpBase::setShaderPath(const std::filesystem::path shaderPath) 12 | { 13 | WarpBase::shaderPath = shaderPath; 14 | } 15 | 16 | //-------------------------------------------------------------- 17 | WarpBase::WarpBase(Type type) 18 | : type(type) 19 | , editing(false) 20 | , dirty(true) 21 | , brightness(1.0f) 22 | , width(640.0f) 23 | , height(480.0f) 24 | , numControlsX(2) 25 | , numControlsY(2) 26 | , selectedIndex(-1) 27 | , selectedTime(0.0f) 28 | , luminance(0.5f) 29 | , gamma(1.0f) 30 | , exponent(2.0f) 31 | , edges(0.0f) 32 | { 33 | this->windowSize = glm::vec2(ofGetWidth(), ofGetHeight()); 34 | } 35 | 36 | //-------------------------------------------------------------- 37 | WarpBase::~WarpBase() 38 | {} 39 | 40 | //-------------------------------------------------------------- 41 | WarpBase::Type WarpBase::getType() const 42 | { 43 | return this->type; 44 | } 45 | 46 | //-------------------------------------------------------------- 47 | void WarpBase::serialize(nlohmann::json & json) 48 | { 49 | // Main parameters. 50 | json["type"] = this->type; 51 | json["brightness"] = this->brightness; 52 | 53 | // Warp parameters. 54 | { 55 | auto & jsonWarp = json["warp"]; 56 | 57 | jsonWarp["columns"] = this->numControlsX; 58 | jsonWarp["rows"] = this->numControlsY; 59 | 60 | std::vector points; 61 | for (auto & controlPoint : this->controlPoints) 62 | { 63 | std::ostringstream oss; 64 | oss << controlPoint; 65 | points.push_back(oss.str()); 66 | } 67 | jsonWarp["control points"] = points; 68 | } 69 | 70 | // Blend parameters. 71 | { 72 | auto & jsonBlend = json["blend"]; 73 | 74 | jsonBlend["exponent"] = this->exponent; 75 | 76 | std::ostringstream oss; 77 | oss << this->edges; 78 | jsonBlend["edges"] = oss.str(); 79 | 80 | oss.str(""); 81 | oss << this->gamma; 82 | jsonBlend["gamma"] = oss.str(); 83 | 84 | oss.str(""); 85 | oss << this->luminance; 86 | jsonBlend["luminance"] = oss.str(); 87 | } 88 | } 89 | 90 | //-------------------------------------------------------------- 91 | void WarpBase::deserialize(const nlohmann::json & json) 92 | { 93 | // Main parameters. 94 | int typeAsInt = json["type"]; 95 | this->type = (Type)typeAsInt; 96 | this->brightness = json["brightness"]; 97 | 98 | // Warp parameters. 99 | { 100 | const auto & jsonWarp = json["warp"]; 101 | 102 | this->numControlsX = jsonWarp["columns"]; 103 | this->numControlsY = jsonWarp["rows"]; 104 | 105 | this->controlPoints.clear(); 106 | for (const auto & jsonPoint : jsonWarp["control points"]) 107 | { 108 | glm::vec2 controlPoint; 109 | std::istringstream iss; 110 | iss.str(jsonPoint); 111 | iss >> controlPoint; 112 | this->controlPoints.push_back(controlPoint); 113 | } 114 | } 115 | 116 | // Blend parameters. 117 | { 118 | const auto & jsonBlend = json["blend"]; 119 | 120 | this->exponent = jsonBlend["exponent"]; 121 | 122 | { 123 | std::istringstream iss; 124 | iss.str(jsonBlend["edges"]); 125 | iss >> this->edges; 126 | } 127 | { 128 | std::istringstream iss; 129 | iss.str(jsonBlend["gamma"]); 130 | iss >> this->gamma; 131 | } 132 | { 133 | std::istringstream iss; 134 | iss.str(jsonBlend["luminance"]); 135 | iss >> this->luminance; 136 | } 137 | } 138 | 139 | this->dirty = true; 140 | } 141 | 142 | //-------------------------------------------------------------- 143 | void WarpBase::setEditing(bool editing) 144 | { 145 | this->editing = editing; 146 | } 147 | 148 | //-------------------------------------------------------------- 149 | void WarpBase::toggleEditing() 150 | { 151 | this->setEditing(!this->editing); 152 | } 153 | 154 | //-------------------------------------------------------------- 155 | bool WarpBase::isEditing() const 156 | { 157 | return this->editing; 158 | } 159 | 160 | //-------------------------------------------------------------- 161 | void WarpBase::setWidth(float width) 162 | { 163 | this->setSize(width, this->height); 164 | } 165 | 166 | //-------------------------------------------------------------- 167 | float WarpBase::getWidth() const 168 | { 169 | return this->width; 170 | } 171 | 172 | //-------------------------------------------------------------- 173 | void WarpBase::setHeight(float height) 174 | { 175 | this->setSize(this->width, height); 176 | } 177 | 178 | //-------------------------------------------------------------- 179 | float WarpBase::getHeight() const 180 | { 181 | return this->height; 182 | } 183 | 184 | //-------------------------------------------------------------- 185 | void WarpBase::setSize(float width, float height) 186 | { 187 | this->width = width; 188 | this->height = height; 189 | this->dirty = true; 190 | } 191 | 192 | //-------------------------------------------------------------- 193 | void WarpBase::setSize(const glm::vec2 & size) 194 | { 195 | this->setSize(size.x, size.y); 196 | } 197 | 198 | //-------------------------------------------------------------- 199 | glm::vec2 WarpBase::getSize() const 200 | { 201 | return glm::vec2(this->width, this->height); 202 | } 203 | 204 | //-------------------------------------------------------------- 205 | ofRectangle WarpBase::getBounds() const 206 | { 207 | return ofRectangle(0, 0, this->width, this->height); 208 | } 209 | 210 | //-------------------------------------------------------------- 211 | void WarpBase::setBrightness(float brightness) 212 | { 213 | this->brightness = brightness; 214 | } 215 | 216 | //-------------------------------------------------------------- 217 | float WarpBase::getBrightness() const 218 | { 219 | return this->brightness; 220 | } 221 | 222 | //-------------------------------------------------------------- 223 | void WarpBase::setLuminance(float luminance) 224 | { 225 | this->luminance = glm::vec3(luminance); 226 | } 227 | 228 | //-------------------------------------------------------------- 229 | void WarpBase::setLuminance(float red, float green, float blue) 230 | { 231 | this->luminance = glm::vec3(red, green, blue); 232 | } 233 | 234 | //-------------------------------------------------------------- 235 | void WarpBase::setLuminance(const glm::vec3 & rgb) 236 | { 237 | this->luminance = rgb; 238 | } 239 | 240 | //-------------------------------------------------------------- 241 | const glm::vec3 & WarpBase::getLuminance() const 242 | { 243 | return this->luminance; 244 | } 245 | 246 | //-------------------------------------------------------------- 247 | void WarpBase::setGamma(float gamma) 248 | { 249 | this->gamma = glm::vec3(gamma); 250 | } 251 | 252 | //-------------------------------------------------------------- 253 | void WarpBase::setGamma(float red, float green, float blue) 254 | { 255 | this->gamma = glm::vec3(red, green, blue); 256 | } 257 | 258 | //-------------------------------------------------------------- 259 | void WarpBase::setGamma(const glm::vec3 & rgb) 260 | { 261 | this->gamma = rgb; 262 | } 263 | 264 | //-------------------------------------------------------------- 265 | const glm::vec3 & WarpBase::getGamma() const 266 | { 267 | return this->gamma; 268 | } 269 | 270 | //-------------------------------------------------------------- 271 | void WarpBase::setExponent(float exponent) 272 | { 273 | this->exponent = exponent; 274 | } 275 | 276 | //-------------------------------------------------------------- 277 | float WarpBase::getExponent() const 278 | { 279 | return this->exponent; 280 | } 281 | 282 | //-------------------------------------------------------------- 283 | void WarpBase::setEdges(float left, float top, float right, float bottom) 284 | { 285 | this->setEdges(glm::vec4(left, top, right, bottom)); 286 | } 287 | 288 | //-------------------------------------------------------------- 289 | void WarpBase::setEdges(const glm::vec4 & edges) 290 | { 291 | this->edges.x = ofClamp(edges.x * 0.5f, 0.0f, 1.0f); 292 | this->edges.y = ofClamp(edges.y * 0.5f, 0.0f, 1.0f); 293 | this->edges.z = ofClamp(edges.z * 0.5f, 0.0f, 1.0f); 294 | this->edges.w = ofClamp(edges.w * 0.5f, 0.0f, 1.0f); 295 | } 296 | 297 | //-------------------------------------------------------------- 298 | glm::vec4 WarpBase::getEdges() const 299 | { 300 | return this->edges * 2.0f; 301 | } 302 | 303 | //-------------------------------------------------------------- 304 | void WarpBase::draw(const ofTexture & texture) 305 | { 306 | this->draw(texture, ofRectangle(0, 0, texture.getWidth(), texture.getHeight()), this->getBounds()); 307 | } 308 | 309 | //-------------------------------------------------------------- 310 | void WarpBase::draw(const ofTexture & texture, const ofRectangle & srcBounds) 311 | { 312 | this->draw(texture, srcBounds, this->getBounds()); 313 | } 314 | 315 | //-------------------------------------------------------------- 316 | void WarpBase::draw(const ofTexture & texture, const ofRectangle & srcBounds, const ofRectangle & dstBounds) 317 | { 318 | this->drawTexture(texture, srcBounds, dstBounds); 319 | this->drawControls(); 320 | } 321 | 322 | //-------------------------------------------------------------- 323 | bool WarpBase::clip(ofRectangle & srcBounds, ofRectangle & dstBounds) const 324 | { 325 | bool clipped = false; 326 | 327 | glm::vec4 srcVec = glm::vec4(srcBounds.getMinX(), srcBounds.getMinY(), srcBounds.getMaxX(), srcBounds.getMaxY()); 328 | glm::vec4 dstVec = glm::vec4(dstBounds.getMinX(), dstBounds.getMinY(), dstBounds.getMaxX(), dstBounds.getMaxY()); 329 | 330 | float x1 = dstVec.x / this->width; 331 | float x2 = dstVec.z / this->width; 332 | float y1 = dstVec.y / this->height; 333 | float y2 = dstVec.w / this->height; 334 | 335 | if (x1 < 0.0f) 336 | { 337 | dstVec.x = 0.0f; 338 | srcVec.x -= (x1 * srcBounds.getWidth()); 339 | clipped = true; 340 | } 341 | else if (x1 > 1.0f) 342 | { 343 | dstVec.x = this->width; 344 | srcVec.x -= ((1.0f / x1) * srcBounds.getWidth()); 345 | clipped = true; 346 | } 347 | 348 | if (x2 < 0.0f) 349 | { 350 | dstVec.z = 0.0f; 351 | srcVec.z -= (x2 * srcBounds.getWidth()); 352 | clipped = true; 353 | } 354 | else if (x2 > 1.0f) { 355 | dstVec.z = this->width; 356 | srcVec.z -= ((1.0f / x2) * srcBounds.getWidth()); 357 | clipped = true; 358 | } 359 | 360 | if (y1 < 0.0f) 361 | { 362 | dstVec.y = 0.0f; 363 | srcVec.y -= (y1 * srcBounds.getHeight()); 364 | clipped = true; 365 | } 366 | else if (y1 > 1.0f) 367 | { 368 | dstVec.y = this->height; 369 | srcVec.y -= ((1.0f / y1) * srcBounds.getHeight()); 370 | clipped = true; 371 | } 372 | 373 | if (y2 < 0.0f) { 374 | dstVec.w = 0.0f; 375 | srcVec.w -= (y2 * srcBounds.getHeight()); 376 | clipped = true; 377 | } 378 | else if (y2 > 1.0f) { 379 | dstVec.w = this->height; 380 | srcVec.w -= ((1.0f / y2) * srcBounds.getHeight()); 381 | clipped = true; 382 | } 383 | 384 | srcBounds.set(srcVec.x, srcVec.y, srcVec.z - srcVec.x, srcVec.w - srcVec.y); 385 | dstBounds.set(dstVec.x, dstVec.y, dstVec.z - dstVec.x, dstVec.w - dstVec.y); 386 | 387 | return clipped; 388 | } 389 | 390 | //-------------------------------------------------------------- 391 | glm::vec2 WarpBase::getControlPoint(size_t index) const 392 | { 393 | if (index >= this->controlPoints.size()) return glm::vec2(0.0f); 394 | 395 | return this->controlPoints[index]; 396 | } 397 | 398 | //-------------------------------------------------------------- 399 | void WarpBase::setControlPoint(size_t index, const glm::vec2 & pos) 400 | { 401 | if (index >= this->controlPoints.size()) return; 402 | 403 | this->controlPoints[index] = pos; 404 | this->dirty = true; 405 | } 406 | 407 | //-------------------------------------------------------------- 408 | void WarpBase::moveControlPoint(size_t index, const glm::vec2 & shift) 409 | { 410 | if (index >= this->controlPoints.size()) return; 411 | 412 | this->controlPoints[index] += shift; 413 | this->dirty = true; 414 | } 415 | 416 | //-------------------------------------------------------------- 417 | size_t WarpBase::getNumControlPoints() const 418 | { 419 | return this->controlPoints.size(); 420 | } 421 | 422 | //-------------------------------------------------------------- 423 | size_t WarpBase::getSelectedControlPoint() const 424 | { 425 | return this->selectedIndex; 426 | } 427 | 428 | //-------------------------------------------------------------- 429 | void WarpBase::selectControlPoint(size_t index) 430 | { 431 | if (index >= this->controlPoints.size() || index == this->selectedIndex) return; 432 | 433 | this->selectedIndex = index; 434 | this->selectedTime = ofGetElapsedTimef(); 435 | } 436 | 437 | //-------------------------------------------------------------- 438 | void WarpBase::deselectControlPoint() 439 | { 440 | this->selectedIndex = -1; 441 | } 442 | 443 | //-------------------------------------------------------------- 444 | size_t WarpBase::findClosestControlPoint(const glm::vec2 & pos, float * distance) const 445 | { 446 | size_t index; 447 | auto minDistance = std::numeric_limits::max(); 448 | 449 | for (auto i = 0; i < this->controlPoints.size(); ++i) 450 | { 451 | auto candidate = glm::distance(pos, this->getControlPoint(i) * this->windowSize); 452 | if (candidate < minDistance) 453 | { 454 | minDistance = candidate; 455 | index = i; 456 | } 457 | } 458 | 459 | *distance = minDistance; 460 | return index; 461 | } 462 | 463 | //-------------------------------------------------------------- 464 | size_t WarpBase::getNumControlsX() const 465 | { 466 | return this->numControlsX; 467 | } 468 | 469 | //-------------------------------------------------------------- 470 | size_t WarpBase::getNumControlsY() const 471 | { 472 | return this->numControlsY; 473 | } 474 | 475 | //-------------------------------------------------------------- 476 | void WarpBase::queueControlPoint(const glm::vec2 & pos, bool selected, bool attached) 477 | { 478 | if (selected && attached) 479 | { 480 | queueControlPoint(pos, ofFloatColor(0.0f, 0.8f, 0.0f)); 481 | } 482 | else if (selected) 483 | { 484 | auto scale = 0.9f + 0.2f * sinf(6.0f * (ofGetElapsedTimef() - this->selectedTime)); 485 | queueControlPoint(pos, ofFloatColor(0.9f, 0.9f, 0.9f), scale); 486 | } 487 | else if (attached) 488 | { 489 | queueControlPoint(pos, ofFloatColor(0.0f, 0.4f, 0.0f)); 490 | } 491 | else 492 | { 493 | queueControlPoint(pos, ofFloatColor(0.4f, 0.4f, 0.4f)); 494 | } 495 | } 496 | 497 | //-------------------------------------------------------------- 498 | void WarpBase::queueControlPoint(const glm::vec2 & pos, const ofFloatColor & color, float scale) 499 | { 500 | if (this->controlData.size() < MAX_NUM_CONTROL_POINTS) 501 | { 502 | this->controlData.emplace_back(ControlData(pos, color, scale)); 503 | } 504 | } 505 | 506 | //-------------------------------------------------------------- 507 | void WarpBase::setupControlPoints() 508 | { 509 | if (this->controlMesh.getVertices().empty()) 510 | { 511 | // Set up the vbo mesh. 512 | ofPolyline unitCircle; 513 | unitCircle.arc(glm::vec3(0.0f), 1.0f, 1.0f, 0.0f, 360.0f, 18); 514 | const auto & circlePoints = unitCircle.getVertices(); 515 | static const auto radius = 15.0f; 516 | static const auto halfVec = glm::vec2(0.5f); 517 | this->controlMesh.clear(); 518 | this->controlMesh.setMode(OF_PRIMITIVE_TRIANGLE_FAN); 519 | this->controlMesh.setUsage(GL_STATIC_DRAW); 520 | this->controlMesh.addVertex(glm::vec3(0.0f)); 521 | this->controlMesh.addTexCoord(halfVec); 522 | for (auto & pt : circlePoints) 523 | { 524 | this->controlMesh.addVertex(pt * radius); 525 | this->controlMesh.addTexCoord(glm::vec2(pt) * 0.5f + halfVec); 526 | } 527 | 528 | // Set up per-instance data to the vbo. 529 | std::vector instanceData; 530 | instanceData.resize(MAX_NUM_CONTROL_POINTS); 531 | 532 | this->controlMesh.getVbo().setAttributeData(INSTANCE_POS_SCALE_ATTRIBUTE, (float *)&instanceData[0].pos, 4, instanceData.size(), GL_STREAM_DRAW, sizeof(ControlData)); 533 | this->controlMesh.getVbo().setAttributeDivisor(INSTANCE_POS_SCALE_ATTRIBUTE, 1); 534 | this->controlMesh.getVbo().setAttributeData(INSTANCE_COLOR_ATTRIBUTE, (float *)&instanceData[0].color, 4, instanceData.size(), GL_STREAM_DRAW, sizeof(ControlData)); 535 | this->controlMesh.getVbo().setAttributeDivisor(INSTANCE_COLOR_ATTRIBUTE, 1); 536 | } 537 | 538 | if (!this->controlShader.isLoaded()) 539 | { 540 | // Load the shader. 541 | this->controlShader.setupShaderFromFile(GL_VERTEX_SHADER, WarpBase::shaderPath / "ControlPoint.vert"); 542 | this->controlShader.setupShaderFromFile(GL_FRAGMENT_SHADER, WarpBase::shaderPath / "ControlPoint.frag"); 543 | this->controlShader.bindAttribute(INSTANCE_POS_SCALE_ATTRIBUTE, "iPositionScale"); 544 | this->controlShader.bindAttribute(INSTANCE_COLOR_ATTRIBUTE, "iColor"); 545 | this->controlShader.bindDefaults(); 546 | this->controlShader.linkProgram(); 547 | } 548 | } 549 | 550 | //-------------------------------------------------------------- 551 | void WarpBase::drawControlPoints() 552 | { 553 | this->setupControlPoints(); 554 | 555 | if (!this->controlData.empty()) 556 | { 557 | this->controlMesh.getVbo().updateAttributeData(INSTANCE_POS_SCALE_ATTRIBUTE, (float *)&this->controlData[0].pos, this->controlData.size()); 558 | this->controlMesh.getVbo().updateAttributeData(INSTANCE_COLOR_ATTRIBUTE, (float *)&this->controlData[0].color, this->controlData.size()); 559 | 560 | this->controlShader.begin(); 561 | { 562 | this->controlMesh.drawInstanced(OF_MESH_FILL, this->controlData.size()); 563 | } 564 | this->controlShader.end(); 565 | } 566 | 567 | this->controlData.clear(); 568 | } 569 | 570 | //-------------------------------------------------------------- 571 | bool WarpBase::handleCursorDown(const glm::vec2 & pos) 572 | { 573 | if (!this->editing || this->selectedIndex >= this->controlPoints.size()) return false; 574 | 575 | // Calculate offset by converting control point from normalized to screen space. 576 | glm::vec2 screenPoint = (this->getControlPoint(this->selectedIndex) * this->windowSize); 577 | this->selectedOffset = pos - screenPoint; 578 | 579 | return true; 580 | } 581 | 582 | //-------------------------------------------------------------- 583 | bool WarpBase::handleCursorDrag(const glm::vec2 & pos) 584 | { 585 | if (!this->editing || this->selectedIndex >= this->controlPoints.size()) return false; 586 | 587 | // Set control point in normalized space. 588 | glm::vec2 screenPoint = pos - this->selectedOffset; 589 | this->setControlPoint(this->selectedIndex, screenPoint / this->windowSize); 590 | 591 | this->dirty = true; 592 | 593 | return true; 594 | } 595 | 596 | //-------------------------------------------------------------- 597 | bool WarpBase::handleWindowResize(int width, int height) 598 | { 599 | this->windowSize = glm::vec2(width, height); 600 | this->dirty = true; 601 | 602 | return true; 603 | } 604 | } 605 | -------------------------------------------------------------------------------- /src/ofxWarp/WarpBilinear.cpp: -------------------------------------------------------------------------------- 1 | #include "WarpBilinear.h" 2 | 3 | #include "ofGraphics.h" 4 | #include "ofPolyline.h" 5 | 6 | namespace ofxWarp 7 | { 8 | //-------------------------------------------------------------- 9 | WarpBilinear::WarpBilinear(const ofFbo::Settings & fboSettings) 10 | : WarpBase(TYPE_BILINEAR) 11 | , fboSettings(fboSettings) 12 | , linear(false) 13 | , adaptive(true) 14 | , corners(0.0f, 0.0f, 1.0f, 1.0f) 15 | , resolutionX(0) 16 | , resolutionY(0) 17 | , resolution(16) // higher value is coarser mesh 18 | { 19 | this->reset(); 20 | 21 | this->shader.load(WarpBase::shaderPath / "WarpBilinear"); 22 | } 23 | 24 | //-------------------------------------------------------------- 25 | WarpBilinear::~WarpBilinear() 26 | {} 27 | 28 | //-------------------------------------------------------------- 29 | void WarpBilinear::serialize(nlohmann::json & json) 30 | { 31 | WarpBase::serialize(json); 32 | 33 | json["resolution"] = this->resolution; 34 | json["linear"] = this->linear; 35 | json["adaptive"] = this->adaptive; 36 | } 37 | 38 | //-------------------------------------------------------------- 39 | void WarpBilinear::deserialize(const nlohmann::json & json) 40 | { 41 | WarpBase::deserialize(json); 42 | 43 | this->resolution = json["resolution"]; 44 | this->linear = json["linear"]; 45 | this->adaptive = json["adaptive"]; 46 | } 47 | 48 | //-------------------------------------------------------------- 49 | void WarpBilinear::setSize(float width, float height) 50 | { 51 | WarpBase::setSize(width, height); 52 | this->fbo.clear(); 53 | } 54 | 55 | //-------------------------------------------------------------- 56 | void WarpBilinear::setFboSettings(const ofFbo::Settings & fboSettings) 57 | { 58 | this->fboSettings = fboSettings; 59 | this->fbo.clear(); 60 | } 61 | 62 | //-------------------------------------------------------------- 63 | void WarpBilinear::setLinear(bool linear) 64 | { 65 | this->linear = linear; 66 | this->dirty = true; 67 | } 68 | 69 | //-------------------------------------------------------------- 70 | bool WarpBilinear::getLinear() const 71 | { 72 | return this->linear; 73 | } 74 | 75 | //-------------------------------------------------------------- 76 | void WarpBilinear::setAdaptive(bool adaptive) 77 | { 78 | this->adaptive = adaptive; 79 | this->dirty = true; 80 | } 81 | 82 | //-------------------------------------------------------------- 83 | bool WarpBilinear::getAdaptive() const 84 | { 85 | return this->adaptive; 86 | } 87 | 88 | //-------------------------------------------------------------- 89 | void WarpBilinear::increaseResolution() 90 | { 91 | if (this->resolution < 64) 92 | { 93 | this->resolution += 4; 94 | this->dirty = true; 95 | } 96 | } 97 | 98 | //-------------------------------------------------------------- 99 | void WarpBilinear::decreaseResolution() 100 | { 101 | if (this->resolution > 4) 102 | { 103 | this->resolution -= 4; 104 | this->dirty = true; 105 | } 106 | } 107 | 108 | //-------------------------------------------------------------- 109 | int WarpBilinear::getResolution() const 110 | { 111 | return this->resolution; 112 | } 113 | 114 | //-------------------------------------------------------------- 115 | void WarpBilinear::reset(const glm::vec2 & scale, const glm::vec2 & offset) 116 | { 117 | this->controlPoints.clear(); 118 | for (auto x = 0; x < this->numControlsX; ++x) 119 | { 120 | for (auto y = 0; y < this->numControlsY; ++y) 121 | { 122 | this->controlPoints.push_back(glm::vec2(x / float(this->numControlsX - 1), y / float(this->numControlsY - 1)) * scale + offset); 123 | } 124 | } 125 | 126 | this->dirty = true; 127 | } 128 | 129 | //-------------------------------------------------------------- 130 | void WarpBilinear::begin() 131 | { 132 | this->setupFbo(); 133 | 134 | this->fbo.begin(); 135 | } 136 | 137 | //-------------------------------------------------------------- 138 | void WarpBilinear::end() 139 | { 140 | this->fbo.end(); 141 | 142 | // Draw flipped. 143 | auto srcBounds = ofRectangle(0.0f, 0.0f, this->fbo.getWidth(), this->fbo.getHeight()); 144 | this->draw(this->fbo.getTexture(), srcBounds, this->getBounds()); 145 | } 146 | 147 | //-------------------------------------------------------------- 148 | void WarpBilinear::drawTexture(const ofTexture & texture, const ofRectangle & srcBounds, const ofRectangle & dstBounds) 149 | { 150 | // Clip against bounds. 151 | auto srcClip = srcBounds; 152 | auto dstClip = dstBounds; 153 | this->clip(srcClip, dstClip); 154 | 155 | // Set corner texture coordinates. 156 | if (texture.getTextureData().textureTarget == GL_TEXTURE_RECTANGLE_ARB) 157 | { 158 | if (texture.getTextureData().bFlipTexture) 159 | { 160 | this->setCorners(srcClip.getMinX(), srcClip.getMaxY(), srcClip.getMaxX(), srcClip.getMinY()); 161 | } 162 | else 163 | { 164 | this->setCorners(srcClip.getMinX(), srcClip.getMinY(), srcClip.getMaxX(), srcClip.getMaxY()); 165 | } 166 | } 167 | else 168 | { 169 | if (texture.getTextureData().bFlipTexture) 170 | { 171 | this->setCorners(srcClip.getMinX() / texture.getWidth(), srcClip.getMaxY() / texture.getHeight(), srcClip.getMaxX() / texture.getWidth(), srcClip.getMinY() / texture.getHeight()); 172 | } 173 | else 174 | { 175 | this->setCorners(srcClip.getMinX() / texture.getWidth(), srcClip.getMinY() / texture.getHeight(), srcClip.getMaxX() / texture.getWidth(), srcClip.getMaxY() / texture.getHeight()); 176 | } 177 | } 178 | 179 | this->setupVbo(); 180 | 181 | auto currentColor = ofGetStyle().color; 182 | ofPushStyle(); 183 | { 184 | auto wasDepthTest = glIsEnabled(GL_DEPTH_TEST); 185 | ofDisableDepthTest(); 186 | 187 | glHint(GL_LINE_SMOOTH_HINT, GL_NICEST); 188 | 189 | // Adjust brightness. 190 | if (this->brightness < 1.0f) 191 | { 192 | currentColor *= this->brightness; 193 | ofSetColor(currentColor); 194 | } 195 | 196 | this->shader.begin(); 197 | { 198 | this->shader.setUniformTexture("uTexture", texture, 1); 199 | this->shader.setUniform4f("uExtends", glm::vec4(this->width, this->height, this->width / float(this->numControlsX - 1), this->height / float(this->numControlsY - 1))); 200 | this->shader.setUniform3f("uLuminance", this->luminance); 201 | this->shader.setUniform3f("uGamma", this->gamma); 202 | this->shader.setUniform4f("uEdges", this->edges); 203 | this->shader.setUniform4f("uCorners", this->corners); 204 | this->shader.setUniform1f("uExponent", this->exponent); 205 | this->shader.setUniform1i("uEditing", this->editing); 206 | 207 | this->vbo.drawElements(GL_TRIANGLES, this->vbo.getNumIndices()); 208 | } 209 | this->shader.end(); 210 | 211 | if (wasDepthTest) 212 | { 213 | ofEnableDepthTest(); 214 | } 215 | } 216 | ofPopStyle(); 217 | } 218 | 219 | //-------------------------------------------------------------- 220 | void WarpBilinear::drawControls() 221 | { 222 | if (this->editing && this->selectedIndex < this->controlPoints.size()) 223 | { 224 | // Draw control points. 225 | for (auto i = 0; i < this->controlPoints.size(); ++i) 226 | { 227 | this->queueControlPoint(this->getControlPoint(i) * this->windowSize, i == this->selectedIndex); 228 | } 229 | 230 | this->drawControlPoints(); 231 | } 232 | } 233 | 234 | //-------------------------------------------------------------- 235 | void WarpBilinear::setupFbo() 236 | { 237 | if (!this->fbo.isAllocated() || this->fbo.getWidth() != this->width || this->fbo.getHeight() != this->height) 238 | { 239 | this->fboSettings.width = this->width; 240 | this->fboSettings.height = this->height; 241 | this->fbo.allocate(this->fboSettings); 242 | } 243 | } 244 | 245 | //-------------------------------------------------------------- 246 | void WarpBilinear::setupVbo() 247 | { 248 | if (this->dirty) 249 | { 250 | if (this->adaptive) 251 | { 252 | // Determine a suitable mesh resolution based on the dimensions of the window 253 | // and the size of the mesh in pixels. 254 | auto meshBounds = this->getMeshBounds(); 255 | this->setupMesh(meshBounds.getWidth() / this->resolution, meshBounds.getHeight() / this->resolution); 256 | } 257 | else 258 | { 259 | // Use a fixed mesh resolution. 260 | this->setupMesh(this->width / this->resolution, this->height / this->resolution); 261 | } 262 | this->updateMesh(); 263 | } 264 | } 265 | 266 | //-------------------------------------------------------------- 267 | void WarpBilinear::setupMesh(int resolutionX, int resolutionY) 268 | { 269 | // Convert from number of quads to number of vertices. 270 | ++resolutionX; 271 | ++resolutionY; 272 | 273 | // Find a value for resolutionX and resolutionY that can be evenly divided by numControlsX and numControlsY. 274 | if (this->numControlsX < resolutionX) 275 | { 276 | int dx = (resolutionX - 1) % (this->numControlsX - 1); 277 | if (dx >= (this->numControlsX / 2)) 278 | { 279 | dx -= (this->numControlsX - 1); 280 | } 281 | resolutionX -= dx; 282 | } 283 | else 284 | { 285 | resolutionX = this->numControlsX; 286 | } 287 | 288 | if (this->numControlsY < resolutionY) 289 | { 290 | int dy = (resolutionY - 1) % (this->numControlsY - 1); 291 | if (dy >= (this->numControlsY / 2)) 292 | { 293 | dy -= (this->numControlsY - 1); 294 | } 295 | resolutionY -= dy; 296 | } 297 | else 298 | { 299 | resolutionY = this->numControlsY; 300 | } 301 | 302 | this->resolutionX = resolutionX; 303 | this->resolutionY = resolutionY; 304 | 305 | int numVertices = (resolutionX * resolutionY); 306 | int numTriangles = 2 * (resolutionX - 1) * (resolutionY - 1); 307 | int numIndices = numTriangles * 3; 308 | 309 | // Build the static data. 310 | int i = 0; 311 | int j = 0; 312 | 313 | auto indices = std::vector(numIndices); 314 | auto texCoords = std::vector(numVertices); 315 | 316 | for (int x = 0; x < resolutionX; ++x) 317 | { 318 | for (int y = 0; y < resolutionY; ++y) 319 | { 320 | // Index. 321 | if (((x + 1) < resolutionX) && ((y + 1) < resolutionY)) 322 | { 323 | indices[i++] = (x + 0) * resolutionY + (y + 0); 324 | indices[i++] = (x + 1) * resolutionY + (y + 0); 325 | indices[i++] = (x + 1) * resolutionY + (y + 1); 326 | 327 | indices[i++] = (x + 0) * resolutionY + (y + 0); 328 | indices[i++] = (x + 1) * resolutionY + (y + 1); 329 | indices[i++] = (x + 0) * resolutionY + (y + 1); 330 | } 331 | 332 | // Tex Coord. 333 | float tx = ofLerp(this->corners.x, this->corners.z, x / (float)(this->resolutionX - 1)); 334 | float ty = ofLerp(this->corners.y, this->corners.w, y / (float)(this->resolutionY - 1)); 335 | texCoords[j++] = glm::vec2(tx, ty); 336 | } 337 | } 338 | 339 | // Build placeholder data. 340 | std::vector positions(this->resolutionX * this->resolutionY); 341 | 342 | // Build mesh. 343 | this->vbo.clear(); 344 | this->vbo.setVertexData(positions.data(), positions.size(), GL_STATIC_DRAW); 345 | this->vbo.setTexCoordData(texCoords.data(), texCoords.size(), GL_STATIC_DRAW); 346 | this->vbo.setIndexData(indices.data(), indices.size(), GL_STATIC_DRAW); 347 | 348 | this->dirty = true; 349 | } 350 | 351 | // Mapped buffer seems to be a *tiny* bit faster. 352 | #define USE_MAPPED_BUFFER 1 353 | 354 | //-------------------------------------------------------------- 355 | void WarpBilinear::updateMesh() 356 | { 357 | if (!this->vbo.getIsAllocated() || !this->dirty) return; 358 | 359 | glm::vec2 pt; 360 | float u, v; 361 | int col, row; 362 | 363 | std::vector cols, rows; 364 | 365 | #if USE_MAPPED_BUFFER 366 | auto vertexBuffer = this->vbo.getVertexBuffer(); 367 | auto mappedMesh = (glm::vec3 *)vertexBuffer.map(GL_WRITE_ONLY); 368 | #else 369 | std::vector positions(this->resolutionX * this->resolutionY); 370 | auto index = 0; 371 | #endif 372 | 373 | for (auto x = 0; x < this->resolutionX; ++x) 374 | { 375 | for (auto y = 0; y < this->resolutionY; ++y) 376 | { 377 | // Transform coordinates to [0..numControls] 378 | u = x * (this->numControlsX - 1) / (float)(this->resolutionX - 1); 379 | v = y * (this->numControlsY - 1) / (float)(this->resolutionY - 1); 380 | 381 | // Determine col and row. 382 | col = (int)u; 383 | row = (int)v; 384 | 385 | // Normalize coordinates to [0..1] 386 | u -= col; 387 | v -= row; 388 | 389 | if (this->linear) 390 | { 391 | // Perform linear interpolation. 392 | auto p1 = (1.0f - u) * this->getPoint(col, row) + u * this->getPoint(col + 1, row); 393 | auto p2 = (1.0f - u) * this->getPoint(col, row + 1) + u * this->getPoint(col + 1, row + 1); 394 | pt = ((1.0f - v) * p1 + v * p2) * this->windowSize; 395 | } 396 | else { 397 | // Perform bicubic interpolation. 398 | rows.clear(); 399 | for (int i = -1; i < 3; ++i) 400 | { 401 | cols.clear(); 402 | for (int j = -1; j < 3; ++j) 403 | { 404 | cols.push_back(this->getPoint(col + i, row + j)); 405 | } 406 | rows.push_back(this->cubicInterpolate(cols, v)); 407 | } 408 | pt = this->cubicInterpolate(rows, u) * this->windowSize; 409 | } 410 | 411 | #if USE_MAPPED_BUFFER 412 | *mappedMesh++ = glm::vec3(pt.x, pt.y, 0.0f); 413 | #else 414 | positions[index++] = glm::vec3(pt.x, pt.y, 0.0f); 415 | #endif 416 | } 417 | } 418 | 419 | #if USE_MAPPED_BUFFER 420 | vertexBuffer.unmap(); 421 | #else 422 | this->vbo.updateVertexData(positions.data(), positions.size()); 423 | #endif 424 | 425 | this->dirty = false; 426 | } 427 | 428 | //-------------------------------------------------------------- 429 | glm::vec2 WarpBilinear::getPoint(int col, int row) const 430 | { 431 | auto maxCol = this->numControlsX - 1; 432 | auto maxRow = this->numControlsY - 1; 433 | 434 | // Here's the magic: extrapolate points beyond the edges. 435 | if (col < 0) 436 | { 437 | return (2.0f * getPoint(0, row) - getPoint(0 - col, row)); 438 | } 439 | if (row < 0) 440 | { 441 | return (2.0f * getPoint(col, 0) - getPoint(col, 0 - row)); 442 | } 443 | if (col > maxCol) 444 | { 445 | return (2.0f * getPoint(maxCol, row) - getPoint(2 * maxCol - col, row)); 446 | } 447 | if (row > maxRow) 448 | { 449 | return (2.0f * getPoint(col, maxRow) - getPoint(col, 2 * maxRow - row)); 450 | } 451 | 452 | // Points on the edges or within the mesh can simply be looked up. 453 | auto idx = (col * this->numControlsY) + row; 454 | return this->controlPoints[idx]; 455 | } 456 | 457 | //-------------------------------------------------------------- 458 | // From http://www.paulinternet.nl/?page=bicubic : fast catmull-rom calculation 459 | glm::vec2 WarpBilinear::cubicInterpolate(const std::vector & knots, float t) const 460 | { 461 | assert(knots.size() >= 4); 462 | 463 | return (knots[1] + 0.5f * t * (knots[2] - knots[0] + t * (2.0f * knots[0] - 5.0f * knots[1] + 4.0f * knots[2] - knots[3] + t * (3.0f * (knots[1] - knots[2]) + knots[3] - knots[0])))); 464 | } 465 | 466 | //-------------------------------------------------------------- 467 | void WarpBilinear::setNumControlsX(int n) 468 | { 469 | // There should be a minimum of 2 control points. 470 | n = MAX(2, n); 471 | 472 | // Prevent overflow. 473 | if ((n * this->numControlsY) > MAX_NUM_CONTROL_POINTS) return; 474 | 475 | // Create a list of new points. 476 | std::vector tempPoints(n * this->numControlsY); 477 | 478 | // Perform spline fitting. 479 | for (auto row = 0; row < this->numControlsY; ++row) { 480 | if (this->linear) 481 | { 482 | // Construct piece-wise linear spline. 483 | ofPolyline polyline; 484 | for (auto col = 0; col < this->numControlsX; ++col) 485 | { 486 | polyline.lineTo(glm::vec3(this->getPoint(col, row), 0.0f)); 487 | } 488 | 489 | // Calculate position of new control points. 490 | auto step = 1.0f / (n - 1); 491 | for (auto col = 0; col < n; ++col) 492 | { 493 | auto idx = (col * this->numControlsY) + row; 494 | tempPoints[idx] = glm::vec2(polyline.getPointAtPercent(col * step)); 495 | } 496 | } 497 | else 498 | { 499 | // Construct piece-wise catmull-rom spline. 500 | ofPolyline polyline; 501 | for (auto col = 0; col < this->numControlsX; ++col) 502 | { 503 | auto p0 = this->getPoint(col - 1, row); 504 | auto p1 = this->getPoint(col, row); 505 | auto p2 = this->getPoint(col + 1, row); 506 | auto p3 = this->getPoint(col + 2, row); 507 | 508 | // Control points according to an optimized Catmull-Rom implementation 509 | auto b1 = p1 + (p2 - p0) / 6.0f; 510 | auto b2 = p2 - (p3 - p1) / 6.0f; 511 | 512 | if (col == 0) 513 | { 514 | polyline.lineTo(glm::vec3(p1, 0.0f)); 515 | } 516 | 517 | polyline.curveTo(glm::vec3(p1, 0.0f)); 518 | 519 | if (col < (this->numControlsX - 1)) 520 | { 521 | polyline.curveTo(glm::vec3(b1, 0.0f)); 522 | polyline.curveTo(glm::vec3(b2, 0.0f)); 523 | } 524 | else 525 | { 526 | polyline.lineTo(glm::vec3(p1, 0.0f)); 527 | } 528 | } 529 | 530 | // Calculate position of new control points. 531 | auto step = 1.0f / (n - 1); 532 | for (auto col = 0; col < n; ++col) 533 | { 534 | auto idx = (col * this->numControlsY) + row; 535 | tempPoints[idx] = glm::vec2(polyline.getPointAtPercent(col * step)); 536 | } 537 | } 538 | } 539 | 540 | // Save new control points. 541 | this->controlPoints = tempPoints; 542 | this->numControlsX = n; 543 | 544 | // Find new closest control point. 545 | float distance; 546 | this->selectedIndex = this->findClosestControlPoint(glm::vec2(ofGetMouseX(), ofGetMouseY()), &distance); 547 | 548 | this->dirty = true; 549 | } 550 | 551 | //-------------------------------------------------------------- 552 | void WarpBilinear::setNumControlsY(int n) 553 | { 554 | // There should be a minimum of 2 control points. 555 | n = MAX(2, n); 556 | 557 | // Prevent overflow. 558 | if ((this->numControlsX * n) > MAX_NUM_CONTROL_POINTS) return; 559 | 560 | // Create a list of new points. 561 | std::vector tempPoints(this->numControlsX * n); 562 | 563 | // Perform spline fitting 564 | for (auto col = 0; col < this->numControlsX; ++col) 565 | { 566 | if (this->linear) 567 | { 568 | // Construct piece-wise linear spline. 569 | ofPolyline polyline; 570 | for (auto row = 0; row < this->numControlsY; ++row) 571 | { 572 | polyline.lineTo(glm::vec3(this->getPoint(col, row), 0.0f)); 573 | } 574 | 575 | // Calculate position of new control points. 576 | float step = 1.0f / (n - 1); 577 | for (auto row = 0; row < n; ++row) 578 | { 579 | auto idx = (col * n) + row; 580 | tempPoints[idx] = glm::vec2(polyline.getPointAtPercent(row * step)); 581 | } 582 | } 583 | else 584 | { 585 | // Construct piece-wise catmull-rom spline. 586 | ofPolyline polyline; 587 | for (auto row = 0; row < this->numControlsY; ++row) 588 | { 589 | auto p0 = this->getPoint(col, row - 1); 590 | auto p1 = this->getPoint(col, row); 591 | auto p2 = this->getPoint(col, row + 1); 592 | auto p3 = this->getPoint(col, row + 2); 593 | 594 | // Control points according to an optimized Catmull-Rom implementation 595 | auto b1 = p1 + (p2 - p0) / 6.0f; 596 | auto b2 = p2 - (p3 - p1) / 6.0f; 597 | 598 | if (row == 0) 599 | { 600 | polyline.lineTo(glm::vec3(p1, 0.0f)); 601 | } 602 | 603 | polyline.curveTo(glm::vec3(p1, 0.0f)); 604 | 605 | if (row < (this->numControlsY - 1)) 606 | { 607 | polyline.curveTo(glm::vec3(b1, 0.0f)); 608 | polyline.curveTo(glm::vec3(b2, 0.0f)); 609 | } 610 | else 611 | { 612 | polyline.lineTo(glm::vec3(p1, 0.0f)); 613 | } 614 | } 615 | 616 | // Calculate position of new control points. 617 | auto step = 1.0f / (n - 1); 618 | for (auto row = 0; row < n; ++row) 619 | { 620 | auto idx = (col * n) + row; 621 | tempPoints[idx] = glm::vec2(polyline.getPointAtPercent(row * step)); 622 | } 623 | } 624 | } 625 | 626 | // Save new control points. 627 | this->controlPoints = tempPoints; 628 | this->numControlsY = n; 629 | 630 | // Find new closest control point. 631 | float distance; 632 | this->selectedIndex = this->findClosestControlPoint(glm::vec2(ofGetMouseX(), ofGetMouseY()), &distance); 633 | 634 | this->dirty = true; 635 | } 636 | 637 | //-------------------------------------------------------------- 638 | ofRectangle WarpBilinear::getMeshBounds() const 639 | { 640 | auto min = glm::vec2(1.0f); 641 | auto max = glm::vec2(0.0f); 642 | 643 | for (auto & pt : this->controlPoints) 644 | { 645 | min.x = MIN(pt.x, min.x); 646 | min.y = MIN(pt.y, min.y); 647 | max.x = MAX(pt.x, max.x); 648 | max.y = MAX(pt.y, min.y); 649 | } 650 | 651 | return ofRectangle(min * this->windowSize, max * this->windowSize); 652 | } 653 | 654 | //-------------------------------------------------------------- 655 | void WarpBilinear::setCorners(float left, float top, float right, float bottom) 656 | { 657 | this->dirty |= (left != this->corners.x || top != this->corners.y || right != this->corners.z || bottom != this->corners.w); 658 | if (!this->dirty) return; 659 | 660 | this->corners = glm::vec4(left, top, right, bottom); 661 | } 662 | 663 | //-------------------------------------------------------------- 664 | void WarpBilinear::rotateClockwise() 665 | { 666 | ofLogWarning("WarpBilinear::rotateClockwise") << "Not implemented!"; 667 | } 668 | 669 | //-------------------------------------------------------------- 670 | void WarpBilinear::rotateCounterclockwise() 671 | { 672 | ofLogWarning("WarpBilinear::rotateCounterclockwise") << "Not implemented!"; 673 | } 674 | 675 | //-------------------------------------------------------------- 676 | void WarpBilinear::flipHorizontal() 677 | { 678 | std::vector flippedPoints; 679 | for (int x = this->numControlsX - 1; x >= 0; --x) 680 | { 681 | for (int y = 0; y < this->numControlsY; ++y) 682 | { 683 | auto i = (x * this->numControlsY + y); 684 | flippedPoints.push_back(this->controlPoints[i]); 685 | } 686 | } 687 | this->controlPoints = flippedPoints; 688 | this->dirty = true; 689 | 690 | // Find new closest control point. 691 | float distance; 692 | this->selectedIndex = this->findClosestControlPoint(glm::vec2(ofGetMouseX(), ofGetMouseY()), &distance); 693 | } 694 | 695 | //-------------------------------------------------------------- 696 | void WarpBilinear::flipVertical() 697 | { 698 | std::vector flippedPoints; 699 | for (int x = 0; x < this->numControlsX; ++x) 700 | { 701 | for (int y = this->numControlsY - 1; y >= 0; --y) 702 | { 703 | auto i = (x * this->numControlsY + y); 704 | flippedPoints.push_back(this->controlPoints[i]); 705 | } 706 | } 707 | this->controlPoints = flippedPoints; 708 | this->dirty = true; 709 | 710 | // Find new closest control point. 711 | float distance; 712 | this->selectedIndex = this->findClosestControlPoint(glm::vec2(ofGetMouseX(), ofGetMouseY()), &distance); 713 | } 714 | } 715 | --------------------------------------------------------------------------------