I agree with valley that there is an offset problem when scaling an image bilinear, but I don’t agree with that solution.
But first there is an other problem to deal with. I found this one while finding a better solution for the offset problem.
In LowLevelGraphicsSoftwareRenderer::clippedBlendImageWarping (where transformedImageRender is used) the intersection of the transformed image bounds and the clipping rectangle is calculated. But what’s the reason for adding 1 to the width and the height of the transformed image bounds. This leads to incorrect results, e.g. if I draw an 100x100px image scaled-to-fit with drawImageWithin to an 300x300px rectangle I get and 301x301px result drawn.
So I suggest to remove both +1 there, if no one can explain why they are there:
[code] 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),
Back to the offset problem. Subtracting a fixed value doesn’t work correct, e.g. it breaks the drawing of the tray icon on Linux by shifting the icon image 1px down and right leading to a black border on the top and left.
To solve the problem the offset must be variable and dependent on the source and destination sizes. While testing I noticed that removing the two floor calls in the nearest neighbourhood path and also applying the initial offset gives better results.
[code]template <class DestPixelType, class SrcPixelType>
static void transformedImageRender (Image& destImage,
[…]
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);
-
const double srcXoffset = (1.0 - (double) srcClipWidth / destClipW) / 2.0;
-
const double srcYoffset = (1.0 - (double) srcClipHeight / destClipH) / 2.0;
if (quality == Graphics::lowResamplingQuality) // nearest-neighbour…
{
for (int y = 0; y < destClipH; ++y)
{
-
double sx = srcX;
-
double sy = srcY;
-
const int iy = roundDoubleToInt (sy) - srcClipY;
if (((unsigned int) iy) < (unsigned int) srcClipHeight)
[...]
}
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 - 0.5;
-
double sy = srcY - 0.5;
These images show the difference (they’re small but important):