同一个灰色,POI取出来却是白色:一次Excel颜色解析的踩坑记录

news/2025/9/22 11:38:15/文章来源:https://www.cnblogs.com/maoxiu/p/19104844

最近在写一个后端转换功能,导入xlsx文件,给他解析成JSON格式。结果测试时发现一个问题:两个看起来一模一样的灰色单元格,代码读出来的颜色不一样。属实给我整懵逼了。
比如下面这两个单元格
image

  • A1:能正常拿到灰值,比如 #808080
  • B1:拿到的是 FFFFFF —— 白的

明明都是手动设置的灰色,样式也一样,拿到的填充颜色确不一样。

我第一反应是前景色/背景色搞混了,于是加了一个判断,前景色拿不到就从背景色拿:

XSSFColor color = (XSSFColor) cellStyle.getFillForegroundColorColor();
if (color == null) {color = (XSSFColor) cellStyle.getFillBackgroundColorColor();
}

最后一试,还是白的。

折腾半天,最后发现关键在于:B1 是个主题色,而 A1 是个普通颜色(换了三个AI,问了半天结合起来才折腾出来)。

主题色是什么?

在 Excel 里用“主题颜色”面板选的颜色,比如“辅助色 3”、“深色 1”这类,都叫主题色。它们不是固定的 RGB 值,而是指向当前工作簿的主题定义。也就是说,换一套主题,这颜色可能就变了。

POI 的 XSSFColor 提供了一个方法判断:

color.isThemed() // 返回 true 表示是主题色

如果是主题色,直接调 .getRGB().getARGB() 是拿不到有效值的,经常就是 nullFFFFFF

得结合 ThemesTable 把真正的颜色算出来。

但这还没完。
即使你从 ThemesTable 拿到了主题里的基础颜色,结果可能还是不对。因为还有一个东西叫 tint

什么是 tint

tint 是 Excel 里用来微调主题颜色明暗的一个参数,取值范围是 -1.01.0

  • tint = 0:原色
  • tint > 0:越接近 1,颜色越亮(往白色混合)
  • tint < 0:越接近 -1,颜色越暗(往黑色混合)

比如你选了个“辅助色 3”是深灰,但 Excel 默认给它加了个 tint = -0.24,意思是在这个主题色基础上再压暗一点。如果你忽略这个值,直接拿原始主题色,就会偏亮,甚至变成白色。

所以,只处理主题色不处理 tint,拿到的颜色也可能有问题

最终解决方案

核心思路:

  1. 判断是否为 isThemed()
  2. 如果是,从 ThemesTable 中取出对应索引的基础 RGB
  3. 再根据 getTint() 值,对颜色做明暗调整

下面是封装好的工具类,可以直接用:

import lombok.extern.slf4j.Slf4j;
import org.apache.poi.xssf.model.ThemesTable;
import org.apache.poi.xssf.usermodel.XSSFColor;import java.awt.*;@Slf4j
public class ThemeColorResolver {/*** 获取颜色的ARGB十六进制字符串表示** @param themedColor XSSFColor对象* @param theme       主题表* @return ARGB格式的十六进制字符串(如 " FFFF0000 " 表示红色),如果无法解析则返回null*/public static String getActualColorHex(XSSFColor themedColor, ThemesTable theme) {Color color = resolveActualColor(themedColor, theme);return color != null ? toARGBHex(color) : null;}/*** 获取颜色的RGB十六进制字符串表示(不带Alpha通道)** @param themedColor XSSFColor对象* @param theme       主题表* @return RGB格式的十六进制字符串(如 " FF0000 " 表示红色),如果无法解析则返回null*/public static String getActualColorRGBHex(XSSFColor themedColor, ThemesTable theme) {Color color = resolveActualColor(themedColor, theme);return color != null ? toRGBHex(color) : null;}/*** 获取颜色的java.awt.Color对象** @param themedColor XSSFColor对象* @param theme       主题表* @return 解析后的Color对象,如果无法解析则返回null*/public static Color getActualColor(XSSFColor themedColor, ThemesTable theme) {return resolveActualColor(themedColor, theme);}/*** 核心解析方法*/private static Color resolveActualColor(XSSFColor themedColor, ThemesTable theme) {// 如果不是主题色,直接返回ARGB颜色if (!themedColor.isThemed()) {byte[] argb = themedColor.getARGB();if (argb == null || argb.length < 3) {log.warn("Non-themed color has invalid ARGB value");return null;}return new Color(argb[1] & 0xFF, argb[2] & 0xFF, argb[3] & 0xFF, argb[0] & 0xFF);}// 解析主题颜色try {int themeIndex = themedColor.getTheme();XSSFColor themeColor = theme.getThemeColor(themeIndex);byte[] themeRgb = themeColor.getRGB();if (themeRgb == null || themeRgb.length < 3) {log.warn("Theme color not found for index: {}", themeIndex);return null;}// 获取基础颜色(不考虑tint)Color baseColor = new Color(themeRgb[0] & 0xFF, themeRgb[1] & 0xFF, themeRgb[2] & 0xFF);// 应用tint调整double tint = themedColor.getTint();if (tint != 0.0) {return applyTint(baseColor, tint);}return baseColor;} catch (Exception e) {log.error("Error resolving theme color", e);return null;}}/*** 应用tint值调整颜色*/private static Color applyTint(Color baseColor, double tint) {int r = baseColor.getRed();int g = baseColor.getGreen();int b = baseColor.getBlue();if (tint > 0) {// 变亮(混合白色)r = (int) (r + (255 - r) * tint);g = (int) (g + (255 - g) * tint);b = (int) (b + (255 - b) * tint);} else if (tint < 0) {// 变暗(混合黑色)r = (int) (r * (1 + tint));g = (int) (g * (1 + tint));b = (int) (b * (1 + tint));}// 确保值在0-255范围内r = Math.min(255, Math.max(0, r));g = Math.min(255, Math.max(0, g));b = Math.min(255, Math.max(0, b));return new Color(r, g, b);}/*** 将Color转换为ARGB十六进制字符串*/private static String toARGBHex(Color color) {return String.format("%02X%02X%02X%02X",color.getAlpha(), color.getRed(), color.getGreen(), color.getBlue());}/*** 将Color转换为RGB十六进制字符串(不带Alpha)*/private static String toRGBHex(Color color) {return String.format("%02X%02X%02X",color.getRed(), color.getGreen(), color.getBlue());}
}

使用方式

// 获取工作簿的主题表
ThemesTable themes = workbook instanceof XSSFWorkbook ? ((XSSFWorkbook) workbook).getThemesTable() : null;// 获取单元格样式颜色
XSSFColor fill = (XSSFColor) cellStyle.getFillForegroundColorColor();
Color actualColor = ThemeColorResolver.getActualColor(fill, themes);if (actualColor != null) {String hex = ThemeColorResolver.toHex(actualColor); // 如 #808080
}

总结

  • Excel 的“主题色”不是固定值,不能直接 .getRGB()
  • isThemed() 是第一步判断
  • 最终颜色 = 主题基础色 + tint 调整

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

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

相关文章

坤驰科技携国产化MTCA解决方案,亮相大科学装置控制系统研讨会

“2025MicroTCA/ATCA在大科学装置控制系统中的应用研讨会”在重庆君豪大饭店召开,北京坤驰科技携国产化MTCA硬件平台及数据采集解决方案参会。国产化 MTCA平台 坤驰科技深耕大科学装置(高能物理、激光、光子光束线等…

找出所有项目引用了哪些 NuGet 包、版本号、对应项目路径,并筛选出“同一个包名但版本不同”的情况。

全局扫描所有 .csproj 文件 打开 PowerShell,运行以下脚本(替换为你的代码根目录): $root = "D:\YourCodeRoot" $results = Get-ChildItem -Path $root -Recurse -Filter *.csproj | ForEach-Object {$p…

人形机器人 —— 电机控制的三种模式 —— 力矩、速度、位置

人形机器人 —— 电机控制的三种模式 —— 力矩、速度、位置电机控制的三种模式: 力矩、速度、位置其实,这三种模式说的并不是很清晰,准确来说应该是缺少了一个变量,那就是时间变量,准确的来说就是在单位时间内的…

解决Windows更新后WPF代码报TypeLoadException异常的困难

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

PC与基恩士PLC通信的C#实现

1 确定通信方式与协议 PC与基恩士PLC通信通常主要通过以太网进行,有时也会使用串口。关键在于PLC型号和支持的协议,常见的有:MC协议 (MELSEC Communication Protocol):这是三菱PLC的协议,但概念类似,基恩士有其自…

Excel 表格技能

1:添加删除线:Alt + 1 , 调出设置面板,添加

labelme标注后的json文件和原图同步按角度旋转

点击查看代码 import json import os import base64 import numpy as np import cv2 from math import cos, sin, radians import argparsedef rotate_point_opencv_style(point, rotation_matrix):"""…

rk3588的ai功能和deepseek

rk3588的ai功能 该型号cpu支持 6TOPS NPU、Mali-G610 MP4 GPU。 支持针对rk系列开发的RKNN框架,能够完成模型转换,量化,推理,性能评估,内存评估和量化分析功能。TOPS是Tera Operations Per Second的缩写,1TOPS代…

EPSON L1300打印机清零教程

症状分析 打印机电源灯不断闪烁,旁边的三个状态灯,依次亮红灯闪烁。连接打印机的电脑会有如下提示:如果你的打印机是这种情况,那就说明该清零了。 使用前提 清零软件需要在USB直连打印机的电脑上工作,请确保先满足…

「线性代数」矩阵运算与初等变换

矩阵基本知识基本概念略。 矩阵的运算 矩阵加法、数乘 加法:对于两个 \(n\times m\) 的矩阵 \(A, B\) 定义 \(A + B = C\),\(C\) 仍为 \(n \times m\),且 \(c_{i, j} = a_{i, j} + b_{i, j}\)。 数乘:\(B = xA\),…

移动号码线上复机

记录一下过程: 1.先是抖音搜索; 2.给移动人工客服打电话,询问变成空号原因,是欠费超过3个月,9月7日变成空号;如何解决呢,可以通过中国移动app线上复机;我用联通号码登录的,搜索不到线上复机; 3.再给移动人工…

Uni-App 使用android studio打包最新教程

字数 530,阅读大约需 3 分钟Uni-App 使用android studio打包最新教程 1、下载uniapp离线sdk Dcloud 官方平台地址Android 离线SDK - 正式版 | uni小程序SDK[1] 2、找个自己的文件夹解压注意文件夹不要带中文,android…

tomcat CPU数量和线程数的关系

这个设置建议是基于线程的执行特性和服务器资源的合理利用。以下详细解释为什么这样设置: 1. CPU密集型任务 对于CPU密集型任务,线程主要在执行计算操作,几乎不会主动让出CPU。在这种情况下,线程数过多会导致线程切…

NASA运货飞船天鹅座再次推迟,航天任务为什么总是“彩排”不断

​9月16日,原本计划为国际空间站运送约5000公斤科学实验设备和物资的天鹅座XL货运飞船,再次在轨道抬升过程中遭遇挑战。在两次点火过程中,其主发动机均提前停止工作,导致原定于9月17日的交会对接计划被迫推迟。美国…

Centos系统切换为光盘本地源

Centos系统切换为光盘本地源首先把 centos 的安装 IOS 挂载到光盘 方法一: --切换到根目录 cd / --新建目录 mkdir /media/cdrom --将镜像挂载到这个目录下面 mount /dev/cdrom /media/cdrom -- 进入yum仓库指定目录…

python处理Excel单机小程序:匹数据,增强版VLookup

2025年9月22日 场景: 如果使用vlookup匹数据的话, 每次只能匹配一列, 并且关联的列只能有一列, 比如有同名同姓的数据, 在匹配时就会出现错误 实现目标: 1. 可以同时使用多列数据进行关联, 比如用 姓名和工号, 同时进行…

深入解析:颜群JVM【01】类的生命周期_JMM_volatile

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

var sql 的不同用法

1. 查询某列的string集合 var sql = "select distinct age from WechatUserDetail where IsDeleted=0 and LEN(ISNULL(Age,))>0"; var table = db.ExecuteDataTableSql(sql); …

CF623B Array GCD

显然 gcd > 1 等价于枚举一个数,使得所有数都是这个数的倍数,进一步可以规约到枚举质因数。 如果确定了质因数,我们很好用 DP 做到 \(O(n)\) 的复杂度,但问题就是质因数的规模确实不小。 有一个结论是,只需要枚…

Python爬虫实现双色球历史数据抓取

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档@目录前言一、用python抓取每天的开奖数据二、接口开发总结前言 公司楼下恰巧有个中国福利彩票中心,跟同事每天中午吃完饭都会去买两注,在支持中国…