From 58580fc7922f54cb1e16494dab2b55e50e0efdcf Mon Sep 17 00:00:00 2001 From: jules Date: Sat, 8 Oct 2011 14:09:00 +0100 Subject: [PATCH] OpenGL gradient rendering. Viewport fix. --- .../colour/juce_ColourGradient.cpp | 77 +++++----- .../colour/juce_ColourGradient.h | 8 + modules/juce_graphics/geometry/juce_Point.h | 4 +- .../juce_gui_basics/layout/juce_Viewport.cpp | 34 +++-- .../opengl/juce_OpenGLFrameBuffer.cpp | 2 +- .../juce_opengl/opengl/juce_OpenGLHelpers.cpp | 142 ++++++++++++++++++ .../juce_opengl/opengl/juce_OpenGLHelpers.h | 5 + .../juce_opengl/opengl/juce_OpenGLTexture.cpp | 34 +++-- .../juce_opengl/opengl/juce_OpenGLTexture.h | 5 + 9 files changed, 242 insertions(+), 69 deletions(-) diff --git a/modules/juce_graphics/colour/juce_ColourGradient.cpp b/modules/juce_graphics/colour/juce_ColourGradient.cpp index 38207a726a..ed2bbc82a1 100644 --- a/modules/juce_graphics/colour/juce_ColourGradient.cpp +++ b/modules/juce_graphics/colour/juce_ColourGradient.cpp @@ -30,6 +30,9 @@ ColourGradient::ColourGradient() noexcept { #if JUCE_DEBUG point1.setX (987654.0f); + #define JUCE_COLOURGRADIENT_CHECK_COORDS_INITIALISED jassert (point1.getX() != 987654.0f); + #else + #define JUCE_COLOURGRADIENT_CHECK_COORDS_INITIALISED #endif } @@ -147,52 +150,48 @@ Colour ColourGradient::getColourAtPosition (const double position) const noexcep } //============================================================================== +void ColourGradient::createLookupTable (PixelARGB* const lookupTable, const int numEntries) const noexcept +{ + JUCE_COLOURGRADIENT_CHECK_COORDS_INITIALISED // Trying to use this object without setting its co-ordinates? + jassert (colours.size() >= 2); + jassert (numEntries > 0); + jassert (colours.getReference(0).position == 0); // The first colour specified has to go at position 0 + + PixelARGB pix1 (colours.getReference (0).colour.getPixelARGB()); + int index = 0; + + for (int j = 1; j < colours.size(); ++j) + { + const ColourPoint& p = colours.getReference (j); + const int numToDo = roundToInt (p.position * (numEntries - 1)) - index; + const PixelARGB pix2 (p.colour.getPixelARGB()); + + for (int i = 0; i < numToDo; ++i) + { + jassert (index >= 0 && index < numEntries); + + lookupTable[index] = pix1; + lookupTable[index].tween (pix2, (uint32) (i << 8) / numToDo); + ++index; + } + + pix1 = pix2; + } + + while (index < numEntries) + lookupTable [index++] = pix1; +} + int ColourGradient::createLookupTable (const AffineTransform& transform, HeapBlock & lookupTable) const { - #if JUCE_DEBUG - // trying to use the object without setting its co-ordinates? Have a careful read of - // the comments for the constructors. - jassert (point1.getX() != 987654.0f); - #endif + JUCE_COLOURGRADIENT_CHECK_COORDS_INITIALISED // Trying to use this object without setting its co-ordinates? + jassert (colours.size() >= 2); const int numEntries = jlimit (1, jmax (1, (colours.size() - 1) << 8), 3 * (int) point1.transformedBy (transform) .getDistanceFrom (point2.transformedBy (transform))); lookupTable.malloc ((size_t) numEntries); - - if (colours.size() >= 2) - { - jassert (colours.getReference(0).position == 0); // the first colour specified has to go at position 0 - - PixelARGB pix1 (colours.getReference (0).colour.getPixelARGB()); - int index = 0; - - for (int j = 1; j < colours.size(); ++j) - { - const ColourPoint& p = colours.getReference (j); - const int numToDo = roundToInt (p.position * (numEntries - 1)) - index; - const PixelARGB pix2 (p.colour.getPixelARGB()); - - for (int i = 0; i < numToDo; ++i) - { - jassert (index >= 0 && index < numEntries); - - lookupTable[index] = pix1; - lookupTable[index].tween (pix2, (uint32) (i << 8) / numToDo); - ++index; - } - - pix1 = pix2; - } - - while (index < numEntries) - lookupTable [index++] = pix1; - } - else - { - jassertfalse; // no colours specified! - } - + createLookupTable (lookupTable, numEntries); return numEntries; } diff --git a/modules/juce_graphics/colour/juce_ColourGradient.h b/modules/juce_graphics/colour/juce_ColourGradient.h index 5951713825..809942a487 100644 --- a/modules/juce_graphics/colour/juce_ColourGradient.h +++ b/modules/juce_graphics/colour/juce_ColourGradient.h @@ -128,9 +128,17 @@ public: /** Creates a set of interpolated premultiplied ARGB values. This will resize the HeapBlock, fill it with the colours, and will return the number of colours that it added. + When calling this, the ColourGradient must have at least 2 colour stops specified. */ int createLookupTable (const AffineTransform& transform, HeapBlock & resultLookupTable) const; + /** Creates a set of interpolated premultiplied ARGB values. + This will fill an array of a user-specified size with the gradient, interpolating to fit. + The numEntries argument specifies the size of the array, and this size must be greater than zero. + When calling this, the ColourGradient must have at least 2 colour stops specified. + */ + void createLookupTable (PixelARGB* resultLookupTable, int numEntries) const noexcept; + /** Returns true if all colours are opaque. */ bool isOpaque() const noexcept; diff --git a/modules/juce_graphics/geometry/juce_Point.h b/modules/juce_graphics/geometry/juce_Point.h index b6b30e6427..b2c456cecb 100644 --- a/modules/juce_graphics/geometry/juce_Point.h +++ b/modules/juce_graphics/geometry/juce_Point.h @@ -137,14 +137,14 @@ public: @param angle the angle of the point, in radians clockwise from the 12 o'clock position. */ Point getPointOnCircumference (const float radius, const float angle) const noexcept { return Point (x + radius * std::sin (angle), - y - radius * std::cos (angle)); } + y - radius * std::cos (angle)); } /** Taking this point to be the centre of an ellipse, this returns a point on its circumference. @param radiusX the horizontal radius of the circle. @param radiusY the vertical radius of the circle. @param angle the angle of the point, in radians clockwise from the 12 o'clock position. */ Point getPointOnCircumference (const float radiusX, const float radiusY, const float angle) const noexcept { return Point (x + radiusX * std::sin (angle), - y - radiusY * std::cos (angle)); } + y - radiusY * std::cos (angle)); } /** Uses a transform to change the point's co-ordinates. This will only compile if ValueType = float! diff --git a/modules/juce_gui_basics/layout/juce_Viewport.cpp b/modules/juce_gui_basics/layout/juce_Viewport.cpp index 7d66e48db7..e7eb86711b 100644 --- a/modules/juce_gui_basics/layout/juce_Viewport.cpp +++ b/modules/juce_gui_basics/layout/juce_Viewport.cpp @@ -253,27 +253,29 @@ void Viewport::updateVisibleArea() horizontalScrollBar.setVisible (hBarVisible); verticalScrollBar.setVisible (vBarVisible); - const Point newContentCompPos (viewportPosToCompPos (visibleOrigin)); - - if (contentComp != nullptr && contentComp->getBounds().getPosition() != newContentCompPos) + if (contentComp != nullptr) { - contentComp->setTopLeftPosition (newContentCompPos); // (this will re-entrantly call updateVisibleArea again) - } - else - { - const Rectangle visibleArea (visibleOrigin.getX(), visibleOrigin.getY(), - jmin (contentBounds.getWidth() - visibleOrigin.getX(), contentArea.getWidth()), - jmin (contentBounds.getHeight() - visibleOrigin.getY(), contentArea.getHeight())); + const Point newContentCompPos (viewportPosToCompPos (visibleOrigin)); - if (lastVisibleArea != visibleArea) + if (contentComp->getBounds().getPosition() != newContentCompPos) { - lastVisibleArea = visibleArea; - visibleAreaChanged (visibleArea); + contentComp->setTopLeftPosition (newContentCompPos); // (this will re-entrantly call updateVisibleArea again) + return; } - - horizontalScrollBar.handleUpdateNowIfNeeded(); - verticalScrollBar.handleUpdateNowIfNeeded(); } + + const Rectangle visibleArea (visibleOrigin.getX(), visibleOrigin.getY(), + jmin (contentBounds.getWidth() - visibleOrigin.getX(), contentArea.getWidth()), + jmin (contentBounds.getHeight() - visibleOrigin.getY(), contentArea.getHeight())); + + if (lastVisibleArea != visibleArea) + { + lastVisibleArea = visibleArea; + visibleAreaChanged (visibleArea); + } + + horizontalScrollBar.handleUpdateNowIfNeeded(); + verticalScrollBar.handleUpdateNowIfNeeded(); } //============================================================================== diff --git a/modules/juce_opengl/opengl/juce_OpenGLFrameBuffer.cpp b/modules/juce_opengl/opengl/juce_OpenGLFrameBuffer.cpp index d09e14eee9..17274e3811 100644 --- a/modules/juce_opengl/opengl/juce_OpenGLFrameBuffer.cpp +++ b/modules/juce_opengl/opengl/juce_OpenGLFrameBuffer.cpp @@ -639,7 +639,7 @@ public: private: // Some GL implementations can't take very large triangle lists, so store // the list as a series of blocks containing this max number of triangles. - enum { trianglesPerBlock = 2048 }; + enum { trianglesPerBlock = 256 }; struct TriangleBlock { diff --git a/modules/juce_opengl/opengl/juce_OpenGLHelpers.cpp b/modules/juce_opengl/opengl/juce_OpenGLHelpers.cpp index d4202fa093..fea743ae0c 100644 --- a/modules/juce_opengl/opengl/juce_OpenGLHelpers.cpp +++ b/modules/juce_opengl/opengl/juce_OpenGLHelpers.cpp @@ -121,4 +121,146 @@ void OpenGLHelpers::drawQuad3D (float x1, float y1, float z1, glDrawArrays (GL_TRIANGLE_STRIP, 0, 4); } +namespace OpenGLGradientHelpers +{ + void drawTriangles (GLenum mode, const GLfloat* vertices, const GLfloat* textureCoords, const int numElements) + { + glEnableClientState (GL_VERTEX_ARRAY); + glEnableClientState (GL_TEXTURE_COORD_ARRAY); + glDisableClientState (GL_COLOR_ARRAY); + glDisableClientState (GL_NORMAL_ARRAY); + + glVertexPointer (2, GL_FLOAT, 0, vertices); + glTexCoordPointer (2, GL_FLOAT, 0, textureCoords); + + glColor4f (1.0f, 1.0f, 1.0f, 1.0f); + glDrawArrays (mode, 0, numElements); + } + + void fillWithLinearGradient (const Rectangle& rect, + const ColourGradient& grad, + const AffineTransform& transform, + const int textureSize) + { + const Point p1 (grad.point1.transformedBy (transform)); + const Point p2 (grad.point2.transformedBy (transform)); + const Point p3 (Point (grad.point1.getX() - (grad.point2.getY() - grad.point1.getY()) / textureSize, + grad.point1.getY() + (grad.point2.getX() - grad.point1.getX()) / textureSize).transformedBy (transform)); + + const AffineTransform textureTransform (AffineTransform::fromTargetPoints (p1.getX(), p1.getY(), 0.0f, 0.0f, + p2.getX(), p2.getY(), 1.0f, 0.0f, + p3.getX(), p3.getY(), 0.0f, 1.0f)); + + const float l = (float) rect.getX(); + const float r = (float) rect.getRight(); + const float t = (float) rect.getY(); + const float b = (float) rect.getBottom(); + + const GLfloat vertices[] = { l, t, r, t, l, b, r, b }; + GLfloat textureCoords[] = { l, t, r, t, l, b, r, b }; + + textureTransform.transformPoints (textureCoords[0], textureCoords[1], textureCoords[2], textureCoords[3]); + textureTransform.transformPoints (textureCoords[4], textureCoords[5], textureCoords[6], textureCoords[7]); + + drawTriangles (GL_TRIANGLE_STRIP, vertices, textureCoords, 4); + } + + void fillWithRadialGradient (const Rectangle& rect, + const ColourGradient& grad, + const AffineTransform& transform) + { + const Point centre (grad.point1.transformedBy (transform)); + + const float screenRadius = centre.getDistanceFrom (rect.getCentre().toFloat()) + + Point (rect.getWidth() / 2, + rect.getHeight() / 2).getDistanceFromOrigin() + + 8.0f; + + const AffineTransform inverse (transform.inverted()); + const float renderingRadius = jmax (Point (screenRadius, 0.0f).transformedBy (inverse).getDistanceFromOrigin(), + Point (0.0f, screenRadius).transformedBy (inverse).getDistanceFromOrigin()); + + const int numDivisions = 80; + GLfloat vertices [6 + numDivisions * 4]; + GLfloat textureCoords [6 + numDivisions * 4]; + + { + const float originalRadius = grad.point1.getDistanceFrom (grad.point2); + const float texturePos = renderingRadius / originalRadius; + + GLfloat* t = textureCoords; + *t++ = 0.0f; + *t++ = 0.0f; + + for (int i = numDivisions + 1; --i >= 0;) + { + *t++ = texturePos; + *t++ = 0.0f; + *t++ = texturePos; + *t++ = 1.0f; + } + + jassert (t == textureCoords + numElementsInArray (vertices)); + } + + { + GLfloat* v = vertices; + + *v++ = centre.getX(); + *v++ = centre.getY(); + + const Point first (grad.point1.translated (renderingRadius, -renderingRadius).transformedBy (transform)); + Point last (first); + + for (int i = 0; i < numDivisions; ++i) + { + const float angle = (i + 1) * (float_Pi * 4.0f / numDivisions); + const Point next (grad.point1.translated (std::sin (angle) * renderingRadius, + -std::cos (angle) * renderingRadius) + .transformedBy (transform)); + *v++ = last.getX(); + *v++ = last.getY(); + *v++ = next.getX(); + *v++ = next.getY(); + last = next; + } + + *v++ = last.getX(); + *v++ = last.getY(); + *v++ = first.getX(); + *v++ = first.getY(); + + jassert (v == vertices + numElementsInArray (vertices)); + } + + glEnable (GL_SCISSOR_TEST); + glScissor (rect.getX(), rect.getY(), rect.getWidth(), rect.getHeight()); + drawTriangles (GL_TRIANGLE_FAN, vertices, textureCoords, numDivisions + 3); + glDisable (GL_SCISSOR_TEST); + } +} + +void OpenGLHelpers::fillRectWithColourGradient (const Rectangle& rect, + const ColourGradient& gradient, + const AffineTransform& transform) +{ + const int textureSize = 256; + OpenGLTexture texture; + + HeapBlock lookup (textureSize); + gradient.createLookupTable (lookup, textureSize); + texture.load (lookup, textureSize, 1); + texture.bind(); + + if (gradient.isOpaque()) + glDisable (GL_BLEND); + else + glEnable (GL_BLEND); + + if (gradient.isRadial) + OpenGLGradientHelpers::fillWithRadialGradient (rect, gradient, transform); + else + OpenGLGradientHelpers::fillWithLinearGradient (rect, gradient, transform, textureSize); +} + END_JUCE_NAMESPACE diff --git a/modules/juce_opengl/opengl/juce_OpenGLHelpers.h b/modules/juce_opengl/opengl/juce_OpenGLHelpers.h index be781c6d8d..042224e4e0 100644 --- a/modules/juce_opengl/opengl/juce_OpenGLHelpers.h +++ b/modules/juce_opengl/opengl/juce_OpenGLHelpers.h @@ -60,6 +60,11 @@ public: float x3, float y3, float z3, float x4, float y4, float z4, const Colour& colour); + + /** Fills a rectangle with the specified gradient. */ + static void fillRectWithColourGradient (const Rectangle& rect, + const ColourGradient& gradient, + const AffineTransform& transform); }; diff --git a/modules/juce_opengl/opengl/juce_OpenGLTexture.cpp b/modules/juce_opengl/opengl/juce_OpenGLTexture.cpp index 4b839578d9..ec844fc5e7 100644 --- a/modules/juce_opengl/opengl/juce_OpenGLTexture.cpp +++ b/modules/juce_opengl/opengl/juce_OpenGLTexture.cpp @@ -25,6 +25,12 @@ BEGIN_JUCE_NAMESPACE +#if JUCE_OPENGL_ES + enum { internalGLTextureFormat = GL_RGBA }; +#else + enum { internalGLTextureFormat = 4 }; +#endif + OpenGLTexture::OpenGLTexture() : textureID (0), width (0), height (0) @@ -36,12 +42,12 @@ OpenGLTexture::~OpenGLTexture() release(); } -void OpenGLTexture::load (const Image& image) +void OpenGLTexture::create (const int w, const int h) { release(); - width = image.getWidth(); - height = image.getHeight(); + width = w; + height = h; jassert (BitArray (width).countNumberOfSetBits() == 1); // these dimensions must be a power-of-two jassert (BitArray (height).countNumberOfSetBits() == 1); @@ -57,21 +63,27 @@ void OpenGLTexture::load (const Image& image) glTexParameterf (GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); glPixelStorei (GL_UNPACK_ALIGNMENT, 4); +} + +void OpenGLTexture::load (const Image& image) +{ + create (image.getWidth(), image.getHeight()); Image::BitmapData srcData (image, Image::BitmapData::readOnly); - #if JUCE_OPENGL_ES - enum { internalFormat = GL_RGBA }; - #else - enum { internalFormat = 4 }; - #endif - - glTexImage2D (GL_TEXTURE_2D, 0, internalFormat, - width, height, 0, + glTexImage2D (GL_TEXTURE_2D, 0, internalGLTextureFormat, width, height, 0, image.getFormat() == Image::RGB ? GL_RGB : GL_BGRA_EXT, GL_UNSIGNED_BYTE, srcData.data); } +void OpenGLTexture::load (const PixelARGB* const pixels, const int w, const int h) +{ + create (w, h); + + glTexImage2D (GL_TEXTURE_2D, 0, internalGLTextureFormat, w, h, 0, + GL_BGRA_EXT, GL_UNSIGNED_BYTE, pixels); +} + void OpenGLTexture::release() { if (textureID != 0) diff --git a/modules/juce_opengl/opengl/juce_OpenGLTexture.h b/modules/juce_opengl/opengl/juce_OpenGLTexture.h index 4c80604207..b369810aa2 100644 --- a/modules/juce_opengl/opengl/juce_OpenGLTexture.h +++ b/modules/juce_opengl/opengl/juce_OpenGLTexture.h @@ -40,6 +40,9 @@ public: */ void load (const Image& image); + /** Creates a texture from a raw array of pixels. */ + void load (const PixelARGB* pixels, int width, int height); + /** Frees the texture, if there is one. */ void release(); @@ -67,6 +70,8 @@ private: unsigned int textureID; int width, height; + void create (int w, int h); + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (OpenGLTexture); };