From 9809bb55e5fc44502de4229295be209bfa3b0227 Mon Sep 17 00:00:00 2001 From: Jerry Date: Thu, 30 Sep 2021 13:18:38 +0800 Subject: [PATCH] init --- mcdraw.py | 33 ++++++++++ minecraft-concrete.gpl | 20 ++++++ minecraft-fullblock.gpl | 57 +++++++++++++++++ minecraft.gpl | 62 +++++++++++++++++++ requirements.txt | 2 + schemgen.py | 131 ++++++++++++++++++++++++++++++++++++++++ 6 files changed, 305 insertions(+) create mode 100644 mcdraw.py create mode 100644 minecraft-concrete.gpl create mode 100644 minecraft-fullblock.gpl create mode 100644 minecraft.gpl create mode 100644 requirements.txt create mode 100644 schemgen.py diff --git a/mcdraw.py b/mcdraw.py new file mode 100644 index 0000000..c129432 --- /dev/null +++ b/mcdraw.py @@ -0,0 +1,33 @@ +from PIL import ImageFont, Image, ImageDraw +import re +try: + font = ImageFont.truetype("wqy-zenhei.ttc", 24, 1) + emfont = ImageFont.truetype("NotoEmoji-Regular.ttf", 24, 0) +except OSError: + print("Please download these fonts to the current dir:\n" + "wqy-zenhei.ttc\n\tfrom https://downloads.sourceforge.net/project/wqy/wqy-zenhei/0.9.45%20%28Fighting-state%20RC1%29/wqy-zenhei-0.9.45.tar.gz" + "\n" + "NotoEmoji-Regular.ttf\n\tfrom https://github.com/googlefonts/noto-emoji/raw/2f1ffdd6fbbd05d6f382138a3d3adcd89c5ce800/fonts/NotoEmoji-Regular.ttf" + "\n\n" + ) + raise + +def text_to_im(text="紧密团结在鱼塔同志周围"): + emoji = re.compile("^[" u"\U0001F600-\U0001F64F" u"\U0001F300-\U0001F5FF" u"\U0001F680-\U0001F6FF" u"\U0001F1E0-\U0001F1FF" "]$", flags=re.UNICODE) + brush_location = [0] + def draw_single_char(ch, draw=None): + m = emoji.match(ch) + myfont = emfont if m else font + width = myfont.getsize(ch)[0] + if draw: + draw.text((brush_location[0], -2 if m else 0), ch, fill="black", font=myfont, spacing=0) + brush_location[0] += width + return width + def max_height(text): + return max(*[font.getsize(ch)[1] for ch in text], *[emfont.getsize(ch)[1] for ch in text]) + width, height = sum(draw_single_char(ch) for ch in text), max_height(text) + im = Image.new('1', (width, height), color="white") + draw = ImageDraw.Draw(im) + for ch in text: + draw_single_char(ch, draw) + return im diff --git a/minecraft-concrete.gpl b/minecraft-concrete.gpl new file mode 100644 index 0000000..05ef638 --- /dev/null +++ b/minecraft-concrete.gpl @@ -0,0 +1,20 @@ +GIMP Palette +Name: minecraft-concrete +Columns: 0 +# +208 214 215 white_concrete +226 98 0 orange_concrete +170 45 160 magenta_concrete + 30 137 198 light_blue_concrete +242 176 13 yellow_concrete + 96 171 19 lime_concrete +214 100 143 pink_concrete + 51 55 59 gray_concrete +126 126 116 light_gray_concrete + 15 121 138 cyan_concrete +101 26 158 purple_concrete + 40 42 144 blue_concrete + 97 59 27 brown_concrete + 71 90 31 green_concrete +144 29 29 red_concrete + 2 3 7 black_concrete diff --git a/minecraft-fullblock.gpl b/minecraft-fullblock.gpl new file mode 100644 index 0000000..88e19aa --- /dev/null +++ b/minecraft-fullblock.gpl @@ -0,0 +1,57 @@ +GIMP Palette +Name: minecraft-fullblock +Columns: 0 +# +247 233 163 smooth_sandstone +199 199 199 mushroom_stem[east=true,west=true,north=true,south=true,up=true,down=true] +255 0 0 redstone_block +160 160 255 packed_ice +167 167 167 iron_block + 0 124 0 oak_leaves[distance=7,persistent=true] +255 255 255 white_concrete +164 168 184 clay +151 109 77 dirt +112 112 112 stone +143 119 72 oak_planks +255 252 245 quartz_block +216 127 51 orange_concrete +178 76 216 magenta_concrete +102 153 216 light_blue_concrete +229 229 51 yellow_concrete +127 204 25 lime_concrete +242 127 165 pink_concrete + 76 76 76 gray_concrete +153 153 153 light_gray_concrete + 76 127 153 cyan_concrete +127 63 178 purple_concrete + 51 76 178 blue_concrete +102 76 51 brown_concrete +102 127 51 green_concrete +153 51 51 red_concrete + 25 25 25 black_concrete +250 238 77 gold_block + 92 219 213 diamond_block + 74 128 255 lapis_block + 0 217 58 emerald_block +112 2 0 netherrack +209 177 161 white_terracotta +159 82 36 orange_terracotta +149 87 108 magenta_terracotta +112 108 138 light_blue_terracotta +186 133 36 yellow_terracotta +103 117 53 lime_terracotta +160 77 78 pink_terracotta + 57 41 35 gray_terracotta +135 107 98 light_gray_terracotta + 87 92 92 cyan_terracotta +122 73 88 purple_terracotta + 76 62 92 blue_terracotta + 76 50 35 brown_terracotta + 76 82 42 green_terracotta +142 60 46 red_terracotta + 37 22 16 black_terracotta +189 48 49 crimson_nylium +148 63 97 crimson_planks + 22 126 134 warped_nylium + 58 142 140 warped_planks + 20 180 133 warped_wart_block diff --git a/minecraft.gpl b/minecraft.gpl new file mode 100644 index 0000000..441f63b --- /dev/null +++ b/minecraft.gpl @@ -0,0 +1,62 @@ +GIMP Palette +Name: minecraft +Columns: 0 +# +127 178 56 grass_block[snowy=false] +247 233 163 smooth_sandstone +199 199 199 mushroom_stem[east=true,west=true,north=true,south=true,up=true,down=true] +255 0 0 redstone_block +160 160 255 packed_ice +167 167 167 iron_block + 0 124 0 oak_leaves[distance=7,persistent=true] +255 255 255 white_concrete +164 168 184 clay +151 109 77 dirt +112 112 112 stone + 64 64 255 water[level=0] +143 119 72 oak_planks +255 252 245 quartz_block +216 127 51 orange_concrete +178 76 216 magenta_concrete +102 153 216 light_blue_concrete +229 229 51 yellow_concrete +127 204 25 lime_concrete +242 127 165 pink_concrete + 76 76 76 gray_concrete +153 153 153 light_gray_concrete + 76 127 153 cyan_concrete +127 63 178 purple_concrete + 51 76 178 blue_concrete +102 76 51 brown_concrete +102 127 51 green_concrete +153 51 51 red_concrete + 25 25 25 black_concrete +250 238 77 gold_block + 92 219 213 diamond_block + 74 128 255 lapis_block + 0 217 58 emerald_block +129 86 49 podzol[snowy=false] +112 2 0 netherrack +209 177 161 white_terracotta +159 82 36 orange_terracotta +149 87 108 magenta_terracotta +112 108 138 light_blue_terracotta +186 133 36 yellow_terracotta +103 117 53 lime_terracotta +160 77 78 pink_terracotta + 57 41 35 gray_terracotta +135 107 98 light_gray_terracotta + 87 92 92 cyan_terracotta +122 73 88 purple_terracotta + 76 62 92 blue_terracotta + 76 50 35 brown_terracotta + 76 82 42 green_terracotta +142 60 46 red_terracotta + 37 22 16 black_terracotta +189 48 49 crimson_nylium +148 63 97 crimson_planks + 92 25 29 crimson_hyphae[axis=y] + 22 126 134 warped_nylium + 58 142 140 warped_planks + 86 44 62 warped_hyphae[axis=y] + 20 180 133 warped_wart_block diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..da250e7 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,2 @@ +pillow==8.3.2 +PyNBT==3.1.0 diff --git a/schemgen.py b/schemgen.py new file mode 100644 index 0000000..78ec231 --- /dev/null +++ b/schemgen.py @@ -0,0 +1,131 @@ +from PIL import Image +from pathlib import Path +from itertools import zip_longest +import re + +def read_palette(text): + palette = dict() + metadata = dict() + in_body = False + for line in text.split('\n'): + line = line.strip() + if not line or line == "GIMP Palette": + continue + if line == '#': + in_body = True + continue + if in_body: + *rgb, name = line.split() + r, g, b = [int(x) for x in rgb] + index = len(palette) + palette[f"{r:02x}{g:02x}{b:02x}"] = {"rgb": (r, g, b), "name": name if name.startswith("minecraft:") else f"minecraft:{name}", "index": index} + else: + k, *v = line.split(':') + k, v = k.strip(), ':'.join(v).strip() + metadata[k] = v + return {"metadata": metadata, "palette": palette} + +def read_imtuple(impath, palette_path): + palette = read_palette(Path(palette_path).read_text()) + im = Image.open(impath) + im = im.rotate(180) + im = im.convert('RGB') + used_color_hex = tuple(set(f"{r:02x}{g:02x}{b:02x}" for r, g, b in im.getdata())) + used_mc_palette = tuple(palette["palette"][h]["name"] for h in used_color_hex) + imdata = (used_color_hex.index(f"{r:02x}{g:02x}{b:02x}") for r, g, b in im.getdata()) + #imtuple = tuple(zip_longest(*([iter(imdata)] * im.width), fillvalue=0)) + #assert len(imtuple) == im.height + imtuple = imdata + return (used_mc_palette, imtuple, im.width, im.height) + +def read_text(text): + from mcdraw import text_to_im + im = text_to_im(text) + used_mc_palette = ('minecraft:air', 'minecraft:stone') + imdata = (0 if i else 1 for i in im.getdata()) + imtuple = imdata + return (used_mc_palette, imtuple, im.width, im.height) + +import gzip +from pynbt import NBTFile, TAG_Int, TAG_Compound, TAG_Short, TAG_Byte_Array, TAG_List, TAG_Int_Array +from io import BytesIO +import struct + +class VarInt: + MAX_BYTES = 5 + def __init__(self, io, set_value=None): + self._io = io + if set_value: + self.setval(set_value) + def setval(self, value): + ret = bytes() + while True: + byte = value & 0x7F + value >>= 7 + ret += struct.pack("B", byte | (0x80 if value > 0 else 0)) + if value == 0: + break + if len(ret) > self.MAX_BYTES: + raise ValueError("Tried to write too long of a VarInt") + self._io.write(ret) + @property + def value(self): + number = 0 + bytes_encountered = 0 + while True: + byte = self._io.read(1) + if len(byte) < 1: + raise EOFError("Unexpected end of message.") + byteord = ord(byte) + number |= (byteord & 0x7F) << 7 * bytes_encountered + if not byteord & 0x80: + break + + bytes_encountered += 1 + if bytes_encountered > self.MAX_BYTES: + raise ValueError("Tried to read too long of a VarInt") + return number + +def assemble_nbtfile(used_mc_palette, imtuple, width, height): + blackdataio = BytesIO() + varint = VarInt(blackdataio) + for i in imtuple: + varint.setval(i) + nbtvalue = { + 'Version': TAG_Int(2), + 'PaletteMax': TAG_Int(len(used_mc_palette)), + 'Palette': TAG_Compound( + {name: TAG_Int(i) for i, name in enumerate(used_mc_palette)} + ), + 'Metadata': TAG_Compound({ + 'WEOffsetX': TAG_Int(0), + 'WEOffsetY': TAG_Int(0), + 'WEOffsetZ': TAG_Int(0) + }), + 'Width': TAG_Short(width), + 'Height': TAG_Short(1), + 'Length': TAG_Short(height), + 'DataVersion': TAG_Int(2586), + 'BlockData': TAG_Byte_Array(blackdataio.getvalue()), + 'BlockEntities': TAG_List(TAG_Compound), + 'Offset': TAG_Int_Array([0, 0, 0]) + } + nbt = NBTFile(value=nbtvalue, name='Schematic') + return nbt + +if __name__ == "__main__": + import argparse + parser = argparse.ArgumentParser(description='schemgen') + parser.add_argument('-o', '--output', default='output.schem', help='output worldedit schema file') + parser.add_argument('-i', '--input', type=str, default="input.png", help='input image') + parser.add_argument('-t', '--text', type=str, default="", help='input text') + parser.add_argument('-p', '--palette', type=str, default="minecraft.gpl", help='gimp palette file') + args = parser.parse_args() + + if args.text: + nbt = assemble_nbtfile(*read_text(args.text)) + else: + nbt = assemble_nbtfile(*read_imtuple(args.input, args.palette)) + print(nbt.pretty()) + with gzip.open(args.output, 'w') as f: + nbt.save(f)