-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy pathGimpPatPattern.py
More file actions
148 lines (119 loc) · 3.76 KB
/
GimpPatPattern.py
File metadata and controls
148 lines (119 loc) · 3.76 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
"""Pure python implementation of a gimp pattern file."""
from __future__ import annotations
from io import BytesIO
from pathlib import Path
from PIL import Image
from gimpformats import utils
from gimpformats.binaryiotools import IO
class GimpPatPattern:
"""Pure python implementation of a gimp pattern file.
See:
https://gitlab.gnome.org/GNOME/gimp/blob/master/devel-docs/pat.txt
"""
COLOR_MODES = [None, "L", "LA", "RGB", "RGBA"]
def __init__(self, fileName: BytesIO | str | None = None) -> None:
"""Pure python implementation of a gimp pattern file.
Args:
----
fileName (BytesIO, optional): filename or pointer. Defaults to None.
"""
self.fileName = None
self.version = 1
self.width = 0
self.height = 0
self.bpp = 4
self.mode = self.COLOR_MODES[self.bpp]
self.name = ""
self._rawImage = None
self._image = None
if fileName is not None:
self.load(fileName)
def load(self, fileName: BytesIO | str | Path) -> None:
"""Load a gimp file.
:param fileName: can be a file name or a file-like object
"""
self.fileName, data = utils.fileOpen(fileName)
self.decode(data)
def decode(self, data: bytearray | bytes, index: int = 0) -> int:
"""Decode a byte buffer.
Args:
----
data (bytearray): data to decode
index (int, optional): index to start from. Defaults to 0.
Raises:
------
RuntimeError: "File format error. Magic value mismatch."
Returns:
-------
int: pointer
"""
ioBuf = IO(data, index)
headerSize = ioBuf.u32
self.version = ioBuf.u32
self.width = ioBuf.u32
self.height = ioBuf.u32
self.bpp = ioBuf.u32
self.mode = self.COLOR_MODES[self.bpp]
magic = ioBuf.getbytearray(4)
if magic.decode("ascii") != "GPAT":
msg = "File format error. Magic value mismatch."
raise RuntimeError(msg)
nameLen = headerSize - ioBuf.index
self.name = ioBuf.getbytearray(nameLen).decode("UTF-8")
self._rawImage = ioBuf.getbytearray(self.width * self.height * self.bpp)
self._image = None
return ioBuf.index
def encode(self) -> bytearray:
"""Encode to a byte buffer."""
ioBuf = IO()
ioBuf.u32 = 24 + len(self.name)
ioBuf.u32 = self.version
ioBuf.u32 = self.width
ioBuf.u32 = self.height
ioBuf.u32 = len(self.image.mode)
ioBuf.addbytearray("GPAT")
ioBuf.addbytearray(self.name.encode("utf-8"))
if self._rawImage is None and self.image:
rawImage = self.image.tobytes(encoder_name="raw")
else:
rawImage = self._rawImage
ioBuf.addbytearray(rawImage)
return ioBuf.data
@property
def size(self) -> tuple[int, int]:
"""The size of the pattern."""
return (self.width, self.height)
@property
def image(self) -> Image.Image | None:
"""Get a final, compiled image."""
if self._image is None:
if self._rawImage is None:
return None
self._image = Image.frombytes(self.mode, self.size, self._rawImage, decoder_name="raw")
return self._image
@image.setter
def image(self, image: Image.Image) -> None:
self._image = image
self._rawImage = None
def save(self, tofileName: Path | str | BytesIO, toExtension: str | None = None) -> None:
"""Save this gimp image to a file."""
asImage = False
if toExtension is None:
toExtension = Path(str(tofileName)).suffix or None
if toExtension and toExtension != ".pat":
asImage = True
if asImage and self.image is not None:
self.image.save(tofileName)
else:
utils.save(self.encode(), tofileName)
def __str__(self) -> str:
"""Get a textual representation of this object."""
return self.__repr__()
def __repr__(self) -> str:
"""Get a textual representation of this object."""
return (
f"<GimpPatPattern name={self.name!r}, version={self.version}, "
f"size={self.width}x{self.height}, "
f"bpp={self.bpp}, mode={self.mode}"
f"{', fileName=' + repr(self.fileName) if self.fileName is not None else ''}>"
)