/
Metallist64
/
rbfx
Обзор
Документация
Войти
/
Metallist64
/
rbfx
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
Аналитика
Безопасность
master
Source/Tools/SpritePacker/SpritePacker.cpp
419 строк
15 KB
Rokas Kupstys
Merge branch 'upstream-1.8'
07 фев 2022, 11:34
07 фев 2022, 11:34
39146ba
Код
Авторство
О чём код?
// // Copyright (c) 2008-2022 the Urho3D project. // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. // #include <Urho3D/Core/Context.h> #include <Urho3D/Core/ProcessUtils.h> #include <Urho3D/Core/StringUtils.h> #include <Urho3D/IO/File.h> #include <Urho3D/IO/FileSystem.h> #include <Urho3D/IO/Log.h> #include <Urho3D/Resource/Image.h> #include <Urho3D/Resource/XMLElement.h> #include <Urho3D/Resource/XMLFile.h> #ifdef WIN32 #include <windows.h> #endif #define STBRP_LARGE_RECTS #if !URHO3D_STATIC # define STB_RECT_PACK_IMPLEMENTATION #endif #include <STB/stb_rect_pack.h> #include <Urho3D/DebugNew.h> using namespace Urho3D; // number of nodes allocated to each packer info. since this packer is not suited for real time purposes we can over allocate. const int PACKER_NUM_NODES = 4096; const int MAX_TEXTURE_SIZE = 2048; int main(int argc, char** argv); void Run(ea::vector<ea::string>& arguments); class PackerInfo : public RefCounted { public: ea::string path; ea::string name; int x{}; int y{}; int offsetX{}; int offsetY{}; int width{}; int height{}; int frameWidth{}; int frameHeight{}; PackerInfo(const ea::string& path_, const ea::string& name_) : path(path_), name(name_) { } ~PackerInfo() override = default; }; void Help() { ErrorExit("Usage: SpritePacker -options <input file> <input file> <output png file>\n" "\n" "Options:\n" "-h Shows this help message.\n" "-px Adds x pixels of padding per image to width.\n" "-py Adds y pixels of padding per image to height.\n" "-ox Adds x pixels to the horizontal position per image.\n" "-oy Adds y pixels to the horizontal position per image.\n" "-frameHeight Sets a fixed height for image and centers within frame.\n" "-frameWidth Sets a fixed width for image and centers within frame.\n" "-trim Trims excess transparent space from individual images offsets by frame size.\n" "-xml \'path\' Generates an SpriteSheet xml file at path.\n" "-debug Draws allocation boxes on sprite.\n"); } int main(int argc, char** argv) { ea::vector<ea::string> arguments; #ifdef WIN32 arguments = ParseArguments(GetCommandLineW()); #else arguments = ParseArguments(argc, argv); #endif Run(arguments); return 0; } void Run(ea::vector<ea::string>& arguments) { if (arguments.size() < 2) Help(); SharedPtr<Context> context(new Context()); context->RegisterSubsystem(new FileSystem(context)); context->RegisterSubsystem(new Log(context)); auto* fileSystem = context->GetSubsystem<FileSystem>(); ea::vector<ea::string> inputFiles; ea::string outputFile; ea::string spriteSheetFileName; bool debug = false; unsigned padX = 0; unsigned padY = 0; unsigned offsetX = 0; unsigned offsetY = 0; unsigned frameWidth = 0; unsigned frameHeight = 0; bool help = false; bool trim = false; while (arguments.size() > 0) { ea::string arg = arguments[0]; arguments.pop_front(); if (arg.empty()) continue; if (arg.starts_with("-")) { if (arg == "-px") { padX = ToUInt(arguments[0]); arguments.pop_front(); } else if (arg == "-py") { padY = ToUInt(arguments[0]); arguments.pop_front(); } else if (arg == "-ox") { offsetX = ToUInt(arguments[0]); arguments.pop_front(); } else if (arg == "-oy") { offsetY = ToUInt(arguments[0]); arguments.pop_front(); } else if (arg == "-frameWidth") { frameWidth = ToUInt(arguments[0]); arguments.pop_front(); } else if (arg == "-frameHeight") { frameHeight = ToUInt(arguments[0]); arguments.pop_front(); } else if (arg == "-trim") { trim = true; } else if (arg == "-xml") { spriteSheetFileName = arguments[0]; arguments.pop_front(); } else if (arg == "-h") { help = true; break; } else if (arg == "-debug") { debug = true; } } else inputFiles.push_back(arg); } if (help) Help(); if (inputFiles.size() < 2) ErrorExit("An input and output file must be specified."); if (frameWidth ^ frameHeight) ErrorExit("Both frameHeight and frameWidth must be omitted or specified."); // take last input file as output if (inputFiles.size() > 1) { outputFile = inputFiles[inputFiles.size() - 1]; URHO3D_LOGINFO("Output file set to " + outputFile + "."); inputFiles.erase_at(inputFiles.size() - 1); } // set spritesheet name to outputfile.xml if not specified if (spriteSheetFileName.empty()) spriteSheetFileName = ReplaceExtension(outputFile, ".xml"); if (GetParentPath(spriteSheetFileName) != GetParentPath(outputFile)) ErrorExit("Both output xml and png must be in the same folder"); // check all input files exist for (unsigned i = 0; i < inputFiles.size(); ++i) { URHO3D_LOGINFO("Checking " + inputFiles[i] + " to see if file exists."); if (!fileSystem->FileExists(inputFiles[i])) ErrorExit("File " + inputFiles[i] + " does not exist."); } // Set the max offset equal to padding to prevent images from going out of bounds offsetX = Min((int)offsetX, (int)padX); offsetY = Min((int)offsetY, (int)padY); ea::vector<SharedPtr<PackerInfo > > packerInfos; for (unsigned i = 0; i < inputFiles.size(); ++i) { ea::string path = inputFiles[i]; ea::string name = ReplaceExtension(GetFileName(path), ""); File file(context, path); Image image(context); if (!image.Load(file)) ErrorExit("Could not load image " + path + "."); if (image.IsCompressed()) ErrorExit(path + " is compressed. Compressed images are not allowed."); SharedPtr<PackerInfo> packerInfo(new PackerInfo(path, name)); int imageWidth = image.GetWidth(); int imageHeight = image.GetHeight(); int trimOffsetX = 0; int trimOffsetY = 0; int adjustedWidth = imageWidth; int adjustedHeight = imageHeight; if (trim) { int minX = imageWidth; int minY = imageHeight; int maxX = 0; int maxY = 0; for (int y = 0; y < imageHeight; ++y) { for (int x = 0; x < imageWidth; ++x) { bool found = (image.GetPixelInt(x, y) & 0x000000ffu) != 0; if (found) { minX = Min(minX, x); minY = Min(minY, y); maxX = Max(maxX, x); maxY = Max(maxY, y); } } } trimOffsetX = minX; trimOffsetY = minY; adjustedWidth = maxX - minX + 1; adjustedHeight = maxY - minY + 1; } if (trim) { packerInfo->frameWidth = imageWidth; packerInfo->frameHeight = imageHeight; } else if (frameWidth || frameHeight) { packerInfo->frameWidth = frameWidth; packerInfo->frameHeight = frameHeight; } packerInfo->width = adjustedWidth; packerInfo->height = adjustedHeight; packerInfo->offsetX -= trimOffsetX; packerInfo->offsetY -= trimOffsetY; packerInfos.push_back(packerInfo); } int packedWidth = MAX_TEXTURE_SIZE; int packedHeight = MAX_TEXTURE_SIZE; { // fill up an list of tries in increasing size and take the first win ea::vector<IntVector2> tries; for(unsigned x=2; x<11; ++x) { for(unsigned y=2; y<11; ++y) tries.push_back(IntVector2((1u << x), (1u << y))); } // load rectangles auto* packerRects = new stbrp_rect[packerInfos.size()]; for (unsigned i = 0; i < packerInfos.size(); ++i) { PackerInfo* packerInfo = packerInfos[i]; stbrp_rect* packerRect = &packerRects[i]; packerRect->id = i; packerRect->h = packerInfo->height + padY; packerRect->w = packerInfo->width + padX; } bool success = false; while (tries.size() > 0) { IntVector2 size = tries[0]; tries.pop_front(); bool fit = true; int textureHeight = size.y_; int textureWidth = size.x_; if (success && textureHeight * textureWidth > packedWidth * packedHeight) continue; stbrp_context packerContext; stbrp_node packerMemory[PACKER_NUM_NODES]; stbrp_init_target(&packerContext, textureWidth, textureHeight, packerMemory, PACKER_NUM_NODES); if (!stbrp_pack_rects(&packerContext, packerRects, packerInfos.size())) { // check to see if everything fit fit = false; } if (fit) { success = true; // distribute values to packer info for (unsigned i = 0; i < packerInfos.size(); ++i) { stbrp_rect* packerRect = &packerRects[i]; PackerInfo* packerInfo = packerInfos[packerRect->id]; packerInfo->x = packerRect->x; packerInfo->y = packerRect->y; } packedWidth = size.x_; packedHeight = size.y_; } } delete[] packerRects; if (!success) ErrorExit("Could not allocate for all images. The max sprite sheet texture size is " + ea::to_string(MAX_TEXTURE_SIZE) + "x" + ea::to_string(MAX_TEXTURE_SIZE) + "."); } // create image for spritesheet Image spriteSheetImage(context); spriteSheetImage.SetSize(packedWidth, packedHeight, 4); // zero out image spriteSheetImage.SetData(nullptr); XMLFile xml(context); XMLElement root = xml.CreateRoot("TextureAtlas"); root.SetAttribute("imagePath", GetFileNameAndExtension(outputFile)); for (unsigned i = 0; i < packerInfos.size(); ++i) { SharedPtr<PackerInfo> packerInfo = packerInfos[i]; XMLElement subTexture = root.CreateChild("SubTexture"); subTexture.SetString("name", packerInfo->name); subTexture.SetInt("x", packerInfo->x + offsetX); subTexture.SetInt("y", packerInfo->y + offsetY); subTexture.SetInt("width", packerInfo->width); subTexture.SetInt("height", packerInfo->height); if (packerInfo->frameWidth || packerInfo->frameHeight) { subTexture.SetInt("frameWidth", packerInfo->frameWidth); subTexture.SetInt("frameHeight", packerInfo->frameHeight); subTexture.SetInt("offsetX", packerInfo->offsetX); subTexture.SetInt("offsetY", packerInfo->offsetY); } URHO3D_LOGINFO("Transferring " + packerInfo->path + " to sprite sheet."); File file(context, packerInfo->path); Image image(context); if (!image.Load(file)) ErrorExit("Could not load image " + packerInfo->path + "."); for (int y = 0; y < packerInfo->height; ++y) { for (int x = 0; x < packerInfo->width; ++x) { unsigned color = image.GetPixelInt(x - packerInfo->offsetX, y - packerInfo->offsetY); spriteSheetImage.SetPixelInt( packerInfo->x + offsetX + x, packerInfo->y + offsetY + y, color); } } } if (debug) { unsigned OUTER_BOUNDS_DEBUG_COLOR = Color::BLUE.ToUInt(); unsigned INNER_BOUNDS_DEBUG_COLOR = Color::GREEN.ToUInt(); URHO3D_LOGINFO("Drawing debug information."); for (unsigned i = 0; i < packerInfos.size(); ++i) { SharedPtr<PackerInfo> packerInfo = packerInfos[i]; // Draw outer bounds for (int x = 0; x < packerInfo->frameWidth; ++x) { spriteSheetImage.SetPixelInt(packerInfo->x + x, packerInfo->y, OUTER_BOUNDS_DEBUG_COLOR); spriteSheetImage.SetPixelInt(packerInfo->x + x, packerInfo->y + packerInfo->frameHeight, OUTER_BOUNDS_DEBUG_COLOR); } for (int y = 0; y < packerInfo->frameHeight; ++y) { spriteSheetImage.SetPixelInt(packerInfo->x, packerInfo->y + y, OUTER_BOUNDS_DEBUG_COLOR); spriteSheetImage.SetPixelInt(packerInfo->x + packerInfo->frameWidth, packerInfo->y + y, OUTER_BOUNDS_DEBUG_COLOR); } // Draw inner bounds for (int x = 0; x < packerInfo->width; ++x) { spriteSheetImage.SetPixelInt(packerInfo->x + offsetX + x, packerInfo->y + offsetY, INNER_BOUNDS_DEBUG_COLOR); spriteSheetImage.SetPixelInt(packerInfo->x + offsetX + x, packerInfo->y + offsetY + packerInfo->height, INNER_BOUNDS_DEBUG_COLOR); } for (int y = 0; y < packerInfo->height; ++y) { spriteSheetImage.SetPixelInt(packerInfo->x + offsetX, packerInfo->y + offsetY + y, INNER_BOUNDS_DEBUG_COLOR); spriteSheetImage.SetPixelInt(packerInfo->x + offsetX + packerInfo->width, packerInfo->y + offsetY + y, INNER_BOUNDS_DEBUG_COLOR); } } } URHO3D_LOGINFO("Saving output image."); spriteSheetImage.SavePNG(outputFile); URHO3D_LOGINFO("Saving SpriteSheet xml file."); File spriteSheetFile(context); spriteSheetFile.Open(spriteSheetFileName, FILE_WRITE); xml.Save(spriteSheetFile); }