好库网
/*
 * Copyright (C) 2013 Chen Hui <calmer91@链接已屏蔽>
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      链接已屏蔽
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package master.flame.danmaku.danmaku.model.android;

import android.annotation.SuppressLint;
import android.graphics.Camera;
import android.graphics.Canvas;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.Paint.Style;
import android.graphics.Typeface;
import android.os.Build;
import android.text.TextPaint;

import master.flame.danmaku.danmaku.model.AbsDisplayer;
import master.flame.danmaku.danmaku.model.AlphaValue;
import master.flame.danmaku.danmaku.model.BaseDanmaku;
import master.flame.danmaku.danmaku.parser.DanmakuFactory;
import master.flame.danmaku.danmaku.renderer.IRenderer;

import java.util.HashMap;
import java.util.Map;

/**
 * Created by MoiTempete.
 */
public class AndroidDisplayer extends AbsDisplayer<Canvas> {

    private Camera camera = new Camera();

    private Matrix matrix = new Matrix();
    
    private final static Map<Float,Float> sTextHeightCache = new HashMap<Float,Float>();
    
    private static float sLastScaleTextSize;
    private final static Map<Float,Float> sCachedScaleSize = new HashMap<Float, Float>(10);

    @SuppressWarnings("unused")
    private int HIT_CACHE_COUNT = 0;

    @SuppressWarnings("unused")
    private int NO_CACHE_COUNT = 0;

    public static TextPaint PAINT, PAINT_DUPLICATE;

    private static Paint ALPHA_PAINT;

    private static Paint UNDERLINE_PAINT;
    
    private static Paint BORDER_PAINT;

    /**
     * 涓嬪垝绾块珮搴?     */
    public static int UNDERLINE_HEIGHT = 4;
    
    /**
     * 杈规鍘氬害
     */
    public static final int BORDER_WIDTH = 4;

    /**
     * 闃村奖鍗婂緞
     */
    private static float SHADOW_RADIUS = 4.0f;

    /**
     * 鎻忚竟瀹藉害
     */
    private static float STROKE_WIDTH = 3.5f;

    /**
     * 鎶曞奖鍙傛暟
     */
    private static float sProjectionOffsetX = 1.0f;
    private static float sProjectionOffsetY = 1.0f;
    private static int sProjectionAlpha = 0xCC;

    /**
     * 寮€鍚槾褰憋紝鍙姩鎬佹敼鍙?     */
    public static boolean CONFIG_HAS_SHADOW = false;
    private static boolean HAS_SHADOW = CONFIG_HAS_SHADOW;

    /**
     * 寮€鍚弿杈癸紝鍙姩鎬佹敼鍙?     */
    public static boolean CONFIG_HAS_STROKE = true;
    private static boolean HAS_STROKE = CONFIG_HAS_STROKE;

    /**
     * 寮€鍚姇褰憋紝鍙姩鎬佹敼鍙?     */
    public static boolean CONFIG_HAS_PROJECTION = false;
    private static boolean HAS_PROJECTION = CONFIG_HAS_PROJECTION;

    /**
     * 寮€鍚姉閿娇锛屽彲鍔ㄦ€佹敼鍙?     */
    public static boolean CONFIG_ANTI_ALIAS = true;
    private static boolean ANTI_ALIAS = CONFIG_ANTI_ALIAS;

    static {
        PAINT = new TextPaint();
        PAINT.setStrokeWidth(STROKE_WIDTH);
        PAINT_DUPLICATE = new TextPaint(PAINT);
        ALPHA_PAINT = new Paint();
        UNDERLINE_PAINT = new Paint();
        UNDERLINE_PAINT.setStrokeWidth(UNDERLINE_HEIGHT);
        UNDERLINE_PAINT.setStyle(Style.STROKE);
        BORDER_PAINT = new Paint();
        BORDER_PAINT.setStyle(Style.STROKE);
        BORDER_PAINT.setStrokeWidth(BORDER_WIDTH);
    }
    
    @SuppressLint("NewApi")
    private static final int getMaximumBitmapWidth(Canvas c) {
        if(Build.VERSION.SDK_INT >= 14) {
            return c.getMaximumBitmapWidth();
        } else {
            return c.getWidth();
        }
    }

    @SuppressLint("NewApi")
    private static final int getMaximumBitmapHeight(Canvas c) {
        if(Build.VERSION.SDK_INT >= 14) {
            return c.getMaximumBitmapHeight();
        } else {
            return c.getHeight();
        }
    }

    public static void setTypeFace(Typeface font){
        if(PAINT!=null)
            PAINT.setTypeface(font);
    }
    
    public static void setShadowRadius(float s){
        SHADOW_RADIUS = s;
    }
    
    public static void setPaintStorkeWidth(float s){
        PAINT.setStrokeWidth(s);
        STROKE_WIDTH = s;
    }

    public static void setProjectionConfig(float offsetX, float offsetY, int alpha) {
        if (sProjectionOffsetX != offsetX || sProjectionOffsetY != offsetY || sProjectionAlpha != alpha) {
            sProjectionOffsetX = (offsetX > 1.0f) ? offsetX : 1.0f;
            sProjectionOffsetY = (offsetY > 1.0f) ? offsetY : 1.0f;
            sProjectionAlpha = (alpha < 0) ? 0 : ((alpha > 255) ? 255 : alpha);
        }
    }

    public static void setFakeBoldText(boolean fakeBoldText){
        PAINT.setFakeBoldText(fakeBoldText);
    }

    public Canvas canvas;

    private int width;

    private int height;

    private float density = 1;

    private int densityDpi = 160;

    private float scaledDensity = 1;

    private int mSlopPixel = 0;

    private boolean mIsHardwareAccelerated = true;

    private int mMaximumBitmapWidth = 2048;

    private int mMaximumBitmapHeight = 2048;

    private void update(Canvas c) {
        canvas = c;
        if (c != null) {
            width = c.getWidth();
            height = c.getHeight();
            if (mIsHardwareAccelerated) {
                mMaximumBitmapWidth = getMaximumBitmapWidth(c);
                mMaximumBitmapHeight = getMaximumBitmapHeight(c);
            }
        }
    }

    @Override
    public int getWidth() {
        return width;
    }

    @Override
    public int getHeight() {
        return height;
    }

    @Override
    public float getDensity() {
        return density;
    }

    @Override
    public int getDensityDpi() {
        return densityDpi;
    }

    @Override
    public int draw(BaseDanmaku danmaku) {
        float top = danmaku.getTop();
        float left = danmaku.getLeft();
        if (canvas != null) {

            Paint alphaPaint = null;
            boolean needRestore = false;
            if (danmaku.getType() == BaseDanmaku.TYPE_SPECIAL) {
                if (danmaku.getAlpha() == AlphaValue.TRANSPARENT) {
                    return IRenderer.NOTHING_RENDERING;
                }
                if (danmaku.rotationZ != 0 || danmaku.rotationY != 0) {
                    saveCanvas(danmaku, canvas, left, top);
                    needRestore = true;
                }

                int alpha = danmaku.getAlpha();
                if ( alpha != AlphaValue.MAX) {
                    alphaPaint = ALPHA_PAINT;
                    alphaPaint.setAlpha(danmaku.getAlpha());
                }
            }
            
            // skip drawing when danmaku is transparent
            if(alphaPaint!=null && alphaPaint.getAlpha()== AlphaValue.TRANSPARENT){
                return IRenderer.NOTHING_RENDERING;
            }
            // drawing cache
            boolean cacheDrawn = false;
            int result = IRenderer.CACHE_RENDERING;
            if (danmaku.hasDrawingCache()) {
                DrawingCacheHolder holder = ((DrawingCache) danmaku.cache).get();
                if (holder != null) {
                    cacheDrawn = holder.draw(canvas, left, top, alphaPaint);
                }
            }
            if (!cacheDrawn) {
                if (alphaPaint != null) {
                    PAINT.setAlpha(alphaPaint.getAlpha());
                } else {
                    resetPaintAlpha(PAINT);
                }
                drawDanmaku(danmaku, canvas, left, top, true);
                result = IRenderer.TEXT_RENDERING;
            }

            if (needRestore) {
                restoreCanvas(canvas);
            }
            
            return result;
        }
        
        return IRenderer.NOTHING_RENDERING;
    }

    private void resetPaintAlpha(Paint paint) {
        if (paint.getAlpha() != AlphaValue.MAX) {
            paint.setAlpha(AlphaValue.MAX);
        }
    }

    private void restoreCanvas(Canvas canvas) {
        canvas.restore();
    }

    private int saveCanvas(BaseDanmaku danmaku, Canvas canvas, float left, float top) {
        camera.save();
        camera.rotateY(-danmaku.rotationY);
        camera.rotateZ(-danmaku.rotationZ);
        camera.getMatrix(matrix);
        matrix.preTranslate(-left, -top);
        matrix.postTranslate(left, top);
        camera.restore();
        int count = canvas.save();
        canvas.concat(matrix);
        return count;
    }

    public static void drawDanmaku(BaseDanmaku danmaku, Canvas canvas, float left, float top,
                                   boolean quick) {
        float _left = left;
        float _top = top;
        left += danmaku.padding;
        top += danmaku.padding;
        if (danmaku.borderColor != 0) {
            left += BORDER_WIDTH;
            top += BORDER_WIDTH;
        }
        
        HAS_STROKE = CONFIG_HAS_STROKE;
        HAS_SHADOW = CONFIG_HAS_SHADOW;
        HAS_PROJECTION = CONFIG_HAS_PROJECTION;
        ANTI_ALIAS = !quick && CONFIG_ANTI_ALIAS;
        TextPaint paint = getPaint(danmaku, quick);
        if (danmaku.lines != null) {
            String[] lines = danmaku.lines;
            if (lines.length == 1) {
                if (hasStroke(danmaku)) {
                    applyPaintConfig(danmaku, paint, true);
                    float strokeLeft = left;
                    float strokeTop = top - paint.ascent();
                    if (HAS_PROJECTION) {
                        strokeLeft += sProjectionOffsetX;
                        strokeTop += sProjectionOffsetY;
                    }
                    canvas.drawText(lines[0], strokeLeft, strokeTop, paint);
                }
                applyPaintConfig(danmaku, paint, false);
                canvas.drawText(lines[0], left, top - paint.ascent(), paint);
            } else {
                float textHeight = (danmaku.paintHeight - 2 * danmaku.padding) / lines.length;
                for (int t = 0; t < lines.length; t++) {
                    if (lines[t] == null || lines[t].length() == 0) {
                        continue;
                    }
                    if (hasStroke(danmaku)) {
                        applyPaintConfig(danmaku, paint, true);
                        float strokeLeft = left;
                        float strokeTop = t * textHeight + top - paint.ascent();
                        if (HAS_PROJECTION) {
                            strokeLeft += sProjectionOffsetX;
                            strokeTop += sProjectionOffsetY;
                        }
                        canvas.drawText(lines[t], strokeLeft, strokeTop, paint);
                    }
                    applyPaintConfig(danmaku, paint, false);
                    canvas.drawText(lines[t], left, t * textHeight + top - paint.ascent(), paint);
                }
            }
        } else {
            if (hasStroke(danmaku)) {                
                applyPaintConfig(danmaku, paint, true);
                float strokeLeft = left;
                float strokeTop = top - paint.ascent();
                if (HAS_PROJECTION) {
                    strokeLeft += sProjectionOffsetX;
                    strokeTop += sProjectionOffsetY;
                }
                canvas.drawText(danmaku.text, strokeLeft, strokeTop, paint);
            }
            applyPaintConfig(danmaku, paint, false);
            canvas.drawText(danmaku.text, left, top - paint.ascent(), paint);
        }

        // draw underline
        if (danmaku.underlineColor != 0) {
            Paint linePaint = getUnderlinePaint(danmaku);
            float bottom = _top + danmaku.paintHeight - UNDERLINE_HEIGHT;
            canvas.drawLine(_left, bottom, _left + danmaku.paintWidth, bottom, linePaint);
        }
        
        //draw border
        if (danmaku.borderColor != 0) {
            Paint borderPaint = getBorderPaint(danmaku);
            canvas.drawRect(_left, _top, _left + danmaku.paintWidth, _top + danmaku.paintHeight,
                    borderPaint);
        }

    }
    
    private static boolean hasStroke(BaseDanmaku danmaku) {
        return (HAS_STROKE || HAS_PROJECTION) && STROKE_WIDTH > 0 && danmaku.textShadowColor != 0;
    }

    public static Paint getBorderPaint(BaseDanmaku danmaku) {
        BORDER_PAINT.setColor(danmaku.borderColor);
        return BORDER_PAINT;
    }

    public static Paint getUnderlinePaint(BaseDanmaku danmaku){
        UNDERLINE_PAINT.setColor(danmaku.underlineColor);
        return UNDERLINE_PAINT;
    }
    
    private static TextPaint getPaint(BaseDanmaku danmaku, boolean quick) {
        TextPaint paint;
        if (quick) {
            paint = PAINT_DUPLICATE;
            paint.set(PAINT);
        } else {
            paint = PAINT;
        }
        paint.setTextSize(danmaku.textSize);
        applyTextScaleConfig(danmaku, paint);
        
        //ignore the transparent textShadowColor
        if (!HAS_SHADOW || SHADOW_RADIUS <= 0 || danmaku.textShadowColor == 0) {
            paint.clearShadowLayer();
        } else {
            paint.setShadowLayer(SHADOW_RADIUS, 0, 0, danmaku.textShadowColor);
        }
        paint.setAntiAlias(ANTI_ALIAS);
        return paint;
    }

    public static TextPaint getPaint(BaseDanmaku danmaku) {
        return getPaint(danmaku, false);
    }
    
    private static void applyPaintConfig(BaseDanmaku danmaku, Paint paint,boolean stroke) {

        if (DanmakuGlobalConfig.DEFAULT.isTranslucent) {
            if(stroke){
                paint.setStyle(HAS_PROJECTION ? Style.FILL : Style.STROKE);
                paint.setColor(danmaku.textShadowColor & 0x00FFFFFF);
                int alpha = HAS_PROJECTION ? (int)(sProjectionAlpha * ((float)DanmakuGlobalConfig.DEFAULT.transparency / AlphaValue.MAX))
                        : DanmakuGlobalConfig.DEFAULT.transparency;
                paint.setAlpha(alpha);
            }else{
                paint.setStyle(Style.FILL);
                paint.setColor(danmaku.textColor & 0x00FFFFFF);
                paint.setAlpha(DanmakuGlobalConfig.DEFAULT.transparency);
            }
        } else {
            if(stroke){
                paint.setStyle(HAS_PROJECTION ? Style.FILL : Style.STROKE);
                paint.setColor(danmaku.textShadowColor & 0x00FFFFFF);
                int alpha = HAS_PROJECTION ? sProjectionAlpha : AlphaValue.MAX;
                paint.setAlpha(alpha);
            }else{
                paint.setStyle(Style.FILL);
                paint.setColor(danmaku.textColor & 0x00FFFFFF);
                paint.setAlpha(AlphaValue.MAX);
            }
        }
            
    }

    private static void applyTextScaleConfig(BaseDanmaku danmaku, Paint paint) {
        if (!DanmakuGlobalConfig.DEFAULT.isTextScaled) {
            return;
        }
        Float size = sCachedScaleSize.get(danmaku.textSize);
        if (size == null || sLastScaleTextSize != DanmakuGlobalConfig.DEFAULT.scaleTextSize) {
            sLastScaleTextSize = DanmakuGlobalConfig.DEFAULT.scaleTextSize;
            size = danmaku.textSize * DanmakuGlobalConfig.DEFAULT.scaleTextSize;
            sCachedScaleSize.put(danmaku.textSize, size);
        }
        paint.setTextSize(size);
    }
    
    @Override
    public void measure(BaseDanmaku danmaku) {
        TextPaint paint = getPaint(danmaku);
        if (HAS_STROKE) {
            applyPaintConfig(danmaku, paint, true);
        }
        calcPaintWH(danmaku, paint);
        if (HAS_STROKE) {
            applyPaintConfig(danmaku, paint, false);
        }
    }
    
    private void calcPaintWH(BaseDanmaku danmaku, TextPaint paint) {
        float w = 0;
        Float textHeight = getTextHeight(paint);
        if (danmaku.lines == null) {
            w = danmaku.text == null ? 0 : paint.measureText(danmaku.text);
            setDanmakuPaintWidthAndHeight(danmaku,w,textHeight);
            return;
        }

        for(String tempStr : danmaku.lines){
            if (tempStr.length() > 0) {
                float tr = paint.measureText(tempStr);
                w = Math.max(tr, w);
            }
        }

        setDanmakuPaintWidthAndHeight(danmaku,w,danmaku.lines.length * textHeight);
    }
        
    private void setDanmakuPaintWidthAndHeight(BaseDanmaku danmaku, float w, float h) {
        float pw = w + 2 * danmaku.padding;
        float ph = h + 2 * danmaku.padding;
        if (danmaku.borderColor != 0) {
            pw += 2 * BORDER_WIDTH;
            ph += 2 * BORDER_WIDTH;
        }
        danmaku.paintWidth = pw + getStrokeWidth();
        danmaku.paintHeight = ph;
    }

    private static float getTextHeight(TextPaint paint) {
        Float textSize = paint.getTextSize();
        Float textHeight = sTextHeightCache.get(textSize);
        if(textHeight == null){
            Paint.FontMetrics fontMetrics = paint.getFontMetrics();
            textHeight = fontMetrics.descent - fontMetrics.ascent + fontMetrics.leading;
            sTextHeightCache.put(textSize, textHeight);
        }  
        return textHeight;
    }
    
    public static void clearTextHeightCache(){
        sTextHeightCache.clear();
        sCachedScaleSize.clear();
    }

    @Override
    public float getScaledDensity() {
        return scaledDensity;
    }

    @Override
    public void resetSlopPixel(float factor) {
        float d = Math.max(density, scaledDensity);
        d = Math.max(factor, getWidth() / (float) DanmakuFactory.BILI_PLAYER_WIDTH); //correct for low density and high resolution
        float slop = d * DanmakuFactory.DANMAKU_MEDIUM_TEXTSIZE; 
        mSlopPixel = (int) slop;
        if (factor > 1f)
            mSlopPixel = (int) (slop * factor);
    }

    @Override
    public int getSlopPixel() {
        return mSlopPixel;
    }

    @Override
    public void setDensities(float density, int densityDpi, float scaledDensity) {
        this.density = density;
        this.densityDpi = densityDpi;
        this.scaledDensity = scaledDensity;
    }

    @Override
    public void setSize(int width, int height) {
        this.width = width;
        this.height = height;
    }

    @Override
    public void setExtraData(Canvas data) {
        update(data);
    }

    @Override
    public Canvas getExtraData() {
        return this.canvas;
    }

    @Override
    public float getStrokeWidth() {
        if (HAS_SHADOW && HAS_STROKE) {
            return Math.max(SHADOW_RADIUS, STROKE_WIDTH);
        }
        if (HAS_SHADOW) {
            return SHADOW_RADIUS;
        }
        if (HAS_STROKE) {
            return STROKE_WIDTH;
        }
        return 0f;
    }

    @Override
    public void setHardwareAccelerated(boolean enable) {
        mIsHardwareAccelerated = enable;
    }

    @Override
    public boolean isHardwareAccelerated() {
        return mIsHardwareAccelerated ;
    }

    @Override
    public int getMaximumCacheWidth() {
        return mMaximumBitmapWidth;
    }

    @Override
    public int getMaximumCacheHeight() {
        return mMaximumBitmapHeight;
    }


}