详细介绍:kotlin - 显示HDR图(heic格式),使用GainMap算法,速度从5秒提升到0.6秒

news/2025/12/3 20:27:31/文章来源:https://www.cnblogs.com/yangykaifa/p/19303878

kotlin - 显示HDR图(heic格式),使用GainMap算法,速度从5秒提升到0.6秒

class HdrImageDecoderActivity : AppCompatActivity() , View.OnClickListener{override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)setContentView(R.layout.hdr_image_decoder_main)findViewById

package com.example.androidkotlindemo2.hdr;/*** Author : wn* Email : maoning20080809@163.com* Date : 2025/11/1 14:47* Description :*/import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.util.DisplayMetrics;
import android.util.Log;import androidx.exifinterface.media.ExifInterface;import com.example.androidkotlindemo2.utils.LogUtils;public class GainMapDecoder {private static final String TAG = "GainMapDecoder";public static class GainMapResult {public Bitmap baseImage;public Bitmap gainMap;public float gamma;public float hdrCapacityMin;public float hdrCapacityMax;public float offsetSdr;public float offsetHdr;public GainMapResult() {// 默认值this.gamma = 1.0f;this.hdrCapacityMin = 0.0f;this.hdrCapacityMax = 1.0f;this.offsetSdr = 0.0f;this.offsetHdr = 0.0f;}}public static GainMapResult decodeGainMap(String imagePath) {GainMapResult result = new GainMapResult();try {// 第一步:获取图片尺寸而不加载到内存BitmapFactory.Options sizeOptions = new BitmapFactory.Options();sizeOptions.inJustDecodeBounds = true;BitmapFactory.decodeFile(imagePath, sizeOptions);int imageWidth = sizeOptions.outWidth;int imageHeight = sizeOptions.outHeight;// 第二步:根据屏幕宽度计算合适的采样率DisplayMetrics displayMetrics = Resources.getSystem().getDisplayMetrics();int screenWidth = displayMetrics.widthPixels ;// 计算采样率,确保解码后的图片宽度接近屏幕宽度int inSampleSize = calculateInSampleSize(imageWidth , screenWidth);//int inSampleSize = 2;LogUtils.Companion.e("BBB", "decodeGainMap imageWidth = " + imageWidth +" , imageHeight = " + imageHeight +" , inSampleSize = " + inSampleSize +" ,screenWidth = " + screenWidth);// 第三步:使用计算出的采样率解码图片BitmapFactory.Options decodeOptions = new BitmapFactory.Options();decodeOptions.inPreferredConfig = Bitmap.Config.ARGB_8888;decodeOptions.inSampleSize = inSampleSize;result.baseImage = BitmapFactory.decodeFile(imagePath, decodeOptions);LogUtils.Companion.e("BBB", "decodeGainMap result imageWidth = " + result.baseImage.getWidth() +" , imageHeight = " + result.baseImage.getHeight());if (result.baseImage == null) {Log.e(TAG, "Failed to decode base image");return null;}// 尝试从EXIF数据读取GainMap信息ExifInterface exif = new ExifInterface(imagePath);// 检查是否存在GainMap相关标签boolean hasGainMap = hasGainMapMetadata(exif);if (hasGainMap || true) {Log.d(TAG, "GainMap metadata found, attempting to decode...");result.gainMap = extractGainMapFromExif(exif);readGainMapParameters(exif, result);} else {Log.d(TAG, "No GainMap metadata found, using base image only");}} catch (Exception e) {Log.e(TAG, "Error decoding GainMap: " + e.getMessage(), e);// 出错时至少返回基础图像if (result.baseImage == null) {try {result.baseImage = BitmapFactory.decodeFile(imagePath);} catch (Exception ex) {Log.e(TAG, "Failed to decode base image as fallback", ex);}}}return result;}/*** 计算合适的采样率* @param imageWidth 图片原始宽度* @param targetWidth 目标宽度(屏幕宽度)* @return 采样率,总是2的幂次方*/private static int calculateInSampleSize(int imageWidth, int targetWidth) {int inSampleSize = 1;if (imageWidth > targetWidth) {// 计算理论采样率float ratio = (float) imageWidth / targetWidth;inSampleSize = Math.round(ratio);// 确保采样率是2的幂次方(BitmapFactory的要求)inSampleSize = roundToPowerOfTwo(inSampleSize);}// 设置最小和最大采样率限制inSampleSize = Math.max(1, inSampleSize);inSampleSize = Math.min(16, inSampleSize); // 防止采样率过大return inSampleSize;}/*** 将数值向上取整到最近的2的幂次方* 例如:3→4, 5→8, 9→16*/private static int roundToPowerOfTwo(int value) {int power = 1;while (power < value) {power *= 2;}return power;}private static boolean hasGainMapMetadata(ExifInterface exif) {// 检查常见的GainMap相关EXIF标签String makerNote = exif.getAttribute(ExifInterface.TAG_MAKER_NOTE);String userComment = exif.getAttribute(ExifInterface.TAG_USER_COMMENT);LogUtils.Companion.e("BBB", "hasGainMapMetadata makerNote = " + makerNote +" , userComment = " + userComment);return (makerNote != null && makerNote.contains("GainMap")) ||(userComment != null && userComment.contains("GainMap")) ||exif.getAttribute("GainMapVersion") != null;}private static Bitmap extractGainMapFromExif(ExifInterface exif) {try {// 尝试从MakerNote或其他EXIF字段提取GainMap数据byte[] gainMapData = exif.getThumbnail();if (gainMapData != null && gainMapData.length > 0) {return BitmapFactory.decodeByteArray(gainMapData, 0, gainMapData.length);}} catch (Exception e) {Log.e(TAG, "Failed to extract GainMap from EXIF", e);}return null;}private static void readGainMapParameters(ExifInterface exif, GainMapResult result) {try {// 读取GainMap参数String gamma = exif.getAttribute("GainMapGamma");String hdrMin = exif.getAttribute("GainMapHDRMin");String hdrMax = exif.getAttribute("GainMapHDRMax");String offsetSdr = exif.getAttribute("GainMapOffsetSDR");String offsetHdr = exif.getAttribute("GainMapOffsetHDR");if (gamma != null) result.gamma = Float.parseFloat(gamma);if (hdrMin != null) result.hdrCapacityMin = Float.parseFloat(hdrMin);if (hdrMax != null) result.hdrCapacityMax = Float.parseFloat(hdrMax);if (offsetSdr != null) result.offsetSdr = Float.parseFloat(offsetSdr);if (offsetHdr != null) result.offsetHdr = Float.parseFloat(offsetHdr);} catch (Exception e) {Log.e(TAG, "Error reading GainMap parameters", e);}}}

package com.example.androidkotlindemo2.hdr;import android.graphics.Bitmap;import com.example.androidkotlindemo2.utils.LogUtils;/*** Author : wn* Email : maoning20080809@163.com* Date : 2025/11/1 14:43* Description :*/public class GainMapProcessor {private static final String TAG = "GainMapProcessor";/*** 应用GainMap算法 - 优化曝光版本*/public static Bitmap applyGainMapAlgorithm(Bitmap baseImage, Bitmap gainMap,float gamma, float hdrCapacityMin,float hdrCapacityMax, float offsetSdr,float offsetHdr) {if (baseImage == null) return null;if (gainMap == null) return baseImage;int width = baseImage.getWidth();int height = baseImage.getHeight();LogUtils.Companion.d("BBB", "applyGainMapAlgorithm " + baseImage.getWidth() +" , " + baseImage.getHeight() +" , " + gainMap.getWidth() +" , " + gainMap.getHeight());// 调整GainMap尺寸以匹配基础图像Bitmap scaledGainMap = Bitmap.createScaledBitmap(gainMap, width, height, true);// 创建结果BitmapBitmap result = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);// 应用GainMap算法applyGainMapPixelsOptimized(baseImage, scaledGainMap, result,gamma, hdrCapacityMin, hdrCapacityMax,offsetSdr, offsetHdr);if (scaledGainMap != gainMap) {scaledGainMap.recycle();}return result;}private static void applyGainMapPixelsOptimized(Bitmap baseImage, Bitmap gainMap,Bitmap result, float gamma,float hdrCapacityMin, float hdrCapacityMax,float offsetSdr, float offsetHdr) {int width = baseImage.getWidth();int height = baseImage.getHeight();int[] basePixels = new int[width * height];int[] gainPixels = new int[width * height];int[] resultPixels = new int[width * height];// 获取像素数据baseImage.getPixels(basePixels, 0, width, 0, 0, width, height);gainMap.getPixels(gainPixels, 0, width, 0, 0, width, height);// 直接复制基础图像到结果,避免暗像素的额外处理System.arraycopy(basePixels, 0, resultPixels, 0, basePixels.length);// 关键修复:analyzeBrightness 只调用一次!float[] brightnessStats = analyzeBrightness(basePixels, width, height);int brightCount = 0;// 处理每个像素for (int i = 0; i < basePixels.length; i++) {int basePixel = basePixels[i];// 快速亮度计算,避免函数调用开销int r = (basePixel >> 16) & 0xFF;int g = (basePixel >> 8) & 0xFF;int b = basePixel & 0xFF;// 快速亮度近似计算float pixelBrightness = (r * 0.299f + g * 0.587f + b * 0.114f) / 255.0f;// 提高亮度阈值,只处理真正的高光区域if (pixelBrightness < 0.85f) { // 从0.8提高到0.85continue;}brightCount++;int gainPixel = gainPixels[i];// 提取RGB分量float baseR = r / 255.0f;float baseG = g / 255.0f;float baseB = b / 255.0f;float gainR = ((gainPixel >> 16) & 0xFF) / 255.0f;float gainG = ((gainPixel >> 8) & 0xFF) / 255.0f;float gainB = (gainPixel & 0xFF) / 255.0f;// 应用自适应GainMap算法float[] hdrColor = applyAdaptiveGainMap(baseR, baseG, baseB,gainR, gainG, gainB,pixelBrightness, brightnessStats,gamma, hdrCapacityMin, hdrCapacityMax,offsetSdr, offsetHdr);// 转换回ARGB并钳制int resultR = Math.max(0, Math.min(255, (int)(hdrColor[0] * 255)));int resultG = Math.max(0, Math.min(255, (int)(hdrColor[1] * 255)));int resultB = Math.max(0, Math.min(255, (int)(hdrColor[2] * 255)));resultPixels[i] = (basePixel & 0xFF000000) | (resultR << 16) | (resultG << 8) | resultB;}LogUtils.Companion.d("BBB", "brightCount = " + brightCount);result.setPixels(resultPixels, 0, width, 0, 0, width, height);}/*** 分析图像亮度统计信息*/private static float[] analyzeBrightness(int[] pixels, int width, int height) {float totalBrightness = 0;float minBrightness = 1.0f;float maxBrightness = 0.0f;int sampleCount = 0;// 采样分析亮度for (int i = 0; i < pixels.length; i += 4) {int pixel = pixels[i];float r = ((pixel >> 16) & 0xFF) / 255.0f;float g = ((pixel >> 8) & 0xFF) / 255.0f;float b = (pixel & 0xFF) / 255.0f;float brightness = calculateLuminance(r, g, b);totalBrightness += brightness;minBrightness = Math.min(minBrightness, brightness);maxBrightness = Math.max(maxBrightness, brightness);sampleCount++;}float avgBrightness = totalBrightness / sampleCount;return new float[] {avgBrightness,    // 平均亮度minBrightness,    // 最小亮度maxBrightness,    // 最大亮度maxBrightness - minBrightness // 亮度范围};}/*** 自适应GainMap应用算法 - 优化曝光*/private static float[] applyAdaptiveGainMap(float baseR, float baseG, float baseB,float gainR, float gainG, float gainB,float pixelBrightness, float[] brightnessStats,float gamma, float hdrCapacityMin,float hdrCapacityMax, float offsetSdr,float offsetHdr) {// 1. 对基础图像进行伽马解码float[] linearBase = srgbToLinear(baseR, baseG, baseB);// 2. 计算基础增益值 - 使用更保守的增益计算float baseGain = calculateConservativeGainFromGainMap(gainR, gainG, gainB, gamma,hdrCapacityMin, hdrCapacityMax);// 3. 根据像素亮度自适应调整增益 - 使用更保守的策略float adaptiveGain = calculateConservativeAdaptiveGain(baseGain, pixelBrightness, brightnessStats);// 4. 应用增益 - 使用更保守的增益应用float[] hdrLinear = applyConservativeGainToLinear(linearBase, adaptiveGain,pixelBrightness, offsetSdr, offsetHdr);// 5. 伽马编码回sRGB空间return linearToSrgb(hdrLinear[0], hdrLinear[1], hdrLinear[2]);}/*** 保守的增益计算 - 大幅降低增益强度*/private static float calculateConservativeGainFromGainMap(float gainR, float gainG, float gainB,float gamma, float hdrCapacityMin,float hdrCapacityMax) {// 使用增益图的亮度信息float gainLuminance = calculateLuminance(gainR, gainG, gainB);// 对增益图进行伽马解码float decodedGain = (float) Math.pow(gainLuminance, 1.0 / gamma);// 归一化处理 - 使用更窄的范围float normalizedGain = (decodedGain - hdrCapacityMin) / (hdrCapacityMax - hdrCapacityMin);normalizedGain = Math.max(0.1f, Math.min(0.8f, normalizedGain)); // 上限从1.0降到0.8// 转换为线性增益值 - 大幅降低增益范围return 1.0f + normalizedGain * 0.6f; // 增益范围:1.0x - 1.6x (原来是1.0x - 2.5x)}/*** 保守的自适应增益调整*/private static float calculateConservativeAdaptiveGain(float baseGain, float pixelBrightness,float[] brightnessStats) {float avgBrightness = brightnessStats[0];float minBrightness = brightnessStats[1];float maxBrightness = brightnessStats[2];float brightnessRange = brightnessStats[3];// 更保守的自适应因子float adaptiveFactor;if (pixelBrightness < 0.3f) {// 暗部区域:轻微增加增益adaptiveFactor = 1.0f + (0.3f - pixelBrightness) * 0.3f; // 从1.5降到0.3} else if (pixelBrightness > 0.7f) {// 高光区域:显著降低增益adaptiveFactor = 0.7f + (1.0f - pixelBrightness) * 0.1f; // 从0.2降到0.1} else {// 中间调:基本不调整adaptiveFactor = 1.0f;}// 根据图像整体亮度进一步调整 - 更保守float imageBrightnessFactor;if (avgBrightness < 0.3f) {// 整体偏暗的图像:轻微增加整体增益imageBrightnessFactor = 1.0f + (0.3f - avgBrightness) * 0.3f; // 从1.0降到0.3} else if (avgBrightness > 0.7f) {// 整体偏亮的图像:降低整体增益imageBrightnessFactor = 0.8f; // 从0.7提高到0.8} else {imageBrightnessFactor = 1.0f;}return baseGain * adaptiveFactor * imageBrightnessFactor;}/*** 保守的增益应用 - 防止过曝*/private static float[] applyConservativeGainToLinear(float[] linearBase, float gain,float pixelBrightness,float offsetSdr, float offsetHdr) {float[] result = new float[3];for (int i = 0; i < 3; i++) {float baseChannel = linearBase[i];// 更小的偏移量float dynamicOffsetSdr = offsetSdr * (1.0f - pixelBrightness) * 0.05f; // 从0.1降到0.05float dynamicOffsetHdr = offsetHdr * (1.0f - pixelBrightness) * 0.05f; // 从0.1降到0.05// 应用偏移float adjustedBase = baseChannel + dynamicOffsetSdr;// 应用增益float hdrChannel = adjustedBase * gain;// 应用HDR偏移hdrChannel += dynamicOffsetHdr;// 更严格的动态钳制float maxValue;if (pixelBrightness > 0.9f) {maxValue = 1.0f; // 极高光区域严格限制} else if (pixelBrightness > 0.8f) {maxValue = 1.2f; // 高光区域较严格限制} else {maxValue = 1.5f; // 其他区域适中限制}hdrChannel = Math.max(0, Math.min(maxValue, hdrChannel));result[i] = hdrChannel;}return result;}/*** sRGB到线性颜色空间转换*/private static float[] srgbToLinear(float r, float g, float b) {return new float[] {srgbToLinearSingle(r),srgbToLinearSingle(g),srgbToLinearSingle(b)};}private static float srgbToLinearSingle(float channel) {if (channel <= 0.04045f) {return channel / 12.92f;} else {return (float) Math.pow((channel + 0.055f) / 1.055f, 2.4f);}}/*** 计算亮度*/private static float calculateLuminance(float r, float g, float b) {return 0.2126f * r + 0.7152f * g + 0.0722f * b;}/*** 线性到sRGB颜色空间转换*/private static float[] linearToSrgb(float r, float g, float b) {return new float[] {linearToSrgbSingle(r),linearToSrgbSingle(g),linearToSrgbSingle(b)};}private static float linearToSrgbSingle(float channel) {if (channel <= 0.0031308f) {return channel * 12.92f;} else {return (float) (1.055f * Math.pow(channel, 1.0 / 2.4) - 0.055f);}}/*** 超保守版本 - 防止过曝*/public static Bitmap applyGainMapConservative(Bitmap baseImage, Bitmap gainMap) {// 使用更保守的参数float gamma = 1.8f; // 更高的gamma值float hdrCapacityMin = 0.3f; // 提高最小值float hdrCapacityMax = 0.6f; // 降低最大值float offsetSdr = 0.005f; // 减小偏移float offsetHdr = 0.002f; // 减小偏移return applyGainMapAlgorithm(baseImage, gainMap,gamma, hdrCapacityMin, hdrCapacityMax, offsetSdr, offsetHdr);}/*** 轻度增强版本 - 平衡效果和自然度*/public static Bitmap applyGainMapBalanced(Bitmap baseImage, Bitmap gainMap) {float gamma = 1.5f;float hdrCapacityMin = 0.25f;float hdrCapacityMax = 0.65f;float offsetSdr = 0.008f;float offsetHdr = 0.004f;return applyGainMapAlgorithm(baseImage, gainMap,gamma, hdrCapacityMin, hdrCapacityMax, offsetSdr, offsetHdr);}
}

hdr_image_decoder_main.xml布局

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/985994.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

anything

ValueError Traceback (most recent call last)Cell In[20], line 8 3 client = OpenAI() 4 # client = OpenAI(api_key=os.getenv("OPENAI_API_KEY"), base_url=os.getenv("OPENAI_BASE_URL…

递归函数,闭包,装饰器3

递归函数,闭包,装饰器3递归函数 含义:如果一个函数在内部不调用其他的函数,而是调用它本身的话,这个函数就是递归函数 条件: 1,明确的结束条件 2.没进行更深一层的递归时,问题规模相比上次递归都要有所减少。…

从vw/vh到clamp(),前端响应式设计的痛点与进化 - 实践

从vw/vh到clamp(),前端响应式设计的痛点与进化 - 实践2025-12-03 20:17 tlnshuju 阅读(0) 评论(0) 收藏 举报pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; d…

10413_基于Springboot的智慧养老院管理系统

1、项目包含 项目源码、项目文档、数据库脚本、软件工具等资料; 带你从零开始部署运行本套系统。 2、项目介绍 随着老龄化社会的加速发展,传统养老院管理模式面临效率低、服务滞后等挑战。本研究着力打造一套凭Sprin…

【Unity URP】Rendering Debugger和可视化MipMap方案

写在前面 最近开始学习Unity性能优化,是结合了《Unity游戏优化》这本书和教程《Unity性能优化》第叁节——静态资源优化(3)——纹理的基础概念一起学习。在学习纹理优化部分时候遇到了问题,固定管线下Unity的Scene窗…

How to do a biology experiment for a Physician.

Im not kidding.Be early. Normally, all materials should be scheduled. But if theres not schedule, all biologists will think he is important ———— because they know what life is. The humblest will be…

2025–2030 年最紧缺的八大 IC 岗位

面对摩尔定律放缓、AI 和汽车电子爆发、Chiplet 与 3D-IC 持续改写系统架构,半导体行业正在进入一个全新的竞争周期。技术路径的变化,也正在深刻影响人才需求结构:行业不再只需要“通才”,而是更稀缺、也更关键的专…

Firefox 禁用按下 Alt 显示菜单

进入 about:config,将 ui.key.menuAccessKey 默认的 18 改成 0,重启浏览器。

LC 3479(2100) 线段树二分 水果成篮

题目 题目: 给你两个长度为 n 的整数数组,fruits 和 baskets,其中 fruits[i] 表示第 i 种水果的 数量,baskets[j] 表示第 j 个篮子的 容量。 你需要对 fruits 数组从左到右按照以下规则放置水果: 每种水果必须放入…

文件的常用操作

Path相关操作,主要为文件属性,路径目录等点击查看代码 def _Path():from pathlib import Pathimport shutil# 创建 Path 对象print(Path().absolute())p = Path("data/example.txt")p1 = Path("data/d…

聊聊Oracle数据库的向量能力 - 详解

pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", …

ReAct+LangGraph:构建智能AI Agent的完整指南(建议收藏) - 详解

pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", …

第七天项目

苍穹外卖项目 - 第7天冲刺日志(项目总结与复盘) 日期:2025-12-02 冲刺周期:第7天/共7天 参会人员:李靖华 温尚熙 谢斯越 郑哲磊一、站立会议照片团队成员进行项目总结和复盘讨论二、最终验收会议记录 郑哲磊(后端…

Spring Boot框架中在Controller方法里获取Request和Response对象的2种方式

写在前面 javax.servlet.ServletRequest和javax.servlet.ServletResponse都是Servlet容器中定义的接口,分别用于获取客户端请求信息和将响应消息发送给客户端。 有两种方法在Contoller方法中获取它们:直接在Controll…

2025煤炭氟氯测定仪TOP5权威推荐:精准检测选对品牌,奥

煤质环保检测领域中,氟氯测定仪作为判定煤炭环保合规性的核心设备,其精准度、耐用性直接影响检测结果与企业生产效率。2024年行业数据显示,因氟氯测定仪检测偏差导致的环保合规风险事件占煤质检测问题的30%,而耐用…

2025年上海办公室装修公司口碑排名:迎湖办公室装修实力可靠

办公室是企业的第二战场,从空间规划到材料环保,每一处细节都关乎员工效率与品牌形象。面对市场上良莠不齐的装修公司,企业主常常陷入承诺与现实不符的困境:报价藏增项、工期拖延、材料不环保等问题频发。2025年办公…

Scrum 冲刺博客_4

Scrum 冲刺博客_4 站立式会议照片:昨天已完成工作:团队共同敲定数据接口规范 v1.0,明确了所有核心接口的参数、返回格式及异常码。 前端组基于接口规范,完成了前端请求封装的 TypeScript 类型声明,搭建了 Axios 请…

第五天项目

苍穹外卖项目 - 第5天冲刺日志 日期:2025-11-30 冲刺周期:第5天/共7天 会议时间:09:00 - 09:15 会议地点:开发室 参会人员:李靖华 温尚熙 谢斯越 郑哲磊一、站立会议照片团队成员正在讨论数据统计功能的实现细节二…

[豪の算法奇妙冒险] 代码随想录算法训练营第十四天 | 翻转二叉树、对称二叉树、二叉树的最大深度、二叉树的最小深度

翻转二叉树、对称二叉树、二叉树的最大深度、二叉树的最小深度代码随想录算法训练营第十四天 | 翻转二叉树、对称二叉树、二叉树的最大深度、二叉树的最小深度翻转二叉树题目链接:https://leetcode.cn/problems/inver…

团队作业4——7天敏捷冲刺

项目冲刺这个作业属于哪个课程 https://edu.cnblogs.com/campus/gdgy/Class34Grade23ComputerScience这个作业要求在哪里 https://edu.cnblogs.com/campus/gdgy/Class34Grade23ComputerScience/homework/13483这个作业…