////////////////////////////////////////////////////////////////////// // This file is part of Remere's Map Editor ////////////////////////////////////////////////////////////////////// // Remere's Map Editor is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // Remere's Map Editor is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program. If not, see . ////////////////////////////////////////////////////////////////////// #include "main.h" #include "bitmap_to_map_converter.h" #include "editor.h" #include "map.h" #include "tile.h" #include "item.h" #include "ground_brush.h" #include "brush.h" #include "action.h" #include "gui.h" #include "settings.h" BitmapToMapConverter::BitmapToMapConverter(Editor &editor) : editor(editor) { } static float rgbToHue(uint8_t r, uint8_t g, uint8_t b) { float rf = r / 255.0f; float gf = g / 255.0f; float bf = b / 255.0f; float maxC = std::max({ rf, gf, bf }); float minC = std::min({ rf, gf, bf }); float delta = maxC - minC; if (delta < kAchromaticDelta) { return -1.0f; } float hue = 0.0f; if (maxC == rf) { hue = 60.0f * fmod((gf - bf) / delta, 6.0f); } else if (maxC == gf) { hue = 60.0f * ((bf - rf) / delta + 2.0f); } else { hue = 60.0f * ((rf - gf) / delta + 4.0f); } if (hue < 0.0f) { hue += 360.0f; } return hue; } const ColorMapping* BitmapToMapConverter::findMatchingColor( uint8_t r, uint8_t g, uint8_t b, const std::vector &mappings, int tolerance, MatchMode matchMode ) const { const ColorMapping* bestMatch = nullptr; int bestDistance = tolerance + 1; for (const auto &mapping : mappings) { if (mapping.ignore || mapping.brushName.empty()) { continue; } int distance = -1; if (matchMode == MatchMode::MATCH_HUE_HSL) { float pixelHue = rgbToHue(r, g, b); float mappingHue = rgbToHue(mapping.r, mapping.g, mapping.b); if (pixelHue < 0.0f || mappingHue < 0.0f) { distance = std::abs((int)r - (int)mapping.r) + std::abs((int)g - (int)mapping.g) + std::abs((int)b - (int)mapping.b); } else { float hueDiff = fabs(pixelHue - mappingHue); hueDiff = (hueDiff > 180.0f) ? 360.0f - hueDiff : hueDiff; distance = static_cast(hueDiff); } } else { distance = std::abs((int)r - (int)mapping.r) + std::abs((int)g - (int)mapping.g) + std::abs((int)b - (int)mapping.b); } if (distance >= 0 && distance <= tolerance && distance < bestDistance) { bestDistance = distance; bestMatch = &mapping; } } return bestMatch; } bool BitmapToMapConverter::isValidMapPosition(int x, int y, int z) const { return x >= 0 && y >= 0 && x <= rme::MapMaxWidth && y <= rme::MapMaxHeight && z >= 0 && z <= rme::MapMaxLayer; } void BitmapToMapConverter::trackBorderNeighbors(int mapX, int mapY, int mapZ, std::set &borderPositions) const { for (int dy = -1; dy <= 1; dy++) { for (int dx = -1; dx <= 1; dx++) { int bx = mapX + dx; int by = mapY + dy; if (isValidMapPosition(bx, by, mapZ)) { borderPositions.insert(Position(bx, by, mapZ)); } } } } void BitmapToMapConverter::placeGroundTiles( const ConvertParams ¶ms, BatchAction* batch, std::set &borderPositions, ConvertResult &result ) { Map &map = editor.getMap(); int imgWidth = params.image.GetWidth(); int imgHeight = params.image.GetHeight(); int totalPixels = imgWidth * imgHeight; Action* action = editor.createAction(batch); const unsigned char* imgData = params.image.GetData(); bool hasAlpha = params.image.HasAlpha(); const unsigned char* alphaData = hasAlpha ? params.image.GetAlpha() : nullptr; int pixelsDone = 0; for (int py = 0; py < imgHeight; py++) { for (int px = 0; px < imgWidth; px++) { if (pixelsDone % 4096 == 0) { g_gui.SetLoadDone(static_cast(50.0 * pixelsDone / totalPixels)); } pixelsDone++; if (hasAlpha && alphaData[py * imgWidth + px] < 128) { result.tilesSkipped++; continue; } int idx = (py * imgWidth + px) * 3; uint8_t r = imgData[idx]; uint8_t g_color = imgData[idx + 1]; uint8_t b_color = imgData[idx + 2]; const ColorMapping* mapping = findMatchingColor(r, g_color, b_color, params.mappings, params.tolerance, params.matchMode); if (!mapping) { result.tilesSkipped++; continue; } Brush* brush = g_brushes.getBrush(mapping->brushName); if (!brush || !brush->isGround()) { result.tilesSkipped++; continue; } int mapX = px + params.offsetX; int mapY = py + params.offsetY; int mapZ = params.offsetZ; if (!isValidMapPosition(mapX, mapY, mapZ)) { result.tilesSkipped++; continue; } Position pos(mapX, mapY, mapZ); TileLocation* location = map.createTileL(pos); Tile* tile = location->get(); Tile* new_tile = nullptr; if (tile) { new_tile = tile->deepCopy(map); new_tile->cleanBorders(); } else { new_tile = map.allocator(location); } brush->asGround()->draw(&map, new_tile, nullptr); action->addChange(newd Change(new_tile)); result.tilesPlaced++; trackBorderNeighbors(mapX, mapY, mapZ, borderPositions); } } batch->addAndCommitAction(action); } void BitmapToMapConverter::borderizeTiles( const std::set &borderPositions, BatchAction* batch ) { if (borderPositions.empty()) { return; } Map &map = editor.getMap(); Action* action = editor.createAction(batch); int bordersDone = 0; auto totalBorders = static_cast(borderPositions.size()); for (const Position &pos : borderPositions) { if (bordersDone % 4096 == 0) { g_gui.SetLoadDone(static_cast(50 + 49.0 * bordersDone / totalBorders)); } bordersDone++; TileLocation* location = map.createTileL(pos); Tile* tile = location->get(); if (tile) { Tile* new_tile = tile->deepCopy(map); new_tile->borderize(&map); action->addChange(newd Change(new_tile)); continue; } std::unique_ptr new_tile(map.allocator(location)); new_tile->borderize(&map); if (!new_tile->empty()) { action->addChange(newd Change(new_tile.release())); } } batch->addAndCommitAction(action); } ConvertResult BitmapToMapConverter::convert( const wxImage &image, const std::vector &mappings, int tolerance, MatchMode matchMode, int offsetX, int offsetY, int offsetZ ) { ConvertResult result; result.tilesPlaced = 0; result.tilesSkipped = 0; result.success = false; if (!image.IsOk()) { result.errorMessage = "Invalid image."; return result; } if (mappings.empty()) { result.errorMessage = "No color mappings defined."; return result; } g_gui.CreateLoadBar("Generating map from bitmap..."); BatchAction* batch = editor.createBatch(ACTION_DRAW); std::set borderPositions; ConvertParams params { image, mappings, tolerance, matchMode, offsetX, offsetY, offsetZ }; placeGroundTiles(params, batch, borderPositions, result); borderizeTiles(borderPositions, batch); editor.addBatch(batch); editor.updateActions(); g_gui.DestroyLoadBar(); g_gui.RefreshView(); result.success = true; return result; }