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.

529 lines
16KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE library.
  4. Copyright (c) 2017 - ROLI Ltd.
  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 5 End-User License
  8. Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
  9. 27th April 2017).
  10. End User License Agreement: www.juce.com/juce-5-licence
  11. Privacy Policy: www.juce.com/juce-5-privacy-policy
  12. Or: You may also use this code under the terms of the GPL v3 (see
  13. www.gnu.org/licenses).
  14. JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
  15. EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
  16. DISCLAIMED.
  17. ==============================================================================
  18. */
  19. /**
  20. This scene description is broadcast to all the clients, and contains a list of all
  21. the clients involved, as well as the set of shapes to be drawn.
  22. Each client will draw the part of the path that lies within its own area. It can
  23. find its area by looking at the list of clients contained in this structure.
  24. All the path coordinates are roughly in units of inches, and devices will convert
  25. this to pixels based on their screen size and DPI
  26. */
  27. struct SharedCanvasDescription
  28. {
  29. SharedCanvasDescription() {}
  30. Colour backgroundColour = Colours::black;
  31. struct ColouredPath
  32. {
  33. Path path;
  34. FillType fill;
  35. };
  36. Array<ColouredPath> paths;
  37. struct ClientArea
  38. {
  39. String name;
  40. Point<float> centre; // in inches
  41. float scaleFactor; // extra scaling
  42. };
  43. Array<ClientArea> clients;
  44. //==============================================================================
  45. void reset()
  46. {
  47. paths.clearQuick();
  48. clients.clearQuick();
  49. }
  50. void swapWith (SharedCanvasDescription& other)
  51. {
  52. std::swap (backgroundColour, other.backgroundColour);
  53. paths.swapWith (other.paths);
  54. clients.swapWith (other.clients);
  55. }
  56. // This is a fixed size that represents the overall canvas limits that
  57. // content should lie within
  58. Rectangle<float> getLimits() const
  59. {
  60. float inchesX = 60.0f;
  61. float inchesY = 30.0f;
  62. return { inchesX * -0.5f, inchesY * -0.5f, inchesX, inchesY };
  63. }
  64. //==============================================================================
  65. void draw (Graphics& g, Rectangle<float> targetArea, Rectangle<float> clientArea) const
  66. {
  67. draw (g, clientArea,
  68. AffineTransform::fromTargetPoints (clientArea.getX(), clientArea.getY(),
  69. targetArea.getX(), targetArea.getY(),
  70. clientArea.getRight(), clientArea.getY(),
  71. targetArea.getRight(), targetArea.getY(),
  72. clientArea.getRight(), clientArea.getBottom(),
  73. targetArea.getRight(), targetArea.getBottom()));
  74. }
  75. void draw (Graphics& g, Rectangle<float> clientArea, AffineTransform t) const
  76. {
  77. g.saveState();
  78. g.addTransform (t);
  79. for (const auto& p : paths)
  80. {
  81. if (p.path.getBounds().intersects (clientArea))
  82. {
  83. g.setFillType (p.fill);
  84. g.fillPath (p.path);
  85. }
  86. }
  87. g.restoreState();
  88. }
  89. const ClientArea* findClient (const String& clientName) const
  90. {
  91. for (const auto& c : clients)
  92. if (c.name == clientName)
  93. return &c;
  94. return nullptr;
  95. }
  96. //==============================================================================
  97. // Serialisation...
  98. void save (OutputStream& out) const
  99. {
  100. out.writeInt (magic);
  101. out.writeInt ((int) backgroundColour.getARGB());
  102. out.writeInt (clients.size());
  103. for (const auto& c : clients)
  104. {
  105. out.writeString (c.name);
  106. writePoint (out, c.centre);
  107. out.writeFloat (c.scaleFactor);
  108. }
  109. out.writeInt (paths.size());
  110. for (const auto& p : paths)
  111. {
  112. writeFill (out, p.fill);
  113. p.path.writePathToStream (out);
  114. }
  115. }
  116. void load (InputStream& in)
  117. {
  118. if (in.readInt() != magic)
  119. return;
  120. backgroundColour = Colour ((uint32) in.readInt());
  121. {
  122. const int numClients = in.readInt();
  123. clients.clearQuick();
  124. for (int i = 0; i < numClients; ++i)
  125. {
  126. ClientArea c;
  127. c.name = in.readString();
  128. c.centre = readPoint (in);
  129. c.scaleFactor = in.readFloat();
  130. clients.add (c);
  131. }
  132. }
  133. {
  134. const int numPaths = in.readInt();
  135. paths.clearQuick();
  136. for (int i = 0; i < numPaths; ++i)
  137. {
  138. ColouredPath p;
  139. p.fill = readFill (in);
  140. p.path.loadPathFromStream (in);
  141. paths.add (std::move (p));
  142. }
  143. }
  144. }
  145. MemoryBlock toMemoryBlock() const
  146. {
  147. MemoryOutputStream o;
  148. save (o);
  149. return o.getMemoryBlock();
  150. }
  151. private:
  152. //==============================================================================
  153. static void writePoint (OutputStream& out, Point<float> p)
  154. {
  155. out.writeFloat (p.x);
  156. out.writeFloat (p.y);
  157. }
  158. static void writeRect (OutputStream& out, Rectangle<float> r)
  159. {
  160. writePoint (out, r.getPosition());
  161. out.writeFloat (r.getWidth());
  162. out.writeFloat (r.getHeight());
  163. }
  164. static Point<float> readPoint (InputStream& in)
  165. {
  166. Point<float> p;
  167. p.x = in.readFloat();
  168. p.y = in.readFloat();
  169. return p;
  170. }
  171. static Rectangle<float> readRect (InputStream& in)
  172. {
  173. Rectangle<float> r;
  174. r.setPosition (readPoint (in));
  175. r.setWidth (in.readFloat());
  176. r.setHeight (in.readFloat());
  177. return r;
  178. }
  179. static void writeFill (OutputStream& out, const FillType& f)
  180. {
  181. if (f.isColour())
  182. {
  183. out.writeByte (0);
  184. out.writeInt ((int) f.colour.getARGB());
  185. }
  186. else if (f.isGradient())
  187. {
  188. const ColourGradient& cg = *f.gradient;
  189. jassert (cg.getNumColours() >= 2);
  190. out.writeByte (cg.isRadial ? 2 : 1);
  191. writePoint (out, cg.point1);
  192. writePoint (out, cg.point2);
  193. out.writeCompressedInt (cg.getNumColours());
  194. for (int i = 0; i < cg.getNumColours(); ++i)
  195. {
  196. out.writeDouble (cg.getColourPosition (i));
  197. out.writeInt ((int) cg.getColour(i).getARGB());
  198. }
  199. }
  200. else
  201. {
  202. jassertfalse;
  203. }
  204. }
  205. static FillType readFill (InputStream& in)
  206. {
  207. int type = in.readByte();
  208. if (type == 0)
  209. return FillType (Colour ((uint32) in.readInt()));
  210. if (type > 2)
  211. {
  212. jassertfalse;
  213. return FillType();
  214. }
  215. ColourGradient cg;
  216. cg.point1 = readPoint (in);
  217. cg.point2 = readPoint (in);
  218. cg.clearColours();
  219. int numColours = in.readCompressedInt();
  220. for (int i = 0; i < numColours; ++i)
  221. {
  222. const double pos = in.readDouble();
  223. cg.addColour (pos, Colour ((uint32) in.readInt()));
  224. }
  225. jassert (cg.getNumColours() >= 2);
  226. return FillType (cg);
  227. }
  228. const int magic = 0x2381239a;
  229. JUCE_DECLARE_NON_COPYABLE (SharedCanvasDescription)
  230. };
  231. //==============================================================================
  232. class CanvasGeneratingContext : public LowLevelGraphicsContext
  233. {
  234. public:
  235. CanvasGeneratingContext (SharedCanvasDescription& c) : canvas (c)
  236. {
  237. stateStack.add (new SavedState());
  238. }
  239. //==============================================================================
  240. bool isVectorDevice() const override { return true; }
  241. float getPhysicalPixelScaleFactor() override { return 1.0f; }
  242. void setOrigin (Point<int> o) override { addTransform (AffineTransform::translation ((float) o.x, (float) o.y)); }
  243. void addTransform (const AffineTransform& t) override
  244. {
  245. getState().transform = t.followedBy (getState().transform);
  246. }
  247. bool clipToRectangle (const Rectangle<int>&) override { return true; }
  248. bool clipToRectangleList (const RectangleList<int>&) override { return true; }
  249. void excludeClipRectangle (const Rectangle<int>&) override {}
  250. void clipToPath (const Path&, const AffineTransform&) override {}
  251. void clipToImageAlpha (const Image&, const AffineTransform&) override {}
  252. void saveState() override
  253. {
  254. stateStack.add (new SavedState (getState()));
  255. }
  256. void restoreState() override
  257. {
  258. jassert (stateStack.size() > 0);
  259. if (stateStack.size() > 0)
  260. stateStack.removeLast();
  261. }
  262. void beginTransparencyLayer (float alpha) override
  263. {
  264. saveState();
  265. getState().transparencyLayer = new SharedCanvasHolder();
  266. getState().transparencyOpacity = alpha;
  267. }
  268. void endTransparencyLayer() override
  269. {
  270. const ReferenceCountedObjectPtr<SharedCanvasHolder> finishedTransparencyLayer (getState().transparencyLayer);
  271. float alpha = getState().transparencyOpacity;
  272. restoreState();
  273. if (SharedCanvasHolder* c = finishedTransparencyLayer)
  274. {
  275. for (auto& path : c->canvas.paths)
  276. {
  277. path.fill.setOpacity (path.fill.getOpacity() * alpha);
  278. getTargetCanvas().paths.add (path);
  279. }
  280. }
  281. }
  282. Rectangle<int> getClipBounds() const override
  283. {
  284. return canvas.getLimits().getSmallestIntegerContainer()
  285. .transformedBy (getState().transform.inverted());
  286. }
  287. bool clipRegionIntersects (const Rectangle<int>&) override { return true; }
  288. bool isClipEmpty() const override { return false; }
  289. //==============================================================================
  290. void setFill (const FillType& fillType) override { getState().fillType = fillType; }
  291. void setOpacity (float op) override { getState().fillType.setOpacity (op); }
  292. void setInterpolationQuality (Graphics::ResamplingQuality) override {}
  293. //==============================================================================
  294. void fillRect (const Rectangle<int>& r, bool) override { fillRect (r.toFloat()); }
  295. void fillRectList (const RectangleList<float>& list) override { fillPath (list.toPath(), AffineTransform()); }
  296. void fillRect (const Rectangle<float>& r) override
  297. {
  298. Path p;
  299. p.addRectangle (r.toFloat());
  300. fillPath (p, AffineTransform());
  301. }
  302. void fillPath (const Path& p, const AffineTransform& t) override
  303. {
  304. Path p2 (p);
  305. p2.applyTransform (t.followedBy (getState().transform));
  306. getTargetCanvas().paths.add ({ std::move (p2), getState().fillType });
  307. }
  308. void drawImage (const Image&, const AffineTransform&) override {}
  309. void drawLine (const Line<float>& line) override
  310. {
  311. Path p;
  312. p.addLineSegment (line, 1.0f);
  313. fillPath (p, AffineTransform());
  314. }
  315. //==============================================================================
  316. const Font& getFont() override { return getState().font; }
  317. void setFont (const Font& newFont) override { getState().font = newFont; }
  318. void drawGlyph (int glyphNumber, const AffineTransform& transform) override
  319. {
  320. Path p;
  321. Font& font = getState().font;
  322. font.getTypeface()->getOutlineForGlyph (glyphNumber, p);
  323. fillPath (p, AffineTransform::scale (font.getHeight() * font.getHorizontalScale(), font.getHeight()).followedBy (transform));
  324. }
  325. private:
  326. //==============================================================================
  327. struct SharedCanvasHolder : public ReferenceCountedObject
  328. {
  329. SharedCanvasDescription canvas;
  330. };
  331. struct SavedState
  332. {
  333. FillType fillType;
  334. AffineTransform transform;
  335. Font font;
  336. ReferenceCountedObjectPtr<SharedCanvasHolder> transparencyLayer;
  337. float transparencyOpacity = 1.0f;
  338. };
  339. SharedCanvasDescription& getTargetCanvas() const
  340. {
  341. if (SharedCanvasHolder* c = getState().transparencyLayer)
  342. return c->canvas;
  343. return canvas;
  344. }
  345. SavedState& getState() const noexcept
  346. {
  347. jassert (stateStack.size() > 0);
  348. return *stateStack.getLast();
  349. }
  350. SharedCanvasDescription& canvas;
  351. OwnedArray<SavedState> stateStack;
  352. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (CanvasGeneratingContext)
  353. };
  354. //==============================================================================
  355. /** Helper for breaking and reassembling a memory block into smaller checksummed
  356. blocks that will fit inside UDP packets
  357. */
  358. struct BlockPacketiser
  359. {
  360. void createBlocksFromData (const MemoryBlock& data, size_t maxBlockSize)
  361. {
  362. jassert (blocks.size() == 0);
  363. int offset = 0;
  364. size_t remaining = data.getSize();
  365. while (remaining > 0)
  366. {
  367. const int num = jmin (maxBlockSize, remaining);
  368. blocks.add (MemoryBlock (addBytesToPointer (data.getData(), offset), (size_t) num));
  369. offset += num;
  370. remaining -= num;
  371. }
  372. MemoryOutputStream checksumBlock;
  373. checksumBlock << getLastPacketPrefix() << MD5 (data).toHexString() << (char) 0 << (char) 0;
  374. blocks.add (checksumBlock.getMemoryBlock());
  375. for (int i = 0; i < blocks.size(); ++i)
  376. {
  377. uint32 index = ByteOrder::swapIfBigEndian (i);
  378. blocks.getReference(i).append (&index, sizeof (index));
  379. }
  380. }
  381. // returns true if this is an end-of-sequence block
  382. bool appendIncomingBlock (MemoryBlock data)
  383. {
  384. if (data.getSize() > 4)
  385. blocks.addSorted (*this, data);
  386. return String (CharPointer_ASCII ((const char*) data.getData())).startsWith (getLastPacketPrefix());
  387. }
  388. bool reassemble (MemoryBlock& result)
  389. {
  390. result.reset();
  391. if (blocks.size() > 1)
  392. {
  393. for (int i = 0; i < blocks.size() - 1; ++i)
  394. result.append (blocks.getReference(i).getData(), blocks.getReference(i).getSize() - 4);
  395. String storedMD5 (String (CharPointer_ASCII ((const char*) blocks.getLast().getData()))
  396. .fromFirstOccurrenceOf (getLastPacketPrefix(), false, false));
  397. blocks.clearQuick();
  398. if (MD5 (result).toHexString().trim().equalsIgnoreCase (storedMD5.trim()))
  399. return true;
  400. }
  401. result.reset();
  402. return false;
  403. }
  404. static int compareElements (const MemoryBlock& b1, const MemoryBlock& b2)
  405. {
  406. int i1 = ByteOrder::littleEndianInt (addBytesToPointer (b1.getData(), b1.getSize() - 4));
  407. int i2 = ByteOrder::littleEndianInt (addBytesToPointer (b2.getData(), b2.getSize() - 4));
  408. return i1 - i2;
  409. }
  410. static const char* getLastPacketPrefix() { return "**END_OF_PACKET_LIST** "; }
  411. Array<MemoryBlock> blocks;
  412. };
  413. //==============================================================================
  414. struct AnimatedContent
  415. {
  416. virtual ~AnimatedContent() {}
  417. virtual String getName() const = 0;
  418. virtual void reset() = 0;
  419. virtual void generateCanvas (Graphics&, SharedCanvasDescription& canvas, Rectangle<float> activeArea) = 0;
  420. virtual void handleTouch (Point<float> position) = 0;
  421. };