БлогNot. SetPixel в .NET: почему так медленно и можно ли быстрее?

SetPixel в .NET: почему так медленно и можно ли быстрее?

Код проверен в Studio 2015. Создав пустое приложение Windows Forms, разместим на форме контейнер Panel с установленными свойствами AutoScroll = true и Dock = Fill (панелька растянется на всё окно формы), а на нём - стандартный элемент PictureBox с установленным свойством SizeMode = AutoSize и положением в левом верхнему углу клиентской области формы (Location = 0; 0).

Попытаемся сделать что-то, чего не делали в этой статье по обработке изображений, скажем, поработать с отдельными пикселами (сформировать из них картинку).

Выполнять показанные ниже кусочки кода можно, например, из стандартного меню MenuStrip, перетащив его в окно конструктора, вписав название нужного пункта меню и затем создав двойным щелчком мыши обработчик его выбора.

Рисовать по пикселам можно на Bitmap, созданном вот таким или подобным кодом:

Bitmap ^ Img = gcnew Bitmap(this->panel1->ClientSize.Width, this->panel1->ClientSize.Height);
Graphics ^g = Graphics::FromImage(Img);
g->Clear(Color::White);
pictureBox1->Image = Img;

У Bitmap есть готовые методы SetPixel и GetPixel, попробуем ими воспользоваться, чтобы сделать несложный попиксельный градиент на нашем рисунке, например, такой:

int w = pictureBox1->Image->Width, h = pictureBox1->Image->Height;
Bitmap ^bitmap1 = gcnew Bitmap(w, h);
Graphics ^g = Graphics::FromImage(bitmap1);
g->Clear(Color::White);
for (int i=0; i<w; i++) {
 for (int j = 0; j < h; j++) {
  int r = 128+rand()%128;
  int g = rand() % std::max(r,1);
  int b = rand() % std::max(g,1);
  bitmap1->SetPixel(i,j,Color::FromArgb(255,r,g,b));
 }
}
pictureBox1->Image = bitmap1;
скриншот примера из статьи
скриншот примера из статьи

Предполагается, что к файлу .h с кодом дополнительно подключены библиотеки:

#include <cstdlib>
#include <algorithm>

Этот код - весьма медленный, вообще SetPixel и GetPixel у Bitmap весьма медленны. В инете много советов работать с битовыми массивами вместо вызовов SetPixel, но работающего кода для C++/CLI как-то не находится.

Ниже показан небольшой класс DirectBitmap с собственными реализациями методов SetPixel и GetPixel, я просто добавил его перед реализацией класса своей формы, вот сюда:

#pragma once
#include <cstdlib>
#include <algorithm>
namespace Graphics_01 {
 using namespace //...

public ref class DirectBitmap : IDisposable {
 //это оно!
};


/// <summary>
/// Сводка для MyForm
/// </summary>
public ref class MyForm : public System::Windows::Forms::Form {
 //...
};

}

Вот полный код класса DirectBitmap:

public ref class DirectBitmap : IDisposable {
 public: Bitmap ^bitmap1;
 public: array <System::Int32> ^Bits;
 public: bool Disposed;
 public: int Height;
 public: int Width;

 protected: System::Runtime::InteropServices::GCHandle ^BitsHandle;

 public: DirectBitmap(int width, int height) {
  Width = width;
  Height = height;
  Bits = gcnew array <System::Int32>(width * height);
  BitsHandle = System::Runtime::InteropServices::GCHandle::Alloc
   (Bits, System::Runtime::InteropServices::GCHandleType::Pinned);
  bitmap1 = gcnew Bitmap(width, height, width * 4, 
   System::Drawing::Imaging::PixelFormat::Format32bppPArgb, BitsHandle->AddrOfPinnedObject());
 }

 public: void SetPixel (int x, int y, Color color)  {
  int index = x + (y * Width);
  int col = color.ToArgb();
  Bits[index] = col;
 }

 public: Color GetPixel (int x, int y) {
  int index = x + (y * Width);
  int col = Bits[index];
  Color result = Color::FromArgb(col);
  return result;
 }

 public: ~DirectBitmap() {
  if (Disposed) return;
  Disposed = true;
  delete bitmap1;
  BitsHandle->Free();
 }
};

и изменённый код для попиксельной генерации изображения:

int w = pictureBox1->Image->Width, h = pictureBox1->Image->Height;
DirectBitmap ^ bitmap1 = gcnew DirectBitmap(w,h);
for (int i = 0; i<w; i++) {
 for (int j = 0; j < h; j++) {
  int r = 128 + rand() % 128;
  int g = rand() % std::max(r, 1);
  int b = rand() % std::max(g, 1);
  bitmap1->SetPixel(i, j, Color::FromArgb(255, r, g, b));
 }
}
pictureBox1->Image = bitmap1->bitmap1;

Это будет работать в 2,5-3 раза быстрее.

Можно сделать вывод, что главная причина, по которой SetPixel такой медленный - запирание/отпирание битов, выполняемое при каждом обращении к пикселам.

Я сделал попытку написать код ещё быстрее, но особой разницы по сравнению со вторым подходом не заметил, может, ещё чуть пошустрей :) Вот этот код:

int w = pictureBox1->Image->Width, h = pictureBox1->Image->Height;
Bitmap ^bitmap1 = gcnew Bitmap(w, h);
try {
 System::Drawing::Imaging::BitmapData ^bitmapData = bitmap1->LockBits(Rectangle(0,0,w,h), 
  System::Drawing::Imaging::ImageLockMode::ReadOnly, System::Drawing::Imaging::PixelFormat::Format32bppArgb);
 int dstStrideOffset = bitmapData->Stride;
 int bytesPerPixel = 4;
 int bytes = Math::Abs(dstStrideOffset) * h;
 array <System::Byte>^ rgbValues = gcnew array<System::Byte> (bytes);
 System::IntPtr start = bitmapData->Scan0;
 unsafe: {
  for (int counter = 0; counter < rgbValues->Length; counter += bytesPerPixel) {
   int r = 128 + rand() % 128;
   int g = rand() % std::max(r, 1);
   int b = rand() % std::max(g, 1);
   rgbValues[counter] = b;
   rgbValues[counter+1] = g;
   rgbValues[counter+2] = r;
   rgbValues[counter+3] = 255; // Alpha
  }
 }
 System::Runtime::InteropServices::Marshal::Copy (rgbValues, 0, start, bytes);
 bitmap1->UnlockBits(bitmapData);
}
catch (InvalidOperationException ^e) { MessageBox::Show("Error!"); }
pictureBox1->Image = bitmap1;

Ну а внешне все три картинки будут похожи, только скорость генерации разная :)

24.10.2018, 14:39 [2640 просмотров]


теги: программирование графика random время c++/cli

К этой статье пока нет комментариев, Ваш будет первым