Last active
October 18, 2024 16:50
-
-
Save grilme99/d1c4bf3042ff86b6b6db5a441f0b372c to your computer and use it in GitHub Desktop.
SVG rendering in Unity with SkiaSharp + OneJS
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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", | |
}, | |
}, | |
}); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | |
} | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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