Bibata_Cursor icon indicating copy to clipboard operation
Bibata_Cursor copied to clipboard

Multi-resolution cursors on Windows

Open stanio opened this issue 2 years ago • 16 comments

(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 avatar Sep 23 '23 04:09 stanio

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

stanio avatar Sep 23 '23 05:09 stanio

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

ful1e5 avatar Sep 28 '23 03:09 ful1e5

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 avatar Sep 30 '23 10:09 stanio

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 avatar Oct 16 '23 11:10 stanio

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

stanio avatar Oct 28 '23 23:10 stanio

@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.

ful1e5 avatar Jan 12 '24 06:01 ful1e5

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

  • The cursors in downloaded Windows packages still contain a single resolution

    • In my opinion it is not very user-friendly to ask non-tech-savvy users to choose from such fine-grained resolutions; moreover
    • Lacking multiple resolutions a cursor is suboptimal for use on a system with multiple monitors having different resolutions (ultimately causing the original issue: #116, #119)
  • There's no possibility to download higher resolution (>= 32) of a "Regular" or "Large" size schemes

    Every dimension >= 32 produces just an "Extra-Large" scheme cursors:

    Regular Large bibata.live
    (Extra-Large)
    48×48 Pointer-48-regular Pointer-48-large Pointer-48-extra-large

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 avatar Jan 14 '24 05:01 stanio

[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 avatar Feb 15 '24 06:02 stanio

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

stanio avatar Feb 15 '24 06:02 stanio

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

  • Unavailable-cur.zip
    • Regular: Unavailable.cur
    • Large: Unavailable_l.cur
    • Extra-Large: Unavailable_xl.cur

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

stanio avatar Mar 02 '24 21:03 stanio

@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.

ful1e5 avatar Mar 05 '24 10:03 ful1e5

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 avatar Mar 05 '24 10:03 stanio

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

Here are a couple of observations from me:

  • The static cursors store the resolution variants in ascending order, while the standard cursors have them in reverse (compare with screenshots I've posted previously to this issue):

    Unavailable-128

    I don't know if this could be a problem;

  • The animated cursors have the resolutions in descending order but they appear to mix bitmaps from different relative sizes (like "Small", "Regular", "Large", "Extra-Large") in a single resolution:

    Frame 1 Frame 2 Frame 3 Frame 4
    Work-96-001 Work-96-002 Work-96-003 Work-96-004

    It is somewhat inconsistent. The 96x96 resolution of the Regular variant contains 4 * 54 (216) frames, the 64x64 and 48x48 resolutions contain 2 * 54 (108) frames, and the 32x32 resolution contains 5 * 54 (324) frames. The Extra-Large variant contains just a 96x96 resolution with just 54 frames.

stanio avatar Jun 18 '24 10:06 stanio

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 avatar Jun 18 '24 10:06 stanio

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:

  • 6 × 32×32
  • 2 × 48×48
  • 2 × 64×64
  • 4 × 96×96

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 avatar Jun 21 '24 04:06 stanio

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".

stanio avatar Jul 06 '24 05:07 stanio