Multi-density images
images command at a glancepurgetss images generates every Titanium density variant of your UI images (buttons, illustrations, screen graphics, logos) from a single source per image.
- Android:
res-mdpi,res-hdpi,res-xhdpi,res-xxhdpi,res-xxxhdpi(5 densities) - iPhone:
@1x,@2x,@3x(3 scales via filename suffix)
Works on Alloy and Classic projects. The layout is auto-detected.
This guide covers the purgetss/images/ convention, the 4× master convention, re-processing single files, and how it fits alongside build.
For a terse reference of every flag, see the images command reference.
Why multi-density?
Android's UI toolkit resolves images by density: a Pixel 7 (xxhdpi ≈ 3×) picks files from res-xxhdpi/, a low-end Moto (hdpi ≈ 1.5×) picks from res-hdpi/. If the right density isn't available, Android upscales the closest one, which looks noticeably blurry on high-DPI screens.
iOS uses the same idea with filename suffixes: icon.png, icon@2x.png, icon@3x.png. iPhone 15 Pro picks @3x, older iPads pick @2x.
Shipping every variant keeps your UI sharp on every device. purgetss images does it in one pass from a single source per image.
Quick start
Drop source images into purgetss/images/, then run the command:
> cp my-hero-illustration.png purgetss/images/
> cp my-button.svg purgetss/images/buttons/primary.svg
> purgetss images
Output in an Alloy project:
app/assets/
├── android/images/
│ ├── res-mdpi/
│ │ ├── my-hero-illustration.png
│ │ └── buttons/primary.svg
│ ├── res-hdpi/…
│ ├── res-xhdpi/…
│ ├── res-xxhdpi/…
│ └── res-xxxhdpi/…
└── iphone/images/
├── my-hero-illustration.png (@1x)
├── my-hero-illustration@2x.png
├── my-hero-illustration@3x.png
└── buttons/
├── primary.svg (@1x)
├── primary@2x.svg
└── primary@3x.svg
The purgetss/images/ convention
init creates purgetss/images/ (alongside fonts/ and brand/), so the folder is already there the first time you look for it, even before you've dropped any sources.
purgetss/images/
├── logo-screen.svg
├── hero.png
├── buttons/
│ ├── primary.png
│ └── secondary.png
└── icons/
└── home.svg
Supported input formats: .svg, .png, .jpg, .jpeg, .webp, .gif.
Subdirectories are preserved in the output. A file at purgetss/images/buttons/primary.png ends up at app/assets/android/images/res-*/buttons/primary.png and app/assets/iphone/images/buttons/primary.png. Organize however makes sense for your project.
SVG scales losslessly to every density. A single icon.svg rasterizes perfectly to every res-*dpi folder without pixel loss. PNG/JPG sources must be downscaled and lose a bit of sharpness at smaller densities.
Source sizes — the 4× convention
PurgeTSS (and Titanium Alloy generally) treats every source image as a 4× master (res-xxxhdpi / @xxxhdpi on Android, equivalent to @4x on iPhone if iOS supported it). All smaller densities are downscaled from that source.
That means:
| Source size (logical) | Use for |
|---|---|
| 1920×1080 or larger | Full-screen illustration / hero image |
| 800×800 | Card, list item, large icon |
| 200×200 | Button, inline icon |
| 96×96 | Small inline icon |
If your source is smaller than 4×, the tool still runs but the larger density outputs are essentially upscaled — quality drops on high-DPI devices.
Recommended sizes for common UI elements (in source pixels, assumed 4×):
- Full-screen illustration: at least
1920×1080 - Tab bar icon: at least
192×192(source for 48px at xxxhdpi) - List-row thumbnail: at least
320×320 - Button background: match the intended display size × 4
The images: config section
On the first run, purgetss images injects an images: block into your existing purgetss/config.cjs (between brand: and theme:) with these defaults:
images: {
quality: 85, // JPEG/WebP/AVIF quality (0-100)
format: null, // null = keep original; 'webp' | 'jpeg' | 'png' to convert every image
confirmOverwrites: true // prompt before overwriting files (set false to skip)
}
Change whatever you want to override globally; CLI flags still win for one-off runs.
Overwrite confirmation
images writes directly into the project, so it asks before overwriting anything:
Continue? [y/N/a]
y/yes— write this timeN/no/Enter— abort (nothing is written)a/always— write, then addconfirmOverwrites: falseto theimages:section ofconfig.cjsso the prompt stays quiet on future runs
The prompt is skipped automatically when:
stdinis not a TTY (thealloy.jmkhook, CI, a pipe)- you pass
-y/--yes— one-shot bypass PURGETSS_YES=1is set in the environment — lasts the whole shell sessionconfirmOverwrites: falseis already in theimages:config
> purgetss images -y # skip prompt once
> PURGETSS_YES=1 purgetss images # skip for the whole session
Re-processing a single file or subfolder
Common workflow: you tweaked one image in Affinity or Figma and only want to regenerate that one, not the whole folder.
Pass its path directly:
> purgetss images buttons/primary.png
Short paths auto-resolve against purgetss/images/, so you don't need to type purgetss/images/buttons/primary.png. The command tries two interpretations:
purgetss/images/buttons/primary.png(matches the convention folder)../buttons/primary.png(fallback, relative to the project root).
Subdirectory structure is preserved in the output whenever the source lives inside purgetss/images/, whether you pass the full folder, a subfolder, or a single file. Re-processing one file produces the same output path it had in a full run.
Re-process a whole subfolder
> purgetss images buttons/
Walks purgetss/images/buttons/ recursively and regenerates every image inside. Everything outside stays untouched.
Pointing to sources outside the convention
If your source images live elsewhere (e.g. next to your design files in docs/screenshots/), pass an absolute or cwd-relative path:
> purgetss images ./docs/screenshots/home-hero.png
> purgetss images /Users/cesar/Design/banner.svg
When the source is outside purgetss/images/, subdirectory preservation uses the directory of the source file as the root instead.
Platform filter
By default, every run generates both Android densities and iPhone scales. Scope to one platform for targeted runs:
> purgetss images --android # Android only (skip iPhone)
> purgetss images --ios # iPhone only (skip Android)
Useful when:
- You're iterating on an iOS-only screen and don't need to regenerate Android assets every time.
- You want to tune JPEG quality differently for the two platforms (run the command twice with different flags).
The two flags are mutually exclusive. Pass neither to get both.
Format conversion
The default preserves each source's format: drop in .png, get out .png; drop in .jpg, get out .jpg. Add --format <ext> to convert every output to a single target format:
> purgetss images --format webp # convert every output to WebP
> purgetss images --format jpeg --quality 90
Valid targets: webp, jpeg, png, avif, gif, tiff.
Why WebP?
WebP produces ~25-35% smaller files than JPEG at similar visual quality, and Titanium supports it natively on both platforms. For a large UI asset library, switching to WebP can shave several MB off your APK/IPA.
> purgetss images --format webp --quality 85
Keep --format null (the default) when you need to stay in the same format as the source, for example PNG with alpha that shouldn't be flattened.
Full pipeline alongside build
The typical sequence when iterating on an app:
# 1. Edit your source images in Affinity/Figma, drop into purgetss/images/
# 2. Regenerate the variants
> purgetss images
# 3. Run purgetss build to regenerate utilities.tss if you changed classes
> purgetss build
# 4. Build the app
> ti build -p android -T emulator
If you only tweaked CSS classes (no image changes), you don't need to re-run purgetss images. It's safe to skip.
Cleaning up
purgetss images never deletes files. It only creates them. If you remove an image from purgetss/images/, the previously-generated copies in app/assets/android/images/res-*/ and app/assets/iphone/images/ stay in place. Remove them manually (or via git) when you clean up.
Troubleshooting
The output is blurry on high-DPI devices
Your source is likely smaller than the 4× master convention. A larger source means sharper output across all densities. Aim for at least 4× the intended display size, or use SVG sources when possible.
JPG output has a white background instead of transparency
JPEG doesn't support alpha channels. If your source is PNG with transparency and you convert to JPEG (via --format jpeg), the alpha gets flattened on white. To preserve transparency, keep the format as PNG or WebP:
> purgetss images --format webp # supports alpha
> purgetss images --format png # keeps alpha
My subdirectories aren't preserved in the output
Verify your source path is inside purgetss/images/. When passing sources from outside the convention (e.g. ./docs/screenshots), the directory of the source file is used as the root, so a file at ./docs/screenshots/hero.png outputs to images/hero.png (flat), not images/screenshots/hero.png.
Move the file into purgetss/images/screenshots/ if you want subdirectory preservation.
I want to preview what would happen before writing files
> purgetss images --dry-run
Shows every file that would be written, no side effects.
Can I use this for app icons?
No. App icons need a different pipeline (adaptive icons, mask safe-zones, marketplace flattening, iOS 18+ Dark/Tinted). Use purgetss brand for the launcher icon.
purgetss images is for the UI assets inside your screens: buttons, backgrounds, illustrations, inline icons.