apftool 1.2.1__tar.gz → 1.2.3__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.
@@ -1,12 +1,13 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: apftool
3
- Version: 1.2.1
3
+ Version: 1.2.3
4
4
  Summary: Encoder and decoder for Aperture Science Image Format (apf), Aperture Science Image Format 2 (apf2), Wireless Bitmap (wbmp), Over-The-Air Bitmap (otb), and FaceDev's .bruh format
5
5
  Author: Mari (kbity)
6
6
  Requires-Python: >=3.10
7
7
  Description-Content-Type: text/markdown
8
8
  License-File: LICENSE
9
9
  Requires-Dist: Pillow
10
+ Requires-Dist: psqueeze
10
11
  Dynamic: license-file
11
12
 
12
13
  # A library for converting to and from Aperture Science apf files, the custom extended apf2s, otb/wbmp images, and facedev's bruh format.
@@ -100,6 +101,10 @@ apftool provides many extensions tuples:
100
101
 
101
102
  ## changelog:
102
103
 
104
+ 1.2.3 - add +1 bias for near-truecolor modes, reducing the darkening problem
105
+
106
+ 1.2.2 - add support for squeeze, improve handling for numpy/scipy not being installed. Signifigant performance improvement for Truecolor modes.
107
+
103
108
  1.2.1 - add support for 3 of apf2's compressed modes [bzip2, gzip, ascii packing]
104
109
 
105
110
  1.2.0 - increase apfcli verbosity, add lossy encoding options. Allow apfcli to take its own formats as inputs and outputs, also adds a much better quantization algorithm and removes the limitations on indexed animations.
@@ -89,6 +89,10 @@ apftool provides many extensions tuples:
89
89
 
90
90
  ## changelog:
91
91
 
92
+ 1.2.3 - add +1 bias for near-truecolor modes, reducing the darkening problem
93
+
94
+ 1.2.2 - add support for squeeze, improve handling for numpy/scipy not being installed. Signifigant performance improvement for Truecolor modes.
95
+
92
96
  1.2.1 - add support for 3 of apf2's compressed modes [bzip2, gzip, ascii packing]
93
97
 
94
98
  1.2.0 - increase apfcli verbosity, add lossy encoding options. Allow apfcli to take its own formats as inputs and outputs, also adds a much better quantization algorithm and removes the limitations on indexed animations.
@@ -14,7 +14,7 @@ extensions_mqif = (".mqif", ".mqi")
14
14
 
15
15
  extensions_txt = (".txt", ".text") # txt is seen as a generic container for apf/apf2 because they're just txt files
16
16
 
17
- version = "1.2.1"
17
+ version = "1.2.3"
18
18
 
19
19
  extensions_all = []
20
20
  extensions_all.extend(extensions_apf+extensions_apf2+extensions_capf2+extensions_wbmp+extensions_otb+extensions_bruh+extensions_mqif)
@@ -26,7 +26,7 @@ def decode(data, format: str = None, returnImageObject: bool = False, provide_ex
26
26
  return apf2.decode(data, format, returnImageObject) # apf and af2 decoder
27
27
 
28
28
  elif isinstance(data, bytes):
29
- if data.startswith(b'APERTURE IMAGE FORMAT (c) ') or data.startswith(b'APF2') or data.startswith(b'BZh') or data.startswith(b'\x1f\x8b\x08') or data.startswith(b'\x83\x42\x2D\x2A\x95\x69\x45\x41'): # this makes me GLaD
29
+ if data.startswith(b'APERTURE IMAGE FORMAT (c) ') or data.startswith(b'APF2') or data.startswith(b'BZh') or data.startswith(b'v\xff') or data.startswith(b'\x1f\x8b\x08') or data.startswith(b'\x83\x42\x2D\x2A\x95\x69\x45\x41'): # this makes me GLaD
30
30
  if format is None:
31
31
  format = "PNG"
32
32
  return apf2.decode(data, format, returnImageObject, provide_extra_data=provide_extra_data)
@@ -1,6 +1,6 @@
1
1
  from PIL import Image, ImageSequence
2
2
  from collections import Counter
3
- import io, textwrap, math
3
+ import io, textwrap, math, squeeze
4
4
 
5
5
  compressgood = False
6
6
 
@@ -32,8 +32,12 @@ def quant_to_base95(g: int):
32
32
  return round(g/X)
33
33
 
34
34
  mapping = []
35
- for i in range(256):
35
+ for i in range(257): # +1 to cover bias +1
36
36
  mapping.append(chr(quant_to_base95(i)+32))
37
+ mapping_invert = {}
38
+ for i, c in enumerate(mapping):
39
+ if not c in mapping_invert:
40
+ mapping_invert[c] = i
37
41
 
38
42
  # slow pure python version
39
43
  def quantize_most_frequent_basic(img: Image.Image, n_colors: int, palette: list = None, dither: bool = False, dither_mode: str = None): # for use with A and P>256 images, dithering unsupported for basic mode
@@ -81,20 +85,11 @@ else:
81
85
  def split_datapoints(data: str, datlen: int):
82
86
  leng = len(data)
83
87
 
84
- trunc = False
85
- if not leng%datlen == 0:
88
+ if leng % datlen:
86
89
  print("Error: Data must be divisible by the apf2 segment length! Attempting decode anyways...")
87
- trunc = True
90
+ leng -= leng % datlen
88
91
 
89
- split_points = []
90
-
91
- for n in range(0, leng, datlen):
92
- split_points.append(data[n:(n+datlen)])
93
-
94
- if trunc:
95
- split_points.pop(-1)
96
-
97
- return split_points
92
+ return [data[n:n + datlen] for n in range(0, leng, datlen)]
98
93
 
99
94
  def apf2_apfdecodedata(data: str, h: int, w: int, apfbuffer: list, lineskip: int, pals: list, trans: bool = False, topdown: bool = False):
100
95
  x = 0
@@ -156,19 +151,19 @@ def flexTypeToRGB(inpt: str, mode: int, trans: bool = False):
156
151
  # 7 - 95-level Grayscale
157
152
  # 8 - 8-bit Grayscale
158
153
  if mode == 3:
159
- r = mapping.index(inpt[0])
160
- g = mapping.index(inpt[1])
161
- b = mapping.index(inpt[2])
154
+ r = mapping_invert[inpt[0]]
155
+ g = mapping_invert[inpt[1]]
156
+ b = mapping_invert[inpt[2]]
162
157
  r = r+int(r == 254)
163
158
  g = g+int(g == 254)
164
159
  b = b+int(b == 254)
165
160
  return r, g, b
166
161
 
167
162
  elif mode == 4:
168
- r = mapping.index(inpt[0])
169
- g = mapping.index(inpt[1])
170
- b = mapping.index(inpt[2])
171
- a = mapping.index(inpt[3])
163
+ r = mapping_invert[inpt[0]]
164
+ g = mapping_invert[inpt[1]]
165
+ b = mapping_invert[inpt[2]]
166
+ a = mapping_invert[inpt[3]]
172
167
  r = r+int(r == 254)
173
168
  g = g+int(g == 254)
174
169
  b = b+int(b == 254)
@@ -176,9 +171,9 @@ def flexTypeToRGB(inpt: str, mode: int, trans: bool = False):
176
171
  return r, g, b, a
177
172
 
178
173
  elif mode == 5:
179
- r = mapping.index(inpt[0])
180
- g = mapping.index(inpt[1])
181
- b = mapping.index(inpt[2])
174
+ r = mapping_invert[inpt[0]]
175
+ g = mapping_invert[inpt[1]]
176
+ b = mapping_invert[inpt[2]]
182
177
 
183
178
  # compute corrections
184
179
  c = ord(inpt[3])-32
@@ -193,10 +188,10 @@ def flexTypeToRGB(inpt: str, mode: int, trans: bool = False):
193
188
  return r+rc, g+gc, b+bc
194
189
 
195
190
  elif mode == 6:
196
- r = mapping.index(inpt[0])
197
- g = mapping.index(inpt[1])
198
- b = mapping.index(inpt[2])
199
- a = mapping.index(inpt[3])
191
+ r = mapping_invert[inpt[0]]
192
+ g = mapping_invert[inpt[1]]
193
+ b = mapping_invert[inpt[2]]
194
+ a = mapping_invert[inpt[3]]
200
195
 
201
196
  # compute corrections
202
197
  c = ord(inpt[4])-32
@@ -208,7 +203,7 @@ def flexTypeToRGB(inpt: str, mode: int, trans: bool = False):
208
203
  return r+rc, g+gc, b+bc, a+ac
209
204
 
210
205
  elif mode == 7:
211
- r = mapping.index(inpt[0])
206
+ r = mapping_invert[inpt[0]]
212
207
  return [r, r, r]
213
208
 
214
209
  elif mode == 8:
@@ -343,7 +338,7 @@ def graypal(perfect: bool = False):
343
338
  else:
344
339
  for n in set(mapping):
345
340
  if not n in pal:
346
- pal[n] = (mapping.index(n), mapping.index(n), mapping.index(n))
341
+ pal[n] = (mapping_invert[n], mapping_invert[n], mapping_invert[n])
347
342
  return pal
348
343
 
349
344
  def int_alpha_composit(l1: tuple, l2: tuple):
@@ -408,6 +403,8 @@ def decode(apf2: str | bytes, format: str = 'PNG', returnImageObject: bool = Fal
408
403
  apf2 = gzip.decompress(compressed_blob)
409
404
  elif alg == b'ci':
410
405
  apf2 = asciipak.decompress(compressed_blob)
406
+ elif alg == b'sq':
407
+ apf2 = squeeze.decompress(compressed_blob)
411
408
  else:
412
409
  raise Exception("Invalid/Unsupported Compression Algorithm")
413
410
  apf2 = apf2.decode("ascii")
@@ -417,6 +414,9 @@ def decode(apf2: str | bytes, format: str = 'PNG', returnImageObject: bool = Fal
417
414
  elif apf2.startswith(b'\x1f\x8b\x08'):
418
415
  apf2 = gzip.decompress(apf2)
419
416
  apf2 = apf2.decode("ascii")
417
+ elif apf2.startswith(b'v\xff'):
418
+ apf2 = squeeze.decompress(apf2)
419
+ apf2 = apf2.decode("ascii")
420
420
  elif apf2.startswith(b'\x83\x42\x2D\x2A\x95\x69\x45\x41'):
421
421
  apf2 = asciipak.decompress(apf2)
422
422
  apf2 = apf2.decode("ascii")
@@ -791,9 +791,9 @@ def generate_runs_apf2_l(bitmap: list, lineskip: int, w: int, h: int, topside_fi
791
791
  runlens.append(runcounter)
792
792
  return runlens
793
793
 
794
- def uint8_to_base95_with_corrector(val):
795
- d = mapping[val]
796
- c = mapping.index(d)
794
+ def uint8_to_base95_with_corrector(val, bias = 0):
795
+ d = mapping[val+bias]
796
+ c = mapping_invert[d]
797
797
  e = val - c
798
798
  return d, e
799
799
 
@@ -849,6 +849,7 @@ def generate_runs_apf2_f(bitmap: list, lineskip: int, w: int, h: int, trans, com
849
849
  perfect = False
850
850
  alpha = False
851
851
  fakealpha = False
852
+ bias = 1
852
853
 
853
854
  if mode in (7, 8):
854
855
  gray = True
@@ -857,6 +858,7 @@ def generate_runs_apf2_f(bitmap: list, lineskip: int, w: int, h: int, trans, com
857
858
 
858
859
  if mode in (5, 6):
859
860
  perfect = True
861
+ bias = 0
860
862
 
861
863
  if mode in (4, 6):
862
864
  alpha = True
@@ -919,13 +921,13 @@ def generate_runs_apf2_f(bitmap: list, lineskip: int, w: int, h: int, trans, com
919
921
  if combine and pixel == (0,0,0,0) and not isTransPix:
920
922
  pixel = (255,0,255,0) # alpha is zero but the RGB is non-zero
921
923
 
922
- r, rc = uint8_to_base95_with_corrector(pixel[0])
923
- g, gc = uint8_to_base95_with_corrector(pixel[1])
924
- b, bc = uint8_to_base95_with_corrector(pixel[2])
924
+ r, rc = uint8_to_base95_with_corrector(pixel[0], bias)
925
+ g, gc = uint8_to_base95_with_corrector(pixel[1], bias)
926
+ b, bc = uint8_to_base95_with_corrector(pixel[2], bias)
925
927
  a, ac = "~", 0
926
928
 
927
929
  if alpha:
928
- a, ac = uint8_to_base95_with_corrector(pixel[3])
930
+ a, ac = uint8_to_base95_with_corrector(pixel[3], bias)
929
931
 
930
932
  # tri-level alpha for free
931
933
  elif fakealpha and perfect:
@@ -1199,15 +1201,15 @@ def encode(img: bytes | Image.Image, lineskip: int = None, findbestlineskip: boo
1199
1201
  if not compressor in (None, "none", "bzip2", "gzip", "asciipak", "squeeze"):
1200
1202
  raise Exception('Error: Invalid Compressor! Options: none, bzip2, gzip, asciipak, squeeze')
1201
1203
 
1202
- if compressor == "squeeze":
1203
- raise Exception('Error: squeeze is not currently supported by apftool!')
1204
-
1205
1204
  if compressor in ("bzip2", "gzip") and not compressgood:
1206
1205
  raise Exception('Error: bzip2 and gzip is not currently supported by your python install!')
1207
1206
 
1208
1207
  if compressor == "none":
1209
1208
  compressor = None # set it to the real nonetype
1210
1209
 
1210
+ if useadvancedquant and qmf == "basic": # disable the good quantizer
1211
+ useadvancedquant = False
1212
+
1211
1213
  trans = int(trans) # compatability filter
1212
1214
  if trans > 2:
1213
1215
  raise Exception("Error: Invalid Transparency Mode!")
@@ -16,9 +16,12 @@ Pipeline
16
16
  """
17
17
  import heapq
18
18
 
19
- import numpy as np
20
- from PIL import Image
21
- from scipy.spatial import cKDTree
19
+ try:
20
+ import numpy as np
21
+ from PIL import Image
22
+ from scipy.spatial import cKDTree
23
+ except Exception:
24
+ print("Error Loading numpy or scipy")
22
25
 
23
26
  # ---------------------------------------------------------------- Oklab ----
24
27
  _M1 = np.array([[0.4122214708, 0.5363325363, 0.0514459929],
@@ -233,4 +236,4 @@ def quantize_perceptual(img: Image.Image, n_colors: int, palette: list = None,
233
236
  raise ValueError("dither_mode must be 'ordered' or 'fs'")
234
237
 
235
238
  quantized = pal_rgba[idx].reshape(h, w, 4)
236
- return Image.fromarray(quantized, "RGBA"), [tuple(int(v) for v in c) for c in pal_rgba]
239
+ return Image.fromarray(quantized, "RGBA"), [tuple(int(v) for v in c) for c in pal_rgba]
@@ -1,12 +1,13 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: apftool
3
- Version: 1.2.1
3
+ Version: 1.2.3
4
4
  Summary: Encoder and decoder for Aperture Science Image Format (apf), Aperture Science Image Format 2 (apf2), Wireless Bitmap (wbmp), Over-The-Air Bitmap (otb), and FaceDev's .bruh format
5
5
  Author: Mari (kbity)
6
6
  Requires-Python: >=3.10
7
7
  Description-Content-Type: text/markdown
8
8
  License-File: LICENSE
9
9
  Requires-Dist: Pillow
10
+ Requires-Dist: psqueeze
10
11
  Dynamic: license-file
11
12
 
12
13
  # A library for converting to and from Aperture Science apf files, the custom extended apf2s, otb/wbmp images, and facedev's bruh format.
@@ -100,6 +101,10 @@ apftool provides many extensions tuples:
100
101
 
101
102
  ## changelog:
102
103
 
104
+ 1.2.3 - add +1 bias for near-truecolor modes, reducing the darkening problem
105
+
106
+ 1.2.2 - add support for squeeze, improve handling for numpy/scipy not being installed. Signifigant performance improvement for Truecolor modes.
107
+
103
108
  1.2.1 - add support for 3 of apf2's compressed modes [bzip2, gzip, ascii packing]
104
109
 
105
110
  1.2.0 - increase apfcli verbosity, add lossy encoding options. Allow apfcli to take its own formats as inputs and outputs, also adds a much better quantization algorithm and removes the limitations on indexed animations.
@@ -0,0 +1,2 @@
1
+ Pillow
2
+ psqueeze
@@ -4,12 +4,12 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "apftool"
7
- version = "1.2.1"
7
+ version = "1.2.3"
8
8
  description = "Encoder and decoder for Aperture Science Image Format (apf), Aperture Science Image Format 2 (apf2), Wireless Bitmap (wbmp), Over-The-Air Bitmap (otb), and FaceDev's .bruh format"
9
9
  authors = [{name = "Mari (kbity)"}]
10
10
  readme = "README.md"
11
11
  requires-python = ">=3.10"
12
- dependencies = ["Pillow"]
12
+ dependencies = ["Pillow", "psqueeze"]
13
13
 
14
14
  [project.scripts]
15
15
  apfcli = "apftool:cli"
@@ -1 +0,0 @@
1
- Pillow
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes