blob: 57c8b252ff4fabf19af4cd2a0a177ec071e392cd [file] [log] [blame]
/*****************************************************************************/
// Copyright 2006-2008 Adobe Systems Incorporated
// All Rights Reserved.
//
// NOTICE: Adobe permits you to use, modify, and distribute this file in
// accordance with the terms of the Adobe license agreement accompanying it.
/*****************************************************************************/
/* $Id: //mondo/dng_sdk_1_4/dng_sdk/source/dng_color_spec.cpp#1 $ */
/* $DateTime: 2012/05/30 13:28:51 $ */
/* $Change: 832332 $ */
/* $Author: tknoll $ */
#include "dng_color_spec.h"
#include "dng_assertions.h"
#include "dng_camera_profile.h"
#include "dng_exceptions.h"
#include "dng_matrix.h"
#include "dng_negative.h"
#include "dng_temperature.h"
#include "dng_utils.h"
#include "dng_xy_coord.h"
/*****************************************************************************/
dng_matrix_3by3 MapWhiteMatrix (const dng_xy_coord &white1,
const dng_xy_coord &white2)
{
// Use the linearized Bradford adaptation matrix.
dng_matrix_3by3 Mb ( 0.8951, 0.2664, -0.1614,
-0.7502, 1.7135, 0.0367,
0.0389, -0.0685, 1.0296);
dng_vector_3 w1 = Mb * XYtoXYZ (white1);
dng_vector_3 w2 = Mb * XYtoXYZ (white2);
// Negative white coordinates are kind of meaningless.
w1 [0] = Max_real64 (w1 [0], 0.0);
w1 [1] = Max_real64 (w1 [1], 0.0);
w1 [2] = Max_real64 (w1 [2], 0.0);
w2 [0] = Max_real64 (w2 [0], 0.0);
w2 [1] = Max_real64 (w2 [1], 0.0);
w2 [2] = Max_real64 (w2 [2], 0.0);
// Limit scaling to something reasonable.
dng_matrix_3by3 A;
A [0] [0] = Pin_real64 (0.1, w1 [0] > 0.0 ? w2 [0] / w1 [0] : 10.0, 10.0);
A [1] [1] = Pin_real64 (0.1, w1 [1] > 0.0 ? w2 [1] / w1 [1] : 10.0, 10.0);
A [2] [2] = Pin_real64 (0.1, w1 [2] > 0.0 ? w2 [2] / w1 [2] : 10.0, 10.0);
dng_matrix_3by3 B = Invert (Mb) * A * Mb;
return B;
}
/******************************************************************************/
dng_color_spec::dng_color_spec (const dng_negative &negative,
const dng_camera_profile *profile)
: fChannels (negative.ColorChannels ())
, fTemperature1 (0.0)
, fTemperature2 (0.0)
, fColorMatrix1 ()
, fColorMatrix2 ()
, fForwardMatrix1 ()
, fForwardMatrix2 ()
, fReductionMatrix1 ()
, fReductionMatrix2 ()
, fCameraCalibration1 ()
, fCameraCalibration2 ()
, fAnalogBalance ()
, fWhiteXY ()
, fCameraWhite ()
, fCameraToPCS ()
, fPCStoCamera ()
{
if (fChannels > 1)
{
if (!profile || !profile->IsValid (fChannels))
{
ThrowBadFormat ();
}
if (profile->WasStubbed ())
{
ThrowProgramError ("Using stubbed profile");
}
fTemperature1 = profile->CalibrationTemperature1 ();
fTemperature2 = profile->CalibrationTemperature2 ();
fColorMatrix1 = profile->ColorMatrix1 ();
fColorMatrix2 = profile->ColorMatrix2 ();
fForwardMatrix1 = profile->ForwardMatrix1 ();
fForwardMatrix2 = profile->ForwardMatrix2 ();
fReductionMatrix1 = profile->ReductionMatrix1 ();
fReductionMatrix2 = profile->ReductionMatrix2 ();
fCameraCalibration1.SetIdentity (fChannels);
fCameraCalibration2.SetIdentity (fChannels);
if (negative. CameraCalibrationSignature () ==
profile->ProfileCalibrationSignature ())
{
if (negative.CameraCalibration1 ().Rows () == fChannels &&
negative.CameraCalibration1 ().Cols () == fChannels)
{
fCameraCalibration1 = negative.CameraCalibration1 ();
}
if (negative.CameraCalibration2 ().Rows () == fChannels &&
negative.CameraCalibration2 ().Cols () == fChannels)
{
fCameraCalibration2 = negative.CameraCalibration2 ();
}
}
fAnalogBalance = dng_matrix (fChannels, fChannels);
for (uint32 j = 0; j < fChannels; j++)
{
fAnalogBalance [j] [j] = negative.AnalogBalance (j);
}
dng_camera_profile::NormalizeForwardMatrix (fForwardMatrix1);
fColorMatrix1 = fAnalogBalance * fCameraCalibration1 * fColorMatrix1;
if (!profile->HasColorMatrix2 () ||
fTemperature1 <= 0.0 ||
fTemperature2 <= 0.0 ||
fTemperature1 == fTemperature2)
{
fTemperature1 = 5000.0;
fTemperature2 = 5000.0;
fColorMatrix2 = fColorMatrix1;
fForwardMatrix2 = fForwardMatrix1;
fReductionMatrix2 = fReductionMatrix1;
fCameraCalibration2 = fCameraCalibration1;
}
else
{
dng_camera_profile::NormalizeForwardMatrix (fForwardMatrix2);
fColorMatrix2 = fAnalogBalance * fCameraCalibration2 * fColorMatrix2;
// Swap values if temperatures are out of order.
if (fTemperature1 > fTemperature2)
{
real64 temp = fTemperature1;
fTemperature1 = fTemperature2;
fTemperature2 = temp;
dng_matrix T = fColorMatrix1;
fColorMatrix1 = fColorMatrix2;
fColorMatrix2 = T;
T = fForwardMatrix1;
fForwardMatrix1 = fForwardMatrix2;
fForwardMatrix2 = T;
T = fReductionMatrix1;
fReductionMatrix1 = fReductionMatrix2;
fReductionMatrix2 = T;
T = fCameraCalibration1;
fCameraCalibration1 = fCameraCalibration2;
fCameraCalibration2 = T;
}
}
}
}
/*****************************************************************************/
dng_matrix dng_color_spec::FindXYZtoCamera (const dng_xy_coord &white,
dng_matrix *forwardMatrix,
dng_matrix *reductionMatrix,
dng_matrix *cameraCalibration)
{
// Convert to temperature/offset space.
dng_temperature td (white);
// Find fraction to weight the first calibration.
real64 g;
if (td.Temperature () <= fTemperature1)
g = 1.0;
else if (td.Temperature () >= fTemperature2)
g = 0.0;
else
{
real64 invT = 1.0 / td.Temperature ();
g = (invT - (1.0 / fTemperature2)) /
((1.0 / fTemperature1) - (1.0 / fTemperature2));
}
// Interpolate the color matrix.
dng_matrix colorMatrix;
if (g >= 1.0)
colorMatrix = fColorMatrix1;
else if (g <= 0.0)
colorMatrix = fColorMatrix2;
else
colorMatrix = (g ) * fColorMatrix1 +
(1.0 - g) * fColorMatrix2;
// Interpolate forward matrix, if any.
if (forwardMatrix)
{
bool has1 = fForwardMatrix1.NotEmpty ();
bool has2 = fForwardMatrix2.NotEmpty ();
if (has1 && has2)
{
if (g >= 1.0)
*forwardMatrix = fForwardMatrix1;
else if (g <= 0.0)
*forwardMatrix = fForwardMatrix2;
else
*forwardMatrix = (g ) * fForwardMatrix1 +
(1.0 - g) * fForwardMatrix2;
}
else if (has1)
{
*forwardMatrix = fForwardMatrix1;
}
else if (has2)
{
*forwardMatrix = fForwardMatrix2;
}
else
{
forwardMatrix->Clear ();
}
}
// Interpolate reduction matrix, if any.
if (reductionMatrix)
{
bool has1 = fReductionMatrix1.NotEmpty ();
bool has2 = fReductionMatrix2.NotEmpty ();
if (has1 && has2)
{
if (g >= 1.0)
*reductionMatrix = fReductionMatrix1;
else if (g <= 0.0)
*reductionMatrix = fReductionMatrix2;
else
*reductionMatrix = (g ) * fReductionMatrix1 +
(1.0 - g) * fReductionMatrix2;
}
else if (has1)
{
*reductionMatrix = fReductionMatrix1;
}
else if (has2)
{
*reductionMatrix = fReductionMatrix2;
}
else
{
reductionMatrix->Clear ();
}
}
// Interpolate camera calibration matrix.
if (cameraCalibration)
{
if (g >= 1.0)
*cameraCalibration = fCameraCalibration1;
else if (g <= 0.0)
*cameraCalibration = fCameraCalibration2;
else
*cameraCalibration = (g ) * fCameraCalibration1 +
(1.0 - g) * fCameraCalibration2;
}
// Return the interpolated color matrix.
return colorMatrix;
}
/*****************************************************************************/
void dng_color_spec::SetWhiteXY (const dng_xy_coord &white)
{
fWhiteXY = white;
// Deal with monochrome cameras.
if (fChannels == 1)
{
fCameraWhite.SetIdentity (1);
fCameraToPCS = PCStoXYZ ().AsColumn ();
return;
}
// Interpolate an matric values for this white point.
dng_matrix colorMatrix;
dng_matrix forwardMatrix;
dng_matrix reductionMatrix;
dng_matrix cameraCalibration;
colorMatrix = FindXYZtoCamera (fWhiteXY,
&forwardMatrix,
&reductionMatrix,
&cameraCalibration);
// Find the camera white values.
fCameraWhite = colorMatrix * XYtoXYZ (fWhiteXY);
real64 cameraWhiteMaxEntry = MaxEntry (fCameraWhite);
if (cameraWhiteMaxEntry == 0)
{
ThrowBadFormat ();
}
real64 whiteScale = 1.0 / cameraWhiteMaxEntry;
for (uint32 j = 0; j < fChannels; j++)
{
// We don't support non-positive values for camera neutral values.
fCameraWhite [j] = Pin_real64 (0.001,
whiteScale * fCameraWhite [j],
1.0);
}
// Find PCS to Camera transform. Scale matrix so PCS white can just be
// reached when the first camera channel saturates
fPCStoCamera = colorMatrix * MapWhiteMatrix (PCStoXY (), fWhiteXY);
real64 scale = MaxEntry (fPCStoCamera * PCStoXYZ ());
if (scale == 0)
{
ThrowBadFormat ();
}
fPCStoCamera = (1.0 / scale) * fPCStoCamera;
// If we have a forward matrix, then just use that.
if (forwardMatrix.NotEmpty ())
{
dng_matrix individualToReference = Invert (fAnalogBalance * cameraCalibration);
dng_vector refCameraWhite = individualToReference * fCameraWhite;
fCameraToPCS = forwardMatrix *
Invert (refCameraWhite.AsDiagonal ()) *
individualToReference;
}
// Else we need to use the adapt in XYZ method.
else
{
// Invert this PCS to camera matrix. Note that if there are more than three
// camera channels, this inversion is non-unique.
fCameraToPCS = Invert (fPCStoCamera, reductionMatrix);
}
}
/*****************************************************************************/
const dng_xy_coord & dng_color_spec::WhiteXY () const
{
DNG_ASSERT (fWhiteXY.IsValid (), "Using invalid WhiteXY");
return fWhiteXY;
}
/*****************************************************************************/
const dng_vector & dng_color_spec::CameraWhite () const
{
DNG_ASSERT (fCameraWhite.NotEmpty (), "Using invalid CameraWhite");
return fCameraWhite;
}
/*****************************************************************************/
const dng_matrix & dng_color_spec::CameraToPCS () const
{
DNG_ASSERT (fCameraToPCS.NotEmpty (), "Using invalid CameraToPCS");
return fCameraToPCS;
}
/*****************************************************************************/
const dng_matrix & dng_color_spec::PCStoCamera () const
{
DNG_ASSERT (fPCStoCamera.NotEmpty (), "Using invalid PCStoCamera");
return fPCStoCamera;
}
/*****************************************************************************/
dng_xy_coord dng_color_spec::NeutralToXY (const dng_vector &neutral)
{
const uint32 kMaxPasses = 30;
if (fChannels == 1)
{
return PCStoXY ();
}
dng_xy_coord last = D50_xy_coord ();
for (uint32 pass = 0; pass < kMaxPasses; pass++)
{
dng_matrix xyzToCamera = FindXYZtoCamera (last);
dng_xy_coord next = XYZtoXY (Invert (xyzToCamera) * neutral);
if (Abs_real64 (next.x - last.x) +
Abs_real64 (next.y - last.y) < 0.0000001)
{
return next;
}
// If we reach the limit without converging, we are most likely
// in a two value oscillation. So take the average of the last
// two estimates and give up.
if (pass == kMaxPasses - 1)
{
next.x = (last.x + next.x) * 0.5;
next.y = (last.y + next.y) * 0.5;
}
last = next;
}
return last;
}
/*****************************************************************************/