1
0
forked from me/IronOS-Meta

Cleaning up _more_

This commit is contained in:
Ben V. Brown
2022-02-14 21:59:50 +11:00
parent 70d788783f
commit a526c3bb0d
4 changed files with 83 additions and 130 deletions

View File

@@ -1,8 +1,12 @@
# Preview of the logos
||   TS100   |   TS80    |   TS80P    |   Pinecil    |   IronOS    
-|-|-|-|-|-
static<br>(right)|<img src="/Bootup Logo/Logos/TS100.png" alt="TS100" width="200%">|<img src="/Bootup Logo/Logos/TS80.png" alt="TS80" width="100%">|<img src="/Bootup Logo/Logos/TS80P.png" alt="TS80P" width="100%">|<img src="/Bootup Logo/Logos/Pinecil.png" alt="Pinecil" width="100%">|<img src="/Bootup Logo/Logos/IronOS.png" alt="IronOS" width="100%">
static<br>(left)|<img src="/Bootup Logo/Logos/TS100_L.png" alt="TS100_L" width="200%">|<img src="/Bootup Logo/Logos/TS80_L.png" alt="TS80_L" width="100%">|<img src="/Bootup Logo/Logos/TS80P_L.png" alt="TS80P_L" width="100%">|<img src="/Bootup Logo/Logos/Pinecil_L.png" alt="Pinecil_L" width="100%">|<img src="/Bootup Logo/Logos/IronOS_L.png" alt="IronOS_L" width="100%">
animated|||||<img src="https://user-images.githubusercontent.com/53649486/153309202-ff517235-c81f-48e7-9ca2-990d0bb0764f.gif" alt="IronOS" width="100%">
notes|||||&emsp;&emsp;&ensp;^^^^^^^^<br>*(This loops just for<br>demonstration&nbsp;purposes,<br>the real thing<br>only plays once.)*
## Boot up logo's are logos or animations shown on boot of IronOS
These are programmed into the device just like the normal firmware.
They can be (re)programmed as many times as desired after flashing the normal firmware.
### Data storage format
The data is stored into the second last page of flash, this gives 1024 bytes of space for the entire payload of bootup logo data.
The first byte is marked purely to

View File

@@ -11,11 +11,7 @@ from output_dfu import DFUOutput
try:
from PIL import Image, ImageOps
except ImportError as error:
raise ImportError(
"{}: {} requres Python Imaging Library (PIL). "
"Install with `pip` (pip3 install pillow) or OS-specific package "
"management tool.".format(error, sys.argv[0])
)
raise ImportError("{}: {} requres Python Imaging Library (PIL). " "Install with `pip` (pip3 install pillow) or OS-specific package " "management tool.".format(error, sys.argv[0]))
VERSION_STRING = "1.0"
@@ -24,6 +20,9 @@ LCD_HEIGHT = 16
LCD_NUM_BYTES = LCD_WIDTH * LCD_HEIGHT // 8
LCD_PAGE_SIZE = 1024
DATA_PROGRAMMED_MARKER = 0xAA
FULL_FRAME_MARKER = 0xFF
class MiniwareSettings:
IMAGE_ADDRESS = 0x0800F800
@@ -41,9 +40,7 @@ class PinecilSettings:
DFU_PINECIL_PRODUCT = 0x0189
def still_image_to_bytes(
image: Image, negative: bool, dither: bool, threshold: int, preview_filename
):
def still_image_to_bytes(image: Image, negative: bool, dither: bool, threshold: int, preview_filename):
# convert to luminance
# do even if already black/white because PIL can't invert 1-bit so
# can't just pass thru in case --negative flag
@@ -67,11 +64,7 @@ def still_image_to_bytes(
if preview_filename:
image.save(preview_filename)
# pad to this size (also will be repeated in output Intel hex file)
data = [0] * LCD_PAGE_SIZE
# magic/required header
data[0] = 0xAA # Indicates programmed page
data[1] = 0xBB
data = []
# convert to LCD format
for ndx in range(LCD_WIDTH * LCD_HEIGHT // 8):
@@ -80,7 +73,15 @@ def still_image_to_bytes(
for y in range(8):
if image.getpixel((ndx % LCD_WIDTH, y + bottom_half_offset)):
byte |= 1 << y
data[2 + ndx] = byte
data.append(byte)
""" DEBUG
for row in range(LCD_HEIGHT):
for column in range(LCD_WIDTH):
if image.getpixel((column, row)): sys.stderr.write('')
else: sys.stderr.write(' ')
sys.stderr.write('\n')
"""
return data
@@ -93,9 +94,27 @@ def calculate_frame_delta_encode(previous_frame: bytearray, this_frame: bytearra
return damage
def animated_image_to_bytes(
imageIn: Image, negative: bool, dither: bool, threshold: int
):
def get_screen_blob(previous_frame: bytearray, this_frame: bytearray):
"""
Given two screens, returns the smaller representation
Either a full screen update
OR
A delta encoded form
"""
outputData = []
delta = calculate_frame_delta_encode(previous_frame, this_frame)
if len(delta) < (len(this_frame)):
outputData.append(len(delta))
outputData.extend(delta)
# print("delta encoded frame")
else:
outputData.append(FULL_FRAME_MARKER)
outputData.extend(this_frame)
# print("full encoded frame")
return outputData
def animated_image_to_bytes(imageIn: Image, negative: bool, dither: bool, threshold: int):
"""
Convert the gif into our best effort startup animation
We are delta-encoding on a byte by byte basis
@@ -106,75 +125,32 @@ def animated_image_to_bytes(
The naïve implementation would save the frame 5 times
But if we delta encode; we can make far more frames of animation for _some_ types of animations.
This means reveals are better than moves.
Data is stored in the byte blobs, so if you change one pixel in upper or lower row, changing another pixel in that column on that row is "free"
Data is stored in the byte blobs, so if you change one pixel, changing another pixel in that column on that row is "free"
"""
frameData = []
frameTiming = None
for framenum in range(0, imageIn.n_frames):
print(f"Frame {framenum}")
imageIn.seek(framenum)
image = imageIn
if image.mode != "L":
image = image.convert("L")
# Resize to lcd size using bicubic sampling
if image.size != (LCD_WIDTH, LCD_HEIGHT):
image = image.resize((LCD_WIDTH, LCD_HEIGHT), Image.BICUBIC)
if negative:
image = ImageOps.invert(image)
threshold = 255 - threshold # have to invert threshold as well
if dither:
image = image.convert("1")
else:
image = image.point(lambda pixel: 0 if pixel < threshold else 1, "1")
frameb = [0] * LCD_WIDTH * (LCD_HEIGHT // 8)
for ndx in range(LCD_WIDTH * LCD_HEIGHT // 8):
bottom_half_offset = 0 if ndx < LCD_WIDTH else 8
byte = 0
for y in range(8):
if image.getpixel((ndx % LCD_WIDTH, y + bottom_half_offset)):
byte |= 1 << y
# store in endian-reversed byte order
frameb[ndx] = byte
frameb = still_image_to_bytes(image, negative, dither, threshold, None)
frameData.append(frameb)
# Store inter-frame duration
frameDuration_ms = image.info["duration"]
if frameDuration_ms > 255:
frameDuration_ms = 255
if frameTiming is None:
if frameTiming is None or frameTiming == 0:
frameTiming = frameDuration_ms
print(f"Found {len(frameData)} frames")
print(f"Found {len(frameData)} frames, interval {frameTiming}ms")
# We have no mangled the image into our frambuffers
# Now create the "deltas" for each frame
frameDeltas = [[]]
for frame in range(1, len(frameData)):
frameDeltas.append(
calculate_frame_delta_encode(frameData[frame - 1], frameData[frame])
)
# Now we can build our output data blob
# First we always start with a full first frame; future optimisation to check if we should or not
bytes_black = sum([1 if x == 0 else 0 for x in frameData[0]])
if bytes_black > 96:
# It will take less room to delta encode first frame
outputData = [0xAA, 0xCC, frameTiming]
delta = calculate_frame_delta_encode([0x00] * (LCD_NUM_BYTES), frameData[0])
if len(delta) > (LCD_NUM_BYTES / 2):
raise Exception("BUG: Shouldn't delta encode more than 50%% of the screen")
outputData.append(len(delta))
outputData.extend(delta)
print("delta encoded first frame")
else:
outputData = [0xAA, 0xDD, frameTiming]
outputData.extend(frameData[0])
print("Used full encoded first frame")
# Now we delta encode all following frames
outputData = [DATA_PROGRAMMED_MARKER]
outputData.append(frameTiming)
outputData.extend(get_screen_blob([0x00] * (LCD_NUM_BYTES), frameData[0]))
"""
Format for each frame block is:
@@ -184,26 +160,13 @@ def animated_image_to_bytes(
OR
[0xFF][Full frame data]
"""
for frame in range(1, len(frameData)):
data = []
if len(frameDeltas[frame]) > LCD_NUM_BYTES:
data.append(0xFF)
data.extend(frameData[frame])
print(f"Frame {frame} full encodes to {len(data)} bytes")
else:
data.append(len(frameDeltas[frame]))
data.extend(frameDeltas[frame])
print(f"Frame {frame} delta encodes to {len(data)} bytes")
if len(outputData) + len(data) > 1024:
print(
f"Animation truncated, frame {frame} and onwards out of {len(frameData)} discarded"
)
for id in range(1, len(frameData)):
frameBlob = get_screen_blob(frameData[id - 1], frameData[id])
if (len(outputData) + len(frameBlob)) > LCD_PAGE_SIZE:
print(f"Truncating animation after {id} frames as we are out of space")
break
outputData.extend(data)
if len(outputData) < 1024:
pad = [0] * (1024 - len(outputData))
outputData.extend(pad)
print(f"Frame {id} encoded to {len(frameBlob)} bytes")
outputData.extend(frameBlob)
return outputData
@@ -237,22 +200,19 @@ def img2hex(
except BaseException as e:
raise IOError('error reading image file "{}": {}'.format(input_filename, e))
""" DEBUG
for row in range(LCD_HEIGHT):
for column in range(LCD_WIDTH):
if image.getpixel((column, row)): sys.stderr.write('')
else: sys.stderr.write(' ')
sys.stderr.write('\n')
"""
if make_erase_image:
data = [0xFF] * 1024
elif getattr(image, "is_animated", False):
data = animated_image_to_bytes(image, negative, dither, threshold)
else:
data = still_image_to_bytes(
image, negative, dither, threshold, preview_filename
)
# magic/required header
data = [DATA_PROGRAMMED_MARKER, 0xBB]
data.extend(still_image_to_bytes(image, negative, dither, threshold, preview_filename))
# Pad up to the full page size
if len(data) < LCD_PAGE_SIZE:
pad = [0] * (LCD_PAGE_SIZE - len(data))
data.extend(pad)
deviceSettings = MiniwareSettings
if isPinecil:
deviceSettings = PinecilSettings
@@ -266,9 +226,7 @@ def img2hex(
deviceSettings.DFU_PINECIL_PRODUCT,
deviceSettings.DFU_PINECIL_VENDOR,
)
HexOutput.writeFile(
output_filename_base + ".hex", data, deviceSettings.IMAGE_ADDRESS
)
HexOutput.writeFile(output_filename_base + ".hex", data, deviceSettings.IMAGE_ADDRESS)
def parse_commandline():
@@ -305,9 +263,7 @@ def parse_commandline():
"--threshold",
type=zero_to_255,
default=128,
help="0 to 255: gray (or color converted to gray) "
"above this becomes white, below becomes black; "
"ignored if using --dither",
help="0 to 255: gray (or color converted to gray) " "above this becomes white, below becomes black; " "ignored if using --dither",
)
parser.add_argument(
@@ -317,9 +273,7 @@ def parse_commandline():
help="use dithering (speckling) to convert gray or " "color to black and white",
)
parser.add_argument(
"-f", "--force", action="store_true", help="force overwriting of existing files"
)
parser.add_argument("-f", "--force", action="store_true", help="force overwriting of existing files")
parser.add_argument(
"-E",
"--erase",
@@ -327,9 +281,7 @@ def parse_commandline():
help="generate a logo erase file instead of a logo",
)
parser.add_argument(
"-p", "--pinecil", action="store_true", help="generate files for Pinecil"
)
parser.add_argument("-p", "--pinecil", action="store_true", help="generate files for Pinecil")
parser.add_argument(
"-v",
"--version",
@@ -346,17 +298,11 @@ if __name__ == "__main__":
args = parse_commandline()
if os.path.exists(args.output_filename) and not args.force:
sys.stderr.write(
'Won\'t overwrite existing file "{}" (use --force '
"option to override)\n".format(args.output_filename)
)
sys.stderr.write('Won\'t overwrite existing file "{}" (use --force ' "option to override)\n".format(args.output_filename))
sys.exit(1)
if args.preview and os.path.exists(args.preview) and not args.force:
sys.stderr.write(
'Won\'t overwrite existing file "{}" (use --force '
"option to override)\n".format(args.preview)
)
sys.stderr.write('Won\'t overwrite existing file "{}" (use --force ' "option to override)\n".format(args.preview))
sys.exit(1)
img2hex(

View File

@@ -14,17 +14,14 @@ class DFUOutput:
def writeFile(
cls,
file_name: str,
data: bytearray,
data_in: bytearray,
data_address: int,
tagetName: str,
alt_number: int,
product_id: int,
vendor_id: int,
):
data: bytearray = b""
for byte in data:
data += byte.to_bytes(1, byteorder="big")
data: bytearray = bytearray(data_in)
data = struct.pack("<2I", data_address, len(data)) + data
data = (

View File

@@ -1,2 +1,8 @@
# IronOS-Meta
Storing meta information about devices that dont need to be in the main repo
Storing meta information for IronOS.
This are things that are not part of the core "OS".
This includes photographs of hardware, datasheets, schematics and of course **bootup logos**.
This repository uses github actions to automagically build the logos for each device.
Periodically a "release" will be tagged and pre-compiled logo's will be put there as well to make it easy.