web-dev-qa-db-ja.com

Android Paint:.measureText()vs .getTextBounds()

レンダリングするテキストの高さと幅の両方を取得することに興味があるため、Paint.getTextBounds()を使用してテキストを測定しています。ただし、実際に表示されるテキストは、.width()によって入力されるRect情報のgetTextBounds()よりも常に少し広いです。

驚いたことに、.measureText()をテストしたところ、異なる(より高い)値を返すことがわかりました。試してみたところ、正しいことがわかりました。

なぜ異なる幅を報告するのですか?高さと幅を正しく取得するにはどうすればよいですか?つまり、私はcan.measureText()を使用していますが、.height()によって返されるgetTextBounds()を信頼すべきかどうかはわかりません。

要求に応じて、問題を再現するための最小限のコードを次に示します。

final String someText = "Hello. I believe I'm some text!";

Paint p = new Paint();
Rect bounds = new Rect();

for (float f = 10; f < 40; f += 1f) {
    p.setTextSize(f);

    p.getTextBounds(someText, 0, someText.length(), bounds);

    Log.d("Test", String.format(
        "Size %f, measureText %f, getTextBounds %d",
        f,
        p.measureText(someText),
        bounds.width())
    );
}

出力は、差が1より大きくなるだけでなく(最後の最後の丸め誤差もありません)、サイズとともに増加するように見えることを示しています(より多くの結論を出そうとしていましたが、完全にフォントに依存する可能性があります):

D/Test    (  607): Size 10.000000, measureText 135.000000, getTextBounds 134
D/Test    (  607): Size 11.000000, measureText 149.000000, getTextBounds 148
D/Test    (  607): Size 12.000000, measureText 156.000000, getTextBounds 155
D/Test    (  607): Size 13.000000, measureText 171.000000, getTextBounds 169
D/Test    (  607): Size 14.000000, measureText 195.000000, getTextBounds 193
D/Test    (  607): Size 15.000000, measureText 201.000000, getTextBounds 199
D/Test    (  607): Size 16.000000, measureText 211.000000, getTextBounds 210
D/Test    (  607): Size 17.000000, measureText 225.000000, getTextBounds 223
D/Test    (  607): Size 18.000000, measureText 245.000000, getTextBounds 243
D/Test    (  607): Size 19.000000, measureText 251.000000, getTextBounds 249
D/Test    (  607): Size 20.000000, measureText 269.000000, getTextBounds 267
D/Test    (  607): Size 21.000000, measureText 275.000000, getTextBounds 272
D/Test    (  607): Size 22.000000, measureText 297.000000, getTextBounds 294
D/Test    (  607): Size 23.000000, measureText 305.000000, getTextBounds 302
D/Test    (  607): Size 24.000000, measureText 319.000000, getTextBounds 316
D/Test    (  607): Size 25.000000, measureText 330.000000, getTextBounds 326
D/Test    (  607): Size 26.000000, measureText 349.000000, getTextBounds 346
D/Test    (  607): Size 27.000000, measureText 357.000000, getTextBounds 354
D/Test    (  607): Size 28.000000, measureText 369.000000, getTextBounds 365
D/Test    (  607): Size 29.000000, measureText 396.000000, getTextBounds 392
D/Test    (  607): Size 30.000000, measureText 401.000000, getTextBounds 397
D/Test    (  607): Size 31.000000, measureText 418.000000, getTextBounds 414
D/Test    (  607): Size 32.000000, measureText 423.000000, getTextBounds 418
D/Test    (  607): Size 33.000000, measureText 446.000000, getTextBounds 441
D/Test    (  607): Size 34.000000, measureText 455.000000, getTextBounds 450
D/Test    (  607): Size 35.000000, measureText 468.000000, getTextBounds 463
D/Test    (  607): Size 36.000000, measureText 474.000000, getTextBounds 469
D/Test    (  607): Size 37.000000, measureText 500.000000, getTextBounds 495
D/Test    (  607): Size 38.000000, measureText 506.000000, getTextBounds 501
D/Test    (  607): Size 39.000000, measureText 521.000000, getTextBounds 515
182
slezica

そのような問題を検査するために私がしたことをすることができます:

Paint_JavaソースのAndroidソースコードを調べます。measureTextメソッドとgetTextBoundsメソッドの両方を参照してください。 measureTextはnative_measureTextを呼び出し、getTextBoundsはnativeGetStringBoundsを呼び出します。これはC++で実装されたネイティブメソッドです。

したがって、両方を実装するPaint.cppの学習を続けます。

native_measureText-> SkPaintGlue :: measureText_CII

nativeGetStringBounds-> SkPaintGlue :: getStringBounds

ここで、これらの方法がどこで異なるかを調査します。いくつかのパラメーターチェックの後、両方ともSkia Lib(Androidの一部)で関数SkPaint :: measureTextを呼び出しますが、どちらも異なるオーバーロードされたフォームを呼び出します。

Skiaをさらに掘り下げてみると、両方の呼び出しが同じ関数で同じ計算になり、結果が異なるだけであることがわかります。

質問に答えるには:どちらの呼び出しも同じ計算を行います。結果の可能な違いは、getTextBoundsが整数として境界を返すのに対し、measureTextBoundsfloat値を返します。

したがって、floatからintへの変換中に丸めエラーが発生します。これは、SkPaintGlue :: doTextBoundsのPaint.cppで関数SkRect :: roundOutの呼び出しで発生します。

これら2つの呼び出しの計算された幅の差は、最大1になる場合があります。

2011年10月4日編集

視覚化よりも優れている可能性があります。私は自分自身を探求し、賞金に値する努力をしました:)

enter image description here

これはフォントサイズ60、赤はbounds長方形、紫はmeasureTextの結果です。

左の境界部分は左から数ピクセルから始まり、measureTextの値はこの値だけ左右に増加していることがわかります。これは、GlyphのAdvanceX値と呼ばれるものです。 (SkPaint.cppのSkiaソースでこれを発見しました)

そのため、テストの結果はmeasureTextが事前に値を両側のテキストに追加する一方で、getTextBoundsは与えられたテキストが収まる最小境界を計算するということです。

この結果があなたの役に立つことを願っています。

テストコード:

  protected void onDraw(Canvas canvas){
     final String s = "Hello. I'm some text!";

     Paint p = new Paint();
     Rect bounds = new Rect();
     p.setTextSize(60);

     p.getTextBounds(s, 0, s.length(), bounds);
     float mt = p.measureText(s);
     int bw = bounds.width();

     Log.i("LCG", String.format(
          "measureText %f, getTextBounds %d (%s)",
          mt,
          bw, bounds.toShortString())
      );
     bounds.offset(0, -bounds.top);
     p.setStyle(Style.STROKE);
     canvas.drawColor(0xff000080);
     p.setColor(0xffff0000);
     canvas.drawRect(bounds, p);
     p.setColor(0xff00ff00);
     canvas.drawText(s, 0, bounds.bottom, p);
  }
356
Pointer Null

これに関する私の経験では、getTextBoundsは、必ずしもレンダリング時に使用される測定された幅ではなく、テキストをカプセル化する絶対最小境界四角形を返します。また、measureTextは1行であると想定しています。

正確な測定結果を得るには、StaticLayoutを使用してテキストをレンダリングし、測定値を引き出す必要があります。

例えば:

String text = "text";
TextPaint textPaint = textView.getPaint();
int boundedWidth = 1000;

StaticLayout layout = new StaticLayout(text, textPaint, boundedWidth , Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false);
int height = layout.getHeight();
20
Chase

マウスの答えは素晴らしいです...そして、実際の問題の説明は次のとおりです。

短い簡単な答えは、Paint.getTextBounds(String text, int start, int end, Rect bounds)(0,0)で始まらないRectを返すということです。つまり、samePaintオブジェクトを使用してCanvas.drawText(String text, float x, float y, Paint paint)を呼び出して設定されるテキストの実際の幅を取得するには、getTextBounds()の左位置を追加する必要がありますレクそんな感じ:

public int getTextWidth(String text, Paint paint) {
    Rect bounds = new Rect();
    Paint.getTextBounds(text, 0, end, bounds);
    int width = bounds.left + bounds.width();
    return width;
}

このbounds.left-これが問題の鍵であることに注意してください。

このようにして、Canvas.drawText()を使用して受け取るのと同じテキスト幅を受け取ります。

そして、同じ関数がテキストのheightを取得する必要があります。

public int getTextHeight(String text, Paint paint) {
    Rect bounds = new Rect();
    Paint.getTextBounds(text, 0, end, bounds);
    int height = bounds.bottom + bounds.height();
    return height;
}

追伸:この正確なコードはテストしませんでしたが、概念はテストしました。


より詳細な説明は this answerにあります。

16
Prizoff

その質問に再び答えて申し訳ありません...私は画像を埋め込む必要がありました。

@miceが見つけた結果は誤解を招くと思います。観察結果は、フォントサイズが60の場合は正しいかもしれませんが、テキストが小さい場合はさらに大きく変わります。例えば。 10px。その場合、テキストは実際には境界を超えて描画されます。

enter image description here

スクリーンショットのソースコード:

  @Override
  protected void onDraw( Canvas canvas ) {
    for( int i = 0; i < 20; i++ ) {
      int startSize = 10;
      int curSize = i + startSize;
      Paint.setTextSize( curSize );
      String text = i + startSize + " - " + TEXT_SNIPPET;
      Rect bounds = new Rect();
      Paint.getTextBounds( text, 0, text.length(), bounds );
      float top = STEP_DISTANCE * i + curSize;
      bounds.top += top;
      bounds.bottom += top;
      canvas.drawRect( bounds, bgPaint );
      canvas.drawText( text, 0, STEP_DISTANCE * i + curSize, Paint );
    }
  }
12
Moritz

免責事項:このソリューションは、最小幅の決定に関して100%正確ではありません。

また、キャンバス上のテキストを測定する方法を考えていました。マウスから素晴らしい投稿を読んだ後、複数行のテキストを測定する方法についていくつかの問題がありました。これらの貢献から明らかな方法はありませんが、いくつかの調査の後、StaticLayoutクラスを横切って見ます。これにより、複数行のテキスト(「\ n」の付いたテキスト)を測定し、関連付けられたPaintを介してテキストのより多くのプロパティを構成できます。

以下は、複数行のテキストを測定する方法を示すスニペットです。

private StaticLayout measure( TextPaint textPaint, String text, Integer wrapWidth ) {
    int boundedWidth = Integer.MAX_VALUE;
    if (wrapWidth != null && wrapWidth > 0 ) {
       boundedWidth = wrapWidth;
    }
    StaticLayout layout = new StaticLayout( text, textPaint, boundedWidth, Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false );
    return layout;
}

Wrapwitdhは、マルチラインテキストを特定の幅に制限するかどうかを決定できます。

StaticLayout.getWidth()はこのboundedWidthのみを返すため、複数行テキストに必要な最大幅を取得するには、別の手順を実行する必要があります。各線幅を決定することができ、最大幅はもちろん最高の線幅です。

private float getMaxLineWidth( StaticLayout layout ) {
    float maxLine = 0.0f;
    int lineCount = layout.getLineCount();
    for( int i = 0; i < lineCount; i++ ) {
        if( layout.getLineWidth( i ) > maxLine ) {
            maxLine = layout.getLineWidth( i );
        }
    }
    return maxLine;
}
10
Moritz

テキストの境界を正確に測定する別の方法があります。まず、現在のペイントとテキストのパスを取得する必要があります。あなたの場合、次のようになります:

p.getTextPath(someText, 0, someText.length(), 0.0f, 0.0f, mPath);

その後、電話することができます:

mPath.computeBounds(mBoundsPath, true);

私のコードでは、常に正しい期待値を返します。しかし、あなたのアプローチよりも速く動作するかどうかはわかりません。

2
Roman

getTextBoundsmeasureTextの違いを以下の画像で説明します。

要するに、

  1. getTextBoundsは、正確なテキストのRECTを取得します。 measureTextは、左右の余分なギャップを含むテキストの長さです。

  2. テキスト間にスペースがある場合、measureTextで測定されますが、TextBoundsの長さには含まれませんが、座標はシフトします。

  3. テキストを左に傾けることができます(スキュー)。この場合、バウンディングボックスの左側がmeasureTextの測定範囲外になり、テキストバインドの全長がmeasureTextより大きくなります。

  4. テキストを右に傾けることができます(スキュー)。この場合、バウンディングボックスの右側はmeasureTextの測定範囲を超え、テキストバインドの全長はmeasureTextより大きくなります。

enter image description here

0
Elye

これは、最初の文字の実際の寸法を計算する方法です(ニーズに合わせてメソッドヘッダーを変更できます。つまり、char[]の代わりにStringを使用できます)。

private void calculateTextSize(char[] text, PointF outSize) {
    // use measureText to calculate width
    float width = mPaint.measureText(text, 0, 1);

    // use height from getTextBounds()
    Rect textBounds = new Rect();
    mPaint.getTextBounds(text, 0, 1, textBounds);
    float height = textBounds.height();
    outSize.x = width;
    outSize.y = height;
}

元のPaintクラスの代わりにTextPaintを使用していることに注意してください。

0
milosmns