From 6e141323c833cbd4aa024c20a8c65f2ff207324b Mon Sep 17 00:00:00 2001 From: falkTX Date: Mon, 17 May 2021 04:52:37 +0100 Subject: [PATCH] Implement core cairo support (shapes and images) --- dgl/Cairo.hpp | 20 ++ dgl/ImageBase.hpp | 1 + dgl/StandaloneWindow.hpp | 3 + dgl/src/Cairo.cpp | 218 +++++++++++++++++++--- dgl/src/ImageBase.cpp | 2 +- dgl/src/OpenGL.cpp | 12 +- dgl/src/SubWidgetPrivateData.cpp | 1 + tests/Demo.cpp | 18 +- tests/widgets/ExampleRectanglesWidget.hpp | 6 +- 9 files changed, 241 insertions(+), 40 deletions(-) diff --git a/dgl/Cairo.hpp b/dgl/Cairo.hpp index fb4f11ba..ba97dd4c 100644 --- a/dgl/Cairo.hpp +++ b/dgl/Cairo.hpp @@ -72,14 +72,34 @@ public: */ ~CairoImage() override; + /** + Load image data from memory. + @note @a rawData must remain valid for the lifetime of this Image. + */ + void loadFromMemory(const char* rawData, + const Size& size, + ImageFormat format = kImageFormatBGRA) noexcept override; + /** Draw this image at position @a pos using the graphics context @a context. */ void drawAt(const GraphicsContext& context, const Point& pos) override; + /** + TODO document this. + */ + CairoImage& operator=(const CairoImage& image) noexcept; + // FIXME this should not be needed + inline void loadFromMemory(const char* rawData, uint w, uint h, ImageFormat format) + { loadFromMemory(rawData, Size(w, h), format); }; inline void drawAt(const GraphicsContext& context, int x, int y) { drawAt(context, Point(x, y)); }; + +private: + cairo_surface_t* surface; + uchar* surfacedata; + int* datarefcount; }; // -------------------------------------------------------------------------------------------------------------------- diff --git a/dgl/ImageBase.hpp b/dgl/ImageBase.hpp index a03c1567..1e41bd63 100644 --- a/dgl/ImageBase.hpp +++ b/dgl/ImageBase.hpp @@ -24,6 +24,7 @@ START_NAMESPACE_DGL // -------------------------------------------------------------------------------------------------------------------- enum ImageFormat { + kImageFormatNull, kImageFormatBGR, kImageFormatBGRA, kImageFormatRGB, diff --git a/dgl/StandaloneWindow.hpp b/dgl/StandaloneWindow.hpp index 148626fa..5f2d553d 100644 --- a/dgl/StandaloneWindow.hpp +++ b/dgl/StandaloneWindow.hpp @@ -61,6 +61,9 @@ public: { return Window::addIdleCallback(callback, timerFrequencyInMs); } bool removeIdleCallback(IdleCallback* callback) { return Window::removeIdleCallback(callback); } const GraphicsContext& getGraphicsContext() const noexcept { return Window::getGraphicsContext(); } + void setGeometryConstraints(uint minimumWidth, uint minimumHeight, + bool keepAspectRatio = false, bool automaticallyScale = false) + { Window::setGeometryConstraints(minimumWidth, minimumHeight, keepAspectRatio, automaticallyScale); } DISTRHO_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(StandaloneWindow) }; diff --git a/dgl/src/Cairo.cpp b/dgl/src/Cairo.cpp index bd60fe40..e51cda98 100644 --- a/dgl/src/Cairo.cpp +++ b/dgl/src/Cairo.cpp @@ -92,20 +92,28 @@ static void drawCircle(cairo_t* const handle, const T origy = pos.getY(); double t, x = size, y = 0.0; + // TODO use arc /* - glBegin(outline ? GL_LINE_LOOP : GL_POLYGON); + cairo_arc(handle, origx, origy, size, sin, cos); + */ + + cairo_move_to(handle, x + origx, y + origy); - for (uint i=0; i @@ -158,7 +166,15 @@ static void drawTriangle(cairo_t* const handle, { DISTRHO_SAFE_ASSERT_RETURN(pos1 != pos2 && pos1 != pos3,); - // TODO + cairo_move_to(handle, pos1.getX(), pos1.getY()); + cairo_line_to(handle, pos2.getX(), pos2.getY()); + cairo_line_to(handle, pos3.getX(), pos3.getY()); + cairo_line_to(handle, pos1.getX(), pos1.getY()); + + if (outline) + cairo_stroke(handle); + else + cairo_fill(handle); } template @@ -206,7 +222,12 @@ template class Triangle; template static void drawRectangle(cairo_t* const handle, const Rectangle& rect, const bool outline) { - // TODO + cairo_rectangle(handle, rect.getX(), rect.getY(), rect.getWidth(), rect.getHeight()); + + if (outline) + cairo_stroke(handle); + else + cairo_fill(handle); } template @@ -253,24 +274,152 @@ template class Rectangle; // ----------------------------------------------------------------------- // CairoImage +static cairo_format_t asCairoImageFormat(const ImageFormat format) +{ + switch (format) + { + case kImageFormatNull: + break; + case kImageFormatBGR: + case kImageFormatRGB: + return CAIRO_FORMAT_RGB24; + case kImageFormatBGRA: + case kImageFormatRGBA: + return CAIRO_FORMAT_ARGB32; + } + + return CAIRO_FORMAT_INVALID; +} + CairoImage::CairoImage() - : ImageBase() {} + : ImageBase(), + surface(nullptr), + surfacedata(nullptr), + datarefcount(nullptr) {} CairoImage::CairoImage(const char* const rawData, const uint width, const uint height, const ImageFormat format) - : ImageBase(rawData, width, height, format) {} + : ImageBase(rawData, width, height, format), + surface(nullptr), + surfacedata(nullptr), + datarefcount(nullptr) +{ + loadFromMemory(rawData, width, height, format); +} CairoImage::CairoImage(const char* const rawData, const Size& size, const ImageFormat format) - : ImageBase(rawData, size, format) {} + : ImageBase(rawData, size, format), + surface(nullptr), + surfacedata(nullptr), + datarefcount(nullptr) +{ + loadFromMemory(rawData, size, format); +} CairoImage::CairoImage(const CairoImage& image) - : ImageBase(image.rawData, image.size, image.format) {} + : ImageBase(image.rawData, image.size, image.format), + surface(cairo_surface_reference(image.surface)), + surfacedata(image.surfacedata), + datarefcount(image.datarefcount) +{ + if (datarefcount != nullptr) + ++(*datarefcount); +} CairoImage::~CairoImage() { + cairo_surface_destroy(surface); + + if (datarefcount != nullptr && --(*datarefcount) == 0) + { + std::free(surfacedata); + std::free(datarefcount); + } } -void CairoImage::drawAt(const GraphicsContext&, const Point&) +void CairoImage::loadFromMemory(const char* const rdata, const Size& s, const ImageFormat fmt) noexcept { + const cairo_format_t cairoformat = asCairoImageFormat(fmt); + const uint width = s.getWidth(); + const uint height = s.getHeight(); + const int stride = cairo_format_stride_for_width(cairoformat, width); + + uchar* const newdata = (uchar*)std::malloc(width * height * stride * 4); + DISTRHO_SAFE_ASSERT_RETURN(newdata != nullptr,); + + cairo_surface_t* const newsurface = cairo_image_surface_create_for_data(newdata, cairoformat, width, height, stride); + DISTRHO_SAFE_ASSERT_RETURN(newsurface != nullptr,); + DISTRHO_SAFE_ASSERT_RETURN(s.getWidth() == cairo_image_surface_get_width(newsurface),); + DISTRHO_SAFE_ASSERT_RETURN(s.getHeight() == cairo_image_surface_get_height(newsurface),); + + cairo_surface_destroy(surface); + + if (datarefcount != nullptr && --(*datarefcount) == 0) + std::free(surfacedata); + else + datarefcount = (int*)malloc(sizeof(*datarefcount)); + + surface = newsurface; + surfacedata = newdata; + *datarefcount = 1; + + switch (fmt) + { + case kImageFormatNull: + break; + case kImageFormatBGR: + // BGR8 to CAIRO_FORMAT_RGB24 + for (uint h = 0; h < height; ++h) + { + for (uint w = 0; w < width; ++w) + { + newdata[h*width*4+w*4+0] = rdata[h*width*3+w*3+0]; + newdata[h*width*4+w*4+1] = rdata[h*width*3+w*3+1]; + newdata[h*width*4+w*4+2] = rdata[h*width*3+w*3+2]; + newdata[h*width*4+w*4+3] = 0; + } + } + break; + case kImageFormatBGRA: + // RGB8 to CAIRO_FORMAT_ARGB32 + // TODO + break; + case kImageFormatRGB: + // RGB8 to CAIRO_FORMAT_RGB24 + // TODO + break; + case kImageFormatRGBA: + // RGBA8 to CAIRO_FORMAT_ARGB32 + // TODO + break; + } + + ImageBase::loadFromMemory(rdata, s, fmt); +} + +void CairoImage::drawAt(const GraphicsContext& context, const Point& pos) +{ + if (surface == nullptr) + return; + + cairo_t* const handle = ((const CairoGraphicsContext&)context).handle; + + cairo_set_source_surface(handle, surface, pos.getX(), pos.getY()); + cairo_paint(handle); +} + +CairoImage& CairoImage::operator=(const CairoImage& image) noexcept +{ + cairo_surface_t* newsurface = cairo_surface_reference(image.surface); + cairo_surface_destroy(surface); + surface = newsurface; + rawData = image.rawData; + size = image.size; + format = image.format; + surfacedata = image.surfacedata; + datarefcount = image.datarefcount; + if (datarefcount != nullptr) + ++(*datarefcount); + return *this; } // ----------------------------------------------------------------------- @@ -318,21 +467,47 @@ template class ImageBaseAboutWindow; void SubWidget::PrivateData::display(const uint width, const uint height, const double autoScaleFactor) { - /* - if ((skipDisplay && ! renderingSubWidget) || size.isInvalid() || ! visible) - return; - */ + cairo_t* const handle = static_cast(self->getGraphicsContext()).handle; + + bool needsResetClip = false; - cairo_t* cr = static_cast(self->getGraphicsContext()).handle; cairo_matrix_t matrix; - cairo_get_matrix(cr, &matrix); - cairo_translate(cr, absolutePos.getX(), absolutePos.getY()); - // TODO: autoScaling and cropping + cairo_get_matrix(handle, &matrix); + + if (needsFullViewportForDrawing || (absolutePos.isZero() && self->getSize() == Size(width, height))) + { + // full viewport size + cairo_translate(handle, 0, 0); + } + else if (needsViewportScaling) + { + // limit viewport to widget bounds + // NOTE only used for nanovg for now, which is not relevant here + cairo_translate(handle, 0, 0); + } + else + { + // set viewport pos + cairo_translate(handle, absolutePos.getX(), absolutePos.getY()); + + // then cut the outer bounds + cairo_rectangle(handle, + 0, + 0, + std::round(self->getWidth() * autoScaleFactor), + std::round(self->getHeight() * autoScaleFactor)); + + cairo_clip(handle); + needsResetClip = true; + } // display widget self->onDisplay(); - cairo_set_matrix(cr, &matrix); + if (needsResetClip) + cairo_reset_clip(handle); + + cairo_set_matrix(handle, &matrix); selfw->pData->displaySubWidgets(width, height, autoScaleFactor); } @@ -347,6 +522,7 @@ void TopLevelWidget::PrivateData::display() const double autoScaleFactor = window.pData->autoScaleFactor; + // FIXME anything needed here? #if 0 // full viewport size if (window.pData->autoScaling) diff --git a/dgl/src/ImageBase.cpp b/dgl/src/ImageBase.cpp index 2691c4a7..ea3477b6 100644 --- a/dgl/src/ImageBase.cpp +++ b/dgl/src/ImageBase.cpp @@ -24,7 +24,7 @@ START_NAMESPACE_DGL ImageBase::ImageBase() : rawData(nullptr), size(0, 0), - format(kImageFormatBGRA) {} + format(kImageFormatNull) {} ImageBase::ImageBase(const char* const rdata, const uint width, const uint height, const ImageFormat fmt) : rawData(rdata), diff --git a/dgl/src/OpenGL.cpp b/dgl/src/OpenGL.cpp index e8567146..6e733bc9 100644 --- a/dgl/src/OpenGL.cpp +++ b/dgl/src/OpenGL.cpp @@ -264,11 +264,14 @@ template class Rectangle; template class Rectangle; // ----------------------------------------------------------------------- +// OpenGLImage static GLenum asOpenGLImageFormat(const ImageFormat format) { switch (format) { + case kImageFormatNull: + break; case kImageFormatBGR: return GL_BGR; case kImageFormatBGRA: @@ -279,7 +282,7 @@ static GLenum asOpenGLImageFormat(const ImageFormat format) return GL_RGBA; } - return GL_BGRA; + return 0x0; } static void setupOpenGLImage(const OpenGLImage& image, GLuint textureId) @@ -353,8 +356,8 @@ OpenGLImage::~OpenGLImage() void OpenGLImage::loadFromMemory(const char* const rdata, const Size& s, const ImageFormat fmt) noexcept { - ImageBase::loadFromMemory(rdata, s, fmt); setupCalled = false; + ImageBase::loadFromMemory(rdata, s, fmt); } void OpenGLImage::drawAt(const GraphicsContext&, const Point& pos) @@ -456,7 +459,7 @@ void SubWidget::PrivateData::display(const uint width, const uint height, const } else { - // only set viewport pos + // set viewport pos glViewport(absolutePos.getX() * autoScaleFactor, -std::round((height * autoScaleFactor - height) + (absolutePos.getY() * autoScaleFactor)), std::round(width * autoScaleFactor), @@ -476,10 +479,7 @@ void SubWidget::PrivateData::display(const uint width, const uint height, const self->onDisplay(); if (needsDisableScissor) - { glDisable(GL_SCISSOR_TEST); - needsDisableScissor = false; - } selfw->pData->displaySubWidgets(width, height, autoScaleFactor); } diff --git a/dgl/src/SubWidgetPrivateData.cpp b/dgl/src/SubWidgetPrivateData.cpp index 737b7024..342d017a 100644 --- a/dgl/src/SubWidgetPrivateData.cpp +++ b/dgl/src/SubWidgetPrivateData.cpp @@ -26,6 +26,7 @@ SubWidget::PrivateData::PrivateData(SubWidget* const s, Widget* const pw) selfw((Widget*)s), parentWidget(pw), absolutePos(), + needsFullViewportForDrawing(false), needsViewportScaling(false) { parentWidget->pData->subWidgets.push_back(self); diff --git a/tests/Demo.cpp b/tests/Demo.cpp index 72f5b16a..92acd481 100644 --- a/tests/Demo.cpp +++ b/tests/Demo.cpp @@ -67,17 +67,19 @@ public: curPage(0), curHover(-1) { -#ifdef DGL_OPENGL - // for text - nvg.loadSharedResources(); -#endif using namespace DemoArtwork; img1.loadFromMemory(ico1Data, ico1Width, ico1Height, kImageFormatBGR); img2.loadFromMemory(ico2Data, ico2Width, ico2Height, kImageFormatBGR); img3.loadFromMemory(ico3Data, ico3Width, ico2Height, kImageFormatBGR); img4.loadFromMemory(ico4Data, ico4Width, ico4Height, kImageFormatBGR); + +#ifdef DGL_OPENGL img5.loadFromMemory(ico5Data, ico5Width, ico5Height, kImageFormatBGR); + + // for text + nvg.loadSharedResources(); +#endif } protected: @@ -120,9 +122,10 @@ protected: img2.drawAt(context, pad, pad + 3 + iconSize); img3.drawAt(context, pad, pad + 6 + iconSize*2); img4.drawAt(context, pad, pad + 9 + iconSize*3); - img5.drawAt(context, pad, pad + 12 + iconSize*4); #ifdef DGL_OPENGL + img5.drawAt(context, pad, pad + 12 + iconSize*4); + // draw some text nvg.beginFrame(this); @@ -267,10 +270,6 @@ public: #endif wLeft.setAbsolutePos(2, 2); - setResizable(true); - setSize(600, 500); - setTitle("DGL Demo"); - curPageChanged(0); } @@ -350,6 +349,7 @@ template void createAndShowExampleWidgetStandaloneWindow(Application& app) { ExampleWidgetStandaloneWindow swin(app); + swin.setResizable(true); swin.setSize(600, 500); swin.setTitle(ExampleWidgetStandaloneWindow::kExampleWidgetName); swin.show(); diff --git a/tests/widgets/ExampleRectanglesWidget.hpp b/tests/widgets/ExampleRectanglesWidget.hpp index c001674c..9dee758a 100644 --- a/tests/widgets/ExampleRectanglesWidget.hpp +++ b/tests/widgets/ExampleRectanglesWidget.hpp @@ -89,7 +89,7 @@ protected: else Color(0.3f, 0.5f, 0.8f).setFor(context); - r.draw(); + r.draw(context); // 2nd r.setY(3 + height/3); @@ -99,7 +99,7 @@ protected: else Color(0.3f, 0.5f, 0.8f).setFor(context); - r.draw(); + r.draw(context); // 3rd r.setY(3 + height*2/3); @@ -109,7 +109,7 @@ protected: else Color(0.3f, 0.5f, 0.8f).setFor(context); - r.draw(); + r.draw(context); } }