| // Copyright 2022 The Pigweed Authors |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); you may not |
| // use this file except in compliance with the License. You may obtain a copy of |
| // the License at |
| // |
| // https://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT |
| // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the |
| // License for the specific language governing permissions and limitations under |
| // the License. |
| |
| #include "pw_draw/draw.h" |
| |
| #include <math.h> |
| |
| #include "pw_color/color.h" |
| #include "pw_draw/sprite_sheet.h" |
| #include "pw_framebuffer/framebuffer.h" |
| #include "pw_framebuffer/writer.h" |
| |
| using pw::color::color_rgb565_t; |
| using pw::framebuffer::Framebuffer; |
| using pw::framebuffer::FramebufferWriter; |
| using pw::math::Size; |
| using pw::math::Vector2; |
| |
| namespace pw::draw { |
| |
| namespace { |
| |
| // Erase a rectangle the size of a font glyph to the background color. |
| Size<int> DrawSpace(Vector2<int> pos, |
| color_rgb565_t bg_color, |
| const FontSet& font, |
| Framebuffer& framebuffer) { |
| FramebufferWriter writer(framebuffer); |
| for (int font_row = 0; font_row < font.height; font_row++) { |
| for (int font_column = 0; font_column < font.width; font_column++) { |
| writer.SetPixel(pos.x + font_column, pos.y + font_row, bg_color); |
| } |
| } |
| return Size<int>{font.width, font.height}; |
| } |
| |
| } // namespace |
| |
| void DrawLine( |
| Framebuffer& fb, int x1, int y1, int x2, int y2, color_rgb565_t pen_color) { |
| // Bresenham's Line Algorithm |
| int16_t steep_gradient = abs(y2 - y1) > abs(x2 - x1); |
| // Swap values |
| int16_t temp; |
| if (steep_gradient) { |
| temp = x1; |
| x1 = y1; |
| y1 = temp; |
| temp = x2; |
| x2 = y2; |
| y2 = temp; |
| } |
| if (x1 > x2) { |
| temp = x1; |
| x1 = x2; |
| x2 = temp; |
| temp = y1; |
| y1 = y2; |
| y2 = temp; |
| } |
| |
| int16_t dx = x2 - x1; |
| int16_t dy = abs(y2 - y1); |
| int16_t error_value = dx / 2; |
| int16_t ystep = y1 < y2 ? 1 : -1; |
| |
| FramebufferWriter writer(fb); |
| for (; x1 <= x2; x1++) { |
| if (steep_gradient) { |
| writer.SetPixel(y1, x1, pen_color); |
| } else { |
| writer.SetPixel(x1, y1, pen_color); |
| } |
| error_value -= dy; |
| if (error_value < 0) { |
| y1 += ystep; |
| error_value += dx; |
| } |
| } |
| } |
| |
| // Draw a circle at center_x, center_y with given radius and color. Only a |
| // one-pixel outline is drawn if filled is false. |
| void DrawCircle(Framebuffer& fb, |
| int center_x, |
| int center_y, |
| int radius, |
| color_rgb565_t pen_color, |
| bool filled = false) { |
| int fx = 0, fy = 0; |
| int x = -radius, y = 0; |
| int error_value = 2 - 2 * radius; |
| FramebufferWriter writer(fb); |
| while (x < 0) { |
| if (!filled) { |
| fx = x; |
| fy = y; |
| } |
| // Draw each quarter circle |
| for (int i = x; i <= fx; i++) { |
| // Lower right |
| writer.SetPixel(center_x - i, center_y + y, pen_color); |
| // Upper left |
| writer.SetPixel(center_x + i, center_y - y, pen_color); |
| } |
| for (int i = fy; i <= y; i++) { |
| // Lower left |
| writer.SetPixel(center_x - i, center_y - x, pen_color); |
| // Upper right |
| writer.SetPixel(center_x + i, center_y + x, pen_color); |
| } |
| radius = error_value; |
| if (radius <= y) { |
| y++; |
| error_value += y * 2 + 1; |
| } |
| if (radius > x || error_value > y) { |
| x++; |
| error_value += x * 2 + 1; |
| } |
| } |
| } |
| |
| void DrawHLine( |
| Framebuffer& fb, int x1, int x2, int y, color_rgb565_t pen_color) { |
| FramebufferWriter writer(fb); |
| for (int i = x1; i <= x2; i++) { |
| writer.SetPixel(i, y, pen_color); |
| } |
| } |
| |
| void DrawRect(Framebuffer& fb, |
| int x1, |
| int y1, |
| int x2, |
| int y2, |
| color_rgb565_t pen_color, |
| bool filled = false) { |
| // Draw top and bottom lines. |
| DrawHLine(fb, x1, x2, y1, pen_color); |
| DrawHLine(fb, x1, x2, y2, pen_color); |
| if (filled) { |
| for (int y = y1 + 1; y < y2; y++) { |
| DrawHLine(fb, x1, x2, y, pen_color); |
| } |
| } else { |
| FramebufferWriter writer(fb); |
| for (int y = y1 + 1; y < y2; y++) { |
| writer.SetPixel(x1, y, pen_color); |
| writer.SetPixel(x2, y, pen_color); |
| } |
| } |
| } |
| |
| void DrawRectWH(Framebuffer& fb, |
| int x, |
| int y, |
| int w, |
| int h, |
| color_rgb565_t pen_color, |
| bool filled = false) { |
| DrawRect(fb, x, y, x - 1 + w, y - 1 + h, pen_color, filled); |
| } |
| |
| void Fill(Framebuffer& fb, color_rgb565_t pen_color) { |
| FramebufferWriter writer(fb); |
| writer.Fill(pen_color); |
| } |
| |
| void DrawSprite(Framebuffer& fb, |
| int x, |
| int y, |
| pw::draw::SpriteSheet* sprite_sheet, |
| int integer_scale = 1) { |
| uint16_t color; |
| int start_x, start_y; |
| FramebufferWriter writer(fb); |
| for (int current_x = 0; current_x < sprite_sheet->width; current_x++) { |
| for (int current_y = 0; current_y < sprite_sheet->height; current_y++) { |
| color = sprite_sheet->GetColor( |
| current_x, current_y, sprite_sheet->current_index); |
| if (color != sprite_sheet->transparent_color) { |
| if (integer_scale == 1) { |
| writer.SetPixel(x + current_x, y + current_y, color); |
| } |
| // If integer_scale > 1: draw a rectangle |
| else if (integer_scale > 1) { |
| start_x = x + (integer_scale * current_x); |
| start_y = y + (integer_scale * current_y); |
| DrawRectWH( |
| fb, start_x, start_y, integer_scale, integer_scale, color, true); |
| } |
| } |
| } |
| } |
| } |
| |
| void DrawTestPattern(Framebuffer& fb) { |
| color_rgb565_t color = pw::color::ColorRGBA(0x00, 0xFF, 0xFF).ToRgb565(); |
| // Create a Test Pattern |
| FramebufferWriter writer(fb); |
| for (int x = 0; x < fb.size().width; x++) { |
| for (int y = 0; y < fb.size().height; y++) { |
| if (y % 10 != x % 10) { |
| writer.SetPixel(x, y, color); |
| } |
| } |
| } |
| } |
| |
| Size<int> DrawCharacter(int ch, |
| Vector2<int> pos, |
| color_rgb565_t fg_color, |
| color_rgb565_t bg_color, |
| const FontSet& font, |
| Framebuffer& framebuffer) { |
| if (ch == ' ' || ch == '\0') { |
| // The font doesn't have a space glyph (why?), so special-case this. |
| return DrawSpace(pos, bg_color, font, framebuffer); |
| } |
| if (ch < font.starting_character || ch > font.ending_character) { |
| return Size<int>{0, font.height}; |
| } |
| const int character_index = (int)ch - font.starting_character; |
| |
| FramebufferWriter writer(framebuffer); |
| for (int font_row = 0; font_row < font.height; font_row++) { |
| for (int font_column = 0; font_column < font.width; font_column++) { |
| const bool pixel_on = |
| PW_FONT_BIT(font.width - font_column - 1, |
| font.data[font.height * character_index + font_row]); |
| writer.SetPixel(pos.x + font_column, |
| pos.y + font_row, |
| pixel_on ? fg_color : bg_color); |
| } |
| } |
| return Size<int>{font.width, font.height}; |
| } |
| |
| Size<int> DrawString(std::wstring_view str, |
| Vector2<int> pos, |
| color_rgb565_t fg_color, |
| color_rgb565_t bg_color, |
| const FontSet& font, |
| Framebuffer& framebuffer) { |
| Size<int> string_dimensions{0, font.height}; |
| for (const wchar_t& ch : str) { |
| auto char_dimensions = |
| DrawCharacter(ch, pos, fg_color, bg_color, font, framebuffer); |
| pos.x += char_dimensions.width; |
| string_dimensions.width += char_dimensions.width; |
| } |
| return string_dimensions; |
| } |
| |
| } // namespace pw::draw |