From 565436bf5f814395ce49deb939815a130c8976dc Mon Sep 17 00:00:00 2001 From: Harley Acheson Date: Wed, 12 Jul 2023 22:39:23 +0200 Subject: [PATCH] UI: SVG Thumbnails Allow SVG files to have previews in the File Browser. Adds nanosvgrast.h to extern\nanosvg\, which is an SVG rasterizer that is an optional part of the nanosvg source. Pull Request: https://projects.blender.org/blender/blender/pulls/109567 --- extern/CMakeLists.txt | 1 + extern/nanosvg/CMakeLists.txt | 21 + extern/nanosvg/blender_nanosvg.c | 10 + extern/nanosvg/blender_raster.c | 6 + extern/nanosvg/nanosvgrast.h | 1458 +++++++++++++++++ source/blender/editors/space_file/filelist.cc | 10 +- source/blender/imbuf/CMakeLists.txt | 2 + source/blender/imbuf/IMB_thumbs.h | 1 + source/blender/imbuf/intern/IMB_filetype.h | 13 + source/blender/imbuf/intern/filetype.cc | 16 + source/blender/imbuf/intern/format_svg.cc | 66 + source/blender/imbuf/intern/thumbs.cc | 9 +- source/blender/io/gpencil/CMakeLists.txt | 9 +- .../gpencil/intern/gpencil_io_import_svg.cc | 4 - .../python/intern/bpy_utils_previews.c | 3 + 15 files changed, 1616 insertions(+), 13 deletions(-) create mode 100644 extern/nanosvg/CMakeLists.txt create mode 100644 extern/nanosvg/blender_nanosvg.c create mode 100644 extern/nanosvg/blender_raster.c create mode 100644 extern/nanosvg/nanosvgrast.h create mode 100644 source/blender/imbuf/intern/format_svg.cc diff --git a/extern/CMakeLists.txt b/extern/CMakeLists.txt index b31fbd82bb8..b22eae254d1 100644 --- a/extern/CMakeLists.txt +++ b/extern/CMakeLists.txt @@ -22,6 +22,7 @@ endif() add_subdirectory(rangetree) +add_subdirectory(nanosvg) add_subdirectory(wcwidth) if(WITH_BULLET) diff --git a/extern/nanosvg/CMakeLists.txt b/extern/nanosvg/CMakeLists.txt new file mode 100644 index 00000000000..98cc1d1a5cd --- /dev/null +++ b/extern/nanosvg/CMakeLists.txt @@ -0,0 +1,21 @@ +# SPDX-FileCopyrightText: 2002-2022 Blender Foundation +# +# SPDX-License-Identifier: GPL-2.0-or-later + +set(INC + PUBLIC . + ../../source/blender/blenlib +) + +set(SRC + nanosvg.h + nanosvgrast.h + blender_nanosvg.c + blender_raster.c +) + +set(LIB +) + +blender_add_lib(extern_nanosvg "${SRC}" "${INC}" "" "${LIB}") +add_library(bf::extern::nanosvg ALIAS extern_nanosvg) diff --git a/extern/nanosvg/blender_nanosvg.c b/extern/nanosvg/blender_nanosvg.c new file mode 100644 index 00000000000..96eb6c147fc --- /dev/null +++ b/extern/nanosvg/blender_nanosvg.c @@ -0,0 +1,10 @@ +/* SPDX-FileCopyrightText: 2023 Blender Foundation + * + * SPDX-License-Identifier: GPL-2.0-or-later */ + +#define NANOSVG_IMPLEMENTATION +#define NANOSVG_ALL_COLOR_KEYWORDS + +#include +#include "BLI_utildefines.h" +#include "nanosvg.h" diff --git a/extern/nanosvg/blender_raster.c b/extern/nanosvg/blender_raster.c new file mode 100644 index 00000000000..ed2c2076b1d --- /dev/null +++ b/extern/nanosvg/blender_raster.c @@ -0,0 +1,6 @@ +/* SPDX-FileCopyrightText: 2023 Blender Foundation + * + * SPDX-License-Identifier: GPL-2.0-or-later */ + +#define NANOSVGRAST_IMPLEMENTATION +#include "nanosvgrast.h" diff --git a/extern/nanosvg/nanosvgrast.h b/extern/nanosvg/nanosvgrast.h new file mode 100644 index 00000000000..17ba3b03c9b --- /dev/null +++ b/extern/nanosvg/nanosvgrast.h @@ -0,0 +1,1458 @@ +/* + * Copyright (c) 2013-14 Mikko Mononen memon@inside.org + * + * This software is provided 'as-is', without any express or implied + * warranty. In no event will the authors be held liable for any damages + * arising from the use of this software. + * + * Permission is granted to anyone to use this software for any purpose, + * including commercial applications, and to alter it and redistribute it + * freely, subject to the following restrictions: + * + * 1. The origin of this software must not be misrepresented; you must not + * claim that you wrote the original software. If you use this software + * in a product, an acknowledgment in the product documentation would be + * appreciated but is not required. + * 2. Altered source versions must be plainly marked as such, and must not be + * misrepresented as being the original software. + * 3. This notice may not be removed or altered from any source distribution. + * + * The polygon rasterization is heavily based on stb_truetype rasterizer + * by Sean Barrett - http://nothings.org/ + * + */ + +#ifndef NANOSVGRAST_H +#define NANOSVGRAST_H + +#include "nanosvg.h" + +#ifndef NANOSVGRAST_CPLUSPLUS +#ifdef __cplusplus +extern "C" { +#endif +#endif + +typedef struct NSVGrasterizer NSVGrasterizer; + +/* Example Usage: + // Load SVG + NSVGimage* image; + image = nsvgParseFromFile("test.svg", "px", 96); + + // Create rasterizer (can be used to render multiple images). + struct NSVGrasterizer* rast = nsvgCreateRasterizer(); + // Allocate memory for image + unsigned char* img = malloc(w*h*4); + // Rasterize + nsvgRasterize(rast, image, 0,0,1, img, w, h, w*4); +*/ + +// Allocated rasterizer context. +NSVGrasterizer* nsvgCreateRasterizer(void); + +// Rasterizes SVG image, returns RGBA image (non-premultiplied alpha) +// r - pointer to rasterizer context +// image - pointer to image to rasterize +// tx,ty - image offset (applied after scaling) +// scale - image scale +// dst - pointer to destination image data, 4 bytes per pixel (RGBA) +// w - width of the image to render +// h - height of the image to render +// stride - number of bytes per scaleline in the destination buffer +void nsvgRasterize(NSVGrasterizer* r, + NSVGimage* image, float tx, float ty, float scale, + unsigned char* dst, int w, int h, int stride); + +// Deletes rasterizer context. +void nsvgDeleteRasterizer(NSVGrasterizer*); + + +#ifndef NANOSVGRAST_CPLUSPLUS +#ifdef __cplusplus +} +#endif +#endif + +#ifdef NANOSVGRAST_IMPLEMENTATION + +#include +#include +#include + +#define NSVG__SUBSAMPLES 5 +#define NSVG__FIXSHIFT 10 +#define NSVG__FIX (1 << NSVG__FIXSHIFT) +#define NSVG__FIXMASK (NSVG__FIX-1) +#define NSVG__MEMPAGE_SIZE 1024 + +typedef struct NSVGedge { + float x0,y0, x1,y1; + int dir; + struct NSVGedge* next; +} NSVGedge; + +typedef struct NSVGpoint { + float x, y; + float dx, dy; + float len; + float dmx, dmy; + unsigned char flags; +} NSVGpoint; + +typedef struct NSVGactiveEdge { + int x,dx; + float ey; + int dir; + struct NSVGactiveEdge *next; +} NSVGactiveEdge; + +typedef struct NSVGmemPage { + unsigned char mem[NSVG__MEMPAGE_SIZE]; + int size; + struct NSVGmemPage* next; +} NSVGmemPage; + +typedef struct NSVGcachedPaint { + signed char type; + char spread; + float xform[6]; + unsigned int colors[256]; +} NSVGcachedPaint; + +struct NSVGrasterizer +{ + float px, py; + + float tessTol; + float distTol; + + NSVGedge* edges; + int nedges; + int cedges; + + NSVGpoint* points; + int npoints; + int cpoints; + + NSVGpoint* points2; + int npoints2; + int cpoints2; + + NSVGactiveEdge* freelist; + NSVGmemPage* pages; + NSVGmemPage* curpage; + + unsigned char* scanline; + int cscanline; + + unsigned char* bitmap; + int width, height, stride; +}; + +NSVGrasterizer* nsvgCreateRasterizer(void) +{ + NSVGrasterizer* r = (NSVGrasterizer*)malloc(sizeof(NSVGrasterizer)); + if (r == NULL) goto error; + memset(r, 0, sizeof(NSVGrasterizer)); + + r->tessTol = 0.25f; + r->distTol = 0.01f; + + return r; + +error: + nsvgDeleteRasterizer(r); + return NULL; +} + +void nsvgDeleteRasterizer(NSVGrasterizer* r) +{ + NSVGmemPage* p; + + if (r == NULL) return; + + p = r->pages; + while (p != NULL) { + NSVGmemPage* next = p->next; + free(p); + p = next; + } + + if (r->edges) free(r->edges); + if (r->points) free(r->points); + if (r->points2) free(r->points2); + if (r->scanline) free(r->scanline); + + free(r); +} + +static NSVGmemPage* nsvg__nextPage(NSVGrasterizer* r, NSVGmemPage* cur) +{ + NSVGmemPage *newp; + + // If using existing chain, return the next page in chain + if (cur != NULL && cur->next != NULL) { + return cur->next; + } + + // Alloc new page + newp = (NSVGmemPage*)malloc(sizeof(NSVGmemPage)); + if (newp == NULL) return NULL; + memset(newp, 0, sizeof(NSVGmemPage)); + + // Add to linked list + if (cur != NULL) + cur->next = newp; + else + r->pages = newp; + + return newp; +} + +static void nsvg__resetPool(NSVGrasterizer* r) +{ + NSVGmemPage* p = r->pages; + while (p != NULL) { + p->size = 0; + p = p->next; + } + r->curpage = r->pages; +} + +static unsigned char* nsvg__alloc(NSVGrasterizer* r, int size) +{ + unsigned char* buf; + if (size > NSVG__MEMPAGE_SIZE) return NULL; + if (r->curpage == NULL || r->curpage->size+size > NSVG__MEMPAGE_SIZE) { + r->curpage = nsvg__nextPage(r, r->curpage); + } + buf = &r->curpage->mem[r->curpage->size]; + r->curpage->size += size; + return buf; +} + +static int nsvg__ptEquals(float x1, float y1, float x2, float y2, float tol) +{ + float dx = x2 - x1; + float dy = y2 - y1; + return dx*dx + dy*dy < tol*tol; +} + +static void nsvg__addPathPoint(NSVGrasterizer* r, float x, float y, int flags) +{ + NSVGpoint* pt; + + if (r->npoints > 0) { + pt = &r->points[r->npoints-1]; + if (nsvg__ptEquals(pt->x,pt->y, x,y, r->distTol)) { + pt->flags = (unsigned char)(pt->flags | flags); + return; + } + } + + if (r->npoints+1 > r->cpoints) { + r->cpoints = r->cpoints > 0 ? r->cpoints * 2 : 64; + r->points = (NSVGpoint*)realloc(r->points, sizeof(NSVGpoint) * r->cpoints); + if (r->points == NULL) return; + } + + pt = &r->points[r->npoints]; + pt->x = x; + pt->y = y; + pt->flags = (unsigned char)flags; + r->npoints++; +} + +static void nsvg__appendPathPoint(NSVGrasterizer* r, NSVGpoint pt) +{ + if (r->npoints+1 > r->cpoints) { + r->cpoints = r->cpoints > 0 ? r->cpoints * 2 : 64; + r->points = (NSVGpoint*)realloc(r->points, sizeof(NSVGpoint) * r->cpoints); + if (r->points == NULL) return; + } + r->points[r->npoints] = pt; + r->npoints++; +} + +static void nsvg__duplicatePoints(NSVGrasterizer* r) +{ + if (r->npoints > r->cpoints2) { + r->cpoints2 = r->npoints; + r->points2 = (NSVGpoint*)realloc(r->points2, sizeof(NSVGpoint) * r->cpoints2); + if (r->points2 == NULL) return; + } + + memcpy(r->points2, r->points, sizeof(NSVGpoint) * r->npoints); + r->npoints2 = r->npoints; +} + +static void nsvg__addEdge(NSVGrasterizer* r, float x0, float y0, float x1, float y1) +{ + NSVGedge* e; + + // Skip horizontal edges + if (y0 == y1) + return; + + if (r->nedges+1 > r->cedges) { + r->cedges = r->cedges > 0 ? r->cedges * 2 : 64; + r->edges = (NSVGedge*)realloc(r->edges, sizeof(NSVGedge) * r->cedges); + if (r->edges == NULL) return; + } + + e = &r->edges[r->nedges]; + r->nedges++; + + if (y0 < y1) { + e->x0 = x0; + e->y0 = y0; + e->x1 = x1; + e->y1 = y1; + e->dir = 1; + } else { + e->x0 = x1; + e->y0 = y1; + e->x1 = x0; + e->y1 = y0; + e->dir = -1; + } +} + +static float nsvg__normalize(float *x, float* y) +{ + float d = sqrtf((*x)*(*x) + (*y)*(*y)); + if (d > 1e-6f) { + float id = 1.0f / d; + *x *= id; + *y *= id; + } + return d; +} + +static float nsvg__absf(float x) { return x < 0 ? -x : x; } + +static void nsvg__flattenCubicBez(NSVGrasterizer* r, + float x1, float y1, float x2, float y2, + float x3, float y3, float x4, float y4, + int level, int type) +{ + float x12,y12,x23,y23,x34,y34,x123,y123,x234,y234,x1234,y1234; + float dx,dy,d2,d3; + + if (level > 10) return; + + x12 = (x1+x2)*0.5f; + y12 = (y1+y2)*0.5f; + x23 = (x2+x3)*0.5f; + y23 = (y2+y3)*0.5f; + x34 = (x3+x4)*0.5f; + y34 = (y3+y4)*0.5f; + x123 = (x12+x23)*0.5f; + y123 = (y12+y23)*0.5f; + + dx = x4 - x1; + dy = y4 - y1; + d2 = nsvg__absf(((x2 - x4) * dy - (y2 - y4) * dx)); + d3 = nsvg__absf(((x3 - x4) * dy - (y3 - y4) * dx)); + + if ((d2 + d3)*(d2 + d3) < r->tessTol * (dx*dx + dy*dy)) { + nsvg__addPathPoint(r, x4, y4, type); + return; + } + + x234 = (x23+x34)*0.5f; + y234 = (y23+y34)*0.5f; + x1234 = (x123+x234)*0.5f; + y1234 = (y123+y234)*0.5f; + + nsvg__flattenCubicBez(r, x1,y1, x12,y12, x123,y123, x1234,y1234, level+1, 0); + nsvg__flattenCubicBez(r, x1234,y1234, x234,y234, x34,y34, x4,y4, level+1, type); +} + +static void nsvg__flattenShape(NSVGrasterizer* r, NSVGshape* shape, float scale) +{ + int i, j; + NSVGpath* path; + + for (path = shape->paths; path != NULL; path = path->next) { + r->npoints = 0; + // Flatten path + nsvg__addPathPoint(r, path->pts[0]*scale, path->pts[1]*scale, 0); + for (i = 0; i < path->npts-1; i += 3) { + float* p = &path->pts[i*2]; + nsvg__flattenCubicBez(r, p[0]*scale,p[1]*scale, p[2]*scale,p[3]*scale, p[4]*scale,p[5]*scale, p[6]*scale,p[7]*scale, 0, 0); + } + // Close path + nsvg__addPathPoint(r, path->pts[0]*scale, path->pts[1]*scale, 0); + // Build edges + for (i = 0, j = r->npoints-1; i < r->npoints; j = i++) + nsvg__addEdge(r, r->points[j].x, r->points[j].y, r->points[i].x, r->points[i].y); + } +} + +enum NSVGpointFlags +{ + NSVG_PT_CORNER = 0x01, + NSVG_PT_BEVEL = 0x02, + NSVG_PT_LEFT = 0x04 +}; + +static void nsvg__initClosed(NSVGpoint* left, NSVGpoint* right, NSVGpoint* p0, NSVGpoint* p1, float lineWidth) +{ + float w = lineWidth * 0.5f; + float dx = p1->x - p0->x; + float dy = p1->y - p0->y; + float len = nsvg__normalize(&dx, &dy); + float px = p0->x + dx*len*0.5f, py = p0->y + dy*len*0.5f; + float dlx = dy, dly = -dx; + float lx = px - dlx*w, ly = py - dly*w; + float rx = px + dlx*w, ry = py + dly*w; + left->x = lx; left->y = ly; + right->x = rx; right->y = ry; +} + +static void nsvg__buttCap(NSVGrasterizer* r, NSVGpoint* left, NSVGpoint* right, NSVGpoint* p, float dx, float dy, float lineWidth, int connect) +{ + float w = lineWidth * 0.5f; + float px = p->x, py = p->y; + float dlx = dy, dly = -dx; + float lx = px - dlx*w, ly = py - dly*w; + float rx = px + dlx*w, ry = py + dly*w; + + nsvg__addEdge(r, lx, ly, rx, ry); + + if (connect) { + nsvg__addEdge(r, left->x, left->y, lx, ly); + nsvg__addEdge(r, rx, ry, right->x, right->y); + } + left->x = lx; left->y = ly; + right->x = rx; right->y = ry; +} + +static void nsvg__squareCap(NSVGrasterizer* r, NSVGpoint* left, NSVGpoint* right, NSVGpoint* p, float dx, float dy, float lineWidth, int connect) +{ + float w = lineWidth * 0.5f; + float px = p->x - dx*w, py = p->y - dy*w; + float dlx = dy, dly = -dx; + float lx = px - dlx*w, ly = py - dly*w; + float rx = px + dlx*w, ry = py + dly*w; + + nsvg__addEdge(r, lx, ly, rx, ry); + + if (connect) { + nsvg__addEdge(r, left->x, left->y, lx, ly); + nsvg__addEdge(r, rx, ry, right->x, right->y); + } + left->x = lx; left->y = ly; + right->x = rx; right->y = ry; +} + +#ifndef NSVG_PI +#define NSVG_PI (3.14159265358979323846264338327f) +#endif + +static void nsvg__roundCap(NSVGrasterizer* r, NSVGpoint* left, NSVGpoint* right, NSVGpoint* p, float dx, float dy, float lineWidth, int ncap, int connect) +{ + int i; + float w = lineWidth * 0.5f; + float px = p->x, py = p->y; + float dlx = dy, dly = -dx; + float lx = 0, ly = 0, rx = 0, ry = 0, prevx = 0, prevy = 0; + + for (i = 0; i < ncap; i++) { + float a = (float)i/(float)(ncap-1)*NSVG_PI; + float ax = cosf(a) * w, ay = sinf(a) * w; + float x = px - dlx*ax - dx*ay; + float y = py - dly*ax - dy*ay; + + if (i > 0) + nsvg__addEdge(r, prevx, prevy, x, y); + + prevx = x; + prevy = y; + + if (i == 0) { + lx = x; ly = y; + } else if (i == ncap-1) { + rx = x; ry = y; + } + } + + if (connect) { + nsvg__addEdge(r, left->x, left->y, lx, ly); + nsvg__addEdge(r, rx, ry, right->x, right->y); + } + + left->x = lx; left->y = ly; + right->x = rx; right->y = ry; +} + +static void nsvg__bevelJoin(NSVGrasterizer* r, NSVGpoint* left, NSVGpoint* right, NSVGpoint* p0, NSVGpoint* p1, float lineWidth) +{ + float w = lineWidth * 0.5f; + float dlx0 = p0->dy, dly0 = -p0->dx; + float dlx1 = p1->dy, dly1 = -p1->dx; + float lx0 = p1->x - (dlx0 * w), ly0 = p1->y - (dly0 * w); + float rx0 = p1->x + (dlx0 * w), ry0 = p1->y + (dly0 * w); + float lx1 = p1->x - (dlx1 * w), ly1 = p1->y - (dly1 * w); + float rx1 = p1->x + (dlx1 * w), ry1 = p1->y + (dly1 * w); + + nsvg__addEdge(r, lx0, ly0, left->x, left->y); + nsvg__addEdge(r, lx1, ly1, lx0, ly0); + + nsvg__addEdge(r, right->x, right->y, rx0, ry0); + nsvg__addEdge(r, rx0, ry0, rx1, ry1); + + left->x = lx1; left->y = ly1; + right->x = rx1; right->y = ry1; +} + +static void nsvg__miterJoin(NSVGrasterizer* r, NSVGpoint* left, NSVGpoint* right, NSVGpoint* p0, NSVGpoint* p1, float lineWidth) +{ + float w = lineWidth * 0.5f; + float dlx0 = p0->dy, dly0 = -p0->dx; + float dlx1 = p1->dy, dly1 = -p1->dx; + float lx0, rx0, lx1, rx1; + float ly0, ry0, ly1, ry1; + + if (p1->flags & NSVG_PT_LEFT) { + lx0 = lx1 = p1->x - p1->dmx * w; + ly0 = ly1 = p1->y - p1->dmy * w; + nsvg__addEdge(r, lx1, ly1, left->x, left->y); + + rx0 = p1->x + (dlx0 * w); + ry0 = p1->y + (dly0 * w); + rx1 = p1->x + (dlx1 * w); + ry1 = p1->y + (dly1 * w); + nsvg__addEdge(r, right->x, right->y, rx0, ry0); + nsvg__addEdge(r, rx0, ry0, rx1, ry1); + } else { + lx0 = p1->x - (dlx0 * w); + ly0 = p1->y - (dly0 * w); + lx1 = p1->x - (dlx1 * w); + ly1 = p1->y - (dly1 * w); + nsvg__addEdge(r, lx0, ly0, left->x, left->y); + nsvg__addEdge(r, lx1, ly1, lx0, ly0); + + rx0 = rx1 = p1->x + p1->dmx * w; + ry0 = ry1 = p1->y + p1->dmy * w; + nsvg__addEdge(r, right->x, right->y, rx1, ry1); + } + + left->x = lx1; left->y = ly1; + right->x = rx1; right->y = ry1; +} + +static void nsvg__roundJoin(NSVGrasterizer* r, NSVGpoint* left, NSVGpoint* right, NSVGpoint* p0, NSVGpoint* p1, float lineWidth, int ncap) +{ + int i, n; + float w = lineWidth * 0.5f; + float dlx0 = p0->dy, dly0 = -p0->dx; + float dlx1 = p1->dy, dly1 = -p1->dx; + float a0 = atan2f(dly0, dlx0); + float a1 = atan2f(dly1, dlx1); + float da = a1 - a0; + float lx, ly, rx, ry; + + if (da < NSVG_PI) da += NSVG_PI*2; + if (da > NSVG_PI) da -= NSVG_PI*2; + + n = (int)ceilf((nsvg__absf(da) / NSVG_PI) * (float)ncap); + if (n < 2) n = 2; + if (n > ncap) n = ncap; + + lx = left->x; + ly = left->y; + rx = right->x; + ry = right->y; + + for (i = 0; i < n; i++) { + float u = (float)i/(float)(n-1); + float a = a0 + u*da; + float ax = cosf(a) * w, ay = sinf(a) * w; + float lx1 = p1->x - ax, ly1 = p1->y - ay; + float rx1 = p1->x + ax, ry1 = p1->y + ay; + + nsvg__addEdge(r, lx1, ly1, lx, ly); + nsvg__addEdge(r, rx, ry, rx1, ry1); + + lx = lx1; ly = ly1; + rx = rx1; ry = ry1; + } + + left->x = lx; left->y = ly; + right->x = rx; right->y = ry; +} + +static void nsvg__straightJoin(NSVGrasterizer* r, NSVGpoint* left, NSVGpoint* right, NSVGpoint* p1, float lineWidth) +{ + float w = lineWidth * 0.5f; + float lx = p1->x - (p1->dmx * w), ly = p1->y - (p1->dmy * w); + float rx = p1->x + (p1->dmx * w), ry = p1->y + (p1->dmy * w); + + nsvg__addEdge(r, lx, ly, left->x, left->y); + nsvg__addEdge(r, right->x, right->y, rx, ry); + + left->x = lx; left->y = ly; + right->x = rx; right->y = ry; +} + +static int nsvg__curveDivs(float r, float arc, float tol) +{ + float da = acosf(r / (r + tol)) * 2.0f; + int divs = (int)ceilf(arc / da); + if (divs < 2) divs = 2; + return divs; +} + +static void nsvg__expandStroke(NSVGrasterizer* r, NSVGpoint* points, int npoints, int closed, int lineJoin, int lineCap, float lineWidth) +{ + int ncap = nsvg__curveDivs(lineWidth*0.5f, NSVG_PI, r->tessTol); // Calculate divisions per half circle. + NSVGpoint left = {0,0,0,0,0,0,0,0}, right = {0,0,0,0,0,0,0,0}, firstLeft = {0,0,0,0,0,0,0,0}, firstRight = {0,0,0,0,0,0,0,0}; + NSVGpoint* p0, *p1; + int j, s, e; + + // Build stroke edges + if (closed) { + // Looping + p0 = &points[npoints-1]; + p1 = &points[0]; + s = 0; + e = npoints; + } else { + // Add cap + p0 = &points[0]; + p1 = &points[1]; + s = 1; + e = npoints-1; + } + + if (closed) { + nsvg__initClosed(&left, &right, p0, p1, lineWidth); + firstLeft = left; + firstRight = right; + } else { + // Add cap + float dx = p1->x - p0->x; + float dy = p1->y - p0->y; + nsvg__normalize(&dx, &dy); + if (lineCap == NSVG_CAP_BUTT) + nsvg__buttCap(r, &left, &right, p0, dx, dy, lineWidth, 0); + else if (lineCap == NSVG_CAP_SQUARE) + nsvg__squareCap(r, &left, &right, p0, dx, dy, lineWidth, 0); + else if (lineCap == NSVG_CAP_ROUND) + nsvg__roundCap(r, &left, &right, p0, dx, dy, lineWidth, ncap, 0); + } + + for (j = s; j < e; ++j) { + if (p1->flags & NSVG_PT_CORNER) { + if (lineJoin == NSVG_JOIN_ROUND) + nsvg__roundJoin(r, &left, &right, p0, p1, lineWidth, ncap); + else if (lineJoin == NSVG_JOIN_BEVEL || (p1->flags & NSVG_PT_BEVEL)) + nsvg__bevelJoin(r, &left, &right, p0, p1, lineWidth); + else + nsvg__miterJoin(r, &left, &right, p0, p1, lineWidth); + } else { + nsvg__straightJoin(r, &left, &right, p1, lineWidth); + } + p0 = p1++; + } + + if (closed) { + // Loop it + nsvg__addEdge(r, firstLeft.x, firstLeft.y, left.x, left.y); + nsvg__addEdge(r, right.x, right.y, firstRight.x, firstRight.y); + } else { + // Add cap + float dx = p1->x - p0->x; + float dy = p1->y - p0->y; + nsvg__normalize(&dx, &dy); + if (lineCap == NSVG_CAP_BUTT) + nsvg__buttCap(r, &right, &left, p1, -dx, -dy, lineWidth, 1); + else if (lineCap == NSVG_CAP_SQUARE) + nsvg__squareCap(r, &right, &left, p1, -dx, -dy, lineWidth, 1); + else if (lineCap == NSVG_CAP_ROUND) + nsvg__roundCap(r, &right, &left, p1, -dx, -dy, lineWidth, ncap, 1); + } +} + +static void nsvg__prepareStroke(NSVGrasterizer* r, float miterLimit, int lineJoin) +{ + int i, j; + NSVGpoint* p0, *p1; + + p0 = &r->points[r->npoints-1]; + p1 = &r->points[0]; + for (i = 0; i < r->npoints; i++) { + // Calculate segment direction and length + p0->dx = p1->x - p0->x; + p0->dy = p1->y - p0->y; + p0->len = nsvg__normalize(&p0->dx, &p0->dy); + // Advance + p0 = p1++; + } + + // calculate joins + p0 = &r->points[r->npoints-1]; + p1 = &r->points[0]; + for (j = 0; j < r->npoints; j++) { + float dlx0, dly0, dlx1, dly1, dmr2, cross; + dlx0 = p0->dy; + dly0 = -p0->dx; + dlx1 = p1->dy; + dly1 = -p1->dx; + // Calculate extrusions + p1->dmx = (dlx0 + dlx1) * 0.5f; + p1->dmy = (dly0 + dly1) * 0.5f; + dmr2 = p1->dmx*p1->dmx + p1->dmy*p1->dmy; + if (dmr2 > 0.000001f) { + float s2 = 1.0f / dmr2; + if (s2 > 600.0f) { + s2 = 600.0f; + } + p1->dmx *= s2; + p1->dmy *= s2; + } + + // Clear flags, but keep the corner. + p1->flags = (p1->flags & NSVG_PT_CORNER) ? NSVG_PT_CORNER : 0; + + // Keep track of left turns. + cross = p1->dx * p0->dy - p0->dx * p1->dy; + if (cross > 0.0f) + p1->flags |= NSVG_PT_LEFT; + + // Check to see if the corner needs to be beveled. + if (p1->flags & NSVG_PT_CORNER) { + if ((dmr2 * miterLimit*miterLimit) < 1.0f || lineJoin == NSVG_JOIN_BEVEL || lineJoin == NSVG_JOIN_ROUND) { + p1->flags |= NSVG_PT_BEVEL; + } + } + + p0 = p1++; + } +} + +static void nsvg__flattenShapeStroke(NSVGrasterizer* r, NSVGshape* shape, float scale) +{ + int i, j, closed; + NSVGpath* path; + NSVGpoint* p0, *p1; + float miterLimit = shape->miterLimit; + int lineJoin = shape->strokeLineJoin; + int lineCap = shape->strokeLineCap; + float lineWidth = shape->strokeWidth * scale; + + for (path = shape->paths; path != NULL; path = path->next) { + // Flatten path + r->npoints = 0; + nsvg__addPathPoint(r, path->pts[0]*scale, path->pts[1]*scale, NSVG_PT_CORNER); + for (i = 0; i < path->npts-1; i += 3) { + float* p = &path->pts[i*2]; + nsvg__flattenCubicBez(r, p[0]*scale,p[1]*scale, p[2]*scale,p[3]*scale, p[4]*scale,p[5]*scale, p[6]*scale,p[7]*scale, 0, NSVG_PT_CORNER); + } + if (r->npoints < 2) + continue; + + closed = path->closed; + + // If the first and last points are the same, remove the last, mark as closed path. + p0 = &r->points[r->npoints-1]; + p1 = &r->points[0]; + if (nsvg__ptEquals(p0->x,p0->y, p1->x,p1->y, r->distTol)) { + r->npoints--; + p0 = &r->points[r->npoints-1]; + closed = 1; + } + + if (shape->strokeDashCount > 0) { + int idash = 0, dashState = 1; + float totalDist = 0, dashLen, allDashLen, dashOffset; + NSVGpoint cur; + + if (closed) + nsvg__appendPathPoint(r, r->points[0]); + + // Duplicate points -> points2. + nsvg__duplicatePoints(r); + + r->npoints = 0; + cur = r->points2[0]; + nsvg__appendPathPoint(r, cur); + + // Figure out dash offset. + allDashLen = 0; + for (j = 0; j < shape->strokeDashCount; j++) + allDashLen += shape->strokeDashArray[j]; + if (shape->strokeDashCount & 1) + allDashLen *= 2.0f; + // Find location inside pattern + dashOffset = fmodf(shape->strokeDashOffset, allDashLen); + if (dashOffset < 0.0f) + dashOffset += allDashLen; + + while (dashOffset > shape->strokeDashArray[idash]) { + dashOffset -= shape->strokeDashArray[idash]; + idash = (idash + 1) % shape->strokeDashCount; + } + dashLen = (shape->strokeDashArray[idash] - dashOffset) * scale; + + for (j = 1; j < r->npoints2; ) { + float dx = r->points2[j].x - cur.x; + float dy = r->points2[j].y - cur.y; + float dist = sqrtf(dx*dx + dy*dy); + + if ((totalDist + dist) > dashLen) { + // Calculate intermediate point + float d = (dashLen - totalDist) / dist; + float x = cur.x + dx * d; + float y = cur.y + dy * d; + nsvg__addPathPoint(r, x, y, NSVG_PT_CORNER); + + // Stroke + if (r->npoints > 1 && dashState) { + nsvg__prepareStroke(r, miterLimit, lineJoin); + nsvg__expandStroke(r, r->points, r->npoints, 0, lineJoin, lineCap, lineWidth); + } + // Advance dash pattern + dashState = !dashState; + idash = (idash+1) % shape->strokeDashCount; + dashLen = shape->strokeDashArray[idash] * scale; + // Restart + cur.x = x; + cur.y = y; + cur.flags = NSVG_PT_CORNER; + totalDist = 0.0f; + r->npoints = 0; + nsvg__appendPathPoint(r, cur); + } else { + totalDist += dist; + cur = r->points2[j]; + nsvg__appendPathPoint(r, cur); + j++; + } + } + // Stroke any leftover path + if (r->npoints > 1 && dashState) + nsvg__expandStroke(r, r->points, r->npoints, 0, lineJoin, lineCap, lineWidth); + } else { + nsvg__prepareStroke(r, miterLimit, lineJoin); + nsvg__expandStroke(r, r->points, r->npoints, closed, lineJoin, lineCap, lineWidth); + } + } +} + +static int nsvg__cmpEdge(const void *p, const void *q) +{ + const NSVGedge* a = (const NSVGedge*)p; + const NSVGedge* b = (const NSVGedge*)q; + + if (a->y0 < b->y0) return -1; + if (a->y0 > b->y0) return 1; + return 0; +} + + +static NSVGactiveEdge* nsvg__addActive(NSVGrasterizer* r, NSVGedge* e, float startPoint) +{ + NSVGactiveEdge* z; + + if (r->freelist != NULL) { + // Restore from freelist. + z = r->freelist; + r->freelist = z->next; + } else { + // Alloc new edge. + z = (NSVGactiveEdge*)nsvg__alloc(r, sizeof(NSVGactiveEdge)); + if (z == NULL) return NULL; + } + + float dxdy = (e->x1 - e->x0) / (e->y1 - e->y0); +// STBTT_assert(e->y0 <= start_point); + // round dx down to avoid going too far + if (dxdy < 0) + z->dx = (int)(-floorf(NSVG__FIX * -dxdy)); + else + z->dx = (int)floorf(NSVG__FIX * dxdy); + z->x = (int)floorf(NSVG__FIX * (e->x0 + dxdy * (startPoint - e->y0))); +// z->x -= off_x * FIX; + z->ey = e->y1; + z->next = 0; + z->dir = e->dir; + + return z; +} + +static void nsvg__freeActive(NSVGrasterizer* r, NSVGactiveEdge* z) +{ + z->next = r->freelist; + r->freelist = z; +} + +static void nsvg__fillScanline(unsigned char* scanline, int len, int x0, int x1, int maxWeight, int* xmin, int* xmax) +{ + int i = x0 >> NSVG__FIXSHIFT; + int j = x1 >> NSVG__FIXSHIFT; + if (i < *xmin) *xmin = i; + if (j > *xmax) *xmax = j; + if (i < len && j >= 0) { + if (i == j) { + // x0,x1 are the same pixel, so compute combined coverage + scanline[i] = (unsigned char)(scanline[i] + ((x1 - x0) * maxWeight >> NSVG__FIXSHIFT)); + } else { + if (i >= 0) // add antialiasing for x0 + scanline[i] = (unsigned char)(scanline[i] + (((NSVG__FIX - (x0 & NSVG__FIXMASK)) * maxWeight) >> NSVG__FIXSHIFT)); + else + i = -1; // clip + + if (j < len) // add antialiasing for x1 + scanline[j] = (unsigned char)(scanline[j] + (((x1 & NSVG__FIXMASK) * maxWeight) >> NSVG__FIXSHIFT)); + else + j = len; // clip + + for (++i; i < j; ++i) // fill pixels between x0 and x1 + scanline[i] = (unsigned char)(scanline[i] + maxWeight); + } + } +} + +// note: this routine clips fills that extend off the edges... ideally this +// wouldn't happen, but it could happen if the truetype glyph bounding boxes +// are wrong, or if the user supplies a too-small bitmap +static void nsvg__fillActiveEdges(unsigned char* scanline, int len, NSVGactiveEdge* e, int maxWeight, int* xmin, int* xmax, char fillRule) +{ + // non-zero winding fill + int x0 = 0, w = 0; + + if (fillRule == NSVG_FILLRULE_NONZERO) { + // Non-zero + while (e != NULL) { + if (w == 0) { + // if we're currently at zero, we need to record the edge start point + x0 = e->x; w += e->dir; + } else { + int x1 = e->x; w += e->dir; + // if we went to zero, we need to draw + if (w == 0) + nsvg__fillScanline(scanline, len, x0, x1, maxWeight, xmin, xmax); + } + e = e->next; + } + } else if (fillRule == NSVG_FILLRULE_EVENODD) { + // Even-odd + while (e != NULL) { + if (w == 0) { + // if we're currently at zero, we need to record the edge start point + x0 = e->x; w = 1; + } else { + int x1 = e->x; w = 0; + nsvg__fillScanline(scanline, len, x0, x1, maxWeight, xmin, xmax); + } + e = e->next; + } + } +} + +static float nsvg__clampf(float a, float mn, float mx) { return a < mn ? mn : (a > mx ? mx : a); } + +static unsigned int nsvg__RGBA(unsigned char r, unsigned char g, unsigned char b, unsigned char a) +{ + return ((unsigned int)r) | ((unsigned int)g << 8) | ((unsigned int)b << 16) | ((unsigned int)a << 24); +} + +static unsigned int nsvg__lerpRGBA(unsigned int c0, unsigned int c1, float u) +{ + int iu = (int)(nsvg__clampf(u, 0.0f, 1.0f) * 256.0f); + int r = (((c0) & 0xff)*(256-iu) + (((c1) & 0xff)*iu)) >> 8; + int g = (((c0>>8) & 0xff)*(256-iu) + (((c1>>8) & 0xff)*iu)) >> 8; + int b = (((c0>>16) & 0xff)*(256-iu) + (((c1>>16) & 0xff)*iu)) >> 8; + int a = (((c0>>24) & 0xff)*(256-iu) + (((c1>>24) & 0xff)*iu)) >> 8; + return nsvg__RGBA((unsigned char)r, (unsigned char)g, (unsigned char)b, (unsigned char)a); +} + +static unsigned int nsvg__applyOpacity(unsigned int c, float u) +{ + int iu = (int)(nsvg__clampf(u, 0.0f, 1.0f) * 256.0f); + int r = (c) & 0xff; + int g = (c>>8) & 0xff; + int b = (c>>16) & 0xff; + int a = (((c>>24) & 0xff)*iu) >> 8; + return nsvg__RGBA((unsigned char)r, (unsigned char)g, (unsigned char)b, (unsigned char)a); +} + +static inline int nsvg__div255(int x) +{ + return ((x+1) * 257) >> 16; +} + +static void nsvg__scanlineSolid(unsigned char* dst, int count, unsigned char* cover, int x, int y, + float tx, float ty, float scale, NSVGcachedPaint* cache) +{ + + if (cache->type == NSVG_PAINT_COLOR) { + int i, cr, cg, cb, ca; + cr = cache->colors[0] & 0xff; + cg = (cache->colors[0] >> 8) & 0xff; + cb = (cache->colors[0] >> 16) & 0xff; + ca = (cache->colors[0] >> 24) & 0xff; + + for (i = 0; i < count; i++) { + int r,g,b; + int a = nsvg__div255((int)cover[0] * ca); + int ia = 255 - a; + // Premultiply + r = nsvg__div255(cr * a); + g = nsvg__div255(cg * a); + b = nsvg__div255(cb * a); + + // Blend over + r += nsvg__div255(ia * (int)dst[0]); + g += nsvg__div255(ia * (int)dst[1]); + b += nsvg__div255(ia * (int)dst[2]); + a += nsvg__div255(ia * (int)dst[3]); + + dst[0] = (unsigned char)r; + dst[1] = (unsigned char)g; + dst[2] = (unsigned char)b; + dst[3] = (unsigned char)a; + + cover++; + dst += 4; + } + } else if (cache->type == NSVG_PAINT_LINEAR_GRADIENT) { + // TODO: spread modes. + // TODO: plenty of opportunities to optimize. + float fx, fy, dx, gy; + float* t = cache->xform; + int i, cr, cg, cb, ca; + unsigned int c; + + fx = ((float)x - tx) / scale; + fy = ((float)y - ty) / scale; + dx = 1.0f / scale; + + for (i = 0; i < count; i++) { + int r,g,b,a,ia; + gy = fx*t[1] + fy*t[3] + t[5]; + c = cache->colors[(int)nsvg__clampf(gy*255.0f, 0, 255.0f)]; + cr = (c) & 0xff; + cg = (c >> 8) & 0xff; + cb = (c >> 16) & 0xff; + ca = (c >> 24) & 0xff; + + a = nsvg__div255((int)cover[0] * ca); + ia = 255 - a; + + // Premultiply + r = nsvg__div255(cr * a); + g = nsvg__div255(cg * a); + b = nsvg__div255(cb * a); + + // Blend over + r += nsvg__div255(ia * (int)dst[0]); + g += nsvg__div255(ia * (int)dst[1]); + b += nsvg__div255(ia * (int)dst[2]); + a += nsvg__div255(ia * (int)dst[3]); + + dst[0] = (unsigned char)r; + dst[1] = (unsigned char)g; + dst[2] = (unsigned char)b; + dst[3] = (unsigned char)a; + + cover++; + dst += 4; + fx += dx; + } + } else if (cache->type == NSVG_PAINT_RADIAL_GRADIENT) { + // TODO: spread modes. + // TODO: plenty of opportunities to optimize. + // TODO: focus (fx,fy) + float fx, fy, dx, gx, gy, gd; + float* t = cache->xform; + int i, cr, cg, cb, ca; + unsigned int c; + + fx = ((float)x - tx) / scale; + fy = ((float)y - ty) / scale; + dx = 1.0f / scale; + + for (i = 0; i < count; i++) { + int r,g,b,a,ia; + gx = fx*t[0] + fy*t[2] + t[4]; + gy = fx*t[1] + fy*t[3] + t[5]; + gd = sqrtf(gx*gx + gy*gy); + c = cache->colors[(int)nsvg__clampf(gd*255.0f, 0, 255.0f)]; + cr = (c) & 0xff; + cg = (c >> 8) & 0xff; + cb = (c >> 16) & 0xff; + ca = (c >> 24) & 0xff; + + a = nsvg__div255((int)cover[0] * ca); + ia = 255 - a; + + // Premultiply + r = nsvg__div255(cr * a); + g = nsvg__div255(cg * a); + b = nsvg__div255(cb * a); + + // Blend over + r += nsvg__div255(ia * (int)dst[0]); + g += nsvg__div255(ia * (int)dst[1]); + b += nsvg__div255(ia * (int)dst[2]); + a += nsvg__div255(ia * (int)dst[3]); + + dst[0] = (unsigned char)r; + dst[1] = (unsigned char)g; + dst[2] = (unsigned char)b; + dst[3] = (unsigned char)a; + + cover++; + dst += 4; + fx += dx; + } + } +} + +static void nsvg__rasterizeSortedEdges(NSVGrasterizer *r, float tx, float ty, float scale, NSVGcachedPaint* cache, char fillRule) +{ + NSVGactiveEdge *active = NULL; + int y, s; + int e = 0; + int maxWeight = (255 / NSVG__SUBSAMPLES); // weight per vertical scanline + int xmin, xmax; + + for (y = 0; y < r->height; y++) { + memset(r->scanline, 0, r->width); + xmin = r->width; + xmax = 0; + for (s = 0; s < NSVG__SUBSAMPLES; ++s) { + // find center of pixel for this scanline + float scany = (float)(y*NSVG__SUBSAMPLES + s) + 0.5f; + NSVGactiveEdge **step = &active; + + // update all active edges; + // remove all active edges that terminate before the center of this scanline + while (*step) { + NSVGactiveEdge *z = *step; + if (z->ey <= scany) { + *step = z->next; // delete from list +// NSVG__assert(z->valid); + nsvg__freeActive(r, z); + } else { + z->x += z->dx; // advance to position for current scanline + step = &((*step)->next); // advance through list + } + } + + // resort the list if needed + for (;;) { + int changed = 0; + step = &active; + while (*step && (*step)->next) { + if ((*step)->x > (*step)->next->x) { + NSVGactiveEdge* t = *step; + NSVGactiveEdge* q = t->next; + t->next = q->next; + q->next = t; + *step = q; + changed = 1; + } + step = &(*step)->next; + } + if (!changed) break; + } + + // insert all edges that start before the center of this scanline -- omit ones that also end on this scanline + while (e < r->nedges && r->edges[e].y0 <= scany) { + if (r->edges[e].y1 > scany) { + NSVGactiveEdge* z = nsvg__addActive(r, &r->edges[e], scany); + if (z == NULL) break; + // find insertion point + if (active == NULL) { + active = z; + } else if (z->x < active->x) { + // insert at front + z->next = active; + active = z; + } else { + // find thing to insert AFTER + NSVGactiveEdge* p = active; + while (p->next && p->next->x < z->x) + p = p->next; + // at this point, p->next->x is NOT < z->x + z->next = p->next; + p->next = z; + } + } + e++; + } + + // now process all active edges in non-zero fashion + if (active != NULL) + nsvg__fillActiveEdges(r->scanline, r->width, active, maxWeight, &xmin, &xmax, fillRule); + } + // Blit + if (xmin < 0) xmin = 0; + if (xmax > r->width-1) xmax = r->width-1; + if (xmin <= xmax) { + nsvg__scanlineSolid(&r->bitmap[y * r->stride] + xmin*4, xmax-xmin+1, &r->scanline[xmin], xmin, y, tx,ty, scale, cache); + } + } + +} + +static void nsvg__unpremultiplyAlpha(unsigned char* image, int w, int h, int stride) +{ + int x,y; + + // Unpremultiply + for (y = 0; y < h; y++) { + unsigned char *row = &image[y*stride]; + for (x = 0; x < w; x++) { + int r = row[0], g = row[1], b = row[2], a = row[3]; + if (a != 0) { + row[0] = (unsigned char)(r*255/a); + row[1] = (unsigned char)(g*255/a); + row[2] = (unsigned char)(b*255/a); + } + row += 4; + } + } + + // Defringe + for (y = 0; y < h; y++) { + unsigned char *row = &image[y*stride]; + for (x = 0; x < w; x++) { + int r = 0, g = 0, b = 0, a = row[3], n = 0; + if (a == 0) { + if (x-1 > 0 && row[-1] != 0) { + r += row[-4]; + g += row[-3]; + b += row[-2]; + n++; + } + if (x+1 < w && row[7] != 0) { + r += row[4]; + g += row[5]; + b += row[6]; + n++; + } + if (y-1 > 0 && row[-stride+3] != 0) { + r += row[-stride]; + g += row[-stride+1]; + b += row[-stride+2]; + n++; + } + if (y+1 < h && row[stride+3] != 0) { + r += row[stride]; + g += row[stride+1]; + b += row[stride+2]; + n++; + } + if (n > 0) { + row[0] = (unsigned char)(r/n); + row[1] = (unsigned char)(g/n); + row[2] = (unsigned char)(b/n); + } + } + row += 4; + } + } +} + + +static void nsvg__initPaint(NSVGcachedPaint* cache, NSVGpaint* paint, float opacity) +{ + int i, j; + NSVGgradient* grad; + + cache->type = paint->type; + + if (paint->type == NSVG_PAINT_COLOR) { + cache->colors[0] = nsvg__applyOpacity(paint->color, opacity); + return; + } + + grad = paint->gradient; + + cache->spread = grad->spread; + memcpy(cache->xform, grad->xform, sizeof(float)*6); + + if (grad->nstops == 0) { + for (i = 0; i < 256; i++) + cache->colors[i] = 0; + } if (grad->nstops == 1) { + for (i = 0; i < 256; i++) + cache->colors[i] = nsvg__applyOpacity(grad->stops[i].color, opacity); + } else { + unsigned int ca, cb = 0; + float ua, ub, du, u; + int ia, ib, count; + + ca = nsvg__applyOpacity(grad->stops[0].color, opacity); + ua = nsvg__clampf(grad->stops[0].offset, 0, 1); + ub = nsvg__clampf(grad->stops[grad->nstops-1].offset, ua, 1); + ia = (int)(ua * 255.0f); + ib = (int)(ub * 255.0f); + for (i = 0; i < ia; i++) { + cache->colors[i] = ca; + } + + for (i = 0; i < grad->nstops-1; i++) { + ca = nsvg__applyOpacity(grad->stops[i].color, opacity); + cb = nsvg__applyOpacity(grad->stops[i+1].color, opacity); + ua = nsvg__clampf(grad->stops[i].offset, 0, 1); + ub = nsvg__clampf(grad->stops[i+1].offset, 0, 1); + ia = (int)(ua * 255.0f); + ib = (int)(ub * 255.0f); + count = ib - ia; + if (count <= 0) continue; + u = 0; + du = 1.0f / (float)count; + for (j = 0; j < count; j++) { + cache->colors[ia+j] = nsvg__lerpRGBA(ca,cb,u); + u += du; + } + } + + for (i = ib; i < 256; i++) + cache->colors[i] = cb; + } + +} + +/* +static void dumpEdges(NSVGrasterizer* r, const char* name) +{ + float xmin = 0, xmax = 0, ymin = 0, ymax = 0; + NSVGedge *e = NULL; + int i; + if (r->nedges == 0) return; + FILE* fp = fopen(name, "w"); + if (fp == NULL) return; + + xmin = xmax = r->edges[0].x0; + ymin = ymax = r->edges[0].y0; + for (i = 0; i < r->nedges; i++) { + e = &r->edges[i]; + xmin = nsvg__minf(xmin, e->x0); + xmin = nsvg__minf(xmin, e->x1); + xmax = nsvg__maxf(xmax, e->x0); + xmax = nsvg__maxf(xmax, e->x1); + ymin = nsvg__minf(ymin, e->y0); + ymin = nsvg__minf(ymin, e->y1); + ymax = nsvg__maxf(ymax, e->y0); + ymax = nsvg__maxf(ymax, e->y1); + } + + fprintf(fp, "", xmin, ymin, (xmax - xmin), (ymax - ymin)); + + for (i = 0; i < r->nedges; i++) { + e = &r->edges[i]; + fprintf(fp ,"", e->x0,e->y0, e->x1,e->y1); + } + + for (i = 0; i < r->npoints; i++) { + if (i+1 < r->npoints) + fprintf(fp ,"", r->points[i].x, r->points[i].y, r->points[i+1].x, r->points[i+1].y); + fprintf(fp ,"", r->points[i].x, r->points[i].y, r->points[i].flags == 0 ? "#f00" : "#0f0"); + } + + fprintf(fp, ""); + fclose(fp); +} +*/ + +void nsvgRasterize(NSVGrasterizer* r, + NSVGimage* image, float tx, float ty, float scale, + unsigned char* dst, int w, int h, int stride) +{ + NSVGshape *shape = NULL; + NSVGedge *e = NULL; + NSVGcachedPaint cache; + int i; + + r->bitmap = dst; + r->width = w; + r->height = h; + r->stride = stride; + + if (w > r->cscanline) { + r->cscanline = w; + r->scanline = (unsigned char*)realloc(r->scanline, w); + if (r->scanline == NULL) return; + } + + for (i = 0; i < h; i++) + memset(&dst[i*stride], 0, w*4); + + for (shape = image->shapes; shape != NULL; shape = shape->next) { + if (!(shape->flags & NSVG_FLAGS_VISIBLE)) + continue; + + if (shape->fill.type != NSVG_PAINT_NONE) { + nsvg__resetPool(r); + r->freelist = NULL; + r->nedges = 0; + + nsvg__flattenShape(r, shape, scale); + + // Scale and translate edges + for (i = 0; i < r->nedges; i++) { + e = &r->edges[i]; + e->x0 = tx + e->x0; + e->y0 = (ty + e->y0) * NSVG__SUBSAMPLES; + e->x1 = tx + e->x1; + e->y1 = (ty + e->y1) * NSVG__SUBSAMPLES; + } + + // Rasterize edges + if (r->nedges != 0) + qsort(r->edges, r->nedges, sizeof(NSVGedge), nsvg__cmpEdge); + + // now, traverse the scanlines and find the intersections on each scanline, use non-zero rule + nsvg__initPaint(&cache, &shape->fill, shape->opacity); + + nsvg__rasterizeSortedEdges(r, tx,ty,scale, &cache, shape->fillRule); + } + if (shape->stroke.type != NSVG_PAINT_NONE && (shape->strokeWidth * scale) > 0.01f) { + nsvg__resetPool(r); + r->freelist = NULL; + r->nedges = 0; + + nsvg__flattenShapeStroke(r, shape, scale); + +// dumpEdges(r, "edge.svg"); + + // Scale and translate edges + for (i = 0; i < r->nedges; i++) { + e = &r->edges[i]; + e->x0 = tx + e->x0; + e->y0 = (ty + e->y0) * NSVG__SUBSAMPLES; + e->x1 = tx + e->x1; + e->y1 = (ty + e->y1) * NSVG__SUBSAMPLES; + } + + // Rasterize edges + if (r->nedges != 0) + qsort(r->edges, r->nedges, sizeof(NSVGedge), nsvg__cmpEdge); + + // now, traverse the scanlines and find the intersections on each scanline, use non-zero rule + nsvg__initPaint(&cache, &shape->stroke, shape->opacity); + + nsvg__rasterizeSortedEdges(r, tx,ty,scale, &cache, NSVG_FILLRULE_NONZERO); + } + } + + nsvg__unpremultiplyAlpha(dst, w, h, stride); + + r->bitmap = NULL; + r->width = 0; + r->height = 0; + r->stride = 0; +} + +#endif // NANOSVGRAST_IMPLEMENTATION + +#endif // NANOSVGRAST_H diff --git a/source/blender/editors/space_file/filelist.cc b/source/blender/editors/space_file/filelist.cc index ff552732d98..9fb94c518f8 100644 --- a/source/blender/editors/space_file/filelist.cc +++ b/source/blender/editors/space_file/filelist.cc @@ -1504,7 +1504,7 @@ static void filelist_cache_preview_runf(TaskPool *__restrict pool, void *taskdat // printf("%s: %d - %s - %p\n", __func__, preview->index, preview->path, preview->img); BLI_assert(preview->flags & (FILE_TYPE_IMAGE | FILE_TYPE_MOVIE | FILE_TYPE_FTFONT | FILE_TYPE_BLENDER | - FILE_TYPE_BLENDER_BACKUP | FILE_TYPE_BLENDERLIB)); + FILE_TYPE_OBJECT_IO | FILE_TYPE_BLENDER_BACKUP | FILE_TYPE_BLENDERLIB)); if (preview->flags & FILE_TYPE_IMAGE) { source = THB_SOURCE_IMAGE; @@ -1519,6 +1519,9 @@ static void filelist_cache_preview_runf(TaskPool *__restrict pool, void *taskdat else if (preview->flags & FILE_TYPE_FTFONT) { source = THB_SOURCE_FONT; } + else if (preview->flags & FILE_TYPE_OBJECT_IO) { + source = THB_SOURCE_OBJECT_IO; + } IMB_thumb_path_lock(preview->filepath); /* Always generate biggest preview size for now, it's simpler and avoids having to re-generate @@ -1620,8 +1623,9 @@ static void filelist_cache_previews_push(FileList *filelist, FileDirEntry *entry return; } - if (!(entry->typeflag & (FILE_TYPE_IMAGE | FILE_TYPE_MOVIE | FILE_TYPE_FTFONT | - FILE_TYPE_BLENDER | FILE_TYPE_BLENDER_BACKUP | FILE_TYPE_BLENDERLIB))) + if (!(entry->typeflag & + (FILE_TYPE_IMAGE | FILE_TYPE_MOVIE | FILE_TYPE_FTFONT | FILE_TYPE_OBJECT_IO | + FILE_TYPE_BLENDER | FILE_TYPE_BLENDER_BACKUP | FILE_TYPE_BLENDERLIB))) { return; } diff --git a/source/blender/imbuf/CMakeLists.txt b/source/blender/imbuf/CMakeLists.txt index cac7a53a486..f20f1a1642b 100644 --- a/source/blender/imbuf/CMakeLists.txt +++ b/source/blender/imbuf/CMakeLists.txt @@ -30,6 +30,7 @@ set(SRC intern/format_hdr.cc intern/format_png.cc intern/format_psd.cc + intern/format_svg.cc intern/format_targa.cc intern/format_tiff.cc intern/imageprocess.cc @@ -80,6 +81,7 @@ set(LIB PRIVATE bf::intern::guardedalloc bf_intern_memutil bf_intern_opencolorio + PRIVATE bf::extern::nanosvg ${JPEG_LIBRARIES} ) diff --git a/source/blender/imbuf/IMB_thumbs.h b/source/blender/imbuf/IMB_thumbs.h index 70f7a35855d..fda2ece615f 100644 --- a/source/blender/imbuf/IMB_thumbs.h +++ b/source/blender/imbuf/IMB_thumbs.h @@ -31,6 +31,7 @@ typedef enum ThumbSource { THB_SOURCE_MOVIE, THB_SOURCE_BLEND, THB_SOURCE_FONT, + THB_SOURCE_OBJECT_IO, } ThumbSource; /** diff --git a/source/blender/imbuf/intern/IMB_filetype.h b/source/blender/imbuf/intern/IMB_filetype.h index 77fc150856d..6aecc674dcf 100644 --- a/source/blender/imbuf/intern/IMB_filetype.h +++ b/source/blender/imbuf/intern/IMB_filetype.h @@ -290,6 +290,19 @@ struct ImBuf *imb_load_psd(const unsigned char *mem, /** \} */ +/* -------------------------------------------------------------------- */ +/** \name Format: SVG - Only for thumbnails. + * \{ */ + +struct ImBuf *imb_load_filepath_thumbnail_svg(const char *filepath, + const int flags, + const size_t max_thumb_size, + char colorspace[], + size_t *r_width, + size_t *r_height); + +/** \} */ + #ifdef __cplusplus }; #endif diff --git a/source/blender/imbuf/intern/filetype.cc b/source/blender/imbuf/intern/filetype.cc index 42ffc4bd5b4..a579048624a 100644 --- a/source/blender/imbuf/intern/filetype.cc +++ b/source/blender/imbuf/intern/filetype.cc @@ -199,6 +199,22 @@ const ImFileType IMB_FILE_TYPES[] = { /*default_save_role*/ COLOR_ROLE_DEFAULT_BYTE, }, #endif + { + /* Only implementing thumbnailing for SVG file type to support specialized importers. + * General file loading, if wanted, would require a better library and would have to + * support features like user-specified resolution. */ + + /*init*/ nullptr, + /*exit*/ nullptr, + /*is_a*/ nullptr, + /*load*/ nullptr, + /*load_filepath*/ nullptr, + /*load_filepath_thumbnail*/ imb_load_filepath_thumbnail_svg, + /*save*/ nullptr, + /*flag*/ 0, + /*filetype*/ IMB_FTYPE_NONE, + /*default_save_role*/ COLOR_ROLE_DEFAULT_BYTE, + }, {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, 0, 0, 0}, }; diff --git a/source/blender/imbuf/intern/format_svg.cc b/source/blender/imbuf/intern/format_svg.cc new file mode 100644 index 00000000000..e3fe753ef71 --- /dev/null +++ b/source/blender/imbuf/intern/format_svg.cc @@ -0,0 +1,66 @@ +/* SPDX-FileCopyrightText: 2023 Blender Foundation + * + * SPDX-License-Identifier: GPL-2.0-or-later */ + +/** \file + * \ingroup imbuf + * + * SVG vector graphics format support for the purpose of thumbnail-display. + * While loading these as an #ImBuf is trivial to support, it would expose + * limitations of NANOSVG and users may end up needing more advanced options + * specific to loading vector graphics (such as resolution control), see #109567 for details. + */ + +#include "IMB_colormanagement.h" +#include "IMB_filetype.h" +#include "IMB_imbuf_types.h" +#include "nanosvg.h" +#include "nanosvgrast.h" + +ImBuf *imb_load_filepath_thumbnail_svg(const char *filepath, + const int /* flags */, + const size_t max_thumb_size, + char colorspace[], + size_t *r_width, + size_t *r_height) +{ + NSVGimage *image = nsvgParseFromFile(filepath, "px", 96.0f); + + if (image == nullptr) { + return nullptr; + } + + if (image->width == 0 || image->height == 0) { + nsvgDelete(image); + return nullptr; + } + + int w = int(image->width); + int h = int(image->height); + + /* Return full size of the image. */ + *r_width = size_t(w); + *r_height = size_t(h); + + NSVGrasterizer *rast = nsvgCreateRasterizer(); + if (rast == nullptr) { + nsvgDelete(image); + return nullptr; + } + + colorspace_set_default_role(colorspace, IM_MAX_SPACE, COLOR_ROLE_DEFAULT_BYTE); + + const float scale = float(max_thumb_size) / MAX2(w, h); + const int dest_w = MAX2(int(w * scale), 1); + const int dest_h = MAX2(int(h * scale), 1); + + ImBuf *ibuf = IMB_allocImBuf(dest_w, dest_h, 32, IB_rect); + if (ibuf != nullptr) { + nsvgRasterize(rast, image, 0, 0, scale, ibuf->byte_buffer.data, dest_w, dest_h, dest_w * 4); + nsvgDeleteRasterizer(rast); + nsvgDelete(image); + IMB_flipy(ibuf); + } + + return ibuf; +} diff --git a/source/blender/imbuf/intern/thumbs.cc b/source/blender/imbuf/intern/thumbs.cc index 4c0c0423a0d..e025ff0dd3f 100644 --- a/source/blender/imbuf/intern/thumbs.cc +++ b/source/blender/imbuf/intern/thumbs.cc @@ -361,7 +361,8 @@ static ImBuf *thumb_create_ex(const char *file_path, } } else { - if (ELEM(source, THB_SOURCE_IMAGE, THB_SOURCE_BLEND, THB_SOURCE_FONT)) { + if (ELEM(source, THB_SOURCE_IMAGE, THB_SOURCE_BLEND, THB_SOURCE_FONT, THB_SOURCE_OBJECT_IO)) + { /* only load if we didn't give an image */ if (img == nullptr) { switch (source) { @@ -374,6 +375,12 @@ static ImBuf *thumb_create_ex(const char *file_path, case THB_SOURCE_FONT: img = IMB_thumb_load_font(file_path, tsize, tsize); break; + case THB_SOURCE_OBJECT_IO: { + if (BLI_path_extension_check(file_path, ".svg")) { + img = IMB_thumb_load_image(file_path, tsize, nullptr); + } + break; + } default: BLI_assert_unreachable(); /* This should never happen */ } diff --git a/source/blender/io/gpencil/CMakeLists.txt b/source/blender/io/gpencil/CMakeLists.txt index 954c5f8be86..c078198f619 100644 --- a/source/blender/io/gpencil/CMakeLists.txt +++ b/source/blender/io/gpencil/CMakeLists.txt @@ -9,11 +9,12 @@ set(INC ../../bmesh ../../depsgraph ../../editors/include + ../../makesdna ../../makesrna ../../windowmanager ../../../../intern/clog - ../../../../intern/utfconv - ../../../../extern/nanosvg + ../../../../intern/guardedalloc + ../../../../intern/utfconv ) set(INC_SYS @@ -30,15 +31,13 @@ set(SRC intern/gpencil_io_export_base.hh intern/gpencil_io_import_base.hh intern/gpencil_io_import_svg.hh - - # Only so this file is known by CMake. - ../../../../extern/nanosvg/nanosvg.h ) set(LIB PRIVATE bf::blenkernel PRIVATE bf::blenlib PRIVATE bf::dna + PRIVATE bf::extern::nanosvg PRIVATE bf::intern::guardedalloc bf_io_common ) diff --git a/source/blender/io/gpencil/intern/gpencil_io_import_svg.cc b/source/blender/io/gpencil/intern/gpencil_io_import_svg.cc index fb81b3ca776..bfb875b3ac3 100644 --- a/source/blender/io/gpencil/intern/gpencil_io_import_svg.cc +++ b/source/blender/io/gpencil/intern/gpencil_io_import_svg.cc @@ -23,10 +23,6 @@ #include "gpencil_io.h" #include "gpencil_io_import_svg.hh" -/* Custom flags for NanoSVG. */ -#define NANOSVG_ALL_COLOR_KEYWORDS -#define NANOSVG_IMPLEMENTATION - #include "nanosvg.h" using blender::MutableSpan; diff --git a/source/blender/python/intern/bpy_utils_previews.c b/source/blender/python/intern/bpy_utils_previews.c index df4595c9388..97645e071b0 100644 --- a/source/blender/python/intern/bpy_utils_previews.c +++ b/source/blender/python/intern/bpy_utils_previews.c @@ -112,6 +112,9 @@ static PyObject *bpy_utils_previews_load(PyObject *UNUSED(self), PyObject *args) else if (STREQ(path_type_s, "FONT")) { path_type = THB_SOURCE_FONT; } + else if (STREQ(path_type_s, "OBJECT_IO")) { + path_type = THB_SOURCE_OBJECT_IO; + } else { PyErr_Format(PyExc_ValueError, "load: invalid '%s' filetype, only [" STR_SOURCE_TYPES