Describe the Bug
A CIccXform whose destination is an MCS port receives the XYZ media-white /
black-point affine on its first three channels, because AdjustPCS() treats
pixel[0..2] as X, Y, Z regardless of what the port actually carries. Channels
3 and above pass through untouched.
Reached via an IIccAdjustPCSXform hint — in practice CIccApplyBPCHint, used by
iccApplyToLink, IccWrapper and IccConnect.
Why it happens
Three facts combine:
m_bAdjustPCS is set with no port test. The hint path in
CIccXform::Begin() sets it purely on CalcFactors() succeeding.
CIccApplyBPC::CalcFactors() constrains the profile — it rejects absolute
intent and the icSigLinkClass / icSigAbstractClass / icSigNamedColorClass
device classes — but never the port.
GetDstSpace() returns m_Header.mcs when m_nMCS == icToMCS, with no
colorimetric test.
- No handover happens.
CheckPCSConnections()'s two chain-edge blocks gate on
IsSpaceColorimetricPCS(), which an MCS signature never satisfies, so no
CIccPcsXform is built and the xform performs the adjustment itself.
CIccXformMpe's override adds only an intent test, and at perceptual intent that
test is satisfied rather than violated, so it does not exclude the case.
Build Instructions
git clone https://github.com/InternationalColorConsortium/DemoIccMAX.git
cd DemoIccMAX
cmake --preset vs2022-x64 -B msvc -S Build/Cmake -DCMAKE_EXPORT_COMPILE_COMMANDS=ON
cmake --build msvc -- /m /maxcpucount
Reproduce Bug or Crash
Construct an icSigInputClass v5 profile carrying both AToB0 and AToM0, apply it
with icXformLutMCS at icPerceptual with a CIccApplyBPCHint attached, and feed an
identity AToM0.
Input 0.20 0.40 0.60 0.80:
observed 0.213048 0.409379 0.587843 0.800000
expected 0.200000 0.400000 0.600000 0.800000
^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ← XYZ affine applied to MCS channels
A regression test that pins this exists on branch refactor/pcs-adjust-in-pcsxform
as pcsAdjustHintReachesANonPcsPort() in
.github/ci/regression/pcs-adjust-placement.cpp.
Root cause shared with #2331
m_bAdjustPCS can be set on an xform with no guarantee its port is XYZ or Lab —
the two IsSpacePCS(m_Header.pcs) setters test the profile header, not the port,
and the hint path tests nothing at all — while AdjustPCS() unconditionally reads
pixel[0..2] as X, Y, Z. A spectral PCS port and an MCS port are two consequences of
that single assumption.
Status and the open question
Branch refactor/pcs-adjust-in-pcsxform incidentally stops this — it retires the
in-Apply() adjustment path entirely, so nothing applies the affine to an MCS port any
more. That was not the branch's goal; it fell out of the retirement, and it is a
behaviour change relative to master.
The open question is whether stopping is the whole answer:
- If yes — the same reasoning already applied to spectral PCS ports, where an XYZ
affine over samples that are not X, Y, Z is simply wrong — then no further work is
needed and this issue closes with that branch.
- If no — if an MCS port should receive some defined adjustment of its own — then
that needs specifying, and CheckPCSConnections() would need to hand MCS ports to
CIccPcsXform the way it does colorimetric ones.
Describe the Bug
A
CIccXformwhose destination is an MCS port receives the XYZ media-white /black-point affine on its first three channels, because
AdjustPCS()treatspixel[0..2]as X, Y, Z regardless of what the port actually carries. Channels3 and above pass through untouched.
Reached via an
IIccAdjustPCSXformhint — in practiceCIccApplyBPCHint, used byiccApplyToLink,IccWrapperandIccConnect.Why it happens
Three facts combine:
m_bAdjustPCSis set with no port test. The hint path inCIccXform::Begin()sets it purely onCalcFactors()succeeding.CIccApplyBPC::CalcFactors()constrains the profile — it rejects absoluteintent and the
icSigLinkClass/icSigAbstractClass/icSigNamedColorClassdevice classes — but never the port.
GetDstSpace()returnsm_Header.mcswhenm_nMCS == icToMCS, with nocolorimetric test.
CheckPCSConnections()'s two chain-edge blocks gate onIsSpaceColorimetricPCS(), which an MCS signature never satisfies, so noCIccPcsXformis built and the xform performs the adjustment itself.CIccXformMpe's override adds only an intent test, and at perceptual intent thattest is satisfied rather than violated, so it does not exclude the case.
Build Instructions
Reproduce Bug or Crash
Construct an
icSigInputClassv5 profile carrying bothAToB0andAToM0, apply itwith
icXformLutMCSaticPerceptualwith aCIccApplyBPCHintattached, and feed anidentity
AToM0.Input
0.20 0.40 0.60 0.80:A regression test that pins this exists on branch
refactor/pcs-adjust-in-pcsxformas
pcsAdjustHintReachesANonPcsPort()in.github/ci/regression/pcs-adjust-placement.cpp.Root cause shared with #2331
m_bAdjustPCScan be set on an xform with no guarantee its port is XYZ or Lab —the two
IsSpacePCS(m_Header.pcs)setters test the profile header, not the port,and the hint path tests nothing at all — while
AdjustPCS()unconditionally readspixel[0..2]as X, Y, Z. A spectral PCS port and an MCS port are two consequences ofthat single assumption.
Status and the open question
Branch
refactor/pcs-adjust-in-pcsxformincidentally stops this — it retires thein-
Apply()adjustment path entirely, so nothing applies the affine to an MCS port anymore. That was not the branch's goal; it fell out of the retirement, and it is a
behaviour change relative to
master.The open question is whether stopping is the whole answer:
affine over samples that are not X, Y, Z is simply wrong — then no further work is
needed and this issue closes with that branch.
that needs specifying, and
CheckPCSConnections()would need to hand MCS ports toCIccPcsXformthe way it does colorimetric ones.