1#include "quad_art.h"
2
3#include "editor.h"
4#include "editor_actions.h"
5
6#include <base/fs.h>
7#include <base/str.h>
8
9#include <game/editor/mapitems/image.h>
10
11#include <algorithm>
12#include <array>
13#include <vector>
14
15CQuadArt::CQuadArt(CQuadArtParameters Parameters, CImageInfo &&Img) :
16 m_Parameters(Parameters), m_Img(std::move(Img))
17{
18 m_vVisitedPixels.resize(sz: m_Img.m_Height * m_Img.m_Width, c: false);
19}
20
21CQuadArt::~CQuadArt()
22{
23 m_Img.Free();
24}
25
26ivec2 CQuadArt::GetOptimizedQuadSize(const ColorRGBA &Pixel, const ivec2 &Pos)
27{
28 ivec2 OptimizedSize(0, 0);
29 ivec2 Size(0, 0);
30 size_t ImgPixelSize = m_Parameters.m_ImagePixelSize;
31
32 while(IsPixelOptimizable(Pos: Pos + Size, Pixel))
33 {
34 while(IsPixelOptimizable(Pos: Pos + Size, Pixel) && (!OptimizedSize.y || Size.y < OptimizedSize.y))
35 Size.y += ImgPixelSize;
36
37 if(!OptimizedSize.y || Size.y < OptimizedSize.y)
38 OptimizedSize.y = Size.y;
39
40 Size.y = 0;
41 Size.x += ImgPixelSize;
42 OptimizedSize.x = Size.x;
43 }
44
45 MarkPixelAsVisited(Pos, Size: OptimizedSize);
46 Size = OptimizedSize / ImgPixelSize;
47 return Size;
48}
49
50size_t CQuadArt::FindSuperPixelSize(const ColorRGBA &Pixel, const ivec2 &Pos, const size_t CurrentSize)
51{
52 ivec2 Size(CurrentSize, CurrentSize);
53 if(Pos.x + CurrentSize >= m_Img.m_Width || Pos.y + CurrentSize >= m_Img.m_Height)
54 {
55 MarkPixelAsVisited(Pos, Size);
56 return CurrentSize;
57 }
58
59 for(int i = 0; i < 2; i++)
60 {
61 for(size_t j = 0; j < CurrentSize + 1; j++)
62 {
63 ivec2 CurrentPos = Pos;
64 CurrentPos.x += i == 0 ? j : CurrentSize;
65 CurrentPos.y += i == 0 ? CurrentSize : j;
66
67 ColorRGBA CheckPixel = GetPixelClamped(Pos: CurrentPos);
68 if(CurrentPos.x >= (int)m_Img.m_Width || CurrentPos.y >= (int)m_Img.m_Height || Pixel != CheckPixel)
69 {
70 MarkPixelAsVisited(Pos, Size);
71 return CurrentSize;
72 }
73 }
74 }
75
76 return FindSuperPixelSize(Pixel, Pos, CurrentSize: CurrentSize + 1);
77}
78
79ColorRGBA CQuadArt::GetPixelClamped(const ivec2 &Pos) const
80{
81 size_t x = std::clamp<size_t>(val: Pos.x, lo: 0, hi: m_Img.m_Width - 1);
82 size_t y = std::clamp<size_t>(val: Pos.y, lo: 0, hi: m_Img.m_Height - 1);
83 return m_Img.PixelColor(x, y);
84}
85
86bool CQuadArt::IsPixelOptimizable(const ivec2 &Pos, const ColorRGBA &Pixel) const
87{
88 if(Pos.x >= (int)m_Img.m_Width || Pos.y >= (int)m_Img.m_Height)
89 return false;
90 ColorRGBA CheckPixel = m_Img.PixelColor(x: Pos.x, y: Pos.y);
91 return !m_vVisitedPixels[Pos.x + Pos.y * m_Img.m_Width] && CheckPixel.a > 0 && Pixel == CheckPixel;
92}
93
94void CQuadArt::MarkPixelAsVisited(const ivec2 &Pos, const ivec2 &Size)
95{
96 for(int y = Pos.y; y < Pos.y + Size.y; y++)
97 {
98 for(int x = Pos.x; x < Pos.x + Size.x; x++)
99 {
100 size_t Index = (size_t)(x + y * m_Img.m_Width);
101 if(Index < m_vVisitedPixels.size())
102 m_vVisitedPixels[Index] = true;
103 }
104 }
105}
106
107CQuad CQuadArt::CreateNewQuad(const vec2 &Pos, const ivec2 &Size, const ColorRGBA &Color) const
108{
109 CQuad Quad;
110 Quad.m_PosEnv = Quad.m_ColorEnv = -1;
111 Quad.m_PosEnvOffset = Quad.m_ColorEnvOffset = 0;
112 int x = f2fx(v: Pos.x), y = f2fx(v: Pos.y), w = f2fx(v: Size.x / 2.f), h = f2fx(v: Size.y / 2.f);
113
114 for(int i = 0; i < 2; ++i)
115 {
116 Quad.m_aPoints[i].y = y - h;
117 Quad.m_aPoints[i + 2].y = y + h;
118 Quad.m_aPoints[i * 2].x = x - w;
119 Quad.m_aPoints[i * 2 + 1].x = x + w;
120 }
121
122 for(auto &QuadColor : Quad.m_aColors)
123 {
124 QuadColor.r = (int)(Color.r * 255);
125 QuadColor.g = (int)(Color.g * 255);
126 QuadColor.b = (int)(Color.b * 255);
127 QuadColor.a = (int)(Color.a * 255);
128 }
129
130 Quad.m_aPoints[4].x = m_Parameters.m_Centralize ? i2fx(v: -1) : x;
131 Quad.m_aPoints[4].y = m_Parameters.m_Centralize ? i2fx(v: -1) : y;
132
133 for(int i = 0; i < 4; ++i)
134 {
135 Quad.m_aTexcoords[i].x = i2fx(v: i % 2);
136 Quad.m_aTexcoords[i].y = i2fx(v: i / 2);
137 }
138 return Quad;
139}
140
141bool CQuadArt::Create(std::shared_ptr<CLayerQuads> &pQuadLayer)
142{
143 size_t MaxNewQuads = std::ceil(x: (float)(m_Img.m_Width * m_Img.m_Height) / m_Parameters.m_ImagePixelSize);
144 pQuadLayer->m_vQuads.clear();
145 pQuadLayer->m_vQuads.reserve(n: MaxNewQuads);
146
147 size_t ImgPixelSize = m_Parameters.m_ImagePixelSize;
148 ivec2 Scale(1, 1);
149
150 for(size_t y = 0; y < m_Img.m_Height; y += ImgPixelSize)
151 {
152 for(size_t x = 0; x < m_Img.m_Width; x += ImgPixelSize)
153 {
154 ivec2 ImgPos(x, y);
155 ColorRGBA Pixel = GetPixelClamped(Pos: ImgPos);
156 if(m_vVisitedPixels[x + y * m_Img.m_Width] || Pixel.a == 0.f)
157 continue;
158
159 if(m_Parameters.m_Optimize)
160 Scale = GetOptimizedQuadSize(Pixel, Pos: ImgPos);
161
162 ivec2 Size = Scale * m_Parameters.m_QuadPixelSize;
163 vec2 Pos(((x / (float)ImgPixelSize) + (Scale.x / 2.f)) * m_Parameters.m_QuadPixelSize,
164 ((y / (float)ImgPixelSize) + (Scale.y / 2.f)) * m_Parameters.m_QuadPixelSize);
165
166 CQuad Quad = CreateNewQuad(Pos, Size, Color: Pixel);
167 pQuadLayer->m_vQuads.emplace_back(args&: Quad);
168 }
169 }
170 pQuadLayer->m_vQuads.shrink_to_fit();
171 return true;
172}
173
174void CEditorMap::AddQuadArt(CImageInfo &&Image, const CQuadArtParameters &Parameters, bool IgnoreHistory)
175{
176 char aQuadArtName[IO_MAX_PATH_LENGTH];
177 fs_split_file_extension(filename: fs_filename(path: Parameters.m_aFilename), name: aQuadArtName, name_size: sizeof(aQuadArtName));
178
179 std::shared_ptr<CLayerGroup> pGroup = NewGroup();
180 str_copy(dst&: pGroup->m_aName, src: aQuadArtName);
181 pGroup->m_UseClipping = true;
182 pGroup->m_ClipX = -1;
183 pGroup->m_ClipY = -1;
184 pGroup->m_ClipH = std::ceil(x: Image.m_Height * 1.f * Parameters.m_QuadPixelSize / Parameters.m_ImagePixelSize) + 2;
185 pGroup->m_ClipW = std::ceil(x: Image.m_Width * 1.f * Parameters.m_QuadPixelSize / Parameters.m_ImagePixelSize) + 2;
186
187 std::shared_ptr<CLayerQuads> pLayer = std::make_shared<CLayerQuads>(args: this);
188 str_copy(dst&: pLayer->m_aName, src: aQuadArtName);
189 pGroup->AddLayer(pLayer);
190 pLayer->m_Flags |= LAYERFLAG_DETAIL;
191
192 CQuadArt QuadArt(Parameters, std::move(Image));
193 QuadArt.Create(pQuadLayer&: pLayer);
194
195 if(!IgnoreHistory)
196 m_EditorHistory.RecordAction(pAction: std::make_shared<CEditorActionQuadArt>(args: this, args&: pGroup));
197
198 OnModify();
199}
200
201bool CEditor::CallbackAddQuadArt(const char *pFilepath, int StorageType, void *pUser)
202{
203 CEditor *pEditor = (CEditor *)pUser;
204
205 pEditor->m_QuadArtParameters.m_ImagePixelSize = 1;
206 pEditor->m_QuadArtParameters.m_QuadPixelSize = 4;
207 pEditor->m_QuadArtParameters.m_Optimize = true;
208 pEditor->m_QuadArtParameters.m_Centralize = false;
209
210 if(!pEditor->Graphics()->LoadPng(Image&: pEditor->m_QuadArtImageInfo, pFilename: pFilepath, StorageType))
211 {
212 pEditor->ShowFileDialogError(pFormat: "Failed to load image from file '%s'.", pFilepath);
213 return false;
214 }
215
216 str_copy(dst&: pEditor->m_QuadArtParameters.m_aFilename, src: pFilepath);
217
218 CUIRect View = *(pEditor->Ui()->Screen());
219
220 static SPopupMenuId s_PopupQuadArtId;
221 constexpr float PopupWidth = 400.0f;
222 constexpr float PopupHeight = 120.0f;
223 pEditor->Ui()->DoPopupMenu(pId: &s_PopupQuadArtId, X: View.w / 2.0f - PopupWidth / 2.0f, Y: View.h / 2.0f - PopupHeight / 2.0f, Width: PopupWidth, Height: PopupHeight, pContext: pEditor, pfnFunc: PopupQuadArt);
224 return false;
225}
226
227CUi::EPopupMenuFunctionResult CEditor::PopupQuadArt(void *pContext, CUIRect View, bool Active)
228{
229 CEditor *pEditor = static_cast<CEditor *>(pContext);
230
231 enum
232 {
233 PROP_IMAGE_PIXELSIZE = 0,
234 PROP_QUAD_PIXELSIZE,
235 PROP_OPTIMIZE,
236 PROP_CENTRALIZE,
237 NUM_PROPS,
238 };
239
240 CProperty aProps[] = {
241 {"Image pixelsize", pEditor->m_QuadArtParameters.m_ImagePixelSize, PROPTYPE_INT, 1, 1024},
242 {"Quad pixelsize", pEditor->m_QuadArtParameters.m_QuadPixelSize, PROPTYPE_INT, 1, 1024},
243 {"Optimize", pEditor->m_QuadArtParameters.m_Optimize, PROPTYPE_BOOL, false, true},
244 {"Centralize", pEditor->m_QuadArtParameters.m_Centralize, PROPTYPE_BOOL, false, true},
245 {nullptr},
246 };
247
248 static int s_aIds[NUM_PROPS] = {0};
249 int NewVal = 0;
250
251 // Title
252 CUIRect Label;
253 View.HSplitTop(Cut: 20.0f, pTop: &Label, pBottom: &View);
254 pEditor->Ui()->DoLabel(pRect: &Label, pText: "Configure quad art", Size: 20.0f, Align: TEXTALIGN_MC);
255 View.HSplitTop(Cut: 10.0f, pTop: nullptr, pBottom: &View);
256
257 // Properties
258 int Prop = pEditor->DoProperties(pToolbox: &View, pProps: aProps, pIds: s_aIds, pNewVal: &NewVal);
259
260 if(Prop == PROP_IMAGE_PIXELSIZE)
261 {
262 pEditor->m_QuadArtParameters.m_ImagePixelSize = NewVal;
263 }
264 else if(Prop == PROP_QUAD_PIXELSIZE)
265 {
266 pEditor->m_QuadArtParameters.m_QuadPixelSize = NewVal;
267 }
268 else if(Prop == PROP_OPTIMIZE)
269 {
270 pEditor->m_QuadArtParameters.m_Optimize = (bool)NewVal;
271 }
272 else if(Prop == PROP_CENTRALIZE)
273 {
274 pEditor->m_QuadArtParameters.m_Centralize = (bool)NewVal;
275 }
276
277 // Buttons
278 CUIRect BottomBar, Left, Right;
279 View.HSplitBottom(Cut: 20.f, pTop: &View, pBottom: &BottomBar);
280 BottomBar.VSplitLeft(Cut: 110.f, pLeft: &Left, pRight: &BottomBar);
281
282 static int s_Cancel;
283 if(pEditor->DoButton_Editor(pId: &s_Cancel, pText: "Cancel", Checked: 0, pRect: &Left, Flags: BUTTONFLAG_LEFT, pToolTip: nullptr))
284 {
285 pEditor->m_QuadArtImageInfo.Free();
286 return CUi::POPUP_CLOSE_CURRENT;
287 }
288
289 BottomBar.VSplitRight(Cut: 110.f, pLeft: &BottomBar, pRight: &Right);
290 static int s_Confirm;
291 constexpr int MaximumQuadThreshold = 100'000;
292 if(pEditor->DoButton_Editor(pId: &s_Confirm, pText: "Confirm", Checked: 0, pRect: &Right, Flags: BUTTONFLAG_LEFT, pToolTip: nullptr))
293 {
294 size_t MaximumQuadNumber = (pEditor->m_QuadArtImageInfo.m_Width / pEditor->m_QuadArtParameters.m_ImagePixelSize) *
295 (pEditor->m_QuadArtImageInfo.m_Height / pEditor->m_QuadArtParameters.m_ImagePixelSize);
296 if(MaximumQuadNumber > MaximumQuadThreshold)
297 {
298 pEditor->m_PopupEventType = CEditor::POPEVENT_QUAD_ART_BIG_IMAGE;
299 pEditor->m_PopupEventActivated = true;
300 }
301 else
302 {
303 pEditor->Map()->AddQuadArt(Image: std::move(pEditor->m_QuadArtImageInfo), Parameters: pEditor->m_QuadArtParameters, IgnoreHistory: false);
304 pEditor->OnDialogClose();
305 }
306 return CUi::POPUP_CLOSE_CURRENT;
307 }
308
309 return CUi::POPUP_KEEP_OPEN;
310}
311