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);
    }
    

Anmelden zum Antworten