pillow-minecraft-map 1.0.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pillow_minecraft_map-1.0.0/.gitignore +3 -0
- pillow_minecraft_map-1.0.0/LICENSE +21 -0
- pillow_minecraft_map-1.0.0/PKG-INFO +83 -0
- pillow_minecraft_map-1.0.0/README.md +69 -0
- pillow_minecraft_map-1.0.0/pyproject.toml +24 -0
- pillow_minecraft_map-1.0.0/src/pillow_minecraft_map/__init__.py +4 -0
- pillow_minecraft_map-1.0.0/src/pillow_minecraft_map/plugin.py +288 -0
- pillow_minecraft_map-1.0.0/tests/data/map_111.dat +0 -0
- pillow_minecraft_map-1.0.0/tests/data/map_130.dat +0 -0
- pillow_minecraft_map-1.0.0/tests/pillow_minecraft_map_synopsis.py +9 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Christopher Bruns
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: pillow-minecraft-map
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: A Pillow Image plugin to open Minecraft .dat map files.
|
|
5
|
+
Project-URL: Homepage, https://github.com/cmbruns/pillow-minecraft-map
|
|
6
|
+
Author-email: Christopher Bruns <cmbruns@rotatingpenguin.com>
|
|
7
|
+
License-File: LICENSE
|
|
8
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
9
|
+
Classifier: Operating System :: OS Independent
|
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
|
11
|
+
Classifier: Topic :: Multimedia :: Graphics
|
|
12
|
+
Requires-Dist: pillow>=10.0.0
|
|
13
|
+
Description-Content-Type: text/markdown
|
|
14
|
+
|
|
15
|
+
# pillow-minecraft-map
|
|
16
|
+
|
|
17
|
+
A plugin for [Pillow (PIL)](https://pillow.readthedocs.io) that enables loading, viewing, and manipulation of Minecraft `.dat` map item files.
|
|
18
|
+
|
|
19
|
+
## Features
|
|
20
|
+
|
|
21
|
+
- Open Minecraft maps using standard Pillow syntax (`Image.open("map_0.dat")`).
|
|
22
|
+
- Exposes location details (`xCenter`, `zCenter`, `scale`) to Pillow's `.info` attribute.
|
|
23
|
+
|
|
24
|
+
## Important Note on Compatibility
|
|
25
|
+
|
|
26
|
+
> **Only Minecraft Java Edition map files are supported at this time.**
|
|
27
|
+
> Minecraft Bedrock Edition utilizes a different map info format.
|
|
28
|
+
|
|
29
|
+
## Installation
|
|
30
|
+
|
|
31
|
+
Install the package via pip:
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
pip install pillow-minecraft-map
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## Usage
|
|
38
|
+
|
|
39
|
+
Import the module to register the `.dat` plugin handlers into Pillow's image loader database.
|
|
40
|
+
|
|
41
|
+
### Reading Map Images
|
|
42
|
+
|
|
43
|
+
```python
|
|
44
|
+
from PIL import Image
|
|
45
|
+
import pillow_minecraft_map # Activates the extension
|
|
46
|
+
|
|
47
|
+
# Open the map file
|
|
48
|
+
with Image.open("tests/data/map_130.dat") as img:
|
|
49
|
+
# Convert from 8-bit color-indexed format ("P") to True Color ("RGB")
|
|
50
|
+
rgb_img = img.convert("RGB")
|
|
51
|
+
|
|
52
|
+
# Open inside your system's default image viewer
|
|
53
|
+
rgb_img.show()
|
|
54
|
+
|
|
55
|
+
# Export it to a standard format
|
|
56
|
+
rgb_img.save("minecraft_map_render.png")
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
### Accessing Map Metadata (`info`)
|
|
60
|
+
|
|
61
|
+
The plugin maps in-game positioning values into the image's `.info` dictionary:
|
|
62
|
+
|
|
63
|
+
```python
|
|
64
|
+
from PIL import Image
|
|
65
|
+
import pillow_minecraft_map
|
|
66
|
+
|
|
67
|
+
with Image.open("tests/data/map_130.dat") as img:
|
|
68
|
+
x = img.info.get("x_center")
|
|
69
|
+
z = img.info.get("z_center")
|
|
70
|
+
scale_factor = img.info.get("scale")
|
|
71
|
+
version = img.info.get("data_version")
|
|
72
|
+
|
|
73
|
+
# Calculate real-world block coverage
|
|
74
|
+
block_width = 128 * (2 ** scale_factor)
|
|
75
|
+
|
|
76
|
+
print(f"Minecraft DataVersion: {version}")
|
|
77
|
+
print(f"Center Coordinates: X={x}, Z={z}")
|
|
78
|
+
print(f"Scale: 1:{2**scale_factor} ({block_width}x{block_width} blocks total)")
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
## License
|
|
82
|
+
|
|
83
|
+
This project is licensed under the MIT License - see the LICENSE file for details.
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# pillow-minecraft-map
|
|
2
|
+
|
|
3
|
+
A plugin for [Pillow (PIL)](https://pillow.readthedocs.io) that enables loading, viewing, and manipulation of Minecraft `.dat` map item files.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- Open Minecraft maps using standard Pillow syntax (`Image.open("map_0.dat")`).
|
|
8
|
+
- Exposes location details (`xCenter`, `zCenter`, `scale`) to Pillow's `.info` attribute.
|
|
9
|
+
|
|
10
|
+
## Important Note on Compatibility
|
|
11
|
+
|
|
12
|
+
> **Only Minecraft Java Edition map files are supported at this time.**
|
|
13
|
+
> Minecraft Bedrock Edition utilizes a different map info format.
|
|
14
|
+
|
|
15
|
+
## Installation
|
|
16
|
+
|
|
17
|
+
Install the package via pip:
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
pip install pillow-minecraft-map
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Usage
|
|
24
|
+
|
|
25
|
+
Import the module to register the `.dat` plugin handlers into Pillow's image loader database.
|
|
26
|
+
|
|
27
|
+
### Reading Map Images
|
|
28
|
+
|
|
29
|
+
```python
|
|
30
|
+
from PIL import Image
|
|
31
|
+
import pillow_minecraft_map # Activates the extension
|
|
32
|
+
|
|
33
|
+
# Open the map file
|
|
34
|
+
with Image.open("tests/data/map_130.dat") as img:
|
|
35
|
+
# Convert from 8-bit color-indexed format ("P") to True Color ("RGB")
|
|
36
|
+
rgb_img = img.convert("RGB")
|
|
37
|
+
|
|
38
|
+
# Open inside your system's default image viewer
|
|
39
|
+
rgb_img.show()
|
|
40
|
+
|
|
41
|
+
# Export it to a standard format
|
|
42
|
+
rgb_img.save("minecraft_map_render.png")
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
### Accessing Map Metadata (`info`)
|
|
46
|
+
|
|
47
|
+
The plugin maps in-game positioning values into the image's `.info` dictionary:
|
|
48
|
+
|
|
49
|
+
```python
|
|
50
|
+
from PIL import Image
|
|
51
|
+
import pillow_minecraft_map
|
|
52
|
+
|
|
53
|
+
with Image.open("tests/data/map_130.dat") as img:
|
|
54
|
+
x = img.info.get("x_center")
|
|
55
|
+
z = img.info.get("z_center")
|
|
56
|
+
scale_factor = img.info.get("scale")
|
|
57
|
+
version = img.info.get("data_version")
|
|
58
|
+
|
|
59
|
+
# Calculate real-world block coverage
|
|
60
|
+
block_width = 128 * (2 ** scale_factor)
|
|
61
|
+
|
|
62
|
+
print(f"Minecraft DataVersion: {version}")
|
|
63
|
+
print(f"Center Coordinates: X={x}, Z={z}")
|
|
64
|
+
print(f"Scale: 1:{2**scale_factor} ({block_width}x{block_width} blocks total)")
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## License
|
|
68
|
+
|
|
69
|
+
This project is licensed under the MIT License - see the LICENSE file for details.
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "pillow-minecraft-map"
|
|
7
|
+
version = "1.0.0"
|
|
8
|
+
description = "A Pillow Image plugin to open Minecraft .dat map files."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
authors = [
|
|
11
|
+
{ name = "Christopher Bruns", email = "cmbruns@rotatingpenguin.com" }
|
|
12
|
+
]
|
|
13
|
+
classifiers = [
|
|
14
|
+
"Programming Language :: Python :: 3",
|
|
15
|
+
"License :: OSI Approved :: MIT License",
|
|
16
|
+
"Operating System :: OS Independent",
|
|
17
|
+
"Topic :: Multimedia :: Graphics",
|
|
18
|
+
]
|
|
19
|
+
dependencies = [
|
|
20
|
+
"Pillow>=10.0.0"
|
|
21
|
+
]
|
|
22
|
+
|
|
23
|
+
[project.urls]
|
|
24
|
+
Homepage = "https://github.com/cmbruns/pillow-minecraft-map"
|
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Module pillow_minecraft_map
|
|
3
|
+
|
|
4
|
+
PIL plugin for loading a Minecraft map info file as an Image.
|
|
5
|
+
These files are usually stored in server files like "world/data/map_<id>.dat"
|
|
6
|
+
Minecraft Java Edition map info files are gzipped NBT files.
|
|
7
|
+
|
|
8
|
+
Limitations:
|
|
9
|
+
* Support for Minecraft Jave Edition map info files only. Bedrock Edition maps
|
|
10
|
+
are not currently supported.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
import gzip
|
|
15
|
+
from PIL import Image, ImageFile, ImagePalette
|
|
16
|
+
|
|
17
|
+
# --- HISTORICAL MAP PALETTES ---
|
|
18
|
+
# --- STEP 1: Baseline 1.7 Legacy Map Palette (14 base IDs / 56 colors) ---
|
|
19
|
+
MINECRAFT_1_7_MAP_PALETTE = [
|
|
20
|
+
# ID 0: Air / Transparent
|
|
21
|
+
(0, 0, 0), (0, 0, 0), (0, 0, 0), (0, 0, 0),
|
|
22
|
+
# ID 1: Grass
|
|
23
|
+
(89, 125, 39), (109, 153, 48), (127, 178, 56), (67, 94, 29),
|
|
24
|
+
# ID 2: Sand
|
|
25
|
+
(174, 164, 115), (213, 201, 140), (247, 233, 163), (130, 123, 86),
|
|
26
|
+
# ID 3: Cloth / White Wool
|
|
27
|
+
(140, 140, 140), (171, 171, 171), (199, 199, 199), (105, 105, 105),
|
|
28
|
+
# ID 4: TNT / Fire / Lava
|
|
29
|
+
(180, 0, 0), (220, 0, 0), (255, 0, 0), (135, 0, 0),
|
|
30
|
+
# ID 5: Ice
|
|
31
|
+
(112, 112, 180), (138, 138, 220), (160, 160, 255), (84, 84, 135),
|
|
32
|
+
# ID 6: Iron / Metal
|
|
33
|
+
(117, 117, 117), (144, 144, 144), (167, 167, 167), (88, 88, 88),
|
|
34
|
+
# ID 7: Foliage / Plant
|
|
35
|
+
(0, 87, 0), (0, 106, 0), (0, 124, 0), (0, 65, 0),
|
|
36
|
+
# ID 8: Snow
|
|
37
|
+
(180, 180, 180), (220, 220, 220), (255, 255, 255), (135, 135, 135),
|
|
38
|
+
# ID 9: Clay
|
|
39
|
+
(115, 118, 129), (141, 145, 158), (164, 168, 184), (86, 88, 97),
|
|
40
|
+
# ID 10: Dirt
|
|
41
|
+
(106, 76, 54), (130, 94, 66), (151, 109, 77), (79, 57, 40),
|
|
42
|
+
# ID 11: Stone
|
|
43
|
+
(79, 79, 79), (96, 96, 96), (112, 112, 112), (59, 59, 59),
|
|
44
|
+
# ID 12: Water
|
|
45
|
+
(45, 45, 180), (55, 55, 220), (64, 64, 255), (33, 33, 135),
|
|
46
|
+
# ID 13: Wood
|
|
47
|
+
(100, 84, 50), (123, 102, 62), (143, 119, 72), (75, 63, 38)
|
|
48
|
+
]
|
|
49
|
+
|
|
50
|
+
# --- STEP 2: Release 1.8 to 1.11 Map Palette (Adds 16 wool colors + precious blocks -> 35 IDs) ---
|
|
51
|
+
MINECRAFT_1_8_MAP_PALETTE = MINECRAFT_1_7_MAP_PALETTE + [
|
|
52
|
+
# ID 14: Quartz
|
|
53
|
+
(180, 177, 172), (220, 217, 211), (255, 252, 245), (135, 133, 129),
|
|
54
|
+
# ID 15: Orange Wool
|
|
55
|
+
(152, 89, 36), (186, 109, 44), (216, 127, 51), (114, 67, 27),
|
|
56
|
+
# ID 16: Magenta Wool
|
|
57
|
+
(125, 53, 152), (153, 65, 186), (178, 76, 216), (94, 40, 114),
|
|
58
|
+
# ID 17: Light Blue Wool
|
|
59
|
+
(72, 108, 152), (88, 132, 186), (102, 153, 216), (54, 81, 114),
|
|
60
|
+
# ID 18: Yellow Wool
|
|
61
|
+
(161, 161, 36), (197, 197, 44), (229, 229, 51), (121, 121, 27),
|
|
62
|
+
# ID 19: Lime Wool
|
|
63
|
+
(89, 144, 17), (109, 176, 21), (127, 204, 25), (67, 108, 13),
|
|
64
|
+
# ID 20: Pink Wool
|
|
65
|
+
(170, 89, 116), (208, 109, 142), (242, 127, 165), (128, 67, 87),
|
|
66
|
+
# ID 21: Gray Wool
|
|
67
|
+
(53, 53, 53), (65, 65, 65), (76, 76, 76), (40, 40, 40),
|
|
68
|
+
# ID 22: Light Gray Wool
|
|
69
|
+
(108, 108, 108), (132, 132, 132), (153, 153, 153), (81, 81, 81),
|
|
70
|
+
# ID 23: Cyan Wool
|
|
71
|
+
(53, 89, 108), (65, 109, 132), (76, 127, 153), (40, 67, 81),
|
|
72
|
+
# ID 24: Purple Wool
|
|
73
|
+
(89, 44, 125), (109, 54, 153), (127, 63, 178), (67, 33, 94),
|
|
74
|
+
# ID 25: Blue Wool
|
|
75
|
+
(36, 53, 125), (44, 65, 153), (51, 76, 178), (27, 40, 94),
|
|
76
|
+
# ID 26: Brown Wool
|
|
77
|
+
(72, 53, 36), (88, 65, 44), (102, 76, 51), (54, 40, 27),
|
|
78
|
+
# ID 27: Green Wool
|
|
79
|
+
(72, 89, 36), (88, 109, 44), (102, 127, 51), (54, 67, 27),
|
|
80
|
+
# ID 28: Red Wool
|
|
81
|
+
(108, 36, 36), (132, 44, 44), (153, 51, 51), (81, 27, 27),
|
|
82
|
+
# ID 29: Black Wool
|
|
83
|
+
(17, 17, 17), (21, 21, 21), (25, 25, 25), (13, 13, 13),
|
|
84
|
+
# ID 30: Gold
|
|
85
|
+
(176, 168, 54), (215, 205, 66), (250, 238, 77), (132, 126, 40),
|
|
86
|
+
# ID 31: Diamond
|
|
87
|
+
(64, 154, 150), (79, 189, 183), (92, 219, 213), (48, 116, 112),
|
|
88
|
+
# ID 32: Lapis
|
|
89
|
+
(52, 90, 180), (63, 110, 220), (74, 128, 255), (39, 67, 135),
|
|
90
|
+
# ID 33: Emerald
|
|
91
|
+
(0, 153, 40), (0, 187, 50), (0, 217, 58), (0, 114, 30),
|
|
92
|
+
# ID 34: Podzol
|
|
93
|
+
(91, 60, 34), (111, 74, 42), (129, 86, 49), (68, 45, 25),
|
|
94
|
+
# ID 35: Netherrack
|
|
95
|
+
(79, 1, 0), (96, 1, 0), (112, 2, 0), (59, 1, 0)
|
|
96
|
+
]
|
|
97
|
+
|
|
98
|
+
# --- STEP 3: Release 1.12 to 1.15 Map Palette (Adds 16 Terracotta options -> 51 IDs) ---
|
|
99
|
+
MINECRAFT_1_12_MAP_PALETTE = MINECRAFT_1_8_MAP_PALETTE + [
|
|
100
|
+
# ID 36: White Terracotta
|
|
101
|
+
(147, 124, 113), (180, 152, 138), (209, 177, 161), (110, 93, 85),
|
|
102
|
+
# ID 37: Orange Terracotta
|
|
103
|
+
(112, 57, 25), (137, 70, 31), (159, 82, 36), (84, 43, 19),
|
|
104
|
+
# ID 38: Magenta Terracotta
|
|
105
|
+
(105, 61, 76), (128, 75, 93), (149, 87, 108), (78, 46, 57),
|
|
106
|
+
# ID 39: Light Blue Terracotta
|
|
107
|
+
(79, 76, 97), (96, 93, 119), (112, 108, 138), (59, 57, 73),
|
|
108
|
+
# ID 40: Yellow Terracotta
|
|
109
|
+
(131, 93, 25), (160, 114, 31), (186, 133, 36), (98, 70, 19),
|
|
110
|
+
# ID 41: Lime Terracotta
|
|
111
|
+
(72, 82, 37), (88, 100, 45), (103, 117, 53), (54, 61, 28),
|
|
112
|
+
# ID 42: Pink Terracotta
|
|
113
|
+
(112, 54, 55), (138, 66, 67), (160, 77, 78), (84, 40, 41),
|
|
114
|
+
# ID 43: Gray Terracotta
|
|
115
|
+
(40, 28, 24), (49, 35, 30), (57, 41, 35), (30, 21, 18),
|
|
116
|
+
# ID 44: Light Gray Terracotta
|
|
117
|
+
(95, 75, 69), (116, 92, 84), (135, 107, 98), (71, 56, 51),
|
|
118
|
+
# ID 45: Cyan Terracotta
|
|
119
|
+
(61, 64, 64), (75, 79, 79), (87, 92, 92), (46, 48, 48),
|
|
120
|
+
# ID 46: Purple Terracotta
|
|
121
|
+
(86, 51, 62), (105, 62, 75), (122, 73, 88), (64, 38, 46),
|
|
122
|
+
# ID 47: Blue Terracotta
|
|
123
|
+
(53, 43, 64), (65, 53, 79), (76, 62, 92), (40, 32, 48),
|
|
124
|
+
# ID 48: Brown Terracotta
|
|
125
|
+
(53, 35, 24), (65, 43, 30), (76, 50, 35), (40, 26, 18),
|
|
126
|
+
# ID 49: Green Terracotta
|
|
127
|
+
(53, 57, 29), (65, 70, 36), (76, 82, 42), (40, 43, 22),
|
|
128
|
+
# ID 50: Red Terracotta
|
|
129
|
+
(100, 42, 32), (122, 51, 39), (142, 60, 46), (75, 31, 24),
|
|
130
|
+
# ID 51: Black Terracotta
|
|
131
|
+
(26, 15, 11), (31, 18, 13), (37, 22, 16), (19, 11, 8)
|
|
132
|
+
]
|
|
133
|
+
|
|
134
|
+
# --- STEP 4: Release 1.16 to 1.17 Map Palette (Adds Nether woods/nyliums -> 58 IDs) ---
|
|
135
|
+
MINECRAFT_1_16_MAP_PALETTE = MINECRAFT_1_12_MAP_PALETTE + [
|
|
136
|
+
# ID 52: Crimson Nylium
|
|
137
|
+
(133, 33, 34), (163, 41, 42), (189, 48, 49), (100, 25, 25),
|
|
138
|
+
# ID 53: Crimson Stem
|
|
139
|
+
(104, 44, 68), (127, 54, 83), (148, 63, 97), (78, 33, 51),
|
|
140
|
+
# ID 54: Crimson Hyphae
|
|
141
|
+
(64, 17, 20), (79, 21, 25), (92, 25, 29), (48, 13, 15),
|
|
142
|
+
# ID 55: Warped Nylium
|
|
143
|
+
(15, 88, 94), (18, 108, 115), (22, 126, 134), (11, 66, 71),
|
|
144
|
+
# ID 56: Warped Stem
|
|
145
|
+
(40, 100, 98), (50, 122, 120), (58, 142, 140), (30, 75, 74),
|
|
146
|
+
# ID 57: Warped Hyphae
|
|
147
|
+
(60, 31, 43), (74, 37, 53), (86, 44, 62), (45, 23, 32),
|
|
148
|
+
# ID 58: Warped Wart Block
|
|
149
|
+
(14, 127, 93), (17, 155, 114), (20, 180, 133), (10, 95, 70)
|
|
150
|
+
]
|
|
151
|
+
|
|
152
|
+
# --- STEP 5: Release 1.18 to 1.19 Map Palette (Adds Deepslate, Raw Iron, Glow Lichen -> 61 IDs) ---
|
|
153
|
+
MINECRAFT_1_18_MAP_PALETTE = MINECRAFT_1_16_MAP_PALETTE + [
|
|
154
|
+
# ID 59: Deepslate
|
|
155
|
+
(70, 70, 70), (86, 86, 86), (100, 100, 100), (52, 52, 52),
|
|
156
|
+
# ID 60: Raw Iron
|
|
157
|
+
(152, 123, 103), (186, 150, 126), (216, 175, 147), (114, 92, 77),
|
|
158
|
+
# ID 61: Glow Lichen
|
|
159
|
+
(89, 117, 105), (109, 144, 129), (127, 167, 150), (67, 88, 79)
|
|
160
|
+
]
|
|
161
|
+
|
|
162
|
+
# --- STEP 6: Release 1.20+ Map Palette (Adds Copper variants, Cherry Wood -> 65 IDs) ---
|
|
163
|
+
MINECRAFT_1_20_MAP_PALETTE = MINECRAFT_1_18_MAP_PALETTE + [
|
|
164
|
+
# ID 62: Verdigris / Oxidized Copper
|
|
165
|
+
(104, 154, 133), (127, 189, 163), (148, 219, 189), (78, 116, 100),
|
|
166
|
+
# ID 63: Terracotta Light Gray / Pale Purple
|
|
167
|
+
(79, 60, 52), (96, 74, 64), (112, 86, 75), (59, 45, 39),
|
|
168
|
+
# ID 64: Cherry Wood / Pink Tint
|
|
169
|
+
(149, 102, 116), (182, 124, 142), (211, 144, 165), (111, 76, 87),
|
|
170
|
+
# ID 65: Bogged / Mud / Mangrove Root
|
|
171
|
+
(58, 48, 31), (71, 59, 38), (83, 69, 45), (43, 36, 23)
|
|
172
|
+
]
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
class MinecraftMapImageFile(ImageFile.ImageFile):
|
|
176
|
+
format = "MINECRAFT_MAP"
|
|
177
|
+
format_description = "Minecraft Map Item Format (.dat)"
|
|
178
|
+
|
|
179
|
+
@staticmethod
|
|
180
|
+
def accept(prefix: bytes) -> bool:
|
|
181
|
+
"""
|
|
182
|
+
Quickly checks the first 4-16 bytes of a file to see
|
|
183
|
+
if it matches this plugin's format.
|
|
184
|
+
"""
|
|
185
|
+
# Match only the standard GZIP magic numbers
|
|
186
|
+
return len(prefix) >= 3 and prefix[:3] == b"\x1f\x8b\x08"
|
|
187
|
+
|
|
188
|
+
def _find_tag_payload(self, tag: str) -> int:
|
|
189
|
+
tag_bytes = tag.encode()
|
|
190
|
+
nbt_string_prefix = len(tag_bytes).to_bytes(2, "big") + tag_bytes
|
|
191
|
+
return self.file_bytes.index(nbt_string_prefix) + len(nbt_string_prefix)
|
|
192
|
+
|
|
193
|
+
def get_byte(self, tag: str) -> int:
|
|
194
|
+
"""Fetch an NBT single byte value by tag name."""
|
|
195
|
+
pos = self._find_tag_payload(tag)
|
|
196
|
+
assert isinstance(self.file_bytes, bytes)
|
|
197
|
+
return int.from_bytes(self.file_bytes[pos: pos + 1], "big", signed=True)
|
|
198
|
+
|
|
199
|
+
def get_byte_array(self, tag: str) -> bytes:
|
|
200
|
+
"""Fetch an NBT byte array by tag name"""
|
|
201
|
+
pos = self._find_tag_payload(tag)
|
|
202
|
+
assert isinstance(self.file_bytes, bytes)
|
|
203
|
+
size = int.from_bytes(self.file_bytes[pos: pos + 4], "big")
|
|
204
|
+
return self.file_bytes[pos + 4: pos + 4 + size]
|
|
205
|
+
|
|
206
|
+
def get_int(self, tag: str) -> int:
|
|
207
|
+
"""Fetch an NBT integer value by tag name"""
|
|
208
|
+
pos = self._find_tag_payload(tag)
|
|
209
|
+
assert isinstance(self.file_bytes, bytes)
|
|
210
|
+
return int.from_bytes(self.file_bytes[pos: pos + 4], "big", signed=True)
|
|
211
|
+
|
|
212
|
+
def load(self):
|
|
213
|
+
"""Override load to push the bytes straight to the internal C core."""
|
|
214
|
+
if hasattr(self, "_pixels") and self._pixels is not None:
|
|
215
|
+
self.load_prepare()
|
|
216
|
+
self.frombytes(self._pixels, "raw", ("P", 0, 1))
|
|
217
|
+
if self.palette:
|
|
218
|
+
raw_mode, data_bytes = self.palette.getdata()
|
|
219
|
+
self.im.putpalette("RGB", raw_mode, data_bytes)
|
|
220
|
+
self._pixels = None
|
|
221
|
+
return super().load()
|
|
222
|
+
|
|
223
|
+
def _open(self) -> None:
|
|
224
|
+
"""
|
|
225
|
+
Reads the file header, sets metadata, and loads the small pixel array.
|
|
226
|
+
"""
|
|
227
|
+
# 1. Rewind the stream and open it as a GZIP stream
|
|
228
|
+
self.fp.seek(0)
|
|
229
|
+
with gzip.open(self.fp, mode="rb") as gz:
|
|
230
|
+
_fb = gz.read()
|
|
231
|
+
assert isinstance(_fb, bytes)
|
|
232
|
+
self.file_bytes: bytes = _fb
|
|
233
|
+
self._pixels = self.get_byte_array("colors")
|
|
234
|
+
assert 128*128 == len(self._pixels)
|
|
235
|
+
self._size = 128, 128
|
|
236
|
+
# Handle color palette
|
|
237
|
+
self._mode = "P"
|
|
238
|
+
try:
|
|
239
|
+
data_version = self.get_int("DataVersion")
|
|
240
|
+
except ValueError:
|
|
241
|
+
# Fallback layout if DataVersion string signature is absent in older files
|
|
242
|
+
data_version = 0
|
|
243
|
+
if data_version >= 3463: # Minecraft Java Edition 1.20+
|
|
244
|
+
raw_palette = MINECRAFT_1_20_MAP_PALETTE
|
|
245
|
+
elif data_version >= 2975: # 1.18 - 1.19
|
|
246
|
+
raw_palette = MINECRAFT_1_18_MAP_PALETTE
|
|
247
|
+
elif data_version >= 2566: # 1.16 - 1.17
|
|
248
|
+
raw_palette = MINECRAFT_1_16_MAP_PALETTE
|
|
249
|
+
elif data_version >= 1139: # 1.12 - 1.15
|
|
250
|
+
raw_palette = MINECRAFT_1_12_MAP_PALETTE
|
|
251
|
+
elif data_version >= 99: # 1.8 - 1.11
|
|
252
|
+
raw_palette = MINECRAFT_1_8_MAP_PALETTE
|
|
253
|
+
else: # Pre-1.8 Legacy Beta
|
|
254
|
+
raw_palette = MINECRAFT_1_7_MAP_PALETTE
|
|
255
|
+
# Flatten the list of RGB tuples into a 1D sequence of integers
|
|
256
|
+
flat_palette = [color for rgb in raw_palette for color in rgb]
|
|
257
|
+
# Pad out to exactly 768 entries (256 colors * 3 channels) using zeros
|
|
258
|
+
pil_palette = flat_palette + [0] * (768 - len(flat_palette))
|
|
259
|
+
self.palette = ImagePalette.ImagePalette(mode="RGB", palette=pil_palette)
|
|
260
|
+
# Parse metadata
|
|
261
|
+
try:
|
|
262
|
+
x_center = self.get_int("xCenter")
|
|
263
|
+
z_center = self.get_int("zCenter")
|
|
264
|
+
scale = self.get_byte("scale")
|
|
265
|
+
except ValueError:
|
|
266
|
+
# Safe fallbacks if parsing legacy/custom map structures
|
|
267
|
+
x_center, z_center, scale = 0, 0, 0
|
|
268
|
+
# Inject into Pillow's standard metadata API
|
|
269
|
+
self.info["x_center"] = x_center
|
|
270
|
+
self.info["z_center"] = z_center
|
|
271
|
+
self.info["scale"] = scale
|
|
272
|
+
self.info["data_version"] = data_version
|
|
273
|
+
# File handle is no longer needed now
|
|
274
|
+
self.fp = None
|
|
275
|
+
|
|
276
|
+
|
|
277
|
+
def register_plugin():
|
|
278
|
+
"""Hooks the Minecraft decoder module into Pillow's driver registry."""
|
|
279
|
+
if MinecraftMapImageFile.format not in Image.ID:
|
|
280
|
+
Image.register_extensions(
|
|
281
|
+
MinecraftMapImageFile.format,
|
|
282
|
+
extensions=[".dat"],
|
|
283
|
+
)
|
|
284
|
+
Image.register_open(
|
|
285
|
+
MinecraftMapImageFile.format,
|
|
286
|
+
MinecraftMapImageFile,
|
|
287
|
+
MinecraftMapImageFile.accept,
|
|
288
|
+
)
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
from PIL import Image
|
|
2
|
+
import pillow_minecraft_map # "unused" import loads the plugin as a side effect
|
|
3
|
+
|
|
4
|
+
with Image.open("../tests/data/map_111.dat") as img:
|
|
5
|
+
assert img.mode == "P"
|
|
6
|
+
for tag in ["x_center", "z_center", "scale", "data_version"]:
|
|
7
|
+
val = img.info.get(tag)
|
|
8
|
+
print(f"{tag} = {val}")
|
|
9
|
+
img.show()
|