1
0
Fork 0
mirror of https://github.com/juce-framework/JUCE.git synced 2026-01-09 23:34:20 +00:00
JUCE/examples/Demo/Source/Demos/GraphicsDemo.cpp

714 lines
25 KiB
C++

/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2017 - ROLI Ltd.
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 5 End-User License
Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
27th April 2017).
End User License Agreement: www.juce.com/juce-5-licence
Privacy Policy: www.juce.com/juce-5-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
#include "../JuceDemoHeader.h"
/** Holds the various toggle buttons for the animation modes. */
class ControllersComponent : public Component
{
public:
ControllersComponent()
{
setOpaque (true);
initialiseToggle (animatePosition, "Animate Position", true);
initialiseToggle (animateRotation, "Animate Rotation", true);
initialiseToggle (animateSize, "Animate Size", false);
initialiseToggle (animateShear, "Animate Shearing", false);
initialiseToggle (animateAlpha, "Animate Alpha", false);
initialiseToggle (clipToRectangle, "Clip to Rectangle", false);
initialiseToggle (clipToPath, "Clip to Path", false);
initialiseToggle (clipToImage, "Clip to Image", false);
initialiseToggle (quality, "Higher quality image interpolation", false);
}
void paint (Graphics& g) override
{
g.fillAll (getUIColourIfAvailable (LookAndFeel_V4::ColourScheme::UIColour::windowBackground));
}
void resized() override
{
auto r = getLocalBounds().reduced (4);
int buttonHeight = 22;
auto columns = r.removeFromTop (buttonHeight * 4);
auto col = columns.removeFromLeft (200);
animatePosition.setBounds (col.removeFromTop (buttonHeight));
animateRotation.setBounds (col.removeFromTop (buttonHeight));
animateSize.setBounds (col.removeFromTop (buttonHeight));
animateShear.setBounds (col.removeFromTop (buttonHeight));
columns.removeFromLeft (20);
col = columns.removeFromLeft (200);
animateAlpha.setBounds (col.removeFromTop (buttonHeight));
clipToRectangle.setBounds (col.removeFromTop (buttonHeight));
clipToPath.setBounds (col.removeFromTop (buttonHeight));
clipToImage.setBounds (col.removeFromTop (buttonHeight));
r.removeFromBottom (6);
quality.setBounds (r.removeFromTop (buttonHeight));
}
void initialiseToggle (ToggleButton& b, const char* name, bool on)
{
addAndMakeVisible (b);
b.setButtonText (name);
b.setToggleState (on, dontSendNotification);
}
ToggleButton animateRotation, animatePosition, animateAlpha, animateSize, animateShear;
ToggleButton clipToRectangle, clipToPath, clipToImage, quality;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ControllersComponent)
};
//==============================================================================
class GraphicsDemoBase : public Component
{
public:
GraphicsDemoBase (ControllersComponent& cc, const String& name)
: Component (name), controls (cc),
lastRenderStartTime (0),
averageTimeMs (0),
averageActualFPS (0)
{
displayFont = Font (Font::getDefaultMonospacedFontName(), 12.0f, Font::bold);
}
AffineTransform getTransform()
{
const float hw = 0.5f * getWidth();
const float hh = 0.5f * getHeight();
AffineTransform t;
if (controls.animateRotation.getToggleState())
t = t.rotated (rotation.getValue() * float_Pi * 2.0f);
if (controls.animateSize.getToggleState())
t = t.scaled (0.3f + size.getValue() * 2.0f);
if (controls.animatePosition.getToggleState())
t = t.translated (hw + hw * (offsetX.getValue() - 0.5f),
hh + hh * (offsetY.getValue() - 0.5f));
else
t = t.translated (hw, hh);
if (controls.animateShear.getToggleState())
t = t.sheared (shear.getValue() * 2.0f - 1.0f, 0.0f);
return t;
}
float getAlpha() const
{
if (controls.animateAlpha.getToggleState())
return alpha.getValue();
return 1.0f;
}
void paint (Graphics& g) override
{
double startTime = 0.0;
{
// A ScopedSaveState will return the Graphics context to the state it was at the time of
// construction when it goes out of scope. We use it here to avoid clipping the fps text
const Graphics::ScopedSaveState state (g);
if (controls.clipToRectangle.getToggleState()) clipToRectangle (g);
if (controls.clipToPath .getToggleState()) clipToPath (g);
if (controls.clipToImage .getToggleState()) clipToImage (g);
g.setImageResamplingQuality (controls.quality.getToggleState() ? Graphics::highResamplingQuality
: Graphics::mediumResamplingQuality);
// take a note of the time before the render
startTime = Time::getMillisecondCounterHiRes();
// then let the demo draw itself..
drawDemo (g);
}
double now = Time::getMillisecondCounterHiRes();
double filtering = 0.08;
const double elapsedMs = now - startTime;
averageTimeMs += (elapsedMs - averageTimeMs) * filtering;
const double sinceLastRender = now - lastRenderStartTime;
lastRenderStartTime = now;
const double effectiveFPS = 1000.0 / averageTimeMs;
const double actualFPS = sinceLastRender > 0 ? (1000.0 / sinceLastRender) : 0;
averageActualFPS += (actualFPS - averageActualFPS) * filtering;
GlyphArrangement ga;
ga.addFittedText (displayFont,
"Time: " + String (averageTimeMs, 2)
+ " ms\nEffective FPS: " + String (effectiveFPS, 1)
+ "\nActual FPS: " + String (averageActualFPS, 1),
0, 10.0f, getWidth() - 10.0f, (float) getHeight(), Justification::topRight, 3);
g.setColour (Colours::white.withAlpha (0.5f));
g.fillRect (ga.getBoundingBox (0, ga.getNumGlyphs(), true).getSmallestIntegerContainer().expanded (4));
g.setColour (Colours::black);
ga.draw (g);
}
virtual void drawDemo (Graphics&) = 0;
void clipToRectangle (Graphics& g)
{
int w = getWidth() / 2;
int h = getHeight() / 2;
int x = (int) (w * clipRectX.getValue());
int y = (int) (h * clipRectY.getValue());
g.reduceClipRegion (x, y, w, h);
}
void clipToPath (Graphics& g)
{
float pathSize = (float) jmin (getWidth(), getHeight());
Path p;
p.addStar (Point<float> (clipPathX.getValue(),
clipPathY.getValue()) * pathSize,
7,
pathSize * (0.5f + clipPathDepth.getValue()),
pathSize * 0.5f,
clipPathAngle.getValue());
g.reduceClipRegion (p, AffineTransform());
}
void clipToImage (Graphics& g)
{
if (! clipImage.isValid())
createClipImage();
AffineTransform transform (AffineTransform::translation (clipImage.getWidth() / -2.0f,
clipImage.getHeight() / -2.0f)
.rotated (clipImageAngle.getValue() * float_Pi * 2.0f)
.scaled (2.0f + clipImageSize.getValue() * 3.0f)
.translated (getWidth() * 0.5f,
getHeight() * 0.5f));
g.reduceClipRegion (clipImage, transform);
}
void createClipImage()
{
clipImage = Image (Image::ARGB, 300, 300, true);
Graphics g (clipImage);
g.setGradientFill (ColourGradient (Colours::transparentBlack, 0, 0,
Colours::black, 0, 300, false));
for (int i = 0; i < 20; ++i)
g.fillRect (Random::getSystemRandom().nextInt (200),
Random::getSystemRandom().nextInt (200),
Random::getSystemRandom().nextInt (100),
Random::getSystemRandom().nextInt (100));
}
//==============================================================================
ControllersComponent& controls;
SlowerBouncingNumber offsetX, offsetY, rotation, size, shear, alpha, clipRectX,
clipRectY, clipPathX, clipPathY, clipPathDepth, clipPathAngle,
clipImageX, clipImageY, clipImageAngle, clipImageSize;
double lastRenderStartTime, averageTimeMs, averageActualFPS;
Image clipImage;
Font displayFont;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (GraphicsDemoBase)
};
//==============================================================================
class RectangleFillTypesDemo : public GraphicsDemoBase
{
public:
RectangleFillTypesDemo (ControllersComponent& cc)
: GraphicsDemoBase (cc, "Fill Types: Rectangles"),
colour1 (Colours::red),
colour2 (Colours::green)
{
}
void drawDemo (Graphics& g) override
{
g.addTransform (getTransform());
const int rectSize = jmin (getWidth(), getHeight()) / 2 - 20;
g.setColour (colour1.withAlpha (getAlpha()));
g.fillRect (-rectSize, -rectSize, rectSize, rectSize);
g.setGradientFill (ColourGradient (colour1, 10.0f, (float) -rectSize,
colour2, 10.0f + rectSize, 0.0f, false));
g.setOpacity (getAlpha());
g.fillRect (10, -rectSize, rectSize, rectSize);
g.setGradientFill (ColourGradient (colour1, rectSize * -0.5f, 10.0f + rectSize * 0.5f,
colour2, 0, 10.0f + rectSize, true));
g.setOpacity (getAlpha());
g.fillRect (-rectSize, 10, rectSize, rectSize);
g.setGradientFill (ColourGradient (colour1, 10.0f, 10.0f,
colour2, 10.0f + rectSize, 10.0f + rectSize, false));
g.setOpacity (getAlpha());
g.drawRect (10, 10, rectSize, rectSize, 5);
}
Colour colour1, colour2;
};
//==============================================================================
class PathsDemo : public GraphicsDemoBase
{
public:
PathsDemo (ControllersComponent& cc, bool linear, bool radial)
: GraphicsDemoBase (cc, String ("Paths") + (radial ? ": Radial Gradients"
: (linear ? ": Linear Gradients"
: ": Solid"))),
useLinearGradient (linear), useRadialGradient (radial)
{
logoPath = MainAppWindow::getJUCELogoPath();
// rescale the logo path so that it's centred about the origin and has the right size.
logoPath.applyTransform (RectanglePlacement (RectanglePlacement::centred)
.getTransformToFit (logoPath.getBounds(),
Rectangle<float> (-120.0f, -120.0f, 240.0f, 240.0f)));
// Surround it with some other shapes..
logoPath.addStar (Point<float> (-300.0f, -50.0f), 7, 30.0f, 70.0f, 0.1f);
logoPath.addStar (Point<float> (300.0f, 50.0f), 6, 40.0f, 70.0f, 0.1f);
logoPath.addEllipse (-100.0f, 150.0f, 200.0f, 140.0f);
logoPath.addRectangle (-100.0f, -280.0f, 200.0f, 140.0f);
}
void drawDemo (Graphics& g) override
{
const Path& p = logoPath;
if (useLinearGradient || useRadialGradient)
{
Colour c1 (gradientColours[0].getValue(), gradientColours[1].getValue(), gradientColours[2].getValue(), 1.0f);
Colour c2 (gradientColours[3].getValue(), gradientColours[4].getValue(), gradientColours[5].getValue(), 1.0f);
Colour c3 (gradientColours[6].getValue(), gradientColours[7].getValue(), gradientColours[8].getValue(), 1.0f);
float x1 = gradientPositions[0].getValue() * getWidth() * 0.25f;
float y1 = gradientPositions[1].getValue() * getHeight() * 0.25f;
float x2 = gradientPositions[2].getValue() * getWidth() * 0.75f;
float y2 = gradientPositions[3].getValue() * getHeight() * 0.75f;
ColourGradient gradient (c1, x1, y1,
c2, x2, y2,
useRadialGradient);
gradient.addColour (gradientIntermediate.getValue(), c3);
g.setGradientFill (gradient);
}
else
{
g.setColour (Colours::blue);
}
g.setOpacity (getAlpha());
g.fillPath (p, getTransform());
}
Path logoPath;
bool useLinearGradient, useRadialGradient;
SlowerBouncingNumber gradientColours[9], gradientPositions[4], gradientIntermediate;
};
//==============================================================================
class StrokesDemo : public GraphicsDemoBase
{
public:
StrokesDemo (ControllersComponent& cc)
: GraphicsDemoBase (cc, "Paths: Stroked")
{
}
void drawDemo (Graphics& g) override
{
float w = (float) getWidth();
float h = (float) getHeight();
Path p;
p.startNewSubPath (points[0].getValue() * w,
points[1].getValue() * h);
for (int i = 2; i < numElementsInArray (points); i += 4)
p.quadraticTo (points[i].getValue() * w,
points[i + 1].getValue() * h,
points[i + 2].getValue() * w,
points[i + 3].getValue() * h);
p.closeSubPath();
PathStrokeType stroke (0.5f + 10.0f * thickness.getValue());
g.setColour (Colours::purple.withAlpha (getAlpha()));
g.strokePath (p, stroke, AffineTransform());
}
SlowerBouncingNumber points[2 + 4 * 8], thickness;
};
//==============================================================================
class ImagesRenderingDemo : public GraphicsDemoBase
{
public:
ImagesRenderingDemo (ControllersComponent& cc, bool argb_, bool tiled_)
: GraphicsDemoBase (cc, String ("Images") + (argb_ ? ": ARGB" : ": RGB") + (tiled_ ? " Tiled" : String() )),
isArgb (argb_), isTiled (tiled_)
{
argbImage = ImageFileFormat::loadFrom (BinaryData::juce_icon_png, (size_t) BinaryData::juce_icon_pngSize);
rgbImage = ImageFileFormat::loadFrom (BinaryData::portmeirion_jpg, (size_t) BinaryData::portmeirion_jpgSize);
}
void drawDemo (Graphics& g) override
{
Image image = isArgb ? argbImage : rgbImage;
AffineTransform transform (AffineTransform::translation ((float) (image.getWidth() / -2),
(float) (image.getHeight() / -2))
.followedBy (getTransform()));
if (isTiled)
{
FillType fill (image, transform);
fill.setOpacity (getAlpha());
g.setFillType (fill);
g.fillAll();
}
else
{
g.setOpacity (getAlpha());
g.drawImageTransformed (image, transform, false);
}
}
bool isArgb, isTiled;
Image rgbImage, argbImage;
};
//==============================================================================
class GlyphsDemo : public GraphicsDemoBase
{
public:
GlyphsDemo (ControllersComponent& cc)
: GraphicsDemoBase (cc, "Glyphs")
{
glyphs.addFittedText (Font (20.0f), "The Quick Brown Fox Jumped Over The Lazy Dog",
-120, -50, 240, 100, Justification::centred, 2, 1.0f);
}
void drawDemo (Graphics& g) override
{
g.setColour (Colours::black.withAlpha (getAlpha()));
glyphs.draw (g, getTransform());
}
GlyphArrangement glyphs;
};
//==============================================================================
class SVGDemo : public GraphicsDemoBase
{
public:
SVGDemo (ControllersComponent& cc)
: GraphicsDemoBase (cc, "SVG")
{
createSVGDrawable();
}
void drawDemo (Graphics& g) override
{
if (Time::getCurrentTime().toMilliseconds() > lastSVGLoadTime.toMilliseconds() + 2000)
createSVGDrawable();
svgDrawable->draw (g, getAlpha(), getTransform());
}
void createSVGDrawable()
{
lastSVGLoadTime = Time::getCurrentTime();
MemoryInputStream iconsFileStream (BinaryData::icons_zip, BinaryData::icons_zipSize, false);
ZipFile icons (&iconsFileStream, false);
// Load a random SVG file from our embedded icons.zip file.
const ScopedPointer<InputStream> svgFileStream (icons.createStreamForEntry (Random::getSystemRandom().nextInt (icons.getNumEntries())));
if (svgFileStream != nullptr)
{
svgDrawable = dynamic_cast<DrawableComposite*> (Drawable::createFromImageDataStream (*svgFileStream));
if (svgDrawable != nullptr)
{
// to make our icon the right size, we'll set its bounding box to the size and position that we want.
svgDrawable->setBoundingBox (RelativeParallelogram (Point<float> (-100, -100),
Point<float> (100, -100),
Point<float> (-100, 100)));
}
}
}
Time lastSVGLoadTime;
ScopedPointer<DrawableComposite> svgDrawable;
};
//==============================================================================
class LinesDemo : public GraphicsDemoBase
{
public:
LinesDemo (ControllersComponent& cc)
: GraphicsDemoBase (cc, "Lines")
{
}
void drawDemo (Graphics& g) override
{
{
RectangleList<float> verticalLines;
verticalLines.ensureStorageAllocated (getWidth());
float pos = offset.getValue();
for (int x = 0; x < getWidth(); ++x)
{
float y = getHeight() * 0.3f;
float length = y * std::abs (std::sin (x / 100.0f + 2.0f * pos));
verticalLines.addWithoutMerging (Rectangle<float> ((float) x, y - length * 0.5f, 1.0f, length));
}
g.setColour (Colours::blue.withAlpha (getAlpha()));
g.fillRectList (verticalLines);
}
{
RectangleList<float> horizontalLines;
horizontalLines.ensureStorageAllocated (getHeight());
float pos = offset.getValue();
for (int y = 0; y < getHeight(); ++y)
{
float x = getWidth() * 0.3f;
float length = x * std::abs (std::sin (y / 100.0f + 2.0f * pos));
horizontalLines.addWithoutMerging (Rectangle<float> (x - length * 0.5f, (float) y, length, 1.0f));
}
g.setColour (Colours::green.withAlpha (getAlpha()));
g.fillRectList (horizontalLines);
}
g.setColour (Colours::red.withAlpha (getAlpha()));
const float w = (float) getWidth();
const float h = (float) getHeight();
g.drawLine (positions[0].getValue() * w,
positions[1].getValue() * h,
positions[2].getValue() * w,
positions[3].getValue() * h);
g.drawLine (positions[4].getValue() * w,
positions[5].getValue() * h,
positions[6].getValue() * w,
positions[7].getValue() * h);
}
SlowerBouncingNumber offset, positions[8];
};
//==============================================================================
class DemoHolderComponent : public Component,
private Timer
{
public:
DemoHolderComponent()
: currentDemo (nullptr)
{
setOpaque (true);
}
void paint (Graphics& g) override
{
g.fillCheckerBoard (getLocalBounds(), 48, 48,
Colours::lightgrey, Colours::white);
}
void timerCallback() override
{
if (currentDemo != nullptr)
currentDemo->repaint();
}
void setDemo (GraphicsDemoBase* newDemo)
{
if (currentDemo != nullptr)
removeChildComponent (currentDemo);
currentDemo = newDemo;
if (currentDemo != nullptr)
{
addAndMakeVisible (currentDemo);
startTimerHz (60);
resized();
}
}
void resized() override
{
if (currentDemo != nullptr)
currentDemo->setBounds (getLocalBounds());
}
private:
GraphicsDemoBase* currentDemo;
};
//==============================================================================
class TestListComponent : public Component,
private ListBoxModel
{
public:
TestListComponent (DemoHolderComponent& holder, ControllersComponent& controls)
: demoHolder (holder)
{
demos.add (new PathsDemo (controls, false, true));
demos.add (new PathsDemo (controls, true, false));
demos.add (new PathsDemo (controls, false, false));
demos.add (new RectangleFillTypesDemo (controls));
demos.add (new StrokesDemo (controls));
demos.add (new ImagesRenderingDemo (controls, false, false));
demos.add (new ImagesRenderingDemo (controls, false, true));
demos.add (new ImagesRenderingDemo (controls, true, false));
demos.add (new ImagesRenderingDemo (controls, true, true));
demos.add (new GlyphsDemo (controls));
demos.add (new SVGDemo (controls));
demos.add (new LinesDemo (controls));
addAndMakeVisible (listBox);
listBox.setModel (this);
listBox.selectRow (0);
}
void resized()
{
listBox.setBounds (getLocalBounds());
}
int getNumRows()
{
return demos.size();
}
void paintListBoxItem (int rowNumber, Graphics& g, int width, int height, bool rowIsSelected)
{
if (auto* demo = demos[rowNumber])
{
if (rowIsSelected)
g.fillAll (Colour::contrasting (findColour (ListBox::textColourId),
findColour (ListBox::backgroundColourId)));
g.setColour (findColour (ListBox::textColourId));
g.setFont (14.0f);
g.drawFittedText (demo->getName(), 8, 0, width - 10, height, Justification::centredLeft, 2);
}
}
void selectedRowsChanged (int lastRowSelected)
{
demoHolder.setDemo (demos [lastRowSelected]);
}
private:
DemoHolderComponent& demoHolder;
ListBox listBox;
OwnedArray<GraphicsDemoBase> demos;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (TestListComponent)
};
//==============================================================================
class GraphicsDemo : public Component
{
public:
GraphicsDemo()
: testList (demoHolder, controllersComponent)
{
setOpaque (true);
addAndMakeVisible (demoHolder);
addAndMakeVisible (controllersComponent);
addAndMakeVisible (performanceDisplay);
addAndMakeVisible (testList);
}
void paint (Graphics& g) override
{
g.fillAll (Colours::grey);
}
void resized() override
{
auto area = getLocalBounds();
controllersComponent.setBounds (area.removeFromBottom (150));
testList.setBounds (area.removeFromRight (150));
demoHolder.setBounds (area);
performanceDisplay.setBounds (area.removeFromTop (20).removeFromRight (100));
}
private:
ControllersComponent controllersComponent;
DemoHolderComponent demoHolder;
Label performanceDisplay;
TestListComponent testList;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (GraphicsDemo)
};
// This static object will register this demo type in a global list of demos..
static JuceDemoType<GraphicsDemo> demo ("20 Graphics: 2D Rendering");