From 39a0401c2710dde0579be274b615770157f82741 Mon Sep 17 00:00:00 2001 From: dP Date: Mon, 28 Aug 2023 19:45:58 +0400 Subject: [PATCH] ogfx1ui: Simple grf with UI sprites from OpenGFX1 --- grf/ogfx1gui/generate.py | 253 +++++++++++++++++++++++++++++++++++++++ grf/ogfx1gui/id_map.json | 4 + 2 files changed, 257 insertions(+) create mode 100644 grf/ogfx1gui/generate.py create mode 100644 grf/ogfx1gui/id_map.json diff --git a/grf/ogfx1gui/generate.py b/grf/ogfx1gui/generate.py new file mode 100644 index 0000000000..607c9ff148 --- /dev/null +++ b/grf/ogfx1gui/generate.py @@ -0,0 +1,253 @@ +from pathlib import Path + +import numpy as np +import spectra +from PIL import Image + +import grf + +OGFX_PATH = Path('opengfx-0.5.2') +OGFX_BASE_PATH = OGFX_PATH / 'ogfx1_base.grf' +OGFX_EXTRA_BASE_PATH = OGFX_PATH / 'ogfxe_extra.grf' +THIS_FILE = grf.PythonFile(__file__) + +# OGFX_BASE_PATH = Path('OpenGFX_BigGUI-2.0.0/ogfx-biggui.grf') + +class GRFFile(grf.LoadedResourceFile): + def __init__(self, path): + self.path = path + self.real_sprites = None + self.load() + + def load(self): + if self.real_sprites is not None: + return + print(f'Decompiling {self.path}...') + self.context = grf.decompile.ParsingContext() + self.f = open(self.path, 'rb') + self.g, self.container, self.real_sprites = grf.decompile.read(self.f, self.context) + + def unload(self): + self.context = None + self.gen = None + self.container = None + self.real_sprites = None + self.f.close() + + def get_sprite_data(self, sprite_id, num): + s = self.real_sprites[sprite_id + 1][num] + self.f.seek(s.offset) + data, _ = grf.decompile.decode_sprite(self.f, s, self.container) + return data + + def get_sprites(self, sprite_id): + return [ + GRFSprite(self, s.type, s.bpp, sprite_id, i, w=s.width, h=s.height, xofs=s.xofs, yofs=s.yofs, zoom=s.zoom, crop=False) + for i, s in enumerate(self.real_sprites[sprite_id + 1]) + ] + + +class GRFSprite(grf.Sprite): + + class Mask(grf.ImageMask): + def get_fingerprint(self): + return True + + def __init__(self, file, type, grf_bpp, sprite_id, num, *args, **kw): + super().__init__(*args, **kw) + self.file = file + self.type = type + self.grf_bpp = grf_bpp + self.sprite_id = sprite_id + self.num = num # index with the same sprite_id + + bpp = grf_bpp + if self.type & 0x04: + bpp -= 1 + + if bpp == 3: + self.bpp = grf.BPP_24 + elif bpp == 4: + self.bpp = grf.BPP_32 + else: + self.bpp = grf.BPP_8 + + self._image = None + + def get_resource_files(self): + return (self.file, THIS_FILE) + + def get_fingerprint(self): + return { + 'class': self.__class__.__name__, + 'sprite_id': self.sprite_id, + } + + def load(self): + if self._image is not None: + return + + data = self.file.get_sprite_data(self.sprite_id, self.num) + + a = np.frombuffer(data, dtype=np.uint8) + assert a.size == self.w * self.h * self.grf_bpp, (a.size, self.w, self.h, self.grf_bpp) + + bpp = self.grf_bpp + a.shape = (self.h, self.w, bpp) + + mask = None + if self.type & 0x04 > 0: + bpp -= 1 + mask = Image.fromarray(a[:, :, -1], mode='P') + mask.putpalette(grf.PALETTE) + + if bpp == 3: + self._image = Image.fromarray(a[:, :, :bpp], mode='RGB'), grf.BPP_24 + elif bpp == 4: + self._image = Image.fromarray(a[:, :, :bpp], mode='RGBA'), grf.BPP_32 + else: + self._image = mask, grf.BPP_8 + mask = None + + if mask is not None: + self.mask = self.Mask(mask) + + # return self._image[0].show() + + def get_image(self): + self.load() + return self._image + + +def gen_recolour(color_func): + out = np.arange(256, dtype=np.uint8) + for i, c in grf.SPECTRA_PALETTE.items(): + if i not in grf.SAFE_COLOURS: + continue + out[i] = grf.find_best_color(color_func(c)) + return out + + +def gen_tint(tint, ratio): + return lambda x: x.blend(tint, ratio=ratio) + +GREEN = gen_recolour(gen_tint(spectra.rgb(0, 1, 0), 0.5)) + + +class RecolourSprite(grf.Sprite): + def __init__(self, sprite, recolour): + super().__init__(sprite.w, sprite.h, xofs=sprite.xofs, yofs=sprite.yofs, bpp=sprite.bpp, zoom=sprite.zoom, crop=sprite.crop) + self.sprite = sprite + self.recolour = recolour + self._image = None + + def get_resource_files(self): + return self.sprite.get_resource_files() + + def get_fingerprint(self): + return grf.combine_fingerprint( + super().get_fingerprint_base(), + sprite=self.sprite.get_fingerprint, + recolour=True, + ) + + def get_image(self): + if self._image is not None: + return self._image + im, bpp = self.sprite.get_image() + assert bpp == 8 + data = np.array(im) + data = self.recolour[data] + im2 = Image.fromarray(data) + im2.putpalette(im.getpalette()) + self._image = im2, grf.BPP_8 + return self._image + + +g = grf.NewGRF( + grfid=b'CMU1', + name='OpenGFX1 GUI sprites', + description='Only GUI sprites from OpenGFX1. For those like me who don''t want to wait for baseset parameters', + version=0, + min_compatible_version=0, +) + +ogfx = GRFFile(OGFX_BASE_PATH) +ogfx_extra = GRFFile(OGFX_EXTRA_BASE_PATH) + +# bui = GRFFile('OpenGFX_BigGUI-2.0.0/ogfx-biggui.grf') +# ogfx_extra.load() +# for s in ogfx_extra.g.generators: +# if isinstance(s, grf.ReplaceOldSprites): +# print('OLD', s.sets) +# if isinstance(s, grf.ReplaceNewSprites): +# print('NEW', s.set_type, s.count, s.offset) + +def replace_old_sprites(first_id, amount): + g.add(grf.ReplaceOldSprites([(first_id, amount)])) + res = [] + for i in range(amount): + sprites = ogfx.get_sprites(first_id + i) + # sprites = [RecolourSprite(s, GREEN) for s in sprites] + res.append(grf.AlternativeSprites(*sprites)) + g.add(*res) + return res + + +def replace_new_sprites(set_type, count, offset, first_id): + g.add(grf.ReplaceNewSprites(set_type, count, offset=offset)) + res = [] + for i in range(count): + sprites = ogfx_extra.get_sprites(first_id + i) + # sprites = [RecolourSprite(s, GREEN) for s in sprites] + res.append(grf.AlternativeSprites(*sprites)) + g.add(*res) + return res + + +replace_new_sprites(11, 4, None, 2488) +# replace_new_sprites(20, 36, None, 2488+112+15+55) +# replace_new_sprites(21, 137, 38, 2488+112+15+55+38+36+6) +# replace_new_sprites(21, 137, 38, 2750) +ofs, sprite = 38, 2750 +for x in [16] + [1] * 39 + [17, 4, 23, 1, 8, 1, 6, 9, 13]: + replace_new_sprites(21, x, ofs, sprite) + sprite += x + 1 + ofs += x + +# Mouse cursors +replace_old_sprites(0, 2) + +# arrows and check marks +replace_old_sprites(130, 1) +replace_old_sprites(140, 1) +replace_old_sprites(142, 1) +replace_old_sprites(143, 1) +replace_old_sprites(145, 1) + + +replace_old_sprites(679, 73) # Base GUI +replace_old_sprites(1251, 12) # GUI rails +replace_old_sprites(1263, 12) # Rail cursors +replace_old_sprites(1291, 1) # Signal icon +replace_old_sprites(1292, 2) # Signal cursors +replace_old_sprites(1294, 2) # Depot icons +replace_old_sprites(1296, 2) # Depot cursors +replace_old_sprites(1298, 2) # Rail station icon +replace_old_sprites(1300, 1) # Rail station cursor +replace_old_sprites(1309, 2) # Road icons +replace_old_sprites(1311, 2) # Road cursors +replace_old_sprites(2429, 4) # Tunnel icons +replace_old_sprites(2433, 4) # Tunnel cursors +replace_old_sprites(2593, 1) # Bridge cursor +replace_old_sprites(2594, 7) # Bridge icons +replace_old_sprites(3090, 2) # Start-stop icons +replace_old_sprites(4077, 1) # Town icon +replace_old_sprites(4080, 2) # Town and industry cursors +replace_old_sprites(4082, 6) # Object toolbar icons +replace_old_sprites(4791, 1) # Land own icon +replace_old_sprites(4792, 1) # Land own cursor + +grf.main(g, 'cm_ogfx1_gui.grf') + + diff --git a/grf/ogfx1gui/id_map.json b/grf/ogfx1gui/id_map.json new file mode 100644 index 0000000000..ce346afb18 --- /dev/null +++ b/grf/ogfx1gui/id_map.json @@ -0,0 +1,4 @@ +{ + "version": 1, + "index": {} +} \ No newline at end of file