1
0
Fork 0
mirror of https://github.com/juce-framework/JUCE.git synced 2026-01-13 00:04:19 +00:00

Added Animated App template and examples

This commit is contained in:
Felix Faire 2014-10-29 15:55:23 +00:00
parent fefcf7aca6
commit ff6520a89a
1141 changed files with 438491 additions and 94 deletions

View file

@ -0,0 +1,262 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2013 - Raw Material Software Ltd.
Permission is granted to use this software under the terms of either:
a) the GPL v2 (or any later version)
b) the Affero GPL v3
Details of these licenses can be found at: www.gnu.org/licenses
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
------------------------------------------------------------------------------
To release a closed-source product which uses JUCE, commercial licenses are
available: visit www.juce.com for more information.
==============================================================================
*/
AffineTransform::AffineTransform() noexcept
: mat00 (1.0f), mat01 (0), mat02 (0),
mat10 (0), mat11 (1.0f), mat12 (0)
{
}
AffineTransform::AffineTransform (const AffineTransform& other) noexcept
: mat00 (other.mat00), mat01 (other.mat01), mat02 (other.mat02),
mat10 (other.mat10), mat11 (other.mat11), mat12 (other.mat12)
{
}
AffineTransform::AffineTransform (const float m00, const float m01, const float m02,
const float m10, const float m11, const float m12) noexcept
: mat00 (m00), mat01 (m01), mat02 (m02),
mat10 (m10), mat11 (m11), mat12 (m12)
{
}
AffineTransform& AffineTransform::operator= (const AffineTransform& other) noexcept
{
mat00 = other.mat00;
mat01 = other.mat01;
mat02 = other.mat02;
mat10 = other.mat10;
mat11 = other.mat11;
mat12 = other.mat12;
return *this;
}
bool AffineTransform::operator== (const AffineTransform& other) const noexcept
{
return mat00 == other.mat00
&& mat01 == other.mat01
&& mat02 == other.mat02
&& mat10 == other.mat10
&& mat11 == other.mat11
&& mat12 == other.mat12;
}
bool AffineTransform::operator!= (const AffineTransform& other) const noexcept
{
return ! operator== (other);
}
//==============================================================================
bool AffineTransform::isIdentity() const noexcept
{
return (mat01 == 0)
&& (mat02 == 0)
&& (mat10 == 0)
&& (mat12 == 0)
&& (mat00 == 1.0f)
&& (mat11 == 1.0f);
}
const AffineTransform AffineTransform::identity;
//==============================================================================
AffineTransform AffineTransform::followedBy (const AffineTransform& other) const noexcept
{
return AffineTransform (other.mat00 * mat00 + other.mat01 * mat10,
other.mat00 * mat01 + other.mat01 * mat11,
other.mat00 * mat02 + other.mat01 * mat12 + other.mat02,
other.mat10 * mat00 + other.mat11 * mat10,
other.mat10 * mat01 + other.mat11 * mat11,
other.mat10 * mat02 + other.mat11 * mat12 + other.mat12);
}
AffineTransform AffineTransform::translated (const float dx, const float dy) const noexcept
{
return AffineTransform (mat00, mat01, mat02 + dx,
mat10, mat11, mat12 + dy);
}
AffineTransform AffineTransform::translation (const float dx, const float dy) noexcept
{
return AffineTransform (1.0f, 0, dx,
0, 1.0f, dy);
}
AffineTransform AffineTransform::withAbsoluteTranslation (const float tx, const float ty) const noexcept
{
return AffineTransform (mat00, mat01, tx,
mat10, mat11, ty);
}
AffineTransform AffineTransform::rotated (const float rad) const noexcept
{
const float cosRad = std::cos (rad);
const float sinRad = std::sin (rad);
return AffineTransform (cosRad * mat00 + -sinRad * mat10,
cosRad * mat01 + -sinRad * mat11,
cosRad * mat02 + -sinRad * mat12,
sinRad * mat00 + cosRad * mat10,
sinRad * mat01 + cosRad * mat11,
sinRad * mat02 + cosRad * mat12);
}
AffineTransform AffineTransform::rotation (const float rad) noexcept
{
const float cosRad = std::cos (rad);
const float sinRad = std::sin (rad);
return AffineTransform (cosRad, -sinRad, 0,
sinRad, cosRad, 0);
}
AffineTransform AffineTransform::rotation (const float rad, const float pivotX, const float pivotY) noexcept
{
const float cosRad = std::cos (rad);
const float sinRad = std::sin (rad);
return AffineTransform (cosRad, -sinRad, -cosRad * pivotX + sinRad * pivotY + pivotX,
sinRad, cosRad, -sinRad * pivotX + -cosRad * pivotY + pivotY);
}
AffineTransform AffineTransform::rotated (const float angle, const float pivotX, const float pivotY) const noexcept
{
return followedBy (rotation (angle, pivotX, pivotY));
}
AffineTransform AffineTransform::scaled (const float factorX, const float factorY) const noexcept
{
return AffineTransform (factorX * mat00, factorX * mat01, factorX * mat02,
factorY * mat10, factorY * mat11, factorY * mat12);
}
AffineTransform AffineTransform::scaled (const float factor) const noexcept
{
return AffineTransform (factor * mat00, factor * mat01, factor * mat02,
factor * mat10, factor * mat11, factor * mat12);
}
AffineTransform AffineTransform::scale (const float factorX, const float factorY) noexcept
{
return AffineTransform (factorX, 0, 0, 0, factorY, 0);
}
AffineTransform AffineTransform::scale (const float factor) noexcept
{
return AffineTransform (factor, 0, 0, 0, factor, 0);
}
AffineTransform AffineTransform::scaled (const float factorX, const float factorY,
const float pivotX, const float pivotY) const noexcept
{
return AffineTransform (factorX * mat00, factorX * mat01, factorX * mat02 + pivotX * (1.0f - factorX),
factorY * mat10, factorY * mat11, factorY * mat12 + pivotY * (1.0f - factorY));
}
AffineTransform AffineTransform::scale (const float factorX, const float factorY,
const float pivotX, const float pivotY) noexcept
{
return AffineTransform (factorX, 0, pivotX * (1.0f - factorX),
0, factorY, pivotY * (1.0f - factorY));
}
AffineTransform AffineTransform::shear (float shearX, float shearY) noexcept
{
return AffineTransform (1.0f, shearX, 0,
shearY, 1.0f, 0);
}
AffineTransform AffineTransform::sheared (const float shearX, const float shearY) const noexcept
{
return AffineTransform (mat00 + shearX * mat10,
mat01 + shearX * mat11,
mat02 + shearX * mat12,
mat10 + shearY * mat00,
mat11 + shearY * mat01,
mat12 + shearY * mat02);
}
AffineTransform AffineTransform::verticalFlip (const float height) noexcept
{
return AffineTransform (1.0f, 0, 0, 0, -1.0f, height);
}
AffineTransform AffineTransform::inverted() const noexcept
{
double determinant = (mat00 * mat11 - mat10 * mat01);
if (determinant != 0.0)
{
determinant = 1.0 / determinant;
const float dst00 = (float) ( mat11 * determinant);
const float dst10 = (float) (-mat10 * determinant);
const float dst01 = (float) (-mat01 * determinant);
const float dst11 = (float) ( mat00 * determinant);
return AffineTransform (dst00, dst01, -mat02 * dst00 - mat12 * dst01,
dst10, dst11, -mat02 * dst10 - mat12 * dst11);
}
else
{
// singularity..
return *this;
}
}
bool AffineTransform::isSingularity() const noexcept
{
return (mat00 * mat11 - mat10 * mat01) == 0;
}
AffineTransform AffineTransform::fromTargetPoints (const float x00, const float y00,
const float x10, const float y10,
const float x01, const float y01) noexcept
{
return AffineTransform (x10 - x00, x01 - x00, x00,
y10 - y00, y01 - y00, y00);
}
AffineTransform AffineTransform::fromTargetPoints (const float sx1, const float sy1, const float tx1, const float ty1,
const float sx2, const float sy2, const float tx2, const float ty2,
const float sx3, const float sy3, const float tx3, const float ty3) noexcept
{
return fromTargetPoints (sx1, sy1, sx2, sy2, sx3, sy3)
.inverted()
.followedBy (fromTargetPoints (tx1, ty1, tx2, ty2, tx3, ty3));
}
bool AffineTransform::isOnlyTranslation() const noexcept
{
return (mat01 == 0)
&& (mat10 == 0)
&& (mat00 == 1.0f)
&& (mat11 == 1.0f);
}
float AffineTransform::getScaleFactor() const noexcept
{
return (std::abs (mat00) + std::abs (mat11)) / 2.0f;
}

View file

@ -0,0 +1,285 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2013 - Raw Material Software Ltd.
Permission is granted to use this software under the terms of either:
a) the GPL v2 (or any later version)
b) the Affero GPL v3
Details of these licenses can be found at: www.gnu.org/licenses
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
------------------------------------------------------------------------------
To release a closed-source product which uses JUCE, commercial licenses are
available: visit www.juce.com for more information.
==============================================================================
*/
#ifndef JUCE_AFFINETRANSFORM_H_INCLUDED
#define JUCE_AFFINETRANSFORM_H_INCLUDED
//==============================================================================
/**
Represents a 2D affine-transformation matrix.
An affine transformation is a transformation such as a rotation, scale, shear,
resize or translation.
These are used for various 2D transformation tasks, e.g. with Path objects.
@see Path, Point, Line
*/
class JUCE_API AffineTransform
{
public:
//==============================================================================
/** Creates an identity transform. */
AffineTransform() noexcept;
/** Creates a copy of another transform. */
AffineTransform (const AffineTransform& other) noexcept;
/** Creates a transform from a set of raw matrix values.
The resulting matrix is:
(mat00 mat01 mat02)
(mat10 mat11 mat12)
( 0 0 1 )
*/
AffineTransform (float mat00, float mat01, float mat02,
float mat10, float mat11, float mat12) noexcept;
/** Copies from another AffineTransform object */
AffineTransform& operator= (const AffineTransform& other) noexcept;
/** Compares two transforms. */
bool operator== (const AffineTransform& other) const noexcept;
/** Compares two transforms. */
bool operator!= (const AffineTransform& other) const noexcept;
/** A ready-to-use identity transform, which you can use to append other
transformations to.
e.g. @code
AffineTransform myTransform = AffineTransform::identity.rotated (.5f)
.scaled (2.0f);
@endcode
*/
static const AffineTransform identity;
//==============================================================================
/** Transforms a 2D coordinate using this matrix. */
template <typename ValueType>
void transformPoint (ValueType& x, ValueType& y) const noexcept
{
const ValueType oldX = x;
x = static_cast<ValueType> (mat00 * oldX + mat01 * y + mat02);
y = static_cast<ValueType> (mat10 * oldX + mat11 * y + mat12);
}
/** Transforms two 2D coordinates using this matrix.
This is just a shortcut for calling transformPoint() on each of these pairs of
coordinates in turn. (And putting all the calculations into one function hopefully
also gives the compiler a bit more scope for pipelining it).
*/
template <typename ValueType>
void transformPoints (ValueType& x1, ValueType& y1,
ValueType& x2, ValueType& y2) const noexcept
{
const ValueType oldX1 = x1, oldX2 = x2;
x1 = static_cast<ValueType> (mat00 * oldX1 + mat01 * y1 + mat02);
y1 = static_cast<ValueType> (mat10 * oldX1 + mat11 * y1 + mat12);
x2 = static_cast<ValueType> (mat00 * oldX2 + mat01 * y2 + mat02);
y2 = static_cast<ValueType> (mat10 * oldX2 + mat11 * y2 + mat12);
}
/** Transforms three 2D coordinates using this matrix.
This is just a shortcut for calling transformPoint() on each of these pairs of
coordinates in turn. (And putting all the calculations into one function hopefully
also gives the compiler a bit more scope for pipelining it).
*/
template <typename ValueType>
void transformPoints (ValueType& x1, ValueType& y1,
ValueType& x2, ValueType& y2,
ValueType& x3, ValueType& y3) const noexcept
{
const ValueType oldX1 = x1, oldX2 = x2, oldX3 = x3;
x1 = static_cast<ValueType> (mat00 * oldX1 + mat01 * y1 + mat02);
y1 = static_cast<ValueType> (mat10 * oldX1 + mat11 * y1 + mat12);
x2 = static_cast<ValueType> (mat00 * oldX2 + mat01 * y2 + mat02);
y2 = static_cast<ValueType> (mat10 * oldX2 + mat11 * y2 + mat12);
x3 = static_cast<ValueType> (mat00 * oldX3 + mat01 * y3 + mat02);
y3 = static_cast<ValueType> (mat10 * oldX3 + mat11 * y3 + mat12);
}
//==============================================================================
/** Returns a new transform which is the same as this one followed by a translation. */
AffineTransform translated (float deltaX,
float deltaY) const noexcept;
/** Returns a new transform which is the same as this one followed by a translation. */
template <typename PointType>
AffineTransform translated (PointType delta) const noexcept
{
return translated ((float) delta.x, (float) delta.y);
}
/** Returns a new transform which is a translation. */
static AffineTransform translation (float deltaX,
float deltaY) noexcept;
/** Returns a new transform which is a translation. */
template <typename PointType>
static AffineTransform translation (PointType delta) noexcept
{
return translation ((float) delta.x, (float) delta.y);
}
/** Returns a copy of this transform with the specified translation matrix values. */
AffineTransform withAbsoluteTranslation (float translationX,
float translationY) const noexcept;
/** Returns a transform which is the same as this one followed by a rotation.
The rotation is specified by a number of radians to rotate clockwise, centred around
the origin (0, 0).
*/
AffineTransform rotated (float angleInRadians) const noexcept;
/** Returns a transform which is the same as this one followed by a rotation about a given point.
The rotation is specified by a number of radians to rotate clockwise, centred around
the coordinates passed in.
*/
AffineTransform rotated (float angleInRadians,
float pivotX,
float pivotY) const noexcept;
/** Returns a new transform which is a rotation about (0, 0). */
static AffineTransform rotation (float angleInRadians) noexcept;
/** Returns a new transform which is a rotation about a given point. */
static AffineTransform rotation (float angleInRadians,
float pivotX,
float pivotY) noexcept;
/** Returns a transform which is the same as this one followed by a re-scaling.
The scaling is centred around the origin (0, 0).
*/
AffineTransform scaled (float factorX,
float factorY) const noexcept;
/** Returns a transform which is the same as this one followed by a re-scaling.
The scaling is centred around the origin (0, 0).
*/
AffineTransform scaled (float factor) const noexcept;
/** Returns a transform which is the same as this one followed by a re-scaling.
The scaling is centred around the origin provided.
*/
AffineTransform scaled (float factorX, float factorY,
float pivotX, float pivotY) const noexcept;
/** Returns a new transform which is a re-scale about the origin. */
static AffineTransform scale (float factorX,
float factorY) noexcept;
/** Returns a new transform which is a re-scale about the origin. */
static AffineTransform scale (float factor) noexcept;
/** Returns a new transform which is a re-scale centred around the point provided. */
static AffineTransform scale (float factorX, float factorY,
float pivotX, float pivotY) noexcept;
/** Returns a transform which is the same as this one followed by a shear.
The shear is centred around the origin (0, 0).
*/
AffineTransform sheared (float shearX, float shearY) const noexcept;
/** Returns a shear transform, centred around the origin (0, 0). */
static AffineTransform shear (float shearX, float shearY) noexcept;
/** Returns a transform that will flip coordinates vertically within a window of the given height.
This is handy for converting between upside-down coordinate systems such as OpenGL or CoreGraphics.
*/
static AffineTransform verticalFlip (float height) noexcept;
/** Returns a matrix which is the inverse operation of this one.
Some matrices don't have an inverse - in this case, the method will just return
an identity transform.
*/
AffineTransform inverted() const noexcept;
/** Returns the transform that will map three known points onto three coordinates
that are supplied.
This returns the transform that will transform (0, 0) into (x00, y00),
(1, 0) to (x10, y10), and (0, 1) to (x01, y01).
*/
static AffineTransform fromTargetPoints (float x00, float y00,
float x10, float y10,
float x01, float y01) noexcept;
/** Returns the transform that will map three specified points onto three target points. */
static AffineTransform fromTargetPoints (float sourceX1, float sourceY1, float targetX1, float targetY1,
float sourceX2, float sourceY2, float targetX2, float targetY2,
float sourceX3, float sourceY3, float targetX3, float targetY3) noexcept;
//==============================================================================
/** Returns the result of concatenating another transformation after this one. */
AffineTransform followedBy (const AffineTransform& other) const noexcept;
/** Returns true if this transform has no effect on points. */
bool isIdentity() const noexcept;
/** Returns true if this transform maps to a singularity - i.e. if it has no inverse. */
bool isSingularity() const noexcept;
/** Returns true if the transform only translates, and doesn't scale or rotate the
points. */
bool isOnlyTranslation() const noexcept;
/** If this transform is only a translation, this returns the X offset.
@see isOnlyTranslation
*/
float getTranslationX() const noexcept { return mat02; }
/** If this transform is only a translation, this returns the X offset.
@see isOnlyTranslation
*/
float getTranslationY() const noexcept { return mat12; }
/** Returns the approximate scale factor by which lengths will be transformed.
Obviously a length may be scaled by entirely different amounts depending on its
direction, so this is only appropriate as a rough guide.
*/
float getScaleFactor() const noexcept;
//==============================================================================
/* The transform matrix is:
(mat00 mat01 mat02)
(mat10 mat11 mat12)
( 0 0 1 )
*/
float mat00, mat01, mat02;
float mat10, mat11, mat12;
private:
//==============================================================================
JUCE_LEAK_DETECTOR (AffineTransform)
};
#endif // JUCE_AFFINETRANSFORM_H_INCLUDED

View file

@ -0,0 +1,153 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2013 - Raw Material Software Ltd.
Permission is granted to use this software under the terms of either:
a) the GPL v2 (or any later version)
b) the Affero GPL v3
Details of these licenses can be found at: www.gnu.org/licenses
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
------------------------------------------------------------------------------
To release a closed-source product which uses JUCE, commercial licenses are
available: visit www.juce.com for more information.
==============================================================================
*/
#ifndef JUCE_BORDERSIZE_H_INCLUDED
#define JUCE_BORDERSIZE_H_INCLUDED
//==============================================================================
/**
Specifies a set of gaps to be left around the sides of a rectangle.
This is basically the size of the spaces at the top, bottom, left and right of
a rectangle. It's used by various component classes to specify borders.
@see Rectangle
*/
template <typename ValueType>
class BorderSize
{
public:
//==============================================================================
/** Creates a null border.
All sizes are left as 0.
*/
BorderSize() noexcept
: top(), left(), bottom(), right()
{
}
/** Creates a copy of another border. */
BorderSize (const BorderSize& other) noexcept
: top (other.top), left (other.left), bottom (other.bottom), right (other.right)
{
}
/** Creates a border with the given gaps. */
BorderSize (ValueType topGap, ValueType leftGap, ValueType bottomGap, ValueType rightGap) noexcept
: top (topGap), left (leftGap), bottom (bottomGap), right (rightGap)
{
}
/** Creates a border with the given gap on all sides. */
explicit BorderSize (ValueType allGaps) noexcept
: top (allGaps), left (allGaps), bottom (allGaps), right (allGaps)
{
}
//==============================================================================
/** Returns the gap that should be left at the top of the region. */
ValueType getTop() const noexcept { return top; }
/** Returns the gap that should be left at the top of the region. */
ValueType getLeft() const noexcept { return left; }
/** Returns the gap that should be left at the top of the region. */
ValueType getBottom() const noexcept { return bottom; }
/** Returns the gap that should be left at the top of the region. */
ValueType getRight() const noexcept { return right; }
/** Returns the sum of the top and bottom gaps. */
ValueType getTopAndBottom() const noexcept { return top + bottom; }
/** Returns the sum of the left and right gaps. */
ValueType getLeftAndRight() const noexcept { return left + right; }
/** Returns true if this border has no thickness along any edge. */
bool isEmpty() const noexcept { return left + right + top + bottom == ValueType(); }
//==============================================================================
/** Changes the top gap. */
void setTop (ValueType newTopGap) noexcept { top = newTopGap; }
/** Changes the left gap. */
void setLeft (ValueType newLeftGap) noexcept { left = newLeftGap; }
/** Changes the bottom gap. */
void setBottom (ValueType newBottomGap) noexcept { bottom = newBottomGap; }
/** Changes the right gap. */
void setRight (ValueType newRightGap) noexcept { right = newRightGap; }
//==============================================================================
/** Returns a rectangle with these borders removed from it. */
Rectangle<ValueType> subtractedFrom (const Rectangle<ValueType>& original) const noexcept
{
return Rectangle<ValueType> (original.getX() + left,
original.getY() + top,
original.getWidth() - (left + right),
original.getHeight() - (top + bottom));
}
/** Removes this border from a given rectangle. */
void subtractFrom (Rectangle<ValueType>& rectangle) const noexcept
{
rectangle = subtractedFrom (rectangle);
}
/** Returns a rectangle with these borders added around it. */
Rectangle<ValueType> addedTo (const Rectangle<ValueType>& original) const noexcept
{
return Rectangle<ValueType> (original.getX() - left,
original.getY() - top,
original.getWidth() + (left + right),
original.getHeight() + (top + bottom));
}
/** Adds this border around a given rectangle. */
void addTo (Rectangle<ValueType>& rectangle) const noexcept
{
rectangle = addedTo (rectangle);
}
//==============================================================================
bool operator== (const BorderSize& other) const noexcept
{
return top == other.top && left == other.left && bottom == other.bottom && right == other.right;
}
bool operator!= (const BorderSize& other) const noexcept
{
return ! operator== (other);
}
private:
//==============================================================================
ValueType top, left, bottom, right;
};
#endif // JUCE_BORDERSIZE_H_INCLUDED

View file

@ -0,0 +1,831 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2013 - Raw Material Software Ltd.
Permission is granted to use this software under the terms of either:
a) the GPL v2 (or any later version)
b) the Affero GPL v3
Details of these licenses can be found at: www.gnu.org/licenses
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
------------------------------------------------------------------------------
To release a closed-source product which uses JUCE, commercial licenses are
available: visit www.juce.com for more information.
==============================================================================
*/
const int juce_edgeTableDefaultEdgesPerLine = 32;
//==============================================================================
EdgeTable::EdgeTable (const Rectangle<int>& area,
const Path& path, const AffineTransform& transform)
: bounds (area),
maxEdgesPerLine (juce_edgeTableDefaultEdgesPerLine),
lineStrideElements (juce_edgeTableDefaultEdgesPerLine * 2 + 1),
needToCheckEmptiness (true)
{
allocate();
int* t = table;
for (int i = bounds.getHeight(); --i >= 0;)
{
*t = 0;
t += lineStrideElements;
}
const int leftLimit = bounds.getX() << 8;
const int topLimit = bounds.getY() << 8;
const int rightLimit = bounds.getRight() << 8;
const int heightLimit = bounds.getHeight() << 8;
PathFlatteningIterator iter (path, transform);
while (iter.next())
{
int y1 = roundToInt (iter.y1 * 256.0f);
int y2 = roundToInt (iter.y2 * 256.0f);
if (y1 != y2)
{
y1 -= topLimit;
y2 -= topLimit;
const int startY = y1;
int direction = -1;
if (y1 > y2)
{
std::swap (y1, y2);
direction = 1;
}
if (y1 < 0)
y1 = 0;
if (y2 > heightLimit)
y2 = heightLimit;
if (y1 < y2)
{
const double startX = 256.0f * iter.x1;
const double multiplier = (iter.x2 - iter.x1) / (iter.y2 - iter.y1);
const int stepSize = jlimit (1, 256, 256 / (1 + (int) std::abs (multiplier)));
do
{
const int step = jmin (stepSize, y2 - y1, 256 - (y1 & 255));
int x = roundToInt (startX + multiplier * ((y1 + (step >> 1)) - startY));
if (x < leftLimit)
x = leftLimit;
else if (x >= rightLimit)
x = rightLimit - 1;
addEdgePoint (x, y1 >> 8, direction * step);
y1 += step;
}
while (y1 < y2);
}
}
}
sanitiseLevels (path.isUsingNonZeroWinding());
}
EdgeTable::EdgeTable (const Rectangle<int>& rectangleToAdd)
: bounds (rectangleToAdd),
maxEdgesPerLine (juce_edgeTableDefaultEdgesPerLine),
lineStrideElements (juce_edgeTableDefaultEdgesPerLine * 2 + 1),
needToCheckEmptiness (true)
{
allocate();
table[0] = 0;
const int x1 = rectangleToAdd.getX() << 8;
const int x2 = rectangleToAdd.getRight() << 8;
int* t = table;
for (int i = rectangleToAdd.getHeight(); --i >= 0;)
{
t[0] = 2;
t[1] = x1;
t[2] = 255;
t[3] = x2;
t[4] = 0;
t += lineStrideElements;
}
}
EdgeTable::EdgeTable (const RectangleList<int>& rectanglesToAdd)
: bounds (rectanglesToAdd.getBounds()),
maxEdgesPerLine (juce_edgeTableDefaultEdgesPerLine),
lineStrideElements (juce_edgeTableDefaultEdgesPerLine * 2 + 1),
needToCheckEmptiness (true)
{
allocate();
clearLineSizes();
for (const Rectangle<int>* r = rectanglesToAdd.begin(), * const e = rectanglesToAdd.end(); r != e; ++r)
{
const int x1 = r->getX() << 8;
const int x2 = r->getRight() << 8;
int y = r->getY() - bounds.getY();
for (int j = r->getHeight(); --j >= 0;)
addEdgePointPair (x1, x2, y++, 255);
}
sanitiseLevels (true);
}
EdgeTable::EdgeTable (const RectangleList<float>& rectanglesToAdd)
: bounds (rectanglesToAdd.getBounds().getSmallestIntegerContainer()),
maxEdgesPerLine (rectanglesToAdd.getNumRectangles() * 2),
lineStrideElements (rectanglesToAdd.getNumRectangles() * 4 + 1),
needToCheckEmptiness (true)
{
bounds.setHeight (bounds.getHeight() + 1);
allocate();
clearLineSizes();
for (const Rectangle<float>* r = rectanglesToAdd.begin(), * const e = rectanglesToAdd.end(); r != e; ++r)
{
const int x1 = roundToInt (r->getX() * 256.0f);
const int x2 = roundToInt (r->getRight() * 256.0f);
const int y1 = roundToInt (r->getY() * 256.0f) - (bounds.getY() << 8);
const int y2 = roundToInt (r->getBottom() * 256.0f) - (bounds.getY() << 8);
if (x2 <= x1 || y2 <= y1)
continue;
int y = y1 >> 8;
const int lastLine = y2 >> 8;
if (y == lastLine)
{
addEdgePointPair (x1, x2, y, y2 - y1);
}
else
{
addEdgePointPair (x1, x2, y++, 255 - (y1 & 255));
while (y < lastLine)
addEdgePointPair (x1, x2, y++, 255);
jassert (y < bounds.getHeight());
addEdgePointPair (x1, x2, y, y2 & 255);
}
}
sanitiseLevels (true);
}
EdgeTable::EdgeTable (const Rectangle<float>& rectangleToAdd)
: bounds (Rectangle<int> ((int) std::floor (rectangleToAdd.getX()),
roundToInt (rectangleToAdd.getY() * 256.0f) >> 8,
2 + (int) rectangleToAdd.getWidth(),
2 + (int) rectangleToAdd.getHeight())),
maxEdgesPerLine (juce_edgeTableDefaultEdgesPerLine),
lineStrideElements ((juce_edgeTableDefaultEdgesPerLine << 1) + 1),
needToCheckEmptiness (true)
{
jassert (! rectangleToAdd.isEmpty());
allocate();
table[0] = 0;
const int x1 = roundToInt (rectangleToAdd.getX() * 256.0f);
const int x2 = roundToInt (rectangleToAdd.getRight() * 256.0f);
int y1 = roundToInt (rectangleToAdd.getY() * 256.0f) - (bounds.getY() << 8);
jassert (y1 < 256);
int y2 = roundToInt (rectangleToAdd.getBottom() * 256.0f) - (bounds.getY() << 8);
if (x2 <= x1 || y2 <= y1)
{
bounds.setHeight (0);
return;
}
int lineY = 0;
int* t = table;
if ((y1 >> 8) == (y2 >> 8))
{
t[0] = 2;
t[1] = x1;
t[2] = y2 - y1;
t[3] = x2;
t[4] = 0;
++lineY;
t += lineStrideElements;
}
else
{
t[0] = 2;
t[1] = x1;
t[2] = 255 - (y1 & 255);
t[3] = x2;
t[4] = 0;
++lineY;
t += lineStrideElements;
while (lineY < (y2 >> 8))
{
t[0] = 2;
t[1] = x1;
t[2] = 255;
t[3] = x2;
t[4] = 0;
++lineY;
t += lineStrideElements;
}
jassert (lineY < bounds.getHeight());
t[0] = 2;
t[1] = x1;
t[2] = y2 & 255;
t[3] = x2;
t[4] = 0;
++lineY;
t += lineStrideElements;
}
while (lineY < bounds.getHeight())
{
t[0] = 0;
t += lineStrideElements;
++lineY;
}
}
EdgeTable::EdgeTable (const EdgeTable& other)
{
operator= (other);
}
EdgeTable& EdgeTable::operator= (const EdgeTable& other)
{
bounds = other.bounds;
maxEdgesPerLine = other.maxEdgesPerLine;
lineStrideElements = other.lineStrideElements;
needToCheckEmptiness = other.needToCheckEmptiness;
allocate();
copyEdgeTableData (table, lineStrideElements, other.table, lineStrideElements, bounds.getHeight());
return *this;
}
EdgeTable::~EdgeTable()
{
}
//==============================================================================
static size_t getEdgeTableAllocationSize (int lineStride, int height) noexcept
{
// (leave an extra line at the end for use as scratch space)
return (size_t) (lineStride * (2 + jmax (0, height)));
}
void EdgeTable::allocate()
{
table.malloc (getEdgeTableAllocationSize (lineStrideElements, bounds.getHeight()));
}
void EdgeTable::clearLineSizes() noexcept
{
int* t = table;
for (int i = bounds.getHeight(); --i >= 0;)
{
*t = 0;
t += lineStrideElements;
}
}
void EdgeTable::copyEdgeTableData (int* dest, const int destLineStride, const int* src, const int srcLineStride, int numLines) noexcept
{
while (--numLines >= 0)
{
memcpy (dest, src, (size_t) (src[0] * 2 + 1) * sizeof (int));
src += srcLineStride;
dest += destLineStride;
}
}
void EdgeTable::sanitiseLevels (const bool useNonZeroWinding) noexcept
{
// Convert the table from relative windings to absolute levels..
int* lineStart = table;
for (int y = bounds.getHeight(); --y >= 0;)
{
int num = lineStart[0];
if (num > 0)
{
LineItem* items = reinterpret_cast<LineItem*> (lineStart + 1);
LineItem* const itemsEnd = items + num;
// sort the X coords
std::sort (items, itemsEnd);
const LineItem* src = items;
int correctedNum = num;
int level = 0;
while (src < itemsEnd)
{
level += src->level;
const int x = src->x;
++src;
while (src < itemsEnd && src->x == x)
{
level += src->level;
++src;
--correctedNum;
}
int corrected = std::abs (level);
if (corrected >> 8)
{
if (useNonZeroWinding)
{
corrected = 255;
}
else
{
corrected &= 511;
if (corrected >> 8)
corrected = 511 - corrected;
}
}
items->x = x;
items->level = corrected;
++items;
}
lineStart[0] = correctedNum;
(items - 1)->level = 0; // force the last level to 0, just in case something went wrong in creating the table
}
lineStart += lineStrideElements;
}
}
void EdgeTable::remapTableForNumEdges (const int newNumEdgesPerLine)
{
if (newNumEdgesPerLine != maxEdgesPerLine)
{
maxEdgesPerLine = newNumEdgesPerLine;
jassert (bounds.getHeight() > 0);
const int newLineStrideElements = maxEdgesPerLine * 2 + 1;
HeapBlock<int> newTable (getEdgeTableAllocationSize (newLineStrideElements, bounds.getHeight()));
copyEdgeTableData (newTable, newLineStrideElements, table, lineStrideElements, bounds.getHeight());
table.swapWith (newTable);
lineStrideElements = newLineStrideElements;
}
}
void EdgeTable::optimiseTable()
{
int maxLineElements = 0;
for (int i = bounds.getHeight(); --i >= 0;)
maxLineElements = jmax (maxLineElements, table [i * lineStrideElements]);
remapTableForNumEdges (maxLineElements);
}
void EdgeTable::addEdgePoint (const int x, const int y, const int winding)
{
jassert (y >= 0 && y < bounds.getHeight());
int* line = table + lineStrideElements * y;
const int numPoints = line[0];
if (numPoints >= maxEdgesPerLine)
{
remapTableForNumEdges (maxEdgesPerLine + juce_edgeTableDefaultEdgesPerLine);
jassert (numPoints < maxEdgesPerLine);
line = table + lineStrideElements * y;
}
line[0]++;
int n = numPoints << 1;
line [n + 1] = x;
line [n + 2] = winding;
}
void EdgeTable::addEdgePointPair (int x1, int x2, int y, int winding)
{
jassert (y >= 0 && y < bounds.getHeight());
int* line = table + lineStrideElements * y;
const int numPoints = line[0];
if (numPoints + 1 >= maxEdgesPerLine)
{
remapTableForNumEdges (maxEdgesPerLine + juce_edgeTableDefaultEdgesPerLine);
jassert (numPoints < maxEdgesPerLine);
line = table + lineStrideElements * y;
}
line[0] = numPoints + 2;
line += numPoints << 1;
line[1] = x1;
line[2] = winding;
line[3] = x2;
line[4] = -winding;
}
void EdgeTable::translate (float dx, const int dy) noexcept
{
bounds.translate ((int) std::floor (dx), dy);
int* lineStart = table;
const int intDx = (int) (dx * 256.0f);
for (int i = bounds.getHeight(); --i >= 0;)
{
int* line = lineStart;
lineStart += lineStrideElements;
int num = *line++;
while (--num >= 0)
{
*line += intDx;
line += 2;
}
}
}
void EdgeTable::multiplyLevels (float amount)
{
int* lineStart = table;
const int multiplier = (int) (amount * 256.0f);
for (int y = 0; y < bounds.getHeight(); ++y)
{
int numPoints = lineStart[0];
LineItem* item = reinterpret_cast<LineItem*> (lineStart + 1);
lineStart += lineStrideElements;
while (--numPoints > 0)
{
item->level = jmin (255, (item->level * multiplier) >> 8);
++item;
}
}
}
void EdgeTable::intersectWithEdgeTableLine (const int y, const int* const otherLine)
{
jassert (y >= 0 && y < bounds.getHeight());
int* srcLine = table + lineStrideElements * y;
int srcNum1 = *srcLine;
if (srcNum1 == 0)
return;
int srcNum2 = *otherLine;
if (srcNum2 == 0)
{
*srcLine = 0;
return;
}
const int right = bounds.getRight() << 8;
// optimise for the common case where our line lies entirely within a
// single pair of points, as happens when clipping to a simple rect.
if (srcNum2 == 2 && otherLine[2] >= 255)
{
clipEdgeTableLineToRange (srcLine, otherLine[1], jmin (right, otherLine[3]));
return;
}
bool isUsingTempSpace = false;
const int* src1 = srcLine + 1;
int x1 = *src1++;
const int* src2 = otherLine + 1;
int x2 = *src2++;
int destIndex = 0, destTotal = 0;
int level1 = 0, level2 = 0;
int lastX = std::numeric_limits<int>::min(), lastLevel = 0;
while (srcNum1 > 0 && srcNum2 > 0)
{
int nextX;
if (x1 <= x2)
{
if (x1 == x2)
{
level2 = *src2++;
x2 = *src2++;
--srcNum2;
}
nextX = x1;
level1 = *src1++;
x1 = *src1++;
--srcNum1;
}
else
{
nextX = x2;
level2 = *src2++;
x2 = *src2++;
--srcNum2;
}
if (nextX > lastX)
{
if (nextX >= right)
break;
lastX = nextX;
const int nextLevel = (level1 * (level2 + 1)) >> 8;
jassert (isPositiveAndBelow (nextLevel, (int) 256));
if (nextLevel != lastLevel)
{
if (destTotal >= maxEdgesPerLine)
{
srcLine[0] = destTotal;
if (isUsingTempSpace)
{
const size_t tempSize = (size_t) srcNum1 * 2 * sizeof (int);
int* const oldTemp = static_cast<int*> (alloca (tempSize));
memcpy (oldTemp, src1, tempSize);
remapTableForNumEdges (jmax (256, destTotal * 2));
srcLine = table + lineStrideElements * y;
int* const newTemp = table + lineStrideElements * bounds.getHeight();
memcpy (newTemp, oldTemp, tempSize);
src1 = newTemp;
}
else
{
remapTableForNumEdges (jmax (256, destTotal * 2));
srcLine = table + lineStrideElements * y;
}
}
++destTotal;
lastLevel = nextLevel;
if (! isUsingTempSpace)
{
isUsingTempSpace = true;
int* const temp = table + lineStrideElements * bounds.getHeight();
memcpy (temp, src1, (size_t) srcNum1 * 2 * sizeof (int));
src1 = temp;
}
srcLine[++destIndex] = nextX;
srcLine[++destIndex] = nextLevel;
}
}
}
if (lastLevel > 0)
{
if (destTotal >= maxEdgesPerLine)
{
srcLine[0] = destTotal;
remapTableForNumEdges (jmax (256, destTotal * 2));
srcLine = table + lineStrideElements * y;
}
++destTotal;
srcLine[++destIndex] = right;
srcLine[++destIndex] = 0;
}
srcLine[0] = destTotal;
}
void EdgeTable::clipEdgeTableLineToRange (int* dest, const int x1, const int x2) noexcept
{
int* lastItem = dest + (dest[0] * 2 - 1);
if (x2 < lastItem[0])
{
if (x2 <= dest[1])
{
dest[0] = 0;
return;
}
while (x2 < lastItem[-2])
{
--(dest[0]);
lastItem -= 2;
}
lastItem[0] = x2;
lastItem[1] = 0;
}
if (x1 > dest[1])
{
while (lastItem[0] > x1)
lastItem -= 2;
const int itemsRemoved = (int) (lastItem - (dest + 1)) / 2;
if (itemsRemoved > 0)
{
dest[0] -= itemsRemoved;
memmove (dest + 1, lastItem, (size_t) dest[0] * (sizeof (int) * 2));
}
dest[1] = x1;
}
}
//==============================================================================
void EdgeTable::clipToRectangle (const Rectangle<int>& r)
{
const Rectangle<int> clipped (r.getIntersection (bounds));
if (clipped.isEmpty())
{
needToCheckEmptiness = false;
bounds.setHeight (0);
}
else
{
const int top = clipped.getY() - bounds.getY();
const int bottom = clipped.getBottom() - bounds.getY();
if (bottom < bounds.getHeight())
bounds.setHeight (bottom);
for (int i = top; --i >= 0;)
table [lineStrideElements * i] = 0;
if (clipped.getX() > bounds.getX() || clipped.getRight() < bounds.getRight())
{
const int x1 = clipped.getX() << 8;
const int x2 = jmin (bounds.getRight(), clipped.getRight()) << 8;
int* line = table + lineStrideElements * top;
for (int i = bottom - top; --i >= 0;)
{
if (line[0] != 0)
clipEdgeTableLineToRange (line, x1, x2);
line += lineStrideElements;
}
}
needToCheckEmptiness = true;
}
}
void EdgeTable::excludeRectangle (const Rectangle<int>& r)
{
const Rectangle<int> clipped (r.getIntersection (bounds));
if (! clipped.isEmpty())
{
const int top = clipped.getY() - bounds.getY();
const int bottom = clipped.getBottom() - bounds.getY();
const int rectLine[] = { 4, std::numeric_limits<int>::min(), 255,
clipped.getX() << 8, 0,
clipped.getRight() << 8, 255,
std::numeric_limits<int>::max(), 0 };
for (int i = top; i < bottom; ++i)
intersectWithEdgeTableLine (i, rectLine);
needToCheckEmptiness = true;
}
}
void EdgeTable::clipToEdgeTable (const EdgeTable& other)
{
const Rectangle<int> clipped (other.bounds.getIntersection (bounds));
if (clipped.isEmpty())
{
needToCheckEmptiness = false;
bounds.setHeight (0);
}
else
{
const int top = clipped.getY() - bounds.getY();
const int bottom = clipped.getBottom() - bounds.getY();
if (bottom < bounds.getHeight())
bounds.setHeight (bottom);
if (clipped.getRight() < bounds.getRight())
bounds.setRight (clipped.getRight());
for (int i = 0; i < top; ++i)
table [lineStrideElements * i] = 0;
const int* otherLine = other.table + other.lineStrideElements * (clipped.getY() - other.bounds.getY());
for (int i = top; i < bottom; ++i)
{
intersectWithEdgeTableLine (i, otherLine);
otherLine += other.lineStrideElements;
}
needToCheckEmptiness = true;
}
}
void EdgeTable::clipLineToMask (int x, int y, const uint8* mask, int maskStride, int numPixels)
{
y -= bounds.getY();
if (y < 0 || y >= bounds.getHeight())
return;
needToCheckEmptiness = true;
if (numPixels <= 0)
{
table [lineStrideElements * y] = 0;
return;
}
int* tempLine = static_cast<int*> (alloca ((size_t) (numPixels * 2 + 4) * sizeof (int)));
int destIndex = 0, lastLevel = 0;
while (--numPixels >= 0)
{
const int alpha = *mask;
mask += maskStride;
if (alpha != lastLevel)
{
tempLine[++destIndex] = (x << 8);
tempLine[++destIndex] = alpha;
lastLevel = alpha;
}
++x;
}
if (lastLevel > 0)
{
tempLine[++destIndex] = (x << 8);
tempLine[++destIndex] = 0;
}
tempLine[0] = destIndex >> 1;
intersectWithEdgeTableLine (y, tempLine);
}
bool EdgeTable::isEmpty() noexcept
{
if (needToCheckEmptiness)
{
needToCheckEmptiness = false;
int* t = table;
for (int i = bounds.getHeight(); --i >= 0;)
{
if (t[0] > 1)
return false;
t += lineStrideElements;
}
bounds.setHeight (0);
}
return bounds.getHeight() == 0;
}

View file

@ -0,0 +1,220 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2013 - Raw Material Software Ltd.
Permission is granted to use this software under the terms of either:
a) the GPL v2 (or any later version)
b) the Affero GPL v3
Details of these licenses can be found at: www.gnu.org/licenses
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
------------------------------------------------------------------------------
To release a closed-source product which uses JUCE, commercial licenses are
available: visit www.juce.com for more information.
==============================================================================
*/
#ifndef JUCE_EDGETABLE_H_INCLUDED
#define JUCE_EDGETABLE_H_INCLUDED
//==============================================================================
/**
A table of horizontal scan-line segments - used for rasterising Paths.
@see Path, Graphics
*/
class JUCE_API EdgeTable
{
public:
//==============================================================================
/** Creates an edge table containing a path.
A table is created with a fixed vertical range, and only sections of the path
which lie within this range will be added to the table.
@param clipLimits only the region of the path that lies within this area will be added
@param pathToAdd the path to add to the table
@param transform a transform to apply to the path being added
*/
EdgeTable (const Rectangle<int>& clipLimits,
const Path& pathToAdd,
const AffineTransform& transform);
/** Creates an edge table containing a rectangle. */
explicit EdgeTable (const Rectangle<int>& rectangleToAdd);
/** Creates an edge table containing a rectangle list. */
explicit EdgeTable (const RectangleList<int>& rectanglesToAdd);
/** Creates an edge table containing a rectangle list. */
explicit EdgeTable (const RectangleList<float>& rectanglesToAdd);
/** Creates an edge table containing a rectangle. */
explicit EdgeTable (const Rectangle<float>& rectangleToAdd);
/** Creates a copy of another edge table. */
EdgeTable (const EdgeTable&);
/** Copies from another edge table. */
EdgeTable& operator= (const EdgeTable&);
/** Destructor. */
~EdgeTable();
//==============================================================================
void clipToRectangle (const Rectangle<int>& r);
void excludeRectangle (const Rectangle<int>& r);
void clipToEdgeTable (const EdgeTable&);
void clipLineToMask (int x, int y, const uint8* mask, int maskStride, int numPixels);
bool isEmpty() noexcept;
const Rectangle<int>& getMaximumBounds() const noexcept { return bounds; }
void translate (float dx, int dy) noexcept;
/** Scales all the alpha-levels in the table by the given multiplier. */
void multiplyLevels (float factor);
/** Reduces the amount of space the table has allocated.
This will shrink the table down to use as little memory as possible - useful for
read-only tables that get stored and re-used for rendering.
*/
void optimiseTable();
//==============================================================================
/** Iterates the lines in the table, for rendering.
This function will iterate each line in the table, and call a user-defined class
to render each pixel or continuous line of pixels that the table contains.
@param iterationCallback this templated class must contain the following methods:
@code
inline void setEdgeTableYPos (int y);
inline void handleEdgeTablePixel (int x, int alphaLevel) const;
inline void handleEdgeTablePixelFull (int x) const;
inline void handleEdgeTableLine (int x, int width, int alphaLevel) const;
inline void handleEdgeTableLineFull (int x, int width) const;
@endcode
(these don't necessarily have to be 'const', but it might help it go faster)
*/
template <class EdgeTableIterationCallback>
void iterate (EdgeTableIterationCallback& iterationCallback) const noexcept
{
const int* lineStart = table;
for (int y = 0; y < bounds.getHeight(); ++y)
{
const int* line = lineStart;
lineStart += lineStrideElements;
int numPoints = line[0];
if (--numPoints > 0)
{
int x = *++line;
jassert ((x >> 8) >= bounds.getX() && (x >> 8) < bounds.getRight());
int levelAccumulator = 0;
iterationCallback.setEdgeTableYPos (bounds.getY() + y);
while (--numPoints >= 0)
{
const int level = *++line;
jassert (isPositiveAndBelow (level, (int) 256));
const int endX = *++line;
jassert (endX >= x);
const int endOfRun = (endX >> 8);
if (endOfRun == (x >> 8))
{
// small segment within the same pixel, so just save it for the next
// time round..
levelAccumulator += (endX - x) * level;
}
else
{
// plot the fist pixel of this segment, including any accumulated
// levels from smaller segments that haven't been drawn yet
levelAccumulator += (0x100 - (x & 0xff)) * level;
levelAccumulator >>= 8;
x >>= 8;
if (levelAccumulator > 0)
{
if (levelAccumulator >= 255)
iterationCallback.handleEdgeTablePixelFull (x);
else
iterationCallback.handleEdgeTablePixel (x, levelAccumulator);
}
// if there's a run of similar pixels, do it all in one go..
if (level > 0)
{
jassert (endOfRun <= bounds.getRight());
const int numPix = endOfRun - ++x;
if (numPix > 0)
iterationCallback.handleEdgeTableLine (x, numPix, level);
}
// save the bit at the end to be drawn next time round the loop.
levelAccumulator = (endX & 0xff) * level;
}
x = endX;
}
levelAccumulator >>= 8;
if (levelAccumulator > 0)
{
x >>= 8;
jassert (x >= bounds.getX() && x < bounds.getRight());
if (levelAccumulator >= 255)
iterationCallback.handleEdgeTablePixelFull (x);
else
iterationCallback.handleEdgeTablePixel (x, levelAccumulator);
}
}
}
}
private:
//==============================================================================
// table line format: number of points; point0 x, point0 levelDelta, point1 x, point1 levelDelta, etc
struct LineItem
{
int x, level;
bool operator< (const LineItem& other) const noexcept { return x < other.x; }
};
HeapBlock<int> table;
Rectangle<int> bounds;
int maxEdgesPerLine, lineStrideElements;
bool needToCheckEmptiness;
void allocate();
void clearLineSizes() noexcept;
void addEdgePoint (int x, int y, int winding);
void addEdgePointPair (int x1, int x2, int y, int winding);
void remapTableForNumEdges (int newNumEdgesPerLine);
void intersectWithEdgeTableLine (int y, const int* otherLine);
void clipEdgeTableLineToRange (int* line, int x1, int x2) noexcept;
void sanitiseLevels (bool useNonZeroWinding) noexcept;
static void copyEdgeTableData (int* dest, int destLineStride, const int* src, int srcLineStride, int numLines) noexcept;
JUCE_LEAK_DETECTOR (EdgeTable)
};
#endif // JUCE_EDGETABLE_H_INCLUDED

View file

@ -0,0 +1,416 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2013 - Raw Material Software Ltd.
Permission is granted to use this software under the terms of either:
a) the GPL v2 (or any later version)
b) the Affero GPL v3
Details of these licenses can be found at: www.gnu.org/licenses
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
------------------------------------------------------------------------------
To release a closed-source product which uses JUCE, commercial licenses are
available: visit www.juce.com for more information.
==============================================================================
*/
#ifndef JUCE_LINE_H_INCLUDED
#define JUCE_LINE_H_INCLUDED
//==============================================================================
/**
Represents a line.
This class contains a bunch of useful methods for various geometric
tasks.
The ValueType template parameter should be a primitive type - float or double
are what it's designed for. Integer types will work in a basic way, but some methods
that perform mathematical operations may not compile, or they may not produce
sensible results.
@see Point, Rectangle, Path, Graphics::drawLine
*/
template <typename ValueType>
class Line
{
public:
//==============================================================================
/** Creates a line, using (0, 0) as its start and end points. */
Line() noexcept {}
/** Creates a copy of another line. */
Line (const Line& other) noexcept
: start (other.start),
end (other.end)
{
}
/** Creates a line based on the coordinates of its start and end points. */
Line (ValueType startX, ValueType startY, ValueType endX, ValueType endY) noexcept
: start (startX, startY),
end (endX, endY)
{
}
/** Creates a line from its start and end points. */
Line (const Point<ValueType> startPoint,
const Point<ValueType> endPoint) noexcept
: start (startPoint),
end (endPoint)
{
}
/** Copies a line from another one. */
Line& operator= (const Line& other) noexcept
{
start = other.start;
end = other.end;
return *this;
}
/** Destructor. */
~Line() noexcept {}
//==============================================================================
/** Returns the x coordinate of the line's start point. */
inline ValueType getStartX() const noexcept { return start.x; }
/** Returns the y coordinate of the line's start point. */
inline ValueType getStartY() const noexcept { return start.y; }
/** Returns the x coordinate of the line's end point. */
inline ValueType getEndX() const noexcept { return end.x; }
/** Returns the y coordinate of the line's end point. */
inline ValueType getEndY() const noexcept { return end.y; }
/** Returns the line's start point. */
inline Point<ValueType> getStart() const noexcept { return start; }
/** Returns the line's end point. */
inline Point<ValueType> getEnd() const noexcept { return end; }
/** Changes this line's start point */
void setStart (ValueType newStartX, ValueType newStartY) noexcept { start.setXY (newStartX, newStartY); }
/** Changes this line's end point */
void setEnd (ValueType newEndX, ValueType newEndY) noexcept { end.setXY (newEndX, newEndY); }
/** Changes this line's start point */
void setStart (const Point<ValueType> newStart) noexcept { start = newStart; }
/** Changes this line's end point */
void setEnd (const Point<ValueType> newEnd) noexcept { end = newEnd; }
/** Returns a line that is the same as this one, but with the start and end reversed, */
const Line reversed() const noexcept { return Line (end, start); }
/** Applies an affine transform to the line's start and end points. */
void applyTransform (const AffineTransform& transform) noexcept
{
start.applyTransform (transform);
end.applyTransform (transform);
}
//==============================================================================
/** Returns the length of the line. */
ValueType getLength() const noexcept { return start.getDistanceFrom (end); }
/** Returns true if the line's start and end x coordinates are the same. */
bool isVertical() const noexcept { return start.x == end.x; }
/** Returns true if the line's start and end y coordinates are the same. */
bool isHorizontal() const noexcept { return start.y == end.y; }
/** Returns the line's angle.
This value is the number of radians clockwise from the 12 o'clock direction,
where the line's start point is considered to be at the centre.
*/
typename Point<ValueType>::FloatType getAngle() const noexcept { return start.getAngleToPoint (end); }
/** Casts this line to float coordinates. */
Line<float> toFloat() const noexcept { return Line<float> (start.toFloat(), end.toFloat()); }
/** Casts this line to double coordinates. */
Line<double> toDouble() const noexcept { return Line<double> (start.toDouble(), end.toDouble()); }
//==============================================================================
/** Compares two lines. */
bool operator== (const Line& other) const noexcept { return start == other.start && end == other.end; }
/** Compares two lines. */
bool operator!= (const Line& other) const noexcept { return start != other.start || end != other.end; }
//==============================================================================
/** Finds the intersection between two lines.
@param line the line to intersect with
@returns the point at which the lines intersect, even if this lies beyond the end of the lines
*/
Point<ValueType> getIntersection (const Line& line) const noexcept
{
Point<ValueType> p;
findIntersection (start, end, line.start, line.end, p);
return p;
}
/** Finds the intersection between two lines.
@param line the other line
@param intersection the position of the point where the lines meet (or
where they would meet if they were infinitely long)
the intersection (if the lines intersect). If the lines
are parallel, this will just be set to the position
of one of the line's endpoints.
@returns true if the line segments intersect; false if they dont. Even if they
don't intersect, the intersection coordinates returned will still
be valid
*/
bool intersects (const Line& line, Point<ValueType>& intersection) const noexcept
{
return findIntersection (start, end, line.start, line.end, intersection);
}
/** Returns true if this line intersects another. */
bool intersects (const Line& other) const noexcept
{
Point<ValueType> ignored;
return findIntersection (start, end, other.start, other.end, ignored);
}
//==============================================================================
/** Returns the location of the point which is a given distance along this line.
@param distanceFromStart the distance to move along the line from its
start point. This value can be negative or longer
than the line itself
@see getPointAlongLineProportionally
*/
Point<ValueType> getPointAlongLine (ValueType distanceFromStart) const noexcept
{
return start + (end - start) * (distanceFromStart / getLength());
}
/** Returns a point which is a certain distance along and to the side of this line.
This effectively moves a given distance along the line, then another distance
perpendicularly to this, and returns the resulting position.
@param distanceFromStart the distance to move along the line from its
start point. This value can be negative or longer
than the line itself
@param perpendicularDistance how far to move sideways from the line. If you're
looking along the line from its start towards its
end, then a positive value here will move to the
right, negative value move to the left.
*/
Point<ValueType> getPointAlongLine (ValueType distanceFromStart,
ValueType perpendicularDistance) const noexcept
{
const Point<ValueType> delta (end - start);
const double length = juce_hypot ((double) delta.x,
(double) delta.y);
if (length <= 0)
return start;
return Point<ValueType> (start.x + static_cast <ValueType> ((delta.x * distanceFromStart - delta.y * perpendicularDistance) / length),
start.y + static_cast <ValueType> ((delta.y * distanceFromStart + delta.x * perpendicularDistance) / length));
}
/** Returns the location of the point which is a given distance along this line
proportional to the line's length.
@param proportionOfLength the distance to move along the line from its
start point, in multiples of the line's length.
So a value of 0.0 will return the line's start point
and a value of 1.0 will return its end point. (This value
can be negative or greater than 1.0).
@see getPointAlongLine
*/
Point<ValueType> getPointAlongLineProportionally (ValueType proportionOfLength) const noexcept
{
return start + (end - start) * proportionOfLength;
}
/** Returns the smallest distance between this line segment and a given point.
So if the point is close to the line, this will return the perpendicular
distance from the line; if the point is a long way beyond one of the line's
end-point's, it'll return the straight-line distance to the nearest end-point.
pointOnLine receives the position of the point that is found.
@returns the point's distance from the line
@see getPositionAlongLineOfNearestPoint
*/
ValueType getDistanceFromPoint (const Point<ValueType> targetPoint,
Point<ValueType>& pointOnLine) const noexcept
{
const Point<ValueType> delta (end - start);
const double length = delta.x * delta.x + delta.y * delta.y;
if (length > 0)
{
const double prop = ((targetPoint.x - start.x) * delta.x
+ (targetPoint.y - start.y) * delta.y) / length;
if (prop >= 0 && prop <= 1.0)
{
pointOnLine = start + delta * static_cast <ValueType> (prop);
return targetPoint.getDistanceFrom (pointOnLine);
}
}
const float fromStart = targetPoint.getDistanceFrom (start);
const float fromEnd = targetPoint.getDistanceFrom (end);
if (fromStart < fromEnd)
{
pointOnLine = start;
return fromStart;
}
else
{
pointOnLine = end;
return fromEnd;
}
}
/** Finds the point on this line which is nearest to a given point, and
returns its position as a proportional position along the line.
@returns a value 0 to 1.0 which is the distance along this line from the
line's start to the point which is nearest to the point passed-in. To
turn this number into a position, use getPointAlongLineProportionally().
@see getDistanceFromPoint, getPointAlongLineProportionally
*/
ValueType findNearestProportionalPositionTo (const Point<ValueType> point) const noexcept
{
const Point<ValueType> delta (end - start);
const double length = delta.x * delta.x + delta.y * delta.y;
return length <= 0 ? 0
: jlimit (ValueType(), static_cast <ValueType> (1),
static_cast <ValueType> ((((point.x - start.x) * delta.x
+ (point.y - start.y) * delta.y) / length)));
}
/** Finds the point on this line which is nearest to a given point.
@see getDistanceFromPoint, findNearestProportionalPositionTo
*/
Point<ValueType> findNearestPointTo (const Point<ValueType> point) const noexcept
{
return getPointAlongLineProportionally (findNearestProportionalPositionTo (point));
}
/** Returns true if the given point lies above this line.
The return value is true if the point's y coordinate is less than the y
coordinate of this line at the given x (assuming the line extends infinitely
in both directions).
*/
bool isPointAbove (const Point<ValueType> point) const noexcept
{
return start.x != end.x
&& point.y < ((end.y - start.y)
* (point.x - start.x)) / (end.x - start.x) + start.y;
}
//==============================================================================
/** Returns a shortened copy of this line.
This will chop off part of the start of this line by a certain amount, (leaving the
end-point the same), and return the new line.
*/
Line withShortenedStart (ValueType distanceToShortenBy) const noexcept
{
return Line (getPointAlongLine (jmin (distanceToShortenBy, getLength())), end);
}
/** Returns a shortened copy of this line.
This will chop off part of the end of this line by a certain amount, (leaving the
start-point the same), and return the new line.
*/
Line withShortenedEnd (ValueType distanceToShortenBy) const noexcept
{
const ValueType length = getLength();
return Line (start, getPointAlongLine (length - jmin (distanceToShortenBy, length)));
}
private:
//==============================================================================
Point<ValueType> start, end;
static bool findIntersection (const Point<ValueType> p1, const Point<ValueType> p2,
const Point<ValueType> p3, const Point<ValueType> p4,
Point<ValueType>& intersection) noexcept
{
if (p2 == p3)
{
intersection = p2;
return true;
}
const Point<ValueType> d1 (p2 - p1);
const Point<ValueType> d2 (p4 - p3);
const ValueType divisor = d1.x * d2.y - d2.x * d1.y;
if (divisor == 0)
{
if (! (d1.isOrigin() || d2.isOrigin()))
{
if (d1.y == 0 && d2.y != 0)
{
const ValueType along = (p1.y - p3.y) / d2.y;
intersection = p1.withX (p3.x + along * d2.x);
return along >= 0 && along <= static_cast <ValueType> (1);
}
else if (d2.y == 0 && d1.y != 0)
{
const ValueType along = (p3.y - p1.y) / d1.y;
intersection = p3.withX (p1.x + along * d1.x);
return along >= 0 && along <= static_cast <ValueType> (1);
}
else if (d1.x == 0 && d2.x != 0)
{
const ValueType along = (p1.x - p3.x) / d2.x;
intersection = p1.withY (p3.y + along * d2.y);
return along >= 0 && along <= static_cast <ValueType> (1);
}
else if (d2.x == 0 && d1.x != 0)
{
const ValueType along = (p3.x - p1.x) / d1.x;
intersection = p3.withY (p1.y + along * d1.y);
return along >= 0 && along <= static_cast <ValueType> (1);
}
}
intersection = (p2 + p3) / static_cast <ValueType> (2);
return false;
}
const ValueType along1 = ((p1.y - p3.y) * d2.x - (p1.x - p3.x) * d2.y) / divisor;
intersection = p1 + d1 * along1;
if (along1 < 0 || along1 > static_cast <ValueType> (1))
return false;
const ValueType along2 = ((p1.y - p3.y) * d1.x - (p1.x - p3.x) * d1.y) / divisor;
return along2 >= 0 && along2 <= static_cast <ValueType> (1);
}
};
#endif // JUCE_LINE_H_INCLUDED

View file

@ -0,0 +1,792 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2013 - Raw Material Software Ltd.
Permission is granted to use this software under the terms of either:
a) the GPL v2 (or any later version)
b) the Affero GPL v3
Details of these licenses can be found at: www.gnu.org/licenses
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
------------------------------------------------------------------------------
To release a closed-source product which uses JUCE, commercial licenses are
available: visit www.juce.com for more information.
==============================================================================
*/
#ifndef JUCE_PATH_H_INCLUDED
#define JUCE_PATH_H_INCLUDED
//==============================================================================
/**
A path is a sequence of lines and curves that may either form a closed shape
or be open-ended.
To use a path, you can create an empty one, then add lines and curves to it
to create shapes, then it can be rendered by a Graphics context or used
for geometric operations.
e.g. @code
Path myPath;
myPath.startNewSubPath (10.0f, 10.0f); // move the current position to (10, 10)
myPath.lineTo (100.0f, 200.0f); // draw a line from here to (100, 200)
myPath.quadraticTo (0.0f, 150.0f, 5.0f, 50.0f); // draw a curve that ends at (5, 50)
myPath.closeSubPath(); // close the subpath with a line back to (10, 10)
// add an ellipse as well, which will form a second sub-path within the path..
myPath.addEllipse (50.0f, 50.0f, 40.0f, 30.0f);
// double the width of the whole thing..
myPath.applyTransform (AffineTransform::scale (2.0f, 1.0f));
// and draw it to a graphics context with a 5-pixel thick outline.
g.strokePath (myPath, PathStrokeType (5.0f));
@endcode
A path object can actually contain multiple sub-paths, which may themselves
be open or closed.
@see PathFlatteningIterator, PathStrokeType, Graphics
*/
class JUCE_API Path
{
public:
//==============================================================================
/** Creates an empty path. */
Path();
/** Creates a copy of another path. */
Path (const Path&);
/** Destructor. */
~Path();
/** Copies this path from another one. */
Path& operator= (const Path&);
#if JUCE_COMPILER_SUPPORTS_MOVE_SEMANTICS
Path (Path&&) noexcept;
Path& operator= (Path&&) noexcept;
#endif
bool operator== (const Path&) const noexcept;
bool operator!= (const Path&) const noexcept;
//==============================================================================
/** Returns true if the path doesn't contain any lines or curves. */
bool isEmpty() const noexcept;
/** Returns the smallest rectangle that contains all points within the path.
*/
Rectangle<float> getBounds() const noexcept;
/** Returns the smallest rectangle that contains all points within the path
after it's been transformed with the given tranasform matrix.
*/
Rectangle<float> getBoundsTransformed (const AffineTransform& transform) const noexcept;
/** Checks whether a point lies within the path.
This is only relevent for closed paths (see closeSubPath()), and
may produce false results if used on a path which has open sub-paths.
The path's winding rule is taken into account by this method.
The tolerance parameter is the maximum error allowed when flattening the path,
so this method could return a false positive when your point is up to this distance
outside the path's boundary.
@see closeSubPath, setUsingNonZeroWinding
*/
bool contains (float x, float y,
float tolerance = 1.0f) const;
/** Checks whether a point lies within the path.
This is only relevent for closed paths (see closeSubPath()), and
may produce false results if used on a path which has open sub-paths.
The path's winding rule is taken into account by this method.
The tolerance parameter is the maximum error allowed when flattening the path,
so this method could return a false positive when your point is up to this distance
outside the path's boundary.
@see closeSubPath, setUsingNonZeroWinding
*/
bool contains (const Point<float> point,
float tolerance = 1.0f) const;
/** Checks whether a line crosses the path.
This will return positive if the line crosses any of the paths constituent
lines or curves. It doesn't take into account whether the line is inside
or outside the path, or whether the path is open or closed.
The tolerance parameter is the maximum error allowed when flattening the path,
so this method could return a false positive when your point is up to this distance
outside the path's boundary.
*/
bool intersectsLine (const Line<float>& line,
float tolerance = 1.0f);
/** Cuts off parts of a line to keep the parts that are either inside or
outside this path.
Note that this isn't smart enough to cope with situations where the
line would need to be cut into multiple pieces to correctly clip against
a re-entrant shape.
@param line the line to clip
@param keepSectionOutsidePath if true, it's the section outside the path
that will be kept; if false its the section inside
the path
*/
Line<float> getClippedLine (const Line<float>& line, bool keepSectionOutsidePath) const;
/** Returns the length of the path.
@see getPointAlongPath
*/
float getLength (const AffineTransform& transform = AffineTransform::identity) const;
/** Returns a point that is the specified distance along the path.
If the distance is greater than the total length of the path, this will return the
end point.
@see getLength
*/
Point<float> getPointAlongPath (float distanceFromStart,
const AffineTransform& transform = AffineTransform::identity) const;
/** Finds the point along the path which is nearest to a given position.
This sets pointOnPath to the nearest point, and returns the distance of this point from the start
of the path.
*/
float getNearestPoint (const Point<float> targetPoint,
Point<float>& pointOnPath,
const AffineTransform& transform = AffineTransform::identity) const;
//==============================================================================
/** Removes all lines and curves, resetting the path completely. */
void clear() noexcept;
/** Begins a new subpath with a given starting position.
This will move the path's current position to the coordinates passed in and
make it ready to draw lines or curves starting from this position.
After adding whatever lines and curves are needed, you can either
close the current sub-path using closeSubPath() or call startNewSubPath()
to move to a new sub-path, leaving the old one open-ended.
@see lineTo, quadraticTo, cubicTo, closeSubPath
*/
void startNewSubPath (float startX, float startY);
/** Begins a new subpath with a given starting position.
This will move the path's current position to the coordinates passed in and
make it ready to draw lines or curves starting from this position.
After adding whatever lines and curves are needed, you can either
close the current sub-path using closeSubPath() or call startNewSubPath()
to move to a new sub-path, leaving the old one open-ended.
@see lineTo, quadraticTo, cubicTo, closeSubPath
*/
void startNewSubPath (const Point<float> start);
/** Closes a the current sub-path with a line back to its start-point.
When creating a closed shape such as a triangle, don't use 3 lineTo()
calls - instead use two lineTo() calls, followed by a closeSubPath()
to join the final point back to the start.
This ensures that closes shapes are recognised as such, and this is
important for tasks like drawing strokes, which needs to know whether to
draw end-caps or not.
@see startNewSubPath, lineTo, quadraticTo, cubicTo, closeSubPath
*/
void closeSubPath();
/** Adds a line from the shape's last position to a new end-point.
This will connect the end-point of the last line or curve that was added
to a new point, using a straight line.
See the class description for an example of how to add lines and curves to a path.
@see startNewSubPath, quadraticTo, cubicTo, closeSubPath
*/
void lineTo (float endX, float endY);
/** Adds a line from the shape's last position to a new end-point.
This will connect the end-point of the last line or curve that was added
to a new point, using a straight line.
See the class description for an example of how to add lines and curves to a path.
@see startNewSubPath, quadraticTo, cubicTo, closeSubPath
*/
void lineTo (const Point<float> end);
/** Adds a quadratic bezier curve from the shape's last position to a new position.
This will connect the end-point of the last line or curve that was added
to a new point, using a quadratic spline with one control-point.
See the class description for an example of how to add lines and curves to a path.
@see startNewSubPath, lineTo, cubicTo, closeSubPath
*/
void quadraticTo (float controlPointX,
float controlPointY,
float endPointX,
float endPointY);
/** Adds a quadratic bezier curve from the shape's last position to a new position.
This will connect the end-point of the last line or curve that was added
to a new point, using a quadratic spline with one control-point.
See the class description for an example of how to add lines and curves to a path.
@see startNewSubPath, lineTo, cubicTo, closeSubPath
*/
void quadraticTo (const Point<float> controlPoint,
const Point<float> endPoint);
/** Adds a cubic bezier curve from the shape's last position to a new position.
This will connect the end-point of the last line or curve that was added
to a new point, using a cubic spline with two control-points.
See the class description for an example of how to add lines and curves to a path.
@see startNewSubPath, lineTo, quadraticTo, closeSubPath
*/
void cubicTo (float controlPoint1X,
float controlPoint1Y,
float controlPoint2X,
float controlPoint2Y,
float endPointX,
float endPointY);
/** Adds a cubic bezier curve from the shape's last position to a new position.
This will connect the end-point of the last line or curve that was added
to a new point, using a cubic spline with two control-points.
See the class description for an example of how to add lines and curves to a path.
@see startNewSubPath, lineTo, quadraticTo, closeSubPath
*/
void cubicTo (const Point<float> controlPoint1,
const Point<float> controlPoint2,
const Point<float> endPoint);
/** Returns the last point that was added to the path by one of the drawing methods.
*/
Point<float> getCurrentPosition() const;
//==============================================================================
/** Adds a rectangle to the path.
The rectangle is added as a new sub-path. (Any currently open paths will be left open).
@see addRoundedRectangle, addTriangle
*/
void addRectangle (float x, float y, float width, float height);
/** Adds a rectangle to the path.
The rectangle is added as a new sub-path. (Any currently open paths will be left open).
@see addRoundedRectangle, addTriangle
*/
template <typename ValueType>
void addRectangle (const Rectangle<ValueType>& rectangle)
{
addRectangle (static_cast <float> (rectangle.getX()), static_cast <float> (rectangle.getY()),
static_cast <float> (rectangle.getWidth()), static_cast <float> (rectangle.getHeight()));
}
/** Adds a rectangle with rounded corners to the path.
The rectangle is added as a new sub-path. (Any currently open paths will be left open).
@see addRectangle, addTriangle
*/
void addRoundedRectangle (float x, float y, float width, float height,
float cornerSize);
/** Adds a rectangle with rounded corners to the path.
The rectangle is added as a new sub-path. (Any currently open paths will be left open).
@see addRectangle, addTriangle
*/
void addRoundedRectangle (float x, float y, float width, float height,
float cornerSizeX,
float cornerSizeY);
/** Adds a rectangle with rounded corners to the path.
The rectangle is added as a new sub-path. (Any currently open paths will be left open).
@see addRectangle, addTriangle
*/
void addRoundedRectangle (float x, float y, float width, float height,
float cornerSizeX, float cornerSizeY,
bool curveTopLeft, bool curveTopRight,
bool curveBottomLeft, bool curveBottomRight);
/** Adds a rectangle with rounded corners to the path.
The rectangle is added as a new sub-path. (Any currently open paths will be left open).
@see addRectangle, addTriangle
*/
template <typename ValueType>
void addRoundedRectangle (const Rectangle<ValueType>& rectangle, float cornerSizeX, float cornerSizeY)
{
addRoundedRectangle (static_cast <float> (rectangle.getX()), static_cast <float> (rectangle.getY()),
static_cast <float> (rectangle.getWidth()), static_cast <float> (rectangle.getHeight()),
cornerSizeX, cornerSizeY);
}
/** Adds a rectangle with rounded corners to the path.
The rectangle is added as a new sub-path. (Any currently open paths will be left open).
@see addRectangle, addTriangle
*/
template <typename ValueType>
void addRoundedRectangle (const Rectangle<ValueType>& rectangle, float cornerSize)
{
addRoundedRectangle (rectangle, cornerSize, cornerSize);
}
/** Adds a triangle to the path.
The triangle is added as a new closed sub-path. (Any currently open paths will be left open).
Note that whether the vertices are specified in clockwise or anticlockwise
order will affect how the triangle is filled when it overlaps other
shapes (the winding order setting will affect this of course).
*/
void addTriangle (float x1, float y1,
float x2, float y2,
float x3, float y3);
/** Adds a quadrilateral to the path.
The quad is added as a new closed sub-path. (Any currently open paths will be left open).
Note that whether the vertices are specified in clockwise or anticlockwise
order will affect how the quad is filled when it overlaps other
shapes (the winding order setting will affect this of course).
*/
void addQuadrilateral (float x1, float y1,
float x2, float y2,
float x3, float y3,
float x4, float y4);
/** Adds an ellipse to the path.
The shape is added as a new sub-path. (Any currently open paths will be left open).
@see addArc
*/
void addEllipse (float x, float y, float width, float height);
/** Adds an ellipse to the path.
The shape is added as a new sub-path. (Any currently open paths will be left open).
@see addArc
*/
void addEllipse (Rectangle<float> area);
/** Adds an elliptical arc to the current path.
Note that when specifying the start and end angles, the curve will be drawn either clockwise
or anti-clockwise according to whether the end angle is greater than the start. This means
that sometimes you may need to use values greater than 2*Pi for the end angle.
@param x the left-hand edge of the rectangle in which the elliptical outline fits
@param y the top edge of the rectangle in which the elliptical outline fits
@param width the width of the rectangle in which the elliptical outline fits
@param height the height of the rectangle in which the elliptical outline fits
@param fromRadians the angle (clockwise) in radians at which to start the arc segment (where 0 is the
top-centre of the ellipse)
@param toRadians the angle (clockwise) in radians at which to end the arc segment (where 0 is the
top-centre of the ellipse). This angle can be greater than 2*Pi, so for example to
draw a curve clockwise from the 9 o'clock position to the 3 o'clock position via
12 o'clock, you'd use 1.5*Pi and 2.5*Pi as the start and finish points.
@param startAsNewSubPath if true, the arc will begin a new subpath from its starting point; if false,
it will be added to the current sub-path, continuing from the current postition
@see addCentredArc, arcTo, addPieSegment, addEllipse
*/
void addArc (float x, float y, float width, float height,
float fromRadians,
float toRadians,
bool startAsNewSubPath = false);
/** Adds an arc which is centred at a given point, and can have a rotation specified.
Note that when specifying the start and end angles, the curve will be drawn either clockwise
or anti-clockwise according to whether the end angle is greater than the start. This means
that sometimes you may need to use values greater than 2*Pi for the end angle.
@param centreX the centre x of the ellipse
@param centreY the centre y of the ellipse
@param radiusX the horizontal radius of the ellipse
@param radiusY the vertical radius of the ellipse
@param rotationOfEllipse an angle by which the whole ellipse should be rotated about its centre, in radians (clockwise)
@param fromRadians the angle (clockwise) in radians at which to start the arc segment (where 0 is the
top-centre of the ellipse)
@param toRadians the angle (clockwise) in radians at which to end the arc segment (where 0 is the
top-centre of the ellipse). This angle can be greater than 2*Pi, so for example to
draw a curve clockwise from the 9 o'clock position to the 3 o'clock position via
12 o'clock, you'd use 1.5*Pi and 2.5*Pi as the start and finish points.
@param startAsNewSubPath if true, the arc will begin a new subpath from its starting point; if false,
it will be added to the current sub-path, continuing from the current postition
@see addArc, arcTo
*/
void addCentredArc (float centreX, float centreY,
float radiusX, float radiusY,
float rotationOfEllipse,
float fromRadians,
float toRadians,
bool startAsNewSubPath = false);
/** Adds a "pie-chart" shape to the path.
The shape is added as a new sub-path. (Any currently open paths will be
left open).
Note that when specifying the start and end angles, the curve will be drawn either clockwise
or anti-clockwise according to whether the end angle is greater than the start. This means
that sometimes you may need to use values greater than 2*Pi for the end angle.
@param x the left-hand edge of the rectangle in which the elliptical outline fits
@param y the top edge of the rectangle in which the elliptical outline fits
@param width the width of the rectangle in which the elliptical outline fits
@param height the height of the rectangle in which the elliptical outline fits
@param fromRadians the angle (clockwise) in radians at which to start the arc segment (where 0 is the
top-centre of the ellipse)
@param toRadians the angle (clockwise) in radians at which to end the arc segment (where 0 is the
top-centre of the ellipse)
@param innerCircleProportionalSize if this is > 0, then the pie will be drawn as a curved band around a hollow
ellipse at its centre, where this value indicates the inner ellipse's size with
respect to the outer one.
@see addArc
*/
void addPieSegment (float x, float y,
float width, float height,
float fromRadians,
float toRadians,
float innerCircleProportionalSize);
/** Adds a line with a specified thickness.
The line is added as a new closed sub-path. (Any currently open paths will be
left open).
@see addArrow
*/
void addLineSegment (const Line<float>& line, float lineThickness);
/** Adds a line with an arrowhead on the end.
The arrow is added as a new closed sub-path. (Any currently open paths will be left open).
@see PathStrokeType::createStrokeWithArrowheads
*/
void addArrow (const Line<float>& line,
float lineThickness,
float arrowheadWidth,
float arrowheadLength);
/** Adds a polygon shape to the path.
@see addStar
*/
void addPolygon (const Point<float> centre,
int numberOfSides,
float radius,
float startAngle = 0.0f);
/** Adds a star shape to the path.
@see addPolygon
*/
void addStar (const Point<float> centre,
int numberOfPoints,
float innerRadius,
float outerRadius,
float startAngle = 0.0f);
/** Adds a speech-bubble shape to the path.
@param bodyArea the area of the body of the bubble shape
@param maximumArea an area which encloses the body area and defines the limits within which
the arrow tip can be drawn - if the tip lies outside this area, the bubble
will be drawn without an arrow
@param arrowTipPosition the location of the tip of the arrow
@param cornerSize the size of the rounded corners
@param arrowBaseWidth the width of the base of the arrow where it joins the main rectangle
*/
void addBubble (const Rectangle<float>& bodyArea,
const Rectangle<float>& maximumArea,
const Point<float> arrowTipPosition,
const float cornerSize,
const float arrowBaseWidth);
/** Adds another path to this one.
The new path is added as a new sub-path. (Any currently open paths in this
path will be left open).
@param pathToAppend the path to add
*/
void addPath (const Path& pathToAppend);
/** Adds another path to this one, transforming it on the way in.
The new path is added as a new sub-path, its points being transformed by the given
matrix before being added.
@param pathToAppend the path to add
@param transformToApply an optional transform to apply to the incoming vertices
*/
void addPath (const Path& pathToAppend,
const AffineTransform& transformToApply);
/** Swaps the contents of this path with another one.
The internal data of the two paths is swapped over, so this is much faster than
copying it to a temp variable and back.
*/
void swapWithPath (Path&) noexcept;
//==============================================================================
/** Preallocates enough space for adding the given number of coordinates to the path.
If you're about to add a large number of lines or curves to the path, it can make
the task much more efficient to call this first and avoid costly reallocations
as the structure grows.
The actual value to pass is a bit tricky to calculate because the space required
depends on what you're adding - e.g. each lineTo() or startNewSubPath() will
require 3 coords (x, y and a type marker). Each quadraticTo() will need 5, and
a cubicTo() will require 7. Closing a sub-path will require 1.
*/
void preallocateSpace (int numExtraCoordsToMakeSpaceFor);
//==============================================================================
/** Applies a 2D transform to all the vertices in the path.
@see AffineTransform, scaleToFit, getTransformToScaleToFit
*/
void applyTransform (const AffineTransform& transform) noexcept;
/** Rescales this path to make it fit neatly into a given space.
This is effectively a quick way of calling
applyTransform (getTransformToScaleToFit (x, y, w, h, preserveProportions))
@param x the x position of the rectangle to fit the path inside
@param y the y position of the rectangle to fit the path inside
@param width the width of the rectangle to fit the path inside
@param height the height of the rectangle to fit the path inside
@param preserveProportions if true, it will fit the path into the space without altering its
horizontal/vertical scale ratio; if false, it will distort the
path to fill the specified ratio both horizontally and vertically
@see applyTransform, getTransformToScaleToFit
*/
void scaleToFit (float x, float y, float width, float height,
bool preserveProportions) noexcept;
/** Returns a transform that can be used to rescale the path to fit into a given space.
@param x the x position of the rectangle to fit the path inside
@param y the y position of the rectangle to fit the path inside
@param width the width of the rectangle to fit the path inside
@param height the height of the rectangle to fit the path inside
@param preserveProportions if true, it will fit the path into the space without altering its
horizontal/vertical scale ratio; if false, it will distort the
path to fill the specified ratio both horizontally and vertically
@param justificationType if the proportions are preseved, the resultant path may be smaller
than the available rectangle, so this describes how it should be
positioned within the space.
@returns an appropriate transformation
@see applyTransform, scaleToFit
*/
AffineTransform getTransformToScaleToFit (float x, float y, float width, float height,
bool preserveProportions,
Justification justificationType = Justification::centred) const;
/** Returns a transform that can be used to rescale the path to fit into a given space.
@param area the rectangle to fit the path inside
@param preserveProportions if true, it will fit the path into the space without altering its
horizontal/vertical scale ratio; if false, it will distort the
path to fill the specified ratio both horizontally and vertically
@param justificationType if the proportions are preseved, the resultant path may be smaller
than the available rectangle, so this describes how it should be
positioned within the space.
@returns an appropriate transformation
@see applyTransform, scaleToFit
*/
AffineTransform getTransformToScaleToFit (const Rectangle<float>& area,
bool preserveProportions,
Justification justificationType = Justification::centred) const;
/** Creates a version of this path where all sharp corners have been replaced by curves.
Wherever two lines meet at an angle, this will replace the corner with a curve
of the given radius.
*/
Path createPathWithRoundedCorners (float cornerRadius) const;
//==============================================================================
/** Changes the winding-rule to be used when filling the path.
If set to true (which is the default), then the path uses a non-zero-winding rule
to determine which points are inside the path. If set to false, it uses an
alternate-winding rule.
The winding-rule comes into play when areas of the shape overlap other
areas, and determines whether the overlapping regions are considered to be
inside or outside.
Changing this value just sets a flag - it doesn't affect the contents of the
path.
@see isUsingNonZeroWinding
*/
void setUsingNonZeroWinding (bool isNonZeroWinding) noexcept;
/** Returns the flag that indicates whether the path should use a non-zero winding rule.
The default for a new path is true.
@see setUsingNonZeroWinding
*/
bool isUsingNonZeroWinding() const { return useNonZeroWinding; }
//==============================================================================
/** Iterates the lines and curves that a path contains.
@see Path, PathFlatteningIterator
*/
class JUCE_API Iterator
{
public:
//==============================================================================
Iterator (const Path& path);
~Iterator();
//==============================================================================
/** Moves onto the next element in the path.
If this returns false, there are no more elements. If it returns true,
the elementType variable will be set to the type of the current element,
and some of the x and y variables will be filled in with values.
*/
bool next();
//==============================================================================
enum PathElementType
{
startNewSubPath, /**< For this type, x1 and y1 will be set to indicate the first point in the subpath. */
lineTo, /**< For this type, x1 and y1 indicate the end point of the line. */
quadraticTo, /**< For this type, x1, y1, x2, y2 indicate the control point and endpoint of a quadratic curve. */
cubicTo, /**< For this type, x1, y1, x2, y2, x3, y3 indicate the two control points and the endpoint of a cubic curve. */
closePath /**< Indicates that the sub-path is being closed. None of the x or y values are valid in this case. */
};
PathElementType elementType;
float x1, y1, x2, y2, x3, y3;
//==============================================================================
private:
const Path& path;
size_t index;
JUCE_DECLARE_NON_COPYABLE (Iterator)
};
//==============================================================================
/** Loads a stored path from a data stream.
The data in the stream must have been written using writePathToStream().
Note that this will append the stored path to whatever is currently in
this path, so you might need to call clear() beforehand.
@see loadPathFromData, writePathToStream
*/
void loadPathFromStream (InputStream& source);
/** Loads a stored path from a block of data.
This is similar to loadPathFromStream(), but just reads from a block
of data. Useful if you're including stored shapes in your code as a
block of static data.
@see loadPathFromStream, writePathToStream
*/
void loadPathFromData (const void* data, size_t numberOfBytes);
/** Stores the path by writing it out to a stream.
After writing out a path, you can reload it using loadPathFromStream().
@see loadPathFromStream, loadPathFromData
*/
void writePathToStream (OutputStream& destination) const;
//==============================================================================
/** Creates a string containing a textual representation of this path.
@see restoreFromString
*/
String toString() const;
/** Restores this path from a string that was created with the toString() method.
@see toString()
*/
void restoreFromString (StringRef stringVersion);
private:
//==============================================================================
friend class PathFlatteningIterator;
friend class Path::Iterator;
ArrayAllocationBase<float, DummyCriticalSection> data;
size_t numElements;
struct PathBounds
{
PathBounds() noexcept;
Rectangle<float> getRectangle() const noexcept;
void reset() noexcept;
void reset (float, float) noexcept;
void extend (float, float) noexcept;
void extend (float, float, float, float) noexcept;
float pathXMin, pathXMax, pathYMin, pathYMax;
};
PathBounds bounds;
bool useNonZeroWinding;
static const float lineMarker;
static const float moveMarker;
static const float quadMarker;
static const float cubicMarker;
static const float closeSubPathMarker;
JUCE_LEAK_DETECTOR (Path)
};
#endif // JUCE_PATH_H_INCLUDED

View file

@ -0,0 +1,286 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2013 - Raw Material Software Ltd.
Permission is granted to use this software under the terms of either:
a) the GPL v2 (or any later version)
b) the Affero GPL v3
Details of these licenses can be found at: www.gnu.org/licenses
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
------------------------------------------------------------------------------
To release a closed-source product which uses JUCE, commercial licenses are
available: visit www.juce.com for more information.
==============================================================================
*/
#if JUCE_MSVC && JUCE_DEBUG
#pragma optimize ("t", on)
#endif
const float PathFlatteningIterator::defaultTolerance = 0.6f;
//==============================================================================
PathFlatteningIterator::PathFlatteningIterator (const Path& path_,
const AffineTransform& transform_,
const float tolerance)
: x2 (0),
y2 (0),
closesSubPath (false),
subPathIndex (-1),
path (path_),
transform (transform_),
points (path_.data.elements),
toleranceSquared (tolerance * tolerance),
subPathCloseX (0),
subPathCloseY (0),
isIdentityTransform (transform_.isIdentity()),
stackBase (32),
index (0),
stackSize (32)
{
stackPos = stackBase;
}
PathFlatteningIterator::~PathFlatteningIterator()
{
}
bool PathFlatteningIterator::isLastInSubpath() const noexcept
{
return stackPos == stackBase.getData()
&& (index >= path.numElements || points [index] == Path::moveMarker);
}
bool PathFlatteningIterator::next()
{
x1 = x2;
y1 = y2;
float x3 = 0;
float y3 = 0;
float x4 = 0;
float y4 = 0;
for (;;)
{
float type;
if (stackPos == stackBase)
{
if (index >= path.numElements)
return false;
type = points [index++];
if (type != Path::closeSubPathMarker)
{
x2 = points [index++];
y2 = points [index++];
if (type == Path::quadMarker)
{
x3 = points [index++];
y3 = points [index++];
if (! isIdentityTransform)
transform.transformPoints (x2, y2, x3, y3);
}
else if (type == Path::cubicMarker)
{
x3 = points [index++];
y3 = points [index++];
x4 = points [index++];
y4 = points [index++];
if (! isIdentityTransform)
transform.transformPoints (x2, y2, x3, y3, x4, y4);
}
else
{
if (! isIdentityTransform)
transform.transformPoint (x2, y2);
}
}
}
else
{
type = *--stackPos;
if (type != Path::closeSubPathMarker)
{
x2 = *--stackPos;
y2 = *--stackPos;
if (type == Path::quadMarker)
{
x3 = *--stackPos;
y3 = *--stackPos;
}
else if (type == Path::cubicMarker)
{
x3 = *--stackPos;
y3 = *--stackPos;
x4 = *--stackPos;
y4 = *--stackPos;
}
}
}
if (type == Path::lineMarker)
{
++subPathIndex;
closesSubPath = (stackPos == stackBase)
&& (index < path.numElements)
&& (points [index] == Path::closeSubPathMarker)
&& x2 == subPathCloseX
&& y2 == subPathCloseY;
return true;
}
if (type == Path::quadMarker)
{
const size_t offset = (size_t) (stackPos - stackBase);
if (offset >= stackSize - 10)
{
stackSize <<= 1;
stackBase.realloc (stackSize);
stackPos = stackBase + offset;
}
const float m1x = (x1 + x2) * 0.5f;
const float m1y = (y1 + y2) * 0.5f;
const float m2x = (x2 + x3) * 0.5f;
const float m2y = (y2 + y3) * 0.5f;
const float m3x = (m1x + m2x) * 0.5f;
const float m3y = (m1y + m2y) * 0.5f;
const float errorX = m3x - x2;
const float errorY = m3y - y2;
if (errorX * errorX + errorY * errorY > toleranceSquared)
{
*stackPos++ = y3;
*stackPos++ = x3;
*stackPos++ = m2y;
*stackPos++ = m2x;
*stackPos++ = Path::quadMarker;
*stackPos++ = m3y;
*stackPos++ = m3x;
*stackPos++ = m1y;
*stackPos++ = m1x;
*stackPos++ = Path::quadMarker;
}
else
{
*stackPos++ = y3;
*stackPos++ = x3;
*stackPos++ = Path::lineMarker;
*stackPos++ = m3y;
*stackPos++ = m3x;
*stackPos++ = Path::lineMarker;
}
jassert (stackPos < stackBase + stackSize);
}
else if (type == Path::cubicMarker)
{
const size_t offset = (size_t) (stackPos - stackBase);
if (offset >= stackSize - 16)
{
stackSize <<= 1;
stackBase.realloc (stackSize);
stackPos = stackBase + offset;
}
const float m1x = (x1 + x2) * 0.5f;
const float m1y = (y1 + y2) * 0.5f;
const float m2x = (x3 + x2) * 0.5f;
const float m2y = (y3 + y2) * 0.5f;
const float m3x = (x3 + x4) * 0.5f;
const float m3y = (y3 + y4) * 0.5f;
const float m4x = (m1x + m2x) * 0.5f;
const float m4y = (m1y + m2y) * 0.5f;
const float m5x = (m3x + m2x) * 0.5f;
const float m5y = (m3y + m2y) * 0.5f;
const float error1X = m4x - x2;
const float error1Y = m4y - y2;
const float error2X = m5x - x3;
const float error2Y = m5y - y3;
if (error1X * error1X + error1Y * error1Y > toleranceSquared
|| error2X * error2X + error2Y * error2Y > toleranceSquared)
{
*stackPos++ = y4;
*stackPos++ = x4;
*stackPos++ = m3y;
*stackPos++ = m3x;
*stackPos++ = m5y;
*stackPos++ = m5x;
*stackPos++ = Path::cubicMarker;
*stackPos++ = (m4y + m5y) * 0.5f;
*stackPos++ = (m4x + m5x) * 0.5f;
*stackPos++ = m4y;
*stackPos++ = m4x;
*stackPos++ = m1y;
*stackPos++ = m1x;
*stackPos++ = Path::cubicMarker;
}
else
{
*stackPos++ = y4;
*stackPos++ = x4;
*stackPos++ = Path::lineMarker;
*stackPos++ = m5y;
*stackPos++ = m5x;
*stackPos++ = Path::lineMarker;
*stackPos++ = m4y;
*stackPos++ = m4x;
*stackPos++ = Path::lineMarker;
}
}
else if (type == Path::closeSubPathMarker)
{
if (x2 != subPathCloseX || y2 != subPathCloseY)
{
x1 = x2;
y1 = y2;
x2 = subPathCloseX;
y2 = subPathCloseY;
closesSubPath = true;
return true;
}
}
else
{
jassert (type == Path::moveMarker);
subPathIndex = -1;
subPathCloseX = x1 = x2;
subPathCloseY = y1 = y2;
}
}
}
#if JUCE_MSVC && JUCE_DEBUG
#pragma optimize ("", on) // resets optimisations to the project defaults
#endif

View file

@ -0,0 +1,113 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2013 - Raw Material Software Ltd.
Permission is granted to use this software under the terms of either:
a) the GPL v2 (or any later version)
b) the Affero GPL v3
Details of these licenses can be found at: www.gnu.org/licenses
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
------------------------------------------------------------------------------
To release a closed-source product which uses JUCE, commercial licenses are
available: visit www.juce.com for more information.
==============================================================================
*/
#ifndef JUCE_PATHITERATOR_H_INCLUDED
#define JUCE_PATHITERATOR_H_INCLUDED
//==============================================================================
/**
Flattens a Path object into a series of straight-line sections.
Use one of these to iterate through a Path object, and it will convert
all the curves into line sections so it's easy to render or perform
geometric operations on.
@see Path
*/
class JUCE_API PathFlatteningIterator
{
public:
//==============================================================================
/** Creates a PathFlatteningIterator.
After creation, use the next() method to initialise the fields in the
object with the first line's position.
@param path the path to iterate along
@param transform a transform to apply to each point in the path being iterated
@param tolerance the amount by which the curves are allowed to deviate from the lines
into which they are being broken down - a higher tolerance contains
less lines, so can be generated faster, but will be less smooth.
*/
PathFlatteningIterator (const Path& path,
const AffineTransform& transform = AffineTransform::identity,
float tolerance = defaultTolerance);
/** Destructor. */
~PathFlatteningIterator();
//==============================================================================
/** Fetches the next line segment from the path.
This will update the member variables x1, y1, x2, y2, subPathIndex and closesSubPath
so that they describe the new line segment.
@returns false when there are no more lines to fetch.
*/
bool next();
float x1; /**< The x position of the start of the current line segment. */
float y1; /**< The y position of the start of the current line segment. */
float x2; /**< The x position of the end of the current line segment. */
float y2; /**< The y position of the end of the current line segment. */
/** Indicates whether the current line segment is closing a sub-path.
If the current line is the one that connects the end of a sub-path
back to the start again, this will be true.
*/
bool closesSubPath;
/** The index of the current line within the current sub-path.
E.g. you can use this to see whether the line is the first one in the
subpath by seeing if it's 0.
*/
int subPathIndex;
/** Returns true if the current segment is the last in the current sub-path. */
bool isLastInSubpath() const noexcept;
/** This is the default value that should be used for the tolerance value (see the constructor parameters). */
static const float defaultTolerance;
private:
//==============================================================================
const Path& path;
const AffineTransform transform;
float* points;
const float toleranceSquared;
float subPathCloseX, subPathCloseY;
const bool isIdentityTransform;
HeapBlock <float> stackBase;
float* stackPos;
size_t index, stackSize;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (PathFlatteningIterator)
};
#endif // JUCE_PATHITERATOR_H_INCLUDED

View file

@ -0,0 +1,740 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2013 - Raw Material Software Ltd.
Permission is granted to use this software under the terms of either:
a) the GPL v2 (or any later version)
b) the Affero GPL v3
Details of these licenses can be found at: www.gnu.org/licenses
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
------------------------------------------------------------------------------
To release a closed-source product which uses JUCE, commercial licenses are
available: visit www.juce.com for more information.
==============================================================================
*/
PathStrokeType::PathStrokeType (float strokeThickness) noexcept
: thickness (strokeThickness), jointStyle (mitered), endStyle (butt)
{
}
PathStrokeType::PathStrokeType (float strokeThickness, JointStyle joint, EndCapStyle end) noexcept
: thickness (strokeThickness), jointStyle (joint), endStyle (end)
{
}
PathStrokeType::PathStrokeType (const PathStrokeType& other) noexcept
: thickness (other.thickness),
jointStyle (other.jointStyle),
endStyle (other.endStyle)
{
}
PathStrokeType& PathStrokeType::operator= (const PathStrokeType& other) noexcept
{
thickness = other.thickness;
jointStyle = other.jointStyle;
endStyle = other.endStyle;
return *this;
}
PathStrokeType::~PathStrokeType() noexcept
{
}
bool PathStrokeType::operator== (const PathStrokeType& other) const noexcept
{
return thickness == other.thickness
&& jointStyle == other.jointStyle
&& endStyle == other.endStyle;
}
bool PathStrokeType::operator!= (const PathStrokeType& other) const noexcept
{
return ! operator== (other);
}
//==============================================================================
namespace PathStrokeHelpers
{
static bool lineIntersection (const float x1, const float y1,
const float x2, const float y2,
const float x3, const float y3,
const float x4, const float y4,
float& intersectionX,
float& intersectionY,
float& distanceBeyondLine1EndSquared) noexcept
{
if (x2 != x3 || y2 != y3)
{
const float dx1 = x2 - x1;
const float dy1 = y2 - y1;
const float dx2 = x4 - x3;
const float dy2 = y4 - y3;
const float divisor = dx1 * dy2 - dx2 * dy1;
if (divisor == 0)
{
if (! ((dx1 == 0 && dy1 == 0) || (dx2 == 0 && dy2 == 0)))
{
if (dy1 == 0 && dy2 != 0)
{
const float along = (y1 - y3) / dy2;
intersectionX = x3 + along * dx2;
intersectionY = y1;
distanceBeyondLine1EndSquared = intersectionX - x2;
distanceBeyondLine1EndSquared *= distanceBeyondLine1EndSquared;
if ((x2 > x1) == (intersectionX < x2))
distanceBeyondLine1EndSquared = -distanceBeyondLine1EndSquared;
return along >= 0 && along <= 1.0f;
}
else if (dy2 == 0 && dy1 != 0)
{
const float along = (y3 - y1) / dy1;
intersectionX = x1 + along * dx1;
intersectionY = y3;
distanceBeyondLine1EndSquared = (along - 1.0f) * dx1;
distanceBeyondLine1EndSquared *= distanceBeyondLine1EndSquared;
if (along < 1.0f)
distanceBeyondLine1EndSquared = -distanceBeyondLine1EndSquared;
return along >= 0 && along <= 1.0f;
}
else if (dx1 == 0 && dx2 != 0)
{
const float along = (x1 - x3) / dx2;
intersectionX = x1;
intersectionY = y3 + along * dy2;
distanceBeyondLine1EndSquared = intersectionY - y2;
distanceBeyondLine1EndSquared *= distanceBeyondLine1EndSquared;
if ((y2 > y1) == (intersectionY < y2))
distanceBeyondLine1EndSquared = -distanceBeyondLine1EndSquared;
return along >= 0 && along <= 1.0f;
}
else if (dx2 == 0 && dx1 != 0)
{
const float along = (x3 - x1) / dx1;
intersectionX = x3;
intersectionY = y1 + along * dy1;
distanceBeyondLine1EndSquared = (along - 1.0f) * dy1;
distanceBeyondLine1EndSquared *= distanceBeyondLine1EndSquared;
if (along < 1.0f)
distanceBeyondLine1EndSquared = -distanceBeyondLine1EndSquared;
return along >= 0 && along <= 1.0f;
}
}
intersectionX = 0.5f * (x2 + x3);
intersectionY = 0.5f * (y2 + y3);
distanceBeyondLine1EndSquared = 0.0f;
return false;
}
else
{
const float along1 = ((y1 - y3) * dx2 - (x1 - x3) * dy2) / divisor;
intersectionX = x1 + along1 * dx1;
intersectionY = y1 + along1 * dy1;
if (along1 >= 0 && along1 <= 1.0f)
{
const float along2 = ((y1 - y3) * dx1 - (x1 - x3) * dy1);
if (along2 >= 0 && along2 <= divisor)
{
distanceBeyondLine1EndSquared = 0.0f;
return true;
}
}
distanceBeyondLine1EndSquared = along1 - 1.0f;
distanceBeyondLine1EndSquared *= distanceBeyondLine1EndSquared;
distanceBeyondLine1EndSquared *= (dx1 * dx1 + dy1 * dy1);
if (along1 < 1.0f)
distanceBeyondLine1EndSquared = -distanceBeyondLine1EndSquared;
return false;
}
}
intersectionX = x2;
intersectionY = y2;
distanceBeyondLine1EndSquared = 0.0f;
return true;
}
static void addEdgeAndJoint (Path& destPath,
const PathStrokeType::JointStyle style,
const float maxMiterExtensionSquared, const float width,
const float x1, const float y1,
const float x2, const float y2,
const float x3, const float y3,
const float x4, const float y4,
const float midX, const float midY)
{
if (style == PathStrokeType::beveled
|| (x3 == x4 && y3 == y4)
|| (x1 == x2 && y1 == y2))
{
destPath.lineTo (x2, y2);
destPath.lineTo (x3, y3);
}
else
{
float jx, jy, distanceBeyondLine1EndSquared;
// if they intersect, use this point..
if (lineIntersection (x1, y1, x2, y2,
x3, y3, x4, y4,
jx, jy, distanceBeyondLine1EndSquared))
{
destPath.lineTo (jx, jy);
}
else
{
if (style == PathStrokeType::mitered)
{
if (distanceBeyondLine1EndSquared < maxMiterExtensionSquared
&& distanceBeyondLine1EndSquared > 0.0f)
{
destPath.lineTo (jx, jy);
}
else
{
// the end sticks out too far, so just use a blunt joint
destPath.lineTo (x2, y2);
destPath.lineTo (x3, y3);
}
}
else
{
// curved joints
float angle1 = std::atan2 (x2 - midX, y2 - midY);
float angle2 = std::atan2 (x3 - midX, y3 - midY);
const float angleIncrement = 0.1f;
destPath.lineTo (x2, y2);
if (std::abs (angle1 - angle2) > angleIncrement)
{
if (angle2 > angle1 + float_Pi
|| (angle2 < angle1 && angle2 >= angle1 - float_Pi))
{
if (angle2 > angle1)
angle2 -= float_Pi * 2.0f;
jassert (angle1 <= angle2 + float_Pi);
angle1 -= angleIncrement;
while (angle1 > angle2)
{
destPath.lineTo (midX + width * std::sin (angle1),
midY + width * std::cos (angle1));
angle1 -= angleIncrement;
}
}
else
{
if (angle1 > angle2)
angle1 -= float_Pi * 2.0f;
jassert (angle1 >= angle2 - float_Pi);
angle1 += angleIncrement;
while (angle1 < angle2)
{
destPath.lineTo (midX + width * std::sin (angle1),
midY + width * std::cos (angle1));
angle1 += angleIncrement;
}
}
}
destPath.lineTo (x3, y3);
}
}
}
}
static void addLineEnd (Path& destPath,
const PathStrokeType::EndCapStyle style,
const float x1, const float y1,
const float x2, const float y2,
const float width)
{
if (style == PathStrokeType::butt)
{
destPath.lineTo (x2, y2);
}
else
{
float offx1, offy1, offx2, offy2;
float dx = x2 - x1;
float dy = y2 - y1;
const float len = juce_hypot (dx, dy);
if (len == 0)
{
offx1 = offx2 = x1;
offy1 = offy2 = y1;
}
else
{
const float offset = width / len;
dx *= offset;
dy *= offset;
offx1 = x1 + dy;
offy1 = y1 - dx;
offx2 = x2 + dy;
offy2 = y2 - dx;
}
if (style == PathStrokeType::square)
{
// sqaure ends
destPath.lineTo (offx1, offy1);
destPath.lineTo (offx2, offy2);
destPath.lineTo (x2, y2);
}
else
{
// rounded ends
const float midx = (offx1 + offx2) * 0.5f;
const float midy = (offy1 + offy2) * 0.5f;
destPath.cubicTo (x1 + (offx1 - x1) * 0.55f, y1 + (offy1 - y1) * 0.55f,
offx1 + (midx - offx1) * 0.45f, offy1 + (midy - offy1) * 0.45f,
midx, midy);
destPath.cubicTo (midx + (offx2 - midx) * 0.55f, midy + (offy2 - midy) * 0.55f,
offx2 + (x2 - offx2) * 0.45f, offy2 + (y2 - offy2) * 0.45f,
x2, y2);
}
}
}
struct Arrowhead
{
float startWidth, startLength;
float endWidth, endLength;
};
static void addArrowhead (Path& destPath,
const float x1, const float y1,
const float x2, const float y2,
const float tipX, const float tipY,
const float width,
const float arrowheadWidth)
{
Line<float> line (x1, y1, x2, y2);
destPath.lineTo (line.getPointAlongLine (-(arrowheadWidth / 2.0f - width), 0));
destPath.lineTo (tipX, tipY);
destPath.lineTo (line.getPointAlongLine (arrowheadWidth - (arrowheadWidth / 2.0f - width), 0));
destPath.lineTo (x2, y2);
}
struct LineSection
{
float x1, y1, x2, y2; // original line
float lx1, ly1, lx2, ly2; // the left-hand stroke
float rx1, ry1, rx2, ry2; // the right-hand stroke
};
static void shortenSubPath (Array<LineSection>& subPath, float amountAtStart, float amountAtEnd)
{
while (amountAtEnd > 0 && subPath.size() > 0)
{
LineSection& l = subPath.getReference (subPath.size() - 1);
float dx = l.rx2 - l.rx1;
float dy = l.ry2 - l.ry1;
const float len = juce_hypot (dx, dy);
if (len <= amountAtEnd && subPath.size() > 1)
{
LineSection& prev = subPath.getReference (subPath.size() - 2);
prev.x2 = l.x2;
prev.y2 = l.y2;
subPath.removeLast();
amountAtEnd -= len;
}
else
{
const float prop = jmin (0.9999f, amountAtEnd / len);
dx *= prop;
dy *= prop;
l.rx1 += dx;
l.ry1 += dy;
l.lx2 += dx;
l.ly2 += dy;
break;
}
}
while (amountAtStart > 0 && subPath.size() > 0)
{
LineSection& l = subPath.getReference (0);
float dx = l.rx2 - l.rx1;
float dy = l.ry2 - l.ry1;
const float len = juce_hypot (dx, dy);
if (len <= amountAtStart && subPath.size() > 1)
{
LineSection& next = subPath.getReference (1);
next.x1 = l.x1;
next.y1 = l.y1;
subPath.remove (0);
amountAtStart -= len;
}
else
{
const float prop = jmin (0.9999f, amountAtStart / len);
dx *= prop;
dy *= prop;
l.rx2 -= dx;
l.ry2 -= dy;
l.lx1 -= dx;
l.ly1 -= dy;
break;
}
}
}
static void addSubPath (Path& destPath, Array<LineSection>& subPath,
const bool isClosed, const float width, const float maxMiterExtensionSquared,
const PathStrokeType::JointStyle jointStyle, const PathStrokeType::EndCapStyle endStyle,
const Arrowhead* const arrowhead)
{
jassert (subPath.size() > 0);
if (arrowhead != nullptr)
shortenSubPath (subPath, arrowhead->startLength, arrowhead->endLength);
const LineSection& firstLine = subPath.getReference (0);
float lastX1 = firstLine.lx1;
float lastY1 = firstLine.ly1;
float lastX2 = firstLine.lx2;
float lastY2 = firstLine.ly2;
if (isClosed)
{
destPath.startNewSubPath (lastX1, lastY1);
}
else
{
destPath.startNewSubPath (firstLine.rx2, firstLine.ry2);
if (arrowhead != nullptr)
addArrowhead (destPath, firstLine.rx2, firstLine.ry2, lastX1, lastY1, firstLine.x1, firstLine.y1,
width, arrowhead->startWidth);
else
addLineEnd (destPath, endStyle, firstLine.rx2, firstLine.ry2, lastX1, lastY1, width);
}
for (int i = 1; i < subPath.size(); ++i)
{
const LineSection& l = subPath.getReference (i);
addEdgeAndJoint (destPath, jointStyle,
maxMiterExtensionSquared, width,
lastX1, lastY1, lastX2, lastY2,
l.lx1, l.ly1, l.lx2, l.ly2,
l.x1, l.y1);
lastX1 = l.lx1;
lastY1 = l.ly1;
lastX2 = l.lx2;
lastY2 = l.ly2;
}
const LineSection& lastLine = subPath.getReference (subPath.size() - 1);
if (isClosed)
{
const LineSection& l = subPath.getReference (0);
addEdgeAndJoint (destPath, jointStyle,
maxMiterExtensionSquared, width,
lastX1, lastY1, lastX2, lastY2,
l.lx1, l.ly1, l.lx2, l.ly2,
l.x1, l.y1);
destPath.closeSubPath();
destPath.startNewSubPath (lastLine.rx1, lastLine.ry1);
}
else
{
destPath.lineTo (lastX2, lastY2);
if (arrowhead != nullptr)
addArrowhead (destPath, lastX2, lastY2, lastLine.rx1, lastLine.ry1, lastLine.x2, lastLine.y2,
width, arrowhead->endWidth);
else
addLineEnd (destPath, endStyle, lastX2, lastY2, lastLine.rx1, lastLine.ry1, width);
}
lastX1 = lastLine.rx1;
lastY1 = lastLine.ry1;
lastX2 = lastLine.rx2;
lastY2 = lastLine.ry2;
for (int i = subPath.size() - 1; --i >= 0;)
{
const LineSection& l = subPath.getReference (i);
addEdgeAndJoint (destPath, jointStyle,
maxMiterExtensionSquared, width,
lastX1, lastY1, lastX2, lastY2,
l.rx1, l.ry1, l.rx2, l.ry2,
l.x2, l.y2);
lastX1 = l.rx1;
lastY1 = l.ry1;
lastX2 = l.rx2;
lastY2 = l.ry2;
}
if (isClosed)
{
addEdgeAndJoint (destPath, jointStyle,
maxMiterExtensionSquared, width,
lastX1, lastY1, lastX2, lastY2,
lastLine.rx1, lastLine.ry1, lastLine.rx2, lastLine.ry2,
lastLine.x2, lastLine.y2);
}
else
{
// do the last line
destPath.lineTo (lastX2, lastY2);
}
destPath.closeSubPath();
}
static void createStroke (const float thickness, const PathStrokeType::JointStyle jointStyle,
const PathStrokeType::EndCapStyle endStyle,
Path& destPath, const Path& source,
const AffineTransform& transform,
const float extraAccuracy, const Arrowhead* const arrowhead)
{
jassert (extraAccuracy > 0);
if (thickness <= 0)
{
destPath.clear();
return;
}
const Path* sourcePath = &source;
Path temp;
if (sourcePath == &destPath)
{
destPath.swapWithPath (temp);
sourcePath = &temp;
}
else
{
destPath.clear();
}
destPath.setUsingNonZeroWinding (true);
const float maxMiterExtensionSquared = 9.0f * thickness * thickness;
const float width = 0.5f * thickness;
// Iterate the path, creating a list of the
// left/right-hand lines along either side of it...
PathFlatteningIterator it (*sourcePath, transform, PathFlatteningIterator::defaultTolerance / extraAccuracy);
Array <LineSection> subPath;
subPath.ensureStorageAllocated (512);
LineSection l;
l.x1 = 0;
l.y1 = 0;
const float minSegmentLength = 0.0001f;
while (it.next())
{
if (it.subPathIndex == 0)
{
if (subPath.size() > 0)
{
addSubPath (destPath, subPath, false, width, maxMiterExtensionSquared, jointStyle, endStyle, arrowhead);
subPath.clearQuick();
}
l.x1 = it.x1;
l.y1 = it.y1;
}
l.x2 = it.x2;
l.y2 = it.y2;
float dx = l.x2 - l.x1;
float dy = l.y2 - l.y1;
const float hypotSquared = dx*dx + dy*dy;
if (it.closesSubPath || hypotSquared > minSegmentLength || it.isLastInSubpath())
{
const float len = std::sqrt (hypotSquared);
if (len == 0)
{
l.rx1 = l.rx2 = l.lx1 = l.lx2 = l.x1;
l.ry1 = l.ry2 = l.ly1 = l.ly2 = l.y1;
}
else
{
const float offset = width / len;
dx *= offset;
dy *= offset;
l.rx2 = l.x1 - dy;
l.ry2 = l.y1 + dx;
l.lx1 = l.x1 + dy;
l.ly1 = l.y1 - dx;
l.lx2 = l.x2 + dy;
l.ly2 = l.y2 - dx;
l.rx1 = l.x2 - dy;
l.ry1 = l.y2 + dx;
}
subPath.add (l);
if (it.closesSubPath)
{
addSubPath (destPath, subPath, true, width, maxMiterExtensionSquared, jointStyle, endStyle, arrowhead);
subPath.clearQuick();
}
else
{
l.x1 = it.x2;
l.y1 = it.y2;
}
}
}
if (subPath.size() > 0)
addSubPath (destPath, subPath, false, width, maxMiterExtensionSquared, jointStyle, endStyle, arrowhead);
}
}
void PathStrokeType::createStrokedPath (Path& destPath, const Path& sourcePath,
const AffineTransform& transform, const float extraAccuracy) const
{
PathStrokeHelpers::createStroke (thickness, jointStyle, endStyle, destPath, sourcePath,
transform, extraAccuracy, 0);
}
void PathStrokeType::createDashedStroke (Path& destPath,
const Path& sourcePath,
const float* dashLengths,
int numDashLengths,
const AffineTransform& transform,
const float extraAccuracy) const
{
jassert (extraAccuracy > 0);
if (thickness <= 0)
return;
// this should really be an even number..
jassert ((numDashLengths & 1) == 0);
Path newDestPath;
PathFlatteningIterator it (sourcePath, transform, PathFlatteningIterator::defaultTolerance / extraAccuracy);
bool first = true;
int dashNum = 0;
float pos = 0.0f, lineLen = 0.0f, lineEndPos = 0.0f;
float dx = 0.0f, dy = 0.0f;
for (;;)
{
const bool isSolid = ((dashNum & 1) == 0);
const float dashLen = dashLengths [dashNum++ % numDashLengths];
jassert (dashLen > 0); // must be a positive increment!
if (dashLen <= 0)
break;
pos += dashLen;
while (pos > lineEndPos)
{
if (! it.next())
{
if (isSolid && ! first)
newDestPath.lineTo (it.x2, it.y2);
createStrokedPath (destPath, newDestPath, AffineTransform::identity, extraAccuracy);
return;
}
if (isSolid && ! first)
newDestPath.lineTo (it.x1, it.y1);
else
newDestPath.startNewSubPath (it.x1, it.y1);
dx = it.x2 - it.x1;
dy = it.y2 - it.y1;
lineLen = juce_hypot (dx, dy);
lineEndPos += lineLen;
first = it.closesSubPath;
}
const float alpha = (pos - (lineEndPos - lineLen)) / lineLen;
if (isSolid)
newDestPath.lineTo (it.x1 + dx * alpha,
it.y1 + dy * alpha);
else
newDestPath.startNewSubPath (it.x1 + dx * alpha,
it.y1 + dy * alpha);
}
}
void PathStrokeType::createStrokeWithArrowheads (Path& destPath,
const Path& sourcePath,
const float arrowheadStartWidth, const float arrowheadStartLength,
const float arrowheadEndWidth, const float arrowheadEndLength,
const AffineTransform& transform,
const float extraAccuracy) const
{
PathStrokeHelpers::Arrowhead head;
head.startWidth = arrowheadStartWidth;
head.startLength = arrowheadStartLength;
head.endWidth = arrowheadEndWidth;
head.endLength = arrowheadEndLength;
PathStrokeHelpers::createStroke (thickness, jointStyle, endStyle,
destPath, sourcePath, transform, extraAccuracy, &head);
}

View file

@ -0,0 +1,204 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2013 - Raw Material Software Ltd.
Permission is granted to use this software under the terms of either:
a) the GPL v2 (or any later version)
b) the Affero GPL v3
Details of these licenses can be found at: www.gnu.org/licenses
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
------------------------------------------------------------------------------
To release a closed-source product which uses JUCE, commercial licenses are
available: visit www.juce.com for more information.
==============================================================================
*/
#ifndef JUCE_PATHSTROKETYPE_H_INCLUDED
#define JUCE_PATHSTROKETYPE_H_INCLUDED
//==============================================================================
/**
Describes a type of stroke used to render a solid outline along a path.
A PathStrokeType object can be used directly to create the shape of an outline
around a path, and is used by Graphics::strokePath to specify the type of
stroke to draw.
@see Path, Graphics::strokePath
*/
class JUCE_API PathStrokeType
{
public:
//==============================================================================
/** The type of shape to use for the corners between two adjacent line segments. */
enum JointStyle
{
mitered, /**< Indicates that corners should be drawn with sharp joints.
Note that for angles that curve back on themselves, drawing a
mitre could require extending the point too far away from the
path, so a mitre limit is imposed and any corners that exceed it
are drawn as bevelled instead. */
curved, /**< Indicates that corners should be drawn as rounded-off. */
beveled /**< Indicates that corners should be drawn with a line flattening their
outside edge. */
};
/** The type shape to use for the ends of lines. */
enum EndCapStyle
{
butt, /**< Ends of lines are flat and don't extend beyond the end point. */
square, /**< Ends of lines are flat, but stick out beyond the end point for half
the thickness of the stroke. */
rounded /**< Ends of lines are rounded-off with a circular shape. */
};
//==============================================================================
/** Creates a stroke type with a given line-width, and default joint/end styles. */
explicit PathStrokeType (float strokeThickness) noexcept;
/** Creates a stroke type.
@param strokeThickness the width of the line to use
@param jointStyle the type of joints to use for corners
@param endStyle the type of end-caps to use for the ends of open paths.
*/
PathStrokeType (float strokeThickness,
JointStyle jointStyle,
EndCapStyle endStyle = butt) noexcept;
/** Creates a copy of another stroke type. */
PathStrokeType (const PathStrokeType&) noexcept;
/** Copies another stroke onto this one. */
PathStrokeType& operator= (const PathStrokeType&) noexcept;
/** Destructor. */
~PathStrokeType() noexcept;
//==============================================================================
/** Applies this stroke type to a path and returns the resultant stroke as another Path.
@param destPath the resultant stroked outline shape will be copied into this path.
Note that it's ok for the source and destination Paths to be
the same object, so you can easily turn a path into a stroked version
of itself.
@param sourcePath the path to use as the source
@param transform an optional transform to apply to the points from the source path
as they are being used
@param extraAccuracy if this is greater than 1.0, it will subdivide the path to
a higher resolution, which improves the quality if you'll later want
to enlarge the stroked path. So for example, if you're planning on drawing
the stroke at 3x the size that you're creating it, you should set this to 3.
@see createDashedStroke
*/
void createStrokedPath (Path& destPath,
const Path& sourcePath,
const AffineTransform& transform = AffineTransform::identity,
float extraAccuracy = 1.0f) const;
//==============================================================================
/** Applies this stroke type to a path, creating a dashed line.
This is similar to createStrokedPath, but uses the array passed in to
break the stroke up into a series of dashes.
@param destPath the resultant stroked outline shape will be copied into this path.
Note that it's ok for the source and destination Paths to be
the same object, so you can easily turn a path into a stroked version
of itself.
@param sourcePath the path to use as the source
@param dashLengths An array of alternating on/off lengths. E.g. { 2, 3, 4, 5 } will create
a line of length 2, then skip a length of 3, then add a line of length 4,
skip 5, and keep repeating this pattern.
@param numDashLengths The number of lengths in the dashLengths array. This should really be
an even number, otherwise the pattern will get out of step as it
repeats.
@param transform an optional transform to apply to the points from the source path
as they are being used
@param extraAccuracy if this is greater than 1.0, it will subdivide the path to
a higher resolution, which improves the quality if you'll later want
to enlarge the stroked path. So for example, if you're planning on drawing
the stroke at 3x the size that you're creating it, you should set this to 3.
*/
void createDashedStroke (Path& destPath,
const Path& sourcePath,
const float* dashLengths,
int numDashLengths,
const AffineTransform& transform = AffineTransform::identity,
float extraAccuracy = 1.0f) const;
//==============================================================================
/** Applies this stroke type to a path and returns the resultant stroke as another Path.
@param destPath the resultant stroked outline shape will be copied into this path.
Note that it's ok for the source and destination Paths to be
the same object, so you can easily turn a path into a stroked version
of itself.
@param sourcePath the path to use as the source
@param arrowheadStartWidth the width of the arrowhead at the start of the path
@param arrowheadStartLength the length of the arrowhead at the start of the path
@param arrowheadEndWidth the width of the arrowhead at the end of the path
@param arrowheadEndLength the length of the arrowhead at the end of the path
@param transform an optional transform to apply to the points from the source path
as they are being used
@param extraAccuracy if this is greater than 1.0, it will subdivide the path to
a higher resolution, which improves the quality if you'll later want
to enlarge the stroked path. So for example, if you're planning on drawing
the stroke at 3x the size that you're creating it, you should set this to 3.
@see createDashedStroke
*/
void createStrokeWithArrowheads (Path& destPath,
const Path& sourcePath,
float arrowheadStartWidth, float arrowheadStartLength,
float arrowheadEndWidth, float arrowheadEndLength,
const AffineTransform& transform = AffineTransform::identity,
float extraAccuracy = 1.0f) const;
//==============================================================================
/** Returns the stroke thickness. */
float getStrokeThickness() const noexcept { return thickness; }
/** Sets the stroke thickness. */
void setStrokeThickness (float newThickness) noexcept { thickness = newThickness; }
/** Returns the joint style. */
JointStyle getJointStyle() const noexcept { return jointStyle; }
/** Sets the joint style. */
void setJointStyle (JointStyle newStyle) noexcept { jointStyle = newStyle; }
/** Returns the end-cap style. */
EndCapStyle getEndStyle() const noexcept { return endStyle; }
/** Sets the end-cap style. */
void setEndStyle (EndCapStyle newStyle) noexcept { endStyle = newStyle; }
//==============================================================================
/** Compares the stroke thickness, joint and end styles of two stroke types. */
bool operator== (const PathStrokeType&) const noexcept;
/** Compares the stroke thickness, joint and end styles of two stroke types. */
bool operator!= (const PathStrokeType&) const noexcept;
private:
//==============================================================================
float thickness;
JointStyle jointStyle;
EndCapStyle endStyle;
JUCE_LEAK_DETECTOR (PathStrokeType)
};
#endif // JUCE_PATHSTROKETYPE_H_INCLUDED

View file

@ -0,0 +1,228 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2013 - Raw Material Software Ltd.
Permission is granted to use this software under the terms of either:
a) the GPL v2 (or any later version)
b) the Affero GPL v3
Details of these licenses can be found at: www.gnu.org/licenses
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
------------------------------------------------------------------------------
To release a closed-source product which uses JUCE, commercial licenses are
available: visit www.juce.com for more information.
==============================================================================
*/
#ifndef JUCE_POINT_H_INCLUDED
#define JUCE_POINT_H_INCLUDED
//==============================================================================
/**
A pair of (x, y) coordinates.
The ValueType template should be a primitive type such as int, float, double,
rather than a class.
@see Line, Path, AffineTransform
*/
template <typename ValueType>
class Point
{
public:
/** Creates a point at the origin */
Point() noexcept : x(), y() {}
/** Creates a copy of another point. */
Point (const Point& other) noexcept : x (other.x), y (other.y) {}
/** Creates a point from an (x, y) position. */
Point (ValueType initialX, ValueType initialY) noexcept : x (initialX), y (initialY) {}
//==============================================================================
/** Copies this point from another one. */
Point& operator= (const Point& other) noexcept { x = other.x; y = other.y; return *this; }
inline bool operator== (Point other) const noexcept { return x == other.x && y == other.y; }
inline bool operator!= (Point other) const noexcept { return x != other.x || y != other.y; }
/** Returns true if the point is (0, 0). */
bool isOrigin() const noexcept { return x == ValueType() && y == ValueType(); }
/** Returns the point's x coordinate. */
inline ValueType getX() const noexcept { return x; }
/** Returns the point's y coordinate. */
inline ValueType getY() const noexcept { return y; }
/** Sets the point's x coordinate. */
inline void setX (ValueType newX) noexcept { x = newX; }
/** Sets the point's y coordinate. */
inline void setY (ValueType newY) noexcept { y = newY; }
/** Returns a point which has the same Y position as this one, but a new X. */
Point withX (ValueType newX) const noexcept { return Point (newX, y); }
/** Returns a point which has the same X position as this one, but a new Y. */
Point withY (ValueType newY) const noexcept { return Point (x, newY); }
/** Changes the point's x and y coordinates. */
void setXY (ValueType newX, ValueType newY) noexcept { x = newX; y = newY; }
/** Adds a pair of coordinates to this value. */
void addXY (ValueType xToAdd, ValueType yToAdd) noexcept { x += xToAdd; y += yToAdd; }
//==============================================================================
/** Returns a point with a given offset from this one. */
Point translated (ValueType deltaX, ValueType deltaY) const noexcept { return Point (x + deltaX, y + deltaY); }
/** Adds two points together */
Point operator+ (Point other) const noexcept { return Point (x + other.x, y + other.y); }
/** Adds another point's coordinates to this one */
Point& operator+= (Point other) noexcept { x += other.x; y += other.y; return *this; }
/** Subtracts one points from another */
Point operator- (Point other) const noexcept { return Point (x - other.x, y - other.y); }
/** Subtracts another point's coordinates to this one */
Point& operator-= (Point other) noexcept { x -= other.x; y -= other.y; return *this; }
/** Multiplies two points together */
template <typename OtherType>
Point operator* (Point<OtherType> other) const noexcept { return Point ((ValueType) (x * other.x), (ValueType) (y * other.y)); }
/** Multiplies another point's coordinates to this one */
template <typename OtherType>
Point& operator*= (Point<OtherType> other) noexcept { *this = *this * other; return *this; }
/** Divides one point by another */
template <typename OtherType>
Point operator/ (Point<OtherType> other) const noexcept { return Point ((ValueType) (x / other.x), (ValueType) (y / other.y)); }
/** Divides this point's coordinates by another */
template <typename OtherType>
Point& operator/= (Point<OtherType> other) noexcept { *this = *this / other; return *this; }
/** Returns a point whose coordinates are multiplied by a given scalar value. */
template <typename FloatType>
Point operator* (FloatType multiplier) const noexcept { return Point ((ValueType) (x * multiplier), (ValueType) (y * multiplier)); }
/** Returns a point whose coordinates are divided by a given scalar value. */
template <typename FloatType>
Point operator/ (FloatType divisor) const noexcept { return Point ((ValueType) (x / divisor), (ValueType) (y / divisor)); }
/** Multiplies the point's coordinates by a scalar value. */
template <typename FloatType>
Point& operator*= (FloatType multiplier) noexcept { x = (ValueType) (x * multiplier); y = (ValueType) (y * multiplier); return *this; }
/** Divides the point's coordinates by a scalar value. */
template <typename FloatType>
Point& operator/= (FloatType divisor) noexcept { x = (ValueType) (x / divisor); y = (ValueType) (y / divisor); return *this; }
/** Returns the inverse of this point. */
Point operator-() const noexcept { return Point (-x, -y); }
//==============================================================================
/** This type will be double if the Point's type is double, otherwise it will be float. */
typedef typename TypeHelpers::SmallestFloatType<ValueType>::type FloatType;
//==============================================================================
/** Returns the straight-line distance between this point and the origin. */
ValueType getDistanceFromOrigin() const noexcept { return juce_hypot (x, y); }
/** Returns the straight-line distance between this point and another one. */
ValueType getDistanceFrom (Point other) const noexcept { return juce_hypot (x - other.x, y - other.y); }
/** Returns the angle from this point to another one.
The return value is the number of radians clockwise from the 12 o'clock direction,
where this point is the centre and the other point is on the circumference.
*/
FloatType getAngleToPoint (Point other) const noexcept
{
return static_cast<FloatType> (std::atan2 (static_cast<FloatType> (other.x - x),
static_cast<FloatType> (y - other.y)));
}
/** Returns the point that would be reached by rotating this point clockwise
about the origin by the specified angle.
*/
Point rotatedAboutOrigin (ValueType angleRadians) const noexcept
{
return Point (x * std::cos (angleRadians) - y * std::sin (angleRadians),
x * std::sin (angleRadians) + y * std::cos (angleRadians));
}
/** Taking this point to be the centre of a circle, this returns a point on its circumference.
@param radius the radius of the circle.
@param angle the angle of the point, in radians clockwise from the 12 o'clock position.
*/
Point<FloatType> getPointOnCircumference (float radius, float angle) const noexcept
{
return Point<FloatType> (static_cast<FloatType> (x + radius * std::sin (angle)),
static_cast<FloatType> (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<FloatType> getPointOnCircumference (float radiusX, float radiusY, float angle) const noexcept
{
return Point<FloatType> (static_cast<FloatType> (x + radiusX * std::sin (angle)),
static_cast<FloatType> (y - radiusY * std::cos (angle)));
}
/** Returns the dot-product of two points (x1 * x2 + y1 * y2). */
FloatType getDotProduct (Point other) const noexcept { return x * other.x + y * other.y; }
//==============================================================================
/** Uses a transform to change the point's coordinates.
This will only compile if ValueType = float!
@see AffineTransform::transformPoint
*/
void applyTransform (const AffineTransform& transform) noexcept { transform.transformPoint (x, y); }
/** Returns the position of this point, if it is transformed by a given AffineTransform. */
Point transformedBy (const AffineTransform& transform) const noexcept
{
return Point (static_cast<ValueType> (transform.mat00 * x + transform.mat01 * y + transform.mat02),
static_cast<ValueType> (transform.mat10 * x + transform.mat11 * y + transform.mat12));
}
//==============================================================================
/** Casts this point to a Point<int> object. */
Point<int> toInt() const noexcept { return Point<int> (static_cast<int> (x), static_cast<int> (y)); }
/** Casts this point to a Point<float> object. */
Point<float> toFloat() const noexcept { return Point<float> (static_cast<float> (x), static_cast<float> (y)); }
/** Casts this point to a Point<double> object. */
Point<double> toDouble() const noexcept { return Point<double> (static_cast<double> (x), static_cast<double> (y)); }
/** Casts this point to a Point<int> object using roundToInt() to convert the values. */
Point<int> roundToInt() const noexcept { return Point<int> (juce::roundToInt (x), juce::roundToInt (y)); }
/** Returns the point as a string in the form "x, y". */
String toString() const { return String (x) + ", " + String (y); }
//==============================================================================
ValueType x; /**< The point's X coordinate. */
ValueType y; /**< The point's Y coordinate. */
};
#endif // JUCE_POINT_H_INCLUDED

View file

@ -0,0 +1,917 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2013 - Raw Material Software Ltd.
Permission is granted to use this software under the terms of either:
a) the GPL v2 (or any later version)
b) the Affero GPL v3
Details of these licenses can be found at: www.gnu.org/licenses
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
------------------------------------------------------------------------------
To release a closed-source product which uses JUCE, commercial licenses are
available: visit www.juce.com for more information.
==============================================================================
*/
#ifndef JUCE_RECTANGLE_H_INCLUDED
#define JUCE_RECTANGLE_H_INCLUDED
//==============================================================================
/**
Manages a rectangle and allows geometric operations to be performed on it.
@see RectangleList, Path, Line, Point
*/
template <typename ValueType>
class Rectangle
{
public:
//==============================================================================
/** Creates a rectangle of zero size.
The default coordinates will be (0, 0, 0, 0).
*/
Rectangle() noexcept
: w(), h()
{
}
/** Creates a copy of another rectangle. */
Rectangle (const Rectangle& other) noexcept
: pos (other.pos), w (other.w), h (other.h)
{
}
/** Creates a rectangle with a given position and size. */
Rectangle (const ValueType initialX, const ValueType initialY,
const ValueType width, const ValueType height) noexcept
: pos (initialX, initialY),
w (width), h (height)
{
}
/** Creates a rectangle with a given size, and a position of (0, 0). */
Rectangle (const ValueType width, const ValueType height) noexcept
: w (width), h (height)
{
}
/** Creates a Rectangle from the positions of two opposite corners. */
Rectangle (const Point<ValueType> corner1, const Point<ValueType> corner2) noexcept
: pos (jmin (corner1.x, corner2.x),
jmin (corner1.y, corner2.y)),
w (corner1.x - corner2.x),
h (corner1.y - corner2.y)
{
if (w < ValueType()) w = -w;
if (h < ValueType()) h = -h;
}
/** Creates a Rectangle from a set of left, right, top, bottom coordinates.
The right and bottom values must be larger than the left and top ones, or the resulting
rectangle will have a negative size.
*/
static Rectangle leftTopRightBottom (const ValueType left, const ValueType top,
const ValueType right, const ValueType bottom) noexcept
{
return Rectangle (left, top, right - left, bottom - top);
}
Rectangle& operator= (const Rectangle& other) noexcept
{
pos = other.pos;
w = other.w; h = other.h;
return *this;
}
/** Destructor. */
~Rectangle() noexcept {}
//==============================================================================
/** Returns true if the rectangle's width or height are zero or less */
bool isEmpty() const noexcept { return w <= ValueType() || h <= ValueType(); }
/** Returns the x coordinate of the rectangle's left-hand-side. */
inline ValueType getX() const noexcept { return pos.x; }
/** Returns the y coordinate of the rectangle's top edge. */
inline ValueType getY() const noexcept { return pos.y; }
/** Returns the width of the rectangle. */
inline ValueType getWidth() const noexcept { return w; }
/** Returns the height of the rectangle. */
inline ValueType getHeight() const noexcept { return h; }
/** Returns the x coordinate of the rectangle's right-hand-side. */
inline ValueType getRight() const noexcept { return pos.x + w; }
/** Returns the y coordinate of the rectangle's bottom edge. */
inline ValueType getBottom() const noexcept { return pos.y + h; }
/** Returns the x coordinate of the rectangle's centre. */
ValueType getCentreX() const noexcept { return pos.x + w / (ValueType) 2; }
/** Returns the y coordinate of the rectangle's centre. */
ValueType getCentreY() const noexcept { return pos.y + h / (ValueType) 2; }
/** Returns the centre point of the rectangle. */
Point<ValueType> getCentre() const noexcept { return Point<ValueType> (pos.x + w / (ValueType) 2,
pos.y + h / (ValueType) 2); }
/** Returns the aspect ratio of the rectangle's width / height.
If widthOverHeight is true, it returns width / height; if widthOverHeight is false,
it returns height / width. */
ValueType getAspectRatio (const bool widthOverHeight = true) const noexcept { return widthOverHeight ? w / h : h / w; }
//==============================================================================
/** Returns the rectangle's top-left position as a Point. */
inline Point<ValueType> getPosition() const noexcept { return pos; }
/** Changes the position of the rectangle's top-left corner (leaving its size unchanged). */
inline void setPosition (const Point<ValueType> newPos) noexcept { pos = newPos; }
/** Changes the position of the rectangle's top-left corner (leaving its size unchanged). */
inline void setPosition (const ValueType newX, const ValueType newY) noexcept { pos.setXY (newX, newY); }
/** Returns the rectangle's top-left position as a Point. */
Point<ValueType> getTopLeft() const noexcept { return pos; }
/** Returns the rectangle's top-right position as a Point. */
Point<ValueType> getTopRight() const noexcept { return Point<ValueType> (pos.x + w, pos.y); }
/** Returns the rectangle's bottom-left position as a Point. */
Point<ValueType> getBottomLeft() const noexcept { return Point<ValueType> (pos.x, pos.y + h); }
/** Returns the rectangle's bottom-right position as a Point. */
Point<ValueType> getBottomRight() const noexcept { return Point<ValueType> (pos.x + w, pos.y + h); }
/** Changes the rectangle's size, leaving the position of its top-left corner unchanged. */
void setSize (const ValueType newWidth, const ValueType newHeight) noexcept { w = newWidth; h = newHeight; }
/** Changes all the rectangle's coordinates. */
void setBounds (const ValueType newX, const ValueType newY,
const ValueType newWidth, const ValueType newHeight) noexcept { pos.x = newX; pos.y = newY; w = newWidth; h = newHeight; }
/** Changes the rectangle's X coordinate */
inline void setX (const ValueType newX) noexcept { pos.x = newX; }
/** Changes the rectangle's Y coordinate */
inline void setY (const ValueType newY) noexcept { pos.y = newY; }
/** Changes the rectangle's width */
inline void setWidth (const ValueType newWidth) noexcept { w = newWidth; }
/** Changes the rectangle's height */
inline void setHeight (const ValueType newHeight) noexcept { h = newHeight; }
/** Changes the position of the rectangle's centre (leaving its size unchanged). */
inline void setCentre (const ValueType newCentreX, const ValueType newCentreY) noexcept { pos.x = newCentreX - w / (ValueType) 2; pos.y = newCentreY - h / (ValueType) 2; }
/** Changes the position of the rectangle's centre (leaving its size unchanged). */
inline void setCentre (const Point<ValueType> newCentre) noexcept { setCentre (newCentre.x, newCentre.y); }
/** Returns a rectangle which has the same size and y-position as this one, but with a different x-position. */
Rectangle withX (const ValueType newX) const noexcept { return Rectangle (newX, pos.y, w, h); }
/** Returns a rectangle which has the same size and x-position as this one, but with a different y-position. */
Rectangle withY (const ValueType newY) const noexcept { return Rectangle (pos.x, newY, w, h); }
/** Returns a rectangle with the same size as this one, but a new position. */
Rectangle withPosition (const ValueType newX, const ValueType newY) const noexcept { return Rectangle (newX, newY, w, h); }
/** Returns a rectangle with the same size as this one, but a new position. */
Rectangle withPosition (const Point<ValueType> newPos) const noexcept { return Rectangle (newPos.x, newPos.y, w, h); }
/** Returns a rectangle whose size is the same as this one, but whose top-left position is (0, 0). */
Rectangle withZeroOrigin() const noexcept { return Rectangle (w, h); }
/** Returns a rectangle with the same size as this one, but a new centre position. */
Rectangle withCentre (const Point<ValueType> newCentre) const noexcept { return Rectangle (newCentre.x - w / (ValueType) 2,
newCentre.y - h / (ValueType) 2, w, h); }
/** Returns a rectangle which has the same position and height as this one, but with a different width. */
Rectangle withWidth (ValueType newWidth) const noexcept { return Rectangle (pos.x, pos.y, newWidth, h); }
/** Returns a rectangle which has the same position and width as this one, but with a different height. */
Rectangle withHeight (ValueType newHeight) const noexcept { return Rectangle (pos.x, pos.y, w, newHeight); }
/** Returns a rectangle with the same top-left position as this one, but a new size. */
Rectangle withSize (ValueType newWidth, ValueType newHeight) const noexcept { return Rectangle (pos.x, pos.y, newWidth, newHeight); }
/** Returns a rectangle with the same centre position as this one, but a new size. */
Rectangle withSizeKeepingCentre (ValueType newWidth, ValueType newHeight) const noexcept { return Rectangle (pos.x + (w - newWidth) / (ValueType) 2,
pos.y + (h - newHeight) / (ValueType) 2, newWidth, newHeight); }
/** Moves the x position, adjusting the width so that the right-hand edge remains in the same place.
If the x is moved to be on the right of the current right-hand edge, the width will be set to zero.
@see withLeft
*/
void setLeft (ValueType newLeft) noexcept { w = jmax (ValueType(), pos.x + w - newLeft); pos.x = newLeft; }
/** Returns a new rectangle with a different x position, but the same right-hand edge as this one.
If the new x is beyond the right of the current right-hand edge, the width will be set to zero.
@see setLeft
*/
Rectangle withLeft (ValueType newLeft) const noexcept { return Rectangle (newLeft, pos.y, jmax (ValueType(), pos.x + w - newLeft), h); }
/** Moves the y position, adjusting the height so that the bottom edge remains in the same place.
If the y is moved to be below the current bottom edge, the height will be set to zero.
@see withTop
*/
void setTop (ValueType newTop) noexcept { h = jmax (ValueType(), pos.y + h - newTop); pos.y = newTop; }
/** Returns a new rectangle with a different y position, but the same bottom edge as this one.
If the new y is beyond the bottom of the current rectangle, the height will be set to zero.
@see setTop
*/
Rectangle withTop (ValueType newTop) const noexcept { return Rectangle (pos.x, newTop, w, jmax (ValueType(), pos.y + h - newTop)); }
/** Adjusts the width so that the right-hand edge of the rectangle has this new value.
If the new right is below the current X value, the X will be pushed down to match it.
@see getRight, withRight
*/
void setRight (ValueType newRight) noexcept { pos.x = jmin (pos.x, newRight); w = newRight - pos.x; }
/** Returns a new rectangle with a different right-hand edge position, but the same left-hand edge as this one.
If the new right edge is below the current left-hand edge, the width will be set to zero.
@see setRight
*/
Rectangle withRight (ValueType newRight) const noexcept { return Rectangle (jmin (pos.x, newRight), pos.y, jmax (ValueType(), newRight - pos.x), h); }
/** Adjusts the height so that the bottom edge of the rectangle has this new value.
If the new bottom is lower than the current Y value, the Y will be pushed down to match it.
@see getBottom, withBottom
*/
void setBottom (ValueType newBottom) noexcept { pos.y = jmin (pos.y, newBottom); h = newBottom - pos.y; }
/** Returns a new rectangle with a different bottom edge position, but the same top edge as this one.
If the new y is beyond the bottom of the current rectangle, the height will be set to zero.
@see setBottom
*/
Rectangle withBottom (ValueType newBottom) const noexcept { return Rectangle (pos.x, jmin (pos.y, newBottom), w, jmax (ValueType(), newBottom - pos.y)); }
/** Returns a version of this rectangle with the given amount removed from its left-hand edge. */
Rectangle withTrimmedLeft (ValueType amountToRemove) const noexcept { return withLeft (pos.x + amountToRemove); }
/** Returns a version of this rectangle with the given amount removed from its right-hand edge. */
Rectangle withTrimmedRight (ValueType amountToRemove) const noexcept { return withWidth (w - amountToRemove); }
/** Returns a version of this rectangle with the given amount removed from its top edge. */
Rectangle withTrimmedTop (ValueType amountToRemove) const noexcept { return withTop (pos.y + amountToRemove); }
/** Returns a version of this rectangle with the given amount removed from its bottom edge. */
Rectangle withTrimmedBottom (ValueType amountToRemove) const noexcept { return withHeight (h - amountToRemove); }
//==============================================================================
/** Moves the rectangle's position by adding amount to its x and y coordinates. */
void translate (const ValueType deltaX,
const ValueType deltaY) noexcept
{
pos.x += deltaX;
pos.y += deltaY;
}
/** Returns a rectangle which is the same as this one moved by a given amount. */
Rectangle translated (const ValueType deltaX,
const ValueType deltaY) const noexcept
{
return Rectangle (pos.x + deltaX, pos.y + deltaY, w, h);
}
/** Returns a rectangle which is the same as this one moved by a given amount. */
Rectangle operator+ (const Point<ValueType> deltaPosition) const noexcept
{
return Rectangle (pos.x + deltaPosition.x, pos.y + deltaPosition.y, w, h);
}
/** Moves this rectangle by a given amount. */
Rectangle& operator+= (const Point<ValueType> deltaPosition) noexcept
{
pos += deltaPosition;
return *this;
}
/** Returns a rectangle which is the same as this one moved by a given amount. */
Rectangle operator- (const Point<ValueType> deltaPosition) const noexcept
{
return Rectangle (pos.x - deltaPosition.x, pos.y - deltaPosition.y, w, h);
}
/** Moves this rectangle by a given amount. */
Rectangle& operator-= (const Point<ValueType> deltaPosition) noexcept
{
pos -= deltaPosition;
return *this;
}
/** Returns a rectangle that has been scaled by the given amount, centred around the origin.
Note that if the rectangle has int coordinates and it's scaled by a
floating-point amount, then the result will be converted back to integer
coordinates using getSmallestIntegerContainer().
*/
template <typename FloatType>
Rectangle operator* (FloatType scaleFactor) const noexcept
{
Rectangle r (*this);
r *= scaleFactor;
return r;
}
/** Scales this rectangle by the given amount, centred around the origin.
Note that if the rectangle has int coordinates and it's scaled by a
floating-point amount, then the result will be converted back to integer
coordinates using getSmallestIntegerContainer().
*/
template <typename FloatType>
Rectangle operator*= (FloatType scaleFactor) noexcept
{
Rectangle<FloatType> (pos.x * scaleFactor,
pos.y * scaleFactor,
w * scaleFactor,
h * scaleFactor).copyWithRounding (*this);
return *this;
}
/** Scales this rectangle by the given X and Y factors, centred around the origin.
Note that if the rectangle has int coordinates and it's scaled by a
floating-point amount, then the result will be converted back to integer
coordinates using getSmallestIntegerContainer().
*/
template <typename FloatType>
Rectangle operator*= (Point<FloatType> scaleFactor) noexcept
{
Rectangle<FloatType> (pos.x * scaleFactor.x,
pos.y * scaleFactor.y,
w * scaleFactor.x,
h * scaleFactor.y).copyWithRounding (*this);
return *this;
}
/** Scales this rectangle by the given amount, centred around the origin. */
template <typename FloatType>
Rectangle operator/ (FloatType scaleFactor) const noexcept
{
Rectangle r (*this);
r /= scaleFactor;
return r;
}
/** Scales this rectangle by the given amount, centred around the origin. */
template <typename FloatType>
Rectangle operator/= (FloatType scaleFactor) noexcept
{
Rectangle<FloatType> (pos.x / scaleFactor,
pos.y / scaleFactor,
w / scaleFactor,
h / scaleFactor).copyWithRounding (*this);
return *this;
}
/** Scales this rectangle by the given X and Y factors, centred around the origin. */
template <typename FloatType>
Rectangle operator/= (Point<FloatType> scaleFactor) noexcept
{
Rectangle<FloatType> (pos.x / scaleFactor.x,
pos.y / scaleFactor.y,
w / scaleFactor.x,
h / scaleFactor.y).copyWithRounding (*this);
return *this;
}
/** Expands the rectangle by a given amount.
Effectively, its new size is (x - deltaX, y - deltaY, w + deltaX * 2, h + deltaY * 2).
@see expanded, reduce, reduced
*/
void expand (const ValueType deltaX,
const ValueType deltaY) noexcept
{
const ValueType nw = jmax (ValueType(), w + deltaX * 2);
const ValueType nh = jmax (ValueType(), h + deltaY * 2);
setBounds (pos.x - deltaX, pos.y - deltaY, nw, nh);
}
/** Returns a rectangle that is larger than this one by a given amount.
Effectively, the rectangle returned is (x - deltaX, y - deltaY, w + deltaX * 2, h + deltaY * 2).
@see expand, reduce, reduced
*/
Rectangle expanded (const ValueType deltaX,
const ValueType deltaY) const noexcept
{
const ValueType nw = jmax (ValueType(), w + deltaX * 2);
const ValueType nh = jmax (ValueType(), h + deltaY * 2);
return Rectangle (pos.x - deltaX, pos.y - deltaY, nw, nh);
}
/** Returns a rectangle that is larger than this one by a given amount.
Effectively, the rectangle returned is (x - delta, y - delta, w + delta * 2, h + delta * 2).
@see expand, reduce, reduced
*/
Rectangle expanded (const ValueType delta) const noexcept
{
return expanded (delta, delta);
}
/** Shrinks the rectangle by a given amount.
Effectively, its new size is (x + deltaX, y + deltaY, w - deltaX * 2, h - deltaY * 2).
@see reduced, expand, expanded
*/
void reduce (const ValueType deltaX,
const ValueType deltaY) noexcept
{
expand (-deltaX, -deltaY);
}
/** Returns a rectangle that is smaller than this one by a given amount.
Effectively, the rectangle returned is (x + deltaX, y + deltaY, w - deltaX * 2, h - deltaY * 2).
@see reduce, expand, expanded
*/
Rectangle reduced (const ValueType deltaX,
const ValueType deltaY) const noexcept
{
return expanded (-deltaX, -deltaY);
}
/** Returns a rectangle that is smaller than this one by a given amount.
Effectively, the rectangle returned is (x + delta, y + delta, w - delta * 2, h - delta * 2).
@see reduce, expand, expanded
*/
Rectangle reduced (const ValueType delta) const noexcept
{
return reduced (delta, delta);
}
/** Removes a strip from the top of this rectangle, reducing this rectangle
by the specified amount and returning the section that was removed.
E.g. if this rectangle is (100, 100, 300, 300) and amountToRemove is 50, this will
return (100, 100, 300, 50) and leave this rectangle as (100, 150, 300, 250).
If amountToRemove is greater than the height of this rectangle, it'll be clipped to
that value.
*/
Rectangle removeFromTop (const ValueType amountToRemove) noexcept
{
const Rectangle r (pos.x, pos.y, w, jmin (amountToRemove, h));
pos.y += r.h; h -= r.h;
return r;
}
/** Removes a strip from the left-hand edge of this rectangle, reducing this rectangle
by the specified amount and returning the section that was removed.
E.g. if this rectangle is (100, 100, 300, 300) and amountToRemove is 50, this will
return (100, 100, 50, 300) and leave this rectangle as (150, 100, 250, 300).
If amountToRemove is greater than the width of this rectangle, it'll be clipped to
that value.
*/
Rectangle removeFromLeft (const ValueType amountToRemove) noexcept
{
const Rectangle r (pos.x, pos.y, jmin (amountToRemove, w), h);
pos.x += r.w; w -= r.w;
return r;
}
/** Removes a strip from the right-hand edge of this rectangle, reducing this rectangle
by the specified amount and returning the section that was removed.
E.g. if this rectangle is (100, 100, 300, 300) and amountToRemove is 50, this will
return (250, 100, 50, 300) and leave this rectangle as (100, 100, 250, 300).
If amountToRemove is greater than the width of this rectangle, it'll be clipped to
that value.
*/
Rectangle removeFromRight (ValueType amountToRemove) noexcept
{
amountToRemove = jmin (amountToRemove, w);
const Rectangle r (pos.x + w - amountToRemove, pos.y, amountToRemove, h);
w -= amountToRemove;
return r;
}
/** Removes a strip from the bottom of this rectangle, reducing this rectangle
by the specified amount and returning the section that was removed.
E.g. if this rectangle is (100, 100, 300, 300) and amountToRemove is 50, this will
return (100, 250, 300, 50) and leave this rectangle as (100, 100, 300, 250).
If amountToRemove is greater than the height of this rectangle, it'll be clipped to
that value.
*/
Rectangle removeFromBottom (ValueType amountToRemove) noexcept
{
amountToRemove = jmin (amountToRemove, h);
const Rectangle r (pos.x, pos.y + h - amountToRemove, w, amountToRemove);
h -= amountToRemove;
return r;
}
//==============================================================================
/** Returns true if the two rectangles are identical. */
bool operator== (const Rectangle& other) const noexcept { return pos == other.pos && w == other.w && h == other.h; }
/** Returns true if the two rectangles are not identical. */
bool operator!= (const Rectangle& other) const noexcept { return pos != other.pos || w != other.w || h != other.h; }
/** Returns true if this coordinate is inside the rectangle. */
bool contains (const ValueType xCoord, const ValueType yCoord) const noexcept
{
return xCoord >= pos.x && yCoord >= pos.y && xCoord < pos.x + w && yCoord < pos.y + h;
}
/** Returns true if this coordinate is inside the rectangle. */
bool contains (const Point<ValueType> point) const noexcept
{
return point.x >= pos.x && point.y >= pos.y && point.x < pos.x + w && point.y < pos.y + h;
}
/** Returns true if this other rectangle is completely inside this one. */
bool contains (const Rectangle& other) const noexcept
{
return pos.x <= other.pos.x && pos.y <= other.pos.y
&& pos.x + w >= other.pos.x + other.w && pos.y + h >= other.pos.y + other.h;
}
/** Returns the nearest point to the specified point that lies within this rectangle. */
Point<ValueType> getConstrainedPoint (const Point<ValueType> point) const noexcept
{
return Point<ValueType> (jlimit (pos.x, pos.x + w, point.x),
jlimit (pos.y, pos.y + h, point.y));
}
/** Returns a point within this rectangle, specified as proportional coordinates.
The relative X and Y values should be between 0 and 1, where 0 is the left or
top of this rectangle, and 1 is the right or bottom. (Out-of-bounds values
will return a point outside the rectangle).
*/
Point<ValueType> getRelativePoint (double relativeX, double relativeY) const noexcept
{
return Point<ValueType> (pos.x + static_cast <ValueType> (w * relativeX),
pos.y + static_cast <ValueType> (h * relativeY));
}
/** Returns true if any part of another rectangle overlaps this one. */
bool intersects (const Rectangle& other) const noexcept
{
return pos.x + w > other.pos.x
&& pos.y + h > other.pos.y
&& pos.x < other.pos.x + other.w
&& pos.y < other.pos.y + other.h
&& w > ValueType() && h > ValueType()
&& other.w > ValueType() && other.h > ValueType();
}
/** Returns true if any part of the given line lies inside this rectangle. */
bool intersects (const Line<ValueType>& line) const noexcept
{
return contains (line.getStart()) || contains (line.getEnd())
|| line.intersects (Line<ValueType> (getTopLeft(), getTopRight()))
|| line.intersects (Line<ValueType> (getTopRight(), getBottomRight()))
|| line.intersects (Line<ValueType> (getBottomRight(), getBottomLeft()))
|| line.intersects (Line<ValueType> (getBottomLeft(), getTopLeft()));
}
/** Returns the region that is the overlap between this and another rectangle.
If the two rectangles don't overlap, the rectangle returned will be empty.
*/
Rectangle getIntersection (const Rectangle& other) const noexcept
{
const ValueType nx = jmax (pos.x, other.pos.x);
const ValueType ny = jmax (pos.y, other.pos.y);
const ValueType nw = jmin (pos.x + w, other.pos.x + other.w) - nx;
const ValueType nh = jmin (pos.y + h, other.pos.y + other.h) - ny;
if (nw >= ValueType() && nh >= ValueType())
return Rectangle (nx, ny, nw, nh);
return Rectangle();
}
/** Clips a set of rectangle coordinates so that they lie only within this one.
This is a non-static version of intersectRectangles().
Returns false if the two rectangles didn't overlap.
*/
bool intersectRectangle (ValueType& otherX, ValueType& otherY, ValueType& otherW, ValueType& otherH) const noexcept
{
const ValueType maxX (jmax (otherX, pos.x));
otherW = jmin (otherX + otherW, pos.x + w) - maxX;
if (otherW > ValueType())
{
const ValueType maxY (jmax (otherY, pos.y));
otherH = jmin (otherY + otherH, pos.y + h) - maxY;
if (otherH > ValueType())
{
otherX = maxX; otherY = maxY;
return true;
}
}
return false;
}
/** Clips a rectangle so that it lies only within this one.
Returns false if the two rectangles didn't overlap.
*/
bool intersectRectangle (Rectangle<ValueType>& rectangleToClip) const noexcept
{
return intersectRectangle (rectangleToClip.pos.x, rectangleToClip.pos.y,
rectangleToClip.w, rectangleToClip.h);
}
/** Returns the smallest rectangle that contains both this one and the one passed-in.
If either this or the other rectangle are empty, they will not be counted as
part of the resulting region.
*/
Rectangle getUnion (const Rectangle& other) const noexcept
{
if (other.isEmpty()) return *this;
if (isEmpty()) return other;
const ValueType newX = jmin (pos.x, other.pos.x);
const ValueType newY = jmin (pos.y, other.pos.y);
return Rectangle (newX, newY,
jmax (pos.x + w, other.pos.x + other.w) - newX,
jmax (pos.y + h, other.pos.y + other.h) - newY);
}
/** If this rectangle merged with another one results in a simple rectangle, this
will set this rectangle to the result, and return true.
Returns false and does nothing to this rectangle if the two rectangles don't overlap,
or if they form a complex region.
*/
bool enlargeIfAdjacent (const Rectangle& other) noexcept
{
if (pos.x == other.pos.x && getRight() == other.getRight()
&& (other.getBottom() >= pos.y && other.pos.y <= getBottom()))
{
const ValueType newY = jmin (pos.y, other.pos.y);
h = jmax (getBottom(), other.getBottom()) - newY;
pos.y = newY;
return true;
}
if (pos.y == other.pos.y && getBottom() == other.getBottom()
&& (other.getRight() >= pos.x && other.pos.x <= getRight()))
{
const ValueType newX = jmin (pos.x, other.pos.x);
w = jmax (getRight(), other.getRight()) - newX;
pos.x = newX;
return true;
}
return false;
}
/** If after removing another rectangle from this one the result is a simple rectangle,
this will set this object's bounds to be the result, and return true.
Returns false and does nothing to this rectangle if the two rectangles don't overlap,
or if removing the other one would form a complex region.
*/
bool reduceIfPartlyContainedIn (const Rectangle& other) noexcept
{
int inside = 0;
const ValueType otherR (other.getRight());
if (pos.x >= other.pos.x && pos.x < otherR) inside = 1;
const ValueType otherB (other.getBottom());
if (pos.y >= other.pos.y && pos.y < otherB) inside |= 2;
const ValueType r (pos.x + w);
if (r >= other.pos.x && r < otherR) inside |= 4;
const ValueType b (pos.y + h);
if (b >= other.pos.y && b < otherB) inside |= 8;
switch (inside)
{
case 1 + 2 + 8: w = r - otherR; pos.x = otherR; return true;
case 1 + 2 + 4: h = b - otherB; pos.y = otherB; return true;
case 2 + 4 + 8: w = other.pos.x - pos.x; return true;
case 1 + 4 + 8: h = other.pos.y - pos.y; return true;
}
return false;
}
/** Tries to fit this rectangle within a target area, returning the result.
If this rectangle is not completely inside the target area, then it'll be
shifted (without changing its size) so that it lies within the target. If it
is larger than the target rectangle in either dimension, then that dimension
will be reduced to fit within the target.
*/
Rectangle constrainedWithin (const Rectangle& areaToFitWithin) const noexcept
{
const ValueType newW (jmin (w, areaToFitWithin.getWidth()));
const ValueType newH (jmin (h, areaToFitWithin.getHeight()));
return Rectangle (jlimit (areaToFitWithin.getX(), areaToFitWithin.getRight() - newW, pos.x),
jlimit (areaToFitWithin.getY(), areaToFitWithin.getBottom() - newH, pos.y),
newW, newH);
}
/** Returns the smallest rectangle that can contain the shape created by applying
a transform to this rectangle.
This should only be used on floating point rectangles.
*/
Rectangle transformedBy (const AffineTransform& transform) const noexcept
{
typedef typename TypeHelpers::SmallestFloatType<ValueType>::type FloatType;
FloatType x1 = static_cast<FloatType> (pos.x), y1 = static_cast<FloatType> (pos.y);
FloatType x2 = static_cast<FloatType> (pos.x + w), y2 = static_cast<FloatType> (pos.y);
FloatType x3 = static_cast<FloatType> (pos.x), y3 = static_cast<FloatType> (pos.y + h);
FloatType x4 = static_cast<FloatType> (x2), y4 = static_cast<FloatType> (y3);
transform.transformPoints (x1, y1, x2, y2);
transform.transformPoints (x3, y3, x4, y4);
const FloatType rx1 = jmin (x1, x2, x3, x4);
const FloatType rx2 = jmax (x1, x2, x3, x4);
const FloatType ry1 = jmin (y1, y2, y3, y4);
const FloatType ry2 = jmax (y1, y2, y3, y4);
Rectangle r;
Rectangle<FloatType> (rx1, ry1, rx2 - rx1, ry2 - ry1).copyWithRounding (r);
return r;
}
/** Returns the smallest integer-aligned rectangle that completely contains this one.
This is only relevent for floating-point rectangles, of course.
@see toFloat()
*/
Rectangle<int> getSmallestIntegerContainer() const noexcept
{
const int x1 = floorAsInt (pos.x);
const int y1 = floorAsInt (pos.y);
const int x2 = ceilAsInt (pos.x + w);
const int y2 = ceilAsInt (pos.y + h);
return Rectangle<int> (x1, y1, x2 - x1, y2 - y1);
}
/** Casts this rectangle to a Rectangle<float>.
@see getSmallestIntegerContainer
*/
Rectangle<float> toFloat() const noexcept
{
return Rectangle<float> (static_cast<float> (pos.x), static_cast<float> (pos.y),
static_cast<float> (w), static_cast<float> (h));
}
/** Casts this rectangle to a Rectangle<double>.
@see getSmallestIntegerContainer
*/
Rectangle<double> toDouble() const noexcept
{
return Rectangle<double> (static_cast<double> (pos.x), static_cast<double> (pos.y),
static_cast<double> (w), static_cast<double> (h));
}
/** Casts this rectangle to a Rectangle with the given type.
If the target type is a conversion from float to int, then the conversion
will be done using getSmallestIntegerContainer().
*/
template <typename TargetType>
Rectangle<TargetType> toType() const noexcept
{
Rectangle<TargetType> r;
copyWithRounding (r);
return r;
}
/** Returns the smallest Rectangle that can contain a set of points. */
static Rectangle findAreaContainingPoints (const Point<ValueType>* const points, const int numPoints) noexcept
{
if (numPoints == 0)
return Rectangle();
ValueType minX (points[0].x);
ValueType maxX (minX);
ValueType minY (points[0].y);
ValueType maxY (minY);
for (int i = 1; i < numPoints; ++i)
{
minX = jmin (minX, points[i].x);
maxX = jmax (maxX, points[i].x);
minY = jmin (minY, points[i].y);
maxY = jmax (maxY, points[i].y);
}
return Rectangle (minX, minY, maxX - minX, maxY - minY);
}
//==============================================================================
/** Static utility to intersect two sets of rectangular coordinates.
Returns false if the two regions didn't overlap.
@see intersectRectangle
*/
static bool intersectRectangles (ValueType& x1, ValueType& y1, ValueType& w1, ValueType& h1,
const ValueType x2, const ValueType y2, const ValueType w2, const ValueType h2) noexcept
{
const ValueType x (jmax (x1, x2));
w1 = jmin (x1 + w1, x2 + w2) - x;
if (w1 > ValueType())
{
const ValueType y (jmax (y1, y2));
h1 = jmin (y1 + h1, y2 + h2) - y;
if (h1 > ValueType())
{
x1 = x; y1 = y;
return true;
}
}
return false;
}
//==============================================================================
/** Creates a string describing this rectangle.
The string will be of the form "x y width height", e.g. "100 100 400 200".
Coupled with the fromString() method, this is very handy for things like
storing rectangles (particularly component positions) in XML attributes.
@see fromString
*/
String toString() const
{
String s;
s.preallocateBytes (32);
s << pos.x << ' ' << pos.y << ' ' << w << ' ' << h;
return s;
}
/** Parses a string containing a rectangle's details.
The string should contain 4 integer tokens, in the form "x y width height". They
can be comma or whitespace separated.
This method is intended to go with the toString() method, to form an easy way
of saving/loading rectangles as strings.
@see toString
*/
static Rectangle fromString (StringRef stringVersion)
{
StringArray toks;
toks.addTokens (stringVersion.text.findEndOfWhitespace(), ",; \t\r\n", "");
return Rectangle (parseIntAfterSpace (toks[0]),
parseIntAfterSpace (toks[1]),
parseIntAfterSpace (toks[2]),
parseIntAfterSpace (toks[3]));
}
#ifndef DOXYGEN
// This has been renamed by transformedBy, in order to match the method names used in the Point class.
JUCE_DEPRECATED_WITH_BODY (Rectangle transformed (const AffineTransform& t) const noexcept, { return transformedBy (t); })
#endif
private:
template <typename OtherType> friend class Rectangle;
Point<ValueType> pos;
ValueType w, h;
static int parseIntAfterSpace (StringRef s) noexcept
{ return s.text.findEndOfWhitespace().getIntValue32(); }
void copyWithRounding (Rectangle<int>& result) const noexcept { result = getSmallestIntegerContainer(); }
void copyWithRounding (Rectangle<float>& result) const noexcept { result = toFloat(); }
void copyWithRounding (Rectangle<double>& result) const noexcept { result = toDouble(); }
static int floorAsInt (int n) noexcept { return n; }
static int floorAsInt (float n) noexcept { return (int) std::floor (n); }
static int floorAsInt (double n) noexcept { return (int) std::floor (n); }
static int ceilAsInt (int n) noexcept { return n; }
static int ceilAsInt (float n) noexcept { return (int) std::ceil (n); }
static int ceilAsInt (double n) noexcept { return (int) std::ceil (n); }
};
#endif // JUCE_RECTANGLE_H_INCLUDED

View file

@ -0,0 +1,648 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2013 - Raw Material Software Ltd.
Permission is granted to use this software under the terms of either:
a) the GPL v2 (or any later version)
b) the Affero GPL v3
Details of these licenses can be found at: www.gnu.org/licenses
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
------------------------------------------------------------------------------
To release a closed-source product which uses JUCE, commercial licenses are
available: visit www.juce.com for more information.
==============================================================================
*/
#ifndef JUCE_RECTANGLELIST_H_INCLUDED
#define JUCE_RECTANGLELIST_H_INCLUDED
//==============================================================================
/**
Maintains a set of rectangles as a complex region.
This class allows a set of rectangles to be treated as a solid shape, and can
add and remove rectangular sections of it, and simplify overlapping or
adjacent rectangles.
@see Rectangle
*/
template <typename ValueType>
class RectangleList
{
public:
typedef Rectangle<ValueType> RectangleType;
//==============================================================================
/** Creates an empty RectangleList */
RectangleList() noexcept {}
/** Creates a copy of another list */
RectangleList (const RectangleList& other) : rects (other.rects)
{
}
/** Creates a list containing just one rectangle. */
RectangleList (const RectangleType& rect)
{
addWithoutMerging (rect);
}
/** Copies this list from another one. */
RectangleList& operator= (const RectangleList& other)
{
rects = other.rects;
return *this;
}
#if JUCE_COMPILER_SUPPORTS_MOVE_SEMANTICS
RectangleList (RectangleList&& other) noexcept
: rects (static_cast<Array<RectangleType>&&> (other.rects))
{
}
RectangleList& operator= (RectangleList&& other) noexcept
{
rects = static_cast<Array<RectangleType>&&> (other.rects);
return *this;
}
#endif
//==============================================================================
/** Returns true if the region is empty. */
bool isEmpty() const noexcept { return rects.size() == 0; }
/** Returns the number of rectangles in the list. */
int getNumRectangles() const noexcept { return rects.size(); }
/** Returns one of the rectangles at a particular index.
@returns the rectangle at the index, or an empty rectangle if the index is out-of-range.
*/
RectangleType getRectangle (int index) const noexcept { return rects[index]; }
//==============================================================================
/** Removes all rectangles to leave an empty region. */
void clear()
{
rects.clearQuick();
}
/** Merges a new rectangle into the list.
The rectangle being added will first be clipped to remove any parts of it
that overlap existing rectangles in the list, and adjacent rectangles will be
merged into it.
*/
void add (const RectangleType& rect)
{
if (! rect.isEmpty())
{
if (rects.size() == 0)
{
rects.add (rect);
}
else
{
bool anyOverlaps = false;
for (int j = rects.size(); --j >= 0;)
{
RectangleType& ourRect = rects.getReference (j);
if (rect.intersects (ourRect))
{
if (rect.contains (ourRect))
rects.remove (j);
else if (! ourRect.reduceIfPartlyContainedIn (rect))
anyOverlaps = true;
}
}
if (anyOverlaps && rects.size() > 0)
{
RectangleList r (rect);
for (int i = rects.size(); --i >= 0;)
{
const RectangleType& ourRect = rects.getReference (i);
if (rect.intersects (ourRect))
{
r.subtract (ourRect);
if (r.rects.size() == 0)
return;
}
}
rects.addArray (r.rects);
}
else
{
rects.add (rect);
}
}
}
}
/** Merges a new rectangle into the list.
The rectangle being added will first be clipped to remove any parts of it
that overlap existing rectangles in the list.
*/
void add (ValueType x, ValueType y, ValueType width, ValueType height)
{
add (RectangleType (x, y, width, height));
}
/** Dumbly adds a rectangle to the list without checking for overlaps.
This simply adds the rectangle to the end, it doesn't merge it or remove
any overlapping bits.
*/
void addWithoutMerging (const RectangleType& rect)
{
if (! rect.isEmpty())
rects.add (rect);
}
/** Merges another rectangle list into this one.
Any overlaps between the two lists will be clipped, so that the result is
the union of both lists.
*/
void add (const RectangleList& other)
{
for (const RectangleType* r = other.begin(), * const e = other.end(); r != e; ++r)
add (*r);
}
/** Removes a rectangular region from the list.
Any rectangles in the list which overlap this will be clipped and subdivided
if necessary.
*/
void subtract (const RectangleType& rect)
{
const int originalNumRects = rects.size();
if (originalNumRects > 0)
{
const ValueType x1 = rect.getX();
const ValueType y1 = rect.getY();
const ValueType x2 = x1 + rect.getWidth();
const ValueType y2 = y1 + rect.getHeight();
for (int i = getNumRectangles(); --i >= 0;)
{
RectangleType& r = rects.getReference (i);
const ValueType rx1 = r.getX();
const ValueType ry1 = r.getY();
const ValueType rx2 = rx1 + r.getWidth();
const ValueType ry2 = ry1 + r.getHeight();
if (! (x2 <= rx1 || x1 >= rx2 || y2 <= ry1 || y1 >= ry2))
{
if (x1 > rx1 && x1 < rx2)
{
if (y1 <= ry1 && y2 >= ry2 && x2 >= rx2)
{
r.setWidth (x1 - rx1);
}
else
{
r.setX (x1);
r.setWidth (rx2 - x1);
rects.insert (++i, RectangleType (rx1, ry1, x1 - rx1, ry2 - ry1));
++i;
}
}
else if (x2 > rx1 && x2 < rx2)
{
r.setX (x2);
r.setWidth (rx2 - x2);
if (y1 > ry1 || y2 < ry2 || x1 > rx1)
{
rects.insert (++i, RectangleType (rx1, ry1, x2 - rx1, ry2 - ry1));
++i;
}
}
else if (y1 > ry1 && y1 < ry2)
{
if (x1 <= rx1 && x2 >= rx2 && y2 >= ry2)
{
r.setHeight (y1 - ry1);
}
else
{
r.setY (y1);
r.setHeight (ry2 - y1);
rects.insert (++i, RectangleType (rx1, ry1, rx2 - rx1, y1 - ry1));
++i;
}
}
else if (y2 > ry1 && y2 < ry2)
{
r.setY (y2);
r.setHeight (ry2 - y2);
if (x1 > rx1 || x2 < rx2 || y1 > ry1)
{
rects.insert (++i, RectangleType (rx1, ry1, rx2 - rx1, y2 - ry1));
++i;
}
}
else
{
rects.remove (i);
}
}
}
}
}
/** Removes all areas in another RectangleList from this one.
Any rectangles in the list which overlap this will be clipped and subdivided
if necessary.
@returns true if the resulting list is non-empty.
*/
bool subtract (const RectangleList& otherList)
{
for (int i = otherList.rects.size(); --i >= 0 && rects.size() > 0;)
subtract (otherList.rects.getReference (i));
return rects.size() > 0;
}
/** Removes any areas of the region that lie outside a given rectangle.
Any rectangles in the list which overlap this will be clipped and subdivided
if necessary.
Returns true if the resulting region is not empty, false if it is empty.
@see getIntersectionWith
*/
bool clipTo (const RectangleType& rect)
{
bool notEmpty = false;
if (rect.isEmpty())
{
clear();
}
else
{
for (int i = rects.size(); --i >= 0;)
{
RectangleType& r = rects.getReference (i);
if (! rect.intersectRectangle (r))
rects.remove (i);
else
notEmpty = true;
}
}
return notEmpty;
}
/** Removes any areas of the region that lie outside a given rectangle list.
Any rectangles in this object which overlap the specified list will be clipped
and subdivided if necessary.
Returns true if the resulting region is not empty, false if it is empty.
@see getIntersectionWith
*/
template <typename OtherValueType>
bool clipTo (const RectangleList<OtherValueType>& other)
{
if (rects.size() == 0)
return false;
RectangleList result;
for (int j = 0; j < rects.size(); ++j)
{
const RectangleType& rect = rects.getReference (j);
for (const Rectangle<OtherValueType>* r = other.begin(), * const e = other.end(); r != e; ++r)
{
RectangleType clipped (r->template toType<ValueType>());
if (rect.intersectRectangle (clipped))
result.rects.add (clipped);
}
}
swapWith (result);
return ! isEmpty();
}
/** Creates a region which is the result of clipping this one to a given rectangle.
Unlike the other clipTo method, this one doesn't affect this object - it puts the
resulting region into the list whose reference is passed-in.
Returns true if the resulting region is not empty, false if it is empty.
@see clipTo
*/
bool getIntersectionWith (const RectangleType& rect, RectangleList& destRegion) const
{
destRegion.clear();
if (! rect.isEmpty())
{
for (int i = rects.size(); --i >= 0;)
{
RectangleType r (rects.getReference (i));
if (rect.intersectRectangle (r))
destRegion.rects.add (r);
}
}
return destRegion.rects.size() > 0;
}
/** Swaps the contents of this and another list.
This swaps their internal pointers, so is hugely faster than using copy-by-value
to swap them.
*/
void swapWith (RectangleList& otherList) noexcept
{
rects.swapWith (otherList.rects);
}
//==============================================================================
/** Checks whether the region contains a given point.
@returns true if the point lies within one of the rectangles in the list
*/
bool containsPoint (Point<ValueType> point) const noexcept
{
for (const RectangleType* r = rects.begin(), * const e = rects.end(); r != e; ++r)
if (r->contains (point))
return true;
return false;
}
/** Checks whether the region contains a given point.
@returns true if the point lies within one of the rectangles in the list
*/
bool containsPoint (ValueType x, ValueType y) const noexcept
{
return containsPoint (Point<ValueType> (x, y));
}
/** Checks whether the region contains the whole of a given rectangle.
@returns true all parts of the rectangle passed in lie within the region
defined by this object
@see intersectsRectangle, containsPoint
*/
bool containsRectangle (const RectangleType& rectangleToCheck) const
{
if (rects.size() > 1)
{
RectangleList r (rectangleToCheck);
for (int i = rects.size(); --i >= 0;)
{
r.subtract (rects.getReference (i));
if (r.rects.size() == 0)
return true;
}
}
else if (rects.size() > 0)
{
return rects.getReference (0).contains (rectangleToCheck);
}
return false;
}
/** Checks whether the region contains any part of a given rectangle.
@returns true if any part of the rectangle passed in lies within the region
defined by this object
@see containsRectangle
*/
bool intersectsRectangle (const RectangleType& rectangleToCheck) const noexcept
{
for (const RectangleType* r = rects.begin(), * const e = rects.end(); r != e; ++r)
if (r->intersects (rectangleToCheck))
return true;
return false;
}
/** Checks whether this region intersects any part of another one.
@see intersectsRectangle
*/
bool intersects (const RectangleList& other) const noexcept
{
for (const RectangleType* r = rects.begin(), * const e = rects.end(); r != e; ++r)
if (other.intersectsRectangle (*r))
return true;
return false;
}
//==============================================================================
/** Returns the smallest rectangle that can enclose the whole of this region. */
RectangleType getBounds() const noexcept
{
if (rects.size() <= 1)
{
if (rects.size() == 0)
return RectangleType();
return rects.getReference (0);
}
const RectangleType& r = rects.getReference (0);
ValueType minX = r.getX();
ValueType minY = r.getY();
ValueType maxX = minX + r.getWidth();
ValueType maxY = minY + r.getHeight();
for (int i = rects.size(); --i > 0;)
{
const RectangleType& r2 = rects.getReference (i);
minX = jmin (minX, r2.getX());
minY = jmin (minY, r2.getY());
maxX = jmax (maxX, r2.getRight());
maxY = jmax (maxY, r2.getBottom());
}
return RectangleType (minX, minY, maxX - minX, maxY - minY);
}
/** Optimises the list into a minimum number of constituent rectangles.
This will try to combine any adjacent rectangles into larger ones where
possible, to simplify lists that might have been fragmented by repeated
add/subtract calls.
*/
void consolidate()
{
for (int i = 0; i < getNumRectangles() - 1; ++i)
{
RectangleType& r = rects.getReference (i);
const ValueType rx1 = r.getX();
const ValueType ry1 = r.getY();
const ValueType rx2 = rx1 + r.getWidth();
const ValueType ry2 = ry1 + r.getHeight();
for (int j = rects.size(); --j > i;)
{
RectangleType& r2 = rects.getReference (j);
const ValueType jrx1 = r2.getX();
const ValueType jry1 = r2.getY();
const ValueType jrx2 = jrx1 + r2.getWidth();
const ValueType jry2 = jry1 + r2.getHeight();
// if the vertical edges of any blocks are touching and their horizontals don't
// line up, split them horizontally..
if (jrx1 == rx2 || jrx2 == rx1)
{
if (jry1 > ry1 && jry1 < ry2)
{
r.setHeight (jry1 - ry1);
rects.add (RectangleType (rx1, jry1, rx2 - rx1, ry2 - jry1));
i = -1;
break;
}
if (jry2 > ry1 && jry2 < ry2)
{
r.setHeight (jry2 - ry1);
rects.add (RectangleType (rx1, jry2, rx2 - rx1, ry2 - jry2));
i = -1;
break;
}
else if (ry1 > jry1 && ry1 < jry2)
{
r2.setHeight (ry1 - jry1);
rects.add (RectangleType (jrx1, ry1, jrx2 - jrx1, jry2 - ry1));
i = -1;
break;
}
else if (ry2 > jry1 && ry2 < jry2)
{
r2.setHeight (ry2 - jry1);
rects.add (RectangleType (jrx1, ry2, jrx2 - jrx1, jry2 - ry2));
i = -1;
break;
}
}
}
}
for (int i = 0; i < rects.size() - 1; ++i)
{
RectangleType& r = rects.getReference (i);
for (int j = rects.size(); --j > i;)
{
if (r.enlargeIfAdjacent (rects.getReference (j)))
{
rects.remove (j);
i = -1;
break;
}
}
}
}
/** Adds an x and y value to all the coordinates. */
void offsetAll (Point<ValueType> offset) noexcept
{
for (RectangleType* r = rects.begin(), * const e = rects.end(); r != e; ++r)
*r += offset;
}
/** Adds an x and y value to all the coordinates. */
void offsetAll (ValueType dx, ValueType dy) noexcept
{
offsetAll (Point<ValueType> (dx, dy));
}
/** Scales all the coordinates. */
template <typename ScaleType>
void scaleAll (ScaleType scaleFactor) noexcept
{
for (RectangleType* r = rects.begin(), * const e = rects.end(); r != e; ++r)
*r *= scaleFactor;
}
/** Applies a transform to all the rectangles.
Obviously this will create a mess if the transform involves any
rotation or skewing.
*/
void transformAll (const AffineTransform& transform) noexcept
{
for (RectangleType* r = rects.begin(), * const e = rects.end(); r != e; ++r)
*r = r->transformedBy (transform);
}
//==============================================================================
/** Creates a Path object to represent this region. */
Path toPath() const
{
Path p;
for (int i = 0; i < rects.size(); ++i)
p.addRectangle (rects.getReference (i));
return p;
}
//==============================================================================
/** Standard method for iterating the rectangles in the list. */
const RectangleType* begin() const noexcept { return rects.begin(); }
/** Standard method for iterating the rectangles in the list. */
const RectangleType* end() const noexcept { return rects.end(); }
/** Increases the internal storage to hold a minimum number of rectangles.
Calling this before adding a large number of rectangles means that
the array won't have to keep dynamically resizing itself as the elements
are added, and it'll therefore be more efficient.
@see Array::ensureStorageAllocated
*/
void ensureStorageAllocated (int minNumRectangles)
{
rects.ensureStorageAllocated (minNumRectangles);
}
private:
//==============================================================================
Array<RectangleType> rects;
};
#endif // JUCE_RECTANGLELIST_H_INCLUDED