diff DPF-Prymula-audioplugins/dpf/dgl/src/Cairo.cpp @ 3:84e66ea83026

DPF-Prymula-audioplugins-0.231015-2
author prymula <prymula76@outlook.com>
date Mon, 16 Oct 2023 21:53:34 +0200
parents
children
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/DPF-Prymula-audioplugins/dpf/dgl/src/Cairo.cpp	Mon Oct 16 21:53:34 2023 +0200
@@ -0,0 +1,827 @@
+/*
+ * DISTRHO Plugin Framework (DPF)
+ * Copyright (C) 2012-2022 Filipe Coelho <falktx@falktx.com>
+ * Copyright (C) 2019-2021 Jean Pierre Cimalando <jp-dev@inbox.ru>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any purpose with
+ * or without fee is hereby granted, provided that the above copyright notice and this
+ * permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD
+ * TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN
+ * NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
+ * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER
+ * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
+ * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#ifdef _MSC_VER
+// instantiated template classes whose methods are defined elsewhere
+# pragma warning(disable:4661)
+#endif
+
+#include "../Cairo.hpp"
+#include "../Color.hpp"
+#include "../ImageBaseWidgets.hpp"
+
+#include "SubWidgetPrivateData.hpp"
+#include "TopLevelWidgetPrivateData.hpp"
+#include "WidgetPrivateData.hpp"
+#include "WindowPrivateData.hpp"
+
+// templated classes
+#include "ImageBaseWidgets.cpp"
+
+START_NAMESPACE_DGL
+
+// -----------------------------------------------------------------------
+
+static void notImplemented(const char* const name)
+{
+    d_stderr2("cairo function not implemented: %s", name);
+}
+
+// -----------------------------------------------------------------------
+// Color
+
+void Color::setFor(const GraphicsContext& context, const bool includeAlpha)
+{
+    cairo_t* const handle = ((const CairoGraphicsContext&)context).handle;
+
+    if (includeAlpha)
+        cairo_set_source_rgba(handle, red, green, blue, alpha);
+    else
+        cairo_set_source_rgb(handle, red, green, blue);
+}
+
+// -----------------------------------------------------------------------
+// Line
+
+template<typename T>
+void Line<T>::draw(const GraphicsContext& context, const T width)
+{
+    DISTRHO_SAFE_ASSERT_RETURN(posStart != posEnd,);
+    DISTRHO_SAFE_ASSERT_RETURN(width != 0,);
+
+    cairo_t* const handle = ((const CairoGraphicsContext&)context).handle;
+
+    cairo_set_line_width(handle, width);
+    cairo_move_to(handle, posStart.getX(), posStart.getY());
+    cairo_line_to(handle, posEnd.getX(), posEnd.getY());
+    cairo_stroke(handle);
+}
+
+template<typename T>
+void Line<T>::draw()
+{
+    notImplemented("Line::draw");
+}
+
+template class Line<double>;
+template class Line<float>;
+template class Line<int>;
+template class Line<uint>;
+template class Line<short>;
+template class Line<ushort>;
+
+// -----------------------------------------------------------------------
+// Circle
+
+template<typename T>
+static void drawCircle(cairo_t* const handle,
+                       const Point<T>& pos,
+                       const uint numSegments,
+                       const float size,
+                       const float sin,
+                       const float cos,
+                       const bool outline)
+{
+    DISTRHO_SAFE_ASSERT_RETURN(numSegments >= 3 && size > 0.0f,);
+
+    const T origx = pos.getX();
+    const T origy = pos.getY();
+    double t, x = size, y = 0.0;
+
+    // TODO use arc
+    /*
+    cairo_arc(handle, origx, origy, size, sin, cos);
+    */
+
+    cairo_move_to(handle, x + origx, y + origy);
+
+    for (uint i=1; i<numSegments; ++i)
+    {
+        cairo_line_to(handle, x + origx, y + origy);
+
+        t = x;
+        x = cos * x - sin * y;
+        y = sin * t + cos * y;
+    }
+
+    cairo_line_to(handle, x + origx, y + origy);
+
+    if (outline)
+        cairo_stroke(handle);
+    else
+        cairo_fill(handle);
+}
+
+template<typename T>
+void Circle<T>::draw(const GraphicsContext& context)
+{
+    cairo_t* const handle = ((const CairoGraphicsContext&)context).handle;
+
+    drawCircle<T>(handle, fPos, fNumSegments, fSize, fSin, fCos, false);
+}
+
+template<typename T>
+void Circle<T>::drawOutline(const GraphicsContext& context, const T lineWidth)
+{
+    DISTRHO_SAFE_ASSERT_RETURN(lineWidth != 0,);
+
+    cairo_t* const handle = ((const CairoGraphicsContext&)context).handle;
+
+    cairo_set_line_width(handle, lineWidth);
+    drawCircle<T>(handle, fPos, fNumSegments, fSize, fSin, fCos, true);
+}
+
+template<typename T>
+void Circle<T>::draw()
+{
+    notImplemented("Circle::draw");
+}
+
+template<typename T>
+void Circle<T>::drawOutline()
+{
+    notImplemented("Circle::drawOutline");
+}
+
+template class Circle<double>;
+template class Circle<float>;
+template class Circle<int>;
+template class Circle<uint>;
+template class Circle<short>;
+template class Circle<ushort>;
+
+// -----------------------------------------------------------------------
+// Triangle
+
+template<typename T>
+static void drawTriangle(cairo_t* const handle,
+                         const Point<T>& pos1,
+                         const Point<T>& pos2,
+                         const Point<T>& pos3,
+                         const bool outline)
+{
+    DISTRHO_SAFE_ASSERT_RETURN(pos1 != pos2 && pos1 != pos3,);
+
+    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<typename T>
+void Triangle<T>::draw(const GraphicsContext& context)
+{
+    cairo_t* const handle = ((const CairoGraphicsContext&)context).handle;
+
+    drawTriangle<T>(handle, pos1, pos2, pos3, false);
+}
+
+template<typename T>
+void Triangle<T>::drawOutline(const GraphicsContext& context, const T lineWidth)
+{
+    DISTRHO_SAFE_ASSERT_RETURN(lineWidth != 0,);
+
+    cairo_t* const handle = ((const CairoGraphicsContext&)context).handle;
+
+    cairo_set_line_width(handle, lineWidth);
+    drawTriangle<T>(handle, pos1, pos2, pos3, true);
+}
+
+template<typename T>
+void Triangle<T>::draw()
+{
+    notImplemented("Triangle::draw");
+}
+
+template<typename T>
+void Triangle<T>::drawOutline()
+{
+    notImplemented("Triangle::drawOutline");
+}
+
+template class Triangle<double>;
+template class Triangle<float>;
+template class Triangle<int>;
+template class Triangle<uint>;
+template class Triangle<short>;
+template class Triangle<ushort>;
+
+// -----------------------------------------------------------------------
+// Rectangle
+
+template<typename T>
+static void drawRectangle(cairo_t* const handle, const Rectangle<T>& rect, const bool outline)
+{
+    cairo_rectangle(handle, rect.getX(), rect.getY(), rect.getWidth(), rect.getHeight());
+
+    if (outline)
+        cairo_stroke(handle);
+    else
+        cairo_fill(handle);
+}
+
+template<typename T>
+void Rectangle<T>::draw(const GraphicsContext& context)
+{
+    DISTRHO_SAFE_ASSERT_RETURN(isValid(),);
+
+    cairo_t* const handle = ((const CairoGraphicsContext&)context).handle;
+
+    drawRectangle(handle, *this, false);
+}
+
+template<typename T>
+void Rectangle<T>::drawOutline(const GraphicsContext& context, const T lineWidth)
+{
+    DISTRHO_SAFE_ASSERT_RETURN(isValid(),);
+    DISTRHO_SAFE_ASSERT_RETURN(lineWidth != 0,);
+
+    cairo_t* const handle = ((const CairoGraphicsContext&)context).handle;
+
+    cairo_set_line_width(handle, lineWidth);
+    drawRectangle(handle, *this, true);
+}
+
+template<typename T>
+void Rectangle<T>::draw()
+{
+    notImplemented("Rectangle::draw");
+}
+
+template<typename T>
+void Rectangle<T>::drawOutline()
+{
+    notImplemented("Rectangle::drawOutline");
+}
+
+template class Rectangle<double>;
+template class Rectangle<float>;
+template class Rectangle<int>;
+template class Rectangle<uint>;
+template class Rectangle<short>;
+template class Rectangle<ushort>;
+
+// -----------------------------------------------------------------------
+// CairoImage
+
+static cairo_format_t asCairoImageFormat(const ImageFormat format) noexcept
+{
+    switch (format)
+    {
+    case kImageFormatNull:
+        break;
+    case kImageFormatGrayscale:
+        return CAIRO_FORMAT_A8;
+    case kImageFormatBGR:
+    case kImageFormatRGB:
+        return CAIRO_FORMAT_RGB24;
+    case kImageFormatBGRA:
+    case kImageFormatRGBA:
+        return CAIRO_FORMAT_ARGB32;
+    }
+
+    return CAIRO_FORMAT_INVALID;
+}
+
+/*
+static ImageFormat asCairoImageFormat(const cairo_format_t format) noexcept
+{
+    switch (format)
+    {
+    case CAIRO_FORMAT_INVALID:
+        break;
+    case CAIRO_FORMAT_ARGB32:
+        break;
+    case CAIRO_FORMAT_RGB24:
+        break;
+    case CAIRO_FORMAT_A8:
+        break;
+    case CAIRO_FORMAT_A1:
+        break;
+    case CAIRO_FORMAT_RGB16_565:
+        break;
+    case CAIRO_FORMAT_RGB30:
+        break;
+    }
+
+    return kImageFormatNull;
+}
+*/
+
+CairoImage::CairoImage()
+    : ImageBase(),
+      surface(nullptr),
+      surfacedata(nullptr),
+      datarefcount(nullptr) {}
+
+CairoImage::CairoImage(const char* const rdata, const uint w, const uint h, const ImageFormat fmt)
+    : ImageBase(rdata, w, h, fmt),
+      surface(nullptr),
+      surfacedata(nullptr),
+      datarefcount(nullptr)
+{
+    loadFromMemory(rdata, w, h, fmt);
+}
+
+CairoImage::CairoImage(const char* const rdata, const Size<uint>& s, const ImageFormat fmt)
+    : ImageBase(rdata, s, fmt),
+      surface(nullptr),
+      surfacedata(nullptr),
+      datarefcount(nullptr)
+{
+    loadFromMemory(rdata, s, fmt);
+}
+
+CairoImage::CairoImage(const CairoImage& image)
+    : 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::loadFromMemory(const char* const rdata, const Size<uint>& s, const ImageFormat fmt) noexcept
+{
+    const cairo_format_t cairoformat = asCairoImageFormat(fmt);
+    const int width  = static_cast<int>(s.getWidth());
+    const int height = static_cast<int>(s.getHeight());
+    const int stride = cairo_format_stride_for_width(cairoformat, width);
+
+    uchar* const newdata = (uchar*)std::malloc(static_cast<size_t>(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(static_cast<int>(s.getWidth()) == cairo_image_surface_get_width(newsurface),);
+    DISTRHO_SAFE_ASSERT_RETURN(static_cast<int>(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 kImageFormatGrayscale:
+        // Grayscale to A8
+        // TODO
+        break;
+    case kImageFormatBGR:
+        // BGR8 to CAIRO_FORMAT_RGB24
+        for (int h = 0; h < height; ++h)
+        {
+            for (int w = 0; w < width; ++w)
+            {
+                newdata[h*width*4+w*4+0] = static_cast<uchar>(rdata[h*width*3+w*3+0]);
+                newdata[h*width*4+w*4+1] = static_cast<uchar>(rdata[h*width*3+w*3+1]);
+                newdata[h*width*4+w*4+2] = static_cast<uchar>(rdata[h*width*3+w*3+2]);
+                newdata[h*width*4+w*4+3] = 0;
+            }
+        }
+        break;
+    case kImageFormatBGRA:
+        // BGRA8 to CAIRO_FORMAT_ARGB32
+        // FIXME something is wrong here...
+        for (int h = 0, t; h < height; ++h)
+        {
+            for (int w = 0; w < width; ++w)
+            {
+                if ((t = rdata[h*width*4+w*4+3]) != 0)
+                {
+                    newdata[h*width*4+w*4+0] = static_cast<uchar>(rdata[h*width*4+w*4+0]);
+                    newdata[h*width*4+w*4+1] = static_cast<uchar>(rdata[h*width*4+w*4+1]);
+                    newdata[h*width*4+w*4+2] = static_cast<uchar>(rdata[h*width*4+w*4+2]);
+                    newdata[h*width*4+w*4+3] = static_cast<uchar>(t);
+                }
+                else
+                {
+                    // make all pixels zero, cairo does not render full transparency otherwise
+                    memset(&newdata[h*width*4+w*4], 0, 4);
+                }
+            }
+        }
+        break;
+    case kImageFormatRGB:
+        // RGB8 to CAIRO_FORMAT_RGB24
+        // TODO
+        break;
+    case kImageFormatRGBA:
+        // RGBA8 to CAIRO_FORMAT_ARGB32
+        // TODO
+        break;
+    }
+
+    ImageBase::loadFromMemory(rdata, s, fmt);
+}
+
+// const GraphicsContext& context
+void CairoImage::loadFromPNG(const char* const pngData, const uint pngSize) noexcept
+{
+    struct PngReaderData
+    {
+        const char* dataPtr;
+        uint sizeLeft;
+
+        static cairo_status_t read(void* const closure, uchar* const data, const uint length) noexcept
+        {
+            PngReaderData& readerData = *reinterpret_cast<PngReaderData*>(closure);
+
+            if (readerData.sizeLeft < length)
+                return CAIRO_STATUS_READ_ERROR;
+
+            std::memcpy(data, readerData.dataPtr, length);
+            readerData.dataPtr += length;
+            readerData.sizeLeft -= length;
+            return CAIRO_STATUS_SUCCESS;
+        }
+    };
+
+    PngReaderData readerData;
+    readerData.dataPtr = pngData;
+    readerData.sizeLeft = pngSize;
+
+    cairo_surface_t* const newsurface = cairo_image_surface_create_from_png_stream(PngReaderData::read, &readerData);
+    DISTRHO_SAFE_ASSERT_RETURN(newsurface != nullptr,);
+
+    const int newwidth = cairo_image_surface_get_width(newsurface);
+    const int newheight = cairo_image_surface_get_height(newsurface);
+    DISTRHO_SAFE_ASSERT_INT_RETURN(newwidth > 0, newwidth,);
+    DISTRHO_SAFE_ASSERT_INT_RETURN(newheight > 0, newheight,);
+
+    cairo_surface_destroy(surface);
+
+    if (datarefcount != nullptr && --(*datarefcount) == 0)
+        std::free(surfacedata);
+    else
+        datarefcount = (int*)malloc(sizeof(*datarefcount));
+
+    surface = newsurface;
+    surfacedata = nullptr; // cairo_image_surface_get_data(newsurface);
+    *datarefcount = 1;
+
+    rawData = nullptr;
+    format = kImageFormatNull; // asCairoImageFormat(cairo_image_surface_get_format(newsurface));
+    size = Size<uint>(static_cast<uint>(newwidth), static_cast<uint>(newheight));
+}
+
+void CairoImage::drawAt(const GraphicsContext& context, const Point<int>& 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);
+
+    if (datarefcount != nullptr && --(*datarefcount) == 0)
+    {
+        std::free(surfacedata);
+        std::free(datarefcount);
+    }
+
+    surface = newsurface;
+    rawData = image.rawData;
+    size    = image.size;
+    format  = image.format;
+    surfacedata = image.surfacedata;
+    datarefcount = image.datarefcount;
+
+    if (datarefcount != nullptr)
+        ++(*datarefcount);
+
+    return *this;
+}
+
+// -----------------------------------------------------------------------
+// CairoSubWidget
+
+template <>
+CairoBaseWidget<SubWidget>::CairoBaseWidget(Widget* const parent)
+    : SubWidget(parent) {}
+
+template class CairoBaseWidget<SubWidget>;
+
+// -----------------------------------------------------------------------
+// CairoTopLevelWidget
+
+template <>
+CairoBaseWidget<TopLevelWidget>::CairoBaseWidget(Window& windowToMapTo)
+    : TopLevelWidget(windowToMapTo) {}
+
+template class CairoBaseWidget<TopLevelWidget>;
+
+// -----------------------------------------------------------------------
+// CairoStandaloneWindow
+
+template <>
+CairoBaseWidget<StandaloneWindow>::CairoBaseWidget(Application& app)
+    : StandaloneWindow(app) {}
+
+template <>
+CairoBaseWidget<StandaloneWindow>::CairoBaseWidget(Application& app, Window& parentWindow)
+    : StandaloneWindow(app, parentWindow) {}
+
+template class CairoBaseWidget<StandaloneWindow>;
+
+// -----------------------------------------------------------------------
+// ImageBaseAboutWindow
+
+#if 0
+template <>
+void ImageBaseAboutWindow<CairoImage>::onDisplay()
+{
+    img.draw(getGraphicsContext());
+}
+#endif
+
+template class ImageBaseAboutWindow<CairoImage>;
+
+// -----------------------------------------------------------------------
+// ImageBaseButton
+
+template class ImageBaseButton<CairoImage>;
+
+// -----------------------------------------------------------------------
+// ImageBaseKnob
+
+template <>
+void ImageBaseKnob<CairoImage>::PrivateData::init()
+{
+    alwaysRepaint = true;
+    cairoSurface = nullptr;
+}
+
+template <>
+void ImageBaseKnob<CairoImage>::PrivateData::cleanup()
+{
+    cairo_surface_destroy((cairo_surface_t*)cairoSurface);
+    cairoSurface = nullptr;
+}
+
+/**
+   Get the pixel size in bytes.
+   @return pixel size, or 0 if the format is unknown, or pixels are not aligned to bytes.
+*/
+static int getBytesPerPixel(const cairo_format_t format) noexcept
+{
+    switch (format)
+    {
+    case CAIRO_FORMAT_ARGB32:
+    case CAIRO_FORMAT_RGB24:
+    case CAIRO_FORMAT_RGB30:
+        return 4;
+    case CAIRO_FORMAT_RGB16_565:
+        return 2;
+    case CAIRO_FORMAT_A8:
+        return 1;
+    case CAIRO_FORMAT_A1:
+        return 0;
+    default:
+        DISTRHO_SAFE_ASSERT(false);
+        return 0;
+    }
+}
+
+static cairo_surface_t* getRegion(cairo_surface_t* origsurface, int x, int y, int width, int height) noexcept
+{
+    const cairo_format_t format = cairo_image_surface_get_format(origsurface);
+    const int bpp = getBytesPerPixel(format);
+
+    if (bpp == 0)
+        return nullptr;
+
+    const int fullWidth   = cairo_image_surface_get_width(origsurface);
+    const int fullHeight  = cairo_image_surface_get_height(origsurface);
+    const int stride      = cairo_image_surface_get_stride(origsurface);
+    uchar* const fullData = cairo_image_surface_get_data(origsurface);
+
+    x = (x < fullWidth) ? x : fullWidth;
+    y = (y < fullHeight) ? y : fullHeight;
+    width = (x + width < fullWidth) ? width : (fullWidth - x);
+    height = (x + height < fullHeight) ? height : (fullHeight - x);
+
+    uchar* const data = fullData + (x * bpp + y * stride);
+    return cairo_image_surface_create_for_data(data, format, width, height, stride);
+}
+
+template <>
+void ImageBaseKnob<CairoImage>::onDisplay()
+{
+    const GraphicsContext& context(getGraphicsContext());
+    cairo_t* const handle = ((const CairoGraphicsContext&)context).handle;
+    const double normValue = getNormalizedValue();
+
+    cairo_surface_t* surface = (cairo_surface_t*)pData->cairoSurface;
+
+    if (! pData->isReady)
+    {
+        const int layerW = static_cast<int>(pData->imgLayerWidth);
+        const int layerH = static_cast<int>(pData->imgLayerHeight);
+        int layerNum = 0;
+
+        if (pData->rotationAngle == 0)
+            layerNum = static_cast<int>(normValue * static_cast<double>(pData->imgLayerCount - 1) + 0.5);
+
+        const int layerX = pData->isImgVertical ? 0 : layerNum * layerW;
+        const int layerY = !pData->isImgVertical ? 0 : layerNum * layerH;
+
+        cairo_surface_t* newsurface;
+
+        if (pData->rotationAngle == 0)
+        {
+            newsurface = getRegion(pData->image.getSurface(), layerX, layerY, layerW, layerH);
+        }
+        else
+        {
+            newsurface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, layerW, layerH);
+            cairo_t* const cr = cairo_create(newsurface);
+            cairo_translate(cr, 0.5 * layerW, 0.5 * layerH);
+            cairo_rotate(cr, normValue * pData->rotationAngle * (M_PI / 180));
+            cairo_set_source_surface(cr, pData->image.getSurface(), -0.5 * layerW, -0.5 * layerH);
+            cairo_paint(cr);
+            cairo_destroy(cr);
+        }
+
+        DISTRHO_SAFE_ASSERT_RETURN(newsurface != nullptr,);
+
+        cairo_surface_destroy(surface);
+        pData->cairoSurface = surface = newsurface;
+        pData->isReady = true;
+    }
+
+    if (surface != nullptr)
+    {
+        cairo_set_source_surface(handle, surface, 0, 0);
+        cairo_paint(handle);
+    }
+}
+
+template class ImageBaseKnob<CairoImage>;
+
+// -----------------------------------------------------------------------
+// ImageBaseSlider
+
+template class ImageBaseSlider<CairoImage>;
+
+// -----------------------------------------------------------------------
+// ImageBaseSwitch
+
+template class ImageBaseSwitch<CairoImage>;
+
+// -----------------------------------------------------------------------
+
+void SubWidget::PrivateData::display(const uint width, const uint height, const double autoScaleFactor)
+{
+    cairo_t* const handle = static_cast<const CairoGraphicsContext&>(self->getGraphicsContext()).handle;
+
+    bool needsResetClip = false;
+
+    cairo_matrix_t matrix;
+    cairo_get_matrix(handle, &matrix);
+
+    if (needsViewportScaling)
+    {
+        // limit viewport to widget bounds
+        // NOTE only used for nanovg for now, which is not relevant here
+    }
+    else if (needsFullViewportForDrawing || (absolutePos.isZero() && self->getSize() == Size<uint>(width, height)))
+    {
+        // full viewport size
+        cairo_translate(handle, 0, 0);
+        cairo_scale(handle, autoScaleFactor, autoScaleFactor);
+    }
+    else
+    {
+        // set viewport pos
+        cairo_translate(handle, absolutePos.getX() * autoScaleFactor, absolutePos.getY() * autoScaleFactor);
+
+        // 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;
+
+        // set viewport scaling
+        cairo_scale(handle, autoScaleFactor, autoScaleFactor);
+    }
+
+    // display widget
+    self->onDisplay();
+
+    if (needsResetClip)
+        cairo_reset_clip(handle);
+
+    cairo_set_matrix(handle, &matrix);
+
+    selfw->pData->displaySubWidgets(width, height, autoScaleFactor);
+}
+
+// -----------------------------------------------------------------------
+
+void TopLevelWidget::PrivateData::display()
+{
+    if (! selfw->pData->visible)
+        return;
+
+    cairo_t* const handle = static_cast<const CairoGraphicsContext&>(self->getGraphicsContext()).handle;
+
+    const Size<uint> size(window.getSize());
+    const uint width  = size.getWidth();
+    const uint height = size.getHeight();
+
+    const double autoScaleFactor = window.pData->autoScaleFactor;
+
+    cairo_matrix_t matrix;
+    cairo_get_matrix(handle, &matrix);
+
+    // full viewport size
+    if (window.pData->autoScaling)
+    {
+        cairo_translate(handle, 0, 0);
+        cairo_scale(handle, autoScaleFactor, autoScaleFactor);
+    }
+    else
+    {
+        cairo_translate(handle, 0, 0);
+        cairo_scale(handle, 1.0, 1.0);
+    }
+
+    // main widget drawing
+    self->onDisplay();
+
+    cairo_set_matrix(handle, &matrix);
+
+    // now draw subwidgets if there are any
+    selfw->pData->displaySubWidgets(width, height, autoScaleFactor);
+}
+
+// -----------------------------------------------------------------------
+
+void Window::PrivateData::renderToPicture(const char*, const GraphicsContext&, uint, uint)
+{
+    notImplemented("Window::PrivateData::renderToPicture");
+}
+
+// -----------------------------------------------------------------------
+
+const GraphicsContext& Window::PrivateData::getGraphicsContext() const noexcept
+{
+    GraphicsContext& context((GraphicsContext&)graphicsContext);
+    ((CairoGraphicsContext&)context).handle = (cairo_t*)puglGetContext(view);
+    return context;
+}
+
+// -----------------------------------------------------------------------
+
+END_NAMESPACE_DGL