Shrink large textures in Koikatsu files
  • Python 99.6%
  • Batchfile 0.4%
Find a file
2026-06-28 04:34:39 -04:00
kkpy chore: fix copy 2026-06-28 04:34:39 -04:00
.gitignore feat: initial import 2026-05-16 15:49:18 -04:00
config.yml feat: add HS2 support, Texture Index/Search, and GUI workflow overhaul 2026-06-28 04:22:40 -04:00
gui_app.py feat: reimplement KK file processing without kkloader 2026-06-13 19:04:19 -04:00
LICENSE feat: initial import 2026-05-16 15:49:18 -04:00
main.py style: apply ruff format 2026-06-28 04:22:40 -04:00
pyproject.toml chore: version bump 2026-06-28 04:34:39 -04:00
README.md feat: add HS2 support, Texture Index/Search, and GUI workflow overhaul 2026-06-28 04:22:40 -04:00
run-gui.bat feat: GUI (#3) 2026-05-24 07:18:19 +00:00
uv.lock chore: version bump 2026-06-28 04:34:39 -04:00

KKPy

A texture optimizer for Koikatsu game files. Reduces texture sizes by resizing large textures to a maximum dimension and optimizing PNG compression. The GUI can also batch-externalize Material Editor textures to on-disk LOCAL storage.

GUI Application

KKPy includes a graphical user interface for easy texture optimization without command-line usage.

Quick Start

Windows:

  1. Download the project files
  2. Double-click run-gui.bat
  3. The script will:
    • Check if UV is installed (and install it if needed)
    • Set up the project files
    • Launch the GUI application

Linux:

uv run gui_app.py

GUI Features

The GUI provides tabs: Process, Extract Textures, Embed Textures, Texture Index, and Config.

Process Tab

Select files or directories to optimize with the following options:

Feature Description
Input/Output Browse for input and output paths, or drag-and-drop files
File Type Auto-detect or manually select (chara, scene, coord, etc.)
In-place Modify files directly without creating copies
Recursive Process subdirectories when input is a directory

Progress Monitoring:

  • Real-time progress bar with file counter
  • Elapsed time display
  • Summary statistics (textures processed, bytes saved)
  • ANSI color support in log output

Extract Textures Tab

Batch-convert Material Editor textures from bundled or deduped storage to LOCAL format so the same texture bytes can be reused across scenes and character cards.

Feature Description
Input/Output Same as Process tab — browse, drag-and-drop, in-place, recursive
Optimize textures Optional resize/compress pass using Config tab settings (config.yml)
Local texture pool Writes MaterialEditor/_LocalTextures/ME_LocalTex_{hash}.* to UserData, the batch output directory, or both (see output mode below)
Per-file mirror When output mode is Output directory, also copies required ME_LocalTex_* files beside each output PNG
Scene characters Externalizes ME data on embedded scene characters, not just scene-level mod_data
Coordinate files Skipped with a warning — use the Process tab to compress coordinate PNGs
Deduped-only character cards Skipped with a warning — bytes live in the studio scene DEDUPED_TextureDictionary_DATA pool; extract from the scene PNG
Incomplete ME maps Files fail when deduped/LOCAL id maps reference textures that cannot be loaded (missing pool bytes or ME_LocalTex_* files)

Output format: Character and scene PNGs store LOCAL_TextureDictionary (id→hash map) with PluginData.version = 2. Texture bytes live on disk, not inside the PNG.

Local texture pool: ME_LocalTex_{hash} files are content-addressed. kkPy follows Material Editor write-if-missing — an existing file at the same hash path is never overwritten. When optimize is enabled, kkPy recomputes expected landed bytes from source data plus config.yml rules and reuses on-disk files when hash and bytes match. The optional com.kkpy.texture_history metadata in output PNGs is a fast-path cache; correctness does not depend on it.

Cancellation: Stopping an Extract batch (GUI Cancel or interrupt) may leave a partial result: some PNGs rewritten to LOCAL, local pool files written, later files unchanged. Re-run Extract on the same folder; already-LOCAL cards are handled idempotently.

In-game setup: Copy ME_LocalTex_* files from the batch output (local texture pool or per-PNG mirror) into your games UserData/MaterialEditor/_LocalTextures/. If Material Editors Local Texture Path Override is set, use that folder instead.

Embed Textures Tab

Re-bundles LOCAL ME textures from _LocalTextures back into PNG TextureDictionary data (inverse of Extract Textures). Character, scene (including embedded characters), and coordinate PNGs that contain LOCAL_TextureDictionary are supported. Extract Textures skips coordinate files because Material Editor loads coordinate ME textures from bundled data only; Embed restores bundled bytes when LOCAL refs are present.

Embed does not resize or recompress textures.

Texture Index Tab

Read-only diagnostics that map LOCAL Material Editor texture references on character/scene cards against the UserData MaterialEditor/_LocalTextures pool. Use after Extract Textures to find missing pool files (card refs a hash with no on-disk ME_LocalTex_*) and orphan pool files (on disk but not referenced by any indexed card).

Feature Description
Scan root Drag-drop a folder or leave empty to scan UserData chara + studio/scene
Game scope Partitioned by active game from the Game menu (KK / KKS; HS2 blocked)
Multi-root Index accumulates across sessions; only the folder scanned this time is pruned on re-index
HTML report Summary, missing/orphan lists, file-type breakdown, ref-count histogram
Index workers Config tab index_workers (12) parallelizes KKEx parse on cache misses

The SQLite database is texture_index.sqlite beside your config file (KKPY_TEXTURE_INDEX_PATH to override). Legacy texture_orphan_index.sqlite is still read if present. Re-scan after Embed Textures or when you change the UserData path (resets that game's partition).

Memory and large batches

Extract Textures builds a cross-file texture pool per chunk (size set by batch_chunk_size). Embed Textures re-reads each PNG per chunk and deduplicates loaded bytes in memory (TextureByteIntern) but does not retain a global extract pool. For very large libraries:

Guidance Recommendation
Whole collections Prefer Process (per-file) or split input into subfolders
batch_chunk_size Default 1000 in config.yml — lower (e.g. 200) on RAM-limited machines; reduces the peak memory estimate
GUI warning Appears when estimated peak per-chunk texture data exceeds ~8 GB (total batch volume is shown for context); sampling uses up to 20 files
Workers Automatically reduced above 500 files or ~8 GB estimated peak texture volume per chunk
Extract without optimize Streams unique textures to _LocalTextures during extract to limit peak RAM

The Process tab is the safest choice for multi-terabyte folders because it does not build a cross-file global texture pool.

Config Tab

Resolution rules fill the tab; optimization settings, paths, and save/load sit in a footer at the bottom.

Section Description
Optimization Max texture size, compression, resize filter, workers, index scan workers, always optimize, verbosity
UserData folder Per-game UserData paths (Koikatsu, Sunshine, HS2) — select active game from the Game menu; kkPy loads ME local textures from MaterialEditor/_LocalTextures (does not affect optimization history hashes)
Resolution rules Per-texture rules table — add, edit, reorder, filter

Rule properties: name, max size (or skip), property/material globs, object type (04).

Additional Features

  • Dark/Light Mode: Toggle via View menu
  • Game selection: Koikatsu / Koikatsu Sunshine / HS2 via Game menu (per-game UserData paths)
  • Drag-and-Drop: Drop files directly onto the application window

Requirements

  • Python: 3.14 or higher
  • Package Manager: uv (recommended) or pip

Game / Material Editor (Extract Textures)

kkPy can read bundled and deduped ME data from older cards offline. Loading Extract Textures output in-game requires a recent Material Editor build:

Requirement Version Notes
Minimum Material Editor 3.13.5 (KK_Plugins v268) First release with LOCAL_TextureDictionary and ME_LocalTex_* disk loading
Recommended Material Editor 4.0+ Matches PluginData.version = 2 that kkPy writes

Update KKAPI / Modding API from the same modpack release when upgrading Material Editor.

Installation

# Clone the repository
git clone <repo-url>
cd kkPy

# Install with uv (recommended)
uv sync

# Or install with pip
pip install -e .

Note: For 2-4x faster image operations, install pillow-simd instead of pillow:

pip install pillow-simd

Supported File Types

Type Description
chara / character Character files
scene Scene files
coord / coordinate Coordinate/clothes files

Usage

Process a single file

uv run main.py input.png output.png

Process a directory

uv run main.py input_dir output_dir

Auto-detect file type

uv run main.py input.png output.png

Specify file type explicitly

uv run main.py -t chara input.png output.png

In-place modification

# Single file (will prompt for confirmation)
uv run main.py -i input.png

# Directory (will prompt for confirmation)
uv run main.py -i input_dir

Command-line Options

usage: main.py [-h] [-t {chara,scene,coord,scene_kk,scene_kks}]
               [-v] [-j JOBS] [-r] [-i] [--max-size MAX_SIZE]
               [--compress-level COMPRESS_LEVEL]
               [--resize-filter {nearest,bilinear,bicubic,lanczos}]
               [--always-optimize]
               [--config CONFIG]
               input output

Load KK files, optimize large textures in materialeditor data, and save.

positional arguments:
  input                 Path to input file or directory
  output                Path to output file or directory (optional with --in-place)

options:
  -h, --help            show this help message and exit
  -t, --type            Type of file to process. If not provided, will be
                        auto-detected from file content.
  -v, --verbose         Show detailed progress messages
  -j, --jobs            Number of parallel jobs for directory processing
                        (default: auto)
  -r, --recursive       Search subdirectories recursively when processing
                        a directory
  -i, --in-place        Modify files in place (output path becomes optional,
                        will prompt for confirmation)
  --max-size            Maximum texture dimension in pixels (default: 2048)
  --compress-level      PNG compression level, 0-9 (default: 6, higher =
                        smaller but slower)
  --resize-filter       Resize filter algorithm (default: bilinear)
  --always-optimize     Optimize textures even if under max resolution
                        (only if smaller)
  --config              Path to YAML config file for per-texture resolution
                        rules (default: config.yml)

Option Details

Option Shorthand Default Description
--type -t auto-detect File type (chara, scene, coord, etc.)
--verbose -v false Show detailed progress messages
--jobs -j CPU count Number of parallel workers
--recursive -r false Process subdirectories recursively
--in-place -i false Modify files in place (prompts for confirmation)
--max-size 2048 Maximum texture dimension in pixels
--compress-level 6 PNG compression level (0-9)
--resize-filter bilinear Resize algorithm (nearest, bilinear, bicubic, lanczos)
--always-optimize -ao false Optimize even if under max size (if smaller)
--config config.yml Path to YAML config file for per-texture resolution rules

Configuration

KKPy can be configured using a YAML file (config.yml by default) for per-texture resolution rules. This allows different maximum sizes for different texture types based on their properties.

Configuration File Format

# Default settings for textures that don't match any rule
default_max_size: 2048    # Maximum dimension in pixels
compress_level: 6         # PNG compression level (0-9)
resize_filter: bilinear # Resize algorithm: nearest, bilinear, bicubic, lanczos
always_optimize: false    # Optimize textures even if under max resolution
max_workers: 4            # Number of parallel workers

# Resolution rules - evaluated in order, first match wins
rules:
  - name: "Rule name"
    max_size: 1024        # Maximum dimension for matching textures
    match:
      property: "DetailMask"     # Glob pattern for texture property (e.g., "MainTex", "Normal*")
      material_name: "cf_m_hair_f_*"  # Glob pattern for material name (optional)
      object_type: 2          # Exact object type ID (optional)

Top-Level Settings

Setting Type Default Description
default_max_size int 2048 Default maximum texture dimension for textures not matching any rule
compress_level int 6 PNG compression level (0-9, higher = smaller but slower)
resize_filter string bilinear Resize algorithm: nearest, bilinear, bicubic, or lanczos
always_optimize bool false Optimize textures even if under max resolution (if smaller)
max_workers int 4 Number of parallel workers for texture processing
batch_chunk_size int 1000 Extract/Embed batch chunk size (files per in-memory pool cycle)
active_game string koikatsu Active game for UserData resolution: koikatsu, sunshine, or hs2
userdata_paths map (unset) Per-game UserData folders for loading MaterialEditor/_LocalTextures (excluded from optimization hashes). Legacy userdata_path alone is treated as koikatsu on load.

Resolution Rules

Rules are evaluated in order; the first matching rule determines the max_size for a texture. Each rule supports the following match criteria:

Match Field Type Description
property string (glob) Texture property name pattern (e.g., MainTex, Normal*, Shadow*)
material_name string (glob) Material name pattern (e.g., Skin*, Cloth*)
object_type int or list[int] Object type ID(s) to match exactly

All specified match conditions must be satisfied (AND logic).

Object Types

The object_type field in resolution rules matches against the ObjectType value in the MaterialTexturePropertyList. The following object types are defined in the MaterialEditor plugin:

Object Type Description
0 Unknown (reserved)
1 Clothing/Body textures
2 Accessory textures
3 Hair textures
4 Character (face/body parts)

Example Configuration

# Matcap textures - typically used for lighting effects
- name: "Matcap textures"
  max_size: 512
  match:
    property: "Matcap"

# Shadow textures - don't need high resolution
- name: "Shadow textures"
  max_size: 512
  match:
    property: "Shadow*"

# Normal maps - can be lower resolution
- name: "Normal maps"
  max_size: 1024
  match:
    property: "Normal*"

# Accessory textures (ObjectType 2) - often need higher resolution
- name: "Accessory textures"
  max_size: 2048
  match:
    object_type: 2

Development

Setup

# Install with development dependencies
uv sync --extra dev

# Or with pip
pip install -e ".[dev]"

Running Tests

# Run all tests
uv run pytest

# Run with coverage
uv run pytest --cov=kkpy

# Run specific domain
uv run pytest tests/pipeline

Code Quality

# Run linting
uv run ruff check .

# Run type checking
uv run ty check .

# Format code
uv run ruff format .