web-dev-qa-db-ja.com

透明なピクセルの上にDrawStringを使用した不適切なテキストレンダリング

テキストをビットマップにレンダリングするとき、不透明でないアルファのある領域の上にレンダリングすると、テキストの見栄えが非常に悪くなることがわかりました。下にあるピクセルがより透明になるにつれて、問題は次第に悪化します。下にあるピクセルが透明である場合、テキストレンダラーはアンチエイリアス処理された「灰色」のピクセルを黒一色で描画すると推測する必要があります。

ここにいくつかのスクリーンショットがあります:

透明なピクセルの上に描画されたテキスト:

alt text

半透明のピクセルの上に描画されたテキスト:

alt text

不透明なピクセルに描画されたテキスト:

alt text

テキストのレンダリングに使用されるコードは次のとおりです。

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

この問題を回避するために使用したオプションは次のとおりです。

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

TextRenderingHintには他にも便利なオプションがいくつかあります

それが役に立てば幸い

28
Guilherme

これには非常に簡単な答えがあります...

g.TextRenderingHint = Drawing.Text.TextRenderingHint.AntiAliasGridFit

テキストをレンダリングする前にこれを設定すると、明確になります。さらに、このメソッドはより多くのフォントサイズをサポートします(デフォルトではサイズ56までしかありません)。

この投稿を読んでいただきありがとうございます。

16
user3470185

最初の出力は、黒い背景に黒いテキストを描画したときに得られるものです。おそらくColor.Transparentです。 2番目はほぼ黒の背景に描かれました。 3番目はそれが表示されているのと同じ背景に描かれました。

透明な背景では、アンチエイリアシングは機能しません。アンチエイリアシングピクセルに使用される色は、テキストが別の背景で表示されている場合、文字の形を背景にブレンドしません。これらのピクセルは非常に目立つようになり、テキストの見栄えが非常に悪くなります。

SmoothingModeはテキスト出力に影響を与えないことに注意してください。低品質のTextRenderingHintと、アルファがゼロの灰色がかった背景色を使用すると、見た目が少し悪くなります。 TextRenderingHint.SingleBitPerPixelGridFitのみが、すべてのアンチエイリアシングの問題を回避します。

これを完全に修正することは非常に困難です。ウィンドウのタイトルバーに対するVistaのガラス効果は、非常に微妙な陰影を使用して、テキストに明確な背景色を与えます。実際に表示するには、SysInternalsのZoomItツールが必要です。 iGlowSizeがゼロ以外のDrawThemeTextEx()関数。

12
Hans Passant

デフォルトでGDI +よりも少し優れたアンチエイリアスを維持するものを探している場合は、クロマキーを使用してGraphics.Clearを呼び出し、結果として生じるクロマアーティファクトを手動で削除できます。 (DrawStringがこんなにくだらないように見えるのはなぜですか?および醜い見た目テキストの問題

これが私が最終的に同様の問題を解決することになった方法です:

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

GDI/GDI +がこれをまだ行っていないのは残念ですが、それは賢明なことですよね。 :)

unsafeコンテキストを使用できない場合は、Bitmap.GetPixelBitmap.SetPixelで同じロジックを簡単に使用できますが、かなり遅くなります。

2
hydroiodic