A Python script to convert animated GIFs into 1024×1024 PNG spritesheets of various layouts, with letterboxing to preserve the original aspect ratio of each frame. Ideal for workflows where you need a single atlas of frames for VRChat, game engines, or other applications.
-
Auto Mode
Automatically chooses between 2×2, 4×4, or 8×8 layouts based on the number of frames:- Up to 4 frames: 2×2 (4 slots)
- Up to 16 frames: 4×4 (16 slots)
- Otherwise: 8×8 (64 slots)
-
Discarding / Repeating Logic
- If there are more frames than the capacity, the script discards frames based on your chosen method (consecutive or percentage).
- If there are fewer frames than the capacity, the script finds the largest divisor of the layout capacity (\leq) the original frame count, discards any excess frames accordingly, and repeats those frames to fill the entire sheet.
-
Frame Selection Methods
- Consecutive: Takes frames in a continuous block from the start.
- Percentage: Evenly samples frames across the entire GIF duration.
-
Letterboxing
Rather than stretching or squashing frames, each frame is resized proportionally to fit within its cell, and the remaining space is filled with transparency. -
1024×1024 Output
Outputs a single PNG spritesheet of size 1024×1024.
-
Basic Usage (auto mode, consecutive selection):
python imageConverter.py \ --input input.gif \ --mode auto \ --method consecutive \ --output output.png
-
Manual Layout (4×4, percentage-based sampling):
python imageConverter.py \ --input example.gif \ --mode 4x4 \ --method percentage \ --output spritesheet.png
- Clone the Repository
git clone https://github.com/P529/vrchatgifconverter.git cd vrchatgifconverter
- Install Requirements
We recommend using virtual environments, but it’s optional.pip install -r requirements.txt
- Run the Script
python imageConverter.py --input input.gif --mode auto --method consecutive --output spritesheet.png
- Python 3.7+
- Pillow 9.1+ (or you can use an older Pillow version if you update the code to use
Image.ANTIALIAS
instead ofResampling.LANCZOS
)
A sample requirements.txt might look like:
Pillow>=9.1.0
(Adjust or pin exact versions as needed.)
Argument | Default | Choices | Description |
---|---|---|---|
--input |
(required) | N/A | Path to the source GIF file. |
--mode |
auto |
2x2 , 4x4 , 8x8 , auto |
Layout mode. auto picks the layout based on the frame count. |
--method |
consecutive |
consecutive , percentage |
How to sample frames if discarding is needed. consecutive takes frames from the start, percentage evenly samples. |
--output |
spritesheet.png |
N/A | Output PNG filename. |
-
Determine Layout
- If
--mode auto
, we pick 2×2 (4 slots) if there are 4 or fewer frames, 4×4 (16 slots) if ≤16 frames, otherwise 8×8 (64 slots).
- If
-
Get Frames
- Extracts frames from the GIF via
PIL.ImageSequence.Iterator
, converting each to RGBA.
- Extracts frames from the GIF via
-
Discard / Repeat
- If more frames than the sheet capacity, we discard down to that capacity using either consecutive or percentage.
- If fewer frames, we find the largest divisor of the capacity that is ≤ the frame count, sample frames down to that divisor if necessary, then replicate to fill the entire sheet.
-
Letterbox
- Each frame is resized proportionally to fit the tile size (512×512 for 2×2, 256×256 for 4×4, 128×128 for 8×8), centered on a transparent background so there’s no aspect-ratio distortion.
-
Sprite Assembly
- Frames are placed row-by-row on a 1024×1024 RGBA canvas.
-
Save
- The resulting sprite atlas is saved as a PNG file.
-
Case 1: A GIF with 9 frames,
--mode auto
,--method consecutive
- Auto chooses 4×4 (16 slots).
- 9 < 16 → largest divisor ≤9 for 16 is 8 → discard 1 frame (keep 8) → replicate 2× → 16 frames total.
-
Case 2: A GIF with 26 frames,
--mode auto
,--method percentage
- Auto chooses 8×8 (64 slots).
- 26 < 64 → largest divisor ≤26 for 64 might be 16 → sample 16 frames evenly → replicate 4× to 64 total.
-
Pillow Version Conflicts: If you get an error like
module 'PIL.Image' has no attribute 'Resampling'
, try upgrading Pillow:pip install --upgrade Pillow
Or modify the code to use
Image.ANTIALIAS
if you’re on an older version. -
Missing Frames: If you unexpectedly discard frames, switch your method to
percentage
to spread out the sampling. -
Aspect Ratio: If you see black bars or distortion, verify letterboxing is enabled. The script uses a transparent background in the unused space, so your frames stay undistorted.