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.

juce_GraphicsContext.cpp 21KB

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