diff --git a/.gitignore b/.gitignore index 0447b8b..ed7a539 100644 --- a/.gitignore +++ b/.gitignore @@ -114,3 +114,108 @@ dmypy.json # Pyre type checker .pyre/ + +### PyCharm ### +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider +# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 + +# User-specific stuff +.idea/**/workspace.xml +.idea/**/tasks.xml +.idea/**/usage.statistics.xml +.idea/**/dictionaries +.idea/**/shelf + +# Generated files +.idea/**/contentModel.xml + +# Sensitive or high-churn files +.idea/**/dataSources/ +.idea/**/dataSources.ids +.idea/**/dataSources.local.xml +.idea/**/sqlDataSources.xml +.idea/**/dynamic.xml +.idea/**/uiDesigner.xml +.idea/**/dbnavigator.xml + +# Gradle +.idea/**/gradle.xml +.idea/**/libraries + +# Gradle and Maven with auto-import +# When using Gradle or Maven with auto-import, you should exclude module files, +# since they will be recreated, and may cause churn. Uncomment if using +# auto-import. +# .idea/artifacts +# .idea/compiler.xml +# .idea/jarRepositories.xml +# .idea/modules.xml +# .idea/*.iml +# .idea/modules +# *.iml +# *.ipr + +# CMake +cmake-build-*/ + +# Mongo Explorer plugin +.idea/**/mongoSettings.xml + +# File-based project format +*.iws + +# IntelliJ +out/ + +# mpeltonen/sbt-idea plugin +.idea_modules/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Cursive Clojure plugin +.idea/replstate.xml + +# Crashlytics plugin (for Android Studio and IntelliJ) +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties +fabric.properties + +# Editor-based Rest Client +.idea/httpRequests + +# Android studio 3.1+ serialized cache file +.idea/caches/build_file_checksums.ser + +### PyCharm Patch ### +# Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721 + +# *.iml +# modules.xml +# .idea/misc.xml +# *.ipr + +# Sonarlint plugin +# https://plugins.jetbrains.com/plugin/7973-sonarlint +.idea/**/sonarlint/ + +# SonarQube Plugin +# https://plugins.jetbrains.com/plugin/7238-sonarqube-community-plugin +.idea/**/sonarIssues.xml + +# Markdown Navigator plugin +# https://plugins.jetbrains.com/plugin/7896-markdown-navigator-enhanced +.idea/**/markdown-navigator.xml +.idea/**/markdown-navigator-enh.xml +.idea/**/markdown-navigator/ + +# Cache file creation bug +# See https://youtrack.jetbrains.com/issue/JBR-2257 +.idea/$CACHE_FILE$ + +# CodeStream plugin +# https://plugins.jetbrains.com/plugin/12206-codestream +.idea/codestream.xml + +/output \ No newline at end of file diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..5c98b42 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,2 @@ +# Default ignored files +/workspace.xml \ No newline at end of file diff --git a/.idea/inspectionProfiles/profiles_settings.xml b/.idea/inspectionProfiles/profiles_settings.xml new file mode 100644 index 0000000..105ce2d --- /dev/null +++ b/.idea/inspectionProfiles/profiles_settings.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/.idea/live-a-png.iml b/.idea/live-a-png.iml new file mode 100644 index 0000000..74d515a --- /dev/null +++ b/.idea/live-a-png.iml @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..6522620 --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..36c862f --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..94a25f7 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/README.md b/README.md index a20e2b8..cd95621 100644 --- a/README.md +++ b/README.md @@ -21,6 +21,7 @@ Edit those files to populate the correct values. ## Run the code +Edit `main.py` to set the directory of the extracted asset bundles. Run `main.py` in the terminal to start the application. ## Development: Making changes diff --git a/image_assembler.py b/image_assembler.py new file mode 100644 index 0000000..fd58245 --- /dev/null +++ b/image_assembler.py @@ -0,0 +1,44 @@ +import math +from PIL import Image + + +class ImageAssembler: + def __init__(self, metadata, atlas: Image, cell_size: int, padding: int): + self.atlas = atlas + self.atlas_width_cells = int(atlas.size[0] / cell_size) + self.atlas_height_cells = int(atlas.size[1] / cell_size) + + self.cell_size = cell_size + self.padding = padding + self.metadata = metadata + + self.output = Image.new('RGBA', (metadata['width'], metadata['height']), (255, 255, 255, 0)) + + # TODO double check this logic + self.output_width_cells = math.ceil(float(metadata['width']) / cell_size) + + def atlas_get_cell(self, cell_no: int): + cell_x_pos = cell_no % self.atlas_width_cells + cell_y_pos = self.atlas_height_cells - math.floor(cell_no / self.atlas_width_cells) - 1 # From top + return self.atlas.crop(( + cell_x_pos * self.cell_size, + cell_y_pos * self.cell_size, + (cell_x_pos + 1) * self.cell_size, + (cell_y_pos + 1) * self.cell_size + )) + + def paint(self): + for i in range(len(self.metadata['cellIndexList'])): + source_cell_number = self.metadata['cellIndexList'][i] + source_cell = self.atlas_get_cell(source_cell_number) + + output_x_pos = i % self.output_width_cells + output_y_pos = math.floor(i / self.output_width_cells) # From bottom + self.output.paste(source_cell, ( + output_x_pos * (self.cell_size - (2*self.padding)), + self.output.size[1] - self.cell_size - (output_y_pos * (self.cell_size - (2*self.padding))) + )) + + def get_output(self): + return self.output + diff --git a/main.py b/main.py new file mode 100644 index 0000000..470c0a7 --- /dev/null +++ b/main.py @@ -0,0 +1,31 @@ +import json +from os import PathLike +from pathlib import Path +from PIL import Image + +from image_assembler import ImageAssembler + +json_dir = Path('C:\\extracts\\MonoBehaviour') +atlas_dir = Path('C:\\extracts\\Texture2D') +output_dir = Path('./output') + + +def assemble_json(json_file: PathLike): + with open(json_file) as f: + metadata = json.load(f) + + for image in metadata['textureDataList']: + # TODO try-catch Image.open + atlas = Image.open(atlas_dir / (image['atlasName'] + '.png')) + assembler = ImageAssembler(image, atlas, metadata['cellSize'], metadata['padding']) + assembler.paint() + output = assembler.get_output() + + output.save(output_dir / (image['name'] + '.png'), 'PNG') + + +if __name__ == '__main__': + for item in json_dir.iterdir(): + if item.is_file() and (str(item)).endswith('.json'): + print(item) + assemble_json(item) diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..334e961 --- /dev/null +++ b/requirements.txt @@ -0,0 +1 @@ +Pillow==7.2.0