Skip to content

Commit

Permalink
Merge 314147b into 466eefe
Browse files Browse the repository at this point in the history
  • Loading branch information
Gadgetoid committed Nov 9, 2020
2 parents 466eefe + 314147b commit 26d6ef3
Show file tree
Hide file tree
Showing 11 changed files with 255 additions and 85 deletions.
1 change: 1 addition & 0 deletions src/setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ install_requires =
pyserial
tqdm
freetype-py
construct

[options.packages.find]
exclude =
Expand Down
2 changes: 1 addition & 1 deletion src/tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,4 @@
def parsers():
parser = argparse.ArgumentParser()
parser.add_argument('--debug', action='store_true', help='Enable exception traces')
return parser, parser.add_subparsers(dest='command', help='Commands')
return parser, parser.add_subparsers(dest='command', help='Commands')
2 changes: 0 additions & 2 deletions src/tests/test_cmake.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import argparse
import base64
import tempfile

Expand Down Expand Up @@ -45,7 +44,6 @@ def test_asset_config_file():
return temp_yml



@pytest.fixture
def test_cmake_file():
temp_cmake = tempfile.NamedTemporaryFile('wb', suffix='.cmake')
Expand Down
1 change: 0 additions & 1 deletion src/tests/test_image_cli.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import argparse
import base64
import tempfile

Expand Down
1 change: 0 additions & 1 deletion src/tests/test_map_cli.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import argparse
import tempfile

import pytest
Expand Down
21 changes: 13 additions & 8 deletions src/tests/test_metadata.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import argparse
import base64
import tempfile

Expand All @@ -23,20 +22,26 @@ def test_invalid_binary_file():

@pytest.fixture
def test_metadata_file():
temp_png = tempfile.NamedTemporaryFile('wb', suffix='.png')
temp_png.write(base64.b64decode(b'iVBORw0KGgoAAAANSUhEUgAAAIAAAACACAMAAAD04JH5AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyNpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuNi1jMTQ4IDc5LjE2NDAzNiwgMjAxOS8wOC8xMy0wMTowNjo1NyAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIDIxLjAgKFdpbmRvd3MpIiB4bXBNTTpJbnN0YW5jZUlEPSJ4bXAuaWlkOjE5NkU4OENBNTk3NDExRUFCMTgyODFBRDFGMTZDODJGIiB4bXBNTTpEb2N1bWVudElEPSJ4bXAuZGlkOjE5NkU4OENCNTk3NDExRUFCMTgyODFBRDFGMTZDODJGIj4gPHhtcE1NOkRlcml2ZWRGcm9tIHN0UmVmOmluc3RhbmNlSUQ9InhtcC5paWQ6MTk2RTg4Qzg1OTc0MTFFQUIxODI4MUFEMUYxNkM4MkYiIHN0UmVmOmRvY3VtZW50SUQ9InhtcC5kaWQ6MTk2RTg4Qzk1OTc0MTFFQUIxODI4MUFEMUYxNkM4MkYiLz4gPC9yZGY6RGVzY3JpcHRpb24+IDwvcmRmOlJERj4gPC94OnhtcG1ldGE+IDw/eHBhY2tldCBlbmQ9InIiPz4ohDCNAAAACVBMVEUAAAD///8A/wDg4n4DAAAAmklEQVR42uzZMQqAMAxA0er9D+1Wl1ITijTi+6PQ8qYYsTVJUu/Ilj569gAAAABqAtIDM30UAAAAoDrAuwAAAODLK9nCdAQAAACoBHh/FD84AQAAALYDJEnpQTk6MPgCD98LAAAAUAiQXkXT9wIAAADUBEx/qEQBg2fhUQwAAAAAAAAAAHADFnbCKSC8EwIAAABsAkjST7sEGACd4xph9WtahAAAAABJRU5ErkJggg=='))
temp_png.flush()
temp_icon = tempfile.NamedTemporaryFile('wb', suffix='.png')
temp_icon.write(base64.b64decode(b'iVBORw0KGgoAAAANSUhEUgAAAIAAAACACAMAAAD04JH5AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyNpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuNi1jMTQ4IDc5LjE2NDAzNiwgMjAxOS8wOC8xMy0wMTowNjo1NyAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIDIxLjAgKFdpbmRvd3MpIiB4bXBNTTpJbnN0YW5jZUlEPSJ4bXAuaWlkOjE5NkU4OENBNTk3NDExRUFCMTgyODFBRDFGMTZDODJGIiB4bXBNTTpEb2N1bWVudElEPSJ4bXAuZGlkOjE5NkU4OENCNTk3NDExRUFCMTgyODFBRDFGMTZDODJGIj4gPHhtcE1NOkRlcml2ZWRGcm9tIHN0UmVmOmluc3RhbmNlSUQ9InhtcC5paWQ6MTk2RTg4Qzg1OTc0MTFFQUIxODI4MUFEMUYxNkM4MkYiIHN0UmVmOmRvY3VtZW50SUQ9InhtcC5kaWQ6MTk2RTg4Qzk1OTc0MTFFQUIxODI4MUFEMUYxNkM4MkYiLz4gPC9yZGY6RGVzY3JpcHRpb24+IDwvcmRmOlJERj4gPC94OnhtcG1ldGE+IDw/eHBhY2tldCBlbmQ9InIiPz4ohDCNAAAACVBMVEUAAAD///8A/wDg4n4DAAAAmklEQVR42uzZMQqAMAxA0er9D+1Wl1ITijTi+6PQ8qYYsTVJUu/Ilj569gAAAABqAtIDM30UAAAAoDrAuwAAAODLK9nCdAQAAACoBHh/FD84AQAAALYDJEnpQTk6MPgCD98LAAAAUAiQXkXT9wIAAADUBEx/qEQBg2fhUQwAAAAAAAAAAHADFnbCKSC8EwIAAABsAkjST7sEGACd4xph9WtahAAAAABJRU5ErkJggg=='))
temp_icon.flush()

tmp_splash = tempfile.NamedTemporaryFile('wb', suffix='.png')
tmp_splash.write(base64.b64decode(b'iVBORw0KGgoAAAANSUhEUgAAAAgAAAAICAYAAADED76LAAAAS0lEQVR4nGNkYGD4Xy7PzoANdD78ycBSLs/OkOAAEVhwAELD+AwH2BmYYKoXHECSQFLMxIADwBTjVEDQBBiAK0hwQOhCBowMBLwJACzCE3PfXB5IAAAAAElFTkSuQmCC'))
tmp_splash.flush()

temp_yml = tempfile.NamedTemporaryFile('w', suffix='.yml')
temp_yml.write(f'''title: Rocks & Diamonds
description: A pulse pounding, rock rollin', diamond hunting adventure
author: gadgetoid
icon:
file: {temp_icon.name}
splash:
file: {temp_png.name}
file: {tmp_splash.name}
version: v1.0.0
''')
temp_yml.flush()
return temp_yml, temp_png
return temp_yml, temp_icon, tmp_splash


def test_metadata_no_args(parsers):
Expand All @@ -53,7 +58,7 @@ def test_metadata_no_args(parsers):
def test_metadata(parsers, test_metadata_file, test_binary_file):
from ttblit.tool import metadata

test_metadata_file, test_metadata_splash_png = test_metadata_file
test_metadata_file, test_metadata_icon_png, test_metadata_splash_png = test_metadata_file
parser, subparser = parsers

metadata = metadata.Metadata(subparser)
Expand All @@ -69,7 +74,7 @@ def test_metadata(parsers, test_metadata_file, test_binary_file):
def test_metadata_invalid_bin(parsers, test_metadata_file, test_invalid_binary_file):
from ttblit.tool import metadata

test_metadata_file, test_metadata_splash_png = test_metadata_file
test_metadata_file, test_metadata_icon_png, test_metadata_splash_png = test_metadata_file
parser, subparser = parsers

metadata = metadata.Metadata(subparser)
Expand Down
1 change: 0 additions & 1 deletion src/tests/test_packer_cli.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import argparse
import base64
import pathlib
import tempfile
Expand Down
26 changes: 9 additions & 17 deletions src/ttblit/asset/image.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

from ..core.assetbuilder import AssetBuilder
from ..core.palette import Colour, Palette, type_palette
from ..core.struct import struct_blit_image


class ImageAsset(AssetBuilder):
Expand Down Expand Up @@ -71,26 +72,17 @@ def quantize_image(self, input_data):
def to_binary(self, input_data):
image = self.quantize_image(input_data)

palette_data = self.palette.tobytes()

if self.packed:
bit_length = self.palette.bit_length()
image_data = BitArray().join(BitArray(uint=x, length=bit_length) for x in image.tobytes()).tobytes()
else:
image_data = image.tobytes()

palette_len = len(self.palette)
palette_size = struct.pack('<B', 0 if palette_len == 256 else palette_len)

payload_size = struct.pack('<I', len(image_data) + len(palette_data) + 20)
image_size = struct.pack('<HH', *image.size)

data = bytes('SPRITEPK' if self.packed else 'SPRITERW', encoding='utf-8')
data += payload_size
data += image_size
data += b'\x02' # Pixel format
data += palette_size
data += palette_data
data += image_data

return data
return struct_blit_image.build({
'type': 'PK' if self.packed else 'RW',
'width': image.size[0],
'height': image.size[1],
'palette_entries': len(self.palette),
'palette': self.palette.tostruct(),
'data': image_data
})
11 changes: 11 additions & 0 deletions src/ttblit/core/palette.py
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,17 @@ def tolist(self):
def tobytes(self):
return bytes(self.tolist())

def tostruct(self):
result = []
for r, g, b, a in self.entries:
result.append({
'r': r,
'g': g,
'b': b,
'a': a,
})
return result

def bit_length(self):
return max(1, len(self.entries) - 1).bit_length()

Expand Down
116 changes: 116 additions & 0 deletions src/ttblit/core/struct.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
import math
import binascii

from construct import (Array, Bytes, Checksum, Computed, Const, Int8ul,
Int16ul, Int32ul, Optional, PaddedString, Prefixed,
PrefixedArray, RawCopy, Struct, Adapter, Rebuild,
this, len_)


def compute_bit_length(ctx):
"""Compute the required bit length for image data.
Uses the count of items in the palette to determine how
densely we can pack the image data.
"""
if ctx.type == "RW":
return 8
else:
return max(1, (ctx.palette_entries - 1).bit_length())


def compute_data_length(ctx):
"""Compute the required data length for palette based images.
We need this computation here so we can use `math.ceil` and
byte-align the result.
"""
return math.ceil((ctx.width * ctx.height * ctx.bit_length) / 8)


class PaletteCountAdapter(Adapter):
def _decode(self, obj, context, path):
if obj == 0:
obj = 256
return obj

def _encode(self, obj, context, path):
if obj == 256:
obj = 0
return obj


struct_blit_pixel = Struct(
'r' / Int8ul,
'g' / Int8ul,
'b' / Int8ul,
'a' / Int8ul
)

struct_blit_image = Struct(
'header' / Const(b'SPRITE'),
'type' / PaddedString(2, 'ASCII'),
'size' / Rebuild(Int32ul, len_(this.data) + (this.palette_entries * 4) + 18),
'width' / Int16ul,
'height' / Int16ul,
'format' / Const(0x02, Int8ul),
'palette_entries' / PaletteCountAdapter(Int8ul),
'palette' / Array(this.palette_entries, struct_blit_pixel),
'bit_length' / Computed(compute_bit_length),
'data_length' / Computed(compute_data_length),
'data' / Array(this.data_length, Int8ul)
)

struct_blit_meta = Struct(
'header' / Const(b'BLITMETA'),
'data' / Prefixed(Int16ul, Struct(
'checksum' / Checksum(
Int32ul,
lambda data: binascii.crc32(data),
this._._.bin.data
),
'date' / PaddedString(16, 'ascii'),
'title' / PaddedString(25, 'ascii'),
'description' / PaddedString(129, 'ascii'),
'version' / PaddedString(17, 'ascii'),
'author' / PaddedString(17, 'ascii'),
'icon' / struct_blit_image,
'splash' / struct_blit_image
))
)

struct_blit_bin = Struct(
'header' / Const(b'BLIT'),
'render' / Int32ul,
'update' / Int32ul,
'init' / Int32ul,
'length' / Int32ul,
# The length above is actually the _flash_end symbol from startup_user.s
# it includes the offset into 0x90000000 (external flash)
# we mask out the highest nibble to correct this into the actual bin length
# plus subtract 20 bytes for header, symbol and length dwords
'bin' / Bytes((this.length & 0x0FFFFFFF) - 20)
)

struct_blit_relo = Struct(
'header' / Const(b'RELO'),
'relocs' / PrefixedArray(Int32ul, Struct(
'reloc' / Int32ul
))
)

blit_game = Struct(
'relo' / Optional(struct_blit_relo),
'bin' / RawCopy(struct_blit_bin),
'meta' / Optional(struct_blit_meta)
)

blit_game_with_meta = Struct(
'relo' / Optional(struct_blit_relo),
'bin' / RawCopy(struct_blit_bin),
'meta' / struct_blit_meta
)

blit_game_with_meta_and_relo = Struct(
'relo' / struct_blit_relo,
'bin' / RawCopy(struct_blit_bin),
'meta' / struct_blit_meta
)

0 comments on commit 26d6ef3

Please sign in to comment.