MeasureString und DrawString - GDI auf die harte Tour



  • Klingt gut.

    Aber damit der Text wirklich gut aussieht, muss ich ihn horizontal auch mit Bruchteilen von Pixeln platzieren können -- das geht nur mit Gdi+.

    Die weiter führenden Fontinformationen erhalte ich aber nur aus GDI, und dafür muss ich sogar einen Gdi-kompatiblen Font erstellen.

    Inzwischen habe ich meinen Versuchsaufbau erweitert:
    Test #1 und #2 wie gehabt in Gdi+ aber jetzt mit der Unit Pixel, damit ich gleiche Bedingungen für die Anwendung von Gdi schaffen kann. Test #3 soll dasselbe einfach nur in Gdi nachvollziehen, aber die Resultate sind doch zu verschieden von den beiden ersten Tests. Das kann ich im Moment noch nicht erklären.

    Hier ist der gesamte geänderte und erweiterte Code meines Versuchsaufbaus; die Debug-Resultate für die String-Weiten habe ich überall als Kommentare hinzugefügt:

    void ApplWindow_TextDrawTest(HDC hDC)
    {
    	Gdiplus::Graphics *G = new Gdiplus::Graphics(hDC);
    	G->SetTextRenderingHint(TextRenderingHint::TextRenderingHintClearTypeGridFit);
    
    	Gdiplus::StringFormat MyFormat;
    	MyFormat.SetAlignment(Gdiplus::StringAlignment::StringAlignmentNear);
    	MyFormat.SetFormatFlags(Gdiplus::StringFormatFlags::StringFormatFlagsNoWrap);
    
    	Gdiplus::Font TextFont(L"Calibri", 36, Gdiplus::FontStyle::FontStyleBold, Gdiplus::Unit::UnitPixel);
    
    	const wchar_t *Text1M = L"M";
    	Gdiplus::PointF TextOrigin1M(0, 0);
    	Gdiplus::RectF TextBounds1M;
    
    	const wchar_t *Text2M = L"MM";
    	Gdiplus::PointF TextOrigin2M(0, 50);
    	Gdiplus::RectF TextBounds2M;
    
    	//--- Test #1:  using MeasureString ----------
    	G->MeasureString(Text1M, (INT)wcslen(Text1M), &TextFont, TextOrigin1M, &MyFormat, &TextBounds1M);
    	G->MeasureString(Text2M, (INT)wcslen(Text2M), &TextFont, TextOrigin2M, &MyFormat, &TextBounds2M);
    	//--- Results:  Text 1 Width= 44.414  ("M")
    	//---           Text 2 Width= 76.828  ("MM")
    
    	//--- Test #2:  using MeasureCharacterRanges ----------
    	Gdiplus::Status RCode;
    	Gdiplus::RectF LayoutRect(0, 0, 1000, 100);
    	Gdiplus::Region RegionsList[3];
    	Gdiplus::CharacterRange CRanges[3];
    	CRanges[0].First = 0; CRanges[0].Length = 1;
    	CRanges[1].First = 1; CRanges[1].Length = 1;
    	CRanges[2].First = 0; CRanges[2].Length = 2;
    	MyFormat.SetMeasurableCharacterRanges(3, CRanges);
    	G->MeasureCharacterRanges(Text2M, (INT)wcslen(Text2M), &TextFont, LayoutRect, &MyFormat, 3, RegionsList);
    	RCode = RegionsList[0].GetBounds(&TextBounds1M, G);		// Result: Text 1 Width = 32.000  ("M")
    	RCode = RegionsList[1].GetBounds(&TextBounds1M, G);		// Result: Text 1 Width = 32.000  ("M"; the second char)
    	RCode = RegionsList[2].GetBounds(&TextBounds2M, G);		// Result: Text 2 Width = 64.000  ("MM")
    
    	//--- Test #3:  using the good old GDI ----------
    	int MapModeResult = SetMapMode(hDC, MM_TEXT);			// MM_TEXT is equivalent to Unit::UnitPixel?
    	HFONT TextFont3 = CreateFont(36, 0, 0, 0, FW_BOLD, false, false, false, ANSI_CHARSET, OUT_TT_ONLY_PRECIS, CLIP_DEFAULT_PRECIS, CLEARTYPE_QUALITY, VARIABLE_PITCH | FF_DONTCARE, L"Calibri");
    	HGDIOBJ PrevFont = SelectObject(hDC, TextFont3);
    	ABCFLOAT ABCCharData;
    	BOOL RFlg = GetCharABCWidthsFloat(hDC, (UINT)'M', (UINT)'M', &ABCCharData); // Results: abcfA = 2.000; abcfB = 22.000; abcfC = 2.000
    
    	//--- End of Test ---
    	delete G;
    }
    

    Bei einem einzigen Zeichen wird per Definition kein Kerning aktiv, aber trotzdem habe ich bei jedem der drei Tests vollkommen verschiedene Stringbreiten für das Einzelzeichen "M" erhalten.

    Ich habe wohl noch was übersehen, aber ich weiß nicht was ... 😃



  • WishfulThinking schrieb:

    Die weiter führenden Fontinformationen erhalte ich aber nur aus GDI, und dafür muss ich sogar einen Gdi-kompatiblen Font erstellen.

    Du kannst Gdiplus::Font in einen HFONT konvertieren. Gdiplus::Font::GetLogFontW und dann CreateFontIndirect oder so. Vielleicht sind die Fonts ja nicht gleich.

    Ansonsten musst mal mit den Optionen spielen. Auf den ersten Blick seh ich keine Fehler, aber das sagt noch nichts 😉



  • Danke, das war ein klasse Hinweis. Dabei habe ich in den LOGFONT-Daten auch gesehen, dass ich den GDI-Font mit einer falschen Schriftgröße erstellt hatte: Der Parameter hätte auf -36 (statt +36) gesetzt werden müssen.

    Mein dritter Testabschnitt sieht nun so aus:

    //--- Test #3:  using the good old GDI ----------
    	int MapModeResult = SetMapMode(hDC, MM_TEXT);			// MM_TEXT is equivalent to Unit::UnitPixel?
    	LOGFONTW TextFontLogDataW3;
    	RCode = TextFont.GetLogFontW(G, &TextFontLogDataW3);
    	HFONT TextFont3 = CreateFontIndirectW(&TextFontLogDataW3);
    	HGDIOBJ PrevFont = SelectObject(hDC, TextFont3);
    	ABCFLOAT ABCCharData;
    	BOOL RFlg = GetCharABCWidthsFloat(hDC, (UINT)'M', (UINT)'M', &ABCCharData); // Results: abcfA = 2.000; abcfB = 27.000; abcfC = 2.000
    

    Damit kommt das "M" nun auf eine Zeichenbreite von 31 Pixel, das ist aber noch immer nicht nahe genug am Resultat der Messung mit MeasureCharacterRanges.

    Bei den Resultaten aus dem zweiten und dritten Test bin ich aber auch noch generell skeptisch, denn ich kann mir nicht vorstellen, dass hier nur ganzzahlige Werte vorkommen...



  • WishfulThinking schrieb:

    Damit kommt das "M" nun auf eine Zeichenbreite von 31 Pixel, das ist aber noch immer nicht nahe genug am Resultat der Messung mit MeasureCharacterRanges.

    Du hast doch einmal 31 und ein mal 32 Pixel, sind doch schon mal ziemlich ähnliche Werte?
    GetCharABCWidthsFloat gibt dir ja den Abstand vor dem Zeichen, nach dem Zeichen, und die Breite vom Zeichen zurück. Du hast jeweils zwei Pixel vor und hinter dem "M". Aber wenn die gezeichnet werden, sind zwischen "MM" doch keine vier Pixel?
    D.h., das eigentlich interessante für dich ist wahrscheinlich die B Breite von dem GetCharABCWidthsFloat.

    Kannst du die Zeichen stark vergrößert in ein Messraster zeichnen? Also, keinen größeren Font verwenden, sondern einfach hochskalieren, das Raster aber nicht mitskalieren, damit man die Pixel von Hand abzählen kann.



  • Nein, leider kann ich diese Differenz bei weitem nicht akzeptieren. Im Beispiel handelt es sich ja nur um einen einzelnen Buchstaben, und solch eine Differenz würde sich über eine Zeile auch noch vergrößern.

    Was mich auch noch stört, ist, dass die Funktion GetCharABCWidthsFloat immer nur ganzzahlige Werte zurückliefert, egal, welche Schriftgröße ich einsetze.

    Mir ist bekannt, dass Fonts in einem ganzzahligen Raster (i.d.R. 1024x1024 oder 20148x2048) designed werden. daraus errechnen sich dann in Abhängigkeit von der tatsächlich gewählten Schriftgröße die aktuellen Werte der Punkte, u.a. auch für die Breite des Glyph, und dann natürlich immer in float-Werten.

    Wenn ich an die Werte für die Design-Schriftgröße und an die Weiten-Werte dieser Design-Schriftgröße herankäme, wäre die Umrechnung wohl deutlich einfacher. Aber diese Funktionen habe ich noch nicht gefunden. Irgendwie sind diese Werte in den Font-Daten enthalten, und ich mag einfach nicht glauben, dass ich dafür die Daten aus der Font-Datei selbst noch einmal interpretieren muss.



  • Noch schnell ein Nachtrag:

    Messraster usw sind sicher Lösungen für einzelne Zeichen, aber ich möchte schon eine mehr generelle Messmethode finden. (Jeder Editor kann das, und ganz bestimmt auch ohne sehr aufwändige Umwege - sonst würde er sich totrechnen).



  • WishfulThinking schrieb:

    Noch schnell ein Nachtrag:

    Du kannst deinen Beitrag auch bearbeiten.



  • Ist schon klar, aber irgendwie sieht das dann nicht authentisch aus.



  • Das Thema interessiert mich auch. Gibt es eigentlich auch eine Möglichkeit, einen GDI-Font (HFONT) direkt aus dem GDI+-Font abzuleiten und nicht, wie im Beispiel geschehen, den Font in GDI neu zu erstellen?



  • WishfulThinking schrieb:

    Nein, leider kann ich diese Differenz bei weitem nicht akzeptieren. Im Beispiel handelt es sich ja nur um einen einzelnen Buchstaben, und solch eine Differenz würde sich über eine Zeile auch noch vergrößern.

    Das ist schon klar, ich wollte nur sichergehen, dass wir von denselben Werten reden.
    Das Messraster wäre eine Hilfsmethode um festzustellen, wie die Funktionen genau ticken. Das muss man erstmal genau verstanden haben, und die verschiedenen Werte könnte man am Messraster dann evtl. besser nachvollziehen.

    GetCharABCWidthsFloat gibt im MM_TEXT Modus tatsächlich nur Integer zurück. Der Sinn von den Floats hier ist nur, dass du bei anderen Koordinatensystemen genauere Umrechnungen hast.

    PaulB48a schrieb:

    Gibt es eigentlich auch eine Möglichkeit, einen GDI-Font (HFONT) direkt aus dem GDI+-Font abzuleiten und nicht, wie im Beispiel geschehen, den Font in GDI neu zu erstellen?

    Hab ich doch schon geschrieben.



  • Du könntest vielleicht mal versuchen, dir das Buch "Windows Graphics Programming" zu besorgen. Da steht auch einiges zu dem Thema drin. Hab jetzt z.B. noch die Funktion GetCharacterPlacement gefunden, die hatte ich gar nicht mehr in Erinnerung. Bin mir nicht sicher, ob sie dir jezt weiterhilft, könnte aber sein.



  • Mit dem modifizierten Test #3 hatte ich tatsächlich nur die LOGFONT-Daten aus dem Gdi+-Font gewonnen (Funktion GetLogFontW ) und mit diesen Daten dann per CreateFontIndirectW einen neuen Gdi-Font erzeugt, in der Hoffnung, dass dieser wirklich dem Gdi+-Font ähnlich genug ist. Ich habe keinen Zweifel daran, dass beide Fonts ihre Daten aus derselben Quelle bezogen haben, aber ob die beiden Fonts auch in allen Parametern gleich sind, weiß ich leider nicht.

    Immerhin gibt es bei der Test-Messung der Breite von "M" ja Unterschiede.....

    MM_TEXT hatte ich gewählt, weil dieses Maßsystem in GDI und GDI+ gleich zu sein scheint; für die anderen Koordinatensysteme sah ich zwischen GDI und GDI+ eben keine Übereinstimmungen. Welches alternative Koordinatensystem würdest du vorschlagen?

    Dass GetABCCharWidthsFloat im Maßsystem MM_TEXT nur ganzzahlige Werte zurückgibt, habe ich in meiner Quelle (http://msdn.microsoft.com/en-us/library/dd144858%28v=vs.85%29.aspx) nicht nachvollziehen können. Hast du dazu eine andere Quelle? Die würde mich sehr interessieren. Bei der Beschreibung von GetABCCharWidths habe ich übrigens noch einen Hinweis gefunden, wie ich an Messwerte in Font Design Units kommen kann. Die sind, soweit ich weiß, tatsächlich nur ganzzahlig. Der Sache werde ich in dieser Woche mal nachgehen.



  • Ich scheine mir wirklich ein komplexes Thema ausgesucht zu haben. Bei meiner Suche im Netz bin ich allerdings gerade auf einen hochinteressanten Artikel gestoßen, in dem die wesentlichen Unterschiede beim Zeichnen (und vermutlich auch beim Berechnen der Textlänge) zwischen GDI und GDI+ aufgezeigt werden: http://support.microsoft.com/kb/307208/en-us. Die deutsche Übersetzung ist allerdings nicht besonders gut.

    Das werde ich mal verinnerlichen und meine Tests entsprechend anpassen. Mal sehen, wie weit ich damit komme.
    (Kann man hier eigentlich auch Bilder anhängen? Dann könnte ich auch zeigen, wie die Resultate von DrawString vs TextOut aussehen)



  • WishfulThinking schrieb:

    MM_TEXT hatte ich gewählt, weil dieses Maßsystem in GDI und GDI+ gleich zu sein scheint; für die anderen Koordinatensysteme sah ich zwischen GDI und GDI+ eben keine Übereinstimmungen. Welches alternative Koordinatensystem würdest du vorschlagen?

    Dass GetABCCharWidthsFloat im Maßsystem MM_TEXT nur ganzzahlige Werte zurückgibt, habe ich in meiner Quelle (http://msdn.microsoft.com/en-us/library/dd144858%28v=vs.85%29.aspx) nicht nachvollziehen können. Hast du dazu eine andere Quelle?

    Das steht in dem Buch, das ich erwähnt habe, Windows Graphics Programming. Das geht sehr detailliert auf viele Themen bezüglich GDI ein.

    Du kriegst die Infos mit GetABCCharWidthsFloat nicht genauer raus (steht auch in dem Buch). Das sind nur Floats, damit du in anderen Koordinatensystemen auch sinnvolle Werte bekommst. Also, wenn du sagst, du willst die Breite in Metern, dann bringt dir ein Integer nichts. Du kriegst einen Float, der auf Pixel gerundet ist. Aber eben nicht genauer als Pixel.

    Edit: hier kannst du keine Bilder anhängen, aber du kannst irgendeinen Imagehoster verwenden und hier einen Link reinposten.



  • Der in meinem vorangegangenen Posting erwähnte Artikel hat mir ein sehr gutes Stück weiter geholfen. Hier meine bisherigen Erkenntnisse:

    • GDI+ kann nicht als Ersatz für GDI angesehen werden -- GDI+ ist eine alternative Methode zur Ausgabe von Grafik.
    • GDI funktioniert pixelorientiert und damit unbedingt abhängig von dem Ausgabe-Device bzw. der gewählten Auflösung. Zeichen werden immer in ein ganzzahliges Geviert von Pixeln aufgelöst, deshalb sind Höhe und Breite immer nur Integer-Werte. So kommen bei höherer Auflösung natürlich sehr viel genauere Konturen zu Stande, weil dann eine größere Annäherung an das Design-Raster erzielt wird. Damit ändert sich die Breite eines ausgegebenen Textes nicht einfach linear mit der Auflösung oder der Schriftgröße.
    • GDI+ löst seine Ausgaben auch in Bruchteilen von Pixeln auf; die Ausgaberesultate werden damit (weitgehend) unabhängig von der Auflösung oder Schriftgröße und damit auch unabhängig von der Ausgabe-Device.

    Na schön, damit wäre erklärt, dass ich GDI-Ausgaben nicht mit GDI+-Ausgaben vergleichen kann.
    In GDI kann ich tatsächlich nur pixelgenau messen und positionieren, etwas anderes macht in diesem System auch keinen Sinn. Die gemessene Breite eines Zeichens (z.B. "M"), mal zehn genommen, entspricht auch genau der gemessenen Breite eines String mit zehn "M".
    Als nächstes werde ich mal versuchen, ähnliche Resultate in GDI+ zu erzielen, nur eben auf Bruchteile von Pixeln genau.

    Editiert: fett gedruckte Stellen



  • Klasse Zusammenfassung, das habe ich endlich mal verstanden.
    In Zukunft benutze ich nur noch GDI



  • Vorsicht -- das kann nach hinten losgehen!

    Die Beschränkung auf auflösungsabhängige Ausgaben (oder auch: Device-abhängig) bedeutet, dass man kein Wysiwyg hat. Auf dem Bildschirm mit z.B. 96 dpi bekommt man dann vollkommen andere Textumbrüche als auf dem Drucker, der mit 300, 600 oder sogar mit 1200 dpi arbeitet.

    Aus diesem Grund würde ich - wenn nur irgendwie nachvollziehbar möglich - lieber GDI+ verwenden. Dann würden meine Texte auf dem Bildschirm UND auf dem Drucker praktisch vollkommen übereinstimmen.



  • Ich bin nicht sicher, wie du das meinst, aber ich denke, das ist im Endeffekt egal. Wenn du die Daten irgendwie "speicherst", dann solltest du eigene Einheiten verwenden, die möglichst genau und geräteunabhängig auflösen, z.B. in Bruchteilen von mm.
    Aber wenns dann ums "Zeichnen" geht, versteh ich nicht, wo du hier WYSIWYG siehst oder nicht siehst. Wenn du die Ausgabe für den Drucker berechnest, hast du ein anderes Device Context, als wenn du die für den Bildschirm berechnest. Oder wolltest du die für den Bildschirm voberechnen und dann für den den Drucker einfach hochskalieren? Das wäre in der Tat nicht sinnvoll.



  • @Mechanics
    Zwischen Speichern (bzw. Laden) und Zeichnen muss man erstmal noch das Layout erstellen.

    Wenn dieser Teil über GDI implementiert ist, dann ist er deviceabhängig, und dadurch kann es passieren dass für den Schirm z.B. Zeilenumbrüche anders gesetzt werden als für den Drucker. Das ist dann doof.

    Deswegen muss die Layout-/Flow-Engine deviceunabhängig arbeiten.

    Und wenn die Layout-/Flow-Engine deviceunabhängig arbeitet, dann muss auch der Renderer/Rasterizer deviceunabhängig arbeiten. Weil er sonst u.U. den Text gar nicht in den von der Layout-/Flow-Engine berechneten Platz reinbringt. Oder viel zu wenig braucht. Was dann beides doof aussieht.

    @WishfulThinking
    Ich denke du hast dich da verschrieben:

    GDI funktioniert pixelorientiert und damit unbedingt von der Ausgabe-Device.

    Ich würde hier "abhängig" schreiben. "unbedingt" macht hier für mich keinen Sinn. Wenn dann würde ich "unbedingt" als "unabhängig" interpretieren, und das ist es ja gerade nicht.
    Und "Device" wird im Deutschen üblicherweise als Neutrum verwendet. Also "das Device", nicht "die Device" 😉



  • hustbaer schrieb:

    @Mechanics
    Zwischen Speichern (bzw. Laden) und Zeichnen muss man erstmal noch das Layout erstellen.

    Ich seh das Layouten erstmal als Teil des Zeichnens. Je nachdem, um welche Datenmengen es geht, würde ich es dann direkt beim Zeichnen machen, device abhängig. Wenn man aber tatsächlich sowas wie eine Textverarbeitung schreibt, die das Layout mitspeichert und unabhängig von der Ausgabe wissen muss, wieviele Seiten es gibt usw., dann muss man das Layout natürlich geräteunabhängig bestimmen.


Anmelden zum Antworten