| 1 | #include "editor.h" |
| 2 | #include "editor_actions.h" |
| 3 | |
| 4 | #include <game/editor/mapitems/image.h> |
| 5 | |
| 6 | #include <array> |
| 7 | |
| 8 | static bool operator<(const ColorRGBA &Left, const ColorRGBA &Right) // NOLINT(unused-function) |
| 9 | { |
| 10 | if(Left.r != Right.r) |
| 11 | return Left.r < Right.r; |
| 12 | else if(Left.g != Right.g) |
| 13 | return Left.g < Right.g; |
| 14 | else if(Left.b != Right.b) |
| 15 | return Left.b < Right.b; |
| 16 | else |
| 17 | return Left.a < Right.a; |
| 18 | } |
| 19 | |
| 20 | static std::vector<ColorRGBA> GetUniqueColors(const CImageInfo &Image) |
| 21 | { |
| 22 | std::set<ColorRGBA> ColorSet; |
| 23 | std::vector<ColorRGBA> vUniqueColors; |
| 24 | for(size_t x = 0; x < Image.m_Width; x++) |
| 25 | { |
| 26 | for(size_t y = 0; y < Image.m_Height; y++) |
| 27 | { |
| 28 | ColorRGBA Color = Image.PixelColor(x, y); |
| 29 | if(Color.a > 0 && ColorSet.insert(x: Color).second) |
| 30 | vUniqueColors.push_back(x: Color); |
| 31 | } |
| 32 | } |
| 33 | std::sort(first: vUniqueColors.begin(), last: vUniqueColors.end()); |
| 34 | |
| 35 | return vUniqueColors; |
| 36 | } |
| 37 | |
| 38 | constexpr int NumTilesRow = 16; |
| 39 | constexpr int NumTilesColumn = 16; |
| 40 | constexpr int NumTiles = NumTilesRow * NumTilesColumn; |
| 41 | constexpr int TileSize = 64; |
| 42 | |
| 43 | static int GetColorIndex(const std::array<ColorRGBA, NumTiles> &ColorGroup, ColorRGBA Color) |
| 44 | { |
| 45 | std::array<ColorRGBA, NumTiles>::const_iterator Iterator = std::find(first: ColorGroup.begin(), last: ColorGroup.end(), val: Color); |
| 46 | if(Iterator == ColorGroup.end()) |
| 47 | return 0; |
| 48 | return Iterator - ColorGroup.begin(); |
| 49 | } |
| 50 | |
| 51 | static std::vector<std::array<ColorRGBA, NumTiles>> GroupColors(const std::vector<ColorRGBA> &vColors) |
| 52 | { |
| 53 | std::vector<std::array<ColorRGBA, NumTiles>> vaColorGroups; |
| 54 | |
| 55 | for(size_t i = 0; i < vColors.size(); i += NumTiles - 1) |
| 56 | { |
| 57 | auto &Group = vaColorGroups.emplace_back(); |
| 58 | std::copy_n(first: vColors.begin() + i, n: std::min<size_t>(a: NumTiles - 1, b: vColors.size() - i), result: Group.begin() + 1); |
| 59 | } |
| 60 | |
| 61 | return vaColorGroups; |
| 62 | } |
| 63 | |
| 64 | static void SetColorTile(CImageInfo &Image, int x, int y, ColorRGBA Color) |
| 65 | { |
| 66 | for(int i = 0; i < TileSize; i++) |
| 67 | { |
| 68 | for(int j = 0; j < TileSize; j++) |
| 69 | Image.SetPixelColor(x: x * TileSize + i, y: y * TileSize + j, Color); |
| 70 | } |
| 71 | } |
| 72 | |
| 73 | static CImageInfo ColorGroupToImage(const std::array<ColorRGBA, NumTiles> &aColorGroup) |
| 74 | { |
| 75 | CImageInfo Image; |
| 76 | Image.m_Width = NumTilesRow * TileSize; |
| 77 | Image.m_Height = NumTilesColumn * TileSize; |
| 78 | Image.m_Format = CImageInfo::FORMAT_RGBA; |
| 79 | Image.m_pData = static_cast<uint8_t *>(malloc(size: Image.DataSize())); |
| 80 | |
| 81 | for(int y = 0; y < NumTilesColumn; y++) |
| 82 | { |
| 83 | for(int x = 0; x < NumTilesRow; x++) |
| 84 | { |
| 85 | int ColorIndex = x + NumTilesRow * y; |
| 86 | SetColorTile(Image, x, y, Color: aColorGroup[ColorIndex]); |
| 87 | } |
| 88 | } |
| 89 | |
| 90 | return Image; |
| 91 | } |
| 92 | |
| 93 | static std::vector<CImageInfo> ColorGroupsToImages(const std::vector<std::array<ColorRGBA, NumTiles>> &vaColorGroups) |
| 94 | { |
| 95 | std::vector<CImageInfo> vImages; |
| 96 | vImages.reserve(n: vaColorGroups.size()); |
| 97 | for(const auto &ColorGroup : vaColorGroups) |
| 98 | vImages.push_back(x: ColorGroupToImage(aColorGroup: ColorGroup)); |
| 99 | |
| 100 | return vImages; |
| 101 | } |
| 102 | |
| 103 | static std::shared_ptr<CEditorImage> ImageInfoToEditorImage(CEditorMap *pMap, const CImageInfo &Image, const char *pName) |
| 104 | { |
| 105 | std::shared_ptr<CEditorImage> pEditorImage = std::make_shared<CEditorImage>(args&: pMap); |
| 106 | pEditorImage->m_Width = Image.m_Width; |
| 107 | pEditorImage->m_Height = Image.m_Height; |
| 108 | pEditorImage->m_Format = Image.m_Format; |
| 109 | pEditorImage->m_pData = Image.m_pData; |
| 110 | |
| 111 | int TextureLoadFlag = pMap->Editor()->Graphics()->Uses2DTextureArrays() ? IGraphics::TEXLOAD_TO_2D_ARRAY_TEXTURE : IGraphics::TEXLOAD_TO_3D_TEXTURE; |
| 112 | pEditorImage->m_Texture = pMap->Editor()->Graphics()->LoadTextureRaw(Image, Flags: TextureLoadFlag, pTexName: pName); |
| 113 | pEditorImage->m_External = 0; |
| 114 | str_copy(dst&: pEditorImage->m_aName, src: pName); |
| 115 | |
| 116 | return pEditorImage; |
| 117 | } |
| 118 | |
| 119 | static std::shared_ptr<CLayerTiles> AddLayerWithImage(CEditorMap *pMap, const std::shared_ptr<CLayerGroup> &pGroup, int Width, int Height, const CImageInfo &Image, const char *pName) |
| 120 | { |
| 121 | std::shared_ptr<CEditorImage> pEditorImage = ImageInfoToEditorImage(pMap, Image, pName); |
| 122 | pMap->m_vpImages.push_back(x: pEditorImage); |
| 123 | |
| 124 | std::shared_ptr<CLayerTiles> pLayer = std::make_shared<CLayerTiles>(args&: pMap, args&: Width, args&: Height); |
| 125 | str_copy(dst&: pLayer->m_aName, src: pName); |
| 126 | pLayer->m_Image = pMap->m_vpImages.size() - 1; |
| 127 | pGroup->AddLayer(pLayer); |
| 128 | |
| 129 | return pLayer; |
| 130 | } |
| 131 | |
| 132 | static void SetTilelayerIndices(const std::shared_ptr<CLayerTiles> &pLayer, const std::array<ColorRGBA, NumTiles> &aColorGroup, const CImageInfo &Image) |
| 133 | { |
| 134 | for(int x = 0; x < pLayer->m_Width; x++) |
| 135 | { |
| 136 | for(int y = 0; y < pLayer->m_Height; y++) |
| 137 | pLayer->m_pTiles[x + y * pLayer->m_Width].m_Index = GetColorIndex(ColorGroup: aColorGroup, Color: Image.PixelColor(x, y)); |
| 138 | } |
| 139 | } |
| 140 | |
| 141 | void CEditor::AddTileart(bool IgnoreHistory) |
| 142 | { |
| 143 | char aTileArtFilename[IO_MAX_PATH_LENGTH]; |
| 144 | IStorage::StripPathAndExtension(pFilename: m_aTileartFilename, pBuffer: aTileArtFilename, BufferSize: sizeof(aTileArtFilename)); |
| 145 | |
| 146 | std::shared_ptr<CLayerGroup> pGroup = m_Map.NewGroup(); |
| 147 | str_copy(dst&: pGroup->m_aName, src: aTileArtFilename); |
| 148 | |
| 149 | int ImageCount = m_Map.m_vpImages.size(); |
| 150 | |
| 151 | auto vUniqueColors = GetUniqueColors(Image: m_TileartImageInfo); |
| 152 | auto vaColorGroups = GroupColors(vColors: vUniqueColors); |
| 153 | auto vColorImages = ColorGroupsToImages(vaColorGroups); |
| 154 | char aImageName[IO_MAX_PATH_LENGTH]; |
| 155 | for(size_t i = 0; i < vColorImages.size(); i++) |
| 156 | { |
| 157 | str_format(buffer: aImageName, buffer_size: sizeof(aImageName), format: "%s %" PRIzu, aTileArtFilename, i + 1); |
| 158 | std::shared_ptr<CLayerTiles> pLayer = AddLayerWithImage(pMap: &m_Map, pGroup, Width: m_TileartImageInfo.m_Width, Height: m_TileartImageInfo.m_Height, Image: vColorImages[i], pName: aImageName); |
| 159 | SetTilelayerIndices(pLayer, aColorGroup: vaColorGroups[i], Image: m_TileartImageInfo); |
| 160 | } |
| 161 | auto IndexMap = m_Map.SortImages(); |
| 162 | |
| 163 | if(!IgnoreHistory) |
| 164 | { |
| 165 | m_Map.m_EditorHistory.RecordAction(pAction: std::make_shared<CEditorActionTileArt>(args: &m_Map, args&: ImageCount, args&: m_aTileartFilename, args&: IndexMap)); |
| 166 | } |
| 167 | |
| 168 | m_TileartImageInfo.Free(); |
| 169 | m_Map.OnModify(); |
| 170 | OnDialogClose(); |
| 171 | } |
| 172 | |
| 173 | void CEditor::TileartCheckColors() |
| 174 | { |
| 175 | auto vUniqueColors = GetUniqueColors(Image: m_TileartImageInfo); |
| 176 | int NumColorGroups = std::ceil(x: vUniqueColors.size() / 255.0f); |
| 177 | if(m_Map.m_vpImages.size() + NumColorGroups >= 64) |
| 178 | { |
| 179 | m_PopupEventType = CEditor::POPEVENT_TILEART_TOO_MANY_COLORS; |
| 180 | m_PopupEventActivated = true; |
| 181 | m_TileartImageInfo.Free(); |
| 182 | } |
| 183 | else if(NumColorGroups > 1) |
| 184 | { |
| 185 | m_PopupEventType = CEditor::POPEVENT_TILEART_MANY_COLORS; |
| 186 | m_PopupEventActivated = true; |
| 187 | } |
| 188 | else |
| 189 | AddTileart(); |
| 190 | } |
| 191 | |
| 192 | bool CEditor::CallbackAddTileart(const char *pFilepath, int StorageType, void *pUser) |
| 193 | { |
| 194 | CEditor *pEditor = (CEditor *)pUser; |
| 195 | |
| 196 | if(!pEditor->Graphics()->LoadPng(Image&: pEditor->m_TileartImageInfo, pFilename: pFilepath, StorageType)) |
| 197 | { |
| 198 | pEditor->ShowFileDialogError(pFormat: "Failed to load image from file '%s'." , pFilepath); |
| 199 | return false; |
| 200 | } |
| 201 | |
| 202 | str_copy(dst&: pEditor->m_aTileartFilename, src: pFilepath); |
| 203 | if(pEditor->m_TileartImageInfo.m_Width * pEditor->m_TileartImageInfo.m_Height > 10'000) |
| 204 | { |
| 205 | pEditor->m_PopupEventType = CEditor::POPEVENT_TILEART_BIG_IMAGE; |
| 206 | pEditor->m_PopupEventActivated = true; |
| 207 | return false; |
| 208 | } |
| 209 | else |
| 210 | { |
| 211 | pEditor->TileartCheckColors(); |
| 212 | return false; |
| 213 | } |
| 214 | } |
| 215 | |