diff --git a/CHANGELOG.md b/CHANGELOG.md index e075e36..6e13581 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,14 @@ All major and minor version changes will be documented in this file. Details of patch-level version changes can be found in [commit messages](../../commits/master). +## 2024.2 - 2024/09/15 + +- support means of image preprocessing through a `resize_method` option. It can take one of the following values: + - **"outerCrop"**: Resizes the image to fit within the target dimensions while preserving its original aspect ratio. Any space left over is filled with black bars (letterboxing), if applicable. + - **"innerCrop"**: Resizes the image by cropping parts of it to fit the target dimensions + - **"stretch"**: Stretches the image to fit the target dimensions exactly, ignoring the original aspect ratio, which can lead to distortion. + - **null**: No resizing method is applied. + ## 2024.1.1 - 2024/09/13 - compress images diff --git a/README.md b/README.md index 87c2eeb..f5bfabd 100644 --- a/README.md +++ b/README.md @@ -23,7 +23,6 @@ configuration file. usage: nxtheme-creator [-h] [--nxtheme NXTHEME] [--input INPUT] [--output OUTPUT] [--config CONFIG] ``` - **Options**: - `-h, --help`: Show help message and exit. diff --git a/documentation/reference/README.md b/documentation/reference/README.md index 4228430..799a4e3 100644 --- a/documentation/reference/README.md +++ b/documentation/reference/README.md @@ -10,4 +10,5 @@ A full list of `Nxtheme-creator` project modules. - [Nxtheme](nxtheme_creator/backends/nxtheme.md#nxtheme) - [Sarc Tool](nxtheme_creator/backends/sarc_tool.md#sarc-tool) - [Img Info](nxtheme_creator/img_info.md#img-info) + - [Process Image](nxtheme_creator/process_image.md#process-image) - [Process Themes](nxtheme_creator/process_themes.md#process-themes) diff --git a/documentation/reference/nxtheme_creator/index.md b/documentation/reference/nxtheme_creator/index.md index cd7859f..207d2d1 100644 --- a/documentation/reference/nxtheme_creator/index.md +++ b/documentation/reference/nxtheme_creator/index.md @@ -27,4 +27,5 @@ def cli() -> None: ... - [Module](./module.md) - [Backends](backends/index.md) - [Img Info](./img_info.md) +- [Process Image](./process_image.md) - [Process Themes](./process_themes.md) \ No newline at end of file diff --git a/documentation/reference/nxtheme_creator/process_image.md b/documentation/reference/nxtheme_creator/process_image.md new file mode 100644 index 0000000..6982c29 --- /dev/null +++ b/documentation/reference/nxtheme_creator/process_image.md @@ -0,0 +1,65 @@ +# Process Image + +[Nxtheme-creator Index](../README.md#nxtheme-creator-index) / [Nxtheme Creator](./index.md#nxtheme-creator) / Process Image + +> Auto-generated documentation for [nxtheme_creator.process_image](../../../nxtheme_creator/process_image.py) module. + +- [Process Image](#process-image) + - [resize_center_crop](#resize_center_crop) + - [resize_image](#resize_image) + - [resize_outer_crop_letterbox](#resize_outer_crop_letterbox) + - [resize_stretch](#resize_stretch) + +## resize_center_crop + +[Show source in process_image.py:9](../../../nxtheme_creator/process_image.py#L9) + +Resize the image using center crop method. + +#### Signature + +```python +def resize_center_crop(image: Image.Image): ... +``` + + + +## resize_image + +[Show source in process_image.py:35](../../../nxtheme_creator/process_image.py#L35) + +Resize the image using the specified method and save the output. + +#### Signature + +```python +def resize_image(input_path, output_path, method="stretch"): ... +``` + + + +## resize_outer_crop_letterbox + +[Show source in process_image.py:28](../../../nxtheme_creator/process_image.py#L28) + +Resize the image using outer crop (letterbox) method. + +#### Signature + +```python +def resize_outer_crop_letterbox(image: Image.Image): ... +``` + + + +## resize_stretch + +[Show source in process_image.py:5](../../../nxtheme_creator/process_image.py#L5) + +Resize the image by stretching it to the target SIZE. + +#### Signature + +```python +def resize_stretch(image: Image.Image): ... +``` \ No newline at end of file diff --git a/documentation/reference/nxtheme_creator/process_themes.md b/documentation/reference/nxtheme_creator/process_themes.md index 223ca3a..fd1d599 100644 --- a/documentation/reference/nxtheme_creator/process_themes.md +++ b/documentation/reference/nxtheme_creator/process_themes.md @@ -11,7 +11,7 @@ ## processImages -[Show source in process_themes.py:123](../../../nxtheme_creator/process_themes.py#L123) +[Show source in process_themes.py:131](../../../nxtheme_creator/process_themes.py#L131) Process images from the specified input directory to generate Nintendo Switch themes. This function handles the following tasks: @@ -44,7 +44,7 @@ def processImages( ## resolveConf -[Show source in process_themes.py:85](../../../nxtheme_creator/process_themes.py#L85) +[Show source in process_themes.py:93](../../../nxtheme_creator/process_themes.py#L93) Resolve the file paths for layout configurations specified in the `conf` dictionary. This function checks if the specified layout files exist. If they do not, it attempts @@ -73,7 +73,7 @@ def resolveConf(nxthemebin: str | None, conf: dict) -> dict: ... ## walkfiletree -[Show source in process_themes.py:16](../../../nxtheme_creator/process_themes.py#L16) +[Show source in process_themes.py:18](../../../nxtheme_creator/process_themes.py#L18) Create a theme_image_map from an input directory by walking the dir and getting theme names and corresponding images for each component. diff --git a/documentation/tutorials/README.md b/documentation/tutorials/README.md index ffb766b..8e2d1bc 100644 --- a/documentation/tutorials/README.md +++ b/documentation/tutorials/README.md @@ -1,7 +1,9 @@ # **Tutorial: Generating Nintendo Switch Themes with `nxtheme-creator`** -This tutorial will guide you through using the `nxtheme-creator` tool to generate custom themes for your Nintendo Switch. Follow these steps to create multiple themes from images (with optional configuration for layouts) +This tutorial will guide you through using the `nxtheme-creator` tool to generate custom themes for +your Nintendo Switch. Follow these steps to create multiple themes from images (with optional +configuration for layouts) ## **Step 1: Install `nxtheme-creator`** @@ -13,7 +15,8 @@ python3 -m pip install ## **Step 2: Prepare Your Images** -**Create an Input Directory**: Organize your images in a directory. The directory structure should look like this: +**Create an Input Directory**: Organize your images in a directory. The directory structure should +look like this: ```sh themes_directory/ @@ -28,20 +31,17 @@ themes_directory/ │ ├── home,lock.jpg │ ├── apps,set,user.jpg │ └── news.jpg -└── theme3/ - ├── home.jpg - ├── lock.jpg - ├── apps.jpg - ├── set.jpg - ├── user.jpg - └── news.jpg +└── theme3.jpg + ``` -Each image file should correspond to a different part of the theme (home screen, lock screen, etc.). But note these may be combined with a comma as shown above to apply one image to multiple parts of the UI +Each image file should correspond to a different part of the theme (home screen, lock screen, etc.). +These may be combined with a comma as shown above to apply one image to multiple parts of the UI. +Alternatively one image e.g. `theme3.jpg` may be used to create all parts of a theme. ## **Step 3: \[Optional] Configure the `conf.json` File** -Create or modify a `conf.json` file in the root directory of `nxtheme-creator`. This file will specify how the tool should process your images. Here’s an example configuration: +Create a `conf.json` file in the root directory of `nxtheme-creator`. This file will specify how the tool should process your images. Here’s an example configuration: ```json { @@ -51,16 +51,37 @@ Create or modify a `conf.json` file in the root directory of `nxtheme-creator`. "set": null, "user": null, "news": null, - "author_name": "JohnDoe" + "author_name": "JohnDoe", + "resize_method": "outerCrop" } - ``` -Note that items ["home", "lock", "apps", "set", "user", "news"] are configured with the layout file, `.json` may be omitted from the file end, also note that if the file does not exist locally, we fallback to the copy included with `SwitchThemes.exe` if available. If not found, an error is returned +| Key | Value | Descriptionscreen | +| ------------- | ------------------- | ------------------------------------------------------------------------------------------------------------------------------------ | +| home | "DogeLayoutRounded" | \[optional] Path to the layout.json for the home screen or a preset layout, e.g. the "DogeLayoutRounded" provided by nxtheme-creator | +| lock | null | \[optional] Path to the layout.json for the lock screen | +| apps | null | \[optional] Path to the layout.json for the apps screen | +| set | null | \[optional] Path to the layout.json for the settings screen | +| user | null | \[optional] Path to the layout.json for the user section | +| news | null | \[optional] Path to the layout.json for the news section | +| author_name | "JohnDoe" | Name of the author | +| resize_method | "outerCrop" | \[optional] The method used for image resizing. One of \["outerCrop", "innerCrop", "stretch", null] | + +Note that items ["home", "lock", "apps", "set", "user", "news"] are configured with a layout file, +`.json` may be omitted from the file end. Also note that if the path does not exist, we use to the +copy included with either the `SwitchThemes.exe`, or the `nxtheme-creator` backend if available. +If not found, an error is returned + +The `resize_method` option defines how an image is resized. It can take one of the following values: + +- **"outerCrop"**: Resizes the image to fit within the target dimensions while preserving its original aspect ratio. Any space left over is filled with black bars (letterboxing), if applicable. +- **"innerCrop"**: Resizes the image by cropping parts of it to fit the target dimensions +- **"stretch"**: Stretches the image to fit the target dimensions exactly, ignoring the original aspect ratio, which can lead to distortion. +- **null**: No resizing method is applied. ## **Step 4: Run the Tool** -### **Using the Command Line** +### **With the SwitchThemes.exe backend** Open your terminal or command prompt and run the following command to generate your theme: @@ -75,26 +96,24 @@ Replace: - `output_directory` with the directory where you want to save the generated theme files.This defaults to `./output/` - `conf.json` with the path to your configuration file. Note this is optional, though needed if you wish to customise the layouts or set an `author_name` -### **Example Command** - -Assuming you have the following setup: +### **With the nxtheme-creator backend** -- `SwitchThemes.exe` located at `/path/to/SwitchThemes.exe` -- Input directory is `./input_images` -- Output directory is `./themes` -- Configuration file is `./conf.json` - -Run the command: +Open your terminal or command prompt and run the following command to generate your theme: ```bash -python3 -m nxtheme-creator --nxtheme /path/to/SwitchThemes.exe --input ./input_images --output ./themes --config ./conf.json +python3 -m nxtheme-creator --input input_directory --output output_directory --config conf.json ``` +Replace: + +- `input_directory` with the path to your images. This defaults to the directory you run `nxtheme-creator` from +- `output_directory` with the directory where you want to save the generated theme files.This defaults to `./output/` +- `conf.json` with the path to your configuration file. Note this is optional, though needed if you wish to customise the layouts or set an `author_name` + ## **Step 5: Verify and Install Your Theme** 1. **Check the Output Directory**: After running the command, check the `output_directory` for the generated theme files (`.nxtheme` files). - -2. **Install the Theme**: Follow your preferred method to install the generated theme on your Nintendo Switch. Typically, you would use an NXTheme installer or similar tool to apply the theme. +2. **Install the Theme**: Follow your preferred method to install the generated theme on your Nintendo Switch. Typically, you would use an NXTheme installer or similar tool to apply the theme. For example, https://github.com/exelix11/SwitchThemeInjector ## **Troubleshooting** diff --git a/nxtheme_creator/conf.json b/nxtheme_creator/conf.json index 29481c8..eceaa04 100644 --- a/nxtheme_creator/conf.json +++ b/nxtheme_creator/conf.json @@ -5,5 +5,6 @@ "set": null, "user": null, "news": null, - "author_name": "JohnDoe" + "author_name": "JohnDoe", + "resize_method": null } diff --git a/nxtheme_creator/process_image.py b/nxtheme_creator/process_image.py new file mode 100644 index 0000000..86151af --- /dev/null +++ b/nxtheme_creator/process_image.py @@ -0,0 +1,49 @@ +from PIL import Image, ImageOps + +SIZE = (1280, 720) + +def resize_stretch(image: Image.Image): + """Resize the image by stretching it to the target SIZE.""" + return image.resize(SIZE, Image.Resampling.LANCZOS) + +def resize_center_crop(image: Image.Image): + """Resize the image using center crop method.""" + # Find the aspect ratio of the target SIZE and original image + aspect_ratio_target = SIZE[0] / SIZE[1] + aspect_ratio_original = image.width / image.height + + if aspect_ratio_original > aspect_ratio_target: + # Image is wider than target, crop horizontally + new_width = int(aspect_ratio_target * image.height) + offset = (image.width - new_width) // 2 + cropped_image = image.crop((offset, 0, offset + new_width, image.height)) + else: + # Image is taller than target, crop vertically + new_height = int(image.width / aspect_ratio_target) + offset = (image.height - new_height) // 2 + cropped_image = image.crop((0, offset, image.width, offset + new_height)) + + return cropped_image.resize(SIZE, Image.Resampling.LANCZOS) + +def resize_outer_crop_letterbox(image: Image.Image): + """Resize the image using outer crop (letterbox) method.""" + # Add padding if necessary (letterbox) + image.thumbnail(SIZE, Image.Resampling.LANCZOS) + letterbox_image = ImageOps.pad(image, SIZE, color=(0, 0, 0)) + return letterbox_image + +def resize_image(input_path, output_path, method='stretch'): + """Resize the image using the specified method and save the output.""" + image = Image.open(input_path) + + if method == 'stretch': + resized_image = resize_stretch(image) + elif method == 'centerCrop': + resized_image = resize_center_crop(image) + elif method == 'outerCrop': + resized_image = resize_outer_crop_letterbox(image) + else: + return input_path + + resized_image.save(output_path, progressive=False) + return output_path diff --git a/nxtheme_creator/process_themes.py b/nxtheme_creator/process_themes.py index 5fd7ef7..b062cbd 100644 --- a/nxtheme_creator/process_themes.py +++ b/nxtheme_creator/process_themes.py @@ -7,6 +7,8 @@ from pathlib import Path from nxtheme_creator.backends import nxtheme, sarc_tool +from nxtheme_creator.process_image import resize_image +from nxtheme_creator import img_info SCREEN_TYPES = ["home", "lock", "apps", "set", "user", "news"] @@ -50,34 +52,40 @@ def walkfiletree(inputdir: str) -> dict: """ theme_image_map = {} + def insert_img_path(theme_name: str, screen_type: str, img_path: str): + theme_image_map[theme_name][screen_type] = img_path + + # Walk over directories under inputdir for root, _dirs, files in os.walk(inputdir): for file in files: if file.endswith((".jpg", ".dds")): # Extract theme name from the directory structure theme_name = Path(root).name - if theme_name not in theme_image_map: theme_image_map[theme_name] = {} # Extract the screen types from the image name e.g., 'home,lock.jpg' matches = re.match(r"(\w+(,\w+)*)", file) if matches: - screen_types = matches.group(1) + img_file_name = matches.group(1) - # Split by comma and map each screen type to the image path top_level_theme = False - for screen_type in screen_types.split(","): + img_path = os.path.join(root, file) + + # Split by comma and map each screen type to the image path + for screen_type in img_file_name.split(","): if screen_type in SCREEN_TYPES: - theme_image_map[theme_name][screen_type] = os.path.join(root, file) + insert_img_path(theme_name=theme_name, screen_type=screen_type, img_path=img_path) else: top_level_theme = True + + # Create an nxtheme for all screen types when the image is a 'top level theme' if top_level_theme: - theme_image_map[screen_types] = {} - for default_screen_type in SCREEN_TYPES: - theme_image_map[screen_types][default_screen_type] = os.path.join( - root, file - ) + theme_image_map[img_file_name] = {} + for screen_type in SCREEN_TYPES: + insert_img_path(theme_name=img_file_name, screen_type=screen_type, img_path=img_path) + return theme_image_map @@ -139,6 +147,7 @@ def processImages(nxthemebin: str | None, inputdir: str, outputdir: str, config: """ themeimgmap = walkfiletree(inputdir=inputdir) config = resolveConf(nxthemebin, conf=config) + method=config.get("resize_method") author_name = config.get("author_name") or "JohnDoe" @@ -150,6 +159,20 @@ def processImages(nxthemebin: str | None, inputdir: str, outputdir: str, config: (Path(outputdir) / theme_name).mkdir(exist_ok=True, parents=True) + # check image + if method: + img = Path(image_path) + width, height, is_progressive, is_dxt1 = 0,0,False, True + if img.suffix == ".jpg": + width, height, is_progressive = img_info.get_jpeg_info(img.read_bytes()) + if img.suffix == ".dds": + width, height, is_dxt1 = img_info.get_dds_info(img.read_bytes()) + + if not is_dxt1 or is_progressive or (width, height) != (1280, 720): + image_out_path = f"{outputdir}/{theme_name}/{full_theme_name}.jpg" + image_path = resize_image(input_path=image_path, output_path=image_out_path, method=method) + + if nxthemebin is not None: nxtheme.execute( nxthemebin=nxthemebin, diff --git a/pyproject.toml b/pyproject.toml index 5630657..7f7f567 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "nxtheme_creator" -version = "2024.1.1" +version = "2024.2" license = "gpl-2.0-or-later" description = "Tool for making custom Nintendo Switch themes using `SwitchThemes.exe` with images, and a simple config" authors = ["FredHappyface"] @@ -22,6 +22,8 @@ readme = "README.md" [tool.poetry.dependencies] python = ">=3.8,<4" sarc = "<3,>=2.0.5" +oead = "<2,>=1.2.9.post4" +pillow = "<11,>=10.4.0" [tool.poetry.scripts] nxtheme_creator = 'nxtheme_creator:cli' diff --git a/requirements.txt b/requirements.txt index fd85596..fa4a485 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1 +1,3 @@ +oead<2,>=1.2.9.post4 +pillow<11,>=10.4.0 sarc<3,>=2.0.5