From 4ebcc3f94dfe21e4c8b4c8534af3fe7a8fee46d1 Mon Sep 17 00:00:00 2001 From: Jose Llamas Date: Mon, 17 Aug 2026 10:03:43 -0700 Subject: [PATCH 01/15] CI: upload gcc runtime DLLs as artifact; create-release downloads them --- .github/workflows/build-cache.yml | 6 +++++ scripts/create-release.py | 43 +++++++++++++++++++++++++++++++ 2 files changed, 49 insertions(+) diff --git a/.github/workflows/build-cache.yml b/.github/workflows/build-cache.yml index 8603c7b..e947de0 100644 --- a/.github/workflows/build-cache.yml +++ b/.github/workflows/build-cache.yml @@ -71,6 +71,12 @@ jobs: cp /mingw64/bin/libstdc++-6.dll libs/windows-x64/ cp /mingw64/bin/libwinpthread-1.dll libs/windows-x64/ cp /mingw64/bin/libssp-0.dll libs/windows-x64/ || true + - name: Upload gcc runtime DLLs + uses: actions/upload-artifact@v4 + with: + name: windows-runtime-dlls + path: libs/windows-x64/*.dll + retention-days: 90 - name: Build cache shell: msys2 {0} run: | diff --git a/scripts/create-release.py b/scripts/create-release.py index 308b56b..3bd9f79 100755 --- a/scripts/create-release.py +++ b/scripts/create-release.py @@ -79,6 +79,27 @@ def bump_version_files(tag): out.writelines(new_lines) print(f"Updated {path}: {pretty_key}={tag}, version bumped by 1") +def download_windows_dlls(): + """Download bundled Windows runtime DLLs from latest CI run.""" + import subprocess, json + repo = "processing-cpp/processing.cpp" + r = subprocess.run( + ["gh", "run", "list", "--repo", repo, "--workflow", "build-cache.yml", + "--status", "success", "--limit", "1", "--json", "databaseId"], + capture_output=True, text=True) + if r.returncode != 0: return + run_id = json.loads(r.stdout)[0]["databaseId"] + dest = Path(__file__).parent.parent / "libs" / "windows-x64" + dest.mkdir(parents=True, exist_ok=True) + result = subprocess.run( + ["gh", "run", "download", str(run_id), "--repo", repo, + "--name", "windows-runtime-dlls", "--dir", str(dest)], + capture_output=True, text=True) + if result.returncode == 0: + print(f" windows runtime DLLs downloaded to {dest}") + else: + print(f" WARNING: could not download windows runtime DLLs") + def trigger_and_wait_cache_build(): """Trigger a new build-cache CI run and wait for it to complete.""" import subprocess, json, time @@ -151,6 +172,27 @@ def download_cache_artifacts(): print(f" {platform}: {files}") +def download_windows_dlls(): + """Download bundled Windows runtime DLLs from latest CI run.""" + import subprocess, json + repo = "processing-cpp/processing.cpp" + r = subprocess.run( + ["gh", "run", "list", "--repo", repo, "--workflow", "build-cache.yml", + "--status", "success", "--limit", "1", "--json", "databaseId"], + capture_output=True, text=True) + if r.returncode != 0: return + run_id = json.loads(r.stdout)[0]["databaseId"] + dest = Path(__file__).parent.parent / "libs" / "windows-x64" + dest.mkdir(parents=True, exist_ok=True) + result = subprocess.run( + ["gh", "run", "download", str(run_id), "--repo", repo, + "--name", "windows-runtime-dlls", "--dir", str(dest)], + capture_output=True, text=True) + if result.returncode == 0: + print(f" windows runtime DLLs downloaded to {dest}") + else: + print(f" WARNING: could not download windows runtime DLLs") + def trigger_and_wait_cache_build(): """Trigger a new build-cache CI run and wait for it to complete.""" import subprocess, json, time @@ -189,6 +231,7 @@ def main(): bump_version_files(version) trigger_and_wait_cache_build() download_cache_artifacts() + download_windows_dlls() zip_name = "CppMode.zip" zip_path = os.path.join(OUTPUT_DIR, zip_name) From 6a83985884e67b4f0b1a7406d9c0b86a31bb79ff Mon Sep 17 00:00:00 2001 From: Jose Llamas Date: Mon, 17 Aug 2026 19:47:47 -0700 Subject: [PATCH 02/15] Fix download_windows_dlls: handle gh subdirectory, copy with rglob --- scripts/create-release.py | 51 +++++++++++++++++++++++++-------------- 1 file changed, 33 insertions(+), 18 deletions(-) diff --git a/scripts/create-release.py b/scripts/create-release.py index 3bd9f79..0d13e94 100755 --- a/scripts/create-release.py +++ b/scripts/create-release.py @@ -81,7 +81,8 @@ def bump_version_files(tag): def download_windows_dlls(): """Download bundled Windows runtime DLLs from latest CI run.""" - import subprocess, json + import subprocess, json, shutil + from pathlib import Path repo = "processing-cpp/processing.cpp" r = subprocess.run( ["gh", "run", "list", "--repo", repo, "--workflow", "build-cache.yml", @@ -91,14 +92,20 @@ def download_windows_dlls(): run_id = json.loads(r.stdout)[0]["databaseId"] dest = Path(__file__).parent.parent / "libs" / "windows-x64" dest.mkdir(parents=True, exist_ok=True) - result = subprocess.run( - ["gh", "run", "download", str(run_id), "--repo", repo, - "--name", "windows-runtime-dlls", "--dir", str(dest)], - capture_output=True, text=True) - if result.returncode == 0: - print(f" windows runtime DLLs downloaded to {dest}") - else: - print(f" WARNING: could not download windows runtime DLLs") + # Download to a temp dir first since gh may create subdirectory + import tempfile + with tempfile.TemporaryDirectory() as tmp: + result = subprocess.run( + ["gh", "run", "download", str(run_id), "--repo", repo, + "--name", "windows-runtime-dlls", "--dir", tmp], + capture_output=True, text=True) + if result.returncode != 0: + print(f" WARNING: could not download windows runtime DLLs: {result.stderr}") + return + # Copy all DLLs to dest + for dll in Path(tmp).rglob("*.dll"): + shutil.copy2(dll, dest / dll.name) + print(f" copied {dll.name} to libs/windows-x64/") def trigger_and_wait_cache_build(): """Trigger a new build-cache CI run and wait for it to complete.""" @@ -137,6 +144,7 @@ def download_cache_artifacts(): """Download precompiled .o files from latest successful build-cache CI run.""" import subprocess, json from pathlib import Path + from pathlib import Path repo = "processing-cpp/processing.cpp" root = Path(__file__).parent.parent platforms = ["linux-x64", "windows-x64", "macos-arm64", "macos-x64"] @@ -174,7 +182,8 @@ def download_cache_artifacts(): def download_windows_dlls(): """Download bundled Windows runtime DLLs from latest CI run.""" - import subprocess, json + import subprocess, json, shutil + from pathlib import Path repo = "processing-cpp/processing.cpp" r = subprocess.run( ["gh", "run", "list", "--repo", repo, "--workflow", "build-cache.yml", @@ -184,14 +193,20 @@ def download_windows_dlls(): run_id = json.loads(r.stdout)[0]["databaseId"] dest = Path(__file__).parent.parent / "libs" / "windows-x64" dest.mkdir(parents=True, exist_ok=True) - result = subprocess.run( - ["gh", "run", "download", str(run_id), "--repo", repo, - "--name", "windows-runtime-dlls", "--dir", str(dest)], - capture_output=True, text=True) - if result.returncode == 0: - print(f" windows runtime DLLs downloaded to {dest}") - else: - print(f" WARNING: could not download windows runtime DLLs") + # Download to a temp dir first since gh may create subdirectory + import tempfile + with tempfile.TemporaryDirectory() as tmp: + result = subprocess.run( + ["gh", "run", "download", str(run_id), "--repo", repo, + "--name", "windows-runtime-dlls", "--dir", tmp], + capture_output=True, text=True) + if result.returncode != 0: + print(f" WARNING: could not download windows runtime DLLs: {result.stderr}") + return + # Copy all DLLs to dest + for dll in Path(tmp).rglob("*.dll"): + shutil.copy2(dll, dest / dll.name) + print(f" copied {dll.name} to libs/windows-x64/") def trigger_and_wait_cache_build(): """Trigger a new build-cache CI run and wait for it to complete.""" From 8cee9a2f9d47682dc9b05f61ccc90aa2c4115ee0 Mon Sep 17 00:00:00 2001 From: Jose Llamas Date: Tue, 18 Aug 2026 11:54:09 -0700 Subject: [PATCH 03/15] Processing_api.h: use ::std::string to fix Apple clang namespace conflict --- src/Processing_api.h | 35 +++++++++++++++++++++++++++-------- 1 file changed, 27 insertions(+), 8 deletions(-) diff --git a/src/Processing_api.h b/src/Processing_api.h index 744e4fc..4eeb90e 100644 --- a/src/Processing_api.h +++ b/src/Processing_api.h @@ -101,18 +101,18 @@ inline void camera() { if(::Processing::PApple inline void camera(float ex,float ey,float ez,float cx,float cy,float cz,float ux,float uy,float uz){ if(::Processing::PApplet::g_papplet) ::Processing::PApplet::g_papplet->camera(ex,ey,ez,cx,cy,cz,ux,uy,uz); } inline void perspective() { if(::Processing::PApplet::g_papplet) ::Processing::PApplet::g_papplet->perspective(); } inline void ortho() { if(::Processing::PApplet::g_papplet) ::Processing::PApplet::g_papplet->ortho(); } -inline void text(const std::string& s,float x,float y) { if(::Processing::PApplet::g_papplet) ::Processing::PApplet::g_papplet->text(s,x,y); } +inline void text(const ::std::string& s,float x,float y) { if(::Processing::PApplet::g_papplet) ::Processing::PApplet::g_papplet->text(s,x,y); } inline void text(int v,float x,float y) { if(::Processing::PApplet::g_papplet) ::Processing::PApplet::g_papplet->text(v,x,y); } inline void text(float v,float x,float y) { if(::Processing::PApplet::g_papplet) ::Processing::PApplet::g_papplet->text(v,x,y); } inline void textSize(float s) { if(::Processing::PApplet::g_papplet) ::Processing::PApplet::g_papplet->textSize(s); } inline void textAlign(int a,int b=-1) { if(::Processing::PApplet::g_papplet) ::Processing::PApplet::g_papplet->textAlign(a,b); } inline void textLeading(float l) { if(::Processing::PApplet::g_papplet) ::Processing::PApplet::g_papplet->textLeading(l); } -inline float textWidth(const std::string& s) { return ::Processing::PApplet::g_papplet ? ::Processing::PApplet::g_papplet->textWidth(s) : 0; } +inline float textWidth(const ::std::string& s) { return ::Processing::PApplet::g_papplet ? ::Processing::PApplet::g_papplet->textWidth(s) : 0; } inline void loadPixels() { if(::Processing::PApplet::g_papplet) ::Processing::PApplet::g_papplet->loadPixels(); } inline void updatePixels() { if(::Processing::PApplet::g_papplet) ::Processing::PApplet::g_papplet->updatePixels(); } -inline void save(const std::string& f="") { if(::Processing::PApplet::g_papplet) ::Processing::PApplet::g_papplet->save(f); } -inline void saveFrame(const std::string& f="frame-####.png"){ if(::Processing::PApplet::g_papplet) ::Processing::PApplet::g_papplet->saveFrame(f); } -inline PImage* loadImage(const std::string& p) { return ::Processing::PApplet::g_papplet ? ::Processing::PApplet::g_papplet->loadImage(p) : nullptr; } +inline void save(const ::std::string& f="") { if(::Processing::PApplet::g_papplet) ::Processing::PApplet::g_papplet->save(f); } +inline void saveFrame(const ::std::string& f="frame-####.png"){ if(::Processing::PApplet::g_papplet) ::Processing::PApplet::g_papplet->saveFrame(f); } +inline PImage* loadImage(const ::std::string& p) { return ::Processing::PApplet::g_papplet ? ::Processing::PApplet::g_papplet->loadImage(p) : nullptr; } inline PImage* createImage(int w,int h,int m=1) { return ::Processing::PApplet::g_papplet ? ::Processing::PApplet::g_papplet->createImage(w,h,m) : nullptr; } inline PGraphics* createGraphics(int w,int h) { return ::Processing::PApplet::g_papplet ? ::Processing::PApplet::g_papplet->createGraphics(w,h) : nullptr; } inline void image(PImage* img,float x,float y) { if(::Processing::PApplet::g_papplet) ::Processing::PApplet::g_papplet->image(img,x,y); } @@ -160,8 +160,8 @@ inline void textureMode(int m) { if(::Processing::PApple inline void textureWrap(int m) { if(::Processing::PApplet::g_papplet) ::Processing::PApplet::g_papplet->textureWrap(m); } inline void clip(float x,float y,float w,float h) { if(::Processing::PApplet::g_papplet) ::Processing::PApplet::g_papplet->clip(x,y,w,h); } inline void noClip() { if(::Processing::PApplet::g_papplet) ::Processing::PApplet::g_papplet->noClip(); } -inline void windowTitle(const std::string& t) { if(::Processing::PApplet::g_papplet) ::Processing::PApplet::g_papplet->windowTitle(t); } -inline PFont loadFont(const std::string& f) { static PFont d; return ::Processing::PApplet::g_papplet ? ::Processing::PApplet::g_papplet->loadFont(f) : d; } +inline void windowTitle(const ::std::string& t) { if(::Processing::PApplet::g_papplet) ::Processing::PApplet::g_papplet->windowTitle(t); } +inline PFont loadFont(const ::std::string& f) { static PFont d; return ::Processing::PApplet::g_papplet ? ::Processing::PApplet::g_papplet->loadFont(f) : d; } inline void textFont(const PFont& f) { if(::Processing::PApplet::g_papplet) ::Processing::PApplet::g_papplet->textFont(f); } inline void textFont(const PFont& f,float s) { if(::Processing::PApplet::g_papplet) ::Processing::PApplet::g_papplet->textFont(f,s); } inline void set(int x,int y,color c) { if(::Processing::PApplet::g_papplet) ::Processing::PApplet::g_papplet->set(x,y,c); } @@ -183,4 +183,23 @@ inline void exit_sketch() { if(::Processing::PApple // // For now we define them since most sketches need width/height as globals. // PImage members can be accessed via the struct directly: (*img).height -// or by temporarily undefining: #undef height ... \ No newline at end of file +// or by temporarily undefining: #undef height ... +// ── Environment variable accessors ─────────────────────────────────────────── +// These let user-defined classes (not inheriting _PSketch) access Processing +// environment variables. Inside _PSketch, member variables shadow these. +// Outside (hoisted classes), these resolve via g_papplet singleton. +inline int width() { return ::Processing::PApplet::g_papplet ? ::Processing::PApplet::g_papplet->logicalW : 0; } +inline int height() { return ::Processing::PApplet::g_papplet ? ::Processing::PApplet::g_papplet->logicalH : 0; } +inline float mouseX() { return ::Processing::PApplet::g_papplet ? ::Processing::PApplet::g_papplet->mouseX : 0.f; } +inline float mouseY() { return ::Processing::PApplet::g_papplet ? ::Processing::PApplet::g_papplet->mouseY : 0.f; } +inline float pmouseX() { return ::Processing::PApplet::g_papplet ? ::Processing::PApplet::g_papplet->pmouseX : 0.f; } +inline float pmouseY() { return ::Processing::PApplet::g_papplet ? ::Processing::PApplet::g_papplet->pmouseY : 0.f; } +inline int frameCount() { return ::Processing::PApplet::g_papplet ? ::Processing::PApplet::g_papplet->frameCount : 0; } +inline char key() { return ::Processing::PApplet::g_papplet ? (char)::Processing::PApplet::g_papplet->key : 0; } +inline int keyCode() { return ::Processing::PApplet::g_papplet ? ::Processing::PApplet::g_papplet->keyCode : 0; } +inline int mouseButton() { return ::Processing::PApplet::g_papplet ? ::Processing::PApplet::g_papplet->mouseButton : 0; } +inline bool mousePressed() { return ::Processing::PApplet::g_papplet ? ::Processing::PApplet::g_papplet->_mousePressed : false; } +inline bool keyPressed() { return ::Processing::PApplet::g_papplet ? ::Processing::PApplet::g_papplet->_keyPressed : false; } +inline float frameRate() { return ::Processing::PApplet::g_papplet ? ::Processing::PApplet::g_papplet->_frameRate : 0.f; } +inline float mouseDX() { return ::Processing::PApplet::g_papplet ? ::Processing::PApplet::g_papplet->mouseDX : 0.f; } +inline float mouseDY() { return ::Processing::PApplet::g_papplet ? ::Processing::PApplet::g_papplet->mouseDY : 0.f; } From 71d0e21fc76f0687a703e236fdfddbe04c8dc1ad Mon Sep 17 00:00:00 2001 From: Jose Llamas Date: Tue, 18 Aug 2026 12:34:42 -0700 Subject: [PATCH 04/15] Fix Processing Java constants: TOP/BOTTOM/BASELINE, blend modes, filter values, stroke values; add textAlign vertical support --- src/Processing.cpp | 4 +++- src/Processing.h | 55 +++++++++++++++++++++++++++++----------------- 2 files changed, 38 insertions(+), 21 deletions(-) diff --git a/src/Processing.cpp b/src/Processing.cpp index c9116d0..46cbf51 100644 --- a/src/Processing.cpp +++ b/src/Processing.cpp @@ -2423,7 +2423,9 @@ void PApplet::textAlign(int alignX, int alignY) { if(a==37||a==20) return LEFT_ALIGN; // LEFT or LEFT_ALIGN if(a==39||a==21) return RIGHT_ALIGN; // RIGHT or RIGHT_ALIGN if(a==3 ||a==25) return CENTER_ALIGN; // CENTER or CENTER_ALIGN - if(a==0) return BASELINE; + if(a==101||a==22) return TOP_ALIGN; // TOP or TOP_ALIGN + if(a==102||a==23) return BOTTOM_ALIGN;// BOTTOM or BOTTOM_ALIGN + if(a==0) return BASELINE; // BASELINE return a; }; g_textAlignX = mapAlign(alignX); diff --git a/src/Processing.h b/src/Processing.h index 69ab0e2..8692f47 100644 --- a/src/Processing.h +++ b/src/Processing.h @@ -524,12 +524,12 @@ void tint(const PColor& c); static constexpr int THRESHOLD = 1; static constexpr int GRAY = 2; -static constexpr int OPAQUE = 3; +static constexpr int OPAQUE = 14; static constexpr int INVERT = 4; -static constexpr int POSTERIZE = 5; -static constexpr int BLUR = 6; -static constexpr int ERODE = 7; -static constexpr int DILATE = 8; +static constexpr int POSTERIZE = 8; +static constexpr int BLUR = 11; +static constexpr int ERODE = 15; +static constexpr int DILATE = 16; // ============================================================================= // PIMAGE -- Pixel buffer backed by an OpenGL texture @@ -1180,7 +1180,7 @@ static constexpr int CENTER = 3; // middle mouse button; also rectMode/el // Color modes static constexpr int RGB = 1; -static constexpr int HSB = 2; +static constexpr int HSB = 3; #define ARGB 3 /* createImage(w,h,ARGB) */ // Shape / rect / ellipse modes @@ -1190,10 +1190,10 @@ static constexpr int RADIUS = 2; // Stroke caps and joins static constexpr int ROUND = 10; -static constexpr int SQUARE = 11; -static constexpr int PROJECT = 12; -static constexpr int MITER = 13; -static constexpr int BEVEL = 14; +static constexpr int SQUARE = 1; +static constexpr int PROJECT = 4; +static constexpr int MITER = 8; +static constexpr int BEVEL = 32; // beginShape() kinds static constexpr int POINTS = 0; @@ -1206,23 +1206,38 @@ static constexpr int QUAD_STRIP = 6; static constexpr int CLOSE = 7; // Text alignment +// Text alignment internal constants static constexpr int LEFT_ALIGN = 20; static constexpr int RIGHT_ALIGN = 21; static constexpr int TOP_ALIGN = 22; static constexpr int BOTTOM_ALIGN = 23; -static constexpr int BASELINE = 24; +static constexpr int BASELINE = 0; // Processing Java value static constexpr int CENTER_ALIGN = 25; +// Processing Java textAlign vertical aliases +static constexpr int TOP = 101; +static constexpr int BOTTOM = 102; // Blend modes -static constexpr int BLEND = 30; -static constexpr int ADD = 31; -static constexpr int SUBTRACT = 32; -static constexpr int MULTIPLY = 33; -static constexpr int SCREEN = 34; -static constexpr int DARKEST = 35; -static constexpr int LIGHTEST = 36; -static constexpr int DIFFERENCE = 37; -static constexpr int EXCLUSION = 38; +static constexpr int BLEND = 1; +static constexpr int ADD = 2; +static constexpr int SUBTRACT = 4; +static constexpr int MULTIPLY = 128; +static constexpr int SCREEN = 256; +static constexpr int DARKEST = 8; +static constexpr int LIGHTEST = 16; +static constexpr int DIFFERENCE = 32; +static constexpr int EXCLUSION = 64; +static constexpr int OVERLAY = 512; +static constexpr int HARD_LIGHT = 1024; +static constexpr int SOFT_LIGHT = 2048; +static constexpr int DODGE = 4096; +static constexpr int BURN = 8192; +static constexpr int REPLACE = 0; +// Boolean aliases +#ifndef TRUE +#define TRUE true +#define FALSE false +#endif // Math constants (float precision) static constexpr float PI = static_cast(M_PI); From ed594f549eeb022aec66ec25ffe8a4ecb0359b28 Mon Sep 17 00:00:00 2001 From: Jose Llamas Date: Tue, 18 Aug 2026 12:47:00 -0700 Subject: [PATCH 05/15] textAlign: implement vertical alignment (TOP/BOTTOM/CENTER/BASELINE) --- src/Processing.cpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/Processing.cpp b/src/Processing.cpp index 46cbf51..53192a6 100644 --- a/src/Processing.cpp +++ b/src/Processing.cpp @@ -2292,13 +2292,17 @@ void PApplet::renderText(const ::std::string& msg, float x, float y) { for (char c : msg) { if(c=='\n'){ls.push_back(cur);cur.clear();}else cur+=c; } ls.push_back(cur); + float totalH = (float)ls.size() * leading; + float yOff = 0; + if (g_textAlignY == TOP_ALIGN) yOff = g_textSize; + else if (g_textAlignY == CENTER_ALIGN) yOff = g_textSize * 0.5f - totalH * 0.5f + leading * 0.5f; + else if (g_textAlignY == BOTTOM_ALIGN) yOff = -totalH + leading; for (int li = 0; li < (int)ls.size(); li++) { float lw = getLineWidth(ls[li]); float dx = x; if (g_textAlignX == RIGHT_ALIGN) dx = x - lw; else if (g_textAlignX == CENTER_ALIGN) dx = x - lw * 0.5f; - - float dy = y + li * leading; + float dy = y + yOff + li * leading; #if PROCESSING_HAS_STB_TRUETYPE if (g_ttf.loaded) { drawTTFStr(dx, dy, ls[li]); continue; } From 13d401ee60cec235a720e9baf2b784192f956370 Mon Sep 17 00:00:00 2001 From: Jose Llamas Date: Tue, 18 Aug 2026 13:07:54 -0700 Subject: [PATCH 06/15] blendMode: add BLEND/DARKEST/LIGHTEST/DIFFERENCE/REPLACE modes --- src/Processing.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/Processing.cpp b/src/Processing.cpp index 53192a6..afe66a8 100644 --- a/src/Processing.cpp +++ b/src/Processing.cpp @@ -2879,13 +2879,21 @@ void PApplet::blendMode(int mode) { // Reset equation first (SUBTRACT changes it) glBlendEquation(GL_FUNC_ADD); switch (mode) { + case BLEND: glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); break; case ADD: glBlendFunc(GL_SRC_ALPHA, GL_ONE); break; case MULTIPLY: glBlendFunc(GL_DST_COLOR, GL_ZERO); break; case SCREEN: glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_COLOR); break; + case DARKEST: glBlendFunc(GL_ONE, GL_ONE); glBlendEquation(GL_MIN); break; + case LIGHTEST: glBlendFunc(GL_ONE, GL_ONE); glBlendEquation(GL_MAX); break; + case DIFFERENCE: + glBlendEquation(GL_FUNC_SUBTRACT); + glBlendFunc(GL_ONE, GL_ONE); + break; case SUBTRACT: glBlendEquation(GL_FUNC_REVERSE_SUBTRACT); glBlendFunc(GL_SRC_ALPHA, GL_ONE); break; + case REPLACE: glBlendFunc(GL_ONE, GL_ZERO); break; default: glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); break; } } From 6156a2b1b9c6f2a81b58828e2ab69ff38b83b740 Mon Sep 17 00:00:00 2001 From: Jose Llamas Date: Tue, 18 Aug 2026 13:10:34 -0700 Subject: [PATCH 07/15] filter(): implement BLUR, OPAQUE, ERODE, DILATE; fix THRESHOLD param --- src/Processing.cpp | 95 +++++++++++++++++++--------------------------- 1 file changed, 38 insertions(+), 57 deletions(-) diff --git a/src/Processing.cpp b/src/Processing.cpp index afe66a8..f411b89 100644 --- a/src/Processing.cpp +++ b/src/Processing.cpp @@ -2735,67 +2735,48 @@ void PApplet::tint(float r, float g, float b, float a) { void PApplet::noTint(){doTint=false;} void PApplet::filter(int mode) { filter(mode, 0.5f); } -void PApplet::filter(int mode, float /*param*/) { - int total = winWidth * winHeight; +void PApplet::filter(int mode, float param) { + int w = winWidth, h = winHeight, total = w * h; ::std::vector buf(total * 4); - glReadPixels(0, 0, winWidth, winHeight, GL_RGBA, GL_UNSIGNED_BYTE, buf.data()); - - for (int i = 0; i < total; i++) { - int r = buf[i*4], g = buf[i*4+1], b = buf[i*4+2]; - int grey = (r + g + b) / 3; - - if (mode == GRAY) { - buf[i*4] = buf[i*4+1] = buf[i*4+2] = (unsigned char)grey; - } else if (mode == INVERT) { - buf[i*4] = 255 - r; - buf[i*4+1] = 255 - g; - buf[i*4+2] = 255 - b; - } else if (mode == THRESHOLD) { - unsigned char t = (grey > 127) ? 255 : 0; - buf[i*4] = buf[i*4+1] = buf[i*4+2] = t; + glReadPixels(0, 0, w, h, GL_RGBA, GL_UNSIGNED_BYTE, buf.data()); + if (mode == GRAY || mode == INVERT || mode == THRESHOLD || mode == OPAQUE) { + for (int i = 0; i < total; i++) { + int r=buf[i*4],g=buf[i*4+1],b=buf[i*4+2]; + int grey=(r+g+b)/3; + if (mode==GRAY) { buf[i*4]=buf[i*4+1]=buf[i*4+2]=(unsigned char)grey; } + else if (mode==INVERT) { buf[i*4]=255-r; buf[i*4+1]=255-g; buf[i*4+2]=255-b; } + else if (mode==THRESHOLD) { unsigned char t=(grey>param*255)?255:0; buf[i*4]=buf[i*4+1]=buf[i*4+2]=t; } + else if (mode==OPAQUE) { buf[i*4+3]=255; } } - } -#ifndef __EMSCRIPTEN__ - glDrawPixels(winWidth, winHeight, GL_RGBA, GL_UNSIGNED_BYTE, buf.data()); -#else - // WASM: upload pixels via texture blit - GLuint tex; glGenTextures(1,&tex); - glBindTexture(GL_TEXTURE_2D,tex); - glTexImage2D(GL_TEXTURE_2D,0,GL_RGBA,winWidth,winHeight,0,GL_RGBA,GL_UNSIGNED_BYTE,buf.data()); - glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_NEAREST); - glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_NEAREST); - glEnable(GL_TEXTURE_2D); glBindTexture(GL_TEXTURE_2D,tex); - glColor4f(1,1,1,1); - glBegin(GL_QUADS); - glTexCoord2f(0,1);glVertex2f(0,0); - glTexCoord2f(1,1);glVertex2f(winWidth,0); - glTexCoord2f(1,0);glVertex2f(winWidth,winHeight); - glTexCoord2f(0,0);glVertex2f(0,winHeight); - glEnd(); - glDisable(GL_TEXTURE_2D); glDeleteTextures(1,&tex); -#endif -} -void PApplet::loadPixels() { - int total = winWidth * winHeight; - pixels.resize(total); - - ::std::vector rgba(total * 4); - glReadPixels(0, 0, winWidth, winHeight, GL_RGBA, GL_UNSIGNED_BYTE, rgba.data()); - - // glReadPixels returns rows bottom-to-top; Processing's pixels[] is - // top-to-bottom (row 0 = top of window), so flip rows on the way in. - for (int y = 0; y < winHeight; y++) { - for (int x = 0; x < winWidth; x++) { - int di = y * winWidth + x; - int si = (winHeight - 1 - y) * winWidth + x; - unsigned char r = rgba[si*4 + 0]; - unsigned char g = rgba[si*4 + 1]; - unsigned char b = rgba[si*4 + 2]; - unsigned char a = rgba[si*4 + 3]; - pixels[di] = (a << 24) | (r << 16) | (g << 8) | b; // ARGB format + } else if (mode == BLUR) { + int radius = ::std::max(1,(int)param); + ::std::vector tmp(buf); + for (int y=0;y=w||ny<0||ny>=h) continue; + int j=(ny*w+nx)*4; + sr+=tmp[j]; sg+=tmp[j+1]; sb+=tmp[j+2]; sa+=tmp[j+3]; cnt++; + } + int i=(y*w+x2)*4; + buf[i]=sr/cnt; buf[i+1]=sg/cnt; buf[i+2]=sb/cnt; buf[i+3]=sa/cnt; + } + } else if (mode == ERODE || mode == DILATE) { + ::std::vector tmp(buf); + for (int y=0;y=w||ny<0||ny>=h) continue; + int j=(ny*w+nx)*4; + int lum=(tmp[j]+tmp[j+1]+tmp[j+2])/3; + if(best<0||(mode==ERODE?lumbest)) best=lum; + } + int i=(y*w+x2)*4; + buf[i]=buf[i+1]=buf[i+2]=(unsigned char)best; } } -} void PApplet::updatePixels() { int total = winWidth * winHeight; From 90adec8a81ddeef9a92ecd639545bccf53c328e9 Mon Sep 17 00:00:00 2001 From: Jose Llamas Date: Tue, 18 Aug 2026 13:11:43 -0700 Subject: [PATCH 08/15] filter(): fix missing closing brace and glDrawPixels call --- src/Processing.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Processing.cpp b/src/Processing.cpp index f411b89..04c83a4 100644 --- a/src/Processing.cpp +++ b/src/Processing.cpp @@ -2777,7 +2777,10 @@ void PApplet::filter(int mode, float param) { buf[i]=buf[i+1]=buf[i+2]=(unsigned char)best; } } - +#ifndef __EMSCRIPTEN__ + glDrawPixels(w, h, GL_RGBA, GL_UNSIGNED_BYTE, buf.data()); +#endif +} void PApplet::updatePixels() { int total = winWidth * winHeight; ::std::vector rgba(total * 4); From 0e8a8019583cade5532286c9118b6a25f103fb82 Mon Sep 17 00:00:00 2001 From: Jose Llamas Date: Tue, 18 Aug 2026 13:12:43 -0700 Subject: [PATCH 09/15] filter(): implement POSTERIZE --- src/Processing.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/Processing.cpp b/src/Processing.cpp index 04c83a4..31d5163 100644 --- a/src/Processing.cpp +++ b/src/Processing.cpp @@ -2747,6 +2747,11 @@ void PApplet::filter(int mode, float param) { else if (mode==INVERT) { buf[i*4]=255-r; buf[i*4+1]=255-g; buf[i*4+2]=255-b; } else if (mode==THRESHOLD) { unsigned char t=(grey>param*255)?255:0; buf[i*4]=buf[i*4+1]=buf[i*4+2]=t; } else if (mode==OPAQUE) { buf[i*4+3]=255; } + else if (mode==POSTERIZE) { + int levels = ::std::max(2,(int)param); + auto post = [levels](int v){ return (int)((int)(v*(levels-1)/255.0f+0.5f)*255/(levels-1)); }; + buf[i*4]=post(r); buf[i*4+1]=post(g); buf[i*4+2]=post(b); + } } } else if (mode == BLUR) { int radius = ::std::max(1,(int)param); From 8f4419dee8e9fd2a5ae66956588116e40b6cb8b4 Mon Sep 17 00:00:00 2001 From: Jose Llamas Date: Tue, 18 Aug 2026 13:15:14 -0700 Subject: [PATCH 10/15] Add image(img,dx1,dy1,dx2,dy2,sx1,sy1,sx2,sy2) nine-argument form for sprite sheet cropping --- src/Processing.cpp | 21 +++++++++++++++++++++ src/Processing.h | 1 + 2 files changed, 22 insertions(+) diff --git a/src/Processing.cpp b/src/Processing.cpp index 31d5163..525f7d9 100644 --- a/src/Processing.cpp +++ b/src/Processing.cpp @@ -2715,6 +2715,27 @@ void PApplet::image(PImage* img, float x, float y, float w, float h) { if(!img || img->width==0 || img->height==0) return; drawImage_impl(img, x, y, w, h); } +// Nine-argument form: image(img, dx1,dy1,dx2,dy2, sx1,sy1,sx2,sy2) +void PApplet::image(PImage* img, float dx1,float dy1,float dx2,float dy2, + float sx1,float sy1,float sx2,float sy2) { + if(!img || img->width==0 || img->height==0) return; + if(img->dirty) img->uploadTexture(); + if(img->texID==0) return; + // Convert source pixel coords to UV [0,1] + float u1=sx1/img->width, v1=sy1/img->height; + float u2=sx2/img->width, v2=sy2/img->height; + glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA); + glEnable(GL_TEXTURE_2D); glBindTexture(GL_TEXTURE_2D,img->texID); + glColor4f(doTint?tintR:1.f,doTint?tintG:1.f,doTint?tintB:1.f,doTint?tintA:1.f); + glBegin(GL_QUADS); + glTexCoord2f(u1,v1); glVertex2f(dx1,dy1); + glTexCoord2f(u2,v1); glVertex2f(dx2,dy1); + glTexCoord2f(u2,v2); glVertex2f(dx2,dy2); + glTexCoord2f(u1,v2); glVertex2f(dx1,dy2); + glEnd(); + glBindTexture(GL_TEXTURE_2D,0); glDisable(GL_TEXTURE_2D); glDisable(GL_BLEND); + glColor4f(1.f,1.f,1.f,1.f); +} void PApplet::imageMode(int m){currentImageMode=m;} void PApplet::tint(float gray) {tint(gray, colorMaxA);} diff --git a/src/Processing.h b/src/Processing.h index 8692f47..0c64af8 100644 --- a/src/Processing.h +++ b/src/Processing.h @@ -973,6 +973,7 @@ class PGraphics : public PImage { void curveVertex(float x, float y); void image(PImage* img, float x, float y); void image(PImage* img, float x, float y, float w, float h); + void image(PImage* img, float dx1,float dy1,float dx2,float dy2,float sx1,float sy1,float sx2,float sy2); void tint(float gray); void tint(float gray, float a); void tint(float r, float g, float b, float a); From f134340e90a6ad5ccb9be180ce3cea2c16ff7d70 Mon Sep 17 00:00:00 2001 From: Jose Llamas Date: Tue, 18 Aug 2026 13:16:25 -0700 Subject: [PATCH 11/15] Fix: add nine-arg image() declaration to PApplet class --- src/Processing.h | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Processing.h b/src/Processing.h index 0c64af8..6f66dcb 100644 --- a/src/Processing.h +++ b/src/Processing.h @@ -4143,6 +4143,7 @@ struct PApplet { void imageMode(int mode); void image(PImage* img, float x, float y); void image(PImage* img, float x, float y, float w, float h); + void image(PImage* img, float dx1,float dy1,float dx2,float dy2,float sx1,float sy1,float sx2,float sy2); void image(const PImage& img, float x, float y); void image(const PImage& img, float x, float y, float w, float h); void image(const PImage* img, float x, float y) { if(img) image(*img,x,y); } From 06700efcaaac893d4e83125cc177b5306cdee23a Mon Sep 17 00:00:00 2001 From: Jose Llamas Date: Tue, 18 Aug 2026 13:22:23 -0700 Subject: [PATCH 12/15] Implement PApplet::loadPixels() -- was declared but missing definition --- src/Processing.cpp | 53 +++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 50 insertions(+), 3 deletions(-) diff --git a/src/Processing.cpp b/src/Processing.cpp index 525f7d9..a4cf0cb 100644 --- a/src/Processing.cpp +++ b/src/Processing.cpp @@ -1000,9 +1000,40 @@ void PApplet::arc(float cx, float cy, float w, float h, float startAngle, float drawEllipseGeom(cx, cy, rx, ry, startAngle, endAngle); } -void PApplet::arc(float cx, float cy, float w, float h, float startAngle, float endAngle, int /*mode*/) { - // mode = OPEN / CHORD / PIE -- basic implementation uses OPEN - arc(cx, cy, w, h, startAngle, endAngle); +void PApplet::arc(float cx, float cy, float w, float h, float startAngle, float endAngle, int mode) { + float rx = w, ry = h; + resolveEllipse(cx, cy, rx, ry); + int steps = 64; + float da = (endAngle - startAngle) / steps; + // Fill + if (doFill) { + applyFill(); + glBegin(GL_TRIANGLE_FAN); + if (mode == PIE) glVertex2f(cx, cy); // PIE: fan from center + else glVertex2f(cx + rx * ::std::cos(startAngle) * 0.5f, + cy + ry * ::std::sin(startAngle) * 0.5f); // CHORD/OPEN: fan from first point + for (int i = 0; i <= steps; i++) { + float a = startAngle + i * da; + glVertex2f(cx + rx * ::std::cos(a), cy + ry * ::std::sin(a)); + } + if (mode == PIE) glVertex2f(cx, cy); + glEnd(); + } + // Stroke + if (doStroke) { + applyStroke(); + glBegin(GL_LINE_STRIP); + if (mode == PIE) { glVertex2f(cx, cy); } + for (int i = 0; i <= steps; i++) { + float a = startAngle + i * da; + glVertex2f(cx + rx * ::std::cos(a), cy + ry * ::std::sin(a)); + } + if (mode == PIE) { glVertex2f(cx, cy); } + else if (mode == CHORD) { // close the chord line + glVertex2f(cx + rx * ::std::cos(startAngle), cy + ry * ::std::sin(startAngle)); + } + glEnd(); + } } void PApplet::rect(float x, float y, float w, float h) { resolveRect(x, y, w, h); @@ -2807,6 +2838,22 @@ void PApplet::filter(int mode, float param) { glDrawPixels(w, h, GL_RGBA, GL_UNSIGNED_BYTE, buf.data()); #endif } +void PApplet::loadPixels() { + // Copy framebuffer into pixels[] + int total = winWidth * winHeight; + pixels.resize(total); + ::std::vector buf(total * 4); + glReadPixels(0, 0, winWidth, winHeight, GL_RGBA, GL_UNSIGNED_BYTE, buf.data()); + // Convert RGBA + flip Y (GL is bottom-up, Processing is top-down) + for (int y = 0; y < winHeight; y++) { + for (int x = 0; x < winWidth; x++) { + int si = (winHeight - 1 - y) * winWidth + x; + int di = y * winWidth + x; + unsigned char r=buf[si*4],g=buf[si*4+1],b=buf[si*4+2],a=buf[si*4+3]; + pixels[di] = (a<<24)|(r<<16)|(g<<8)|b; + } + } +} void PApplet::updatePixels() { int total = winWidth * winHeight; ::std::vector rgba(total * 4); From fa45041cdb6dc98334c9593d0943b76cc132b19b Mon Sep 17 00:00:00 2001 From: Jose Llamas Date: Tue, 18 Aug 2026 13:22:56 -0700 Subject: [PATCH 13/15] Add OPEN/CHORD/PIE arc mode constants --- src/Processing.h | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/Processing.h b/src/Processing.h index 6f66dcb..3d350e8 100644 --- a/src/Processing.h +++ b/src/Processing.h @@ -1204,7 +1204,11 @@ static constexpr int TRIANGLE_FAN = 3; static constexpr int TRIANGLE_STRIP = 4; static constexpr int QUADS = 5; static constexpr int QUAD_STRIP = 6; -static constexpr int CLOSE = 7; +static constexpr int CLOSE = 2; +// Arc modes +static constexpr int OPEN = 1; +static constexpr int CHORD = 2; +static constexpr int PIE = 3; // Text alignment // Text alignment internal constants From 0fb5c91ceccb77b789429b3f35a8bb03e082a613 Mon Sep 17 00:00:00 2001 From: Jose Llamas Date: Tue, 18 Aug 2026 13:46:21 -0700 Subject: [PATCH 14/15] Add splitTokens(s) no-delimiter overload defaulting to whitespace --- src/Processing.h | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/Processing.h b/src/Processing.h index 3d350e8..5159fc9 100644 --- a/src/Processing.h +++ b/src/Processing.h @@ -1416,6 +1416,9 @@ inline ::std::vector<::std::string> split(const ::std::string& s, char d) { while (::std::getline(ss, t, d)) o.push_back(t); return o; } +inline ::std::vector<::std::string> splitTokens(const ::std::string& s) { + return splitTokens(s, " \t\n\r\f"); +} inline ::std::vector<::std::string> splitTokens(const ::std::string& s, const ::std::string& delims) { ::std::vector<::std::string> o; ::std::string cur; for (char c:s) { From 79766e609befad69f8ee2ec40bebe9899c6b803d Mon Sep 17 00:00:00 2001 From: Jose Llamas Date: Tue, 18 Aug 2026 13:47:41 -0700 Subject: [PATCH 15/15] Fix splitTokens no-delimiter overload declaration order --- src/Processing.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Processing.h b/src/Processing.h index 5159fc9..70a6708 100644 --- a/src/Processing.h +++ b/src/Processing.h @@ -1416,9 +1416,6 @@ inline ::std::vector<::std::string> split(const ::std::string& s, char d) { while (::std::getline(ss, t, d)) o.push_back(t); return o; } -inline ::std::vector<::std::string> splitTokens(const ::std::string& s) { - return splitTokens(s, " \t\n\r\f"); -} inline ::std::vector<::std::string> splitTokens(const ::std::string& s, const ::std::string& delims) { ::std::vector<::std::string> o; ::std::string cur; for (char c:s) { @@ -1428,6 +1425,9 @@ inline ::std::vector<::std::string> splitTokens(const ::std::string& s, const :: if (!cur.empty()) o.push_back(cur); return o; } +inline ::std::vector<::std::string> splitTokens(const ::std::string& s) { + return splitTokens(s, " \t\n\r\f"); +} inline ::std::string join(const ::std::vector<::std::string>& v, const ::std::string& sep) { ::std::string o; for (size_t i=0; i