/* * Display_Shapes.c * * Created: Mon Jul 19 2021 17:57:03 * Author Chris */ #include "Display_Shapes.h" #include "Display_Color.h" #include "Display_Objects.h" #include "Round_Corners_Lookup_Table.h" #include "hardware/dma.h" #include #include #include #include #include #include // ============================================================================================ // Defines #define CORNER_TOP_LEFT 0x01 #define CORNER_TOP_RIGHT 0x02 #define CORNER_BOTTOM_RIGHT 0x04 // Fixed: was incorrectly 0x08 #define CORNER_BOTTOM_LEFT 0x08 // Fixed: was incorrectly 0x04 #define DEG2RAD (float)(M_PI / 180) // ============================================================================================ // Variables static Display_Image_Buffer** _Current_Buffer; static int _DMA_Channel_Fill_Screen; static dma_channel_config _DMA_Config_Fill_Screen; static int _DMA_Channel_Drawing; static dma_channel_config _DMA_Config_Drawing; // ============================================================================================ // Function Declarations void Display_Shapes_Draw_Rounded_Rect_Frame_1 (int16_t x, int16_t y, uint16_t width, uint16_t height, uint16_t radius, Display_Color color); void Display_Shapes_Draw_Circle_Frame_1 (int16_t center_x, int16_t center_y, uint16_t radius, Display_Color color); void Display_Shapes_Draw_Circle_Helper(int16_t x0, int16_t y0, uint16_t radius, uint16_t thickness, uint8_t cornername, Display_Color color); void Display_Shapes_Draw_Circle_Helper_Improved(int16_t x0, int16_t y0, uint16_t radius, uint16_t thickness, uint8_t cornername, Display_Color color); void Display_Shapes_Draw_Circle_Helper_Single_Pixel(int16_t x0, int16_t y0, uint16_t radius, uint8_t cornername, Display_Color color); void Display_Shapes_Draw_Circle_Helper_Filled(int16_t x0, int16_t y0, uint16_t radius, uint8_t corners, int16_t delta, Display_Color color); /******************************************************************* Functions *******************************************************************/ void Display_Shapes_Init(Display_Image_Buffer** current_buffer) { _Current_Buffer = current_buffer; _DMA_Channel_Fill_Screen = dma_claim_unused_channel(true); _DMA_Config_Fill_Screen = dma_channel_get_default_config(_DMA_Channel_Fill_Screen); channel_config_set_transfer_data_size(&_DMA_Config_Fill_Screen, DMA_SIZE_32); channel_config_set_read_increment(&_DMA_Config_Fill_Screen, false); channel_config_set_write_increment(&_DMA_Config_Fill_Screen, true); _DMA_Channel_Drawing = dma_claim_unused_channel(true); _DMA_Config_Drawing = dma_channel_get_default_config(_DMA_Channel_Drawing); channel_config_set_transfer_data_size(&_DMA_Config_Drawing, DMA_SIZE_32); channel_config_set_read_increment(&_DMA_Config_Drawing, false); channel_config_set_write_increment(&_DMA_Config_Drawing, true); } void Display_Shapes_Fill_Screen(Display_Color color) { static uint32_t Clear_Value; Clear_Value = (color << 16) | color; // Duplicate 16-bit value dma_channel_configure(_DMA_Channel_Fill_Screen, &_DMA_Config_Fill_Screen, (*_Current_Buffer)->Dim_1, &Clear_Value, DISPLAY_IMAGE_BUFFER_PIXEL_SIZE/2, false); // Half the transfers dma_channel_start(_DMA_Channel_Fill_Screen); dma_channel_wait_for_finish_blocking(_DMA_Channel_Fill_Screen); } void Display_Shapes_Draw_Pixel_Safe(int16_t x, int16_t y, Display_Color color) { if(x >= 0 && x < DISPLAY_WIDTH && y >= 0 && y < DISPLAY_HEIGHT) { (*_Current_Buffer)->Dim_2[y][x] = color; } } void Display_Shapes_Draw_HLine(int16_t x, int16_t y, uint16_t width, uint16_t thickness, Display_Color color) { Display_Shapes_Draw_Rect_Filled(x, y, width, thickness, color); } void Display_Shapes_Draw_VLine(int16_t x, int16_t y, uint16_t height, uint16_t thickness, Display_Color color) { Display_Shapes_Draw_Rect_Filled(x, y, thickness, height, color); } void Display_Shapes_Draw_Line_XY(int16_t x0, int16_t y0, int16_t x1, int16_t y1, uint16_t thickness, Display_Color color) { int16_t Temp; if(y0 == y1) { if(x1 > x0) { Display_Shapes_Draw_HLine(x0, y0, x1 - x0 + 1, thickness, color); } else if (x1 < x0) { Display_Shapes_Draw_HLine(x1, y0, x0 - x1 + 1, thickness, color); } else { Display_Shapes_Draw_Circle_Filled(x0, y0, thickness, color); } return; } else if(x0 == x1) { if(y1 > y0) { Display_Shapes_Draw_VLine(x0, y0, y1 - y0 + 1, thickness, color); } else { Display_Shapes_Draw_VLine(x0, y1, y0 - y1 + 1, thickness, color); } return; } bool Steep = abs(y1 - y0) > abs(x1 - x0); if(Steep == true) { Temp = x0; x0 = y0; y0 = Temp; Temp = x1; x1 = y1; y1 = Temp; } if(x0 > x1) { Temp = x0; x0 = x1; x1 = Temp; Temp = y0; y0 = y1; y1 = Temp; } int16_t dx, dy; dx = x1 - x0; dy = abs(y1 - y0); int16_t err = dx / 2; int16_t ystep; if (y0 < y1) { ystep = 1; } else { ystep = -1; } int16_t xbegin = x0; if(Steep == true) { for (; x0<=x1; x0++) { err -= dy; if (err < 0) { int16_t len = x0 - xbegin; if (len>1) { Display_Shapes_Draw_VLine(y0, xbegin, len + 1, thickness, color); } else { if(thickness == 1) Display_Shapes_Draw_Pixel_Safe(y0, x0, color); else Display_Shapes_Draw_Circle_Filled(y0, x0, thickness >> 1, color); } xbegin = x0 + 1; y0 += ystep; err += dx; } } if (x0 > xbegin + 1) { Display_Shapes_Draw_VLine(y0, xbegin, x0 - xbegin, thickness, color); } } else { for (; x0<=x1; x0++) { err -= dy; if (err < 0) { int16_t len = x0 - xbegin; if (len>1) { Display_Shapes_Draw_HLine(xbegin, y0, len + 1, thickness, color); } else { if(thickness == 1) Display_Shapes_Draw_Pixel_Safe(x0, y0, color); else Display_Shapes_Draw_Circle_Filled(x0, y0, thickness >> 1, color); } xbegin = x0 + 1; y0 += ystep; err += dx; } } if (x0 > xbegin + 1) { Display_Shapes_Draw_HLine(xbegin, y0, x0 - xbegin, thickness, color); } } } void Display_Shapes_Draw_Line_Rad(int16_t x, int16_t y, float angle, uint16_t radius_start, uint16_t radius_end, uint16_t thickness, Display_Color color) { int16_t X0 = x + cos(angle * DEG2RAD) * radius_start; int16_t Y0 = y + sin(angle * DEG2RAD) * radius_start; int16_t X1 = x + cos(angle * DEG2RAD) * radius_end; int16_t Y1 = y + sin(angle * DEG2RAD) * radius_end; Display_Shapes_Draw_Line_XY(X0, Y0, X1, Y1, thickness, color); } void Display_Shapes_Draw_Rect_Frame(int16_t x, int16_t y, uint16_t width, uint16_t height, uint16_t thickness, Display_Color color) { // The Rectangle Frame stays within the given height and width. Meaning the Border is drawn inside the width an height. // For Example: // If you specify a width = 50 and height = 30 with a thickness = 3, the // inside area will be (50 - 2*3) x (30 - 2*3) = 44x24 pixels int16_t X_Left = x; int16_t X_Right = x + width - thickness; int16_t Y_Top = y; int16_t Y_Bottom = y + height - thickness; Display_Shapes_Draw_HLine(X_Left, Y_Top , width , thickness, color); Display_Shapes_Draw_HLine(X_Left, Y_Bottom , width , thickness, color); Display_Shapes_Draw_VLine(X_Left , Y_Top, height, thickness, color); Display_Shapes_Draw_VLine(X_Right , Y_Top, height, thickness, color); } void Display_Shapes_Draw_Rect_Filled(int16_t x, int16_t y, uint16_t width, uint16_t height, Display_Color color) { // Early bounds checking if (x >= DISPLAY_WIDTH || y >= DISPLAY_HEIGHT || x + width <= 0 || y + height <= 0) { return; } // Clip to screen bounds int16_t Start_X = (x < 0) ? 0 : x; int16_t Start_Y = (y < 0) ? 0 : y; int16_t End_X = (x + width > DISPLAY_WIDTH) ? DISPLAY_WIDTH : x + width; int16_t End_Y = (y + height > DISPLAY_HEIGHT) ? DISPLAY_HEIGHT : y + height; uint16_t Clipped_Width = End_X - Start_X; uint16_t Clipped_Height = End_Y - Start_Y; if (Clipped_Width == 0 || Clipped_Height == 0) { return; } // For narrow rectangles, use optimized nested loop for (int16_t row = Start_Y; row < End_Y; row++) { // Each row is contiguous in memory - cache friendly Display_Color* Row_Ptr = &(*_Current_Buffer)->Dim_2[row][Start_X]; for (int16_t col = 0; col < Clipped_Width; col++) { Row_Ptr[col] = color; } } } void Display_Shapes_Draw_Circle_Frame(int16_t center_x, int16_t center_y, uint16_t radius, uint16_t thickness, Display_Color color) { if (thickness == 0 || radius == 0) { return; } if(thickness == 1) { Display_Shapes_Draw_Circle_Frame_1(center_x, center_y, radius, color); return; } uint16_t Inner_Radius = radius - thickness; Display_Shapes_Draw_HLine(center_x - radius , center_y - 1, thickness, 2, color); Display_Shapes_Draw_HLine(center_x + radius - thickness , center_y - 1, thickness, 2, color); Display_Shapes_Draw_VLine(center_x - 1, center_y - radius , thickness, 2, color); Display_Shapes_Draw_VLine(center_x - 1, center_y + radius - thickness , thickness, 2, color); const uint8_t* Outer_Data = _Corner_Lookup_Tables[radius].Data; uint8_t Outer_Size = _Corner_Lookup_Tables[radius].Size; const uint8_t* Inner_Data = _Corner_Lookup_Tables[Inner_Radius].Data; uint8_t Inner_Size = _Corner_Lookup_Tables[Inner_Radius].Size; // Draw frame using lookup table data for (uint8_t i = 0; i < Outer_Size; i++) { int16_t Outer_X_Offset = Outer_Data[i]; int16_t Y_Offset = i; uint16_t Line_Width = 0; if (i < thickness) { // Full outer circle width for top/bottom thickness rows Line_Width = radius - Outer_X_Offset; } else { // Frame width = outer - inner uint8_t Inner_Index = i - thickness; if (Inner_Index < Inner_Size) { int16_t Inner_X_Offset = Inner_Data[Inner_Index]; Line_Width = (radius - Outer_X_Offset) - (Inner_Radius - Inner_X_Offset); } } if (Line_Width > 0) { int16_t Top_Y = center_y - radius + Y_Offset - 1; int16_t Bottom_Y = center_y + radius - Y_Offset; // Left side of frame int16_t Left_X = center_x - (radius - Outer_X_Offset) - 1; Display_Shapes_Draw_HLine(Left_X, Top_Y, Line_Width, 1, color); if (Y_Offset > 0) { Display_Shapes_Draw_HLine(Left_X, Bottom_Y, Line_Width, 1, color); } // Right side of frame int16_t Right_X = center_x + (radius - Outer_X_Offset) - Line_Width + 1; Display_Shapes_Draw_HLine(Right_X, Top_Y, Line_Width, 1, color); if (Y_Offset > 0) { Display_Shapes_Draw_HLine(Right_X, Bottom_Y, Line_Width, 1, color); } } } } void Display_Shapes_Draw_Circle_Filled(int16_t center_x, int16_t center_y, uint16_t radius, Display_Color color) { if (radius == 0) { Display_Shapes_Draw_Pixel_Safe(center_x, center_y, color); return; } Display_Shapes_Draw_HLine(center_x - radius, center_y - 1, 2*radius, 2, color); const uint8_t* Data = _Corner_Lookup_Tables[radius].Data; uint8_t Size = _Corner_Lookup_Tables[radius].Size; // Draw horizontal lines using lookup table data for (uint8_t i = 0; i < Size; i++) { int16_t X_Offset = Data[i]; int16_t Y_Offset = i; // Calculate line width for this Y position uint16_t Line_Width = 2 * (radius - X_Offset) + 2; // Draw upper and lower horizontal lines int16_t Top_Y = center_y - radius + Y_Offset - 0; int16_t Bottom_Y = center_y + radius - Y_Offset; int16_t Start_X = center_x - (radius - X_Offset) - 1; Display_Shapes_Draw_HLine(Start_X, Top_Y, Line_Width, 1, color); if (Y_Offset > 0) { // Avoid drawing center line twice Display_Shapes_Draw_HLine(Start_X, Bottom_Y, Line_Width, 1, color); } } } void Display_Shapes_Draw_Round_Rect_Frame(int16_t x, int16_t y, uint16_t width, uint16_t height, uint16_t radius, uint16_t thickness, Display_Color color) { // Validate input parameters if (width < 2 || height < 2 || thickness == 0) { return; } if(thickness == 1) { Display_Shapes_Draw_Rounded_Rect_Frame_1(x, y, width, height, radius, color); return; } // Clamp radius to maximum possible value uint16_t Max_Radius = ((width < height) ? width : height) / 2; if (radius > Max_Radius) { radius = Max_Radius; } // For no radius, draw a regular Rectangle Frame if (radius == 0) { Display_Shapes_Draw_Rect_Frame(x, y, width, height, thickness, color); return; } // Calculate inner rectangle dimensions int16_t Inner_Width = width - 2 * thickness; int16_t Inner_Height = height - 2 * thickness; // Calculate inner radius (ensuring it's valid) uint16_t Inner_Radius = (radius > thickness) ? radius - thickness : 0; uint16_t Outer_Radius = radius; const uint8_t* Outer_Data = _Corner_Lookup_Tables[Outer_Radius].Data; uint8_t Outer_Size = _Corner_Lookup_Tables[Outer_Radius].Size; const uint8_t* Inner_Data = _Corner_Lookup_Tables[Inner_Radius].Data; uint8_t Inner_Size = _Corner_Lookup_Tables[Inner_Radius].Size; // Draw straight edges Display_Shapes_Draw_HLine(x + radius , y , width - 2 * radius, thickness, color); Display_Shapes_Draw_HLine(x + radius , y + height - thickness, width - 2 * radius, thickness, color); Display_Shapes_Draw_VLine(x , y + radius , height - 2 * radius, thickness, color); Display_Shapes_Draw_VLine(x + width - thickness , y + radius , height - 2 * radius, thickness, color); // Draw corner regions for (uint8_t i = 0; i < Outer_Size; i++) { int16_t Outer_X_Offset = Outer_Data[i]; // Calculate corner positions for outer edge int16_t TL_Outer_X = x + Outer_X_Offset; int16_t TR_Outer_X = x + width - 1 - Outer_X_Offset; int16_t BL_Outer_X = x + Outer_X_Offset; int16_t BR_Outer_X = x + width - 1 - Outer_X_Offset; int16_t Top_Y = y + i; int16_t Bottom_Y = y + height - 1 - i; uint16_t Line_Width = 0; if(i < thickness) { Line_Width = Outer_Radius - Outer_X_Offset; } else { uint8_t Inner_Radius_Index = i - thickness; int16_t Inner_X_Offset = Inner_Data[Inner_Radius_Index]; Line_Width = (Outer_Radius - Outer_X_Offset) - (Inner_Radius - Inner_X_Offset); } Display_Shapes_Draw_HLine(TL_Outer_X, Top_Y , Line_Width, 1, color); Display_Shapes_Draw_HLine(BL_Outer_X, Bottom_Y , Line_Width, 1, color); Display_Shapes_Draw_HLine(TR_Outer_X - Line_Width + 1, Top_Y , Line_Width, 1, color); Display_Shapes_Draw_HLine(BR_Outer_X - Line_Width + 1, Bottom_Y , Line_Width, 1, color); } } void Display_Shapes_Draw_Round_Rect_Filled(int16_t x, int16_t y, uint16_t width, uint16_t height, uint16_t radius, Display_Color color) { uint16_t Max_Radius = ((width < height) ? width : height) / 2; if (radius > Max_Radius) { radius = Max_Radius; } // Draw the main body rectangle (excluding corner regions) if (height > 2 * radius) { Display_Shapes_Draw_Rect_Filled(x, y + radius, width, height - 2 * radius, color); } const uint8_t* Data = _Corner_Lookup_Tables[radius].Data; uint8_t Size = _Corner_Lookup_Tables[radius].Size; // Draw corner regions using lookup table for (uint8_t i = 0; i < Size; i++) { int16_t X_Offset = Data[i]; // Calculate Y positions for top and bottom int16_t Top_Y = y + i; int16_t Bottom_Y = y + height - 1 - i; // Calculate line width for this Y position uint16_t Line_Width = width - 2 * X_Offset; // Draw Top Corners Display_Shapes_Draw_HLine(x + X_Offset, Top_Y, Line_Width, 1, color); // Draw Bottom Corners Display_Shapes_Draw_HLine(x + X_Offset, Bottom_Y, Line_Width, 1, color); } } void Display_Shapes_Draw_Arc_Frame(int16_t center_x, int16_t center_y, int16_t radius, uint16_t thickness, float angle_start, float angle_end, uint16_t steps, Display_Color color) { if (thickness == 0 || radius == 0) { return; } // Normalize angles to 0-360 range while (angle_start < 0.0f) angle_start += 360.0f; while (angle_start >= 360.0f) angle_start -= 360.0f; while (angle_end < 0.0f) angle_end += 360.0f; while (angle_end >= 360.0f) angle_end -= 360.0f; // Handle case where arc crosses 0° boundary while(angle_end < angle_start) { angle_end += 360; } if(steps == ARC_FRAME_AUTO_STEPS) { float Arc_Length = angle_end - angle_start; steps = (uint16_t)(Arc_Length * 2.0f); // 2 Steps per Degree // Minimum steps for small arcs if (steps < 8) { steps = 8; } } float Angle_Step = (float)(angle_end - angle_start) / (float)steps; for (float i = angle_start; i <= angle_end; i = i + Angle_Step) { if(thickness == 1) { Display_Shapes_Draw_Pixel_Safe(center_x + cos(i*DEG2RAD) * radius, center_y + sin(i*DEG2RAD) * radius, color); } else { Display_Shapes_Draw_Circle_Filled(center_x + cos(i*DEG2RAD) * radius, center_y + sin(i*DEG2RAD) * radius, thickness >> 1, color); } } } void Display_Shapes_Draw_Glow_Circle(int16_t center_x, int16_t center_y, uint16_t radius, Display_Color core_color, Display_Color glow_color) { // Draw a circle with a subtle glow effect // Core circle with gradually fading outer ring if (radius == 0) { return; } // Define glow parameters uint16_t Glow_Radius = radius + 4; // Glow extends 4 pixels beyond core uint16_t Core_Radius = radius; // Draw glow layers from outside to inside for proper alpha blending effect for (uint16_t Layer = Glow_Radius; Layer > Core_Radius; Layer--) { // Calculate glow intensity based on distance from core float Distance_From_Core = (float)(Layer - Core_Radius); float Max_Glow_Distance = (float)(Glow_Radius - Core_Radius); float Glow_Intensity = 1.0f - (Distance_From_Core / Max_Glow_Distance); // Apply easing curve for more natural glow falloff Glow_Intensity = Glow_Intensity * Glow_Intensity; // Quadratic falloff // Blend glow color with background (assuming dark background) Display_Color Layer_Color = Display_Color_Interpolate_Float( Display_Objects_Background_Color_Get(), glow_color, Glow_Intensity * 0.3f // Max 30% glow intensity ); // Draw this glow layer as a thin circle frame Display_Shapes_Draw_Circle_Frame(center_x, center_y, Layer, 1, Layer_Color); } // Draw the solid core circle Display_Shapes_Draw_Circle_Filled(center_x, center_y, Core_Radius, core_color); // Add a subtle highlight for 3D effect (optional) if (Core_Radius > 3) { // Small highlight offset towards top-left int16_t Highlight_X = center_x - (Core_Radius / 3); int16_t Highlight_Y = center_y - (Core_Radius / 3); uint16_t Highlight_Radius = Core_Radius / 3; Display_Color Highlight_Color = Display_Color_Interpolate_Float( core_color, DISPLAY_COLOR_WHITE, 0.4f // 40% white blend for highlight ); Display_Shapes_Draw_Circle_Filled(Highlight_X, Highlight_Y, Highlight_Radius, Highlight_Color); } } Coordinates Display_Shapes_Polar_To_XY(int16_t origin_x, int16_t origin_y, float angle, uint16_t radius) { Coordinates Return_Value = { .X = origin_x, .Y = origin_y }; Return_Value.X += cos(angle * DEG2RAD) * radius; Return_Value.Y += sin(angle * DEG2RAD) * radius; return Return_Value; } /******************************************************************* Internal Functions *******************************************************************/ void Display_Shapes_Draw_Rounded_Rect_Frame_1(int16_t x, int16_t y, uint16_t width, uint16_t height, uint16_t radius, Display_Color color) { // Validate input parameters if (width < 2 || height < 2) { return; } // Clamp radius to maximum possible value uint16_t Max_Radius = ((width < height) ? width : height) / 2; if (radius > Max_Radius) { radius = Max_Radius; } // For no radius, draw a regular Rectangle Frame if (radius == 0) { Display_Shapes_Draw_Rect_Frame(x, y, width, height, 1, color); return; } // Draw straight edges Display_Shapes_Draw_HLine(x + radius , y , width - 2 * radius, 1, color); Display_Shapes_Draw_HLine(x + radius , y + height - 1, width - 2 * radius, 1, color); Display_Shapes_Draw_VLine(x , y + radius , height - 2 * radius, 1, color); Display_Shapes_Draw_VLine(x + width - 1 , y + radius , height - 2 * radius, 1, color); const uint8_t* Data = _Corner_Lookup_Tables[radius].Data; uint8_t Size = _Corner_Lookup_Tables[radius].Size; int16_t Last_X = Data[0]; // Draw corners with gap filling for (uint8_t i = 0; i 1) { Display_Shapes_Draw_HLine(TL_X, TL_Y, X_Step, 1, color); Display_Shapes_Draw_HLine(BL_X, BL_Y, X_Step, 1, color); Display_Shapes_Draw_HLine(TR_X - X_Step + 1, TR_Y, X_Step, 1, color); Display_Shapes_Draw_HLine(BR_X - X_Step + 1, BR_Y, X_Step, 1, color); } else { Display_Shapes_Draw_Pixel_Safe(TL_X, TL_Y, color); Display_Shapes_Draw_Pixel_Safe(BL_X, BL_Y, color); Display_Shapes_Draw_Pixel_Safe(TR_X, TR_Y, color); Display_Shapes_Draw_Pixel_Safe(BR_X, BR_Y, color); } Last_X = Data[i]; } } void Display_Shapes_Draw_Circle_Frame_1(int16_t center_x, int16_t center_y, uint16_t radius, Display_Color color) { if(radius == 0) { return; } const uint8_t* Data = _Corner_Lookup_Tables[radius].Data; uint8_t Size = _Corner_Lookup_Tables[radius].Size; int16_t Last_X = Data[0]; // Draw corners with gap filling for (uint8_t i = 0; i 1) { Display_Shapes_Draw_HLine(TL_X, TL_Y, X_Step, 1, color); Display_Shapes_Draw_HLine(BL_X, BL_Y, X_Step, 1, color); Display_Shapes_Draw_HLine(TR_X - X_Step + 1, TR_Y, X_Step, 1, color); Display_Shapes_Draw_HLine(BR_X - X_Step + 1, BR_Y, X_Step, 1, color); } else { Display_Shapes_Draw_Pixel_Safe(TL_X, TL_Y, color); Display_Shapes_Draw_Pixel_Safe(BL_X, BL_Y, color); Display_Shapes_Draw_Pixel_Safe(TR_X, TR_Y, color); Display_Shapes_Draw_Pixel_Safe(BR_X, BR_Y, color); } Last_X = Data[i]; } } /**************************************************************************/ /*! @brief Quarter-circle drawer, used to do circles and roundrects @param x0 Center-point x coordinate @param y0 Center-point y coordinate @param r Radius of circle @param cornername Mask bit #1 or bit #2 to indicate which quarters of the circle we're doing @param color 16-bit 5-6-5 Color to draw with */ /**************************************************************************/ void Display_Shapes_Draw_Circle_Helper(int16_t x0, int16_t y0, uint16_t radius, uint16_t thickness, uint8_t cornername, Display_Color color) { int16_t f = 1 - radius; int16_t ddF_x = 1; int16_t ddF_y = -2 * radius; int16_t x = 0; int16_t y = radius; while (x < y) { if (f >= 0) { y--; ddF_y += 2; f += ddF_y; } x++; ddF_x += 2; f += ddF_x; if (cornername & CORNER_BOTTOM_RIGHT) { Display_Shapes_Draw_Circle_Filled(x0 + x, y0 + y, thickness >> 1, color); Display_Shapes_Draw_Circle_Filled(x0 + y, y0 + x, thickness >> 1, color); } if (cornername & CORNER_TOP_RIGHT) { Display_Shapes_Draw_Circle_Filled(x0 + x, y0 - y, thickness >> 1, color); Display_Shapes_Draw_Circle_Filled(x0 + y, y0 - x, thickness >> 1, color); } if (cornername & CORNER_BOTTOM_LEFT) { Display_Shapes_Draw_Circle_Filled(x0 - x, y0 + y, thickness >> 1, color); Display_Shapes_Draw_Circle_Filled(x0 - y, y0 + x, thickness >> 1, color); } if (cornername & CORNER_TOP_LEFT) { Display_Shapes_Draw_Circle_Filled(x0 - x, y0 - y, thickness >> 1, color); Display_Shapes_Draw_Circle_Filled(x0 - y, y0 - x, thickness >> 1, color); } } } void Display_Shapes_Draw_Circle_Helper_Improved(int16_t x0, int16_t y0, uint16_t radius, uint16_t thickness, uint8_t cornername, Display_Color color) { if (radius == 0) return; // For thickness of 1, use single pixel drawing if (thickness == 1) { Display_Shapes_Draw_Circle_Helper_Single_Pixel(x0, y0, radius, cornername, color); return; } // For thicker lines, draw multiple concentric quarter-circles // This ensures consistent thickness with the straight edges for (uint16_t t = 0; t < thickness; t++) { uint16_t current_radius = radius - t; if (current_radius == 0) break; Display_Shapes_Draw_Circle_Helper_Single_Pixel(x0, y0, current_radius, cornername, color); } } void Display_Shapes_Draw_Circle_Helper_Single_Pixel(int16_t x0, int16_t y0, uint16_t radius, uint8_t cornername, Display_Color color) { if (radius == 0) return; // Use Bresenham's circle algorithm for single pixel drawing int16_t f = 1 - radius; int16_t ddF_x = 1; int16_t ddF_y = -2 * radius; int16_t x = 0; int16_t y = radius; while (x < y) { if (f >= 0) { y--; ddF_y += 2; f += ddF_y; } x++; ddF_x += 2; f += ddF_x; // Draw quarter-circle segments based on corner mask - single pixels only if (cornername & CORNER_TOP_LEFT) { Display_Shapes_Draw_Pixel_Safe(x0 - x, y0 - y, color); Display_Shapes_Draw_Pixel_Safe(x0 - y, y0 - x, color); } if (cornername & CORNER_TOP_RIGHT) { Display_Shapes_Draw_Pixel_Safe(0 + x, y0 - y, color); Display_Shapes_Draw_Pixel_Safe(x0 + y, y0 - x, color); } if (cornername & CORNER_BOTTOM_RIGHT) { Display_Shapes_Draw_Pixel_Safe(x0 + x, y0 + y, color); Display_Shapes_Draw_Pixel_Safe(x0 + y, y0 + x, color); } if (cornername & CORNER_BOTTOM_LEFT) { Display_Shapes_Draw_Pixel_Safe(x0 - x, y0 + y, color); Display_Shapes_Draw_Pixel_Safe(x0 - y, y0 + x, color); } } } /**************************************************************************/ /*! @brief Quarter-circle drawer with fill, used for circles and roundrects @param x0 Center-point x coordinate @param y0 Center-point y coordinate @param r Radius of circle @param corners Mask bits indicating which quarters we're doing @param delta Offset from center-point, used for round-rects @param color 16-bit 5-6-5 Color to fill with */ /**************************************************************************/ void Display_Shapes_Draw_Circle_Helper_Filled(int16_t x0, int16_t y0, uint16_t radius, uint8_t corners, int16_t delta, Display_Color color) { int16_t f = 1 - radius; int16_t ddF_x = 1; int16_t ddF_y = -2 * radius; int16_t x = 0; int16_t y = radius; int16_t px = x; int16_t py = y; delta++; // Avoid some +1's in the loop while (x < y) { if (f >= 0) { y--; ddF_y += 2; f += ddF_y; } x++; ddF_x += 2; f += ddF_x; // These checks avoid double-drawing certain lines, important // for the SSD1306 library which has an INVERT drawing mode. if (x < (y + 1)) { if (corners & CORNER_TOP_LEFT) { Display_Shapes_Draw_VLine(x0 + x, y0 - y, 2 * y + delta, 1, color); // writeFastVLine(x0 + x, y0 - y, 2 * y + delta, color); } if (corners & CORNER_TOP_RIGHT) { Display_Shapes_Draw_VLine(x0 - x, y0 - y, 2 * y + delta, 1, color); // writeFastVLine(x0 - x, y0 - y, 2 * y + delta, color); } } if (y != py) { if (corners & CORNER_TOP_LEFT) { Display_Shapes_Draw_VLine(x0 + py, y0 - px, 2 * px + delta, 1, color); // writeFastVLine(x0 + py, y0 - px, 2 * px + delta, color); } if (corners & CORNER_TOP_RIGHT) { Display_Shapes_Draw_VLine(x0 - py, y0 - px, 2 * px + delta, 1, color); // writeFastVLine(x0 - py, y0 - px, 2 * px + delta, color); } py = y; } px = x; } }