/* ============================================================================== This file is part of the JUCE library - "Jules' Utility Class Extensions" Copyright 2004-9 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. ============================================================================== */ #include "../../../core/juce_StandardHeader.h" BEGIN_JUCE_NAMESPACE #include "juce_Graphics.h" #include "../fonts/juce_GlyphArrangement.h" #include "../geometry/juce_PathStrokeType.h" #include "juce_EdgeTable.h" #include "juce_LowLevelGraphicsContext.h" static const Graphics::ResamplingQuality defaultQuality = Graphics::mediumResamplingQuality; //============================================================================== #define MINIMUM_COORD -0x3fffffff #define MAXIMUM_COORD 0x3fffffff #undef ASSERT_COORDS_ARE_SENSIBLE_NUMBERS #define ASSERT_COORDS_ARE_SENSIBLE_NUMBERS(x, y, w, h) \ jassert ((int) x >= MINIMUM_COORD \ && (int) x <= MAXIMUM_COORD \ && (int) y >= MINIMUM_COORD \ && (int) y <= MAXIMUM_COORD \ && (int) w >= MINIMUM_COORD \ && (int) w <= MAXIMUM_COORD \ && (int) h >= MINIMUM_COORD \ && (int) h <= MAXIMUM_COORD); //============================================================================== LowLevelGraphicsContext::LowLevelGraphicsContext() { } LowLevelGraphicsContext::~LowLevelGraphicsContext() { } //============================================================================== Graphics::Graphics (Image& imageToDrawOnto) throw() : context (imageToDrawOnto.createLowLevelContext()), ownsContext (true), state (new GraphicsState()), saveStatePending (false) { } Graphics::Graphics (LowLevelGraphicsContext* const internalContext) throw() : context (internalContext), ownsContext (false), state (new GraphicsState()), saveStatePending (false) { } Graphics::~Graphics() throw() { delete state; if (ownsContext) delete context; } //============================================================================== void Graphics::resetToDefaultState() throw() { setColour (Colours::black); state->font.resetToDefaultState(); state->quality = defaultQuality; } bool Graphics::isVectorDevice() const throw() { return context->isVectorDevice(); } bool Graphics::reduceClipRegion (const int x, const int y, const int w, const int h) throw() { saveStateIfPending(); return context->reduceClipRegion (x, y, w, h); } bool Graphics::reduceClipRegion (const RectangleList& clipRegion) throw() { saveStateIfPending(); return context->reduceClipRegion (clipRegion); } void Graphics::excludeClipRegion (const int x, const int y, const int w, const int h) throw() { saveStateIfPending(); context->excludeClipRegion (x, y, w, h); } bool Graphics::isClipEmpty() const throw() { return context->isClipEmpty(); } const Rectangle Graphics::getClipBounds() const throw() { return context->getClipBounds(); } void Graphics::saveState() throw() { saveStateIfPending(); saveStatePending = true; } void Graphics::restoreState() throw() { if (saveStatePending) { saveStatePending = false; } else { const int stackSize = stateStack.size(); if (stackSize > 0) { context->restoreState(); delete state; state = stateStack.getUnchecked (stackSize - 1); stateStack.removeLast (1, false); } else { // Trying to call restoreState() more times than you've called saveState() ! // Be careful to correctly match each saveState() with exactly one call to restoreState(). jassertfalse } } } void Graphics::saveStateIfPending() throw() { if (saveStatePending) { saveStatePending = false; context->saveState(); stateStack.add (new GraphicsState (*state)); } } void Graphics::setOrigin (const int newOriginX, const int newOriginY) throw() { saveStateIfPending(); context->setOrigin (newOriginX, newOriginY); } bool Graphics::clipRegionIntersects (const int x, const int y, const int w, const int h) const throw() { return context->clipRegionIntersects (x, y, w, h); } //============================================================================== void Graphics::setColour (const Colour& newColour) throw() { saveStateIfPending(); state->colour = newColour; deleteAndZero (state->brush); } void Graphics::setOpacity (const float newOpacity) throw() { saveStateIfPending(); state->colour = state->colour.withAlpha (newOpacity); } void Graphics::setBrush (const Brush* const newBrush) throw() { saveStateIfPending(); delete state->brush; if (newBrush != 0) state->brush = newBrush->createCopy(); else state->brush = 0; } //============================================================================== Graphics::GraphicsState::GraphicsState() throw() : colour (Colours::black), brush (0), quality (defaultQuality) { } Graphics::GraphicsState::GraphicsState (const GraphicsState& other) throw() : colour (other.colour), brush (other.brush != 0 ? other.brush->createCopy() : 0), font (other.font), quality (other.quality) { } Graphics::GraphicsState::~GraphicsState() throw() { delete brush; } //============================================================================== void Graphics::setFont (const Font& newFont) throw() { saveStateIfPending(); state->font = newFont; } void Graphics::setFont (const float newFontHeight, const int newFontStyleFlags) throw() { saveStateIfPending(); state->font.setSizeAndStyle (newFontHeight, newFontStyleFlags, 1.0f, 0.0f); } //============================================================================== void Graphics::drawSingleLineText (const String& text, const int startX, const int baselineY) const throw() { if (text.isNotEmpty() && startX < context->getClipBounds().getRight()) { GlyphArrangement arr; arr.addLineOfText (state->font, text, (float) startX, (float) baselineY); arr.draw (*this); } } void Graphics::drawTextAsPath (const String& text, const AffineTransform& transform) const throw() { if (text.isNotEmpty()) { GlyphArrangement arr; arr.addLineOfText (state->font, 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 throw() { if (text.isNotEmpty() && startX < context->getClipBounds().getRight()) { GlyphArrangement arr; arr.addJustifiedText (state->font, 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 throw() { if (text.isNotEmpty() && context->clipRegionIntersects (x, y, width, height)) { GlyphArrangement arr; arr.addCurtailedLineOfText (state->font, 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 throw() { if (text.isNotEmpty() && width > 0 && height > 0 && context->clipRegionIntersects (x, y, width, height)) { GlyphArrangement arr; arr.addFittedText (state->font, 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 throw() { // passing in a silly number can cause maths problems in rendering! ASSERT_COORDS_ARE_SENSIBLE_NUMBERS (x, y, width, height); SolidColourBrush colourBrush (state->colour); (state->brush != 0 ? *(state->brush) : (Brush&) colourBrush).paintRectangle (*context, x, y, width, height); } void Graphics::fillRect (const Rectangle& r) const throw() { fillRect (r.getX(), r.getY(), r.getWidth(), r.getHeight()); } void Graphics::fillRect (const float x, const float y, const float width, const float height) const throw() { // passing in a silly number can cause maths problems in rendering! ASSERT_COORDS_ARE_SENSIBLE_NUMBERS (x, y, width, height); Path p; p.addRectangle (x, y, width, height); fillPath (p); } void Graphics::setPixel (int x, int y) const throw() { if (context->clipRegionIntersects (x, y, 1, 1)) { SolidColourBrush colourBrush (state->colour); (state->brush != 0 ? *(state->brush) : (Brush&) colourBrush).paintRectangle (*context, x, y, 1, 1); } } void Graphics::fillAll() const throw() { fillRect (context->getClipBounds()); } void Graphics::fillAll (const Colour& colourToUse) const throw() { if (! colourToUse.isTransparent()) { const Rectangle clip (context->getClipBounds()); context->fillRectWithColour (clip.getX(), clip.getY(), clip.getWidth(), clip.getHeight(), colourToUse, false); } } //============================================================================== void Graphics::fillPath (const Path& path, const AffineTransform& transform) const throw() { if ((! context->isClipEmpty()) && ! path.isEmpty()) { SolidColourBrush colourBrush (state->colour); (state->brush != 0 ? *(state->brush) : (Brush&) colourBrush).paintPath (*context, path, transform); } } void Graphics::strokePath (const Path& path, const PathStrokeType& strokeType, const AffineTransform& transform) const throw() { if ((! state->colour.isTransparent()) || state->brush != 0) { Path stroke; strokeType.createStrokedPath (stroke, path, transform); fillPath (stroke); } } //============================================================================== void Graphics::drawRect (const int x, const int y, const int width, const int height, const int lineThickness) const throw() { // passing in a silly number can cause maths problems in rendering! ASSERT_COORDS_ARE_SENSIBLE_NUMBERS (x, y, width, height); SolidColourBrush colourBrush (state->colour); Brush& b = (state->brush != 0 ? *(state->brush) : (Brush&) colourBrush); b.paintRectangle (*context, x, y, width, lineThickness); b.paintRectangle (*context, x, y + lineThickness, lineThickness, height - lineThickness * 2); b.paintRectangle (*context, x + width - lineThickness, y + lineThickness, lineThickness, height - lineThickness * 2); b.paintRectangle (*context, x, y + height - lineThickness, width, lineThickness); } void Graphics::drawRect (const float x, const float y, const float width, const float height, const float lineThickness) const throw() { // passing in a silly number can cause maths problems in rendering! ASSERT_COORDS_ARE_SENSIBLE_NUMBERS (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& r, const int lineThickness) const throw() { 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 throw() { // passing in a silly number can cause maths problems in rendering! ASSERT_COORDS_ARE_SENSIBLE_NUMBERS (x, y, width, height); if (clipRegionIntersects (x, y, width, height)) { const float oldOpacity = 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->fillRectWithColour (x + i, y + i, width - i * 2, 1, topLeftColour.withMultipliedAlpha (op), false); context->fillRectWithColour (x + i, y + i + 1, 1, height - i * 2 - 2, topLeftColour.withMultipliedAlpha (op * 0.75f), false); context->fillRectWithColour (x + i, y + height - i - 1, width - i * 2, 1, bottomRightColour.withMultipliedAlpha (op), false); context->fillRectWithColour (x + width - i - 1, y + i + 1, 1, height - i * 2 - 2, bottomRightColour.withMultipliedAlpha (op * 0.75f), false); } } } //============================================================================== void Graphics::fillEllipse (const float x, const float y, const float width, const float height) const throw() { // passing in a silly number can cause maths problems in rendering! ASSERT_COORDS_ARE_SENSIBLE_NUMBERS (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 throw() { // passing in a silly number can cause maths problems in rendering! ASSERT_COORDS_ARE_SENSIBLE_NUMBERS (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 throw() { // passing in a silly number can cause maths problems in rendering! ASSERT_COORDS_ARE_SENSIBLE_NUMBERS (x, y, width, height); Path p; p.addRoundedRectangle (x, y, width, height, cornerSize); fillPath (p); } void Graphics::fillRoundedRectangle (const Rectangle& r, const float cornerSize) const throw() { fillRoundedRectangle ((float) r.getX(), (float) r.getY(), (float) r.getWidth(), (float) 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 throw() { // passing in a silly number can cause maths problems in rendering! ASSERT_COORDS_ARE_SENSIBLE_NUMBERS (x, y, width, height); Path p; p.addRoundedRectangle (x, y, width, height, cornerSize); strokePath (p, PathStrokeType (lineThickness)); } void Graphics::drawRoundedRectangle (const Rectangle& r, const float cornerSize, const float lineThickness) const throw() { drawRoundedRectangle ((float) r.getX(), (float) r.getY(), (float) r.getWidth(), (float) r.getHeight(), cornerSize, lineThickness); } void Graphics::drawArrow (const float startX, const float startY, const float endX, const float endY, const float lineThickness, const float arrowheadWidth, const float arrowheadLength) const throw() { Path p; p.addArrow (startX, startY, endX, endY, lineThickness, arrowheadWidth, arrowheadLength); fillPath (p); } void Graphics::fillCheckerBoard (int x, int y, int width, int height, const int checkWidth, const int checkHeight, const Colour& colour1, const Colour& colour2) const throw() { jassert (checkWidth > 0 && checkHeight > 0); // can't be zero or less! if (checkWidth > 0 && checkHeight > 0) { if (colour1 == colour2) { context->fillRectWithColour (x, y, width, height, colour1, false); } else { const Rectangle clip (context->getClipBounds()); const int right = jmin (x + width, clip.getRight()); const int bottom = jmin (y + height, clip.getBottom()); int cy = 0; while (y < bottom) { int cx = cy; for (int xx = x; xx < right; xx += checkWidth) context->fillRectWithColour (xx, y, jmin (checkWidth, right - xx), jmin (checkHeight, bottom - y), ((cx++ & 1) == 0) ? colour1 : colour2, false); ++cy; y += checkHeight; } } } } //============================================================================== void Graphics::drawVerticalLine (const int x, float top, float bottom) const throw() { SolidColourBrush colourBrush (state->colour); (state->brush != 0 ? *(state->brush) : (Brush&) colourBrush).paintVerticalLine (*context, x, top, bottom); } void Graphics::drawHorizontalLine (const int y, float left, float right) const throw() { SolidColourBrush colourBrush (state->colour); (state->brush != 0 ? *(state->brush) : (Brush&) colourBrush).paintHorizontalLine (*context, y, left, right); } void Graphics::drawLine (float x1, float y1, float x2, float y2) const throw() { if (! context->isClipEmpty()) { SolidColourBrush colourBrush (state->colour); (state->brush != 0 ? *(state->brush) : (Brush&) colourBrush).paintLine (*context, x1, y1, x2, y2); } } void Graphics::drawLine (const float startX, const float startY, const float endX, const float endY, const float lineThickness) const throw() { Path p; p.addLineSegment (startX, startY, endX, endY, lineThickness); fillPath (p); } void Graphics::drawLine (const Line& line) const throw() { drawLine (line.getStartX(), line.getStartY(), line.getEndX(), line.getEndY()); } void Graphics::drawLine (const Line& line, const float lineThickness) const throw() { drawLine (line.getStartX(), line.getStartY(), line.getEndX(), line.getEndY(), lineThickness); } void Graphics::drawDashedLine (const float startX, const float startY, const float endX, const float endY, const float* const dashLengths, const int numDashLengths, const float lineThickness) const throw() { const double dx = endX - startX; const double dy = endY - startY; const double totalLen = juce_hypot (dx, dy); if (totalLen >= 0.5) { const double onePixAlpha = 1.0 / totalLen; double alpha = 0.0; float x = startX; float y = startY; int n = 0; while (alpha < 1.0f) { alpha = jmin (1.0, alpha + dashLengths[n++] * onePixAlpha); n = n % numDashLengths; const float oldX = x; const float oldY = y; x = (float) (startX + dx * alpha); y = (float) (startY + dy * alpha); if ((n & 1) != 0) { if (lineThickness != 1.0f) drawLine (oldX, oldY, x, y, lineThickness); else drawLine (oldX, oldY, x, y); } } } } //============================================================================== void Graphics::setImageResamplingQuality (const Graphics::ResamplingQuality newQuality) throw() { saveStateIfPending(); state->quality = newQuality; } //============================================================================== void Graphics::drawImageAt (const Image* const imageToDraw, const int topLeftX, const int topLeftY, const bool fillAlphaChannelWithCurrentBrush) const throw() { if (imageToDraw != 0) { 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* const imageToDraw, const int destX, const int destY, const int destW, const int destH, const RectanglePlacement& placementWithinTarget, const bool fillAlphaChannelWithCurrentBrush) const throw() { // passing in a silly number can cause maths problems in rendering! ASSERT_COORDS_ARE_SENSIBLE_NUMBERS (destX, destY, destW, destH); if (imageToDraw != 0) { 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, roundDoubleToInt (newX), roundDoubleToInt (newY), roundDoubleToInt (newW), roundDoubleToInt (newH), 0, 0, imageW, imageH, fillAlphaChannelWithCurrentBrush); } } } } void Graphics::drawImage (const Image* const imageToDraw, int dx, int dy, int dw, int dh, int sx, int sy, int sw, int sh, const bool fillAlphaChannelWithCurrentBrush) const throw() { // passing in a silly number can cause maths problems in rendering! ASSERT_COORDS_ARE_SENSIBLE_NUMBERS (dx, dy, dw, dh); ASSERT_COORDS_ARE_SENSIBLE_NUMBERS (sx, sy, sw, sh); if (imageToDraw == 0 || ! context->clipRegionIntersects (dx, dy, dw, dh)) return; if (sw == dw && sh == dh) { if (sx < 0) { dx -= sx; dw += sx; sw += sx; sx = 0; } if (sx + sw > imageToDraw->getWidth()) { const int amount = sx + sw - imageToDraw->getWidth(); dw -= amount; sw -= amount; } if (sy < 0) { dy -= sy; dh += sy; sh += sy; sy = 0; } if (sy + sh > imageToDraw->getHeight()) { const int amount = sy + sh - imageToDraw->getHeight(); dh -= amount; sh -= amount; } if (dw <= 0 || dh <= 0 || sw <= 0 || sh <= 0) return; if (fillAlphaChannelWithCurrentBrush) { SolidColourBrush colourBrush (state->colour); (state->brush != 0 ? *(state->brush) : (Brush&) colourBrush) .paintAlphaChannel (*context, *imageToDraw, dx - sx, dy - sy, dx, dy, dw, dh); } else { context->blendImage (*imageToDraw, dx, dy, dw, dh, sx, sy, state->colour.getFloatAlpha()); } } else { if (dw <= 0 || dh <= 0 || sw <= 0 || sh <= 0) return; if (fillAlphaChannelWithCurrentBrush) { if (imageToDraw->isRGB()) { fillRect (dx, dy, dw, dh); } else { int tx = dx; int ty = dy; int tw = dw; int th = dh; if (context->getClipBounds().intersectRectangle (tx, ty, tw, th)) { Image temp (imageToDraw->getFormat(), tw, th, true); Graphics g (temp); g.setImageResamplingQuality (state->quality); g.setOrigin (dx - tx, dy - ty); g.drawImage (imageToDraw, 0, 0, dw, dh, sx, sy, sw, sh, false); SolidColourBrush colourBrush (state->colour); (state->brush != 0 ? *(state->brush) : (Brush&) colourBrush) .paintAlphaChannel (*context, temp, tx, ty, tx, ty, tw, th); } } } else { context->blendImageWarping (*imageToDraw, sx, sy, sw, sh, AffineTransform::translation ((float) -sx, (float) -sy) .scaled (dw / (float) sw, dh / (float) sh) .translated ((float) dx, (float) dy), state->colour.getFloatAlpha(), state->quality); } } } void Graphics::drawImageTransformed (const Image* const imageToDraw, int sourceClipX, int sourceClipY, int sourceClipWidth, int sourceClipHeight, const AffineTransform& transform, const bool fillAlphaChannelWithCurrentBrush) const throw() { if (imageToDraw != 0 && (! context->isClipEmpty()) && ! transform.isSingularity()) { if (transform.isIdentity()) { drawImage (imageToDraw, sourceClipX, sourceClipY, sourceClipWidth, sourceClipHeight, sourceClipX, sourceClipY, sourceClipWidth, sourceClipHeight, fillAlphaChannelWithCurrentBrush); } else if (fillAlphaChannelWithCurrentBrush) { Path p; p.addRectangle ((float) sourceClipX, (float) sourceClipY, (float) sourceClipWidth, (float) sourceClipHeight); p.applyTransform (transform); float dx, dy, dw, dh; p.getBounds (dx, dy, dw, dh); int tx = (int) dx; int ty = (int) dy; int tw = roundFloatToInt (dw) + 2; int th = roundFloatToInt (dh) + 2; if (context->getClipBounds().intersectRectangle (tx, ty, tw, th)) { Image temp (imageToDraw->getFormat(), tw, th, true); Graphics g (temp); g.setImageResamplingQuality (state->quality); g.drawImageTransformed (imageToDraw, sourceClipX, sourceClipY, sourceClipWidth, sourceClipHeight, transform.translated ((float) -tx, (float) -ty), false); SolidColourBrush colourBrush (state->colour); (state->brush != 0 ? *(state->brush) : (Brush&) colourBrush).paintAlphaChannel (*context, temp, tx, ty, tx, ty, tw, th); } } else { context->blendImageWarping (*imageToDraw, sourceClipX, sourceClipY, sourceClipWidth, sourceClipHeight, transform, state->colour.getFloatAlpha(), state->quality); } } } END_JUCE_NAMESPACE