Android 实现LED 展示效果

一、前言

LED以其卓越的亮度和醒目的文字和图案,已成为车水马龙的城市中充满烟火气息的象征,深层次的是您红灯的闪烁唤醒着人们的娱乐、怀旧、童年的记忆。当然对新时代来说这显然格格不入的,因此这种霓虹灯能存在多久显然还是个问题。

效果预览

二、实现原理

最初的设想是利用BitmapShader + Shader 实现网格图片,但是最终是失败的,因此绘制出的网格不是纯色。

为什么是需要网格纯色呢 ,主要原因是LED等作为单独的实体,单个LED智能发出一种光,电视也是一样的道理,微小的发光单元不可能同时发出多种光源,这也是LED显示屏的制作原理。至于我们的自定义View,本身是细腻的屏幕上发出的,如果一个LED发出多种光,就会显得很假。但事实上,在绘制View时一个区域可能会出现多种颜色,如何平衡这种颜色也是个问题,优化方式当然是增加采样点;但是采样点多了也会带来新的副作用,一是性能问题,而是过多的全透明和alpha为0的情况,因为这种情况会过度稀释真是的颜色,造成模糊不清的问题,其次和View本身的背景穿透,形成较大范围的噪点,所以绘制过程中一定要控制采样点的数量,其次对alpha为0或者过小的的情况剔除,当然不用担心失真,因为过度的透明人眼会认为是全透明,没有太多意义,我们来做个总结:

  • LED 单元智能发出一种光,因此不适合BitampShader做风格渲染

  • 颜色逼真程度和采样点有关,采样点越多越逼近真色

  • 清晰程度和LED单元大小相关,LED单元越小越清晰

  • 剔除alpha通道过小和颜色值为0的采样点颜色

三、核心逻辑

生成刷子纹理

ini 复制代码
     if (brushBitmap == null) {
            brushBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
            brushCanvas = new Canvas(brushBitmap);
        }

      for (int i = 0; i < drawers.size(); i++) {
            int saveCount = brushCanvas.save();
            drawers.get(i).draw(brushCanvas, width, height, mCommonPaint);
            brushCanvas.restoreToCount(saveCount);
        }

生成网格数据

ini 复制代码
        float blockWidth = (squareWidth + padding);
        int w = width;
        int h = height;
        int columNum = (int) Math.ceil(w / blockWidth);
        int rowNum = (int) Math.ceil(h / blockWidth);

        if (gridRects.isEmpty() && squareWidth > 1f) {
            //通过rowNum * columNum方式降低时间复杂度
            for (int i = 0; i < rowNum * columNum; i++) {

                int col = i % columNum;
                int row = (i / columNum);

                Rect rect = new Rect();
                rect.left = (int) (col * blockWidth);
                rect.top = (int) (row * blockWidth);
                rect.right = (int) (col * blockWidth + squareWidth);
                rect.bottom = (int) (row * blockWidth + squareWidth);
                //记录网格点
                gridRects.add(rect);
            }

        }

采样绘制

ini 复制代码
    //这里是重点 ,LED等可以看作一只灯泡,灯泡区域要么全亮,要么全不亮
        for (int i = 0; i < gridRects.size(); i++) {
            Rect rect = gridRects.get(i);

            if (brushBitmap.getWidth() <= rect.right) {
                continue;
            }
            if (brushBitmap.getHeight() <= rect.bottom) {
                continue;
            }

            if (sampleColors == null) {
                sampleColors = new int[9];
            }

            //取7个点采样,纯粹是为了性能考虑,如果想要更准确的颜色,可以多采样几个点

            sampleColors[0] = brushBitmap.getPixel(rect.left, rect.top);  // left-top
            sampleColors[1] = brushBitmap.getPixel(rect.right, rect.top); // right-top
            sampleColors[2] = brushBitmap.getPixel(rect.right, rect.bottom); // right-bottom
            sampleColors[3] = brushBitmap.getPixel(rect.left, rect.bottom); // left-bottom
            sampleColors[4] = brushBitmap.getPixel(rect.left + rect.width() / 2, rect.top + rect.height() / 2); //center

            sampleColors[5] = brushBitmap.getPixel(rect.left + rect.width() / 2, rect.top + rect.height() / 4);  //top line
            sampleColors[6] = brushBitmap.getPixel(rect.left + rect.width() * 3 / 4, rect.top + rect.height() / 2); //right line
            sampleColors[7] = brushBitmap.getPixel(rect.left + rect.width() / 4, rect.top + rect.height() / 2); // left line
            sampleColors[8] = brushBitmap.getPixel(rect.left + rect.width() / 2, rect.top + rect.height() * 3 / 4);  // bottom line

            int alpha = 0;
            int red = 0;
            int green = 0;
            int blue = 0;
            int num = 0;

            for (int c : sampleColors) {
                if (c == Color.TRANSPARENT) {
                    //剔除全透明的颜色,必须剔除
                    continue;
                }
                int alphaC = Color.alpha(c);
                if (alphaC <= 0) {
                    //剔除alpha为0的颜色,当然可以改大一点,防止降低清晰度
                    continue;                }
                alpha += alphaC;
                red += Color.red(c);
                green += Color.green(c);
                blue += Color.blue(c);
                num++;
            }

            if (num < 1) {
                continue;
            }

            //求出平均值
            int rectColor = Color.argb(alpha / num, red / num, green / num, blue / num);
            if (rectColor != Color.TRANSPARENT) {
                mGridPaint.setColor(rectColor);
                //   canvas.drawRect(rect, mGridPaint);  //绘制矩形
                canvas.drawCircle(rect.centerX(), rect.centerY(), squareWidth / 2, mGridPaint);  //绘制圆
            }
        }

如果不剔除颜色,那么就会有噪点和清晰度问题

全部代码

ini 复制代码
public class LedDisplayView extends View {
    private final DisplayMetrics mDM;
    private TextPaint mGridPaint;
    private TextPaint mCommonPaint;
    private List<IDrawer> drawers = new ArrayList<>();
    private Bitmap brushBitmap = null;
    private float padding = 2; //分界线大小
    private float squareWidth = 5;  //网格大小
    private List<Rect> gridRects = new ArrayList<>();
    int[] sampleColors = null;
    private Canvas brushCanvas = null;

    public LedDisplayView(Context context) {
        this(context, null);
    }

    public LedDisplayView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public LedDisplayView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        mDM = getResources().getDisplayMetrics();
        initPaint();

    }


    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);

        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int widthSize = MeasureSpec.getSize(widthMeasureSpec);

        if (widthMode != MeasureSpec.EXACTLY) {
            widthSize = mDM.widthPixels / 2;
        }

        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        int heightSize = MeasureSpec.getSize(heightMeasureSpec);

        if (heightMode != MeasureSpec.EXACTLY) {
            heightSize = widthSize / 2;
        }
        setMeasuredDimension(widthSize, heightSize);
    }


    public float dp2px(float dp) {
        return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, mDM);
    }

    public float sp2px(float dp) {
        return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, dp, mDM);
    }


    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        if (brushBitmap != null && !brushBitmap.isRecycled()) {
            brushBitmap.recycle();
        }
        brushBitmap = null;
    }


    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        int width = getWidth();
        int height = getHeight();
        if (width <= padding || height <= padding) {
            return;
        }

        if (brushBitmap == null) {
            brushBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
            brushCanvas = new Canvas(brushBitmap);
        }

        for (int i = 0; i < drawers.size(); i++) {
            int saveCount = brushCanvas.save();
            drawers.get(i).draw(brushCanvas, width, height, mCommonPaint);
            brushCanvas.restoreToCount(saveCount);
        }


        float blockWidth = (squareWidth + padding);
        int w = width;
        int h = height;
        int columNum = (int) Math.ceil(w / blockWidth);
        int rowNum = (int) Math.ceil(h / blockWidth);

        if (gridRects.isEmpty() && squareWidth > 1f) {
            //通过rowNum * columNum方式降低时间复杂度
            for (int i = 0; i < rowNum * columNum; i++) {

                int col = i % columNum;
                int row = (i / columNum);

                Rect rect = new Rect();
                rect.left = (int) (col * blockWidth);
                rect.top = (int) (row * blockWidth);
                rect.right = (int) (col * blockWidth + squareWidth);
                rect.bottom = (int) (row * blockWidth + squareWidth);
                //记录网格点
                gridRects.add(rect);
            }

        }
        int color = mGridPaint.getColor();

        //这里是重点 ,LED等可以看作一只灯泡,灯泡区域要么全亮,要们全不亮
        for (int i = 0; i < gridRects.size(); i++) {
            Rect rect = gridRects.get(i);

            if (brushBitmap.getWidth() <= rect.right) {
                continue;
            }
            if (brushBitmap.getHeight() <= rect.bottom) {
                continue;
            }

            if (sampleColors == null) {
                sampleColors = new int[9];
            }

            //取7个点采样,纯粹是为了性能考虑,如果想要更准确的颜色,可以多采样几个点

            sampleColors[0] = brushBitmap.getPixel(rect.left, rect.top);  // left-top
            sampleColors[1] = brushBitmap.getPixel(rect.right, rect.top); // right-top
            sampleColors[2] = brushBitmap.getPixel(rect.right, rect.bottom); // right-bottom
            sampleColors[3] = brushBitmap.getPixel(rect.left, rect.bottom); // left-bottom
            sampleColors[4] = brushBitmap.getPixel(rect.left + rect.width() / 2, rect.top + rect.height() / 2); //center

            sampleColors[5] = brushBitmap.getPixel(rect.left + rect.width() / 2, rect.top + rect.height() / 4);  //top line
            sampleColors[6] = brushBitmap.getPixel(rect.left + rect.width() * 3 / 4, rect.top + rect.height() / 2); //right line
            sampleColors[7] = brushBitmap.getPixel(rect.left + rect.width() / 4, rect.top + rect.height() / 2); // left line
            sampleColors[8] = brushBitmap.getPixel(rect.left + rect.width() / 2, rect.top + rect.height() * 3 / 4);  // bottom line

            int alpha = 0;
            int red = 0;
            int green = 0;
            int blue = 0;
            int num = 0;

            for (int c : sampleColors) {
                if (c == Color.TRANSPARENT) {
                    //剔除全透明的颜色,必须剔除
                    continue;
                }
                int alphaC = Color.alpha(c);
                if (alphaC <= 0) {
                    //剔除alpha为0的颜色,当然可以改大一点,防止降低清晰度
                    continue;
                }
                alpha += alphaC;
                red += Color.red(c);
                green += Color.green(c);
                blue += Color.blue(c);
                num++;
            }

            if (num < 1) {
                continue;
            }

            //求出平均值
            int rectColor = Color.argb(alpha / num, red / num, green / num, blue / num);
            if (rectColor != Color.TRANSPARENT) {
                mGridPaint.setColor(rectColor);
                //   canvas.drawRect(rect, mGridPaint);  //绘制矩形
                canvas.drawCircle(rect.centerX(), rect.centerY(), squareWidth / 2, mGridPaint);  //绘制圆
            }
        }
        mGridPaint.setColor(color);

    }


    private void initPaint() {
        // 实例化画笔并打开抗锯齿
        mGridPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG);
        mGridPaint.setAntiAlias(true);
        mGridPaint.setColor(Color.LTGRAY);
        mGridPaint.setStyle(Paint.Style.FILL);
        mGridPaint.setStrokeCap(Paint.Cap.ROUND);  //否则网格绘制

        //否则提供给外部纹理绘制
        mCommonPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG);
        mCommonPaint.setAntiAlias(true);
        mCommonPaint.setStyle(Paint.Style.FILL_AND_STROKE);
        mCommonPaint.setStrokeCap(Paint.Cap.ROUND);

    }

    public void addDrawer(IDrawer drawer) {
        if (drawer == null) return;
        this.drawers.add(drawer);
        gridRects.clear();
        postInvalidate();
    }

    public void removeDrawer(IDrawer drawer) {
        if (drawer == null) return;
        this.drawers.remove(drawer);
        gridRects.clear();
        postInvalidate();
    }

    public void clearDrawer() {
        this.drawers.clear();
        gridRects.clear();
        postInvalidate();
    }

    public List<IDrawer> getDrawers() {
        return new ArrayList<>(drawers);
    }

    public interface IDrawer {
        void draw(Canvas canvas, int width, int height, Paint paint);
    }

}

使用方式

scss 复制代码
       LedDisplayView displayView = findViewById(R.id.ledview);
        final BitmapDrawable bitmapDrawable1 = (BitmapDrawable)getResources().getDrawable(R.mipmap.mm_07);
        final BitmapDrawable bitmapDrawable2 = (BitmapDrawable)getResources().getDrawable(R.mipmap.mm_08);
        ledDisplayView.addDrawer(new LedDisplayView.IDrawer() {

            Matrix matrix =  new Matrix();
            @Override
            public void draw(Canvas canvas, int width, int height, Paint paint) {
                canvas.translate(width/2,height/2);
                matrix.preTranslate(-width/2,-height/4);
                Bitmap bitmap1 = bitmapDrawable1.getBitmap();
                canvas.drawBitmap(bitmap1,matrix,paint);

                matrix.postTranslate(width/2,height/4);
                Bitmap bitmap2 = bitmapDrawable2.getBitmap();
                canvas.drawBitmap(bitmap2,matrix,paint);
            }
        });
        ledDisplayView.addDrawer(new LedDisplayView.IDrawer() {
            @Override
            public void draw(Canvas canvas, int width, int height, Paint paint) {
                paint.setColor(Color.CYAN);
                float textSize = paint.getTextSize();
                paint.setTextSize(sp2px(50));
                canvas.drawText("你好,L E D", 100, 200, paint);
                canvas.drawText("85%", 100, 350, paint);

                paint.setColor(Color.YELLOW);
                canvas.drawCircle(width*3 / 4, height / 4, 100, paint);

                paint.setTextSize(textSize);
            }
        });

四、总结

这个本质上的核心就是采样,通过采样我们最终实现了纹理贴图,这点类似open gl中的光栅化,将图形分割成小三角形一样,最后着色,理解本篇也能帮助大家理解open gl和led显示原理。

相关推荐
Abladol-aj1 小时前
并发和并行的基础知识
java·linux·windows
清水白石0081 小时前
从一个“支付状态不一致“的bug,看大型分布式系统的“隐藏杀机“
java·数据库·bug
qq_364371721 小时前
Vue 内置组件 keep-alive 中 LRU 缓存淘汰策略和实现
前端·vue.js·缓存
y先森2 小时前
CSS3中的弹性布局之侧轴的对齐方式
前端·css·css3
吾日三省吾码6 小时前
JVM 性能调优
java
y先森7 小时前
CSS3中的伸缩盒模型(弹性盒子、弹性布局)之伸缩容器、伸缩项目、主轴方向、主轴换行方式、复合属性flex-flow
前端·css·css3
前端Hardy7 小时前
纯HTML&CSS实现3D旋转地球
前端·javascript·css·3d·html
susu10830189117 小时前
vue3中父div设置display flex,2个子div重叠
前端·javascript·vue.js
弗拉唐7 小时前
springBoot,mp,ssm整合案例
java·spring boot·mybatis
oi778 小时前
使用itextpdf进行pdf模版填充中文文本时部分字不显示问题
java·服务器