The JUCE cross-platform C++ framework, with DISTRHO/KXStudio specific changes
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

705 lines
22KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE library.
  4. Copyright (c) 2017 - ROLI Ltd.
  5. JUCE is an open source library subject to commercial or open-source
  6. licensing.
  7. By using JUCE, you agree to the terms of both the JUCE 5 End-User License
  8. Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
  9. 27th April 2017).
  10. End User License Agreement: www.juce.com/juce-5-licence
  11. Privacy Policy: www.juce.com/juce-5-privacy-policy
  12. Or: You may also use this code under the terms of the GPL v3 (see
  13. www.gnu.org/licenses).
  14. JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
  15. EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
  16. DISCLAIMED.
  17. ==============================================================================
  18. */
  19. namespace
  20. {
  21. template <typename Type>
  22. Rectangle<Type> coordsToRectangle (Type x, Type y, Type w, Type h) noexcept
  23. {
  24. #if JUCE_DEBUG
  25. const int maxVal = 0x3fffffff;
  26. jassert ((int) x >= -maxVal && (int) x <= maxVal
  27. && (int) y >= -maxVal && (int) y <= maxVal
  28. && (int) w >= 0 && (int) w <= maxVal
  29. && (int) h >= 0 && (int) h <= maxVal);
  30. #endif
  31. return Rectangle<Type> (x, y, w, h);
  32. }
  33. }
  34. //==============================================================================
  35. LowLevelGraphicsContext::LowLevelGraphicsContext() {}
  36. LowLevelGraphicsContext::~LowLevelGraphicsContext() {}
  37. //==============================================================================
  38. Graphics::Graphics (const Image& imageToDrawOnto)
  39. : context (*imageToDrawOnto.createLowLevelContext()),
  40. contextToDelete (&context),
  41. saveStatePending (false)
  42. {
  43. jassert (imageToDrawOnto.isValid()); // Can't draw into a null image!
  44. }
  45. Graphics::Graphics (LowLevelGraphicsContext& internalContext) noexcept
  46. : context (internalContext),
  47. saveStatePending (false)
  48. {
  49. }
  50. Graphics::~Graphics()
  51. {
  52. }
  53. //==============================================================================
  54. void Graphics::resetToDefaultState()
  55. {
  56. saveStateIfPending();
  57. context.setFill (FillType());
  58. context.setFont (Font());
  59. context.setInterpolationQuality (Graphics::mediumResamplingQuality);
  60. }
  61. bool Graphics::isVectorDevice() const
  62. {
  63. return context.isVectorDevice();
  64. }
  65. bool Graphics::reduceClipRegion (Rectangle<int> area)
  66. {
  67. saveStateIfPending();
  68. return context.clipToRectangle (area);
  69. }
  70. bool Graphics::reduceClipRegion (const int x, const int y, const int w, const int h)
  71. {
  72. return reduceClipRegion (coordsToRectangle (x, y, w, h));
  73. }
  74. bool Graphics::reduceClipRegion (const RectangleList<int>& clipRegion)
  75. {
  76. saveStateIfPending();
  77. return context.clipToRectangleList (clipRegion);
  78. }
  79. bool Graphics::reduceClipRegion (const Path& path, const AffineTransform& transform)
  80. {
  81. saveStateIfPending();
  82. context.clipToPath (path, transform);
  83. return ! context.isClipEmpty();
  84. }
  85. bool Graphics::reduceClipRegion (const Image& image, const AffineTransform& transform)
  86. {
  87. saveStateIfPending();
  88. context.clipToImageAlpha (image, transform);
  89. return ! context.isClipEmpty();
  90. }
  91. void Graphics::excludeClipRegion (Rectangle<int> rectangleToExclude)
  92. {
  93. saveStateIfPending();
  94. context.excludeClipRectangle (rectangleToExclude);
  95. }
  96. bool Graphics::isClipEmpty() const
  97. {
  98. return context.isClipEmpty();
  99. }
  100. Rectangle<int> Graphics::getClipBounds() const
  101. {
  102. return context.getClipBounds();
  103. }
  104. void Graphics::saveState()
  105. {
  106. saveStateIfPending();
  107. saveStatePending = true;
  108. }
  109. void Graphics::restoreState()
  110. {
  111. if (saveStatePending)
  112. saveStatePending = false;
  113. else
  114. context.restoreState();
  115. }
  116. void Graphics::saveStateIfPending()
  117. {
  118. if (saveStatePending)
  119. {
  120. saveStatePending = false;
  121. context.saveState();
  122. }
  123. }
  124. void Graphics::setOrigin (Point<int> newOrigin)
  125. {
  126. saveStateIfPending();
  127. context.setOrigin (newOrigin);
  128. }
  129. void Graphics::setOrigin (int x, int y)
  130. {
  131. setOrigin (Point<int> (x, y));
  132. }
  133. void Graphics::addTransform (const AffineTransform& transform)
  134. {
  135. saveStateIfPending();
  136. context.addTransform (transform);
  137. }
  138. bool Graphics::clipRegionIntersects (Rectangle<int> area) const
  139. {
  140. return context.clipRegionIntersects (area);
  141. }
  142. void Graphics::beginTransparencyLayer (float layerOpacity)
  143. {
  144. saveStateIfPending();
  145. context.beginTransparencyLayer (layerOpacity);
  146. }
  147. void Graphics::endTransparencyLayer()
  148. {
  149. context.endTransparencyLayer();
  150. }
  151. //==============================================================================
  152. void Graphics::setColour (Colour newColour)
  153. {
  154. saveStateIfPending();
  155. context.setFill (newColour);
  156. }
  157. void Graphics::setOpacity (const float newOpacity)
  158. {
  159. saveStateIfPending();
  160. context.setOpacity (newOpacity);
  161. }
  162. void Graphics::setGradientFill (const ColourGradient& gradient)
  163. {
  164. setFillType (gradient);
  165. }
  166. void Graphics::setTiledImageFill (const Image& imageToUse, const int anchorX, const int anchorY, const float opacity)
  167. {
  168. saveStateIfPending();
  169. context.setFill (FillType (imageToUse, AffineTransform::translation ((float) anchorX, (float) anchorY)));
  170. context.setOpacity (opacity);
  171. }
  172. void Graphics::setFillType (const FillType& newFill)
  173. {
  174. saveStateIfPending();
  175. context.setFill (newFill);
  176. }
  177. //==============================================================================
  178. void Graphics::setFont (const Font& newFont)
  179. {
  180. saveStateIfPending();
  181. context.setFont (newFont);
  182. }
  183. void Graphics::setFont (const float newFontHeight)
  184. {
  185. setFont (context.getFont().withHeight (newFontHeight));
  186. }
  187. Font Graphics::getCurrentFont() const
  188. {
  189. return context.getFont();
  190. }
  191. //==============================================================================
  192. void Graphics::drawSingleLineText (const String& text, const int startX, const int baselineY,
  193. Justification justification) const
  194. {
  195. if (text.isNotEmpty())
  196. {
  197. // Don't pass any vertical placement flags to this method - they'll be ignored.
  198. jassert (justification.getOnlyVerticalFlags() == 0);
  199. const int flags = justification.getOnlyHorizontalFlags();
  200. if (flags == Justification::right)
  201. {
  202. if (startX < context.getClipBounds().getX())
  203. return;
  204. }
  205. else if (flags == Justification::left)
  206. if (startX > context.getClipBounds().getRight())
  207. return;
  208. GlyphArrangement arr;
  209. arr.addLineOfText (context.getFont(), text, (float) startX, (float) baselineY);
  210. if (flags != Justification::left)
  211. {
  212. float w = arr.getBoundingBox (0, -1, true).getWidth();
  213. if ((flags & (Justification::horizontallyCentred | Justification::horizontallyJustified)) != 0)
  214. w /= 2.0f;
  215. arr.draw (*this, AffineTransform::translation (-w, 0));
  216. }
  217. else
  218. {
  219. arr.draw (*this);
  220. }
  221. }
  222. }
  223. void Graphics::drawMultiLineText (const String& text, const int startX,
  224. const int baselineY, const int maximumLineWidth) const
  225. {
  226. if (text.isNotEmpty()
  227. && startX < context.getClipBounds().getRight())
  228. {
  229. GlyphArrangement arr;
  230. arr.addJustifiedText (context.getFont(), text,
  231. (float) startX, (float) baselineY, (float) maximumLineWidth,
  232. Justification::left);
  233. arr.draw (*this);
  234. }
  235. }
  236. void Graphics::drawText (const String& text, Rectangle<float> area,
  237. Justification justificationType, bool useEllipsesIfTooBig) const
  238. {
  239. if (text.isNotEmpty() && context.clipRegionIntersects (area.getSmallestIntegerContainer()))
  240. {
  241. GlyphArrangement arr;
  242. arr.addCurtailedLineOfText (context.getFont(), text, 0.0f, 0.0f,
  243. area.getWidth(), useEllipsesIfTooBig);
  244. arr.justifyGlyphs (0, arr.getNumGlyphs(),
  245. area.getX(), area.getY(), area.getWidth(), area.getHeight(),
  246. justificationType);
  247. arr.draw (*this);
  248. }
  249. }
  250. void Graphics::drawText (const String& text, Rectangle<int> area,
  251. Justification justificationType, bool useEllipsesIfTooBig) const
  252. {
  253. drawText (text, area.toFloat(), justificationType, useEllipsesIfTooBig);
  254. }
  255. void Graphics::drawText (const String& text, int x, int y, int width, int height,
  256. Justification justificationType, const bool useEllipsesIfTooBig) const
  257. {
  258. drawText (text, coordsToRectangle (x, y, width, height), justificationType, useEllipsesIfTooBig);
  259. }
  260. void Graphics::drawFittedText (const String& text, Rectangle<int> area,
  261. Justification justification,
  262. const int maximumNumberOfLines,
  263. const float minimumHorizontalScale) const
  264. {
  265. if (text.isNotEmpty() && (! area.isEmpty()) && context.clipRegionIntersects (area))
  266. {
  267. GlyphArrangement arr;
  268. arr.addFittedText (context.getFont(), text,
  269. (float) area.getX(), (float) area.getY(),
  270. (float) area.getWidth(), (float) area.getHeight(),
  271. justification,
  272. maximumNumberOfLines,
  273. minimumHorizontalScale);
  274. arr.draw (*this);
  275. }
  276. }
  277. void Graphics::drawFittedText (const String& text, int x, int y, int width, int height,
  278. Justification justification,
  279. const int maximumNumberOfLines,
  280. const float minimumHorizontalScale) const
  281. {
  282. drawFittedText (text, coordsToRectangle (x, y, width, height),
  283. justification, maximumNumberOfLines, minimumHorizontalScale);
  284. }
  285. //==============================================================================
  286. void Graphics::fillRect (Rectangle<int> r) const
  287. {
  288. context.fillRect (r, false);
  289. }
  290. void Graphics::fillRect (Rectangle<float> r) const
  291. {
  292. context.fillRect (r);
  293. }
  294. void Graphics::fillRect (int x, int y, int width, int height) const
  295. {
  296. context.fillRect (coordsToRectangle (x, y, width, height), false);
  297. }
  298. void Graphics::fillRect (float x, float y, float width, float height) const
  299. {
  300. fillRect (coordsToRectangle (x, y, width, height));
  301. }
  302. void Graphics::fillRectList (const RectangleList<float>& rectangles) const
  303. {
  304. context.fillRectList (rectangles);
  305. }
  306. void Graphics::fillRectList (const RectangleList<int>& rects) const
  307. {
  308. for (auto& r : rects)
  309. context.fillRect (r, false);
  310. }
  311. void Graphics::setPixel (int x, int y) const
  312. {
  313. context.fillRect (coordsToRectangle (x, y, 1, 1), false);
  314. }
  315. void Graphics::fillAll() const
  316. {
  317. fillRect (context.getClipBounds());
  318. }
  319. void Graphics::fillAll (Colour colourToUse) const
  320. {
  321. if (! colourToUse.isTransparent())
  322. {
  323. const Rectangle<int> clip (context.getClipBounds());
  324. context.saveState();
  325. context.setFill (colourToUse);
  326. context.fillRect (clip, false);
  327. context.restoreState();
  328. }
  329. }
  330. //==============================================================================
  331. void Graphics::fillPath (const Path& path) const
  332. {
  333. if (! (context.isClipEmpty() || path.isEmpty()))
  334. context.fillPath (path, AffineTransform());
  335. }
  336. void Graphics::fillPath (const Path& path, const AffineTransform& transform) const
  337. {
  338. if (! (context.isClipEmpty() || path.isEmpty()))
  339. context.fillPath (path, transform);
  340. }
  341. void Graphics::strokePath (const Path& path,
  342. const PathStrokeType& strokeType,
  343. const AffineTransform& transform) const
  344. {
  345. Path stroke;
  346. strokeType.createStrokedPath (stroke, path, transform, context.getPhysicalPixelScaleFactor());
  347. fillPath (stroke);
  348. }
  349. //==============================================================================
  350. void Graphics::drawRect (float x, float y, float width, float height, float lineThickness) const
  351. {
  352. drawRect (coordsToRectangle (x, y, width, height), lineThickness);
  353. }
  354. void Graphics::drawRect (int x, int y, int width, int height, int lineThickness) const
  355. {
  356. drawRect (coordsToRectangle (x, y, width, height), lineThickness);
  357. }
  358. void Graphics::drawRect (Rectangle<int> r, int lineThickness) const
  359. {
  360. drawRect (r.toFloat(), (float) lineThickness);
  361. }
  362. void Graphics::drawRect (Rectangle<float> r, const float lineThickness) const
  363. {
  364. jassert (r.getWidth() >= 0.0f && r.getHeight() >= 0.0f);
  365. RectangleList<float> rects;
  366. rects.addWithoutMerging (r.removeFromTop (lineThickness));
  367. rects.addWithoutMerging (r.removeFromBottom (lineThickness));
  368. rects.addWithoutMerging (r.removeFromLeft (lineThickness));
  369. rects.addWithoutMerging (r.removeFromRight (lineThickness));
  370. context.fillRectList (rects);
  371. }
  372. //==============================================================================
  373. void Graphics::fillEllipse (Rectangle<float> area) const
  374. {
  375. Path p;
  376. p.addEllipse (area);
  377. fillPath (p);
  378. }
  379. void Graphics::fillEllipse (float x, float y, float w, float h) const
  380. {
  381. fillEllipse (coordsToRectangle (x, y, w, h));
  382. }
  383. void Graphics::drawEllipse (float x, float y, float width, float height, float lineThickness) const
  384. {
  385. drawEllipse (coordsToRectangle (x, y, width, height), lineThickness);
  386. }
  387. void Graphics::drawEllipse (Rectangle<float> area, float lineThickness) const
  388. {
  389. Path p;
  390. if (area.getWidth() == area.getHeight())
  391. {
  392. // For a circle, we can avoid having to generate a stroke
  393. p.addEllipse (area.expanded (lineThickness * 0.5f));
  394. p.addEllipse (area.reduced (lineThickness * 0.5f));
  395. p.setUsingNonZeroWinding (false);
  396. fillPath (p);
  397. }
  398. else
  399. {
  400. p.addEllipse (area);
  401. strokePath (p, PathStrokeType (lineThickness));
  402. }
  403. }
  404. void Graphics::fillRoundedRectangle (float x, float y, float width, float height, float cornerSize) const
  405. {
  406. fillRoundedRectangle (coordsToRectangle (x, y, width, height), cornerSize);
  407. }
  408. void Graphics::fillRoundedRectangle (Rectangle<float> r, const float cornerSize) const
  409. {
  410. Path p;
  411. p.addRoundedRectangle (r, cornerSize);
  412. fillPath (p);
  413. }
  414. void Graphics::drawRoundedRectangle (float x, float y, float width, float height,
  415. float cornerSize, float lineThickness) const
  416. {
  417. drawRoundedRectangle (coordsToRectangle (x, y, width, height), cornerSize, lineThickness);
  418. }
  419. void Graphics::drawRoundedRectangle (Rectangle<float> r, float cornerSize, float lineThickness) const
  420. {
  421. Path p;
  422. p.addRoundedRectangle (r, cornerSize);
  423. strokePath (p, PathStrokeType (lineThickness));
  424. }
  425. void Graphics::drawArrow (const Line<float>& line, float lineThickness, float arrowheadWidth, float arrowheadLength) const
  426. {
  427. Path p;
  428. p.addArrow (line, lineThickness, arrowheadWidth, arrowheadLength);
  429. fillPath (p);
  430. }
  431. void Graphics::fillCheckerBoard (Rectangle<int> area,
  432. const int checkWidth, const int checkHeight,
  433. Colour colour1, Colour colour2) const
  434. {
  435. jassert (checkWidth > 0 && checkHeight > 0); // can't be zero or less!
  436. if (checkWidth > 0 && checkHeight > 0)
  437. {
  438. context.saveState();
  439. if (colour1 == colour2)
  440. {
  441. context.setFill (colour1);
  442. context.fillRect (area, false);
  443. }
  444. else
  445. {
  446. const Rectangle<int> clipped (context.getClipBounds().getIntersection (area));
  447. if (! clipped.isEmpty())
  448. {
  449. context.clipToRectangle (clipped);
  450. const int checkNumX = (clipped.getX() - area.getX()) / checkWidth;
  451. const int checkNumY = (clipped.getY() - area.getY()) / checkHeight;
  452. const int startX = area.getX() + checkNumX * checkWidth;
  453. const int startY = area.getY() + checkNumY * checkHeight;
  454. const int right = clipped.getRight();
  455. const int bottom = clipped.getBottom();
  456. for (int i = 0; i < 2; ++i)
  457. {
  458. context.setFill (i == ((checkNumX ^ checkNumY) & 1) ? colour1 : colour2);
  459. int cy = i;
  460. for (int y = startY; y < bottom; y += checkHeight)
  461. for (int x = startX + (cy++ & 1) * checkWidth; x < right; x += checkWidth * 2)
  462. context.fillRect (Rectangle<int> (x, y, checkWidth, checkHeight), false);
  463. }
  464. }
  465. }
  466. context.restoreState();
  467. }
  468. }
  469. //==============================================================================
  470. void Graphics::drawVerticalLine (const int x, float top, float bottom) const
  471. {
  472. if (top < bottom)
  473. context.fillRect (Rectangle<float> ((float) x, top, 1.0f, bottom - top));
  474. }
  475. void Graphics::drawHorizontalLine (const int y, float left, float right) const
  476. {
  477. if (left < right)
  478. context.fillRect (Rectangle<float> (left, (float) y, right - left, 1.0f));
  479. }
  480. void Graphics::drawLine (const Line<float>& line) const
  481. {
  482. context.drawLine (line);
  483. }
  484. void Graphics::drawLine (float x1, float y1, float x2, float y2) const
  485. {
  486. context.drawLine (Line<float> (x1, y1, x2, y2));
  487. }
  488. void Graphics::drawLine (float x1, float y1, float x2, float y2, float lineThickness) const
  489. {
  490. drawLine (Line<float> (x1, y1, x2, y2), lineThickness);
  491. }
  492. void Graphics::drawLine (const Line<float>& line, const float lineThickness) const
  493. {
  494. Path p;
  495. p.addLineSegment (line, lineThickness);
  496. fillPath (p);
  497. }
  498. void Graphics::drawDashedLine (const Line<float>& line, const float* const dashLengths,
  499. const int numDashLengths, const float lineThickness, int n) const
  500. {
  501. jassert (n >= 0 && n < numDashLengths); // your start index must be valid!
  502. const Point<double> delta ((line.getEnd() - line.getStart()).toDouble());
  503. const double totalLen = delta.getDistanceFromOrigin();
  504. if (totalLen >= 0.1)
  505. {
  506. const double onePixAlpha = 1.0 / totalLen;
  507. for (double alpha = 0.0; alpha < 1.0;)
  508. {
  509. jassert (dashLengths[n] > 0); // can't have zero-length dashes!
  510. const double lastAlpha = alpha;
  511. alpha += dashLengths [n] * onePixAlpha;
  512. n = (n + 1) % numDashLengths;
  513. if ((n & 1) != 0)
  514. {
  515. const Line<float> segment (line.getStart() + (delta * lastAlpha).toFloat(),
  516. line.getStart() + (delta * jmin (1.0, alpha)).toFloat());
  517. if (lineThickness != 1.0f)
  518. drawLine (segment, lineThickness);
  519. else
  520. context.drawLine (segment);
  521. }
  522. }
  523. }
  524. }
  525. //==============================================================================
  526. void Graphics::setImageResamplingQuality (const Graphics::ResamplingQuality newQuality)
  527. {
  528. saveStateIfPending();
  529. context.setInterpolationQuality (newQuality);
  530. }
  531. //==============================================================================
  532. void Graphics::drawImageAt (const Image& imageToDraw, int x, int y, bool fillAlphaChannel) const
  533. {
  534. drawImageTransformed (imageToDraw,
  535. AffineTransform::translation ((float) x, (float) y),
  536. fillAlphaChannel);
  537. }
  538. void Graphics::drawImage (const Image& imageToDraw, Rectangle<float> targetArea,
  539. RectanglePlacement placementWithinTarget, bool fillAlphaChannelWithCurrentBrush) const
  540. {
  541. if (imageToDraw.isValid())
  542. drawImageTransformed (imageToDraw,
  543. placementWithinTarget.getTransformToFit (imageToDraw.getBounds().toFloat(), targetArea),
  544. fillAlphaChannelWithCurrentBrush);
  545. }
  546. void Graphics::drawImageWithin (const Image& imageToDraw, int dx, int dy, int dw, int dh,
  547. RectanglePlacement placementWithinTarget, bool fillAlphaChannelWithCurrentBrush) const
  548. {
  549. drawImage (imageToDraw, coordsToRectangle (dx, dy, dw, dh).toFloat(),
  550. placementWithinTarget, fillAlphaChannelWithCurrentBrush);
  551. }
  552. void Graphics::drawImage (const Image& imageToDraw,
  553. int dx, int dy, int dw, int dh,
  554. int sx, int sy, int sw, int sh,
  555. const bool fillAlphaChannelWithCurrentBrush) const
  556. {
  557. if (imageToDraw.isValid() && context.clipRegionIntersects (coordsToRectangle (dx, dy, dw, dh)))
  558. drawImageTransformed (imageToDraw.getClippedImage (coordsToRectangle (sx, sy, sw, sh)),
  559. AffineTransform::scale (dw / (float) sw, dh / (float) sh)
  560. .translated ((float) dx, (float) dy),
  561. fillAlphaChannelWithCurrentBrush);
  562. }
  563. void Graphics::drawImageTransformed (const Image& imageToDraw,
  564. const AffineTransform& transform,
  565. const bool fillAlphaChannelWithCurrentBrush) const
  566. {
  567. if (imageToDraw.isValid() && ! context.isClipEmpty())
  568. {
  569. if (fillAlphaChannelWithCurrentBrush)
  570. {
  571. context.saveState();
  572. context.clipToImageAlpha (imageToDraw, transform);
  573. fillAll();
  574. context.restoreState();
  575. }
  576. else
  577. {
  578. context.drawImage (imageToDraw, transform);
  579. }
  580. }
  581. }
  582. //==============================================================================
  583. Graphics::ScopedSaveState::ScopedSaveState (Graphics& g) : context (g)
  584. {
  585. context.saveState();
  586. }
  587. Graphics::ScopedSaveState::~ScopedSaveState()
  588. {
  589. context.restoreState();
  590. }