From 6dd17d3a6eed94fd2c2d661cdb79ea4cb112dcc8 Mon Sep 17 00:00:00 2001 From: Neil Davis Date: Wed, 19 Oct 2022 17:36:19 +0100 Subject: [PATCH 1/2] Fixes & improvements to Image, including scaling & arbitrary rotation. This commit adds bilinear interpolation scaling and arbirary rotation methods to `Image`. In the process, several issues with `Image` were found & fixed: * There was no destructor to free the `canvas` memory. This would cause a memory leak unless the client remembered to call `close()` A virtual destructor has been added to free this memory. * There was no explicit copy & move constructor for `Image` This means whenever Image was copied or passed by value a 'shallow copy' was performed resulting in both instances referencing the same shared canvas memory. This works, and several examples were depending on it, but after the addition of the destructor it caused seg faults since the memory was free()'d several times. An explicit copy constuctor for `Image` has been added to perform a 'deep copy' of the `canvas` memory. A C++11 'move' constructor has been added to avoid excessive deep copies being made when passing `Image` as a temporary. * The same logic for copy/move constructors also applies to assignment operators (operator=()) so these are added. * `Image` (and `Color`) are extensively passed by value within the library. This now potentially inefficient due to the deep copy. (if the move c'tor is not used) Since pass-by-reference semantics are required in these cases they are now passed by reference. * Pass-by-(const)-reference changes also required many methods on `Image` and `Color` that did not modify their members to be declared `const` as well as all `Color` preset constants. * New c'tors and getters added to `Color` to support RGBA access as required by the new scaling method on `Image` Also tidied up c'tors to use common helper methods. * Used new/delete instead of malloc/free for heap memory use in `Image` Just coz it's more conventional in C++ --- example/demo1.cpp | 40 +++- example/demo2.cpp | 4 +- example/neopixel2.cpp | 6 +- example/wsePaperV2.cpp | 4 +- src/controller/Color.cpp | 114 ++++------ src/controller/Color.h | 65 +++--- src/controller/Display.cpp | 14 +- src/controller/Display.h | 10 +- src/controller/Image.cpp | 329 +++++++++++++++++++++++---- src/controller/Image.h | 49 ++-- src/controller/Metadata.h | 7 + src/displays/DisplayNeoPixel.cpp | 8 +- src/displays/DisplayNeoPixel.h | 8 +- src/displays/DisplayWS_ePaper_v2.cpp | 20 +- src/displays/DisplayWS_ePaper_v2.h | 8 +- 15 files changed, 469 insertions(+), 217 deletions(-) diff --git a/example/demo1.cpp b/example/demo1.cpp index bdda2ff..b9bb2ec 100644 --- a/example/demo1.cpp +++ b/example/demo1.cpp @@ -41,7 +41,7 @@ unsigned long long currentTimeMillis() { } -void drawSine(Image image, float offset, float speed, int maxX, int maxY, float waveHeight, Color color, int width) { +void drawSine(Image &image, float offset, float speed, int maxX, int maxY, float waveHeight, Color color, int width) { bool first = true;; int lx = -1, ly = -1; double vx = 0; @@ -62,7 +62,7 @@ void drawSine(Image image, float offset, float speed, int maxX, int maxY, float } -bool demoSineWave(int frameCount, long long start, Image image) { +bool demoSineWave(int frameCount, long long start, Image &image) { long long now = currentTimeMillis(); float refVoltage = 5; @@ -148,6 +148,29 @@ void rotationDemo() { } +void imageRotationDemo(Image &img) { + Image srcImg = img.scale(0.75f, 0.75f, BILINEAR); + for (int deg = 0; deg < 360; deg += 5) { + printf("Rotate: %d degrees CW\n", deg); fflush(stdout); + d1.showImage(srcImg.rotate(deg, DEGREES), DEGREE_270); + } + for (int deg = 0; deg > -360; deg -= 5) { + printf("Rotate: %d degrees CCW\n", deg); fflush(stdout); + d1.showImage(srcImg.rotate(deg, DEGREES), DEGREE_270); + } +} + +void imageScalingDemo(Image &img) { + for (int i = 100; i > 0; i -= 10) { + float scale = i / 100.0f; + d1.showImage(img.scale(scale, scale, BILINEAR), DEGREE_270); + } + for (int i = 20; i <= 200; i += 10) { + float scale = i / 100.0f; + d1.showImage(img.scale(scale, scale, BILINEAR), DEGREE_270); + } +} + void display1Demo() { printf("demo1\n"); fflush(stdout); @@ -170,8 +193,8 @@ void display1Demo() { delay(solidsDelay); d1.clearScreen(BLACK); - d1.showImage(bmp, DEGREE_270); - delay(2000); + imageRotationDemo(bmp); + imageScalingDemo(bmp); rotationDemo(); delay(4000); @@ -189,6 +212,7 @@ void configureDisplay1() { d1Config.width = 240; d1Config.height = 320; d1Config.spiSpeed = spiSpeed; + d1Config.spiMode = 0; d1Config.CS = 21; d1Config.DC = 22; @@ -215,14 +239,6 @@ int main(int argc, char **argv) digitalWrite(10, HIGH); digitalWrite(11, HIGH); - int demos = 3; - pthread_t threads[demos]; - char message[demos][256]; - - for (int i = 0; i < demos; ++i) { - sprintf(message[i], "demo thread %d", i); - } - configureDisplay1(); printf("press control-c to quit\n"); diff --git a/example/demo2.cpp b/example/demo2.cpp index 5a53314..1ceae9a 100644 --- a/example/demo2.cpp +++ b/example/demo2.cpp @@ -42,7 +42,7 @@ unsigned long long currentTimeMillis() { } -void drawSine(Image image, float offset, float speed, int maxX, int maxY, float waveHeight, Color color, int width) { +void drawSine(Image &image, float offset, float speed, int maxX, int maxY, float waveHeight, Color color, int width) { bool first = true;; int lx = -1, ly = -1; double vx = 0; @@ -63,7 +63,7 @@ void drawSine(Image image, float offset, float speed, int maxX, int maxY, float } -bool demoSineWave(int frameCount, long long start, Image image) { +bool demoSineWave(int frameCount, long long start, Image &image) { long long now = currentTimeMillis(); float refVoltage = 5; diff --git a/example/neopixel2.cpp b/example/neopixel2.cpp index bfcb1e9..c34bf3b 100644 --- a/example/neopixel2.cpp +++ b/example/neopixel2.cpp @@ -73,7 +73,7 @@ unsigned long long currentTimeMillis() { } -void drawSine(Image image, float offset, float speed, int maxX, int maxY, float waveHeight, Color color, int width) { +void drawSine(Image &image, float offset, float speed, int maxX, int maxY, float waveHeight, Color color, int width) { bool first = true; int lx = -1, ly = -1; double vx = 0; @@ -94,7 +94,7 @@ void drawSine(Image image, float offset, float speed, int maxX, int maxY, float } -bool demoSineWave(int frameCount, long long start, Image image) { +bool demoSineWave(int frameCount, long long start, Image &image) { long long now = currentTimeMillis(); float refVoltage = 5; @@ -394,7 +394,7 @@ void* setBrightness(void *) { int startTime=currentTimeMillis(); for (int i=0;i<4;++i) { - float v=readVoltage(handle, i, 0); + float v=readVoltage(handle); if (v>6) { v=0; } diff --git a/example/wsePaperV2.cpp b/example/wsePaperV2.cpp index 76fb6b9..f674872 100644 --- a/example/wsePaperV2.cpp +++ b/example/wsePaperV2.cpp @@ -39,7 +39,7 @@ unsigned long long currentTimeMillis() { } -void drawSine(Image image, float offset, float speed, int maxX, int maxY, float waveHeight, Color color, int width) { +void drawSine(Image &image, float offset, float speed, int maxX, int maxY, float waveHeight, Color color, int width) { bool first = true;; int lx = -1, ly = -1; double vx = 0; @@ -60,7 +60,7 @@ void drawSine(Image image, float offset, float speed, int maxX, int maxY, float } -bool demoSineWave(int frameCount, long long start, Image image) { +bool demoSineWave(int frameCount, long long start, Image &image) { long long now = currentTimeMillis(); float refVoltage = 5; diff --git a/src/controller/Color.cpp b/src/controller/Color.cpp index 520b513..292b232 100644 --- a/src/controller/Color.cpp +++ b/src/controller/Color.cpp @@ -9,22 +9,22 @@ Color::Color() { color.opacity = 255; } - Color::Color(ColorType color) { - this->color.red = color.red; - this->color.green = color.green; - this->color.blue = color.blue; - this->color.opacity = color.opacity; + memcpy(&this->color, &color, sizeof(ColorType)); } - Color::Color(_byte red, _byte green, _byte blue) { - color.red = red; - color.green = green; - color.blue = blue; - color.opacity = 255; + setColor(red, green, blue, 255); +} + +Color::Color(_byte red, _byte green, _byte blue, _byte opacity) { + setColor(red, green, blue, opacity); } +Color::Color(uint32_t colorRGB24) { + setRGB24(colorRGB24); +} + Color::Color(const char* hexbytes) { char buf[3]; int intensity; @@ -68,63 +68,37 @@ Color::Color(const char* hexbytes) { } } -bool Color::equals(Color otherColor) { - if (this->color.blue != otherColor.color.blue) { - return false; - } - if (this->color.red!= otherColor.color.red) { - return false; - } - if (this->color.green != otherColor.color.green) { - return false; - } - return true; +bool Color::equals(const Color &otherColor) const { + return equals(otherColor.color); } -bool Color::equals(ColorType otherColor) { - if (this->color.blue != otherColor.blue) { +bool Color::equals(const ColorType &otherColor) const { + if (color.blue != otherColor.blue) { return false; } - if (this->color.red != otherColor.red) { + if (color.red != otherColor.red) { return false; } - if (this->color.green != otherColor.green) { + if (color.green != otherColor.green) { return false; } return true; } -bool Color::equals(ColorType *otherColor) { - if (this->color.blue != otherColor->blue) { - return false; - } - if (this->color.red != otherColor->red) { - return false; - } - if (this->color.green != otherColor->green) { - return false; - } - return true; -} - - -Color::Color(_byte red, _byte green, _byte blue, _byte opacity) { - color.red = red; - color.green = green; - color.blue = blue; - color.opacity = opacity; -} - -ColorType Color::toType() { - return color; +inline void Color::setColor(_byte red, _byte green, _byte blue, _byte opacity) { + color.red = red; + color.green = green; + color.blue = blue; + color.opacity = opacity; } - -int32_t Color::rgb24() { - return (color.green<<16)+(color.red<<8)+(color.blue); +inline void Color::setRGB24(uint32_t colorRGB24) { + color.red = (_byte)(colorRGB24 & 0xFF); + color.green = (_byte)((colorRGB24 & 0xFF00) >> 8); + color.blue = (_byte)((colorRGB24 & 0xFF0000) >> 16); + color.opacity = (_byte)((colorRGB24 & 0xFF000000) >> 24); } - Color::Color(int clr) { color.opacity = 255; @@ -150,7 +124,7 @@ Color::Color(int clr) { } -void Color::print() { +void Color::print() const { printf("color::print: 0x%02x%02x%02x\n", this->color.red, this->color.blue,this->color.green); printf("color::print: 0x%02x%02x%02x\n", color.red, color.blue, color.green); @@ -164,26 +138,26 @@ void Color::print() { // Original 8 -Color BLACK = Color(0, 0, 0); -Color RED = Color(255, 0, 0); -Color GREEN = Color(0, 255, 0); -Color BLUE = Color(0, 0, 255); -Color YELLOW = Color(255, 255, 0); -Color MAGENTA = Color(255, 0, 255); -Color CYAN = Color(0, 255, 255); -Color WHITE = Color(255, 255, 255); +const Color BLACK = Color(0, 0, 0); +const Color RED = Color(255, 0, 0); +const Color GREEN = Color(0, 255, 0); +const Color BLUE = Color(0, 0, 255); +const Color YELLOW = Color(255, 255, 0); +const Color MAGENTA = Color(255, 0, 255); +const Color CYAN = Color(0, 255, 255); +const Color WHITE = Color(255, 255, 255); // extended colors -Color GRAY = Color("#808080"); -Color BROWN = Color(165, 42, 42); -Color ORANGE = Color(255, 128, 0); +const Color GRAY = Color("#808080"); +const Color BROWN = Color(165, 42, 42); +const Color ORANGE = Color(255, 128, 0); -Color DARK_RED = Color(128, 0, 0); -Color DARK_GREEN = Color(0, 128, 0); -Color DARK_BLUE = Color(0, 0, 128); +const Color DARK_RED = Color(128, 0, 0); +const Color DARK_GREEN = Color(0, 128, 0); +const Color DARK_BLUE = Color(0, 0, 128); -Color LIGHT_BLUE = Color(204, 228, 255); -Color LIGHT_GRAY = Color("#E0E0E0"); +const Color LIGHT_BLUE = Color(204, 228, 255); +const Color LIGHT_GRAY = Color("#E0E0E0"); -Color DARK_GRAY_BLUE = Color("003366"); \ No newline at end of file +const Color DARK_GRAY_BLUE = Color("003366"); \ No newline at end of file diff --git a/src/controller/Color.h b/src/controller/Color.h index b6e3f03..401301f 100644 --- a/src/controller/Color.h +++ b/src/controller/Color.h @@ -12,6 +12,9 @@ struct ColorStruct { typedef ColorStruct ColorType; +inline uint32_t getRGB24(const ColorType *color) { + return (color->opacity << 24 ) | (color->blue << 16) | (color->green << 8) | color->red; +} class Color { public: @@ -19,42 +22,44 @@ class Color { Color(); Color(ColorType color); - Color(_byte red, _byte blue, _byte green); + Color(_byte red, _byte green, _byte blue); + Color(_byte red, _byte green, _byte blue, _byte opacity); + Color(uint32_t colorRGB24); Color(const char *hexbytes); - Color(_byte red, _byte blue, _byte green, _byte opacity); - + Color(int pos); - ColorType toType(); - int32_t rgb24(); - void print(); - bool equals(Color otherColor); - bool equals(ColorType otherColor); - bool equals(ColorType *otherColor); + inline ColorType toType() const { return color; } + inline uint32_t rgb24() const { return getRGB24(&color); } + inline void setColor(_byte red, _byte green, _byte blue, _byte opacity); + inline void setRGB24(uint32_t colorRGB24); + void print() const; + bool equals(const Color &otherColor) const; + bool equals(const ColorType &otherColor) const; }; // original 8 -extern Color BLACK; -extern Color RED; -extern Color GREEN; -extern Color BLUE; -extern Color YELLOW; -extern Color MAGENTA; -extern Color CYAN; -extern Color WHITE; - -extern Color GRAY; -extern Color BROWN; -extern Color ORANGE; - -extern Color LIGHT_BLUE; -extern Color LIGHT_GRAY; - -extern Color DARK_BLUE; -extern Color DARK_RED; -extern Color DARK_GREEN; - -extern Color DARK_GRAY_BLUE; +extern const Color BLACK; +extern const Color RED; +extern const Color GREEN; +extern const Color BLUE; +extern const Color YELLOW; +extern const Color MAGENTA; +extern const Color CYAN; +extern const Color WHITE; + +extern const Color GRAY; +extern const Color BROWN; +extern const Color ORANGE; + +extern const Color LIGHT_BLUE; +extern const Color LIGHT_GRAY; + +extern const Color DARK_BLUE; +extern const Color DARK_RED; +extern const Color DARK_GREEN; + +extern const Color DARK_GRAY_BLUE; diff --git a/src/controller/Display.cpp b/src/controller/Display.cpp index b0b9148..058193c 100644 --- a/src/controller/Display.cpp +++ b/src/controller/Display.cpp @@ -138,7 +138,7 @@ namespace udd { } } - void Display::clearWindow(Color color, Point p1, Point p2, Rotation rotation) { + void Display::clearWindow(const Color &color, Point p1, Point p2, Rotation rotation) { screenLock.lock(); openSPI(); @@ -168,7 +168,7 @@ namespace udd { screenLock.unlock(); } - void Display::clearScreen(Color color) { + void Display::clearScreen(const Color &color) { screenLock.lock(); openSPI(); resume(); @@ -204,11 +204,11 @@ namespace udd { } - void Display::showImage(Image& image) { + void Display::showImage(const Image& image) { showImage(image, Point(0,0), Point(config.width-1, config.height-1), DEGREE_0); } - void Display::showImage(Image& image, Rotation rotation) { + void Display::showImage(const Image& image, Rotation rotation) { Point p1 = Point(0, 0); Point p2 = Point(config.width-1, config.height-1); @@ -218,7 +218,7 @@ namespace udd { showImage(image, p1, p2, rotation); } - void Display::showImage(Image& image, Point p1, Point p2, Rotation rotation) { + void Display::showImage(const Image& image, Point p1, Point p2, Rotation rotation) { int width, height; screenLock.lock(); openSPI(); @@ -232,8 +232,8 @@ namespace udd { if (image.getHeight() != height || image.getWidth() != width) { - fprintf(stderr, "Image size does not match window size. Height: [Image=%d, Window=%d] Width: [Image=%d, Window=%d]\n", - image.getHeight(), height, image.getWidth(), width); + fprintf(stderr, "Image size does not match window size. Image: (%dx%d) Window: (%dx%d)\n", + image.getWidth(), image.getHeight(), width, height); } diff --git a/src/controller/Display.h b/src/controller/Display.h index a229056..d2f6890 100644 --- a/src/controller/Display.h +++ b/src/controller/Display.h @@ -75,12 +75,12 @@ namespace udd { void openDisplay(DisplayConfigruation configuratrion); - virtual void clearScreen(Color color); - virtual void clearWindow(Color color, Point p1, Point p2, Rotation rotation); + virtual void clearScreen(const Color &color); + virtual void clearWindow(const Color &color, Point p1, Point p2, Rotation rotation); - virtual void showImage(Image& image); - virtual void showImage(Image& image, Rotation rotation); - virtual void showImage(Image& image, Point p1, Point p2, Rotation rotation); + virtual void showImage(const Image& image); + virtual void showImage(const Image& image, Rotation rotation); + virtual void showImage(const Image& image, Point p1, Point p2, Rotation rotation); virtual void readBusy() { fprintf(stderr, "readBusy() is not implemented for this method\n"); diff --git a/src/controller/Image.cpp b/src/controller/Image.cpp index d5e5614..10c69b2 100644 --- a/src/controller/Image.cpp +++ b/src/controller/Image.cpp @@ -1,42 +1,108 @@ #include "Image.h" #include "fonts.h" #include +#include +#include #include "GUI_BMPfile.h" using namespace udd; -Image::Image() { - width=0; - height=0; - backgroundColor=BLACK; +#undef max // in case any sys header defines it +#undef min // in case any sys header defines it +inline float min(float a, float b) { return (a < b) ? a : b; }; +inline float max(float a, float b) { return (a < b) ? b : a; }; +inline float lerp(float s, float e, float t) {return s+(e-s)*t;} +inline float blerp(float c00, float c10, float c01, float c11, float tx, float ty) { + return lerp(lerp(c00, c10, tx), lerp(c01, c11, tx), ty); +} +inline bool floatsAreEqual(float f1, float f2, float epsilon) { + return fabsf(f1 - f2) < epsilon; +} +inline bool floatsAreEqual(float f1, float f2) { + return floatsAreEqual(f1, f2, std::numeric_limits::epsilon()); +} +const static float RADIANS_EPSILON = 1e-5; +inline bool radiansAreEqual(float f1, float f2) { + return floatsAreEqual(f1, f2, RADIANS_EPSILON); } +#define getByte(value, n) (value >> (n*8) & 0xFF) +// Default c'tor +Image::Image() : Image(0, 0, BLACK) { +} -Image::Image(int width, int height, Color backgroundColor) { - this->width = width; - this->height = height; +// Copy c'tor +Image::Image(const Image &img) { + copy(img); +} - this->backgroundColor = backgroundColor; +// Move c'tor +Image::Image(Image &&img) + : canvas(img.canvas) + , width(img.width) + , height(img.height) + , backgroundColor(std::move(img.backgroundColor)) { + img.canvas = NULL; // We are 'moving' canvas data from the rvalue-ref temp 'img' +} - uint32_t imageSize = width * height; - uint32_t memorySize = imageSize * sizeof(ColorType); +// (Copy) Assignment operator +Image& Image::operator=(const Image &img) { + close(); // free up any existing memory + copy(img); + return *this; +} - canvas = (ColorType*)malloc(memorySize); +// (Move) Assignment operator +Image& Image::operator=(Image &&img) { + canvas = img.canvas; + img.canvas = NULL; // We are 'moving' canvas data from the rvalue-ref temp 'img' + width = img.width; + height = img.height; + backgroundColor = std::move(img.backgroundColor); + return *this; +} +Image::Image(int width, int height, const Color &backgroundColor) + : width(width) + , height(height) + , backgroundColor(backgroundColor) { + init(); clear(backgroundColor); } -int udd::Image::getWidth() { +// Private initializer helper +void Image::init() { + uint32_t imageSize = width * height; + canvas = NULL; + if (imageSize > 0) { + canvas = new ColorType[imageSize]; + } +} + +void Image::copy(const Image &img) { + width = img.width; + height = img.height; + backgroundColor = img.backgroundColor; + init(); + memcpy(canvas, img.canvas, width * height * sizeof(ColorType)); +} + +// D'tor +Image::~Image() { + close(); +} + +int udd::Image::getWidth() const { return width; } -int udd::Image::getHeight() { +int udd::Image::getHeight() const { return height; } -void Image::clear(Color backgroundColor) { +void Image::clear(const Color &backgroundColor) { for (int x = 0; x < width; ++x) { for (int y = 0; y < height; ++y) { ColorType* xp = getPixelColor(x, y); @@ -49,10 +115,17 @@ void Image::clear(Color backgroundColor) { } void Image::close() { - free(canvas); + if (canvas) { + delete[] canvas; + canvas = NULL; + } } -void Image::drawPixel(int x, int y, Color color) { +void Image::drawPixel(int x, int y, const Color &color) { + drawPixel(x, y, color.color); +} + +void Image::drawPixel(int x, int y, const ColorType &color) { if (x < 0) return; if (y < 0) return; if (x >= width) return; @@ -60,21 +133,12 @@ void Image::drawPixel(int x, int y, Color color) { ColorType* xp = getPixelColor(x, y); - xp->red = color.color.red; - xp->green = color.color.green; - xp->blue = color.color.blue; - xp->opacity = color.color.opacity; - - - if (color.color.red != 0 || - color.color.green != 0 || - color.color.blue != 0 - ) { - } + xp->red = color.red; + xp->green = color.green; + xp->blue = color.blue; + xp->opacity = color.opacity; } - - void Image::printPixel(int x, int y) { ColorType* xp = getPixelColor(x, y); @@ -86,20 +150,19 @@ void Image::printPixel(int x, int y) { } } -ColorType* Image::getPixelColor(int x, int y) { +ColorType* Image::getPixelColor(int x, int y) const { if (x<0 || x >= width || y<0 || y>=height) { return NULL; } long offset = (y * width) + x; - ColorType* xp = canvas + offset; - return xp; + return &canvas[offset]; } -ColorType* Image::getPixel(int x, int y, Rotation rotation) { +ColorType* Image::getPixel(int x, int y, Rotation rotation) const { switch (rotation) { case DEGREE_0: return getPixelColor(x, y); case DEGREE_90: return getPixelColor(y, height-x-1); @@ -117,7 +180,7 @@ _word Image::color2word(ColorType* xp) { return ((0x1f & (xp->blue)) << 11) | ((0x3f & (xp->red)) << 5) | ((0x1f & (xp->green))); } -void Image::drawPoint(int x, int y, Color color, int width) { +void Image::drawPoint(int x, int y, const Color &color, int width) { switch (width) { case 0: @@ -147,7 +210,7 @@ void Image::drawPoint(int x, int y, Color color, int width) { } -void Image::drawLine(int x1, int y1, int x2, int y2, Color color, LineStyle style, int width) { +void Image::drawLine(int x1, int y1, int x2, int y2, const Color &color, LineStyle style, int width) { float x, y, dx, dy; int i, longestLeg, rx, ry; @@ -187,7 +250,7 @@ void Image::drawLine(int x1, int y1, int x2, int y2, Color color, LineStyle styl void Image::drawText(int Xstart, int Ystart, const char* pString, - sFONT* Font, Color background, Color foreground) { + sFONT* Font, const Color &background, const Color &foreground) { int Xpoint = Xstart; @@ -217,7 +280,7 @@ void Image::drawText(int Xstart, int Ystart, const char* pString, } void Image::drawChar(int Xpoint, int Ypoint, const char Acsii_Char, - sFONT* Font, Color background, Color foreground) { + sFONT* Font, const Color &background, const Color &foreground) { int Page, Column; @@ -359,19 +422,19 @@ void Image::loadBMP(FILE *fp, int Xstart, int Ystart) { } -void Image::drawRectangle(int x1, int y1, int x2, int y2, Color Color, +void Image::drawRectangle(int x1, int y1, int x2, int y2, const Color &color, FillPattern pattern, LineStyle lineStyle, int width) { switch (pattern) { case NONE: - drawLine(x1, y1, x2, y1, Color, lineStyle, width); - drawLine(x1, y1, x1, y2, Color, lineStyle, width); - drawLine(x2, y2, x2, y1, Color, lineStyle, width); - drawLine(x2, y2, x1, y2, Color, lineStyle, width); + drawLine(x1, y1, x2, y1, color, lineStyle, width); + drawLine(x1, y1, x1, y2, color, lineStyle, width); + drawLine(x2, y2, x2, y1, color, lineStyle, width); + drawLine(x2, y2, x1, y2, color, lineStyle, width); break; case FILL: for (int y = y1; y < y2; y++) { - drawLine(x1, y, x2, y, Color, lineStyle, width); + drawLine(x1, y, x2, y, color, lineStyle, width); } break; case MASK: @@ -395,7 +458,7 @@ void Image::arcPoint(int x, int y, int radius, double degree, int* xPoint, int* *yPoint = iy; } -void Image::drawCircle(int x, int y, int radius, Color color, FillPattern pattern, LineStyle lineStyle, int width) { +void Image::drawCircle(int x, int y, int radius, const Color &color, FillPattern pattern, LineStyle lineStyle, int width) { double xPoint, yPoint; @@ -427,7 +490,7 @@ void Image::drawCircle(int x, int y, int radius, Color color, FillPattern patter } } -void Image::drawLineArc(int x, int y, int length, float degree, Color color, LineStyle style, int width) { +void Image::drawLineArc(int x, int y, int length, float degree, const Color &color, LineStyle style, int width) { double xPoint, yPoint; @@ -437,7 +500,7 @@ void Image::drawLineArc(int x, int y, int length, float degree, Color color, Lin drawLine(x, y, x + round(xPoint), y + round(yPoint), color, style, width); } -void Image::drawPieSlice(int x, int y, int radius, float degree1, float degree2, Color color, LineStyle style, int width) { +void Image::drawPieSlice(int x, int y, int radius, float degree1, float degree2, const Color &color, LineStyle style, int width) { double xPoint, yPoint; @@ -468,7 +531,179 @@ void Image::drawPieSlice(int x, int y, int radius, float degree1, float degree2, } } } +} + +Image Image::scale(float scaleX, float scaleY, ScaleMode mode) { + if (floatsAreEqual(scaleX, 1.0f) && floatsAreEqual(scaleY, 1.0f)) { + // 100% scale. Optimize by copy + return Image(*this); + } + int newWidth = ceilf(width * scaleX); + int newHeight = ceilf(height * scaleY); + Image newImage(newWidth, newHeight, backgroundColor); + switch (mode) { + case BILINEAR: + Image::scaleBilinear(*this, newImage); + break; + default: + fprintf(stderr, "not yet implemented (mode=%d)\n", mode); + } + return newImage; +} + +void Image::scaleBilinear(const Image &src, Image &dst) { + for (int y = 0; y < dst.height; y++) { + for (int x = 0; x < dst.width; x++) { + float gx = min(x / (float)(dst.width) * (src.width) - 0.5f, src.width - 1); + float gy = min(y / (float)(dst.height) * (src.height) - 0.5f, src.height - 1); + int gxi = (int)gx, gyi = (int)gy; + int gxi1 = min(gxi+1, src.width - 1); + int gyi1 = min(gyi+1, src.height - 1); + uint32_t result=0; + uint32_t c00 = getRGB24(src.getPixelColor(gxi, gyi)); + uint32_t c10 = getRGB24(src.getPixelColor(gxi1, gyi)); + uint32_t c01 = getRGB24(src.getPixelColor(gxi, gyi1)); + uint32_t c11 = getRGB24(src.getPixelColor(gxi1, gyi1)); + for(uint8_t i = 0; i < 3; i++){ + result |= (uint8_t)blerp(getByte(c00, i), getByte(c10, i), getByte(c01, i), getByte(c11, i), gx - gxi, gy -gyi) << (8*i); + } + dst.drawPixel(x,y, Color(result)); + } + } +} +Image Image::rotate(float angle, AngleUnit units) { + if (DEGREES == units) { + angle *= PI / 180.0f; + } + angle = -fmodf(angle, 2.0f*PI); // -2*PI <= angle <= 2*PI + if (angle < -PI) { + angle += 2.0f*PI; + } else if (angle > PI) { + angle -= 2.0f*PI; + } + // Now: -PI <= angle <= PI + // Some cases can be optimized when multiples of 90 degrees + // so that we don't need to do any trigonometry operations + udd::Rotation rotation = DEGREE_INVALID; + if (radiansAreEqual(angle, 0.0f)) { + rotation = DEGREE_0; + } else if (radiansAreEqual(angle, PI/2.0f)) { + rotation = DEGREE_90; + } else if (radiansAreEqual(fabsf(angle), PI)) { + rotation = DEGREE_180; + } else if (radiansAreEqual(angle, PI/-2.0f)) { + rotation = DEGREE_270; + } + if (DEGREE_INVALID != rotation) { + return simpleRotate(rotation); + } + // Three shear rotate is effective in the range -PI/2 < angle < PI/2 + // and gives poor results close to +/- PI + // For angles outside this range, we optimize using a two-stage rotate + // using a simpleRotate() as the first step. It will use more memory + // and be slightly slower but will give superior results. + if (angle < -PI/2.0f) { + return simpleRotate(DEGREE_270).threeShearRotate(angle + PI/2.0f); + } + if (angle > PI/2.0f) { + return simpleRotate(DEGREE_90).threeShearRotate(angle - PI/2.0f); + } + return threeShearRotate(angle); +} +Image Image::simpleRotate(udd::Rotation rotation) { + int newWidth = width; + int newHeight = height; + switch (rotation) { + case DEGREE_INVALID: + fprintf(stderr, "Not supported simpleRotate(DEGREE_INVALID)"); + // fallthrough + case DEGREE_0: + // No rotation, optimize by copy + return Image(*this); + case DEGREE_90: + case DEGREE_270: + newWidth = height; + newHeight = width; + break; + default: + break; + } + Image newImage(newWidth, newHeight, backgroundColor); + + // Find the centres of the original and new images + int centreX = width / 2; + int centreY = height / 2; + int newCentreX = newWidth / 2; + int newCentreY = newHeight / 2; + + ColorType *destPixel = NULL; + for (int x = 0; x < width; x++) { + for (int y = 0; y < height; y++) { + // map coords w.r.t centre + int relX = x - centreX; + int relY = height - y - centreY; + int newX = relX, newY = relY; + switch (rotation) { + default: + case DEGREE_0: break; + case DEGREE_90: newX = -relY; newY = relX; break; + case DEGREE_180: newX = -relX; newY = -relY; break; + case DEGREE_270: newX = relY; newY = -relX; break; + } + // Convert back from centre to new img origin coords + newX = max(0, min(newCentreX + newX, newWidth - 1)); + newY = max(0, min(newHeight - (newCentreY + newY), newHeight - 1)); + destPixel = newImage.getPixelColor(newX, newY); + if (destPixel) { + memcpy(destPixel, getPixelColor(x, y), sizeof(ColorType)); + } + } + } + return newImage; +} -} \ No newline at end of file +Image Image::threeShearRotate(float angleRads) { + // Pre-calc some trig ops we will need more than once + float sine = sinf(angleRads); + float cosine = cosf(angleRads); + float shearTangent = tanf(angleRads/2.0f); + + // Create a new image at the correct size to hold the rotated image + int newWidth = roundf(abs(width * cosine) + abs(height * sine)); + int newHeight = roundf(abs(height * cosine) + abs(width * sine)); + + // Find the centres of the original and new images + int centreX = width / 2; + int centreY = height / 2; + int newCentreX = newWidth / 2; + int newCentreY = newHeight / 2; + + // Perform the three-shear rotation using coords w.r.t. centres + // https://datagenetics.com/blog/august32013/index.html + Image img(newWidth, newHeight, backgroundColor); + for (int y = 0; y < height; y++) { + for (int x = 0; x < width; x++) { + // map coords w.r.t centre + int newX = x - centreX; + int newY = height - y - centreY; + // Shear 1 + newX = roundf(newX - newY * shearTangent); + // Shear 2 + newY = roundf(newX * sine + newY); + // Shear 3 + newX = roundf(newX - newY * shearTangent); + // Convert back from centre to img origin coords + newX = max(0, min(newCentreX + newX, newWidth -1)); + newY = max(0, min(newHeight - (newCentreY + newY), newHeight - 1)); + // Write pixel into new image + ColorType *srcPixel = getPixelColor(x, y); + ColorType *destPixel = &img.canvas[newY * newWidth + newX]; + if (destPixel) { + memcpy(destPixel, srcPixel, sizeof(ColorType)); + } + } + } + return img; +} diff --git a/src/controller/Image.h b/src/controller/Image.h index f4b384c..40d2a99 100644 --- a/src/controller/Image.h +++ b/src/controller/Image.h @@ -4,7 +4,7 @@ #include #include -#include +#include "Color.h" #include "inttypes.h" #include "fonts.h" @@ -17,43 +17,57 @@ namespace udd { class Image { public: Image(); + Image(const Image &img); + Image(Image &&img); + Image& operator=(const Image &img); + Image& operator=(Image &&img); + virtual ~Image(); - Image(int width, int height, Color backgroundColor); + Image(int width, int height, const Color &backgroundColor); - int getWidth(); - int getHeight(); + int getWidth() const; + int getHeight() const; - void clear(Color backgroundColor); + void clear(const Color &backgroundColor); void close(); - void drawPixel(int x, int y, Color color); + void drawPixel(int x, int y, const Color &color); + void drawPixel(int x, int y, const ColorType &color); - void drawLine(int x1, int y1, int x2, int y2, Color color, LineStyle style, int width); - void drawLineArc(int x, int y, int radius, float degree, Color color, LineStyle style, int width); + void drawLine(int x1, int y1, int x2, int y2, const Color &color, LineStyle style, int width); + void drawLineArc(int x, int y, int radius, float degree, const Color &color, LineStyle style, int width); - void drawPoint(int x, int y, Color color, int width); + void drawPoint(int x, int y, const Color &color, int width); void drawText(int Xstart, int Ystart, const char* pString, - sFONT* Font, Color background, Color forground); + sFONT* Font, const Color &background, const Color &forground); - void drawChar(int Xpoint, int Ypoint, const char Acsii_Char, sFONT* Font, Color Color_Background, Color Color_Foreground); + void drawChar(int Xpoint, int Ypoint, const char Acsii_Char, sFONT* Font, const Color &Color_Background, const Color &Color_Foreground); void loadBMP(FILE* file, int Xstart, int Ystart); void loadBMP(const char* filename, int Xstart, int Ystart); - void drawRectangle(int x1, int y1, int x2, int y2, Color Color, FillPattern pattern, LineStyle lineStyle, int width); + void drawRectangle(int x1, int y1, int x2, int y2, const Color &Color, FillPattern pattern, LineStyle lineStyle, int width); void arcPoint(int x, int y, int length, double degree, int* xPoint, int* yPoint); - void drawCircle(int x, int y, int radius, Color Color, FillPattern pattern, LineStyle lineStyle, int width); - void drawPieSlice(int x, int y, int radius, float degree1, float degree2, Color color, LineStyle style, int width); + void drawCircle(int x, int y, int radius, const Color &Color, FillPattern pattern, LineStyle lineStyle, int width); + void drawPieSlice(int x, int y, int radius, float degree1, float degree2, const Color &color, LineStyle style, int width); void printPixel(int x, int y); - ColorType* getPixel(int x, int y, udd::Rotation rotation); + ColorType* getPixel(int x, int y, udd::Rotation rotation) const; - ColorType* getPixelColor(int x, int y); + ColorType* getPixelColor(int x, int y) const; + + Image scale(float scaleX, float scaleY, ScaleMode mode); + Image rotate(float angle, AngleUnit units); + + private: + static void scaleBilinear(const Image &src, Image &dst); + Image simpleRotate(udd::Rotation rotation); + Image threeShearRotate(float angleRads); private: @@ -64,6 +78,7 @@ namespace udd { Color backgroundColor; _word color2word(ColorType* xp); - + void init(); + void copy(const Image& img); }; } diff --git a/src/controller/Metadata.h b/src/controller/Metadata.h index 95853dc..d15563c 100644 --- a/src/controller/Metadata.h +++ b/src/controller/Metadata.h @@ -36,6 +36,13 @@ namespace udd { MASK } FillPattern; + typedef enum { + BILINEAR + } ScaleMode; + typedef enum { + DEGREES, + RADIANS + } AngleUnit; } diff --git a/src/displays/DisplayNeoPixel.cpp b/src/displays/DisplayNeoPixel.cpp index 87fcc1a..cf9c682 100644 --- a/src/displays/DisplayNeoPixel.cpp +++ b/src/displays/DisplayNeoPixel.cpp @@ -46,7 +46,7 @@ namespace udd { this->vImage.drawPoint(pixel.point.x, pixel.point.y, pixel.color, 1); } - void DisplayNeoPixel::clearScreen(Color color) { + void DisplayNeoPixel::clearScreen(const Color &color) { for (int x=0;x points); - void clearScreen(Color color); + void clearScreen(const Color &color); - void showImage(Image image, Rotation rotation, ScreenMirror mirror); - void showImage(Image image, Rotation rotation); - void showImage(Image image); + void showImage(const Image &image, Rotation rotation, ScreenMirror mirror); + void showImage(const Image &image, Rotation rotation); + void showImage(const Image &image); void setPixel(Pixel pixel); private: diff --git a/src/displays/DisplayWS_ePaper_v2.cpp b/src/displays/DisplayWS_ePaper_v2.cpp index 2b9af48..4825429 100644 --- a/src/displays/DisplayWS_ePaper_v2.cpp +++ b/src/displays/DisplayWS_ePaper_v2.cpp @@ -96,7 +96,7 @@ namespace udd { } - void DisplayWS_ePaper_v2::clearScreen(Color color) { + void DisplayWS_ePaper_v2::clearScreen(const Color &color) { screenLock.lock(); openSPI(); @@ -167,16 +167,16 @@ namespace udd { } } - void DisplayWS_ePaper_v2::showImage(Image& image) { + void DisplayWS_ePaper_v2::showImage(const Image& image) { Display::showImage(image); } - void DisplayWS_ePaper_v2::showImage(Image& image, Rotation rotation) { + void DisplayWS_ePaper_v2::showImage(const Image& image, Rotation rotation) { Display::showImage(image, rotation); } - void DisplayWS_ePaper_v2::showImage(Image &image, Point p1, Point p2, Rotation rotation) { + void DisplayWS_ePaper_v2::showImage(const Image &image, Point p1, Point p2, Rotation rotation) { fprintf(stderr, "ePaper showImage(%d,%d)\n", config.width, config.height); @@ -198,13 +198,13 @@ namespace udd { ColorType* ct = image.getPixel(x - config.xOffset, y - config.yOffset, rotation); int val=1; if (ct != NULL) { - if (WHITE.equals(ct)) { + if (WHITE.equals(*ct)) { val=1; } - else if (BLACK.equals(ct)) { + else if (BLACK.equals(*ct)) { val=0; } - else if (RED.equals(ct)) { + else if (RED.equals(*ct)) { val=1; } else { fprintf(stderr, "invalid color found at (%d,%d)\n", x, y); @@ -232,13 +232,13 @@ namespace udd { ColorType* ct = image.getPixel(x - config.xOffset, y - config.yOffset, rotation); int val=1; if (ct != NULL) { - if (WHITE.equals(ct)) { + if (WHITE.equals(*ct)) { val=1; } - else if (BLACK.equals(ct)) { + else if (BLACK.equals(*ct)) { val=1; } - else if (RED.equals(ct)) { + else if (RED.equals(*ct)) { val=0; } else { fprintf(stderr, "invalid color found at (%d,%d)\n", x, y); diff --git a/src/displays/DisplayWS_ePaper_v2.h b/src/displays/DisplayWS_ePaper_v2.h index 2aa1b17..e6f690f 100644 --- a/src/displays/DisplayWS_ePaper_v2.h +++ b/src/displays/DisplayWS_ePaper_v2.h @@ -16,10 +16,10 @@ namespace udd { void initPartial(); void reset() override; void readBusy() override; - void clearScreen(Color color) override; - void showImage(Image& image) override; - void showImage(Image& image, Rotation rotation) override; - void showImage(Image& image, Point p1, Point p2, Rotation rotation) override; + void clearScreen(const Color &color) override; + void showImage(const Image& image) override; + void showImage(const Image& image, Rotation rotation) override; + void showImage(const Image& image, Point p1, Point p2, Rotation rotation) override; void EPD_SetFullReg(); void setPartReg(); void enableDisplay(); From d0299a3639f164b607222c6feb111780eda31e0a Mon Sep 17 00:00:00 2001 From: Neil Davis Date: Sun, 23 Oct 2022 13:50:14 +0100 Subject: [PATCH 2/2] Fix typos --- build.sh | 8 ++++---- example/demo1.cpp | 2 +- example/demo2.cpp | 4 ++-- example/neopixel.cpp | 2 +- example/neopixel2.cpp | 2 +- example/wsePaperV2.cpp | 2 +- src/controller/Display.cpp | 2 +- src/controller/Display.h | 6 +++--- src/displays/DisplayNeoPixel.cpp | 2 +- src/displays/DisplayNeoPixel.h | 2 +- 10 files changed, 16 insertions(+), 16 deletions(-) mode change 100644 => 100755 build.sh diff --git a/build.sh b/build.sh old mode 100644 new mode 100755 index 8f4203e..d702bd3 --- a/build.sh +++ b/build.sh @@ -168,22 +168,22 @@ package() { ls -tr $LIB/* 2>/dev/null | tail -1 | grep '\.a' > /dev/null A=$? - echo Buliding source for dynamic library for sake of size and compatibility + echo Building source for dynamic library for sake of size and compatibility DYNAMIC="-fPIC" [ $A = 0 ] && clean objects build - echo Packagiang lib$LIBNAME.so + echo Packaging lib$LIBNAME.so OBJECTS=$(find $OBJ -type f) ${CC} -shared -Wl,-soname,lib$LIBNAME.so -o $LIB/lib$LIBNAME.so.$VERSION ${OBJECTS} || die clean objects fi - echo Buliding source for static library for sake of runtime speed + echo Building source for static library for sake of runtime speed DYNAMIC="" build - echo Packagiang lib$LIBNAME.a + echo Packaging lib$LIBNAME.a OBJECTS=$(find $OBJ -type f) ar rcs $LIB/lib$LIBNAME.a $OBJECTS || die ranlib $LIB/lib$LIBNAME.a || die diff --git a/example/demo1.cpp b/example/demo1.cpp index b9bb2ec..4e543bd 100644 --- a/example/demo1.cpp +++ b/example/demo1.cpp @@ -19,7 +19,7 @@ Wade Ryan using namespace udd; -DisplayConfigruation d1Config; +DisplayConfiguration d1Config; DisplayST7789R d1 = DisplayST7789R(); diff --git a/example/demo2.cpp b/example/demo2.cpp index 1ceae9a..d0cc924 100644 --- a/example/demo2.cpp +++ b/example/demo2.cpp @@ -20,8 +20,8 @@ Wade Ryan using namespace udd; -DisplayConfigruation d1Config; -DisplayConfigruation d2Config; +DisplayConfiguration d1Config; +DisplayConfiguration d2Config; DisplayST7789R d1 = DisplayST7789R(); DisplayST7735R d2 = DisplayST7735R(); diff --git a/example/neopixel.cpp b/example/neopixel.cpp index 3687162..4b2f2a3 100644 --- a/example/neopixel.cpp +++ b/example/neopixel.cpp @@ -23,7 +23,7 @@ I'm using an 8x8 matrix using namespace udd; -DisplayConfigruation d1Config; +DisplayConfiguration d1Config; DisplayNeoPixel d1 = DisplayNeoPixel(); diff --git a/example/neopixel2.cpp b/example/neopixel2.cpp index c34bf3b..b317259 100644 --- a/example/neopixel2.cpp +++ b/example/neopixel2.cpp @@ -44,7 +44,7 @@ Here is the layout: using namespace udd; -DisplayConfigruation d1Config; +DisplayConfiguration d1Config; DisplayNeoPixel d1 = DisplayNeoPixel(); diff --git a/example/wsePaperV2.cpp b/example/wsePaperV2.cpp index f674872..ee3a60d 100644 --- a/example/wsePaperV2.cpp +++ b/example/wsePaperV2.cpp @@ -19,7 +19,7 @@ Wade Ryan using namespace udd; -DisplayConfigruation d1Config; +DisplayConfiguration d1Config; DisplayWS_ePaper_v2 d1 = DisplayWS_ePaper_v2(); diff --git a/src/controller/Display.cpp b/src/controller/Display.cpp index 058193c..724eb27 100644 --- a/src/controller/Display.cpp +++ b/src/controller/Display.cpp @@ -43,7 +43,7 @@ namespace udd { - void Display::openDisplay(DisplayConfigruation configuration) { + void Display::openDisplay(DisplayConfiguration configuration) { this->config = configuration; // this->vImage = Image(config.width, config.height, BLACK); diff --git a/src/controller/Display.h b/src/controller/Display.h index d2f6890..531298e 100644 --- a/src/controller/Display.h +++ b/src/controller/Display.h @@ -50,7 +50,7 @@ namespace udd { int brightness; }; - typedef DisplayConfigurationStruct DisplayConfigruation; + typedef DisplayConfigurationStruct DisplayConfiguration; Rotation validateRotation(char* rotation); @@ -67,12 +67,12 @@ namespace udd { virtual void setWindow(Point p1, Point p2, Rotation rotation); public: - DisplayConfigruation config; + DisplayConfiguration config; virtual void init(); - void openDisplay(DisplayConfigruation configuratrion); + void openDisplay(DisplayConfiguration configuratrion); virtual void clearScreen(const Color &color); diff --git a/src/displays/DisplayNeoPixel.cpp b/src/displays/DisplayNeoPixel.cpp index cf9c682..1cee64c 100644 --- a/src/displays/DisplayNeoPixel.cpp +++ b/src/displays/DisplayNeoPixel.cpp @@ -5,7 +5,7 @@ namespace udd { DisplayNeoPixel::DisplayNeoPixel() : Display() {} - void DisplayNeoPixel::openDisplay(DisplayConfigruation configuration) { + void DisplayNeoPixel::openDisplay(DisplayConfiguration configuration) { this->config = configuration; this->vImage = Image(config.width, config.height, BLACK); diff --git a/src/displays/DisplayNeoPixel.h b/src/displays/DisplayNeoPixel.h index 366175a..78a2c6b 100644 --- a/src/displays/DisplayNeoPixel.h +++ b/src/displays/DisplayNeoPixel.h @@ -22,7 +22,7 @@ namespace udd { public: DisplayNeoPixel(); - void openDisplay(DisplayConfigruation configuration); + void openDisplay(DisplayConfiguration configuration); void printConfiguration(); void setBrightness(int brightness); void render(Rotation rotation);