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.

722 lines
24KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE library - "Jules' Utility Class Extensions"
  4. Copyright 2004-11 by Raw Material Software Ltd.
  5. ------------------------------------------------------------------------------
  6. JUCE can be redistributed and/or modified under the terms of the GNU General
  7. Public License (Version 2), as published by the Free Software Foundation.
  8. A copy of the license is included in the JUCE distribution, or can be found
  9. online at www.gnu.org/licenses.
  10. JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
  11. WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
  12. A PARTICULAR PURPOSE. See the GNU General Public License for more details.
  13. ------------------------------------------------------------------------------
  14. To release a closed-source product which uses JUCE, commercial licenses are
  15. available: visit www.rawmaterialsoftware.com/juce for more information.
  16. ==============================================================================
  17. */
  18. namespace
  19. {
  20. template <typename Type>
  21. bool areCoordsSensibleNumbers (Type x, Type y, Type w, Type h)
  22. {
  23. const int maxVal = 0x3fffffff;
  24. return (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. }
  29. }
  30. //==============================================================================
  31. LowLevelGraphicsContext::LowLevelGraphicsContext() {}
  32. LowLevelGraphicsContext::~LowLevelGraphicsContext() {}
  33. //==============================================================================
  34. Graphics::Graphics (const Image& imageToDrawOnto)
  35. : context (*imageToDrawOnto.createLowLevelContext()),
  36. contextToDelete (&context),
  37. saveStatePending (false)
  38. {
  39. jassert (imageToDrawOnto.isValid()); // Can't draw into a null image!
  40. }
  41. Graphics::Graphics (LowLevelGraphicsContext* const internalContext) noexcept
  42. : context (*internalContext),
  43. saveStatePending (false)
  44. {
  45. jassert (internalContext != nullptr);
  46. }
  47. Graphics::~Graphics()
  48. {
  49. }
  50. //==============================================================================
  51. void Graphics::resetToDefaultState()
  52. {
  53. saveStateIfPending();
  54. context.setFill (FillType());
  55. context.setFont (Font());
  56. context.setInterpolationQuality (Graphics::mediumResamplingQuality);
  57. }
  58. bool Graphics::isVectorDevice() const
  59. {
  60. return context.isVectorDevice();
  61. }
  62. bool Graphics::reduceClipRegion (const Rectangle<int>& area)
  63. {
  64. saveStateIfPending();
  65. return context.clipToRectangle (area);
  66. }
  67. bool Graphics::reduceClipRegion (const int x, const int y, const int w, const int h)
  68. {
  69. return reduceClipRegion (Rectangle<int> (x, y, w, h));
  70. }
  71. bool Graphics::reduceClipRegion (const RectangleList& clipRegion)
  72. {
  73. saveStateIfPending();
  74. return context.clipToRectangleList (clipRegion);
  75. }
  76. bool Graphics::reduceClipRegion (const Path& path, const AffineTransform& transform)
  77. {
  78. saveStateIfPending();
  79. context.clipToPath (path, transform);
  80. return ! context.isClipEmpty();
  81. }
  82. bool Graphics::reduceClipRegion (const Image& image, const AffineTransform& transform)
  83. {
  84. saveStateIfPending();
  85. context.clipToImageAlpha (image, transform);
  86. return ! context.isClipEmpty();
  87. }
  88. void Graphics::excludeClipRegion (const Rectangle<int>& rectangleToExclude)
  89. {
  90. saveStateIfPending();
  91. context.excludeClipRectangle (rectangleToExclude);
  92. }
  93. bool Graphics::isClipEmpty() const
  94. {
  95. return context.isClipEmpty();
  96. }
  97. Rectangle<int> Graphics::getClipBounds() const
  98. {
  99. return context.getClipBounds();
  100. }
  101. void Graphics::saveState()
  102. {
  103. saveStateIfPending();
  104. saveStatePending = true;
  105. }
  106. void Graphics::restoreState()
  107. {
  108. if (saveStatePending)
  109. saveStatePending = false;
  110. else
  111. context.restoreState();
  112. }
  113. void Graphics::saveStateIfPending()
  114. {
  115. if (saveStatePending)
  116. {
  117. saveStatePending = false;
  118. context.saveState();
  119. }
  120. }
  121. void Graphics::setOrigin (const int newOriginX, const int newOriginY)
  122. {
  123. saveStateIfPending();
  124. context.setOrigin (newOriginX, newOriginY);
  125. }
  126. void Graphics::addTransform (const AffineTransform& transform)
  127. {
  128. saveStateIfPending();
  129. context.addTransform (transform);
  130. }
  131. bool Graphics::clipRegionIntersects (const 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 (const 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::setTiledImageFill (const Image& imageToUse, const int anchorX, const int anchorY, const float opacity)
  160. {
  161. saveStateIfPending();
  162. context.setFill (FillType (imageToUse, AffineTransform::translation ((float) anchorX, (float) anchorY)));
  163. context.setOpacity (opacity);
  164. }
  165. void Graphics::setFillType (const FillType& newFill)
  166. {
  167. saveStateIfPending();
  168. context.setFill (newFill);
  169. }
  170. //==============================================================================
  171. void Graphics::setFont (const Font& newFont)
  172. {
  173. saveStateIfPending();
  174. context.setFont (newFont);
  175. }
  176. void Graphics::setFont (const float newFontHeight)
  177. {
  178. setFont (context.getFont().withHeight (newFontHeight));
  179. }
  180. Font Graphics::getCurrentFont() const
  181. {
  182. return context.getFont();
  183. }
  184. //==============================================================================
  185. void Graphics::drawSingleLineText (const String& text, const int startX, const int baselineY,
  186. const Justification& justification) const
  187. {
  188. if (text.isNotEmpty()
  189. && startX < context.getClipBounds().getRight())
  190. {
  191. GlyphArrangement arr;
  192. arr.addLineOfText (context.getFont(), text, (float) startX, (float) baselineY);
  193. // Don't pass any vertical placement flags to this method - they'll be ignored.
  194. jassert (justification.getOnlyVerticalFlags() == 0);
  195. const int flags = justification.getOnlyHorizontalFlags();
  196. if (flags != Justification::left)
  197. {
  198. float w = arr.getBoundingBox (0, -1, true).getWidth();
  199. if ((flags & (Justification::horizontallyCentred | Justification::horizontallyJustified)) != 0)
  200. w /= 2.0f;
  201. arr.draw (*this, AffineTransform::translation (-w, 0));
  202. }
  203. else
  204. {
  205. arr.draw (*this);
  206. }
  207. }
  208. }
  209. void Graphics::drawTextAsPath (const String& text, const AffineTransform& transform) const
  210. {
  211. if (text.isNotEmpty())
  212. {
  213. GlyphArrangement arr;
  214. arr.addLineOfText (context.getFont(), text, 0.0f, 0.0f);
  215. arr.draw (*this, transform);
  216. }
  217. }
  218. void Graphics::drawMultiLineText (const String& text, const int startX,
  219. const int baselineY, const int maximumLineWidth) 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::left);
  228. arr.draw (*this);
  229. }
  230. }
  231. void Graphics::drawText (const String& text, const Rectangle<int>& area,
  232. const Justification& justificationType,
  233. const bool useEllipsesIfTooBig) const
  234. {
  235. if (text.isNotEmpty() && context.clipRegionIntersects (area))
  236. {
  237. GlyphArrangement arr;
  238. arr.addCurtailedLineOfText (context.getFont(), text,
  239. 0.0f, 0.0f, (float) area.getWidth(),
  240. useEllipsesIfTooBig);
  241. arr.justifyGlyphs (0, arr.getNumGlyphs(),
  242. (float) area.getX(), (float) area.getY(),
  243. (float) area.getWidth(), (float) area.getHeight(),
  244. justificationType);
  245. arr.draw (*this);
  246. }
  247. }
  248. void Graphics::drawText (const String& text, const int x, const int y, const int width, const int height,
  249. const Justification& justificationType,
  250. const bool useEllipsesIfTooBig) const
  251. {
  252. drawText (text, Rectangle<int> (x, y, width, height), justificationType, useEllipsesIfTooBig);
  253. }
  254. void Graphics::drawFittedText (const String& text, const Rectangle<int>& area,
  255. const 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, const int x, const int y, const int width, const int height,
  272. const Justification& justification,
  273. const int maximumNumberOfLines,
  274. const float minimumHorizontalScale) const
  275. {
  276. drawFittedText (text,Rectangle<int> (x, y, width, height),
  277. justification, maximumNumberOfLines, minimumHorizontalScale);
  278. }
  279. //==============================================================================
  280. void Graphics::fillRect (int x, int y, int width, int height) const
  281. {
  282. // passing in a silly number can cause maths problems in rendering!
  283. jassert (areCoordsSensibleNumbers (x, y, width, height));
  284. context.fillRect (Rectangle<int> (x, y, width, height), false);
  285. }
  286. void Graphics::fillRect (const Rectangle<int>& r) const
  287. {
  288. context.fillRect (r, false);
  289. }
  290. void Graphics::fillRect (const Rectangle<float>& rectangle) const
  291. {
  292. Path p;
  293. p.addRectangle (rectangle);
  294. fillPath (p);
  295. }
  296. void Graphics::fillRect (const float x, const float y, const float width, const float height) const
  297. {
  298. // passing in a silly number can cause maths problems in rendering!
  299. jassert (areCoordsSensibleNumbers (x, y, width, height));
  300. fillRect (Rectangle<float> (x, y, width, height));
  301. }
  302. void Graphics::setPixel (int x, int y) const
  303. {
  304. context.fillRect (Rectangle<int> (x, y, 1, 1), 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. const Rectangle<int> 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 AffineTransform& transform) const
  323. {
  324. if ((! context.isClipEmpty()) && ! path.isEmpty())
  325. context.fillPath (path, transform);
  326. }
  327. void Graphics::strokePath (const Path& path,
  328. const PathStrokeType& strokeType,
  329. const AffineTransform& transform) const
  330. {
  331. Path stroke;
  332. strokeType.createStrokedPath (stroke, path, transform, context.getScaleFactor());
  333. fillPath (stroke);
  334. }
  335. //==============================================================================
  336. void Graphics::drawRect (const int x, const int y, const int width, const int height,
  337. const int lineThickness) const
  338. {
  339. // passing in a silly number can cause maths problems in rendering!
  340. jassert (areCoordsSensibleNumbers (x, y, width, height));
  341. context.fillRect (Rectangle<int> (x, y, width, lineThickness), false);
  342. context.fillRect (Rectangle<int> (x, y + lineThickness, lineThickness, height - lineThickness * 2), false);
  343. context.fillRect (Rectangle<int> (x + width - lineThickness, y + lineThickness, lineThickness, height - lineThickness * 2), false);
  344. context.fillRect (Rectangle<int> (x, y + height - lineThickness, width, lineThickness), false);
  345. }
  346. void Graphics::drawRect (const float x, const float y, const float width, const float height,
  347. const float lineThickness) const
  348. {
  349. // passing in a silly number can cause maths problems in rendering!
  350. jassert (areCoordsSensibleNumbers (x, y, width, height));
  351. Path p;
  352. p.addRectangle (x, y, width, lineThickness);
  353. p.addRectangle (x, y + lineThickness, lineThickness, height - lineThickness * 2.0f);
  354. p.addRectangle (x + width - lineThickness, y + lineThickness, lineThickness, height - lineThickness * 2.0f);
  355. p.addRectangle (x, y + height - lineThickness, width, lineThickness);
  356. fillPath (p);
  357. }
  358. void Graphics::drawRect (const Rectangle<int>& r, const int lineThickness) const
  359. {
  360. drawRect (r.getX(), r.getY(), r.getWidth(), r.getHeight(), lineThickness);
  361. }
  362. void Graphics::drawRect (const Rectangle<float>& r, const float lineThickness) const
  363. {
  364. drawRect (r.getX(), r.getY(), r.getWidth(), r.getHeight(), lineThickness);
  365. }
  366. //==============================================================================
  367. void Graphics::fillEllipse (const Rectangle<float>& area) const
  368. {
  369. fillEllipse (area.getX(), area.getY(), area.getWidth(), area.getHeight());
  370. }
  371. void Graphics::fillEllipse (const float x, const float y, const float width, const float height) const
  372. {
  373. // passing in a silly number can cause maths problems in rendering!
  374. jassert (areCoordsSensibleNumbers (x, y, width, height));
  375. Path p;
  376. p.addEllipse (x, y, width, height);
  377. fillPath (p);
  378. }
  379. void Graphics::drawEllipse (const float x, const float y, const float width, const float height,
  380. const float lineThickness) const
  381. {
  382. // passing in a silly number can cause maths problems in rendering!
  383. jassert (areCoordsSensibleNumbers (x, y, width, height));
  384. Path p;
  385. p.addEllipse (x, y, width, height);
  386. strokePath (p, PathStrokeType (lineThickness));
  387. }
  388. void Graphics::fillRoundedRectangle (const float x, const float y, const float width, const float height, const float cornerSize) const
  389. {
  390. // passing in a silly number can cause maths problems in rendering!
  391. jassert (areCoordsSensibleNumbers (x, y, width, height));
  392. Path p;
  393. p.addRoundedRectangle (x, y, width, height, cornerSize);
  394. fillPath (p);
  395. }
  396. void Graphics::fillRoundedRectangle (const Rectangle<float>& r, const float cornerSize) const
  397. {
  398. fillRoundedRectangle (r.getX(), r.getY(), r.getWidth(), r.getHeight(), cornerSize);
  399. }
  400. void Graphics::drawRoundedRectangle (const float x, const float y, const float width, const float height,
  401. const float cornerSize, const float lineThickness) const
  402. {
  403. // passing in a silly number can cause maths problems in rendering!
  404. jassert (areCoordsSensibleNumbers (x, y, width, height));
  405. Path p;
  406. p.addRoundedRectangle (x, y, width, height, cornerSize);
  407. strokePath (p, PathStrokeType (lineThickness));
  408. }
  409. void Graphics::drawRoundedRectangle (const Rectangle<float>& r, const float cornerSize, const float lineThickness) const
  410. {
  411. drawRoundedRectangle (r.getX(), r.getY(), r.getWidth(), r.getHeight(), cornerSize, lineThickness);
  412. }
  413. void Graphics::drawArrow (const Line<float>& line, const float lineThickness, const float arrowheadWidth, const float arrowheadLength) const
  414. {
  415. Path p;
  416. p.addArrow (line, lineThickness, arrowheadWidth, arrowheadLength);
  417. fillPath (p);
  418. }
  419. void Graphics::fillCheckerBoard (const Rectangle<int>& area,
  420. const int checkWidth, const int checkHeight,
  421. Colour colour1, Colour colour2) const
  422. {
  423. jassert (checkWidth > 0 && checkHeight > 0); // can't be zero or less!
  424. if (checkWidth > 0 && checkHeight > 0)
  425. {
  426. context.saveState();
  427. if (colour1 == colour2)
  428. {
  429. context.setFill (colour1);
  430. context.fillRect (area, false);
  431. }
  432. else
  433. {
  434. const Rectangle<int> clipped (context.getClipBounds().getIntersection (area));
  435. if (! clipped.isEmpty())
  436. {
  437. context.clipToRectangle (clipped);
  438. const int checkNumX = (clipped.getX() - area.getX()) / checkWidth;
  439. const int checkNumY = (clipped.getY() - area.getY()) / checkHeight;
  440. const int startX = area.getX() + checkNumX * checkWidth;
  441. const int startY = area.getY() + checkNumY * checkHeight;
  442. const int right = clipped.getRight();
  443. const int bottom = clipped.getBottom();
  444. for (int i = 0; i < 2; ++i)
  445. {
  446. context.setFill (i == ((checkNumX ^ checkNumY) & 1) ? colour1 : colour2);
  447. int cy = i;
  448. for (int y = startY; y < bottom; y += checkHeight)
  449. for (int x = startX + (cy++ & 1) * checkWidth; x < right; x += checkWidth * 2)
  450. context.fillRect (Rectangle<int> (x, y, checkWidth, checkHeight), false);
  451. }
  452. }
  453. }
  454. context.restoreState();
  455. }
  456. }
  457. //==============================================================================
  458. void Graphics::drawVerticalLine (const int x, float top, float bottom) const
  459. {
  460. context.drawVerticalLine (x, top, bottom);
  461. }
  462. void Graphics::drawHorizontalLine (const int y, float left, float right) const
  463. {
  464. context.drawHorizontalLine (y, left, right);
  465. }
  466. void Graphics::drawLine (const float x1, const float y1, const float x2, const float y2) const
  467. {
  468. context.drawLine (Line<float> (x1, y1, x2, y2));
  469. }
  470. void Graphics::drawLine (const Line<float>& line) const
  471. {
  472. context.drawLine (line);
  473. }
  474. void Graphics::drawLine (const float x1, const float y1, const float x2, const float y2, const float lineThickness) const
  475. {
  476. drawLine (Line<float> (x1, y1, x2, y2), lineThickness);
  477. }
  478. void Graphics::drawLine (const Line<float>& line, const float lineThickness) const
  479. {
  480. Path p;
  481. p.addLineSegment (line, lineThickness);
  482. fillPath (p);
  483. }
  484. void Graphics::drawDashedLine (const Line<float>& line, const float* const dashLengths,
  485. const int numDashLengths, const float lineThickness, int n) const
  486. {
  487. jassert (n >= 0 && n < numDashLengths); // your start index must be valid!
  488. const Point<double> delta ((line.getEnd() - line.getStart()).toDouble());
  489. const double totalLen = delta.getDistanceFromOrigin();
  490. if (totalLen >= 0.1)
  491. {
  492. const double onePixAlpha = 1.0 / totalLen;
  493. for (double alpha = 0.0; alpha < 1.0;)
  494. {
  495. jassert (dashLengths[n] > 0); // can't have zero-length dashes!
  496. const double lastAlpha = alpha;
  497. alpha += dashLengths [n] * onePixAlpha;
  498. n = (n + 1) % numDashLengths;
  499. if ((n & 1) != 0)
  500. {
  501. const Line<float> segment (line.getStart() + (delta * lastAlpha).toFloat(),
  502. line.getStart() + (delta * jmin (1.0, alpha)).toFloat());
  503. if (lineThickness != 1.0f)
  504. drawLine (segment, lineThickness);
  505. else
  506. context.drawLine (segment);
  507. }
  508. }
  509. }
  510. }
  511. //==============================================================================
  512. void Graphics::setImageResamplingQuality (const Graphics::ResamplingQuality newQuality)
  513. {
  514. saveStateIfPending();
  515. context.setInterpolationQuality (newQuality);
  516. }
  517. //==============================================================================
  518. void Graphics::drawImageAt (const Image& imageToDraw,
  519. const int topLeftX, const int topLeftY,
  520. const bool fillAlphaChannelWithCurrentBrush) const
  521. {
  522. const int imageW = imageToDraw.getWidth();
  523. const int imageH = imageToDraw.getHeight();
  524. drawImage (imageToDraw,
  525. topLeftX, topLeftY, imageW, imageH,
  526. 0, 0, imageW, imageH,
  527. fillAlphaChannelWithCurrentBrush);
  528. }
  529. void Graphics::drawImageWithin (const Image& imageToDraw,
  530. const int destX, const int destY,
  531. const int destW, const int destH,
  532. const RectanglePlacement& placementWithinTarget,
  533. const bool fillAlphaChannelWithCurrentBrush) const
  534. {
  535. // passing in a silly number can cause maths problems in rendering!
  536. jassert (areCoordsSensibleNumbers (destX, destY, destW, destH));
  537. if (imageToDraw.isValid())
  538. {
  539. const int imageW = imageToDraw.getWidth();
  540. const int imageH = imageToDraw.getHeight();
  541. if (imageW > 0 && imageH > 0)
  542. {
  543. double newX = 0.0, newY = 0.0;
  544. double newW = imageW;
  545. double newH = imageH;
  546. placementWithinTarget.applyTo (newX, newY, newW, newH,
  547. destX, destY, destW, destH);
  548. if (newW > 0 && newH > 0)
  549. {
  550. drawImage (imageToDraw,
  551. roundToInt (newX), roundToInt (newY),
  552. roundToInt (newW), roundToInt (newH),
  553. 0, 0, imageW, imageH,
  554. fillAlphaChannelWithCurrentBrush);
  555. }
  556. }
  557. }
  558. }
  559. void Graphics::drawImage (const Image& imageToDraw,
  560. int dx, int dy, int dw, int dh,
  561. int sx, int sy, int sw, int sh,
  562. const bool fillAlphaChannelWithCurrentBrush) const
  563. {
  564. // passing in a silly number can cause maths problems in rendering!
  565. jassert (areCoordsSensibleNumbers (dx, dy, dw, dh));
  566. jassert (areCoordsSensibleNumbers (sx, sy, sw, sh));
  567. if (imageToDraw.isValid() && context.clipRegionIntersects (Rectangle<int> (dx, dy, dw, dh)))
  568. {
  569. drawImageTransformed (imageToDraw.getClippedImage (Rectangle<int> (sx, sy, sw, sh)),
  570. AffineTransform::scale (dw / (float) sw, dh / (float) sh)
  571. .translated ((float) dx, (float) dy),
  572. fillAlphaChannelWithCurrentBrush);
  573. }
  574. }
  575. void Graphics::drawImageTransformed (const Image& imageToDraw,
  576. const AffineTransform& transform,
  577. const bool fillAlphaChannelWithCurrentBrush) const
  578. {
  579. if (imageToDraw.isValid() && ! context.isClipEmpty())
  580. {
  581. if (fillAlphaChannelWithCurrentBrush)
  582. {
  583. context.saveState();
  584. context.clipToImageAlpha (imageToDraw, transform);
  585. fillAll();
  586. context.restoreState();
  587. }
  588. else
  589. {
  590. context.drawImage (imageToDraw, transform);
  591. }
  592. }
  593. }
  594. //==============================================================================
  595. Graphics::ScopedSaveState::ScopedSaveState (Graphics& g)
  596. : context (g)
  597. {
  598. context.saveState();
  599. }
  600. Graphics::ScopedSaveState::~ScopedSaveState()
  601. {
  602. context.restoreState();
  603. }