3

For a couple of days now I've tried to figure out why my nine-slice code does not work as expected. As far as I can see, there seems to be an issue with the Graphics.DrawImage method which handles my nine slice images incorrectly. So my problem is how to compensate for the incorrect scaling that is performed when running my code on the compact framework. I might add that this code of course works perfectly when running in the full framework environment. The problem only occurs when scaling the image to a larger image not the other way around. Here is the snippet:

public class NineSliceBitmapSnippet
{
    private Bitmap m_OriginalBitmap;

    public int CornerLength { get; set; }

    /// <summary>
    /// Initializes a new instance of the NineSliceBitmapSnippet class.
    /// </summary>
    public NineSliceBitmapSnippet(Bitmap bitmap)
    {
        CornerLength = 5;
        m_OriginalBitmap = bitmap;
    }

    public Bitmap ScaleSingleBitmap(Size size)
    {
        Bitmap scaledBitmap = new Bitmap(size.Width, size.Height);
        int[] horizontalTargetSlices = Slice(size.Width);
        int[] verticalTargetSlices = Slice(size.Height);

        int[] horizontalSourceSlices = Slice(m_OriginalBitmap.Width);
        int[] verticalSourceSlices = Slice(m_OriginalBitmap.Height);

        using (Graphics graphics = Graphics.FromImage(scaledBitmap))
        {
            using (Brush brush = new SolidBrush(Color.Fuchsia))
            {
                graphics.FillRectangle(brush, new Rectangle(0, 0, size.Width, size.Height));
            }

            int horizontalTargetOffset = 0;
            int verticalTargetOffset = 0;

            int horizontalSourceOffset = 0;
            int verticalSourceOffset = 0;

            for (int x = 0; x < horizontalTargetSlices.Length; x++)
            {
                verticalTargetOffset = 0;
                verticalSourceOffset = 0;
                for (int y = 0; y < verticalTargetSlices.Length; y++)
                {
                    Rectangle destination = new Rectangle(horizontalTargetOffset, verticalTargetOffset, horizontalTargetSlices[x], verticalTargetSlices[y]);
                    Rectangle source = new Rectangle(horizontalSourceOffset, verticalSourceOffset, horizontalSourceSlices[x], verticalSourceSlices[y]);

                    graphics.DrawImage(m_OriginalBitmap, destination, source, GraphicsUnit.Pixel);

                    verticalTargetOffset += verticalTargetSlices[y];
                    verticalSourceOffset += verticalSourceSlices[y];
                }

                horizontalTargetOffset += horizontalTargetSlices[x];
                horizontalSourceOffset += horizontalSourceSlices[x];
            }
        }
        return scaledBitmap;
    }

    public int[] Slice(int length)
    {
        int cornerLength = CornerLength;

        if (length <= (cornerLength * 2))
            throw new Exception("Image to small for sliceing up");

        int[] slices = new int[3];
        slices[0] = cornerLength;
        slices[1] = length - (2 * cornerLength);
        slices[2] = cornerLength;

        return slices;
    }
}

So, my question is, does anybody now how I could compensate the incorrect scaling?

/Dan

Nick
  • 1,903
  • 2
  • 21
  • 40
  • What is the error? Or can you link an image of what isn't working? – LarsTech Jul 21 '11 at 12:28
  • The problem is, that when I stitch the image together again, scaled to a larger image, the pieces that are stretched in only one direction will get stretched in the oposite direction with one pixel stretch as well. I will try to post an image of incorrect DrawImage-behaviour when I get higher rating of my reputation. – Dan Persson Jul 21 '11 at 13:16
  • It seems that the compact framework version of DrawImage, when running on a smart device (WCE), has problems with stretched images in only one direction thus giving this incorrect behaviour. If I stretch an image in both directions it will be done correct. Summing the issue up: Using DrawImage to stretch an image horizontally will unexpectedly cause the image to be stretched, ever so slightly, in the vertical direction as well. – Dan Persson Jul 22 '11 at 07:19

1 Answers1

1

After some more trial and error I've finally found a solution to my problem. The scaling problems has always been to the top-center, right-center, bottom-center and left-center slices since they're always stretched in only one direction according to the logic of nine slice scaling. If I apply a temporarely square stretch to those slices before applying the correct stretch the final bitmap will be correct. Once again the problem is only visible in the .Net Compact Framework of a Windows CE device (Smart Device). Here's a snippet with code adjusting for the bug in CF. My only concern now is that the slices that get square stretched will take much more memory due to the correction code. On the other hand this step is only a short period of time so I might get away with it. ;)

    public class NineSliceBitmapSnippet
{
    private Bitmap m_OriginalBitmap;

    public int CornerLength { get; set; }

    public NineSliceBitmapSnippet(Bitmap bitmap)
    {
        CornerLength = 5;
        m_OriginalBitmap = bitmap;
    }

    public Bitmap Scale(Size size)
    {
        if (m_OriginalBitmap != null)
        {
            return ScaleSingleBitmap(size);
        }

        return null;
    }

    public Bitmap ScaleSingleBitmap(Size size)
    {
        Bitmap scaledBitmap = new Bitmap(size.Width, size.Height);
        int[] horizontalTargetSlices = Slice(size.Width);
        int[] verticalTargetSlices = Slice(size.Height);

        int[] horizontalSourceSlices = Slice(m_OriginalBitmap.Width);
        int[] verticalSourceSlices = Slice(m_OriginalBitmap.Height);

        using (Graphics graphics = Graphics.FromImage(scaledBitmap))
        {
            using (Brush brush = new SolidBrush(Color.Fuchsia))
            {
                graphics.FillRectangle(brush, new Rectangle(0, 0, size.Width, size.Height));
            }

            int horizontalTargetOffset = 0;
            int verticalTargetOffset = 0;

            int horizontalSourceOffset = 0;
            int verticalSourceOffset = 0;

            for (int x = 0; x < horizontalTargetSlices.Length; x++)
            {
                verticalTargetOffset = 0;
                verticalSourceOffset = 0;
                for (int y = 0; y < verticalTargetSlices.Length; y++)
                {
                    Rectangle destination = new Rectangle(horizontalTargetOffset, verticalTargetOffset, horizontalTargetSlices[x], verticalTargetSlices[y]);
                    Rectangle source = new Rectangle(horizontalSourceOffset, verticalSourceOffset, horizontalSourceSlices[x], verticalSourceSlices[y]);

                    bool isWidthAffectedByVerticalStretch = (y == 1 && (x == 0 || x == 2) && destination.Height > source.Height);
                    bool isHeightAffectedByHorizontalStretch = (x == 1 && (y == 0 || y == 2) && destination.Width > source.Width);
                    if (isHeightAffectedByHorizontalStretch)
                    {
                        BypassDrawImageError(graphics, destination, source, Orientation.Horizontal);
                    }
                    else if (isWidthAffectedByVerticalStretch)
                    {
                        BypassDrawImageError(graphics, destination, source, Orientation.Vertical);
                    }
                    else
                    {
                        graphics.DrawImage(m_OriginalBitmap, destination, source, GraphicsUnit.Pixel);
                    }

                    verticalTargetOffset += verticalTargetSlices[y];
                    verticalSourceOffset += verticalSourceSlices[y];
                }

                horizontalTargetOffset += horizontalTargetSlices[x];
                horizontalSourceOffset += horizontalSourceSlices[x];
            }
        }
        return scaledBitmap;
    }

    private void BypassDrawImageError(Graphics graphics, Rectangle destination, Rectangle source, Orientation orientationAdjustment)
    {
        Size adjustedSize = Size.Empty;
        switch (orientationAdjustment)
        {
            case Orientation.Horizontal:
                adjustedSize = new Size(destination.Width, destination.Width);
                break;
            case Orientation.Vertical:
                adjustedSize = new Size(destination.Height, destination.Height);
                break;
            default:
                break;
        }

        using (Bitmap quadScaledBitmap = new Bitmap(adjustedSize.Width, adjustedSize.Height))
        {
            using (Graphics tempGraphics = Graphics.FromImage(quadScaledBitmap))
            {
                tempGraphics.Clear(Color.Fuchsia);
                tempGraphics.DrawImage(m_OriginalBitmap, new Rectangle(0, 0, adjustedSize.Width, adjustedSize.Height), source, GraphicsUnit.Pixel);
            }
            graphics.DrawImage(quadScaledBitmap, destination, new Rectangle(0, 0, quadScaledBitmap.Width, quadScaledBitmap.Height), GraphicsUnit.Pixel);
        }
    }

    public int[] Slice(int length)
    {
        int cornerLength = CornerLength;

        if (length <= (cornerLength * 2))
            throw new Exception("Image to small for sliceing up");

        int[] slices = new int[3];
        slices[0] = cornerLength;
        slices[1] = length - (2 * cornerLength);
        slices[2] = cornerLength;

        return slices;
    }
}