diff --git a/app/build.gradle b/app/build.gradle index aa7de1186..76cdd3541 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -220,6 +220,11 @@ dependencies { implementation("com.github.bumptech.glide:glide:$glideVersion") kapt("com.github.bumptech.glide:compiler:$glideVersion") + //Svg + implementation("com.caverock:androidsvg-aar:1.4") + //Glide svg plugin + implementation("com.github.qoqa:glide-svg:4.0.2") + //webServer def nanoHttpdVersion = "2.3.1" implementation("org.nanohttpd:nanohttpd:$nanoHttpdVersion") diff --git a/app/src/main/java/io/legado/app/ui/book/read/page/provider/ImageProvider.kt b/app/src/main/java/io/legado/app/ui/book/read/page/provider/ImageProvider.kt index 1de401ac7..27a541345 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/page/provider/ImageProvider.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/page/provider/ImageProvider.kt @@ -17,6 +17,7 @@ import io.legado.app.help.coroutine.Coroutine import io.legado.app.model.ReadBook import io.legado.app.model.localBook.EpubFile import io.legado.app.utils.BitmapUtils +import io.legado.app.utils.SvgUtils import io.legado.app.utils.FileUtils import io.legado.app.utils.toastOnUi import kotlinx.coroutines.Dispatchers.IO @@ -55,8 +56,8 @@ object ImageProvider { if (oldBitmap != errorBitmap) { oldBitmap.recycle() triggerRecycled = true - putDebug("ImageProvider: trigger bitmap recycle. URI: $filePath") - putDebug("ImageProvider : cacheUsage ${size()}bytes / ${maxSize()}bytes") + //putDebug("ImageProvider: trigger bitmap recycle. URI: $filePath") + //putDebug("ImageProvider : cacheUsage ${size()}bytes / ${maxSize()}bytes") } } } @@ -97,15 +98,16 @@ object ImageProvider { bookSource: BookSource? ): Size { val file = cacheImage(book, src, bookSource) + //svg size + val size = SvgUtils.getSize(file.absolutePath) + if (size != null) return size val op = BitmapFactory.Options() // inJustDecodeBounds如果设置为true,仅仅返回图片实际的宽和高,宽和高是赋值给opts.outWidth,opts.outHeight; op.inJustDecodeBounds = true BitmapFactory.decodeFile(file.absolutePath, op) if (op.outWidth < 1 && op.outHeight < 1) { - Coroutine.async { - putDebug("ImageProvider: delete file due to image size ${op.outHeight}*${op.outWidth}. path: ${file.absolutePath}") - file.delete() - } + putDebug("ImageProvider: $src Unsupported image type") + //file.delete() 重复下载 return Size(errorBitmap.width, errorBitmap.height) } return Size(op.outWidth, op.outHeight) @@ -135,6 +137,7 @@ object ImageProvider { if (height != null && AppConfig.asyncLoadImage && ReadBook.pageAnim() == PageAnim.scrollPageAnim) { Coroutine.async { val bitmap = BitmapUtils.decodeBitmap(vFile.absolutePath, width, height) + ?: SvgUtils.createBitmap(vFile.absolutePath, width, height) ?: throw NoStackTraceException(appCtx.getString(R.string.error_decode_bitmap)) withContext(Main) { bitmapLruCache.put(vFile.absolutePath, bitmap) @@ -142,10 +145,6 @@ object ImageProvider { }.onError { //错误图片占位,防止重复获取 bitmapLruCache.put(vFile.absolutePath, errorBitmap) - putDebug( - "ImageProvider: decode bitmap failed. path: ${vFile.absolutePath}\n$it", - it - ) }.onFinally { block?.invoke() } @@ -154,16 +153,13 @@ object ImageProvider { @Suppress("BlockingMethodInNonBlockingContext") return kotlin.runCatching { val bitmap = BitmapUtils.decodeBitmap(vFile.absolutePath, width, height) + ?: SvgUtils.createBitmap(vFile.absolutePath, width, height) ?: throw NoStackTraceException(appCtx.getString(R.string.error_decode_bitmap)) bitmapLruCache.put(vFile.absolutePath, bitmap) bitmap }.onFailure { //错误图片占位,防止重复获取 bitmapLruCache.put(vFile.absolutePath, errorBitmap) - putDebug( - "ImageProvider: decode bitmap failed. path: ${vFile.absolutePath}\n$it", - it - ) }.getOrDefault(errorBitmap) } diff --git a/app/src/main/java/io/legado/app/utils/SvgUtils.kt b/app/src/main/java/io/legado/app/utils/SvgUtils.kt new file mode 100644 index 000000000..00cf873ca --- /dev/null +++ b/app/src/main/java/io/legado/app/utils/SvgUtils.kt @@ -0,0 +1,77 @@ +package io.legado.app.utils + +import android.graphics.Canvas +import android.graphics.Bitmap +import android.graphics.drawable.PictureDrawable +import android.util.Size +import java.io.FileInputStream +import java.io.InputStream +import com.caverock.androidsvg.SVG +import com.caverock.androidsvg.PreserveAspectRatio +import com.caverock.androidsvg.SVGParseException +import kotlin.math.max + +@Suppress("WeakerAccess", "MemberVisibilityCanBePrivate") +object SvgUtils { + + /** + * 从Svg中解码bitmap + * https://github.com/qoqa/glide-svg/blob/master/library/src/main/java/ch/qoqa/glide/svg/SvgBitmapTranscoder.kt + */ + + fun createBitmap(filePath: String, width: Int, height: Int? = null): Bitmap? { + val inputStream = FileInputStream(filePath) + return createBitmap(inputStream, width, height) + } + + fun createBitmap(inputStream: InputStream, width: Int, height: Int? = null): Bitmap? { + return kotlin.runCatching { + val svg = SVG.getFromInputStream(inputStream) + createBitmap(svg, width, height) + }.getOrNull() + } + + //获取svg图片大小 + fun getSize(filePath: String): Size? { + val inputStream = FileInputStream(filePath) + return getSize(inputStream) + } + + fun getSize(inputStream: InputStream): Size? { + return kotlin.runCatching { + val svg = SVG.getFromInputStream(inputStream) + getSize(svg) + }.getOrNull() + } + + /////// private method + private fun createBitmap(svg: SVG, width: Int, height: Int? = null): Bitmap { + val size = getSize(svg) + val wRatio = width.let { size.width / it } ?: -1 + val hRatio = height?.let { size.height / it } ?: -1 + //如果超出指定大小,则缩小相应的比例 + val ratio = when { + wRatio > 1 && hRatio > 1 -> max(wRatio, hRatio) + wRatio > 1 -> wRatio + hRatio > 1 -> hRatio + else -> 1 + } + svg.documentPreserveAspectRatio = PreserveAspectRatio.START + val picture = svg.renderToPicture(size.width / ratio, size.height / ratio) + val drawable = PictureDrawable(picture) + + val bitmap = Bitmap.createBitmap(size.width / ratio, size.height / ratio, Bitmap.Config.ARGB_8888) + val canvas = Canvas(bitmap) + canvas.drawPicture(drawable.picture) + return bitmap + } + + private fun getSize(svg: SVG): Size { + val width = svg.documentWidth.toInt().takeIf { it > 0 } + ?: (svg.documentViewBox.right - svg.documentViewBox.left).toInt() + val height = svg.documentHeight.toInt().takeIf { it > 0 } + ?: (svg.documentViewBox.bottom - svg.documentViewBox.top).toInt() + return Size(width, height) + } + +}