Quantum Painter (#10174)
* Install dependencies before executing unit tests. * Split out UTF-8 decoder. * Fixup python formatting rules. * Add documentation for QGF/QFF and the RLE format used. * Add CLI commands for converting images and fonts. * Add stub rules.mk for QP. * Add stream type. * Add base driver and comms interfaces. * Add support for SPI, SPI+D/C comms drivers. * Include <qp.h> when enabled. * Add base support for SPI+D/C+RST panels, as well as concrete implementation of ST7789. * Add support for GC9A01. * Add support for ILI9341. * Add support for ILI9163. * Add support for SSD1351. * Implement qp_setpixel, including pixdata buffer management. * Implement qp_line. * Implement qp_rect. * Implement qp_circle. * Implement qp_ellipse. * Implement palette interpolation. * Allow for streams to work with either flash or RAM. * Image loading. * Font loading. * QGF palette loading. * Progressive decoder of pixel data supporting Raw+RLE, 1-,2-,4-,8-bpp monochrome and palette-based images. * Image drawing. * Animations. * Font rendering. * Check against 256 colours, dump out the loaded palette if debugging enabled. * Fix build. * AVR is not the intended audience. * `qmk format-c` * Generation fix. * First batch of docs. * More docs and examples. * Review comments. * Public API documentation.
This commit is contained in:
parent
1dbbd2b6b0
commit
1f2b1dedcc
62 changed files with 7561 additions and 35 deletions
137
quantum/painter/qff.c
Normal file
137
quantum/painter/qff.c
Normal file
|
@ -0,0 +1,137 @@
|
|||
// Copyright 2021 Nick Brassel (@tzarc)
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
// Quantum Font File "QFF" File Format.
|
||||
// See https://docs.qmk.fm/#/quantum_painter_qff for more information.
|
||||
|
||||
#include "qff.h"
|
||||
#include "qp_draw.h"
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// QFF API
|
||||
|
||||
bool qff_read_font_descriptor(qp_stream_t *stream, uint8_t *line_height, bool *has_ascii_table, uint16_t *num_unicode_glyphs, uint8_t *bpp, bool *has_palette, painter_compression_t *compression_scheme, uint32_t *total_bytes) {
|
||||
// Seek to the start
|
||||
qp_stream_setpos(stream, 0);
|
||||
|
||||
// Read and validate the font descriptor
|
||||
qff_font_descriptor_v1_t font_descriptor;
|
||||
if (qp_stream_read(&font_descriptor, sizeof(qff_font_descriptor_v1_t), 1, stream) != 1) {
|
||||
qp_dprintf("Failed to read font_descriptor, expected length was not %d\n", (int)sizeof(qff_font_descriptor_v1_t));
|
||||
return false;
|
||||
}
|
||||
|
||||
// Make sure this block is valid
|
||||
if (!qgf_validate_block_header(&font_descriptor.header, QFF_FONT_DESCRIPTOR_TYPEID, (sizeof(qff_font_descriptor_v1_t) - sizeof(qgf_block_header_v1_t)))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Make sure the magic and version are correct
|
||||
if (font_descriptor.magic != QFF_MAGIC || font_descriptor.qff_version != 0x01) {
|
||||
qp_dprintf("Failed to validate font_descriptor, expected magic 0x%06X was 0x%06X, expected version = 0x%02X was 0x%02X\n", (int)QFF_MAGIC, (int)font_descriptor.magic, (int)0x01, (int)font_descriptor.qff_version);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Make sure the file length is valid
|
||||
if (font_descriptor.neg_total_file_size != ~font_descriptor.total_file_size) {
|
||||
qp_dprintf("Failed to validate font_descriptor, expected negated length 0x%08X was 0x%08X\n", (int)(~font_descriptor.total_file_size), (int)font_descriptor.neg_total_file_size);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Copy out the required info
|
||||
if (line_height) {
|
||||
*line_height = font_descriptor.line_height;
|
||||
}
|
||||
if (has_ascii_table) {
|
||||
*has_ascii_table = font_descriptor.has_ascii_table;
|
||||
}
|
||||
if (num_unicode_glyphs) {
|
||||
*num_unicode_glyphs = font_descriptor.num_unicode_glyphs;
|
||||
}
|
||||
if (bpp || has_palette) {
|
||||
if (!qgf_parse_format(font_descriptor.format, bpp, has_palette)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (compression_scheme) {
|
||||
*compression_scheme = font_descriptor.compression_scheme;
|
||||
}
|
||||
if (total_bytes) {
|
||||
*total_bytes = font_descriptor.total_file_size;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool qff_validate_ascii_descriptor(qp_stream_t *stream) {
|
||||
// Read the raw descriptor
|
||||
qff_ascii_glyph_table_v1_t ascii_descriptor;
|
||||
if (qp_stream_read(&ascii_descriptor, sizeof(qff_ascii_glyph_table_v1_t), 1, stream) != 1) {
|
||||
qp_dprintf("Failed to read ascii_descriptor, expected length was not %d\n", (int)sizeof(qff_ascii_glyph_table_v1_t));
|
||||
return false;
|
||||
}
|
||||
|
||||
// Make sure this block is valid
|
||||
if (!qgf_validate_block_header(&ascii_descriptor.header, QFF_ASCII_GLYPH_DESCRIPTOR_TYPEID, (sizeof(qff_ascii_glyph_table_v1_t) - sizeof(qgf_block_header_v1_t)))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool qff_validate_unicode_descriptor(qp_stream_t *stream, uint16_t num_unicode_glyphs) {
|
||||
// Read the raw descriptor
|
||||
qff_unicode_glyph_table_v1_t unicode_descriptor;
|
||||
if (qp_stream_read(&unicode_descriptor, sizeof(qff_unicode_glyph_table_v1_t), 1, stream) != 1) {
|
||||
qp_dprintf("Failed to read unicode_descriptor, expected length was not %d\n", (int)sizeof(qff_unicode_glyph_table_v1_t));
|
||||
return false;
|
||||
}
|
||||
|
||||
// Make sure this block is valid
|
||||
if (!qgf_validate_block_header(&unicode_descriptor.header, QFF_UNICODE_GLYPH_DESCRIPTOR_TYPEID, num_unicode_glyphs * 6)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Skip the necessary amount of data to get to the next block
|
||||
qp_stream_seek(stream, num_unicode_glyphs * sizeof(qff_unicode_glyph_v1_t), SEEK_CUR);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool qff_validate_stream(qp_stream_t *stream) {
|
||||
bool has_ascii_table;
|
||||
uint16_t num_unicode_glyphs;
|
||||
|
||||
if (!qff_read_font_descriptor(stream, NULL, &has_ascii_table, &num_unicode_glyphs, NULL, NULL, NULL, NULL)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (has_ascii_table) {
|
||||
if (!qff_validate_ascii_descriptor(stream)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (num_unicode_glyphs > 0) {
|
||||
if (!qff_validate_unicode_descriptor(stream, num_unicode_glyphs)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
uint32_t qff_get_total_size(qp_stream_t *stream) {
|
||||
// Get the original location
|
||||
uint32_t oldpos = qp_stream_tell(stream);
|
||||
|
||||
// Read the font descriptor, grabbing the size
|
||||
uint32_t total_size;
|
||||
if (!qff_read_font_descriptor(stream, NULL, NULL, NULL, NULL, NULL, NULL, &total_size)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Restore the original location
|
||||
qp_stream_setpos(stream, oldpos);
|
||||
return total_size;
|
||||
}
|
88
quantum/painter/qff.h
Normal file
88
quantum/painter/qff.h
Normal file
|
@ -0,0 +1,88 @@
|
|||
// Copyright 2021 Nick Brassel (@tzarc)
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
// Quantum Font File "QFF" File Format.
|
||||
// See https://docs.qmk.fm/#/quantum_painter_qff for more information.
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#include "qp_stream.h"
|
||||
#include "qp_internal.h"
|
||||
#include "qgf.h"
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// QFF structures
|
||||
|
||||
/////////////////////////////////////////
|
||||
// Font descriptor
|
||||
|
||||
#define QFF_FONT_DESCRIPTOR_TYPEID 0x00
|
||||
|
||||
typedef struct __attribute__((packed)) qff_font_descriptor_v1_t {
|
||||
qgf_block_header_v1_t header; // = { .type_id = 0x00, .neg_type_id = (~0x00), .length = 20 }
|
||||
uint32_t magic : 24; // constant, equal to 0x464651 ("QFF")
|
||||
uint8_t qff_version; // constant, equal to 0x01
|
||||
uint32_t total_file_size; // total size of the entire file, starting at offset zero
|
||||
uint32_t neg_total_file_size; // negated value of total_file_size, used for detecting parsing errors
|
||||
uint8_t line_height; // glyph height in pixels
|
||||
bool has_ascii_table; // whether the font has an ascii table of glyphs (0x20...0x7E)
|
||||
uint16_t num_unicode_glyphs; // the number of glyphs in the unicode table -- no table specified if zero
|
||||
qp_image_format_t format : 8; // Frame format, see qp.h.
|
||||
uint8_t flags; // frame flags, see below.
|
||||
uint8_t compression_scheme; // compression scheme, see below.
|
||||
uint8_t transparency_index; // palette index used for transparent pixels (not yet implemented)
|
||||
} qff_font_descriptor_v1_t;
|
||||
|
||||
_Static_assert(sizeof(qff_font_descriptor_v1_t) == (sizeof(qgf_block_header_v1_t) + 20), "qff_font_descriptor_v1_t must be 25 bytes in v1 of QFF");
|
||||
|
||||
#define QFF_MAGIC 0x464651
|
||||
|
||||
/////////////////////////////////////////
|
||||
// ASCII glyph table descriptor
|
||||
|
||||
#define QFF_ASCII_GLYPH_DESCRIPTOR_TYPEID 0x01
|
||||
|
||||
#define QFF_GLYPH_WIDTH_BITS 6
|
||||
#define QFF_GLYPH_WIDTH_MASK ((1 << QFF_GLYPH_WIDTH_BITS) - 1)
|
||||
#define QFF_GLYPH_OFFSET_BITS 18
|
||||
#define QFF_GLYPH_OFFSET_MASK (((1 << QFF_GLYPH_OFFSET_BITS) - 1) << QFF_GLYPH_WIDTH_BITS)
|
||||
|
||||
typedef struct __attribute__((packed)) qff_ascii_glyph_v1_t {
|
||||
uint32_t value : 24; // Uses QFF_GLYPH_*_(BITS|MASK) as bitfield ordering is compiler-defined
|
||||
} qff_ascii_glyph_v1_t;
|
||||
|
||||
_Static_assert(sizeof(qff_ascii_glyph_v1_t) == 3, "qff_ascii_glyph_v1_t must be 3 bytes in v1 of QFF");
|
||||
|
||||
typedef struct __attribute__((packed)) qff_ascii_glyph_table_v1_t {
|
||||
qgf_block_header_v1_t header; // = { .type_id = 0x01, .neg_type_id = (~0x01), .length = 285 }
|
||||
qff_ascii_glyph_v1_t glyph[95]; // 95 glyphs, 0x20..0x7E
|
||||
} qff_ascii_glyph_table_v1_t;
|
||||
|
||||
_Static_assert(sizeof(qff_ascii_glyph_table_v1_t) == (sizeof(qgf_block_header_v1_t) + (95 * sizeof(qff_ascii_glyph_v1_t))), "qff_ascii_glyph_table_v1_t must be 290 bytes in v1 of QFF");
|
||||
|
||||
/////////////////////////////////////////
|
||||
// Unicode glyph table descriptor
|
||||
|
||||
#define QFF_UNICODE_GLYPH_DESCRIPTOR_TYPEID 0x02
|
||||
|
||||
typedef struct __attribute__((packed)) qff_unicode_glyph_v1_t {
|
||||
uint32_t code_point : 24;
|
||||
uint32_t value : 24; // Uses QFF_GLYPH_*_(BITS|MASK) as bitfield ordering is compiler-defined
|
||||
} qff_unicode_glyph_v1_t;
|
||||
|
||||
_Static_assert(sizeof(qff_unicode_glyph_v1_t) == 6, "qff_unicode_glyph_v1_t must be 6 bytes in v1 of QFF");
|
||||
|
||||
typedef struct __attribute__((packed)) qff_unicode_glyph_table_v1_t {
|
||||
qgf_block_header_v1_t header; // = { .type_id = 0x02, .neg_type_id = (~0x02), .length = (N * 6) }
|
||||
qff_unicode_glyph_v1_t glyph[0]; // Extent of '0' signifies that this struct is immediately followed by the glyph data
|
||||
} qff_unicode_glyph_table_v1_t;
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// QFF API
|
||||
|
||||
bool qff_validate_stream(qp_stream_t *stream);
|
||||
uint32_t qff_get_total_size(qp_stream_t *stream);
|
||||
bool qff_read_font_descriptor(qp_stream_t *stream, uint8_t *line_height, bool *has_ascii_table, uint16_t *num_unicode_glyphs, uint8_t *bpp, bool *has_palette, painter_compression_t *compression_scheme, uint32_t *total_bytes);
|
292
quantum/painter/qgf.c
Normal file
292
quantum/painter/qgf.c
Normal file
|
@ -0,0 +1,292 @@
|
|||
// Copyright 2021 Nick Brassel (@tzarc)
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
// Quantum Graphics File "QGF" File Format.
|
||||
// See https://docs.qmk.fm/#/quantum_painter_qgf for more information.
|
||||
|
||||
#include "qgf.h"
|
||||
#include "qp_draw.h"
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// QGF API
|
||||
|
||||
bool qgf_validate_block_header(qgf_block_header_v1_t *desc, uint8_t expected_typeid, int32_t expected_length) {
|
||||
if (desc->type_id != expected_typeid || desc->neg_type_id != ((~expected_typeid) & 0xFF)) {
|
||||
qp_dprintf("Failed to validate header, expected typeid 0x%02X, was 0x%02X, expected negated typeid 0x%02X, was 0x%02X\n", (int)expected_typeid, (int)desc->type_id, (int)((~desc->type_id) & 0xFF), (int)desc->neg_type_id);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (expected_length >= 0 && desc->length != expected_length) {
|
||||
qp_dprintf("Failed to validate header (typeid 0x%02X), expected length %d, was %d\n", (int)desc->type_id, (int)expected_length, (int)desc->length);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool qgf_parse_format(qp_image_format_t format, uint8_t *bpp, bool *has_palette) {
|
||||
// clang-format off
|
||||
static const struct QP_PACKED {
|
||||
uint8_t bpp;
|
||||
bool has_palette;
|
||||
} formats[] = {
|
||||
[GRAYSCALE_1BPP] = {.bpp = 1, .has_palette = false},
|
||||
[GRAYSCALE_2BPP] = {.bpp = 2, .has_palette = false},
|
||||
[GRAYSCALE_4BPP] = {.bpp = 4, .has_palette = false},
|
||||
[GRAYSCALE_8BPP] = {.bpp = 8, .has_palette = false},
|
||||
[PALETTE_1BPP] = {.bpp = 1, .has_palette = true},
|
||||
[PALETTE_2BPP] = {.bpp = 2, .has_palette = true},
|
||||
[PALETTE_4BPP] = {.bpp = 4, .has_palette = true},
|
||||
[PALETTE_8BPP] = {.bpp = 8, .has_palette = true},
|
||||
};
|
||||
// clang-format on
|
||||
|
||||
// Copy out the required info
|
||||
if (format > PALETTE_8BPP) {
|
||||
qp_dprintf("Failed to parse frame_descriptor, invalid format 0x%02X\n", (int)format);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Copy out the required info
|
||||
if (bpp) {
|
||||
*bpp = formats[format].bpp;
|
||||
}
|
||||
if (has_palette) {
|
||||
*has_palette = formats[format].has_palette;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool qgf_parse_frame_descriptor(qgf_frame_v1_t *frame_descriptor, uint8_t *bpp, bool *has_palette, bool *is_delta, painter_compression_t *compression_scheme, uint16_t *delay) {
|
||||
// Decode the format
|
||||
qgf_parse_format(frame_descriptor->format, bpp, has_palette);
|
||||
|
||||
// Copy out the required info
|
||||
if (is_delta) {
|
||||
*is_delta = (frame_descriptor->flags & QGF_FRAME_FLAG_DELTA) == QGF_FRAME_FLAG_DELTA;
|
||||
}
|
||||
if (compression_scheme) {
|
||||
*compression_scheme = frame_descriptor->compression_scheme;
|
||||
}
|
||||
if (delay) {
|
||||
*delay = frame_descriptor->delay;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool qgf_read_graphics_descriptor(qp_stream_t *stream, uint16_t *image_width, uint16_t *image_height, uint16_t *frame_count, uint32_t *total_bytes) {
|
||||
// Seek to the start
|
||||
qp_stream_setpos(stream, 0);
|
||||
|
||||
// Read and validate the graphics descriptor
|
||||
qgf_graphics_descriptor_v1_t graphics_descriptor;
|
||||
if (qp_stream_read(&graphics_descriptor, sizeof(qgf_graphics_descriptor_v1_t), 1, stream) != 1) {
|
||||
qp_dprintf("Failed to read graphics_descriptor, expected length was not %d\n", (int)sizeof(qgf_graphics_descriptor_v1_t));
|
||||
return false;
|
||||
}
|
||||
|
||||
// Make sure this block is valid
|
||||
if (!qgf_validate_block_header(&graphics_descriptor.header, QGF_GRAPHICS_DESCRIPTOR_TYPEID, (sizeof(qgf_graphics_descriptor_v1_t) - sizeof(qgf_block_header_v1_t)))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Make sure the magic and version are correct
|
||||
if (graphics_descriptor.magic != QGF_MAGIC || graphics_descriptor.qgf_version != 0x01) {
|
||||
qp_dprintf("Failed to validate graphics_descriptor, expected magic 0x%06X was 0x%06X, expected version = 0x%02X was 0x%02X\n", (int)QGF_MAGIC, (int)graphics_descriptor.magic, (int)0x01, (int)graphics_descriptor.qgf_version);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Make sure the file length is valid
|
||||
if (graphics_descriptor.neg_total_file_size != ~graphics_descriptor.total_file_size) {
|
||||
qp_dprintf("Failed to validate graphics_descriptor, expected negated length 0x%08X was 0x%08X\n", (int)(~graphics_descriptor.total_file_size), (int)graphics_descriptor.neg_total_file_size);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Copy out the required info
|
||||
if (image_width) {
|
||||
*image_width = graphics_descriptor.image_width;
|
||||
}
|
||||
if (image_height) {
|
||||
*image_height = graphics_descriptor.image_height;
|
||||
}
|
||||
if (frame_count) {
|
||||
*frame_count = graphics_descriptor.frame_count;
|
||||
}
|
||||
if (total_bytes) {
|
||||
*total_bytes = graphics_descriptor.total_file_size;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool qgf_read_frame_offset(qp_stream_t *stream, uint16_t frame_number, uint32_t *frame_offset) {
|
||||
uint16_t frame_count;
|
||||
if (!qgf_read_graphics_descriptor(stream, NULL, NULL, &frame_count, NULL)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Read the frame offsets descriptor
|
||||
qgf_frame_offsets_v1_t frame_offsets;
|
||||
if (qp_stream_read(&frame_offsets, sizeof(qgf_frame_offsets_v1_t), 1, stream) != 1) {
|
||||
qp_dprintf("Failed to read frame_offsets, expected length was not %d\n", (int)sizeof(qgf_frame_offsets_v1_t));
|
||||
return false;
|
||||
}
|
||||
|
||||
// Make sure this block is valid
|
||||
if (!qgf_validate_block_header(&frame_offsets.header, QGF_FRAME_OFFSET_DESCRIPTOR_TYPEID, (frame_count * sizeof(uint32_t)))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (frame_number >= frame_count) {
|
||||
qp_dprintf("Invalid frame number, was %d but only %d frames in image\n", (int)frame_number, (int)frame_count);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Skip the necessary amount of data to get to the requested frame offset
|
||||
qp_stream_seek(stream, frame_number * sizeof(uint32_t), SEEK_CUR);
|
||||
|
||||
// Read the frame offset
|
||||
uint32_t offset = 0;
|
||||
if (qp_stream_read(&offset, sizeof(uint32_t), 1, stream) != 1) {
|
||||
qp_dprintf("Failed to read frame offset, expected length was not %d\n", (int)sizeof(uint32_t));
|
||||
return false;
|
||||
}
|
||||
|
||||
// Copy out the required info
|
||||
if (frame_offset) {
|
||||
*frame_offset = offset;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void qgf_seek_to_frame_descriptor(qp_stream_t *stream, uint16_t frame_number) {
|
||||
// Read the offset
|
||||
uint32_t offset = 0;
|
||||
qgf_read_frame_offset(stream, frame_number, &offset);
|
||||
|
||||
// Move to the offset
|
||||
qp_stream_setpos(stream, offset);
|
||||
}
|
||||
|
||||
bool qgf_validate_frame_descriptor(qp_stream_t *stream, uint16_t frame_number, uint8_t *bpp, bool *has_palette, bool *is_delta) {
|
||||
// Seek to the correct location
|
||||
qgf_seek_to_frame_descriptor(stream, frame_number);
|
||||
|
||||
// Read the raw descriptor
|
||||
qgf_frame_v1_t frame_descriptor;
|
||||
if (qp_stream_read(&frame_descriptor, sizeof(qgf_frame_v1_t), 1, stream) != 1) {
|
||||
qp_dprintf("Failed to read frame_descriptor, expected length was not %d\n", (int)sizeof(qgf_frame_v1_t));
|
||||
return false;
|
||||
}
|
||||
|
||||
// Make sure this block is valid
|
||||
if (!qgf_validate_block_header(&frame_descriptor.header, QGF_FRAME_DESCRIPTOR_TYPEID, (sizeof(qgf_frame_v1_t) - sizeof(qgf_block_header_v1_t)))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return qgf_parse_frame_descriptor(&frame_descriptor, bpp, has_palette, is_delta, NULL, NULL);
|
||||
}
|
||||
|
||||
bool qgf_validate_palette_descriptor(qp_stream_t *stream, uint16_t frame_number, uint8_t bpp) {
|
||||
// Read the palette descriptor
|
||||
qgf_palette_v1_t palette_descriptor;
|
||||
if (qp_stream_read(&palette_descriptor, sizeof(qgf_palette_v1_t), 1, stream) != 1) {
|
||||
qp_dprintf("Failed to read palette_descriptor, expected length was not %d\n", (int)sizeof(qgf_palette_v1_t));
|
||||
return false;
|
||||
}
|
||||
|
||||
// Make sure this block is valid
|
||||
uint32_t expected_length = (1 << bpp) * 3 * sizeof(uint8_t);
|
||||
if (!qgf_validate_block_header(&palette_descriptor.header, QGF_FRAME_PALETTE_DESCRIPTOR_TYPEID, expected_length)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Move forward in the stream to the next block
|
||||
qp_stream_seek(stream, expected_length, SEEK_CUR);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool qgf_validate_delta_descriptor(qp_stream_t *stream, uint16_t frame_number) {
|
||||
// Read the delta descriptor
|
||||
qgf_delta_v1_t delta_descriptor;
|
||||
if (qp_stream_read(&delta_descriptor, sizeof(qgf_delta_v1_t), 1, stream) != 1) {
|
||||
qp_dprintf("Failed to read delta_descriptor, expected length was not %d\n", (int)sizeof(qgf_delta_v1_t));
|
||||
return false;
|
||||
}
|
||||
|
||||
// Make sure this block is valid
|
||||
if (!qgf_validate_block_header(&delta_descriptor.header, QGF_FRAME_DELTA_DESCRIPTOR_TYPEID, (sizeof(qgf_delta_v1_t) - sizeof(qgf_block_header_v1_t)))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool qgf_validate_frame_data_descriptor(qp_stream_t *stream, uint16_t frame_number) {
|
||||
// Read and validate the data block
|
||||
qgf_data_v1_t data_descriptor;
|
||||
if (qp_stream_read(&data_descriptor, sizeof(qgf_data_v1_t), 1, stream) != 1) {
|
||||
qp_dprintf("Failed to read data_descriptor, expected length was not %d\n", (int)sizeof(qgf_data_v1_t));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!qgf_validate_block_header(&data_descriptor.header, QGF_FRAME_DATA_DESCRIPTOR_TYPEID, -1)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool qgf_validate_stream(qp_stream_t *stream) {
|
||||
uint16_t frame_count;
|
||||
if (!qgf_read_graphics_descriptor(stream, NULL, NULL, &frame_count, NULL)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Read and validate all the frames (automatically validates the frame offset descriptor in the process)
|
||||
for (uint16_t i = 0; i < frame_count; ++i) {
|
||||
// Validate the frame descriptor block
|
||||
uint8_t bpp;
|
||||
bool has_palette;
|
||||
bool has_delta;
|
||||
if (!qgf_validate_frame_descriptor(stream, i, &bpp, &has_palette, &has_delta)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// If we've got a palette block, check it
|
||||
if (has_palette && !qgf_validate_palette_descriptor(stream, i, bpp)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// If we've got a delta block, check it
|
||||
if (has_delta && !qgf_validate_delta_descriptor(stream, i)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check the data block
|
||||
if (!qgf_validate_frame_data_descriptor(stream, i)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Work out the total size of an image definition, assuming we can read far enough into the file
|
||||
uint32_t qgf_get_total_size(qp_stream_t *stream) {
|
||||
// Get the original location
|
||||
uint32_t oldpos = qp_stream_tell(stream);
|
||||
|
||||
// Read the graphics descriptor, grabbing the size
|
||||
uint32_t total_size;
|
||||
if (!qgf_read_graphics_descriptor(stream, NULL, NULL, NULL, &total_size)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Restore the original location
|
||||
qp_stream_setpos(stream, oldpos);
|
||||
return total_size;
|
||||
}
|
136
quantum/painter/qgf.h
Normal file
136
quantum/painter/qgf.h
Normal file
|
@ -0,0 +1,136 @@
|
|||
// Copyright 2021 Nick Brassel (@tzarc)
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
// Quantum Graphics File "QGF" File Format.
|
||||
// See https://docs.qmk.fm/#/quantum_painter_qgf for more information.
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#include "qp_stream.h"
|
||||
#include "qp_internal.h"
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// QGF structures
|
||||
|
||||
/////////////////////////////////////////
|
||||
// Common block header
|
||||
|
||||
typedef struct QP_PACKED qgf_block_header_v1_t {
|
||||
uint8_t type_id; // See each respective block type below.
|
||||
uint8_t neg_type_id; // Negated type ID, used for detecting parsing errors.
|
||||
uint32_t length : 24; // 24-bit blob length, allowing for block sizes of a maximum of 16MB.
|
||||
} qgf_block_header_v1_t;
|
||||
|
||||
_Static_assert(sizeof(qgf_block_header_v1_t) == 5, "qgf_block_header_v1_t must be 5 bytes in v1 of QGF");
|
||||
|
||||
/////////////////////////////////////////
|
||||
// Graphics descriptor
|
||||
|
||||
#define QGF_GRAPHICS_DESCRIPTOR_TYPEID 0x00
|
||||
|
||||
typedef struct QP_PACKED qgf_graphics_descriptor_v1_t {
|
||||
qgf_block_header_v1_t header; // = { .type_id = 0x00, .neg_type_id = (~0x00), .length = 18 }
|
||||
uint32_t magic : 24; // constant, equal to 0x464751 ("QGF")
|
||||
uint8_t qgf_version; // constant, equal to 0x01
|
||||
uint32_t total_file_size; // total size of the entire file, starting at offset zero
|
||||
uint32_t neg_total_file_size; // negated value of total_file_size
|
||||
uint16_t image_width; // in pixels
|
||||
uint16_t image_height; // in pixels
|
||||
uint16_t frame_count; // minimum of 1
|
||||
} qgf_graphics_descriptor_v1_t;
|
||||
|
||||
_Static_assert(sizeof(qgf_graphics_descriptor_v1_t) == (sizeof(qgf_block_header_v1_t) + 18), "qgf_graphics_descriptor_v1_t must be 23 bytes in v1 of QGF");
|
||||
|
||||
#define QGF_MAGIC 0x464751
|
||||
|
||||
/////////////////////////////////////////
|
||||
// Frame offset descriptor
|
||||
|
||||
#define QGF_FRAME_OFFSET_DESCRIPTOR_TYPEID 0x01
|
||||
|
||||
typedef struct QP_PACKED qgf_frame_offsets_v1_t {
|
||||
qgf_block_header_v1_t header; // = { .type_id = 0x01, .neg_type_id = (~0x01), .length = (N * sizeof(uint32_t)) }
|
||||
uint32_t offset[0]; // '0' signifies that this struct is immediately followed by the frame offsets
|
||||
} qgf_frame_offsets_v1_t;
|
||||
|
||||
_Static_assert(sizeof(qgf_frame_offsets_v1_t) == sizeof(qgf_block_header_v1_t), "qgf_frame_offsets_v1_t must only contain qgf_block_header_v1_t in v1 of QGF");
|
||||
|
||||
/////////////////////////////////////////
|
||||
// Frame descriptor
|
||||
|
||||
#define QGF_FRAME_DESCRIPTOR_TYPEID 0x02
|
||||
|
||||
typedef struct QP_PACKED qgf_frame_v1_t {
|
||||
qgf_block_header_v1_t header; // = { .type_id = 0x02, .neg_type_id = (~0x02), .length = 6 }
|
||||
qp_image_format_t format : 8; // Frame format, see qp.h.
|
||||
uint8_t flags; // Frame flags, see below.
|
||||
painter_compression_t compression_scheme : 8; // Compression scheme, see qp.h.
|
||||
uint8_t transparency_index; // palette index used for transparent pixels (not yet implemented)
|
||||
uint16_t delay; // frame delay time for animations (in units of milliseconds)
|
||||
} qgf_frame_v1_t;
|
||||
|
||||
_Static_assert(sizeof(qgf_frame_v1_t) == (sizeof(qgf_block_header_v1_t) + 6), "qgf_frame_v1_t must be 11 bytes in v1 of QGF");
|
||||
|
||||
#define QGF_FRAME_FLAG_DELTA 0x02
|
||||
#define QGF_FRAME_FLAG_TRANSPARENT 0x01
|
||||
|
||||
/////////////////////////////////////////
|
||||
// Frame palette descriptor
|
||||
|
||||
#define QGF_FRAME_PALETTE_DESCRIPTOR_TYPEID 0x03
|
||||
|
||||
typedef struct QP_PACKED qgf_palette_entry_v1_t {
|
||||
uint8_t h; // hue component: `[0,360)` degrees is mapped to `[0,255]` uint8_t.
|
||||
uint8_t s; // saturation component: `[0,1]` is mapped to `[0,255]` uint8_t.
|
||||
uint8_t v; // value component: `[0,1]` is mapped to `[0,255]` uint8_t.
|
||||
} qgf_palette_entry_v1_t;
|
||||
|
||||
_Static_assert(sizeof(qgf_palette_entry_v1_t) == 3, "Palette entry is not 3 bytes in size");
|
||||
|
||||
typedef struct QP_PACKED qgf_palette_v1_t {
|
||||
qgf_block_header_v1_t header; // = { .type_id = 0x03, .neg_type_id = (~0x03), .length = (N * 3 * sizeof(uint8_t)) }
|
||||
qgf_palette_entry_v1_t hsv[0]; // N * hsv, where N is the number of palette entries depending on the frame format in the descriptor
|
||||
} qgf_palette_v1_t;
|
||||
|
||||
_Static_assert(sizeof(qgf_palette_v1_t) == sizeof(qgf_block_header_v1_t), "qgf_palette_v1_t must only contain qgf_block_header_v1_t in v1 of QGF");
|
||||
|
||||
/////////////////////////////////////////
|
||||
// Frame delta descriptor
|
||||
|
||||
#define QGF_FRAME_DELTA_DESCRIPTOR_TYPEID 0x04
|
||||
|
||||
typedef struct QP_PACKED qgf_delta_v1_t {
|
||||
qgf_block_header_v1_t header; // = { .type_id = 0x04, .neg_type_id = (~0x04), .length = 8 }
|
||||
uint16_t left; // The left pixel location to draw the delta image
|
||||
uint16_t top; // The top pixel location to draw the delta image
|
||||
uint16_t right; // The right pixel location to to draw the delta image
|
||||
uint16_t bottom; // The bottom pixel location to to draw the delta image
|
||||
} qgf_delta_v1_t;
|
||||
|
||||
_Static_assert(sizeof(qgf_delta_v1_t) == (sizeof(qgf_block_header_v1_t) + 8), "qgf_delta_v1_t must be 13 bytes in v1 of QGF");
|
||||
|
||||
/////////////////////////////////////////
|
||||
// Frame data descriptor
|
||||
|
||||
#define QGF_FRAME_DATA_DESCRIPTOR_TYPEID 0x05
|
||||
|
||||
typedef struct QP_PACKED qgf_data_v1_t {
|
||||
qgf_block_header_v1_t header; // = { .type_id = 0x05, .neg_type_id = (~0x05), .length = N }
|
||||
uint8_t data[0]; // 0 signifies that this struct is immediately followed by the length of data specified in the header
|
||||
} qgf_data_v1_t;
|
||||
|
||||
_Static_assert(sizeof(qgf_data_v1_t) == sizeof(qgf_block_header_v1_t), "qgf_data_v1_t must only contain qgf_block_header_v1_t in v1 of QGF");
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// QGF API
|
||||
|
||||
uint32_t qgf_get_total_size(qp_stream_t *stream);
|
||||
bool qgf_validate_stream(qp_stream_t *stream);
|
||||
bool qgf_validate_block_header(qgf_block_header_v1_t *desc, uint8_t expected_typeid, int32_t expected_length);
|
||||
bool qgf_read_graphics_descriptor(qp_stream_t *stream, uint16_t *image_width, uint16_t *image_height, uint16_t *frame_count, uint32_t *total_bytes);
|
||||
bool qgf_parse_format(qp_image_format_t format, uint8_t *bpp, bool *has_palette);
|
||||
void qgf_seek_to_frame_descriptor(qp_stream_t *stream, uint16_t frame_number);
|
||||
bool qgf_parse_frame_descriptor(qgf_frame_v1_t *frame_descriptor, uint8_t *bpp, bool *has_palette, bool *is_delta, painter_compression_t *compression_scheme, uint16_t *delay);
|
228
quantum/painter/qp.c
Normal file
228
quantum/painter/qp.c
Normal file
|
@ -0,0 +1,228 @@
|
|||
// Copyright 2021 Nick Brassel (@tzarc)
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <quantum.h>
|
||||
#include <utf8.h>
|
||||
|
||||
#include "qp_internal.h"
|
||||
#include "qp_comms.h"
|
||||
#include "qp_draw.h"
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Internal driver validation
|
||||
|
||||
static bool validate_driver_vtable(struct painter_driver_t *driver) {
|
||||
return (driver->driver_vtable && driver->driver_vtable->init && driver->driver_vtable->power && driver->driver_vtable->clear && driver->driver_vtable->viewport && driver->driver_vtable->pixdata && driver->driver_vtable->palette_convert && driver->driver_vtable->append_pixels) ? true : false;
|
||||
}
|
||||
|
||||
static bool validate_comms_vtable(struct painter_driver_t *driver) {
|
||||
return (driver->comms_vtable && driver->comms_vtable->comms_init && driver->comms_vtable->comms_start && driver->comms_vtable->comms_stop && driver->comms_vtable->comms_send) ? true : false;
|
||||
}
|
||||
|
||||
static bool validate_driver_integrity(struct painter_driver_t *driver) {
|
||||
return validate_driver_vtable(driver) && validate_comms_vtable(driver);
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Quantum Painter External API: qp_init
|
||||
|
||||
bool qp_init(painter_device_t device, painter_rotation_t rotation) {
|
||||
qp_dprintf("qp_init: entry\n");
|
||||
struct painter_driver_t *driver = (struct painter_driver_t *)device;
|
||||
|
||||
driver->validate_ok = false;
|
||||
if (!validate_driver_integrity(driver)) {
|
||||
qp_dprintf("Failed to validate driver integrity in qp_init\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
driver->validate_ok = true;
|
||||
|
||||
if (!qp_comms_init(device)) {
|
||||
driver->validate_ok = false;
|
||||
qp_dprintf("qp_init: fail (could not init comms)\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!qp_comms_start(device)) {
|
||||
qp_dprintf("qp_init: fail (could not start comms)\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Set the rotation before init
|
||||
driver->rotation = rotation;
|
||||
|
||||
// Invoke init
|
||||
bool ret = driver->driver_vtable->init(device, rotation);
|
||||
qp_comms_stop(device);
|
||||
qp_dprintf("qp_init: %s\n", ret ? "ok" : "fail");
|
||||
return ret;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Quantum Painter External API: qp_power
|
||||
|
||||
bool qp_power(painter_device_t device, bool power_on) {
|
||||
qp_dprintf("qp_power: entry\n");
|
||||
struct painter_driver_t *driver = (struct painter_driver_t *)device;
|
||||
if (!driver->validate_ok) {
|
||||
qp_dprintf("qp_power: fail (validation_ok == false)\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!qp_comms_start(device)) {
|
||||
qp_dprintf("qp_power: fail (could not start comms)\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ret = driver->driver_vtable->power(device, power_on);
|
||||
qp_comms_stop(device);
|
||||
qp_dprintf("qp_power: %s\n", ret ? "ok" : "fail");
|
||||
return ret;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Quantum Painter External API: qp_clear
|
||||
|
||||
bool qp_clear(painter_device_t device) {
|
||||
qp_dprintf("qp_clear: entry\n");
|
||||
struct painter_driver_t *driver = (struct painter_driver_t *)device;
|
||||
if (!driver->validate_ok) {
|
||||
qp_dprintf("qp_clear: fail (validation_ok == false)\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!qp_comms_start(device)) {
|
||||
qp_dprintf("qp_clear: fail (could not start comms)\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ret = driver->driver_vtable->clear(device);
|
||||
qp_comms_stop(device);
|
||||
qp_dprintf("qp_clear: %s\n", ret ? "ok" : "fail");
|
||||
return ret;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Quantum Painter External API: qp_flush
|
||||
|
||||
bool qp_flush(painter_device_t device) {
|
||||
qp_dprintf("qp_flush: entry\n");
|
||||
struct painter_driver_t *driver = (struct painter_driver_t *)device;
|
||||
if (!driver->validate_ok) {
|
||||
qp_dprintf("qp_flush: fail (validation_ok == false)\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!qp_comms_start(device)) {
|
||||
qp_dprintf("qp_flush: fail (could not start comms)\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ret = driver->driver_vtable->flush(device);
|
||||
qp_comms_stop(device);
|
||||
qp_dprintf("qp_flush: %s\n", ret ? "ok" : "fail");
|
||||
return ret;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Quantum Painter External API: qp_get_geometry
|
||||
|
||||
void qp_get_geometry(painter_device_t device, uint16_t *width, uint16_t *height, painter_rotation_t *rotation, uint16_t *offset_x, uint16_t *offset_y) {
|
||||
qp_dprintf("qp_geometry: entry\n");
|
||||
struct painter_driver_t *driver = (struct painter_driver_t *)device;
|
||||
|
||||
switch (driver->rotation) {
|
||||
default:
|
||||
case QP_ROTATION_0:
|
||||
case QP_ROTATION_180:
|
||||
if (width) {
|
||||
*width = driver->panel_width;
|
||||
}
|
||||
if (height) {
|
||||
*height = driver->panel_height;
|
||||
}
|
||||
break;
|
||||
case QP_ROTATION_90:
|
||||
case QP_ROTATION_270:
|
||||
if (width) {
|
||||
*width = driver->panel_height;
|
||||
}
|
||||
if (height) {
|
||||
*height = driver->panel_width;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if (rotation) {
|
||||
*rotation = driver->rotation;
|
||||
}
|
||||
|
||||
if (offset_x) {
|
||||
*offset_x = driver->offset_x;
|
||||
}
|
||||
|
||||
if (offset_y) {
|
||||
*offset_y = driver->offset_y;
|
||||
}
|
||||
|
||||
qp_dprintf("qp_geometry: ok\n");
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Quantum Painter External API: qp_set_viewport_offsets
|
||||
|
||||
void qp_set_viewport_offsets(painter_device_t device, uint16_t offset_x, uint16_t offset_y) {
|
||||
qp_dprintf("qp_set_viewport_offsets: entry\n");
|
||||
struct painter_driver_t *driver = (struct painter_driver_t *)device;
|
||||
|
||||
driver->offset_x = offset_x;
|
||||
driver->offset_y = offset_y;
|
||||
|
||||
qp_dprintf("qp_set_viewport_offsets: ok\n");
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Quantum Painter External API: qp_viewport
|
||||
|
||||
bool qp_viewport(painter_device_t device, uint16_t left, uint16_t top, uint16_t right, uint16_t bottom) {
|
||||
qp_dprintf("qp_viewport: entry\n");
|
||||
struct painter_driver_t *driver = (struct painter_driver_t *)device;
|
||||
if (!driver->validate_ok) {
|
||||
qp_dprintf("qp_viewport: fail (validation_ok == false)\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!qp_comms_start(device)) {
|
||||
qp_dprintf("qp_viewport: fail (could not start comms)\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Set the viewport
|
||||
bool ret = driver->driver_vtable->viewport(device, left, top, right, bottom);
|
||||
qp_dprintf("qp_viewport: %s\n", ret ? "ok" : "fail");
|
||||
qp_comms_stop(device);
|
||||
return ret;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Quantum Painter External API: qp_pixdata
|
||||
|
||||
bool qp_pixdata(painter_device_t device, const void *pixel_data, uint32_t native_pixel_count) {
|
||||
qp_dprintf("qp_pixdata: entry\n");
|
||||
struct painter_driver_t *driver = (struct painter_driver_t *)device;
|
||||
if (!driver->validate_ok) {
|
||||
qp_dprintf("qp_pixdata: fail (validation_ok == false)\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!qp_comms_start(device)) {
|
||||
qp_dprintf("qp_pixdata: fail (could not start comms)\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ret = driver->driver_vtable->pixdata(device, pixel_data, native_pixel_count);
|
||||
qp_dprintf("qp_pixdata: %s\n", ret ? "ok" : "fail");
|
||||
qp_comms_stop(device);
|
||||
return ret;
|
||||
}
|
453
quantum/painter/qp.h
Normal file
453
quantum/painter/qp.h
Normal file
|
@ -0,0 +1,453 @@
|
|||
// Copyright 2021 Nick Brassel (@tzarc)
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#include "deferred_exec.h"
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Quantum Painter global configurables (add to your keyboard's config.h)
|
||||
|
||||
#ifndef QUANTUM_PAINTER_NUM_IMAGES
|
||||
/**
|
||||
* @def This controls the maximum number of images that Quantum Painter can load at any one time. Images can be loaded
|
||||
* using \ref qp_load_image_mem, and can be unloaded by calling \ref qp_close_image. Increasing this number in
|
||||
* order to load more images increases the amount of RAM required. Image data is not held in RAM, just metadata.
|
||||
*/
|
||||
# define QUANTUM_PAINTER_NUM_IMAGES 8
|
||||
#endif // QUANTUM_PAINTER_NUM_IMAGES
|
||||
|
||||
#ifndef QUANTUM_PAINTER_NUM_FONTS
|
||||
/**
|
||||
* @def This controls the maximum number of fonts that Quantum Painter can load. Fonts can be loaded using
|
||||
* \ref qp_load_font_mem, and can be unloaded by calling \ref qp_close_font. Increasing this number in order to
|
||||
* load more fonts increases the amount of RAM required. Font data is not held in RAM, unless
|
||||
* \ref QUANTUM_PAINTER_LOAD_FONTS_TO_RAM is set to TRUE.
|
||||
*/
|
||||
# define QUANTUM_PAINTER_NUM_FONTS 4
|
||||
#endif // QUANTUM_PAINTER_NUM_FONTS
|
||||
|
||||
#ifndef QUANTUM_PAINTER_LOAD_FONTS_TO_RAM
|
||||
/**
|
||||
* @def This controls whether or not fonts should be cached in RAM. Under normal circumstances, fonts can have quite
|
||||
* random access patterns, and due to timing of flash memory or external storage, it may be a significant speedup
|
||||
* moving the font into RAM before use. Defaults to "off", but if it's enabled it will fallback to reading from the
|
||||
* original location if corresponding RAM could not be allocated (such as being too large).
|
||||
*/
|
||||
# define QUANTUM_PAINTER_LOAD_FONTS_TO_RAM FALSE
|
||||
#endif
|
||||
|
||||
#ifndef QUANTUM_PAINTER_CONCURRENT_ANIMATIONS
|
||||
/**
|
||||
* @def This controls the maximum number of animations that Quantum Painter can play simultaneously. Increasing this
|
||||
* number in order to play more animations at the same time increases the amount of RAM required.
|
||||
*/
|
||||
# define QUANTUM_PAINTER_CONCURRENT_ANIMATIONS 4
|
||||
#endif // QUANTUM_PAINTER_CONCURRENT_ANIMATIONS
|
||||
|
||||
#ifndef QUANTUM_PAINTER_PIXDATA_BUFFER_SIZE
|
||||
/**
|
||||
* @def This controls the maximum size of the pixel data buffer used for single blocks of transmission. Larger buffers
|
||||
* means more data is processed at one time, with less frequent transmissions, at the cost of RAM.
|
||||
*/
|
||||
# define QUANTUM_PAINTER_PIXDATA_BUFFER_SIZE 32
|
||||
#endif
|
||||
|
||||
#ifndef QUANTUM_PAINTER_SUPPORTS_256_PALETTE
|
||||
/**
|
||||
* @def This controls whether 256-color palettes are supported. This has relatively hefty requirements on RAM -- at
|
||||
* least 1kB extra is required just to store the palette information, with more required for other metadata.
|
||||
*/
|
||||
# define QUANTUM_PAINTER_SUPPORTS_256_PALETTE FALSE
|
||||
#endif
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Quantum Painter types
|
||||
|
||||
/**
|
||||
* @typedef A handle to a Quantum Painter device, such as an LCD or OLED. Most Quantum Painter APIs require this
|
||||
* argument in order to perform operations on the display.
|
||||
*/
|
||||
typedef const void *painter_device_t;
|
||||
|
||||
/**
|
||||
* @typedef The desired rotation of a panel. Used as a parameter to \ref qp_init, and can be queried by
|
||||
* \ref qp_get_geometry.
|
||||
*/
|
||||
typedef enum { QP_ROTATION_0, QP_ROTATION_90, QP_ROTATION_180, QP_ROTATION_270 } painter_rotation_t;
|
||||
|
||||
/**
|
||||
* @typedef A descriptor for a Quantum Painter image.
|
||||
*/
|
||||
typedef struct painter_image_desc_t {
|
||||
uint16_t width; ///< Image width
|
||||
uint16_t height; ///< Image height
|
||||
uint16_t frame_count; ///< Number of frames in this image
|
||||
} painter_image_desc_t;
|
||||
|
||||
/**
|
||||
* @typedef A handle to a Quantum Painter image.
|
||||
*/
|
||||
typedef const painter_image_desc_t *painter_image_handle_t;
|
||||
|
||||
/**
|
||||
* @typedef A descriptor for a Quantum Painter font.
|
||||
*/
|
||||
typedef struct painter_font_desc_t {
|
||||
uint8_t line_height; ///< The number of pixels in height for each line
|
||||
} painter_font_desc_t;
|
||||
|
||||
/**
|
||||
* @typedef A handle to a Quantum Painter font.
|
||||
*/
|
||||
typedef const painter_font_desc_t *painter_font_handle_t;
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Quantum Painter External API
|
||||
|
||||
/**
|
||||
* Initialize a device and set its rotation.
|
||||
*
|
||||
* @param device[in] the handle of the device to initialize
|
||||
* @param rotation[in] the rotation to use
|
||||
* @return true if initialization succeeded
|
||||
* @return false if initialization failed
|
||||
*/
|
||||
bool qp_init(painter_device_t device, painter_rotation_t rotation);
|
||||
|
||||
/**
|
||||
* Controls whether a display is on or off.
|
||||
*
|
||||
* @note If backlighting is used to control brightness (such as for an LCD), it will need to be handled external to
|
||||
* Quantum Painter.
|
||||
*
|
||||
* @param device[in] the handle of the device to control
|
||||
* @param power_on[in] whether or not the device should be on
|
||||
* @return true if controlling the power state succeeded
|
||||
* @return false if controlling the power state failed
|
||||
*/
|
||||
bool qp_power(painter_device_t device, bool power_on);
|
||||
|
||||
/**
|
||||
* Clears a device's screen.
|
||||
*
|
||||
* @param device[in] the handle of the device to control
|
||||
* @return true if clearing the screen succeeded
|
||||
* @return false if clearing the screen failed
|
||||
*/
|
||||
bool qp_clear(painter_device_t device);
|
||||
|
||||
/**
|
||||
* Transmits any outstanding data to the screen in order to persist all changes to the display.
|
||||
*
|
||||
* @note Drivers without internal framebuffers will likely ignore this API.
|
||||
*
|
||||
* @param device[in] the handle of the device to control
|
||||
* @return true if flushing changes to the screen succeeded
|
||||
* @return false if flushing changes to the screen failed
|
||||
*/
|
||||
bool qp_flush(painter_device_t device);
|
||||
|
||||
/**
|
||||
* Retrieves the size, rotation, and offsets for the display.
|
||||
*
|
||||
* @note Any arguments of NULL will be ignored.
|
||||
*
|
||||
* @param device[in] the handle of the device to control
|
||||
* @param width[out] the device's width
|
||||
* @param height[out] the device's height
|
||||
* @param rotation[out] the device's rotation
|
||||
* @param offset_x[out] the device's x-offset applied while drawing
|
||||
* @param offset_y[out] the device's y-offset applied while drawing
|
||||
*/
|
||||
void qp_get_geometry(painter_device_t device, uint16_t *width, uint16_t *height, painter_rotation_t *rotation, uint16_t *offset_x, uint16_t *offset_y);
|
||||
|
||||
/**
|
||||
* Allows repositioning of the viewport if the panel geometry offsets are non-zero.
|
||||
*
|
||||
* @param device[in] the handle of the device to control
|
||||
* @param offset_x[in] the device's x-offset applied while drawing
|
||||
* @param offset_y[in] the device's y-offset applied while drawing
|
||||
*/
|
||||
void qp_set_viewport_offsets(painter_device_t device, uint16_t offset_x, uint16_t offset_y);
|
||||
|
||||
/**
|
||||
* Sets a pixel to the specified color.
|
||||
*
|
||||
* @param device[in] the handle of the device to control
|
||||
* @param x[in] the x-position to draw onto the device
|
||||
* @param y[in] the y-position to draw onto the device
|
||||
* @param hue[in] the hue to use, with 0-360 mapped to 0-255
|
||||
* @param sat[in] the saturation to use, with 0-100% mapped to 0-255
|
||||
* @param val[in] the value to use, with 0-100% mapped to 0-255
|
||||
* @return true if setting the pixel succeeded
|
||||
* @return false if setting the pixel failed
|
||||
*/
|
||||
bool qp_setpixel(painter_device_t device, uint16_t x, uint16_t y, uint8_t hue, uint8_t sat, uint8_t val);
|
||||
|
||||
/**
|
||||
* Draws a line using the specified color.
|
||||
*
|
||||
* @param device[in] the handle of the device to control
|
||||
* @param x0[in] the device's x-position to start
|
||||
* @param y0[in] the device's y-position to start
|
||||
* @param x1[in] the device's x-position to finish
|
||||
* @param y1[in] the device's y-position to finish
|
||||
* @param hue[in] the hue to use, with 0-360 mapped to 0-255
|
||||
* @param sat[in] the saturation to use, with 0-100% mapped to 0-255
|
||||
* @param val[in] the value to use, with 0-100% mapped to 0-255
|
||||
* @return true if drawing the line succeeded
|
||||
* @return false if drawing the line failed
|
||||
*/
|
||||
bool qp_line(painter_device_t device, uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1, uint8_t hue, uint8_t sat, uint8_t val);
|
||||
|
||||
/**
|
||||
* Draws a rectangle using the specified color, optionally filled.
|
||||
*
|
||||
* @param device[in] the handle of the device to control
|
||||
* @param left[in] the device's x-position to start
|
||||
* @param top[in] the device's y-position to start
|
||||
* @param right[in] the device's x-position to finish
|
||||
* @param bottom[in] the device's y-position to finish
|
||||
* @param hue[in] the hue to use, with 0-360 mapped to 0-255
|
||||
* @param sat[in] the saturation to use, with 0-100% mapped to 0-255
|
||||
* @param val[in] the value to use, with 0-100% mapped to 0-255
|
||||
* @param filled[in] whether the rectangle should be filled
|
||||
* @return true if drawing the rectangle succeeded
|
||||
* @return false if drawing the rectangle failed
|
||||
*/
|
||||
bool qp_rect(painter_device_t device, uint16_t left, uint16_t top, uint16_t right, uint16_t bottom, uint8_t hue, uint8_t sat, uint8_t val, bool filled);
|
||||
|
||||
/**
|
||||
* Draws a circle using the specified color, optionally filled.
|
||||
*
|
||||
* @param device[in] the handle of the device to control
|
||||
* @param x[in] the x-position of the centre of the circle to draw onto the device
|
||||
* @param y[in] the y-position of the centre of the circle to draw onto the device
|
||||
* @param radius[in] the radius of the circle to draw
|
||||
* @param hue[in] the hue to use, with 0-360 mapped to 0-255
|
||||
* @param sat[in] the saturation to use, with 0-100% mapped to 0-255
|
||||
* @param val[in] the value to use, with 0-100% mapped to 0-255
|
||||
* @param filled[in] whether the circle should be filled
|
||||
* @return true if drawing the circle succeeded
|
||||
* @return false if drawing the circle failed
|
||||
*/
|
||||
bool qp_circle(painter_device_t device, uint16_t x, uint16_t y, uint16_t radius, uint8_t hue, uint8_t sat, uint8_t val, bool filled);
|
||||
|
||||
/**
|
||||
* Draws a ellipse using the specified color, optionally filled.
|
||||
*
|
||||
* @param device[in] the handle of the device to control
|
||||
* @param x[in] the x-position of the centre of the ellipse to draw onto the device
|
||||
* @param y[in] the y-position of the centre of the ellipse to draw onto the device
|
||||
* @param sizex[in] the horizontal size of the ellipse
|
||||
* @param sizey[in] the vertical size of the ellipse
|
||||
* @param hue[in] the hue to use, with 0-360 mapped to 0-255
|
||||
* @param sat[in] the saturation to use, with 0-100% mapped to 0-255
|
||||
* @param val[in] the value to use, with 0-100% mapped to 0-255
|
||||
* @param filled[in] whether the ellipse should be filled
|
||||
* @return true if drawing the ellipse succeeded
|
||||
* @return false if drawing the ellipse failed
|
||||
*/
|
||||
bool qp_ellipse(painter_device_t device, uint16_t x, uint16_t y, uint16_t sizex, uint16_t sizey, uint8_t hue, uint8_t sat, uint8_t val, bool filled);
|
||||
|
||||
/**
|
||||
* Sets up the location on the display to stream raw pixel data to the display, using \ref qp_pixdata.
|
||||
*
|
||||
* @note This is for advanced uses only, and should not be required for normal Quantum Painter functionality.
|
||||
*
|
||||
* @param device[in] the handle of the device to control
|
||||
* @param left[in] the device's x-position to start
|
||||
* @param top[in] the device's y-position to start
|
||||
* @param right[in] the device's x-position to finish
|
||||
* @param bottom[in] the device's y-position to finish
|
||||
* @return true if setting the viewport succeeded
|
||||
* @return false if setting the viewport failed
|
||||
*/
|
||||
bool qp_viewport(painter_device_t device, uint16_t left, uint16_t top, uint16_t right, uint16_t bottom);
|
||||
|
||||
/**
|
||||
* Streams raw pixel data (in the native panel format) to the area previously set by \ref qp_viewport.
|
||||
*
|
||||
* @note This is for advanced uses only, and should not be required for normal Quantum Painter functionality.
|
||||
*
|
||||
* @param device[in] the handle of the device to control
|
||||
* @param pixel_data[in] pointer to buffer data
|
||||
* @param native_pixel_count[in] the number of pixels to transmit
|
||||
* @return true if streaming of data succeeded
|
||||
* @return false if streaming of data failed
|
||||
*/
|
||||
bool qp_pixdata(painter_device_t device, const void *pixel_data, uint32_t native_pixel_count);
|
||||
|
||||
/**
|
||||
* Loads an image into memory.
|
||||
*
|
||||
* @note Images can be unloaded by calling \ref qp_close_image.
|
||||
*
|
||||
* @param buffer[in] the image data to load
|
||||
* @return an image handle usable with \ref qp_drawimage, \ref qp_drawimage_recolor, \ref qp_animate, and
|
||||
* \ref qp_animate_recolor.
|
||||
* @return NULL if loading the image failed
|
||||
*/
|
||||
painter_image_handle_t qp_load_image_mem(const void *buffer);
|
||||
|
||||
/**
|
||||
* Closes an image handle when no longer in use.
|
||||
*
|
||||
* @param image[in] the handle of the image to unload
|
||||
* @return true if unloading the image succeeded
|
||||
* @return false if unloading the image failed
|
||||
*/
|
||||
bool qp_close_image(painter_image_handle_t image);
|
||||
|
||||
/**
|
||||
* Draws an image to the display.
|
||||
*
|
||||
* @param device[in] the handle of the device to control
|
||||
* @param x[in] the x-position where the image should be drawn onto the device
|
||||
* @param y[in] the y-position where the image should be drawn onto the device
|
||||
* @param image[in] the handle of the image to draw
|
||||
* @return true if drawing the image succeeded
|
||||
* @return false if drawing the image failed
|
||||
*/
|
||||
bool qp_drawimage(painter_device_t device, uint16_t x, uint16_t y, painter_image_handle_t image);
|
||||
|
||||
/**
|
||||
* Draws an image to the display, recoloring monochrome images to the desired foreground/background.
|
||||
*
|
||||
* @param device[in] the handle of the device to control
|
||||
* @param x[in] the x-position where the image should be drawn onto the device
|
||||
* @param y[in] the y-position where the image should be drawn onto the device
|
||||
* @param image[in] the handle of the image to draw
|
||||
* @param hue_fg[in] the foreground hue to use, with 0-360 mapped to 0-255
|
||||
* @param sat_fg[in] the foreground saturation to use, with 0-100% mapped to 0-255
|
||||
* @param val_fg[in] the foreground value to use, with 0-100% mapped to 0-255
|
||||
* @param hue_bg[in] the background hue to use, with 0-360 mapped to 0-255
|
||||
* @param sat_bg[in] the background saturation to use, with 0-100% mapped to 0-255
|
||||
* @param val_bg[in] the background value to use, with 0-100% mapped to 0-255
|
||||
* @return true if drawing the image succeeded
|
||||
* @return false if drawing the image failed
|
||||
*/
|
||||
bool qp_drawimage_recolor(painter_device_t device, uint16_t x, uint16_t y, painter_image_handle_t image, uint8_t hue_fg, uint8_t sat_fg, uint8_t val_fg, uint8_t hue_bg, uint8_t sat_bg, uint8_t val_bg);
|
||||
|
||||
/**
|
||||
* Draws an animation to the display.
|
||||
*
|
||||
* @param device[in] the handle of the device to control
|
||||
* @param x[in] the x-position where the image should be drawn onto the device
|
||||
* @param y[in] the y-position where the image should be drawn onto the device
|
||||
* @param image[in] the handle of the image to draw
|
||||
* @return the \ref deferred_token to use with \ref qp_stop_animation in order to stop animating
|
||||
* @return INVALID_DEFERRED_TOKEN if animating the image failed
|
||||
*/
|
||||
deferred_token qp_animate(painter_device_t device, uint16_t x, uint16_t y, painter_image_handle_t image);
|
||||
|
||||
/**
|
||||
* Draws an animation to the display, recoloring monochrome images to the desired foreground/background.
|
||||
*
|
||||
* @param device[in] the handle of the device to control
|
||||
* @param x[in] the x-position where the image should be drawn onto the device
|
||||
* @param y[in] the y-position where the image should be drawn onto the device
|
||||
* @param image[in] the handle of the image to draw
|
||||
* @param hue_fg[in] the foreground hue to use, with 0-360 mapped to 0-255
|
||||
* @param sat_fg[in] the foreground saturation to use, with 0-100% mapped to 0-255
|
||||
* @param val_fg[in] the foreground value to use, with 0-100% mapped to 0-255
|
||||
* @param hue_bg[in] the background hue to use, with 0-360 mapped to 0-255
|
||||
* @param sat_bg[in] the background saturation to use, with 0-100% mapped to 0-255
|
||||
* @param val_bg[in] the background value to use, with 0-100% mapped to 0-255
|
||||
* @return the \ref deferred_token to use with \ref qp_stop_animation in order to stop animating
|
||||
* @return INVALID_DEFERRED_TOKEN if animating the image failed
|
||||
*/
|
||||
deferred_token qp_animate_recolor(painter_device_t device, uint16_t x, uint16_t y, painter_image_handle_t image, uint8_t hue_fg, uint8_t sat_fg, uint8_t val_fg, uint8_t hue_bg, uint8_t sat_bg, uint8_t val_bg);
|
||||
|
||||
/**
|
||||
* Cancels a running animation.
|
||||
*
|
||||
* @param anim_token[in] the animation token returned by \ref qp_animate, or \ref qp_animate_recolor.
|
||||
*/
|
||||
void qp_stop_animation(deferred_token anim_token);
|
||||
|
||||
/**
|
||||
* Loads a font into memory.
|
||||
*
|
||||
* @note Fonts can be unloaded by calling \ref qp_close_font.
|
||||
*
|
||||
* @param buffer[in] the font data to load
|
||||
* @return an image handle usable with \ref qp_textwidth, \ref qp_drawtext, and \ref qp_drawtext_recolor.
|
||||
* @return NULL if loading the font failed
|
||||
*/
|
||||
painter_font_handle_t qp_load_font_mem(const void *buffer);
|
||||
|
||||
/**
|
||||
* Closes a font handle when no longer in use.
|
||||
*
|
||||
* @param font[in] the handle of the font to unload
|
||||
* @return true if unloading the font succeeded
|
||||
* @return false if unloading the font failed
|
||||
*/
|
||||
bool qp_close_font(painter_font_handle_t font);
|
||||
|
||||
/**
|
||||
* Measures the width (in pixels) of the supplied string, given the specified font.
|
||||
*
|
||||
* @param font[in] the handle of the font
|
||||
* @param str[in] the string to measure
|
||||
* @return the width (in pixels) needed to draw the specified string
|
||||
*/
|
||||
int16_t qp_textwidth(painter_font_handle_t font, const char *str);
|
||||
|
||||
/**
|
||||
* Draws text to the display.
|
||||
*
|
||||
* @param device[in] the handle of the device to control
|
||||
* @param x[in] the x-position where the text should be drawn onto the device
|
||||
* @param y[in] the y-position where the text should be drawn onto the device
|
||||
* @param font[in] the handle of the font
|
||||
* @param str[in] the string to draw
|
||||
* @return the width (in pixels) used when drawing the specified string
|
||||
*/
|
||||
int16_t qp_drawtext(painter_device_t device, uint16_t x, uint16_t y, painter_font_handle_t font, const char *str);
|
||||
|
||||
/**
|
||||
* Draws text to the display, recoloring monochrome fonts to the desired foreground/background.
|
||||
*
|
||||
* @param device[in] the handle of the device to control
|
||||
* @param x[in] the x-position where the text should be drawn onto the device
|
||||
* @param y[in] the y-position where the text should be drawn onto the device
|
||||
* @param font[in] the handle of the font
|
||||
* @param str[in] the string to draw
|
||||
* @param hue_fg[in] the foreground hue to use, with 0-360 mapped to 0-255
|
||||
* @param sat_fg[in] the foreground saturation to use, with 0-100% mapped to 0-255
|
||||
* @param val_fg[in] the foreground value to use, with 0-100% mapped to 0-255
|
||||
* @param hue_bg[in] the background hue to use, with 0-360 mapped to 0-255
|
||||
* @param sat_bg[in] the background saturation to use, with 0-100% mapped to 0-255
|
||||
* @param val_bg[in] the background value to use, with 0-100% mapped to 0-255
|
||||
* @return the width (in pixels) used when drawing the specified string
|
||||
*/
|
||||
int16_t qp_drawtext_recolor(painter_device_t device, uint16_t x, uint16_t y, painter_font_handle_t font, const char *str, uint8_t hue_fg, uint8_t sat_fg, uint8_t val_fg, uint8_t hue_bg, uint8_t sat_bg, uint8_t val_bg);
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Quantum Painter Drivers
|
||||
|
||||
#ifdef QUANTUM_PAINTER_ILI9163_ENABLE
|
||||
# include "qp_ili9163.h"
|
||||
#endif // QUANTUM_PAINTER_ILI9163_ENABLE
|
||||
|
||||
#ifdef QUANTUM_PAINTER_ILI9341_ENABLE
|
||||
# include "qp_ili9341.h"
|
||||
#endif // QUANTUM_PAINTER_ILI9341_ENABLE
|
||||
|
||||
#ifdef QUANTUM_PAINTER_ST7789_ENABLE
|
||||
# include "qp_st7789.h"
|
||||
#endif // QUANTUM_PAINTER_ST7789_ENABLE
|
||||
|
||||
#ifdef QUANTUM_PAINTER_GC9A01_ENABLE
|
||||
# include "qp_gc9a01.h"
|
||||
#endif // QUANTUM_PAINTER_GC9A01_ENABLE
|
||||
|
||||
#ifdef QUANTUM_PAINTER_SSD1351_ENABLE
|
||||
# include "qp_ssd1351.h"
|
||||
#endif // QUANTUM_PAINTER_SSD1351_ENABLE
|
72
quantum/painter/qp_comms.c
Normal file
72
quantum/painter/qp_comms.c
Normal file
|
@ -0,0 +1,72 @@
|
|||
// Copyright 2021 Nick Brassel (@tzarc)
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "qp_comms.h"
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Base comms APIs
|
||||
|
||||
bool qp_comms_init(painter_device_t device) {
|
||||
struct painter_driver_t *driver = (struct painter_driver_t *)device;
|
||||
if (!driver->validate_ok) {
|
||||
qp_dprintf("qp_comms_init: fail (validation_ok == false)\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
return driver->comms_vtable->comms_init(device);
|
||||
}
|
||||
|
||||
bool qp_comms_start(painter_device_t device) {
|
||||
struct painter_driver_t *driver = (struct painter_driver_t *)device;
|
||||
if (!driver->validate_ok) {
|
||||
qp_dprintf("qp_comms_start: fail (validation_ok == false)\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
return driver->comms_vtable->comms_start(device);
|
||||
}
|
||||
|
||||
void qp_comms_stop(painter_device_t device) {
|
||||
struct painter_driver_t *driver = (struct painter_driver_t *)device;
|
||||
if (!driver->validate_ok) {
|
||||
qp_dprintf("qp_comms_stop: fail (validation_ok == false)\n");
|
||||
return;
|
||||
}
|
||||
|
||||
driver->comms_vtable->comms_stop(device);
|
||||
}
|
||||
|
||||
uint32_t qp_comms_send(painter_device_t device, const void *data, uint32_t byte_count) {
|
||||
struct painter_driver_t *driver = (struct painter_driver_t *)device;
|
||||
if (!driver->validate_ok) {
|
||||
qp_dprintf("qp_comms_send: fail (validation_ok == false)\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
return driver->comms_vtable->comms_send(device, data, byte_count);
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Comms APIs that use a D/C pin
|
||||
|
||||
void qp_comms_command(painter_device_t device, uint8_t cmd) {
|
||||
struct painter_driver_t * driver = (struct painter_driver_t *)device;
|
||||
struct painter_comms_with_command_vtable_t *comms_vtable = (struct painter_comms_with_command_vtable_t *)driver->comms_vtable;
|
||||
comms_vtable->send_command(device, cmd);
|
||||
}
|
||||
|
||||
void qp_comms_command_databyte(painter_device_t device, uint8_t cmd, uint8_t data) {
|
||||
qp_comms_command(device, cmd);
|
||||
qp_comms_send(device, &data, sizeof(data));
|
||||
}
|
||||
|
||||
uint32_t qp_comms_command_databuf(painter_device_t device, uint8_t cmd, const void *data, uint32_t byte_count) {
|
||||
qp_comms_command(device, cmd);
|
||||
return qp_comms_send(device, data, byte_count);
|
||||
}
|
||||
|
||||
void qp_comms_bulk_command_sequence(painter_device_t device, const uint8_t *sequence, size_t sequence_len) {
|
||||
struct painter_driver_t * driver = (struct painter_driver_t *)device;
|
||||
struct painter_comms_with_command_vtable_t *comms_vtable = (struct painter_comms_with_command_vtable_t *)driver->comms_vtable;
|
||||
comms_vtable->bulk_command_sequence(device, sequence, sequence_len);
|
||||
}
|
25
quantum/painter/qp_comms.h
Normal file
25
quantum/painter/qp_comms.h
Normal file
|
@ -0,0 +1,25 @@
|
|||
// Copyright 2021 Nick Brassel (@tzarc)
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "qp_internal.h"
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Base comms APIs
|
||||
|
||||
bool qp_comms_init(painter_device_t device);
|
||||
bool qp_comms_start(painter_device_t device);
|
||||
void qp_comms_stop(painter_device_t device);
|
||||
uint32_t qp_comms_send(painter_device_t device, const void* data, uint32_t byte_count);
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Comms APIs that use a D/C pin
|
||||
|
||||
void qp_comms_command(painter_device_t device, uint8_t cmd);
|
||||
void qp_comms_command_databyte(painter_device_t device, uint8_t cmd, uint8_t data);
|
||||
uint32_t qp_comms_command_databuf(painter_device_t device, uint8_t cmd, const void* data, uint32_t byte_count);
|
||||
void qp_comms_bulk_command_sequence(painter_device_t device, const uint8_t* sequence, size_t sequence_len);
|
85
quantum/painter/qp_draw.h
Normal file
85
quantum/painter/qp_draw.h
Normal file
|
@ -0,0 +1,85 @@
|
|||
// Copyright 2021 Nick Brassel (@tzarc)
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "qp_internal.h"
|
||||
#include "qp_stream.h"
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Quantum Painter utility functions
|
||||
|
||||
// Global variable used for native pixel data streaming.
|
||||
extern uint8_t qp_internal_global_pixdata_buffer[QUANTUM_PAINTER_PIXDATA_BUFFER_SIZE];
|
||||
|
||||
// Check if the supplied bpp is capable of being rendered
|
||||
bool qp_internal_bpp_capable(uint8_t bits_per_pixel);
|
||||
|
||||
// Returns the number of pixels that can fit in the pixdata buffer
|
||||
uint32_t qp_internal_num_pixels_in_buffer(painter_device_t device);
|
||||
|
||||
// Fills the supplied buffer with equivalent native pixels matching the supplied HSV
|
||||
void qp_internal_fill_pixdata(painter_device_t device, uint32_t num_pixels, uint8_t hue, uint8_t sat, uint8_t val);
|
||||
|
||||
// qp_setpixel internal implementation, but uses the global pixdata buffer with pre-converted native pixel. Only the first pixel is used.
|
||||
bool qp_internal_setpixel_impl(painter_device_t device, uint16_t x, uint16_t y);
|
||||
|
||||
// qp_rect internal implementation, but uses the global pixdata buffer with pre-converted native pixels.
|
||||
bool qp_internal_fillrect_helper_impl(painter_device_t device, uint16_t l, uint16_t t, uint16_t r, uint16_t b);
|
||||
|
||||
// Convert from input pixel data + palette to equivalent pixels
|
||||
typedef int16_t (*qp_internal_byte_input_callback)(void* cb_arg);
|
||||
typedef bool (*qp_internal_pixel_output_callback)(qp_pixel_t* palette, uint8_t index, void* cb_arg);
|
||||
bool qp_internal_decode_palette(painter_device_t device, uint32_t pixel_count, uint8_t bits_per_pixel, qp_internal_byte_input_callback input_callback, void* input_arg, qp_pixel_t* palette, qp_internal_pixel_output_callback output_callback, void* output_arg);
|
||||
bool qp_internal_decode_grayscale(painter_device_t device, uint32_t pixel_count, uint8_t bits_per_pixel, qp_internal_byte_input_callback input_callback, void* input_arg, qp_internal_pixel_output_callback output_callback, void* output_arg);
|
||||
bool qp_internal_decode_recolor(painter_device_t device, uint32_t pixel_count, uint8_t bits_per_pixel, qp_internal_byte_input_callback input_callback, void* input_arg, qp_pixel_t fg_hsv888, qp_pixel_t bg_hsv888, qp_internal_pixel_output_callback output_callback, void* output_arg);
|
||||
|
||||
// Global variable used for interpolated pixel lookup table.
|
||||
#if QUANTUM_PAINTER_SUPPORTS_256_PALETTE
|
||||
extern qp_pixel_t qp_internal_global_pixel_lookup_table[256];
|
||||
#else
|
||||
extern qp_pixel_t qp_internal_global_pixel_lookup_table[16];
|
||||
#endif
|
||||
|
||||
// Generates a color-interpolated lookup table based off the number of items, from foreground to background, for use with monochrome image rendering.
|
||||
// Returns true if a palette was created, false if the palette is reused.
|
||||
// As this uses a global, this may present a problem if using the same parameters but a different screen converts pixels -- use qp_internal_invalidate_palette() below to reset.
|
||||
bool qp_internal_interpolate_palette(qp_pixel_t fg_hsv888, qp_pixel_t bg_hsv888, int16_t steps);
|
||||
|
||||
// Resets the global palette so that it can be regenerated. Only needed if the colors are identical, but a different display is used with a different internal pixel format.
|
||||
void qp_internal_invalidate_palette(void);
|
||||
|
||||
// Helper shared between image and font rendering -- sets up the global palette to match the palette block specified in the asset. Expects the stream to be positioned at the start of the block header.
|
||||
bool qp_internal_load_qgf_palette(qp_stream_t* stream, uint8_t bpp);
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Quantum Painter codec functions
|
||||
|
||||
enum qp_internal_rle_mode_t {
|
||||
MARKER_BYTE,
|
||||
REPEATING_RUN,
|
||||
NON_REPEATING_RUN,
|
||||
};
|
||||
|
||||
struct qp_internal_byte_input_state {
|
||||
painter_device_t device;
|
||||
qp_stream_t* src_stream;
|
||||
int16_t curr;
|
||||
union {
|
||||
// RLE-specific
|
||||
struct {
|
||||
enum qp_internal_rle_mode_t mode;
|
||||
uint8_t remain; // number of bytes remaining in the current mode
|
||||
} rle;
|
||||
};
|
||||
};
|
||||
|
||||
struct qp_internal_pixel_output_state {
|
||||
painter_device_t device;
|
||||
uint32_t pixel_write_pos;
|
||||
uint32_t max_pixels;
|
||||
};
|
||||
|
||||
bool qp_internal_pixel_appender(qp_pixel_t* palette, uint8_t index, void* cb_arg);
|
||||
|
||||
qp_internal_byte_input_callback qp_internal_prepare_input_state(struct qp_internal_byte_input_state* input_state, painter_compression_t compression);
|
172
quantum/painter/qp_draw_circle.c
Normal file
172
quantum/painter/qp_draw_circle.c
Normal file
|
@ -0,0 +1,172 @@
|
|||
// Copyright 2021 Paul Cotter (@gr1mr3aver)
|
||||
// Copyright 2021 Nick Brassel (@tzarc)
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "qp.h"
|
||||
#include "qp_internal.h"
|
||||
#include "qp_comms.h"
|
||||
#include "qp_draw.h"
|
||||
|
||||
// Utilize 8-way symmetry to draw circles
|
||||
static bool qp_circle_helper_impl(painter_device_t device, uint16_t centerx, uint16_t centery, uint16_t offsetx, uint16_t offsety, bool filled) {
|
||||
/*
|
||||
Circles have the property of 8-way symmetry, so eight pixels can be drawn
|
||||
for each computed [offsetx,offsety] given the center coordinates
|
||||
represented by [centerx,centery].
|
||||
|
||||
For filled circles, we can draw horizontal lines between each pair of
|
||||
pixels with the same final value of y.
|
||||
|
||||
Two special cases exist and have been optimized:
|
||||
1) offsetx == offsety (the final point), makes half the coordinates
|
||||
equivalent, so we can omit them (and the corresponding fill lines)
|
||||
2) offsetx == 0 (the starting point) means that some horizontal lines
|
||||
would be a single pixel in length, so we write individual pixels instead.
|
||||
This also makes half the symmetrical points identical to their twins,
|
||||
so we only need four points or two points and one line
|
||||
*/
|
||||
|
||||
int16_t xpx = ((int16_t)centerx) + ((int16_t)offsetx);
|
||||
int16_t xmx = ((int16_t)centerx) - ((int16_t)offsetx);
|
||||
int16_t xpy = ((int16_t)centerx) + ((int16_t)offsety);
|
||||
int16_t xmy = ((int16_t)centerx) - ((int16_t)offsety);
|
||||
int16_t ypx = ((int16_t)centery) + ((int16_t)offsetx);
|
||||
int16_t ymx = ((int16_t)centery) - ((int16_t)offsetx);
|
||||
int16_t ypy = ((int16_t)centery) + ((int16_t)offsety);
|
||||
int16_t ymy = ((int16_t)centery) - ((int16_t)offsety);
|
||||
|
||||
if (offsetx == 0) {
|
||||
if (!qp_internal_setpixel_impl(device, centerx, ypy)) {
|
||||
return false;
|
||||
}
|
||||
if (!qp_internal_setpixel_impl(device, centerx, ymy)) {
|
||||
return false;
|
||||
}
|
||||
if (filled) {
|
||||
if (!qp_internal_fillrect_helper_impl(device, xpy, centery, xmy, centery)) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
if (!qp_internal_setpixel_impl(device, xpy, centery)) {
|
||||
return false;
|
||||
}
|
||||
if (!qp_internal_setpixel_impl(device, xmy, centery)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
} else if (offsetx == offsety) {
|
||||
if (filled) {
|
||||
if (!qp_internal_fillrect_helper_impl(device, xpy, ypy, xmy, ypy)) {
|
||||
return false;
|
||||
}
|
||||
if (!qp_internal_fillrect_helper_impl(device, xpy, ymy, xmy, ymy)) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
if (!qp_internal_setpixel_impl(device, xpy, ypy)) {
|
||||
return false;
|
||||
}
|
||||
if (!qp_internal_setpixel_impl(device, xmy, ypy)) {
|
||||
return false;
|
||||
}
|
||||
if (!qp_internal_setpixel_impl(device, xpy, ymy)) {
|
||||
return false;
|
||||
}
|
||||
if (!qp_internal_setpixel_impl(device, xmy, ymy)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
if (filled) {
|
||||
if (!qp_internal_fillrect_helper_impl(device, xpx, ypy, xmx, ypy)) {
|
||||
return false;
|
||||
}
|
||||
if (!qp_internal_fillrect_helper_impl(device, xpx, ymy, xmx, ymy)) {
|
||||
return false;
|
||||
}
|
||||
if (!qp_internal_fillrect_helper_impl(device, xpy, ypx, xmy, ypx)) {
|
||||
return false;
|
||||
}
|
||||
if (!qp_internal_fillrect_helper_impl(device, xpy, ymx, xmy, ymx)) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
if (!qp_internal_setpixel_impl(device, xpx, ypy)) {
|
||||
return false;
|
||||
}
|
||||
if (!qp_internal_setpixel_impl(device, xmx, ypy)) {
|
||||
return false;
|
||||
}
|
||||
if (!qp_internal_setpixel_impl(device, xpx, ymy)) {
|
||||
return false;
|
||||
}
|
||||
if (!qp_internal_setpixel_impl(device, xmx, ymy)) {
|
||||
return false;
|
||||
}
|
||||
if (!qp_internal_setpixel_impl(device, xpy, ypx)) {
|
||||
return false;
|
||||
}
|
||||
if (!qp_internal_setpixel_impl(device, xmy, ypx)) {
|
||||
return false;
|
||||
}
|
||||
if (!qp_internal_setpixel_impl(device, xpy, ymx)) {
|
||||
return false;
|
||||
}
|
||||
if (!qp_internal_setpixel_impl(device, xmy, ymx)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Quantum Painter External API: qp_circle
|
||||
|
||||
bool qp_circle(painter_device_t device, uint16_t x, uint16_t y, uint16_t radius, uint8_t hue, uint8_t sat, uint8_t val, bool filled) {
|
||||
qp_dprintf("qp_circle: entry\n");
|
||||
struct painter_driver_t *driver = (struct painter_driver_t *)device;
|
||||
if (!driver->validate_ok) {
|
||||
qp_dprintf("qp_circle: fail (validation_ok == false)\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
// plot the initial set of points for x, y and r
|
||||
int16_t xcalc = 0;
|
||||
int16_t ycalc = (int16_t)radius;
|
||||
int16_t err = ((5 - (radius >> 2)) >> 2);
|
||||
|
||||
qp_internal_fill_pixdata(device, (radius * 2) + 1, hue, sat, val);
|
||||
|
||||
if (!qp_comms_start(device)) {
|
||||
qp_dprintf("qp_circle: fail (could not start comms)\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ret = true;
|
||||
if (!qp_circle_helper_impl(device, x, y, xcalc, ycalc, filled)) {
|
||||
ret = false;
|
||||
}
|
||||
|
||||
if (ret) {
|
||||
while (xcalc < ycalc) {
|
||||
xcalc++;
|
||||
if (err < 0) {
|
||||
err += (xcalc << 1) + 1;
|
||||
} else {
|
||||
ycalc--;
|
||||
err += ((xcalc - ycalc) << 1) + 1;
|
||||
}
|
||||
if (!qp_circle_helper_impl(device, x, y, xcalc, ycalc, filled)) {
|
||||
ret = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
qp_dprintf("qp_circle: %s\n", ret ? "ok" : "fail");
|
||||
qp_comms_stop(device);
|
||||
return ret;
|
||||
}
|
142
quantum/painter/qp_draw_codec.c
Normal file
142
quantum/painter/qp_draw_codec.c
Normal file
|
@ -0,0 +1,142 @@
|
|||
// Copyright 2021 Nick Brassel (@tzarc)
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "qp_internal.h"
|
||||
#include "qp_draw.h"
|
||||
#include "qp_comms.h"
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Palette / Monochrome-format decoder
|
||||
|
||||
static const qp_pixel_t qp_pixel_white = {.hsv888 = {.h = 0, .s = 0, .v = 255}};
|
||||
static const qp_pixel_t qp_pixel_black = {.hsv888 = {.h = 0, .s = 0, .v = 0}};
|
||||
|
||||
bool qp_internal_bpp_capable(uint8_t bits_per_pixel) {
|
||||
#if !(QUANTUM_PAINTER_SUPPORTS_256_PALETTE)
|
||||
if (bits_per_pixel > 4) {
|
||||
qp_dprintf("qp_internal_decode_palette: image bpp greater than 4\n");
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
|
||||
if (bits_per_pixel > 8) {
|
||||
qp_dprintf("qp_internal_decode_palette: image bpp greater than 8\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool qp_internal_decode_palette(painter_device_t device, uint32_t pixel_count, uint8_t bits_per_pixel, qp_internal_byte_input_callback input_callback, void* input_arg, qp_pixel_t* palette, qp_internal_pixel_output_callback output_callback, void* output_arg) {
|
||||
const uint8_t pixel_bitmask = (1 << bits_per_pixel) - 1;
|
||||
const uint8_t pixels_per_byte = 8 / bits_per_pixel;
|
||||
uint32_t remaining_pixels = pixel_count; // don't try to derive from byte_count, we may not use an entire byte
|
||||
while (remaining_pixels > 0) {
|
||||
uint8_t byteval = input_callback(input_arg);
|
||||
if (byteval < 0) {
|
||||
return false;
|
||||
}
|
||||
uint8_t loop_pixels = remaining_pixels < pixels_per_byte ? remaining_pixels : pixels_per_byte;
|
||||
for (uint8_t q = 0; q < loop_pixels; ++q) {
|
||||
if (!output_callback(palette, byteval & pixel_bitmask, output_arg)) {
|
||||
return false;
|
||||
}
|
||||
byteval >>= bits_per_pixel;
|
||||
}
|
||||
remaining_pixels -= loop_pixels;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool qp_internal_decode_grayscale(painter_device_t device, uint32_t pixel_count, uint8_t bits_per_pixel, qp_internal_byte_input_callback input_callback, void* input_arg, qp_internal_pixel_output_callback output_callback, void* output_arg) {
|
||||
return qp_internal_decode_recolor(device, pixel_count, bits_per_pixel, input_callback, input_arg, qp_pixel_white, qp_pixel_black, output_callback, output_arg);
|
||||
}
|
||||
|
||||
bool qp_internal_decode_recolor(painter_device_t device, uint32_t pixel_count, uint8_t bits_per_pixel, qp_internal_byte_input_callback input_callback, void* input_arg, qp_pixel_t fg_hsv888, qp_pixel_t bg_hsv888, qp_internal_pixel_output_callback output_callback, void* output_arg) {
|
||||
struct painter_driver_t* driver = (struct painter_driver_t*)device;
|
||||
int16_t steps = 1 << bits_per_pixel; // number of items we need to interpolate
|
||||
if (qp_internal_interpolate_palette(fg_hsv888, bg_hsv888, steps)) {
|
||||
if (!driver->driver_vtable->palette_convert(device, steps, qp_internal_global_pixel_lookup_table)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return qp_internal_decode_palette(device, pixel_count, bits_per_pixel, input_callback, input_arg, qp_internal_global_pixel_lookup_table, output_callback, output_arg);
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Progressive pull of bytes, push of pixels
|
||||
|
||||
static inline int16_t qp_drawimage_byte_uncompressed_decoder(void* cb_arg) {
|
||||
struct qp_internal_byte_input_state* state = (struct qp_internal_byte_input_state*)cb_arg;
|
||||
state->curr = qp_stream_get(state->src_stream);
|
||||
return state->curr;
|
||||
}
|
||||
|
||||
static inline int16_t qp_drawimage_byte_rle_decoder(void* cb_arg) {
|
||||
struct qp_internal_byte_input_state* state = (struct qp_internal_byte_input_state*)cb_arg;
|
||||
|
||||
// Work out if we're parsing the initial marker byte
|
||||
if (state->rle.mode == MARKER_BYTE) {
|
||||
uint8_t c = qp_stream_get(state->src_stream);
|
||||
if (c >= 128) {
|
||||
state->rle.mode = NON_REPEATING_RUN; // non-repeated run
|
||||
state->rle.remain = c - 127;
|
||||
} else {
|
||||
state->rle.mode = REPEATING_RUN; // repeated run
|
||||
state->rle.remain = c;
|
||||
}
|
||||
|
||||
state->curr = qp_stream_get(state->src_stream);
|
||||
}
|
||||
|
||||
// Work out which byte we're returning
|
||||
uint8_t c = state->curr;
|
||||
|
||||
// Decrement the counter of the bytes remaining
|
||||
state->rle.remain--;
|
||||
|
||||
if (state->rle.remain > 0) {
|
||||
// If we're in a non-repeating run, queue up the next byte
|
||||
if (state->rle.mode == NON_REPEATING_RUN) {
|
||||
state->curr = qp_stream_get(state->src_stream);
|
||||
}
|
||||
} else {
|
||||
// Swap back to querying the marker byte mode
|
||||
state->rle.mode = MARKER_BYTE;
|
||||
}
|
||||
|
||||
return c;
|
||||
}
|
||||
|
||||
bool qp_internal_pixel_appender(qp_pixel_t* palette, uint8_t index, void* cb_arg) {
|
||||
struct qp_internal_pixel_output_state* state = (struct qp_internal_pixel_output_state*)cb_arg;
|
||||
struct painter_driver_t* driver = (struct painter_driver_t*)state->device;
|
||||
|
||||
if (!driver->driver_vtable->append_pixels(state->device, qp_internal_global_pixdata_buffer, palette, state->pixel_write_pos++, 1, &index)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// If we've hit the transmit limit, send out the entire buffer and reset the write position
|
||||
if (state->pixel_write_pos == state->max_pixels) {
|
||||
if (!driver->driver_vtable->pixdata(state->device, qp_internal_global_pixdata_buffer, state->pixel_write_pos)) {
|
||||
return false;
|
||||
}
|
||||
state->pixel_write_pos = 0;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
qp_internal_byte_input_callback qp_internal_prepare_input_state(struct qp_internal_byte_input_state* input_state, painter_compression_t compression) {
|
||||
switch (compression) {
|
||||
case IMAGE_UNCOMPRESSED:
|
||||
return qp_drawimage_byte_uncompressed_decoder;
|
||||
case IMAGE_COMPRESSED_RLE:
|
||||
input_state->rle.mode = MARKER_BYTE;
|
||||
input_state->rle.remain = 0;
|
||||
return qp_drawimage_byte_rle_decoder;
|
||||
default:
|
||||
return NULL;
|
||||
}
|
||||
}
|
294
quantum/painter/qp_draw_core.c
Normal file
294
quantum/painter/qp_draw_core.c
Normal file
|
@ -0,0 +1,294 @@
|
|||
// Copyright 2021-2022 Nick Brassel (@tzarc)
|
||||
// Copyright 2021 Paul Cotter (@gr1mr3aver)
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "qp_internal.h"
|
||||
#include "qp_comms.h"
|
||||
#include "qp_draw.h"
|
||||
#include "qgf.h"
|
||||
|
||||
_Static_assert((QUANTUM_PAINTER_PIXDATA_BUFFER_SIZE > 0) && (QUANTUM_PAINTER_PIXDATA_BUFFER_SIZE % 16) == 0, "QUANTUM_PAINTER_PIXDATA_BUFFER_SIZE needs to be a non-zero multiple of 16");
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Global variables
|
||||
//
|
||||
// NOTE: The variables in this section are intentionally outside a stack frame. They are able to be defined with larger
|
||||
// sizes than the normal stack frames would allow, and as such need to be external.
|
||||
//
|
||||
// **** DO NOT refactor this and decide to place the variables inside the function calling them -- you will ****
|
||||
// **** very likely get artifacts rendered to the screen as a result. ****
|
||||
//
|
||||
|
||||
// Buffer used for transmitting native pixel data to the downstream device.
|
||||
uint8_t qp_internal_global_pixdata_buffer[QUANTUM_PAINTER_PIXDATA_BUFFER_SIZE];
|
||||
|
||||
// Static buffer to contain a generated color palette
|
||||
static bool generated_palette = false;
|
||||
static int16_t generated_steps = -1;
|
||||
static qp_pixel_t interpolated_fg_hsv888;
|
||||
static qp_pixel_t interpolated_bg_hsv888;
|
||||
#if QUANTUM_PAINTER_SUPPORTS_256_PALETTE
|
||||
qp_pixel_t qp_internal_global_pixel_lookup_table[256];
|
||||
#else
|
||||
qp_pixel_t qp_internal_global_pixel_lookup_table[16];
|
||||
#endif
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Helpers
|
||||
|
||||
uint32_t qp_internal_num_pixels_in_buffer(painter_device_t device) {
|
||||
struct painter_driver_t *driver = (struct painter_driver_t *)device;
|
||||
return ((QUANTUM_PAINTER_PIXDATA_BUFFER_SIZE * 8) / driver->native_bits_per_pixel);
|
||||
}
|
||||
|
||||
// qp_setpixel internal implementation, but accepts a buffer with pre-converted native pixel. Only the first pixel is used.
|
||||
bool qp_internal_setpixel_impl(painter_device_t device, uint16_t x, uint16_t y) {
|
||||
struct painter_driver_t *driver = (struct painter_driver_t *)device;
|
||||
return driver->driver_vtable->viewport(device, x, y, x, y) && driver->driver_vtable->pixdata(device, qp_internal_global_pixdata_buffer, 1);
|
||||
}
|
||||
|
||||
// Fills the global native pixel buffer with equivalent pixels matching the supplied HSV
|
||||
void qp_internal_fill_pixdata(painter_device_t device, uint32_t num_pixels, uint8_t hue, uint8_t sat, uint8_t val) {
|
||||
struct painter_driver_t *driver = (struct painter_driver_t *)device;
|
||||
uint32_t pixels_in_pixdata = qp_internal_num_pixels_in_buffer(device);
|
||||
num_pixels = QP_MIN(pixels_in_pixdata, num_pixels);
|
||||
|
||||
// Convert the color to native pixel format
|
||||
qp_pixel_t color = {.hsv888 = {.h = hue, .s = sat, .v = val}};
|
||||
driver->driver_vtable->palette_convert(device, 1, &color);
|
||||
|
||||
// Append the required number of pixels
|
||||
uint8_t palette_idx = 0;
|
||||
for (uint32_t i = 0; i < num_pixels; ++i) {
|
||||
driver->driver_vtable->append_pixels(device, qp_internal_global_pixdata_buffer, &color, i, 1, &palette_idx);
|
||||
}
|
||||
}
|
||||
|
||||
// Resets the global palette so that it can be regenerated. Only needed if the colors are identical, but a different display is used with a different internal pixel format.
|
||||
void qp_internal_invalidate_palette(void) {
|
||||
generated_palette = false;
|
||||
generated_steps = -1;
|
||||
}
|
||||
|
||||
// Interpolates between two colors to generate a palette
|
||||
bool qp_internal_interpolate_palette(qp_pixel_t fg_hsv888, qp_pixel_t bg_hsv888, int16_t steps) {
|
||||
// Check if we need to generate a new palette -- if the input parameters match then assume the palette can stay unchanged.
|
||||
// This may present a problem if using the same parameters but a different screen converts pixels -- use qp_internal_invalidate_palette() to reset.
|
||||
if (generated_palette == true && generated_steps == steps && memcmp(&interpolated_fg_hsv888, &fg_hsv888, sizeof(fg_hsv888)) == 0 && memcmp(&interpolated_bg_hsv888, &bg_hsv888, sizeof(bg_hsv888)) == 0) {
|
||||
// We already have the correct palette, no point regenerating it.
|
||||
return false;
|
||||
}
|
||||
|
||||
// Save the parameters so we know whether we can skip generation
|
||||
generated_palette = true;
|
||||
generated_steps = steps;
|
||||
interpolated_fg_hsv888 = fg_hsv888;
|
||||
interpolated_bg_hsv888 = bg_hsv888;
|
||||
|
||||
int16_t hue_fg = fg_hsv888.hsv888.h;
|
||||
int16_t hue_bg = bg_hsv888.hsv888.h;
|
||||
|
||||
// Make sure we take the "shortest" route from one hue to the other
|
||||
if ((hue_fg - hue_bg) >= 128) {
|
||||
hue_bg += 256;
|
||||
} else if ((hue_fg - hue_bg) <= -128) {
|
||||
hue_bg -= 256;
|
||||
}
|
||||
|
||||
// Interpolate each of the lookup table entries
|
||||
for (int16_t i = 0; i < steps; ++i) {
|
||||
qp_internal_global_pixel_lookup_table[i].hsv888.h = (uint8_t)((hue_fg - hue_bg) * i / (steps - 1) + hue_bg);
|
||||
qp_internal_global_pixel_lookup_table[i].hsv888.s = (uint8_t)((fg_hsv888.hsv888.s - bg_hsv888.hsv888.s) * i / (steps - 1) + bg_hsv888.hsv888.s);
|
||||
qp_internal_global_pixel_lookup_table[i].hsv888.v = (uint8_t)((fg_hsv888.hsv888.v - bg_hsv888.hsv888.v) * i / (steps - 1) + bg_hsv888.hsv888.v);
|
||||
|
||||
qp_dprintf("qp_internal_interpolate_palette: %3d of %d -- H: %3d, S: %3d, V: %3d\n", (int)(i + 1), (int)steps, (int)qp_internal_global_pixel_lookup_table[i].hsv888.h, (int)qp_internal_global_pixel_lookup_table[i].hsv888.s, (int)qp_internal_global_pixel_lookup_table[i].hsv888.v);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Helper shared between image and font rendering -- sets up the global palette to match the palette block specified in the asset. Expects the stream to be positioned at the start of the block header.
|
||||
bool qp_internal_load_qgf_palette(qp_stream_t *stream, uint8_t bpp) {
|
||||
qgf_palette_v1_t palette_descriptor;
|
||||
if (qp_stream_read(&palette_descriptor, sizeof(qgf_palette_v1_t), 1, stream) != 1) {
|
||||
qp_dprintf("Failed to read palette_descriptor, expected length was not %d\n", (int)sizeof(qgf_palette_v1_t));
|
||||
return false;
|
||||
}
|
||||
|
||||
// BPP determines the number of palette entries, each entry is a HSV888 triplet.
|
||||
const uint16_t palette_entries = 1u << bpp;
|
||||
|
||||
// Ensure we aren't reusing any palette
|
||||
qp_internal_invalidate_palette();
|
||||
|
||||
// Read the palette entries
|
||||
for (uint16_t i = 0; i < palette_entries; ++i) {
|
||||
// Read the palette entry
|
||||
qgf_palette_entry_v1_t entry;
|
||||
if (qp_stream_read(&entry, sizeof(qgf_palette_entry_v1_t), 1, stream) != 1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Update the lookup table
|
||||
qp_internal_global_pixel_lookup_table[i].hsv888.h = entry.h;
|
||||
qp_internal_global_pixel_lookup_table[i].hsv888.s = entry.s;
|
||||
qp_internal_global_pixel_lookup_table[i].hsv888.v = entry.v;
|
||||
|
||||
qp_dprintf("qp_internal_load_qgf_palette: %3d of %d -- H: %3d, S: %3d, V: %3d\n", (int)(i + 1), (int)palette_entries, (int)qp_internal_global_pixel_lookup_table[i].hsv888.h, (int)qp_internal_global_pixel_lookup_table[i].hsv888.s, (int)qp_internal_global_pixel_lookup_table[i].hsv888.v);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Quantum Painter External API: qp_setpixel
|
||||
|
||||
bool qp_setpixel(painter_device_t device, uint16_t x, uint16_t y, uint8_t hue, uint8_t sat, uint8_t val) {
|
||||
struct painter_driver_t *driver = (struct painter_driver_t *)device;
|
||||
if (!driver->validate_ok) {
|
||||
qp_dprintf("qp_setpixel: fail (validation_ok == false)\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!qp_comms_start(device)) {
|
||||
qp_dprintf("Failed to start comms in qp_setpixel\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
qp_internal_fill_pixdata(device, 1, hue, sat, val);
|
||||
bool ret = qp_internal_setpixel_impl(device, x, y);
|
||||
qp_comms_stop(device);
|
||||
qp_dprintf("qp_setpixel: %s\n", ret ? "ok" : "fail");
|
||||
return ret;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Quantum Painter External API: qp_line
|
||||
|
||||
bool qp_line(painter_device_t device, uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1, uint8_t hue, uint8_t sat, uint8_t val) {
|
||||
if (x0 == x1 || y0 == y1) {
|
||||
qp_dprintf("qp_line(%d, %d, %d, %d): entry (deferring to qp_rect)\n", (int)x0, (int)y0, (int)x1, (int)y1);
|
||||
bool ret = qp_rect(device, x0, y0, x1, y1, hue, sat, val, true);
|
||||
qp_dprintf("qp_line(%d, %d, %d, %d): %s (deferred to qp_rect)\n", (int)x0, (int)y0, (int)x1, (int)y1, ret ? "ok" : "fail");
|
||||
return ret;
|
||||
}
|
||||
|
||||
qp_dprintf("qp_line(%d, %d, %d, %d): entry\n", (int)x0, (int)y0, (int)x1, (int)y1);
|
||||
struct painter_driver_t *driver = (struct painter_driver_t *)device;
|
||||
if (!driver->validate_ok) {
|
||||
qp_dprintf("qp_line: fail (validation_ok == false)\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!qp_comms_start(device)) {
|
||||
qp_dprintf("Failed to start comms in qp_line\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
qp_internal_fill_pixdata(device, 1, hue, sat, val);
|
||||
|
||||
// draw angled line using Bresenham's algo
|
||||
int16_t x = ((int16_t)x0);
|
||||
int16_t y = ((int16_t)y0);
|
||||
int16_t slopex = ((int16_t)x0) < ((int16_t)x1) ? 1 : -1;
|
||||
int16_t slopey = ((int16_t)y0) < ((int16_t)y1) ? 1 : -1;
|
||||
int16_t dx = abs(((int16_t)x1) - ((int16_t)x0));
|
||||
int16_t dy = -abs(((int16_t)y1) - ((int16_t)y0));
|
||||
|
||||
int16_t e = dx + dy;
|
||||
int16_t e2 = 2 * e;
|
||||
|
||||
bool ret = true;
|
||||
while (x != x1 || y != y1) {
|
||||
if (!qp_internal_setpixel_impl(device, x, y)) {
|
||||
ret = false;
|
||||
break;
|
||||
}
|
||||
e2 = 2 * e;
|
||||
if (e2 >= dy) {
|
||||
e += dy;
|
||||
x += slopex;
|
||||
}
|
||||
if (e2 <= dx) {
|
||||
e += dx;
|
||||
y += slopey;
|
||||
}
|
||||
}
|
||||
// draw the last pixel
|
||||
if (!qp_internal_setpixel_impl(device, x, y)) {
|
||||
ret = false;
|
||||
}
|
||||
|
||||
qp_comms_stop(device);
|
||||
qp_dprintf("qp_line(%d, %d, %d, %d): %s\n", (int)x0, (int)y0, (int)x1, (int)y1, ret ? "ok" : "fail");
|
||||
return ret;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Quantum Painter External API: qp_rect
|
||||
|
||||
bool qp_internal_fillrect_helper_impl(painter_device_t device, uint16_t left, uint16_t top, uint16_t right, uint16_t bottom) {
|
||||
uint32_t pixels_in_pixdata = qp_internal_num_pixels_in_buffer(device);
|
||||
struct painter_driver_t *driver = (struct painter_driver_t *)device;
|
||||
|
||||
uint16_t l = QP_MIN(left, right);
|
||||
uint16_t r = QP_MAX(left, right);
|
||||
uint16_t t = QP_MIN(top, bottom);
|
||||
uint16_t b = QP_MAX(top, bottom);
|
||||
uint16_t w = r - l + 1;
|
||||
uint16_t h = b - t + 1;
|
||||
|
||||
uint32_t remaining = w * h;
|
||||
driver->driver_vtable->viewport(device, l, t, r, b);
|
||||
while (remaining > 0) {
|
||||
uint32_t transmit = QP_MIN(remaining, pixels_in_pixdata);
|
||||
if (!driver->driver_vtable->pixdata(device, qp_internal_global_pixdata_buffer, transmit)) {
|
||||
return false;
|
||||
}
|
||||
remaining -= transmit;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool qp_rect(painter_device_t device, uint16_t left, uint16_t top, uint16_t right, uint16_t bottom, uint8_t hue, uint8_t sat, uint8_t val, bool filled) {
|
||||
qp_dprintf("qp_rect(%d, %d, %d, %d): entry\n", (int)left, (int)top, (int)right, (int)bottom);
|
||||
struct painter_driver_t *driver = (struct painter_driver_t *)device;
|
||||
if (!driver->validate_ok) {
|
||||
qp_dprintf("qp_rect: fail (validation_ok == false)\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Cater for cases where people have submitted the coordinates backwards
|
||||
uint16_t l = QP_MIN(left, right);
|
||||
uint16_t r = QP_MAX(left, right);
|
||||
uint16_t t = QP_MIN(top, bottom);
|
||||
uint16_t b = QP_MAX(top, bottom);
|
||||
uint16_t w = r - l + 1;
|
||||
uint16_t h = b - t + 1;
|
||||
|
||||
bool ret = true;
|
||||
if (!qp_comms_start(device)) {
|
||||
qp_dprintf("Failed to start comms in qp_rect\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (filled) {
|
||||
// Fill up the pixdata buffer with the required number of native pixels
|
||||
qp_internal_fill_pixdata(device, w * h, hue, sat, val);
|
||||
|
||||
// Perform the draw
|
||||
ret = qp_internal_fillrect_helper_impl(device, l, t, r, b);
|
||||
} else {
|
||||
// Fill up the pixdata buffer with the required number of native pixels
|
||||
qp_internal_fill_pixdata(device, QP_MAX(w, h), hue, sat, val);
|
||||
|
||||
// Draw 4x filled single-width rects to create an outline
|
||||
if (!qp_internal_fillrect_helper_impl(device, l, t, r, t) || !qp_internal_fillrect_helper_impl(device, l, b, r, b) || !qp_internal_fillrect_helper_impl(device, l, t + 1, l, b - 1) || !qp_internal_fillrect_helper_impl(device, r, t + 1, r, b - 1)) {
|
||||
ret = false;
|
||||
}
|
||||
}
|
||||
|
||||
qp_comms_stop(device);
|
||||
qp_dprintf("qp_rect(%d, %d, %d, %d): %s\n", (int)l, (int)t, (int)r, (int)b, ret ? "ok" : "fail");
|
||||
return ret;
|
||||
}
|
116
quantum/painter/qp_draw_ellipse.c
Normal file
116
quantum/painter/qp_draw_ellipse.c
Normal file
|
@ -0,0 +1,116 @@
|
|||
// Copyright 2021 Paul Cotter (@gr1mr3aver)
|
||||
// Copyright 2021 Nick Brassel (@tzarc)
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "qp_internal.h"
|
||||
#include "qp_comms.h"
|
||||
#include "qp_draw.h"
|
||||
|
||||
// Utilize 4-way symmetry to draw an ellipse
|
||||
static bool qp_ellipse_helper_impl(painter_device_t device, uint16_t centerx, uint16_t centery, uint16_t offsetx, uint16_t offsety, bool filled) {
|
||||
/*
|
||||
Ellipses have the property of 4-way symmetry, so four pixels can be drawn
|
||||
for each computed [offsetx,offsety] given the center coordinates
|
||||
represented by [centerx,centery].
|
||||
|
||||
For filled ellipses, we can draw horizontal lines between each pair of
|
||||
pixels with the same final value of y.
|
||||
|
||||
When offsetx == 0 only two pixels can be drawn for filled or unfilled ellipses
|
||||
*/
|
||||
|
||||
int16_t xpx = ((int16_t)centerx) + ((int16_t)offsetx);
|
||||
int16_t xmx = ((int16_t)centerx) - ((int16_t)offsetx);
|
||||
int16_t ypy = ((int16_t)centery) + ((int16_t)offsety);
|
||||
int16_t ymy = ((int16_t)centery) - ((int16_t)offsety);
|
||||
|
||||
if (offsetx == 0) {
|
||||
if (!qp_internal_setpixel_impl(device, xpx, ypy)) {
|
||||
return false;
|
||||
}
|
||||
if (!qp_internal_setpixel_impl(device, xpx, ymy)) {
|
||||
return false;
|
||||
}
|
||||
} else if (filled) {
|
||||
if (!qp_internal_fillrect_helper_impl(device, xpx, ypy, xmx, ypy)) {
|
||||
return false;
|
||||
}
|
||||
if (offsety > 0 && !qp_internal_fillrect_helper_impl(device, xpx, ymy, xmx, ymy)) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
if (!qp_internal_setpixel_impl(device, xpx, ypy)) {
|
||||
return false;
|
||||
}
|
||||
if (!qp_internal_setpixel_impl(device, xpx, ymy)) {
|
||||
return false;
|
||||
}
|
||||
if (!qp_internal_setpixel_impl(device, xmx, ypy)) {
|
||||
return false;
|
||||
}
|
||||
if (!qp_internal_setpixel_impl(device, xmx, ymy)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Quantum Painter External API: qp_ellipse
|
||||
|
||||
bool qp_ellipse(painter_device_t device, uint16_t x, uint16_t y, uint16_t sizex, uint16_t sizey, uint8_t hue, uint8_t sat, uint8_t val, bool filled) {
|
||||
qp_dprintf("qp_ellipse: entry\n");
|
||||
struct painter_driver_t *driver = (struct painter_driver_t *)device;
|
||||
if (!driver->validate_ok) {
|
||||
qp_dprintf("qp_ellipse: fail (validation_ok == false)\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
int16_t aa = ((int16_t)sizex) * ((int16_t)sizex);
|
||||
int16_t bb = ((int16_t)sizey) * ((int16_t)sizey);
|
||||
int16_t fa = 4 * ((int16_t)aa);
|
||||
int16_t fb = 4 * ((int16_t)bb);
|
||||
|
||||
int16_t dx = 0;
|
||||
int16_t dy = ((int16_t)sizey);
|
||||
|
||||
qp_internal_fill_pixdata(device, QP_MAX(sizex, sizey), hue, sat, val);
|
||||
|
||||
if (!qp_comms_start(device)) {
|
||||
qp_dprintf("qp_ellipse: fail (could not start comms)\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ret = true;
|
||||
for (int16_t delta = (2 * bb) + (aa * (1 - (2 * sizey))); bb * dx <= aa * dy; dx++) {
|
||||
if (!qp_ellipse_helper_impl(device, x, y, dx, dy, filled)) {
|
||||
ret = false;
|
||||
break;
|
||||
}
|
||||
if (delta >= 0) {
|
||||
delta += fa * (1 - dy);
|
||||
dy--;
|
||||
}
|
||||
delta += bb * (4 * dx + 6);
|
||||
}
|
||||
|
||||
dx = sizex;
|
||||
dy = 0;
|
||||
|
||||
for (int16_t delta = (2 * aa) + (bb * (1 - (2 * sizex))); aa * dy <= bb * dx; dy++) {
|
||||
if (!qp_ellipse_helper_impl(device, x, y, dx, dy, filled)) {
|
||||
ret = false;
|
||||
break;
|
||||
}
|
||||
if (delta >= 0) {
|
||||
delta += fb * (1 - dx);
|
||||
dx--;
|
||||
}
|
||||
delta += aa * (4 * dy + 6);
|
||||
}
|
||||
|
||||
qp_dprintf("qp_ellipse: %s\n", ret ? "ok" : "fail");
|
||||
qp_comms_stop(device);
|
||||
return ret;
|
||||
}
|
382
quantum/painter/qp_draw_image.c
Normal file
382
quantum/painter/qp_draw_image.c
Normal file
|
@ -0,0 +1,382 @@
|
|||
// Copyright 2021 Nick Brassel (@tzarc)
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "qp_internal.h"
|
||||
#include "qp_draw.h"
|
||||
#include "qp_comms.h"
|
||||
#include "qgf.h"
|
||||
#include "deferred_exec.h"
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// QGF image handles
|
||||
|
||||
typedef struct qgf_image_handle_t {
|
||||
painter_image_desc_t base;
|
||||
bool validate_ok;
|
||||
union {
|
||||
qp_stream_t stream;
|
||||
qp_memory_stream_t mem_stream;
|
||||
#ifdef QP_STREAM_HAS_FILE_IO
|
||||
qp_file_stream_t file_stream;
|
||||
#endif // QP_STREAM_HAS_FILE_IO
|
||||
};
|
||||
} qgf_image_handle_t;
|
||||
|
||||
static qgf_image_handle_t image_descriptors[QUANTUM_PAINTER_NUM_IMAGES] = {0};
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Quantum Painter External API: qp_load_image_mem
|
||||
|
||||
painter_image_handle_t qp_load_image_mem(const void *buffer) {
|
||||
qp_dprintf("qp_load_image_mem: entry\n");
|
||||
qgf_image_handle_t *image = NULL;
|
||||
|
||||
// Find a free slot
|
||||
for (int i = 0; i < QUANTUM_PAINTER_NUM_IMAGES; ++i) {
|
||||
if (!image_descriptors[i].validate_ok) {
|
||||
image = &image_descriptors[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Drop out if not found
|
||||
if (!image) {
|
||||
qp_dprintf("qp_load_image_mem: fail (no free slot)\n");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Assume we can read the graphics descriptor
|
||||
image->mem_stream = qp_make_memory_stream((void *)buffer, sizeof(qgf_graphics_descriptor_v1_t));
|
||||
|
||||
// Update the length of the stream to match, and rewind to the start
|
||||
image->mem_stream.length = qgf_get_total_size(&image->stream);
|
||||
image->mem_stream.position = 0;
|
||||
|
||||
// Now that we know the length, validate the input data
|
||||
if (!qgf_validate_stream(&image->stream)) {
|
||||
qp_dprintf("qp_load_image_mem: fail (failed validation)\n");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Fill out the QP image descriptor
|
||||
qgf_read_graphics_descriptor(&image->stream, &image->base.width, &image->base.height, &image->base.frame_count, NULL);
|
||||
|
||||
// Validation success, we can return the handle
|
||||
image->validate_ok = true;
|
||||
qp_dprintf("qp_load_image_mem: ok\n");
|
||||
return (painter_image_handle_t)image;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Quantum Painter External API: qp_close_image
|
||||
|
||||
bool qp_close_image(painter_image_handle_t image) {
|
||||
qgf_image_handle_t *qgf_image = (qgf_image_handle_t *)image;
|
||||
if (!qgf_image->validate_ok) {
|
||||
qp_dprintf("qp_close_image: fail (invalid image)\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Free up this image for use elsewhere.
|
||||
qgf_image->validate_ok = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Quantum Painter External API: qp_drawimage
|
||||
|
||||
bool qp_drawimage(painter_device_t device, uint16_t x, uint16_t y, painter_image_handle_t image) {
|
||||
return qp_drawimage_recolor(device, x, y, image, 0, 0, 255, 0, 0, 0);
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Quantum Painter External API: qp_drawimage_recolor
|
||||
|
||||
typedef struct qgf_frame_info_t {
|
||||
painter_compression_t compression_scheme;
|
||||
uint8_t bpp;
|
||||
bool has_palette;
|
||||
bool is_delta;
|
||||
uint16_t left;
|
||||
uint16_t top;
|
||||
uint16_t right;
|
||||
uint16_t bottom;
|
||||
uint16_t delay;
|
||||
} qgf_frame_info_t;
|
||||
|
||||
static bool qp_drawimage_prepare_frame_for_stream_read(painter_device_t device, qgf_image_handle_t *qgf_image, uint16_t frame_number, qp_pixel_t fg_hsv888, qp_pixel_t bg_hsv888, qgf_frame_info_t *info) {
|
||||
struct painter_driver_t *driver = (struct painter_driver_t *)device;
|
||||
|
||||
// Drop out if we can't actually place the data we read out anywhere
|
||||
if (!info) {
|
||||
qp_dprintf("Failed to prepare stream for read, output info buffer unavailable\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Seek to the frame
|
||||
qgf_seek_to_frame_descriptor(&qgf_image->stream, frame_number);
|
||||
|
||||
// Read the frame descriptor
|
||||
qgf_frame_v1_t frame_descriptor;
|
||||
if (qp_stream_read(&frame_descriptor, sizeof(qgf_frame_v1_t), 1, &qgf_image->stream) != 1) {
|
||||
qp_dprintf("Failed to read frame_descriptor, expected length was not %d\n", (int)sizeof(qgf_frame_v1_t));
|
||||
return false;
|
||||
}
|
||||
|
||||
// Parse out the frame info
|
||||
if (!qgf_parse_frame_descriptor(&frame_descriptor, &info->bpp, &info->has_palette, &info->is_delta, &info->compression_scheme, &info->delay)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Ensure we aren't reusing any palette
|
||||
qp_internal_invalidate_palette();
|
||||
|
||||
// Handle palette if needed
|
||||
const uint16_t palette_entries = 1u << info->bpp;
|
||||
bool needs_pixconvert = false;
|
||||
if (info->has_palette) {
|
||||
// Load the palette from the stream
|
||||
if (!qp_internal_load_qgf_palette((qp_stream_t *)&qgf_image->stream, info->bpp)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
needs_pixconvert = true;
|
||||
} else {
|
||||
// Interpolate from fg/bg
|
||||
needs_pixconvert = qp_internal_interpolate_palette(fg_hsv888, bg_hsv888, palette_entries);
|
||||
}
|
||||
|
||||
if (!qp_internal_bpp_capable(info->bpp)) {
|
||||
qp_dprintf("qp_drawimage_recolor: fail (image bpp too high (%d), check QUANTUM_PAINTER_SUPPORTS_256_PALETTE)\n", (int)info->bpp);
|
||||
qp_comms_stop(device);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (needs_pixconvert) {
|
||||
// Convert the palette to native format
|
||||
if (!driver->driver_vtable->palette_convert(device, palette_entries, qp_internal_global_pixel_lookup_table)) {
|
||||
qp_dprintf("qp_drawimage_recolor: fail (could not convert pixels to native)\n");
|
||||
qp_comms_stop(device);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Handle delta if needed
|
||||
if (info->is_delta) {
|
||||
qgf_delta_v1_t delta_descriptor;
|
||||
if (qp_stream_read(&delta_descriptor, sizeof(qgf_delta_v1_t), 1, &qgf_image->stream) != 1) {
|
||||
qp_dprintf("Failed to read delta_descriptor, expected length was not %d\n", (int)sizeof(qgf_delta_v1_t));
|
||||
return false;
|
||||
}
|
||||
|
||||
info->left = delta_descriptor.left;
|
||||
info->top = delta_descriptor.top;
|
||||
info->right = delta_descriptor.right;
|
||||
info->bottom = delta_descriptor.bottom;
|
||||
}
|
||||
|
||||
// Read the data block
|
||||
qgf_data_v1_t data_descriptor;
|
||||
if (qp_stream_read(&data_descriptor, sizeof(qgf_data_v1_t), 1, &qgf_image->stream) != 1) {
|
||||
qp_dprintf("Failed to read data_descriptor, expected length was not %d\n", (int)sizeof(qgf_data_v1_t));
|
||||
return false;
|
||||
}
|
||||
|
||||
// Stream is now at the point of being able to read pixdata
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool qp_drawimage_recolor_impl(painter_device_t device, uint16_t x, uint16_t y, painter_image_handle_t image, int frame_number, qgf_frame_info_t *frame_info, qp_pixel_t fg_hsv888, qp_pixel_t bg_hsv888) {
|
||||
qp_dprintf("qp_drawimage_recolor: entry\n");
|
||||
struct painter_driver_t *driver = (struct painter_driver_t *)device;
|
||||
if (!driver->validate_ok) {
|
||||
qp_dprintf("qp_drawimage_recolor: fail (validation_ok == false)\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
qgf_image_handle_t *qgf_image = (qgf_image_handle_t *)image;
|
||||
if (!qgf_image->validate_ok) {
|
||||
qp_dprintf("qp_drawimage_recolor: fail (invalid image)\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Read the frame info
|
||||
if (!qp_drawimage_prepare_frame_for_stream_read(device, qgf_image, frame_number, fg_hsv888, bg_hsv888, frame_info)) {
|
||||
qp_dprintf("qp_drawimage_recolor: fail (could not read frame %d)\n", frame_number);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!qp_comms_start(device)) {
|
||||
qp_dprintf("qp_drawimage_recolor: fail (could not start comms)\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
uint16_t l, t, r, b;
|
||||
if (frame_info->is_delta) {
|
||||
l = x + frame_info->left;
|
||||
t = y + frame_info->top;
|
||||
r = x + frame_info->right - 1;
|
||||
b = y + frame_info->bottom - 1;
|
||||
} else {
|
||||
l = x;
|
||||
t = y;
|
||||
r = x + image->width - 1;
|
||||
b = y + image->height - 1;
|
||||
}
|
||||
uint32_t pixel_count = ((uint32_t)(r - l + 1)) * (b - t + 1);
|
||||
|
||||
// Configure where we're going to be rendering to
|
||||
if (!driver->driver_vtable->viewport(device, l, t, r, b)) {
|
||||
qp_dprintf("qp_drawimage_recolor: fail (could not set viewport)\n");
|
||||
qp_comms_stop(device);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Set up the input state
|
||||
struct qp_internal_byte_input_state input_state = {.device = device, .src_stream = &qgf_image->stream};
|
||||
qp_internal_byte_input_callback input_callback = qp_internal_prepare_input_state(&input_state, frame_info->compression_scheme);
|
||||
if (input_callback == NULL) {
|
||||
qp_dprintf("qp_drawimage_recolor: fail (invalid image compression scheme)\n");
|
||||
qp_comms_stop(device);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Set up the output state
|
||||
struct qp_internal_pixel_output_state output_state = {.device = device, .pixel_write_pos = 0, .max_pixels = qp_internal_num_pixels_in_buffer(device)};
|
||||
|
||||
// Decode the pixel data and stream to the display
|
||||
bool ret = qp_internal_decode_palette(device, pixel_count, frame_info->bpp, input_callback, &input_state, qp_internal_global_pixel_lookup_table, qp_internal_pixel_appender, &output_state);
|
||||
|
||||
// Any leftovers need transmission as well.
|
||||
if (ret && output_state.pixel_write_pos > 0) {
|
||||
ret &= driver->driver_vtable->pixdata(device, qp_internal_global_pixdata_buffer, output_state.pixel_write_pos);
|
||||
}
|
||||
|
||||
qp_dprintf("qp_drawimage_recolor: %s\n", ret ? "ok" : "fail");
|
||||
qp_comms_stop(device);
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool qp_drawimage_recolor(painter_device_t device, uint16_t x, uint16_t y, painter_image_handle_t image, uint8_t hue_fg, uint8_t sat_fg, uint8_t val_fg, uint8_t hue_bg, uint8_t sat_bg, uint8_t val_bg) {
|
||||
qgf_frame_info_t frame_info = {0};
|
||||
qp_pixel_t fg_hsv888 = {.hsv888 = {.h = hue_fg, .s = sat_fg, .v = val_fg}};
|
||||
qp_pixel_t bg_hsv888 = {.hsv888 = {.h = hue_bg, .s = sat_bg, .v = val_bg}};
|
||||
return qp_drawimage_recolor_impl(device, x, y, image, 0, &frame_info, fg_hsv888, bg_hsv888);
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Quantum Painter External API: qp_animate
|
||||
|
||||
deferred_token qp_animate(painter_device_t device, uint16_t x, uint16_t y, painter_image_handle_t image) {
|
||||
return qp_animate_recolor(device, x, y, image, 0, 0, 255, 0, 0, 0);
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Quantum Painter External API: qp_animate_recolor
|
||||
|
||||
typedef struct animation_state_t {
|
||||
painter_device_t device;
|
||||
uint16_t x;
|
||||
uint16_t y;
|
||||
painter_image_handle_t image;
|
||||
qp_pixel_t fg_hsv888;
|
||||
qp_pixel_t bg_hsv888;
|
||||
uint16_t frame_number;
|
||||
deferred_token defer_token;
|
||||
} animation_state_t;
|
||||
|
||||
static deferred_executor_t animation_executors[QUANTUM_PAINTER_CONCURRENT_ANIMATIONS] = {0};
|
||||
static animation_state_t animation_states[QUANTUM_PAINTER_CONCURRENT_ANIMATIONS] = {0};
|
||||
|
||||
static deferred_token qp_render_animation_state(animation_state_t *state, uint16_t *delay_ms) {
|
||||
qgf_frame_info_t frame_info = {0};
|
||||
qp_dprintf("qp_render_animation_state: entry (frame #%d)\n", (int)state->frame_number);
|
||||
bool ret = qp_drawimage_recolor_impl(state->device, state->x, state->y, state->image, state->frame_number, &frame_info, state->fg_hsv888, state->bg_hsv888);
|
||||
if (ret) {
|
||||
++state->frame_number;
|
||||
if (state->frame_number >= state->image->frame_count) {
|
||||
state->frame_number = 0;
|
||||
}
|
||||
*delay_ms = frame_info.delay;
|
||||
}
|
||||
qp_dprintf("qp_render_animation_state: %s (delay %dms)\n", ret ? "ok" : "fail", (int)(*delay_ms));
|
||||
return ret;
|
||||
}
|
||||
|
||||
static uint32_t animation_callback(uint32_t trigger_time, void *cb_arg) {
|
||||
animation_state_t *state = (animation_state_t *)cb_arg;
|
||||
uint16_t delay_ms;
|
||||
bool ret = qp_render_animation_state(state, &delay_ms);
|
||||
if (!ret) {
|
||||
// Setting the device to NULL clears the animation slot
|
||||
state->device = NULL;
|
||||
}
|
||||
// If we're successful, keep animating -- returning 0 cancels the deferred execution
|
||||
return ret ? delay_ms : 0;
|
||||
}
|
||||
|
||||
deferred_token qp_animate_recolor(painter_device_t device, uint16_t x, uint16_t y, painter_image_handle_t image, uint8_t hue_fg, uint8_t sat_fg, uint8_t val_fg, uint8_t hue_bg, uint8_t sat_bg, uint8_t val_bg) {
|
||||
qp_dprintf("qp_animate_recolor: entry\n");
|
||||
|
||||
animation_state_t *anim_state = NULL;
|
||||
for (int i = 0; i < QUANTUM_PAINTER_CONCURRENT_ANIMATIONS; ++i) {
|
||||
if (animation_states[i].device == NULL) {
|
||||
anim_state = &animation_states[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!anim_state) {
|
||||
qp_dprintf("qp_animate_recolor: fail (could not find free animation slot)\n");
|
||||
return INVALID_DEFERRED_TOKEN;
|
||||
}
|
||||
|
||||
// Prepare the animation state
|
||||
anim_state->device = device;
|
||||
anim_state->x = x;
|
||||
anim_state->y = y;
|
||||
anim_state->image = image;
|
||||
anim_state->fg_hsv888 = (qp_pixel_t){.hsv888 = {.h = hue_fg, .s = sat_fg, .v = val_fg}};
|
||||
anim_state->bg_hsv888 = (qp_pixel_t){.hsv888 = {.h = hue_bg, .s = sat_bg, .v = val_bg}};
|
||||
anim_state->frame_number = 0;
|
||||
|
||||
// Draw the first frame
|
||||
uint16_t delay_ms;
|
||||
if (!qp_render_animation_state(anim_state, &delay_ms)) {
|
||||
anim_state->device = NULL; // disregard the allocated animation slot
|
||||
qp_dprintf("qp_animate_recolor: fail (could not render first frame)\n");
|
||||
return INVALID_DEFERRED_TOKEN;
|
||||
}
|
||||
|
||||
// Set up the timer
|
||||
anim_state->defer_token = defer_exec_advanced(animation_executors, QUANTUM_PAINTER_CONCURRENT_ANIMATIONS, delay_ms, animation_callback, anim_state);
|
||||
if (anim_state->defer_token == INVALID_DEFERRED_TOKEN) {
|
||||
anim_state->device = NULL; // disregard the allocated animation slot
|
||||
qp_dprintf("qp_animate_recolor: fail (could not set up animation executor)\n");
|
||||
return INVALID_DEFERRED_TOKEN;
|
||||
}
|
||||
|
||||
qp_dprintf("qp_animate_recolor: ok (deferred token = %d)\n", (int)anim_state->defer_token);
|
||||
return anim_state->defer_token;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Quantum Painter External API: qp_stop_animation
|
||||
|
||||
void qp_stop_animation(deferred_token anim_token) {
|
||||
for (int i = 0; i < QUANTUM_PAINTER_CONCURRENT_ANIMATIONS; ++i) {
|
||||
if (animation_states[i].defer_token == anim_token) {
|
||||
cancel_deferred_exec_advanced(animation_executors, QUANTUM_PAINTER_CONCURRENT_ANIMATIONS, anim_token);
|
||||
animation_states[i].device = NULL;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Quantum Painter Core API: qp_internal_animation_tick
|
||||
|
||||
void qp_internal_animation_tick(void) {
|
||||
static uint32_t last_anim_exec = 0;
|
||||
deferred_exec_advanced_task(animation_executors, QUANTUM_PAINTER_CONCURRENT_ANIMATIONS, &last_anim_exec);
|
||||
}
|
444
quantum/painter/qp_draw_text.c
Normal file
444
quantum/painter/qp_draw_text.c
Normal file
|
@ -0,0 +1,444 @@
|
|||
// Copyright 2021 Nick Brassel (@tzarc)
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <quantum.h>
|
||||
#include <utf8.h>
|
||||
|
||||
#include "qp_internal.h"
|
||||
#include "qp_draw.h"
|
||||
#include "qp_comms.h"
|
||||
#include "qff.h"
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// QFF font handles
|
||||
|
||||
typedef struct qff_font_handle_t {
|
||||
painter_font_desc_t base;
|
||||
bool validate_ok;
|
||||
bool has_ascii_table;
|
||||
uint16_t num_unicode_glyphs;
|
||||
uint8_t bpp;
|
||||
bool has_palette;
|
||||
painter_compression_t compression_scheme;
|
||||
union {
|
||||
qp_stream_t stream;
|
||||
qp_memory_stream_t mem_stream;
|
||||
#ifdef QP_STREAM_HAS_FILE_IO
|
||||
qp_file_stream_t file_stream;
|
||||
#endif // QP_STREAM_HAS_FILE_IO
|
||||
};
|
||||
#if QUANTUM_PAINTER_LOAD_FONTS_TO_RAM
|
||||
bool owns_buffer;
|
||||
void *buffer;
|
||||
#endif // QUANTUM_PAINTER_LOAD_FONTS_TO_RAM
|
||||
} qff_font_handle_t;
|
||||
|
||||
static qff_font_handle_t font_descriptors[QUANTUM_PAINTER_NUM_FONTS] = {0};
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Quantum Painter External API: qp_load_font_mem
|
||||
|
||||
painter_font_handle_t qp_load_font_mem(const void *buffer) {
|
||||
qp_dprintf("qp_load_font_mem: entry\n");
|
||||
qff_font_handle_t *font = NULL;
|
||||
|
||||
// Find a free slot
|
||||
for (int i = 0; i < QUANTUM_PAINTER_NUM_FONTS; ++i) {
|
||||
if (!font_descriptors[i].validate_ok) {
|
||||
font = &font_descriptors[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Drop out if not found
|
||||
if (!font) {
|
||||
qp_dprintf("qp_load_font_mem: fail (no free slot)\n");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Assume we can read the graphics descriptor
|
||||
font->mem_stream = qp_make_memory_stream((void *)buffer, sizeof(qff_font_descriptor_v1_t));
|
||||
|
||||
// Update the length of the stream to match, and rewind to the start
|
||||
font->mem_stream.length = qff_get_total_size(&font->stream);
|
||||
font->mem_stream.position = 0;
|
||||
|
||||
// Now that we know the length, validate the input data
|
||||
if (!qff_validate_stream(&font->stream)) {
|
||||
qp_dprintf("qp_load_font_mem: fail (failed validation)\n");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
#if QUANTUM_PAINTER_LOAD_FONTS_TO_RAM
|
||||
// Clear out any existing data
|
||||
font->owns_buffer = false;
|
||||
font->buffer = NULL;
|
||||
|
||||
void *ram_buffer = malloc(font->mem_stream.length);
|
||||
if (ram_buffer == NULL) {
|
||||
qp_dprintf("qp_load_font_mem: could not allocate enough RAM for font, falling back to original\n");
|
||||
} else {
|
||||
do {
|
||||
// Copy the data into RAM
|
||||
if (qp_stream_read(ram_buffer, 1, font->mem_stream.length, &font->mem_stream) != font->mem_stream.length) {
|
||||
qp_dprintf("qp_load_font_mem: could not copy from flash to RAM, falling back to original\n");
|
||||
break;
|
||||
}
|
||||
|
||||
// Create the new stream with the new buffer
|
||||
font->buffer = ram_buffer;
|
||||
font->owns_buffer = true;
|
||||
font->mem_stream = qp_make_memory_stream(font->buffer, font->mem_stream.length);
|
||||
} while (0);
|
||||
}
|
||||
|
||||
// Free the buffer if we were unable to recreate the RAM copy.
|
||||
if (ram_buffer != NULL && !font->owns_buffer) {
|
||||
free(ram_buffer);
|
||||
}
|
||||
#endif // QUANTUM_PAINTER_LOAD_FONTS_TO_RAM
|
||||
|
||||
// Read the info (parsing already successful above, no need to check return value)
|
||||
qff_read_font_descriptor(&font->stream, &font->base.line_height, &font->has_ascii_table, &font->num_unicode_glyphs, &font->bpp, &font->has_palette, &font->compression_scheme, NULL);
|
||||
|
||||
if (!qp_internal_bpp_capable(font->bpp)) {
|
||||
qp_dprintf("qp_load_font_mem: fail (image bpp too high (%d), check QUANTUM_PAINTER_SUPPORTS_256_PALETTE)\n", (int)font->bpp);
|
||||
qp_close_font((painter_font_handle_t)font);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Validation success, we can return the handle
|
||||
font->validate_ok = true;
|
||||
qp_dprintf("qp_load_font_mem: ok\n");
|
||||
return (painter_font_handle_t)font;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Quantum Painter External API: qp_close_font
|
||||
|
||||
bool qp_close_font(painter_font_handle_t font) {
|
||||
qff_font_handle_t *qff_font = (qff_font_handle_t *)font;
|
||||
if (!qff_font->validate_ok) {
|
||||
qp_dprintf("qp_close_font: fail (invalid font)\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
#if QUANTUM_PAINTER_LOAD_FONTS_TO_RAM
|
||||
// Nuke the buffer, if required
|
||||
if (qff_font->owns_buffer) {
|
||||
free(qff_font->buffer);
|
||||
qff_font->buffer = NULL;
|
||||
qff_font->owns_buffer = false;
|
||||
}
|
||||
#endif // QUANTUM_PAINTER_LOAD_FONTS_TO_RAM
|
||||
|
||||
// Free up this font for use elsewhere.
|
||||
qff_font->validate_ok = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Helpers
|
||||
|
||||
// Callback to be invoked for each codepoint detected in the UTF8 input string
|
||||
typedef bool (*code_point_handler)(qff_font_handle_t *qff_font, uint32_t code_point, uint8_t width, uint8_t height, void *cb_arg);
|
||||
|
||||
// Helper that sets up the palette (if required) and returns the offset in the stream that the data starts
|
||||
static inline bool qp_drawtext_prepare_font_for_render(painter_device_t device, qff_font_handle_t *qff_font, qp_pixel_t fg_hsv888, qp_pixel_t bg_hsv888, uint32_t *data_offset) {
|
||||
struct painter_driver_t *driver = (struct painter_driver_t *)device;
|
||||
|
||||
// Drop out if we can't actually place the data we read out anywhere
|
||||
if (!data_offset) {
|
||||
qp_dprintf("Failed to prepare stream for read, output info buffer unavailable\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Work out where we're reading from
|
||||
uint32_t offset = sizeof(qff_font_descriptor_v1_t);
|
||||
if (qff_font->has_ascii_table) {
|
||||
offset += sizeof(qff_ascii_glyph_table_v1_t);
|
||||
}
|
||||
if (qff_font->num_unicode_glyphs > 0) {
|
||||
offset += sizeof(qff_unicode_glyph_table_v1_t) + (qff_font->num_unicode_glyphs * 6);
|
||||
}
|
||||
|
||||
// Handle palette if needed
|
||||
const uint16_t palette_entries = 1u << qff_font->bpp;
|
||||
bool needs_pixconvert = false;
|
||||
if (qff_font->has_palette) {
|
||||
// If this font has a palette, we need to read it out and set up the pixel lookup table
|
||||
qp_stream_setpos(&qff_font->stream, offset);
|
||||
if (!qp_internal_load_qgf_palette(&qff_font->stream, qff_font->bpp)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Skip this block, as far as offset calculations go
|
||||
offset += sizeof(qgf_palette_v1_t) + (palette_entries * 3);
|
||||
needs_pixconvert = true;
|
||||
} else {
|
||||
// Interpolate from fg/bg
|
||||
int16_t palette_entries = 1 << qff_font->bpp;
|
||||
needs_pixconvert = qp_internal_interpolate_palette(fg_hsv888, bg_hsv888, palette_entries);
|
||||
}
|
||||
|
||||
if (needs_pixconvert) {
|
||||
// Convert the palette to native format
|
||||
if (!driver->driver_vtable->palette_convert(device, palette_entries, qp_internal_global_pixel_lookup_table)) {
|
||||
qp_dprintf("qp_drawtext_recolor: fail (could not convert pixels to native)\n");
|
||||
qp_comms_stop(device);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
*data_offset = offset;
|
||||
return true;
|
||||
}
|
||||
|
||||
static inline bool qp_drawtext_prepare_glyph_for_render(qff_font_handle_t *qff_font, uint32_t code_point, uint8_t *width) {
|
||||
if (code_point >= 0x20 && code_point < 0x7F && qff_font->has_ascii_table) {
|
||||
// Do ascii table
|
||||
qff_ascii_glyph_v1_t glyph_info;
|
||||
uint32_t glyph_info_offset = sizeof(qff_font_descriptor_v1_t) // Skip the font descriptor
|
||||
+ sizeof(qgf_block_header_v1_t) // Skip the ascii table header
|
||||
+ (code_point - 0x20) * sizeof(qff_ascii_glyph_v1_t); // Jump direct to the data offset based on the glyph index
|
||||
if (qp_stream_setpos(&qff_font->stream, glyph_info_offset) < 0) {
|
||||
qp_dprintf("Failed to set stream position while reading ascii glyph info\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (qp_stream_read(&glyph_info, sizeof(qff_ascii_glyph_v1_t), 1, &qff_font->stream) != 1) {
|
||||
qp_dprintf("Failed to read glyph info\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
uint8_t glyph_width = (uint8_t)(glyph_info.value & QFF_GLYPH_WIDTH_MASK);
|
||||
uint32_t glyph_offset = ((glyph_info.value & QFF_GLYPH_OFFSET_MASK) >> QFF_GLYPH_WIDTH_BITS);
|
||||
uint32_t data_offset = sizeof(qff_font_descriptor_v1_t) // Skip the font descriptor
|
||||
+ sizeof(qff_ascii_glyph_table_v1_t) // Skip the ascii table
|
||||
+ (qff_font->num_unicode_glyphs > 0 ? (sizeof(qff_unicode_glyph_table_v1_t) + (qff_font->num_unicode_glyphs * sizeof(qff_unicode_glyph_v1_t))) : 0) // Skip the unicode table
|
||||
+ (qff_font->has_palette ? (sizeof(qgf_palette_v1_t) + ((1 << qff_font->bpp) * sizeof(qgf_palette_entry_v1_t))) : 0) // Skip the palette
|
||||
+ sizeof(qgf_block_header_v1_t) // Skip the data block header
|
||||
+ glyph_offset; // Jump to the specified glyph offset
|
||||
|
||||
if (qp_stream_setpos(&qff_font->stream, data_offset) < 0) {
|
||||
qp_dprintf("Failed to set stream position while preparing ascii glyph data\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
*width = glyph_width;
|
||||
return true;
|
||||
} else {
|
||||
// Do unicode table, which may include singular ascii glyphs if full ascii table isn't specified
|
||||
uint32_t glyph_info_offset = sizeof(qff_font_descriptor_v1_t) // Skip the font descriptor
|
||||
+ (qff_font->has_ascii_table ? sizeof(qff_ascii_glyph_table_v1_t) : 0) // Skip the ascii table
|
||||
+ sizeof(qgf_block_header_v1_t); // Skip the unicode block header
|
||||
|
||||
if (qp_stream_setpos(&qff_font->stream, glyph_info_offset) < 0) {
|
||||
qp_dprintf("Failed to set stream position while preparing glyph data\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
qff_unicode_glyph_v1_t glyph_info;
|
||||
for (uint16_t i = 0; i < qff_font->num_unicode_glyphs; ++i) {
|
||||
if (qp_stream_read(&glyph_info, sizeof(qff_unicode_glyph_v1_t), 1, &qff_font->stream) != 1) {
|
||||
qp_dprintf("Failed to set stream position while reading unicode glyph info\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (glyph_info.code_point == code_point) {
|
||||
uint8_t glyph_width = (uint8_t)(glyph_info.value & QFF_GLYPH_WIDTH_MASK);
|
||||
uint32_t glyph_offset = ((glyph_info.value & QFF_GLYPH_OFFSET_MASK) >> QFF_GLYPH_WIDTH_BITS);
|
||||
uint32_t data_offset = sizeof(qff_font_descriptor_v1_t) // Skip the font descriptor
|
||||
+ sizeof(qff_ascii_glyph_table_v1_t) // Skip the ascii table
|
||||
+ (qff_font->num_unicode_glyphs > 0 ? (sizeof(qff_unicode_glyph_table_v1_t) + (qff_font->num_unicode_glyphs * sizeof(qff_unicode_glyph_v1_t))) : 0) // Skip the unicode table
|
||||
+ (qff_font->has_palette ? (sizeof(qgf_palette_v1_t) + ((1 << qff_font->bpp) * sizeof(qgf_palette_entry_v1_t))) : 0) // Skip the palette
|
||||
+ sizeof(qgf_block_header_v1_t) // Skip the data block header
|
||||
+ glyph_offset; // Jump to the specified glyph offset
|
||||
|
||||
if (qp_stream_setpos(&qff_font->stream, data_offset) < 0) {
|
||||
qp_dprintf("Failed to set stream position while preparing unicode glyph data\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
*width = glyph_width;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Not found
|
||||
qp_dprintf("Failed to find unicode glyph info\n");
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Function to iterate over each UTF8 codepoint, invoking the callback for each decoded glyph
|
||||
static inline bool qp_iterate_code_points(qff_font_handle_t *qff_font, const char *str, code_point_handler handler, void *cb_arg) {
|
||||
while (*str) {
|
||||
int32_t code_point = 0;
|
||||
str = decode_utf8(str, &code_point);
|
||||
if (code_point < 0) {
|
||||
qp_dprintf("Invalid unicode code point decoded. Cannot render.\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
uint8_t width;
|
||||
if (!qp_drawtext_prepare_glyph_for_render(qff_font, code_point, &width)) {
|
||||
qp_dprintf("Failed to prepare glyph for rendering.\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!handler(qff_font, code_point, width, qff_font->base.line_height, cb_arg)) {
|
||||
qp_dprintf("Failed to execute glyph handler.\n");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// String width calculation
|
||||
|
||||
// Callback state
|
||||
struct code_point_iter_calcwidth_state {
|
||||
int16_t width;
|
||||
};
|
||||
|
||||
// Codepoint handler callback: width calc
|
||||
static inline bool qp_font_code_point_handler_calcwidth(qff_font_handle_t *qff_font, uint32_t code_point, uint8_t width, uint8_t height, void *cb_arg) {
|
||||
struct code_point_iter_calcwidth_state *state = (struct code_point_iter_calcwidth_state *)cb_arg;
|
||||
|
||||
// Increment the overall width by this glyph's width
|
||||
state->width += width;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// String drawing implementation
|
||||
|
||||
// Callback state
|
||||
struct code_point_iter_drawglyph_state {
|
||||
painter_device_t device;
|
||||
int16_t xpos;
|
||||
int16_t ypos;
|
||||
qp_internal_byte_input_callback input_callback;
|
||||
struct qp_internal_byte_input_state * input_state;
|
||||
struct qp_internal_pixel_output_state *output_state;
|
||||
};
|
||||
|
||||
// Codepoint handler callback: drawing
|
||||
static inline bool qp_font_code_point_handler_drawglyph(qff_font_handle_t *qff_font, uint32_t code_point, uint8_t width, uint8_t height, void *cb_arg) {
|
||||
struct code_point_iter_drawglyph_state *state = (struct code_point_iter_drawglyph_state *)cb_arg;
|
||||
struct painter_driver_t * driver = (struct painter_driver_t *)state->device;
|
||||
|
||||
// Reset the input state's RLE mode -- the stream should already be correctly positioned by qp_iterate_code_points()
|
||||
state->input_state->rle.mode = MARKER_BYTE; // ignored if not using RLE
|
||||
|
||||
// Reset the output state
|
||||
state->output_state->pixel_write_pos = 0;
|
||||
|
||||
// Configure where we're going to be rendering to
|
||||
driver->driver_vtable->viewport(state->device, state->xpos, state->ypos, state->xpos + width - 1, state->ypos + height - 1);
|
||||
|
||||
// Move the x-position for the next glyph
|
||||
state->xpos += width;
|
||||
|
||||
// Decode the pixel data for the glyph
|
||||
uint32_t pixel_count = ((uint32_t)width) * height;
|
||||
bool ret = qp_internal_decode_palette(state->device, pixel_count, qff_font->bpp, state->input_callback, state->input_state, qp_internal_global_pixel_lookup_table, qp_internal_pixel_appender, state->output_state);
|
||||
|
||||
// Any leftovers need transmission as well.
|
||||
if (ret && state->output_state->pixel_write_pos > 0) {
|
||||
ret &= driver->driver_vtable->pixdata(state->device, qp_internal_global_pixdata_buffer, state->output_state->pixel_write_pos);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Quantum Painter External API: qp_textwidth
|
||||
|
||||
int16_t qp_textwidth(painter_font_handle_t font, const char *str) {
|
||||
qff_font_handle_t *qff_font = (qff_font_handle_t *)font;
|
||||
if (!qff_font->validate_ok) {
|
||||
qp_dprintf("qp_textwidth: fail (invalid font)\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Create the codepoint iterator state
|
||||
struct code_point_iter_calcwidth_state state = {.width = 0};
|
||||
// Iterate each codepoint, return the calculated width if successful.
|
||||
return qp_iterate_code_points(qff_font, str, qp_font_code_point_handler_calcwidth, &state) ? state.width : 0;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Quantum Painter External API: qp_drawtext
|
||||
|
||||
int16_t qp_drawtext(painter_device_t device, uint16_t x, uint16_t y, painter_font_handle_t font, const char *str) {
|
||||
// Offload to the recolor variant, substituting fg=white bg=black.
|
||||
// Traditional LCDs with those colors will need to manually invoke qp_drawtext_recolor with the colors reversed.
|
||||
return qp_drawtext_recolor(device, x, y, font, str, 0, 0, 255, 0, 0, 0);
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Quantum Painter External API: qp_drawtext_recolor
|
||||
|
||||
int16_t qp_drawtext_recolor(painter_device_t device, uint16_t x, uint16_t y, painter_font_handle_t font, const char *str, uint8_t hue_fg, uint8_t sat_fg, uint8_t val_fg, uint8_t hue_bg, uint8_t sat_bg, uint8_t val_bg) {
|
||||
qp_dprintf("qp_drawtext_recolor: entry\n");
|
||||
struct painter_driver_t *driver = (struct painter_driver_t *)device;
|
||||
if (!driver->validate_ok) {
|
||||
qp_dprintf("qp_drawtext_recolor: fail (validation_ok == false)\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
qff_font_handle_t *qff_font = (qff_font_handle_t *)font;
|
||||
if (!qff_font->validate_ok) {
|
||||
qp_dprintf("qp_drawtext_recolor: fail (invalid font)\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!qp_comms_start(device)) {
|
||||
qp_dprintf("qp_drawtext_recolor: fail (could not start comms)\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Set up the byte input state and input callback
|
||||
struct qp_internal_byte_input_state input_state = {.device = device, .src_stream = &qff_font->stream};
|
||||
qp_internal_byte_input_callback input_callback = qp_internal_prepare_input_state(&input_state, qff_font->compression_scheme);
|
||||
if (input_callback == NULL) {
|
||||
qp_dprintf("qp_drawtext_recolor: fail (invalid font compression scheme)\n");
|
||||
qp_comms_stop(device);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Set up the pixel output state
|
||||
struct qp_internal_pixel_output_state output_state = {.device = device, .pixel_write_pos = 0, .max_pixels = qp_internal_num_pixels_in_buffer(device)};
|
||||
|
||||
// Set up the codepoint iteration state
|
||||
struct code_point_iter_drawglyph_state state = {// Common
|
||||
.device = device,
|
||||
.xpos = x,
|
||||
.ypos = y,
|
||||
// Input
|
||||
.input_callback = input_callback,
|
||||
.input_state = &input_state,
|
||||
// Output
|
||||
.output_state = &output_state};
|
||||
|
||||
qp_pixel_t fg_hsv888 = {.hsv888 = {.h = hue_fg, .s = sat_fg, .v = val_fg}};
|
||||
qp_pixel_t bg_hsv888 = {.hsv888 = {.h = hue_bg, .s = sat_bg, .v = val_bg}};
|
||||
uint32_t data_offset;
|
||||
if (!qp_drawtext_prepare_font_for_render(driver, qff_font, fg_hsv888, bg_hsv888, &data_offset)) {
|
||||
qp_dprintf("qp_drawtext_recolor: fail (failed to prepare font for rendering)\n");
|
||||
qp_comms_stop(device);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Iterate the codepoints with the drawglyph callback
|
||||
bool ret = qp_iterate_code_points(qff_font, str, qp_font_code_point_handler_drawglyph, &state);
|
||||
|
||||
qp_dprintf("qp_drawtext_recolor: %s\n", ret ? "ok" : "fail");
|
||||
qp_comms_stop(device);
|
||||
return ret ? (state.xpos - x) : 0;
|
||||
}
|
33
quantum/painter/qp_internal.h
Normal file
33
quantum/painter/qp_internal.h
Normal file
|
@ -0,0 +1,33 @@
|
|||
// Copyright 2021 Nick Brassel (@tzarc)
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "quantum.h"
|
||||
#include "qp.h"
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Helpers
|
||||
|
||||
// Mark certain types that there should be no padding bytes between members.
|
||||
#define QP_PACKED __attribute__((packed))
|
||||
|
||||
// Min/max defines
|
||||
#define QP_MIN(X, Y) (((X) < (Y)) ? (X) : (Y))
|
||||
#define QP_MAX(X, Y) (((X) > (Y)) ? (X) : (Y))
|
||||
|
||||
#ifdef QUANTUM_PAINTER_DEBUG
|
||||
# include <debug.h>
|
||||
# include <print.h>
|
||||
# define qp_dprintf(...) dprintf(__VA_ARGS__)
|
||||
#else
|
||||
# define qp_dprintf(...) \
|
||||
do { \
|
||||
} while (0)
|
||||
#endif
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Specific internal definitions
|
||||
|
||||
#include <qp_internal_formats.h>
|
||||
#include <qp_internal_driver.h>
|
82
quantum/painter/qp_internal_driver.h
Normal file
82
quantum/painter/qp_internal_driver.h
Normal file
|
@ -0,0 +1,82 @@
|
|||
// Copyright 2021 Nick Brassel (@tzarc)
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "qp_internal.h"
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Driver callbacks
|
||||
|
||||
typedef bool (*painter_driver_init_func)(painter_device_t device, painter_rotation_t rotation);
|
||||
typedef bool (*painter_driver_power_func)(painter_device_t device, bool power_on);
|
||||
typedef bool (*painter_driver_clear_func)(painter_device_t device);
|
||||
typedef bool (*painter_driver_flush_func)(painter_device_t device);
|
||||
typedef bool (*painter_driver_viewport_func)(painter_device_t device, uint16_t left, uint16_t top, uint16_t right, uint16_t bottom);
|
||||
typedef bool (*painter_driver_pixdata_func)(painter_device_t device, const void *pixel_data, uint32_t native_pixel_count);
|
||||
typedef bool (*painter_driver_convert_palette_func)(painter_device_t device, int16_t palette_size, qp_pixel_t *palette);
|
||||
typedef bool (*painter_driver_append_pixels)(painter_device_t device, uint8_t *target_buffer, qp_pixel_t *palette, uint32_t pixel_offset, uint32_t pixel_count, uint8_t *palette_indices);
|
||||
|
||||
// Driver vtable definition
|
||||
struct painter_driver_vtable_t {
|
||||
painter_driver_init_func init;
|
||||
painter_driver_power_func power;
|
||||
painter_driver_clear_func clear;
|
||||
painter_driver_flush_func flush;
|
||||
painter_driver_viewport_func viewport;
|
||||
painter_driver_pixdata_func pixdata;
|
||||
painter_driver_convert_palette_func palette_convert;
|
||||
painter_driver_append_pixels append_pixels;
|
||||
};
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Comms callbacks
|
||||
|
||||
typedef bool (*painter_driver_comms_init_func)(painter_device_t device);
|
||||
typedef bool (*painter_driver_comms_start_func)(painter_device_t device);
|
||||
typedef void (*painter_driver_comms_stop_func)(painter_device_t device);
|
||||
typedef uint32_t (*painter_driver_comms_send_func)(painter_device_t device, const void *data, uint32_t byte_count);
|
||||
|
||||
struct painter_comms_vtable_t {
|
||||
painter_driver_comms_init_func comms_init;
|
||||
painter_driver_comms_start_func comms_start;
|
||||
painter_driver_comms_stop_func comms_stop;
|
||||
painter_driver_comms_send_func comms_send;
|
||||
};
|
||||
|
||||
typedef void (*painter_driver_comms_send_command_func)(painter_device_t device, uint8_t cmd);
|
||||
typedef void (*painter_driver_comms_bulk_command_sequence)(painter_device_t device, const uint8_t *sequence, size_t sequence_len);
|
||||
|
||||
struct painter_comms_with_command_vtable_t {
|
||||
struct painter_comms_vtable_t base; // must be first, so this object can be cast from the painter_comms_vtable_t* type
|
||||
painter_driver_comms_send_command_func send_command;
|
||||
painter_driver_comms_bulk_command_sequence bulk_command_sequence;
|
||||
};
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Driver base definition
|
||||
|
||||
struct painter_driver_t {
|
||||
const struct painter_driver_vtable_t *driver_vtable;
|
||||
const struct painter_comms_vtable_t * comms_vtable;
|
||||
|
||||
// Flag signifying if validation was successful
|
||||
bool validate_ok;
|
||||
|
||||
// Panel geometry
|
||||
uint16_t panel_width;
|
||||
uint16_t panel_height;
|
||||
|
||||
// Target drawing rotation
|
||||
painter_rotation_t rotation;
|
||||
|
||||
// Automated offsets for setting viewport
|
||||
uint16_t offset_x;
|
||||
uint16_t offset_y;
|
||||
|
||||
// Number of bits per pixel, used for determining how many pixels can be sent during a transmission of the pixdata buffer
|
||||
uint8_t native_bits_per_pixel;
|
||||
|
||||
// Comms config pointer -- needs to point to an appropriate comms config if the comms driver requires it.
|
||||
void *comms_config;
|
||||
};
|
49
quantum/painter/qp_internal_formats.h
Normal file
49
quantum/painter/qp_internal_formats.h
Normal file
|
@ -0,0 +1,49 @@
|
|||
// Copyright 2021 Nick Brassel (@tzarc)
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "qp_internal.h"
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Quantum Painter pixel formats
|
||||
|
||||
// Datatype containing a pixel's color. The internal member used is dependent on the external context.
|
||||
typedef union QP_PACKED qp_pixel_t {
|
||||
uint8_t mono;
|
||||
uint8_t palette_idx;
|
||||
|
||||
struct QP_PACKED {
|
||||
uint8_t h;
|
||||
uint8_t s;
|
||||
uint8_t v;
|
||||
} hsv888;
|
||||
|
||||
struct QP_PACKED {
|
||||
uint8_t r;
|
||||
uint8_t g;
|
||||
uint8_t b;
|
||||
} rgb888;
|
||||
|
||||
uint16_t rgb565;
|
||||
|
||||
uint32_t dummy;
|
||||
} qp_pixel_t;
|
||||
_Static_assert(sizeof(qp_pixel_t) == 4, "Invalid size for qp_pixel_t");
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Quantum Painter image format
|
||||
|
||||
typedef enum qp_image_format_t {
|
||||
// Pixel formats available in the QGF frame format
|
||||
GRAYSCALE_1BPP = 0x00,
|
||||
GRAYSCALE_2BPP = 0x01,
|
||||
GRAYSCALE_4BPP = 0x02,
|
||||
GRAYSCALE_8BPP = 0x03,
|
||||
PALETTE_1BPP = 0x04,
|
||||
PALETTE_2BPP = 0x05,
|
||||
PALETTE_4BPP = 0x06,
|
||||
PALETTE_8BPP = 0x07,
|
||||
} qp_image_format_t;
|
||||
|
||||
typedef enum painter_compression_t { IMAGE_UNCOMPRESSED, IMAGE_COMPRESSED_RLE } painter_compression_t;
|
171
quantum/painter/qp_stream.c
Normal file
171
quantum/painter/qp_stream.c
Normal file
|
@ -0,0 +1,171 @@
|
|||
// Copyright 2021 Nick Brassel (@tzarc)
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "qp_stream.h"
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Stream API
|
||||
|
||||
uint32_t qp_stream_read_impl(void *output_buf, uint32_t member_size, uint32_t num_members, qp_stream_t *stream) {
|
||||
uint8_t *output_ptr = (uint8_t *)output_buf;
|
||||
|
||||
uint32_t i;
|
||||
for (i = 0; i < (num_members * member_size); ++i) {
|
||||
int16_t c = qp_stream_get(stream);
|
||||
if (c < 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
output_ptr[i] = (uint8_t)(c & 0xFF);
|
||||
}
|
||||
|
||||
return i / member_size;
|
||||
}
|
||||
|
||||
uint32_t qp_stream_write_impl(const void *input_buf, uint32_t member_size, uint32_t num_members, qp_stream_t *stream) {
|
||||
uint8_t *input_ptr = (uint8_t *)input_buf;
|
||||
|
||||
uint32_t i;
|
||||
for (i = 0; i < (num_members * member_size); ++i) {
|
||||
if (!qp_stream_put(stream, input_ptr[i])) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return i / member_size;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Memory streams
|
||||
|
||||
int16_t mem_get(qp_stream_t *stream) {
|
||||
qp_memory_stream_t *s = (qp_memory_stream_t *)stream;
|
||||
if (s->position >= s->length) {
|
||||
s->is_eof = true;
|
||||
return STREAM_EOF;
|
||||
}
|
||||
return s->buffer[s->position++];
|
||||
}
|
||||
|
||||
bool mem_put(qp_stream_t *stream, uint8_t c) {
|
||||
qp_memory_stream_t *s = (qp_memory_stream_t *)stream;
|
||||
if (s->position >= s->length) {
|
||||
s->is_eof = true;
|
||||
return false;
|
||||
}
|
||||
s->buffer[s->position++] = c;
|
||||
return true;
|
||||
}
|
||||
|
||||
int mem_seek(qp_stream_t *stream, int32_t offset, int origin) {
|
||||
qp_memory_stream_t *s = (qp_memory_stream_t *)stream;
|
||||
|
||||
// Handle as per fseek
|
||||
int32_t position = s->position;
|
||||
switch (origin) {
|
||||
case SEEK_SET:
|
||||
position = offset;
|
||||
break;
|
||||
case SEEK_CUR:
|
||||
position += offset;
|
||||
break;
|
||||
case SEEK_END:
|
||||
position = s->length + offset;
|
||||
break;
|
||||
default:
|
||||
return -1;
|
||||
}
|
||||
|
||||
// If we're before the start, ignore it.
|
||||
if (position < 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
// If we're at the end it's okay, we only care if we're after the end for failure purposes -- as per lseek()
|
||||
if (position > s->length) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Update the offset
|
||||
s->position = position;
|
||||
|
||||
// Successful invocation of fseek() results in clearing of the EOF flag by default, mirror the same functionality
|
||||
s->is_eof = false;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int32_t mem_tell(qp_stream_t *stream) {
|
||||
qp_memory_stream_t *s = (qp_memory_stream_t *)stream;
|
||||
return s->position;
|
||||
}
|
||||
|
||||
bool mem_is_eof(qp_stream_t *stream) {
|
||||
qp_memory_stream_t *s = (qp_memory_stream_t *)stream;
|
||||
return s->is_eof;
|
||||
}
|
||||
|
||||
qp_memory_stream_t qp_make_memory_stream(void *buffer, int32_t length) {
|
||||
qp_memory_stream_t stream = {
|
||||
.base =
|
||||
{
|
||||
.get = mem_get,
|
||||
.put = mem_put,
|
||||
.seek = mem_seek,
|
||||
.tell = mem_tell,
|
||||
.is_eof = mem_is_eof,
|
||||
},
|
||||
.buffer = (uint8_t *)buffer,
|
||||
.length = length,
|
||||
.position = 0,
|
||||
};
|
||||
return stream;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// FILE streams
|
||||
|
||||
#ifdef QP_STREAM_HAS_FILE_IO
|
||||
|
||||
int16_t file_get(qp_stream_t *stream) {
|
||||
qp_file_stream_t *s = (qp_file_stream_t *)stream;
|
||||
int c = fgetc(s->file);
|
||||
if (c < 0 || feof(s->file)) return STREAM_EOF;
|
||||
return (uint16_t)c;
|
||||
}
|
||||
|
||||
bool file_put(qp_stream_t *stream, uint8_t c) {
|
||||
qp_file_stream_t *s = (qp_file_stream_t *)stream;
|
||||
return fputc(c, s->file) == c;
|
||||
}
|
||||
|
||||
int file_seek(qp_stream_t *stream, int32_t offset, int origin) {
|
||||
qp_file_stream_t *s = (qp_file_stream_t *)stream;
|
||||
return fseek(s->file, offset, origin);
|
||||
}
|
||||
|
||||
int32_t file_tell(qp_stream_t *stream) {
|
||||
qp_file_stream_t *s = (qp_file_stream_t *)stream;
|
||||
return (int32_t)ftell(s->file);
|
||||
}
|
||||
|
||||
bool file_is_eof(qp_stream_t *stream) {
|
||||
qp_file_stream_t *s = (qp_file_stream_t *)stream;
|
||||
return (bool)feof(s->file);
|
||||
}
|
||||
|
||||
qp_file_stream_t qp_make_file_stream(FILE *f) {
|
||||
qp_file_stream_t stream = {
|
||||
.base =
|
||||
{
|
||||
.get = file_get,
|
||||
.put = file_put,
|
||||
.seek = file_seek,
|
||||
.tell = file_tell,
|
||||
.is_eof = file_is_eof,
|
||||
},
|
||||
.file = f,
|
||||
};
|
||||
return stream;
|
||||
}
|
||||
#endif // QP_STREAM_HAS_FILE_IO
|
82
quantum/painter/qp_stream.h
Normal file
82
quantum/painter/qp_stream.h
Normal file
|
@ -0,0 +1,82 @@
|
|||
/* Copyright 2021 Nick Brassel (@tzarc)
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#include "qp_internal.h"
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Stream API
|
||||
|
||||
typedef struct qp_stream_t qp_stream_t;
|
||||
|
||||
#define qp_stream_get(stream_ptr) (((qp_stream_t *)(stream_ptr))->get((qp_stream_t *)(stream_ptr)))
|
||||
#define qp_stream_put(stream_ptr, c) (((qp_stream_t *)(stream_ptr))->put((qp_stream_t *)(stream_ptr), (c)))
|
||||
#define qp_stream_seek(stream_ptr, offset, origin) (((qp_stream_t *)(stream_ptr))->seek((qp_stream_t *)(stream_ptr), (offset), (origin)))
|
||||
#define qp_stream_tell(stream_ptr) (((qp_stream_t *)(stream_ptr))->tell((qp_stream_t *)(stream_ptr)))
|
||||
#define qp_stream_eof(stream_ptr) (((qp_stream_t *)(stream_ptr))->is_eof((qp_stream_t *)(stream_ptr)))
|
||||
#define qp_stream_setpos(stream_ptr, offset) qp_stream_seek((stream_ptr), (offset), SEEK_SET)
|
||||
#define qp_stream_getpos(stream_ptr) qp_stream_tell((stream_ptr))
|
||||
#define qp_stream_read(output_buf, member_size, num_members, stream_ptr) qp_stream_read_impl((output_buf), (member_size), (num_members), (qp_stream_t *)(stream_ptr))
|
||||
#define qp_stream_write(input_buf, member_size, num_members, stream_ptr) qp_stream_write_impl((input_buf), (member_size), (num_members), (qp_stream_t *)(stream_ptr))
|
||||
|
||||
uint32_t qp_stream_read_impl(void *output_buf, uint32_t member_size, uint32_t num_members, qp_stream_t *stream);
|
||||
uint32_t qp_stream_write_impl(const void *input_buf, uint32_t member_size, uint32_t num_members, qp_stream_t *stream);
|
||||
|
||||
#define STREAM_EOF ((int16_t)(-1))
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Stream definition
|
||||
|
||||
struct qp_stream_t {
|
||||
int16_t (*get)(qp_stream_t *stream);
|
||||
bool (*put)(qp_stream_t *stream, uint8_t c);
|
||||
int (*seek)(qp_stream_t *stream, int32_t offset, int origin);
|
||||
int32_t (*tell)(qp_stream_t *stream);
|
||||
bool (*is_eof)(qp_stream_t *stream);
|
||||
};
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Memory streams
|
||||
|
||||
typedef struct qp_memory_stream_t {
|
||||
qp_stream_t base;
|
||||
uint8_t * buffer;
|
||||
int32_t length;
|
||||
int32_t position;
|
||||
bool is_eof;
|
||||
} qp_memory_stream_t;
|
||||
|
||||
qp_memory_stream_t qp_make_memory_stream(void *buffer, int32_t length);
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// FILE streams
|
||||
|
||||
#ifdef QP_STREAM_HAS_FILE_IO
|
||||
|
||||
typedef struct qp_file_stream_t {
|
||||
qp_stream_t base;
|
||||
FILE * file;
|
||||
} qp_file_stream_t;
|
||||
|
||||
qp_file_stream_t qo_make_file_stream(FILE *f);
|
||||
|
||||
#endif // QP_STREAM_HAS_FILE_IO
|
116
quantum/painter/rules.mk
Normal file
116
quantum/painter/rules.mk
Normal file
|
@ -0,0 +1,116 @@
|
|||
# Quantum Painter Configurables
|
||||
QUANTUM_PAINTER_DRIVERS ?=
|
||||
QUANTUM_PAINTER_ANIMATIONS_ENABLE ?= yes
|
||||
|
||||
# The list of permissible drivers that can be listed in QUANTUM_PAINTER_DRIVERS
|
||||
VALID_QUANTUM_PAINTER_DRIVERS := ili9163_spi ili9341_spi st7789_spi gc9a01_spi ssd1351_spi
|
||||
|
||||
#-------------------------------------------------------------------------------
|
||||
|
||||
OPT_DEFS += -DQUANTUM_PAINTER_ENABLE
|
||||
COMMON_VPATH += $(QUANTUM_DIR)/painter
|
||||
SRC += \
|
||||
$(QUANTUM_DIR)/utf8.c \
|
||||
$(QUANTUM_DIR)/color.c \
|
||||
$(QUANTUM_DIR)/painter/qp.c \
|
||||
$(QUANTUM_DIR)/painter/qp_stream.c \
|
||||
$(QUANTUM_DIR)/painter/qgf.c \
|
||||
$(QUANTUM_DIR)/painter/qff.c \
|
||||
$(QUANTUM_DIR)/painter/qp_draw_core.c \
|
||||
$(QUANTUM_DIR)/painter/qp_draw_codec.c \
|
||||
$(QUANTUM_DIR)/painter/qp_draw_circle.c \
|
||||
$(QUANTUM_DIR)/painter/qp_draw_ellipse.c \
|
||||
$(QUANTUM_DIR)/painter/qp_draw_image.c \
|
||||
$(QUANTUM_DIR)/painter/qp_draw_text.c
|
||||
|
||||
# Check if people want animations... enable the defered exec if so.
|
||||
ifeq ($(strip $(QUANTUM_PAINTER_ANIMATIONS_ENABLE)), yes)
|
||||
DEFERRED_EXEC_ENABLE := yes
|
||||
OPT_DEFS += -DQUANTUM_PAINTER_ANIMATIONS_ENABLE
|
||||
endif
|
||||
|
||||
# Comms flags
|
||||
QUANTUM_PAINTER_NEEDS_COMMS_SPI ?= no
|
||||
|
||||
# Handler for each driver
|
||||
define handle_quantum_painter_driver
|
||||
CURRENT_PAINTER_DRIVER := $1
|
||||
|
||||
ifeq ($$(filter $$(strip $$(CURRENT_PAINTER_DRIVER)),$$(VALID_QUANTUM_PAINTER_DRIVERS)),)
|
||||
$$(error "$$(CURRENT_PAINTER_DRIVER)" is not a valid Quantum Painter driver)
|
||||
|
||||
else ifeq ($$(strip $$(CURRENT_PAINTER_DRIVER)),ili9163_spi)
|
||||
QUANTUM_PAINTER_NEEDS_COMMS_SPI := yes
|
||||
QUANTUM_PAINTER_NEEDS_COMMS_SPI_DC_RESET := yes
|
||||
OPT_DEFS += -DQUANTUM_PAINTER_ILI9163_ENABLE -DQUANTUM_PAINTER_ILI9163_SPI_ENABLE
|
||||
COMMON_VPATH += \
|
||||
$(DRIVER_PATH)/painter/tft_panel \
|
||||
$(DRIVER_PATH)/painter/ili9xxx
|
||||
SRC += \
|
||||
$(DRIVER_PATH)/painter/tft_panel/qp_tft_panel.c \
|
||||
$(DRIVER_PATH)/painter/ili9xxx/qp_ili9163.c \
|
||||
|
||||
else ifeq ($$(strip $$(CURRENT_PAINTER_DRIVER)),ili9341_spi)
|
||||
QUANTUM_PAINTER_NEEDS_COMMS_SPI := yes
|
||||
QUANTUM_PAINTER_NEEDS_COMMS_SPI_DC_RESET := yes
|
||||
OPT_DEFS += -DQUANTUM_PAINTER_ILI9341_ENABLE -DQUANTUM_PAINTER_ILI9341_SPI_ENABLE
|
||||
COMMON_VPATH += \
|
||||
$(DRIVER_PATH)/painter/tft_panel \
|
||||
$(DRIVER_PATH)/painter/ili9xxx
|
||||
SRC += \
|
||||
$(DRIVER_PATH)/painter/tft_panel/qp_tft_panel.c \
|
||||
$(DRIVER_PATH)/painter/ili9xxx/qp_ili9341.c \
|
||||
|
||||
else ifeq ($$(strip $$(CURRENT_PAINTER_DRIVER)),st7789_spi)
|
||||
QUANTUM_PAINTER_NEEDS_COMMS_SPI := yes
|
||||
QUANTUM_PAINTER_NEEDS_COMMS_SPI_DC_RESET := yes
|
||||
OPT_DEFS += -DQUANTUM_PAINTER_ST7789_ENABLE -DQUANTUM_PAINTER_ST7789_SPI_ENABLE
|
||||
COMMON_VPATH += \
|
||||
$(DRIVER_PATH)/painter/tft_panel \
|
||||
$(DRIVER_PATH)/painter/st77xx
|
||||
SRC += \
|
||||
$(DRIVER_PATH)/painter/tft_panel/qp_tft_panel.c \
|
||||
$(DRIVER_PATH)/painter/st77xx/qp_st7789.c
|
||||
|
||||
else ifeq ($$(strip $$(CURRENT_PAINTER_DRIVER)),gc9a01_spi)
|
||||
QUANTUM_PAINTER_NEEDS_COMMS_SPI := yes
|
||||
QUANTUM_PAINTER_NEEDS_COMMS_SPI_DC_RESET := yes
|
||||
OPT_DEFS += -DQUANTUM_PAINTER_GC9A01_ENABLE -DQUANTUM_PAINTER_GC9A01_SPI_ENABLE
|
||||
COMMON_VPATH += \
|
||||
$(DRIVER_PATH)/painter/tft_panel \
|
||||
$(DRIVER_PATH)/painter/gc9a01
|
||||
SRC += \
|
||||
$(DRIVER_PATH)/painter/tft_panel/qp_tft_panel.c \
|
||||
$(DRIVER_PATH)/painter/gc9a01/qp_gc9a01.c
|
||||
|
||||
else ifeq ($$(strip $$(CURRENT_PAINTER_DRIVER)),ssd1351_spi)
|
||||
QUANTUM_PAINTER_NEEDS_COMMS_SPI := yes
|
||||
QUANTUM_PAINTER_NEEDS_COMMS_SPI_DC_RESET := yes
|
||||
OPT_DEFS += -DQUANTUM_PAINTER_SSD1351_ENABLE -DQUANTUM_PAINTER_SSD1351_SPI_ENABLE
|
||||
COMMON_VPATH += \
|
||||
$(DRIVER_PATH)/painter/tft_panel \
|
||||
$(DRIVER_PATH)/painter/ssd1351
|
||||
SRC += \
|
||||
$(DRIVER_PATH)/painter/tft_panel/qp_tft_panel.c \
|
||||
$(DRIVER_PATH)/painter/ssd1351/qp_ssd1351.c
|
||||
|
||||
endif
|
||||
endef
|
||||
|
||||
# Iterate through the listed drivers for the build, including what's necessary
|
||||
$(foreach qp_driver,$(QUANTUM_PAINTER_DRIVERS),$(eval $(call handle_quantum_painter_driver,$(qp_driver))))
|
||||
|
||||
# If SPI comms is needed, set up the required files
|
||||
ifeq ($(strip $(QUANTUM_PAINTER_NEEDS_COMMS_SPI)), yes)
|
||||
OPT_DEFS += -DQUANTUM_PAINTER_SPI_ENABLE
|
||||
QUANTUM_LIB_SRC += spi_master.c
|
||||
VPATH += $(DRIVER_PATH)/painter/comms
|
||||
SRC += \
|
||||
$(QUANTUM_DIR)/painter/qp_comms.c \
|
||||
$(DRIVER_PATH)/painter/comms/qp_comms_spi.c
|
||||
|
||||
ifeq ($(strip $(QUANTUM_PAINTER_NEEDS_COMMS_SPI_DC_RESET)), yes)
|
||||
OPT_DEFS += -DQUANTUM_PAINTER_SPI_DC_RESET_ENABLE
|
||||
endif
|
||||
endif
|
||||
|
Loading…
Add table
Add a link
Reference in a new issue