ful1e5 / Bibata_Cursor

Open source, compact, and material designed cursor set.
https://www.bibata.live
GNU General Public License v3.0
2.16k stars 76 forks source link

Multi-resolution cursors on Windows #149

Open stanio opened 1 year ago

stanio commented 1 year ago

(Follow-up to #116 and #119.)

To adapt for hi-dpi (different resolution) screens the standard Windows cursors provide multiple resolutions for each cursor: 32×32, 48×48, 64×64, 96×96, and 128×128, packaged inside the individual cursor files.

The difference between the "Regular", "Large", and "Extra-Large" schemes is the relative amount of space the cursor shape occupies on the canvas, regardless of the resolution:

Regular Large Extra-Large
aero_arrow.cur aero_arrow_l.cur aero_arrow_xl.cur

(The thin red rulers in the middle and the X spots are not part of the cursor images.)

Likely to conserve memory space the standard animated cursors provide just 32×32, 48×48, and 64×64 resolutions:

Regular Large Extra-Large
aero_working.ani aero_working_l.ani aero_working_xl.ani

Compared with the current Bibata packages – they provide just a single resolution (per scheme):

Small (32×32) Regular (32×32) Large (32×32) Extra-Large (48×48)
pointer-small pointer-regular pointer-large pointer-extra-large

One may notice the "Large" and "Extra-Large" schemes provide different resolutions for what is essentially a single scheme (I deem): "Extra-Large". "Small" and "Regular" map more closely to "Regular" and "Large".

Ultimately this causes dynamic upscale resampling and blurry cursors on hi-dpi screens. The individual Bibata cursors for Windows should provide multiple resolutions as with the standard Windows cursors.


I guess this is dependent on clickgen supporting it, also.

stanio commented 1 year ago

I have a sample Bibata-Modern-Ice-Windows-v2.0.4-stanio-1.zip package I've compiled manually to what would be the expected result, attached to https://github.com/ful1e5/Bibata_Cursor/issues/116#issuecomment-1731767032:

Regular Large Extra-Large
pointer-regular pointer-large pointer-extra-large
ful1e5 commented 1 year ago

@stanio Thank you for reporting this. I will look into it soon.

stanio commented 1 year ago

A side note: Given the original bitmaps map to "Extra-Large" (fully occupying the canvas), I'm producing the "Large" and "Regular" variants by expanding the original canvas (filling with space to the right and bottom) with ¼ and ½ from the original size respectively. Then I'm scaling to the same target resolutions:

size-variants

I'm pointing this out as it could be achieved by manipulating the source SVG viewBox:

Extra-Large Large Regular
width="#" height="#"
viewBox="0 0 256 256"
width="#" height="#"
viewBox="0 0 320 320"
width="#" height="#"
viewBox="0 0 384 384"

Updating the width and height to each target resolution then allows rendering the SVG directly at the target resolution, avoiding whatever (minimal) blur may appear from downscaling a single "master" bitmap. This is just a general suggestion not necessary for the purpose of solving the original issue.

stanio commented 1 year ago

Here's a PoC I've come up with: wincur.zip (Java 11+, source included in the package)

alias wincur="java -jar <path-to>/wincur.zip"
alias winani="java -cp <path-to>/wincur.zip io.github.stanio.windows.AnimatedCursor"

Sample usage

Using the rendered source bitmaps (256×256), create Extra-Large, Large, and Regular variants each including five resolutions (32×32, 48×48, 64×64, 96×96, 128×128) of a static cursor:

wincur pin.png -h 42,15 -r 32 -r 48 -r 64 -r 96 -r 128 -o Pin_xl.cur
wincur pin.png -h 42,15 -s 1.25 -r 32 -r 48 -r 64 -r 96 -r 128 -o Pin_l.cur
wincur pin.png -h 42,15 -s 1.5 -r 32 -r 48 -r 64 -r 96 -r 128 -o Pin.cur

-h specifies the hotspot. It is automatically adjusted for the target resolution and extra view-box sizing (-s). I've included possibility for generating cursors with multiple bitmap sources for the different resolutions, also.

Creating an animated cursor is a two-step operation:

# Create cursor files for each frame
for f in left_ptr_watch-*.png; do wincur $f -h 42,15 -r 32 -r 48 -r 64 || break; done

# Create the animated cursor (left_ptr_watch.ani)
winani -j 4 left_ptr_watch-*.cur

-j specifies the frame rate in jiffies (1/60 of a second). I've made it use 3 jiffies (5 ms, 20 fps) by default.

stanio commented 1 year ago

Whomever it may benefit:

Noteworthy * Five resolutions per cursor: 32×32, 48×48, 64×64, 96×96, and 128×128 Animated cursors (`Busy`, `Work`) include 32×32, 48×48, and 64×64. The regular size scheme provides 32×32, 48×48, 64×64, and 96×96 resolutions for the animated cursors; * Stable animation frames (`Busy` – 60 frames @ 20 fps, `Work` – 45 frames @ 15 fps); * All cursors are PNG-compressed; * Rendered each size from the SVG source (vs. resampling from a single "master" bitmap); and * For some of the cursors, used target pixel-grid alignment hint to improve quality/legibility at smaller resolutions (see https://github.com/stanio/Bibata_Cursor/commit/8cc992faefc8d9327957d0d7a58b0ac1687bcc5f#commitcomment-131173743 for an example of how this could affect results); * Corrected the original hotspots not being updated for 200×200 → 256×256 source size change (see https://github.com/ful1e5/Bibata_Cursor/pull/144#pullrequestreview-1691216100); * The included install scripts use different cursor directories and scheme names from the official packages, so they don't clash Include fixes for #150 and #154. See my main changes: https://github.com/ful1e5/Bibata_Cursor/compare/v2.0.4...stanio:Bibata_Cursor:v2.0.4-stanio-3
ful1e5 commented 10 months ago

@stanio Launched Bibata, a web app at https://bibata.live/ for personalizing cursor color and size in browsers.

Thanks for your patience and support. Closing this issue.

stanio commented 10 months ago

Trying out bibata.live I find virtually nothing has changed related to the original issue:

In this regard, I find the following statement on Windows cursors support currently on the bibata.live site largely misleading:

Bibata Studio craft Windows cursors with seamless HiDPI support...

Bibata packages currently provide very basic (standard-resolution) Windows support.

stanio commented 9 months ago

[For archival purposes] Here's a streamlined approach to produce all necessary bitmaps. The following table lists all bitmap sizes to be rendered (or downsampled from a single master bitmap):

size Regular
× ²⁄₃
Large
× ⁴⁄₅
Extra-Large
× 1
32 21.333 → 22 25.6 → 26 32
48 32 38.4 → 39 48
64 42.666 → 43 51.2 → 52 64
96 64 76.8 → 77 96
128 85.333 → 86 102.4 → 103 128

The leftmost column specifies the final bitmap sizes included in a cursor. Each of these is produced by expanding the corresponding rendered (scaled) bitmap size, filling in with space (no resampling) to the bottom right of the target canvas, as necessary.

The relative factors ²⁄₃ (Regular) and ⁴⁄₅ (Large) are derived from my https://github.com/ful1e5/Bibata_Cursor/issues/149#issuecomment-1741739558 approach which is the other way around: expand the master canvas (with space) by ¹⁄₂ (Regular) or ¹⁄₄ (Large), then scale to the target size.

At the end, multi-resolution cursors should be compiled.

stanio commented 9 months ago

In case others stumble upon this issue, here are Windows packages I've compiled for my use: stanio/Bibata_Cursor.

stanio commented 8 months ago

Here is a single cursor sample (Unavailable / circle) – three files:

Each of the three contains the same resolutions (canvas sizes): 32x32, 48x48, 64x64, 96x96, 128x128. However Regular 32x32, Large 32x32, and Extra-Large 32x32 are different bitmaps. As given in a table previously (https://github.com/ful1e5/Bibata_Cursor/issues/149#issuecomment-1945460036) – the actual used size on a 32x32 Regular canvas is ~22x22, for Large it is ~26x26, while Extra-Large occupies the full canvas.

See how it looks in an editor:

Regular Large Extra-Large
Unavailable.cur Unavailable_l.cur Unavailable_xl.cur
ful1e5 commented 8 months ago

@stanio Thanks for your help with the issue. My main challenge now is incorporating multi-dimensional animated cursor frames into a single file.

I discovered bugs in the clickgen code and realized additional features are needed to package Windows cursors based on your suggestions. I plan to look into this further when time allows.

Meanwhile, I'm reverting the previous patch that removed rendering inside the 32 canvas (https://github.com/ful1e5/clickgen/commit/aa9ea8a1102026d656c06ad28a40e9d781d9e124)

Regarding .ani files, I couldn't find information about supporting multiple dimension cursors. If you have insights on this, your contributions to the clickgen/writer/windows module would be appreciated.

stanio commented 8 months ago

I plan to look into this further when time allows.

Fair enough.

Regarding .ani files, I couldn't find information about supporting multiple dimension cursors. If you have insights on this, your contributions to the clickgen/writer/windows module would be appreciated.

I wouldn't be able to help with a source contribution as I don't have Python experience, but given you already have basic .ani support implemented – the ANI file is just a sequence of CUR frames. Each CUR/frame stores its resolutions individually (as a static cursor). That is, the ANI file doesn't care about the resolutions.

I have saved the following general documentation references in my Java implementation:

but again – you should have that implemented already. If you have implemented multiple resolutions in the static CUR cursors – that's all you need for multi-resolution ANI cursors as well.

stanio commented 5 months ago

I see v2.0.7 already includes multi-resolution cursors – looks promising!

Here are a couple of observations from me:

stanio commented 5 months ago

It is somewhat inconsistent.

This is likely my editor software trying to make sense of something broken in the file format. An ANI file has a fixed number of frames. The individual frames (CUR) may unconventionally contain a varying number of resolutions, but that's likely to confuse any software trying to interpret the animation.

stanio commented 5 months ago

This is likely my editor software trying to make sense of something broken in the file format.

From "Bibata-Modern-Ice-Windows-v2.0.7.zip", I have extracted the individual frames (looking up and saving icon chunks) of Work.ani as separate CUR files, and I'm seeing the "Regular" and "Large" frames contain multiple entries of the same resolution:

Each entry of the same resolution appears to be a slightly larger canvas-size version of the previous one. As noted before, the "Extra-Large" frames appear to have just a (single) 96×96 resolution.

stanio commented 4 months ago

I'm seeing the "Regular" and "Large" frames contain multiple entries of the same resolution... Each entry of the same resolution appears to be a slightly larger canvas-size version of the previous one.

As far as I've observed, the effect is Windows picks the last entry of the same resolution which happens to be the largest canvas painting, and animated cursors from all of the "Regular", "Large", and "Extra-Large" variants are displayed just as "Extra-Large".