apftool 1.2.0__tar.gz → 1.2.1__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,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: apftool
3
- Version: 1.2.0
3
+ Version: 1.2.1
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
@@ -40,9 +40,9 @@ bruh is an intentionally terrible format made by facedev. info can be found [her
40
40
 
41
41
  `apf.encode(img: bytes | Image.Image, lineskip: int = 1, findbestlineskip: bool = False, returnbytes: bool = False)` takes image bytes or pil image object and outputs apf string. lineskip is interleave value, findbestlineskip brute-forces different interleave values to the possible max of 199 and uses the smallest one. returnbytes makes it return bytes instead of a string.
42
42
 
43
- `apf2.encode(img: bytes | Image.Image, lineskip: int = 1, findbestlineskip: bool = False, legacy: bool = False, trans = False, pal: int = 95, desc: str = "", prepalette: str = None, dodithering: bool = False, returnbytes: bool = False, compress: bool = True, topside_first: bool = False, emit_redundant_flag: bool = False, mode: int = None, combine: bool = False, avoid_run_breaks: bool = False, width: int = None, height: int = None)`
43
+ `apf2.encode(img: bytes | Image.Image, lineskip: int = None, findbestlineskip: bool = False, legacy: bool = False, trans = False, pal: int = 95, desc: str = "", prepalette: str = None, dodithering: bool = False, returnbytes: bool = False, compress: bool = True, topside_first: bool = False, emit_redundant_flag: bool = False, mode: int = None, combine: bool = False, avoid_run_breaks: bool = False, width: int = None, height: int = None, maxrunerror: float = 0, maxmotionerror: float = 0, verbose: bool = False, useadvancedquant: bool = True, dither_mode: str = "fs", compressor: str = None, wrapper: bool = True, compresslevel: int = 9)`
44
44
 
45
- takes image bytes or pil image object and outputs apf2 as a string or ascii bytes. lineskip is interleave value, findbestlineskip brute-forces different interleave values to the provided lineskip and uses the smallest one. legacy uses apf1-style 2 color data instead of a 95 color palette. trans sets transparency mode, with mode 0 being off, mode 1 being index 0 transparency (GIF-like, it overrides a color), and mode 2 being indexed alpha. pal allows you to manually force a smaller or larger palette anything over 95 will use APF2-1994's Dual Indexed Mode. Prepalette is an already encoded APF2 palette you input into the encoder to make the colors deterministic. dodithering enables or disables dithering. returnbytes makes it return bytes instead of a string. These options use the APF2-2000 mode: compress - disable RLE compression, allows for smaller file sizes where RLE is not beneficial. Topside_first makes the image scan top-to-bottom, like most formats. emit_redundant_flag - adds the implicit i flag explicitally to baseline 95 color images. mode - selects apf2 coding mode [0: 2 colors, 1: 95 colors, 2: 9025 colors, 3: near-truecolor, 4: near-truecolor with alpha, 5: truecolor, 6: truecolor with alpha, 7: quantized grayscale (based on 95 color mode), 8: 8-bit grayscale (based on 9025 color mode)], combine attempts to do cross-frame delta coding. avoid_run_breaks reduces the amount of transparency pixels emitted by combine, typically harms efficiency. width and height force the output resolution of the image. if both are specified, the output is streched to the specified resolution. if only 1 is present, the other is determined based on aspect ratio. If unspecfied, no scaling happens.
45
+ takes image bytes or pil image object and outputs apf2 as a string or ascii bytes. lineskip is interleave value, findbestlineskip brute-forces different interleave values to the provided lineskip and uses the smallest one. legacy uses apf1-style 2 color data instead of a 95 color palette. trans sets transparency mode, with mode 0 being off, mode 1 being index 0 transparency (GIF-like, it overrides a color), and mode 2 being indexed alpha. pal allows you to manually force a smaller or larger palette anything over 95 will use APF2-1994's Dual Indexed Mode. Prepalette is an already encoded APF2 palette you input into the encoder to make the colors deterministic. dodithering enables or disables dithering. returnbytes makes it return bytes instead of a string. These options use the APF2-2000 mode: compress - disable RLE compression, allows for smaller file sizes where RLE is not beneficial. Topside_first makes the image scan top-to-bottom, like most formats. emit_redundant_flag - adds the implicit i flag explicitally to baseline 95 color images. mode - selects apf2 coding mode [0: 2 colors, 1: 95 colors, 2: 9025 colors, 3: near-truecolor, 4: near-truecolor with alpha, 5: truecolor, 6: truecolor with alpha, 7: quantized grayscale (based on 95 color mode), 8: 8-bit grayscale (based on 9025 color mode)], combine attempts to do cross-frame delta coding. avoid_run_breaks reduces the amount of transparency pixels emitted by combine, typically harms efficiency. width and height force the output resolution of the image. if both are specified, the output is streched to the specified resolution. if only 1 is present, the other is determined based on aspect ratio. If unspecfied, no scaling happens. maxrunerror and maxmotionerror control how lossy the image is, verbose increases logging output, useadvancedquant allows you to enable/disable the new quantizer, dither_mode sets its ditherer, which can be "fs" (floyd-steinberg) or "ordered" (bayer), compressor sets the apf2 post-compression algorithm (supports gzip, bzip2, and asciipak (ascii packing)). wrapper toggles wether to append an identifyable magic string to a compressed apf2. compresslevel sets the compression level for bzip2 and gzip
46
46
 
47
47
  `wbmp.encode(img: Image)` takes pil image object and outputs wbmp bytes
48
48
 
@@ -100,6 +100,8 @@ apftool provides many extensions tuples:
100
100
 
101
101
  ## changelog:
102
102
 
103
+ 1.2.1 - add support for 3 of apf2's compressed modes [bzip2, gzip, ascii packing]
104
+
103
105
  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.
104
106
 
105
107
  1.1.3 - add version field, add width and height options to apf2 encoding
@@ -29,9 +29,9 @@ bruh is an intentionally terrible format made by facedev. info can be found [her
29
29
 
30
30
  `apf.encode(img: bytes | Image.Image, lineskip: int = 1, findbestlineskip: bool = False, returnbytes: bool = False)` takes image bytes or pil image object and outputs apf string. lineskip is interleave value, findbestlineskip brute-forces different interleave values to the possible max of 199 and uses the smallest one. returnbytes makes it return bytes instead of a string.
31
31
 
32
- `apf2.encode(img: bytes | Image.Image, lineskip: int = 1, findbestlineskip: bool = False, legacy: bool = False, trans = False, pal: int = 95, desc: str = "", prepalette: str = None, dodithering: bool = False, returnbytes: bool = False, compress: bool = True, topside_first: bool = False, emit_redundant_flag: bool = False, mode: int = None, combine: bool = False, avoid_run_breaks: bool = False, width: int = None, height: int = None)`
32
+ `apf2.encode(img: bytes | Image.Image, lineskip: int = None, findbestlineskip: bool = False, legacy: bool = False, trans = False, pal: int = 95, desc: str = "", prepalette: str = None, dodithering: bool = False, returnbytes: bool = False, compress: bool = True, topside_first: bool = False, emit_redundant_flag: bool = False, mode: int = None, combine: bool = False, avoid_run_breaks: bool = False, width: int = None, height: int = None, maxrunerror: float = 0, maxmotionerror: float = 0, verbose: bool = False, useadvancedquant: bool = True, dither_mode: str = "fs", compressor: str = None, wrapper: bool = True, compresslevel: int = 9)`
33
33
 
34
- takes image bytes or pil image object and outputs apf2 as a string or ascii bytes. lineskip is interleave value, findbestlineskip brute-forces different interleave values to the provided lineskip and uses the smallest one. legacy uses apf1-style 2 color data instead of a 95 color palette. trans sets transparency mode, with mode 0 being off, mode 1 being index 0 transparency (GIF-like, it overrides a color), and mode 2 being indexed alpha. pal allows you to manually force a smaller or larger palette anything over 95 will use APF2-1994's Dual Indexed Mode. Prepalette is an already encoded APF2 palette you input into the encoder to make the colors deterministic. dodithering enables or disables dithering. returnbytes makes it return bytes instead of a string. These options use the APF2-2000 mode: compress - disable RLE compression, allows for smaller file sizes where RLE is not beneficial. Topside_first makes the image scan top-to-bottom, like most formats. emit_redundant_flag - adds the implicit i flag explicitally to baseline 95 color images. mode - selects apf2 coding mode [0: 2 colors, 1: 95 colors, 2: 9025 colors, 3: near-truecolor, 4: near-truecolor with alpha, 5: truecolor, 6: truecolor with alpha, 7: quantized grayscale (based on 95 color mode), 8: 8-bit grayscale (based on 9025 color mode)], combine attempts to do cross-frame delta coding. avoid_run_breaks reduces the amount of transparency pixels emitted by combine, typically harms efficiency. width and height force the output resolution of the image. if both are specified, the output is streched to the specified resolution. if only 1 is present, the other is determined based on aspect ratio. If unspecfied, no scaling happens.
34
+ takes image bytes or pil image object and outputs apf2 as a string or ascii bytes. lineskip is interleave value, findbestlineskip brute-forces different interleave values to the provided lineskip and uses the smallest one. legacy uses apf1-style 2 color data instead of a 95 color palette. trans sets transparency mode, with mode 0 being off, mode 1 being index 0 transparency (GIF-like, it overrides a color), and mode 2 being indexed alpha. pal allows you to manually force a smaller or larger palette anything over 95 will use APF2-1994's Dual Indexed Mode. Prepalette is an already encoded APF2 palette you input into the encoder to make the colors deterministic. dodithering enables or disables dithering. returnbytes makes it return bytes instead of a string. These options use the APF2-2000 mode: compress - disable RLE compression, allows for smaller file sizes where RLE is not beneficial. Topside_first makes the image scan top-to-bottom, like most formats. emit_redundant_flag - adds the implicit i flag explicitally to baseline 95 color images. mode - selects apf2 coding mode [0: 2 colors, 1: 95 colors, 2: 9025 colors, 3: near-truecolor, 4: near-truecolor with alpha, 5: truecolor, 6: truecolor with alpha, 7: quantized grayscale (based on 95 color mode), 8: 8-bit grayscale (based on 9025 color mode)], combine attempts to do cross-frame delta coding. avoid_run_breaks reduces the amount of transparency pixels emitted by combine, typically harms efficiency. width and height force the output resolution of the image. if both are specified, the output is streched to the specified resolution. if only 1 is present, the other is determined based on aspect ratio. If unspecfied, no scaling happens. maxrunerror and maxmotionerror control how lossy the image is, verbose increases logging output, useadvancedquant allows you to enable/disable the new quantizer, dither_mode sets its ditherer, which can be "fs" (floyd-steinberg) or "ordered" (bayer), compressor sets the apf2 post-compression algorithm (supports gzip, bzip2, and asciipak (ascii packing)). wrapper toggles wether to append an identifyable magic string to a compressed apf2. compresslevel sets the compression level for bzip2 and gzip
35
35
 
36
36
  `wbmp.encode(img: Image)` takes pil image object and outputs wbmp bytes
37
37
 
@@ -89,6 +89,8 @@ apftool provides many extensions tuples:
89
89
 
90
90
  ## changelog:
91
91
 
92
+ 1.2.1 - add support for 3 of apf2's compressed modes [bzip2, gzip, ascii packing]
93
+
92
94
  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.
93
95
 
94
96
  1.1.3 - add version field, add width and height options to apf2 encoding
@@ -6,6 +6,7 @@ from .apfcli import main as cli
6
6
  extensions = (".apf", ".apf2", ".aif", ".af2", ".ap2", ".aif2", ".a2k", ".ap2k")
7
7
  extensions_apf = (".apf", ".aif")
8
8
  extensions_apf2 = (".apf2", ".af2", ".ap2", ".aif2", ".a2k", ".ap2k")
9
+ extensions_capf2 = (".a2g", ".a2c", ".a2ci", ".apc", ".aq2", ".aqf", ".a2b")
9
10
  extensions_wbmp = (".wbmp", ".wbitmap", ".wbm")
10
11
  extensions_otb = (".otb", ".ota", ".otab")
11
12
  extensions_bruh = (".bruh", ".brh")
@@ -13,10 +14,10 @@ extensions_mqif = (".mqif", ".mqi")
13
14
 
14
15
  extensions_txt = (".txt", ".text") # txt is seen as a generic container for apf/apf2 because they're just txt files
15
16
 
16
- version = "1.2.0"
17
+ version = "1.2.1"
17
18
 
18
19
  extensions_all = []
19
- extensions_all.extend(extensions_apf+extensions_apf2+extensions_wbmp+extensions_otb+extensions_bruh+extensions_mqif)
20
+ extensions_all.extend(extensions_apf+extensions_apf2+extensions_capf2+extensions_wbmp+extensions_otb+extensions_bruh+extensions_mqif)
20
21
 
21
22
  def decode(data, format: str = None, returnImageObject: bool = False, provide_extra_data=True):
22
23
  if isinstance(data, str):
@@ -25,7 +26,7 @@ def decode(data, format: str = None, returnImageObject: bool = False, provide_ex
25
26
  return apf2.decode(data, format, returnImageObject) # apf and af2 decoder
26
27
 
27
28
  elif isinstance(data, bytes):
28
- if data.startswith(b'APERTURE IMAGE FORMAT (c) '): # 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'\x1f\x8b\x08') or data.startswith(b'\x83\x42\x2D\x2A\x95\x69\x45\x41'): # this makes me GLaD
29
30
  if format is None:
30
31
  format = "PNG"
31
32
  return apf2.decode(data, format, returnImageObject, provide_extra_data=provide_extra_data)
@@ -60,7 +61,7 @@ def decode(data, format: str = None, returnImageObject: bool = False, provide_ex
60
61
  return otb.decode(data, format, returnImageObject) # assume otb if it doesnt look like a wbmp or doesnt taste like a bruh
61
62
 
62
63
  else:
63
- Exception(f"decoding failed! {e}. this likely means the format isnt supported by apftool.")
64
+ Exception(f"decoding failed!. this likely means the format isnt supported by apftool.")
64
65
 
65
66
  else:
66
67
  raise Exception("Invalid data! Must be bytes or str")
@@ -2,6 +2,15 @@ from PIL import Image, ImageSequence
2
2
  from collections import Counter
3
3
  import io, textwrap, math
4
4
 
5
+ compressgood = False
6
+
7
+ from . import asciipak
8
+ try:
9
+ import gzip, bz2
10
+ compressgood = True
11
+ except Exception as e:
12
+ print(f"Importing gzip or bz2 failed, compression will be limited to ascii packing\n{e}")
13
+
5
14
  qmf = "basic"
6
15
 
7
16
  # try numpy and sklearn
@@ -390,7 +399,29 @@ def diff_images(base, overlay, magenter = False):
390
399
 
391
400
  def decode(apf2: str | bytes, format: str = 'PNG', returnImageObject: bool = False, provide_extra_data: bool = False, composite_layers: bool = True):
392
401
  if type(apf2) == bytes:
393
- apf2 = apf2.decode("ascii")
402
+ if apf2.startswith(b"APF2"):
403
+ compressed_blob = apf2[6:]
404
+ alg = apf2[4:6]
405
+ if alg == b'b2':
406
+ apf2 = bz2.decompress(compressed_blob)
407
+ elif alg == b'gz':
408
+ apf2 = gzip.decompress(compressed_blob)
409
+ elif alg == b'ci':
410
+ apf2 = asciipak.decompress(compressed_blob)
411
+ else:
412
+ raise Exception("Invalid/Unsupported Compression Algorithm")
413
+ apf2 = apf2.decode("ascii")
414
+ elif apf2.startswith(b'BZh'):
415
+ apf2 = bz2.decompress(apf2)
416
+ apf2 = apf2.decode("ascii")
417
+ elif apf2.startswith(b'\x1f\x8b\x08'):
418
+ apf2 = gzip.decompress(apf2)
419
+ apf2 = apf2.decode("ascii")
420
+ elif apf2.startswith(b'\x83\x42\x2D\x2A\x95\x69\x45\x41'):
421
+ apf2 = asciipak.decompress(apf2)
422
+ apf2 = apf2.decode("ascii")
423
+ else: # also add the check for unwrapped gzip/bzip2 apf2 here
424
+ apf2 = apf2.decode("ascii")
394
425
 
395
426
  apf_list = apf2.splitlines()
396
427
  apf_lines = []
@@ -1159,12 +1190,24 @@ def encode_wrapper(legacy, bitmaps, gray, truecolor, lineskip, w, h, topside_fir
1159
1190
 
1160
1191
  return output, apf2pal
1161
1192
 
1162
- def encode(img: bytes | Image.Image, lineskip: int = None, findbestlineskip: bool = False, legacy: bool = False, trans = False, pal: int = 95, desc: str = "", prepalette: str = None, dodithering: bool = False, returnbytes: bool = False, compress: bool = True, topside_first: bool = False, emit_redundant_flag: bool = False, mode: int = None, combine: bool = False, avoid_run_breaks: bool = False, width: int = None, height: int = None, maxrunerror: float = 0, maxmotionerror: float = 0, verbose: bool = False, useadvancedquant: bool = True, dither_mode: str = "fs"):
1193
+ def encode(img: bytes | Image.Image, lineskip: int = None, findbestlineskip: bool = False, legacy: bool = False, trans = False, pal: int = 95, desc: str = "", prepalette: str = None, dodithering: bool = False, returnbytes: bool = False, compress: bool = True, topside_first: bool = False, emit_redundant_flag: bool = False, mode: int = None, combine: bool = False, avoid_run_breaks: bool = False, width: int = None, height: int = None, maxrunerror: float = 0, maxmotionerror: float = 0, verbose: bool = False, useadvancedquant: bool = True, dither_mode: str = "fs", compressor: str = None, wrapper: bool = True, compresslevel: int = 9):
1163
1194
  frametiming = None
1164
1195
  bakedpal = None
1165
1196
  dim = False
1166
1197
  transmag = False
1167
1198
 
1199
+ if not compressor in (None, "none", "bzip2", "gzip", "asciipak", "squeeze"):
1200
+ raise Exception('Error: Invalid Compressor! Options: none, bzip2, gzip, asciipak, squeeze')
1201
+
1202
+ if compressor == "squeeze":
1203
+ raise Exception('Error: squeeze is not currently supported by apftool!')
1204
+
1205
+ if compressor in ("bzip2", "gzip") and not compressgood:
1206
+ raise Exception('Error: bzip2 and gzip is not currently supported by your python install!')
1207
+
1208
+ if compressor == "none":
1209
+ compressor = None # set it to the real nonetype
1210
+
1168
1211
  trans = int(trans) # compatability filter
1169
1212
  if trans > 2:
1170
1213
  raise Exception("Error: Invalid Transparency Mode!")
@@ -1476,7 +1519,27 @@ def encode(img: bytes | Image.Image, lineskip: int = None, findbestlineskip: boo
1476
1519
  print(f"APF2 Version: {version}")
1477
1520
  print(f"APF2 Header Line: {apflist[1]}")
1478
1521
 
1479
- if returnbytes:
1480
- return apftext.encode()
1522
+ if compressor is None:
1523
+ if returnbytes:
1524
+ return apftext.encode()
1525
+ else:
1526
+ return apftext
1481
1527
  else:
1482
- return apftext
1528
+ apfbin = apftext.encode()
1529
+ # "none", "bzip2", "gzip", "asciipak", "squeeze"
1530
+ wraphead = b"APF2"
1531
+ if compressor == "bzip2":
1532
+ apfbin_c = bz2.compress(apfbin, compresslevel=compresslevel)
1533
+ wraphead+=b"b2"
1534
+ elif compressor == "gzip":
1535
+ apfbin_c = gzip.compress(apfbin, compresslevel=compresslevel)
1536
+ wraphead+=b"gz"
1537
+ elif compressor == "squeeze":
1538
+ apfbin_c = squeeze.compress(apfbin)
1539
+ wraphead+=b"sq"
1540
+ else:
1541
+ apfbin_c = asciipak.compress(apfbin)
1542
+ wraphead+=b"ci"
1543
+ if wrapper:
1544
+ apfbin_c = wraphead+apfbin_c
1545
+ return apfbin_c
@@ -36,6 +36,9 @@ encoding:
36
36
  --quality=INT - (APF2) (0 to 100) sets both --run-quality and --motion-quality [Alias: -Q=INT]
37
37
  --pillow - (APF2) use pillow's quantizer [Alias: -P]
38
38
  --bayer - (APF2) use faster bayer/ordered dithering for perceptual mode instead of the slower, but prettier floyd-steinberg
39
+ --compressor=[bzip2, gzip, asciipak, none] - (APF2) set APF2 post-compressor
40
+ --compresslevel=[1-9] - (APF2) set compression level for apf2 compressor (gzip/bzip2) (default: 9)
41
+ --nowrap - (APF2) disable the Compressed APF2 wrapper on compressed APF2 Files
39
42
  --mode=INT - (APF2) color mode (specified in A2K) for APF2. [Alias: -M=INT]
40
43
  0 - 2 colors
41
44
  1 - 95 colors
@@ -75,12 +78,12 @@ TODO: MQIF encoding
75
78
  lineskip = None
76
79
  dither = False
77
80
  description = ""
78
- if opext in apftool.extensions_apf2:
79
- wid = None
80
- hei = None
81
- else:
81
+ if opext in apftool.extensions_otb:
82
82
  wid = 255
83
83
  hei = 255
84
+ else:
85
+ wid = None
86
+ hei = None
84
87
  transcolor = None
85
88
  flip = False
86
89
  compress = True
@@ -91,6 +94,9 @@ TODO: MQIF encoding
91
94
  maxrunerror = 0
92
95
  useadvancedquant = True
93
96
  dither_mode = "fs"
97
+ compressor = None
98
+ wrapper = True
99
+ compresslevel = 9
94
100
  if args:
95
101
  if "--findbestlineskip" in args:
96
102
  fbls = True
@@ -114,6 +120,8 @@ TODO: MQIF encoding
114
120
  useadvancedquant = False
115
121
  if "--bayer" in args:
116
122
  dither_mode = "ordered"
123
+ if "--nowrap" in args:
124
+ wrapper = False
117
125
  for arg in args:
118
126
  if arg.startswith("--desc=") or arg.startswith("-D="):
119
127
  description = arg.replace("--desc=", "").replace("-D=", "")
@@ -131,10 +139,11 @@ TODO: MQIF encoding
131
139
  if arg.startswith("--height=") or arg.startswith("-H="):
132
140
  hei = int(arg.replace("--height=", "").replace("-H=", ""))
133
141
 
134
- if arg.startswith("--mode="):
135
- mode = int(arg.replace("--mode=", ""))
136
- if arg.startswith("-M="):
137
- mode = int(arg.replace("-M=", ""))
142
+ if arg.startswith("--mode=") or arg.startswith("-M="):
143
+ mode = int(arg.replace("--mode=", "").replace("-M=", ""))
144
+
145
+ if arg.startswith("--compresslevel="):
146
+ compresslevel = int(arg.replace("--compresslevel=", ""))
138
147
 
139
148
  if arg.startswith("--quality=") or arg.startswith("-Q="):
140
149
  qual = int(arg.replace("--quality=", "").replace("-Q=", ""))/2
@@ -156,6 +165,9 @@ TODO: MQIF encoding
156
165
  tcb = int(transcolor_hex[4:6], 16)
157
166
  transcolor = (tcr, tcg, tcb)
158
167
 
168
+ if arg.startswith("--compressor="):
169
+ compressor = arg.replace("--compressor=", "")
170
+
159
171
  if mode is None:
160
172
  if maxpalette > 9025 and 857375 >= maxpalette:
161
173
  maxpalette = 95
@@ -203,8 +215,8 @@ TODO: MQIF encoding
203
215
  elif opext in apftool.extensions_mqif:
204
216
  encoded = apftool.mqif.encode(img_bytes, transcolor)
205
217
 
206
- elif opext in apftool.extensions_apf2:
207
- encoded = apftool.apf2.encode(img_bytes, lineskip=lineskip, findbestlineskip=fbls, legacy=legacy, trans=trans, pal=maxpalette, desc=description, prepalette= None, dodithering=dither, returnbytes=True, compress=compress, topside_first=flip, mode=mode, combine=combine, avoid_run_breaks=avoid_run_breaks, width=wid, height=hei, maxrunerror = maxrunerror, maxmotionerror = maxmotionerror, verbose = True, useadvancedquant=useadvancedquant, dither_mode=dither_mode)
218
+ elif opext in apftool.extensions_apf2 or opext in apftool.extensions_capf2:
219
+ encoded = apftool.apf2.encode(img_bytes, lineskip=lineskip, findbestlineskip=fbls, legacy=legacy, trans=trans, pal=maxpalette, desc=description, prepalette= None, dodithering=dither, returnbytes=True, compress=compress, topside_first=flip, mode=mode, combine=combine, avoid_run_breaks=avoid_run_breaks, width=wid, height=hei, maxrunerror = maxrunerror, maxmotionerror = maxmotionerror, verbose = True, useadvancedquant=useadvancedquant, dither_mode=dither_mode, compressor=compressor, wrapper=wrapper, compresslevel=compresslevel)
208
220
 
209
221
  else:
210
222
  raise ValueError("Unsupported Image Format!")
@@ -0,0 +1,27 @@
1
+ from .bittools import byte_to_boollist, boollist_to_int_var
2
+
3
+ def compress(inpt: bytes):
4
+ buffr = []
5
+ output = bytearray()
6
+ for byte in inpt:
7
+ buffr.extend(byte_to_boollist(byte)[1:])
8
+ if len(buffr) >= 8:
9
+ output.append(boollist_to_int_var(buffr[0:8]))
10
+ buffr = buffr[8:]
11
+ if buffr:
12
+ while len(buffr) < 8:
13
+ buffr.append(False)
14
+ output.append(boollist_to_int_var(buffr))
15
+ return output
16
+
17
+ def decompress(inpt: bytes):
18
+ buffr = []
19
+ output = bytearray()
20
+ for byte in inpt:
21
+ buffr.extend(byte_to_boollist(byte))
22
+ while len(buffr) >= 8:
23
+ v = boollist_to_int_var([0]+buffr[0:7])
24
+ if v:
25
+ output.append(v)
26
+ buffr = buffr[7:]
27
+ return output
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: apftool
3
- Version: 1.2.0
3
+ Version: 1.2.1
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
@@ -40,9 +40,9 @@ bruh is an intentionally terrible format made by facedev. info can be found [her
40
40
 
41
41
  `apf.encode(img: bytes | Image.Image, lineskip: int = 1, findbestlineskip: bool = False, returnbytes: bool = False)` takes image bytes or pil image object and outputs apf string. lineskip is interleave value, findbestlineskip brute-forces different interleave values to the possible max of 199 and uses the smallest one. returnbytes makes it return bytes instead of a string.
42
42
 
43
- `apf2.encode(img: bytes | Image.Image, lineskip: int = 1, findbestlineskip: bool = False, legacy: bool = False, trans = False, pal: int = 95, desc: str = "", prepalette: str = None, dodithering: bool = False, returnbytes: bool = False, compress: bool = True, topside_first: bool = False, emit_redundant_flag: bool = False, mode: int = None, combine: bool = False, avoid_run_breaks: bool = False, width: int = None, height: int = None)`
43
+ `apf2.encode(img: bytes | Image.Image, lineskip: int = None, findbestlineskip: bool = False, legacy: bool = False, trans = False, pal: int = 95, desc: str = "", prepalette: str = None, dodithering: bool = False, returnbytes: bool = False, compress: bool = True, topside_first: bool = False, emit_redundant_flag: bool = False, mode: int = None, combine: bool = False, avoid_run_breaks: bool = False, width: int = None, height: int = None, maxrunerror: float = 0, maxmotionerror: float = 0, verbose: bool = False, useadvancedquant: bool = True, dither_mode: str = "fs", compressor: str = None, wrapper: bool = True, compresslevel: int = 9)`
44
44
 
45
- takes image bytes or pil image object and outputs apf2 as a string or ascii bytes. lineskip is interleave value, findbestlineskip brute-forces different interleave values to the provided lineskip and uses the smallest one. legacy uses apf1-style 2 color data instead of a 95 color palette. trans sets transparency mode, with mode 0 being off, mode 1 being index 0 transparency (GIF-like, it overrides a color), and mode 2 being indexed alpha. pal allows you to manually force a smaller or larger palette anything over 95 will use APF2-1994's Dual Indexed Mode. Prepalette is an already encoded APF2 palette you input into the encoder to make the colors deterministic. dodithering enables or disables dithering. returnbytes makes it return bytes instead of a string. These options use the APF2-2000 mode: compress - disable RLE compression, allows for smaller file sizes where RLE is not beneficial. Topside_first makes the image scan top-to-bottom, like most formats. emit_redundant_flag - adds the implicit i flag explicitally to baseline 95 color images. mode - selects apf2 coding mode [0: 2 colors, 1: 95 colors, 2: 9025 colors, 3: near-truecolor, 4: near-truecolor with alpha, 5: truecolor, 6: truecolor with alpha, 7: quantized grayscale (based on 95 color mode), 8: 8-bit grayscale (based on 9025 color mode)], combine attempts to do cross-frame delta coding. avoid_run_breaks reduces the amount of transparency pixels emitted by combine, typically harms efficiency. width and height force the output resolution of the image. if both are specified, the output is streched to the specified resolution. if only 1 is present, the other is determined based on aspect ratio. If unspecfied, no scaling happens.
45
+ takes image bytes or pil image object and outputs apf2 as a string or ascii bytes. lineskip is interleave value, findbestlineskip brute-forces different interleave values to the provided lineskip and uses the smallest one. legacy uses apf1-style 2 color data instead of a 95 color palette. trans sets transparency mode, with mode 0 being off, mode 1 being index 0 transparency (GIF-like, it overrides a color), and mode 2 being indexed alpha. pal allows you to manually force a smaller or larger palette anything over 95 will use APF2-1994's Dual Indexed Mode. Prepalette is an already encoded APF2 palette you input into the encoder to make the colors deterministic. dodithering enables or disables dithering. returnbytes makes it return bytes instead of a string. These options use the APF2-2000 mode: compress - disable RLE compression, allows for smaller file sizes where RLE is not beneficial. Topside_first makes the image scan top-to-bottom, like most formats. emit_redundant_flag - adds the implicit i flag explicitally to baseline 95 color images. mode - selects apf2 coding mode [0: 2 colors, 1: 95 colors, 2: 9025 colors, 3: near-truecolor, 4: near-truecolor with alpha, 5: truecolor, 6: truecolor with alpha, 7: quantized grayscale (based on 95 color mode), 8: 8-bit grayscale (based on 9025 color mode)], combine attempts to do cross-frame delta coding. avoid_run_breaks reduces the amount of transparency pixels emitted by combine, typically harms efficiency. width and height force the output resolution of the image. if both are specified, the output is streched to the specified resolution. if only 1 is present, the other is determined based on aspect ratio. If unspecfied, no scaling happens. maxrunerror and maxmotionerror control how lossy the image is, verbose increases logging output, useadvancedquant allows you to enable/disable the new quantizer, dither_mode sets its ditherer, which can be "fs" (floyd-steinberg) or "ordered" (bayer), compressor sets the apf2 post-compression algorithm (supports gzip, bzip2, and asciipak (ascii packing)). wrapper toggles wether to append an identifyable magic string to a compressed apf2. compresslevel sets the compression level for bzip2 and gzip
46
46
 
47
47
  `wbmp.encode(img: Image)` takes pil image object and outputs wbmp bytes
48
48
 
@@ -100,6 +100,8 @@ apftool provides many extensions tuples:
100
100
 
101
101
  ## changelog:
102
102
 
103
+ 1.2.1 - add support for 3 of apf2's compressed modes [bzip2, gzip, ascii packing]
104
+
103
105
  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.
104
106
 
105
107
  1.1.3 - add version field, add width and height options to apf2 encoding
@@ -7,6 +7,7 @@ apftool/apf.py
7
7
  apftool/apf2.py
8
8
  apftool/apf2quant.py
9
9
  apftool/apfcli.py
10
+ apftool/asciipak.py
10
11
  apftool/bittools.py
11
12
  apftool/bruh.py
12
13
  apftool/mqif.py
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "apftool"
7
- version = "1.2.0"
7
+ version = "1.2.1"
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"
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