-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy pathxpaint.c
More file actions
6097 lines (5393 loc) · 210 KB
/
xpaint.c
File metadata and controls
6097 lines (5393 loc) · 210 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
#include <X11/Xatom.h> // XA_*
#include <X11/Xft/Xft.h>
#include <X11/extensions/Xdbe.h> // back buffer
#include <X11/extensions/Xrender.h>
#include <X11/extensions/sync.h>
#include <ctype.h>
#include <dirent.h>
#include <sys/fcntl.h>
#include <sys/mman.h>
#include <sys/time.h>
#include <unistd.h>
// libs
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wunused-function"
#define INCBIN_PREFIX
#define INCBIN_STYLE INCBIN_STYLE_SNAKE
#include "lib/incbin.h"
#define STB_DS_IMPLEMENTATION
#include "lib/stb_ds.h"
#undef STB_DS_IMPLEMENTATION
#define STB_IMAGE_IMPLEMENTATION
#include "lib/stb_image.h"
#undef STB_IMAGE_IMPLEMENTATION
#define STB_IMAGE_WRITE_IMPLEMENTATION
#include "lib/stb_image_write.h"
#undef STB_IMAGE_WRITE_IMPLEMENTATION
#pragma GCC diagnostic pop
/*
* -opt vars are nullable (optional)
* free -dyn vars with 'free' function
* free -arr vars with 'arrfree' function
* free -imdyn vars with 'stbi_image_free' function
* free -xdyn vars with 'XFree' function
* structs with t and d fields are tagged unions
*/
typedef int8_t i8;
typedef uint8_t u8;
typedef int16_t i16;
typedef int32_t i32;
typedef int64_t i64;
typedef uint16_t u16;
typedef uint32_t u32;
typedef uint64_t u64;
typedef size_t usize;
// embedded data
INCBIN(u8, pic_tool_fill, "res/tool-fill.png");
INCBIN(u8, pic_tool_pencil, "res/tool-pencil.png");
INCBIN(u8, pic_tool_picker, "res/tool-picker.png");
INCBIN(u8, pic_tool_select, "res/tool-select.png");
INCBIN(u8, pic_tool_brush, "res/tool-brush.png");
INCBIN(u8, pic_tool_spray, "res/tool-spray.png");
INCBIN(u8, pic_tool_figure, "res/tool-figure.png");
INCBIN(u8, pic_tool_text, "res/tool-text.png");
INCBIN(u8, pic_fig_rect, "res/figure-rectangle.png");
INCBIN(u8, pic_fig_circ, "res/figure-circle.png");
INCBIN(u8, pic_fig_tri, "res/figure-triangle.png");
INCBIN(u8, pic_fig_fill_on, "res/figure-fill-on.png");
INCBIN(u8, pic_fig_fill_off, "res/figure-fill-off.png");
#define MAX(A, B) ((A) > (B) ? (A) : (B))
#define MIN(A, B) ((A) < (B) ? (A) : (B))
#define CLAMP(X, L, H) (((X) < (L)) ? (L) : ((X) > (H)) ? (H) : (X))
#define LENGTH(X) (sizeof(X) / sizeof(X)[0])
#define BETWEEN(X, A, B) ((A) <= (X) && (X) <= (B))
#define COALESCE(A, B) ((A) ? (A) : (B))
#define UNREACHABLE() __builtin_unreachable()
#define IS_PNIL(p_pt) ((p_pt).x == NIL && (p_pt).y == NIL)
#define IS_DPNIL(p_dpt) ((p_dpt).x == NIL && (p_dpt).y == NIL)
#define PT_EQ(p_a, p_b) ((p_a).x == (p_b).x && (p_a).y == (p_b).y)
#define IS_RNIL(p_rect) \
(((p_rect).l) == INT32_MAX && ((p_rect).t) == INT32_MAX && ((p_rect).r) == INT32_MIN && ((p_rect).b) == INT32_MIN)
enum { NO_MOD = 0 };
#define ANY_MOD UINT_MAX
#define NO_KEY XK_VoidSymbol
#define ANY_KEY UINT_MAX
enum { NO_MODE = 0 };
#define ANY_MODE UINT_MAX
// <X11/X.h> doesn't define these, but it is commonly supported
#ifndef Button6
#define Button6 6
#endif
#ifndef Button7
#define Button7 7
#endif
#ifndef Button8
#define Button8 8
#endif
#ifndef Button9
#define Button9 9
#endif
// default value for signed integers
#define NIL (-1)
#define PNIL ((Pt) {NIL, NIL})
#define RNIL ((Rect) {.l = INT32_MAX, .t = INT32_MAX, .r = INT32_MIN, .b = INT32_MIN})
#define DPNIL ((DPt) {NIL, NIL})
#define PI (3.141)
// only one one-byte symbol allowed
#define ARGB_ALPHA ((argb)(0xFF000000))
#define CL_DELIM " "
#define IOCTX_STDIO_STR "-"
#define TEXT_FONT_PROMPT "font: "
#define TEXT_MODE_PROMPT "text: "
#define CL_CMD_PROMPT ":"
#define CURR_TC(p_ctx) ((p_ctx)->tcarr[(p_ctx)->curr_tc])
// XXX workaround
#define COL_FG(p_dc, p_sc) ((p_dc)->schemes_dyn[(p_sc)].fg.pixel | 0xFF000000)
#define COL_BG(p_dc, p_sc) ((p_dc)->schemes_dyn[(p_sc)].bg.pixel | 0xFF000000)
#define OVERLAY_TRANSFORM(p_mode) \
((p_mode)->t != InputT_Transform ? TRANSFORM_DEFAULT : trans_add((p_mode)->d.trans.curr, (p_mode)->d.trans.acc))
#define ZOOM_C(p_dc) (pow(CANVAS_ZOOM_SPEED, (double)(p_dc)->cv.zoom))
#define TRANSFORM_DEFAULT ((Transform) {.scale = {1.0, 1.0}})
#define BTN_EQ(p_btn, p_btn_arr) ((btn_eq_impl((p_btn), (p_btn_arr), (LENGTH((p_btn_arr))))))
#define KEY_EQ(p_key, p_key_arr) ((key_eq_impl((p_key), (p_key_arr), (LENGTH((p_key_arr))))))
#define CAN_ACTION(p_input, p_key, p_mode, p_arr) \
can_action_impl((p_input), (p_key), (p_mode), (p_arr), (LENGTH((p_arr))))
typedef u32 argb;
typedef struct {
i32 x;
i32 y;
} Pt;
typedef struct {
i32 l;
i32 t;
i32 r; // inclusive
i32 b; // inclusive
} Rect;
typedef struct {
double x;
double y;
} DPt;
typedef struct {
KeySym sym;
u32 mask;
} Key;
typedef struct {
u32 button;
u32 mask;
} Button;
enum Schm {
SchmNorm,
SchmFocus,
SchmLast,
};
typedef struct {
enum {
SLM_Spacer, // static spacer
SLM_Text, // static text
SLM_ToolCtx, // list of tool contexts
SLM_Mode, // current mode
SLM_Tool, // current tool
SLM_ToolSettings, // current tool settings
SLM_ColorBox, // rectangle filled with current color
SLM_ColorName, // name of current color
SLM_ColorList, // current index and size of color list
} t; // type
union {
u32 spacer; // for SLM_Spacer
char const* text; // for SLM_Text
u32 color_box_w; // for SLM_ColorBox
} d; // data for corresponding type
} SLModule; // status line modules
enum InputModeFlag {
MF_Int = 0x1, // interact
MF_Color = 0x2, // color
MF_Trans = 0x4, // transform
// MF_Term managed manually because can use any key
};
typedef u32 InputModeFlags;
enum {
A_Cardinal,
A_Clipboard,
A_Targets,
A_Utf8string,
A_ImagePng,
A_TextUriList,
A_XSelData,
A_WmProtocols,
A_WmDeleteWindow,
A_NetWmSyncRequest,
A_NetWmSyncRequestCounter,
A_XDndAware,
A_XDndPosition,
A_XDndSelection,
A_XDndStatus,
A_XDndActionCopy,
A_XDndDrop,
A_Last,
};
enum Icon {
I_None,
I_Select,
I_Pencil,
I_Fill,
I_Picker,
I_Brush,
I_Spray,
I_Figure,
I_Text,
I_FigRect,
I_FigCirc,
I_FigTri,
I_FigFillOn,
I_FigFillOff,
I_Last,
};
struct IconData {
u8 const* data;
usize len;
};
struct Image {
XImage* im;
enum ImageType {
IMT_Png,
IMT_Jpg,
IMT_Unknown,
} type;
};
typedef struct {
Pt move;
DPt scale; // (1, 1) for no scale change
double rotate;
} Transform;
struct Ctx;
struct DrawCtx;
struct ToolCtx;
union SCI_Arg;
typedef void (*draw_fn)(struct Ctx* ctx, Pt p);
typedef enum {
HR_Quit,
HR_Ok,
} HdlrResult;
struct IOCtxWriteCtx {
struct IOCtx const* ioctx;
Bool result_out;
};
struct DrawerData {
enum DrawerShape {
DS_Brush,
DS_Circle,
DS_CircleRandom,
DS_Square,
DS_Point,
} shape;
double spacing;
double hardness; // 0.0 .. 1.0
};
struct Ctx {
struct DrawCtx {
// readonly outside setup and cleanup functions
struct System {
XRenderPictFormat* xrnd_pic_format;
XVisualInfo vinfo;
XIM xim;
XIC xic;
Colormap colmap;
} sys;
Display* dp;
GC gc;
GC screen_gc;
Window window;
u32 width;
u32 height;
XdbeBackBuffer back_buffer; // double buffering
struct Canvas {
XImage* im;
enum ImageType type;
i32 zoom; // 0 == no zoom
DPt scroll;
} cv;
XftFont* fnt;
struct Scheme {
XftColor fg;
XftColor bg;
}* schemes_dyn; // must be len of SchmLast
struct Cache {
Pt dims; // to validate pm and overlay
Pixmap pm; // pixel buffer to update screen
Pixmap overlay; // extra pixmap for overlay
} cache;
} dc;
struct Input {
struct CursorState {
enum CursorStateTag {
CS_None,
CS_Hold,
CS_Drag,
} state;
Button btn; // invalid if state == CS_None
Pt pos; // invalid if state == CS_None
} c;
Pt prev_c; // last cursor position
u64 last_proc_drag_ev_us; // optimization to reduce drag events
Pt anchor; // cursor position of last processed drawing tool event
i32 png_compression_level; // FIXME find better place
i32 jpg_quality_level; // FIXME find better place
// drawn on top of canvas
// clears before *on_press callbacks
// dumps to main canvas after *on_release callbacks
struct InputOverlay {
XImage* im;
Rect rect; // bounding box of actual content
} ovr;
// tracks damage to overlay from _on_press to _on_release.
Rect damage;
// parts of overlay and canvas to redraw in update_screen
// stores last and previous damages to handle full screen clears, e.g. figure tool or selection tool
Rect redraw_track[2];
struct InputMode {
enum InputTag {
InputT_Interact,
InputT_Color,
InputT_Console,
InputT_Transform,
InputT_Text,
} t;
union {
struct InputColorData {
u32 current_digit;
} col;
struct InputConsoleData {
char* cmdarr;
struct ComplsItem {
char* val_dyn;
char* descr_optdyn;
}* compls_arr;
usize compls_curr;
// Automatic delimeter append breaks paths
Bool dont_append_delimeter_after_apply;
} cl;
struct InputTransformData {
Transform acc; // accumulated
Transform curr; // current mouse drag
} trans;
struct InputTextData {
char* textarr;
struct ToolTextData {
Pt lb_corner;
} tool_data; // copied from text tool on mouse release
} text;
} d;
} mode;
} input;
struct ToolCtx {
// returns overlay damage
Rect (*on_press)(struct Ctx*, XButtonPressedEvent const*);
Rect (*on_release)(struct Ctx*, XButtonReleasedEvent const*);
Rect (*on_drag)(struct Ctx*, XMotionEvent const*);
Rect (*on_move)(struct Ctx*, XMotionEvent const*);
argb* colarr;
u32 curr_col;
u32 prev_col;
u32 line_w;
XftFont* text_font;
// 2d array of brush pixels, used for drawer tool
// call brush_cache_update before using this field
struct Brush {
argb* data;
Pt dims;
struct BrushParams {
u32 line_w;
argb col;
struct DrawerData data;
} params;
} brush_cache;
enum ToolTag {
Tool_Selection,
Tool_Drawer,
Tool_Fill,
Tool_Picker,
Tool_Figure,
Tool_Text,
} t;
union ToolData {
struct DrawerData drawer;
struct FigureData {
enum FigureType {
Figure_Circle,
Figure_Rectangle,
Figure_Triangle,
} curr;
Bool fill;
} fig;
struct ToolTextData text;
} d;
}* tcarr;
u32 curr_tc;
struct HistItem {
enum HistType {
HT_Damage,
HT_Resize,
} t;
union HistData {
struct HistDamage {
Pt pivot; // top left corner position
XImage* patch; // changed canvas part
} damage;
struct HistResize {
XImage* cv; // resize can delete canvas contents, need to store
} resize;
} d;
} *hist_prevarr, *hist_nextarr;
struct SelectionCircle {
i32 x;
i32 y;
Bool draw_separators;
struct Item {
void (*on_select)(struct Ctx* ctx, union SCI_Arg arg);
union SCI_Arg {
enum ToolTag tool;
enum DrawerShape drawer;
enum FigureType figure;
argb col;
usize num;
void* custom;
} arg;
enum Icon icon; // option icon
char const* desc; // option description
argb col_outer;
argb col_inner;
}* items_arr;
} sc;
struct StateXSync {
XSyncCounter counter;
XSyncValue last_request_value;
} xsync;
struct SelectionBuffer {
XImage* im;
} sel_buf;
struct IOCtx {
enum {
IO_None,
IO_File,
IO_Stdio,
} t;
union {
struct {
char* path_dyn;
} file;
} d;
} inp, out;
};
#define ENUM_DECL_HELPER(p_tag, p_str) p_tag,
#define TO_STRING_CASE(tag, str) \
case tag: return str;
#define STRING_TO_ENUM_ENTRY(tag, str) {str, tag},
#define DEFINE_ENUM_WITH_STRING_CONVERSIONS(p_type, p_name, p_FOREACH) \
/* Declare enum */ \
enum p_type { p_type##_Invalid = -1, p_FOREACH(ENUM_DECL_HELPER) p_type##_Count }; \
/* Declare *_to_string */ \
const char* p_name##_to_string(enum p_type v) { \
switch (v) { \
p_FOREACH(TO_STRING_CASE) case p_type##_Count: \
case p_type##_Invalid: break; \
} \
return "<unknown " #p_type ">"; \
} \
/* Declare *_from_string */ \
enum p_type p_name##_from_string(const char* s) { \
static const struct { \
const char* name; \
enum p_type val; \
} _map[] = {p_FOREACH(STRING_TO_ENUM_ENTRY)}; \
for (size_t i = 0; i < LENGTH(_map); ++i) { \
if (strcmp(s, _map[i].name) == 0) \
return _map[i].val; \
} \
return (enum p_type)(p_type##_Invalid); /* NOLINT(bugprone-macro-parentheses) */ \
}
#define FOREACH_ClCTag(X) \
X(ClC_Echo, "echo") \
X(ClC_Set, "set") \
X(ClC_Exit, "exit") \
X(ClC_Save, "save") \
X(ClC_W, "w") \
X(ClC_WQ, "wq") \
X(ClC_Load, "load")
DEFINE_ENUM_WITH_STRING_CONVERSIONS(ClCTag, cl_cmd, FOREACH_ClCTag)
#define FOREACH_ClCDSTag(X) \
X(ClCDS_LineW, "line_w") \
X(ClCDS_Col, "col") \
X(ClCDS_UiFont, "ui_font") \
X(ClCDS_TextFont, "font") \
X(ClCDS_Inp, "inp") \
X(ClCDS_Out, "out") \
X(ClCDS_PngCompression, "png_cmpr") \
X(ClCDS_JpgQuality, "jpg_qlty") \
X(ClCDS_Spacing, "spacing") \
X(ClCDS_Hardness, "hardness")
DEFINE_ENUM_WITH_STRING_CONVERSIONS(ClCDSTag, cl_set_prop, FOREACH_ClCDSTag)
#define FOREACH_ClCDSv(X) \
X(ClCDSv_Png, "png") \
X(ClCDSv_Jpg, "jpg")
DEFINE_ENUM_WITH_STRING_CONVERSIONS(ClCDSv, cl_save_type, FOREACH_ClCDSv)
struct ClCommand {
enum ClCTag t;
union ClCData {
struct ClCDSet {
enum ClCDSTag t;
union ClCDSData {
struct ClCDSDLineW {
u32 value;
} line_w;
struct ClCDSDCol {
argb v;
} col;
struct ClCDSDFont {
char* name_dyn;
} ui_font, text_font;
struct ClCDSDInp {
char* path_dyn;
} inp;
struct ClCDSDOut {
char* path_dyn;
} out;
struct ClCDSDPngCpr {
i32 compression;
} png_cpr;
struct ClCDSDJpgQlt {
i32 quality;
} jpg_qlt;
struct ClCDSDSpacing {
double val;
} spacing;
struct ClCDSDHardness {
double val;
} hardness;
} d;
} set;
struct ClCDEcho {
char* msg_dyn;
} echo;
struct ClCDSave {
enum ClCDSv im_type;
char* path_dyn;
} save;
struct ClCDLoad {
char* path_dyn;
} load;
} d;
};
enum {
ClCPrc_Msg = 0x1, // wants to show message
ClCPrc_Exit = 0x2, // wants to exit application
};
typedef u32 ClCPrcFlags;
typedef struct {
ClCPrcFlags flags;
char* msg_dyn; // NULL if not PCCR_MSG
} ClCPrcResult;
typedef struct {
enum {
ClCPrs_Ok,
ClCPrs_ENoArg,
ClCPrs_EInvArg, // invalid
} t;
union {
struct ClCommand ok;
struct {
char* arg_desc_dyn;
char* context_optdyn;
} noarg;
struct {
char* arg_dyn;
char* error_dyn;
char* context_optdyn;
} invarg;
} d;
} ClCPrsResult;
// clang-format off
__attribute__((noreturn)) static void die(char const* errstr, ...);
static void trace(char const* fmt, ...);
static void* ecalloc(u32 n, u32 size);
static u32 digit_count(u32 number);
static void arrpoputf8(char const* strarr);
static usize first_dismatch(char const* restrict s1, char const* restrict s2);
static struct IconData get_icon_data(enum Icon icon);
static double ease_out_cubic_hardness(double hardness, double v);
static double ease_in_expo(double a);
static Bool state_match(u32 a, u32 b);
static Button get_btn(XButtonEvent const* e);
static Bool btn_eq_impl(Button a, Button const* arr, u32 arr_len);
static Bool key_eq_impl(Key a, Key const* arr, u32 arr_len);
static Bool can_action_impl(struct Input const* input, Key curr_key, InputModeFlags mode, Key const* arr, u32 arr_len);
static char* uri_to_path(char const* uri);
static usize figure_side_count(enum FigureType type);
static char* path_expand_home(char const* path);
static Rect rect_bound(Rect a, Rect bound);
static Rect rect_expand(Rect a, Rect b);
// from left-top clockwise
static void rect_corners(Rect a, Pt corners_out[4]);
static Pt rect_dims(Rect a);
// only used in assert's, which breaks release builds
__attribute__((unused)) static Bool is_subrect(Rect outer, Rect inner);
__attribute__((unused)) static Bool is_valid_rect(Rect rect);
static Transform trans_add(Transform a, Transform b);
static XTransform xtrans_overlay_transform_mode(struct Input const* input);
static XTransform xtrans_scale(double x, double y);
static XTransform xtrans_move(double x, double y);
static XTransform xtrans_rotate(double a);
static XTransform xtrans_from_trans(Transform trans);
static XTransform xtrans_mult(XTransform a, XTransform b); // transformations are applied from right to left
static XTransform xtrans_invert(XTransform a);
static void xwindow_set_cardinal(Display* dp, Window window, Atom key, u32 value);
// needs to be 'free'd after use
static char* str_new(char const* fmt, ...);
static char* str_new_va(char const* fmt, va_list args);
static void str_free(char** str_dyn);
static struct ToolCtx tc_new(struct DrawCtx* dc);
static void tc_set_curr_col_num(struct ToolCtx* tc, u32 value);
static argb* tc_curr_col(struct ToolCtx* tc);
static void tc_set_tool(struct ToolCtx* tc, enum ToolTag type, union ToolData* td_opt);
static char const* tc_get_tool_name(struct ToolCtx const* tc);
static void tc_free(Display* dp, struct ToolCtx* tc);
// free: with `free` and `arrfree`
static char** xft_get_fonts_arr(void);
static Bool xft_font_set(struct DrawCtx* dc, char const* font_name, XftFont** fnt_out);
static char const* xft_font_name(XftFont* fnt);
static struct IOCtx ioctx_new(char const* input);
static struct IOCtx ioctx_copy(struct IOCtx const* ioctx);
static void ioctx_set(struct IOCtx* ioctx, char const* input);
static char const* ioctx_as_str(struct IOCtx const* ioctx);
static void ioctx_free(struct IOCtx* ioctx);
static Pt pt_from_cv_to_scr(struct DrawCtx const* dc, Pt p);
static Pt pt_from_cv_to_scr_xy(struct DrawCtx const* dc, i32 x, i32 y);
static Pt pt_from_scr_to_cv_xy(struct DrawCtx const* dc, i32 x, i32 y);
static Pt pt_apply_trans(Pt p, Transform trans);
static Pt pt_apply_trans_pivot(Pt p, Transform trans, Pt pivot);
static Pt dpt_to_pt(DPt p);
static DPt pt_to_dpt(Pt p);
static DPt dpt_rotate(DPt p, double deg); // clockwise
static DPt dpt_add(DPt a, DPt b);
static double dpt_dist(DPt a, DPt b);
static enum ImageType file_type(u8 const* data, u32 len);
static u8* ximage_to_rgb(XImage const* image, Bool rgba);
static Bool ximage_is_valid_pt(XImage const* im, i32 x, i32 y);
static Rect ximage_rect(XImage const* im);
// coefficient c is proportional to the significance of component a
static argb argb_blend(argb a, argb b, u8 c);
// receives premultiplied argb value
static argb argb_normalize(argb c);
static argb argb_from_hsl(double hue, double sat, double light);
// XXX hex non-const because of implementation
static Bool argb_from_hex_col(char* hex, argb* argb_out);
static XRenderColor argb_to_xrender_color(argb col);
static struct Image read_file_from_memory(struct DrawCtx const* dc, u8 const* data, u32 len, argb bg);
static struct Image read_image_io(struct DrawCtx const* dc, struct IOCtx const* ioctx, argb bg);
static void ioctx_write_part(void* pctx, void* data, i32 size);
static Bool write_io(struct DrawCtx* dc, struct Input const* input, enum ImageType type, struct IOCtx const* ioctx);
static void image_free(struct Image* im);
static ClCPrcResult cl_cmd_process(struct Ctx* ctx, struct ClCommand const* cl_cmd);
static ClCPrsResult cl_cmd_parse(struct Ctx* ctx, char const* cl);
static ClCPrsResult cl_prs_noarg(char* arg_desc_dyn, char* context_optdyn);
static ClCPrsResult cl_prs_invarg(char* arg_dyn, char* error_dyn, char* context_optdyn);
static void cl_cmd_parse_res_free(ClCPrsResult* res);
static char* cl_cmd_get_str_dyn(struct InputConsoleData const* d_cl);
static char const* cl_cmd_descr(enum ClCTag t);
static char const* cl_set_prop_descr(enum ClCDSTag t);
static enum ImageType cl_save_type_to_image_type(enum ClCDSv t);
// returns number of completions
static usize cl_compls_new(struct InputConsoleData* cl);
static void cl_free(struct InputConsoleData* cl);
static void cl_compls_free(struct InputConsoleData* cl);
static void cl_push(struct InputConsoleData* cl, char c);
static Bool cl_pop(struct InputConsoleData* cl, Bool force_no_compls);
static void input_set_damage(struct Input* inp, Rect damage);
static void input_mode_set(struct Ctx* ctx, enum InputTag mode_tag);
static void input_mode_free(struct InputMode* input_mode);
static char const* input_mode_as_str(enum InputTag mode_tag);
static void input_free(struct Input* input);
static InputModeFlags input_mode_to_flag(enum InputTag mode);
static void text_mode_push(struct Ctx* ctx, char c);
static Bool text_mode_pop(struct Ctx* ctx);
static void text_mode_rerender(struct Ctx* ctx);
static void sel_circ_init_and_show(struct Ctx* ctx, Button button, i32 x, i32 y);
static void sel_circ_free_and_hide(struct SelectionCircle* sel_circ);
static i32 sel_circ_curr_item(struct SelectionCircle const* sc, i32 x, i32 y);
// selection circle item callbacks. Can be unused, if config changed
__attribute__((unused))
static void sel_circ_on_select_tool(struct Ctx* ctx, union SCI_Arg tool);
__attribute__((unused))
static void sel_circ_on_select_drawer(struct Ctx* ctx, union SCI_Arg tool);
__attribute__((unused))
static void sel_circ_on_select_figure_toggle_fill(struct Ctx* ctx, __attribute__((unused)) union SCI_Arg arg);
__attribute__((unused))
static void sel_circ_on_select_figure(struct Ctx* ctx, union SCI_Arg figure);
__attribute__((unused))
static void sel_circ_on_select_col(struct Ctx* ctx, union SCI_Arg col);
__attribute__((unused))
static void sel_circ_on_select_set_linew(struct Ctx* ctx, union SCI_Arg num);
// separate functions, because they are callbacks
static Rect tool_selection_on_release(struct Ctx* ctx, XButtonReleasedEvent const* event);
static Rect tool_text_on_release(struct Ctx* ctx, XButtonReleasedEvent const* event);
static Rect tool_selection_on_drag(struct Ctx* ctx, XMotionEvent const* event);
static Rect tool_drawer_on_press(struct Ctx* ctx, XButtonPressedEvent const* event);
static Rect tool_drawer_on_release(struct Ctx* ctx, XButtonReleasedEvent const* event);
static Rect tool_drawer_on_drag(struct Ctx* ctx, XMotionEvent const* event);
static Rect tool_figure_on_press(struct Ctx* ctx, XButtonPressedEvent const* event);
static Rect tool_figure_on_release(struct Ctx* ctx, XButtonReleasedEvent const* event);
static Rect tool_figure_on_drag(struct Ctx* ctx, XMotionEvent const* event);
static Rect tool_fill_on_release(struct Ctx* ctx, XButtonReleasedEvent const* event);
static Rect tool_picker_on_release(struct Ctx* ctx, XButtonReleasedEvent const* event);
// canvas_line callbacks
struct CanvasLineDrwCtxDrawer {
XImage* im;
struct Brush* brush_in_out;
struct DrawerData data;
u32 line_w;
argb col;
};
static Rect canvas_line_drawer_callback(void* drw_ctx, Pt p);
struct CanvasLineDrwCtxFloodFill {
XImage* im;
argb col;
};
static Rect canvas_line_flood_fill_callback(void* drw_ctx, Pt p);
static struct HistItem history_new_as_damage(XImage* im, Rect rect);
static struct HistItem history_new_as_resize(XImage* im);
static Bool history_move(struct Ctx* ctx, Bool forward);
static void history_forward(struct Ctx* ctx, struct HistItem hist);
static void history_apply(struct Ctx* ctx, struct HistItem* hist);
static void history_free(struct HistItem* hist);
static void historyarr_clear(struct HistItem** hist);
static usize ximage_data_len(XImage const* im);
static XImage* ximage_apply_xtrans(XImage* im, struct DrawCtx* dc, XTransform xtrans);
static void ximage_blend(XImage* dest, XImage* overlay, Rect blend_mask);
static void ximage_clear(XImage* im, Rect mask);
static Bool ximage_put_checked(XImage* im, i32 x, i32 y, argb col);
static Rect ximage_flood_fill(XImage* im, argb targ_col, i32 x, i32 y);
static Rect ximage_calc_damage(XImage* im);
static Rect canvas_text(struct DrawCtx* dc, XImage* im, Pt lt_c, XftFont* font, argb col, char const* text, u32 text_len);
static Rect canvas_dash_rect(XImage* im, Pt c, Pt dims, u32 w, u32 dash_w, argb col1, argb col2);
static Rect canvas_fill_rect(XImage* im, Pt c, Pt dims, argb col);
static Rect canvas_rect(XImage* im, Pt c, Pt dims, u32 line_w, argb col);
// HACK variant argument not clear (used to alternate figure type)
static Rect canvas_figure(struct Ctx* ctx, XImage* im, u32 variant, Pt p_static, Pt p_dynamic);
// line from `a` to `b` is a polygon height (a is a base);
static Rect canvas_regular_poly(XImage* im, struct ToolCtx* tc, u32 n, Pt a, Pt b, Bool fill);
static Rect canvas_line(Rect (*drawer)(void* drw_ctx, Pt p), void* drw_ctx, Pt from, Pt to, u32 line_w, double spacing, Bool draw_first_pt);
static Rect canvas_line_no_spacing(Rect (*drawer)(void* drw_ctx, Pt p), void* drw_ctx, Pt from, Pt to);
static Rect canvas_apply_drawer(XImage* im, struct DrawerData data, u32 line_w, argb col, Pt c, struct Brush* brush_in_out);
static Rect canvas_copy_region(XImage* dest, XImage* src, Pt from, Pt dims, Pt to);
static void canvas_fill(XImage* im, argb col);
static Bool canvas_load(struct Ctx* ctx, struct Image* image);
static void canvas_free(struct Canvas* cv);
static void canvas_change_zoom(struct DrawCtx* dc, Pt cursor, i32 delta);
static void canvas_resize(struct Ctx* ctx, u32 new_width, u32 new_height);
static void canvas_scroll(struct Canvas* cv, DPt delta);
static void overlay_clear(struct InputOverlay* ovr);
static void overlay_expand_rect(struct InputOverlay* ovr, Rect rect);
static struct InputOverlay get_transformed_overlay(struct DrawCtx* dc, struct Input const* inp);
static void overlay_free(struct InputOverlay* ovr);
static u32 statusline_height(struct DrawCtx const* dc);
// window size - interface parts (e.g. statusline)
static Pt clientarea_size(struct DrawCtx const* dc);
static Pt canvas_size(struct DrawCtx const* dc);
static void draw_arc(struct DrawCtx* dc, Pt c, Pt dims, double a1, double a2, argb col);
static void fill_arc(struct DrawCtx* dc, Pt c, Pt dims, double a1, double a2, argb col);
static u32 draw_string(struct DrawCtx* dc, char const* str, Pt c, enum Schm sc, Bool invert);
static u32 draw_int(struct DrawCtx* dc, i32 i, Pt c, enum Schm sc, Bool invert);
static int fill_rect(struct DrawCtx* dc, Pt p, Pt dim, argb col);
static int draw_line_ex(struct DrawCtx* dc, Pt from, Pt to, u32 w, int line_style, enum Schm sc, Bool invert);
static int draw_line(struct DrawCtx* dc, Pt from, Pt to, u32 w, enum Schm sc, Bool invert);
static void draw_dash_line(struct DrawCtx* dc, Pt from, Pt to, u32 w);
static void draw_dash_rect(struct DrawCtx* dc, Pt pts[4]);
static void draw_dash_cross(struct DrawCtx* dc, Pt cv_center, i32 radius);
// FIXME merge with get_string_rect?
static u32 get_string_width(struct DrawCtx const* dc, char const* str, u32 len);
static Rect get_string_rect(struct DrawCtx const* dc, XftFont* font, char const* str, u32 len, Pt lt_c);
static void draw_selection_circle(struct Ctx* ctx, struct SelectionCircle const* sc, i32 pointer_x, i32 pointer_y);
static void update_screen(struct Ctx* ctx, Pt cur_scr, Bool full_redraw);
static void update_statusline(struct Ctx* ctx);
static void show_message(struct Ctx* ctx, char const* msg);
static void swap_backbuffer(struct Ctx* ctx);
static void dc_cache_init(struct Ctx* ctx);
static void dc_cache_free(struct DrawCtx* dc);
// update Pixmaps for XRender interactions
static void dc_cache_update(struct Ctx* ctx, Rect damage);
static void brush_cache_free(struct Brush* brush);
static void brush_cache_update(struct DrawerData const* data, u32 line_w, argb col, struct Brush* brush_in_out);
static int trigger_clipboard_paste(struct DrawCtx* dc, Atom selection_target);
static struct Ctx ctx_init(Display* dp);
static void xextinit(Display* dp);
static void setup(Display* dp, struct Ctx* ctx);
static void run(struct Ctx* ctx);
static HdlrResult button_press_hdlr(struct Ctx* ctx, XEvent* event);
static HdlrResult button_release_hdlr(struct Ctx* ctx, XEvent* event);
static HdlrResult destroy_notify_hdlr(struct Ctx* ctx, XEvent* event);
static HdlrResult expose_hdlr(struct Ctx* ctx, XEvent* event);
static HdlrResult key_press_hdlr(struct Ctx* ctx, XEvent* event);
static HdlrResult mapping_notify_hdlr(struct Ctx* ctx, XEvent* event);
static HdlrResult motion_notify_hdlr(struct Ctx* ctx, XEvent* event);
static HdlrResult configure_notify_hdlr(struct Ctx* ctx, XEvent* event);
static HdlrResult selection_request_hdlr(struct Ctx* ctx, XEvent* event);
static HdlrResult selection_notify_hdlr(struct Ctx* ctx, XEvent* event);
static HdlrResult client_message_hdlr(struct Ctx* ctx, XEvent* event);
static void cleanup(struct Ctx* ctx);
// clang-format on
static Bool is_verbose_output = False;
static Atom atoms[A_Last];
static XImage* images[I_Last];
#include "config.h"
// include debug.h if exists (for debug functions)
#if defined(__has_include)
#if __has_include("debug.h")
#include "debug.h" // IWYU pragma: keep
#endif
#endif
// clang-format off
static void main_die_if_no_val_for_arg(char const* cmd_name, i32 argc, char** argv, u32 pos);
static Bool main_process_args(struct Ctx* ctx, i32 argc, char** argv);
static void main_show_help_message(FILE* out);
// clang-format on
i32 main(i32 argc, char** argv) {
Display* display = XOpenDisplay(NULL);
if (!display) {
die("cannot open X display");
}
struct Ctx ctx = ctx_init(display);
if (!main_process_args(&ctx, argc, argv)) {
main_show_help_message(stderr);
exit(1);
}
xextinit(display);
setup(display, &ctx);
run(&ctx);
cleanup(&ctx);
XCloseDisplay(display);
return EXIT_SUCCESS;
}
void main_die_if_no_val_for_arg(char const* cmd_name, i32 argc, char** argv, u32 pos) {
if ((i32)pos + 1 == argc || argv[pos + 1][0] == '-') {
die("supply argument for %s", cmd_name);
}
}
Bool main_process_args(struct Ctx* ctx, i32 argc, char** argv) {
Bool result = True;
for (i32 i = 1; i < argc; ++i) {
if (argv[i][0] != '-' || !strcmp(argv[i], IOCTX_STDIO_STR)) {
ioctx_set(&ctx->inp, argv[i]);
ioctx_set(&ctx->out, argv[i]);
} else if (!strcmp(argv[i], "-V") || !strcmp(argv[i], "--version")) {
printf("xpaint " VERSION "\n");
exit(0);
} else if (!strcmp(argv[i], "-v") || !strcmp(argv[i], "--verbose")) {
is_verbose_output = True;
} else if (!strcmp(argv[i], "-i") || !strcmp(argv[i], "--input")) {
main_die_if_no_val_for_arg("-i or --input", argc, argv, i);
ioctx_set(&ctx->inp, argv[++i]);
} else if (!strcmp(argv[i], "-o") || !strcmp(argv[i], "--output")) {
main_die_if_no_val_for_arg("-o or --output", argc, argv, i);
ioctx_set(&ctx->out, argv[++i]);
} else if (!strcmp(argv[i], "-W") || !strcmp(argv[i], "--width")) {
main_die_if_no_val_for_arg("-W or --width", argc, argv, i);
// ctx.dc.width == ctx.dc.cv.im->width at program start
ctx->dc.width = strtol(argv[++i], NULL, 0);
if (!ctx->dc.width) {
die("canvas width must be positive number");
}
} else if (!strcmp(argv[i], "-H") || !strcmp(argv[i], "--height")) {
main_die_if_no_val_for_arg("-H or --height", argc, argv, i);
// ctx.dc.height == ctx.dc.cv.im->height at program start
ctx->dc.height = strtol(argv[++i], NULL, 0);
if (!ctx->dc.height) {
die("canvas height must be positive number");
}
} else if (!strcmp(argv[i], "-h") || !strcmp(argv[i], "--help")) {
main_show_help_message(stdout);
exit(0);
} else {
(void)fprintf(stderr, "Unknown argument '%s'\n", argv[i]);
result = False;
}
}
return result;
}
void main_show_help_message(FILE* out) {
(void)fprintf(
out,
"Usage: xpaint [OPTIONS] [FILE]\n"
"\n"
"Options:\n"
" -h, --help Print help message\n"
" -V, --version Print version\n"
" -v, --verbose Use verbose output\n"
" -W, --width <canvas width> Set canvas width\n"
" -H, --height <canvas height> Set canvas height\n"
" -i, --input <file path> Set load file\n"
" -o, --output <file path> Set save file\n"
);
}
void die(char const* errstr, ...) {
va_list ap;
(void)fprintf(stderr, "xpaint: ");
va_start(ap, errstr);
(void)vfprintf(stderr, errstr, ap);
va_end(ap);
(void)fprintf(stderr, "\n");
exit(EXIT_FAILURE);
}
void trace(char const* fmt, ...) {
if (is_verbose_output) {
va_list ap;
va_start(ap, fmt);