Как правильно реализовать доступ к строке сканирования TBitmap?

Я пытаюсь получить доступ к строке сканирования растрового изображения в соответствии с статьей на Embarcadero. Использование строк сканирования, таких как

for y := 0 to n do
begin
   line := bitmap.scanline [y];
   for x := 0 to n do line [x] := value;

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

unit SCTester;

interface

uses Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
     ExtCtrls;

type
   TRGBQuad = packed record
      b: uInt8;
      g: uInt8;
      r: uInt8;
      alpha: uInt8;
   end; // Record: TQuad //

// Override the definitions in Graphics.pas
   TRGBQuadArray = packed array [0..MaxInt div SizeOf (TRGBQuad) - 1] of TRGBQuad;
   PRGBQuadArray = ^TRGBQuadArray;

  TForm1 = class(TForm)
    Image: TImage;
    procedure ImageDblClick(Sender: TObject);
  end;

var Form1: TForm1;

implementation

{$R *.dfm}

procedure TForm1.ImageDblClick(Sender: TObject);
var Bitmap: TBitmap;
    q: TRGBQuad;
    x, y: NativeInt;
    FirstLine: PRGBQuadArray;
    idx: NativeInt;
    LineLength: NativeInt;
begin
   q.r := 0; q.g := 0;
   Bitmap := TBitmap.Create;
   Bitmap.Height := Image.Height;
   Bitmap.Width  := Image.Width;
   Bitmap.PixelFormat := pf32Bit;
   FirstLine := Bitmap.ScanLine [0];
   LineLength := (NativeInt (Bitmap.Scanline [1]) - NativeInt (FirstLine)) div SizeOf (TRGBQuad);
   try
      for y := Bitmap.Height - 1 downto 0 do
      begin
         for x := 0 to Bitmap.Width - 1 do
         begin
            q.b := (x xor y) mod 255;
            idx := y * LineLength + x;
            FirstLine [idx] := q;
         end; // for
      end; // for
      Image.Picture.Assign (Bitmap);
   finally
       Bitmap.Free;
   end; // try..finally
end;

end.

И я всегда получаю незаконный доступ, когда y=1 и x=0. LineLength имеет отрицательное значение (ширина растрового изображения), но этого можно было ожидать. Что я делаю не так?

EDIT: приведенный выше код изменен, чтобы отразить замечания, обработанные до сих пор.


person Arnold    schedule 01.05.2012    source источник
comment
idx следует объявить как NativeInt, чтобы ваш код можно было использовать и в x64. LineLength не должно быть отрицательным (отсюда и незаконный доступ). Мой вывод таков, что вы запускаете этот код в 64-битном режиме.   -  person LU RD    schedule 01.05.2012
comment
@LURD, точно мои мысли - и каждый LongInt(...) следует заменить на NativeUInt(...)   -  person kobik    schedule 01.05.2012
comment
@LURD LineLength может быть отрицательным (и обычно отрицательным), это не проблема, вызывающая AV.   -  person kludg    schedule 01.05.2012
comment
Я протестировал код на Delphi XE, и он работает; да, LineLength отрицательно, это нормально.   -  person kludg    schedule 01.05.2012
comment
@Serg, я исправлен, отзываю свой ответ. Однако idx должен быть объявлен как NativeInt.   -  person LU RD    schedule 01.05.2012
comment
@LURD Конечно, код неверен в 64-битном режиме из-за указателей, приведенных к longints.   -  person kludg    schedule 01.05.2012
comment
@Serg, именно этот код работал на твоей машине? Я запускаю Delphi-XE на Windows-7 64 бит. В статье davdata.nl/math/drawing1.html объясняется, почему LineLength обычно имеет отрицательное значение. .   -  person Arnold    schedule 01.05.2012
comment
@Arnold да, у меня Delphi XE на 64-битной версии Windows7, ваш код работает, и я не вижу никаких других проблем в вашем коде, кроме приведения указателей к longints в 64-битном режиме Delphi XE2.   -  person kludg    schedule 01.05.2012
comment
@Серг, это определенно не работает на моей машине. Ошибки могут быть разными, теперь это нелегальный доступ. Пробовал менять параметры проекта: выставлял выравнивание поля записи по двойному слову, но это не помогает.   -  person Arnold    schedule 01.05.2012
comment
Измените LineLength := (Longint (Bitmap.Scanline [1]) - Longint (FirstLine)) на LineLength := (NativeInt (Bitmap.Scanline [1]) - NativeInt (FirstLine)), а все объявления LongInt на NativeInt.   -  person LU RD    schedule 01.05.2012
comment
@LU RD, спасибо за предложение. Все еще ошибка проверки диапазона. Может ли это быть связано с тем, что TRGBarray объявлен из [0..big number] и используются отрицательные индексы? Когда я добавляю 400 (ширина растрового изображения) к idx, это дает ошибку проверки диапазона для y = 2, x = 0, добавление 800 дает незаконный доступ при y = 0 и x = 304. Битмап.Высота = 500.   -  person Arnold    schedule 01.05.2012
comment
@LU RD (и другие), спасибо всем вам за ваши усилия, но я, кажется, не понимаю. Приношу свои извинения за то, что отнял у вас больше времени. Я создал небольшую автономную программу со всеми вашими предложениями в актуальном состоянии, смотрите отредактированный код. Я все еще получаю сообщение об ошибке. Если он работает нормально на ваших машинах, может быть, есть опция компилятора, которая может вызвать это?   -  person Arnold    schedule 01.05.2012
comment
@Arnold - я действительно не смотрел код, когда комментировал. Когда я удалил комментарий, было слишком поздно.. Извините.. Взгляните на комментарий/ответ, который я разместил, чтобы увидеть, работает ли он так, как вам хотелось бы.   -  person Sertac Akyuz    schedule 02.05.2012
comment
@Sertac, А, я думал, что видел что-то проходящее мимо :-) Не могли бы вы сделать репост, пожалуйста?   -  person Arnold    schedule 02.05.2012
comment
@Arnold - то, что я предлагаю сейчас, есть в ответе. Комментарий, который я удалил, был неверным.   -  person Sertac Akyuz    schedule 02.05.2012
comment
@Sertac, я пропустил это из-за стресса :-) Это правильный ответ. Я отметил это.   -  person Arnold    schedule 02.05.2012


Ответы (2)


Чтобы не получить доступ к какому-либо отрицательному индексу, я бы сделал

procedure TForm1.Button1Click(Sender: TObject);
var Bitmap: TBitmap;
    q: TRGBQuad;
    x, y: LongInt;
    line{, FirstLine}: PRGBQuadArray;
    idx: NativeInt;
    LastLine: PRGBQuadArray;
    LineLength: NativeInt;
begin
   q.r := 0; q.g := 0;
   Bitmap := TBitmap.Create;
   Bitmap.Height := Image.Height;
   Bitmap.Width  := Image.Width;
   Bitmap.PixelFormat := pf32Bit;

   LastLine := Bitmap.ScanLine[Bitmap.Height - 1];
   LineLength := (NativeInt(Bitmap.Scanline[Bitmap.Height - 2]) - NativeInt(Lastline)) div SizeOf(TRGBQuad);
   try
      for y := 0 to Bitmap.Height - 1 do
      begin
         for x := 0 to Bitmap.Width - 1 do
         begin
            q.b := (x xor y) mod 255;
            idx := y * LineLength + x;
            LastLine [idx] := q;
         end; // for
      end; // for
      Image.Picture.Assign (Bitmap);
   finally
       Bitmap.Free;
   end; // try..finally
end;
person Sertac Akyuz    schedule 01.05.2012
comment
Оно работает! Большое спасибо! Это действительно хороший способ избежать отрицательных чисел. Это поможет мне еще больше ускорить растровые изображения. - person Arnold; 02.05.2012
comment
@Arnold - Пожалуйста! .. Не забудьте убедиться, что у вас растровое изображение снизу вверх. - person Sertac Akyuz; 02.05.2012
comment
Точно! Что я читал в статьях о строках развертки, так это то, что строки развертки «обычно» упорядочены сверху вниз, что приводит к отрицательной длине строки. Следует знать, что для того, чтобы этот код работал правильно во всех случаях, необходимо явно закодировать положительную и отрицательную длину строки. Редактировать: я должен ссылаться на растровые изображения, упорядоченные снизу вверх и сверху вниз, чтобы избежать запутанной терминологии отрицательной или положительной длины строки. - person Arnold; 02.05.2012
comment
Только что проверил код. Скорость невероятная. Спасибо всем за помощь. - person Arnold; 02.05.2012
comment
Это не объясняет, что было не так в исходном коде OP - настройки компилятора, 64-битный режим или что-то еще. ОП даже не сказал, компилирует ли он в 32- или 64-битном режиме - всегда не любит такие вопросы. - person kludg; 02.05.2012
comment
@Serg - К тому времени, когда я был вовлечен, OP получал ошибку проверки диапазона из-за использования отрицательного индекса в «TRGBQuadArray» (11-й комментарий к вопросу на данный момент). С другими возможными ошибками, вероятно, уже разобрались. - person Sertac Akyuz; 02.05.2012
comment
@Serg, извините за путаницу. Я использую delphi XE, а не XE2, как я ошибочно отметил. Где-то в комментариях я сказал, что использовал Delphi-XE на win-7-64, и это было легко не заметить. Мои извинения. Я получил две ошибки: «Ошибка проверки диапазона» и «Незаконный доступ» и никогда не понимал, когда я получил ту или другую. - person Arnold; 02.05.2012

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

person MBo    schedule 01.05.2012
comment
Я понимаю, что вы имеете в виду, но я считаю, что «LineLength имеет отрицательное значение» немного сбивает с толку. Я бы сказал, что в восходящем растровом изображении первая строка сканирования является последней в макете памяти. - person Sertac Akyuz; 01.05.2012