-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathpaniview.c
3147 lines (2519 loc) · 84.8 KB
/
paniview.c
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
/*
* PaniView
* A lightweight image viewer for Windows
*
* Copyright (c) 2021-2023 Aragajaga, Philosoph228 <[email protected]>
*/
#include "precomp.h"
#include "resource.h"
#include "dlnklist.h"
#include "hashmap.h"
#include <GL/glew.h>
#include <GL/wglew.h>
/* Custom window message definitions for render control */
#define WM_SENDNUDES WM_USER + 1
#define WM_FILENEXT WM_USER + 2
#define WM_FILEPREV WM_USER + 3
#define WM_ZOOMOUT WM_USER + 4
#define WM_ZOOMIN WM_USER + 5
#define WM_ACTUALSIZE WM_USER + 6
#define WM_FIT WM_USER + 7
/* COM object releaser */
#define SAFE_RELEASE(obj) \
if (obj) { \
((IUnknown *)(obj))->lpVtbl->Release((IUnknown *)(obj)); \
(obj) = NULL; \
}
#ifndef ASSERT
#ifndef NDEBUG
#define ASSERT(b) \
do { \
if (!(b)) { \
OutputDebugString(L"Assert: " #b "\n"); \
} \
} while(0)
#else
#define ASSERT(b)
#endif
#endif
const FLOAT DEFAULT_DPI = 96.f;
typedef struct _tagMAINFRAMEDATA {
HWND hRenderer;
HWND hToolbar;
} MAINFRAMEDATA, *LPMAINFRAMEDATA;
typedef struct _tagRENDERCTL2 RENDERCTL2, * LPRENDERCTL2;
typedef struct _tagRENDERERCONTEXT RENDERERCONTEXT, *LPRENDERERCONTEXT;
/* Base renderer context data structure */
struct _tagRENDERERCONTEXT {
void (*Release)(LPRENDERERCONTEXT pRendererContext);
void (*Resize)(LPRENDERERCONTEXT pRendererContext, int cx, int cy);
void (*Draw)(LPRENDERERCONTEXT pRendererContext, LPRENDERCTL2 pRenderCtl);
void (*LoadWICBitmap)(LPRENDERERCONTEXT pRendererContext, IWICBitmapSource* pIWICBitmapSource);
};
LPRENDERERCONTEXT CreateRendererContext(void);
/* Render control data */
struct _tagRENDERCTLDATA {
WCHAR szPath[MAX_PATH];
HWND m_hWnd;
};
/*
* Renderer type enum
* Used when loading/saving renderer type from settings
*/
enum {
RENDERER_D2D = 1,
RENDERER_OPENGL = 2,
RENDERER_GDI = 3,
};
enum {
TITLEPATH_NONE = 1,
TITLEPATH_FILE = 2,
TITLEPATH_FULL = 3,
};
enum {
TOOLBARTHEME_DEFAULT_16PX = 1,
TOOLBARTHEME_DEFAULT_24PX = 2,
TOOLBARTHEME_DEFAULT_32PX = 3,
TOOLBARTHEME_MODERN_24PX = 4,
TOOLBARTHEME_FUGUEICONS_24PX = 5,
};
enum {
MIME_UNKNOWN = 0,
MIME_IMAGE_PNG = 1,
MIME_IMAGE_JPG = 2,
MIME_IMAGE_GIF = 3,
MIME_IMAGE_WEBP = 4,
MIME_IMAGE_PGM = 5,
};
typedef struct _tagSETTINGS SETTINGS, * LPSETTINGS;
typedef struct _tagNAVIASSOCENTRY NAVIASSOCENTRY, * LPNAVIASSOCENTRY;
typedef struct _tagPANIVIEWAPP PANIVIEWAPP, * LPPANIVIEWAPP;
/*
* Settings data struct
* This will be saved as binary to the settings.dat
*/
struct _tagSETTINGS {
unsigned char magic[4];
unsigned long nVersion;
unsigned long checksum;
BOOL bEulaAccepted;
BOOL bNaviLoop;
BOOL bNaviAnyFile;
int nPathInTitleType;
int nRendererType;
int nToolbarTheme;
BOOL bFit;
};
struct _tagNAVIASSOCENTRY {
WCHAR szExtension[80];
int nMimeType;
};
HINSTANCE g_hInst;
const unsigned char g_cfgMagic[4] = { 'P', 'N', 'V', 0xE5 };
const unsigned char g_pngMagic[] = { 0x89, 'P', 'N', 'G', 0x0D, 0x0A, 0x1A, 0x0A };
const unsigned char g_gifMagic[] = { 'G', 'I', 'F', '8'};
const unsigned char g_jpgMagic[] = { 0xFF, 0xD8, 0xFF };
const unsigned char g_webpMagic[] = { 'R', 'I', 'F', 'F', 'W', 'E', 'B', 'P' };
const unsigned char g_pgmMagic[] = { 'P', '5' };
const WCHAR szPaniView[] = L"PaniView";
const WCHAR szPaniViewClassName[] = L"PaniView_Main";
const WCHAR szRenderCtlClassName[] = L"PaniView_Renderer";
PWSTR g_pszAboutString;
int APIENTRY wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PWSTR lpCmdLine, int nCmdShow);
BOOL InitInstance(HINSTANCE hInstance);
void PopupError(DWORD dwError, HWND hParent);
BOOL PopupEULADialog(void);
BOOL GetApplicationDataSitePath(PWSTR* ppStr);
unsigned long crc32(unsigned char *data, size_t length);
int compareHWND(const void* key1, size_t key1Size, const void* key2, size_t key2Size);
/* Direct2D Utility functions forward declaration */
static inline D2D1_MATRIX_3X2_F D2DUtilMatrixIdentity(void);
static inline D2D1_MATRIX_3X2_F D2DUtilMakeTranslationMatrix(D2D1_SIZE_F size);
static inline D2D1_MATRIX_3X2_F D2DUtilMatrixMultiply(D2D1_MATRIX_3X2_F *a, D2D1_MATRIX_3X2_F *b);
static inline D2D1_COLOR_F D2DColorFromRGBAndAlpha(COLORREF color, float alpha);
static inline D2D1_COLOR_F D2DColorFromRGB(COLORREF color);
size_t GetPfFileSize(FILE* fp);
void OrthoMatrix(GLfloat mat[4][4], GLfloat width, GLfloat height);
void MatrixMultiply(GLfloat mat1[4][4], GLfloat mat2[4][4], GLfloat mat3[4][4]);
int Rect_GetWidth(LPRECT prc);
int Rect_GetHeight(LPRECT prc);
BOOL RectBlt(HDC hdcDest, RECT rcDest, HDC hdcSrc, RECT rcSrc, DWORD rop);
void RectMatrixMultiply(LPRECT prc, float mat[4][4]);
void RectTranslate(LPRECT prc, float x, float y);
BOOL Settings_LoadFile(SETTINGS *pSettings, PWSTR pszPath);
BOOL Settings_SaveFile(SETTINGS *pSettings, PWSTR pszPath);
BOOL Settings_LoadDefault(SETTINGS *pSettings);
IWICBitmapSource* WICDecodeFromFilename(LPWSTR pszPath);
IWICBitmapSource* WICLoadFromMemory(int width, int height, unsigned char* data, REFWICPixelFormatGUID pixelFormat);
/* Window */
typedef struct _tagWINDOW WINDOW, * LPWINDOW;
struct _tagWINDOW {
void (*PreRegisterClass)(LPWINDOW pWindow, LPWNDCLASSEX lpwcex);
void (*PreCreate)(LPWINDOW pWindow, LPCREATESTRUCT lpcs);
LRESULT (*WndProc)(LPWINDOW pWindow, UINT message, WPARAM wParam, LPARAM lParam);
LRESULT (*DefaultWndProc)(LPWINDOW pWindow, UINT message, WPARAM wParam, LPARAM lParam);
void (*OnCreate)(LPWINDOW pWindow, LPCREATESTRUCT lpcs);
BOOL (*OnCommand)(LPWINDOW pWindow, WPARAM wParam, LPARAM lParam);
void (*OnPaint)(LPWINDOW pWindow);
void (*OnDestroy)(LPWINDOW pWindow);
HWND hWnd;
PWSTR pszClassName;
CREATESTRUCT cs;
WNDCLASSEX wcex;
};
void Window_Init(LPWINDOW pWindow);
HWND Window_Create(LPWINDOW pWindow, HWND hParent);
void Window_RegisterClass(LPWINDOW pWindow);
LRESULT Window_WndProc(LPWINDOW pWindow, UINT message, WPARAM wParam, LPARAM lParam);
LRESULT Window_DefaultWindowProc(LPWINDOW pWindow, UINT message, WPARAM wParam, LPARAM lParam);
void Window_OnCreate(LPWINDOW pWindow, LPCREATESTRUCT lpcs);
BOOL Window_OnCommand(LPWINDOW pWindow, WPARAM wParam, LPARAM lParam);
void Window_OnPaint(LPWINDOW pWindow);
void Window_OnDestroy(LPWINDOW pWindow);
/* Window Map */
HASHMAP g_windowMap;
void WindowMap_Initialize(void);
void WindowMap_Add(HWND hWnd, LPWINDOW pWindow);
LPWINDOW WindowMap_Find(HWND hWnd);
/* RenderCtl2 */
struct _tagRENDERCTL2 {
WINDOW base;
};
void RenderCtl2_Init(LPRENDERCTL2 pRenderCtl);
HWND RenderCtl2_Create(LPRENDERCTL2 pRenderCtl, HWND hParent);
LRESULT RenderCtl2_WndProc(LPRENDERCTL2 pRenderCtl, UINT message, WPARAM wParam, LPARAM lParam);
void RenderCtl2_PreCreate(LPRENDERCTL2 pRenderCtl, LPCREATESTRUCT lpcs);
void RenderCtl2_PreRegister(LPRENDERCTL2 pRenderCtl, LPWNDCLASSEX lpwcex);
void RenderCtl2_OnCreate(LPRENDERCTL2 pRenderCtl, LPCREATESTRUCT lpcs);
void RenderCtl2_OnSize(LPRENDERCTL2 pRenderCtl, UINT state, int cx, int cy);
void RenderCtl2_OnPaint(LPRENDERCTL2 pRenderCtl);
BOOL RenderCtl2_OnCommand(LPRENDERCTL2 pRenderCtl, WPARAM wParam, LPARAM lParam);
void RenderCtl2_OnDestroy(LPRENDERCTL2 pRenderCtl);
/* PaniView Frame */
typedef struct _tagPANIVIEWFRAME PANIVIEWFRAME, *LPPANIVIEWFRAME;
struct _tagPANIVIEWFRAME {
WINDOW base;
HWND hToolbar;
};
void PaniViewFrame_Init(LPPANIVIEWFRAME pPaniViewFrame);
HWND PaniViewFrame_Create(LPPANIVIEWFRAME pPaniViewFrame);
LRESULT PaniViewFrame_WndProc(LPPANIVIEWFRAME pPaniViewFrame, UINT message, WPARAM wParam, LPARAM lParam);
void PaniViewFrame_PreCreate(LPPANIVIEWFRAME pPaniViewFrame, LPCREATESTRUCT lpcs);
void PaniViewFrame_PreRegister(LPPANIVIEWFRAME pPaniViewFrame, LPWNDCLASSEX lpwcex);
void PaniViewFrame_OnCreate(LPPANIVIEWFRAME pPaniViewFrame, LPCREATESTRUCT lpcs);
void PaniViewFrame_OnSize(LPPANIVIEWFRAME pPaniViewFrame, UINT state, int cx, int cy);
BOOL PaniViewFrame_OnCommand(LPPANIVIEWFRAME pPaniViewFrame, WPARAM wParam, LPARAM lParam);
void PaniViewFrame_OnDestroy(LPPANIVIEWFRAME pPaniViewFrame);
void PaniViewFrame_OnFileOpenCommand(LPPANIVIEWFRAME pPaniViewFrame);
void PaniViewFrame_OnViewPrevCommand(LPPANIVIEWFRAME pPaniViewFrame);
void PaniViewFrame_OnViewNextCommand(LPPANIVIEWFRAME pPaniViewFrame);
void PaniViewFrame_OnViewFitCommand(LPPANIVIEWFRAME pPaniViewFrame);
/* Direct2D renderer context data structure */
typedef struct _tagD2DRENDERERCONTEXT {
RENDERERCONTEXT base;
ID2D1Factory* m_pD2DFactory;
ID2D1HwndRenderTarget* m_pRenderTarget;
ID2D1Bitmap* m_pD2DBitmap;
ID2D1SolidColorBrush* m_pLightSlateGrayBrush;
ID2D1SolidColorBrush* m_pCornflowerBlueBrush;
} D2DRENDERERCONTEXT, * LPD2DRENDERERCONTEXT;
/* Direct2D Renderer context forward declarations */
HRESULT D2DRendererContext_CreateDeviceResources(LPD2DRENDERERCONTEXT pD2DRendererContext, HWND hWnd);
void D2DRendererContext_DiscardDeviceResources(LPD2DRENDERERCONTEXT pD2DRendererContext);
void D2DRendererContext_Draw(LPD2DRENDERERCONTEXT pD2DRendererContext, LPRENDERCTL2 pRenderCtl);
void D2DRendererContext_LoadWICBitmap(LPD2DRENDERERCONTEXT pD2DRendererContext, IWICBitmapSource* pIWICBitmapSource);
void D2DRendererContext_Release(LPD2DRENDERERCONTEXT pD2DRendererContext);
void D2DRendererContext_Resize(LPD2DRENDERERCONTEXT pD2DRendererContext, int cx, int cy);
void InitializeD2DRendererContextStruct(LPD2DRENDERERCONTEXT pD2DRendererContext);
LPD2DRENDERERCONTEXT CreateD2DRenderer(void);
/* OpenGL renderer context data structure */
typedef struct _tagOPENGLRENDERERCONTEXT {
RENDERERCONTEXT base;
HGLRC m_hGLContext;
GLuint m_textureId;
GLuint m_programId;
GLuint m_vertexArray;
GLuint m_vertexBuffer;
GLuint m_uvBuffer;
float m_viewportWidth;
float m_viewportHeight;
float m_imageWidth;
float m_imageHeight;
} OPENGLRENDERERCONTEXT, * LPOPENGLRENDERERCONTEXT;
/* OpenGL Renderer context forward declarations */
void OpenGLRendererContext_CreateVBO(LPOPENGLRENDERERCONTEXT pGLRendererContext);
void OpenGLRendererContext_CreateDeviceResources(LPOPENGLRENDERERCONTEXT pGLRendererContext, LPRENDERCTL2 pRenderCtl);
void OpenGLRendererContext_CreateTexture(LPOPENGLRENDERERCONTEXT pGLRendererContext);
void OpenGLRendererContext_Draw(LPOPENGLRENDERERCONTEXT pGLRendererContext, LPRENDERCTL2 pRenderCtl);
void OpenGLRendererContext_DrawVBO(LPOPENGLRENDERERCONTEXT pGLRendererContext);
GLuint OpenGLRendererContext_LoadShader(LPOPENGLRENDERERCONTEXT lpGLRendererContext, PCWSTR shaderFilePath, GLuint shaderType);
GLuint OpenGLRendererContext_LoadShaders(LPOPENGLRENDERERCONTEXT pGLRendererContext, PCWSTR vertexFilePath, PCWSTR fragmentFilePath);
void OpenGLRendererContext_LoadWICBitmap(LPOPENGLRENDERERCONTEXT pGLRendererContext, IWICBitmapSource* pBitmapSource);
void OpenGLRendererContext_Release(LPOPENGLRENDERERCONTEXT pGLRendererContext);
void OpenGLRendererContext_Resize(LPOPENGLRENDERERCONTEXT pGLRendererContext, int cx, int cy);
void InitializeOpenGLRendererContextStruct(LPOPENGLRENDERERCONTEXT pGLRendererContext);
LPOPENGLRENDERERCONTEXT CreateOpenGLRenderer(void);
/* GDI renderer context data structure */
typedef struct _tagGDIRENDERERCONTEXT {
RENDERERCONTEXT base;
HBITMAP m_hBitmap;
int m_width;
int m_height;
} GDIRENDERERCONTEXT, * LPGDIRENDERERCONTEXT;
/* GDI Renderer context forward declarations */
void GDIRendererContext_Draw(LPGDIRENDERERCONTEXT pGDIRendererContext, LPRENDERCTL2 pRenderCtl);
void GDIRendererContext_LoadWICBitmap(LPGDIRENDERERCONTEXT pGDIRendererContext, IWICBitmapSource* pBitmapSource);
void GDIRendererContext_Release(LPGDIRENDERERCONTEXT pGDIRendererContext);
void GDIRendererContext_Resize(LPGDIRENDERERCONTEXT pGDIRendererContext, int cx, int cy);
void InitializeGDIRendererContextStruct(LPGDIRENDERERCONTEXT pGDIRendererContext);
LPGDIRENDERERCONTEXT CreateGDIRenderer(void);
HRESULT InvokeFileOpenDialog(LPWSTR* ppszPath);
int GetFileMIMEType(PCWSTR pszPath);
BOOL NextFileInDir(PWSTR pszCurrent, BOOL fNext, PWSTR lpPathOut);
INT_PTR CALLBACK AboutDlgProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);
INT_PTR CALLBACK SettingsDlgProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);
INT_PTR CALLBACK EULADlgProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);
struct _tagPANIVIEWAPP {
PANIVIEWFRAME mainFrame;
RENDERCTL2 renderCtl;
SETTINGS m_settings;
PWSTR m_appDataSite;
PWSTR m_appCfgPath;
IWICImagingFactory* m_pIWICFactory;
IWICFormatConverter* m_pConvertedSourceBitmap;
LPRENDERERCONTEXT m_rendererContext;
PWSTR pszImagePath;
};
/* Application object methods forward declarations */
static inline LPPANIVIEWAPP GetApp(void);
BOOL PaniViewApp_Initialize(LPPANIVIEWAPP pApp);
PWSTR PaniViewApp_GetAppDataSitePath(LPPANIVIEWAPP pApp);
PWSTR PaniViewApp_GetSettingsFilePath(LPPANIVIEWAPP pApp);
BOOL PaniViewApp_CreateAppDataSite(LPPANIVIEWAPP pApp);
BOOL PaniViewApp_LoadSettings(LPPANIVIEWAPP pApp);
BOOL PaniViewApp_SaveSettings(LPPANIVIEWAPP pApp);
BOOL PaniViewApp_LoadDefaultSettings(LPPANIVIEWAPP pApp);
void PaniViewApp_SetTitle(LPPANIVIEWAPP pApp, PWSTR pszTitle);
void PaniViewApp_UpdateViewport(void);
void PaniViewApp_SetFilePath(PWSTR pszPath);
void PaniViewApp_NextFile(void);
void PaniViewApp_PrevFile(void);
void PaniViewApp_ToggleFit(void);
LPRENDERERCONTEXT PaniViewApp_GetRendererContext(void);
HRESULT PaniViewApp_InitializeWIC(void);
HRESULT PaniViewApp_LoadFromFilePGM(PWSTR pszPath, FILE* pf);
HRESULT PaniViewApp_LoadFromFileWIC(PWSTR pszPath);
HRESULT PaniViewApp_LoadFromFile(PWSTR pszPath);
void PaniViewApp_OnCommand(WPARAM wParam, LPARAM lParam);
/*
* wWinMain
* Main entry point for the PaniView aplication
*
* There all starts and ends.
* After main application window being closed, the `GetMessage` Windows API
* call will catch a `WM_QUIT` message and break the application loop.
*
* After application loop break the process of shutdowing the application
* begins, evolving settings data on disk: window position, recent file list,
* etc.
*/
int APIENTRY wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PWSTR lpCmdLine, int nCmdShow)
{
UNREFERENCED_PARAMETER(hPrevInstance);
UNREFERENCED_PARAMETER(lpCmdLine);
MSG msg = { 0 };
DWORD dwError;
HRESULT hr = S_OK;
/* Initialize COM API */
hr = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE);
dwError = GetLastError();
if (FAILED(hr)) {
PopupError(dwError, NULL);
return -1;
}
/* Initialize common controls */
BOOL bStatus = FALSE;
INITCOMMONCONTROLSEX iccex = { 0 };
iccex.dwSize = sizeof(iccex);
iccex.dwICC = ICC_STANDARD_CLASSES | ICC_BAR_CLASSES;
bStatus = InitCommonControlsEx(&iccex);
if (!bStatus) {
dwError = GetLastError();
assert(FALSE);
PopupError(dwError, NULL);
}
LPPANIVIEWAPP pApp = GetApp();
if (!PaniViewApp_Initialize(pApp)) {
return -1;
}
InitInstance(hInstance); /* Initialize the application's instance — register
classes, lock single instance mutexes, etc. */
WindowMap_Initialize();
PaniViewFrame_Init(&pApp->mainFrame);
HWND hWndMain = PaniViewFrame_Create(&pApp->mainFrame);
RenderCtl2_Init(&pApp->renderCtl);
RenderCtl2_Create(&pApp->renderCtl, hWndMain);
ShowWindow(hWndMain, nCmdShow);
UpdateWindow(hWndMain); /* Force window contents paint inplace after
creating*/
// Open file from shell command
if (lpCmdLine[0] != L'\0') {
int nArgs = 0;
PWSTR* ppszArgv = CommandLineToArgvW(lpCmdLine, &nArgs);
if (nArgs > 0) {
PathUnquoteSpaces(ppszArgv[0]);
PaniViewApp_LoadFromFile(ppszArgv[0]);
}
}
/*
* Application loop
*
* Will be pumped till WM_QUIT message will noticed.
* Then application uninitialization and shutdown begins.
*/
while (GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg); /* Translate key messages into proper keycodes */
DispatchMessage(&msg); /* Proceed message into dispatcher */
}
CoUninitialize();
if (!PaniViewApp_SaveSettings(pApp)) {
MessageBox(NULL, L"Unable to save settings data", NULL, MB_OK | MB_ICONERROR);
}
/* Application shutdown */
return (int) msg.wParam;
}
/*
* InitInstance
* Application instantiating routine. Registers the application's main window
* class, sharing the instance handle access.
*/
BOOL InitInstance(HINSTANCE hInstance)
{
UNREFERENCED_PARAMETER(hInstance);
return TRUE;
}
/*
* PopupError
* Popping up a message box with detailed error message formatted in
* human-readable format
*
* The `dwError` is an system error code gathered via `GetErrorMessage()`
* call, or returned from API calls HRESULT value.
*
* Specifying the `hParent` parameter, he message box might be tied to parent
* window and grab it's focus.
*/
void PopupError(DWORD dwError, HWND hParent)
{
LPWSTR lpszMessage;
LPWSTR lpszSysMessage = NULL;
DWORD dwFmtError = FormatMessage(
FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM |
FORMAT_MESSAGE_IGNORE_INSERTS,
NULL, /* No source. Message from system. */
dwError, /* Error code */
0, /* No line width restrictions */
(LPWSTR) &lpszSysMessage, /* Output buffer */
0, /* minimum count of character to allocate */
NULL); /* No arguments for inserts */
if (!dwFmtError) {
assert(FALSE);
}
/* Get the length of the system message */
size_t lenSysMessage;
StringCchLength(lpszSysMessage, STRSAFE_MAX_CCH, &lenSysMessage);
/* WARNING! Read the comment bellow before changing this string */
const WCHAR szFmt[] = L"Error 0x%08x: %s";
/* Resulted formatted string will be longer than format specifier when
* expanded. As it constant, better to keep it by-hand value.
* We assume that resulted string would be:
* "Error 0x00000000: " (At least), so there is 18 character length. */
static const size_t lenFmt = 18;
/* +1 for null-terminating character */
size_t lenMessage = lenFmt + lenSysMessage + 1;
/* Allocate buffer for our message string */
lpszMessage = (LPWSTR) calloc(1, lenMessage * sizeof(WCHAR));
assert(lpszMessage);
/* Print formatted message */
StringCchPrintf(lpszMessage, lenMessage, szFmt, dwError, lpszSysMessage);
LocalFree(lpszSysMessage); /* Free system error message string */
/* Show popup */
MessageBox(hParent, lpszMessage, NULL, MB_OK | MB_ICONERROR);
free(lpszMessage);
}
BOOL PopupEULADialog(void)
{
HMODULE hMsftEdit = LoadLibrary(L"Msftedit.dll");
if (!hMsftEdit)
return FALSE;
int iStatus = (int)DialogBox(g_hInst, MAKEINTRESOURCE(IDD_EULA), NULL,
(DLGPROC)EULADlgProc);
FreeLibrary(hMsftEdit);
return iStatus == IDOK;
}
/*
* GetApplicationDataSitePath
* Construct the AppData site string. Usually should be called once.
*
* NOTE: The caller manages the memory deallocation
*/
BOOL GetApplicationDataSitePath(PWSTR* ppStr)
{
static const WCHAR szDataSiteDir[] = L"Aragajaga\\PaniView";
HRESULT hr = S_OK;
BOOL bStatus = FALSE;
PWSTR pszAppDataRoot = NULL;
if (!ppStr) {
assert(FALSE);
return FALSE;
}
*ppStr = NULL;
/* Get current user's %APPDATA%\Local path */
hr = SHGetKnownFolderPath(&FOLDERID_LocalAppData,
KF_FLAG_DEFAULT,
NULL, /* No access token, current user's AppData */
&pszAppDataRoot);
if (FAILED(hr)) {
assert(FALSE);
PopupError(hr, NULL);
goto fail;
}
size_t lenAppDataSite = wcslen(pszAppDataRoot) + ARRAYSIZE(szDataSiteDir) + 1;
PWSTR pszAppDataSite = NULL;
pszAppDataSite = calloc(1, (lenAppDataSite + 1) * sizeof(WCHAR));
if (!pszAppDataSite) {
assert(FALSE);
goto fail;
}
StringCchCopy(pszAppDataSite, lenAppDataSite, pszAppDataRoot);
PathCchAppend(pszAppDataSite, lenAppDataSite, szDataSiteDir);
/* Succeed */
bStatus = TRUE;
*ppStr = pszAppDataSite;
fail:
CoTaskMemFree(pszAppDataRoot);
if (bStatus) {
return bStatus;
}
if (pszAppDataSite) {
free(pszAppDataSite);
}
return bStatus;
}
/*
* crc32
* Cycle Redundancy Check hashing algorithm
*/
unsigned long crc32(unsigned char *data, size_t length)
{
static unsigned long table[256];
if (!*table) {
for (unsigned long i = 0; i < 256; ++i) {
unsigned long r = i;
for (int j = 0; j < 8; ++j) {
r = (r & 1 ? 0 : 0xEDB88320UL) ^ r >> 1;
}
table[i] = r ^ 0xFF000000UL;
}
}
unsigned int checksum = 0;
for (size_t i = 0; i < length; ++i) {
checksum = table[(unsigned char)(checksum) ^ ((unsigned char *)data)[i]] ^ checksum >> 8;
}
return checksum;
}
int compareHWND(const void* key1, size_t key1Size, const void* key2, size_t key2Size)
{
if (key1Size != sizeof(HWND) || key2Size != sizeof(HWND)) {
/* Error: Incorrect key size */
return -1;
}
HWND hwndKey1 = *((const HWND*)key1);
HWND hwndKey2 = *((const HWND*)key2);
if (hwndKey1 < hwndKey2) {
return -1;
}
else if (hwndKey1 > hwndKey2) {
return 1;
}
else {
return 0;
}
}
static inline D2D1_MATRIX_3X2_F D2DUtilMatrixIdentity(void) {
D2D1_MATRIX_3X2_F mat = { 0 };
mat._11 = 1.f;
mat._22 = 1.f;
return mat;
}
static inline D2D1_MATRIX_3X2_F D2DUtilMakeTranslationMatrix(D2D1_SIZE_F size)
{
D2D1_MATRIX_3X2_F mat = { 0 };
mat._11 = 1.0;
mat._12 = 0.0;
mat._21 = 0.0;
mat._22 = 1.0;
mat._31 = size.width;
mat._32 = size.height;
return mat;
}
static inline D2D1_MATRIX_3X2_F D2DUtilMatrixMultiply(D2D1_MATRIX_3X2_F *a,
D2D1_MATRIX_3X2_F *b)
{
D2D1_MATRIX_3X2_F mat = { 0 };
mat._11 = a->_11 * b->_11 + a->_12 * b->_21;
mat._12 = a->_11 * b->_12 + a->_12 * b->_22;
mat._21 = a->_21 * b->_11 + a->_22 * b->_21;
mat._22 = a->_21 * b->_12 + a->_22 * b->_22;
mat._31 = a->_31 * b->_11 + a->_32 * b->_21 + b->_31;
mat._32 = a->_31 * b->_12 + a->_32 * b->_22 + b->_32;
return mat;
}
static inline D2D1_COLOR_F D2DColorFromRGBAndAlpha(COLORREF color, float alpha)
{
D2D1_COLOR_F d2dColor;
d2dColor.b = (float)((color >> 16) & 0xFF) / 255.f;
d2dColor.g = (float)((color >> 8 ) & 0xFF) / 255.f;
d2dColor.r = (float)(color & 0xff) / 255.f;
d2dColor.a = alpha;
return d2dColor;
}
static inline D2D1_COLOR_F D2DColorFromRGB(COLORREF color)
{
return D2DColorFromRGBAndAlpha(color, 1.f);
}
BOOL Settings_LoadFile(SETTINGS *pSettings, PWSTR pszPath)
{
if (!(pSettings && pszPath)) {
assert(FALSE);
return FALSE;
}
FILE* pfd = NULL;
errno_t err;
err = _wfopen_s(&pfd, pszPath, L"rb");
if (err || !pfd) {
return FALSE;
}
BOOL bStatus = FALSE;
SETTINGS *tmpCfg = calloc(1, sizeof(SETTINGS));
if (tmpCfg) {
unsigned char magic[4];
fread(magic, sizeof(g_cfgMagic), 1, pfd);
if (!memcmp(magic, g_cfgMagic, sizeof(g_cfgMagic)))
{
fseek(pfd, 0, SEEK_SET);
fread(tmpCfg, sizeof(SETTINGS), 1, pfd);
unsigned long fileChecksum = tmpCfg->checksum;
tmpCfg->checksum = 0xFFFFFFFFUL;
unsigned long calcChecksum = crc32((unsigned char *)tmpCfg, sizeof(SETTINGS));
tmpCfg->checksum = fileChecksum;
if (fileChecksum == calcChecksum) {
memcpy(pSettings, tmpCfg, sizeof(SETTINGS));
bStatus = TRUE;
}
}
}
free(tmpCfg);
fclose(pfd);
return bStatus;
}
BOOL Settings_SaveFile(SETTINGS *pSettings, PWSTR pszPath)
{
if (!(pSettings && pszPath)) {
return FALSE;
}
FILE* pfd = NULL;
errno_t err;
err = _wfopen_s(&pfd, pszPath, L"wb");
if (err || !pfd) {
assert(FALSE);
return FALSE;
}
memcpy(&pSettings->magic, g_cfgMagic, sizeof(g_cfgMagic));
pSettings->checksum = 0xFFFFFFFF;
pSettings->checksum = crc32((unsigned char *)pSettings, sizeof(SETTINGS));
fwrite(pSettings, sizeof(SETTINGS), 1, pfd);
fclose(pfd);
return TRUE;
}
BOOL Settings_LoadDefault(SETTINGS *pSettings)
{
if (!pSettings) {
return FALSE;
}
ZeroMemory(pSettings, sizeof(SETTINGS));
pSettings->bEulaAccepted = FALSE;
pSettings->bNaviLoop = TRUE;
pSettings->nRendererType = RENDERER_D2D;
pSettings->nToolbarTheme = TOOLBARTHEME_DEFAULT_24PX;
return TRUE;
}
static inline LPPANIVIEWAPP GetApp(void)
{
static LPPANIVIEWAPP s_gApp = NULL;
if (!s_gApp) {
s_gApp = calloc(1, sizeof(PANIVIEWAPP));
}
return s_gApp;
}
BOOL PaniViewApp_Initialize(LPPANIVIEWAPP pApp)
{
if (!PaniViewApp_LoadSettings(pApp)) {
if (PaniViewApp_LoadDefaultSettings(pApp))
{
if (!pApp->m_settings.bEulaAccepted)
{
BOOL bAccepted = PopupEULADialog();
if (bAccepted) {
pApp->m_settings.bEulaAccepted = TRUE;
}
else {
return FALSE;
}
}
PWSTR pszDataPath = PaniViewApp_GetAppDataSitePath(pApp);
SHCreateDirectoryEx(pApp->mainFrame.base.hWnd, pszDataPath, NULL);
PaniViewApp_SaveSettings(pApp);
}
else {
return FALSE;
}
}
PaniViewApp_InitializeWIC();
pApp->m_rendererContext = CreateRendererContext();
return TRUE;
}
PWSTR PaniViewApp_GetAppDataSitePath(LPPANIVIEWAPP pApp) {
if (!pApp->m_appDataSite) {
GetApplicationDataSitePath(&pApp->m_appDataSite);
}
return pApp->m_appDataSite;
}
PWSTR PaniViewApp_GetSettingsFilePath(LPPANIVIEWAPP pApp) {
static const WCHAR szCfgFileName[] = L"settings.dat";
PWSTR pszCfgPath = NULL;
if (!pApp->m_appCfgPath) {
PWSTR pszSite = PaniViewApp_GetAppDataSitePath(pApp);
if (pszSite) {
size_t lenCfgPath = wcslen(pszSite) + ARRAYSIZE(szCfgFileName) + 1;
pszCfgPath = calloc(1, lenCfgPath * sizeof(WCHAR));
if (pszCfgPath) {
StringCchCopy(pszCfgPath, lenCfgPath, pszSite);
PathCchAppend(pszCfgPath, lenCfgPath, szCfgFileName);
}
}
}
return pszCfgPath;
}
BOOL PaniViewApp_CreateAppDataSite(LPPANIVIEWAPP pApp) {
PWSTR pszSite;
pszSite = PaniViewApp_GetAppDataSitePath(pApp);
if (!pszSite) {
return FALSE;
}
/* TODO: MAKE RESEARCH, MAY BE CAPPED AT MAX_PATH */
int iStatus = SHCreateDirectoryEx(NULL, (PCWSTR)pszSite, NULL);
if (iStatus != ERROR_SUCCESS && iStatus != ERROR_ALREADY_EXISTS) {
assert(FALSE);
PopupError(iStatus, NULL);
return FALSE;
}
return TRUE;
}
BOOL PaniViewApp_LoadSettings(LPPANIVIEWAPP pApp)
{
if (!pApp) {
return FALSE;
}
PWSTR pszCfgPath = PaniViewApp_GetSettingsFilePath(pApp);
if (!pszCfgPath) {
return FALSE;
}
return Settings_LoadFile(&pApp->m_settings, pszCfgPath);
}
BOOL PaniViewApp_SaveSettings(LPPANIVIEWAPP pApp)
{
if (!pApp) {
return FALSE;
}
PWSTR pszCfgPath = PaniViewApp_GetSettingsFilePath(pApp);
if (!pszCfgPath) {
return FALSE;
}
return Settings_SaveFile(&pApp->m_settings, pszCfgPath);
}
BOOL PaniViewApp_LoadDefaultSettings(LPPANIVIEWAPP pApp)
{
if (!pApp) {
return FALSE;
}
return Settings_LoadDefault(&pApp->m_settings);
}
void PaniViewApp_SetTitle(LPPANIVIEWAPP pApp, PWSTR pszTitle)
{
if (pApp->mainFrame.base.hWnd)
{
if (pszTitle && pszTitle[0] != L'\0')
{
WCHAR szNewTitle[256] = { 0 };
StringCchPrintf(szNewTitle, 256, L"%s - %s", pszTitle, szPaniView);
SetWindowText(pApp->mainFrame.base.hWnd, szNewTitle);
}
else {
SetWindowText(pApp->mainFrame.base.hWnd, szPaniView);
}
}
}
void PaniViewApp_UpdateViewport(void)
{
LPPANIVIEWAPP pApp = GetApp();
InvalidateRect(pApp->renderCtl.base.hWnd, NULL, TRUE);
}
void PaniViewApp_SetFilePath(PWSTR pszPath)
{
LPPANIVIEWAPP pApp = GetApp();
size_t len;
StringCchLength(pszPath, STRSAFE_MAX_CCH, &len);
if (pApp->pszImagePath) {
LPWSTR pszStrNew = realloc(pApp->pszImagePath, (len + 1) * sizeof(WCHAR));
pApp->pszImagePath = pszStrNew;
}
else {
pApp->pszImagePath = malloc((len + 1) * sizeof(WCHAR));
}
StringCchCopy(pApp->pszImagePath, len + 1, pszPath);
pApp->pszImagePath[len] = L'\0';
}
LPRENDERERCONTEXT PaniViewApp_GetRendererContext(void)
{
LPPANIVIEWAPP pApp = GetApp();
return pApp->m_rendererContext;
}
HRESULT PaniViewApp_InitializeWIC(void)
{
HRESULT hr = S_OK;
LPPANIVIEWAPP pApp = GetApp();
IWICImagingFactory** ppIWICFactory = &pApp->m_pIWICFactory;
hr = CoCreateInstance(&CLSID_WICImagingFactory, NULL, CLSCTX_INPROC_SERVER, &IID_IWICImagingFactory, (LPVOID)ppIWICFactory);
if (FAILED(hr)) {
PopupError(hr, NULL);
assert(FALSE);
return hr;
}
return hr;
}
HRESULT PaniViewApp_LoadFromFilePGM(PWSTR pszPath, FILE* pf)
{
HRESULT hr = E_FAIL;
IWICBitmapSource* pConvertedSourceBitmap = NULL;
int width;
int height;