Есть ли более быстрый способ поворота большого растрового изображения на 90 или 270 градусов, чем просто выполнение вложенного цикла с инвертированными координатами?
Растровые изображения имеют размер 8 бит / с и обычно 2048 *2400* 8 бит / с
.
В настоящее время я делаю это, просто копируя примерно с инверсией аргумента (псевдокод:
for x = 0 to 2048-1
for y = 0 to 2048-1
dest[x][y]=src[y][x];
(На самом деле я делаю это с помощью указателей, для большей скорости, но это примерно такая же величина)
GDI довольно медленно работает с большими изображениями, а время загрузки / сохранения графических процессоров для текстур (карты GF7) совпадает с текущим временем ЦП.
Какие-нибудь советы, указатели? Алгоритм на месте был бы даже лучше, но скорость важнее, чем на месте.
Цель - Delphi, но это скорее алгоритмический вопрос. SSE (2) векторизация не проблема, для меня это достаточно большая проблема, чтобы кодировать ее на ассемблере
Продолжение ответа Нильса
- Изображение 2048x2700 -> 2700x2048
- Компилятор Turbo Explorer 2006 с включенной оптимизацией.
- Windows: Схема питания установлена на «Всегда включено». ( важно !!!! )
- Машина: Core2 6600 (2,4 ГГц)
время со старой программой: 32 мс (шаг 1)
время с шагом 8: 12 мс
время с шагом 16: 10 мс
время с шагом 32+: 9 мс
Тем временем я также тестировал на Athlon 64 X2 (5200+ iirc), и скорость там была чуть больше, чем в четыре раза (от 80 до 19 мс).
Ускорение того стоит, спасибо. Может быть, в течение летних месяцев я буду мучить себя версией SSE (2). Однако я уже думал о том, как справиться с этим, и я думаю, что у меня закончатся регистры SSE2 для прямой реализации:
for n:=0 to 7 do
begin
load r0, <source+n*rowsize>
shift byte from r0 into r1
shift byte from r0 into r2
..
shift byte from r0 into r8
end;
store r1, <target>
store r2, <target+1*<rowsize>
..
store r8, <target+7*<rowsize>
Так что 8x8 нужно 9 регистров, а 32-битный SSE имеет только 8. В любом случае, это что-то для летних месяцев: -)
Обратите внимание, что указатель - это то, что я делаю не по инстинкту, но, возможно, что-то в этом есть, если ваши измерения не заданы жестко, компилятор не может превратить мул в сдвиг. В то время как мульс и сич в наше время дешевы, они также создают больше регистрового давления.
Код (проверяется путем вычитания результата из реализации "naieve" rotate1):
const stepsize = 32;
procedure rotatealign(Source: tbw8image; Target:tbw8image);
var stepsx,stepsy,restx,resty : Integer;
RowPitchSource, RowPitchTarget : Integer;
pSource, pTarget,ps1,ps2 : pchar;
x,y,i,j: integer;
rpstep : integer;
begin
RowPitchSource := source.RowPitch; // bytes to jump to next line. Can be negative (includes alignment)
RowPitchTarget := target.RowPitch; rpstep:=RowPitchTarget*stepsize;
stepsx:=source.ImageWidth div stepsize;
stepsy:=source.ImageHeight div stepsize;
// check if mod 16=0 here for both dimensions, if so -> SSE2.
for y := 0 to stepsy - 1 do
begin
psource:=source.GetImagePointer(0,y*stepsize); // gets pointer to pixel x,y
ptarget:=Target.GetImagePointer(target.imagewidth-(y+1)*stepsize,0);
for x := 0 to stepsx - 1 do
begin
for i := 0 to stepsize - 1 do
begin
ps1:=@psource[rowpitchsource*i]; // ( 0,i)
ps2:=@ptarget[stepsize-1-i]; // (maxx-i,0);
for j := 0 to stepsize - 1 do
begin
ps2[0]:=ps1[j];
inc(ps2,RowPitchTarget);
end;
end;
inc(psource,stepsize);
inc(ptarget,rpstep);
end;
end;
// 3 more areas to do, with dimensions
// - stepsy*stepsize * restx // right most column of restx width
// - stepsx*stepsize * resty // bottom row with resty height
// - restx*resty // bottom-right rectangle.
restx:=source.ImageWidth mod stepsize; // typically zero because width is
// typically 1024 or 2048
resty:=source.Imageheight mod stepsize;
if restx>0 then
begin
// one loop less, since we know this fits in one line of "blocks"
psource:=source.GetImagePointer(source.ImageWidth-restx,0); // gets pointer to pixel x,y
ptarget:=Target.GetImagePointer(Target.imagewidth-stepsize,Target.imageheight-restx);
for y := 0 to stepsy - 1 do
begin
for i := 0 to stepsize - 1 do
begin
ps1:=@psource[rowpitchsource*i]; // ( 0,i)
ps2:=@ptarget[stepsize-1-i]; // (maxx-i,0);
for j := 0 to restx - 1 do
begin
ps2[0]:=ps1[j];
inc(ps2,RowPitchTarget);
end;
end;
inc(psource,stepsize*RowPitchSource);
dec(ptarget,stepsize);
end;
end;
if resty>0 then
begin
// one loop less, since we know this fits in one line of "blocks"
psource:=source.GetImagePointer(0,source.ImageHeight-resty); // gets pointer to pixel x,y
ptarget:=Target.GetImagePointer(0,0);
for x := 0 to stepsx - 1 do
begin
for i := 0 to resty- 1 do
begin
ps1:=@psource[rowpitchsource*i]; // ( 0,i)
ps2:=@ptarget[resty-1-i]; // (maxx-i,0);
for j := 0 to stepsize - 1 do
begin
ps2[0]:=ps1[j];
inc(ps2,RowPitchTarget);
end;
end;
inc(psource,stepsize);
inc(ptarget,rpstep);
end;
end;
if (resty>0) and (restx>0) then
begin
// another loop less, since only one block
psource:=source.GetImagePointer(source.ImageWidth-restx,source.ImageHeight-resty); // gets pointer to pixel x,y
ptarget:=Target.GetImagePointer(0,target.ImageHeight-restx);
for i := 0 to resty- 1 do
begin
ps1:=@psource[rowpitchsource*i]; // ( 0,i)
ps2:=@ptarget[resty-1-i]; // (maxx-i,0);
for j := 0 to restx - 1 do
begin
ps2[0]:=ps1[j];
inc(ps2,RowPitchTarget);
end;
end;
end;
end;
Обновление 2 Generics
Я пытался обновить этот код до универсальной версии в Delphi XE. Я потерпел неудачу из-за QC 99703, и форумчане уже подтвердили, что он также существует в XE2. Пожалуйста, проголосуйте за это :-)
Обновление 3 Generics
Работает сейчас в XE10