thorvg/tools/svg2png/svg2png.cpp
2024-06-27 14:42:27 +09:00

418 lines
13 KiB
C++

/*
* Copyright (c) 2020 - 2024 the ThorVG project. All rights reserved.
* 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 <iostream>
#include <thread>
#include <thorvg.h>
#include <vector>
#ifdef _WIN32
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#ifndef PATH_MAX
#define PATH_MAX MAX_PATH
#endif
#else
#include <dirent.h>
#include <unistd.h>
#include <limits.h>
#include <sys/stat.h>
#endif
#include "lodepng.h"
#define WIDTH_8K 7680
#define HEIGHT_8K 4320
#define SIZE_8K 33177600 //WIDTH_8K x HEIGHT_8K
using namespace std;
struct PngBuilder
{
void build(const string& fileName, const uint32_t width, const uint32_t height, uint32_t* buffer)
{
//Used ARGB8888 so have to move pixels now
vector<unsigned char> image;
image.resize(width * height * 4);
for (unsigned y = 0; y < height; y++) {
for (unsigned x = 0; x < width; x++) {
uint32_t n = buffer[y * width + x];
image[4 * width * y + 4 * x + 0] = (n >> 16) & 0xff;
image[4 * width * y + 4 * x + 1] = (n >> 8) & 0xff;
image[4 * width * y + 4 * x + 2] = n & 0xff;
image[4 * width * y + 4 * x + 3] = (n >> 24) & 0xff;
}
}
unsigned error = lodepng::encode(fileName, image, width, height);
//if there's an error, display it
if (error) cout << "encoder error " << error << ": " << lodepng_error_text(error) << endl;
}
};
struct Renderer
{
public:
int render(const char* path, int w, int h, const string& dst, uint32_t bgColor)
{
//Canvas
if (!canvas) createCanvas();
if (!canvas) {
cout << "Error: Canvas failure" << endl;
return 1;
}
//Picture
auto picture = tvg::Picture::gen();
tvg::Result result = picture->load(path);
if (result == tvg::Result::Unknown) {
cout << "Error: Couldn't load image " << path << endl;
return 1;
} else if (result == tvg::Result::InvalidArguments) {
cout << "Error: Couldn't load image(Invalid path or invalid SVG image) : " << path << endl;
return 1;
} else if (result == tvg::Result::NonSupport) {
cout << "Error: Couldn't load image(Not supported extension) : " << path << endl;
return 1;
}
if (w == 0 || h == 0) {
float fw, fh;
picture->size(&fw, &fh);
w = static_cast<uint32_t>(fw);
h = static_cast<uint32_t>(fh);
if (fw > w) w++;
if (fh > h) h++;
if (w * h > SIZE_8K) {
float scale = fw / fh;
if (scale > 1) {
w = WIDTH_8K;
h = static_cast<uint32_t>(w / scale);
} else {
h = HEIGHT_8K;
w = static_cast<uint32_t>(h * scale);
}
cout << "Warning: The SVG width and/or height values exceed the 8k resolution. "
"To avoid the heap overflow, the conversion to the PNG file made in " << w << " x " << h << " resolution." << endl;
picture->size(static_cast<float>(w), static_cast<float>(h));
}
} else {
picture->size(static_cast<float>(w), static_cast<float>(h));
}
//Buffer
createBuffer(w, h);
if (!buffer) {
cout << "Error: Buffer failure" << endl;
return 1;
}
if (canvas->target(buffer, w, w, h, tvg::SwCanvas::ARGB8888S) != tvg::Result::Success) {
cout << "Error: Canvas target failure" << endl;
return 1;
}
//Background color if needed
if (bgColor != 0xffffffff) {
uint8_t r = (uint8_t)((bgColor & 0xff0000) >> 16);
uint8_t g = (uint8_t)((bgColor & 0x00ff00) >> 8);
uint8_t b = (uint8_t)((bgColor & 0x0000ff));
auto shape = tvg::Shape::gen();
shape->appendRect(0, 0, static_cast<float>(w), static_cast<float>(h), 0, 0);
shape->fill(r, g, b);
if (canvas->push(std::move(shape)) != tvg::Result::Success) return 1;
}
//Drawing
canvas->push(std::move(picture));
canvas->draw();
canvas->sync();
//Build Png
PngBuilder builder;
builder.build(dst, w, h, buffer);
cout << "Generated PNG file: " << dst << endl;
canvas->clear(true);
return 0;
}
void terminate()
{
//Terminate ThorVG Engine
tvg::Initializer::term(tvg::CanvasEngine::Sw);
free(buffer);
}
private:
void createCanvas()
{
//Canvas Engine
tvg::CanvasEngine tvgEngine = tvg::CanvasEngine::Sw;
//Threads Count
auto threads = thread::hardware_concurrency();
//Initialize ThorVG Engine
if (tvg::Initializer::init(tvgEngine, threads) != tvg::Result::Success) {
cout << "Error: Engine is not supported" << endl;
}
//Create a Canvas
canvas = tvg::SwCanvas::gen();
}
void createBuffer(int w, int h)
{
uint32_t size = w * h;
//Reuse old buffer if size is enough
if (buffer && bufferSize >= size) return;
//Alloc or realloc buffer
buffer = (uint32_t*) realloc(buffer, sizeof(uint32_t) * size);
bufferSize = size;
}
private:
unique_ptr<tvg::SwCanvas> canvas = nullptr;
uint32_t* buffer = nullptr;
uint32_t bufferSize = 0;
};
struct App
{
public:
int setup(int argc, char** argv)
{
vector<const char*> paths;
for (int i = 1; i < argc; i++) {
const char* p = argv[i];
if (*p == '-') {
//flags
const char* p_arg = (i + 1 < argc) ? argv[++i] : nullptr;
if (p[1] == 'r') {
//image resolution
if (!p_arg) {
cout << "Error: Missing resolution attribute. Expected eg. -r 200x200." << endl;
return 1;
}
const char* x = strchr(p_arg, 'x');
if (x) {
width = atoi(p_arg);
height = atoi(x + 1);
}
if (!x || width <= 0 || height <= 0) {
cout << "Error: Resolution (" << p_arg << ") is corrupted. Expected eg. -r 200x200." << endl;
return 1;
}
} else if (p[1] == 'b') {
//image background color
if (!p_arg) {
cout << "Error: Missing background color attribute. Expected eg. -b fa7410." << endl;
return 1;
}
bgColor = (uint32_t) strtol(p_arg, NULL, 16);
} else {
cout << "Warning: Unknown flag (" << p << ")." << endl;
}
} else {
//arguments
paths.push_back(p);
}
}
int ret = 0;
if (paths.empty()) {
//no attributes - print help
return help();
} else {
for (auto path : paths) {
auto real_path = realFile(path);
if (real_path) {
if (isDirectory(real_path)) {
//load from directory
cout << "Trying load from directory \"" << real_path << "\"." << endl;
if ((ret = handleDirectory(real_path))) break;
} else if (svgFile(path)) {
//load single file
if ((ret = renderFile(real_path))) break;
} else {
//not a directory and not .svg file
cout << "Error: File \"" << path << "\" is not a proper svg file." << endl;
}
} else {
cout << "Error: Invalid file or path name: \"" << path << "\"" << endl;
}
}
}
//Terminate renderer
renderer.terminate();
return ret;
}
private:
Renderer renderer;
uint32_t bgColor = 0xffffffff;
uint32_t width = 0;
uint32_t height = 0;
char full[PATH_MAX];
private:
int help()
{
cout << "Usage:\n svg2png [SVG file] or [SVG folder] [-r resolution] [-b bgColor]\n\nFlags:\n -r set the output image resolution.\n -b set the output image background color.\n\nExamples:\n $ svg2png input.svg\n $ svg2png input.svg -r 200x200\n $ svg2png input.svg -r 200x200 -b ff00ff\n $ svg2png input1.svg input2.svg -r 200x200 -b ff00ff\n $ svg2png . -r 200x200\n\nNote:\n In the case, where the width and height in the SVG file determine the size of the image in resolution higher than 8k (7680 x 4320), limiting the resolution to this value is enforced.\n\n";
return 1;
}
bool svgFile(const char* path)
{
size_t length = strlen(path);
return length > 4 && (strcmp(&path[length - 4], ".svg") == 0);
}
const char* realFile(const char* path)
{
//real path
#ifdef _WIN32
path = _fullpath(full, path, PATH_MAX);
#else
path = realpath(path, full);
#endif
return path;
}
bool isDirectory(const char* path)
{
#ifdef _WIN32
DWORD attr = GetFileAttributes(path);
if (attr == INVALID_FILE_ATTRIBUTES)
return false;
return attr & FILE_ATTRIBUTE_DIRECTORY;
#else
struct stat buf;
if (stat(path, &buf) != 0) {
return false;
}
return S_ISDIR(buf.st_mode);
#endif
}
int renderFile(const char* path)
{
if (!path) return 1;
//destination png file
const char* dot = strrchr(path, '.');
if (!dot) return 1;
string dst(path, dot - path);
dst += ".png";
return renderer.render(path, width, height, dst, bgColor);
}
int handleDirectory(const string& path)
{
//open directory
#ifdef _WIN32
WIN32_FIND_DATA fd;
HANDLE h = FindFirstFileEx((path + "\\*").c_str(), FindExInfoBasic, &fd, FindExSearchNameMatch, NULL, 0);
if (h == INVALID_HANDLE_VALUE) {
cout << "Couldn't open directory \"" << path.c_str() << "\"." << endl;
return 1;
}
int ret = 0;
do {
if (*fd.cFileName == '.' || *fd.cFileName == '$') continue;
if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
string subpath = string(path);
subpath += '\\';
subpath += fd.cFileName;
ret = handleDirectory(subpath);
if (ret) break;
} else {
if (!svgFile(fd.cFileName)) continue;
string fullpath = string(path);
fullpath += '\\';
fullpath += fd.cFileName;
ret = renderFile(fullpath.c_str());
if (ret) break;
}
} while (FindNextFile(h, &fd));
FindClose(h);
#else
DIR* dir = opendir(path.c_str());
if (!dir) {
cout << "Couldn't open directory \"" << path.c_str() << "\"." << endl;
return 1;
}
//list directory
int ret = 0;
struct dirent* entry;
while ((entry = readdir(dir)) != NULL) {
if (*entry->d_name == '.' || *entry->d_name == '$') continue;
if (entry->d_type == DT_DIR) {
string subpath = string(path);
subpath += '/';
subpath += entry->d_name;
ret = handleDirectory(subpath);
if (ret) break;
} else {
if (!svgFile(entry->d_name)) continue;
string fullpath = string(path);
fullpath += '/';
fullpath += entry->d_name;
ret = renderFile(fullpath.c_str());
if (ret) break;
}
}
closedir(dir);
#endif
return ret;
}
};
int main(int argc, char** argv)
{
App app;
return app.setup(argc, argv);
}