Switched to QOI file format

This commit is contained in:
Anthony Rabine 2023-07-28 17:16:13 +02:00
parent 05eda44df7
commit cebd349af4
18 changed files with 9446 additions and 157 deletions

View file

@ -37,6 +37,7 @@
"systick.h": "c",
"critical_section.h": "c",
"serializers.h": "c",
"cstring": "c"
"cstring": "c",
"typeinfo": "c"
}
}

View file

@ -43,6 +43,7 @@ set(OST_SRCS
system/ff/ffsystem.c
system/ff/ff_stubs.c
chip32/chip32_vm.c
library/mini_qoi.c
)
include_directories(
${CMAKE_CURRENT_SOURCE_DIR}/include

259
software/library/mini_qoi.c Normal file
View file

@ -0,0 +1,259 @@
#include "mini_qoi.h"
// ==== utilities ====
static inline uint32_t beu32_get(const uint8_t *a)
{
uint32_t val = 0;
val |= (((uint32_t)a[0]) << 24);
val |= (((uint32_t)a[1]) << 16);
val |= (((uint32_t)a[2]) << 8);
val |= ((uint32_t)a[3]);
return val;
}
// ==== mqoi_desc_t ====
/*
Initializes an mQOI image descriptor object.
*/
void mqoi_desc_init(mqoi_desc_t *desc)
{
memset(desc, 0, sizeof(mqoi_desc_t));
}
/*
Pushes a byte to the mQOI image descriptor object.
*/
void mqoi_desc_push(mqoi_desc_t *desc, uint8_t byte)
{
((uint8_t *)desc)[++desc->head] = byte;
}
/*
Reads a byte from the mQOI image descriptor object.
Returns NULL when there are none left to read.
*/
uint8_t *mqoi_desc_pop(mqoi_desc_t *desc)
{
if (desc->head >= sizeof(mqoi_desc_t) - 1)
return NULL;
return (uint8_t *)(desc + (++desc->head));
}
/*
Checks if a mQOI image descriptor is valid, and reads out its width and height into w and h.
If it returns a nonzero code, it is invalid (use mqoi_desc_err_t to understand what it means)
*/
uint8_t mqoi_desc_verify(mqoi_desc_t *desc, uint32_t *w, uint32_t *h)
{
if (desc->magic[0] != 'q' || desc->magic[1] != 'o' || desc->magic[2] != 'i' || desc->magic[3] != 'f')
{
return MQOI_DESC_INVALID_MAGIC;
}
*w = beu32_get(desc->width);
*h = beu32_get(desc->height);
if (desc->channels != MQOI_CHANNELS_RGB && desc->channels != MQOI_CHANNELS_RGBA)
{
return MQOI_DESC_INVALID_CHANNELS;
}
if (desc->colorspace != MQOI_COLORSPACE_SRGB && desc->colorspace != MQOI_COLORSPACE_LINEAR)
{
return MQOI_DESC_INVALID_COLORSPACE;
}
return MQOI_DESC_OK;
}
/*
Returns true when the mQOI image descriptor object is completely populated.
*/
inline bool mqoi_desc_done(const mqoi_desc_t *desc)
{
return desc->head >= sizeof(mqoi_desc_t) - 1;
}
// ==== mqoi_dec_t ====
/*
Initializes an mQOI decoder object.
If number of pixels in the image are given (via n_pix), the mqoi_dec_done function will work.
*/
void mqoi_dec_init(mqoi_dec_t *dec, uint32_t n_pix)
{
memset(dec, 0, sizeof(mqoi_dec_t));
dec->prev_px.a = 0xff;
dec->pix_left = n_pix;
}
/*
Pushes a byte to the mQOI decoder.
Don't call this more than once unless all the pixels have been popped via mqoi_dec_pop!
Also, please don't intermingle this function with calls to mqoi_dec_take!
*/
void mqoi_dec_push(mqoi_dec_t *dec, uint8_t byte)
{
dec->curr_chunk.value[dec->curr_chunk_head++] = byte;
if (dec->curr_chunk_size == 0 && dec->curr_chunk_head == 1)
{ // if we have the head of a new chunk, get its size
switch (dec->curr_chunk.head)
{ // test 8-bit tags
case MQOI_OP8_RUN_RGBA:
dec->curr_chunk_size = 5;
break;
case MQOI_OP8_RUN_RGB:
dec->curr_chunk_size = 4;
break;
default:
{ // test 2-bit tags
// chunk size is 1 unless it's an OP_LUMA chunk
dec->curr_chunk_size = 1 + ((dec->curr_chunk.head & MQOI_MASK_OP_2B) == MQOI_OP2_LUMA);
break;
}
}
}
}
/*
Automatically read up to 5 bytes so that a subsequent call of mqoi_dec_pop will not return NULL.
Returns the number of bytes that have been "taken" from the bytes array.
Please don't intermingle this function with calls to mqoi_dec_push!
*/
uint8_t mqoi_dec_take(mqoi_dec_t *dec, const uint8_t *bytes)
{
dec->curr_chunk.value[dec->curr_chunk_head++] = *(bytes++);
switch (dec->curr_chunk.head)
{ // test 8-bit tags
case MQOI_OP8_RUN_RGBA:
dec->curr_chunk_size = 5;
break;
case MQOI_OP8_RUN_RGB:
dec->curr_chunk_size = 4;
break;
default:
{ // test 2-bit tags
// chunk size is 1 unless it's an OP_LUMA chunk
dec->curr_chunk_size = 1 + ((dec->curr_chunk.head & MQOI_MASK_OP_2B) == MQOI_OP2_LUMA);
break;
}
}
while (dec->curr_chunk_head < dec->curr_chunk_size)
{
dec->curr_chunk.value[dec->curr_chunk_head++] = *(bytes++);
}
return dec->curr_chunk_head;
}
/*
Pops a pixel from the mQOI decoder.
Returns NULL if more data is needed, otherwise it returns the address of the next pixel.
*/
mqoi_rgba_t *mqoi_dec_pop(mqoi_dec_t *dec)
{
if (dec->curr_chunk_size && dec->curr_chunk_head >= dec->curr_chunk_size)
{ // if we're at the end of the current chunk
mqoi_rgba_t px = {.a = dec->prev_px.a};
mqoi_rgba_t *px_ptr = NULL;
bool chunk_done = true;
switch (dec->curr_chunk.head)
{ // test 8-bit tags
case MQOI_OP8_RUN_RGBA: // rgba handled
px.a = dec->curr_chunk.rgba.a;
case MQOI_OP8_RUN_RGB: // rgb handled
px.r = dec->curr_chunk.rgb.r;
px.g = dec->curr_chunk.rgb.g;
px.b = dec->curr_chunk.rgb.b;
break;
default:
{ // test 2-bit tags
switch (dec->curr_chunk.head & MQOI_MASK_OP_2B)
{
case MQOI_OP2_INDEX:
px_ptr = &dec->hashtable[dec->curr_chunk.head]; // no need to mask bits because the top bits are zero
break;
case MQOI_OP2_DIFF:
// shift out each channel and compute difference
px.b = dec->prev_px.b - 2 + (dec->curr_chunk.head & 0b11); // read out db
dec->curr_chunk.head >>= 2; // shift in dg
px.g = dec->prev_px.g - 2 + (dec->curr_chunk.head & 0b11); // read out dg
dec->curr_chunk.head >>= 2; // shift in dr
px.r = dec->prev_px.r - 2 + (dec->curr_chunk.head & 0b11); // read out dr
break;
case MQOI_OP2_LUMA:
{
int8_t dg = (dec->curr_chunk.head & MQOI_MASK_OP_LUMA_DG) - 32;
px.g = dec->prev_px.g + dg;
px.b = dec->prev_px.b + (dec->curr_chunk.drdb & 0b1111) - 8 + dg;
dec->curr_chunk.drdb >>= 4;
px.r = dec->prev_px.r + (dec->curr_chunk.drdb & 0b1111) - 8 + dg;
break;
}
case MQOI_OP2_RUN:
{
uint8_t run = dec->curr_chunk.head & MQOI_MASK_OP_RUN;
px_ptr = &dec->prev_px;
if (run)
{ // if there are still pixels left to emit,
chunk_done = false; // don't reset the chunk (bring us back to the block)
dec->curr_chunk.head--;
}
break;
}
}
break;
}
}
if (px_ptr == NULL)
{ // if the return pixel is null (not in hashtable yet)
uint8_t px_hash = MQOI_RGBA_HASH(px); // hash it
px_ptr = &dec->hashtable[px_hash]; // get its hashed position
px_ptr->r = px.r;
px_ptr->g = px.g;
px_ptr->b = px.b;
px_ptr->a = px.a;
}
if (chunk_done)
{ // if the chunk is done, reset the head
dec->curr_chunk_head = 0;
dec->curr_chunk_size = 0;
}
dec->prev_px.r = px_ptr->r;
dec->prev_px.g = px_ptr->g;
dec->prev_px.b = px_ptr->b;
dec->prev_px.a = px_ptr->a;
dec->pix_left--;
return px_ptr;
}
return NULL;
}
/*
Returns true if the decoder has emitted all pixels necessary to complete the image being decoded.
Note that this function will only work if n_pix was given during the initialization of the decoder.
*/
inline bool mqoi_dec_done(const mqoi_dec_t *dec)
{
return dec->pix_left == 0;
}

150
software/library/mini_qoi.h Normal file
View file

@ -0,0 +1,150 @@
#pragma once
#ifndef MINI_QOI_H
#define MINI_QOI_H
#ifdef __cplusplus
extern "C"
{
#endif
#include <stdint.h>
#include <string.h>
#include <stdbool.h>
#define MQOI_RGB_HASH(px) ((px.r * 3 + px.g * 5 + px.b * 7) & 0b00111111)
#define MQOI_RGBA_HASH(px) ((px.r * 3 + px.g * 5 + px.b * 7 + px.a * 11) & 0b00111111)
#define MQOI_HEADER_SIZE (14)
#define MQOI_MASK_OP_2B (0b11000000)
#define MQOI_MASK_OP_8B (0b11111111)
#define MQOI_MASK_OP_LUMA_DG (0b00111111)
#define MQOI_MASK_OP_RUN (0b00111111)
// basic types
typedef enum
{
MQOI_DESC_OK = 0, // The descriptor is valid
MQOI_DESC_INVALID_MAGIC, // The magic value isn't correct
MQOI_DESC_INVALID_CHANNELS, // The channel number isn't valid
MQOI_DESC_INVALID_COLORSPACE, // The colorspace isn't valid
} mqoi_desc_err_t;
typedef enum
{
MQOI_OP2_INDEX = (0b00 << 6),
MQOI_OP2_DIFF = (0b01 << 6),
MQOI_OP2_LUMA = (0b10 << 6),
MQOI_OP2_RUN = (0b11 << 6),
MQOI_OP8_RUN_RGB = (0b11111110),
MQOI_OP8_RUN_RGBA = (0b11111111),
} mqoi_op_t;
typedef enum
{
MQOI_CHANNELS_RGB = 3,
MQOI_CHANNELS_RGBA,
} mqoi_channels_t;
typedef enum
{
MQOI_COLORSPACE_SRGB = 0,
MQOI_COLORSPACE_LINEAR = 1,
} mqoi_colorspace_t;
typedef union
{
struct
{
uint8_t r, g, b;
};
uint8_t value[3];
} mqoi_rgb_t;
typedef union
{
struct
{
uint8_t r, g, b, a;
};
uint8_t value[4];
} mqoi_rgba_t;
typedef struct
{
uint8_t head;
uint8_t magic[4];
uint8_t width[4]; // big-endian width
uint8_t height[4]; // big-endian height
uint8_t channels;
uint8_t colorspace;
} mqoi_desc_t;
// ==== chunks ====
typedef union
{
struct
{
uint8_t head;
union
{
mqoi_rgb_t rgb;
mqoi_rgba_t rgba;
uint8_t drdb;
};
};
uint8_t value[5];
} mqoi_chunk_t;
// ==== codecs ====
typedef struct
{
mqoi_rgba_t hashtable[64];
mqoi_rgba_t prev_px;
mqoi_chunk_t working_chunk;
uint8_t working_chunk_size;
} mqoi_enc_t;
typedef struct
{
mqoi_rgba_t hashtable[64];
mqoi_rgba_t prev_px;
mqoi_chunk_t curr_chunk;
uint8_t curr_chunk_head : 4;
uint8_t curr_chunk_size : 4;
uint32_t pix_left;
} mqoi_dec_t;
// ==== mqoi_desc_t ====
void mqoi_desc_init(mqoi_desc_t *desc);
void mqoi_desc_push(mqoi_desc_t *desc, uint8_t byte);
uint8_t *mqoi_desc_pop(mqoi_desc_t *desc);
uint8_t mqoi_desc_verify(mqoi_desc_t *desc, uint32_t *w, uint32_t *h);
bool mqoi_desc_done(const mqoi_desc_t *desc);
/* the encoder is still WIP
void mqoi_enc_init(mqoi_enc_t * enc);
void mqoi_enc_push(mqoi_enc_t * enc, mqoi_rgba_t * pix)
mqoi_chunk_t * mqoi_enc_pop(mqoi_enc_t * enc, uint8_t * size);
*/
// ==== mqoi_dec_t ====
void mqoi_dec_init(mqoi_dec_t *dec, uint32_t n_pix);
void mqoi_dec_push(mqoi_dec_t *dec, uint8_t byte);
uint8_t mqoi_dec_take(mqoi_dec_t *dec, const uint8_t *bytes);
mqoi_rgba_t *mqoi_dec_pop(mqoi_dec_t *dec);
bool mqoi_dec_done(const mqoi_dec_t *dec);
#ifdef __cplusplus
}
#endif
#endif

649
software/library/qoi.h Normal file
View file

@ -0,0 +1,649 @@
/*
Copyright (c) 2021, Dominic Szablewski - https://phoboslab.org
SPDX-License-Identifier: MIT
QOI - The "Quite OK Image" format for fast, lossless image compression
-- About
QOI encodes and decodes images in a lossless format. Compared to stb_image and
stb_image_write QOI offers 20x-50x faster encoding, 3x-4x faster decoding and
20% better compression.
-- Synopsis
// Define `QOI_IMPLEMENTATION` in *one* C/C++ file before including this
// library to create the implementation.
#define QOI_IMPLEMENTATION
#include "qoi.h"
// Encode and store an RGBA buffer to the file system. The qoi_desc describes
// the input pixel data.
qoi_write("image_new.qoi", rgba_pixels, &(qoi_desc){
.width = 1920,
.height = 1080,
.channels = 4,
.colorspace = QOI_SRGB
});
// Load and decode a QOI image from the file system into a 32bbp RGBA buffer.
// The qoi_desc struct will be filled with the width, height, number of channels
// and colorspace read from the file header.
qoi_desc desc;
void *rgba_pixels = qoi_read("image.qoi", &desc, 4);
-- Documentation
This library provides the following functions;
- qoi_read -- read and decode a QOI file
- qoi_decode -- decode the raw bytes of a QOI image from memory
- qoi_write -- encode and write a QOI file
- qoi_encode -- encode an rgba buffer into a QOI image in memory
See the function declaration below for the signature and more information.
If you don't want/need the qoi_read and qoi_write functions, you can define
QOI_NO_STDIO before including this library.
This library uses malloc() and free(). To supply your own malloc implementation
you can define QOI_MALLOC and QOI_FREE before including this library.
This library uses memset() to zero-initialize the index. To supply your own
implementation you can define QOI_ZEROARR before including this library.
-- Data Format
A QOI file has a 14 byte header, followed by any number of data "chunks" and an
8-byte end marker.
struct qoi_header_t {
char magic[4]; // magic bytes "qoif"
uint32_t width; // image width in pixels (BE)
uint32_t height; // image height in pixels (BE)
uint8_t channels; // 3 = RGB, 4 = RGBA
uint8_t colorspace; // 0 = sRGB with linear alpha, 1 = all channels linear
};
Images are encoded row by row, left to right, top to bottom. The decoder and
encoder start with {r: 0, g: 0, b: 0, a: 255} as the previous pixel value. An
image is complete when all pixels specified by width * height have been covered.
Pixels are encoded as
- a run of the previous pixel
- an index into an array of previously seen pixels
- a difference to the previous pixel value in r,g,b
- full r,g,b or r,g,b,a values
The color channels are assumed to not be premultiplied with the alpha channel
("un-premultiplied alpha").
A running array[64] (zero-initialized) of previously seen pixel values is
maintained by the encoder and decoder. Each pixel that is seen by the encoder
and decoder is put into this array at the position formed by a hash function of
the color value. In the encoder, if the pixel value at the index matches the
current pixel, this index position is written to the stream as QOI_OP_INDEX.
The hash function for the index is:
index_position = (r * 3 + g * 5 + b * 7 + a * 11) % 64
Each chunk starts with a 2- or 8-bit tag, followed by a number of data bits. The
bit length of chunks is divisible by 8 - i.e. all chunks are byte aligned. All
values encoded in these data bits have the most significant bit on the left.
The 8-bit tags have precedence over the 2-bit tags. A decoder must check for the
presence of an 8-bit tag first.
The byte stream's end is marked with 7 0x00 bytes followed a single 0x01 byte.
The possible chunks are:
.- QOI_OP_INDEX ----------.
| Byte[0] |
| 7 6 5 4 3 2 1 0 |
|-------+-----------------|
| 0 0 | index |
`-------------------------`
2-bit tag b00
6-bit index into the color index array: 0..63
A valid encoder must not issue 2 or more consecutive QOI_OP_INDEX chunks to the
same index. QOI_OP_RUN should be used instead.
.- QOI_OP_DIFF -----------.
| Byte[0] |
| 7 6 5 4 3 2 1 0 |
|-------+-----+-----+-----|
| 0 1 | dr | dg | db |
`-------------------------`
2-bit tag b01
2-bit red channel difference from the previous pixel between -2..1
2-bit green channel difference from the previous pixel between -2..1
2-bit blue channel difference from the previous pixel between -2..1
The difference to the current channel values are using a wraparound operation,
so "1 - 2" will result in 255, while "255 + 1" will result in 0.
Values are stored as unsigned integers with a bias of 2. E.g. -2 is stored as
0 (b00). 1 is stored as 3 (b11).
The alpha value remains unchanged from the previous pixel.
.- QOI_OP_LUMA -------------------------------------.
| Byte[0] | Byte[1] |
| 7 6 5 4 3 2 1 0 | 7 6 5 4 3 2 1 0 |
|-------+-----------------+-------------+-----------|
| 1 0 | green diff | dr - dg | db - dg |
`---------------------------------------------------`
2-bit tag b10
6-bit green channel difference from the previous pixel -32..31
4-bit red channel difference minus green channel difference -8..7
4-bit blue channel difference minus green channel difference -8..7
The green channel is used to indicate the general direction of change and is
encoded in 6 bits. The red and blue channels (dr and db) base their diffs off
of the green channel difference and are encoded in 4 bits. I.e.:
dr_dg = (cur_px.r - prev_px.r) - (cur_px.g - prev_px.g)
db_dg = (cur_px.b - prev_px.b) - (cur_px.g - prev_px.g)
The difference to the current channel values are using a wraparound operation,
so "10 - 13" will result in 253, while "250 + 7" will result in 1.
Values are stored as unsigned integers with a bias of 32 for the green channel
and a bias of 8 for the red and blue channel.
The alpha value remains unchanged from the previous pixel.
.- QOI_OP_RUN ------------.
| Byte[0] |
| 7 6 5 4 3 2 1 0 |
|-------+-----------------|
| 1 1 | run |
`-------------------------`
2-bit tag b11
6-bit run-length repeating the previous pixel: 1..62
The run-length is stored with a bias of -1. Note that the run-lengths 63 and 64
(b111110 and b111111) are illegal as they are occupied by the QOI_OP_RGB and
QOI_OP_RGBA tags.
.- QOI_OP_RGB ------------------------------------------.
| Byte[0] | Byte[1] | Byte[2] | Byte[3] |
| 7 6 5 4 3 2 1 0 | 7 .. 0 | 7 .. 0 | 7 .. 0 |
|-------------------------+---------+---------+---------|
| 1 1 1 1 1 1 1 0 | red | green | blue |
`-------------------------------------------------------`
8-bit tag b11111110
8-bit red channel value
8-bit green channel value
8-bit blue channel value
The alpha value remains unchanged from the previous pixel.
.- QOI_OP_RGBA ---------------------------------------------------.
| Byte[0] | Byte[1] | Byte[2] | Byte[3] | Byte[4] |
| 7 6 5 4 3 2 1 0 | 7 .. 0 | 7 .. 0 | 7 .. 0 | 7 .. 0 |
|-------------------------+---------+---------+---------+---------|
| 1 1 1 1 1 1 1 1 | red | green | blue | alpha |
`-----------------------------------------------------------------`
8-bit tag b11111111
8-bit red channel value
8-bit green channel value
8-bit blue channel value
8-bit alpha channel value
*/
/* -----------------------------------------------------------------------------
Header - Public functions */
#ifndef QOI_H
#define QOI_H
#ifdef __cplusplus
extern "C" {
#endif
/* A pointer to a qoi_desc struct has to be supplied to all of qoi's functions.
It describes either the input format (for qoi_write and qoi_encode), or is
filled with the description read from the file header (for qoi_read and
qoi_decode).
The colorspace in this qoi_desc is an enum where
0 = sRGB, i.e. gamma scaled RGB channels and a linear alpha channel
1 = all channels are linear
You may use the constants QOI_SRGB or QOI_LINEAR. The colorspace is purely
informative. It will be saved to the file header, but does not affect
how chunks are en-/decoded. */
#define QOI_SRGB 0
#define QOI_LINEAR 1
typedef struct {
unsigned int width;
unsigned int height;
unsigned char channels;
unsigned char colorspace;
} qoi_desc;
#ifndef QOI_NO_STDIO
/* Encode raw RGB or RGBA pixels into a QOI image and write it to the file
system. The qoi_desc struct must be filled with the image width, height,
number of channels (3 = RGB, 4 = RGBA) and the colorspace.
The function returns 0 on failure (invalid parameters, or fopen or malloc
failed) or the number of bytes written on success. */
int qoi_write(const char *filename, const void *data, const qoi_desc *desc);
/* Read and decode a QOI image from the file system. If channels is 0, the
number of channels from the file header is used. If channels is 3 or 4 the
output format will be forced into this number of channels.
The function either returns NULL on failure (invalid data, or malloc or fopen
failed) or a pointer to the decoded pixels. On success, the qoi_desc struct
will be filled with the description from the file header.
The returned pixel data should be free()d after use. */
void *qoi_read(const char *filename, qoi_desc *desc, int channels);
#endif /* QOI_NO_STDIO */
/* Encode raw RGB or RGBA pixels into a QOI image in memory.
The function either returns NULL on failure (invalid parameters or malloc
failed) or a pointer to the encoded data on success. On success the out_len
is set to the size in bytes of the encoded data.
The returned qoi data should be free()d after use. */
void *qoi_encode(const void *data, const qoi_desc *desc, int *out_len);
/* Decode a QOI image from memory.
The function either returns NULL on failure (invalid parameters or malloc
failed) or a pointer to the decoded pixels. On success, the qoi_desc struct
is filled with the description from the file header.
The returned pixel data should be free()d after use. */
void *qoi_decode(const void *data, int size, qoi_desc *desc, int channels);
#ifdef __cplusplus
}
#endif
#endif /* QOI_H */
/* -----------------------------------------------------------------------------
Implementation */
#ifdef QOI_IMPLEMENTATION
#include <stdlib.h>
#include <string.h>
#ifndef QOI_MALLOC
#define QOI_MALLOC(sz) malloc(sz)
#define QOI_FREE(p) free(p)
#endif
#ifndef QOI_ZEROARR
#define QOI_ZEROARR(a) memset((a),0,sizeof(a))
#endif
#define QOI_OP_INDEX 0x00 /* 00xxxxxx */
#define QOI_OP_DIFF 0x40 /* 01xxxxxx */
#define QOI_OP_LUMA 0x80 /* 10xxxxxx */
#define QOI_OP_RUN 0xc0 /* 11xxxxxx */
#define QOI_OP_RGB 0xfe /* 11111110 */
#define QOI_OP_RGBA 0xff /* 11111111 */
#define QOI_MASK_2 0xc0 /* 11000000 */
#define QOI_COLOR_HASH(C) (C.rgba.r*3 + C.rgba.g*5 + C.rgba.b*7 + C.rgba.a*11)
#define QOI_MAGIC \
(((unsigned int)'q') << 24 | ((unsigned int)'o') << 16 | \
((unsigned int)'i') << 8 | ((unsigned int)'f'))
#define QOI_HEADER_SIZE 14
/* 2GB is the max file size that this implementation can safely handle. We guard
against anything larger than that, assuming the worst case with 5 bytes per
pixel, rounded down to a nice clean value. 400 million pixels ought to be
enough for anybody. */
#define QOI_PIXELS_MAX ((unsigned int)400000000)
typedef union {
struct { unsigned char r, g, b, a; } rgba;
unsigned int v;
} qoi_rgba_t;
static const unsigned char qoi_padding[8] = {0,0,0,0,0,0,0,1};
static void qoi_write_32(unsigned char *bytes, int *p, unsigned int v) {
bytes[(*p)++] = (0xff000000 & v) >> 24;
bytes[(*p)++] = (0x00ff0000 & v) >> 16;
bytes[(*p)++] = (0x0000ff00 & v) >> 8;
bytes[(*p)++] = (0x000000ff & v);
}
static unsigned int qoi_read_32(const unsigned char *bytes, int *p) {
unsigned int a = bytes[(*p)++];
unsigned int b = bytes[(*p)++];
unsigned int c = bytes[(*p)++];
unsigned int d = bytes[(*p)++];
return a << 24 | b << 16 | c << 8 | d;
}
void *qoi_encode(const void *data, const qoi_desc *desc, int *out_len) {
int i, max_size, p, run;
int px_len, px_end, px_pos, channels;
unsigned char *bytes;
const unsigned char *pixels;
qoi_rgba_t index[64];
qoi_rgba_t px, px_prev;
if (
data == NULL || out_len == NULL || desc == NULL ||
desc->width == 0 || desc->height == 0 ||
desc->channels < 3 || desc->channels > 4 ||
desc->colorspace > 1 ||
desc->height >= QOI_PIXELS_MAX / desc->width
) {
return NULL;
}
max_size =
desc->width * desc->height * (desc->channels + 1) +
QOI_HEADER_SIZE + sizeof(qoi_padding);
p = 0;
bytes = (unsigned char *) QOI_MALLOC(max_size);
if (!bytes) {
return NULL;
}
qoi_write_32(bytes, &p, QOI_MAGIC);
qoi_write_32(bytes, &p, desc->width);
qoi_write_32(bytes, &p, desc->height);
bytes[p++] = desc->channels;
bytes[p++] = desc->colorspace;
pixels = (const unsigned char *)data;
QOI_ZEROARR(index);
run = 0;
px_prev.rgba.r = 0;
px_prev.rgba.g = 0;
px_prev.rgba.b = 0;
px_prev.rgba.a = 255;
px = px_prev;
px_len = desc->width * desc->height * desc->channels;
px_end = px_len - desc->channels;
channels = desc->channels;
for (px_pos = 0; px_pos < px_len; px_pos += channels) {
px.rgba.r = pixels[px_pos + 0];
px.rgba.g = pixels[px_pos + 1];
px.rgba.b = pixels[px_pos + 2];
if (channels == 4) {
px.rgba.a = pixels[px_pos + 3];
}
if (px.v == px_prev.v) {
run++;
if (run == 62 || px_pos == px_end) {
bytes[p++] = QOI_OP_RUN | (run - 1);
run = 0;
}
}
else {
int index_pos;
if (run > 0) {
bytes[p++] = QOI_OP_RUN | (run - 1);
run = 0;
}
index_pos = QOI_COLOR_HASH(px) % 64;
if (index[index_pos].v == px.v) {
bytes[p++] = QOI_OP_INDEX | index_pos;
}
else {
index[index_pos] = px;
if (px.rgba.a == px_prev.rgba.a) {
signed char vr = px.rgba.r - px_prev.rgba.r;
signed char vg = px.rgba.g - px_prev.rgba.g;
signed char vb = px.rgba.b - px_prev.rgba.b;
signed char vg_r = vr - vg;
signed char vg_b = vb - vg;
if (
vr > -3 && vr < 2 &&
vg > -3 && vg < 2 &&
vb > -3 && vb < 2
) {
bytes[p++] = QOI_OP_DIFF | (vr + 2) << 4 | (vg + 2) << 2 | (vb + 2);
}
else if (
vg_r > -9 && vg_r < 8 &&
vg > -33 && vg < 32 &&
vg_b > -9 && vg_b < 8
) {
bytes[p++] = QOI_OP_LUMA | (vg + 32);
bytes[p++] = (vg_r + 8) << 4 | (vg_b + 8);
}
else {
bytes[p++] = QOI_OP_RGB;
bytes[p++] = px.rgba.r;
bytes[p++] = px.rgba.g;
bytes[p++] = px.rgba.b;
}
}
else {
bytes[p++] = QOI_OP_RGBA;
bytes[p++] = px.rgba.r;
bytes[p++] = px.rgba.g;
bytes[p++] = px.rgba.b;
bytes[p++] = px.rgba.a;
}
}
}
px_prev = px;
}
for (i = 0; i < (int)sizeof(qoi_padding); i++) {
bytes[p++] = qoi_padding[i];
}
*out_len = p;
return bytes;
}
void *qoi_decode(const void *data, int size, qoi_desc *desc, int channels) {
const unsigned char *bytes;
unsigned int header_magic;
unsigned char *pixels;
qoi_rgba_t index[64];
qoi_rgba_t px;
int px_len, chunks_len, px_pos;
int p = 0, run = 0;
if (
data == NULL || desc == NULL ||
(channels != 0 && channels != 3 && channels != 4) ||
size < QOI_HEADER_SIZE + (int)sizeof(qoi_padding)
) {
return NULL;
}
bytes = (const unsigned char *)data;
header_magic = qoi_read_32(bytes, &p);
desc->width = qoi_read_32(bytes, &p);
desc->height = qoi_read_32(bytes, &p);
desc->channels = bytes[p++];
desc->colorspace = bytes[p++];
if (
desc->width == 0 || desc->height == 0 ||
desc->channels < 3 || desc->channels > 4 ||
desc->colorspace > 1 ||
header_magic != QOI_MAGIC ||
desc->height >= QOI_PIXELS_MAX / desc->width
) {
return NULL;
}
if (channels == 0) {
channels = desc->channels;
}
px_len = desc->width * desc->height * channels;
pixels = (unsigned char *) QOI_MALLOC(px_len);
if (!pixels) {
return NULL;
}
QOI_ZEROARR(index);
px.rgba.r = 0;
px.rgba.g = 0;
px.rgba.b = 0;
px.rgba.a = 255;
chunks_len = size - (int)sizeof(qoi_padding);
for (px_pos = 0; px_pos < px_len; px_pos += channels) {
if (run > 0) {
run--;
}
else if (p < chunks_len) {
int b1 = bytes[p++];
if (b1 == QOI_OP_RGB) {
px.rgba.r = bytes[p++];
px.rgba.g = bytes[p++];
px.rgba.b = bytes[p++];
}
else if (b1 == QOI_OP_RGBA) {
px.rgba.r = bytes[p++];
px.rgba.g = bytes[p++];
px.rgba.b = bytes[p++];
px.rgba.a = bytes[p++];
}
else if ((b1 & QOI_MASK_2) == QOI_OP_INDEX) {
px = index[b1];
}
else if ((b1 & QOI_MASK_2) == QOI_OP_DIFF) {
px.rgba.r += ((b1 >> 4) & 0x03) - 2;
px.rgba.g += ((b1 >> 2) & 0x03) - 2;
px.rgba.b += ( b1 & 0x03) - 2;
}
else if ((b1 & QOI_MASK_2) == QOI_OP_LUMA) {
int b2 = bytes[p++];
int vg = (b1 & 0x3f) - 32;
px.rgba.r += vg - 8 + ((b2 >> 4) & 0x0f);
px.rgba.g += vg;
px.rgba.b += vg - 8 + (b2 & 0x0f);
}
else if ((b1 & QOI_MASK_2) == QOI_OP_RUN) {
run = (b1 & 0x3f);
}
index[QOI_COLOR_HASH(px) % 64] = px;
}
pixels[px_pos + 0] = px.rgba.r;
pixels[px_pos + 1] = px.rgba.g;
pixels[px_pos + 2] = px.rgba.b;
if (channels == 4) {
pixels[px_pos + 3] = px.rgba.a;
}
}
return pixels;
}
#ifndef QOI_NO_STDIO
#include <stdio.h>
int qoi_write(const char *filename, const void *data, const qoi_desc *desc) {
FILE *f = fopen(filename, "wb");
int size, err;
void *encoded;
if (!f) {
return 0;
}
encoded = qoi_encode(data, desc, &size);
if (!encoded) {
fclose(f);
return 0;
}
fwrite(encoded, 1, size, f);
fflush(f);
err = ferror(f);
fclose(f);
QOI_FREE(encoded);
return err ? 0 : size;
}
void *qoi_read(const char *filename, qoi_desc *desc, int channels) {
FILE *f = fopen(filename, "rb");
int size, bytes_read;
void *pixels, *data;
if (!f) {
return NULL;
}
fseek(f, 0, SEEK_END);
size = ftell(f);
if (size <= 0 || fseek(f, 0, SEEK_SET) != 0) {
fclose(f);
return NULL;
}
data = QOI_MALLOC(size);
if (!data) {
fclose(f);
return NULL;
}
bytes_read = fread(data, 1, size, f);
fclose(f);
pixels = (bytes_read != size) ? NULL : qoi_decode(data, bytes_read, desc, channels);
QOI_FREE(data);
return pixels;
}
#endif /* QOI_NO_STDIO */
#endif /* QOI_IMPLEMENTATION */

View file

@ -260,6 +260,22 @@ void ST7789_Fill_Line(uint16_t y, const uint8_t *data, const uint8_t *palette)
ost_display_ss_high();
}
void ST7789_Fill_LineRgb888(uint16_t y, const color_t *data)
{
ST7789_SetAddressWindow(0, y, ST7789_WIDTH - 1, ST7789_HEIGHT - 1);
ost_display_dc_high();
ost_display_ss_low();
color_t color;
for (uint16_t i = 0; i < ST7789_WIDTH; i++)
{
uint16_t pixel = color565(data[i].r, data[i].g, data[i].b);
ost_display_transfer_byte(pixel >> 8);
ost_display_transfer_byte(pixel & 0xFF);
}
}
/**
* @brief Fill the DisplayWindow with single color
* @param color -> color to Fill with

View file

@ -3,9 +3,11 @@
#include <stdint.h>
#include "ost_hal.h"
/* If u need Backlight control, uncomment below */
//#define BLK_PORT
//#define BLK_PIN
// #define BLK_PORT
// #define BLK_PIN
/*
* Comment one to use another.
@ -15,121 +17,120 @@
*/
/* Choose a type you are using */
//#define USING_135X240
// #define USING_240X240
//#define USING_170X320
// #define USING_135X240
// #define USING_240X240
// #define USING_170X320
#define USING_240X320 1
/* Choose a display rotation you want to use: (0-3) */
// #define ST7789_ROTATION 0
#define ST7789_ROTATION 1
// #define ST7789_ROTATION 2 // use Normally on 240x240
//#define ST7789_ROTATION 3
// #define ST7789_ROTATION 3
#ifdef USING_135X240
#if ST7789_ROTATION == 0
#define ST7789_WIDTH 135
#define ST7789_HEIGHT 240
#define X_SHIFT 53
#define Y_SHIFT 40
#endif
#if ST7789_ROTATION == 0
#define ST7789_WIDTH 135
#define ST7789_HEIGHT 240
#define X_SHIFT 53
#define Y_SHIFT 40
#endif
#if ST7789_ROTATION == 1
#define ST7789_WIDTH 240
#define ST7789_HEIGHT 135
#define X_SHIFT 40
#define Y_SHIFT 52
#endif
#if ST7789_ROTATION == 1
#define ST7789_WIDTH 240
#define ST7789_HEIGHT 135
#define X_SHIFT 40
#define Y_SHIFT 52
#endif
#if ST7789_ROTATION == 2
#define ST7789_WIDTH 135
#define ST7789_HEIGHT 240
#define X_SHIFT 52
#define Y_SHIFT 40
#endif
#if ST7789_ROTATION == 2
#define ST7789_WIDTH 135
#define ST7789_HEIGHT 240
#define X_SHIFT 52
#define Y_SHIFT 40
#endif
#if ST7789_ROTATION == 3
#define ST7789_WIDTH 240
#define ST7789_HEIGHT 135
#define X_SHIFT 40
#define Y_SHIFT 53
#endif
#if ST7789_ROTATION == 3
#define ST7789_WIDTH 240
#define ST7789_HEIGHT 135
#define X_SHIFT 40
#define Y_SHIFT 53
#endif
#endif
#ifdef USING_240X320
#define ST7789_WIDTH 320
#define ST7789_HEIGHT 240
#define ST7789_WIDTH 320
#define ST7789_HEIGHT 240
#if ST7789_ROTATION == 0
#define X_SHIFT 0
#define Y_SHIFT 0
#elif ST7789_ROTATION == 1
#define X_SHIFT 0
#define Y_SHIFT 0
#elif ST7789_ROTATION == 2
#define X_SHIFT 0
#define Y_SHIFT 0
#elif ST7789_ROTATION == 3
#define X_SHIFT 0
#define Y_SHIFT 0
#endif
#if ST7789_ROTATION == 0
#define X_SHIFT 0
#define Y_SHIFT 0
#elif ST7789_ROTATION == 1
#define X_SHIFT 0
#define Y_SHIFT 0
#elif ST7789_ROTATION == 2
#define X_SHIFT 0
#define Y_SHIFT 0
#elif ST7789_ROTATION == 3
#define X_SHIFT 0
#define Y_SHIFT 0
#endif
#endif
#ifdef USING_240X240
#define ST7789_WIDTH 240
#define ST7789_HEIGHT 240
#define ST7789_WIDTH 240
#define ST7789_HEIGHT 240
#if ST7789_ROTATION == 0
#define X_SHIFT 0
#define Y_SHIFT 80
#elif ST7789_ROTATION == 1
#define X_SHIFT 80
#define Y_SHIFT 0
#elif ST7789_ROTATION == 2
#define X_SHIFT 0
#define Y_SHIFT 0
#elif ST7789_ROTATION == 3
#define X_SHIFT 0
#define Y_SHIFT 0
#endif
#if ST7789_ROTATION == 0
#define X_SHIFT 0
#define Y_SHIFT 80
#elif ST7789_ROTATION == 1
#define X_SHIFT 80
#define Y_SHIFT 0
#elif ST7789_ROTATION == 2
#define X_SHIFT 0
#define Y_SHIFT 0
#elif ST7789_ROTATION == 3
#define X_SHIFT 0
#define Y_SHIFT 0
#endif
#endif
#ifdef USING_170X320
#if ST7789_ROTATION == 0
#define ST7789_WIDTH 170
#define ST7789_HEIGHT 320
#define X_SHIFT 35
#define Y_SHIFT 0
#endif
#if ST7789_ROTATION == 0
#define ST7789_WIDTH 170
#define ST7789_HEIGHT 320
#define X_SHIFT 35
#define Y_SHIFT 0
#endif
#if ST7789_ROTATION == 1
#define ST7789_WIDTH 320
#define ST7789_HEIGHT 170
#define X_SHIFT 0
#define Y_SHIFT 35
#endif
#if ST7789_ROTATION == 1
#define ST7789_WIDTH 320
#define ST7789_HEIGHT 170
#define X_SHIFT 0
#define Y_SHIFT 35
#endif
#if ST7789_ROTATION == 2
#define ST7789_WIDTH 170
#define ST7789_HEIGHT 320
#define X_SHIFT 35
#define Y_SHIFT 0
#endif
#if ST7789_ROTATION == 2
#define ST7789_WIDTH 170
#define ST7789_HEIGHT 320
#define X_SHIFT 35
#define Y_SHIFT 0
#endif
#if ST7789_ROTATION == 3
#define ST7789_WIDTH 320
#define ST7789_HEIGHT 170
#define X_SHIFT 0
#define Y_SHIFT 35
#endif
#if ST7789_ROTATION == 3
#define ST7789_WIDTH 320
#define ST7789_HEIGHT 170
#define X_SHIFT 0
#define Y_SHIFT 35
#endif
#endif
@ -138,52 +139,52 @@
*If you want to use another color, you can choose one in RGB565 format.
*/
#define WHITE 0xFFFF
#define BLACK 0x0000
#define BLUE 0x001F
#define RED 0xF800
#define MAGENTA 0xF81F
#define GREEN 0x07E0
#define CYAN 0x7FFF
#define YELLOW 0xFFE0
#define GRAY 0X8430
#define BRED 0XF81F
#define GRED 0XFFE0
#define GBLUE 0X07FF
#define BROWN 0XBC40
#define BRRED 0XFC07
#define DARKBLUE 0X01CF
#define LIGHTBLUE 0X7D7C
#define GRAYBLUE 0X5458
#define WHITE 0xFFFF
#define BLACK 0x0000
#define BLUE 0x001F
#define RED 0xF800
#define MAGENTA 0xF81F
#define GREEN 0x07E0
#define CYAN 0x7FFF
#define YELLOW 0xFFE0
#define GRAY 0X8430
#define BRED 0XF81F
#define GRED 0XFFE0
#define GBLUE 0X07FF
#define BROWN 0XBC40
#define BRRED 0XFC07
#define DARKBLUE 0X01CF
#define LIGHTBLUE 0X7D7C
#define GRAYBLUE 0X5458
#define LIGHTGREEN 0X841F
#define LGRAY 0XC618
#define LGRAYBLUE 0XA651
#define LBBLUE 0X2B12
#define LIGHTGREEN 0X841F
#define LGRAY 0XC618
#define LGRAYBLUE 0XA651
#define LBBLUE 0X2B12
/* Control Registers and constant codes */
#define ST7789_NOP 0x00
#define ST7789_NOP 0x00
#define ST7789_SWRESET 0x01
#define ST7789_RDDID 0x04
#define ST7789_RDDST 0x09
#define ST7789_RDDID 0x04
#define ST7789_RDDST 0x09
#define ST7789_SLPIN 0x10
#define ST7789_SLPOUT 0x11
#define ST7789_PTLON 0x12
#define ST7789_NORON 0x13
#define ST7789_SLPIN 0x10
#define ST7789_SLPOUT 0x11
#define ST7789_PTLON 0x12
#define ST7789_NORON 0x13
#define ST7789_INVOFF 0x20
#define ST7789_INVON 0x21
#define ST7789_INVOFF 0x20
#define ST7789_INVON 0x21
#define ST7789_DISPOFF 0x28
#define ST7789_DISPON 0x29
#define ST7789_CASET 0x2A
#define ST7789_RASET 0x2B
#define ST7789_RAMWR 0x2C
#define ST7789_RAMRD 0x2E
#define ST7789_DISPON 0x29
#define ST7789_CASET 0x2A
#define ST7789_RASET 0x2B
#define ST7789_RAMWR 0x2C
#define ST7789_RAMRD 0x2E
#define ST7789_PTLAR 0x30
#define ST7789_COLMOD 0x3A
#define ST7789_MADCTL 0x36
#define ST7789_PTLAR 0x30
#define ST7789_COLMOD 0x3A
#define ST7789_MADCTL 0x36
/**
* Memory Data Access Control Register (0x36H)
@ -193,24 +194,24 @@
*/
/* Page Address Order ('0': Top to Bottom, '1': the opposite) */
#define ST7789_MADCTL_MY 0x80
#define ST7789_MADCTL_MY 0x80
/* Column Address Order ('0': Left to Right, '1': the opposite) */
#define ST7789_MADCTL_MX 0x40
#define ST7789_MADCTL_MX 0x40
/* Page/Column Order ('0' = Normal Mode, '1' = Reverse Mode) */
#define ST7789_MADCTL_MV 0x20
#define ST7789_MADCTL_MV 0x20
/* Line Address Order ('0' = LCD Refresh Top to Bottom, '1' = the opposite) */
#define ST7789_MADCTL_ML 0x10
#define ST7789_MADCTL_ML 0x10
/* RGB/BGR Order ('0' = RGB, '1' = BGR) */
#define ST7789_MADCTL_RGB 0x08
#define ST7789_RDID1 0xDA
#define ST7789_RDID2 0xDB
#define ST7789_RDID3 0xDC
#define ST7789_RDID4 0xDD
#define ST7789_RDID1 0xDA
#define ST7789_RDID2 0xDB
#define ST7789_RDID3 0xDC
#define ST7789_RDID4 0xDD
/* Advanced options */
#define ST7789_COLOR_MODE_16bit 0x55 // RGB565 (16bit)
#define ST7789_COLOR_MODE_18bit 0x66 // RGB666 (18bit)
#define ST7789_COLOR_MODE_16bit 0x55 // RGB565 (16bit)
#define ST7789_COLOR_MODE_18bit 0x66 // RGB666 (18bit)
#define ABS(x) ((x) > 0 ? (x) : -(x))
@ -222,6 +223,7 @@ void ST7789_DrawPixel(uint16_t x, uint16_t y, uint16_t color);
void ST7789_Fill(uint16_t xSta, uint16_t ySta, uint16_t xEnd, uint16_t yEnd, uint16_t color);
void ST7789_DrawPixel_4px(uint16_t x, uint16_t y, uint16_t color);
void ST7789_Fill_Line(uint16_t y, const uint8_t *data, const uint8_t *palette);
void ST7789_Fill_LineRgb888(uint16_t y, const color_t *data);
/* Graphical functions. */
void ST7789_DrawLine(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2, uint16_t color);
@ -247,7 +249,7 @@ void ST7789_TearEffect(uint8_t tear);
void ST7789_Test(void);
#ifndef ST7789_ROTATION
#error You should at least choose a display rotation!
#error You should at least choose a display rotation!
#endif
#endif

View file

@ -311,6 +311,11 @@ void ost_display_draw_h_line(uint16_t y, uint8_t *pixels, uint8_t *palette)
ST7789_Fill_Line(y, pixels, palette);
}
void ost_display_draw_h_line_rgb888(uint16_t y, const color_t *data)
{
ST7789_Fill_LineRgb888(y, data);
}
uint8_t ost_display_transfer_byte(uint8_t dat)
{
pico_lcd_spi_transfer(&dat, 1);

View file

@ -14,6 +14,34 @@
#include "audio_player.h"
#include "chip32_vm.h"
#include "mini_qoi.h"
#ifdef OST_USE_FF_LIBRARY
#include "ff.h"
#include "diskio.h"
typedef FIL file_t;
#else
// Use standard library
typedef FILE *file_t;
typedef int FRESULT;
#define F_OK
#endif
file_t file_open(const char *filename)
{
#ifdef OST_USE_FF_LIBRARY
file_t fil;
FRESULT fr = f_open(&fil, filename, FA_READ);
if (fr != FR_OK)
{
debug_printf("ERROR: f_open %d\n\r", (int)fr);
}
return fil;
#else
return fopen(filename, "r");
#endif
}
void ost_hal_panic()
{
@ -24,6 +52,94 @@ void ost_hal_panic()
// ===========================================================================================================
static ost_context_t OstContext;
static mqoi_dec_t dec;
static mqoi_desc_t desc;
/*
if (pixel == info_header.width)
{
// enough pixels to write a line to the screen
ost_display_draw_h_line(pos.y, decompressed, palette);
// debug_printf("POS Y: %d", pos.y);
memset(decompressed, 0, sizeof(decompressed));
// ili9341_write(&pos, decompressed);
// next line...
pos.y++;
totalPixels += info_header.width;
pixel = 0;
nblines++;
}
*/
static uint8_t bmpImage[256];
static color_t line[320];
void display_image(const char *filename)
{
file_t fil;
unsigned int br;
fil = file_open(filename);
uint32_t imgW, imgH;
mqoi_desc_init(&desc);
// 1. Read header
f_read(&fil, &desc.magic[0], sizeof(mqoi_desc_t) - 1, &br);
uint8_t errn = mqoi_desc_verify(&desc, &imgW, &imgH);
if (errn)
{
debug_printf("Invalid image, code %d\n", errn);
return;
}
debug_printf("Image dimensions: %d, %d\n", imgW, imgH);
uint32_t start, end, pxCount = 0;
volatile mqoi_rgba_t *px;
mqoi_dec_init(&dec, imgW * imgH);
// Serial.println("starting decode...");
int index = 256; // force refill first time
int x = 0;
int y = 0;
while (!mqoi_dec_done(&dec))
{
if (index >= sizeof(bmpImage))
{
// refill buffer
f_read(&fil, bmpImage, sizeof(bmpImage), &br);
index = 0;
}
mqoi_dec_push(&dec, bmpImage[index++]);
while ((px = mqoi_dec_pop(&dec)) != NULL)
{
pxCount++;
line[x].r = px->r;
line[x].g = px->g;
line[x].b = px->b;
x++;
if (x >= 320)
{
ost_display_draw_h_line_rgb888(y, line);
x = 0;
y++;
}
}
}
f_close(&fil);
}
// ===========================================================================================================
// HMI TASK (user interface, buttons manager, LCD)
// ===========================================================================================================
@ -49,6 +165,8 @@ void HmiTask(void *args)
filesystem_read_index_file(&OstContext);
display_image("/ba869e4b-03d6-4249-9202-85b4cec767a7/images/bird.qoi");
while (1)
{
uint32_t res = qor_mbox_wait(&HmiMailBox, (void **)&e, 1000);

View file

@ -108,6 +108,7 @@ extern "C"
void ost_display_ss_low();
uint8_t ost_display_transfer_byte(uint8_t dat);
void ost_display_transfer_multi(uint8_t *buff, uint32_t btr);
void ost_display_draw_h_line_rgb888(uint16_t y, const color_t *data);
// ----------------------------------------------------------------------------
// AUDIO HAL

View file

@ -38,6 +38,16 @@
#include "main_window.h"
#include "media_node_model.h"
#define STB_IMAGE_IMPLEMENTATION
#define STBI_ONLY_PNG
#define STBI_NO_LINEAR
#include "stb_image.h"
#define QOI_IMPLEMENTATION
#undef QOI_NO_STDIO
#include "qoi.h"
using QtNodes::CreateCommand;
using QtNodes::BasicGraphicsScene;
using QtNodes::ConnectionStyle;
@ -129,8 +139,8 @@ MainWindow::MainWindow()
m_chip32_ctx.ram.addr = sizeof(m_rom_data);
m_chip32_ctx.ram.size = sizeof(m_ram_data);
Callback<uint8_t(uint8_t)>::func = std::bind(&MainWindow::Syscall, this, std::placeholders::_1);
m_chip32_ctx.syscall = static_cast<syscall_t>(Callback<uint8_t(uint8_t)>::callback);
Callback<uint8_t(chip32_ctx_t *, uint8_t)>::func = std::bind(&MainWindow::Syscall, this, std::placeholders::_1, std::placeholders::_2);
m_chip32_ctx.syscall = static_cast<syscall_t>(Callback<uint8_t(chip32_ctx_t *, uint8_t)>::callback);
// Install event handler now that everything is initialized
Callback<void(QtMsgType , const QMessageLogContext &, const QString &)>::func = std::bind(&MainWindow::MessageOutput, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3);
@ -161,7 +171,7 @@ MainWindow::MainWindow()
});
connect(m_vmDock, &VmDock::sigCompile, [&]() {
// m_scriptEditorDock->setScript(m_project.BuildResources());
CompileToAssembler();
});
connect(m_vmDock, &VmDock::sigStepInstruction, [&]() {
@ -169,7 +179,7 @@ MainWindow::MainWindow()
});
connect(m_vmDock, &VmDock::sigBuild, [&]() {
BuildScript();
BuildAll();
});
connect(&m_model, &StoryGraphModel::sigChooseFile, [&](NodeId id, const QString &type) {
@ -278,7 +288,7 @@ MainWindow::MainWindow()
// QMetaObject::invokeMethod(this, "slotWelcome", Qt::QueuedConnection);
}
void MainWindow::BuildAndRun()
bool MainWindow::CompileToAssembler()
{
// 1. Check if the model can be compiled, check for errors and report
// FIXME
@ -289,14 +299,82 @@ void MainWindow::BuildAndRun()
// Add global functions
code += ReadResourceFile(":/scripts/media.asm").toStdString();
// code += "\thalt\r\n";
m_scriptEditorDock->setScript(code.c_str());
// 3. Compile the assembly to machine binary
BuildScript();
return true;
}
// 4. Run the VM code!
// Convert media into desired output format
void MainWindow::ConvertResources()
{
std::vector<Resource>::const_iterator ptr = m_project.Begin();
for (; ptr != m_project.End(); ++ptr)
{
if (ptr->format == "PNG")
{
QString inputfile = m_model.BuildFullImagePath(ptr->file.c_str());
void *pixels = NULL;
int w = 0;
int h = 0;
int channels = 0;
if(!stbi_info(inputfile.toStdString().c_str(), &w, &h, &channels))
{
qCritical() << "Couldn't read header " << inputfile;
}
// Force all odd encodings to be RGBA
if(channels != 3) {
channels = 4;
}
pixels = (void *)stbi_load(inputfile.toStdString().c_str(), &w, &h, NULL, channels);
if (pixels != NULL)
{
qoi_desc desc;
desc.channels = channels;
desc.colorspace = QOI_SRGB;
desc.width = w;
desc.height = h;
std::string outputfile = m_project.ImagesPath() / StoryProject::RemoveFileExtension(ptr->file);
outputfile += ".qoi";
int encoded = qoi_write(outputfile.c_str(), pixels, &desc);
if (!encoded)
{
qCritical() << "Couldn't write/encode " << outputfile;
}
free(pixels);
}
else
{
qCritical() << "Couldn't load/decode" << inputfile;
}
}
}
}
void MainWindow::BuildAll()
{
// 1. First compile nodes to assembly
CompileToAssembler();
// 2. Compile the assembly to machine binary
GenerateBinary();
// 3. Conert all media
ConvertResources();
}
void MainWindow::BuildAndRun()
{
BuildAll();
// Then run the VM code!
if (m_dbg.run_result == VM_OK)
{
m_dbg.free_run = true;
@ -310,11 +388,11 @@ QString MainWindow::ReadResourceFile(const QString &fileName)
QString data;
QFile file(fileName);
if(!file.open(QIODevice::ReadOnly)) {
qDebug() << "filenot opened";
qDebug() << "ReadResourceFile(): file not opened";
}
else
{
qDebug() << "file opened";
// qDebug() << "file opened";
data = file.readAll();
}
@ -538,7 +616,7 @@ bool MainWindow::event(QEvent *event)
return QMainWindow::event(event);
}
uint8_t MainWindow::Syscall(uint8_t code)
uint8_t MainWindow::Syscall(chip32_ctx_t *ctx, uint8_t code)
{
uint8_t retCode = SYSCALL_RET_OK;
qDebug() << "SYSCALL: " << (int)code;
@ -592,7 +670,7 @@ void MainWindow::stepInstruction()
updateAll();
}
void MainWindow::BuildScript()
void MainWindow::GenerateBinary()
{
m_dbg.run_result = VM_FINISHED;
m_dbg.free_run = false;
@ -603,6 +681,8 @@ void MainWindow::BuildScript()
{
m_result.Print();
qDebug() << "Binary successfully generated.";
// Update ROM memory
std::copy(m_program.begin(), m_program.end(), m_rom_data);
m_ramView->SetMemory(m_ram_data, sizeof(m_ram_data));

View file

@ -173,11 +173,11 @@ private:
void createStatusBar();
void SaveProject();
void DisplayNode(StoryNode *m_tree, QtNodes::NodeId parentId);
void BuildScript();
void GenerateBinary();
void highlightNextLine();
void readSettings();
void updateAll();
uint8_t Syscall(uint8_t code);
uint8_t Syscall(chip32_ctx_t *ctx, uint8_t code);
QString GetFileNameFromMemory(uint32_t addr);
bool event(QEvent *event);
@ -192,6 +192,9 @@ private:
void OpenProject(const QString &filePath);
QString ReadResourceFile(const QString &fileName);
void EventFinished(uint32_t replyEvent);
bool CompileToAssembler();
void ConvertResources();
void BuildAll();
};
#endif // MAIN_WINDOW_H

View file

@ -146,11 +146,11 @@ std::string MediaNodeModel::GenerateConstants()
std::string sound = m_mediaData["sound"].get<std::string>();
if (image.size() > 0)
{
s = StoryProject::FileToConstant(image);
s = StoryProject::FileToConstant(image, ".qoi"); // FIXME: Generate the extension setup in user option of output format
}
if (sound.size() > 0)
{
s += StoryProject::FileToConstant(sound);
s += StoryProject::FileToConstant(sound, ".wav"); // FIXME: Generate the extension setup in user option of output format
}
int nb_out_conns = ComputeOutputConnections();

7987
story-editor/src/stb_image.h Normal file

File diff suppressed because it is too large Load diff

View file

@ -261,10 +261,10 @@ std::string StoryProject::RemoveFileExtension(const std::string &FileName)
return f;
}
std::string StoryProject::FileToConstant(const std::string &FileName)
std::string StoryProject::FileToConstant(const std::string &FileName, const std::string &extension)
{
std::string f = RemoveFileExtension(FileName);
return "$" + f + " DC8 \"" + FileName + "\", 8\r\n";
return "$" + f + " DC8 \"" + f + extension + "\", 8\r\n";
}
void StoryProject::AppendResource(const Resource &res)

View file

@ -86,7 +86,7 @@ struct StoryProject
static std::string GetFileName(const std::string &path);
static std::string RemoveFileExtension(const std::string &FileName);
static void ReplaceCharacter(std::string &theString, const std::string &toFind, const std::string &toReplace);
static std::string FileToConstant(const std::string &FileName);
static std::string FileToConstant(const std::string &FileName, const std::string &extension);
// ------------- Resources Management
void AppendResource(const Resource &res);

View file

@ -804,6 +804,21 @@ void AudioFile<T>::clearAudioBuffer()
samples.clear();
}
//=============================================================
template<class T>
void AudioFile<T>::initializeAudioBuffer(int16_t *buffer, int size, int channels)
{
clearAudioBuffer();
samples.resize (channels);
for (int i = 0; i < size; i++)
{
for (int channel = 0; channel < getNumChannels(); channel++)
{
samples[channel].push_back(buffer[i]);
}
}
}
//=============================================================
template <class T>
@ -878,4 +893,4 @@ T AudioFile<T>::sixteenBitIntToSample (int16_t sample)
//===========================================================
template class AudioFile<float>;
template class AudioFile<double>;
template class AudioFile<int16_t>;
template class AudioFile<int16_t>;

View file

@ -116,6 +116,8 @@ public:
/** Sets the sample rate for the audio file. If you use the save() function, this sample rate will be used */
void setSampleRate (uint32_t newSampleRate);
void initializeAudioBuffer(int16_t *buffer, int size, int channels);
//=============================================================
/** A vector of vectors holding the audio samples for the AudioFile. You can