kotlin - 显示heic图片

kotlin - 显示heic图片

复制代码
package com.example.androidkotlindemo2.hdr

import android.graphics.BitmapFactory
import android.os.Bundle
import android.view.View
import android.widget.Button
import android.widget.ImageView
import androidx.appcompat.app.AppCompatActivity
import com.example.androidkotlindemo2.R
import com.example.androidkotlindemo2.utils.LogUtils
import java.io.File

/**
 * Author : wn
 * Email : maoning20080809@163.com
 * Date : 2025/10/26 10:17
 * Description :  Hdr图片显示
 * 参考:https://dev.vivo.com.cn/documentCenter/doc/837
 */
class HdrImageDecoderActivity : AppCompatActivity() , View.OnClickListener{


    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        setContentView(R.layout.hdr_image_decoder_main)

        findViewById<Button>(R.id.hdr_show_btn1).setOnClickListener(this)

    }

    override fun onClick(v: View?) {
        v?:return
        when(v.id){
            R.id.hdr_show_btn1 -> {
                show1()
            }
        }
    }

    private fun show1(){
        val resultImageView = findViewById<ImageView>(R.id.hdr_show_result)
        val resultImageView2 = findViewById<ImageView>(R.id.hdr_show_result2)
        val filePath = "sdcard/DCIM/Camera/IMG_20251026_111918.HEIC"
        LogUtils.i("AAA", "exist = " + File(filePath).exists())
        HeifLoader.loadHeifImage(filePath, resultImageView)


        val options = BitmapFactory.Options()
        //options.inSampleSize = 20
        val bitmap = BitmapFactory.decodeFile(filePath)
        resultImageView2.setImageBitmap(bitmap)

    }
}
复制代码
package com.example.androidkotlindemo2.hdr

import android.annotation.SuppressLint
import android.content.Context
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.graphics.ImageDecoder
import android.net.Uri
import android.os.Build
import android.util.Log
import android.util.Size
import android.widget.ImageView
import androidx.annotation.RequiresApi
import com.example.androidkotlindemo2.R
import com.example.androidkotlindemo2.utils.LogUtils
import java.io.File
import java.io.InputStream

/**
 * Author : wn
 * Email : maoning20080809@163.com
 * Date : 2025/10/26 10:22
 * Description :
 */
object HeifLoader {

    fun loadHeifImage(filePath: String, imageView: ImageView) {
        try {
            val options = BitmapFactory.Options()
            options.inJustDecodeBounds = false
            val baseBitmap = BitmapFactory.decodeFile(filePath, options)
            LogUtils.i("AAA", "baseBitmap width = " + baseBitmap.width +" , height = " + baseBitmap.height)
            val source = ImageDecoder.createSource(File(filePath))
            val drawable = ImageDecoder.decodeDrawable(source) { decoder, _, _ ->
                // 配置解码参数
                decoder.allocator = ImageDecoder.ALLOCATOR_SOFTWARE
                decoder.isMutableRequired = false
                //val size = calculateTargetSize(imageView)
                //LogUtils.i("AAA", "size width = " + size.width +" , height = " + size.height)
                decoder.setTargetSize(baseBitmap.width, baseBitmap.height)
                //decoder.setTargetSize(1080, 900)
                //decoder.setTargetSize(600, 300)
            }
            imageView.setImageDrawable(drawable)
        } catch (e: Exception) {
            handleDecodeError(imageView, e)
        }
    }

    fun loadHeifImage(context: Context, uri: Uri, imageView: ImageView) {
        if (!isHeifSupported(context, uri)) {
            handleUnsupportedFormat(imageView)
            return
        }

        when {
            Build.VERSION.SDK_INT >= Build.VERSION_CODES.P ->
                loadWithImageDecoder(context, uri, imageView)

            Build.VERSION.SDK_INT >= Build.VERSION_CODES.O ->
                loadWithBitmapFactory(context, uri, imageView)

            else -> handleUnsupportedFormat(imageView)
        }
    }

    @RequiresApi(Build.VERSION_CODES.P)
    private fun loadWithImageDecoder(context: Context, uri: Uri, imageView: ImageView) {
        try {
            val source = ImageDecoder.createSource(context.contentResolver, uri)
            val drawable = ImageDecoder.decodeDrawable(source) { decoder, _, _ ->
                // 配置解码参数
                decoder.allocator = ImageDecoder.ALLOCATOR_SOFTWARE
                decoder.isMutableRequired = false
                val size = calculateTargetSize(imageView)
                decoder.setTargetSize(size.width, size.height)
            }
            imageView.setImageDrawable(drawable)
        } catch (e: Exception) {
            handleDecodeError(imageView, e)
        }
    }

    @Suppress("DEPRECATION")
    private fun loadWithBitmapFactory(context: Context, uri: Uri, imageView: ImageView) {
        try {
            val inputStream = context.contentResolver.openInputStream(uri)
            val options = BitmapFactory.Options().apply {
                inSampleSize = calculateInSampleSize(inputStream, imageView)
                inPreferredConfig = Bitmap.Config.RGB_565
            }

            // 重新打开流
            inputStream?.close()
            val newInputStream = context.contentResolver.openInputStream(uri)

            val bitmap = BitmapFactory.decodeStream(newInputStream, null, options)
            newInputStream?.close()

            bitmap?.let { imageView.setImageBitmap(it) }
                ?: handleDecodeError(imageView, NullPointerException())
        } catch (e: Exception) {
            handleDecodeError(imageView, e)
        }
    }

    private fun calculateTargetSize(imageView: ImageView): Size {
        val width = if (imageView.width > 0) imageView.width else 1080
        val height = if (imageView.height > 0) imageView.height else 1920
        return Size(width, height)
    }

    private fun calculateInSampleSize(inputStream: InputStream?, imageView: ImageView): Int {
        if (inputStream == null) return 1

        val options = BitmapFactory.Options()
        options.inJustDecodeBounds = true
        BitmapFactory.decodeStream(inputStream, null, options)
        inputStream.close()

        val imageWidth = options.outWidth
        val imageHeight = options.outHeight
        val reqWidth = imageView.width.takeIf { it > 0 } ?: 1080
        val reqHeight = imageView.height.takeIf { it > 0 } ?: 1920

        var inSampleSize = 1

        if (imageHeight > reqHeight || imageWidth > reqWidth) {
            val halfHeight = imageHeight / 2
            val halfWidth = imageWidth / 2

            while (halfHeight / inSampleSize >= reqHeight
                && halfWidth / inSampleSize >= reqWidth) {
                inSampleSize *= 2
            }
        }

        return inSampleSize
    }

    private fun handleUnsupportedFormat(imageView: ImageView) {
        imageView.setImageResource(R.drawable.ic_launcher_background)
        imageView.contentDescription = "HEIF format not supported"
    }

    private fun handleDecodeError(imageView: ImageView, e: Exception) {
        Log.e("HeifLoader", "HEIF decode error", e)
        imageView.setImageResource(R.drawable.ic_launcher_background)
        imageView.contentDescription = "Error loading HEIF image"
    }


    fun isHeifSupported(context: Context, uri: Uri): Boolean {
        // 基础系统版本检测
        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) return false

        // 检查MIME类型是否为HEIF格式
        val mimeType = context.contentResolver.getType(uri)
        val isHeif = mimeType == "image/heic" ||
                mimeType == "image/heif" ||
                uri.toString().endsWith(".heic", ignoreCase = true) ||
                uri.toString().endsWith(".heif", ignoreCase = true)

        if (!isHeif) return false

        // Android 9+ 完全支持
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) return true

        // Android 8.x 需要额外检查
        return isHeifSupportedOnOreo(context, uri)
    }

    @SuppressLint("NewApi")
    private fun isHeifSupportedOnOreo(context: Context, uri: Uri): Boolean {
        // 尝试解码HEIF图片以验证支持性
        return try {
            val input = context.contentResolver.openInputStream(uri)
            BitmapFactory.decodeStream(input).run {
                input?.close()
                this != null
            }
        } catch (e: Exception) {
            false
        }
    }
}
复制代码
hdr_image_decoder_main.xml布局
复制代码
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    xmlns:tools="http://schemas.android.com/tools"
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:orientation="vertical"
    tools:context=".MainActivity">


    <androidx.appcompat.widget.AppCompatButton
        android:id="@+id/hdr_show_btn1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="10dp"
        android:layout_gravity="center_horizontal"
        android:textAllCaps="false"
        android:textSize="30sp"
        android:text="显示heic图片"/>

    <androidx.appcompat.widget.AppCompatImageView
        android:id="@+id/hdr_show_result"
        android:layout_width="match_parent"
        android:scaleType="centerCrop"
        android:layout_height="300dp"/>

    <androidx.appcompat.widget.AppCompatImageView
        android:id="@+id/hdr_show_result2"
        android:layout_width="match_parent"
        android:layout_height="300dp"/>

</LinearLayout>
相关推荐
骑着蜗牛撵大象3271 分钟前
MySQL 8.0 迁移后 SQL 变慢:执行计划里 3 类被忽略的成本差异
android·数据库·sql·mysql·优化器·执行计划
我叫张土豆32 分钟前
Android 14+ 模拟器抓包实录:把 Burp CA 装成系统 CA,让 targetSdk≥34 的 App 乖乖解密
android
Nerd(BUG版)2 小时前
KMP学习day4--Kotlin空安全与泛型
学习·安全·kotlin
booboosui4 小时前
Android 悬浮窗藏不住自己:一个标志位与 140 毫秒
android
传奇开心果4 小时前
【Flutter踩坑排雷】第5课:扩展方法冲突坑|同名扩展引发编译歧义,故障规避手段
android·flutter
.痞老板.7 小时前
mysql初阶--数据类型
android·数据库·mysql
StarsGreen7 小时前
Android 启动流程详解
android·linux·单片机·嵌入式硬件
Nerd(BUG版)7 小时前
KMP学习day5--Kotlin Flow异步流基础
开发语言·学习·kotlin
booboosui1 天前
Android 上来电怎么判:7 个条件加 5 个中文词
android
MardaWang1 天前
App 内嵌 H5 的信任边界设计(ArkWeb/HarmonyOS 实战)
android