commit ee867edb51a3d41f67408c5adba876a79da7f8d9 Author: Gwendolyn Date: Sat Mar 12 12:33:52 2022 +0100 initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..9dcd3fb --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +.pio +.idea +CMakeLists*.txt +cmake-* \ No newline at end of file diff --git a/cyber.png b/cyber.png new file mode 100644 index 0000000..80883aa Binary files /dev/null and b/cyber.png differ diff --git a/cyber96.png b/cyber96.png new file mode 100644 index 0000000..45200b9 Binary files /dev/null and b/cyber96.png differ diff --git a/gradient.png b/gradient.png new file mode 100644 index 0000000..9dc5448 Binary files /dev/null and b/gradient.png differ diff --git a/imgtoc.py b/imgtoc.py new file mode 100644 index 0000000..cb3abd8 --- /dev/null +++ b/imgtoc.py @@ -0,0 +1,178 @@ +#!/usr/bin/env python3 + +# ##### ## ## #### ####### #### #### +# # # # # # # # # # # # # +# # # # # # # # # # +# # # # # ### # # # # +# # # # # # # # # # # +# ##### # # #### # #### #### +# +# Converts a bitmap image to c source. + + +import os +import typer +from PIL import Image + + +class PixelList: + byte_align_rows = 0 + align_pixels = 0 + depth = 0 + current_row = [] + rows = [] + current_byte = 0 + current_byte_length = 0 + num_row_bytes = 0 + + def __init__(self, depth: int, align_pixels: int, byte_align_rows: bool): + if depth not in [1, 2, 4, 8]: + raise 'depth must be 1, 2, 4 or 8' + elif align_pixels % depth != 0: + raise 'align_pixels must be a multiple of depth' + elif align_pixels not in [1, 2, 4, 8]: + raise 'align_pixels must be 1, 2, 4 or 8' + + + self.depth = depth + self.byte_align_rows = byte_align_rows + self.align_pixels = align_pixels + + def append_pixel(self, pixel: int): + self.current_byte = (self.current_byte << self.align_pixels) | (pixel & ((1 << self.depth) - 1)) + self.current_byte_length += self.align_pixels + if self.current_byte_length == 8: + self.current_row.append(self.current_byte) + self.num_row_bytes += 1 + self.current_byte = 0 + self.current_byte_length = 0 + pass + + def end_row(self): + if self.current_byte_length > 0 and self.byte_align_rows: + byte = self.current_byte << (8 - self.current_byte_length) + self.current_row.append(byte) + self.num_row_bytes += 1 + self.current_byte = 0 + self.current_byte_length = 0 + self.rows.append(self.current_row) + self.current_row = [] + + def end_image(self): + if self.current_byte_length > 0: + byte = self.current_byte << (8 - self.current_byte_length) + self.rows.append([byte]) + + def get_rows(self): + return self.rows + + +def img_to_c(im: Image, fmt: str, invert: bool, depth: int, align_pixels: int, byte_align_rows: bool): + pixels = PixelList(depth, align_pixels, byte_align_rows) + + for iy in range(0, im.size[1]): # iterate rows + for ix in range(0, im.size[0]): # iterate columns + pix = im.getpixel((ix, iy)) + pixels.append_pixel(pix) + pixels.end_row() + pixels.end_image() + + src_c = '' + for row in pixels.get_rows(): # iterate rows + src_c += ' ' + for byte in row: + if invert: + byte = 0xff - byte + if fmt == 'hex': + src_c += '0x' + format(byte, '02x') + elif fmt == 'bin': + src_c += 'B' + format(byte, '08b') + elif fmt == 'dec': + src_c += f'{byte:>3}' + src_c += ', ' + src_c += '\n' + return src_c + + +def make_palette(depth: int): + num_colors = 1 << depth + colors = [] + color_step = 255 / (num_colors - 1) + for i in range(0, num_colors - 1): + colors.append(int(color_step * i)) + colors.append(255) + return colors + + +def find_closest_palette_color(color: int, palette: [int]) -> (int, int): + return min(enumerate(palette), key=lambda x: abs(x[1] - color)) + + +def dither(original: Image, palette: [int]): + im = Image.new('L', original.size) + im.paste(original) + for iy in range(0, im.size[1]): # iterate rows + for ix in range(0, im.size[0]): # iterate columns + oldpix = im.getpixel((ix, iy)) + closest = find_closest_palette_color(oldpix, palette) + im.putpixel((ix, iy), closest[0]) + quant_error = oldpix - closest[1] + if ix < im.size[0] - 1: + im.putpixel((ix + 1, iy), int(im.getpixel((ix + 1, iy)) + quant_error * 7 / 16)) + if ix > 0 and iy < im.size[1] - 1: + im.putpixel((ix - 1, iy + 1), int(im.getpixel((ix - 1, iy + 1)) + quant_error * 3 / 16)) + if iy < im.size[1] - 1: + im.putpixel((ix, iy + 1), int(im.getpixel((ix, iy + 1)) + quant_error * 5 / 16)) + if ix < im.size[0] - 1 and iy < im.size[1] - 1: + im.putpixel((ix + 1, iy + 1), int(im.getpixel((ix + 1, iy + 1)) + quant_error * 1 / 16)) + + return im + + +def main(input_file: str, + out: str = typer.Option(None, help='File to write to, defaults to standard output'), + name: str = typer.Option(None, help='Name of the bitmap variable in generated source, defaults to the ' + 'filename without extension.'), + fmt: str = typer.Option('hex', help='Format to generate image data in (hex or bin).'), + invert: bool = typer.Option(False, help='Invert brightness.'), + depth: int = typer.Option(1, help='Bit depth of the output, can be 1, 2, 4 or 8.'), + pixel_align: int = typer.Option(None, help='Bit-alignment of the pixels, defaults to the depth, can be 1, 2, 4 or 8.'), + no_row_align: bool = typer.Option(False, help='Disable byte alignment for rows.'), + ): + im: Image = Image.open(input_file) + if im.mode != 'L': + im = im.convert(mode='L') + + im = dither(im, make_palette(depth)) + + if name is None: + name = os.path.splitext(os.path.basename(input_file))[0] + + if pixel_align is None: + pixel_align = depth + + row_align_val = 0 if no_row_align else 1 + + ctext = f'// Image definition of: {input_file}\n' + ctext += 'static PROGMEM bitmap_t ' + name + ' { \n' + ctext += f' .width = {im.size[0]},\n' + ctext += f' .height = {im.size[1]},\n' + ctext += f' .depth = {depth},\n' + ctext += f' .pixel_align = {pixel_align},\n' + ctext += f' .row_align = {row_align_val},\n' + ctext += ' .data = (PROGMEM uint8_t[]) {\n' + + ctext += img_to_c(im, fmt, invert, depth, pixel_align, not no_row_align) + + ctext += ' }\n};\n' + + if out: + with open(out, mode='w') as f: + f.write(ctext) + f.close() + else: + print(ctext) + + +if __name__ == "__main__": + typer.run(main) diff --git a/include/FixedPoint.h b/include/FixedPoint.h new file mode 100644 index 0000000..c008878 --- /dev/null +++ b/include/FixedPoint.h @@ -0,0 +1,302 @@ + +template +class FixedPoint { + int64_t val_; + public: + + constexpr FixedPoint() : val_(0) {} + + constexpr FixedPoint(int64_t v) : val_(v * ScalingFactor) {} + + constexpr FixedPoint(int32_t v) : val_(v * ScalingFactor) {} + + constexpr FixedPoint(int16_t v) : val_(v * ScalingFactor) {} + + constexpr FixedPoint(int8_t v) : val_(v * ScalingFactor) {} + + constexpr FixedPoint(uint64_t v) : val_(v * ScalingFactor) {} + + constexpr FixedPoint(uint32_t v) : val_(v * ScalingFactor) {} + + constexpr FixedPoint(uint16_t v) : val_(v * ScalingFactor) {} + + constexpr FixedPoint(uint8_t v) : val_(v * ScalingFactor) {} + + constexpr FixedPoint(float v) : val_(static_cast(v * ScalingFactor)) {} + + constexpr FixedPoint(double v) : val_(static_cast(v * ScalingFactor)) {} + + constexpr FixedPoint(long double v) : val_(static_cast(v * ScalingFactor)) {} + + constexpr double toDouble() { + return static_cast(val_) / ScalingFactor; + } + + constexpr int64_t toInt() { + return val_ / ScalingFactor; + } + + constexpr int GetScalingFactor() { + return ScalingFactor; + } + + constexpr int64_t GetUnderlyingValue() { + return val_; + } + + constexpr FixedPoint abs() const { + FixedPoint res; + res.val_ = std::abs(val_); + return res; + } + + constexpr FixedPoint exp10(int exp) { + FixedPoint res = *this; + while(exp > 0) { + res *= 10; + exp--; + } + while (exp < 0) { + res *= 10; + exp++; + } + return res; + } + + String toString(int min_decimal_digits = 0, bool zero_prefix_if_less_than_one = true) { + if (val_ == 0) { + return "0"; + } + + constexpr int max_len = std::numeric_limits::max_digits10 + 2; // max digits plus decimal point and sign + char c[max_len]; + int len = 0; + + int64_t val = val_; + + bool negative = false; + if (val < 0) { + negative = true; + val = -val; + } + int64_t decimal_part = val % ScalingFactor; + if (decimal_part != 0) { + while (decimal_part % 10 == 0) { + decimal_part /= 10; + } + do { + len += 1; + c[max_len - len] = '0' + decimal_part % 10; + decimal_part /= 10; + } while (decimal_part > 0); + len += 1; + c[max_len - len] = '.'; + } + + val /= ScalingFactor; + + do { + len += 1; + c[max_len - len] = '0' + val % 10; + val /= 10; + } while (val > 0); + + if (negative) { + len += 1; + c[max_len - len] = '-'; + } + + return String(&c[max_len - len], len); + } + + constexpr FixedPoint operator-() { + FixedPoint res; + res.val_ = -val_; + return res; + } + + constexpr FixedPoint &operator+=(const FixedPoint &rhs) { + val_ += rhs.val_; + return *this; + } + + constexpr FixedPoint &operator+=(int rhs) { + *this += FixedPoint(rhs); + return *this; + } + + constexpr FixedPoint &operator+=(double rhs) { + *this += FixedPoint(rhs); + return *this; + } + + constexpr FixedPoint &operator-=(const FixedPoint &rhs) { + val_ -= rhs.val_; + return *this; + } + + constexpr FixedPoint &operator-=(int rhs) { + *this -= FixedPoint(rhs); + return *this; + } + + constexpr FixedPoint &operator-=(double rhs) { + *this -= FixedPoint(rhs); + return *this; + } + + constexpr FixedPoint &operator*=(const FixedPoint &rhs) { + val_ = val_ * rhs.val_ / ScalingFactor; + return *this; + } + + constexpr FixedPoint &operator*=(int rhs) { + *this *= FixedPoint(rhs); + return *this; + } + + constexpr FixedPoint &operator*=(double rhs) { + *this *= FixedPoint(rhs); + return *this; + } + + constexpr FixedPoint &operator/=(const FixedPoint &rhs) { + val_ = val_ * ScalingFactor / rhs.val_; + return *this; + } + + constexpr FixedPoint &operator/=(int rhs) { + *this /= FixedPoint(rhs); + return *this; + } + + constexpr FixedPoint &operator/=(double rhs) { + *this /= FixedPoint(rhs); + return *this; + } + + constexpr friend FixedPoint operator+(FixedPoint lhs, + const FixedPoint &rhs) { + lhs += rhs; + return lhs; + } + + constexpr friend FixedPoint operator-(FixedPoint lhs, + const FixedPoint &rhs) { + lhs -= rhs; + return lhs; + } + + constexpr friend FixedPoint operator*(FixedPoint lhs, + const FixedPoint &rhs) { + lhs *= rhs; + return lhs; + } + + constexpr friend FixedPoint operator/(FixedPoint lhs, + const FixedPoint &rhs) { + lhs /= rhs; + return lhs; + } + + constexpr friend FixedPoint operator+(FixedPoint lhs, int rhs) { + lhs += rhs; + return lhs; + } + + constexpr friend FixedPoint operator+(int lhs, FixedPoint rhs) { + rhs += lhs; + return rhs; + } + + constexpr friend FixedPoint operator+(FixedPoint lhs, double rhs) { + lhs += rhs; + return lhs; + } + + constexpr friend FixedPoint operator+(double lhs, FixedPoint rhs) { + rhs += lhs; + return rhs; + } + + constexpr friend FixedPoint operator-(FixedPoint lhs, int rhs) { + lhs -= rhs; + return lhs; + } + + constexpr friend FixedPoint operator-(int lhs, FixedPoint rhs) { + return FixedPoint(lhs) - rhs; + } + + constexpr friend FixedPoint operator-(FixedPoint lhs, double rhs) { + lhs -= rhs; + return lhs; + } + + constexpr friend FixedPoint operator-(double lhs, FixedPoint rhs) { + return FixedPoint(lhs) - rhs; + } + + constexpr friend FixedPoint operator*(FixedPoint lhs, int rhs) { + lhs *= rhs; + return lhs; + } + + constexpr friend FixedPoint operator*(int lhs, FixedPoint rhs) { + rhs *= lhs; + return rhs; + } + + constexpr friend FixedPoint operator*(FixedPoint lhs, double rhs) { + lhs *= rhs; + return lhs; + } + + constexpr friend FixedPoint operator*(double lhs, FixedPoint rhs) { + rhs *= lhs; + return rhs; + } + + constexpr friend FixedPoint operator/(FixedPoint lhs, int rhs) { + lhs /= rhs; + return lhs; + } + + constexpr friend FixedPoint operator/(int lhs, FixedPoint rhs) { + return FixedPoint(lhs) / rhs; + } + + constexpr friend FixedPoint operator/(FixedPoint lhs, double rhs) { + lhs /= rhs; + return lhs; + } + + constexpr friend FixedPoint operator/(double lhs, FixedPoint rhs) { + return FixedPoint(lhs) / rhs; + } + + constexpr friend bool operator<(FixedPoint lhs, const FixedPoint &rhs) { + return lhs.val_ < rhs.val_; + } + + constexpr friend bool operator>(FixedPoint lhs, const FixedPoint &rhs) { + return rhs < lhs; + } + + constexpr friend bool operator<=(FixedPoint lhs, const FixedPoint &rhs) { + return !(lhs > rhs); + } + + constexpr friend bool operator>=(FixedPoint lhs, const FixedPoint &rhs) { + return !(lhs < rhs); + } + + constexpr friend bool operator==(FixedPoint lhs, const FixedPoint &rhs) { + return lhs.val_ == rhs.val_; + } + + constexpr friend bool operator!=(FixedPoint lhs, const FixedPoint &rhs) { + return !(lhs == rhs); + } + +}; \ No newline at end of file diff --git a/include/README b/include/README new file mode 100644 index 0000000..194dcd4 --- /dev/null +++ b/include/README @@ -0,0 +1,39 @@ + +This directory is intended for project header files. + +A header file is a file containing C declarations and macro definitions +to be shared between several project source files. You request the use of a +header file in your project source file (C, C++, etc) located in `src` folder +by including it, with the C preprocessing directive `#include'. + +```src/main.c + +#include "header.h" + +int main (void) +{ + ... +} +``` + +Including a header file produces the same results as copying the header file +into each source file that needs it. Such copying would be time-consuming +and error-prone. With a header file, the related declarations appear +in only one place. If they need to be changed, they can be changed in one +place, and programs that include the header file will automatically use the +new version when next recompiled. The header file eliminates the labor of +finding and changing all the copies as well as the risk that a failure to +find one copy will result in inconsistencies within a program. + +In C, the usual convention is to give header files names that end with `.h'. +It is most portable to use only letters, digits, dashes, and underscores in +header file names, and at most one dot. + +Read more about using header files in official GCC documentation: + +* Include Syntax +* Include Operation +* Once-Only Headers +* Computed Includes + +https://gcc.gnu.org/onlinedocs/cpp/Header-Files.html diff --git a/include/bitmaps/cyber96.h b/include/bitmaps/cyber96.h new file mode 100644 index 0000000..5a6a6b2 --- /dev/null +++ b/include/bitmaps/cyber96.h @@ -0,0 +1,36 @@ +// Image definition of: cyber96.png +static PROGMEM bitmap_t cyber96 { + .width = 96, + .height = 25, + .depth = 8, + .pixel_align = 8, + .row_align = 1, + .data = (PROGMEM uint8_t[]) { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x1c, 0x90, 0xde, 0xfc, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe8, 0x5c, 0x00, 0x00, 0x00, 0x00, 0x3d, 0xda, 0xf0, 0x7b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x68, 0xeb, 0xe3, 0x4f, 0x00, 0x00, 0x00, 0x22, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfb, 0xe3, 0x9c, 0x2e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x34, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0xd7, 0x39, 0x00, 0x00, 0x00, 0x45, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xed, 0xa8, 0x2f, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x38, 0xee, 0xfd, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0xfb, 0x19, 0x00, 0x00, 0x00, 0xe4, 0xfc, 0xff, 0xff, 0x3d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x25, 0xfe, 0xfe, 0xfd, 0xf7, 0x0e, 0x00, 0x00, 0x22, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0xfd, 0x81, 0x01, 0x00, 0x00, 0x00, 0x00, 0x34, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0xe2, 0x00, 0x00, 0x00, 0x45, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0xfb, 0x5a, 0x00, + 0x00, 0x00, 0x00, 0x0e, 0xe5, 0xfc, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x35, 0x00, 0x00, 0x0f, 0xff, 0xff, 0xff, 0xff, 0x6c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x51, 0xff, 0xff, 0xff, 0xff, 0x32, 0x00, 0x00, 0x22, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x85, 0x00, 0x00, 0x00, 0x00, 0x34, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x05, 0x00, 0x00, 0x45, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0xfa, 0x2c, + 0x00, 0x00, 0x00, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xfb, 0xfc, 0xfc, 0xfc, 0xfc, 0xfc, 0xff, 0xd6, 0x06, 0x00, 0x00, 0x11, 0xff, 0xff, 0xff, 0xff, 0x6e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x53, 0xff, 0xff, 0xff, 0xff, 0x34, 0x00, 0x00, 0x22, 0xff, 0xff, 0xff, 0xfe, 0xfc, 0xfc, 0xfc, 0xfc, 0xfc, 0xfb, 0xfe, 0xff, 0xff, 0xff, 0xfe, 0x37, 0x00, 0x00, 0x00, 0x34, 0xff, 0xff, 0xff, 0xfd, 0xfc, 0xfc, 0xfc, 0xfc, 0xfc, 0xfc, 0xfc, 0xfc, 0xfc, 0xfc, 0xff, 0xa6, 0x00, 0x00, 0x00, 0x45, 0xff, 0xff, 0xff, 0xfd, 0xfc, 0xfc, 0xfc, 0xfc, 0xfc, 0xfc, 0xfc, 0xfd, 0xff, 0xff, 0xfc, 0xa4, + 0x00, 0x00, 0x0e, 0xf0, 0xfe, 0xff, 0xff, 0xfc, 0x97, 0x88, 0x88, 0x88, 0x88, 0x88, 0x74, 0x10, 0x00, 0x00, 0x00, 0x11, 0xff, 0xff, 0xff, 0xfd, 0xc3, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0xaa, 0xfe, 0xff, 0xff, 0xff, 0x34, 0x00, 0x00, 0x22, 0xff, 0xff, 0xff, 0xff, 0xb3, 0x88, 0x88, 0x88, 0x88, 0x97, 0xe5, 0xfe, 0xff, 0xff, 0xfc, 0xb0, 0x00, 0x00, 0x00, 0x34, 0xff, 0xff, 0xff, 0xff, 0xab, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x64, 0x05, 0x00, 0x00, 0x00, 0x45, 0xff, 0xff, 0xff, 0xff, 0xa3, 0x88, 0x88, 0x88, 0x88, 0x88, 0x8c, 0xea, 0xfc, 0xff, 0xff, 0xea, + 0x00, 0x00, 0x79, 0xff, 0xff, 0xff, 0xfd, 0x97, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0b, 0xf8, 0xfe, 0xff, 0xff, 0xff, 0x8a, 0x00, 0x00, 0x00, 0x00, 0x6d, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x27, 0x00, 0x00, 0x22, 0xff, 0xff, 0xff, 0xff, 0x5c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0e, 0xd9, 0xfc, 0xff, 0xff, 0xf8, 0x03, 0x00, 0x00, 0x34, 0xff, 0xff, 0xff, 0xff, 0x4b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x45, 0xff, 0xff, 0xff, 0xff, 0x3a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x82, 0xfe, 0xff, 0xff, 0xfd, + 0x00, 0x0b, 0xec, 0xfe, 0xff, 0xff, 0xfb, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x65, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x52, 0x00, 0x00, 0x36, 0xfb, 0xfe, 0xff, 0xff, 0xff, 0x86, 0x00, 0x00, 0x00, 0x22, 0xff, 0xff, 0xff, 0xff, 0x5c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6f, 0xff, 0xff, 0xff, 0xff, 0x1a, 0x00, 0x00, 0x34, 0xff, 0xff, 0xff, 0xff, 0x4b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x45, 0xff, 0xff, 0xff, 0xff, 0x3a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7f, 0xff, 0xff, 0xff, 0xff, + 0x00, 0x73, 0xff, 0xff, 0xff, 0xfd, 0x97, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x9f, 0xfe, 0xff, 0xff, 0xfd, 0xf2, 0x23, 0x11, 0xe1, 0xfc, 0xff, 0xff, 0xfd, 0xc0, 0x04, 0x00, 0x00, 0x00, 0x22, 0xff, 0xff, 0xff, 0xff, 0x5c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x5c, 0xff, 0xff, 0xff, 0xff, 0x23, 0x00, 0x00, 0x34, 0xff, 0xff, 0xff, 0xff, 0x4b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x45, 0xff, 0xff, 0xff, 0xff, 0x7c, 0x55, 0x55, 0x55, 0x55, 0x55, 0x56, 0xcf, 0xfc, 0xff, 0xff, 0xf2, + 0x08, 0xe8, 0xfd, 0xff, 0xff, 0xfb, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a, 0xd4, 0xfc, 0xff, 0xff, 0xfc, 0xd1, 0xb7, 0xfd, 0xff, 0xff, 0xfc, 0xe8, 0x17, 0x00, 0x00, 0x00, 0x00, 0x22, 0xff, 0xff, 0xff, 0xff, 0x5c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7c, 0xfe, 0xff, 0xff, 0xff, 0x13, 0x00, 0x00, 0x34, 0xff, 0xff, 0xff, 0xff, 0x4b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x45, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfd, 0xff, 0xff, 0xfc, 0xb7, + 0x5e, 0xff, 0xff, 0xff, 0xfd, 0x98, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x26, 0xf3, 0xfd, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0xfb, 0x3b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x22, 0xff, 0xff, 0xff, 0xff, 0x5c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2f, 0xef, 0xfd, 0xff, 0xff, 0xed, 0x00, 0x00, 0x00, 0x34, 0xff, 0xff, 0xff, 0xff, 0x4b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x45, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x45, + 0xb7, 0xfc, 0xff, 0xff, 0xfb, 0x1e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x4e, 0xfe, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x70, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x22, 0xff, 0xff, 0xff, 0xff, 0xe0, 0xcf, 0xcf, 0xcf, 0xcf, 0xda, 0xfd, 0xfc, 0xff, 0xff, 0xfd, 0x96, 0x00, 0x00, 0x00, 0x34, 0xff, 0xff, 0xff, 0xff, 0xdd, 0xcf, 0xcf, 0xcf, 0xab, 0x28, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x45, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfd, 0xff, 0x88, 0x00, + 0xe5, 0xff, 0xff, 0xfc, 0xab, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x8e, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0xab, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x22, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfd, 0xf6, 0x1d, 0x00, 0x00, 0x00, 0x34, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0xe1, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x45, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe4, 0x60, 0x00, 0x00, + 0xfa, 0xff, 0xff, 0xfe, 0x85, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0xc6, 0xfd, 0xff, 0xff, 0xfc, 0xdb, 0x0d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x22, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x8c, 0x00, 0x00, 0x00, 0x00, 0x34, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x1d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x45, 0xff, 0xff, 0xff, 0xff, 0x6e, 0xc4, 0xfc, 0xff, 0xff, 0xff, 0xa8, 0x27, 0x01, 0x00, 0x00, 0x00, + 0xe4, 0xff, 0xff, 0xfc, 0xab, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x39, 0xff, 0xff, 0xff, 0xff, 0x54, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x22, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfd, 0xf6, 0x1e, 0x00, 0x00, 0x00, 0x34, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0xe0, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x45, 0xff, 0xff, 0xff, 0xff, 0x3a, 0x30, 0xfd, 0xff, 0xff, 0xfe, 0xf5, 0x1a, 0x00, 0x00, 0x00, 0x00, + 0xb6, 0xfc, 0xff, 0xff, 0xfb, 0x1e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x32, 0xff, 0xff, 0xff, 0xff, 0x4d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x22, 0xff, 0xff, 0xff, 0xff, 0xd3, 0xbb, 0xbb, 0xbb, 0xbb, 0xcc, 0xfb, 0xfc, 0xff, 0xff, 0xfd, 0x97, 0x00, 0x00, 0x00, 0x34, 0xff, 0xff, 0xff, 0xff, 0xcf, 0xbb, 0xbb, 0xbb, 0xa2, 0x24, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x45, 0xff, 0xff, 0xff, 0xff, 0x3a, 0x00, 0x9a, 0xfe, 0xff, 0xff, 0xfd, 0xa0, 0x00, 0x00, 0x00, 0x00, + 0x5e, 0xff, 0xff, 0xff, 0xfd, 0x98, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x32, 0xff, 0xff, 0xff, 0xff, 0x4d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x22, 0xff, 0xff, 0xff, 0xff, 0x5c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2a, 0xed, 0xfd, 0xff, 0xff, 0xee, 0x00, 0x00, 0x00, 0x34, 0xff, 0xff, 0xff, 0xff, 0x4b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x45, 0xff, 0xff, 0xff, 0xff, 0x3a, 0x00, 0x16, 0xf2, 0xfe, 0xff, 0xff, 0xfe, 0x36, 0x00, 0x00, 0x00, + 0x08, 0xe7, 0xfd, 0xff, 0xff, 0xfb, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x32, 0xff, 0xff, 0xff, 0xff, 0x4d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x22, 0xff, 0xff, 0xff, 0xff, 0x5c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7b, 0xfe, 0xff, 0xff, 0xff, 0x13, 0x00, 0x00, 0x34, 0xff, 0xff, 0xff, 0xff, 0x4b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x45, 0xff, 0xff, 0xff, 0xff, 0x3a, 0x00, 0x00, 0x74, 0xff, 0xff, 0xff, 0xfc, 0xc8, 0x01, 0x00, 0x00, + 0x00, 0x71, 0xff, 0xff, 0xff, 0xfd, 0x97, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x32, 0xff, 0xff, 0xff, 0xff, 0x4d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x22, 0xff, 0xff, 0xff, 0xff, 0x5c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x5c, 0xff, 0xff, 0xff, 0xff, 0x23, 0x00, 0x00, 0x34, 0xff, 0xff, 0xff, 0xff, 0x4b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x45, 0xff, 0xff, 0xff, 0xff, 0x3a, 0x00, 0x00, 0x06, 0xdc, 0xfc, 0xff, 0xff, 0xff, 0x59, 0x00, 0x00, + 0x00, 0x08, 0xe8, 0xfd, 0xff, 0xff, 0xfb, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x32, 0xff, 0xff, 0xff, 0xff, 0x4d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x22, 0xff, 0xff, 0xff, 0xff, 0x5c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6f, 0xff, 0xff, 0xff, 0xff, 0x1a, 0x00, 0x00, 0x34, 0xff, 0xff, 0xff, 0xff, 0x4b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x45, 0xff, 0xff, 0xff, 0xff, 0x3a, 0x00, 0x00, 0x00, 0x4d, 0xff, 0xff, 0xff, 0xfd, 0xe6, 0x0b, 0x00, + 0x00, 0x00, 0x73, 0xff, 0xff, 0xff, 0xfd, 0x97, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x32, 0xff, 0xff, 0xff, 0xff, 0x4d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x22, 0xff, 0xff, 0xff, 0xff, 0x5c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0xd9, 0xfc, 0xff, 0xff, 0xf7, 0x03, 0x00, 0x00, 0x34, 0xff, 0xff, 0xff, 0xff, 0x4b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x45, 0xff, 0xff, 0xff, 0xff, 0x3a, 0x00, 0x00, 0x00, 0x00, 0xb9, 0xfc, 0xff, 0xff, 0xff, 0x80, 0x00, + 0x00, 0x00, 0x0a, 0xec, 0xfe, 0xff, 0xff, 0xfc, 0x97, 0x88, 0x88, 0x88, 0x88, 0x88, 0x75, 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x32, 0xff, 0xff, 0xff, 0xff, 0x4d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x22, 0xff, 0xff, 0xff, 0xff, 0xb3, 0x88, 0x88, 0x88, 0x88, 0x97, 0xe6, 0xfe, 0xff, 0xff, 0xfc, 0xaf, 0x00, 0x00, 0x00, 0x34, 0xff, 0xff, 0xff, 0xff, 0xab, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x65, 0x05, 0x00, 0x00, 0x00, 0x45, 0xff, 0xff, 0xff, 0xff, 0x3a, 0x00, 0x00, 0x00, 0x00, 0x29, 0xfb, 0xff, 0xff, 0xff, 0xf8, 0x1f, + 0x00, 0x00, 0x00, 0x75, 0xff, 0xff, 0xff, 0xff, 0xfb, 0xfc, 0xfc, 0xfc, 0xfc, 0xfc, 0xff, 0xd8, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x32, 0xff, 0xff, 0xff, 0xff, 0x4d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x22, 0xff, 0xff, 0xff, 0xfe, 0xfc, 0xfc, 0xfc, 0xfc, 0xfc, 0xfb, 0xfe, 0xff, 0xff, 0xff, 0xfe, 0x36, 0x00, 0x00, 0x00, 0x34, 0xff, 0xff, 0xff, 0xfd, 0xfc, 0xfc, 0xfc, 0xfc, 0xfc, 0xfc, 0xfc, 0xfc, 0xfc, 0xfc, 0xff, 0xa8, 0x00, 0x00, 0x00, 0x45, 0xff, 0xff, 0xff, 0xff, 0x3a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x93, 0xfe, 0xff, 0xff, 0xfd, 0xa0, + 0x00, 0x00, 0x00, 0x09, 0xdb, 0xfc, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x36, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0xff, 0xff, 0xff, 0xff, 0x4a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x22, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x84, 0x00, 0x00, 0x00, 0x00, 0x34, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x06, 0x00, 0x00, 0x43, 0xff, 0xff, 0xff, 0xff, 0x38, 0x00, 0x00, 0x00, 0x00, 0x00, 0x12, 0xf0, 0xfe, 0xff, 0xff, 0xef, + 0x00, 0x00, 0x00, 0x00, 0x2b, 0xe6, 0xfe, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0xfa, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0c, 0xf5, 0xfd, 0xfe, 0xfc, 0x1e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x22, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfd, 0xfd, 0x80, 0x01, 0x00, 0x00, 0x00, 0x00, 0x34, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0xe0, 0x00, 0x00, 0x00, 0x1a, 0xfb, 0xfe, 0xfe, 0xf7, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6c, 0xff, 0xff, 0xfc, 0xcf, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x15, 0x89, 0xdb, 0xfb, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe7, 0x5a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x4e, 0xe2, 0xe9, 0x61, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x22, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfb, 0xe1, 0x9a, 0x2c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x34, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0xd6, 0x38, 0x00, 0x00, 0x00, 0x00, 0x5f, 0xe9, 0xe4, 0x52, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x87, 0xed, 0xd0, 0x2e, + } +}; + diff --git a/include/bitmaps/gradient.h b/include/bitmaps/gradient.h new file mode 100644 index 0000000..7e122d7 --- /dev/null +++ b/include/bitmaps/gradient.h @@ -0,0 +1,71 @@ +// Image definition of: gradient.png +static PROGMEM bitmap_t gradient { + .width = 60, + .height = 60, + .depth = 8, + .pixel_align = 8, + .row_align = 1, + .data = (PROGMEM uint8_t[]) { + 0x07, 0x0b, 0x0e, 0x12, 0x15, 0x19, 0x1c, 0x20, 0x23, 0x26, 0x2a, 0x2e, 0x31, 0x34, 0x38, 0x3c, 0x3f, 0x43, 0x46, 0x49, 0x4d, 0x50, 0x54, 0x58, 0x5b, 0x5f, 0x63, 0x66, 0x6a, 0x6d, 0x71, 0x75, 0x78, 0x7c, 0x7f, 0x82, 0x86, 0x8a, 0x8d, 0x91, 0x94, 0x98, 0x9b, 0x9f, 0xa2, 0xa5, 0xa9, 0xad, 0xb0, 0xb4, 0xb7, 0xbb, 0xbe, 0xc1, 0xc5, 0xc9, 0xcc, 0xd0, 0xd3, 0xd7, + 0x07, 0x0b, 0x0e, 0x11, 0x15, 0x18, 0x1c, 0x20, 0x23, 0x27, 0x2a, 0x2e, 0x31, 0x34, 0x38, 0x3c, 0x3f, 0x43, 0x46, 0x4a, 0x4d, 0x50, 0x55, 0x58, 0x5b, 0x5e, 0x62, 0x66, 0x6a, 0x6d, 0x71, 0x75, 0x78, 0x7c, 0x7f, 0x82, 0x86, 0x8a, 0x8d, 0x91, 0x94, 0x98, 0x9b, 0x9f, 0xa2, 0xa6, 0xa9, 0xad, 0xb0, 0xb4, 0xb7, 0xbb, 0xbe, 0xc2, 0xc6, 0xc9, 0xcc, 0xd0, 0xd3, 0xd7, + 0x07, 0x0b, 0x0e, 0x12, 0x15, 0x19, 0x1c, 0x20, 0x23, 0x27, 0x2a, 0x2d, 0x31, 0x34, 0x38, 0x3c, 0x3f, 0x43, 0x46, 0x49, 0x4d, 0x50, 0x54, 0x57, 0x5b, 0x5e, 0x62, 0x66, 0x69, 0x6d, 0x70, 0x74, 0x78, 0x7c, 0x7f, 0x82, 0x86, 0x89, 0x8d, 0x91, 0x94, 0x98, 0x9c, 0x9f, 0xa2, 0xa6, 0xa9, 0xac, 0xb0, 0xb3, 0xb7, 0xba, 0xbe, 0xc2, 0xc5, 0xc9, 0xcc, 0xd0, 0xd3, 0xd7, + 0x07, 0x0a, 0x0e, 0x12, 0x15, 0x19, 0x1c, 0x20, 0x23, 0x27, 0x2a, 0x2e, 0x31, 0x35, 0x38, 0x3b, 0x3f, 0x42, 0x46, 0x49, 0x4d, 0x51, 0x54, 0x57, 0x5b, 0x5f, 0x62, 0x66, 0x69, 0x6d, 0x70, 0x74, 0x77, 0x7b, 0x7f, 0x83, 0x86, 0x8a, 0x8d, 0x90, 0x94, 0x97, 0x9b, 0x9f, 0xa2, 0xa6, 0xa9, 0xad, 0xb0, 0xb3, 0xb7, 0xbb, 0xbe, 0xc1, 0xc5, 0xc8, 0xcc, 0xd0, 0xd3, 0xd7, + 0x06, 0x0a, 0x0d, 0x12, 0x15, 0x18, 0x1c, 0x1f, 0x23, 0x27, 0x2a, 0x2d, 0x31, 0x35, 0x38, 0x3c, 0x3f, 0x43, 0x46, 0x4a, 0x4d, 0x51, 0x54, 0x58, 0x5b, 0x5f, 0x62, 0x66, 0x69, 0x6d, 0x70, 0x74, 0x77, 0x7b, 0x7e, 0x82, 0x85, 0x8a, 0x8d, 0x90, 0x94, 0x97, 0x9b, 0x9f, 0xa2, 0xa6, 0xa9, 0xad, 0xb0, 0xb4, 0xb7, 0xba, 0xbe, 0xc2, 0xc5, 0xc8, 0xcc, 0xd0, 0xd3, 0xd7, + 0x06, 0x0a, 0x0d, 0x11, 0x14, 0x18, 0x1c, 0x1f, 0x23, 0x26, 0x2a, 0x2e, 0x31, 0x35, 0x38, 0x3b, 0x3f, 0x43, 0x46, 0x4a, 0x4d, 0x51, 0x54, 0x58, 0x5b, 0x5f, 0x62, 0x65, 0x69, 0x6c, 0x70, 0x74, 0x77, 0x7b, 0x7e, 0x81, 0x85, 0x88, 0x8d, 0x91, 0x94, 0x98, 0x9b, 0x9e, 0xa2, 0xa6, 0xa9, 0xad, 0xb0, 0xb4, 0xb7, 0xbb, 0xbe, 0xc2, 0xc5, 0xc8, 0xcc, 0xcf, 0xd3, 0xd7, + 0x06, 0x0a, 0x0d, 0x10, 0x14, 0x18, 0x1b, 0x1f, 0x23, 0x26, 0x2a, 0x2e, 0x31, 0x35, 0x38, 0x3c, 0x3f, 0x42, 0x46, 0x4a, 0x4d, 0x50, 0x54, 0x58, 0x5b, 0x5f, 0x62, 0x65, 0x69, 0x6c, 0x70, 0x73, 0x77, 0x7a, 0x7e, 0x81, 0x85, 0x89, 0x8c, 0x8f, 0x93, 0x97, 0x9b, 0x9f, 0xa2, 0xa5, 0xa9, 0xac, 0xb0, 0xb4, 0xb7, 0xbb, 0xbe, 0xc1, 0xc5, 0xc9, 0xcc, 0xd0, 0xd3, 0xd7, + 0x06, 0x0a, 0x0d, 0x11, 0x14, 0x18, 0x1b, 0x1f, 0x22, 0x26, 0x29, 0x2e, 0x31, 0x35, 0x38, 0x3c, 0x3f, 0x43, 0x46, 0x4a, 0x4d, 0x50, 0x54, 0x58, 0x5b, 0x5e, 0x62, 0x66, 0x69, 0x6d, 0x70, 0x74, 0x77, 0x7b, 0x7e, 0x81, 0x85, 0x89, 0x8c, 0x90, 0x93, 0x97, 0x9a, 0x9e, 0xa1, 0xa6, 0xa9, 0xad, 0xb0, 0xb4, 0xb7, 0xbb, 0xbe, 0xc2, 0xc5, 0xc9, 0xcc, 0xd0, 0xd3, 0xd7, + 0x06, 0x0a, 0x0d, 0x11, 0x14, 0x18, 0x1b, 0x1f, 0x22, 0x26, 0x29, 0x2d, 0x30, 0x34, 0x38, 0x3b, 0x3f, 0x43, 0x46, 0x49, 0x4d, 0x51, 0x54, 0x58, 0x5c, 0x5e, 0x62, 0x66, 0x69, 0x6d, 0x70, 0x74, 0x77, 0x7b, 0x7e, 0x82, 0x85, 0x89, 0x8c, 0x8f, 0x93, 0x97, 0x9a, 0x9e, 0xa2, 0xa5, 0xa8, 0xad, 0xb1, 0xb4, 0xb7, 0xbb, 0xbe, 0xc2, 0xc5, 0xc9, 0xcc, 0xd0, 0xd3, 0xd7, + 0x06, 0x0a, 0x0d, 0x11, 0x14, 0x17, 0x1b, 0x1f, 0x22, 0x26, 0x29, 0x2d, 0x30, 0x34, 0x38, 0x3a, 0x3e, 0x43, 0x46, 0x4a, 0x4d, 0x51, 0x54, 0x58, 0x5b, 0x5f, 0x62, 0x66, 0x69, 0x6d, 0x70, 0x74, 0x77, 0x7b, 0x7e, 0x82, 0x85, 0x89, 0x8c, 0x8f, 0x93, 0x97, 0x9a, 0x9e, 0xa1, 0xa4, 0xa8, 0xab, 0xaf, 0xb2, 0xb7, 0xbb, 0xbe, 0xc1, 0xc5, 0xc9, 0xcc, 0xd0, 0xd3, 0xd7, + 0x06, 0x0a, 0x0d, 0x10, 0x14, 0x17, 0x1b, 0x1f, 0x22, 0x26, 0x29, 0x2c, 0x30, 0x34, 0x37, 0x3a, 0x3f, 0x42, 0x45, 0x49, 0x4d, 0x50, 0x54, 0x58, 0x5b, 0x5f, 0x62, 0x66, 0x69, 0x6c, 0x70, 0x74, 0x77, 0x7a, 0x7e, 0x82, 0x85, 0x89, 0x8c, 0x90, 0x93, 0x97, 0x9a, 0x9e, 0xa1, 0xa5, 0xa8, 0xac, 0xaf, 0xb3, 0xb6, 0xba, 0xbd, 0xc1, 0xc5, 0xc9, 0xcc, 0xd0, 0xd3, 0xd7, + 0x06, 0x0a, 0x0d, 0x10, 0x14, 0x18, 0x1b, 0x1f, 0x22, 0x25, 0x29, 0x2d, 0x30, 0x34, 0x37, 0x3b, 0x3e, 0x41, 0x46, 0x49, 0x4c, 0x50, 0x54, 0x57, 0x5b, 0x5f, 0x62, 0x65, 0x69, 0x6c, 0x70, 0x74, 0x77, 0x7b, 0x7e, 0x82, 0x85, 0x89, 0x8c, 0x8f, 0x93, 0x97, 0x9a, 0x9e, 0xa1, 0xa5, 0xa8, 0xac, 0xaf, 0xb3, 0xb6, 0xba, 0xbd, 0xc1, 0xc4, 0xc8, 0xcc, 0xd0, 0xd3, 0xd7, + 0x06, 0x0a, 0x0d, 0x11, 0x14, 0x18, 0x1b, 0x1f, 0x22, 0x26, 0x29, 0x2d, 0x30, 0x34, 0x37, 0x3a, 0x3e, 0x42, 0x45, 0x49, 0x4c, 0x50, 0x53, 0x56, 0x5a, 0x5f, 0x62, 0x66, 0x69, 0x6c, 0x70, 0x74, 0x77, 0x7b, 0x7e, 0x82, 0x85, 0x89, 0x8c, 0x90, 0x93, 0x97, 0x9a, 0x9e, 0xa1, 0xa4, 0xa8, 0xab, 0xaf, 0xb3, 0xb6, 0xba, 0xbe, 0xc1, 0xc4, 0xc8, 0xcb, 0xcf, 0xd3, 0xd7, + 0x06, 0x0a, 0x0d, 0x11, 0x14, 0x18, 0x1b, 0x1f, 0x22, 0x25, 0x2a, 0x2d, 0x30, 0x34, 0x37, 0x3a, 0x3e, 0x42, 0x45, 0x49, 0x4c, 0x50, 0x53, 0x57, 0x5a, 0x5e, 0x61, 0x65, 0x69, 0x6d, 0x70, 0x74, 0x77, 0x7a, 0x7e, 0x81, 0x85, 0x89, 0x8c, 0x90, 0x93, 0x96, 0x9a, 0x9e, 0xa1, 0xa5, 0xa8, 0xac, 0xaf, 0xb3, 0xb6, 0xba, 0xbd, 0xc1, 0xc4, 0xc7, 0xcb, 0xcf, 0xd2, 0xd6, + 0x06, 0x0a, 0x0d, 0x10, 0x14, 0x18, 0x1b, 0x1e, 0x22, 0x26, 0x29, 0x2c, 0x30, 0x34, 0x38, 0x3a, 0x3e, 0x42, 0x45, 0x49, 0x4c, 0x50, 0x54, 0x57, 0x5a, 0x5d, 0x61, 0x65, 0x68, 0x6c, 0x70, 0x74, 0x77, 0x7b, 0x7e, 0x81, 0x86, 0x89, 0x8c, 0x90, 0x93, 0x97, 0x9a, 0x9e, 0xa1, 0xa5, 0xa9, 0xac, 0xb0, 0xb3, 0xb6, 0xba, 0xbe, 0xc1, 0xc4, 0xc7, 0xcc, 0xcf, 0xd2, 0xd6, + 0x06, 0x0a, 0x0d, 0x10, 0x14, 0x18, 0x1b, 0x1f, 0x22, 0x26, 0x29, 0x2d, 0x30, 0x34, 0x37, 0x3b, 0x3e, 0x42, 0x45, 0x49, 0x4c, 0x50, 0x54, 0x57, 0x5a, 0x5e, 0x61, 0x65, 0x68, 0x6c, 0x6f, 0x73, 0x77, 0x7b, 0x7e, 0x82, 0x85, 0x89, 0x8c, 0x90, 0x94, 0x97, 0x9a, 0x9e, 0xa1, 0xa5, 0xa8, 0xac, 0xaf, 0xb3, 0xb6, 0xba, 0xbd, 0xc1, 0xc4, 0xc8, 0xcb, 0xcf, 0xd2, 0xd6, + 0x05, 0x09, 0x0d, 0x10, 0x14, 0x17, 0x1b, 0x1f, 0x22, 0x26, 0x2a, 0x2c, 0x30, 0x34, 0x37, 0x3b, 0x3f, 0x42, 0x45, 0x49, 0x4c, 0x50, 0x53, 0x56, 0x5a, 0x5e, 0x61, 0x65, 0x68, 0x6c, 0x6f, 0x73, 0x76, 0x7a, 0x7d, 0x81, 0x85, 0x89, 0x8c, 0x8f, 0x93, 0x96, 0x9a, 0x9e, 0xa1, 0xa5, 0xa8, 0xac, 0xaf, 0xb3, 0xb7, 0xba, 0xbd, 0xc1, 0xc4, 0xc8, 0xcb, 0xcf, 0xd2, 0xd6, + 0x05, 0x09, 0x0c, 0x10, 0x13, 0x18, 0x1b, 0x1f, 0x22, 0x26, 0x29, 0x2d, 0x30, 0x34, 0x38, 0x3b, 0x3e, 0x42, 0x45, 0x48, 0x4c, 0x50, 0x54, 0x57, 0x5a, 0x5e, 0x61, 0x65, 0x68, 0x6c, 0x6f, 0x73, 0x77, 0x7a, 0x7d, 0x81, 0x85, 0x88, 0x8c, 0x8f, 0x93, 0x97, 0x9a, 0x9e, 0xa1, 0xa4, 0xa8, 0xac, 0xaf, 0xb3, 0xb6, 0xba, 0xbd, 0xc1, 0xc4, 0xc8, 0xcb, 0xce, 0xd2, 0xd6, + 0x05, 0x09, 0x0c, 0x0f, 0x13, 0x17, 0x1b, 0x1e, 0x22, 0x26, 0x29, 0x2d, 0x30, 0x34, 0x37, 0x3b, 0x3f, 0x41, 0x45, 0x49, 0x4d, 0x50, 0x53, 0x57, 0x5a, 0x5e, 0x62, 0x65, 0x69, 0x6c, 0x70, 0x73, 0x76, 0x79, 0x7e, 0x81, 0x84, 0x88, 0x8b, 0x8f, 0x92, 0x97, 0x9a, 0x9e, 0xa1, 0xa5, 0xa8, 0xab, 0xaf, 0xb3, 0xb6, 0xba, 0xbd, 0xc1, 0xc4, 0xc8, 0xcb, 0xce, 0xd2, 0xd6, + 0x05, 0x09, 0x0c, 0x0f, 0x13, 0x17, 0x1a, 0x1e, 0x21, 0x25, 0x29, 0x2d, 0x30, 0x34, 0x37, 0x3b, 0x3e, 0x41, 0x45, 0x49, 0x4c, 0x50, 0x53, 0x57, 0x5a, 0x5e, 0x61, 0x65, 0x68, 0x6c, 0x6f, 0x73, 0x76, 0x7a, 0x7d, 0x81, 0x84, 0x88, 0x8b, 0x8f, 0x93, 0x96, 0x99, 0x9d, 0xa1, 0xa5, 0xa9, 0xab, 0xb0, 0xb3, 0xb6, 0xb9, 0xbe, 0xc1, 0xc4, 0xc8, 0xcb, 0xcf, 0xd3, 0xd6, + 0x05, 0x09, 0x0c, 0x10, 0x13, 0x17, 0x1a, 0x1e, 0x21, 0x25, 0x28, 0x2c, 0x2f, 0x33, 0x37, 0x3b, 0x3e, 0x42, 0x45, 0x49, 0x4c, 0x50, 0x53, 0x57, 0x5a, 0x5e, 0x61, 0x65, 0x69, 0x6b, 0x6f, 0x73, 0x76, 0x7a, 0x7d, 0x81, 0x84, 0x88, 0x8b, 0x8f, 0x92, 0x96, 0x99, 0x9d, 0xa0, 0xa4, 0xa7, 0xac, 0xb0, 0xb3, 0xb6, 0xba, 0xbd, 0xc1, 0xc4, 0xc8, 0xcb, 0xcf, 0xd2, 0xd6, + 0x05, 0x09, 0x0c, 0x10, 0x14, 0x17, 0x1a, 0x1e, 0x22, 0x25, 0x28, 0x2b, 0x2f, 0x33, 0x36, 0x3a, 0x3e, 0x42, 0x45, 0x49, 0x4c, 0x50, 0x53, 0x56, 0x5a, 0x5e, 0x61, 0x65, 0x69, 0x6c, 0x6f, 0x73, 0x76, 0x7a, 0x7e, 0x81, 0x84, 0x88, 0x8c, 0x8f, 0x93, 0x96, 0x99, 0x9d, 0xa1, 0xa4, 0xa8, 0xab, 0xaf, 0xb3, 0xb6, 0xb9, 0xbd, 0xc1, 0xc4, 0xc8, 0xcb, 0xcf, 0xd3, 0xd6, + 0x05, 0x09, 0x0c, 0x10, 0x13, 0x17, 0x1a, 0x1e, 0x21, 0x25, 0x28, 0x2c, 0x2f, 0x32, 0x37, 0x3a, 0x3e, 0x41, 0x44, 0x49, 0x4c, 0x50, 0x54, 0x57, 0x5a, 0x5e, 0x61, 0x65, 0x69, 0x6c, 0x6f, 0x73, 0x76, 0x7a, 0x7d, 0x81, 0x84, 0x88, 0x8b, 0x8f, 0x92, 0x96, 0x99, 0x9d, 0xa1, 0xa4, 0xa7, 0xab, 0xae, 0xb2, 0xb5, 0xb8, 0xbd, 0xc1, 0xc4, 0xc8, 0xcc, 0xcf, 0xd2, 0xd6, + 0x06, 0x09, 0x0c, 0x10, 0x13, 0x16, 0x1b, 0x1e, 0x21, 0x25, 0x29, 0x2c, 0x30, 0x33, 0x37, 0x3a, 0x3e, 0x41, 0x44, 0x48, 0x4b, 0x50, 0x53, 0x56, 0x5a, 0x5e, 0x61, 0x65, 0x68, 0x6c, 0x6f, 0x73, 0x76, 0x7a, 0x7e, 0x81, 0x84, 0x88, 0x8b, 0x8f, 0x92, 0x96, 0x99, 0x9d, 0xa0, 0xa4, 0xa7, 0xab, 0xae, 0xb2, 0xb6, 0xb9, 0xbc, 0xc0, 0xc3, 0xc7, 0xcb, 0xcf, 0xd2, 0xd6, + 0x05, 0x09, 0x0c, 0x10, 0x13, 0x16, 0x1a, 0x1e, 0x22, 0x25, 0x29, 0x2c, 0x2f, 0x32, 0x36, 0x39, 0x3d, 0x41, 0x45, 0x48, 0x4b, 0x4f, 0x52, 0x56, 0x5a, 0x5d, 0x61, 0x65, 0x68, 0x6c, 0x6f, 0x72, 0x76, 0x79, 0x7d, 0x81, 0x84, 0x88, 0x8b, 0x8f, 0x93, 0x96, 0x99, 0x9d, 0xa1, 0xa4, 0xa7, 0xab, 0xaf, 0xb2, 0xb5, 0xb9, 0xbd, 0xc0, 0xc3, 0xc7, 0xca, 0xcf, 0xd2, 0xd6, + 0x05, 0x09, 0x0c, 0x10, 0x13, 0x17, 0x1a, 0x1e, 0x21, 0x25, 0x28, 0x2c, 0x2f, 0x33, 0x36, 0x3a, 0x3d, 0x41, 0x44, 0x48, 0x4b, 0x4f, 0x52, 0x56, 0x5a, 0x5d, 0x61, 0x65, 0x69, 0x6c, 0x70, 0x73, 0x77, 0x7a, 0x7d, 0x81, 0x84, 0x88, 0x8b, 0x8f, 0x92, 0x96, 0x99, 0x9d, 0xa0, 0xa4, 0xa8, 0xab, 0xae, 0xb2, 0xb5, 0xb9, 0xbc, 0xc0, 0xc3, 0xc7, 0xca, 0xce, 0xd1, 0xd5, + 0x05, 0x09, 0x0c, 0x10, 0x13, 0x17, 0x1a, 0x1d, 0x21, 0x25, 0x29, 0x2c, 0x2f, 0x33, 0x36, 0x3a, 0x3d, 0x41, 0x44, 0x48, 0x4b, 0x4f, 0x52, 0x56, 0x59, 0x5d, 0x61, 0x64, 0x68, 0x6b, 0x6f, 0x73, 0x77, 0x79, 0x7d, 0x81, 0x84, 0x88, 0x8b, 0x8e, 0x93, 0x96, 0x99, 0x9d, 0xa0, 0xa4, 0xa7, 0xab, 0xaf, 0xb1, 0xb6, 0xb9, 0xbd, 0xc0, 0xc3, 0xc7, 0xca, 0xce, 0xd1, 0xd5, + 0x05, 0x09, 0x0c, 0x10, 0x13, 0x17, 0x1b, 0x1e, 0x21, 0x25, 0x28, 0x2c, 0x2f, 0x33, 0x37, 0x3a, 0x3d, 0x41, 0x44, 0x48, 0x4b, 0x4f, 0x52, 0x56, 0x59, 0x5d, 0x60, 0x64, 0x67, 0x6b, 0x6e, 0x72, 0x77, 0x79, 0x7e, 0x80, 0x84, 0x88, 0x8b, 0x8f, 0x93, 0x96, 0x99, 0x9d, 0xa0, 0xa4, 0xa7, 0xab, 0xaf, 0xb2, 0xb5, 0xb9, 0xbc, 0xc0, 0xc3, 0xc7, 0xcb, 0xce, 0xd1, 0xd5, + 0x04, 0x08, 0x0c, 0x10, 0x13, 0x17, 0x1a, 0x1e, 0x21, 0x25, 0x28, 0x2c, 0x2f, 0x33, 0x36, 0x3a, 0x3e, 0x41, 0x44, 0x48, 0x4b, 0x4f, 0x52, 0x56, 0x59, 0x5d, 0x60, 0x64, 0x67, 0x6b, 0x6e, 0x72, 0x75, 0x79, 0x7c, 0x81, 0x84, 0x88, 0x8c, 0x8f, 0x92, 0x96, 0x99, 0x9d, 0xa0, 0xa4, 0xa7, 0xab, 0xaf, 0xb2, 0xb5, 0xb9, 0xbd, 0xc0, 0xc3, 0xc7, 0xca, 0xce, 0xd1, 0xd5, + 0x05, 0x08, 0x0b, 0x0f, 0x13, 0x16, 0x1a, 0x1e, 0x21, 0x25, 0x28, 0x2b, 0x30, 0x33, 0x37, 0x3a, 0x3d, 0x40, 0x45, 0x48, 0x4b, 0x4f, 0x52, 0x56, 0x59, 0x5d, 0x60, 0x64, 0x68, 0x6b, 0x6e, 0x72, 0x76, 0x79, 0x7c, 0x80, 0x84, 0x87, 0x8b, 0x8f, 0x92, 0x96, 0x99, 0x9d, 0xa1, 0xa4, 0xa7, 0xab, 0xae, 0xb2, 0xb5, 0xb9, 0xbc, 0xc0, 0xc4, 0xc7, 0xcb, 0xce, 0xd1, 0xd5, + 0x04, 0x08, 0x0c, 0x0e, 0x12, 0x16, 0x19, 0x1e, 0x21, 0x25, 0x28, 0x2c, 0x2f, 0x33, 0x36, 0x39, 0x3e, 0x41, 0x44, 0x48, 0x4c, 0x4f, 0x52, 0x56, 0x59, 0x5d, 0x61, 0x64, 0x67, 0x6b, 0x6f, 0x72, 0x76, 0x79, 0x7c, 0x80, 0x84, 0x87, 0x8a, 0x8e, 0x92, 0x96, 0x99, 0x9d, 0xa0, 0xa3, 0xa7, 0xab, 0xaf, 0xb2, 0xb5, 0xb9, 0xbc, 0xc0, 0xc3, 0xc7, 0xca, 0xce, 0xd2, 0xd5, + 0x04, 0x08, 0x0c, 0x0f, 0x13, 0x16, 0x19, 0x1d, 0x20, 0x25, 0x28, 0x2c, 0x30, 0x33, 0x36, 0x3a, 0x3d, 0x41, 0x45, 0x48, 0x4b, 0x4f, 0x53, 0x56, 0x59, 0x5d, 0x61, 0x64, 0x67, 0x6b, 0x6f, 0x72, 0x75, 0x79, 0x7d, 0x80, 0x83, 0x87, 0x8a, 0x8e, 0x91, 0x95, 0x99, 0x9d, 0xa0, 0xa4, 0xa8, 0xab, 0xaf, 0xb2, 0xb5, 0xb9, 0xbc, 0xc0, 0xc3, 0xc7, 0xcb, 0xce, 0xd2, 0xd5, + 0x04, 0x08, 0x0b, 0x0f, 0x12, 0x16, 0x19, 0x1d, 0x21, 0x24, 0x27, 0x2b, 0x2f, 0x33, 0x36, 0x3a, 0x3d, 0x41, 0x44, 0x48, 0x4b, 0x4f, 0x52, 0x56, 0x59, 0x5d, 0x60, 0x64, 0x67, 0x6b, 0x6e, 0x72, 0x76, 0x79, 0x7d, 0x80, 0x83, 0x87, 0x8b, 0x8e, 0x92, 0x95, 0x98, 0x9c, 0xa0, 0xa3, 0xa7, 0xab, 0xae, 0xb2, 0xb5, 0xb9, 0xbc, 0xc0, 0xc4, 0xc7, 0xcb, 0xce, 0xd1, 0xd4, + 0x04, 0x08, 0x0b, 0x0f, 0x12, 0x16, 0x1a, 0x1d, 0x20, 0x24, 0x27, 0x2b, 0x2e, 0x32, 0x35, 0x3a, 0x3d, 0x41, 0x45, 0x48, 0x4b, 0x4f, 0x52, 0x56, 0x59, 0x5d, 0x60, 0x64, 0x68, 0x6b, 0x6f, 0x72, 0x75, 0x79, 0x7c, 0x80, 0x83, 0x87, 0x8a, 0x8e, 0x92, 0x95, 0x98, 0x9c, 0x9f, 0xa3, 0xa7, 0xaa, 0xaf, 0xb2, 0xb5, 0xb9, 0xbc, 0xc0, 0xc3, 0xc7, 0xca, 0xce, 0xd2, 0xd5, + 0x04, 0x08, 0x0b, 0x0f, 0x12, 0x16, 0x19, 0x1d, 0x21, 0x24, 0x28, 0x2b, 0x2e, 0x32, 0x35, 0x39, 0x3d, 0x40, 0x45, 0x48, 0x4b, 0x4f, 0x52, 0x55, 0x59, 0x5d, 0x61, 0x64, 0x67, 0x6b, 0x6e, 0x72, 0x75, 0x79, 0x7c, 0x80, 0x83, 0x87, 0x8b, 0x8e, 0x92, 0x95, 0x99, 0x9c, 0x9f, 0xa3, 0xa7, 0xaa, 0xad, 0xb1, 0xb5, 0xb9, 0xbd, 0xc0, 0xc4, 0xc7, 0xca, 0xce, 0xd1, 0xd5, + 0x04, 0x08, 0x0c, 0x0f, 0x12, 0x16, 0x1a, 0x1d, 0x21, 0x24, 0x28, 0x2b, 0x2e, 0x32, 0x35, 0x39, 0x3c, 0x40, 0x44, 0x47, 0x4c, 0x4f, 0x52, 0x56, 0x59, 0x5d, 0x61, 0x64, 0x68, 0x6b, 0x6f, 0x72, 0x75, 0x79, 0x7d, 0x80, 0x84, 0x87, 0x8a, 0x8e, 0x91, 0x95, 0x99, 0x9c, 0xa0, 0xa3, 0xa6, 0xaa, 0xad, 0xb1, 0xb4, 0xb8, 0xbb, 0xbf, 0xc4, 0xc7, 0xca, 0xce, 0xd1, 0xd5, + 0x04, 0x08, 0x0b, 0x0f, 0x12, 0x16, 0x19, 0x1d, 0x20, 0x24, 0x27, 0x2b, 0x2e, 0x32, 0x36, 0x39, 0x3d, 0x40, 0x43, 0x47, 0x4a, 0x4e, 0x52, 0x56, 0x59, 0x5d, 0x60, 0x64, 0x68, 0x6b, 0x6e, 0x72, 0x75, 0x79, 0x7c, 0x80, 0x83, 0x87, 0x8b, 0x8e, 0x92, 0x95, 0x98, 0x9c, 0xa0, 0xa3, 0xa7, 0xaa, 0xae, 0xb1, 0xb5, 0xb8, 0xbc, 0xbf, 0xc2, 0xc6, 0xca, 0xce, 0xd1, 0xd5, + 0x04, 0x08, 0x0b, 0x0f, 0x13, 0x16, 0x19, 0x1d, 0x21, 0x24, 0x28, 0x2b, 0x2f, 0x32, 0x35, 0x39, 0x3d, 0x40, 0x43, 0x47, 0x4b, 0x4e, 0x52, 0x55, 0x58, 0x5c, 0x60, 0x64, 0x67, 0x6b, 0x6e, 0x72, 0x75, 0x79, 0x7d, 0x80, 0x83, 0x87, 0x8a, 0x8e, 0x92, 0x95, 0x99, 0x9c, 0xa0, 0xa3, 0xa6, 0xaa, 0xad, 0xb1, 0xb5, 0xb8, 0xbb, 0xbf, 0xc3, 0xc6, 0xc9, 0xcd, 0xd1, 0xd5, + 0x04, 0x08, 0x0b, 0x0f, 0x12, 0x16, 0x1a, 0x1d, 0x21, 0x24, 0x28, 0x2b, 0x2e, 0x32, 0x35, 0x39, 0x3c, 0x40, 0x44, 0x47, 0x4b, 0x4e, 0x52, 0x55, 0x59, 0x5c, 0x60, 0x63, 0x66, 0x6b, 0x6e, 0x72, 0x75, 0x79, 0x7c, 0x80, 0x83, 0x87, 0x8a, 0x8e, 0x91, 0x95, 0x99, 0x9c, 0x9f, 0xa3, 0xa7, 0xaa, 0xad, 0xb1, 0xb5, 0xb8, 0xbc, 0xbf, 0xc2, 0xc6, 0xc9, 0xcd, 0xd1, 0xd4, + 0x04, 0x08, 0x0b, 0x0f, 0x12, 0x16, 0x19, 0x1d, 0x20, 0x24, 0x27, 0x2b, 0x2e, 0x32, 0x36, 0x39, 0x3c, 0x40, 0x43, 0x47, 0x4a, 0x4e, 0x52, 0x55, 0x58, 0x5c, 0x60, 0x63, 0x67, 0x6a, 0x6e, 0x72, 0x75, 0x79, 0x7d, 0x80, 0x84, 0x87, 0x8b, 0x8e, 0x92, 0x95, 0x99, 0x9c, 0x9f, 0xa3, 0xa6, 0xaa, 0xae, 0xb1, 0xb4, 0xb8, 0xbb, 0xbf, 0xc2, 0xc6, 0xca, 0xcd, 0xd1, 0xd4, + 0x04, 0x08, 0x0b, 0x0f, 0x12, 0x16, 0x19, 0x1d, 0x20, 0x24, 0x27, 0x2b, 0x2e, 0x32, 0x35, 0x39, 0x3d, 0x40, 0x43, 0x47, 0x4a, 0x4e, 0x52, 0x55, 0x59, 0x5c, 0x5f, 0x63, 0x67, 0x6a, 0x6e, 0x71, 0x74, 0x78, 0x7c, 0x80, 0x84, 0x87, 0x8b, 0x8e, 0x91, 0x95, 0x98, 0x9c, 0x9f, 0xa3, 0xa7, 0xaa, 0xad, 0xb1, 0xb4, 0xb8, 0xbb, 0xbf, 0xc3, 0xc6, 0xca, 0xcd, 0xd0, 0xd4, + 0x03, 0x07, 0x0a, 0x0f, 0x12, 0x16, 0x19, 0x1d, 0x20, 0x24, 0x28, 0x2b, 0x2e, 0x32, 0x36, 0x39, 0x3c, 0x40, 0x43, 0x47, 0x4b, 0x4e, 0x51, 0x55, 0x58, 0x5c, 0x60, 0x63, 0x67, 0x6a, 0x6e, 0x71, 0x75, 0x78, 0x7c, 0x7f, 0x82, 0x87, 0x8b, 0x8e, 0x91, 0x95, 0x99, 0x9c, 0xa0, 0xa3, 0xa6, 0xaa, 0xae, 0xb1, 0xb4, 0xb8, 0xbc, 0xbf, 0xc3, 0xc6, 0xca, 0xcd, 0xd1, 0xd4, + 0x04, 0x07, 0x0b, 0x0e, 0x12, 0x15, 0x19, 0x1d, 0x21, 0x24, 0x27, 0x2b, 0x2f, 0x32, 0x35, 0x39, 0x3c, 0x40, 0x43, 0x47, 0x4b, 0x4e, 0x52, 0x55, 0x59, 0x5c, 0x60, 0x63, 0x66, 0x6a, 0x6d, 0x71, 0x74, 0x78, 0x7b, 0x7f, 0x82, 0x86, 0x8a, 0x8e, 0x92, 0x95, 0x99, 0x9c, 0x9f, 0xa3, 0xa7, 0xaa, 0xae, 0xb1, 0xb4, 0xb8, 0xbc, 0xbf, 0xc3, 0xc6, 0xca, 0xcd, 0xd1, 0xd4, + 0x03, 0x07, 0x0b, 0x0e, 0x12, 0x15, 0x18, 0x1c, 0x1f, 0x24, 0x28, 0x2b, 0x2e, 0x32, 0x35, 0x39, 0x3d, 0x40, 0x44, 0x47, 0x4a, 0x4e, 0x52, 0x55, 0x58, 0x5c, 0x5f, 0x63, 0x67, 0x6a, 0x6d, 0x71, 0x74, 0x78, 0x7b, 0x7f, 0x83, 0x86, 0x8a, 0x8d, 0x90, 0x94, 0x99, 0x9c, 0xa0, 0xa3, 0xa6, 0xaa, 0xad, 0xb1, 0xb4, 0xb8, 0xbb, 0xbf, 0xc3, 0xc6, 0xca, 0xcd, 0xd0, 0xd4, + 0x04, 0x07, 0x0b, 0x0e, 0x11, 0x15, 0x18, 0x1c, 0x20, 0x23, 0x27, 0x2b, 0x2f, 0x32, 0x35, 0x39, 0x3c, 0x40, 0x44, 0x47, 0x4a, 0x4e, 0x52, 0x55, 0x59, 0x5c, 0x5f, 0x63, 0x67, 0x6a, 0x6d, 0x71, 0x75, 0x78, 0x7b, 0x7f, 0x83, 0x86, 0x8a, 0x8d, 0x91, 0x94, 0x98, 0x9b, 0x9f, 0xa3, 0xa7, 0xaa, 0xae, 0xb1, 0xb5, 0xb8, 0xbb, 0xbf, 0xc3, 0xc6, 0xc9, 0xcd, 0xd1, 0xd4, + 0x04, 0x07, 0x0a, 0x0e, 0x11, 0x15, 0x19, 0x1c, 0x20, 0x23, 0x26, 0x2a, 0x2e, 0x31, 0x35, 0x39, 0x3d, 0x40, 0x43, 0x47, 0x4b, 0x4e, 0x52, 0x55, 0x59, 0x5c, 0x5f, 0x63, 0x67, 0x6a, 0x6e, 0x71, 0x74, 0x78, 0x7b, 0x7f, 0x82, 0x86, 0x89, 0x8d, 0x91, 0x94, 0x98, 0x9b, 0x9e, 0xa2, 0xa5, 0xa9, 0xae, 0xb1, 0xb5, 0xb8, 0xbc, 0xbf, 0xc3, 0xc6, 0xc9, 0xcd, 0xd1, 0xd4, + 0x04, 0x07, 0x0b, 0x0e, 0x11, 0x15, 0x19, 0x1c, 0x1f, 0x23, 0x26, 0x2a, 0x2e, 0x31, 0x35, 0x38, 0x3c, 0x40, 0x43, 0x47, 0x4a, 0x4e, 0x52, 0x55, 0x58, 0x5c, 0x60, 0x63, 0x66, 0x6a, 0x6d, 0x71, 0x74, 0x78, 0x7c, 0x7f, 0x83, 0x86, 0x89, 0x8d, 0x90, 0x94, 0x97, 0x9b, 0x9f, 0xa2, 0xa6, 0xa9, 0xad, 0xb0, 0xb5, 0xb8, 0xbb, 0xbf, 0xc2, 0xc6, 0xc9, 0xcd, 0xd0, 0xd4, + 0x04, 0x07, 0x0a, 0x0e, 0x12, 0x15, 0x19, 0x1c, 0x20, 0x23, 0x27, 0x2a, 0x2d, 0x31, 0x34, 0x38, 0x3c, 0x3f, 0x43, 0x46, 0x4b, 0x4e, 0x52, 0x55, 0x59, 0x5c, 0x5f, 0x63, 0x66, 0x6a, 0x6e, 0x71, 0x74, 0x78, 0x7c, 0x7f, 0x83, 0x86, 0x89, 0x8d, 0x91, 0x94, 0x97, 0x9b, 0x9f, 0xa2, 0xa5, 0xa9, 0xad, 0xb0, 0xb4, 0xb7, 0xba, 0xbf, 0xc3, 0xc6, 0xca, 0xcd, 0xd1, 0xd4, + 0x03, 0x07, 0x0b, 0x0e, 0x12, 0x15, 0x19, 0x1c, 0x1f, 0x23, 0x26, 0x2a, 0x2e, 0x31, 0x34, 0x38, 0x3c, 0x3f, 0x43, 0x46, 0x4a, 0x4d, 0x52, 0x55, 0x58, 0x5c, 0x60, 0x63, 0x67, 0x6a, 0x6e, 0x71, 0x74, 0x78, 0x7b, 0x7f, 0x82, 0x86, 0x89, 0x8d, 0x91, 0x94, 0x98, 0x9b, 0x9f, 0xa2, 0xa6, 0xa9, 0xad, 0xb0, 0xb4, 0xb7, 0xbb, 0xbe, 0xc1, 0xc5, 0xca, 0xcd, 0xd1, 0xd4, + 0x03, 0x07, 0x0a, 0x0e, 0x12, 0x15, 0x19, 0x1c, 0x20, 0x23, 0x26, 0x2a, 0x2e, 0x31, 0x34, 0x38, 0x3c, 0x3f, 0x43, 0x46, 0x4a, 0x4d, 0x50, 0x54, 0x58, 0x5c, 0x60, 0x63, 0x66, 0x6a, 0x6e, 0x71, 0x75, 0x78, 0x7c, 0x7f, 0x83, 0x86, 0x8a, 0x8d, 0x91, 0x94, 0x98, 0x9b, 0x9e, 0xa2, 0xa6, 0xa9, 0xac, 0xb0, 0xb4, 0xb7, 0xbb, 0xbe, 0xc1, 0xc5, 0xc9, 0xcc, 0xd1, 0xd4, + 0x03, 0x07, 0x0a, 0x0e, 0x12, 0x15, 0x19, 0x1c, 0x1f, 0x23, 0x27, 0x2a, 0x2e, 0x31, 0x35, 0x38, 0x3c, 0x3f, 0x43, 0x46, 0x4a, 0x4d, 0x51, 0x54, 0x57, 0x5b, 0x5e, 0x62, 0x67, 0x6a, 0x6d, 0x71, 0x75, 0x78, 0x7c, 0x7f, 0x82, 0x86, 0x89, 0x8d, 0x90, 0x94, 0x97, 0x9b, 0x9f, 0xa3, 0xa6, 0xa9, 0xac, 0xb0, 0xb4, 0xb8, 0xbb, 0xbe, 0xc2, 0xc5, 0xc9, 0xcc, 0xcf, 0xd3, + 0x04, 0x07, 0x0a, 0x0e, 0x11, 0x15, 0x18, 0x1c, 0x20, 0x23, 0x27, 0x2a, 0x2d, 0x31, 0x34, 0x38, 0x3b, 0x3f, 0x43, 0x46, 0x4a, 0x4d, 0x51, 0x54, 0x58, 0x5b, 0x5e, 0x62, 0x66, 0x69, 0x6d, 0x71, 0x75, 0x78, 0x7c, 0x7f, 0x83, 0x86, 0x8a, 0x8d, 0x91, 0x94, 0x97, 0x9b, 0x9f, 0xa2, 0xa6, 0xa9, 0xac, 0xb0, 0xb4, 0xb7, 0xba, 0xbe, 0xc2, 0xc5, 0xc9, 0xcc, 0xd0, 0xd3, + 0x03, 0x07, 0x0b, 0x0e, 0x12, 0x15, 0x19, 0x1c, 0x1f, 0x23, 0x27, 0x2a, 0x2e, 0x31, 0x35, 0x38, 0x3b, 0x3f, 0x43, 0x46, 0x4a, 0x4d, 0x51, 0x54, 0x58, 0x5b, 0x5f, 0x62, 0x66, 0x69, 0x6c, 0x70, 0x74, 0x78, 0x7b, 0x7f, 0x82, 0x86, 0x8a, 0x8d, 0x91, 0x94, 0x98, 0x9b, 0x9f, 0xa2, 0xa5, 0xa9, 0xac, 0xb0, 0xb4, 0xb7, 0xba, 0xbe, 0xc1, 0xc5, 0xc9, 0xcc, 0xd0, 0xd3, + 0x02, 0x06, 0x0a, 0x0e, 0x11, 0x15, 0x19, 0x1c, 0x20, 0x23, 0x27, 0x2a, 0x2d, 0x31, 0x35, 0x38, 0x3c, 0x3f, 0x43, 0x46, 0x4a, 0x4d, 0x51, 0x54, 0x58, 0x5b, 0x5f, 0x62, 0x65, 0x69, 0x6d, 0x70, 0x73, 0x77, 0x7b, 0x7e, 0x83, 0x86, 0x8a, 0x8d, 0x90, 0x95, 0x97, 0x9b, 0x9f, 0xa2, 0xa6, 0xa9, 0xad, 0xb0, 0xb4, 0xb7, 0xbb, 0xbf, 0xc2, 0xc5, 0xc8, 0xcc, 0xd0, 0xd3, + 0x03, 0x06, 0x09, 0x0d, 0x11, 0x15, 0x18, 0x1c, 0x1f, 0x23, 0x26, 0x2a, 0x2e, 0x31, 0x34, 0x38, 0x3b, 0x3f, 0x42, 0x46, 0x4a, 0x4d, 0x51, 0x54, 0x58, 0x5b, 0x5e, 0x62, 0x66, 0x69, 0x6d, 0x70, 0x74, 0x77, 0x7a, 0x7e, 0x81, 0x85, 0x89, 0x8d, 0x91, 0x94, 0x97, 0x9b, 0x9f, 0xa2, 0xa6, 0xa9, 0xac, 0xb0, 0xb4, 0xb7, 0xba, 0xbe, 0xc2, 0xc5, 0xc9, 0xcc, 0xd0, 0xd3, + 0x02, 0x06, 0x0a, 0x0d, 0x11, 0x14, 0x18, 0x1b, 0x1f, 0x23, 0x27, 0x2a, 0x2d, 0x31, 0x35, 0x38, 0x3b, 0x3f, 0x43, 0x46, 0x4a, 0x4d, 0x51, 0x54, 0x58, 0x5b, 0x5f, 0x62, 0x66, 0x69, 0x6c, 0x71, 0x74, 0x77, 0x7b, 0x7e, 0x82, 0x85, 0x89, 0x8c, 0x90, 0x94, 0x98, 0x9b, 0x9e, 0xa2, 0xa6, 0xa9, 0xad, 0xb0, 0xb3, 0xb7, 0xbb, 0xbe, 0xc1, 0xc5, 0xc9, 0xcd, 0xd0, 0xd3, + 0x03, 0x06, 0x0a, 0x0d, 0x11, 0x14, 0x17, 0x1b, 0x1f, 0x22, 0x26, 0x2a, 0x2d, 0x31, 0x34, 0x38, 0x3b, 0x3f, 0x43, 0x46, 0x4a, 0x4d, 0x51, 0x54, 0x57, 0x5b, 0x5e, 0x62, 0x65, 0x69, 0x6d, 0x70, 0x74, 0x77, 0x7a, 0x7e, 0x81, 0x85, 0x89, 0x8c, 0x90, 0x93, 0x97, 0x9a, 0x9e, 0xa2, 0xa5, 0xa9, 0xad, 0xb0, 0xb4, 0xb7, 0xba, 0xbe, 0xc1, 0xc5, 0xc8, 0xcc, 0xcf, 0xd3, + 0x03, 0x06, 0x0a, 0x0d, 0x11, 0x14, 0x17, 0x1b, 0x1e, 0x22, 0x26, 0x29, 0x2d, 0x31, 0x34, 0x38, 0x3b, 0x3f, 0x42, 0x46, 0x4a, 0x4d, 0x51, 0x54, 0x58, 0x5b, 0x5f, 0x62, 0x66, 0x69, 0x6d, 0x70, 0x73, 0x77, 0x7b, 0x7e, 0x82, 0x85, 0x89, 0x8c, 0x90, 0x94, 0x97, 0x9a, 0x9e, 0xa2, 0xa5, 0xa9, 0xad, 0xb0, 0xb4, 0xb7, 0xbb, 0xbe, 0xc2, 0xc5, 0xc9, 0xcd, 0xd0, 0xd3, + 0x03, 0x06, 0x0a, 0x0d, 0x11, 0x14, 0x18, 0x1b, 0x1e, 0x22, 0x26, 0x29, 0x2d, 0x30, 0x33, 0x37, 0x3c, 0x3f, 0x43, 0x46, 0x4a, 0x4d, 0x51, 0x54, 0x58, 0x5b, 0x5f, 0x62, 0x66, 0x69, 0x6d, 0x70, 0x74, 0x77, 0x7a, 0x7e, 0x82, 0x85, 0x89, 0x8c, 0x90, 0x93, 0x96, 0x9b, 0x9e, 0xa1, 0xa5, 0xa8, 0xac, 0xb0, 0xb4, 0xb7, 0xbb, 0xbe, 0xc2, 0xc5, 0xc9, 0xcc, 0xcf, 0xd3, + 0x03, 0x06, 0x0a, 0x0e, 0x11, 0x14, 0x18, 0x1b, 0x1e, 0x22, 0x25, 0x29, 0x2d, 0x30, 0x33, 0x37, 0x3b, 0x3e, 0x42, 0x46, 0x4a, 0x4d, 0x51, 0x54, 0x58, 0x5b, 0x5f, 0x62, 0x66, 0x69, 0x6d, 0x70, 0x74, 0x77, 0x7b, 0x7e, 0x82, 0x85, 0x88, 0x8c, 0x8f, 0x93, 0x97, 0x9a, 0x9e, 0xa1, 0xa5, 0xa8, 0xac, 0xaf, 0xb3, 0xb7, 0xba, 0xbe, 0xc2, 0xc5, 0xc8, 0xcc, 0xcf, 0xd3, + } +}; + diff --git a/include/demos.cpp b/include/demos.cpp new file mode 100644 index 0000000..adb7fe1 --- /dev/null +++ b/include/demos.cpp @@ -0,0 +1,252 @@ + + +float p = 3.1415926; + +typedef struct { + uint8_t width; + uint8_t height; + const uint8_t *data; +} bitmap_t; + +/*const uint8_t PROGMEM dvdlogo_data[] = { + B00001111, B11111111, B11111100, B00000011, B11111111, B11000000, + B00001111, B11111111, B11111100, B00000011, B11111111, B11111000, + B00000000, B00111111, B11111100, B00000111, B11100001, B11111110, + B00000000, B00000111, B11011110, B00001111, B00000000, B00111110, + B00011110, B00000011, B11011110, B00011110, B01111000, B00011111, + B00011110, B00000011, B11011110, B00111100, B01111000, B00011111, + B00011110, B00000011, B11001111, B00111100, B11111000, B00011110, + B00011110, B00000111, B11001111, B01111000, B11110000, B00011110, + B00111110, B00001111, B10000111, B11110000, B11110000, B00111100, + B00111110, B00111111, B00000111, B11100000, B11110000, B11111000, + B00111111, B11111100, B00000111, B11000000, B11111111, B11110000, + B00111111, B11110000, B00000011, B10000001, B11111111, B10000000, + B00000000, B00000000, B00000011, B10000000, B00000000, B00000000, + B00000000, B00000000, B00000011, B00000000, B00000000, B00000000, + B00000000, B00000000, B00000000, B00000000, B00000000, B00000000, + B00000000, B00000000, B00000000, B00000000, B00000000, B00000000, + B00000001, B11111111, B11111111, B11111111, B11111100, B00000000, + B00111111, B11111111, B11100000, B01111111, B11111111, B11100000, + B11111111, B11111111, B10000000, B00011111, B11111111, B11111000, + B01111111, B11111111, B10000000, B00111111, B11111111, B11110000, + B00000111, B11111111, B11111111, B11111111, B11111111, B00000000, + B00000000, B00000011, B11111111, B11111110, B00000000, B00000000 };*/ + +/* DVD logo */ +static PROGMEM bitmap_t dvdlogo = { + .width = 48, + .height = 22, + .data = (PROGMEM uint8_t[]) { + B00001111, B11111111, B11111100, B00000011, B11111111, B11000000, + B00001111, B11111111, B11111100, B00000011, B11111111, B11111000, + B00000000, B00111111, B11111100, B00000111, B11100001, B11111110, + B00000000, B00000111, B11011110, B00001111, B00000000, B00111110, + B00011110, B00000011, B11011110, B00011110, B01111000, B00011111, + B00011110, B00000011, B11011110, B00111100, B01111000, B00011111, + B00011110, B00000011, B11001111, B00111100, B11111000, B00011110, + B00011110, B00000111, B11001111, B01111000, B11110000, B00011110, + B00111110, B00001111, B10000111, B11110000, B11110000, B00111100, + B00111110, B00111111, B00000111, B11100000, B11110000, B11111000, + B00111111, B11111100, B00000111, B11000000, B11111111, B11110000, + B00111111, B11110000, B00000011, B10000001, B11111111, B10000000, + B00000000, B00000000, B00000011, B10000000, B00000000, B00000000, + B00000000, B00000000, B00000011, B00000000, B00000000, B00000000, + B00000000, B00000000, B00000000, B00000000, B00000000, B00000000, + B00000000, B00000000, B00000000, B00000000, B00000000, B00000000, + B00000001, B11111111, B11111111, B11111111, B11111100, B00000000, + B00111111, B11111111, B11100000, B01111111, B11111111, B11100000, + B11111111, B11111111, B10000000, B00011111, B11111111, B11111000, + B01111111, B11111111, B10000000, B00111111, B11111111, B11110000, + B00000111, B11111111, B11111111, B11111111, B11111111, B00000000, + B00000000, B00000011, B11111111, B11111110, B00000000, B00000000 + } +}; + +void testlines(uint16_t color) { + screen.fillScreen(BLACK); + for (uint16_t x = 0; x < screen.width() - 1; x += 6) { + screen.drawLine(0, 0, x, screen.height() - 1, color); + } + for (uint16_t y = 0; y < screen.height() - 1; y += 6) { + screen.drawLine(0, 0, screen.width() - 1, y, color); + } + + screen.fillScreen(BLACK); + for (uint16_t x = 0; x < screen.width() - 1; x += 6) { + screen.drawLine(screen.width() - 1, 0, x, screen.height() - 1, color); + } + for (uint16_t y = 0; y < screen.height() - 1; y += 6) { + screen.drawLine(screen.width() - 1, 0, 0, y, color); + } + + screen.fillScreen(BLACK); + for (uint16_t x = 0; x < screen.width() - 1; x += 6) { + screen.drawLine(0, screen.height() - 1, x, 0, color); + } + for (uint16_t y = 0; y < screen.height() - 1; y += 6) { + screen.drawLine(0, screen.height() - 1, screen.width() - 1, y, color); + } + + screen.fillScreen(BLACK); + for (uint16_t x = 0; x < screen.width() - 1; x += 6) { + screen.drawLine(screen.width() - 1, screen.height() - 1, x, 0, color); + } + for (uint16_t y = 0; y < screen.height() - 1; y += 6) { + screen.drawLine(screen.width() - 1, screen.height() - 1, 0, y, color); + } + +} + +void testdrawtext(const char *text, uint16_t color) { + screen.setCursor(0, 0); + screen.setTextColor(color); + screen.print(text); +} + +void testfastlines(uint16_t color1, uint16_t color2) { + screen.fillScreen(BLACK); + for (uint16_t y = 0; y < screen.height() - 1; y += 5) { + screen.drawFastHLine(0, y, screen.width() - 1, color1); + } + for (uint16_t x = 0; x < screen.width() - 1; x += 5) { + screen.drawFastVLine(x, 0, screen.height() - 1, color2); + } +} + +void testdrawrects(uint16_t color) { + screen.fillScreen(BLACK); + for (uint16_t x = 0; x < screen.height() - 1; x += 6) { + screen.drawRect((screen.width() - 1) / 2 - x / 2, (screen.height() - 1) / 2 - x / 2, x, x, color); + } +} + +void testfillrects(uint16_t color1, uint16_t color2) { + screen.fillScreen(BLACK); + for (uint16_t x = screen.height() - 1; x > 6; x -= 6) { + screen.fillRect((screen.width() - 1) / 2 - x / 2, (screen.height() - 1) / 2 - x / 2, x, x, color1); + screen.drawRect((screen.width() - 1) / 2 - x / 2, (screen.height() - 1) / 2 - x / 2, x, x, color2); + } +} + +void testfillcircles(uint8_t radius, uint16_t color) { + for (uint8_t x = radius; x < screen.width() - 1; x += radius * 2) { + for (uint8_t y = radius; y < screen.height() - 1; y += radius * 2) { + screen.fillCircle(x, y, radius, color); + } + } +} + +void testdrawcircles(uint8_t radius, uint16_t color) { + for (uint8_t x = 0; x < screen.width() - 1 + radius; x += radius * 2) { + for (uint8_t y = 0; y < screen.height() - 1 + radius; y += radius * 2) { + screen.drawCircle(x, y, radius, color); + } + } +} + +void testtriangles() { + screen.fillScreen(BLACK); + int color = 0xF800; + int t; + int w = screen.width() / 2; + int x = screen.height(); + int y = 0; + int z = screen.width(); + for (t = 0; t <= 15; t += 1) { + screen.drawTriangle(w, y, y, x, z, x, color); + x -= 4; + y += 4; + z -= 4; + color += 100; + } +} + +void testroundrects() { + screen.fillScreen(BLACK); + int color = 100; + + int x = 0; + int y = 0; + int w = screen.width(); + int h = screen.height(); + for (int i = 0; i <= 24; i++) { + screen.drawRoundRect(x, y, w, h, 5, color); + x += 2; + y += 3; + w -= 4; + h -= 6; + color += 1100; + Serial.println(i); + } +} + +void oledPrintTest() { + screen.fillScreen(BLACK); + screen.setCursor(0, 5); + screen.setTextColor(RED); + screen.setTextSize(1); + screen.println("Hello World!"); + screen.setTextColor(YELLOW); + screen.setTextSize(2); + screen.println("Hello World!"); + screen.setTextColor(BLUE); + screen.setTextSize(3); + screen.print(1234.567); + delay(1500); + screen.setCursor(0, 5); + screen.fillScreen(BLACK); + screen.setTextColor(WHITE); + screen.setTextSize(0); + screen.println("Hello World!"); + screen.setTextSize(1); + screen.setTextColor(GREEN); + screen.print(p, 6); + screen.println(" Want pi?"); + screen.println(" "); + screen.print(8675309, HEX); // print 8,675,309 out in HEX! + screen.println(" Print HEX!"); + screen.println(" "); + screen.setTextColor(WHITE); + screen.println("Sketch has been"); + screen.println("running for: "); + screen.setTextColor(MAGENTA); + screen.print(millis() / 1000); + screen.setTextColor(WHITE); + screen.print(" seconds."); +} + +void mediabuttons() { + // play + screen.fillScreen(BLACK); + screen.fillRoundRect(25, 10, 78, 60, 8, WHITE); + screen.fillTriangle(42, 20, 42, 60, 90, 40, RED); + delay(500); + // pause + screen.fillRoundRect(25, 90, 78, 60, 8, WHITE); + screen.fillRoundRect(39, 98, 20, 45, 5, GREEN); + screen.fillRoundRect(69, 98, 20, 45, 5, GREEN); + delay(500); + // play color + screen.fillTriangle(42, 20, 42, 60, 90, 40, BLUE); + delay(50); + // pause color + screen.fillRoundRect(39, 98, 20, 45, 5, RED); + screen.fillRoundRect(69, 98, 20, 45, 5, RED); + // play color + screen.fillTriangle(42, 20, 42, 60, 90, 40, GREEN); +} + +/**************************************************************************/ +/*! + @brief Renders a simple test pattern on the screen +*/ +/**************************************************************************/ +void lcdTestPattern(void) { + static const uint16_t PROGMEM colors[] = + {RED, YELLOW, GREEN, CYAN, BLUE, MAGENTA, BLACK, WHITE}; + + for (uint8_t c = 0; c < 8; c++) { + screen.fillRect(0, screen.height() * c / 8, screen.width(), screen.height() / 8, + pgm_read_word(&colors[c])); + } +} diff --git a/include/fptrig.h b/include/fptrig.h new file mode 100644 index 0000000..b8a26f6 --- /dev/null +++ b/include/fptrig.h @@ -0,0 +1,42 @@ +#include + + +/* +Implements the 5-order polynomial approximation to sin(x). +@param i angle (with 2^15 units/circle) +@return 16 bit fixed point Sine value (4.12) (ie: +4096 = +1 & -4096 = -1) + +The result is accurate to within +- 1 count. ie: +/-2.44e-4. +*/ +int16_t fpsin(int16_t i) +{ + /* Convert (signed) input to a value between 0 and 8192. (8192 is pi/2, which is the region of the curve fit). */ + /* ------------------------------------------------------------------- */ + i <<= 1; + uint8_t c = i<0; //set carry for output pos/neg + + if(i == (i|0x4000)) // flip input value to corresponding value in range [0..8192) + i = (1<<15) - i; + i = (i & 0x7FFF) >> 1; + /* ------------------------------------------------------------------- */ + + /* The following section implements the formula: + = y * 2^-n * ( A1 - 2^(q-p)* y * 2^-n * y * 2^-n * [B1 - 2^-r * y * 2^-n * C1 * y]) * 2^(a-q) + Where the constants are defined as follows: + */ + enum {A1=3370945099UL, B1=2746362156UL, C1=292421UL}; + enum {n=13, p=32, q=31, r=3, a=12}; + + uint32_t y = (C1*((uint32_t)i))>>n; + y = B1 - (((uint32_t)i*y)>>r); + y = (uint32_t)i * (y>>n); + y = (uint32_t)i * (y>>n); + y = A1 - (y>>(p-q)); + y = (uint32_t)i * (y>>n); + y = (y+(1UL<<(q-a-1)))>>(q-a); // Rounding + + return c ? -y : y; +} + +//Cos(x) = sin(x + pi/2) +#define fpcos(i) fpsin((int16_t)(((uint16_t)(i)) + 8192U)) \ No newline at end of file diff --git a/include/screen-settings.h b/include/screen-settings.h new file mode 100644 index 0000000..8828807 --- /dev/null +++ b/include/screen-settings.h @@ -0,0 +1,15 @@ +// Screen dimensions +#define SCREEN_WIDTH 128 +#define SCREEN_HEIGHT 128 // Change this to 96 for 1.27" OLED. + +// You can use any (4 or) 5 pins +// #define SCLK_PIN 14 +// #define MOSI_PIN 13 +#define DC_PIN 26 +#define CS_PIN 27 +#define RST_PIN 25 + +#define HSPI_MISO 12 +#define HSPI_MOSI 13 +#define HSPI_SCLK 14 +#define HSPI_SS 15 \ No newline at end of file diff --git a/include/wifi/wifi_command.h b/include/wifi/wifi_command.h new file mode 100644 index 0000000..0186144 --- /dev/null +++ b/include/wifi/wifi_command.h @@ -0,0 +1,3 @@ +#include "Commander.h" + +void registerWifiCommands(Commander &commander); \ No newline at end of file diff --git a/lib/Commander/Commander.cpp b/lib/Commander/Commander.cpp new file mode 100644 index 0000000..3652e51 --- /dev/null +++ b/lib/Commander/Commander.cpp @@ -0,0 +1,382 @@ + +#include +#include +#include "Commander.h" + + +Commander *Commander::serial_instance_ = nullptr; + + +void Commander::EnableSerial() { + assert(!running_); + assert(serial_instance_ == nullptr); + serial_instance_ = this; + enable_serial_ = true; +} + + +void Commander::SetFallbackHandler(const std::function &, + CommanderClient *)> &handler) { + assert(!running_); + handler_ = handler; +} + + +void Commander::Begin() { + assert(!running_); + + running_ = true; + if (enable_serial_) { + serial_client_ = new SerialCommanderClient(this); + serial_client_->Begin(); + serial_instance_ = this; + } + if (enable_wifi_) { + assert(port_ > 0); + tcp_server_ = new AsyncServer(port_); + tcp_server_->onClient(Commander::WifiOnClientHandler, this); + xTaskCreate(Commander::TaskFn, "Commander", 10000, this, 1, &this->task_handle_); + } + +} + +void Commander::Stop() { + assert(running_); + vTaskDelete(task_handle_); + // todo: remove the serial handler, disconnect all wifi tcp clients, stop the wifi tcp server + running_ = false; +} + +void Commander::WifiOnClient(AsyncClient *client) { + wifi_clients_.emplace_back(this, client); +} + + +void Commander::WifiOnClientHandler(void *thisarg, AsyncClient *client) { + ((Commander *)thisarg)->WifiOnClient(client); +} + +void Commander::EnableWifi(uint16_t port) { + assert(!running_); + enable_wifi_ = true; + port_ = port; +} + +void Command::SetPrompt(const String& prompt) { + prompt_ = prompt; +} + +void Commander::TaskFn(void *thisarg) { + ((Commander*)thisarg)->Task(); +} + +[[noreturn]] void Commander::Task() { + while(true) { + ulTaskNotifyTake(pdTRUE, 1000); // todo: number of ticks, can it be made infinite? + // todo: something something queue of newly connected wifi clients, actually use a queue here maybe instead of a notification + } +} + +void CommanderClient::DipatchCurrentCommandLine() { + assert(!current_line_.empty()); + // todo: somehow enter into new shell if a shell command was called without arguments + const Command* shell = shell_stack_.back()->Dispatch(current_line_, this); + if (shell != nullptr) { + shell_stack_.push_back(shell); + } + current_line_.clear(); +} + +void CommanderClient::ProcessCommandLine() { + if (current_line_.empty()) { + current_line_.emplace_back(""); + } + size_t pos = 0; + for (char c : buffer_) { + auto ¤t = current_line_.back(); + switch (c) { + case ' ': + if (in_quotes_ || in_escape_) { + current.concat(' '); + } else { + if (current.length() > 0) { + current_line_.emplace_back(""); + } + } + break; + case '"': + if (in_escape_) { + current.concat('"'); + in_escape_ = false; + } else { + if (in_quotes_) { + in_quotes_ = false; + current_line_.emplace_back(""); + } else { + in_quotes_ = true; + if (current.length() > 0) { + current_line_.emplace_back(""); + } + } + } + break; + case '\\': + if (in_escape_) { + current.concat('\\'); + in_escape_ = false; + } else { + in_escape_ = true; + } + break; + case '\n': + if (in_escape_ || in_quotes_) { + current.concat('\n'); + print("> "); // line continuation prompt + if (in_escape_) { + in_escape_ = false; + } + } else { + goto line_completed; + } + break; + default: + if (c >= 0x20) { // ignore control characters etc (also ignores tab) + current.concat(c); + } + break; + } + pos += 1; + } + goto end; + + line_completed: + pos += 1; + DipatchCurrentCommandLine(); + + end: + buffer_.remove(0, pos); +} + +const String &CommanderClient::GetPrompt() { + return shell_stack_.back()->prompt_; +} + +const String &CommanderClient::GetWelcomeMessage() { + return shell_stack_.back()->welcome_message_; +} + + +void CommanderClient::BufferAppend(const String &str) { + buffer_.concat(str); +} + +void CommanderClient::BufferAppend(char ch) { + buffer_.concat(ch); +} +/* +template +bool CommanderClient::ReadLine(T *out) { + static_assert(!std::is_same::value, "64-bit not supported"); + static_assert(!std::is_same::value, "64-bit not supported"); + static_assert(std::is_integral::value, "integer type required"); + + CmdString line = ReadLine(); + if (line.isEmpty()) return false; + int64_t res; + int i = 0; + + if (!std::is_unsigned::value) { + bool negative = false; + if (line[0] == '-') { + i = 1; + negative = true; + if (line.length() < 2) return false; + } + + char ch = line[i]; + if (ch >= '0' && ch <= '9') { + res = ch - '0'; + if (negative) { + res = -res; + } + } else { + return false; + }; + i += 1; + } + + for (; i < line.length(); i++) { + char ch = line[i]; + if (ch >= '0' && ch <= '9') { + uint8_t digit = ch - '0'; + res *= 10; + res += digit; + if (res > std::numeric_limits::max() || res < std::numeric_limits::min()) { + return false; + } + } else { + return false; + } + } + + *out = res; + return true; +}*/ + +CmdString CommanderClient::ReadLine() { + CmdString ret; + int c = read(); + while (c != '\n') { + if (c >= 0x20) { // ignore control chars, todo: make this better I guess? line editing capabilities? + write(c); + ret += (char)c; + } + c = read(); + } + write('\n'); + return ret; +} + +SerialCommanderClient *SerialCommanderClient::single_instance_ = nullptr; + +size_t SerialCommanderClient::write(uint8_t byte) { return Serial.write(byte); } + +int SerialCommanderClient::available() { return Serial.available(); } + +int SerialCommanderClient::read() { return Serial.read(); } + +int SerialCommanderClient::peek() { return Serial.peek(); } + +void SerialCommanderClient::flush() { Serial.flush(); } + +void SerialCommanderClient::OnReceive() { + assert(task_handle_ != nullptr); + vTaskNotifyGiveFromISR(task_handle_, nullptr); +} + +SerialCommanderClient::SerialCommanderClient(Commander *commander) : CommanderClient(commander) { + assert(single_instance_ == nullptr); + single_instance_ = this; + println(GetWelcomeMessage()); + println(""); + print(GetPrompt()); +} + + +void SerialCommanderClient::Task() { + while(true) { + ulTaskNotifyTake(pdTRUE, pdMS_TO_TICKS(10000)); // todo: number of ticks, possible to wait infinitely long? + if (in_command_) continue; + in_command_ = true; + while(Serial.available()) { + int ch = Serial.read(); + if (ch < 0) { + break; + } + Serial.write(ch); // echo the typed character so the user sees it + BufferAppend((char)ch); + if (ch == '\n') { + ProcessCommandLine(); + print(GetPrompt()); + } + } + in_command_ = false; + } +} + +void WifiCommanderClient::OnData(void *data, size_t len) { + // todo +} + +size_t WifiCommanderClient::write(uint8_t byte) { + // todo: also implement a write(const char*) or something like it +} + +int WifiCommanderClient::available() { + // todo +} + +int WifiCommanderClient::read() { + // todo +} + +int WifiCommanderClient::peek() { + // todo +} + +void WifiCommanderClient::flush() { + // todo +} + +void WifiCommanderClient::OnDataHandler(void *thisarg, AsyncClient *, void *data, size_t len) { + ((WifiCommanderClient *)thisarg)->OnData(data, len); +} + +WifiCommanderClient::WifiCommanderClient(Commander *commander, AsyncClient *client) + : CommanderClient(commander), async_client_(client) { + async_client_->onData(WifiCommanderClient::OnDataHandler, this); +} + +Command *Command::RegisterCommand(const String &name, + const std::function &, CommanderClient *)> &handler) { + commands_.push_back(Command(name, handler, false)); + return &commands_[commands_.size() - 1]; +} + +Command *Command::RegisterCommandWithShell(const String &name) { + commands_.push_back(Command(name, nullptr, true)); + return &commands_[commands_.size() - 1]; +} + +const Command* Command::Dispatch(std::vector cmdline, CommanderClient *client) const { + if (!cmdline.empty()) { + auto &first = cmdline.front(); + for (const auto &item : commands_) { + if (item.name_.equals(first)) { + if (item.shell_) { + return &item; + } + item.Dispatch(std::vector(cmdline.begin() + 1, cmdline.end()), + client); // todo maybe optimize this vector + return nullptr; + } + } + if (first.equals("?")) { + DefaultHelpHandler(client); + return nullptr; + } + } + if (handler_ == nullptr) { + DefaultHandler(cmdline, client); + } else { + handler_(cmdline, client); + } +} + +void Command::DefaultHandler(const std::vector &cmdline, CommanderClient *client) const { + if (commands_.empty()) { + client->println("this command is not implemented"); + } else { + if (cmdline.empty()) { + DefaultHelpHandler(client); + } else { + client->print("unknown sub-command: "); + client->println(cmdline.front()); + } + } +} + +void Command::DefaultHelpHandler(CommanderClient *client) const { + client->println("the following commands are available: "); + for (const auto &item : commands_) { + client->print(" "); + client->print(item.name_); + client->print(" "); + client->print(item.description_); + client->println(); + } +} + +void Command::SetWelcomeMessage(const String &message) { + welcome_message_ = message; +} diff --git a/lib/Commander/Commander.h b/lib/Commander/Commander.h new file mode 100644 index 0000000..42f42dd --- /dev/null +++ b/lib/Commander/Commander.h @@ -0,0 +1,318 @@ +#ifndef ESP32DISPLAYTEST_LIB_COMMANDER_COMMANDER_H_ +#define ESP32DISPLAYTEST_LIB_COMMANDER_COMMANDER_H_ + +#include +#include +#include +#include +#include +#include +#include + + +class CmdString : public String { + + public: + CmdString(const char *cstr = "") : String(cstr){} + CmdString(const String& str) : String(str){} + + + template + bool ReadInto(T *out) const { + static_assert(!std::is_same::value, "64-bit not supported"); + static_assert(!std::is_same::value, "64-bit not supported"); + static_assert(std::is_integral::value, "integer type required"); + + if (isEmpty()) return false; + int64_t res; + int i = 0; + + if (!std::is_unsigned::value) { + bool negative = false; + if (this->charAt(0) == '-') { + i = 1; + negative = true; + if (length() < 2) return false; + } + + char ch = this->charAt(i); + if (ch >= '0' && ch <= '9') { + res = ch - '0'; + if (negative) { + res = -res; + } + } else { + return false; + }; + i += 1; + } + + for (; i < length(); i++) { + char ch = this->charAt(i); + if (ch >= '0' && ch <= '9') { + uint8_t digit = ch - '0'; + res *= 10; + res += digit; + if (res > std::numeric_limits::max() || res < std::numeric_limits::min()) { + return false; + } + } else { + return false; + } + } + + *out = res; + return true; + } +}; + + + + +class Commander; + +class Command; + +class CommanderClient; + +class SerialCommanderClient; + +class WifiCommanderClient; + +class Command { + friend class CommanderClient; + + protected: + String name_; + String description_; + std::function, CommanderClient *)> handler_; + std::vector commands_; + + bool shell_; + + String prompt_ = "> "; + String welcome_message_ = ""; + + Command(const String &name, std::function, CommanderClient *)> handler, bool shell) + : handler_(std::move(handler)), shell_(shell) { + int space_index = name.indexOf(' '); + if (space_index == -1) { + name_ = name; + description_ = ""; + } else { + name_ = name.substring(0, space_index); + description_ = name.substring(space_index); + } + } + + const Command* Dispatch(std::vector cmdline, CommanderClient *client) const; + + void DefaultHandler(const std::vector &cmdline, CommanderClient *client) const; + + void DefaultHelpHandler(CommanderClient *client) const; + + public: + Command *RegisterCommand(const String &name, + const std::function &, + CommanderClient *)> &handler); + + Command *RegisterCommandWithShell(const String &name); + + void SetPrompt(const String &prompt); + + void SetWelcomeMessage(const String &message); + +}; + +class Commander : public Command { + friend CommanderClient; + private: + bool enable_wifi_ = false; + bool enable_serial_ = false; + uint16_t port_ = 0; + AsyncServer *tcp_server_{}; + std::vector wifi_clients_; + SerialCommanderClient *serial_client_{}; + + bool running_ = false; + + static Commander *serial_instance_; + + TaskHandle_t task_handle_{}; + + public: + + Commander() : Command("", Commander::DefaultFallbackHandler, false) {} + + using Command::RegisterCommand; + using Command::RegisterCommandWithShell; + + void EnableSerial(); + void EnableWifi(uint16_t port); + + void SetFallbackHandler(const std::function &, + CommanderClient *)> &handler); + + void Begin(); + + void Stop(); + + private: + void WifiOnClient(AsyncClient *client); + + void SerialReceive(); + + [[noreturn]] void Task(); + + static void SerialReceiveHandler(); + + static void WifiOnClientHandler(void *thisarg, AsyncClient *client); + + [[noreturn]] static void TaskFn(void *thisarg); + + static void DefaultFallbackHandler(std::vector cmd, CommanderClient *client) { + + } +}; + +class CommanderClient : public Stream { + protected: + std::vector shell_stack_; + + bool in_command_ = false; + + String buffer_ = ""; + + std::vector current_line_; + bool in_quotes_ = false; + bool in_escape_ = false; + bool previous_was_cr_ = false; + + TaskHandle_t task_handle_ = nullptr; + + void BufferAppend(const String &str); + + void BufferAppend(char ch); + + void DipatchCurrentCommandLine(); + + void ProcessCommandLine(); + + const String &GetPrompt(); + const String &GetWelcomeMessage(); + + virtual void Task() = 0; + + static void TaskFn(void *thisarg) { + ((CommanderClient *)thisarg)->Task(); + } + + public: + explicit CommanderClient(Commander *commander) { + shell_stack_.push_back(commander); + } + + virtual void Begin() { + xTaskCreate(CommanderClient::TaskFn, "commander client", 10000, this, 1, &this->task_handle_); + } + + CmdString ReadLine(); + + template + bool ReadLine(T *out) { + static_assert(!std::is_same::value, "64-bit not supported"); + static_assert(!std::is_same::value, "64-bit not supported"); + static_assert(std::is_integral::value, "integer type required"); + + CmdString line = ReadLine(); + if (line.isEmpty()) return false; + int64_t res; + int i = 0; + + if (!std::is_unsigned::value) { + bool negative = false; + if (line[0] == '-') { + i = 1; + negative = true; + if (line.length() < 2) return false; + } + + char ch = line[i]; + if (ch >= '0' && ch <= '9') { + res = ch - '0'; + if (negative) { + res = -res; + } + } else { + return false; + }; + i += 1; + } + + for (; i < line.length(); i++) { + char ch = line[i]; + if (ch >= '0' && ch <= '9') { + uint8_t digit = ch - '0'; + res *= 10; + res += digit; + if (res > std::numeric_limits::max() || res < std::numeric_limits::min()) { + return false; + } + } else { + return false; + } + } + + *out = res; + return true; + } + +}; + +class SerialCommanderClient : public CommanderClient { + + static SerialCommanderClient *single_instance_; + + public: + explicit SerialCommanderClient(Commander *commander); + + void OnReceive(); + + size_t write(uint8_t byte) override; + int available() override; + int read() override; + int peek() override; + void flush() override; + + void Begin() override { + CommanderClient::Begin(); + Serial.onReceive(SerialCommanderClient::OnReceiveHandler); + } + + [[noreturn]] void Task() override; + + static void OnReceiveHandler() { + single_instance_->OnReceive(); + } +}; + +class WifiCommanderClient : public CommanderClient { + AsyncClient *async_client_; + public: + explicit WifiCommanderClient(Commander *commander, AsyncClient *client); + + void OnData(void *data, size_t len); + + size_t write(uint8_t byte) override; + int available() override; + int read() override; + int peek() override; + void flush() override; + + void Task() {}; + + private: + static void OnDataHandler(void *thisarg, AsyncClient *, void *data, size_t len); +}; + +#endif //ESP32DISPLAYTEST_LIB_COMMANDER_COMMANDER_H_ diff --git a/lib/ExtendedScreen/ExtendedScreen.cpp b/lib/ExtendedScreen/ExtendedScreen.cpp new file mode 100644 index 0000000..83319df --- /dev/null +++ b/lib/ExtendedScreen/ExtendedScreen.cpp @@ -0,0 +1,57 @@ +#include "ExtendedScreen.h" +#include +#include + +void +ExtendedScreen::drawTransparentBitmap(int16_t x, int16_t y, const bitmap_t& bitmap, const RGB& foreground, const RGB& background) { + _screen->startWrite(); + for (int16_t j = 0; j < bitmap.height; j++, y++) { + for (int16_t i = 0; i < bitmap.width; i++) { + uint8_t alpha = bitmap.data[j*bitmap.width+i]; // todo: allow for less than 8 bit per pixel + uint16_t blended_color = foreground.alpha_blend_over_to565(background, alpha); + _screen->writePixel(x + i, y, blended_color); + } + } + _screen->endWrite(); +} + +void +ExtendedScreen::drawTransparentBitmap(int16_t x, int16_t y, const bitmap_t& bitmap, const RGB& foreground, const RGB& background, const rect_t& rect) { + auto background565 = background.to_565(); + _screen->startWrite(); + + int16_t max_image_width = std::min(static_cast(rect.x + rect.width), static_cast(bitmap.width)); + int16_t max_image_height = std::min(static_cast(rect.y + rect.height), static_cast(bitmap.height)); + + int16_t j = rect.y; + for(; j < 0; j++) { + for (int16_t i = rect.x; i < rect.x + rect.width; i++) { + _screen->writePixel(x + i - rect.x, y + j - rect.y, background565); + } + } + for (; j < max_image_height; j++) { + int16_t i = rect.x; + for (; i < 0; i++) { + _screen->writePixel(x + i - rect.x, y + j - rect.y, background565); + } + + for (; i < max_image_width; i++) { + uint8_t alpha = bitmap.data[j*bitmap.width+i]; // todo: allow for less than 8 bit per pixel + uint16_t blended_color = foreground.alpha_blend_over_to565(background, alpha); + _screen->writePixel(x + i - rect.x, y + j - rect.y, blended_color); + } + + for (; i < rect.x + rect.width; i++) { + _screen->writePixel(x + i - rect.x, y + j - rect.y, background565); + } + } + + for(; j < rect.y + rect.height; j++) { + for (int16_t i = rect.x; i < rect.x + rect.width; i++) { + _screen->writePixel(x + i - rect.x, y + j - rect.y, background565); + } + } + + + _screen->endWrite(); +} diff --git a/lib/ExtendedScreen/ExtendedScreen.h b/lib/ExtendedScreen/ExtendedScreen.h new file mode 100644 index 0000000..537ffab --- /dev/null +++ b/lib/ExtendedScreen/ExtendedScreen.h @@ -0,0 +1,76 @@ + +#ifndef ESP32DISPLAYTEST_EXTENDEDSCREEN_H +#define ESP32DISPLAYTEST_EXTENDEDSCREEN_H + + +#include "Adafruit_GFX.h" +#include "colors.h" +#include "bitmap.h" + +#include + +class ExtendedScreen : public Adafruit_GFX { +private: + Adafruit_GFX *_screen; + + void drawPixel(int16_t x, int16_t y, uint16_t color) override { + _screen->drawPixel(x, y, color); + } + +public: + explicit ExtendedScreen(Adafruit_GFX *actualScreen) : Adafruit_GFX(actualScreen->width(), actualScreen->height()) { + this->_screen = actualScreen; + } + + void drawTransparentBitmap(int16_t x, int16_t y, const bitmap_t& bitmap, const RGB& foreground, const RGB& background); + void drawTransparentBitmap(int16_t x, int16_t y, const bitmap_t& bitmap, const RGB& foreground, const RGB& background, const rect_t& rect); + + + // forward possibly-overridden virtual methods to the actual screen class + void startWrite() override { _screen->startWrite(); }; + + void writePixel(int16_t x, int16_t y, uint16_t color) override { _screen->writePixel(x, y, color); }; + + void writeFillRect(int16_t x, int16_t y, int16_t w, int16_t h, + uint16_t color) override { _screen->writeFillRect(x, y, w, h, color); }; + + void writeFastVLine(int16_t x, int16_t y, int16_t h, uint16_t color) override { + _screen->writeFastVLine(x, y, h, color); + }; + + void writeFastHLine(int16_t x, int16_t y, int16_t w, uint16_t color) override { + _screen->writeFastHLine(x, y, w, color); + }; + + void writeLine(int16_t x0, int16_t y0, int16_t x1, int16_t y1, + uint16_t color) override { _screen->writeLine(x0, y0, x1, y1, color); }; + + void endWrite() override { _screen->endWrite(); }; + + void setRotation(uint8_t r) override { _screen->setRotation(r); }; + + void invertDisplay(bool i) override { _screen->invertDisplay(i); }; + + void drawFastVLine(int16_t x, int16_t y, int16_t h, uint16_t color) override { + _screen->drawFastVLine(x, y, h, color); + }; + + void drawFastHLine(int16_t x, int16_t y, int16_t w, uint16_t color) override { + _screen->drawFastHLine(x, y, w, color); + }; + + void fillRect(int16_t x, int16_t y, int16_t w, int16_t h, + uint16_t color) override { _screen->fillRect(x, y, w, h, color); }; + + void fillScreen(uint16_t color) override { _screen->fillScreen(color); }; + + void drawLine(int16_t x0, int16_t y0, int16_t x1, int16_t y1, + uint16_t color) override { _screen->drawLine(x0, y0, x1, y1, color); }; + + void drawRect(int16_t x, int16_t y, int16_t w, int16_t h, + uint16_t color) override { _screen->drawRect(x, y, w, h, color); }; + +}; + + +#endif //ESP32DISPLAYTEST_EXTENDEDSCREEN_H diff --git a/lib/README b/lib/README new file mode 100644 index 0000000..6debab1 --- /dev/null +++ b/lib/README @@ -0,0 +1,46 @@ + +This directory is intended for project specific (private) libraries. +PlatformIO will compile them to static libraries and link into executable file. + +The source code of each library should be placed in a an own separate directory +("lib/your_library_name/[here are source files]"). + +For example, see a structure of the following two libraries `Foo` and `Bar`: + +|--lib +| | +| |--Bar +| | |--docs +| | |--examples +| | |--src +| | |- Bar.c +| | |- Bar.h +| | |- library.json (optional, custom build options, etc) https://docs.platformio.org/page/librarymanager/config.html +| | +| |--Foo +| | |- Foo.c +| | |- Foo.h +| | +| |- README --> THIS FILE +| +|- platformio.ini +|--src + |- main.c + +and a contents of `src/main.c`: +``` +#include +#include + +int main (void) +{ + ... +} + +``` + +PlatformIO Library Dependency Finder will find automatically dependent +libraries scanning project source files. + +More information about PlatformIO Library Dependency Finder +- https://docs.platformio.org/page/librarymanager/ldf.html diff --git a/lib/Util/HSL.h b/lib/Util/HSL.h new file mode 100644 index 0000000..5cbe297 --- /dev/null +++ b/lib/Util/HSL.h @@ -0,0 +1,162 @@ +#ifndef ESP32DISPLAYTEST_HSL_H +#define ESP32DISPLAYTEST_HSL_H + +#include +#include +#include "RGB.h" + +class HSL { + + static constexpr HSL from_rgb(const RGB &rgb) { + constexpr uint32_t unit = 255 << 8; + + const uint32_t r = rgb.red << 8; + const uint32_t g = rgb.green << 8; + const uint32_t b = rgb.blue << 8; + + const uint32_t max = std::max(r, std::max(g, b)); + const uint32_t min = std::min(r, std::max(g, b)); + + uint32_t h = 0; + uint32_t s = 0; + uint32_t l = (max + min) / 2; + + if (max == min) { + h = 0; + s = 0; + } else { + uint32_t d = max - min; + if (l > unit / 2) { + s = d / (unit * 2 - max - min); + } else { + s = d / (max + min); + } + if (max == r) { + h = (g - b) / 2; + if (g < b) { + h += unit * 6; + } + } else if (max == g) { + h = (b - r) / d + unit * 2; + } else { + h = (r - g) / d + unit * 4; + } + h = h / 6; + } + + return HSL(h, s, l); + } + +public: + const uint16_t hue = 0; + const uint16_t saturation = 0; + const uint16_t lightness = 0; + + constexpr HSL() : hue(0), saturation(0), lightness(0) {} + + + explicit constexpr HSL(const RGB &rgb) : HSL(from_rgb(rgb)) {} + + + constexpr HSL(uint16_t h, uint16_t s, uint16_t l) : hue(h), saturation(s), lightness(l) {} + + constexpr RGB toRGB() const { + const int32_t scale = 1 << 15; + int32_t c_ = 2 * scale * lightness / 100 - scale; + int32_t c = (scale - (c_ >= 0 ? c_ : -c_)) * saturation / 100; + int32_t x_ = ((hue * scale / 60 % (2 * scale)) - scale); + int32_t x = c * (scale - (x_ >= 0 ? x_ : -x_)) / scale; + int32_t m = lightness * scale / 100 - c / 2; + + uint32_t r = 0; + uint32_t g = 0; + uint32_t b = 0; + if (0 <= hue && hue < 60) { + r = c; + g = x; + b = 0; + } else if (60 <= hue && hue < 120) { + r = x; + g = c; + b = 0; + } else if (120 <= hue && hue < 180) { + r = 0; + g = c; + b = x; + } else if (180 <= hue && hue < 240) { + r = 0; + g = x; + b = c; + } else if (240 <= hue && hue < 300) { + r = x; + g = 0; + b = c; + } else if (300 <= hue && hue < 360) { + r = c; + g = 0; + b = x; + } + + + r = (r + m) * 255 / scale; + g = (g + m) * 255 / scale; + b = (b + m) * 255 / scale; + + + return {static_cast(r), static_cast(g), static_cast(b)}; + } + + constexpr uint16_t to_565() const { + const int32_t scale = 1 << 15; + int32_t c_ = 2 * scale * lightness / 100 - scale; + int32_t c = (scale - (c_ >= 0 ? c_ : -c_)) * saturation / 100; + int32_t x_ = ((hue * scale / 60 % (2 * scale)) - scale); + int32_t x = c * (scale - (x_ >= 0 ? x_ : -x_)) / scale; + int32_t m = lightness * scale / 100 - c / 2; + + uint32_t r = 0; + uint32_t g = 0; + uint32_t b = 0; + if (0 <= hue && hue < 60) { + r = c; + g = x; + b = 0; + } else if (60 <= hue && hue < 120) { + r = x; + g = c; + b = 0; + } else if (120 <= hue && hue < 180) { + r = 0; + g = c; + b = x; + } else if (180 <= hue && hue < 240) { + r = 0; + g = x; + b = c; + } else if (240 <= hue && hue < 300) { + r = x; + g = 0; + b = c; + } else if (300 <= hue && hue < 360) { + r = c; + g = 0; + b = x; + } + + + r = (r + m) * 31 / scale; + g = (g + m) * 63 / scale; + b = (b + m) * 31 / scale; + assert(r <= 31); + assert(g <= 63); + assert(b <= 31); + + return (r << 11) + | (g << 5) + | b; + + } +}; + + +#endif //ESP32DISPLAYTEST_HSL_H diff --git a/lib/Util/RGB.h b/lib/Util/RGB.h new file mode 100644 index 0000000..6396b5e --- /dev/null +++ b/lib/Util/RGB.h @@ -0,0 +1,153 @@ +#ifndef ESP32DISPLAYTEST_RGB_H +#define ESP32DISPLAYTEST_RGB_H + +//#include "HSL.h" +#include +#include +#include +#include +#include + + +class RGB { + static constexpr uint8_t hex_char_to_num(char ch) { + assert( + (ch >= '0' && ch <= '9') || + (ch >= 'a' && ch <= 'f') || + (ch >= 'A' && ch <= 'F') + ); + if (ch >= '0' && ch <= '9') return ch - '0'; + if (ch >= 'a' && ch <= 'f') return ch - 'a' + 10; + if (ch >= 'A' && ch <= 'F') return ch - 'A' + 10; + return -1; + } + + static constexpr size_t const_strlen(const char *str) { + size_t len = 0; + while (*(str + len) != '\0') len++; + return len; + } + + static constexpr uint32_t alpha_blend(uint8_t top, uint8_t bottom, uint8_t alpha) { + return ((uint32_t)top) * ((uint32_t)alpha) + ((uint32_t)bottom) * (255 - (uint32_t)alpha); + } + + static constexpr uint32_t hex_str_to_num(const char *hex) { + size_t len = const_strlen(hex); + assert(len >= 6); + if (hex[0] == '0' && (hex[1] == 'x' || hex[1] == 'X')) { + assert(len == 8); + hex += 2; + } else { + assert(len == 6); + } + uint32_t res = 0; + for (size_t i = 0; i < 6; ++i) { + res = (res << 4) | hex_char_to_num(hex[i]); + } + + return res; + } + + static constexpr uint32_t hex_str_to_num(const char *hex, size_t len) { + assert(len >= 6); + if (hex[0] == '0' && (hex[1] == 'x' || hex[1] == 'X')) { + assert(len == 8); + hex += 2; + } else { + assert(len == 6); + } + uint32_t res = 0; + for (size_t i = 0; i < 6; ++i) { + res = (res << 4) | hex_char_to_num(hex[i]); + } + + return res; + } + + explicit constexpr RGB(uint32_t num) : red(static_cast((num >> 16) & 0xFF)), + green(static_cast((num >> 8) & 0xFF)), + blue(static_cast((num) & 0xFF)) { + } + + public: + /*const*/ uint8_t red = 0; + /*const*/ uint8_t green = 0; + /*const*/ uint8_t blue = 0; + + constexpr RGB() : red(0), green(0), blue(0) {} + + constexpr RGB(const RGB &other) = default; + + constexpr RGB(uint8_t r, uint8_t g, uint8_t b) : red(r), green(g), blue(b) { + } + + explicit constexpr RGB(const char *hex) : RGB(hex_str_to_num(hex)) { + } + + explicit constexpr RGB(const char *hex, size_t len) : RGB(hex_str_to_num(hex, len)) { + } + + constexpr uint16_t + + to_565() const { + uint8_t r = this->red >> 3; + uint8_t g = this->green >> 2; + uint8_t b = this->blue >> 3; + return (r << 11) + | (g << 5) + | b; + } + + constexpr RGB alpha_blend_over(const RGB &other, uint8_t alpha) const { + uint32_t r = alpha_blend(this->red, other.red, alpha); + uint32_t g = alpha_blend(this->green, other.green, alpha); + uint32_t b = alpha_blend(this->blue, other.blue, alpha); + return {static_cast(r / 255), static_cast(g / 255), static_cast(b / 255)}; + } + + constexpr uint16_t alpha_blend_over_to565(const RGB &other, uint8_t alpha) const { + uint8_t r = alpha_blend(this->red, other.red, alpha) * 31 / 255 / 255; + uint8_t g = alpha_blend(this->green, other.green, alpha) * 31 / 255 / 255; + uint8_t b = alpha_blend(this->blue, other.blue, alpha) * 31 / 255 / 255; + assert(r <= 31); + assert(g <= 63); + assert(b <= 31); + return (r << 11) + | (g << 5) + | b; + } + + static RGB blend(const std::vector &colors) { + if (colors.empty()) { + return {0, 0, 0}; + } + uint64_t r = 0; + uint64_t g = 0; + uint64_t b = 0; + for (const auto &c : colors) { + r += c.red; + g += c.green; + b += c.blue; + } + r /= colors.size(); + g /= colors.size(); + b /= colors.size(); + return {static_cast(r), static_cast(g), static_cast(b)}; + } + + size_t printTo(Print &p) const { + return p.printf("RGB(%d,%d,%d)", red, green, blue); + } + +}; + +constexpr RGB operator "" _rgb(const char *lit) { + return RGB(lit); +} + +constexpr RGB operator "" _rgb(const char *lit, size_t len) { + return RGB(lit, len); +} + +#endif //ESP32DISPLAYTEST_RGB_H diff --git a/lib/Util/bitmap.h b/lib/Util/bitmap.h new file mode 100644 index 0000000..f35f3de --- /dev/null +++ b/lib/Util/bitmap.h @@ -0,0 +1,24 @@ +#ifndef ESP32DISPLAYTEST_BITMAP_H +#define ESP32DISPLAYTEST_BITMAP_H + +#include + +typedef struct { + uint16_t width; + uint16_t height; + uint8_t depth; + uint8_t pixel_align; + uint8_t row_align; + const uint8_t *data; +} bitmap_t; + + +typedef struct { + int16_t x; + int16_t y; + uint16_t width; + uint16_t height; +} rect_t; + + +#endif //ESP32DISPLAYTEST_BITMAP_H diff --git a/lib/Util/colors.h b/lib/Util/colors.h new file mode 100644 index 0000000..1216187 --- /dev/null +++ b/lib/Util/colors.h @@ -0,0 +1,18 @@ +#ifndef ESP32DISPLAYTEST_COLORS_H +#define ESP32DISPLAYTEST_COLORS_H + +#include "HSL.h" +#include "RGB.h" +#include + +constexpr uint16_t operator "" _rgb565(const char *lit) { + return RGB(lit).to_565(); +} + + +constexpr uint16_t operator "" _rgb565(const char *lit, size_t len) { + return RGB(lit, len).to_565(); +} + + +#endif //ESP32DISPLAYTEST_COLORS_H diff --git a/monitor/filter_esp_exception.py b/monitor/filter_esp_exception.py new file mode 100644 index 0000000..df5aca5 --- /dev/null +++ b/monitor/filter_esp_exception.py @@ -0,0 +1,149 @@ +# Copyright (c) 2014-present PlatformIO +# +# 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 +# +# http://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. + +import os +import re +import subprocess +import sys + +from platformio.compat import path_to_unicode, WINDOWS, PY2 +from platformio.project.exception import PlatformioException +from platformio.project.helpers import load_project_ide_data +from platformio.commands.device import DeviceMonitorFilter + +# By design, __init__ is called inside miniterm and we can't pass context to it. +# pylint: disable=attribute-defined-outside-init + + +class Esp32BetterExceptionDecoder(DeviceMonitorFilter): + NAME = "esp32_better_exception_decoder" + + def __call__(self): + self.buffer = "" + self.backtrace_re = re.compile( + r"^Backtrace: ?((0x[0-9a-fA-F]+:0x[0-9a-fA-F]+ ?)+)\s*$" + ) + + self.firmware_path = None + self.addr2line_path = None + self.enabled = self.setup_paths() + + if self.config.get("env:" + self.environment, "build_type") != "debug": + print( + """ +Please build project in debug configuration to get more details about an exception. +See https://docs.platformio.org/page/projectconf/build_configurations.html + +""" + ) + + return self + + def setup_paths(self): + self.project_dir = path_to_unicode(os.path.abspath(self.project_dir)) + try: + data = load_project_ide_data(self.project_dir, self.environment) + self.firmware_path = data["prog_path"] + if not os.path.isfile(self.firmware_path): + sys.stderr.write( + "%s: firmware at %s does not exist, rebuild the project?\n" + % (self.__class__.__name__, self.firmware_path) + ) + return False + + cc_path = data.get("cc_path", "") + if "-gcc" in cc_path: + path = cc_path.replace("-gcc", "-addr2line") + if os.path.isfile(path): + self.addr2line_path = path + return True + except PlatformioException as e: + sys.stderr.write( + "%s: disabling, exception while looking for addr2line: %s\n" + % (self.__class__.__name__, e) + ) + return False + sys.stderr.write( + "%s: disabling, failed to find addr2line.\n" % self.__class__.__name__ + ) + return False + + def rx(self, text): + if not self.enabled: + return text + + last = 0 + while True: + idx = text.find("\n", last) + if idx == -1: + if len(self.buffer) < 4096: + self.buffer += text[last:] + break + + line = text[last:idx] + if self.buffer: + line = self.buffer + line + self.buffer = "" + last = idx + 1 + + m = self.backtrace_re.match(line) + if m is None: + continue + + trace = self.get_backtrace(m) + if len(trace) != "": + text = text[: idx + 1] + trace + text[idx + 1 :] + last += len(trace) + return text + + def get_backtrace(self, match): + addrs_parts = [part.strip() for part in match.group(1).split("0x") if part.strip() != ""] + addrs_parts = ["0x" + part for part in addrs_parts] + addrs_parts = [part + " " if not part.endswith(":") else part for part in addrs_parts] + addrs = [addr.strip() for addr in "".join(addrs_parts).split(" ") if addr.strip() != ""] + + + trace = "" + enc = "mbcs" if WINDOWS else "utf-8" + args = [self.addr2line_path, u"-fipC", u"-e", self.firmware_path] + if PY2: + args = [a.encode(enc) for a in args] + try: + for i, addr in enumerate(addrs): + if PY2: + addr = addr.encode(enc) + output = ( + subprocess.check_output(args + [addr]) + .decode(enc) + .strip() + ) + output = output.replace( + "\n", "\n " + ) # newlines happen with inlined methods + output = self.strip_project_dir(output) + trace += " #%-2d in %s\n" % (i, output) + except subprocess.CalledProcessError as e: + sys.stderr.write( + "%s: failed to call %s: %s\n" + % (self.__class__.__name__, self.addr2line_path, e) + ) + return trace + + def strip_project_dir(self, trace): + while True: + idx = trace.find(self.project_dir) + if idx == -1: + break + trace = trace[:idx] + trace[idx + len(self.project_dir) + 1 :] + return trace diff --git a/platformio.ini b/platformio.ini new file mode 100644 index 0000000..3b0b9be --- /dev/null +++ b/platformio.ini @@ -0,0 +1,54 @@ +; PlatformIO Project Configuration File +; +; Build options: build flags, source filter +; Upload options: custom upload port, speed and extra flags +; Library options: dependencies, extra library storages +; Advanced options: extra scripting +; +; Please visit documentation for the other options and examples +; https://docs.platformio.org/page/projectconf.html + +[platformio] +default_envs = graphs + +[env] +build_unflags = -std=gnu++11 +build_flags = + -Wfatal-errors + -std=gnu++14 +;platform = espressif32 +platform = https://github.com/platformio/platform-espressif32.git#feature/arduino-upstream +framework = arduino +board = esp32dev +monitor_speed = 9600 +monitor_filters = esp32_better_exception_decoder +build_type = debug + +lib_deps = + adafruit/Adafruit GFX Library@^1.10.13 + adafruit/Adafruit SSD1351 library@^1.2.7 + adafruit/Adafruit BusIO@^1.11.1 + Wire + SPI + WiFi + AsyncTCP + +[env:native] +platform = native +lib_deps = + adafruit/Adafruit GFX Library@^1.10.13 +src_filter = + + +[env:test] +src_filter = + + +[env:pride_flags] +src_filter = + + +[env:wifi] +src_filter = + + + + + +[env:graphs] +src_filter = + \ No newline at end of file diff --git a/src/graphs.cpp b/src/graphs.cpp new file mode 100644 index 0000000..0f23d46 --- /dev/null +++ b/src/graphs.cpp @@ -0,0 +1,781 @@ +//#include "Commander.h" + +#include "fptrig.h" + +#include +#include +#include +#include +#include + +#include "screen-settings.h" +#include "colors.h" + +#include "FixedPoint.h" + + +typedef FixedPoint<1000000> Decimal; + +constexpr Decimal operator "" _d(long double v) { + return v; +} + +constexpr Decimal operator "" _d(unsigned long long int v) { + return v; +} + +static_assert(1_d == 1.0_d, "1 == 1.0"); +static_assert(1_d == 1_d, "1 == 1"); +static_assert(1_d + 1_d == 2_d, "1 + 1 == 2"); +static_assert(2_d * 3_d == 6_d, "2 * 3 == 6"); +static_assert(1.25_d * 2.3_d == 2.875_d, "1.25 * 2.3 == 2.875"); +static_assert(.9_d / 3_d == .3_d, ".9 / 3 == .3"); + +SPIClass hspi(HSPI); +Adafruit_SSD1351 screen(SCREEN_WIDTH, SCREEN_HEIGHT, &hspi, CS_PIN, DC_PIN, RST_PIN); + +//Commander commander; + + +struct Point { + Decimal x; + Decimal y; +}; + +enum InterpolationMethod { + NEAREST_NEIGHBOR, + LINEAR, + COSINE, + CUBIC_HERMITE, + MONOTONIC_CUBIC_HERMITE, +}; + +enum Visualisation { + LINE_DRAW, // todo: this is just for debugging + BAR, + LINE, + AREA, + AREA_WITH_LINE, +}; + +struct CubicCurve { + Decimal a; + Decimal b; + Decimal c; + Decimal d; + + Decimal GetValueAt(Decimal x) const { + return a * x * x * x + + b * x * x + + c * x + + d; + } + + Decimal GetSlopeAt(Decimal x) const { + return 3_d * a * x * x + + 2_d * b * x + + c; + } + + bool const IsEmpty() const { + return a == 0 && b == 0 && c == 0 && d == 0; + } + + static constexpr CubicCurve make(Decimal x1, Decimal y1, Decimal m1, Decimal x2, Decimal y2, Decimal m2) { + Decimal a = (m1 + m2 - 2 * (y2 - y1) / (x2 - x1)) / ((x1 - x2) * (x1 - x2)); + Decimal b = (m2 - m1) / (2 * (x2 - x1)) - 3 / 2 * (x1 + x2) * a; + Decimal c = m1 - 3 * x1 * x1 * a - 2 * x1 * b; + Decimal d = y1 - x1 * x1 * x1 * a - x1 * x1 * b - x1 * c; + return CubicCurve{a, b, c, d}; + } + + static constexpr CubicCurve makeLeftEnd(Decimal x1, Decimal y1, Decimal x2, Decimal y2, Decimal m2) { + // set second derivative to 0 at first point + // todo: actually implement this + + /* + * f(x) = ax^3 + bx^2 + cx + d + * f'(x) = 3ax^2 + 2bx + c + * f''(x) = 6ax + 2b + * + y1 = a*x1^3 + b*x1^2 + c*x1 + d + y2 = a*x2^3 + b*x2^2 + c*x2 + d + m2 = 3*a*x2^2 + 2*b*x2 + c + 0 = 6*a*x1 + 2*b + * + * + * + * + * + */ + + return make(x1, y1, 0, x2, y2, m2); + + } + + static constexpr CubicCurve makeRightEnd(Decimal x1, Decimal y1, Decimal m1, Decimal x2, Decimal y2) { + // set second derivative to 0 at second point + // todo: actually implement this + return make(x1, y1, m1, x2, y2, 0); + } + +}; + +struct Series { + RGB color; + String label; + Visualisation visualisation; + InterpolationMethod interpolation; // does not apply to "bar" visualisation + // point x and y values are scaled with 1000 + std::vector points; + + mutable CubicCurve cached_cubic_curve; + mutable Decimal cached_cubic_curve_from; + mutable Decimal cached_cubic_curve_to; + + std::tuple Interpolate(Decimal at) const { + if (points.size() < 2) { + return std::make_tuple(0, false); + } + if (at < points.front().x || at > points.back().x) { + return std::make_tuple(0, false); + } + switch (interpolation) { + case NEAREST_NEIGHBOR: return InterpolateNearestNeighbour(at); + case LINEAR: return InterpolateLinear(at); + case COSINE: return InterpolateCosine(at); + case CUBIC_HERMITE: return InterpolateCubicHermite(at); + case MONOTONIC_CUBIC_HERMITE: return InterpolateMonotonicCubicHermite(at); + } + + return std::make_tuple(0, false); + } + + std::tuple InterpolateNearestNeighbour(Decimal at) const { + assert(points.size() >= 2); + + if (at == points.front().x) { + return std::make_tuple(points.front().y, true); + } + + for (auto current = points.begin() + 1; current < points.end(); current++) { + if (at == current->x) { + return std::make_tuple(current->y, true); + } + auto prev = current - 1; + if (at > prev->x && at < current->x) { + if (at - prev->x < current->x - at) { + return std::make_tuple(prev->y, true); + } else { + return std::make_tuple(current->y, true); + } + } + } + assert(false); + } + + std::tuple InterpolateLinear(Decimal at) const { + assert(points.size() >= 2); + + if (at == points.front().x) { + return std::make_tuple(points.front().y, true); + } + + for (auto current = points.begin() + 1; current < points.end(); current++) { + if (at == current->x) { + return std::make_tuple(current->y, true); + } + auto prev = current - 1; + if (at > prev->x && at < current->x) { + Decimal dist = current->x - prev->x; + Decimal dist_val = at - prev->x; + Decimal val = (current->y * dist_val + prev->y * (dist - dist_val)) / dist; + return std::make_tuple(val, true); + } + } + assert(false); + } + + std::tuple InterpolateCosine(Decimal at) const { + assert(points.size() >= 2); + + if (at == points.front().x) { + return std::make_tuple(points.front().y, true); + } + + for (auto current = points.begin() + 1; current < points.end(); current++) { + if (at == current->x) { + return std::make_tuple(current->y, true); + } + auto prev = current - 1; + if (at > prev->x && at < current->x) { + Decimal dist = current->x - prev->x; + Decimal dist_val = at - prev->x; + Decimal mu = + 0; // todo: rework this to work with the FixedPoint class (dist - fpcos((1 << 14) * dist_val / dist) * dist / 8192 / 2); + if (mu > dist) { + Serial.print("mu = "); + Serial.println(mu.toString().c_str()); + Serial.print("dist = "); + Serial.println(dist.toString().c_str()); + for (;;); + } + assert(mu <= dist); + Decimal val = (prev->y * (dist - mu) + current->y * mu) / dist; + return std::make_tuple(val, true); + } + } + assert(false); + } + + CubicCurve &GetCubicCurve(Decimal at, bool monotonic) const { + if (!cached_cubic_curve.IsEmpty()) { + if (at >= cached_cubic_curve_from && at <= cached_cubic_curve_to) { + return cached_cubic_curve; + } + } + + for (auto p2 = points.begin() + 1; p2 < points.end(); p2++) { + auto p1 = p2 - 1; + + if (at >= p1->x && at <= p2->x) { + cached_cubic_curve_from = p1->x; + cached_cubic_curve_to = p2->x; + + Decimal m_after_p1 = (p2->y - p1->y) / (p2->x - p1->x); + Decimal m_before_p2 = (p2->y - p1->y) / (p2->x - p1->x); + + Decimal m_before_p1, m_after_p2, m1, m2; + + auto p0 = p1 - 1; + auto p3 = p2 + 1; + bool is_beginning = p0 < points.begin(); + bool is_end = p3 >= points.end(); + + if (!is_beginning) { + m_before_p1 = (p1->y - p0->y) / (p1->x - p0->x); + + if (monotonic && + (p0->y == p1->y + || p1->y == p2->y + || (p0->y < p1->y && p2->y < p1->y) + || (p0->y > p1->y && p2->y > p1->y) + )) { + m1 = 0; + } else { + m1 = (m_before_p1 + m_after_p1) / 2; // todo: use a mean that is weighted towards 0? + } + + } + + if (!is_end) { + m_after_p2 = (p3->y - p2->y) / (p3->x - p2->x); + + if (monotonic && + (p1->y == p2->y + || p2->y == p3->y + || (p1->y < p2->y && p3->y < p2->y) + || (p1->y > p2->y && p3->y > p2->y) + )) { + m1 = 0; + } else { + m2 = (m_before_p2 + m_after_p2) / 2; // todo: use a mean that is weighted towards 0? + } + } + + if (is_beginning) { + cached_cubic_curve = CubicCurve::makeLeftEnd(p1->x, p1->y, p2->x, p2->y, m2); + } else if (is_end) { + cached_cubic_curve = CubicCurve::makeRightEnd(p1->x, p1->y, m1, p2->x, p2->y); + } else { + cached_cubic_curve = CubicCurve::make(p1->x, p1->y, m1, p2->x, p2->y, m2); + } + + return cached_cubic_curve; + } + } + assert(false); + } + + std::tuple InterpolateCubicHermite(Decimal at) const { + assert(points.size() >= 2); + + if (points.size() == 2) { + return InterpolateLinear(at); + } + + if (at < points.front().x || at > points.back().x) { + return std::make_tuple(0, false); + } + + CubicCurve curve = GetCubicCurve(at, false); + + return std::make_tuple(curve.GetValueAt(at), true); + } + + + std::tuple InterpolateMonotonicCubicHermite(Decimal at) const { + assert(points.size() >= 2); + + if (points.size() == 2) { + return InterpolateLinear(at); + } + + if (at < points.front().x || at > points.back().x) { + return std::make_tuple(0, false); + } + + CubicCurve curve = GetCubicCurve(at, true); + + return std::make_tuple(curve.GetValueAt(at), true); + } + +}; + +#define PADDING_BETWEEN_NUMBERS_AND_GRAPH 5 +#define PADDING_BETWEEN_NUMBERS 5 + +[[gnu::pure]] Decimal find_step(Decimal step) { + int exp = 0; + + while (step < 1) { + step *= 10; + exp--; + } + + while (step >= 10) { + step /= 10; + exp++; + } + + if (step > 5) { + step = 10; + } else if (step > 2.5) { + step = 5; + } else if (step > 2) { + step = 2.5; + } else if (step > 1) { + step = 2; + } else { + step = 1; + } + + return step.exp10(exp); +} + +struct Chart { + RGB background_color; + RGB foreground_color; + bool show_axes; + bool show_labels; + bool show_numbers; + bool auto_range_ordinate; + bool auto_range_abscissa; + Decimal ordinate_from; + Decimal ordinate_to; + Decimal abscissa_from; + Decimal abscissa_to; + + std::vector series; + + std::tuple GetAbscissaRange() const { + Decimal from = abscissa_from; + Decimal to = abscissa_to; + if (auto_range_abscissa) { + from = INT32_MAX; + to = INT32_MIN; + for (const auto &s : series) { + for (const auto &p : s.points) { + if (p.x < from) { + from = p.x; + } + if (p.x > to) { + to = p.x; + } + } + } + } + return std::make_tuple(from, to); + } + + std::tuple GetOrdinateRange() const { + Decimal from = ordinate_from; + Decimal to = ordinate_to; + if (auto_range_ordinate) { + from = INT32_MAX; + to = INT32_MIN; + for (const auto &s : series) { + for (const auto &p : s.points) { + if (p.y < from) { + from = p.y; + } + if (p.y > to) { + to = p.y; + } + } + } + } + return std::make_tuple(from, to); + } + + void Draw(Adafruit_GFX &target, + int16_t rect_x, int16_t rect_y, uint16_t rect_w, uint16_t rect_h) { + target.fillRect(rect_x, rect_y, rect_w, rect_h, background_color.to_565()); + target.setTextSize(1); + + int16_t canvas_x = rect_x; + int16_t canvas_y = rect_y; + uint16_t canvas_width = rect_w; + uint16_t canvas_height = rect_h; + + Decimal ord_min, ord_max, abs_min, abs_max; + std::tie(ord_min, ord_max) = GetOrdinateRange(); + std::tie(abs_min, abs_max) = GetAbscissaRange(); + + if (show_labels) { + // todo: draw labels, change canvas size accordingly + } + + if (show_numbers) { + DrawNumbers(target, canvas_x, canvas_y, canvas_width, canvas_height); + } + + DrawGraph(target, canvas_x, canvas_y, canvas_width, canvas_height); + + if (show_axes) { + int16_t ord_pos = val_to_screen(0, canvas_x, canvas_width, abs_min, abs_max, false); + target.drawLine(ord_pos, canvas_y, ord_pos, canvas_y + canvas_height, foreground_color.to_565()); + + int16_t abs_pos = val_to_screen(0, canvas_y, canvas_height, ord_min, ord_max, true); + target.drawLine(canvas_x, abs_pos, canvas_x + canvas_width, abs_pos, foreground_color.to_565()); + } + + } + + void DrawGraph(Adafruit_GFX &target, + int16_t &canvas_x, + int16_t &canvas_y, + uint16_t &canvas_width, + uint16_t &canvas_height) { + + Decimal ord_min, ord_max, abs_min, abs_max; + std::tie(ord_min, ord_max) = GetOrdinateRange(); + std::tie(abs_min, abs_max) = GetAbscissaRange(); + + std::vector normal_series; + for (auto &s : series) { + if (s.visualisation == Visualisation::LINE_DRAW || s.visualisation == Visualisation::BAR) { + continue; + } + normal_series.push_back(&s); + } + + enum PixelType { + NONE, + LINE, + AREA, + }; + + std::vector>> pixels(canvas_height); + assert(pixels.size() == canvas_height); + std::vector> values; + if (!normal_series.empty()) { + for (uint16_t x = 0; x < canvas_width; x++) { + Decimal abs = (abs_min * (canvas_width - 1 - x) + abs_max * x) / (canvas_width - 1); + values.clear(); + for (auto &p : pixels) { + p.clear(); + } + + for (auto s : normal_series) { + Decimal val; + bool success; + std::tie(val, success) = s->Interpolate(abs); + if (!success) continue; + int16_t y = static_cast(((val - ord_min) * (canvas_height - 1) / (ord_max - ord_min)).toInt()); + values.emplace_back(y, s); + } + + for (const auto &item : values) { + // todo: combine this loop with the previous + int16_t y; + const Series *s; + std::tie(y, s) = item; + assert(y >= 0 && y < canvas_height); + + if (s->visualisation == Visualisation::LINE || s->visualisation == Visualisation::AREA_WITH_LINE) { + pixels[y].emplace_back(PixelType::LINE, s); + // todo: this leaves gaps between line segments + } + if (s->visualisation == Visualisation::AREA || s->visualisation == Visualisation::AREA_WITH_LINE) { + for (size_t i = 0; i <= y; ++i) { + pixels[i].emplace_back(PixelType::AREA, s); + } + } + + } + + std::vector line_pixels; + std::vector area_pixels; + const Series *s; + PixelType t; + for (int16_t y = 0; y < canvas_height; ++y) { + line_pixels.clear(); + area_pixels.clear(); + auto pixel = pixels[y]; + for (const auto &item : pixel) { + std::tie(t, s) = item; + if (t == PixelType::LINE) { + line_pixels.push_back(s->color); + } else if (t == PixelType::AREA) { + area_pixels.push_back(s->color); + } + } + + int16_t screen_x = canvas_x + x; + int16_t screen_y = canvas_y + canvas_height - y; + + if (!line_pixels.empty()) { + // draw line pixel + RGB color = RGB::blend(line_pixels); + target.drawPixel(screen_x, screen_y, color.to_565()); + } else if (!area_pixels.empty()) { + // draw area pixel + RGB color = RGB::blend(area_pixels); + uint16_t c2 = color.alpha_blend_over_to565(background_color, 100); // TODO: make configurable + target.drawPixel(screen_x, screen_y, c2); + } else { + // draw background pixel + target.drawPixel(screen_x, screen_y, background_color.to_565()); + } + } + } + } + + for (const auto &s : series) { + if (s.visualisation == Visualisation::LINE_DRAW) { + const Point *last = nullptr; + for (const auto &p : s.points) { + if (last != nullptr) { + int16_t tox = val_to_screen(p.x, canvas_x, canvas_width, abs_min, abs_max, false); + int16_t toy = val_to_screen(p.y, canvas_y, canvas_height, ord_min, ord_max, true); + int16_t fromx = val_to_screen(last->x, canvas_x, canvas_width, abs_min, abs_max, false); + int16_t fromy = val_to_screen(last->y, canvas_y, canvas_height, ord_min, ord_max, true); + target.drawLine(fromx, fromy, tox, toy, s.color.to_565()); + } + last = &p; + } + } else if (s.visualisation == Visualisation::BAR) { + // todo: draw bar series + } + } + + /*for (const auto &s : series) { + if (s.visualisation != Visualisation::BAR) { + for (const auto &p : s.points) { + int16_t x = val_to_screen(p.x, canvas_x, canvas_width, abs_min, abs_max, false); + int16_t y = val_to_screen(p.y, canvas_y, canvas_height, ord_min, ord_max, true); + target.drawCircle(x, y, 1, s.color.to_565()); + } + } + }*/ + } + + void DrawNumbers(Adafruit_GFX &target, + int16_t &canvas_x, + int16_t &canvas_y, + uint16_t &canvas_width, + uint16_t &canvas_height) const { + Decimal ord_min, ord_max, abs_min, abs_max; + std::tie(ord_min, ord_max) = GetOrdinateRange(); + std::tie(abs_min, abs_max) = GetAbscissaRange(); + + int16_t dummy_x, dummy_y; + uint16_t text_width, text_height; + + target.getTextBounds("0", 0, 0, &dummy_x, &dummy_y, &text_width, &text_height); + + uint16_t text_y_offset = (text_height + 1) / 2; // rounded-up half height of text + + canvas_height -= text_height + PADDING_BETWEEN_NUMBERS_AND_GRAPH; + + canvas_height -= 2 * text_y_offset; + + canvas_y += text_y_offset; + + Decimal ord_max_num = ord_max; + Decimal ord_min_num = ord_min; + + uint8_t max_num_ord_numbers = canvas_height / (text_height + PADDING_BETWEEN_NUMBERS); + + Decimal ord_num_step = (ord_max_num - ord_min_num) / max_num_ord_numbers; + + ord_num_step = find_step(ord_num_step); + + if (ord_max_num > 0) { + ord_max_num = (ord_max_num / ord_num_step) * ord_num_step; + } else { + ord_max_num = ((ord_max_num - ord_num_step) / ord_num_step) * ord_num_step; + } + + if (ord_min_num > 0) { + ord_min_num = ((ord_min_num + ord_num_step) / ord_num_step) * ord_num_step; + } else { + ord_min_num = (ord_min_num / ord_num_step) * ord_num_step; + } + + String str; + // value text string width + std::vector> ord_steps; + uint16_t max_width = 0; + + for (Decimal step = ord_min_num; step <= ord_max_num; step += ord_num_step) { + str = + step.toString(2, ord_min_num.abs() < 1000 && ord_max_num.abs() < 1000); // todo: use digits of ord_num_step + target.getTextBounds(str, 0, 0, &dummy_x, &dummy_y, &text_width, &text_height); + ord_steps.emplace_back(step, str, text_width); + max_width = std::max(max_width, text_width); + } + + canvas_x += max_width + PADDING_BETWEEN_NUMBERS_AND_GRAPH; + canvas_width -= max_width + PADDING_BETWEEN_NUMBERS_AND_GRAPH; + + target.setTextColor(foreground_color.to_565()); + for (const auto &step : ord_steps) { + Decimal val; + String text; + uint16_t width; + std::tie(val, text, width) = step; + int16_t posx = canvas_x - width - PADDING_BETWEEN_NUMBERS_AND_GRAPH; + int16_t posy = val_to_screen(val, canvas_y, canvas_height, ord_min, ord_max, true); + + target.setCursor(posx, posy - text_y_offset); + target.print(text); + target.drawLine(canvas_x - 1, posy, canvas_x + 1, posy, foreground_color.to_565()); + } + + Decimal abs_max_num = abs_max; + Decimal abs_min_num = abs_min; + + } + + [[gnu::pure]] static int16_t val_to_screen(Decimal val, + int16_t canvas_start, + uint16_t canvas_size, + Decimal val_min, + Decimal val_max, + bool reverse) { + int16_t canvas_val = static_cast(((val - val_min) * canvas_size / (val_max - val_min)).toInt()); + if (reverse) { + return canvas_start + canvas_size - canvas_val; + } else { + return canvas_start + canvas_val; + } + } + +}; + +RGB color_white = "FFFFFF"_rgb; +RGB color_black = "000000"_rgb; + +std::vector v1 = {{0_d, 10_d}, {5_d, 20_d}, {10_d, 16_d}, {15_d, 50_d}, + {20_d, 0_d}, {25_d, 40_d}}; +std::vector + v2 = {{0_d, 5_d}, {3_d, 10_d}, {7_d, 20_d}, {11_d, 6_d}, {15_d, 22_d}, {22_d, 36_d}, {25_d, 50_d}}; +std::vector + v3 = {{-10_d, 100000_d}, {-5_d, 2000_d}, {0_d, 100000_d}, {5_d, 2000_d}, {10_d, -16000_d}, {15_d, 50000_d}, + {20_d, 1000_d}, {25_d, 40000_d}}; +std::vector v4 = + {{0, 12374_d}, {3_d, 43563_d}, {7_d, 6512_d}, {11_d, 14434_d}, {15_d, 43143_d}, {22_d, 298_d}, {25_d, 93834_d}}; + +Series s1{ + "00FFFF"_rgb, + "CYAN", + Visualisation::AREA_WITH_LINE, + InterpolationMethod::MONOTONIC_CUBIC_HERMITE, + v1 +}; +Series s2{ + "FF0000"_rgb, + "RED", + Visualisation::AREA_WITH_LINE, + InterpolationMethod::MONOTONIC_CUBIC_HERMITE, + v2 +}; + +std::vector series{s1, s2}; +Chart chart1 = {color_black, color_white, + true, true, true, + true, true, 0, 0, 0, 0, + series}; + +Series s3{ + "00FFFF"_rgb, + "CYAN", + Visualisation::AREA_WITH_LINE, + InterpolationMethod::CUBIC_HERMITE, + v1 +}; +Series s4{ + "FF0000"_rgb, + "RED", + Visualisation::AREA_WITH_LINE, + InterpolationMethod::CUBIC_HERMITE, + v2 +}; + +std::vector series2{s3, s4}; + + +Chart chart2 = {color_white, color_black, + true, true, true, + true, false, 0, 0, 0_d, 5_d, + series2}; + + + +void setup() { + Serial.begin(9600); + screen.begin(32000000); + +// commander.EnableSerial(); +// commander.SetPrompt("graph> "); +// commander.Begin(); + + + screen.fillScreen("555555"_rgb565); + + + chart1.Draw(screen, 5, 5, screen.width() - 10, screen.height() / 2 - 10); + chart2.Draw(screen, 5, screen.height() / 2 + 5, screen.width() - 10, screen.height() / 2 - 10); +} + + +Decimal chart2_offset = 0_d; +Decimal chart2_step = 0.2; +bool chart2_direction = true; + +void loop() { + if (chart2_direction) { + chart2_offset += chart2_step; + if (chart2_offset > 20) { + chart2_offset = 20; + chart2_direction = false; + } + } else { + chart2_offset -= chart2_step; + if (chart2_offset < 0) { + chart2_offset = 0; + chart2_direction = true; + } + } + + chart2.abscissa_from = chart2_offset; + chart2.abscissa_to = chart2_offset + 5; + + + chart2.Draw(screen, 5, screen.height() / 2 + 5, screen.width() - 10, screen.height() / 2 - 10); + + delay(100); + +} \ No newline at end of file diff --git a/src/pride_flags.cpp b/src/pride_flags.cpp new file mode 100644 index 0000000..9ca24f1 --- /dev/null +++ b/src/pride_flags.cpp @@ -0,0 +1,276 @@ + +#include "ExtendedScreen.h" + +#include "bitmap.h" +#include "colors.h" + +#include +#include +#include + + +#include "screen-settings.h" + +SPIClass hspi(HSPI); +Adafruit_SSD1351 hardware_screen(SCREEN_WIDTH, SCREEN_HEIGHT, &hspi, CS_PIN, DC_PIN, RST_PIN); +ExtendedScreen screen(&hardware_screen); + + +rect_t draw_striped_bitmap(bitmap_t &image, + uint16_t x, uint16_t y, + int num_stripes, const RGB stripes[], + RGB background, + rect_t old_rect); + +rect_t +draw_bouncy_striped_bitmap(bitmap_t &image, uint16_t num_stripes, const RGB stripes[], RGB background); + + +RGB black = RGB{0, 0, 0}; + +void setup() { + Serial.begin(9600); + hardware_screen.begin(32000000); + screen.fillScreen(black.to_565()); +} + +uint16_t rainbow_saturation = 100; +uint16_t rainbow_lightness = 50; +const int16_t rainbow_flag[] = {0, 60, 120, 180, 240, 300}; +const uint16_t rainbow_length = sizeof(rainbow_flag) / sizeof(rainbow_flag[0]); + +#define hue_step 1 << 7 +#define start_x 20 +#define start_y 90 +#define x_step 7 << 5 +#define y_step 9 << 5 + + + +constexpr RGB dark(50, 50, 50); + +//RGB blue(0, 200, 255); +constexpr RGB blue(0x00, 0xC8, 0xFF); +//RGB trans_pink(220, 0, 255); +constexpr RGB trans_pink(0xDC, 0x00, 0xFF); +//RGB white(255, 255, 255); +constexpr RGB white(0xFF, 0xFF, 0xFF); +const RGB trans_flag[] = {blue, trans_pink, white, trans_pink, blue}; +const uint16_t trans_flag_length = sizeof(trans_flag) / sizeof(trans_flag[0]); + + +RGB gray(150, 150, 150); +RGB green(150, 255, 45); +const RGB agender_flag[] = {dark, gray, white, green, white, gray, dark}; +const uint16_t agender_flag_length = sizeof(agender_flag) / sizeof(agender_flag[0]); + + +RGB red(214, 41, 0); +RGB orange(255, 155, 85); +RGB lesbian_pink(212, 97, 166); +RGB lesbian_purple(165, 0, 98); +const RGB lesbian_flag[] = {red, orange, white, lesbian_pink, lesbian_purple}; +const uint16_t lesbian_flag_length = sizeof(lesbian_flag) / sizeof(lesbian_flag[0]); + + +const RGB enby_yellow(255, 255, 0); +const RGB enby_purple(150, 20, 255); +const RGB enby_flag[] = {enby_yellow, white, enby_purple, dark}; +const uint16_t enby_flag_length = sizeof(enby_flag) / sizeof(enby_flag[0]); + + +#include "bitmaps/cyber96.h" + + +int32_t hue_time = 0; +int32_t toggle_count = 0; + + +void loop() { + int selector = (toggle_count / 100) % 5; + + RGB rainbow_stripes[rainbow_length]; + + switch (selector) { + case 0: + for (size_t i = 0; i < rainbow_length; ++i) { + uint16_t hue = (int16_t) ((rainbow_flag[i] + (hue_time >> 8)) % 360); + rainbow_stripes[i] = HSL(hue, rainbow_saturation, rainbow_lightness).toRGB(); + } + + draw_bouncy_striped_bitmap(cyber96, rainbow_length, rainbow_stripes, black); + + hue_time = (int32_t) (((360 << 8) + hue_time + hue_step) % (360 << 8)); + break; + case 1: + draw_bouncy_striped_bitmap(cyber96, trans_flag_length, trans_flag, black); + break; + case 2: + draw_bouncy_striped_bitmap(cyber96, agender_flag_length, agender_flag, black); + break; + case 3: + draw_bouncy_striped_bitmap(cyber96, lesbian_flag_length, lesbian_flag, black); + break; + case 4: + draw_bouncy_striped_bitmap(cyber96, enby_flag_length, enby_flag, black); + break; + } + toggle_count += 1; + delay(20); +} + + +/** + * draws a bitmap in colored horizontal stripes + * colors are in RGB65 + * note: the width of the image MUST be a multiple of 8 + * returns the rect in which the image was drawn + */ +rect_t draw_striped_bitmap(bitmap_t &image, + uint16_t x, uint16_t y, + int num_stripes, const RGB stripes[], + RGB background, + rect_t old_rect) { + + int row_height = image.height / num_stripes; + int leftover = image.height % num_stripes; + bool even = num_stripes % 2 == 0; + int middle_row_height_1; + int middle_row_height_2; + if (even) { + middle_row_height_1 = row_height + leftover / 2; + middle_row_height_2 = middle_row_height_1 + (leftover % 2); + } else { + middle_row_height_1 = row_height + leftover; + middle_row_height_2 = 0; + } + + uint16_t offset_y = 0; + + int16_t rect_x_offset = 0; + uint16_t rect_width_offset = 0; + + if (old_rect.width > 0) { + if (old_rect.x < x) { + rect_width_offset = x - old_rect.x; + rect_x_offset = -rect_width_offset; + } else { + rect_x_offset = 0; + rect_width_offset = old_rect.x - x; + } + } + + + for (int i = 0; i < num_stripes; i++) { + RGB current_color = stripes[i]; + uint16_t offset_data = offset_y * image.width / 8; + + uint16_t current_row_height = row_height; + if (even) { + if (i == num_stripes / 2) { + current_row_height = middle_row_height_1; + } else if (i == num_stripes / 2 - 1) { + current_row_height = middle_row_height_2; + } + } else { + if (i == num_stripes / 2) { + current_row_height = middle_row_height_1; + } + } + + rect_t stripe_rect = { + .x = rect_x_offset, .y = static_cast(offset_y), + .width = static_cast(image.width + rect_width_offset), .height = current_row_height, + }; + + int16_t rect_y_offset = 0; + uint16_t rect_height_offset = 0; + if (old_rect.height > 0) { + if (i == 0 && old_rect.y < y) { + rect_height_offset = y - old_rect.y; + rect_y_offset = -rect_height_offset; + } else if (i == num_stripes - 1 && old_rect.y > y) { + rect_height_offset = (old_rect.y + old_rect.height) - (y + offset_y + current_row_height); + } + } + stripe_rect.height += rect_height_offset; + stripe_rect.y += rect_y_offset; + + + screen.drawTransparentBitmap(x + rect_x_offset, y + offset_y + rect_y_offset, image, current_color, background, + stripe_rect); + + + offset_y += current_row_height; + } + + return rect_t{static_cast(x), static_cast(y), image.width, image.height}; +} + + +/** + * draws a bouncy image with looping stripes, each time this function is called the image moves and the colors of the stripes are shifted + * colors are in HSL + * start positions are screen coordinates + * steps for position and hue are scaled with 255 + * returns the rect in which the image was drawn + */ +rect_t draw_bouncy_striped_bitmap(bitmap_t &image, + uint16_t num_stripes, const RGB stripes[], + RGB background +) { + static int16_t x = start_x; + static int16_t y = start_y; + static int32_t scaled_x = start_x << 8; + static int32_t scaled_y = start_y << 8; + static int32_t move_x = x_step; + static int32_t move_y = y_step; + + static rect_t rect = {}; + + + const int32_t max_scaled_x = (SCREEN_WIDTH - image.width - 1) << 8; + const int32_t max_scaled_y = (SCREEN_HEIGHT - image.height - 1) << 8; + + if (x_step >= max_scaled_x || x_step <= -max_scaled_x) { + screen.setCursor(0, 5); + screen.setTextColor(RGB(255, 0, 0).to_565()); + screen.setTextSize(1); + screen.println("x_step is too large"); + return rect_t{}; + } + if (y_step >= max_scaled_y || y_step <= -max_scaled_y) { + screen.setCursor(0, 5); + screen.setTextColor(RGB(255, 0, 0).to_565()); + screen.setTextSize(1); + screen.println("y_step is too large"); + return rect_t{}; + } + + + rect = draw_striped_bitmap(image, x, y, num_stripes, stripes, background, rect); + + + scaled_x = scaled_x + move_x; + scaled_y = scaled_y + move_y; + + if (scaled_x < 0) { + scaled_x = -scaled_x; + move_x = -move_x; + } else if (scaled_x > max_scaled_x) { + scaled_x = 2 * max_scaled_x - scaled_x; + move_x = -move_x; + } + + if (scaled_y < 0) { + scaled_y = -scaled_y; + move_y = -move_y; + } else if (scaled_y > max_scaled_y) { + scaled_y = 2 * max_scaled_y - scaled_y; + move_y = -move_y; + } + + x = (int16_t) (scaled_x >> 8); + y = (int16_t) (scaled_y >> 8); + return rect; +} diff --git a/src/test.cpp b/src/test.cpp new file mode 100644 index 0000000..f9ea010 --- /dev/null +++ b/src/test.cpp @@ -0,0 +1,27 @@ + +#include "colors.h" + +#include +#include +#include + + +#include "screen-settings.h" + +SPIClass hspi(HSPI); +Adafruit_SSD1351 screen(SCREEN_WIDTH, SCREEN_HEIGHT, &hspi, CS_PIN, DC_PIN, RST_PIN); + +void setup() { + screen.begin(32000000); + screen.fillScreen(0xFF4444_rgb565); +} + +int hue = 0; + +void loop() { + uint16_t color = HSL(hue, 100, 50).toRGB().alpha_blend_over_to565(RGB(00,50,00), 50); + screen.fillScreen(color); + hue += 1; + if (hue >= 360) hue -= 360; + delay(50); +} \ No newline at end of file diff --git a/src/wifi.cpp b/src/wifi.cpp new file mode 100644 index 0000000..34ca2ae --- /dev/null +++ b/src/wifi.cpp @@ -0,0 +1,179 @@ +#include "Commander.h" +#include "wifi/wifi_command.h" + +#include +#include +#include +#include + +#include "screen-settings.h" +#include "colors.h" + + +SPIClass hspi(HSPI); +Adafruit_SSD1351 screen(SCREEN_WIDTH, SCREEN_HEIGHT, &hspi, CS_PIN, DC_PIN, RST_PIN); + +Commander commander; + +RGB global_color; + +RGB parse_color(const std::vector ¶ms) { + // todo: rgb and hsl colors + return RGB(params.front().c_str()); +} + +void draw_set_color(const std::vector ¶ms, CommanderClient *client) { + if (params.empty()) { + client->println("no color provided"); + return; + } + global_color = parse_color(params); +} + +void draw_fill_screen(const std::vector ¶ms, CommanderClient *client) { + RGB color; + if (params.empty()) { + color = global_color; + } else { + color = parse_color(params); + } + screen.fillScreen(color.to_565()); +} + +// [color] +void draw_fill_rect(const std::vector ¶ms, CommanderClient *client) { + if (params.size() < 4) { + client->println("missing parameters"); + return; + } + int16_t x, y, width, height; + + if (!params[0].ReadInto(&x)) { + client->println("parameter x is not valid"); + return; + } + if (!params[1].ReadInto(&y)) { + client->println("parameter y is not valid"); + return; + } + if (!params[2].ReadInto(&width)) { + client->println("parameter width is not valid"); + return; + } + if (!params[3].ReadInto(&height)) { + client->println("parameter height is not valid"); + return; + } + + RGB color; + if (params.size() < 5) { + color = global_color; + } else { + color = parse_color(std::vector(params.begin()+4, params.end())); + } + + screen.fillRect(x, y, width, height, color.to_565()); +} + +// [color] +void draw_draw_line(const std::vector ¶ms, CommanderClient *client) { + if (params.size() < 4) { + client->println("missing parameters"); + return; + } + int16_t x1, y1, x2, y2; + + if (!params[0].ReadInto(&x1)) { + client->println("parameter x1 is not valid"); + return; + } + if (!params[1].ReadInto(&y1)) { + client->println("parameter y1 is not valid"); + return; + } + if (!params[2].ReadInto(&x2)) { + client->println("parameter x2 is not valid"); + return; + } + if (!params[3].ReadInto(&y2)) { + client->println("parameter y2 is not valid"); + return; + } + + RGB color; + if (params.size() < 5) { + color = global_color; + } else { + color = parse_color(std::vector(params.begin()+4, params.end())); + } + + screen.drawLine(x1, y1, x2, y2, color.to_565()); +} + +void setup() { + Serial.begin(9600); + + screen.begin(32000000); +// +// WiFi.mode(WIFI_AP); +// WiFi.softAP("esp32", "very gay"); + + // todo: automatically enable AP if configured + /*Serial.print("connecting to wifi..."); + if (WiFi.begin() != WL_CONNECT_FAILED) { + while (WiFi.status() != WL_CONNECTED && WiFi.status() != WL_CONNECT_FAILED) { + vTaskDelay(pdMS_TO_TICKS(500)); + Serial.print('.'); + } + Serial.println(); + if (WiFi.status() == WL_CONNECT_FAILED) { + Serial.println("connection failed"); + WiFi.enableSTA(false); + } else { + Serial.println("connected"); + } + }*/ + + auto hello_cmd = commander.RegisterCommand("hello", [](const std::vector ¶ms, CommanderClient *client) { + client->println("world"); + }); + + auto draw_cmd = commander.RegisterCommandWithShell("draw"); + + draw_cmd->SetPrompt("esp32/draw> "); + + draw_cmd->RegisterCommand("set-color ( | HSL | RGB )", + draw_set_color); + draw_cmd->RegisterCommand("fill-screen [color]", draw_fill_screen); + draw_cmd->RegisterCommand("fill-rect [color]", draw_fill_rect); + draw_cmd->RegisterCommand("draw-line [color]", draw_draw_line); + + registerWifiCommands(commander); + + commander.EnableSerial(); +// commander.EnableWifi(5000); + + + commander.SetPrompt("esp32> "); + + commander.Begin(); + +} + + + + +void loop() { +// WiFiClient client = server.available(); +// if (client) { +// Serial.println("[client connected]"); +// while (client.connected()) { +// if (client.available()) { +// char c = client.read(); +// Serial.write(c); +// } +// } +// client.stop(); +// Serial.println("[client disconnected]"); +// } +} \ No newline at end of file diff --git a/src/wifi/wifi_command.cpp b/src/wifi/wifi_command.cpp new file mode 100644 index 0000000..11f2a01 --- /dev/null +++ b/src/wifi/wifi_command.cpp @@ -0,0 +1,200 @@ +#include +#include "Commander.h" + + +void wifi_autoconnect(const std::vector ¶ms, CommanderClient *client) { + WiFi.enableSTA(true); + WiFi.begin(); + + WiFi.setAutoConnect(true); + WiFi.setAutoReconnect(true); + + client->print("connecting to network..."); + while (WiFi.status() != WL_CONNECTED && WiFi.status() != WL_CONNECT_FAILED) { + vTaskDelay(pdMS_TO_TICKS(500)); + client->print('.'); + } + client->println(); + if (WiFi.status() == WL_CONNECTED) { + client->print("connected to "); + client->println(WiFi.SSID()); + } else { + client->println("connection failed"); + } + +} + +void wifi_connect(const std::vector ¶ms, CommanderClient *client) { + WiFi.enableSTA(true); + client->println("scanning..."); + int16_t count = WiFi.scanNetworks(); + client->println("available networks:"); + for (int16_t i = 0; i < count; ++i) { + client->print(i); + client->print(") "); + client->print(WiFi.SSID(i)); + client->print("\t signal: "); + client->print(WiFi.RSSI(i)); + client->print("\t encryption: "); + switch (WiFi.encryptionType(i)) { + case WIFI_AUTH_OPEN: client->print("none"); + break; + case WIFI_AUTH_WEP: client->print("WEP"); + break; + case WIFI_AUTH_WPA_PSK: client->print("WPA PSK"); + break; + case WIFI_AUTH_WPA2_PSK: client->print("WPA2 PSK"); + break; + case WIFI_AUTH_WPA2_ENTERPRISE: client->print("WPA2 ENTERPRISE"); + break; + case WIFI_AUTH_WPA_WPA2_PSK: client->print("WPA/WPA2 PSK"); + break; + default: client->print("unknown"); + } + client->println(); + } + + int16_t selected_network; + do { + client->print("please select which network to connect to: "); + } while (!client->ReadLine(&selected_network) && selected_network < count); + + CmdString wifi_name = WiFi.SSID(selected_network); + + switch (WiFi.encryptionType(selected_network)) { + + case WIFI_AUTH_OPEN: WiFi.begin(wifi_name.c_str()); + break; + case WIFI_AUTH_WEP: + case WIFI_AUTH_WPA_PSK: + case WIFI_AUTH_WPA2_PSK: + case WIFI_AUTH_WPA_WPA2_PSK: + case WIFI_AUTH_WPA2_ENTERPRISE: { + client->print("wifi password: "); + CmdString key = client->ReadLine(); + WiFi.begin(wifi_name.c_str(), key.c_str()); + break; + } + default: client->println("error: unknown encryption type"); + return; + } + + WiFi.setAutoConnect(true); + WiFi.setAutoReconnect(true); + + client->print("connecting to network..."); + while (WiFi.status() != WL_CONNECTED && WiFi.status() != WL_CONNECT_FAILED) { + vTaskDelay(pdMS_TO_TICKS(500)); + client->print('.'); + } + client->println(); + if (WiFi.status() == WL_CONNECTED) { + client->print("connected to "); + client->println(WiFi.SSID()); + } else { + client->println("connection failed"); + } +} + +void wifi_disconnect(const std::vector ¶ms, CommanderClient *client) { + WiFi.disconnect(true); + client->print("disconnecting..."); + while (WiFi.status() != WL_DISCONNECTED) { + client->print('.'); + vTaskDelay(pdMS_TO_TICKS(500)); + } + + client->println(); + client->println("disconnected from wifi network"); + +} + +void wifi_status(const std::vector ¶ms, CommanderClient *client) { + if (!WiFi.isConnected()) { + client->println("wifi is not connected"); + return; + } + client->println("wifi is connected"); + + client->print("SSID: "); + client->println(WiFi.SSID()); + + client->print("RSSI: "); + client->println(WiFi.RSSI()); + + client->print("IPv4: "); + client->println(WiFi.localIP()); + + client->print("IPv6: "); + client->println(WiFi.localIPv6()); + + client->print("MAC: "); + client->println(WiFi.macAddress()); +} + +void wifi_ap_enable(const std::vector ¶ms, CommanderClient *client) { + WiFi.enableAP(true); + // todo: anything to do here? +} + +void wifi_ap_disable(const std::vector ¶ms, CommanderClient *client) { + WiFi.softAPdisconnect(true); +} + +void wifi_ap_setup(const std::vector ¶ms, CommanderClient *client) { + client->print("SSID for AP: "); + CmdString ssid = client->ReadLine(); + client->print("password for AP: "); + CmdString passphrase = client->ReadLine(); + + WiFi.softAP(ssid.c_str(), passphrase.c_str()); + + // todo: WiFi.softAPConfig, WiFi.softAPsetHostname, WiFi.softAPmacAddress +} + +void wifi_ap_status(const std::vector ¶ms, CommanderClient *client) { + if ((WiFi.getMode() & WIFI_MODE_AP) == 0) { // AP is disabled + client->println("AP is disabled"); + return; + } + client->print("SSID: "); + client->println(WiFi.softAPSSID()); + + client->print("MAC: "); + client->println(WiFi.softAPmacAddress()); + + client->print("IPv4: "); + client->println(WiFi.softAPIP()); + + client->print("IPv6: "); + client->println(WiFi.softAPIPv6()); + + client->print("network ID: "); + client->println(WiFi.softAPNetworkID()); + + client->print("broadcast IP: "); + client->println(WiFi.softAPBroadcastIP()); + + client->print("subnet: "); + client->println(WiFi.softAPSubnetCIDR()); + + client->print("hostname: "); + client->println(WiFi.softAPgetHostname()); + + client->print("connected clients: "); + client->println(WiFi.softAPgetStationNum()); +} + +void registerWifiCommands(Commander &commander) { + auto wifi_cmd = commander.RegisterCommand("wifi", nullptr); + wifi_cmd->RegisterCommand("autoconnect", wifi_connect); + wifi_cmd->RegisterCommand("connect", wifi_connect); + wifi_cmd->RegisterCommand("disconnect", wifi_disconnect); + wifi_cmd->RegisterCommand("status", wifi_status); + + auto wifi_ap_cmd = wifi_cmd->RegisterCommand("ap", nullptr); + wifi_ap_cmd->RegisterCommand("enable", wifi_ap_enable); + wifi_ap_cmd->RegisterCommand("disable", wifi_ap_disable); + wifi_ap_cmd->RegisterCommand("setup", wifi_ap_setup); + wifi_ap_cmd->RegisterCommand("status", wifi_ap_status); +} \ No newline at end of file diff --git a/test/README b/test/README new file mode 100644 index 0000000..d387df1 --- /dev/null +++ b/test/README @@ -0,0 +1,18 @@ + +This directory is intended for PlatformIO Unit Testing and project tests. + +Unit Testing is a software testing method by which individual units of +source code, sets of one or more MCU program modules together with associated +control data, usage procedures, and operating procedures, are tested to +determine whether they are fit for use. Unit testing finds problems early +in the development cycle. + +More information about PlatformIO Unit Testing: +- https://docs.platformio.org/page/plus/unit-testing.html + + + +lib_deps = + adafruit/Adafruit GFX Library@^1.10.13 + adafruit/Adafruit SSD1351 library@^1.2.7 + adafruit/Adafruit BusIO@^1.11.1 \ No newline at end of file