diff --git a/README.md b/README.md index 2cf8de9..c1fa798 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,7 @@ Then run `ifstools` from anywhere in a command prompt. ## Usage ``` usage: ifstools [-h] [-e] [-y] [-o OUT_DIR] [--tex-only] [-c] - [--bounds] [--no-cache] [-m] [-s] [-r] + [--bounds] [--uv] [--no-cache] [-m] [-s] [-r] file_to_unpack.ifs|folder_to_repack_ifs [file_to_unpack.ifs|folder_to_repack_ifs ...] @@ -43,6 +43,8 @@ optional arguments: -c, --canvas dump the image canvas as defined by the texturelist.xml in _canvas.png --bounds draw image bounds on the exported canvas in red + --uv crop images to uvrect (usually 1px smaller than + imgrect). Forces --tex-only --no-cache ignore texture cache, recompress all -m, --extract-manifest extract the IFS manifest for inspection diff --git a/ifstools/handlers/GenericFile.py b/ifstools/handlers/GenericFile.py index 1ce8aed..7a1cff7 100644 --- a/ifstools/handlers/GenericFile.py +++ b/ifstools/handlers/GenericFile.py @@ -16,24 +16,24 @@ class GenericFile(Node): self.start = self.size = None def extract(self, base, **kwargs): - data = self.load() + data = self.load(**kwargs) path = os.path.join(base, self.full_path) utils.save_with_timestamp(path, data, self.time) - def load(self, convert_kbin = True): + def load(self, **kwargs): if self.from_ifs: - return self._load_from_ifs(convert_kbin) + return self._load_from_ifs(**kwargs) else: - return self._load_from_filesystem() + return self._load_from_filesystem(**kwargs) - def _load_from_ifs(self, convert_kbin = True): + def _load_from_ifs(self, convert_kbin = True, **kwargs): data = self.ifs_data.get(self.start, self.size) if convert_kbin and self.name.endswith('.xml') and KBinXML.is_binary_xml(data): data = KBinXML(data).to_text().encode('utf8') return data - def _load_from_filesystem(self): + def _load_from_filesystem(self, **kwargs): with open(self.disk_path, 'rb') as f: ret = f.read() self.size = len(ret) diff --git a/ifstools/handlers/ImageFile.py b/ifstools/handlers/ImageFile.py index 3a80ed6..c9c775a 100644 --- a/ifstools/handlers/ImageFile.py +++ b/ifstools/handlers/ImageFile.py @@ -25,21 +25,26 @@ class ImageFile(GenericFile): self.format = fmt self.compress = compress - self.uvrect = self._split_ints(image_elem.find('uvrect').text) - self.imgrect = self._split_ints(image_elem.find('imgrect').text) + # all values are multiplied by 2, odd values have never been seen + self.uvrect = [x//2 for x in self._split_ints(image_elem.find('uvrect').text)] + self.imgrect = [x//2 for x in self._split_ints(image_elem.find('imgrect').text)] self.img_size = ( - (self.imgrect[1]-self.imgrect[0])//2, - (self.imgrect[3]-self.imgrect[2])//2 + self.imgrect[1]-self.imgrect[0], + self.imgrect[3]-self.imgrect[2] + ) + self.uv_size = ( + self.uvrect[1]-self.uvrect[0], + self.uvrect[3]-self.uvrect[2] ) def extract(self, base, use_cache = True, **kwargs): GenericFile.extract(self, base, **kwargs) if use_cache and self.compress and self.from_ifs and self.format in cachable_formats: - self.write_cache(GenericFile._load_from_ifs(self), base) + self.write_cache(GenericFile._load_from_ifs(self, **kwargs), base) - def _load_from_ifs(self, convert_kbin = False): - data = GenericFile._load_from_ifs(self, False) + def _load_from_ifs(self, crop_to_uvrect = False, **kwargs): + data = GenericFile._load_from_ifs(self, **kwargs) if self.compress == 'avslz': uncompressed_size = unpack('>I', data[:4])[0] @@ -60,6 +65,17 @@ class ImageFile(GenericFile): else: raise NotImplementedError('Unknown format {}'.format(self.format)) + if crop_to_uvrect: + start_x = self.uvrect[0] - self.imgrect[0] + start_y = self.uvrect[2] - self.imgrect[2] + dims = ( + start_x, + start_y, + start_x + self.uv_size[0], + start_y + self.uv_size[1], + ) + im = im.crop(dims) + b = BytesIO() im.save(b, format = 'PNG') return b.getvalue() diff --git a/ifstools/handlers/TexFolder.py b/ifstools/handlers/TexFolder.py index fb7ed40..4ab4ff8 100644 --- a/ifstools/handlers/TexFolder.py +++ b/ifstools/handlers/TexFolder.py @@ -27,29 +27,27 @@ class ImageCanvas(GenericFile): 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 + def extract(self, base, dump_canvas = False, **kwargs): if dump_canvas: GenericFile.extract(self, base, **kwargs) - def load(self, convert_kbin = False): + def load(self, draw_bbox = False, **kwargs): ''' 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) draw = None - if self.bbox: + if draw_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] + size = sprite.imgrect im.paste(sprite_im, (size[0], size[2])) - if self.bbox: + if draw_bbox: draw.rectangle((size[0], size[2], size[1], size[3]), outline='red') del draw diff --git a/ifstools/ifstools.py b/ifstools/ifstools.py index e6ccdaa..2cc99de 100644 --- a/ifstools/ifstools.py +++ b/ifstools/ifstools.py @@ -43,6 +43,7 @@ def main(): parser.add_argument('--tex-only', action='store_true', help='only extract textures', dest='tex_only') 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('--uv', action='store_true', help='crop images to uvrect (usually 1px smaller than imgrect). Forces --tex-only', dest='crop_to_uvrect') 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', @@ -52,6 +53,9 @@ def main(): args = parser.parse_args() + if args.crop_to_uvrect: + args.tex_only = True + if args.extract_folders: dirs = [f for f in args.files if os.path.isdir(f)] # prune