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.

699 lines
22KB

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