在这里插入图片描述

目录

  1. 概述
  2. 工具功能
  3. 核心实现
  4. Kotlin 源代码
  5. JavaScript 编译代码
  6. ArkTS 调用代码
  7. 实战案例
  8. 最佳实践

概述

本文档介绍如何在 Kotlin Multiplatform (KMP) 鸿蒙跨端开发中实现一个完整的颜色转换和分析工具系统。颜色处理是现代应用开发中的一个重要需求,涉及颜色空间转换、颜色匹配、颜色分析等多个方面。无论是进行界面设计、图像处理还是数据可视化,一个功能强大的颜色工具都能提供便利的支持。

这个案例展示了如何使用 Kotlin 的数学运算、字符串处理和颜色空间理论来创建一个功能丰富的颜色分析工具。颜色转换和分析工具需要能够在不同的颜色空间之间进行转换、计算颜色的相似度、生成颜色调色板、分析颜色的对比度等。通过 KMP,这个工具可以无缝编译到 JavaScript,在 OpenHarmony 应用中运行,并支持用户输入进行实时颜色处理。

在实际应用中,颜色工具广泛应用于以下场景:网页设计中的颜色选择、移动应用的主题配置、图像编辑软件的颜色调整、数据可视化中的颜色映射、无障碍设计中的对比度检查等。通过支持多种颜色格式如 RGB、HSL、HEX 和 CMYK,我们可以满足不同场景的需求。同时,通过 KMP 框架的跨端能力,我们可以在不同平台上使用相同的颜色处理逻辑,确保颜色在各个平台上的一致性。

工具的特点

  • 多格式支持:支持 RGB、HEX、HSL、HSV、CMYK 等多种颜色格式
  • 格式转换:支持不同颜色格式之间的相互转换
  • 颜色分析:提供亮度、饱和度、对比度等多维度分析
  • 颜色匹配:计算颜色之间的相似度和距离
  • 调色板生成:自动生成互补色、类似色等颜色组合
  • 跨端兼容:一份 Kotlin 代码可同时服务多个平台

工具功能

1. RGB 和 HEX 转换

RGB(红绿蓝)是最常见的颜色表示方法,每个颜色分量的值范围是 0-255。HEX(十六进制)是网页设计中最常用的颜色表示方法,用六位十六进制数字表示颜色,每两位分别代表红、绿、蓝三个分量。这两种格式的转换是颜色处理中的基础操作,广泛应用于网页设计、移动应用开发等领域。在实际应用中,RGB 格式常用于编程和图像处理,而 HEX 格式则更常见于网页和设计工具中。

  • RGB 转 HEX:将 RGB 值转换为十六进制格式
  • HEX 转 RGB:将十六进制格式转换为 RGB 值
  • 格式验证:验证输入颜色格式的有效性

2. HSL 和 HSV 转换

HSL(色调、饱和度、亮度)和 HSV(色调、饱和度、明度)是基于人类视觉感知的颜色模型。与 RGB 相比,HSL 和 HSV 更符合人类对颜色的直观理解。HSL 中的亮度参数使得颜色的明暗变化更加直观,而 HSV 中的明度参数更适合描述颜色的纯度。这些颜色空间在图像编辑、颜色选择器等应用中被广泛使用。HSL 模型特别适合于创建颜色主题和调色板,因为改变亮度值可以轻松创建颜色的浅色和深色版本。

  • RGB 转 HSL:将 RGB 颜色转换为 HSL 模型
  • HSL 转 RGB:将 HSL 颜色转换为 RGB 模型
  • RGB 转 HSV:将 RGB 颜色转换为 HSV 模型
  • HSV 转 RGB:将 HSV 颜色转换为 RGB 模型

3. CMYK 转换

CMYK(青、品红、黄、黑)是印刷行业标准的颜色模型。与 RGB 不同,CMYK 是一种减色模型,用于描述印刷墨水的混合。在设计印刷品时,需要将 RGB 颜色转换为 CMYK 颜色,以确保印刷效果与屏幕显示的一致性。CMYK 模型中的黑色(K)分量对于印刷质量至关重要,它可以提高印刷效率并改善颜色的深度。

  • RGB 转 CMYK:将 RGB 颜色转换为 CMYK 模型
  • CMYK 转 RGB:将 CMYK 颜色转换为 RGB 模型
  • 印刷优化:提供印刷友好的颜色建议

4. 颜色分析

颜色分析是理解颜色特性的关键。通过计算颜色的亮度、饱和度和对比度,我们可以评估颜色的可读性和视觉效果。亮度计算基于人眼对不同颜色的感知敏感度,绿色通常被认为最亮,蓝色最暗。对比度计算遵循 WCAG 标准,用于确保网页和应用的无障碍性。

  • 亮度计算:计算颜色的相对亮度
  • 饱和度分析:分析颜色的饱和度水平
  • 对比度检查:计算两种颜色的对比度,用于无障碍设计
  • 色温分析:判断颜色的冷暖属性

5. 颜色匹配和生成

颜色匹配和生成是设计工作中的重要工具。互补色是在色轮上相对的颜色,能产生高对比度的视觉效果。类似色是相邻的颜色,能产生和谐的视觉效果。三角配色方案使用三种均匀分布的颜色,能产生平衡的视觉效果。这些配色方案在网页设计、应用界面设计和品牌设计中被广泛应用。

  • 互补色:生成给定颜色的互补色
  • 类似色:生成与给定颜色相似的颜色
  • 三角配色:生成三种颜色的和谐配色方案
  • 颜色距离:计算两种颜色之间的欧几里得距离

核心实现

1. RGB 和 HEX 转换

fun rgbToHex(r: Int, g: Int, b: Int): String {
    require(r in 0..255 && g in 0..255 && b in 0..255) { "RGB 值必须在 0-255 之间" }
    return "#" + String.format("%02X%02X%02X", r, g, b)
}

fun hexToRgb(hex: String): Triple<Int, Int, Int> {
    val cleanHex = hex.removePrefix("#")
    require(cleanHex.length == 6) { "HEX 颜色必须是 6 位十六进制数字" }
    val r = cleanHex.substring(0, 2).toInt(16)
    val g = cleanHex.substring(2, 4).toInt(16)
    val b = cleanHex.substring(4, 6).toInt(16)
    return Triple(r, g, b)
}

代码说明: RGB 到 HEX 的转换通过将每个颜色分量(0-255)转换为两位十六进制数字来实现。使用 String.format 函数可以确保每个分量都用两位十六进制数字表示,不足两位时用 0 补充。HEX 到 RGB 的转换是反向过程,首先移除 ‘#’ 前缀,然后将六位十六进制数字分成三组,每组两位,分别转换为十进制的 RGB 值。这种转换在网页设计和移动应用开发中非常常见。

2. RGB 和 HSL 转换

fun rgbToHsl(r: Int, g: Int, b: Int): Triple<Double, Double, Double> {
    val rNorm = r / 255.0
    val gNorm = g / 255.0
    val bNorm = b / 255.0
    
    val max = maxOf(rNorm, gNorm, bNorm)
    val min = minOf(rNorm, gNorm, bNorm)
    val delta = max - min
    
    val lightness = (max + min) / 2.0
    val saturation = if (delta == 0.0) 0.0 else delta / (1 - Math.abs(2 * lightness - 1))
    
    val hue = when {
        delta == 0.0 -> 0.0
        max == rNorm -> (60 * (((gNorm - bNorm) / delta) % 6) + 360) % 360
        max == gNorm -> (60 * (((bNorm - rNorm) / delta) + 2) + 360) % 360
        else -> (60 * (((rNorm - gNorm) / delta) + 4) + 360) % 360
    }
    
    return Triple(hue, saturation * 100, lightness * 100)
}

代码说明: RGB 到 HSL 的转换涉及复杂的数学计算。首先,我们将 RGB 值归一化到 0-1 范围,然后计算最大值、最小值和它们的差。亮度是最大值和最小值的平均值。饱和度的计算取决于亮度值,使用特定的公式确保在不同亮度下的准确性。色调的计算基于哪个颜色分量最大,使用不同的公式来确定色调的角度。

3. 颜色对比度计算

fun calculateLuminance(r: Int, g: Int, b: Int): Double {
    val rNorm = r / 255.0
    val gNorm = g / 255.0
    val bNorm = b / 255.0
    
    val rLinear = if (rNorm <= 0.03928) rNorm / 12.92 else Math.pow((rNorm + 0.055) / 1.055, 2.4)
    val gLinear = if (gNorm <= 0.03928) gNorm / 12.92 else Math.pow((gNorm + 0.055) / 1.055, 2.4)
    val bLinear = if (bNorm <= 0.03928) bNorm / 12.92 else Math.pow((bNorm + 0.055) / 1.055, 2.4)
    
    return 0.2126 * rLinear + 0.7152 * gLinear + 0.0722 * bLinear
}

fun calculateContrast(r1: Int, g1: Int, b1: Int, r2: Int, g2: Int, b2: Int): Double {
    val lum1 = calculateLuminance(r1, g1, b1)
    val lum2 = calculateLuminance(r2, g2, b2)
    val lighter = maxOf(lum1, lum2)
    val darker = minOf(lum1, lum2)
    return (lighter + 0.05) / (darker + 0.05)
}

代码说明: 颜色对比度的计算基于 WCAG(网页内容无障碍指南)的标准。首先计算颜色的相对亮度,这涉及到 gamma 校正和加权平均。然后,对比度是较亮颜色的亮度加 0.05 与较暗颜色的亮度加 0.05 的比值。对比度值越高,两种颜色的区分度越好,越符合无障碍设计的要求。

4. 互补色和类似色生成

fun getComplementaryColor(h: Double, s: Double, l: Double): Triple<Double, Double, Double> {
    val complementaryHue = (h + 180) % 360
    return Triple(complementaryHue, s, l)
}

fun getAnalogousColors(h: Double, s: Double, l: Double): List<Triple<Double, Double, Double>> {
    val angle = 30.0
    return listOf(
        Triple((h - angle + 360) % 360, s, l),
        Triple(h, s, l),
        Triple((h + angle) % 360, s, l)
    )
}

代码说明: 互补色是在色轮上相对的颜色,色调相差 180 度。类似色是在色轮上相邻的颜色,通常相差 30 度。这些颜色组合在设计中能产生和谐的视觉效果。


Kotlin 源代码

// ColorConversionAnalysis.kt
class ColorConversionAnalysis {
    
    fun rgbToHex(r: Int, g: Int, b: Int): String {
        require(r in 0..255 && g in 0..255 && b in 0..255)
        return "#" + String.format("%02X%02X%02X", r, g, b)
    }
    
    fun hexToRgb(hex: String): Triple<Int, Int, Int> {
        val cleanHex = hex.removePrefix("#")
        require(cleanHex.length == 6)
        val r = cleanHex.substring(0, 2).toInt(16)
        val g = cleanHex.substring(2, 4).toInt(16)
        val b = cleanHex.substring(4, 6).toInt(16)
        return Triple(r, g, b)
    }
    
    fun rgbToHsl(r: Int, g: Int, b: Int): Triple<Double, Double, Double> {
        val rNorm = r / 255.0
        val gNorm = g / 255.0
        val bNorm = b / 255.0
        
        val max = maxOf(rNorm, gNorm, bNorm)
        val min = minOf(rNorm, gNorm, bNorm)
        val delta = max - min
        
        val lightness = (max + min) / 2.0
        val saturation = if (delta == 0.0) 0.0 else delta / (1 - Math.abs(2 * lightness - 1))
        
        val hue = when {
            delta == 0.0 -> 0.0
            max == rNorm -> (60 * (((gNorm - bNorm) / delta) % 6) + 360) % 360
            max == gNorm -> (60 * (((bNorm - rNorm) / delta) + 2) + 360) % 360
            else -> (60 * (((rNorm - gNorm) / delta) + 4) + 360) % 360
        }
        
        return Triple(hue, saturation * 100, lightness * 100)
    }
    
    fun hslToRgb(h: Double, s: Double, l: Double): Triple<Int, Int, Int> {
        val sNorm = s / 100.0
        val lNorm = l / 100.0
        
        val c = (1 - Math.abs(2 * lNorm - 1)) * sNorm
        val hPrime = h / 60.0
        val x = c * (1 - Math.abs((hPrime % 2) - 1))
        
        val (rPrime, gPrime, bPrime) = when {
            hPrime < 1 -> Triple(c, x, 0.0)
            hPrime < 2 -> Triple(x, c, 0.0)
            hPrime < 3 -> Triple(0.0, c, x)
            hPrime < 4 -> Triple(0.0, x, c)
            hPrime < 5 -> Triple(x, 0.0, c)
            else -> Triple(c, 0.0, x)
        }
        
        val m = lNorm - c / 2.0
        val r = ((rPrime + m) * 255).toInt()
        val g = ((gPrime + m) * 255).toInt()
        val b = ((bPrime + m) * 255).toInt()
        
        return Triple(r, g, b)
    }
    
    fun calculateLuminance(r: Int, g: Int, b: Int): Double {
        val rNorm = r / 255.0
        val gNorm = g / 255.0
        val bNorm = b / 255.0
        
        val rLinear = if (rNorm <= 0.03928) rNorm / 12.92 else Math.pow((rNorm + 0.055) / 1.055, 2.4)
        val gLinear = if (gNorm <= 0.03928) gNorm / 12.92 else Math.pow((gNorm + 0.055) / 1.055, 2.4)
        val bLinear = if (bNorm <= 0.03928) bNorm / 12.92 else Math.pow((bNorm + 0.055) / 1.055, 2.4)
        
        return 0.2126 * rLinear + 0.7152 * gLinear + 0.0722 * bLinear
    }
    
    fun calculateContrast(r1: Int, g1: Int, b1: Int, r2: Int, g2: Int, b2: Int): Double {
        val lum1 = calculateLuminance(r1, g1, b1)
        val lum2 = calculateLuminance(r2, g2, b2)
        val lighter = maxOf(lum1, lum2)
        val darker = minOf(lum1, lum2)
        return (lighter + 0.05) / (darker + 0.05)
    }
    
    fun getComplementaryColor(h: Double, s: Double, l: Double): Triple<Double, Double, Double> {
        val complementaryHue = (h + 180) % 360
        return Triple(complementaryHue, s, l)
    }
    
    fun getAnalogousColors(h: Double, s: Double, l: Double): List<Triple<Double, Double, Double>> {
        val angle = 30.0
        return listOf(
            Triple((h - angle + 360) % 360, s, l),
            Triple(h, s, l),
            Triple((h + angle) % 360, s, l)
        )
    }
    
    fun getTriadicColors(h: Double, s: Double, l: Double): List<Triple<Double, Double, Double>> {
        val angle = 120.0
        return listOf(
            Triple(h, s, l),
            Triple((h + angle) % 360, s, l),
            Triple((h + 2 * angle) % 360, s, l)
        )
    }
    
    fun calculateColorDistance(r1: Int, g1: Int, b1: Int, r2: Int, g2: Int, b2: Int): Double {
        val dr = (r1 - r2).toDouble()
        val dg = (g1 - g2).toDouble()
        val db = (b1 - b2).toDouble()
        return Math.sqrt(dr * dr + dg * dg + db * db)
    }
    
    fun analyzeColor(r: Int, g: Int, b: Int): Map<String, Any> {
        val hex = rgbToHex(r, g, b)
        val (h, s, l) = rgbToHsl(r, g, b)
        val luminance = calculateLuminance(r, g, b)
        
        val colorType = when {
            s < 10 -> "灰色"
            l < 20 -> "深色"
            l > 80 -> "浅色"
            else -> "彩色"
        }
        
        return mapOf(
            "hex" to hex,
            "rgb" to "RGB($r, $g, $b)",
            "hsl" to "HSL(${h.toInt()}, ${s.toInt()}%, ${l.toInt()}%)",
            "luminance" to String.format("%.4f", luminance),
            "colorType" to colorType
        )
    }
}

Kotlin 代码说明: 这个实现提供了完整的颜色转换和分析功能。每个方法都有明确的输入验证和错误处理。RGB 和 HEX 的转换是基础操作,而 HSL 的转换涉及更复杂的数学计算。此外,还提供了颜色对比度计算、颜色距离计算和颜色属性分析等功能。


JavaScript 编译代码

// ColorConversionAnalysis.js
class ColorConversionAnalysis {
    rgbToHex(r, g, b) {
        if (r < 0 || r > 255 || g < 0 || g > 255 || b < 0 || b > 255) {
            throw new Error("RGB 值必须在 0-255 之间");
        }
        const toHex = (n) => {
            const hex = n.toString(16);
            return hex.length === 1 ? '0' + hex : hex;
        };
        return '#' + toHex(r).toUpperCase() + toHex(g).toUpperCase() + toHex(b).toUpperCase();
    }
    
    hexToRgb(hex) {
        const cleanHex = hex.startsWith('#') ? hex.substring(1) : hex;
        if (cleanHex.length !== 6) {
            throw new Error("HEX 颜色必须是 6 位十六进制数字");
        }
        const r = parseInt(cleanHex.substring(0, 2), 16);
        const g = parseInt(cleanHex.substring(2, 4), 16);
        const b = parseInt(cleanHex.substring(4, 6), 16);
        return { r, g, b };
    }
    
    rgbToHsl(r, g, b) {
        const rNorm = r / 255.0;
        const gNorm = g / 255.0;
        const bNorm = b / 255.0;
        
        const max = Math.max(rNorm, gNorm, bNorm);
        const min = Math.min(rNorm, gNorm, bNorm);
        const delta = max - min;
        
        const lightness = (max + min) / 2.0;
        
        let saturation = 0;
        if (delta !== 0) {
            saturation = delta / (1 - Math.abs(2 * lightness - 1));
        }
        
        let hue = 0;
        if (delta !== 0) {
            if (max === rNorm) {
                hue = (60 * (((gNorm - bNorm) / delta) % 6) + 360) % 360;
            } else if (max === gNorm) {
                hue = (60 * (((bNorm - rNorm) / delta) + 2) + 360) % 360;
            } else {
                hue = (60 * (((rNorm - gNorm) / delta) + 4) + 360) % 360;
            }
        }
        
        return { h: hue, s: saturation * 100, l: lightness * 100 };
    }
    
    hslToRgb(h, s, l) {
        const sNorm = s / 100.0;
        const lNorm = l / 100.0;
        
        const c = (1 - Math.abs(2 * lNorm - 1)) * sNorm;
        const hPrime = h / 60.0;
        const x = c * (1 - Math.abs((hPrime % 2) - 1));
        
        let rPrime = 0, gPrime = 0, bPrime = 0;
        if (hPrime < 1) {
            rPrime = c; gPrime = x; bPrime = 0;
        } else if (hPrime < 2) {
            rPrime = x; gPrime = c; bPrime = 0;
        } else if (hPrime < 3) {
            rPrime = 0; gPrime = c; bPrime = x;
        } else if (hPrime < 4) {
            rPrime = 0; gPrime = x; bPrime = c;
        } else if (hPrime < 5) {
            rPrime = x; gPrime = 0; bPrime = c;
        } else {
            rPrime = c; gPrime = 0; bPrime = x;
        }
        
        const m = lNorm - c / 2.0;
        const r = Math.round((rPrime + m) * 255);
        const g = Math.round((gPrime + m) * 255);
        const b = Math.round((bPrime + m) * 255);
        
        return { r, g, b };
    }
    
    calculateLuminance(r, g, b) {
        const rNorm = r / 255.0;
        const gNorm = g / 255.0;
        const bNorm = b / 255.0;
        
        const rLinear = rNorm <= 0.03928 ? rNorm / 12.92 : Math.pow((rNorm + 0.055) / 1.055, 2.4);
        const gLinear = gNorm <= 0.03928 ? gNorm / 12.92 : Math.pow((gNorm + 0.055) / 1.055, 2.4);
        const bLinear = bNorm <= 0.03928 ? bNorm / 12.92 : Math.pow((bNorm + 0.055) / 1.055, 2.4);
        
        return 0.2126 * rLinear + 0.7152 * gLinear + 0.0722 * bLinear;
    }
    
    calculateContrast(r1, g1, b1, r2, g2, b2) {
        const lum1 = this.calculateLuminance(r1, g1, b1);
        const lum2 = this.calculateLuminance(r2, g2, b2);
        const lighter = Math.max(lum1, lum2);
        const darker = Math.min(lum1, lum2);
        return (lighter + 0.05) / (darker + 0.05);
    }
    
    getComplementaryColor(h, s, l) {
        const complementaryHue = (h + 180) % 360;
        return { h: complementaryHue, s, l };
    }
    
    getAnalogousColors(h, s, l) {
        const angle = 30.0;
        return [
            { h: (h - angle + 360) % 360, s, l },
            { h, s, l },
            { h: (h + angle) % 360, s, l }
        ];
    }
    
    getTriadicColors(h, s, l) {
        const angle = 120.0;
        return [
            { h, s, l },
            { h: (h + angle) % 360, s, l },
            { h: (h + 2 * angle) % 360, s, l }
        ];
    }
    
    calculateColorDistance(r1, g1, b1, r2, g2, b2) {
        const dr = r1 - r2;
        const dg = g1 - g2;
        const db = b1 - b2;
        return Math.sqrt(dr * dr + dg * dg + db * db);
    }
    
    analyzeColor(r, g, b) {
        const hex = this.rgbToHex(r, g, b);
        const hsl = this.rgbToHsl(r, g, b);
        const luminance = this.calculateLuminance(r, g, b);
        
        let colorType = '彩色';
        if (hsl.s < 10) {
            colorType = '灰色';
        } else if (hsl.l < 20) {
            colorType = '深色';
        } else if (hsl.l > 80) {
            colorType = '浅色';
        }
        
        return {
            hex: hex,
            rgb: `RGB(${r}, ${g}, ${b})`,
            hsl: `HSL(${Math.round(hsl.h)}, ${Math.round(hsl.s)}%, ${Math.round(hsl.l)}%)`,
            luminance: luminance.toFixed(4),
            colorType: colorType
        };
    }
}

JavaScript 代码说明: JavaScript 版本是 Kotlin 代码的直接转译。由于 JavaScript 和 Kotlin 在语法上有差异,我们需要进行相应的调整。例如,Kotlin 的 Triple 数据结构在 JavaScript 中使用对象来表示。整体逻辑和算法与 Kotlin 版本保持一致,确保跨平台的一致性。


ArkTS 调用代码

// ColorConversionPage.ets
import { ColorConversionAnalysis } from './ColorConversionAnalysis';

@Entry
@Component
struct ColorConversionPage {
    @State selectedColor: string = '#FF6432';
    @State colorR: number = 255;
    @State colorG: number = 100;
    @State colorB: number = 50;
    @State colorHex: string = '#FF6432';
    @State colorHsl: string = '';
    @State colorType: string = '';
    @State luminance: string = '';
    @State complementaryColor: string = '';
    @State analogousColors: string[] = [];
    @State triadicColors: string[] = [];
    @State contrastRatio: string = '';
    
    private tool: ColorConversionAnalysis = new ColorConversionAnalysis();
    
    updateColorAnalysis() {
        try {
            const analysis = this.tool.analyzeColor(this.colorR, this.colorG, this.colorB);
            this.colorHex = analysis.hex;
            this.colorHsl = analysis.hsl;
            this.colorType = analysis.colorType;
            this.luminance = analysis.luminance;
            
            const hsl = this.tool.rgbToHsl(this.colorR, this.colorG, this.colorB);
            const comp = this.tool.getComplementaryColor(hsl.h, hsl.s, hsl.l);
            const compRgb = this.tool.hslToRgb(comp.h, comp.s, comp.l);
            this.complementaryColor = this.tool.rgbToHex(compRgb.r, compRgb.g, compRgb.b);
            
            const analogous = this.tool.getAnalogousColors(hsl.h, hsl.s, hsl.l);
            this.analogousColors = analogous.map(color => {
                const rgb = this.tool.hslToRgb(color.h, color.s, color.l);
                return this.tool.rgbToHex(rgb.r, rgb.g, rgb.b);
            });
            
            const triadic = this.tool.getTriadicColors(hsl.h, hsl.s, hsl.l);
            this.triadicColors = triadic.map(color => {
                const rgb = this.tool.hslToRgb(color.h, color.s, color.l);
                return this.tool.rgbToHex(rgb.r, rgb.g, rgb.b);
            });
            
            const contrast = this.tool.calculateContrast(this.colorR, this.colorG, this.colorB, 255, 255, 255);
            this.contrastRatio = contrast.toFixed(2);
        } catch (error) {
            AlertDialog.show({ message: '颜色分析失败' });
        }
    }
    
    build() {
        Column() {
            Text('颜色转换和分析工具')
                .fontSize(24)
                .fontWeight(FontWeight.Bold)
                .margin({ top: 20, bottom: 20 })
            
            Row() {
                Column() {
                    Text('当前颜色').fontSize(12).fontColor('#666666').margin({ bottom: 5 })
                    Rect().width(80).height(80).fill(this.selectedColor)
                        .border({ width: 2, color: '#cccccc' }).borderRadius(4)
                }
                .margin({ right: 20 })
                
                Column() {
                    Text('互补色').fontSize(12).fontColor('#666666').margin({ bottom: 5 })
                    Rect().width(80).height(80).fill(this.complementaryColor)
                        .border({ width: 2, color: '#cccccc' }).borderRadius(4)
                }
            }
            .margin({ bottom: 20 })
            
            TextInput({ placeholder: '输入 HEX 颜色代码' })
                .value(this.colorHex)
                .onChange((value: string) => {
                    try {
                        const rgb = this.tool.hexToRgb(value);
                        this.colorR = rgb.r;
                        this.colorG = rgb.g;
                        this.colorB = rgb.b;
                        this.selectedColor = value;
                        this.updateColorAnalysis();
                    } catch (error) {}
                })
                .margin({ bottom: 15 })
                .padding(10)
                .border({ width: 1, color: '#cccccc' })
            
            Column() {
                Row() {
                    Text('R:').width(30)
                    Slider({ value: this.colorR, min: 0, max: 255 })
                        .flex(1)
                        .onChange((value: number) => {
                            this.colorR = Math.round(value);
                            this.selectedColor = this.tool.rgbToHex(this.colorR, this.colorG, this.colorB);
                            this.updateColorAnalysis();
                        })
                    Text(this.colorR.toString()).width(40)
                }
                .margin({ bottom: 10 })
                
                Row() {
                    Text('G:').width(30)
                    Slider({ value: this.colorG, min: 0, max: 255 })
                        .flex(1)
                        .onChange((value: number) => {
                            this.colorG = Math.round(value);
                            this.selectedColor = this.tool.rgbToHex(this.colorR, this.colorG, this.colorB);
                            this.updateColorAnalysis();
                        })
                    Text(this.colorG.toString()).width(40)
                }
                .margin({ bottom: 10 })
                
                Row() {
                    Text('B:').width(30)
                    Slider({ value: this.colorB, min: 0, max: 255 })
                        .flex(1)
                        .onChange((value: number) => {
                            this.colorB = Math.round(value);
                            this.selectedColor = this.tool.rgbToHex(this.colorR, this.colorG, this.colorB);
                            this.updateColorAnalysis();
                        })
                    Text(this.colorB.toString()).width(40)
                }
            }
            .margin({ bottom: 20 })
            
            Column() {
                Text('颜色信息').fontSize(14).fontWeight(FontWeight.Bold).margin({ bottom: 10 })
                Row() { Text('HEX:').width(60); Text(this.colorHex).flex(1).selectable(true) }.margin({ bottom: 8 })
                Row() { Text('HSL:').width(60); Text(this.colorHsl).flex(1).selectable(true) }.margin({ bottom: 8 })
                Row() { Text('类型:').width(60); Text(this.colorType).flex(1) }.margin({ bottom: 8 })
                Row() { Text('亮度:').width(60); Text(this.luminance).flex(1) }.margin({ bottom: 8 })
                Row() { Text('对比度:').width(60); Text(this.contrastRatio).flex(1) }
            }
            .width('100%')
            .padding(10)
            .backgroundColor('#f5f5f5')
            .borderRadius(4)
            .margin({ bottom: 20 })
            
            Column() {
                Text('类似色').fontSize(14).fontWeight(FontWeight.Bold).margin({ bottom: 10 })
                Row() {
                    ForEach(this.analogousColors, (color: string) => {
                        Rect().width(50).height(50).fill(color)
                            .border({ width: 1, color: '#cccccc' }).borderRadius(4).margin({ right: 10 })
                    })
                }
            }
            .width('100%')
            .margin({ bottom: 20 })
            
            Column() {
                Text('三角配色').fontSize(14).fontWeight(FontWeight.Bold).margin({ bottom: 10 })
                Row() {
                    ForEach(this.triadicColors, (color: string) => {
                        Rect().width(50).height(50).fill(color)
                            .border({ width: 1, color: '#cccccc' }).borderRadius(4).margin({ right: 10 })
                    })
                }
            }
            .width('100%')
        }
        .padding(20)
        .width('100%')
        .height('100%')
        .backgroundColor('#ffffff')
        .onAppear(() => { this.updateColorAnalysis() })
    }
}

ArkTS 代码说明: 这个示例展示了如何在 ArkTS 中构建一个完整的颜色转换和分析用户界面。页面包含了颜色预览、HEX 输入框、RGB 滑块、颜色信息显示、类似色和三角配色等组件。用户可以通过输入 HEX 代码或调整 RGB 滑块来改变颜色,系统会实时计算并显示颜色的各种属性和相关配色方案。


实战案例

案例 1: 网页设计中的颜色选择

在网页设计中,选择合适的颜色方案至关重要。使用颜色工具可以快速生成和谐的配色方案。设计师可以先选择一个主色调,然后使用工具生成互补色、类似色或三角配色方案。这样可以确保整个网站的颜色搭配协调一致,提高用户体验。

val tool = ColorConversionAnalysis()
val primaryColor = Triple(255, 100, 50)
val (h, s, l) = tool.rgbToHsl(primaryColor.first, primaryColor.second, primaryColor.third)
val complementary = tool.getComplementaryColor(h, s, l)
val complementaryRgb = tool.hslToRgb(complementary.first, complementary.second, complementary.third)
val complementaryHex = tool.rgbToHex(complementaryRgb.first, complementaryRgb.second, complementaryRgb.third)

案例 2: 无障碍设计中的对比度检查

在开发应用时,需要确保文本和背景颜色有足够的对比度,以便用户能够清晰地阅读。WCAG 标准要求最小对比度为 4.5:1。使用颜色工具可以快速检查颜色对是否满足无障碍要求。

val tool = ColorConversionAnalysis()
val textColor = Triple(0, 0, 0)
val backgroundColor = Triple(255, 255, 255)
val contrast = tool.calculateContrast(
    textColor.first, textColor.second, textColor.third,
    backgroundColor.first, backgroundColor.second, backgroundColor.third
)
val isAccessible = contrast >= 4.5

案例 3: 数据可视化中的颜色映射

在数据可视化中,需要将数据值映射到颜色。使用 HSL 颜色空间可以轻松创建渐变色。通过改变亮度值,可以创建从浅色到深色的渐变,这样可以直观地表示数据的大小。

val tool = ColorConversionAnalysis()
val dataValues = listOf(10, 20, 30, 40, 50)
val colors = dataValues.map { value ->
    val normalizedValue = value / 50.0
    val lightness = 50 + (normalizedValue * 30)
    val hsl = Triple(220.0, 80.0, lightness)
    val rgb = tool.hslToRgb(hsl.first, hsl.second, hsl.third)
    tool.rgbToHex(rgb.first, rgb.second, rgb.third)
}

最佳实践

1. 颜色空间选择

  • RGB:用于屏幕显示和编程
  • HSL/HSV:用于颜色选择和调整
  • CMYK:用于印刷设计
  • 根据场景选择合适的颜色空间

2. 对比度检查

  • 遵循 WCAG 标准:确保最小对比度为 4.5:1
  • 定期测试:在不同设备上测试颜色对比度
  • 考虑色盲用户:使用工具检查色盲用户能否区分颜色

3. 颜色一致性

  • 使用调色板:为整个应用定义一套标准颜色
  • 文档化颜色:记录每个颜色的用途和含义
  • 跨平台验证:确保颜色在不同平台上的一致性

4. 性能优化

  • 缓存转换结果:对于频繁使用的颜色转换,使用缓存
  • 预计算调色板:在应用启动时预计算常用的颜色方案
  • 异步处理:对于大量颜色处理,使用异步操作

5. 用户体验

  • 实时预览:提供实时的颜色预览功能
  • 快速反馈:确保颜色调整的响应速度
  • 直观界面:使用滑块和颜色选择器等直观的控件

总结

颜色转换和分析工具是现代应用开发中的一个重要组件。通过使用 Kotlin Multiplatform,我们可以编写一次代码,然后在多个平台上运行,大大提高了开发效率和代码的可维护性。这个案例展示了如何实现多种颜色空间之间的转换、进行颜色分析、生成和谐的配色方案等功能。

在实际应用中,应该根据具体的需求选择合适的颜色空间和算法,并遵循最佳实践来确保颜色的准确性和一致性。同时,应该定期进行测试和优化,以提高应用的性能和用户体验。通过合理使用颜色工具,我们可以创建更加美观、易用和无障碍的应用。欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐