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