| @@ -258,14 +258,14 @@ public: | |||
| : width (w), height (h), | |||
| data (w * h) | |||
| { | |||
| buffer.readPixels (data, Rectangle<int> (0, 0, w, h)); | |||
| buffer.readPixels (data, 0, Rectangle<int> (0, 0, w, h)); | |||
| } | |||
| bool restore (OpenGLFrameBuffer& buffer) | |||
| { | |||
| if (buffer.initialise (width, height)) | |||
| { | |||
| buffer.writePixels (data, Rectangle<int> (0, 0, width, height)); | |||
| buffer.writePixels (data, 0, 4, Rectangle<int> (0, 0, width, height)); | |||
| return true; | |||
| } | |||
| @@ -286,7 +286,7 @@ OpenGLFrameBuffer::~OpenGLFrameBuffer() {} | |||
| bool OpenGLFrameBuffer::initialise (int width, int height) | |||
| { | |||
| pimpl = nullptr; | |||
| pimpl = new Pimpl (width, height, true, false); | |||
| pimpl = new Pimpl (width, height, false, false); | |||
| if (! pimpl->ok) | |||
| pimpl = nullptr; | |||
| @@ -294,6 +294,18 @@ bool OpenGLFrameBuffer::initialise (int width, int height) | |||
| return pimpl != nullptr; | |||
| } | |||
| bool OpenGLFrameBuffer::initialise (const Image& content) | |||
| { | |||
| if (initialise (content.getWidth(), content.getHeight())) | |||
| { | |||
| Image::BitmapData bitmap (content, Image::BitmapData::readOnly); | |||
| return writePixels (bitmap.data, bitmap.lineStride / bitmap.pixelStride, | |||
| bitmap.pixelStride, content.getBounds()); | |||
| } | |||
| return false; | |||
| } | |||
| void OpenGLFrameBuffer::release() | |||
| { | |||
| pimpl = nullptr; | |||
| @@ -311,11 +323,14 @@ void OpenGLFrameBuffer::saveAndRelease() | |||
| bool OpenGLFrameBuffer::reloadSavedCopy() | |||
| { | |||
| if (savedState != nullptr | |||
| && savedState->restore (*this)) | |||
| if (savedState != nullptr) | |||
| { | |||
| savedState = nullptr; | |||
| return true; | |||
| ScopedPointer<SavedState> state (savedState); | |||
| if (state->restore (*this)) | |||
| return true; | |||
| savedState = state; | |||
| } | |||
| return false; | |||
| @@ -349,7 +364,7 @@ void OpenGLFrameBuffer::clear (const Colour& colour) | |||
| } | |||
| } | |||
| bool OpenGLFrameBuffer::readPixels (void* target, const Rectangle<int>& area) | |||
| bool OpenGLFrameBuffer::readPixels (void* target, int lineStride, const Rectangle<int>& area) | |||
| { | |||
| if (! makeCurrentTarget()) | |||
| return false; | |||
| @@ -357,6 +372,7 @@ bool OpenGLFrameBuffer::readPixels (void* target, const Rectangle<int>& area) | |||
| OpenGLHelpers::prepareFor2D (pimpl->width, pimpl->height); | |||
| glPixelStorei (GL_PACK_ALIGNMENT, 4); | |||
| glPixelStorei (GL_PACK_ROW_LENGTH, lineStride); | |||
| glReadPixels (area.getX(), area.getY(), area.getWidth(), area.getHeight(), GL_RGBA, GL_UNSIGNED_BYTE, target); | |||
| glBindFramebufferEXT (GL_FRAMEBUFFER_EXT, 0); | |||
| @@ -364,13 +380,16 @@ bool OpenGLFrameBuffer::readPixels (void* target, const Rectangle<int>& area) | |||
| return true; | |||
| } | |||
| bool OpenGLFrameBuffer::writePixels (const void* data, const Rectangle<int>& area) | |||
| bool OpenGLFrameBuffer::writePixels (const void* data, int lineStride, int pixelStride, const Rectangle<int>& area) | |||
| { | |||
| if (! makeCurrentTarget()) | |||
| return false; | |||
| OpenGLHelpers::prepareFor2D (pimpl->width, pimpl->height); | |||
| jassert (pixelStride == 3 || pixelStride == 4); // can only handle RGB or ARGB | |||
| const int format = pixelStride == 3 ? GL_RGB : GL_BGRA_EXT; | |||
| glDisable (GL_DEPTH_TEST); | |||
| glDisable (GL_BLEND); | |||
| @@ -385,9 +404,10 @@ bool OpenGLFrameBuffer::writePixels (const void* data, const Rectangle<int>& are | |||
| glEnable (GL_TEXTURE_2D); | |||
| glBindTexture (GL_TEXTURE_2D, temporaryTexture); | |||
| glPixelStorei (GL_UNPACK_ALIGNMENT, 4); | |||
| glPixelStorei (GL_UNPACK_ALIGNMENT, pixelStride); | |||
| glPixelStorei (GL_UNPACK_ROW_LENGTH, lineStride); | |||
| glTexImage2D (GL_TEXTURE_2D, 0, GL_RGBA, area.getWidth(), area.getHeight(), 0, | |||
| GL_BGRA_EXT, GL_UNSIGNED_BYTE, data); | |||
| format, GL_UNSIGNED_BYTE, data); | |||
| glTexParameterf (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); | |||
| glTexParameterf (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); | |||
| @@ -404,8 +424,9 @@ bool OpenGLFrameBuffer::writePixels (const void* data, const Rectangle<int>& are | |||
| #else | |||
| glRasterPos2i (area.getX(), area.getY()); | |||
| glBindTexture (GL_TEXTURE_2D, 0); | |||
| glPixelStorei (GL_UNPACK_ALIGNMENT, 4); | |||
| glDrawPixels (area.getWidth(), area.getHeight(), GL_BGRA_EXT, GL_UNSIGNED_BYTE, data); | |||
| glPixelStorei (GL_UNPACK_ALIGNMENT, pixelStride); | |||
| glPixelStorei (GL_UNPACK_ROW_LENGTH, lineStride); | |||
| glDrawPixels (area.getWidth(), area.getHeight(), format, GL_UNSIGNED_BYTE, data); | |||
| #endif | |||
| glBindFramebufferEXT (GL_FRAMEBUFFER_EXT, 0); | |||
| @@ -441,350 +462,6 @@ void OpenGLFrameBuffer::draw3D (float x1, float y1, float z1, | |||
| } | |||
| } | |||
| //============================================================================== | |||
| // This breaks down a path into a series of horizontal strips of trapezoids.. | |||
| class TrapezoidedPath | |||
| { | |||
| public: | |||
| TrapezoidedPath (const Path& p) | |||
| : firstSlice (nullptr), | |||
| windingMask (p.isUsingNonZeroWinding() ? -1 : 1) | |||
| { | |||
| for (PathFlatteningIterator iter (p); iter.next();) | |||
| addLine (floatToInt (iter.x1), floatToInt (iter.y1), | |||
| floatToInt (iter.x2), floatToInt (iter.y2)); | |||
| } | |||
| ~TrapezoidedPath() | |||
| { | |||
| for (HorizontalSlice* s = firstSlice; s != nullptr;) | |||
| { | |||
| const ScopedPointer<HorizontalSlice> deleter (s); | |||
| s = s->next; | |||
| } | |||
| } | |||
| template <class Consumer> | |||
| void iterate (Consumer& consumer) const | |||
| { | |||
| for (HorizontalSlice* s = firstSlice; s != nullptr; s = s->next) | |||
| s->iterate (consumer, windingMask); | |||
| } | |||
| private: | |||
| void addLine (int x1, int y1, int x2, int y2) | |||
| { | |||
| int winding = 1; | |||
| if (y2 < y1) | |||
| { | |||
| std::swap (x1, x2); | |||
| std::swap (y1, y2); | |||
| winding = -1; | |||
| } | |||
| HorizontalSlice* last = nullptr; | |||
| HorizontalSlice* s = firstSlice; | |||
| while (y2 > y1) | |||
| { | |||
| if (s == nullptr) | |||
| { | |||
| insert (last, new HorizontalSlice (nullptr, x1, y1, x2, y2, winding)); | |||
| break; | |||
| } | |||
| if (s->y2 > y1) | |||
| { | |||
| if (y1 < s->y1) | |||
| { | |||
| if (y2 <= s->y1) | |||
| { | |||
| insert (last, new HorizontalSlice (s, x1, y1, x2, y2, winding)); | |||
| break; | |||
| } | |||
| else | |||
| { | |||
| const int newX = x1 + (s->y1 - y1) * (x2 - x1) / (y2 - y1); | |||
| HorizontalSlice* const newSlice = new HorizontalSlice (s, x1, y1, newX, s->y1, winding); | |||
| insert (last, newSlice); | |||
| last = newSlice; | |||
| x1 = newX; | |||
| y1 = s->y1; | |||
| continue; | |||
| } | |||
| } | |||
| else if (y1 > s->y1) | |||
| { | |||
| s->split (y1); | |||
| s = s->next; | |||
| jassert (s != nullptr); | |||
| } | |||
| jassert (y1 == s->y1); | |||
| if (y2 > s->y2) | |||
| { | |||
| const int newY = s->y2; | |||
| const int newX = x1 + (newY - y1) * (x2 - x1) / (y2 - y1); | |||
| s->addLine (x1, newX, winding); | |||
| x1 = newX; | |||
| y1 = newY; | |||
| } | |||
| else | |||
| { | |||
| if (y2 < s->y2) | |||
| s->split (y2); | |||
| jassert (y2 == s->y2); | |||
| s->addLine (x1, x2, winding); | |||
| break; | |||
| } | |||
| } | |||
| last = s; | |||
| s = s->next; | |||
| } | |||
| } | |||
| struct HorizontalSlice | |||
| { | |||
| HorizontalSlice (const HorizontalSlice& other, HorizontalSlice* const next_, int y1_, int y2_) | |||
| : next (next_), y1 (y1_), y2 (y2_), segments (other.segments) | |||
| { | |||
| } | |||
| HorizontalSlice (HorizontalSlice* const next_, int x1, int y1_, int x2, int y2_, int winding) | |||
| : next (next_), y1 (y1_), y2 (y2_) | |||
| { | |||
| jassert (next != this); | |||
| jassert (y2 > y1); | |||
| segments.ensureStorageAllocated (32); | |||
| segments.add (LineSegment (x1, x2, winding)); | |||
| } | |||
| void addLine (const int x1, const int x2, int winding) | |||
| { | |||
| const int dy = y2 - y1; | |||
| for (int i = 0; i < segments.size(); ++i) | |||
| { | |||
| const LineSegment& l = segments.getReference (i); | |||
| const int diff1 = l.x1 - x1; | |||
| const int diff2 = l.x2 - x2; | |||
| if ((diff1 < 0) == (diff2 > 0)) | |||
| { | |||
| const int dx1 = l.x2 - l.x1; | |||
| const int dx2 = x2 - x1; | |||
| const int dxDiff = dx2 - dx1; | |||
| if (dxDiff != 0) | |||
| { | |||
| const int intersectionY = (dy * diff1) / dxDiff; | |||
| if (intersectionY > 0 && intersectionY < dy) | |||
| { | |||
| const int intersectionX = x1 + (intersectionY * dx2) / dy; | |||
| split (intersectionY + y1); | |||
| next->addLine (intersectionX, x2, winding); | |||
| addLine (x1, intersectionX, winding); | |||
| return; | |||
| } | |||
| } | |||
| } | |||
| if (diff1 + diff2 > 0) | |||
| { | |||
| segments.insert (i, LineSegment (x1, x2, winding)); | |||
| return; | |||
| } | |||
| } | |||
| segments.add (LineSegment (x1, x2, winding)); | |||
| } | |||
| void split (const int newY) | |||
| { | |||
| jassert (newY > y1 && newY < y2); | |||
| const int dy1 = newY - y1; | |||
| const int dy2 = y2 - y1; | |||
| next = new HorizontalSlice (*this, next, newY, y2); | |||
| y2 = newY; | |||
| LineSegment* const oldSegments = segments.getRawDataPointer(); | |||
| LineSegment* const newSegments = next->segments.getRawDataPointer(); | |||
| for (int i = 0; i < segments.size(); ++i) | |||
| { | |||
| LineSegment& l = oldSegments[i]; | |||
| const int newX = l.x1 + dy1 * (l.x2 - l.x1) / dy2; | |||
| newSegments[i].x1 = newX; | |||
| l.x2 = newX; | |||
| } | |||
| } | |||
| template <class Consumer> | |||
| void iterate (Consumer& consumer, const int windingMask) | |||
| { | |||
| jassert (segments.size() > 0); | |||
| const float fy1 = intToFloat (y1); | |||
| const float fy2 = intToFloat (y2); | |||
| const LineSegment* s1 = segments.getRawDataPointer(); | |||
| const LineSegment* s2 = s1; | |||
| int winding = s1->winding; | |||
| for (int i = segments.size(); --i > 0;) | |||
| { | |||
| ++s2; | |||
| winding += s2->winding; | |||
| if ((winding & windingMask) == 0) | |||
| { | |||
| const float ax1 = intToFloat (s1->x1); | |||
| const float ax2 = intToFloat (s1->x2); | |||
| if (s1->x1 == s2->x1) | |||
| consumer.addTriangle (ax1, fy1, ax2, fy2, intToFloat (s2->x2), fy2); | |||
| else if (s1->x2 == s2->x2) | |||
| consumer.addTriangle (ax1, fy1, intToFloat (s2->x1), fy1, ax2, fy2); | |||
| else | |||
| consumer.addTrapezoid (fy1, fy2, ax1, ax2, intToFloat (s2->x1), intToFloat (s2->x2)); | |||
| s1 = s2 + 1; | |||
| } | |||
| } | |||
| } | |||
| HorizontalSlice* next; | |||
| int y1, y2; | |||
| private: | |||
| struct LineSegment | |||
| { | |||
| inline LineSegment (int x1_, int x2_, int winding_) noexcept | |||
| : x1 (x1_), x2 (x2_), winding (winding_) {} | |||
| int x1, x2; | |||
| int winding; | |||
| }; | |||
| Array<LineSegment> segments; | |||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (HorizontalSlice); | |||
| }; | |||
| HorizontalSlice* firstSlice; | |||
| const int windingMask; | |||
| inline void insert (HorizontalSlice* const last, HorizontalSlice* const newOne) noexcept | |||
| { | |||
| if (last == nullptr) | |||
| firstSlice = newOne; | |||
| else | |||
| last->next = newOne; | |||
| } | |||
| enum { factor = 128 }; | |||
| static inline int floatToInt (const float n) noexcept { return roundToInt (n * (float) factor); } | |||
| static inline float intToFloat (const int n) noexcept { return n * (1.0f / (float) factor); } | |||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (TrapezoidedPath); | |||
| }; | |||
| //============================================================================== | |||
| // Breaks a path into a set of openGL triangles.. | |||
| class TriangulatedPath | |||
| { | |||
| public: | |||
| TriangulatedPath (const Path& path) | |||
| { | |||
| startNewBlock(); | |||
| TrapezoidedPath (path).iterate (*this); | |||
| } | |||
| void draw (const int oversamplingLevel) const | |||
| { | |||
| glColor4f (1.0f, 1.0f, 1.0f, 1.0f / (oversamplingLevel * oversamplingLevel)); | |||
| glTranslatef (-0.5f, -0.5f, 0.0f); | |||
| const float inc = 1.0f / oversamplingLevel; | |||
| for (int y = oversamplingLevel; --y >= 0;) | |||
| { | |||
| for (int x = oversamplingLevel; --x >= 0;) | |||
| { | |||
| glTranslatef (inc, 0.0f, 0.0f); | |||
| for (int i = 0; i < blocks.size(); ++i) | |||
| blocks.getUnchecked(i)->draw(); | |||
| } | |||
| glTranslatef (-1.0f, inc, 0.0f); | |||
| } | |||
| } | |||
| void addTriangle (GLfloat x1, GLfloat y1, GLfloat x2, GLfloat y2, GLfloat x3, GLfloat y3) | |||
| { | |||
| if (currentBlock->numDone >= trianglesPerBlock) | |||
| startNewBlock(); | |||
| GLfloat* t = currentBlock->getNextTriangle(); | |||
| *t++ = x1; *t++ = y1; *t++ = x2; *t++ = y2; *t++ = x3; *t++ = y3; | |||
| currentBlock->numDone++; | |||
| } | |||
| void addTrapezoid (GLfloat y1, GLfloat y2, GLfloat x1, GLfloat x2, GLfloat x3, GLfloat x4) | |||
| { | |||
| if (currentBlock->numDone >= trianglesPerBlock - 1) | |||
| startNewBlock(); | |||
| GLfloat* t = currentBlock->getNextTriangle(); | |||
| *t++ = x1; *t++ = y1; *t++ = x2; *t++ = y2; *t++ = x3; *t++ = y1; | |||
| *t++ = x4; *t++ = y2; *t++ = x2; *t++ = y2; *t++ = x3; *t++ = y1; | |||
| currentBlock->numDone += 2; | |||
| } | |||
| 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 = 256 }; | |||
| struct TriangleBlock | |||
| { | |||
| TriangleBlock() noexcept : numDone (0) {} | |||
| void draw() const | |||
| { | |||
| glVertexPointer (2, GL_FLOAT, 0, triangles); | |||
| glDrawArrays (GL_TRIANGLES, 0, numDone * 3); | |||
| } | |||
| inline GLfloat* getNextTriangle() noexcept { return triangles + numDone * 6; } | |||
| int numDone; | |||
| GLfloat triangles [trianglesPerBlock * 6]; | |||
| }; | |||
| void startNewBlock() | |||
| { | |||
| currentBlock = new TriangleBlock(); | |||
| blocks.add (currentBlock); | |||
| } | |||
| OwnedArray<TriangleBlock> blocks; | |||
| TriangleBlock* currentBlock; | |||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (TriangulatedPath); | |||
| }; | |||
| //============================================================================== | |||
| void OpenGLFrameBuffer::createAlphaChannelFromPath (const Path& path, const int oversamplingLevel) | |||
| { | |||
| @@ -49,6 +49,12 @@ public: | |||
| */ | |||
| bool initialise (int width, int height); | |||
| /** Tries to allocates a buffer containing a copy of a given image. | |||
| Note that a valid openGL context must be selected when you call this method, | |||
| or it will fail. | |||
| */ | |||
| bool initialise (const Image& content); | |||
| /** Releases the buffer, if one has been allocated. | |||
| Any saved state that was created with saveAndRelease() will also be freed by this call. | |||
| */ | |||
| @@ -102,11 +108,19 @@ public: | |||
| float x4, float y4, float z4, | |||
| const Colour& colour) const; | |||
| /** Reads an area of pixels from the framebuffer into a specified pixel array. */ | |||
| bool readPixels (void* target, const Rectangle<int>& area); | |||
| /** Reads an area of pixels from the framebuffer into a 32-bit ARGB pixel array. | |||
| The lineStride is measured as a number of pixels, not bytes - pass a stride | |||
| of 0 to indicate a packed array. | |||
| */ | |||
| bool readPixels (void* targetData, int lineStride, const Rectangle<int>& sourceArea); | |||
| /** Writes an area of pixels into the framebuffer from a specified pixel array. */ | |||
| bool writePixels (const void* target, const Rectangle<int>& area); | |||
| /** Writes an area of pixels into the framebuffer from a specified pixel array. | |||
| The lineStride is measured as a number of pixels, not bytes - pass a stride | |||
| of 0 to indicate a packed array. | |||
| */ | |||
| bool writePixels (const void* srcData, | |||
| int srcLineStride, int srcPixelStride, | |||
| const Rectangle<int>& targetArea); | |||
| /** This will render an anti-aliased path into just the alpha channel of this framebuffer. | |||
| @@ -121,6 +121,7 @@ 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) | |||
| @@ -180,27 +181,23 @@ namespace OpenGLGradientHelpers | |||
| const float sourceRadius = 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 int numDivisions = 90; | |||
| GLfloat vertices [4 + numDivisions * 2]; | |||
| GLfloat textureCoords [4 + numDivisions * 2]; | |||
| { | |||
| const float originalRadius = grad.point1.getDistanceFrom (grad.point2); | |||
| const float texturePos = sourceRadius / originalRadius; | |||
| GLfloat* t = textureCoords; | |||
| *t++ = 0.0f; | |||
| *t++ = 0.0f; | |||
| const float originalRadius = grad.point1.getDistanceFrom (grad.point2); | |||
| const float texturePos = sourceRadius / originalRadius; | |||
| for (int i = numDivisions + 1; --i >= 0;) | |||
| { | |||
| *t++ = texturePos; | |||
| *t++ = 0.0f; | |||
| *t++ = texturePos; | |||
| *t++ = 1.0f; | |||
| } | |||
| jassert (t == textureCoords + numElementsInArray (vertices)); | |||
| } | |||
| { | |||
| @@ -208,33 +205,28 @@ namespace OpenGLGradientHelpers | |||
| *v++ = centre.getX(); | |||
| *v++ = centre.getY(); | |||
| const Point<float> first (grad.point1.translated (sourceRadius, -sourceRadius).transformedBy (transform)); | |||
| Point<float> last (first); | |||
| const Point<float> first (grad.point1.translated (0, -sourceRadius) | |||
| .transformedBy (transform)); | |||
| *v++ = first.getX(); | |||
| *v++ = first.getY(); | |||
| for (int i = 0; i < numDivisions; ++i) | |||
| for (int i = 1; i < numDivisions; ++i) | |||
| { | |||
| const float angle = (i + 1) * (float_Pi * 4.0f / numDivisions); | |||
| const Point<float> next (grad.point1.translated (std::sin (angle) * sourceRadius, | |||
| std::cos (angle) * -sourceRadius) | |||
| .transformedBy (transform)); | |||
| *v++ = last.getX(); | |||
| *v++ = last.getY(); | |||
| *v++ = next.getX(); | |||
| *v++ = next.getY(); | |||
| last = next; | |||
| const float angle = i * (float_Pi * 2.0f / numDivisions); | |||
| const Point<float> p (grad.point1.translated (std::sin (angle) * sourceRadius, | |||
| std::cos (angle) * -sourceRadius) | |||
| .transformedBy (transform)); | |||
| *v++ = p.getX(); | |||
| *v++ = p.getY(); | |||
| } | |||
| *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); | |||
| drawTriangles (GL_TRIANGLE_FAN, vertices, textureCoords, numDivisions + 2); | |||
| glDisable (GL_SCISSOR_TEST); | |||
| } | |||
| } | |||
| @@ -256,10 +248,375 @@ void OpenGLHelpers::fillRectWithColourGradient (const Rectangle<int>& rect, | |||
| else | |||
| glEnable (GL_BLEND); | |||
| if (gradient.isRadial) | |||
| OpenGLGradientHelpers::fillWithRadialGradient (rect, gradient, transform); | |||
| if (gradient.point1 == gradient.point2) | |||
| { | |||
| fillRectWithColour (rect, gradient.getColourAtPosition (1.0)); | |||
| } | |||
| else | |||
| OpenGLGradientHelpers::fillWithLinearGradient (rect, gradient, transform, textureSize); | |||
| { | |||
| if (gradient.isRadial) | |||
| OpenGLGradientHelpers::fillWithRadialGradient (rect, gradient, transform); | |||
| else | |||
| OpenGLGradientHelpers::fillWithLinearGradient (rect, gradient, transform, textureSize); | |||
| } | |||
| } | |||
| void OpenGLHelpers::fillRectWithColour (const Rectangle<int>& rect, const Colour& colour) | |||
| { | |||
| glEnableClientState (GL_VERTEX_ARRAY); | |||
| glDisableClientState (GL_TEXTURE_COORD_ARRAY); | |||
| glDisableClientState (GL_COLOR_ARRAY); | |||
| glDisableClientState (GL_NORMAL_ARRAY); | |||
| const GLfloat vertices[] = { (float) rect.getX(), (float) rect.getY(), | |||
| (float) rect.getRight(), (float) rect.getY(), | |||
| (float) rect.getX(), (float) rect.getBottom(), | |||
| (float) rect.getRight(), (float) rect.getBottom() }; | |||
| setColour (colour); | |||
| glVertexPointer (2, GL_FLOAT, 0, vertices); | |||
| glDrawArrays (GL_TRIANGLE_STRIP, 0, 4); | |||
| } | |||
| //============================================================================== | |||
| // This breaks down a path into a series of horizontal strips of trapezoids.. | |||
| class TriangulatedPath::TrapezoidedPath | |||
| { | |||
| public: | |||
| TrapezoidedPath (const Path& p) | |||
| : firstSlice (nullptr), | |||
| windingMask (p.isUsingNonZeroWinding() ? -1 : 1) | |||
| { | |||
| for (PathFlatteningIterator iter (p); iter.next();) | |||
| addLine (floatToInt (iter.x1), floatToInt (iter.y1), | |||
| floatToInt (iter.x2), floatToInt (iter.y2)); | |||
| } | |||
| ~TrapezoidedPath() | |||
| { | |||
| for (HorizontalSlice* s = firstSlice; s != nullptr;) | |||
| { | |||
| const ScopedPointer<HorizontalSlice> deleter (s); | |||
| s = s->next; | |||
| } | |||
| } | |||
| template <class Consumer> | |||
| void iterate (Consumer& consumer) const | |||
| { | |||
| for (HorizontalSlice* s = firstSlice; s != nullptr; s = s->next) | |||
| s->iterate (consumer, windingMask); | |||
| } | |||
| private: | |||
| void addLine (int x1, int y1, int x2, int y2) | |||
| { | |||
| int winding = 1; | |||
| if (y2 < y1) | |||
| { | |||
| std::swap (x1, x2); | |||
| std::swap (y1, y2); | |||
| winding = -1; | |||
| } | |||
| HorizontalSlice* last = nullptr; | |||
| HorizontalSlice* s = firstSlice; | |||
| while (y2 > y1) | |||
| { | |||
| if (s == nullptr) | |||
| { | |||
| insert (last, new HorizontalSlice (nullptr, x1, y1, x2, y2, winding)); | |||
| break; | |||
| } | |||
| if (s->y2 > y1) | |||
| { | |||
| if (y1 < s->y1) | |||
| { | |||
| if (y2 <= s->y1) | |||
| { | |||
| insert (last, new HorizontalSlice (s, x1, y1, x2, y2, winding)); | |||
| break; | |||
| } | |||
| else | |||
| { | |||
| const int newX = x1 + (s->y1 - y1) * (x2 - x1) / (y2 - y1); | |||
| HorizontalSlice* const newSlice = new HorizontalSlice (s, x1, y1, newX, s->y1, winding); | |||
| insert (last, newSlice); | |||
| last = newSlice; | |||
| x1 = newX; | |||
| y1 = s->y1; | |||
| continue; | |||
| } | |||
| } | |||
| else if (y1 > s->y1) | |||
| { | |||
| s->split (y1); | |||
| s = s->next; | |||
| jassert (s != nullptr); | |||
| } | |||
| jassert (y1 == s->y1); | |||
| if (y2 > s->y2) | |||
| { | |||
| const int newY = s->y2; | |||
| const int newX = x1 + (newY - y1) * (x2 - x1) / (y2 - y1); | |||
| s->addLine (x1, newX, winding); | |||
| x1 = newX; | |||
| y1 = newY; | |||
| } | |||
| else | |||
| { | |||
| if (y2 < s->y2) | |||
| s->split (y2); | |||
| jassert (y2 == s->y2); | |||
| s->addLine (x1, x2, winding); | |||
| break; | |||
| } | |||
| } | |||
| last = s; | |||
| s = s->next; | |||
| } | |||
| } | |||
| struct HorizontalSlice | |||
| { | |||
| HorizontalSlice (const HorizontalSlice& other, HorizontalSlice* const next_, int y1_, int y2_) | |||
| : next (next_), y1 (y1_), y2 (y2_), segments (other.segments) | |||
| { | |||
| } | |||
| HorizontalSlice (HorizontalSlice* const next_, int x1, int y1_, int x2, int y2_, int winding) | |||
| : next (next_), y1 (y1_), y2 (y2_) | |||
| { | |||
| jassert (next != this); | |||
| jassert (y2 > y1); | |||
| segments.ensureStorageAllocated (32); | |||
| segments.add (LineSegment (x1, x2, winding)); | |||
| } | |||
| void addLine (const int x1, const int x2, int winding) | |||
| { | |||
| const int dy = y2 - y1; | |||
| for (int i = 0; i < segments.size(); ++i) | |||
| { | |||
| const LineSegment& l = segments.getReference (i); | |||
| const int diff1 = l.x1 - x1; | |||
| const int diff2 = l.x2 - x2; | |||
| if ((diff1 < 0) == (diff2 > 0)) | |||
| { | |||
| const int dx1 = l.x2 - l.x1; | |||
| const int dx2 = x2 - x1; | |||
| const int dxDiff = dx2 - dx1; | |||
| if (dxDiff != 0) | |||
| { | |||
| const int intersectionY = (dy * diff1) / dxDiff; | |||
| if (intersectionY > 0 && intersectionY < dy) | |||
| { | |||
| const int intersectionX = x1 + (intersectionY * dx2) / dy; | |||
| split (intersectionY + y1); | |||
| next->addLine (intersectionX, x2, winding); | |||
| addLine (x1, intersectionX, winding); | |||
| return; | |||
| } | |||
| } | |||
| } | |||
| if (diff1 + diff2 > 0) | |||
| { | |||
| segments.insert (i, LineSegment (x1, x2, winding)); | |||
| return; | |||
| } | |||
| } | |||
| segments.add (LineSegment (x1, x2, winding)); | |||
| } | |||
| void split (const int newY) | |||
| { | |||
| jassert (newY > y1 && newY < y2); | |||
| const int dy1 = newY - y1; | |||
| const int dy2 = y2 - y1; | |||
| next = new HorizontalSlice (*this, next, newY, y2); | |||
| y2 = newY; | |||
| LineSegment* const oldSegments = segments.getRawDataPointer(); | |||
| LineSegment* const newSegments = next->segments.getRawDataPointer(); | |||
| for (int i = 0; i < segments.size(); ++i) | |||
| { | |||
| LineSegment& l = oldSegments[i]; | |||
| const int newX = l.x1 + dy1 * (l.x2 - l.x1) / dy2; | |||
| newSegments[i].x1 = newX; | |||
| l.x2 = newX; | |||
| } | |||
| } | |||
| template <class Consumer> | |||
| void iterate (Consumer& consumer, const int windingMask) | |||
| { | |||
| jassert (segments.size() > 0); | |||
| const float fy1 = intToFloat (y1); | |||
| const float fy2 = intToFloat (y2); | |||
| const LineSegment* s1 = segments.getRawDataPointer(); | |||
| const LineSegment* s2 = s1; | |||
| int winding = s1->winding; | |||
| for (int i = segments.size(); --i > 0;) | |||
| { | |||
| ++s2; | |||
| winding += s2->winding; | |||
| if ((winding & windingMask) == 0) | |||
| { | |||
| const float ax1 = intToFloat (s1->x1); | |||
| const float ax2 = intToFloat (s1->x2); | |||
| if (s1->x1 == s2->x1) | |||
| consumer.addTriangle (ax1, fy1, ax2, fy2, intToFloat (s2->x2), fy2); | |||
| else if (s1->x2 == s2->x2) | |||
| consumer.addTriangle (ax1, fy1, intToFloat (s2->x1), fy1, ax2, fy2); | |||
| else | |||
| consumer.addTrapezoid (fy1, fy2, ax1, ax2, intToFloat (s2->x1), intToFloat (s2->x2)); | |||
| s1 = s2 + 1; | |||
| } | |||
| } | |||
| } | |||
| HorizontalSlice* next; | |||
| int y1, y2; | |||
| private: | |||
| struct LineSegment | |||
| { | |||
| inline LineSegment (int x1_, int x2_, int winding_) noexcept | |||
| : x1 (x1_), x2 (x2_), winding (winding_) {} | |||
| int x1, x2; | |||
| int winding; | |||
| }; | |||
| Array<LineSegment> segments; | |||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (HorizontalSlice); | |||
| }; | |||
| HorizontalSlice* firstSlice; | |||
| const int windingMask; | |||
| inline void insert (HorizontalSlice* const last, HorizontalSlice* const newOne) noexcept | |||
| { | |||
| if (last == nullptr) | |||
| firstSlice = newOne; | |||
| else | |||
| last->next = newOne; | |||
| } | |||
| enum { factor = 128 }; | |||
| static inline int floatToInt (const float n) noexcept { return roundToInt (n * (float) factor); } | |||
| static inline float intToFloat (const int n) noexcept { return n * (1.0f / (float) factor); } | |||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (TrapezoidedPath); | |||
| }; | |||
| //============================================================================== | |||
| struct TriangulatedPath::TriangleBlock | |||
| { | |||
| TriangleBlock() noexcept | |||
| : numVertices (0), | |||
| triangles (maxVerticesPerBlock) | |||
| {} | |||
| void draw() const | |||
| { | |||
| glVertexPointer (2, GL_FLOAT, 0, triangles); | |||
| glDrawArrays (GL_TRIANGLES, 0, numVertices / 2); | |||
| } | |||
| inline GLfloat* getNextTriangle() noexcept { return triangles + numVertices; } | |||
| void optimiseStorage() { triangles.realloc (numVertices); } | |||
| // 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 { maxVerticesPerBlock = 256 * 6 }; | |||
| unsigned int numVertices; | |||
| HeapBlock<GLfloat> triangles; | |||
| }; | |||
| TriangulatedPath::TriangulatedPath (const Path& path) | |||
| { | |||
| startNewBlock(); | |||
| TrapezoidedPath (path).iterate (*this); | |||
| } | |||
| void TriangulatedPath::draw (const int oversamplingLevel) const | |||
| { | |||
| glColor4f (1.0f, 1.0f, 1.0f, 1.0f / (oversamplingLevel * oversamplingLevel)); | |||
| glTranslatef (-0.5f, -0.5f, 0.0f); | |||
| const float inc = 1.0f / oversamplingLevel; | |||
| for (int y = oversamplingLevel; --y >= 0;) | |||
| { | |||
| for (int x = oversamplingLevel; --x >= 0;) | |||
| { | |||
| glTranslatef (inc, 0.0f, 0.0f); | |||
| for (int i = 0; i < blocks.size(); ++i) | |||
| blocks.getUnchecked(i)->draw(); | |||
| } | |||
| glTranslatef (-1.0f, inc, 0.0f); | |||
| } | |||
| } | |||
| void TriangulatedPath::optimiseStorage() | |||
| { | |||
| currentBlock->optimiseStorage(); | |||
| } | |||
| void TriangulatedPath::startNewBlock() | |||
| { | |||
| currentBlock = new TriangleBlock(); | |||
| blocks.add (currentBlock); | |||
| } | |||
| void TriangulatedPath::addTriangle (GLfloat x1, GLfloat y1, GLfloat x2, GLfloat y2, GLfloat x3, GLfloat y3) | |||
| { | |||
| if (currentBlock->numVertices >= TriangleBlock::maxVerticesPerBlock) | |||
| startNewBlock(); | |||
| GLfloat* t = currentBlock->getNextTriangle(); | |||
| *t++ = x1; *t++ = y1; *t++ = x2; *t++ = y2; *t++ = x3; *t++ = y3; | |||
| currentBlock->numVertices += 6; | |||
| } | |||
| void TriangulatedPath::addTrapezoid (GLfloat y1, GLfloat y2, GLfloat x1, GLfloat x2, GLfloat x3, GLfloat x4) | |||
| { | |||
| if (currentBlock->numVertices >= TriangleBlock::maxVerticesPerBlock - 6) | |||
| startNewBlock(); | |||
| GLfloat* t = currentBlock->getNextTriangle(); | |||
| *t++ = x1; *t++ = y1; *t++ = x2; *t++ = y2; *t++ = x3; *t++ = y1; | |||
| *t++ = x4; *t++ = y2; *t++ = x2; *t++ = y2; *t++ = x3; *t++ = y1; | |||
| currentBlock->numVertices += 12; | |||
| } | |||
| END_JUCE_NAMESPACE | |||
| @@ -26,6 +26,7 @@ | |||
| #ifndef __JUCE_OPENGLHELPERS_JUCEHEADER__ | |||
| #define __JUCE_OPENGLHELPERS_JUCEHEADER__ | |||
| //============================================================================== | |||
| /** | |||
| A set of miscellaneous openGL helper functions. | |||
| */ | |||
| @@ -61,11 +62,48 @@ public: | |||
| float x4, float y4, float z4, | |||
| const Colour& colour); | |||
| /** Fills a rectangle with the specified colour. */ | |||
| static void fillRectWithColour (const Rectangle<int>& rect, | |||
| const Colour& colour); | |||
| /** Fills a rectangle with the specified gradient. */ | |||
| static void fillRectWithColourGradient (const Rectangle<int>& rect, | |||
| const ColourGradient& gradient, | |||
| const AffineTransform& transform); | |||
| }; | |||
| //============================================================================== | |||
| /** Holds a set of OpenGL triangles, having generated them from a Path object. | |||
| */ | |||
| class JUCE_API TriangulatedPath | |||
| { | |||
| public: | |||
| TriangulatedPath (const Path& path); | |||
| /** Renders the path, using a jittered oversampling method. | |||
| The oversampling level is the square root of the number of times it | |||
| should be oversampled, so 3 or 4 might be reasonable. | |||
| */ | |||
| void draw (int oversamplingLevel) const; | |||
| /** Reduces the memory footprint of this object to the minimum possible. */ | |||
| void optimiseStorage(); | |||
| private: | |||
| class TrapezoidedPath; | |||
| friend class TrapezoidedPath; | |||
| void startNewBlock(); | |||
| void addTriangle (GLfloat x1, GLfloat y1, GLfloat x2, GLfloat y2, GLfloat x3, GLfloat y3); | |||
| void addTrapezoid (GLfloat y1, GLfloat y2, GLfloat x1, GLfloat x2, GLfloat x3, GLfloat x4); | |||
| struct TriangleBlock; | |||
| friend class OwnedArray<TriangleBlock>; | |||
| OwnedArray<TriangleBlock> blocks; | |||
| TriangleBlock* currentBlock; | |||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (TriangulatedPath); | |||
| }; | |||
| #endif // __JUCE_OPENGLHELPERS_JUCEHEADER__ | |||
| @@ -74,7 +74,7 @@ namespace OpenGLImageHelpers | |||
| { | |||
| static void read (OpenGLFrameBuffer& frameBuffer, Image::BitmapData& bitmapData, int x, int y) | |||
| { | |||
| frameBuffer.readPixels (bitmapData.data, Rectangle<int> (x, y, bitmapData.width, bitmapData.height)); | |||
| frameBuffer.readPixels (bitmapData.data, 0, Rectangle<int> (x, y, bitmapData.width, bitmapData.height)); | |||
| } | |||
| }; | |||
| @@ -86,7 +86,7 @@ namespace OpenGLImageHelpers | |||
| void write (const void* const data) const noexcept | |||
| { | |||
| frameBuffer.writePixels (data, area); | |||
| frameBuffer.writePixels (data, 0, 4, area); | |||
| } | |||
| OpenGLFrameBuffer& frameBuffer; | |||
| @@ -61,8 +61,6 @@ void OpenGLTexture::create (const int w, const int h) | |||
| glTexParameterf (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); | |||
| glTexParameterf (GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); | |||
| glTexParameterf (GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); | |||
| glPixelStorei (GL_UNPACK_ALIGNMENT, 4); | |||
| } | |||
| void OpenGLTexture::load (const Image& image) | |||
| @@ -71,8 +69,11 @@ void OpenGLTexture::load (const Image& image) | |||
| Image::BitmapData srcData (image, Image::BitmapData::readOnly); | |||
| glPixelStorei (GL_UNPACK_ALIGNMENT, srcData.pixelFormat); | |||
| glPixelStorei (GL_UNPACK_ROW_LENGTH, srcData.lineStride); | |||
| glTexImage2D (GL_TEXTURE_2D, 0, internalGLTextureFormat, width, height, 0, | |||
| image.getFormat() == Image::RGB ? GL_RGB : GL_BGRA_EXT, | |||
| srcData.pixelFormat == Image::RGB ? GL_RGB : GL_BGRA_EXT, | |||
| GL_UNSIGNED_BYTE, srcData.data); | |||
| } | |||
| @@ -80,6 +81,7 @@ void OpenGLTexture::load (const PixelARGB* const pixels, const int w, const int | |||
| { | |||
| create (w, h); | |||
| glPixelStorei (GL_UNPACK_ALIGNMENT, 4); | |||
| glTexImage2D (GL_TEXTURE_2D, 0, internalGLTextureFormat, w, h, 0, | |||
| GL_BGRA_EXT, GL_UNSIGNED_BYTE, pixels); | |||
| } | |||