1#include "quadart.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 CEditor::AddQuadArt(bool IgnoreHistory)
172{
173 char aQuadArtName[IO_MAX_PATH_LENGTH];
174 IStorage::StripPathAndExtension(pFilename: m_QuadArtParameters.m_aFilename, pBuffer: aQuadArtName, BufferSize: sizeof(aQuadArtName));
175
176 std::shared_ptr<CLayerGroup> pGroup = m_Map.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: m_QuadArtImageInfo.m_Height * 1.f * m_QuadArtParameters.m_QuadPixelSize / m_QuadArtParameters.m_ImagePixelSize) + 2;
182 pGroup->m_ClipW = std::ceil(x: m_QuadArtImageInfo.m_Width * 1.f * m_QuadArtParameters.m_QuadPixelSize / m_QuadArtParameters.m_ImagePixelSize) + 2;
183
184 std::shared_ptr<CLayerQuads> pLayer = std::make_shared<CLayerQuads>(args: &m_Map);
185 str_copy(dst&: pLayer->m_aName, src: aQuadArtName);
186 pGroup->AddLayer(pLayer);
187 pLayer->m_Flags |= LAYERFLAG_DETAIL;
188
189 CQuadArt QuadArt(m_QuadArtParameters, std::move(m_QuadArtImageInfo));
190 QuadArt.Create(pQuadLayer&: pLayer);
191
192 if(!IgnoreHistory)
193 m_Map.m_EditorHistory.RecordAction(pAction: std::make_shared<CEditorActionQuadArt>(args: &m_Map, args&: m_QuadArtParameters));
194
195 m_Map.OnModify();
196 OnDialogClose();
197}
198
199bool CEditor::CallbackAddQuadArt(const char *pFilepath, int StorageType, void *pUser)
200{
201 CEditor *pEditor = (CEditor *)pUser;
202
203 pEditor->m_QuadArtParameters.m_ImagePixelSize = 1;
204 pEditor->m_QuadArtParameters.m_QuadPixelSize = 4;
205 pEditor->m_QuadArtParameters.m_Optimize = true;
206 pEditor->m_QuadArtParameters.m_Centralize = false;
207
208 if(!pEditor->Graphics()->LoadPng(Image&: pEditor->m_QuadArtImageInfo, pFilename: pFilepath, StorageType))
209 {
210 pEditor->ShowFileDialogError(pFormat: "Failed to load image from file '%s'.", pFilepath);
211 return false;
212 }
213
214 str_copy(dst&: pEditor->m_QuadArtParameters.m_aFilename, src: pFilepath);
215
216 CUIRect View = *(pEditor->Ui()->Screen());
217
218 static SPopupMenuId s_PopupQuadArtId;
219 constexpr float PopupWidth = 400.0f;
220 constexpr float PopupHeight = 120.0f;
221 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);
222 return false;
223}
224