Skip to content

Commit 947a630

Browse files
committed
Versión Estable v2.0.0
- Se migró el proyecto a un estado híbrido PySide6/PySide2 mediante `ui/qt_compat.py`, que detecta automáticamente el backend Qt disponible según la versión de Python. Esto añade compatibilidad con Windows 7/8/8.1 usando Python 3.8 + PySide2 5.15.2, sin afectar el funcionamiento en Python 3.10+ con PySide6 - Se actualizó `requirements.txt` para instalar automáticamente el backend Qt correcto según la versión de Python (PySide6 para Python ≥ 3.10, PySide2 5.15.2 para Python < 3.10) - Corregida la errata "Save Tilset" → "Save Tileset" en el menú Tileset - Movidos los archivos README localizados a la carpeta `docs/` - Corregido el comportamiento de "Save Tileset", "Save Tilemap" y "Save Selection" que apuntaban al directorio de la aplicación en lugar del último directorio utilizado o la carpeta Documentos - Corregido el orden de dimensiones en el diálogo Open Tilemap: los tamaños 32×N ahora aparecen antes que los 20×N - Corregidos errores en conversiones a 4bpp causados por el reemplazo del color transparente cuando coincidía con entradas de la paleta - `last_image_directory` ahora se actualiza en memoria en cada operación de archivo, independientemente de la opción "Remember File Paths", para usarse como directorio por defecto en los diálogos de la sesión actual - Corregido el botón "Select Area" que dejaba de funcionar tras usar "Save Selection": el área de selección no se redibujaba y los botones complementarios (Mirror, Flip, Swap, Replace, etc.) no se reactivaban - Corregido el comportamiento que activaba el modo de dibujo en el tilemap al confirmar "Save Selection" con un área seleccionada - "Save Selection" ahora permanece desactivado hasta que se seleccione un área; las dimensiones del área seleccionada se muestran en la barra de estado en tiempo real - Exportar ahora solicita confirmación antes de sobrescribir archivos existentes - Corregido el desplazamiento inesperado del scroll del tilemap hacia arriba al aplicar un relleno con un área seleccionada - Al reducir el tilemap ahora se muestra una máscara en tiempo real indicando el área que será recortada - El preview del diálogo de conversión ahora muestra las dimensiones finales de la imagen en tiempo real, recortando o rellenando con tiles transparentes según corresponda - Corregido el error "translator not defined" al intentar mover el tilemap con los botones de desplazamiento
1 parent 51dd678 commit 947a630

75 files changed

Lines changed: 1917 additions & 964 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

GBA_Studio_Wizard.bat

Lines changed: 58 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,63 @@
11
@echo off
22
setlocal enabledelayedexpansion
3-
title GBA Background Studio - Language Selection
3+
title GBA Background Studio - Wizard & Setup
44
color 0F
55

6+
:: ======================================================
7+
:: SECTION 1: PYTHON ENVIRONMENT CHECK
8+
:: ======================================================
9+
echo ======================================================
10+
echo CHECKING EXECUTION ENVIRONMENT...
11+
echo ======================================================
12+
13+
python --version >nul 2>&1
14+
if %errorlevel% neq 0 (
15+
echo [ERROR] Python is not installed or not in PATH.
16+
pause
17+
exit
18+
)
19+
20+
:: Get Python Lib path
21+
for /f "tokens=*" %%a in ('python -c "import sys; print(sys.prefix)"') do set "PY_PATH=%%a"
22+
23+
:: ======================================================
24+
:: SECTION 2: PHYSICAL DEPENDENCY CHECK
25+
:: ======================================================
26+
echo Checking core libraries in: %PY_PATH%
27+
28+
set "MISSING="
29+
30+
if not exist "%PY_PATH%\Lib\site-packages\numpy" set "MISSING=1"
31+
if not exist "%PY_PATH%\Lib\site-packages\PIL" set "MISSING=1"
32+
if not exist "%PY_PATH%\Lib\site-packages\sklearn" set "MISSING=1"
33+
if not exist "%PY_PATH%\Lib\site-packages\cv2" set "MISSING=1"
34+
35+
if defined MISSING (
36+
echo [INFO] Dependencies not found. Initializing installation...
37+
echo.
38+
39+
python -m ensurepip --upgrade >nul 2>&1
40+
python -m pip install --upgrade pip
41+
42+
echo [INFO] Installing requirements.txt...
43+
python -m pip install -r requirements.txt
44+
45+
if !errorlevel! neq 0 (
46+
echo.
47+
echo [ERROR] Installation failed.
48+
echo Please run: pip install -r requirements.txt manually.
49+
pause
50+
exit
51+
)
52+
echo [OK] Environment successfully configured.
53+
timeout /t 3 >nul
54+
) else (
55+
echo [OK] All core libraries detected.
56+
)
57+
58+
:: ======================================================
59+
:: SECTION 3: LANGUAGE SELECTION MENU
60+
:: ======================================================
661
:MENU
762
cls
863
echo ======================================================
@@ -35,12 +90,8 @@ if "%L%"=="9" call "scripts\convert_tur.bat"
3590
if "%L%"=="10" call "scripts\convert_pol.bat"
3691
if "%L%"=="11" call "scripts\convert_vie.bat"
3792
if "%L%"=="12" call "scripts\convert_ind.bat"
38-
if "%L%"=="13" call "scripts\convert_eng.bat"
39-
if "%L%"=="14" call "scripts\convert_eng.bat"
40-
if "%L%"=="15" call "scripts\convert_eng.bat"
41-
if "%L%"=="16" call "scripts\convert_eng.bat"
42-
if "%L%"=="17" call "scripts\convert_eng.bat"
43-
if "%L%"=="18" call "scripts\convert_eng.bat"
93+
94+
if "%L%" geq "13" if "%L%" leq "18" call "scripts\convert_eng.bat"
4495

4596
if "%L%"=="0" exit
4697
goto MENU

README.md

Lines changed: 37 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,8 @@
1414
This README is available in the following languages:
1515

1616
<p align="center">
17-
<a href="README.md">English</a> | <a href="README.spa.md">Español</a> | <a href="README.brp.md">Português (BR)</a> | <a href="README.fra.md">Français</a> | <a href="README.deu.md">Deutsch</a> | <a href="README.ita.md">Italiano</a> | <a href="README.por.md">Português</a> | <a href="README.nld.md">Nederlands</a> | <a href="README.pol.md">Polski</a><br>
18-
<a href="README.tur.md">Türkçe</a> | <a href="README.vie.md">Tiếng Việt</a> | <a href="README.ind.md">Bahasa Indonesia</a> | <a href="README.hin.md">हिन्दी</a> | <a href="README.rus.md">Русский</a> | <a href="README.jpn.md">日本語</a> | <a href="README.zhs.md">简体中文</a> | <a href="README.zht.md">繁體中文</a> | <a href="README.kor.md">한국어</a>
17+
<a href="README.md">English</a> | <a href="docs/README.spa.md">Español</a> | <a href="docs/README.brp.md">Português (BR)</a> | <a href="docs/README.fra.md">Français</a> | <a href="docs/README.deu.md">Deutsch</a> | <a href="docs/README.ita.md">Italiano</a> | <a href="docs/README.por.md">Português</a> | <a href="docs/README.nld.md">Nederlands</a> | <a href="docs/README.pol.md">Polski</a><br>
18+
<a href="docs/README.tur.md">Türkçe</a> | <a href="docs/README.vie.md">Tiếng Việt</a> | <a href="docs/README.ind.md">Bahasa Indonesia</a> | <a href="docs/README.hin.md">हिन्दी</a> | <a href="docs/README.rus.md">Русский</a> | <a href="docs/README.jpn.md">日本語</a> | <a href="docs/README.zhs.md">简体中文</a> | <a href="docs/README.zht.md">繁體中文</a> | <a href="docs/README.kor.md">한국어</a>
1919
</p>
2020

2121
---
@@ -69,7 +69,7 @@ This README is available in the following languages:
6969

7070
## 🏗️ Architecture Overview
7171

72-
GBA Background Studio is built with **Python** and **PySide6**, and follows a modular UI design:
72+
GBA Background Studio is built with **Python** and **PySide6 / PySide2** (hybrid), and follows a modular UI design:
7373

7474
- **Main window (`GBABackgroundStudio`)**
7575
- Manages application state (current BPP, zoom level, tile and palette selection).
@@ -93,38 +93,54 @@ GBA Background Studio is built with **Python** and **PySide6**, and follows a mo
9393

9494
## 📦 Installation
9595

96-
### Requirements
97-
- **Python** (3.12+ recommended)
98-
- **Pip** (Python package manager)
99-
- **Operating System Support for PySide6:**
100-
- **Windows:** Windows 10 (Version 1809) or later.
101-
- **macOS:** macOS 11 (Big Sur) or later.
102-
- **Linux:** Modern distributions with glibc 2.28 or later (e.g., Ubuntu 20.04+, Debian 11+).
96+
### Option 1 — Installer (recommended)
10397

104-
### Dependencies
105-
Core dependencies include:
106-
- `PySide6` (Qt for Python) - *Note: Requires the OS versions mentioned above.*
107-
- `Pillow` (PIL) for image processing
98+
Download the latest release from [GitHub Releases](https://github.com/CompuMaxx/gba-background-studio/releases):
99+
100+
| Installer | Python | OS |
101+
|---|---|---|
102+
| `GBABackgroundStudio_Setup.exe` | Bundled (no install needed) | Windows 10 / 11 |
103+
| `GBABackgroundStudio_Legacy_Setup.exe` | Bundled (no install needed) | Windows 7 / 8 / 8.1 |
104+
105+
Just run the installer and launch the app — no Python or pip required.
106+
107+
---
108+
109+
### Option 2 — Run from source
110+
111+
#### Requirements
112+
113+
| Environment | Python | Qt Backend | OS |
114+
|---|---|---|---|
115+
| **Modern** | 3.10+ | PySide6 (auto) | Windows 10/11, macOS 11+, Linux |
116+
| **Legacy** | 3.8 / 3.9 | PySide2 5.15.2 (auto) | Windows 7 / 8 / 8.1 |
117+
118+
The application automatically detects which Qt backend to use based on your Python version — no manual configuration needed.
119+
120+
#### Dependencies
108121

109-
You can install dependencies using:
110122
```bash
111123
pip install -r requirements.txt
112124
```
113125

126+
`requirements.txt` automatically installs the correct Qt backend:
127+
- **Python ≥ 3.10**`PySide6`
128+
- **Python 3.8 / 3.9**`PySide2 5.15.2`
129+
130+
Other dependencies: `Pillow`, `numpy`, `scipy`, `scikit-learn`, `opencv-python`, `certifi`.
131+
114132
---
115133

116134
### 🏛️ Legacy OS Support (Windows 7 / 8 / 8.1)
117-
If you are using an older version of Windows that does not support **PySide6** (the GUI framework), you can still use the core conversion engine through our **Multi-language Command Line Wizard**.
118135

119-
#### Requirements
120-
- **Python** (3.8+ recommended)
136+
The full graphical interface runs on Windows 7 and later. Use **Python 3.8** and the `requirements.txt` will automatically install **PySide2 5.15.2**.
121137

122-
This allows you to convert images to GBA assets without the graphical interface, using a step-by-step guided assistant in your native language.
138+
Alternatively, the **Multi-language CLI Wizard** (`GBA_Studio_Wizard.bat`) is available for batch conversions without the GUI, and works on any Windows version with Python 3.8+:
123139

124140
1. Navigate to the project root.
125-
2. Run the **`GBA_Studio_Wizard.bat`** file.
141+
2. Run **`GBA_Studio_Wizard.bat`**.
126142
3. Select your language (18 languages supported).
127-
4. Follow the instructions to drag and drop your image and configure the GBA output.
143+
4. Follow the step-by-step instructions to convert your image.
128144

129145
---
130146

changelog.txt

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,36 @@
1-
Version: 1.1.0
2-
Date: April 1st, 2026
1+
Version: 2.0.0
2+
Date: May 1st, 2026
33

44
Official Discord Server: https://discord.gg/wsFFExCWFu
55

66
Please report any issues on GitHub: [https://github.com/CompuMaxx/gba-background-studio/issues](https://github.com/CompuMaxx/gba-background-studio/issues)
77

88
---
99

10+
## [2.0.0] - 2026-05-01
11+
12+
### Added
13+
- Hybrid PySide6/PySide2 support via `ui/qt_compat.py`: the application now automatically selects the appropriate Qt backend based on the Python version (PySide6 on Python 3.10+, PySide2 5.15.2 on Python 3.8/3.9), enabling compatibility with Windows 7/8/8.1 legacy systems.
14+
- `requirements.txt` now uses PEP 508 environment markers to install the correct Qt backend automatically (`PySide6` for Python ≥ 3.10, `PySide2==5.15.2` for Python < 3.10).
15+
- "Save Selection" action is now disabled until an area is selected on the tilemap; the selected area dimensions are shown in the status bar in real time.
16+
- The conversion dialog preview now updates in real time to reflect the final output dimensions, cropping or padding with transparent tiles as needed.
17+
18+
### Changed
19+
- Moved all localized README files to the `docs/` folder for a cleaner repository structure.
20+
21+
### Fixed
22+
- Corrected typo "Save Tilset" → "Save Tileset" in the Tileset menu.
23+
- "Save Tileset", "Save Tilemap", and "Save Selection" now correctly default to the last used directory (or the Documents folder) instead of the application root directory.
24+
- Fixed dimension ordering in the Open Tilemap dialog: 32×N sizes now appear before 20×N sizes.
25+
- Fixed 4bpp conversion errors caused by color replacement when the transparent color matched palette entries; the quantization logic now handles this case correctly.
26+
- `last_image_directory` is now always updated in memory on every file operation, regardless of the "Remember File Paths" setting, so it is consistently used as the default directory for open/save dialogs within the same session.
27+
- Fixed "Select Area" toolbar button becoming non-functional after using "Save Selection": the selection overlay was not being redrawn and the complementary toolbar buttons (Mirror, Flip, Swap, Replace, etc.) were not re-enabled.
28+
- Fixed tilemap being put into drawing mode after confirming "Save Selection" while an area was selected.
29+
- Export now prompts for confirmation before overwriting existing files.
30+
- Fixed tilemap scrolling to the top unexpectedly when applying a fill operation while an area was selected.
31+
- The crop mask (red overlay) is now displayed in real time while resizing the tilemap to indicate which tiles will be removed.
32+
- Fixed "translator not defined" error when moving the tilemap using the shift buttons.
33+
1034
## [1.1.0] - 2026-04-01
1135

1236
### Added

core/config_manager.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
from pathlib import Path
55

66
class ConfigManager:
7-
APP_VERSION = "1.1.0"
7+
APP_VERSION = "2.0.0"
88

99
def __init__(self, config_file="config.ini"):
1010
self.config_file = config_file
@@ -39,7 +39,8 @@ def load_config(self):
3939
'overlay_alpha': '128'
4040
}
4141
self.config['PATHS'] = {
42-
'last_image_directory': ''
42+
'last_image_directory': '',
43+
'last_save_directory': ''
4344
}
4445
self.config['CONVERSION'] = {
4546
'bpp': '0',

core/library_loader.py

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,15 @@
55
from types import ModuleType
66
from typing import Dict, List, Tuple
77
from utils.translator import Translator
8+
from ui.qt_compat import QFileDialog
89
translator = Translator()
910

1011
class LibraryLoader:
1112
def __init__(self):
1213
self.libraries_to_preload = [
13-
"PySide6.QtCore",
14-
"PySide6.QtGui",
15-
"PySide6.QtWidgets",
14+
"PySide2.QtCore",
15+
"PySide2.QtGui",
16+
"PySide2.QtWidgets",
1617
"PIL.Image",
1718
"PIL.ImageOps",
1819
"numpy",
@@ -42,8 +43,8 @@ def preload_libraries(self, progress_callback=None):
4243
dummy_img = Image.new('P', (8, 8))
4344
_ = dummy_img.getpalette()
4445

45-
elif module_name == "PySide6.QtWidgets":
46-
from PySide6.QtWidgets import QFileDialog
46+
elif module_name == "PySide2.QtWidgets":
47+
4748
_ = QFileDialog
4849

4950
time.sleep(0.02)

core/quantization.py

Lines changed: 32 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -72,21 +72,28 @@ def quantize_to_n_colors_4bpp(groups_dir, selected_palettes=None, transparent_co
7272
n_clusters = min(max_possible_clusters, len(cluster_pixels))
7373

7474
if n_clusters > 0:
75+
import warnings
76+
from sklearn.exceptions import ConvergenceWarning
77+
7578
if len(cluster_pixels) > 5000:
7679
np.random.seed(42)
7780

7881
sample_size = min(5000, len(cluster_pixels))
7982
indices = np.random.choice(len(cluster_pixels), sample_size, replace=False)
8083
sample_pixels = cluster_pixels[indices]
8184

82-
kmeans = KMeans(n_clusters=n_clusters, random_state=42, n_init=10, max_iter=30, tol=1e-5)
83-
kmeans.fit(sample_pixels)
85+
with warnings.catch_warnings():
86+
warnings.filterwarnings("ignore", category=ConvergenceWarning)
87+
kmeans = KMeans(n_clusters=n_clusters, random_state=42, n_init=10, max_iter=30, tol=1e-5)
88+
kmeans.fit(sample_pixels)
8489

8590
labels = kmeans.predict(cluster_pixels)
8691
cluster_centers = kmeans.cluster_centers_.round().astype(int)
8792
else:
88-
kmeans = KMeans(n_clusters=n_clusters, random_state=42, n_init=10, max_iter=30, tol=1e-5)
89-
labels = kmeans.fit_predict(cluster_pixels)
93+
with warnings.catch_warnings():
94+
warnings.filterwarnings("ignore", category=ConvergenceWarning)
95+
kmeans = KMeans(n_clusters=n_clusters, random_state=42, n_init=10, max_iter=30, tol=1e-5)
96+
labels = kmeans.fit_predict(cluster_pixels)
9097
cluster_centers = kmeans.cluster_centers_.round().astype(int)
9198

9299
initial_palette = [final_color_0]
@@ -178,18 +185,20 @@ def quantize_to_n_colors_4bpp(groups_dir, selected_palettes=None, transparent_co
178185
new_img.putpalette(flat_palette)
179186

180187
old_to_new = {}
181-
for new_idx, color in enumerate(reordered_rgb):
182-
best_old_idx = 0
188+
for old_idx in range(16):
189+
old_color = rgb_palette[old_idx]
190+
best_new_idx = 0
183191
best_dist = float('inf')
184-
for old_idx in range(16):
185-
dr = rgb_palette[old_idx][0] - color[0]
186-
dg = rgb_palette[old_idx][1] - color[1]
187-
db = rgb_palette[old_idx][2] - color[2]
192+
for new_idx in range(16):
193+
new_color = reordered_rgb[new_idx]
194+
dr = old_color[0] - new_color[0]
195+
dg = old_color[1] - new_color[1]
196+
db = old_color[2] - new_color[2]
188197
dist = dr*dr + dg*dg + db*db
189198
if dist < best_dist:
190199
best_dist = dist
191-
best_old_idx = old_idx
192-
old_to_new[best_old_idx] = new_idx
200+
best_new_idx = new_idx
201+
old_to_new[old_idx] = best_new_idx
193202

194203
img_flat = img_data.flatten()
195204
new_flat = np.array([old_to_new.get(idx, 0) for idx in img_flat])
@@ -250,21 +259,28 @@ def quantize_to_n_colors_8bpp(img, n_colors, start_index=0, transparent_color=(0
250259
if n_clusters <= 0:
251260
n_clusters = 1
252261

262+
import warnings
263+
from sklearn.exceptions import ConvergenceWarning
264+
253265
if len(cluster_pixels) > 5000:
254266
np.random.seed(42)
255267

256268
sample_size = min(5000, len(cluster_pixels))
257269
indices = np.random.choice(len(cluster_pixels), sample_size, replace=False)
258270
sample_pixels = cluster_pixels[indices]
259271

260-
kmeans = KMeans(n_clusters=n_clusters, random_state=42, n_init=10, max_iter=30, tol=1e-5)
261-
kmeans.fit(sample_pixels)
272+
with warnings.catch_warnings():
273+
warnings.filterwarnings("ignore", category=ConvergenceWarning)
274+
kmeans = KMeans(n_clusters=n_clusters, random_state=42, n_init=10, max_iter=30, tol=1e-5)
275+
kmeans.fit(sample_pixels)
262276

263277
labels = kmeans.predict(cluster_pixels)
264278
cluster_centers = kmeans.cluster_centers_.round(0).astype(int)
265279
else:
266-
kmeans = KMeans(n_clusters=n_clusters, random_state=42, n_init=10, max_iter=30, tol=1e-5)
267-
labels = kmeans.fit_predict(cluster_pixels)
280+
with warnings.catch_warnings():
281+
warnings.filterwarnings("ignore", category=ConvergenceWarning)
282+
kmeans = KMeans(n_clusters=n_clusters, random_state=42, n_init=10, max_iter=30, tol=1e-5)
283+
labels = kmeans.fit_predict(cluster_pixels)
268284
cluster_centers = kmeans.cluster_centers_.round(0).astype(int)
269285

270286
reduced_palette = []

0 commit comments

Comments
 (0)