分享

openpyxl获取单元格的主题色的颜色值

 小小明代码实体 2023-11-18 发布于广东

📢作者: 小小明-代码实体

📢博客主页:https://blog.csdn.net/as604049322

📢欢迎点赞 👍 收藏 ⭐留言 📝 欢迎讨论!

openpyxl 支持以下几种颜色类型:

  1. RGB (Red, Green, Blue): 这是最常用的颜色类型,允许通过指定红、绿、蓝三原色的组合来自定义颜色。RGB值通常以十六进制格式表示。
  2. Theme: Excel有一套主题颜色,可以通过指定主题颜色的索引来使用这些颜色。
  3. Indexed: 这是Excel早期版本中使用的一种颜色系统,它通过索引号来引用一组预定义的颜色。

对于RGB类型的颜色直接使用cell.fill.start_color.rgb即可获取其颜色,但是对于Theme类型的单元格获取颜色却返回一个错误。

这是因为主题色会随着主题的变化而变化,如下图:

image-20231117200205613

可以看到每个主题有10个基础色,然后受到透明度的影响,我将第一个单元格设置了上图的主题色。

我们创建Excel文件进行测试:

from openpyxl import load_workbook

wb = load_workbook("color_test.xlsx")
wbs = wb.active
cell = wbs.cell(1, 1)
cell.fill.start_color
<openpyxl.styles.colors.Color object>
Parameters:
rgb=None, indexed=None, auto=None, theme=5, tint=0.4, type='theme'

可以看到这就是一个theme类型的颜色,确实是索引5的位置,透明度40%。

我们尝试获取rgb颜色:

cell.fill.start_color.rgb

会返回Values must be of type <class 'str'>这样一个带有错误信息的字符串。

当然也可以调用index属性自动获取rgb或者在主题色中的索引:

cell.fill.start_color.index
5

如果是一个RGB 类型的颜色:

cell = wbs.cell(1, 3)
cell.fill.start_color
<openpyxl.styles.colors.Color object>
Parameters:
rgb='FFF4B382', indexed=None, auto=None, theme=None, tint=0.0, type='rgb'

此时调用index或rgb属性都可以获取rgb颜色值。

那么我们如何获取主题色对应的RGB颜色呢?这个openpyxl并没有提供一个直接的方式,我们只能自己做xml解析了。下面我封装了一个工具类:

from colorsys import rgb_to_hls, hls_to_rgb

class ThemeColorConverter:
    RGBMAX = 0xff
    HLSMAX = 240

    def __init__(self, wb):
        self.colors = self.get_theme_colors(wb)

    @staticmethod
    def tint_luminance(tint, lum):
        if tint < 0:
            return int(round(lum * (1.0 + tint)))
        return int(round((ThemeColorConverter.HLSMAX - lum) * tint)) + lum

    @staticmethod
    def ms_hls_to_rgb(hue, lightness=None, saturation=None):
        if lightness is None:
            hue, lightness, saturation = hue
        hlsmax = ThemeColorConverter.HLSMAX
        return hls_to_rgb(hue / hlsmax, lightness / hlsmax, saturation / hlsmax)

    @staticmethod
    def rgb_to_hex(red, green=None, blue=None):
        if green is None:
            red, green, blue = red
        return '{:02X}{:02X}{:02X}'.format(
            int(red * ThemeColorConverter.RGBMAX),
            int(green * ThemeColorConverter.RGBMAX),
            int(blue * ThemeColorConverter.RGBMAX)
        )

    @staticmethod
    def rgb_to_ms_hls(red, green=None, blue=None):
        if green is None:
            if isinstance(red, str):
                if len(red) > 6:
                    red = red[-6:]  # Ignore preceding '#' and alpha values
                rgbmax = ThemeColorConverter.RGBMAX
                blue = int(red[4:], 16) / rgbmax
                green = int(red[2:4], 16) / rgbmax
                red = int(red[0:2], 16) / rgbmax
            else:
                red, green, blue = red
        h, l, s = rgb_to_hls(red, green, blue)
        hlsmax = ThemeColorConverter.HLSMAX
        return (int(round(h * hlsmax)), int(round(l * hlsmax)),
                int(round(s * hlsmax)))

    @staticmethod
    def get_theme_colors(wb):
        from openpyxl.xml.functions import QName, fromstring
        xlmns = 'http://schemas./drawingml/2006/main'
        root = fromstring(wb.loaded_theme)
        themeEl = root.find(QName(xlmns, 'themeElements').text)
        colorSchemes = themeEl.findall(QName(xlmns, 'clrScheme').text)
        firstColorScheme = colorSchemes[0]
        colors = []
        for c in ['lt1', 'dk1', 'lt2', 'dk2', 'accent1', 'accent2', 'accent3', 'accent4', 'accent5', 'accent6']:
            accent = firstColorScheme.find(QName(xlmns, c).text)
            for i in list(accent):
                if 'window' in i.attrib['val']:
                    colors.append(i.attrib['lastClr'])
                else:
                    colors.append(i.attrib['val'])
        return colors

    def theme_and_tint_to_rgb(self, theme, tint):
        rgb = self.colors[theme]
        h, l, s = self.rgb_to_ms_hls(rgb)
        return self.rgb_to_hex(self.ms_hls_to_rgb(h, self.tint_luminance(tint, l), s))

具体如何xml解析可以看上面的get_theme_colors函数。

下面我们获取一下当前Excel选中主题的10个基础色调:

theme_color = ThemeColorConverter(wb)
print(theme_color.colors)
['FFFFFF', '000000', 'E7E6E6', '44546A', '4874CB', 'EE822F', 'F2BA02', '75BD42', '30C0B4', 'E54C5E']

然后我们传入索引和透明度获取颜色:

theme_color.theme_and_tint_to_rgb(5, 0.4)
'F4B281'

但使用取色工具测量第一个单元格的颜色值为#f4b382,有微量误差,这属于正常现象,也完全不会影响视觉。这是因为hls+透明度转rgb颜色的过程中存在小数运算,四舍五入后就会造成一定误差。

人眼对低位数据变化不敏感

image-20231117204721115

在24位位图中高8位构成蓝色通道,中8位构成绿色通道,低8位构成红色通道。经测试,在删除各通道的低4位数据并加入随机噪音后,图片用肉眼无法观察到任何变化。现在展示一张图片在删除各通道的低位数据,并加入随机噪音后图片的变化。

image-20231117201646876

可以看到在每个通道低三位的数据进行随意修改,肉眼几乎看不出变化。低三位,意味着7以内的变化都不会有影响。

最后我们封装一个可以查看任何单元格颜色的函数:

from openpyxl.styles.colors import COLOR_INDEX


def get_cell_color(cell):
    color = cell.fill.start_color
    if color.type == "rgb":
        return color.rgb
    elif color.type == "indexed":
        color_index = color.indexed
        if color_index is None or color_index < len(COLOR_INDEX):
            raise Exception("Invalid indexed color")
        return COLOR_INDEX[color_index]
    elif color.type == "theme":
        return "FF" + theme_color.theme_and_tint_to_rgb(color.theme, color.tint)
    else:
        raise Exception(f"Other type: {color.type}")

下面我们将这些主题色都测一测:

image-20231117202903861

theme_color = ThemeColorConverter(wb)
wbs = wb.active
for r in range(1, 4):
    colors = [get_cell_color(wbs.cell(r, c)) for c in range(1, 8)]
    print(f"第{r}行的单元格的颜色为", colors)
第1行的单元格的颜色为 ['FFFFFFFF', 'FF000000', 'FFE7E6E6', 'FF44546A', 'FF4772CA', 'FFEE802E', 'FFF2BC02']
第2行的单元格的颜色为 ['FFF2F2F2', 'FF7F7F7F', 'FFD0CECE', 'FFD5DBE4', 'FFDAE3F4', 'FFFBE5D5', 'FFFEF2CA']
第3行的单元格的颜色为 ['FFBFBFBF', 'FF3F3F3F', 'FF757070', 'FF8496AF', 'FF90A9DF', 'FFF4B281', 'FFFDDA60']

可以看到,全部获取到低位误差小于1的RGB颜色。

附录

openpyxl获取的颜色值由四组16进制数表示,分别是:

  1. Alpha(透明度):范围也是00FF,其中FF表示完全不透明,而00表示完全透明。
  2. 红色(Red)
  3. 绿色(Green)
  4. 蓝色(Blue)

不过在wps中测试,透明度不起任何效果,不排除office或WPS未来版本支持ARGB的颜色,但目前WPS对A通道的透明度值会直接忽略。

本文链接:https://blog.csdn.net/as604049322/article/details/134470419

    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多