ソースを参照

Added -exportsvg, RGBA & FL32 image output

Chlumsky 1 年間 前
コミット
aa9478e9ae
12 ファイル変更328 行追加13 行削除
  1. 2 2
      README.md
  2. 79 0
      core/export-svg.cpp
  3. 11 0
      core/export-svg.h
  4. 2 0
      core/save-bmp.cpp
  5. 39 0
      core/save-fl32.cpp
  6. 12 0
      core/save-fl32.h
  7. 133 0
      core/save-rgba.cpp
  8. 16 0
      core/save-rgba.h
  9. 2 0
      core/save-tiff.cpp
  10. 28 10
      main.cpp
  11. 3 0
      msdfgen.h
  12. 1 1
      vcpkg.json

+ 2 - 2
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 \<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).
  - **-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.
@@ -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.
 

+ 79 - 0
core/export-svg.cpp

@@ -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 - 0
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);
+
+}

+ 2 - 0
core/save-bmp.cpp

@@ -8,10 +8,12 @@
 #ifdef MSDFGEN_USE_CPP11
     #include <cstdint>
 #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"

+ 39 - 0
core/save-fl32.cpp

@@ -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 - 0
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 <int N>
+bool saveFl32(const BitmapConstRef<float, N> &bitmap, const char *filename);
+
+}

+ 133 - 0
core/save-rgba.cpp

@@ -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 - 0
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<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);
+
+}

+ 2 - 0
core/save-tiff.cpp

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

+ 28 - 10
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<float, N> &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<float, N> &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 <filename.txt>\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"
         "\tSets the fill rule for the scanline pass. Default is nonzero.\n"
 #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
-    "  -format <bmp / tiff / text / textfloat / bin / binfloat / binfloatbe>\n"
+    "  -format <bmp / tiff / rgba / fl32 / text / textfloat / bin / binfloat / binfloatbe>\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:

+ 3 - 0
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 {
 

+ 1 - 1
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",