ttyengine 0.1.0__py3-none-any.whl

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.
@@ -0,0 +1,813 @@
1
+ import os, re, struct, subprocess, zlib
2
+ from array import array
3
+ from math import cos, pi, sqrt
4
+
5
+
6
+ class ImageError(ValueError):
7
+ pass
8
+
9
+
10
+ def read(path):
11
+ path = os.fspath(path)
12
+ with open(path, 'rb') as f:
13
+ data = f.read()
14
+ return decode(data, path)
15
+
16
+
17
+ def decode(data, path=None):
18
+ error = None
19
+ for check, decoder in _FORMATS:
20
+ if check(data):
21
+ try:
22
+ return decoder(data)
23
+ except ImageError as e:
24
+ error = e
25
+ except (ArithmeticError, LookupError, MemoryError, TypeError, ValueError, StopIteration, struct.error, zlib.error) as e:
26
+ error = ImageError('corrupt or truncated image data')
27
+ error.__cause__ = e
28
+ break
29
+ converted, reason = _ffmpeg(data, path)
30
+ if converted is not None:
31
+ return converted
32
+ if error is not None:
33
+ raise error
34
+ raise ImageError('unsupported image format (' + reason + ')')
35
+
36
+
37
+ def _ffmpeg(data, path):
38
+ from .sound import find_ffmpeg
39
+ exe = find_ffmpeg()
40
+ if not exe:
41
+ return None, 'PNG, JPEG, GIF, BMP and PNM are built in; install ffmpeg to read other formats'
42
+ command = [exe, '-v', 'error', '-nostdin', '-i', '{}', '-an', '-sn', '-frames:v', '1', '-c:v', 'png',
43
+ '-pix_fmt', 'rgba', '-pred', 'none', '-f', 'image2pipe', 'pipe:1']
44
+ sources = [('pipe:0', {'input': data})]
45
+ if path is not None:
46
+ sources.append(('file:' + os.path.abspath(path), {'stdin': subprocess.DEVNULL}))
47
+ reason = 'ffmpeg could not decode the image'
48
+ for source, options in sources:
49
+ args = [source if part == '{}' else part for part in command]
50
+ try:
51
+ proc = subprocess.run(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE, timeout=120, **options)
52
+ except (OSError, subprocess.SubprocessError) as e:
53
+ reason = f'ffmpeg failed: {e}'
54
+ continue
55
+ if proc.returncode == 0 and _is_png(proc.stdout):
56
+ try:
57
+ return _png(proc.stdout), ''
58
+ except (ImageError, ArithmeticError, LookupError, MemoryError, TypeError, ValueError, struct.error, zlib.error):
59
+ reason = 'ffmpeg produced unreadable output'
60
+ continue
61
+ lines = proc.stderr.decode('utf-8', 'replace').strip().splitlines()
62
+ if lines:
63
+ reason = 'ffmpeg: ' + lines[-1]
64
+ return None, reason
65
+
66
+
67
+ def _blank(w, h):
68
+ if w < 1 or h < 1 or w * h > 1 << 28:
69
+ raise ImageError('invalid image dimensions')
70
+ return bytearray(w * h * 4)
71
+
72
+
73
+ _ADAM7 = ((0, 0, 8, 8), (4, 0, 8, 8), (0, 4, 4, 8), (2, 0, 4, 4), (0, 2, 2, 4), (1, 0, 2, 2), (0, 1, 1, 2))
74
+ _PNG_CHANNELS = {0: 1, 2: 3, 3: 1, 4: 2, 6: 4}
75
+
76
+
77
+ def _is_png(d):
78
+ return d.startswith(b'\x89PNG\r\n\x1a\n')
79
+
80
+
81
+ def _png_unfilter(raw, pos, stride, bpp, rows):
82
+ prev = bytearray(stride)
83
+ out = []
84
+ for _ in range(rows):
85
+ ft = raw[pos]
86
+ line = bytearray(raw[pos + 1:pos + 1 + stride])
87
+ if len(line) != stride:
88
+ raise ImageError('truncated PNG data')
89
+ pos += 1 + stride
90
+ if ft == 1:
91
+ for i in range(bpp, stride):
92
+ line[i] = (line[i] + line[i - bpp]) & 255
93
+ elif ft == 2:
94
+ for i in range(stride):
95
+ line[i] = (line[i] + prev[i]) & 255
96
+ elif ft == 3:
97
+ for i in range(stride):
98
+ left = line[i - bpp] if i >= bpp else 0
99
+ line[i] = (line[i] + ((left + prev[i]) >> 1)) & 255
100
+ elif ft == 4:
101
+ for i in range(stride):
102
+ a = line[i - bpp] if i >= bpp else 0
103
+ b = prev[i]
104
+ c = prev[i - bpp] if i >= bpp else 0
105
+ p = a + b - c
106
+ pa, pb, pc = abs(p - a), abs(p - b), abs(p - c)
107
+ pr = a if pa <= pb and pa <= pc else (b if pb <= pc else c)
108
+ line[i] = (line[i] + pr) & 255
109
+ elif ft != 0:
110
+ raise ImageError('invalid PNG filter')
111
+ out.append(line)
112
+ prev = line
113
+ return out, pos
114
+
115
+
116
+ def _png_samples(line, count, depth):
117
+ if depth == 8:
118
+ return line[:count]
119
+ if depth == 16:
120
+ return line[0:count * 2:2]
121
+ per = 8 // depth
122
+ mask = (1 << depth) - 1
123
+ return [(line[i // per] >> (8 - depth * (i % per + 1))) & mask for i in range(count)]
124
+
125
+
126
+ def _png(data):
127
+ pos = 8
128
+ idat = []
129
+ plte = b''
130
+ trns = b''
131
+ hdr = None
132
+ while pos + 8 <= len(data):
133
+ n, kind = struct.unpack('>I4s', data[pos:pos + 8])
134
+ body = data[pos + 8:pos + 8 + n]
135
+ pos += 12 + n
136
+ if kind == b'IHDR':
137
+ hdr = struct.unpack('>IIBBBBB', body)
138
+ elif kind == b'PLTE':
139
+ plte = body
140
+ elif kind == b'tRNS':
141
+ trns = body
142
+ elif kind == b'IDAT':
143
+ idat.append(body)
144
+ elif kind == b'IEND':
145
+ break
146
+ if hdr is None or not idat:
147
+ raise ImageError('corrupt PNG')
148
+ w, h, depth, ctype, _, _, interlace = hdr
149
+ if ctype not in _PNG_CHANNELS or depth not in (1, 2, 4, 8, 16) or interlace not in (0, 1):
150
+ raise ImageError('invalid PNG header')
151
+ if (ctype == 3 and depth > 8) or (ctype in (2, 4, 6) and depth < 8):
152
+ raise ImageError('invalid PNG header')
153
+ channels = _PNG_CHANNELS[ctype]
154
+ raw = zlib.decompress(b''.join(idat))
155
+ out = _blank(w, h)
156
+ sdepth = 8 if depth == 16 else depth
157
+ maxv = (1 << sdepth) - 1
158
+ keyed = None
159
+ if ctype == 0 and len(trns) >= 2:
160
+ keyed = int.from_bytes(trns[:2], 'big')
161
+ elif ctype == 2 and len(trns) >= 6:
162
+ keyed = tuple(int.from_bytes(trns[i:i + 2], 'big') for i in (0, 2, 4))
163
+ pal = [(plte[i], plte[i + 1], plte[i + 2], trns[i // 3] if i // 3 < len(trns) else 255)
164
+ for i in range(0, len(plte) - 2, 3)]
165
+ bpp = max(1, channels * depth // 8)
166
+ fast = ctype == 6 and depth == 8 and not interlace
167
+ black = (0, 0, 0, 255)
168
+ pos = 0
169
+ for x0, y0, dx, dy in (_ADAM7 if interlace else ((0, 0, 1, 1),)):
170
+ pw = (w - x0 + dx - 1) // dx
171
+ ph = (h - y0 + dy - 1) // dy
172
+ if pw <= 0 or ph <= 0:
173
+ continue
174
+ stride = (pw * channels * depth + 7) // 8
175
+ rows, pos = _png_unfilter(raw, pos, stride, bpp, ph)
176
+ for j, line in enumerate(rows):
177
+ if fast:
178
+ o = j * w * 4
179
+ out[o:o + w * 4] = line
180
+ continue
181
+ s = _png_samples(line, pw * channels, depth)
182
+ k = s
183
+ if keyed is not None and depth == 16:
184
+ k = [(line[n * 2] << 8) | line[n * 2 + 1] for n in range(pw * channels)]
185
+ base = (y0 + j * dy) * w + x0
186
+ for i in range(pw):
187
+ if ctype == 0:
188
+ v = s[i]
189
+ g = v * 255 // maxv
190
+ px = (g, g, g, 0 if k[i] == keyed else 255)
191
+ elif ctype == 2:
192
+ px = (s[i * 3], s[i * 3 + 1], s[i * 3 + 2], 0 if (k[i * 3], k[i * 3 + 1], k[i * 3 + 2]) == keyed else 255)
193
+ elif ctype == 3:
194
+ px = pal[s[i]] if s[i] < len(pal) else black
195
+ elif ctype == 4:
196
+ px = (s[i * 2], s[i * 2], s[i * 2], s[i * 2 + 1])
197
+ else:
198
+ px = (s[i * 4], s[i * 4 + 1], s[i * 4 + 2], s[i * 4 + 3])
199
+ o = (base + i * dx) * 4
200
+ out[o:o + 4] = bytes(px)
201
+ return w, h, out
202
+
203
+
204
+ def _is_bmp(d):
205
+ return d.startswith(b'BM')
206
+
207
+
208
+ def _bmp_channel(v, mask):
209
+ if not mask:
210
+ return 255
211
+ shift = (mask & -mask).bit_length() - 1
212
+ return ((v & mask) >> shift) * 255 // (mask >> shift)
213
+
214
+
215
+ def _bmp(data):
216
+ off = struct.unpack_from('<I', data, 10)[0]
217
+ size = struct.unpack_from('<I', data, 14)[0]
218
+ if size == 12:
219
+ w, h, _, bpp = struct.unpack_from('<HHHH', data, 18)
220
+ comp, used, entry = 0, 0, 3
221
+ else:
222
+ w, h, _, bpp, comp, _, _, _, used, _ = struct.unpack_from('<iiHHIIiiII', data, 18)
223
+ entry = 4
224
+ topdown = h < 0
225
+ h = abs(h)
226
+ if comp not in (0, 3, 6):
227
+ raise ImageError('compressed BMP is not supported')
228
+ if bpp not in (1, 4, 8, 16, 24, 32):
229
+ raise ImageError('unsupported BMP bit depth')
230
+ out = _blank(w, h)
231
+ pal = []
232
+ if bpp <= 8:
233
+ start = 14 + size
234
+ for i in range(used or (1 << bpp)):
235
+ p = start + i * entry
236
+ if p + 3 > len(data):
237
+ break
238
+ pal.append((data[p + 2], data[p + 1], data[p], 255))
239
+ masks = (0, 0, 0, 0)
240
+ if bpp in (16, 32):
241
+ if comp in (3, 6) and size >= 52:
242
+ masks = struct.unpack_from('<III', data, 54) + (struct.unpack_from('<I', data, 66)[0] if size >= 56 else 0,)
243
+ elif comp in (3, 6):
244
+ masks = struct.unpack_from('<III', data, 14 + size) + (0,)
245
+ elif bpp == 16:
246
+ masks = (0x7C00, 0x03E0, 0x001F, 0)
247
+ else:
248
+ masks = (0xFF0000, 0xFF00, 0xFF, 0)
249
+ stride = ((w * bpp + 31) // 32) * 4
250
+ for row in range(h):
251
+ y = row if topdown else h - 1 - row
252
+ line = data[off + row * stride:off + row * stride + stride]
253
+ if len(line) < stride:
254
+ raise ImageError('truncated BMP data')
255
+ for x in range(w):
256
+ if bpp == 24:
257
+ px = (line[x * 3 + 2], line[x * 3 + 1], line[x * 3], 255)
258
+ elif bpp == 32:
259
+ v = line[x * 4] | line[x * 4 + 1] << 8 | line[x * 4 + 2] << 16 | line[x * 4 + 3] << 24
260
+ px = tuple(_bmp_channel(v, m) for m in masks)
261
+ elif bpp == 16:
262
+ v = line[x * 2] | line[x * 2 + 1] << 8
263
+ px = tuple(_bmp_channel(v, m) for m in masks)
264
+ else:
265
+ per = 8 // bpp
266
+ idx = (line[x // per] >> (8 - bpp * (x % per + 1))) & ((1 << bpp) - 1)
267
+ px = pal[idx] if idx < len(pal) else (0, 0, 0, 255)
268
+ o = (y * w + x) * 4
269
+ out[o:o + 4] = bytes(px)
270
+ return w, h, out
271
+
272
+
273
+ def _is_pnm(d):
274
+ return len(d) > 2 and d[0:1] == b'P' and d[1:2] in b'123456' and d[2:3].isspace()
275
+
276
+
277
+ def _pnm(data):
278
+ kind = data[1:2]
279
+ pos = 2
280
+
281
+ def token():
282
+ nonlocal pos
283
+ while True:
284
+ while pos < len(data) and data[pos:pos + 1].isspace():
285
+ pos += 1
286
+ if data[pos:pos + 1] == b'#':
287
+ while pos < len(data) and data[pos] not in b'\r\n':
288
+ pos += 1
289
+ else:
290
+ break
291
+ start = pos
292
+ while pos < len(data) and not data[pos:pos + 1].isspace():
293
+ pos += 1
294
+ return data[start:pos]
295
+
296
+ w = int(token())
297
+ h = int(token())
298
+ bitmap = kind in (b'1', b'4')
299
+ maxv = 1 if bitmap else int(token())
300
+ if maxv < 1 or maxv > 65535:
301
+ raise ImageError('invalid PNM maximum value')
302
+ channels = 3 if kind in (b'3', b'6') else 1
303
+ count = w * h * channels
304
+ out = _blank(w, h)
305
+ if kind == b'1':
306
+ vals = [0 if c == 49 else 1 for c in re.sub(rb'#[^\r\n]*', b'', data[pos:]) if c in b'01']
307
+ elif kind == b'4':
308
+ pos += 1
309
+ stride = (w + 7) // 8
310
+ vals = []
311
+ for y in range(h):
312
+ line = data[pos + y * stride:pos + (y + 1) * stride]
313
+ if len(line) < stride:
314
+ raise ImageError('truncated PNM data')
315
+ vals.extend(0 if (line[x // 8] >> (7 - x % 8)) & 1 else 1 for x in range(w))
316
+ elif kind in (b'2', b'3'):
317
+ vals = [int(t) for t in re.sub(rb'#[^\r\n]*', b'', data[pos:]).split()]
318
+ else:
319
+ pos += 1
320
+ if maxv < 256:
321
+ vals = data[pos:pos + count]
322
+ else:
323
+ vals = [(data[i] << 8) | data[i + 1] for i in range(pos, pos + count * 2 - 1, 2)]
324
+ if len(vals) < count:
325
+ raise ImageError('truncated PNM data')
326
+ scale = 1 if bitmap else maxv
327
+ for i in range(w * h):
328
+ if channels == 3:
329
+ px = (vals[i * 3] * 255 // scale, vals[i * 3 + 1] * 255 // scale, vals[i * 3 + 2] * 255 // scale, 255)
330
+ else:
331
+ g = vals[i] * 255 // scale
332
+ px = (g, g, g, 255)
333
+ out[i * 4:i * 4 + 4] = bytes(px)
334
+ return w, h, out
335
+
336
+
337
+ def _is_gif(d):
338
+ return d[:6] in (b'GIF87a', b'GIF89a')
339
+
340
+
341
+ def _gif_lzw(data, minbits):
342
+ clear = 1 << minbits
343
+ end = clear + 1
344
+ base = [bytes((i,)) for i in range(clear)] + [b'', b'']
345
+ table = list(base)
346
+ size = minbits + 1
347
+ prev = None
348
+ out = bytearray()
349
+ buf = 0
350
+ nbits = 0
351
+ for byte in data:
352
+ buf |= byte << nbits
353
+ nbits += 8
354
+ while nbits >= size:
355
+ code = buf & ((1 << size) - 1)
356
+ buf >>= size
357
+ nbits -= size
358
+ if code == clear:
359
+ table = list(base)
360
+ size = minbits + 1
361
+ prev = None
362
+ continue
363
+ if code == end:
364
+ return out
365
+ if prev is None:
366
+ entry = table[code]
367
+ else:
368
+ if code > len(table):
369
+ raise ImageError('corrupt GIF data')
370
+ entry = table[code] if code < len(table) else prev + prev[:1]
371
+ if len(table) < 4096:
372
+ table.append(prev + entry[:1])
373
+ if len(table) == (1 << size) and size < 12:
374
+ size += 1
375
+ out += entry
376
+ prev = entry
377
+ return out
378
+
379
+
380
+ def _gif(data):
381
+ sw, sh, flags = struct.unpack_from('<HHB', data, 6)
382
+ pos = 13
383
+ gct = b''
384
+ if flags & 0x80:
385
+ n = 3 * (2 << (flags & 7))
386
+ gct = data[pos:pos + n]
387
+ pos += n
388
+ transparent = None
389
+ out = _blank(sw, sh)
390
+ while pos < len(data):
391
+ block = data[pos]
392
+ pos += 1
393
+ if block == 0x21:
394
+ label = data[pos]
395
+ pos += 1
396
+ if label == 0xF9 and data[pos] >= 4:
397
+ if data[pos + 1] & 1:
398
+ transparent = data[pos + 4]
399
+ while data[pos]:
400
+ pos += data[pos] + 1
401
+ pos += 1
402
+ elif block == 0x2C:
403
+ left, top, fw, fh, fflags = struct.unpack_from('<HHHHB', data, pos)
404
+ pos += 9
405
+ table = gct
406
+ if fflags & 0x80:
407
+ n = 3 * (2 << (fflags & 7))
408
+ table = data[pos:pos + n]
409
+ pos += n
410
+ minbits = data[pos]
411
+ if not 1 <= minbits <= 8:
412
+ raise ImageError('corrupt GIF data')
413
+ pos += 1
414
+ chunks = []
415
+ while data[pos]:
416
+ chunks.append(data[pos + 1:pos + 1 + data[pos]])
417
+ pos += data[pos] + 1
418
+ idx = _gif_lzw(b''.join(chunks), minbits)
419
+ if len(idx) < fw * fh:
420
+ idx += bytes(fw * fh - len(idx))
421
+ if fflags & 0x40:
422
+ order = [y for start, step in ((0, 8), (4, 8), (2, 4), (1, 2)) for y in range(start, fh, step)]
423
+ else:
424
+ order = range(fh)
425
+ for src, y in enumerate(order):
426
+ oy = top + y
427
+ if oy >= sh:
428
+ continue
429
+ for x in range(fw):
430
+ ox = left + x
431
+ if ox >= sw:
432
+ break
433
+ i = idx[src * fw + x]
434
+ o = (oy * sw + ox) * 4
435
+ if i == transparent or i * 3 + 2 >= len(table):
436
+ continue
437
+ out[o:o + 4] = bytes((table[i * 3], table[i * 3 + 1], table[i * 3 + 2], 255))
438
+ return sw, sh, out
439
+ else:
440
+ break
441
+ raise ImageError('GIF contains no image')
442
+
443
+
444
+ _ZIGZAG = (
445
+ 0, 1, 8, 16, 9, 2, 3, 10, 17, 24, 32, 25, 18, 11, 4, 5,
446
+ 12, 19, 26, 33, 40, 48, 41, 34, 27, 20, 13, 6, 7, 14, 21, 28,
447
+ 35, 42, 49, 56, 57, 50, 43, 36, 29, 22, 15, 23, 30, 37, 44, 51,
448
+ 58, 59, 52, 45, 38, 31, 39, 46, 53, 60, 61, 54, 47, 55, 62, 63,
449
+ )
450
+ _IDCT = [[(1 / sqrt(2) if u == 0 else 1.0) / 2 * cos((2 * x + 1) * u * pi / 16) for u in range(8)] for x in range(8)]
451
+ _CLIP = bytes(400) + bytes(range(256)) + b'\xff' * 400
452
+ _CR_R = [int(round(1.402 * (i - 128))) for i in range(256)]
453
+ _CB_B = [int(round(1.772 * (i - 128))) for i in range(256)]
454
+ _CR_G = [-0.714136 * (i - 128) for i in range(256)]
455
+ _CB_G = [-0.344136 * (i - 128) for i in range(256)]
456
+ _JPEG_RST = re.compile(rb'\xff[\xd0-\xd7]')
457
+ _JPEG_END = re.compile(rb'\xff[\x01-\xcf\xd8-\xfe]')
458
+
459
+
460
+ def _is_jpeg(d):
461
+ return d.startswith(b'\xff\xd8')
462
+
463
+
464
+ class _Bits:
465
+ __slots__ = ('d', 'p', 'buf', 'n')
466
+
467
+ def __init__(self, d):
468
+ self.d = d
469
+ self.p = 0
470
+ self.buf = 0
471
+ self.n = 0
472
+
473
+ def huff(self, lut):
474
+ while self.n < 16:
475
+ self.buf = (self.buf << 8) | (self.d[self.p] if self.p < len(self.d) else 0)
476
+ self.p += 1
477
+ self.n += 8
478
+ v = lut[(self.buf >> (self.n - 16)) & 0xFFFF]
479
+ if not v:
480
+ raise ImageError('corrupt JPEG data')
481
+ self.n -= v >> 8
482
+ self.buf &= (1 << self.n) - 1
483
+ return v & 255
484
+
485
+ def bits(self, k):
486
+ if not k:
487
+ return 0
488
+ while self.n < k:
489
+ self.buf = (self.buf << 8) | (self.d[self.p] if self.p < len(self.d) else 0)
490
+ self.p += 1
491
+ self.n += 8
492
+ self.n -= k
493
+ v = self.buf >> self.n
494
+ self.buf &= (1 << self.n) - 1
495
+ return v
496
+
497
+ def signed(self, t):
498
+ v = self.bits(t)
499
+ return v if v >= (1 << (t - 1)) else v - (1 << t) + 1
500
+
501
+
502
+ def _jpeg_lut(counts, symbols):
503
+ lut = [0] * 65536
504
+ code = 0
505
+ k = 0
506
+ for length in range(1, 17):
507
+ for _ in range(counts[length - 1]):
508
+ start = code << (16 - length)
509
+ span = 1 << (16 - length)
510
+ lut[start:start + span] = [(length << 8) | symbols[k]] * span
511
+ code += 1
512
+ k += 1
513
+ code <<= 1
514
+ return lut
515
+
516
+
517
+ def _jpeg_ac_refine(bits, lut, coef, base, ss, se, al, eobrun):
518
+ p1 = 1 << al
519
+ m1 = -1 << al
520
+ k = ss
521
+ if eobrun == 0:
522
+ while k <= se:
523
+ rs = bits.huff(lut)
524
+ r = rs >> 4
525
+ s = rs & 15
526
+ if s:
527
+ s = p1 if bits.bits(1) else m1
528
+ elif r != 15:
529
+ eobrun = 1 << r
530
+ if r:
531
+ eobrun += bits.bits(r)
532
+ break
533
+ while k <= se:
534
+ idx = base + _ZIGZAG[k]
535
+ c = coef[idx]
536
+ if c:
537
+ if bits.bits(1) and not c & p1:
538
+ coef[idx] = c + p1 if c >= 0 else c + m1
539
+ else:
540
+ r -= 1
541
+ if r < 0:
542
+ break
543
+ k += 1
544
+ if s and k <= 63:
545
+ coef[base + _ZIGZAG[k]] = s
546
+ k += 1
547
+ if eobrun > 0:
548
+ while k <= se:
549
+ idx = base + _ZIGZAG[k]
550
+ c = coef[idx]
551
+ if c and bits.bits(1) and not c & p1:
552
+ coef[idx] = c + p1 if c >= 0 else c + m1
553
+ k += 1
554
+ eobrun -= 1
555
+ return eobrun
556
+
557
+
558
+ def _jpeg_block(bits, coef, base, state, comp, dc, ac, ss, se, ah, al, progressive):
559
+ if not progressive:
560
+ t = bits.huff(dc)
561
+ comp['pred'] += bits.signed(t) if t else 0
562
+ coef[base] = comp['pred']
563
+ k = 1
564
+ while k < 64:
565
+ rs = bits.huff(ac)
566
+ r = rs >> 4
567
+ s = rs & 15
568
+ if s == 0:
569
+ if r != 15:
570
+ break
571
+ k += 16
572
+ continue
573
+ k += r
574
+ if k > 63:
575
+ break
576
+ coef[base + _ZIGZAG[k]] = bits.signed(s)
577
+ k += 1
578
+ return
579
+ if ss == 0:
580
+ if ah == 0:
581
+ t = bits.huff(dc)
582
+ comp['pred'] += bits.signed(t) if t else 0
583
+ coef[base] = comp['pred'] << al
584
+ elif bits.bits(1):
585
+ coef[base] |= 1 << al
586
+ return
587
+ if ah:
588
+ state[0] = _jpeg_ac_refine(bits, ac, coef, base, ss, se, al, state[0])
589
+ return
590
+ if state[0] > 0:
591
+ state[0] -= 1
592
+ return
593
+ k = ss
594
+ while k <= se:
595
+ rs = bits.huff(ac)
596
+ r = rs >> 4
597
+ s = rs & 15
598
+ if s == 0:
599
+ if r < 15:
600
+ state[0] = (1 << r) - 1
601
+ if r:
602
+ state[0] += bits.bits(r)
603
+ break
604
+ k += 16
605
+ continue
606
+ k += r
607
+ if k > 63:
608
+ break
609
+ coef[base + _ZIGZAG[k]] = bits.signed(s) * (1 << al)
610
+ k += 1
611
+
612
+
613
+ def _jpeg_scan(entropy, scan, comps, frame, restart, progressive):
614
+ ss, se, ah, al = scan['ss'], scan['se'], scan['ah'], scan['al']
615
+ segments = [s.replace(b'\xff\x00', b'\xff') for s in _JPEG_RST.split(entropy)]
616
+ sel = scan['comps']
617
+ if len(sel) == 1:
618
+ c = sel[0][0]
619
+ cw = -(-frame['w'] * c['h'] // frame['hmax'])
620
+ ch = -(-frame['h'] * c['v'] // frame['vmax'])
621
+ cols = -(-cw // 8)
622
+ total = cols * -(-ch // 8)
623
+ else:
624
+ total = frame['mcux'] * frame['mcuy']
625
+ state = [0]
626
+ seg = 0
627
+ bits = _Bits(segments[0])
628
+ for c, _, _ in sel:
629
+ c['pred'] = 0
630
+ for m in range(total):
631
+ if restart and m and m % restart == 0:
632
+ seg += 1
633
+ bits = _Bits(segments[seg] if seg < len(segments) else b'')
634
+ state[0] = 0
635
+ for c, _, _ in sel:
636
+ c['pred'] = 0
637
+ if len(sel) == 1:
638
+ c, dc, ac = sel[0]
639
+ by, bx = divmod(m, cols)
640
+ _jpeg_block(bits, c['coef'], (by * c['bw'] + bx) * 64, state, c, dc, ac, ss, se, ah, al, progressive)
641
+ else:
642
+ my, mx = divmod(m, frame['mcux'])
643
+ for c, dc, ac in sel:
644
+ for v in range(c['v']):
645
+ for h in range(c['h']):
646
+ by = my * c['v'] + v
647
+ bx = mx * c['h'] + h
648
+ _jpeg_block(bits, c['coef'], (by * c['bw'] + bx) * 64, state, c, dc, ac, ss, se, ah, al, progressive)
649
+
650
+
651
+ def _jpeg_plane(c):
652
+ bw, bh = c['bw'], c['bh']
653
+ pw = bw * 8
654
+ plane = bytearray(pw * bh * 8)
655
+ q = c['q']
656
+ coef = c['coef']
657
+ zeros = [0.0] * 8
658
+ for by in range(bh):
659
+ for bx in range(bw):
660
+ base = (by * bw + bx) * 64
661
+ blk = coef[base:base + 64]
662
+ if not any(blk[1:]):
663
+ v = min(255, max(0, int(blk[0] * q[0] / 8 + 128.5)))
664
+ for y in range(8):
665
+ o = (by * 8 + y) * pw + bx * 8
666
+ plane[o:o + 8] = bytes((v,)) * 8
667
+ continue
668
+ blk = [a * b for a, b in zip(blk, q)]
669
+ tmp = []
670
+ for v in range(8):
671
+ r = blk[v * 8:v * 8 + 8]
672
+ tmp.append([sum(m * f for m, f in zip(row, r)) for row in _IDCT] if any(r) else zeros)
673
+ cols = [[sum(m * tmp[v][x] for v, m in enumerate(row)) for x in range(8)] for row in _IDCT]
674
+ for y in range(8):
675
+ o = (by * 8 + y) * pw + bx * 8
676
+ plane[o:o + 8] = bytes(min(255, max(0, int(s + 128.5))) for s in cols[y])
677
+ return plane, pw
678
+
679
+
680
+ def _jpeg(data):
681
+ qts = {}
682
+ dcs = {}
683
+ acs = {}
684
+ frame = None
685
+ comps = []
686
+ restart = 0
687
+ progressive = False
688
+ adobe = None
689
+ pos = 2
690
+ while pos + 4 <= len(data):
691
+ if data[pos] != 0xFF:
692
+ pos += 1
693
+ continue
694
+ marker = data[pos + 1]
695
+ if marker == 0xFF:
696
+ pos += 1
697
+ continue
698
+ if marker in (0xD8, 0x01) or 0xD0 <= marker <= 0xD7:
699
+ pos += 2
700
+ continue
701
+ if marker == 0xD9:
702
+ break
703
+ length = struct.unpack_from('>H', data, pos + 2)[0]
704
+ seg = data[pos + 4:pos + 2 + length]
705
+ pos += 2 + length
706
+ if marker == 0xDB:
707
+ i = 0
708
+ while i < len(seg):
709
+ pq, tq = seg[i] >> 4, seg[i] & 15
710
+ i += 1
711
+ if pq:
712
+ vals = struct.unpack_from('>64H', seg, i)
713
+ i += 128
714
+ else:
715
+ vals = seg[i:i + 64]
716
+ i += 64
717
+ q = [0] * 64
718
+ for k in range(64):
719
+ q[_ZIGZAG[k]] = vals[k]
720
+ qts[tq] = q
721
+ elif marker == 0xC4:
722
+ i = 0
723
+ while i < len(seg):
724
+ tc, th = seg[i] >> 4, seg[i] & 15
725
+ counts = seg[i + 1:i + 17]
726
+ n = sum(counts)
727
+ lut = _jpeg_lut(counts, seg[i + 17:i + 17 + n])
728
+ (acs if tc else dcs)[th] = lut
729
+ i += 17 + n
730
+ elif marker in (0xC0, 0xC1, 0xC2):
731
+ progressive = marker == 0xC2
732
+ if seg[0] != 8:
733
+ raise ImageError('unsupported JPEG precision')
734
+ h, w, n = struct.unpack_from('>HHB', seg, 1)
735
+ if n not in (1, 3):
736
+ raise ImageError('unsupported JPEG color space')
737
+ comps = []
738
+ for k in range(n):
739
+ cid, hv, tq = seg[6 + k * 3:9 + k * 3]
740
+ comps.append({'id': cid, 'h': hv >> 4, 'v': hv & 15, 'tq': tq})
741
+ hmax = max(c['h'] for c in comps)
742
+ vmax = max(c['v'] for c in comps)
743
+ mcux = -(-w // (8 * hmax))
744
+ mcuy = -(-h // (8 * vmax))
745
+ frame = {'w': w, 'h': h, 'hmax': hmax, 'vmax': vmax, 'mcux': mcux, 'mcuy': mcuy}
746
+ for c in comps:
747
+ c['bw'] = mcux * c['h']
748
+ c['bh'] = mcuy * c['v']
749
+ c['coef'] = array('h', bytes(2 * c['bw'] * c['bh'] * 64))
750
+ elif marker in (0xC3, 0xC5, 0xC6, 0xC7, 0xC9, 0xCA, 0xCB, 0xCD, 0xCE, 0xCF):
751
+ raise ImageError('unsupported JPEG encoding')
752
+ elif marker == 0xDD:
753
+ restart = struct.unpack_from('>H', seg)[0]
754
+ elif marker == 0xEE and seg[:5] == b'Adobe':
755
+ adobe = seg[11]
756
+ elif marker == 0xDA:
757
+ if frame is None:
758
+ raise ImageError('corrupt JPEG')
759
+ ns = seg[0]
760
+ sel = []
761
+ for k in range(ns):
762
+ cid, t = seg[1 + k * 2], seg[2 + k * 2]
763
+ c = next(c for c in comps if c['id'] == cid)
764
+ sel.append((c, dcs.get(t >> 4), acs.get(t & 15)))
765
+ ss, se, a = seg[1 + ns * 2:4 + ns * 2]
766
+ scan = {'comps': sel, 'ss': ss, 'se': se, 'ah': a >> 4, 'al': a & 15}
767
+ m = _JPEG_END.search(data, pos)
768
+ end = m.start() if m else len(data)
769
+ _jpeg_scan(data[pos:end], scan, comps, frame, restart, progressive)
770
+ pos = end
771
+ if frame is None:
772
+ raise ImageError('corrupt JPEG')
773
+ w, h = frame['w'], frame['h']
774
+ planes = []
775
+ for c in comps:
776
+ c['q'] = qts[c['tq']]
777
+ plane, pw = _jpeg_plane(c)
778
+ xmap = [x * c['h'] // frame['hmax'] for x in range(w)]
779
+ planes.append((plane, pw, xmap, c['v']))
780
+ c['coef'] = None
781
+ out = _blank(w, h)
782
+ rgb = len(comps) == 3 and (adobe == 0 or [c['id'] for c in comps] == [82, 71, 66])
783
+ alpha = b'\xff' * w
784
+ for y in range(h):
785
+ rows = []
786
+ for plane, pw, xmap, v in planes:
787
+ o = (y * v // frame['vmax']) * pw
788
+ row = plane[o:o + pw]
789
+ rows.append([row[x] for x in xmap])
790
+ if len(rows) == 1:
791
+ r = g = b = bytes(rows[0])
792
+ elif rgb:
793
+ r, g, b = bytes(rows[0]), bytes(rows[1]), bytes(rows[2])
794
+ else:
795
+ yy, cb, cr = rows
796
+ r = bytes(_CLIP[400 + a + _CR_R[c]] for a, c in zip(yy, cr))
797
+ g = bytes(_CLIP[400 + int(a + _CB_G[b_] + _CR_G[c] + 0.5)] for a, b_, c in zip(yy, cb, cr))
798
+ b = bytes(_CLIP[400 + a + _CB_B[c]] for a, c in zip(yy, cb))
799
+ o = y * w * 4
800
+ out[o:o + w * 4:4] = r
801
+ out[o + 1:o + w * 4:4] = g
802
+ out[o + 2:o + w * 4:4] = b
803
+ out[o + 3:o + w * 4:4] = alpha
804
+ return w, h, out
805
+
806
+
807
+ _FORMATS = (
808
+ (_is_png, _png),
809
+ (_is_jpeg, _jpeg),
810
+ (_is_gif, _gif),
811
+ (_is_bmp, _bmp),
812
+ (_is_pnm, _pnm),
813
+ )