C # Создание формата пикселей.Формат32bppArgb искажение изображения

#c# #bitmap #bitmapsource

#c# #растровое изображение #источник растрового изображения

Вопрос:

Я пытаюсь объединить 3 растровых изображения в оттенках серого в одно цветное растровое изображение. Все три изображения в оттенках серого имеют одинаковый размер (это основано на данных с Хаббла). Моя логика такова: загрузите «синее» изображение и преобразуйте в PixelFormat.Format24bppRgb . Исходя из этого, создайте новый массив байтов, который в 4 раза больше, чем длина массива данных синего цвета / 3 (так что это будет один байт для синего, один байт для зеленого, один байт для красного, один байт для альфа на пиксель, поскольку моя система имеет небольшой конец). Заполните «синие» байты массива из «синих» байтов синего изображения (и в этом первом цикле установите альфа-байт равным 255). Затем я загружаю зеленые и красные растровые изображения, преобразую их в PixelFormat.Format24bppRgb , извлекаю значение g / r и добавляю его в нужное место в массиве данных. Из того, что я могу сказать, в конечном массиве данных байты bgra установлены правильно.

Когда у меня есть заполненный массив данных, я использовал его для:

Создайте, а PixelFormats.Bgra32 BitmapSource затем преобразуйте его в растровое изображение.

Создайте PixelFormat.Format32bppArgb растровое изображение с помощью конструктора Bitmap (ширина, высота, шаг, PixelForma, IntPtr)

Создание PixelFormat.Format32bppArgb растрового изображения с использованием указателей

Все три способа создания возвращаемого растрового изображения приводят к тому, что изображение «искажается» (извините, я не знаю лучшего слова).

Фактический результат (из всех трех способов создания конечного растрового изображения): Фактический результат

Желаемый результат выглядит примерно так (это было сделано в Photoshop, поэтому он немного отличается): Желаемый результат

Три имени файлов ( _blueFileName, _greenFileName, _redFileName ) задаются в конструкторе, и я проверяю, существуют ли файлы перед созданием класса. Я могу опубликовать этот код, если кто-нибудь захочет.

Кто-нибудь может сказать мне, что я делаю не так? Я предполагаю, что это связано с шагом или чем-то в этом роде?

Примечание: я не могу публиковать ссылки на изображения, которые я использую в качестве входных данных, поскольку у меня нет 10 очков репутации. Может быть, я мог бы отправить ссылки по электронной почте или что-то в этом роде, если они кому-то тоже нужны.

Вот мой код (с некоторыми комментариями, комментарии описывают, что произойдет, если вместо этого будет использоваться каждый закомментированный блок):

     public Bitmap Merge()
    {
        //  Load original "blue" bitmap.
        Bitmap tblueBitmap = (Bitmap)Image.FromFile(_blueFileName);
        int width = tblueBitmap.Width;
        int height = tblueBitmap.Height;
        //  Convert to 24 bpp rgb (which is bgr on little endian machines)
        Bitmap blueBitmap = new Bitmap(width, height, PixelFormat.Format24bppRgb);
        using (Graphics gr = Graphics.FromImage(blueBitmap))
        {
            gr.DrawImage(tblueBitmap, 0, 0, width, height);
        }
        tblueBitmap.Dispose();
        //  Lock and copy to byte array.
        BitmapData blueData = blueBitmap.LockBits(new Rectangle(0, 0, blueBitmap.Width, blueBitmap.Height), ImageLockMode.ReadOnly,
            blueBitmap.PixelFormat);
        int numbBytes = blueData.Stride*blueBitmap.Height;
        byte[] blueBytes = new byte[numbBytes];
        Marshal.Copy(blueData.Scan0, blueBytes, 0, numbBytes);
        blueBitmap.UnlockBits(blueData);
        blueData = null;
        blueBitmap.Dispose();
        int mult = 4;
        byte[] data = new byte[(numbBytes/3)*mult];
        int count = 0;
        //  Copy every third byte starting at 0 to the final data array (data).
        for (int i = 0; i < data.Length / mu< i  )
        {
            //  Check for overflow
            if (blueBytes.Length <= count*3   2)
            {
                continue;
            }
            //  First pass, set Alpha channel.
            data[i * mult   3] = 255;
            //  Set blue byte.
            data[i*mult] = blueBytes[count*3];
            count  ;
        }
        //  Cleanup.
        blueBytes = null;
        int generation = GC.GetGeneration(this);
        GC.Collect(generation);

        Bitmap tgreenBitmap = (Bitmap)Image.FromFile(_greenFileName);
        Bitmap greenBitmap = new Bitmap(width, height, PixelFormat.Format24bppRgb);
        using (Graphics gr = Graphics.FromImage(greenBitmap))
        {
            gr.DrawImage(tgreenBitmap, 0, 0, width, height);
        }
        tgreenBitmap.Dispose();
        BitmapData greenData = greenBitmap.LockBits(new Rectangle(0, 0, greenBitmap.Width, greenBitmap.Height), ImageLockMode.ReadOnly,
            greenBitmap.PixelFormat);
        numbBytes = greenData.Stride * greenBitmap.Height;
        byte[] greenBytes = new byte[numbBytes];
        Marshal.Copy(greenData.Scan0, greenBytes, 0, numbBytes);
        greenBitmap.UnlockBits(greenData);
        greenData = null;
        greenBitmap.Dispose();
        count = 0;
        for (int i = 0; i < data.Length / mu< i  )
        {
            if (greenBytes.Length <= count * 3   1)
            {
                continue;
            }
            //  Set green byte
            data[i * mult   1] = greenBytes[count * 3   1];
            count  ;
        }
        greenBytes = null;
        generation = GC.GetGeneration(this);
        GC.Collect(generation);

        Bitmap tredBitmap = (Bitmap)Image.FromFile(_redFileName);
        Bitmap redBitmap = new Bitmap(width, height, PixelFormat.Format24bppRgb);
        using (Graphics gr = Graphics.FromImage(redBitmap))
        {
            gr.DrawImage(tredBitmap, 0, 0, width, height);
        }
        tredBitmap.Dispose();
        BitmapData redData = redBitmap.LockBits(new Rectangle(0, 0, redBitmap.Width, redBitmap.Height), ImageLockMode.ReadOnly,
            redBitmap.PixelFormat);
        numbBytes = redData.Stride * redBitmap.Height;
        byte[] redBytes = new byte[numbBytes];
        Marshal.Copy(redData.Scan0, redBytes, 0, numbBytes);
        redBitmap.UnlockBits(redData);
        redData = null;
        redBitmap.Dispose();
        count = 0;
        for (int i = 0; i < data.Length / mu< i  )
        {
            if (redBytes.Length <= count * 3 2)
            {
                count  ;
                continue;
            }
            // set red byte
            data[i * mult   2] = redBytes[count * 3   2];
            count  ;
        }
        redBytes = null;
        generation = GC.GetGeneration(this);
        GC.Collect(generation);
        int stride = (width*32   7)/8;
        var bi = BitmapSource.Create(width, height, 96, 96, PixelFormats.Bgra32, null, data, stride);
        //  uncomment out below to see what a bitmap source to bitmap does.  So far, it is exactly the same as 
        //  the uncommented out lines below.
        //  ---------------------------------------------------------------------------------------------------
        //return BitmapImage2Bitmap(bi);
        unsafe
        {
            fixed (byte* p = data)
            {
                IntPtr ptr = (IntPtr)p;
                //  Trying the commented out lines returns the same bitmap as the uncommented out lines.
                //  ------------------------------------------------------------------------------------
                byte* p2 = (byte*)ptr;
                Bitmap retBitmap = new Bitmap(width, height, PixelFormat.Format32bppArgb);
                BitmapData fData = retBitmap.LockBits(new Rectangle(0, 0, width, height), ImageLockMode.ReadWrite,
                    PixelFormat.Format32bppArgb);
                unsafe
                {
                    for (int i = 0; i < fData.Height; i  )
                    {
                        byte* imgPtr = (byte*)(fData.Scan0   (fData.Stride * i));
                        for (int x = 0; x < fData.Width; x  )
                        {
                            for (int ii = 0; ii < 4; ii  )
                            {
                                *imgPtr   = *p2  ;
                            }
                            //*imgPtr   = 255;
                        }
                    }
                }
                retBitmap.UnlockBits(fData);
                //Bitmap retBitmap = new Bitmap(width, height, GetStride(width, PixelFormat.Format32bppArgb),
                //    PixelFormat.Format32bppArgb, ptr);
                return retBitmap;
            }
        }

    }

    private Bitmap BitmapImage2Bitmap(BitmapSource bitmapSrc)
    {
        using (MemoryStream outStream = new MemoryStream())
        {
            BitmapEncoder enc = new BmpBitmapEncoder();
            enc.Frames.Add(BitmapFrame.Create(bitmapSrc));
            enc.Save(outStream);
            Bitmap bitmap = new Bitmap(outStream);
            return new Bitmap(bitmap);
        }
    }

    private int GetStride(int width, PixelFormat pxFormat)
    {
        int bitsPerPixel = ((int)pxFormat >> 8) amp; 0xFF;
        int validBitsPerLine = width * bitsPerPixel;
        int stride = ((validBitsPerLine   31) / 32) * 4;
        return stride;
    }
  

Ответ №1:

Вам не хватает промежутка между строками. Stride Значение — это не количество данных в строке, а расстояние от начала одной строки до следующей. В конце каждой строки может быть пробел для выравнивания следующей строки по четной границе адреса.

Stride Значение может быть даже отрицательным, тогда изображение сохраняется в памяти вверх ногами. Чтобы получить данные без пробелов и обработать все случаи, вам нужно копировать по одной строке за раз:

 BitmapData blueData = blueBitmap.LockBits(new Rectangle(0, 0, blueBitmap.Width, blueBitmap.Height), ImageLockMode.ReadOnly, blueBitmap.PixelFormat);
int lineBytes = blueBitmap.Width * 3;
int numbBytes = lineBytes * blueBitmap.Height;
byte[] blueBytes = new byte[numbBytes];
for (int y = 0; y < blueBitmap.Height; y  ) {
  Marshal.Copy(blueData.Scan0   y * blueData.Stride, blueBytes, y * lineBytes, lineBytes);
}
blueBitmap.UnlockBits(blueData);
blueBitmap.Dispose();
  

Комментарии:

1. Выровнено кратно 4, не «четно». Растровые изображения обычно перевернуты, шаг отрицательный, когда первая строка развертки имеет смещение 0.

2. @HansPassant: четная граница адреса — это не то же самое, что «четный адрес». Что касается шага, вы ошибаетесь. Шаг — это смещение от одной строки сканирования к следующей, поэтому, когда растровое изображение перевернуто, шаг отрицательный. Вы добавляете шаг к адресу, чтобы перейти к следующей строке сканирования, вы не вычитаете его.

3. @Guffa спасибо. Я думал, что шаг — это количество данных в строке. Знание того, что это полная ширина строки (данные отступы для перехода к следующей четной границе адреса), действительно многое проясняет для меня.