qgif 0.1.0__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.
- qgif-0.1.0/PKG-INFO +13 -0
- qgif-0.1.0/qgif/__init__.py +1 -0
- qgif-0.1.0/qgif/__main__.py +61 -0
- qgif-0.1.0/qgif/gen_lvgl_file.py +84 -0
- qgif-0.1.0/qgif/gif2png.py +135 -0
- qgif-0.1.0/qgif/qgif +61 -0
- qgif-0.1.0/qgif/qgif.py +182 -0
- qgif-0.1.0/qgif/qgif_C/libqgif.so +0 -0
- qgif-0.1.0/qgif.egg-info/PKG-INFO +13 -0
- qgif-0.1.0/qgif.egg-info/SOURCES.txt +13 -0
- qgif-0.1.0/qgif.egg-info/dependency_links.txt +1 -0
- qgif-0.1.0/qgif.egg-info/requires.txt +4 -0
- qgif-0.1.0/qgif.egg-info/top_level.txt +1 -0
- qgif-0.1.0/setup.cfg +4 -0
- qgif-0.1.0/setup.py +54 -0
qgif-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: qgif
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Convert GIF to QGIF, or decode QGIF.
|
|
5
|
+
Author: Hangzhou Nationalchip Inc.
|
|
6
|
+
Author-email: zhengdi@nationalchip.com
|
|
7
|
+
License: MIT Licence
|
|
8
|
+
Keywords: qgif nationalchip
|
|
9
|
+
Classifier: Development Status :: 3 - Alpha
|
|
10
|
+
Classifier: Intended Audience :: Developers
|
|
11
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
12
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
VERSION = "0.1.0"
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
|
|
3
|
+
import sys
|
|
4
|
+
import os
|
|
5
|
+
import argparse
|
|
6
|
+
|
|
7
|
+
from qgif import VERSION
|
|
8
|
+
from qgif.qgif import convert_gif_to_qgif, decode_qgif
|
|
9
|
+
|
|
10
|
+
def check_range(param_name, t):
|
|
11
|
+
def checker(value):
|
|
12
|
+
value = t(value)
|
|
13
|
+
if value < 0 or value > 10:
|
|
14
|
+
raise argparse.ArgumentTypeError(f"Argument '{param_name}' must be between 0 and 10, but got {value}")
|
|
15
|
+
return value
|
|
16
|
+
return checker
|
|
17
|
+
|
|
18
|
+
if __name__ == "__main__":
|
|
19
|
+
parser = argparse.ArgumentParser(prog="qgif", description="QGIF generator and decoder")
|
|
20
|
+
subparsers = parser.add_subparsers(dest="command")
|
|
21
|
+
|
|
22
|
+
parser_convert = subparsers.add_parser("convert", help="Convert GIF to QGIF.")
|
|
23
|
+
parser_convert.add_argument("-f", "--format", required=True, choices=["gx64", "gx96"], help="The format of generated QGIF frames.")
|
|
24
|
+
parser_convert.add_argument("-i", "--input", required=True, help="The input GIF file name.")
|
|
25
|
+
parser_convert.add_argument("-o", "--output", required=True, help="Generated QGIF file name.")
|
|
26
|
+
parser_convert.add_argument("-fr", "--framerate", type=int, default=0, help="The frame rate of QGIF file. (default: same as GIF)")
|
|
27
|
+
parser_convert.add_argument("-l", "--lvgl", action="store_true", help="Generate LVGL C file.")
|
|
28
|
+
parser_convert.add_argument("--crop-size", type=int, nargs=2, help="Size to crop the image (width height)")
|
|
29
|
+
parser_convert.add_argument("--scale-size", type=int, nargs=2, help="Size to scale the image (width height)")
|
|
30
|
+
parser_convert.add_argument("--bilateral-strength", default=0, type=check_range("--bilateral-strength", float),
|
|
31
|
+
help="Strength of bilateral filter (0-10, can be a float)")
|
|
32
|
+
parser_convert.add_argument("--median-strength", default=0, type=check_range("--median-strength", int),
|
|
33
|
+
help="Strength of spatial median filter (0-10)")
|
|
34
|
+
parser_convert.add_argument("--interframe-threshold", default=0, type=check_range("--interframe-threshold", int),
|
|
35
|
+
help="Threshold for interframe median filtering (0-10)")
|
|
36
|
+
parser_convert.add_argument("--temporal-threshold", default=0, type=check_range("--temporal-threshold", int),
|
|
37
|
+
help="Threshold for temporal denoise (0-10)")
|
|
38
|
+
|
|
39
|
+
parser_decode = subparsers.add_parser("decode", help="Decode QGIF to frames.")
|
|
40
|
+
parser_decode.add_argument("-i", "--input", required=True, help="The input QGIF file name.")
|
|
41
|
+
parser_decode.add_argument("-o", "--output", required=True, help="The directory of output frames and pngs.")
|
|
42
|
+
parser_decode.add_argument("-c", "--colordepth", type=int, required=True, choices=[16, 32], help="The color depth of decoded frames.")
|
|
43
|
+
|
|
44
|
+
parser.add_argument("-V", "--version", action="version", version="qgif %s" % VERSION)
|
|
45
|
+
|
|
46
|
+
args = parser.parse_args()
|
|
47
|
+
if args.command == "convert":
|
|
48
|
+
convert_gif_to_qgif(args.input,
|
|
49
|
+
args.output,
|
|
50
|
+
args.format,
|
|
51
|
+
args.lvgl,
|
|
52
|
+
args.framerate,
|
|
53
|
+
crop_size=args.crop_size,
|
|
54
|
+
scale_size=args.scale_size,
|
|
55
|
+
bilateral_strength=args.bilateral_strength,
|
|
56
|
+
median_strength=args.median_strength,
|
|
57
|
+
interframe_threshold=args.interframe_threshold,
|
|
58
|
+
temporal_threshold=args.temporal_threshold)
|
|
59
|
+
elif args.command == "decode":
|
|
60
|
+
decode_qgif(args.input, args.output, args.colordepth)
|
|
61
|
+
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import re
|
|
3
|
+
|
|
4
|
+
def sanitize_filename(filename):
|
|
5
|
+
# 移除不合法的字符,只保留字母、数字和下划线
|
|
6
|
+
sanitized = re.sub(r'[^a-zA-Z0-9_]', '_', filename)
|
|
7
|
+
|
|
8
|
+
# 如果第一个字符是数字,则在前面加上一个下划线
|
|
9
|
+
if sanitized and sanitized[0].isdigit():
|
|
10
|
+
sanitized = '_' + sanitized
|
|
11
|
+
|
|
12
|
+
# 检查是否是C语言的关键字
|
|
13
|
+
c_keywords = {
|
|
14
|
+
"auto", "break", "case", "char", "const", "continue", "default", "do", "double", "else", "enum", "extern",
|
|
15
|
+
"float", "for", "goto", "if", "inline", "int", "long", "register", "restrict", "return", "short", "signed",
|
|
16
|
+
"sizeof", "static", "struct", "switch", "typedef", "union", "unsigned", "void", "volatile", "while", "_Alignas",
|
|
17
|
+
"_Alignof", "_Atomic", "_Bool", "_Complex", "_Generic", "_Imaginary", "_Noreturn", "_Static_assert", "_Thread_local"
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
if sanitized in c_keywords:
|
|
21
|
+
sanitized = '_' + sanitized
|
|
22
|
+
|
|
23
|
+
return sanitized
|
|
24
|
+
|
|
25
|
+
def gen_lvgl_file(bin_file, lvgl_file, image_width, image_height):
|
|
26
|
+
image_name_with_extension = os.path.basename(bin_file)
|
|
27
|
+
image_name, _ = os.path.splitext(image_name_with_extension)
|
|
28
|
+
image_name = sanitize_filename(image_name)
|
|
29
|
+
|
|
30
|
+
# 读取二进制文件
|
|
31
|
+
with open(bin_file, 'rb') as bin_file:
|
|
32
|
+
bin_data = bin_file.read()
|
|
33
|
+
|
|
34
|
+
# 生成C文件内容
|
|
35
|
+
c_file_content = f"""#ifdef __has_include
|
|
36
|
+
#if __has_include("lvgl.h")
|
|
37
|
+
#ifndef LV_LVGL_H_INCLUDE_SIMPLE
|
|
38
|
+
#define LV_LVGL_H_INCLUDE_SIMPLE
|
|
39
|
+
#endif
|
|
40
|
+
#endif
|
|
41
|
+
#endif
|
|
42
|
+
|
|
43
|
+
#if defined(LV_LVGL_H_INCLUDE_SIMPLE)
|
|
44
|
+
#include "lvgl.h"
|
|
45
|
+
#else
|
|
46
|
+
#include "lvgl/lvgl.h"
|
|
47
|
+
#endif
|
|
48
|
+
|
|
49
|
+
#ifndef LV_ATTRIBUTE_MEM_ALIGN
|
|
50
|
+
#define LV_ATTRIBUTE_MEM_ALIGN
|
|
51
|
+
#endif
|
|
52
|
+
|
|
53
|
+
#ifndef LV_ATTRIBUTE_IMG_{image_name.upper()}
|
|
54
|
+
#define LV_ATTRIBUTE_IMG_{image_name.upper()}
|
|
55
|
+
#endif
|
|
56
|
+
|
|
57
|
+
const LV_ATTRIBUTE_MEM_ALIGN LV_ATTRIBUTE_LARGE_CONST LV_ATTRIBUTE_IMG_{image_name.upper()} uint8_t {image_name}_bin[] = {{
|
|
58
|
+
"""
|
|
59
|
+
|
|
60
|
+
# 将二进制数据转换为C数组格式
|
|
61
|
+
for i, byte in enumerate(bin_data):
|
|
62
|
+
if i % 12 == 0:
|
|
63
|
+
c_file_content += '\n '
|
|
64
|
+
c_file_content += f'0x{byte:02x}, '
|
|
65
|
+
|
|
66
|
+
c_file_content = c_file_content.rstrip(', ') # 移除最后一个逗号和空格
|
|
67
|
+
c_file_content += '\n};\n\n'
|
|
68
|
+
|
|
69
|
+
# 添加图像描述符
|
|
70
|
+
c_file_content += f"""const lv_img_dsc_t {image_name} = {{
|
|
71
|
+
.header.cf = LV_IMG_CF_RAW_CHROMA_KEYED,
|
|
72
|
+
.header.always_zero = 0,
|
|
73
|
+
.header.reserved = 0,
|
|
74
|
+
.header.w = {image_width},
|
|
75
|
+
.header.h = {image_height},
|
|
76
|
+
.data_size = {len(bin_data)},
|
|
77
|
+
.data = {image_name}_bin,
|
|
78
|
+
}};
|
|
79
|
+
"""
|
|
80
|
+
|
|
81
|
+
# 写入C文件
|
|
82
|
+
with open(lvgl_file, 'w') as c_file:
|
|
83
|
+
c_file.write(c_file_content)
|
|
84
|
+
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
# coding: utf-8
|
|
2
|
+
import sys
|
|
3
|
+
import os
|
|
4
|
+
from PIL import Image, ImageFilter
|
|
5
|
+
import cv2
|
|
6
|
+
import numpy as np
|
|
7
|
+
from tqdm import tqdm
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def map_strength_to_parameters(strength):
|
|
11
|
+
if strength < 0 or strength > 10:
|
|
12
|
+
raise ValueError("强度值应在0到10之间")
|
|
13
|
+
|
|
14
|
+
# 非线性映射,使用指数函数控制强度
|
|
15
|
+
min_sigma = 0.00001 # 极低时接近不处理
|
|
16
|
+
max_sigma = 25.0 # 高强度时的最大滤波参数
|
|
17
|
+
|
|
18
|
+
sigma = min_sigma + (max_sigma - min_sigma) * (strength / 10.0)
|
|
19
|
+
return sigma
|
|
20
|
+
|
|
21
|
+
def apply_bilateral_filter(image, strength):
|
|
22
|
+
image_cv = np.array(image)
|
|
23
|
+
image_cv = cv2.cvtColor(image_cv, cv2.COLOR_RGB2BGR)
|
|
24
|
+
sigma = map_strength_to_parameters(strength)
|
|
25
|
+
filtered_image_cv = cv2.bilateralFilter(image_cv, d=5, sigmaColor=sigma, sigmaSpace=sigma)
|
|
26
|
+
filtered_image_cv = cv2.cvtColor(filtered_image_cv, cv2.COLOR_BGR2RGB)
|
|
27
|
+
return Image.fromarray(filtered_image_cv)
|
|
28
|
+
|
|
29
|
+
def reduce_quantization_noise(image, strength=10):
|
|
30
|
+
image_cv = np.array(image)
|
|
31
|
+
image_lab = cv2.cvtColor(image_cv, cv2.COLOR_RGB2Lab)
|
|
32
|
+
L, a, b = cv2.split(image_lab)
|
|
33
|
+
L_filtered = cv2.bilateralFilter(L, d=9, sigmaColor=strength, sigmaSpace=strength)
|
|
34
|
+
image_filtered = cv2.merge((L_filtered, a, b))
|
|
35
|
+
image_rgb = cv2.cvtColor(image_filtered, cv2.COLOR_Lab2RGB)
|
|
36
|
+
return Image.fromarray(image_rgb)
|
|
37
|
+
|
|
38
|
+
def apply_interframe_median_filter(prev_frame, curr_frame, next_frame, threshold):
|
|
39
|
+
prev_frame_np = np.array(prev_frame)
|
|
40
|
+
curr_frame_np = np.array(curr_frame)
|
|
41
|
+
next_frame_np = np.array(next_frame)
|
|
42
|
+
mask = (np.abs(curr_frame_np - prev_frame_np) < threshold) & (prev_frame_np == next_frame_np)
|
|
43
|
+
curr_frame_np[mask] = prev_frame_np[mask]
|
|
44
|
+
return Image.fromarray(curr_frame_np)
|
|
45
|
+
|
|
46
|
+
def apply_temporal_denoise(display_frame, curr_frame, threshold):
|
|
47
|
+
display_frame_np = np.array(display_frame)
|
|
48
|
+
curr_frame_np = np.array(curr_frame)
|
|
49
|
+
mask = np.abs(curr_frame_np - display_frame_np) < threshold
|
|
50
|
+
curr_frame_np[mask] = display_frame_np[mask]
|
|
51
|
+
return Image.fromarray(curr_frame_np)
|
|
52
|
+
|
|
53
|
+
def crop_and_resize(image, crop_size=None, scale_size=None):
|
|
54
|
+
if crop_size:
|
|
55
|
+
width, height = image.size
|
|
56
|
+
crop_width, crop_height = crop_size
|
|
57
|
+
left = (width - crop_width) / 2
|
|
58
|
+
top = (height - crop_height) / 2
|
|
59
|
+
right = (width + crop_width) / 2
|
|
60
|
+
bottom = (height + crop_height) / 2
|
|
61
|
+
image = image.crop((left, top, right, bottom))
|
|
62
|
+
|
|
63
|
+
if scale_size:
|
|
64
|
+
image = image.resize(scale_size, Image.BILINEAR)
|
|
65
|
+
|
|
66
|
+
return image
|
|
67
|
+
|
|
68
|
+
def extract_frames(gif_path, output_dir, crop_size=None, scale_size=None,
|
|
69
|
+
bilateral_strength=0, median_strength=0, interframe_threshold=0, temporal_threshold=0):
|
|
70
|
+
gif = Image.open(gif_path)
|
|
71
|
+
|
|
72
|
+
gif_info = {}
|
|
73
|
+
gif_info["frames"] = gif.n_frames
|
|
74
|
+
gif_info["duration"] = gif.info["duration"] # 获取第一帧的持续时间
|
|
75
|
+
|
|
76
|
+
width, height = gif.size
|
|
77
|
+
if crop_size is not None:
|
|
78
|
+
width, height = crop_size
|
|
79
|
+
if scale_size is not None:
|
|
80
|
+
width, height = scale_size
|
|
81
|
+
gif_info["width"], gif_info["height"] = width, height
|
|
82
|
+
|
|
83
|
+
if gif_info["width"] % 4 != 0 or gif_info["height"] % 4 != 0:
|
|
84
|
+
print(f"img width {gif_info['width']} or height {gif_info['height']} is incorrect, must be aligned to 4")
|
|
85
|
+
sys.exit(1)
|
|
86
|
+
|
|
87
|
+
if not os.path.exists(output_dir):
|
|
88
|
+
os.makedirs(output_dir)
|
|
89
|
+
|
|
90
|
+
frames = []
|
|
91
|
+
display_frame = None
|
|
92
|
+
frame_count = 0
|
|
93
|
+
try:
|
|
94
|
+
while True:
|
|
95
|
+
frame = gif.convert("RGB")
|
|
96
|
+
|
|
97
|
+
# 裁剪和缩放处理
|
|
98
|
+
frame = crop_and_resize(frame, crop_size=crop_size, scale_size=scale_size)
|
|
99
|
+
|
|
100
|
+
# 双边滤波处理
|
|
101
|
+
if bilateral_strength > 0:
|
|
102
|
+
frame = apply_bilateral_filter(frame, bilateral_strength)
|
|
103
|
+
|
|
104
|
+
# 空域中值滤波处理
|
|
105
|
+
if median_strength > 0:
|
|
106
|
+
frame = frame.filter(ImageFilter.MedianFilter(size=int(median_strength * 2 + 1)))
|
|
107
|
+
|
|
108
|
+
frames.append(frame)
|
|
109
|
+
frame_count += 1
|
|
110
|
+
gif.seek(gif.tell() + 1)
|
|
111
|
+
except EOFError:
|
|
112
|
+
pass
|
|
113
|
+
|
|
114
|
+
# 时域中值滤波
|
|
115
|
+
if len(frames) > 1 and interframe_threshold > 0:
|
|
116
|
+
prev_frame = frames[-1]
|
|
117
|
+
for frame_index in tqdm(range(len(frames)), desc="Applying interframe median filter"):
|
|
118
|
+
next_frame = frames[(frame_index + 1) % len(frames)]
|
|
119
|
+
frames[frame_index] = apply_interframe_median_filter(prev_frame, frames[frame_index], next_frame, interframe_threshold)
|
|
120
|
+
prev_frame = frames[frame_index]
|
|
121
|
+
|
|
122
|
+
# 时域不更新算法
|
|
123
|
+
if temporal_threshold > 0:
|
|
124
|
+
for frame_index in tqdm(range(len(frames)), desc="Applying temporal denoise"):
|
|
125
|
+
if display_frame is not None:
|
|
126
|
+
frames[frame_index] = apply_temporal_denoise(display_frame, frames[frame_index], temporal_threshold)
|
|
127
|
+
display_frame = frames[frame_index]
|
|
128
|
+
|
|
129
|
+
for frame_index, frame in tqdm(enumerate(frames), total=frame_count, desc="Saving frames"):
|
|
130
|
+
png_filename = f"{os.path.splitext(os.path.basename(gif_path))[0]}_{frame_index}.png"
|
|
131
|
+
png_path = os.path.join(output_dir, png_filename)
|
|
132
|
+
frame.save(png_path, "PNG")
|
|
133
|
+
|
|
134
|
+
return gif_info
|
|
135
|
+
|
qgif-0.1.0/qgif/qgif
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
|
|
3
|
+
import sys
|
|
4
|
+
import os
|
|
5
|
+
import argparse
|
|
6
|
+
|
|
7
|
+
from qgif import VERSION
|
|
8
|
+
from qgif.qgif import convert_gif_to_qgif, decode_qgif
|
|
9
|
+
|
|
10
|
+
def check_range(param_name, t):
|
|
11
|
+
def checker(value):
|
|
12
|
+
value = t(value)
|
|
13
|
+
if value < 0 or value > 10:
|
|
14
|
+
raise argparse.ArgumentTypeError(f"Argument '{param_name}' must be between 0 and 10, but got {value}")
|
|
15
|
+
return value
|
|
16
|
+
return checker
|
|
17
|
+
|
|
18
|
+
if __name__ == "__main__":
|
|
19
|
+
parser = argparse.ArgumentParser(prog="qgif", description="QGIF generator and decoder")
|
|
20
|
+
subparsers = parser.add_subparsers(dest="command")
|
|
21
|
+
|
|
22
|
+
parser_convert = subparsers.add_parser("convert", help="Convert GIF to QGIF.")
|
|
23
|
+
parser_convert.add_argument("-f", "--format", required=True, choices=["gx64", "gx96"], help="The format of generated QGIF frames.")
|
|
24
|
+
parser_convert.add_argument("-i", "--input", required=True, help="The input GIF file name.")
|
|
25
|
+
parser_convert.add_argument("-o", "--output", required=True, help="Generated QGIF file name.")
|
|
26
|
+
parser_convert.add_argument("-fr", "--framerate", type=int, default=0, help="The frame rate of QGIF file. (default: same as GIF)")
|
|
27
|
+
parser_convert.add_argument("-l", "--lvgl", action="store_true", help="Generate LVGL C file.")
|
|
28
|
+
parser_convert.add_argument("--crop-size", type=int, nargs=2, help="Size to crop the image (width height)")
|
|
29
|
+
parser_convert.add_argument("--scale-size", type=int, nargs=2, help="Size to scale the image (width height)")
|
|
30
|
+
parser_convert.add_argument("--bilateral-strength", default=0, type=check_range("--bilateral-strength", float),
|
|
31
|
+
help="Strength of bilateral filter (0-10, can be a float)")
|
|
32
|
+
parser_convert.add_argument("--median-strength", default=0, type=check_range("--median-strength", int),
|
|
33
|
+
help="Strength of spatial median filter (0-10)")
|
|
34
|
+
parser_convert.add_argument("--interframe-threshold", default=0, type=check_range("--interframe-threshold", int),
|
|
35
|
+
help="Threshold for interframe median filtering (0-10)")
|
|
36
|
+
parser_convert.add_argument("--temporal-threshold", default=0, type=check_range("--temporal-threshold", int),
|
|
37
|
+
help="Threshold for temporal denoise (0-10)")
|
|
38
|
+
|
|
39
|
+
parser_decode = subparsers.add_parser("decode", help="Decode QGIF to frames.")
|
|
40
|
+
parser_decode.add_argument("-i", "--input", required=True, help="The input QGIF file name.")
|
|
41
|
+
parser_decode.add_argument("-o", "--output", required=True, help="The directory of output frames and pngs.")
|
|
42
|
+
parser_decode.add_argument("-c", "--colordepth", type=int, required=True, choices=[16, 32], help="The color depth of decoded frames.")
|
|
43
|
+
|
|
44
|
+
parser.add_argument("-V", "--version", action="version", version="qgif %s" % VERSION)
|
|
45
|
+
|
|
46
|
+
args = parser.parse_args()
|
|
47
|
+
if args.command == "convert":
|
|
48
|
+
convert_gif_to_qgif(args.input,
|
|
49
|
+
args.output,
|
|
50
|
+
args.format,
|
|
51
|
+
args.lvgl,
|
|
52
|
+
args.framerate,
|
|
53
|
+
crop_size=args.crop_size,
|
|
54
|
+
scale_size=args.scale_size,
|
|
55
|
+
bilateral_strength=args.bilateral_strength,
|
|
56
|
+
median_strength=args.median_strength,
|
|
57
|
+
interframe_threshold=args.interframe_threshold,
|
|
58
|
+
temporal_threshold=args.temporal_threshold)
|
|
59
|
+
elif args.command == "decode":
|
|
60
|
+
decode_qgif(args.input, args.output, args.colordepth)
|
|
61
|
+
|
qgif-0.1.0/qgif/qgif.py
ADDED
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
# coding: utf-8
|
|
2
|
+
import os
|
|
3
|
+
import sys
|
|
4
|
+
import signal
|
|
5
|
+
import ctypes
|
|
6
|
+
from ctypes import CFUNCTYPE, c_int
|
|
7
|
+
import numpy as np
|
|
8
|
+
import time
|
|
9
|
+
from PIL import Image
|
|
10
|
+
from tqdm import tqdm
|
|
11
|
+
|
|
12
|
+
from qgif.gif2png import extract_frames
|
|
13
|
+
from qgif.gen_lvgl_file import gen_lvgl_file
|
|
14
|
+
|
|
15
|
+
qgif_lib_path = os.path.join(os.path.dirname(__file__), "./qgif_C/libqgif.so")
|
|
16
|
+
qgif_lib = ctypes.CDLL(qgif_lib_path)
|
|
17
|
+
|
|
18
|
+
exit_flag = False
|
|
19
|
+
def signal_handler(sig, frame):
|
|
20
|
+
global exit_flag
|
|
21
|
+
exit_flag = True
|
|
22
|
+
sys.exit(1)
|
|
23
|
+
|
|
24
|
+
signal.signal(signal.SIGINT, signal_handler)
|
|
25
|
+
|
|
26
|
+
def get_filename_without_extension(file_path):
|
|
27
|
+
file_name_with_extension = os.path.basename(file_path)
|
|
28
|
+
file_name, _ = os.path.splitext(file_name_with_extension)
|
|
29
|
+
return file_name
|
|
30
|
+
|
|
31
|
+
def replace_extension(file_path, extension):
|
|
32
|
+
base = os.path.splitext(file_path)[0]
|
|
33
|
+
return base + extension
|
|
34
|
+
|
|
35
|
+
def get_compress_type_value(compress_type):
|
|
36
|
+
# 0: gx64
|
|
37
|
+
# 1: gx64a
|
|
38
|
+
# 2: gx96
|
|
39
|
+
# 3: gx96a
|
|
40
|
+
compress_types = {
|
|
41
|
+
"gx64" : 0,
|
|
42
|
+
"gx96" : 2,
|
|
43
|
+
}
|
|
44
|
+
return compress_types[compress_type]
|
|
45
|
+
|
|
46
|
+
"""
|
|
47
|
+
void compress_video(const char *image_path, const char *output_path, uint8_t compress_type, uint8_t frame_rate, int (*progress_callback)(int));
|
|
48
|
+
"""
|
|
49
|
+
qgif_lib.compress_video.argtypes = [ctypes.c_char_p, ctypes.c_char_p, ctypes.c_uint8, ctypes.c_uint8, CFUNCTYPE(c_int, c_int)]
|
|
50
|
+
qgif_lib.compress_video.restype = None
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
"""
|
|
54
|
+
void decode_qgif(const char *input_path, const char *output_path, unsigned char color_depth, int *width, int *height);
|
|
55
|
+
"""
|
|
56
|
+
qgif_lib.decode_qgif.argtypes = [ctypes.c_char_p, ctypes.c_char_p, ctypes.c_uint8,
|
|
57
|
+
ctypes.POINTER(ctypes.c_int), ctypes.POINTER(ctypes.c_int)]
|
|
58
|
+
qgif_lib.decode_qgif.restype = None
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def convert_gif_to_qgif(input_path, output_path, qgif_format, need_output_lvgl, frame_rate=0,\
|
|
62
|
+
crop_size=None, scale_size=None, bilateral_strength=0, median_strength=0,\
|
|
63
|
+
interframe_threshold=0, temporal_threshold=0):
|
|
64
|
+
png_path = "_tmp"
|
|
65
|
+
if not os.path.exists(png_path):
|
|
66
|
+
os.makedirs(png_path)
|
|
67
|
+
input_name = get_filename_without_extension(input_path)
|
|
68
|
+
input_png_path = os.path.join(png_path, f"{input_name}_X.png")
|
|
69
|
+
|
|
70
|
+
# gif to png
|
|
71
|
+
print("Convert gif to png ...")
|
|
72
|
+
gif_info = extract_frames(input_path, png_path, crop_size, scale_size, bilateral_strength, median_strength,
|
|
73
|
+
interframe_threshold, temporal_threshold)
|
|
74
|
+
print("Convert gif to png finish.")
|
|
75
|
+
|
|
76
|
+
# png to qgif
|
|
77
|
+
print("Convert png to qgif ...")
|
|
78
|
+
progress_bar = tqdm(total=gif_info["frames"])
|
|
79
|
+
|
|
80
|
+
if frame_rate < 1 or frame_rate > 64:
|
|
81
|
+
frame_rate = int(1000. / gif_info["duration"])
|
|
82
|
+
|
|
83
|
+
global exit_flag
|
|
84
|
+
def progress_callback(frame_index):
|
|
85
|
+
progress_bar.n = frame_index
|
|
86
|
+
progress_bar.refresh()
|
|
87
|
+
if exit_flag:
|
|
88
|
+
return True
|
|
89
|
+
else:
|
|
90
|
+
return False
|
|
91
|
+
|
|
92
|
+
PROGRESS_CALLBACK = CFUNCTYPE(c_int, c_int)
|
|
93
|
+
c_progress_callback = PROGRESS_CALLBACK(progress_callback)
|
|
94
|
+
|
|
95
|
+
compress_type_value = get_compress_type_value(qgif_format)
|
|
96
|
+
qgif_lib.compress_video(input_png_path.encode("utf-8"), output_path.encode("utf-8"), compress_type_value,
|
|
97
|
+
frame_rate, c_progress_callback)
|
|
98
|
+
os.system(f"rm -r {png_path}")
|
|
99
|
+
progress_bar.close()
|
|
100
|
+
if exit_flag:
|
|
101
|
+
sys.exit(1)
|
|
102
|
+
print(f"Convert png to qgif {output_path} finish.")
|
|
103
|
+
|
|
104
|
+
if need_output_lvgl:
|
|
105
|
+
print("Generate LVGL C file ...")
|
|
106
|
+
bin_path = output_path
|
|
107
|
+
lvgl_path = replace_extension(bin_path, ".c")
|
|
108
|
+
gen_lvgl_file(bin_path, lvgl_path, gif_info["width"], gif_info["height"])
|
|
109
|
+
print(f"Generate LVGL C file {lvgl_path} finish.")
|
|
110
|
+
|
|
111
|
+
print(f"============================")
|
|
112
|
+
print(f"QGIF {output_path}:")
|
|
113
|
+
print(f"frames: {gif_info['frames']}")
|
|
114
|
+
print(f"framerate: {frame_rate}")
|
|
115
|
+
print(f"img width: {gif_info['width']}, height: {gif_info['height']}")
|
|
116
|
+
print(f"============================")
|
|
117
|
+
|
|
118
|
+
def decode_qgif(input_path, output_path, color_depth):
|
|
119
|
+
if not os.path.exists(output_path):
|
|
120
|
+
os.makedirs(output_path)
|
|
121
|
+
output_frame_path = os.path.join(output_path, "frames")
|
|
122
|
+
if not os.path.exists(output_frame_path):
|
|
123
|
+
os.makedirs(output_frame_path)
|
|
124
|
+
output_png_path = os.path.join(output_path, "pngs")
|
|
125
|
+
if not os.path.exists(output_png_path):
|
|
126
|
+
os.makedirs(output_png_path)
|
|
127
|
+
|
|
128
|
+
print("Decode qgif to frames ...")
|
|
129
|
+
width = ctypes.c_int()
|
|
130
|
+
height = ctypes.c_int()
|
|
131
|
+
qgif_lib.decode_qgif(input_path.encode("utf-8"), output_frame_path.encode("utf-8"), color_depth,
|
|
132
|
+
ctypes.byref(width), ctypes.byref(height))
|
|
133
|
+
print("Decode qgif to frames finish.")
|
|
134
|
+
|
|
135
|
+
print("Save frames to pngs ...")
|
|
136
|
+
display_images(output_frame_path, output_png_path, width.value, height.value, color_depth)
|
|
137
|
+
print("Save frames to pngs finish.")
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def rgb565_to_rgb888(rgb565):
|
|
141
|
+
rgb565 = ((rgb565 >> 8) & 0xff) | ((rgb565 & 0xff) << 8)
|
|
142
|
+
r = ((rgb565 >> 11) & 0x1F) << 3
|
|
143
|
+
g = ((rgb565 >> 5) & 0x3F) << 2
|
|
144
|
+
b = (rgb565 & 0x1F) << 3
|
|
145
|
+
return (r, g, b)
|
|
146
|
+
|
|
147
|
+
def convert_rgb888_to_image(data, width, height):
|
|
148
|
+
img = Image.new("RGB", (width, height))
|
|
149
|
+
pixels = img.load()
|
|
150
|
+
for y in range(height):
|
|
151
|
+
for x in range(width):
|
|
152
|
+
index = y * width + x
|
|
153
|
+
pixels[x, y] = (data[index] & 0xff, (data[index] >> 8) & 0xff, (data[index] >> 16) & 0xff)
|
|
154
|
+
return img
|
|
155
|
+
|
|
156
|
+
def convert_rgb565_to_image(data, width, height):
|
|
157
|
+
img = Image.new("RGB", (width, height))
|
|
158
|
+
pixels = img.load()
|
|
159
|
+
for y in range(height):
|
|
160
|
+
for x in range(width):
|
|
161
|
+
index = y * width + x
|
|
162
|
+
rgb565 = data[index]
|
|
163
|
+
pixels[x, y] = rgb565_to_rgb888(rgb565)
|
|
164
|
+
return img
|
|
165
|
+
|
|
166
|
+
def display_images(frame_folder, png_folder, width, height, color_depth):
|
|
167
|
+
files = [f for f in os.listdir(frame_folder) if f.endswith(".rgb")]
|
|
168
|
+
files = [f for f in files if f.split(".")[0].isdigit()]
|
|
169
|
+
sorted_files = sorted(files, key=lambda x: int(x.split(".")[0]))
|
|
170
|
+
|
|
171
|
+
for image_file in tqdm(sorted_files):
|
|
172
|
+
with open(os.path.join(frame_folder, image_file), "rb") as f:
|
|
173
|
+
rgb_image = f.read()
|
|
174
|
+
if color_depth == 32:
|
|
175
|
+
rgb888_data = np.frombuffer(rgb_image, dtype=np.uint32)
|
|
176
|
+
img = convert_rgb888_to_image(rgb888_data, width, height)
|
|
177
|
+
elif color_depth == 16:
|
|
178
|
+
rgb565_data = np.frombuffer(rgb_image, dtype=np.uint16)
|
|
179
|
+
img = convert_rgb565_to_image(rgb565_data, width, height)
|
|
180
|
+
image_name = get_filename_without_extension(image_file)
|
|
181
|
+
img.save(os.path.join(png_folder, image_name + ".png"))
|
|
182
|
+
|
|
Binary file
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: qgif
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Convert GIF to QGIF, or decode QGIF.
|
|
5
|
+
Author: Hangzhou Nationalchip Inc.
|
|
6
|
+
Author-email: zhengdi@nationalchip.com
|
|
7
|
+
License: MIT Licence
|
|
8
|
+
Keywords: qgif nationalchip
|
|
9
|
+
Classifier: Development Status :: 3 - Alpha
|
|
10
|
+
Classifier: Intended Audience :: Developers
|
|
11
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
12
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
setup.py
|
|
2
|
+
qgif/__init__.py
|
|
3
|
+
qgif/__main__.py
|
|
4
|
+
qgif/gen_lvgl_file.py
|
|
5
|
+
qgif/gif2png.py
|
|
6
|
+
qgif/qgif
|
|
7
|
+
qgif/qgif.py
|
|
8
|
+
qgif.egg-info/PKG-INFO
|
|
9
|
+
qgif.egg-info/SOURCES.txt
|
|
10
|
+
qgif.egg-info/dependency_links.txt
|
|
11
|
+
qgif.egg-info/requires.txt
|
|
12
|
+
qgif.egg-info/top_level.txt
|
|
13
|
+
qgif/qgif_C/libqgif.so
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
qgif
|
qgif-0.1.0/setup.cfg
ADDED
qgif-0.1.0/setup.py
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# Copyright 2024 The QGIF Authors. All Rights Reserved.
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Proprietary License;
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
|
|
6
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
7
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
8
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
9
|
+
# See the License for the specific language governing permissions and
|
|
10
|
+
# limitations under the License.
|
|
11
|
+
# ==============================================================================
|
|
12
|
+
from setuptools import find_packages, setup
|
|
13
|
+
from setuptools.command.build_py import build_py
|
|
14
|
+
import os
|
|
15
|
+
|
|
16
|
+
from qgif import VERSION
|
|
17
|
+
|
|
18
|
+
REQUIRED_PACKAGES = [
|
|
19
|
+
'numpy',
|
|
20
|
+
'pillow',
|
|
21
|
+
'tqdm',
|
|
22
|
+
'opencv-python'
|
|
23
|
+
]
|
|
24
|
+
|
|
25
|
+
setup(
|
|
26
|
+
name='qgif',
|
|
27
|
+
version=VERSION,
|
|
28
|
+
description='Convert GIF to QGIF, or decode QGIF.',
|
|
29
|
+
author='Hangzhou Nationalchip Inc.',
|
|
30
|
+
author_email='zhengdi@nationalchip.com',
|
|
31
|
+
license='MIT Licence',
|
|
32
|
+
|
|
33
|
+
# PyPI package information.
|
|
34
|
+
classifiers=[
|
|
35
|
+
'Development Status :: 3 - Alpha',
|
|
36
|
+
'Intended Audience :: Developers',
|
|
37
|
+
'Topic :: Software Development :: Libraries :: Python Modules',
|
|
38
|
+
'License :: OSI Approved :: MIT License',
|
|
39
|
+
'Programming Language :: Python :: 3',
|
|
40
|
+
],
|
|
41
|
+
|
|
42
|
+
keywords='qgif nationalchip',
|
|
43
|
+
|
|
44
|
+
# Contained modules and scripts.
|
|
45
|
+
packages=find_packages(),
|
|
46
|
+
install_requires=REQUIRED_PACKAGES,
|
|
47
|
+
|
|
48
|
+
package_data={
|
|
49
|
+
'qgif': ['qgif_C/libqgif.so']
|
|
50
|
+
},
|
|
51
|
+
scripts=['qgif/qgif'],
|
|
52
|
+
entry_points={
|
|
53
|
+
},
|
|
54
|
+
)
|