diff --git a/README.md b/README.md index 717698f..8e8cede 100644 --- a/README.md +++ b/README.md @@ -67,7 +67,7 @@ The input can be specified as one of: The complete list of available options can be printed with **-help**. Some of the important ones are: - **-o \** – 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 \ \** – specifies the dimensions of the output distance field (in pixels). - **-range \**, **-pxrange \** – 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. @@ -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 `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 - `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` on the distance field beforehand to simulate the standard 8 bits/channel image format. diff --git a/core/export-svg.cpp b/core/export-svg.cpp new file mode 100644 index 0000000..f6b9191 --- /dev/null +++ b/core/export-svg.cpp @@ -0,0 +1,79 @@ + +#include "export-svg.h" + +#include +#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("\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, "\n", f); + fclose(f); + return true; + } + return false; +} + +} diff --git a/core/export-svg.h b/core/export-svg.h new file mode 100644 index 0000000..64b4553 --- /dev/null +++ b/core/export-svg.h @@ -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); + +} diff --git a/core/save-bmp.cpp b/core/save-bmp.cpp index f761ee4..bf8e8e4 100644 --- a/core/save-bmp.cpp +++ b/core/save-bmp.cpp @@ -8,10 +8,12 @@ #ifdef MSDFGEN_USE_CPP11 #include #else +namespace msdfgen { typedef int int32_t; typedef unsigned uint32_t; typedef unsigned short uint16_t; typedef unsigned char uint8_t; +} #endif #include "pixel-conversion.hpp" diff --git a/core/save-fl32.cpp b/core/save-fl32.cpp new file mode 100644 index 0000000..bd14777 --- /dev/null +++ b/core/save-fl32.cpp @@ -0,0 +1,39 @@ + +#include "save-fl32.h" + +#include + +namespace msdfgen { + +// Requires byte reversal for floats on big-endian platform +#ifndef __BIG_ENDIAN__ + +template +bool saveFl32(const BitmapConstRef &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 &bitmap, const char *filename); +template bool saveFl32(const BitmapConstRef &bitmap, const char *filename); +template bool saveFl32(const BitmapConstRef &bitmap, const char *filename); +template bool saveFl32(const BitmapConstRef &bitmap, const char *filename); + +#endif + +} diff --git a/core/save-fl32.h b/core/save-fl32.h new file mode 100644 index 0000000..eded7f3 --- /dev/null +++ b/core/save-fl32.h @@ -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 +bool saveFl32(const BitmapConstRef &bitmap, const char *filename); + +} diff --git a/core/save-rgba.cpp b/core/save-rgba.cpp new file mode 100644 index 0000000..3af5a4f --- /dev/null +++ b/core/save-rgba.cpp @@ -0,0 +1,133 @@ + +#include "save-rgba.h" + +#include +#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 &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 &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 &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 &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 &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 &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; +} + +} diff --git a/core/save-rgba.h b/core/save-rgba.h new file mode 100644 index 0000000..50e8396 --- /dev/null +++ b/core/save-rgba.h @@ -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 &bitmap, const char *filename); +bool saveRgba(const BitmapConstRef &bitmap, const char *filename); +bool saveRgba(const BitmapConstRef &bitmap, const char *filename); +bool saveRgba(const BitmapConstRef &bitmap, const char *filename); +bool saveRgba(const BitmapConstRef &bitmap, const char *filename); +bool saveRgba(const BitmapConstRef &bitmap, const char *filename); + +} diff --git a/core/save-tiff.cpp b/core/save-tiff.cpp index 71405e0..62c379c 100644 --- a/core/save-tiff.cpp +++ b/core/save-tiff.cpp @@ -8,10 +8,12 @@ #ifdef MSDFGEN_USE_CPP11 #include #else +namespace msdfgen { typedef int int32_t; typedef unsigned uint32_t; typedef unsigned short uint16_t; typedef unsigned char uint8_t; +} #endif namespace msdfgen { diff --git a/main.cpp b/main.cpp index 3bfa62a..6dc20d0 100644 --- a/main.cpp +++ b/main.cpp @@ -30,7 +30,7 @@ #define DEFAULT_IMAGE_EXTENSION "png" #define SAVE_DEFAULT_IMAGE_FORMAT savePng #else -#define DEFAULT_IMAGE_EXTENSION "tif" +#define DEFAULT_IMAGE_EXTENSION "tiff" #define SAVE_DEFAULT_IMAGE_FORMAT saveTiff #endif @@ -41,6 +41,8 @@ enum Format { PNG, BMP, TIFF, + RGBA, + FL32, TEXT, TEXT_FLOAT, BINARY, @@ -49,7 +51,7 @@ enum 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) { @@ -263,7 +265,9 @@ static const char *writeOutput(const BitmapConstRef &bitmap, const cha return "PNG format is not available in core-only version."; #endif 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, ".bin")) format = BINARY; else @@ -275,6 +279,8 @@ static const char *writeOutput(const BitmapConstRef &bitmap, const cha #endif 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 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: { FILE *file = fopen(filename, "w"); 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" " -exportshape \n" "\tSaves the shape description into a text file that can be edited and loaded using -shapedesc.\n" + " -exportsvg \n" + "\tSaves the shape geometry into a simple SVG file.\n" " -fillrule \n" "\tSets the fill rule for the scanline pass. Default is nonzero.\n" #if defined(MSDFGEN_EXTENSIONS) && !defined(MSDFGEN_DISABLE_PNG) - " -format \n" + " -format \n" #else - " -format \n" + " -format \n" #endif "\tSpecifies the output format of the distance field. Otherwise it is chosen based on output file extension.\n" " -guessorder\n" @@ -548,6 +556,7 @@ int main(int argc, const char *const *argv) { const char *input = NULL; const char *output = "output." DEFAULT_IMAGE_EXTENSION; const char *shapeExport = NULL; + const char *svgExport = NULL; const char *testRender = NULL; const char *testRenderMulti = NULL; 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); #endif 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("textfloat") || ARG_IS("txtfloat")) SET_FORMAT(TEXT_FLOAT, "txt"); 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++]; continue; } + ARG_CASE("-exportsvg", 1) { + svgExport = argv[argPos++]; + continue; + } ARG_CASE("-testrender", 3) { unsigned w, h; testRender = argv[argPos++]; @@ -1041,7 +1056,7 @@ int main(int argc, const char *const *argv) { getGlyphIndex(glyphIndex, guard.font, unicode); if (!loadGlyph(shape, guard.font, glyphIndex, fontCoordinateScaling, &glyphAdvance)) 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( "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" @@ -1104,7 +1119,7 @@ int main(int argc, const char *const *argv) { double avgScale = .5*(scale.x+scale.y); Shape::Bounds bounds = { }; - if (autoFrame || mode == METRICS || printMetrics || orientation == GUESS) + if (autoFrame || mode == METRICS || printMetrics || orientation == GUESS || svgExport) bounds = shape.getBounds(); if (outputDistanceShift) { @@ -1278,13 +1293,16 @@ int main(int argc, const char *const *argv) { // Save output if (shapeExport) { - FILE *file = fopen(shapeExport, "w"); - if (file) { + if (FILE *file = fopen(shapeExport, "w")) { writeShapeDescription(file, shape); fclose(file); } else 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; switch (mode) { case SINGLE: diff --git a/msdfgen.h b/msdfgen.h index da9933f..ae909ef 100644 --- a/msdfgen.h +++ b/msdfgen.h @@ -36,7 +36,10 @@ #include "core/sdf-error-estimation.h" #include "core/save-bmp.h" #include "core/save-tiff.h" +#include "core/save-rgba.h" +#include "core/save-fl32.h" #include "core/shape-description.h" +#include "core/export-svg.h" namespace msdfgen { diff --git a/vcpkg.json b/vcpkg.json index ebcbaa7..bcbccb6 100644 --- a/vcpkg.json +++ b/vcpkg.json @@ -1,7 +1,7 @@ { "$schema": "https://raw.githubusercontent.com/microsoft/vcpkg-tool/master/docs/vcpkg.schema.json", "name": "msdfgen", - "version": "1.11.0", + "version": "1.12.0", "default-features": [ "extensions", "geometry-preprocessing",