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.

698 lines
22KB

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