1
0
Fork 0
mirror of https://github.com/juce-framework/JUCE.git synced 2026-01-14 00:14:18 +00:00
JUCE/src/gui/graphics/contexts/juce_LowLevelGraphicsSoftwareRenderer.cpp

2246 lines
76 KiB
C++

/*
==============================================================================
This file is part of the JUCE library - "Jules' Utility Class Extensions"
Copyright 2004-9 by Raw Material Software Ltd.
------------------------------------------------------------------------------
JUCE can be redistributed and/or modified under the terms of the GNU General
Public License (Version 2), as published by the Free Software Foundation.
A copy of the license is included in the JUCE distribution, or can be found
online at www.gnu.org/licenses.
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
------------------------------------------------------------------------------
To release a closed-source product which uses JUCE, commercial licenses are
available: visit www.rawmaterialsoftware.com/juce for more information.
==============================================================================
*/
#include "../../../core/juce_StandardHeader.h"
BEGIN_JUCE_NAMESPACE
#include "juce_LowLevelGraphicsSoftwareRenderer.h"
#include "juce_EdgeTable.h"
#include "../imaging/juce_Image.h"
#include "../colour/juce_PixelFormats.h"
#include "../geometry/juce_PathStrokeType.h"
#include "../geometry/juce_Rectangle.h"
#include "../../../core/juce_SystemStats.h"
#include "../../../utilities/juce_DeletedAtShutdown.h"
#if (JUCE_WINDOWS || JUCE_LINUX) && ! JUCE_64BIT
#define JUCE_USE_SSE_INSTRUCTIONS 1
#endif
#if JUCE_DEBUG && JUCE_MSVC
#pragma warning (disable: 4714)
#endif
#define MINIMUM_COORD -0x3fffffff
#define MAXIMUM_COORD 0x3fffffff
#undef ASSERT_COORDS_ARE_SENSIBLE_NUMBERS
#define ASSERT_COORDS_ARE_SENSIBLE_NUMBERS(x, y, w, h) \
jassert ((int) x >= MINIMUM_COORD \
&& (int) x <= MAXIMUM_COORD \
&& (int) y >= MINIMUM_COORD \
&& (int) y <= MAXIMUM_COORD \
&& (int) w >= 0 \
&& (int) w < MAXIMUM_COORD \
&& (int) h >= 0 \
&& (int) h < MAXIMUM_COORD);
//==============================================================================
static void replaceRectRGB (uint8* pixels, const int w, int h, const int stride, const Colour& colour) throw()
{
const PixelARGB blendColour (colour.getPixelARGB());
if (w < 32)
{
while (--h >= 0)
{
PixelRGB* dest = (PixelRGB*) pixels;
for (int i = w; --i >= 0;)
(dest++)->set (blendColour);
pixels += stride;
}
}
else
{
// for wider fills, it's worth using some optimisations..
const uint8 r = blendColour.getRed();
const uint8 g = blendColour.getGreen();
const uint8 b = blendColour.getBlue();
if (r == g && r == b) // if all the component values are the same, we can cheat..
{
while (--h >= 0)
{
memset (pixels, r, w * 3);
pixels += stride;
}
}
else
{
PixelRGB filler [4];
filler[0].set (blendColour);
filler[1].set (blendColour);
filler[2].set (blendColour);
filler[3].set (blendColour);
const int* const intFiller = (const int*) filler;
while (--h >= 0)
{
uint8* dest = (uint8*) pixels;
int i = w;
while ((i > 8) && (((pointer_sized_int) dest & 7) != 0))
{
((PixelRGB*) dest)->set (blendColour);
dest += 3;
--i;
}
while (i >= 4)
{
((int*) dest) [0] = intFiller[0];
((int*) dest) [1] = intFiller[1];
((int*) dest) [2] = intFiller[2];
dest += 12;
i -= 4;
}
while (--i >= 0)
{
((PixelRGB*) dest)->set (blendColour);
dest += 3;
}
pixels += stride;
}
}
}
}
static void replaceRectARGB (uint8* pixels, const int w, int h, const int stride, const Colour& colour) throw()
{
const PixelARGB blendColour (colour.getPixelARGB());
while (--h >= 0)
{
PixelARGB* const dest = (PixelARGB*) pixels;
for (int i = 0; i < w; ++i)
dest[i] = blendColour;
pixels += stride;
}
}
static void blendRectRGB (uint8* pixels, const int w, int h, const int stride, const Colour& colour) throw()
{
if (colour.isOpaque())
{
replaceRectRGB (pixels, w, h, stride, colour);
}
else
{
const PixelARGB blendColour (colour.getPixelARGB());
const int alpha = blendColour.getAlpha();
if (alpha <= 0)
return;
#if JUCE_USE_SSE_INSTRUCTIONS
if (SystemStats::hasSSE())
{
int64 rgb0 = (((int64) blendColour.getRed()) << 32)
| (int64) ((blendColour.getGreen() << 16)
| blendColour.getBlue());
const int invAlpha = 0xff - alpha;
int64 aaaa = (invAlpha << 16) | invAlpha;
aaaa = (aaaa << 16) | aaaa;
#ifndef JUCE_GCC
__asm
{
movq mm1, aaaa
movq mm2, rgb0
pxor mm7, mm7
}
while (--h >= 0)
{
__asm
{
mov edx, pixels
mov ebx, w
pixloop:
prefetchnta [edx]
mov ax, [edx + 1]
shl eax, 8
mov al, [edx]
movd mm0, eax
punpcklbw mm0, mm7
pmullw mm0, mm1
psrlw mm0, 8
paddw mm0, mm2
packuswb mm0, mm7
movd eax, mm0
mov [edx], al
inc edx
shr eax, 8
mov [edx], ax
add edx, 2
dec ebx
jg pixloop
}
pixels += stride;
}
__asm emms
#else
__asm__ __volatile__ (
"\tpush %%ebx \n"
"\tmovq %[aaaa], %%mm1 \n"
"\tmovq %[rgb0], %%mm2 \n"
"\tpxor %%mm7, %%mm7 \n"
".lineLoop2: \n"
"\tmovl %%esi,%%edx \n"
"\tmovl %[w], %%ebx \n"
".pixLoop2: \n"
"\tprefetchnta (%%edx) \n"
"\tmov (%%edx), %%ax \n"
"\tshl $8, %%eax \n"
"\tmov 2(%%edx), %%al \n"
"\tmovd %%eax, %%mm0 \n"
"\tpunpcklbw %%mm7, %%mm0 \n"
"\tpmullw %%mm1, %%mm0 \n"
"\tpsrlw $8, %%mm0 \n"
"\tpaddw %%mm2, %%mm0 \n"
"\tpackuswb %%mm7, %%mm0 \n"
"\tmovd %%mm0, %%eax \n"
"\tmovb %%al, (%%edx) \n"
"\tinc %%edx \n"
"\tshr $8, %%eax \n"
"\tmovw %%ax, (%%edx) \n"
"\tadd $2, %%edx \n"
"\tdec %%ebx \n"
"\tjg .pixLoop2 \n"
"\tadd %%edi, %%esi \n"
"\tdec %%ecx \n"
"\tjg .lineLoop2 \n"
"\tpop %%ebx \n"
"\temms \n"
: /* No output registers */
: [aaaa] "m" (aaaa), /* Input registers */
[rgb0] "m" (rgb0),
[w] "m" (w),
"c" (h),
[stride] "D" (stride),
[pixels] "S" (pixels)
: "cc", "eax", "edx", "memory" /* Clobber list */
);
#endif
}
else
#endif
{
while (--h >= 0)
{
PixelRGB* dest = (PixelRGB*) pixels;
for (int i = w; --i >= 0;)
(dest++)->blend (blendColour);
pixels += stride;
}
}
}
}
static void blendRectARGB (uint8* pixels, const int w, int h, const int stride, const Colour& colour) throw()
{
if (colour.isOpaque())
{
replaceRectARGB (pixels, w, h, stride, colour);
}
else
{
const PixelARGB blendColour (colour.getPixelARGB());
const int alpha = blendColour.getAlpha();
if (alpha <= 0)
return;
while (--h >= 0)
{
PixelARGB* dest = (PixelARGB*) pixels;
for (int i = w; --i >= 0;)
(dest++)->blend (blendColour);
pixels += stride;
}
}
}
//==============================================================================
static void blendAlphaMapARGB (uint8* destPixel, const int imageStride,
const uint8* alphaValues, const int w, int h,
const int pixelStride, const int lineStride,
const Colour& colour) throw()
{
const PixelARGB srcPix (colour.getPixelARGB());
while (--h >= 0)
{
PixelARGB* dest = (PixelARGB*) destPixel;
const uint8* src = alphaValues;
int i = w;
while (--i >= 0)
{
unsigned int srcAlpha = *src;
src += pixelStride;
if (srcAlpha > 0)
dest->blend (srcPix, srcAlpha);
++dest;
}
alphaValues += lineStride;
destPixel += imageStride;
}
}
static void blendAlphaMapRGB (uint8* destPixel, const int imageStride,
const uint8* alphaValues, int const width, int height,
const int pixelStride, const int lineStride,
const Colour& colour) throw()
{
const PixelARGB srcPix (colour.getPixelARGB());
while (--height >= 0)
{
PixelRGB* dest = (PixelRGB*) destPixel;
const uint8* src = alphaValues;
int i = width;
while (--i >= 0)
{
unsigned int srcAlpha = *src;
src += pixelStride;
if (srcAlpha > 0)
dest->blend (srcPix, srcAlpha);
++dest;
}
alphaValues += lineStride;
destPixel += imageStride;
}
}
//==============================================================================
template <class PixelType>
class SolidColourEdgeTableRenderer
{
uint8* const data;
const int stride;
PixelType* linePixels;
PixelARGB sourceColour;
SolidColourEdgeTableRenderer (const SolidColourEdgeTableRenderer&);
const SolidColourEdgeTableRenderer& operator= (const SolidColourEdgeTableRenderer&);
public:
SolidColourEdgeTableRenderer (uint8* const data_,
const int stride_,
const Colour& colour) throw()
: data (data_),
stride (stride_),
sourceColour (colour.getPixelARGB())
{
}
forcedinline void setEdgeTableYPos (const int y) throw()
{
linePixels = (PixelType*) (data + stride * y);
}
forcedinline void handleEdgeTablePixel (const int x, const int alphaLevel) const throw()
{
linePixels[x].blend (sourceColour, alphaLevel);
}
forcedinline void handleEdgeTableLine (const int x, int width, const int alphaLevel) const throw()
{
PixelARGB p (sourceColour);
p.multiplyAlpha (alphaLevel);
PixelType* dest = linePixels + x;
if (p.getAlpha() < 0xff)
{
do
{
dest->blend (p);
++dest;
} while (--width > 0);
}
else
{
do
{
dest->set (p);
++dest;
} while (--width > 0);
}
}
};
class AlphaBitmapRenderer
{
uint8* data;
int stride;
uint8* lineStart;
AlphaBitmapRenderer (const AlphaBitmapRenderer&);
const AlphaBitmapRenderer& operator= (const AlphaBitmapRenderer&);
public:
AlphaBitmapRenderer (uint8* const data_,
const int stride_) throw()
: data (data_),
stride (stride_)
{
}
forcedinline void setEdgeTableYPos (const int y) throw()
{
lineStart = data + (stride * y);
}
forcedinline void handleEdgeTablePixel (const int x, const int alphaLevel) const throw()
{
lineStart [x] = (uint8) alphaLevel;
}
forcedinline void handleEdgeTableLine (const int x, int width, const int alphaLevel) const throw()
{
uint8* d = lineStart + x;
while (--width >= 0)
*d++ = (uint8) alphaLevel;
}
};
//==============================================================================
static const int numScaleBits = 12;
class LinearGradientPixelGenerator
{
const PixelARGB* const lookupTable;
const int numEntries;
PixelARGB linePix;
int start, scale;
double grad, yTerm;
bool vertical, horizontal;
LinearGradientPixelGenerator (const LinearGradientPixelGenerator&);
const LinearGradientPixelGenerator& operator= (const LinearGradientPixelGenerator&);
public:
LinearGradientPixelGenerator (const ColourGradient& gradient,
const PixelARGB* const lookupTable_, const int numEntries_)
: lookupTable (lookupTable_),
numEntries (numEntries_)
{
jassert (numEntries_ >= 0);
float x1 = gradient.x1;
float y1 = gradient.y1;
float x2 = gradient.x2;
float y2 = gradient.y2;
if (! gradient.transform.isIdentity())
{
Line l (x2, y2, x1, y1);
const Point p3 = l.getPointAlongLine (0.0, 100.0f);
float x3 = p3.getX();
float y3 = p3.getY();
gradient.transform.transformPoint (x1, y1);
gradient.transform.transformPoint (x2, y2);
gradient.transform.transformPoint (x3, y3);
Line l2 (x2, y2, x3, y3);
float prop = l2.findNearestPointTo (x1, y1);
const Point newP2 (l2.getPointAlongLineProportionally (prop));
x2 = newP2.getX();
y2 = newP2.getY();
}
vertical = fabs (x1 - x2) < 0.001f;
horizontal = fabs (y1 - y2) < 0.001f;
if (vertical)
{
scale = roundDoubleToInt ((numEntries << numScaleBits) / (double) (y2 - y1));
start = roundDoubleToInt (y1 * scale);
}
else if (horizontal)
{
scale = roundDoubleToInt ((numEntries << numScaleBits) / (double) (x2 - x1));
start = roundDoubleToInt (x1 * scale);
}
else
{
grad = (y2 - y1) / (double) (x1 - x2);
yTerm = y1 - x1 / grad;
scale = roundDoubleToInt ((numEntries << numScaleBits) / (yTerm * grad - (y2 * grad - x2)));
grad *= scale;
}
}
forcedinline void setY (const int y) throw()
{
if (vertical)
linePix = lookupTable [jlimit (0, numEntries, (y * scale - start) >> numScaleBits)];
else if (! horizontal)
start = roundDoubleToInt ((y - yTerm) * grad);
}
forcedinline const PixelARGB getPixel (const int x) const throw()
{
if (vertical)
return linePix;
return lookupTable [jlimit (0, numEntries, (x * scale - start) >> numScaleBits)];
}
};
class RadialGradientPixelGenerator
{
protected:
const PixelARGB* const lookupTable;
const int numEntries;
const double gx1, gy1;
double maxDist, invScale;
double dy;
RadialGradientPixelGenerator (const RadialGradientPixelGenerator&);
const RadialGradientPixelGenerator& operator= (const RadialGradientPixelGenerator&);
public:
RadialGradientPixelGenerator (const ColourGradient& gradient,
const PixelARGB* const lookupTable_, const int numEntries_) throw()
: lookupTable (lookupTable_),
numEntries (numEntries_),
gx1 (gradient.x1),
gy1 (gradient.y1)
{
jassert (numEntries_ >= 0);
const float dx = gradient.x1 - gradient.x2;
const float dy = gradient.y1 - gradient.y2;
maxDist = dx * dx + dy * dy;
invScale = (numEntries + 1) / sqrt (maxDist);
}
forcedinline void setY (const int y) throw()
{
dy = y - gy1;
dy *= dy;
}
forcedinline const PixelARGB getPixel (const int px) const throw()
{
double x = px - gx1;
x *= x;
x += dy;
if (x >= maxDist)
return lookupTable [numEntries];
else
return lookupTable [jmin (numEntries, roundDoubleToInt (sqrt (x) * invScale))];
}
};
class TransformedRadialGradientPixelGenerator : public RadialGradientPixelGenerator
{
double tM10, tM00, lineYM01, lineYM11;
AffineTransform inverseTransform;
TransformedRadialGradientPixelGenerator (const TransformedRadialGradientPixelGenerator&);
const TransformedRadialGradientPixelGenerator& operator= (const TransformedRadialGradientPixelGenerator&);
public:
TransformedRadialGradientPixelGenerator (const ColourGradient& gradient,
const PixelARGB* const lookupTable_, const int numEntries_) throw()
: RadialGradientPixelGenerator (gradient, lookupTable_, numEntries_),
inverseTransform (gradient.transform.inverted())
{
tM10 = inverseTransform.mat10;
tM00 = inverseTransform.mat00;
}
forcedinline void setY (const int y) throw()
{
lineYM01 = inverseTransform.mat01 * y + inverseTransform.mat02 - gx1;
lineYM11 = inverseTransform.mat11 * y + inverseTransform.mat12 - gy1;
}
forcedinline const PixelARGB getPixel (const int px) const throw()
{
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, roundDoubleToInt (sqrt (x) * invScale))];
}
};
template <class PixelType, class GradientType>
class GradientEdgeTableRenderer : public GradientType
{
uint8* const data;
const int stride;
PixelType* linePixels;
GradientEdgeTableRenderer (const GradientEdgeTableRenderer&);
const GradientEdgeTableRenderer& operator= (const GradientEdgeTableRenderer&);
public:
GradientEdgeTableRenderer (uint8* const data_,
const int stride_,
const ColourGradient& gradient,
const PixelARGB* const lookupTable, const int numEntries) throw()
: GradientType (gradient, lookupTable, numEntries - 1),
data (data_),
stride (stride_)
{
}
forcedinline void setEdgeTableYPos (const int y) throw()
{
linePixels = (PixelType*) (data + stride * y);
GradientType::setY (y);
}
forcedinline void handleEdgeTablePixel (const int x, const int alphaLevel) const throw()
{
linePixels[x].blend (GradientType::getPixel (x), alphaLevel);
}
forcedinline void handleEdgeTableLine (int x, int width, const int alphaLevel) const throw()
{
PixelType* dest = linePixels + x;
if (alphaLevel < 0xff)
{
do
{
(dest++)->blend (GradientType::getPixel (x++), alphaLevel);
} while (--width > 0);
}
else
{
do
{
(dest++)->blend (GradientType::getPixel (x++));
} while (--width > 0);
}
}
};
//==============================================================================
template <class DestPixelType, class SrcPixelType>
class ImageFillEdgeTableRenderer
{
uint8* const destImageData;
const uint8* srcImageData;
int stride, srcStride, extraAlpha;
DestPixelType* linePixels;
SrcPixelType* sourceLineStart;
ImageFillEdgeTableRenderer (const ImageFillEdgeTableRenderer&);
const ImageFillEdgeTableRenderer& operator= (const ImageFillEdgeTableRenderer&);
public:
ImageFillEdgeTableRenderer (uint8* const destImageData_,
const int stride_,
const uint8* srcImageData_,
const int srcStride_,
int extraAlpha_,
SrcPixelType*) throw() // dummy param to avoid compiler error
: destImageData (destImageData_),
srcImageData (srcImageData_),
stride (stride_),
srcStride (srcStride_),
extraAlpha (extraAlpha_)
{
}
forcedinline void setEdgeTableYPos (int y) throw()
{
linePixels = (DestPixelType*) (destImageData + stride * y);
sourceLineStart = (SrcPixelType*) (srcImageData + srcStride * y);
}
forcedinline void handleEdgeTablePixel (const int x, int alphaLevel) const throw()
{
alphaLevel = (alphaLevel * extraAlpha) >> 8;
linePixels[x].blend (sourceLineStart [x], alphaLevel);
}
forcedinline void handleEdgeTableLine (int x, int width, int alphaLevel) const throw()
{
DestPixelType* dest = linePixels + x;
alphaLevel = (alphaLevel * extraAlpha) >> 8;
if (alphaLevel < 0xfe)
{
do
{
dest++ ->blend (sourceLineStart [x++], alphaLevel);
} while (--width > 0);
}
else
{
do
{
dest++ ->blend (sourceLineStart [x++]);
} while (--width > 0);
}
}
};
//==============================================================================
static void blendRowOfPixels (PixelARGB* dst,
const PixelRGB* src,
int width) throw()
{
while (--width >= 0)
(dst++)->set (*src++);
}
static void blendRowOfPixels (PixelRGB* dst,
const PixelRGB* src,
int width) throw()
{
memcpy (dst, src, 3 * width);
}
static void blendRowOfPixels (PixelRGB* dst,
const PixelARGB* src,
int width) throw()
{
while (--width >= 0)
(dst++)->blend (*src++);
}
static void blendRowOfPixels (PixelARGB* dst,
const PixelARGB* src,
int width) throw()
{
while (--width >= 0)
(dst++)->blend (*src++);
}
static void blendRowOfPixels (PixelARGB* dst,
const PixelRGB* src,
int width,
const uint8 alpha) throw()
{
while (--width >= 0)
(dst++)->blend (*src++, alpha);
}
static void blendRowOfPixels (PixelRGB* dst,
const PixelRGB* src,
int width,
const uint8 alpha) throw()
{
uint8* d = (uint8*) dst;
const uint8* s = (const uint8*) src;
const int inverseAlpha = 0xff - alpha;
while (--width >= 0)
{
d[0] = (uint8) (s[0] + (((d[0] - s[0]) * inverseAlpha) >> 8));
d[1] = (uint8) (s[1] + (((d[1] - s[1]) * inverseAlpha) >> 8));
d[2] = (uint8) (s[2] + (((d[2] - s[2]) * inverseAlpha) >> 8));
d += 3;
s += 3;
}
}
static void blendRowOfPixels (PixelRGB* dst,
const PixelARGB* src,
int width,
const uint8 alpha) throw()
{
while (--width >= 0)
(dst++)->blend (*src++, alpha);
}
static void blendRowOfPixels (PixelARGB* dst,
const PixelARGB* src,
int width,
const uint8 alpha) throw()
{
while (--width >= 0)
(dst++)->blend (*src++, alpha);
}
template <class DestPixelType, class SrcPixelType>
static void overlayImage (DestPixelType* dest,
const int destStride,
const SrcPixelType* src,
const int srcStride,
const int width,
int height,
const uint8 alpha) throw()
{
if (alpha < 0xff)
{
while (--height >= 0)
{
blendRowOfPixels (dest, src, width, alpha);
dest = (DestPixelType*) (((uint8*) dest) + destStride);
src = (const SrcPixelType*) (((const uint8*) src) + srcStride);
}
}
else
{
while (--height >= 0)
{
blendRowOfPixels (dest, src, width);
dest = (DestPixelType*) (((uint8*) dest) + destStride);
src = (const SrcPixelType*) (((const uint8*) src) + srcStride);
}
}
}
template <class DestPixelType, class SrcPixelType>
static void transformedImageRender (Image& destImage,
const Image& sourceImage,
const int destClipX, const int destClipY,
const int destClipW, const int destClipH,
const int srcClipX, const int srcClipY,
const int srcClipWidth, const int srcClipHeight,
double srcX, double srcY,
const double lineDX, const double lineDY,
const double pixelDX, const double pixelDY,
const uint8 alpha,
const Graphics::ResamplingQuality quality,
DestPixelType*,
SrcPixelType*) throw() // forced by a compiler bug to include dummy
// parameters of the templated classes to
// make it use the correct instance of this function..
{
int destStride, destPixelStride;
uint8* const destPixels = destImage.lockPixelDataReadWrite (destClipX, destClipY, destClipW, destClipH, destStride, destPixelStride);
int srcStride, srcPixelStride;
const uint8* const srcPixels = sourceImage.lockPixelDataReadOnly (srcClipX, srcClipY, srcClipWidth, srcClipHeight, srcStride, srcPixelStride);
if (quality == Graphics::lowResamplingQuality) // nearest-neighbour..
{
if (alpha == 255)
{
for (int y = 0; y < destClipH; ++y)
{
double sx = srcX;
double sy = srcY;
DestPixelType* dest = (DestPixelType*) (destPixels + destStride * y);
for (int x = destClipW; --x >= 0;)
{
const int ix = ((int) sx) - srcClipX;
if (((unsigned int) ix) < (unsigned int) srcClipWidth)
{
const int iy = ((int) sy) - srcClipY;
if (((unsigned int) iy) < (unsigned int) srcClipHeight)
{
const SrcPixelType* const src = (const SrcPixelType*) (srcPixels + srcStride * iy + srcPixelStride * ix);
dest->blend (*src);
}
}
++dest;
sx += pixelDX;
sy += pixelDY;
}
srcX += lineDX;
srcY += lineDY;
}
}
else
{
for (int y = 0; y < destClipH; ++y)
{
double sx = srcX;
double sy = srcY;
DestPixelType* dest = (DestPixelType*) (destPixels + destStride * y);
for (int x = destClipW; --x >= 0;)
{
const int ix = ((int) sx) - srcClipX;
if (((unsigned int) ix) < (unsigned int) srcClipWidth)
{
const int iy = ((int) sy) - srcClipY;
if (((unsigned int) iy) < (unsigned int) srcClipHeight)
{
const SrcPixelType* const src = (const SrcPixelType*) (srcPixels + srcStride * iy + srcPixelStride * ix);
dest->blend (*src, alpha);
}
}
++dest;
sx += pixelDX;
sy += pixelDY;
}
srcX += lineDX;
srcY += lineDY;
}
}
}
else
{
jassert (quality == Graphics::mediumResamplingQuality); // (only bilinear is implemented, so that's what you'll get here..)
for (int y = 0; y < destClipH; ++y)
{
double sx = srcX - (srcClipWidth == destClipW ? 0.0 : 0.5);
double sy = srcY - (srcClipHeight == destClipH ? 0.0 : 0.5);
DestPixelType* dest = (DestPixelType*) (destPixels + destStride * y);
for (int x = 0; x < destClipW; ++x)
{
const double fx = floor (sx);
const double fy = floor (sy);
const int ix = roundDoubleToInt (fx) - srcClipX;
const int iy = roundDoubleToInt (fy) - srcClipY;
if (ix < srcClipWidth && iy < srcClipHeight)
{
PixelARGB p1 (0), p2 (0), p3 (0), p4 (0);
const SrcPixelType* src = (const SrcPixelType*) (srcPixels + srcStride * iy + srcPixelStride * ix);
if (iy >= 0)
{
if (ix >= 0)
p1.set (src[0]);
if (((unsigned int) (ix + 1)) < (unsigned int) srcClipWidth)
p2.set (src[1]);
}
if (((unsigned int) (iy + 1)) < (unsigned int) srcClipHeight)
{
src = (const SrcPixelType*) (((const uint8*) src) + srcStride);
if (ix >= 0)
p3.set (src[0]);
if (((unsigned int) (ix + 1)) < (unsigned int) srcClipWidth)
p4.set (src[1]);
}
const int dx = roundDoubleToInt ((sx - fx) * 255.0);
p1.tween (p2, dx);
p3.tween (p4, dx);
p1.tween (p3, roundDoubleToInt ((sy - fy) * 255.0));
if (p1.getAlpha() > 0)
dest->blend (p1, alpha);
}
++dest;
sx += pixelDX;
sy += pixelDY;
}
srcX += lineDX;
srcY += lineDY;
}
}
destImage.releasePixelDataReadWrite (destPixels);
sourceImage.releasePixelDataReadOnly (srcPixels);
}
template <class SrcPixelType, class DestPixelType>
static void renderAlphaMap (DestPixelType* destPixels,
int destStride,
SrcPixelType* srcPixels,
int srcStride,
const uint8* alphaValues,
const int lineStride, const int pixelStride,
int width, int height,
const int extraAlpha) throw()
{
while (--height >= 0)
{
SrcPixelType* srcPix = srcPixels;
srcPixels = (SrcPixelType*) (((const uint8*) srcPixels) + srcStride);
DestPixelType* destPix = destPixels;
destPixels = (DestPixelType*) (((uint8*) destPixels) + destStride);
const uint8* alpha = alphaValues;
alphaValues += lineStride;
if (extraAlpha < 0x100)
{
for (int i = width; --i >= 0;)
{
destPix++ ->blend (*srcPix++, (extraAlpha * *alpha) >> 8);
alpha += pixelStride;
}
}
else
{
for (int i = width; --i >= 0;)
{
destPix++ ->blend (*srcPix++, *alpha);
alpha += pixelStride;
}
}
}
}
//==============================================================================
LowLevelGraphicsSoftwareRenderer::LowLevelGraphicsSoftwareRenderer (Image& image_)
: image (image_),
xOffset (0),
yOffset (0),
stateStack (20),
colour (0xff000000),
gradient (0)
{
clip = new RectangleList (Rectangle (0, 0, image_.getWidth(), image_.getHeight()));
}
LowLevelGraphicsSoftwareRenderer::~LowLevelGraphicsSoftwareRenderer()
{
delete clip;
delete gradient;
}
bool LowLevelGraphicsSoftwareRenderer::isVectorDevice() const
{
return false;
}
//==============================================================================
void LowLevelGraphicsSoftwareRenderer::setOrigin (int x, int y)
{
xOffset += x;
yOffset += y;
}
bool LowLevelGraphicsSoftwareRenderer::reduceClipRegion (int x, int y, int w, int h)
{
return clip->clipTo (Rectangle (x + xOffset, y + yOffset, w, h));
}
bool LowLevelGraphicsSoftwareRenderer::reduceClipRegion (const RectangleList& clipRegion)
{
RectangleList temp (clipRegion);
temp.offsetAll (xOffset, yOffset);
return clip->clipTo (temp);
}
void LowLevelGraphicsSoftwareRenderer::excludeClipRegion (int x, int y, int w, int h)
{
clip->subtract (Rectangle (x + xOffset, y + yOffset, w, h));
}
bool LowLevelGraphicsSoftwareRenderer::clipRegionIntersects (int x, int y, int w, int h)
{
return clip->intersectsRectangle (Rectangle (x + xOffset, y + yOffset, w, h));
}
const Rectangle LowLevelGraphicsSoftwareRenderer::getClipBounds() const
{
return clip->getBounds().translated (-xOffset, -yOffset);
}
bool LowLevelGraphicsSoftwareRenderer::isClipEmpty() const
{
return clip->isEmpty();
}
//==============================================================================
LowLevelGraphicsSoftwareRenderer::SavedState::SavedState (RectangleList* const clip_,
const int xOffset_, const int yOffset_,
const Font& font_, const Colour& colour_, ColourGradient* gradient_,
Graphics::ResamplingQuality interpolationQuality_)
: clip (clip_),
xOffset (xOffset_),
yOffset (yOffset_),
font (font_),
colour (colour_),
gradient (gradient_),
interpolationQuality (interpolationQuality_)
{
}
LowLevelGraphicsSoftwareRenderer::SavedState::~SavedState()
{
delete clip;
delete gradient;
}
void LowLevelGraphicsSoftwareRenderer::saveState()
{
stateStack.add (new SavedState (new RectangleList (*clip), xOffset, yOffset,
font, colour, gradient != 0 ? new ColourGradient (*gradient) : 0,
interpolationQuality));
}
void LowLevelGraphicsSoftwareRenderer::restoreState()
{
SavedState* const top = stateStack.getLast();
if (top != 0)
{
swapVariables (clip, top->clip);
xOffset = top->xOffset;
yOffset = top->yOffset;
font = top->font;
colour = top->colour;
swapVariables (gradient, top->gradient);
interpolationQuality = top->interpolationQuality;
stateStack.removeLast();
}
else
{
jassertfalse // trying to pop with an empty stack!
}
}
//==============================================================================
void LowLevelGraphicsSoftwareRenderer::setColour (const Colour& colour_)
{
deleteAndZero (gradient);
colour = colour_;
}
void LowLevelGraphicsSoftwareRenderer::setGradient (const ColourGradient& gradient_)
{
delete gradient;
gradient = new ColourGradient (gradient_);
}
void LowLevelGraphicsSoftwareRenderer::setOpacity (float opacity)
{
colour = colour.withAlpha (opacity);
}
void LowLevelGraphicsSoftwareRenderer::setInterpolationQuality (Graphics::ResamplingQuality quality)
{
interpolationQuality = quality;
}
//==============================================================================
void LowLevelGraphicsSoftwareRenderer::fillRect (int x, int y, int w, int h, const bool replaceExistingContents)
{
if (gradient != 0)
{
if (replaceExistingContents && ! gradient->isOpaque())
{
for (RectangleList::Iterator i (*clip); i.next();)
clippedFillRectWithColour (*i.getRectangle(), x + xOffset, y + yOffset, w, h, Colours::transparentBlack, true);
}
Path p;
p.addRectangle ((float) x, (float) y, (float) w, (float) h);
fillPath (p, AffineTransform::identity, EdgeTable::Oversampling_none);
}
else
{
x += xOffset;
y += yOffset;
for (RectangleList::Iterator i (*clip); i.next();)
clippedFillRectWithColour (*i.getRectangle(), x, y, w, h, colour, replaceExistingContents);
}
}
void LowLevelGraphicsSoftwareRenderer::clippedFillRectWithColour (const Rectangle& clipRect,
int x, int y, int w, int h, const Colour& colour, const bool replaceExistingContents)
{
if (clipRect.intersectRectangle (x, y, w, h))
{
int stride, pixelStride;
uint8* const pixels = (uint8*) image.lockPixelDataReadWrite (x, y, w, h, stride, pixelStride);
if (image.getFormat() == Image::RGB)
{
if (replaceExistingContents)
replaceRectRGB (pixels, w, h, stride, colour);
else
blendRectRGB (pixels, w, h, stride, colour);
}
else if (image.getFormat() == Image::ARGB)
{
if (replaceExistingContents)
replaceRectARGB (pixels, w, h, stride, colour);
else
blendRectARGB (pixels, w, h, stride, colour);
}
else
{
jassertfalse // not done!
}
image.releasePixelDataReadWrite (pixels);
}
}
//==============================================================================
bool LowLevelGraphicsSoftwareRenderer::getPathBounds (int clipX, int clipY, int clipW, int clipH,
const Path& path, const AffineTransform& transform,
int& x, int& y, int& w, int& h) const
{
float tx, ty, tw, th;
path.getBoundsTransformed (transform, tx, ty, tw, th);
x = roundDoubleToInt (tx) - 1;
y = roundDoubleToInt (ty) - 1;
w = roundDoubleToInt (tw) + 2;
h = roundDoubleToInt (th) + 2;
// seems like this operation is using some crazy out-of-range numbers..
ASSERT_COORDS_ARE_SENSIBLE_NUMBERS (x, y, w, h);
return Rectangle::intersectRectangles (x, y, w, h, clipX, clipY, clipW, clipH);
}
void LowLevelGraphicsSoftwareRenderer::fillPath (const Path& path, const AffineTransform& transform, EdgeTable::OversamplingLevel quality)
{
for (RectangleList::Iterator i (*clip); i.next();)
{
const Rectangle& r = *i.getRectangle();
clippedFillPath (r.getX(), r.getY(), r.getWidth(), r.getHeight(),
path, transform, quality);
}
}
void LowLevelGraphicsSoftwareRenderer::clippedFillPath (int clipX, int clipY, int clipW, int clipH, const Path& path,
const AffineTransform& t, EdgeTable::OversamplingLevel quality)
{
const AffineTransform transform (t.translated ((float) xOffset, (float) yOffset));
int cx, cy, cw, ch;
if (getPathBounds (clipX, clipY, clipW, clipH, path, transform, cx, cy, cw, ch))
{
EdgeTable edgeTable (0, ch, quality);
edgeTable.addPath (path, transform.translated ((float) -cx, (float) -cy));
int stride, pixelStride;
uint8* const pixels = (uint8*) image.lockPixelDataReadWrite (cx, cy, cw, ch, stride, pixelStride);
if (gradient != 0)
{
ColourGradient g2 (*gradient);
const bool isIdentity = g2.transform.isIdentity();
if (isIdentity)
{
g2.x1 += xOffset - cx;
g2.x2 += xOffset - cx;
g2.y1 += yOffset - cy;
g2.y2 += yOffset - cy;
}
else
{
g2.transform = g2.transform.translated ((float) (xOffset - cx),
(float) (yOffset - cy));
}
int numLookupEntries;
PixelARGB* const lookupTable = g2.createLookupTable (numLookupEntries);
jassert (numLookupEntries > 0);
if (image.getFormat() == Image::RGB)
{
jassert (pixelStride == 3);
if (g2.isRadial)
{
if (isIdentity)
{
GradientEdgeTableRenderer <PixelRGB, RadialGradientPixelGenerator> renderer (pixels, stride, g2, lookupTable, numLookupEntries);
edgeTable.iterate (renderer, 0, 0, cw, ch, 0);
}
else
{
GradientEdgeTableRenderer <PixelRGB, TransformedRadialGradientPixelGenerator> renderer (pixels, stride, g2, lookupTable, numLookupEntries);
edgeTable.iterate (renderer, 0, 0, cw, ch, 0);
}
}
else
{
GradientEdgeTableRenderer <PixelRGB, LinearGradientPixelGenerator> renderer (pixels, stride, g2, lookupTable, numLookupEntries);
edgeTable.iterate (renderer, 0, 0, cw, ch, 0);
}
}
else if (image.getFormat() == Image::ARGB)
{
jassert (pixelStride == 4);
if (g2.isRadial)
{
if (isIdentity)
{
GradientEdgeTableRenderer <PixelARGB, RadialGradientPixelGenerator> renderer (pixels, stride, g2, lookupTable, numLookupEntries);
edgeTable.iterate (renderer, 0, 0, cw, ch, 0);
}
else
{
GradientEdgeTableRenderer <PixelARGB, TransformedRadialGradientPixelGenerator> renderer (pixels, stride, g2, lookupTable, numLookupEntries);
edgeTable.iterate (renderer, 0, 0, cw, ch, 0);
}
}
else
{
GradientEdgeTableRenderer <PixelARGB, LinearGradientPixelGenerator> renderer (pixels, stride, g2, lookupTable, numLookupEntries);
edgeTable.iterate (renderer, 0, 0, cw, ch, 0);
}
}
else if (image.getFormat() == Image::SingleChannel)
{
jassertfalse // not done!
}
juce_free (lookupTable);
}
else
{
if (image.getFormat() == Image::RGB)
{
jassert (pixelStride == 3);
SolidColourEdgeTableRenderer <PixelRGB> renderer (pixels, stride, colour);
edgeTable.iterate (renderer, 0, 0, cw, ch, 0);
}
else if (image.getFormat() == Image::ARGB)
{
jassert (pixelStride == 4);
SolidColourEdgeTableRenderer <PixelARGB> renderer (pixels, stride, colour);
edgeTable.iterate (renderer, 0, 0, cw, ch, 0);
}
else if (image.getFormat() == Image::SingleChannel)
{
jassert (pixelStride == 1);
AlphaBitmapRenderer renderer (pixels, stride);
edgeTable.iterate (renderer, 0, 0, cw, ch, 0);
}
}
image.releasePixelDataReadWrite (pixels);
}
}
void LowLevelGraphicsSoftwareRenderer::fillPathWithImage (const Path& path, const AffineTransform& transform,
const Image& sourceImage, int imageX, int imageY, EdgeTable::OversamplingLevel quality)
{
imageX += xOffset;
imageY += yOffset;
for (RectangleList::Iterator i (*clip); i.next();)
{
const Rectangle& r = *i.getRectangle();
clippedFillPathWithImage (r.getX(), r.getY(), r.getWidth(), r.getHeight(),
path, transform, sourceImage, imageX, imageY,
colour.getFloatAlpha(), quality);
}
}
void LowLevelGraphicsSoftwareRenderer::clippedFillPathWithImage (int x, int y, int w, int h, const Path& path, const AffineTransform& transform,
const Image& sourceImage, int imageX, int imageY, float opacity, EdgeTable::OversamplingLevel quality)
{
if (Rectangle::intersectRectangles (x, y, w, h, imageX, imageY, sourceImage.getWidth(), sourceImage.getHeight()))
{
EdgeTable edgeTable (0, h, quality);
edgeTable.addPath (path, transform.translated ((float) (xOffset - x), (float) (yOffset - y)));
int stride, pixelStride;
uint8* const pixels = (uint8*) image.lockPixelDataReadWrite (x, y, w, h, stride, pixelStride);
int srcStride, srcPixelStride;
const uint8* const srcPix = (const uint8*) sourceImage.lockPixelDataReadOnly (x - imageX, y - imageY, w, h, srcStride, srcPixelStride);
const int alpha = jlimit (0, 255, roundDoubleToInt (opacity * 255.0f));
if (image.getFormat() == Image::RGB)
{
if (sourceImage.getFormat() == Image::RGB)
{
ImageFillEdgeTableRenderer <PixelRGB, PixelRGB> renderer (pixels, stride,
srcPix, srcStride,
alpha, (PixelRGB*) 0);
edgeTable.iterate (renderer, 0, 0, w, h, 0);
}
else if (sourceImage.getFormat() == Image::ARGB)
{
ImageFillEdgeTableRenderer <PixelRGB, PixelARGB> renderer (pixels, stride,
srcPix, srcStride,
alpha, (PixelARGB*) 0);
edgeTable.iterate (renderer, 0, 0, w, h, 0);
}
else
{
jassertfalse // not done!
}
}
else if (image.getFormat() == Image::ARGB)
{
if (sourceImage.getFormat() == Image::RGB)
{
ImageFillEdgeTableRenderer <PixelARGB, PixelRGB> renderer (pixels, stride,
srcPix, srcStride,
alpha, (PixelRGB*) 0);
edgeTable.iterate (renderer, 0, 0, w, h, 0);
}
else if (sourceImage.getFormat() == Image::ARGB)
{
ImageFillEdgeTableRenderer <PixelARGB, PixelARGB> renderer (pixels, stride,
srcPix, srcStride,
alpha, (PixelARGB*) 0);
edgeTable.iterate (renderer, 0, 0, w, h, 0);
}
else
{
jassertfalse // not done!
}
}
else
{
jassertfalse // not done!
}
sourceImage.releasePixelDataReadOnly (srcPix);
image.releasePixelDataReadWrite (pixels);
}
}
//==============================================================================
void LowLevelGraphicsSoftwareRenderer::fillAlphaChannel (const Image& clipImage, int x, int y)
{
x += xOffset;
y += yOffset;
for (RectangleList::Iterator i (*clip); i.next();)
{
const Rectangle& r = *i.getRectangle();
clippedFillAlphaChannel (r.getX(), r.getY(), r.getWidth(), r.getHeight(),
clipImage, x, y);
}
}
void LowLevelGraphicsSoftwareRenderer::clippedFillAlphaChannel (int clipX, int clipY, int clipW, int clipH, const Image& clipImage, int x, int y)
{
if (gradient != 0)
{
if (Rectangle::intersectRectangles (clipX, clipY, clipW, clipH, x, y, clipImage.getWidth(), clipImage.getHeight()))
{
ColourGradient g2 (*gradient);
g2.x1 += xOffset - clipX;
g2.x2 += xOffset - clipX;
g2.y1 += yOffset - clipY;
g2.y2 += yOffset - clipY;
Image temp (g2.isOpaque() ? Image::RGB : Image::ARGB, clipW, clipH, true);
LowLevelGraphicsSoftwareRenderer tempG (temp);
tempG.setGradient (g2);
tempG.fillRect (0, 0, clipW, clipH, false);
clippedFillAlphaChannelWithImage (clipX, clipY, clipW, clipH,
clipImage, x, y,
temp, clipX, clipY, 1.0f);
}
}
else
{
int w = clipImage.getWidth();
int h = clipImage.getHeight();
int sx = 0;
int sy = 0;
if (x < clipX)
{
sx = clipX - x;
w -= clipX - x;
x = clipX;
}
if (y < clipY)
{
sy = clipY - y;
h -= clipY - y;
y = clipY;
}
if (x + w > clipX + clipW)
w = clipX + clipW - x;
if (y + h > clipY + clipH)
h = clipY + clipH - y;
if (w > 0 && h > 0)
{
int stride, alphaStride, pixelStride;
uint8* const pixels = (uint8*) image.lockPixelDataReadWrite (x, y, w, h, stride, pixelStride);
const uint8* const alphaValues
= clipImage.lockPixelDataReadOnly (sx, sy, w, h, alphaStride, pixelStride);
#if JUCE_BIG_ENDIAN
const uint8* const alphas = alphaValues;
#else
const uint8* const alphas = alphaValues + (clipImage.getFormat() == Image::ARGB ? 3 : 0);
#endif
if (image.getFormat() == Image::RGB)
{
blendAlphaMapRGB (pixels, stride,
alphas, w, h,
pixelStride, alphaStride,
colour);
}
else if (image.getFormat() == Image::ARGB)
{
blendAlphaMapARGB (pixels, stride,
alphas, w, h,
pixelStride, alphaStride,
colour);
}
else
{
jassertfalse // not done!
}
clipImage.releasePixelDataReadOnly (alphaValues);
image.releasePixelDataReadWrite (pixels);
}
}
}
void LowLevelGraphicsSoftwareRenderer::fillAlphaChannelWithImage (const Image& alphaImage, int alphaImageX, int alphaImageY,
const Image& fillerImage, int fillerImageX, int fillerImageY)
{
alphaImageX += xOffset;
alphaImageY += yOffset;
fillerImageX += xOffset;
fillerImageY += yOffset;
for (RectangleList::Iterator i (*clip); i.next();)
{
const Rectangle& r = *i.getRectangle();
clippedFillAlphaChannelWithImage (r.getX(), r.getY(), r.getWidth(), r.getHeight(),
alphaImage, alphaImageX, alphaImageY,
fillerImage, fillerImageX, fillerImageY,
colour.getFloatAlpha());
}
}
void LowLevelGraphicsSoftwareRenderer::clippedFillAlphaChannelWithImage (int x, int y, int w, int h, const Image& alphaImage, int alphaImageX, int alphaImageY,
const Image& fillerImage, int fillerImageX, int fillerImageY, float opacity)
{
if (Rectangle::intersectRectangles (x, y, w, h, alphaImageX, alphaImageY, alphaImage.getWidth(), alphaImage.getHeight())
&& Rectangle::intersectRectangles (x, y, w, h, fillerImageX, fillerImageY, fillerImage.getWidth(), fillerImage.getHeight()))
{
int dstStride, dstPixStride;
uint8* const dstPix = image.lockPixelDataReadWrite (x, y, w, h, dstStride, dstPixStride);
int srcStride, srcPixStride;
const uint8* const srcPix = fillerImage.lockPixelDataReadOnly (x - fillerImageX, y - fillerImageY, w, h, srcStride, srcPixStride);
int maskStride, maskPixStride;
const uint8* const alpha
= alphaImage.lockPixelDataReadOnly (x - alphaImageX, y - alphaImageY, w, h, maskStride, maskPixStride);
#if JUCE_BIG_ENDIAN
const uint8* const alphaValues = alpha;
#else
const uint8* const alphaValues = alpha + (alphaImage.getFormat() == Image::ARGB ? 3 : 0);
#endif
const int extraAlpha = jlimit (0, 0x100, roundDoubleToInt (opacity * 256.0f));
if (image.getFormat() == Image::RGB)
{
if (fillerImage.getFormat() == Image::RGB)
{
renderAlphaMap ((PixelRGB*) dstPix, dstStride, (const PixelRGB*) srcPix, srcStride, alphaValues, maskStride, maskPixStride, w, h, extraAlpha);
}
else if (fillerImage.getFormat() == Image::ARGB)
{
renderAlphaMap ((PixelRGB*) dstPix, dstStride, (const PixelARGB*) srcPix, srcStride, alphaValues, maskStride, maskPixStride, w, h, extraAlpha);
}
else
{
jassertfalse // not done!
}
}
else if (image.getFormat() == Image::ARGB)
{
if (fillerImage.getFormat() == Image::RGB)
{
renderAlphaMap ((PixelARGB*) dstPix, dstStride, (const PixelRGB*) srcPix, srcStride, alphaValues, maskStride, maskPixStride, w, h, extraAlpha);
}
else if (fillerImage.getFormat() == Image::ARGB)
{
renderAlphaMap ((PixelARGB*) dstPix, dstStride, (const PixelARGB*) srcPix, srcStride, alphaValues, maskStride, maskPixStride, w, h, extraAlpha);
}
else
{
jassertfalse // not done!
}
}
else
{
jassertfalse // not done!
}
alphaImage.releasePixelDataReadOnly (alphaValues);
fillerImage.releasePixelDataReadOnly (srcPix);
image.releasePixelDataReadWrite (dstPix);
}
}
//==============================================================================
void LowLevelGraphicsSoftwareRenderer::blendImage (const Image& sourceImage, int dx, int dy, int dw, int dh, int sx, int sy)
{
dx += xOffset;
dy += yOffset;
for (RectangleList::Iterator i (*clip); i.next();)
{
const Rectangle& r = *i.getRectangle();
clippedBlendImage (r.getX(), r.getY(), r.getWidth(), r.getHeight(),
sourceImage, dx, dy, dw, dh, sx, sy);
}
}
void LowLevelGraphicsSoftwareRenderer::clippedBlendImage (int clipX, int clipY, int clipW, int clipH,
const Image& sourceImage, int dx, int dy, int dw, int dh, int sx, int sy)
{
if (dx < clipX)
{
sx += clipX - dx;
dw -= clipX - dx;
dx = clipX;
}
if (dy < clipY)
{
sy += clipY - dy;
dh -= clipY - dy;
dy = clipY;
}
if (dx + dw > clipX + clipW)
dw = clipX + clipW - dx;
if (dy + dh > clipY + clipH)
dh = clipY + clipH - dy;
if (dw <= 0 || dh <= 0)
return;
const uint8 alpha = (uint8) colour.getAlpha();
if (alpha == 0)
return;
int dstStride, dstPixelStride;
uint8* const dstPixels = image.lockPixelDataReadWrite (dx, dy, dw, dh, dstStride, dstPixelStride);
int srcStride, srcPixelStride;
const uint8* const srcPixels = sourceImage.lockPixelDataReadOnly (sx, sy, dw, dh, srcStride, srcPixelStride);
if (image.getFormat() == Image::ARGB)
{
if (sourceImage.getFormat() == Image::ARGB)
{
overlayImage ((PixelARGB*) dstPixels, dstStride,
(PixelARGB*) srcPixels, srcStride,
dw, dh, alpha);
}
else if (sourceImage.getFormat() == Image::RGB)
{
overlayImage ((PixelARGB*) dstPixels, dstStride,
(PixelRGB*) srcPixels, srcStride,
dw, dh, alpha);
}
else
{
jassertfalse
}
}
else if (image.getFormat() == Image::RGB)
{
if (sourceImage.getFormat() == Image::ARGB)
{
overlayImage ((PixelRGB*) dstPixels, dstStride,
(PixelARGB*) srcPixels, srcStride,
dw, dh, alpha);
}
else if (sourceImage.getFormat() == Image::RGB)
{
overlayImage ((PixelRGB*) dstPixels, dstStride,
(PixelRGB*) srcPixels, srcStride,
dw, dh, alpha);
}
else
{
jassertfalse
}
}
else
{
jassertfalse
}
image.releasePixelDataReadWrite (dstPixels);
sourceImage.releasePixelDataReadOnly (srcPixels);
}
//==============================================================================
void LowLevelGraphicsSoftwareRenderer::blendImageWarping (const Image& sourceImage,
int srcClipX, int srcClipY, int srcClipW, int srcClipH,
const AffineTransform& t)
{
const AffineTransform transform (t.translated ((float) xOffset, (float) yOffset));
for (RectangleList::Iterator i (*clip); i.next();)
{
const Rectangle& r = *i.getRectangle();
clippedBlendImageWarping (r.getX(), r.getY(), r.getWidth(), r.getHeight(),
sourceImage, srcClipX, srcClipY, srcClipW, srcClipH,
transform);
}
}
void LowLevelGraphicsSoftwareRenderer::clippedBlendImageWarping (int destClipX, int destClipY, int destClipW, int destClipH,
const Image& sourceImage,
int srcClipX, int srcClipY, int srcClipW, int srcClipH,
const AffineTransform& transform)
{
if ((! colour.isTransparent()) && destClipW > 0 && destClipH > 0 && ! transform.isSingularity())
{
Rectangle::intersectRectangles (srcClipX, srcClipY, srcClipW, srcClipH,
0, 0, sourceImage.getWidth(), sourceImage.getHeight());
if (srcClipW <= 0 || srcClipH <= 0)
return;
jassert (srcClipX >= 0 && srcClipY >= 0);
Path imageBounds;
imageBounds.addRectangle ((float) srcClipX, (float) srcClipY, (float) srcClipW, (float) srcClipH);
imageBounds.applyTransform (transform);
float imX, imY, imW, imH;
imageBounds.getBounds (imX, imY, imW, imH);
if (Rectangle::intersectRectangles (destClipX, destClipY, destClipW, destClipH,
(int) floorf (imX),
(int) floorf (imY),
1 + roundDoubleToInt (imW),
1 + roundDoubleToInt (imH)))
{
const uint8 alpha = (uint8) colour.getAlpha();
float srcX1 = (float) destClipX;
float srcY1 = (float) destClipY;
float srcX2 = (float) (destClipX + destClipW);
float srcY2 = srcY1;
float srcX3 = srcX1;
float srcY3 = (float) (destClipY + destClipH);
AffineTransform inverse (transform.inverted());
inverse.transformPoint (srcX1, srcY1);
inverse.transformPoint (srcX2, srcY2);
inverse.transformPoint (srcX3, srcY3);
const double lineDX = (double) (srcX3 - srcX1) / destClipH;
const double lineDY = (double) (srcY3 - srcY1) / destClipH;
const double pixelDX = (double) (srcX2 - srcX1) / destClipW;
const double pixelDY = (double) (srcY2 - srcY1) / destClipW;
if (image.getFormat() == Image::ARGB)
{
if (sourceImage.getFormat() == Image::ARGB)
{
transformedImageRender (image, sourceImage,
destClipX, destClipY, destClipW, destClipH,
srcClipX, srcClipY, srcClipW, srcClipH,
srcX1, srcY1, lineDX, lineDY, pixelDX, pixelDY,
alpha, interpolationQuality, (PixelARGB*)0, (PixelARGB*)0);
}
else if (sourceImage.getFormat() == Image::RGB)
{
transformedImageRender (image, sourceImage,
destClipX, destClipY, destClipW, destClipH,
srcClipX, srcClipY, srcClipW, srcClipH,
srcX1, srcY1, lineDX, lineDY, pixelDX, pixelDY,
alpha, interpolationQuality, (PixelARGB*)0, (PixelRGB*)0);
}
else
{
jassertfalse
}
}
else if (image.getFormat() == Image::RGB)
{
if (sourceImage.getFormat() == Image::ARGB)
{
transformedImageRender (image, sourceImage,
destClipX, destClipY, destClipW, destClipH,
srcClipX, srcClipY, srcClipW, srcClipH,
srcX1, srcY1, lineDX, lineDY, pixelDX, pixelDY,
alpha, interpolationQuality, (PixelRGB*)0, (PixelARGB*)0);
}
else if (sourceImage.getFormat() == Image::RGB)
{
transformedImageRender (image, sourceImage,
destClipX, destClipY, destClipW, destClipH,
srcClipX, srcClipY, srcClipW, srcClipH,
srcX1, srcY1, lineDX, lineDY, pixelDX, pixelDY,
alpha, interpolationQuality, (PixelRGB*)0, (PixelRGB*)0);
}
else
{
jassertfalse
}
}
else
{
jassertfalse
}
}
}
}
//==============================================================================
void LowLevelGraphicsSoftwareRenderer::drawLine (double x1, double y1, double x2, double y2)
{
x1 += xOffset;
y1 += yOffset;
x2 += xOffset;
y2 += yOffset;
for (RectangleList::Iterator i (*clip); i.next();)
{
const Rectangle& r = *i.getRectangle();
clippedDrawLine (r.getX(), r.getY(), r.getWidth(), r.getHeight(),
x1, y1, x2, y2);
}
}
void LowLevelGraphicsSoftwareRenderer::clippedDrawLine (int clipX, int clipY, int clipW, int clipH, double x1, double y1, double x2, double y2)
{
if (clipW > 0 && clipH > 0)
{
if (x1 == x2)
{
if (y2 < y1)
swapVariables (y1, y2);
clippedDrawVerticalLine (clipX, clipY, clipW, clipH, roundDoubleToInt (x1), y1, y2);
}
else if (y1 == y2)
{
if (x2 < x1)
swapVariables (x1, x2);
clippedDrawHorizontalLine (clipX, clipY, clipW, clipH, roundDoubleToInt (y1), x1, x2);
}
else
{
double gradient = (y2 - y1) / (x2 - x1);
if (fabs (gradient) > 1.0)
{
gradient = 1.0 / gradient;
int y = roundDoubleToInt (y1);
const int startY = y;
int endY = roundDoubleToInt (y2);
if (y > endY)
swapVariables (y, endY);
while (y < endY)
{
const double x = x1 + gradient * (y - startY);
clippedDrawHorizontalLine (clipX, clipY, clipW, clipH, y, x, x + 1.0);
++y;
}
}
else
{
int x = roundDoubleToInt (x1);
const int startX = x;
int endX = roundDoubleToInt (x2);
if (x > endX)
swapVariables (x, endX);
while (x < endX)
{
const double y = y1 + gradient * (x - startX);
clippedDrawVerticalLine (clipX, clipY, clipW, clipH, x, y, y + 1.0);
++x;
}
}
}
}
}
void LowLevelGraphicsSoftwareRenderer::drawVerticalLine (const int x, double top, double bottom)
{
for (RectangleList::Iterator i (*clip); i.next();)
{
const Rectangle& r = *i.getRectangle();
clippedDrawVerticalLine (r.getX(), r.getY(), r.getWidth(), r.getHeight(),
x + xOffset, top + yOffset, bottom + yOffset);
}
}
void LowLevelGraphicsSoftwareRenderer::clippedDrawVerticalLine (int clipX, int clipY, int clipW, int clipH,
const int x, double top, double bottom)
{
jassert (top <= bottom);
if (((unsigned int) (x - clipX)) < (unsigned int) clipW
&& top < clipY + clipH
&& bottom > clipY
&& clipW > 0)
{
if (top < clipY)
top = clipY;
if (bottom > clipY + clipH)
bottom = clipY + clipH;
if (bottom > top)
drawVertical (x, top, bottom);
}
}
void LowLevelGraphicsSoftwareRenderer::drawHorizontalLine (const int y, double left, double right)
{
for (RectangleList::Iterator i (*clip); i.next();)
{
const Rectangle& r = *i.getRectangle();
clippedDrawHorizontalLine (r.getX(), r.getY(), r.getWidth(), r.getHeight(),
y + yOffset, left + xOffset, right + xOffset);
}
}
void LowLevelGraphicsSoftwareRenderer::clippedDrawHorizontalLine (int clipX, int clipY, int clipW, int clipH,
const int y, double left, double right)
{
jassert (left <= right);
if (((unsigned int) (y - clipY)) < (unsigned int) clipH
&& left < clipX + clipW
&& right > clipX
&& clipW > 0)
{
if (left < clipX)
left = clipX;
if (right > clipX + clipW)
right = clipX + clipW;
if (right > left)
drawHorizontal (y, left, right);
}
}
void LowLevelGraphicsSoftwareRenderer::drawVertical (const int x,
const double top,
const double bottom)
{
int wholeStart = (int) top;
const int wholeEnd = (int) bottom;
const int lastAlpha = roundDoubleToInt (255.0 * (bottom - wholeEnd));
const int totalPixels = (wholeEnd - wholeStart) + (lastAlpha > 0 ? 1 : 0);
if (totalPixels <= 0)
return;
int lineStride, dstPixelStride;
uint8* const dstPixels = image.lockPixelDataReadWrite (x, wholeStart, 1, totalPixels, lineStride, dstPixelStride);
uint8* dest = dstPixels;
PixelARGB colour (this->colour.getPixelARGB());
if (wholeEnd == wholeStart)
{
if (image.getFormat() == Image::ARGB)
((PixelARGB*) dest)->blend (colour, roundDoubleToInt (255.0 * (bottom - top)));
else if (image.getFormat() == Image::RGB)
((PixelRGB*) dest)->blend (colour, roundDoubleToInt (255.0 * (bottom - top)));
else
{
jassertfalse
}
}
else
{
if (image.getFormat() == Image::ARGB)
{
((PixelARGB*) dest)->blend (colour, roundDoubleToInt (255.0 * (1.0 - (top - wholeStart))));
++wholeStart;
dest += lineStride;
if (colour.getAlpha() == 0xff)
{
while (wholeEnd > wholeStart)
{
((PixelARGB*) dest)->set (colour);
++wholeStart;
dest += lineStride;
}
}
else
{
while (wholeEnd > wholeStart)
{
((PixelARGB*) dest)->blend (colour);
++wholeStart;
dest += lineStride;
}
}
if (lastAlpha > 0)
{
((PixelARGB*) dest)->blend (colour, lastAlpha);
}
}
else if (image.getFormat() == Image::RGB)
{
((PixelRGB*) dest)->blend (colour, roundDoubleToInt (255.0 * (1.0 - (top - wholeStart))));
++wholeStart;
dest += lineStride;
if (colour.getAlpha() == 0xff)
{
while (wholeEnd > wholeStart)
{
((PixelRGB*) dest)->set (colour);
++wholeStart;
dest += lineStride;
}
}
else
{
while (wholeEnd > wholeStart)
{
((PixelRGB*) dest)->blend (colour);
++wholeStart;
dest += lineStride;
}
}
if (lastAlpha > 0)
{
((PixelRGB*) dest)->blend (colour, lastAlpha);
}
}
else
{
jassertfalse
}
}
image.releasePixelDataReadWrite (dstPixels);
}
void LowLevelGraphicsSoftwareRenderer::drawHorizontal (const int y,
const double top,
const double bottom)
{
int wholeStart = (int) top;
const int wholeEnd = (int) bottom;
const int lastAlpha = roundDoubleToInt (255.0 * (bottom - wholeEnd));
const int totalPixels = (wholeEnd - wholeStart) + (lastAlpha > 0 ? 1 : 0);
if (totalPixels <= 0)
return;
int lineStride, dstPixelStride;
uint8* const dstPixels = image.lockPixelDataReadWrite (wholeStart, y, totalPixels, 1, lineStride, dstPixelStride);
uint8* dest = dstPixels;
PixelARGB colour (this->colour.getPixelARGB());
if (wholeEnd == wholeStart)
{
if (image.getFormat() == Image::ARGB)
((PixelARGB*) dest)->blend (colour, roundDoubleToInt (255.0 * (bottom - top)));
else if (image.getFormat() == Image::RGB)
((PixelRGB*) dest)->blend (colour, roundDoubleToInt (255.0 * (bottom - top)));
else
{
jassertfalse
}
}
else
{
if (image.getFormat() == Image::ARGB)
{
((PixelARGB*) dest)->blend (colour, roundDoubleToInt (255.0 * (1.0 - (top - wholeStart))));
dest += dstPixelStride;
++wholeStart;
if (colour.getAlpha() == 0xff)
{
while (wholeEnd > wholeStart)
{
((PixelARGB*) dest)->set (colour);
dest += dstPixelStride;
++wholeStart;
}
}
else
{
while (wholeEnd > wholeStart)
{
((PixelARGB*) dest)->blend (colour);
dest += dstPixelStride;
++wholeStart;
}
}
if (lastAlpha > 0)
{
((PixelARGB*) dest)->blend (colour, lastAlpha);
}
}
else if (image.getFormat() == Image::RGB)
{
((PixelRGB*) dest)->blend (colour, roundDoubleToInt (255.0 * (1.0 - (top - wholeStart))));
dest += dstPixelStride;
++wholeStart;
if (colour.getAlpha() == 0xff)
{
while (wholeEnd > wholeStart)
{
((PixelRGB*) dest)->set (colour);
dest += dstPixelStride;
++wholeStart;
}
}
else
{
while (wholeEnd > wholeStart)
{
((PixelRGB*) dest)->blend (colour);
dest += dstPixelStride;
++wholeStart;
}
}
if (lastAlpha > 0)
{
((PixelRGB*) dest)->blend (colour, lastAlpha);
}
}
else
{
jassertfalse
}
}
image.releasePixelDataReadWrite (dstPixels);
}
//==============================================================================
void LowLevelGraphicsSoftwareRenderer::setFont (const Font& newFont)
{
font = newFont;
}
void LowLevelGraphicsSoftwareRenderer::drawGlyph (int glyphNumber, float x, float y)
{
font.renderGlyphIndirectly (*this, glyphNumber, x, y);
}
void LowLevelGraphicsSoftwareRenderer::drawGlyph (int glyphNumber, const AffineTransform& transform)
{
font.renderGlyphIndirectly (*this, glyphNumber, transform);
}
END_JUCE_NAMESPACE