From 9a5bfb944d3a63507b293b8ee635b95fd5a16de5 Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Boric Date: Sat, 30 Mar 2019 17:05:04 +0000 Subject: [PATCH] [kandinsky] Basics of post-processing --- escher/include/escher/window.h | 2 +- kandinsky/Makefile | 4 + kandinsky/include/kandinsky.h | 4 + kandinsky/include/kandinsky/color.h | 1 + kandinsky/include/kandinsky/context.h | 14 ++- kandinsky/include/kandinsky/ion_context.h | 31 ++++++- .../include/kandinsky/postprocess_context.h | 20 +++++ .../kandinsky/postprocess_gamma_context.h | 19 +++++ .../kandinsky/postprocess_invert_context.h | 15 ++++ .../kandinsky/postprocess_zoom_context.h | 20 +++++ kandinsky/src/ion_context.cpp | 44 +++++++++- kandinsky/src/postprocess_context.cpp | 40 +++++++++ kandinsky/src/postprocess_gamma_context.cpp | 85 +++++++++++++++++++ kandinsky/src/postprocess_invert_context.cpp | 28 ++++++ kandinsky/src/postprocess_zoom_context.cpp | 53 ++++++++++++ 15 files changed, 368 insertions(+), 12 deletions(-) create mode 100644 kandinsky/include/kandinsky/postprocess_context.h create mode 100644 kandinsky/include/kandinsky/postprocess_gamma_context.h create mode 100644 kandinsky/include/kandinsky/postprocess_invert_context.h create mode 100644 kandinsky/include/kandinsky/postprocess_zoom_context.h create mode 100644 kandinsky/src/postprocess_context.cpp create mode 100644 kandinsky/src/postprocess_gamma_context.cpp create mode 100644 kandinsky/src/postprocess_invert_context.cpp create mode 100644 kandinsky/src/postprocess_zoom_context.cpp diff --git a/escher/include/escher/window.h b/escher/include/escher/window.h index 931f07e3e..7930d64d2 100644 --- a/escher/include/escher/window.h +++ b/escher/include/escher/window.h @@ -6,7 +6,7 @@ class Window : public View { public: Window() : m_contentView(nullptr) {} - void redraw(bool force = false); + virtual void redraw(bool force = false); void setContentView(View * contentView); protected: #if ESCHER_VIEW_LOGGING diff --git a/kandinsky/Makefile b/kandinsky/Makefile index 7aaf9fca2..dfac55922 100644 --- a/kandinsky/Makefile +++ b/kandinsky/Makefile @@ -12,6 +12,10 @@ kandinsky_src += $(addprefix kandinsky/src/,\ framebuffer_context.cpp \ ion_context.cpp \ point.cpp \ + postprocess_context.cpp \ + postprocess_gamma_context.cpp \ + postprocess_invert_context.cpp \ + postprocess_zoom_context.cpp \ rect.cpp \ ) diff --git a/kandinsky/include/kandinsky.h b/kandinsky/include/kandinsky.h index bc3d951c7..26dd0be7e 100644 --- a/kandinsky/include/kandinsky.h +++ b/kandinsky/include/kandinsky.h @@ -9,6 +9,10 @@ #include #include #include +#include +#include +#include +#include #include #include diff --git a/kandinsky/include/kandinsky/color.h b/kandinsky/include/kandinsky/color.h index a416bd2d7..8e1cafa50 100644 --- a/kandinsky/include/kandinsky/color.h +++ b/kandinsky/include/kandinsky/color.h @@ -32,6 +32,7 @@ public: } static KDColor blend(KDColor first, KDColor second, uint8_t alpha); + KDColor invert() const { return KDColor(~m_value); } operator uint16_t() const { return m_value; } private: constexpr KDColor(uint16_t value) : m_value(value) {} diff --git a/kandinsky/include/kandinsky/context.h b/kandinsky/include/kandinsky/context.h index 684be6cf3..bcfc4ee74 100644 --- a/kandinsky/include/kandinsky/context.h +++ b/kandinsky/include/kandinsky/context.h @@ -5,10 +5,15 @@ #include #include +class KDPostProcessContext; + class KDContext { + friend KDPostProcessContext; public: - void setOrigin(KDPoint origin); - void setClippingRect(KDRect clippingRect); + KDPoint origin() const { return m_origin; } + KDRect clippingRect() const { return m_clippingRect; } + virtual void setOrigin(KDPoint origin); + virtual void setClippingRect(KDRect clippingRect); // Pixel manipulation void setPixel(KDPoint p, KDColor c); @@ -28,11 +33,12 @@ public: void fillRectWithPixels(KDRect rect, const KDColor * pixels, KDColor * workingBuffer); void blendRectWithMask(KDRect rect, KDColor color, const uint8_t * mask, KDColor * workingBuffer); void strokeRect(KDRect rect, KDColor color); -protected: - KDContext(KDPoint origin, KDRect clippingRect); + virtual void pushRect(KDRect, const KDColor * pixels) = 0; virtual void pushRectUniform(KDRect rect, KDColor color) = 0; virtual void pullRect(KDRect rect, KDColor * pixels) = 0; +protected: + KDContext(KDPoint origin, KDRect clippingRect); private: KDRect absoluteFillRect(KDRect rect); KDPoint pushOrPullString(const char * text, KDPoint p, const KDFont * font, KDColor textColor, KDColor backgroundColor, int maxByteLength, bool push, int * result = nullptr); diff --git a/kandinsky/include/kandinsky/ion_context.h b/kandinsky/include/kandinsky/ion_context.h index 42eb738ad..5cfd6cd9e 100644 --- a/kandinsky/include/kandinsky/ion_context.h +++ b/kandinsky/include/kandinsky/ion_context.h @@ -2,15 +2,38 @@ #define KANDINSKY_ION_CONTEXT_H #include +#include +#include +#include -class KDIonContext : public KDContext { +class KDRealIonContext : public KDContext { public: - static KDIonContext * sharedContext(); -private: - KDIonContext(); + KDRealIonContext(); void pushRect(KDRect rect, const KDColor * pixels) override; void pushRectUniform(KDRect rect, KDColor color) override; void pullRect(KDRect rect, KDColor * pixels) override; }; +class KDIonContext : public KDContext { +public: + static KDIonContext * sharedContext(); + void updatePostProcessingEffects(); + + KDPostProcessInvertContext invert; + KDPostProcessZoomContext zoom; + KDPostProcessGammaContext gamma; + bool invertEnabled; + bool zoomEnabled; + bool zoomInhibit; + bool gammaEnabled; + int zoomPosition; +private: + KDIonContext(); + void pushRect(KDRect rect, const KDColor * pixels) override; + void pushRectUniform(KDRect rect, KDColor color) override; + void pullRect(KDRect rect, KDColor * pixels) override; + KDContext *rootContext; + KDRealIonContext m_realContext; +}; + #endif diff --git a/kandinsky/include/kandinsky/postprocess_context.h b/kandinsky/include/kandinsky/postprocess_context.h new file mode 100644 index 000000000..7c1ed6aeb --- /dev/null +++ b/kandinsky/include/kandinsky/postprocess_context.h @@ -0,0 +1,20 @@ +#ifndef KANDINSKY_POSTPROCESS_CONTEXT_H +#define KANDINSKY_POSTPROCESS_CONTEXT_H + +#include + +class KDPostProcessContext : public KDContext { +public: + virtual void setOrigin(KDPoint origin) override; + virtual void setClippingRect(KDRect clippingRect) override; + void setTarget(KDContext * context); +protected: + KDPostProcessContext(); + virtual void pushRect(KDRect rect, const KDColor * pixels) override; + virtual void pushRectUniform(KDRect rect, KDColor color) override; + virtual void pullRect(KDRect rect, KDColor * pixels) override; +private: + KDContext * m_target; +}; + +#endif diff --git a/kandinsky/include/kandinsky/postprocess_gamma_context.h b/kandinsky/include/kandinsky/postprocess_gamma_context.h new file mode 100644 index 000000000..c3195f2ff --- /dev/null +++ b/kandinsky/include/kandinsky/postprocess_gamma_context.h @@ -0,0 +1,19 @@ +#ifndef KANDINSKY_POSTPROCESS_GAMMA_CONTEXT_H +#define KANDINSKY_POSTPROCESS_GAMMA_CONTEXT_H + +#include + +class KDPostProcessGammaContext : public KDPostProcessContext { +public: + KDPostProcessGammaContext(); + void gamma(float& red, float& green, float& blue); + void gamma(int& red, int& green, int& blue); + void setGamma(int red, int green, int blue); +private: + void pushRect(KDRect rect, const KDColor * pixels) override; + void pushRectUniform(KDRect rect, KDColor color) override; + void pullRect(KDRect rect, KDColor * pixels) override; + int m_redGamma, m_greenGamma, m_blueGamma; +}; + +#endif diff --git a/kandinsky/include/kandinsky/postprocess_invert_context.h b/kandinsky/include/kandinsky/postprocess_invert_context.h new file mode 100644 index 000000000..bc976fcc5 --- /dev/null +++ b/kandinsky/include/kandinsky/postprocess_invert_context.h @@ -0,0 +1,15 @@ +#ifndef KANDINSKY_POSTPROCESS_INVERT_CONTEXT_H +#define KANDINSKY_POSTPROCESS_INVERT_CONTEXT_H + +#include + +class KDPostProcessInvertContext : public KDPostProcessContext { +public: + KDPostProcessInvertContext() = default; +private: + void pushRect(KDRect rect, const KDColor * pixels) override; + void pushRectUniform(KDRect rect, KDColor color) override; + void pullRect(KDRect rect, KDColor * pixels) override; +}; + +#endif diff --git a/kandinsky/include/kandinsky/postprocess_zoom_context.h b/kandinsky/include/kandinsky/postprocess_zoom_context.h new file mode 100644 index 000000000..028c5bdaa --- /dev/null +++ b/kandinsky/include/kandinsky/postprocess_zoom_context.h @@ -0,0 +1,20 @@ +#ifndef KANDINSKY_POSTPROCESS_ZOOM_CONTEXT_H +#define KANDINSKY_POSTPROCESS_ZOOM_CONTEXT_H + +#include + +class KDPostProcessZoomContext : public KDPostProcessContext { +public: + KDPostProcessZoomContext(); + KDRect viewingArea() const { return m_viewingArea; } + void setViewingArea(KDRect viewingArea) { m_viewingArea = viewingArea; } + KDRect targetArea() const { return m_targetArea; } + void setTargetArea(KDRect targetArea) { m_targetArea = targetArea; } +private: + void pushRect(KDRect rect, const KDColor * pixels) override; + void pushRectUniform(KDRect rect, KDColor color) override; + void pullRect(KDRect rect, KDColor * pixels) override; + KDRect m_viewingArea, m_targetArea; +}; + +#endif diff --git a/kandinsky/src/ion_context.cpp b/kandinsky/src/ion_context.cpp index ee7fcb5af..ee4e1edd4 100644 --- a/kandinsky/src/ion_context.cpp +++ b/kandinsky/src/ion_context.cpp @@ -1,11 +1,40 @@ #include #include +KDRealIonContext::KDRealIonContext() : KDContext(KDPointZero, KDRect(0, 0, Ion::Display::Width, Ion::Display::Height)) {} +void KDRealIonContext::pushRect(KDRect rect, const KDColor * pixels) { + Ion::Display::pushRect(rect, pixels); +} +void KDRealIonContext::pushRectUniform(KDRect rect, KDColor color) { + Ion::Display::pushRectUniform(rect, color); +} +void KDRealIonContext::pullRect(KDRect rect, KDColor * pixels) { + Ion::Display::pullRect(rect, pixels); +} + KDIonContext * KDIonContext::sharedContext() { static KDIonContext context; return &context; } +void KDIonContext::updatePostProcessingEffects() { + rootContext = &m_realContext; + if (invertEnabled) { + invert.setTarget(rootContext); + rootContext = &invert; + } + if (zoomEnabled && !zoomInhibit) { + zoom.setTarget(rootContext); + zoom.setTargetArea(KDRect(0,0,320,240)); + zoom.setViewingArea(KDRect(80*(zoomPosition%3),120-60*(zoomPosition/3),160,120)); + rootContext = &zoom; + } + if (gammaEnabled) { + gamma.setTarget(rootContext); + rootContext = γ + } +} + KDIonContext::KDIonContext() : KDContext(KDPointZero, KDRect(0, 0, Ion::Display::Width, Ion::Display::Height)) @@ -13,13 +42,22 @@ KDContext(KDPointZero, } void KDIonContext::pushRect(KDRect rect, const KDColor * pixels) { - Ion::Display::pushRect(rect, pixels); + if (!rootContext) { + rootContext = &m_realContext; + } + rootContext->pushRect(rect, pixels); } void KDIonContext::pushRectUniform(KDRect rect, KDColor color) { - Ion::Display::pushRectUniform(rect, color); + if (!rootContext) { + rootContext = &m_realContext; + } + rootContext->pushRectUniform(rect, color); } void KDIonContext::pullRect(KDRect rect, KDColor * pixels) { - Ion::Display::pullRect(rect, pixels); + if (!rootContext) { + rootContext = &m_realContext; + } + rootContext->pullRect(rect, pixels); } diff --git a/kandinsky/src/postprocess_context.cpp b/kandinsky/src/postprocess_context.cpp new file mode 100644 index 000000000..db7487546 --- /dev/null +++ b/kandinsky/src/postprocess_context.cpp @@ -0,0 +1,40 @@ +#include +#include + +KDPostProcessContext::KDPostProcessContext() : +KDContext(KDPointZero, KDRectZero), +m_target(nullptr) +{ +} + +void KDPostProcessContext::setOrigin(KDPoint origin) { + KDContext::setOrigin(origin); + assert(m_target); + m_target->setOrigin(origin); +} + +void KDPostProcessContext::setClippingRect(KDRect clippingRect) { + KDContext::setClippingRect(clippingRect); + assert(m_target); + m_target->setClippingRect(clippingRect); +} + +void KDPostProcessContext::setTarget(KDContext * target) +{ + m_target = target; +} + +void KDPostProcessContext::pushRect(KDRect rect, const KDColor * pixels) +{ + m_target->pushRect(rect, pixels); +} + +void KDPostProcessContext::pushRectUniform(KDRect rect, KDColor color) +{ + m_target->pushRectUniform(rect, color); +} + +void KDPostProcessContext::pullRect(KDRect rect, KDColor * pixels) +{ + m_target->pullRect(rect, pixels); +} diff --git a/kandinsky/src/postprocess_gamma_context.cpp b/kandinsky/src/postprocess_gamma_context.cpp new file mode 100644 index 000000000..6a75e60db --- /dev/null +++ b/kandinsky/src/postprocess_gamma_context.cpp @@ -0,0 +1,85 @@ +#include +#include +#include + +constexpr int MaxGammaStates = 7; +constexpr float MaxGammaGamut = 0.75; + +constexpr float toGamma(int gamma) { + return 1.f / (1 + (float(gamma) / MaxGammaStates * MaxGammaGamut)); +} + +constexpr int clampGamma(int gamma) { + return gamma < -MaxGammaStates ? -MaxGammaStates : (gamma > MaxGammaStates ? MaxGammaStates : gamma); +} + +KDPostProcessGammaContext::KDPostProcessGammaContext() : + m_redGamma(0), m_greenGamma(0), m_blueGamma(0) {} + +void KDPostProcessGammaContext::gamma(int& red, int& green, int& blue) { + red = m_redGamma; + green = m_greenGamma; + blue = m_blueGamma; +} + +void KDPostProcessGammaContext::gamma(float& red, float& green, float& blue) { + red = (m_redGamma + MaxGammaStates) / float(MaxGammaStates*2); + green = (m_greenGamma + MaxGammaStates) / float(MaxGammaStates*2); + blue = (m_blueGamma + MaxGammaStates) / float(MaxGammaStates*2); +} + +void KDPostProcessGammaContext::setGamma(int red, int green, int blue) { + m_redGamma = clampGamma(red); + m_greenGamma = clampGamma(green); + m_blueGamma = clampGamma(blue); +} + +void KDPostProcessGammaContext::pushRect(KDRect rect, const KDColor * pixels) { + const float redGamma = toGamma(m_redGamma); + const float greenGamma = toGamma(m_greenGamma); + const float blueGamma = toGamma(m_blueGamma); + KDColor workingBuffer[rect.width()]; + + for (KDCoordinate y = 0; y < rect.height(); y++) { + KDRect workingRect(rect.x(), rect.y()+y, rect.width(), 1); + + for (KDCoordinate x = 0; x < rect.width(); x++) { + const KDColor color = pixels[y*rect.width()+x]; + const KDColor result = KDColor::RGB888( + (uint8_t)(powf(color.red()/255.f, redGamma)*255), + (uint8_t)(powf(color.green()/255.f, greenGamma)*255), + (uint8_t)(powf(color.blue()/255.f, blueGamma)*255)); + workingBuffer[x] = result; + } + KDPostProcessContext::pushRect(workingRect, workingBuffer); + } +} + +void KDPostProcessGammaContext::pushRectUniform(KDRect rect, KDColor color) { + const float redGamma = toGamma(m_redGamma); + const float greenGamma = toGamma(m_greenGamma); + const float blueGamma = toGamma(m_blueGamma); + const KDColor result = KDColor::RGB888( + (uint8_t)(powf(color.red()/255.f, redGamma)*255), + (uint8_t)(powf(color.green()/255.f, greenGamma)*255), + (uint8_t)(powf(color.blue()/255.f, blueGamma)*255)); + + KDPostProcessContext::pushRectUniform(rect, result); +} + +void KDPostProcessGammaContext::pullRect(KDRect rect, KDColor * pixels) { + const float redGamma = 1.f/toGamma(m_redGamma); + const float greenGamma = 1.f/toGamma(m_greenGamma); + const float blueGamma = 1.f/toGamma(m_blueGamma); + KDPostProcessContext::pullRect(rect, pixels); + for (KDCoordinate y = 0; y < rect.height(); y++) { + for (KDCoordinate x = 0; x < rect.width(); x++) { + const KDColor color = pixels[y*rect.width()+x]; + const KDColor result = KDColor::RGB888( + (uint8_t)(powf(color.red()/255.f, redGamma)*255), + (uint8_t)(powf(color.green()/255.f,greenGamma)*255), + (uint8_t)(powf(color.blue()/255.f, blueGamma)*255)); + pixels[y*rect.width()+x] = result; + } + } +} diff --git a/kandinsky/src/postprocess_invert_context.cpp b/kandinsky/src/postprocess_invert_context.cpp new file mode 100644 index 000000000..05471b790 --- /dev/null +++ b/kandinsky/src/postprocess_invert_context.cpp @@ -0,0 +1,28 @@ +#include +#include + +void KDPostProcessInvertContext::pushRect(KDRect rect, const KDColor * pixels) { + KDColor workingBuffer[rect.width()]; + + for (KDCoordinate y = 0; y < rect.height(); y++) { + KDRect workingRect(rect.x(), rect.y()+y, rect.width(), 1); + + for (KDCoordinate x = 0; x < rect.width(); x++) { + workingBuffer[x] = pixels[y*rect.width()+x].invert(); + } + KDPostProcessContext::pushRect(workingRect, workingBuffer); + } +} + +void KDPostProcessInvertContext::pushRectUniform(KDRect rect, KDColor color) { + KDPostProcessContext::pushRectUniform(rect, color.invert()); +} + +void KDPostProcessInvertContext::pullRect(KDRect rect, KDColor * pixels) { + KDPostProcessContext::pullRect(rect, pixels); + for (KDCoordinate y = 0; y < rect.height(); y++) { + for (KDCoordinate x = 0; x < rect.width(); x++) { + pixels[y*rect.width()+x] = pixels[y*rect.width()+x].invert(); + } + } +} diff --git a/kandinsky/src/postprocess_zoom_context.cpp b/kandinsky/src/postprocess_zoom_context.cpp new file mode 100644 index 000000000..8e626d441 --- /dev/null +++ b/kandinsky/src/postprocess_zoom_context.cpp @@ -0,0 +1,53 @@ +#include +#include + +KDPostProcessZoomContext::KDPostProcessZoomContext() : m_viewingArea(KDRectZero), m_targetArea(KDRectZero) +{ +} + +void KDPostProcessZoomContext::pushRect(KDRect rect, const KDColor * pixels) { + auto translatedRect = rect.translatedBy(KDPoint(-m_viewingArea.x(),-m_viewingArea.y())); + auto targetRect = KDRect(translatedRect.x()*2, translatedRect.y()*2, translatedRect.width()*2, translatedRect.height()*2); + auto clippedTargetRect = m_targetArea.intersectedWith(targetRect); + + KDColor targetBuffer[targetRect.width()]; + + for (int y = 0; y < rect.height(); y++) { + for (int x = 0; x < rect.width(); x++) { + targetBuffer[2*x+1] = targetBuffer[2*x] = pixels[y*rect.width()+x]; + } + + for (int i = 0; i < 2; i++) { + auto outputRect = KDRect(targetRect.x(), targetRect.y()+y*2+i, targetRect.width(), 1); + KDPostProcessContext::pushRect(m_targetArea.intersectedWith(outputRect), targetBuffer+(clippedTargetRect.x()-targetRect.x())); + } + } +} + +void KDPostProcessZoomContext::pushRectUniform(KDRect rect, KDColor color) { + auto clippedRect = m_viewingArea.intersectedWith(rect); + auto targetRect = KDRect(clippedRect.x()*2, clippedRect.y()*2, clippedRect.width()*2, clippedRect.height()*2); + targetRect = targetRect.translatedBy(KDPoint(-m_viewingArea.x()*2,-m_viewingArea.y()*2)); + targetRect = m_targetArea.intersectedWith(targetRect); + + KDPostProcessContext::pushRectUniform(targetRect, color); +} + +void KDPostProcessZoomContext::pullRect(KDRect rect, KDColor * pixels) { + auto translatedRect = rect.translatedBy(KDPoint(-m_viewingArea.x(),-m_viewingArea.y())); + auto targetRect = KDRect(translatedRect.x()*2, translatedRect.y()*2, translatedRect.width()*2, translatedRect.height()*2); + auto clippedTargetRect = m_targetArea.intersectedWith(targetRect); + + KDColor targetBuffer[targetRect.width()]; + + for (int y = 0; y < rect.height(); y++) { + memset(targetBuffer, 0x00, sizeof(targetBuffer)); + + auto outputRect = KDRect(targetRect.x(), targetRect.y()+y*2, targetRect.width(), 1).intersectedWith(m_targetArea); + KDPostProcessContext::pullRect(outputRect, targetBuffer+(clippedTargetRect.x()-targetRect.x())); + + for (int x = 0; x < rect.width(); x++) { + pixels[y*rect.width()+x] = targetBuffer[x*2]; + } + } +}