王妍芳 发表于 2025-10-1 19:05:40

同一个灰色,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() 是拿不到有效值的,经常就是 null 或 FFFFFF。
得结合 ThemesTable 把真正的颜色算出来。
但这还没完。
即使你从 ThemesTable 拿到了主题里的基础颜色,结果可能还是不对。因为还有一个东西叫 tint。
什么是 tint

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

[*]tint = 0:原色
[*]tint > 0:越接近 1,颜色越亮(往白色混合)
[*]tint < 0:越接近 -1,颜色越暗(往黑色混合)
比如你选了个“辅助色 3”是深灰,但 Excel 默认给它加了个 tint = -0.24,意思是在这个主题色基础上再压暗一点。如果你忽略这个值,直接拿原始主题色,就会偏亮,甚至变成白色。
所以,只处理主题色不处理 tint,拿到的颜色也可能有问题。
最终解决方案

核心思路:

[*]判断是否为 isThemed()
[*]如果是,从 ThemesTable 中取出对应索引的基础 RGB
[*]再根据 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 & 0xFF, argb & 0xFF, argb & 0xFF, argb & 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 & 0xFF, themeRgb & 0xFF, themeRgb & 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 调整

来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

庇床铍 发表于 2025-10-27 18:07:09

东西不错很实用谢谢分享

丧血槌 发表于 2025-10-31 21:07:29

谢谢楼主提供!

季卓然 发表于 2025-12-5 07:30:29

很好很强大我过来先占个楼 待编辑
页: [1]
查看完整版本: 同一个灰色,POI取出来却是白色:一次Excel颜色解析的踩坑记录