Версия для печати темы
Нажмите сюда для просмотра этой темы в оригинальном формате
Форум программистов > Delphi: Для новичков > Инвертировать часть изображения


Автор: PsiMagistr 23.6.2010, 09:44
Ребята, мне надо инвертировать часть изображения. Какие есть пути решения пограмотней?

Автор: DarkProg 23.6.2010, 09:53
Если просто прямоугольником от координаты (x1, y1) до (x2, y2), то можно скопировать часть изображения на отдельный битмап, и там уже его инвертировать, а решений есть много одно из них вы с легкостью найдёте в DRKB или Delphiworld

Автор: PsiMagistr 23.6.2010, 10:17
Спасибо. Это на поверхности конечно. А без создания отдельного BitMap?

Автор: AntonN 23.6.2010, 11:46
пробегаешь по канвасу (если оно есть) или по пикселям битмапа (если он есть) сканлайном, достаешь каналы пикселя (GetRvalue() например, у сканлайна они уже готовые), отнимаешь их от 255 и записываешь обратно

Автор: Snowy 23.6.2010, 12:19
Зачем сканлайн? Это долго.
Да и смысл изобретать велосипед, когда всё уже готово.
И да, можно без второго битмапа. И всего в одну строку.

Допустим, нужно инвертировать прямоугольную область x100:y50, шириной 200:100
Код
  BitBlt(bmp.Canvas.Handle, 100, 50, 200, 100, bmp.Canvas.Handle, 100, 50, DSTINVERT);
DSTINVERT говорит, что мы просто хотим инвертировать фрагмент исходного изображения.

Автор: Frees 23.6.2010, 12:35
Код

var
  r: TRect;
begin
  Image1.Picture.Bitmap.Canvas.CopyMode := cmDstInvert;
  r := Rect(10,10,100,100);
  Image1.Picture.Bitmap.Canvas.CopyRect(r, Image1.Picture.Bitmap.Canvas, r);
  Image1.Picture.Bitmap.Canvas.CopyMode := cmSrcCopy;

Автор: PsiMagistr 23.6.2010, 12:42
Спасибо. Но пока не вышло. Имею Image на форме. При нажатии на кнопку должен инвертироваться прямоугольник.

Координаты верхней точки прямоугольника 0, 0.

ширина 128, высота 83.

Пробовал 

Код

BitBlt(Im.Picture.Bitmap.Canvas.Handle, 0, 0, 128, 83, Im.Picture.Bitmap.Canvas.Handle, 128, 83, DSTINVERT);


Ноль внимания.

Добавлено через 1 минуту и 9 секунд
Frees,  спасибо, сейчас еще ваш пример попробую.

Добавлено через 6 минут и 26 секунд
Frees,  Ваше работает однако. 

А что за метод: CopyMode? И что такое  cmSrcCopy; 

Автор: Snowy 23.6.2010, 12:58
Цитата(PsiMagistr @  23.6.2010,  12:42 Найти цитируемый пост)
Ноль внимания.
Правильно. Код только инвертирует изображение. Но не рисует его.
В случае с имаджем, нужно его обновить.
Код
  BitBlt(Im.Picture.Bitmap.Canvas.Handle, 0, 0, 128, 83, Im.Picture.Bitmap.Canvas.Handle, 0, 0, DSTINVERT);
  Im.Refresh;


Цитата(PsiMagistr @  23.6.2010,  12:42 Найти цитируемый пост)
А что за метод: CopyMode? И что такое  cmSrcCopy; 
CopyMode - последний параметр в BitBlt.
cmDstInvert в CopyRect - то же самое, что и DSTINVERT в BitBlt.

Автор: PsiMagistr 23.6.2010, 13:28
Snowy,  Сенкс, воспользовался BitBlit.

Автор: AntonN 23.6.2010, 14:03
Snowy
Цитата

Зачем сканлайн? Это долго.

долго для чего?

Автор: PsiMagistr 23.6.2010, 14:34
AntonN, Если сканлайнить то надо строки изображения циклом проходить, как я понял...

Автор: PsiMagistr 23.6.2010, 16:30
Заметил любопытнейшую вещь.

Если ДО вызова функции  BitBlt выполнить в Image загрузку изображения из какого либо файла, действие функции отображается несмотря на отсутствие refresh.

То есть

Код

BitBlt(Im.Picture.Bitmap.Canvas.Handle, 0, 0, 128, 83, Im.Picture.Bitmap.Canvas.Handle, 0, 0, DSTINVERT);
Im.Refresh;


Визуально аналогично следующему:

Код

Im1.Picture.LoadFromFile(ExtractFilePath(Application.ExeName)+ 'Proba.bmp');
BitBlt(Im.Picture.Bitmap.Canvas.Handle, 0, 0, 128, 83, Im.Picture.Bitmap.Canvas.Handle, 0, 0, DSTINVERT);


Как и почему так получается не разобрался. Да картинка вроде обновляется из за загрузки. Но она же обновляется до операции BitBlt


Автор: Mikel 23.6.2010, 16:49
Скорее всего оно перерисовывается сообщением, которое обрабатывается после того как исполнится весь код и будет выполняться обработчик сообщений.

Автор: PsiMagistr 23.6.2010, 17:08
Mikel,  Шут его знает дружище, вот например погляди:

Код

procedure TfrmGame.ImgMapClick(Sender: TObject);
 var R1,R2:TRect; //Области копирования.
begin
 ImgMap.Picture.LoadFromFile(ExtractFilePath(Application.ExeName)+'map.bmp'); //Грузим файл.
 ImgMap.Canvas.Brush.Style := bsClear; //Очищаем кисть
 ImgMap.Canvas.Pen.Color := clblue;
 ImgMap.Canvas.Rectangle(Lt1,Tp1,Lt2,Tp2);
 R1:=Rect(0,0,128,83); //Определяем область куда хотим копировать.
 R2:= Rect(Lt1,Tp1,Lt2,Tp2);  //Определяем область откуда хотим копировать.
 ImgLocation.Canvas.CopyRect(R1, ImgMap.Canvas, R2); //Рисуем нечто на изображении. А вот если здесь заккоментировать, для BitBlit понадобится рефреш в конце.
 BitBlt(ImgLocation.Picture.Bitmap.Canvas.Handle, 0, 0, 128, 83, ImgMap.Picture.Bitmap.Canvas.Handle, LT1, TP1, DSTINVERT); 

//BitBlt идет вообще последней командой. Однако рефрешить изображение не надо. Ибо мы рисовали на нем. 
end;



Автор: PsiMagistr 24.6.2010, 08:40
Просто пытаюсь понятькак работает функция. 

Все интересней и интересней. Есть два Image. ImgMap, ImLocation. В ImgLocation есть некое стартовое изображение по умолчанию.  Мы берем ImMap, и копируем оттуда кусочек в ImgLocation. Копировать надо с инвертированием.

Итак:

Код

procedure TfrmGame.ImgMapClick(Sender: TObject);
 var R1,R2:TRect; //Области копирования.
begin
 ImgMap.Picture.LoadFromFile(ExtractFilePath(Application.ExeName)+'map.bmp'); //Грузим файл.
 ImgMap.Canvas.Brush.Style := bsClear; //Очищаем кисть
 ImgMap.Canvas.Pen.Color := clblue;
 ImgMap.Canvas.Rectangle(Lt1,Tp1,Lt2,Tp2);
 R1:=Rect(0,0,128,83); //Определяем область куда хотим копировать.
 R2:= Rect(Lt1,Tp1,Lt2,Tp2);  //Определяем область откуда хотим копировать.
 ImgLocation.Picture.Bitmap.Canvas.CopyMode := cmDstInvert; //Если убрать эту строку, само копирование нужных участков происходит нормально, но естесственно без инвертирования. Если добавить эту строку, будет постоянно инвертироваться (туда-сюда), не меняясь, стартовое изображение.
 ImgLocation.Canvas.CopyRect(R1, ImgMap.Canvas, R2);
 end;

//В причинах не разобрался. Но получается, что сам режим cmInvert мешает правильному копированию участка. Как такое быть может?




Автор: Mikel 24.6.2010, 08:50
Ну да, скорее всего если вместо ImgLocation.Canvas.CopyRect(R1, ImgMap.Canvas, R2); будешь работать не с канвами самого image, а с bitmap.canvas, то не перерисуется smile а перерисовывается при любом изменении посредством GDI каким-н сообщением CM_INVALIDATE.
Работает- оставь и не трогай  smile 

Автор: PsiMagistr 24.6.2010, 08:53
Гм. Ничего не понял. Как инверт-режим может мешать правильному копированию участка?

Автор: PsiMagistr 24.6.2010, 09:21
Не поленюсь - выложу исходный кусок.


Автор: PsiMagistr 24.6.2010, 10:38
Как будто точно от режима зависит. странно.

При попытке просто копировать участок изображения в другой Image

Код

BitBlt(ImgLocation.Picture.Bitmap.Canvas.Handle, 0,0,128,83, ImgMap.Picture.Bitmap.Canvas.Handle, Lt1,Tp1, SrcCopy);
ImgLocation.Refresh;



Как по маслу.

При попытке изменить режим на DstInvert, чтобы :

Код

BitBlt(ImgLocation.Picture.Bitmap.Canvas.Handle, 0,0,128,83, ImgMap.Picture.Bitmap.Canvas.Handle, Lt1,Tp1, DstInvert);
ImgLocation.Refresh;


Не происходит копирования участка.

Автор: AntonN 24.6.2010, 11:27
Код

procedure InvertRect_GDI(BT:Tbitmap; x,y:integer; width,height:word);
const
 MaxPixelCountA = MaxInt div SizeOf(TRGBQuad);
type
 PRGBAArray = ^TRGBAArray;
 TRGBAArray = array[0..MaxPixelCountA-1] of TRGBQuad;
var xTo,sx,YTo,sy,dstw,dsth: integer;
   i,ii:integer; RSource:PRGBAArray;
begin
 dstw:=bt.width;
 dsth:=BT.height;
 XTo:=x+Width-1; YTo:=y+height-1;
 if (y>=dstH) or (x>=dstW) or (YTo<0) or (XTo<0) then exit;
 sx:=Width; sy:=height;
 if X<0 then begin
     inc(sx,X);
     x:=0;
 end;
 if Y<0 then begin
     inc(sy,Y);
     y:=0;
 end;
 if XTo>=dstw then dec(sx,XTo-dstw+1);
 if YTo>=dsth then dec(sy,YTo-dsth+1);
 if (sx<=0) or (sy<=0) then exit;
 BT.PixelFormat:=pf32bit;

   for i:=0 to sy-1 do begin
     RSource:=BT.ScanLine[i+y];
     for ii:=0 to sx-1 do begin
       RSource[x+ii].rgbRed:=255-RSource[x+ii].rgbRed;
       RSource[x+ii].rgbGreen:=255-RSource[x+ii].rgbGreen;
       RSource[x+ii].rgbBlue:=255-RSource[x+ii].rgbBlue;
     end;
   end;
end;

procedure TForm1.Button1Click(Sender: TObject);
begin
  InvertRect_GDI(Image1.Picture.Bitmap,164,6,250,350);
  Image1.Invalidate;
end;

Автор: PsiMagistr 24.6.2010, 11:33
Спасибо большое. Только долго.


Автор: Snowy 24.6.2010, 11:44
Цитата(PsiMagistr @  23.6.2010,  16:30 Найти цитируемый пост)
Если ДО вызова функции  BitBlt выполнить в Image загрузку изображения из какого либо файла, действие функции отображается несмотря на отсутствие refresh.
Так рефреш выполняет процедура зарузки.
Сам рефреш не перерисовывает, а только даёт запрос в очередь сообщений, что нужно перерисовать.
Он отработает тогда, когда приложением будет получено сообщение на перерисовку.
А оно не будет получено, пока мы не отпустим тред.
Если между коммандами вставить Application.ProcessMessages, то он выполнит очередь сообщений и только потом будет инфертирование.
Как только мы закончим, очередь сообщений будет обработана, запрос отрисовки будет выполнен.

Цитата(PsiMagistr @  24.6.2010,  11:33 Найти цитируемый пост)
Только долго.
Ну так я сразу так и сказал...

Автор: AntonN 24.6.2010, 11:54
PsiMagistr
Цитата

Спасибо большое. Только долго.

Долго? для чего долго?

Добавлено через 2 минуты и 11 секунд
1000 инвертирований за 0,6 секунды областей 250*250 - это долго?

Добавлено через 5 минут и 7 секунд
небольшое изменение...
Код

for i:=0 to sy-1 do begin
     RSource:=BT.ScanLine[i+y];
     for ii:=0 to sx-1 do begin
       ix:=x+ii;
       RSource[ix].rgbRed:=255-RSource[ix].rgbRed;
       RSource[ix].rgbGreen:=255-RSource[ix].rgbGreen;
       RSource[ix].rgbBlue:=255-RSource[ix].rgbBlue;
     end;
   end;


и те же 1000 инвертов улетают за 0,18 секунды, это 5555 операций в секунду? ты ничего с "долго" не перепутал?

Автор: PsiMagistr 24.6.2010, 12:15
Не перепутал дружище. Если есть функция инвертирования, то зачем изобретение велосипеда?)

Автор: AntonN 24.6.2010, 12:15
Код

procedure TForm1.Button1Click(Sender: TObject);
var Divisor:Int64; T1,T2:Int64; i:integer; TotalTime:Extended;
begin
  Image1.Picture.Bitmap.PixelFormat:=pf32bit;
  if QueryPerformanceFrequency( Divisor ) then begin
     QueryPerformanceCounter(T2);


     for i:=0 to 1001-1 do begin
       InvertRect_GDI(Image1.Picture.Bitmap,0,0,640,480);
       //0,71
     end;

     {for i:=0 to 1001-1 do begin
       BitBlt(Image1.Picture.Bitmap.Canvas.Handle, 0, 0, 640, 480, Image1.Picture.Bitmap.Canvas.Handle, 0, 0, DSTINVERT);
       //0,88
     end; }


     QueryPerformanceCounter(T1);
     TotalTime:=(T1-T2)/Divisor ;
     edit1.text:=floattostr(TotalTime);
   end;

  Image1.Invalidate;
end;

сканлайн оказался быстрее на 20% (глубина цветности дисплея 32 бита). "Долго"...
Это еще без оптимизации до асма, пара xor+add с такой скоростью улетят, что моргнуть не успеем...

Автор: AntonN 24.6.2010, 12:15
PsiMagistr
я и вжу как она есть, уже второй день не можешь ее победить smile
а потом вдруг тебе понадобится серпия, crayscale, еще что нибудь - тоже готовое будешь искать? smile

Автор: Mikel 24.6.2010, 12:32
Я не понял что ты хочешь, но попробуй так: )))
Код

 ImgLocation.Picture.Bitmap.Canvas.CopyMode := NOTSRCCOPY;  //Попробуйте заккоментировать строку.
 ImgLocation.Picture.Bitmap.Canvas.CopyRect(R1, ImgMap.Canvas, R2);

Автор: AntonN 24.6.2010, 12:34
С позволения, я еще пофлужу...
Код

function DIBBits(BMP: TBitmap): Pointer;
var Section:TDIBSECTION;
begin
BMP.HandleType:=bmDIB;
GetObject(BMP.Handle,sizeof(TDIBSECTION),@Section);
Result:=Section.dsBm.bmBits;
end;

function ScanLineSize(BMP: TBitmap): Integer;
var Section:TDIBSECTION;
begin
BMP.HandleType:=bmDIB;
GetObject(BMP.Handle,sizeof(TDIBSECTION),@Section);
Result:=((Section.dsBmih.biBitCount * Section.dsBmih.biWidth + 31) shr 3) and $FFFFFFFC;;
end;

procedure InvertRect_GDI_asm(BT:Tbitmap; x,y:integer; width,height:word);
const
 MaxPixelCountA = MaxInt div SizeOf(TRGBQuad);
type
 PRGBAArray = ^TRGBAArray;
 TRGBAArray = array[0..MaxPixelCountA-1] of TRGBQuad;
var xTo,sx,YTo,sy,dstw,dsth: integer;
   i,ii,ix,inc1:integer; RSource:PRGBAArray;  DstBits:DWORD;
begin
 dstw:=bt.width;
 dsth:=BT.height;
 XTo:=x+Width-1; YTo:=y+height-1;
 if (y>=dstH) or (x>=dstW) or (YTo<0) or (XTo<0) then exit;
 sx:=Width; sy:=height;
 if X<0 then begin
     inc(sx,X);
     x:=0;
 end;
 if Y<0 then begin
     inc(sy,Y);
     y:=0;
 end;
 if XTo>=dstw then dec(sx,XTo-dstw+1);
 if YTo>=dsth then dec(sy,YTo-dsth+1);
 if (sx<=0) or (sy<=0) then exit;
 BT.PixelFormat:=pf32bit;
 inc1:=ScanLineSize(BT);
 DstBits:=DWORD( DWORD( DIBBits(BT)) + y*inc1 + x*4 );

  asm
   mov   eax, $FFFFFF
   @outer_loop:
    mov   ecx, sx
    mov   edi, DstBits
    
   @loop:
    xor   [edi], eax
    add   edi, 4
    dec   ecx
    jnz   @loop

   @l1:
    mov   ecx, inc1
    add   DstBits, ecx
    dec   sy
    jnz   @outer_loop
  end;
end;

накидал на скорую руку, выполняется при тех же параметрах из моего предшествующего поста за 0.3 секунды, это чуть больше чем в 2.5 раза быстрее BitBlt()

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

Автор: Snowy 24.6.2010, 13:14
Для точности эксперимента увеличил до 10000 инверсий.
Результат на моем ноуте:
InvertRect_GDI - 40 секунд
BitBlt - 13 секунд
Итог: Битблит в 3 раза быстрее

Автор: AntonN 24.6.2010, 13:21
У меня просто проц не слабый. С последней функцией можешь сравнить? просто интересно

Автор: PsiMagistr 24.6.2010, 13:23
Спасибо большое. Уже ассемблерные вставки появились.  smile  

Автор: PsiMagistr 24.6.2010, 13:58
Ребята, все. Уф-ф-ф. Спасибо всем. Причина: У меня картинка не очень чистая была. На глаз нипочем не заметно. А при инвертировании - пятна моментом.

Добавлено через 2 минуты и 39 секунд
Ребята, все. Уф-ф-ф. Спасибо всем. Причина: У меня картинка не очень чистая была. На глаз нипочем не заметно. А при инвертировании - пятна моментом.

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