FoxiGram/TMessagesProj/src/main/java/tw/nekomimi/nekogram/helpers/BlurredTextSpan.java
instant992 87b8ea4949 Use a real gaussian blur for the masked phone number
- Add BlurredTextSpan: draws the real glyphs through a BlurMaskFilter
  into a cached offscreen bitmap, so the blur works on both software and
  hardware-accelerated views
- formatOwnPhone() now returns this span in Blur mode instead of
  replacing digits with bullets
- SettingsActivity header builds the subtitle with a SpannableStringBuilder
  so the span is preserved
2026-06-10 03:03:03 +04:00

109 lines
3.6 KiB
Java

package tw.nekomimi.nekogram.helpers;
import android.graphics.Bitmap;
import android.graphics.BlurMaskFilter;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.text.style.ReplacementSpan;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import org.telegram.messenger.AndroidUtilities;
/**
* Renders a piece of text genuinely blurred (Gaussian-style), instead of just
* masking the characters. The real glyphs are drawn into an offscreen software
* bitmap with a {@link BlurMaskFilter} applied and then blitted onto the target
* canvas, so it works the same on both software and hardware-accelerated views.
*/
public class BlurredTextSpan extends ReplacementSpan {
private final String text;
private final float blurRadius;
private Bitmap cache;
private int cacheWidth;
private int cacheHeight;
private float cacheTextSize;
private int cacheColor;
private float cacheBaseline;
private int cachePad;
public BlurredTextSpan(String text) {
this(text, AndroidUtilities.dp(3.5f));
}
public BlurredTextSpan(String text, float blurRadius) {
this.text = text == null ? "" : text;
this.blurRadius = Math.max(0.5f, blurRadius);
}
@Override
public int getSize(@NonNull Paint paint, CharSequence charSequence, int start, int end, @Nullable Paint.FontMetricsInt fm) {
if (fm != null) {
Paint.FontMetricsInt pfm = paint.getFontMetricsInt();
fm.ascent = pfm.ascent;
fm.descent = pfm.descent;
fm.top = pfm.top;
fm.bottom = pfm.bottom;
fm.leading = pfm.leading;
}
return (int) Math.ceil(paint.measureText(text));
}
@Override
public void draw(@NonNull Canvas canvas, CharSequence charSequence, int start, int end, float x, int top, int y, int bottom, @NonNull Paint paint) {
ensureCache(paint);
if (cache == null) {
return;
}
canvas.drawBitmap(cache, x - cachePad, y - cacheBaseline, null);
}
private void ensureCache(Paint paint) {
final float textSize = paint.getTextSize();
final int color = paint.getColor();
final int width = (int) Math.ceil(paint.measureText(text));
if (width <= 0) {
cache = null;
return;
}
// padding so the blur isn't clipped at the edges
final int pad = (int) Math.ceil(blurRadius * 2.5f);
Paint.FontMetricsInt fm = paint.getFontMetricsInt();
final int textHeight = fm.descent - fm.ascent;
final int w = width + pad * 2;
final int h = textHeight + pad * 2;
if (cache != null && cacheWidth == w && cacheHeight == h
&& cacheTextSize == textSize && cacheColor == color) {
return;
}
if (cache != null) {
cache.recycle();
}
try {
cache = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
} catch (Throwable e) {
cache = null;
return;
}
cacheWidth = w;
cacheHeight = h;
cacheTextSize = textSize;
cacheColor = color;
cachePad = pad;
// baseline offset inside the cache bitmap, relative to the text top
cacheBaseline = pad - fm.ascent;
Canvas c = new Canvas(cache);
Paint p = new Paint(Paint.ANTI_ALIAS_FLAG);
p.setTextSize(textSize);
p.setColor(color);
p.setTypeface(paint.getTypeface());
p.setMaskFilter(new BlurMaskFilter(blurRadius, BlurMaskFilter.Blur.NORMAL));
c.drawText(text, pad, cacheBaseline, p);
}
}