Главная страница
    Top.Mail.Ru    Яндекс.Метрика
Форум: "Media";
Текущий архив: 2006.05.28;
Скачать: [xml.tar.bz2];

Вниз

Формат bitmap   Найти похожие ветки 

 
Woolen ©   (2005-12-21 17:43) [0]

Помогите разобраться в формате, пожалуйста. Не могу понять, что лежит между строк и какого оно размера.
GetPixel - это слишком медленно (знают все), но и обрабатывать в через ScanLine тоже, мягко говоря, не быстро.
Написал по другому:
procedure BitmapDarkness6(Bmp: TBitmap);
var
 PixelCicle: Cardinal;
 FirstClr,SecClr,ThirdClr: PByte;
 LinePtr: PByteArray;
 WinBmp: Windows.TBitmap;
begin
if GetObject(Bmp.Handle,SizeOf(WinBmp),@WinBmp) > 0 then
 begin
 LinePtr := WinBmp.bmBits;
 for PixelCicle := 0 to WinBmp.bmWidth * WinBmp.bmHeight - 1
   do
   begin
   FirstClr := Pointer(Cardinal(LinePtr) + PixelCicle * 3);
   SecClr := Pointer(Cardinal(FirstClr) + 1);
   ThirdClr := Pointer(Cardinal(FirstClr) + 2);
   FirstClr^ := FirstClr^ div 2;
   SecClr^ := SecClr^ div 2;
   ThirdClr^ := ThirdClr^ div 2;
   end;
 end;
end;

Время работы такой процедуры составила менее 39% от варианта со ScanLine, но некоторое количество пикселей не закрашивается. Цикл завершается раньше. То есть, строки, как я понял, лежат не вплотную. Видимо я корежу какую-то служебную информацию между ними, в то время как через ее надо "перешагивать". Поправьте, если я не прав, и помогите разобраться с форматом, пожалуйста. Что там лежит и для чего оно нужно? Спасибо
P.S. Битмап - 24 бита, RGB.


 
PAVIA ©   (2005-12-21 18:40) [1]

Скорее всего там передается 32битная картика. Для 24битной картике код рабоате верно. Там нету никакой дополнительной информации строки идут одна за другой. Только надо помнить что Бэмпешка храниться в обратном порядке.
С таким же успехом ты мог использовать и ScanLine[bmp.Height - 1]


 
MBo ©   (2005-12-21 18:46) [2]

1. Доступ через Scanline не медленнее использованного способа
2. Начала строк выравниваются на 4-байтовую границу


 
Woolen ©   (2005-12-21 19:13) [3]


> Доступ через Scanline не медленнее использованного способа

Быстрее, но на много смотря как делать. Возможно, я не прав, поправьте, если что.
Даю полные выкладки, потому как объяснять будет долго.
Проц - Celeron (P4) 2 ГГц. На процах с архитектурой, отличной от NetBirst, и (возможно) другим размером кэша, при обработке изображения другого размера, результаты могут немного отличаться. Расчет ведется при помощи вызова процедур, высталяющих и снимающих максимальные приоритеты процесса и потока, снимающих копию регистра RDTSC (счетчика тактов) до и после вызова с вычислением разницы. Есть пограшности на вход, выход таких процедур и т.п.
Вариант 1. Самый простой и распространенный. Принимаю за 100% времени.
procedure BitmapDarkness1(Bmp: TBitmap);
var
 RowCicle,PixelCicle,FirstClr,SecClr,ThirdClr: Cardinal;
 LinePtr: PByteArray;
begin
for RowCicle := 0 to Bmp.Height - 1 do
 begin
 LinePtr := Bmp.ScanLine[RowCicle];
 for PixelCicle := 0 to Bmp.Width - 1 do
   begin
   FirstClr := PixelCicle * 3;
   SecClr := FirstClr + 1;
   ThirdClr := FirstClr + 2;
   LinePtr[FirstClr] := LinePtr[FirstClr] div 2;
   LinePtr[SecClr] := LinePtr[SecClr] div 2;
   LinePtr[ThirdClr] := LinePtr[ThirdClr] div 2;
   end;
 end;
end;

Вариант 2. Оптимизированный со ScanLine"ом. Сотавляет 63% от первого.
procedure BitmapDarkness2(Bmp: TBitmap);
var
 RowCicle,PixelCicle: Cardinal;
 FirstClr,SecClr,ThirdClr: PByte;
 LinePtr: PByteArray;
begin
for RowCicle := 0 to Bmp.Height - 1 do
 begin
 LinePtr := Bmp.ScanLine[RowCicle];
 for PixelCicle := 0 to Bmp.Width - 1 do
   begin
   FirstClr := Pointer(Cardinal(LinePtr) + PixelCicle * 3);
   SecClr := Pointer(Cardinal(FirstClr) + 1);
   ThirdClr := Pointer(Cardinal(FirstClr) + 2);
   FirstClr^ := FirstClr^ div 2;
   SecClr^ := SecClr^ div 2;
   ThirdClr^ := ThirdClr^ div 2;
   end;
 end;
end;

Вариант 3. Составляет 39% от первого.
procedure BitmapDarkness6(Bmp: TBitmap);
var
 PixelCicle: Cardinal;
 FirstClr,SecClr,ThirdClr: PByte;
 LinePtr: PByteArray;
 WinBmp: Windows.TBitmap;
begin
if GetObject(Bmp.Handle,SizeOf(WinBmp),@WinBmp) > 0 then
 begin
 LinePtr := WinBmp.bmBits;
 for PixelCicle := 0 to WinBmp.bmWidth * WinBmp.bmHeight - 1
   do
   begin
   FirstClr := Pointer(Cardinal(LinePtr) + PixelCicle * 3);
   SecClr := Pointer(Cardinal(FirstClr) + 1);
   ThirdClr := Pointer(Cardinal(FirstClr) + 2);
   FirstClr^ := FirstClr^ div 2;
   SecClr^ := SecClr^ div 2;
   ThirdClr^ := ThirdClr^ div 2;
   end;
 end;
end;

Подозреваю, что можно сделать вариант, работающий еще быстрее.

> Начала строк выравниваются на 4-байтовую границу

Спасибо


 
Sapersky   (2005-12-21 20:01) [4]

Указатель лучше не высчитывать для каждого пикселя, а инкрементировать.

for RowCicle := 0 to Bmp.Height - 1 do begin
 FirstClr := PByte( Bmp.ScanLine[RowCicle] );
 for PixelCicle := 0 to Bmp.Width*3 - 1 do begin
   FirstClr^ := FirstClr^ shr 1; // хотя по идее компилятор сам должен заменять на это
  Inc(FirstClr);
 end;
end;

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

Ещё варианты:
1) MMX
2) Аппаратный вывод графики
3) 8-битные картинки, у которых нужно обрабатывать только палитру.


 
wicked ©   (2005-12-21 21:54) [5]


> 2. Начала строк выравниваются на 4-байтовую границу

афаир размеры строк выравниваются на 4 байтовую границу.... соответственно, между ними будут промежутки, если картинка не 32 битная или кол-во пикселей в строке не делится нацело на 4....


 
Woolen ©   (2005-12-22 12:36) [6]


> Указатель лучше не высчитывать для каждого пикселя, а инкрементировать.

А это мысль!

> хотя по идее компилятор сам должен заменять на это

Так и есть. Он сам понимает, что деление на 2, это сдвиг на 1. Он так и делает

> for RowCicle := 0 to Bmp.Height - 1 do begin
>  FirstClr := PByte( Bmp.ScanLine[RowCicle] );
>  for PixelCicle := 0 to Bmp.Width*3 - 1 do begin
>    FirstClr^ := FirstClr^ shr 1;
>   Inc(FirstClr);
>  end;
> end;

Нет, это будет очень медленно. Я уже убедился, что от ScanLine надо избавляться, только процесорное время жрет. От вложенного цикла тоже. При каждом завершении внутреннего цикла - останов, сброс и перезагрузка конвейера гарантирована. А на большинстве нынешних процов - это очень жрет производительность. В особенности это касается Prescott. Там 31 ступень. Кроме того, обрабатывать по одной цветовой компоненте за итерацию медленно. Как правило, процессор может гораздо больше комманд вертеть внутри ядра и кэша, причем NetBurst вообще имеет очередь на 12000 микроопераций уже во внутреннем представлении ядра. Так что я думаю, что надо еще побольше как-то тело цикла сделать. Должно быть быстрее так. А одна компонента за итерацию - медленно, я вчера уже проверял. Так и есть, как я думал.

> MMX

А у него есть побайтовый сдвиг? Я знаю только о пословном!

> Аппаратный вывод графики

Нужно затенить bmp очень быстро и все. Вывод потом не мое дело. Я просто делаю Canvas.Draw.

> 8-битные картинки, у которых нужно обрабатывать только палитру

Нет, 24-бита. И только.


 
MBo ©   (2005-12-22 13:54) [7]

Ограничивает не арифметика, а скорость работы с памятью.
В следующем коде (не вполне корректном) нет почти ничего лишнего. Проверь на своей машине. У меня в полтора раза быстрее, чем BitmapDarkness6


procedure DarkBmp(B:TBitmap);
var
 Delta:Integer;
 P:PInteger;
 i, Size:Integer;
begin
 if B.Height>1 then
   Delta:=Integer(B.Scanline[1])-Integer(B.Scanline[0]);
 Size:=B.Height*Abs(Delta);
 P:=B.Scanline[(B.Height-1)*Ord(Delta<0)];
 for i:= 0 to (Size shr 2)- 1 do begin
   P^:=(P^ and $FEFEFEFE) shr 1;
   Inc(P);
 end;
end;



 
Sapersky   (2005-12-22 14:11) [8]

от ScanLine надо избавляться, только процесорное время жрет

Сейчас посмотрел в реализацию TBitmap - да, накручено изрядно. Я-то обычно использую FastLIB:

http://prdownloads.sourceforge.net/skinner/FastLib.zip?download

Там Scanline - просто свойство, даже поле объекта, и поэтому никаких тормозов не добавляет.

А у него есть побайтовый сдвиг? Я знаю только о пословном!

Точно, нету :(
Тогда лучше MBo ©   (22.12.05 13:54) [7]


 
MBo ©   (2005-12-22 14:50) [9]

> MMX
>А у него есть побайтовый сдвиг? Я знаю только о пословном!

Это ничему не мешает. однако использование MMX в данном случае ускоряет незначительно, процентов на 10 (проба на скорую руку)

 PMask^:= $FEFEFEFEFEFEFEFE;
 asm
   mov eax, PMask
   movq mm1, [eax]
   mov eax,p
   mov edx, Size
@@Start:
   movq mm0,[eax]
   pand mm0, mm1
   psrlq mm0,1
   movq [eax],mm0
   add eax,8
   dec edx
   jnz @@Start
   emms
 end;
end;


 
miek ©   (2005-12-23 09:51) [10]

если использовать оптимизацию под две трубы и еще раскрыть цикл на 2 итерации (итого в каждой итерации чтение и запись 4 регистров MMX), ускорение будет сильнее, +50% должно быть точно


 
MBo ©   (2005-12-23 10:08) [11]

>miek ©   (23.12.05 09:51) [10]
Пример есть? Я не сталкивался с оптимизацией под пайпы, а интересно было бы посмотреть, например, простенькую обработку массива Int64.

З.Ы. Приведенный в [9] код обрабатывает массив 400К за 0.7 мс на AthlonXP1900, память PC2700(333 мгц), т.е. 560 мег в секунду. Поэтому я и склонен считать, что узкое место - доступ к памяти, а не арифметика. Может, конечно, префетчинг поможет.


 
Woolen ©   (2005-12-23 17:06) [12]


> если использовать оптимизацию под две трубы и еще раскрыть
> цикл на 2 итерации (итого в каждой итерации чтение и запись
> 4 регистров MMX), ускорение будет сильнее, +50% должно быть
> точно

Попробовал без MMX. Видимо, я чего-то недопонимаю. Вот вариант (растр должен иметь ширину, кратную 4):
if GetObject(Bmp.Handle,SizeOf(WinBmp),@WinBmp) > 0 then
 begin
 FirstDWORD := WinBmp.bmBits;
 SecDWORD := Pointer(Cardinal(FirstDWORD) + 4);
 for PixelCicle := 0 to WinBmp.bmWidth * WinBmp.bmHeight * 3 div 8 - 1 do
   begin
   FirstDWORD^ := (FirstDWORD^ and $FEFEFEFE) div 2;
   Inc(FirstDWORD,2);
   SecDWORD^ := (SecDWORD^ and $FEFEFEFE) div 2;
   Inc(SecDWORD,2);
   end;
 end;

По сравнению с обработкой в один DWORD за цикл прироста практически нет. Даже небольшое замедление. Хотя рагистры использует разные и зависимости между командами я не нашел. То есть, теоретически FirstDWORD и SecDWORD должны обрабатываться разными конвейрами. Но этого, судя по скорости, не происходит.


 
Woolen ©   (2005-12-23 17:17) [13]

procedure BitmapDarkness9(Bmp: TBitmap);
var
 PixelCicle: Cardinal;
 FirstDWORD: PInt64;
 WinBmp: Windows.TBitmap;
begin
if GetObject(Bmp.Handle,SizeOf(WinBmp),@WinBmp) > 0 then
 begin
 FirstDWORD := WinBmp.bmBits;
 for PixelCicle := 0 to WinBmp.bmWidth * WinBmp.bmHeight * 3 div 8 - 1 do
   begin
   FirstDWORD^ := (FirstDWORD^ and $FEFEFEFEFEFEFEFE) shr 1;
   Inc(FirstDWORD);
   end;
 end;
end;

И так тоже не быстрее. Хотя тут это можно объяснить зависимостью.
shrd eax,edx,1 - зависимость явная. Из-за этой команды проц точно ничего не разбросает по разным пайпам.


 
Woolen ©   (2005-12-27 12:28) [14]


> если использовать оптимизацию под две трубы и еще раскрыть
> цикл на 2 итерации (итого в каждой итерации чтение и запись
> 4 регистров MMX), ускорение будет сильнее, +50% должно быть
> точно

При развертке цикла в 2 раза (с использованием разных регистров) прибавки в скорости, почему-то, не видно. Может что-то не так делаю?
procedure BitmapDarkness11(Bmp: TBitmap);
var
 PixelCicle: Cardinal;
 WinBmp: Windows.TBitmap;
 Mask: Int64;
 PMask: PInt64;
begin
if GetObject(Bmp.Handle,SizeOf(WinBmp),@WinBmp) > 0 then
 begin
 Mask := $FEFEFEFEFEFEFEFE;
 PMask := @Mask;
 PixelCicle := WinBmp.bmWidth * WinBmp.bmHeight * 3 div 16;
   asm
     mov eax,PMask
     movq mm1,[eax]
     movq mm3,mm1
     mov eax,WinBmp.bmBits
     lea edx,[eax+8]
     mov ecx,PixelCicle
   @@LoopBegin:
     movq mm0,[eax]
     pand mm0,mm1
     psrlq mm0,1
     movq [eax],mm0
     movq mm2,[edx]
     pand mm2,mm3
     psrlq mm2,1
     movq [edx],mm2
     add eax,$10
     add edx,$10
     loop @@LoopBegin
     emms
   end;
 end;
end;


 
_Lucky_   (2005-12-27 13:49) [15]

WinBmp.bmWidth * WinBmp.bmHeight * 3 div 8 - 1
а зачем ваще такие вещи каждый раз вычислять?


 
Woolen ©   (2005-12-27 14:45) [16]


> а зачем ваще такие вещи каждый раз вычислять?

Какой именно каждый? В коде, приведенном в 12 и 13 постах, он вычисляется 1 раз. При инициализации цикла.


 
Woolen ©   (2005-12-27 14:45) [17]


> а зачем ваще такие вещи каждый раз вычислять?

Какой именно каждый? В коде, приведенном в 12 и 13 постах, он вычисляется 1 раз. При инициализации цикла.


 
_Lucky_   (2005-12-27 18:30) [18]


> Какой именно каждый? В коде, приведенном в 12 и 13 постах,
>  он вычисляется 1 раз. При инициализации цикла.


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

смотри оптимизацию циклов
http://www.delphimaster.ru/articles/optimization.html


 
Anton_K ©   (2005-12-27 18:43) [19]


> _Lucky_


Сколько раз выполнится тело цикла?

var i,n:Integer;
...
n:=2;
for i:=1 to n do n:=n+10;
...


 
MBo ©   (2005-12-27 20:44) [20]

>_Lucky_   (27.12.05 18:30) [18]

>Лично я думаю, что как раз таки на оборот, значения пересчитываются каждый раз, да это и логично

Читай описание языка

>смотри оптимизацию циклов
>http://www.delphimaster.ru/articles/optimization.html

А этого "добра" лучше не читать ;)


 
Woolen ©   (2005-12-27 23:34) [21]


> Лично я думаю, что как раз таки на оборот, значения пересчитываются
> каждый раз, да это и логично, там же ведь переменные стоят,
>  т.е. в теле цикла любая из них молга изменится, а если
> цикл был инициализирова - как вы утверждаете, то изменения
> не учтутся

Да, представляешь, они действительно не учтутся! Даже, если ты сделаешь цикл до переменной, а не до выражения и будешь ее в цикле менять, не учтутся!!! Даже если сделать так:
for Cicle := 0 to A do
 Inc(A);

А еще я тебя удивлю: в Delphi даже циклы to компилируются с прокруткой в обратную сторону - по типу downto. То есть в вышеуказанном случае будет крутиться от A до 0. А если написать так:
for Cicle := 0 to A do
 B := Cicle;

то опять же будет крутиться обратно, но для переменной Cicle тебе компилятор специально будет крутить одно число (отдельно) вперед, на увеличение, чтоб ты мог использовать значение своей переменной.
Короче, будешь читать буквари - узнаешь много нового.
Рекомендую для начала: Зубков - Ассемблер для DOS,Windows и UNIX; Delphi Help; ассемблерный отладчик.

> смотри оптимизацию циклов

Смотри, что компилирует Delphi


 
_Lucky_   (2005-12-28 13:53) [22]


> Anton_K ©   (27.12.05 18:43) [19]


Да действительно - это не то что я ожидал.


> MBo ©   (27.12.05 20:44) [20]


> Woolen ©   (27.12.05 23:34) [21]


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

http://www.delphimaster.ru/articles/optimization.html - очень жаль, что допущена такая ошибка.


 
Woolen ©   (2005-12-28 15:23) [23]


> я пишу на сях

А в них циклы пересчитываются на лету?

> ассемблер ваше не причем

При понимании того, что выдает компилятор и при написании вставок на ассемблере. Короче, при производительности. Не при чем, это если ты под виртуальную машину пишешь. Почти во всех остальных случаях, очень даже причем.

> раз переменные, то должны пересчитываться

А цикл сначала начинать? Или что с ним делать? А еще, видимо, неправильно, что адресация с 0, и что битов в байте 8, а не 10. :-)


 
Sapersky   (2005-12-28 15:49) [24]

очень жаль, что допущена такая ошибка.

Никакой ошибки нет.
Если есть вызов метода - вызывает на каждой итерации. Если нет - считает один раз. В [12] bmWidth и bmHeight - это поля записи, а не свойства объекта...


 
MBo ©   (2005-12-28 15:56) [25]

>Если есть вызов метода - вызывает на каждой итерации

???

Поясни


 
Sapersky   (2005-12-28 16:21) [26]

Если есть вызов метода - вызывает на каждой итерации

Это автор в статье утверждал.
Но до меня уже дошло, что он действительно не прав :(
Забыл, что в ассемблерном листинге for пишется два раза (проверка в начале и jump в конце), посмотрел на первый - ага, call в "теле цикла" есть, ну и решил, что зря на человека наезжаете.


 
_Lucky_   (2005-12-29 13:37) [27]


> Woolen ©   (28.12.05 15:23) [23]


Ну дак в сях все циклы с условием, т.е. там вычисляется некоторое выражение, и соответственно делает вывод true/false и т.д.

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


> А цикл сначала начинать? Или что с ним делать? А еще, видимо,
>  неправильно, что адресация с 0, и что битов в байте 8,
> а не 10. :-)

м.б. и смешно, но циклы типа
while (bEnd) do
begin
bEnd := ....
end;

вопросов начинать ли сначала или пересчитывать не вызывают?
Что еще не правильно решать Вам.


 
_Lucky_   (2005-12-29 14:21) [28]

А что действительно это так: "по стандарту Паскаля значение переменной цикла после выхода не определено"?


 
MBo ©   (2005-12-29 15:23) [29]

>_Lucky_   (29.12.05 14:21) [28]

Да



Страницы: 1 вся ветка

Форум: "Media";
Текущий архив: 2006.05.28;
Скачать: [xml.tar.bz2];

Наверх





Память: 0.56 MB
Время: 0.01 c
2-1147368301
Vitalik__
2006-05-11 21:25
2006.05.28
string


15-1146487535
dyd
2006-05-01 16:45
2006.05.28
ICQ


2-1147372407
ribbon
2006-05-11 22:33
2006.05.28
Проект, наподобие IBExpert


1-1145659505
gear
2006-04-22 02:45
2006.05.28
Теряется StayOnTop у формы.


11-1127135844
=mike=
2005-09-19 17:17
2006.05.28
Не получается скомпилировать проект





Afrikaans Albanian Arabic Armenian Azerbaijani Basque Belarusian Bulgarian Catalan Chinese (Simplified) Chinese (Traditional) Croatian Czech Danish Dutch English Estonian Filipino Finnish French
Galician Georgian German Greek Haitian Creole Hebrew Hindi Hungarian Icelandic Indonesian Irish Italian Japanese Korean Latvian Lithuanian Macedonian Malay Maltese Norwegian
Persian Polish Portuguese Romanian Russian Serbian Slovak Slovenian Spanish Swahili Swedish Thai Turkish Ukrainian Urdu Vietnamese Welsh Yiddish Bengali Bosnian
Cebuano Esperanto Gujarati Hausa Hmong Igbo Javanese Kannada Khmer Lao Latin Maori Marathi Mongolian Nepali Punjabi Somali Tamil Telugu Yoruba
Zulu
Английский Французский Немецкий Итальянский Португальский Русский Испанский