From 6e743c8f12076503871b0faf308b0b0390989e64 Mon Sep 17 00:00:00 2001 From: jules Date: Thu, 24 May 2012 18:50:26 +0100 Subject: [PATCH] Refactored the software renderer code, moving most of the guts into the juce_RenderingHelpers.h header file to make it possible for people to use it in their own code. --- .../juce_LowLevelGraphicsSoftwareRenderer.cpp | 2127 +---------------- .../juce_LowLevelGraphicsSoftwareRenderer.h | 17 +- .../native/juce_RenderingHelpers.h | 2123 +++++++++++++++- 3 files changed, 2094 insertions(+), 2173 deletions(-) diff --git a/modules/juce_graphics/contexts/juce_LowLevelGraphicsSoftwareRenderer.cpp b/modules/juce_graphics/contexts/juce_LowLevelGraphicsSoftwareRenderer.cpp index f540222fb2..973cd24929 100644 --- a/modules/juce_graphics/contexts/juce_LowLevelGraphicsSoftwareRenderer.cpp +++ b/modules/juce_graphics/contexts/juce_LowLevelGraphicsSoftwareRenderer.cpp @@ -23,2104 +23,33 @@ ============================================================================== */ -#if JUCE_MSVC - #pragma warning (push) - #pragma warning (disable: 4127) // "expression is constant" warning - #if JUCE_DEBUG - #pragma optimize ("t", on) // optimise just this file, to avoid sluggish graphics when debugging - #pragma warning (disable: 4714) // warning about forcedinline methods not being inlined - #endif -#endif - -namespace SoftwareRendererClasses -{ - -//============================================================================== -template -class SolidColourEdgeTableRenderer -{ -public: - SolidColourEdgeTableRenderer (const Image::BitmapData& data_, const PixelARGB& colour) - : data (data_), - sourceColour (colour) - { - if (sizeof (PixelType) == 3) - { - areRGBComponentsEqual = sourceColour.getRed() == sourceColour.getGreen() - && sourceColour.getGreen() == sourceColour.getBlue(); - filler[0].set (sourceColour); - filler[1].set (sourceColour); - filler[2].set (sourceColour); - filler[3].set (sourceColour); - } - } - - forcedinline void setEdgeTableYPos (const int y) noexcept - { - linePixels = (PixelType*) data.getLinePointer (y); - } - - forcedinline void handleEdgeTablePixel (const int x, const int alphaLevel) const noexcept - { - if (replaceExisting) - linePixels[x].set (sourceColour); - else - linePixels[x].blend (sourceColour, (uint32) alphaLevel); - } - - forcedinline void handleEdgeTablePixelFull (const int x) const noexcept - { - if (replaceExisting) - linePixels[x].set (sourceColour); - else - linePixels[x].blend (sourceColour); - } - - forcedinline void handleEdgeTableLine (const int x, const int width, const int alphaLevel) const noexcept - { - PixelARGB p (sourceColour); - p.multiplyAlpha (alphaLevel); - - PixelType* dest = linePixels + x; - - if (replaceExisting || p.getAlpha() >= 0xff) - replaceLine (dest, p, width); - else - blendLine (dest, p, width); - } - - forcedinline void handleEdgeTableLineFull (const int x, const int width) const noexcept - { - PixelType* dest = linePixels + x; - - if (replaceExisting || sourceColour.getAlpha() >= 0xff) - replaceLine (dest, sourceColour, width); - else - blendLine (dest, sourceColour, width); - } - -private: - const Image::BitmapData& data; - PixelType* linePixels; - PixelARGB sourceColour; - PixelRGB filler [4]; - bool areRGBComponentsEqual; - - inline void blendLine (PixelType* dest, const PixelARGB& colour, int width) const noexcept - { - do - { - dest->blend (colour); - ++dest; - } while (--width > 0); - } - - forcedinline void replaceLine (PixelRGB* dest, const PixelARGB& colour, int width) const noexcept - { - if (areRGBComponentsEqual) // if all the component values are the same, we can cheat.. - { - memset (dest, colour.getRed(), (size_t) width * 3); - } - else - { - if (width >> 5) - { - const int* const intFiller = reinterpret_cast (filler); - - while (width > 8 && (((pointer_sized_int) dest) & 7) != 0) - { - dest->set (colour); - ++dest; - --width; - } - - while (width > 4) - { - int* d = reinterpret_cast (dest); - *d++ = intFiller[0]; - *d++ = intFiller[1]; - *d++ = intFiller[2]; - dest = reinterpret_cast (d); - width -= 4; - } - } - - while (--width >= 0) - { - dest->set (colour); - ++dest; - } - } - } - - forcedinline void replaceLine (PixelAlpha* const dest, const PixelARGB& colour, int const width) const noexcept - { - memset (dest, colour.getAlpha(), (size_t) width); - } - - forcedinline void replaceLine (PixelARGB* dest, const PixelARGB& colour, int width) const noexcept - { - do - { - dest->set (colour); - ++dest; - - } while (--width > 0); - } - - JUCE_DECLARE_NON_COPYABLE (SolidColourEdgeTableRenderer); -}; - -//============================================================================== -class LinearGradientPixelGenerator -{ -public: - LinearGradientPixelGenerator (const ColourGradient& gradient, const AffineTransform& transform, - const PixelARGB* const lookupTable_, const int numEntries_) - : lookupTable (lookupTable_), - numEntries (numEntries_) - { - jassert (numEntries_ >= 0); - Point p1 (gradient.point1); - Point p2 (gradient.point2); - - if (! transform.isIdentity()) - { - const Line l (p2, p1); - Point p3 = l.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 * scale); - } - else if (horizontal) - { - scale = roundToInt ((numEntries << (int) numScaleBits) / (double) (p2.x - p1.x)); - start = roundToInt (p1.x * 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 (const 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 (const int x) const noexcept - { - return vertical ? linePix - : lookupTable [jlimit (0, numEntries, (x * scale - start) >> (int) numScaleBits)]; - } - -private: - 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 (LinearGradientPixelGenerator); -}; - -//============================================================================== -class RadialGradientPixelGenerator -{ -public: - RadialGradientPixelGenerator (const ColourGradient& gradient, const AffineTransform&, - const PixelARGB* const lookupTable_, const int numEntries_) - : lookupTable (lookupTable_), - numEntries (numEntries_), - gx1 (gradient.point1.x), - gy1 (gradient.point1.y) - { - jassert (numEntries_ >= 0); - const Point 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 (const int y) noexcept - { - dy = y - gy1; - dy *= dy; - } - - inline PixelARGB getPixel (const int px) const noexcept - { - double x = px - gx1; - x *= x; - x += dy; - - return lookupTable [x >= maxDist ? numEntries : roundToInt (std::sqrt (x) * invScale)]; - } - -protected: - const PixelARGB* const lookupTable; - const int numEntries; - const double gx1, gy1; - double maxDist, invScale, dy; - - JUCE_DECLARE_NON_COPYABLE (RadialGradientPixelGenerator); -}; - -//============================================================================== -class TransformedRadialGradientPixelGenerator : public RadialGradientPixelGenerator -{ -public: - TransformedRadialGradientPixelGenerator (const ColourGradient& gradient, const AffineTransform& transform, - const PixelARGB* const lookupTable_, const int numEntries_) - : RadialGradientPixelGenerator (gradient, transform, lookupTable_, numEntries_), - inverseTransform (transform.inverted()) - { - tM10 = inverseTransform.mat10; - tM00 = inverseTransform.mat00; - } - - forcedinline void setY (const int y) noexcept - { - lineYM01 = inverseTransform.mat01 * y + inverseTransform.mat02 - gx1; - lineYM11 = inverseTransform.mat11 * y + inverseTransform.mat12 - gy1; - } - - inline PixelARGB getPixel (const int px) const noexcept - { - double x = px; - const double y = tM10 * x + lineYM11; - x = tM00 * x + lineYM01; - x *= x; - x += y * y; - - if (x >= maxDist) - return lookupTable [numEntries]; - else - return lookupTable [jmin (numEntries, roundToInt (std::sqrt (x) * invScale))]; - } - -private: - double tM10, tM00, lineYM01, lineYM11; - const AffineTransform inverseTransform; - - JUCE_DECLARE_NON_COPYABLE (TransformedRadialGradientPixelGenerator); -}; - -//============================================================================== -template -class GradientEdgeTableRenderer : public GradientType -{ -public: - GradientEdgeTableRenderer (const Image::BitmapData& destData_, const ColourGradient& gradient, const AffineTransform& transform, - const PixelARGB* const lookupTable_, const int numEntries_) - : GradientType (gradient, transform, lookupTable_, numEntries_ - 1), - destData (destData_) - { - } - - forcedinline void setEdgeTableYPos (const int y) noexcept - { - linePixels = (PixelType*) destData.getLinePointer (y); - GradientType::setY (y); - } - - forcedinline void handleEdgeTablePixel (const int x, const int alphaLevel) const noexcept - { - linePixels[x].blend (GradientType::getPixel (x), (uint32) alphaLevel); - } - - forcedinline void handleEdgeTablePixelFull (const int x) const noexcept - { - linePixels[x].blend (GradientType::getPixel (x)); - } - - void handleEdgeTableLine (int x, int width, const int alphaLevel) const noexcept - { - PixelType* dest = linePixels + x; - - if (alphaLevel < 0xff) - { - do - { - (dest++)->blend (GradientType::getPixel (x++), (uint32) alphaLevel); - } while (--width > 0); - } - else - { - do - { - (dest++)->blend (GradientType::getPixel (x++)); - } while (--width > 0); - } - } - - void handleEdgeTableLineFull (int x, int width) const noexcept - { - PixelType* dest = linePixels + x; - - do - { - (dest++)->blend (GradientType::getPixel (x++)); - } while (--width > 0); - } - -private: - const Image::BitmapData& destData; - PixelType* linePixels; - - JUCE_DECLARE_NON_COPYABLE (GradientEdgeTableRenderer); -}; - -//============================================================================== -template -class ImageFillEdgeTableRenderer -{ -public: - ImageFillEdgeTableRenderer (const Image::BitmapData& destData_, - const Image::BitmapData& srcData_, - const int extraAlpha_, - const int x, const int y) - : destData (destData_), - srcData (srcData_), - extraAlpha (extraAlpha_ + 1), - xOffset (repeatPattern ? negativeAwareModulo (x, srcData_.width) - srcData_.width : x), - yOffset (repeatPattern ? negativeAwareModulo (y, srcData_.height) - srcData_.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 (const int x, int alphaLevel) const noexcept - { - alphaLevel = (alphaLevel * extraAlpha) >> 8; - - linePixels[x].blend (sourceLineStart [repeatPattern ? ((x - xOffset) % srcData.width) : (x - xOffset)], (uint32) alphaLevel); - } - - forcedinline void handleEdgeTablePixelFull (const int x) const noexcept - { - linePixels[x].blend (sourceLineStart [repeatPattern ? ((x - xOffset) % srcData.width) : (x - xOffset)], (uint32) extraAlpha); - } - - void handleEdgeTableLine (int x, int width, int alphaLevel) const noexcept - { - DestPixelType* dest = linePixels + x; - alphaLevel = (alphaLevel * extraAlpha) >> 8; - x -= xOffset; - - jassert (repeatPattern || (x >= 0 && x + width <= srcData.width)); - - if (alphaLevel < 0xfe) - { - do - { - dest++ ->blend (sourceLineStart [repeatPattern ? (x++ % srcData.width) : x++], (uint32) alphaLevel); - } while (--width > 0); - } - else - { - if (repeatPattern) - { - do - { - dest++ ->blend (sourceLineStart [x++ % srcData.width]); - } while (--width > 0); - } - else - { - copyRow (dest, sourceLineStart + x, width); - } - } - } - - void handleEdgeTableLineFull (int x, int width) const noexcept - { - DestPixelType* dest = linePixels + x; - x -= xOffset; - - jassert (repeatPattern || (x >= 0 && x + width <= srcData.width)); - - if (extraAlpha < 0xfe) - { - do - { - dest++ ->blend (sourceLineStart [repeatPattern ? (x++ % srcData.width) : x++], (uint32) extraAlpha); - } while (--width > 0); - } - else - { - if (repeatPattern) - { - do - { - dest++ ->blend (sourceLineStart [x++ % srcData.width]); - } while (--width > 0); - } - else - { - copyRow (dest, sourceLineStart + x, width); - } - } - } - - void clipEdgeTableLine (EdgeTable& et, int x, int y, int width) - { - jassert (x - xOffset >= 0 && x + width - xOffset <= srcData.width); - SrcPixelType* s = (SrcPixelType*) srcData.getLinePointer (y - yOffset); - uint8* 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; - - template - static forcedinline void copyRow (PixelType1* dest, PixelType2* src, int width) noexcept - { - do - { - dest++ ->blend (*src++); - } while (--width > 0); - } - - static forcedinline void copyRow (PixelRGB* dest, PixelRGB* src, int width) noexcept - { - memcpy (dest, src, sizeof (PixelRGB) * (size_t) width); - } - - JUCE_DECLARE_NON_COPYABLE (ImageFillEdgeTableRenderer); -}; - -//============================================================================== -template -class TransformedImageFillEdgeTableRenderer -{ -public: - TransformedImageFillEdgeTableRenderer (const Image::BitmapData& destData_, - const Image::BitmapData& srcData_, - const AffineTransform& transform, - const int extraAlpha_, - const bool betterQuality_) - : interpolator (transform, - betterQuality_ ? 0.5f : 0.0f, - betterQuality_ ? -128 : 0), - destData (destData_), - srcData (srcData_), - extraAlpha (extraAlpha_ + 1), - betterQuality (betterQuality_), - maxX (srcData_.width - 1), - maxY (srcData_.height - 1), - scratchSize (2048) - { - scratchBuffer.malloc (scratchSize); - } - - forcedinline void setEdgeTableYPos (const int newY) noexcept - { - y = newY; - linePixels = (DestPixelType*) destData.getLinePointer (newY); - } - - forcedinline void handleEdgeTablePixel (const int x, const int alphaLevel) noexcept - { - SrcPixelType p; - generate (&p, x, 1); - - linePixels[x].blend (p, (uint32) (alphaLevel * extraAlpha) >> 8); - } - - forcedinline void handleEdgeTablePixelFull (const int x) noexcept - { - SrcPixelType p; - generate (&p, x, 1); - - linePixels[x].blend (p, (uint32) extraAlpha); - } - - void handleEdgeTableLine (const 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); - - DestPixelType* dest = linePixels + x; - alphaLevel *= extraAlpha; - alphaLevel >>= 8; - - if (alphaLevel < 0xfe) - { - do - { - dest++ ->blend (*span++, (uint32) alphaLevel); - } while (--width > 0); - } - else - { - do - { - dest++ ->blend (*span++); - } while (--width > 0); - } - } - - forcedinline void handleEdgeTableLineFull (const int x, int width) noexcept - { - handleEdgeTableLine (x, width, 255); - } - - void clipEdgeTableLine (EdgeTable& et, int x, int y_, int width) - { - if (width > (int) scratchSize) - { - scratchSize = (size_t) width; - scratchBuffer.malloc (scratchSize); - } - - y = y_; - generate (scratchBuffer.getData(), x, width); - - et.clipLineToMask (x, y_, - reinterpret_cast (scratchBuffer.getData()) + SrcPixelType::indexA, - sizeof (SrcPixelType), width); - } - -private: - //============================================================================== - template - void generate (PixelType* dest, const int x, int numPixels) noexcept - { - this->interpolator.setStartOfLine ((float) x, (float) y, 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 (betterQuality) - { - 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; - } - else - { - // At a top or bottom edge.. - if (! repeatPattern) - { - 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)) - { - // At a left or right hand edge.. - if (! repeatPattern) - { - 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* const dest, const uint8* src, const int subPixelX, const int subPixelY) noexcept - { - uint32 c[4] = { 256 * 128, 256 * 128, 256 * 128, 256 * 128 }; - - uint32 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]; - - weight = (uint32) (subPixelX * (256 - subPixelY)); - c[0] += weight * src[4]; - c[1] += weight * src[5]; - c[2] += weight * src[6]; - c[3] += weight * src[7]; - - src += this->srcData.lineStride; - - 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]; - - weight = (uint32) (subPixelX * subPixelY); - c[0] += weight * src[4]; - c[1] += weight * src[5]; - c[2] += weight * src[6]; - c[3] += weight * src[7]; - - 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* const dest, const uint8* src, const 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]; - - weight = subPixelX; - c[0] += weight * src[4]; - c[1] += weight * src[5]; - c[2] += weight * src[6]; - c[3] += weight * src[7]; - - 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* const dest, const uint8* src, const 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* const dest, const uint8* src, const uint32 subPixelX, const 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]; - - weight = subPixelX * (256 - subPixelY); - c[0] += weight * src[3]; - c[1] += weight * src[4]; - c[2] += weight * src[5]; - - src += this->srcData.lineStride; - - weight = (256 - subPixelX) * subPixelY; - c[0] += weight * src[0]; - c[1] += weight * src[1]; - c[2] += weight * src[2]; - - weight = subPixelX * subPixelY; - c[0] += weight * src[3]; - c[1] += weight * src[4]; - c[2] += weight * src[5]; - - dest->setARGB ((uint8) 255, - (uint8) (c[PixelRGB::indexR] >> 16), - (uint8) (c[PixelRGB::indexG] >> 16), - (uint8) (c[PixelRGB::indexB] >> 16)); - } - - void render2PixelAverageX (PixelRGB* const dest, const uint8* src, const 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]; - - c[0] += subPixelX * src[3]; - c[1] += subPixelX * src[4]; - c[2] += subPixelX * src[5]; - - dest->setARGB ((uint8) 255, - (uint8) (c[PixelRGB::indexR] >> 8), - (uint8) (c[PixelRGB::indexG] >> 8), - (uint8) (c[PixelRGB::indexB] >> 8)); - } - - void render2PixelAverageY (PixelRGB* const dest, const uint8* src, const 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* const dest, const uint8* src, const uint32 subPixelX, const uint32 subPixelY) noexcept - { - uint32 c = 256 * 128; - c += src[0] * ((256 - subPixelX) * (256 - subPixelY)); - c += src[1] * (subPixelX * (256 - subPixelY)); - src += this->srcData.lineStride; - c += src[0] * ((256 - subPixelX) * subPixelY); - c += src[1] * (subPixelX * subPixelY); - - *((uint8*) dest) = (uint8) (c >> 16); - } - - void render2PixelAverageX (PixelAlpha* const dest, const uint8* src, const uint32 subPixelX) noexcept - { - uint32 c = 128; - c += src[0] * (256 - subPixelX); - c += src[1] * subPixelX; - *((uint8*) dest) = (uint8) (c >> 8); - } - - void render2PixelAverageY (PixelAlpha* const dest, const uint8* src, const uint32 subPixelY) noexcept - { - uint32 c = 128; - c += src[0] * (256 - subPixelY); - src += this->srcData.lineStride; - c += src[0] * subPixelY; - *((uint8*) dest) = (uint8) (c >> 8); - } - - //============================================================================== - class TransformedImageSpanInterpolator - { - public: - TransformedImageSpanInterpolator (const AffineTransform& transform, const float pixelOffset_, const int pixelOffsetInt_) noexcept - : inverseTransform (transform.inverted()), - pixelOffset (pixelOffset_), pixelOffsetInt (pixelOffsetInt_) - {} - - void setStartOfLine (float x, float y, const int numPixels) noexcept - { - jassert (numPixels > 0); - - x += pixelOffset; - y += pixelOffset; - float x1 = x, y1 = y; - x += numPixels; - inverseTransform.transformPoints (x1, y1, x, y); - - xBresenham.set ((int) (x1 * 256.0f), (int) (x * 256.0f), numPixels, pixelOffsetInt); - yBresenham.set ((int) (y1 * 256.0f), (int) (y * 256.0f), numPixels, pixelOffsetInt); - } - - void next (int& x, int& y) noexcept - { - x = xBresenham.n; - xBresenham.stepToNext(); - y = yBresenham.n; - yBresenham.stepToNext(); - } - - private: - class BresenhamInterpolator - { - public: - BresenhamInterpolator() noexcept {} - - void set (const int n1, const int n2, const int numSteps_, const int pixelOffsetInt) noexcept - { - numSteps = numSteps_; - step = (n2 - n1) / numSteps; - remainder = modulo = (n2 - n1) % numSteps; - n = n1 + pixelOffsetInt; - - 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 bool betterQuality; - const int maxX, maxY; - int y; - DestPixelType* linePixels; - HeapBlock scratchBuffer; - size_t scratchSize; - - JUCE_DECLARE_NON_COPYABLE (TransformedImageFillEdgeTableRenderer); -}; - -//============================================================================== -class ClipRegionBase : public SingleThreadedReferenceCountedObject -{ -public: - ClipRegionBase() {} - virtual ~ClipRegionBase() {} - - typedef ReferenceCountedObjectPtr Ptr; - - virtual Ptr clone() const = 0; - virtual Ptr applyClipTo (const Ptr& target) const = 0; - - virtual Ptr clipToRectangle (const Rectangle& r) = 0; - virtual Ptr clipToRectangleList (const RectangleList& r) = 0; - virtual Ptr excludeClipRectangle (const Rectangle& r) = 0; - virtual Ptr clipToPath (const Path& p, const AffineTransform& transform) = 0; - virtual Ptr clipToEdgeTable (const EdgeTable& et) = 0; - virtual Ptr clipToImageAlpha (const Image& image, const AffineTransform& t, const bool betterQuality) = 0; - virtual void translate (const Point& delta) = 0; - - virtual bool clipRegionIntersects (const Rectangle& r) const = 0; - virtual Rectangle getClipBounds() const = 0; - - virtual void fillRectWithColour (Image::BitmapData& destData, const Rectangle& area, const PixelARGB& colour, bool replaceContents) const = 0; - virtual void fillRectWithColour (Image::BitmapData& destData, const Rectangle& area, const PixelARGB& colour) const = 0; - virtual void fillAllWithColour (Image::BitmapData& destData, const PixelARGB& colour, bool replaceContents) const = 0; - virtual void fillAllWithGradient (Image::BitmapData& destData, ColourGradient& gradient, const AffineTransform& transform, bool isIdentity) const = 0; - virtual void renderImageTransformed (const Image::BitmapData& destData, const Image::BitmapData& srcData, const int alpha, const AffineTransform& t, bool betterQuality, bool tiledFill) const = 0; - virtual void renderImageUntransformed (const Image::BitmapData& destData, const Image::BitmapData& srcData, const int alpha, int x, int y, bool tiledFill) const = 0; - -protected: - //============================================================================== - template - static void renderImageTransformedInternal (Iterator& iter, const Image::BitmapData& destData, const Image::BitmapData& srcData, - const int alpha, const AffineTransform& transform, bool betterQuality, bool tiledFill) - { - switch (destData.pixelFormat) - { - case Image::ARGB: - switch (srcData.pixelFormat) - { - case Image::ARGB: - if (tiledFill) { TransformedImageFillEdgeTableRenderer r (destData, srcData, transform, alpha, betterQuality); iter.iterate (r); } - else { TransformedImageFillEdgeTableRenderer r (destData, srcData, transform, alpha, betterQuality); iter.iterate (r); } - break; - case Image::RGB: - if (tiledFill) { TransformedImageFillEdgeTableRenderer r (destData, srcData, transform, alpha, betterQuality); iter.iterate (r); } - else { TransformedImageFillEdgeTableRenderer r (destData, srcData, transform, alpha, betterQuality); iter.iterate (r); } - break; - default: - if (tiledFill) { TransformedImageFillEdgeTableRenderer r (destData, srcData, transform, alpha, betterQuality); iter.iterate (r); } - else { TransformedImageFillEdgeTableRenderer r (destData, srcData, transform, alpha, betterQuality); iter.iterate (r); } - break; - } - break; - - case Image::RGB: - switch (srcData.pixelFormat) - { - case Image::ARGB: - if (tiledFill) { TransformedImageFillEdgeTableRenderer r (destData, srcData, transform, alpha, betterQuality); iter.iterate (r); } - else { TransformedImageFillEdgeTableRenderer r (destData, srcData, transform, alpha, betterQuality); iter.iterate (r); } - break; - case Image::RGB: - if (tiledFill) { TransformedImageFillEdgeTableRenderer r (destData, srcData, transform, alpha, betterQuality); iter.iterate (r); } - else { TransformedImageFillEdgeTableRenderer r (destData, srcData, transform, alpha, betterQuality); iter.iterate (r); } - break; - default: - if (tiledFill) { TransformedImageFillEdgeTableRenderer r (destData, srcData, transform, alpha, betterQuality); iter.iterate (r); } - else { TransformedImageFillEdgeTableRenderer r (destData, srcData, transform, alpha, betterQuality); iter.iterate (r); } - break; - } - break; - - default: - switch (srcData.pixelFormat) - { - case Image::ARGB: - if (tiledFill) { TransformedImageFillEdgeTableRenderer r (destData, srcData, transform, alpha, betterQuality); iter.iterate (r); } - else { TransformedImageFillEdgeTableRenderer r (destData, srcData, transform, alpha, betterQuality); iter.iterate (r); } - break; - case Image::RGB: - if (tiledFill) { TransformedImageFillEdgeTableRenderer r (destData, srcData, transform, alpha, betterQuality); iter.iterate (r); } - else { TransformedImageFillEdgeTableRenderer r (destData, srcData, transform, alpha, betterQuality); iter.iterate (r); } - break; - default: - if (tiledFill) { TransformedImageFillEdgeTableRenderer r (destData, srcData, transform, alpha, betterQuality); iter.iterate (r); } - else { TransformedImageFillEdgeTableRenderer r (destData, srcData, transform, alpha, betterQuality); iter.iterate (r); } - break; - } - break; - } - } - - template - static void renderImageUntransformedInternal (Iterator& iter, const Image::BitmapData& destData, const Image::BitmapData& srcData, const int alpha, int x, int y, bool tiledFill) - { - switch (destData.pixelFormat) - { - case Image::ARGB: - switch (srcData.pixelFormat) - { - case Image::ARGB: - if (tiledFill) { ImageFillEdgeTableRenderer r (destData, srcData, alpha, x, y); iter.iterate (r); } - else { ImageFillEdgeTableRenderer r (destData, srcData, alpha, x, y); iter.iterate (r); } - break; - case Image::RGB: - if (tiledFill) { ImageFillEdgeTableRenderer r (destData, srcData, alpha, x, y); iter.iterate (r); } - else { ImageFillEdgeTableRenderer r (destData, srcData, alpha, x, y); iter.iterate (r); } - break; - default: - if (tiledFill) { ImageFillEdgeTableRenderer r (destData, srcData, alpha, x, y); iter.iterate (r); } - else { ImageFillEdgeTableRenderer r (destData, srcData, alpha, x, y); iter.iterate (r); } - break; - } - break; - - case Image::RGB: - switch (srcData.pixelFormat) - { - case Image::ARGB: - if (tiledFill) { ImageFillEdgeTableRenderer r (destData, srcData, alpha, x, y); iter.iterate (r); } - else { ImageFillEdgeTableRenderer r (destData, srcData, alpha, x, y); iter.iterate (r); } - break; - case Image::RGB: - if (tiledFill) { ImageFillEdgeTableRenderer r (destData, srcData, alpha, x, y); iter.iterate (r); } - else { ImageFillEdgeTableRenderer r (destData, srcData, alpha, x, y); iter.iterate (r); } - break; - default: - if (tiledFill) { ImageFillEdgeTableRenderer r (destData, srcData, alpha, x, y); iter.iterate (r); } - else { ImageFillEdgeTableRenderer r (destData, srcData, alpha, x, y); iter.iterate (r); } - break; - } - break; - - default: - switch (srcData.pixelFormat) - { - case Image::ARGB: - if (tiledFill) { ImageFillEdgeTableRenderer r (destData, srcData, alpha, x, y); iter.iterate (r); } - else { ImageFillEdgeTableRenderer r (destData, srcData, alpha, x, y); iter.iterate (r); } - break; - case Image::RGB: - if (tiledFill) { ImageFillEdgeTableRenderer r (destData, srcData, alpha, x, y); iter.iterate (r); } - else { ImageFillEdgeTableRenderer r (destData, srcData, alpha, x, y); iter.iterate (r); } - break; - default: - if (tiledFill) { ImageFillEdgeTableRenderer r (destData, srcData, alpha, x, y); iter.iterate (r); } - else { ImageFillEdgeTableRenderer r (destData, srcData, alpha, x, y); iter.iterate (r); } - break; - } - break; - } - } - - template - static void renderSolidFill (Iterator& iter, const Image::BitmapData& destData, const PixelARGB& fillColour, const bool replaceContents, DestPixelType*) - { - jassert (destData.pixelStride == sizeof (DestPixelType)); - if (replaceContents) - { - SolidColourEdgeTableRenderer r (destData, fillColour); - iter.iterate (r); - } - else - { - SolidColourEdgeTableRenderer r (destData, fillColour); - iter.iterate (r); - } - } - - template - static void renderGradient (Iterator& iter, const Image::BitmapData& destData, const ColourGradient& g, const AffineTransform& transform, - const PixelARGB* const lookupTable, const int numLookupEntries, const bool isIdentity, DestPixelType*) - { - jassert (destData.pixelStride == sizeof (DestPixelType)); - - if (g.isRadial) - { - if (isIdentity) - { - GradientEdgeTableRenderer renderer (destData, g, transform, lookupTable, numLookupEntries); - iter.iterate (renderer); - } - else - { - GradientEdgeTableRenderer renderer (destData, g, transform, lookupTable, numLookupEntries); - iter.iterate (renderer); - } - } - else - { - GradientEdgeTableRenderer renderer (destData, g, transform, lookupTable, numLookupEntries); - iter.iterate (renderer); - } - } -}; - -//============================================================================== -class ClipRegion_EdgeTable : public ClipRegionBase -{ -public: - ClipRegion_EdgeTable (const EdgeTable& e) : edgeTable (e) {} - ClipRegion_EdgeTable (const Rectangle& r) : edgeTable (r) {} - ClipRegion_EdgeTable (const Rectangle& r) : edgeTable (r) {} - ClipRegion_EdgeTable (const RectangleList& r) : edgeTable (r) {} - ClipRegion_EdgeTable (const Rectangle& bounds, const Path& p, const AffineTransform& t) : edgeTable (bounds, p, t) {} - ClipRegion_EdgeTable (const ClipRegion_EdgeTable& other) : edgeTable (other.edgeTable) {} - - Ptr clone() const - { - return new ClipRegion_EdgeTable (*this); - } - - Ptr applyClipTo (const Ptr& target) const - { - return target->clipToEdgeTable (edgeTable); - } - - Ptr clipToRectangle (const Rectangle& r) - { - edgeTable.clipToRectangle (r); - return edgeTable.isEmpty() ? nullptr : this; - } - - Ptr clipToRectangleList (const RectangleList& r) - { - RectangleList inverse (edgeTable.getMaximumBounds()); - - if (inverse.subtract (r)) - for (RectangleList::Iterator iter (inverse); iter.next();) - edgeTable.excludeRectangle (*iter.getRectangle()); - - return edgeTable.isEmpty() ? nullptr : this; - } - - Ptr excludeClipRectangle (const Rectangle& r) - { - edgeTable.excludeRectangle (r); - return edgeTable.isEmpty() ? nullptr : this; - } - - Ptr clipToPath (const Path& p, const AffineTransform& transform) - { - EdgeTable et (edgeTable.getMaximumBounds(), p, transform); - edgeTable.clipToEdgeTable (et); - return edgeTable.isEmpty() ? nullptr : this; - } - - Ptr clipToEdgeTable (const EdgeTable& et) - { - edgeTable.clipToEdgeTable (et); - return edgeTable.isEmpty() ? nullptr : this; - } - - Ptr clipToImageAlpha (const Image& image, const AffineTransform& transform, const bool betterQuality) - { - const Image::BitmapData srcData (image, Image::BitmapData::readOnly); - - if (transform.isOnlyTranslation()) - { - // If our translation doesn't involve any distortion, just use a simple blit.. - const int tx = (int) (transform.getTranslationX() * 256.0f); - const int ty = (int) (transform.getTranslationY() * 256.0f); - - if ((! betterQuality) || ((tx | ty) & 224) == 0) - { - const int imageX = ((tx + 128) >> 8); - const int imageY = ((ty + 128) >> 8); - - if (image.getFormat() == Image::ARGB) - straightClipImage (srcData, imageX, imageY, (PixelARGB*) 0); - else - straightClipImage (srcData, imageX, imageY, (PixelAlpha*) 0); - - return edgeTable.isEmpty() ? nullptr : this; - } - } - - if (transform.isSingularity()) - return nullptr; - - { - 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, betterQuality, (PixelARGB*) 0); - else - transformedClipImage (srcData, transform, betterQuality, (PixelAlpha*) 0); - } - - return edgeTable.isEmpty() ? nullptr : this; - } - - void translate (const Point& delta) - { - edgeTable.translate ((float) delta.x, delta.y); - } - - bool clipRegionIntersects (const Rectangle& r) const - { - return edgeTable.getMaximumBounds().intersects (r); - } - - Rectangle getClipBounds() const - { - return edgeTable.getMaximumBounds(); - } - - void fillRectWithColour (Image::BitmapData& destData, const Rectangle& area, const PixelARGB& colour, bool replaceContents) const - { - const Rectangle totalClip (edgeTable.getMaximumBounds()); - const Rectangle clipped (totalClip.getIntersection (area)); - - if (! clipped.isEmpty()) - { - ClipRegion_EdgeTable et (clipped); - et.edgeTable.clipToEdgeTable (edgeTable); - et.fillAllWithColour (destData, colour, replaceContents); - } - } - - void fillRectWithColour (Image::BitmapData& destData, const Rectangle& area, const PixelARGB& colour) const - { - const Rectangle totalClip (edgeTable.getMaximumBounds().toFloat()); - const Rectangle clipped (totalClip.getIntersection (area)); - - if (! clipped.isEmpty()) - { - ClipRegion_EdgeTable et (clipped); - et.edgeTable.clipToEdgeTable (edgeTable); - et.fillAllWithColour (destData, colour, false); - } - } - - void fillAllWithColour (Image::BitmapData& destData, const PixelARGB& colour, bool replaceContents) const - { - switch (destData.pixelFormat) - { - case Image::ARGB: renderSolidFill (edgeTable, destData, colour, replaceContents, (PixelARGB*) 0); break; - case Image::RGB: renderSolidFill (edgeTable, destData, colour, replaceContents, (PixelRGB*) 0); break; - default: renderSolidFill (edgeTable, destData, colour, replaceContents, (PixelAlpha*) 0); break; - } - } - - void fillAllWithGradient (Image::BitmapData& destData, ColourGradient& gradient, const AffineTransform& transform, bool isIdentity) const - { - HeapBlock lookupTable; - const int numLookupEntries = gradient.createLookupTable (transform, lookupTable); - jassert (numLookupEntries > 0); - - switch (destData.pixelFormat) - { - case Image::ARGB: renderGradient (edgeTable, destData, gradient, transform, lookupTable, numLookupEntries, isIdentity, (PixelARGB*) 0); break; - case Image::RGB: renderGradient (edgeTable, destData, gradient, transform, lookupTable, numLookupEntries, isIdentity, (PixelRGB*) 0); break; - default: renderGradient (edgeTable, destData, gradient, transform, lookupTable, numLookupEntries, isIdentity, (PixelAlpha*) 0); break; - } - } - - void renderImageTransformed (const Image::BitmapData& destData, const Image::BitmapData& srcData, const int alpha, const AffineTransform& transform, bool betterQuality, bool tiledFill) const - { - renderImageTransformedInternal (edgeTable, destData, srcData, alpha, transform, betterQuality, tiledFill); - } - - void renderImageUntransformed (const Image::BitmapData& destData, const Image::BitmapData& srcData, const int alpha, int x, int y, bool tiledFill) const - { - renderImageUntransformedInternal (edgeTable, destData, srcData, alpha, x, y, tiledFill); - } - - EdgeTable edgeTable; - -private: - //============================================================================== - template - void transformedClipImage (const Image::BitmapData& srcData, const AffineTransform& transform, const bool betterQuality, const SrcPixelType*) - { - TransformedImageFillEdgeTableRenderer renderer (srcData, srcData, transform, 255, betterQuality); - - 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); - - ImageFillEdgeTableRenderer renderer (srcData, srcData, 255, imageX, imageY); - - for (int y = 0; y < r.getHeight(); ++y) - renderer.clipEdgeTableLine (edgeTable, r.getX(), y + r.getY(), r.getWidth()); - } - - ClipRegion_EdgeTable& operator= (const ClipRegion_EdgeTable&); -}; - - -//============================================================================== -class ClipRegion_RectangleList : public ClipRegionBase -{ -public: - ClipRegion_RectangleList (const Rectangle& r) : clip (r) {} - ClipRegion_RectangleList (const RectangleList& r) : clip (r) {} - ClipRegion_RectangleList (const ClipRegion_RectangleList& other) : clip (other.clip) {} - - Ptr clone() const - { - return new ClipRegion_RectangleList (*this); - } - - Ptr applyClipTo (const Ptr& target) const - { - return target->clipToRectangleList (clip); - } - - Ptr clipToRectangle (const Rectangle& r) - { - clip.clipTo (r); - return clip.isEmpty() ? nullptr : this; - } - - Ptr clipToRectangleList (const RectangleList& r) - { - clip.clipTo (r); - return clip.isEmpty() ? nullptr : this; - } - - Ptr excludeClipRectangle (const Rectangle& r) - { - clip.subtract (r); - return clip.isEmpty() ? nullptr : this; - } - - Ptr clipToPath (const Path& p, const AffineTransform& transform) - { - return toEdgeTable()->clipToPath (p, transform); - } - - Ptr clipToEdgeTable (const EdgeTable& et) - { - return toEdgeTable()->clipToEdgeTable (et); - } - - Ptr clipToImageAlpha (const Image& image, const AffineTransform& transform, const bool betterQuality) - { - return toEdgeTable()->clipToImageAlpha (image, transform, betterQuality); - } - - void translate (const Point& delta) - { - clip.offsetAll (delta.x, delta.y); - } - - bool clipRegionIntersects (const Rectangle& r) const - { - return clip.intersects (r); - } - - Rectangle getClipBounds() const - { - return clip.getBounds(); - } - - void fillRectWithColour (Image::BitmapData& destData, const Rectangle& area, const PixelARGB& colour, bool replaceContents) const - { - SubRectangleIterator iter (clip, area); - - switch (destData.pixelFormat) - { - case Image::ARGB: renderSolidFill (iter, destData, colour, replaceContents, (PixelARGB*) 0); break; - case Image::RGB: renderSolidFill (iter, destData, colour, replaceContents, (PixelRGB*) 0); break; - default: renderSolidFill (iter, destData, colour, replaceContents, (PixelAlpha*) 0); break; - } - } - - void fillRectWithColour (Image::BitmapData& destData, const Rectangle& area, const PixelARGB& colour) const - { - SubRectangleIteratorFloat iter (clip, area); - - switch (destData.pixelFormat) - { - case Image::ARGB: renderSolidFill (iter, destData, colour, false, (PixelARGB*) 0); break; - case Image::RGB: renderSolidFill (iter, destData, colour, false, (PixelRGB*) 0); break; - default: renderSolidFill (iter, destData, colour, false, (PixelAlpha*) 0); break; - } - } - - void fillAllWithColour (Image::BitmapData& destData, const PixelARGB& colour, bool replaceContents) const - { - switch (destData.pixelFormat) - { - case Image::ARGB: renderSolidFill (*this, destData, colour, replaceContents, (PixelARGB*) 0); break; - case Image::RGB: renderSolidFill (*this, destData, colour, replaceContents, (PixelRGB*) 0); break; - default: renderSolidFill (*this, destData, colour, replaceContents, (PixelAlpha*) 0); break; - } - } - - void fillAllWithGradient (Image::BitmapData& destData, ColourGradient& gradient, const AffineTransform& transform, bool isIdentity) const - { - HeapBlock lookupTable; - const int numLookupEntries = gradient.createLookupTable (transform, lookupTable); - jassert (numLookupEntries > 0); - - switch (destData.pixelFormat) - { - case Image::ARGB: renderGradient (*this, destData, gradient, transform, lookupTable, numLookupEntries, isIdentity, (PixelARGB*) 0); break; - case Image::RGB: renderGradient (*this, destData, gradient, transform, lookupTable, numLookupEntries, isIdentity, (PixelRGB*) 0); break; - default: renderGradient (*this, destData, gradient, transform, lookupTable, numLookupEntries, isIdentity, (PixelAlpha*) 0); break; - } - } - - void renderImageTransformed (const Image::BitmapData& destData, const Image::BitmapData& srcData, const int alpha, const AffineTransform& transform, bool betterQuality, bool tiledFill) const - { - renderImageTransformedInternal (*this, destData, srcData, alpha, transform, betterQuality, tiledFill); - } - - void renderImageUntransformed (const Image::BitmapData& destData, const Image::BitmapData& srcData, const int alpha, int x, int y, bool tiledFill) const - { - renderImageUntransformedInternal (*this, destData, srcData, alpha, x, y, tiledFill); - } - - RectangleList clip; - - //============================================================================== - template - void iterate (Renderer& r) const noexcept - { - RectangleList::Iterator iter (clip); - - while (iter.next()) - { - const Rectangle rect (*iter.getRectangle()); - const int x = rect.getX(); - const int w = rect.getWidth(); - jassert (w > 0); - const int bottom = rect.getBottom(); - - for (int y = rect.getY(); y < bottom; ++y) - { - r.setEdgeTableYPos (y); - r.handleEdgeTableLineFull (x, w); - } - } - } - -private: - //============================================================================== - class SubRectangleIterator - { - public: - SubRectangleIterator (const RectangleList& clip_, const Rectangle& area_) - : clip (clip_), area (area_) - { - } - - template - void iterate (Renderer& r) const noexcept - { - RectangleList::Iterator iter (clip); - - while (iter.next()) - { - const Rectangle rect (iter.getRectangle()->getIntersection (area)); - - if (! rect.isEmpty()) - { - const int x = rect.getX(); - const int w = rect.getWidth(); - const int bottom = rect.getBottom(); - - for (int y = rect.getY(); y < bottom; ++y) - { - r.setEdgeTableYPos (y); - r.handleEdgeTableLineFull (x, w); - } - } - } - } - - private: - const RectangleList& clip; - const Rectangle area; - - JUCE_DECLARE_NON_COPYABLE (SubRectangleIterator); - }; - - //============================================================================== - class SubRectangleIteratorFloat - { - public: - SubRectangleIteratorFloat (const RectangleList& clip_, const Rectangle& area_) noexcept - : clip (clip_), area (area_) - { - } - - template - void iterate (Renderer& r) const noexcept - { - const RenderingHelpers::FloatRectangleRasterisingInfo f (area); - RectangleList::Iterator iter (clip); - - while (iter.next()) - { - const int clipLeft = iter.getRectangle()->getX(); - const int clipRight = iter.getRectangle()->getRight(); - const int clipTop = iter.getRectangle()->getY(); - const int clipBottom = iter.getRectangle()->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); - } - - const int endY = jmin (f.bottom, clipBottom); - for (int y = jmax (clipTop, f.top); y < endY; ++y) - { - r.setEdgeTableYPos (y); - r.handleEdgeTablePixelFull (f.left); - } - - if (f.bottomAlpha != 0 && f.bottom < clipBottom) - { - r.setEdgeTableYPos (f.bottom); - r.handleEdgeTablePixel (f.left, f.bottomAlpha); - } - } - else - { - const int clippedLeft = jmax (f.left, clipLeft); - const int clippedWidth = jmin (f.right, clipRight) - clippedLeft; - const bool doLeftAlpha = f.leftAlpha != 0 && f.totalLeft >= clipLeft; - const 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()); - } - - const int endY = jmin (f.bottom, clipBottom); - for (int y = jmax (clipTop, f.top); y < endY; ++y) - { - r.setEdgeTableYPos (y); - - if (doLeftAlpha) r.handleEdgeTablePixel (f.totalLeft, f.leftAlpha); - if (clippedWidth > 0) r.handleEdgeTableLineFull (clippedLeft, clippedWidth); - if (doRightAlpha) r.handleEdgeTablePixel (f.right, 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; - const Rectangle& area; - - JUCE_DECLARE_NON_COPYABLE (SubRectangleIteratorFloat); - }; - - inline Ptr toEdgeTable() const { return new ClipRegion_EdgeTable (clip); } - - ClipRegion_RectangleList& operator= (const ClipRegion_RectangleList&); -}; - -} - -//============================================================================== -class LowLevelGraphicsSoftwareRenderer::SavedState -{ -public: - SavedState (const Image& image_, const Rectangle& clip_) - : image (image_), clip (new SoftwareRendererClasses::ClipRegion_RectangleList (clip_)), - transform (0, 0), - interpolationQuality (Graphics::mediumResamplingQuality), - transparencyLayerAlpha (1.0f) - { - } - - SavedState (const Image& image_, const RectangleList& clip_, const int xOffset_, const int yOffset_) - : image (image_), clip (new SoftwareRendererClasses::ClipRegion_RectangleList (clip_)), - transform (xOffset_, yOffset_), - interpolationQuality (Graphics::mediumResamplingQuality), - transparencyLayerAlpha (1.0f) - { - } - - SavedState (const SavedState& other) - : image (other.image), clip (other.clip), transform (other.transform), - font (other.font), fillType (other.fillType), - interpolationQuality (other.interpolationQuality), - transparencyLayerAlpha (other.transparencyLayerAlpha) - { - } - - bool clipToRectangle (const Rectangle& r) - { - if (clip != nullptr) - { - if (transform.isOnlyTranslated) - { - cloneClipIfMultiplyReferenced(); - clip = clip->clipToRectangle (transform.translated (r)); - } - else - { - Path p; - p.addRectangle (r); - clipToPath (p, AffineTransform::identity); - } - } - - return clip != nullptr; - } - - bool clipToRectangleList (const RectangleList& r) - { - if (clip != nullptr) - { - if (transform.isOnlyTranslated) - { - cloneClipIfMultiplyReferenced(); - RectangleList offsetList (r); - offsetList.offsetAll (transform.xOffset, transform.yOffset); - clip = clip->clipToRectangleList (offsetList); - } - else - { - clipToPath (r.toPath(), AffineTransform::identity); - } - } - - return clip != nullptr; - } - - bool excludeClipRectangle (const Rectangle& r) - { - if (clip != nullptr) - { - cloneClipIfMultiplyReferenced(); - - if (transform.isOnlyTranslated) - { - clip = clip->excludeClipRectangle (transform.translated (r)); - } - else - { - Path p; - p.addRectangle (r.toFloat()); - p.applyTransform (transform.complexTransform); - p.addRectangle (clip->getClipBounds().toFloat()); - p.setUsingNonZeroWinding (false); - clip = clip->clipToPath (p, AffineTransform::identity); - } - } - - 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 != Graphics::lowResamplingQuality); - } - else - { - Path p; - p.addRectangle (sourceImage.getBounds()); - clipToPath (p, t); - } - } - } - - bool clipRegionIntersects (const Rectangle& r) const - { - if (clip != nullptr) - { - if (transform.isOnlyTranslated) - return clip->clipRegionIntersects (transform.translated (r)); - else - return getClipBounds().intersects (r); - } - - return false; - } - - Rectangle getClipBounds() const - { - return clip != nullptr ? transform.deviceSpaceToUserSpace (clip->getClipBounds()) - : Rectangle(); - } - - SavedState* beginTransparencyLayer (float opacity) - { - SavedState* s = new SavedState (*this); - - if (clip != nullptr) - { - const Rectangle layerBounds (clip->getClipBounds()); - - s->image = Image (Image::ARGB, layerBounds.getWidth(), layerBounds.getHeight(), true); - s->transparencyLayerAlpha = opacity; - s->transform.moveOriginInDeviceSpace (-layerBounds.getX(), -layerBounds.getY()); - - s->cloneClipIfMultiplyReferenced(); - s->clip->translate (-layerBounds.getPosition()); - } - - return s; - } - - void endTransparencyLayer (SavedState& finishedLayerState) - { - if (clip != nullptr) - { - const Rectangle layerBounds (clip->getClipBounds()); - - const ScopedPointer g (image.createLowLevelContext()); - g->setOpacity (finishedLayerState.transparencyLayerAlpha); - g->drawImage (finishedLayerState.image, AffineTransform::translation ((float) layerBounds.getX(), - (float) layerBounds.getY())); - } - } - - //============================================================================== - void fillRect (const Rectangle& r, const bool replaceContents) - { - if (clip != nullptr) - { - if (transform.isOnlyTranslated) - { - if (fillType.isColour()) - { - Image::BitmapData destData (image, Image::BitmapData::readWrite); - clip->fillRectWithColour (destData, transform.translated (r), fillType.colour.getPixelARGB(), replaceContents); - } - else - { - const Rectangle totalClip (clip->getClipBounds()); - const Rectangle clipped (totalClip.getIntersection (transform.translated (r))); - - if (! clipped.isEmpty()) - fillShape (new SoftwareRendererClasses::ClipRegion_RectangleList (clipped), false); - } - } - else - { - Path p; - p.addRectangle (r); - fillPath (p, AffineTransform::identity); - } - } - } - - void fillRect (const Rectangle& r) - { - if (clip != nullptr) - { - if (transform.isOnlyTranslated) - { - if (fillType.isColour()) - { - Image::BitmapData destData (image, Image::BitmapData::readWrite); - clip->fillRectWithColour (destData, transform.translated (r), fillType.colour.getPixelARGB()); - } - else - { - const Rectangle totalClip (clip->getClipBounds().toFloat()); - const Rectangle clipped (totalClip.getIntersection (transform.translated (r))); - - if (! clipped.isEmpty()) - fillShape (new SoftwareRendererClasses::ClipRegion_EdgeTable (clipped), false); - } - } - else - { - Path p; - p.addRectangle (r); - fillPath (p, AffineTransform::identity); - } - } - } - - void fillPath (const Path& path, const AffineTransform& t) - { - if (clip != nullptr) - fillShape (new SoftwareRendererClasses::ClipRegion_EdgeTable (clip->getClipBounds(), path, transform.getTransformWith (t)), false); - } - - void fillEdgeTable (const EdgeTable& edgeTable, const float x, const int y) - { - jassert (transform.isOnlyTranslated); - - if (clip != nullptr) - { - SoftwareRendererClasses::ClipRegion_EdgeTable* edgeTableClip = new SoftwareRendererClasses::ClipRegion_EdgeTable (edgeTable); - edgeTableClip->edgeTable.translate (x + transform.xOffset, - y + transform.yOffset); - fillShape (edgeTableClip, false); - } - } - - void drawGlyph (const Font& f, int glyphNumber, const AffineTransform& t) - { - if (clip != nullptr) - { - const ScopedPointer et (f.getTypeface()->getEdgeTableForGlyph (glyphNumber, transform.getTransformWith (t))); - - if (et != nullptr) - fillShape (new SoftwareRendererClasses::ClipRegion_EdgeTable (*et), false); - } - } - - void fillShape (SoftwareRendererClasses::ClipRegionBase::Ptr shapeToFill, const bool replaceContents) - { - jassert (clip != nullptr); - - shapeToFill = clip->applyClipTo (shapeToFill); - - if (shapeToFill != nullptr) - { - Image::BitmapData destData (image, Image::BitmapData::readWrite); - - if (fillType.isGradient()) - { - jassert (! replaceContents); // that option is just for solid colours - - ColourGradient g2 (*(fillType.gradient)); - g2.multiplyOpacity (fillType.getOpacity()); - AffineTransform t (transform.getTransformWith (fillType.transform).translated (-0.5f, -0.5f)); - - const 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 = AffineTransform::identity; - } - - shapeToFill->fillAllWithGradient (destData, g2, t, isIdentity); - } - else if (fillType.isTiledImage()) - { - renderImage (fillType.image, fillType.transform, shapeToFill); - } - else - { - shapeToFill->fillAllWithColour (destData, fillType.colour.getPixelARGB(), replaceContents); - } - } - } - - //============================================================================== - void renderImage (const Image& sourceImage, const AffineTransform& trans, - const SoftwareRendererClasses::ClipRegionBase* const tiledFillClipRegion) - { - const AffineTransform t (transform.getTransformWith (trans)); - - const Image::BitmapData destData (image, Image::BitmapData::readWrite); - const Image::BitmapData srcData (sourceImage, Image::BitmapData::readOnly); - const int alpha = fillType.colour.getAlpha(); - const bool betterQuality = (interpolationQuality != Graphics::lowResamplingQuality); - - if (t.isOnlyTranslation()) - { - // If our translation doesn't involve any distortion, just use a simple blit.. - int tx = (int) (t.getTranslationX() * 256.0f); - int ty = (int) (t.getTranslationY() * 256.0f); - - if ((! betterQuality) || ((tx | ty) & 224) == 0) - { - tx = ((tx + 128) >> 8); - ty = ((ty + 128) >> 8); - - if (tiledFillClipRegion != nullptr) - { - tiledFillClipRegion->renderImageUntransformed (destData, srcData, alpha, tx, ty, true); - } - else - { - Rectangle area (tx, ty, sourceImage.getWidth(), sourceImage.getHeight()); - area = area.getIntersection (image.getBounds()); - - if (! area.isEmpty()) - { - SoftwareRendererClasses::ClipRegionBase::Ptr c (clip->applyClipTo (new SoftwareRendererClasses::ClipRegion_EdgeTable (area))); - - if (c != nullptr) - c->renderImageUntransformed (destData, srcData, alpha, tx, ty, false); - } - } - - return; - } - } - - if (t.isSingularity()) - return; - - if (tiledFillClipRegion != nullptr) - { - tiledFillClipRegion->renderImageTransformed (destData, srcData, alpha, t, betterQuality, true); - } - else - { - Path p; - p.addRectangle (sourceImage.getBounds()); - - SoftwareRendererClasses::ClipRegionBase::Ptr c (clip->clone()); - c = c->clipToPath (p, t); - - if (c != nullptr) - c->renderImageTransformed (destData, srcData, alpha, t, betterQuality, false); - } - } - - //============================================================================== - Image image; - SoftwareRendererClasses::ClipRegionBase::Ptr clip; - RenderingHelpers::TranslationOrTransform transform; - Font font; - FillType fillType; - Graphics::ResamplingQuality interpolationQuality; - -private: - float transparencyLayerAlpha; - - void cloneClipIfMultiplyReferenced() - { - if (clip->getReferenceCount() > 1) - clip = clip->clone(); - } - - SavedState& operator= (const SavedState&); -}; - - -//============================================================================== LowLevelGraphicsSoftwareRenderer::LowLevelGraphicsSoftwareRenderer (const Image& image) - : savedState (new SavedState (image, image.getBounds())) + : savedState (new RenderingHelpers::SoftwareRendererSavedState (image, image.getBounds())) { } LowLevelGraphicsSoftwareRenderer::LowLevelGraphicsSoftwareRenderer (const Image& image, const Point& origin, const RectangleList& initialClip) - : savedState (new SavedState (image, initialClip, origin.x, origin.y)) + : savedState (new RenderingHelpers::SoftwareRendererSavedState (image, initialClip, origin.x, origin.y)) { } -LowLevelGraphicsSoftwareRenderer::~LowLevelGraphicsSoftwareRenderer() -{ -} - -bool LowLevelGraphicsSoftwareRenderer::isVectorDevice() const -{ - return false; -} +LowLevelGraphicsSoftwareRenderer::~LowLevelGraphicsSoftwareRenderer() {} //============================================================================== -void LowLevelGraphicsSoftwareRenderer::setOrigin (int x, int y) -{ - savedState->transform.setOrigin (x, y); -} - -void LowLevelGraphicsSoftwareRenderer::addTransform (const AffineTransform& transform) -{ - savedState->transform.addTransform (transform); -} - -float LowLevelGraphicsSoftwareRenderer::getScaleFactor() -{ - return savedState->transform.getScaleFactor(); -} +bool LowLevelGraphicsSoftwareRenderer::isVectorDevice() const { return false; } -bool LowLevelGraphicsSoftwareRenderer::clipToRectangle (const Rectangle& r) -{ - return savedState->clipToRectangle (r); -} +void LowLevelGraphicsSoftwareRenderer::setOrigin (int x, int y) { savedState->transform.setOrigin (x, y); } +void LowLevelGraphicsSoftwareRenderer::addTransform (const AffineTransform& t) { savedState->transform.addTransform (t); } +float LowLevelGraphicsSoftwareRenderer::getScaleFactor() { return savedState->transform.getScaleFactor(); } -bool LowLevelGraphicsSoftwareRenderer::clipToRectangleList (const RectangleList& clipRegion) -{ - return savedState->clipToRectangleList (clipRegion); -} +Rectangle LowLevelGraphicsSoftwareRenderer::getClipBounds() const { return savedState->getClipBounds(); } +bool LowLevelGraphicsSoftwareRenderer::isClipEmpty() const { return savedState->clip == nullptr; } -void LowLevelGraphicsSoftwareRenderer::excludeClipRectangle (const Rectangle& r) -{ - savedState->excludeClipRectangle (r); -} +bool LowLevelGraphicsSoftwareRenderer::clipToRectangle (const Rectangle& r) { return savedState->clipToRectangle (r); } +bool LowLevelGraphicsSoftwareRenderer::clipToRectangleList (const RectangleList& r) { return savedState->clipToRectangleList (r); } +void LowLevelGraphicsSoftwareRenderer::excludeClipRectangle (const Rectangle& r) { savedState->excludeClipRectangle (r); } void LowLevelGraphicsSoftwareRenderer::clipToPath (const Path& path, const AffineTransform& transform) { @@ -2137,19 +66,9 @@ bool LowLevelGraphicsSoftwareRenderer::clipRegionIntersects (const RectangleclipRegionIntersects (r); } -Rectangle LowLevelGraphicsSoftwareRenderer::getClipBounds() const -{ - return savedState->getClipBounds(); -} - -bool LowLevelGraphicsSoftwareRenderer::isClipEmpty() const -{ - return savedState->clip == nullptr; -} - //============================================================================== -void LowLevelGraphicsSoftwareRenderer::saveState() { savedState.save(); } -void LowLevelGraphicsSoftwareRenderer::restoreState() { savedState.restore(); } +void LowLevelGraphicsSoftwareRenderer::saveState() { savedState.save(); } +void LowLevelGraphicsSoftwareRenderer::restoreState() { savedState.restore(); } void LowLevelGraphicsSoftwareRenderer::beginTransparencyLayer (float opacity) { savedState.beginTransparencyLayer (opacity); } void LowLevelGraphicsSoftwareRenderer::endTransparencyLayer() { savedState.endTransparencyLayer(); } @@ -2193,13 +112,13 @@ void LowLevelGraphicsSoftwareRenderer::drawLine (const Line & line) fillPath (p, AffineTransform::identity); } -void LowLevelGraphicsSoftwareRenderer::drawVerticalLine (const int x, float top, float bottom) +void LowLevelGraphicsSoftwareRenderer::drawVerticalLine (const int x, const float top, const float bottom) { if (bottom > top) savedState->fillRect (Rectangle ((float) x, top, 1.0f, bottom - top)); } -void LowLevelGraphicsSoftwareRenderer::drawHorizontalLine (const int y, float left, float right) +void LowLevelGraphicsSoftwareRenderer::drawHorizontalLine (const int y, const float left, const float right) { if (right > left) savedState->fillRect (Rectangle (left, (float) y, right - left, 1.0f)); @@ -2207,11 +126,13 @@ void LowLevelGraphicsSoftwareRenderer::drawHorizontalLine (const int y, float le void LowLevelGraphicsSoftwareRenderer::drawGlyph (int glyphNumber, const AffineTransform& transform) { - Font& f = savedState->font; + const Font& f = savedState->font; if (transform.isOnlyTranslation() && savedState->transform.isOnlyTranslated) { - RenderingHelpers::GlyphCache , SavedState>::getInstance() + using namespace RenderingHelpers; + + GlyphCache , SoftwareRendererSavedState>::getInstance() .drawGlyph (*savedState, f, glyphNumber, transform.getTranslationX(), transform.getTranslationY()); @@ -2227,11 +148,3 @@ void LowLevelGraphicsSoftwareRenderer::drawGlyph (int glyphNumber, const AffineT void LowLevelGraphicsSoftwareRenderer::setFont (const Font& newFont) { savedState->font = newFont; } const Font& LowLevelGraphicsSoftwareRenderer::getFont() { return savedState->font; } - -#if JUCE_MSVC - #pragma warning (pop) - - #if JUCE_DEBUG - #pragma optimize ("", on) // resets optimisations to the project defaults - #endif -#endif diff --git a/modules/juce_graphics/contexts/juce_LowLevelGraphicsSoftwareRenderer.h b/modules/juce_graphics/contexts/juce_LowLevelGraphicsSoftwareRenderer.h index 94e004ae8f..b4fa0b5dee 100644 --- a/modules/juce_graphics/contexts/juce_LowLevelGraphicsSoftwareRenderer.h +++ b/modules/juce_graphics/contexts/juce_LowLevelGraphicsSoftwareRenderer.h @@ -27,10 +27,7 @@ #define __JUCE_LOWLEVELGRAPHICSSOFTWARERENDERER_JUCEHEADER__ #include "juce_LowLevelGraphicsContext.h" - -#ifndef DOXYGEN #include "../native/juce_RenderingHelpers.h" -#endif //============================================================================== /** @@ -44,8 +41,9 @@ class JUCE_API LowLevelGraphicsSoftwareRenderer : public LowLevelGraphicsCon { public: //============================================================================== - LowLevelGraphicsSoftwareRenderer (const Image& imageToRenderOn); - LowLevelGraphicsSoftwareRenderer (const Image& imageToRenderOn, const Point& origin, const RectangleList& initialClip); + LowLevelGraphicsSoftwareRenderer (const Image& imageToRenderOnto); + LowLevelGraphicsSoftwareRenderer (const Image& imageToRenderOnto, const Point& origin, + const RectangleList& initialClip); ~LowLevelGraphicsSoftwareRenderer(); bool isVectorDevice() const; @@ -76,8 +74,7 @@ public: void drawImage (const Image&, const AffineTransform&); - void drawLine (const Line & line); - + void drawLine (const Line &); void drawVerticalLine (int x, float top, float bottom); void drawHorizontalLine (int x, float top, float bottom); @@ -86,12 +83,8 @@ public: void drawGlyph (int glyphNumber, float x, float y); void drawGlyph (int glyphNumber, const AffineTransform&); - #ifndef DOXYGEN - class SavedState; - #endif - protected: - RenderingHelpers::SavedStateStack savedState; + RenderingHelpers::SavedStateStack savedState; JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (LowLevelGraphicsSoftwareRenderer); }; diff --git a/modules/juce_graphics/native/juce_RenderingHelpers.h b/modules/juce_graphics/native/juce_RenderingHelpers.h index f58638bfd1..c22200907e 100644 --- a/modules/juce_graphics/native/juce_RenderingHelpers.h +++ b/modules/juce_graphics/native/juce_RenderingHelpers.h @@ -26,6 +26,11 @@ #ifndef __JUCE_RENDERINGHELPERS_JUCEHEADER__ #define __JUCE_RENDERINGHELPERS_JUCEHEADER__ +#if JUCE_MSVC + #pragma warning (push) + #pragma warning (disable: 4127) // "expression is constant" warning +#endif + namespace RenderingHelpers { @@ -135,6 +140,7 @@ private: }; //============================================================================== +/** Holds a cache of recently-used glyph objects of some type. */ template class GlyphCache : private DeletedAtShutdown { @@ -247,6 +253,7 @@ private: }; //============================================================================== +/** Caches a glyph as an edge-table. */ template class CachedGlyphEdgeTable { @@ -289,60 +296,9 @@ private: }; //============================================================================== -template -class SavedStateStack -{ -public: - SavedStateStack (StateObjectType* const initialState) noexcept - : currentState (initialState) - {} - - inline StateObjectType* operator->() const noexcept { return currentState; } - inline StateObjectType& operator*() const noexcept { return *currentState; } - - void save() - { - stack.add (new StateObjectType (*currentState)); - } - - void restore() - { - StateObjectType* const top = stack.getLast(); - - if (top != nullptr) - { - currentState = top; - stack.removeLast (1, false); - } - else - { - jassertfalse; // trying to pop with an empty stack! - } - } - - void beginTransparencyLayer (float opacity) - { - save(); - currentState = currentState->beginTransparencyLayer (opacity); - } - - void endTransparencyLayer() - { - const ScopedPointer finishedTransparencyLayer (currentState); - restore(); - currentState->endTransparencyLayer (*finishedTransparencyLayer); - } - -private: - ScopedPointer currentState; - OwnedArray stack; - - JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (SavedStateStack); -}; - -//============================================================================== -// Calculates the alpha values and positions for rendering the edges of a non-pixel -// aligned rectangle. +/** Calculates the alpha values and positions for rendering the edges of a + non-pixel-aligned rectangle. +*/ struct FloatRectangleRasterisingInfo { FloatRectangleRasterisingInfo (const Rectangle& area) @@ -428,6 +384,2065 @@ struct FloatRectangleRasterisingInfo 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 */ + class Linear + { + public: + Linear (const ColourGradient& gradient, const AffineTransform& transform, + const PixelARGB* const lookupTable_, const int numEntries_) + : lookupTable (lookupTable_), + numEntries (numEntries_) + { + jassert (numEntries_ >= 0); + Point p1 (gradient.point1); + Point p2 (gradient.point2); + + if (! transform.isIdentity()) + { + const Line l (p2, p1); + Point p3 = l.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 * scale); + } + else if (horizontal) + { + scale = roundToInt ((numEntries << (int) numScaleBits) / (double) (p2.x - p1.x)); + start = roundToInt (p1.x * 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 (const 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 (const int x) const noexcept + { + return vertical ? linePix + : lookupTable [jlimit (0, numEntries, (x * scale - start) >> (int) numScaleBits)]; + } + + private: + 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 */ + class Radial + { + public: + Radial (const ColourGradient& gradient, const AffineTransform&, + const PixelARGB* const lookupTable_, const int numEntries_) + : lookupTable (lookupTable_), + numEntries (numEntries_), + gx1 (gradient.point1.x), + gy1 (gradient.point1.y) + { + jassert (numEntries_ >= 0); + const Point 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 (const int y) noexcept + { + dy = y - gy1; + dy *= dy; + } + + inline PixelARGB getPixel (const int px) const noexcept + { + double x = px - gx1; + x *= x; + x += dy; + + return lookupTable [x >= maxDist ? numEntries : roundToInt (std::sqrt (x) * invScale)]; + } + + protected: + 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 */ + class TransformedRadial : public Radial + { + public: + TransformedRadial (const ColourGradient& gradient, const AffineTransform& transform, + const PixelARGB* const lookupTable_, const int numEntries_) + : Radial (gradient, transform, lookupTable_, numEntries_), + inverseTransform (transform.inverted()) + { + tM10 = inverseTransform.mat10; + tM00 = inverseTransform.mat00; + } + + forcedinline void setY (const int y) noexcept + { + lineYM01 = inverseTransform.mat01 * y + inverseTransform.mat02 - gx1; + lineYM11 = inverseTransform.mat11 * y + inverseTransform.mat12 - gy1; + } + + inline PixelARGB getPixel (const int px) const noexcept + { + double x = px; + const double y = tM10 * x + lineYM11; + x = tM00 * x + lineYM01; + x *= x; + x += y * y; + + if (x >= maxDist) + return lookupTable [numEntries]; + else + return lookupTable [jmin (numEntries, roundToInt (std::sqrt (x) * invScale))]; + } + + private: + double tM10, tM00, lineYM01, lineYM11; + const AffineTransform inverseTransform; + + JUCE_DECLARE_NON_COPYABLE (TransformedRadial); + }; } +//============================================================================== +/** Contains classes for filling edge tables with various fill types. */ +namespace EdgeTableFillers +{ + /** Fills an edge-table with a solid colour. */ + template + class SolidColour + { + public: + SolidColour (const Image::BitmapData& data_, const PixelARGB& colour) + : data (data_), + sourceColour (colour) + { + if (sizeof (PixelType) == 3) + { + areRGBComponentsEqual = sourceColour.getRed() == sourceColour.getGreen() + && sourceColour.getGreen() == sourceColour.getBlue(); + filler[0].set (sourceColour); + filler[1].set (sourceColour); + filler[2].set (sourceColour); + filler[3].set (sourceColour); + } + } + + forcedinline void setEdgeTableYPos (const int y) noexcept + { + linePixels = (PixelType*) data.getLinePointer (y); + } + + forcedinline void handleEdgeTablePixel (const int x, const int alphaLevel) const noexcept + { + if (replaceExisting) + linePixels[x].set (sourceColour); + else + linePixels[x].blend (sourceColour, (uint32) alphaLevel); + } + + forcedinline void handleEdgeTablePixelFull (const int x) const noexcept + { + if (replaceExisting) + linePixels[x].set (sourceColour); + else + linePixels[x].blend (sourceColour); + } + + forcedinline void handleEdgeTableLine (const int x, const int width, const int alphaLevel) const noexcept + { + PixelARGB p (sourceColour); + p.multiplyAlpha (alphaLevel); + + PixelType* dest = linePixels + x; + + if (replaceExisting || p.getAlpha() >= 0xff) + replaceLine (dest, p, width); + else + blendLine (dest, p, width); + } + + forcedinline void handleEdgeTableLineFull (const int x, const int width) const noexcept + { + PixelType* dest = linePixels + x; + + if (replaceExisting || sourceColour.getAlpha() >= 0xff) + replaceLine (dest, sourceColour, width); + else + blendLine (dest, sourceColour, width); + } + + private: + const Image::BitmapData& data; + PixelType* linePixels; + PixelARGB sourceColour; + PixelRGB filler [4]; + bool areRGBComponentsEqual; + + inline void blendLine (PixelType* dest, const PixelARGB& colour, int width) const noexcept + { + do { dest++ ->blend (colour); } while (--width > 0); + } + + forcedinline void replaceLine (PixelRGB* dest, const PixelARGB& colour, int width) const noexcept + { + if (areRGBComponentsEqual) // if all the component values are the same, we can cheat.. + { + memset (dest, colour.getRed(), (size_t) width * 3); + } + else + { + if (width >> 5) + { + const int* const intFiller = reinterpret_cast (filler); + + while (width > 8 && (((pointer_sized_int) dest) & 7) != 0) + { + dest->set (colour); + ++dest; + --width; + } + + while (width > 4) + { + int* d = reinterpret_cast (dest); + *d++ = intFiller[0]; + *d++ = intFiller[1]; + *d++ = intFiller[2]; + dest = reinterpret_cast (d); + width -= 4; + } + } + + while (--width >= 0) + { + dest->set (colour); + ++dest; + } + } + } + + forcedinline void replaceLine (PixelAlpha* const dest, const PixelARGB& colour, int const width) const noexcept + { + memset (dest, colour.getAlpha(), (size_t) width); + } + + forcedinline void replaceLine (PixelARGB* dest, const PixelARGB& colour, int width) const noexcept + { + do + { + dest->set (colour); + ++dest; + + } while (--width > 0); + } + + JUCE_DECLARE_NON_COPYABLE (SolidColour); + }; + + //============================================================================== + /** Fills an edge-table with a gradient. */ + template + class Gradient : public GradientType + { + public: + Gradient (const Image::BitmapData& destData_, const ColourGradient& gradient, const AffineTransform& transform, + const PixelARGB* const lookupTable_, const int numEntries_) + : GradientType (gradient, transform, lookupTable_, numEntries_ - 1), + destData (destData_) + { + } + + forcedinline void setEdgeTableYPos (const int y) noexcept + { + linePixels = (PixelType*) destData.getLinePointer (y); + GradientType::setY (y); + } + + forcedinline void handleEdgeTablePixel (const int x, const int alphaLevel) const noexcept + { + linePixels[x].blend (GradientType::getPixel (x), (uint32) alphaLevel); + } + + forcedinline void handleEdgeTablePixelFull (const int x) const noexcept + { + linePixels[x].blend (GradientType::getPixel (x)); + } + + void handleEdgeTableLine (int x, int width, const int alphaLevel) const noexcept + { + PixelType* dest = linePixels + x; + + if (alphaLevel < 0xff) + { + do + { + (dest++)->blend (GradientType::getPixel (x++), (uint32) alphaLevel); + } while (--width > 0); + } + else + { + do + { + (dest++)->blend (GradientType::getPixel (x++)); + } while (--width > 0); + } + } + + void handleEdgeTableLineFull (int x, int width) const noexcept + { + PixelType* dest = linePixels + x; + + do + { + (dest++)->blend (GradientType::getPixel (x++)); + } while (--width > 0); + } + + private: + const Image::BitmapData& destData; + PixelType* linePixels; + + JUCE_DECLARE_NON_COPYABLE (Gradient); + }; + + //============================================================================== + /** Fills an edge-table with a non-transformed image. */ + template + class ImageFill + { + public: + ImageFill (const Image::BitmapData& destData_, const Image::BitmapData& srcData_, + const int extraAlpha_, const int x, const int y) + : destData (destData_), + srcData (srcData_), + extraAlpha (extraAlpha_ + 1), + xOffset (repeatPattern ? negativeAwareModulo (x, srcData_.width) - srcData_.width : x), + yOffset (repeatPattern ? negativeAwareModulo (y, srcData_.height) - srcData_.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 (const int x, int alphaLevel) const noexcept + { + alphaLevel = (alphaLevel * extraAlpha) >> 8; + + linePixels[x].blend (sourceLineStart [repeatPattern ? ((x - xOffset) % srcData.width) : (x - xOffset)], (uint32) alphaLevel); + } + + forcedinline void handleEdgeTablePixelFull (const int x) const noexcept + { + linePixels[x].blend (sourceLineStart [repeatPattern ? ((x - xOffset) % srcData.width) : (x - xOffset)], (uint32) extraAlpha); + } + + void handleEdgeTableLine (int x, int width, int alphaLevel) const noexcept + { + DestPixelType* dest = linePixels + x; + alphaLevel = (alphaLevel * extraAlpha) >> 8; + x -= xOffset; + + jassert (repeatPattern || (x >= 0 && x + width <= srcData.width)); + + if (alphaLevel < 0xfe) + { + do + { + dest++ ->blend (sourceLineStart [repeatPattern ? (x++ % srcData.width) : x++], (uint32) alphaLevel); + } while (--width > 0); + } + else + { + if (repeatPattern) + { + do + { + dest++ ->blend (sourceLineStart [x++ % srcData.width]); + } while (--width > 0); + } + else + { + copyRow (dest, sourceLineStart + x, width); + } + } + } + + void handleEdgeTableLineFull (int x, int width) const noexcept + { + DestPixelType* dest = linePixels + x; + x -= xOffset; + + jassert (repeatPattern || (x >= 0 && x + width <= srcData.width)); + + if (extraAlpha < 0xfe) + { + do + { + dest++ ->blend (sourceLineStart [repeatPattern ? (x++ % srcData.width) : x++], (uint32) extraAlpha); + } while (--width > 0); + } + else + { + if (repeatPattern) + { + do + { + dest++ ->blend (sourceLineStart [x++ % srcData.width]); + } while (--width > 0); + } + else + { + copyRow (dest, sourceLineStart + x, width); + } + } + } + + void clipEdgeTableLine (EdgeTable& et, int x, int y, int width) + { + jassert (x - xOffset >= 0 && x + width - xOffset <= srcData.width); + SrcPixelType* s = (SrcPixelType*) srcData.getLinePointer (y - yOffset); + uint8* 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; + + template + static forcedinline void copyRow (PixelType1* dest, PixelType2* src, int width) noexcept + { + do + { + dest++ ->blend (*src++); + } while (--width > 0); + } + + static forcedinline void copyRow (PixelRGB* dest, PixelRGB* src, int width) noexcept + { + memcpy (dest, src, sizeof (PixelRGB) * (size_t) width); + } + + JUCE_DECLARE_NON_COPYABLE (ImageFill); + }; + + //============================================================================== + /** Fills an edge-table with a transformed image. */ + template + class TransformedImageFill + { + public: + TransformedImageFill (const Image::BitmapData& destData_, const Image::BitmapData& srcData_, + const AffineTransform& transform, const int extraAlpha_, const bool betterQuality_) + : interpolator (transform, + betterQuality_ ? 0.5f : 0.0f, + betterQuality_ ? -128 : 0), + destData (destData_), + srcData (srcData_), + extraAlpha (extraAlpha_ + 1), + betterQuality (betterQuality_), + maxX (srcData_.width - 1), + maxY (srcData_.height - 1), + scratchSize (2048) + { + scratchBuffer.malloc (scratchSize); + } + + forcedinline void setEdgeTableYPos (const int newY) noexcept + { + y = newY; + linePixels = (DestPixelType*) destData.getLinePointer (newY); + } + + forcedinline void handleEdgeTablePixel (const int x, const int alphaLevel) noexcept + { + SrcPixelType p; + generate (&p, x, 1); + + linePixels[x].blend (p, (uint32) (alphaLevel * extraAlpha) >> 8); + } + + forcedinline void handleEdgeTablePixelFull (const int x) noexcept + { + SrcPixelType p; + generate (&p, x, 1); + + linePixels[x].blend (p, (uint32) extraAlpha); + } + + void handleEdgeTableLine (const 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); + + DestPixelType* dest = linePixels + x; + alphaLevel *= extraAlpha; + alphaLevel >>= 8; + + if (alphaLevel < 0xfe) + { + do + { + dest++ ->blend (*span++, (uint32) alphaLevel); + } while (--width > 0); + } + else + { + do + { + dest++ ->blend (*span++); + } while (--width > 0); + } + } + + forcedinline void handleEdgeTableLineFull (const int x, int width) noexcept + { + handleEdgeTableLine (x, width, 255); + } + + void clipEdgeTableLine (EdgeTable& et, int x, int y_, int width) + { + if (width > (int) scratchSize) + { + scratchSize = (size_t) width; + scratchBuffer.malloc (scratchSize); + } + + y = y_; + generate (scratchBuffer.getData(), x, width); + + et.clipLineToMask (x, y_, + reinterpret_cast (scratchBuffer.getData()) + SrcPixelType::indexA, + sizeof (SrcPixelType), width); + } + + private: + //============================================================================== + template + void generate (PixelType* dest, const int x, int numPixels) noexcept + { + this->interpolator.setStartOfLine ((float) x, (float) y, 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 (betterQuality) + { + 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; + } + else 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* const dest, const uint8* src, const int subPixelX, const int subPixelY) noexcept + { + uint32 c[4] = { 256 * 128, 256 * 128, 256 * 128, 256 * 128 }; + + uint32 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]; + + weight = (uint32) (subPixelX * (256 - subPixelY)); + c[0] += weight * src[4]; + c[1] += weight * src[5]; + c[2] += weight * src[6]; + c[3] += weight * src[7]; + + src += this->srcData.lineStride; + + 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]; + + weight = (uint32) (subPixelX * subPixelY); + c[0] += weight * src[4]; + c[1] += weight * src[5]; + c[2] += weight * src[6]; + c[3] += weight * src[7]; + + 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* const dest, const uint8* src, const 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]; + + weight = subPixelX; + c[0] += weight * src[4]; + c[1] += weight * src[5]; + c[2] += weight * src[6]; + c[3] += weight * src[7]; + + 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* const dest, const uint8* src, const 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* const dest, const uint8* src, const uint32 subPixelX, const 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]; + + weight = subPixelX * (256 - subPixelY); + c[0] += weight * src[3]; + c[1] += weight * src[4]; + c[2] += weight * src[5]; + + src += this->srcData.lineStride; + + weight = (256 - subPixelX) * subPixelY; + c[0] += weight * src[0]; + c[1] += weight * src[1]; + c[2] += weight * src[2]; + + weight = subPixelX * subPixelY; + c[0] += weight * src[3]; + c[1] += weight * src[4]; + c[2] += weight * src[5]; + + dest->setARGB ((uint8) 255, + (uint8) (c[PixelRGB::indexR] >> 16), + (uint8) (c[PixelRGB::indexG] >> 16), + (uint8) (c[PixelRGB::indexB] >> 16)); + } + + void render2PixelAverageX (PixelRGB* const dest, const uint8* src, const 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]; + + c[0] += subPixelX * src[3]; + c[1] += subPixelX * src[4]; + c[2] += subPixelX * src[5]; + + dest->setARGB ((uint8) 255, + (uint8) (c[PixelRGB::indexR] >> 8), + (uint8) (c[PixelRGB::indexG] >> 8), + (uint8) (c[PixelRGB::indexB] >> 8)); + } + + void render2PixelAverageY (PixelRGB* const dest, const uint8* src, const 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* const dest, const uint8* src, const uint32 subPixelX, const uint32 subPixelY) noexcept + { + uint32 c = 256 * 128; + c += src[0] * ((256 - subPixelX) * (256 - subPixelY)); + c += src[1] * (subPixelX * (256 - subPixelY)); + src += this->srcData.lineStride; + c += src[0] * ((256 - subPixelX) * subPixelY); + c += src[1] * (subPixelX * subPixelY); + + *((uint8*) dest) = (uint8) (c >> 16); + } + + void render2PixelAverageX (PixelAlpha* const dest, const uint8* src, const uint32 subPixelX) noexcept + { + uint32 c = 128; + c += src[0] * (256 - subPixelX); + c += src[1] * subPixelX; + *((uint8*) dest) = (uint8) (c >> 8); + } + + void render2PixelAverageY (PixelAlpha* const dest, const uint8* src, const uint32 subPixelY) noexcept + { + uint32 c = 128; + c += src[0] * (256 - subPixelY); + src += this->srcData.lineStride; + c += src[0] * subPixelY; + *((uint8*) dest) = (uint8) (c >> 8); + } + + //============================================================================== + class TransformedImageSpanInterpolator + { + public: + TransformedImageSpanInterpolator (const AffineTransform& transform, + const float pixelOffset_, const int pixelOffsetInt_) noexcept + : inverseTransform (transform.inverted()), + pixelOffset (pixelOffset_), pixelOffsetInt (pixelOffsetInt_) + {} + + void setStartOfLine (float x, float y, const int numPixels) noexcept + { + jassert (numPixels > 0); + + x += pixelOffset; + y += pixelOffset; + float x1 = x, y1 = y; + x += numPixels; + inverseTransform.transformPoints (x1, y1, x, y); + + xBresenham.set ((int) (x1 * 256.0f), (int) (x * 256.0f), numPixels, pixelOffsetInt); + yBresenham.set ((int) (y1 * 256.0f), (int) (y * 256.0f), numPixels, pixelOffsetInt); + } + + void next (int& x, int& y) noexcept + { + x = xBresenham.n; xBresenham.stepToNext(); + y = yBresenham.n; yBresenham.stepToNext(); + } + + private: + class BresenhamInterpolator + { + public: + BresenhamInterpolator() noexcept {} + + void set (const int n1, const int n2, const int numSteps_, const int pixelOffsetInt) noexcept + { + numSteps = numSteps_; + step = (n2 - n1) / numSteps; + remainder = modulo = (n2 - n1) % numSteps; + n = n1 + pixelOffsetInt; + + 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 bool betterQuality; + const int maxX, maxY; + int y; + DestPixelType* linePixels; + HeapBlock scratchBuffer; + size_t scratchSize; + + JUCE_DECLARE_NON_COPYABLE (TransformedImageFill); + }; + + + //============================================================================== + template + void renderImageTransformed (Iterator& iter, const Image::BitmapData& destData, const Image::BitmapData& srcData, + const int alpha, const AffineTransform& transform, bool betterQuality, bool tiledFill) + { + switch (destData.pixelFormat) + { + case Image::ARGB: + switch (srcData.pixelFormat) + { + case Image::ARGB: + if (tiledFill) { TransformedImageFill r (destData, srcData, transform, alpha, betterQuality); iter.iterate (r); } + else { TransformedImageFill r (destData, srcData, transform, alpha, betterQuality); iter.iterate (r); } + break; + case Image::RGB: + if (tiledFill) { TransformedImageFill r (destData, srcData, transform, alpha, betterQuality); iter.iterate (r); } + else { TransformedImageFill r (destData, srcData, transform, alpha, betterQuality); iter.iterate (r); } + break; + default: + if (tiledFill) { TransformedImageFill r (destData, srcData, transform, alpha, betterQuality); iter.iterate (r); } + else { TransformedImageFill r (destData, srcData, transform, alpha, betterQuality); iter.iterate (r); } + break; + } + break; + + case Image::RGB: + switch (srcData.pixelFormat) + { + case Image::ARGB: + if (tiledFill) { TransformedImageFill r (destData, srcData, transform, alpha, betterQuality); iter.iterate (r); } + else { TransformedImageFill r (destData, srcData, transform, alpha, betterQuality); iter.iterate (r); } + break; + case Image::RGB: + if (tiledFill) { TransformedImageFill r (destData, srcData, transform, alpha, betterQuality); iter.iterate (r); } + else { TransformedImageFill r (destData, srcData, transform, alpha, betterQuality); iter.iterate (r); } + break; + default: + if (tiledFill) { TransformedImageFill r (destData, srcData, transform, alpha, betterQuality); iter.iterate (r); } + else { TransformedImageFill r (destData, srcData, transform, alpha, betterQuality); iter.iterate (r); } + break; + } + break; + + default: + switch (srcData.pixelFormat) + { + case Image::ARGB: + if (tiledFill) { TransformedImageFill r (destData, srcData, transform, alpha, betterQuality); iter.iterate (r); } + else { TransformedImageFill r (destData, srcData, transform, alpha, betterQuality); iter.iterate (r); } + break; + case Image::RGB: + if (tiledFill) { TransformedImageFill r (destData, srcData, transform, alpha, betterQuality); iter.iterate (r); } + else { TransformedImageFill r (destData, srcData, transform, alpha, betterQuality); iter.iterate (r); } + break; + default: + if (tiledFill) { TransformedImageFill r (destData, srcData, transform, alpha, betterQuality); iter.iterate (r); } + else { TransformedImageFill r (destData, srcData, transform, alpha, betterQuality); iter.iterate (r); } + break; + } + break; + } + } + + template + void renderImageUntransformed (Iterator& iter, const Image::BitmapData& destData, const Image::BitmapData& srcData, const 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; + 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; + 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; + + 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; + 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, const PixelARGB& fillColour, const bool replaceContents, DestPixelType*) + { + jassert (destData.pixelStride == sizeof (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* const lookupTable, const int numLookupEntries, const bool isIdentity, DestPixelType*) + { + jassert (destData.pixelStride == sizeof (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); + } + } +} + +//============================================================================== +namespace ClipRegions +{ + class Base : public SingleThreadedReferenceCountedObject + { + public: + Base() {} + virtual ~Base() {} + + typedef ReferenceCountedObjectPtr Ptr; + + virtual Ptr clone() const = 0; + virtual Ptr applyClipTo (const Ptr& target) const = 0; + + virtual Ptr clipToRectangle (const Rectangle&) = 0; + virtual Ptr clipToRectangleList (const RectangleList&) = 0; + virtual Ptr excludeClipRectangle (const Rectangle&) = 0; + virtual Ptr clipToPath (const Path&, const AffineTransform&) = 0; + virtual Ptr clipToEdgeTable (const EdgeTable& et) = 0; + virtual Ptr clipToImageAlpha (const Image&, const AffineTransform&, const bool betterQuality) = 0; + virtual void translate (const Point& delta) = 0; + + virtual bool clipRegionIntersects (const Rectangle&) const = 0; + virtual Rectangle getClipBounds() const = 0; + + virtual void fillRectWithColour (Image::BitmapData& destData, const Rectangle&, const PixelARGB& colour, bool replaceContents) const = 0; + virtual void fillRectWithColour (Image::BitmapData& destData, const Rectangle&, const PixelARGB& colour) const = 0; + virtual void fillAllWithColour (Image::BitmapData& destData, const PixelARGB& colour, bool replaceContents) const = 0; + virtual void fillAllWithGradient (Image::BitmapData& destData, ColourGradient&, const AffineTransform&, bool isIdentity) const = 0; + virtual void renderImageTransformed (const Image::BitmapData& destData, const Image::BitmapData& srcData, const int alpha, const AffineTransform&, bool betterQuality, bool tiledFill) const = 0; + virtual void renderImageUntransformed (const Image::BitmapData& destData, const Image::BitmapData& srcData, const int alpha, int x, int y, bool tiledFill) const = 0; + }; + + //============================================================================== + class EdgeTableRegion : public Base + { + public: + EdgeTableRegion (const EdgeTable& e) : edgeTable (e) {} + EdgeTableRegion (const Rectangle& r) : edgeTable (r) {} + EdgeTableRegion (const Rectangle& r) : edgeTable (r) {} + EdgeTableRegion (const RectangleList& r) : edgeTable (r) {} + EdgeTableRegion (const Rectangle& bounds, const Path& p, const AffineTransform& t) : edgeTable (bounds, p, t) {} + EdgeTableRegion (const EdgeTableRegion& other) : edgeTable (other.edgeTable) {} + + Ptr clone() const { return new EdgeTableRegion (*this); } + Ptr applyClipTo (const Ptr& target) const { return target->clipToEdgeTable (edgeTable); } + + Ptr clipToRectangle (const Rectangle& r) + { + edgeTable.clipToRectangle (r); + return edgeTable.isEmpty() ? nullptr : this; + } + + Ptr clipToRectangleList (const RectangleList& r) + { + RectangleList inverse (edgeTable.getMaximumBounds()); + + if (inverse.subtract (r)) + for (RectangleList::Iterator iter (inverse); iter.next();) + edgeTable.excludeRectangle (*iter.getRectangle()); + + return edgeTable.isEmpty() ? nullptr : this; + } + + Ptr excludeClipRectangle (const Rectangle& r) + { + edgeTable.excludeRectangle (r); + return edgeTable.isEmpty() ? nullptr : this; + } + + Ptr clipToPath (const Path& p, const AffineTransform& transform) + { + EdgeTable et (edgeTable.getMaximumBounds(), p, transform); + edgeTable.clipToEdgeTable (et); + return edgeTable.isEmpty() ? nullptr : this; + } + + Ptr clipToEdgeTable (const EdgeTable& et) + { + edgeTable.clipToEdgeTable (et); + return edgeTable.isEmpty() ? nullptr : this; + } + + Ptr clipToImageAlpha (const Image& image, const AffineTransform& transform, const bool betterQuality) + { + const Image::BitmapData srcData (image, Image::BitmapData::readOnly); + + if (transform.isOnlyTranslation()) + { + // If our translation doesn't involve any distortion, just use a simple blit.. + const int tx = (int) (transform.getTranslationX() * 256.0f); + const int ty = (int) (transform.getTranslationY() * 256.0f); + + if ((! betterQuality) || ((tx | ty) & 224) == 0) + { + const int imageX = ((tx + 128) >> 8); + const int imageY = ((ty + 128) >> 8); + + if (image.getFormat() == Image::ARGB) + straightClipImage (srcData, imageX, imageY, (PixelARGB*) 0); + else + straightClipImage (srcData, imageX, imageY, (PixelAlpha*) 0); + + return edgeTable.isEmpty() ? nullptr : this; + } + } + + if (transform.isSingularity()) + return nullptr; + + { + 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, betterQuality, (PixelARGB*) 0); + else + transformedClipImage (srcData, transform, betterQuality, (PixelAlpha*) 0); + } + + return edgeTable.isEmpty() ? nullptr : this; + } + + void translate (const Point& delta) + { + edgeTable.translate ((float) delta.x, delta.y); + } + + bool clipRegionIntersects (const Rectangle& r) const + { + return edgeTable.getMaximumBounds().intersects (r); + } + + Rectangle getClipBounds() const + { + return edgeTable.getMaximumBounds(); + } + + void fillRectWithColour (Image::BitmapData& destData, const Rectangle& area, const PixelARGB& colour, bool replaceContents) const + { + const Rectangle totalClip (edgeTable.getMaximumBounds()); + const Rectangle clipped (totalClip.getIntersection (area)); + + if (! clipped.isEmpty()) + { + EdgeTableRegion et (clipped); + et.edgeTable.clipToEdgeTable (edgeTable); + et.fillAllWithColour (destData, colour, replaceContents); + } + } + + void fillRectWithColour (Image::BitmapData& destData, const Rectangle& area, const PixelARGB& colour) const + { + const Rectangle totalClip (edgeTable.getMaximumBounds().toFloat()); + const Rectangle clipped (totalClip.getIntersection (area)); + + if (! clipped.isEmpty()) + { + EdgeTableRegion et (clipped); + et.edgeTable.clipToEdgeTable (edgeTable); + et.fillAllWithColour (destData, colour, false); + } + } + + void fillAllWithColour (Image::BitmapData& destData, const PixelARGB& colour, bool replaceContents) const + { + switch (destData.pixelFormat) + { + case Image::ARGB: EdgeTableFillers::renderSolidFill (edgeTable, destData, colour, replaceContents, (PixelARGB*) 0); break; + case Image::RGB: EdgeTableFillers::renderSolidFill (edgeTable, destData, colour, replaceContents, (PixelRGB*) 0); break; + default: EdgeTableFillers::renderSolidFill (edgeTable, destData, colour, replaceContents, (PixelAlpha*) 0); break; + } + } + + void fillAllWithGradient (Image::BitmapData& destData, ColourGradient& gradient, const AffineTransform& transform, bool isIdentity) const + { + HeapBlock lookupTable; + const int numLookupEntries = gradient.createLookupTable (transform, lookupTable); + jassert (numLookupEntries > 0); + + switch (destData.pixelFormat) + { + case Image::ARGB: EdgeTableFillers::renderGradient (edgeTable, destData, gradient, transform, lookupTable, numLookupEntries, isIdentity, (PixelARGB*) 0); break; + case Image::RGB: EdgeTableFillers::renderGradient (edgeTable, destData, gradient, transform, lookupTable, numLookupEntries, isIdentity, (PixelRGB*) 0); break; + default: EdgeTableFillers::renderGradient (edgeTable, destData, gradient, transform, lookupTable, numLookupEntries, isIdentity, (PixelAlpha*) 0); break; + } + } + + void renderImageTransformed (const Image::BitmapData& destData, const Image::BitmapData& srcData, const int alpha, const AffineTransform& transform, bool betterQuality, bool tiledFill) const + { + EdgeTableFillers::renderImageTransformed (edgeTable, destData, srcData, alpha, transform, betterQuality, tiledFill); + } + + void renderImageUntransformed (const Image::BitmapData& destData, const Image::BitmapData& srcData, const int alpha, int x, int y, bool tiledFill) const + { + EdgeTableFillers::renderImageUntransformed (edgeTable, destData, srcData, alpha, x, y, tiledFill); + } + + EdgeTable edgeTable; + + private: + //============================================================================== + template + void transformedClipImage (const Image::BitmapData& srcData, const AffineTransform& transform, const bool betterQuality, const SrcPixelType*) + { + EdgeTableFillers::TransformedImageFill renderer (srcData, srcData, transform, 255, betterQuality); + + 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()); + } + + EdgeTableRegion& operator= (const EdgeTableRegion&); + }; + + //============================================================================== + class RectangleListRegion : public Base + { + public: + RectangleListRegion (const Rectangle& r) : clip (r) {} + RectangleListRegion (const RectangleList& r) : clip (r) {} + RectangleListRegion (const RectangleListRegion& other) : clip (other.clip) {} + + Ptr clone() const { return new RectangleListRegion (*this); } + Ptr applyClipTo (const Ptr& target) const { return target->clipToRectangleList (clip); } + + Ptr clipToRectangle (const Rectangle& r) + { + clip.clipTo (r); + return clip.isEmpty() ? nullptr : this; + } + + Ptr clipToRectangleList (const RectangleList& r) + { + clip.clipTo (r); + return clip.isEmpty() ? nullptr : this; + } + + Ptr excludeClipRectangle (const Rectangle& r) + { + clip.subtract (r); + return clip.isEmpty() ? nullptr : this; + } + + Ptr clipToPath (const Path& p, const AffineTransform& transform) { return toEdgeTable()->clipToPath (p, transform); } + Ptr clipToEdgeTable (const EdgeTable& et) { return toEdgeTable()->clipToEdgeTable (et); } + + Ptr clipToImageAlpha (const Image& image, const AffineTransform& transform, const bool betterQuality) + { + return toEdgeTable()->clipToImageAlpha (image, transform, betterQuality); + } + + void translate (const Point& delta) + { + clip.offsetAll (delta.x, delta.y); + } + + bool clipRegionIntersects (const Rectangle& r) const { return clip.intersects (r); } + Rectangle getClipBounds() const { return clip.getBounds(); } + + void fillRectWithColour (Image::BitmapData& destData, const Rectangle& area, const PixelARGB& colour, bool replaceContents) const + { + SubRectangleIterator iter (clip, area); + + switch (destData.pixelFormat) + { + case Image::ARGB: EdgeTableFillers::renderSolidFill (iter, destData, colour, replaceContents, (PixelARGB*) 0); break; + case Image::RGB: EdgeTableFillers::renderSolidFill (iter, destData, colour, replaceContents, (PixelRGB*) 0); break; + default: EdgeTableFillers::renderSolidFill (iter, destData, colour, replaceContents, (PixelAlpha*) 0); break; + } + } + + void fillRectWithColour (Image::BitmapData& destData, const Rectangle& area, const PixelARGB& colour) const + { + SubRectangleIteratorFloat iter (clip, area); + + switch (destData.pixelFormat) + { + case Image::ARGB: EdgeTableFillers::renderSolidFill (iter, destData, colour, false, (PixelARGB*) 0); break; + case Image::RGB: EdgeTableFillers::renderSolidFill (iter, destData, colour, false, (PixelRGB*) 0); break; + default: EdgeTableFillers::renderSolidFill (iter, destData, colour, false, (PixelAlpha*) 0); break; + } + } + + void fillAllWithColour (Image::BitmapData& destData, const PixelARGB& colour, bool replaceContents) const + { + switch (destData.pixelFormat) + { + case Image::ARGB: EdgeTableFillers::renderSolidFill (*this, destData, colour, replaceContents, (PixelARGB*) 0); break; + case Image::RGB: EdgeTableFillers::renderSolidFill (*this, destData, colour, replaceContents, (PixelRGB*) 0); break; + default: EdgeTableFillers::renderSolidFill (*this, destData, colour, replaceContents, (PixelAlpha*) 0); break; + } + } + + void fillAllWithGradient (Image::BitmapData& destData, ColourGradient& gradient, const AffineTransform& transform, bool isIdentity) const + { + HeapBlock lookupTable; + const int numLookupEntries = gradient.createLookupTable (transform, lookupTable); + jassert (numLookupEntries > 0); + + switch (destData.pixelFormat) + { + case Image::ARGB: EdgeTableFillers::renderGradient (*this, destData, gradient, transform, lookupTable, numLookupEntries, isIdentity, (PixelARGB*) 0); break; + case Image::RGB: EdgeTableFillers::renderGradient (*this, destData, gradient, transform, lookupTable, numLookupEntries, isIdentity, (PixelRGB*) 0); break; + default: EdgeTableFillers::renderGradient (*this, destData, gradient, transform, lookupTable, numLookupEntries, isIdentity, (PixelAlpha*) 0); break; + } + } + + void renderImageTransformed (const Image::BitmapData& destData, const Image::BitmapData& srcData, const int alpha, const AffineTransform& transform, bool betterQuality, bool tiledFill) const + { + EdgeTableFillers::renderImageTransformed (*this, destData, srcData, alpha, transform, betterQuality, tiledFill); + } + + void renderImageUntransformed (const Image::BitmapData& destData, const Image::BitmapData& srcData, const int alpha, int x, int y, bool tiledFill) const + { + EdgeTableFillers::renderImageUntransformed (*this, destData, srcData, alpha, x, y, tiledFill); + } + + RectangleList clip; + + //============================================================================== + template + void iterate (Renderer& r) const noexcept + { + RectangleList::Iterator iter (clip); + + while (iter.next()) + { + const Rectangle rect (*iter.getRectangle()); + const int x = rect.getX(); + const int w = rect.getWidth(); + jassert (w > 0); + const int bottom = rect.getBottom(); + + for (int y = rect.getY(); y < bottom; ++y) + { + r.setEdgeTableYPos (y); + r.handleEdgeTableLineFull (x, w); + } + } + } + + private: + //============================================================================== + class SubRectangleIterator + { + public: + SubRectangleIterator (const RectangleList& clip_, const Rectangle& area_) + : clip (clip_), area (area_) + {} + + template + void iterate (Renderer& r) const noexcept + { + RectangleList::Iterator iter (clip); + + while (iter.next()) + { + const Rectangle rect (iter.getRectangle()->getIntersection (area)); + + if (! rect.isEmpty()) + { + const int x = rect.getX(); + const int w = rect.getWidth(); + const int bottom = rect.getBottom(); + + for (int y = rect.getY(); y < bottom; ++y) + { + r.setEdgeTableYPos (y); + r.handleEdgeTableLineFull (x, w); + } + } + } + } + + private: + const RectangleList& clip; + const Rectangle area; + + JUCE_DECLARE_NON_COPYABLE (SubRectangleIterator); + }; + + //============================================================================== + class SubRectangleIteratorFloat + { + public: + SubRectangleIteratorFloat (const RectangleList& clip_, const Rectangle& area_) noexcept + : clip (clip_), area (area_) + { + } + + template + void iterate (Renderer& r) const noexcept + { + const RenderingHelpers::FloatRectangleRasterisingInfo f (area); + RectangleList::Iterator iter (clip); + + while (iter.next()) + { + const int clipLeft = iter.getRectangle()->getX(); + const int clipRight = iter.getRectangle()->getRight(); + const int clipTop = iter.getRectangle()->getY(); + const int clipBottom = iter.getRectangle()->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); + } + + const int endY = jmin (f.bottom, clipBottom); + for (int y = jmax (clipTop, f.top); y < endY; ++y) + { + r.setEdgeTableYPos (y); + r.handleEdgeTablePixelFull (f.left); + } + + if (f.bottomAlpha != 0 && f.bottom < clipBottom) + { + r.setEdgeTableYPos (f.bottom); + r.handleEdgeTablePixel (f.left, f.bottomAlpha); + } + } + else + { + const int clippedLeft = jmax (f.left, clipLeft); + const int clippedWidth = jmin (f.right, clipRight) - clippedLeft; + const bool doLeftAlpha = f.leftAlpha != 0 && f.totalLeft >= clipLeft; + const 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()); + } + + const int endY = jmin (f.bottom, clipBottom); + for (int y = jmax (clipTop, f.top); y < endY; ++y) + { + r.setEdgeTableYPos (y); + + if (doLeftAlpha) r.handleEdgeTablePixel (f.totalLeft, f.leftAlpha); + if (clippedWidth > 0) r.handleEdgeTableLineFull (clippedLeft, clippedWidth); + if (doRightAlpha) r.handleEdgeTablePixel (f.right, 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; + const Rectangle& area; + + JUCE_DECLARE_NON_COPYABLE (SubRectangleIteratorFloat); + }; + + inline Ptr toEdgeTable() const { return new EdgeTableRegion (clip); } + + RectangleListRegion& operator= (const RectangleListRegion&); + }; +} + +//============================================================================== +class SoftwareRendererSavedState +{ +public: + SoftwareRendererSavedState (const Image& image_, const Rectangle& clip_) + : image (image_), clip (new ClipRegions::RectangleListRegion (clip_)), + transform (0, 0), + interpolationQuality (Graphics::mediumResamplingQuality), + transparencyLayerAlpha (1.0f) + { + } + + SoftwareRendererSavedState (const Image& image_, const RectangleList& clip_, const int xOffset_, const int yOffset_) + : image (image_), clip (new ClipRegions::RectangleListRegion (clip_)), + transform (xOffset_, yOffset_), + interpolationQuality (Graphics::mediumResamplingQuality), + transparencyLayerAlpha (1.0f) + { + } + + SoftwareRendererSavedState (const SoftwareRendererSavedState& other) + : image (other.image), clip (other.clip), transform (other.transform), + font (other.font), fillType (other.fillType), + interpolationQuality (other.interpolationQuality), + transparencyLayerAlpha (other.transparencyLayerAlpha) + { + } + + bool clipToRectangle (const Rectangle& r) + { + if (clip != nullptr) + { + if (transform.isOnlyTranslated) + { + cloneClipIfMultiplyReferenced(); + clip = clip->clipToRectangle (transform.translated (r)); + } + else + { + Path p; + p.addRectangle (r); + clipToPath (p, AffineTransform::identity); + } + } + + return clip != nullptr; + } + + bool clipToRectangleList (const RectangleList& r) + { + if (clip != nullptr) + { + if (transform.isOnlyTranslated) + { + cloneClipIfMultiplyReferenced(); + RectangleList offsetList (r); + offsetList.offsetAll (transform.xOffset, transform.yOffset); + clip = clip->clipToRectangleList (offsetList); + } + else + { + clipToPath (r.toPath(), AffineTransform::identity); + } + } + + return clip != nullptr; + } + + bool excludeClipRectangle (const Rectangle& r) + { + if (clip != nullptr) + { + cloneClipIfMultiplyReferenced(); + + if (transform.isOnlyTranslated) + { + clip = clip->excludeClipRectangle (transform.translated (r)); + } + else + { + Path p; + p.addRectangle (r.toFloat()); + p.applyTransform (transform.complexTransform); + p.addRectangle (clip->getClipBounds().toFloat()); + p.setUsingNonZeroWinding (false); + clip = clip->clipToPath (p, AffineTransform::identity); + } + } + + 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 != Graphics::lowResamplingQuality); + } + else + { + Path p; + p.addRectangle (sourceImage.getBounds()); + clipToPath (p, t); + } + } + } + + bool clipRegionIntersects (const Rectangle& r) const + { + if (clip != nullptr) + { + if (transform.isOnlyTranslated) + return clip->clipRegionIntersects (transform.translated (r)); + else + return getClipBounds().intersects (r); + } + + return false; + } + + Rectangle getClipBounds() const + { + return clip != nullptr ? transform.deviceSpaceToUserSpace (clip->getClipBounds()) + : Rectangle(); + } + + SoftwareRendererSavedState* beginTransparencyLayer (float opacity) + { + SoftwareRendererSavedState* s = new SoftwareRendererSavedState (*this); + + if (clip != nullptr) + { + const Rectangle layerBounds (clip->getClipBounds()); + + s->image = Image (Image::ARGB, layerBounds.getWidth(), layerBounds.getHeight(), true); + s->transparencyLayerAlpha = opacity; + s->transform.moveOriginInDeviceSpace (-layerBounds.getX(), -layerBounds.getY()); + + s->cloneClipIfMultiplyReferenced(); + s->clip->translate (-layerBounds.getPosition()); + } + + return s; + } + + void endTransparencyLayer (SoftwareRendererSavedState& finishedLayerState) + { + if (clip != nullptr) + { + const Rectangle layerBounds (clip->getClipBounds()); + + const ScopedPointer g (image.createLowLevelContext()); + g->setOpacity (finishedLayerState.transparencyLayerAlpha); + g->drawImage (finishedLayerState.image, AffineTransform::translation ((float) layerBounds.getX(), + (float) layerBounds.getY())); + } + } + + //============================================================================== + void fillRect (const Rectangle& r, const bool replaceContents) + { + if (clip != nullptr) + { + if (transform.isOnlyTranslated) + { + if (fillType.isColour()) + { + Image::BitmapData destData (image, Image::BitmapData::readWrite); + clip->fillRectWithColour (destData, transform.translated (r), fillType.colour.getPixelARGB(), replaceContents); + } + else + { + const Rectangle totalClip (clip->getClipBounds()); + const Rectangle clipped (totalClip.getIntersection (transform.translated (r))); + + if (! clipped.isEmpty()) + fillShape (new ClipRegions::RectangleListRegion (clipped), false); + } + } + else + { + Path p; + p.addRectangle (r); + fillPath (p, AffineTransform::identity); + } + } + } + + void fillRect (const Rectangle& r) + { + if (clip != nullptr) + { + if (transform.isOnlyTranslated) + { + if (fillType.isColour()) + { + Image::BitmapData destData (image, Image::BitmapData::readWrite); + clip->fillRectWithColour (destData, transform.translated (r), fillType.colour.getPixelARGB()); + } + else + { + const Rectangle totalClip (clip->getClipBounds().toFloat()); + const Rectangle clipped (totalClip.getIntersection (transform.translated (r))); + + if (! clipped.isEmpty()) + fillShape (new ClipRegions::EdgeTableRegion (clipped), false); + } + } + else + { + Path p; + p.addRectangle (r); + fillPath (p, AffineTransform::identity); + } + } + } + + void fillPath (const Path& path, const AffineTransform& t) + { + if (clip != nullptr) + fillShape (new ClipRegions::EdgeTableRegion (clip->getClipBounds(), path, transform.getTransformWith (t)), false); + } + + void fillEdgeTable (const EdgeTable& edgeTable, const float x, const int y) + { + jassert (transform.isOnlyTranslated); + + if (clip != nullptr) + { + ClipRegions::EdgeTableRegion* edgeTableClip = new ClipRegions::EdgeTableRegion (edgeTable); + edgeTableClip->edgeTable.translate (x + transform.xOffset, + y + transform.yOffset); + fillShape (edgeTableClip, false); + } + } + + void drawGlyph (const Font& f, int glyphNumber, const AffineTransform& t) + { + if (clip != nullptr) + { + const ScopedPointer et (f.getTypeface()->getEdgeTableForGlyph (glyphNumber, transform.getTransformWith (t))); + + if (et != nullptr) + fillShape (new ClipRegions::EdgeTableRegion (*et), false); + } + } + + void fillShape (ClipRegions::Base::Ptr shapeToFill, const bool replaceContents) + { + jassert (clip != nullptr); + + shapeToFill = clip->applyClipTo (shapeToFill); + + if (shapeToFill != nullptr) + { + Image::BitmapData destData (image, Image::BitmapData::readWrite); + + if (fillType.isGradient()) + { + jassert (! replaceContents); // that option is just for solid colours + + ColourGradient g2 (*(fillType.gradient)); + g2.multiplyOpacity (fillType.getOpacity()); + AffineTransform t (transform.getTransformWith (fillType.transform).translated (-0.5f, -0.5f)); + + const 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 = AffineTransform::identity; + } + + shapeToFill->fillAllWithGradient (destData, g2, t, isIdentity); + } + else if (fillType.isTiledImage()) + { + renderImage (fillType.image, fillType.transform, shapeToFill); + } + else + { + shapeToFill->fillAllWithColour (destData, fillType.colour.getPixelARGB(), replaceContents); + } + } + } + + //============================================================================== + void renderImage (const Image& sourceImage, const AffineTransform& trans, + const ClipRegions::Base* const tiledFillClipRegion) + { + const AffineTransform t (transform.getTransformWith (trans)); + + const Image::BitmapData destData (image, Image::BitmapData::readWrite); + const Image::BitmapData srcData (sourceImage, Image::BitmapData::readOnly); + const int alpha = fillType.colour.getAlpha(); + const bool betterQuality = (interpolationQuality != Graphics::lowResamplingQuality); + + if (t.isOnlyTranslation()) + { + // If our translation doesn't involve any distortion, just use a simple blit.. + int tx = (int) (t.getTranslationX() * 256.0f); + int ty = (int) (t.getTranslationY() * 256.0f); + + if ((! betterQuality) || ((tx | ty) & 224) == 0) + { + tx = ((tx + 128) >> 8); + ty = ((ty + 128) >> 8); + + if (tiledFillClipRegion != nullptr) + { + tiledFillClipRegion->renderImageUntransformed (destData, srcData, alpha, tx, ty, true); + } + else + { + Rectangle area (tx, ty, sourceImage.getWidth(), sourceImage.getHeight()); + area = area.getIntersection (image.getBounds()); + + if (! area.isEmpty()) + { + ClipRegions::Base::Ptr c (clip->applyClipTo (new ClipRegions::EdgeTableRegion (area))); + + if (c != nullptr) + c->renderImageUntransformed (destData, srcData, alpha, tx, ty, false); + } + } + + return; + } + } + + if (! t.isSingularity()) + { + if (tiledFillClipRegion != nullptr) + { + tiledFillClipRegion->renderImageTransformed (destData, srcData, alpha, t, betterQuality, true); + } + else + { + Path p; + p.addRectangle (sourceImage.getBounds()); + + ClipRegions::Base::Ptr c (clip->clone()); + c = c->clipToPath (p, t); + + if (c != nullptr) + c->renderImageTransformed (destData, srcData, alpha, t, betterQuality, false); + } + } + } + + //============================================================================== + Image image; + ClipRegions::Base::Ptr clip; + RenderingHelpers::TranslationOrTransform transform; + Font font; + FillType fillType; + Graphics::ResamplingQuality interpolationQuality; + +private: + float transparencyLayerAlpha; + + void cloneClipIfMultiplyReferenced() + { + if (clip->getReferenceCount() > 1) + clip = clip->clone(); + } + + SoftwareRendererSavedState& operator= (const SoftwareRendererSavedState&); +}; + +//============================================================================== +template +class SavedStateStack +{ +public: + SavedStateStack (StateObjectType* const initialState) noexcept + : currentState (initialState) + {} + + inline StateObjectType* operator->() const noexcept { return currentState; } + inline StateObjectType& operator*() const noexcept { return *currentState; } + + void save() + { + stack.add (new StateObjectType (*currentState)); + } + + void restore() + { + StateObjectType* const top = stack.getLast(); + + if (top != nullptr) + { + currentState = top; + stack.removeLast (1, false); + } + else + { + jassertfalse; // trying to pop with an empty stack! + } + } + + void beginTransparencyLayer (float opacity) + { + save(); + currentState = currentState->beginTransparencyLayer (opacity); + } + + void endTransparencyLayer() + { + const ScopedPointer finishedTransparencyLayer (currentState); + restore(); + currentState->endTransparencyLayer (*finishedTransparencyLayer); + } + +private: + ScopedPointer currentState; + OwnedArray stack; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (SavedStateStack); +}; + +} + +#if JUCE_MSVC + #pragma warning (pop) +#endif + #endif // __JUCE_RENDERINGHELPERS_JUCEHEADER__