Image Decoder
Source: src/image_decoder.c · 215 lines · 11 functions.
Module entry points
These functions are exposed to the rest of the project. Open one to follow its decisions and the helpers it calls.
How to read these diagrams
Each function has its own expandable diagram using the actual expressions and statements from this file. Decisions split into True and False; loop bodies are indented. Returns end the function immediately; break and continue follow their C meanings. Arrows between statements show their order when execution reaches them.
These are structural source diagrams before preprocessing, not runtime traces. Conditional compilation and macros remain source expressions. Ternary choices remain inside their statement. Calls are linked separately; callback execution and task scheduling are not inferred.
Functions in this file
hasExtension() · source line 20
static bool hasExtension(const char *name, const char *extension)
- Execute
const size_t name_length = strlen(name); - Execute
const size_t extension_length = strlen(extension); - Decision
name_length < extension_lengthTrue- Exit this function
return false;
FalseContinue after this decision.
- Execute
name += name_length - extension_length; - FOR loop
size_t index = 0; index < extension_length; ++indexRepeat body while the loop condition permits; then continue below.
- Decision
tolower((unsigned char)name[index]) != extension[index]True- Exit this function
return false;
FalseContinue after this decision.
- Exit this function
return true;
Read original function source
static bool hasExtension(const char *name, const char *extension)
{
const size_t name_length = strlen(name);
const size_t extension_length = strlen(extension);
if (name_length < extension_length) return false;
name += name_length - extension_length;
for (size_t index = 0; index < extension_length; ++index) {
if (tolower((unsigned char)name[index]) != extension[index]) return false;
}
return true;
}imageIsSupportedName() · source line 32
bool imageIsSupportedName(const char *name)
Calls into this documentation:
- Exit this function
return name != NULL && (hasExtension(name, ".jpg") || hasExtension(name, ".jpeg") || hasExtension(name, ".png"));
Read original function source
bool imageIsSupportedName(const char *name)
{
return name != NULL && (hasExtension(name, ".jpg") || hasExtension(name, ".jpeg") || hasExtension(name, ".png"));
}jpegInput() · source line 37
static UINT jpegInput(JDEC *decoder, BYTE *buffer, UINT bytes)
- Execute
FILE *file = (FILE *)decoder->device; - Decision
buffer != NULLTrue- Exit this function
return (UINT)fread(buffer, 1, bytes, file);
FalseContinue after this decision.
- Exit this function
return fseek(file, (long)bytes, SEEK_CUR) == 0 ? bytes : 0;
Read original function source
static UINT jpegInput(JDEC *decoder, BYTE *buffer, UINT bytes)
{
FILE *file = (FILE *)decoder->device;
if (buffer != NULL) return (UINT)fread(buffer, 1, bytes, file);
return fseek(file, (long)bytes, SEEK_CUR) == 0 ? bytes : 0;
}jpegOutput() · source line 44
static UINT jpegOutput(JDEC *decoder, void *bitmap, JRECT *rect)
- Execute
(void)decoder; - Decision
jpeg_target == NULL || rect->right + jpeg_offset_x >= LCD_WIDTH || rect->bottom + jpeg_offset_y >= LCD_HEIGHTTrue- Exit this function
return 0;
FalseContinue after this decision.
- Execute
const BYTE *source = bitmap; - Execute
const unsigned width = rect->right - rect->left + 1U; - Execute
const unsigned height = rect->bottom - rect->top + 1U; - FOR loop
unsigned row = 0; row < height; ++rowRepeat body while the loop condition permits; then continue below.
- FOR loop
unsigned column = 0; column < width; ++columnRepeat body while the loop condition permits; then continue below.
- Execute
const unsigned pixel = row * width + column; - Execute
jpeg_target[(rect->top + row + jpeg_offset_y) * LCD_WIDTH + rect->left + column + jpeg_offset_x] = RGB565(source[pixel * 3U], source[pixel * 3U + 1U], source[pixel * 3U + 2U]);
- Exit this function
return 1;
Read original function source
static UINT jpegOutput(JDEC *decoder, void *bitmap, JRECT *rect)
{
(void)decoder;
if (jpeg_target == NULL || rect->right + jpeg_offset_x >= LCD_WIDTH || rect->bottom + jpeg_offset_y >= LCD_HEIGHT) return 0;
const BYTE *source = bitmap;
const unsigned width = rect->right - rect->left + 1U;
const unsigned height = rect->bottom - rect->top + 1U;
for (unsigned row = 0; row < height; ++row) for (unsigned column = 0; column < width; ++column) {
const unsigned pixel = row * width + column;
jpeg_target[(rect->top + row + jpeg_offset_y) * LCD_WIDTH + rect->left + column + jpeg_offset_x] =
RGB565(source[pixel * 3U], source[pixel * 3U + 1U], source[pixel * 3U + 2U]);
}
return 1;
}decodeJpegFile() · source line 59
static bool decodeJpegFile(const char *path, uint16_t *frame, unsigned offset_x, unsigned offset_y, bool clear_target,
unsigned requested_scale)Calls into this documentation:
- Execute
FILE *file = fopen(path, "rb"); - Decision
file == NULLTrue- Exit this function
return false;
FalseContinue after this decision.
- Execute
JDEC decoder; - Execute
JRESULT result = jd_prepare(&decoder, jpegInput, jpeg_work, sizeof(jpeg_work), file); - Decision
result != JDR_OKTrue- Execute
fclose(file); - Exit this function
return false;
FalseContinue after this decision.
- Execute
unsigned scale = requested_scale; - Decision
clear_targetTrue- Execute
scale = 0; - WHILE loop
scale < 3U && (((decoder.width + ((1U << scale) - 1U)) >> scale) > LCD_WIDTH || ((decoder.height + ((1U << scale) - 1U)) >> scale) > LCD_HEIGHT)Repeat body while the loop condition permits; then continue below.
- Execute
++scale;
FalseContinue after this decision.
- Decision
scale > 3UTrue- Execute
fclose(file); - Exit this function
return false;
FalseContinue after this decision.
- Execute
const unsigned width = (decoder.width + ((1U << scale) - 1U)) >> scale; - Execute
const unsigned height = (decoder.height + ((1U << scale) - 1U)) >> scale; - Decision
width > LCD_WIDTH || height > LCD_HEIGHT || offset_x > LCD_WIDTH - width || offset_y > LCD_HEIGHT - heightTrue- Execute
fclose(file); - Exit this function
return false;
FalseContinue after this decision.
- Decision
clear_targetTrue- Execute
clear(frame, COLOR_BLACK);
FalseContinue after this decision.
- Execute
jpeg_offset_x = clear_target ? (LCD_WIDTH - width) / 2U : offset_x; - Execute
jpeg_offset_y = clear_target ? (LCD_HEIGHT - height) / 2U : offset_y; - Execute
jpeg_target = frame; - Execute
result = jd_decomp(&decoder, jpegOutput, scale); - Execute
jpeg_target = NULL; - Execute
fclose(file); - Exit this function
return result == JDR_OK;
Read original function source
static bool decodeJpegFile(const char *path, uint16_t *frame, unsigned offset_x, unsigned offset_y, bool clear_target,
unsigned requested_scale)
{
FILE *file = fopen(path, "rb");
if (file == NULL) return false;
JDEC decoder;
JRESULT result = jd_prepare(&decoder, jpegInput, jpeg_work, sizeof(jpeg_work), file);
if (result != JDR_OK) {
fclose(file);
return false;
}
unsigned scale = requested_scale;
if (clear_target) {
scale = 0;
while (scale < 3U && (((decoder.width + ((1U << scale) - 1U)) >> scale) > LCD_WIDTH ||
((decoder.height + ((1U << scale) - 1U)) >> scale) > LCD_HEIGHT)) ++scale;
}
if (scale > 3U) { fclose(file); return false; }
const unsigned width = (decoder.width + ((1U << scale) - 1U)) >> scale;
const unsigned height = (decoder.height + ((1U << scale) - 1U)) >> scale;
if (width > LCD_WIDTH || height > LCD_HEIGHT || offset_x > LCD_WIDTH - width || offset_y > LCD_HEIGHT - height) {
fclose(file);
return false;
}
if (clear_target) clear(frame, COLOR_BLACK);
jpeg_offset_x = clear_target ? (LCD_WIDTH - width) / 2U : offset_x;
jpeg_offset_y = clear_target ? (LCD_HEIGHT - height) / 2U : offset_y;
jpeg_target = frame;
result = jd_decomp(&decoder, jpegOutput, scale);
jpeg_target = NULL;
fclose(file);
return result == JDR_OK;
}readBigEndian() · source line 93
static uint32_t readBigEndian(const uint8_t *bytes)
- Exit this function
return ((uint32_t)bytes[0] << 24) | ((uint32_t)bytes[1] << 16) | ((uint32_t)bytes[2] << 8) | bytes[3];
Read original function source
static uint32_t readBigEndian(const uint8_t *bytes)
{
return ((uint32_t)bytes[0] << 24) | ((uint32_t)bytes[1] << 16) | ((uint32_t)bytes[2] << 8) | bytes[3];
}paeth() · source line 98
static uint8_t paeth(uint8_t left, uint8_t above, uint8_t upper_left)
- Execute
const int prediction = (int)left + above - upper_left; - Execute
const int left_distance = prediction > left ? prediction - left : left - prediction; - Execute
const int above_distance = prediction > above ? prediction - above : above - prediction; - Execute
const int upper_left_distance = prediction > upper_left ? prediction - upper_left : upper_left - prediction; - Decision
left_distance <= above_distance && left_distance <= upper_left_distanceTrue- Exit this function
return left;
FalseContinue after this decision.
- Exit this function
return above_distance <= upper_left_distance ? above : upper_left;
Read original function source
static uint8_t paeth(uint8_t left, uint8_t above, uint8_t upper_left)
{
const int prediction = (int)left + above - upper_left;
const int left_distance = prediction > left ? prediction - left : left - prediction;
const int above_distance = prediction > above ? prediction - above : above - prediction;
const int upper_left_distance = prediction > upper_left ? prediction - upper_left : upper_left - prediction;
if (left_distance <= above_distance && left_distance <= upper_left_distance) return left;
return above_distance <= upper_left_distance ? above : upper_left;
}decodePngFile() · source line 108
static bool decodePngFile(const char *path, uint16_t *frame)
Calls into this documentation:
- Execute
static const uint8_t png_signature[PNG_SIGNATURE_BYTES] = { 137, 80, 78, 71, 13, 10, 26, 10 }; - Execute
FILE *file = fopen(path, "rb"); - Execute
uint8_t signature[PNG_SIGNATURE_BYTES], header[8], ihdr[PNG_IHDR_BYTES]; - Execute
uint8_t *compressed = NULL; - Execute
uint8_t *pixels = NULL; - Execute
tinfl_decompressor *decompressor = NULL; - Execute
size_t compressed_bytes = 0; - Execute
bool result = false; - Execute
unsigned channels = 0; - Decision
file == NULL || fread(signature, 1, sizeof(signature), file) != sizeof(signature) || memcmp(signature, png_signature, sizeof(signature)) != 0True- Jump to label
goto done;
FalseContinue after this decision.
- FOR loop
;;Repeat body while the loop condition permits; then continue below.
- Decision
fread(header, 1, sizeof(header), file) != sizeof(header)True- Jump to label
goto done;
FalseContinue after this decision.
- Execute
const uint32_t length = readBigEndian(header); - Decision
length > PNG_MAX_COMPRESSED_BYTESTrue- Jump to label
goto done;
FalseContinue after this decision.
- Decision
memcmp(header + 4, "IHDR", 4) == 0True- Decision
length != PNG_IHDR_BYTES || fread(ihdr, 1, sizeof(ihdr), file) != sizeof(ihdr)True- Jump to label
goto done;
FalseContinue after this decision.
- Decision
readBigEndian(ihdr) != LCD_WIDTH || readBigEndian(ihdr + 4) != LCD_HEIGHT || ihdr[8] != 8U || ihdr[10] != 0U || ihdr[11] != 0U || ihdr[12] != 0UTrue- Jump to label
goto done;
FalseContinue after this decision.
- Decision
ihdr[9] == 2UTrue- Execute
channels = 3U;
False- Decision
ihdr[9] == 6UTrue- Execute
channels = 4U;
False- Jump to label
goto done;
False- Decision
memcmp(header + 4, "IDAT", 4) == 0True- Decision
length > PNG_MAX_COMPRESSED_BYTES - compressed_bytesTrue- Jump to label
goto done;
FalseContinue after this decision.
- Execute
uint8_t *expanded = heap_caps_realloc(compressed, compressed_bytes + length, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT); - Decision
expanded == NULLTrue- Jump to label
goto done;
FalseContinue after this decision.
- Execute
compressed = expanded; - Decision
fread(compressed + compressed_bytes, 1, length, file) != lengthTrue- Jump to label
goto done;
FalseContinue after this decision.
- Execute
compressed_bytes += length;
False- Decision
fseek(file, (long)length, SEEK_CUR) != 0True- Jump to label
goto done;
FalseContinue after this decision.
- Decision
fseek(file, 4L, SEEK_CUR) != 0True- Jump to label
goto done;
FalseContinue after this decision.
- Decision
memcmp(header + 4, "IEND", 4) == 0True- Exit enclosing loop or switch
break;
FalseContinue after this decision.
- Decision
channels == 0U || compressed_bytes == 0UTrue- Jump to label
goto done;
FalseContinue after this decision.
- Execute
const size_t row_bytes = LCD_WIDTH * channels; - Execute
const size_t decoded_bytes = LCD_HEIGHT * (row_bytes + 1U); - Execute
pixels = heap_caps_malloc(decoded_bytes, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT); - Execute
decompressor = calloc(1, sizeof(*decompressor)); - Decision
pixels == NULL || decompressor == NULLTrue- Jump to label
goto done;
FalseContinue after this decision.
- Execute
tinfl_init(decompressor); - Execute
size_t input_position = 0, output_position = 0; - FOR loop
;;Repeat body while the loop condition permits; then continue below.
- Execute
size_t input_bytes = compressed_bytes - input_position; - Execute
size_t output_bytes = decoded_bytes - output_position; - Execute
const tinfl_status status = tinfl_decompress(decompressor, compressed + input_position, &input_bytes, pixels, pixels + output_position, &output_bytes, TINFL_FLAG_PARSE_ZLIB_HEADER | TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF); - Execute
input_position += input_bytes; - Execute
output_position += output_bytes; - Decision
status == TINFL_STATUS_DONETrue- Exit enclosing loop or switch
break;
FalseContinue after this decision.
- Decision
status < TINFL_STATUS_DONE || (input_bytes == 0U && output_bytes == 0U)True- Jump to label
goto done;
FalseContinue after this decision.
- Decision
output_position != decoded_bytesTrue- Jump to label
goto done;
FalseContinue after this decision.
- FOR loop
unsigned row = 0; row < LCD_HEIGHT; ++rowRepeat body while the loop condition permits; then continue below.
- Execute
uint8_t *current = pixels + row * (row_bytes + 1U) + 1U; - Execute
const uint8_t *previous = row == 0U ? NULL : current - (row_bytes + 1U); - Execute
const uint8_t filter = current[-1]; - Decision
filter > 4UTrue- Jump to label
goto done;
FalseContinue after this decision.
- FOR loop
size_t column = 0; column < row_bytes; ++columnRepeat body while the loop condition permits; then continue below.
- Execute
const uint8_t left = column < channels ? 0U : current[column - channels]; - Execute
const uint8_t above = previous == NULL ? 0U : previous[column]; - Execute
const uint8_t upper_left = previous == NULL || column < channels ? 0U : previous[column - channels]; - Decision
filter == 1UTrue- Execute
current[column] = (uint8_t)(current[column] + left);
False- Decision
filter == 2UTrue- Execute
current[column] = (uint8_t)(current[column] + above);
False- Decision
filter == 3UTrue- Execute
current[column] = (uint8_t)(current[column] + ((left + above) >> 1));
False- Decision
filter == 4UTrue- Execute
current[column] = (uint8_t)(current[column] + paeth(left, above, upper_left));
FalseContinue after this decision.
- FOR loop
unsigned y = 0; y < LCD_HEIGHT; ++yRepeat body while the loop condition permits; then continue below.
- FOR loop
unsigned x = 0; x < LCD_WIDTH; ++xRepeat body while the loop condition permits; then continue below.
- Execute
const uint8_t *pixel = pixels + y * (row_bytes + 1U) + 1U + x * channels; - Execute
const uint8_t alpha = channels == 4U ? pixel[3] : 255U; - Execute
frame[y * LCD_WIDTH + x] = RGB565((uint8_t)((pixel[0] * alpha) / 255U), (uint8_t)((pixel[1] * alpha) / 255U), (uint8_t)((pixel[2] * alpha) / 255U));
- Execute
result = true; - Execute
done: free(pixels); - Execute
free(compressed); - Execute
free(decompressor); - Decision
file != NULLTrue- Execute
fclose(file);
FalseContinue after this decision.
- Exit this function
return result;
Read original function source
static bool decodePngFile(const char *path, uint16_t *frame)
{
static const uint8_t png_signature[PNG_SIGNATURE_BYTES] = { 137, 80, 78, 71, 13, 10, 26, 10 };
FILE *file = fopen(path, "rb");
uint8_t signature[PNG_SIGNATURE_BYTES], header[8], ihdr[PNG_IHDR_BYTES];
uint8_t *compressed = NULL;
uint8_t *pixels = NULL;
tinfl_decompressor *decompressor = NULL;
size_t compressed_bytes = 0;
bool result = false;
unsigned channels = 0;
if (file == NULL || fread(signature, 1, sizeof(signature), file) != sizeof(signature) ||
memcmp(signature, png_signature, sizeof(signature)) != 0) goto done;
for (;;) {
if (fread(header, 1, sizeof(header), file) != sizeof(header)) goto done;
const uint32_t length = readBigEndian(header);
if (length > PNG_MAX_COMPRESSED_BYTES) goto done;
if (memcmp(header + 4, "IHDR", 4) == 0) {
if (length != PNG_IHDR_BYTES || fread(ihdr, 1, sizeof(ihdr), file) != sizeof(ihdr)) goto done;
if (readBigEndian(ihdr) != LCD_WIDTH || readBigEndian(ihdr + 4) != LCD_HEIGHT ||
ihdr[8] != 8U || ihdr[10] != 0U || ihdr[11] != 0U || ihdr[12] != 0U) goto done;
if (ihdr[9] == 2U) channels = 3U;
else if (ihdr[9] == 6U) channels = 4U;
else goto done;
} else if (memcmp(header + 4, "IDAT", 4) == 0) {
if (length > PNG_MAX_COMPRESSED_BYTES - compressed_bytes) goto done;
uint8_t *expanded = heap_caps_realloc(compressed, compressed_bytes + length,
MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT);
if (expanded == NULL) goto done;
compressed = expanded;
if (fread(compressed + compressed_bytes, 1, length, file) != length) goto done;
compressed_bytes += length;
} else if (fseek(file, (long)length, SEEK_CUR) != 0) goto done;
if (fseek(file, 4L, SEEK_CUR) != 0) goto done;
if (memcmp(header + 4, "IEND", 4) == 0) break;
}
if (channels == 0U || compressed_bytes == 0U) goto done;
const size_t row_bytes = LCD_WIDTH * channels;
const size_t decoded_bytes = LCD_HEIGHT * (row_bytes + 1U);
pixels = heap_caps_malloc(decoded_bytes, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT);
decompressor = calloc(1, sizeof(*decompressor));
if (pixels == NULL || decompressor == NULL) goto done;
tinfl_init(decompressor);
size_t input_position = 0, output_position = 0;
for (;;) {
size_t input_bytes = compressed_bytes - input_position;
size_t output_bytes = decoded_bytes - output_position;
const tinfl_status status = tinfl_decompress(decompressor, compressed + input_position, &input_bytes,
pixels, pixels + output_position, &output_bytes,
TINFL_FLAG_PARSE_ZLIB_HEADER | TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF);
input_position += input_bytes;
output_position += output_bytes;
if (status == TINFL_STATUS_DONE) break;
if (status < TINFL_STATUS_DONE || (input_bytes == 0U && output_bytes == 0U)) goto done;
}
if (output_position != decoded_bytes) goto done;
for (unsigned row = 0; row < LCD_HEIGHT; ++row) {
uint8_t *current = pixels + row * (row_bytes + 1U) + 1U;
const uint8_t *previous = row == 0U ? NULL : current - (row_bytes + 1U);
const uint8_t filter = current[-1];
if (filter > 4U) goto done;
for (size_t column = 0; column < row_bytes; ++column) {
const uint8_t left = column < channels ? 0U : current[column - channels];
const uint8_t above = previous == NULL ? 0U : previous[column];
const uint8_t upper_left = previous == NULL || column < channels ? 0U : previous[column - channels];
if (filter == 1U) current[column] = (uint8_t)(current[column] + left);
else if (filter == 2U) current[column] = (uint8_t)(current[column] + above);
else if (filter == 3U) current[column] = (uint8_t)(current[column] + ((left + above) >> 1));
else if (filter == 4U) current[column] = (uint8_t)(current[column] + paeth(left, above, upper_left));
}
}
for (unsigned y = 0; y < LCD_HEIGHT; ++y) for (unsigned x = 0; x < LCD_WIDTH; ++x) {
const uint8_t *pixel = pixels + y * (row_bytes + 1U) + 1U + x * channels;
const uint8_t alpha = channels == 4U ? pixel[3] : 255U;
frame[y * LCD_WIDTH + x] = RGB565((uint8_t)((pixel[0] * alpha) / 255U),
(uint8_t)((pixel[1] * alpha) / 255U),
(uint8_t)((pixel[2] * alpha) / 255U));
}
result = true;
done:
free(pixels);
free(compressed);
free(decompressor);
if (file != NULL) fclose(file);
return result;
}imageDecodeFile() · source line 197
bool imageDecodeFile(const char *path, uint16_t *frame)
Calls into this documentation:
- Decision
path == NULL || frame == NULLTrue- Exit this function
return false;
FalseContinue after this decision.
- Decision
hasExtension(path, ".jpg") || hasExtension(path, ".jpeg")True- Exit this function
return decodeJpegFile(path, frame, 0U, 0U, true, 0U);
FalseContinue after this decision.
- Decision
hasExtension(path, ".png")True- Exit this function
return decodePngFile(path, frame);
FalseContinue after this decision.
- Exit this function
return false;
Read original function source
bool imageDecodeFile(const char *path, uint16_t *frame)
{
if (path == NULL || frame == NULL) return false;
if (hasExtension(path, ".jpg") || hasExtension(path, ".jpeg")) return decodeJpegFile(path, frame, 0U, 0U, true, 0U);
if (hasExtension(path, ".png")) return decodePngFile(path, frame);
return false;
}imageDecodeJpegAt() · source line 205
bool imageDecodeJpegAt(const char *path, uint16_t *frame, unsigned x, unsigned y)
Calls into this documentation:
- Decision
path == NULL || frame == NULL || (!hasExtension(path, ".jpg") && !hasExtension(path, ".jpeg"))True- Exit this function
return false;
FalseContinue after this decision.
- Exit this function
return decodeJpegFile(path, frame, x, y, false, 0U);
Read original function source
bool imageDecodeJpegAt(const char *path, uint16_t *frame, unsigned x, unsigned y)
{
if (path == NULL || frame == NULL || (!hasExtension(path, ".jpg") && !hasExtension(path, ".jpeg"))) return false;
return decodeJpegFile(path, frame, x, y, false, 0U);
}imageDecodeJpegScaledAt() · source line 211
bool imageDecodeJpegScaledAt(const char *path, uint16_t *frame, unsigned x, unsigned y, unsigned scale)
Calls into this documentation:
- Decision
path == NULL || frame == NULL || scale > 3U || (!hasExtension(path, ".jpg") && !hasExtension(path, ".jpeg"))True- Exit this function
return false;
FalseContinue after this decision.
- Exit this function
return decodeJpegFile(path, frame, x, y, false, scale);
Read original function source
bool imageDecodeJpegScaledAt(const char *path, uint16_t *frame, unsigned x, unsigned y, unsigned scale)
{
if (path == NULL || frame == NULL || scale > 3U || (!hasExtension(path, ".jpg") && !hasExtension(path, ".jpeg"))) return false;
return decodeJpegFile(path, frame, x, y, false, scale);
}Complete original file, including constants and data
/* Coded By JD(2026) */
#error "Reference listing only — not provided as build-ready source."
#include "image_decoder.h"
#include "game_graphics.h"
#include "esp32s3/rom/tjpgd.h"
#include "esp_heap_caps.h"
#include "miniz.h"
#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define JPEG_WORK_BYTES 8192U
#define PNG_SIGNATURE_BYTES 8U
#define PNG_IHDR_BYTES 13U
#define PNG_MAX_COMPRESSED_BYTES (1024U * 1024U)
static uint8_t jpeg_work[JPEG_WORK_BYTES];
static uint16_t *jpeg_target;
static unsigned jpeg_offset_x, jpeg_offset_y;
static bool hasExtension(const char *name, const char *extension)
{
const size_t name_length = strlen(name);
const size_t extension_length = strlen(extension);
if (name_length < extension_length) return false;
name += name_length - extension_length;
for (size_t index = 0; index < extension_length; ++index) {
if (tolower((unsigned char)name[index]) != extension[index]) return false;
}
return true;
}
bool imageIsSupportedName(const char *name)
{
return name != NULL && (hasExtension(name, ".jpg") || hasExtension(name, ".jpeg") || hasExtension(name, ".png"));
}
static UINT jpegInput(JDEC *decoder, BYTE *buffer, UINT bytes)
{
FILE *file = (FILE *)decoder->device;
if (buffer != NULL) return (UINT)fread(buffer, 1, bytes, file);
return fseek(file, (long)bytes, SEEK_CUR) == 0 ? bytes : 0;
}
static UINT jpegOutput(JDEC *decoder, void *bitmap, JRECT *rect)
{
(void)decoder;
if (jpeg_target == NULL || rect->right + jpeg_offset_x >= LCD_WIDTH || rect->bottom + jpeg_offset_y >= LCD_HEIGHT) return 0;
const BYTE *source = bitmap;
const unsigned width = rect->right - rect->left + 1U;
const unsigned height = rect->bottom - rect->top + 1U;
for (unsigned row = 0; row < height; ++row) for (unsigned column = 0; column < width; ++column) {
const unsigned pixel = row * width + column;
jpeg_target[(rect->top + row + jpeg_offset_y) * LCD_WIDTH + rect->left + column + jpeg_offset_x] =
RGB565(source[pixel * 3U], source[pixel * 3U + 1U], source[pixel * 3U + 2U]);
}
return 1;
}
static bool decodeJpegFile(const char *path, uint16_t *frame, unsigned offset_x, unsigned offset_y, bool clear_target,
unsigned requested_scale)
{
FILE *file = fopen(path, "rb");
if (file == NULL) return false;
JDEC decoder;
JRESULT result = jd_prepare(&decoder, jpegInput, jpeg_work, sizeof(jpeg_work), file);
if (result != JDR_OK) {
fclose(file);
return false;
}
unsigned scale = requested_scale;
if (clear_target) {
scale = 0;
while (scale < 3U && (((decoder.width + ((1U << scale) - 1U)) >> scale) > LCD_WIDTH ||
((decoder.height + ((1U << scale) - 1U)) >> scale) > LCD_HEIGHT)) ++scale;
}
if (scale > 3U) { fclose(file); return false; }
const unsigned width = (decoder.width + ((1U << scale) - 1U)) >> scale;
const unsigned height = (decoder.height + ((1U << scale) - 1U)) >> scale;
if (width > LCD_WIDTH || height > LCD_HEIGHT || offset_x > LCD_WIDTH - width || offset_y > LCD_HEIGHT - height) {
fclose(file);
return false;
}
if (clear_target) clear(frame, COLOR_BLACK);
jpeg_offset_x = clear_target ? (LCD_WIDTH - width) / 2U : offset_x;
jpeg_offset_y = clear_target ? (LCD_HEIGHT - height) / 2U : offset_y;
jpeg_target = frame;
result = jd_decomp(&decoder, jpegOutput, scale);
jpeg_target = NULL;
fclose(file);
return result == JDR_OK;
}
static uint32_t readBigEndian(const uint8_t *bytes)
{
return ((uint32_t)bytes[0] << 24) | ((uint32_t)bytes[1] << 16) | ((uint32_t)bytes[2] << 8) | bytes[3];
}
static uint8_t paeth(uint8_t left, uint8_t above, uint8_t upper_left)
{
const int prediction = (int)left + above - upper_left;
const int left_distance = prediction > left ? prediction - left : left - prediction;
const int above_distance = prediction > above ? prediction - above : above - prediction;
const int upper_left_distance = prediction > upper_left ? prediction - upper_left : upper_left - prediction;
if (left_distance <= above_distance && left_distance <= upper_left_distance) return left;
return above_distance <= upper_left_distance ? above : upper_left;
}
static bool decodePngFile(const char *path, uint16_t *frame)
{
static const uint8_t png_signature[PNG_SIGNATURE_BYTES] = { 137, 80, 78, 71, 13, 10, 26, 10 };
FILE *file = fopen(path, "rb");
uint8_t signature[PNG_SIGNATURE_BYTES], header[8], ihdr[PNG_IHDR_BYTES];
uint8_t *compressed = NULL;
uint8_t *pixels = NULL;
tinfl_decompressor *decompressor = NULL;
size_t compressed_bytes = 0;
bool result = false;
unsigned channels = 0;
if (file == NULL || fread(signature, 1, sizeof(signature), file) != sizeof(signature) ||
memcmp(signature, png_signature, sizeof(signature)) != 0) goto done;
for (;;) {
if (fread(header, 1, sizeof(header), file) != sizeof(header)) goto done;
const uint32_t length = readBigEndian(header);
if (length > PNG_MAX_COMPRESSED_BYTES) goto done;
if (memcmp(header + 4, "IHDR", 4) == 0) {
if (length != PNG_IHDR_BYTES || fread(ihdr, 1, sizeof(ihdr), file) != sizeof(ihdr)) goto done;
if (readBigEndian(ihdr) != LCD_WIDTH || readBigEndian(ihdr + 4) != LCD_HEIGHT ||
ihdr[8] != 8U || ihdr[10] != 0U || ihdr[11] != 0U || ihdr[12] != 0U) goto done;
if (ihdr[9] == 2U) channels = 3U;
else if (ihdr[9] == 6U) channels = 4U;
else goto done;
} else if (memcmp(header + 4, "IDAT", 4) == 0) {
if (length > PNG_MAX_COMPRESSED_BYTES - compressed_bytes) goto done;
uint8_t *expanded = heap_caps_realloc(compressed, compressed_bytes + length,
MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT);
if (expanded == NULL) goto done;
compressed = expanded;
if (fread(compressed + compressed_bytes, 1, length, file) != length) goto done;
compressed_bytes += length;
} else if (fseek(file, (long)length, SEEK_CUR) != 0) goto done;
if (fseek(file, 4L, SEEK_CUR) != 0) goto done;
if (memcmp(header + 4, "IEND", 4) == 0) break;
}
if (channels == 0U || compressed_bytes == 0U) goto done;
const size_t row_bytes = LCD_WIDTH * channels;
const size_t decoded_bytes = LCD_HEIGHT * (row_bytes + 1U);
pixels = heap_caps_malloc(decoded_bytes, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT);
decompressor = calloc(1, sizeof(*decompressor));
if (pixels == NULL || decompressor == NULL) goto done;
tinfl_init(decompressor);
size_t input_position = 0, output_position = 0;
for (;;) {
size_t input_bytes = compressed_bytes - input_position;
size_t output_bytes = decoded_bytes - output_position;
const tinfl_status status = tinfl_decompress(decompressor, compressed + input_position, &input_bytes,
pixels, pixels + output_position, &output_bytes,
TINFL_FLAG_PARSE_ZLIB_HEADER | TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF);
input_position += input_bytes;
output_position += output_bytes;
if (status == TINFL_STATUS_DONE) break;
if (status < TINFL_STATUS_DONE || (input_bytes == 0U && output_bytes == 0U)) goto done;
}
if (output_position != decoded_bytes) goto done;
for (unsigned row = 0; row < LCD_HEIGHT; ++row) {
uint8_t *current = pixels + row * (row_bytes + 1U) + 1U;
const uint8_t *previous = row == 0U ? NULL : current - (row_bytes + 1U);
const uint8_t filter = current[-1];
if (filter > 4U) goto done;
for (size_t column = 0; column < row_bytes; ++column) {
const uint8_t left = column < channels ? 0U : current[column - channels];
const uint8_t above = previous == NULL ? 0U : previous[column];
const uint8_t upper_left = previous == NULL || column < channels ? 0U : previous[column - channels];
if (filter == 1U) current[column] = (uint8_t)(current[column] + left);
else if (filter == 2U) current[column] = (uint8_t)(current[column] + above);
else if (filter == 3U) current[column] = (uint8_t)(current[column] + ((left + above) >> 1));
else if (filter == 4U) current[column] = (uint8_t)(current[column] + paeth(left, above, upper_left));
}
}
for (unsigned y = 0; y < LCD_HEIGHT; ++y) for (unsigned x = 0; x < LCD_WIDTH; ++x) {
const uint8_t *pixel = pixels + y * (row_bytes + 1U) + 1U + x * channels;
const uint8_t alpha = channels == 4U ? pixel[3] : 255U;
frame[y * LCD_WIDTH + x] = RGB565((uint8_t)((pixel[0] * alpha) / 255U),
(uint8_t)((pixel[1] * alpha) / 255U),
(uint8_t)((pixel[2] * alpha) / 255U));
}
result = true;
done:
free(pixels);
free(compressed);
free(decompressor);
if (file != NULL) fclose(file);
return result;
}
bool imageDecodeFile(const char *path, uint16_t *frame)
{
if (path == NULL || frame == NULL) return false;
if (hasExtension(path, ".jpg") || hasExtension(path, ".jpeg")) return decodeJpegFile(path, frame, 0U, 0U, true, 0U);
if (hasExtension(path, ".png")) return decodePngFile(path, frame);
return false;
}
bool imageDecodeJpegAt(const char *path, uint16_t *frame, unsigned x, unsigned y)
{
if (path == NULL || frame == NULL || (!hasExtension(path, ".jpg") && !hasExtension(path, ".jpeg"))) return false;
return decodeJpegFile(path, frame, x, y, false, 0U);
}
bool imageDecodeJpegScaledAt(const char *path, uint16_t *frame, unsigned x, unsigned y, unsigned scale)
{
if (path == NULL || frame == NULL || scale > 3U || (!hasExtension(path, ".jpg") && !hasExtension(path, ".jpeg"))) return false;
return decodeJpegFile(path, frame, x, y, false, scale);
}