Merge remote-tracking branch 'upstream/master'

Conflicts:
	firmware/application/Makefile
	firmware/application/analog_audio_app.cpp
	firmware/application/analog_audio_app.hpp
	firmware/application/event.cpp
	firmware/application/irq_ipc.hpp
	firmware/application/portapack.hpp
	firmware/application/receiver_model.cpp
	firmware/application/receiver_model.hpp
	firmware/application/recent_entries.cpp
	firmware/application/string_format.hpp
	firmware/application/ui_debug.cpp
	firmware/application/ui_debug.hpp
	firmware/application/ui_menu.cpp
	firmware/application/ui_navigation.cpp
	firmware/application/ui_navigation.hpp
	firmware/application/ui_receiver.cpp
	firmware/application/ui_receiver.hpp
	firmware/application/ui_sd_card_status_view.cpp
	firmware/application/ui_sd_card_status_view.hpp
	firmware/application/ui_setup.cpp
	firmware/application/ui_setup.hpp
	firmware/application/ui_spectrum.hpp
	firmware/baseband-tx/dsp_fir_taps.cpp
	firmware/baseband-tx/dsp_fir_taps.hpp
	firmware/baseband-tx/irq_ipc_m4.cpp
	firmware/baseband-tx/irq_ipc_m4.hpp
	firmware/baseband-tx/proc_audiotx.cpp
	firmware/baseband/Makefile
	firmware/baseband/audio_output.cpp
	firmware/baseband/audio_output.hpp
	firmware/baseband/block_decimator.hpp
	firmware/baseband/dsp_decimate.cpp
	firmware/baseband/dsp_decimate.hpp
	firmware/baseband/dsp_demodulate.cpp
	firmware/baseband/dsp_demodulate.hpp
	firmware/baseband/dsp_fir_taps.cpp
	firmware/baseband/irq_ipc_m4.cpp
	firmware/baseband/irq_ipc_m4.hpp
	firmware/baseband/proc_am_audio.cpp
	firmware/baseband/proc_am_audio.hpp
	firmware/baseband/proc_nfm_audio.cpp
	firmware/baseband/proc_nfm_audio.hpp
	firmware/baseband/proc_wfm_audio.cpp
	firmware/baseband/proc_wfm_audio.hpp
	firmware/baseband/spectrum_collector.hpp
	firmware/common/dsp_fir_taps.cpp
	firmware/common/dsp_fir_taps.hpp
	firmware/common/event.hpp
	firmware/common/message.hpp
	firmware/common/ui_painter.cpp
	firmware/common/ui_painter.hpp
This commit is contained in:
furrtek
2016-02-04 11:35:55 +01:00
45 changed files with 1148 additions and 877 deletions

View File

@@ -34,6 +34,13 @@ struct fir_taps_real {
std::array<int16_t, N> taps;
};
template<size_t N>
struct fir_taps_complex {
float pass_frequency_normalized;
float stop_frequency_normalized;
std::array<complex16_t, N> taps;
};
// NBFM 16K0F3E emission type /////////////////////////////////////////////
// IFIR image-reject filter: fs=3072000, pass=8000, stop=344000, decim=8, fout=384000
@@ -170,8 +177,8 @@ constexpr fir_taps_real<32> taps_6k0_decim_1 {
} },
};
// Channel filter: fs=48000, pass=3000, stop=6700, decim=1, fout=48000
constexpr fir_taps_real<32> taps_6k0_channel {
// IFIR prototype filter: fs=48000, pass=3000, stop=6700, decim=4, fout=12000
constexpr fir_taps_real<32> taps_6k0_decim_2 {
.pass_frequency_normalized = 3000.0f / 48000.0f,
.stop_frequency_normalized = 6700.0f / 48000.0f,
.taps = { {
@@ -182,6 +189,85 @@ constexpr fir_taps_real<32> taps_6k0_channel {
} },
};
// Channel filter: fs=12000, pass=3000, stop=3300, decim=1, fout=12000
/* NOTE: Slightly less than 1.0 gain (normalized to 65536) due to max(taps) being
* slightly larger than 32767 (33312).
*/
constexpr fir_taps_complex<64> taps_6k0_dsb_channel {
.pass_frequency_normalized = 3000.0f / 12000.0f,
.stop_frequency_normalized = 3300.0f / 12000.0f,
.taps = { {
{ -69, 0 }, { -140, 0 }, { 119, 0 }, { 89, 0 },
{ -132, 0 }, { -134, 0 }, { 197, 0 }, { 167, 0 },
{ -273, 0 }, { -206, 0 }, { 372, 0 }, { 247, 0 },
{ -497, 0 }, { -289, 0 }, { 654, 0 }, { 331, 0 },
{ -854, 0 }, { -372, 0 }, { 1112, 0 }, { 411, 0 },
{ -1455, 0 }, { -446, 0 }, { 1933, 0 }, { 476, 0 },
{ -2654, 0 }, { -501, 0 }, { 3902, 0 }, { 520, 0 },
{ -6717, 0 }, { -531, 0 }, { 20478, 0 }, { 32767, 0 },
{ 20478, 0 }, { -531, 0 }, { -6717, 0 }, { 520, 0 },
{ 3902, 0 }, { -501, 0 }, { -2654, 0 }, { 476, 0 },
{ 1933, 0 }, { -446, 0 }, { -1455, 0 }, { 411, 0 },
{ 1112, 0 }, { -372, 0 }, { -854, 0 }, { 331, 0 },
{ 654, 0 }, { -289, 0 }, { -497, 0 }, { 247, 0 },
{ 372, 0 }, { -206, 0 }, { -273, 0 }, { 167, 0 },
{ 197, 0 }, { -134, 0 }, { -132, 0 }, { 89, 0 },
{ 119, 0 }, { -140, 0 }, { -69, 0 }, { 0, 0 },
} },
};
// USB AM 2K80J3E emission type ///////////////////////////////////////////
// IFIR prototype filter: fs=12000, pass=3000, stop=3300, decim=1, fout=12000
constexpr fir_taps_complex<64> taps_2k8_usb_channel {
.pass_frequency_normalized = 3000.0f / 12000.0f,
.stop_frequency_normalized = 3300.0f / 12000.0f,
.taps = { {
{ -146, 0 }, { -41, -45 }, { -1, 10 }, { -95, 69 },
{ -194, -41 }, { -91, -158 }, { 14, -43 }, { -150, 67 },
{ -299, -133 }, { -100, -307 }, { 50, -86 }, { -254, 54 },
{ -453, -329 }, { -62, -587 }, { 170, -189 }, { -334, 0 },
{ -580, -645 }, { 104, -986 }, { 418, -304 }, { -412, -88 },
{ -680, -1178 }, { 527, -1623 }, { 970, -432 }, { -441, -196 },
{ -698, -2149 }, { 1617, -2800 }, { 2384, -507 }, { -429, -311 },
{ -545, -5181 }, { 6925, -7691 }, { 14340, 0 }, { 10601, 11773 },
{ -1499, 14261 }, { -8373, 6083 }, { -5095, -1083 }, { -265, -459 },
{ -753, 2318 }, { -2954, 1315 }, { -2064, -919 }, { -149, -459 },
{ -531, 920 }, { -1669, 355 }, { -1100, -800 }, { -44, -419 },
{ -346, 384 }, { -992, 0 }, { -580, -645 }, { 35, -332 },
{ -205, 149 }, { -577, -123 }, { -280, -485 }, { 80, -247 },
{ -91, 40 }, { -294, -131 }, { -101, -312 }, { 82, -142 },
{ -44, 9 }, { -147, -107 }, { -21, -197 }, { 79, -88 },
{ 10, 0 }, { -41, -45 }, { 15, -145 }, { 0, 0 },
} },
};
// LSB AM 2K80J3E emission type ///////////////////////////////////////////
// IFIR prototype filter: fs=12000, pass=3000, stop=3300, decim=1, fout=12000
constexpr fir_taps_complex<64> taps_2k8_lsb_channel {
.pass_frequency_normalized = 3000.0f / 12000.0f,
.stop_frequency_normalized = 3300.0f / 12000.0f,
.taps = { {
{ -146, 0 }, { -41, 45 }, { -1, -10 }, { -95, -69 },
{ -194, 41 }, { -91, 158 }, { 14, 43 }, { -150, -67 },
{ -299, 133 }, { -100, 307 }, { 50, 86 }, { -254, -54 },
{ -453, 329 }, { -62, 587 }, { 170, 189 }, { -334, 0 },
{ -580, 645 }, { 104, 986 }, { 418, 304 }, { -412, 88 },
{ -680, 1178 }, { 527, 1623 }, { 970, 432 }, { -441, 196 },
{ -698, 2149 }, { 1617, 2800 }, { 2384, 507 }, { -429, 311 },
{ -545, 5181 }, { 6925, 7691 }, { 14340, 0 }, { 10601, -11773 },
{ -1499, -14261 }, { -8373, -6083 }, { -5095, 1083 }, { -265, 459 },
{ -753, -2318 }, { -2954, -1315 }, { -2064, 919 }, { -149, 459 },
{ -531, -920 }, { -1669, -355 }, { -1100, 800 }, { -44, 419 },
{ -346, -384 }, { -992, 0 }, { -580, 645 }, { 35, 332 },
{ -205, -149 }, { -577, 123 }, { -280, 485 }, { 80, 247 },
{ -91, -40 }, { -294, 131 }, { -101, 312 }, { 82, 142 },
{ -44, -9 }, { -147, 107 }, { -21, 197 }, { 79, 88 },
{ 10, 0 }, { -41, 45 }, { 15, 145 }, { 0, 0 },
} },
};
// WFM 200KF8E emission type //////////////////////////////////////////////
// IFIR image-reject filter: fs=3072000, pass=100000, stop=484000, decim=4, fout=768000

View File

@@ -0,0 +1,57 @@
/*
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
*
* This file is part of PortaPack.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; see the file COPYING. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street,
* Boston, MA 02110-1301, USA.
*/
#include "dsp_iir.hpp"
#include <hal.h>
void IIRBiquadFilter::configure(const iir_biquad_config_t& new_config) {
config = new_config;
}
void IIRBiquadFilter::execute(const buffer_f32_t& buffer_in, const buffer_f32_t& buffer_out) {
const auto a_ = config.a;
const auto b_ = config.b;
auto x_ = x;
auto y_ = y;
// TODO: Assert that buffer_out.count == buffer_in.count.
for(size_t i=0; i<buffer_out.count; i++) {
x_[0] = x_[1];
x_[1] = x_[2];
x_[2] = buffer_in.p[i];
y_[0] = y_[1];
y_[1] = y_[2];
y_[2] = b_[0] * x_[2] + b_[1] * x_[1] + b_[2] * x_[0]
- a_[1] * y_[1] - a_[2] * y_[0];
buffer_out.p[i] = y_[2];
}
x = x_;
y = y_;
}
void IIRBiquadFilter::execute_in_place(const buffer_f32_t& buffer) {
execute(buffer, buffer);
}

View File

@@ -0,0 +1,70 @@
/*
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
*
* This file is part of PortaPack.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; see the file COPYING. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street,
* Boston, MA 02110-1301, USA.
*/
#ifndef __DSP_IIR_H__
#define __DSP_IIR_H__
#include <array>
#include "dsp_types.hpp"
struct iir_biquad_config_t {
std::array<float, 3> b;
std::array<float, 3> a;
};
constexpr iir_biquad_config_t iir_config_passthrough {
{ { 1.0f, 0.0f, 0.0f } },
{ { 0.0f, 0.0f, 0.0f } },
};
constexpr iir_biquad_config_t iir_config_no_pass {
{ { 0.0f, 0.0f, 0.0f } },
{ { 0.0f, 0.0f, 0.0f } },
};
class IIRBiquadFilter {
public:
// http://www.musicdsp.org/files/Audio-EQ-Cookbook.txt
constexpr IIRBiquadFilter(
) : IIRBiquadFilter(iir_config_no_pass)
{
}
// Assume all coefficients are normalized so that a0=1.0
constexpr IIRBiquadFilter(
const iir_biquad_config_t& config
) : config(config)
{
}
void configure(const iir_biquad_config_t& new_config);
void execute(const buffer_f32_t& buffer_in, const buffer_f32_t& buffer_out);
void execute_in_place(const buffer_f32_t& buffer);
private:
iir_biquad_config_t config;
std::array<float, 3> x { { 0.0f, 0.0f, 0.0f } };
std::array<float, 3> y { { 0.0f, 0.0f, 0.0f } };
};
#endif/*__DSP_IIR_H__*/

View File

@@ -0,0 +1,112 @@
/*
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
*
* This file is part of PortaPack.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; see the file COPYING. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street,
* Boston, MA 02110-1301, USA.
*/
#ifndef __DSP_IIR_CONFIG_H__
#define __DSP_IIR_CONFIG_H__
#include "dsp_iir.hpp"
// scipy.signal.butter(2, 30 / 24000.0, 'highpass', analog=False)
constexpr iir_biquad_config_t audio_48k_hpf_30hz_config {
{ 0.99722705f, -1.99445410f, 0.99722705f },
{ 1.00000000f, -1.99444641f, 0.99446179f }
};
// scipy.signal.butter(2, 300 / 24000.0, 'highpass', analog=False)
constexpr iir_biquad_config_t audio_48k_hpf_300hz_config {
{ 0.97261390f, -1.94522780f, 0.97261390f },
{ 1.00000000f, -1.94447766f, 0.94597794f }
};
// scipy.signal.butter(2, 300 / 12000.0, 'highpass', analog=False)
constexpr iir_biquad_config_t audio_24k_hpf_300hz_config {
{ 0.94597686f, -1.89195371f, 0.94597686f },
{ 1.00000000f, -1.88903308f, 0.89487434f }
};
// scipy.signal.butter(2, 300 / 8000.0, 'highpass', analog=False)
constexpr iir_biquad_config_t audio_16k_hpf_300hz_config {
{ 0.92006616f, -1.84013232f, 0.92006616f },
{ 1.00000000f, -1.83373266f, 0.84653197f }
};
// scipy.signal.butter(2, 300 / 6000.0, 'highpass', analog=False)
constexpr iir_biquad_config_t audio_12k_hpf_300hz_config {
{ 0.89485861f, -1.78971721f, 0.89485861f },
{ 1.00000000f, -1.77863178f, 0.80080265f }
};
// scipy.signal.butter(2, 300 / 4000.0, 'highpass', analog=False)
constexpr iir_biquad_config_t audio_8k_hpf_300hz_config {
{ 0.84645925f, -1.69291851f, 0.84645925f },
{ 1.00000000f, -1.66920314f, 0.71663387f }
};
// scipy.signal.iirdesign(wp=8000 / 24000.0, ws= 4000 / 24000.0, gpass=1, gstop=18, ftype='ellip')
constexpr iir_biquad_config_t non_audio_hpf_config {
{ 0.51891061f, -0.95714180f, 0.51891061f },
{ 1.0f , -0.79878302f, 0.43960231f }
};
// scipy.signal.butter(1, 300 / 24000.0, 'lowpass', analog=False)
// NOTE: Technically, order-1 filter, b[2] = a[2] = 0.
constexpr iir_biquad_config_t audio_48k_deemph_300_6_config {
{ 0.01925927f, 0.01925927f, 0.00000000f },
{ 1.00000000f, -0.96148145f, 0.00000000f }
};
// scipy.signal.butter(1, 300 / 12000.0, 'lowpass', analog=False)
// NOTE: Technically, order-1 filter, b[2] = a[2] = 0.
constexpr iir_biquad_config_t audio_24k_deemph_300_6_config {
{ 0.03780475f, 0.03780475f, 0.00000000f },
{ 1.00000000f, -0.92439049f, 0.00000000f }
};
// scipy.signal.butter(1, 300 / 8000.0, 'lowpass', analog=False)
// NOTE: Technically, order-1 filter, b[2] = a[2] = 0.
constexpr iir_biquad_config_t audio_16k_deemph_300_6_config {
{ 0.05568894f, 0.05568894f, 0.00000000f },
{ 1.00000000f, -0.88862213f, 0.00000000f }
};
// scipy.signal.butter(1, 300 / 6000.0, 'lowpass', analog=False)
// NOTE: Technically, order-1 filter, b[2] = a[2] = 0.
constexpr iir_biquad_config_t audio_12k_deemph_300_6_config {
{ 0.07295966f, 0.07295966f, 0.00000000f },
{ 1.00000000f, -0.85408069f, 0.00000000f }
};
// scipy.signal.butter(1, 300 / 4000.0, 'lowpass', analog=False)
// NOTE: Technically, order-1 filter, b[2] = a[2] = 0.
constexpr iir_biquad_config_t audio_8k_deemph_300_6_config {
{ 0.10583178f, 0.10583178f, 0.00000000f },
{ 1.00000000f, -0.78833643f, 0.00000000f }
};
// 75us RC time constant, used in broadcast FM in Americas, South Korea
// scipy.signal.butter(1, 2122 / 24000.0, 'lowpass', analog=False)
// NOTE: Technically, order-1 filter, b[2] = a[2] = 0.
constexpr iir_biquad_config_t audio_48k_deemph_2122_6_config {
{ 0.12264116f, 0.12264116f, 0.00000000f },
{ 1.00000000f, -0.75471767f, 0.00000000f }
};
#endif/*__DSP_IIR_CONFIG_H__*/

View File

@@ -394,20 +394,29 @@ void ILI9341::draw_pixels(
io.lcd_write_pixels(colors, count);
}
void ILI9341::draw_bitmap(
const ui::Point p,
const ui::Size size,
const uint8_t* const pixels,
const ui::Color foreground,
const ui::Color background
) {
lcd_start_ram_write(p, size);
const size_t count = size.w * size.h;
for(size_t i=0; i<count; i++) {
const auto pixel = pixels[i >> 3] & (1U << (i & 0x7));
io.lcd_write_pixel(pixel ? foreground : background);
}
}
void ILI9341::draw_glyph(
const ui::Point p,
const ui::Glyph& glyph,
const ui::Color foreground,
const ui::Color background
) {
lcd_start_ram_write(p, glyph.size());
const size_t count = glyph.w() * glyph.h();
const auto pixels = glyph.pixels();
for(size_t i=0; i<count; i++) {
const auto pixel = pixels[i >> 3] & (1U << (i & 0x7));
io.lcd_write_pixel(pixel ? foreground : background);
}
draw_bitmap(p, glyph.size(), glyph.pixels(), foreground, background);
}
void ILI9341::scroll_set_area(

View File

@@ -69,6 +69,14 @@ public:
draw_pixels(r, colors.data(), colors.size());
}
void draw_bitmap(
const ui::Point p,
const ui::Size size,
const uint8_t* const data,
const ui::Color foreground,
const ui::Color background
);
void draw_glyph(
const ui::Point p,
const ui::Glyph& glyph,

View File

@@ -30,6 +30,7 @@
#include "baseband_packet.hpp"
#include "ert_packet.hpp"
#include "dsp_fir_taps.hpp"
#include "dsp_iir.hpp"
#include "fifo.hpp"
#include "utility.hpp"
@@ -38,7 +39,7 @@
class Message {
public:
static constexpr size_t MAX_SIZE = 276;
static constexpr size_t MAX_SIZE = 512;
enum class ID : uint32_t {
/* Assign consecutive IDs. IDs are used to index array. */
@@ -326,19 +327,28 @@ public:
const fir_taps_real<24> decim_0_filter,
const fir_taps_real<32> decim_1_filter,
const fir_taps_real<32> channel_filter,
const size_t deviation
const size_t channel_decimation,
const size_t deviation,
const iir_biquad_config_t audio_hpf_config,
const iir_biquad_config_t audio_deemph_config
) : Message { ID::NBFMConfigure },
decim_0_filter(decim_0_filter),
decim_1_filter(decim_1_filter),
channel_filter(channel_filter),
deviation { deviation }
channel_decimation { channel_decimation },
deviation { deviation },
audio_hpf_config(audio_hpf_config),
audio_deemph_config(audio_deemph_config)
{
}
const fir_taps_real<24> decim_0_filter;
const fir_taps_real<32> decim_1_filter;
const fir_taps_real<32> channel_filter;
const size_t channel_decimation;
const size_t deviation;
const iir_biquad_config_t audio_hpf_config;
const iir_biquad_config_t audio_deemph_config;
};
class WFMConfigureMessage : public Message {
@@ -347,12 +357,16 @@ public:
const fir_taps_real<24> decim_0_filter,
const fir_taps_real<16> decim_1_filter,
const fir_taps_real<64> audio_filter,
const size_t deviation
const size_t deviation,
const iir_biquad_config_t audio_hpf_config,
const iir_biquad_config_t audio_deemph_config
) : Message { ID::WFMConfigure },
decim_0_filter(decim_0_filter),
decim_1_filter(decim_1_filter),
audio_filter(audio_filter),
deviation { deviation }
deviation { deviation },
audio_hpf_config(audio_hpf_config),
audio_deemph_config(audio_deemph_config)
{
}
@@ -360,24 +374,40 @@ public:
const fir_taps_real<16> decim_1_filter;
const fir_taps_real<64> audio_filter;
const size_t deviation;
const iir_biquad_config_t audio_hpf_config;
const iir_biquad_config_t audio_deemph_config;
};
class AMConfigureMessage : public Message {
public:
enum class Modulation : int32_t {
DSB = 0,
SSB = 1,
};
constexpr AMConfigureMessage(
const fir_taps_real<24> decim_0_filter,
const fir_taps_real<32> decim_1_filter,
const fir_taps_real<32> channel_filter
const fir_taps_real<32> decim_2_filter,
const fir_taps_complex<64> channel_filter,
const Modulation modulation,
const iir_biquad_config_t audio_hpf_config
) : Message { ID::AMConfigure },
decim_0_filter(decim_0_filter),
decim_1_filter(decim_1_filter),
channel_filter(channel_filter)
decim_2_filter(decim_2_filter),
channel_filter(channel_filter),
modulation { modulation },
audio_hpf_config(audio_hpf_config)
{
}
const fir_taps_real<24> decim_0_filter;
const fir_taps_real<32> decim_1_filter;
const fir_taps_real<32> channel_filter;
const fir_taps_real<32> decim_2_filter;
const fir_taps_complex<64> channel_filter;
const Modulation modulation;
const iir_biquad_config_t audio_hpf_config;
};
class TXDoneMessage : public Message {

View File

@@ -273,6 +273,11 @@ struct Rect {
}
};
struct Bitmap {
const Size size;
const uint8_t* const data;
};
enum class KeyEvent {
/* Ordinals map to bit positions reported by CPLD */
Right = 0,

View File

@@ -54,6 +54,10 @@ int Painter::draw_string(Point p, const Style& style, const std::string text) {
return width;
}
void Painter::draw_bitmap(const Point p, const Bitmap& bitmap, const Color foreground, const Color background) {
display.draw_bitmap(p, bitmap.size, bitmap.data, foreground, background);
}
void Painter::draw_hline(Point p, int width, const Color c) {
display.fill_rectangle({ p, { width, 1 } }, c);
}

View File

@@ -50,6 +50,8 @@ public:
int draw_string(Point p, const Style& style, const std::string text);
void draw_bitmap(const Point p, const Bitmap& bitmap, const Color background, const Color foreground);
void draw_rectangle(const Rect r, const Color c);
void fill_rectangle(const Rect r, const Color c);