StretchBlt, verschiedene Mapping Modes



  • Guten Morgen.

    Mein Ziel ist es, für eine Druckvorschau ein DC mit MM_LOMETRIC in ein DC mit MM_TEXT mit StretchBlt zu blitten. Prinzipiell funktioniert das auch, jedoch ist der geblittete DC immer schwarz. Wenn beide den gleichen Mapping Mode haben, funktioniert das einwandfrei, allerdings bin ich auf MM_LOMETRIC angewiesen, da der so erzeugte DC auch zum Drucken verwendet werden soll/muss.

    Das ganze sieht so aus:

    // create dc for double buffering
    	HDC backBufferDC = CreateCompatibleDC (hdc);
    	HBITMAP backBufferBMP = CreateCompatibleBitmap (hdc, rect.right - rect.left, rect.bottom - rect.top);
    	HBITMAP oldOriginalBMP = (HBITMAP) SelectObject (backBufferDC, backBufferBMP);
    	SetBkMode (backBufferDC, TRANSPARENT);
    
    	// fill background
    	HBRUSH bkgColor = (HBRUSH) COLOR_BACKGROUND + 1;
    	FillRect (backBufferDC, &rect, bkgColor);
    	DeleteObject (bkgColor);
    
    	RECT tmpRect;
    	SetRect (&tmpRect, rect.left + 10, rect.top + 10, rect.right - 10, rect.bottom - 10);
    
    	// preparations for print preview dc
    	RECT unscaledRect;
    	SetRect (&unscaledRect, 0, paperY, paperX, 0);
    
    	HDC unscaledDC = CreateCompatibleDC (backBufferDC);
    	SetMapMode (unscaledDC, MM_LOMETRIC);
    
    	HBITMAP unscaledBMP = CreateCompatibleBitmap (backBufferDC, paperX, paperY);
    	HBITMAP oldBMP = (HBITMAP) SelectObject (unscaledDC, unscaledBMP);
    
    	// draw white rectangle on print preview dc
    	HBRUSH paperColor = (HBRUSH) CreateSolidBrush (RGB (255, 255, 255));
    	FillRect (unscaledDC, &unscaledRect, paperColor);
    	DeleteObject (paperColor);
    
    	if (paperX > 0 && paperY > 0)	// only draw if paperX and paperY have valid values
    	{
    		/* blit print preview dc to back buffer dc depending on paper size (preview should be centered) */
    
    		// neccessary calculations
    		int canvasWidth = tmpRect.right - tmpRect.left;
    		int canvasHeight = tmpRect.bottom - tmpRect.top;
    		double canvasFactor = (double) canvasWidth / (double) canvasHeight;
    		double paperFactor = (double) paperX / (double) paperY;
    
    		if ((canvasFactor - paperFactor) >= 0.0009) // vertically centered
    		{
    			double paperWidth = (canvasHeight * paperFactor);
    			int margin = ((canvasWidth / 2.0) - (paperWidth / 2.0));
    
    			POINT convert = {paperX, paperY};
    			DPtoLP (unscaledDC, &convert, 1);
    
    			StretchBlt (backBufferDC, tmpRect.left + margin, tmpRect.top, tmpRect.right - tmpRect.left - (margin * 2), canvasHeight, unscaledDC, unscaledRect.left, unscaledRect.bottom, convert.x, convert.y, SRCCOPY);
    		}
    		// else if ((paperFactor - canvasFactor) >= 0.0009)  horizontally centered
    
    	}
    
    	// reset print preview dc's objects
    	SelectObject (unscaledDC, oldBMP);
    	DeleteObject (unscaledBMP);
    	DeleteDC (unscaledDC);
    
    	// blit back buffer dc to original dc
    	BitBlt (hdc, rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top, backBufferDC, 0, 0, SRCCOPY);
    
    	// reset back buffer dc's objects
    	SelectObject (backBufferDC, oldOriginalBMP);
    	DeleteObject (backBufferBMP);
    	DeleteDC (backBufferDC);
    

    Ich habe sowohl mit SetStretchBltMode, als auch mit den Copy Bits rumexperimentiert (und einigen anderen Einstellungen), aber das Ergebnis bleibt immer das gleiche: ein schwarzer Kasten in der richtigen Größe. Auch wenn ich zusätzlich noch etwas Farbiges einzeichne (z.B. einen roten Kreis), wird der gesamte DC (unscaledDC) schwarz rein geblittet. Ich gehe davon aus, dass der Blit nicht richtig ausgeführt wird und der angegebene Bereich einfach aufgefüllt wird. Trotz mehrstündiger Suche bin ich dem Fehler aber nicht auf die Schliche gekommen (Konzentration lässt irgendwann einfach nach). Ich würde mich über einen Lösungsvorschlag freuen (ist hoffentlich nur ein triviales Problem).

    Vielen Dank schonmal!

    EDIT: Gerade vergessen: StretchBlt schlägt laut Rückgabewert nicht fehl. Meine persönliche Vermutung ist, dass CreateCompatibleBitmap ein falsches Bitmap erzeugt. Laut MSDN benötigt die Funktion Pixel, jedoch übergebe ich logische Einheit Millimeter. Dass der ganze Block jedoch schwarz bleibt und nicht wenigstens ein Teil weiß wird kann ich zwar gerade nicht nachvollziehen, aber testen kann ich jetzt erstml auch nichts mehr.



  • Was passiert wenn du den Map-Mode temporär für den StretchBlt auf MM_TEXT umstellst?
    Kannst ihn danach ja wieder auf MM_LOMETRIC zurückstellen wenn das nötig ist.

    ps:
    Das

    HBITMAP unscaledBMP = CreateCompatibleBitmap (backBufferDC, paperX, paperY);
    

    ist AFAIK falsch. Du musst hier wieder den originalen DC nehmen, also hdc in deinem Beispiel. Sonst bekommst du ne monochrom Bitmap (1 bpp), da backBufferDC ja bereits selbst ein "compatible DC" ist.



  • hustbaer schrieb:

    Was passiert wenn du den Map-Mode temporär für den StretchBlt auf MM_TEXT umstellst?
    Kannst ihn danach ja wieder auf MM_LOMETRIC zurückstellen wenn das nötig ist.

    ps:
    Das

    HBITMAP unscaledBMP = CreateCompatibleBitmap (backBufferDC, paperX, paperY);
    

    ist AFAIK falsch. Du musst hier wieder den originalen DC nehmen, also hdc in deinem Beispiel. Sonst bekommst du ne monochrom Bitmap (1 bpp), da backBufferDC ja bereits selbst ein "compatible DC" ist.

    Das hatte ich probiert. Es passierte gar nichts. Den Fehler habe ich jetzt gerade gefunden. Das Problem bestand darin, dass CreateCompatibleBitmap Pixel erwartet und alle anderen Funktionen "logical units" (bspw. Millimeter, je nach Map Mode). Die Lösung ist also, die Größe des Bitmaps von Pixeln in die entsprechende logische Einheit umzurechnen. Bei allen anderen Funktionen kann ich dann mit den logischen Einheiten weiterarbeiten. Hierbei besteht nur das "Problem", dass die Genauigkeit entweder auf der Strecke bleibt, da ab einer bestimmten Größe CreateCompatibleBitmap fehlschlägt (vereinfacht gesagt ab einer Größe von 10 x 5 Metern, umgerechnet für MM_HIMETRIC in 0,01 Millimeter) oder eben die maximal druckbare Größe schnell begrenzt ist (vernachlässigbar).
    CreateCompatibleDC und CreateCompatibleBitmap ist es ziemlich egal, was für ein DC sie bekommen. Der erstellte DC passt nach meiner bisherigen Erfahrung immer mit dem übergebenen zusammen. Wobei du dennoch recht hast und hdc anzuraten ist (habe es wieder geändert).
    Jetzt bleibt nur das Problem, dass ich die entsprechenden Objekte mit höchster Genauigkeit zeichnen und dann nochmals einzeln reinblitten muss. Intuitiv ist wirklich was anderes (wo sonst werden die Koordinatensysteme vollkommen entkoppelt, da muss ja intern auch eine Menge rumgerechnet werden).



  • Nein, ist nicht egal. Steht auch in der Doku:

    MSDN schrieb:

    Note: When a memory device context is created, it initially has a 1-by-1 monochrome bitmap selected into it. If this memory device context is used in CreateCompatibleBitmap, the bitmap that is created is a monochrome bitmap. To create a color bitmap, use the HDC that was used to create the memory device context, as shown in the following code:



  • hustbaer schrieb:

    Nein, ist nicht egal. Steht auch in der Doku:

    MSDN schrieb:

    Note: When a memory device context is created, it initially has a 1-by-1 monochrome bitmap selected into it. If this memory device context is used in CreateCompatibleBitmap, the bitmap that is created is a monochrome bitmap. To create a color bitmap, use the HDC that was used to create the memory device context, as shown in the following code:

    Und nochmal: prinzipiell hast du ja recht, dass hdc genutzt werden sollte. Es funktioniert trotzdem auf diese Art und Weise. Und es wird auch nicht nur ein monochromes Bitmap erstellt. GetObject bringt in beiden Varianten die gleichen Ergebnisse. Nenn es undefiniertes Verhalten oder was weiß ich.

    Richtig wäre es meiner Meinung nach so:

    HDC backBufferDC = CreateCompatibleDC (hdc);
    HBITMAP backBufferBMP = CreateCompatibleBitmap (backBufferDC, rect.right - rect.left, rect.bottom - rect.top);
    
    // Variante 1
    HDC unscaledDC = CreateCompatibleDC (backBufferDC);
    HBITMAP unscaledBMP = CreateCompatibleBitmap (unscaledDC, paperX, paperY);
    
    // Variante 2
    HDC unscaledDC = CreateCompatibleDC (hdc);
    HBITMAP unscaledBMP = CreateCompatibleBitmap (unscaledDC, paperX, paperY);
    

    Nehme ich anstatt hdc NULL bekomme ich eine monochrome Bitmap. Sonst nicht.



  • Hm.
    Komisch.
    Kann sein dass es geht nachdem man die originale 1x1x1bpp Bitmap schon ausgetauscht hat.
    Ansonsten wäre das Kommentar in der MSDN einfach nur total falsch.

    Fake oder Echt schrieb:

    HDC backBufferDC = CreateCompatibleDC (hdc);
    HBITMAP backBufferBMP = CreateCompatibleBitmap (backBufferDC, rect.right - rect.left, rect.bottom - rect.top);
    

    Genau das -- hier muss backBufferDC ja noch die originale Bitmap enthalten -- muss laut MSDN zu einer 1bpp Bitmap führen. Hab's aber nicht ausprobiert.

    Wobei ich die ganze 1x1x1bpp Geschichte sowieso immer doof gefunden habe. Ich fände es viel logischer wenn die von CreateCompatibleDC mit-erzeugte Bitmap einfach die kleinste mögliche Bitmap mit dem Farbformat des Original-DCs wäre. Dann könnte man so nen Memory-DC auch ohne Probleme "weiterklonen".

    ps: Falls du mit "richtig wäre es meiner Meinung nach so" meinst dass du es schön fändest wenn es so funktionieren würde, dann kann ich nur sagen: ja, fände ich auch schön. Wenn es darum geht was wirklich funktioniert, dann bin ich mir fast sicher dass es so gerade nicht funktioniert 😉



  • hustbaer schrieb:

    ps: Falls du mit "richtig wäre es meiner Meinung nach so" meinst dass du es schön fändest wenn es so funktionieren würde, dann kann ich nur sagen: ja, fände ich auch schön. Wenn es darum geht was wirklich funktioniert, dann bin ich mir fast sicher dass es so gerade nicht funktioniert 😉

    Da gebe ich dir allerdings recht. So wie ich das dargestellt habe ist es falsch. Richtig und definiert ist es so wie du es gesagt hast: Immer den gleichen hdc für CreateCompatibleDC und CreateCompatibleBitmap.


Log in to reply