aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--README.md25
-rwxr-xr-xxcursor2svg.sh67
2 files changed, 57 insertions, 35 deletions
diff --git a/README.md b/README.md
index fe59851..74ec57b 100644
--- a/README.md
+++ b/README.md
@@ -4,10 +4,11 @@ Please note that Generative AI was used for making this project
In GNOME >=51, it is now required to use only svg based cursor as xcursor got deprecated
now, I didn't want to give up on my xcursor themes, which were drawn by artists and not
-something rasterized, what this script does is it takes each pixel of the original cursor
-and draws a svg pixel-by-pixel, I think desktop environment like GNOME and KDE cache the
-svg into bitmap, so having a thousand rectangles in a few hundred frame wont cause any
-performance issue. (atleast I hope so, idk I didn't check this).
+something rasterized, what this script does is it takes the bitmap of each cursor frame,
+resizes it with nearest-neighbor sampling, and embeds it in an svg as a base64 data URI.
+This keeps the original pixel art intact while producing the svg cursors that newer
+desktops expect. If you prefer the old approach, `--pixel` draws every visible pixel as
+an SVG rectangle instead.
I tested this on [Hatsune Miku cursor theme by
supermariofps](https://github.com/supermariofps/hatsune-miku-windows-linux-cursors) and a
@@ -15,8 +16,8 @@ few other legacy xcursor themes. It worked pretty well for me on GNOME 51. Pleas
Pull request or submit an issue if something goes wrong. Thank you ^^
> [!IMPORTANT]
-> This project requires `xcur2png` and `imagemagick` which you can find in your standard distro
-> repos.
+> This project requires `xcur2png`, `imagemagick`, and `base64` (part of coreutils), which
+> you can find in your standard distro repos.
## Usage :
@@ -24,16 +25,20 @@ Pull request or submit an issue if something goes wrong. Thank you ^^
./xcursor2svg.sh --res 64 ~/.icons/miku-cursor-linux # install from a old xcursor theme
./xcursor2svg.sh --res 64 -y /path/to/theme # Convert and install automatically
./xcursor2svg.sh --res 64 -n /path/to/theme # Convert only
+./xcursor2svg.sh --res 64 --pixel /path/to/theme # Draw rectangles instead of embedding the bitmap
# For a theme already installed in a standard icon directory:
./xcursor2svg.sh --res 64 miku-cursor-linux
```
`--res` sets the nominal cursor size (default: 32). For each cursor, the closest
available Xcursor size is selected, preferring the larger size in a tie. All
-frames at that size are resized with nearest-neighbor sampling. Visible pixels
-become SVG rectangles with their original colors and transparency. A source at
-the requested size keeps its pixels exactly; resizing replicates or drops pixels,
-without smoothing. These SVGs retain the bitmap appearance when scaled.
+frames at that size are resized with nearest-neighbor sampling and embedded in
+the SVG as a base64 PNG. A source at the requested size keeps its pixels exactly;
+resizing replicates or drops pixels, without smoothing.
+
+Pass `--pixel` to use the older output format, where every visible pixel becomes
+an SVG rectangle with its original color and transparency. That produces larger,
+more verbose files but requires no embedded image data.
Animation order, delays, hotspots, and aliases are preserved in
`cursors_scalable/`, with missing GNOME/CSS names filled from legacy equivalents.
diff --git a/xcursor2svg.sh b/xcursor2svg.sh
index 0cd00fb..8ac64f3 100755
--- a/xcursor2svg.sh
+++ b/xcursor2svg.sh
@@ -1,13 +1,14 @@
#!/usr/bin/env bash
-# Convert an Xcursor theme to pixel-based SVG cursors.
+# Convert an Xcursor theme to SVG cursors with embedded bitmaps.
set -euo pipefail
export LC_ALL=C
usage() {
- echo "Usage: $0 [--res SIZE] [-y | -n] THEME_DIRECTORY_OR_NAME"
+ echo "Usage: $0 [--res SIZE] [--pixel] [-y | -n] THEME_DIRECTORY_OR_NAME"
echo "Point to the Xcursor theme directory containing the cursors/ subdirectory."
echo "An installed theme name can also be used."
echo " --res SIZE Nominal cursor size (default: 32)."
+ echo " --pixel Draw each pixel as an SVG rectangle instead of embedding the bitmap."
echo " -y Automatically install after conversion."
echo " -n Convert only; do not install."
echo "Example: $0 --res 64 -y miku-cursor-linux"
@@ -17,6 +18,7 @@ die() { echo "Error: $*" >&2; exit 1; }
res=32
theme=
+method=embed
install_mode=ask
while (($#)); do
case "$1" in
@@ -25,6 +27,7 @@ while (($#)); do
res=$2
shift 2
;;
+ --pixel) method=pixel; shift ;;
-y) [[ $install_mode != n ]] || die "Use either -y or -n, not both."; install_mode=y; shift ;;
-n) [[ $install_mode != y ]] || die "Use either -y or -n, not both."; install_mode=n; shift ;;
-h|--help) usage; exit 0 ;;
@@ -40,6 +43,9 @@ done
for command in xcur2png awk readlink; do
command -v "$command" >/dev/null || die "Required command not found: $command"
done
+if [[ $method == embed ]]; then
+ command -v base64 >/dev/null || die "Required command not found: base64"
+fi
if command -v magick >/dev/null; then
image_command=magick
elif command -v convert >/dev/null; then
@@ -94,7 +100,7 @@ else
fi
convert_cursor() {
- local cursor=$1 shape=$2 selected size xhot yhot png delay
+ local cursor=$1 shape=$2 selected size xhot yhot png delay bitmap
local width height scaled_width scaled_height hotspot_x hotspot_y extraction_log frame=0 comma=
local directory=$build/cursors_scalable/$shape
@@ -125,28 +131,39 @@ convert_cursor() {
read -r hotspot_x hotspot_y < <(awk -v x="$xhot" -v y="$yhot" -v w="$width" -v h="$height" \
-v sw="$scaled_width" -v sh="$scaled_height" 'BEGIN {printf "%.8f %.8f\n", x*sw/w, y*sh/h}')
- # Nearest-neighbor resizing preserves hard pixel edges. Each visible
- # pixel becomes a unit rectangle, including its original alpha.
- "$image_command" "$png" -filter point -resize "${scaled_width}x${scaled_height}!" \
- -colorspace sRGB -alpha on -depth 8 txt:- |
- awk -v w="$scaled_width" -v h="$scaled_height" '
- BEGIN {
- printf "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"%d\" height=\"%d\" viewBox=\"0 0 %d %d\" shape-rendering=\"crispEdges\">\n", w,h,w,h
- }
- /^#/ {next}
- {
- split($1, xy, /[:,]/)
- hex=""
- for (i=2; i<=NF; i++) if ($i ~ /^#[[:xdigit:]]+$/) {hex=$i; break}
- if (length(hex) != 9) exit 1
- alpha=16*(index("0123456789ABCDEF", toupper(substr(hex,8,1)))-1) + index("0123456789ABCDEF", toupper(substr(hex,9,1)))-1
- if (alpha == 0) next
- printf "<rect x=\"%d\" y=\"%d\" width=\"1\" height=\"1\" fill=\"%s\"", xy[1],xy[2],substr(hex,1,7)
- if (alpha < 255) printf " fill-opacity=\"%.8f\"", alpha/255
- print "/>"
- }
- END {print "</svg>"}
- ' > "$directory/frame-$frame.svg"
+ # Nearest-neighbor resizing preserves hard pixel edges. By default the
+ # bitmap is resized and embedded in the SVG as a base64 data URI.
+ # With --pixel, each visible pixel becomes a unit rectangle instead.
+ if [[ $method == pixel ]]; then
+ "$image_command" "$png" -filter point -resize "${scaled_width}x${scaled_height}!" \
+ -colorspace sRGB -alpha on -depth 8 txt:- |
+ awk -v w="$scaled_width" -v h="$scaled_height" '
+ BEGIN {
+ printf "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"%d\" height=\"%d\" viewBox=\"0 0 %d %d\" shape-rendering=\"crispEdges\">\n", w,h,w,h
+ }
+ /^#/ {next}
+ {
+ split($1, xy, /[:,]/)
+ hex=""
+ for (i=2; i<=NF; i++) if ($i ~ /^#[[:xdigit:]]+$/) {hex=$i; break}
+ if (length(hex) != 9) exit 1
+ alpha=16*(index("0123456789ABCDEF", toupper(substr(hex,8,1)))-1) + index("0123456789ABCDEF", toupper(substr(hex,9,1)))-1
+ if (alpha == 0) next
+ printf "<rect x=\"%d\" y=\"%d\" width=\"1\" height=\"1\" fill=\"%s\"", xy[1],xy[2],substr(hex,1,7)
+ if (alpha < 255) printf " fill-opacity=\"%.8f\"", alpha/255
+ print "/>"
+ }
+ END {print "</svg>"}
+ ' > "$directory/frame-$frame.svg"
+ else
+ bitmap=$("$image_command" "$png" -filter point -resize "${scaled_width}x${scaled_height}!" \
+ -colorspace sRGB -alpha on -depth 8 png:- | base64 -w0) ||
+ die "Cannot encode $png"
+ [[ -n $bitmap ]] || die "Cannot encode $png"
+ printf '<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="%d" height="%d" viewBox="0 0 %d %d" image-rendering="optimizeSpeed">\n<image width="%d" height="%d" xlink:href="data:image/png;base64,%s"/>\n</svg>\n' \
+ "$scaled_width" "$scaled_height" "$scaled_width" "$scaled_height" \
+ "$scaled_width" "$scaled_height" "$bitmap" > "$directory/frame-$frame.svg"
+ fi
printf '%s {"filename":"frame-%s.svg","delay":%s,"hotspot_x":%s,"hotspot_y":%s,"nominal_size":%s}' \
"$comma" "$frame" "${delay:-0}" "$hotspot_x" "$hotspot_y" "$res" >> "$directory/metadata.json"
comma=$',\n'