当前位置: 首页 > news >正文

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

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

  • 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.dtcms.com/a/395240.html

相关文章:

  • Excel——常用函数一
  • 立项依据不足会给项目带来哪些风险
  • 从 0 到 1 精通 SkyWalking:分布式系统的 “透视镜“ 技术全解析
  • SkyWalking 核心概念与智能探针工作原理深度揭秘(下)
  • Dockerfile入门指南
  • iOS 原生开发全流程解析,iOS 应用开发步骤、Xcode 开发环境配置、ipa 文件打包上传与 App Store 上架实战经验
  • 数据分析报告的写作流程
  • 当你的断点在说谎:深入解析RTOS中的“幽灵”Bug
  • [BUG]MarkupSafe==3.0.2
  • 机器学习笔试选择题:题组1
  • 79-数据可视化-地图可视化
  • python全栈-数据可视化
  • 【国产桌面操作系统】安装mysql客户端及C/C++开发
  • IntelliJ:找不到相关的 gradle 配置,请重新导入 Gradle 项目,然后重试。
  • 云计算微服务架构与容器化技术:服务网格与边缘计算融合实践
  • 飞牛NAS上搭建OpenWrt旁路由教程(适用于x86的Docker部署)
  • python14——函数
  • 14.Linux 硬盘分区管理及RAID存储技术
  • ★ Linux ★ 信号
  • macOS在IDEA里滚动行为混乱问题
  • ✨Vue 静态路由详解:构建应用的导航骨架(4)
  • 08-2Dcss动画
  • 使用IOT-Tree消息流Modbus Slave节点,实现Modbus设备的模拟
  • 创作者模式—单例设计模式
  • PostgreSQL 备份
  • SQL-多表查询
  • Hive SQL 中的时间戳转换详解
  • Linux笔记---select、poll、epoll总结对比
  • MySQL查询详细介绍
  • 面试题二:业务篇