Версия для печати темы
Нажмите сюда для просмотра этой темы в оригинальном формате
Форум программистов > Общие вопросы по .NET и C# > LockBits вместо SetPixel


Автор: Georgich 3.2.2011, 12:23
Приветствую.

В общем, такая проблема. Есть метод отрисовки луча по пикселям:
Код

              private void DrawBeam(ref Bitmap bm)
              {
                  X = X0;
                  Y = Y0;
                  Random rand = new Random();
                  const double digitWeight = 360.0 / 65536;
                  double angle = ScanAngle * digitWeight;
                  double cos = Math.Cos(Math.PI * (90 - angle) / 180);
                  double sin = Math.Sin(Math.PI * (90 - angle) / 180);
       
                  for (int k = 0; k < 256; ++k)
                  {
                      X += cos;
                      Y -= sin;
                      //bm.SetPixel((int)Form1.X, (int)Form1.Y, Color.FromArgb(255, 255, 255));
                  }
       
                  for (int k = 256; k < 768; ++k)
                  {
                      int num = rand.Next(0, 255);
                      X += cos;
                      Y -= sin;
                      bm.SetPixel((int)X, (int)Y, Color.FromArgb(num, num, num));
                  }
              }

Отрисовывает он не особо-то и быстро. Решил попробовать заменить на LockBits. Сделал таким образом:
Код

        BitmapData bmpData;
        byte[] rgbValues = new byte[2400000];

        private void DrawBeam(ref Bitmap bm)
        {
            X = X0;
            Y = Y0;
            Random rand = new Random();
            const double digitWeight = 360.0 / 65536;
            double angle = ScanAngle * digitWeight;
            double cos = Math.Cos(Math.PI * (90 - angle) / 180);
            double sin = Math.Sin(Math.PI * (90 - angle) / 180);

            Rectangle rect = new Rectangle(0, 0, (int)width, (int)height);
            bmpData = bm.LockBits(rect, ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb);
            
            for (int a = 0; a < 768; a++)
            {
                if (a <= 256)
                {
                    X += cos;
                    Y -= sin;
                }
                else
                {
                    X += cos;
                    Y -= sin;
                    int num = rand.Next(0, 255);
                    rgbValues[(int)Y * bmpData.Stride + (int)X * 3] = (byte)num;
                    rgbValues[(int)Y * bmpData.Stride + (int)X * 3 + 1] = (byte)num;
                    rgbValues[(int)Y * bmpData.Stride + (int)X * 3 + 2] = (byte)num;
                }
            }

            Marshal.Copy(rgbValues, 0, bmpData.Scan0, rgbValues.Length);

            bm.UnlockBits(bmpData);
         }

И получил вообще тормоза. Дело в том, что он при отрисовки нужных мне пикселей, отрисовывает и остальные пиксели черным цветом. И так при каждой перерисовке. Можно ли этого как-то избежать? Может, действительно, LockBits будет пошустрее выводить тогда изображение? Спасибо.

Автор: Experimenter 7.2.2011, 14:32
Georgich, если еще актуально...

Код

            for (int a = 0; a < 768; a++)
            {
                if (a <= 256)
                {
                    X += cos;
                    Y -= sin;
                }
                else
                {
                    X += cos;
                    Y -= sin;

                    int num = rand.Next(0, 255);
                    int offset = (int)Y * bmpData.Stride + (int)X * 3;
                    Marshal.WriteByte(bmpData.Scan0, offset, (byte)num);

                    offset = (int)Y * bmpData.Stride + (int)X * 3 + 1;
                    // и т.д.
                }
            }
            // Marshal.Copy(rgbValues, 0, bmpData.Scan0, rgbValues.Length);
            bm.UnlockBits(bmpData);


может, как-то так?

Автор: Georgich 7.2.2011, 15:42
Experimenter, спасибо, что заглянули в тему.
Все так же тормозит. SetPixel пока превосходит данные пляски по скорости. Неиспользуемая часть битмапа отрисовывается черным цветом. И, так как она отрисовывается в каждом шаге, то и получаются тормоза.
Скорости отрисовки я стараюсь добиться максимальной, так как придется еще получать данные из usb на скорости около 25 Мб/сек, обработать эти данные и по ним уже отрисовать картинку.

Автор: Experimenter 7.2.2011, 18:27
Georgich, хм, думается, что проблема не в отрисовке, через LockBits должно работать быстрее
Я сделал замеры, и получается, что LockBits при первом вызове метода срабатывает раза в три быстрее, при последующих - примерно в 10 раз быстрее. Код:

Код

        private void button1_Click(object sender, EventArgs e)
        {
            Bitmap b = CreateBitmap();

            pictureBox1.Image = b;

            Stopwatch sw = new Stopwatch();
            sw.Start();

            DrawBeamSetPixel(b);

            sw.Stop();

            label1.Text = sw.ElapsedTicks.ToString();
        }

        private Bitmap CreateBitmap()
        {
            Bitmap b = new Bitmap(
                1000, 800, PixelFormat.Format24bppRgb);

            using (Graphics g = Graphics.FromImage(b))
            {
                g.Clear(Color.YellowGreen);

                g.DrawImage(b, 0.0f, 0.0f);
            }

            return b;
        }

        private void DrawBeamSetPixel(Bitmap bm)
        {
            int X = 0;
            int Y = 0;

            for (int k = 0; k < 768; k++)
            {
                X++;
                Y++;

                bm.SetPixel((int)X, (int)Y, Color.FromArgb(255, 255, 255));
            }
        }

        private void button2_Click(object sender, EventArgs e)
        {
            Bitmap b = CreateBitmap();

            pictureBox1.Image = b;

            Stopwatch sw = new Stopwatch();
            sw.Start();

            DrawBeamLockBits(b);

            sw.Stop();

            label1.Text = sw.ElapsedTicks.ToString();
        }

        private void DrawBeamLockBits(Bitmap bm)
        {
            int X = 0;
            int Y = 0;

            Rectangle rect = new Rectangle(0, 0, bm.Width, bm.Height);
            BitmapData bmpData = bm.LockBits(rect, ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb);

            for (int a = 0; a < 768; a++)
            {
                X++;
                Y++;

                int offset = Y * bmpData.Stride + X * 3;
                Marshal.WriteByte(bmpData.Scan0, offset, 122);

                offset++;
                Marshal.WriteByte(bmpData.Scan0, offset, 122);

                offset++;
                Marshal.WriteByte(bmpData.Scan0, offset, 122);
            }

            bm.UnlockBits(bmpData);
        }


Хотя и через SetPixel счет идет на миллисекунды, тормозов не наблюдаю. Судите сами.

Автор: Georgich 7.2.2011, 20:04
Experimenter, спасибо за код. В скорости LockBits я убедился. Дабы не быть голословным в том, что в моем коде что-то тормозит, прикладываю исходники. Посмотрите их, пожалуйста, если вам не составит труда. Возможно, они прояснят то, что я пытаюсь объяснить.
P.S. Сейчас там сделано через LockBits, но если вы закомментируете этот кусок и расскоментируете кусок с SetPixel, то увидите разницу.

Спасибо.

Автор: Experimenter 8.2.2011, 09:46
Georgich, посмотрел код. Вы в цикле лочите битмап, отсюда и задержки. Попробуйте вынести LockBits в обработку события Timer_Tick

Автор: neutrino 8.2.2011, 09:47
А какая сама задача? Почему просто не воспользоваться GDI+? Или если надо самому по какой-то причине, то есть быстрые алгоритмы рисования линий. Начать можно с Брезенгама.

Автор: Georgich 8.2.2011, 10:43
Experimenter, да, спасибо. Действительно надо было вынести в Timer_tick.
Но, остается другой вопрос: остальной фон - черный. Можно ли как-то от него избавится, чтобы его не перерисовывать?

Автор: Experimenter 8.2.2011, 11:35
Georgich, думаю, что битмап сначала нужно "нарисовать" в конструкторе или где-то еще до запуска таймера, ведь вы оперируете с BitmapData, и туда при блокировании битмапа передаются сплошные нули (черный цвет), если с битмапом ничего не делалось после его создания.

Автор: Georgich 8.2.2011, 12:40
Experimenter, так, вроде, в конструкторе формы я и отрисовываю:
Код

        Bitmap bmSector;
        BitmapData bmpData;
        DrawPicture drawSector;

        public Form1()
        {
            InitializeComponent();
            drawSector = new DrawPicture(ref pictureBox1);//(pictureBox1.Width, pictureBox1.Height);
            bmSector = new Bitmap(pictureBox1.Width, pictureBox1.Height);
        }

        private void timer1_Tick(object sender, EventArgs e)
        {
            Rectangle rect = Rectangle.FromLTRB(0, 0, pictureBox1.Width, pictureBox1.Height);
            bmpData = bmSector.LockBits(rect, ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb);
            drawSector.DrawBeams(ref bmSector, ref bmpData);
            pictureBox1.Image = bmSector;
            pictureBox1.Invalidate();
            bmSector.UnlockBits(bmpData);
        }

Что-то не так?

Автор: Experimenter 8.2.2011, 12:48
Georgich, добавьте в конструктор еще одну строку:
Код

            pictureBox1.Image = bmSector;

и увидите, что у вас в битмапе

Автор: Georgich 8.2.2011, 12:57
Experimenter, сделал так, закомментил эту же строку в Timer_Tick. При загрузке формы весь pictureBox прозрачный. Нажимаю на кнопку - черный фон. Не могу сейчас осознать до конца этот момент.

Автор: Experimenter 8.2.2011, 13:27
Georgich, сравните два конструктора битмапа:
Код

    new Bitmap(pictureBox1.Width, pictureBox1.Height, PixelFormat.Format32bppArgb);
и
Код

    new Bitmap(pictureBox1.Width, pictureBox1.Height, PixelFormat.Format24bppRgb);

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

Автор: Georgich 8.2.2011, 13:47
Experimenter, спасибо за разъяснение про "закулисие".
Так, собственно, а как от черного фона-то избавится при формате в 24 бита? Вот это мне не ясно.

Автор: Experimenter 8.2.2011, 14:08
Georgich, ну, например, создать изображение с фоном формы.
Код

        public Form1()
        {
            InitializeComponent();
            drawSector = new DrawPicture(ref pictureBox1);//(pictureBox1.Width, pictureBox1.Height);
            bmSector = CreateBitmap();
        pictureBox1.Image = bmSector;
        }

        private Bitmap CreateBitmap()
        {
            Bitmap b = new Bitmap(
                pictureBox1.Width, pictureBox1.Height, PixelFormat.Format24bppRgb);
            using (Graphics g = Graphics.FromImage(b))
            {
                g.Clear(this.BackColor);
                g.DrawImage(b, 0.0f, 0.0f);
            }
            return b;
        }

Автор: Georgich 8.2.2011, 14:13
Experimenter, спасибо. Вроде побыстрее, но загрузка процессора, что при SetPixel, что при LockBits ~ 77%.
Теперь буду смотреть и тестить, будет ли успевать отрисовка при получении данных по usb и их обработке.

Powered by Invision Power Board (http://www.invisionboard.com)
© Invision Power Services (http://www.invisionpower.com)