Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add metadata packing tool #16

Merged
merged 6 commits into from
Sep 27, 2020
Merged

Add metadata packing tool #16

merged 6 commits into from
Sep 27, 2020

Conversation

Gadgetoid
Copy link
Contributor

Allows title, description, version, icon and splash to be packed and prepended to a 32blit .bin file.

icon should be an 8x8 .png
splash should be a 128x96 .png

Images can be configured just like the asset pipeline- packed, unpacked, paletted, etc

Example metadata.yml:

title: Rocks & Diamonds
description: A game of rocks and diamonds
version: v1.0.0
icon:
  file: icon-8x8.png
splash:
  file: splash-128x96.png

@Gadgetoid
Copy link
Contributor Author

The metadata format is pretty crude:

header
length (uint16)
title + '\0'
description + '\0'
version  + '\0'
icon
splash
  • The header is BLITGAME (ie: 42 4C 49 54 47 41 4D 45)
  • Two bytes of length- this is the length of the METADATA only and does not include the length of the header, or the two bytes of the length itself. So length + 8 + 2 is the start of the binary.
  • An unlimited(!!) length title string, null terminated
  • An unlimited(!!!) length description string, null terminated
  • An unlimited(!!!!!) length free text version string, null terminated
  • A SPRITEPK or SPRITERW containing the 8x8 pixel icon (need to enforce max size here)
  • A SPRITEPK or SPRITERW containing the 128x96 cover art (again need to enforce max size)

I think the full header would need to be written to Flash in order to ID games stored there?

I'm wondering if the cover art should be 64x48 pixels and pixel-doubled for display. That would put the cover art under 10k for a raw image. Enough room to express the character of the game, while being artistically constrained enough to gently encourage a nice pixel art covert instead of a straight up game screenshot or crop.

@Gadgetoid
Copy link
Contributor Author

Here's a very roughed-out example of a 64x48 cover art

image

@Daft-Freak
Copy link
Collaborator

In order to make less work for the firmware, I think the metadata should be appended with a small header to point to it. Otherwise:

  • It can't work with non-PIC unless you drop the metadata when flashing (and PIC would need more offsetting)
  • The header size would need to be aligned to 4 bytes (minor)

Maybe something like:

(based on the current header)
BLITGAME
[init/render/update pointers]
[pointer to end] (the PIC branch already has this here)

[game]

(appended)
BLITMETA
[metadata stuff]

@Gadgetoid
Copy link
Contributor Author

I did strongly suspect this would be the case - especially good point about the non-PIC code.

Easy enough to change the builder code.

How do you feel about it otherwise. Particularly the arbitrary length strings with null terminators?

I might settle on giving users a choice of:

  • 64 x 48 - RGB cover art
  • 128 x 96 - Paletted cover art

This reflects our screen mode choices and offers the same artistic constraints in addition to keeping the metadata payload relatively compact.

@Daft-Freak
Copy link
Collaborator

Rough attempt at parsing it seemed okay:

struct blit_game_metadata {
  std::string title, description, version;
};

void parse_metadata(char *data, uint16_t metadata_len, blit_game_metadata &metadata) {

  // parse strings
  uint16_t offset = 0;

  auto len = strlen(data);
  metadata.title = std::string(data, len);
  offset += len + 1;

  len = strlen(data + offset);
  metadata.description = std::string(data + offset, len);
  offset += len + 1;

  len = strlen(data + offset);
  metadata.version = std::string(data + offset, len);
  offset += len + 1;
}

Will probably need to be more careful about reading it though since the firmware currently only has a 63k heap...

@Gadgetoid
Copy link
Contributor Author

Haha, nice use of strlen to basically walk through the data and find the null terminator.

I guess I should add some artificial limits to cap title, description and version to some sensible ranges, too.

Maybe 64 chars for title, 1024 for description ( it could scroll, include extra info, etc ) and 16 chars for version which should generally be in the form vX.X.X but might include "alpha" or "beta" or something.

Allows title, description, version, icon and splash to be packed and prepended to a 32blit .bin file.

icon should be an 8x8 .png
splash should be a 128x96 .png

Images can be configured just like the asset pipeline- packed, unpacked, paletted, etc

Example metadata.yml:

```yml
title: Rocks & Diamonds
description: A game of rocks and diamonds
version: v1.0.0
icon:
  file: icon-8x8.png
splash:
  file: splash-128x96.png
```
@Daft-Freak
Copy link
Collaborator

Hmm, it isn't currently possible to have an RGB image. (packed=false is still paletted). Also, with icon and splash both optional it isn't possible to tell which one is there if one was specified but not the other.

@Gadgetoid
Copy link
Contributor Author

Gadgetoid commented Sep 26, 2020

My janky pasted-into-discord patch actually prefixes the icon/splash with "ICON" and "SPLA" for this purpose, but that might... not be good.

Re-iterated here for posterity:

diff --git a/src/ttblit/tool/metadata.py b/src/ttblit/tool/metadata.py
index 45a7653..0f32c11 100644
--- a/src/ttblit/tool/metadata.py
+++ b/src/ttblit/tool/metadata.py
@@ -3,8 +3,8 @@ import pathlib
 import struct

 import yaml
+from PIL import Image

-from ..asset.image import ImageAsset
 from ..core.tool import Tool


@@ -32,16 +32,24 @@ class Metadata(Tool):

         self.config = config

-    def prepare_image_asset(self, name, config):
-        image_file = pathlib.Path(config.get('file', ''))
-        config['input_file'] = image_file
-        config['output_file'] = image_file.with_suffix('.bin')
+    def prepare_image_asset(self, name, file):
+        required_size = {'icon': (8, 8), 'splash': (64, 48)}[name]
+        image_file = pathlib.Path(file)
         if not image_file.is_file():
             raise ValueError(f'{name} "{image_file}" does not exist!')
-        asset = ImageAsset(argparse.ArgumentParser().add_subparsers())
-        asset.prepare(config)
+        image = Image.open(image_file)
+        image = image.convert('RGB')

-        return asset.to_binary(open(image_file, 'rb').read())
+        if not (image.size == required_size):
+            iw, ih = image.size
+            w, h = required_size
+            raise ValueError(f'{name} should be {w}x{h} (is {iw}x{ih})')
+
+        header = bytes(name[:4].upper().encode('utf-8'))
+
+        image = image.tobytes()
+
+        return header + image

     def binary_size(self, bin):
         return struct.unpack('<I', bin[16:20])[0] & 0xffffff

But maybe the best case is to fill with a blank image or something when one or the other is not specified.

@Gadgetoid Gadgetoid merged commit 965cfc3 into master Sep 27, 2020
@Gadgetoid
Copy link
Contributor Author

Merging current state, since I want the fixes to tests while I work on #15

@Daft-Freak Daft-Freak deleted the blit-metadata branch January 26, 2023 12:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants