Draw overlay

Intrebari legate de programarea cu biblioteci precum MFC, ATL, WTL si GDI+.
mesajflaviu
Membru++
Membru++
Posts: 687
Joined: 10 Sep 2008, 21:40
Judet: Cluj

Draw overlay

Post by mesajflaviu » 04 Oct 2012, 15:38

Am intalnit de curand o problema pe care nu stiu cum s-o rezolv: intr-o aplicatie MDI care afiseaza bitmap-uri trebuie sa suprapun un text+linii fixe ... bun, pana aici totul OK:

Code: Select all

void CTestDrawView::OnDraw(CDC* pDC)
{
	CTestDrawDoc* pDoc = GetDocument();
	ASSERT_VALID(pDoc);
	// TODO: add draw code for native data here
///*//// Draw the bitmap ////////////////////////////////////////////////////////////////
	CDC dcPicture;
	dcPicture.CreateCompatibleDC(pDC);
	CSize sizeBmp = pDoc->GetDocSize();
	CBitmap* pOldBitmap = dcPicture.SelectObject(pDoc->GetBitmap());
	pDC->SetStretchBltMode(COLORONCOLOR);
	pDC->StretchBlt(0, 0, sizeBmp.cx, -sizeBmp.cy, &dcPicture, 0, 0, sizeBmp.cx, sizeBmp.cy, SRCCOPY);
	pDC->SelectObject(pOldBitmap);
///*/////////////////////////////////////////////////////////////////////////////////////
///*//// Draw overlay /////////////////////////////////////////
	CPen pen(PS_SOLID, 0, RGB(0,220,0));
	CPen* pOldPen = pDC->SelectObject(&pen);
	CPoint ptText(63, 53);
	pDC->DPtoLP(&ptText);
	pDC->SetBkMode(TRANSPARENT);
	pDC->TextOut(ptText.x, ptText.y, _T("ABCDEFGH"));
	pDC->SelectObject(pOldPen);
///*///////////////////////////////////////////////////////////
}
si textul ramane fix cand bitmap-ul nu este marit (aplicatia poate mari bitmap-ul) ... dar cand se foloseste zoom mai mare de o anumita valoare (in cazul testat 20) acest text fix (overlay) nu mai e fix ... ci oscileaza putin, la scroll sau cand imaginea este mutata cu drag-and-drop (cu mouse stanga jos) ... am atasat o mica aplicatie de test, iar din CTestDrawDoc::OnNewDocument() se poate schimba bitmap-ul afisat ...

Unde cred ca e problema: acel punct, ptText, este convertit din unitati device in unitati logice cum ii trebuie metodei CDC::TextOut(...) ... pentru ca daca view-ul e marit mult, acele valori ptText.x = 63, ptText.y = 53 de exemplu (care sant date in unitati device), nu isi au corespondenta in unitati logice ...

Acest comportament poate fi vazut cu usurinta in proiectul atasat, dupa ce se mareste bitmap-ul cu mouse-wheel, cu tasta '+', sau marcand o zona a bitmap-ului cu right-button-down ...

Cum as putea rezolva aceasta problema ? Sa renunt la convertirea DPtoLP nu pot ... orice sugestie/idee ar fi foarte bine venita .... Multumesc.
Attachments
TestDraw2.zip
(171.23 KiB) Downloaded 466 times



User avatar
Ovidiu Cucu
Fondator
Fondator
Posts: 3778
Joined: 11 Jul 2007, 16:10
Judet: Iaşi
Location: Iasi
Contact:

Re: Draw overlay

Post by Ovidiu Cucu » 04 Oct 2012, 20:13

Eu zic asa: atata timp cat scopul tau este sa afisezi un bitmap intr-un screen DC, cu zoom sau fara zoom, si atata timp cat ai la dispozitie StretchBlt si echivalente, si nu vrei alte efecte speciale, uita de alte moduri de mapare decat MM_TEXT!

mesajflaviu
Membru++
Membru++
Posts: 687
Joined: 10 Sep 2008, 21:40
Judet: Cluj

Re: Draw overlay

Post by mesajflaviu » 04 Oct 2012, 22:25

Eu am reprodus un exemplu, dar aplicatia reala are mod de mapare MM_TEXT, si la factor de zoom de doar 2 are acest comportament nedorit ... numa ca zoom-ul e facut mult mai complicat ...

User avatar
Ovidiu Cucu
Fondator
Fondator
Posts: 3778
Joined: 11 Jul 2007, 16:10
Judet: Iaşi
Location: Iasi
Contact:

Re: Draw overlay

Post by Ovidiu Cucu » 05 Oct 2012, 09:14

Ok, atunci daca se lucreaza cu MM_TEXT de ce apare acolo LPtoDP?
Ia pune in OnDraw

Code: Select all

_ASSERTE(MM_TEXT == pDC->GetMapMode()); // Tzeapa!
si vezi ce se intampla!
Sunt prin cod niste apeluri la OnPrepareDC care iti schimba maparea si alte minuni.
Pe OnViewZoomin si OnViewZoomout n-ar trebui decat sa setezi o variabila care sa-ti tina un factor in functie de care calculezi parametrii pe care-i dai lui StretchBlt si setezi noii parametri pentru scroll, pastrand maparea MM_TEXT. Simplu si fara alte brizbriz-uri.

mesajflaviu
Membru++
Membru++
Posts: 687
Joined: 10 Sep 2008, 21:40
Judet: Cluj

Re: Draw overlay

Post by mesajflaviu » 05 Oct 2012, 10:07

Asa e, ai dreptate, o sa modific proiectul, intradevar cred ca va iesi mult mai simplu ...

Viorel
Microsoft MVP
Microsoft MVP
Posts: 293
Joined: 13 Jul 2007, 12:26

Re: Draw overlay

Post by Viorel » 05 Oct 2012, 10:28

Dacă vei desena nu doar imagini ci și figuri sau linii, atunci probabil nu întotdeauna vei reuși să efectuezi scalarea în modul MM_TEXT.

Partea cu desenarea o poți lăsa nemodificată iar afișarea textului o poți face cam așa:

Code: Select all

int n = pDC->SaveDC();
pDC->SetMapMode(MM_TEXT);
pDC->SetWindowOrg(0, 0);
pDC->SetViewportOrg(0, 0);
CPoint ptText(63, 53);
pDC->SetBkMode(TRANSPARENT);
pDC->TextOut(ptText.x, ptText.y, _T("ABCDEFGH"));
pDC->RestoreDC(n);

mesajflaviu
Membru++
Membru++
Posts: 687
Joined: 10 Sep 2008, 21:40
Judet: Cluj

Re: Draw overlay

Post by mesajflaviu » 05 Oct 2012, 16:04

Am modificat proiectul, singurul mod de mapare este MM_TEXT in toate operatiile view-ului (zoom, etc.) ... este intradevar mult mai simplu asa ... insa acea problema ramane in continuare :

Code: Select all

	int nSave = pDC->SaveDC();
	pDC->SetViewportOrg(0,0);
	pDC->SetWindowOrg(0,0);
	CPoint ptText(63, 53);
	pDC->SetBkMode(TRANSPARENT);
	pDC->TextOut(ptText.x, ptText.y, _T("ABCDEFGH"));
	pDC->RestoreDC(nSave);
am atasat si proiectul de test modificat ...
Attachments
TestDraw3.zip
(172.81 KiB) Downloaded 483 times
Last edited by mesajflaviu on 09 Oct 2012, 13:26, edited 1 time in total.

mesajflaviu
Membru++
Membru++
Posts: 687
Joined: 10 Sep 2008, 21:40
Judet: Cluj

Re: Draw overlay

Post by mesajflaviu » 08 Oct 2012, 11:41

Nu mai fac nici o conversie DPtoLP(...) sau LPtoDP(...), si totusi cand se misca view-ul cu dra&drop acel overlay nu ramane fix, ceea ce arata clar ca problema nu este la conversia intre unitati device->logic sau viceversa .... mai sap ... oricum, multumesc pentru idei.

User avatar
Ovidiu Cucu
Fondator
Fondator
Posts: 3778
Joined: 11 Jul 2007, 16:10
Judet: Iaşi
Location: Iasi
Contact:

Re: Draw overlay

Post by Ovidiu Cucu » 10 Oct 2012, 10:53

In primul rand ai cateva greseli care-ti pot pune usor programul pe chituci.
1.

Code: Select all

   if(m_pBitmap)
   {
      m_pBitmap->Detach();
      m_pBitmap->LoadBitmap(IDB_WALLPAPER);
   }

Testezi daca ai un bitmap valid prin pointerul la CBitmap (m_pBitmap). Nu-i Ok, pentru ca poate fi un pointer valid (se vede si din demo ca-l aloci pe costructor si-l stergi in destructor) si sa nu contina un handle la un bitmap valid (HBITMAP). Deci, corect este sa vezi ce-i cu acel handle, usual faci asta cu GetSafeHandle.
In plus, nu este suficient Detach pentru a "scapa" de un obiect GDI (bitmap, in cazul nostru). Pentru asta folosesti DeleteObject, altfel te alegi cu leaks de nu te vezi.
Deci, corect e asa:

Code: Select all

   ASSERT_VALID(m_pBitmap); // not a valid CBitmap pointer

   if(m_pBitmap->GetSafeHandle()) // see if already contains a valid bitmap 
   {
      m_pBitmap->DeleteObject(); // delete the old bitmap
   }
   m_pBitmap->LoadBitmap(IDB_WALLPAPER); // load the new bitmap
Ai aceste greseli in mai multe locuri din cod.

2.

Code: Select all

   afx_msg void OnZoomToWindow(WPARAM wParam, LPARAM lParam);
La asta VS6.0 nu da eroare, poate sa mearga in DEBUG insa poate la fel de bine sa crape in release.
Am mai zis de zeci de ori: "atunci cand mapezi cu mana un mesaj, respecta prototipul din documentate!".
Pentru ON_MESSAGE acesta este "LRESULT memberFn(WPARAM, LPARAM)" si NU "void memberFn(WPARAM, LPARAM)". Vezi User-Defined Handlers in MSDN.
Deci corect este:

Code: Select all

   afx_msg LRESULT OnZoomToWindow(WPARAM wParam, LPARAM lParam);
Si-or mai fi de-om mai gasi. :)

Acuma revin la problema initiala.
E atat de incalcit ce-i acolo incat nici nu pot urmari (inclusiv postezi mesaje custom pe undeva unde nu le vad rostul).
Pare ceva adaptat dupa cod luat de pe la CG sau CP, de numai stie nici darku ce se intampla acolo (apropo ce spuneam aici despre "bucati de cod luate de aiurea" :)).
Nu am timp acum insa o sa incerc mai incolo sa fac un "schelet" pentru ce vrei tu. Simplu si fara brizbriz-uri.

mesajflaviu
Membru++
Membru++
Posts: 687
Joined: 10 Sep 2008, 21:40
Judet: Cluj

Re: Draw overlay

Post by mesajflaviu » 10 Oct 2012, 14:16

Am mai zis de zeci de ori: "atunci cand mapezi cu mana un mesaj, respecta prototipul din documentate!".
Pentru ON_MESSAGE acesta este "LRESULT memberFn(WPARAM, LPARAM)" si NU "void memberFn(WPARAM, LPARAM)".
:oops: My mistake.
Testezi daca ai un bitmap valid prin pointerul la CBitmap (m_pBitmap). Nu-i Ok, pentru ca poate fi un pointer valid (se vede si din demo ca-l aloci pe costructor si-l stergi in destructor) si sa nu contina un handle la un bitmap valid (HBITMAP). Deci, corect este sa vezi ce-i cu acel handle, usual faci asta cu GetSafeHandle.
In plus, nu este suficient Detach pentru a "scapa" de un obiect GDI (bitmap, in cazul nostru). Pentru asta folosesti DeleteObject, altfel te alegi cu leaks de nu te vezi.
Ca sa scap de acele "memory-leaks" am intors-o pe dupa sura, prin clasa aplicatie ca sa deschid un fisier .bmp.

Am refacut proiectul si l-am simplificat, (se pot deschide fisiere bitmap, sau cu File->New, are un wallpaper propriu) ... sper ca e OK acum (am atasat proiectul refacut).

Dar problema initiala ramane: acel overlay pe care il desenez in CTestDrawView::OnDraw(...)

Code: Select all

///*//// Draw overlay /////////////////////////////////////////
	int nSave = pDC->SaveDC();
	pDC->SetViewportOrg(0,0);
//	pDC->SetWindowOrg(0,0);
	CPoint ptText(63, 53);
	pDC->SetBkMode(TRANSPARENT);
	pDC->TextOut(ptText.x, ptText.y, _T("ABCDEFGH"));
	pDC->RestoreDC(nSave);
///*///////////////////////////////////////////////////////////
nu este stabil cand imaginea este marita si se misca cu drag&drop-ul din LeftButtonDown-ul al mouse-ului ... si in proiectul real este mult mai vizibil comportamentul asta ciudat ... din pacate, nu imi dau seama de unde vine problema :?: Caci pana atunci nu am sanse sa o rezolv ...
Attachments
TestDraw3.zip
(173.5 KiB) Downloaded 425 times

mesajflaviu
Membru++
Membru++
Posts: 687
Joined: 10 Sep 2008, 21:40
Judet: Cluj

Re: Draw overlay

Post by mesajflaviu » 11 Oct 2012, 13:30

Cred ca incep sa-mi dau seama unde e problema: desenez bitmap-ul, si apoi, dupa aceea, mai desenez acel overlay ... si nu cred ca e bine asa:

Code: Select all

void CTestDrawView::OnDraw(CDC* pDC)
{
	CTestDrawDoc* pDoc = GetDocument();
	ASSERT_VALID(pDoc);
	// TODO: add draw code for native data here
///*//// Draw the bitmap ////////////////////////////////////////////////////////////////
	CDC MemDC;
	MemDC.CreateCompatibleDC(pDC);
	CSize sizeDoc = pDoc->GetDocSize();
	int cx = (int)((float)sizeDoc.cx * m_fZoomFactor);
	int cy = (int)((float)sizeDoc.cy * m_fZoomFactor);
	CSize sizeLog(cx, cy);
	CBitmap* pOldBitmap = MemDC.SelectObject(pDoc->GetBitmap());
	pDC->SetStretchBltMode(COLORONCOLOR);
	pDC->StretchBlt(0, 0, sizeLog.cx, sizeLog.cy, &MemDC, 0, 0, sizeDoc.cx, sizeDoc.cy, SRCCOPY);
	pDC->SelectObject(pOldBitmap);
///*/////////////////////////////////////////////////////////////////////////////////////
///*//// Draw overlay /////////////////////////////////////////
	int nSave = pDC->SaveDC();
	pDC->SetViewportOrg(0,0);
	pDC->SetWindowOrg(0,0);
	CPoint ptText(63, 53);
	pDC->DPtoLP(&ptText);
	pDC->SetBkMode(TRANSPARENT);
	pDC->TextOut(ptText.x, ptText.y, _T("ABCDEFGH"));
	pDC->RestoreDC(nSave);
///*///////////////////////////////////////////////////////////
nu stiu inca cum sa desenez overlay-ul in MemDC inainte de a afisa bitma-ul ( cu StretchBlt (...) ). [Daca nu spun vreo prostie].

mesajflaviu
Membru++
Membru++
Posts: 687
Joined: 10 Sep 2008, 21:40
Judet: Cluj

Re: Draw overlay

Post by mesajflaviu » 11 Oct 2012, 15:59

Cum spuneam, in proiectul original comportamentul asta ciudat se observa mult mai bine ... am atasat un proiect de test mai apropiat de cel real, in care se deseneaza bitmap-ul cu ajutorul clasei CDib, si apoi se deseneaza overlay-ul:

Code: Select all

void CTestDrawView::OnDraw(CDC* pDC)
{
	CTestDrawDoc* pDoc = GetDocument();
	ASSERT_VALID(pDoc);
	// TODO: add draw code for native data here
///*//// Draw the bitmap ////////////////////////////////////////////////////////////////
	CSize sizeDoc = pDoc->GetDib()->GetDimensions();
	int cx = (int)((float)sizeDoc.cx * m_fZoomFactor);
	int cy = (int)((float)sizeDoc.cy * m_fZoomFactor);
	CSize sizeLog(cx, cy);
	pDoc->GetDib()->Draw(pDC, CPoint(0,0), sizeLog);
///*/////////////////////////////////////////////////////////////////////////////////////
///*//// Draw overlay /////////////////////////////////////////
	int nSave = pDC->SaveDC();
	pDC->SetViewportOrg(0,0);
//	pDC->SetWindowOrg(0,0);
	CPoint ptText(63, 53);
	pDC->SetBkMode(TRANSPARENT);
	pDC->TextOut(ptText.x, ptText.y, _T("ABCDEFGH"));
	pDC->RestoreDC(nSave);
///*///////////////////////////////////////////////////////////
ceea ce cred ca nu e bine ...
Attachments
TestDraw4.zip
(169.61 KiB) Downloaded 453 times

mesajflaviu
Membru++
Membru++
Posts: 687
Joined: 10 Sep 2008, 21:40
Judet: Cluj

Re: Draw overlay

Post by mesajflaviu » 16 Oct 2012, 12:36

Sant aproape. (Sau asa cred eu).

Code: Select all

///*//// Draw the bitmap ////////////////////////////////////////////////////////////////
	CSize sizeDoc = pDoc->GetDib()->GetDimensions();
	int cx = (int)((float)sizeDoc.cx * m_fZoomFactor);
	int cy = (int)((float)sizeDoc.cy * m_fZoomFactor);
	CSize sizeLog(cx, cy);
	CBitmap bmp;
	bmp.CreateCompatibleBitmap(pDC, sizeLog.cx, sizeLog.cy); // create background bitmap
	CDC MemDC;
	MemDC.CreateCompatibleDC(pDC); // create memory device context
	CBitmap* pOldBitmap = MemDC.SelectObject(&bmp); // select background into memory device context
	pDoc->GetDib()->Draw(&MemDC, CPoint(0,0), sizeLog); // draw content of dib into memory device context
	DrawOverlay(&MemDC, pDC); // draw overlay (inline method)
	pDC->BitBlt(0, 0, sizeLog.cx, sizeLog.cy, &MemDC, 0, 0, SRCCOPY); // switch memory device context to real device context
	pDC->SelectObject(pOldBitmap); // select old bitmap
///*/////////////////////////////////////////////////////////////////////////////////////
aici desenez intreg continutul (bitmap-ul din CDib si overlay-ul) in memory DC, si apoi pun totul in DC real... in fapt desenez o singura data ... si merge aproape bine (acum overlay-ul e stabil si nu mai are flickering). Dar acest mod de desenare consuma multe resurse si cand fac drag&drop pe view, imaginea cu totul (bitmap-ul si overlay-ul) are delay (merge sacadat) ... plus ca procesorul bate catre 70%-80% ... am atasat si aplicatia de test pentru exemplificare ...

S-ar putea eficientiza codul de mai sus ? Sau ce nu am facut bine ?
Attachments
TestDraw5.zip
(193.67 KiB) Downloaded 431 times

mesajflaviu
Membru++
Membru++
Posts: 687
Joined: 10 Sep 2008, 21:40
Judet: Cluj

Re: Draw overlay

Post by mesajflaviu » 22 Oct 2012, 09:44

Pana la urma am reusit folosind tehnica "double-buffering". Atasez aici un proiect de test pentru cei care vor avea aceeasi problema.
Attachments
TestDraw6.zip
(183.92 KiB) Downloaded 474 times

User avatar
Ovidiu Cucu
Fondator
Fondator
Posts: 3778
Joined: 11 Jul 2007, 16:10
Judet: Iaşi
Location: Iasi
Contact:

Re: Draw overlay

Post by Ovidiu Cucu » 22 Oct 2012, 15:36

Acum se pare ca merge / se misca binisor.
Totusi iata cateva observatii si recomandari:

1. Foloseste HALFTONE si nu COLORONCOLOR.

Code: Select all

	//pDC->SetStretchBltMode(COLORONCOLOR); // <-- remove this 
   pDC->SetStretchBltMode(HALFTONE);       // <-- add this
   ::StretchDIBits(...
HALFTONE e cel mai bun mod pe care-l stie StretchBlt si StretchDIBits. Poti testa cu amandoua si-ai sa vezi o diferenta de calitate clara.

2. Nu arunca CException* ci CUserException*.

Code: Select all

   if(nCount != sizeof(BITMAPFILEHEADER))
   {
      //throw new CException;   // <-- remove this
      throw new CUserException; // <-- add this
   }
In implementarile mai noi de MFC, CException este o clasa abstracta ceea ce va duce la erori de compilare. Abstracta ar fi trebuit sa fie de la inceput insa probabil a fost o omisiune in VS6.0 si mai vechi.
Alternativ, ceva mai elegant ai putea sa derivezi propria clasa din CException in care sa suprascrii GetErrorMessage si ReportError.

3. Odata ce folosest MM_TEXT peste tot, cred ca nu-si mai au rost DPtoLP si LPtoDP-urile care ti-au ramas prin cod.

4. IDC_HAND este deja definit in SDK, pentru WINVER >= 0x0500. Sigur, VS6.0 defineste by default WINVER 0x0400 insa pentru a preveni orice conflicte ulterioare, e bine sa-l denumesti altfel, de exemplu IDC_HANDX.

5. De ce nu folosesti GDI+? Ai putea sa incarci mai multe formate (BMP, JPEG, GIF, TIFF, PNG, ICO) fara mare bataie de cap.
In plus, la zoom ai avea de ales intre metode de interpolare care sa dea o calitate mult mai buna decat StretchBlt cu HALFTONE.
Nu vine la pachet cu VS6.0 insa din cate stiu eu, se gaseste in PSDK din Martie 2003

Post Reply