Прошло 2 года.
This commit is contained in:
21
lib/epdiy/scripts/LICENSE
Normal file
21
lib/epdiy/scripts/LICENSE
Normal file
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2017 Mr. PK
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
105
lib/epdiy/scripts/README.md
Normal file
105
lib/epdiy/scripts/README.md
Normal file
@@ -0,0 +1,105 @@
|
||||
## Scripts in this folder are for adding addtional capabilities to epdiy.
|
||||
|
||||
|
||||
|
||||
## imgconvert.py
|
||||
|
||||
#### usage:
|
||||
|
||||
python3 imgconvert.py [-h] -i INPUTFILE -n NAME -o OUTPUTFILE [-maxw MAX_WIDTH]
|
||||
[-maxh MAX_HEIGHT] [-nd] [-l GRAY_LEVELS]
|
||||
|
||||
**optional arguments:**
|
||||
|
||||
* **-h, --help** show this help message and exit
|
||||
|
||||
* **-i INPUTFILE**
|
||||
|
||||
* **-n NAME**
|
||||
|
||||
* **-o OUTPUTFILE**
|
||||
|
||||
* **-maxw MAX_WIDTH**
|
||||
|
||||
* **-maxh MAX_HEIGHT**
|
||||
|
||||
* **-nd** disable dithering
|
||||
|
||||
* **-l GRAY_LEVELS** configure the amount of colors
|
||||
|
||||
|
||||
==========================================================
|
||||
|
||||
## fontconvert.py
|
||||
|
||||
#### usage:
|
||||
|
||||
python3 fontconvert.py [-h] [--compress] [--additional-intervals ADDITIONAL_INTERVALS]
|
||||
[--string STRING]
|
||||
name size fontstack [fontstack ...]
|
||||
|
||||
Generate a header file from a font to be used with epdiy.
|
||||
|
||||
**positional arguments:**
|
||||
|
||||
* **name** name of the font to be used in epdiy.
|
||||
* **size** font size to use.
|
||||
* **fontstack** list of font files, ordered by descending priority. This is not actually implemented as yet. Please just use one file for now.
|
||||
|
||||
**optional arguments:**
|
||||
|
||||
* **-h**, --help show this help message and exit
|
||||
|
||||
* **--compress** compress glyph bitmaps.
|
||||
|
||||
* **--additional-intervals** ADDITIONAL_INTERVALS
|
||||
|
||||
Additional code point intervals to export as min,max. This argument
|
||||
can be repeated.
|
||||
|
||||
* **--string STRING** A quoted string of all required characters. The intervals are will be made from these characters if they exist in the ttf file. Missing characters will warn about their abscence.
|
||||
|
||||
|
||||
|
||||
####example:
|
||||
1. Download a ttf from where you like to a directory. As in: "~/Downloads/any_old_ttf.ttf"
|
||||
in the download directory
|
||||
|
||||
2. Run
|
||||
|
||||
`python3 fontconvert.py my_font 30 ~/Downloads/any_old_ttf.ttf --string '/0123456789:;@ABCDEFGH[\]^_`abcdefgh\{|}~¡¢£¤¥¦§¨©ª' > fonts.h`
|
||||
|
||||
* you will need to use special escapes for characters like ' or " This is system dependant though.
|
||||
|
||||
3. copy fonts.h into your app folder or where ever your app can find it.
|
||||
4. include it into your project with
|
||||
`#include fonts.h`
|
||||
Then use it just like any other font file in epdiy.
|
||||
|
||||
**To run this script the freetype module needs to be installed. This can be done with `pip install freetype-py` You will be warned if it is not accessible by the script.**
|
||||
|
||||
==========================================================
|
||||
|
||||
##waveform_hdrgen.py
|
||||
|
||||
####usage:
|
||||
|
||||
waveform_hdrgen.py [-h] [--list-modes] [--temperature-range TEMPERATURE_RANGE]
|
||||
[--export-modes EXPORT_MODES]
|
||||
name
|
||||
|
||||
**positional arguments:**
|
||||
name name of the waveform object.
|
||||
|
||||
**optional arguments:**
|
||||
|
||||
* **-h, --help** show this help message and exit
|
||||
|
||||
* **--list-modes** list the available modes for tis file.
|
||||
|
||||
* **--temperature-range TEMPERATURE_RANGE**
|
||||
only export waveforms in the temperature range of min,max °C.
|
||||
|
||||
* **--export-modes EXPORT_MODES**
|
||||
comma-separated list of waveform mode IDs to export.
|
||||
|
||||
160
lib/epdiy/scripts/epdiy_waveform_gen.py
Executable file
160
lib/epdiy/scripts/epdiy_waveform_gen.py
Executable file
@@ -0,0 +1,160 @@
|
||||
#!env python3
|
||||
|
||||
import json
|
||||
from modenames import mode_names, mode_id
|
||||
import sys
|
||||
|
||||
"""
|
||||
This script generates json files to generate waveforms from.
|
||||
"""
|
||||
|
||||
FRAME_TIMES_WHITE_TO_GL16 = {
|
||||
"ED097OC4": [3.0, 3.0, 2.0, 2.0, 3.0, 3.0, 3.0, 4.0, 4.0, 5.0, 5.0, 5.0, 10.0, 20.0, 30.0],
|
||||
"ED097OC1": [3.0, 3.0, 2.0, 2.0, 3.0, 3.0, 3.0, 4.0, 4.0, 5.0, 5.0, 5.0, 10.0, 20.0, 30.0],
|
||||
"ED060SC4": [3.0, 3.0, 2.0, 2.0, 3.0, 3.0, 3.0, 4.0, 4.0, 5.0, 5.0, 5.0, 10.0, 20.0, 30.0],
|
||||
"ED060XC3": [5.0, 4.0, 3.0, 3.0, 3.0, 3.0, 4.0, 4.0, 4.0, 5.0, 5.0, 5.0, 10.0, 20.0, 120.0],
|
||||
"ED047TC1": [3.0, 3.0, 2.0, 2.0, 3.0, 3.0, 3.0, 4.0, 4.0, 5.0, 5.0, 5.0, 10.0, 20.0, 30.0],
|
||||
"ED097TC2": [1.5, .8, .8, .8, .8, .8, 1.0, 1.0, 1.0, 1.0, 2.0, 2.0, 5.0, 10.0, 20.0],
|
||||
"ED060SCT": [1.5, .8, .8, .8, .8, .8, 1.0, 1.0, 1.0, 1.0, 2.0, 2.0, 5.0, 10.0, 20.0],
|
||||
"ED133UT2": [6.0, 3.0, 2.0, 2.0, 2.0, 2.0, 2.0, 3.0, 3.0, 3.0, 5.0, 5.0, 10.0, 20.0, 30.0],
|
||||
}
|
||||
|
||||
FRAME_TIMES_BLACK_TO_GL16 = {
|
||||
|
||||
"ED097OC4": [1.0, 1.0, .8, .8, .8, .8, .8, 1.0, 1.0, 1.5, 1.5, 2.0, 2.0, 10.0, 30.0],
|
||||
"ED097OC1": [1.0, 1.0, .8, .8, .8, .8, .8, 1.0, 1.0, 1.5, 1.5, 2.0, 2.0, 10.0, 30.0],
|
||||
"ED060SC4": [1.0, 1.0, .8, .8, .8, .8, .8, .8, .8, 1.0, 1.0, 2.0, 2.0, 10.0, 30.0],
|
||||
"ED060XC3": [1.0, 1.0, .5, .5, .5, .5, .5, .5, .5, 1.0, 1.0, 2.0, 2.0, 10.0, 30.0],
|
||||
"ED047TC1": [1.0, 1.0, .8, .8, .8, .8, .8, 1.0, 1.0, 1.5, 1.5, 2.0, 2.0, 10.0, 30.0],
|
||||
"ED097TC2": [1.5, .8, .4, .3, .3, .3, .3, .6, .6, .6, .6, 1.5, 2.0, 5.0, 15.0],
|
||||
"ED060SCT": [1.5, .8, .4, .3, .3, .3, .3, .6, .6, .6, .6, 1.5, 2.0, 5.0, 15.0],
|
||||
"ED133UT2": [2.0, 1.5, 1.5, 1.5, 1.5, 1.5, 1.5, 1.5, 1.5, 2.0, 3.0, 3.0, 5.0, 10.0, 20.0]
|
||||
}
|
||||
|
||||
FRAME_TIMES_DU = {
|
||||
"ED097OC4": [100, 100, 100, 100],
|
||||
"ED097OC1": [100, 100, 100, 100],
|
||||
"ED060SC4": [100, 100, 100, 100],
|
||||
"ED060XC3": [100, 100, 100, 100],
|
||||
"ED047TC1": [100, 100, 100, 100, 100],
|
||||
"ED097TC2": [100, 100, 100, 100, 100],
|
||||
"ED060SCT": [100, 100, 100, 100, 100],
|
||||
"ED133UT2": [100, 100, 100, 100]
|
||||
}
|
||||
|
||||
|
||||
def generate_frame(lut_func):
|
||||
phase = []
|
||||
for t in range(0, 32):
|
||||
line = []
|
||||
for f in range(0, 32):
|
||||
line.append(lut_func(t, f))
|
||||
phase.append(line)
|
||||
return phase
|
||||
|
||||
def generate_mode_x_to_GL16(display, mode):
|
||||
"""
|
||||
Generates LUTs for drawing successive black frames
|
||||
on a white screen.
|
||||
"""
|
||||
frame_times = None
|
||||
dirfunc = None
|
||||
if mode_names[mode] == "MODE_EPDIY_WHITE_TO_GL16":
|
||||
frame_times = FRAME_TIMES_WHITE_TO_GL16[display]
|
||||
def dirfunc(f, t, frame):
|
||||
return (t < (30 - 2 * frame) and f == 30)
|
||||
dirfunc = dirfunc
|
||||
|
||||
elif mode_names[mode] == "MODE_EPDIY_BLACK_TO_GL16":
|
||||
frame_times = FRAME_TIMES_BLACK_TO_GL16[display]
|
||||
def dirfunc(f, t, frame):
|
||||
return 2 * (t > (2 * frame) and f == 0)
|
||||
dirfunc = dirfunc
|
||||
else:
|
||||
raise ValueError(f"Cannot generate {mode} here.")
|
||||
|
||||
phases = []
|
||||
for frame in range(15):
|
||||
phase = generate_frame(lambda t, f: dirfunc(f, t, frame))
|
||||
phases.append(phase)
|
||||
return {"mode": mode, "ranges": [{"index": 0, "phases": phases, "phase_times": frame_times}]}
|
||||
|
||||
def generate_mode_GC16(display):
|
||||
"""
|
||||
Do arbitrary transitions by going to full black, then the desired value.
|
||||
"""
|
||||
phases = []
|
||||
for frame in range(15):
|
||||
phase = generate_frame(lambda t, f: f >= (30 - 2 * frame))
|
||||
phases.append(phase)
|
||||
for frame in range(15):
|
||||
phase = generate_frame(lambda t, f: 2 * (t > (2 * frame)))
|
||||
phases.append(phase)
|
||||
|
||||
frame_times = FRAME_TIMES_WHITE_TO_GL16[display] + FRAME_TIMES_BLACK_TO_GL16[display]
|
||||
return {"mode": mode_id("MODE_GC16"), "ranges": [{"index": 0, "phases": phases, "phase_times": frame_times}]}
|
||||
|
||||
def generate_mode_GL16(display):
|
||||
"""
|
||||
Do non-flashing arbitrary transitions by going to full black, then the desired value.
|
||||
"""
|
||||
phases = []
|
||||
for frame in range(15):
|
||||
phase = generate_frame(lambda t, f: f >= (30 - 2 * frame))
|
||||
phase[30][30] = 0
|
||||
phase[0][0] = 0
|
||||
phases.append(phase)
|
||||
for frame in range(15):
|
||||
phase = generate_frame(lambda t, f: 2 * (t > (2 * frame)))
|
||||
phase[30][30] = 0
|
||||
phase[0][0] = 0
|
||||
phases.append(phase)
|
||||
|
||||
frame_times = FRAME_TIMES_WHITE_TO_GL16[display] + FRAME_TIMES_BLACK_TO_GL16[display]
|
||||
return {"mode": mode_id("MODE_GL16"), "ranges": [{"index": 0, "phases": phases, "phase_times": frame_times}]}
|
||||
|
||||
|
||||
def generate_du(display):
|
||||
"""
|
||||
Generate the "direct update" mode for a display.
|
||||
"""
|
||||
frame_times = FRAME_TIMES_DU[display];
|
||||
num_phases = len(frame_times)
|
||||
|
||||
phases = []
|
||||
for frame in range(num_phases):
|
||||
def lutfunc(t, f):
|
||||
if t == 0 and f > 0:
|
||||
return 1
|
||||
elif t == 30 and f < 30:
|
||||
return 2
|
||||
else:
|
||||
return 0
|
||||
phase = generate_frame(lutfunc)
|
||||
phases.append(phase)
|
||||
|
||||
return {"mode": mode_id("MODE_DU"), "ranges": [{"index": 0, "phases": phases, "phase_times": frame_times}]}
|
||||
|
||||
|
||||
def generate_epdiy_waveform(display):
|
||||
# we say that these temperature waveforms are valid in ~room temperature.
|
||||
default_temperature_range = {"from": 20, "to": 30}
|
||||
|
||||
white_to_gl16 = generate_mode_x_to_GL16(display, mode_id("MODE_EPDIY_WHITE_TO_GL16"))
|
||||
black_to_gl16 = generate_mode_x_to_GL16(display, mode_id("MODE_EPDIY_BLACK_TO_GL16"))
|
||||
gc16 = generate_mode_GC16(display)
|
||||
gl16 = generate_mode_GL16(display)
|
||||
du = generate_du(display)
|
||||
|
||||
temperature_ranges = {"range_bounds": [default_temperature_range]}
|
||||
return {"temperature_ranges": temperature_ranges, "modes": [
|
||||
du,
|
||||
gc16,
|
||||
gl16,
|
||||
white_to_gl16,
|
||||
black_to_gl16
|
||||
]}
|
||||
|
||||
|
||||
waveform = generate_epdiy_waveform(sys.argv[1])
|
||||
print (json.dumps(waveform))
|
||||
299
lib/epdiy/scripts/fontconvert.py
Normal file
299
lib/epdiy/scripts/fontconvert.py
Normal file
@@ -0,0 +1,299 @@
|
||||
#!python3
|
||||
import sys
|
||||
|
||||
# inclusive unicode code point intervals
|
||||
# must not overlap and be in ascending order
|
||||
# modify intervals here
|
||||
# however if the "string" command line argument is used these are ignored
|
||||
|
||||
intervals = [
|
||||
(32, 126),
|
||||
(160, 255),
|
||||
# punctuation
|
||||
(0x2010, 0x205F),
|
||||
# arrows
|
||||
(0x2190, 0x21FF),
|
||||
# math
|
||||
#(0x2200, 0x22FF),
|
||||
# symbols
|
||||
(0x2300, 0x23FF),
|
||||
# box drawing
|
||||
#(0x2500, 0x259F),
|
||||
# geometric shapes
|
||||
(0x25A0, 0x25FF),
|
||||
# misc symbols
|
||||
(0x2600, 0x26F0),
|
||||
(0x2700, 0x27BF),
|
||||
# powerline symbols
|
||||
#(0xE0A0, 0xE0A2),
|
||||
#(0xE0B0, 0xE0B3),
|
||||
#(0x1F600, 0x1F680),
|
||||
]
|
||||
|
||||
|
||||
|
||||
try:
|
||||
import freetype
|
||||
except ImportError as error:
|
||||
sys.exit("To run this script the freetype module needs to be installed.\nThis can be done using:\npip install freetype-py")
|
||||
import zlib
|
||||
import sys
|
||||
import re
|
||||
import math
|
||||
import argparse
|
||||
from collections import namedtuple
|
||||
#see https://freetype-py.readthedocs.io/en/latest/ for documentation
|
||||
parser = argparse.ArgumentParser(description="Generate a header file from a font to be used with epdiy.")
|
||||
parser.add_argument("name", action="store", help="name of the font.")
|
||||
parser.add_argument("size", type=int, help="font size to use.")
|
||||
parser.add_argument("fontstack", action="store", nargs='+', help="list of font files, ordered by descending priority. This is not actually implemented please just use one file for now.")
|
||||
parser.add_argument("--compress", dest="compress", action="store_true", help="compress glyph bitmaps.")
|
||||
parser.add_argument("--additional-intervals", dest="additional_intervals", action="append", help="Additional code point intervals to export as min,max. This argument can be repeated.")
|
||||
parser.add_argument("--string", action="store", help="A string of all required characters. intervals are made up of this" )
|
||||
|
||||
args = parser.parse_args()
|
||||
command_line = ""
|
||||
prev_arg = ""
|
||||
for arg in sys.argv:
|
||||
# ~ if prev_arg == "--string":
|
||||
# ~ command_line = command_line + " '" + arg +"'"
|
||||
# ~ else:
|
||||
command_line = command_line + " " + arg
|
||||
# ~ prev_arg = arg
|
||||
|
||||
# ~ print (command_line)
|
||||
GlyphProps = namedtuple("GlyphProps", ["width", "height", "advance_x", "left", "top", "compressed_size", "data_offset", "code_point"])
|
||||
|
||||
font_stack = [freetype.Face(f) for f in args.fontstack]
|
||||
font_files = args.fontstack
|
||||
face_index = 0
|
||||
font_file = font_files[face_index]
|
||||
compress = args.compress
|
||||
size = args.size
|
||||
font_name = args.name
|
||||
|
||||
for face in font_stack:
|
||||
# shift by 6 bytes, because sizes are given as 6-bit fractions
|
||||
# the display has about 150 dpi.
|
||||
face.set_char_size(size << 6, size << 6, 150, 150)
|
||||
|
||||
|
||||
# assign intervals from argument parrameters ie. handle the string arg
|
||||
|
||||
if args.string != None:
|
||||
font_file = font_files[face_index]
|
||||
string = " " + args.string # always add space to the string it is easily forgotten
|
||||
chars = sorted(set(string))
|
||||
#make array of code pointscode_ponts.append(ord(char))
|
||||
code_points = list()
|
||||
intervals = [] # empty the intevals array NB. if you want to allways add default characters comment out this line
|
||||
# go through the sorted characters and make the intervals
|
||||
for char in chars:
|
||||
if( face.get_char_index(ord(char)) != 0 ):
|
||||
# this character is in the font file so add it to the new string.
|
||||
code_points.append(ord(char))
|
||||
else:
|
||||
print("The character ", char, " is not available in ", font_file, file=sys.stderr)
|
||||
lower = code_points[0]
|
||||
len_x = len(code_points)
|
||||
x = 0
|
||||
while x < len_x:
|
||||
# ~ print ("loop value x = ", x , file=sys.stderr)
|
||||
a = code_points[x];
|
||||
b = a;
|
||||
if( x < len_x - 1):
|
||||
b = code_points[x + 1];
|
||||
|
||||
if( a == b - 1 ):
|
||||
# ~ print("sequential", a, b, file=sys.stderr)
|
||||
if( lower == -1):
|
||||
lower = a
|
||||
else:
|
||||
# ~ print("non sequential", a, b , file=sys.stderr)
|
||||
if( lower == -1):
|
||||
# ~ print("single character")
|
||||
interval = (a , a)
|
||||
else:
|
||||
interval = (lower, a)
|
||||
# ~ print("interval", interval , file=sys.stderr)
|
||||
intervals.append(interval)
|
||||
lower = -1
|
||||
x = x + 1
|
||||
|
||||
|
||||
# base intervals are assigned dditional intervals from arguments
|
||||
add_ints = []
|
||||
if args.additional_intervals != None:
|
||||
add_ints = [tuple([int(n, base=0) for n in i.split(",")]) for i in args.additional_intervals]
|
||||
|
||||
intervals = sorted(intervals + add_ints)
|
||||
|
||||
# ~ print("Intervals are now: ", intervals, file=sys.stderr)
|
||||
|
||||
|
||||
def norm_floor(val):
|
||||
return int(math.floor(val / (1 << 6)))
|
||||
|
||||
def norm_ceil(val):
|
||||
return int(math.ceil(val / (1 << 6)))
|
||||
|
||||
for face in font_stack:
|
||||
# shift by 6 bytes, because sizes are given as 6-bit fractions
|
||||
# the display has about 150 dpi.
|
||||
face.set_char_size(size << 6, size << 6, 150, 150)
|
||||
|
||||
def chunks(l, n):
|
||||
for i in range(0, len(l), n):
|
||||
yield l[i:i + n]
|
||||
|
||||
total_size = 0
|
||||
total_packed = 0
|
||||
all_glyphs = []
|
||||
|
||||
# new globals
|
||||
total_chars = 0
|
||||
ascender = 0
|
||||
descender = 100
|
||||
f_height = 0
|
||||
|
||||
def load_glyph(code_point):
|
||||
global face_index
|
||||
face_index = 0
|
||||
while face_index < len(font_stack):
|
||||
face = font_stack[face_index]
|
||||
glyph_index = face.get_char_index(code_point)
|
||||
if glyph_index > 0:
|
||||
face.load_glyph(glyph_index, freetype.FT_LOAD_RENDER)
|
||||
#count characters found and find bounds of characters
|
||||
global ascender
|
||||
if ascender < face.size.ascender:
|
||||
ascender = face.size.ascender
|
||||
global descender
|
||||
if descender > face.size.descender:
|
||||
descender = face.size.descender
|
||||
global f_height
|
||||
if f_height < face.size.height:
|
||||
f_height = face.size.height
|
||||
global total_chars
|
||||
total_chars += 1
|
||||
return face
|
||||
break
|
||||
face_index += 1
|
||||
# this needs work
|
||||
# this needs to be handled better to show failed character and continue not just die a questionable death
|
||||
# this appears to have been designed to combine several font files
|
||||
# but that is not clear to the end user and this then looks like a bug
|
||||
print (f"falling back to font {face_index} for {chr(code_point)}.", file=sys.stderr)
|
||||
raise ValueError(f"code point {code_point} not found in font stack!")
|
||||
|
||||
for i_start, i_end in intervals:
|
||||
for code_point in range(i_start, i_end + 1):
|
||||
# handle missing characters in font file
|
||||
if( face.get_char_index(code_point) == 0 ):
|
||||
print("Character ", chr(code_point), "(", code_point, ") is not in ", font_file, file=sys.stderr)
|
||||
continue
|
||||
face = load_glyph(code_point)
|
||||
bitmap = face.glyph.bitmap
|
||||
pixels = []
|
||||
px = 0
|
||||
for i, v in enumerate(bitmap.buffer):
|
||||
y = i / bitmap.width
|
||||
x = i % bitmap.width
|
||||
if x % 2 == 0:
|
||||
px = (v >> 4)
|
||||
else:
|
||||
px = px | (v & 0xF0)
|
||||
pixels.append(px);
|
||||
px = 0
|
||||
# eol
|
||||
if x == bitmap.width - 1 and bitmap.width % 2 > 0:
|
||||
pixels.append(px)
|
||||
px = 0
|
||||
|
||||
packed = bytes(pixels);
|
||||
total_packed += len(packed)
|
||||
compressed = packed
|
||||
if compress:
|
||||
compressed = zlib.compress(packed)
|
||||
|
||||
glyph = GlyphProps(
|
||||
width = bitmap.width,
|
||||
height = bitmap.rows,
|
||||
advance_x = norm_floor(face.glyph.advance.x),
|
||||
left = face.glyph.bitmap_left,
|
||||
top = face.glyph.bitmap_top,
|
||||
compressed_size = len(compressed),
|
||||
data_offset = total_size,
|
||||
code_point = code_point,
|
||||
)
|
||||
total_size += len(compressed)
|
||||
all_glyphs.append((glyph, compressed))
|
||||
|
||||
# pipe seems to be a good heuristic for the "real" descender
|
||||
# face = load_glyph(ord('|'))
|
||||
# removed as max descender and assender are handled above
|
||||
|
||||
glyph_data = []
|
||||
glyph_props = []
|
||||
for index, glyph in enumerate(all_glyphs):
|
||||
props, compressed = glyph
|
||||
glyph_data.extend([b for b in compressed])
|
||||
glyph_props.append(props)
|
||||
print("", file=sys.stderr)
|
||||
print(f"Original font file {font_file} as {font_name} using {total_chars} characters", file=sys.stderr)
|
||||
|
||||
print("total", total_packed, file=sys.stderr)
|
||||
print("compressed", total_size, file=sys.stderr)
|
||||
|
||||
print("#pragma once")
|
||||
print("#include \"epdiy.h\"")
|
||||
|
||||
# add font file origin and characters at the head of the output file
|
||||
print("/*")
|
||||
print ( "Created with")
|
||||
print(command_line)
|
||||
print(f"As '{font_name}' with available {total_chars} characters")
|
||||
for i, g in enumerate(glyph_props):
|
||||
print (f"{chr(g.code_point)}", end ="" )
|
||||
print("")
|
||||
print("*/")
|
||||
|
||||
print(f"const uint8_t {font_name}_Bitmaps[{len(glyph_data)}] = {{")
|
||||
for c in chunks(glyph_data, 16):
|
||||
print (" " + " ".join(f"0x{b:02X}," for b in c))
|
||||
print ("};");
|
||||
|
||||
|
||||
print ('// GlyphProps[width, height, advance_x, left, top, compressed_size, data_offset, code_point]')
|
||||
print(f"const EpdGlyph {font_name}_Glyphs[] = {{")
|
||||
for i, g in enumerate(glyph_props):
|
||||
print (" { " + ", ".join([f"{a}" for a in list(g[:-1])]),"},", f"// '{chr(g.code_point) if g.code_point != 92 else '<backslash>'}'")
|
||||
print ("};");
|
||||
|
||||
print(f"const EpdUnicodeInterval {font_name}_Intervals[] = {{")
|
||||
offset = 0
|
||||
for i_start, i_end in intervals:
|
||||
print (f" {{ 0x{i_start:X}, 0x{i_end:X}, 0x{offset:X} }},")
|
||||
offset += i_end - i_start + 1
|
||||
print ("};");
|
||||
|
||||
print(f"const EpdFont {font_name} = {{")
|
||||
print(f" {font_name}_Bitmaps, // (*bitmap) Glyph bitmap pointer, all concatenated together")
|
||||
print(f" {font_name}_Glyphs, // glyphs Glyph array")
|
||||
print(f" {font_name}_Intervals, // intervals Valid unicode intervals for this font")
|
||||
print(f" {len(intervals)}, // interval_count Number of unicode intervals.intervals")
|
||||
print(f" {1 if compress else 0}, // compressed Does this font use compressed glyph bitmaps?")
|
||||
print(f" {norm_ceil(f_height)}, // advance_y Newline distance (y axis)")
|
||||
print(f" {norm_ceil(ascender)}, // ascender Maximal height of a glyph above the base line")
|
||||
print(f" {norm_floor(descender)}, // descender Maximal height of a glyph below the base line")
|
||||
print("};")
|
||||
print("/*")
|
||||
print("Included intervals")
|
||||
for i_start, i_end in intervals:
|
||||
print (f" ( {i_start}, {i_end}), ie. '{chr(i_start)}' - '{chr(i_end)}'")
|
||||
print("Included intervals", file=sys.stderr)
|
||||
for i_start, i_end in intervals:
|
||||
print (f" ( {i_start}, {i_end}), ie. '{chr(i_start)}' - '{chr(i_end)}'", file=sys.stderr)
|
||||
print("")
|
||||
print("*/")
|
||||
|
||||
75
lib/epdiy/scripts/imgconvert.py
Executable file
75
lib/epdiy/scripts/imgconvert.py
Executable file
@@ -0,0 +1,75 @@
|
||||
#!python3
|
||||
|
||||
from PIL import Image
|
||||
from argparse import ArgumentParser
|
||||
import sys
|
||||
import math
|
||||
|
||||
parser = ArgumentParser()
|
||||
parser.add_argument('-i', action="store", dest="inputfile", required=True)
|
||||
parser.add_argument('-n', action="store", dest="name", required=True)
|
||||
parser.add_argument('-o', action="store", dest="outputfile", required=True)
|
||||
parser.add_argument('-maxw', action="store", dest="max_width", default=1200, type=int)
|
||||
parser.add_argument('-maxh', action="store", dest="max_height", default=825, type=int)
|
||||
parser.add_argument('-nd', action="store_false", dest="dither", required=False, default=True)
|
||||
parser.add_argument('-l', action="store", dest="levels", default=16, type=int)
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
if args.levels > 16:
|
||||
raise Exception("Maximum 16 levels of gray supported by this program")
|
||||
|
||||
if args.levels < 2:
|
||||
# less than 2 doesn't make sense, there is at least on and off
|
||||
arg.levels = 2
|
||||
|
||||
if args.max_height < 1:
|
||||
raise Exception("Max height cannot be lower than 1")
|
||||
|
||||
if args.max_width < 1:
|
||||
raise Exception("Max width cannot be lower than 1")
|
||||
|
||||
|
||||
im = Image.open(args.inputfile)
|
||||
if im.mode == 'I;16': # some pngs load like this
|
||||
# so we fix it
|
||||
im = im.point(lambda i:i*(1./256)).convert('L')
|
||||
im.thumbnail((args.max_width, args.max_height), Image.LANCZOS)
|
||||
if im.mode != 'L':
|
||||
if im.mode != 'RGB':
|
||||
im = im.convert(mode='RGB') # drop alpha
|
||||
# now we can convert it to grayscale to get rid of colors
|
||||
im = im.convert(mode='L')
|
||||
|
||||
# but the quant algorithm works much better on RGB, so go to RGB mode just for that (even though we're still grayscale)
|
||||
im = im.convert(mode='RGB')
|
||||
|
||||
# prepare a palette for the levels of gray
|
||||
paletteImage = Image.new(mode='P', size=[1,1])
|
||||
# we make a palette that matches the indexed colors of the display, just repeating the same value for rgb
|
||||
paletteColors = [round(i * 255.0 / (args.levels - 1)) for i in range(0,args.levels) for c in range(0,3)]
|
||||
paletteImage.putpalette(paletteColors, rawmode='RGB')
|
||||
im = im.quantize(colors=args.levels, palette=paletteImage, dither=Image.Dither.FLOYDSTEINBERG if args.dither else Image.Dither.NONE)
|
||||
# now it's quantizied to the palette, 0 == dark, args.levels-1 == white
|
||||
|
||||
|
||||
# Write out the output file.
|
||||
with open(args.outputfile, 'w') as f:
|
||||
f.write("const uint32_t {}_width = {};\n".format(args.name, im.size[0]))
|
||||
f.write("const uint32_t {}_height = {};\n".format(args.name, im.size[1]))
|
||||
f.write(
|
||||
"const uint8_t {}_data[({}*{})/2] = {{\n".format(args.name, math.ceil(im.size[0] / 2) * 2, im.size[1])
|
||||
)
|
||||
for y in range(0, im.size[1]):
|
||||
byte = 0
|
||||
for x in range(0, im.size[0]):
|
||||
l = im.getpixel((x, y))
|
||||
if x % 2 == 0:
|
||||
byte = l
|
||||
else:
|
||||
byte |= l << 4
|
||||
f.write("0x{:02X}, ".format(byte))
|
||||
if im.size[0] % 2 == 1:
|
||||
f.write("0x{:02X}, ".format(byte))
|
||||
f.write("\n\t")
|
||||
f.write("};\n")
|
||||
21
lib/epdiy/scripts/modenames.py
Normal file
21
lib/epdiy/scripts/modenames.py
Normal file
@@ -0,0 +1,21 @@
|
||||
mode_names = {
|
||||
0: "MODE_INIT",
|
||||
1: "MODE_DU",
|
||||
2: "MODE_GC16",
|
||||
3: "MODE_GC16_FAST",
|
||||
4: "MODE_A2",
|
||||
5: "MODE_GL16",
|
||||
6: "MODE_GL16_FAST",
|
||||
7: "MODE_DU4",
|
||||
10: "MODE_GL4",
|
||||
11: "MODE_GL16_INV",
|
||||
0x10: "MODE_EPDIY_WHITE_TO_GL16",
|
||||
0x11: "MODE_EPDIY_BLACK_TO_GL16",
|
||||
0x3F: "MODE_UNKNOWN_WAVEFORM",
|
||||
}
|
||||
|
||||
def mode_id(name):
|
||||
for i, n in mode_names.items():
|
||||
if name == n:
|
||||
return i
|
||||
return 0x3F
|
||||
137
lib/epdiy/scripts/waveform_hdrgen.py
Executable file
137
lib/epdiy/scripts/waveform_hdrgen.py
Executable file
@@ -0,0 +1,137 @@
|
||||
#!env python3
|
||||
|
||||
import json
|
||||
import sys
|
||||
import argparse
|
||||
from modenames import mode_names
|
||||
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("--list-modes", help="list the available modes for tis file.", action = "store_true");
|
||||
parser.add_argument("--temperature-range", help="only export waveforms in the temperature range of min,max °C.");
|
||||
parser.add_argument("--export-modes", help="comma-separated list of waveform mode IDs to export.");
|
||||
parser.add_argument("name", help="name of the waveform object.");
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
waveforms = json.load(sys.stdin);
|
||||
|
||||
|
||||
total_size = 0
|
||||
|
||||
def phase_to_c(phase,bits_per_pixel_c=4):
|
||||
|
||||
N1 = len(phase)
|
||||
N2 = len(phase[0])
|
||||
N = 2**bits_per_pixel_c
|
||||
|
||||
if N1%N != 0:
|
||||
raise ValueError(f"first dimension of phases is {N1}. Allowed are multiples of {N}")
|
||||
|
||||
step1 = int(N1/N)
|
||||
|
||||
if N2%N != 0:
|
||||
raise ValueError(f"second dimension of phases is {N2}. Allowed are multiples of {N}")
|
||||
|
||||
step2 = int(N2/N)
|
||||
|
||||
targets = []
|
||||
for t in range(0, N1, step1):
|
||||
chunk = 0
|
||||
line = []
|
||||
i = 0
|
||||
for f in range(0, N2, step2):
|
||||
fr = phase[t][f]
|
||||
chunk = (chunk << 2) | fr
|
||||
i += 1
|
||||
if i == 4:
|
||||
i = 0
|
||||
line.append(chunk)
|
||||
chunk = 0
|
||||
targets.append(line)
|
||||
|
||||
return targets
|
||||
|
||||
def list_to_c(l):
|
||||
if isinstance(l, list):
|
||||
children = [list_to_c(c) for c in l]
|
||||
return "{" + ",".join(children) + "}"
|
||||
elif isinstance(l, int):
|
||||
return f"0x{l:02x}"
|
||||
else:
|
||||
assert(false)
|
||||
|
||||
if args.list_modes:
|
||||
for mode in waveforms["modes"]:
|
||||
print(f"""{mode["mode"]}: {mode_names[mode["mode"]]}""" )
|
||||
sys.exit(0)
|
||||
|
||||
tmin = -100
|
||||
tmax = 1000
|
||||
|
||||
if args.temperature_range:
|
||||
tmin, tmax = map(int, args.temperature_range.split(","))
|
||||
|
||||
modes = []
|
||||
|
||||
mode_filter = [wm["mode"] for wm in waveforms["modes"]]
|
||||
|
||||
if args.export_modes:
|
||||
mode_filter = list(map(int, args.export_modes.split(",")))
|
||||
|
||||
mode_filter = [m for m in mode_filter if any([wm["mode"] == m for wm in waveforms["modes"]])]
|
||||
|
||||
num_modes = len(mode_filter)
|
||||
|
||||
temp_intervals = []
|
||||
for bounds in waveforms["temperature_ranges"]["range_bounds"]:
|
||||
if bounds["to"] < tmin or bounds["from"] > tmax:
|
||||
continue
|
||||
temp_intervals.append(f"{{ .min = {bounds['from']}, .max = {bounds['to']} }}")
|
||||
|
||||
modes = []
|
||||
num_ranges = -1
|
||||
for m_index, mode in enumerate(waveforms["modes"]):
|
||||
|
||||
if not mode["mode"] in mode_filter:
|
||||
continue
|
||||
|
||||
ranges = []
|
||||
for i, r in enumerate(mode["ranges"]):
|
||||
bounds = waveforms["temperature_ranges"]["range_bounds"][i]
|
||||
if bounds["to"] < tmin or bounds["from"] > tmax:
|
||||
continue
|
||||
|
||||
phases = []
|
||||
phase_count = len(r["phases"])
|
||||
prev_phase = None
|
||||
for phase in r["phases"]:
|
||||
phases.append(phase_to_c(phase))
|
||||
|
||||
name = f"epd_wp_{args.name}_{mode['mode']}_{r['index']}"
|
||||
|
||||
phase_times= None
|
||||
if r.get("phase_times"):
|
||||
phase_times = [str(int(t * 10)) for t in r["phase_times"]]
|
||||
print(f"const int {name}_times[{len(phase_times)}] = {{ {','.join(phase_times) } }};")
|
||||
|
||||
|
||||
phase_times_str = f"&{name}_times[0]" if phase_times else "NULL"
|
||||
print(f"const uint8_t {name}_data[{phase_count}][16][4] = {list_to_c(phases)};")
|
||||
print(f"const EpdWaveformPhases {name} = {{ .phases = {phase_count}, .phase_times = {phase_times_str}, .luts = (const uint8_t*)&{name}_data[0] }};")
|
||||
ranges.append(name)
|
||||
|
||||
assert(num_ranges < 0 or num_ranges == len(ranges))
|
||||
|
||||
num_ranges = len(ranges)
|
||||
name = f"epd_wm_{args.name}_{mode['mode']}"
|
||||
range_pointers = ','.join(['&' + n for n in ranges])
|
||||
print(f"const EpdWaveformPhases* {name}_ranges[{len(ranges)}] = {{ {range_pointers} }};")
|
||||
print(f"const EpdWaveformMode {name} = {{ .type = {mode['mode']}, .temp_ranges = {len(ranges)}, .range_data = &{name}_ranges[0] }};");
|
||||
modes.append(name)
|
||||
|
||||
mode_pointers = ','.join(['&' + n for n in modes])
|
||||
range_data = ",".join(temp_intervals)
|
||||
|
||||
print(f"const EpdWaveformTempInterval {args.name}_intervals[{len(temp_intervals)}] = {{ {range_data} }};");
|
||||
print(f"const EpdWaveformMode* {args.name}_modes[{num_modes}] = {{ {mode_pointers} }};");
|
||||
print(f"const EpdWaveform {args.name} = {{ .num_modes = {num_modes}, .num_temp_ranges = {num_ranges}, .mode_data = &{args.name}_modes[0], .temp_intervals = &{args.name}_intervals[0] }};");
|
||||
Reference in New Issue
Block a user