blob: 316a4e85ca15b306feadad924efa748bf06d5ad3 [file] [log] [blame]
// Copyright 2024 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/font_set.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::geometry::Size;
using pw::geometry::Vector2;
namespace pw::draw {
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 < 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