Audio plugin host https://kx.studio/carla
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.

706 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. jassert ((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. LowLevelGraphicsContext::LowLevelGraphicsContext() {}
  37. LowLevelGraphicsContext::~LowLevelGraphicsContext() {}
  38. //==============================================================================
  39. Graphics::Graphics (const Image& imageToDrawOnto)
  40. : contextHolder (imageToDrawOnto.createLowLevelContext()),
  41. context (*contextHolder)
  42. {
  43. jassert (imageToDrawOnto.isValid()); // Can't draw into a null image!
  44. }
  45. Graphics::Graphics (LowLevelGraphicsContext& internalContext) noexcept
  46. : context (internalContext)
  47. {
  48. }
  49. Graphics::~Graphics()
  50. {
  51. }
  52. //==============================================================================
  53. void Graphics::resetToDefaultState()
  54. {
  55. saveStateIfPending();
  56. context.setFill (FillType());
  57. context.setFont (Font());
  58. context.setInterpolationQuality (Graphics::mediumResamplingQuality);
  59. }
  60. bool Graphics::isVectorDevice() const
  61. {
  62. return context.isVectorDevice();
  63. }
  64. bool Graphics::reduceClipRegion (Rectangle<int> area)
  65. {
  66. saveStateIfPending();
  67. return context.clipToRectangle (area);
  68. }
  69. bool Graphics::reduceClipRegion (int x, int y, int w, int h)
  70. {
  71. return reduceClipRegion (coordsToRectangle (x, y, w, h));
  72. }
  73. bool Graphics::reduceClipRegion (const RectangleList<int>& clipRegion)
  74. {
  75. saveStateIfPending();
  76. return context.clipToRectangleList (clipRegion);
  77. }
  78. bool Graphics::reduceClipRegion (const Path& path, const AffineTransform& transform)
  79. {
  80. saveStateIfPending();
  81. context.clipToPath (path, transform);
  82. return ! context.isClipEmpty();
  83. }
  84. bool Graphics::reduceClipRegion (const Image& image, const AffineTransform& transform)
  85. {
  86. saveStateIfPending();
  87. context.clipToImageAlpha (image, transform);
  88. return ! context.isClipEmpty();
  89. }
  90. void Graphics::excludeClipRegion (Rectangle<int> rectangleToExclude)
  91. {
  92. saveStateIfPending();
  93. context.excludeClipRectangle (rectangleToExclude);
  94. }
  95. bool Graphics::isClipEmpty() const
  96. {
  97. return context.isClipEmpty();
  98. }
  99. Rectangle<int> Graphics::getClipBounds() const
  100. {
  101. return context.getClipBounds();
  102. }
  103. void Graphics::saveState()
  104. {
  105. saveStateIfPending();
  106. saveStatePending = true;
  107. }
  108. void Graphics::restoreState()
  109. {
  110. if (saveStatePending)
  111. saveStatePending = false;
  112. else
  113. context.restoreState();
  114. }
  115. void Graphics::saveStateIfPending()
  116. {
  117. if (saveStatePending)
  118. {
  119. saveStatePending = false;
  120. context.saveState();
  121. }
  122. }
  123. void Graphics::setOrigin (Point<int> newOrigin)
  124. {
  125. saveStateIfPending();
  126. context.setOrigin (newOrigin);
  127. }
  128. void Graphics::setOrigin (int x, int y)
  129. {
  130. setOrigin ({ x, y });
  131. }
  132. void Graphics::addTransform (const AffineTransform& transform)
  133. {
  134. saveStateIfPending();
  135. context.addTransform (transform);
  136. }
  137. bool Graphics::clipRegionIntersects (Rectangle<int> area) const
  138. {
  139. return context.clipRegionIntersects (area);
  140. }
  141. void Graphics::beginTransparencyLayer (float layerOpacity)
  142. {
  143. saveStateIfPending();
  144. context.beginTransparencyLayer (layerOpacity);
  145. }
  146. void Graphics::endTransparencyLayer()
  147. {
  148. context.endTransparencyLayer();
  149. }
  150. //==============================================================================
  151. void Graphics::setColour (Colour newColour)
  152. {
  153. saveStateIfPending();
  154. context.setFill (newColour);
  155. }
  156. void Graphics::setOpacity (float newOpacity)
  157. {
  158. saveStateIfPending();
  159. context.setOpacity (newOpacity);
  160. }
  161. void Graphics::setGradientFill (const ColourGradient& gradient)
  162. {
  163. setFillType (gradient);
  164. }
  165. void Graphics::setGradientFill (ColourGradient&& gradient)
  166. {
  167. setFillType (std::move (gradient));
  168. }
  169. void Graphics::setTiledImageFill (const Image& imageToUse, const int anchorX, const int anchorY, const float opacity)
  170. {
  171. saveStateIfPending();
  172. context.setFill (FillType (imageToUse, AffineTransform::translation ((float) anchorX, (float) anchorY)));
  173. context.setOpacity (opacity);
  174. }
  175. void Graphics::setFillType (const FillType& newFill)
  176. {
  177. saveStateIfPending();
  178. context.setFill (newFill);
  179. }
  180. //==============================================================================
  181. void Graphics::setFont (const Font& newFont)
  182. {
  183. saveStateIfPending();
  184. context.setFont (newFont);
  185. }
  186. void Graphics::setFont (const float newFontHeight)
  187. {
  188. setFont (context.getFont().withHeight (newFontHeight));
  189. }
  190. Font Graphics::getCurrentFont() const
  191. {
  192. return context.getFont();
  193. }
  194. //==============================================================================
  195. void Graphics::drawSingleLineText (const String& text, const int startX, const int baselineY,
  196. Justification justification) const
  197. {
  198. if (text.isNotEmpty())
  199. {
  200. // Don't pass any vertical placement flags to this method - they'll be ignored.
  201. jassert (justification.getOnlyVerticalFlags() == 0);
  202. auto flags = justification.getOnlyHorizontalFlags();
  203. if (flags == Justification::right && startX < context.getClipBounds().getX())
  204. return;
  205. if (flags == Justification::left && startX > context.getClipBounds().getRight())
  206. return;
  207. GlyphArrangement arr;
  208. arr.addLineOfText (context.getFont(), text, (float) startX, (float) baselineY);
  209. if (flags != Justification::left)
  210. {
  211. auto w = arr.getBoundingBox (0, -1, true).getWidth();
  212. if ((flags & (Justification::horizontallyCentred | Justification::horizontallyJustified)) != 0)
  213. w /= 2.0f;
  214. arr.draw (*this, AffineTransform::translation (-w, 0));
  215. }
  216. else
  217. {
  218. arr.draw (*this);
  219. }
  220. }
  221. }
  222. void Graphics::drawMultiLineText (const String& text, const int startX,
  223. const int baselineY, const int maximumLineWidth,
  224. Justification justification, const float leading) 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, leading);
  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::fillAll() const
  312. {
  313. fillRect (context.getClipBounds());
  314. }
  315. void Graphics::fillAll (Colour colourToUse) const
  316. {
  317. if (! colourToUse.isTransparent())
  318. {
  319. auto clip = context.getClipBounds();
  320. context.saveState();
  321. context.setFill (colourToUse);
  322. context.fillRect (clip, false);
  323. context.restoreState();
  324. }
  325. }
  326. //==============================================================================
  327. void Graphics::fillPath (const Path& path) const
  328. {
  329. if (! (context.isClipEmpty() || path.isEmpty()))
  330. context.fillPath (path, AffineTransform());
  331. }
  332. void Graphics::fillPath (const Path& path, const AffineTransform& transform) const
  333. {
  334. if (! (context.isClipEmpty() || path.isEmpty()))
  335. context.fillPath (path, transform);
  336. }
  337. void Graphics::strokePath (const Path& path,
  338. const PathStrokeType& strokeType,
  339. const AffineTransform& transform) const
  340. {
  341. Path stroke;
  342. strokeType.createStrokedPath (stroke, path, transform, context.getPhysicalPixelScaleFactor());
  343. fillPath (stroke);
  344. }
  345. //==============================================================================
  346. void Graphics::drawRect (float x, float y, float width, float height, float lineThickness) const
  347. {
  348. drawRect (coordsToRectangle (x, y, width, height), lineThickness);
  349. }
  350. void Graphics::drawRect (int x, int y, int width, int height, int lineThickness) const
  351. {
  352. drawRect (coordsToRectangle (x, y, width, height), lineThickness);
  353. }
  354. void Graphics::drawRect (Rectangle<int> r, int lineThickness) const
  355. {
  356. drawRect (r.toFloat(), (float) lineThickness);
  357. }
  358. void Graphics::drawRect (Rectangle<float> r, const float lineThickness) const
  359. {
  360. jassert (r.getWidth() >= 0.0f && r.getHeight() >= 0.0f);
  361. RectangleList<float> rects;
  362. rects.addWithoutMerging (r.removeFromTop (lineThickness));
  363. rects.addWithoutMerging (r.removeFromBottom (lineThickness));
  364. rects.addWithoutMerging (r.removeFromLeft (lineThickness));
  365. rects.addWithoutMerging (r.removeFromRight (lineThickness));
  366. context.fillRectList (rects);
  367. }
  368. //==============================================================================
  369. void Graphics::fillEllipse (Rectangle<float> area) const
  370. {
  371. Path p;
  372. p.addEllipse (area);
  373. fillPath (p);
  374. }
  375. void Graphics::fillEllipse (float x, float y, float w, float h) const
  376. {
  377. fillEllipse (coordsToRectangle (x, y, w, h));
  378. }
  379. void Graphics::drawEllipse (float x, float y, float width, float height, float lineThickness) const
  380. {
  381. drawEllipse (coordsToRectangle (x, y, width, height), lineThickness);
  382. }
  383. void Graphics::drawEllipse (Rectangle<float> area, float lineThickness) const
  384. {
  385. Path p;
  386. if (area.getWidth() == area.getHeight())
  387. {
  388. // For a circle, we can avoid having to generate a stroke
  389. p.addEllipse (area.expanded (lineThickness * 0.5f));
  390. p.addEllipse (area.reduced (lineThickness * 0.5f));
  391. p.setUsingNonZeroWinding (false);
  392. fillPath (p);
  393. }
  394. else
  395. {
  396. p.addEllipse (area);
  397. strokePath (p, PathStrokeType (lineThickness));
  398. }
  399. }
  400. void Graphics::fillRoundedRectangle (float x, float y, float width, float height, float cornerSize) const
  401. {
  402. fillRoundedRectangle (coordsToRectangle (x, y, width, height), cornerSize);
  403. }
  404. void Graphics::fillRoundedRectangle (Rectangle<float> r, const float cornerSize) const
  405. {
  406. Path p;
  407. p.addRoundedRectangle (r, cornerSize);
  408. fillPath (p);
  409. }
  410. void Graphics::drawRoundedRectangle (float x, float y, float width, float height,
  411. float cornerSize, float lineThickness) const
  412. {
  413. drawRoundedRectangle (coordsToRectangle (x, y, width, height), cornerSize, lineThickness);
  414. }
  415. void Graphics::drawRoundedRectangle (Rectangle<float> r, float cornerSize, float lineThickness) const
  416. {
  417. Path p;
  418. p.addRoundedRectangle (r, cornerSize);
  419. strokePath (p, PathStrokeType (lineThickness));
  420. }
  421. void Graphics::drawArrow (Line<float> line, float lineThickness, float arrowheadWidth, float arrowheadLength) const
  422. {
  423. Path p;
  424. p.addArrow (line, lineThickness, arrowheadWidth, arrowheadLength);
  425. fillPath (p);
  426. }
  427. void Graphics::fillCheckerBoard (Rectangle<float> area, float checkWidth, float checkHeight,
  428. Colour colour1, Colour colour2) const
  429. {
  430. jassert (checkWidth > 0 && checkHeight > 0); // can't be zero or less!
  431. if (checkWidth > 0 && checkHeight > 0)
  432. {
  433. context.saveState();
  434. if (colour1 == colour2)
  435. {
  436. context.setFill (colour1);
  437. context.fillRect (area);
  438. }
  439. else
  440. {
  441. auto clipped = context.getClipBounds().getIntersection (area.getSmallestIntegerContainer());
  442. if (! clipped.isEmpty())
  443. {
  444. const int checkNumX = (int) (((float) clipped.getX() - area.getX()) / checkWidth);
  445. const int checkNumY = (int) (((float) clipped.getY() - area.getY()) / checkHeight);
  446. const float startX = area.getX() + (float) checkNumX * checkWidth;
  447. const float startY = area.getY() + (float) checkNumY * checkHeight;
  448. const float right = (float) clipped.getRight();
  449. const float bottom = (float) clipped.getBottom();
  450. for (int i = 0; i < 2; ++i)
  451. {
  452. int cy = i;
  453. RectangleList<float> checks;
  454. for (float y = startY; y < bottom; y += checkHeight)
  455. for (float x = startX + (cy++ & 1) * checkWidth; x < right; x += checkWidth * 2.0f)
  456. checks.addWithoutMerging ({ x, y, checkWidth, checkHeight });
  457. checks.clipTo (area);
  458. context.setFill (i == ((checkNumX ^ checkNumY) & 1) ? colour1 : colour2);
  459. context.fillRectList (checks);
  460. }
  461. }
  462. }
  463. context.restoreState();
  464. }
  465. }
  466. //==============================================================================
  467. void Graphics::drawVerticalLine (const int x, float top, float bottom) const
  468. {
  469. if (top < bottom)
  470. context.fillRect (Rectangle<float> ((float) x, top, 1.0f, bottom - top));
  471. }
  472. void Graphics::drawHorizontalLine (const int y, float left, float right) const
  473. {
  474. if (left < right)
  475. context.fillRect (Rectangle<float> (left, (float) y, right - left, 1.0f));
  476. }
  477. void Graphics::drawLine (Line<float> line) const
  478. {
  479. context.drawLine (line);
  480. }
  481. void Graphics::drawLine (float x1, float y1, float x2, float y2) const
  482. {
  483. context.drawLine (Line<float> (x1, y1, x2, y2));
  484. }
  485. void Graphics::drawLine (float x1, float y1, float x2, float y2, float lineThickness) const
  486. {
  487. drawLine (Line<float> (x1, y1, x2, y2), lineThickness);
  488. }
  489. void Graphics::drawLine (Line<float> line, const float lineThickness) const
  490. {
  491. Path p;
  492. p.addLineSegment (line, lineThickness);
  493. fillPath (p);
  494. }
  495. void Graphics::drawDashedLine (Line<float> line, const float* dashLengths,
  496. int numDashLengths, float lineThickness, int n) const
  497. {
  498. jassert (n >= 0 && n < numDashLengths); // your start index must be valid!
  499. const Point<double> delta ((line.getEnd() - line.getStart()).toDouble());
  500. const double totalLen = delta.getDistanceFromOrigin();
  501. if (totalLen >= 0.1)
  502. {
  503. const double onePixAlpha = 1.0 / totalLen;
  504. for (double alpha = 0.0; alpha < 1.0;)
  505. {
  506. jassert (dashLengths[n] > 0); // can't have zero-length dashes!
  507. const double lastAlpha = alpha;
  508. alpha += dashLengths [n] * onePixAlpha;
  509. n = (n + 1) % numDashLengths;
  510. if ((n & 1) != 0)
  511. {
  512. const Line<float> segment (line.getStart() + (delta * lastAlpha).toFloat(),
  513. line.getStart() + (delta * jmin (1.0, alpha)).toFloat());
  514. if (lineThickness != 1.0f)
  515. drawLine (segment, lineThickness);
  516. else
  517. context.drawLine (segment);
  518. }
  519. }
  520. }
  521. }
  522. //==============================================================================
  523. void Graphics::setImageResamplingQuality (const Graphics::ResamplingQuality newQuality)
  524. {
  525. saveStateIfPending();
  526. context.setInterpolationQuality (newQuality);
  527. }
  528. //==============================================================================
  529. void Graphics::drawImageAt (const Image& imageToDraw, int x, int y, bool fillAlphaChannel) const
  530. {
  531. drawImageTransformed (imageToDraw,
  532. AffineTransform::translation ((float) x, (float) y),
  533. fillAlphaChannel);
  534. }
  535. void Graphics::drawImage (const Image& imageToDraw, Rectangle<float> targetArea,
  536. RectanglePlacement placementWithinTarget, bool fillAlphaChannelWithCurrentBrush) const
  537. {
  538. if (imageToDraw.isValid())
  539. drawImageTransformed (imageToDraw,
  540. placementWithinTarget.getTransformToFit (imageToDraw.getBounds().toFloat(), targetArea),
  541. fillAlphaChannelWithCurrentBrush);
  542. }
  543. void Graphics::drawImageWithin (const Image& imageToDraw, int dx, int dy, int dw, int dh,
  544. RectanglePlacement placementWithinTarget, bool fillAlphaChannelWithCurrentBrush) const
  545. {
  546. drawImage (imageToDraw, coordsToRectangle (dx, dy, dw, dh).toFloat(),
  547. placementWithinTarget, fillAlphaChannelWithCurrentBrush);
  548. }
  549. void Graphics::drawImage (const Image& imageToDraw,
  550. int dx, int dy, int dw, int dh,
  551. int sx, int sy, int sw, int sh,
  552. const bool fillAlphaChannelWithCurrentBrush) const
  553. {
  554. if (imageToDraw.isValid() && context.clipRegionIntersects (coordsToRectangle (dx, dy, dw, dh)))
  555. drawImageTransformed (imageToDraw.getClippedImage (coordsToRectangle (sx, sy, sw, sh)),
  556. AffineTransform::scale ((float) dw / (float) sw, (float) dh / (float) sh)
  557. .translated ((float) dx, (float) dy),
  558. fillAlphaChannelWithCurrentBrush);
  559. }
  560. void Graphics::drawImageTransformed (const Image& imageToDraw,
  561. const AffineTransform& transform,
  562. const bool fillAlphaChannelWithCurrentBrush) const
  563. {
  564. if (imageToDraw.isValid() && ! context.isClipEmpty())
  565. {
  566. if (fillAlphaChannelWithCurrentBrush)
  567. {
  568. context.saveState();
  569. context.clipToImageAlpha (imageToDraw, transform);
  570. fillAll();
  571. context.restoreState();
  572. }
  573. else
  574. {
  575. context.drawImage (imageToDraw, transform);
  576. }
  577. }
  578. }
  579. //==============================================================================
  580. Graphics::ScopedSaveState::ScopedSaveState (Graphics& g) : context (g)
  581. {
  582. context.saveState();
  583. }
  584. Graphics::ScopedSaveState::~ScopedSaveState()
  585. {
  586. context.restoreState();
  587. }
  588. } // namespace juce