Added -exportsvg, RGBA & FL32 image output

This commit is contained in:
Chlumsky 2024-05-07 21:32:45 +02:00
parent d7ac1e084d
commit aa9478e9ae
12 changed files with 328 additions and 13 deletions

View File

@ -67,7 +67,7 @@ The input can be specified as one of:
The complete list of available options can be printed with **-help**. The complete list of available options can be printed with **-help**.
Some of the important ones are: Some of the important ones are:
- **-o \<filename\>** &ndash; specifies the output file name. The desired format will be deduced from the extension - **-o \<filename\>** &ndash; specifies the output file name. The desired format will be deduced from the extension
(png, bmp, tif, txt, bin). Otherwise, use -format. (png, bmp, tiff, rgba, fl32, txt, bin). Otherwise, use -format.
- **-dimensions \<width\> \<height\>** &ndash; specifies the dimensions of the output distance field (in pixels). - **-dimensions \<width\> \<height\>** &ndash; specifies the dimensions of the output distance field (in pixels).
- **-range \<range\>**, **-pxrange \<range\>** &ndash; specifies the width of the range around the shape - **-range \<range\>**, **-pxrange \<range\>** &ndash; specifies the width of the range around the shape
between the minimum and maximum representable signed distance in shape units or distance field pixels, respectivelly. between the minimum and maximum representable signed distance in shape units or distance field pixels, respectivelly.
@ -107,7 +107,7 @@ in order to generate a distance field. Please note that all classes and function
This can be performed automatically using the `edgeColoringSimple` (or other) heuristic, or manually by setting each edge's This can be performed automatically using the `edgeColoringSimple` (or other) heuristic, or manually by setting each edge's
`color` member. Keep in mind that at least two color channels must be turned on in each edge. `color` member. Keep in mind that at least two color channels must be turned on in each edge.
- Call `generateSDF`, `generatePSDF`, `generateMSDF`, or `generateMTSDF` to generate a distance field into a floating point - Call `generateSDF`, `generatePSDF`, `generateMSDF`, or `generateMTSDF` to generate a distance field into a floating point
`Bitmap` object. This can then be worked with further or saved to a file using `saveBmp`, `savePng`, or `saveTiff`. `Bitmap` object. This can then be worked with further or saved to a file using `saveBmp`, `savePng`, `saveTiff`, etc.
- You may also render an image from the distance field using `renderSDF`. Consider calling `simulate8bit` - You may also render an image from the distance field using `renderSDF`. Consider calling `simulate8bit`
on the distance field beforehand to simulate the standard 8 bits/channel image format. on the distance field beforehand to simulate the standard 8 bits/channel image format.

79
core/export-svg.cpp Normal file
View File

@ -0,0 +1,79 @@
#include "export-svg.h"
#include <cstdio>
#include "edge-segments.h"
namespace msdfgen {
static void writeSvgCoord(FILE *f, Point2 coord) {
fprintf(f, "%.17g %.17g", coord.x, coord.y);
}
static void writeSvgPathDef(FILE *f, const Shape &shape) {
bool beginning = true;
for (const Contour &c : shape.contours) {
if (c.edges.empty())
continue;
if (beginning)
beginning = false;
else
fputc(' ', f);
fputs("M ", f);
writeSvgCoord(f, c.edges[0]->controlPoints()[0]);
for (const EdgeHolder &e : c.edges) {
const Point2 *cp = e->controlPoints();
switch (e->type()) {
case (int) LinearSegment::EDGE_TYPE:
fputs(" L ", f);
writeSvgCoord(f, cp[1]);
break;
case (int) QuadraticSegment::EDGE_TYPE:
fputs(" Q ", f);
writeSvgCoord(f, cp[1]);
fputc(' ', f);
writeSvgCoord(f, cp[2]);
break;
case (int) CubicSegment::EDGE_TYPE:
fputs(" C ", f);
writeSvgCoord(f, cp[1]);
fputc(' ', f);
writeSvgCoord(f, cp[2]);
fputc(' ', f);
writeSvgCoord(f, cp[3]);
break;
}
}
fputs(" Z", f);
}
}
bool saveSvgShape(const Shape &shape, const char *filename) {
if (FILE *f = fopen(filename, "w")) {
fputs("<svg xmlns=\"http://www.w3.org/2000/svg\"><path", f);
if (!shape.inverseYAxis)
fputs(" transform=\"scale(1 -1)\"", f);
fputs(" d=\"", f);
writeSvgPathDef(f, shape);
fputs("\"/></svg>\n", f);
fclose(f);
return true;
}
return false;
}
bool saveSvgShape(const Shape &shape, const Shape::Bounds &bounds, const char *filename) {
if (FILE *f = fopen(filename, "w")) {
fprintf(f, "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"%.17g %.17g %.17g %.17g\"><path", bounds.l, bounds.b, bounds.r-bounds.l, bounds.t-bounds.b);
if (!shape.inverseYAxis)
fprintf(f, " transform=\"translate(0 %.17g) scale(1 -1)\"", bounds.b+bounds.t);
fputs(" d=\"", f);
writeSvgPathDef(f, shape);
fputs("\"/></svg>\n", f);
fclose(f);
return true;
}
return false;
}
}

11
core/export-svg.h Normal file
View File

@ -0,0 +1,11 @@
#pragma once
#include "Shape.h"
namespace msdfgen {
bool saveSvgShape(const Shape &shape, const char *filename);
bool saveSvgShape(const Shape &shape, const Shape::Bounds &bounds, const char *filename);
}

View File

@ -8,10 +8,12 @@
#ifdef MSDFGEN_USE_CPP11 #ifdef MSDFGEN_USE_CPP11
#include <cstdint> #include <cstdint>
#else #else
namespace msdfgen {
typedef int int32_t; typedef int int32_t;
typedef unsigned uint32_t; typedef unsigned uint32_t;
typedef unsigned short uint16_t; typedef unsigned short uint16_t;
typedef unsigned char uint8_t; typedef unsigned char uint8_t;
}
#endif #endif
#include "pixel-conversion.hpp" #include "pixel-conversion.hpp"

39
core/save-fl32.cpp Normal file
View File

@ -0,0 +1,39 @@
#include "save-fl32.h"
#include <cstdio>
namespace msdfgen {
// Requires byte reversal for floats on big-endian platform
#ifndef __BIG_ENDIAN__
template <int N>
bool saveFl32(const BitmapConstRef<float, N> &bitmap, const char *filename) {
if (FILE *f = fopen(filename, "wb")) {
byte header[16] = { byte('F'), byte('L'), byte('3'), byte('2') };
header[4] = byte(bitmap.height);
header[5] = byte(bitmap.height>>8);
header[6] = byte(bitmap.height>>16);
header[7] = byte(bitmap.height>>24);
header[8] = byte(bitmap.width);
header[9] = byte(bitmap.width>>8);
header[10] = byte(bitmap.width>>16);
header[11] = byte(bitmap.width>>24);
header[12] = byte(N);
fwrite(header, 1, 16, f);
fwrite(bitmap.pixels, sizeof(float), N*bitmap.width*bitmap.height, f);
fclose(f);
return true;
}
return false;
}
template bool saveFl32(const BitmapConstRef<float, 1> &bitmap, const char *filename);
template bool saveFl32(const BitmapConstRef<float, 2> &bitmap, const char *filename);
template bool saveFl32(const BitmapConstRef<float, 3> &bitmap, const char *filename);
template bool saveFl32(const BitmapConstRef<float, 4> &bitmap, const char *filename);
#endif
}

12
core/save-fl32.h Normal file
View File

@ -0,0 +1,12 @@
#pragma once
#include "BitmapRef.hpp"
namespace msdfgen {
/// Saves the bitmap as an uncompressed floating-point FL32 file, which can be decoded trivially.
template <int N>
bool saveFl32(const BitmapConstRef<float, N> &bitmap, const char *filename);
}

133
core/save-rgba.cpp Normal file
View File

@ -0,0 +1,133 @@
#include "save-rgba.h"
#include <cstdio>
#include "pixel-conversion.hpp"
namespace msdfgen {
class RgbaFileOutput {
FILE *file;
public:
RgbaFileOutput(const char *filename, unsigned width, unsigned height) {
if ((file = fopen(filename, "wb"))) {
byte header[12] = { byte('R'), byte('G'), byte('B'), byte('A') };
header[4] = byte(width>>24);
header[5] = byte(width>>16);
header[6] = byte(width>>8);
header[7] = byte(width);
header[8] = byte(height>>24);
header[9] = byte(height>>16);
header[10] = byte(height>>8);
header[11] = byte(height);
fwrite(header, 1, 12, file);
}
}
~RgbaFileOutput() {
if (file)
fclose(file);
}
void writePixel(const byte rgba[4]) {
fwrite(rgba, 1, 4, file);
}
operator FILE *() {
return file;
}
};
bool saveRgba(const BitmapConstRef<byte, 1> &bitmap, const char *filename) {
RgbaFileOutput output(filename, bitmap.width, bitmap.height);
if (output) {
byte rgba[4] = { byte(0), byte(0), byte(0), byte(0xff) };
for (int y = bitmap.height; y--;) {
for (const byte *p = bitmap(0, y), *end = p+bitmap.width; p < end; ++p) {
rgba[0] = rgba[1] = rgba[2] = *p;
output.writePixel(rgba);
}
}
return true;
}
return false;
}
bool saveRgba(const BitmapConstRef<byte, 3> &bitmap, const char *filename) {
RgbaFileOutput output(filename, bitmap.width, bitmap.height);
if (output) {
byte rgba[4] = { byte(0), byte(0), byte(0), byte(0xff) };
for (int y = bitmap.height; y--;) {
for (const byte *p = bitmap(0, y), *end = p+3*bitmap.width; p < end; p += 3) {
rgba[0] = p[0], rgba[1] = p[1], rgba[2] = p[2];
output.writePixel(rgba);
}
}
return true;
}
return false;
}
bool saveRgba(const BitmapConstRef<byte, 4> &bitmap, const char *filename) {
RgbaFileOutput output(filename, bitmap.width, bitmap.height);
if (output) {
for (int y = bitmap.height; y--;)
fwrite(bitmap(0, y), 1, 4*bitmap.width, output);
return true;
}
return false;
}
bool saveRgba(const BitmapConstRef<float, 1> &bitmap, const char *filename) {
RgbaFileOutput output(filename, bitmap.width, bitmap.height);
if (output) {
byte rgba[4] = { byte(0), byte(0), byte(0), byte(0xff) };
for (int y = bitmap.height; y--;) {
for (const float *p = bitmap(0, y), *end = p+bitmap.width; p < end; ++p) {
rgba[0] = rgba[1] = rgba[2] = pixelFloatToByte(*p);
output.writePixel(rgba);
}
}
return true;
}
return false;
}
bool saveRgba(const BitmapConstRef<float, 3> &bitmap, const char *filename) {
RgbaFileOutput output(filename, bitmap.width, bitmap.height);
if (output) {
byte rgba[4] = { byte(0), byte(0), byte(0), byte(0xff) };
for (int y = bitmap.height; y--;) {
for (const float *p = bitmap(0, y), *end = p+3*bitmap.width; p < end; p += 3) {
rgba[0] = pixelFloatToByte(p[0]);
rgba[1] = pixelFloatToByte(p[1]);
rgba[2] = pixelFloatToByte(p[2]);
output.writePixel(rgba);
}
}
return true;
}
return false;
}
bool saveRgba(const BitmapConstRef<float, 4> &bitmap, const char *filename) {
RgbaFileOutput output(filename, bitmap.width, bitmap.height);
if (output) {
byte rgba[4];
for (int y = bitmap.height; y--;) {
for (const float *p = bitmap(0, y), *end = p+4*bitmap.width; p < end; p += 4) {
rgba[0] = pixelFloatToByte(p[0]);
rgba[1] = pixelFloatToByte(p[1]);
rgba[2] = pixelFloatToByte(p[2]);
rgba[3] = pixelFloatToByte(p[3]);
output.writePixel(rgba);
}
}
return true;
}
return false;
}
}

16
core/save-rgba.h Normal file
View File

@ -0,0 +1,16 @@
#pragma once
#include "BitmapRef.hpp"
namespace msdfgen {
/// Saves the bitmap as a simple RGBA file, which can be decoded trivially.
bool saveRgba(const BitmapConstRef<byte, 1> &bitmap, const char *filename);
bool saveRgba(const BitmapConstRef<byte, 3> &bitmap, const char *filename);
bool saveRgba(const BitmapConstRef<byte, 4> &bitmap, const char *filename);
bool saveRgba(const BitmapConstRef<float, 1> &bitmap, const char *filename);
bool saveRgba(const BitmapConstRef<float, 3> &bitmap, const char *filename);
bool saveRgba(const BitmapConstRef<float, 4> &bitmap, const char *filename);
}

View File

@ -8,10 +8,12 @@
#ifdef MSDFGEN_USE_CPP11 #ifdef MSDFGEN_USE_CPP11
#include <cstdint> #include <cstdint>
#else #else
namespace msdfgen {
typedef int int32_t; typedef int int32_t;
typedef unsigned uint32_t; typedef unsigned uint32_t;
typedef unsigned short uint16_t; typedef unsigned short uint16_t;
typedef unsigned char uint8_t; typedef unsigned char uint8_t;
}
#endif #endif
namespace msdfgen { namespace msdfgen {

View File

@ -30,7 +30,7 @@
#define DEFAULT_IMAGE_EXTENSION "png" #define DEFAULT_IMAGE_EXTENSION "png"
#define SAVE_DEFAULT_IMAGE_FORMAT savePng #define SAVE_DEFAULT_IMAGE_FORMAT savePng
#else #else
#define DEFAULT_IMAGE_EXTENSION "tif" #define DEFAULT_IMAGE_EXTENSION "tiff"
#define SAVE_DEFAULT_IMAGE_FORMAT saveTiff #define SAVE_DEFAULT_IMAGE_FORMAT saveTiff
#endif #endif
@ -41,6 +41,8 @@ enum Format {
PNG, PNG,
BMP, BMP,
TIFF, TIFF,
RGBA,
FL32,
TEXT, TEXT,
TEXT_FLOAT, TEXT_FLOAT,
BINARY, BINARY,
@ -49,7 +51,7 @@ enum Format {
}; };
static bool is8bitFormat(Format format) { static bool is8bitFormat(Format format) {
return format == PNG || format == BMP || format == TEXT || format == BINARY; return format == PNG || format == BMP || format == RGBA || format == TEXT || format == BINARY;
} }
static char toupper(char c) { static char toupper(char c) {
@ -263,7 +265,9 @@ static const char *writeOutput(const BitmapConstRef<float, N> &bitmap, const cha
return "PNG format is not available in core-only version."; return "PNG format is not available in core-only version.";
#endif #endif
else if (cmpExtension(filename, ".bmp")) format = BMP; else if (cmpExtension(filename, ".bmp")) format = BMP;
else if (cmpExtension(filename, ".tif") || cmpExtension(filename, ".tiff")) format = TIFF; else if (cmpExtension(filename, ".tiff") || cmpExtension(filename, ".tif")) format = TIFF;
else if (cmpExtension(filename, ".rgba")) format = RGBA;
else if (cmpExtension(filename, ".fl32")) format = FL32;
else if (cmpExtension(filename, ".txt")) format = TEXT; else if (cmpExtension(filename, ".txt")) format = TEXT;
else if (cmpExtension(filename, ".bin")) format = BINARY; else if (cmpExtension(filename, ".bin")) format = BINARY;
else else
@ -275,6 +279,8 @@ static const char *writeOutput(const BitmapConstRef<float, N> &bitmap, const cha
#endif #endif
case BMP: return saveBmp(bitmap, filename) ? NULL : "Failed to write output BMP image."; case BMP: return saveBmp(bitmap, filename) ? NULL : "Failed to write output BMP image.";
case TIFF: return saveTiff(bitmap, filename) ? NULL : "Failed to write output TIFF image."; case TIFF: return saveTiff(bitmap, filename) ? NULL : "Failed to write output TIFF image.";
case RGBA: return saveRgba(bitmap, filename) ? NULL : "Failed to write output RGBA image.";
case FL32: return saveFl32(bitmap, filename) ? NULL : "Failed to write output FL32 image.";
case TEXT: case TEXT_FLOAT: { case TEXT: case TEXT_FLOAT: {
FILE *file = fopen(filename, "w"); FILE *file = fopen(filename, "w");
if (!file) return "Failed to write output text file."; if (!file) return "Failed to write output text file.";
@ -416,12 +422,14 @@ static const char *const helpText =
"\tComputes and prints the distance field's estimated fill error to the standard output.\n" "\tComputes and prints the distance field's estimated fill error to the standard output.\n"
" -exportshape <filename.txt>\n" " -exportshape <filename.txt>\n"
"\tSaves the shape description into a text file that can be edited and loaded using -shapedesc.\n" "\tSaves the shape description into a text file that can be edited and loaded using -shapedesc.\n"
" -exportsvg <filename.svg>\n"
"\tSaves the shape geometry into a simple SVG file.\n"
" -fillrule <nonzero / evenodd / positive / negative>\n" " -fillrule <nonzero / evenodd / positive / negative>\n"
"\tSets the fill rule for the scanline pass. Default is nonzero.\n" "\tSets the fill rule for the scanline pass. Default is nonzero.\n"
#if defined(MSDFGEN_EXTENSIONS) && !defined(MSDFGEN_DISABLE_PNG) #if defined(MSDFGEN_EXTENSIONS) && !defined(MSDFGEN_DISABLE_PNG)
" -format <png / bmp / tiff / text / textfloat / bin / binfloat / binfloatbe>\n" " -format <png / bmp / tiff / rgba / fl32 / text / textfloat / bin / binfloat / binfloatbe>\n"
#else #else
" -format <bmp / tiff / text / textfloat / bin / binfloat / binfloatbe>\n" " -format <bmp / tiff / rgba / fl32 / text / textfloat / bin / binfloat / binfloatbe>\n"
#endif #endif
"\tSpecifies the output format of the distance field. Otherwise it is chosen based on output file extension.\n" "\tSpecifies the output format of the distance field. Otherwise it is chosen based on output file extension.\n"
" -guessorder\n" " -guessorder\n"
@ -548,6 +556,7 @@ int main(int argc, const char *const *argv) {
const char *input = NULL; const char *input = NULL;
const char *output = "output." DEFAULT_IMAGE_EXTENSION; const char *output = "output." DEFAULT_IMAGE_EXTENSION;
const char *shapeExport = NULL; const char *shapeExport = NULL;
const char *svgExport = NULL;
const char *testRender = NULL; const char *testRender = NULL;
const char *testRenderMulti = NULL; const char *testRenderMulti = NULL;
bool outputSpecified = false; bool outputSpecified = false;
@ -747,7 +756,9 @@ int main(int argc, const char *const *argv) {
fputs("PNG format is not available in core-only version.\n", stderr); fputs("PNG format is not available in core-only version.\n", stderr);
#endif #endif
else if (ARG_IS("bmp")) SET_FORMAT(BMP, "bmp"); else if (ARG_IS("bmp")) SET_FORMAT(BMP, "bmp");
else if (ARG_IS("tiff") || ARG_IS("tif")) SET_FORMAT(TIFF, "tif"); else if (ARG_IS("tiff") || ARG_IS("tif")) SET_FORMAT(TIFF, "tiff");
else if (ARG_IS("rgba")) SET_FORMAT(RGBA, "rgba");
else if (ARG_IS("fl32")) SET_FORMAT(FL32, "fl32");
else if (ARG_IS("text") || ARG_IS("txt")) SET_FORMAT(TEXT, "txt"); else if (ARG_IS("text") || ARG_IS("txt")) SET_FORMAT(TEXT, "txt");
else if (ARG_IS("textfloat") || ARG_IS("txtfloat")) SET_FORMAT(TEXT_FLOAT, "txt"); else if (ARG_IS("textfloat") || ARG_IS("txtfloat")) SET_FORMAT(TEXT_FLOAT, "txt");
else if (ARG_IS("bin") || ARG_IS("binary")) SET_FORMAT(BINARY, "bin"); else if (ARG_IS("bin") || ARG_IS("binary")) SET_FORMAT(BINARY, "bin");
@ -919,6 +930,10 @@ int main(int argc, const char *const *argv) {
shapeExport = argv[argPos++]; shapeExport = argv[argPos++];
continue; continue;
} }
ARG_CASE("-exportsvg", 1) {
svgExport = argv[argPos++];
continue;
}
ARG_CASE("-testrender", 3) { ARG_CASE("-testrender", 3) {
unsigned w, h; unsigned w, h;
testRender = argv[argPos++]; testRender = argv[argPos++];
@ -1041,7 +1056,7 @@ int main(int argc, const char *const *argv) {
getGlyphIndex(glyphIndex, guard.font, unicode); getGlyphIndex(glyphIndex, guard.font, unicode);
if (!loadGlyph(shape, guard.font, glyphIndex, fontCoordinateScaling, &glyphAdvance)) if (!loadGlyph(shape, guard.font, glyphIndex, fontCoordinateScaling, &glyphAdvance))
ABORT("Failed to load glyph from font file."); ABORT("Failed to load glyph from font file.");
if (!fontCoordinateScalingSpecified && (!autoFrame || scaleSpecified || rangeMode == RANGE_UNIT || mode == METRICS || printMetrics || shapeExport)) { if (!fontCoordinateScalingSpecified && (!autoFrame || scaleSpecified || rangeMode == RANGE_UNIT || mode == METRICS || printMetrics || shapeExport || svgExport)) {
fputs( fputs(
"Warning: Using legacy font coordinate conversion for compatibility reasons.\n" "Warning: Using legacy font coordinate conversion for compatibility reasons.\n"
" The implicit scaling behavior will likely change in a future version resulting in different output.\n" " The implicit scaling behavior will likely change in a future version resulting in different output.\n"
@ -1104,7 +1119,7 @@ int main(int argc, const char *const *argv) {
double avgScale = .5*(scale.x+scale.y); double avgScale = .5*(scale.x+scale.y);
Shape::Bounds bounds = { }; Shape::Bounds bounds = { };
if (autoFrame || mode == METRICS || printMetrics || orientation == GUESS) if (autoFrame || mode == METRICS || printMetrics || orientation == GUESS || svgExport)
bounds = shape.getBounds(); bounds = shape.getBounds();
if (outputDistanceShift) { if (outputDistanceShift) {
@ -1278,13 +1293,16 @@ int main(int argc, const char *const *argv) {
// Save output // Save output
if (shapeExport) { if (shapeExport) {
FILE *file = fopen(shapeExport, "w"); if (FILE *file = fopen(shapeExport, "w")) {
if (file) {
writeShapeDescription(file, shape); writeShapeDescription(file, shape);
fclose(file); fclose(file);
} else } else
fputs("Failed to write shape export file.\n", stderr); fputs("Failed to write shape export file.\n", stderr);
} }
if (svgExport) {
if (!saveSvgShape(shape, bounds, svgExport))
fputs("Failed to write shape SVG file.\n", stderr);
}
const char *error = NULL; const char *error = NULL;
switch (mode) { switch (mode) {
case SINGLE: case SINGLE:

View File

@ -36,7 +36,10 @@
#include "core/sdf-error-estimation.h" #include "core/sdf-error-estimation.h"
#include "core/save-bmp.h" #include "core/save-bmp.h"
#include "core/save-tiff.h" #include "core/save-tiff.h"
#include "core/save-rgba.h"
#include "core/save-fl32.h"
#include "core/shape-description.h" #include "core/shape-description.h"
#include "core/export-svg.h"
namespace msdfgen { namespace msdfgen {

View File

@ -1,7 +1,7 @@
{ {
"$schema": "https://raw.githubusercontent.com/microsoft/vcpkg-tool/master/docs/vcpkg.schema.json", "$schema": "https://raw.githubusercontent.com/microsoft/vcpkg-tool/master/docs/vcpkg.schema.json",
"name": "msdfgen", "name": "msdfgen",
"version": "1.11.0", "version": "1.12.0",
"default-features": [ "default-features": [
"extensions", "extensions",
"geometry-preprocessing", "geometry-preprocessing",