Skip to content

Instantly share code, notes, and snippets.

@grilme99
Last active October 18, 2024 16:50
Show Gist options
  • Save grilme99/d1c4bf3042ff86b6b6db5a441f0b372c to your computer and use it in GitHub Desktop.
Save grilme99/d1c4bf3042ff86b6b6db5a441f0b372c to your computer and use it in GitHub Desktop.
SVG rendering in Unity with SkiaSharp + OneJS
import { h } from "onejs-preact";
import { useEffect, useMemo, useRef } from "onejs-preact/hooks";
import { useSvgTexture } from "src/contexts/svg-cache/use-svg-texture";
import { tv } from "tailwind-variants";
import filledIcons from "src/generated/icons/filled.json";
import outlinedIcons from "src/generated/icons/outlined.json";
export type IconSize = "x-small" | "small" | "medium" | "large" | "x-large" | "xx-large";
function iconSizeToDimension(size: IconSize) {
switch (size) {
case "x-small":
return 8;
case "small":
return 16;
case "medium":
return 32;
case "large":
return 48;
case "x-large":
return 64;
case "xx-large":
return 80;
}
}
export type Props = {
name: string;
size?: IconSize;
filled?: boolean;
rotation?: number;
class?: string;
};
export function Icon(props: Props) {
const iconSize = props.size || "medium";
const iconStyle = icon({ size: iconSize });
const imageRef = useRef<Element>();
const iconSvg = useMemo(() => {
const icons = props.filled ? filledIcons : outlinedIcons;
const svg = (icons as any)[props.name];
if (!svg) {
console.error(`Icon ${props.name} not found`);
return "";
}
return svg;
}, [props.name, props.filled]);
const dimension = iconSizeToDimension(iconSize);
const iconTexture = useSvgTexture(iconSvg, dimension, dimension);
useEffect(() => {
imageRef.current.style.backgroundImage = iconTexture;
}, [iconTexture]);
useEffect(() => {
imageRef.current.style.rotate = props.rotation || 0;
}, [props.rotation]);
return <div class={`${iconStyle} ${props.class || ""}`} ref={imageRef} />;
}
const icon = tv({
base: "content-default",
variants: {
size: {
"x-small": "h-2 w-2",
small: "h-4 w-4",
medium: "h-8 w-8",
large: "h-12 w-12",
"x-large": "h-16 w-16",
"xx-large": "h-20 w-20",
},
},
});
import { Texture2D } from "UnityEngine";
import { createContext } from "onejs-preact";
export type SvgCache = {
cache: Map<string, Texture2D>;
};
const DEFAULT: SvgCache = {
cache: new Map(),
};
export const SvgCacheContext = createContext(DEFAULT);
using SkiaSharp;
using UnityEngine;
using Svg.Skia;
using Debug = System.Diagnostics.Debug;
namespace _Project.Scripts.Utility
{
public class SvgRenderer : MonoBehaviour
{
public Texture2D RenderSvg(string svgSrc, int width, int height)
{
var svg = new SKSvg();
var picture = svg.FromSvg(svgSrc);
Debug.Assert(picture != null, "Failed to create svg");
var cullRect = picture.CullRect;
var bmp = picture.ToBitmap(SKColor.Empty, width / cullRect.Width, height / cullRect.Height,
SKColorType.Rgba8888, SKAlphaType.Unpremul, null);
Debug.Assert(bmp != null, "Compiled bitmap is null");
var texture = new Texture2D(width, height, TextureFormat.RGBA32, false)
{
wrapMode = TextureWrapMode.Repeat
};
var surface = SKSurface.Create(bmp.Info);
var pixmap = surface.PeekPixels();
texture.LoadRawTextureData(bmp.GetPixels(), pixmap.RowBytes * pixmap.Height);
texture.name = "SVG";
texture.Apply();
return texture;
}
}
}
import { useContext, useMemo } from "onejs-preact/hooks";
import { SvgCacheContext } from "./svg-cache-context";
import { Texture2D } from "UnityEngine";
declare namespace CS {
export interface SvgRenderer {
/// Responsible for rendering an SVG into a texture at a specified size.
RenderSvg(svgSrc: string, width: number, height: number): Texture2D;
}
}
declare const SvgRenderer: CS.SvgRenderer;
function useSvgTextureWithGlobalCache(svg: string, width: number, height: number): Texture2D {
const { cache } = useContext(SvgCacheContext);
// TODO: Investigate if this is too big - SVG string is very large
const cacheKey = `${svg}-${width}-${height}`;
if (cache.has(cacheKey)) {
return cache.get(cacheKey) as Texture2D;
}
const texture = SvgRenderer.RenderSvg(svg, width, height);
cache.set(cacheKey, texture);
return texture;
}
export function useSvgTexture(svg: string, width: number, height: number): Texture2D {
const texture = useMemo(() => {
return useSvgTextureWithGlobalCache(svg, width, height);
}, [svg, width, height]);
return texture;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment