-
Notifications
You must be signed in to change notification settings - Fork 1
/
convert_animated_gif_to_ssd1306_images.py
executable file
·146 lines (115 loc) · 5.21 KB
/
convert_animated_gif_to_ssd1306_images.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
#!/usr/bin/env python3
import argparse
import os.path
import sys
import zlib
from PIL import Image
from PIL.GifImagePlugin import GifImageFile
import ssd1306_image_converter
import ssd1306_image_reader
# By default, we use do not use the dithering when converting the GIF to 1-bit per pixel
# It may be useful when the animated GIF uses a lot of colors (videos...)
default_dither_method = Image.NONE # or Image.FLOYDSTEINBERG
debug = False
def get_pil_image_info_str(img) -> str:
str = "{}x{} mode {}".format(img.width, img.height, img.mode)
if img.format != None:
str += ", {}".format(img.format)
if hasattr(img, 'n_frames'):
str += ", {} frames".format(img.n_frames) if img.n_frames > 1 else "single frame"
if hasattr(img, 'filename'):
str += ", {}".format(img.filename)
return str
def convert(verbose, compression, overwrite, input_filename,
zlib_window_size=ssd1306_image_reader.DEFAULT_ZLIB_WINDOW_SIZE,
dither_method=default_dither_method):
# Check if input file exits
if not os.path.isfile(input_filename):
print("Error: file {} does not exit!".format(input_filename), file=sys.stderr)
exit(1) # exit with error
# Load animation file with PIL
img_in = Image.open(input_filename)
if verbose:
print("input image : {}".format(get_pil_image_info_str(img_in)))
#img_in.seek(0)
#img_in.show("default")
# Determine the number of frames
if hasattr(img_in, 'n_frames'):
n_frames = img_in.n_frames # Animated GIF case
else:
n_frames = 1 # Single frame image (PNG, GIF...)
# Prepare the output filename (from "filename.gif" to "filename.widthxheight.nimg.z" or ".raw")
output_filename = "{}.{}x{}.{}img".format(input_filename.rsplit('.', 1)[0], img_in.width, img_in.height, n_frames)
if compression:
output_filename = "{}.z".format(output_filename)
else:
output_filename = "{}.raw".format(output_filename)
if verbose:
print("output image: {}".format(output_filename))
# Check if output file already exists...
if os.path.isfile(output_filename):
if not overwrite:
print("Error: file {} already exits, please delete it or use the proper option to overwrite it!".format(output_filename),
file=sys.stderr)
exit(1) # exit with error
else:
if verbose:
print("Warning: file {} already exits and will be overwritten.".format(output_filename))
img_out_file = open(output_filename, "wb") # TODO better manage errors
# Prepare the compression if necessary
if compression:
img_out_compress = zlib.compressobj(9, zlib.DEFLATED, zlib_window_size)
for frame in range(n_frames):
if verbose:
print("{:5}/{} in progress...".format(frame + 1, n_frames))
img_in.seek(frame)
# Convert in 1-bit (black & white), stored with 1 pixel per byte in PIL buffer (0, 1)
img_tmp = img_in.convert("1", dither = dither_method)
if debug:
print("tmp image: {}".format(get_pil_image_info_str(img_tmp)))
# Extract the buffer from the current frame
img_tmp_buf = img_tmp.tobytes(encoder_name = "raw")
# Convert to ssd1306 format (TODO add a documentation link somewhere)
img_out_buf = bytearray((img_in.width * img_in.height) // 8)
ssd1306_image_converter.to_ssd1306(img_in.width, img_in.height, img_tmp_buf, img_out_buf)
# Compress the buffer if requested
if compression:
# TODO check compress returned value?
img_out_compressed_buf = img_out_compress.compress(img_out_buf)
# Save the buffer to disk
img_out_file.write(img_out_compressed_buf) # TODO better manage errors
else:
# Save the buffer to disk
img_out_file.write(img_out_buf) # TODO better manage errors
# Close the file
if compression:
img_out_compressed_buf = img_out_compress.flush()
# Save the buffer to disk
img_out_file.write(img_out_compressed_buf) # TODO better manage errors
img_out_file.close()
if verbose:
print("{} successfully generated :-)".format(output_filename))
def init_argparse() -> argparse.ArgumentParser:
parser = argparse.ArgumentParser(
usage="%(prog)s [OPTIONS] filename.gif",
description=
"""
Convert an animated GIF (or a single image GIF) to an image file for ssd1306-like OLED panel.
Notes:
- The ssd1306 image filename uses the format filename.WidthxHeight.Nimg.raw (.z if compressed).
For example "my_animation.128x64.42img.z".""",
formatter_class=argparse.RawTextHelpFormatter,
)
parser.add_argument("filename")
parser.add_argument("-c", "--compress", action="store_true", help="compress output (zlib)")
parser.add_argument("-f", "--force", action="store_true", help="force overwrite")
parser.add_argument("-v", "--verbose", action="store_true", help="explain what is being done")
return parser
def main() -> None:
parser = init_argparse()
args = parser.parse_args()
if debug:
print(args)
convert(args.verbose, args.compress, args.force, args.filename)
if __name__ == "__main__":
main()