| @@ -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 | |||
| } | |||
| //============================================================================== | |||
| int ColourGradient::createLookupTable (const AffineTransform& transform, HeapBlock <PixelARGB>& lookupTable) const | |||
| void ColourGradient::createLookupTable (PixelARGB* const lookupTable, const int numEntries) const noexcept | |||
| { | |||
| #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); | |||
| jassert (numEntries > 0); | |||
| jassert (colours.getReference(0).position == 0); // The first colour specified has to go at position 0 | |||
| 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); | |||
| PixelARGB pix1 (colours.getReference (0).colour.getPixelARGB()); | |||
| int index = 0; | |||
| if (colours.size() >= 2) | |||
| for (int j = 1; j < colours.size(); ++j) | |||
| { | |||
| 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; | |||
| const ColourPoint& p = colours.getReference (j); | |||
| const int numToDo = roundToInt (p.position * (numEntries - 1)) - index; | |||
| const PixelARGB pix2 (p.colour.getPixelARGB()); | |||
| for (int j = 1; j < colours.size(); ++j) | |||
| for (int i = 0; i < numToDo; ++i) | |||
| { | |||
| 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); | |||
| jassert (index >= 0 && index < numEntries); | |||
| lookupTable[index] = pix1; | |||
| lookupTable[index].tween (pix2, (uint32) (i << 8) / numToDo); | |||
| ++index; | |||
| } | |||
| pix1 = pix2; | |||
| lookupTable[index] = pix1; | |||
| lookupTable[index].tween (pix2, (uint32) (i << 8) / numToDo); | |||
| ++index; | |||
| } | |||
| while (index < numEntries) | |||
| lookupTable [index++] = pix1; | |||
| } | |||
| else | |||
| { | |||
| jassertfalse; // no colours specified! | |||
| pix1 = pix2; | |||
| } | |||
| while (index < numEntries) | |||
| lookupTable [index++] = pix1; | |||
| } | |||
| int ColourGradient::createLookupTable (const AffineTransform& transform, HeapBlock <PixelARGB>& lookupTable) const | |||
| { | |||
| 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); | |||
| createLookupTable (lookupTable, numEntries); | |||
| return numEntries; | |||
| } | |||
| @@ -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 <PixelARGB>& 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; | |||
| @@ -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<float> (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<float> (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! | |||
| @@ -253,27 +253,29 @@ void Viewport::updateVisibleArea() | |||
| horizontalScrollBar.setVisible (hBarVisible); | |||
| verticalScrollBar.setVisible (vBarVisible); | |||
| const Point<int> newContentCompPos (viewportPosToCompPos (visibleOrigin)); | |||
| if (contentComp != nullptr && contentComp->getBounds().getPosition() != newContentCompPos) | |||
| { | |||
| contentComp->setTopLeftPosition (newContentCompPos); // (this will re-entrantly call updateVisibleArea again) | |||
| } | |||
| else | |||
| if (contentComp != nullptr) | |||
| { | |||
| const Rectangle<int> visibleArea (visibleOrigin.getX(), visibleOrigin.getY(), | |||
| jmin (contentBounds.getWidth() - visibleOrigin.getX(), contentArea.getWidth()), | |||
| jmin (contentBounds.getHeight() - visibleOrigin.getY(), contentArea.getHeight())); | |||
| const Point<int> 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; | |||
| } | |||
| } | |||
| const Rectangle<int> visibleArea (visibleOrigin.getX(), visibleOrigin.getY(), | |||
| jmin (contentBounds.getWidth() - visibleOrigin.getX(), contentArea.getWidth()), | |||
| jmin (contentBounds.getHeight() - visibleOrigin.getY(), contentArea.getHeight())); | |||
| horizontalScrollBar.handleUpdateNowIfNeeded(); | |||
| verticalScrollBar.handleUpdateNowIfNeeded(); | |||
| if (lastVisibleArea != visibleArea) | |||
| { | |||
| lastVisibleArea = visibleArea; | |||
| visibleAreaChanged (visibleArea); | |||
| } | |||
| horizontalScrollBar.handleUpdateNowIfNeeded(); | |||
| verticalScrollBar.handleUpdateNowIfNeeded(); | |||
| } | |||
| //============================================================================== | |||
| @@ -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 | |||
| { | |||
| @@ -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<int>& rect, | |||
| const ColourGradient& grad, | |||
| const AffineTransform& transform, | |||
| const int textureSize) | |||
| { | |||
| const Point<float> p1 (grad.point1.transformedBy (transform)); | |||
| const Point<float> p2 (grad.point2.transformedBy (transform)); | |||
| const Point<float> p3 (Point<float> (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<int>& rect, | |||
| const ColourGradient& grad, | |||
| const AffineTransform& transform) | |||
| { | |||
| const Point<float> centre (grad.point1.transformedBy (transform)); | |||
| const float screenRadius = centre.getDistanceFrom (rect.getCentre().toFloat()) | |||
| + Point<int> (rect.getWidth() / 2, | |||
| rect.getHeight() / 2).getDistanceFromOrigin() | |||
| + 8.0f; | |||
| const AffineTransform inverse (transform.inverted()); | |||
| const float renderingRadius = jmax (Point<float> (screenRadius, 0.0f).transformedBy (inverse).getDistanceFromOrigin(), | |||
| Point<float> (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<float> first (grad.point1.translated (renderingRadius, -renderingRadius).transformedBy (transform)); | |||
| Point<float> last (first); | |||
| for (int i = 0; i < numDivisions; ++i) | |||
| { | |||
| const float angle = (i + 1) * (float_Pi * 4.0f / numDivisions); | |||
| const Point<float> 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<int>& rect, | |||
| const ColourGradient& gradient, | |||
| const AffineTransform& transform) | |||
| { | |||
| const int textureSize = 256; | |||
| OpenGLTexture texture; | |||
| HeapBlock<PixelARGB> 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 | |||
| @@ -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<int>& rect, | |||
| const ColourGradient& gradient, | |||
| const AffineTransform& transform); | |||
| }; | |||
| @@ -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); | |||
| } | |||
| Image::BitmapData srcData (image, Image::BitmapData::readOnly); | |||
| void OpenGLTexture::load (const Image& image) | |||
| { | |||
| create (image.getWidth(), image.getHeight()); | |||
| #if JUCE_OPENGL_ES | |||
| enum { internalFormat = GL_RGBA }; | |||
| #else | |||
| enum { internalFormat = 4 }; | |||
| #endif | |||
| Image::BitmapData srcData (image, Image::BitmapData::readOnly); | |||
| 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) | |||
| @@ -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); | |||
| }; | |||