|
- /*
- ==============================================================================
-
- This file is part of the JUCE library.
- Copyright (c) 2022 - Raw Material Software Limited
-
- JUCE is an open source library subject to commercial or open-source
- licensing.
-
- By using JUCE, you agree to the terms of both the JUCE 7 End-User License
- Agreement and JUCE Privacy Policy.
-
- End User License Agreement: www.juce.com/juce-7-licence
- Privacy Policy: www.juce.com/juce-privacy-policy
-
- Or: You may also use this code under the terms of the GPL v3 (see
- www.gnu.org/licenses).
-
- JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
- EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
- DISCLAIMED.
-
- ==============================================================================
- */
-
-
- /**
- This scene description is broadcast to all the clients, and contains a list of all
- the clients involved, as well as the set of shapes to be drawn.
-
- Each client will draw the part of the path that lies within its own area. It can
- find its area by looking at the list of clients contained in this structure.
-
- All the path coordinates are roughly in units of inches, and devices will convert
- this to pixels based on their screen size and DPI
- */
- struct SharedCanvasDescription
- {
- SharedCanvasDescription() {}
-
- Colour backgroundColour = Colours::black;
-
- struct ColouredPath
- {
- Path path;
- FillType fill;
- };
-
- Array<ColouredPath> paths;
-
- struct ClientArea
- {
- String name;
- Point<float> centre; // in inches
- float scaleFactor; // extra scaling
- };
-
- Array<ClientArea> clients;
-
- //==============================================================================
- void reset()
- {
- paths.clearQuick();
- clients.clearQuick();
- }
-
- void swapWith (SharedCanvasDescription& other)
- {
- std::swap (backgroundColour, other.backgroundColour);
- paths.swapWith (other.paths);
- clients.swapWith (other.clients);
- }
-
- // This is a fixed size that represents the overall canvas limits that
- // content should lie within
- Rectangle<float> getLimits() const
- {
- float inchesX = 60.0f;
- float inchesY = 30.0f;
-
- return { inchesX * -0.5f, inchesY * -0.5f, inchesX, inchesY };
- }
-
- //==============================================================================
- void draw (Graphics& g, Rectangle<float> targetArea, Rectangle<float> clientArea) const
- {
- draw (g, clientArea,
- AffineTransform::fromTargetPoints (clientArea.getX(), clientArea.getY(),
- targetArea.getX(), targetArea.getY(),
- clientArea.getRight(), clientArea.getY(),
- targetArea.getRight(), targetArea.getY(),
- clientArea.getRight(), clientArea.getBottom(),
- targetArea.getRight(), targetArea.getBottom()));
- }
-
- void draw (Graphics& g, Rectangle<float> clientArea, AffineTransform t) const
- {
- g.saveState();
- g.addTransform (t);
-
- for (const auto& p : paths)
- {
- if (p.path.getBounds().intersects (clientArea))
- {
- g.setFillType (p.fill);
- g.fillPath (p.path);
- }
- }
-
- g.restoreState();
- }
-
- const ClientArea* findClient (const String& clientName) const
- {
- for (const auto& c : clients)
- if (c.name == clientName)
- return &c;
-
- return nullptr;
- }
-
- //==============================================================================
- // Serialisation...
-
- void save (OutputStream& out) const
- {
- out.writeInt (magic);
- out.writeInt ((int) backgroundColour.getARGB());
-
- out.writeInt (clients.size());
-
- for (const auto& c : clients)
- {
- out.writeString (c.name);
- writePoint (out, c.centre);
- out.writeFloat (c.scaleFactor);
- }
-
- out.writeInt (paths.size());
-
- for (const auto& p : paths)
- {
- writeFill (out, p.fill);
- p.path.writePathToStream (out);
- }
- }
-
- void load (InputStream& in)
- {
- if (in.readInt() != magic)
- return;
-
- backgroundColour = Colour ((uint32) in.readInt());
-
- {
- const int numClients = in.readInt();
- clients.clearQuick();
-
- for (int i = 0; i < numClients; ++i)
- {
- ClientArea c;
- c.name = in.readString();
- c.centre = readPoint (in);
- c.scaleFactor = in.readFloat();
- clients.add (c);
- }
- }
-
- {
- const int numPaths = in.readInt();
- paths.clearQuick();
-
- for (int i = 0; i < numPaths; ++i)
- {
- ColouredPath p;
- p.fill = readFill (in);
- p.path.loadPathFromStream (in);
-
- paths.add (std::move (p));
- }
- }
- }
-
- MemoryBlock toMemoryBlock() const
- {
- MemoryOutputStream o;
- save (o);
- return o.getMemoryBlock();
- }
-
- private:
- //==============================================================================
- static void writePoint (OutputStream& out, Point<float> p)
- {
- out.writeFloat (p.x);
- out.writeFloat (p.y);
- }
-
- static void writeRect (OutputStream& out, Rectangle<float> r)
- {
- writePoint (out, r.getPosition());
- out.writeFloat (r.getWidth());
- out.writeFloat (r.getHeight());
- }
-
- static Point<float> readPoint (InputStream& in)
- {
- Point<float> p;
- p.x = in.readFloat();
- p.y = in.readFloat();
- return p;
- }
-
- static Rectangle<float> readRect (InputStream& in)
- {
- Rectangle<float> r;
- r.setPosition (readPoint (in));
- r.setWidth (in.readFloat());
- r.setHeight (in.readFloat());
- return r;
- }
-
- static void writeFill (OutputStream& out, const FillType& f)
- {
- if (f.isColour())
- {
- out.writeByte (0);
- out.writeInt ((int) f.colour.getARGB());
- }
- else if (f.isGradient())
- {
- const ColourGradient& cg = *f.gradient;
- jassert (cg.getNumColours() >= 2);
-
- out.writeByte (cg.isRadial ? 2 : 1);
-
- writePoint (out, cg.point1);
- writePoint (out, cg.point2);
-
- out.writeCompressedInt (cg.getNumColours());
-
- for (int i = 0; i < cg.getNumColours(); ++i)
- {
- out.writeDouble (cg.getColourPosition (i));
- out.writeInt ((int) cg.getColour(i).getARGB());
- }
- }
- else
- {
- jassertfalse;
- }
- }
-
- static FillType readFill (InputStream& in)
- {
- int type = in.readByte();
-
- if (type == 0)
- return FillType (Colour ((uint32) in.readInt()));
-
- if (type > 2)
- {
- jassertfalse;
- return FillType();
- }
-
- ColourGradient cg;
- cg.point1 = readPoint (in);
- cg.point2 = readPoint (in);
-
- cg.clearColours();
-
- int numColours = in.readCompressedInt();
-
- for (int i = 0; i < numColours; ++i)
- {
- const double pos = in.readDouble();
- cg.addColour (pos, Colour ((uint32) in.readInt()));
- }
-
- jassert (cg.getNumColours() >= 2);
-
- return FillType (cg);
- }
-
- const int magic = 0x2381239a;
-
- JUCE_DECLARE_NON_COPYABLE (SharedCanvasDescription)
- };
-
- //==============================================================================
- class CanvasGeneratingContext : public LowLevelGraphicsContext
- {
- public:
- CanvasGeneratingContext (SharedCanvasDescription& c) : canvas (c)
- {
- stateStack.add (new SavedState());
- }
-
- //==============================================================================
- bool isVectorDevice() const override { return true; }
- float getPhysicalPixelScaleFactor() override { return 1.0f; }
- void setOrigin (Point<int> o) override { addTransform (AffineTransform::translation ((float) o.x, (float) o.y)); }
-
- void addTransform (const AffineTransform& t) override
- {
- getState().transform = t.followedBy (getState().transform);
- }
-
- bool clipToRectangle (const Rectangle<int>&) override { return true; }
- bool clipToRectangleList (const RectangleList<int>&) override { return true; }
- void excludeClipRectangle (const Rectangle<int>&) override {}
- void clipToPath (const Path&, const AffineTransform&) override {}
- void clipToImageAlpha (const Image&, const AffineTransform&) override {}
-
- void saveState() override
- {
- stateStack.add (new SavedState (getState()));
- }
-
- void restoreState() override
- {
- jassert (stateStack.size() > 0);
-
- if (stateStack.size() > 0)
- stateStack.removeLast();
- }
-
- void beginTransparencyLayer (float alpha) override
- {
- saveState();
- getState().transparencyLayer = new SharedCanvasHolder();
- getState().transparencyOpacity = alpha;
- }
-
- void endTransparencyLayer() override
- {
- const ReferenceCountedObjectPtr<SharedCanvasHolder> finishedTransparencyLayer (getState().transparencyLayer);
- float alpha = getState().transparencyOpacity;
- restoreState();
-
- if (SharedCanvasHolder* c = finishedTransparencyLayer)
- {
- for (auto& path : c->canvas.paths)
- {
- path.fill.setOpacity (path.fill.getOpacity() * alpha);
- getTargetCanvas().paths.add (path);
- }
- }
- }
-
- Rectangle<int> getClipBounds() const override
- {
- return canvas.getLimits().getSmallestIntegerContainer()
- .transformedBy (getState().transform.inverted());
- }
-
- bool clipRegionIntersects (const Rectangle<int>&) override { return true; }
- bool isClipEmpty() const override { return false; }
-
- //==============================================================================
- void setFill (const FillType& fillType) override { getState().fillType = fillType; }
- void setOpacity (float op) override { getState().fillType.setOpacity (op); }
- void setInterpolationQuality (Graphics::ResamplingQuality) override {}
-
- //==============================================================================
- void fillRect (const Rectangle<int>& r, bool) override { fillRect (r.toFloat()); }
- void fillRectList (const RectangleList<float>& list) override { fillPath (list.toPath(), AffineTransform()); }
-
- void fillRect (const Rectangle<float>& r) override
- {
- Path p;
- p.addRectangle (r.toFloat());
- fillPath (p, AffineTransform());
- }
-
- void fillPath (const Path& p, const AffineTransform& t) override
- {
- Path p2 (p);
- p2.applyTransform (t.followedBy (getState().transform));
-
- getTargetCanvas().paths.add ({ std::move (p2), getState().fillType });
- }
-
- void drawImage (const Image&, const AffineTransform&) override {}
-
- void drawLine (const Line<float>& line) override
- {
- Path p;
- p.addLineSegment (line, 1.0f);
- fillPath (p, AffineTransform());
- }
-
- //==============================================================================
- const Font& getFont() override { return getState().font; }
- void setFont (const Font& newFont) override { getState().font = newFont; }
-
- void drawGlyph (int glyphNumber, const AffineTransform& transform) override
- {
- Path p;
- Font& font = getState().font;
- font.getTypefacePtr()->getOutlineForGlyph (glyphNumber, p);
- fillPath (p, AffineTransform::scale (font.getHeight() * font.getHorizontalScale(), font.getHeight()).followedBy (transform));
- }
-
- private:
- //==============================================================================
- struct SharedCanvasHolder : public ReferenceCountedObject
- {
- SharedCanvasDescription canvas;
- };
-
- struct SavedState
- {
- FillType fillType;
- AffineTransform transform;
- Font font;
- ReferenceCountedObjectPtr<SharedCanvasHolder> transparencyLayer;
- float transparencyOpacity = 1.0f;
- };
-
- SharedCanvasDescription& getTargetCanvas() const
- {
- if (SharedCanvasHolder* c = getState().transparencyLayer)
- return c->canvas;
-
- return canvas;
- }
-
- SavedState& getState() const noexcept
- {
- jassert (stateStack.size() > 0);
- return *stateStack.getLast();
- }
-
- SharedCanvasDescription& canvas;
- OwnedArray<SavedState> stateStack;
-
- JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (CanvasGeneratingContext)
- };
-
- //==============================================================================
- /** Helper for breaking and reassembling a memory block into smaller checksummed
- blocks that will fit inside UDP packets
- */
- struct BlockPacketiser
- {
- void createBlocksFromData (const MemoryBlock& data, size_t maxBlockSize)
- {
- jassert (blocks.size() == 0);
-
- int offset = 0;
- size_t remaining = data.getSize();
-
- while (remaining > 0)
- {
- auto num = (size_t) jmin (maxBlockSize, remaining);
- blocks.add (MemoryBlock (addBytesToPointer (data.getData(), offset), num));
- offset += (int) num;
- remaining -= num;
- }
-
- MemoryOutputStream checksumBlock;
- checksumBlock << getLastPacketPrefix() << MD5 (data).toHexString() << (char) 0 << (char) 0;
- blocks.add (checksumBlock.getMemoryBlock());
-
- for (int i = 0; i < blocks.size(); ++i)
- {
- auto index = (uint32) ByteOrder::swapIfBigEndian (i);
- blocks.getReference(i).append (&index, sizeof (index));
- }
- }
-
- // returns true if this is an end-of-sequence block
- bool appendIncomingBlock (MemoryBlock data)
- {
- if (data.getSize() > 4)
- blocks.addSorted (*this, data);
-
- return String (CharPointer_ASCII ((const char*) data.getData())).startsWith (getLastPacketPrefix());
- }
-
- bool reassemble (MemoryBlock& result)
- {
- result.reset();
-
- if (blocks.size() > 1)
- {
- for (int i = 0; i < blocks.size() - 1; ++i)
- result.append (blocks.getReference(i).getData(), blocks.getReference(i).getSize() - 4);
-
- String storedMD5 (String (CharPointer_ASCII ((const char*) blocks.getLast().getData()))
- .fromFirstOccurrenceOf (getLastPacketPrefix(), false, false));
-
- blocks.clearQuick();
-
- if (MD5 (result).toHexString().trim().equalsIgnoreCase (storedMD5.trim()))
- return true;
- }
-
- result.reset();
- return false;
- }
-
- static int compareElements (const MemoryBlock& b1, const MemoryBlock& b2)
- {
- auto i1 = ByteOrder::littleEndianInt (addBytesToPointer (b1.getData(), b1.getSize() - 4));
- auto i2 = ByteOrder::littleEndianInt (addBytesToPointer (b2.getData(), b2.getSize() - 4));
- return (int) (i1 - i2);
- }
-
- static const char* getLastPacketPrefix() { return "**END_OF_PACKET_LIST** "; }
-
- Array<MemoryBlock> blocks;
- };
-
-
- //==============================================================================
- struct AnimatedContent
- {
- virtual ~AnimatedContent() {}
-
- virtual String getName() const = 0;
- virtual void reset() = 0;
- virtual void generateCanvas (Graphics&, SharedCanvasDescription& canvas, Rectangle<float> activeArea) = 0;
- virtual void handleTouch (Point<float> position) = 0;
- };
|