limage - xcf/decoder.go
1 // Package xcf implements an image encoder and decoder for GIMPs XCF format
2 package xcf // import "vimagination.zapto.org/limage/xcf"
3
4 import (
5 "errors"
6 "image"
7 "image/color"
8 "io"
9 "sync"
10
11 "vimagination.zapto.org/limage"
12 "vimagination.zapto.org/limage/internal"
13 "vimagination.zapto.org/limage/lcolor"
14 )
15
16 func decodeConfig(r io.Reader) (image.Config, error) {
17 ra := internal.GetReaderAt(r)
18 if ra == nil {
19 return image.Config{}, ErrNeedReaderAt
20 }
21 return DecodeConfig(ra)
22 }
23
24 func decode(r io.Reader) (image.Image, error) {
25 ra := internal.GetReaderAt(r)
26 if ra == nil {
27 return nil, ErrNeedReaderAt
28 }
29 return Decode(ra)
30 }
31
32 func init() {
33 image.RegisterFormat("xcf", fileTypeID, decode, decodeConfig)
34 }
35
36 const (
37 fileTypeID = "gimp xcf "
38 fileVersion0 = "file"
39 fileVersion1 = "v001"
40 fileVersion2 = "v002"
41 fileVersion3 = "v003"
42 fileVersion4 = "v004"
43 fileVersion5 = "v005"
44 fileVersion6 = "v006"
45 fileVersion7 = "v007"
46 fileVersion8 = "v008"
47 fileVersion9 = "v009"
48 fileVersion10 = "v010"
49 fileVersion11 = "v011"
50 fileVersion12 = "v012"
51 fileVersion13 = "v013"
52 )
53
54 const (
55 //baseRGB = 0
56 //baseGrey = 1
57 baseIndexed = 2
58 )
59
60 type decoder struct {
61 reader
62 compression uint8
63 decompress bool
64 baseType uint32
65 palette lcolor.AlphaPalette
66 precision uint32
67 mode uint32
68 }
69
70 // DecodeConfig retrieves the color model and dimensions of the XCF image
71 func DecodeConfig(r io.ReaderAt) (image.Config, error) {
72 var c image.Config
73
74 dr := newReader(r)
75
76 // check header
77
78 var header [14]byte
79 dr.Read(header[:])
80 if dr.Err != nil {
81 return c, dr.Err
82 }
83 if string(header[:9]) != fileTypeID {
84 return c, ErrInvalidFileTypeID
85 }
86 var newMode bool
87 switch string(header[9:13]) {
88 case fileVersion0, fileVersion1, fileVersion2, fileVersion3:
89 case fileVersion4, fileVersion5, fileVersion6, fileVersion7, fileVersion8, fileVersion9, fileVersion10, fileVersion11, fileVersion12, fileVersion13:
90 newMode = true
91 default:
92 return c, ErrUnsupportedVersion
93 }
94 if header[13] != 0 {
95 return c, ErrInvalidHeader
96 }
97
98 c.Width = int(dr.ReadUint32())
99 c.Height = int(dr.ReadUint32())
100 baseType := dr.ReadUint32()
101 if newMode {
102 dr.ReadUint32()
103 }
104 switch baseType {
105 case 0:
106 c.ColorModel = color.NRGBAModel
107 case 1:
108 c.ColorModel = lcolor.GrayAlphaModel
109 case 2:
110 PropertyLoop:
111 for {
112 typ := dr.ReadUint32()
113 plength := dr.ReadUint32()
114 switch typ {
115 case propEnd:
116 if plength != 0 {
117 return c, ErrInvalidProperties
118 }
119 break PropertyLoop
120
121 // the one we care about
122 case propColorMap:
123 if baseType != baseIndexed {
124 dr.Skip(plength) // skip
125 }
126 palette := make(lcolor.AlphaPalette, dr.ReadUint32())
127 for n := range palette {
128 r := dr.ReadUint8()
129 g := dr.ReadUint8()
130 b := dr.ReadUint8()
131 palette[n] = lcolor.RGB{
132 R: r,
133 G: g,
134 B: b,
135 }
136 }
137 c.ColorModel = palette
138 break PropertyLoop
139
140 //general properties
141 case propLinked:
142 dr.SkipBoolProperty()
143 case propLockContent:
144 dr.SkipBoolProperty()
145 case propOpacity:
146 if o := dr.ReadUint32(); o > 255 {
147 return c, ErrInvalidOpacity
148 }
149 case propParasites:
150 dr.SkipParasites(plength)
151 case propTattoo:
152 dr.SkipUint32()
153 case propVisible:
154 dr.SkipBoolProperty()
155 case propCompression:
156 if dr.ReadUint8() > 1 {
157 return c, ErrUnknownCompression
158 }
159 case propGuides:
160 ng := plength / 5
161 if ng*5 != plength {
162 return c, ErrInvalidGuideLength
163 }
164 for n := uint32(0); n < ng; n++ {
165 dr.SkipUint32()
166 dr.SkipBoolProperty()
167 }
168 case propPaths:
169 dr.SkipPaths()
170 case propResolution:
171 dr.SkipFloat32()
172 dr.SkipFloat32()
173 case propSamplePoints:
174 if plength&1 == 1 {
175 return c, ErrInvalidSampleLength
176 }
177 for i := uint32(0); i < plength>>1; i++ {
178 dr.SkipUint32()
179 dr.SkipUint32()
180 }
181 case propUnit:
182 if unit := dr.ReadUint32(); unit > 4 {
183 return c, ErrInvalidUnit
184 }
185 case propUserUnit:
186 dr.SkipFloat32()
187 dr.SkipUint32()
188 dr.SkipString()
189 dr.SkipString()
190 dr.SkipString()
191 dr.SkipString()
192 dr.SkipString()
193 case propVectors:
194 dr.SkipVectors()
195 default:
196 dr.Skip(plength)
197 }
198 }
199 }
200
201 return c, dr.Err
202 }
203
204 // Decode reads an XCF layered image from the given ReaderAt
205 func Decode(r io.ReaderAt) (limage.Image, error) {
206 return decodeImage(r, true)
207 }
208
209 // DecodeCompressed reads an XCF layered image, as Decode, but defers decoding
210 // and decompressing, doing so upon an At method
211 func DecodeCompressed(r io.ReaderAt) (limage.Image, error) {
212 return decodeImage(r, false)
213 }
214
215 func decodeImage(r io.ReaderAt, decompress bool) (limage.Image, error) {
216 dr := newReader(r)
217
218 // check header
219
220 var header [14]byte
221 dr.Read(header[:])
222 if dr.Err != nil {
223 return nil, dr.Err // wrap?
224 }
225 if string(header[:9]) != fileTypeID {
226 return nil, ErrInvalidFileTypeID
227 }
228 var mode uint32
229 switch string(header[9:13]) {
230 case fileVersion0, fileVersion1, fileVersion2, fileVersion3:
231 case fileVersion4, fileVersion5, fileVersion6, fileVersion7, fileVersion8, fileVersion9, fileVersion10:
232 mode = 1
233 case fileVersion11, fileVersion12, fileVersion13:
234 mode = 2
235 default:
236 return nil, ErrUnsupportedVersion
237 }
238 if header[13] != 0 {
239 return nil, ErrInvalidHeader
240 }
241
242 width := int(dr.ReadUint32())
243 height := int(dr.ReadUint32())
244 bounds := image.Rect(0, 0, width, height)
245 baseType := dr.ReadUint32()
246 var precision uint32
247 if mode > 0 {
248 precision = dr.ReadUint32()
249 }
250
251 var (
252 palette lcolor.AlphaPalette
253 compression uint8
254 )
255
256 // read image properties
257 PropertyLoop:
258 for {
259 typ := dr.ReadUint32()
260 plength := dr.ReadUint32()
261 switch typ {
262 case propEnd:
263 if plength != 0 {
264 return nil, ErrInvalidProperties
265 }
266 break PropertyLoop
267
268 //general properties
269 case propLinked:
270 dr.ReadBoolProperty()
271 case propLockContent:
272 dr.ReadBoolProperty()
273 case propOpacity:
274 o := dr.ReadUint32()
275 if o > 255 {
276 return nil, ErrInvalidOpacity
277 }
278 case propParasites:
279 dr.ReadParasites(plength)
280 case propTattoo:
281 dr.ReadUint32()
282 case propVisible:
283 dr.ReadBoolProperty()
284
285 // image properties
286 case propColorMap:
287 if baseType != baseIndexed {
288 dr.Skip(plength) // skip
289 }
290 palette = make(lcolor.AlphaPalette, dr.ReadUint32())
291 for n := range palette {
292 r := dr.ReadUint8()
293 g := dr.ReadUint8()
294 b := dr.ReadUint8()
295 palette[n] = lcolor.RGB{
296 R: r,
297 G: g,
298 B: b,
299 }
300 }
301 case propCompression:
302 compression = dr.ReadUint8()
303 if compression > 1 {
304 return nil, ErrUnknownCompression
305 }
306 case propGuides:
307 ng := plength / 5
308 if ng*5 != plength {
309 return nil, ErrInvalidGuideLength
310 }
311 for n := uint32(0); n < ng; n++ {
312 dr.SkipUint32()
313 dr.SkipBoolProperty()
314 }
315 case propPaths:
316 dr.SkipPaths()
317 case propResolution:
318 dr.SkipFloat32() // x
319 dr.SkipFloat32() // y
320 case propSamplePoints:
321 if plength&1 == 1 {
322 return nil, ErrInvalidSampleLength
323 }
324 for i := uint32(0); i < plength>>1; i++ {
325 dr.SkipUint32()
326 dr.SkipUint32()
327 }
328 case propUnit:
329 if dr.ReadUint32() > 4 {
330 return nil, ErrInvalidUnit
331 }
332 case propUserUnit:
333 dr.SkipFloat32() // factor
334 dr.SkipUint32() // number of decimal igits
335 dr.SkipString() // id
336 dr.SkipString() // symbol
337 dr.SkipString() // abbr.
338 dr.SkipString() // singular name
339 dr.SkipString() // plural name
340 case propVectors:
341 dr.SkipVectors()
342 default:
343 dr.Skip(plength)
344 }
345 }
346
347 layerptrs := make([]uint64, 0, 32)
348 for {
349 var lptr uint64
350 if mode < 2 {
351 lptr = uint64(dr.ReadUint32())
352 } else {
353 lptr = dr.ReadUint64()
354 }
355 if lptr == 0 {
356 break
357 }
358 layerptrs = append(layerptrs, lptr)
359 }
360
361 if dr.Err != nil {
362 return nil, dr.Err
363 }
364
365 type groupOffset struct {
366 Group limage.Image
367 OffsetX, OffsetY int
368 Parent *limage.Image
369 Offset int
370 }
371
372 var (
373 groups = make(map[string]*groupOffset)
374 n rune
375 alpha = true
376 )
377
378 layers := make([]layer, len(layerptrs))
379
380 var (
381 errLock sync.Mutex
382 wg sync.WaitGroup
383 )
384 wg.Add(len(layerptrs))
385 for n, lptr := range layerptrs {
386 go func(n int, lptr uint64) {
387 d := decoder{
388 reader: newReader(r),
389 baseType: baseType,
390 palette: palette,
391 compression: compression,
392 decompress: decompress,
393 precision: precision,
394 mode: mode,
395 }
396 d.Goto(lptr)
397 layers[n] = d.ReadLayer()
398 if d.Err != nil {
399 errLock.Lock()
400 dr.SetError(d.Err)
401 errLock.Unlock()
402 }
403 wg.Done()
404 }(n, lptr)
405 }
406
407 wg.Wait()
408
409 if dr.Err != nil {
410 return nil, dr.Err
411 }
412
413 groups[""] = &groupOffset{Group: make(limage.Image, 0, 32)}
414 for _, l := range layers {
415 if !alpha {
416 return nil, ErrMissingAlpha
417 }
418 alpha = l.alpha
419 if len(l.itemPath) == 0 {
420 l.itemPath = []rune{n}
421 n++
422 }
423 g := groups[string(l.itemPath[:len(l.itemPath)-1])]
424 if g == nil {
425 return nil, ErrInvalidGroup
426 }
427 if l.group {
428 groups[string(l.itemPath)] = &groupOffset{
429 Group: make(limage.Image, 0, 32),
430 OffsetX: l.LayerBounds.Min.X,
431 OffsetY: l.LayerBounds.Min.Y,
432 Parent: &g.Group,
433 Offset: len(g.Group),
434 }
435 }
436 l.LayerBounds = l.LayerBounds.Intersect(bounds).Sub(image.Pt(g.OffsetX, g.OffsetY))
437 g.Group = append(g.Group, l.Layer)
438 }
439
440 var im limage.Image
441
442 for _, g := range groups {
443 ng := make(limage.Image, len(g.Group))
444 copy(ng, g.Group)
445 g.Group = ng
446 if g.Parent == nil {
447 im = ng
448 } else {
449 (*g.Parent)[g.Offset].Image = ng
450 }
451 }
452
453 if len(im) > 0 {
454 switch im[len(im)-1].Mode {
455 case limage.CompositeNormal, limage.CompositeDissolve:
456 default:
457 im[len(im)-1].Mode = 0
458 }
459 }
460
461 return im, nil
462 }
463
464 // Errors
465 var (
466 ErrInvalidFileTypeID = errors.New("invalid file type identification")
467 ErrUnsupportedVersion = errors.New("unsupported file version")
468 ErrInvalidHeader = errors.New("invalid header")
469 ErrInvalidProperties = errors.New("invalid property list")
470 ErrInvalidOpacity = errors.New("opacity not in valid range")
471 ErrInvalidGuideLength = errors.New("invalid guide length")
472 ErrInvalidUnit = errors.New("invalid unit")
473 ErrInvalidSampleLength = errors.New("invalid sample points length")
474 ErrInvalidGroup = errors.New("invalid or unknown group specified for layer")
475 ErrUnknownCompression = errors.New("unknown compression method")
476 ErrMissingAlpha = errors.New("non-bottom layer missing alpha channel")
477 ErrNeedReaderAt = errors.New("need a io.ReaderAt")
478 )
479