Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions IccJSON/IccLibJSON/IccProfileJson.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -82,8 +82,8 @@ static IccJson icJsonGetHeaderFlags(icUInt32Number flags)
j["UseWithEmbeddedDataOnly"] = (bool)(flags & icUseWithEmbeddedDataOnly);
if (flags & icExtendedRangePCS) j["ExtendedRangePCS"] = true;
if (flags & icMCSNeedsSubsetTrue) j["MCSNeedsSubset"] = true;
icUInt32Number other = flags & ~(icEmbeddedProfileTrue | icUseWithEmbeddedDataOnly |
icExtendedRangePCS | icMCSNeedsSubsetTrue);
icUInt32Number other = flags & ~(icUInt32Number)(icEmbeddedProfileTrue | icUseWithEmbeddedDataOnly |
icExtendedRangePCS | icMCSNeedsSubsetTrue);
if (other) {
char buf[16]; snprintf(buf, sizeof(buf), "%08x", other);
j["VendorFlags"] = buf;
Expand Down
84 changes: 64 additions & 20 deletions IccJSON/IccLibJSON/IccTagJson.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -727,11 +727,17 @@ bool CIccTagJsonColorantOrder::ParseJson(const IccJson &j, std::string & /*parse

bool CIccTagJsonColorantTable::ToJson(IccJson &j)
{
j["pcsEncoding"] = "Lab";
IccJson arr = IccJson::array();
for (icUInt32Number i = 0; i < m_nCount; i++) {
icFloatNumber pcs[3];
pcs[0] = icU16toF(m_pData[i].data[0]);
pcs[1] = icU16toF(m_pData[i].data[1]);
pcs[2] = icU16toF(m_pData[i].data[2]);
icLabFromPcs(pcs);
IccJson c;
c["name"] = m_pData[i].name;
c["pcs"] = IccJson::array({ m_pData[i].data[0], m_pData[i].data[1], m_pData[i].data[2] });
c["pcs"] = IccJson::array({ pcs[0], pcs[1], pcs[2] });
arr.push_back(c);
}
j["colorantTable"] = arr;
Expand All @@ -740,6 +746,9 @@ bool CIccTagJsonColorantTable::ToJson(IccJson &j)

bool CIccTagJsonColorantTable::ParseJson(const IccJson &j, std::string & /*parseStr*/)
{
std::string pcsEncoding = "Lab";
jGetString(j, "pcsEncoding", pcsEncoding);

if (jsonExistsField(j, "colorantTable") && j["colorantTable"].is_array()) {
const IccJson &arr = j["colorantTable"];
if (!SetSize((icUInt32Number)arr.size())) return false;
Expand All @@ -748,8 +757,21 @@ bool CIccTagJsonColorantTable::ParseJson(const IccJson &j, std::string & /*parse
std::string name;
if (jGetString(c, "name", name))
strncpy(m_pData[i].name, name.c_str(), sizeof(m_pData[i].name)-1);
if (jsonExistsField(c, "pcs") && c["pcs"].is_array() && c["pcs"].size() >= 3)
jGetArray(c, "pcs", m_pData[i].data, 3);
if (jsonExistsField(c, "pcs") && c["pcs"].is_array() && c["pcs"].size() >= 3) {
if (pcsEncoding == "16bit") {
jGetArray(c, "pcs", m_pData[i].data, 3);
} else {
icFloatNumber pcs[3];
jGetArray(c, "pcs", pcs, 3);
if (pcsEncoding == "XYZ")
icXyzToPcs(pcs);
else // "Lab" (default)
icLabToPcs(pcs);
m_pData[i].data[0] = icFtoU16(pcs[0]);
m_pData[i].data[1] = icFtoU16(pcs[1]);
m_pData[i].data[2] = icFtoU16(pcs[2]);
}
}
}
}
return true;
Expand Down Expand Up @@ -1386,24 +1408,38 @@ bool CIccTagJsonCurve::ToJson(IccJson &j, icConvertType nType)
j["curveType"] = "gamma";
j["gamma"] = (double)m_Curve[0];
} else {
j["curveType"] = "table";
IccJson arr = IccJson::array();
for (icUInt32Number i = 0; i < m_nSize; i++) {
switch (nType) {
case icConvert8Bit:
arr.push_back((int)(m_Curve[i] * 255.0f + 0.5f)); break;
case icConvert16Bit:
case icConvertVariable:
arr.push_back((int)(m_Curve[i] * 65535.0f + 0.5f)); break;
default:
arr.push_back((double)m_Curve[i]); break;
// Check whether the table is a sampled identity (linear ramp 0..1).
// Tolerance of 0.5/65535 covers 16-bit quantisation rounding.
bool isIdentity = true;
const icFloatNumber tol = 0.5f / 65535.0f;
for (icUInt32Number i = 0; i < m_nSize && isIdentity; i++) {
icFloatNumber expected = (icFloatNumber)i / (icFloatNumber)(m_nSize - 1);
if (fabsf(m_Curve[i] - expected) > tol)
isIdentity = false;
}
if (isIdentity) {
j["curveType"] = "identity";
j["size"] = (int)m_nSize;
} else {
j["curveType"] = "table";
IccJson arr = IccJson::array();
for (icUInt32Number i = 0; i < m_nSize; i++) {
switch (nType) {
case icConvert8Bit:
arr.push_back((int)(m_Curve[i] * 255.0f + 0.5f)); break;
case icConvert16Bit:
case icConvertVariable:
arr.push_back((int)(m_Curve[i] * 65535.0f + 0.5f)); break;
default:
arr.push_back((double)m_Curve[i]); break;
}
}
if (nType == icConvert8Bit)
j["precision"] = 1;
else if (nType == icConvert16Bit || nType == icConvertVariable)
j["precision"] = 2;
j["table"] = arr;
}
if (nType == icConvert8Bit)
j["precision"] = 1;
else if (nType == icConvert16Bit || nType == icConvertVariable)
j["precision"] = 2;
j["table"] = arr;
}
return true;
}
Expand All @@ -1418,7 +1454,15 @@ bool CIccTagJsonCurve::ParseJson(const IccJson &j, icConvertType /*nType*/, std:
std::string curveType;
if (!jGetString(j, "curveType", curveType)) return false;
if (curveType == "identity") {
SetSize(0);
int size = 0;
jGetValue(j, "size", size);
if (size >= 2) {
if (!SetSize((icUInt32Number)size)) return false;
for (int i = 0; i < size; i++)
m_Curve[i] = (icFloatNumber)i / (icFloatNumber)(size - 1);
} else {
SetSize(0);
}
} else if (curveType == "gamma") {
SetSize(1);
double gamma = 1.0;
Expand Down
15 changes: 14 additions & 1 deletion docs/icc-profile.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -234,6 +234,7 @@
"required": ["curveType"],
"properties": {
"curveType": { "type": "string", "enum": ["identity", "gamma", "table"] },
"size": { "type": "integer", "minimum": 2, "description": "Number of samples in a sampled-identity curve (when curveType is 'identity' and the curve has 2 or more entries). Omitted for the 0-entry ICC identity." },
"gamma": { "type": "number", "description": "Gamma exponent (when curveType is 'gamma')" },
"precision": { "type": "integer", "enum": [1, 2], "description": "Table encoding: 1 = 8-bit integers (0–255), 2 = 16-bit integers (0–65535). Omitted for floating-point." },
"table": { "type": "array", "items": { "type": "number" }, "description": "Curve samples (when curveType is 'table'). Integers when precision is present, floats otherwise." }
Expand Down Expand Up @@ -343,14 +344,25 @@
"description": "colorantTableType",
"required": ["colorantTable"],
"properties": {
"pcsEncoding": {
"type": "string",
"enum": ["Lab", "XYZ", "16bit"],
"description": "Encoding used for the 'pcs' arrays. 'Lab': L* (0–100), a*, b* (−128–127). 'XYZ': X, Y, Z floats (0–2 range). '16bit': raw ICC U16 integers. ToJson always writes 'Lab'; ParseJson defaults to 'Lab' when absent."
},
"colorantTable": {
"type": "array",
"items": {
"type": "object",
"required": ["name", "pcs"],
"properties": {
"name": { "type": "string" },
"pcs": { "$ref": "#/$defs/XYZTriplet" }
"pcs": {
"type": "array",
"description": "PCS colorant coordinates. Interpretation depends on pcsEncoding.",
"items": { "type": "number" },
"minItems": 3,
"maxItems": 3
}
}
}
}
Expand Down Expand Up @@ -567,6 +579,7 @@
"properties": {
"type": { "type": "string", "enum": ["Curve", "ParametricCurve", "SegmentedCurve"] },
"curveType": { "type": "string", "enum": ["identity", "gamma", "table"] },
"size": { "type": "integer", "minimum": 2, "description": "Number of samples for a sampled-identity curve (when curveType is 'identity' and size >= 2)." },
"gamma": { "type": "number" },
"precision": { "type": "integer", "enum": [1, 2], "description": "Table encoding: 1 = 8-bit (0–255), 2 = 16-bit (0–65535). Omitted for float curves." },
"table": { "type": "array", "items": { "type": "number" }, "description": "Integers when precision present, floats otherwise." },
Expand Down
46 changes: 44 additions & 2 deletions docs/iccjson.md
Original file line number Diff line number Diff line change
Expand Up @@ -261,7 +261,7 @@ Each XYZ value is a JSON array `[X, Y, Z]`. The `"XYZ"` field is an array of suc

The `"curveType"` field selects one of three subtypes: `"identity"`, `"gamma"`, or `"table"`.

Identity (no-op):
Identity (0-entry ICC no-op):
```json
{
"redTRCTag": {
Expand All @@ -270,6 +270,17 @@ Identity (no-op):
}
```

Sampled identity (linear ramp with a specific number of entries — emitted instead of a full table when all entries match the ramp `i/(n-1)` within 16-bit quantisation tolerance):
```json
{
"redTRCTag": {
"data": { "type": "curveType", "curveType": "identity", "size": 256 }
}
}
```

When parsing, a `"size"` of 2 or more reconstructs the linear ramp `m_Curve[i] = i/(size-1)`. When `"size"` is absent the 0-entry ICC identity is used.

Gamma:
```json
{
Expand Down Expand Up @@ -354,6 +365,37 @@ Segment types:
| `FormulaSegment` | `functionType`, `parameters` |
| `SampledSegment` | `samples` |

### Colorant Tags

#### `colorantTableType` — colorant names and PCS coordinates

Because `ToJson`/`ParseJson` have no access to the profile header, the encoding of the `"pcs"` arrays is declared explicitly via a `"pcsEncoding"` field:

| `pcsEncoding` | `pcs` array contents |
|---------------|----------------------|
| `"Lab"` (default) | L\* (0–100), a\*, b\* (−128–127) — human-readable CIE Lab |
| `"XYZ"` | X, Y, Z floats (0–2 range) |
| `"16bit"` | Raw ICC U16 integers (0–65535) |

`ToJson` always writes `"pcsEncoding": "Lab"`. `ParseJson` defaults to `"Lab"` when the field is absent (backward compatible with files written before this field was added).

```json
{
"colorantTableTag": {
"data": {
"type": "colorantTableType",
"pcsEncoding": "Lab",
"colorantTable": [
{ "name": "Cyan", "pcs": [55.11, -37.40, -5.18] },
{ "name": "Magenta", "pcs": [48.24, 74.12, -49.52] },
{ "name": "Yellow", "pcs": [89.02, -5.68, 93.14] },
{ "name": "Black", "pcs": [ 0.00, 0.00, 0.00] }
]
}
}
}
```

### Signature Tags

#### `signatureType`
Expand Down Expand Up @@ -428,7 +470,7 @@ MBB curve type values:

| `type` | Subtype fields |
|------------------|---------------------------------------------|
| `Curve` | `curveType` (`"identity"`, `"gamma"`, `"table"`), `gamma`, `table` |
| `Curve` | `curveType` (`"identity"`, `"gamma"`, `"table"`), `size` (identity with entries), `gamma`, `table` |
| `ParametricCurve`| `functionType`, `params` |
| `SegmentedCurve` | `segments` array |

Expand Down
Loading