1
0
Fork 0
mirror of https://github.com/juce-framework/JUCE.git synced 2026-01-10 23:44:24 +00:00
JUCE/modules/juce_graphics/contexts/juce_GraphicsContext.cpp

718 lines
24 KiB
C++

/*
==============================================================================
This file is part of the JUCE library - "Jules' Utility Class Extensions"
Copyright 2004-11 by Raw Material Software Ltd.
------------------------------------------------------------------------------
JUCE can be redistributed and/or modified under the terms of the GNU General
Public License (Version 2), as published by the Free Software Foundation.
A copy of the license is included in the JUCE distribution, or can be found
online 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.rawmaterialsoftware.com/juce for more information.
==============================================================================
*/
BEGIN_JUCE_NAMESPACE
//==============================================================================
namespace
{
template <typename Type>
bool areCoordsSensibleNumbers (Type x, Type y, Type w, Type h)
{
const int maxVal = 0x3fffffff;
return (int) x >= -maxVal && (int) x <= maxVal
&& (int) y >= -maxVal && (int) y <= maxVal
&& (int) w >= -maxVal && (int) w <= maxVal
&& (int) h >= -maxVal && (int) h <= maxVal;
}
}
//==============================================================================
LowLevelGraphicsContext::LowLevelGraphicsContext()
{
}
LowLevelGraphicsContext::~LowLevelGraphicsContext()
{
}
//==============================================================================
Graphics::Graphics (const Image& imageToDrawOnto)
: context (imageToDrawOnto.createLowLevelContext()),
contextToDelete (context),
saveStatePending (false)
{
}
Graphics::Graphics (LowLevelGraphicsContext* const internalContext) noexcept
: context (internalContext),
saveStatePending (false)
{
}
Graphics::~Graphics()
{
}
//==============================================================================
void Graphics::resetToDefaultState()
{
saveStateIfPending();
context->setFill (FillType());
context->setFont (Font());
context->setInterpolationQuality (Graphics::mediumResamplingQuality);
}
bool Graphics::isVectorDevice() const
{
return context->isVectorDevice();
}
bool Graphics::reduceClipRegion (const Rectangle<int>& area)
{
saveStateIfPending();
return context->clipToRectangle (area);
}
bool Graphics::reduceClipRegion (const int x, const int y, const int w, const int h)
{
return reduceClipRegion (Rectangle<int> (x, y, w, h));
}
bool Graphics::reduceClipRegion (const RectangleList& clipRegion)
{
saveStateIfPending();
return context->clipToRectangleList (clipRegion);
}
bool Graphics::reduceClipRegion (const Path& path, const AffineTransform& transform)
{
saveStateIfPending();
context->clipToPath (path, transform);
return ! context->isClipEmpty();
}
bool Graphics::reduceClipRegion (const Image& image, const AffineTransform& transform)
{
saveStateIfPending();
context->clipToImageAlpha (image, transform);
return ! context->isClipEmpty();
}
void Graphics::excludeClipRegion (const Rectangle<int>& rectangleToExclude)
{
saveStateIfPending();
context->excludeClipRectangle (rectangleToExclude);
}
bool Graphics::isClipEmpty() const
{
return context->isClipEmpty();
}
const Rectangle<int> Graphics::getClipBounds() const
{
return context->getClipBounds();
}
void Graphics::saveState()
{
saveStateIfPending();
saveStatePending = true;
}
void Graphics::restoreState()
{
if (saveStatePending)
saveStatePending = false;
else
context->restoreState();
}
void Graphics::saveStateIfPending()
{
if (saveStatePending)
{
saveStatePending = false;
context->saveState();
}
}
void Graphics::setOrigin (const int newOriginX, const int newOriginY)
{
saveStateIfPending();
context->setOrigin (newOriginX, newOriginY);
}
void Graphics::addTransform (const AffineTransform& transform)
{
saveStateIfPending();
context->addTransform (transform);
}
bool Graphics::clipRegionIntersects (const Rectangle<int>& area) const
{
return context->clipRegionIntersects (area);
}
void Graphics::beginTransparencyLayer (float layerOpacity)
{
saveStateIfPending();
context->beginTransparencyLayer (layerOpacity);
}
void Graphics::endTransparencyLayer()
{
context->endTransparencyLayer();
}
//==============================================================================
void Graphics::setColour (const Colour& newColour)
{
saveStateIfPending();
context->setFill (newColour);
}
void Graphics::setOpacity (const float newOpacity)
{
saveStateIfPending();
context->setOpacity (newOpacity);
}
void Graphics::setGradientFill (const ColourGradient& gradient)
{
setFillType (gradient);
}
void Graphics::setTiledImageFill (const Image& imageToUse, const int anchorX, const int anchorY, const float opacity)
{
saveStateIfPending();
context->setFill (FillType (imageToUse, AffineTransform::translation ((float) anchorX, (float) anchorY)));
context->setOpacity (opacity);
}
void Graphics::setFillType (const FillType& newFill)
{
saveStateIfPending();
context->setFill (newFill);
}
//==============================================================================
void Graphics::setFont (const Font& newFont)
{
saveStateIfPending();
context->setFont (newFont);
}
void Graphics::setFont (const float newFontHeight, const int newFontStyleFlags)
{
saveStateIfPending();
Font f (context->getFont());
f.setSizeAndStyle (newFontHeight, newFontStyleFlags, 1.0f, 0);
context->setFont (f);
}
Font Graphics::getCurrentFont() const
{
return context->getFont();
}
//==============================================================================
void Graphics::drawSingleLineText (const String& text, const int startX, const int baselineY) const
{
if (text.isNotEmpty()
&& startX < context->getClipBounds().getRight())
{
GlyphArrangement arr;
arr.addLineOfText (context->getFont(), text, (float) startX, (float) baselineY);
arr.draw (*this);
}
}
void Graphics::drawTextAsPath (const String& text, const AffineTransform& transform) const
{
if (text.isNotEmpty())
{
GlyphArrangement arr;
arr.addLineOfText (context->getFont(), text, 0.0f, 0.0f);
arr.draw (*this, transform);
}
}
void Graphics::drawMultiLineText (const String& text, const int startX, const int baselineY, const int maximumLineWidth) const
{
if (text.isNotEmpty()
&& startX < context->getClipBounds().getRight())
{
GlyphArrangement arr;
arr.addJustifiedText (context->getFont(), text,
(float) startX, (float) baselineY, (float) maximumLineWidth,
Justification::left);
arr.draw (*this);
}
}
void Graphics::drawText (const String& text,
const int x, const int y, const int width, const int height,
const Justification& justificationType,
const bool useEllipsesIfTooBig) const
{
if (text.isNotEmpty() && context->clipRegionIntersects (Rectangle<int> (x, y, width, height)))
{
GlyphArrangement arr;
arr.addCurtailedLineOfText (context->getFont(), text,
0.0f, 0.0f, (float) width,
useEllipsesIfTooBig);
arr.justifyGlyphs (0, arr.getNumGlyphs(),
(float) x, (float) y, (float) width, (float) height,
justificationType);
arr.draw (*this);
}
}
void Graphics::drawFittedText (const String& text,
const int x, const int y, const int width, const int height,
const Justification& justification,
const int maximumNumberOfLines,
const float minimumHorizontalScale) const
{
if (text.isNotEmpty()
&& width > 0 && height > 0
&& context->clipRegionIntersects (Rectangle<int> (x, y, width, height)))
{
GlyphArrangement arr;
arr.addFittedText (context->getFont(), text,
(float) x, (float) y, (float) width, (float) height,
justification,
maximumNumberOfLines,
minimumHorizontalScale);
arr.draw (*this);
}
}
//==============================================================================
void Graphics::fillRect (int x, int y, int width, int height) const
{
// passing in a silly number can cause maths problems in rendering!
jassert (areCoordsSensibleNumbers (x, y, width, height));
context->fillRect (Rectangle<int> (x, y, width, height), false);
}
void Graphics::fillRect (const Rectangle<int>& r) const
{
context->fillRect (r, false);
}
void Graphics::fillRect (const float x, const float y, const float width, const float height) const
{
// passing in a silly number can cause maths problems in rendering!
jassert (areCoordsSensibleNumbers (x, y, width, height));
Path p;
p.addRectangle (x, y, width, height);
fillPath (p);
}
void Graphics::setPixel (int x, int y) const
{
context->fillRect (Rectangle<int> (x, y, 1, 1), false);
}
void Graphics::fillAll() const
{
fillRect (context->getClipBounds());
}
void Graphics::fillAll (const Colour& colourToUse) const
{
if (! colourToUse.isTransparent())
{
const Rectangle<int> clip (context->getClipBounds());
context->saveState();
context->setFill (colourToUse);
context->fillRect (clip, false);
context->restoreState();
}
}
//==============================================================================
void Graphics::fillPath (const Path& path, const AffineTransform& transform) const
{
if ((! context->isClipEmpty()) && ! path.isEmpty())
context->fillPath (path, transform);
}
void Graphics::strokePath (const Path& path,
const PathStrokeType& strokeType,
const AffineTransform& transform) const
{
Path stroke;
strokeType.createStrokedPath (stroke, path, transform, context->getScaleFactor());
fillPath (stroke);
}
//==============================================================================
void Graphics::drawRect (const int x, const int y, const int width, const int height,
const int lineThickness) const
{
// passing in a silly number can cause maths problems in rendering!
jassert (areCoordsSensibleNumbers (x, y, width, height));
context->fillRect (Rectangle<int> (x, y, width, lineThickness), false);
context->fillRect (Rectangle<int> (x, y + lineThickness, lineThickness, height - lineThickness * 2), false);
context->fillRect (Rectangle<int> (x + width - lineThickness, y + lineThickness, lineThickness, height - lineThickness * 2), false);
context->fillRect (Rectangle<int> (x, y + height - lineThickness, width, lineThickness), false);
}
void Graphics::drawRect (const float x, const float y, const float width, const float height, const float lineThickness) const
{
// passing in a silly number can cause maths problems in rendering!
jassert (areCoordsSensibleNumbers (x, y, width, height));
Path p;
p.addRectangle (x, y, width, lineThickness);
p.addRectangle (x, y + lineThickness, lineThickness, height - lineThickness * 2.0f);
p.addRectangle (x + width - lineThickness, y + lineThickness, lineThickness, height - lineThickness * 2.0f);
p.addRectangle (x, y + height - lineThickness, width, lineThickness);
fillPath (p);
}
void Graphics::drawRect (const Rectangle<int>& r, const int lineThickness) const
{
drawRect (r.getX(), r.getY(), r.getWidth(), r.getHeight(), lineThickness);
}
void Graphics::drawBevel (const int x, const int y, const int width, const int height,
const int bevelThickness, const Colour& topLeftColour, const Colour& bottomRightColour,
const bool useGradient, const bool sharpEdgeOnOutside) const
{
// passing in a silly number can cause maths problems in rendering!
jassert (areCoordsSensibleNumbers (x, y, width, height));
if (clipRegionIntersects (Rectangle<int> (x, y, width, height)))
{
context->saveState();
const float oldOpacity = 1.0f;//xxx state->colour.getFloatAlpha();
const float ramp = oldOpacity / bevelThickness;
for (int i = bevelThickness; --i >= 0;)
{
const float op = useGradient ? ramp * (sharpEdgeOnOutside ? bevelThickness - i : i)
: oldOpacity;
context->setFill (topLeftColour.withMultipliedAlpha (op));
context->fillRect (Rectangle<int> (x + i, y + i, width - i * 2, 1), false);
context->setFill (topLeftColour.withMultipliedAlpha (op * 0.75f));
context->fillRect (Rectangle<int> (x + i, y + i + 1, 1, height - i * 2 - 2), false);
context->setFill (bottomRightColour.withMultipliedAlpha (op));
context->fillRect (Rectangle<int> (x + i, y + height - i - 1, width - i * 2, 1), false);
context->setFill (bottomRightColour.withMultipliedAlpha (op * 0.75f));
context->fillRect (Rectangle<int> (x + width - i - 1, y + i + 1, 1, height - i * 2 - 2), false);
}
context->restoreState();
}
}
//==============================================================================
void Graphics::fillEllipse (const float x, const float y, const float width, const float height) const
{
// passing in a silly number can cause maths problems in rendering!
jassert (areCoordsSensibleNumbers (x, y, width, height));
Path p;
p.addEllipse (x, y, width, height);
fillPath (p);
}
void Graphics::drawEllipse (const float x, const float y, const float width, const float height,
const float lineThickness) const
{
// passing in a silly number can cause maths problems in rendering!
jassert (areCoordsSensibleNumbers (x, y, width, height));
Path p;
p.addEllipse (x, y, width, height);
strokePath (p, PathStrokeType (lineThickness));
}
void Graphics::fillRoundedRectangle (const float x, const float y, const float width, const float height, const float cornerSize) const
{
// passing in a silly number can cause maths problems in rendering!
jassert (areCoordsSensibleNumbers (x, y, width, height));
Path p;
p.addRoundedRectangle (x, y, width, height, cornerSize);
fillPath (p);
}
void Graphics::fillRoundedRectangle (const Rectangle<float>& r, const float cornerSize) const
{
fillRoundedRectangle (r.getX(), r.getY(), r.getWidth(), r.getHeight(), cornerSize);
}
void Graphics::drawRoundedRectangle (const float x, const float y, const float width, const float height,
const float cornerSize, const float lineThickness) const
{
// passing in a silly number can cause maths problems in rendering!
jassert (areCoordsSensibleNumbers (x, y, width, height));
Path p;
p.addRoundedRectangle (x, y, width, height, cornerSize);
strokePath (p, PathStrokeType (lineThickness));
}
void Graphics::drawRoundedRectangle (const Rectangle<float>& r, const float cornerSize, const float lineThickness) const
{
drawRoundedRectangle (r.getX(), r.getY(), r.getWidth(), r.getHeight(), cornerSize, lineThickness);
}
void Graphics::drawArrow (const Line<float>& line, const float lineThickness, const float arrowheadWidth, const float arrowheadLength) const
{
Path p;
p.addArrow (line, lineThickness, arrowheadWidth, arrowheadLength);
fillPath (p);
}
void Graphics::fillCheckerBoard (const Rectangle<int>& area,
const int checkWidth, const int checkHeight,
const Colour& colour1, const Colour& colour2) const
{
jassert (checkWidth > 0 && checkHeight > 0); // can't be zero or less!
if (checkWidth > 0 && checkHeight > 0)
{
context->saveState();
if (colour1 == colour2)
{
context->setFill (colour1);
context->fillRect (area, false);
}
else
{
const Rectangle<int> clipped (context->getClipBounds().getIntersection (area));
if (! clipped.isEmpty())
{
context->clipToRectangle (clipped);
const int checkNumX = (clipped.getX() - area.getX()) / checkWidth;
const int checkNumY = (clipped.getY() - area.getY()) / checkHeight;
const int startX = area.getX() + checkNumX * checkWidth;
const int startY = area.getY() + checkNumY * checkHeight;
const int right = clipped.getRight();
const int bottom = clipped.getBottom();
for (int i = 0; i < 2; ++i)
{
context->setFill (i == ((checkNumX ^ checkNumY) & 1) ? colour1 : colour2);
int cy = i;
for (int y = startY; y < bottom; y += checkHeight)
for (int x = startX + (cy++ & 1) * checkWidth; x < right; x += checkWidth * 2)
context->fillRect (Rectangle<int> (x, y, checkWidth, checkHeight), false);
}
}
}
context->restoreState();
}
}
//==============================================================================
void Graphics::drawVerticalLine (const int x, float top, float bottom) const
{
context->drawVerticalLine (x, top, bottom);
}
void Graphics::drawHorizontalLine (const int y, float left, float right) const
{
context->drawHorizontalLine (y, left, right);
}
void Graphics::drawLine (const float x1, const float y1, const float x2, const float y2) const
{
context->drawLine (Line<float> (x1, y1, x2, y2));
}
void Graphics::drawLine (const Line<float>& line) const
{
context->drawLine (line);
}
void Graphics::drawLine (const float x1, const float y1, const float x2, const float y2, const float lineThickness) const
{
drawLine (Line<float> (x1, y1, x2, y2), lineThickness);
}
void Graphics::drawLine (const Line<float>& line, const float lineThickness) const
{
Path p;
p.addLineSegment (line, lineThickness);
fillPath (p);
}
void Graphics::drawDashedLine (const Line<float>& line, const float* const dashLengths,
const int numDashLengths, const float lineThickness, int n) const
{
jassert (n >= 0 && n < numDashLengths); // your start index must be valid!
const Point<double> delta ((line.getEnd() - line.getStart()).toDouble());
const double totalLen = delta.getDistanceFromOrigin();
if (totalLen >= 0.1)
{
const double onePixAlpha = 1.0 / totalLen;
for (double alpha = 0.0; alpha < 1.0;)
{
jassert (dashLengths[n] > 0); // can't have zero-length dashes!
const double lastAlpha = alpha;
alpha = jmin (1.0, alpha + dashLengths [n] * onePixAlpha);
n = (n + 1) % numDashLengths;
if ((n & 1) != 0)
{
const Line<float> segment (line.getStart() + (delta * lastAlpha).toFloat(),
line.getStart() + (delta * alpha).toFloat());
if (lineThickness != 1.0f)
drawLine (segment, lineThickness);
else
context->drawLine (segment);
}
}
}
}
//==============================================================================
void Graphics::setImageResamplingQuality (const Graphics::ResamplingQuality newQuality)
{
saveStateIfPending();
context->setInterpolationQuality (newQuality);
}
//==============================================================================
void Graphics::drawImageAt (const Image& imageToDraw,
const int topLeftX, const int topLeftY,
const bool fillAlphaChannelWithCurrentBrush) const
{
const int imageW = imageToDraw.getWidth();
const int imageH = imageToDraw.getHeight();
drawImage (imageToDraw,
topLeftX, topLeftY, imageW, imageH,
0, 0, imageW, imageH,
fillAlphaChannelWithCurrentBrush);
}
void Graphics::drawImageWithin (const Image& imageToDraw,
const int destX, const int destY,
const int destW, const int destH,
const RectanglePlacement& placementWithinTarget,
const bool fillAlphaChannelWithCurrentBrush) const
{
// passing in a silly number can cause maths problems in rendering!
jassert (areCoordsSensibleNumbers (destX, destY, destW, destH));
if (imageToDraw.isValid())
{
const int imageW = imageToDraw.getWidth();
const int imageH = imageToDraw.getHeight();
if (imageW > 0 && imageH > 0)
{
double newX = 0.0, newY = 0.0;
double newW = imageW;
double newH = imageH;
placementWithinTarget.applyTo (newX, newY, newW, newH,
destX, destY, destW, destH);
if (newW > 0 && newH > 0)
{
drawImage (imageToDraw,
roundToInt (newX), roundToInt (newY),
roundToInt (newW), roundToInt (newH),
0, 0, imageW, imageH,
fillAlphaChannelWithCurrentBrush);
}
}
}
}
void Graphics::drawImage (const Image& imageToDraw,
int dx, int dy, int dw, int dh,
int sx, int sy, int sw, int sh,
const bool fillAlphaChannelWithCurrentBrush) const
{
// passing in a silly number can cause maths problems in rendering!
jassert (areCoordsSensibleNumbers (dx, dy, dw, dh));
jassert (areCoordsSensibleNumbers (sx, sy, sw, sh));
if (imageToDraw.isValid() && context->clipRegionIntersects (Rectangle<int> (dx, dy, dw, dh)))
{
drawImageTransformed (imageToDraw.getClippedImage (Rectangle<int> (sx, sy, sw, sh)),
AffineTransform::scale (dw / (float) sw, dh / (float) sh)
.translated ((float) dx, (float) dy),
fillAlphaChannelWithCurrentBrush);
}
}
void Graphics::drawImageTransformed (const Image& imageToDraw,
const AffineTransform& transform,
const bool fillAlphaChannelWithCurrentBrush) const
{
if (imageToDraw.isValid() && ! context->isClipEmpty())
{
if (fillAlphaChannelWithCurrentBrush)
{
context->saveState();
context->clipToImageAlpha (imageToDraw, transform);
fillAll();
context->restoreState();
}
else
{
context->drawImage (imageToDraw, transform, false);
}
}
}
//==============================================================================
Graphics::ScopedSaveState::ScopedSaveState (Graphics& g)
: context (g)
{
context.saveState();
}
Graphics::ScopedSaveState::~ScopedSaveState()
{
context.restoreState();
}
END_JUCE_NAMESPACE