From 5f284b852e42aad4ddd846f4a09d17c708832694 Mon Sep 17 00:00:00 2001 From: Sean Dawson Date: Wed, 19 Jun 2024 19:32:38 +1000 Subject: [PATCH 01/28] chore: boilerplate for qt6 application --- .../index/main.cpp.E7FC585F8D7EAC01.idx | Bin 0 -> 704 bytes .../index/mainwindow.cpp.924DFEAE07B83393.idx | Bin 0 -> 906 bytes .../index/mainwindow.h.05CA90BAE134E07C.idx | Bin 0 -> 286 bytes sirc-tiledit/.clang-format | 5 ++ sirc-tiledit/.clang-tidy | 3 + sirc-tiledit/.gitignore | 63 ++++++++++++++++++ sirc-tiledit/.vscode/c_cpp_properties.json | 18 +++++ sirc-tiledit/.vscode/launch.json | 24 +++++++ sirc-tiledit/.vscode/settings.json | 59 ++++++++++++++++ sirc-tiledit/include/mainwindow.h | 22 ++++++ sirc-tiledit/meson.build | 51 ++++++++++++++ sirc-tiledit/src/main.cpp | 10 +++ sirc-tiledit/src/mainwindow.cpp | 9 +++ sirc-tiledit/ui/mainwindow.ui | 31 +++++++++ 14 files changed, 295 insertions(+) create mode 100644 sirc-tiledit/.cache/clangd/index/main.cpp.E7FC585F8D7EAC01.idx create mode 100644 sirc-tiledit/.cache/clangd/index/mainwindow.cpp.924DFEAE07B83393.idx create mode 100644 sirc-tiledit/.cache/clangd/index/mainwindow.h.05CA90BAE134E07C.idx create mode 100644 sirc-tiledit/.clang-format create mode 100644 sirc-tiledit/.clang-tidy create mode 100644 sirc-tiledit/.gitignore create mode 100644 sirc-tiledit/.vscode/c_cpp_properties.json create mode 100644 sirc-tiledit/.vscode/launch.json create mode 100644 sirc-tiledit/.vscode/settings.json create mode 100644 sirc-tiledit/include/mainwindow.h create mode 100644 sirc-tiledit/meson.build create mode 100644 sirc-tiledit/src/main.cpp create mode 100644 sirc-tiledit/src/mainwindow.cpp create mode 100644 sirc-tiledit/ui/mainwindow.ui diff --git a/sirc-tiledit/.cache/clangd/index/main.cpp.E7FC585F8D7EAC01.idx b/sirc-tiledit/.cache/clangd/index/main.cpp.E7FC585F8D7EAC01.idx new file mode 100644 index 0000000000000000000000000000000000000000..d4d97d85bd4568207a8896bba60425063036a560 GIT binary patch literal 704 zcmWIYbaUIm#K7R3;#rZKT9U}Zz`!63#Kk2=nNJxR7+8S<6?4`G#pd5O;JNajQ{`&J zL|*1U7p(jqEX=igV^(V{(plJYa+3N@zwhtekMmArIvBT$ec~SLre)`prU-dWy>43( zWzAY>I#a+ZI(M4=r6n5k-^pFJ$k5MR&8u-YE<^vN+WBezg-7yk@P2x|$Y}hS-laF7@7Km%8uV4Q(cHZ*YOv|4ax?~({ zGiF=2)?_Xd%O383JU`c$Rkc2PZC3a%%2=!0FJ63GROp4a%^6?vs;Vcwn!k`ya>B;D z(zcOPV|N{va*|K4cvf+(MepMDwFYd;74QE{xF4l&kbRqbv1slS#z1DT8)4o$haEM_ zRYYF@)$%sb($#v+bgI!SeNKO?W!L>3@vbYBuQ(UXo$fI8o8kM`=j`Fk{0eJ?jz7>z zXV#q6<{iG-&SQB=_+~@J^zXYK=~o&(dd@H}{Mg%;lhMyzS?|o)x9snSQdW_C2D6Ox z!xmgCOb&%{`SR$-O>@5=ab(|ziMsa%XIU6&2unqYo<6Da%gc}N{g$U3C$BzDztCLh z%e(EV_6HBn!|d08F8x|_J#(?0siHz4ckav_sZH*mte?-HSzZ@%_{`#MKf-T5tUvR2 z!}EX%{rLqM8s~Y>{EbfS`op-MIVOssxH30M9vJH#=QX!_XiPoG!=TB)puxhz!T~0L zG7wOdnpUg_WWv-ja-ql<9-28_BAfFPBaa3f6B`dqRZ(hAB1nI6QF5_3kY?6+D1EGX zwfEP%jOq;PAQ=cq&P~bD1~OGORhWdhxOw<^1^ET!IXT3o#1z=sSml^ml*J`vB&CH# OwM4WPwPckT)ffPEZ4(;+ literal 0 HcmV?d00001 diff --git a/sirc-tiledit/.cache/clangd/index/mainwindow.cpp.924DFEAE07B83393.idx b/sirc-tiledit/.cache/clangd/index/mainwindow.cpp.924DFEAE07B83393.idx new file mode 100644 index 0000000000000000000000000000000000000000..480c5608c3ef74a31d5901a83405bd271705942f GIT binary patch literal 906 zcmWIYbaQKBW?*nm@vO*AElFfyU|9l6?4`G#pd5u5PACF#bI&i zO_7uQYahGJoAvB)-evP{hQbR56%BLWrS7l2UbuK?u$or>lM@;@oX^gi= ze&z=CmWMSF%MIT+ZM+o0zv=j$tk+7*{NIRw30KMyEW333SYc;tkznr3#qQCbuT+hj z=I$|_Qlhdt`&6!L)cLgmzOKI*=2)(nWS3spKYvfjmMdlFOT^YBFI^I~T{Lg^?bag( zjV5uCkADXV6`%FdJm4aq5MlMt?bs9Uw>Ka0Hg?)F8y&Li=;(KQy#B?T4{?%Q093r`fA$)tnT*Aixk+}w zl)&bzrV=MTK~$VcPlG|5gNK7xOMpp$nSlc+2LxX8bT2&2UUy84NgXK7%)=}OCLodw z3`MDF#XEr@a&H)c zPElhqfEfVvK~5sbH^oKC#mXRtCWrt7X6?2cpYp9|?pw>Gsl}iL6J}sY&P~bD269z3 uRhWdhxOw<^1^ET!IXT3o#1z=sSml^ml*J`vB&CH#bwqR(b!3$o)ffO&3SJTb literal 0 HcmV?d00001 diff --git a/sirc-tiledit/.cache/clangd/index/mainwindow.h.05CA90BAE134E07C.idx b/sirc-tiledit/.cache/clangd/index/mainwindow.h.05CA90BAE134E07C.idx new file mode 100644 index 0000000000000000000000000000000000000000..575fd5b2c1d732c55c62a7ba56dcee50604ec935 GIT binary patch literal 286 zcmWIYbaNA9WMFVk@vO*AElFfyU||X=)LLrMd)kgj9|5Ev!a!v8Rl|1`SqMyGSBU7SFiQ)KPuPS znooy_1TNiCKh11?oRmv!jji(8Uq4=F8_55ks`jq9GB?QqXphN7!{31lfuc-IOh6t8 zu&{Bl@i1^gI7+u~{>hv#_lBK`kr^Vw!p_Dn0F`EBVJ%8cE7k+bxQ4Da%&rUH%gDvT t$iXNAQOm#p)6d8W)(5k#C^aV$q@=hgxtJSBGqEV!pJNDobU~Vd0RSpwQ~Ce^ literal 0 HcmV?d00001 diff --git a/sirc-tiledit/.clang-format b/sirc-tiledit/.clang-format new file mode 100644 index 00000000..2e85ba00 --- /dev/null +++ b/sirc-tiledit/.clang-format @@ -0,0 +1,5 @@ +# see https://clang.llvm.org/docs/ClangFormatStyleOptions.html +--- +BasedOnStyle: LLVM +Language: Cpp +Standard: c++17 diff --git a/sirc-tiledit/.clang-tidy b/sirc-tiledit/.clang-tidy new file mode 100644 index 00000000..dd0e2d7a --- /dev/null +++ b/sirc-tiledit/.clang-tidy @@ -0,0 +1,3 @@ +--- +Checks: 'clang-diagnostic-*,clang-analyzer-*,cppcoreguidelines-*,modernize-*,-modernize-use-trailing-return-type' +FormatStyle: llvm diff --git a/sirc-tiledit/.gitignore b/sirc-tiledit/.gitignore new file mode 100644 index 00000000..c849abd7 --- /dev/null +++ b/sirc-tiledit/.gitignore @@ -0,0 +1,63 @@ +# Created by https://www.toptal.com/developers/gitignore/api/meson,c++ +# Edit at https://www.toptal.com/developers/gitignore?templates=meson,c++ + +### C++ ### +# Prerequisites +*.d + +# Compiled Object files +*.slo +*.lo +*.o +*.obj + +# Precompiled Headers +*.gch +*.pch + +# Compiled Dynamic libraries +*.so +*.dylib +*.dll + +# Fortran module files +*.mod +*.smod + +# Compiled Static libraries +*.lai +*.la +*.a +*.lib + +# Executables +*.exe +*.out +*.app + +### Meson ### +# subproject directories +/subprojects/* +!/subprojects/*.wrap + +# Meson Directories +meson-logs +meson-private +build + +# Meson Files +meson_benchmark_setup.dat +meson_test_setup.dat +sanitycheckcpp.cc # C++ specific +sanitycheckcpp.exe # C++ specific + +# Ninja +build.ninja +.ninja_deps +.ninja_logs + +# Misc +compile_commands.json + + +# End of https://www.toptal.com/developers/gitignore/api/meson,c++ diff --git a/sirc-tiledit/.vscode/c_cpp_properties.json b/sirc-tiledit/.vscode/c_cpp_properties.json new file mode 100644 index 00000000..c2098a2d --- /dev/null +++ b/sirc-tiledit/.vscode/c_cpp_properties.json @@ -0,0 +1,18 @@ +{ + "configurations": [ + { + "name": "linux-gcc-x64", + "includePath": [ + "${workspaceFolder}/**" + ], + "compilerPath": "/usr/bin/gcc", + "cStandard": "${default}", + "cppStandard": "${default}", + "intelliSenseMode": "linux-gcc-x64", + "compilerArgs": [ + "" + ] + } + ], + "version": 4 +} \ No newline at end of file diff --git a/sirc-tiledit/.vscode/launch.json b/sirc-tiledit/.vscode/launch.json new file mode 100644 index 00000000..5b163c95 --- /dev/null +++ b/sirc-tiledit/.vscode/launch.json @@ -0,0 +1,24 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "name": "C/C++ Runner: Debug Session", + "type": "cppdbg", + "request": "launch", + "args": [], + "stopAtEntry": false, + "externalConsole": false, + "cwd": "/var/home/seandawson/Development/Personal/sirc/tiledit/src", + "program": "/var/home/seandawson/Development/Personal/sirc/tiledit/src/build/Debug/outDebug", + "MIMode": "gdb", + "miDebuggerPath": "gdb", + "setupCommands": [ + { + "description": "Enable pretty-printing for gdb", + "text": "-enable-pretty-printing", + "ignoreFailures": true + } + ] + } + ] +} \ No newline at end of file diff --git a/sirc-tiledit/.vscode/settings.json b/sirc-tiledit/.vscode/settings.json new file mode 100644 index 00000000..3e5eb956 --- /dev/null +++ b/sirc-tiledit/.vscode/settings.json @@ -0,0 +1,59 @@ +{ + "C_Cpp_Runner.cCompilerPath": "gcc", + "C_Cpp_Runner.cppCompilerPath": "g++", + "C_Cpp_Runner.debuggerPath": "gdb", + "C_Cpp_Runner.cStandard": "", + "C_Cpp_Runner.cppStandard": "", + "C_Cpp_Runner.msvcBatchPath": "", + "C_Cpp_Runner.useMsvc": false, + "C_Cpp_Runner.warnings": [ + "-Wall", + "-Wextra", + "-Wpedantic", + "-Wshadow", + "-Wformat=2", + "-Wcast-align", + "-Wconversion", + "-Wsign-conversion", + "-Wnull-dereference" + ], + "C_Cpp_Runner.msvcWarnings": [ + "/W4", + "/permissive-", + "/w14242", + "/w14287", + "/w14296", + "/w14311", + "/w14826", + "/w44062", + "/w44242", + "/w14905", + "/w14906", + "/w14263", + "/w44265", + "/w14928" + ], + "C_Cpp_Runner.enableWarnings": true, + "C_Cpp_Runner.warningsAsError": false, + "C_Cpp_Runner.compilerArgs": [], + "C_Cpp_Runner.linkerArgs": [], + "C_Cpp_Runner.includePaths": [], + "C_Cpp_Runner.includeSearch": [ + "*", + "**/*" + ], + "C_Cpp_Runner.excludeSearch": [ + "**/build", + "**/build/**", + "**/.*", + "**/.*/**", + "**/.vscode", + "**/.vscode/**" + ], + "C_Cpp_Runner.useAddressSanitizer": false, + "C_Cpp_Runner.useUndefinedSanitizer": false, + "C_Cpp_Runner.useLeakSanitizer": false, + "C_Cpp_Runner.showCompilationTime": false, + "C_Cpp_Runner.useLinkTimeOptimization": false, + "C_Cpp_Runner.msvcSecureNoWarnings": false +} \ No newline at end of file diff --git a/sirc-tiledit/include/mainwindow.h b/sirc-tiledit/include/mainwindow.h new file mode 100644 index 00000000..94cd27c7 --- /dev/null +++ b/sirc-tiledit/include/mainwindow.h @@ -0,0 +1,22 @@ +#ifndef MAINWINDOW_H +#define MAINWINDOW_H + +#include + +QT_BEGIN_NAMESPACE +namespace Ui { +class MainWindow; +} +QT_END_NAMESPACE + +class MainWindow : public QMainWindow { + Q_OBJECT + +public: + MainWindow(QWidget *parent = nullptr); + ~MainWindow(); + +private: + Ui::MainWindow *ui; +}; +#endif // MAINWINDOW_H diff --git a/sirc-tiledit/meson.build b/sirc-tiledit/meson.build new file mode 100644 index 00000000..f7af7d74 --- /dev/null +++ b/sirc-tiledit/meson.build @@ -0,0 +1,51 @@ +project( + 'sirc-tiledit', + 'cpp', + version : '1.0.0', + license : 'AGPL-3.0-only' +) +add_global_arguments(language : 'cpp') + +qt6 = import('qt6') +qt6_dep = dependency('qt6', modules: ['Core', 'Gui', 'Widgets']) + +project_source_files = [ + 'src/mainwindow.cpp', + 'src/main.cpp' +] + +project_include_files = include_directories('include') + +project_dependencies = [ + qt6_dep +] + +moc_files = qt6.compile_moc(headers : 'include/mainwindow.h', + include_directories: project_include_files, + dependencies: qt6_dep +) + +compiled_ui_files = qt6.compile_ui(sources: ['ui/mainwindow.ui']) + +build_args = [ + '-DPROJECT_NAME=' + meson.project_name(), + '-DPROJECT_VERSION=' + meson.project_version(), + '-std=c++17', + '-Wall', + '-Werror', + '-Wshadow', + '-Wextra', + '-Wpedantic', +] + +project_target = executable( + meson.project_name(), + moc_files, + project_source_files, + compiled_ui_files, + dependencies: project_dependencies, + include_directories: project_include_files, + cpp_args : build_args, +) + +test('basic', project_target) diff --git a/sirc-tiledit/src/main.cpp b/sirc-tiledit/src/main.cpp new file mode 100644 index 00000000..dbe97d05 --- /dev/null +++ b/sirc-tiledit/src/main.cpp @@ -0,0 +1,10 @@ +#include + +#include + +int main(int argc, char *argv[]) { + QApplication a(argc, argv); + MainWindow w; + w.show(); + return a.exec(); +} diff --git a/sirc-tiledit/src/mainwindow.cpp b/sirc-tiledit/src/mainwindow.cpp new file mode 100644 index 00000000..0d257600 --- /dev/null +++ b/sirc-tiledit/src/mainwindow.cpp @@ -0,0 +1,9 @@ +#include "./ui_mainwindow.h" +#include + +MainWindow::MainWindow(QWidget *parent) + : QMainWindow(parent), ui(new Ui::MainWindow) { + ui->setupUi(this); +} + +MainWindow::~MainWindow() { delete ui; } diff --git a/sirc-tiledit/ui/mainwindow.ui b/sirc-tiledit/ui/mainwindow.ui new file mode 100644 index 00000000..c6854ca2 --- /dev/null +++ b/sirc-tiledit/ui/mainwindow.ui @@ -0,0 +1,31 @@ + + + MainWindow + + + + 0 + 0 + 800 + 600 + + + + MainWindow + + + + + + 0 + 0 + 800 + 23 + + + + + + + + From d7001dadea9f0b1f3d1642453c2d0017aa22b01b Mon Sep 17 00:00:00 2001 From: Sean Dawson Date: Thu, 20 Jun 2024 12:42:12 +1000 Subject: [PATCH 02/28] chore: add some example art to use to test tiledit - Thanks to https://ansimuz.itch.io/mountain-dusk-parallax-background --- .../examples/source/dusk/far-clouds.png | Bin 0 -> 1841 bytes .../examples/source/dusk/far-mountains.png | Bin 0 -> 2980 bytes .../examples/source/dusk/mountains.png | Bin 0 -> 6272 bytes .../examples/source/dusk/near-clouds.png | Bin 0 -> 2733 bytes .../resources/examples/source/dusk/sky.png | Bin 0 -> 3080 bytes .../resources/examples/source/dusk/trees.png | Bin 0 -> 5444 bytes .../source/mountain/parallax-mountain-bg.png | Bin 0 -> 2025 bytes .../parallax-mountain-foreground-trees.png | Bin 0 -> 6703 bytes .../parallax-mountain-montain-far.png | Bin 0 -> 1093 bytes .../mountain/parallax-mountain-mountains.png | Bin 0 -> 4729 bytes .../mountain/parallax-mountain-trees.png | Bin 0 -> 4500 bytes .../examples/source/public-license.txt | 24 ++++++++++++++++++ 12 files changed, 24 insertions(+) create mode 100644 sirc-tiledit/resources/examples/source/dusk/far-clouds.png create mode 100644 sirc-tiledit/resources/examples/source/dusk/far-mountains.png create mode 100644 sirc-tiledit/resources/examples/source/dusk/mountains.png create mode 100644 sirc-tiledit/resources/examples/source/dusk/near-clouds.png create mode 100644 sirc-tiledit/resources/examples/source/dusk/sky.png create mode 100644 sirc-tiledit/resources/examples/source/dusk/trees.png create mode 100644 sirc-tiledit/resources/examples/source/mountain/parallax-mountain-bg.png create mode 100644 sirc-tiledit/resources/examples/source/mountain/parallax-mountain-foreground-trees.png create mode 100644 sirc-tiledit/resources/examples/source/mountain/parallax-mountain-montain-far.png create mode 100644 sirc-tiledit/resources/examples/source/mountain/parallax-mountain-mountains.png create mode 100644 sirc-tiledit/resources/examples/source/mountain/parallax-mountain-trees.png create mode 100755 sirc-tiledit/resources/examples/source/public-license.txt diff --git a/sirc-tiledit/resources/examples/source/dusk/far-clouds.png b/sirc-tiledit/resources/examples/source/dusk/far-clouds.png new file mode 100644 index 0000000000000000000000000000000000000000..f74c2ede46f1f6af454a9661d3b0686369f30200 GIT binary patch literal 1841 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®uNJ)uXF`&Av?+_4b}ap2v6`W?(3Gv6`MV- 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 Vc6^kBQT-G3b+z<0%hj#J{s(er+ob>i literal 0 HcmV?d00001 diff --git a/sirc-tiledit/resources/examples/source/dusk/near-clouds.png b/sirc-tiledit/resources/examples/source/dusk/near-clouds.png new file mode 100644 index 0000000000000000000000000000000000000000..b665700475175c8c0dfbd9c68a8320653591a82c GIT binary patch literal 2733 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 KZdI7N#{UbiD64q@ literal 0 HcmV?d00001 diff --git a/sirc-tiledit/resources/examples/source/dusk/trees.png b/sirc-tiledit/resources/examples/source/dusk/trees.png new file mode 100644 index 0000000000000000000000000000000000000000..2056595dae073a5491f9a88600a6f36e2c51de53 GIT binary patch literal 5444 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 Qzp|x~z8SpargQB70COdIE&u=k literal 0 HcmV?d00001 diff --git a/sirc-tiledit/resources/examples/source/mountain/parallax-mountain-bg.png b/sirc-tiledit/resources/examples/source/mountain/parallax-mountain-bg.png new file mode 100644 index 0000000000000000000000000000000000000000..a4a480568fa798de5792566ace4239acc6467159 GIT binary patch literal 2025 zcmd6o={FmQ8phL_(KO=PN@=5N*V-j?LL*aq8pN)#M1xREY_XT9T8nZjMj1;=ql#8* zZ^bf}AfmLEiLsuhh@nFTHA=X8&&>aDzxO}LCA;{6E@1iWutIFoQ4063G4 zGP~gvJM*~=F}e&p_cJ$J;RnU%SU{y8G!x_oQmc7>1#tGJ)IFHwYnj0Q)6Z`RR3={o zHCl@svDsZm55_(WH5&5{A+v*QTVC%(9yMWGV?;ZN&j-7G*;s$|0Vvz}o;ZD)k@|;W z-cg^_^sVL2As$%nc13kit;xkvaeRXRGm!q4yjCd4so1^<)%-{#1opGEDbb{_PGV1* zAKg!neNyO!7H*u75;HKXA&8`dm6%&r_?V@f=C8d@&%+&r`iZf!^BRk zHfJELTv~GDymOe-iyXk64HRW~UBg8B%}do@N3`A<{QVR~WbS!wQOsvsmuVxQ1Ox(i zHNhZf-y$igd8JmE<{W}hF5 z1oEq?#s?L_`p=>UHt*Xh_R|B1`hTmu?R#FR6nRT5E~||+CYWyP!UkftsA_G2vWleI z-E{&#t#q~nMndLK(BYhPoq#%xs82YYZd=oj z{v1TzLQT40Uo2YBQ(`s5*muLSEzf;cZ+m9soA_in0IWDd+-ONWn$I}N-j>D`(HAJK z6VD2uo%7gwq_{06nizHSC3$f1tQYxPrQgz(sqkMmK`@A5#RS&9OfnDfHSE-HtExS4 z5`ko0uIw|{Bs_Gyh9+?~m14m2jtP>Zds=$)um9a zpPoMz)WMp4yml>VY|bbVv5f|qkvx13MyH0L5`dytiML4%no(8nmQ@kS3xQ9--%y&O*$mz0|nU^G{#x2cJ z;AFCE%7jluZkyI%yY;$ZYVP9#9~)bQ;z8_jQ(m7zT*Rn)Ku~iwbg#dsy|FskS^ zB>Y;b(^?~H6jB}oXb`48ZxZ`a7g;sh zv$ow{x(yltnU|Dom^!StGKAAK=}?|!N0|1xnriw?;iU2LY~SAj{N2t<8*O%v{+<$i zyP<=>oJgy~sM)+6`*Trlh4%I7ZjrTS(%F`RkWr&~pVF1(^%)wR*}@Co`OJgg&8O|V zFbbX8g8gK83X&EpStrU-;%!;^cv zcsx>1q4V<6=<1y6L5k`kiX5&Ul{BS=rOWL}K3ZQr@wR*&)grIQ@yeFGJY5RkEPMB= zSloz_tLx$r@>N3%yi!B=a9(o=quyV}C$58R&k1g(%az=?Td zuHc{HeJe*+h8}?kV~m3J&<9A2GSx;C*<{U3d#Dn1De*t8(nx;um_iI}(39!!$Qrxi z7%S9`Dzl`fXbxbIe^ELz-Y({v%i?aE6i(n_rZ{g_Fwx{l50fh2dT;G&aevb%5`gD( zlaJ^w7kB0m+?rFO?yf8;FA@^1ZM&@;K5juRqyb-bmY7W$r=;UWOv?|K;uOI50!0M= rZS(&seS&}WpOXJS*d2!EBo00x9JpcfVx;mQ1 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 QKzZrjG1M&Ac=+VM0C|+3W&i*H literal 0 HcmV?d00001 diff --git a/sirc-tiledit/resources/examples/source/mountain/parallax-mountain-montain-far.png b/sirc-tiledit/resources/examples/source/mountain/parallax-mountain-montain-far.png new file mode 100644 index 0000000000000000000000000000000000000000..745ef833fb5f443040d298be3bdda2ad8b049ef5 GIT binary patch literal 1093 zcmeAS@N?(olHy`uVBq!ia0y~yU=#qd7jUov$s^m1uK+2@64!{5;QX|b^2DN42FH~A zq*MjZ+{E~)xx2CJN`ORLV){+(jiNa$dl}w4u6HX?&=_QiYGtT$}kzGcG(!5rN^6JH#9XV7M7IWuL_|3A+w54e@-YpaWruzkSnOIk)))sMW~y{+EkW> z@-uJUpAz=>ee20Db$lN^geNXCo%FEN_o}Smtfvlc$?V^xxRlsM6+%hUI}+o~u9 zF?QXxlcb!4>=fjVU#jv^YFEi|s4-*LZC&zT{P-auLlwpMjV^ud5d~~_1AKl3c8W|g z;8sa8InwmPea_nCO_dJYxY{ig)I`}+&K7jAu05Kv_QD#u38se>CAKX1lFYg)Ug3FP zTY`ehNv5PshkqS9ViUH$%j{q~(s}YtLxj|YJP$vbP0l+XkK>7kEq literal 0 HcmV?d00001 diff --git a/sirc-tiledit/resources/examples/source/mountain/parallax-mountain-mountains.png b/sirc-tiledit/resources/examples/source/mountain/parallax-mountain-mountains.png new file mode 100644 index 0000000000000000000000000000000000000000..90ec81645704b2862f1fa5dd16c88cfe16c1818c GIT binary patch literal 4729 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 Date: Thu, 20 Jun 2024 12:42:43 +1000 Subject: [PATCH 03/28] feat(tiledit): can now open a source file and it is displayed --- sirc-tiledit/include/aboutdialog.h | 21 ++ sirc-tiledit/include/mainwindow.h | 11 ++ sirc-tiledit/meson.build | 7 +- sirc-tiledit/meson.build.user | 299 +++++++++++++++++++++++++++++ sirc-tiledit/src/aboutdialog.cpp | 9 + sirc-tiledit/src/mainwindow.cpp | 40 ++++ sirc-tiledit/ui/aboutdialog.ui | 129 +++++++++++++ sirc-tiledit/ui/mainwindow.ui | 76 +++++++- 8 files changed, 585 insertions(+), 7 deletions(-) create mode 100644 sirc-tiledit/include/aboutdialog.h create mode 100644 sirc-tiledit/meson.build.user create mode 100644 sirc-tiledit/src/aboutdialog.cpp create mode 100644 sirc-tiledit/ui/aboutdialog.ui diff --git a/sirc-tiledit/include/aboutdialog.h b/sirc-tiledit/include/aboutdialog.h new file mode 100644 index 00000000..0a5d432e --- /dev/null +++ b/sirc-tiledit/include/aboutdialog.h @@ -0,0 +1,21 @@ +#ifndef ABOUTDIALOG_H +#define ABOUTDIALOG_H + +#include + +namespace Ui { +class AboutDialog; +} + +class AboutDialog : public QDialog { + Q_OBJECT + +public: + explicit AboutDialog(QWidget *parent = nullptr); + ~AboutDialog(); + +private: + Ui::AboutDialog *ui; +}; + +#endif // ABOUTDIALOG_H diff --git a/sirc-tiledit/include/mainwindow.h b/sirc-tiledit/include/mainwindow.h index 94cd27c7..abe7096e 100644 --- a/sirc-tiledit/include/mainwindow.h +++ b/sirc-tiledit/include/mainwindow.h @@ -16,7 +16,18 @@ class MainWindow : public QMainWindow { MainWindow(QWidget *parent = nullptr); ~MainWindow(); +protected: +#ifndef QT_NO_CONTEXTMENU + void contextMenuEvent(QContextMenuEvent *event) override; +#endif // QT_NO_CONTEXTMENU + +private slots: + void on_actionOpen_triggered(); + + void on_actionAbout_triggered(); + private: Ui::MainWindow *ui; + QString openedSourceFilename; }; #endif // MAINWINDOW_H diff --git a/sirc-tiledit/meson.build b/sirc-tiledit/meson.build index f7af7d74..1cd137da 100644 --- a/sirc-tiledit/meson.build +++ b/sirc-tiledit/meson.build @@ -11,6 +11,7 @@ qt6_dep = dependency('qt6', modules: ['Core', 'Gui', 'Widgets']) project_source_files = [ 'src/mainwindow.cpp', + 'src/aboutdialog.cpp', 'src/main.cpp' ] @@ -20,17 +21,17 @@ project_dependencies = [ qt6_dep ] -moc_files = qt6.compile_moc(headers : 'include/mainwindow.h', +moc_files = qt6.compile_moc(headers : ['include/mainwindow.h', 'include/aboutdialog.h'], include_directories: project_include_files, dependencies: qt6_dep ) -compiled_ui_files = qt6.compile_ui(sources: ['ui/mainwindow.ui']) +compiled_ui_files = qt6.compile_ui(sources: ['ui/mainwindow.ui', 'ui/aboutdialog.ui']) build_args = [ '-DPROJECT_NAME=' + meson.project_name(), '-DPROJECT_VERSION=' + meson.project_version(), - '-std=c++17', + '-std=c++20', '-Wall', '-Werror', '-Wshadow', diff --git a/sirc-tiledit/meson.build.user b/sirc-tiledit/meson.build.user new file mode 100644 index 00000000..20177fd6 --- /dev/null +++ b/sirc-tiledit/meson.build.user @@ -0,0 +1,299 @@ + + + + + + EnvironmentId + {bd16a659-68d5-47ae-82d1-69da92514835} + + + ProjectExplorer.Project.ActiveTarget + 0 + + + ProjectExplorer.Project.EditorSettings + + true + false + true + + Cpp + + CppGlobal + + + + QmlJS + + QmlJSGlobal + + + 2 + UTF-8 + false + 4 + false + 80 + true + true + 1 + 0 + false + true + false + 2 + true + true + 0 + 8 + true + false + 1 + true + true + true + *.md, *.MD, Makefile + false + true + true + + + + ProjectExplorer.Project.PluginSettings + + + true + false + true + true + true + true + + + 0 + true + + true + true + Builtin.DefaultTidyAndClazy + 2 + true + + + + true + + + false + true + + + true + + + + + ProjectExplorer.Project.Target.0 + + Desktop + Meson + Meson + {232ddd2e-ce40-4c97-be40-9c0d33f35c54} + 0 + 0 + 0 + + + debug + /var/home/seandawson/Development/Personal/sirc/sirc-tiledit/build/Meson-debug + + + + all + true + Build + MesonProjectManager.BuildStep + + 1 + Build + Build + ProjectExplorer.BuildSteps.Build + + + + + clean + true + Build + MesonProjectManager.BuildStep + + 1 + Clean + Clean + ProjectExplorer.BuildSteps.Clean + + 2 + false + + false + + Debug + MesonProjectManager.BuildConfiguration + + + + release + /var/home/seandawson/Development/Personal/sirc/sirc-tiledit/build/Meson-release + + + + all + true + MesonProjectManager.BuildStep + + 1 + Build + Build + ProjectExplorer.BuildSteps.Build + + + + + clean + true + MesonProjectManager.BuildStep + + 1 + Clean + Clean + ProjectExplorer.BuildSteps.Clean + + 2 + false + + false + + Release + MesonProjectManager.BuildConfiguration + + + + debugoptimized + /var/home/seandawson/Development/Personal/sirc/sirc-tiledit/build/Meson-debugoptimized + + + + all + true + MesonProjectManager.BuildStep + + 1 + Build + Build + ProjectExplorer.BuildSteps.Build + + + + + clean + true + MesonProjectManager.BuildStep + + 1 + Clean + Clean + ProjectExplorer.BuildSteps.Clean + + 2 + false + + false + + Debug With Optimizations + MesonProjectManager.BuildConfiguration + + + + minsize + /var/home/seandawson/Development/Personal/sirc/sirc-tiledit/build/Meson-minsize + + + + all + true + MesonProjectManager.BuildStep + + 1 + Build + Build + ProjectExplorer.BuildSteps.Build + + + + + clean + true + MesonProjectManager.BuildStep + + 1 + Clean + Clean + ProjectExplorer.BuildSteps.Clean + + 2 + false + + false + + Minimum Size + MesonProjectManager.BuildConfiguration + + 4 + + + 0 + Deploy + Deploy + ProjectExplorer.BuildSteps.Deploy + + 1 + + false + ProjectExplorer.DefaultDeployConfiguration + + 1 + + true + true + 0 + true + + 2 + + false + -e cpu-cycles --call-graph dwarf,4096 -F 250 + sirc-tiledit + MesonProjectManager.MesonRunConfigurationsirc-tiledit + sirc-tiledit + false + true + true + true + /var/home/seandawson/Development/Personal/sirc/sirc-tiledit/build/Meson-debug + + 1 + + + + ProjectExplorer.Project.TargetCount + 1 + + + ProjectExplorer.Project.Updater.FileVersion + 22 + + + Version + 22 + + diff --git a/sirc-tiledit/src/aboutdialog.cpp b/sirc-tiledit/src/aboutdialog.cpp new file mode 100644 index 00000000..12789c97 --- /dev/null +++ b/sirc-tiledit/src/aboutdialog.cpp @@ -0,0 +1,9 @@ +#include "aboutdialog.h" +#include "ui_aboutdialog.h" + +AboutDialog::AboutDialog(QWidget *parent) + : QDialog(parent), ui(new Ui::AboutDialog) { + ui->setupUi(this); +} + +AboutDialog::~AboutDialog() { delete ui; } diff --git a/sirc-tiledit/src/mainwindow.cpp b/sirc-tiledit/src/mainwindow.cpp index 0d257600..5a56db20 100644 --- a/sirc-tiledit/src/mainwindow.cpp +++ b/sirc-tiledit/src/mainwindow.cpp @@ -1,9 +1,49 @@ +#include + #include "./ui_mainwindow.h" +#include "aboutdialog.h" #include +const int SOURCE_IMAGE_PADDING = 10; + MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWindow) { ui->setupUi(this); } MainWindow::~MainWindow() { delete ui; } + +#ifndef QT_NO_CONTEXTMENU +void MainWindow::contextMenuEvent(QContextMenuEvent *event) { + QMenu menu(this); + menu.exec(event->globalPos()); +} +#endif // QT_NO_CONTEXTMENU + +void MainWindow::on_actionOpen_triggered() { + openedSourceFilename = QFileDialog::getOpenFileName( + this, tr("Open source file"), "/home", + tr("Images (*.png *.xpm *.jpg *.gif *.tif)")); + auto reader = QImageReader(openedSourceFilename); + auto pixmap = QPixmap::fromImageReader(&reader); + + auto scaledPixmap = + pixmap.scaled(ui->sourceImageGraphicsView->size().shrunkBy( + QMargins(SOURCE_IMAGE_PADDING, SOURCE_IMAGE_PADDING, + SOURCE_IMAGE_PADDING, SOURCE_IMAGE_PADDING)), + Qt::KeepAspectRatio, Qt::FastTransformation); + + // TODO: clang-tidy cppcoreguidelines-owning-memory false positive? + // NOLINTNEXTLINE + auto scene = new QGraphicsScene(); + scene->addPixmap(scaledPixmap); + ui->sourceImageGraphicsView->setScene(scene); +} + +void MainWindow::on_actionAbout_triggered() { + // TODO: clang-tidy cppcoreguidelines-owning-memory false positive? + // NOLINTNEXTLINE + auto aboutDialog = new AboutDialog(this); + aboutDialog->setModal(true); + aboutDialog->show(); +} diff --git a/sirc-tiledit/ui/aboutdialog.ui b/sirc-tiledit/ui/aboutdialog.ui new file mode 100644 index 00000000..96ba7228 --- /dev/null +++ b/sirc-tiledit/ui/aboutdialog.ui @@ -0,0 +1,129 @@ + + + AboutDialog + + + + 0 + 0 + 400 + 300 + + + + Dialog + + + + + 30 + 240 + 341 + 32 + + + + Qt::Horizontal + + + QDialogButtonBox::Ok + + + + + + 10 + 10 + 371 + 211 + + + + + + + + 14 + true + + + + Tiledit for the SIRC platform + + + + + + + Qt::Vertical + + + QSizePolicy::Fixed + + + + 20 + 10 + + + + + + + + <html><head/><body><p>Makes it easy(ish) to create tile data for the SIRC PPU.</p><p>By Sean Dawson 2024</p><p><br/></p></body></html> + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + buttonBox + accepted() + AboutDialog + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + AboutDialog + reject() + + + 316 + 260 + + + 286 + 274 + + + + + diff --git a/sirc-tiledit/ui/mainwindow.ui b/sirc-tiledit/ui/mainwindow.ui index c6854ca2..3f93a7ef 100644 --- a/sirc-tiledit/ui/mainwindow.ui +++ b/sirc-tiledit/ui/mainwindow.ui @@ -6,26 +6,94 @@ 0 0 - 800 + 1150 600 MainWindow - + + + + + 20 + 20 + 512 + 512 + + + + + + + 560 + 20 + 512 + 512 + + + + 0 0 - 800 + 1150 23 + + + File + + + + + + + Help + + + + + + + + Open Source Image + + + + + Exit + + + + + About + + - + + + actionExit + triggered() + MainWindow + close() + + + -1 + -1 + + + 399 + 299 + + + + From 9b5910bf36d07d7bab4969c20fcdba844eb22142 Mon Sep 17 00:00:00 2001 From: Sean Dawson Date: Thu, 20 Jun 2024 15:05:14 +1000 Subject: [PATCH 04/28] feat(tiledit): add conversion to/from SIRC image format --- sirc-tiledit/include/imageprocessor.h | 27 +++++++++ sirc-tiledit/meson.build | 1 + sirc-tiledit/src/imageprocessor.cpp | 81 +++++++++++++++++++++++++++ sirc-tiledit/src/mainwindow.cpp | 21 ++++--- 4 files changed, 121 insertions(+), 9 deletions(-) create mode 100644 sirc-tiledit/include/imageprocessor.h create mode 100644 sirc-tiledit/src/imageprocessor.cpp diff --git a/sirc-tiledit/include/imageprocessor.h b/sirc-tiledit/include/imageprocessor.h new file mode 100644 index 00000000..170f5226 --- /dev/null +++ b/sirc-tiledit/include/imageprocessor.h @@ -0,0 +1,27 @@ +#ifndef IMAGEPROCESSOR_H +#define IMAGEPROCESSOR_H + +#include +#include +#include + +const int WIDTH_PIXELS = 256; +const int HEIGHT_PIXELS = 256; +// The number of palette slots in the SIRC PPU +const int MAX_PALETTE_SIZE = 256; + +class ImageProcessor { + +public: + static ImageProcessor fromQPixmap(QPixmap *const qPixmap); + QPixmap toQPixmap() const; + +private: + u_int8_t pixelData[WIDTH_PIXELS][HEIGHT_PIXELS] = {}; + u_int16_t palette[MAX_PALETTE_SIZE] = {}; + std::map paletteLookup; + + ImageProcessor(); +}; + +#endif // IMAGEPROCESSOR_H diff --git a/sirc-tiledit/meson.build b/sirc-tiledit/meson.build index 1cd137da..279aada6 100644 --- a/sirc-tiledit/meson.build +++ b/sirc-tiledit/meson.build @@ -12,6 +12,7 @@ qt6_dep = dependency('qt6', modules: ['Core', 'Gui', 'Widgets']) project_source_files = [ 'src/mainwindow.cpp', 'src/aboutdialog.cpp', + 'src/imageprocessor.cpp', 'src/main.cpp' ] diff --git a/sirc-tiledit/src/imageprocessor.cpp b/sirc-tiledit/src/imageprocessor.cpp new file mode 100644 index 00000000..dc30cfb3 --- /dev/null +++ b/sirc-tiledit/src/imageprocessor.cpp @@ -0,0 +1,81 @@ +#include "imageprocessor.h" +#include +#include + +// QColor uses standard 32 bit colour ARGB (8bpp) +const unsigned int Q_COLOR_RANGE = 0xFF; +// SIRC uses a packed 16 bit color RGB (5bpp) +const unsigned int SIRC_COLOR_COMPONENT_BITS = 5; +const unsigned int SIRC_COLOR_RANGE = (1 << (SIRC_COLOR_COMPONENT_BITS)) - 1; +const unsigned int Q_TO_SIRC_COLOR_RATIO = Q_COLOR_RANGE / SIRC_COLOR_RANGE; + +u_int16_t sircColorFromQRgb(const QColor qColor) { + const unsigned int r = qColor.red() / Q_TO_SIRC_COLOR_RATIO; + const unsigned int g = qColor.green() / Q_TO_SIRC_COLOR_RATIO; + const unsigned int b = qColor.blue() / Q_TO_SIRC_COLOR_RATIO; + + return r << SIRC_COLOR_COMPONENT_BITS * 2 | g << SIRC_COLOR_COMPONENT_BITS | + b; +} + +QColor qRgbFromSircColor(u_int16_t color) { + const unsigned int sircR = + (color >> (SIRC_COLOR_COMPONENT_BITS * 2)) & SIRC_COLOR_RANGE; + const unsigned int sircG = + (color >> SIRC_COLOR_COMPONENT_BITS) & SIRC_COLOR_RANGE; + const unsigned int sircB = color & SIRC_COLOR_RANGE; + + QColor qColor; + + qColor.setRed((int)(sircR * Q_TO_SIRC_COLOR_RATIO)); + qColor.setGreen((int)(sircG * Q_TO_SIRC_COLOR_RATIO)); + qColor.setBlue((int)(sircB * Q_TO_SIRC_COLOR_RATIO)); + + return qColor; +} + +ImageProcessor::ImageProcessor() = default; + +ImageProcessor ImageProcessor::fromQPixmap(QPixmap *const qPixmap) { + auto imageProcessor = ImageProcessor(); + auto image = qPixmap->toImage(); + + assert(image.width() >= WIDTH_PIXELS && image.height() >= HEIGHT_PIXELS); + unsigned int nextPaletteIndex = 0; + + for (int x = 0; x < WIDTH_PIXELS; x++) { + for (int y = 0; y < HEIGHT_PIXELS; y++) { + auto pixel = image.pixelColor(x, y); + auto paletteColor = sircColorFromQRgb(pixel); + + if (auto existingPaletteIndex = + imageProcessor.paletteLookup.find(paletteColor); + existingPaletteIndex != imageProcessor.paletteLookup.end()) { + imageProcessor.pixelData[x][y] = existingPaletteIndex->second; + } else { + imageProcessor.palette[nextPaletteIndex] = paletteColor; + imageProcessor.paletteLookup.insert({paletteColor, nextPaletteIndex}); + imageProcessor.pixelData[x][y] = nextPaletteIndex; + ++nextPaletteIndex; + } + } + } + + qDebug("Total palette entries: %d", nextPaletteIndex); + + return imageProcessor; +} + +QPixmap ImageProcessor::toQPixmap() const { + auto image = QImage(WIDTH_PIXELS, HEIGHT_PIXELS, QImage::Format_RGB32); + + for (int x = 0; x < WIDTH_PIXELS; x++) { + for (int y = 0; y < HEIGHT_PIXELS; y++) { + auto paletteColor = this->pixelData[x][y]; + auto sircColor = this->palette[paletteColor]; + + image.setPixelColor(x, y, qRgbFromSircColor(sircColor)); + } + } + return QPixmap::fromImage(image); +} diff --git a/sirc-tiledit/src/mainwindow.cpp b/sirc-tiledit/src/mainwindow.cpp index 5a56db20..de95d4bb 100644 --- a/sirc-tiledit/src/mainwindow.cpp +++ b/sirc-tiledit/src/mainwindow.cpp @@ -2,10 +2,9 @@ #include "./ui_mainwindow.h" #include "aboutdialog.h" +#include "imageprocessor.h" #include -const int SOURCE_IMAGE_PADDING = 10; - MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWindow) { ui->setupUi(this); @@ -28,16 +27,20 @@ void MainWindow::on_actionOpen_triggered() { auto pixmap = QPixmap::fromImageReader(&reader); auto scaledPixmap = - pixmap.scaled(ui->sourceImageGraphicsView->size().shrunkBy( - QMargins(SOURCE_IMAGE_PADDING, SOURCE_IMAGE_PADDING, - SOURCE_IMAGE_PADDING, SOURCE_IMAGE_PADDING)), - Qt::KeepAspectRatio, Qt::FastTransformation); + pixmap.scaled(WIDTH_PIXELS, HEIGHT_PIXELS, Qt::KeepAspectRatioByExpanding, + Qt::FastTransformation); // TODO: clang-tidy cppcoreguidelines-owning-memory false positive? // NOLINTNEXTLINE - auto scene = new QGraphicsScene(); - scene->addPixmap(scaledPixmap); - ui->sourceImageGraphicsView->setScene(scene); + auto sourceScene = new QGraphicsScene(); + sourceScene->addPixmap(scaledPixmap); + ui->sourceImageGraphicsView->setScene(sourceScene); + + auto imageProcessor = ImageProcessor::fromQPixmap(&scaledPixmap); + auto targetPixmap = imageProcessor.toQPixmap(); + auto targetScene = new QGraphicsScene(); + targetScene->addPixmap(targetPixmap); + ui->targetImageGraphicsView->setScene(targetScene); } void MainWindow::on_actionAbout_triggered() { From 2c627bf8ea18b399cbe879ad0e85fca909b6d8b4 Mon Sep 17 00:00:00 2001 From: Sean Dawson Date: Thu, 20 Jun 2024 19:58:16 +1000 Subject: [PATCH 05/28] feat(tiledit): show palette in UI --- sirc-tiledit/include/imageprocessor.h | 11 +++- sirc-tiledit/meson.build.user | 2 +- sirc-tiledit/src/imageprocessor.cpp | 21 ++++-- sirc-tiledit/src/mainwindow.cpp | 25 +++++++ sirc-tiledit/ui/mainwindow.ui | 95 ++++++++++++++++++++------- 5 files changed, 122 insertions(+), 32 deletions(-) diff --git a/sirc-tiledit/include/imageprocessor.h b/sirc-tiledit/include/imageprocessor.h index 170f5226..e5434c29 100644 --- a/sirc-tiledit/include/imageprocessor.h +++ b/sirc-tiledit/include/imageprocessor.h @@ -4,6 +4,10 @@ #include #include #include +#include + +typedef u_int16_t SircColor; +typedef u_int8_t PaletteReference; const int WIDTH_PIXELS = 256; const int HEIGHT_PIXELS = 256; @@ -15,11 +19,12 @@ class ImageProcessor { public: static ImageProcessor fromQPixmap(QPixmap *const qPixmap); QPixmap toQPixmap() const; + std::vector getPaletteColors() const; private: - u_int8_t pixelData[WIDTH_PIXELS][HEIGHT_PIXELS] = {}; - u_int16_t palette[MAX_PALETTE_SIZE] = {}; - std::map paletteLookup; + PaletteReference pixelData[WIDTH_PIXELS][HEIGHT_PIXELS] = {}; + std::vector palette = {}; + std::map paletteLookup; ImageProcessor(); }; diff --git a/sirc-tiledit/meson.build.user b/sirc-tiledit/meson.build.user index 20177fd6..82377951 100644 --- a/sirc-tiledit/meson.build.user +++ b/sirc-tiledit/meson.build.user @@ -1,6 +1,6 @@ - + EnvironmentId diff --git a/sirc-tiledit/src/imageprocessor.cpp b/sirc-tiledit/src/imageprocessor.cpp index dc30cfb3..5be711fb 100644 --- a/sirc-tiledit/src/imageprocessor.cpp +++ b/sirc-tiledit/src/imageprocessor.cpp @@ -41,7 +41,6 @@ ImageProcessor ImageProcessor::fromQPixmap(QPixmap *const qPixmap) { auto image = qPixmap->toImage(); assert(image.width() >= WIDTH_PIXELS && image.height() >= HEIGHT_PIXELS); - unsigned int nextPaletteIndex = 0; for (int x = 0; x < WIDTH_PIXELS; x++) { for (int y = 0; y < HEIGHT_PIXELS; y++) { @@ -53,15 +52,15 @@ ImageProcessor ImageProcessor::fromQPixmap(QPixmap *const qPixmap) { existingPaletteIndex != imageProcessor.paletteLookup.end()) { imageProcessor.pixelData[x][y] = existingPaletteIndex->second; } else { - imageProcessor.palette[nextPaletteIndex] = paletteColor; - imageProcessor.paletteLookup.insert({paletteColor, nextPaletteIndex}); - imageProcessor.pixelData[x][y] = nextPaletteIndex; - ++nextPaletteIndex; + imageProcessor.palette.push_back(paletteColor); + auto paletteIndex = imageProcessor.palette.size() - 1; + imageProcessor.paletteLookup.insert({paletteColor, paletteIndex}); + imageProcessor.pixelData[x][y] = paletteIndex; } } } - qDebug("Total palette entries: %d", nextPaletteIndex); + qDebug("Total palette entries: %zu", imageProcessor.palette.size()); return imageProcessor; } @@ -79,3 +78,13 @@ QPixmap ImageProcessor::toQPixmap() const { } return QPixmap::fromImage(image); } + +std::vector ImageProcessor::getPaletteColors() const { + auto convertedPalette = std::vector(); + + std::vector output; + std::transform(this->palette.begin(), this->palette.end(), + std::back_inserter(output), + [](SircColor c) { return qRgbFromSircColor(c); }); + return output; +} diff --git a/sirc-tiledit/src/mainwindow.cpp b/sirc-tiledit/src/mainwindow.cpp index de95d4bb..037e7907 100644 --- a/sirc-tiledit/src/mainwindow.cpp +++ b/sirc-tiledit/src/mainwindow.cpp @@ -41,6 +41,31 @@ void MainWindow::on_actionOpen_triggered() { auto targetScene = new QGraphicsScene(); targetScene->addPixmap(targetPixmap); ui->targetImageGraphicsView->setScene(targetScene); + + // TODO: Why can't I set this alignment in the UI? + ui->paletteScrollLayout->setAlignment(Qt::AlignTop); + auto paletteColors = imageProcessor.getPaletteColors(); + for (size_t i = 0; i < paletteColors.size(); i++) { + auto color = paletteColors[i]; + + auto hWidget = new QWidget(); + hWidget->setMaximumHeight(40); + + auto hLayout = new QHBoxLayout(); + hWidget->setLayout(hLayout); + + auto label = new QLabel(QString("%1: ").arg(i)); + auto colorIndicator = new QFrame(); + + QPalette pal = QPalette(); + pal.setColor(QPalette::Window, color); + colorIndicator->setAutoFillBackground(true); + colorIndicator->setPalette(pal); + + hLayout->addWidget(label); + hLayout->addWidget(colorIndicator); + ui->paletteScrollLayout->addWidget(hWidget); + } } void MainWindow::on_actionAbout_triggered() { diff --git a/sirc-tiledit/ui/mainwindow.ui b/sirc-tiledit/ui/mainwindow.ui index 3f93a7ef..cef38de4 100644 --- a/sirc-tiledit/ui/mainwindow.ui +++ b/sirc-tiledit/ui/mainwindow.ui @@ -6,7 +6,7 @@ 0 0 - 1150 + 1391 600 @@ -14,33 +14,84 @@ MainWindow - - - - 20 - 20 - 512 - 512 - - - - - - - 560 - 20 - 512 - 512 - - - + + + + + + 0 + 0 + + + + + 512 + 512 + + + + + + + + + 0 + 0 + + + + + 512 + 512 + + + + + + + + QFrame::StyledPanel + + + QFrame::Raised + + + + + + true + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop + + + + + 0 + 0 + 315 + 514 + + + + + + + + + + + + + + 0 0 - 1150 + 1391 23 From 315b11aeddfe437dc3b0b522c4ec9fb66840f96a Mon Sep 17 00:00:00 2001 From: Sean Dawson Date: Fri, 21 Jun 2024 07:20:55 +1000 Subject: [PATCH 06/28] chore(tiledit): disable clang-tidy rule --- sirc-tiledit/.clang-tidy | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/sirc-tiledit/.clang-tidy b/sirc-tiledit/.clang-tidy index dd0e2d7a..7be885a3 100644 --- a/sirc-tiledit/.clang-tidy +++ b/sirc-tiledit/.clang-tidy @@ -1,3 +1,5 @@ --- -Checks: 'clang-diagnostic-*,clang-analyzer-*,cppcoreguidelines-*,modernize-*,-modernize-use-trailing-return-type' +Checks: 'clang-diagnostic-*,clang-analyzer-*,cppcoreguidelines-*,modernize-*,-modernize-use-trailing-return-type,-cppcoreguidelines-owning-memory' FormatStyle: llvm + +# cppcoreguidelines-owning-memory is disabled because it doesn't really fit with the memory model of QT where the caller doesn't own the widget From 55ac2c04ac73fc1c4376a5a1683a4d2f3272a893 Mon Sep 17 00:00:00 2001 From: Sean Dawson Date: Fri, 21 Jun 2024 07:21:21 +1000 Subject: [PATCH 07/28] refactor(tiledit): do some minor code cleanup --- sirc-tiledit/include/imageprocessor.h | 2 +- sirc-tiledit/include/mainwindow.h | 9 ++++- sirc-tiledit/src/imageprocessor.cpp | 4 +-- sirc-tiledit/src/mainwindow.cpp | 52 ++++++++++++++++----------- 4 files changed, 42 insertions(+), 25 deletions(-) diff --git a/sirc-tiledit/include/imageprocessor.h b/sirc-tiledit/include/imageprocessor.h index e5434c29..22bfb3d0 100644 --- a/sirc-tiledit/include/imageprocessor.h +++ b/sirc-tiledit/include/imageprocessor.h @@ -17,7 +17,7 @@ const int MAX_PALETTE_SIZE = 256; class ImageProcessor { public: - static ImageProcessor fromQPixmap(QPixmap *const qPixmap); + static ImageProcessor fromQPixmap(const QPixmap &pixmap); QPixmap toQPixmap() const; std::vector getPaletteColors() const; diff --git a/sirc-tiledit/include/mainwindow.h b/sirc-tiledit/include/mainwindow.h index abe7096e..9cf05a4d 100644 --- a/sirc-tiledit/include/mainwindow.h +++ b/sirc-tiledit/include/mainwindow.h @@ -3,6 +3,8 @@ #include +class ImageProcessor; + QT_BEGIN_NAMESPACE namespace Ui { class MainWindow; @@ -22,11 +24,16 @@ class MainWindow : public QMainWindow { #endif // QT_NO_CONTEXTMENU private slots: + // Menu Actions void on_actionOpen_triggered(); - void on_actionAbout_triggered(); private: + // UI Setup + void setupSourceImageView(const QPixmap &scaledPixmap); + void setupTargetImageView(const ImageProcessor &imageProcessor); + void setupPaletteView(const ImageProcessor &imageProcessor); + Ui::MainWindow *ui; QString openedSourceFilename; }; diff --git a/sirc-tiledit/src/imageprocessor.cpp b/sirc-tiledit/src/imageprocessor.cpp index 5be711fb..f40a874c 100644 --- a/sirc-tiledit/src/imageprocessor.cpp +++ b/sirc-tiledit/src/imageprocessor.cpp @@ -36,9 +36,9 @@ QColor qRgbFromSircColor(u_int16_t color) { ImageProcessor::ImageProcessor() = default; -ImageProcessor ImageProcessor::fromQPixmap(QPixmap *const qPixmap) { +ImageProcessor ImageProcessor::fromQPixmap(const QPixmap &qPixmap) { auto imageProcessor = ImageProcessor(); - auto image = qPixmap->toImage(); + auto image = qPixmap.toImage(); assert(image.width() >= WIDTH_PIXELS && image.height() >= HEIGHT_PIXELS); diff --git a/sirc-tiledit/src/mainwindow.cpp b/sirc-tiledit/src/mainwindow.cpp index 037e7907..4aa6b1bf 100644 --- a/sirc-tiledit/src/mainwindow.cpp +++ b/sirc-tiledit/src/mainwindow.cpp @@ -5,6 +5,8 @@ #include "imageprocessor.h" #include +const int PALLETE_VIEW_ITEM_HEIGHT = 40; + MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWindow) { ui->setupUi(this); @@ -19,42 +21,33 @@ void MainWindow::contextMenuEvent(QContextMenuEvent *event) { } #endif // QT_NO_CONTEXTMENU -void MainWindow::on_actionOpen_triggered() { - openedSourceFilename = QFileDialog::getOpenFileName( - this, tr("Open source file"), "/home", - tr("Images (*.png *.xpm *.jpg *.gif *.tif)")); - auto reader = QImageReader(openedSourceFilename); - auto pixmap = QPixmap::fromImageReader(&reader); - - auto scaledPixmap = - pixmap.scaled(WIDTH_PIXELS, HEIGHT_PIXELS, Qt::KeepAspectRatioByExpanding, - Qt::FastTransformation); +// UI Setup - // TODO: clang-tidy cppcoreguidelines-owning-memory false positive? - // NOLINTNEXTLINE +void MainWindow::setupSourceImageView(const QPixmap &scaledPixmap) { auto sourceScene = new QGraphicsScene(); sourceScene->addPixmap(scaledPixmap); ui->sourceImageGraphicsView->setScene(sourceScene); +} +void MainWindow::setupTargetImageView(const ImageProcessor &imageProcessor) { - auto imageProcessor = ImageProcessor::fromQPixmap(&scaledPixmap); auto targetPixmap = imageProcessor.toQPixmap(); auto targetScene = new QGraphicsScene(); targetScene->addPixmap(targetPixmap); ui->targetImageGraphicsView->setScene(targetScene); +} +void MainWindow::setupPaletteView(const ImageProcessor &imageProcessor) { // TODO: Why can't I set this alignment in the UI? ui->paletteScrollLayout->setAlignment(Qt::AlignTop); - auto paletteColors = imageProcessor.getPaletteColors(); - for (size_t i = 0; i < paletteColors.size(); i++) { - auto color = paletteColors[i]; - + int paletteIndex = 0; + for (auto color : imageProcessor.getPaletteColors()) { auto hWidget = new QWidget(); - hWidget->setMaximumHeight(40); + hWidget->setMaximumHeight(PALLETE_VIEW_ITEM_HEIGHT); auto hLayout = new QHBoxLayout(); hWidget->setLayout(hLayout); - auto label = new QLabel(QString("%1: ").arg(i)); + auto label = new QLabel(QString("%1: ").arg(paletteIndex++)); auto colorIndicator = new QFrame(); QPalette pal = QPalette(); @@ -68,9 +61,26 @@ void MainWindow::on_actionOpen_triggered() { } } +// Menu Actions + +void MainWindow::on_actionOpen_triggered() { + openedSourceFilename = QFileDialog::getOpenFileName( + this, tr("Open source file"), "/home", + tr("Images (*.png *.xpm *.jpg *.gif *.tif)")); + auto reader = QImageReader(openedSourceFilename); + auto pixmap = QPixmap::fromImageReader(&reader); + + auto scaledPixmap = + pixmap.scaled(WIDTH_PIXELS, HEIGHT_PIXELS, Qt::KeepAspectRatioByExpanding, + Qt::FastTransformation); + + setupSourceImageView(scaledPixmap); + auto imageProcessor = ImageProcessor::fromQPixmap(scaledPixmap); + setupTargetImageView(imageProcessor); + setupPaletteView(imageProcessor); +} + void MainWindow::on_actionAbout_triggered() { - // TODO: clang-tidy cppcoreguidelines-owning-memory false positive? - // NOLINTNEXTLINE auto aboutDialog = new AboutDialog(this); aboutDialog->setModal(true); aboutDialog->show(); From 41deeb5bbbb609162fb919642908feca5464fc44 Mon Sep 17 00:00:00 2001 From: Sean Dawson Date: Fri, 21 Jun 2024 07:39:52 +1000 Subject: [PATCH 08/28] build: add build job for tiledit - Name the build definitions better --- .github/workflows/sirc-tiledit.yml | 27 +++++++++++++++++++++ .github/workflows/{rust.yml => sirc-vm.yml} | 2 +- 2 files changed, 28 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/sirc-tiledit.yml rename .github/workflows/{rust.yml => sirc-vm.yml} (99%) diff --git a/.github/workflows/sirc-tiledit.yml b/.github/workflows/sirc-tiledit.yml new file mode 100644 index 00000000..4f62fa73 --- /dev/null +++ b/.github/workflows/sirc-tiledit.yml @@ -0,0 +1,27 @@ +name: SIRC Tiledit + +on: + push: + branches: ["main"] + pull_request: + branches: ["main"] + +defaults: + run: + working-directory: ./sirc-tiledit + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v1 + - uses: BSFishy/meson-build@v1.0.3 + with: + action: build + - uses: BSFishy/meson-build@v1.0.3 + with: + action: tidy + + \ No newline at end of file diff --git a/.github/workflows/rust.yml b/.github/workflows/sirc-vm.yml similarity index 99% rename from .github/workflows/rust.yml rename to .github/workflows/sirc-vm.yml index c401a3f8..196bdea1 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/sirc-vm.yml @@ -1,4 +1,4 @@ -name: Rust +name: SIRC VM on: push: From 2ac1bdaed126a5ee16e20bcb787675959abecc60 Mon Sep 17 00:00:00 2001 From: Sean Dawson Date: Fri, 21 Jun 2024 07:54:47 +1000 Subject: [PATCH 09/28] refactor(tiledit): cleanup remaining clang-tidy warnings --- sirc-tiledit/.clang-tidy | 3 ++- sirc-tiledit/include/aboutdialog.h | 7 ++++++- sirc-tiledit/include/imageprocessor.h | 12 +++++++----- sirc-tiledit/include/mainwindow.h | 7 ++++++- sirc-tiledit/src/imageprocessor.cpp | 3 +++ 5 files changed, 24 insertions(+), 8 deletions(-) diff --git a/sirc-tiledit/.clang-tidy b/sirc-tiledit/.clang-tidy index 7be885a3..5db2eebe 100644 --- a/sirc-tiledit/.clang-tidy +++ b/sirc-tiledit/.clang-tidy @@ -1,5 +1,6 @@ --- -Checks: 'clang-diagnostic-*,clang-analyzer-*,cppcoreguidelines-*,modernize-*,-modernize-use-trailing-return-type,-cppcoreguidelines-owning-memory' +Checks: 'clang-diagnostic-*,clang-analyzer-*,cppcoreguidelines-*,modernize-*,-modernize-use-trailing-return-type,-cppcoreguidelines-owning-memory,-cppcoreguidelines-pro-type-vararg' FormatStyle: llvm # cppcoreguidelines-owning-memory is disabled because it doesn't really fit with the memory model of QT where the caller doesn't own the widget +# cppcoreguidelines-pro-type-vararg can be useful sometimes, I'll try to use it sparingly diff --git a/sirc-tiledit/include/aboutdialog.h b/sirc-tiledit/include/aboutdialog.h index 0a5d432e..a51fb9a1 100644 --- a/sirc-tiledit/include/aboutdialog.h +++ b/sirc-tiledit/include/aboutdialog.h @@ -12,7 +12,12 @@ class AboutDialog : public QDialog { public: explicit AboutDialog(QWidget *parent = nullptr); - ~AboutDialog(); + ~AboutDialog() override; + + AboutDialog(const AboutDialog &) = delete; + AboutDialog &operator=(const AboutDialog &) = delete; + AboutDialog(AboutDialog &&) noexcept = delete; + AboutDialog &operator=(AboutDialog &&) noexcept = delete; private: Ui::AboutDialog *ui; diff --git a/sirc-tiledit/include/imageprocessor.h b/sirc-tiledit/include/imageprocessor.h index 22bfb3d0..897e59d9 100644 --- a/sirc-tiledit/include/imageprocessor.h +++ b/sirc-tiledit/include/imageprocessor.h @@ -3,11 +3,12 @@ #include #include +#include #include #include -typedef u_int16_t SircColor; -typedef u_int8_t PaletteReference; +using SircColor = u_int16_t; +using PaletteReference = u_int8_t; const int WIDTH_PIXELS = 256; const int HEIGHT_PIXELS = 256; @@ -18,11 +19,12 @@ class ImageProcessor { public: static ImageProcessor fromQPixmap(const QPixmap &pixmap); - QPixmap toQPixmap() const; - std::vector getPaletteColors() const; + [[nodiscard]] QPixmap toQPixmap() const; + [[nodiscard]] std::vector getPaletteColors() const; private: - PaletteReference pixelData[WIDTH_PIXELS][HEIGHT_PIXELS] = {}; + std::array, WIDTH_PIXELS> + pixelData = {}; std::vector palette = {}; std::map paletteLookup; diff --git a/sirc-tiledit/include/mainwindow.h b/sirc-tiledit/include/mainwindow.h index 9cf05a4d..4623ecbe 100644 --- a/sirc-tiledit/include/mainwindow.h +++ b/sirc-tiledit/include/mainwindow.h @@ -16,7 +16,12 @@ class MainWindow : public QMainWindow { public: MainWindow(QWidget *parent = nullptr); - ~MainWindow(); + ~MainWindow() override; + + MainWindow(const MainWindow &) = delete; + MainWindow &operator=(const MainWindow &) = delete; + MainWindow(MainWindow &&) noexcept = delete; + MainWindow &operator=(MainWindow &&) noexcept = delete; protected: #ifndef QT_NO_CONTEXTMENU diff --git a/sirc-tiledit/src/imageprocessor.cpp b/sirc-tiledit/src/imageprocessor.cpp index f40a874c..370c2598 100644 --- a/sirc-tiledit/src/imageprocessor.cpp +++ b/sirc-tiledit/src/imageprocessor.cpp @@ -50,11 +50,13 @@ ImageProcessor ImageProcessor::fromQPixmap(const QPixmap &qPixmap) { if (auto existingPaletteIndex = imageProcessor.paletteLookup.find(paletteColor); existingPaletteIndex != imageProcessor.paletteLookup.end()) { + // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-constant-array-index) imageProcessor.pixelData[x][y] = existingPaletteIndex->second; } else { imageProcessor.palette.push_back(paletteColor); auto paletteIndex = imageProcessor.palette.size() - 1; imageProcessor.paletteLookup.insert({paletteColor, paletteIndex}); + // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-constant-array-index) imageProcessor.pixelData[x][y] = paletteIndex; } } @@ -70,6 +72,7 @@ QPixmap ImageProcessor::toQPixmap() const { for (int x = 0; x < WIDTH_PIXELS; x++) { for (int y = 0; y < HEIGHT_PIXELS; y++) { + // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-constant-array-index) auto paletteColor = this->pixelData[x][y]; auto sircColor = this->palette[paletteColor]; From 46c62588a1d2149303b8a8ee05a76674e2931cd0 Mon Sep 17 00:00:00 2001 From: Sean Dawson Date: Fri, 21 Jun 2024 08:02:56 +1000 Subject: [PATCH 10/28] refactor(tiledit): give imageprocessor a better name --- sirc-tiledit/include/mainwindow.h | 6 +++--- .../include/{imageprocessor.h => sircimage.h} | 13 ++++++++++--- sirc-tiledit/meson.build | 2 +- sirc-tiledit/meson.build.user | 2 +- sirc-tiledit/src/mainwindow.cpp | 8 ++++---- .../src/{imageprocessor.cpp => sircimage.cpp} | 12 ++++++------ 6 files changed, 25 insertions(+), 18 deletions(-) rename sirc-tiledit/include/{imageprocessor.h => sircimage.h} (66%) rename sirc-tiledit/src/{imageprocessor.cpp => sircimage.cpp} (91%) diff --git a/sirc-tiledit/include/mainwindow.h b/sirc-tiledit/include/mainwindow.h index 4623ecbe..468d1dad 100644 --- a/sirc-tiledit/include/mainwindow.h +++ b/sirc-tiledit/include/mainwindow.h @@ -3,7 +3,7 @@ #include -class ImageProcessor; +class SircImage; QT_BEGIN_NAMESPACE namespace Ui { @@ -36,8 +36,8 @@ private slots: private: // UI Setup void setupSourceImageView(const QPixmap &scaledPixmap); - void setupTargetImageView(const ImageProcessor &imageProcessor); - void setupPaletteView(const ImageProcessor &imageProcessor); + void setupTargetImageView(const SircImage &imageProcessor); + void setupPaletteView(const SircImage &imageProcessor); Ui::MainWindow *ui; QString openedSourceFilename; diff --git a/sirc-tiledit/include/imageprocessor.h b/sirc-tiledit/include/sircimage.h similarity index 66% rename from sirc-tiledit/include/imageprocessor.h rename to sirc-tiledit/include/sircimage.h index 897e59d9..6c07306b 100644 --- a/sirc-tiledit/include/imageprocessor.h +++ b/sirc-tiledit/include/sircimage.h @@ -15,10 +15,17 @@ const int HEIGHT_PIXELS = 256; // The number of palette slots in the SIRC PPU const int MAX_PALETTE_SIZE = 256; -class ImageProcessor { +/** + * @brief Represents an image in the format supported by the SIRC PPU + * + * The SIRC PPU uses a 15 bit (5bpp) color format with a palette. + * The palette can store 256 colors but usually tile data will + * only support max 4bpp (16 colors). + */ +class SircImage { public: - static ImageProcessor fromQPixmap(const QPixmap &pixmap); + static SircImage fromQPixmap(const QPixmap &pixmap); [[nodiscard]] QPixmap toQPixmap() const; [[nodiscard]] std::vector getPaletteColors() const; @@ -28,7 +35,7 @@ class ImageProcessor { std::vector palette = {}; std::map paletteLookup; - ImageProcessor(); + SircImage(); }; #endif // IMAGEPROCESSOR_H diff --git a/sirc-tiledit/meson.build b/sirc-tiledit/meson.build index 279aada6..307baa50 100644 --- a/sirc-tiledit/meson.build +++ b/sirc-tiledit/meson.build @@ -12,7 +12,7 @@ qt6_dep = dependency('qt6', modules: ['Core', 'Gui', 'Widgets']) project_source_files = [ 'src/mainwindow.cpp', 'src/aboutdialog.cpp', - 'src/imageprocessor.cpp', + 'src/sircimage.cpp', 'src/main.cpp' ] diff --git a/sirc-tiledit/meson.build.user b/sirc-tiledit/meson.build.user index 82377951..0fd27a4e 100644 --- a/sirc-tiledit/meson.build.user +++ b/sirc-tiledit/meson.build.user @@ -1,6 +1,6 @@ - + EnvironmentId diff --git a/sirc-tiledit/src/mainwindow.cpp b/sirc-tiledit/src/mainwindow.cpp index 4aa6b1bf..fb0c0e09 100644 --- a/sirc-tiledit/src/mainwindow.cpp +++ b/sirc-tiledit/src/mainwindow.cpp @@ -2,7 +2,7 @@ #include "./ui_mainwindow.h" #include "aboutdialog.h" -#include "imageprocessor.h" +#include "sircimage.h" #include const int PALLETE_VIEW_ITEM_HEIGHT = 40; @@ -28,7 +28,7 @@ void MainWindow::setupSourceImageView(const QPixmap &scaledPixmap) { sourceScene->addPixmap(scaledPixmap); ui->sourceImageGraphicsView->setScene(sourceScene); } -void MainWindow::setupTargetImageView(const ImageProcessor &imageProcessor) { +void MainWindow::setupTargetImageView(const SircImage &imageProcessor) { auto targetPixmap = imageProcessor.toQPixmap(); auto targetScene = new QGraphicsScene(); @@ -36,7 +36,7 @@ void MainWindow::setupTargetImageView(const ImageProcessor &imageProcessor) { ui->targetImageGraphicsView->setScene(targetScene); } -void MainWindow::setupPaletteView(const ImageProcessor &imageProcessor) { +void MainWindow::setupPaletteView(const SircImage &imageProcessor) { // TODO: Why can't I set this alignment in the UI? ui->paletteScrollLayout->setAlignment(Qt::AlignTop); int paletteIndex = 0; @@ -75,7 +75,7 @@ void MainWindow::on_actionOpen_triggered() { Qt::FastTransformation); setupSourceImageView(scaledPixmap); - auto imageProcessor = ImageProcessor::fromQPixmap(scaledPixmap); + auto imageProcessor = SircImage::fromQPixmap(scaledPixmap); setupTargetImageView(imageProcessor); setupPaletteView(imageProcessor); } diff --git a/sirc-tiledit/src/imageprocessor.cpp b/sirc-tiledit/src/sircimage.cpp similarity index 91% rename from sirc-tiledit/src/imageprocessor.cpp rename to sirc-tiledit/src/sircimage.cpp index 370c2598..0f43b38f 100644 --- a/sirc-tiledit/src/imageprocessor.cpp +++ b/sirc-tiledit/src/sircimage.cpp @@ -1,4 +1,4 @@ -#include "imageprocessor.h" +#include "sircimage.h" #include #include @@ -34,10 +34,10 @@ QColor qRgbFromSircColor(u_int16_t color) { return qColor; } -ImageProcessor::ImageProcessor() = default; +SircImage::SircImage() = default; -ImageProcessor ImageProcessor::fromQPixmap(const QPixmap &qPixmap) { - auto imageProcessor = ImageProcessor(); +SircImage SircImage::fromQPixmap(const QPixmap &qPixmap) { + auto imageProcessor = SircImage(); auto image = qPixmap.toImage(); assert(image.width() >= WIDTH_PIXELS && image.height() >= HEIGHT_PIXELS); @@ -67,7 +67,7 @@ ImageProcessor ImageProcessor::fromQPixmap(const QPixmap &qPixmap) { return imageProcessor; } -QPixmap ImageProcessor::toQPixmap() const { +QPixmap SircImage::toQPixmap() const { auto image = QImage(WIDTH_PIXELS, HEIGHT_PIXELS, QImage::Format_RGB32); for (int x = 0; x < WIDTH_PIXELS; x++) { @@ -82,7 +82,7 @@ QPixmap ImageProcessor::toQPixmap() const { return QPixmap::fromImage(image); } -std::vector ImageProcessor::getPaletteColors() const { +std::vector SircImage::getPaletteColors() const { auto convertedPalette = std::vector(); std::vector output; From e5f8d3e7119ac5d8028973d80de93b7eae6f073d Mon Sep 17 00:00:00 2001 From: Sean Dawson Date: Fri, 21 Jun 2024 08:33:20 +1000 Subject: [PATCH 11/28] feat(tiledit): add palette reduction combo box --- sirc-tiledit/include/mainwindow.h | 6 ++++-- sirc-tiledit/include/sircimage.h | 8 ++++++-- sirc-tiledit/src/mainwindow.cpp | 28 ++++++++++++++++++++++---- sirc-tiledit/src/sircimage.cpp | 33 +++++++++++++++++++++---------- sirc-tiledit/ui/mainwindow.ui | 33 +++++++++++++++++++++++++++---- 5 files changed, 86 insertions(+), 22 deletions(-) diff --git a/sirc-tiledit/include/mainwindow.h b/sirc-tiledit/include/mainwindow.h index 468d1dad..865b5009 100644 --- a/sirc-tiledit/include/mainwindow.h +++ b/sirc-tiledit/include/mainwindow.h @@ -1,10 +1,9 @@ #ifndef MAINWINDOW_H #define MAINWINDOW_H +#include "sircimage.h" #include -class SircImage; - QT_BEGIN_NAMESPACE namespace Ui { class MainWindow; @@ -34,7 +33,10 @@ private slots: void on_actionAbout_triggered(); private: + PaletteReductionBpp getPaletteReductionBpp() const; + // UI Setup + void setupPaletteReductionOptions(); void setupSourceImageView(const QPixmap &scaledPixmap); void setupTargetImageView(const SircImage &imageProcessor); void setupPaletteView(const SircImage &imageProcessor); diff --git a/sirc-tiledit/include/sircimage.h b/sirc-tiledit/include/sircimage.h index 6c07306b..18b80f6d 100644 --- a/sirc-tiledit/include/sircimage.h +++ b/sirc-tiledit/include/sircimage.h @@ -15,6 +15,8 @@ const int HEIGHT_PIXELS = 256; // The number of palette slots in the SIRC PPU const int MAX_PALETTE_SIZE = 256; +enum class PaletteReductionBpp { None, FourBpp, TwoBpp }; + /** * @brief Represents an image in the format supported by the SIRC PPU * @@ -26,8 +28,10 @@ class SircImage { public: static SircImage fromQPixmap(const QPixmap &pixmap); - [[nodiscard]] QPixmap toQPixmap() const; - [[nodiscard]] std::vector getPaletteColors() const; + // TODO: Avoid doing palette reduction in each of the following functions + [[nodiscard]] QPixmap toQPixmap(const PaletteReductionBpp bpp) const; + [[nodiscard]] std::vector + getPaletteColors(const PaletteReductionBpp bpp) const; private: std::array, WIDTH_PIXELS> diff --git a/sirc-tiledit/src/mainwindow.cpp b/sirc-tiledit/src/mainwindow.cpp index fb0c0e09..978d2d2f 100644 --- a/sirc-tiledit/src/mainwindow.cpp +++ b/sirc-tiledit/src/mainwindow.cpp @@ -2,7 +2,6 @@ #include "./ui_mainwindow.h" #include "aboutdialog.h" -#include "sircimage.h" #include const int PALLETE_VIEW_ITEM_HEIGHT = 40; @@ -10,6 +9,7 @@ const int PALLETE_VIEW_ITEM_HEIGHT = 40; MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWindow) { ui->setupUi(this); + setupPaletteReductionOptions(); } MainWindow::~MainWindow() { delete ui; } @@ -21,16 +21,34 @@ void MainWindow::contextMenuEvent(QContextMenuEvent *event) { } #endif // QT_NO_CONTEXTMENU +PaletteReductionBpp MainWindow::getPaletteReductionBpp() const { + auto currentItem = ui->paletteReductionOptions->currentData(); + if (currentItem.isNull() || !currentItem.isValid()) { + return PaletteReductionBpp::None; + } + return currentItem.value(); +} + // UI Setup +void MainWindow::setupPaletteReductionOptions() { + ui->paletteReductionOptions->addItem( + "1:1", QVariant::fromValue(PaletteReductionBpp::None)); + ui->paletteReductionOptions->addItem( + "4bpp", QVariant::fromValue(PaletteReductionBpp::FourBpp)); + ui->paletteReductionOptions->addItem( + "2bpp", QVariant::fromValue(PaletteReductionBpp::TwoBpp)); + ui->paletteReductionOptions->setCurrentIndex(0); +} + void MainWindow::setupSourceImageView(const QPixmap &scaledPixmap) { auto sourceScene = new QGraphicsScene(); sourceScene->addPixmap(scaledPixmap); ui->sourceImageGraphicsView->setScene(sourceScene); } void MainWindow::setupTargetImageView(const SircImage &imageProcessor) { - - auto targetPixmap = imageProcessor.toQPixmap(); + auto paletteReductionBpp = getPaletteReductionBpp(); + auto targetPixmap = imageProcessor.toQPixmap(paletteReductionBpp); auto targetScene = new QGraphicsScene(); targetScene->addPixmap(targetPixmap); ui->targetImageGraphicsView->setScene(targetScene); @@ -39,8 +57,9 @@ void MainWindow::setupTargetImageView(const SircImage &imageProcessor) { void MainWindow::setupPaletteView(const SircImage &imageProcessor) { // TODO: Why can't I set this alignment in the UI? ui->paletteScrollLayout->setAlignment(Qt::AlignTop); + auto paletteReductionBpp = getPaletteReductionBpp(); int paletteIndex = 0; - for (auto color : imageProcessor.getPaletteColors()) { + for (auto color : imageProcessor.getPaletteColors(paletteReductionBpp)) { auto hWidget = new QWidget(); hWidget->setMaximumHeight(PALLETE_VIEW_ITEM_HEIGHT); @@ -75,6 +94,7 @@ void MainWindow::on_actionOpen_triggered() { Qt::FastTransformation); setupSourceImageView(scaledPixmap); + auto imageProcessor = SircImage::fromQPixmap(scaledPixmap); setupTargetImageView(imageProcessor); setupPaletteView(imageProcessor); diff --git a/sirc-tiledit/src/sircimage.cpp b/sirc-tiledit/src/sircimage.cpp index 0f43b38f..91fefe3f 100644 --- a/sirc-tiledit/src/sircimage.cpp +++ b/sirc-tiledit/src/sircimage.cpp @@ -18,12 +18,12 @@ u_int16_t sircColorFromQRgb(const QColor qColor) { b; } -QColor qRgbFromSircColor(u_int16_t color) { +QColor qRgbFromSircColor(const u_int16_t sircColor) { const unsigned int sircR = - (color >> (SIRC_COLOR_COMPONENT_BITS * 2)) & SIRC_COLOR_RANGE; + (sircColor >> (SIRC_COLOR_COMPONENT_BITS * 2)) & SIRC_COLOR_RANGE; const unsigned int sircG = - (color >> SIRC_COLOR_COMPONENT_BITS) & SIRC_COLOR_RANGE; - const unsigned int sircB = color & SIRC_COLOR_RANGE; + (sircColor >> SIRC_COLOR_COMPONENT_BITS) & SIRC_COLOR_RANGE; + const unsigned int sircB = sircColor & SIRC_COLOR_RANGE; QColor qColor; @@ -34,6 +34,18 @@ QColor qRgbFromSircColor(u_int16_t color) { return qColor; } +QColor qRgbFromSircColorWithReduction(const u_int16_t sircColor, + const PaletteReductionBpp bpp) { + switch (bpp) { + case PaletteReductionBpp::None: + return qRgbFromSircColor(sircColor); + case PaletteReductionBpp::FourBpp: + case PaletteReductionBpp::TwoBpp: + // TODO: PaletteReduction + return {}; + } +} + SircImage::SircImage() = default; SircImage SircImage::fromQPixmap(const QPixmap &qPixmap) { @@ -67,7 +79,7 @@ SircImage SircImage::fromQPixmap(const QPixmap &qPixmap) { return imageProcessor; } -QPixmap SircImage::toQPixmap() const { +QPixmap SircImage::toQPixmap(const PaletteReductionBpp bpp) const { auto image = QImage(WIDTH_PIXELS, HEIGHT_PIXELS, QImage::Format_RGB32); for (int x = 0; x < WIDTH_PIXELS; x++) { @@ -76,18 +88,19 @@ QPixmap SircImage::toQPixmap() const { auto paletteColor = this->pixelData[x][y]; auto sircColor = this->palette[paletteColor]; - image.setPixelColor(x, y, qRgbFromSircColor(sircColor)); + image.setPixelColor(x, y, qRgbFromSircColorWithReduction(sircColor, bpp)); } } return QPixmap::fromImage(image); } -std::vector SircImage::getPaletteColors() const { +std::vector +SircImage::getPaletteColors(const PaletteReductionBpp bpp) const { auto convertedPalette = std::vector(); std::vector output; - std::transform(this->palette.begin(), this->palette.end(), - std::back_inserter(output), - [](SircColor c) { return qRgbFromSircColor(c); }); + std::transform( + this->palette.begin(), this->palette.end(), std::back_inserter(output), + [&bpp](SircColor c) { return qRgbFromSircColorWithReduction(c, bpp); }); return output; } diff --git a/sirc-tiledit/ui/mainwindow.ui b/sirc-tiledit/ui/mainwindow.ui index cef38de4..e8ef2328 100644 --- a/sirc-tiledit/ui/mainwindow.ui +++ b/sirc-tiledit/ui/mainwindow.ui @@ -7,7 +7,7 @@ 0 0 1391 - 600 + 576 @@ -29,6 +29,31 @@ 512 + + Qt::AlignCenter + + + + + + + QFrame::StyledPanel + + + QFrame::Raised + + + + + + + 100 + 0 + + + + + @@ -48,7 +73,7 @@ - + QFrame::StyledPanel @@ -69,8 +94,8 @@ 0 0 - 315 - 514 + 189 + 490 From 1c7a730ed0486f213b8fd2c0c5022b0ef8baaa5d Mon Sep 17 00:00:00 2001 From: Sean Dawson Date: Fri, 21 Jun 2024 10:38:00 +1000 Subject: [PATCH 12/28] feat(tiledit): add quantizer boilerplate --- sirc-tiledit/include/mainwindow.h | 1 + sirc-tiledit/include/mediancutquantizer.h | 14 +++++ sirc-tiledit/include/quantizer.h | 14 +++++ sirc-tiledit/include/sircimage.h | 25 +++++---- sirc-tiledit/meson.build | 1 + sirc-tiledit/src/mainwindow.cpp | 19 ++++--- sirc-tiledit/src/mediancutquantizer.cpp | 13 +++++ sirc-tiledit/src/sircimage.cpp | 64 ++++++++++++----------- 8 files changed, 103 insertions(+), 48 deletions(-) create mode 100644 sirc-tiledit/include/mediancutquantizer.h create mode 100644 sirc-tiledit/include/quantizer.h create mode 100644 sirc-tiledit/src/mediancutquantizer.cpp diff --git a/sirc-tiledit/include/mainwindow.h b/sirc-tiledit/include/mainwindow.h index 865b5009..3e1b04f0 100644 --- a/sirc-tiledit/include/mainwindow.h +++ b/sirc-tiledit/include/mainwindow.h @@ -1,6 +1,7 @@ #ifndef MAINWINDOW_H #define MAINWINDOW_H +#include "quantizer.h" #include "sircimage.h" #include diff --git a/sirc-tiledit/include/mediancutquantizer.h b/sirc-tiledit/include/mediancutquantizer.h new file mode 100644 index 00000000..77d73e9b --- /dev/null +++ b/sirc-tiledit/include/mediancutquantizer.h @@ -0,0 +1,14 @@ +#ifndef MEDIANCUTQUANTIZER_H +#define MEDIANCUTQUANTIZER_H + +#include "quantizer.h" + +class MedianCutQuantizer : public Quantizer { +public: + MedianCutQuantizer(); + + SircImage quantize(const SircImage &sircImage, + const PaletteReductionBpp bpp) const override; +}; + +#endif // MEDIANCUTQUANTIZER_H diff --git a/sirc-tiledit/include/quantizer.h b/sirc-tiledit/include/quantizer.h new file mode 100644 index 00000000..e47e9768 --- /dev/null +++ b/sirc-tiledit/include/quantizer.h @@ -0,0 +1,14 @@ +#ifndef QUANTIZER_H +#define QUANTIZER_H + +#include "sircimage.h" + +enum class PaletteReductionBpp { None, FourBpp, TwoBpp }; + +class Quantizer { +public: + virtual SircImage quantize(const SircImage &sircImage, + const PaletteReductionBpp bpp) const = 0; +}; + +#endif // QUANTIZER_H diff --git a/sirc-tiledit/include/sircimage.h b/sirc-tiledit/include/sircimage.h index 18b80f6d..4be92d8a 100644 --- a/sirc-tiledit/include/sircimage.h +++ b/sirc-tiledit/include/sircimage.h @@ -4,18 +4,24 @@ #include #include #include +#include #include #include -using SircColor = u_int16_t; -using PaletteReference = u_int8_t; - const int WIDTH_PIXELS = 256; const int HEIGHT_PIXELS = 256; // The number of palette slots in the SIRC PPU const int MAX_PALETTE_SIZE = 256; -enum class PaletteReductionBpp { None, FourBpp, TwoBpp }; +using SircColor = uint16_t; +using PaletteReference = uint8_t; +using PixelData = + std::array, WIDTH_PIXELS>; + +struct SircImageData { + std::vector palette; + PixelData pixelData; +}; /** * @brief Represents an image in the format supported by the SIRC PPU @@ -28,15 +34,14 @@ class SircImage { public: static SircImage fromQPixmap(const QPixmap &pixmap); + static SircImage fromSircImageData(const SircImageData &imageData); + // TODO: Avoid doing palette reduction in each of the following functions - [[nodiscard]] QPixmap toQPixmap(const PaletteReductionBpp bpp) const; - [[nodiscard]] std::vector - getPaletteColors(const PaletteReductionBpp bpp) const; + [[nodiscard]] QPixmap toQPixmap() const; + [[nodiscard]] std::vector getPaletteColors() const; private: - std::array, WIDTH_PIXELS> - pixelData = {}; - std::vector palette = {}; + SircImageData imageData = {}; std::map paletteLookup; SircImage(); diff --git a/sirc-tiledit/meson.build b/sirc-tiledit/meson.build index 307baa50..ccf9a51c 100644 --- a/sirc-tiledit/meson.build +++ b/sirc-tiledit/meson.build @@ -13,6 +13,7 @@ project_source_files = [ 'src/mainwindow.cpp', 'src/aboutdialog.cpp', 'src/sircimage.cpp', + 'src/mediancutquantizer.cpp', 'src/main.cpp' ] diff --git a/sirc-tiledit/src/mainwindow.cpp b/sirc-tiledit/src/mainwindow.cpp index 978d2d2f..865fa873 100644 --- a/sirc-tiledit/src/mainwindow.cpp +++ b/sirc-tiledit/src/mainwindow.cpp @@ -2,6 +2,7 @@ #include "./ui_mainwindow.h" #include "aboutdialog.h" +#include "mediancutquantizer.h" #include const int PALLETE_VIEW_ITEM_HEIGHT = 40; @@ -47,8 +48,7 @@ void MainWindow::setupSourceImageView(const QPixmap &scaledPixmap) { ui->sourceImageGraphicsView->setScene(sourceScene); } void MainWindow::setupTargetImageView(const SircImage &imageProcessor) { - auto paletteReductionBpp = getPaletteReductionBpp(); - auto targetPixmap = imageProcessor.toQPixmap(paletteReductionBpp); + auto targetPixmap = imageProcessor.toQPixmap(); auto targetScene = new QGraphicsScene(); targetScene->addPixmap(targetPixmap); ui->targetImageGraphicsView->setScene(targetScene); @@ -57,9 +57,9 @@ void MainWindow::setupTargetImageView(const SircImage &imageProcessor) { void MainWindow::setupPaletteView(const SircImage &imageProcessor) { // TODO: Why can't I set this alignment in the UI? ui->paletteScrollLayout->setAlignment(Qt::AlignTop); - auto paletteReductionBpp = getPaletteReductionBpp(); + int paletteIndex = 0; - for (auto color : imageProcessor.getPaletteColors(paletteReductionBpp)) { + for (auto color : imageProcessor.getPaletteColors()) { auto hWidget = new QWidget(); hWidget->setMaximumHeight(PALLETE_VIEW_ITEM_HEIGHT); @@ -95,9 +95,14 @@ void MainWindow::on_actionOpen_triggered() { setupSourceImageView(scaledPixmap); - auto imageProcessor = SircImage::fromQPixmap(scaledPixmap); - setupTargetImageView(imageProcessor); - setupPaletteView(imageProcessor); + auto sircImage = SircImage::fromQPixmap(scaledPixmap); + + auto paletteReductionBpp = getPaletteReductionBpp(); + auto quantizer = MedianCutQuantizer(); + auto quantizedImage = quantizer.quantize(sircImage, paletteReductionBpp); + + setupTargetImageView(quantizedImage); + setupPaletteView(quantizedImage); } void MainWindow::on_actionAbout_triggered() { diff --git a/sirc-tiledit/src/mediancutquantizer.cpp b/sirc-tiledit/src/mediancutquantizer.cpp new file mode 100644 index 00000000..6797fe00 --- /dev/null +++ b/sirc-tiledit/src/mediancutquantizer.cpp @@ -0,0 +1,13 @@ +#include "mediancutquantizer.h" + +MedianCutQuantizer::MedianCutQuantizer() {} + +SircImage MedianCutQuantizer::quantize(const SircImage &sircImage, + const PaletteReductionBpp bpp) const { + if (bpp == PaletteReductionBpp::None) { + return sircImage; + } + + // TODO: Quantize + return sircImage; +} diff --git a/sirc-tiledit/src/sircimage.cpp b/sirc-tiledit/src/sircimage.cpp index 91fefe3f..b6a32902 100644 --- a/sirc-tiledit/src/sircimage.cpp +++ b/sirc-tiledit/src/sircimage.cpp @@ -34,22 +34,10 @@ QColor qRgbFromSircColor(const u_int16_t sircColor) { return qColor; } -QColor qRgbFromSircColorWithReduction(const u_int16_t sircColor, - const PaletteReductionBpp bpp) { - switch (bpp) { - case PaletteReductionBpp::None: - return qRgbFromSircColor(sircColor); - case PaletteReductionBpp::FourBpp: - case PaletteReductionBpp::TwoBpp: - // TODO: PaletteReduction - return {}; - } -} - SircImage::SircImage() = default; SircImage SircImage::fromQPixmap(const QPixmap &qPixmap) { - auto imageProcessor = SircImage(); + auto sircImage = SircImage(); auto image = qPixmap.toImage(); assert(image.width() >= WIDTH_PIXELS && image.height() >= HEIGHT_PIXELS); @@ -60,47 +48,61 @@ SircImage SircImage::fromQPixmap(const QPixmap &qPixmap) { auto paletteColor = sircColorFromQRgb(pixel); if (auto existingPaletteIndex = - imageProcessor.paletteLookup.find(paletteColor); - existingPaletteIndex != imageProcessor.paletteLookup.end()) { + sircImage.paletteLookup.find(paletteColor); + existingPaletteIndex != sircImage.paletteLookup.end()) { // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-constant-array-index) - imageProcessor.pixelData[x][y] = existingPaletteIndex->second; + sircImage.imageData.pixelData[x][y] = existingPaletteIndex->second; } else { - imageProcessor.palette.push_back(paletteColor); - auto paletteIndex = imageProcessor.palette.size() - 1; - imageProcessor.paletteLookup.insert({paletteColor, paletteIndex}); + sircImage.imageData.palette.push_back(paletteColor); + auto paletteIndex = sircImage.imageData.palette.size() - 1; + sircImage.paletteLookup.insert({paletteColor, paletteIndex}); // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-constant-array-index) - imageProcessor.pixelData[x][y] = paletteIndex; + sircImage.imageData.pixelData[x][y] = paletteIndex; } } } - qDebug("Total palette entries: %zu", imageProcessor.palette.size()); + qDebug("Total palette entries: %zu", sircImage.imageData.palette.size()); + + return sircImage; +} + +SircImage SircImage::fromSircImageData(const SircImageData &imageData) { + auto sircImage = SircImage(); + // TODO: Check if this is a copy + sircImage.imageData = imageData; + + int i = 0; + for (auto paletteColor : sircImage.imageData.palette) { + sircImage.paletteLookup.insert({paletteColor, i++}); + } + + qDebug("Total palette entries: %zu", sircImage.imageData.palette.size()); - return imageProcessor; + return sircImage; } -QPixmap SircImage::toQPixmap(const PaletteReductionBpp bpp) const { +QPixmap SircImage::toQPixmap() const { auto image = QImage(WIDTH_PIXELS, HEIGHT_PIXELS, QImage::Format_RGB32); for (int x = 0; x < WIDTH_PIXELS; x++) { for (int y = 0; y < HEIGHT_PIXELS; y++) { // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-constant-array-index) - auto paletteColor = this->pixelData[x][y]; - auto sircColor = this->palette[paletteColor]; + auto paletteColor = this->imageData.pixelData[x][y]; + auto sircColor = this->imageData.palette[paletteColor]; - image.setPixelColor(x, y, qRgbFromSircColorWithReduction(sircColor, bpp)); + image.setPixelColor(x, y, qRgbFromSircColor(sircColor)); } } return QPixmap::fromImage(image); } -std::vector -SircImage::getPaletteColors(const PaletteReductionBpp bpp) const { +std::vector SircImage::getPaletteColors() const { auto convertedPalette = std::vector(); std::vector output; - std::transform( - this->palette.begin(), this->palette.end(), std::back_inserter(output), - [&bpp](SircColor c) { return qRgbFromSircColorWithReduction(c, bpp); }); + std::transform(this->imageData.palette.begin(), this->imageData.palette.end(), + std::back_inserter(output), + [](SircColor c) { return qRgbFromSircColor(c); }); return output; } From 3fa8733eb939d9d776385334988f294fecf97c28 Mon Sep 17 00:00:00 2001 From: Sean Dawson Date: Fri, 21 Jun 2024 17:30:45 +1000 Subject: [PATCH 13/28] feat(tiledit): got rough quantizer working --- sirc-tiledit/include/mainwindow.h | 2 +- sirc-tiledit/include/mediancutquantizer.h | 14 +- sirc-tiledit/include/quantizer.h | 12 +- sirc-tiledit/include/sircimage.h | 8 + .../resources/examples/source/snes.jpg | Bin 0 -> 6842 bytes sirc-tiledit/src/mainwindow.cpp | 6 + sirc-tiledit/src/mediancutquantizer.cpp | 201 +++++++++++++++++- sirc-tiledit/src/sircimage.cpp | 16 +- 8 files changed, 240 insertions(+), 19 deletions(-) create mode 100644 sirc-tiledit/resources/examples/source/snes.jpg diff --git a/sirc-tiledit/include/mainwindow.h b/sirc-tiledit/include/mainwindow.h index 3e1b04f0..6775af40 100644 --- a/sirc-tiledit/include/mainwindow.h +++ b/sirc-tiledit/include/mainwindow.h @@ -34,7 +34,7 @@ private slots: void on_actionAbout_triggered(); private: - PaletteReductionBpp getPaletteReductionBpp() const; + [[nodiscard]] PaletteReductionBpp getPaletteReductionBpp() const; // UI Setup void setupPaletteReductionOptions(); diff --git a/sirc-tiledit/include/mediancutquantizer.h b/sirc-tiledit/include/mediancutquantizer.h index 77d73e9b..d6b9f25e 100644 --- a/sirc-tiledit/include/mediancutquantizer.h +++ b/sirc-tiledit/include/mediancutquantizer.h @@ -3,12 +3,20 @@ #include "quantizer.h" +/** + * @brief A simple quantizer that can only reduce the palette in multiples of + * two + * + * @see + * https://gowtham000.hashnode.dev/median-cut-a-popular-colour-quantization-strategy + */ class MedianCutQuantizer : public Quantizer { public: - MedianCutQuantizer(); + MedianCutQuantizer() = default; - SircImage quantize(const SircImage &sircImage, - const PaletteReductionBpp bpp) const override; + [[nodiscard]] SircImage + quantize(const SircImage &sircImage, + const PaletteReductionBpp bpp) const override; }; #endif // MEDIANCUTQUANTIZER_H diff --git a/sirc-tiledit/include/quantizer.h b/sirc-tiledit/include/quantizer.h index e47e9768..d6e5e5ba 100644 --- a/sirc-tiledit/include/quantizer.h +++ b/sirc-tiledit/include/quantizer.h @@ -7,8 +7,16 @@ enum class PaletteReductionBpp { None, FourBpp, TwoBpp }; class Quantizer { public: - virtual SircImage quantize(const SircImage &sircImage, - const PaletteReductionBpp bpp) const = 0; + Quantizer() = default; + + [[nodiscard]] virtual SircImage + quantize(const SircImage &sircImage, const PaletteReductionBpp bpp) const = 0; + + Quantizer(const Quantizer &) = default; + Quantizer &operator=(const Quantizer &) = default; + Quantizer(Quantizer &&) noexcept = default; + Quantizer &operator=(Quantizer &&) noexcept = default; + virtual ~Quantizer() = default; }; #endif // QUANTIZER_H diff --git a/sirc-tiledit/include/sircimage.h b/sirc-tiledit/include/sircimage.h index 4be92d8a..6d943d86 100644 --- a/sirc-tiledit/include/sircimage.h +++ b/sirc-tiledit/include/sircimage.h @@ -13,6 +13,13 @@ const int HEIGHT_PIXELS = 256; // The number of palette slots in the SIRC PPU const int MAX_PALETTE_SIZE = 256; +// QColor uses standard 32 bit colour ARGB (8bpp) +const unsigned int Q_COLOR_RANGE = 0xFF; +// SIRC uses a packed 16 bit color RGB (5bpp) +const unsigned int SIRC_COLOR_COMPONENT_BITS = 5; +const unsigned int SIRC_COLOR_RANGE = (1 << (SIRC_COLOR_COMPONENT_BITS)) - 1; +const unsigned int Q_TO_SIRC_COLOR_RATIO = Q_COLOR_RANGE / SIRC_COLOR_RANGE; + using SircColor = uint16_t; using PaletteReference = uint8_t; using PixelData = @@ -39,6 +46,7 @@ class SircImage { // TODO: Avoid doing palette reduction in each of the following functions [[nodiscard]] QPixmap toQPixmap() const; [[nodiscard]] std::vector getPaletteColors() const; + [[nodiscard]] SircImageData getImageData() const; private: SircImageData imageData = {}; diff --git a/sirc-tiledit/resources/examples/source/snes.jpg b/sirc-tiledit/resources/examples/source/snes.jpg new file mode 100644 index 0000000000000000000000000000000000000000..80f24d3e2149cb7294d6c1d20b8832512c749840 GIT binary patch literal 6842 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::AlignTop); + auto children = ui->paletteScrollContents->findChildren(); + for (auto child : children) { + child->deleteLater(); + } int paletteIndex = 0; for (auto color : imageProcessor.getPaletteColors()) { @@ -97,6 +101,8 @@ void MainWindow::on_actionOpen_triggered() { auto sircImage = SircImage::fromQPixmap(scaledPixmap); + qWarning("Opening file: %s", openedSourceFilename.toStdString().c_str()); + auto paletteReductionBpp = getPaletteReductionBpp(); auto quantizer = MedianCutQuantizer(); auto quantizedImage = quantizer.quantize(sircImage, paletteReductionBpp); diff --git a/sirc-tiledit/src/mediancutquantizer.cpp b/sirc-tiledit/src/mediancutquantizer.cpp index 6797fe00..3494b756 100644 --- a/sirc-tiledit/src/mediancutquantizer.cpp +++ b/sirc-tiledit/src/mediancutquantizer.cpp @@ -1,13 +1,206 @@ #include "mediancutquantizer.h" -MedianCutQuantizer::MedianCutQuantizer() {} +#include +#include +#include + +enum class ImageChannel { R, G, B }; + +std::vector +paletteAsSingleChannel(const std::vector &palette, + const ImageChannel channel) { + + std::vector paletteAsSingleChannel; + std::transform(palette.begin(), palette.end(), + std::back_inserter(paletteAsSingleChannel), + [channel](SircColor sircColor) { + switch (channel) { + case ImageChannel::R: + return (sircColor >> (SIRC_COLOR_COMPONENT_BITS * 2)) & + SIRC_COLOR_RANGE; + case ImageChannel::G: + return (sircColor >> SIRC_COLOR_COMPONENT_BITS) & + SIRC_COLOR_RANGE; + case ImageChannel::B: + return sircColor & SIRC_COLOR_RANGE; + } + }); + return paletteAsSingleChannel; +} + +SircColor averageColors(const std::vector &palette) { + std::vector r = paletteAsSingleChannel(palette, ImageChannel::R); + std::vector g = paletteAsSingleChannel(palette, ImageChannel::G); + std::vector b = paletteAsSingleChannel(palette, ImageChannel::B); + + auto rAverage = std::accumulate(r.begin(), r.end(), 0) / r.size(); + auto gAverage = std::accumulate(g.begin(), g.end(), 0) / g.size(); + auto bAverage = std::accumulate(b.begin(), b.end(), 0) / b.size(); + + return (rAverage << (SIRC_COLOR_COMPONENT_BITS * 2)) | + (gAverage << SIRC_COLOR_COMPONENT_BITS) | bAverage; +} + +unsigned short findRangeOfChannel(const std::vector &palette, + const ImageChannel channel) { + + std::vector p = paletteAsSingleChannel(palette, channel); + auto minmax = minmax_element(p.begin(), p.end()); + return minmax.second - minmax.first; +} + +std::vector +sortPaletteByChannel(const std::vector &palette, + const ImageChannel channel) { + auto output = palette; + std::sort( + output.begin(), output.end(), + [channel](SircColor leftColor, SircColor rightColor) { + switch (channel) { + case ImageChannel::R: { + auto a = + (leftColor >> (SIRC_COLOR_COMPONENT_BITS * 2)) & SIRC_COLOR_RANGE; + auto b = (rightColor >> (SIRC_COLOR_COMPONENT_BITS * 2)) & + SIRC_COLOR_RANGE; + return a < b; + } + case ImageChannel::G: { + auto a = (leftColor >> SIRC_COLOR_COMPONENT_BITS) & SIRC_COLOR_RANGE; + auto b = (rightColor >> SIRC_COLOR_COMPONENT_BITS) & SIRC_COLOR_RANGE; + return a < b; + } + case ImageChannel::B: { + auto a = leftColor & SIRC_COLOR_RANGE; + auto b = rightColor & SIRC_COLOR_RANGE; + return a < b; + } + } + }); + return output; +} + +std::vector> +quantizeRecurse(const std::vector &originalPalette, + const unsigned short maxBucketSize) { + if (originalPalette.size() <= maxBucketSize) { + auto average = averageColors(originalPalette); + std::vector> paired; + std::transform(originalPalette.begin(), originalPalette.end(), + std::back_inserter(paired), + [average](SircColor originalColor) { + return std::pair(originalColor, average); + }); + return paired; + } + + const auto rRange = findRangeOfChannel(originalPalette, ImageChannel::R); + const auto gRange = findRangeOfChannel(originalPalette, ImageChannel::G); + const auto bRange = findRangeOfChannel(originalPalette, ImageChannel::B); + + const auto maxRange = std::max({rRange, gRange, bRange}); + + ImageChannel channelWithMostRange = {}; + if (maxRange == rRange) { + channelWithMostRange = ImageChannel::R; + } else if (maxRange == rRange) { + channelWithMostRange = ImageChannel::G; + } else { + channelWithMostRange = ImageChannel::B; + } + + auto sortedPalette = + sortPaletteByChannel(originalPalette, channelWithMostRange); + + const unsigned short halfSize = sortedPalette.size() / 2; + std::vector lowerPalette(sortedPalette.begin(), + sortedPalette.begin() + halfSize); + std::vector upperPalette(sortedPalette.begin() + halfSize, + sortedPalette.end()); + auto lowerQuantized = quantizeRecurse(lowerPalette, maxBucketSize); + auto upperQuantized = quantizeRecurse(upperPalette, maxBucketSize); + + // Concatinate result + std::vector> out(lowerQuantized.begin(), + lowerQuantized.end()); + out.insert(out.end(), upperQuantized.begin(), upperQuantized.end()); + return out; +} + +std::map buildPaletteMapping( + std::vector> quantizedColorPairs, + std::vector originalPalette, + std::vector quantizedPalette) { + std::map out; + for (auto &pair : quantizedColorPairs) { + SircColor originalColor = pair.first; + SircColor quantizedColor = pair.second; + auto originalIndexIt = std::find(originalPalette.begin(), + originalPalette.end(), originalColor); + auto newIndexIt = std::find(quantizedPalette.begin(), + quantizedPalette.end(), quantizedColor); + assert(originalIndexIt != originalPalette.end() && + newIndexIt != quantizedPalette.end()); + + PaletteReference originalIndex = originalIndexIt - originalPalette.begin(); + PaletteReference newIndex = newIndexIt - quantizedPalette.begin(); + + out[originalIndex] = newIndex; + } + return out; +} SircImage MedianCutQuantizer::quantize(const SircImage &sircImage, const PaletteReductionBpp bpp) const { - if (bpp == PaletteReductionBpp::None) { + unsigned short maxPaletteSize = {}; + switch (bpp) { + case PaletteReductionBpp::None: + // NOLINTNEXTLINE(cppcoreguidelines-avoid-magic-numbers) + maxPaletteSize = 256; + break; + case PaletteReductionBpp::FourBpp: + // NOLINTNEXTLINE(cppcoreguidelines-avoid-magic-numbers) + maxPaletteSize = 16; + break; + case PaletteReductionBpp::TwoBpp: + // NOLINTNEXTLINE(cppcoreguidelines-avoid-magic-numbers) + maxPaletteSize = 4; + break; + } + + const auto originalPixelData = sircImage.getImageData().pixelData; + const auto originalPalette = sircImage.getImageData().palette; + + qInfo("Quantizing image with palette size %zu to maxPaletteSize: %hu", + originalPalette.size(), maxPaletteSize); + + if (originalPalette.size() <= maxPaletteSize) { return sircImage; } - // TODO: Quantize - return sircImage; + // TODO: What to do if palette isn't power of two? + // Round down to next number power of two below maxPaletteSize? + const unsigned short maxBucketSize = originalPalette.size() / maxPaletteSize; + + auto quantizedPalettePairs = quantizeRecurse(originalPalette, maxBucketSize); + + auto quantizedPaletteWithDupes = std::views::values(quantizedPalettePairs); + auto quantizedPaletteSet = std::set(quantizedPaletteWithDupes.begin(), + quantizedPaletteWithDupes.end()); + auto quantizedPaletteWithoutDupes = std::vector( + quantizedPaletteSet.begin(), quantizedPaletteSet.end()); + + auto paletteMapping = buildPaletteMapping( + quantizedPalettePairs, originalPalette, quantizedPaletteWithoutDupes); + + SircImageData quantizedImage = {.palette = quantizedPaletteWithoutDupes, + .pixelData = {}}; + + for (int x = 0; x < WIDTH_PIXELS; x++) { + for (int y = 0; y < HEIGHT_PIXELS; y++) { + // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-constant-array-index) + quantizedImage.pixelData[x][y] = paletteMapping[originalPixelData[x][y]]; + } + } + + return SircImage::fromSircImageData(quantizedImage); } diff --git a/sirc-tiledit/src/sircimage.cpp b/sirc-tiledit/src/sircimage.cpp index b6a32902..ae44edf7 100644 --- a/sirc-tiledit/src/sircimage.cpp +++ b/sirc-tiledit/src/sircimage.cpp @@ -2,13 +2,6 @@ #include #include -// QColor uses standard 32 bit colour ARGB (8bpp) -const unsigned int Q_COLOR_RANGE = 0xFF; -// SIRC uses a packed 16 bit color RGB (5bpp) -const unsigned int SIRC_COLOR_COMPONENT_BITS = 5; -const unsigned int SIRC_COLOR_RANGE = (1 << (SIRC_COLOR_COMPONENT_BITS)) - 1; -const unsigned int Q_TO_SIRC_COLOR_RATIO = Q_COLOR_RANGE / SIRC_COLOR_RANGE; - u_int16_t sircColorFromQRgb(const QColor qColor) { const unsigned int r = qColor.red() / Q_TO_SIRC_COLOR_RATIO; const unsigned int g = qColor.green() / Q_TO_SIRC_COLOR_RATIO; @@ -62,7 +55,7 @@ SircImage SircImage::fromQPixmap(const QPixmap &qPixmap) { } } - qDebug("Total palette entries: %zu", sircImage.imageData.palette.size()); + qInfo("Total palette entries: %zu", sircImage.imageData.palette.size()); return sircImage; } @@ -77,7 +70,7 @@ SircImage SircImage::fromSircImageData(const SircImageData &imageData) { sircImage.paletteLookup.insert({paletteColor, i++}); } - qDebug("Total palette entries: %zu", sircImage.imageData.palette.size()); + qInfo("Total palette entries: %zu", sircImage.imageData.palette.size()); return sircImage; } @@ -97,6 +90,7 @@ QPixmap SircImage::toQPixmap() const { return QPixmap::fromImage(image); } +// TODO: Make it more obvious this is QColor colors std::vector SircImage::getPaletteColors() const { auto convertedPalette = std::vector(); @@ -106,3 +100,7 @@ std::vector SircImage::getPaletteColors() const { [](SircColor c) { return qRgbFromSircColor(c); }); return output; } + +// TODO: This breaks encapsulation I suppose, possibly making this class kind of +// pointless. Might need to revisit +SircImageData SircImage::getImageData() const { return this->imageData; } From b26f5fb48fb284835802ad5927fef3fc927ae439 Mon Sep 17 00:00:00 2001 From: Sean Dawson Date: Fri, 21 Jun 2024 17:52:29 +1000 Subject: [PATCH 14/28] feat(tiledit): fix max bucket size for non powers of two - rounding up when dividing seems to do the trick --- sirc-tiledit/src/mediancutquantizer.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/sirc-tiledit/src/mediancutquantizer.cpp b/sirc-tiledit/src/mediancutquantizer.cpp index 3494b756..32cec36d 100644 --- a/sirc-tiledit/src/mediancutquantizer.cpp +++ b/sirc-tiledit/src/mediancutquantizer.cpp @@ -177,9 +177,8 @@ SircImage MedianCutQuantizer::quantize(const SircImage &sircImage, return sircImage; } - // TODO: What to do if palette isn't power of two? - // Round down to next number power of two below maxPaletteSize? - const unsigned short maxBucketSize = originalPalette.size() / maxPaletteSize; + const unsigned short maxBucketSize = + (originalPalette.size() + maxPaletteSize - 1) / maxPaletteSize; auto quantizedPalettePairs = quantizeRecurse(originalPalette, maxBucketSize); From 9959b575c21de11f3baf37f57cdfc55d5f577e7c Mon Sep 17 00:00:00 2001 From: Sean Dawson Date: Fri, 21 Jun 2024 17:57:16 +1000 Subject: [PATCH 15/28] docs(tiledit): add README.md --- sirc-tiledit/README.md | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 sirc-tiledit/README.md diff --git a/sirc-tiledit/README.md b/sirc-tiledit/README.md new file mode 100644 index 00000000..08bab28f --- /dev/null +++ b/sirc-tiledit/README.md @@ -0,0 +1,25 @@ +# SIRC Tiledit + +A QT based GUI for manipulating tile data. + +## Building + +I've been using Qt Creator to work on this project +but it should be able to work in other IDEs that +support clangd (although you wouldn't get the +UI editor) + +``` +$ meson setup build +$ cd build +$ meson compile +$ meson test +``` + +# Roadmap + +- [x] Get a boilerplate QT app running +- [x] Get some quantization working to reduce palette size for tile data +- [ ] Export tile data as assembly files for import into projects +- [ ] Manage tilemap data +- [ ] Manage sprite data \ No newline at end of file From 11df115cff10fc9533c5a6fc526f9bb69b19f166 Mon Sep 17 00:00:00 2001 From: Sean Dawson Date: Fri, 21 Jun 2024 18:06:17 +1000 Subject: [PATCH 16/28] build(tiledit): meson-build action doesn't support subdirectories --- .github/workflows/sirc-tiledit.yml | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/.github/workflows/sirc-tiledit.yml b/.github/workflows/sirc-tiledit.yml index 4f62fa73..ce35e8ce 100644 --- a/.github/workflows/sirc-tiledit.yml +++ b/.github/workflows/sirc-tiledit.yml @@ -15,13 +15,17 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 - - uses: actions/setup-python@v1 - - uses: BSFishy/meson-build@v1.0.3 + - name: Checkout code + uses: actions/checkout@v4 + - name: Set up Python + uses: actions/setup-python@v5 with: - action: build - - uses: BSFishy/meson-build@v1.0.3 - with: - action: tidy + python-version: '3.x' + - name: Install dependencies + run: python -m pip install meson==${{ matrix.meson_version }} ninja + - name: Configure Project + run: meson setup build/ + env: + CC: gcc \ No newline at end of file From 5609748c9af5f34d25ecd04a00600f003db26746 Mon Sep 17 00:00:00 2001 From: Sean Dawson Date: Fri, 21 Jun 2024 18:08:15 +1000 Subject: [PATCH 17/28] build(tiledit): make sure meson version is specified --- .github/workflows/sirc-tiledit.yml | 9 ++++++++- sirc-tiledit/meson.build.user | 2 +- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/.github/workflows/sirc-tiledit.yml b/.github/workflows/sirc-tiledit.yml index ce35e8ce..aa396742 100644 --- a/.github/workflows/sirc-tiledit.yml +++ b/.github/workflows/sirc-tiledit.yml @@ -10,9 +10,16 @@ defaults: run: working-directory: ./sirc-tiledit +# Source: https://github.com/mesonbuild/meson/blob/master/docs/markdown/Continuous-Integration.md + jobs: build: - runs-on: ubuntu-latest + name: Build and Test on ${{ matrix.os }} with Meson v${{ matrix.meson_version }} + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [ubuntu-latest] + meson_version: ["1.4.1"] steps: - name: Checkout code diff --git a/sirc-tiledit/meson.build.user b/sirc-tiledit/meson.build.user index 0fd27a4e..928f61d9 100644 --- a/sirc-tiledit/meson.build.user +++ b/sirc-tiledit/meson.build.user @@ -1,6 +1,6 @@ - + EnvironmentId From 4305964fe32e49706a3d5d200c21c1fd840cd98c Mon Sep 17 00:00:00 2001 From: Sean Dawson Date: Fri, 21 Jun 2024 20:15:07 +1000 Subject: [PATCH 18/28] build(tiledit): Install QT as part of build --- .github/workflows/sirc-tiledit.yml | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/.github/workflows/sirc-tiledit.yml b/.github/workflows/sirc-tiledit.yml index aa396742..de3bd598 100644 --- a/.github/workflows/sirc-tiledit.yml +++ b/.github/workflows/sirc-tiledit.yml @@ -27,12 +27,14 @@ jobs: - name: Set up Python uses: actions/setup-python@v5 with: - python-version: '3.x' + python-version: "3.x" + - name: Install Qt + uses: jurplel/install-qt-action@v4 + with: + version: 6.6.* - name: Install dependencies run: python -m pip install meson==${{ matrix.meson_version }} ninja - name: Configure Project run: meson setup build/ env: CC: gcc - - \ No newline at end of file From 51b587d6fb25423580ed0c2b1107d1a5b0c400ee Mon Sep 17 00:00:00 2001 From: Sean Dawson Date: Fri, 21 Jun 2024 20:32:07 +1000 Subject: [PATCH 19/28] build(tiledit): build and lint meson project --- .github/workflows/sirc-tiledit.yml | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/.github/workflows/sirc-tiledit.yml b/.github/workflows/sirc-tiledit.yml index de3bd598..f3c6b67f 100644 --- a/.github/workflows/sirc-tiledit.yml +++ b/.github/workflows/sirc-tiledit.yml @@ -19,6 +19,7 @@ jobs: strategy: matrix: os: [ubuntu-latest] + # TODO: Get version from .tool-versions? meson_version: ["1.4.1"] steps: @@ -32,9 +33,20 @@ jobs: uses: jurplel/install-qt-action@v4 with: version: 6.6.* - - name: Install dependencies + - name: Install ubuntu dependencies + run: sudo apt-get install clang-format clang-tidy + - name: Install python based dependencies run: python -m pip install meson==${{ matrix.meson_version }} ninja - name: Configure Project - run: meson setup build/ + run: > + meson setup build/ env: CC: gcc + - name: Compile Project + run: | + cd build + meson compile + - name: Lint Project + run: | + ninja clang-tidy + ninja clang-format-check From 143e6a224a653a9b10d2543860c5e48064988937 Mon Sep 17 00:00:00 2001 From: Sean Dawson Date: Fri, 21 Jun 2024 20:55:20 +1000 Subject: [PATCH 20/28] chore(tiledit): make linter happy by preventing unreachable --- sirc-tiledit/src/mediancutquantizer.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/sirc-tiledit/src/mediancutquantizer.cpp b/sirc-tiledit/src/mediancutquantizer.cpp index 32cec36d..8f2218bc 100644 --- a/sirc-tiledit/src/mediancutquantizer.cpp +++ b/sirc-tiledit/src/mediancutquantizer.cpp @@ -24,6 +24,7 @@ paletteAsSingleChannel(const std::vector &palette, case ImageChannel::B: return sircColor & SIRC_COLOR_RANGE; } + throw std::runtime_error("Invalid ImageChannel value"); }); return paletteAsSingleChannel; } @@ -75,6 +76,7 @@ sortPaletteByChannel(const std::vector &palette, return a < b; } } + throw std::runtime_error("Invalid ImageChannel value"); }); return output; } From 6e43448a3e53addf89f73d0388f0ef310ee32ec7 Mon Sep 17 00:00:00 2001 From: Sean Dawson Date: Fri, 21 Jun 2024 20:58:08 +1000 Subject: [PATCH 21/28] build(tiledit): add missing cd --- .github/workflows/sirc-tiledit.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/sirc-tiledit.yml b/.github/workflows/sirc-tiledit.yml index f3c6b67f..e4c5b6e7 100644 --- a/.github/workflows/sirc-tiledit.yml +++ b/.github/workflows/sirc-tiledit.yml @@ -48,5 +48,6 @@ jobs: meson compile - name: Lint Project run: | + cd build ninja clang-tidy ninja clang-format-check From 9593c97d3cedfb303e1c5f45e9883075f714d703 Mon Sep 17 00:00:00 2001 From: Sean Dawson Date: Fri, 21 Jun 2024 21:04:14 +1000 Subject: [PATCH 22/28] build(tiledit): use clang for the CI build --- .github/workflows/sirc-tiledit.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/sirc-tiledit.yml b/.github/workflows/sirc-tiledit.yml index e4c5b6e7..a07b65ba 100644 --- a/.github/workflows/sirc-tiledit.yml +++ b/.github/workflows/sirc-tiledit.yml @@ -37,11 +37,14 @@ jobs: run: sudo apt-get install clang-format clang-tidy - name: Install python based dependencies run: python -m pip install meson==${{ matrix.meson_version }} ninja + - name: Set up Clang + uses: egor-tensin/setup-clang@v1 - name: Configure Project run: > meson setup build/ env: - CC: gcc + CC: clang + CXX: clang++ - name: Compile Project run: | cd build From e3a94dbb07b7eec49452208714c1d662ac2fe6d5 Mon Sep 17 00:00:00 2001 From: Sean Dawson Date: Fri, 21 Jun 2024 21:11:27 +1000 Subject: [PATCH 23/28] build(tiledit): try to get up-to-date clang --- .github/workflows/sirc-tiledit.yml | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/.github/workflows/sirc-tiledit.yml b/.github/workflows/sirc-tiledit.yml index a07b65ba..406d4206 100644 --- a/.github/workflows/sirc-tiledit.yml +++ b/.github/workflows/sirc-tiledit.yml @@ -35,10 +35,13 @@ jobs: version: 6.6.* - name: Install ubuntu dependencies run: sudo apt-get install clang-format clang-tidy + - name: Install up-to-date clang + run: | + wget https://apt.llvm.org/llvm.sh + chmod +x llvm.sh + sudo ./llvm.sh 18 - name: Install python based dependencies run: python -m pip install meson==${{ matrix.meson_version }} ninja - - name: Set up Clang - uses: egor-tensin/setup-clang@v1 - name: Configure Project run: > meson setup build/ From 641cf86bef71cfb046b8e6e4256d1488fdd4b6af Mon Sep 17 00:00:00 2001 From: Sean Dawson Date: Fri, 21 Jun 2024 21:16:27 +1000 Subject: [PATCH 24/28] build(tiledit): try specific version suffixes --- .github/workflows/sirc-tiledit.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/sirc-tiledit.yml b/.github/workflows/sirc-tiledit.yml index 406d4206..595b7388 100644 --- a/.github/workflows/sirc-tiledit.yml +++ b/.github/workflows/sirc-tiledit.yml @@ -46,8 +46,8 @@ jobs: run: > meson setup build/ env: - CC: clang - CXX: clang++ + CC: clang-18 + CXX: clang++-18 - name: Compile Project run: | cd build From 3e5fd2b79a3f4334d2d83c0f14bb4d8920ef0821 Mon Sep 17 00:00:00 2001 From: Sean Dawson Date: Fri, 21 Jun 2024 21:20:10 +1000 Subject: [PATCH 25/28] build(tiledit): force clang-format/clang-tidy versions --- .github/workflows/sirc-tiledit.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/sirc-tiledit.yml b/.github/workflows/sirc-tiledit.yml index 595b7388..977dfa7f 100644 --- a/.github/workflows/sirc-tiledit.yml +++ b/.github/workflows/sirc-tiledit.yml @@ -33,13 +33,12 @@ jobs: uses: jurplel/install-qt-action@v4 with: version: 6.6.* - - name: Install ubuntu dependencies - run: sudo apt-get install clang-format clang-tidy - name: Install up-to-date clang run: | wget https://apt.llvm.org/llvm.sh chmod +x llvm.sh sudo ./llvm.sh 18 + sudo apt-get install clang-format-18 clang-tidy-18 - name: Install python based dependencies run: python -m pip install meson==${{ matrix.meson_version }} ninja - name: Configure Project From 6d55450382f5065cd85a91292d716b074235cf34 Mon Sep 17 00:00:00 2001 From: Sean Dawson Date: Fri, 21 Jun 2024 21:25:43 +1000 Subject: [PATCH 26/28] build(tiledit): log versions --- .github/workflows/sirc-tiledit.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/sirc-tiledit.yml b/.github/workflows/sirc-tiledit.yml index 977dfa7f..9a2e4fe5 100644 --- a/.github/workflows/sirc-tiledit.yml +++ b/.github/workflows/sirc-tiledit.yml @@ -54,5 +54,7 @@ jobs: - name: Lint Project run: | cd build + clang-tidy --version ninja clang-tidy - ninja clang-format-check + clang-format --version + ninja clang-format From 7914cc4cfddc1db78148414d82bd25a18a078cbf Mon Sep 17 00:00:00 2001 From: Sean Dawson Date: Fri, 21 Jun 2024 21:47:07 +1000 Subject: [PATCH 27/28] build(tiledit): try removing old LLVM first --- .github/workflows/sirc-tiledit.yml | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/.github/workflows/sirc-tiledit.yml b/.github/workflows/sirc-tiledit.yml index 9a2e4fe5..41062757 100644 --- a/.github/workflows/sirc-tiledit.yml +++ b/.github/workflows/sirc-tiledit.yml @@ -25,6 +25,15 @@ jobs: steps: - name: Checkout code uses: actions/checkout@v4 + + - name: Remove old LLVM + run: | + sudo apt-get purge clang-format-14 clang-tidy-14 clang-tools-14 clang-14 clangd-14 libc++1-14 libc++abi1-14 libclang1-14 libomp5-14 lld-14 lldb-14 llvm-14 python3-clang-14 + - name: Install up-to-date LLVM + run: | + wget https://apt.llvm.org/llvm.sh + chmod +x llvm.sh + sudo ./llvm.sh 18 all - name: Set up Python uses: actions/setup-python@v5 with: @@ -33,12 +42,6 @@ jobs: uses: jurplel/install-qt-action@v4 with: version: 6.6.* - - name: Install up-to-date clang - run: | - wget https://apt.llvm.org/llvm.sh - chmod +x llvm.sh - sudo ./llvm.sh 18 - sudo apt-get install clang-format-18 clang-tidy-18 - name: Install python based dependencies run: python -m pip install meson==${{ matrix.meson_version }} ninja - name: Configure Project From 134ea24573244cc82454da179aede6989e3917b5 Mon Sep 17 00:00:00 2001 From: Sean Dawson Date: Fri, 21 Jun 2024 22:03:20 +1000 Subject: [PATCH 28/28] build(tiledit): try putting clang tools in path --- .github/workflows/sirc-tiledit.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/sirc-tiledit.yml b/.github/workflows/sirc-tiledit.yml index 41062757..d79a0f3f 100644 --- a/.github/workflows/sirc-tiledit.yml +++ b/.github/workflows/sirc-tiledit.yml @@ -34,6 +34,9 @@ jobs: wget https://apt.llvm.org/llvm.sh chmod +x llvm.sh sudo ./llvm.sh 18 all + # TODO: Surely there is an easier way to set default version + ln -s $(which clang-tidy-18) /usr/local/bin/clang-tidy + ln -s $(which clang-format-18) /usr/local/bin/clang-format - name: Set up Python uses: actions/setup-python@v5 with: @@ -60,4 +63,4 @@ jobs: clang-tidy --version ninja clang-tidy clang-format --version - ninja clang-format + ninja clang-format-check