[安卓]实现苹果实现的效果之 流光溢彩背景

前言

如你所见,本专栏的名称叫做《把苹果抄的裤衩子都不剩》
目的就是实现applemusic中一些炫酷的效果。
这篇博文写于2021-12-17。本文发布时(2022-3-9),AppleMusic已经实现了更真实的流动效果。

正文

老模板,上代码之前,先给大家放一下我实现的效果。
由于AppleMusic在最近的版本中已经加强了效果,我就不上它的演示了。
请添加图片描述

思路

刚看到肯定是一点思路都没有的,甚至不知道这个效果应该叫什么名字(有人可能会问:不是已经知道它叫流光溢彩了吗?别着急,我们往下看)。
既然没有思路,那我们可以通过某些手段看看它是如何实现的。
身为一个合格的安卓程序员,开发和逆向必须都 了如指掌 的。
打开jadX,拖入AppleMusic的安装包,打开一看发现它的类名被混淆的亲妈都不认识了。遇到这种情况,肯定是选择 直接放弃 百度了~
于是我翻遍了github和gitee,终于发现了一个具有同样效果的音乐播放器软件<椒*音乐>
就是因为这个播放器把背景效果叫做流光溢彩,所有才有了这篇文章的命名。
经过漫长的源码定位后,发现了一些蛛丝马迹,椒*播放器中存在一个类,内部有一个Log,它的TAG设置的是"FlowingLightView",翻译过来正好是流光溢彩。版权原因,我不贴出相关的代码,我只介绍实现的方式。
实际上就是把一张图片切分成几个部分,然后对他进行降低分辨率,再提高分辨率的处理,最后让这些图片在canvas上做无规律旋转。为了避免重复感和撕裂感,还会对边缘进行混色处理,并且做一次随机的mesh。
这种写法会创建多个bitmap对象,稍稍了解过安卓开发的朋友都知道bitmap是很吃内存的,操作起来也很废性能。所以我实现的时候,选择了简化某些操作,但还让它的观感和AppleMusic相同。

实战

根据上述的分析,我们可以把过程分为以下几步:

  1. 分割图片
  2. 将图片缩小
  3. mesh处理,色调处理
  4. 将图片放大
  5. 高斯模糊
  6. 让图片旋转

我再把过程修改一下:

  1. 将图片缩小
  2. 高斯模糊
  3. mesh处理
  4. 将图片放大
  5. mesh处理
  6. 将图片放大
  7. 高斯模糊
  8. 处理色调

图片缩小是为了让后面高斯模糊影响更大的区域。
mesh和色调处理是为了去掉重复感和撕裂感。
图片放大后再次模糊是为了去除放大后颜色落差过大形成的波纹。


实现这些效果,我们可以撰写一个处理bitmap的工具类。得益于kotlin强大的 拓展函数 ,我们可以把功能直接加在Bitmap类中

fun Bitmap.zoom(newHeight: Float, newWidth: Float): Bitmap {
    val matrix = Matrix()
    val scaleWidth = newWidth / width
    val scaleHeight = newHeight / height
    matrix.postScale(scaleWidth, scaleHeight)
    return Bitmap.createBitmap(
        this, 0, 0, width,
        height, matrix, true
    )
}

fun Bitmap.blur(context: Context, radius: Float, ty: Float): Bitmap {
    val bitmap = Bitmap.createScaledBitmap(
        this,
        (width / ty).toInt(), (height / ty).toInt(), false
    ) //先缩放图片,增加模糊速度
    val rs = RenderScript.create(context)
    val input = Allocation.createFromBitmap(
        rs, bitmap, Allocation.MipmapControl.MIPMAP_NONE,
        Allocation.USAGE_SCRIPT
    )
    val output = Allocation.createTyped(rs, input.type)
    val script = ScriptIntrinsicBlur.create(rs, Element.U8_4(rs))
    script.setRadius(25F.coerceAtLeast(radius))
    script.setInput(input)
    script.forEach(output)
    output.copyTo(bitmap)
    rs.destroy()
    return bitmap
}

fun Bitmap.brightness(): Float {
    val bmp = zoom(3F, 3F) //转3*3大小的位图
    val pixel = bmp.getPixel(1, 1) //取中间位置的像素
    val r = (pixel shr 16 and 0xff) / 255.0f
    val g = (pixel shr 8 and 0xff) / 255.0f
    val b = (pixel and 0xff) / 255.0f
    return 0.299f * r + 0.587f * g + 0.114f * b //计算灰阶
}

fun Bitmap.drawColor(color: Int): Bitmap {
    val newBit = Bitmap.createBitmap(this)
    val canvas = Canvas(newBit)
    canvas.drawColor(color)
    return newBit
}
fun Bitmap.handleImageEffect(saturation: Float): Bitmap {
    val saturationMatrix = ColorMatrix()
    saturationMatrix.setSaturation(saturation)
    val imageMatrix = ColorMatrix()
    imageMatrix.postConcat(saturationMatrix)
    val paint = Paint()
    paint.colorFilter = ColorMatrixColorFilter(imageMatrix)
    val bitmap = Bitmap.createBitmap(width,height, Bitmap.Config.ARGB_8888)
    val canvas = Canvas(bitmap)
    canvas.drawBitmap(this, 0F, 0F, paint)
    return bitmap
}
fun Bitmap.mesh(floats: FloatArray): Bitmap {
    val fArr2 = FloatArray(72)
    var i = 0
    while (i <= 5) {
        var i2 = 0
        var i3 = 5
        while (i2 <= i3) {
            val i4 = i * 12 + i2 * 2
            val i5 = i4 + 1
            fArr2[i4] = floats[i4] * width.toFloat()
            fArr2[i5] = floats[i5] * height.toFloat()
            i2++
            i3 = 5
        }
        i++
    }
    val newBit = Bitmap.createBitmap(this)
    val canvas = Canvas(newBit)
    canvas.drawBitmapMesh(newBit, 5, 5, fArr2, 0, null, 0, null)
    return newBit
}

然后我们实现对图片的链式处理

fun processBitmap(bitmap: Bitmap):Bitmap{
//这个mesh的参数是偷的AppleMusic中的一个 
//AppleMusic中有5个Mesh参数,每次随机使用一个
        val floats = floatArrayOf(-0.2351f, -0.0967f, 0.2135f, -0.1414f, 0.9221f, -0.0908f, 0.9221f, -0.0685f, 1.3027f, 0.0253f, 1.2351f, 0.1786f, -0.3768f, 0.1851f, 0.2f, 0.2f, 0.6615f, 0.3146f, 0.9543f, 0.0f, 0.6969f, 0.1911f, 1.0f, 0.2f, 0.0f, 0.4f, 0.2f, 0.4f, 0.0776f, 0.2318f, 0.6f, 0.4f, 0.6615f, 0.3851f, 1.0f, 0.4f, 0.0f, 0.6f, 0.1291f, 0.6f, 0.4f, 0.6f, 0.4f, 0.4304f, 0.4264f, 0.5792f, 1.2029f, 0.8188f, -0.1192f, 1.0f, 0.6f, 0.8f, 0.4264f, 0.8104f, 0.6f, 0.8f, 0.8f, 0.8f, 1.0f, 0.8f, 0.0f, 1.0f, 0.0776f, 1.0283f, 0.4f, 1.0f, 0.6f, 1.0f, 0.8f, 1.0f, 1.1868f, 1.0283f)
        val tmp = bitmap.zoom(150f, (bitmap.getHeight() * 150 / bitmap.getWidth()).toFloat())
            .blur(context, 25F, 1F)
            .mesh(floats)
            .zoom(1000F, 1000F)
            .mesh(floats)
            .blur(context, 12F, 1F)
            .handleImageEffect(1.8f)
        val float = tmp.brightness()
        return when {
            float > 0.8 -> 
                tmp.drawColor(Color.parseColor("#50000000"))
            float < 0.2 -> 
                tmp.drawColor(Color.parseColor("#50FFFFFF"))
            else -> 
                tmp
        }
    }

其实不需要用旋转的效果,只要让这个处理后的Bitmap动起来就可以了。
所以我选择交给开源库KenBurnsView

新建一个类FlowingLightView

package simon.music.widget

import android.content.Context
import android.graphics.Bitmap
import android.graphics.Color
import android.util.AttributeSet
import com.flaviofaria.kenburnsview.KenBurnsView
import simon.tool.*
import com.flaviofaria.kenburnsview.RandomTransitionGenerator

/**
 * 如果这段代码能够正常工作,那么请记住作者是Simon。
 * 如果不能正常工作,那我也不知道是谁写的。
 */
class FlowingLightView @JvmOverloads constructor(
    context: Context?,
    attrs: AttributeSet? = null,
    defStyle: Int = 0
) : KenBurnsView(context, attrs, defStyle) {
    init {
        val randomTransitionGenerator = RandomTransitionGenerator()
        randomTransitionGenerator.setTransitionDuration(3400)
        setTransitionGenerator(randomTransitionGenerator)
    }
    fun setFlowingLight(bitmap: Bitmap) {
        val floats = floatArrayOf(-0.2351f, -0.0967f, 0.2135f, -0.1414f, 0.9221f, -0.0908f, 0.9221f, -0.0685f, 1.3027f, 0.0253f, 1.2351f, 0.1786f, -0.3768f, 0.1851f, 0.2f, 0.2f, 0.6615f, 0.3146f, 0.9543f, 0.0f, 0.6969f, 0.1911f, 1.0f, 0.2f, 0.0f, 0.4f, 0.2f, 0.4f, 0.0776f, 0.2318f, 0.6f, 0.4f, 0.6615f, 0.3851f, 1.0f, 0.4f, 0.0f, 0.6f, 0.1291f, 0.6f, 0.4f, 0.6f, 0.4f, 0.4304f, 0.4264f, 0.5792f, 1.2029f, 0.8188f, -0.1192f, 1.0f, 0.6f, 0.8f, 0.4264f, 0.8104f, 0.6f, 0.8f, 0.8f, 0.8f, 1.0f, 0.8f, 0.0f, 1.0f, 0.0776f, 1.0283f, 0.4f, 1.0f, 0.6f, 1.0f, 0.8f, 1.0f, 1.1868f, 1.0283f)
        var tmp = bitmap.zoom(150f, (bitmap.getHeight() * 150 / bitmap.getWidth()).toFloat())
            .blur(context, 25F, 1F)
            .mesh(floats)
            .zoom(1000F, 1000F)
            .mesh(floats)
            .blur(context, 12F, 1F)
            .handleImageEffect(1.8f)
        val float = tmp.brightness()
        when {
            float > 0.8 -> {//判断图片大体颜色是深色还是浅色
                tmp = tmp.drawColor(Color.parseColor("#50000000"))
                setImageBitmap(tmp)//浅色就加入黑色遮罩
            }
            float < 0.2 -> {
                tmp = tmp.drawColor(Color.parseColor("#50FFFFFF"))
                setImageBitmap(tmp)//深色就加入白色遮罩
            }
            else -> {
                setImageBitmap(tmp)
            }
        }
    }
}

这样,通过最简单的方式,实现了和AppleMusic可以媲美的效果。
而且内存占用远比AppleMusic低!

后话

已经在AppleMusic中找到了对应的实现,发现椒*音乐的实现有些相似啊,居然函数名字/变量作用 都一样。咋回事也不用我多说了吧。

22年-6月-22日,椒*音乐开发者主动联系我,证实了我的猜想。他说他已经改用了新的方式。嗯,就这样。

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

有头发的琦玉

打点钱,我会再努力的

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值