From 8c5ed5a3b9cc1ececaa706c609dbd4677406bab8 Mon Sep 17 00:00:00 2001 From: Will Toohey Date: Sun, 22 Apr 2018 18:09:58 +0200 Subject: [PATCH] Canvas dumping, rename nocache to no-cache --- ifstools/handlers/TexFolder.py | 63 +++++++++++++++++++++++++++++++--- ifstools/handlers/__init__.py | 2 +- ifstools/ifs.py | 13 +++++-- ifstools/ifstools.py | 4 ++- 4 files changed, 73 insertions(+), 9 deletions(-) diff --git a/ifstools/handlers/TexFolder.py b/ifstools/handlers/TexFolder.py index ef6976d..516c3b2 100644 --- a/ifstools/handlers/TexFolder.py +++ b/ifstools/handlers/TexFolder.py @@ -1,25 +1,75 @@ +from io import BytesIO + from kbinxml import KBinXML +from tqdm import tqdm +from PIL import Image, ImageDraw from . import MD5Folder, ImageFile, GenericFile +from .ImageDecoders import cachable_formats class TextureList(GenericFile): def _load_from_filesystem(self): raw = GenericFile._load_from_filesystem(self) k = KBinXML(raw) - # force the only type we can compress + # fallback to a type we can encode for tex in k.xml_doc.iterchildren(): - tex.attrib['format'] = 'argb8888rev' + if tex.attrib['format'] not in cachable_formats: + tex.attrib['format'] = 'argb8888rev' return k.to_binary() +class ImageCanvas(GenericFile): + def __init__(self, name, size, images, parent): + self.name = '_canvas_{}.png'.format(name) + self._packed_name = self.name + self.time = parent.time + self.path = parent.path + + self.images = images + self.img_size = size + self.bbox = False + + def extract(self, base, dump_canvas = False, draw_bbox = False, **kwargs): + self.bbox = draw_bbox + if dump_canvas: + GenericFile.extract(self, base, **kwargs) + + def load(self, convert_kbin = False): + ''' Makes the canvas. + This could be far speedier if it copied raw pixels, but that would + take far too much time to write vs using Image inbuilts ''' + im = Image.new('RGBA', self.img_size) + if self.bbox: + draw = ImageDraw.Draw(im) + + for sprite in self.images: + data = sprite.load() + sprite_im = Image.open(BytesIO(data)) + + size = [x//2 for x in sprite.imgrect] + im.paste(sprite_im, (size[0], size[2])) + if self.bbox: + draw.rectangle((size[0], size[2], size[1], size[3]), outline='red') + + del draw + b = BytesIO() + im.save(b, format = 'PNG') + return b.getvalue() + + # since it's basically metadata, we ignore similarly to _cache + def repack(self, manifest, data_blob, tqdm_progress, **kwargs): + return + class TexFolder(MD5Folder): def __init__(self, ifs_data, obj, parent = None, path = '', name = '', supers = None): MD5Folder.__init__(self, ifs_data, obj, parent, path, name, supers, 'image', '.png') def tree_complete(self): MD5Folder.tree_complete(self) + if '_cache' in self.folders: self.folders.pop('_cache') + if not self.info_kbin: return @@ -32,12 +82,17 @@ class TexFolder(MD5Folder): for tex in self.info_kbin.xml_doc.iterchildren(): folder = tex.attrib['name'] fmt = tex.attrib['format'] + canvas_contents = [] + canvas_size = None for indiv in tex.iterchildren(): if indiv.tag == 'size': - continue + canvas_size = self._split_ints(indiv.text) elif indiv.tag == 'image': name = indiv.attrib['name'] + '.png' if name in self.files: ImageFile.upgrade_generic(self.files[name], indiv, fmt, self.compress) + canvas_contents.append(self.files[name]) else: - print('Unknown texturelist.xml element {}'.format(indiv.tag)) + tqdm.write('Unknown texturelist.xml element {}'.format(indiv.tag)) + canvas = ImageCanvas(folder, canvas_size, canvas_contents, self) + self.files[canvas.name] = canvas diff --git a/ifstools/handlers/__init__.py b/ifstools/handlers/__init__.py index 99a0c74..3320ceb 100644 --- a/ifstools/handlers/__init__.py +++ b/ifstools/handlers/__init__.py @@ -6,4 +6,4 @@ from .ImageFile import ImageFile from .GenericFolder import GenericFolder from .MD5Folder import MD5Folder from .AfpFolder import AfpFolder -from .TexFolder import TexFolder +from .TexFolder import TexFolder, ImageCanvas diff --git a/ifstools/ifs.py b/ifstools/ifs.py index 74926a3..6fff011 100644 --- a/ifstools/ifs.py +++ b/ifstools/ifs.py @@ -11,7 +11,7 @@ from tqdm import tqdm from kbinxml import KBinXML from kbinxml.bytebuffer import ByteBuffer -from .handlers import GenericFolder, MD5Folder, ImageFile +from .handlers import GenericFolder, MD5Folder, ImageFile, ImageCanvas from . import utils SIGNATURE = 0x6CAD8F89 @@ -143,7 +143,14 @@ class IFS: # build the tree for folder in self.tree.all_folders: - if tex_only and folder.name != 'tex': + if tex_only and folder.name == 'tex': + self.tree = folder + # make it root to discourage repacking + folder.name = '' + for f in folder.all_files: + f.path = '' + break + elif tex_only: continue f_path = join(path, folder.full_path) utils.mkdir_silent(f_path) @@ -151,7 +158,7 @@ class IFS: # extract the files for f in tqdm(self.tree.all_files): - if tex_only and not isinstance(f, ImageFile): + if tex_only and not isinstance(f, ImageFile) and not isinstance(f, ImageCanvas): continue f.extract(path, **kwargs) if progress: diff --git a/ifstools/ifstools.py b/ifstools/ifstools.py index 065432a..e6ccdaa 100644 --- a/ifstools/ifstools.py +++ b/ifstools/ifstools.py @@ -41,7 +41,9 @@ def main(): parser.add_argument('-y', action='store_true', help='don\'t prompt for file/folder overwrite', dest='overwrite') parser.add_argument('-o', default='.', help='output directory', dest='out_dir') parser.add_argument('--tex-only', action='store_true', help='only extract textures', dest='tex_only') - parser.add_argument('--nocache', action='store_false', help='ignore texture cache, recompress all', dest='use_cache') + parser.add_argument('-c', '--canvas', action='store_true', help='dump the image canvas as defined by the texturelist.xml in _canvas.png', dest='dump_canvas') + parser.add_argument('--bounds', action='store_true', help='draw image bounds on the exported canvas in red', dest='draw_bbox') + parser.add_argument('--no-cache', action='store_false', help='ignore texture cache, recompress all', dest='use_cache') parser.add_argument('-m', '--extract-manifest', action='store_true', help='extract the IFS manifest for inspection', dest='extract_manifest') parser.add_argument('-s', '--silent', action='store_false', dest='progress', help='don\'t display files as they are processed')