Game Graphics

Source: src/game_graphics.c · 874 lines · 38 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

graphics_text_effect_calculate() · source line 9
void graphics_text_effect_calculate(graphics_text_effect_t effect, uint32_t elapsed_ms,
                                    unsigned text_length,
                                    const graphics_text_effect_params_t *params,
                                    uint16_t base_color,
                                    const uint16_t *colour_cycle,
                                    unsigned colour_cycle_count,
                                    graphics_text_effect_state_t *state)
  1. Decisionstate == NULL
    True
    1. Exit this functionreturn;
    False

    Continue after this decision.

  2. Execute*state = (graphics_text_effect_state_t){ .visible_characters = text_length, .color = base_color, .scale = 1.0f, };
  3. Decisionparams == NULL || effect >= GRAPHICS_TEXT_EFFECT_COUNT
    True
    1. Exit this functionreturn;
    False

    Continue after this decision.

  4. Select a caseeffect

    Enter matching case; follow source fall-through until a break, return or end.

      1. Case / labelcase GRAPHICS_TEXT_EFFECT_TRAVEL_LEFT:
      2. Decisionparams->travel_distance > 0
        True
          1. Executeconst unsigned cycle = (unsigned)params->travel_distance * 2U;
          2. Executestate->x_offset = params->travel_distance - (int)((elapsed_ms * 55U / 1000U) % cycle);
        False

        Continue after this decision.

      3. Exit enclosing loop or switchbreak;
      4. Case / labelcase GRAPHICS_TEXT_EFFECT_DRIFT:
      5. Decisionparams->drift_distance > 0
        True
          1. Executeconst unsigned cycle = (unsigned)params->drift_distance * 2U;
          2. Executestate->x_offset = (int)((elapsed_ms * 55U / 1000U) % cycle) - params->drift_distance;
        False

        Continue after this decision.

      6. Exit enclosing loop or switchbreak;
      7. Case / labelcase GRAPHICS_TEXT_EFFECT_BOB:
      8. Executestate->y_offset = (int)(sinf((float)elapsed_ms / 445.0f) * (float)params->vertical_amplitude);
      9. Exit enclosing loop or switchbreak;
      10. Case / labelcase GRAPHICS_TEXT_EFFECT_WAVE:
      11. Executestate->wave = true;
      12. Exit enclosing loop or switchbreak;
      13. Case / labelcase GRAPHICS_TEXT_EFFECT_JITTER:
      14. Decisionparams->jitter_amplitude > 0
        True
          1. Executeconst unsigned range = (unsigned)params->jitter_amplitude * 2U + 1U;
          2. Executestate->x_offset = (int)((elapsed_ms / 18U) % range) - params->jitter_amplitude;
        False

        Continue after this decision.

      15. Exit enclosing loop or switchbreak;
      16. Case / labelcase GRAPHICS_TEXT_EFFECT_REVEAL:
      17. Executestate->visible_characters = (elapsed_ms / 130U) % (text_length + 1U);
      18. Exit enclosing loop or switchbreak;
      19. Case / labelcase GRAPHICS_TEXT_EFFECT_COLOUR_CYCLE:
      20. Decisioncolour_cycle != NULL && colour_cycle_count > 0U
        True
          1. Executestate->color = colour_cycle[(elapsed_ms / 180U) % colour_cycle_count];
        False

        Continue after this decision.

      21. Exit enclosing loop or switchbreak;
      22. Case / labelcase GRAPHICS_TEXT_EFFECT_SWING:
      23. Executestate->angle_degrees = sinf((float)elapsed_ms / 382.0f) * params->maximum_angle_degrees;
      24. Exit enclosing loop or switchbreak;
      25. Case / labelcase GRAPHICS_TEXT_EFFECT_PULSE:
      26. Decisionparams->minimum_scale > 0.0f && params->maximum_scale >= params->minimum_scale
        True
          1. Executeconst float progress = (sinf((float)elapsed_ms / 318.0f) + 1.0f) * 0.5f;
          2. Executestate->scale = params->minimum_scale + (params->maximum_scale - params->minimum_scale) * progress;
        False

        Continue after this decision.

      27. Exit enclosing loop or switchbreak;
      28. Case / labelcase GRAPHICS_TEXT_EFFECT_DROP:
      29. Decisionparams->drop_height > 0
        True
          1. Executeconst uint32_t drop_elapsed = elapsed_ms % 2800U;
          2. Executeconst int travelled = (int)(drop_elapsed * (uint32_t)params->drop_height / 700U);
          3. Executestate->y_offset = params->drop_height - (travelled < params->drop_height ? travelled : params->drop_height);
        False

        Continue after this decision.

      30. Exit enclosing loop or switchbreak;
      31. Case / labelcase GRAPHICS_TEXT_EFFECT_TYPEWRITER:
      32. Executestate->visible_characters = (elapsed_ms / 90U) % (text_length + 1U);
      33. Exit enclosing loop or switchbreak;
      34. Case / labeldefault:
      35. Exit enclosing loop or switchbreak;
Read original function source
void graphics_text_effect_calculate(graphics_text_effect_t effect, uint32_t elapsed_ms,
                                    unsigned text_length,
                                    const graphics_text_effect_params_t *params,
                                    uint16_t base_color,
                                    const uint16_t *colour_cycle,
                                    unsigned colour_cycle_count,
                                    graphics_text_effect_state_t *state)
{
    if (state == NULL) return;

    *state = (graphics_text_effect_state_t){
        .visible_characters = text_length,
        .color = base_color,
        .scale = 1.0f,
    };
    if (params == NULL || effect >= GRAPHICS_TEXT_EFFECT_COUNT) return;

    switch (effect) {
    case GRAPHICS_TEXT_EFFECT_TRAVEL_LEFT:
        if (params->travel_distance > 0) {
            const unsigned cycle = (unsigned)params->travel_distance * 2U;
            state->x_offset = params->travel_distance -
                (int)((elapsed_ms * 55U / 1000U) % cycle);
        }
        break;
    case GRAPHICS_TEXT_EFFECT_DRIFT:
        if (params->drift_distance > 0) {
            const unsigned cycle = (unsigned)params->drift_distance * 2U;
            state->x_offset = (int)((elapsed_ms * 55U / 1000U) % cycle) -
                              params->drift_distance;
        }
        break;
    case GRAPHICS_TEXT_EFFECT_BOB:
        state->y_offset = (int)(sinf((float)elapsed_ms / 445.0f) *
                                (float)params->vertical_amplitude);
        break;
    case GRAPHICS_TEXT_EFFECT_WAVE:
        state->wave = true;
        break;
    case GRAPHICS_TEXT_EFFECT_JITTER:
        if (params->jitter_amplitude > 0) {
            const unsigned range = (unsigned)params->jitter_amplitude * 2U + 1U;
            state->x_offset = (int)((elapsed_ms / 18U) % range) -
                              params->jitter_amplitude;
        }
        break;
    case GRAPHICS_TEXT_EFFECT_REVEAL:
        state->visible_characters = (elapsed_ms / 130U) % (text_length + 1U);
        break;
    case GRAPHICS_TEXT_EFFECT_COLOUR_CYCLE:
        if (colour_cycle != NULL && colour_cycle_count > 0U) {
            state->color = colour_cycle[(elapsed_ms / 180U) % colour_cycle_count];
        }
        break;
    case GRAPHICS_TEXT_EFFECT_SWING:
        state->angle_degrees = sinf((float)elapsed_ms / 382.0f) *
                               params->maximum_angle_degrees;
        break;
    case GRAPHICS_TEXT_EFFECT_PULSE:
        if (params->minimum_scale > 0.0f &&
            params->maximum_scale >= params->minimum_scale) {
            const float progress = (sinf((float)elapsed_ms / 318.0f) + 1.0f) * 0.5f;
            state->scale = params->minimum_scale +
                           (params->maximum_scale - params->minimum_scale) * progress;
        }
        break;
    case GRAPHICS_TEXT_EFFECT_DROP:
        if (params->drop_height > 0) {
            const uint32_t drop_elapsed = elapsed_ms % 2800U;
            const int travelled = (int)(drop_elapsed * (uint32_t)params->drop_height / 700U);
            state->y_offset = params->drop_height -
                              (travelled < params->drop_height ? travelled : params->drop_height);
        }
        break;
    case GRAPHICS_TEXT_EFFECT_TYPEWRITER:
        state->visible_characters = (elapsed_ms / 90U) % (text_length + 1U);
        break;
    default:
        break;
    }
}
graphics_text_effect_wave_offset() · source line 91
int graphics_text_effect_wave_offset(uint32_t elapsed_ms, unsigned character_index,
                                     int wave_amplitude)
  1. Decisionwave_amplitude <= 0
    True
    1. Exit this functionreturn 0;
    False

    Continue after this decision.

  2. Executeconst float phase = ((float)(elapsed_ms % 900U) / 900.0f) * 6.2831853f;
  3. Exit this functionreturn (int)(sinf(phase + (float)character_index * 0.55f) * (float)wave_amplitude);
Read original function source
int graphics_text_effect_wave_offset(uint32_t elapsed_ms, unsigned character_index,
                                     int wave_amplitude)
{
    if (wave_amplitude <= 0) return 0;
    const float phase = ((float)(elapsed_ms % 900U) / 900.0f) * 6.2831853f;
    return (int)(sinf(phase + (float)character_index * 0.55f) *
                 (float)wave_amplitude);
}
clear() · source line 100
void clear(uint16_t *frame, uint16_t color)
  1. FOR loopsize_t pixel = 0; pixel < LCD_WIDTH * LCD_HEIGHT; ++pixel

    Repeat body while the loop condition permits; then continue below.

      1. Executeframe[pixel] = color;
Read original function source
void clear(uint16_t *frame, uint16_t color)
{
    for (size_t pixel = 0; pixel < LCD_WIDTH * LCD_HEIGHT; ++pixel) {
        frame[pixel] = color;
    }
}
fill_rect() · source line 107
void fill_rect(uint16_t *frame, int x, int y, int width, int height,
                         uint16_t color)
  1. FOR loopint row = y; row < y + height; ++row

    Repeat body while the loop condition permits; then continue below.

      1. FOR loopint column = x; column < x + width; ++column

        Repeat body while the loop condition permits; then continue below.

          1. Decisioncolumn >= 0 && column < LCD_WIDTH && row >= 0 && row < LCD_HEIGHT
            True
              1. Executeframe[(size_t)row * LCD_WIDTH + column] = color;
            False

            Continue after this decision.

Read original function source
void fill_rect(uint16_t *frame, int x, int y, int width, int height,
                         uint16_t color)
{
    for (int row = y; row < y + height; ++row) {
        for (int column = x; column < x + width; ++column) {
            if (column >= 0 && column < LCD_WIDTH && row >= 0 && row < LCD_HEIGHT) {
                frame[(size_t)row * LCD_WIDTH + column] = color;
            }
        }
    }
}
draw_rolling_raster_band() · source line 119
void draw_rolling_raster_band(uint16_t *frame, int band_top, int block_height,
                              int band_height, uint32_t elapsed_ms,
                              uint32_t colour_roll_ms,
                              const graphics_raster_band_palette_t *palette)

Calls into this documentation:

  1. Decisionframe == NULL || palette == NULL || palette->colours == NULL || palette->colour_count == 0U || block_height <= 0 || band_height <= 0 || colour_roll_ms == 0U
    True
    1. Exit this functionreturn;
    False

    Continue after this decision.

  2. Executeconst int stripe_count = (block_height + band_height - 1) / band_height;
  3. Executeconst int colour_roll_offset = (int)(elapsed_ms / colour_roll_ms) % stripe_count;
  4. FOR loopint y = band_top, stripe = 0; y < band_top + block_height; y += band_height, ++stripe

    Repeat body while the loop condition permits; then continue below.

      1. Executeconst int rolled = (stripe + colour_roll_offset) % stripe_count;
      2. Executeconst int palette_index = stripe_count > 1 ? rolled * ((int)palette->colour_count - 1) / (stripe_count - 1) : 0;
      3. Executeconst int visible_height = y + band_height <= band_top + block_height ? band_height : band_top + block_height - y;
      4. Executefill_rect(frame, 0, y, LCD_WIDTH, visible_height, palette->colours[palette_index]);
Read original function source
void draw_rolling_raster_band(uint16_t *frame, int band_top, int block_height,
                              int band_height, uint32_t elapsed_ms,
                              uint32_t colour_roll_ms,
                              const graphics_raster_band_palette_t *palette)
{
    if (frame == NULL || palette == NULL || palette->colours == NULL ||
        palette->colour_count == 0U || block_height <= 0 || band_height <= 0 ||
        colour_roll_ms == 0U) return;

    const int stripe_count = (block_height + band_height - 1) / band_height;
    const int colour_roll_offset = (int)(elapsed_ms / colour_roll_ms) % stripe_count;
    for (int y = band_top, stripe = 0; y < band_top + block_height;
         y += band_height, ++stripe) {
        const int rolled = (stripe + colour_roll_offset) % stripe_count;
        const int palette_index = stripe_count > 1 ?
            rolled * ((int)palette->colour_count - 1) / (stripe_count - 1) : 0;
        const int visible_height = y + band_height <= band_top + block_height ?
            band_height : band_top + block_height - y;
        fill_rect(frame, 0, y, LCD_WIDTH, visible_height,
                  palette->colours[palette_index]);
    }
}
draw_scaled_image() · source line 142
static void draw_scaled_image(uint16_t *frame, int x, int y,
                              const uint16_t *pixels, const uint8_t *mask,
                              int source_width, int source_height,
                              int draw_width, int draw_height, uint16_t color)
  1. Decisionframe == NULL || source_width <= 0 || source_height <= 0 || draw_width <= 0 || draw_height <= 0
    True
      1. Exit this functionreturn;
    False

    Continue after this decision.

  2. Executeconst size_t mask_stride = ((size_t)source_width + 7U) / 8U;
  3. Decision(size_t)source_height > SIZE_MAX / mask_stride || (pixels != NULL && (size_t)source_height > SIZE_MAX / sizeof(*pixels) / (size_t)source_width)
    True
      1. Exit this functionreturn;
    False

    Continue after this decision.

  4. Executeconst int64_t right = (int64_t)x + draw_width;
  5. Executeconst int64_t bottom = (int64_t)y + draw_height;
  6. Decisionright <= 0 || bottom <= 0 || x >= LCD_WIDTH || y >= LCD_HEIGHT
    True
      1. Exit this functionreturn;
    False

    Continue after this decision.

  7. Executeconst int left = x < 0 ? 0 : x;
  8. Executeconst int top = y < 0 ? 0 : y;
  9. Executeconst int end_x = right > LCD_WIDTH ? LCD_WIDTH : (int)right;
  10. Executeconst int end_y = bottom > LCD_HEIGHT ? LCD_HEIGHT : (int)bottom;
  11. FOR loopint row = top; row < end_y; ++row

    Repeat body while the loop condition permits; then continue below.

      1. Executeconst size_t source_y = (size_t)(((int64_t)row - y) * source_height / draw_height);
      2. FOR loopint column = left; column < end_x; ++column

        Repeat body while the loop condition permits; then continue below.

          1. Executeconst size_t source_x = (size_t)(((int64_t)column - x) * source_width / draw_width);
          2. Decisionmask != NULL && (mask[source_y * mask_stride + source_x / 8U] & (1U << (7U - source_x % 8U))) == 0
            True
              1. Next loop iterationcontinue;
            False

            Continue after this decision.

          3. Executeframe[(size_t)row * LCD_WIDTH + column] = pixels != NULL ? pixels[source_y * (size_t)source_width + source_x] : color;
Read original function source
static void draw_scaled_image(uint16_t *frame, int x, int y,
                              const uint16_t *pixels, const uint8_t *mask,
                              int source_width, int source_height,
                              int draw_width, int draw_height, uint16_t color)
{
    if (frame == NULL || source_width <= 0 || source_height <= 0 ||
        draw_width <= 0 || draw_height <= 0) {
        return;
    }

    const size_t mask_stride = ((size_t)source_width + 7U) / 8U;
    if ((size_t)source_height > SIZE_MAX / mask_stride ||
        (pixels != NULL &&
         (size_t)source_height > SIZE_MAX / sizeof(*pixels) / (size_t)source_width)) {
        return;
    }

    // Clip the destination before sampling; retain the original image origin.
    const int64_t right = (int64_t)x + draw_width;
    const int64_t bottom = (int64_t)y + draw_height;
    if (right <= 0 || bottom <= 0 || x >= LCD_WIDTH || y >= LCD_HEIGHT) {
        return;
    }
    const int left = x < 0 ? 0 : x;
    const int top = y < 0 ? 0 : y;
    const int end_x = right > LCD_WIDTH ? LCD_WIDTH : (int)right;
    const int end_y = bottom > LCD_HEIGHT ? LCD_HEIGHT : (int)bottom;

    for (int row = top; row < end_y; ++row) {
        const size_t source_y = (size_t)(((int64_t)row - y) * source_height / draw_height);
        for (int column = left; column < end_x; ++column) {
            const size_t source_x = (size_t)(((int64_t)column - x) * source_width / draw_width);
            if (mask != NULL &&
                (mask[source_y * mask_stride + source_x / 8U] &
                 (1U << (7U - source_x % 8U))) == 0) {
                continue;
            }
            frame[(size_t)row * LCD_WIDTH + column] = pixels != NULL
                ? pixels[source_y * (size_t)source_width + source_x] : color;
        }
    }
}
draw_bitmap_scaled() · source line 185
void draw_bitmap_scaled(uint16_t *frame, int x, int y,
                    const uint8_t *bitmap, int source_width, int source_height,
                    int draw_width, int draw_height, uint16_t color)

Calls into this documentation:

  1. Decisionbitmap == NULL
    True
      1. Exit this functionreturn;
    False

    Continue after this decision.

  2. Executedraw_scaled_image(frame, x, y, NULL, bitmap, source_width, source_height, draw_width, draw_height, color);
Read original function source
void draw_bitmap_scaled(uint16_t *frame, int x, int y,
                    const uint8_t *bitmap, int source_width, int source_height,
                    int draw_width, int draw_height, uint16_t color)
{
    if (bitmap == NULL) {
        return;
    }
    draw_scaled_image(frame, x, y, NULL, bitmap, source_width, source_height,
                      draw_width, draw_height, color);
}
draw_rgb565_scaled() · source line 196
void draw_rgb565_scaled(uint16_t *frame, int x, int y,
                          const uint16_t *pixels, const uint8_t *mask,
                          int source_width, int source_height,
                          int draw_width, int draw_height)

Calls into this documentation:

  1. Decisionpixels == NULL
    True
      1. Exit this functionreturn;
    False

    Continue after this decision.

  2. Executedraw_scaled_image(frame, x, y, pixels, mask, source_width, source_height, draw_width, draw_height, 0);
Read original function source
void draw_rgb565_scaled(uint16_t *frame, int x, int y,
                          const uint16_t *pixels, const uint8_t *mask,
                          int source_width, int source_height,
                          int draw_width, int draw_height)
{
    if (pixels == NULL) {
        return;
    }
    draw_scaled_image(frame, x, y, pixels, mask, source_width, source_height,
                      draw_width, draw_height, 0);
}
draw_transformed_image() · source line 208
static void draw_transformed_image(uint16_t *frame, int destination_centre_x,
                                   int destination_centre_y,
                                   const uint16_t *pixels, const uint8_t *mask,
                                   int source_width, int source_height,
                                   float scale, float angle_degrees, uint16_t color)
  1. Decisionframe == NULL || source_width <= 0 || source_height <= 0 || scale <= 0.0f || !isfinite(scale) || !isfinite(angle_degrees)
    True
      1. Exit this functionreturn;
    False

    Continue after this decision.

  2. Executeconst float radians = angle_degrees * 0.01745329252f;
  3. Executeconst float cosine = cosf(radians);
  4. Executeconst float sine = sinf(radians);
  5. Executeconst float half_width = (fabsf(cosine) * source_width + fabsf(sine) * source_height) * scale * 0.5f + 1.0f;
  6. Executeconst float half_height = (fabsf(sine) * source_width + fabsf(cosine) * source_height) * scale * 0.5f + 1.0f;
  7. Executeconst int start_x = (int)floorf(destination_centre_x - half_width);
  8. Executeconst int end_x = (int)ceilf(destination_centre_x + half_width);
  9. Executeconst int start_y = (int)floorf(destination_centre_y - half_height);
  10. Executeconst int end_y = (int)ceilf(destination_centre_y + half_height);
  11. Executeconst size_t mask_stride = ((size_t)source_width + 7U) / 8U;
  12. Executeconst float source_centre_x = ((float)source_width - 1.0f) * 0.5f;
  13. Executeconst float source_centre_y = ((float)source_height - 1.0f) * 0.5f;
  14. FOR loopint destination_y = start_y; destination_y <= end_y; ++destination_y

    Repeat body while the loop condition permits; then continue below.

      1. Decisiondestination_y < 0 || destination_y >= LCD_HEIGHT
        True
        1. Next loop iterationcontinue;
        False

        Continue after this decision.

      2. Executeconst float y = (float)(destination_y - destination_centre_y);
      3. FOR loopint destination_x = start_x; destination_x <= end_x; ++destination_x

        Repeat body while the loop condition permits; then continue below.

          1. Decisiondestination_x < 0 || destination_x >= LCD_WIDTH
            True
            1. Next loop iterationcontinue;
            False

            Continue after this decision.

          2. Executeconst float x = (float)(destination_x - destination_centre_x);
          3. Executeconst int source_x = (int)floorf((cosine * x + sine * y) / scale + source_centre_x + 0.5f);
          4. Executeconst int source_y = (int)floorf((-sine * x + cosine * y) / scale + source_centre_y + 0.5f);
          5. Decisionsource_x < 0 || source_x >= source_width || source_y < 0 || source_y >= source_height
            True
            1. Next loop iterationcontinue;
            False

            Continue after this decision.

          6. Decisionmask != NULL && (mask[(size_t)source_y * mask_stride + (unsigned)source_x / 8U] & (1U << (7U - (unsigned)source_x % 8U))) == 0
            True
            1. Next loop iterationcontinue;
            False

            Continue after this decision.

          7. Executeframe[(size_t)destination_y * LCD_WIDTH + destination_x] = pixels != NULL ? pixels[(size_t)source_y * source_width + source_x] : color;
Read original function source
static void draw_transformed_image(uint16_t *frame, int destination_centre_x,
                                   int destination_centre_y,
                                   const uint16_t *pixels, const uint8_t *mask,
                                   int source_width, int source_height,
                                   float scale, float angle_degrees, uint16_t color)
{
    if (frame == NULL || source_width <= 0 || source_height <= 0 ||
        scale <= 0.0f || !isfinite(scale) || !isfinite(angle_degrees)) {
        return;
    }

    const float radians = angle_degrees * 0.01745329252f;
    const float cosine = cosf(radians);
    const float sine = sinf(radians);
    const float half_width = (fabsf(cosine) * source_width +
                              fabsf(sine) * source_height) * scale * 0.5f + 1.0f;
    const float half_height = (fabsf(sine) * source_width +
                               fabsf(cosine) * source_height) * scale * 0.5f + 1.0f;
    const int start_x = (int)floorf(destination_centre_x - half_width);
    const int end_x = (int)ceilf(destination_centre_x + half_width);
    const int start_y = (int)floorf(destination_centre_y - half_height);
    const int end_y = (int)ceilf(destination_centre_y + half_height);
    const size_t mask_stride = ((size_t)source_width + 7U) / 8U;
    const float source_centre_x = ((float)source_width - 1.0f) * 0.5f;
    const float source_centre_y = ((float)source_height - 1.0f) * 0.5f;

    for (int destination_y = start_y; destination_y <= end_y; ++destination_y) {
        if (destination_y < 0 || destination_y >= LCD_HEIGHT) continue;
        const float y = (float)(destination_y - destination_centre_y);
        for (int destination_x = start_x; destination_x <= end_x; ++destination_x) {
            if (destination_x < 0 || destination_x >= LCD_WIDTH) continue;
            const float x = (float)(destination_x - destination_centre_x);
            const int source_x = (int)floorf((cosine * x + sine * y) / scale +
                                              source_centre_x + 0.5f);
            const int source_y = (int)floorf((-sine * x + cosine * y) / scale +
                                              source_centre_y + 0.5f);
            if (source_x < 0 || source_x >= source_width ||
                source_y < 0 || source_y >= source_height) continue;
            if (mask != NULL &&
                (mask[(size_t)source_y * mask_stride + (unsigned)source_x / 8U] &
                 (1U << (7U - (unsigned)source_x % 8U))) == 0) continue;
            frame[(size_t)destination_y * LCD_WIDTH + destination_x] = pixels != NULL
                ? pixels[(size_t)source_y * source_width + source_x] : color;
        }
    }
}
draw_bitmap_transformed() · source line 255
void draw_bitmap_transformed(uint16_t *frame, int destination_centre_x,
                                int destination_centre_y, const uint8_t *bitmap,
                                int source_width, int source_height,
                                float scale, float angle_degrees, uint16_t color)

Calls into this documentation:

  1. Decisionbitmap == NULL
    True
    1. Exit this functionreturn;
    False

    Continue after this decision.

  2. Executedraw_transformed_image(frame, destination_centre_x, destination_centre_y, NULL, bitmap, source_width, source_height, scale, angle_degrees, color);
Read original function source
void draw_bitmap_transformed(uint16_t *frame, int destination_centre_x,
                                int destination_centre_y, const uint8_t *bitmap,
                                int source_width, int source_height,
                                float scale, float angle_degrees, uint16_t color)
{
    if (bitmap == NULL) return;
    draw_transformed_image(frame, destination_centre_x, destination_centre_y,
                           NULL, bitmap, source_width, source_height,
                           scale, angle_degrees, color);
}
draw_rgb565_transformed() · source line 266
void draw_rgb565_transformed(uint16_t *frame, int destination_centre_x,
                                      int destination_centre_y, const uint16_t *pixels,
                                      const uint8_t *mask, int source_width,
                                      int source_height, float scale,
                                      float angle_degrees)

Calls into this documentation:

  1. Decisionpixels == NULL
    True
    1. Exit this functionreturn;
    False

    Continue after this decision.

  2. Executedraw_transformed_image(frame, destination_centre_x, destination_centre_y, pixels, mask, source_width, source_height, scale, angle_degrees, 0);
Read original function source
void draw_rgb565_transformed(uint16_t *frame, int destination_centre_x,
                                      int destination_centre_y, const uint16_t *pixels,
                                      const uint8_t *mask, int source_width,
                                      int source_height, float scale,
                                      float angle_degrees)
{
    if (pixels == NULL) return;
    draw_transformed_image(frame, destination_centre_x, destination_centre_y,
                           pixels, mask, source_width, source_height,
                           scale, angle_degrees, 0);
}
draw_ui_bitmap_8x8() · source line 278
void draw_ui_bitmap_8x8(uint16_t *frame, int x, int y,
                        const uint8_t bitmap[8], int scale, uint16_t color)

Calls into this documentation:

  1. Decisionscale <= 0 || scale > INT_MAX / 8
    True
      1. Exit this functionreturn;
    False

    Continue after this decision.

  2. Executedraw_bitmap_scaled(frame, x, y, bitmap, 8, 8, 8 * scale, 8 * scale, color);
Read original function source
void draw_ui_bitmap_8x8(uint16_t *frame, int x, int y,
                        const uint8_t bitmap[8], int scale, uint16_t color)
{
    if (scale <= 0 || scale > INT_MAX / 8) {
        return;
    }
    draw_bitmap_scaled(frame, x, y, bitmap, 8, 8, 8 * scale, 8 * scale, color);
}
fill_circle() · source line 287
void fill_circle(uint16_t *frame, int x, int y, int radius,
                           uint16_t color)

Calls into this documentation:

  1. FOR loopint row = -radius; row <= radius; ++row

    Repeat body while the loop condition permits; then continue below.

      1. FOR loopint column = -radius; column <= radius; ++column

        Repeat body while the loop condition permits; then continue below.

          1. Decisioncolumn * column + row * row <= radius * radius
            True
              1. Executefill_rect(frame, x + column, y + row, 1, 1, color);
            False

            Continue after this decision.

Read original function source
void fill_circle(uint16_t *frame, int x, int y, int radius,
                           uint16_t color)
{
    for (int row = -radius; row <= radius; ++row) {
        for (int column = -radius; column <= radius; ++column) {
            if (column * column + row * row <= radius * radius) {
                fill_rect(frame, x + column, y + row, 1, 1, color);
            }
        }
    }
}
ui_glyph() · source line 322
static const uint8_t *ui_glyph(char symbol)
  1. Executestatic const uint8_t colon[UI_FONT_HEIGHT] = {0,0x0C,0x0C,0,0x0C,0x0C,0,0};
  2. Executestatic const uint8_t slash[UI_FONT_HEIGHT] = {0x03,0x06,0x06,0x0C,0x18,0x18,0x30,0};
  3. Executestatic const uint8_t minus[UI_FONT_HEIGHT] = {0,0,0,0x3F,0,0,0,0};
  4. Executestatic const uint8_t plus[UI_FONT_HEIGHT] = {0,0x0C,0x0C,0x3F,0x0C,0x0C,0,0};
  5. Executestatic const uint8_t dot[UI_FONT_HEIGHT] = {0,0,0,0,0,0,0x0C,0};
  6. Executestatic const uint8_t apostrophe[UI_FONT_HEIGHT] = {0x0C,0x0C,0x18,0,0,0,0,0};
  7. Executestatic const uint8_t question[UI_FONT_HEIGHT] = {0x1E,0x33,0x03,0x06,0x0C,0,0x0C,0};
  8. Executestatic const uint8_t exclamation[UI_FONT_HEIGHT] = {0x0C,0x0C,0x0C,0x0C,0x0C,0,0x0C,0};
  9. Decisionsymbol >= 'a' && symbol <= 'z'
    True
    1. Executesymbol = (char)(symbol - 'a' + 'A');
    False

    Continue after this decision.

  10. Decisionsymbol >= '0' && symbol <= '9'
    True
    1. Exit this functionreturn ui_glyphs[symbol - '0'];
    False

    Continue after this decision.

  11. Decisionsymbol >= 'A' && symbol <= 'Z'
    True
    1. Exit this functionreturn ui_glyphs[symbol - 'A' + 10];
    False

    Continue after this decision.

  12. Select a casesymbol

    Enter matching case; follow source fall-through until a break, return or end.

      1. Case / labelcase ':':
      2. Exit this functionreturn colon;
      3. Case / labelcase '/':
      4. Exit this functionreturn slash;
      5. Case / labelcase '-':
      6. Exit this functionreturn minus;
      7. Case / labelcase '+':
      8. Exit this functionreturn plus;
      9. Case / labelcase '.':
      10. Exit this functionreturn dot;
      11. Case / labelcase '\'':
      12. Exit this functionreturn apostrophe;
      13. Case / labelcase '?':
      14. Exit this functionreturn question;
      15. Case / labelcase '!':
      16. Exit this functionreturn exclamation;
      17. Case / labeldefault:
      18. Exit this functionreturn NULL;
Read original function source
static const uint8_t *ui_glyph(char symbol)
{
    static const uint8_t colon[UI_FONT_HEIGHT] = {0,0x0C,0x0C,0,0x0C,0x0C,0,0};
    static const uint8_t slash[UI_FONT_HEIGHT] = {0x03,0x06,0x06,0x0C,0x18,0x18,0x30,0};
    static const uint8_t minus[UI_FONT_HEIGHT] = {0,0,0,0x3F,0,0,0,0};
    static const uint8_t plus[UI_FONT_HEIGHT] = {0,0x0C,0x0C,0x3F,0x0C,0x0C,0,0};
    static const uint8_t dot[UI_FONT_HEIGHT] = {0,0,0,0,0,0,0x0C,0};
    static const uint8_t apostrophe[UI_FONT_HEIGHT] = {0x0C,0x0C,0x18,0,0,0,0,0};
    static const uint8_t question[UI_FONT_HEIGHT] = {0x1E,0x33,0x03,0x06,0x0C,0,0x0C,0};
    static const uint8_t exclamation[UI_FONT_HEIGHT] = {0x0C,0x0C,0x0C,0x0C,0x0C,0,0x0C,0};
    if (symbol >= 'a' && symbol <= 'z') symbol = (char)(symbol - 'a' + 'A');
    if (symbol >= '0' && symbol <= '9') return ui_glyphs[symbol - '0'];
    if (symbol >= 'A' && symbol <= 'Z') return ui_glyphs[symbol - 'A' + 10];
    switch (symbol) {
    case ':': return colon;
    case '/': return slash;
    case '-': return minus;
    case '+': return plus;
    case '.': return dot;
    case '\'': return apostrophe;
    case '?': return question;
    case '!': return exclamation;
    default: return NULL;
    }
}
draw_text() · source line 348
void draw_text(uint16_t *frame, int x, int y, const char *text,
               int scale, uint16_t color)

Calls into this documentation:

  1. Decisionframe == NULL || text == NULL || scale <= 0
    True
    1. Exit this functionreturn;
    False

    Continue after this decision.

  2. FOR loop; *text != '\0'; ++text, x += UI_FONT_ADVANCE * scale

    Repeat body while the loop condition permits; then continue below.

      1. Executeconst uint8_t *glyph = ui_glyph(*text);
      2. Decisionglyph == NULL
        True
        1. Next loop iterationcontinue;
        False

        Continue after this decision.

      3. FOR loopint row = 0; row < UI_FONT_HEIGHT; ++row

        Repeat body while the loop condition permits; then continue below.

          1. FOR loopint column = 0; column < UI_FONT_WIDTH; ++column

            Repeat body while the loop condition permits; then continue below.

              1. Decision(glyph[row] & (1U << (UI_FONT_WIDTH - 1 - column))) != 0
                True
                1. Executefill_rect(frame, x + column * scale, y + row * scale, scale, scale, color);
                False

                Continue after this decision.

Read original function source
void draw_text(uint16_t *frame, int x, int y, const char *text,
               int scale, uint16_t color)
{
    if (frame == NULL || text == NULL || scale <= 0) return;
    for (; *text != '\0'; ++text, x += UI_FONT_ADVANCE * scale) {
        const uint8_t *glyph = ui_glyph(*text);
        if (glyph == NULL) continue;
        for (int row = 0; row < UI_FONT_HEIGHT; ++row) {
            for (int column = 0; column < UI_FONT_WIDTH; ++column) {
                if ((glyph[row] & (1U << (UI_FONT_WIDTH - 1 - column))) != 0)
                    fill_rect(frame, x + column * scale, y + row * scale,
                              scale, scale, color);
            }
        }
    }
}
draw_rect() · source line 366
void draw_rect(uint16_t *frame, int x, int y, int width, int height,
                     uint16_t color)

Calls into this documentation:

  1. Decisionwidth <= 0 || height <= 0
    True
      1. Exit this functionreturn;
    False

    Continue after this decision.

  2. Executefill_rect(frame, x, y, width, 1, color);
  3. Executefill_rect(frame, x, y + height - 1, width, 1, color);
  4. Executefill_rect(frame, x, y, 1, height, color);
  5. Executefill_rect(frame, x + width - 1, y, 1, height, color);
Read original function source
void draw_rect(uint16_t *frame, int x, int y, int width, int height,
                     uint16_t color)
{
    if (width <= 0 || height <= 0) {
        return;
    }
    fill_rect(frame, x, y, width, 1, color);
    fill_rect(frame, x, y + height - 1, width, 1, color);
    fill_rect(frame, x, y, 1, height, color);
    fill_rect(frame, x + width - 1, y, 1, height, color);
}
draw_line() · source line 378
void draw_line(uint16_t *frame, int x0, int y0, int x1, int y1, uint16_t color)

Calls into this documentation:

  1. Executeconst int dx = abs(x1 - x0);
  2. Executeconst int dy = -abs(y1 - y0);
  3. Executeconst int sx = x0 < x1 ? 1 : -1;
  4. Executeconst int sy = y0 < y1 ? 1 : -1;
  5. Executeint error = dx + dy;
  6. WHILE looptrue

    Repeat body while the loop condition permits; then continue below.

      1. Executefill_rect(frame, x0, y0, 1, 1, color);
      2. Decisionx0 == x1 && y0 == y1
        True
          1. Exit enclosing loop or switchbreak;
        False

        Continue after this decision.

      3. Executeconst int twice_error = 2 * error;
      4. Decisiontwice_error >= dy
        True
          1. Executeerror += dy;
          2. Executex0 += sx;
        False

        Continue after this decision.

      5. Decisiontwice_error <= dx
        True
          1. Executeerror += dx;
          2. Executey0 += sy;
        False

        Continue after this decision.

Read original function source
void draw_line(uint16_t *frame, int x0, int y0, int x1, int y1, uint16_t color)
{
    const int dx = abs(x1 - x0);
    const int dy = -abs(y1 - y0);
    const int sx = x0 < x1 ? 1 : -1;
    const int sy = y0 < y1 ? 1 : -1;
    int error = dx + dy;
    while (true) {
        fill_rect(frame, x0, y0, 1, 1, color);
        if (x0 == x1 && y0 == y1) {
            break;
        }
        const int twice_error = 2 * error;
        if (twice_error >= dy) {
            error += dy;
            x0 += sx;
        }
        if (twice_error <= dx) {
            error += dx;
            y0 += sy;
        }
    }
}
mix_dotted_seed() · source line 402
static uint32_t mix_dotted_seed(uint32_t value)
  1. Executevalue ^= value >> 16;
  2. Executevalue *= 0x7FEB352DU;
  3. Executevalue ^= value >> 15;
  4. Executevalue *= 0x846CA68BU;
  5. Exit this functionreturn value ^ (value >> 16);
Read original function source
static uint32_t mix_dotted_seed(uint32_t value)
{
    value ^= value >> 16;
    value *= 0x7FEB352DU;
    value ^= value >> 15;
    value *= 0x846CA68BU;
    return value ^ (value >> 16);
}
greatest_common_divisor() · source line 411
static unsigned greatest_common_divisor(unsigned left, unsigned right)
  1. WHILE loopright != 0U

    Repeat body while the loop condition permits; then continue below.

      1. Executeconst unsigned remainder = left % right;
      2. Executeleft = right;
      3. Executeright = remainder;
  2. Exit this functionreturn left;
Read original function source
static unsigned greatest_common_divisor(unsigned left, unsigned right)
{
    while (right != 0U) {
        const unsigned remainder = left % right;
        left = right;
        right = remainder;
    }
    return left;
}
dotted_index_is_active() · source line 421
static bool dotted_index_is_active(unsigned index, unsigned dot_count,
                                   unsigned dot_density_percent, uint32_t random_seed)

Calls into this documentation:

  1. Decisiondot_density_percent == 0U || dot_count == 0U
    True
    1. Exit this functionreturn false;
    False

    Continue after this decision.

  2. Decisiondot_density_percent >= 100U || dot_count == 1U
    True
    1. Exit this functionreturn true;
    False

    Continue after this decision.

  3. Executeconst unsigned active_count = (dot_count * dot_density_percent + 99U) / 100U;
  4. Decisionactive_count >= dot_count
    True
    1. Exit this functionreturn true;
    False

    Continue after this decision.

  5. Executeconst unsigned start = mix_dotted_seed(random_seed) % dot_count;
  6. Executeunsigned step = 1U + mix_dotted_seed(random_seed ^ 0x9E3779B9U) % (dot_count - 1U);
  7. WHILE loopgreatest_common_divisor(step, dot_count) != 1U

    Repeat body while the loop condition permits; then continue below.

      1. Execute++step;
      2. Decisionstep >= dot_count
        True
        1. Executestep = 1U;
        False

        Continue after this decision.

  8. FOR loopunsigned selected = 0U; selected < active_count; ++selected

    Repeat body while the loop condition permits; then continue below.

      1. Executeconst unsigned candidate = (unsigned)(((uint64_t)start + (uint64_t)selected * step) % dot_count);
      2. Decisioncandidate == index
        True
        1. Exit this functionreturn true;
        False

        Continue after this decision.

  9. Exit this functionreturn false;
Read original function source
static bool dotted_index_is_active(unsigned index, unsigned dot_count,
                                   unsigned dot_density_percent, uint32_t random_seed)
{
    if (dot_density_percent == 0U || dot_count == 0U) return false;
    if (dot_density_percent >= 100U || dot_count == 1U) return true;

    const unsigned active_count = (dot_count * dot_density_percent + 99U) / 100U;
    if (active_count >= dot_count) return true;

    const unsigned start = mix_dotted_seed(random_seed) % dot_count;
    unsigned step = 1U + mix_dotted_seed(random_seed ^ 0x9E3779B9U) % (dot_count - 1U);
    while (greatest_common_divisor(step, dot_count) != 1U) {
        ++step;
        if (step >= dot_count) step = 1U;
    }
    for (unsigned selected = 0U; selected < active_count; ++selected) {
        const unsigned candidate = (unsigned)(((uint64_t)start +
                                                (uint64_t)selected * step) % dot_count);
        if (candidate == index) return true;
    }
    return false;
}
dotted_index_is_sparkle() · source line 444
static bool dotted_index_is_sparkle(unsigned index, unsigned sparkle_density_percent,
                                    uint32_t random_seed)

Calls into this documentation:

  1. Decisionsparkle_density_percent == 0U
    True
    1. Exit this functionreturn false;
    False

    Continue after this decision.

  2. Decisionsparkle_density_percent >= 100U
    True
    1. Exit this functionreturn true;
    False

    Continue after this decision.

  3. Executeconst uint32_t value = mix_dotted_seed(random_seed ^ index * 0x9E3779B9U);
  4. Exit this functionreturn value % 100U < sparkle_density_percent;
Read original function source
static bool dotted_index_is_sparkle(unsigned index, unsigned sparkle_density_percent,
                                    uint32_t random_seed)
{
    if (sparkle_density_percent == 0U) return false;
    if (sparkle_density_percent >= 100U) return true;
    const uint32_t value = mix_dotted_seed(random_seed ^ index * 0x9E3779B9U);
    return value % 100U < sparkle_density_percent;
}
draw_dotted_line_density() · source line 453
void draw_dotted_line_density(uint16_t *frame, int x0, int y0, int x1, int y1,
                              unsigned dot_spacing, int dot_radius, uint16_t color,
                              unsigned dot_density_percent, uint32_t random_seed)

Calls into this documentation:

  1. Decisionframe == NULL || dot_spacing == 0U || dot_radius < 0 || dot_density_percent > 100U
    True
    1. Exit this functionreturn;
    False

    Continue after this decision.

  2. Executeconst float delta_x = (float)x1 - (float)x0;
  3. Executeconst float delta_y = (float)y1 - (float)y0;
  4. Executeconst float line_length = sqrtf(delta_x * delta_x + delta_y * delta_y);
  5. Decisionline_length == 0.0f
    True
      1. Decisiondotted_index_is_active(0U, 1U, dot_density_percent, random_seed)
        True
          1. Executefill_circle(frame, x0, y0, dot_radius, color);
        False

        Continue after this decision.

      2. Exit this functionreturn;
    False

    Continue after this decision.

  6. Executeconst unsigned dot_count = (unsigned)floorf(line_length / (float)dot_spacing) + 1U;
  7. FOR loopunsigned index = 0U; index < dot_count; ++index

    Repeat body while the loop condition permits; then continue below.

      1. Decision!dotted_index_is_active(index, dot_count, dot_density_percent, random_seed)
        True
          1. Next loop iterationcontinue;
        False

        Continue after this decision.

      2. Executeconst float distance = (float)index * (float)dot_spacing;
      3. Executeconst float progress = distance / line_length;
      4. Executeconst int x = x0 + (int)lroundf(delta_x * progress);
      5. Executeconst int y = y0 + (int)lroundf(delta_y * progress);
      6. Executefill_circle(frame, x, y, dot_radius, color);
Read original function source
void draw_dotted_line_density(uint16_t *frame, int x0, int y0, int x1, int y1,
                              unsigned dot_spacing, int dot_radius, uint16_t color,
                              unsigned dot_density_percent, uint32_t random_seed)
{
    if (frame == NULL || dot_spacing == 0U || dot_radius < 0 ||
        dot_density_percent > 100U) return;
    const float delta_x = (float)x1 - (float)x0;
    const float delta_y = (float)y1 - (float)y0;
    const float line_length = sqrtf(delta_x * delta_x + delta_y * delta_y);
    if (line_length == 0.0f) {
        if (dotted_index_is_active(0U, 1U, dot_density_percent, random_seed)) {
            fill_circle(frame, x0, y0, dot_radius, color);
        }
        return;
    }
    const unsigned dot_count = (unsigned)floorf(line_length / (float)dot_spacing) + 1U;
    for (unsigned index = 0U; index < dot_count; ++index) {
        if (!dotted_index_is_active(index, dot_count, dot_density_percent, random_seed)) {
            continue;
        }
        const float distance = (float)index * (float)dot_spacing;
        const float progress = distance / line_length;
        const int x = x0 + (int)lroundf(delta_x * progress);
        const int y = y0 + (int)lroundf(delta_y * progress);
        fill_circle(frame, x, y, dot_radius, color);
    }
}
draw_dotted_line() · source line 481
void draw_dotted_line(uint16_t *frame, int x0, int y0, int x1, int y1,
                      unsigned dot_spacing, int dot_radius, uint16_t color)

Calls into this documentation:

  1. Executedraw_dotted_line_density(frame, x0, y0, x1, y1, dot_spacing, dot_radius, color, 100U, 0U);
Read original function source
void draw_dotted_line(uint16_t *frame, int x0, int y0, int x1, int y1,
                      unsigned dot_spacing, int dot_radius, uint16_t color)
{
    draw_dotted_line_density(frame, x0, y0, x1, y1, dot_spacing, dot_radius, color,
                             100U, 0U);
}
packed_bitmap_pixel() · source line 488
static bool packed_bitmap_pixel(const uint8_t *bitmap, unsigned source_width,
                                unsigned x, unsigned y)
  1. Executeconst size_t row_bytes = ((size_t)source_width + 7U) / 8U;
  2. Executeconst size_t byte_index = (size_t)y * row_bytes + x / 8U;
  3. Exit this functionreturn (bitmap[byte_index] & (uint8_t)(1U << (7U - x % 8U))) != 0U;
Read original function source
static bool packed_bitmap_pixel(const uint8_t *bitmap, unsigned source_width,
                                unsigned x, unsigned y)
{
    const size_t row_bytes = ((size_t)source_width + 7U) / 8U;
    const size_t byte_index = (size_t)y * row_bytes + x / 8U;
    return (bitmap[byte_index] & (uint8_t)(1U << (7U - x % 8U))) != 0U;
}
project_dotted_bitmap_point() · source line 505
static graphics_point_t project_dotted_bitmap_point(float source_x, float source_y,
                                                     float depth,
                                                     const dotted_bitmap_3d_params_t *params,
                                                     const dotted_bitmap_rotation_t *rotation)
  1. Executeconst float scaled_x = source_x * params->scale;
  2. Executeconst float scaled_y = source_y * params->scale;
  3. Executeconst float pitched_y = scaled_y * rotation->pitch_cosine - depth * rotation->pitch_sine;
  4. Executeconst float pitched_z = scaled_y * rotation->pitch_sine + depth * rotation->pitch_cosine;
  5. Executeconst float yawed_x = scaled_x * rotation->yaw_cosine + pitched_z * rotation->yaw_sine;
  6. Executeconst float yawed_z = -scaled_x * rotation->yaw_sine + pitched_z * rotation->yaw_cosine;
  7. Executeconst float rotated_x = yawed_x * rotation->roll_cosine - pitched_y * rotation->roll_sine;
  8. Executeconst float rotated_y = yawed_x * rotation->roll_sine + pitched_y * rotation->roll_cosine;
  9. Executeconst float perspective = params->camera_distance / (params->camera_distance + yawed_z);
  10. Executeconst graphics_point_t point = { .x = params->centre_x + (int)lroundf(rotated_x * perspective), .y = params->centre_y + (int)lroundf(rotated_y * perspective), };
  11. Exit this functionreturn point;
Read original function source
static graphics_point_t project_dotted_bitmap_point(float source_x, float source_y,
                                                     float depth,
                                                     const dotted_bitmap_3d_params_t *params,
                                                     const dotted_bitmap_rotation_t *rotation)
{
    const float scaled_x = source_x * params->scale;
    const float scaled_y = source_y * params->scale;
    const float pitched_y = scaled_y * rotation->pitch_cosine -
                            depth * rotation->pitch_sine;
    const float pitched_z = scaled_y * rotation->pitch_sine +
                            depth * rotation->pitch_cosine;
    const float yawed_x = scaled_x * rotation->yaw_cosine +
                          pitched_z * rotation->yaw_sine;
    const float yawed_z = -scaled_x * rotation->yaw_sine +
                          pitched_z * rotation->yaw_cosine;
    const float rotated_x = yawed_x * rotation->roll_cosine -
                            pitched_y * rotation->roll_sine;
    const float rotated_y = yawed_x * rotation->roll_sine +
                            pitched_y * rotation->roll_cosine;
    const float perspective = params->camera_distance /
                              (params->camera_distance + yawed_z);
    const graphics_point_t point = {
        .x = params->centre_x + (int)lroundf(rotated_x * perspective),
        .y = params->centre_y + (int)lroundf(rotated_y * perspective),
    };
    return point;
}
draw_dotted_bitmap_depth_line() · source line 533
static void draw_dotted_bitmap_depth_line(uint16_t *frame,
                                          float x, float y,
                                          const dotted_bitmap_3d_params_t *params,
                                          const dotted_bitmap_rotation_t *rotation,
                                          uint32_t random_seed)

Calls into this documentation:

  1. Executeconst graphics_point_t front = project_dotted_bitmap_point(x, y, 0.0f, params, rotation);
  2. Executeconst graphics_point_t back = project_dotted_bitmap_point(x, y, params->depth, params, rotation);
  3. Executedraw_dotted_line_density(frame, front.x, front.y, back.x, back.y, params->depth_dot_spacing, params->dot_radius, params->side_color, params->dot_density_percent, random_seed);
Read original function source
static void draw_dotted_bitmap_depth_line(uint16_t *frame,
                                          float x, float y,
                                          const dotted_bitmap_3d_params_t *params,
                                          const dotted_bitmap_rotation_t *rotation,
                                          uint32_t random_seed)
{
    const graphics_point_t front = project_dotted_bitmap_point(x, y, 0.0f,
                                                                 params, rotation);
    const graphics_point_t back = project_dotted_bitmap_point(x, y, params->depth,
                                                                params, rotation);
    draw_dotted_line_density(frame, front.x, front.y, back.x, back.y,
                              params->depth_dot_spacing, params->dot_radius,
                              params->side_color, params->dot_density_percent,
                              random_seed);
}
draw_dotted_bitmap_face() · source line 549
static void draw_dotted_bitmap_face(uint16_t *frame, const uint8_t *bitmap,
                                    unsigned source_width, unsigned source_height,
                                    float depth, uint16_t color,
                                    const dotted_bitmap_3d_params_t *params,
                                    const dotted_bitmap_rotation_t *rotation,
                                    uint32_t random_seed,
                                    bool draw_sparkles)

Calls into this documentation:

  1. Executeconst float half_width = (float)source_width * 0.5f;
  2. Executeconst float half_height = (float)source_height * 0.5f;
  3. FOR loopunsigned y = 0U; y < source_height; ++y

    Repeat body while the loop condition permits; then continue below.

      1. Executeunsigned run_start = 0U;
      2. WHILE looprun_start < source_width

        Repeat body while the loop condition permits; then continue below.

          1. WHILE looprun_start < source_width && !packed_bitmap_pixel(bitmap, source_width, run_start, y)

            Repeat body while the loop condition permits; then continue below.

              1. Execute++run_start;
          2. Decisionrun_start == source_width
            True
            1. Exit enclosing loop or switchbreak;
            False

            Continue after this decision.

          3. Executeunsigned run_end = run_start + 1U;
          4. WHILE looprun_end < source_width && packed_bitmap_pixel(bitmap, source_width, run_end, y)

            Repeat body while the loop condition permits; then continue below.

              1. Execute++run_end;
          5. Executeconst unsigned dot_count = run_end - run_start;
          6. Executeconst uint32_t line_seed = random_seed ^ y * 0x45D9F3BU ^ run_start * 0x119DE1F3U;
          7. FOR loopunsigned index = 0U; index < dot_count; ++index

            Repeat body while the loop condition permits; then continue below.

              1. Decision!dotted_index_is_active(index, dot_count, params->dot_density_percent, line_seed)
                True
                  1. Next loop iterationcontinue;
                False

                Continue after this decision.

              2. Executeconst float source_x = (float)(run_start + index) + 0.5f - half_width;
              3. Executeconst float source_y = (float)y + 0.5f - half_height;
              4. Executeconst graphics_point_t dot = project_dotted_bitmap_point(source_x, source_y, depth, params, rotation);
              5. Executefill_circle(frame, dot.x, dot.y, params->dot_radius, color);
              6. Decisiondraw_sparkles && params->sparkle_density_percent != 0U && dotted_index_is_sparkle(index, params->sparkle_density_percent, line_seed ^ params->sparkle_phase * 0x27D4EB2DU)
                True
                  1. Executefill_circle(frame, dot.x, dot.y, params->sparkle_radius, params->sparkle_color);
                False

                Continue after this decision.

          8. Executerun_start = run_end;
Read original function source
static void draw_dotted_bitmap_face(uint16_t *frame, const uint8_t *bitmap,
                                    unsigned source_width, unsigned source_height,
                                    float depth, uint16_t color,
                                    const dotted_bitmap_3d_params_t *params,
                                    const dotted_bitmap_rotation_t *rotation,
                                    uint32_t random_seed,
                                    bool draw_sparkles)
{
    const float half_width = (float)source_width * 0.5f;
    const float half_height = (float)source_height * 0.5f;
    for (unsigned y = 0U; y < source_height; ++y) {
        unsigned run_start = 0U;
        while (run_start < source_width) {
            while (run_start < source_width &&
                   !packed_bitmap_pixel(bitmap, source_width, run_start, y)) {
                ++run_start;
            }
            if (run_start == source_width) break;
            unsigned run_end = run_start + 1U;
            while (run_end < source_width &&
                   packed_bitmap_pixel(bitmap, source_width, run_end, y)) {
                ++run_end;
            }
            const unsigned dot_count = run_end - run_start;
            const uint32_t line_seed = random_seed ^ y * 0x45D9F3BU ^
                                       run_start * 0x119DE1F3U;
            for (unsigned index = 0U; index < dot_count; ++index) {
                if (!dotted_index_is_active(index, dot_count,
                                            params->dot_density_percent, line_seed)) {
                    continue;
                }
                const float source_x = (float)(run_start + index) + 0.5f - half_width;
                const float source_y = (float)y + 0.5f - half_height;
                const graphics_point_t dot = project_dotted_bitmap_point(source_x, source_y,
                                                                           depth, params,
                                                                           rotation);
                fill_circle(frame, dot.x, dot.y, params->dot_radius, color);
                if (draw_sparkles && params->sparkle_density_percent != 0U &&
                    dotted_index_is_sparkle(index, params->sparkle_density_percent,
                                             line_seed ^ params->sparkle_phase * 0x27D4EB2DU)) {
                    fill_circle(frame, dot.x, dot.y, params->sparkle_radius,
                                params->sparkle_color);
                }
            }
            run_start = run_end;
        }
    }
}
draw_bitmap_dotted_3d() · source line 598
void draw_bitmap_dotted_3d(uint16_t *frame, const uint8_t *bitmap,
                           unsigned source_width, unsigned source_height,
                           const dotted_bitmap_3d_params_t *params)

Calls into this documentation:

  1. Decisionframe == NULL || bitmap == NULL || params == NULL || source_width == 0U || source_height == 0U || params->scale <= 0.0f || params->depth <= 0.0f || params->camera_distance <= 0.0f || params->depth_dot_spacing == 0U || params->dot_radius < 0 || params->dot_density_percent > 100U || params->sparkle_density_percent > 100U || params->sparkle_radius < 0 || !isfinite(params->scale) || !isfinite(params->pitch_degrees) || !isfinite(params->yaw_degrees) || !isfinite(params->roll_degrees) || !isfinite(params->depth) || !isfinite(params->camera_distance)
    True
      1. Exit this functionreturn;
    False

    Continue after this decision.

  2. Executeconst float half_width = (float)source_width * params->scale * 0.5f;
  3. Executeconst float half_height = (float)source_height * params->scale * 0.5f;
  4. Executeconst float safe_camera_distance = sqrtf(half_width * half_width + half_height * half_height + params->depth * params->depth);
  5. Decisionparams->camera_distance <= safe_camera_distance
    True
      1. Exit this functionreturn;
    False

    Continue after this decision.

  6. Executeconst float pitch_radians = params->pitch_degrees * 0.01745329252f;
  7. Executeconst float yaw_radians = params->yaw_degrees * 0.01745329252f;
  8. Executeconst float roll_radians = params->roll_degrees * 0.01745329252f;
  9. Executeconst dotted_bitmap_rotation_t rotation = { .pitch_cosine = cosf(pitch_radians), .pitch_sine = sinf(pitch_radians), .yaw_cosine = cosf(yaw_radians), .yaw_sine = sinf(yaw_radians), .roll_cosine = cosf(roll_radians), .roll_sine = sinf(roll_radians), };
  10. Executeconst float source_half_width = (float)source_width * 0.5f;
  11. Executeconst float source_half_height = (float)source_height * 0.5f;
  12. Executedraw_dotted_bitmap_face(frame, bitmap, source_width, source_height, params->depth, params->back_color, params, &rotation, params->random_seed ^ 0xA511E9B3U, false);
  13. FOR loopunsigned y = 0U; y < source_height; ++y

    Repeat body while the loop condition permits; then continue below.

      1. Executeunsigned run_start = 0U;
      2. WHILE looprun_start < source_width

        Repeat body while the loop condition permits; then continue below.

          1. WHILE looprun_start < source_width && !packed_bitmap_pixel(bitmap, source_width, run_start, y)

            Repeat body while the loop condition permits; then continue below.

              1. Execute++run_start;
          2. Decisionrun_start == source_width
            True
            1. Exit enclosing loop or switchbreak;
            False

            Continue after this decision.

          3. Executeunsigned run_end = run_start + 1U;
          4. WHILE looprun_end < source_width && packed_bitmap_pixel(bitmap, source_width, run_end, y)

            Repeat body while the loop condition permits; then continue below.

              1. Execute++run_end;
          5. Executeconst float edge_y = (float)y - source_half_height;
          6. Executedraw_dotted_bitmap_depth_line(frame, (float)run_start - source_half_width, edge_y, params, &rotation, params->random_seed ^ y * 0x45D9F3BU ^ run_start * 0x119DE1F3U ^ 0x3C6EF372U);
          7. Executedraw_dotted_bitmap_depth_line(frame, (float)run_end - source_half_width, edge_y, params, &rotation, params->random_seed ^ y * 0x45D9F3BU ^ run_end * 0x119DE1F3U ^ 0xDAA66D2BU);
          8. Executerun_start = run_end;
  14. Executedraw_dotted_bitmap_face(frame, bitmap, source_width, source_height, 0.0f, params->front_color, params, &rotation, params->random_seed ^ 0x63D83595U, true);
Read original function source
void draw_bitmap_dotted_3d(uint16_t *frame, const uint8_t *bitmap,
                           unsigned source_width, unsigned source_height,
                           const dotted_bitmap_3d_params_t *params)
{
    if (frame == NULL || bitmap == NULL || params == NULL || source_width == 0U ||
        source_height == 0U || params->scale <= 0.0f || params->depth <= 0.0f ||
        params->camera_distance <= 0.0f || params->depth_dot_spacing == 0U ||
        params->dot_radius < 0 || params->dot_density_percent > 100U ||
        params->sparkle_density_percent > 100U || params->sparkle_radius < 0 ||
        !isfinite(params->scale) || !isfinite(params->pitch_degrees) ||
        !isfinite(params->yaw_degrees) || !isfinite(params->roll_degrees) ||
        !isfinite(params->depth) ||
        !isfinite(params->camera_distance)) {
        return;
    }

    const float half_width = (float)source_width * params->scale * 0.5f;
    const float half_height = (float)source_height * params->scale * 0.5f;
    const float safe_camera_distance = sqrtf(half_width * half_width +
                                             half_height * half_height +
                                             params->depth * params->depth);
    if (params->camera_distance <= safe_camera_distance) {
        return;
    }

    const float pitch_radians = params->pitch_degrees * 0.01745329252f;
    const float yaw_radians = params->yaw_degrees * 0.01745329252f;
    const float roll_radians = params->roll_degrees * 0.01745329252f;
    const dotted_bitmap_rotation_t rotation = {
        .pitch_cosine = cosf(pitch_radians),
        .pitch_sine = sinf(pitch_radians),
        .yaw_cosine = cosf(yaw_radians),
        .yaw_sine = sinf(yaw_radians),
        .roll_cosine = cosf(roll_radians),
        .roll_sine = sinf(roll_radians),
    };
    const float source_half_width = (float)source_width * 0.5f;
    const float source_half_height = (float)source_height * 0.5f;

    // Draw the back first, then edge joins, then front, so the transparent
    // dotted volume remains readable over the caller's existing background.
    draw_dotted_bitmap_face(frame, bitmap, source_width, source_height,
                            params->depth, params->back_color, params, &rotation,
                            params->random_seed ^ 0xA511E9B3U, false);

    for (unsigned y = 0U; y < source_height; ++y) {
        unsigned run_start = 0U;
        while (run_start < source_width) {
            while (run_start < source_width &&
                   !packed_bitmap_pixel(bitmap, source_width, run_start, y)) {
                ++run_start;
            }
            if (run_start == source_width) break;
            unsigned run_end = run_start + 1U;
            while (run_end < source_width &&
                   packed_bitmap_pixel(bitmap, source_width, run_end, y)) {
                ++run_end;
            }
            const float edge_y = (float)y - source_half_height;
            draw_dotted_bitmap_depth_line(frame, (float)run_start - source_half_width, edge_y,
                                          params, &rotation,
                                          params->random_seed ^ y * 0x45D9F3BU ^
                                          run_start * 0x119DE1F3U ^ 0x3C6EF372U);
            draw_dotted_bitmap_depth_line(frame, (float)run_end - source_half_width, edge_y,
                                          params, &rotation,
                                          params->random_seed ^ y * 0x45D9F3BU ^
                                          run_end * 0x119DE1F3U ^ 0xDAA66D2BU);
            run_start = run_end;
        }
    }

    draw_dotted_bitmap_face(frame, bitmap, source_width, source_height,
                            0.0f, params->front_color, params, &rotation,
                            params->random_seed ^ 0x63D83595U, true);
}
fill_convex_polygon() · source line 674
void fill_convex_polygon(uint16_t *frame, const graphics_point_t *points,
                         size_t point_count, uint16_t color)

Calls into this documentation:

  1. Decisionframe == NULL || points == NULL || point_count < 3U
    True
    1. Exit this functionreturn;
    False

    Continue after this decision.

  2. Executeint minimum_y = points[0].y;
  3. Executeint maximum_y = points[0].y;
  4. FOR loopsize_t point = 1U; point < point_count; ++point

    Repeat body while the loop condition permits; then continue below.

      1. Decisionpoints[point].y < minimum_y
        True
        1. Executeminimum_y = points[point].y;
        False

        Continue after this decision.

      2. Decisionpoints[point].y > maximum_y
        True
        1. Executemaximum_y = points[point].y;
        False

        Continue after this decision.

  5. Decisionminimum_y < 0
    True
    1. Executeminimum_y = 0;
    False

    Continue after this decision.

  6. Decisionmaximum_y >= LCD_HEIGHT
    True
    1. Executemaximum_y = LCD_HEIGHT - 1;
    False

    Continue after this decision.

  7. FOR loopint y = minimum_y; y <= maximum_y; ++y

    Repeat body while the loop condition permits; then continue below.

      1. Executeint left = LCD_WIDTH;
      2. Executeint right = -1;
      3. FOR loopsize_t point = 0U; point < point_count; ++point

        Repeat body while the loop condition permits; then continue below.

          1. Executeconst graphics_point_t first = points[point];
          2. Executeconst graphics_point_t second = points[(point + 1U) % point_count];
          3. Executeconst int edge_min_y = first.y < second.y ? first.y : second.y;
          4. Executeconst int edge_max_y = first.y > second.y ? first.y : second.y;
          5. Decisionedge_min_y == edge_max_y || y < edge_min_y || y >= edge_max_y
            True
            1. Next loop iterationcontinue;
            False

            Continue after this decision.

          6. Executeconst int x = first.x + (y - first.y) * (second.x - first.x) / (second.y - first.y);
          7. Decisionx < left
            True
            1. Executeleft = x;
            False

            Continue after this decision.

          8. Decisionx > right
            True
            1. Executeright = x;
            False

            Continue after this decision.

      4. Decisionright < 0 || left >= LCD_WIDTH
        True
        1. Next loop iterationcontinue;
        False

        Continue after this decision.

      5. Decisionleft < 0
        True
        1. Executeleft = 0;
        False

        Continue after this decision.

      6. Decisionright >= LCD_WIDTH
        True
        1. Executeright = LCD_WIDTH - 1;
        False

        Continue after this decision.

      7. Decisionleft <= right
        True
        1. Executefill_rect(frame, left, y, right - left + 1, 1, color);
        False

        Continue after this decision.

Read original function source
void fill_convex_polygon(uint16_t *frame, const graphics_point_t *points,
                         size_t point_count, uint16_t color)
{
    if (frame == NULL || points == NULL || point_count < 3U) return;

    int minimum_y = points[0].y;
    int maximum_y = points[0].y;
    for (size_t point = 1U; point < point_count; ++point) {
        if (points[point].y < minimum_y) minimum_y = points[point].y;
        if (points[point].y > maximum_y) maximum_y = points[point].y;
    }
    if (minimum_y < 0) minimum_y = 0;
    if (maximum_y >= LCD_HEIGHT) maximum_y = LCD_HEIGHT - 1;

    for (int y = minimum_y; y <= maximum_y; ++y) {
        int left = LCD_WIDTH;
        int right = -1;
        for (size_t point = 0U; point < point_count; ++point) {
            const graphics_point_t first = points[point];
            const graphics_point_t second = points[(point + 1U) % point_count];
            const int edge_min_y = first.y < second.y ? first.y : second.y;
            const int edge_max_y = first.y > second.y ? first.y : second.y;
            if (edge_min_y == edge_max_y || y < edge_min_y || y >= edge_max_y) continue;
            const int x = first.x + (y - first.y) * (second.x - first.x) /
                (second.y - first.y);
            if (x < left) left = x;
            if (x > right) right = x;
        }
        if (right < 0 || left >= LCD_WIDTH) continue;
        if (left < 0) left = 0;
        if (right >= LCD_WIDTH) right = LCD_WIDTH - 1;
        if (left <= right) fill_rect(frame, left, y, right - left + 1, 1, color);
    }
}
draw_ring() · source line 709
void draw_ring(uint16_t *frame, int x, int y, int radius, uint16_t color)

Calls into this documentation:

  1. FOR loopint row = -radius; row <= radius; ++row

    Repeat body while the loop condition permits; then continue below.

      1. FOR loopint column = -radius; column <= radius; ++column

        Repeat body while the loop condition permits; then continue below.

          1. Executeconst int distance = column * column + row * row;
          2. Decisiondistance <= radius * radius && (radius == 0 || distance > (radius - 1) * (radius - 1))
            True
              1. Executefill_rect(frame, x + column, y + row, 1, 1, color);
            False

            Continue after this decision.

Read original function source
void draw_ring(uint16_t *frame, int x, int y, int radius, uint16_t color)
{
    for (int row = -radius; row <= radius; ++row) {
        for (int column = -radius; column <= radius; ++column) {
            const int distance = column * column + row * row;
            if (distance <= radius * radius &&
                (radius == 0 || distance > (radius - 1) * (radius - 1))) {
                fill_rect(frame, x + column, y + row, 1, 1, color);
            }
        }
    }
}
draw_text_centred() · source line 722
void draw_text_centred(uint16_t *frame, int x, int y, const char *text,
                          int scale, uint16_t color)

Calls into this documentation:

  1. Executeconst int length = (int)strlen(text);
  2. Executeconst int width = length > 0 ? length * UI_FONT_ADVANCE * scale - scale : 0;
  3. Executedraw_text(frame, x - width / 2, y, text, scale, color);
Read original function source
void draw_text_centred(uint16_t *frame, int x, int y, const char *text,
                          int scale, uint16_t color)
{
    const int length = (int)strlen(text);
    const int width = length > 0 ? length * UI_FONT_ADVANCE * scale - scale : 0;
    draw_text(frame, x - width / 2, y, text, scale, color);
}
draw_text_transformed() · source line 730
void draw_text_transformed(uint16_t *frame, int destination_centre_x,
                              int destination_centre_y, const char *text,
                              int source_scale, float transform_scale,
                              float angle_degrees, uint16_t color)

Calls into this documentation:

  1. Decisionframe == NULL || text == NULL || source_scale <= 0 || transform_scale <= 0.0f || !isfinite(transform_scale) || !isfinite(angle_degrees)
    True
    1. Exit this functionreturn;
    False

    Continue after this decision.

  2. Executeint length = 0;
  3. WHILE looptext[length] != '\0'

    Repeat body while the loop condition permits; then continue below.

    1. Execute++length;
  4. Decisionlength == 0
    True
    1. Exit this functionreturn;
    False

    Continue after this decision.

  5. Executeconst int source_width = length * UI_FONT_ADVANCE * source_scale - source_scale;
  6. Executeconst int source_height = UI_FONT_HEIGHT * source_scale;
  7. Executeconst float radians = angle_degrees * 0.01745329252f;
  8. Executeconst float cosine = cosf(radians), sine = sinf(radians);
  9. Executeconst float centre_x = ((float)source_width - 1.0f) * 0.5f;
  10. Executeconst float centre_y = ((float)source_height - 1.0f) * 0.5f;
  11. Executeint glyph_x = 0;
  12. FOR loopint character = 0; character < length; ++character, glyph_x += UI_FONT_ADVANCE * source_scale

    Repeat body while the loop condition permits; then continue below.

      1. Executeconst uint8_t *glyph = ui_glyph(text[character]);
      2. Decisionglyph == NULL
        True
        1. Next loop iterationcontinue;
        False

        Continue after this decision.

      3. FOR loopint row = 0; row < UI_FONT_HEIGHT; ++row

        Repeat body while the loop condition permits; then continue below.

        1. FOR loopint column = 0; column < UI_FONT_WIDTH; ++column

          Repeat body while the loop condition permits; then continue below.

            1. Decision(glyph[row] & (1U << (UI_FONT_WIDTH - 1 - column))) == 0
              True
              1. Next loop iterationcontinue;
              False

              Continue after this decision.

            2. FOR loopint pixel_y = 0; pixel_y < source_scale; ++pixel_y

              Repeat body while the loop condition permits; then continue below.

              1. FOR loopint pixel_x = 0; pixel_x < source_scale; ++pixel_x

                Repeat body while the loop condition permits; then continue below.

                  1. Executeconst float source_x = glyph_x + column * source_scale + pixel_x - centre_x;
                  2. Executeconst float source_y = row * source_scale + pixel_y - centre_y;
                  3. Executeconst int destination_x = (int)lroundf(destination_centre_x + (cosine * source_x - sine * source_y) * transform_scale);
                  4. Executeconst int destination_y = (int)lroundf(destination_centre_y + (sine * source_x + cosine * source_y) * transform_scale);
                  5. Decisiondestination_x >= 0 && destination_x < LCD_WIDTH && destination_y >= 0 && destination_y < LCD_HEIGHT
                    True
                    1. Executeframe[(size_t)destination_y * LCD_WIDTH + destination_x] = color;
                    False

                    Continue after this decision.

Read original function source
void draw_text_transformed(uint16_t *frame, int destination_centre_x,
                              int destination_centre_y, const char *text,
                              int source_scale, float transform_scale,
                              float angle_degrees, uint16_t color)
{
    if (frame == NULL || text == NULL || source_scale <= 0 ||
        transform_scale <= 0.0f || !isfinite(transform_scale) ||
        !isfinite(angle_degrees)) return;

    int length = 0;
    while (text[length] != '\0') ++length;
    if (length == 0) return;
    const int source_width = length * UI_FONT_ADVANCE * source_scale - source_scale;
    const int source_height = UI_FONT_HEIGHT * source_scale;
    const float radians = angle_degrees * 0.01745329252f;
    const float cosine = cosf(radians), sine = sinf(radians);
    const float centre_x = ((float)source_width - 1.0f) * 0.5f;
    const float centre_y = ((float)source_height - 1.0f) * 0.5f;
    int glyph_x = 0;

    for (int character = 0; character < length; ++character, glyph_x += UI_FONT_ADVANCE * source_scale) {
        const uint8_t *glyph = ui_glyph(text[character]);
        if (glyph == NULL) continue;

        for (int row = 0; row < UI_FONT_HEIGHT; ++row) for (int column = 0; column < UI_FONT_WIDTH; ++column) {
            if ((glyph[row] & (1U << (UI_FONT_WIDTH - 1 - column))) == 0) continue;
            for (int pixel_y = 0; pixel_y < source_scale; ++pixel_y)
                for (int pixel_x = 0; pixel_x < source_scale; ++pixel_x) {
                    const float source_x = glyph_x + column * source_scale + pixel_x - centre_x;
                    const float source_y = row * source_scale + pixel_y - centre_y;
                    const int destination_x = (int)lroundf(destination_centre_x +
                        (cosine * source_x - sine * source_y) * transform_scale);
                    const int destination_y = (int)lroundf(destination_centre_y +
                        (sine * source_x + cosine * source_y) * transform_scale);
                    if (destination_x >= 0 && destination_x < LCD_WIDTH &&
                        destination_y >= 0 && destination_y < LCD_HEIGHT)
                        frame[(size_t)destination_y * LCD_WIDTH + destination_x] = color;
                }
        }
    }
}
radial_flame_random() · source line 772
static uint32_t radial_flame_random(radial_flame_t *flame)
  1. Executeflame->random_state ^= flame->random_state << 13;
  2. Executeflame->random_state ^= flame->random_state >> 17;
  3. Executeflame->random_state ^= flame->random_state << 5;
  4. Exit this functionreturn flame->random_state;
Read original function source
static uint32_t radial_flame_random(radial_flame_t *flame)
{
    flame->random_state ^= flame->random_state << 13;
    flame->random_state ^= flame->random_state >> 17;
    flame->random_state ^= flame->random_state << 5;
    return flame->random_state;
}
radial_flame_initialize() · source line 780
void radial_flame_initialize(radial_flame_t *flame, uint8_t *heat,
                             unsigned segments, unsigned layers,
                             float layer_depth, uint32_t seed)
  1. Decisionflame == NULL || heat == NULL || segments < 2U || layers < 2U || layer_depth <= 0.0f || !isfinite(layer_depth)
    True
    1. Exit this functionreturn;
    False

    Continue after this decision.

  2. Executeflame->heat = heat;
  3. Executeflame->segments = segments;
  4. Executeflame->layers = layers;
  5. Executeflame->layer_depth = layer_depth;
  6. Executeflame->random_state = seed == 0U ? 0x6D2B79F5U : seed;
  7. Executememset(heat, 0, (size_t)segments * layers);
Read original function source
void radial_flame_initialize(radial_flame_t *flame, uint8_t *heat,
                             unsigned segments, unsigned layers,
                             float layer_depth, uint32_t seed)
{
    if (flame == NULL || heat == NULL || segments < 2U || layers < 2U ||
        layer_depth <= 0.0f || !isfinite(layer_depth)) return;
    flame->heat = heat;
    flame->segments = segments;
    flame->layers = layers;
    flame->layer_depth = layer_depth;
    flame->random_state = seed == 0U ? 0x6D2B79F5U : seed;
    memset(heat, 0, (size_t)segments * layers);
}
radial_flame_update_internal() · source line 794
static void radial_flame_update_internal(radial_flame_t *flame, float wave_amplitude,
                                         float wave_count, float wave_phase_degrees,
                                         unsigned extra_cooling)

Calls into this documentation:

  1. Decisionflame == NULL || flame->heat == NULL || flame->segments < 2U || flame->layers < 2U
    True
    1. Exit this functionreturn;
    False

    Continue after this decision.

  2. Decisionextra_cooling > 248U
    True
    1. Executeextra_cooling = 248U;
    False

    Continue after this decision.

  3. FOR loopunsigned layer = flame->layers - 1U; layer > 0U; --layer

    Repeat body while the loop condition permits; then continue below.

      1. FOR loopunsigned segment = 0U; segment < flame->segments; ++segment

        Repeat body while the loop condition permits; then continue below.

          1. Executeint source = (int)segment + (int)(radial_flame_random(flame) % 3U) - 1;
          2. Decisionsource < 0
            True
            1. Executesource += (int)flame->segments;
            False

            Continue after this decision.

          3. Decisionsource >= (int)flame->segments
            True
            1. Executesource -= (int)flame->segments;
            False

            Continue after this decision.

          4. Executeconst int heat = flame->heat[(layer - 1U) * flame->segments + (unsigned)source] - (int)(3U + radial_flame_random(flame) % (7U + extra_cooling));
          5. Executeflame->heat[layer * flame->segments + segment] = heat > 0 ? (uint8_t)heat : 0U;
  4. Executeconst float phase = wave_phase_degrees * 0.01745329252f;
  5. FOR loopunsigned segment = 0U; segment < flame->segments; ++segment

    Repeat body while the loop condition permits; then continue below.

      1. Executeconst float position = (float)segment * 6.28318531f * wave_count / flame->segments;
      2. Executeint heat = 170 + (int)(radial_flame_random(flame) % 86U) + (int)lroundf(sinf(position + phase) * wave_amplitude);
      3. Decisionheat < 40
        True
        1. Executeheat = 40;
        False

        Continue after this decision.

      4. Decisionheat > 255
        True
        1. Executeheat = 255;
        False

        Continue after this decision.

      5. Executeflame->heat[segment] = (uint8_t)heat;
Read original function source
static void radial_flame_update_internal(radial_flame_t *flame, float wave_amplitude,
                                         float wave_count, float wave_phase_degrees,
                                         unsigned extra_cooling)
{
    if (flame == NULL || flame->heat == NULL || flame->segments < 2U ||
        flame->layers < 2U) return;
    if (extra_cooling > 248U) extra_cooling = 248U;
    for (unsigned layer = flame->layers - 1U; layer > 0U; --layer) {
        for (unsigned segment = 0U; segment < flame->segments; ++segment) {
            int source = (int)segment + (int)(radial_flame_random(flame) % 3U) - 1;
            if (source < 0) source += (int)flame->segments;
            if (source >= (int)flame->segments) source -= (int)flame->segments;
            const int heat = flame->heat[(layer - 1U) * flame->segments + (unsigned)source] -
                             (int)(3U + radial_flame_random(flame) %
                                   (7U + extra_cooling));
            flame->heat[layer * flame->segments + segment] = heat > 0 ? (uint8_t)heat : 0U;
        }
    }
    const float phase = wave_phase_degrees * 0.01745329252f;
    for (unsigned segment = 0U; segment < flame->segments; ++segment) {
        const float position = (float)segment * 6.28318531f * wave_count / flame->segments;
        int heat = 170 + (int)(radial_flame_random(flame) % 86U) +
                   (int)lroundf(sinf(position + phase) * wave_amplitude);
        if (heat < 40) heat = 40;
        if (heat > 255) heat = 255;
        flame->heat[segment] = (uint8_t)heat;
    }
}
radial_flame_update() · source line 823
void radial_flame_update(radial_flame_t *flame)

Calls into this documentation:

  1. Executeradial_flame_update_internal(flame, 0.0f, 1.0f, 0.0f, 0U);
Read original function source
void radial_flame_update(radial_flame_t *flame)
{
    radial_flame_update_internal(flame, 0.0f, 1.0f, 0.0f, 0U);
}
radial_flame_update_wavy() · source line 828
void radial_flame_update_wavy(radial_flame_t *flame, float wave_amplitude,
                              float wave_count, float wave_phase_degrees,
                              unsigned extra_cooling)

Calls into this documentation:

  1. Decision!isfinite(wave_amplitude) || !isfinite(wave_count) || !isfinite(wave_phase_degrees) || wave_amplitude < 0.0f || wave_count <= 0.0f
    True
    1. Exit this functionreturn;
    False

    Continue after this decision.

  2. Executeradial_flame_update_internal(flame, wave_amplitude, wave_count, wave_phase_degrees, extra_cooling);
Read original function source
void radial_flame_update_wavy(radial_flame_t *flame, float wave_amplitude,
                              float wave_count, float wave_phase_degrees,
                              unsigned extra_cooling)
{
    if (!isfinite(wave_amplitude) || !isfinite(wave_count) ||
        !isfinite(wave_phase_degrees) || wave_amplitude < 0.0f ||
        wave_count <= 0.0f) return;
    radial_flame_update_internal(flame, wave_amplitude, wave_count,
                                 wave_phase_degrees, extra_cooling);
}
draw_radial_flame() · source line 839
void draw_radial_flame(uint16_t *frame, const radial_flame_t *flame,
                       int centre_x, int centre_y, float inner_radius,
                       float angle_offset_degrees, const uint16_t colours[5])

Calls into this documentation:

  1. Decisionframe == NULL || flame == NULL || flame->heat == NULL || colours == NULL || flame->segments < 2U || flame->layers < 2U || flame->layer_depth <= 0.0f || inner_radius < 0.0f || !isfinite(inner_radius) || !isfinite(angle_offset_degrees)
    True
    1. Exit this functionreturn;
    False

    Continue after this decision.

  2. Executeconst float offset = angle_offset_degrees * 0.01745329252f;
  3. Executeconst float angle_step = 6.28318531f / flame->segments;
  4. FOR loopunsigned layer = 0U; layer < flame->layers; ++layer

    Repeat body while the loop condition permits; then continue below.

      1. Executeconst float radius = inner_radius + (float)layer * flame->layer_depth;
      2. FOR loopunsigned segment = 0U; segment < flame->segments; ++segment

        Repeat body while the loop condition permits; then continue below.

          1. Executeconst uint8_t heat = flame->heat[layer * flame->segments + segment];
          2. Decisionheat < 40U
            True
            1. Next loop iterationcontinue;
            False

            Continue after this decision.

          3. Executeconst unsigned colour = heat < 90U ? 0U : heat < 140U ? 1U : heat < 190U ? 2U : heat < 230U ? 3U : 4U;
          4. Executeconst float first_angle = offset + (float)segment * angle_step;
          5. Executeconst float second_angle = first_angle + angle_step;
          6. Executeconst float inner = radius > 0.75f ? radius - 0.75f : 0.0f;
          7. Executeconst float outer = radius + flame->layer_depth + 0.75f;
          8. Executeconst graphics_point_t wedge[4] = { { centre_x + (int)lroundf(cosf(first_angle) * inner), centre_y + (int)lroundf(sinf(first_angle) * inner) }, { centre_x + (int)lroundf(cosf(second_angle) * inner), centre_y + (int)lroundf(sinf(second_angle) * inner) }, { centre_x + (int)lroundf(cosf(second_angle) * outer), centre_y + (int)lroundf(sinf(second_angle) * outer) }, { centre_x + (int)lroundf(cosf(first_angle) * outer), centre_y + (int)lroundf(sinf(first_angle) * outer) }, };
          9. Executefill_convex_polygon(frame, wedge, 4U, colours[colour]);
Read original function source
void draw_radial_flame(uint16_t *frame, const radial_flame_t *flame,
                       int centre_x, int centre_y, float inner_radius,
                       float angle_offset_degrees, const uint16_t colours[5])
{
    if (frame == NULL || flame == NULL || flame->heat == NULL || colours == NULL ||
        flame->segments < 2U || flame->layers < 2U || flame->layer_depth <= 0.0f ||
        inner_radius < 0.0f || !isfinite(inner_radius) ||
        !isfinite(angle_offset_degrees)) return;
    const float offset = angle_offset_degrees * 0.01745329252f;
    const float angle_step = 6.28318531f / flame->segments;
    for (unsigned layer = 0U; layer < flame->layers; ++layer) {
        const float radius = inner_radius + (float)layer * flame->layer_depth;
        for (unsigned segment = 0U; segment < flame->segments; ++segment) {
            const uint8_t heat = flame->heat[layer * flame->segments + segment];
            if (heat < 40U) continue;
            const unsigned colour = heat < 90U ? 0U : heat < 140U ? 1U :
                                    heat < 190U ? 2U : heat < 230U ? 3U : 4U;
            const float first_angle = offset + (float)segment * angle_step;
            const float second_angle = first_angle + angle_step;
            // The overlap removes both radial and angular black seams between cells.
            const float inner = radius > 0.75f ? radius - 0.75f : 0.0f;
            const float outer = radius + flame->layer_depth + 0.75f;
            const graphics_point_t wedge[4] = {
                { centre_x + (int)lroundf(cosf(first_angle) * inner),
                  centre_y + (int)lroundf(sinf(first_angle) * inner) },
                { centre_x + (int)lroundf(cosf(second_angle) * inner),
                  centre_y + (int)lroundf(sinf(second_angle) * inner) },
                { centre_x + (int)lroundf(cosf(second_angle) * outer),
                  centre_y + (int)lroundf(sinf(second_angle) * outer) },
                { centre_x + (int)lroundf(cosf(first_angle) * outer),
                  centre_y + (int)lroundf(sinf(first_angle) * outer) },
            };
            fill_convex_polygon(frame, wedge, 4U, colours[colour]);
        }
    }
}
Complete original file, including constants and data
/* Coded By JD(2026) */
#error "Reference listing only — not provided as build-ready source."
#include "game_graphics.h"
#include <stdbool.h>
#include <stddef.h>
#include <stdlib.h>
#include <string.h>
#include <limits.h>
#include <math.h>

void graphics_text_effect_calculate(graphics_text_effect_t effect, uint32_t elapsed_ms,
                                    unsigned text_length,
                                    const graphics_text_effect_params_t *params,
                                    uint16_t base_color,
                                    const uint16_t *colour_cycle,
                                    unsigned colour_cycle_count,
                                    graphics_text_effect_state_t *state)
{
    if (state == NULL) return;

    *state = (graphics_text_effect_state_t){
        .visible_characters = text_length,
        .color = base_color,
        .scale = 1.0f,
    };
    if (params == NULL || effect >= GRAPHICS_TEXT_EFFECT_COUNT) return;

    switch (effect) {
    case GRAPHICS_TEXT_EFFECT_TRAVEL_LEFT:
        if (params->travel_distance > 0) {
            const unsigned cycle = (unsigned)params->travel_distance * 2U;
            state->x_offset = params->travel_distance -
                (int)((elapsed_ms * 55U / 1000U) % cycle);
        }
        break;
    case GRAPHICS_TEXT_EFFECT_DRIFT:
        if (params->drift_distance > 0) {
            const unsigned cycle = (unsigned)params->drift_distance * 2U;
            state->x_offset = (int)((elapsed_ms * 55U / 1000U) % cycle) -
                              params->drift_distance;
        }
        break;
    case GRAPHICS_TEXT_EFFECT_BOB:
        state->y_offset = (int)(sinf((float)elapsed_ms / 445.0f) *
                                (float)params->vertical_amplitude);
        break;
    case GRAPHICS_TEXT_EFFECT_WAVE:
        state->wave = true;
        break;
    case GRAPHICS_TEXT_EFFECT_JITTER:
        if (params->jitter_amplitude > 0) {
            const unsigned range = (unsigned)params->jitter_amplitude * 2U + 1U;
            state->x_offset = (int)((elapsed_ms / 18U) % range) -
                              params->jitter_amplitude;
        }
        break;
    case GRAPHICS_TEXT_EFFECT_REVEAL:
        state->visible_characters = (elapsed_ms / 130U) % (text_length + 1U);
        break;
    case GRAPHICS_TEXT_EFFECT_COLOUR_CYCLE:
        if (colour_cycle != NULL && colour_cycle_count > 0U) {
            state->color = colour_cycle[(elapsed_ms / 180U) % colour_cycle_count];
        }
        break;
    case GRAPHICS_TEXT_EFFECT_SWING:
        state->angle_degrees = sinf((float)elapsed_ms / 382.0f) *
                               params->maximum_angle_degrees;
        break;
    case GRAPHICS_TEXT_EFFECT_PULSE:
        if (params->minimum_scale > 0.0f &&
            params->maximum_scale >= params->minimum_scale) {
            const float progress = (sinf((float)elapsed_ms / 318.0f) + 1.0f) * 0.5f;
            state->scale = params->minimum_scale +
                           (params->maximum_scale - params->minimum_scale) * progress;
        }
        break;
    case GRAPHICS_TEXT_EFFECT_DROP:
        if (params->drop_height > 0) {
            const uint32_t drop_elapsed = elapsed_ms % 2800U;
            const int travelled = (int)(drop_elapsed * (uint32_t)params->drop_height / 700U);
            state->y_offset = params->drop_height -
                              (travelled < params->drop_height ? travelled : params->drop_height);
        }
        break;
    case GRAPHICS_TEXT_EFFECT_TYPEWRITER:
        state->visible_characters = (elapsed_ms / 90U) % (text_length + 1U);
        break;
    default:
        break;
    }
}

int graphics_text_effect_wave_offset(uint32_t elapsed_ms, unsigned character_index,
                                     int wave_amplitude)
{
    if (wave_amplitude <= 0) return 0;
    const float phase = ((float)(elapsed_ms % 900U) / 900.0f) * 6.2831853f;
    return (int)(sinf(phase + (float)character_index * 0.55f) *
                 (float)wave_amplitude);
}

void clear(uint16_t *frame, uint16_t color)
{
    for (size_t pixel = 0; pixel < LCD_WIDTH * LCD_HEIGHT; ++pixel) {
        frame[pixel] = color;
    }
}

void fill_rect(uint16_t *frame, int x, int y, int width, int height,
                         uint16_t color)
{
    for (int row = y; row < y + height; ++row) {
        for (int column = x; column < x + width; ++column) {
            if (column >= 0 && column < LCD_WIDTH && row >= 0 && row < LCD_HEIGHT) {
                frame[(size_t)row * LCD_WIDTH + column] = color;
            }
        }
    }
}

void draw_rolling_raster_band(uint16_t *frame, int band_top, int block_height,
                              int band_height, uint32_t elapsed_ms,
                              uint32_t colour_roll_ms,
                              const graphics_raster_band_palette_t *palette)
{
    if (frame == NULL || palette == NULL || palette->colours == NULL ||
        palette->colour_count == 0U || block_height <= 0 || band_height <= 0 ||
        colour_roll_ms == 0U) return;

    const int stripe_count = (block_height + band_height - 1) / band_height;
    const int colour_roll_offset = (int)(elapsed_ms / colour_roll_ms) % stripe_count;
    for (int y = band_top, stripe = 0; y < band_top + block_height;
         y += band_height, ++stripe) {
        const int rolled = (stripe + colour_roll_offset) % stripe_count;
        const int palette_index = stripe_count > 1 ?
            rolled * ((int)palette->colour_count - 1) / (stripe_count - 1) : 0;
        const int visible_height = y + band_height <= band_top + block_height ?
            band_height : band_top + block_height - y;
        fill_rect(frame, 0, y, LCD_WIDTH, visible_height,
                  palette->colours[palette_index]);
    }
}

static void draw_scaled_image(uint16_t *frame, int x, int y,
                              const uint16_t *pixels, const uint8_t *mask,
                              int source_width, int source_height,
                              int draw_width, int draw_height, uint16_t color)
{
    if (frame == NULL || source_width <= 0 || source_height <= 0 ||
        draw_width <= 0 || draw_height <= 0) {
        return;
    }

    const size_t mask_stride = ((size_t)source_width + 7U) / 8U;
    if ((size_t)source_height > SIZE_MAX / mask_stride ||
        (pixels != NULL &&
         (size_t)source_height > SIZE_MAX / sizeof(*pixels) / (size_t)source_width)) {
        return;
    }

    // Clip the destination before sampling; retain the original image origin.
    const int64_t right = (int64_t)x + draw_width;
    const int64_t bottom = (int64_t)y + draw_height;
    if (right <= 0 || bottom <= 0 || x >= LCD_WIDTH || y >= LCD_HEIGHT) {
        return;
    }
    const int left = x < 0 ? 0 : x;
    const int top = y < 0 ? 0 : y;
    const int end_x = right > LCD_WIDTH ? LCD_WIDTH : (int)right;
    const int end_y = bottom > LCD_HEIGHT ? LCD_HEIGHT : (int)bottom;

    for (int row = top; row < end_y; ++row) {
        const size_t source_y = (size_t)(((int64_t)row - y) * source_height / draw_height);
        for (int column = left; column < end_x; ++column) {
            const size_t source_x = (size_t)(((int64_t)column - x) * source_width / draw_width);
            if (mask != NULL &&
                (mask[source_y * mask_stride + source_x / 8U] &
                 (1U << (7U - source_x % 8U))) == 0) {
                continue;
            }
            frame[(size_t)row * LCD_WIDTH + column] = pixels != NULL
                ? pixels[source_y * (size_t)source_width + source_x] : color;
        }
    }
}

void draw_bitmap_scaled(uint16_t *frame, int x, int y,
                    const uint8_t *bitmap, int source_width, int source_height,
                    int draw_width, int draw_height, uint16_t color)
{
    if (bitmap == NULL) {
        return;
    }
    draw_scaled_image(frame, x, y, NULL, bitmap, source_width, source_height,
                      draw_width, draw_height, color);
}

void draw_rgb565_scaled(uint16_t *frame, int x, int y,
                          const uint16_t *pixels, const uint8_t *mask,
                          int source_width, int source_height,
                          int draw_width, int draw_height)
{
    if (pixels == NULL) {
        return;
    }
    draw_scaled_image(frame, x, y, pixels, mask, source_width, source_height,
                      draw_width, draw_height, 0);
}

static void draw_transformed_image(uint16_t *frame, int destination_centre_x,
                                   int destination_centre_y,
                                   const uint16_t *pixels, const uint8_t *mask,
                                   int source_width, int source_height,
                                   float scale, float angle_degrees, uint16_t color)
{
    if (frame == NULL || source_width <= 0 || source_height <= 0 ||
        scale <= 0.0f || !isfinite(scale) || !isfinite(angle_degrees)) {
        return;
    }

    const float radians = angle_degrees * 0.01745329252f;
    const float cosine = cosf(radians);
    const float sine = sinf(radians);
    const float half_width = (fabsf(cosine) * source_width +
                              fabsf(sine) * source_height) * scale * 0.5f + 1.0f;
    const float half_height = (fabsf(sine) * source_width +
                               fabsf(cosine) * source_height) * scale * 0.5f + 1.0f;
    const int start_x = (int)floorf(destination_centre_x - half_width);
    const int end_x = (int)ceilf(destination_centre_x + half_width);
    const int start_y = (int)floorf(destination_centre_y - half_height);
    const int end_y = (int)ceilf(destination_centre_y + half_height);
    const size_t mask_stride = ((size_t)source_width + 7U) / 8U;
    const float source_centre_x = ((float)source_width - 1.0f) * 0.5f;
    const float source_centre_y = ((float)source_height - 1.0f) * 0.5f;

    for (int destination_y = start_y; destination_y <= end_y; ++destination_y) {
        if (destination_y < 0 || destination_y >= LCD_HEIGHT) continue;
        const float y = (float)(destination_y - destination_centre_y);
        for (int destination_x = start_x; destination_x <= end_x; ++destination_x) {
            if (destination_x < 0 || destination_x >= LCD_WIDTH) continue;
            const float x = (float)(destination_x - destination_centre_x);
            const int source_x = (int)floorf((cosine * x + sine * y) / scale +
                                              source_centre_x + 0.5f);
            const int source_y = (int)floorf((-sine * x + cosine * y) / scale +
                                              source_centre_y + 0.5f);
            if (source_x < 0 || source_x >= source_width ||
                source_y < 0 || source_y >= source_height) continue;
            if (mask != NULL &&
                (mask[(size_t)source_y * mask_stride + (unsigned)source_x / 8U] &
                 (1U << (7U - (unsigned)source_x % 8U))) == 0) continue;
            frame[(size_t)destination_y * LCD_WIDTH + destination_x] = pixels != NULL
                ? pixels[(size_t)source_y * source_width + source_x] : color;
        }
    }
}

void draw_bitmap_transformed(uint16_t *frame, int destination_centre_x,
                                int destination_centre_y, const uint8_t *bitmap,
                                int source_width, int source_height,
                                float scale, float angle_degrees, uint16_t color)
{
    if (bitmap == NULL) return;
    draw_transformed_image(frame, destination_centre_x, destination_centre_y,
                           NULL, bitmap, source_width, source_height,
                           scale, angle_degrees, color);
}

void draw_rgb565_transformed(uint16_t *frame, int destination_centre_x,
                                      int destination_centre_y, const uint16_t *pixels,
                                      const uint8_t *mask, int source_width,
                                      int source_height, float scale,
                                      float angle_degrees)
{
    if (pixels == NULL) return;
    draw_transformed_image(frame, destination_centre_x, destination_centre_y,
                           pixels, mask, source_width, source_height,
                           scale, angle_degrees, 0);
}

void draw_ui_bitmap_8x8(uint16_t *frame, int x, int y,
                        const uint8_t bitmap[8], int scale, uint16_t color)
{
    if (scale <= 0 || scale > INT_MAX / 8) {
        return;
    }
    draw_bitmap_scaled(frame, x, y, bitmap, 8, 8, 8 * scale, 8 * scale, color);
}

void fill_circle(uint16_t *frame, int x, int y, int radius,
                           uint16_t color)
{
    for (int row = -radius; row <= radius; ++row) {
        for (int column = -radius; column <= radius; ++column) {
            if (column * column + row * row <= radius * radius) {
                fill_rect(frame, x + column, y + row, 1, 1, color);
            }
        }
    }
}

// Six-bit rows, ordered as digits followed by uppercase letters.  The empty
// eighth row gives the typeface a less crowded baseline than the former 5x7 font.
static const uint8_t ui_glyphs[36][UI_FONT_HEIGHT] = {
    {0x1E,0x33,0x37,0x3B,0x33,0x33,0x1E,0x00}, {0x0C,0x1C,0x0C,0x0C,0x0C,0x0C,0x3F,0x00},
    {0x1E,0x33,0x03,0x06,0x0C,0x18,0x3F,0x00}, {0x1E,0x33,0x03,0x0E,0x03,0x33,0x1E,0x00},
    {0x06,0x0E,0x1E,0x36,0x3F,0x06,0x06,0x00}, {0x3F,0x30,0x3E,0x03,0x03,0x33,0x1E,0x00},
    {0x0E,0x18,0x30,0x3E,0x33,0x33,0x1E,0x00}, {0x3F,0x03,0x06,0x0C,0x18,0x18,0x18,0x00},
    {0x1E,0x33,0x33,0x1E,0x33,0x33,0x1E,0x00}, {0x1E,0x33,0x33,0x1F,0x03,0x06,0x1C,0x00},
    {0x0C,0x1E,0x33,0x33,0x3F,0x33,0x33,0x00}, {0x3E,0x33,0x33,0x3E,0x33,0x33,0x3E,0x00},
    {0x1E,0x33,0x30,0x30,0x30,0x33,0x1E,0x00}, {0x3C,0x36,0x33,0x33,0x33,0x36,0x3C,0x00},
    {0x3F,0x30,0x30,0x3E,0x30,0x30,0x3F,0x00}, {0x3F,0x30,0x30,0x3E,0x30,0x30,0x30,0x00},
    {0x1E,0x33,0x30,0x37,0x33,0x33,0x1F,0x00}, {0x33,0x33,0x33,0x3F,0x33,0x33,0x33,0x00},
    {0x1E,0x0C,0x0C,0x0C,0x0C,0x0C,0x1E,0x00}, {0x07,0x03,0x03,0x03,0x03,0x33,0x1E,0x00},
    {0x33,0x36,0x3C,0x38,0x3C,0x36,0x33,0x00}, {0x30,0x30,0x30,0x30,0x30,0x30,0x3F,0x00},
    {0x21,0x33,0x3F,0x3F,0x33,0x33,0x33,0x00}, {0x33,0x3B,0x3F,0x3F,0x37,0x33,0x33,0x00},
    {0x1E,0x33,0x33,0x33,0x33,0x33,0x1E,0x00}, {0x3E,0x33,0x33,0x3E,0x30,0x30,0x30,0x00},
    {0x1E,0x33,0x33,0x33,0x3B,0x36,0x1D,0x00}, {0x3E,0x33,0x33,0x3E,0x3C,0x36,0x33,0x00},
    {0x1F,0x30,0x30,0x1E,0x03,0x03,0x3E,0x00}, {0x3F,0x2D,0x0C,0x0C,0x0C,0x0C,0x1E,0x00},
    {0x33,0x33,0x33,0x33,0x33,0x33,0x1E,0x00}, {0x33,0x33,0x33,0x33,0x33,0x1E,0x0C,0x00},
    {0x33,0x33,0x33,0x3F,0x3F,0x33,0x33,0x00}, {0x33,0x33,0x1E,0x0C,0x1E,0x33,0x33,0x00},
    {0x33,0x33,0x1E,0x0C,0x0C,0x0C,0x1E,0x00}, {0x3F,0x03,0x06,0x0C,0x18,0x30,0x3F,0x00},
};

static const uint8_t *ui_glyph(char symbol)
{
    static const uint8_t colon[UI_FONT_HEIGHT] = {0,0x0C,0x0C,0,0x0C,0x0C,0,0};
    static const uint8_t slash[UI_FONT_HEIGHT] = {0x03,0x06,0x06,0x0C,0x18,0x18,0x30,0};
    static const uint8_t minus[UI_FONT_HEIGHT] = {0,0,0,0x3F,0,0,0,0};
    static const uint8_t plus[UI_FONT_HEIGHT] = {0,0x0C,0x0C,0x3F,0x0C,0x0C,0,0};
    static const uint8_t dot[UI_FONT_HEIGHT] = {0,0,0,0,0,0,0x0C,0};
    static const uint8_t apostrophe[UI_FONT_HEIGHT] = {0x0C,0x0C,0x18,0,0,0,0,0};
    static const uint8_t question[UI_FONT_HEIGHT] = {0x1E,0x33,0x03,0x06,0x0C,0,0x0C,0};
    static const uint8_t exclamation[UI_FONT_HEIGHT] = {0x0C,0x0C,0x0C,0x0C,0x0C,0,0x0C,0};
    if (symbol >= 'a' && symbol <= 'z') symbol = (char)(symbol - 'a' + 'A');
    if (symbol >= '0' && symbol <= '9') return ui_glyphs[symbol - '0'];
    if (symbol >= 'A' && symbol <= 'Z') return ui_glyphs[symbol - 'A' + 10];
    switch (symbol) {
    case ':': return colon;
    case '/': return slash;
    case '-': return minus;
    case '+': return plus;
    case '.': return dot;
    case '\'': return apostrophe;
    case '?': return question;
    case '!': return exclamation;
    default: return NULL;
    }
}

void draw_text(uint16_t *frame, int x, int y, const char *text,
               int scale, uint16_t color)
{
    if (frame == NULL || text == NULL || scale <= 0) return;
    for (; *text != '\0'; ++text, x += UI_FONT_ADVANCE * scale) {
        const uint8_t *glyph = ui_glyph(*text);
        if (glyph == NULL) continue;
        for (int row = 0; row < UI_FONT_HEIGHT; ++row) {
            for (int column = 0; column < UI_FONT_WIDTH; ++column) {
                if ((glyph[row] & (1U << (UI_FONT_WIDTH - 1 - column))) != 0)
                    fill_rect(frame, x + column * scale, y + row * scale,
                              scale, scale, color);
            }
        }
    }
}


void draw_rect(uint16_t *frame, int x, int y, int width, int height,
                     uint16_t color)
{
    if (width <= 0 || height <= 0) {
        return;
    }
    fill_rect(frame, x, y, width, 1, color);
    fill_rect(frame, x, y + height - 1, width, 1, color);
    fill_rect(frame, x, y, 1, height, color);
    fill_rect(frame, x + width - 1, y, 1, height, color);
}

void draw_line(uint16_t *frame, int x0, int y0, int x1, int y1, uint16_t color)
{
    const int dx = abs(x1 - x0);
    const int dy = -abs(y1 - y0);
    const int sx = x0 < x1 ? 1 : -1;
    const int sy = y0 < y1 ? 1 : -1;
    int error = dx + dy;
    while (true) {
        fill_rect(frame, x0, y0, 1, 1, color);
        if (x0 == x1 && y0 == y1) {
            break;
        }
        const int twice_error = 2 * error;
        if (twice_error >= dy) {
            error += dy;
            x0 += sx;
        }
        if (twice_error <= dx) {
            error += dx;
            y0 += sy;
        }
    }
}

static uint32_t mix_dotted_seed(uint32_t value)
{
    value ^= value >> 16;
    value *= 0x7FEB352DU;
    value ^= value >> 15;
    value *= 0x846CA68BU;
    return value ^ (value >> 16);
}

static unsigned greatest_common_divisor(unsigned left, unsigned right)
{
    while (right != 0U) {
        const unsigned remainder = left % right;
        left = right;
        right = remainder;
    }
    return left;
}

static bool dotted_index_is_active(unsigned index, unsigned dot_count,
                                   unsigned dot_density_percent, uint32_t random_seed)
{
    if (dot_density_percent == 0U || dot_count == 0U) return false;
    if (dot_density_percent >= 100U || dot_count == 1U) return true;

    const unsigned active_count = (dot_count * dot_density_percent + 99U) / 100U;
    if (active_count >= dot_count) return true;

    const unsigned start = mix_dotted_seed(random_seed) % dot_count;
    unsigned step = 1U + mix_dotted_seed(random_seed ^ 0x9E3779B9U) % (dot_count - 1U);
    while (greatest_common_divisor(step, dot_count) != 1U) {
        ++step;
        if (step >= dot_count) step = 1U;
    }
    for (unsigned selected = 0U; selected < active_count; ++selected) {
        const unsigned candidate = (unsigned)(((uint64_t)start +
                                                (uint64_t)selected * step) % dot_count);
        if (candidate == index) return true;
    }
    return false;
}

static bool dotted_index_is_sparkle(unsigned index, unsigned sparkle_density_percent,
                                    uint32_t random_seed)
{
    if (sparkle_density_percent == 0U) return false;
    if (sparkle_density_percent >= 100U) return true;
    const uint32_t value = mix_dotted_seed(random_seed ^ index * 0x9E3779B9U);
    return value % 100U < sparkle_density_percent;
}

void draw_dotted_line_density(uint16_t *frame, int x0, int y0, int x1, int y1,
                              unsigned dot_spacing, int dot_radius, uint16_t color,
                              unsigned dot_density_percent, uint32_t random_seed)
{
    if (frame == NULL || dot_spacing == 0U || dot_radius < 0 ||
        dot_density_percent > 100U) return;
    const float delta_x = (float)x1 - (float)x0;
    const float delta_y = (float)y1 - (float)y0;
    const float line_length = sqrtf(delta_x * delta_x + delta_y * delta_y);
    if (line_length == 0.0f) {
        if (dotted_index_is_active(0U, 1U, dot_density_percent, random_seed)) {
            fill_circle(frame, x0, y0, dot_radius, color);
        }
        return;
    }
    const unsigned dot_count = (unsigned)floorf(line_length / (float)dot_spacing) + 1U;
    for (unsigned index = 0U; index < dot_count; ++index) {
        if (!dotted_index_is_active(index, dot_count, dot_density_percent, random_seed)) {
            continue;
        }
        const float distance = (float)index * (float)dot_spacing;
        const float progress = distance / line_length;
        const int x = x0 + (int)lroundf(delta_x * progress);
        const int y = y0 + (int)lroundf(delta_y * progress);
        fill_circle(frame, x, y, dot_radius, color);
    }
}

void draw_dotted_line(uint16_t *frame, int x0, int y0, int x1, int y1,
                      unsigned dot_spacing, int dot_radius, uint16_t color)
{
    draw_dotted_line_density(frame, x0, y0, x1, y1, dot_spacing, dot_radius, color,
                             100U, 0U);
}

static bool packed_bitmap_pixel(const uint8_t *bitmap, unsigned source_width,
                                unsigned x, unsigned y)
{
    const size_t row_bytes = ((size_t)source_width + 7U) / 8U;
    const size_t byte_index = (size_t)y * row_bytes + x / 8U;
    return (bitmap[byte_index] & (uint8_t)(1U << (7U - x % 8U))) != 0U;
}

typedef struct {
    float pitch_cosine;
    float pitch_sine;
    float yaw_cosine;
    float yaw_sine;
    float roll_cosine;
    float roll_sine;
} dotted_bitmap_rotation_t;

static graphics_point_t project_dotted_bitmap_point(float source_x, float source_y,
                                                     float depth,
                                                     const dotted_bitmap_3d_params_t *params,
                                                     const dotted_bitmap_rotation_t *rotation)
{
    const float scaled_x = source_x * params->scale;
    const float scaled_y = source_y * params->scale;
    const float pitched_y = scaled_y * rotation->pitch_cosine -
                            depth * rotation->pitch_sine;
    const float pitched_z = scaled_y * rotation->pitch_sine +
                            depth * rotation->pitch_cosine;
    const float yawed_x = scaled_x * rotation->yaw_cosine +
                          pitched_z * rotation->yaw_sine;
    const float yawed_z = -scaled_x * rotation->yaw_sine +
                          pitched_z * rotation->yaw_cosine;
    const float rotated_x = yawed_x * rotation->roll_cosine -
                            pitched_y * rotation->roll_sine;
    const float rotated_y = yawed_x * rotation->roll_sine +
                            pitched_y * rotation->roll_cosine;
    const float perspective = params->camera_distance /
                              (params->camera_distance + yawed_z);
    const graphics_point_t point = {
        .x = params->centre_x + (int)lroundf(rotated_x * perspective),
        .y = params->centre_y + (int)lroundf(rotated_y * perspective),
    };
    return point;
}

static void draw_dotted_bitmap_depth_line(uint16_t *frame,
                                          float x, float y,
                                          const dotted_bitmap_3d_params_t *params,
                                          const dotted_bitmap_rotation_t *rotation,
                                          uint32_t random_seed)
{
    const graphics_point_t front = project_dotted_bitmap_point(x, y, 0.0f,
                                                                 params, rotation);
    const graphics_point_t back = project_dotted_bitmap_point(x, y, params->depth,
                                                                params, rotation);
    draw_dotted_line_density(frame, front.x, front.y, back.x, back.y,
                              params->depth_dot_spacing, params->dot_radius,
                              params->side_color, params->dot_density_percent,
                              random_seed);
}

static void draw_dotted_bitmap_face(uint16_t *frame, const uint8_t *bitmap,
                                    unsigned source_width, unsigned source_height,
                                    float depth, uint16_t color,
                                    const dotted_bitmap_3d_params_t *params,
                                    const dotted_bitmap_rotation_t *rotation,
                                    uint32_t random_seed,
                                    bool draw_sparkles)
{
    const float half_width = (float)source_width * 0.5f;
    const float half_height = (float)source_height * 0.5f;
    for (unsigned y = 0U; y < source_height; ++y) {
        unsigned run_start = 0U;
        while (run_start < source_width) {
            while (run_start < source_width &&
                   !packed_bitmap_pixel(bitmap, source_width, run_start, y)) {
                ++run_start;
            }
            if (run_start == source_width) break;
            unsigned run_end = run_start + 1U;
            while (run_end < source_width &&
                   packed_bitmap_pixel(bitmap, source_width, run_end, y)) {
                ++run_end;
            }
            const unsigned dot_count = run_end - run_start;
            const uint32_t line_seed = random_seed ^ y * 0x45D9F3BU ^
                                       run_start * 0x119DE1F3U;
            for (unsigned index = 0U; index < dot_count; ++index) {
                if (!dotted_index_is_active(index, dot_count,
                                            params->dot_density_percent, line_seed)) {
                    continue;
                }
                const float source_x = (float)(run_start + index) + 0.5f - half_width;
                const float source_y = (float)y + 0.5f - half_height;
                const graphics_point_t dot = project_dotted_bitmap_point(source_x, source_y,
                                                                           depth, params,
                                                                           rotation);
                fill_circle(frame, dot.x, dot.y, params->dot_radius, color);
                if (draw_sparkles && params->sparkle_density_percent != 0U &&
                    dotted_index_is_sparkle(index, params->sparkle_density_percent,
                                             line_seed ^ params->sparkle_phase * 0x27D4EB2DU)) {
                    fill_circle(frame, dot.x, dot.y, params->sparkle_radius,
                                params->sparkle_color);
                }
            }
            run_start = run_end;
        }
    }
}

void draw_bitmap_dotted_3d(uint16_t *frame, const uint8_t *bitmap,
                           unsigned source_width, unsigned source_height,
                           const dotted_bitmap_3d_params_t *params)
{
    if (frame == NULL || bitmap == NULL || params == NULL || source_width == 0U ||
        source_height == 0U || params->scale <= 0.0f || params->depth <= 0.0f ||
        params->camera_distance <= 0.0f || params->depth_dot_spacing == 0U ||
        params->dot_radius < 0 || params->dot_density_percent > 100U ||
        params->sparkle_density_percent > 100U || params->sparkle_radius < 0 ||
        !isfinite(params->scale) || !isfinite(params->pitch_degrees) ||
        !isfinite(params->yaw_degrees) || !isfinite(params->roll_degrees) ||
        !isfinite(params->depth) ||
        !isfinite(params->camera_distance)) {
        return;
    }

    const float half_width = (float)source_width * params->scale * 0.5f;
    const float half_height = (float)source_height * params->scale * 0.5f;
    const float safe_camera_distance = sqrtf(half_width * half_width +
                                             half_height * half_height +
                                             params->depth * params->depth);
    if (params->camera_distance <= safe_camera_distance) {
        return;
    }

    const float pitch_radians = params->pitch_degrees * 0.01745329252f;
    const float yaw_radians = params->yaw_degrees * 0.01745329252f;
    const float roll_radians = params->roll_degrees * 0.01745329252f;
    const dotted_bitmap_rotation_t rotation = {
        .pitch_cosine = cosf(pitch_radians),
        .pitch_sine = sinf(pitch_radians),
        .yaw_cosine = cosf(yaw_radians),
        .yaw_sine = sinf(yaw_radians),
        .roll_cosine = cosf(roll_radians),
        .roll_sine = sinf(roll_radians),
    };
    const float source_half_width = (float)source_width * 0.5f;
    const float source_half_height = (float)source_height * 0.5f;

    // Draw the back first, then edge joins, then front, so the transparent
    // dotted volume remains readable over the caller's existing background.
    draw_dotted_bitmap_face(frame, bitmap, source_width, source_height,
                            params->depth, params->back_color, params, &rotation,
                            params->random_seed ^ 0xA511E9B3U, false);

    for (unsigned y = 0U; y < source_height; ++y) {
        unsigned run_start = 0U;
        while (run_start < source_width) {
            while (run_start < source_width &&
                   !packed_bitmap_pixel(bitmap, source_width, run_start, y)) {
                ++run_start;
            }
            if (run_start == source_width) break;
            unsigned run_end = run_start + 1U;
            while (run_end < source_width &&
                   packed_bitmap_pixel(bitmap, source_width, run_end, y)) {
                ++run_end;
            }
            const float edge_y = (float)y - source_half_height;
            draw_dotted_bitmap_depth_line(frame, (float)run_start - source_half_width, edge_y,
                                          params, &rotation,
                                          params->random_seed ^ y * 0x45D9F3BU ^
                                          run_start * 0x119DE1F3U ^ 0x3C6EF372U);
            draw_dotted_bitmap_depth_line(frame, (float)run_end - source_half_width, edge_y,
                                          params, &rotation,
                                          params->random_seed ^ y * 0x45D9F3BU ^
                                          run_end * 0x119DE1F3U ^ 0xDAA66D2BU);
            run_start = run_end;
        }
    }

    draw_dotted_bitmap_face(frame, bitmap, source_width, source_height,
                            0.0f, params->front_color, params, &rotation,
                            params->random_seed ^ 0x63D83595U, true);
}

void fill_convex_polygon(uint16_t *frame, const graphics_point_t *points,
                         size_t point_count, uint16_t color)
{
    if (frame == NULL || points == NULL || point_count < 3U) return;

    int minimum_y = points[0].y;
    int maximum_y = points[0].y;
    for (size_t point = 1U; point < point_count; ++point) {
        if (points[point].y < minimum_y) minimum_y = points[point].y;
        if (points[point].y > maximum_y) maximum_y = points[point].y;
    }
    if (minimum_y < 0) minimum_y = 0;
    if (maximum_y >= LCD_HEIGHT) maximum_y = LCD_HEIGHT - 1;

    for (int y = minimum_y; y <= maximum_y; ++y) {
        int left = LCD_WIDTH;
        int right = -1;
        for (size_t point = 0U; point < point_count; ++point) {
            const graphics_point_t first = points[point];
            const graphics_point_t second = points[(point + 1U) % point_count];
            const int edge_min_y = first.y < second.y ? first.y : second.y;
            const int edge_max_y = first.y > second.y ? first.y : second.y;
            if (edge_min_y == edge_max_y || y < edge_min_y || y >= edge_max_y) continue;
            const int x = first.x + (y - first.y) * (second.x - first.x) /
                (second.y - first.y);
            if (x < left) left = x;
            if (x > right) right = x;
        }
        if (right < 0 || left >= LCD_WIDTH) continue;
        if (left < 0) left = 0;
        if (right >= LCD_WIDTH) right = LCD_WIDTH - 1;
        if (left <= right) fill_rect(frame, left, y, right - left + 1, 1, color);
    }
}

void draw_ring(uint16_t *frame, int x, int y, int radius, uint16_t color)
{
    for (int row = -radius; row <= radius; ++row) {
        for (int column = -radius; column <= radius; ++column) {
            const int distance = column * column + row * row;
            if (distance <= radius * radius &&
                (radius == 0 || distance > (radius - 1) * (radius - 1))) {
                fill_rect(frame, x + column, y + row, 1, 1, color);
            }
        }
    }
}

void draw_text_centred(uint16_t *frame, int x, int y, const char *text,
                          int scale, uint16_t color)
{
    const int length = (int)strlen(text);
    const int width = length > 0 ? length * UI_FONT_ADVANCE * scale - scale : 0;
    draw_text(frame, x - width / 2, y, text, scale, color);
}

void draw_text_transformed(uint16_t *frame, int destination_centre_x,
                              int destination_centre_y, const char *text,
                              int source_scale, float transform_scale,
                              float angle_degrees, uint16_t color)
{
    if (frame == NULL || text == NULL || source_scale <= 0 ||
        transform_scale <= 0.0f || !isfinite(transform_scale) ||
        !isfinite(angle_degrees)) return;

    int length = 0;
    while (text[length] != '\0') ++length;
    if (length == 0) return;
    const int source_width = length * UI_FONT_ADVANCE * source_scale - source_scale;
    const int source_height = UI_FONT_HEIGHT * source_scale;
    const float radians = angle_degrees * 0.01745329252f;
    const float cosine = cosf(radians), sine = sinf(radians);
    const float centre_x = ((float)source_width - 1.0f) * 0.5f;
    const float centre_y = ((float)source_height - 1.0f) * 0.5f;
    int glyph_x = 0;

    for (int character = 0; character < length; ++character, glyph_x += UI_FONT_ADVANCE * source_scale) {
        const uint8_t *glyph = ui_glyph(text[character]);
        if (glyph == NULL) continue;

        for (int row = 0; row < UI_FONT_HEIGHT; ++row) for (int column = 0; column < UI_FONT_WIDTH; ++column) {
            if ((glyph[row] & (1U << (UI_FONT_WIDTH - 1 - column))) == 0) continue;
            for (int pixel_y = 0; pixel_y < source_scale; ++pixel_y)
                for (int pixel_x = 0; pixel_x < source_scale; ++pixel_x) {
                    const float source_x = glyph_x + column * source_scale + pixel_x - centre_x;
                    const float source_y = row * source_scale + pixel_y - centre_y;
                    const int destination_x = (int)lroundf(destination_centre_x +
                        (cosine * source_x - sine * source_y) * transform_scale);
                    const int destination_y = (int)lroundf(destination_centre_y +
                        (sine * source_x + cosine * source_y) * transform_scale);
                    if (destination_x >= 0 && destination_x < LCD_WIDTH &&
                        destination_y >= 0 && destination_y < LCD_HEIGHT)
                        frame[(size_t)destination_y * LCD_WIDTH + destination_x] = color;
                }
        }
    }
}

static uint32_t radial_flame_random(radial_flame_t *flame)
{
    flame->random_state ^= flame->random_state << 13;
    flame->random_state ^= flame->random_state >> 17;
    flame->random_state ^= flame->random_state << 5;
    return flame->random_state;
}

void radial_flame_initialize(radial_flame_t *flame, uint8_t *heat,
                             unsigned segments, unsigned layers,
                             float layer_depth, uint32_t seed)
{
    if (flame == NULL || heat == NULL || segments < 2U || layers < 2U ||
        layer_depth <= 0.0f || !isfinite(layer_depth)) return;
    flame->heat = heat;
    flame->segments = segments;
    flame->layers = layers;
    flame->layer_depth = layer_depth;
    flame->random_state = seed == 0U ? 0x6D2B79F5U : seed;
    memset(heat, 0, (size_t)segments * layers);
}

static void radial_flame_update_internal(radial_flame_t *flame, float wave_amplitude,
                                         float wave_count, float wave_phase_degrees,
                                         unsigned extra_cooling)
{
    if (flame == NULL || flame->heat == NULL || flame->segments < 2U ||
        flame->layers < 2U) return;
    if (extra_cooling > 248U) extra_cooling = 248U;
    for (unsigned layer = flame->layers - 1U; layer > 0U; --layer) {
        for (unsigned segment = 0U; segment < flame->segments; ++segment) {
            int source = (int)segment + (int)(radial_flame_random(flame) % 3U) - 1;
            if (source < 0) source += (int)flame->segments;
            if (source >= (int)flame->segments) source -= (int)flame->segments;
            const int heat = flame->heat[(layer - 1U) * flame->segments + (unsigned)source] -
                             (int)(3U + radial_flame_random(flame) %
                                   (7U + extra_cooling));
            flame->heat[layer * flame->segments + segment] = heat > 0 ? (uint8_t)heat : 0U;
        }
    }
    const float phase = wave_phase_degrees * 0.01745329252f;
    for (unsigned segment = 0U; segment < flame->segments; ++segment) {
        const float position = (float)segment * 6.28318531f * wave_count / flame->segments;
        int heat = 170 + (int)(radial_flame_random(flame) % 86U) +
                   (int)lroundf(sinf(position + phase) * wave_amplitude);
        if (heat < 40) heat = 40;
        if (heat > 255) heat = 255;
        flame->heat[segment] = (uint8_t)heat;
    }
}

void radial_flame_update(radial_flame_t *flame)
{
    radial_flame_update_internal(flame, 0.0f, 1.0f, 0.0f, 0U);
}

void radial_flame_update_wavy(radial_flame_t *flame, float wave_amplitude,
                              float wave_count, float wave_phase_degrees,
                              unsigned extra_cooling)
{
    if (!isfinite(wave_amplitude) || !isfinite(wave_count) ||
        !isfinite(wave_phase_degrees) || wave_amplitude < 0.0f ||
        wave_count <= 0.0f) return;
    radial_flame_update_internal(flame, wave_amplitude, wave_count,
                                 wave_phase_degrees, extra_cooling);
}

void draw_radial_flame(uint16_t *frame, const radial_flame_t *flame,
                       int centre_x, int centre_y, float inner_radius,
                       float angle_offset_degrees, const uint16_t colours[5])
{
    if (frame == NULL || flame == NULL || flame->heat == NULL || colours == NULL ||
        flame->segments < 2U || flame->layers < 2U || flame->layer_depth <= 0.0f ||
        inner_radius < 0.0f || !isfinite(inner_radius) ||
        !isfinite(angle_offset_degrees)) return;
    const float offset = angle_offset_degrees * 0.01745329252f;
    const float angle_step = 6.28318531f / flame->segments;
    for (unsigned layer = 0U; layer < flame->layers; ++layer) {
        const float radius = inner_radius + (float)layer * flame->layer_depth;
        for (unsigned segment = 0U; segment < flame->segments; ++segment) {
            const uint8_t heat = flame->heat[layer * flame->segments + segment];
            if (heat < 40U) continue;
            const unsigned colour = heat < 90U ? 0U : heat < 140U ? 1U :
                                    heat < 190U ? 2U : heat < 230U ? 3U : 4U;
            const float first_angle = offset + (float)segment * angle_step;
            const float second_angle = first_angle + angle_step;
            // The overlap removes both radial and angular black seams between cells.
            const float inner = radius > 0.75f ? radius - 0.75f : 0.0f;
            const float outer = radius + flame->layer_depth + 0.75f;
            const graphics_point_t wedge[4] = {
                { centre_x + (int)lroundf(cosf(first_angle) * inner),
                  centre_y + (int)lroundf(sinf(first_angle) * inner) },
                { centre_x + (int)lroundf(cosf(second_angle) * inner),
                  centre_y + (int)lroundf(sinf(second_angle) * inner) },
                { centre_x + (int)lroundf(cosf(second_angle) * outer),
                  centre_y + (int)lroundf(sinf(second_angle) * outer) },
                { centre_x + (int)lroundf(cosf(first_angle) * outer),
                  centre_y + (int)lroundf(sinf(first_angle) * outer) },
            };
            fill_convex_polygon(frame, wedge, 4U, colours[colour]);
        }
    }
}