@@ -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); | |||
} | |||