macOS: Only integer scale factors at app level. Can stretch final composited framebuffer at very end for non-integer display scales.
Android: Many (but discrete) scale levels (ldpi (0.75x), mdpi, hdpi (1.5x), xhdpi (2x), xxhdpi (3x), xxhdpi (4x))
Windows: has "not recommended" free form text entry for scale factor between 100% and 500%.
Integer scale factors are needed in any case so let's get that working first. Actually, let's focus on just 2x for now.
physical_
. Physical coordinates should as much as possible not cross API boundaries.Resources such as icons, cursors, bitmap fonts are scale-dependent: In HighDPI modes, different resources need to be loaded.
A 2x resource should look like a 1x resource, just with less jagged edges. A horizontal or vertical line that's 1 pixel wide in 1x should be 2 pixels wide in 2x.
A good guideline for black-and-white images: start with a 1x bitmap, resize it to 200% using nearest-neighbor filtering, and then move black pixels around to smooth diagonal edges -- but the number of black pixels shouldn't change relative to the 200% nearest-neighbor-resampled image. If that's not possible, err towards making the icon smaller instead of larger. A good technique is to use the Ctrl-Shift-Super-I shortcut in HighDPI mode to toggle between low-res and high-res icons in HighDPI mode.
While a 1x 32x32 bitmap and a 2x 16x16 bitmap both have 32x32 pixels, they don't have to look the same: The 2x 16x16 should look exactly like the corresponding 1x 16x16, just with smoother edges. The 1x 32x32 pixel resource could instead pick a different crop. As a concrete example, the 1x 7x10 ladybug emoji image currently just has the ladybug's shell (for space reasons), and so should the 2x version of that emoji. On the other hand, if we add a higher-res 1x 14x20 ladybug emoji at some point, we might want show the ladybug's legs on it, instead of just a smoother rendition of just the shell. (The 2x version of that 14x20 emoji would then have legs and shell in less jagged.)
currently:
res/
cursors/
arrowx2y2.png
...
emoji/ (currently all 7x10 px)
U+1F346.png
...
fonts/
CsillaRegular10.font
...
graphics/
brand-banner.png
...
icons/
16x16/
small app icons, small filetype icons, toolbar icons, window buttons, ...
32x32/
large app icons, large filetype icons, message box icons
various per-app folders with in-app UI images (XXX: maybe move into "apps" subdir?)
themes/
Coffee/
16x16/
custom window buttons
(more themes)
...
wallpapers/
desktop wallpapers
Every one of these could grow a 2x variant (and if we do more scale factors later, even more variants).
Possible new structures:
Have "1x", "2x" folders right inside res and then mirror folder structures inside them:
res/
1x/
cursors/
16x16/
emoji/
...
2x/
cursors/
16x16/
emoji/
...
Instead of having the 1x/2x fork at the root, have it at each leaf:
res/
cursors/
1x/
arrowx2y2.png
...
2x/
arrowx2y2.png
...
emoji/
1/
U+1F346.png
...
2/
U+1F346.png
...
...
Use filename suffixes instead of directories (similar to macOS):
res/
cursors/
arrowx2y2.png
arrowx2y2@2x.png
...
emoji/
U+1F346.png
U+1F346@2x.png
...
Use suffixes on directory instead of subdirectory:
res/
cursors/
arrowx2y2.png
...
cursors-2x/
arrowx2y2.png
...
Root-level split makes it easy to see which scale factors exist and is subjectively aesthetically pleasing.
Filename suffixes make it easy to see which icons don't have high-res versions (but in return clutter up an icon directory), and it makes it easy to get the intrinsic scale factor of a bitmap (just need to look at the image's basename, not at any directory).
Android has additional modifiers in addition to scale factors in its resource system (UI language, light/dark mode, screen size in addition to resolution, etc). If we ever add more factors to the resource system, a suffix-based system would probably extend more nicely than a nesting-based one.
In the end probably doesn't matter all that much which version to pick.
For now, we're going with a "-2x" suffix on the file name.
eagerly load one scale, reload at new scale on scale factor change events
have BitmapCollection that stores high-res and low-res path and load lazily when needed
eagerly load both and use the right one at paint time
This isn't figured out yet, for now we're doing the first approach in select places in the window server.
Currently:
auto app_icon = GUI::Icon::default_icon("app-playground");
or
s_unfilled_circle_bitmap = Bitmap::load_from_file("/res/icons/serenity/unfilled-radio-circle.png");
or
header.set_font(Gfx::Font::load_from_file("/res/fonts/PebbletonBold14.font"));
Going forward:
FIXME (depends on loading strategy decision a bit?)
The plan is to have all applications use highdpi backbuffers eventually. It'll take some time to get there though, so here's a plan for getting there incrementally.
We're currently in the middle of point 3. Some window server icons are high-resolution, but fonts aren't yet, and in-window-server things with their own backing store (eg menus) aren't yet either.