Чтение и запись непосредственно в неуправляемую память Unlocked Bitmap (Scan0)

Можно ли писать и читать напрямую из неуправляемой памяти с разблокированным растровым изображением?

Могу ли я продолжать использовать BitmapData после разблокировки битов битовой карты? Я сделал тестовое приложение, в котором я могу прочитать пиксель растрового изображения PictureBox в позиции мыши, в то время как другой поток записывает пиксели в то же растровое изображение.

РЕДАКТИРОВАТЬ 1: КакBoing указал в своем ответе:Scan0 не указывает на фактические данные пикселей объекта Bitmap; скорее он указывает на временный буфер, который представляет часть данных пикселей в объекте Bitmap. " отMSDN.

Но как только я получу Scan0, яЯ могу читать / записывать в Bitmap без необходимости Lockbit или UnlockBits! Я'Я делаю это много раз в потоке. В соответствии с MSDN, это не должно происходить, потому что Scan0 указывает на КОПИЮ битовых данных! Ну, в C # весь тест показывает, что это не копия. В C ++ я нене знаю, работает ли он так, как должен.

РЕДАКТИРОВАТЬ 2: Использование метода поворота иногда приводит к тому, что ОС освобождает копию данных растрового изображения. Заключение,it is not safe to read/write an unlocked Bitmap Scan0, Спасибо Boing за ваш ответ и комментарии!

Ниже описано, как получить BitmapData, прочитать и записать значение пикселя.

    /// 
    /// Locks and unlocks the Bitmap to get the BitmapData.
    /// 
    /// Bitmap
    /// BitmapData
    public static BitmapData GetBitmapData(Bitmap bmp)
    {
        BitmapData bmpData = bmp.LockBits(new Rectangle(0, 0, bmp.Width, bmp.Height), ImageLockMode.ReadOnly, bmp.PixelFormat);
        bmp.UnlockBits(bmpData);
        return bmpData;
    }

    /// 
    /// Get pixel directly from unamanged pixel data based on the Scan0 pointer.
    /// 
    /// BitmapData of the Bitmap to get the pixel
    /// Pixel position
    /// Channel
    /// Pixel value
    public static byte GetPixel(BitmapData bmpData, Point p, int channel)
    {
        if ((p.X > bmpData.Width - 1) || (p.Y > bmpData.Height - 1))
            throw new ArgumentException("GetPixel Point p is outside image bounds!");

        int bitsPerPixel = ((int)bmpData.PixelFormat >> 8) & 0xFF;
        int bpp = bitsPerPixel / 8;
        byte data;
        int id = p.Y * bmpData.Stride + p.X * bpp;
        unsafe
        {
            byte* pData = (byte*)bmpData.Scan0;
            data = pData[id + channel];
        }
        return data;
    }

    //Non UI Thread
    private void DrawtoBitmapLoop()
    {
        while (_drawBitmap)
        {
            _drawPoint = new Point(_drawPoint.X + 10, _drawPoint.Y + 10);
            if (_drawPoint.X > _backImageData.Width - 20)
                _drawPoint.X = 0;
            if (_drawPoint.Y > _backImageData.Height - 20)
                _drawPoint.Y = 0;

            DrawToScan0(_backImageData, _drawPoint, 1);

            Thread.Sleep(10);
        }
    }

    private static void DrawToScan0(BitmapData bmpData, Point start, int channel = 0)
    {
        int x = start.X;
        int y = start.Y;
        int bitsPerPixel = ((int)bmpData.PixelFormat >> 8) & 0xFF;
        int bpp = bitsPerPixel / 8;
        for (int i = 0; i < 10; i++)
        {
            unsafe
            {
                byte* p = (byte*)bmpData.Scan0;
                int id = bmpData.Stride * y + channel + (x + i) * bpp;
                p[id] = 255;
            }
        }
    }
 Pedro7719 июн. 2013 г., 04:14
@ Mehran, пожалуйста, взгляните на мой ответ и ответ Boings. Вы заметили, что ячтение / запись в разблокированный битмап? В соответствии с MSDN это не должно работать.
 Pedro7719 июн. 2013 г., 14:03
@Mehran, вы смотрели метод "public static BitmapData GetBitmapData (Bitmap bmp) "? Ну, как вы видите, я разблокирую это. Без разблокировки яВы получите красный крест, как и вы. 1. Создайте растровое изображение. 2. Получить BitmapData. 3. Добавьте растровое изображение в PictureBox 4. Сделайте все, что вы хотите с растровым изображением (чтение / запись), результат будет отображаться в PictureBox. :)
 Mehran10 июн. 2013 г., 20:27
для обработки больших изображений в c #, да, это очень быстрый способ получения и установки пикселей, но если выработаешь с маленькими изображениями (< 500 * 500), используяGetPixel а такжеSetPixel было бы намного проще
 Mehran19 июн. 2013 г., 06:30
Я попробовал то, что вы сказали, но без разблокировки я получил красный крест на белом фоне в моемpicturebox и нет изображения! Как вы уже упоминали, это не должно работать, но я понятия не имею, почему это работает в вашем случае!

Ответы на вопрос(1)

Решение Вопроса

официальное объяснение ясно об этом.

Scan0не указать фактические данные пикселей объекта Bitmap; скорее он указывает на временный буфер, который представляет часть данных пикселей в объекте Bitmap. Код записывает значение 0xff00ff00 (зеленый) в 1500 мест во временном буфере.Позже вызов Bitmap :: UnlockBits копирует эти значения для самого объекта Bitmap.

Я бы согласился, что есть "ошибка» вUnLockBits()потому что каждый неImageLockModeUserInputBuf BitmapData должен иметь свое поле сброса (особенно scan0) после 'релиз / разблокировки.

Управляемые буферы Scan0 GDI могут оставаться доступными послеUnLockBits, но это просто удача, вы не получите недопустимую ошибку памяти. Графической подсистеме может потребоваться это пространство памяти для резервного копирования другого растрового изображения или того же растрового изображения, но для другого прямоугольника или другого пиксельного формата.

Scan0 неt представляют внутренние данные растрового изображения, ноCOPY, написанный GDI в то время какLockBits(...| ImageLockModeRead...) и читать из GDI в то время какUnLockBits() (.. if LockBitswith(.. | ImageLockModeWrite ..)

Вот что такое абстракция BitmapData. Теперь, может быть, если вы используете прямоугольник, равный размеру растрового изображения и пиксельному режиму, совпадающему с изображением вашей видеокарты, GDIможет верните фактический адрес хранения пикселей растрового изображения в scan0 (а не в копию), но вы никогда не должны полагаться на это (или создавать программу, которая работает только на вашем компьютере).

РЕДАКТИРОВАТЬ 1: Я уже объяснил выше, почему вам повезло, что вы можете использовать scan0 вне блокировки. Потому что вы используетеоригинал ВМРPixelFormat и что GDI оптимизирован в этом случае, чтобы дать вам указатель, а не копию. Этот указатель действует до тех пор, пока ОС не решит его освободить.только время есть гарантиямежду LockBits а такжеUnLockBits, Период. Вот код, который нужно добавить к вашему, поместите его в форму, чтобы проверить это несколько серьезно. Я могу разбить с каким-то "нейтральный» позвонить сRotate180FlipX нажатием кнопки.Растровое изображениевнутренности являются частными, Период. ОС может в любой момент принять решение об изменении своего представления, даже не делая этого "действие» на нем (например, сворачивание окна и множество других возможностей).

РЕДАКТИРОВАТЬ 2: Ваш вопрос:Есть ли практическая разница, блокирующая растровое изображение в режиме ReadOnly или WriteOnly, когда пользовательский буфер не задан?

С пользовательским буфером или без него, разница есть. Одна копия на LockBits (если только для чтения)И / ИЛИ одна копия на UnlockBits (если пишется только). Тщательно выбирайте, чтобы не делать ненужные копии. Подсказка: перестаньте думать, что вы работаете в том же пиксельном формате, логическивы не, Написатьтолько буфер в 64bpp получен полностью заполненнымшум (или нетронутым, если это также пользовательский буфер). Вам лучше полностью заполнить его до разблокировки. (не просто тыкать в несколько пикселей). Именование enum вводит в заблуждение, потому что WriteOnly | ReadOnly == ReadWrite

Доступ к одному пикселю за раз с использованием LockBits - ПЛОХО. Никто не хочет этого делать. Что вы делаете, чтобы создать / изменитьочень много пиксель (используя указатель / scan0) и зафиксируйте их в квазиоднократной операции ATOMIC (Lock / Marhsal.Copy / UnLock) в растровое изображение (и Invalidate () / redraw, если вы хотите что-то увидеть)

public MainForm()
{
InitializeComponent();

pictureBox.SizeMode = PictureBoxSizeMode.StretchImage;
// use a .gif for 8bpp
Bitmap bmp = (Bitmap)Bitmap.FromFile(@"C:\Users\Public\Pictures\Sample Pictures\Forest Flowers.jpg"); 
pictureBox.Image = bmp;
_backImageData = GetBitmapData(bmp);
_drawBitmap = true;
_thread= new Thread(DrawtoBitmapLoop);
_thread.IsBackground= true;
_thread.Start();

button.Text = "Let's get real";
button.Click += (object sender, EventArgs e) =>
    {
        // OK on my system, it does not rreallocate but ...
        bmp.RotateFlip(RotateFlipType.Rotate180FlipX); 
        // ** FAIL with Rotate180FlipY on my system**       
    };  
}
Thread _thread;
bool _drawBitmap;
BitmapData _backImageData;

//Non UI Thread
private void DrawtoBitmapLoop()
{
    while (_drawBitmap)
    {
        ScrollColors(_backImageData);

        this.Invoke((ThreadStart)(() =>
        {
            if (!this.IsDisposed)
                this.pictureBox.Invalidate();
        }));                
        Thread.Sleep(100);
    }
}

private unsafe static void ScrollColors(BitmapData bmpData)
{
    byte* ptr = (byte*)bmpData.Scan0;
    ptr--;
    byte* last = &ptr[(bmpData.Stride) * bmpData.Height];
    while (++ptr <= last)
    {
        *ptr = (byte)((*ptr << 7) | (*ptr >> 1));
    }
}
 Boing20 июн. 2013 г., 23:19
@ Pedro77 Ваши приветствуются :-). Я отредактировал свой ответ, чтобы ответить на этот следующий вопрос.
 Mauro Sampietro05 сент. 2014 г., 12:25
квази не квази
 jrh09 дек. 2016 г., 16:53
Для тех из вас, кто из документов .NET,BitmapData.Scan0 на MSDN говорит говоритGets or sets the address of the first pixel data in the bitmap. This can also be thought of as the first scan line in the bitmap. но Боинг прав, поэтому документы .NET в лучшем случае немного обманчивы.Функция GDI + BitmapData.Scan0 говоритScan0 does not point to the actual pixel data of the Bitmap object
 Pedro7720 июн. 2013 г., 15:02
Если я создаю растровое изображение со своими выделенными неуправляемыми данными, ОС все равно перемещает данные в блокировках битов ».
 Boing20 июн. 2013 г., 16:33
Если вы создаете Bitmap всеми возможными способами (ctor со Scan0 илиUserInputBuffer Пиши LockBits), результат тот же: Битовые карты имеют свои собственные копии, и они НЕ будут касаться ваших, как вы НЕ должны касаться их.
 Boing20 июн. 2013 г., 16:25
GDI оптимизирован в этом случае, чтобы дать вам указатель, а не копию " где это сказано? Говорятв моем ответе, Он испытан через код, он полностью логичен, потому что указатель является его копией. Баг в том, что ОС победила• блокировать память при доступе на запись (используя MMU), когда клиент запрашивает только чтение. Следующая версия ОС или патч может изменить егоне такт паника).Прекратить использование Scan0 снаружиLockBits, нет смысла для этого. Или просто надеяться на худшее.
 Boing20 июн. 2013 г., 00:25
@ Pedro77 Это не проблема C ++. Прямой вызов .NET напрямую в метод GDI OS / C ++ (используйте ILSpy). Это работает, это должно в обоих случаях. Смотрите редактировать. Копирование / преобразование не требуется, если запрашиваемый PixelFormat идентичен. "
 Pedro7718 июн. 2013 г., 18:59
Ну, статья явно поддерживает то, что вы говорите. "Scan0 не указывает на фактические данные пикселей объекта Bitmap; скорее он указывает на временный буфер, который представляет часть данных пикселей в объекте Bitmap. " Но когда я пишу в Scan0, отображаемое растровое изображение изменяется, но в статье говорится, что это должно произойти только после UnLockBits! Может быть, в C ++ работает как надо. Может кто-нибудь попробовать это? Вы говорите, ямне повезло иметь Scan0 для фактических растровых данных, но яя былсчастливый" За все время, на более чем одной машине. Я хочу понять, что на самом деле происходит.
 Pedro7720 июн. 2013 г., 18:58
Я был так счастлив, используя Scan0 напрямую. Но вы показали мне, что это неправильно. Большое спасибо, Боинг. Другой вопрос: есть ли практическая разница в блокировке растрового изображения в режиме ReadOnly или WriteOnly, когда пользовательский буфер не задан?
 jrh09 дек. 2016 г., 16:55
 Pedro7720 июн. 2013 г., 14:49
GDI оптимизирован в этом случае, чтобы дать вам указатель, а не копию " где это сказано? Вы правы, roteflip несколько раз вносит изменения в Scan0! Что если я создаю растровое изображение, используя мой неуправляемый буфер? Я думаю, что в этом случае изменение Scan0. Я'Я собираюсь проверить это.

Ваш ответ на вопрос