Last updated Jun 08, 2026 at 08:46pm

This started because we were building the online cursor editor and ran into something that made no sense. Some animated cursor exports would load fine in Windows. Others looked fine in previews, looked fine in the file structure, and then Windows just refused to load them.

Just Found Out Windows Supports Multi-Size Animated Cursors

TLDR

Windows really can load animated .ani files that contain multiple sizes per frame, kind of like multi-size .cur files.

The problem is that Windows does not handle those files consistently. In our testing, some valid multi-size animated cursors loaded and some did not. The pass/fail line depended on a mix of:

  • which sizes were in the file
  • how many size pages were in each frame
  • how dense the image data was
  • how many frames the animation had
  • whether the pages were stored as DIB or PNG

So this is not some clean rule like "Windows only supports 5 sizes." It is messier than that.  However, out of this research and experiment we were able to standardize our site and come out with a new rule for delivering animated cursors.

All future animated cursors will come in sizes like below if max size is 256x256:

  • Default: 32,48,64
  • Large: 72,96,128
  • Extra Large: 256

ELI5

Think of an animated cursor like a flipbook.

Each frame can hold one image, or it can hold a few versions of the same image at different sizes, like 32x32, 48x48, and 64x64.

In theory, Windows should pick the best one for the user's DPI and show a sharp cursor.

And sometimes it actually does.

But once you start mixing certain sizes together, Windows can just decide it hates your file and refuse to load it. No useful error. No explanation. It just fails.

Why We Even Cared And How We Fell Into This Hole

We are building a browser-based cursor editor for Cursors-4U.com that exports both .cur and .ani.

For static .cur files, multi-size export was easy. Windows handles it fine. You get better DPI scaling. No drama.

The problem started during animated export.

We would make an animated cursor in the editor, export it, and it would look fine in our own previews. The file would look structurally valid. Sometimes it would even look normal when inspected outside of Windows' actual cursor loader path. But then Windows itself would refuse to use it. Another cursor exported almost the same way would load fine.

That is what kicked this whole thing off.

At first it looked random. Then it started looking like a pattern. Then it turned into a full rabbit hole because from the site's point of view, we cannot ship an export button that works for one animated cursor and silently breaks for the next one.

We also cared for a practical reason: standardization.

If multi-size animated .ani worked reliably, we would not need to generate a stupid number of separate animated files just to cover common DPI ranges. That matters because it affects:

  • storage
  • processing
  • bandwidth
  • ZIP size
  • how clean the whole export system is

The editor is still a work in progress. If the current schedule holds, the target is May 2026.

Before The Testing: DPI Scaling vs Accessibility Pointer Size Primer

One thing that is easy to mix up here is that Windows has two different size systems that can both make the cursor appear larger.

The first is normal display / DPI scaling.

That is the regular Windows scaling setting for the whole display, like 100%, 125%, 150%, 200%, and so on. Microsoft also allows custom scaling between 100% and 500%. So if you start from a 32x32 cursor, the DPI-scaled displayed size can plausibly range from about 32x32 up to about 160x160.

The second is the Accessibility pointer size slider.

That is a separate setting from display scale. In the sizing model we use for the editor, the 1..15 slider range maps like this:

  • 1 = 32
  • 2 = 48
  • 3 = 64
  • 4 = 80
  • 5 = 96
  • 6 = 112
  • 7 = 128
  • 8 = 144
  • 9 = 160
  • 10 = 176
  • 11 = 192
  • 12 = 208
  • 13 = 224
  • 14 = 240
  • 15 = 256

The important part is that these are not the same thing.

Windows has a DPI scaling path, and it also has a separate accessibility size path.

From direct observation, the accessibility pointer-size behavior appears to add some extra size on top of DPI scaling, but it does not appear to multiply the DPI-scaled result.  Our site has a max size of 256x256, but windows can go higher than this when DPI and Accessibility and mixed together.

So the safe takeaway is:

  • DPI scaling can go much higher than people usually think
  • the accessibility slider can also push cursor size much higher than the default range
  • that is exactly why larger cursor resources matter, even before you get into the weird ANI loader behavior

Quick History

There are really two different questions here:

  1. When did Windows support .ani at all?
  2. When did multi-size animated cursors start making sense as a DPI feature?

For basic .ani support, Microsoft's LoadCursorFromFile docs list Windows 2000 Professional as the minimum supported client. Microsoft's 2007 animated-cursor security advisory and MS07-017 also hit Windows 2000, XP, Server 2003, and Vista, so animated cursor loading definitely existed well before Windows 7.

For multiple images inside cursor resources, Microsoft's CURSOR resource docs say cursor resources can contain more than one image. LookupIconIdFromDirectoryEx also documents the best-fit image selection logic.

For multi-resolution animated cursors as a practical DPI feature, the best evidence points to the Windows 7 era. RealWorld's Multi-resolution Windows 7 cursors page and its cursor editor page both point that way. That is not an official Microsoft "introduced in Windows 7" statement, so I am not pretending it is. But it lines up with what we found.

Just Found Out It's Buggy as Hell

Why We Had To Start Testing So Hard

Once we realized the editor could export two animated cursors that looked basically fine to us, but only one of them would actually load in Windows, we had to stop guessing.

Preview images were not enough.

"The file parses" was not enough.

"It opened in a tool" was not enough.

The only thing that mattered was whether Windows itself accepted the file.

That is why the testing got more serious.

How We Tested It

We built a Python harness that generated .ani files while changing:

  • number of size pages per frame
  • exact dimensions
  • DIB vs PNG encoding
  • frame count
  • sparse vs dense payloads
  • real cursor art vs synthetic images

We tested files in a few ways:

We also checked the failing files structurally:

  • RIFF layout
  • anih fields
  • offsets
  • hotspot data
  • image sizes
  • padding and alignment

The direct loader tests were done on:

  • Windows 11 23H2
  • WSL2 Ubuntu for generation and analysis
  • Python for structural checks
  • PowerShell to hit the real Windows loader

The First Real Boundary We Hit

One of the first useful tests was not some giant fuzzing matrix. It was five real sample exports from the same cursor series, all using the same animation and artwork, but with more size pages added each time.

SampleSizes Per FrameResult
1256Loads
232,48,256Loads
332,48,64,256Loads
432,48,64,72,256Loads
532,48,64,72,96,256Fails

Then we made a copy of the failing file and removed only 256.

That new file loaded.

So right away, it was obvious this was not just "Windows hates 96" or "Windows hates 256." It was the combination.

The Patterns That Actually Mattered

These were the big ones:

  • single-size .ani worked reliably
  • static multi-size .cur worked reliably
  • some multi-size animated .ani files loaded
  • other multi-size animated .ani files with valid structure failed silently

And the silent part is what made it so annoying. Windows did not tell us what was wrong. In the direct API tests it usually just returned a null handle.

Some of the strongest patterns:

  • DIB-heavy animated multi-size files failed a lot more often than PNG-only ones
  • dense PNG-only 6-page sets could still fail
  • the same exact multi-size frame payload could load as a static .cur and fail as a 1-frame .ani
  • changing requested size with LoadImage did not save failing files
  • changing thread cursor scaling with SetThreadCursorCreationScaling did not save failing files
  • high-only size groups above 128 were especially sketchy

One of the strongest repros was this:

  • take frame 0 from a failing ANI
  • save that exact frame as a standalone multi-size .cur
  • Windows loads the .cur
  • wrap that same payload back into a 1-frame .ani
  • Windows rejects the .ani

That is a pretty strong sign that this is not just "oops, bad file."

The Most Useful Loader Results

This is the smaller set of results that really shaped the final decision.

TestSetupResult
Sparse PNG stress file32..256, 225 pages per frameLoads
Fixed size set32,48,64,72,96,256, 14 frames, all-PNG sparseLoads
Fixed size set32,48,64,72,96,256, 14 frames, all-PNG denseFails
Fixed size set32,48,64,72,96,256, 14 frames, mixed DIB + PNGFails
Fixed size set32,48,64,72,96,256, 14 frames, all-DIBFails
Dense PNG boundary32,48,64,72,max, 2 frames, max=96..256All loaded
Dense PNG boundary32,48,64,72,96,max, 2 frames, max=128..256All failed

That is where it stopped looking like a simple size-count problem and started looking like some ugly combination of size mix, payload density, and loader path.

What the Internet Was Good For

The docs were useful for confirming the basics, not for explaining the bug.

What the docs do support:

The community stuff was mixed.

Older RealWorld forum posts blamed some ANI failures on 128x128, but our testing showed that was not the whole story.

The most useful outside clue was this RealWorld thread about hybrid BMP/DIB plus PNG cursor resources, where the author says Windows 10/11 gets weird when only the larger pages are PNG-compressed. That matched part of what we saw, especially with mixed DIB+PNG files.

Microsoft's MS07-017 was also a useful reminder that ANI handling has had hardened validation logic for a long time. It does not explain our exact issue, but it makes "there is some weird internal validator here" feel very believable.

What we did not find:

  • no official Microsoft page documenting an ANI-specific multi-size limit
  • no community post that cleanly described the exact failure pattern we reproduced

What We Think the Problem Is

At this point, the best explanation is not "the format says no."

The better explanation is:

  • Windows has an ANI-specific loader or validation quirk
  • it probably has more than one trigger
  • the triggers seem to involve some mix of:
    • size combination
    • page count per frame
    • frame count
    • payload density
    • DIB vs PNG code paths

Why we think that:

  • the failing files were structurally valid
  • LoadCursorFromFileW and LoadImage agreed on the same failures
  • explicit requested sizes did not help
  • thread cursor scaling changes did not help
  • the same exact frame payload could load as .cur and fail as .ani

So the honest answer is:

This is probably a Windows bug, or at least some undocumented loader validation behavior deep enough that black-box testing can only narrow it down, not fully explain it.

If You Actually Know the Answer

If you know the exact internal reason this happens, I would love to know it.

The useful kinds of expertise here would be:

  • Windows cursor internals
  • user32.dll cursor loading
  • ANI parsing or validation internals
  • cursor theme authoring on modern Windows
  • reverse engineering the animated cursor loader path

Just Found Out How To Standardize This On Our Site

The Size Presets That Actually Made Sense

At some point the question stopped being:

"Why is Windows like this?"

And became:

"What can we actually ship that still saves resources and does not randomly break?"

The most defensible grouped presets from the testing were:

RoleSizesResultWhy We Kept It
Default32,48,64LoadsClean low-range preset that kept working
Large72,96,128LoadsGood middle-range preset that also kept working
Extra Large256LoadsSimpler and safer than grouped high-range presets

We also had follow-up tests where 128,256 loaded, but once we kept pushing deeper into grouped high-range presets above 128, things got shaky again fast. That is why we did not build the final standard around "lots of big grouped sizes." It was not stable enough.

The Full Combo List From Our Notes

I went back through the saved research notes and the later follow-up test thread and pulled the combinations into one place.

This is not every possible size set in the world. It is the full set of combinations we explicitly wrote down while doing this research. Unless a row says otherwise, these were direct Windows loader checks on the same Windows 11 23H2 machine.

Real Export Series

These were five real animated exports from the same cursor series. Same artwork. Same animation. More sizes added each time.

SizesResultNotes
256LoadsReal sample export
32,48,256LoadsReal sample export
32,48,64,256LoadsReal sample export
32,48,64,72,256LoadsReal sample export
32,48,64,72,96,256FailsReal sample export
32,48,64,72,96LoadsFailing sample with only 256 removed

DIB And Mixed Boundary Combos

These were the early combinations that made it obvious this was not just "Windows hates 128" or "Windows hates 256."

SizesEncoding / SetupResult
32-43consecutive DIBLoads
32-44consecutive DIBFails
42-49consecutive DIBLoads
42-50consecutive DIBFails
52-57consecutive DIBLoads
62-65consecutive DIBLoads
62-66consecutive DIBFails
72-74consecutive DIBLoads
82-84consecutive DIBLoads
232consecutive DIBLoads
232-233consecutive DIBFails
32,48,64,72,96spaced DIBLoads
32,48,64,72,128spaced DIBLoads
32,48,64,96,128spaced DIBFails
32,48,64,72,96,128spaced DIBFails
256PNG-only pageLoads
32,48,256mixed DIB + PNGLoads
32,48,64,256mixed DIB + PNGLoads
32,48,64,72,256mixed DIB + PNGLoads
32,48,64,72,96,256mixed DIB + PNGFails
32,48,64,72,256dense PNG-only, 2 framesLoads
32,48,64,72,96,256dense PNG-only, 2 framesFails

Dense PNG Sweep Results

These were useful because they showed that "PNG-only" still was not enough by itself.

All of these loaded

  • 32,48,64,72,96
  • 32,48,64,72,128
  • 32,48,64,72,144
  • 32,48,64,72,160
  • 32,48,64,72,192
  • 32,48,64,72,224
  • 32,48,64,72,256

All of these failed

  • 32,48,64,72,96,128
  • 32,48,64,72,96,144
  • 32,48,64,72,96,160
  • 32,48,64,72,96,192
  • 32,48,64,72,96,224
  • 32,48,64,72,96,256

We also had the 14-frame version of this same pattern:

  • 32,48,64,72,96 loaded
  • 32,48,64,72,96,128 failed

Sparse PNG Stress Files

These loaded:

  • 32..144 with 113 pages per frame, 2 frames, one visible pixel per page
  • 32..248 with 217 pages per frame
  • 32..256 with 225 pages per frame

So page count alone was clearly not the whole story.

Grouped Preset Follow-Ups

This is where the "just use 3 sizes" theory broke down. Some 3-size groups worked. Some absolutely did not.

SizesResult
32,48,64Loads
72,96,128Loads
128,256Loads
96,128,256Fails
128,144,256Fails
128,160,256Fails
128,176,256Fails
128,192,256Fails
128,224,256Fails
72,96,112,128Fails
72,96,128,256Fails
96,128,192,256Fails
128,160,192,256Fails

So no, "3 sizes always works" was not true. Exact size choice still mattered.

Low-Range PNG-Only Follow-Ups

After all the weird mid-range and high-range failures, we went back and tested the low end more aggressively.

First, we ran a full PNG-only matrix on a 4px grid from 32 to 64:

  • 32,36,40,44,48,52,56,60,64
  • every 3-size combination
  • every 4-size combination
  • every 5-size combination
  • every 6-size combination
  • dense synthetic payload
  • tested at both 2 frames and 14 frames

That was 420 ANI files per frame-count pass, 840 real Windows loader checks total.

Result:

Size Count2 Frames14 Frames
384/84 passed84/84 passed
4126/126 passed126/126 passed
5126/126 passed126/126 passed
684/84 passed84/84 passed

So low-range PNG-only combinations were much more stable than the mid/high-range combinations.

Then we pushed a different low-range shape: a single ANI containing every consecutive size from 32 upward.

For that consecutive PNG-only ladder, the break point was:

  • 32..46 (15 entries) loaded
  • 32..47 (16 entries) failed

And that was true at both 2 frames and 14 frames.

Here is the full cutoff ladder from that test:

Consecutive SizesEntry Count2 Frames14 Frames
32..387LoadsLoads
32..398LoadsLoads
32..409LoadsLoads
32..4110LoadsLoads
32..4211LoadsLoads
32..4312LoadsLoads
32..4413LoadsLoads
32..4514LoadsLoads
32..4615LoadsLoads
32..4716FailsFails
32..4817FailsFails
32..4918FailsFails
32..5019FailsFails
32..5120FailsFails
32..5221FailsFails
32..5322FailsFails
32..5423FailsFails
32..5524FailsFails
32..5625FailsFails
32..5726FailsFails
32..5827FailsFails
32..5928FailsFails
32..6029FailsFails
32..6130FailsFails
32..6231FailsFails
32..6332FailsFails
32..6433FailsFails

So the low end turned out to have a much wider safe zone than we expected, but not an unlimited one.

High-Only Combos Above 128

We also ran a focused PNG-only scan using only sizes above 128. Every one of these failed at both 2 frames and 14 frames.

2-size high-only combos that failed

  • 144,160
  • 144,176
  • 144,192
  • 144,224
  • 144,256
  • 160,176
  • 160,192
  • 160,224
  • 160,256
  • 176,192
  • 176,224
  • 176,256
  • 192,224
  • 192,256
  • 224,256

3-size high-only combos that failed

  • 144,160,176
  • 144,160,192
  • 144,160,224
  • 144,160,256
  • 144,176,192
  • 144,176,224
  • 144,176,256
  • 144,192,224
  • 144,192,256
  • 144,224,256
  • 160,176,192
  • 160,176,224
  • 160,176,256
  • 160,192,224
  • 160,192,256
  • 160,224,256
  • 176,192,224
  • 176,192,256
  • 176,224,256
  • 192,224,256

Smaller Follow-Up Combos From The Same Research Thread

These were later spot checks on the same Windows 11 box. I am listing them separately because they were not part of the earlier saved harness tables, but they are still part of the research trail from this same project.

Reported as loaded in follow-up checks

  • 32,48,72
  • 32,48,96
  • 32,64,72
  • 32,64,96
  • 32,72,96
  • 32,48,64,72
  • 32,48,64,96
  • 32,48,72,96
  • 32,64,72,96
  • 32,48,128
  • 32,64,128
  • 32,64,96,128
  • 32,48,256
  • 32,64,256
  • 32,96,256
  • 32,48,64,256
  • 32,48,96,256
  • 32,64,96,256
  • 32,48,80,112
  • 32,56,80,104
  • 72,96,112
  • 80,96,128

Those later spot checks are part of why the site ended up standardizing on a short list of exact presets instead of letting people build whatever multi-size animated combination they want.

What We Are Doing Now

We started this whole thing hoping for one nice multi-size animated export that could cover everything from 32 to 256 in a single ANI.

We did not get that.

What we got instead was a more realistic rule:

  • keep animated multi-size ANI PNG-only
  • stop allowing random freeform animated size combinations
  • standardize around three animated size tiers
  • use standalone 256 as the extra-large fallback instead of trying to get fancy in the high range

Those three tiers are:

  • Default: 32,48,64
  • Large: 72,96,128
  • Extra Large: 256

That is also what the ingester/export rules reflect right now.

That still gives Windows something large to downscale from when it needs to, which is a safer bet than asking the ANI loader to accept a bunch of shaky grouped high-range presets.

So yeah, the final lesson is kind of annoying:

Windows does support multi-size animated cursors.

It just does not support them cleanly enough to trust a freeform exporter that our editor wanted. BTW, who's using cursor sizes 128x128 or higher?  Leave a comment if you are.

Sources

chat_bubble_outline

Comments

Loading comments...