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 [2760 просмотров]