diff --git a/app/src/main/java/io/legado/app/help/ReadBookConfig.kt b/app/src/main/java/io/legado/app/help/ReadBookConfig.kt index 915ea9f5a..9238c1341 100644 --- a/app/src/main/java/io/legado/app/help/ReadBookConfig.kt +++ b/app/src/main/java/io/legado/app/help/ReadBookConfig.kt @@ -1,6 +1,7 @@ package io.legado.app.help import android.graphics.Color +import android.graphics.drawable.BitmapDrawable import android.graphics.drawable.ColorDrawable import android.graphics.drawable.Drawable import io.legado.app.App @@ -52,7 +53,11 @@ object ReadBookConfig { } fun upBg() { - bg = getConfig().bgDrawable() + val resources = App.INSTANCE.resources + val dm = resources.displayMetrics + val width = dm.widthPixels + val height = dm.heightPixels + bg = getConfig().bgDrawable(width, height) } fun save() { @@ -162,20 +167,30 @@ object ReadBookConfig { else bgType } - fun bgDrawable(): Drawable { + fun bgDrawable(width: Int, height: Int): Drawable { var bgDrawable: Drawable? = null - kotlin.runCatching { - when (bgType()) { - 0 -> bgDrawable = ColorDrawable(Color.parseColor(bgStr())) - 1 -> bgDrawable = - Drawable.createFromStream( - App.INSTANCE.assets.open("bg" + File.separator + bgStr()), - "bg" + val resources = App.INSTANCE.resources + try { + bgDrawable = when (bgType()) { + 0 -> ColorDrawable(Color.parseColor(bgStr())) + 1 -> { + BitmapDrawable( + resources, + BitmapUtil.decodeBitmap( + App.INSTANCE, + "bg" + File.separator + bgStr(), + width, + height + ) ) - else -> runCatching { - bgDrawable = Drawable.createFromPath(bgStr()) } + else -> BitmapDrawable( + resources, + BitmapUtil.decodeBitmap(bgStr(), width, height) + ) } + } catch (e: Exception) { + e.printStackTrace() } return bgDrawable ?: ColorDrawable(App.INSTANCE.getCompatColor(R.color.background)) } diff --git a/app/src/main/java/io/legado/app/ui/book/read/config/ReadStyleDialog.kt b/app/src/main/java/io/legado/app/ui/book/read/config/ReadStyleDialog.kt index ea80392c8..114552bf6 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/config/ReadStyleDialog.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/config/ReadStyleDialog.kt @@ -249,7 +249,7 @@ class ReadStyleDialog : DialogFragment() { ReadBookConfig.getConfig(i).apply { when (bgType()) { 2 -> ImageLoader.load(requireContext(), bgStr()).centerCrop().setAsDrawable(iv) - else -> iv.setImageDrawable(bgDrawable()) + else -> iv.setImageDrawable(bgDrawable(100, 150)) } } } diff --git a/app/src/main/java/io/legado/app/ui/widget/page/ContentView.kt b/app/src/main/java/io/legado/app/ui/widget/page/ContentView.kt index 719129a03..228e6f2e9 100644 --- a/app/src/main/java/io/legado/app/ui/widget/page/ContentView.kt +++ b/app/src/main/java/io/legado/app/ui/widget/page/ContentView.kt @@ -10,7 +10,6 @@ import android.widget.ImageView import androidx.appcompat.widget.AppCompatImageView import io.legado.app.R import io.legado.app.constant.AppConst.TIME_FORMAT -import io.legado.app.help.ImageLoader import io.legado.app.help.ReadBookConfig import io.legado.app.utils.* import kotlinx.android.synthetic.main.view_book_page.view.* @@ -90,10 +89,7 @@ class ContentView : FrameLayout { } fun setBg(bg: Drawable?) { - //all supported - ImageLoader.load(context, bg) - .centerCrop() - .setAsDrawable(bgImage) + bgImage.background = bg } fun upTime() { diff --git a/app/src/main/java/io/legado/app/utils/BitmapUtil.kt b/app/src/main/java/io/legado/app/utils/BitmapUtil.kt new file mode 100644 index 000000000..a5c7b776f --- /dev/null +++ b/app/src/main/java/io/legado/app/utils/BitmapUtil.kt @@ -0,0 +1,252 @@ +package io.legado.app.utils + +import android.content.Context +import android.graphics.Bitmap +import android.graphics.Bitmap.Config +import android.graphics.BitmapFactory +import android.graphics.Canvas +import android.renderscript.Allocation +import android.renderscript.Element +import android.renderscript.RenderScript +import android.renderscript.ScriptIntrinsicBlur +import android.view.View +import io.legado.app.App +import java.io.IOException +import kotlin.math.ceil + + +@SuppressWarnings("unused", "WeakerAccess") +object BitmapUtil { + /** + * 从path中获取图片信息,在通过BitmapFactory.decodeFile(String path)方法将突破转成Bitmap时, + * 遇到大一些的图片,我们经常会遇到OOM(Out Of Memory)的问题。所以用到了我们上面提到的BitmapFactory.Options这个类。 + * + * @param path 文件路径 + * @param width 想要显示的图片的宽度 + * @param height 想要显示的图片的高度 + * @return + */ + fun decodeBitmap(path: String, width: Int, height: Int): Bitmap { + val op = BitmapFactory.Options() + // inJustDecodeBounds如果设置为true,仅仅返回图片实际的宽和高,宽和高是赋值给opts.outWidth,opts.outHeight; + op.inJustDecodeBounds = true + BitmapFactory.decodeFile(path, op) //获取尺寸信息 + //获取比例大小 + val wRatio = ceil((op.outWidth / width).toDouble()).toInt() + val hRatio = ceil((op.outHeight / height).toDouble()).toInt() + //如果超出指定大小,则缩小相应的比例 + if (wRatio > 1 && hRatio > 1) { + if (wRatio > hRatio) { + op.inSampleSize = wRatio + } else { + op.inSampleSize = hRatio + } + } + op.inJustDecodeBounds = false + return BitmapFactory.decodeFile(path, op) + } + + /** 从path中获取Bitmap图片 + * @param path 图片路径 + * @return + */ + + fun decodeBitmap(path: String): Bitmap { + val opts = BitmapFactory.Options() + + opts.inJustDecodeBounds = true + BitmapFactory.decodeFile(path, opts) + + opts.inSampleSize = computeSampleSize(opts, -1, 128 * 128) + + opts.inJustDecodeBounds = false + + return BitmapFactory.decodeFile(path, opts) + } + + /** + * 以最省内存的方式读取本地资源的图片 + * @param context 设备上下文 + * @param resId 资源ID + * @return + */ + fun decodeBitmap(context: Context, resId: Int): Bitmap? { + val opt = BitmapFactory.Options() + opt.inPreferredConfig = Config.RGB_565 + //获取资源图片 + val `is` = context.resources.openRawResource(resId) + return BitmapFactory.decodeStream(`is`, null, opt) + } + + /** + * @param context 设备上下文 + * @param resId 资源ID + * @param width + * @param height + * @return + */ + fun decodeBitmap(context: Context, resId: Int, width: Int, height: Int): Bitmap? { + + var inputStream = context.resources.openRawResource(resId) + + val op = BitmapFactory.Options() + // inJustDecodeBounds如果设置为true,仅仅返回图片实际的宽和高,宽和高是赋值给opts.outWidth,opts.outHeight; + op.inJustDecodeBounds = true + BitmapFactory.decodeStream(inputStream, null, op) //获取尺寸信息 + //获取比例大小 + val wRatio = ceil((op.outWidth / width).toDouble()).toInt() + val hRatio = ceil((op.outHeight / height).toDouble()).toInt() + //如果超出指定大小,则缩小相应的比例 + if (wRatio > 1 && hRatio > 1) { + if (wRatio > hRatio) { + op.inSampleSize = wRatio + } else { + op.inSampleSize = hRatio + } + } + inputStream = context.resources.openRawResource(resId) + op.inJustDecodeBounds = false + return BitmapFactory.decodeStream(inputStream, null, op) + } + + /** + * @param context 设备上下文 + * @param fileNameInAssets Assets里面文件的名称 + * @param width 图片的宽度 + * @param height 图片的高度 + * @return Bitmap + * @throws IOException + */ + @Throws(IOException::class) + fun decodeBitmap(context: Context, fileNameInAssets: String, width: Int, height: Int): Bitmap? { + + var inputStream = context.assets.open(fileNameInAssets) + val op = BitmapFactory.Options() + // inJustDecodeBounds如果设置为true,仅仅返回图片实际的宽和高,宽和高是赋值给opts.outWidth,opts.outHeight; + op.inJustDecodeBounds = true + BitmapFactory.decodeStream(inputStream, null, op) //获取尺寸信息 + //获取比例大小 + val wRatio = ceil((op.outWidth / width).toDouble()).toInt() + val hRatio = ceil((op.outHeight / height).toDouble()).toInt() + //如果超出指定大小,则缩小相应的比例 + if (wRatio > 1 && hRatio > 1) { + if (wRatio > hRatio) { + op.inSampleSize = wRatio + } else { + op.inSampleSize = hRatio + } + } + inputStream = context.assets.open(fileNameInAssets) + op.inJustDecodeBounds = false + return BitmapFactory.decodeStream(inputStream, null, op) + } + + + //图片不被压缩 + fun convertViewToBitmap(view: View, bitmapWidth: Int, bitmapHeight: Int): Bitmap { + val bitmap = Bitmap.createBitmap(bitmapWidth, bitmapHeight, Bitmap.Config.ARGB_8888) + view.draw(Canvas(bitmap)) + return bitmap + } + + + /** + * @param options + * @param minSideLength + * @param maxNumOfPixels + * @return + * 设置恰当的inSampleSize是解决该问题的关键之一。BitmapFactory.Options提供了另一个成员inJustDecodeBounds。 + * 设置inJustDecodeBounds为true后,decodeFile并不分配空间,但可计算出原始图片的长度和宽度,即opts.width和opts.height。 + * 有了这两个参数,再通过一定的算法,即可得到一个恰当的inSampleSize。 + * 查看Android源码,Android提供了下面这种动态计算的方法。 + */ + fun computeSampleSize( + options: BitmapFactory.Options, + minSideLength: Int, + maxNumOfPixels: Int + ): Int { + + val initialSize = computeInitialSampleSize(options, minSideLength, maxNumOfPixels) + + var roundedSize: Int + + if (initialSize <= 8) { + roundedSize = 1 + while (roundedSize < initialSize) { + roundedSize = roundedSize shl 1 + } + } else { + roundedSize = (initialSize + 7) / 8 * 8 + } + + return roundedSize + } + + + private fun computeInitialSampleSize( + options: BitmapFactory.Options, + minSideLength: Int, + maxNumOfPixels: Int + ): Int { + + val w = options.outWidth.toDouble() + val h = options.outHeight.toDouble() + + val lowerBound = if (maxNumOfPixels == -1) + 1 + else + Math.ceil(Math.sqrt(w * h / maxNumOfPixels)).toInt() + + val upperBound = if (minSideLength == -1) 128 else Math.min( + Math.floor(w / minSideLength), + Math.floor(h / minSideLength) + ).toInt() + + if (upperBound < lowerBound) { + // return the larger one when there is no overlapping zone. + return lowerBound + } + + return if (maxNumOfPixels == -1 && minSideLength == -1) { + 1 + } else if (minSideLength == -1) { + lowerBound + } else { + upperBound + } + } + + /** + * 高斯模糊 + */ + fun stackBlur(srcBitmap: Bitmap?): Bitmap? { + if (srcBitmap == null) return null + val rs = RenderScript.create(App.INSTANCE) + val blurredBitmap = srcBitmap.copy(Config.ARGB_8888, true) + + //分配用于渲染脚本的内存 + val input = Allocation.createFromBitmap( + rs, + blurredBitmap, + Allocation.MipmapControl.MIPMAP_FULL, + Allocation.USAGE_SHARED + ) + val output = Allocation.createTyped(rs, input.type) + + //加载我们想要使用的特定脚本的实例。 + val script = ScriptIntrinsicBlur.create(rs, Element.U8_4(rs)) + script.setInput(input) + + //设置模糊半径 + script.setRadius(8f) + + //启动 ScriptIntrinsicBlur + script.forEach(output) + + //将输出复制到模糊的位图 + output.copyTo(blurredBitmap) + + return blurredBitmap + } + +}