Skip to content

Commit b78fe74

Browse files
committed
encode gimpimagelevel/hierarchy
1 parent ce300e2 commit b78fe74

8 files changed

Lines changed: 165 additions & 34 deletions

File tree

documentation/reference/gimpformats/GimpImageHierarchy.md

Lines changed: 33 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,31 @@
2020

2121
Represents packed pixels from a GIMP image hierarchy.
2222

23-
NOTE: Originally designed as a hierarchy, but currently only the top level (64x64) is used.
23+
Note that the XCF docs say this was originally designed as a hierarchy,
24+
but currently only the top level (lptr) is used
25+
26+
uint32 width Width of the pixel array
27+
uint32 height Height of the pixel array
28+
uint32 bpp Number of bytes per pixel; this depends on the
29+
color mode and image precision (fields 'base_type'
30+
and 'precision' of the image header). For
31+
instance, some combination values:
32+
3: RGB color without alpha in 8-bit precision
33+
4: RGB color with alpha in 8-bit precision
34+
6: RGB color without alpha in 16-bit precision
35+
16: RGB color with alpha in 32-bit precision
36+
1: Grayscale without alpha in 8-bit precision
37+
4: Grayscale with alpha in 16-bit precision
38+
1: Indexed without alpha (always 8-bit)
39+
2: Indexed with alpha (always 8-bit)
40+
And so on.
41+
42+
pointer lptr Pointer to the "level" structure
43+
,-------- ------ Repeat zero or more times
44+
| pointer dlevel Pointer to an unused level structure (dummy level)
45+
`--
46+
pointer 0 Zero marks the end of the list of level pointers.
47+
.
2448

2549
#### Signature
2650

@@ -35,7 +59,7 @@ class GimpImageHierarchy(GimpIOBase):
3559

3660
### GimpImageHierarchy().__repr__
3761

38-
[Show source in GimpImageHierarchy.py:112](../../../gimpformats/GimpImageHierarchy.py#L112)
62+
[Show source in GimpImageHierarchy.py:141](../../../gimpformats/GimpImageHierarchy.py#L141)
3963

4064
Get a textual representation of this object.
4165

@@ -47,7 +71,7 @@ def __repr__(self) -> str: ...
4771

4872
### GimpImageHierarchy().__str__
4973

50-
[Show source in GimpImageHierarchy.py:108](../../../gimpformats/GimpImageHierarchy.py#L108)
74+
[Show source in GimpImageHierarchy.py:137](../../../gimpformats/GimpImageHierarchy.py#L137)
5175

5276
Get a textual representation of this object.
5377

@@ -59,7 +83,7 @@ def __str__(self) -> str: ...
5983

6084
### GimpImageHierarchy().decode
6185

62-
[Show source in GimpImageHierarchy.py:34](../../../gimpformats/GimpImageHierarchy.py#L34)
86+
[Show source in GimpImageHierarchy.py:58](../../../gimpformats/GimpImageHierarchy.py#L58)
6387

6488
Decode packed pixels from a byte buffer.
6589

@@ -71,19 +95,19 @@ def decode(self, data: bytearray, index: int = 0) -> int: ...
7195

7296
### GimpImageHierarchy().encode
7397

74-
[Show source in GimpImageHierarchy.py:62](../../../gimpformats/GimpImageHierarchy.py#L62)
98+
[Show source in GimpImageHierarchy.py:88](../../../gimpformats/GimpImageHierarchy.py#L88)
7599

76100
Encode packed pixels data into a byte buffer.
77101

78102
#### Signature
79103

80104
```python
81-
def encode(self) -> bytearray: ...
105+
def encode(self, offset: int = 0) -> bytearray: ...
82106
```
83107

84108
### GimpImageHierarchy().image
85109

86-
[Show source in GimpImageHierarchy.py:93](../../../gimpformats/GimpImageHierarchy.py#L93)
110+
[Show source in GimpImageHierarchy.py:122](../../../gimpformats/GimpImageHierarchy.py#L122)
87111

88112
Get a final, compiled image.
89113

@@ -96,7 +120,7 @@ def image(self) -> Image.Image | None: ...
96120

97121
### GimpImageHierarchy().image
98122

99-
[Show source in GimpImageHierarchy.py:98](../../../gimpformats/GimpImageHierarchy.py#L98)
123+
[Show source in GimpImageHierarchy.py:127](../../../gimpformats/GimpImageHierarchy.py#L127)
100124

101125
Set the image.
102126

@@ -109,7 +133,7 @@ def image(self, image: Image.Image) -> None: ...
109133

110134
### GimpImageHierarchy().levels
111135

112-
[Show source in GimpImageHierarchy.py:79](../../../gimpformats/GimpImageHierarchy.py#L79)
136+
[Show source in GimpImageHierarchy.py:108](../../../gimpformats/GimpImageHierarchy.py#L108)
113137

114138
Get the levels within this hierarchy.
115139

documentation/reference/gimpformats/GimpImageLevel.md

Lines changed: 21 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,15 @@ Gets packed pixels from a gimp image.
2626

2727
This represents a single level in an imageHierarchy
2828

29+
The level structure for the first level is laid out as follows:
30+
31+
uint32 width Width of the pixel array
32+
uint32 height Height of the pixel array
33+
,----------------- Repeat for each of the ceil(width/64)*ceil(height/64) tiles
34+
| pointer tptr Pointer to tile data
35+
`--
36+
pointer 0 Zero marks the end of the array of tile pointers.
37+
2938
#### Signature
3039

3140
```python
@@ -39,7 +48,7 @@ class GimpImageLevel(GimpIOBase):
3948

4049
### GimpImageLevel().__repr__
4150

42-
[Show source in GimpImageLevel.py:301](../../../gimpformats/GimpImageLevel.py#L301)
51+
[Show source in GimpImageLevel.py:310](../../../gimpformats/GimpImageLevel.py#L310)
4352

4453
Get a textual representation of this object.
4554

@@ -51,7 +60,7 @@ def __repr__(self) -> str: ...
5160

5261
### GimpImageLevel().__str__
5362

54-
[Show source in GimpImageLevel.py:297](../../../gimpformats/GimpImageLevel.py#L297)
63+
[Show source in GimpImageLevel.py:306](../../../gimpformats/GimpImageLevel.py#L306)
5564

5665
Get a textual representation of this object.
5766

@@ -63,7 +72,7 @@ def __str__(self) -> str: ...
6372

6473
### GimpImageLevel()._encodeRLE
6574

66-
[Show source in GimpImageLevel.py:165](../../../gimpformats/GimpImageLevel.py#L165)
75+
[Show source in GimpImageLevel.py:174](../../../gimpformats/GimpImageLevel.py#L174)
6776

6877
Encode image to RLE image data.
6978

@@ -75,7 +84,7 @@ def _encodeRLE(self, data: bytearray, bpp: int) -> bytearray: ...
7584

7685
### GimpImageLevel()._imgToTiles
7786

78-
[Show source in GimpImageLevel.py:262](../../../gimpformats/GimpImageLevel.py#L262)
87+
[Show source in GimpImageLevel.py:271](../../../gimpformats/GimpImageLevel.py#L271)
7988

8089
Break an image into a series of tiles, each<=64x64.
8190

@@ -87,7 +96,7 @@ def _imgToTiles(self, image: Image.Image) -> list[Image.Image]: ...
8796

8897
### GimpImageLevel().bpp
8998

90-
[Show source in GimpImageLevel.py:242](../../../gimpformats/GimpImageLevel.py#L242)
99+
[Show source in GimpImageLevel.py:251](../../../gimpformats/GimpImageLevel.py#L251)
91100

92101
Get bpp.
93102

@@ -100,7 +109,7 @@ def bpp(self) -> int: ...
100109

101110
### GimpImageLevel().decode
102111

103-
[Show source in GimpImageLevel.py:30](../../../gimpformats/GimpImageLevel.py#L30)
112+
[Show source in GimpImageLevel.py:40](../../../gimpformats/GimpImageLevel.py#L40)
104113

105114
Decode a byte buffer.
106115

@@ -117,19 +126,19 @@ def decode(self, data: bytearray | bytes | None, index: int = 0) -> int: ...
117126

118127
### GimpImageLevel().encode
119128

120-
[Show source in GimpImageLevel.py:74](../../../gimpformats/GimpImageLevel.py#L74)
129+
[Show source in GimpImageLevel.py:84](../../../gimpformats/GimpImageLevel.py#L84)
121130

122131
Encode this object to a byte buffer.
123132

124133
#### Signature
125134

126135
```python
127-
def encode(self, offset=0) -> bytearray: ...
136+
def encode(self, offset: int = 0) -> bytearray: ...
128137
```
129138

130139
### GimpImageLevel().image
131140

132-
[Show source in GimpImageLevel.py:273](../../../gimpformats/GimpImageLevel.py#L273)
141+
[Show source in GimpImageLevel.py:282](../../../gimpformats/GimpImageLevel.py#L282)
133142

134143
Get a final, compiled image.
135144

@@ -142,7 +151,7 @@ def image(self) -> Image.Image: ...
142151

143152
### GimpImageLevel().image
144153

145-
[Show source in GimpImageLevel.py:289](../../../gimpformats/GimpImageLevel.py#L289)
154+
[Show source in GimpImageLevel.py:298](../../../gimpformats/GimpImageLevel.py#L298)
146155

147156
#### Signature
148157

@@ -153,7 +162,7 @@ def image(self, image: Image.Image) -> None: ...
153162

154163
### GimpImageLevel().mode
155164

156-
[Show source in GimpImageLevel.py:247](../../../gimpformats/GimpImageLevel.py#L247)
165+
[Show source in GimpImageLevel.py:256](../../../gimpformats/GimpImageLevel.py#L256)
157166

158167
Get mode.
159168

@@ -166,7 +175,7 @@ def mode(self) -> str: ...
166175

167176
### GimpImageLevel().tiles
168177

169-
[Show source in GimpImageLevel.py:253](../../../gimpformats/GimpImageLevel.py#L253)
178+
[Show source in GimpImageLevel.py:262](../../../gimpformats/GimpImageLevel.py#L262)
170179

171180
Get tiles.
172181

gimpformats/GimpImageHierarchy.py

Lines changed: 37 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,31 @@ class GimpImageHierarchy(GimpIOBase):
1717
"""
1818
Represents packed pixels from a GIMP image hierarchy.
1919
20-
NOTE: Originally designed as a hierarchy, but currently only the top level (64x64) is used.
20+
Note that the XCF docs say this was originally designed as a hierarchy,
21+
but currently only the top level (lptr) is used
22+
23+
uint32 width Width of the pixel array
24+
uint32 height Height of the pixel array
25+
uint32 bpp Number of bytes per pixel; this depends on the
26+
color mode and image precision (fields 'base_type'
27+
and 'precision' of the image header). For
28+
instance, some combination values:
29+
3: RGB color without alpha in 8-bit precision
30+
4: RGB color with alpha in 8-bit precision
31+
6: RGB color without alpha in 16-bit precision
32+
16: RGB color with alpha in 32-bit precision
33+
1: Grayscale without alpha in 8-bit precision
34+
4: Grayscale with alpha in 16-bit precision
35+
1: Indexed without alpha (always 8-bit)
36+
2: Indexed with alpha (always 8-bit)
37+
And so on.
38+
39+
pointer lptr Pointer to the "level" structure
40+
,-------- ------ Repeat zero or more times
41+
| pointer dlevel Pointer to an unused level structure (dummy level)
42+
`--
43+
pointer 0 Zero marks the end of the list of level pointers.
44+
.
2145
"""
2246

2347
def __init__(self, parent, image: Image.Image | None = None) -> None:
@@ -35,6 +59,7 @@ def decode(self, data: bytearray, index: int = 0) -> int:
3559
"""
3660
Decode packed pixels from a byte buffer.
3761
"""
62+
3863
if not data:
3964
msg = "No data provided for decoding."
4065
raise RuntimeError(msg)
@@ -54,26 +79,30 @@ def decode(self, data: bytearray, index: int = 0) -> int:
5479
break
5580
self._levelPtrs.append(ptr)
5681

82+
# Here we enforce that only the first pointer is actually used
5783
if self._levelPtrs:
5884
self._levelPtrs = [self._levelPtrs[0]]
5985
self._data = data
6086
return ioBuf.index
6187

62-
def encode(self) -> bytearray:
88+
def encode(self, offset: int = 0) -> bytearray:
6389
"""
6490
Encode packed pixels data into a byte buffer.
6591
"""
66-
dataioBuf = IO()
6792
ioBuf = IO()
6893
ioBuf.u32 = self.width
6994
ioBuf.u32 = self.height
7095
ioBuf.u32 = self.bpp
71-
dataIndex = ioBuf.index + self.pointerSize * (len(self.levels) + 1)
72-
for level in self.levels:
73-
ioBuf.addbytearray(self._pointerEncode(dataIndex + dataioBuf.index))
74-
dataioBuf.addbytearray(level.encode())
96+
pointerSizeb = self.pointerSize // 8
97+
98+
dataIndex = offset + ioBuf.index + pointerSizeb + pointerSizeb
99+
100+
topLevel = self.levels[0]
101+
data = topLevel.encode(offset=dataIndex)
102+
ioBuf.addbytearray(self._pointerEncode(dataIndex))
75103
ioBuf.addbytearray(self._pointerEncode(0))
76-
ioBuf.addbytearray(dataioBuf.data)
104+
ioBuf.addbytearray(data)
105+
77106
return ioBuf.data
78107

79108
@property

gimpformats/GimpImageLevel.py

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,16 @@ class GimpImageLevel(GimpIOBase):
1818
"""Gets packed pixels from a gimp image.
1919
2020
This represents a single level in an imageHierarchy
21+
22+
The level structure for the first level is laid out as follows:
23+
24+
uint32 width Width of the pixel array
25+
uint32 height Height of the pixel array
26+
,----------------- Repeat for each of the ceil(width/64)*ceil(height/64) tiles
27+
| pointer tptr Pointer to tile data
28+
`--
29+
pointer 0 Zero marks the end of the array of tile pointers.
30+
2131
"""
2232

2333
def __init__(self, parent: Any) -> None:
@@ -71,14 +81,14 @@ def decode(self, data: bytearray | bytes | None, index: int = 0) -> int:
7181
_ = self._pointerDecode(ioBuf) # list ends with nul character
7282
return ioBuf.index
7383

74-
def encode(self, offset=0) -> bytearray:
84+
def encode(self, offset:int=0) -> bytearray:
7585
"""Encode this object to a byte buffer."""
7686
ioBuf = IO()
7787
ioBuf.u32 = self.width
7888
ioBuf.u32 = self.height
7989
pointerSizeb = self.pointerSize // 8
80-
# This is a super dodgy line atm, I think I'm missing something!
81-
dataIndex = offset + ioBuf.index - 4 # ? + * (len(self.tiles or []) + 1)
90+
91+
dataIndex = offset + ioBuf.index + pointerSizeb * (len(self.tiles or []) + 1)
8292
computed_tiles = []
8393
for tile in self.tiles or []:
8494
data = tile.tobytes(encoder_name="raw")
@@ -92,9 +102,8 @@ def encode(self, offset=0) -> bytearray:
92102
msg = f"ERR: unsupported compression mode {self.doc.compression}"
93103
raise RuntimeError(msg)
94104

95-
dataIndex += pointerSizeb + len(data)
96-
97105
ioBuf.addbytearray(self._pointerEncode(dataIndex))
106+
dataIndex += len(data)
98107

99108
computed_tiles.append(data)
100109

tests/image_hierarchy/__init__.py

Whitespace-only changes.

tests/image_hierarchy/test.xcf.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
b'gimp xcf v011\x00\x00\x00\x00@\x00\x00\x00@\x00\x00\x00\x00\x00\x00\x00\x96\x00\x00\x00\x11\x00\x00\x00\x01\x01\x00\x00\x00\x13\x00\x00\x00\x08B\x90\x00\x00B\x90\x00\x00\x00\x00\x00\x14\x00\x00\x00\x04\x00\x00\x00\x03\x00\x00\x00\x16\x00\x00\x00\x04\x00\x00\x00\x01\x00\x00\x00\x15\x00\x00\x00\xf3\x00\x00\x00\rgimp-comment\x00\x00\x00\x00\x01\x00\x00\x00\x12Created with GIMP\x00\x00\x00\x00\x10gimp-image-grid\x00\x00\x00\x00\x01\x00\x00\x00\xac(style solid)\n(fgcolor (color-rgba 0 0 0 1))\n(bgcolor (color-rgba 1 1 1 1))\n(xspacing 16)\n(yspacing 16)\n(spacing-unit inches)\n(xoffset 0)\n(yoffset 0)\n(offset-unit inches)\n\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01r\x00\x00\x00\x00\x00\x00\x02\xce\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00@\x00\x00\x00@\x00\x00\x00\x01\x00\x00\x00\x0cLayer Group\x00\x00\x00\x00\x1d\x00\x00\x00\x00\x00\x00\x00\x06\x00\x00\x00\x04\x00\x00\x00\xff\x00\x00\x00!\x00\x00\x00\x04?\x80\x00\x00\x00\x00\x00\x08\x00\x00\x00\x04\x00\x00\x00\x01\x00\x00\x00\t\x00\x00\x00\x04\x00\x00\x00\x00\x00\x00\x00"\x00\x00\x00\x04\x00\x00\x00\x00\x00\x00\x00\x1c\x00\x00\x00\x04\x00\x00\x00\x00\x00\x00\x00\n\x00\x00\x00\x04\x00\x00\x00\x00\x00\x00\x00 \x00\x00\x00\x04\x00\x00\x00\x00\x00\x00\x00\x0b\x00\x00\x00\x04\x00\x00\x00\x00\x00\x00\x00\x0c\x00\x00\x00\x04\x00\x00\x00\x00\x00\x00\x00\r\x00\x00\x00\x04\x00\x00\x00\x00\x00\x00\x00\x0f\x00\x00\x00\x08\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x07\x00\x00\x00\x04\x00\x00\x00\x1c\x00\x00\x00%\x00\x00\x00\x04\x00\x00\x00\x00\x00\x00\x00$\x00\x00\x00\x04\xff\xff\xff\xff\x00\x00\x00#\x00\x00\x00\x04\xff\xff\xff\xff\x00\x00\x00\x14\x00\x00\x00\x04\x00\x00\x00\x03\x00\x00\x00\x1f\x00\x00\x00\x04\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x8a\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00@\x00\x00\x00@\x00\x00\x00\x04\x00\x00\x00\x00\x00\x00\x02\xa6\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00@\x00\x00\x00@\x00\x00\x00\x00\x00\x00\x02\xbe\x00\x00\x00\x00\x00\x00\x00\x00\x7f\x10\x00\xaa\x7f\x10\x00\xaa\x7f\x10\x00\xaa\x7f\x10\x00\xff\x00\x00\x00@\x00\x00\x00@\x00\x00\x00\x00\x00\x00\x00\x0bBackground\x00\x00\x00\x00\x1e\x00\x00\x00\x08\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x00\x00\x00\x00\x00\x00\x06\x00\x00\x00\x04\x00\x00\x00\xff\x00\x00\x00!\x00\x00\x00\x04?\x80\x00\x00\x00\x00\x00\x08\x00\x00\x00\x04\x00\x00\x00\x01\x00\x00\x00\t\x00\x00\x00\x04\x00\x00\x00\x00\x00\x00\x00"\x00\x00\x00\x04\x00\x00\x00\x00\x00\x00\x00\x1c\x00\x00\x00\x04\x00\x00\x00\x00\x00\x00\x00\n\x00\x00\x00\x04\x00\x00\x00\x00\x00\x00\x00 \x00\x00\x00\x04\x00\x00\x00\x00\x00\x00\x00\x0b\x00\x00\x00\x04\x00\x00\x00\x00\x00\x00\x00\x0c\x00\x00\x00\x04\x00\x00\x00\x00\x00\x00\x00\r\x00\x00\x00\x04\x00\x00\x00\x00\x00\x00\x00\x0f\x00\x00\x00\x08\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x07\x00\x00\x00\x04\x00\x00\x00\x1c\x00\x00\x00%\x00\x00\x00\x04\x00\x00\x00\x00\x00\x00\x00$\x00\x00\x00\x04\xff\xff\xff\xff\x00\x00\x00#\x00\x00\x00\x04\xff\xff\xff\xff\x00\x00\x00\x14\x00\x00\x00\x04\x00\x00\x00\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x03\xe9\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00@\x00\x00\x00@\x00\x00\x00\x03\x00\x00\x00\x00\x00\x00\x04\x05\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00@\x00\x00\x00@\x00\x00\x00\x00\x00\x00\x04\x1d\x00\x00\x00\x00\x00\x00\x00\x00\x7f\x10\x00\xaa\x7f\x10\x00\xaa\x7f\x10\x00\xaa'

0 commit comments

Comments
 (0)