Merge pull request #388 from SAHIL-Sharma21/feat/Vtracer_implementation

Feat: add VTracer converter for raster to vector conversion
This commit is contained in:
Emrik Östling
2025-08-13 18:37:35 +02:00
committed by GitHub
4 changed files with 117 additions and 18 deletions

View File

@@ -43,7 +43,7 @@ RUN bun run build
# copy production dependencies and source code into final image
FROM base AS release
# install additional dependencies
# install additional dependencies
RUN apt-get update && apt-get install -y \
assimp-utils \
calibre \
@@ -75,6 +75,19 @@ RUN apt-get update && apt-get install -y \
--no-install-recommends \
&& rm -rf /var/lib/apt/lists/*
# Install VTracer binary
RUN ARCH=$(uname -m) && \
if [ "$ARCH" = "aarch64" ]; then \
VTRACER_ASSET="vtracer-aarch64-unknown-linux-musl.tar.gz"; \
else \
VTRACER_ASSET="vtracer-x86_64-unknown-linux-musl.tar.gz"; \
fi && \
curl -L -o /tmp/vtracer.tar.gz "https://github.com/visioncortex/vtracer/releases/download/0.6.4/${VTRACER_ASSET}" && \
tar -xzf /tmp/vtracer.tar.gz -C /tmp/ && \
mv /tmp/vtracer /usr/local/bin/vtracer && \
chmod +x /usr/local/bin/vtracer && \
rm /tmp/vtracer.tar.gz
COPY --from=install /temp/prod/node_modules node_modules
COPY --from=prerelease /app/public/generated.css /app/public/
COPY --from=prerelease /app/dist /app/dist
@@ -86,4 +99,4 @@ EXPOSE 3000/tcp
# used for calibre
ENV QTWEBENGINE_CHROMIUM_FLAGS="--no-sandbox"
ENV NODE_ENV=production
ENTRYPOINT [ "bun", "run", "dist/src/index.js" ]
ENTRYPOINT [ "bun", "run", "dist/src/index.js" ]

View File

@@ -25,22 +25,23 @@ A self-hosted online file converter. Supports over a thousand different formats.
## Converters supported
| Converter | Use case | Converts from | Converts to |
| ------------------------------------------------ | ---------------- | ------------- | ----------- |
| [libjxl](https://github.com/libjxl/libjxl) | JPEG XL | 11 | 11 |
| [resvg](https://github.com/RazrFalcon/resvg) | SVG | 1 | 1 |
| [Vips](https://github.com/libvips/libvips) | Images | 45 | 23 |
| [libheif](https://github.com/strukturag/libheif) | HEIF | 2 | 4 |
| [XeLaTeX](https://tug.org/xetex/) | LaTeX | 1 | 1 |
| [Calibre](https://calibre-ebook.com/) | E-books | 26 | 19 |
| [Pandoc](https://pandoc.org/) | Documents | 43 | 65 |
| [dvisvgm](https://dvisvgm.de/) | Vector images | 4 | 2 |
| [ImageMagick](https://imagemagick.org/) | Images | 245 | 183 |
| [GraphicsMagick](http://www.graphicsmagick.org/) | Images | 167 | 130 |
| [Inkscape](https://inkscape.org/) | Vector images | 7 | 17 |
| [Assimp](https://github.com/assimp/assimp) | 3D Assets | 77 | 23 |
| [FFmpeg](https://ffmpeg.org/) | Video | ~472 | ~199 |
| [Potrace](https://potrace.sourceforge.net/) | Raster to vector | 4 | 11 |
| Converter | Use case | Converts from | Converts to |
| -------------------------------------------------- | ---------------- | ------------- | ----------- |
| [libjxl](https://github.com/libjxl/libjxl) | JPEG XL | 11 | 11 |
| [resvg](https://github.com/RazrFalcon/resvg) | SVG | 1 | 1 |
| [Vips](https://github.com/libvips/libvips) | Images | 45 | 23 |
| [libheif](https://github.com/strukturag/libheif) | HEIF | 2 | 4 |
| [XeLaTeX](https://tug.org/xetex/) | LaTeX | 1 | 1 |
| [Calibre](https://calibre-ebook.com/) | E-books | 26 | 19 |
| [Pandoc](https://pandoc.org/) | Documents | 43 | 65 |
| [dvisvgm](https://dvisvgm.de/) | Vector images | 4 | 2 |
| [ImageMagick](https://imagemagick.org/) | Images | 245 | 183 |
| [GraphicsMagick](http://www.graphicsmagick.org/) | Images | 167 | 130 |
| [Inkscape](https://inkscape.org/) | Vector images | 7 | 17 |
| [Assimp](https://github.com/assimp/assimp) | 3D Assets | 77 | 23 |
| [FFmpeg](https://ffmpeg.org/) | Video | ~472 | ~199 |
| [Potrace](https://potrace.sourceforge.net/) | Raster to vector | 4 | 11 |
| [VTracer](https://github.com/visioncortex/vtracer) | Raster to vector | 8 | 1 |
<!-- many ffmpeg fileformats are duplicates -->

View File

@@ -20,6 +20,7 @@ import { convert as convertPandoc, properties as propertiesPandoc } from "./pand
import { convert as convertPotrace, properties as propertiesPotrace } from "./potrace";
import { convert as convertresvg, properties as propertiesresvg } from "./resvg";
import { convert as convertImage, properties as propertiesImage } from "./vips";
import { convert as convertVtracer, properties as propertiesVtracer } from "./vtracer";
import { convert as convertxelatex, properties as propertiesxelatex } from "./xelatex";
// This should probably be reconstructed so that the functions are not imported instead the functions hook into this to make the converters more modular
@@ -117,6 +118,10 @@ const properties: Record<
properties: propertiesPotrace,
converter: convertPotrace,
},
vtracer: {
properties: propertiesVtracer,
converter: convertVtracer,
},
};
function chunks<T>(arr: T[], size: number): T[][] {

80
src/converters/vtracer.ts Normal file
View File

@@ -0,0 +1,80 @@
import { execFile as execFileOriginal } from "node:child_process";
import { ExecFileFn } from "./types";
export const properties = {
from: {
images: ["jpg", "jpeg", "png", "bmp", "gif", "tiff", "tif", "webp"],
},
to: {
images: ["svg"],
},
};
interface VTracerOptions {
colormode?: string;
hierarchical?: string;
mode?: string;
filter_speckle?: string | number;
color_precision?: string | number;
layer_difference?: string | number;
corner_threshold?: string | number;
length_threshold?: string | number;
max_iterations?: string | number;
splice_threshold?: string | number;
path_precision?: string | number;
}
export function convert(
filePath: string,
fileType: string,
convertTo: string,
targetPath: string,
options?: unknown,
execFile: ExecFileFn = execFileOriginal, // to make it mockable
): Promise<string> {
return new Promise((resolve, reject) => {
// Build vtracer arguments
const args = ["--input", filePath, "--output", targetPath];
// Add optional parameter if provided
if (options && typeof options === "object") {
const opts = options as VTracerOptions;
const validOptions: Array<keyof VTracerOptions> = [
"colormode",
"hierarchical",
"mode",
"filter_speckle",
"color_precision",
"layer_difference",
"corner_threshold",
"length_threshold",
"max_iterations",
"splice_threshold",
"path_precision",
];
for (const option of validOptions) {
if (opts[option] !== undefined) {
args.push(`--${option}`, String(opts[option]));
}
}
}
execFile("vtracer", args, (error, stdout, stderr) => {
if (error) {
reject(`error: ${error}${stderr ? `\nstderr: ${stderr}` : ""}`);
return;
}
if (stdout) {
console.log(`stdout: ${stdout}`);
}
if (stderr) {
console.log(`stderr: ${stderr}`);
}
resolve("Done");
});
});
}