Markierungen in Bitmap zeichnen
-
Hi!
Ich habe mir eine Dialog-Anwendung erstellt, die GDI+ nutzt um Bilder (BMP, JPG; PNG, ...) anzuzeigen.
In diese Bilder möchte ich als Benutzer mit der Maus Markierungen in Form von Linien zwischen zwei Bildpunkten einfügen. Diese Linien sollen beim Zoomen nicht "fetter" werden, aber entsprechend des Zoomfaktors länger oder kürzer werden, damit sie immer die selben Bildpunkte verbinden.Meine Idee wäre die Linien jedes Mal neuzuzeichen, das erscheint mir aber sehr umständlich. Gibt es hierfür eine elegantere Lösung?
Ein weiteres Problem habe ich auch schon beim Setzen der Markierungen, da mir die Methode OnLButtonDown(UINT nFlags, CPoint point) mit point die Mausposition relativ zum Dialogfenster bereitstellt, das Bild stelle ich aber in einem Rahmen innerhalb des Dialogfensters dar, so dass ich die Koordinaten relativ zu dem Rahmen bräuchte oder noch besser relativ zum Bild.
Hier wäre meine Idee, mir das Rechteck des Rahmens über das Control des Rahmens zu holen.
this->m_statFRM.GetClientRect(&lRect);Und dann mit dem Zoomfaktor und der Verschiebung des Koordinatenursprungs rumrechnen. Auch das erscheint mir wieder sehr umständlich. Gibt es auch hierfür eine elegantere Lösung?
Für Vorschläge wäre ich sehr dankbar.
Und so zeige ich das Bild an, zoome und verschiebe es:
void CFotovermessungDlg::OnFileopen() { // Bild auswaehlen if(this->ImgSelect() == 0) { //Wenn Auswahl erfolgreich //Bild oeffnen if(this->ImgOpen() == 0) { //Wenn Oeffnen erfolgreich //Bild anzeigen this->ImgShow(); //Buttons aktivieren this->m_butFileCl.EnableWindow(true); this->m_butZoomP.EnableWindow(true); this->m_butZoom100.EnableWindow(true); this->m_butZoomM.EnableWindow(true); }//if else //Wenn Oeffnen nicht erfolgreich //Fehlerhinweis anzeigen AfxMessageBox("Konnte Bilddatei nicht öffnen!"); }//if } BOOL CFotovermessungDlg::ImgSelect() { static char szFilter[]="Alle Dateien (*.*)|*.*||"; CFileDialog dlgFile(TRUE, "*.JPG", this->m_strBMPFileName, OFN_HIDEREADONLY|OFN_OVERWRITEPROMPT, szFilter); if(dlgFile.DoModal() == IDOK) { //Datei wurde ausgewählt this->ImgClose(); this->m_strBMPFileName = dlgFile.GetPathName(); return 0; } //if else //Auswahl wurde durch Benutzer abgebrochen return 1; } BOOL CFotovermessungDlg::ImgOpen() { //Bild laden, falls nötig vorher Speicher holen if(this->m_bmpOrig == NULL) this->m_bmpOrig = new Gdiplus::Bitmap(this->m_strBMPFileName.AllocSysString()); else this->m_bmpOrig->FromFile(this->m_strBMPFileName.AllocSysString()); if(this->m_bmpOrig != NULL) { if(this->m_bmpOrig->GetFlags() == Gdiplus::ImageFlagsNone) return 1; else { this->ImgZoom(); //Zoomfaktor automatisch bestimmen this->ImgMove(); //Ursprung automatisch bestimmen return 0; }//else }//if else return 1; } BOOL CFotovermessungDlg::ImgShow() { CRect lrect; this->m_statFRM.GetClientRect(&lrect); CMemDC dc (this->m_statFRM.GetDC(), &lrect); Gdiplus::Graphics graphics(dc.m_hDC); graphics.Clear(Gdiplus::Color::Black); graphics.DrawImage(this->m_bmpOrig, this->m_ptOrigin.x, this->m_ptOrigin.y, this->GetImgWidth(this->m_bmpOrig), this->GetImgHeight(this->m_bmpOrig)); return 0; } BOOL CFotovermessungDlg::ImgZoom() { float scaleX = 0.0f, scaleY = 0.0f; CRect lRect; if(this->m_bmpOrig != NULL) { this->m_statFRM.GetClientRect(&lRect); lRect.NormalizeRect(); this->m_szView.cx = lRect.Width(); this->m_szView.cy = lRect.Height(); scaleX = (float) this->m_szView.cx / this->m_bmpOrig->GetWidth(); scaleY = (float) this->m_szView.cy / this->m_bmpOrig->GetHeight(); if(scaleX > scaleY) this->m_fScaleFactor = scaleY; else this->m_fScaleFactor = scaleX; // Werte in der Anzeige aktualisieren UpdateControls(); return 0; }//if else return 1; } BOOL CFotovermessungDlg::ImgZoom(float factor) { if(this->m_bmpOrig != NULL) { this->m_fScaleFactor = factor; this->ImgMove(); // Werte in der Anzeige aktualisieren UpdateControls(); return 0; } else return 1; } void CFotovermessungDlg::ImgClose() { if(this->m_bmp != NULL) { delete this->m_bmp; this->m_bmp = NULL; }//if if(this->m_bmpWork != NULL) { delete this->m_bmpWork; this->m_bmpWork = NULL; }//if if(this->m_bmpOrig != NULL) { delete this->m_bmpOrig; this->m_bmpOrig = NULL; }//if this->m_strBMPFileName = ""; } void CFotovermessungDlg::OnZoomplus() { this->ImgZoom(this->m_fScaleFactor * 1.1f); this->ImgShow(); } void CFotovermessungDlg::OnZoom100() { this->ImgZoom(1.0f); this->ImgShow(); } void CFotovermessungDlg::OnZoomminus() { this->ImgZoom(this->m_fScaleFactor * 0.9f); this->ImgShow(); } BOOL CFotovermessungDlg::ImgMove(int x, int y) { CRect lrect; int b = 0, h = 0; this->m_statFRM.GetClientRect(&lrect); if(this->m_bmpOrig != NULL) { b = this->GetImgWidth(this->m_bmpOrig); h = this->GetImgHeight(this->m_bmpOrig); if(b > lrect.right) if(b + m_ptOrigin.x + x > lrect.right && m_ptOrigin.x + x <= 0) this->m_ptOrigin.x += x; if(h > lrect.bottom) if(h + m_ptOrigin.y + y > lrect.bottom && m_ptOrigin.y + y <= 0) this->m_ptOrigin.y += y; return 0; }//if else return 1; } BOOL CFotovermessungDlg::ImgMove(CPoint point) { return this->ImgMove(point.x, point.y); } BOOL CFotovermessungDlg::ImgMove() { long x = 0, y = 0; if(this->m_bmpOrig != NULL) { x = (long) (this->GetImgWidth(this->m_bmpOrig)); y = (long) (this->GetImgHeight(this->m_bmpOrig)); m_ptOrigin.x = (m_szView.cx - x) / 2; m_ptOrigin.y = (m_szView.cy - y) / 2; return 0; }//if else return 1; }
-
wchristian schrieb:
...mit point die Mausposition relativ zum Dialogfenster bereitstellt, das Bild stelle ich aber in einem Rahmen innerhalb des Dialogfensters dar, so dass ich die Koordinaten relativ zu dem Rahmen bräuchte oder noch besser relativ zum Bild...
dafür gibts die Function ScreenToClient / ClientToScreen
bei dem anderen kann ich dir leider nicht weiterhelfen.
-
ClientToScreen() und ScreenToClient() sind doch aber für die Umrechnung von Window-Koordinaten in Desktop-Koordinaten, oder? Habs natürlich trotzdem mal getestet, klappt damit nicht. Oder habe ich da was vergessen?
Gruß
-
Eigentlich so:
this->m_statFRM.GetClientRect(&lRect); this->m_statFRM.ClientToScreen( &lRect ); this->ScreenToClient( &lRect );
-
Danke, habe es jetzt so hinbekommen:
void CFotovermessungDlg::OnLButtonDown(UINT nFlags, CPoint point) { CRect lFRect, lDRect; this->m_statFRM.GetClientRect(&lFRect); this->m_statFRM.GetWindowRect(&lDRect); ClientToScreen(&point); if(point.x >= lDRect.left && point.x <= lDRect.right && point.y >= lDRect.top && point.y <= lDRect.bottom) { point.x = point.x - lDRect.left; point.y = point.y - lDRect.top; } if(this->m_bmpWork == NULL) this->m_bmpWork = new Gdiplus::Bitmap(lFRect.right, lFRect.bottom); Gdiplus::Graphics graphics(this->m_bmpWork); Gdiplus::Pen lpen(Gdiplus::Color::Red, 1.0f); graphics.DrawLine(&lpen, point.x-5, point.y, point.x+5, point.y); graphics.DrawLine(&lpen, point.x, point.y-5, point.x, point.y+5); this->ImgShow(); CDialog::OnLButtonDown(nFlags, point); }In ImgShow() wird dann zuerst das richtige Bild gezeichnet und dann das Bild mit den Linien drübergelegt. Dabei gibt es aber noch das Problem, dass er die Linien jetzt zwar in der Nähe des Mausklicks zeichnet, aber eben leider nicht genau dort, wo ich geklickt habe, aber das kriege ich bestimmt noch hin.

-
Könnte es sein, daß es daran liegt das Du in der DrawLine Funktion an der falschen Stelle 5 dazu addierst?
Außerdem halte ich es für riskant die Basisversion der Funktion mit veränderten Werten aufzurufen.
-
Mittlerweile hat sich der Code noch ganz schön geändert:
void CFotovermessungDlg::OnLButtonDown(UINT nFlags, CPoint point) { CRect lDRect; CPoint lpt = point; this->m_statFRM.GetWindowRect(&lDRect); ClientToScreen(&lpt); if(lpt.x >= lDRect.left && lpt.x <= lDRect.right && lpt.y >= lDRect.top && lpt.y <= lDRect.bottom) { lpt.x = (long) ((lpt.x - lDRect.left - 2 - this->m_ptOrigin.x + 0.5f) / this->m_fScaleFactor); lpt.y = (long) ((lpt.y - lDRect.top - 2 - this->m_ptOrigin.y + 0.5f) / this->m_fScaleFactor); } if(this->m_bmpWork == NULL) this->m_bmpWork = new Gdiplus::Bitmap(this->GetImgWidth(this->m_bmpOrig), this->GetImgHeight(this->m_bmpOrig)); Gdiplus::Graphics graphics(this->m_bmpWork); Gdiplus::Pen lpen(Gdiplus::Color::Red, 1.0f); graphics.DrawLine(&lpen, lpt.x-5, lpt.y, lpt.x+5, lpt.y); graphics.DrawLine(&lpen, lpt.x, lpt.y-5, lpt.x, lpt.y+5); this->ImgShow(); CDialog::OnLButtonDown(nFlags, point); }Ich bin jetzt erstmal davon abgekommen Linien zu zeichnen und zeichne erstmal kleine Kreuze, das lässt sich besser überprüfen. Die werden mit obigem Code sogar dort platziert, wo ich geklickt habe
Aber nur, wenn ich in der ersten IF-Klausel bei lpt.x und lpt.y je 2 Pixel abziehe, warum ich die abziehen muss, erschließt sich mir allerdings nicht so recht...BOOL CFotovermessungDlg::ImgShow() { CRect lrect; this->m_statFRM.GetClientRect(&lrect); CMemDC dc (this->m_statFRM.GetDC(), &lrect); Gdiplus::Graphics graphics(dc.m_hDC); graphics.Clear(Gdiplus::Color::Black); graphics.DrawImage(this->m_bmpOrig, this->m_ptOrigin.x, this->m_ptOrigin.y, this->GetImgWidth(this->m_bmpOrig), this->GetImgHeight(this->m_bmpOrig)); if(this->m_bmpWork != NULL) graphics.DrawImage(this->m_bmpWork, this->m_ptOrigin.x, this->m_ptOrigin.y, this->GetImgWidth(this->m_bmpWork), this->GetImgHeight(this->m_bmpWork)); return 0; }Habe mal 2 Screenshots gemacht, einmal ohne die 2 Pixel abzuziehen und einmal mit: http://img170.imageshack.us/gal.php?g=zwischenablage3.png
geklickt habe ich in beiden Fällen auf die Mitte des großen schwarzen Kreuzes, das sich in der Bitmap befindet und nicht durch das Programm gezeichnet wurde.Gruß
-
ClientRect und WindowRect haben i.d.R. verschiedene Größen. Sieht so aus als hätte m_statFRM einen Rahmen

-
Du hast Recht, die beiden Funktionen liefern (auch nach ScreenToClient) unterschiedliche Werte. hast du vielleicht einen Vorschlag wie ich es besser machen kann, damit ich auf die "- 2" verzichen kann?
Gruß
-
Na mal angenommen du wölltest den point aus OnLButtonDown in ClientKoordinaten von m_statFRM umrechnen:
void CFotovermessungDlg::OnLButtonDown(UINT nFlags, CPoint point) { CPoint ptFRM( point ); ClientToScreen( ptFRM ); m_statFRM.ScreenToClient( ptFRM ); }D.h. einfach wo es geht Clientkoordinaten verwenden und die Screenkoordinaten nur als Bezugssystem nutzen.
-
Vielen Dank, für deine Hilfe! Es funktioniert jetzt ohne 2 abzuziehen

void CFotovermessungDlg::OnLButtonDown(UINT nFlags, CPoint point) { Gdiplus::Pen lpen(Gdiplus::Color::Red, 1.0f); CPoint lpt(point); ClientToScreen(&lpt); CRect lDRect; this->m_statFRM.GetWindowRect(&lDRect); if(lpt.x >= lDRect.left && lpt.x <= lDRect.right && lpt.y >= lDRect.top && lpt.y <= lDRect.bottom) { this->m_statFRM.ScreenToClient(&lpt); lpt.x = (long) ((lpt.x - this->m_ptOrigin.x + 0.5f) / this->m_fScaleFactor); lpt.y = (long) ((lpt.y - this->m_ptOrigin.y + 0.5f) / this->m_fScaleFactor); this->PointSave(&lpt); if(this->m_bmpWork == NULL) this->m_bmpWork = new Gdiplus::Bitmap(this->m_bmpOrig->GetWidth(), this->m_bmpOrig->GetHeight()); Gdiplus::Graphics graphics(this->m_bmpWork); this->PointDrawCross(&graphics, &lpen, &lpt); this->PointDrawLines(); this->ImgShow(); } CDialog::OnLButtonDown(nFlags, point); }
-
Ich würde noch auf GetWindowRect verzichten:
void CFotovermessungDlg::OnLButtonDown(UINT nFlags, CPoint point) { Gdiplus::Pen lpen( Gdiplus::Color::Red, 1.0f ); CPoint lpt( point ); ClientToScreen( lpt ); m_statFRM.ScreenToClient( lpt ) CRect lDRect; m_statFRM.GetClientRect(&lDRect); if( !( lpt.x < 0 || lpt.y < 0 || lpt.x >= lDRect.Width() || lpt.y >= lDRect.Height() ) ) { lpt.x /= m_fScaleFactor; lpt.y /= m_fScaleFactor; PointSave( &lpt ); if( m_bmpWork == NULL) m_bmpWork = new Gdiplus::Bitmap( m_bmpOrig->GetWidth(), m_bmpOrig->GetHeight()); Gdiplus::Graphics graphics( m_bmpWork ); PointDrawCross(&graphics, &lpen, &lpt); PointDrawLines(); ImgShow(); } CDialog::OnLButtonDown(nFlags, point); }