2010-06-07 21 views
30

Lorsque je restitue un texte dans une image bitmap, je trouve que le texte semble très mauvais lorsqu'il est rendu au-dessus d'une zone avec alpha non opaque. Le problème est progressivement pire que les pixels sous-jacents deviennent plus transparents. Si je devais deviner, je dirais que lorsque les pixels sous-jacents sont transparents, le moteur de rendu de texte dessine les pixels «gris» anti-aliasés en noir uni.System.Drawing - Rendu de texte incorrect à l'aide de DrawString au-dessus des pixels transparents

Voici quelques captures d'écran:

Texte dessiné au-dessus de pixels transparents:

alt text

Texte tiré sur le dessus de pixels semi-transparents:

alt text

Texte tiré sur les pixels opaques:

alt text

Voici le code utilisé pour rendre le texte:

g.SmoothingMode = SmoothingMode.HighQuality; 
    g.DrawString("Press the spacebar", Font, Brushes.Black, textLeft, textTop); 
+2

Je crois que le résultat dépendra également si ClearType est activé ou non. – AMissico

+0

Il semble que vous n'effaciez pas (ou plutôt que vous n'invalidiez pas) l'arrière-plan transparent. – leppie

+0

une solution finale avec le code source complet? – Kiquenet

Répondre

23

L'option que je l'habitude de contourner ce problème était:

Graphics graphics = new Graphics(); 
graphics.TextRenderingHint = System.Drawing.Text.TextRenderingHint.SingleBitPerPixelGridFit; 

Il y a d'autres options utiles dans TextRenderingHint

Hope it helps

+0

Échange de réponse pour celui-ci, bien que ce soit une très vieille question et je n'ai pas testé cette réponse. – mackenir

+1

La réponse de l'utilisateur3470185 (g.TextRenderingHint = Drawing.Text.TextRenderingHint.AntiAliasGridFit) produit un meilleur résultat. – yclkvnc

11

La première sortie est ce que vous obtenez lorsque vous dessinez un texte noir sur noir fond, probablement Color.Transparent. Le 2ème a été dessiné sur un fond presque noir. Le 3ème a été dessiné sur le même fond avec lequel il est affiché.

L'anticrénelage ne peut pas fonctionner sur un arrière-plan transparent. Les couleurs utilisées pour les pixels anti-aliasing ne fondront pas la forme de la lettre en arrière-plan lorsque le texte est affiché avec un arrière-plan différent. Ces pixels deviendront très et rendront le texte très mauvais.

Notez que SmoothingMode n'affecte pas la sortie de texte. Il semblera légèrement moins mauvais si vous utilisez une TextRenderingHint de qualité inférieure et une couleur d'arrière-plan qui est grisâtre avec un alpha de zéro. Seul TextRenderingHint.SingleBitPerPixelGridFit évite tous les problèmes d'anti-aliasing.

Obtenir une solution parfaite pour cela est très difficile. L'effet de verre de Vista sur la barre de titre de la fenêtre utilise un ombrage très subtil pour donner au texte une couleur d'arrière-plan bien définie. Vous auriez besoin de l'outil ZoomIt de SysInternals pour le voir. Fonction DrawThemeTextEx() avec un iGlowSize non nul.

+0

Pouce. Je suppose que je pourrais dessiner du texte noir sur du blanc sur un bitmap temporaire, convertir du blanc en transparent, et du gris au noir semi-transparent, puis dessiner cela sur le bitmap final. Je vais essayer différents TextRenderingHints. Sinon, je pense que l'effet avec la transparence d'arrière-plan partielle n'est pas trop mauvais. Une chose cependant - dans chaque cas, l'arrière-plan est la même couleur (blanc cassé) avec une transparence différente, donc je ne voudrais pas que le texte dessine comme ça avec une couleur d'arrière-plan «efficace» de noir. – mackenir

+0

Vous ne savez pas ce que vous voulez dire. Le problème principal est que le GDI ne peut pas voir la transparence, il voit la valeur RVB de la couleur transparente. Color.Transparent est un très mauvais choix, sa valeur RGB est nulle. Noir. –

+0

Ce que je veux dire, c'est que l'arrière-plan est peint en blanc cassé, avec une composante alpha. Vous ne savez pas où vous avez obtenu Color.Transparent. – mackenir

2

Si vous cherchez quelque chose qui préserve anticrénelage un peu mieux que GDI + par défaut, vous pouvez appeler Graphics.Clear avec une clé de chrominance, puis supprimer manuellement les artefacts de chrominance qui en résultent. (Voir Why does DrawString look so crappy? et Ugly looking text problem.)

Voilà comment j'ai finalement fini par résoudre un problème similaire:

static Bitmap TextToBitmap(string text, Font font, Color foregroundColor) 
{ 
    SizeF textSize; 

    using (var g = Graphics.FromHwndInternal(IntPtr.Zero)) 
    textSize = g.MeasureString(text, font); 

    var image = new Bitmap((int)Math.Ceiling(textSize.Width), (int)Math.Ceiling(textSize.Height)); 
    var brush = new SolidBrush(foregroundColor); 

    using (var g = Graphics.FromImage(image)) 
    { 
    g.Clear(Color.Magenta); 
    g.SmoothingMode = SmoothingMode.AntiAlias; 
    g.InterpolationMode = InterpolationMode.HighQualityBicubic; 
    g.PixelOffsetMode = PixelOffsetMode.HighQuality; 
    g.DrawString(text, font, brush, 0, 0); 
    g.Flush(); 
    } 

    image.MakeTransparent(Color.Magenta); 

    // The image now has a transparent background, but around each letter are antialiasing artifacts still keyed to magenta. We need to remove those. 
    RemoveChroma(image, foregroundColor, Color.Magenta); 
    return image; 
} 

static unsafe void RemoveChroma(Bitmap image, Color foregroundColor, Color chroma) 
{ 
    if (image == null) throw new ArgumentNullException("image"); 
    BitmapData data = null; 

    try 
    { 
    data = image.LockBits(new Rectangle(Point.Empty, image.Size), ImageLockMode.ReadWrite, PixelFormat.Format32bppArgb); 

    for (int y = data.Height - 1; y >= 0; --y) 
    { 
     int* row = (int*)(data.Scan0 + (y * data.Stride)); 
     for (int x = data.Width - 1; x >= 0; --x) 
     { 
     if (row[x] == 0) continue; 
     Color pixel = Color.FromArgb(row[x]); 

     if ((pixel != foregroundColor) && 
      ((pixel.B >= foregroundColor.B) && (pixel.B <= chroma.B)) && 
      ((pixel.G >= foregroundColor.G) && (pixel.G <= chroma.G)) && 
      ((pixel.R >= foregroundColor.R) && (pixel.R <= chroma.R))) 
     { 
      row[x] = Color.FromArgb(
      255 - ((int) 
       ((Math.Abs(pixel.B - foregroundColor.B) + 
       Math.Abs(pixel.G - foregroundColor.G) + 
       Math.Abs(pixel.R - foregroundColor.R))/3)), 
      foregroundColor).ToArgb(); 
     } 
     } 
    } 
    } 
    finally 
    { 
    if (data != null) image.UnlockBits(data); 
    } 
} 

Il est dommage GDI/GDI + ne fait pas déjà, mais ce serait raisonnable, ne serait-il? :)

Si vous n'êtes pas en mesure d'utiliser un contexte unsafe, vous pouvez facilement utiliser la même logique avec Bitmap.GetPixel et Bitmap.SetPixel, bien que ce soit beaucoup plus lent.

+0

Très bonne explication. – tmighty

10

Il y a une réponse très simple à cette ...

g.TextRenderingHint = Drawing.Text.TextRenderingHint.AntiAliasGridFit 

Si vous définissez cette avant de vous rendre votre texte, il sortira clair. En outre, cette méthode prend en charge plus de tailles de police (la taille par défaut ne dépasse pas 56).

Merci d'avoir lu ce post.