LockBits image rotation method not working?
Asked Answered
V

1

0

Hey all. I resorted to using LockBits for 2d bitmap image rotation after getting fed up with the slow performance and wacky behavior of both Get/Set Pixel, and RotateTransfom. So here is the code I've come up with, and by my reckoning, it should work perfectly. It doesn't.

private static void InternalRotateImage(Bitmap originalBitmap, Bitmap rotatedBitmap, PointF centerPoint, float theta)
    {
        BitmapData originalData = originalBitmap.LockBits(
            new Rectangle(0, 0, originalBitmap.Width, originalBitmap.Height),
            ImageLockMode.ReadOnly,
            originalBitmap.PixelFormat);

        BitmapData rotatedData = rotatedBitmap.LockBits(
            new Rectangle(0, 0, rotatedBitmap.Width, rotatedBitmap.Height),
            ImageLockMode.WriteOnly,
            rotatedBitmap.PixelFormat);

        unsafe
        {
            byte[,] A = new byte[originalData.Height * 2, originalBitmap.Width * 2];
            byte[,] R = new byte[originalData.Height * 2, originalBitmap.Width * 2];
            byte[,] G = new byte[originalData.Height * 2, originalBitmap.Width * 2];
            byte[,] B = new byte[originalData.Height * 2, originalBitmap.Width * 2];

            for (int y = 0; y < originalData.Height; y++)
            {
                byte* row = (byte*)originalData.Scan0 + (y * originalData.Stride);
                for (int x = 0; x < originalData.Width; x++)
                {
                    B[y, x] = row[x * 4];
                    G[y, x] = row[x * 4 + 1];
                    R[y, x] = row[x * 4 + 2];
                    A[y, x] = row[x * 4 + 3];
                }
            }

            for (int y = 0; y < rotatedData.Height; y++)
            {
                byte* row = (byte*)rotatedData.Scan0 + (y * rotatedData.Stride);
                for (int x = 0; x < rotatedData.Width; x++)
                {
                    int newy = (int)Math.Abs((Math.Cos(theta) * (x - centerPoint.X) - Math.Sin(theta) * (y - centerPoint.Y) + centerPoint.X));
                    int newx = (int)Math.Abs((Math.Sin(theta) * (x - centerPoint.X) + Math.Cos(theta) * (y - centerPoint.Y) + centerPoint.Y));

                    row[x * 4] = B[newy, newx];
                    row[x * 4 + 1] = G[newy, newx];
                    row[x * 4 + 2] = R[newy, newx];
                    row[x * 4 + 3] = A[newy, newx];
                }
            }

        }
            originalBitmap.UnlockBits(originalData);
            rotatedBitmap.UnlockBits(rotatedData);
        }

Anybody got any ideas? I'm fresh out. Thanks in advance!

EDIT: This is what I ended up using (many thanks to Hans Passant):

private Image RotateImage(Image img, float rotationAngle)
    {
        Image image = new Bitmap(img.Width * 2, img.Height * 2);
        Graphics gfx = Graphics.FromImage(image);

        int center = (int)Math.Sqrt(img.Width * img.Width + img.Height * img.Height) / 2;
        gfx.TranslateTransform(center, center);
        gfx.RotateTransform(rotationAngle);
        gfx.DrawImage(img, -img.Width / 2, -img.Height / 2);

        return image;
    }

It's the same thing as his, just on a per image basis, as opposed to a form.

Voltameter answered 5/10, 2010 at 0:0 Comment(2)
What are you trying to accomplish with the four byte[,] A = new byte[... lines? I don't understand why you're creating a two-dimensional byte array where one dimension is twice the original bitmap's height and the second dimension is the height times the width.Tottering
Sorry, the height width one was an old edit. I edited it to reflect whats correct... And I times them both by 2 in order to make sure they can handle of ranges of the rotation.Voltameter
L
2

You are digging yourself a deeper hole. This goes wrong early, the size of the rotated bitmap is not Width x Height. It is also very inefficient. You need to get RotateTransform going, it is important to also use TranslateTransform and pick the correct image drawing location.

Here's a sample Windows Forms app that rotates a bitmap around its center point, offset just enough to touch the inner edge of the form when it rotates. Drop a Timer on the form and add an image resource with Project + Properties, Resource tab. Name it SampleImage, it doesn't have to be square. Make the code look like this:

public partial class Form1 : Form {
    private float mDegrees;
    private Image mBmp;
    public Form1() {
        InitializeComponent();
        mBmp = Properties.Resources.SampleImage;
        timer1.Enabled = true;
        timer1.Interval = 50;
        timer1.Tick += new System.EventHandler(this.timer1_Tick);
        this.DoubleBuffered = true;
    }
    private void timer1_Tick(object sender, EventArgs e) {
        mDegrees += 3.0F;
        this.Invalidate();
    }
    protected override void OnPaint(PaintEventArgs e) {
        int center = (int)Math.Sqrt(mBmp.Width * mBmp.Width + mBmp.Height * mBmp.Height) / 2;
        e.Graphics.TranslateTransform(center, center);
        e.Graphics.RotateTransform(mDegrees);
        e.Graphics.DrawImage(mBmp, -mBmp.Width/2, -mBmp.Height/2);
    }
}

You can make draw a lot faster by creating a bitmap in the 32bppPArgb format, I skipped that step.

Lukash answered 5/10, 2010 at 3:34 Comment(3)
Is it just me, or does the 1st row and column get screwed up during the rotate? In my case, I fixed it with empty pixels for them, and just set my image to be one pixel larger than I needed.Voltameter
Not sure what you mean. You many need to round up center if the image size is divisible by two. Only an odd sized bitmap has a perfect center pixel. Or use the overloads of these methods that take float.Lukash
@ Hans: With any picture, the 1st row and column are distorted. Look at it for yourself...Voltameter

© 2022 - 2024 — McMap. All rights reserved.