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_LowLevelGraphicsPostScriptRenderer.cpp 17KB

9 years ago
9 years ago
10 years ago
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533
  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE library.
  4. Copyright (c) 2015 - ROLI 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. // this will throw an assertion if you try to draw something that's not
  18. // possible in postscript
  19. #define WARN_ABOUT_NON_POSTSCRIPT_OPERATIONS 0
  20. //==============================================================================
  21. #if JUCE_DEBUG && WARN_ABOUT_NON_POSTSCRIPT_OPERATIONS
  22. #define notPossibleInPostscriptAssert jassertfalse
  23. #else
  24. #define notPossibleInPostscriptAssert
  25. #endif
  26. //==============================================================================
  27. LowLevelGraphicsPostScriptRenderer::LowLevelGraphicsPostScriptRenderer (OutputStream& resultingPostScript,
  28. const String& documentTitle,
  29. const int totalWidth_,
  30. const int totalHeight_)
  31. : out (resultingPostScript),
  32. totalWidth (totalWidth_),
  33. totalHeight (totalHeight_),
  34. needToClip (true)
  35. {
  36. stateStack.add (new SavedState());
  37. stateStack.getLast()->clip = Rectangle<int> (totalWidth_, totalHeight_);
  38. const float scale = jmin ((520.0f / totalWidth_), (750.0f / totalHeight));
  39. out << "%!PS-Adobe-3.0 EPSF-3.0"
  40. "\n%%BoundingBox: 0 0 600 824"
  41. "\n%%Pages: 0"
  42. "\n%%Creator: ROLI Ltd. JUCE"
  43. "\n%%Title: " << documentTitle <<
  44. "\n%%CreationDate: none"
  45. "\n%%LanguageLevel: 2"
  46. "\n%%EndComments"
  47. "\n%%BeginProlog"
  48. "\n%%BeginResource: JRes"
  49. "\n/bd {bind def} bind def"
  50. "\n/c {setrgbcolor} bd"
  51. "\n/m {moveto} bd"
  52. "\n/l {lineto} bd"
  53. "\n/rl {rlineto} bd"
  54. "\n/ct {curveto} bd"
  55. "\n/cp {closepath} bd"
  56. "\n/pr {3 index 3 index moveto 1 index 0 rlineto 0 1 index rlineto pop neg 0 rlineto pop pop closepath} bd"
  57. "\n/doclip {initclip newpath} bd"
  58. "\n/endclip {clip newpath} bd"
  59. "\n%%EndResource"
  60. "\n%%EndProlog"
  61. "\n%%BeginSetup"
  62. "\n%%EndSetup"
  63. "\n%%Page: 1 1"
  64. "\n%%BeginPageSetup"
  65. "\n%%EndPageSetup\n\n"
  66. << "40 800 translate\n"
  67. << scale << ' ' << scale << " scale\n\n";
  68. }
  69. LowLevelGraphicsPostScriptRenderer::~LowLevelGraphicsPostScriptRenderer()
  70. {
  71. }
  72. //==============================================================================
  73. bool LowLevelGraphicsPostScriptRenderer::isVectorDevice() const
  74. {
  75. return true;
  76. }
  77. void LowLevelGraphicsPostScriptRenderer::setOrigin (Point<int> o)
  78. {
  79. if (! o.isOrigin())
  80. {
  81. stateStack.getLast()->xOffset += o.x;
  82. stateStack.getLast()->yOffset += o.y;
  83. needToClip = true;
  84. }
  85. }
  86. void LowLevelGraphicsPostScriptRenderer::addTransform (const AffineTransform& /*transform*/)
  87. {
  88. //xxx
  89. jassertfalse;
  90. }
  91. float LowLevelGraphicsPostScriptRenderer::getPhysicalPixelScaleFactor() { return 1.0f; }
  92. bool LowLevelGraphicsPostScriptRenderer::clipToRectangle (const Rectangle<int>& r)
  93. {
  94. needToClip = true;
  95. return stateStack.getLast()->clip.clipTo (r.translated (stateStack.getLast()->xOffset, stateStack.getLast()->yOffset));
  96. }
  97. bool LowLevelGraphicsPostScriptRenderer::clipToRectangleList (const RectangleList<int>& clipRegion)
  98. {
  99. needToClip = true;
  100. return stateStack.getLast()->clip.clipTo (clipRegion);
  101. }
  102. void LowLevelGraphicsPostScriptRenderer::excludeClipRectangle (const Rectangle<int>& r)
  103. {
  104. needToClip = true;
  105. stateStack.getLast()->clip.subtract (r.translated (stateStack.getLast()->xOffset, stateStack.getLast()->yOffset));
  106. }
  107. void LowLevelGraphicsPostScriptRenderer::clipToPath (const Path& path, const AffineTransform& transform)
  108. {
  109. writeClip();
  110. Path p (path);
  111. p.applyTransform (transform.translated ((float) stateStack.getLast()->xOffset, (float) stateStack.getLast()->yOffset));
  112. writePath (p);
  113. out << "clip\n";
  114. }
  115. void LowLevelGraphicsPostScriptRenderer::clipToImageAlpha (const Image& /*sourceImage*/, const AffineTransform& /*transform*/)
  116. {
  117. needToClip = true;
  118. jassertfalse; // xxx
  119. }
  120. bool LowLevelGraphicsPostScriptRenderer::clipRegionIntersects (const Rectangle<int>& r)
  121. {
  122. return stateStack.getLast()->clip.intersectsRectangle (r.translated (stateStack.getLast()->xOffset, stateStack.getLast()->yOffset));
  123. }
  124. Rectangle<int> LowLevelGraphicsPostScriptRenderer::getClipBounds() const
  125. {
  126. return stateStack.getLast()->clip.getBounds().translated (-stateStack.getLast()->xOffset,
  127. -stateStack.getLast()->yOffset);
  128. }
  129. bool LowLevelGraphicsPostScriptRenderer::isClipEmpty() const
  130. {
  131. return stateStack.getLast()->clip.isEmpty();
  132. }
  133. //==============================================================================
  134. LowLevelGraphicsPostScriptRenderer::SavedState::SavedState()
  135. : xOffset (0),
  136. yOffset (0)
  137. {
  138. }
  139. LowLevelGraphicsPostScriptRenderer::SavedState::~SavedState()
  140. {
  141. }
  142. void LowLevelGraphicsPostScriptRenderer::saveState()
  143. {
  144. stateStack.add (new SavedState (*stateStack.getLast()));
  145. }
  146. void LowLevelGraphicsPostScriptRenderer::restoreState()
  147. {
  148. jassert (stateStack.size() > 0);
  149. if (stateStack.size() > 0)
  150. stateStack.removeLast();
  151. }
  152. void LowLevelGraphicsPostScriptRenderer::beginTransparencyLayer (float)
  153. {
  154. }
  155. void LowLevelGraphicsPostScriptRenderer::endTransparencyLayer()
  156. {
  157. }
  158. //==============================================================================
  159. void LowLevelGraphicsPostScriptRenderer::writeClip()
  160. {
  161. if (needToClip)
  162. {
  163. needToClip = false;
  164. out << "doclip ";
  165. int itemsOnLine = 0;
  166. for (const Rectangle<int>* i = stateStack.getLast()->clip.begin(), * const e = stateStack.getLast()->clip.end(); i != e; ++i)
  167. {
  168. if (++itemsOnLine == 6)
  169. {
  170. itemsOnLine = 0;
  171. out << '\n';
  172. }
  173. out << i->getX() << ' ' << -i->getY() << ' '
  174. << i->getWidth() << ' ' << -i->getHeight() << " pr ";
  175. }
  176. out << "endclip\n";
  177. }
  178. }
  179. void LowLevelGraphicsPostScriptRenderer::writeColour (Colour colour)
  180. {
  181. Colour c (Colours::white.overlaidWith (colour));
  182. if (lastColour != c)
  183. {
  184. lastColour = c;
  185. out << String (c.getFloatRed(), 3) << ' '
  186. << String (c.getFloatGreen(), 3) << ' '
  187. << String (c.getFloatBlue(), 3) << " c\n";
  188. }
  189. }
  190. void LowLevelGraphicsPostScriptRenderer::writeXY (const float x, const float y) const
  191. {
  192. out << String (x, 2) << ' '
  193. << String (-y, 2) << ' ';
  194. }
  195. void LowLevelGraphicsPostScriptRenderer::writePath (const Path& path) const
  196. {
  197. out << "newpath ";
  198. float lastX = 0.0f;
  199. float lastY = 0.0f;
  200. int itemsOnLine = 0;
  201. Path::Iterator i (path);
  202. while (i.next())
  203. {
  204. if (++itemsOnLine == 4)
  205. {
  206. itemsOnLine = 0;
  207. out << '\n';
  208. }
  209. switch (i.elementType)
  210. {
  211. case Path::Iterator::startNewSubPath:
  212. writeXY (i.x1, i.y1);
  213. lastX = i.x1;
  214. lastY = i.y1;
  215. out << "m ";
  216. break;
  217. case Path::Iterator::lineTo:
  218. writeXY (i.x1, i.y1);
  219. lastX = i.x1;
  220. lastY = i.y1;
  221. out << "l ";
  222. break;
  223. case Path::Iterator::quadraticTo:
  224. {
  225. const float cp1x = lastX + (i.x1 - lastX) * 2.0f / 3.0f;
  226. const float cp1y = lastY + (i.y1 - lastY) * 2.0f / 3.0f;
  227. const float cp2x = cp1x + (i.x2 - lastX) / 3.0f;
  228. const float cp2y = cp1y + (i.y2 - lastY) / 3.0f;
  229. writeXY (cp1x, cp1y);
  230. writeXY (cp2x, cp2y);
  231. writeXY (i.x2, i.y2);
  232. out << "ct ";
  233. lastX = i.x2;
  234. lastY = i.y2;
  235. }
  236. break;
  237. case Path::Iterator::cubicTo:
  238. writeXY (i.x1, i.y1);
  239. writeXY (i.x2, i.y2);
  240. writeXY (i.x3, i.y3);
  241. out << "ct ";
  242. lastX = i.x3;
  243. lastY = i.y3;
  244. break;
  245. case Path::Iterator::closePath:
  246. out << "cp ";
  247. break;
  248. default:
  249. jassertfalse;
  250. break;
  251. }
  252. }
  253. out << '\n';
  254. }
  255. void LowLevelGraphicsPostScriptRenderer::writeTransform (const AffineTransform& trans) const
  256. {
  257. out << "[ "
  258. << trans.mat00 << ' '
  259. << trans.mat10 << ' '
  260. << trans.mat01 << ' '
  261. << trans.mat11 << ' '
  262. << trans.mat02 << ' '
  263. << trans.mat12 << " ] concat ";
  264. }
  265. //==============================================================================
  266. void LowLevelGraphicsPostScriptRenderer::setFill (const FillType& fillType)
  267. {
  268. stateStack.getLast()->fillType = fillType;
  269. }
  270. void LowLevelGraphicsPostScriptRenderer::setOpacity (float /*opacity*/)
  271. {
  272. }
  273. void LowLevelGraphicsPostScriptRenderer::setInterpolationQuality (Graphics::ResamplingQuality /*quality*/)
  274. {
  275. }
  276. //==============================================================================
  277. void LowLevelGraphicsPostScriptRenderer::fillRect (const Rectangle<int>& r, const bool /*replaceExistingContents*/)
  278. {
  279. fillRect (r.toFloat());
  280. }
  281. void LowLevelGraphicsPostScriptRenderer::fillRect (const Rectangle<float>& r)
  282. {
  283. if (stateStack.getLast()->fillType.isColour())
  284. {
  285. writeClip();
  286. writeColour (stateStack.getLast()->fillType.colour);
  287. Rectangle<float> r2 (r.translated ((float) stateStack.getLast()->xOffset,
  288. (float) stateStack.getLast()->yOffset));
  289. out << r2.getX() << ' ' << -r2.getBottom() << ' ' << r2.getWidth() << ' ' << r2.getHeight() << " rectfill\n";
  290. }
  291. else
  292. {
  293. Path p;
  294. p.addRectangle (r);
  295. fillPath (p, AffineTransform::identity);
  296. }
  297. }
  298. void LowLevelGraphicsPostScriptRenderer::fillRectList (const RectangleList<float>& list)
  299. {
  300. fillPath (list.toPath(), AffineTransform::identity);
  301. }
  302. //==============================================================================
  303. void LowLevelGraphicsPostScriptRenderer::fillPath (const Path& path, const AffineTransform& t)
  304. {
  305. if (stateStack.getLast()->fillType.isColour())
  306. {
  307. writeClip();
  308. Path p (path);
  309. p.applyTransform (t.translated ((float) stateStack.getLast()->xOffset,
  310. (float) stateStack.getLast()->yOffset));
  311. writePath (p);
  312. writeColour (stateStack.getLast()->fillType.colour);
  313. out << "fill\n";
  314. }
  315. else if (stateStack.getLast()->fillType.isGradient())
  316. {
  317. // this doesn't work correctly yet - it could be improved to handle solid gradients, but
  318. // postscript can't do semi-transparent ones.
  319. notPossibleInPostscriptAssert; // you can disable this warning by setting the WARN_ABOUT_NON_POSTSCRIPT_OPERATIONS flag at the top of this file
  320. writeClip();
  321. out << "gsave ";
  322. {
  323. Path p (path);
  324. p.applyTransform (t.translated ((float) stateStack.getLast()->xOffset, (float) stateStack.getLast()->yOffset));
  325. writePath (p);
  326. out << "clip\n";
  327. }
  328. const Rectangle<int> bounds (stateStack.getLast()->clip.getBounds());
  329. // ideally this would draw lots of lines or ellipses to approximate the gradient, but for the
  330. // time-being, this just fills it with the average colour..
  331. writeColour (stateStack.getLast()->fillType.gradient->getColourAtPosition (0.5f));
  332. out << bounds.getX() << ' ' << -bounds.getBottom() << ' ' << bounds.getWidth() << ' ' << bounds.getHeight() << " rectfill\n";
  333. out << "grestore\n";
  334. }
  335. }
  336. //==============================================================================
  337. void LowLevelGraphicsPostScriptRenderer::writeImage (const Image& im,
  338. const int sx, const int sy,
  339. const int maxW, const int maxH) const
  340. {
  341. out << "{<\n";
  342. const int w = jmin (maxW, im.getWidth());
  343. const int h = jmin (maxH, im.getHeight());
  344. int charsOnLine = 0;
  345. const Image::BitmapData srcData (im, 0, 0, w, h);
  346. Colour pixel;
  347. for (int y = h; --y >= 0;)
  348. {
  349. for (int x = 0; x < w; ++x)
  350. {
  351. const uint8* pixelData = srcData.getPixelPointer (x, y);
  352. if (x >= sx && y >= sy)
  353. {
  354. if (im.isARGB())
  355. {
  356. PixelARGB p (*(const PixelARGB*) pixelData);
  357. p.unpremultiply();
  358. pixel = Colours::white.overlaidWith (Colour (p));
  359. }
  360. else if (im.isRGB())
  361. {
  362. pixel = Colour (*((const PixelRGB*) pixelData));
  363. }
  364. else
  365. {
  366. pixel = Colour ((uint8) 0, (uint8) 0, (uint8) 0, *pixelData);
  367. }
  368. }
  369. else
  370. {
  371. pixel = Colours::transparentWhite;
  372. }
  373. const uint8 pixelValues[3] = { pixel.getRed(), pixel.getGreen(), pixel.getBlue() };
  374. out << String::toHexString (pixelValues, 3, 0);
  375. charsOnLine += 3;
  376. if (charsOnLine > 100)
  377. {
  378. out << '\n';
  379. charsOnLine = 0;
  380. }
  381. }
  382. }
  383. out << "\n>}\n";
  384. }
  385. void LowLevelGraphicsPostScriptRenderer::drawImage (const Image& sourceImage, const AffineTransform& transform)
  386. {
  387. const int w = sourceImage.getWidth();
  388. const int h = sourceImage.getHeight();
  389. writeClip();
  390. out << "gsave ";
  391. writeTransform (transform.translated ((float) stateStack.getLast()->xOffset, (float) stateStack.getLast()->yOffset)
  392. .scaled (1.0f, -1.0f));
  393. RectangleList<int> imageClip;
  394. sourceImage.createSolidAreaMask (imageClip, 0.5f);
  395. out << "newpath ";
  396. int itemsOnLine = 0;
  397. for (const Rectangle<int>* i = imageClip.begin(), * const e = imageClip.end(); i != e; ++i)
  398. {
  399. if (++itemsOnLine == 6)
  400. {
  401. out << '\n';
  402. itemsOnLine = 0;
  403. }
  404. out << i->getX() << ' ' << i->getY() << ' ' << i->getWidth() << ' ' << i->getHeight() << " pr ";
  405. }
  406. out << " clip newpath\n";
  407. out << w << ' ' << h << " scale\n";
  408. out << w << ' ' << h << " 8 [" << w << " 0 0 -" << h << ' ' << (int) 0 << ' ' << h << " ]\n";
  409. writeImage (sourceImage, 0, 0, w, h);
  410. out << "false 3 colorimage grestore\n";
  411. needToClip = true;
  412. }
  413. //==============================================================================
  414. void LowLevelGraphicsPostScriptRenderer::drawLine (const Line <float>& line)
  415. {
  416. Path p;
  417. p.addLineSegment (line, 1.0f);
  418. fillPath (p, AffineTransform::identity);
  419. }
  420. //==============================================================================
  421. void LowLevelGraphicsPostScriptRenderer::setFont (const Font& newFont)
  422. {
  423. stateStack.getLast()->font = newFont;
  424. }
  425. const Font& LowLevelGraphicsPostScriptRenderer::getFont()
  426. {
  427. return stateStack.getLast()->font;
  428. }
  429. void LowLevelGraphicsPostScriptRenderer::drawGlyph (int glyphNumber, const AffineTransform& transform)
  430. {
  431. Path p;
  432. Font& font = stateStack.getLast()->font;
  433. font.getTypeface()->getOutlineForGlyph (glyphNumber, p);
  434. fillPath (p, AffineTransform::scale (font.getHeight() * font.getHorizontalScale(), font.getHeight()).followedBy (transform));
  435. }