├── .editorconfig ├── .gitattributes ├── license ├── readme.md └── schema.json /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = tab 5 | end_of_line = lf 6 | charset = utf-8 7 | trim_trailing_whitespace = true 8 | insert_final_newline = true 9 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto eol=lf 2 | -------------------------------------------------------------------------------- /license: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) Sindre Sorhus (https://sindresorhus.com) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 10 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # Simple Color Palette Spec v0.1 2 | 3 | > A minimal JSON-based file format for defining color palettes 4 | 5 | It uses [extended linear sRGB](#what-is-extended-linear-srgb) with optional opacity. 6 | 7 | [Learn more ›](https://simplecolorpalette.com) 8 | 9 | [**Feedback wanted!**](https://github.com/simple-color-palette/spec/issues) 10 | 11 | > [!CAUTION] 12 | > Work in progress. 13 | 14 | ## Links 15 | 16 | - [JSON Schema](schema.json) *(v0.1)* 17 | 18 | ## JSON Structure 19 | 20 | ```json 21 | { 22 | "name": "Favorites", 23 | "colors": [ 24 | { 25 | "name": "Hot Pink", 26 | "components": [1, 0.1274, 0.418] 27 | }, 28 | { 29 | "components": [0.592, 0.278, 0.996, 0.9] 30 | } 31 | ] 32 | } 33 | ``` 34 | 35 | - Palette name is optional. 36 | - Color name is optional. 37 | - Opacity is optional. 38 | 39 | ## Details 40 | 41 | ### Top-level Object 42 | 43 | - `name`: Optional. String. Name of the palette. If omitted, use the filename without extension. 44 | - `colors`: Required. Array of color entries. 45 | 46 | ### Color Entry 47 | 48 | Each color is an object with the following fields: 49 | 50 | - `name`: Optional. String. 51 | - `components`: Required. Array of 3 or 4 floating-point numbers. 52 | - `[red, green, blue]` or `[red, green, blue, opacity]` 53 | - The color should use extended linear sRGB color space. 54 | - The color components can be negative and more than 1. 55 | - The opacity defaults to 1 if omitted. 56 | - The opacity should be clamped to `0...1` range when reading and writing the palette. It should not throw if outside the range. 57 | 58 | ### Numeric Precision 59 | 60 | All numeric values in the color components array must be stored with a maximum of 4 decimal places, using banker's rounding (round-half-to-even) when needed. For example, 0.12345 becomes 0.1235, while 0.12350 rounds to 0.1235 (nearest even). This applies to both RGB components and opacity. 61 | 62 | #### Why 4 Decimal Places? 63 | 64 | - Provides up to 10000 distinct values per component (0 to 10000). 65 | - Exceeds human color perception capabilities. 66 | - Matches or exceeds most display technology precision. 67 | - Sufficient for HDR and professional color work. 68 | - Enables reliable color deduplication and comparison. 69 | - Preserves color identity through format conversions and rounding errors. 70 | 71 | #### Implementation Considerations 72 | 73 | - Use banker's rounding to prevent statistical bias. 74 | - Perform calculations at full precision. 75 | - Apply rounding only for final storage or display. 76 | - Validate that stored values never exceed 4 decimal places. 77 | 78 | #### Example (Swift) 79 | 80 | ```swift 81 | extension Double { 82 | /** 83 | Rounds the number to specified decimal places using banker's rounding. 84 | 85 | - Parameter places: Number of decimal places (must be >= 0). 86 | - Returns: The rounded number. 87 | */ 88 | func rounded(toPlaces places: Int) -> Self { 89 | guard places >= 0 else { 90 | return self 91 | } 92 | 93 | let multiplier = pow(10.0, Self(places)) 94 | return (self * multiplier).rounded(.toNearestOrEven) / multiplier 95 | } 96 | } 97 | 98 | let number = 3.14159265359 99 | number.rounded(toPlaces: 4) 100 | //=> 3.1416 101 | ``` 102 | 103 | ## Format 104 | 105 | ### File Extension 106 | 107 | `.color-palette` 108 | 109 | ### MIME Type 110 | 111 | ``` 112 | application/x.sindresorhus.simple-color-palette+json 113 | ``` 114 | 115 | *(temporary)* 116 | 117 | ### Uniform Type Identifier 118 | 119 | ``` 120 | com.sindresorhus.simple-color-palette 121 | ``` 122 | 123 | #### Swift example 124 | 125 | ```swift 126 | extension UTType { 127 | static var simpleColorPalette: Self { .init(importedAs: "com.sindresorhus.simple-color-palette", conformingTo: .json) } 128 | } 129 | ``` 130 | 131 | #### `Info.plist` example 132 | 133 | ```xml 134 | UTImportedTypeDeclarations 135 | 136 | 137 | UTTypeIdentifier 138 | com.sindresorhus.simple-color-palette 139 | UTTypeDescription 140 | Simple Color Palette 141 | UTTypeConformsTo 142 | 143 | public.json 144 | 145 | UTTypeTagSpecification 146 | 147 | public.filename-extension 148 | 149 | color-palette 150 | 151 | public.mime-type 152 | application/x.sindresorhus.simple-color-palette+json 153 | 154 | 155 | 156 | CFBundleDocumentTypes 157 | 158 | 159 | CFBundleTypeName 160 | Simple Color Palette 161 | LSItemContentTypes 162 | 163 | com.sindresorhus.simple-color-palette 164 | 165 | CFBundleTypeExtensions 166 | 167 | color-palette 168 | 169 | CFBundleTypeRole 170 | Viewer 171 | LSHandlerRank 172 | Alternate 173 | 174 | 175 | ``` 176 | 177 | ## FAQ 178 | 179 | ### Why JSON? 180 | 181 | - Human-readable. 182 | - Ubiquitous and supported everywhere. 183 | - Easy to parse in any language. 184 | 185 | ### Why is the color space hard-coded? 186 | 187 | - Simplicity and clarity - no ambiguity about color interpretation. 188 | - Avoiding the complexity of color space conversion and management. 189 | - Color space conversion is better handled at the app level when needed. 190 | 191 | ### What is “extended linear sRGB”? 192 | 193 | The same color space as [sRGB](https://en.wikipedia.org/wiki/SRGB) but with two differences: 194 | 1. Extended: Values can go beyond 0-1 for HDR colors. 195 | 1. Linear: Raw RGB values without [gamma correction](https://en.wikipedia.org/wiki/Gamma_correction). 196 | 197 | ### What is gamma correction? 198 | 199 | Standard sRGB uses a non-linear encoding to match human perception. This format uses raw linear values instead - better for color computations but requires conversion (gamma correction) for display. 200 | 201 | ### Why extended sRGB? 202 | 203 | - Allows values outside 0-1 range for HDR and wide-gamut colors. 204 | - Simple extension of familiar sRGB. 205 | - Easy to clamp to standard sRGB when needed. 206 | 207 | ### Why linear color? 208 | 209 | - Eliminates gamma correction ambiguity. 210 | - More accurate color blending and interpolation. 211 | 212 | ### Why not Display P3 or other color spaces? 213 | 214 | No benefit over extended sRGB since both can represent the same range of colors. 215 | 216 | ### Why no CMYK support? 217 | 218 | CMYK is output-dependent and requires device-specific ICC profiles. This format is for authoring and sharing colors, not managing print workflows. 219 | 220 | ### Does the format support gradients? 221 | 222 | No, only flat colors are supported. 223 | -------------------------------------------------------------------------------- /schema.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json-schema.org/draft/2020-12/schema", 3 | "title": "Simple Color Palette", 4 | "description": "A minimal JSON-based format for defining color palettes", 5 | "$comment": "File extension: .color-palette", 6 | "type": "object", 7 | "additionalProperties": false, 8 | "required": [ 9 | "colors" 10 | ], 11 | "properties": { 12 | "name": { 13 | "type": "string", 14 | "description": "Optional name of the palette. If omitted, use filename without extension", 15 | "minLength": 1 16 | }, 17 | "colors": { 18 | "type": "array", 19 | "description": "Array of color entries", 20 | "minItems": 1, 21 | "items": { 22 | "type": "object", 23 | "additionalProperties": false, 24 | "required": [ 25 | "components" 26 | ], 27 | "properties": { 28 | "name": { 29 | "type": "string", 30 | "description": "Optional color name", 31 | "minLength": 1 32 | }, 33 | "components": { 34 | "type": "array", 35 | "description": "RGB or RGBA color components in extended linear sRGB color space", 36 | "minItems": 3, 37 | "maxItems": 4, 38 | "items": false, 39 | "prefixItems": [ 40 | { 41 | "type": "number", 42 | "title": "red", 43 | "description": "Red color component (can be negative or > 1 for HDR)" 44 | }, 45 | { 46 | "type": "number", 47 | "title": "green", 48 | "description": "Green color component (can be negative or > 1 for HDR)" 49 | }, 50 | { 51 | "type": "number", 52 | "title": "blue", 53 | "description": "Blue color component (can be negative or > 1 for HDR)" 54 | }, 55 | { 56 | "type": "number", 57 | "title": "opacity", 58 | "description": "Opacity (defaults to 1 if omitted)", 59 | "minimum": 0, 60 | "maximum": 1 61 | } 62 | ], 63 | "examples": [ 64 | [1, 0.1274, 0.418], 65 | [0.592, 0.278, 0.996, 0.9], 66 | [-0.2, 1.5, 2.0, 0.5] 67 | ] 68 | } 69 | } 70 | }, 71 | "examples": [ 72 | [ 73 | { 74 | "name": "Hot Pink", 75 | "components": [1, 0.1274, 0.418] 76 | }, 77 | { 78 | "components": [0.592, 0.278, 0.996, 0.9] 79 | } 80 | ] 81 | ] 82 | } 83 | }, 84 | "examples": [ 85 | { 86 | "name": "Favorites", 87 | "colors": [ 88 | { 89 | "name": "Hot Pink", 90 | "components": [1, 0.1274, 0.418] 91 | }, 92 | { 93 | "components": [0.592, 0.278, 0.996, 0.9] 94 | } 95 | ] 96 | } 97 | ] 98 | } 99 | --------------------------------------------------------------------------------