Files
RP2350_MIDI_Lighter/Firmware/Display_Image.c

404 lines
14 KiB
C

/*
* Display_Image.c
*
* Created: Thu Nov 25 2021 13:15:42
* Author Chris
*/
#include "Display_Image.h"
#include <math.h>
#include "Display_Color.h"
#include "Display_Shapes.h"
// ============================================================================================
// Defines
// ============================================================================================
// Variables
static Display_Image_Buffer** _Current_Buffer;
static Display_Color _Transparent_Color;
static const uint8_t IMAGE_DEF_WIDTH = 0;
static const uint8_t IMAGE_DEF_HEIGHT = 1;
static const uint8_t IMAGE_DEF_DATA_SIZE = 2;
static const uint8_t IMAGE_DEF_DATA_OFFSET = 3;
// ============================================================================================
// Function Declarations
/*******************************************************************
Functions
*******************************************************************/
void Display_Image_Init(Display_Image_Buffer** current_buffer, Display_Color transparent_color)
{
_Current_Buffer = current_buffer;
_Transparent_Color = transparent_color;
}
void Display_Image_Draw_Color(int16_t x, int16_t y, Image_Color* image)
{
Display_Image_Draw_Color_Alpha(x, y, image, UINT8_MAX);
}
void Display_Image_Draw_Color_Alpha(int16_t x, int16_t y, Image_Color* image, uint8_t alpha)
{
if(image == NULL) {
return;
}
// Early exit for fully transparent
if(alpha == 0) {
return;
}
int16_t Width = image[IMAGE_DEF_WIDTH];
int16_t Height = image[IMAGE_DEF_HEIGHT];
// Early exit for completely off-screen images
if(x >= DISPLAY_WIDTH || y >= DISPLAY_HEIGHT || x + Width <= 0 || y + Height <= 0) {
return;
}
// Calculate clipping bounds
int16_t Start_X = (x < 0) ? -x : 0;
int16_t Start_Y = (y < 0) ? -y : 0;
int16_t End_X = (x + Width > DISPLAY_WIDTH) ? DISPLAY_WIDTH - x : Width;
int16_t End_Y = (y + Height > DISPLAY_HEIGHT) ? DISPLAY_HEIGHT - y : Height;
// Optimized rendering based on alpha value
if(alpha == 255)
{
// Fully opaque - use direct copy for maximum performance
for(int16_t iy = Start_Y; iy < End_Y; iy++)
{
int16_t Screen_Y = y + iy;
uint32_t Src_Row_Offset = IMAGE_DEF_DATA_OFFSET + iy * Width;
for(int16_t ix = Start_X; ix < End_X; ix++)
{
Display_Color Pixel_Color = image[Src_Row_Offset + ix];
if(Pixel_Color != _Transparent_Color) {
(*_Current_Buffer)->Dim_2[Screen_Y][x + ix] = Pixel_Color;
}
}
}
}
else
{
// Alpha blending required
for(int16_t iy = Start_Y; iy < End_Y; iy++)
{
int16_t Screen_Y = y + iy;
uint32_t Src_Row_Offset = IMAGE_DEF_DATA_OFFSET + iy * Width;
for(int16_t ix = Start_X; ix < End_X; ix++)
{
Display_Color Src_Pixel = image[Src_Row_Offset + ix];
if(Src_Pixel != _Transparent_Color) {
int16_t Screen_X = x + ix;
Display_Color Dst_Pixel = (*_Current_Buffer)->Dim_2[Screen_Y][Screen_X];
Display_Color Blended_Pixel = Display_Color_Blend_RGB565(Src_Pixel, Dst_Pixel, alpha);
(*_Current_Buffer)->Dim_2[Screen_Y][Screen_X] = Blended_Pixel;
}
}
}
}
}
void Display_Image_Draw_Color_Scaled(int16_t x, int16_t y, Image_Color* image, float scale)
{
Display_Image_Draw_Color_Scaled_Alpha(x, y, image, scale, 255);
}
void Display_Image_Draw_Color_Scaled_Alpha(int16_t x, int16_t y, Image_Color* image, float scale, uint8_t alpha)
{
if(image == NULL || scale <= 0.0f) {
return;
}
// Early exit for fully transparent
if(alpha == 0) {
return;
}
int16_t Original_Width = image[IMAGE_DEF_WIDTH];
int16_t Original_Height = image[IMAGE_DEF_HEIGHT];
// Calculate scaled dimensions
int16_t Scaled_Width = (int16_t)(Original_Width * scale);
int16_t Scaled_Height = (int16_t)(Original_Height * scale);
// Early exit for completely off-screen images
if(x >= DISPLAY_WIDTH || y >= DISPLAY_HEIGHT || x + Scaled_Width <= 0 || y + Scaled_Height <= 0) {
return;
}
// Calculate clipping bounds
int16_t Start_X = (x < 0) ? -x : 0;
int16_t Start_Y = (y < 0) ? -y : 0;
int16_t End_X = (x + Scaled_Width > DISPLAY_WIDTH) ? DISPLAY_WIDTH - x : Scaled_Width;
int16_t End_Y = (y + Scaled_Height > DISPLAY_HEIGHT) ? DISPLAY_HEIGHT - y : Scaled_Height;
// Pre-calculate fixed-point inverse scale factors
uint32_t Inv_Scale_X = (uint32_t)((65536.0f) / scale); // 16.16 fixed point
uint32_t Inv_Scale_Y = (uint32_t)((65536.0f) / scale);
// Optimized scaling with alpha blending
if(alpha == 255) {
// Fully opaque - direct buffer access for maximum performance
for(int16_t iy = Start_Y; iy < End_Y; iy++)
{
// Calculate source Y coordinate using fixed-point math
int16_t Orig_Y = (iy * Inv_Scale_Y) >> 16;
if(Orig_Y >= Original_Height) {
break;
}
// Pre-calculate source row offset
uint32_t Src_Row_Offset = IMAGE_DEF_DATA_OFFSET + Orig_Y * Original_Width;
int16_t Screen_Y = y + iy;
for(int16_t ix = Start_X; ix < End_X; ix++)
{
// Calculate source X coordinate using fixed-point math
int16_t Orig_X = (ix * Inv_Scale_X) >> 16;
if(Orig_X >= Original_Width) {
break;
}
Display_Color Pixel_Color = image[Src_Row_Offset + Orig_X];
if(Pixel_Color != _Transparent_Color) {
// Direct buffer write
(*_Current_Buffer)->Dim_2[Screen_Y][x + ix] = Pixel_Color;
}
}
}
}
else {
// Alpha blending required
for(int16_t iy = Start_Y; iy < End_Y; iy++)
{
// Calculate source Y coordinate using fixed-point math
int16_t Orig_Y = (iy * Inv_Scale_Y) >> 16;
if(Orig_Y >= Original_Height) {
break;
}
// Pre-calculate source row offset
uint32_t Src_Row_Offset = IMAGE_DEF_DATA_OFFSET + Orig_Y * Original_Width;
int16_t Screen_Y = y + iy;
for(int16_t ix = Start_X; ix < End_X; ix++)
{
// Calculate source X coordinate using fixed-point math
int16_t Orig_X = (ix * Inv_Scale_X) >> 16;
if(Orig_X >= Original_Width) {
break;
}
Display_Color Src_Pixel = image[Src_Row_Offset + Orig_X];
if(Src_Pixel != _Transparent_Color) {
int16_t Screen_X = x + ix;
Display_Color Dst_Pixel = (*_Current_Buffer)->Dim_2[Screen_Y][Screen_X];
Display_Color Blended_Pixel = Display_Color_Blend_RGB565(Src_Pixel, Dst_Pixel, alpha);
(*_Current_Buffer)->Dim_2[Screen_Y][Screen_X] = Blended_Pixel;
}
}
}
}
}
void Display_Image_Draw_Color_Rotated(int16_t x, int16_t y, Image_Color* image, uint16_t angle)
{
Display_Image_Draw_Color_Rotated_Alpha(x, y, image, angle, UINT8_MAX);
}
void Display_Image_Draw_Color_Rotated_Alpha(int16_t x, int16_t y, Image_Color* image, uint16_t angle, uint8_t alpha)
{
if(image == NULL) {
return;
}
// Early exit for fully transparent
if(alpha == 0) {
return;
}
int16_t Height = image[IMAGE_DEF_HEIGHT];
int16_t Width = image[IMAGE_DEF_WIDTH];
// Adjust coordinates so rotation center is at (x, y)
x += (Width >> 1);
y += (Height >> 1);
// Convert angle to radians
float Angle_Rad = (angle % 360) * M_PI / 180.0f;
float Cos_Angle = cosf(Angle_Rad);
float Sin_Angle = sinf(Angle_Rad);
// Calculate image center
float Center_X = Width / 2.0f;
float Center_Y = Height / 2.0f;
// Calculate bounding box of rotated image
float Corners_X[4] = {0, Width-1, Width-1, 0};
float Corners_Y[4] = {0, 0, Height-1, Height-1};
int16_t Min_X = 0, Max_X = 0, Min_Y = 0, Max_Y = 0;
for(int i = 0; i < 4; i++) {
float rx = (Corners_X[i] - Center_X) * Cos_Angle - (Corners_Y[i] - Center_Y) * Sin_Angle;
float ry = (Corners_X[i] - Center_X) * Sin_Angle + (Corners_Y[i] - Center_Y) * Cos_Angle;
if(i == 0) {
Min_X = Max_X = (int16_t)roundf(rx);
Min_Y = Max_Y = (int16_t)roundf(ry);
} else {
if(rx < Min_X) Min_X = (int16_t)roundf(rx);
if(rx > Max_X) Max_X = (int16_t)roundf(rx);
if(ry < Min_Y) Min_Y = (int16_t)roundf(ry);
if(ry > Max_Y) Max_Y = (int16_t)roundf(ry);
}
}
// Optimized rendering based on alpha value
if(alpha == 255)
{
// Fully opaque - use direct pixel writes for maximum performance
for(int16_t dy = Min_Y; dy <= Max_Y; dy++)
{
for(int16_t dx = Min_X; dx <= Max_X; dx++)
{
// Reverse rotation to find source pixel
float Src_X = dx * Cos_Angle + dy * Sin_Angle + Center_X;
float Src_Y = -dx * Sin_Angle + dy * Cos_Angle + Center_Y;
// Check if source coordinates are within image bounds
int16_t ix = (int16_t)roundf(Src_X);
int16_t iy = (int16_t)roundf(Src_Y);
if(ix >= 0 && ix < Width && iy >= 0 && iy < Height)
{
Display_Color Pixel = image[IMAGE_DEF_DATA_OFFSET + iy * Width + ix];
if(Pixel != _Transparent_Color) {
// Calculate screen coordinates
int16_t Screen_X = x + dx;
int16_t Screen_Y = y + dy;
// Bounds check for screen buffer
if(Screen_X >= 0 && Screen_X < DISPLAY_WIDTH && Screen_Y >= 0 && Screen_Y < DISPLAY_HEIGHT) {
(*_Current_Buffer)->Dim_2[Screen_Y][Screen_X] = Pixel;
}
}
}
}
}
}
else
{
// Alpha blending required
for(int16_t dy = Min_Y; dy <= Max_Y; dy++)
{
for(int16_t dx = Min_X; dx <= Max_X; dx++)
{
// Reverse rotation to find source pixel
float Src_X = dx * Cos_Angle + dy * Sin_Angle + Center_X;
float Src_Y = -dx * Sin_Angle + dy * Cos_Angle + Center_Y;
// Check if source coordinates are within image bounds
int16_t ix = (int16_t)roundf(Src_X);
int16_t iy = (int16_t)roundf(Src_Y);
if(ix >= 0 && ix < Width && iy >= 0 && iy < Height) {
Display_Color Src_Pixel = image[IMAGE_DEF_DATA_OFFSET + iy * Width + ix];
if(Src_Pixel != _Transparent_Color) {
// Calculate screen coordinates
int16_t Screen_X = x + dx;
int16_t Screen_Y = y + dy;
// Bounds check for screen buffer
if(Screen_X >= 0 && Screen_X < DISPLAY_WIDTH && Screen_Y >= 0 && Screen_Y < DISPLAY_HEIGHT) {
Display_Color Dst_Pixel = (*_Current_Buffer)->Dim_2[Screen_Y][Screen_X];
Display_Color Blended_Pixel = Display_Color_Blend_RGB565(Src_Pixel, Dst_Pixel, alpha);
(*_Current_Buffer)->Dim_2[Screen_Y][Screen_X] = Blended_Pixel;
}
}
}
}
}
}
}
void Display_Image_Draw_Alpha(int16_t x, int16_t y, Image_Alpha* image, Display_Color color)
{
if(image == NULL) { return; }
int16_t Height = image[IMAGE_DEF_HEIGHT];
int16_t Width = image[IMAGE_DEF_WIDTH];
for(int16_t iy = 0; iy < Height ; iy++)
{
for (int16_t ix = 0; ix < Width; ix++)
{
if(image[IMAGE_DEF_DATA_OFFSET + iy * Width + ix] == UINT8_MAX) {
Display_Shapes_Draw_Pixel_Safe(x + ix, y + iy, color);
}
else if(image[IMAGE_DEF_DATA_OFFSET + iy * Width + ix] > 0) {
// This is only temporary here...
// Color needs to be scaled according to alpha value from image array
// Skipped for now, as only threshold images will be applied
Display_Shapes_Draw_Pixel_Safe(x + ix, y + iy, color);
}
}
}
}
uint16_t Display_Image_Get_Width(Image_Color* image)
{
if(image == NULL) { return 0; }
return image[IMAGE_DEF_WIDTH];
}
uint16_t Display_Image_Get_Height(Image_Color* image)
{
if(image == NULL) { return 0; }
return image[IMAGE_DEF_HEIGHT];
}
uint16_t Display_Image_Get_Scaled_Width(Image_Color* image, float scale)
{
if(image == NULL || scale <= 0.0f) { return 0; }
uint16_t Original_Width = image[IMAGE_DEF_WIDTH];
return (uint16_t)(Original_Width * scale);
}
uint16_t Display_Image_Get_Scaled_Height(Image_Color* image, float scale)
{
if(image == NULL || scale <= 0.0f) { return 0; }
uint16_t Original_Height = image[IMAGE_DEF_HEIGHT];
return (uint16_t)(Original_Height * scale);
}
/*******************************************************************
Internal Functions
*******************************************************************/