Почему некоторые картинки изогнуты, используя мою функцию? - PullRequest
1 голос
/ 13 января 2011
struct BitmapDataAccessor
{
    private readonly byte[] data;
    private readonly int[] rowStarts;
    public readonly int Height;
    public readonly int Width;

    public BitmapDataAccessor(byte[] data, int width, int height)
    {
        this.data = data;
        this.Height = height;
        this.Width = width;
        rowStarts = new int[height];
        for (int y = 0; y < Height; y++)
            rowStarts[y] = y * width;
    }

    public byte this[int x, int y, int color] // Maybe use an enum with Red = 0, Green = 1, and Blue = 2 members?
    {
        get { return data[(rowStarts[y] + x) * 3 + color]; }
        set { data[(rowStarts[y] + x) * 3 + color] = value; }
    }

    public byte[] Data
    {
        get { return data; }
    }
}

    public static byte[, ,] Bitmap2Byte(Bitmap obraz)
    {
        int h = obraz.Height;
        int w = obraz.Width;

        byte[, ,] wynik = new byte[w, h, 3];

        BitmapData bd = obraz.LockBits(new Rectangle(0, 0, w, h), ImageLockMode.ReadOnly, PixelFormat.Format24bppRgb);

        int bytes  = Math.Abs(bd.Stride) * h;
        byte[] rgbValues = new byte[bytes];
        IntPtr ptr = bd.Scan0;
        System.Runtime.InteropServices.Marshal.Copy(ptr, rgbValues, 0, bytes);

        BitmapDataAccessor bda = new BitmapDataAccessor(rgbValues, w, h);

        for (int i = 0; i < h; i++)
        {
            for (int j = 0; j < w; j++)
            {
                wynik[j, i, 0] = bda[j, i, 2];
                wynik[j, i, 1] = bda[j, i, 1];
                wynik[j, i, 2] = bda[j, i, 0];
            }
        }

        obraz.UnlockBits(bd);
        return wynik;
    }

    public static Bitmap Byte2Bitmap(byte[, ,] tablica)
    {
        if (tablica.GetLength(2) != 3)
        {
            throw new NieprawidlowyWymiarTablicyException();
        }

        int w = tablica.GetLength(0);
        int h = tablica.GetLength(1);

        Bitmap obraz = new Bitmap(w, h, PixelFormat.Format24bppRgb);

        for (int i = 0; i < w; i++)
        {
            for (int j = 0; j < h; j++)
            {
                Color kol = Color.FromArgb(tablica[i, j, 0], tablica[i, j, 1], tablica[i, j, 2]);
                obraz.SetPixel(i, j, kol);
            }
        }

        return obraz;
    }

Теперь, если я сделаю:

    private void btnLoad_Click(object sender, EventArgs e)
    {
        if (dgOpenFile.ShowDialog() == DialogResult.OK)
        {
            try
            {
                Bitmap img = new Bitmap(dgOpenFile.FileName);

                byte[, ,] tab = Grafika.Bitmap2Byte(img);

                picture.Image = Grafika.Byte2Bitmap(tab);
                picture.Size = img.Size;


            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message);
            }
        }
    }

Большинство картинок обрабатываются правильно, но не очень. Пример картинки, которая не работает:

http://ifotos.pl/img/1018038_hpnarpq.jpg

Это дает следующий результат (это только фрагмент изображения):

http://ifotos.pl/img/example_hpnarhp.jpg

Почему это?

Ответы [ 3 ]

3 голосов
/ 13 января 2011

Вам необходимо учитывать BitmapData.Stride при доступе к данным.

alt text

РЕДАКТИРОВАТЬ:

Вот решениечто я использую для копирования поверхности DirectX в растровое изображение.Идея та же, но вам нужно немного ее изменить.Я копирую по одной строчке изображения за раз с вызовом RtlMoveMemory (P / Invoke to kernel32.dll)

//// Snippet

        int pitch;
        int bytesPerPixel = 4;
        Rectangle lockRectangle = new Rectangle(0, 0, bitmap.Width, bitmap.Height);

        // Lock the bitmap
        GraphicsStream surfacedata = surface.LockRectangle(LockFlags.ReadOnly, out pitch);
        BitmapData bitmapdata = bitmap.LockBits(lockRectangle, ImageLockMode.WriteOnly, PixelFormat.Format32bppRgb);

        // Copy surface to bitmap
        for (int scanline = 0; scanline < bitmap.Height; ++scanline)
        {
            byte* dest = (byte*)bitmapdata.Scan0 + (scanline * bitmap.Width * bytesPerPixel);
            byte* source = (byte*)surfacedata.InternalData + (scanline * pitch);

            RtlMoveMemory(new IntPtr(dest), new IntPtr(source), (bitmap.Width * bytesPerPixel));
        }

////

РЕДАКТИРОВАНИЕ # 2:

Проверьте это: Учебное пособие по шагам / шагам

Все это нацелено на DirectX, но концепция та же самая.

1 голос
/ 13 января 2011

Спасибо @Lazarus и tbridge Мне удалось, как это сделать.

Сначала нам нужно вычислить отступ в Bitmap2Byte:

int padding  = bd.Stride - (((w * 24) + 7) / 8);

и передайте его BitmapDataAccessor и измените строку

this.Width = width;

до

this.Width = width + (4-padding)%4;

Вот и все. Спасибо, ребята.

1 голос
/ 13 января 2011

Кажется, что память, выделенная для растровых изображений, должна быть выровнена по 32-битной границе, и поэтому возможно, что некоторые изображения заполнены из-за их размера.Так как у вас здесь есть 24-битный пиксель, то некоторые ширины линий заканчиваются на 32-битных, другие - нет.Вам нужно использовать следующую формулу для определения используемого заполнения, а затем учесть его:

int padding  = bd.Stride - (((w * 24) + 7) / 8);

Возможно, вы захотите загрузить свой байтовый массив, используя GetPixel (x, y), а не проходить через весьпреобразовать в байтовый массив, прежде чем начать чтение пикселей.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...