整合营销服务商

电脑端+手机端+微信端=数据同步管理

免费咨询热线:

CSS设置背景模糊

CSS设置背景模糊

SS设置背景模糊

在做一些页面的时候,为了让页面更好看,我们常常需要设置一些背景图片,但是,当背景图片太过花哨的时候,又会影响我们的主体内容,所以我们就需要用到filter属性来设置他的模糊值。

html代码如下

<!DOCTYPE html>
<html>
<head>
 <meta charset="utf-8">
 <meta name="viewport" content="width=device-width">
 <title>JS Bin</title>
</head>
<body>
<div class="cover">
 <h1>我是需要突出显示的内容
</div>
</body>
</html>

但是如果直接在背景图片上使用的话,

.cover{
 width:600px;
 height:300px;
 position:relative;
 text-align:center;
 line-height:300px;
 color:white;
 background:transparent url(http://91jean.oss-cn-hangzhou.aliyuncs.com/18-8-31/16567303.jpg) center center no-repeat;
 filter:blur(5px);
 background-size:cover;
}

可能会造成下面的这种情况。

我们会发现背景和主体内容都变糊了。

解决办法:给背景图片增加一个伪元素,将背景图片和filter属性设置在伪元素上,具体代码如下

.cover{
 width:600px;
 height:300px;
 position:relative;
 text-align:center;
 line-height:300px;
 color:white;
}
.cover::before{ 
 content:'';
 position:absolute;
 top:0;
 left:0;
 width:600px;
 height:300px;
 background:transparent url(http://91jean.oss-cn-hangzhou.aliyuncs.com/18-8-31/16567303.jpg) center center no-repeat;
 filter:blur(5px);
 z-index:-1;
 background-size:cover;
}

复制代码



在一名 Android 程序猿的职业生涯中,大概率会与设计狮有过这样的讨论:

:“我要这个毛玻璃效果”

:“这个效果实现不了”

(掏出iPhone):“你看人家苹果都有,你怎么做不了?”

:“iOS 可以,Android 真的做不了,童叟无欺”


这个让设计师心心念念的视觉效果,就是背景模糊。

iOS 8 加入的 UIVisualEffectView、CSS 中的backdrop-filter 以及 Flutter 中的 BackdropFilter 类,都可以实现这个效果,只有 Android 一直没有支持该能力。


在 Android 领域内,背景模糊效果有两个不同场景,需要做一下区分:

  • Window 级背景模糊:将某个半透明 Window 的背景内容进行模糊处理,常见于通知栏下拉之类的场景。
  • View 级背景模糊:在某一个App页面内,某控件的背景内容进行模糊处理。这个能力 Android 一直没有支持,只有一些开源框架进行过尝试。

Window 级的背景模糊,多年以来各手机厂商都有自己的实现方案。而 Android 12 里,AOSP 对SurfaceFlinger 进行了重构,GPU 合成的部分也使用 Skia 进行渲染,同时对跨 Window 的背景模糊做了官方的支持[1],并 public 了相关的 API。

Android 12 支持的Window背景模糊

(模糊内容属于背后的窗口)


而本文要讨论的是后者——View 级背景模糊的实现方式。

iOS 上的 View 级背景模糊效果

(模糊内容属于同一窗口内背后的控件)


笔者整理了现在开源库中的两类解决方案,外加酷派团队调研出的两种方案,共四种写法,供大家参考。





开源方案

如果要从应用侧实现这个效果,最重要的一个步骤是获取模糊控件背景的内容,目前 Github 上的相关框架,大概分为两个方案:



方案一:找到背景控件、调用draw()

代表框架:

500px/500px-android-blur、Dimezis/BlurView、mmin18/RealtimeBlurView,这三个框架的??数量均为 2.7k 左右,接受度比较高。


思路解析

创建一个链接到 Bitmap 的离屏Canvas,在模糊控件绘制之前,将下层布局手动绘制到这个 Canvas 里,这样在 Bitmap 里就拿到了控件背景内容。


其中:500px-android-blur 的做法是手动指定背景的布局,在模糊控件 onDraw 的时候,对指定的下层布局进行绘制。这种做法实现起来比较简单,但每次都需要手动指定下层布局,而且模糊控件不能包含在该布局里,缺少灵活性。

// 手动指定下层布局


blurringView.setBlurredView(blurredView);

而 Dimezis/BlurView、mmin18/RealtimeBlurView 的解法则更加灵活,下层布局不用特别指定,直接使用rootView(一般为DecorView)。在模糊控件onPreDraw的时候,将rootView绘制到离屏Canvas。


但 rootView 并不是下层布局,因为模糊控件也包含在内。这两个框架使用比较取巧的办法解决了这个问题:在draw(Canvas)方法里,判断如果是离屏 Canvas 在绘制,则跳过自身绘制。


获取背景内容后,剩下的步骤则是对 Bitmap 进行模糊处理并绘制,由于比较简单就不赘述了。


方案效果(以500px-android-blur为例)

优点

  • 兼容性好。没有使用系统隐藏接口,理论上在任意 Android 版本上都能运行。


缺点

方案缺点主要是额外的性能开销。

  • 无法使用 Hardware Bitmap。这几个框架都使用了 RenderScript 做模糊处理,无法使用 Hardware Bitmap。模糊的结果在上屏之前还需要进行一次纹理上传。频繁的 Bitmap 更新会带来内存及性能的更多开销[2]。
  • 额外离屏绘制。由于调用了 draw 方法,每一帧都需要进行一次额外离屏绘制。而且由于 Canvas 对应的是非 Hardware Bitmap,这个离屏绘制也无法使用硬件加速




方案二:Canvas GL Functor

代表框架:HokoFly/HokoBlurDrawable

方案效果


这个框架使用起来非常简单,不需要做额外的设置,只需要给View设置一个background即可。

final BlurDrawable blurDrawable=new BlurDrawable();
view.setBackgroundDrawable(blurDrawable);

调查了源码,它也没有把下层布局再次绘制,那它是如何获取到背景内容的?


思路解析

秘密在于这个隐藏方法:RecordingCanvas.callDrawGLFunction2()

/**
* Records the functor specified with the drawGLFunction function pointer. This is
* functionality used by webview for calling into their renderer from our display lists.
*
* @param drawGLFunction A native function pointer
*
* @hide
* @deprecated Use {@link #drawWebViewFunctor(int)}
*/
@Deprecated
public void callDrawGLFunction2(long drawGLFunction) {
    nCallDrawGLFunction(mNativeCanvasWrapper, drawGLFunction, null);
}

这个隐藏的方法看起来比较陌生,因为它不是设计给 App 使用的。它的作用是将外部的 OpenGL 方法链接到 Canvas 的绘制流程里,目前官方的使用场景是 Android 的 WebView。

WebView 使用自己的 OpenGL 方法对网页进行渲染,再调用这个方法,将结果链接到你的 App Window 里。

这刚好能解释,为什么 WebView 使用独立的渲染机制,但不需要使用 SurfaceView 的独立 layer 也能显示到屏幕上。

经过一番调查发现,参数drawGLFunction,是 Android Native 层的一个通用函数指针,结构如下:

// File: system/core/libutils/include/utils/Functor.h

class Functor {

public:

    Functor() {}

    virtual ~Functor() {}

    virtual status_t operator()(int /*what*/, void* /*data*/) { return OK; }

};

在 UI 线程调用callDrawGLFunction2()方法后,只是设置了函数指针,并没有起到绘制效果。真正的绘制逻辑,发生在 RenderThread 里:

// File: frameworks/base/libs/hwui/pipeline/skia/GLFunctorDrawable.cpp


void GLFunctorDrawable::onDraw(SkCanvas* canvas) {


    // 省略部分代码


    ……






    GLuint fboID=0;


    SkISize fboSize;


    GetFboDetails(canvas, &fboID, &fboSize);






    // 省略部分代码:初始化GLContext、判断离屏Layer等


    ……


    DrawGlInfo info;


    info.clipLeft=clipBounds.fLeft;


    info.clipTop=clipBounds.fTop;


    info.clipRight=clipBounds.fRight;


    info.clipBottom=clipBounds.fBottom;


    info.isLayer=fboID !=0;


    info.width=fboSize.width();


    info.height=fboSize.height();


    mat4.getColMajor(&info.transform[0]);


    info.color_space_ptr=canvas->imageInfo().colorSpace();






    // 省略部分代码:绑定FBO、设置GL环境


    ……


    if (mAnyFunctor.index()==0) {


        std::get<0>(mAnyFunctor).handle->drawGl(info);


    } else {


        // 这里会调用到函数指针Functor


        (*(std::get<1>(mAnyFunctor).functor))(DrawGlInfo::kModeDraw, &info);


    }


    // 省略部分代码


    ……


}

看到这里,为防止有读者不了解 hwui,补充一些前提知识:
我们都知道 Android 的 View 系统,在底层是使用 Skia 图形库进行渲染,而连接 View 与 Skia 的组件便是 libhwui。App 界面的绘制指令,最终都通过 hwui 库,在 RenderThread 中得以执行。


Skia 的SkDrawable结构,与 Android 的Drawable类似,都是在onDraw(canvas)方法中执行绘制指令,实际上后者也是对前者的一个效仿设计。

在 hwui 库中,一共有这几种 SkDrawable 类型:

  • RenderNodeDrawable:最常见的一个SkDrawable,99% 的 View 和布局的绘制指令会保存到 RenderNode 中,最终都由这个 Drawable 进行绘制。
  • AnimatedImageDrawable:对应到 Java 层的同名类,一般用于播放 gif 图片。
  • AnimatedRoundRect、AnimatedCircle、AnimatedRippleDrawable:Material Design中的按钮Ripple 效果、Canvas的drawCircledrawRect方法的底层实现。
  • LayerDrawable:绘制一个 OpenGL 纹理。TextureView的底层正是用它实现。
  • FunctorDrawable:在 Skia 绘制指令中,链接到外部绘制指令。它有三个子类:GLFunctorDrawable、VkFunctorDrawable,分别链接 OpenGL 与 Vulkan 指令。


而方案二,正是使用了 GLFunctorDrawable


回到这个库中,上面的GLFunctorDrawable,会调用上层设置过来的 functor方法,并将此时的DrawGlInfo传递过去。

我们来看看这个库的 Functor 真正做了些什么:

经过 jni 的多次中转,Functor 最终调用到了上面的 Java 层逻辑。与一般的 Java 层逻辑不同,图中的代码实际上运行在 RenderThread 中,而且拥有 GLFunctorDrawable 已经设置好的 OpenGL 上下文。


这里的逻辑大概分为三步:获取当前屏幕内容、进行 X/Y 轴两次模糊运算、放大并叠加颜色

最关键的代码就是这行glCopyTexSubImage2D,它可以将当前屏幕已绘制内容进行区域拷贝[3]。由于这个代码的执行在背景内容绘制和控件内容绘制之间,这样便获取到了控件的背景内容


总结

这个方案使用GLFunctorDrawable的机制,将自己的 OpenGL 指令嵌入到 RenderThread 每一帧的绘制中,获取背景内容,做模糊并上屏。


优点

  • 使用方便:可以用 Drawable 的方式使用,嵌入到任意层级的布局。


缺点

当然,这个方案的缺点也十分明显,导致它无法在 app 里商用。

  1. 兼容性差

callDrawGLFunctor2 是一个 hide 方法,其在不同 Android 版本都有不同实现,框架本身做了多平台兼容。

但在 Google 对 hide 方法的态度及采取的措施面前,这个做法显得不可持续。

首先,Android 11 开始采取了更严格的反射限制,框架使用者需要额外去处理限制突破逻辑。

其次,callDrawGLFunctor2(long functor)是一个废弃方法,在 Android 12 开始被移除。

在 Android 12 中,被换成 drawWebViewFunctor(int functor),不仅 functor 换了结构,还必须同时支持 OpenGLVulkan 两种实现。

这个方案的兼容成本已经非常高了,框架作者也没有继续进行维护,目前该框架在 Android 12 上无法使用。


  1. 使用场景受限制

在 hwui 的 pipeline 里,如果这块内容被绘制到了一块离屏 buffer 再上屏,那么这里的 GLFunctor 便无法获取到控件背后的内容。

这是因为,在 hwui 每一帧绘制开始之前,会先把离屏的 Layer 先渲染完成得到结果,再把这些结果当做图像资源,在这一帧参与绘制。
需要离屏 buffer 的场景有这几种:
设置小于 1 的 alpha需要 clip 的 Functor有拉伸或者RenderEffect 效果(Android 12 添加)等,比较常见。

// File: frameworks/base/libs/hwui/RenderProperties.h


bool promotedToLayer() const {


    return mLayerProperties.mType==LayerType::None && fitsOnLayer() &&


            // 是functor且有clip、animation、translation等情况。


           (mComputedFields.mNeedLayerForFunctors ||


            // 设置了RenderEffect(Android 12新增)


            mLayerProperties.mImageFilter !=nullptr ||


            // 当前有拉伸效果(Android 12新增)


            mLayerProperties.getStretchEffect().requiresLayer() ||


            // 当前View设置了alpha,且hasOverlappingRendering为true


            (!MathUtils::isZero(mPrimitiveFields.mAlpha) && mPrimitiveFields.mAlpha < 1 &&


             mPrimitiveFields.mHasOverlappingRendering));


    }

所以当 GLFunctor 被离屏渲染时,它会提前执行,此时便无法获取到它背后的内容。也就是说这种方案,无法对模糊控件设置 alpha 或者切圆角,这点也在框架的 issues 里得到了印证:






自研方案

方案三:扩展 Canvas.saveLayer()


笔者发现,Flutter 中的 BackdropFilter 类也能实现效果,其也使用 Skia 作为渲染引擎。


BackdropFilter这个 Dart 层的 Widget,经过 SceneBuilder 的中转,最终映射到 native 层的BackdropFilterLayer类:

它并没有特殊的绘制逻辑,只是在绘制 children 内容之前,调用了一个saveLayer操作。难道 Flutter仅仅通过saveLayer,就实现了背景模糊?

Android中也有Canvas.saveLayer()这个API,其作用是创建一个新的Layer,后续的绘制均发生在这个新的Layer里,绘制完成后将结果再一起上屏。所以这个方法开销比较大,非必要不建议使用。
更关键的是,它并不支持背景模糊功能。

看来红框中的这一行代码是关键。调查SaveLayerRec发现,它是 skia 引擎中的结构体,大致结构如下:

enum SaveLayerFlagsSet {


    kInitWithPrevious_SaveLayerFlag=1 << 2, //!< initializes with previous contents


};






struct SaveLayerRec {


    SaveLayerRec(const SkRect* bounds, const SkPaint* paint, SaveLayerFlags saveLayerFlags=0)


        : fBounds(bounds)


        , fPaint(paint)


        , fSaveLayerFlags(saveLayerFlags)


    {}


    SaveLayerRec(const SkRect* bounds, const SkPaint* paint, const SkImageFilter* backdrop, SaveLayerFlags saveLayerFlags)


        : fBounds(bounds)


        , fPaint(paint)


        , fBackdrop(backdrop)


        , fSaveLayerFlags(saveLayerFlags)


    {}


    /** hints at layer size limit */


    const SkRect*        fBounds=nullptr;


    /** modifies overlay */


    const SkPaint*       fPaint=nullptr;


    /**


     *  If not null, this triggers the same initialization behavior as setting


     *  kInitWithPrevious_SaveLayerFlag on fSaveLayerFlags: the current layer is copied into


     *  the new layer, rather than initializing the new layer with transparent-black.


     *  This is then filtered by fBackdrop (respecting the current clip).


     */


    const SkImageFilter* fBackdrop=nullptr;


    /** preserves LCD text, creates with prior layer contents */


    SaveLayerFlags       fSaveLayerFlags=0;


};

这里的SkImageFilter是 Skia 中可以实现图形效果的工具类,常见的 filter 有:Blur、ColorFilter、Matrix、XferMode 等等。
Skia 在很早就支持了这个能力,在 Android 12 中,谷歌在上层封装了RenderEffect类,第一次将其开放给上层调用[4]。

看下真正执行模糊运算的地方:

void SkCanvas::internalSaveLayer(const SaveLayerRec& rec, SaveLayerStrategy strategy) {


    // 省略代码


    ……


    // If we have a backdrop filter, then we must apply it to the entire layer (clip-bounds)


    // regardless of any hint-rect from the caller. skbug.com/8783


    if (rec.fBackdrop) {


        bounds=nullptr;


    }


    // 两种情况下会绘制背景内容


    bool initBackdrop=(rec.fSaveLayerFlags & kInitWithPrevious_SaveLayerFlag) || rec.fBackdrop;


    // 省略代码


    ……


    if (initBackdrop) {


        DrawDeviceWithFilter(priorDevice, rec.fBackdrop, newDevice.get(), { ir.fLeft, ir.fTop }, fMCRec->fMatrix.asM33());


    }


    // 省略代码


    ……


}






void SkCanvas::DrawDeviceWithFilter(SkBaseDevice* src, const SkImageFilter* filter, SkBaseDevice* dst, const SkIPoint& dstOrigin, const SkMatrix& ctm) {


    // 省略代码


    ……


    // 截取当前已经绘制的内容


    auto special=src->snapSpecial(backdropBounds);


    if (!special) {


        return;


    }


    // 省略代码


    …


    SkIPoint offset;


    // 使用指定的filter进行处理


    special=as_IFB(filter)->filterImage(ctx).imageAndOffset(&offset);


    if (special) {


        offset +=layerInputBounds.topLeft();


        SkMatrix dstCTM=toRoot;


        dstCTM.postTranslate(-dstOrigin.x(), -dstOrigin.y());


        dstCTM.preTranslate(offset.fX, offset.fY);


        // 将处理结果进行绘制


        dst->drawSpecial(special.get(), dstCTM, sampling, p);


    }


    // 省略代码


    ……


}

结合注释与代码得知,有两个组合可以实现背景模糊效果:

  • 组合一:backdrop设置为对应的filter,比如SkBlurImageFilter即可,将saveLayerFlags设为0。
  • 组合二:backdrop仍然为null,但saveLayerFlags设为kInitWithPrevious_SaveLayerFlag,此时这个新建的layer会将之前layer的内容复制一份。然后使用paint->setImageFilter(),将 filter 设置到 paint 中。


区别在于,前者会将整个 Canvas 的内容都进行处理,然后 clip 到相应区域;而后者只会截取指定区域的内容,另外也不会立即处理,而是选择在新 layer 上屏的时刻统一处理。

Flutter 使用的是第一个组合。笔者两个方案都进行了尝试,本文与Flutter保持一致,讨论第一种组合。


思路解析

看完 Flutter,我们再来看看 Android 里的saveLayer逻辑,经过一些中转,它最终调用到了这里

// File: frameworks/base/libs/hwui/SkiaCanvas.cpp


int SkiaCanvas::saveLayer(float left, float top, float right, float bottom, const SkPaint* paint, SaveFlags::Flags flags) {


    const SkRect bounds=SkRect::MakeLTRB(left, top, right, bottom);


    // 这里只用到了 bounds, paint, layerFlags 三个参数


    const SkCanvas::SaveLayerRec rec(&bounds, paint, layerFlags(flags));


    return mCanvas->saveLayer(rec);


}

可以看到,Android 直接忽略了后面两个参数,并没有提供任何暴露途径。


那我们的思路也就很简单了:

  1. 新增Canvas.saveLayer()的overload方法,增加backdropFilter参数。在内部转为对SaveLayerRec的后两个参数的设置。

  1. 暴露对SkImageFilter对象的创建方法。如果是Android 12环境,则可以跳过这一步,使用RenderEffect.getNativeInstance()即可。

  1. 由于是对 Canvas 的调用,简单的办法是封装成 Drawable,供 View 设置为background使用。
<?xml version="1.0" encoding="utf-8"?>


<coolx.graphics.drawable.BackdropBlurDrawable


    xmlns:android="http://schemas.android.com/apk/res/android"


    xmlns:app="http://schemas.android.com/apk/res-auto"


    app:blurRadius="30dp"


    app:saturation="1.8"


    app:fallbackColor="#AAFFFFFF">


    <shape


        android:shape="rectangle">


        <solid android:color="#BFEFEFEF"/>


    </shape>


</coolx.graphics.drawable.BackdropBlurDrawable>

实机效果演示


优点

  • 使用简单、效果好。将SkImageFilter进行组合,可以轻松实现透亮的毛玻璃效果。图例叠加了模糊和饱和度的修改,效果非常接近 iOS。
  • 兼容性高。模糊控件可以随意动画、clip。


缺点

  • 使用场景受限制。由于是调用Canvas接口,所以封装为 Drawable 设置为 View 的背景更适合使用。此时也会受到与方案二相同的限制:虽然限制少一些,可以正常 clip圆角,但当 View 设置 alpha 的时候,模糊仍会失效
  • 需要修改系统源码


这个方案我提交到了AOSP[5],谷歌工程师给了两个反馈:

  1. 性能差(very, very slow)。
  2. 在 alpha 的时候会失效。


第一条反馈与实际表现不符合,关于性能是否符合要求,需要进一步的调查和实验。

但第二条确实如此,所以还需要继续找寻新的解法。




方案四:修改libhwui,增加模糊计算

View 的 alpha 发生改变,其实是设置的RenderNode.setAlpha()方法。
方案二与方案三,由于都使用了Canvas的接口,所以无论是重写View.onDraw()方法,还是封装 Drawable,这个调用指令都在该ViewRenderNode内部。这样当 alpha 变化时,就无法获取到背景内容。

除了 alpha 外,这次也打算将所有的边界场景一次考虑清楚:View 的 transform、动画、clip 等。目前能想到的方案,是让背景模糊逻辑脱离RenderNode


参照前文,RenderNode的真正绘制,是在RenderNodeDrawable中,我们可以新定义一个BackdropFilterDrawable类型,与其平级。


BackdropFilterDrawable的绘制顺序,提前到RenderNodeDrawable之前即可。


BackdropFilterDrawable类的关键逻辑如下:

void BackdropFilterDrawable::onDraw(SkCanvas* canvas) {


    // 对后面内容进行截图(并不会创建新的buffer),此截图为Canvas完整截图。


    auto backdropImage=canvas->getSurface()->makeImageSnapshot();


    // 从target RenderNode那里,同步properties,无论它是否在做动画、缩放、是否有clip等,都进行同步。计算结果保存到 mImageSubset 里,这是我们上层RenderNode真正的可见区域。


    if (!prepareToDraw(canvas, properties, backdropImage->width(), backdropImage->height())) {


        // 当返回false的时候,说明不可见,则我们也跳过绘制。


        return;


    }






    auto imageSubset=mImageSubset.roundOut();


    // 将截图里的上层区域进行filter处理。


    backdropImage=        backdropImage->makeWithFilter(canvas->recordingContext(), backdropFilter, imageSubset,


                                          imageSubset, &mOutSubset, &mOutOffset);


    // 将filter结果进行绘制。


    canvas->drawImageRect(backdropImage, SkRect::Make(mOutSubset), mDstBounds,


                          SkSamplingOptions(SkFilterMode::kLinear), &mPaint,


                          SkCanvas::kStrict_SrcRectConstraint);


}


,时长00:06


优点

与方案三相同,优势在于性能和效果。

  • 使用简单、效果好。将SkImageFilter进行组合,可以轻松实现透亮的毛玻璃效果。图例叠加了模糊和饱和度的修改,效果非常接近 iOS。
  • 兼容性高。模糊控件可以随意动画、clip、变换 alpha。


缺点

  • 使用场景受限制。虽然解决了方案二的 alpha 问题,但如果该控件的父布局设置了 alpha,它仍然无法拿到父布局以外的背景内容。这算是个小小的遗憾。
  • 需要修改系统源码。对系统源码的改动,比方案三多不少。


这个方案我也提交到了AOSP[6],目前状态为待 Review。感兴趣的可以编译看下效果。





方案对比



我们在酷派COOL 20s 5G上,将四种方案进行横向对比。

这台机器配置为天玑700、6GB内存、128GB存储、1080p 90Hz的屏幕。


,时长00:02


使用perfetto抓取的trace,来衡量和计算每种方案在Choreographer.doFrame()RenderThread分别消耗的时间。

在perfetto里可以直观地看到平均耗时


抓取无模糊效果的耗时,作为基准指标。分别为:doFrame 1.588ms, RenderThread: 3.485ms。

最终对比结果如下:



方案一

方案二

方案三

方案四

实现复杂度

位移

??

??

??

??

缩放

?

内容错误

??

??

??

clip圆角

??

?

??

??

alpha

??

?

?

??

parent alpha仍然不支持)

视觉效果

?

仅支持模糊

?

仅支持模糊

??

支持更多效果扩展(如saturation)

??

支持更多效果扩展(如saturation)

兼容性

??

?

Android 12不兼容

修改源码

修改源码

doFrame

性能开销

+2.934ms

(4.552ms)

-0.167ms

(1.421ms)

-0.071ms

(1.517ms)

-0.047ms

(1.547ms)

RenderThread

开销

+0.587ms

(4.072ms)

+1.678ms

(5.163ms)

+1.771ms

(5.256ms)

+1.579ms

(5.064ms)


方案一综合开销最高,后面三个方案的doFrame耗时,与基准耗时的差异在误差范围内,几乎没有引入额外的计算。


综合来看,方案四各方面表现都很优秀,酷派在自研的COOLOS里,已经有多处采用了它。





后记



经过这样一番调研,笔者的有很多感悟和提升,其中感触最深的是:如果对一块领域感兴趣,但网络和社区没有更好解法时,就自己读源码吧。


在这个能力的调研过程中,有非常多有意思的技术问题,每一项都值得深入去探讨和研究。

比如:

  • 为什么有时候模糊边缘会闪烁?
  • 模糊运算原理是什么?模糊计算本身有没有性能优化空间?
  • 想使用酷派调研的方案,有没有办法不修改源码来用到它们?
  • ……


由于篇幅限制,本文不再展开,有机会可以开些续文,详细讲讲。

感兴趣的读者,欢迎评论区跟我们一起讨论!



参考链接

  1. Window Blurs | Android Open Source Project
  2. (https://source.android.com/devices/tech/display/window-blurs)
  3. Glide v4 : Hardware Bitmaps
  4. (https://bumptech.github.io/glide/doc/hardwarebitmaps.html#why-should-we-use-hardware-bitmaps)
  5. glCopyTexSubImage2D | khronos.org
  6. (https://www.khronos.org/registry/OpenGL-Refpages/es2.0/xhtml/glCopyTexSubImage2D.xml)
  7. RenderEffect | Android Developers
  8. (https://developer.android.com/reference/android/graphics/RenderEffect)
  9. 方案三 | Android Code Review
  10. (https://android-review.googlesource.com/c/platform/frameworks/base/+/1985086)
  11. 方案四 | Android Code Review
  12. (https://android-review.googlesource.com/c/platform/frameworks/base/+/2033223)

作者:每天都吃麦当劳

来源-微信公众号:酷派技术团队

出处:https://mp.weixin.qq.com/s/GJU2JxrjqRpyuJkrHJNC1w

砂玻璃效果已经在互联网上流行了很多年,Mac OS以其磨砂玻璃效果而闻名,Windows 10也通过其他一些灯光,深度,运动,材质,比例尺实现了磨砂玻璃的效果

在CSS中使用磨砂玻璃效果时,我们中的一些人知道该怎么做,而其他人仍会在百度搜索:

怎么做??

“ css光泽效果”
“ css毛玻璃”
“透明模糊背景css”
“毛玻璃效果photoshop”
“仅cs模糊背景”
“ css玻璃窗格”
“ css背景滤镜”
“ css模糊覆盖物”
“ css div后面的模糊背景”

今天,我将展示仅CSS的一种方法,教你可以使用该方法在CSS中进行磨砂玻璃效果。

1,创建一个HTML标记

为简单起见,我将向你展示如何在空的div上制作磨砂玻璃效果。因此,HTML中所需的只是一个空的div。

<div> </div>

2.重置浏览器默认样式

*{
 margin: 0;
 padding: 0;
}

3.添加背景图片

我们需要我们的背景占据页面的整个宽度和高度,并且我们不想重复我们的背景,我们也希望我们的背景是固定的。我们希望背景是固定的,因为我们不希望以后在继承背景时将整个背景显示在div中。

body{
 background-image: url(http://bit.ly/2gPLxZ4); //add "" if you want
 background-repeat: no-repeat;
 background-attachment: fixed;
 background-size: cover;
}

4.现在给Div一些样式

现在,我们将使用背景继承为div设置一些宽度和高度。我们还需要确定绝对位置,以确保叠加层不会占用网页的整个宽度和高度

div{
 background: inherit;
 width: 250px;
 height: 350px;
 position: absolute;
}

5,固定和不固定附件的示例

现在,我们知道在固定了背景附件的情况下,我们只能看到div后面的div内部的背景图像区域,而这正是我们希望毛玻璃效果起作用的地方

6,现在我们需要创建一个覆盖层

我们需要content: “”确保之前的伪类能够正常工作。我们还从其父级继承了背景,并且我们使用绝对位置将其在其父元素DIV中对齐。我们正在使用盒子阴影添加白色透明叠加层,并且正在使用模糊来模糊该叠加层。

div:before{
 content: “ ”;
 background: inherit; 
 position: absolute;
 left: 0;
 right: 0;
 top: 0; 
 bottom: 0;
 box-shadow: inset 0 0 0 3000px rgba(255,255,255,0.3);
 filter: blur(10px);
}

7,修复DIV的不模糊边缘

现在,我们需要修复div的未模糊边缘,为此,我们需要增加叠加层的尺寸,使其比其父尺寸稍高一些,然后将其上下位置设为负(-25)。我们还需要给它的父对象提供隐藏的溢出,以确保父DIV之外的任何覆盖都不会显示并被隐藏。

div{
 background: inherit;
 width: 250px;
 height: 350px;
 position: absolute;
 overflow: hidden;  //adding overflow hidden
}
div:before{
 content: ‘’;
 width: 300px;
 height: 400px;
 background: inherit; 
 position: absolute;
 left: -25px;  //giving minus -25px left position
 right: 0;
 top: -25px;   //giving minus -25px top position 
 bottom: 0;
 box-shadow: inset 0 0 0 200px rgba(255,255,255,0.3);
 filter: blur(10px);
}

到这里,我们就实现了CSS的磨砂玻璃效果

你还可以使用CSS属性“backdrop-filter: blur(20px)”来实现此效果,该属性在工作方面要容易得多,并且是更好的选择,但兼容性还不是很强。