Programing

Android 페인트 : .measureText () 및 .getTextBounds ()

lottogame 2020. 5. 15. 07:58
반응형

Android 페인트 : .measureText () 및 .getTextBounds ()


Paint.getTextBounds()렌더링 할 텍스트의 높이와 너비를 모두 얻는 데 관심이 있기 때문에을 사용하여 텍스트를 측정 하고 있습니다. 그러나, 실제 텍스트 렌더링 항상보다 약간 넓은 .width()Rect의해 채워진 정보 getTextBounds().

놀랍게도, 나는 테스트 .measureText()했으며 다른 (높은) 값을 반환한다는 것을 알았습니다. 나는 그것을 시도하고 그것이 맞는 것을 발견했다.

너비가 다른 이유는 무엇입니까? 높이와 너비를 올바르게 얻으려면 어떻게해야합니까? 나는을 사용할 .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

그런 문제를 검사하기 위해 내가 한 일을 할 수 있습니다.

Android 소스 코드 인 Paint.java 소스를 연구하고 measureText 및 getTextBounds 메소드를 참조하십시오. measureText는 native_measureText를 호출하고 getTextBounds는 C ++로 구현 된 기본 메소드 인 nativeGetStringBounds를 호출한다는 것을 배웠습니다.

따라서 두 가지를 모두 구현하는 Paint.cpp를 계속 연구합니다.

native_measureText-> SkPaintGlue :: measureText_CII

nativeGetStringBounds-> SkPaintGlue :: getStringBounds

이제 연구는 이러한 방법이 다른 곳을 확인합니다. 일부 매개 변수 검사 후 Skia Lib (Android의 일부)에서 SkPaint :: measureText 함수를 호출하지만 둘 다 다른 오버로드 된 형식을 호출합니다.

Skia를 더 자세히 살펴보면 두 호출 모두 동일한 함수에서 동일한 계산으로 결과가 다르게 반환됩니다.

질문에 대답하려면 두 통화 모두 동일한 계산을 수행합니다. 결과의 가능한 차이는 getTextBounds 가 범위를 정수로 리턴 하는 반면 measureText 는 float 값 을 리턴 한다는 사실입니다 .

따라서 float을 int로 변환하는 동안 반올림 오류가 발생하며 SkRect :: roundOut 함수를 호출하는 SkPaintGlue :: doTextBounds의 Paint.cpp에서 발생합니다.

이 두 호출의 계산 너비 차이는 최대 1 일 수 있습니다.

2011 년 10 월 4 일 수정

시각화보다 낫다. 나는 자신의 탐험과 현상금을 얻기 위해 노력했습니다. :)

여기에 이미지 설명을 입력하십시오

이 글꼴 크기는 60이고 빨간색은 경계 사각형이며 자주색은 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);
  }

이것에 대한 나의 경험은 getTextBounds렌더링 할 때 사용되는 측정 된 너비가 아니라 텍스트를 캡슐화하는 절대 최소 경계 직사각형을 반환 한다는 것입니다. 또한 measureText한 줄로 가정 하고 싶습니다 .

정확한 측정 결과를 얻으려면을 사용 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();

마우스 대답은 훌륭합니다 ... 그리고 실제 문제에 대한 설명은 다음과 같습니다.

간단한 대답은 에서 시작하지 않는 Paint.getTextBounds(String text, int start, int end, Rect bounds)리턴 Rect입니다 (0,0). 즉, getTextBounds ()에서 동일한 Paint 객체를 호출 Canvas.drawText(String text, float x, float y, Paint paint)하여 설정할 텍스트의 실제 너비를 얻으려면 Rect의 왼쪽 위치를 추가해야합니다. 그런 것 :

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

추신 : 나는이 정확한 코드를 테스트하지는 않았지만 개념을 테스트했습니다.


답변 에는 훨씬 더 자세한 설명이 나와 있습니다.


그 질문에 다시 대답해서 죄송합니다 ... 이미지를 포함해야했습니다.

@mice에서 찾은 결과가 잘못되었다고 생각합니다. 글꼴 크기가 60 인 경우 관측 값이 정확할 수 있지만 텍스트가 더 작 으면 훨씬 다르게 나타납니다. 예 : 10px. 이 경우 텍스트는 실제로 경계를 넘어서 그려집니다.

여기에 이미지 설명을 입력하십시오

스크린 샷의 소스 코드 :

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

면책 조항 : 이 솔루션은 최소 너비를 결정하는 관점에서 100 % 정확하지 않습니다.

I was also figuring out how to measure text on a canvas. After reading the great post from mice i had some problems on how to measure multiline text. There is no obvious way from these contributions but after some research i cam across the StaticLayout class. It allows you to measure multiline text (text with "\n") and configure much more properties of your text via the associated Paint.

Here is a snippet showing how to measure multiline text:

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

The wrapwitdh is able to determin if you want to limit your multiline text to a certain width.

Since the StaticLayout.getWidth() only returns this boundedWidth you have to take another step to get the maximum width required by your multiline text. You are able to determine each lines width and the max width is the highest line width of course:

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

There is another way to measure the text bounds precisely, first you should get the path for the current Paint and text. In your case it should be like this:

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

After that you can call:

mPath.computeBounds(mBoundsPath, true);

In my code it always returns correct and expected values. But, not sure if it works faster than your approach.


This is how I calculated the real dimensions for the first letter (you can change the method header to suit your needs, i.e. instead of char[] use 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;
}

Note that I'm using TextPaint instead of the original Paint class.


The different between getTextBounds and measureText is described with the image below.

In short,

  1. getTextBounds is to get the RECT of the exact text. The measureText is the length of the text, including the extra gap on the left and right.

  2. 텍스트 사이에 공백이 있으면 measureText좌표가 이동하더라도 TextBounds의 길이는 포함되지 않고 측정됩니다 .

  3. 텍스트가 기울어 질 수 있습니다 (Skew). 이 경우 왼쪽 경계 상자가 measureText의 측정 범위를 초과하고 텍스트 경계의 전체 길이가measureText

  4. 텍스트가 오른쪽으로 기울어 질 수 있습니다 (Skew). 이 경우 경계 상자 오른쪽이 measureText의 측정 범위를 초과하고 텍스트 경계의 전체 길이가measureText

여기에 이미지 설명을 입력하십시오

참고 URL : https://stackoverflow.com/questions/7549182/android-paint-measuretext-vs-gettextbounds

반응형