/* ============================================================================== This file is part of the JUCE 6 technical preview. Copyright (c) 2020 - Raw Material Software Limited You may use this code under the terms of the GPL v3 (see www.gnu.org/licenses). For this technical preview, this file is not subject to commercial licensing. JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE DISCLAIMED. ============================================================================== */ namespace juce { JUCE_BEGIN_IGNORE_WARNINGS_MSVC (4127) namespace RenderingHelpers { //============================================================================== /** Holds either a simple integer translation, or an affine transform. @tags{Graphics} */ class TranslationOrTransform { public: TranslationOrTransform() = default; TranslationOrTransform (Point origin) noexcept : offset (origin) {} TranslationOrTransform (const TranslationOrTransform& other) = default; AffineTransform getTransform() const noexcept { return isOnlyTranslated ? AffineTransform::translation (offset) : complexTransform; } AffineTransform getTransformWith (const AffineTransform& userTransform) const noexcept { return isOnlyTranslated ? userTransform.translated (offset) : userTransform.followedBy (complexTransform); } bool isIdentity() const noexcept { return isOnlyTranslated && offset.isOrigin(); } void setOrigin (Point delta) noexcept { if (isOnlyTranslated) offset += delta; else complexTransform = AffineTransform::translation (delta) .followedBy (complexTransform); } void addTransform (const AffineTransform& t) noexcept { if (isOnlyTranslated && t.isOnlyTranslation()) { auto tx = (int) (t.getTranslationX() * 256.0f); auto ty = (int) (t.getTranslationY() * 256.0f); if (((tx | ty) & 0xf8) == 0) { offset += Point (tx >> 8, ty >> 8); return; } } complexTransform = getTransformWith (t); isOnlyTranslated = false; isRotated = (complexTransform.mat01 != 0.0f || complexTransform.mat10 != 0.0f || complexTransform.mat00 < 0 || complexTransform.mat11 < 0); } float getPhysicalPixelScaleFactor() const noexcept { return isOnlyTranslated ? 1.0f : std::sqrt (std::abs (complexTransform.getDeterminant())); } void moveOriginInDeviceSpace (Point delta) noexcept { if (isOnlyTranslated) offset += delta; else complexTransform = complexTransform.translated (delta); } Rectangle translated (Rectangle r) const noexcept { jassert (isOnlyTranslated); return r + offset; } Rectangle translated (Rectangle r) const noexcept { jassert (isOnlyTranslated); return r + offset.toFloat(); } template RectangleOrPoint transformed (RectangleOrPoint r) const noexcept { jassert (! isOnlyTranslated); return r.transformedBy (complexTransform); } template Rectangle deviceSpaceToUserSpace (Rectangle r) const noexcept { return isOnlyTranslated ? r - offset : r.transformedBy (complexTransform.inverted()); } AffineTransform complexTransform; Point offset; bool isOnlyTranslated = true, isRotated = false; }; //============================================================================== /** Holds a cache of recently-used glyph objects of some type. @tags{Graphics} */ template class GlyphCache : private DeletedAtShutdown { public: GlyphCache() { reset(); } ~GlyphCache() override { getSingletonPointer() = nullptr; } static GlyphCache& getInstance() { auto& g = getSingletonPointer(); if (g == nullptr) g = new GlyphCache(); return *g; } //============================================================================== void reset() { const ScopedLock sl (lock); glyphs.clear(); addNewGlyphSlots (120); hits = 0; misses = 0; } void drawGlyph (RenderTargetType& target, const Font& font, const int glyphNumber, Point pos) { if (auto glyph = findOrCreateGlyph (font, glyphNumber)) { glyph->lastAccessCount = ++accessCounter; glyph->draw (target, pos); } } ReferenceCountedObjectPtr findOrCreateGlyph (const Font& font, int glyphNumber) { const ScopedLock sl (lock); if (auto g = findExistingGlyph (font, glyphNumber)) { ++hits; return g; } ++misses; auto g = getGlyphForReuse(); jassert (g != nullptr); g->generate (font, glyphNumber); return g; } private: ReferenceCountedArray glyphs; Atomic accessCounter, hits, misses; CriticalSection lock; ReferenceCountedObjectPtr findExistingGlyph (const Font& font, int glyphNumber) const noexcept { for (auto g : glyphs) if (g->glyph == glyphNumber && g->font == font) return *g; return {}; } ReferenceCountedObjectPtr getGlyphForReuse() { if (hits.get() + misses.get() > glyphs.size() * 16) { if (misses.get() * 2 > hits.get()) addNewGlyphSlots (32); hits = 0; misses = 0; } if (auto g = findLeastRecentlyUsedGlyph()) return *g; addNewGlyphSlots (32); return glyphs.getLast(); } void addNewGlyphSlots (int num) { glyphs.ensureStorageAllocated (glyphs.size() + num); while (--num >= 0) glyphs.add (new CachedGlyphType()); } CachedGlyphType* findLeastRecentlyUsedGlyph() const noexcept { CachedGlyphType* oldest = nullptr; auto oldestCounter = std::numeric_limits::max(); for (auto* g : glyphs) { if (g->lastAccessCount <= oldestCounter && g->getReferenceCount() == 1) { oldestCounter = g->lastAccessCount; oldest = g; } } return oldest; } static GlyphCache*& getSingletonPointer() noexcept { static GlyphCache* g = nullptr; return g; } JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (GlyphCache) }; //============================================================================== /** Caches a glyph as an edge-table. @tags{Graphics} */ template class CachedGlyphEdgeTable : public ReferenceCountedObject { public: CachedGlyphEdgeTable() = default; void draw (RendererType& state, Point pos) const { if (snapToIntegerCoordinate) pos.x = std::floor (pos.x + 0.5f); if (edgeTable != nullptr) state.fillEdgeTable (*edgeTable, pos.x, roundToInt (pos.y)); } void generate (const Font& newFont, int glyphNumber) { font = newFont; auto* typeface = newFont.getTypeface(); snapToIntegerCoordinate = typeface->isHinted(); glyph = glyphNumber; auto fontHeight = font.getHeight(); edgeTable.reset (typeface->getEdgeTableForGlyph (glyphNumber, AffineTransform::scale (fontHeight * font.getHorizontalScale(), fontHeight), fontHeight)); } Font font; std::unique_ptr edgeTable; int glyph = 0, lastAccessCount = 0; bool snapToIntegerCoordinate = false; JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (CachedGlyphEdgeTable) }; //============================================================================== /** Calculates the alpha values and positions for rendering the edges of a non-pixel-aligned rectangle. @tags{Graphics} */ struct FloatRectangleRasterisingInfo { FloatRectangleRasterisingInfo (Rectangle area) : left (roundToInt (256.0f * area.getX())), top (roundToInt (256.0f * area.getY())), right (roundToInt (256.0f * area.getRight())), bottom (roundToInt (256.0f * area.getBottom())) { if ((top >> 8) == (bottom >> 8)) { topAlpha = bottom - top; bottomAlpha = 0; totalTop = top >> 8; totalBottom = bottom = top = totalTop + 1; } else { if ((top & 255) == 0) { topAlpha = 0; top = totalTop = (top >> 8); } else { topAlpha = 255 - (top & 255); totalTop = (top >> 8); top = totalTop + 1; } bottomAlpha = bottom & 255; bottom >>= 8; totalBottom = bottom + (bottomAlpha != 0 ? 1 : 0); } if ((left >> 8) == (right >> 8)) { leftAlpha = right - left; rightAlpha = 0; totalLeft = (left >> 8); totalRight = right = left = totalLeft + 1; } else { if ((left & 255) == 0) { leftAlpha = 0; left = totalLeft = (left >> 8); } else { leftAlpha = 255 - (left & 255); totalLeft = (left >> 8); left = totalLeft + 1; } rightAlpha = right & 255; right >>= 8; totalRight = right + (rightAlpha != 0 ? 1 : 0); } } template void iterate (Callback& callback) const { if (topAlpha != 0) callback (totalLeft, totalTop, totalRight - totalLeft, 1, topAlpha); if (bottomAlpha != 0) callback (totalLeft, bottom, totalRight - totalLeft, 1, bottomAlpha); if (leftAlpha != 0) callback (totalLeft, totalTop, 1, totalBottom - totalTop, leftAlpha); if (rightAlpha != 0) callback (right, totalTop, 1, totalBottom - totalTop, rightAlpha); callback (left, top, right - left, bottom - top, 255); } inline bool isOnePixelWide() const noexcept { return right - left == 1 && leftAlpha + rightAlpha == 0; } inline int getTopLeftCornerAlpha() const noexcept { return (topAlpha * leftAlpha) >> 8; } inline int getTopRightCornerAlpha() const noexcept { return (topAlpha * rightAlpha) >> 8; } inline int getBottomLeftCornerAlpha() const noexcept { return (bottomAlpha * leftAlpha) >> 8; } inline int getBottomRightCornerAlpha() const noexcept { return (bottomAlpha * rightAlpha) >> 8; } //============================================================================== int left, top, right, bottom; // bounds of the solid central area, excluding anti-aliased edges int totalTop, totalLeft, totalBottom, totalRight; // bounds of the total area, including edges int topAlpha, leftAlpha, bottomAlpha, rightAlpha; // alpha of each anti-aliased edge }; //============================================================================== /** Contains classes for calculating the colour of pixels within various types of gradient. */ namespace GradientPixelIterators { /** Iterates the colour of pixels in a linear gradient */ struct Linear { Linear (const ColourGradient& gradient, const AffineTransform& transform, const PixelARGB* colours, int numColours) : lookupTable (colours), numEntries (numColours) { jassert (numColours >= 0); auto p1 = gradient.point1; auto p2 = gradient.point2; if (! transform.isIdentity()) { auto p3 = Line (p2, p1).getPointAlongLine (0.0f, 100.0f); p1.applyTransform (transform); p2.applyTransform (transform); p3.applyTransform (transform); p2 = Line (p2, p3).findNearestPointTo (p1); } vertical = std::abs (p1.x - p2.x) < 0.001f; horizontal = std::abs (p1.y - p2.y) < 0.001f; if (vertical) { scale = roundToInt ((numEntries << (int) numScaleBits) / (double) (p2.y - p1.y)); start = roundToInt (p1.y * (float) scale); } else if (horizontal) { scale = roundToInt ((numEntries << (int) numScaleBits) / (double) (p2.x - p1.x)); start = roundToInt (p1.x * (float) scale); } else { grad = (p2.getY() - p1.y) / (double) (p1.x - p2.x); yTerm = p1.getY() - p1.x / grad; scale = roundToInt ((numEntries << (int) numScaleBits) / (yTerm * grad - (p2.y * grad - p2.x))); grad *= scale; } } forcedinline void setY (int y) noexcept { if (vertical) linePix = lookupTable[jlimit (0, numEntries, (y * scale - start) >> (int) numScaleBits)]; else if (! horizontal) start = roundToInt ((y - yTerm) * grad); } inline PixelARGB getPixel (int x) const noexcept { return vertical ? linePix : lookupTable[jlimit (0, numEntries, (x * scale - start) >> (int) numScaleBits)]; } const PixelARGB* const lookupTable; const int numEntries; PixelARGB linePix; int start, scale; double grad, yTerm; bool vertical, horizontal; enum { numScaleBits = 12 }; JUCE_DECLARE_NON_COPYABLE (Linear) }; //============================================================================== /** Iterates the colour of pixels in a circular radial gradient */ struct Radial { Radial (const ColourGradient& gradient, const AffineTransform&, const PixelARGB* colours, int numColours) : lookupTable (colours), numEntries (numColours), gx1 (gradient.point1.x), gy1 (gradient.point1.y) { jassert (numColours >= 0); auto diff = gradient.point1 - gradient.point2; maxDist = diff.x * diff.x + diff.y * diff.y; invScale = numEntries / std::sqrt (maxDist); jassert (roundToInt (std::sqrt (maxDist) * invScale) <= numEntries); } forcedinline void setY (int y) noexcept { dy = y - gy1; dy *= dy; } inline PixelARGB getPixel (int px) const noexcept { auto x = px - gx1; x *= x; x += dy; return lookupTable[x >= maxDist ? numEntries : roundToInt (std::sqrt (x) * invScale)]; } const PixelARGB* const lookupTable; const int numEntries; const double gx1, gy1; double maxDist, invScale, dy; JUCE_DECLARE_NON_COPYABLE (Radial) }; //============================================================================== /** Iterates the colour of pixels in a skewed radial gradient */ struct TransformedRadial : public Radial { TransformedRadial (const ColourGradient& gradient, const AffineTransform& transform, const PixelARGB* colours, int numColours) : Radial (gradient, transform, colours, numColours), inverseTransform (transform.inverted()) { tM10 = inverseTransform.mat10; tM00 = inverseTransform.mat00; } forcedinline void setY (int y) noexcept { auto floatY = (float) y; lineYM01 = inverseTransform.mat01 * floatY + inverseTransform.mat02 - gx1; lineYM11 = inverseTransform.mat11 * floatY + inverseTransform.mat12 - gy1; } inline PixelARGB getPixel (int px) const noexcept { double x = px; auto y = tM10 * x + lineYM11; x = tM00 * x + lineYM01; x *= x; x += y * y; if (x >= maxDist) return lookupTable[numEntries]; return lookupTable[jmin (numEntries, roundToInt (std::sqrt (x) * invScale))]; } private: double tM10, tM00, lineYM01, lineYM11; const AffineTransform inverseTransform; JUCE_DECLARE_NON_COPYABLE (TransformedRadial) }; } #define JUCE_PERFORM_PIXEL_OP_LOOP(op) \ { \ const int destStride = destData.pixelStride; \ do { dest->op; dest = addBytesToPointer (dest, destStride); } while (--width > 0); \ } //============================================================================== /** Contains classes for filling edge tables with various fill types. */ namespace EdgeTableFillers { /** Fills an edge-table with a solid colour. */ template struct SolidColour { SolidColour (const Image::BitmapData& image, PixelARGB colour) : destData (image), sourceColour (colour) { if (sizeof (PixelType) == 3 && (size_t) destData.pixelStride == sizeof (PixelType)) areRGBComponentsEqual = sourceColour.getRed() == sourceColour.getGreen() && sourceColour.getGreen() == sourceColour.getBlue(); else areRGBComponentsEqual = false; } forcedinline void setEdgeTableYPos (int y) noexcept { linePixels = (PixelType*) destData.getLinePointer (y); } forcedinline void handleEdgeTablePixel (int x, int alphaLevel) const noexcept { if (replaceExisting) getPixel (x)->set (sourceColour); else getPixel (x)->blend (sourceColour, (uint32) alphaLevel); } forcedinline void handleEdgeTablePixelFull (int x) const noexcept { if (replaceExisting) getPixel (x)->set (sourceColour); else getPixel (x)->blend (sourceColour); } forcedinline void handleEdgeTableLine (int x, int width, int alphaLevel) const noexcept { auto p = sourceColour; p.multiplyAlpha (alphaLevel); auto* dest = getPixel (x); if (replaceExisting || p.getAlpha() >= 0xff) replaceLine (dest, p, width); else blendLine (dest, p, width); } forcedinline void handleEdgeTableLineFull (int x, int width) const noexcept { auto* dest = getPixel (x); if (replaceExisting || sourceColour.getAlpha() >= 0xff) replaceLine (dest, sourceColour, width); else blendLine (dest, sourceColour, width); } void handleEdgeTableRectangle (int x, int y, int width, int height, int alphaLevel) noexcept { auto p = sourceColour; p.multiplyAlpha (alphaLevel); setEdgeTableYPos (y); auto* dest = getPixel (x); if (replaceExisting || p.getAlpha() >= 0xff) { while (--height >= 0) { replaceLine (dest, p, width); dest = addBytesToPointer (dest, destData.lineStride); } } else { while (--height >= 0) { blendLine (dest, p, width); dest = addBytesToPointer (dest, destData.lineStride); } } } void handleEdgeTableRectangleFull (int x, int y, int width, int height) noexcept { handleEdgeTableRectangle (x, y, width, height, 255); } private: const Image::BitmapData& destData; PixelType* linePixels; PixelARGB sourceColour; bool areRGBComponentsEqual; forcedinline PixelType* getPixel (int x) const noexcept { return addBytesToPointer (linePixels, x * destData.pixelStride); } inline void blendLine (PixelType* dest, PixelARGB colour, int width) const noexcept { JUCE_PERFORM_PIXEL_OP_LOOP (blend (colour)) } forcedinline void replaceLine (PixelRGB* dest, PixelARGB colour, int width) const noexcept { if ((size_t) destData.pixelStride == sizeof (*dest) && areRGBComponentsEqual) memset ((void*) dest, colour.getRed(), (size_t) width * 3); // if all the component values are the same, we can cheat.. else JUCE_PERFORM_PIXEL_OP_LOOP (set (colour)); } forcedinline void replaceLine (PixelAlpha* dest, const PixelARGB colour, int width) const noexcept { if ((size_t) destData.pixelStride == sizeof (*dest)) memset ((void*) dest, colour.getAlpha(), (size_t) width); else JUCE_PERFORM_PIXEL_OP_LOOP (setAlpha (colour.getAlpha())) } forcedinline void replaceLine (PixelARGB* dest, const PixelARGB colour, int width) const noexcept { JUCE_PERFORM_PIXEL_OP_LOOP (set (colour)) } JUCE_DECLARE_NON_COPYABLE (SolidColour) }; //============================================================================== /** Fills an edge-table with a gradient. */ template struct Gradient : public GradientType { Gradient (const Image::BitmapData& dest, const ColourGradient& gradient, const AffineTransform& transform, const PixelARGB* colours, int numColours) : GradientType (gradient, transform, colours, numColours - 1), destData (dest) { } forcedinline void setEdgeTableYPos (int y) noexcept { linePixels = (PixelType*) destData.getLinePointer (y); GradientType::setY (y); } forcedinline void handleEdgeTablePixel (int x, int alphaLevel) const noexcept { getPixel (x)->blend (GradientType::getPixel (x), (uint32) alphaLevel); } forcedinline void handleEdgeTablePixelFull (int x) const noexcept { getPixel (x)->blend (GradientType::getPixel (x)); } void handleEdgeTableLine (int x, int width, int alphaLevel) const noexcept { auto* dest = getPixel (x); if (alphaLevel < 0xff) JUCE_PERFORM_PIXEL_OP_LOOP (blend (GradientType::getPixel (x++), (uint32) alphaLevel)) else JUCE_PERFORM_PIXEL_OP_LOOP (blend (GradientType::getPixel (x++))) } void handleEdgeTableLineFull (int x, int width) const noexcept { auto* dest = getPixel (x); JUCE_PERFORM_PIXEL_OP_LOOP (blend (GradientType::getPixel (x++))) } void handleEdgeTableRectangle (int x, int y, int width, int height, int alphaLevel) noexcept { while (--height >= 0) { setEdgeTableYPos (y++); handleEdgeTableLine (x, width, alphaLevel); } } void handleEdgeTableRectangleFull (int x, int y, int width, int height) noexcept { while (--height >= 0) { setEdgeTableYPos (y++); handleEdgeTableLineFull (x, width); } } private: const Image::BitmapData& destData; PixelType* linePixels; forcedinline PixelType* getPixel (int x) const noexcept { return addBytesToPointer (linePixels, x * destData.pixelStride); } JUCE_DECLARE_NON_COPYABLE (Gradient) }; //============================================================================== /** Fills an edge-table with a non-transformed image. */ template struct ImageFill { ImageFill (const Image::BitmapData& dest, const Image::BitmapData& src, int alpha, int x, int y) : destData (dest), srcData (src), extraAlpha (alpha + 1), xOffset (repeatPattern ? negativeAwareModulo (x, src.width) - src.width : x), yOffset (repeatPattern ? negativeAwareModulo (y, src.height) - src.height : y) { } forcedinline void setEdgeTableYPos (int y) noexcept { linePixels = (DestPixelType*) destData.getLinePointer (y); y -= yOffset; if (repeatPattern) { jassert (y >= 0); y %= srcData.height; } sourceLineStart = (SrcPixelType*) srcData.getLinePointer (y); } forcedinline void handleEdgeTablePixel (int x, int alphaLevel) const noexcept { alphaLevel = (alphaLevel * extraAlpha) >> 8; getDestPixel (x)->blend (*getSrcPixel (repeatPattern ? ((x - xOffset) % srcData.width) : (x - xOffset)), (uint32) alphaLevel); } forcedinline void handleEdgeTablePixelFull (int x) const noexcept { getDestPixel (x)->blend (*getSrcPixel (repeatPattern ? ((x - xOffset) % srcData.width) : (x - xOffset)), (uint32) extraAlpha); } void handleEdgeTableLine (int x, int width, int alphaLevel) const noexcept { auto* dest = getDestPixel (x); alphaLevel = (alphaLevel * extraAlpha) >> 8; x -= xOffset; if (repeatPattern) { if (alphaLevel < 0xfe) JUCE_PERFORM_PIXEL_OP_LOOP (blend (*getSrcPixel (x++ % srcData.width), (uint32) alphaLevel)) else JUCE_PERFORM_PIXEL_OP_LOOP (blend (*getSrcPixel (x++ % srcData.width))) } else { jassert (x >= 0 && x + width <= srcData.width); if (alphaLevel < 0xfe) JUCE_PERFORM_PIXEL_OP_LOOP (blend (*getSrcPixel (x++), (uint32) alphaLevel)) else copyRow (dest, getSrcPixel (x), width); } } void handleEdgeTableLineFull (int x, int width) const noexcept { auto* dest = getDestPixel (x); x -= xOffset; if (repeatPattern) { if (extraAlpha < 0xfe) JUCE_PERFORM_PIXEL_OP_LOOP (blend (*getSrcPixel (x++ % srcData.width), (uint32) extraAlpha)) else JUCE_PERFORM_PIXEL_OP_LOOP (blend (*getSrcPixel (x++ % srcData.width))) } else { jassert (x >= 0 && x + width <= srcData.width); if (extraAlpha < 0xfe) JUCE_PERFORM_PIXEL_OP_LOOP (blend (*getSrcPixel (x++), (uint32) extraAlpha)) else copyRow (dest, getSrcPixel (x), width); } } void handleEdgeTableRectangle (int x, int y, int width, int height, int alphaLevel) noexcept { while (--height >= 0) { setEdgeTableYPos (y++); handleEdgeTableLine (x, width, alphaLevel); } } void handleEdgeTableRectangleFull (int x, int y, int width, int height) noexcept { while (--height >= 0) { setEdgeTableYPos (y++); handleEdgeTableLineFull (x, width); } } void clipEdgeTableLine (EdgeTable& et, int x, int y, int width) { jassert (x - xOffset >= 0 && x + width - xOffset <= srcData.width); auto* s = (SrcPixelType*) srcData.getLinePointer (y - yOffset); auto* mask = (uint8*) (s + x - xOffset); if (sizeof (SrcPixelType) == sizeof (PixelARGB)) mask += PixelARGB::indexA; et.clipLineToMask (x, y, mask, sizeof (SrcPixelType), width); } private: const Image::BitmapData& destData; const Image::BitmapData& srcData; const int extraAlpha, xOffset, yOffset; DestPixelType* linePixels; SrcPixelType* sourceLineStart; forcedinline DestPixelType* getDestPixel (int x) const noexcept { return addBytesToPointer (linePixels, x * destData.pixelStride); } forcedinline SrcPixelType const* getSrcPixel (int x) const noexcept { return addBytesToPointer (sourceLineStart, x * srcData.pixelStride); } forcedinline void copyRow (DestPixelType* dest, SrcPixelType const* src, int width) const noexcept { auto destStride = destData.pixelStride; auto srcStride = srcData.pixelStride; if (destStride == srcStride && srcData.pixelFormat == Image::RGB && destData.pixelFormat == Image::RGB) { memcpy ((void*) dest, src, (size_t) (width * srcStride)); } else { do { dest->blend (*src); dest = addBytesToPointer (dest, destStride); src = addBytesToPointer (src, srcStride); } while (--width > 0); } } JUCE_DECLARE_NON_COPYABLE (ImageFill) }; //============================================================================== /** Fills an edge-table with a transformed image. */ template struct TransformedImageFill { TransformedImageFill (const Image::BitmapData& dest, const Image::BitmapData& src, const AffineTransform& transform, int alpha, Graphics::ResamplingQuality q) : interpolator (transform, q != Graphics::lowResamplingQuality ? 0.5f : 0.0f, q != Graphics::lowResamplingQuality ? -128 : 0), destData (dest), srcData (src), extraAlpha (alpha + 1), quality (q), maxX (src.width - 1), maxY (src.height - 1) { scratchBuffer.malloc (scratchSize); } forcedinline void setEdgeTableYPos (int newY) noexcept { currentY = newY; linePixels = (DestPixelType*) destData.getLinePointer (newY); } forcedinline void handleEdgeTablePixel (int x, int alphaLevel) noexcept { SrcPixelType p; generate (&p, x, 1); getDestPixel (x)->blend (p, (uint32) (alphaLevel * extraAlpha) >> 8); } forcedinline void handleEdgeTablePixelFull (int x) noexcept { SrcPixelType p; generate (&p, x, 1); getDestPixel (x)->blend (p, (uint32) extraAlpha); } void handleEdgeTableLine (int x, int width, int alphaLevel) noexcept { if (width > (int) scratchSize) { scratchSize = (size_t) width; scratchBuffer.malloc (scratchSize); } SrcPixelType* span = scratchBuffer; generate (span, x, width); auto* dest = getDestPixel (x); alphaLevel *= extraAlpha; alphaLevel >>= 8; if (alphaLevel < 0xfe) JUCE_PERFORM_PIXEL_OP_LOOP (blend (*span++, (uint32) alphaLevel)) else JUCE_PERFORM_PIXEL_OP_LOOP (blend (*span++)) } forcedinline void handleEdgeTableLineFull (int x, int width) noexcept { handleEdgeTableLine (x, width, 255); } void handleEdgeTableRectangle (int x, int y, int width, int height, int alphaLevel) noexcept { while (--height >= 0) { setEdgeTableYPos (y++); handleEdgeTableLine (x, width, alphaLevel); } } void handleEdgeTableRectangleFull (int x, int y, int width, int height) noexcept { while (--height >= 0) { setEdgeTableYPos (y++); handleEdgeTableLineFull (x, width); } } void clipEdgeTableLine (EdgeTable& et, int x, int y, int width) { if (width > (int) scratchSize) { scratchSize = (size_t) width; scratchBuffer.malloc (scratchSize); } currentY = y; generate (scratchBuffer.get(), x, width); et.clipLineToMask (x, y, reinterpret_cast (scratchBuffer.get()) + SrcPixelType::indexA, sizeof (SrcPixelType), width); } private: forcedinline DestPixelType* getDestPixel (int x) const noexcept { return addBytesToPointer (linePixels, x * destData.pixelStride); } //============================================================================== template void generate (PixelType* dest, int x, int numPixels) noexcept { this->interpolator.setStartOfLine ((float) x, (float) currentY, numPixels); do { int hiResX, hiResY; this->interpolator.next (hiResX, hiResY); int loResX = hiResX >> 8; int loResY = hiResY >> 8; if (repeatPattern) { loResX = negativeAwareModulo (loResX, srcData.width); loResY = negativeAwareModulo (loResY, srcData.height); } if (quality != Graphics::lowResamplingQuality) { if (isPositiveAndBelow (loResX, maxX)) { if (isPositiveAndBelow (loResY, maxY)) { // In the centre of the image.. render4PixelAverage (dest, this->srcData.getPixelPointer (loResX, loResY), hiResX & 255, hiResY & 255); ++dest; continue; } if (! repeatPattern) { // At a top or bottom edge.. if (loResY < 0) render2PixelAverageX (dest, this->srcData.getPixelPointer (loResX, 0), hiResX & 255); else render2PixelAverageX (dest, this->srcData.getPixelPointer (loResX, maxY), hiResX & 255); ++dest; continue; } } else { if (isPositiveAndBelow (loResY, maxY) && ! repeatPattern) { // At a left or right hand edge.. if (loResX < 0) render2PixelAverageY (dest, this->srcData.getPixelPointer (0, loResY), hiResY & 255); else render2PixelAverageY (dest, this->srcData.getPixelPointer (maxX, loResY), hiResY & 255); ++dest; continue; } } } if (! repeatPattern) { if (loResX < 0) loResX = 0; if (loResY < 0) loResY = 0; if (loResX > maxX) loResX = maxX; if (loResY > maxY) loResY = maxY; } dest->set (*(const PixelType*) this->srcData.getPixelPointer (loResX, loResY)); ++dest; } while (--numPixels > 0); } //============================================================================== void render4PixelAverage (PixelARGB* dest, const uint8* src, int subPixelX, int subPixelY) noexcept { uint32 c[4] = { 256 * 128, 256 * 128, 256 * 128, 256 * 128 }; auto weight = (uint32) ((256 - subPixelX) * (256 - subPixelY)); c[0] += weight * src[0]; c[1] += weight * src[1]; c[2] += weight * src[2]; c[3] += weight * src[3]; src += this->srcData.pixelStride; weight = (uint32) (subPixelX * (256 - subPixelY)); c[0] += weight * src[0]; c[1] += weight * src[1]; c[2] += weight * src[2]; c[3] += weight * src[3]; src += this->srcData.lineStride; weight = (uint32) (subPixelX * subPixelY); c[0] += weight * src[0]; c[1] += weight * src[1]; c[2] += weight * src[2]; c[3] += weight * src[3]; src -= this->srcData.pixelStride; weight = (uint32) ((256 - subPixelX) * subPixelY); c[0] += weight * src[0]; c[1] += weight * src[1]; c[2] += weight * src[2]; c[3] += weight * src[3]; dest->setARGB ((uint8) (c[PixelARGB::indexA] >> 16), (uint8) (c[PixelARGB::indexR] >> 16), (uint8) (c[PixelARGB::indexG] >> 16), (uint8) (c[PixelARGB::indexB] >> 16)); } void render2PixelAverageX (PixelARGB* dest, const uint8* src, uint32 subPixelX) noexcept { uint32 c[4] = { 128, 128, 128, 128 }; uint32 weight = 256 - subPixelX; c[0] += weight * src[0]; c[1] += weight * src[1]; c[2] += weight * src[2]; c[3] += weight * src[3]; src += this->srcData.pixelStride; weight = subPixelX; c[0] += weight * src[0]; c[1] += weight * src[1]; c[2] += weight * src[2]; c[3] += weight * src[3]; dest->setARGB ((uint8) (c[PixelARGB::indexA] >> 8), (uint8) (c[PixelARGB::indexR] >> 8), (uint8) (c[PixelARGB::indexG] >> 8), (uint8) (c[PixelARGB::indexB] >> 8)); } void render2PixelAverageY (PixelARGB* dest, const uint8* src, uint32 subPixelY) noexcept { uint32 c[4] = { 128, 128, 128, 128 }; uint32 weight = 256 - subPixelY; c[0] += weight * src[0]; c[1] += weight * src[1]; c[2] += weight * src[2]; c[3] += weight * src[3]; src += this->srcData.lineStride; weight = subPixelY; c[0] += weight * src[0]; c[1] += weight * src[1]; c[2] += weight * src[2]; c[3] += weight * src[3]; dest->setARGB ((uint8) (c[PixelARGB::indexA] >> 8), (uint8) (c[PixelARGB::indexR] >> 8), (uint8) (c[PixelARGB::indexG] >> 8), (uint8) (c[PixelARGB::indexB] >> 8)); } //============================================================================== void render4PixelAverage (PixelRGB* dest, const uint8* src, uint32 subPixelX, uint32 subPixelY) noexcept { uint32 c[3] = { 256 * 128, 256 * 128, 256 * 128 }; uint32 weight = (256 - subPixelX) * (256 - subPixelY); c[0] += weight * src[0]; c[1] += weight * src[1]; c[2] += weight * src[2]; src += this->srcData.pixelStride; weight = subPixelX * (256 - subPixelY); c[0] += weight * src[0]; c[1] += weight * src[1]; c[2] += weight * src[2]; src += this->srcData.lineStride; weight = subPixelX * subPixelY; c[0] += weight * src[0]; c[1] += weight * src[1]; c[2] += weight * src[2]; src -= this->srcData.pixelStride; weight = (256 - subPixelX) * subPixelY; c[0] += weight * src[0]; c[1] += weight * src[1]; c[2] += weight * src[2]; dest->setARGB ((uint8) 255, (uint8) (c[PixelRGB::indexR] >> 16), (uint8) (c[PixelRGB::indexG] >> 16), (uint8) (c[PixelRGB::indexB] >> 16)); } void render2PixelAverageX (PixelRGB* dest, const uint8* src, uint32 subPixelX) noexcept { uint32 c[3] = { 128, 128, 128 }; const uint32 weight = 256 - subPixelX; c[0] += weight * src[0]; c[1] += weight * src[1]; c[2] += weight * src[2]; src += this->srcData.pixelStride; c[0] += subPixelX * src[0]; c[1] += subPixelX * src[1]; c[2] += subPixelX * src[2]; dest->setARGB ((uint8) 255, (uint8) (c[PixelRGB::indexR] >> 8), (uint8) (c[PixelRGB::indexG] >> 8), (uint8) (c[PixelRGB::indexB] >> 8)); } void render2PixelAverageY (PixelRGB* dest, const uint8* src, uint32 subPixelY) noexcept { uint32 c[3] = { 128, 128, 128 }; const uint32 weight = 256 - subPixelY; c[0] += weight * src[0]; c[1] += weight * src[1]; c[2] += weight * src[2]; src += this->srcData.lineStride; c[0] += subPixelY * src[0]; c[1] += subPixelY * src[1]; c[2] += subPixelY * src[2]; dest->setARGB ((uint8) 255, (uint8) (c[PixelRGB::indexR] >> 8), (uint8) (c[PixelRGB::indexG] >> 8), (uint8) (c[PixelRGB::indexB] >> 8)); } //============================================================================== void render4PixelAverage (PixelAlpha* dest, const uint8* src, uint32 subPixelX, uint32 subPixelY) noexcept { uint32 c = 256 * 128; c += src[0] * ((256 - subPixelX) * (256 - subPixelY)); src += this->srcData.pixelStride; c += src[0] * (subPixelX * (256 - subPixelY)); src += this->srcData.lineStride; c += src[0] * (subPixelX * subPixelY); src -= this->srcData.pixelStride; c += src[0] * ((256 - subPixelX) * subPixelY); *((uint8*) dest) = (uint8) (c >> 16); } void render2PixelAverageX (PixelAlpha* dest, const uint8* src, uint32 subPixelX) noexcept { uint32 c = 128; c += src[0] * (256 - subPixelX); src += this->srcData.pixelStride; c += src[0] * subPixelX; *((uint8*) dest) = (uint8) (c >> 8); } void render2PixelAverageY (PixelAlpha* dest, const uint8* src, uint32 subPixelY) noexcept { uint32 c = 128; c += src[0] * (256 - subPixelY); src += this->srcData.lineStride; c += src[0] * subPixelY; *((uint8*) dest) = (uint8) (c >> 8); } //============================================================================== struct TransformedImageSpanInterpolator { TransformedImageSpanInterpolator (const AffineTransform& transform, float offsetFloat, int offsetInt) noexcept : inverseTransform (transform.inverted()), pixelOffset (offsetFloat), pixelOffsetInt (offsetInt) {} void setStartOfLine (float sx, float sy, int numPixels) noexcept { jassert (numPixels > 0); sx += pixelOffset; sy += pixelOffset; auto x1 = sx, y1 = sy; sx += (float) numPixels; inverseTransform.transformPoints (x1, y1, sx, sy); xBresenham.set ((int) (x1 * 256.0f), (int) (sx * 256.0f), numPixels, pixelOffsetInt); yBresenham.set ((int) (y1 * 256.0f), (int) (sy * 256.0f), numPixels, pixelOffsetInt); } void next (int& px, int& py) noexcept { px = xBresenham.n; xBresenham.stepToNext(); py = yBresenham.n; yBresenham.stepToNext(); } private: struct BresenhamInterpolator { BresenhamInterpolator() = default; void set (int n1, int n2, int steps, int offsetInt) noexcept { numSteps = steps; step = (n2 - n1) / numSteps; remainder = modulo = (n2 - n1) % numSteps; n = n1 + offsetInt; if (modulo <= 0) { modulo += numSteps; remainder += numSteps; --step; } modulo -= numSteps; } forcedinline void stepToNext() noexcept { modulo += remainder; n += step; if (modulo > 0) { modulo -= numSteps; ++n; } } int n; private: int numSteps, step, modulo, remainder; }; const AffineTransform inverseTransform; BresenhamInterpolator xBresenham, yBresenham; const float pixelOffset; const int pixelOffsetInt; JUCE_DECLARE_NON_COPYABLE (TransformedImageSpanInterpolator) }; //============================================================================== TransformedImageSpanInterpolator interpolator; const Image::BitmapData& destData; const Image::BitmapData& srcData; const int extraAlpha; const Graphics::ResamplingQuality quality; const int maxX, maxY; int currentY; DestPixelType* linePixels; HeapBlock scratchBuffer; size_t scratchSize = 2048; JUCE_DECLARE_NON_COPYABLE (TransformedImageFill) }; //============================================================================== template void renderImageTransformed (Iterator& iter, const Image::BitmapData& destData, const Image::BitmapData& srcData, int alpha, const AffineTransform& transform, Graphics::ResamplingQuality quality, bool tiledFill) { switch (destData.pixelFormat) { case Image::ARGB: switch (srcData.pixelFormat) { case Image::ARGB: if (tiledFill) { TransformedImageFill r (destData, srcData, transform, alpha, quality); iter.iterate (r); } else { TransformedImageFill r (destData, srcData, transform, alpha, quality); iter.iterate (r); } break; case Image::RGB: if (tiledFill) { TransformedImageFill r (destData, srcData, transform, alpha, quality); iter.iterate (r); } else { TransformedImageFill r (destData, srcData, transform, alpha, quality); iter.iterate (r); } break; case Image::SingleChannel: case Image::UnknownFormat: default: if (tiledFill) { TransformedImageFill r (destData, srcData, transform, alpha, quality); iter.iterate (r); } else { TransformedImageFill r (destData, srcData, transform, alpha, quality); iter.iterate (r); } break; } break; case Image::RGB: { switch (srcData.pixelFormat) { case Image::ARGB: if (tiledFill) { TransformedImageFill r (destData, srcData, transform, alpha, quality); iter.iterate (r); } else { TransformedImageFill r (destData, srcData, transform, alpha, quality); iter.iterate (r); } break; case Image::RGB: if (tiledFill) { TransformedImageFill r (destData, srcData, transform, alpha, quality); iter.iterate (r); } else { TransformedImageFill r (destData, srcData, transform, alpha, quality); iter.iterate (r); } break; case Image::SingleChannel: case Image::UnknownFormat: default: if (tiledFill) { TransformedImageFill r (destData, srcData, transform, alpha, quality); iter.iterate (r); } else { TransformedImageFill r (destData, srcData, transform, alpha, quality); iter.iterate (r); } break; } break; } case Image::SingleChannel: case Image::UnknownFormat: default: switch (srcData.pixelFormat) { case Image::ARGB: if (tiledFill) { TransformedImageFill r (destData, srcData, transform, alpha, quality); iter.iterate (r); } else { TransformedImageFill r (destData, srcData, transform, alpha, quality); iter.iterate (r); } break; case Image::RGB: if (tiledFill) { TransformedImageFill r (destData, srcData, transform, alpha, quality); iter.iterate (r); } else { TransformedImageFill r (destData, srcData, transform, alpha, quality); iter.iterate (r); } break; case Image::SingleChannel: case Image::UnknownFormat: default: if (tiledFill) { TransformedImageFill r (destData, srcData, transform, alpha, quality); iter.iterate (r); } else { TransformedImageFill r (destData, srcData, transform, alpha, quality); iter.iterate (r); } break; } break; } } template void renderImageUntransformed (Iterator& iter, const Image::BitmapData& destData, const Image::BitmapData& srcData, int alpha, int x, int y, bool tiledFill) { switch (destData.pixelFormat) { case Image::ARGB: switch (srcData.pixelFormat) { case Image::ARGB: if (tiledFill) { ImageFill r (destData, srcData, alpha, x, y); iter.iterate (r); } else { ImageFill r (destData, srcData, alpha, x, y); iter.iterate (r); } break; case Image::RGB: if (tiledFill) { ImageFill r (destData, srcData, alpha, x, y); iter.iterate (r); } else { ImageFill r (destData, srcData, alpha, x, y); iter.iterate (r); } break; case Image::SingleChannel: case Image::UnknownFormat: default: if (tiledFill) { ImageFill r (destData, srcData, alpha, x, y); iter.iterate (r); } else { ImageFill r (destData, srcData, alpha, x, y); iter.iterate (r); } break; } break; case Image::RGB: switch (srcData.pixelFormat) { case Image::ARGB: if (tiledFill) { ImageFill r (destData, srcData, alpha, x, y); iter.iterate (r); } else { ImageFill r (destData, srcData, alpha, x, y); iter.iterate (r); } break; case Image::RGB: if (tiledFill) { ImageFill r (destData, srcData, alpha, x, y); iter.iterate (r); } else { ImageFill r (destData, srcData, alpha, x, y); iter.iterate (r); } break; case Image::SingleChannel: case Image::UnknownFormat: default: if (tiledFill) { ImageFill r (destData, srcData, alpha, x, y); iter.iterate (r); } else { ImageFill r (destData, srcData, alpha, x, y); iter.iterate (r); } break; } break; case Image::SingleChannel: case Image::UnknownFormat: default: switch (srcData.pixelFormat) { case Image::ARGB: if (tiledFill) { ImageFill r (destData, srcData, alpha, x, y); iter.iterate (r); } else { ImageFill r (destData, srcData, alpha, x, y); iter.iterate (r); } break; case Image::RGB: if (tiledFill) { ImageFill r (destData, srcData, alpha, x, y); iter.iterate (r); } else { ImageFill r (destData, srcData, alpha, x, y); iter.iterate (r); } break; case Image::SingleChannel: case Image::UnknownFormat: default: if (tiledFill) { ImageFill r (destData, srcData, alpha, x, y); iter.iterate (r); } else { ImageFill r (destData, srcData, alpha, x, y); iter.iterate (r); } break; } break; } } template void renderSolidFill (Iterator& iter, const Image::BitmapData& destData, PixelARGB fillColour, bool replaceContents, DestPixelType*) { if (replaceContents) { EdgeTableFillers::SolidColour r (destData, fillColour); iter.iterate (r); } else { EdgeTableFillers::SolidColour r (destData, fillColour); iter.iterate (r); } } template void renderGradient (Iterator& iter, const Image::BitmapData& destData, const ColourGradient& g, const AffineTransform& transform, const PixelARGB* lookupTable, int numLookupEntries, bool isIdentity, DestPixelType*) { if (g.isRadial) { if (isIdentity) { EdgeTableFillers::Gradient renderer (destData, g, transform, lookupTable, numLookupEntries); iter.iterate (renderer); } else { EdgeTableFillers::Gradient renderer (destData, g, transform, lookupTable, numLookupEntries); iter.iterate (renderer); } } else { EdgeTableFillers::Gradient renderer (destData, g, transform, lookupTable, numLookupEntries); iter.iterate (renderer); } } } //============================================================================== template struct ClipRegions { struct Base : public SingleThreadedReferenceCountedObject { Base() = default; ~Base() override = default; using Ptr = ReferenceCountedObjectPtr; virtual Ptr clone() const = 0; virtual Ptr applyClipTo (const Ptr& target) const = 0; virtual Ptr clipToRectangle (Rectangle) = 0; virtual Ptr clipToRectangleList (const RectangleList&) = 0; virtual Ptr excludeClipRectangle (Rectangle) = 0; virtual Ptr clipToPath (const Path&, const AffineTransform&) = 0; virtual Ptr clipToEdgeTable (const EdgeTable&) = 0; virtual Ptr clipToImageAlpha (const Image&, const AffineTransform&, Graphics::ResamplingQuality) = 0; virtual void translate (Point delta) = 0; virtual bool clipRegionIntersects (Rectangle) const = 0; virtual Rectangle getClipBounds() const = 0; virtual void fillRectWithColour (SavedStateType&, Rectangle, PixelARGB colour, bool replaceContents) const = 0; virtual void fillRectWithColour (SavedStateType&, Rectangle, PixelARGB colour) const = 0; virtual void fillAllWithColour (SavedStateType&, PixelARGB colour, bool replaceContents) const = 0; virtual void fillAllWithGradient (SavedStateType&, ColourGradient&, const AffineTransform&, bool isIdentity) const = 0; virtual void renderImageTransformed (SavedStateType&, const Image&, int alpha, const AffineTransform&, Graphics::ResamplingQuality, bool tiledFill) const = 0; virtual void renderImageUntransformed (SavedStateType&, const Image&, int alpha, int x, int y, bool tiledFill) const = 0; }; //============================================================================== struct EdgeTableRegion : public Base { EdgeTableRegion (const EdgeTable& e) : edgeTable (e) {} EdgeTableRegion (Rectangle r) : edgeTable (r) {} EdgeTableRegion (Rectangle r) : edgeTable (r) {} EdgeTableRegion (const RectangleList& r) : edgeTable (r) {} EdgeTableRegion (const RectangleList& r) : edgeTable (r) {} EdgeTableRegion (Rectangle bounds, const Path& p, const AffineTransform& t) : edgeTable (bounds, p, t) {} EdgeTableRegion (const EdgeTableRegion& other) : Base(), edgeTable (other.edgeTable) {} EdgeTableRegion& operator= (const EdgeTableRegion&) = delete; using Ptr = typename Base::Ptr; Ptr clone() const override { return *new EdgeTableRegion (*this); } Ptr applyClipTo (const Ptr& target) const override { return target->clipToEdgeTable (edgeTable); } Ptr clipToRectangle (Rectangle r) override { edgeTable.clipToRectangle (r); return edgeTable.isEmpty() ? Ptr() : Ptr (*this); } Ptr clipToRectangleList (const RectangleList& r) override { RectangleList inverse (edgeTable.getMaximumBounds()); if (inverse.subtract (r)) for (auto& i : inverse) edgeTable.excludeRectangle (i); return edgeTable.isEmpty() ? Ptr() : Ptr (*this); } Ptr excludeClipRectangle (Rectangle r) override { edgeTable.excludeRectangle (r); return edgeTable.isEmpty() ? Ptr() : Ptr (*this); } Ptr clipToPath (const Path& p, const AffineTransform& transform) override { EdgeTable et (edgeTable.getMaximumBounds(), p, transform); edgeTable.clipToEdgeTable (et); return edgeTable.isEmpty() ? Ptr() : Ptr (*this); } Ptr clipToEdgeTable (const EdgeTable& et) override { edgeTable.clipToEdgeTable (et); return edgeTable.isEmpty() ? Ptr() : Ptr (*this); } Ptr clipToImageAlpha (const Image& image, const AffineTransform& transform, Graphics::ResamplingQuality quality) override { const Image::BitmapData srcData (image, Image::BitmapData::readOnly); if (transform.isOnlyTranslation()) { // If our translation doesn't involve any distortion, just use a simple blit.. auto tx = (int) (transform.getTranslationX() * 256.0f); auto ty = (int) (transform.getTranslationY() * 256.0f); if (quality == Graphics::lowResamplingQuality || ((tx | ty) & 224) == 0) { auto imageX = ((tx + 128) >> 8); auto imageY = ((ty + 128) >> 8); if (image.getFormat() == Image::ARGB) straightClipImage (srcData, imageX, imageY, (PixelARGB*) nullptr); else straightClipImage (srcData, imageX, imageY, (PixelAlpha*) nullptr); return edgeTable.isEmpty() ? Ptr() : Ptr (*this); } } if (transform.isSingularity()) return Ptr(); { Path p; p.addRectangle (0, 0, (float) srcData.width, (float) srcData.height); EdgeTable et2 (edgeTable.getMaximumBounds(), p, transform); edgeTable.clipToEdgeTable (et2); } if (! edgeTable.isEmpty()) { if (image.getFormat() == Image::ARGB) transformedClipImage (srcData, transform, quality, (PixelARGB*) nullptr); else transformedClipImage (srcData, transform, quality, (PixelAlpha*) nullptr); } return edgeTable.isEmpty() ? Ptr() : Ptr (*this); } void translate (Point delta) override { edgeTable.translate ((float) delta.x, delta.y); } bool clipRegionIntersects (Rectangle r) const override { return edgeTable.getMaximumBounds().intersects (r); } Rectangle getClipBounds() const override { return edgeTable.getMaximumBounds(); } void fillRectWithColour (SavedStateType& state, Rectangle area, PixelARGB colour, bool replaceContents) const override { auto totalClip = edgeTable.getMaximumBounds(); auto clipped = totalClip.getIntersection (area); if (! clipped.isEmpty()) { EdgeTableRegion et (clipped); et.edgeTable.clipToEdgeTable (edgeTable); state.fillWithSolidColour (et.edgeTable, colour, replaceContents); } } void fillRectWithColour (SavedStateType& state, Rectangle area, PixelARGB colour) const override { auto totalClip = edgeTable.getMaximumBounds().toFloat(); auto clipped = totalClip.getIntersection (area); if (! clipped.isEmpty()) { EdgeTableRegion et (clipped); et.edgeTable.clipToEdgeTable (edgeTable); state.fillWithSolidColour (et.edgeTable, colour, false); } } void fillAllWithColour (SavedStateType& state, PixelARGB colour, bool replaceContents) const override { state.fillWithSolidColour (edgeTable, colour, replaceContents); } void fillAllWithGradient (SavedStateType& state, ColourGradient& gradient, const AffineTransform& transform, bool isIdentity) const override { state.fillWithGradient (edgeTable, gradient, transform, isIdentity); } void renderImageTransformed (SavedStateType& state, const Image& src, int alpha, const AffineTransform& transform, Graphics::ResamplingQuality quality, bool tiledFill) const override { state.renderImageTransformed (edgeTable, src, alpha, transform, quality, tiledFill); } void renderImageUntransformed (SavedStateType& state, const Image& src, int alpha, int x, int y, bool tiledFill) const override { state.renderImageUntransformed (edgeTable, src, alpha, x, y, tiledFill); } EdgeTable edgeTable; private: template void transformedClipImage (const Image::BitmapData& srcData, const AffineTransform& transform, Graphics::ResamplingQuality quality, const SrcPixelType*) { EdgeTableFillers::TransformedImageFill renderer (srcData, srcData, transform, 255, quality); for (int y = 0; y < edgeTable.getMaximumBounds().getHeight(); ++y) renderer.clipEdgeTableLine (edgeTable, edgeTable.getMaximumBounds().getX(), y + edgeTable.getMaximumBounds().getY(), edgeTable.getMaximumBounds().getWidth()); } template void straightClipImage (const Image::BitmapData& srcData, int imageX, int imageY, const SrcPixelType*) { Rectangle r (imageX, imageY, srcData.width, srcData.height); edgeTable.clipToRectangle (r); EdgeTableFillers::ImageFill renderer (srcData, srcData, 255, imageX, imageY); for (int y = 0; y < r.getHeight(); ++y) renderer.clipEdgeTableLine (edgeTable, r.getX(), y + r.getY(), r.getWidth()); } }; //============================================================================== class RectangleListRegion : public Base { public: RectangleListRegion (Rectangle r) : clip (r) {} RectangleListRegion (const RectangleList& r) : clip (r) {} RectangleListRegion (const RectangleListRegion& other) : Base(), clip (other.clip) {} using Ptr = typename Base::Ptr; Ptr clone() const override { return *new RectangleListRegion (*this); } Ptr applyClipTo (const Ptr& target) const override { return target->clipToRectangleList (clip); } Ptr clipToRectangle (Rectangle r) override { clip.clipTo (r); return clip.isEmpty() ? Ptr() : Ptr (*this); } Ptr clipToRectangleList (const RectangleList& r) override { clip.clipTo (r); return clip.isEmpty() ? Ptr() : Ptr (*this); } Ptr excludeClipRectangle (Rectangle r) override { clip.subtract (r); return clip.isEmpty() ? Ptr() : Ptr (*this); } Ptr clipToPath (const Path& p, const AffineTransform& transform) override { return toEdgeTable()->clipToPath (p, transform); } Ptr clipToEdgeTable (const EdgeTable& et) override { return toEdgeTable()->clipToEdgeTable (et); } Ptr clipToImageAlpha (const Image& image, const AffineTransform& transform, Graphics::ResamplingQuality quality) override { return toEdgeTable()->clipToImageAlpha (image, transform, quality); } void translate (Point delta) override { clip.offsetAll (delta); } bool clipRegionIntersects (Rectangle r) const override { return clip.intersects (r); } Rectangle getClipBounds() const override { return clip.getBounds(); } void fillRectWithColour (SavedStateType& state, Rectangle area, PixelARGB colour, bool replaceContents) const override { SubRectangleIterator iter (clip, area); state.fillWithSolidColour (iter, colour, replaceContents); } void fillRectWithColour (SavedStateType& state, Rectangle area, PixelARGB colour) const override { SubRectangleIteratorFloat iter (clip, area); state.fillWithSolidColour (iter, colour, false); } void fillAllWithColour (SavedStateType& state, PixelARGB colour, bool replaceContents) const override { state.fillWithSolidColour (*this, colour, replaceContents); } void fillAllWithGradient (SavedStateType& state, ColourGradient& gradient, const AffineTransform& transform, bool isIdentity) const override { state.fillWithGradient (*this, gradient, transform, isIdentity); } void renderImageTransformed (SavedStateType& state, const Image& src, int alpha, const AffineTransform& transform, Graphics::ResamplingQuality quality, bool tiledFill) const override { state.renderImageTransformed (*this, src, alpha, transform, quality, tiledFill); } void renderImageUntransformed (SavedStateType& state, const Image& src, int alpha, int x, int y, bool tiledFill) const override { state.renderImageUntransformed (*this, src, alpha, x, y, tiledFill); } RectangleList clip; //============================================================================== template void iterate (Renderer& r) const noexcept { for (auto& i : clip) { auto x = i.getX(); auto w = i.getWidth(); jassert (w > 0); auto bottom = i.getBottom(); for (int y = i.getY(); y < bottom; ++y) { r.setEdgeTableYPos (y); r.handleEdgeTableLineFull (x, w); } } } private: //============================================================================== class SubRectangleIterator { public: SubRectangleIterator (const RectangleList& clipList, Rectangle clipBounds) : clip (clipList), area (clipBounds) {} template void iterate (Renderer& r) const noexcept { for (auto& i : clip) { auto rect = i.getIntersection (area); if (! rect.isEmpty()) r.handleEdgeTableRectangleFull (rect.getX(), rect.getY(), rect.getWidth(), rect.getHeight()); } } private: const RectangleList& clip; const Rectangle area; JUCE_DECLARE_NON_COPYABLE (SubRectangleIterator) }; //============================================================================== class SubRectangleIteratorFloat { public: SubRectangleIteratorFloat (const RectangleList& clipList, Rectangle clipBounds) noexcept : clip (clipList), area (clipBounds) { } template void iterate (Renderer& r) const noexcept { const RenderingHelpers::FloatRectangleRasterisingInfo f (area); for (auto& i : clip) { auto clipLeft = i.getX(); auto clipRight = i.getRight(); auto clipTop = i.getY(); auto clipBottom = i.getBottom(); if (f.totalBottom > clipTop && f.totalTop < clipBottom && f.totalRight > clipLeft && f.totalLeft < clipRight) { if (f.isOnePixelWide()) { if (f.topAlpha != 0 && f.totalTop >= clipTop) { r.setEdgeTableYPos (f.totalTop); r.handleEdgeTablePixel (f.left, f.topAlpha); } auto y1 = jmax (clipTop, f.top); auto y2 = jmin (f.bottom, clipBottom); auto h = y2 - y1; if (h > 0) r.handleEdgeTableRectangleFull (f.left, y1, 1, h); if (f.bottomAlpha != 0 && f.bottom < clipBottom) { r.setEdgeTableYPos (f.bottom); r.handleEdgeTablePixel (f.left, f.bottomAlpha); } } else { auto clippedLeft = jmax (f.left, clipLeft); auto clippedWidth = jmin (f.right, clipRight) - clippedLeft; bool doLeftAlpha = f.leftAlpha != 0 && f.totalLeft >= clipLeft; bool doRightAlpha = f.rightAlpha != 0 && f.right < clipRight; if (f.topAlpha != 0 && f.totalTop >= clipTop) { r.setEdgeTableYPos (f.totalTop); if (doLeftAlpha) r.handleEdgeTablePixel (f.totalLeft, f.getTopLeftCornerAlpha()); if (clippedWidth > 0) r.handleEdgeTableLine (clippedLeft, clippedWidth, f.topAlpha); if (doRightAlpha) r.handleEdgeTablePixel (f.right, f.getTopRightCornerAlpha()); } auto y1 = jmax (clipTop, f.top); auto y2 = jmin (f.bottom, clipBottom); auto h = y2 - y1; if (h > 0) { if (h == 1) { r.setEdgeTableYPos (y1); if (doLeftAlpha) r.handleEdgeTablePixel (f.totalLeft, f.leftAlpha); if (clippedWidth > 0) r.handleEdgeTableLineFull (clippedLeft, clippedWidth); if (doRightAlpha) r.handleEdgeTablePixel (f.right, f.rightAlpha); } else { if (doLeftAlpha) r.handleEdgeTableRectangle (f.totalLeft, y1, 1, h, f.leftAlpha); if (clippedWidth > 0) r.handleEdgeTableRectangleFull (clippedLeft, y1, clippedWidth, h); if (doRightAlpha) r.handleEdgeTableRectangle (f.right, y1, 1, h, f.rightAlpha); } } if (f.bottomAlpha != 0 && f.bottom < clipBottom) { r.setEdgeTableYPos (f.bottom); if (doLeftAlpha) r.handleEdgeTablePixel (f.totalLeft, f.getBottomLeftCornerAlpha()); if (clippedWidth > 0) r.handleEdgeTableLine (clippedLeft, clippedWidth, f.bottomAlpha); if (doRightAlpha) r.handleEdgeTablePixel (f.right, f.getBottomRightCornerAlpha()); } } } } } private: const RectangleList& clip; Rectangle area; JUCE_DECLARE_NON_COPYABLE (SubRectangleIteratorFloat) }; Ptr toEdgeTable() const { return *new EdgeTableRegion (clip); } RectangleListRegion& operator= (const RectangleListRegion&) = delete; }; }; //============================================================================== template class SavedStateBase { public: using BaseRegionType = typename ClipRegions::Base; using EdgeTableRegionType = typename ClipRegions::EdgeTableRegion; using RectangleListRegionType = typename ClipRegions::RectangleListRegion; SavedStateBase (Rectangle initialClip) : clip (new RectangleListRegionType (initialClip)), interpolationQuality (Graphics::mediumResamplingQuality), transparencyLayerAlpha (1.0f) { } SavedStateBase (const RectangleList& clipList, Point origin) : clip (new RectangleListRegionType (clipList)), transform (origin), interpolationQuality (Graphics::mediumResamplingQuality), transparencyLayerAlpha (1.0f) { } SavedStateBase (const SavedStateBase& other) : clip (other.clip), transform (other.transform), fillType (other.fillType), interpolationQuality (other.interpolationQuality), transparencyLayerAlpha (other.transparencyLayerAlpha) { } SavedStateType& getThis() noexcept { return *static_cast (this); } bool clipToRectangle (Rectangle r) { if (clip != nullptr) { if (transform.isOnlyTranslated) { cloneClipIfMultiplyReferenced(); clip = clip->clipToRectangle (transform.translated (r)); } else if (! transform.isRotated) { cloneClipIfMultiplyReferenced(); clip = clip->clipToRectangle (transform.transformed (r)); } else { Path p; p.addRectangle (r); clipToPath (p, {}); } } return clip != nullptr; } bool clipToRectangleList (const RectangleList& r) { if (clip != nullptr) { if (transform.isOnlyTranslated) { cloneClipIfMultiplyReferenced(); if (transform.isIdentity()) { clip = clip->clipToRectangleList (r); } else { RectangleList offsetList (r); offsetList.offsetAll (transform.offset); clip = clip->clipToRectangleList (offsetList); } } else if (! transform.isRotated) { cloneClipIfMultiplyReferenced(); RectangleList scaledList; for (auto& i : r) scaledList.add (transform.transformed (i)); clip = clip->clipToRectangleList (scaledList); } else { clipToPath (r.toPath(), {}); } } return clip != nullptr; } static Rectangle getLargestIntegerWithin (Rectangle r) { auto x1 = (int) std::ceil (r.getX()); auto y1 = (int) std::ceil (r.getY()); auto x2 = (int) std::floor (r.getRight()); auto y2 = (int) std::floor (r.getBottom()); return { x1, y1, x2 - x1, y2 - y1 }; } bool excludeClipRectangle (Rectangle r) { if (clip != nullptr) { cloneClipIfMultiplyReferenced(); if (transform.isOnlyTranslated) { clip = clip->excludeClipRectangle (getLargestIntegerWithin (transform.translated (r.toFloat()))); } else if (! transform.isRotated) { clip = clip->excludeClipRectangle (getLargestIntegerWithin (transform.transformed (r.toFloat()))); } else { Path p; p.addRectangle (r.toFloat()); p.applyTransform (transform.complexTransform); p.addRectangle (clip->getClipBounds().toFloat()); p.setUsingNonZeroWinding (false); clip = clip->clipToPath (p, {}); } } return clip != nullptr; } void clipToPath (const Path& p, const AffineTransform& t) { if (clip != nullptr) { cloneClipIfMultiplyReferenced(); clip = clip->clipToPath (p, transform.getTransformWith (t)); } } void clipToImageAlpha (const Image& sourceImage, const AffineTransform& t) { if (clip != nullptr) { if (sourceImage.hasAlphaChannel()) { cloneClipIfMultiplyReferenced(); clip = clip->clipToImageAlpha (sourceImage, transform.getTransformWith (t), interpolationQuality); } else { Path p; p.addRectangle (sourceImage.getBounds()); clipToPath (p, t); } } } bool clipRegionIntersects (Rectangle r) const { if (clip != nullptr) { if (transform.isOnlyTranslated) return clip->clipRegionIntersects (transform.translated (r)); return getClipBounds().intersects (r); } return false; } Rectangle getClipBounds() const { return clip != nullptr ? transform.deviceSpaceToUserSpace (clip->getClipBounds()) : Rectangle(); } void setFillType (const FillType& newFill) { fillType = newFill; } void fillTargetRect (Rectangle r, bool replaceContents) { if (fillType.isColour()) { clip->fillRectWithColour (getThis(), r, fillType.colour.getPixelARGB(), replaceContents); } else { auto clipped = clip->getClipBounds().getIntersection (r); if (! clipped.isEmpty()) fillShape (*new RectangleListRegionType (clipped), false); } } void fillTargetRect (Rectangle r) { if (fillType.isColour()) { clip->fillRectWithColour (getThis(), r, fillType.colour.getPixelARGB()); } else { auto clipped = clip->getClipBounds().toFloat().getIntersection (r); if (! clipped.isEmpty()) fillShape (*new EdgeTableRegionType (clipped), false); } } template void fillRectAsPath (Rectangle r) { Path p; p.addRectangle (r); fillPath (p, {}); } void fillRect (Rectangle r, bool replaceContents) { if (clip != nullptr) { if (transform.isOnlyTranslated) { fillTargetRect (transform.translated (r), replaceContents); } else if (! transform.isRotated) { fillTargetRect (transform.transformed (r), replaceContents); } else { jassert (! replaceContents); // not implemented.. fillRectAsPath (r); } } } void fillRect (Rectangle r) { if (clip != nullptr) { if (transform.isOnlyTranslated) fillTargetRect (transform.translated (r)); else if (! transform.isRotated) fillTargetRect (transform.transformed (r)); else fillRectAsPath (r); } } void fillRectList (const RectangleList& list) { if (clip != nullptr) { if (list.getNumRectangles() == 1) return fillRect (*list.begin()); if (transform.isIdentity()) { fillShape (*new EdgeTableRegionType (list), false); } else if (! transform.isRotated) { RectangleList transformed (list); if (transform.isOnlyTranslated) transformed.offsetAll (transform.offset.toFloat()); else transformed.transformAll (transform.getTransform()); fillShape (*new EdgeTableRegionType (transformed), false); } else { fillPath (list.toPath(), {}); } } } void fillPath (const Path& path, const AffineTransform& t) { if (clip != nullptr) { auto trans = transform.getTransformWith (t); auto clipRect = clip->getClipBounds(); if (path.getBoundsTransformed (trans).getSmallestIntegerContainer().intersects (clipRect)) fillShape (*new EdgeTableRegionType (clipRect, path, trans), false); } } void fillEdgeTable (const EdgeTable& edgeTable, float x, int y) { if (clip != nullptr) { auto* edgeTableClip = new EdgeTableRegionType (edgeTable); edgeTableClip->edgeTable.translate (x, y); if (fillType.isColour()) { auto brightness = fillType.colour.getBrightness() - 0.5f; if (brightness > 0.0f) edgeTableClip->edgeTable.multiplyLevels (1.0f + 1.6f * brightness); } fillShape (*edgeTableClip, false); } } void drawLine (Line line) { Path p; p.addLineSegment (line, 1.0f); fillPath (p, {}); } void drawImage (const Image& sourceImage, const AffineTransform& trans) { if (clip != nullptr && ! fillType.colour.isTransparent()) renderImage (sourceImage, trans, {}); } static bool isOnlyTranslationAllowingError (const AffineTransform& t, float tolerance) noexcept { return std::abs (t.mat01) < tolerance && std::abs (t.mat10) < tolerance && std::abs (t.mat00 - 1.0f) < tolerance && std::abs (t.mat11 - 1.0f) < tolerance; } void renderImage (const Image& sourceImage, const AffineTransform& trans, const BaseRegionType* tiledFillClipRegion) { auto t = transform.getTransformWith (trans); auto alpha = fillType.colour.getAlpha(); if (isOnlyTranslationAllowingError (t, 0.002f)) { // If our translation doesn't involve any distortion, just use a simple blit.. auto tx = (int) (t.getTranslationX() * 256.0f); auto ty = (int) (t.getTranslationY() * 256.0f); if (interpolationQuality == Graphics::lowResamplingQuality || ((tx | ty) & 224) == 0) { tx = ((tx + 128) >> 8); ty = ((ty + 128) >> 8); if (tiledFillClipRegion != nullptr) { tiledFillClipRegion->renderImageUntransformed (getThis(), sourceImage, alpha, tx, ty, true); } else { Rectangle area (tx, ty, sourceImage.getWidth(), sourceImage.getHeight()); area = area.getIntersection (getThis().getMaximumBounds()); if (! area.isEmpty()) if (auto c = clip->applyClipTo (*new EdgeTableRegionType (area))) c->renderImageUntransformed (getThis(), sourceImage, alpha, tx, ty, false); } return; } } if (! t.isSingularity()) { if (tiledFillClipRegion != nullptr) { tiledFillClipRegion->renderImageTransformed (getThis(), sourceImage, alpha, t, interpolationQuality, true); } else { Path p; p.addRectangle (sourceImage.getBounds()); if (auto c = clip->clone()->clipToPath (p, t)) c->renderImageTransformed (getThis(), sourceImage, alpha, t, interpolationQuality, false); } } } void fillShape (typename BaseRegionType::Ptr shapeToFill, bool replaceContents) { jassert (clip != nullptr); shapeToFill = clip->applyClipTo (shapeToFill); if (shapeToFill != nullptr) { if (fillType.isGradient()) { jassert (! replaceContents); // that option is just for solid colours auto g2 = *(fillType.gradient); g2.multiplyOpacity (fillType.getOpacity()); auto t = transform.getTransformWith (fillType.transform).translated (-0.5f, -0.5f); bool isIdentity = t.isOnlyTranslation(); if (isIdentity) { // If our translation doesn't involve any distortion, we can speed it up.. g2.point1.applyTransform (t); g2.point2.applyTransform (t); t = {}; } shapeToFill->fillAllWithGradient (getThis(), g2, t, isIdentity); } else if (fillType.isTiledImage()) { renderImage (fillType.image, fillType.transform, shapeToFill.get()); } else { shapeToFill->fillAllWithColour (getThis(), fillType.colour.getPixelARGB(), replaceContents); } } } void cloneClipIfMultiplyReferenced() { if (clip->getReferenceCount() > 1) clip = clip->clone(); } typename BaseRegionType::Ptr clip; RenderingHelpers::TranslationOrTransform transform; FillType fillType; Graphics::ResamplingQuality interpolationQuality; float transparencyLayerAlpha; }; //============================================================================== class SoftwareRendererSavedState : public SavedStateBase { using BaseClass = SavedStateBase; public: SoftwareRendererSavedState (const Image& im, Rectangle clipBounds) : BaseClass (clipBounds), image (im) { } SoftwareRendererSavedState (const Image& im, const RectangleList& clipList, Point origin) : BaseClass (clipList, origin), image (im) { } SoftwareRendererSavedState (const SoftwareRendererSavedState& other) = default; SoftwareRendererSavedState* beginTransparencyLayer (float opacity) { auto* s = new SoftwareRendererSavedState (*this); if (clip != nullptr) { auto layerBounds = clip->getClipBounds(); s->image = Image (Image::ARGB, layerBounds.getWidth(), layerBounds.getHeight(), true); s->transparencyLayerAlpha = opacity; s->transform.moveOriginInDeviceSpace (-layerBounds.getPosition()); s->cloneClipIfMultiplyReferenced(); s->clip->translate (-layerBounds.getPosition()); } return s; } void endTransparencyLayer (SoftwareRendererSavedState& finishedLayerState) { if (clip != nullptr) { auto layerBounds = clip->getClipBounds(); auto g = image.createLowLevelContext(); g->setOpacity (finishedLayerState.transparencyLayerAlpha); g->drawImage (finishedLayerState.image, AffineTransform::translation (layerBounds.getPosition())); } } using GlyphCacheType = GlyphCache, SoftwareRendererSavedState>; static void clearGlyphCache() { GlyphCacheType::getInstance().reset(); } //============================================================================== void drawGlyph (int glyphNumber, const AffineTransform& trans) { if (clip != nullptr) { if (trans.isOnlyTranslation() && ! transform.isRotated) { auto& cache = GlyphCacheType::getInstance(); Point pos (trans.getTranslationX(), trans.getTranslationY()); if (transform.isOnlyTranslated) { cache.drawGlyph (*this, font, glyphNumber, pos + transform.offset.toFloat()); } else { pos = transform.transformed (pos); Font f (font); f.setHeight (font.getHeight() * transform.complexTransform.mat11); auto xScale = transform.complexTransform.mat00 / transform.complexTransform.mat11; if (std::abs (xScale - 1.0f) > 0.01f) f.setHorizontalScale (xScale); cache.drawGlyph (*this, f, glyphNumber, pos); } } else { auto fontHeight = font.getHeight(); auto t = transform.getTransformWith (AffineTransform::scale (fontHeight * font.getHorizontalScale(), fontHeight) .followedBy (trans)); std::unique_ptr et (font.getTypeface()->getEdgeTableForGlyph (glyphNumber, t, fontHeight)); if (et != nullptr) fillShape (*new EdgeTableRegionType (*et), false); } } } Rectangle getMaximumBounds() const { return image.getBounds(); } //============================================================================== template void renderImageTransformed (IteratorType& iter, const Image& src, int alpha, const AffineTransform& trans, Graphics::ResamplingQuality quality, bool tiledFill) const { Image::BitmapData destData (image, Image::BitmapData::readWrite); const Image::BitmapData srcData (src, Image::BitmapData::readOnly); EdgeTableFillers::renderImageTransformed (iter, destData, srcData, alpha, trans, quality, tiledFill); } template void renderImageUntransformed (IteratorType& iter, const Image& src, int alpha, int x, int y, bool tiledFill) const { Image::BitmapData destData (image, Image::BitmapData::readWrite); const Image::BitmapData srcData (src, Image::BitmapData::readOnly); EdgeTableFillers::renderImageUntransformed (iter, destData, srcData, alpha, x, y, tiledFill); } template void fillWithSolidColour (IteratorType& iter, PixelARGB colour, bool replaceContents) const { Image::BitmapData destData (image, Image::BitmapData::readWrite); switch (destData.pixelFormat) { case Image::ARGB: EdgeTableFillers::renderSolidFill (iter, destData, colour, replaceContents, (PixelARGB*) nullptr); break; case Image::RGB: EdgeTableFillers::renderSolidFill (iter, destData, colour, replaceContents, (PixelRGB*) nullptr); break; case Image::SingleChannel: case Image::UnknownFormat: default: EdgeTableFillers::renderSolidFill (iter, destData, colour, replaceContents, (PixelAlpha*) nullptr); break; } } template void fillWithGradient (IteratorType& iter, ColourGradient& gradient, const AffineTransform& trans, bool isIdentity) const { HeapBlock lookupTable; auto numLookupEntries = gradient.createLookupTable (trans, lookupTable); jassert (numLookupEntries > 0); Image::BitmapData destData (image, Image::BitmapData::readWrite); switch (destData.pixelFormat) { case Image::ARGB: EdgeTableFillers::renderGradient (iter, destData, gradient, trans, lookupTable, numLookupEntries, isIdentity, (PixelARGB*) nullptr); break; case Image::RGB: EdgeTableFillers::renderGradient (iter, destData, gradient, trans, lookupTable, numLookupEntries, isIdentity, (PixelRGB*) nullptr); break; case Image::SingleChannel: case Image::UnknownFormat: default: EdgeTableFillers::renderGradient (iter, destData, gradient, trans, lookupTable, numLookupEntries, isIdentity, (PixelAlpha*) nullptr); break; } } //============================================================================== Image image; Font font; private: SoftwareRendererSavedState& operator= (const SoftwareRendererSavedState&) = delete; }; //============================================================================== template class SavedStateStack { public: SavedStateStack (StateObjectType* initialState) noexcept : currentState (initialState) {} SavedStateStack() = default; void initialise (StateObjectType* state) { currentState.reset (state); } inline StateObjectType* operator->() const noexcept { return currentState.get(); } inline StateObjectType& operator*() const noexcept { return *currentState; } void save() { stack.add (new StateObjectType (*currentState)); } void restore() { if (auto* top = stack.getLast()) { currentState.reset (top); stack.removeLast (1, false); } else { jassertfalse; // trying to pop with an empty stack! } } void beginTransparencyLayer (float opacity) { save(); currentState.reset (currentState->beginTransparencyLayer (opacity)); } void endTransparencyLayer() { std::unique_ptr finishedTransparencyLayer (currentState.release()); restore(); currentState->endTransparencyLayer (*finishedTransparencyLayer); } private: std::unique_ptr currentState; OwnedArray stack; JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (SavedStateStack) }; //============================================================================== template class StackBasedLowLevelGraphicsContext : public LowLevelGraphicsContext { public: bool isVectorDevice() const override { return false; } void setOrigin (Point o) override { stack->transform.setOrigin (o); } void addTransform (const AffineTransform& t) override { stack->transform.addTransform (t); } float getPhysicalPixelScaleFactor() override { return stack->transform.getPhysicalPixelScaleFactor(); } Rectangle getClipBounds() const override { return stack->getClipBounds(); } bool isClipEmpty() const override { return stack->clip == nullptr; } bool clipRegionIntersects (const Rectangle& r) override { return stack->clipRegionIntersects (r); } bool clipToRectangle (const Rectangle& r) override { return stack->clipToRectangle (r); } bool clipToRectangleList (const RectangleList& r) override { return stack->clipToRectangleList (r); } void excludeClipRectangle (const Rectangle& r) override { stack->excludeClipRectangle (r); } void clipToPath (const Path& path, const AffineTransform& t) override { stack->clipToPath (path, t); } void clipToImageAlpha (const Image& im, const AffineTransform& t) override { stack->clipToImageAlpha (im, t); } void saveState() override { stack.save(); } void restoreState() override { stack.restore(); } void beginTransparencyLayer (float opacity) override { stack.beginTransparencyLayer (opacity); } void endTransparencyLayer() override { stack.endTransparencyLayer(); } void setFill (const FillType& fillType) override { stack->setFillType (fillType); } void setOpacity (float newOpacity) override { stack->fillType.setOpacity (newOpacity); } void setInterpolationQuality (Graphics::ResamplingQuality quality) override { stack->interpolationQuality = quality; } void fillRect (const Rectangle& r, bool replace) override { stack->fillRect (r, replace); } void fillRect (const Rectangle& r) override { stack->fillRect (r); } void fillRectList (const RectangleList& list) override { stack->fillRectList (list); } void fillPath (const Path& path, const AffineTransform& t) override { stack->fillPath (path, t); } void drawImage (const Image& im, const AffineTransform& t) override { stack->drawImage (im, t); } void drawGlyph (int glyphNumber, const AffineTransform& t) override { stack->drawGlyph (glyphNumber, t); } void drawLine (const Line& line) override { stack->drawLine (line); } void setFont (const Font& newFont) override { stack->font = newFont; } const Font& getFont() override { return stack->font; } protected: StackBasedLowLevelGraphicsContext (SavedStateType* initialState) : stack (initialState) {} StackBasedLowLevelGraphicsContext() = default; RenderingHelpers::SavedStateStack stack; }; } JUCE_END_IGNORE_WARNINGS_MSVC } // namespace juce