-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathProcessing.h
More file actions
4641 lines (4178 loc) · 229 KB
/
Copy pathProcessing.h
File metadata and controls
4641 lines (4178 loc) · 229 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
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
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
#pragma once
#if __has_include("stb_truetype.h") && !defined(PROCESSING_HAS_STB_TRUETYPE)
# define PROCESSING_HAS_STB_TRUETYPE 1
#endif
#if PROCESSING_HAS_STB_TRUETYPE
// Include stb_truetype header-only (no implementation) for type definitions
# ifndef STB_TRUETYPE_IMPLEMENTATION
# include "stb_truetype.h"
# endif
#endif
#ifndef _WIN32
#include <dirent.h>
#endif
#include <functional>
// =============================================================================
// Processing.h -- processing-cpp API
// =============================================================================
// processing-cpp is a C++ creative coding framework inspired by Processing (Java).
// It exposes a familiar draw-loop API backed by OpenGL/GLFW/GLEW.
//
// HOW TO USE:
// 1. Include this header in your sketch file.
// 2. Inside namespace Processing { ... } define:
// void setup() { size(640,360); }
// void draw() { background(0); ellipse(mouseX,mouseY,40,40); }
// 3. Compile with Processing.cpp and link against GLFW + GLEW + OpenGL.
//
// FILE STRUCTURE:
// Processing.h -- This file. API declarations, inline helpers, classes.
// Processing.cpp -- Implementation of all declared functions.
// Platform.h -- OS abstraction (file dialogs, serial, process, sleep).
// IDE.cpp -- The processing-cpp IDE (sketch editor, build, run, terminal).
// =============================================================================
// ---------------------------------------------------------------------------
// Platform shim (must come first; provides termios/glob stubs on Windows)
// ---------------------------------------------------------------------------
#if __has_include("Platform.h")
# include "Platform.h"
#endif
// ---------------------------------------------------------------------------
// M_PI: Windows (MinGW) only defines this when _USE_MATH_DEFINES is set
// before including <cmath>. We also provide a fallback just in case.
// ---------------------------------------------------------------------------
#ifndef _USE_MATH_DEFINES
# define _USE_MATH_DEFINES
#endif
#include <cmath>
#ifndef M_PI
# define M_PI 3.14159265358979323846
#endif
// ---------------------------------------------------------------------------
// Standard library includes
// ---------------------------------------------------------------------------
#include <iostream>
#include <sstream>
#include <fstream>
#include <regex>
#include <cstdlib>
#include <climits>
#include <cstdarg>
#include <ctime>
#include <chrono>
#include <string>
#include <vector>
#include <thread>
#include <functional>
#include <algorithm>
#include <memory>
#include <map>
#include <set>
#include <random>
#include <stack>
#include <queue>
#include <list>
#include <deque>
#include <tuple>
#include <optional>
#include <variant>
#include <numeric>
#include <iterator>
#include <memory>
#include <regex>
#include <iomanip>
#include <unordered_map>
#include <unordered_set>
// ---------------------------------------------------------------------------
// OpenGL / GLFW
// ---------------------------------------------------------------------------
// Java-style string + number concatenation
inline std::string operator+(const std::string& s, int n) { return s + std::to_string(n); }
inline std::string operator+(const std::string& s, long n) { return s + std::to_string(n); }
inline std::string operator+(const std::string& s, size_t n) { return s + std::to_string(n); }
inline std::string operator+(const std::string& s, float n) { return s + std::to_string(n); }
inline std::string operator+(const std::string& s, double n) { return s + std::to_string(n); }
inline std::string operator+(const std::string& s, char c) { return s + std::string(1, c); }
inline std::string operator+(int n, const std::string& s) { return std::to_string(n) + s; }
inline std::string operator+(long n, const std::string& s) { return std::to_string(n) + s; }
inline std::string operator+(size_t n, const std::string& s) { return std::to_string(n) + s; }
inline std::string operator+(float n, const std::string& s) { return std::to_string(n) + s; }
inline std::string operator+(double n, const std::string& s) { return std::to_string(n) + s; }
inline std::string operator+(char c, const std::string& s) { return std::string(1, c) + s; }
#include <GL/glew.h>
#include <GLFW/glfw3.h>
// =============================================================================
// WINDOWS MACRO CLEANUP
// =============================================================================
// Root cause: on Windows/MSYS2/MinGW, <GL/glew.h> includes <windows.h> which
// pulls in <wingdi.h>. That header defines macros like OPAQUE, TRANSPARENT,
// DELETE, CLOSE, DIFFERENCE, BLEND, ADD, MULTIPLY, SCREEN, GRAY, INVERT, etc.
// as plain integer preprocessor macros. Later in this file we define Processing
// constants with those same names as "static constexpr int OPAQUE = 3;" -- but
// the macro fires first and turns that into "static constexpr int 2 = 3;" which
// is a syntax error ("expected unqualified-id before numeric constant").
//
// WIN32_LEAN_AND_MEAN doesn't help because GLEW needs wingdi.h for its own GL
// type definitions. The only correct fix is to #undef the offending macros
// after the includes that caused them, before our own definitions use the names.
// Each undef is inside #ifdef so it is a complete no-op on Linux and macOS.
#ifdef OPAQUE
# undef OPAQUE
#endif
#ifdef TRANSPARENT
# undef TRANSPARENT
#endif
#ifdef ALTERNATE
# undef ALTERNATE
#endif
#ifdef WINDING
# undef WINDING
#endif
#ifdef RELATIVE
# undef RELATIVE
#endif
#ifdef ABSOLUTE
# undef ABSOLUTE
#endif
#ifdef CLOSE
# undef CLOSE
#endif
#ifdef DELETE
# undef DELETE
#endif
#ifdef DIFFERENCE
# undef DIFFERENCE
#endif
#ifdef BLEND
# undef BLEND
#endif
#ifdef ADD
# undef ADD
#endif
#ifdef SUBTRACT
# undef SUBTRACT
#endif
#ifdef MULTIPLY
# undef MULTIPLY
#endif
#ifdef SCREEN
# undef SCREEN
#endif
#ifdef OVERLAY
# undef OVERLAY
#endif
#ifdef DARKEST
# undef DARKEST
#endif
#ifdef LIGHTEST
# undef LIGHTEST
#endif
#ifdef INVERT
# undef INVERT
#endif
#ifdef GRAY
# undef GRAY
#endif
#ifdef CROSS
# undef CROSS
#endif
#ifdef ARROW
# undef ARROW
#endif
#ifdef HAND
# undef HAND
#endif
#ifdef MOVE
# undef MOVE
#endif
#ifdef WAIT
# undef WAIT
#endif
#ifdef ERROR
# undef ERROR
#endif
#ifdef NEAR
# undef NEAR
#endif
#ifdef FAR
# undef FAR
#endif
// =============================================================================
// DEBUG OUTPUT -- toggle with -DPROCESSING_DEBUG at compile time
// =============================================================================
// Use PDEBUG(...) anywhere you'd normally reach for a raw fprintf(stderr,...)
// call while investigating something. It's a no-op (compiles to nothing,
// zero runtime cost) unless PROCESSING_DEBUG is defined, so debug prints
// can be left in the source permanently without ever reaching a normal
// build or a user's console -- no more hunting down and deleting stray
// fprintf calls by hand once an investigation is done.
//
// Usage (same argument style as fprintf, always include the trailing \n):
// PDEBUG("beginDraw: width=%d height=%d\n", width, height);
//
// To actually see the output during local debugging, rebuild with:
// g++ -DPROCESSING_DEBUG ... (alongside the other -D flags already used)
#ifndef PROCESSING_BUILD_STAMP
// Fallback for any compile that doesn't go through rebuild-engine.sh
// (e.g. the IDE's own per-sketch compile, which links the pre-built
// Processing.o but never defines this itself). Seeing "UNKNOWN" at
// runtime is itself a useful signal that something bypassed the
// normal engine-build script.
#define PROCESSING_BUILD_STAMP "UNKNOWN"
#endif
#ifndef PROCESSING_WEBSITE_URL
// Fallback only -- the REAL value always comes from
// config/cppmode.properties's website.base.url, read fresh by
// rebuild-engine.sh and passed in via -DPROCESSING_WEBSITE_URL at
// build time. Nothing in this source file ever hardcodes the actual
// URL string itself; this fallback only exists so a compile that
// bypasses the script entirely still produces a valid (if generic)
// message instead of a broken one.
#define PROCESSING_WEBSITE_URL "https://processing-cpp.github.io"
#endif
#ifdef PROCESSING_DEBUG
#define PDEBUG(...) fprintf(stderr, "[PDEBUG] " __VA_ARGS__)
#else
#define PDEBUG(...) do {} while (0)
#endif
namespace Processing {
// =============================================================================
// PVECTOR -- 2D/3D vector with all standard Processing operations
// =============================================================================
class PVector {
public:
float x, y, z;
// Constructors
PVector() : x(0), y(0), z(0) {}
// Accept any arithmetic type (int, float, double) to match Java's implicit
// widening -- eliminates narrowing-conversion warnings from expressions like
// PVector(width/2, height/2) where width/height are int.
template<typename A, typename B,
typename = std::enable_if_t<std::is_arithmetic_v<A> && std::is_arithmetic_v<B>>>
PVector(A x, B y) : x((float)x), y((float)y), z(0) {}
template<typename A, typename B, typename C,
typename = std::enable_if_t<std::is_arithmetic_v<A> && std::is_arithmetic_v<B> && std::is_arithmetic_v<C>>>
PVector(A x, B y, C z) : x((float)x), y((float)y), z((float)z) {}
// Setters
PVector& set(float _x, float _y, float _z=0) { x=_x; y=_y; z=_z; return *this; }
PVector& set(const PVector& v) { x=v.x; y=v.y; z=v.z; return *this; }
PVector copy() const { return PVector(x, y, z); }
// Magnitude
float mag() const { return std::sqrt(x*x + y*y + z*z); }
float magSq() const { return x*x + y*y + z*z; }
// Arithmetic (in-place)
PVector& add(float _x, float _y, float _z=0) { x+=_x; y+=_y; z+=_z; return *this; }
PVector& add(const PVector& v) { x+=v.x; y+=v.y; z+=v.z; return *this; }
PVector& sub(float _x, float _y, float _z=0) { x-=_x; y-=_y; z-=_z; return *this; }
PVector& sub(const PVector& v) { x-=v.x; y-=v.y; z-=v.z; return *this; }
PVector& mult(float s) { x*=s; y*=s; z*=s; return *this; }
PVector& div(float s) { x/=s; y/=s; z/=s; return *this; }
// Arithmetic (static, returns new vector)
static PVector add(const PVector& a, const PVector& b) { return PVector(a.x+b.x, a.y+b.y, a.z+b.z); }
static PVector sub(const PVector& a, const PVector& b) { return PVector(a.x-b.x, a.y-b.y, a.z-b.z); }
static PVector mult(const PVector& v, float s) { return PVector(v.x*s, v.y*s, v.z*s); }
static PVector div(const PVector& v, float s) { return PVector(v.x/s, v.y/s, v.z/s); }
// Operators
PVector operator+(const PVector& v) const { return PVector(x+v.x, y+v.y, z+v.z); }
PVector operator-(const PVector& v) const { return PVector(x-v.x, y-v.y, z-v.z); }
PVector operator*(float s) const { return PVector(x*s, y*s, z*s); }
PVector operator/(float s) const { return PVector(x/s, y/s, z/s); }
PVector& operator+=(const PVector& v) { return add(v); }
PVector& operator-=(const PVector& v) { return sub(v); }
PVector& operator*=(float s) { return mult(s); }
PVector& operator/=(float s) { return div(s); }
bool operator==(const PVector& v) const { return x==v.x && y==v.y && z==v.z; }
bool operator!=(const PVector& v) const { return !(*this==v); }
// Dot / cross product
float dot(const PVector& v) const { return x*v.x + y*v.y + z*v.z; }
float dot(float _x, float _y, float _z=0) const { return x*_x + y*_y + z*_z; }
static float dot(const PVector& a, const PVector& b) { return a.dot(b); }
PVector cross(const PVector& v) const { return PVector(y*v.z-z*v.y, z*v.x-x*v.z, x*v.y-y*v.x); }
static PVector cross(const PVector& a, const PVector& b) { return a.cross(b); }
// Normalization / limits
PVector& normalize() { float m=mag(); if(m>0) div(m); return *this; }
PVector normalized() const { PVector v(*this); return v.normalize(); }
PVector& limit(float mx) { if(magSq()>mx*mx){ normalize(); mult(mx); } return *this; }
PVector& setMag(float m) { normalize(); mult(m); return *this; }
// Distance / angle
float dist(const PVector& v) const { float dx=x-v.x,dy=y-v.y,dz=z-v.z; return std::sqrt(dx*dx+dy*dy+dz*dz); }
static float dist(const PVector& a, const PVector& b) { return a.dist(b); }
float heading() const { return std::atan2(y, x); }
// heading2D() is @Deprecated in Processing 4 Java but still present as an
// alias -- keep it here so sketches copied from old examples just work.
float heading2D() const { return heading(); }
float angleBetween(const PVector& v) const {
float m = mag() * v.mag();
if (m == 0) return 0;
float c = dot(v) / m;
c = c < -1 ? -1 : (c > 1 ? 1 : c);
return std::acos(c);
}
static float angleBetween(const PVector& a, const PVector& b) { return a.angleBetween(b); }
// Mutation
PVector& rotate(float t) { float c=std::cos(t),s=std::sin(t),nx=x*c-y*s,ny=x*s+y*c; x=nx; y=ny; return *this; }
PVector& lerp(const PVector& v, float t) { x+=(v.x-x)*t; y+=(v.y-y)*t; z+=(v.z-z)*t; return *this; }
PVector& lerp(float _x, float _y, float _z, float t) { x+=(_x-x)*t; y+=(_y-y)*t; z+=(_z-z)*t; return *this; }
static PVector lerp(const PVector& a, const PVector& b, float t) {
return PVector(a.x+(b.x-a.x)*t, a.y+(b.y-a.y)*t, a.z+(b.z-a.z)*t);
}
// Static constructors
static PVector fromAngle(float a, float len=1.0f) { return PVector(std::cos(a)*len, std::sin(a)*len); }
static PVector random2D() {
float a = static_cast<float>(rand()) / RAND_MAX * 6.28318f;
return fromAngle(a);
}
static PVector random3D() {
float t = static_cast<float>(rand()) / RAND_MAX * 6.28318f;
float p = std::acos(2.0f * static_cast<float>(rand()) / RAND_MAX - 1.0f);
return PVector(std::sin(p)*std::cos(t), std::sin(p)*std::sin(t), std::cos(p));
}
std::string toString() const {
std::ostringstream ss;
ss << "[ " << x << ", " << y << ", " << z << " ]";
return ss.str();
}
};
// =============================================================================
// PCOLOR -- RGBA color with HSB conversion and blend operations
// =============================================================================
class PColor {
public:
float r, g, b, a;
PColor() : r(0), g(0), b(0), a(255) {}
PColor(float gray) : r(gray),g(gray),b(gray),a(255) {}
PColor(float gray, float a) : r(gray),g(gray),b(gray),a(a) {}
PColor(float r, float g, float b) : r(r), g(g), b(b), a(255) {}
PColor(float r, float g, float b, float a) : r(r), g(g), b(b), a(a) {}
// Construct from packed ARGB integer (0xAARRGGBB)
explicit PColor(unsigned int argb)
: r((argb>>16)&0xFF), g((argb>>8)&0xFF), b(argb&0xFF), a((argb>>24)&0xFF) {}
// Pack to ARGB integer
unsigned int toARGB() const {
int ri=(int)std::fmax(0,std::fmin(255,r));
int gi=(int)std::fmax(0,std::fmin(255,g));
int bi=(int)std::fmax(0,std::fmin(255,b));
int ai=(int)std::fmax(0,std::fmin(255,a));
return (unsigned int)((ai<<24)|(ri<<16)|(gi<<8)|bi);
}
// Normalised [0..1] accessors
float rf() const { return r/255.0f; }
float gf() const { return g/255.0f; }
float bf() const { return b/255.0f; }
float af() const { return a/255.0f; }
PColor& set(float _r, float _g, float _b, float _a=255) { r=_r; g=_g; b=_b; a=_a; return *this; }
PColor& set(float gray, float _a=255) { r=g=b=gray; a=_a; return *this; }
PColor copy() const { return PColor(r, g, b, a); }
// HSB conversions
float hue() const {
float rf_=r/255.f, gf_=g/255.f, bf_=b/255.f;
float mx=std::fmax(rf_,std::fmax(gf_,bf_));
float mn=std::fmin(rf_,std::fmin(gf_,bf_));
float d=mx-mn;
if (d==0) return 0;
float h = (mx==rf_) ? (gf_-bf_)/d : (mx==gf_) ? 2.f+(bf_-rf_)/d : 4.f+(rf_-gf_)/d;
h *= 60.f;
if (h < 0) h += 360.f;
return h;
}
float saturation() const {
float mx=std::fmax(r,std::fmax(g,b));
float mn=std::fmin(r,std::fmin(g,b));
return mx==0 ? 0 : ((mx-mn)/mx)*100.f;
}
float brightness() const { return std::fmax(r,std::fmax(g,b))/255.f*100.f; }
static PColor fromHSB(float h, float s, float bv, float a=255) {
s /= 100.f; bv /= 100.f;
if (s == 0) { float v=bv*255.f; return PColor(v,v,v,a); }
float hh=std::fmod(h,360.f)/60.f;
int i=(int)hh;
float f=hh-i, p=bv*(1-s), q=bv*(1-s*f), t=bv*(1-s*(1-f));
float rv,gv,blv;
switch(i){
case 0: rv=bv;gv=t; blv=p; break;
case 1: rv=q; gv=bv;blv=p; break;
case 2: rv=p; gv=bv;blv=t; break;
case 3: rv=p; gv=q; blv=bv; break;
case 4: rv=t; gv=p; blv=bv; break;
default:rv=bv;gv=p; blv=q; break;
}
return PColor(rv*255,gv*255,blv*255,a);
}
// Arithmetic operators
PColor operator+(const PColor& o) const { return PColor(r+o.r, g+o.g, b+o.b, a+o.a); }
PColor operator-(const PColor& o) const { return PColor(r-o.r, g-o.g, b-o.b, a-o.a); }
PColor operator*(float s) const { return PColor(r*s, g*s, b*s, a*s); }
PColor operator/(float s) const { return PColor(r/s, g/s, b/s, a/s); }
PColor& operator+=(const PColor& o) { r+=o.r; g+=o.g; b+=o.b; a+=o.a; return *this; }
PColor& operator-=(const PColor& o) { r-=o.r; g-=o.g; b-=o.b; a-=o.a; return *this; }
PColor& operator*=(float s) { r*=s; g*=s; b*=s; a*=s; return *this; }
PColor& operator/=(float s) { r/=s; g/=s; b/=s; a/=s; return *this; }
bool operator==(const PColor& o) const { return r==o.r && g==o.g && b==o.b && a==o.a; }
bool operator!=(const PColor& o) const { return !(*this==o); }
// Utility
static PColor lerp(const PColor& c1, const PColor& c2, float t) {
return PColor(c1.r+(c2.r-c1.r)*t, c1.g+(c2.g-c1.g)*t, c1.b+(c2.b-c1.b)*t, c1.a+(c2.a-c1.a)*t);
}
PColor& clamp() {
r=std::fmax(0,std::fmin(255,r)); g=std::fmax(0,std::fmin(255,g));
b=std::fmax(0,std::fmin(255,b)); a=std::fmax(0,std::fmin(255,a));
return *this;
}
PColor multRGB(float s) const { return PColor(r*s, g*s, b*s, a); }
// Blend modes (return new color)
static PColor blend(const PColor& src, const PColor& dst) {
float sa = src.a/255.f;
return PColor(src.r*sa+dst.r*(1-sa), src.g*sa+dst.g*(1-sa), src.b*sa+dst.b*(1-sa), 255);
}
static PColor add(const PColor& a, const PColor& b) {
return PColor(std::fmin(255,a.r+b.r), std::fmin(255,a.g+b.g), std::fmin(255,a.b+b.b), a.a);
}
static PColor multiply(const PColor& a, const PColor& b) {
return PColor((a.r/255.f)*b.r, (a.g/255.f)*b.g, (a.b/255.f)*b.b, a.a);
}
static PColor screen(const PColor& a, const PColor& b) {
auto sc=[](float x,float y){ return 255-(255-x)*(255-y)/255.f; };
return PColor(sc(a.r,b.r), sc(a.g,b.g), sc(a.b,b.b), a.a);
}
float brightness255() const { return std::fmax(r, std::fmax(g, b)); }
std::string toString() const {
std::ostringstream ss;
ss << "PColor(" << r << ", " << g << ", " << b << ", " << a << ")";
return ss.str();
}
};
// Forward declarations so PColor overloads compile below class definitions
void fill(const PColor& c);
void stroke(const PColor& c);
void background(const PColor& c);
void tint(const PColor& c);
// IMAGE FILTER CONSTANTS
// =============================================================================
static constexpr int THRESHOLD = 1;
static constexpr int GRAY = 2;
static constexpr int OPAQUE = 3;
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;
// =============================================================================
// PIMAGE -- Pixel buffer backed by an OpenGL texture
// =============================================================================
class PImage {
public:
int width = 0;
int height = 0;
std::vector<unsigned int> pixels;
GLuint texID = 0;
bool dirty = false;
PImage() = default;
PImage(int w, int h) {
// Guard against bad dimensions from corrupted files or failed loads
if (w > 0 && h > 0 && w < 16384 && h < 16384) {
width = w; height = h;
pixels.assign((size_t)w * h, 0xFF000000);
}
}
// Pixel read/write (bounds-checked)
unsigned int get(int x, int y) const {
if (x<0||x>=width||y<0||y>=height) return 0;
return pixels[y*width+x];
}
void set(int x, int y, unsigned int c) {
if (x<0||x>=width||y<0||y>=height) return;
pixels[y*width+x] = c;
dirty = true;
}
// These mirror the Processing Java API; dirty flag is used by updatePixels()
void loadPixels() {}
void updatePixels() { dirty = true; }
// Upload CPU pixels to the GPU texture
void uploadTexture(); // defined in Processing.cpp
void resize(int w, int h) { width=w; height=h; pixels.assign(w*h, 0xFF000000); dirty=true; }
// Apply an image filter to all pixels. Mirrors Java's filter(int kind)
// -- fills in the same per-mode defaults Java uses when no level is
// given (THRESHOLD: 0.5, BLUR: radius 1; everything else ignores the
// level entirely). POSTERIZE has no documented Java default for the
// no-level call; 4 is a reasonable stand-in, not a spec'd value.
void filter(int mode) {
switch (mode) {
case THRESHOLD: filter(mode, 0.5f); return;
case POSTERIZE: filter(mode, 4.0f); return;
case BLUR: filter(mode, 1.0f); return;
default: filter(mode, 0.0f); return; // GRAY/OPAQUE/INVERT/ERODE/DILATE take no level
}
}
// Mirrors Java's filter(int kind, float param).
void filter(int mode, float param) {
// Luminance conversion (matches the weighting Processing itself
// uses for GRAY/THRESHOLD, credited in its docs to toxi) --
// closer to real Processing than a flat (r+g+b)/3 average.
auto luminance = [](int r, int g, int b) {
int v = (int)(0.299f*r + 0.587f*g + 0.114f*b);
return v<0 ? 0 : (v>255 ? 255 : v);
};
if (mode == GRAY) {
for (auto& p : pixels) {
int r=(p>>16)&0xFF, g=(p>>8)&0xFF, b=p&0xFF, a=(p>>24)&0xFF;
int gr = luminance(r,g,b);
p = ((unsigned)a<<24)|((unsigned)gr<<16)|((unsigned)gr<<8)|(unsigned)gr;
}
} else if (mode == INVERT) {
for (auto& p : pixels) {
int r=(p>>16)&0xFF, g=(p>>8)&0xFF, b=p&0xFF, a=(p>>24)&0xFF;
p = ((unsigned)a<<24)|((unsigned)(255-r)<<16)|((unsigned)(255-g)<<8)|(unsigned)(255-b);
}
} else if (mode == THRESHOLD) {
int t = (int)(param * 255.0f);
for (auto& p : pixels) {
int r=(p>>16)&0xFF, g=(p>>8)&0xFF, b=p&0xFF, a=(p>>24)&0xFF;
int v = luminance(r,g,b) > t ? 255 : 0;
p = ((unsigned)a<<24)|((unsigned)v<<16)|((unsigned)v<<8)|(unsigned)v;
}
} else if (mode == OPAQUE) {
for (auto& p : pixels) p |= 0xFF000000u;
} else if (mode == POSTERIZE) {
int steps = (int)param;
if (steps < 2) steps = 2;
if (steps > 255) steps = 255;
auto quantize = [steps](int c) {
int lvl = (c * steps) / 256;
int out = lvl * 255 / (steps - 1);
return out<0 ? 0 : (out>255 ? 255 : out);
};
for (auto& p : pixels) {
int r=(p>>16)&0xFF, g=(p>>8)&0xFF, b=p&0xFF, a=(p>>24)&0xFF;
r=quantize(r); g=quantize(g); b=quantize(b);
p = ((unsigned)a<<24)|((unsigned)r<<16)|((unsigned)g<<8)|(unsigned)b;
}
} else if (mode == BLUR) {
applyBoxBlurApprox(param > 0 ? param : 1.0f);
} else if (mode == ERODE) {
applyMorphology(false);
} else if (mode == DILATE) {
applyMorphology(true);
}
dirty = true;
}
// Extract a sub-image
PImage get(int x, int y, int w, int h) const {
PImage out(w, h);
for (int iy=0; iy<h; iy++)
for (int ix=0; ix<w; ix++)
out.pixels[iy*w+ix] = get(x+ix, y+iy);
return out;
}
// Duplicate the whole image into a new, independent PImage -- mirrors
// Java's PImage.copy() (equivalent to Java's now-deprecated no-arg
// get(), itself defined as get(0,0,width,height)). Returned via the
// move constructor below (NRVO, or implicit move-on-return for a
// named local going out of scope) -- the deleted copy constructor
// just above is never invoked here.
PImage copy() const {
PImage out(width, height);
out.pixels = pixels;
return out;
}
// Copy from another image with scaling
void copy(const PImage& src, int sx, int sy, int sw, int sh, int dx, int dy, int dw, int dh) {
for (int iy=0; iy<dh; iy++)
for (int ix=0; ix<dw; ix++) {
int srcX = sx+(int)(ix*(float)sw/dw);
int srcY = sy+(int)(iy*(float)sh/dh);
set(dx+ix, dy+iy, src.get(srcX, srcY));
}
dirty = true;
}
private:
// 3 passes of separable box blur closely approximates a true Gaussian
// blur (a well-known equivalence) -- this is NOT a port of Processing's
// own specific stack-blur implementation (credited to Mario Klingemann
// in Java's docs), so results won't be bit-identical to real Processing,
// but filter(BLUR) / filter(BLUR, level) match Java's API and produce
// the same kind of visual softening, scaling with level the same way.
void applyBoxBlurApprox(float radiusParam) {
int r = (int)(radiusParam + 0.5f);
if (r < 1) r = 1;
for (int pass = 0; pass < 3; pass++) {
boxBlurPass(true, r);
boxBlurPass(false, r);
}
}
void boxBlurPass(bool horizontal, int r) {
std::vector<unsigned int> out(pixels.size());
int w = width, h = height;
for (int y = 0; y < h; y++) {
for (int x = 0; x < w; x++) {
long sa=0, sr=0, sg=0, sb=0; int count=0;
for (int k = -r; k <= r; k++) {
int sx = horizontal ? x+k : x;
int sy = horizontal ? y : y+k;
if (sx < 0) sx = 0;
if (sx >= w) sx = w-1;
if (sy < 0) sy = 0;
if (sy >= h) sy = h-1;
unsigned int c = pixels[sy*w+sx];
sa += (c>>24)&0xFF; sr += (c>>16)&0xFF; sg += (c>>8)&0xFF; sb += c&0xFF;
count++;
}
unsigned int a=(unsigned)(sa/count), rr=(unsigned)(sr/count),
g=(unsigned)(sg/count), b=(unsigned)(sb/count);
out[y*w+x] = (a<<24)|(rr<<16)|(g<<8)|b;
}
}
pixels = std::move(out);
}
// ERODE (shrink light areas) / DILATE (grow light areas): replaces each
// pixel with the min- (erode) or max- (dilate) luminance color among
// itself and its 4-connected neighbors, using 77/151/28-weighted
// luminance (the standard 0.299/0.587/0.114 coefficients scaled to
// 256), matching the structure of real Processing's own dilate()/
// erode() implementation. Edge pixels clamp to themselves for any
// missing neighbor rather than wrapping or reading out of bounds.
void applyMorphology(bool isDilate) {
std::vector<unsigned int> out(pixels.size());
auto lum = [](unsigned int c) {
int r=(c>>16)&0xFF, g=(c>>8)&0xFF, b=c&0xFF;
return 77*r + 151*g + 28*b;
};
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
int idx = y*width + x;
unsigned int best = pixels[idx];
int bestLum = lum(best);
const int nx[4] = {x-1, x+1, x, x};
const int ny[4] = {y, y, y-1, y+1};
for (int k = 0; k < 4; k++) {
if (nx[k]<0 || nx[k]>=width || ny[k]<0 || ny[k]>=height) continue;
unsigned int c = pixels[ny[k]*width + nx[k]];
int l = lum(c);
if (isDilate ? (l > bestLum) : (l < bestLum)) { best = c; bestLum = l; }
}
out[idx] = best;
}
}
pixels = std::move(out);
}
public:
// Apply alpha mask from another grayscale image
void mask(const PImage& m) {
for (int i=0; i<width*height && i<(int)m.pixels.size(); i++) {
int a = (m.pixels[i]>>16)&0xFF;
pixels[i] = (pixels[i]&0x00FFFFFF)|(a<<24);
}
dirty = true;
}
void mask(const PImage* m) { if (m) mask(*m); }
// Destructor frees GPU texture
virtual ~PImage() { if (texID) glDeleteTextures(1, &texID); }
// Non-copyable (owns GPU resource -- use PImage* for assignment)
PImage(const PImage&) __attribute__((error(
"E0002: PImage value-style copying is not supported. "
"Declare PImage* instead of PImage. "
"See " PROCESSING_WEBSITE_URL "/error/E0002.html"
)));
PImage& operator=(const PImage&) __attribute__((error(
"E0002: PImage value-style assignment is not supported. "
"Declare PImage* instead of PImage. "
"See " PROCESSING_WEBSITE_URL "/error/E0002.html"
)));
// Movable
PImage(PImage&& o) noexcept
: width(o.width), height(o.height), pixels(std::move(o.pixels)),
texID(o.texID), dirty(o.dirty) { o.texID=0; }
};
// =============================================================================
// PGRAPHICS -- Off-screen render target (framebuffer object)
// =============================================================================
class PGraphics : public PImage {
public:
GLuint fbo = 0; // framebuffer object
GLuint rbo = 0; // renderbuffer (depth+stencil)
bool active = false;
// Independent per-buffer style state. Real Processing's PGraphics has
// its OWN fill/stroke/text/etc. settings, completely separate from the
// main canvas's -- setting fill() on the main canvas must never affect
// a PGraphics buffer, and vice versa. Previously every PGraphics method
// forwarded directly to the single global PApplet::g_papplet singleton,
// meaning style state silently bled between the main canvas and every
// buffer (e.g. a thick green main-canvas stroke would incorrectly show
// up on an ellipse drawn inside a buffer that never set its own
// stroke). beginDraw()/endDraw() now swap PApplet's current style out
// for this buffer's OWN remembered style, and swap it back after,
// exactly mirroring how Java's PGraphics keeps independent state.
struct StyleSnapshot {
// Defaults match PApplet's own real defaults (white fill, black
// stroke) -- these are also real Processing's documented
// beginDraw() defaults ("Sets the default properties"), NOT an
// arbitrary choice. The earlier version of this struct had
// fillR=0 (black fill), which is backwards -- every fresh
// PGraphics buffer with no explicit fill()/stroke() calls should
// look exactly like a freshly created Processing sketch: white
// fill, black stroke, weight 1.
float fillR=1, fillG=1, fillB=1, fillA=1;
float strokeR=0, strokeG=0, strokeB=0, strokeA=1;
float strokeW=1;
bool doFill=true, doStroke=true, smoothing=true;
// BUG FIX: these were raw 0/0/0 literals, which silently meant
// CORNER mode (CORNER=0) for ellipseMode specifically, when real
// Processing's actual default is CENTER (=3). That made every
// fresh PGraphics buffer's ellipse() calls interpret their first
// two arguments as the bounding box's top-left corner instead of
// its center, shifting every default-mode ellipse by half its
// width/height toward the bottom-right. rectMode's and
// imageMode's real defaults ARE actually CORNER (=0), so those
// two were correct by coincidence -- only currentEllipseMode
// needed the real CENTER constant.
// Using literal values, not the CORNER/CENTER named constants:
// those constants are declared later in this file, after
// PGraphics's own definition, so they're not in scope yet here.
// CORNER=0, CENTER=3 (see the static constexpr declarations
// further down in this file).
int currentRectMode=0 /*CORNER*/, currentEllipseMode=3 /*CENTER*/, currentImageMode=0 /*CORNER*/;
float tintR=1, tintG=1, tintB=1, tintA=1;
bool doTint=false;
int colorModeVal=0;
float colorMaxH=255.f, colorMaxS=255.f, colorMaxB=255.f, colorMaxA=255.f;
float g_textSize=14.0f;
int g_textAlignX=0, g_textAlignY=0;
float g_textLeading=0.0f;
bool initialized=false; // false until beginDraw() runs once and sets real Processing defaults
};
StyleSnapshot myStyle; // this buffer's OWN persistent style
StyleSnapshot _savedMainStyle; // main canvas's style, stashed during beginDraw()..endDraw()
// Multisampled render target: PGraphics now matches real Processing's
// default antialiasing (smooth(2) on P2D/P3D) by rendering into a
// multisample renderbuffer-backed FBO, then resolving (blitting) down
// into the plain texture-backed FBO that drawPGraphicsRect samples
// from. Without this, the main canvas's window-level MSAA never
// applied to off-screen buffers at all.
GLuint msaaFbo = 0;
GLuint msaaColorRbo = 0;
GLuint msaaDepthRbo = 0;
int samples = 0; // 0 = no multisampling
bool is3D = false; // true if created via createGraphics(w,h,P3D)
PGraphics() = default;
PGraphics(int w, int h) : PImage(w, h) {
// Can't reach PApplet::g_papplet here -- PApplet's complete type
// isn't available yet at this point in the header (PGraphics is
// defined before it). Default directly to real Processing's own
// P2D/P3D default (smooth(2)) rather than reaching across that
// forward-reference gap. A sketch wanting a different level for
// its buffers can extend this later if needed.
samples = 0; // TEMPORARY: forced off to test if MSAA itself is the bug
// Resolve target: plain, non-multisampled FBO + texture --
// unchanged from before, just filled via a blit-resolve now.
glGenFramebuffers(1, &fbo);
glBindFramebuffer(GL_FRAMEBUFFER, fbo);
if (texID == 0) glGenTextures(1, &texID);
glBindTexture(GL_TEXTURE_2D, texID);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texID, 0);
glGenRenderbuffers(1, &rbo);
glBindRenderbuffer(GL_RENDERBUFFER, rbo);
glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, w, h);
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, rbo);
glBindFramebuffer(GL_FRAMEBUFFER, 0);
// Multisample render target: only created if antialiasing was
// actually requested. beginDraw() binds THIS one; endDraw() blits
// it down into the resolve target above.
if (samples > 0) {
glGenFramebuffers(1, &msaaFbo);
glBindFramebuffer(GL_FRAMEBUFFER, msaaFbo);
glGenRenderbuffers(1, &msaaColorRbo);
glBindRenderbuffer(GL_RENDERBUFFER, msaaColorRbo);
glRenderbufferStorageMultisample(GL_RENDERBUFFER, samples, GL_RGBA8, w, h);
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, msaaColorRbo);
glGenRenderbuffers(1, &msaaDepthRbo);
glBindRenderbuffer(GL_RENDERBUFFER, msaaDepthRbo);
glRenderbufferStorageMultisample(GL_RENDERBUFFER, samples, GL_DEPTH24_STENCIL8, w, h);
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, msaaDepthRbo);
GLenum msaaStatus = glCheckFramebufferStatus(GL_FRAMEBUFFER);
if (msaaStatus != GL_FRAMEBUFFER_COMPLETE) {
glDeleteFramebuffers(1, &msaaFbo); msaaFbo = 0;
glDeleteRenderbuffers(1, &msaaColorRbo); msaaColorRbo = 0;
glDeleteRenderbuffers(1, &msaaDepthRbo); msaaDepthRbo = 0;
samples = 0;
}
glBindFramebuffer(GL_FRAMEBUFFER, 0);
}
}
PGraphics(int w, int h, bool threeD) : PGraphics(w, h) {
is3D = threeD;
}
GLint savedViewport[4] = {};
void beginDraw(); // defined after PApplet (needs its complete type for style swap)
void _endDrawImpl(); // body of endDraw(), out-of-line for the same reason
void endDraw() { _endDrawImpl(); }
// Drawing methods forwarded to Processing -- implemented after full decls
void background(float g); void background(float r, float g, float b); void background(float r, float g, float b, float a);
void fill(float g); void fill(float r, float g, float b); void fill(float r, float g, float b, float a);
void noFill(); void stroke(float g); void stroke(float r, float g, float b); void noStroke();
void strokeWeight(float w);
void ellipse(float x, float y, float w, float h);
void rect(float x, float y, float w, float h);
void line(float x1, float y1, float x2, float y2);
void point(float x, float y);
void triangle(float x1,float y1,float x2,float y2,float x3,float y3);
void text(const std::string& s, float x, float y);
void textSize(float size);
void textAlign(int alignX);
void textAlign(int alignX, int alignY);
void translate(float x, float y, float z);
void rotateX(float angle);
void rotateY(float angle);
void rotateZ(float angle);
void box(float size);
void box(float w, float h, float d);
void sphere(float r);
void lights();
void noLights();
void ambientLight(float r, float g, float b);
void ambientLight(float r, float g, float b, float x, float y, float z);
void directionalLight(float r, float g, float b, float nx, float ny, float nz);
void pointLight(float r, float g, float b, float x, float y, float z);
void spotLight(float r, float g, float b, float x, float y, float z,
float nx, float ny, float nz, float angle, float conc);
void lightFalloff(float c, float l, float q);
void lightSpecular(float r, float g, float b);
void translate(float x, float y); void rotate(float a); void scale(float s);
void pushMatrix(); void popMatrix();
void beginShape(); void endShape(int mode=0); void vertex(float x, float y);
void clear();
~PGraphics() {
// Defensive cleanup: a PGraphics can be destroyed (via delete, or
// by going out of scope) while its beginDraw() was never matched
// with an endDraw() -- e.g. "pg = createGraphics(...)" reassigns
// a pointer, leaking the OLD PGraphics it pointed to if nothing
// explicitly deleted it first; if something DOES eventually
// delete it (or CppBuild auto-inserts a delete for exactly this
// case), the destructor running mid-beginDraw() needs to
// gracefully unwind that state rather than leaving the matrix
// stack unbalanced or GL bindings dangling on whatever context
// outlives this object.
if (active) {
PDEBUG("PGraphics::~PGraphics: destroying while still active "
"(beginDraw() never matched with endDraw()) -- "
"auto-closing now. this=%p\n", (void*)this);
_endDrawImpl();
}
if (msaaFbo) glDeleteFramebuffers(1, &msaaFbo);
if (msaaColorRbo) glDeleteRenderbuffers(1, &msaaColorRbo);
if (msaaDepthRbo) glDeleteRenderbuffers(1, &msaaDepthRbo);
if (fbo) glDeleteFramebuffers(1, &fbo);
if (rbo) glDeleteRenderbuffers(1, &rbo);
}
PGraphics(const PGraphics&) __attribute__((error(
"E0001: PGraphics value-style copying is not supported. "
"Declare PGraphics* instead of PGraphics. "
"See " PROCESSING_WEBSITE_URL "/error/E0001.html"
)));
PGraphics& operator=(const PGraphics&) __attribute__((error(
"E0001: PGraphics value-style assignment is not supported. "
"Declare PGraphics* instead of PGraphics. "
"See " PROCESSING_WEBSITE_URL "/error/E0001.html"
)));
// Allow assignment from pointer (PGraphics pg; pg = createGraphics(w,h))
// [E0001] REMOVED: the legacy "PGraphics pg; pg = createGraphics(...);"
// value-style assignment is no longer supported. PGraphics owns
// unique GPU resources (FBO, renderbuffers, texture) -- unlike
// PShape/PFont, which hold only plain CPU-side data and are safely
// copyable, copying or reassigning a PGraphics VALUE has no safe
// meaning. Declare it as a pointer instead:
//
// PGraphics* pg;
// pg = createGraphics(w, h);
// pg->beginDraw();
// ...
// pg->endDraw();
//
// This explicit compile error is intentional: it tells you exactly
// what to fix, rather than silently compiling against a value-style
// declaration that would behave incorrectly or unsafely. The actual
// URL in the error message below comes from PROCESSING_WEBSITE_URL
// (ultimately config/cppmode.properties), never hardcoded here.
PGraphics& operator=(PGraphics* p) __attribute__((error(
"E0001: PGraphics value-style assignment is not supported. "
"Declare PGraphics* instead of PGraphics. "
"See " PROCESSING_WEBSITE_URL "/error/E0001"
)));
};
// =============================================================================
// =============================================================================
// Aliases: 'width' and 'height' are the canonical Processing names
// width/height always equal what size() set -- never corrupted by WM tile resize.
// =============================================================================
// =============================================================================
// =============================================================================
// =============================================================================
// Define any of these in your sketch; unimplemented ones are safely skipped.
//
// On Linux/macOS: declared __attribute__((weak)) so undefined ones link as nullptr.
// On Windows (MinGW): weak declarations don't work; instead, _wireCallbacksFn is
// set at the bottom of IDE.cpp/sketch to point to a function that wires
// all _on* function pointers. See the Windows Event Wiring section of IDE.cpp.
// ---------------------------------------------------------------------------