summaryrefslogtreecommitdiff
path: root/profile
diff options
context:
space:
mode:
Diffstat (limited to 'profile')
-rw-r--r--profile/3dap5k.sp103
-rw-r--r--profile/CIE_C.sp118
-rw-r--r--profile/D50_0.0.sp132
-rw-r--r--profile/D50_0.1.sp132
-rw-r--r--profile/D50_0.3.sp132
-rw-r--r--profile/D50_0.5.sp132
-rw-r--r--profile/D50_0.7.sp132
-rw-r--r--profile/D50_1.0.sp132
-rw-r--r--profile/D50_1.2.sp132
-rw-r--r--profile/D50_1.5.sp132
-rw-r--r--profile/D50_1.7.sp132
-rw-r--r--profile/D50_2.0.sp132
-rw-r--r--profile/D50_2.5.sp132
-rw-r--r--profile/D50_3.0.sp132
-rw-r--r--profile/GTIPlus.sp64
-rw-r--r--profile/Jamfile104
-rw-r--r--profile/License.txt662
-rw-r--r--profile/Makefile.am27
-rw-r--r--profile/Office.sp103
-rw-r--r--profile/Readme.txt3
-rw-r--r--profile/Trulux.sp103
-rw-r--r--profile/TruluxPlus.sp103
-rw-r--r--profile/afiles40
-rw-r--r--profile/applycal.c693
-rw-r--r--profile/cb2ti3.c244
-rw-r--r--profile/colprof.c1116
-rw-r--r--profile/example.sp132
-rw-r--r--profile/example121.sp147
-rw-r--r--profile/invprofcheck.c733
-rw-r--r--profile/kodak2ti3.c1273
-rw-r--r--profile/mppcheck.c588
-rw-r--r--profile/mppprof.c961
-rw-r--r--profile/printcal.c2320
-rw-r--r--profile/prof.h109
-rw-r--r--profile/profcheck.c1349
-rw-r--r--profile/profin.c1310
-rw-r--r--profile/profout.c2963
-rw-r--r--profile/simpprof.c433
-rw-r--r--profile/splitti3.c400
-rw-r--r--profile/txt2ti3.c830
-rw-r--r--profile/verify.c783
41 files changed, 19398 insertions, 0 deletions
diff --git a/profile/3dap5k.sp b/profile/3dap5k.sp
new file mode 100644
index 0000000..cfbc67d
--- /dev/null
+++ b/profile/3dap5k.sp
@@ -0,0 +1,103 @@
+SPECT
+
+DESCRIPTOR "Argyll Spectral Power information for 3DAP viewing booth at D50 - Foam reflector, extendedto 355nm by estimate of UV"
+ORIGINATOR "Argyll CMS"
+CREATED "Tue Apr 9 17:15:32 2002"
+KEYWORD "SPECTRAL_BANDS"
+SPECTRAL_BANDS "80"
+KEYWORD "SPECTRAL_START_NM"
+SPECTRAL_START_NM "355.000000"
+KEYWORD "SPECTRAL_END_NM"
+SPECTRAL_END_NM "750.000000"
+KEYWORD "SPECTRAL_NORM"
+SPECTRAL_NORM "3945.000"
+
+KEYWORD "SPEC_355"
+KEYWORD "SPEC_360"
+KEYWORD "SPEC_365"
+KEYWORD "SPEC_370"
+KEYWORD "SPEC_375"
+KEYWORD "SPEC_380"
+KEYWORD "SPEC_385"
+KEYWORD "SPEC_390"
+KEYWORD "SPEC_395"
+KEYWORD "SPEC_400"
+KEYWORD "SPEC_405"
+KEYWORD "SPEC_410"
+KEYWORD "SPEC_415"
+KEYWORD "SPEC_420"
+KEYWORD "SPEC_425"
+KEYWORD "SPEC_430"
+KEYWORD "SPEC_435"
+KEYWORD "SPEC_440"
+KEYWORD "SPEC_445"
+KEYWORD "SPEC_450"
+KEYWORD "SPEC_455"
+KEYWORD "SPEC_460"
+KEYWORD "SPEC_465"
+KEYWORD "SPEC_470"
+KEYWORD "SPEC_475"
+KEYWORD "SPEC_480"
+KEYWORD "SPEC_485"
+KEYWORD "SPEC_490"
+KEYWORD "SPEC_495"
+KEYWORD "SPEC_500"
+KEYWORD "SPEC_505"
+KEYWORD "SPEC_510"
+KEYWORD "SPEC_515"
+KEYWORD "SPEC_520"
+KEYWORD "SPEC_525"
+KEYWORD "SPEC_530"
+KEYWORD "SPEC_535"
+KEYWORD "SPEC_540"
+KEYWORD "SPEC_545"
+KEYWORD "SPEC_550"
+KEYWORD "SPEC_555"
+KEYWORD "SPEC_560"
+KEYWORD "SPEC_565"
+KEYWORD "SPEC_570"
+KEYWORD "SPEC_575"
+KEYWORD "SPEC_580"
+KEYWORD "SPEC_585"
+KEYWORD "SPEC_590"
+KEYWORD "SPEC_595"
+KEYWORD "SPEC_600"
+KEYWORD "SPEC_605"
+KEYWORD "SPEC_610"
+KEYWORD "SPEC_615"
+KEYWORD "SPEC_620"
+KEYWORD "SPEC_625"
+KEYWORD "SPEC_630"
+KEYWORD "SPEC_635"
+KEYWORD "SPEC_640"
+KEYWORD "SPEC_645"
+KEYWORD "SPEC_650"
+KEYWORD "SPEC_655"
+KEYWORD "SPEC_660"
+KEYWORD "SPEC_665"
+KEYWORD "SPEC_670"
+KEYWORD "SPEC_675"
+KEYWORD "SPEC_680"
+KEYWORD "SPEC_685"
+KEYWORD "SPEC_690"
+KEYWORD "SPEC_695"
+KEYWORD "SPEC_700"
+KEYWORD "SPEC_705"
+KEYWORD "SPEC_710"
+KEYWORD "SPEC_715"
+KEYWORD "SPEC_720"
+KEYWORD "SPEC_725"
+KEYWORD "SPEC_730"
+KEYWORD "SPEC_735"
+KEYWORD "SPEC_740"
+KEYWORD "SPEC_745"
+KEYWORD "SPEC_750"
+NUMBER_OF_FIELDS 80
+BEGIN_DATA_FORMAT
+SPEC_355 SPEC_360 SPEC_365 SPEC_370 SPEC_375 SPEC_380 SPEC_385 SPEC_390 SPEC_395 SPEC_400 SPEC_405 SPEC_410 SPEC_415 SPEC_420 SPEC_425 SPEC_430 SPEC_435 SPEC_440 SPEC_445 SPEC_450 SPEC_455 SPEC_460 SPEC_465 SPEC_470 SPEC_475 SPEC_480 SPEC_485 SPEC_490 SPEC_495 SPEC_500 SPEC_505 SPEC_510 SPEC_515 SPEC_520 SPEC_525 SPEC_530 SPEC_535 SPEC_540 SPEC_545 SPEC_550 SPEC_555 SPEC_560 SPEC_565 SPEC_570 SPEC_575 SPEC_580 SPEC_585 SPEC_590 SPEC_595 SPEC_600 SPEC_605 SPEC_610 SPEC_615 SPEC_620 SPEC_625 SPEC_630 SPEC_635 SPEC_640 SPEC_645 SPEC_650 SPEC_655 SPEC_660 SPEC_665 SPEC_670 SPEC_675 SPEC_680 SPEC_685 SPEC_690 SPEC_695 SPEC_700 SPEC_705 SPEC_710 SPEC_715 SPEC_720 SPEC_725 SPEC_730 SPEC_735 SPEC_740 SPEC_745 SPEC_750
+END_DATA_FORMAT
+
+NUMBER_OF_SETS 1
+BEGIN_DATA
+0.0 44.97 103.94 164.9 200.88 269.8301111 310.0680556 414.1321667 586.6544444 1066.693611 1133.624333 1031.633944 972.29 1043.722333 1360.529389 2417.268111 3132.216667 2818.478056 2192.068111 1957.285667 2012.437833 2086.816 2149.097611 2205.945556 2264.570111 2339.1085 2399.303778 2451.353278 2494.1235 2516.631 2534.502167 2543.351444 2565.551778 2603.578944 2647.086278 2702.404611 3045.447667 3626.533111 3944.484778 3524.838667 3082.760333 2947.282889 2987.046333 3115.224611 3264.204778 3254.242611 3106.393611 3101.613167 3069.166278 3050.570056 3084.844278 3103.531722 3083.711278 3053.589722 3031.742722 2965.037333 2901.627444 2814.946667 2731.505611 2628.440667 2523.334611 2418.187556 2309.318778 2191.377056 2074.207278 1952.003389 1830.847556 1709.364111 1586.339889 1466.436333 1359.227167 1251.518111 1158.713556 1056.849167 982.2127778 900.2322778 833.9932222 763.601 692.7145556 643.0055556
+END_DATA
diff --git a/profile/CIE_C.sp b/profile/CIE_C.sp
new file mode 100644
index 0000000..8b771fe
--- /dev/null
+++ b/profile/CIE_C.sp
@@ -0,0 +1,118 @@
+SPECT
+
+DESCRIPTOR "Argyll CIE illuminant C spectral power"
+
+ORIGINATOR "Argyll CMS"
+
+CREATED "Thu Dec 02 23:05:57 2009"
+KEYWORD "SPECTRAL_BANDS"
+SPECTRAL_BANDS "93"
+KEYWORD "SPECTRAL_START_NM"
+SPECTRAL_START_NM "320.000000"
+KEYWORD "SPECTRAL_END_NM"
+SPECTRAL_END_NM "780.000000"
+KEYWORD "SPECTRAL_NORM"
+SPECTRAL_NORM "100.000000"
+
+KEYWORD "SPEC_320"
+KEYWORD "SPEC_325"
+KEYWORD "SPEC_330"
+KEYWORD "SPEC_335"
+KEYWORD "SPEC_340"
+KEYWORD "SPEC_345"
+KEYWORD "SPEC_350"
+KEYWORD "SPEC_355"
+KEYWORD "SPEC_360"
+KEYWORD "SPEC_365"
+KEYWORD "SPEC_370"
+KEYWORD "SPEC_375"
+KEYWORD "SPEC_380"
+KEYWORD "SPEC_385"
+KEYWORD "SPEC_390"
+KEYWORD "SPEC_395"
+KEYWORD "SPEC_400"
+KEYWORD "SPEC_405"
+KEYWORD "SPEC_410"
+KEYWORD "SPEC_415"
+KEYWORD "SPEC_420"
+KEYWORD "SPEC_425"
+KEYWORD "SPEC_430"
+KEYWORD "SPEC_435"
+KEYWORD "SPEC_440"
+KEYWORD "SPEC_445"
+KEYWORD "SPEC_450"
+KEYWORD "SPEC_455"
+KEYWORD "SPEC_460"
+KEYWORD "SPEC_465"
+KEYWORD "SPEC_470"
+KEYWORD "SPEC_475"
+KEYWORD "SPEC_480"
+KEYWORD "SPEC_485"
+KEYWORD "SPEC_490"
+KEYWORD "SPEC_495"
+KEYWORD "SPEC_500"
+KEYWORD "SPEC_505"
+KEYWORD "SPEC_510"
+KEYWORD "SPEC_515"
+KEYWORD "SPEC_520"
+KEYWORD "SPEC_525"
+KEYWORD "SPEC_530"
+KEYWORD "SPEC_535"
+KEYWORD "SPEC_540"
+KEYWORD "SPEC_545"
+KEYWORD "SPEC_550"
+KEYWORD "SPEC_555"
+KEYWORD "SPEC_560"
+KEYWORD "SPEC_565"
+KEYWORD "SPEC_570"
+KEYWORD "SPEC_575"
+KEYWORD "SPEC_580"
+KEYWORD "SPEC_585"
+KEYWORD "SPEC_590"
+KEYWORD "SPEC_595"
+KEYWORD "SPEC_600"
+KEYWORD "SPEC_605"
+KEYWORD "SPEC_610"
+KEYWORD "SPEC_615"
+KEYWORD "SPEC_620"
+KEYWORD "SPEC_625"
+KEYWORD "SPEC_630"
+KEYWORD "SPEC_635"
+KEYWORD "SPEC_640"
+KEYWORD "SPEC_645"
+KEYWORD "SPEC_650"
+KEYWORD "SPEC_655"
+KEYWORD "SPEC_660"
+KEYWORD "SPEC_665"
+KEYWORD "SPEC_670"
+KEYWORD "SPEC_675"
+KEYWORD "SPEC_680"
+KEYWORD "SPEC_685"
+KEYWORD "SPEC_690"
+KEYWORD "SPEC_695"
+KEYWORD "SPEC_700"
+KEYWORD "SPEC_705"
+KEYWORD "SPEC_710"
+KEYWORD "SPEC_715"
+KEYWORD "SPEC_720"
+KEYWORD "SPEC_725"
+KEYWORD "SPEC_730"
+KEYWORD "SPEC_735"
+KEYWORD "SPEC_740"
+KEYWORD "SPEC_745"
+KEYWORD "SPEC_750"
+KEYWORD "SPEC_755"
+KEYWORD "SPEC_760"
+KEYWORD "SPEC_765"
+KEYWORD "SPEC_770"
+KEYWORD "SPEC_775"
+KEYWORD "SPEC_780"
+NUMBER_OF_FIELDS 93
+BEGIN_DATA_FORMAT
+SPEC_320 SPEC_325 SPEC_330 SPEC_335 SPEC_340 SPEC_345 SPEC_350 SPEC_355 SPEC_360 SPEC_365 SPEC_370 SPEC_375 SPEC_380 SPEC_385 SPEC_390 SPEC_395 SPEC_400 SPEC_405 SPEC_410 SPEC_415 SPEC_420 SPEC_425 SPEC_430 SPEC_435 SPEC_440 SPEC_445 SPEC_450 SPEC_455 SPEC_460 SPEC_465 SPEC_470 SPEC_475 SPEC_480 SPEC_485 SPEC_490 SPEC_495 SPEC_500 SPEC_505 SPEC_510 SPEC_515 SPEC_520 SPEC_525 SPEC_530 SPEC_535 SPEC_540 SPEC_545 SPEC_550 SPEC_555 SPEC_560 SPEC_565 SPEC_570 SPEC_575 SPEC_580 SPEC_585 SPEC_590 SPEC_595 SPEC_600 SPEC_605 SPEC_610 SPEC_615 SPEC_620 SPEC_625 SPEC_630 SPEC_635 SPEC_640 SPEC_645 SPEC_650 SPEC_655 SPEC_660 SPEC_665 SPEC_670 SPEC_675 SPEC_680 SPEC_685 SPEC_690 SPEC_695 SPEC_700 SPEC_705 SPEC_710 SPEC_715 SPEC_720 SPEC_725 SPEC_730 SPEC_735 SPEC_740 SPEC_745 SPEC_750 SPEC_755 SPEC_760 SPEC_765 SPEC_770 SPEC_775 SPEC_780
+END_DATA_FORMAT
+
+NUMBER_OF_SETS 1
+BEGIN_DATA
+0.01 0.20 0.40 1.55 2.70 4.85 7.00 9.95 12.90 17.20 21.40 27.50 33.00 39.92 47.40 55.17 63.30 71.81 80.60 89.53 98.10 105.80 112.40 117.75 121.50 123.45 124.00 123.60 123.10 123.30 123.80 124.09 123.90 122.92 120.70 116.90 112.10 106.98 102.30 98.81 96.90 96.78 98.00 99.94 102.10 103.95 105.20 105.67 105.30 104.11 102.30 100.15 97.80 95.43 93.20 91.22 89.70 88.83 88.40 88.19 88.10 88.06 88.00 87.86 87.80 87.99 88.20 88.20 87.90 87.22 86.30 85.30 84.00 82.21 80.20 78.24 76.30 74.36 72.40 70.40 68.30 66.30 64.40 62.80 61.50 60.20 59.20 58.50 58.10 58.00 58.20 58.50 59.10
+END_DATA
diff --git a/profile/D50_0.0.sp b/profile/D50_0.0.sp
new file mode 100644
index 0000000..8904fca
--- /dev/null
+++ b/profile/D50_0.0.sp
@@ -0,0 +1,132 @@
+SPECT
+
+DESCRIPTOR "Argyll D50 illimunant spectral power, 0% U.V."
+
+ORIGINATOR "Argyll CMS"
+
+CREATED "Fri Jul 06 17:49:57 2001"
+KEYWORD "SPECTRAL_BANDS"
+SPECTRAL_BANDS "107"
+KEYWORD "SPECTRAL_START_NM"
+SPECTRAL_START_NM "300.000000"
+KEYWORD "SPECTRAL_END_NM"
+SPECTRAL_END_NM "830.000000"
+KEYWORD "SPECTRAL_NORM"
+SPECTRAL_NORM "100.000000"
+
+KEYWORD "SPEC_300"
+KEYWORD "SPEC_305"
+KEYWORD "SPEC_310"
+KEYWORD "SPEC_315"
+KEYWORD "SPEC_320"
+KEYWORD "SPEC_325"
+KEYWORD "SPEC_330"
+KEYWORD "SPEC_335"
+KEYWORD "SPEC_340"
+KEYWORD "SPEC_345"
+KEYWORD "SPEC_350"
+KEYWORD "SPEC_355"
+KEYWORD "SPEC_360"
+KEYWORD "SPEC_365"
+KEYWORD "SPEC_370"
+KEYWORD "SPEC_375"
+KEYWORD "SPEC_380"
+KEYWORD "SPEC_385"
+KEYWORD "SPEC_390"
+KEYWORD "SPEC_395"
+KEYWORD "SPEC_400"
+KEYWORD "SPEC_405"
+KEYWORD "SPEC_410"
+KEYWORD "SPEC_415"
+KEYWORD "SPEC_420"
+KEYWORD "SPEC_425"
+KEYWORD "SPEC_430"
+KEYWORD "SPEC_435"
+KEYWORD "SPEC_440"
+KEYWORD "SPEC_445"
+KEYWORD "SPEC_450"
+KEYWORD "SPEC_455"
+KEYWORD "SPEC_460"
+KEYWORD "SPEC_465"
+KEYWORD "SPEC_470"
+KEYWORD "SPEC_475"
+KEYWORD "SPEC_480"
+KEYWORD "SPEC_485"
+KEYWORD "SPEC_490"
+KEYWORD "SPEC_495"
+KEYWORD "SPEC_500"
+KEYWORD "SPEC_505"
+KEYWORD "SPEC_510"
+KEYWORD "SPEC_515"
+KEYWORD "SPEC_520"
+KEYWORD "SPEC_525"
+KEYWORD "SPEC_530"
+KEYWORD "SPEC_535"
+KEYWORD "SPEC_540"
+KEYWORD "SPEC_545"
+KEYWORD "SPEC_550"
+KEYWORD "SPEC_555"
+KEYWORD "SPEC_560"
+KEYWORD "SPEC_565"
+KEYWORD "SPEC_570"
+KEYWORD "SPEC_575"
+KEYWORD "SPEC_580"
+KEYWORD "SPEC_585"
+KEYWORD "SPEC_590"
+KEYWORD "SPEC_595"
+KEYWORD "SPEC_600"
+KEYWORD "SPEC_605"
+KEYWORD "SPEC_610"
+KEYWORD "SPEC_615"
+KEYWORD "SPEC_620"
+KEYWORD "SPEC_625"
+KEYWORD "SPEC_630"
+KEYWORD "SPEC_635"
+KEYWORD "SPEC_640"
+KEYWORD "SPEC_645"
+KEYWORD "SPEC_650"
+KEYWORD "SPEC_655"
+KEYWORD "SPEC_660"
+KEYWORD "SPEC_665"
+KEYWORD "SPEC_670"
+KEYWORD "SPEC_675"
+KEYWORD "SPEC_680"
+KEYWORD "SPEC_685"
+KEYWORD "SPEC_690"
+KEYWORD "SPEC_695"
+KEYWORD "SPEC_700"
+KEYWORD "SPEC_705"
+KEYWORD "SPEC_710"
+KEYWORD "SPEC_715"
+KEYWORD "SPEC_720"
+KEYWORD "SPEC_725"
+KEYWORD "SPEC_730"
+KEYWORD "SPEC_735"
+KEYWORD "SPEC_740"
+KEYWORD "SPEC_745"
+KEYWORD "SPEC_750"
+KEYWORD "SPEC_755"
+KEYWORD "SPEC_760"
+KEYWORD "SPEC_765"
+KEYWORD "SPEC_770"
+KEYWORD "SPEC_775"
+KEYWORD "SPEC_780"
+KEYWORD "SPEC_785"
+KEYWORD "SPEC_790"
+KEYWORD "SPEC_795"
+KEYWORD "SPEC_800"
+KEYWORD "SPEC_805"
+KEYWORD "SPEC_810"
+KEYWORD "SPEC_815"
+KEYWORD "SPEC_820"
+KEYWORD "SPEC_825"
+KEYWORD "SPEC_830"
+NUMBER_OF_FIELDS 107
+BEGIN_DATA_FORMAT
+SPEC_300 SPEC_305 SPEC_310 SPEC_315 SPEC_320 SPEC_325 SPEC_330 SPEC_335 SPEC_340 SPEC_345 SPEC_350 SPEC_355 SPEC_360 SPEC_365 SPEC_370 SPEC_375 SPEC_380 SPEC_385 SPEC_390 SPEC_395 SPEC_400 SPEC_405 SPEC_410 SPEC_415 SPEC_420 SPEC_425 SPEC_430 SPEC_435 SPEC_440 SPEC_445 SPEC_450 SPEC_455 SPEC_460 SPEC_465 SPEC_470 SPEC_475 SPEC_480 SPEC_485 SPEC_490 SPEC_495 SPEC_500 SPEC_505 SPEC_510 SPEC_515 SPEC_520 SPEC_525 SPEC_530 SPEC_535 SPEC_540 SPEC_545 SPEC_550 SPEC_555 SPEC_560 SPEC_565 SPEC_570 SPEC_575 SPEC_580 SPEC_585 SPEC_590 SPEC_595 SPEC_600 SPEC_605 SPEC_610 SPEC_615 SPEC_620 SPEC_625 SPEC_630 SPEC_635 SPEC_640 SPEC_645 SPEC_650 SPEC_655 SPEC_660 SPEC_665 SPEC_670 SPEC_675 SPEC_680 SPEC_685 SPEC_690 SPEC_695 SPEC_700 SPEC_705 SPEC_710 SPEC_715 SPEC_720 SPEC_725 SPEC_730 SPEC_735 SPEC_740 SPEC_745 SPEC_750 SPEC_755 SPEC_760 SPEC_765 SPEC_770 SPEC_775 SPEC_780 SPEC_785 SPEC_790 SPEC_795 SPEC_800 SPEC_805 SPEC_810 SPEC_815 SPEC_820 SPEC_825 SPEC_830
+END_DATA_FORMAT
+
+NUMBER_OF_SETS 1
+BEGIN_DATA
+0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 25.0 57.82 66.32 74.82 81.04 87.25 88.93 90.61 90.99 91.37 93.24 95.11 93.54 91.96 93.84 95.72 96.17 96.61 96.87 97.13 99.61 102.1 101.43 100.75 101.54 102.32 101.16 100.0 98.87 97.74 98.33 98.92 96.21 93.5 95.59 97.69 98.48 99.27 99.16 99.04 97.38 95.72 97.29 98.86 97.26 95.67 96.93 98.19 100.6 103.0 101.07 99.13 93.26 87.38 89.49 91.6 92.25 92.89 84.87 76.85 81.68 86.51 89.55 92.58 85.4 78.23 67.96 57.69 70.31 82.92 80.6 78.27 78.91 79.55 76.48 73.4 68.66 63.92 67.35 70.78 72.61 74.44
+END_DATA
diff --git a/profile/D50_0.1.sp b/profile/D50_0.1.sp
new file mode 100644
index 0000000..ad747fb
--- /dev/null
+++ b/profile/D50_0.1.sp
@@ -0,0 +1,132 @@
+SPECT
+
+DESCRIPTOR "Argyll D50 illimunant spectral power, 0% U.V."
+
+ORIGINATOR "Argyll CMS"
+
+CREATED "Fri Jul 06 17:49:57 2001"
+KEYWORD "SPECTRAL_BANDS"
+SPECTRAL_BANDS "107"
+KEYWORD "SPECTRAL_START_NM"
+SPECTRAL_START_NM "300.000000"
+KEYWORD "SPECTRAL_END_NM"
+SPECTRAL_END_NM "830.000000"
+KEYWORD "SPECTRAL_NORM"
+SPECTRAL_NORM "100.000000"
+
+KEYWORD "SPEC_300"
+KEYWORD "SPEC_305"
+KEYWORD "SPEC_310"
+KEYWORD "SPEC_315"
+KEYWORD "SPEC_320"
+KEYWORD "SPEC_325"
+KEYWORD "SPEC_330"
+KEYWORD "SPEC_335"
+KEYWORD "SPEC_340"
+KEYWORD "SPEC_345"
+KEYWORD "SPEC_350"
+KEYWORD "SPEC_355"
+KEYWORD "SPEC_360"
+KEYWORD "SPEC_365"
+KEYWORD "SPEC_370"
+KEYWORD "SPEC_375"
+KEYWORD "SPEC_380"
+KEYWORD "SPEC_385"
+KEYWORD "SPEC_390"
+KEYWORD "SPEC_395"
+KEYWORD "SPEC_400"
+KEYWORD "SPEC_405"
+KEYWORD "SPEC_410"
+KEYWORD "SPEC_415"
+KEYWORD "SPEC_420"
+KEYWORD "SPEC_425"
+KEYWORD "SPEC_430"
+KEYWORD "SPEC_435"
+KEYWORD "SPEC_440"
+KEYWORD "SPEC_445"
+KEYWORD "SPEC_450"
+KEYWORD "SPEC_455"
+KEYWORD "SPEC_460"
+KEYWORD "SPEC_465"
+KEYWORD "SPEC_470"
+KEYWORD "SPEC_475"
+KEYWORD "SPEC_480"
+KEYWORD "SPEC_485"
+KEYWORD "SPEC_490"
+KEYWORD "SPEC_495"
+KEYWORD "SPEC_500"
+KEYWORD "SPEC_505"
+KEYWORD "SPEC_510"
+KEYWORD "SPEC_515"
+KEYWORD "SPEC_520"
+KEYWORD "SPEC_525"
+KEYWORD "SPEC_530"
+KEYWORD "SPEC_535"
+KEYWORD "SPEC_540"
+KEYWORD "SPEC_545"
+KEYWORD "SPEC_550"
+KEYWORD "SPEC_555"
+KEYWORD "SPEC_560"
+KEYWORD "SPEC_565"
+KEYWORD "SPEC_570"
+KEYWORD "SPEC_575"
+KEYWORD "SPEC_580"
+KEYWORD "SPEC_585"
+KEYWORD "SPEC_590"
+KEYWORD "SPEC_595"
+KEYWORD "SPEC_600"
+KEYWORD "SPEC_605"
+KEYWORD "SPEC_610"
+KEYWORD "SPEC_615"
+KEYWORD "SPEC_620"
+KEYWORD "SPEC_625"
+KEYWORD "SPEC_630"
+KEYWORD "SPEC_635"
+KEYWORD "SPEC_640"
+KEYWORD "SPEC_645"
+KEYWORD "SPEC_650"
+KEYWORD "SPEC_655"
+KEYWORD "SPEC_660"
+KEYWORD "SPEC_665"
+KEYWORD "SPEC_670"
+KEYWORD "SPEC_675"
+KEYWORD "SPEC_680"
+KEYWORD "SPEC_685"
+KEYWORD "SPEC_690"
+KEYWORD "SPEC_695"
+KEYWORD "SPEC_700"
+KEYWORD "SPEC_705"
+KEYWORD "SPEC_710"
+KEYWORD "SPEC_715"
+KEYWORD "SPEC_720"
+KEYWORD "SPEC_725"
+KEYWORD "SPEC_730"
+KEYWORD "SPEC_735"
+KEYWORD "SPEC_740"
+KEYWORD "SPEC_745"
+KEYWORD "SPEC_750"
+KEYWORD "SPEC_755"
+KEYWORD "SPEC_760"
+KEYWORD "SPEC_765"
+KEYWORD "SPEC_770"
+KEYWORD "SPEC_775"
+KEYWORD "SPEC_780"
+KEYWORD "SPEC_785"
+KEYWORD "SPEC_790"
+KEYWORD "SPEC_795"
+KEYWORD "SPEC_800"
+KEYWORD "SPEC_805"
+KEYWORD "SPEC_810"
+KEYWORD "SPEC_815"
+KEYWORD "SPEC_820"
+KEYWORD "SPEC_825"
+KEYWORD "SPEC_830"
+NUMBER_OF_FIELDS 107
+BEGIN_DATA_FORMAT
+SPEC_300 SPEC_305 SPEC_310 SPEC_315 SPEC_320 SPEC_325 SPEC_330 SPEC_335 SPEC_340 SPEC_345 SPEC_350 SPEC_355 SPEC_360 SPEC_365 SPEC_370 SPEC_375 SPEC_380 SPEC_385 SPEC_390 SPEC_395 SPEC_400 SPEC_405 SPEC_410 SPEC_415 SPEC_420 SPEC_425 SPEC_430 SPEC_435 SPEC_440 SPEC_445 SPEC_450 SPEC_455 SPEC_460 SPEC_465 SPEC_470 SPEC_475 SPEC_480 SPEC_485 SPEC_490 SPEC_495 SPEC_500 SPEC_505 SPEC_510 SPEC_515 SPEC_520 SPEC_525 SPEC_530 SPEC_535 SPEC_540 SPEC_545 SPEC_550 SPEC_555 SPEC_560 SPEC_565 SPEC_570 SPEC_575 SPEC_580 SPEC_585 SPEC_590 SPEC_595 SPEC_600 SPEC_605 SPEC_610 SPEC_615 SPEC_620 SPEC_625 SPEC_630 SPEC_635 SPEC_640 SPEC_645 SPEC_650 SPEC_655 SPEC_660 SPEC_665 SPEC_670 SPEC_675 SPEC_680 SPEC_685 SPEC_690 SPEC_695 SPEC_700 SPEC_705 SPEC_710 SPEC_715 SPEC_720 SPEC_725 SPEC_730 SPEC_735 SPEC_740 SPEC_745 SPEC_750 SPEC_755 SPEC_760 SPEC_765 SPEC_770 SPEC_775 SPEC_780 SPEC_785 SPEC_790 SPEC_795 SPEC_800 SPEC_805 SPEC_810 SPEC_815 SPEC_820 SPEC_825 SPEC_830
+END_DATA_FORMAT
+
+NUMBER_OF_SETS 1
+BEGIN_DATA
+0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 2.45 5.44 11.95 23.75 39.45 47.62 56.51 58.27 60.03 58.93 57.82 66.32 74.82 81.04 87.25 88.93 90.61 90.99 91.37 93.24 95.11 93.54 91.96 93.84 95.72 96.17 96.61 96.87 97.13 99.61 102.1 101.43 100.75 101.54 102.32 101.16 100.0 98.87 97.74 98.33 98.92 96.21 93.5 95.59 97.69 98.48 99.27 99.16 99.04 97.38 95.72 97.29 98.86 97.26 95.67 96.93 98.19 100.6 103.0 101.07 99.13 93.26 87.38 89.49 91.6 92.25 92.89 84.87 76.85 81.68 86.51 89.55 92.58 85.4 78.23 67.96 57.69 70.31 82.92 80.6 78.27 78.91 79.55 76.48 73.4 68.66 63.92 67.35 70.78 72.61 74.44
+END_DATA
diff --git a/profile/D50_0.3.sp b/profile/D50_0.3.sp
new file mode 100644
index 0000000..1a64a27
--- /dev/null
+++ b/profile/D50_0.3.sp
@@ -0,0 +1,132 @@
+SPECT
+
+DESCRIPTOR "Argyll D50 illimunant spectral power, 30% U.V."
+
+ORIGINATOR "Argyll CMS"
+
+CREATED "Fri Jul 06 17:49:57 2001"
+KEYWORD "SPECTRAL_BANDS"
+SPECTRAL_BANDS "107"
+KEYWORD "SPECTRAL_START_NM"
+SPECTRAL_START_NM "300.000000"
+KEYWORD "SPECTRAL_END_NM"
+SPECTRAL_END_NM "830.000000"
+KEYWORD "SPECTRAL_NORM"
+SPECTRAL_NORM "100.000000"
+
+KEYWORD "SPEC_300"
+KEYWORD "SPEC_305"
+KEYWORD "SPEC_310"
+KEYWORD "SPEC_315"
+KEYWORD "SPEC_320"
+KEYWORD "SPEC_325"
+KEYWORD "SPEC_330"
+KEYWORD "SPEC_335"
+KEYWORD "SPEC_340"
+KEYWORD "SPEC_345"
+KEYWORD "SPEC_350"
+KEYWORD "SPEC_355"
+KEYWORD "SPEC_360"
+KEYWORD "SPEC_365"
+KEYWORD "SPEC_370"
+KEYWORD "SPEC_375"
+KEYWORD "SPEC_380"
+KEYWORD "SPEC_385"
+KEYWORD "SPEC_390"
+KEYWORD "SPEC_395"
+KEYWORD "SPEC_400"
+KEYWORD "SPEC_405"
+KEYWORD "SPEC_410"
+KEYWORD "SPEC_415"
+KEYWORD "SPEC_420"
+KEYWORD "SPEC_425"
+KEYWORD "SPEC_430"
+KEYWORD "SPEC_435"
+KEYWORD "SPEC_440"
+KEYWORD "SPEC_445"
+KEYWORD "SPEC_450"
+KEYWORD "SPEC_455"
+KEYWORD "SPEC_460"
+KEYWORD "SPEC_465"
+KEYWORD "SPEC_470"
+KEYWORD "SPEC_475"
+KEYWORD "SPEC_480"
+KEYWORD "SPEC_485"
+KEYWORD "SPEC_490"
+KEYWORD "SPEC_495"
+KEYWORD "SPEC_500"
+KEYWORD "SPEC_505"
+KEYWORD "SPEC_510"
+KEYWORD "SPEC_515"
+KEYWORD "SPEC_520"
+KEYWORD "SPEC_525"
+KEYWORD "SPEC_530"
+KEYWORD "SPEC_535"
+KEYWORD "SPEC_540"
+KEYWORD "SPEC_545"
+KEYWORD "SPEC_550"
+KEYWORD "SPEC_555"
+KEYWORD "SPEC_560"
+KEYWORD "SPEC_565"
+KEYWORD "SPEC_570"
+KEYWORD "SPEC_575"
+KEYWORD "SPEC_580"
+KEYWORD "SPEC_585"
+KEYWORD "SPEC_590"
+KEYWORD "SPEC_595"
+KEYWORD "SPEC_600"
+KEYWORD "SPEC_605"
+KEYWORD "SPEC_610"
+KEYWORD "SPEC_615"
+KEYWORD "SPEC_620"
+KEYWORD "SPEC_625"
+KEYWORD "SPEC_630"
+KEYWORD "SPEC_635"
+KEYWORD "SPEC_640"
+KEYWORD "SPEC_645"
+KEYWORD "SPEC_650"
+KEYWORD "SPEC_655"
+KEYWORD "SPEC_660"
+KEYWORD "SPEC_665"
+KEYWORD "SPEC_670"
+KEYWORD "SPEC_675"
+KEYWORD "SPEC_680"
+KEYWORD "SPEC_685"
+KEYWORD "SPEC_690"
+KEYWORD "SPEC_695"
+KEYWORD "SPEC_700"
+KEYWORD "SPEC_705"
+KEYWORD "SPEC_710"
+KEYWORD "SPEC_715"
+KEYWORD "SPEC_720"
+KEYWORD "SPEC_725"
+KEYWORD "SPEC_730"
+KEYWORD "SPEC_735"
+KEYWORD "SPEC_740"
+KEYWORD "SPEC_745"
+KEYWORD "SPEC_750"
+KEYWORD "SPEC_755"
+KEYWORD "SPEC_760"
+KEYWORD "SPEC_765"
+KEYWORD "SPEC_770"
+KEYWORD "SPEC_775"
+KEYWORD "SPEC_780"
+KEYWORD "SPEC_785"
+KEYWORD "SPEC_790"
+KEYWORD "SPEC_795"
+KEYWORD "SPEC_800"
+KEYWORD "SPEC_805"
+KEYWORD "SPEC_810"
+KEYWORD "SPEC_815"
+KEYWORD "SPEC_820"
+KEYWORD "SPEC_825"
+KEYWORD "SPEC_830"
+NUMBER_OF_FIELDS 107
+BEGIN_DATA_FORMAT
+SPEC_300 SPEC_305 SPEC_310 SPEC_315 SPEC_320 SPEC_325 SPEC_330 SPEC_335 SPEC_340 SPEC_345 SPEC_350 SPEC_355 SPEC_360 SPEC_365 SPEC_370 SPEC_375 SPEC_380 SPEC_385 SPEC_390 SPEC_395 SPEC_400 SPEC_405 SPEC_410 SPEC_415 SPEC_420 SPEC_425 SPEC_430 SPEC_435 SPEC_440 SPEC_445 SPEC_450 SPEC_455 SPEC_460 SPEC_465 SPEC_470 SPEC_475 SPEC_480 SPEC_485 SPEC_490 SPEC_495 SPEC_500 SPEC_505 SPEC_510 SPEC_515 SPEC_520 SPEC_525 SPEC_530 SPEC_535 SPEC_540 SPEC_545 SPEC_550 SPEC_555 SPEC_560 SPEC_565 SPEC_570 SPEC_575 SPEC_580 SPEC_585 SPEC_590 SPEC_595 SPEC_600 SPEC_605 SPEC_610 SPEC_615 SPEC_620 SPEC_625 SPEC_630 SPEC_635 SPEC_640 SPEC_645 SPEC_650 SPEC_655 SPEC_660 SPEC_665 SPEC_670 SPEC_675 SPEC_680 SPEC_685 SPEC_690 SPEC_695 SPEC_700 SPEC_705 SPEC_710 SPEC_715 SPEC_720 SPEC_725 SPEC_730 SPEC_735 SPEC_740 SPEC_745 SPEC_750 SPEC_755 SPEC_760 SPEC_765 SPEC_770 SPEC_775 SPEC_780 SPEC_785 SPEC_790 SPEC_795 SPEC_800 SPEC_805 SPEC_810 SPEC_815 SPEC_820 SPEC_825 SPEC_830
+END_DATA_FORMAT
+
+NUMBER_OF_SETS 1
+BEGIN_DATA
+0.01 0.31 0.62 1.47 2.33 3.38 4.43 4.91 5.39 5.84 6.3 6.74 7.18 7.64 8.09 7.72 9.06 11.96 17.32 28.5 42.41 49.21 56.51 58.27 60.03 58.93 57.82 66.32 74.82 81.04 87.25 88.93 90.61 90.99 91.37 93.24 95.11 93.54 91.96 93.84 95.72 96.17 96.61 96.87 97.13 99.61 102.1 101.43 100.75 101.54 102.32 101.16 100.0 98.87 97.74 98.33 98.92 96.21 93.5 95.59 97.69 98.48 99.27 99.16 99.04 97.38 95.72 97.29 98.86 97.26 95.67 96.93 98.19 100.6 103.0 101.07 99.13 93.26 87.38 89.49 91.6 92.25 92.89 84.87 76.85 81.68 86.51 89.55 92.58 85.4 78.23 67.96 57.69 70.31 82.92 80.6 78.27 78.91 79.55 76.48 73.4 68.66 63.92 67.35 70.78 72.61 74.44
+END_DATA
diff --git a/profile/D50_0.5.sp b/profile/D50_0.5.sp
new file mode 100644
index 0000000..367d0d3
--- /dev/null
+++ b/profile/D50_0.5.sp
@@ -0,0 +1,132 @@
+SPECT
+
+DESCRIPTOR "Argyll D50 illimunant spectral power, 50% U.V."
+
+ORIGINATOR "Argyll CMS"
+
+CREATED "Fri Jul 06 17:49:57 2001"
+KEYWORD "SPECTRAL_BANDS"
+SPECTRAL_BANDS "107"
+KEYWORD "SPECTRAL_START_NM"
+SPECTRAL_START_NM "300.000000"
+KEYWORD "SPECTRAL_END_NM"
+SPECTRAL_END_NM "830.000000"
+KEYWORD "SPECTRAL_NORM"
+SPECTRAL_NORM "100.000000"
+
+KEYWORD "SPEC_300"
+KEYWORD "SPEC_305"
+KEYWORD "SPEC_310"
+KEYWORD "SPEC_315"
+KEYWORD "SPEC_320"
+KEYWORD "SPEC_325"
+KEYWORD "SPEC_330"
+KEYWORD "SPEC_335"
+KEYWORD "SPEC_340"
+KEYWORD "SPEC_345"
+KEYWORD "SPEC_350"
+KEYWORD "SPEC_355"
+KEYWORD "SPEC_360"
+KEYWORD "SPEC_365"
+KEYWORD "SPEC_370"
+KEYWORD "SPEC_375"
+KEYWORD "SPEC_380"
+KEYWORD "SPEC_385"
+KEYWORD "SPEC_390"
+KEYWORD "SPEC_395"
+KEYWORD "SPEC_400"
+KEYWORD "SPEC_405"
+KEYWORD "SPEC_410"
+KEYWORD "SPEC_415"
+KEYWORD "SPEC_420"
+KEYWORD "SPEC_425"
+KEYWORD "SPEC_430"
+KEYWORD "SPEC_435"
+KEYWORD "SPEC_440"
+KEYWORD "SPEC_445"
+KEYWORD "SPEC_450"
+KEYWORD "SPEC_455"
+KEYWORD "SPEC_460"
+KEYWORD "SPEC_465"
+KEYWORD "SPEC_470"
+KEYWORD "SPEC_475"
+KEYWORD "SPEC_480"
+KEYWORD "SPEC_485"
+KEYWORD "SPEC_490"
+KEYWORD "SPEC_495"
+KEYWORD "SPEC_500"
+KEYWORD "SPEC_505"
+KEYWORD "SPEC_510"
+KEYWORD "SPEC_515"
+KEYWORD "SPEC_520"
+KEYWORD "SPEC_525"
+KEYWORD "SPEC_530"
+KEYWORD "SPEC_535"
+KEYWORD "SPEC_540"
+KEYWORD "SPEC_545"
+KEYWORD "SPEC_550"
+KEYWORD "SPEC_555"
+KEYWORD "SPEC_560"
+KEYWORD "SPEC_565"
+KEYWORD "SPEC_570"
+KEYWORD "SPEC_575"
+KEYWORD "SPEC_580"
+KEYWORD "SPEC_585"
+KEYWORD "SPEC_590"
+KEYWORD "SPEC_595"
+KEYWORD "SPEC_600"
+KEYWORD "SPEC_605"
+KEYWORD "SPEC_610"
+KEYWORD "SPEC_615"
+KEYWORD "SPEC_620"
+KEYWORD "SPEC_625"
+KEYWORD "SPEC_630"
+KEYWORD "SPEC_635"
+KEYWORD "SPEC_640"
+KEYWORD "SPEC_645"
+KEYWORD "SPEC_650"
+KEYWORD "SPEC_655"
+KEYWORD "SPEC_660"
+KEYWORD "SPEC_665"
+KEYWORD "SPEC_670"
+KEYWORD "SPEC_675"
+KEYWORD "SPEC_680"
+KEYWORD "SPEC_685"
+KEYWORD "SPEC_690"
+KEYWORD "SPEC_695"
+KEYWORD "SPEC_700"
+KEYWORD "SPEC_705"
+KEYWORD "SPEC_710"
+KEYWORD "SPEC_715"
+KEYWORD "SPEC_720"
+KEYWORD "SPEC_725"
+KEYWORD "SPEC_730"
+KEYWORD "SPEC_735"
+KEYWORD "SPEC_740"
+KEYWORD "SPEC_745"
+KEYWORD "SPEC_750"
+KEYWORD "SPEC_755"
+KEYWORD "SPEC_760"
+KEYWORD "SPEC_765"
+KEYWORD "SPEC_770"
+KEYWORD "SPEC_775"
+KEYWORD "SPEC_780"
+KEYWORD "SPEC_785"
+KEYWORD "SPEC_790"
+KEYWORD "SPEC_795"
+KEYWORD "SPEC_800"
+KEYWORD "SPEC_805"
+KEYWORD "SPEC_810"
+KEYWORD "SPEC_815"
+KEYWORD "SPEC_820"
+KEYWORD "SPEC_825"
+KEYWORD "SPEC_830"
+NUMBER_OF_FIELDS 107
+BEGIN_DATA_FORMAT
+SPEC_300 SPEC_305 SPEC_310 SPEC_315 SPEC_320 SPEC_325 SPEC_330 SPEC_335 SPEC_340 SPEC_345 SPEC_350 SPEC_355 SPEC_360 SPEC_365 SPEC_370 SPEC_375 SPEC_380 SPEC_385 SPEC_390 SPEC_395 SPEC_400 SPEC_405 SPEC_410 SPEC_415 SPEC_420 SPEC_425 SPEC_430 SPEC_435 SPEC_440 SPEC_445 SPEC_450 SPEC_455 SPEC_460 SPEC_465 SPEC_470 SPEC_475 SPEC_480 SPEC_485 SPEC_490 SPEC_495 SPEC_500 SPEC_505 SPEC_510 SPEC_515 SPEC_520 SPEC_525 SPEC_530 SPEC_535 SPEC_540 SPEC_545 SPEC_550 SPEC_555 SPEC_560 SPEC_565 SPEC_570 SPEC_575 SPEC_580 SPEC_585 SPEC_590 SPEC_595 SPEC_600 SPEC_605 SPEC_610 SPEC_615 SPEC_620 SPEC_625 SPEC_630 SPEC_635 SPEC_640 SPEC_645 SPEC_650 SPEC_655 SPEC_660 SPEC_665 SPEC_670 SPEC_675 SPEC_680 SPEC_685 SPEC_690 SPEC_695 SPEC_700 SPEC_705 SPEC_710 SPEC_715 SPEC_720 SPEC_725 SPEC_730 SPEC_735 SPEC_740 SPEC_745 SPEC_750 SPEC_755 SPEC_760 SPEC_765 SPEC_770 SPEC_775 SPEC_780 SPEC_785 SPEC_790 SPEC_795 SPEC_800 SPEC_805 SPEC_810 SPEC_815 SPEC_820 SPEC_825 SPEC_830
+END_DATA_FORMAT
+
+NUMBER_OF_SETS 1
+BEGIN_DATA
+0.01 0.52 1.03 2.46 3.89 5.63 7.38 8.18 8.98 9.74 10.51 11.24 11.97 12.73 13.48 12.86 13.47 16.31 20.91 31.67 44.38 50.26 56.51 58.27 60.03 58.93 57.82 66.32 74.82 81.04 87.25 88.93 90.61 90.99 91.37 93.24 95.11 93.54 91.96 93.84 95.72 96.17 96.61 96.87 97.13 99.61 102.1 101.43 100.75 101.54 102.32 101.16 100.0 98.87 97.74 98.33 98.92 96.21 93.5 95.59 97.69 98.48 99.27 99.16 99.04 97.38 95.72 97.29 98.86 97.26 95.67 96.93 98.19 100.6 103.0 101.07 99.13 93.26 87.38 89.49 91.6 92.25 92.89 84.87 76.85 81.68 86.51 89.55 92.58 85.4 78.23 67.96 57.69 70.31 82.92 80.6 78.27 78.91 79.55 76.48 73.4 68.66 63.92 67.35 70.78 72.61 74.44
+END_DATA
diff --git a/profile/D50_0.7.sp b/profile/D50_0.7.sp
new file mode 100644
index 0000000..2e0b2e8
--- /dev/null
+++ b/profile/D50_0.7.sp
@@ -0,0 +1,132 @@
+SPECT
+
+DESCRIPTOR "Argyll D50 illimunant spectral power, 70% U.V."
+
+ORIGINATOR "Argyll CMS"
+
+CREATED "Fri Jul 06 17:49:57 2001"
+KEYWORD "SPECTRAL_BANDS"
+SPECTRAL_BANDS "107"
+KEYWORD "SPECTRAL_START_NM"
+SPECTRAL_START_NM "300.000000"
+KEYWORD "SPECTRAL_END_NM"
+SPECTRAL_END_NM "830.000000"
+KEYWORD "SPECTRAL_NORM"
+SPECTRAL_NORM "100.000000"
+
+KEYWORD "SPEC_300"
+KEYWORD "SPEC_305"
+KEYWORD "SPEC_310"
+KEYWORD "SPEC_315"
+KEYWORD "SPEC_320"
+KEYWORD "SPEC_325"
+KEYWORD "SPEC_330"
+KEYWORD "SPEC_335"
+KEYWORD "SPEC_340"
+KEYWORD "SPEC_345"
+KEYWORD "SPEC_350"
+KEYWORD "SPEC_355"
+KEYWORD "SPEC_360"
+KEYWORD "SPEC_365"
+KEYWORD "SPEC_370"
+KEYWORD "SPEC_375"
+KEYWORD "SPEC_380"
+KEYWORD "SPEC_385"
+KEYWORD "SPEC_390"
+KEYWORD "SPEC_395"
+KEYWORD "SPEC_400"
+KEYWORD "SPEC_405"
+KEYWORD "SPEC_410"
+KEYWORD "SPEC_415"
+KEYWORD "SPEC_420"
+KEYWORD "SPEC_425"
+KEYWORD "SPEC_430"
+KEYWORD "SPEC_435"
+KEYWORD "SPEC_440"
+KEYWORD "SPEC_445"
+KEYWORD "SPEC_450"
+KEYWORD "SPEC_455"
+KEYWORD "SPEC_460"
+KEYWORD "SPEC_465"
+KEYWORD "SPEC_470"
+KEYWORD "SPEC_475"
+KEYWORD "SPEC_480"
+KEYWORD "SPEC_485"
+KEYWORD "SPEC_490"
+KEYWORD "SPEC_495"
+KEYWORD "SPEC_500"
+KEYWORD "SPEC_505"
+KEYWORD "SPEC_510"
+KEYWORD "SPEC_515"
+KEYWORD "SPEC_520"
+KEYWORD "SPEC_525"
+KEYWORD "SPEC_530"
+KEYWORD "SPEC_535"
+KEYWORD "SPEC_540"
+KEYWORD "SPEC_545"
+KEYWORD "SPEC_550"
+KEYWORD "SPEC_555"
+KEYWORD "SPEC_560"
+KEYWORD "SPEC_565"
+KEYWORD "SPEC_570"
+KEYWORD "SPEC_575"
+KEYWORD "SPEC_580"
+KEYWORD "SPEC_585"
+KEYWORD "SPEC_590"
+KEYWORD "SPEC_595"
+KEYWORD "SPEC_600"
+KEYWORD "SPEC_605"
+KEYWORD "SPEC_610"
+KEYWORD "SPEC_615"
+KEYWORD "SPEC_620"
+KEYWORD "SPEC_625"
+KEYWORD "SPEC_630"
+KEYWORD "SPEC_635"
+KEYWORD "SPEC_640"
+KEYWORD "SPEC_645"
+KEYWORD "SPEC_650"
+KEYWORD "SPEC_655"
+KEYWORD "SPEC_660"
+KEYWORD "SPEC_665"
+KEYWORD "SPEC_670"
+KEYWORD "SPEC_675"
+KEYWORD "SPEC_680"
+KEYWORD "SPEC_685"
+KEYWORD "SPEC_690"
+KEYWORD "SPEC_695"
+KEYWORD "SPEC_700"
+KEYWORD "SPEC_705"
+KEYWORD "SPEC_710"
+KEYWORD "SPEC_715"
+KEYWORD "SPEC_720"
+KEYWORD "SPEC_725"
+KEYWORD "SPEC_730"
+KEYWORD "SPEC_735"
+KEYWORD "SPEC_740"
+KEYWORD "SPEC_745"
+KEYWORD "SPEC_750"
+KEYWORD "SPEC_755"
+KEYWORD "SPEC_760"
+KEYWORD "SPEC_765"
+KEYWORD "SPEC_770"
+KEYWORD "SPEC_775"
+KEYWORD "SPEC_780"
+KEYWORD "SPEC_785"
+KEYWORD "SPEC_790"
+KEYWORD "SPEC_795"
+KEYWORD "SPEC_800"
+KEYWORD "SPEC_805"
+KEYWORD "SPEC_810"
+KEYWORD "SPEC_815"
+KEYWORD "SPEC_820"
+KEYWORD "SPEC_825"
+KEYWORD "SPEC_830"
+NUMBER_OF_FIELDS 107
+BEGIN_DATA_FORMAT
+SPEC_300 SPEC_305 SPEC_310 SPEC_315 SPEC_320 SPEC_325 SPEC_330 SPEC_335 SPEC_340 SPEC_345 SPEC_350 SPEC_355 SPEC_360 SPEC_365 SPEC_370 SPEC_375 SPEC_380 SPEC_385 SPEC_390 SPEC_395 SPEC_400 SPEC_405 SPEC_410 SPEC_415 SPEC_420 SPEC_425 SPEC_430 SPEC_435 SPEC_440 SPEC_445 SPEC_450 SPEC_455 SPEC_460 SPEC_465 SPEC_470 SPEC_475 SPEC_480 SPEC_485 SPEC_490 SPEC_495 SPEC_500 SPEC_505 SPEC_510 SPEC_515 SPEC_520 SPEC_525 SPEC_530 SPEC_535 SPEC_540 SPEC_545 SPEC_550 SPEC_555 SPEC_560 SPEC_565 SPEC_570 SPEC_575 SPEC_580 SPEC_585 SPEC_590 SPEC_595 SPEC_600 SPEC_605 SPEC_610 SPEC_615 SPEC_620 SPEC_625 SPEC_630 SPEC_635 SPEC_640 SPEC_645 SPEC_650 SPEC_655 SPEC_660 SPEC_665 SPEC_670 SPEC_675 SPEC_680 SPEC_685 SPEC_690 SPEC_695 SPEC_700 SPEC_705 SPEC_710 SPEC_715 SPEC_720 SPEC_725 SPEC_730 SPEC_735 SPEC_740 SPEC_745 SPEC_750 SPEC_755 SPEC_760 SPEC_765 SPEC_770 SPEC_775 SPEC_780 SPEC_785 SPEC_790 SPEC_795 SPEC_800 SPEC_805 SPEC_810 SPEC_815 SPEC_820 SPEC_825 SPEC_830
+END_DATA_FORMAT
+
+NUMBER_OF_SETS 1
+BEGIN_DATA
+0.01 0.72 1.44 3.44 5.45 7.88 10.33 11.45 12.57 13.64 14.71 15.74 16.76 17.82 18.87 18 17.88 20.66 24.49 34.84 46.35 51.32 56.51 58.27 60.03 58.93 57.82 66.32 74.82 81.04 87.25 88.93 90.61 90.99 91.37 93.24 95.11 93.54 91.96 93.84 95.72 96.17 96.61 96.87 97.13 99.61 102.1 101.43 100.75 101.54 102.32 101.16 100.0 98.87 97.74 98.33 98.92 96.21 93.5 95.59 97.69 98.48 99.27 99.16 99.04 97.38 95.72 97.29 98.86 97.26 95.67 96.93 98.19 100.6 103.0 101.07 99.13 93.26 87.38 89.49 91.6 92.25 92.89 84.87 76.85 81.68 86.51 89.55 92.58 85.4 78.23 67.96 57.69 70.31 82.92 80.6 78.27 78.91 79.55 76.48 73.4 68.66 63.92 67.35 70.78 72.61 74.44
+END_DATA
diff --git a/profile/D50_1.0.sp b/profile/D50_1.0.sp
new file mode 100644
index 0000000..d74e76a
--- /dev/null
+++ b/profile/D50_1.0.sp
@@ -0,0 +1,132 @@
+SPECT
+
+DESCRIPTOR "Argyll D50 illimunant spectral power, Standard U.V."
+
+ORIGINATOR "Argyll CMS"
+
+CREATED "Fri Jul 06 17:49:57 2001"
+KEYWORD "SPECTRAL_BANDS"
+SPECTRAL_BANDS "107"
+KEYWORD "SPECTRAL_START_NM"
+SPECTRAL_START_NM "300.000000"
+KEYWORD "SPECTRAL_END_NM"
+SPECTRAL_END_NM "830.000000"
+KEYWORD "SPECTRAL_NORM"
+SPECTRAL_NORM "100.000000"
+
+KEYWORD "SPEC_300"
+KEYWORD "SPEC_305"
+KEYWORD "SPEC_310"
+KEYWORD "SPEC_315"
+KEYWORD "SPEC_320"
+KEYWORD "SPEC_325"
+KEYWORD "SPEC_330"
+KEYWORD "SPEC_335"
+KEYWORD "SPEC_340"
+KEYWORD "SPEC_345"
+KEYWORD "SPEC_350"
+KEYWORD "SPEC_355"
+KEYWORD "SPEC_360"
+KEYWORD "SPEC_365"
+KEYWORD "SPEC_370"
+KEYWORD "SPEC_375"
+KEYWORD "SPEC_380"
+KEYWORD "SPEC_385"
+KEYWORD "SPEC_390"
+KEYWORD "SPEC_395"
+KEYWORD "SPEC_400"
+KEYWORD "SPEC_405"
+KEYWORD "SPEC_410"
+KEYWORD "SPEC_415"
+KEYWORD "SPEC_420"
+KEYWORD "SPEC_425"
+KEYWORD "SPEC_430"
+KEYWORD "SPEC_435"
+KEYWORD "SPEC_440"
+KEYWORD "SPEC_445"
+KEYWORD "SPEC_450"
+KEYWORD "SPEC_455"
+KEYWORD "SPEC_460"
+KEYWORD "SPEC_465"
+KEYWORD "SPEC_470"
+KEYWORD "SPEC_475"
+KEYWORD "SPEC_480"
+KEYWORD "SPEC_485"
+KEYWORD "SPEC_490"
+KEYWORD "SPEC_495"
+KEYWORD "SPEC_500"
+KEYWORD "SPEC_505"
+KEYWORD "SPEC_510"
+KEYWORD "SPEC_515"
+KEYWORD "SPEC_520"
+KEYWORD "SPEC_525"
+KEYWORD "SPEC_530"
+KEYWORD "SPEC_535"
+KEYWORD "SPEC_540"
+KEYWORD "SPEC_545"
+KEYWORD "SPEC_550"
+KEYWORD "SPEC_555"
+KEYWORD "SPEC_560"
+KEYWORD "SPEC_565"
+KEYWORD "SPEC_570"
+KEYWORD "SPEC_575"
+KEYWORD "SPEC_580"
+KEYWORD "SPEC_585"
+KEYWORD "SPEC_590"
+KEYWORD "SPEC_595"
+KEYWORD "SPEC_600"
+KEYWORD "SPEC_605"
+KEYWORD "SPEC_610"
+KEYWORD "SPEC_615"
+KEYWORD "SPEC_620"
+KEYWORD "SPEC_625"
+KEYWORD "SPEC_630"
+KEYWORD "SPEC_635"
+KEYWORD "SPEC_640"
+KEYWORD "SPEC_645"
+KEYWORD "SPEC_650"
+KEYWORD "SPEC_655"
+KEYWORD "SPEC_660"
+KEYWORD "SPEC_665"
+KEYWORD "SPEC_670"
+KEYWORD "SPEC_675"
+KEYWORD "SPEC_680"
+KEYWORD "SPEC_685"
+KEYWORD "SPEC_690"
+KEYWORD "SPEC_695"
+KEYWORD "SPEC_700"
+KEYWORD "SPEC_705"
+KEYWORD "SPEC_710"
+KEYWORD "SPEC_715"
+KEYWORD "SPEC_720"
+KEYWORD "SPEC_725"
+KEYWORD "SPEC_730"
+KEYWORD "SPEC_735"
+KEYWORD "SPEC_740"
+KEYWORD "SPEC_745"
+KEYWORD "SPEC_750"
+KEYWORD "SPEC_755"
+KEYWORD "SPEC_760"
+KEYWORD "SPEC_765"
+KEYWORD "SPEC_770"
+KEYWORD "SPEC_775"
+KEYWORD "SPEC_780"
+KEYWORD "SPEC_785"
+KEYWORD "SPEC_790"
+KEYWORD "SPEC_795"
+KEYWORD "SPEC_800"
+KEYWORD "SPEC_805"
+KEYWORD "SPEC_810"
+KEYWORD "SPEC_815"
+KEYWORD "SPEC_820"
+KEYWORD "SPEC_825"
+KEYWORD "SPEC_830"
+NUMBER_OF_FIELDS 107
+BEGIN_DATA_FORMAT
+SPEC_300 SPEC_305 SPEC_310 SPEC_315 SPEC_320 SPEC_325 SPEC_330 SPEC_335 SPEC_340 SPEC_345 SPEC_350 SPEC_355 SPEC_360 SPEC_365 SPEC_370 SPEC_375 SPEC_380 SPEC_385 SPEC_390 SPEC_395 SPEC_400 SPEC_405 SPEC_410 SPEC_415 SPEC_420 SPEC_425 SPEC_430 SPEC_435 SPEC_440 SPEC_445 SPEC_450 SPEC_455 SPEC_460 SPEC_465 SPEC_470 SPEC_475 SPEC_480 SPEC_485 SPEC_490 SPEC_495 SPEC_500 SPEC_505 SPEC_510 SPEC_515 SPEC_520 SPEC_525 SPEC_530 SPEC_535 SPEC_540 SPEC_545 SPEC_550 SPEC_555 SPEC_560 SPEC_565 SPEC_570 SPEC_575 SPEC_580 SPEC_585 SPEC_590 SPEC_595 SPEC_600 SPEC_605 SPEC_610 SPEC_615 SPEC_620 SPEC_625 SPEC_630 SPEC_635 SPEC_640 SPEC_645 SPEC_650 SPEC_655 SPEC_660 SPEC_665 SPEC_670 SPEC_675 SPEC_680 SPEC_685 SPEC_690 SPEC_695 SPEC_700 SPEC_705 SPEC_710 SPEC_715 SPEC_720 SPEC_725 SPEC_730 SPEC_735 SPEC_740 SPEC_745 SPEC_750 SPEC_755 SPEC_760 SPEC_765 SPEC_770 SPEC_775 SPEC_780 SPEC_785 SPEC_790 SPEC_795 SPEC_800 SPEC_805 SPEC_810 SPEC_815 SPEC_820 SPEC_825 SPEC_830
+END_DATA_FORMAT
+
+NUMBER_OF_SETS 1
+BEGIN_DATA
+0.020000 1.0300 2.0500 4.9100 7.7800 11.260 14.750 16.350 17.950 19.480 21.010 22.480 23.940 25.450 26.960 25.720 24.490 27.180 29.870 39.590 49.310 52.910 56.510 58.270 60.030 58.930 57.820 66.320 74.820 81.040 87.250 88.930 90.610 90.990 91.370 93.240 95.110 93.540 91.960 93.840 95.720 96.170 96.610 96.870 97.130 99.610 102.10 101.43 100.75 101.54 102.32 101.16 100.00 98.870 97.740 98.330 98.920 96.210 93.500 95.590 97.690 98.480 99.270 99.160 99.040 97.380 95.720 97.290 98.860 97.260 95.670 96.930 98.190 100.60 103.00 101.07 99.130 93.260 87.380 89.490 91.600 92.250 92.890 84.870 76.850 81.680 86.510 89.550 92.580 85.400 78.230 67.960 57.690 70.310 82.920 80.600 78.270 78.910 79.550 76.480 73.400 68.660 63.920 67.350 70.780 72.610 74.440
+END_DATA
diff --git a/profile/D50_1.2.sp b/profile/D50_1.2.sp
new file mode 100644
index 0000000..180d650
--- /dev/null
+++ b/profile/D50_1.2.sp
@@ -0,0 +1,132 @@
+SPECT
+
+DESCRIPTOR "Argyll D50 illimunant spectral power, 120% U.V."
+
+ORIGINATOR "Argyll CMS"
+
+CREATED "Fri Jul 06 17:49:57 2001"
+KEYWORD "SPECTRAL_BANDS"
+SPECTRAL_BANDS "107"
+KEYWORD "SPECTRAL_START_NM"
+SPECTRAL_START_NM "300.000000"
+KEYWORD "SPECTRAL_END_NM"
+SPECTRAL_END_NM "830.000000"
+KEYWORD "SPECTRAL_NORM"
+SPECTRAL_NORM "100.000000"
+
+KEYWORD "SPEC_300"
+KEYWORD "SPEC_305"
+KEYWORD "SPEC_310"
+KEYWORD "SPEC_315"
+KEYWORD "SPEC_320"
+KEYWORD "SPEC_325"
+KEYWORD "SPEC_330"
+KEYWORD "SPEC_335"
+KEYWORD "SPEC_340"
+KEYWORD "SPEC_345"
+KEYWORD "SPEC_350"
+KEYWORD "SPEC_355"
+KEYWORD "SPEC_360"
+KEYWORD "SPEC_365"
+KEYWORD "SPEC_370"
+KEYWORD "SPEC_375"
+KEYWORD "SPEC_380"
+KEYWORD "SPEC_385"
+KEYWORD "SPEC_390"
+KEYWORD "SPEC_395"
+KEYWORD "SPEC_400"
+KEYWORD "SPEC_405"
+KEYWORD "SPEC_410"
+KEYWORD "SPEC_415"
+KEYWORD "SPEC_420"
+KEYWORD "SPEC_425"
+KEYWORD "SPEC_430"
+KEYWORD "SPEC_435"
+KEYWORD "SPEC_440"
+KEYWORD "SPEC_445"
+KEYWORD "SPEC_450"
+KEYWORD "SPEC_455"
+KEYWORD "SPEC_460"
+KEYWORD "SPEC_465"
+KEYWORD "SPEC_470"
+KEYWORD "SPEC_475"
+KEYWORD "SPEC_480"
+KEYWORD "SPEC_485"
+KEYWORD "SPEC_490"
+KEYWORD "SPEC_495"
+KEYWORD "SPEC_500"
+KEYWORD "SPEC_505"
+KEYWORD "SPEC_510"
+KEYWORD "SPEC_515"
+KEYWORD "SPEC_520"
+KEYWORD "SPEC_525"
+KEYWORD "SPEC_530"
+KEYWORD "SPEC_535"
+KEYWORD "SPEC_540"
+KEYWORD "SPEC_545"
+KEYWORD "SPEC_550"
+KEYWORD "SPEC_555"
+KEYWORD "SPEC_560"
+KEYWORD "SPEC_565"
+KEYWORD "SPEC_570"
+KEYWORD "SPEC_575"
+KEYWORD "SPEC_580"
+KEYWORD "SPEC_585"
+KEYWORD "SPEC_590"
+KEYWORD "SPEC_595"
+KEYWORD "SPEC_600"
+KEYWORD "SPEC_605"
+KEYWORD "SPEC_610"
+KEYWORD "SPEC_615"
+KEYWORD "SPEC_620"
+KEYWORD "SPEC_625"
+KEYWORD "SPEC_630"
+KEYWORD "SPEC_635"
+KEYWORD "SPEC_640"
+KEYWORD "SPEC_645"
+KEYWORD "SPEC_650"
+KEYWORD "SPEC_655"
+KEYWORD "SPEC_660"
+KEYWORD "SPEC_665"
+KEYWORD "SPEC_670"
+KEYWORD "SPEC_675"
+KEYWORD "SPEC_680"
+KEYWORD "SPEC_685"
+KEYWORD "SPEC_690"
+KEYWORD "SPEC_695"
+KEYWORD "SPEC_700"
+KEYWORD "SPEC_705"
+KEYWORD "SPEC_710"
+KEYWORD "SPEC_715"
+KEYWORD "SPEC_720"
+KEYWORD "SPEC_725"
+KEYWORD "SPEC_730"
+KEYWORD "SPEC_735"
+KEYWORD "SPEC_740"
+KEYWORD "SPEC_745"
+KEYWORD "SPEC_750"
+KEYWORD "SPEC_755"
+KEYWORD "SPEC_760"
+KEYWORD "SPEC_765"
+KEYWORD "SPEC_770"
+KEYWORD "SPEC_775"
+KEYWORD "SPEC_780"
+KEYWORD "SPEC_785"
+KEYWORD "SPEC_790"
+KEYWORD "SPEC_795"
+KEYWORD "SPEC_800"
+KEYWORD "SPEC_805"
+KEYWORD "SPEC_810"
+KEYWORD "SPEC_815"
+KEYWORD "SPEC_820"
+KEYWORD "SPEC_825"
+KEYWORD "SPEC_830"
+NUMBER_OF_FIELDS 107
+BEGIN_DATA_FORMAT
+SPEC_300 SPEC_305 SPEC_310 SPEC_315 SPEC_320 SPEC_325 SPEC_330 SPEC_335 SPEC_340 SPEC_345 SPEC_350 SPEC_355 SPEC_360 SPEC_365 SPEC_370 SPEC_375 SPEC_380 SPEC_385 SPEC_390 SPEC_395 SPEC_400 SPEC_405 SPEC_410 SPEC_415 SPEC_420 SPEC_425 SPEC_430 SPEC_435 SPEC_440 SPEC_445 SPEC_450 SPEC_455 SPEC_460 SPEC_465 SPEC_470 SPEC_475 SPEC_480 SPEC_485 SPEC_490 SPEC_495 SPEC_500 SPEC_505 SPEC_510 SPEC_515 SPEC_520 SPEC_525 SPEC_530 SPEC_535 SPEC_540 SPEC_545 SPEC_550 SPEC_555 SPEC_560 SPEC_565 SPEC_570 SPEC_575 SPEC_580 SPEC_585 SPEC_590 SPEC_595 SPEC_600 SPEC_605 SPEC_610 SPEC_615 SPEC_620 SPEC_625 SPEC_630 SPEC_635 SPEC_640 SPEC_645 SPEC_650 SPEC_655 SPEC_660 SPEC_665 SPEC_670 SPEC_675 SPEC_680 SPEC_685 SPEC_690 SPEC_695 SPEC_700 SPEC_705 SPEC_710 SPEC_715 SPEC_720 SPEC_725 SPEC_730 SPEC_735 SPEC_740 SPEC_745 SPEC_750 SPEC_755 SPEC_760 SPEC_765 SPEC_770 SPEC_775 SPEC_780 SPEC_785 SPEC_790 SPEC_795 SPEC_800 SPEC_805 SPEC_810 SPEC_815 SPEC_820 SPEC_825 SPEC_830
+END_DATA_FORMAT
+
+NUMBER_OF_SETS 1
+BEGIN_DATA
+0.02 1.24 2.46 5.89 9.34 13.51 17.7 19.62 21.54 23.38 25.21 26.98 28.73 30.54 32.35 30.86 28.9 31.53 33.45 42.76 51.28 53.97 56.51 58.27 60.03 58.93 57.82 66.32 74.82 81.04 87.25 88.93 90.61 90.99 91.37 93.24 95.11 93.54 91.96 93.84 95.72 96.17 96.61 96.87 97.13 99.61 102.1 101.43 100.75 101.54 102.32 101.16 100.0 98.87 97.74 98.33 98.92 96.21 93.5 95.59 97.69 98.48 99.27 99.16 99.04 97.38 95.72 97.29 98.86 97.26 95.67 96.93 98.19 100.6 103.0 101.07 99.13 93.26 87.38 89.49 91.6 92.25 92.89 84.87 76.85 81.68 86.51 89.55 92.58 85.4 78.23 67.96 57.69 70.31 82.92 80.6 78.27 78.91 79.55 76.48 73.4 68.66 63.92 67.35 70.78 72.61 74.44
+END_DATA
diff --git a/profile/D50_1.5.sp b/profile/D50_1.5.sp
new file mode 100644
index 0000000..786e292
--- /dev/null
+++ b/profile/D50_1.5.sp
@@ -0,0 +1,132 @@
+SPECT
+
+DESCRIPTOR "Argyll D50 illimunant spectral power, 150% U.V."
+
+ORIGINATOR "Argyll CMS"
+
+CREATED "Fri Jul 06 17:49:57 2001"
+KEYWORD "SPECTRAL_BANDS"
+SPECTRAL_BANDS "107"
+KEYWORD "SPECTRAL_START_NM"
+SPECTRAL_START_NM "300.000000"
+KEYWORD "SPECTRAL_END_NM"
+SPECTRAL_END_NM "830.000000"
+KEYWORD "SPECTRAL_NORM"
+SPECTRAL_NORM "100.000000"
+
+KEYWORD "SPEC_300"
+KEYWORD "SPEC_305"
+KEYWORD "SPEC_310"
+KEYWORD "SPEC_315"
+KEYWORD "SPEC_320"
+KEYWORD "SPEC_325"
+KEYWORD "SPEC_330"
+KEYWORD "SPEC_335"
+KEYWORD "SPEC_340"
+KEYWORD "SPEC_345"
+KEYWORD "SPEC_350"
+KEYWORD "SPEC_355"
+KEYWORD "SPEC_360"
+KEYWORD "SPEC_365"
+KEYWORD "SPEC_370"
+KEYWORD "SPEC_375"
+KEYWORD "SPEC_380"
+KEYWORD "SPEC_385"
+KEYWORD "SPEC_390"
+KEYWORD "SPEC_395"
+KEYWORD "SPEC_400"
+KEYWORD "SPEC_405"
+KEYWORD "SPEC_410"
+KEYWORD "SPEC_415"
+KEYWORD "SPEC_420"
+KEYWORD "SPEC_425"
+KEYWORD "SPEC_430"
+KEYWORD "SPEC_435"
+KEYWORD "SPEC_440"
+KEYWORD "SPEC_445"
+KEYWORD "SPEC_450"
+KEYWORD "SPEC_455"
+KEYWORD "SPEC_460"
+KEYWORD "SPEC_465"
+KEYWORD "SPEC_470"
+KEYWORD "SPEC_475"
+KEYWORD "SPEC_480"
+KEYWORD "SPEC_485"
+KEYWORD "SPEC_490"
+KEYWORD "SPEC_495"
+KEYWORD "SPEC_500"
+KEYWORD "SPEC_505"
+KEYWORD "SPEC_510"
+KEYWORD "SPEC_515"
+KEYWORD "SPEC_520"
+KEYWORD "SPEC_525"
+KEYWORD "SPEC_530"
+KEYWORD "SPEC_535"
+KEYWORD "SPEC_540"
+KEYWORD "SPEC_545"
+KEYWORD "SPEC_550"
+KEYWORD "SPEC_555"
+KEYWORD "SPEC_560"
+KEYWORD "SPEC_565"
+KEYWORD "SPEC_570"
+KEYWORD "SPEC_575"
+KEYWORD "SPEC_580"
+KEYWORD "SPEC_585"
+KEYWORD "SPEC_590"
+KEYWORD "SPEC_595"
+KEYWORD "SPEC_600"
+KEYWORD "SPEC_605"
+KEYWORD "SPEC_610"
+KEYWORD "SPEC_615"
+KEYWORD "SPEC_620"
+KEYWORD "SPEC_625"
+KEYWORD "SPEC_630"
+KEYWORD "SPEC_635"
+KEYWORD "SPEC_640"
+KEYWORD "SPEC_645"
+KEYWORD "SPEC_650"
+KEYWORD "SPEC_655"
+KEYWORD "SPEC_660"
+KEYWORD "SPEC_665"
+KEYWORD "SPEC_670"
+KEYWORD "SPEC_675"
+KEYWORD "SPEC_680"
+KEYWORD "SPEC_685"
+KEYWORD "SPEC_690"
+KEYWORD "SPEC_695"
+KEYWORD "SPEC_700"
+KEYWORD "SPEC_705"
+KEYWORD "SPEC_710"
+KEYWORD "SPEC_715"
+KEYWORD "SPEC_720"
+KEYWORD "SPEC_725"
+KEYWORD "SPEC_730"
+KEYWORD "SPEC_735"
+KEYWORD "SPEC_740"
+KEYWORD "SPEC_745"
+KEYWORD "SPEC_750"
+KEYWORD "SPEC_755"
+KEYWORD "SPEC_760"
+KEYWORD "SPEC_765"
+KEYWORD "SPEC_770"
+KEYWORD "SPEC_775"
+KEYWORD "SPEC_780"
+KEYWORD "SPEC_785"
+KEYWORD "SPEC_790"
+KEYWORD "SPEC_795"
+KEYWORD "SPEC_800"
+KEYWORD "SPEC_805"
+KEYWORD "SPEC_810"
+KEYWORD "SPEC_815"
+KEYWORD "SPEC_820"
+KEYWORD "SPEC_825"
+KEYWORD "SPEC_830"
+NUMBER_OF_FIELDS 107
+BEGIN_DATA_FORMAT
+SPEC_300 SPEC_305 SPEC_310 SPEC_315 SPEC_320 SPEC_325 SPEC_330 SPEC_335 SPEC_340 SPEC_345 SPEC_350 SPEC_355 SPEC_360 SPEC_365 SPEC_370 SPEC_375 SPEC_380 SPEC_385 SPEC_390 SPEC_395 SPEC_400 SPEC_405 SPEC_410 SPEC_415 SPEC_420 SPEC_425 SPEC_430 SPEC_435 SPEC_440 SPEC_445 SPEC_450 SPEC_455 SPEC_460 SPEC_465 SPEC_470 SPEC_475 SPEC_480 SPEC_485 SPEC_490 SPEC_495 SPEC_500 SPEC_505 SPEC_510 SPEC_515 SPEC_520 SPEC_525 SPEC_530 SPEC_535 SPEC_540 SPEC_545 SPEC_550 SPEC_555 SPEC_560 SPEC_565 SPEC_570 SPEC_575 SPEC_580 SPEC_585 SPEC_590 SPEC_595 SPEC_600 SPEC_605 SPEC_610 SPEC_615 SPEC_620 SPEC_625 SPEC_630 SPEC_635 SPEC_640 SPEC_645 SPEC_650 SPEC_655 SPEC_660 SPEC_665 SPEC_670 SPEC_675 SPEC_680 SPEC_685 SPEC_690 SPEC_695 SPEC_700 SPEC_705 SPEC_710 SPEC_715 SPEC_720 SPEC_725 SPEC_730 SPEC_735 SPEC_740 SPEC_745 SPEC_750 SPEC_755 SPEC_760 SPEC_765 SPEC_770 SPEC_775 SPEC_780 SPEC_785 SPEC_790 SPEC_795 SPEC_800 SPEC_805 SPEC_810 SPEC_815 SPEC_820 SPEC_825 SPEC_830
+END_DATA_FORMAT
+
+NUMBER_OF_SETS 1
+BEGIN_DATA
+0.03 1.55 3.08 7.37 11.67 16.89 22.13 24.53 26.93 29.22 31.52 33.72 35.91 38.18 40.44 38.58 35.51 38.05 38.83 47.51 54.24 55.56 56.51 58.27 60.03 58.93 57.82 66.32 74.82 81.04 87.25 88.93 90.61 90.99 91.37 93.24 95.11 93.54 91.96 93.84 95.72 96.17 96.61 96.87 97.13 99.61 102.1 101.43 100.75 101.54 102.32 101.16 100.0 98.87 97.74 98.33 98.92 96.21 93.5 95.59 97.69 98.48 99.27 99.16 99.04 97.38 95.72 97.29 98.86 97.26 95.67 96.93 98.19 100.6 103.0 101.07 99.13 93.26 87.38 89.49 91.6 92.25 92.89 84.87 76.85 81.68 86.51 89.55 92.58 85.4 78.23 67.96 57.69 70.31 82.92 80.6 78.27 78.91 79.55 76.48 73.4 68.66 63.92 67.35 70.78 72.61 74.44
+END_DATA
diff --git a/profile/D50_1.7.sp b/profile/D50_1.7.sp
new file mode 100644
index 0000000..ba8dc6b
--- /dev/null
+++ b/profile/D50_1.7.sp
@@ -0,0 +1,132 @@
+SPECT
+
+DESCRIPTOR "Argyll D50 illimunant spectral power, 170% U.V."
+
+ORIGINATOR "Argyll CMS"
+
+CREATED "Fri Jul 06 17:49:57 2001"
+KEYWORD "SPECTRAL_BANDS"
+SPECTRAL_BANDS "107"
+KEYWORD "SPECTRAL_START_NM"
+SPECTRAL_START_NM "300.000000"
+KEYWORD "SPECTRAL_END_NM"
+SPECTRAL_END_NM "830.000000"
+KEYWORD "SPECTRAL_NORM"
+SPECTRAL_NORM "100.000000"
+
+KEYWORD "SPEC_300"
+KEYWORD "SPEC_305"
+KEYWORD "SPEC_310"
+KEYWORD "SPEC_315"
+KEYWORD "SPEC_320"
+KEYWORD "SPEC_325"
+KEYWORD "SPEC_330"
+KEYWORD "SPEC_335"
+KEYWORD "SPEC_340"
+KEYWORD "SPEC_345"
+KEYWORD "SPEC_350"
+KEYWORD "SPEC_355"
+KEYWORD "SPEC_360"
+KEYWORD "SPEC_365"
+KEYWORD "SPEC_370"
+KEYWORD "SPEC_375"
+KEYWORD "SPEC_380"
+KEYWORD "SPEC_385"
+KEYWORD "SPEC_390"
+KEYWORD "SPEC_395"
+KEYWORD "SPEC_400"
+KEYWORD "SPEC_405"
+KEYWORD "SPEC_410"
+KEYWORD "SPEC_415"
+KEYWORD "SPEC_420"
+KEYWORD "SPEC_425"
+KEYWORD "SPEC_430"
+KEYWORD "SPEC_435"
+KEYWORD "SPEC_440"
+KEYWORD "SPEC_445"
+KEYWORD "SPEC_450"
+KEYWORD "SPEC_455"
+KEYWORD "SPEC_460"
+KEYWORD "SPEC_465"
+KEYWORD "SPEC_470"
+KEYWORD "SPEC_475"
+KEYWORD "SPEC_480"
+KEYWORD "SPEC_485"
+KEYWORD "SPEC_490"
+KEYWORD "SPEC_495"
+KEYWORD "SPEC_500"
+KEYWORD "SPEC_505"
+KEYWORD "SPEC_510"
+KEYWORD "SPEC_515"
+KEYWORD "SPEC_520"
+KEYWORD "SPEC_525"
+KEYWORD "SPEC_530"
+KEYWORD "SPEC_535"
+KEYWORD "SPEC_540"
+KEYWORD "SPEC_545"
+KEYWORD "SPEC_550"
+KEYWORD "SPEC_555"
+KEYWORD "SPEC_560"
+KEYWORD "SPEC_565"
+KEYWORD "SPEC_570"
+KEYWORD "SPEC_575"
+KEYWORD "SPEC_580"
+KEYWORD "SPEC_585"
+KEYWORD "SPEC_590"
+KEYWORD "SPEC_595"
+KEYWORD "SPEC_600"
+KEYWORD "SPEC_605"
+KEYWORD "SPEC_610"
+KEYWORD "SPEC_615"
+KEYWORD "SPEC_620"
+KEYWORD "SPEC_625"
+KEYWORD "SPEC_630"
+KEYWORD "SPEC_635"
+KEYWORD "SPEC_640"
+KEYWORD "SPEC_645"
+KEYWORD "SPEC_650"
+KEYWORD "SPEC_655"
+KEYWORD "SPEC_660"
+KEYWORD "SPEC_665"
+KEYWORD "SPEC_670"
+KEYWORD "SPEC_675"
+KEYWORD "SPEC_680"
+KEYWORD "SPEC_685"
+KEYWORD "SPEC_690"
+KEYWORD "SPEC_695"
+KEYWORD "SPEC_700"
+KEYWORD "SPEC_705"
+KEYWORD "SPEC_710"
+KEYWORD "SPEC_715"
+KEYWORD "SPEC_720"
+KEYWORD "SPEC_725"
+KEYWORD "SPEC_730"
+KEYWORD "SPEC_735"
+KEYWORD "SPEC_740"
+KEYWORD "SPEC_745"
+KEYWORD "SPEC_750"
+KEYWORD "SPEC_755"
+KEYWORD "SPEC_760"
+KEYWORD "SPEC_765"
+KEYWORD "SPEC_770"
+KEYWORD "SPEC_775"
+KEYWORD "SPEC_780"
+KEYWORD "SPEC_785"
+KEYWORD "SPEC_790"
+KEYWORD "SPEC_795"
+KEYWORD "SPEC_800"
+KEYWORD "SPEC_805"
+KEYWORD "SPEC_810"
+KEYWORD "SPEC_815"
+KEYWORD "SPEC_820"
+KEYWORD "SPEC_825"
+KEYWORD "SPEC_830"
+NUMBER_OF_FIELDS 107
+BEGIN_DATA_FORMAT
+SPEC_300 SPEC_305 SPEC_310 SPEC_315 SPEC_320 SPEC_325 SPEC_330 SPEC_335 SPEC_340 SPEC_345 SPEC_350 SPEC_355 SPEC_360 SPEC_365 SPEC_370 SPEC_375 SPEC_380 SPEC_385 SPEC_390 SPEC_395 SPEC_400 SPEC_405 SPEC_410 SPEC_415 SPEC_420 SPEC_425 SPEC_430 SPEC_435 SPEC_440 SPEC_445 SPEC_450 SPEC_455 SPEC_460 SPEC_465 SPEC_470 SPEC_475 SPEC_480 SPEC_485 SPEC_490 SPEC_495 SPEC_500 SPEC_505 SPEC_510 SPEC_515 SPEC_520 SPEC_525 SPEC_530 SPEC_535 SPEC_540 SPEC_545 SPEC_550 SPEC_555 SPEC_560 SPEC_565 SPEC_570 SPEC_575 SPEC_580 SPEC_585 SPEC_590 SPEC_595 SPEC_600 SPEC_605 SPEC_610 SPEC_615 SPEC_620 SPEC_625 SPEC_630 SPEC_635 SPEC_640 SPEC_645 SPEC_650 SPEC_655 SPEC_660 SPEC_665 SPEC_670 SPEC_675 SPEC_680 SPEC_685 SPEC_690 SPEC_695 SPEC_700 SPEC_705 SPEC_710 SPEC_715 SPEC_720 SPEC_725 SPEC_730 SPEC_735 SPEC_740 SPEC_745 SPEC_750 SPEC_755 SPEC_760 SPEC_765 SPEC_770 SPEC_775 SPEC_780 SPEC_785 SPEC_790 SPEC_795 SPEC_800 SPEC_805 SPEC_810 SPEC_815 SPEC_820 SPEC_825 SPEC_830
+END_DATA_FORMAT
+
+NUMBER_OF_SETS 1
+BEGIN_DATA
+0.03 1.75 3.49 8.35 13.23 19.14 25.08 27.8 30.52 33.12 35.72 38.22 40.7 43.27 45.83 43.72 39.92 42.4 42.42 50.68 56.21 56.61 56.51 58.27 60.03 58.93 57.82 66.32 74.82 81.04 87.25 88.93 90.61 90.99 91.37 93.24 95.11 93.54 91.96 93.84 95.72 96.17 96.61 96.87 97.13 99.61 102.1 101.43 100.75 101.54 102.32 101.16 100.0 98.87 97.74 98.33 98.92 96.21 93.5 95.59 97.69 98.48 99.27 99.16 99.04 97.38 95.72 97.29 98.86 97.26 95.67 96.93 98.19 100.6 103.0 101.07 99.13 93.26 87.38 89.49 91.6 92.25 92.89 84.87 76.85 81.68 86.51 89.55 92.58 85.4 78.23 67.96 57.69 70.31 82.92 80.6 78.27 78.91 79.55 76.48 73.4 68.66 63.92 67.35 70.78 72.61 74.44
+END_DATA
diff --git a/profile/D50_2.0.sp b/profile/D50_2.0.sp
new file mode 100644
index 0000000..efa0214
--- /dev/null
+++ b/profile/D50_2.0.sp
@@ -0,0 +1,132 @@
+SPECT
+
+DESCRIPTOR "Argyll D50 illimunant spectral power, 200% U.V."
+
+ORIGINATOR "Argyll CMS"
+
+CREATED "Fri Jul 06 17:49:57 2001"
+KEYWORD "SPECTRAL_BANDS"
+SPECTRAL_BANDS "107"
+KEYWORD "SPECTRAL_START_NM"
+SPECTRAL_START_NM "300.000000"
+KEYWORD "SPECTRAL_END_NM"
+SPECTRAL_END_NM "830.000000"
+KEYWORD "SPECTRAL_NORM"
+SPECTRAL_NORM "100.000000"
+
+KEYWORD "SPEC_300"
+KEYWORD "SPEC_305"
+KEYWORD "SPEC_310"
+KEYWORD "SPEC_315"
+KEYWORD "SPEC_320"
+KEYWORD "SPEC_325"
+KEYWORD "SPEC_330"
+KEYWORD "SPEC_335"
+KEYWORD "SPEC_340"
+KEYWORD "SPEC_345"
+KEYWORD "SPEC_350"
+KEYWORD "SPEC_355"
+KEYWORD "SPEC_360"
+KEYWORD "SPEC_365"
+KEYWORD "SPEC_370"
+KEYWORD "SPEC_375"
+KEYWORD "SPEC_380"
+KEYWORD "SPEC_385"
+KEYWORD "SPEC_390"
+KEYWORD "SPEC_395"
+KEYWORD "SPEC_400"
+KEYWORD "SPEC_405"
+KEYWORD "SPEC_410"
+KEYWORD "SPEC_415"
+KEYWORD "SPEC_420"
+KEYWORD "SPEC_425"
+KEYWORD "SPEC_430"
+KEYWORD "SPEC_435"
+KEYWORD "SPEC_440"
+KEYWORD "SPEC_445"
+KEYWORD "SPEC_450"
+KEYWORD "SPEC_455"
+KEYWORD "SPEC_460"
+KEYWORD "SPEC_465"
+KEYWORD "SPEC_470"
+KEYWORD "SPEC_475"
+KEYWORD "SPEC_480"
+KEYWORD "SPEC_485"
+KEYWORD "SPEC_490"
+KEYWORD "SPEC_495"
+KEYWORD "SPEC_500"
+KEYWORD "SPEC_505"
+KEYWORD "SPEC_510"
+KEYWORD "SPEC_515"
+KEYWORD "SPEC_520"
+KEYWORD "SPEC_525"
+KEYWORD "SPEC_530"
+KEYWORD "SPEC_535"
+KEYWORD "SPEC_540"
+KEYWORD "SPEC_545"
+KEYWORD "SPEC_550"
+KEYWORD "SPEC_555"
+KEYWORD "SPEC_560"
+KEYWORD "SPEC_565"
+KEYWORD "SPEC_570"
+KEYWORD "SPEC_575"
+KEYWORD "SPEC_580"
+KEYWORD "SPEC_585"
+KEYWORD "SPEC_590"
+KEYWORD "SPEC_595"
+KEYWORD "SPEC_600"
+KEYWORD "SPEC_605"
+KEYWORD "SPEC_610"
+KEYWORD "SPEC_615"
+KEYWORD "SPEC_620"
+KEYWORD "SPEC_625"
+KEYWORD "SPEC_630"
+KEYWORD "SPEC_635"
+KEYWORD "SPEC_640"
+KEYWORD "SPEC_645"
+KEYWORD "SPEC_650"
+KEYWORD "SPEC_655"
+KEYWORD "SPEC_660"
+KEYWORD "SPEC_665"
+KEYWORD "SPEC_670"
+KEYWORD "SPEC_675"
+KEYWORD "SPEC_680"
+KEYWORD "SPEC_685"
+KEYWORD "SPEC_690"
+KEYWORD "SPEC_695"
+KEYWORD "SPEC_700"
+KEYWORD "SPEC_705"
+KEYWORD "SPEC_710"
+KEYWORD "SPEC_715"
+KEYWORD "SPEC_720"
+KEYWORD "SPEC_725"
+KEYWORD "SPEC_730"
+KEYWORD "SPEC_735"
+KEYWORD "SPEC_740"
+KEYWORD "SPEC_745"
+KEYWORD "SPEC_750"
+KEYWORD "SPEC_755"
+KEYWORD "SPEC_760"
+KEYWORD "SPEC_765"
+KEYWORD "SPEC_770"
+KEYWORD "SPEC_775"
+KEYWORD "SPEC_780"
+KEYWORD "SPEC_785"
+KEYWORD "SPEC_790"
+KEYWORD "SPEC_795"
+KEYWORD "SPEC_800"
+KEYWORD "SPEC_805"
+KEYWORD "SPEC_810"
+KEYWORD "SPEC_815"
+KEYWORD "SPEC_820"
+KEYWORD "SPEC_825"
+KEYWORD "SPEC_830"
+NUMBER_OF_FIELDS 107
+BEGIN_DATA_FORMAT
+SPEC_300 SPEC_305 SPEC_310 SPEC_315 SPEC_320 SPEC_325 SPEC_330 SPEC_335 SPEC_340 SPEC_345 SPEC_350 SPEC_355 SPEC_360 SPEC_365 SPEC_370 SPEC_375 SPEC_380 SPEC_385 SPEC_390 SPEC_395 SPEC_400 SPEC_405 SPEC_410 SPEC_415 SPEC_420 SPEC_425 SPEC_430 SPEC_435 SPEC_440 SPEC_445 SPEC_450 SPEC_455 SPEC_460 SPEC_465 SPEC_470 SPEC_475 SPEC_480 SPEC_485 SPEC_490 SPEC_495 SPEC_500 SPEC_505 SPEC_510 SPEC_515 SPEC_520 SPEC_525 SPEC_530 SPEC_535 SPEC_540 SPEC_545 SPEC_550 SPEC_555 SPEC_560 SPEC_565 SPEC_570 SPEC_575 SPEC_580 SPEC_585 SPEC_590 SPEC_595 SPEC_600 SPEC_605 SPEC_610 SPEC_615 SPEC_620 SPEC_625 SPEC_630 SPEC_635 SPEC_640 SPEC_645 SPEC_650 SPEC_655 SPEC_660 SPEC_665 SPEC_670 SPEC_675 SPEC_680 SPEC_685 SPEC_690 SPEC_695 SPEC_700 SPEC_705 SPEC_710 SPEC_715 SPEC_720 SPEC_725 SPEC_730 SPEC_735 SPEC_740 SPEC_745 SPEC_750 SPEC_755 SPEC_760 SPEC_765 SPEC_770 SPEC_775 SPEC_780 SPEC_785 SPEC_790 SPEC_795 SPEC_800 SPEC_805 SPEC_810 SPEC_815 SPEC_820 SPEC_825 SPEC_830
+END_DATA_FORMAT
+
+NUMBER_OF_SETS 1
+BEGIN_DATA
+0.04 2.06 4.1 9.82 15.56 22.52 29.5 32.7 35.9 38.96 42.02 44.96 47.88 50.9 53.92 51.44 46.53 48.92 47.79 55.43 59.17 58.2 56.51 58.27 60.03 58.93 57.82 66.32 74.82 81.04 87.25 88.93 90.61 90.99 91.37 93.24 95.11 93.54 91.96 93.84 95.72 96.17 96.61 96.87 97.13 99.61 102.1 101.43 100.75 101.54 102.32 101.16 100.0 98.87 97.74 98.33 98.92 96.21 93.5 95.59 97.69 98.48 99.27 99.16 99.04 97.38 95.72 97.29 98.86 97.26 95.67 96.93 98.19 100.6 103.0 101.07 99.13 93.26 87.38 89.49 91.6 92.25 92.89 84.87 76.85 81.68 86.51 89.55 92.58 85.4 78.23 67.96 57.69 70.31 82.92 80.6 78.27 78.91 79.55 76.48 73.4 68.66 63.92 67.35 70.78 72.61 74.44
+END_DATA
diff --git a/profile/D50_2.5.sp b/profile/D50_2.5.sp
new file mode 100644
index 0000000..8057f14
--- /dev/null
+++ b/profile/D50_2.5.sp
@@ -0,0 +1,132 @@
+SPECT
+
+DESCRIPTOR "Argyll D50 illimunant spectral power, 250% U.V."
+
+ORIGINATOR "Argyll CMS"
+
+CREATED "Fri Jul 06 17:49:57 2001"
+KEYWORD "SPECTRAL_BANDS"
+SPECTRAL_BANDS "107"
+KEYWORD "SPECTRAL_START_NM"
+SPECTRAL_START_NM "300.000000"
+KEYWORD "SPECTRAL_END_NM"
+SPECTRAL_END_NM "830.000000"
+KEYWORD "SPECTRAL_NORM"
+SPECTRAL_NORM "100.000000"
+
+KEYWORD "SPEC_300"
+KEYWORD "SPEC_305"
+KEYWORD "SPEC_310"
+KEYWORD "SPEC_315"
+KEYWORD "SPEC_320"
+KEYWORD "SPEC_325"
+KEYWORD "SPEC_330"
+KEYWORD "SPEC_335"
+KEYWORD "SPEC_340"
+KEYWORD "SPEC_345"
+KEYWORD "SPEC_350"
+KEYWORD "SPEC_355"
+KEYWORD "SPEC_360"
+KEYWORD "SPEC_365"
+KEYWORD "SPEC_370"
+KEYWORD "SPEC_375"
+KEYWORD "SPEC_380"
+KEYWORD "SPEC_385"
+KEYWORD "SPEC_390"
+KEYWORD "SPEC_395"
+KEYWORD "SPEC_400"
+KEYWORD "SPEC_405"
+KEYWORD "SPEC_410"
+KEYWORD "SPEC_415"
+KEYWORD "SPEC_420"
+KEYWORD "SPEC_425"
+KEYWORD "SPEC_430"
+KEYWORD "SPEC_435"
+KEYWORD "SPEC_440"
+KEYWORD "SPEC_445"
+KEYWORD "SPEC_450"
+KEYWORD "SPEC_455"
+KEYWORD "SPEC_460"
+KEYWORD "SPEC_465"
+KEYWORD "SPEC_470"
+KEYWORD "SPEC_475"
+KEYWORD "SPEC_480"
+KEYWORD "SPEC_485"
+KEYWORD "SPEC_490"
+KEYWORD "SPEC_495"
+KEYWORD "SPEC_500"
+KEYWORD "SPEC_505"
+KEYWORD "SPEC_510"
+KEYWORD "SPEC_515"
+KEYWORD "SPEC_520"
+KEYWORD "SPEC_525"
+KEYWORD "SPEC_530"
+KEYWORD "SPEC_535"
+KEYWORD "SPEC_540"
+KEYWORD "SPEC_545"
+KEYWORD "SPEC_550"
+KEYWORD "SPEC_555"
+KEYWORD "SPEC_560"
+KEYWORD "SPEC_565"
+KEYWORD "SPEC_570"
+KEYWORD "SPEC_575"
+KEYWORD "SPEC_580"
+KEYWORD "SPEC_585"
+KEYWORD "SPEC_590"
+KEYWORD "SPEC_595"
+KEYWORD "SPEC_600"
+KEYWORD "SPEC_605"
+KEYWORD "SPEC_610"
+KEYWORD "SPEC_615"
+KEYWORD "SPEC_620"
+KEYWORD "SPEC_625"
+KEYWORD "SPEC_630"
+KEYWORD "SPEC_635"
+KEYWORD "SPEC_640"
+KEYWORD "SPEC_645"
+KEYWORD "SPEC_650"
+KEYWORD "SPEC_655"
+KEYWORD "SPEC_660"
+KEYWORD "SPEC_665"
+KEYWORD "SPEC_670"
+KEYWORD "SPEC_675"
+KEYWORD "SPEC_680"
+KEYWORD "SPEC_685"
+KEYWORD "SPEC_690"
+KEYWORD "SPEC_695"
+KEYWORD "SPEC_700"
+KEYWORD "SPEC_705"
+KEYWORD "SPEC_710"
+KEYWORD "SPEC_715"
+KEYWORD "SPEC_720"
+KEYWORD "SPEC_725"
+KEYWORD "SPEC_730"
+KEYWORD "SPEC_735"
+KEYWORD "SPEC_740"
+KEYWORD "SPEC_745"
+KEYWORD "SPEC_750"
+KEYWORD "SPEC_755"
+KEYWORD "SPEC_760"
+KEYWORD "SPEC_765"
+KEYWORD "SPEC_770"
+KEYWORD "SPEC_775"
+KEYWORD "SPEC_780"
+KEYWORD "SPEC_785"
+KEYWORD "SPEC_790"
+KEYWORD "SPEC_795"
+KEYWORD "SPEC_800"
+KEYWORD "SPEC_805"
+KEYWORD "SPEC_810"
+KEYWORD "SPEC_815"
+KEYWORD "SPEC_820"
+KEYWORD "SPEC_825"
+KEYWORD "SPEC_830"
+NUMBER_OF_FIELDS 107
+BEGIN_DATA_FORMAT
+SPEC_300 SPEC_305 SPEC_310 SPEC_315 SPEC_320 SPEC_325 SPEC_330 SPEC_335 SPEC_340 SPEC_345 SPEC_350 SPEC_355 SPEC_360 SPEC_365 SPEC_370 SPEC_375 SPEC_380 SPEC_385 SPEC_390 SPEC_395 SPEC_400 SPEC_405 SPEC_410 SPEC_415 SPEC_420 SPEC_425 SPEC_430 SPEC_435 SPEC_440 SPEC_445 SPEC_450 SPEC_455 SPEC_460 SPEC_465 SPEC_470 SPEC_475 SPEC_480 SPEC_485 SPEC_490 SPEC_495 SPEC_500 SPEC_505 SPEC_510 SPEC_515 SPEC_520 SPEC_525 SPEC_530 SPEC_535 SPEC_540 SPEC_545 SPEC_550 SPEC_555 SPEC_560 SPEC_565 SPEC_570 SPEC_575 SPEC_580 SPEC_585 SPEC_590 SPEC_595 SPEC_600 SPEC_605 SPEC_610 SPEC_615 SPEC_620 SPEC_625 SPEC_630 SPEC_635 SPEC_640 SPEC_645 SPEC_650 SPEC_655 SPEC_660 SPEC_665 SPEC_670 SPEC_675 SPEC_680 SPEC_685 SPEC_690 SPEC_695 SPEC_700 SPEC_705 SPEC_710 SPEC_715 SPEC_720 SPEC_725 SPEC_730 SPEC_735 SPEC_740 SPEC_745 SPEC_750 SPEC_755 SPEC_760 SPEC_765 SPEC_770 SPEC_775 SPEC_780 SPEC_785 SPEC_790 SPEC_795 SPEC_800 SPEC_805 SPEC_810 SPEC_815 SPEC_820 SPEC_825 SPEC_830
+END_DATA_FORMAT
+
+NUMBER_OF_SETS 1
+BEGIN_DATA
+0.05 2.58 5.13 12.28 19.45 28.15 36.88 40.88 44.88 48.7 52.53 56.2 59.85 63.63 67.4 64.3 57.55 59.8 56.75 63.34 64.1 60.85 56.51 58.27 60.03 58.93 57.82 66.32 74.82 81.04 87.25 88.93 90.61 90.99 91.37 93.24 95.11 93.54 91.96 93.84 95.72 96.17 96.61 96.87 97.13 99.61 102.1 101.43 100.75 101.54 102.32 101.16 100.0 98.87 97.74 98.33 98.92 96.21 93.5 95.59 97.69 98.48 99.27 99.16 99.04 97.38 95.72 97.29 98.86 97.26 95.67 96.93 98.19 100.6 103.0 101.07 99.13 93.26 87.38 89.49 91.6 92.25 92.89 84.87 76.85 81.68 86.51 89.55 92.58 85.4 78.23 67.96 57.69 70.31 82.92 80.6 78.27 78.91 79.55 76.48 73.4 68.66 63.92 67.35 70.78 72.61 74.44
+END_DATA
diff --git a/profile/D50_3.0.sp b/profile/D50_3.0.sp
new file mode 100644
index 0000000..e68d891
--- /dev/null
+++ b/profile/D50_3.0.sp
@@ -0,0 +1,132 @@
+SPECT
+
+DESCRIPTOR "Argyll D50 illimunant spectral power, 300% U.V."
+
+ORIGINATOR "Argyll CMS"
+
+CREATED "Fri Jul 06 17:49:57 2001"
+KEYWORD "SPECTRAL_BANDS"
+SPECTRAL_BANDS "107"
+KEYWORD "SPECTRAL_START_NM"
+SPECTRAL_START_NM "300.000000"
+KEYWORD "SPECTRAL_END_NM"
+SPECTRAL_END_NM "830.000000"
+KEYWORD "SPECTRAL_NORM"
+SPECTRAL_NORM "100.000000"
+
+KEYWORD "SPEC_300"
+KEYWORD "SPEC_305"
+KEYWORD "SPEC_310"
+KEYWORD "SPEC_315"
+KEYWORD "SPEC_320"
+KEYWORD "SPEC_325"
+KEYWORD "SPEC_330"
+KEYWORD "SPEC_335"
+KEYWORD "SPEC_340"
+KEYWORD "SPEC_345"
+KEYWORD "SPEC_350"
+KEYWORD "SPEC_355"
+KEYWORD "SPEC_360"
+KEYWORD "SPEC_365"
+KEYWORD "SPEC_370"
+KEYWORD "SPEC_375"
+KEYWORD "SPEC_380"
+KEYWORD "SPEC_385"
+KEYWORD "SPEC_390"
+KEYWORD "SPEC_395"
+KEYWORD "SPEC_400"
+KEYWORD "SPEC_405"
+KEYWORD "SPEC_410"
+KEYWORD "SPEC_415"
+KEYWORD "SPEC_420"
+KEYWORD "SPEC_425"
+KEYWORD "SPEC_430"
+KEYWORD "SPEC_435"
+KEYWORD "SPEC_440"
+KEYWORD "SPEC_445"
+KEYWORD "SPEC_450"
+KEYWORD "SPEC_455"
+KEYWORD "SPEC_460"
+KEYWORD "SPEC_465"
+KEYWORD "SPEC_470"
+KEYWORD "SPEC_475"
+KEYWORD "SPEC_480"
+KEYWORD "SPEC_485"
+KEYWORD "SPEC_490"
+KEYWORD "SPEC_495"
+KEYWORD "SPEC_500"
+KEYWORD "SPEC_505"
+KEYWORD "SPEC_510"
+KEYWORD "SPEC_515"
+KEYWORD "SPEC_520"
+KEYWORD "SPEC_525"
+KEYWORD "SPEC_530"
+KEYWORD "SPEC_535"
+KEYWORD "SPEC_540"
+KEYWORD "SPEC_545"
+KEYWORD "SPEC_550"
+KEYWORD "SPEC_555"
+KEYWORD "SPEC_560"
+KEYWORD "SPEC_565"
+KEYWORD "SPEC_570"
+KEYWORD "SPEC_575"
+KEYWORD "SPEC_580"
+KEYWORD "SPEC_585"
+KEYWORD "SPEC_590"
+KEYWORD "SPEC_595"
+KEYWORD "SPEC_600"
+KEYWORD "SPEC_605"
+KEYWORD "SPEC_610"
+KEYWORD "SPEC_615"
+KEYWORD "SPEC_620"
+KEYWORD "SPEC_625"
+KEYWORD "SPEC_630"
+KEYWORD "SPEC_635"
+KEYWORD "SPEC_640"
+KEYWORD "SPEC_645"
+KEYWORD "SPEC_650"
+KEYWORD "SPEC_655"
+KEYWORD "SPEC_660"
+KEYWORD "SPEC_665"
+KEYWORD "SPEC_670"
+KEYWORD "SPEC_675"
+KEYWORD "SPEC_680"
+KEYWORD "SPEC_685"
+KEYWORD "SPEC_690"
+KEYWORD "SPEC_695"
+KEYWORD "SPEC_700"
+KEYWORD "SPEC_705"
+KEYWORD "SPEC_710"
+KEYWORD "SPEC_715"
+KEYWORD "SPEC_720"
+KEYWORD "SPEC_725"
+KEYWORD "SPEC_730"
+KEYWORD "SPEC_735"
+KEYWORD "SPEC_740"
+KEYWORD "SPEC_745"
+KEYWORD "SPEC_750"
+KEYWORD "SPEC_755"
+KEYWORD "SPEC_760"
+KEYWORD "SPEC_765"
+KEYWORD "SPEC_770"
+KEYWORD "SPEC_775"
+KEYWORD "SPEC_780"
+KEYWORD "SPEC_785"
+KEYWORD "SPEC_790"
+KEYWORD "SPEC_795"
+KEYWORD "SPEC_800"
+KEYWORD "SPEC_805"
+KEYWORD "SPEC_810"
+KEYWORD "SPEC_815"
+KEYWORD "SPEC_820"
+KEYWORD "SPEC_825"
+KEYWORD "SPEC_830"
+NUMBER_OF_FIELDS 107
+BEGIN_DATA_FORMAT
+SPEC_300 SPEC_305 SPEC_310 SPEC_315 SPEC_320 SPEC_325 SPEC_330 SPEC_335 SPEC_340 SPEC_345 SPEC_350 SPEC_355 SPEC_360 SPEC_365 SPEC_370 SPEC_375 SPEC_380 SPEC_385 SPEC_390 SPEC_395 SPEC_400 SPEC_405 SPEC_410 SPEC_415 SPEC_420 SPEC_425 SPEC_430 SPEC_435 SPEC_440 SPEC_445 SPEC_450 SPEC_455 SPEC_460 SPEC_465 SPEC_470 SPEC_475 SPEC_480 SPEC_485 SPEC_490 SPEC_495 SPEC_500 SPEC_505 SPEC_510 SPEC_515 SPEC_520 SPEC_525 SPEC_530 SPEC_535 SPEC_540 SPEC_545 SPEC_550 SPEC_555 SPEC_560 SPEC_565 SPEC_570 SPEC_575 SPEC_580 SPEC_585 SPEC_590 SPEC_595 SPEC_600 SPEC_605 SPEC_610 SPEC_615 SPEC_620 SPEC_625 SPEC_630 SPEC_635 SPEC_640 SPEC_645 SPEC_650 SPEC_655 SPEC_660 SPEC_665 SPEC_670 SPEC_675 SPEC_680 SPEC_685 SPEC_690 SPEC_695 SPEC_700 SPEC_705 SPEC_710 SPEC_715 SPEC_720 SPEC_725 SPEC_730 SPEC_735 SPEC_740 SPEC_745 SPEC_750 SPEC_755 SPEC_760 SPEC_765 SPEC_770 SPEC_775 SPEC_780 SPEC_785 SPEC_790 SPEC_795 SPEC_800 SPEC_805 SPEC_810 SPEC_815 SPEC_820 SPEC_825 SPEC_830
+END_DATA_FORMAT
+
+NUMBER_OF_SETS 1
+BEGIN_DATA
+0.06 3.09 6.15 14.73 23.34 33.78 44.25 49.05 53.85 58.44 63.03 67.44 71.82 76.35 80.88 77.16 68.57 70.67 65.71 71.26 69.03 63.49 56.51 58.27 60.03 58.93 57.82 66.32 74.82 81.04 87.25 88.93 90.61 90.99 91.37 93.24 95.11 93.54 91.96 93.84 95.72 96.17 96.61 96.87 97.13 99.61 102.1 101.43 100.75 101.54 102.32 101.16 100.0 98.87 97.74 98.33 98.92 96.21 93.5 95.59 97.69 98.48 99.27 99.16 99.04 97.38 95.72 97.29 98.86 97.26 95.67 96.93 98.19 100.6 103.0 101.07 99.13 93.26 87.38 89.49 91.6 92.25 92.89 84.87 76.85 81.68 86.51 89.55 92.58 85.4 78.23 67.96 57.69 70.31 82.92 80.6 78.27 78.91 79.55 76.48 73.4 68.66 63.92 67.35 70.78 72.61 74.44
+END_DATA
diff --git a/profile/GTIPlus.sp b/profile/GTIPlus.sp
new file mode 100644
index 0000000..4c4c164
--- /dev/null
+++ b/profile/GTIPlus.sp
@@ -0,0 +1,64 @@
+SPECT
+
+DESCRIPTOR "Argyll Spectral Power information for GTI D50 viewer - extended to 350nm with high estimation of UV"
+ORIGINATOR "Argyll CMS"
+CREATED "Thu May 20 15:12:32 2004"
+KEYWORD "SPECTRAL_BANDS"
+SPECTRAL_BANDS "80"
+KEYWORD "SPECTRAL_START_NM"
+SPECTRAL_START_NM "340.000000"
+KEYWORD "SPECTRAL_END_NM"
+SPECTRAL_END_NM "750.000000"
+KEYWORD "SPECTRAL_NORM"
+SPECTRAL_NORM "50.000"
+
+KEYWORD "SPEC_340"
+KEYWORD "SPEC_350"
+KEYWORD "SPEC_360"
+KEYWORD "SPEC_370"
+KEYWORD "SPEC_380"
+KEYWORD "SPEC_390"
+KEYWORD "SPEC_400"
+KEYWORD "SPEC_410"
+KEYWORD "SPEC_420"
+KEYWORD "SPEC_430"
+KEYWORD "SPEC_440"
+KEYWORD "SPEC_450"
+KEYWORD "SPEC_460"
+KEYWORD "SPEC_470"
+KEYWORD "SPEC_480"
+KEYWORD "SPEC_490"
+KEYWORD "SPEC_500"
+KEYWORD "SPEC_510"
+KEYWORD "SPEC_520"
+KEYWORD "SPEC_530"
+KEYWORD "SPEC_540"
+KEYWORD "SPEC_550"
+KEYWORD "SPEC_560"
+KEYWORD "SPEC_570"
+KEYWORD "SPEC_580"
+KEYWORD "SPEC_590"
+KEYWORD "SPEC_600"
+KEYWORD "SPEC_610"
+KEYWORD "SPEC_620"
+KEYWORD "SPEC_630"
+KEYWORD "SPEC_640"
+KEYWORD "SPEC_650"
+KEYWORD "SPEC_660"
+KEYWORD "SPEC_670"
+KEYWORD "SPEC_680"
+KEYWORD "SPEC_690"
+KEYWORD "SPEC_700"
+KEYWORD "SPEC_710"
+KEYWORD "SPEC_720"
+KEYWORD "SPEC_730"
+NUMBER_OF_FIELDS 40
+BEGIN_DATA_FORMAT
+SPEC_340 SPEC_350 SPEC_360 SPEC_370 SPEC_380 SPEC_390 SPEC_400 SPEC_410 SPEC_420 SPEC_430 SPEC_440 SPEC_450 SPEC_460 SPEC_470 SPEC_480 SPEC_490 SPEC_500 SPEC_510 SPEC_520 SPEC_530 SPEC_540 SPEC_550 SPEC_560 SPEC_570 SPEC_580 SPEC_590 SPEC_600 SPEC_610 SPEC_620 SPEC_630 SPEC_640 SPEC_650 SPEC_660 SPEC_670 SPEC_680 SPEC_690 SPEC_700 SPEC_710 SPEC_720 SPEC_730
+END_DATA_FORMAT
+
+NUMBER_OF_SETS 1
+BEGIN_DATA
+5.0 7.0 7.0 6.0 3.257189 6.165859 19.269892 21.802994 16.533678 39.975159 57.056049 30.274649 30.155033 32.977394 35.03986 36.617123 37.617252 38.191128 38.476234 39.155827 51.626312 62.044827 42.804649 43.08567 48.201847 44.268539 43.22028 43.416119 42.784763 41.291107 39.292709 36.579609 33.26786 29.675453 26.125034 22.980124 19.86338 16.922205 13.994864 11.727232
+END_DATA
+
diff --git a/profile/Jamfile b/profile/Jamfile
new file mode 100644
index 0000000..780f9a5
--- /dev/null
+++ b/profile/Jamfile
@@ -0,0 +1,104 @@
+
+#PREF_CCFLAGS += $(CCOPTFLAG) ; # Turn optimisation on
+PREF_CCFLAGS += $(CCDEBUGFLAG) ; # Debugging flags
+#PREF_CCFLAGS += $(CCHEAPDEBUG) ; # Heap Debugging flags
+PREF_LINKFLAGS += $(LINKDEBUGFLAG) ;
+
+#Products
+Libraries = libprof ;
+Executables = cb2ti3 kodak2ti3 txt2ti3 splitti3 mppcheck mppprof
+ profcheck invprofcheck verify colprof printcal applycal ;
+Headers = prof.h ;
+Samples = example.sp example121.sp 3dap5k.sp GTIPlus.sp Office.sp Trulux.sp TruluxPlus.sp
+ D50_0.0.sp D50_0.1.sp D50_0.3.sp D50_0.5.sp D50_0.7.sp D50_1.0.sp D50_1.2.sp
+ D50_1.5.sp D50_1.7.sp D50_2.0.sp D50_2.5.sp D50_3.0.sp CIE_C.sp ;
+
+#Install
+InstallBin $(DESTDIR)$(PREFIX)/bin : $(Executables) ;
+InstallFile $(DESTDIR)$(PREFIX)/$(REFSUBDIR) : $(Samples) ;
+#InstallFile $(DESTDIR)$(PREFIX)/h : $(Headers) ;
+#InstallLib $(DESTDIR)$(PREFIX)/lib : $(Libraries) ;
+
+HDRS = ../h ../icc ../cgats ../rspl
+ ../numlib ../gamut ../xicc ../spectro
+ ../target ../plot ;
+
+# PROF library
+Library libprof : profin.c profout.c ;
+
+
+LINKLIBS = ../rspl/librspl ../icc/libicc ../cgats/libcgats ../numlib/libnum ../plot/libplot
+ ../plot/libvrml ;
+
+# Simple profile generator
+Main simpprof : simpprof.c ;
+
+#Kodak raw profile data to Argyll CGATS format converter
+Main kodak2ti3 : kodak2ti3.c ;
+
+#Colorblind raw profile data to Argyll CGATS format converter
+Main cb2ti3 : cb2ti3.c ;
+
+# the gcc linker is retarded, and can't link to things it's gone past, hence 2 x libxicc...
+LINKLIBS = ../xicc/libxicc ../spectro/libinsttypes ../xicc/libxicc ../gamut/libgamut $(LINKLIBS) ;
+
+#Gretag/Logo raw CMYK profile data to Argyll CGATS format converter
+Main txt2ti3 : txt2ti3.c ;
+
+#Split a .ti3 into two pieces randomly
+Main splitti3 : splitti3.c ;
+
+# Profile checker
+Main profcheck : profcheck.c ;
+
+# Reverse Profile checker
+Main invprofcheck : invprofcheck.c ;
+
+# Model Printer Profile generator
+Main mppprof : mppprof.c ;
+
+# Model Printer Profile checker
+Main mppcheck : mppcheck.c ;
+
+LINKLIBS = ../plot/libvrml $(LINKLIBS) ;
+
+# Verifyer
+Main verify : verify.c ;
+
+LINKLIBS = libprof ../gamut/libgammap $(LINKLIBS) $(TIFFLIB) $(JPEGLIB) ;
+
+# Full profile generator
+Main colprof : colprof.c : : : $(TIFFINC) $(JPEGINC) ;
+
+# Print calibration
+Main printcal : printcal.c : : : $(TIFFINC) $(JPEGINC) ;
+
+# Applying calibration
+Main applycal : applycal.c ;
+
+# Optimised Separation Generator
+#Main sepgen : sepgen.c ;
+
+# Test code
+if $(HOME) = "d:\\usr\\graeme" && $(PWD) = "/src/argyll/profile" {
+ Main specinpprof : specinpprof.c ;
+}
+
+# Development code
+if [ GLOB . : retargti3.c ] {
+ Main retargti3 : retargti3.c ;
+}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/profile/License.txt b/profile/License.txt
new file mode 100644
index 0000000..a871fcf
--- /dev/null
+++ b/profile/License.txt
@@ -0,0 +1,662 @@
+ GNU AFFERO GENERAL PUBLIC LICENSE
+ Version 3, 19 November 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The GNU Affero General Public License is a free, copyleft license for
+software and other kinds of works, specifically designed to ensure
+cooperation with the community in the case of network server software.
+
+ The licenses for most software and other practical works are designed
+to take away your freedom to share and change the works. By contrast,
+our General Public Licenses are intended to guarantee your freedom to
+share and change all versions of a program--to make sure it remains free
+software for all its users.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+them if you wish), that you receive source code or can get it if you
+want it, that you can change the software or use pieces of it in new
+free programs, and that you know you can do these things.
+
+ Developers that use our General Public Licenses protect your rights
+with two steps: (1) assert copyright on the software, and (2) offer
+you this License which gives you legal permission to copy, distribute
+and/or modify the software.
+
+ A secondary benefit of defending all users' freedom is that
+improvements made in alternate versions of the program, if they
+receive widespread use, become available for other developers to
+incorporate. Many developers of free software are heartened and
+encouraged by the resulting cooperation. However, in the case of
+software used on network servers, this result may fail to come about.
+The GNU General Public License permits making a modified version and
+letting the public access it on a server without ever releasing its
+source code to the public.
+
+ The GNU Affero General Public License is designed specifically to
+ensure that, in such cases, the modified source code becomes available
+to the community. It requires the operator of a network server to
+provide the source code of the modified version running there to the
+users of that server. Therefore, public use of a modified version, on
+a publicly accessible server, gives the public access to the source
+code of the modified version.
+
+ An older license, called the Affero General Public License and
+published by Affero, was designed to accomplish similar goals. This is
+a different license, not a version of the Affero GPL, but Affero has
+released a new version of the Affero GPL which permits relicensing under
+this license.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ TERMS AND CONDITIONS
+
+ 0. Definitions.
+
+ "This License" refers to version 3 of the GNU Affero General Public License.
+
+ "Copyright" also means copyright-like laws that apply to other kinds of
+works, such as semiconductor masks.
+
+ "The Program" refers to any copyrightable work licensed under this
+License. Each licensee is addressed as "you". "Licensees" and
+"recipients" may be individuals or organizations.
+
+ To "modify" a work means to copy from or adapt all or part of the work
+in a fashion requiring copyright permission, other than the making of an
+exact copy. The resulting work is called a "modified version" of the
+earlier work or a work "based on" the earlier work.
+
+ A "covered work" means either the unmodified Program or a work based
+on the Program.
+
+ To "propagate" a work means to do anything with it that, without
+permission, would make you directly or secondarily liable for
+infringement under applicable copyright law, except executing it on a
+computer or modifying a private copy. Propagation includes copying,
+distribution (with or without modification), making available to the
+public, and in some countries other activities as well.
+
+ To "convey" a work means any kind of propagation that enables other
+parties to make or receive copies. Mere interaction with a user through
+a computer network, with no transfer of a copy, is not conveying.
+
+ An interactive user interface displays "Appropriate Legal Notices"
+to the extent that it includes a convenient and prominently visible
+feature that (1) displays an appropriate copyright notice, and (2)
+tells the user that there is no warranty for the work (except to the
+extent that warranties are provided), that licensees may convey the
+work under this License, and how to view a copy of this License. If
+the interface presents a list of user commands or options, such as a
+menu, a prominent item in the list meets this criterion.
+
+ 1. Source Code.
+
+ The "source code" for a work means the preferred form of the work
+for making modifications to it. "Object code" means any non-source
+form of a work.
+
+ A "Standard Interface" means an interface that either is an official
+standard defined by a recognized standards body, or, in the case of
+interfaces specified for a particular programming language, one that
+is widely used among developers working in that language.
+
+ The "System Libraries" of an executable work include anything, other
+than the work as a whole, that (a) is included in the normal form of
+packaging a Major Component, but which is not part of that Major
+Component, and (b) serves only to enable use of the work with that
+Major Component, or to implement a Standard Interface for which an
+implementation is available to the public in source code form. A
+"Major Component", in this context, means a major essential component
+(kernel, window system, and so on) of the specific operating system
+(if any) on which the executable work runs, or a compiler used to
+produce the work, or an object code interpreter used to run it.
+
+ The "Corresponding Source" for a work in object code form means all
+the source code needed to generate, install, and (for an executable
+work) run the object code and to modify the work, including scripts to
+control those activities. However, it does not include the work's
+System Libraries, or general-purpose tools or generally available free
+programs which are used unmodified in performing those activities but
+which are not part of the work. For example, Corresponding Source
+includes interface definition files associated with source files for
+the work, and the source code for shared libraries and dynamically
+linked subprograms that the work is specifically designed to require,
+such as by intimate data communication or control flow between those
+subprograms and other parts of the work.
+
+ The Corresponding Source need not include anything that users
+can regenerate automatically from other parts of the Corresponding
+Source.
+
+ The Corresponding Source for a work in source code form is that
+same work.
+
+ 2. Basic Permissions.
+
+ All rights granted under this License are granted for the term of
+copyright on the Program, and are irrevocable provided the stated
+conditions are met. This License explicitly affirms your unlimited
+permission to run the unmodified Program. The output from running a
+covered work is covered by this License only if the output, given its
+content, constitutes a covered work. This License acknowledges your
+rights of fair use or other equivalent, as provided by copyright law.
+
+ You may make, run and propagate covered works that you do not
+convey, without conditions so long as your license otherwise remains
+in force. You may convey covered works to others for the sole purpose
+of having them make modifications exclusively for you, or provide you
+with facilities for running those works, provided that you comply with
+the terms of this License in conveying all material for which you do
+not control copyright. Those thus making or running the covered works
+for you must do so exclusively on your behalf, under your direction
+and control, on terms that prohibit them from making any copies of
+your copyrighted material outside their relationship with you.
+
+ Conveying under any other circumstances is permitted solely under
+the conditions stated below. Sublicensing is not allowed; section 10
+makes it unnecessary.
+
+ 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
+
+ No covered work shall be deemed part of an effective technological
+measure under any applicable law fulfilling obligations under article
+11 of the WIPO copyright treaty adopted on 20 December 1996, or
+similar laws prohibiting or restricting circumvention of such
+measures.
+
+ When you convey a covered work, you waive any legal power to forbid
+circumvention of technological measures to the extent such circumvention
+is effected by exercising rights under this License with respect to
+the covered work, and you disclaim any intention to limit operation or
+modification of the work as a means of enforcing, against the work's
+users, your or third parties' legal rights to forbid circumvention of
+technological measures.
+
+ 4. Conveying Verbatim Copies.
+
+ You may convey verbatim copies of the Program's source code as you
+receive it, in any medium, provided that you conspicuously and
+appropriately publish on each copy an appropriate copyright notice;
+keep intact all notices stating that this License and any
+non-permissive terms added in accord with section 7 apply to the code;
+keep intact all notices of the absence of any warranty; and give all
+recipients a copy of this License along with the Program.
+
+ You may charge any price or no price for each copy that you convey,
+and you may offer support or warranty protection for a fee.
+
+ 5. Conveying Modified Source Versions.
+
+ You may convey a work based on the Program, or the modifications to
+produce it from the Program, in the form of source code under the
+terms of section 4, provided that you also meet all of these conditions:
+
+ a) The work must carry prominent notices stating that you modified
+ it, and giving a relevant date.
+
+ b) The work must carry prominent notices stating that it is
+ released under this License and any conditions added under section
+ 7. This requirement modifies the requirement in section 4 to
+ "keep intact all notices".
+
+ c) You must license the entire work, as a whole, under this
+ License to anyone who comes into possession of a copy. This
+ License will therefore apply, along with any applicable section 7
+ additional terms, to the whole of the work, and all its parts,
+ regardless of how they are packaged. This License gives no
+ permission to license the work in any other way, but it does not
+ invalidate such permission if you have separately received it.
+
+ d) If the work has interactive user interfaces, each must display
+ Appropriate Legal Notices; however, if the Program has interactive
+ interfaces that do not display Appropriate Legal Notices, your
+ work need not make them do so.
+
+ A compilation of a covered work with other separate and independent
+works, which are not by their nature extensions of the covered work,
+and which are not combined with it such as to form a larger program,
+in or on a volume of a storage or distribution medium, is called an
+"aggregate" if the compilation and its resulting copyright are not
+used to limit the access or legal rights of the compilation's users
+beyond what the individual works permit. Inclusion of a covered work
+in an aggregate does not cause this License to apply to the other
+parts of the aggregate.
+
+ 6. Conveying Non-Source Forms.
+
+ You may convey a covered work in object code form under the terms
+of sections 4 and 5, provided that you also convey the
+machine-readable Corresponding Source under the terms of this License,
+in one of these ways:
+
+ a) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by the
+ Corresponding Source fixed on a durable physical medium
+ customarily used for software interchange.
+
+ b) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by a
+ written offer, valid for at least three years and valid for as
+ long as you offer spare parts or customer support for that product
+ model, to give anyone who possesses the object code either (1) a
+ copy of the Corresponding Source for all the software in the
+ product that is covered by this License, on a durable physical
+ medium customarily used for software interchange, for a price no
+ more than your reasonable cost of physically performing this
+ conveying of source, or (2) access to copy the
+ Corresponding Source from a network server at no charge.
+
+ c) Convey individual copies of the object code with a copy of the
+ written offer to provide the Corresponding Source. This
+ alternative is allowed only occasionally and noncommercially, and
+ only if you received the object code with such an offer, in accord
+ with subsection 6b.
+
+ d) Convey the object code by offering access from a designated
+ place (gratis or for a charge), and offer equivalent access to the
+ Corresponding Source in the same way through the same place at no
+ further charge. You need not require recipients to copy the
+ Corresponding Source along with the object code. If the place to
+ copy the object code is a network server, the Corresponding Source
+ may be on a different server (operated by you or a third party)
+ that supports equivalent copying facilities, provided you maintain
+ clear directions next to the object code saying where to find the
+ Corresponding Source. Regardless of what server hosts the
+ Corresponding Source, you remain obligated to ensure that it is
+ available for as long as needed to satisfy these requirements.
+
+ e) Convey the object code using peer-to-peer transmission, provided
+ you inform other peers where the object code and Corresponding
+ Source of the work are being offered to the general public at no
+ charge under subsection 6d.
+
+ A separable portion of the object code, whose source code is excluded
+from the Corresponding Source as a System Library, need not be
+included in conveying the object code work.
+
+ A "User Product" is either (1) a "consumer product", which means any
+tangible personal property which is normally used for personal, family,
+or household purposes, or (2) anything designed or sold for incorporation
+into a dwelling. In determining whether a product is a consumer product,
+doubtful cases shall be resolved in favor of coverage. For a particular
+product received by a particular user, "normally used" refers to a
+typical or common use of that class of product, regardless of the status
+of the particular user or of the way in which the particular user
+actually uses, or expects or is expected to use, the product. A product
+is a consumer product regardless of whether the product has substantial
+commercial, industrial or non-consumer uses, unless such uses represent
+the only significant mode of use of the product.
+
+ "Installation Information" for a User Product means any methods,
+procedures, authorization keys, or other information required to install
+and execute modified versions of a covered work in that User Product from
+a modified version of its Corresponding Source. The information must
+suffice to ensure that the continued functioning of the modified object
+code is in no case prevented or interfered with solely because
+modification has been made.
+
+ If you convey an object code work under this section in, or with, or
+specifically for use in, a User Product, and the conveying occurs as
+part of a transaction in which the right of possession and use of the
+User Product is transferred to the recipient in perpetuity or for a
+fixed term (regardless of how the transaction is characterized), the
+Corresponding Source conveyed under this section must be accompanied
+by the Installation Information. But this requirement does not apply
+if neither you nor any third party retains the ability to install
+modified object code on the User Product (for example, the work has
+been installed in ROM).
+
+ The requirement to provide Installation Information does not include a
+requirement to continue to provide support service, warranty, or updates
+for a work that has been modified or installed by the recipient, or for
+the User Product in which it has been modified or installed. Access to a
+network may be denied when the modification itself materially and
+adversely affects the operation of the network or violates the rules and
+protocols for communication across the network.
+
+ Corresponding Source conveyed, and Installation Information provided,
+in accord with this section must be in a format that is publicly
+documented (and with an implementation available to the public in
+source code form), and must require no special password or key for
+unpacking, reading or copying.
+
+ 7. Additional Terms.
+
+ "Additional permissions" are terms that supplement the terms of this
+License by making exceptions from one or more of its conditions.
+Additional permissions that are applicable to the entire Program shall
+be treated as though they were included in this License, to the extent
+that they are valid under applicable law. If additional permissions
+apply only to part of the Program, that part may be used separately
+under those permissions, but the entire Program remains governed by
+this License without regard to the additional permissions.
+
+ When you convey a copy of a covered work, you may at your option
+remove any additional permissions from that copy, or from any part of
+it. (Additional permissions may be written to require their own
+removal in certain cases when you modify the work.) You may place
+additional permissions on material, added by you to a covered work,
+for which you have or can give appropriate copyright permission.
+
+ Notwithstanding any other provision of this License, for material you
+add to a covered work, you may (if authorized by the copyright holders of
+that material) supplement the terms of this License with terms:
+
+ a) Disclaiming warranty or limiting liability differently from the
+ terms of sections 15 and 16 of this License; or
+
+ b) Requiring preservation of specified reasonable legal notices or
+ author attributions in that material or in the Appropriate Legal
+ Notices displayed by works containing it; or
+
+ c) Prohibiting misrepresentation of the origin of that material, or
+ requiring that modified versions of such material be marked in
+ reasonable ways as different from the original version; or
+
+ d) Limiting the use for publicity purposes of names of licensors or
+ authors of the material; or
+
+ e) Declining to grant rights under trademark law for use of some
+ trade names, trademarks, or service marks; or
+
+ f) Requiring indemnification of licensors and authors of that
+ material by anyone who conveys the material (or modified versions of
+ it) with contractual assumptions of liability to the recipient, for
+ any liability that these contractual assumptions directly impose on
+ those licensors and authors.
+
+ All other non-permissive additional terms are considered "further
+restrictions" within the meaning of section 10. If the Program as you
+received it, or any part of it, contains a notice stating that it is
+governed by this License along with a term that is a further
+restriction, you may remove that term. If a license document contains
+a further restriction but permits relicensing or conveying under this
+License, you may add to a covered work material governed by the terms
+of that license document, provided that the further restriction does
+not survive such relicensing or conveying.
+
+ If you add terms to a covered work in accord with this section, you
+must place, in the relevant source files, a statement of the
+additional terms that apply to those files, or a notice indicating
+where to find the applicable terms.
+
+ Additional terms, permissive or non-permissive, may be stated in the
+form of a separately written license, or stated as exceptions;
+the above requirements apply either way.
+
+ 8. Termination.
+
+ You may not propagate or modify a covered work except as expressly
+provided under this License. Any attempt otherwise to propagate or
+modify it is void, and will automatically terminate your rights under
+this License (including any patent licenses granted under the third
+paragraph of section 11).
+
+ However, if you cease all violation of this License, then your
+license from a particular copyright holder is reinstated (a)
+provisionally, unless and until the copyright holder explicitly and
+finally terminates your license, and (b) permanently, if the copyright
+holder fails to notify you of the violation by some reasonable means
+prior to 60 days after the cessation.
+
+ Moreover, your license from a particular copyright holder is
+reinstated permanently if the copyright holder notifies you of the
+violation by some reasonable means, this is the first time you have
+received notice of violation of this License (for any work) from that
+copyright holder, and you cure the violation prior to 30 days after
+your receipt of the notice.
+
+ Termination of your rights under this section does not terminate the
+licenses of parties who have received copies or rights from you under
+this License. If your rights have been terminated and not permanently
+reinstated, you do not qualify to receive new licenses for the same
+material under section 10.
+
+ 9. Acceptance Not Required for Having Copies.
+
+ You are not required to accept this License in order to receive or
+run a copy of the Program. Ancillary propagation of a covered work
+occurring solely as a consequence of using peer-to-peer transmission
+to receive a copy likewise does not require acceptance. However,
+nothing other than this License grants you permission to propagate or
+modify any covered work. These actions infringe copyright if you do
+not accept this License. Therefore, by modifying or propagating a
+covered work, you indicate your acceptance of this License to do so.
+
+ 10. Automatic Licensing of Downstream Recipients.
+
+ Each time you convey a covered work, the recipient automatically
+receives a license from the original licensors, to run, modify and
+propagate that work, subject to this License. You are not responsible
+for enforcing compliance by third parties with this License.
+
+ An "entity transaction" is a transaction transferring control of an
+organization, or substantially all assets of one, or subdividing an
+organization, or merging organizations. If propagation of a covered
+work results from an entity transaction, each party to that
+transaction who receives a copy of the work also receives whatever
+licenses to the work the party's predecessor in interest had or could
+give under the previous paragraph, plus a right to possession of the
+Corresponding Source of the work from the predecessor in interest, if
+the predecessor has it or can get it with reasonable efforts.
+
+ You may not impose any further restrictions on the exercise of the
+rights granted or affirmed under this License. For example, you may
+not impose a license fee, royalty, or other charge for exercise of
+rights granted under this License, and you may not initiate litigation
+(including a cross-claim or counterclaim in a lawsuit) alleging that
+any patent claim is infringed by making, using, selling, offering for
+sale, or importing the Program or any portion of it.
+
+ 11. Patents.
+
+ A "contributor" is a copyright holder who authorizes use under this
+License of the Program or a work on which the Program is based. The
+work thus licensed is called the contributor's "contributor version".
+
+ A contributor's "essential patent claims" are all patent claims
+owned or controlled by the contributor, whether already acquired or
+hereafter acquired, that would be infringed by some manner, permitted
+by this License, of making, using, or selling its contributor version,
+but do not include claims that would be infringed only as a
+consequence of further modification of the contributor version. For
+purposes of this definition, "control" includes the right to grant
+patent sublicenses in a manner consistent with the requirements of
+this License.
+
+ Each contributor grants you a non-exclusive, worldwide, royalty-free
+patent license under the contributor's essential patent claims, to
+make, use, sell, offer for sale, import and otherwise run, modify and
+propagate the contents of its contributor version.
+
+ In the following three paragraphs, a "patent license" is any express
+agreement or commitment, however denominated, not to enforce a patent
+(such as an express permission to practice a patent or covenant not to
+sue for patent infringement). To "grant" such a patent license to a
+party means to make such an agreement or commitment not to enforce a
+patent against the party.
+
+ If you convey a covered work, knowingly relying on a patent license,
+and the Corresponding Source of the work is not available for anyone
+to copy, free of charge and under the terms of this License, through a
+publicly available network server or other readily accessible means,
+then you must either (1) cause the Corresponding Source to be so
+available, or (2) arrange to deprive yourself of the benefit of the
+patent license for this particular work, or (3) arrange, in a manner
+consistent with the requirements of this License, to extend the patent
+license to downstream recipients. "Knowingly relying" means you have
+actual knowledge that, but for the patent license, your conveying the
+covered work in a country, or your recipient's use of the covered work
+in a country, would infringe one or more identifiable patents in that
+country that you have reason to believe are valid.
+
+ If, pursuant to or in connection with a single transaction or
+arrangement, you convey, or propagate by procuring conveyance of, a
+covered work, and grant a patent license to some of the parties
+receiving the covered work authorizing them to use, propagate, modify
+or convey a specific copy of the covered work, then the patent license
+you grant is automatically extended to all recipients of the covered
+work and works based on it.
+
+ A patent license is "discriminatory" if it does not include within
+the scope of its coverage, prohibits the exercise of, or is
+conditioned on the non-exercise of one or more of the rights that are
+specifically granted under this License. You may not convey a covered
+work if you are a party to an arrangement with a third party that is
+in the business of distributing software, under which you make payment
+to the third party based on the extent of your activity of conveying
+the work, and under which the third party grants, to any of the
+parties who would receive the covered work from you, a discriminatory
+patent license (a) in connection with copies of the covered work
+conveyed by you (or copies made from those copies), or (b) primarily
+for and in connection with specific products or compilations that
+contain the covered work, unless you entered into that arrangement,
+or that patent license was granted, prior to 28 March 2007.
+
+ Nothing in this License shall be construed as excluding or limiting
+any implied license or other defenses to infringement that may
+otherwise be available to you under applicable patent law.
+
+ 12. No Surrender of Others' Freedom.
+
+ If conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot convey a
+covered work so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you may
+not convey it at all. For example, if you agree to terms that obligate you
+to collect a royalty for further conveying from those to whom you convey
+the Program, the only way you could satisfy both those terms and this
+License would be to refrain entirely from conveying the Program.
+
+ 13. Remote Network Interaction; Use with the GNU General Public License.
+
+ Notwithstanding any other provision of this License, if you modify the
+Program, your modified version must prominently offer all users
+interacting with it remotely through a computer network (if your version
+supports such interaction) an opportunity to receive the Corresponding
+Source of your version by providing access to the Corresponding Source
+from a network server at no charge, through some standard or customary
+means of facilitating copying of software. This Corresponding Source
+shall include the Corresponding Source for any work covered by version 3
+of the GNU General Public License that is incorporated pursuant to the
+following paragraph.
+
+ Notwithstanding any other provision of this License, you have
+permission to link or combine any covered work with a work licensed
+under version 3 of the GNU General Public License into a single
+combined work, and to convey the resulting work. The terms of this
+License will continue to apply to the part which is the covered work,
+but the work with which it is combined will remain governed by version
+3 of the GNU General Public License.
+
+ 14. Revised Versions of this License.
+
+ The Free Software Foundation may publish revised and/or new versions of
+the GNU Affero General Public License from time to time. Such new versions
+will be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+ Each version is given a distinguishing version number. If the
+Program specifies that a certain numbered version of the GNU Affero General
+Public License "or any later version" applies to it, you have the
+option of following the terms and conditions either of that numbered
+version or of any later version published by the Free Software
+Foundation. If the Program does not specify a version number of the
+GNU Affero General Public License, you may choose any version ever published
+by the Free Software Foundation.
+
+ If the Program specifies that a proxy can decide which future
+versions of the GNU Affero General Public License can be used, that proxy's
+public statement of acceptance of a version permanently authorizes you
+to choose that version for the Program.
+
+ Later license versions may give you additional or different
+permissions. However, no additional obligations are imposed on any
+author or copyright holder as a result of your choosing to follow a
+later version.
+
+ 15. Disclaimer of Warranty.
+
+ THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
+APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
+HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
+OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
+IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
+ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+ 16. Limitation of Liability.
+
+ IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
+THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
+GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
+USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
+DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
+PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
+EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGES.
+
+ 17. Interpretation of Sections 15 and 16.
+
+ If the disclaimer of warranty and limitation of liability provided
+above cannot be given local legal effect according to their terms,
+reviewing courts shall apply local law that most closely approximates
+an absolute waiver of all civil liability in connection with the
+Program, unless a warranty or assumption of liability accompanies a
+copy of the Program in return for a fee.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+state the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+ <one line to give the program's name and a brief idea of what it does.>
+ Copyright (C) <year> <name of author>
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+Also add information on how to contact you by electronic and paper mail.
+
+ If your software can interact with users remotely through a computer
+network, you should also make sure that it provides a way for users to
+get its source. For example, if your program is a web application, its
+interface could display a "Source" link that leads users to an archive
+of the code. There are many ways you could offer source, and different
+solutions will be better for different programs; see section 13 for the
+specific requirements.
+
+ You should also get your employer (if you work as a programmer) or school,
+if any, to sign a "copyright disclaimer" for the program, if necessary.
+For more information on this, and how to apply and follow the GNU AGPL, see
+<http://www.gnu.org/licenses/>.
+
diff --git a/profile/Makefile.am b/profile/Makefile.am
new file mode 100644
index 0000000..4b60046
--- /dev/null
+++ b/profile/Makefile.am
@@ -0,0 +1,27 @@
+include $(top_srcdir)/Makefile.shared
+
+privatelib_LTLIBRARIES = libprof.la
+privatelibdir = $(pkglibdir)
+
+libprof_la_SOURCES = prof.h profin.c profout.c
+libprof_la_LIBADD = ../gamut/libgammap.la $(ICC_LIBS) \
+ ../gamut/libgamut.la ../xicc/libxicc.la \
+ ../numlib/libargyllnum.la ../spectro/libinsttypes.la \
+ ../xicc/libxutils.la ../libargyll.la
+
+LDADD = ./libprof.la ../xicc/libxutils.la ../spectro/libinst.la \
+ ../xicc/libxicc.la ../spectro/libinsttypes.la \
+ ../gamut/libgamut.la ../gamut/libgammap.la ../plot/libvrml.la \
+ ../plot/libplot.la ../rspl/librspl.la \
+ ../numlib/libargyllnum.la $(ICC_LIBS) ../cgats/libcgats.la \
+ ../libargyll.la $(TIFF_LIBS) ../spectro/libconv.la
+
+bin_PROGRAMS = simpprof kodak2ti3 cb2ti3 txt2ti3 splitti3 \
+ profcheck invprofcheck mppprof mppcheck verify colprof printcal \
+ applycal
+
+refdir = $(datadir)/color/argyll/ref
+
+ref_DATA = $(wildcard *.sp)
+
+EXTRA_DIST = License.txt Readme.txt
diff --git a/profile/Office.sp b/profile/Office.sp
new file mode 100644
index 0000000..290d310
--- /dev/null
+++ b/profile/Office.sp
@@ -0,0 +1,103 @@
+SPECT
+
+DESCRIPTOR "Argyll Spectral Power information for Typical Office lighting (Ref), extended to 355nm by estimate of UV"
+ORIGINATOR "Argyll CMS"
+CREATED "Wed Sep 12 15:12:32 2001"
+KEYWORD "SPECTRAL_BANDS"
+SPECTRAL_BANDS "80"
+KEYWORD "SPECTRAL_START_NM"
+SPECTRAL_START_NM "380.000000"
+KEYWORD "SPECTRAL_END_NM"
+SPECTRAL_END_NM "750.000000"
+KEYWORD "SPECTRAL_NORM"
+SPECTRAL_NORM "100.000"
+
+KEYWORD "SPEC_355"
+KEYWORD "SPEC_360"
+KEYWORD "SPEC_365"
+KEYWORD "SPEC_370"
+KEYWORD "SPEC_375"
+KEYWORD "SPEC_380"
+KEYWORD "SPEC_385"
+KEYWORD "SPEC_390"
+KEYWORD "SPEC_395"
+KEYWORD "SPEC_400"
+KEYWORD "SPEC_405"
+KEYWORD "SPEC_410"
+KEYWORD "SPEC_415"
+KEYWORD "SPEC_420"
+KEYWORD "SPEC_425"
+KEYWORD "SPEC_430"
+KEYWORD "SPEC_435"
+KEYWORD "SPEC_440"
+KEYWORD "SPEC_445"
+KEYWORD "SPEC_450"
+KEYWORD "SPEC_455"
+KEYWORD "SPEC_460"
+KEYWORD "SPEC_465"
+KEYWORD "SPEC_470"
+KEYWORD "SPEC_475"
+KEYWORD "SPEC_480"
+KEYWORD "SPEC_485"
+KEYWORD "SPEC_490"
+KEYWORD "SPEC_495"
+KEYWORD "SPEC_500"
+KEYWORD "SPEC_505"
+KEYWORD "SPEC_510"
+KEYWORD "SPEC_515"
+KEYWORD "SPEC_520"
+KEYWORD "SPEC_525"
+KEYWORD "SPEC_530"
+KEYWORD "SPEC_535"
+KEYWORD "SPEC_540"
+KEYWORD "SPEC_545"
+KEYWORD "SPEC_550"
+KEYWORD "SPEC_555"
+KEYWORD "SPEC_560"
+KEYWORD "SPEC_565"
+KEYWORD "SPEC_570"
+KEYWORD "SPEC_575"
+KEYWORD "SPEC_580"
+KEYWORD "SPEC_585"
+KEYWORD "SPEC_590"
+KEYWORD "SPEC_595"
+KEYWORD "SPEC_600"
+KEYWORD "SPEC_605"
+KEYWORD "SPEC_610"
+KEYWORD "SPEC_615"
+KEYWORD "SPEC_620"
+KEYWORD "SPEC_625"
+KEYWORD "SPEC_630"
+KEYWORD "SPEC_635"
+KEYWORD "SPEC_640"
+KEYWORD "SPEC_645"
+KEYWORD "SPEC_650"
+KEYWORD "SPEC_655"
+KEYWORD "SPEC_660"
+KEYWORD "SPEC_665"
+KEYWORD "SPEC_670"
+KEYWORD "SPEC_675"
+KEYWORD "SPEC_680"
+KEYWORD "SPEC_685"
+KEYWORD "SPEC_690"
+KEYWORD "SPEC_695"
+KEYWORD "SPEC_700"
+KEYWORD "SPEC_705"
+KEYWORD "SPEC_710"
+KEYWORD "SPEC_715"
+KEYWORD "SPEC_720"
+KEYWORD "SPEC_725"
+KEYWORD "SPEC_730"
+KEYWORD "SPEC_735"
+KEYWORD "SPEC_740"
+KEYWORD "SPEC_745"
+KEYWORD "SPEC_750"
+NUMBER_OF_FIELDS 80
+BEGIN_DATA_FORMAT
+SPEC_355 SPEC_360 SPEC_365 SPEC_370 SPEC_375 SPEC_380 SPEC_385 SPEC_390 SPEC_395 SPEC_400 SPEC_405 SPEC_410 SPEC_415 SPEC_420 SPEC_425 SPEC_430 SPEC_435 SPEC_440 SPEC_445 SPEC_450 SPEC_455 SPEC_460 SPEC_465 SPEC_470 SPEC_475 SPEC_480 SPEC_485 SPEC_490 SPEC_495 SPEC_500 SPEC_505 SPEC_510 SPEC_515 SPEC_520 SPEC_525 SPEC_530 SPEC_535 SPEC_540 SPEC_545 SPEC_550 SPEC_555 SPEC_560 SPEC_565 SPEC_570 SPEC_575 SPEC_580 SPEC_585 SPEC_590 SPEC_595 SPEC_600 SPEC_605 SPEC_610 SPEC_615 SPEC_620 SPEC_625 SPEC_630 SPEC_635 SPEC_640 SPEC_645 SPEC_650 SPEC_655 SPEC_660 SPEC_665 SPEC_670 SPEC_675 SPEC_680 SPEC_685 SPEC_690 SPEC_695 SPEC_700 SPEC_705 SPEC_710 SPEC_715 SPEC_720 SPEC_725 SPEC_730 SPEC_735 SPEC_740 SPEC_745 SPEC_750
+END_DATA_FORMAT
+
+NUMBER_OF_SETS 1
+BEGIN_DATA
+0.0 3.0 5.0 6.0 8.0 9.4368 10.6036 13.842 20.7332 34.842 32.098 26.2668 24.5324 24.5532 33.8892 67.0204 82.9432 65.6956 48.8256 39.5772 38.0888 37.9392 37.624 37.78 37.4864 37.5828 37.4708 37.6088 37.4588 37.4644 37.622 38.0968 39.4056 41.7988 45.5484 50.8436 69.6052 106.3616 130.678 114.7876 97.5064 99.306 107.0748 117.8096 127.0744 126.3964 116.4272 111.9564 106.0416 99.828 93.2196 85.5196 77.282 69.6072 62.604 55.1144 48.3624 42.4144 37.872 33.3228 29.2668 25.792 23.0656 19.8412 17.7056 15.4692 13.7852 12.314 10.8312 9.4588 8.542 7.4892 6.912 5.8156 5.7536 5.3348 5.1076 4.636 4.0384 4.1468
+END_DATA
diff --git a/profile/Readme.txt b/profile/Readme.txt
new file mode 100644
index 0000000..5fc569a
--- /dev/null
+++ b/profile/Readme.txt
@@ -0,0 +1,3 @@
+
+This directory has the source code ICC profile creation.
+
diff --git a/profile/Trulux.sp b/profile/Trulux.sp
new file mode 100644
index 0000000..fc5bb0f
--- /dev/null
+++ b/profile/Trulux.sp
@@ -0,0 +1,103 @@
+SPECT
+
+DESCRIPTOR "Argyll Spectral Power information for Trulux D50 viewer - Foam reflector, extended to 355nm with estimation of UV"
+ORIGINATOR "Argyll CMS"
+CREATED "Fri Oct 5 15:12:32 2001"
+KEYWORD "SPECTRAL_BANDS"
+SPECTRAL_BANDS "80"
+KEYWORD "SPECTRAL_START_NM"
+SPECTRAL_START_NM "380.000000"
+KEYWORD "SPECTRAL_END_NM"
+SPECTRAL_END_NM "750.000000"
+KEYWORD "SPECTRAL_NORM"
+SPECTRAL_NORM "100.000"
+
+KEYWORD "SPEC_355"
+KEYWORD "SPEC_360"
+KEYWORD "SPEC_365"
+KEYWORD "SPEC_370"
+KEYWORD "SPEC_375"
+KEYWORD "SPEC_380"
+KEYWORD "SPEC_385"
+KEYWORD "SPEC_390"
+KEYWORD "SPEC_395"
+KEYWORD "SPEC_400"
+KEYWORD "SPEC_405"
+KEYWORD "SPEC_410"
+KEYWORD "SPEC_415"
+KEYWORD "SPEC_420"
+KEYWORD "SPEC_425"
+KEYWORD "SPEC_430"
+KEYWORD "SPEC_435"
+KEYWORD "SPEC_440"
+KEYWORD "SPEC_445"
+KEYWORD "SPEC_450"
+KEYWORD "SPEC_455"
+KEYWORD "SPEC_460"
+KEYWORD "SPEC_465"
+KEYWORD "SPEC_470"
+KEYWORD "SPEC_475"
+KEYWORD "SPEC_480"
+KEYWORD "SPEC_485"
+KEYWORD "SPEC_490"
+KEYWORD "SPEC_495"
+KEYWORD "SPEC_500"
+KEYWORD "SPEC_505"
+KEYWORD "SPEC_510"
+KEYWORD "SPEC_515"
+KEYWORD "SPEC_520"
+KEYWORD "SPEC_525"
+KEYWORD "SPEC_530"
+KEYWORD "SPEC_535"
+KEYWORD "SPEC_540"
+KEYWORD "SPEC_545"
+KEYWORD "SPEC_550"
+KEYWORD "SPEC_555"
+KEYWORD "SPEC_560"
+KEYWORD "SPEC_565"
+KEYWORD "SPEC_570"
+KEYWORD "SPEC_575"
+KEYWORD "SPEC_580"
+KEYWORD "SPEC_585"
+KEYWORD "SPEC_590"
+KEYWORD "SPEC_595"
+KEYWORD "SPEC_600"
+KEYWORD "SPEC_605"
+KEYWORD "SPEC_610"
+KEYWORD "SPEC_615"
+KEYWORD "SPEC_620"
+KEYWORD "SPEC_625"
+KEYWORD "SPEC_630"
+KEYWORD "SPEC_635"
+KEYWORD "SPEC_640"
+KEYWORD "SPEC_645"
+KEYWORD "SPEC_650"
+KEYWORD "SPEC_655"
+KEYWORD "SPEC_660"
+KEYWORD "SPEC_665"
+KEYWORD "SPEC_670"
+KEYWORD "SPEC_675"
+KEYWORD "SPEC_680"
+KEYWORD "SPEC_685"
+KEYWORD "SPEC_690"
+KEYWORD "SPEC_695"
+KEYWORD "SPEC_700"
+KEYWORD "SPEC_705"
+KEYWORD "SPEC_710"
+KEYWORD "SPEC_715"
+KEYWORD "SPEC_720"
+KEYWORD "SPEC_725"
+KEYWORD "SPEC_730"
+KEYWORD "SPEC_735"
+KEYWORD "SPEC_740"
+KEYWORD "SPEC_745"
+KEYWORD "SPEC_750"
+NUMBER_OF_FIELDS 80
+BEGIN_DATA_FORMAT
+SPEC_355 SPEC_360 SPEC_365 SPEC_370 SPEC_375 SPEC_380 SPEC_385 SPEC_390 SPEC_395 SPEC_400 SPEC_405 SPEC_410 SPEC_415 SPEC_420 SPEC_425 SPEC_430 SPEC_435 SPEC_440 SPEC_445 SPEC_450 SPEC_455 SPEC_460 SPEC_465 SPEC_470 SPEC_475 SPEC_480 SPEC_485 SPEC_490 SPEC_495 SPEC_500 SPEC_505 SPEC_510 SPEC_515 SPEC_520 SPEC_525 SPEC_530 SPEC_535 SPEC_540 SPEC_545 SPEC_550 SPEC_555 SPEC_560 SPEC_565 SPEC_570 SPEC_575 SPEC_580 SPEC_585 SPEC_590 SPEC_595 SPEC_600 SPEC_605 SPEC_610 SPEC_615 SPEC_620 SPEC_625 SPEC_630 SPEC_635 SPEC_640 SPEC_645 SPEC_650 SPEC_655 SPEC_660 SPEC_665 SPEC_670 SPEC_675 SPEC_680 SPEC_685 SPEC_690 SPEC_695 SPEC_700 SPEC_705 SPEC_710 SPEC_715 SPEC_720 SPEC_725 SPEC_730 SPEC_735 SPEC_740 SPEC_745 SPEC_750
+END_DATA_FORMAT
+
+NUMBER_OF_SETS 1
+BEGIN_DATA
+0.0 0.685175 1.37035 2.255525 3.0407 4.11105 4.09644 5.01837 7.36485 11.98614 11.19519 7.11198 3.33963 6.44973 21.99549 67.47228 93.88269 79.77081 64.85577 57.85737 65.37378 75.29142 82.82394 87.81939 90.44277 92.28321 93.10557 93.11004 92.17122 89.96841 87.65004 84.8304 82.13712 77.75847 74.04651 74.87289 91.45155 123.67365 139.4193 115.8867 87.2391 79.06761 81.88944 91.58172 101.15043 102.11544 94.71408 93.87819 94.00419 95.28195 97.80723 99.53307 99.40416 98.43141 97.50237 95.58378 93.41358 91.00236 88.69698 85.72017 83.18451 80.22261 77.45886 73.79589 70.02708 66.29484 62.08128 57.95706 53.89545 49.70493 45.99504 42.19815 38.68413 35.16609 32.78418 30.1482 27.3201 24.13857 21.88494 20.10168
+END_DATA
diff --git a/profile/TruluxPlus.sp b/profile/TruluxPlus.sp
new file mode 100644
index 0000000..0fd9c39
--- /dev/null
+++ b/profile/TruluxPlus.sp
@@ -0,0 +1,103 @@
+SPECT
+
+DESCRIPTOR "Argyll Spectral Power information for Trulux D50 viewer - Foam reflector, extended to 355nm with larger estimation of UV"
+ORIGINATOR "Argyll CMS"
+CREATED "Fri Oct 5 15:12:32 2001"
+KEYWORD "SPECTRAL_BANDS"
+SPECTRAL_BANDS "80"
+KEYWORD "SPECTRAL_START_NM"
+SPECTRAL_START_NM "355.000000"
+KEYWORD "SPECTRAL_END_NM"
+SPECTRAL_END_NM "750.000000"
+KEYWORD "SPECTRAL_NORM"
+SPECTRAL_NORM "100.000"
+
+KEYWORD "SPEC_355"
+KEYWORD "SPEC_360"
+KEYWORD "SPEC_365"
+KEYWORD "SPEC_370"
+KEYWORD "SPEC_375"
+KEYWORD "SPEC_380"
+KEYWORD "SPEC_385"
+KEYWORD "SPEC_390"
+KEYWORD "SPEC_395"
+KEYWORD "SPEC_400"
+KEYWORD "SPEC_405"
+KEYWORD "SPEC_410"
+KEYWORD "SPEC_415"
+KEYWORD "SPEC_420"
+KEYWORD "SPEC_425"
+KEYWORD "SPEC_430"
+KEYWORD "SPEC_435"
+KEYWORD "SPEC_440"
+KEYWORD "SPEC_445"
+KEYWORD "SPEC_450"
+KEYWORD "SPEC_455"
+KEYWORD "SPEC_460"
+KEYWORD "SPEC_465"
+KEYWORD "SPEC_470"
+KEYWORD "SPEC_475"
+KEYWORD "SPEC_480"
+KEYWORD "SPEC_485"
+KEYWORD "SPEC_490"
+KEYWORD "SPEC_495"
+KEYWORD "SPEC_500"
+KEYWORD "SPEC_505"
+KEYWORD "SPEC_510"
+KEYWORD "SPEC_515"
+KEYWORD "SPEC_520"
+KEYWORD "SPEC_525"
+KEYWORD "SPEC_530"
+KEYWORD "SPEC_535"
+KEYWORD "SPEC_540"
+KEYWORD "SPEC_545"
+KEYWORD "SPEC_550"
+KEYWORD "SPEC_555"
+KEYWORD "SPEC_560"
+KEYWORD "SPEC_565"
+KEYWORD "SPEC_570"
+KEYWORD "SPEC_575"
+KEYWORD "SPEC_580"
+KEYWORD "SPEC_585"
+KEYWORD "SPEC_590"
+KEYWORD "SPEC_595"
+KEYWORD "SPEC_600"
+KEYWORD "SPEC_605"
+KEYWORD "SPEC_610"
+KEYWORD "SPEC_615"
+KEYWORD "SPEC_620"
+KEYWORD "SPEC_625"
+KEYWORD "SPEC_630"
+KEYWORD "SPEC_635"
+KEYWORD "SPEC_640"
+KEYWORD "SPEC_645"
+KEYWORD "SPEC_650"
+KEYWORD "SPEC_655"
+KEYWORD "SPEC_660"
+KEYWORD "SPEC_665"
+KEYWORD "SPEC_670"
+KEYWORD "SPEC_675"
+KEYWORD "SPEC_680"
+KEYWORD "SPEC_685"
+KEYWORD "SPEC_690"
+KEYWORD "SPEC_695"
+KEYWORD "SPEC_700"
+KEYWORD "SPEC_705"
+KEYWORD "SPEC_710"
+KEYWORD "SPEC_715"
+KEYWORD "SPEC_720"
+KEYWORD "SPEC_725"
+KEYWORD "SPEC_730"
+KEYWORD "SPEC_735"
+KEYWORD "SPEC_740"
+KEYWORD "SPEC_745"
+KEYWORD "SPEC_750"
+NUMBER_OF_FIELDS 80
+BEGIN_DATA_FORMAT
+SPEC_355 SPEC_360 SPEC_365 SPEC_370 SPEC_375 SPEC_380 SPEC_385 SPEC_390 SPEC_395 SPEC_400 SPEC_405 SPEC_410 SPEC_415 SPEC_420 SPEC_425 SPEC_430 SPEC_435 SPEC_440 SPEC_445 SPEC_450 SPEC_455 SPEC_460 SPEC_465 SPEC_470 SPEC_475 SPEC_480 SPEC_485 SPEC_490 SPEC_495 SPEC_500 SPEC_505 SPEC_510 SPEC_515 SPEC_520 SPEC_525 SPEC_530 SPEC_535 SPEC_540 SPEC_545 SPEC_550 SPEC_555 SPEC_560 SPEC_565 SPEC_570 SPEC_575 SPEC_580 SPEC_585 SPEC_590 SPEC_595 SPEC_600 SPEC_605 SPEC_610 SPEC_615 SPEC_620 SPEC_625 SPEC_630 SPEC_635 SPEC_640 SPEC_645 SPEC_650 SPEC_655 SPEC_660 SPEC_665 SPEC_670 SPEC_675 SPEC_680 SPEC_685 SPEC_690 SPEC_695 SPEC_700 SPEC_705 SPEC_710 SPEC_715 SPEC_720 SPEC_725 SPEC_730 SPEC_735 SPEC_740 SPEC_745 SPEC_750
+END_DATA_FORMAT
+
+NUMBER_OF_SETS 1
+BEGIN_DATA
+10.0 10.0 10.0 10.0 10.0407 6.11105 5.09644 5.01837 7.36485 11.98614 11.19519 7.11198 3.33963 6.44973 21.99549 67.47228 93.88269 79.77081 64.85577 57.85737 65.37378 75.29142 82.82394 87.81939 90.44277 92.28321 93.10557 93.11004 92.17122 89.96841 87.65004 84.8304 82.13712 77.75847 74.04651 74.87289 91.45155 123.67365 139.4193 115.8867 87.2391 79.06761 81.88944 91.58172 101.15043 102.11544 94.71408 93.87819 94.00419 95.28195 97.80723 99.53307 99.40416 98.43141 97.50237 95.58378 93.41358 91.00236 88.69698 85.72017 83.18451 80.22261 77.45886 73.79589 70.02708 66.29484 62.08128 57.95706 53.89545 49.70493 45.99504 42.19815 38.68413 35.16609 32.78418 30.1482 27.3201 24.13857 21.88494 20.10168
+END_DATA
diff --git a/profile/afiles b/profile/afiles
new file mode 100644
index 0000000..986bf70
--- /dev/null
+++ b/profile/afiles
@@ -0,0 +1,40 @@
+Readme.txt
+License.txt
+afiles
+Jamfile
+printcal.c
+prof.h
+profin.c
+profout.c
+colprof.c
+profcheck.c
+invprofcheck.c
+verify.c
+applycal.c
+mppprof.c
+mppcheck.c
+simpprof.c
+kodak2ti3.c
+cb2ti3.c
+txt2ti3.c
+splitti3.c
+3dap5k.sp
+GTIPlus.sp
+Office.sp
+Trulux.sp
+TruluxPlus.sp
+example.sp
+example121.sp
+CIE_C.sp
+D50_0.0.sp
+D50_0.1.sp
+D50_0.3.sp
+D50_0.5.sp
+D50_0.7.sp
+D50_1.0.sp
+D50_1.2.sp
+D50_1.5.sp
+D50_1.7.sp
+D50_2.0.sp
+D50_2.5.sp
+D50_3.0.sp
diff --git a/profile/applycal.c b/profile/applycal.c
new file mode 100644
index 0000000..ff9a76d
--- /dev/null
+++ b/profile/applycal.c
@@ -0,0 +1,693 @@
+
+/*
+ * ArgyllCMS.
+ * Apply a device calibration to an ICC profile.
+ *
+ * Author: Graeme W. Gill
+ * Date: 2009/8/31
+ * Version: 1.00
+ *
+ * Copyright 2009 Graeme W. Gill
+ *
+ * This material is licenced under the GNU AFFERO GENERAL PUBLIC LICENSE Version 3 :-
+ * see the License.txt file for licencing details.
+ */
+
+/* TTBD:
+ *
+ * Would be good if remove restores shared curves, rather than
+ * leaving duplicates.
+ *
+ * Could stash the whole cal file in a text tag.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <fcntl.h>
+#include <string.h>
+#include <time.h>
+#include "copyright.h"
+#include "aconfig.h"
+#include "cgats.h"
+#include "numlib.h"
+#include "rspl.h"
+#include "xicc.h"
+
+#undef DEBUG
+
+#ifdef DEBUG
+#undef DBG
+#define DBG(xxx) printf xxx ;
+#else
+#undef DBG
+#define DBG(xxx)
+#endif
+
+void usage(char *diag, ...) {
+ int i;
+ fprintf(stderr,"Apply device calibration to an ICC profile, Version %s\n",ARGYLL_VERSION_STR);
+ fprintf(stderr,"Author: Graeme W. Gill, licensed under the AGPL Version 3\n");
+ if (diag != NULL) {
+ va_list args;
+ fprintf(stderr," Diagnostic: ");
+ va_start(args, diag);
+ vfprintf(stderr, diag, args);
+ va_end(args);
+ fprintf(stderr,"\n");
+ }
+ fprintf(stderr,"usage: %s [-options] [calfile.cal] inprof%s [outprof%s]\n","applycal",ICC_FILE_EXT,ICC_FILE_EXT);
+ fprintf(stderr," -v Verbose mode\n");
+ fprintf(stderr," -a Apply or re-apply calibration (default)\n");
+ fprintf(stderr," -u Remove calibration\n");
+ fprintf(stderr," -c Check calibration\n");
+ fprintf(stderr," calfile.cal Calibration file to apply\n");
+ fprintf(stderr," inprof%s ICC profile to read\n",ICC_FILE_EXT);
+ fprintf(stderr," outprof%s modified ICC profile to write\n",ICC_FILE_EXT);
+ exit(2);
+}
+
+/* A primary signature and its backup */
+struct _tagsigpair {
+ icTagSignature prim;
+ icTagSignature back;
+ int chan;
+ int dir; /* 0 = none or out, 1 = in */
+}; typedef struct _tagsigpair tagsigpair;
+
+int
+main(int argc, char *argv[]) {
+ int fa,nfa; /* argument we're looking at */
+ char cal_name[MAXNAMEL+1];
+ char in_name[MAXNAMEL+1];
+ char out_name[MAXNAMEL+1];
+ xcal *cal = NULL; /* Calibration to apply */
+ icmFile *rd_fp = NULL, *wr_fp = NULL;
+ icc *icco;
+ int apply = 1;
+ int remove = 0;
+ int check = 0;
+ int verb = 0;
+ int found = -1;
+ int rv;
+
+ if (argc < 3)
+ usage("Too few arguments");
+
+ /* Process the arguments */
+ for(fa = 1;fa < argc;fa++) {
+ nfa = fa; /* skip to nfa if next argument is used */
+ if (argv[fa][0] == '-') { /* Look for any flags */
+ char *na = NULL; /* next argument after flag, null if none */
+
+ if (argv[fa][2] != '\000')
+ na = &argv[fa][2]; /* next is directly after flag */
+ else {
+ if ((fa+1) < argc) {
+ if (argv[fa+1][0] != '-') {
+ nfa = fa + 1;
+ na = argv[nfa]; /* next is seperate non-flag argument */
+ }
+ }
+ }
+
+ if (argv[fa][1] == '?')
+ usage("Usage requested");
+
+ else if (argv[fa][1] == 'v' || argv[fa][1] == 'V')
+ verb = 1;
+
+ else if (argv[fa][1] == 'a' || argv[fa][1] == 'A') {
+ apply = 1;
+ remove = 0;
+ check = 0;
+ }
+
+ else if (argv[fa][1] == 'u' || argv[fa][1] == 'U') {
+ apply = 0;
+ remove = 1;
+ check = 0;
+ }
+
+ else if (argv[fa][1] == 'c' || argv[fa][1] == 'C') {
+ apply = 0;
+ remove = 0;
+ check = 1;
+ }
+
+ else
+ usage("Unknown option '%s'",argv[fa][1]);
+
+ } else
+ break;
+ }
+
+ if (apply) {
+ if (fa >= argc || argv[fa][0] == '-') usage("Missing calibration filename");
+ strncpy(cal_name,argv[fa++],MAXNAMEL); cal_name[MAXNAMEL] = '\000';
+ }
+
+ if (fa >= argc || argv[fa][0] == '-') usage("Missing input profile filename");
+ strncpy(in_name,argv[fa++],MAXNAMEL); in_name[MAXNAMEL] = '\000';
+
+ if (apply || remove) {
+ if (fa >= argc || argv[fa][0] == '-') usage("Missing output profile name");
+ strncpy(out_name,argv[fa++],MAXNAMEL); out_name[MAXNAMEL] = '\000';
+ }
+ if (fa < argc) usage("Extra argument '%s'",argv[fa]);
+
+ DBG(("apply %d, remove %d, check %d, calfile '%s', infile '%s', outfile '%s'\n",apply,remove,check,cal_name,in_name,out_name));
+
+ if (apply) {
+ /* Open up the calibration file */
+ if ((cal = new_xcal()) == NULL)
+ error("new_xcal failed");
+ if ((cal->read(cal, cal_name)) != 0)
+ error("%s",cal->err);
+ }
+
+ /* Open up the profile for reading */
+ if ((rd_fp = new_icmFileStd_name(in_name,"r")) == NULL)
+ error ("Can't open file '%s'",in_name);
+
+ if ((icco = new_icc()) == NULL)
+ error ("Creation of ICC object failed");
+
+ /* Read header etc. */
+ if ((rv = icco->read(icco,rd_fp,0)) != 0)
+ error ("%d, %s",rv,icco->err);
+
+ /* Read every tag */
+ if (icco->read_all_tags(icco) != 0) {
+ error("Unable to read all tags: %d, %s",icco->errc,icco->err);
+ }
+
+ rd_fp->del(rd_fp);
+
+ /* ======================================= */
+ {
+ tagsigpair sigs[] = { /* Signatures to modify and their backups */
+ { icSigProfileDescriptionTag, icmMakeTag('A','R','0','T'), 0, 0 },
+ { icSigAToB0Tag, icmMakeTag('A','R','1','0'), 0, 1 },
+ { icSigAToB1Tag, icmMakeTag('A','R','2','0'), 0, 1 },
+ { icSigAToB2Tag, icmMakeTag('A','R','3','0'), 0, 1 },
+ { icSigBToA0Tag, icmMakeTag('A','R','4','0'), 0, 0 },
+ { icSigBToA1Tag, icmMakeTag('A','R','5','0'), 0, 0 },
+ { icSigBToA2Tag, icmMakeTag('A','R','6','0'), 0, 0 },
+ { icSigRedTRCTag, icmMakeTag('A','R','7','0'), 0, 0 },
+ { icSigGreenTRCTag, icmMakeTag('A','R','7','1'), 1, 0 },
+ { icSigBlueTRCTag, icmMakeTag('A','R','7','2'), 2, 0 },
+ { icSigGrayTRCTag, icmMakeTag('A','R','8','0'), 0, 0 },
+ { 0, 0, 0 }
+ };
+ tagsigpair linksigs[] = { /* Signatures to modify */
+ { icSigProfileDescriptionTag, icmMakeTag('A','R','0','T'), 0 },
+ { icSigAToB0Tag, icmMakeTag('A','R','9','1'), 1 },
+ { icSigAToB0Tag, icmMakeTag('A','R','9','0'), 0 },
+ { 0, 0, 0 }
+ };
+ tagsigpair *ssigp, *sigp;
+ int ntags = 0; /* Number of tags done */
+ icmBase *dtags[50]; /* Address of tags done */
+ int inp = 0; /* NZ for input calibration */
+ unsigned int i, j;
+
+ if (icco->header->deviceClass == icSigInputClass && cal->devclass != icSigInputClass) {
+ warning("Non-input calibration being applied to an input profile");
+ }
+
+ if (check) {
+ DBG(("Checking...\n"));
+
+ for (sigp = linksigs; sigp->prim != 0; sigp++) {
+ icmBase *primt;
+
+ if (sigp->prim != icSigProfileDescriptionTag)
+ continue;
+
+ if ((primt = icco->read_tag(icco, sigp->prim)) == NULL)
+ error("Can't find icSigProfileDescriptionTag in profile");
+
+ /* See if we have a backup */
+ if (icco->read_tag(icco, sigp->back) != NULL)
+ found = 1;
+ else
+ found = 0;
+ }
+ DBG(("found = %d\n",found));
+ if (found < 0)
+ error("Internal, didn't look for ProfileDescriptionTag");
+
+ if (verb) {
+ if (found)
+ printf("Profile has had calibration applied\n");
+ else
+ printf("Profile has NOT had calibration applied\n");
+ }
+ } else if (apply) {
+ DBG(("Applying...\n"));
+
+ if (icco->header->deviceClass == icSigInputClass
+ || icco->header->deviceClass == icSigDisplayClass
+ || icco->header->deviceClass == icSigOutputClass) {
+
+ DBG(("Input, display or output profile\n"));
+
+ /* Check colorspace is compatible */
+ if (cal->colspace != icco->header->colorSpace)
+ error("Calibration space %s doesn't match profile %s",
+ icm2str(icmColorSpaceSignature, cal->colspace),
+ icm2str(icmColorSpaceSignature, icco->header->colorSpace));
+
+ ssigp = sigs;
+ /* Note the cal direction */
+ if (cal->devclass == icSigInputClass)
+ inp = 1;
+ else
+ inp = 0;
+
+ } else if (icco->header->deviceClass == icSigLinkClass) {
+ DBG(("Device link profile\n"));
+
+ /* Check colorspace is compatible */
+ if (cal->colspace != icco->header->pcs)
+ error("Calibration space %s doesn't match profile %s",
+ icm2str(icmColorSpaceSignature, cal->colspace),
+ icm2str(icmColorSpaceSignature, icco->header->pcs));
+ ssigp = linksigs;
+ /* Noe the cal direction */
+ if (cal->devclass == icSigInputClass)
+ inp = 1;
+ else
+ inp = 0;
+ } else {
+ error("Can't apply calibration to profile of class %s",
+ icm2str(icmProfileClassSignature, icco->header->deviceClass));
+ }
+ DBG(("input calibration = %d\n",inp));
+
+ /* First pass is to duplicate any linked TRCTags */
+ for (sigp = ssigp; sigp->prim != 0; sigp++) { /* Process each tag */
+ icmBase *primt;
+
+ DBG(("looking for tag '%s'\n",icm2str(icmTagSignature, sigp->prim)));
+ if ((primt = icco->read_tag(icco, sigp->prim)) == NULL) {
+ if (sigp->prim == icSigProfileDescriptionTag)
+ error("Can't find icSigProfileDescriptionTag in profile");
+ continue; /* Don't have this tag */
+ }
+
+ /* XXXXTRCTag */
+ if (primt->ttype == icSigCurveType) {
+ icmCurve *wo, *ro = (icmCurve *)primt;
+
+ /* See if we have a backup */
+ if ((wo = (icmCurve *)icco->read_tag(icco, sigp->back)) == NULL) {
+
+ /* If tag is shared, we need to separated it */
+ if (ro->refcount > 1) {
+ DBG(("tag is shared, so separate it\n"));
+ if ((wo = (icmCurve *)icco->add_tag(
+ icco, sigp->back, icSigCurveType)) == NULL)
+ error("Failed to create tag '%s'\n", icm2str(icmTagSignature, sigp->back));
+ wo->flag = ro->flag;
+ wo->size = ro->size;
+ wo->allocate((icmBase *)wo); /* Allocate space */
+ for (i = 0; i < wo->size; i++) /* Copy the curve */
+ wo->data[i] = ro->data[i];
+
+ if (icco->delete_tag(icco, sigp->prim))
+ error("Failed to delete tag '%s'",icm2str(icmTagSignature, sigp->prim));
+ if (icco->rename_tag(icco, sigp->back, sigp->prim))
+ error("Failed to rename tag '%s' to '%s'",
+ icm2str(icmTagSignature, sigp->back),
+ icm2str(icmTagSignature, sigp->prim));
+ }
+ } else {
+ if (ro->refcount > 1)
+ error("Found tag %s has backup, but is shared",icm2str(icmTagSignature,sigp->prim));
+ }
+ }
+ }
+ /* Second pass we create backups and calibrated curves */
+ for (sigp = ssigp; sigp->prim != 0; sigp++) { /* Process each tag */
+ icmBase *primt;
+
+ DBG(("looking for tag '%s'\n",icm2str(icmTagSignature, sigp->prim)));
+ if ((primt = icco->read_tag(icco, sigp->prim)) == NULL) {
+ if (sigp->prim == icSigProfileDescriptionTag)
+ error("Can't find icSigProfileDescriptionTag in profile");
+ continue; /* Don't have this tag */
+ }
+
+ /* icSigProfileDescriptionTag type */
+ if (primt->ttype == icSigTextDescriptionType) {
+ icmTextDescription *wo, *ro = (icmTextDescription *)primt;
+ char *extra = NULL;
+
+ /* See if we've done this tag before due to links */
+ for (i = 0; i < ntags; i++) {
+ if (dtags[i] == primt)
+ break; /* Yes */
+ }
+ if (i < ntags) {
+ DBG(("Found this tag before (link)\n"));
+ continue; /* Skip tag */
+ }
+ if (ntags >= 50) /* Impossible */
+ error("Internal, run out of previuos tags space");
+ dtags[ntags++] = primt; /* Remember this one */
+
+ DBG(("ProfileDescriptionTag\n"));
+ DBG(("Looking for backup tag '%s'\n",icm2str(icmTagSignature, sigp->back)));
+
+ /* See if we have a backup */
+ if ((wo = (icmTextDescription *)icco->read_tag(icco, sigp->back)) == NULL) {
+ DBG(("No backup, creating one\n"));
+ /* No, so create one */
+ if ((wo = (icmTextDescription *)icco->add_tag(
+ icco, sigp->back, icSigTextDescriptionType)) == NULL)
+ error("Failed to create tag '%s'\n", icm2str(icmTagSignature, sigp->back));
+
+ wo->size = ro->size;
+ wo->allocate((icmBase *)wo); /* Allocate space */
+ strcpy(wo->desc, ro->desc); /* Copy the string in */
+ /* Hmm. what should we do with Unicode and script ? */
+ }
+
+ if (cal->xpi.profDesc != NULL)
+ extra = cal->xpi.profDesc;
+ else
+ extra = cal_name;
+
+ ro->size = strlen(ro->desc) + 3 + strlen(extra) + 3;
+ ro->allocate((icmBase *)ro); /* Allocate space */
+ strcpy(ro->desc, wo->desc);
+ strcat(ro->desc, " [ ");
+ strcat(ro->desc, extra);
+ strcat(ro->desc, " ]");
+
+ DBG(("Set tag contents to '%s'\n",ro->desc));
+
+ /* icSigAToBXTag or icSigBToAXTag */
+ } else if (primt->ttype == icSigLut8Type
+ || primt->ttype == icSigLut16Type) {
+ icmLut *ro = (icmLut *)primt; /* Modified Lut */
+
+ /* See if we've done this tag before due to links */
+ for (i = 0; i < ntags; i++) {
+ if (dtags[i] == primt)
+ break; /* Yes */
+ }
+ if (i < ntags) {
+ DBG(("Found this tag before (link)\n"));
+ continue; /* Skip tag */
+ }
+ if (ntags >= 50) /* Impossible */
+ error("Internal, run out of previous tags space");
+ dtags[ntags++] = primt; /* Remember this one */
+
+ DBG(("Lut8 or Lut16\n"));
+
+ if (sigp->dir) {
+ /* Apply calibration to the input table */
+ for (j = 0; j < ro->inputChan; j++) {
+ icTagSignature bsig = sigp->back;
+ icmCurve *wo; /* Backup of original */
+
+ /* Create a tag per channel */
+ bsig += j;
+
+ DBG(("Looking for backup tag '%s'\n",icm2str(icmTagSignature, bsig)));
+
+ /* See if we have a backup */
+ if ((wo = (icmCurve *)icco->read_tag(icco, bsig)) == NULL) {
+ DBG(("No backup, creating one\n"));
+ /* No, so create one */
+ if ((wo = (icmCurve *)icco->add_tag(
+ icco, bsig, icSigCurveType)) == NULL)
+ error("Failed to create tag '%s'\n", icm2str(icmTagSignature, bsig));
+ wo->flag = icmCurveSpec; /* Specified version */
+ wo->size = ro->inputEnt;
+ wo->allocate((icmBase *)wo); /* Allocate space */
+ for (i = 0; i < wo->size; i++) /* Copy the curve */
+ wo->data[i] = ro->inputTable[j * ro->inputEnt + i];
+ }
+
+ /* Create new input curve from inv cal + orginal curve */
+ for (i = 0; i < ro->inputEnt; i++) {
+ double val;
+ val = i/(ro->inputEnt-1.0);
+ if (inp)
+ val = cal->interp_ch(cal, j, val); /* Do calibration */
+ else
+ val = cal->inv_interp_ch(cal, j, val); /* Undo calibration */
+ wo->lookup_fwd(wo, &val, &val); /* Original curve */
+ ro->inputTable[j * ro->inputEnt + i] = val;
+ }
+ DBG(("Created calibrated input curve\n"));
+ }
+ } else {
+ /* Apply calibration to the output table */
+ for (j = 0; j < ro->outputChan; j++) {
+ icTagSignature bsig = sigp->back;
+ icmCurve *wo; /* Backup of original */
+
+ /* Create a tag per channel */
+ bsig += j;
+
+ DBG(("Looking for backup tag '%s'\n",icm2str(icmTagSignature, bsig)));
+ /* See if we have a backup */
+ if ((wo = (icmCurve *)icco->read_tag(icco, bsig)) == NULL) {
+ DBG(("No backup, creating one\n"));
+ /* No, so create one */
+ if ((wo = (icmCurve *)icco->add_tag(
+ icco, bsig, icSigCurveType)) == NULL)
+ error("Failed to create tag '%s'\n", icm2str(icmTagSignature, bsig));
+
+ wo->flag = icmCurveSpec; /* Specified version */
+ wo->size = ro->outputEnt;
+ wo->allocate((icmBase *)wo); /* Allocate space */
+ for (i = 0; i < wo->size; i++) /* Copy the curve */
+ wo->data[i] = ro->outputTable[j * ro->outputEnt + i];
+ }
+
+ /* Create new output curve from original + cal */
+ for (i = 0; i < ro->outputEnt; i++) {
+ double val;
+ val = i/(ro->outputEnt-1.0);
+ wo->lookup_fwd(wo, &val, &val); /* Original curve */
+ if (inp)
+ val = cal->interp_ch(cal, j, val); /* Undo calibration */
+ else
+ val = cal->interp_ch(cal, j, val); /* Do calibration */
+ ro->outputTable[j * ro->outputEnt + i] = val;
+ }
+ DBG(("Created calibrated output curve\n"));
+ }
+ }
+
+ /* XXXXTRCTag */
+ } else if (primt->ttype == icSigCurveType) {
+ icmCurve *wo, *ro = (icmCurve *)primt;
+
+ DBG(("CurveType\n"));
+
+ DBG(("Looking for backup tag '%s'\n",icm2str(icmTagSignature, sigp->back)));
+
+ /* See if we have a backup */
+ if ((wo = (icmCurve *)icco->read_tag(icco, sigp->back)) == NULL) {
+
+ DBG(("No backup, creating one\n"));
+ /* No, so create one */
+ if ((wo = (icmCurve *)icco->add_tag(
+ icco, sigp->back, icSigCurveType)) == NULL)
+ error("Failed to create tag '%s'\n", icm2str(icmTagSignature, sigp->back));
+
+ wo->flag = ro->flag;
+ wo->size = ro->size;
+ wo->allocate((icmBase *)wo); /* Allocate space */
+ for (i = 0; i < wo->size; i++) /* Copy the curve */
+ wo->data[i] = ro->data[i];
+
+ /* Change type & size of ro if necessary */
+ if (ro->flag != icmCurveSpec || wo->size < 256) {
+ ro->flag = icmCurveSpec;
+ ro->size = 256;
+ ro->allocate((icmBase *)wo); /* Allocate space */
+ }
+ }
+
+ /* Create new forward direction curve from cal + orginal curve */
+ j = sigp->chan;
+ for (i = 0; i < ro->size; i++) {
+ double val;
+ val = i/(ro->size-1.0);
+//printf("~1 Input val %f", val);
+ if (inp)
+ val = cal->interp_ch(cal, j, val); /* Input calibration */
+ else
+ val = cal->inv_interp_ch(cal, j, val); /* Inverse output calibration */
+//printf(", after inv curve %f", val);
+ wo->lookup_fwd(wo, &val, &val); /* Original curve */
+//printf(", after orig %f\n", val);
+ ro->data[i] = val;
+ }
+ DBG(("Created calibrated %s curve for chan %d\n",inp ? "input" : "output",j));
+ } else {
+ error("Tag %s is type %s we don't know how to handle",
+ icm2str(icmTagSignature, sigp->prim),
+ icm2str(icmTypeSignature, primt->ttype));
+ }
+ }
+
+ } else if (remove) {
+ int k;
+ DBG(("Removing...\n"));
+ for (k = 0; k < 2; k++) {
+ if (k == 0)
+ ssigp = sigs;
+ else if (k == 1)
+ ssigp = linksigs;
+
+ for (sigp = ssigp; sigp->prim != 0; sigp++) { /* Process each tag */
+ icmBase *backt, *primt;
+
+ DBG(("Looking for baclup tag '%s'\n",icm2str(icmTagSignature, sigp->back)));
+ if ((backt = icco->read_tag(icco, sigp->back)) == NULL)
+ continue; /* Don't have this backup tag */
+
+ DBG(("Looking for primary tag '%s'\n",icm2str(icmTagSignature, sigp->prim)));
+ if ((primt = icco->read_tag(icco, sigp->prim)) == NULL) {
+ error("Can't find primary tag %s for backup %s",
+ icm2str(icmTagSignature, sigp->prim),
+ icm2str(icmTagSignature, sigp->back));
+ }
+
+ /* icSigProfileDescriptionTag type */
+ if (primt->ttype == icSigTextDescriptionType) {
+ icmTextDescription *wo, *ro = (icmTextDescription *)primt;
+
+ DBG(("ProfileDescriptionTag\n"));
+
+ wo = (icmTextDescription *)backt;
+
+ /* Restore primary table */
+ ro->size = wo->size;
+ ro->allocate((icmBase *)ro); /* Reallocate space */
+ strcpy(ro->desc, wo->desc); /* Restore description */
+
+ /* delete backup */
+ if (icco->delete_tag(icco, sigp->back))
+ error("Failed to delete tag '%s'",icm2str(icmTagSignature, sigp->prim));
+ DBG(("Restored primary and deleted backup\n"));
+
+ /* icSigAToBXTag or icSigBToAXTag */
+ } else if (primt->ttype == icSigLut8Type
+ || primt->ttype == icSigLut16Type) {
+ icmLut *ro = (icmLut *)primt; /* Modified Lut */
+
+ if (sigp->dir) {
+ /* Restore the input table */
+ for (j = 0; j < ro->inputChan; j++) {
+ icTagSignature bsig = sigp->back;
+ icmCurve *wo; /* Backup of original */
+
+ /* Create a tag per channel */
+ bsig += j;
+
+ DBG(("Looking for backup tag '%s'\n",icm2str(icmTagSignature, bsig)));
+
+ /* See if we have a backup */
+ if ((wo = (icmCurve *)icco->read_tag(icco, bsig)) == NULL)
+ error("Can't find original table data in tag %s",
+ icm2str(icmTagSignature, sigp->back));
+
+ /* Restore primary table */
+ for (i = 0; i < wo->size; i++) /* Copy the curve */
+ ro->inputTable[j * ro->inputEnt + i] = wo->data[i];
+
+ /* delete backup */
+ if (icco->delete_tag(icco, bsig))
+ error("Failed to delete tag '%s'",icm2str(icmTagSignature, bsig));
+ DBG(("Restored primary and deleted backup\n"));
+ }
+ } else {
+ /* Restore the output table */
+ for (j = 0; j < ro->outputChan; j++) {
+ icTagSignature bsig = sigp->back;
+ icmCurve *wo; /* Backup of original */
+
+ /* Create a tag per channel */
+ bsig += j;
+
+ DBG(("Looking for backup tag '%s'\n",icm2str(icmTagSignature, bsig)));
+
+ /* See if we have a backup */
+ if ((wo = (icmCurve *)icco->read_tag(icco, bsig)) == NULL)
+ error("Can't find original table data in tag %s",
+ icm2str(icmTagSignature, sigp->back));
+
+ /* Restore primary table */
+ for (i = 0; i < wo->size; i++) /* Copy the curve */
+ ro->outputTable[j * ro->outputEnt + i] = wo->data[i];
+
+ /* delete backup */
+ if (icco->delete_tag(icco, bsig))
+ error("Failed to delete tag '%s'",icm2str(icmTagSignature, bsig));
+ DBG(("Restored primary and deleted backup\n"));
+ }
+ }
+
+ /* XXXXTRCTag */
+ } else if (primt->ttype == icSigCurveType) {
+ icmCurve *wo, *ro = (icmCurve *)primt;
+
+ DBG(("CurveType\n"));
+ DBG(("Looking for backup tag '%s'\n",icm2str(icmTagSignature, sigp->back)));
+
+ /* See if we have a backup */
+ wo = (icmCurve *)backt;
+
+ /* Restore primary table */
+ ro->flag = wo->flag;
+ ro->size = wo->size;
+ ro->allocate((icmBase *)ro); /* Allocate space */
+ for (i = 0; i < wo->size; i++) /* Copy the curve */
+ ro->data[i] = wo->data[i];
+
+ /* delete backup */
+ if (icco->delete_tag(icco, sigp->back))
+ error("Failed to delete tag '%s'",icm2str(icmTagSignature, sigp->back));
+ DBG(("Restored primary and deleted backup\n"));
+
+ } else {
+ error("Tag %s is type %s we don't know how to handle",
+ icm2str(icmTagSignature, sigp->prim),
+ icm2str(icmTypeSignature, primt->ttype));
+ }
+ }
+ }
+ }
+ }
+ /* ======================================= */
+
+ if (apply || remove) {
+ /* Open up the other profile for writing */
+ if ((wr_fp = new_icmFileStd_name(out_name,"w")) == NULL)
+ error ("Can't open file '%s'",out_name);
+
+ if ((rv = icco->write(icco,wr_fp,0)) != 0)
+ error ("Write file: %d, %s",rv,icco->err);
+ wr_fp->del(wr_fp);
+ }
+
+ if (cal != NULL)
+ cal->del(cal);
+ icco->del(icco);
+
+ if (found == 1)
+ return 1;
+ return 0;
+}
+
diff --git a/profile/cb2ti3.c b/profile/cb2ti3.c
new file mode 100644
index 0000000..4fc7866
--- /dev/null
+++ b/profile/cb2ti3.c
@@ -0,0 +1,244 @@
+
+/*
+ * Argyll Color Correction System
+ *
+ * Read in the device data from Colorblind device files,
+ * and convert it into a .ti3 CGATs format suitable for
+ * the Argyll CMM.
+ *
+ * Derived from kodak2cgats.c
+ * Author: Graeme W. Gill
+ * Date: 16/11/00
+ *
+ * Copyright 2000, 2010, Graeme W. Gill
+ *
+ * This material is licenced under the GNU AFFERO GENERAL PUBLIC LICENSE Version 3 :-
+ * see the License.txt file for licencing details.
+ */
+
+#define VERSION "1.0"
+
+/* TTBD
+ */
+
+#undef DEBUG
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <math.h>
+#include <sys/types.h>
+#include <time.h>
+#include <string.h>
+#include <stdarg.h>
+#include "copyright.h"
+#include "aconfig.h"
+#include "numlib.h"
+#include "cgats.h"
+
+void
+usage(void) {
+ fprintf(stderr,"Convert Colorblind raw device profile data to Argyll data, Version %s\n",ARGYLL_VERSION_STR);
+ fprintf(stderr,"Author: Graeme W. Gill, licensed under the AGPL Version 3\n");
+ fprintf(stderr,"usage: cb2ti3 [-v] [-l limit] infile outfile\n");
+ fprintf(stderr," -v Verbose mode\n");
+ fprintf(stderr," -l limit Set inklimit in .ti3 file\n");
+ fprintf(stderr," infile Base name for input.CMY and input.nCIE file\n");
+ fprintf(stderr," outfile Base name for output.ti3 file\n");
+ exit(1);
+ }
+
+int main(int argc, char *argv[])
+{
+ int i;
+ int fa,nfa; /* current argument we're looking at */
+ int verb = 0;
+ static char tarname[200] = { 0 }; /* Input .CMY file */
+ static char inname[200] = { 0 }; /* Input .nCIE file */
+ static char outname[200] = { 0 }; /* Output cgats .ti3 file base name */
+ cgats *cmy; /* Input RGB reference file */
+ int f_id1, f_c, f_m, f_y; /* Field indexes */
+ cgats *ncie; /* Inpit CIE readings file */
+ int f_id2, f_xx, f_yy, f_zz; /* Field indexes */
+ cgats *ocg; /* output cgats structure */
+ time_t clk = time(0);
+ struct tm *tsp = localtime(&clk);
+ char *atm = asctime(tsp); /* Ascii time */
+ int npat = 0; /* Number of patches */
+
+ error_program = "cb2ti3";
+
+ if (argc <= 1)
+ usage();
+
+ /* Process the arguments */
+ for(fa = 1;fa < argc;fa++)
+ {
+ nfa = fa; /* skip to nfa if next argument is used */
+ if (argv[fa][0] == '-') /* Look for any flags */
+ {
+ char *na = NULL; /* next argument after flag, null if none */
+
+ if (argv[fa][2] != '\000')
+ na = &argv[fa][2]; /* next is directly after flag */
+ else
+ {
+ if ((fa+1) < argc)
+ {
+ if (argv[fa+1][0] != '-')
+ {
+ nfa = fa + 1;
+ na = argv[nfa]; /* next is seperate non-flag argument */
+ }
+ }
+ }
+
+ if (argv[fa][1] == '?')
+ usage();
+
+ else if (argv[fa][1] == 'v' || argv[fa][1] == 'V')
+ verb = 1;
+ else
+ usage();
+ }
+ else
+ break;
+ }
+
+ /* Get the file name argument */
+ if (fa >= argc || argv[fa][0] == '-') usage();
+
+ strcpy(inname,argv[fa]);
+ strcpy(tarname,argv[fa++]);
+ strcat(inname,".CMY");
+ strcat(tarname,".nCIE");
+
+ if (fa >= argc || argv[fa][0] == '-') usage();
+ strcpy(outname, argv[fa++]);
+ strcat(outname,".ti3");
+
+ /* Open up the Input CMY reference file */
+ cmy = new_cgats(); /* Create a CGATS structure */
+ cmy->add_other(cmy, "CBTA"); /* Colorblind Target file */
+ if (cmy->read_name(cmy, inname))
+ error ("Read: Can't open file '%s'",inname);
+ if (cmy->ntables == 0 || cmy->t[0].tt != tt_other || cmy->t[0].oi != 0)
+ error ("Input file isn't a 'CBTA' format file");
+ if (cmy->ntables != 1)
+ fprintf(stderr,"Input file '%s' doesn't contain exactly one table",inname);
+
+ if ((npat = cmy->t[0].nsets) <= 0)
+ error("No patches");
+
+ if ((f_id1 = cmy->find_field(cmy, 0, "SAMPLE_ID")) < 0)
+ error("Input file doesn't contain field SAMPLE_ID");
+ if (cmy->t[0].ftype[f_id1] != nqcs_t)
+ error("Field SAMPLE_ID is wrong type");
+
+ if ((f_c = cmy->find_field(cmy, 0, "C")) < 0)
+ error("Input file doesn't contain field C");
+ if (cmy->t[0].ftype[f_c] != r_t)
+ error("Field C is wrong type");
+
+ if ((f_m = cmy->find_field(cmy, 0, "M")) < 0)
+ error("Input file doesn't contain field M");
+ if (cmy->t[0].ftype[f_m] != r_t)
+ error("Field M is wrong type");
+
+ if ((f_y = cmy->find_field(cmy, 0, "Y")) < 0)
+ error("Input file doesn't contain field Y");
+ if (cmy->t[0].ftype[f_y] != r_t)
+ error("Field Y is wrong type");
+
+ /* Open up the input nCIE device data file */
+ ncie = new_cgats(); /* Create a CGATS structure */
+ ncie->add_other(ncie, "CBPR"); /* Colorblind Printer Response file */
+ if (ncie->read_name(ncie, tarname))
+ error ("Read: Can't open file '%s'",tarname);
+ if (ncie->ntables == 0 || ncie->t[0].tt != tt_other || ncie->t[0].oi != 0)
+ error ("Input file isn't a 'CBTA' format file");
+ if (ncie->ntables != 1)
+ fprintf(stderr,"Input file '%s' doesn't contain exactly one table",tarname);
+
+ if (npat != ncie->t[0].nsets)
+ error("Number of patches doesn't match");
+
+ if ((f_id2 = ncie->find_field(ncie, 0, "SAMPLE_ID")) < 0)
+ error("Input file doesn't contain field SAMPLE_ID");
+ if (ncie->t[0].ftype[f_id2] != nqcs_t)
+ error("Field SAMPLE_ID is wrong type");
+
+ if ((f_xx = ncie->find_field(ncie, 0, "XYZ_X")) < 0)
+ error("Input file doesn't contain field XYZ_X");
+ if (ncie->t[0].ftype[f_xx] != r_t)
+ error("Field XYZ_X is wrong type");
+
+ if ((f_yy = ncie->find_field(ncie, 0, "XYZ_Y")) < 0)
+ error("Input file doesn't contain field XYZ_Y");
+ if (ncie->t[0].ftype[f_yy] != r_t)
+ error("Field XYZ_Y is wrong type");
+
+ if ((f_zz = ncie->find_field(ncie, 0, "XYZ_Z")) < 0)
+ error("Input file doesn't contain field XYZ_Z");
+ if (ncie->t[0].ftype[f_zz] != r_t)
+ error("Field XYZ_Z is wrong type");
+
+ /* Setup output cgats file */
+ ocg = new_cgats(); /* Create a CGATS structure */
+ ocg->add_other(ocg, "CTI3"); /* our special type is Calibration Target Information 3 */
+ ocg->add_table(ocg, tt_other, 0); /* Start the first table */
+
+ ocg->add_kword(ocg, 0, "DESCRIPTOR", "Argyll Calibration Target chart information 3",NULL);
+ ocg->add_kword(ocg, 0, "ORIGINATOR", "Argyll target", NULL);
+ atm[strlen(atm)-1] = '\000'; /* Remove \n from end */
+ ocg->add_kword(ocg, 0, "CREATED",atm, NULL);
+ ocg->add_kword(ocg, 0, "DEVICE_CLASS","OUTPUT", NULL); /* What sort of device this is */
+
+ /* Fields we want */
+ ocg->add_field(ocg, 0, "SAMPLE_ID", nqcs_t);
+
+ ocg->add_field(ocg, 0, "RGB_R", r_t);
+ ocg->add_field(ocg, 0, "RGB_G", r_t);
+ ocg->add_field(ocg, 0, "RGB_B", r_t);
+ ocg->add_kword(ocg, 0, "COLOR_REP","RGB_XYZ", NULL);
+ ocg->add_field(ocg, 0, "XYZ_X", r_t);
+ ocg->add_field(ocg, 0, "XYZ_Y", r_t);
+ ocg->add_field(ocg, 0, "XYZ_Z", r_t);
+
+ /* Write out the patch info to the output CGATS file */
+ for (i = 0; i < npat; i++) {
+ char id[100];
+ double rgb[3];
+ double xyz[3];
+
+ if (strcmp(((char *)cmy->t[0].fdata[i][f_id1]),
+ ((char *)ncie->t[0].fdata[i][f_id2])) != 0) {
+ error("Patch label mismatch, patch %d, '%s' != '%s'\n",
+ i, ((char *)cmy->t[0].fdata[i][f_id1]),
+ ((char *)ncie->t[0].fdata[i][f_id2]));
+ }
+
+ rgb[0] = 100.0 - *((double *)cmy->t[0].fdata[i][f_c]); /* Convert to RGB */
+ rgb[1] = 100.0 - *((double *)cmy->t[0].fdata[i][f_m]);
+ rgb[2] = 100.0 - *((double *)cmy->t[0].fdata[i][f_y]);
+
+ xyz[0] = *((double *)ncie->t[0].fdata[i][f_xx]);
+ xyz[1] = *((double *)ncie->t[0].fdata[i][f_yy]);
+ xyz[2] = *((double *)ncie->t[0].fdata[i][f_zz]);
+
+ sprintf(id, "%d", i+1);
+ ocg->add_set(ocg, 0, id, rgb[0], rgb[1], rgb[2],
+ xyz[0], xyz[1], xyz[2]);
+ }
+
+ if (ocg->write_name(ocg, outname))
+ error("Write error : %s",ocg->err);
+
+ ncie->del(ncie); /* Clean up */
+ cmy->del(cmy);
+ ocg->del(ocg);
+
+ return 0;
+}
+
+
+
diff --git a/profile/colprof.c b/profile/colprof.c
new file mode 100644
index 0000000..99e02cf
--- /dev/null
+++ b/profile/colprof.c
@@ -0,0 +1,1116 @@
+/*
+ * Argyll Color Correction System
+ * Color Device profile generator.
+ *
+ * Author: Graeme W. Gill
+ * Date: 15/2/97
+ *
+ * Copyright 1996-2011 Graeme W. Gill
+ * All rights reserved.
+ *
+ * This material is licenced under the GNU AFFERO GENERAL PUBLIC LICENSE Version 3 :-
+ * see the License.txt file for licencing details.
+ */
+
+/*
+ * This program takes in the scattered test chart
+ * points, and interpolates them into a gridded
+ * forward ICC device profile, as well as creating
+ * backward conversions based on the forward grid.
+ *
+ * Preview profiles are not currently generated.
+ *
+ * The gamut cLUT should be implemented with xicc/rspl
+ */
+
+/*
+ * TTBD:
+ * Should allow ICC Device attributes to be set.
+ *
+ * Add Argyll private tag to record ink limit etc. to automate link parameters.
+ * Estimate ink limit from B2A tables if no private tag ?
+ * Add used option for black relative
+ * Add used option for separate high res reverse tables
+ * Fix 400% assumptions for > 4 color devices ?
+ *
+ * Should allow creating profiles from .MPP directly for <= 4 dev channels.
+ * Should allow creating profiles from existing ICC profiles (deprecate revfix ?)
+ *
+ * Should allow creating profiles >4 channels by providing .MPP for input,
+ * dev link .icm for psudo-dev to device & .ti3 for Pseudo-dev to PCS.
+ * Note gamut should come from psudo-dev to PCS.
+ */
+
+#undef DEBUG
+#undef DO_TIME /* Time the operation */
+
+#define verbo stdout
+
+#include <stdio.h>
+#include <string.h>
+#include <time.h>
+#include "copyright.h"
+#include "aconfig.h"
+#include "numlib.h"
+#include "cgats.h"
+#include "xicc.h"
+#include "prof.h"
+
+#define DEFAVGDEV 0.5 /* Default average deviation percentage */
+ /* This equates to a uniform added error of +/- 1% */
+
+/*
+
+ Flags used:
+
+ ABCDEFGHIJKLMNOPQRSTUVWXYZ
+ upper . .. . ... .. .... .
+ lower .... .. . .. ......... .
+
+*/
+
+void usage(char *diag, ...) {
+ int i;
+ fprintf(stderr,"Create ICC profile, Version %s\n",ARGYLL_VERSION_STR);
+ fprintf(stderr,"Author: Graeme W. Gill, licensed under the AGPL Version 3\n");
+ if (diag != NULL) {
+ va_list args;
+ fprintf(stderr," Diagnostic: ");
+ va_start(args, diag);
+ vfprintf(stderr, diag, args);
+ va_end(args);
+ fprintf(stderr,"\n");
+ }
+ fprintf(stderr,"usage: %s [-options] inoutfile\n",error_program);
+ fprintf(stderr," -v Verbose mode\n");
+ fprintf(stderr," -A manufacturer Manufacturer description string\n");
+ fprintf(stderr," -M model Model description string\n");
+ fprintf(stderr," -D description Profile Description string (Default \"inoutfile\")\n");
+ fprintf(stderr," -C copyright Copyright string\n");
+ fprintf(stderr," -Z tmnb Attributes: Transparency, Matte, Negative, BlackAndWhite\n");
+ fprintf(stderr," -Z prsa Default intent: Perceptual, Rel. Colorimetric, Saturation, Abs. Colorimetric\n");
+
+ fprintf(stderr," -q lmhu Quality - Low, Medium (def), High, Ultra\n");
+// fprintf(stderr," -q fmsu Speed - Fast, Medium (def), Slow, Ultra Slow\n");
+ fprintf(stderr," -b [lmhun] Low quality B2A table - or specific B2A quality or none for input device\n");
+// fprintf(stderr," -b [fmsun] B2A Speed - Fast, Medium, Slow, Ultra Slow, None, same as -q (def)\n");
+ fprintf(stderr," -y Verify A2B profile\n");
+ fprintf(stderr," -ni Don't create input (Device) shaper curves\n");
+ fprintf(stderr," -np Don't create input (Device) grid position curves\n");
+ fprintf(stderr," -no Don't create output (PCS) shaper curves\n");
+ fprintf(stderr," -nc Don't put the input .ti3 data in the profile\n");
+ fprintf(stderr," -k zhxr Black value target: z = zero K,\n");
+ fprintf(stderr," h = 0.5 K, x = max K, r = ramp K (def.)\n");
+ fprintf(stderr," -k p stle stpo enpo enle shape\n");
+ fprintf(stderr," stle: K level at White 0.0 - 1.0\n");
+ fprintf(stderr," stpo: start point of transition Wh 0.0 - Bk 1.0\n");
+ fprintf(stderr," enpo: End point of transition Wh 0.0 - Bk 1.0\n");
+ fprintf(stderr," enle: K level at Black 0.0 - 1.0\n");
+ fprintf(stderr," shape: 1.0 = straight, 0.0-1.0 concave, 1.0-2.0 convex\n");
+ fprintf(stderr," -K parameters Same as -k, but target is K locus rather than K value itself\n");
+ fprintf(stderr," -l tlimit override total ink limit, 0 - 400%% (default from .ti3)\n");
+ fprintf(stderr," -L klimit override black ink limit, 0 - 100%% (default from .ti3)\n");
+ fprintf(stderr," -a lxXgsmGS Algorithm type override\n");
+ fprintf(stderr," l = Lab cLUT (def.), x = XYZ cLUT, X = display XYZ cLUT + matrix\n");
+ fprintf(stderr," g = gamma+matrix, s = shaper+matrix, m = matrix only,\n");
+ fprintf(stderr," G = single gamma+matrix, S = single shaper+matrix\n");
+// Development - not supported
+// fprintf(stderr," -I ver Set ICC profile version > 2.2.0\n");
+// fprintf(stderr," ver = 4, Enable ICC V4 creation\n");
+ fprintf(stderr," -u If input profile, auto scale WP to allow extrapolation\n");
+ fprintf(stderr," -uc If input profile, clip cLUT values above WP\n");
+ fprintf(stderr," -U scale If input profile, scale media white point by scale\n");
+ fprintf(stderr," -R Restrict white <= 1.0, black and primaries to be +ve\n");
+ fprintf(stderr," -f [illum] Use Fluorescent Whitening Agent compensation [opt. simulated inst. illum.:\n");
+ fprintf(stderr," M0, M1, M2, A, C, D50 (def.), D50M2, D65, F5, F8, F10 or file.sp]\n");
+ fprintf(stderr," -i illum Choose illuminant for computation of CIE XYZ from spectral data & FWA:\n");
+ fprintf(stderr," A, C, D50 (def.), D50M2, D65, F5, F8, F10 or file.sp\n");
+ fprintf(stderr," -o observ Choose CIE Observer for spectral data:\n");
+ fprintf(stderr," 1931_2 (def), 1964_10, S&B 1955_2, shaw, J&V 1978_2\n");
+ fprintf(stderr," -r avgdev Average deviation of device+instrument readings as a percentage (default %4.2f%%)\n",DEFAVGDEV);
+/* Research options: */
+/* fprintf(stderr," -r sSMOOTH RSPL or shaper suplimental optimised smoothing factor\n"); */
+/* fprintf(stderr," -r rSMOOTH RSPL or shaper raw underlying smoothing factor\n"); */
+ fprintf(stderr," -s src%s Apply gamut mapping to output profile perceptual B2A table for given source space\n",ICC_FILE_EXT);
+ fprintf(stderr," -S src%s Apply gamut mapping to output profile perceptual and saturation B2A table\n",ICC_FILE_EXT);
+ fprintf(stderr," -nP Use colormetric source gamut to make output profile perceptual table\n");
+ fprintf(stderr," -nS Use colormetric source gamut to make output profile saturation table\n");
+ fprintf(stderr," -g src.gam Use source image gamut as well for output profile gamut mapping\n");
+ fprintf(stderr," -p absprof,... Incorporate abstract profile(s) into output tables\n");
+ fprintf(stderr," -t intent Override gamut mapping intent for output profile perceptual table:\n");
+ fprintf(stderr," -T intent Override gamut mapping intent for output profile saturation table:\n");
+ for (i = 0; ; i++) {
+ icxGMappingIntent gmi;
+ if (xicc_enum_gmapintent(&gmi, i, NULL) == icxIllegalGMIntent)
+ break;
+ fprintf(stderr," %s\n",gmi.desc);
+ }
+ fprintf(stderr," -c viewcond set input viewing conditions for output profile %s gamut mapping,\n",icxcam_description(cam_default));
+ fprintf(stderr," either an enumerated choice, or a parameter\n");
+ fprintf(stderr," -d viewcond set output viewing conditions for output profile %s gamut mapping\n",icxcam_description(cam_default));
+ fprintf(stderr," either an enumerated choice, or a parameter\n");
+ fprintf(stderr," Also sets out of gamut clipping CAM space.\n");
+ fprintf(stderr," either an enumerated choice, or a series of parameters:value changes\n");
+ for (i = 0; ; i++) {
+ icxViewCond vc;
+ if (xicc_enum_viewcond(NULL, &vc, i, NULL, 1, NULL) == -999)
+ break;
+
+ fprintf(stderr," %s\n",vc.desc);
+ }
+ fprintf(stderr," -P Create gamut gammap_p.wrl and gammap_s.wrl diagostics\n");
+ fprintf(stderr," -O outputfile Override the default output filename.\n");
+ fprintf(stderr," inoutfile Base name for input.ti3/output%s file\n",ICC_FILE_EXT);
+ exit(1);
+}
+
+int main(int argc, char *argv[]) {
+ int fa,nfa,mfa; /* current argument we're looking at */
+#ifdef DO_TIME /* Time the operation */
+ clock_t stime, ttime; /* Start and total times */
+#endif
+ int verb = 0;
+ int iquality = 1; /* A2B quality */
+ int oquality = -1; /* B2A quality same as A2B */
+ int verify = 0;
+ int noisluts = 0; /* No input shaper luts */
+ int noipluts = 0; /* No input position luts */
+ int nooluts = 0; /* No output shaper luts */
+ int nocied = 0; /* No .ti3 CIE data in profile */
+ int noptop = 0; /* Use colormetric source gamut to make perceptual table */
+ int nostos = 0; /* Use colormetric source gamut to make saturation table */
+ int gamdiag = 0; /* Make gamut mapping diagnostic wrl plots */
+ int autowpsc = 0; /* Auto scale the WP to prevent clipping above WP patch */
+ int clipovwp = 0; /* Clip cLUT values above WP */
+ int clipprims = 0; /* Clip white, black and primaries */
+ double iwpscale = -1.0; /* Input white point scale factor */
+ int doinextrap = 1; /* Sythesize extra sample points for input device cLUT */
+ int doinb2a = 1; /* Create an input device B2A table */
+ int inking = 3; /* Default K target ramp K */
+ int locus = 0; /* Default K value target */
+ double Kstle = 0.0, Kstpo = 0.0, Kenle = 0.0, Kenpo = 0.0, Kshap = 0.0;
+ int tlimit = -1; /* Total ink limit as a % */
+ int klimit = -1; /* Black ink limit as a % */
+ int fwacomp = 0; /* FWA compensation */
+ double avgdev = DEFAVGDEV/100.0; /* Average measurement deviation */
+ double smooth = 1.0; /* RSPL Smoothness factor (relative, for verification) */
+ int spec = 0; /* Use spectral data flag */
+ icxIllumeType tillum = icxIT_none; /* Target/simulated instrument illuminant */
+ xspect cust_tillum; /* Custom target/simulated illumination spectrum */
+ icxIllumeType illum = icxIT_D50; /* Spectral defaults */
+ xspect cust_illum; /* Custom illumination spectrum */
+ icxObserverType observ = icxOT_CIE_1931_2; /* The classic observer */
+ char ipname[MAXNAMEL+1] = ""; /* Input icc profile - enables gamut map */
+ char sgname[MAXNAMEL+1] = ""; /* Image source gamut name */
+ char absstring[3 * MAXNAMEL +1]; /* Storage for absnames */
+ char *absnames[3] = { NULL, NULL, NULL }; /* Abstract profile name */
+ int sepsat = 0; /* Create separate saturation B2A table */
+ icxViewCond ivc_p; /* Input Viewing Parameters for CAM */
+ icxViewCond ovc_p; /* Output Viewing Parameters for CAM (enables CAM clip) */
+ int ivc_e = -1, ovc_e = -1; /* Enumerated viewing condition */
+ icxGMappingIntent pgmi; /* default Perceptual gamut mapping intent */
+ int pgmi_set = 0; /* Set by user option */
+ icxGMappingIntent sgmi; /* default Saturation gamut mapping intent */
+ int sgmi_set = 0; /* Set by user option */
+ char baname[MAXNAMEL+1] = ""; /* Input & Output base name */
+ char inname[MAXNAMEL+1] = ""; /* Input cgats file base name */
+ char outname[MAXNAMEL+1] = ""; /* Output cgats file base name */
+ cgats *icg; /* input cgats structure */
+ int ti; /* Temporary CGATs index */
+ prof_atype ptype = prof_default; /* Default for each type of device */
+ int mtxtoo = 0; /* NZ if matrix tags should be created for Display XYZ cLUT */
+ icmICCVersion iccver = icmVersionDefault; /* ICC profile version to create */
+ profxinf xpi; /* Extra profile information */
+
+
+#ifdef DO_TIME /* Time the operation */
+ stime = clock();
+#endif /* DO_TIME */
+ error_program = argv[0];
+ check_if_not_interactive();
+ memset((void *)&xpi, 0, sizeof(profxinf)); /* Init extra profile info to defaults */
+ xpi.default_ri = icMaxEnumIntent; /* Default default */
+
+ /* Init VC overrides so that we know when the've been set */
+ ivc_p.Ev = -1;
+ ivc_p.Wxyz[0] = -1.0; ivc_p.Wxyz[1] = -1.0; ivc_p.Wxyz[2] = -1.0;
+ ivc_p.La = -1.0;
+ ivc_p.Yb = -1.0;
+ ivc_p.Lv = -1.0;
+ ivc_p.Yf = -1.0;
+ ivc_p.Fxyz[0] = -1.0; ivc_p.Fxyz[1] = -1.0; ivc_p.Fxyz[2] = -1.0;
+
+ ovc_p.Ev = -1;
+ ovc_p.Wxyz[0] = -1.0; ovc_p.Wxyz[1] = -1.0; ovc_p.Wxyz[2] = -1.0;
+ ovc_p.La = -1.0;
+ ovc_p.Yb = -1.0;
+ ovc_p.Lv = -1.0;
+ ovc_p.Yf = -1.0;
+ ovc_p.Fxyz[0] = -1.0; ovc_p.Fxyz[1] = -1.0; ovc_p.Fxyz[2] = -1.0;
+
+ xicc_enum_gmapintent(&pgmi, icxPerceptualGMIntent, NULL);
+ xicc_enum_gmapintent(&sgmi, icxSaturationGMIntent, NULL);
+
+ if (argc <= 1)
+ usage("Too few arguments, got %d expect at least %d",argc-1,1);
+
+ /* Process the arguments */
+ mfa = 1; /* Minimum final arguments */
+ for(fa = 1;fa < argc;fa++) {
+
+ nfa = fa; /* skip to nfa if next argument is used */
+ if (argv[fa][0] == '-') { /* Look for any flags */
+ char *na = NULL; /* next argument after flag, null if none */
+
+ if (argv[fa][2] != '\000')
+ na = &argv[fa][2]; /* next is directly after flag */
+ else {
+ if ((fa+1+mfa) < argc) {
+ if (argv[fa+1][0] != '-') {
+ nfa = fa + 1;
+ na = argv[nfa]; /* next is seperate non-flag argument */
+ }
+ }
+ }
+
+ if (argv[fa][1] == '?')
+ usage("Usage requested");
+
+ else if (argv[fa][1] == 'v' || argv[fa][1] == 'V')
+ verb = 1;
+
+ /* Manufacturer description string */
+ else if (argv[fa][1] == 'A') {
+ fa = nfa;
+ if (na == NULL) usage("Expect argument to manufacturer description flag -A");
+ xpi.deviceMfgDesc = na;
+ }
+
+ /* Model description string */
+ else if (argv[fa][1] == 'M') {
+ fa = nfa;
+ if (na == NULL) usage("Expect argument to model description flag -M");
+ xpi.modelDesc = na;
+ }
+
+ /* Profile Description */
+ else if (argv[fa][1] == 'D') {
+ fa = nfa;
+ if (na == NULL) usage("Expect argument to profile description flag -D");
+ xpi.profDesc = na;
+ }
+
+ /* Copyright string */
+ else if (argv[fa][1] == 'C') {
+ fa = nfa;
+ if (na == NULL) usage("Expect argument to copyright flag -C");
+ xpi.copyright = na;
+ }
+
+ /* Attribute bits */
+ else if (argv[fa][1] == 'Z') {
+ int i, j;
+
+ fa = nfa;
+ if (na == NULL) usage("Expect argument to attribute flag -Z");
+ for (j = i = 0; j == 0; i++) {
+
+ switch(na[i]) {
+
+ /* Attribute */
+ case 't':
+ case 'T':
+ xpi.transparency = 1;
+ break;
+ case 'm':
+ case 'M':
+ xpi.matte = 1;
+ break;
+ case 'n':
+ case 'N':
+ xpi.negative = 1;
+ break;
+ case 'b':
+ case 'B':
+ xpi.blackandwhite = 1;
+ break;
+
+ /* Default intent */
+ case 'p':
+ case 'P':
+ xpi.default_ri = icPerceptual;
+ break;
+ case 'r':
+ case 'R':
+ xpi.default_ri = icRelativeColorimetric;
+ break;
+ case 's':
+ case 'S':
+ xpi.default_ri = icSaturation;
+ break;
+ case 'a':
+ case 'A':
+ xpi.default_ri = icAbsoluteColorimetric;
+ break;
+ default:
+ j = 1;
+ break;
+ }
+ }
+ if (na[i-1] != '\000') usage("Unknown argument '%c' to attribute flag -Z",na[i-1]);
+ }
+
+ /* Quality */
+ else if (argv[fa][1] == 'q' || argv[fa][1] == 'Q') {
+ fa = nfa;
+// if (na == NULL) usage("Expect argument to quality flag -q");
+ if (na == NULL) usage("Expect argument to speed flag -q");
+ switch (na[0]) {
+ case 'f': /* Fast */
+ case 'l':
+ case 'L':
+ iquality = 0;
+ break;
+ case 'm': /* Medium */
+ case 'M':
+ iquality = 1;
+ break;
+ case 's': /* Slow */
+ case 'h':
+ case 'H':
+ iquality = 2;
+ break;
+ case 'u': /* Ultra Slow */
+ case 'U':
+ iquality = 3;
+ break;
+ default:
+ usage("Unknown argument '%c' to quality flag -q",na[0]);
+// usage("Unknown argument '%c' to speed flag -q",na[0]);
+ }
+ }
+ else if (argv[fa][1] == 'b') {
+ if (na != NULL) { /* Got a B2A quaiity */
+ fa = nfa;
+ switch (na[0]) {
+ case 'f': /* Fast */
+ case 'l':
+ case 'L':
+ oquality = 0;
+ break;
+ case 'm': /* Medium */
+ case 'M':
+ oquality = 1;
+ break;
+ case 's': /* Slow */
+ case 'h':
+ case 'H':
+ oquality = 2;
+ break;
+ case 'u': /* Ultra Slow */
+ case 'U':
+ oquality = 3;
+ break;
+ case 'n': /* No B2A for input device */
+ case 'N':
+ oquality = -2;
+ doinb2a = 0;
+ break;
+ default:
+ usage("Unknown argument '%c' to quality flag -q",na[0]);
+ }
+ } else
+ oquality = 0;
+ }
+
+ else if (argv[fa][1] == 'B') {
+ oquality = -2;
+ doinb2a = 0;
+ }
+
+ else if (argv[fa][1] == 'y' || argv[fa][1] == 'Y')
+ verify = 1;
+
+ /* Disable input or output luts */
+ else if (argv[fa][1] == 'n' || argv[fa][1] == 'N') {
+ fa = nfa;
+ if (na == NULL) { /* Backwards compatible */
+ nooluts = 1;
+ } else {
+ if (na[0] == 'i')
+ noisluts = 1;
+ else if (na[0] == 'p')
+ noipluts = 1;
+ else if (na[0] == 'o')
+ nooluts = 1;
+ else if (na[0] == 'c')
+ nocied = 1;
+ else if (na[0] == 'P')
+ noptop = 1;
+ else if (na[0] == 'S')
+ nostos = 1;
+ else
+ usage("Unknown argument '%c' to flag -n",na[0]);
+ }
+ }
+
+ else if (argv[fa][1] == 'u') {
+ autowpsc = 1;
+ clipovwp = 0;
+ if (argv[fa][2] == 'c') {
+ autowpsc = 0;
+ clipovwp = 1;
+ } else if (argv[fa][2] != '\000') {
+ usage("Unknown flag '%c' after -u",argv[fa][2]);
+ }
+ }
+ else if (argv[fa][1] == 'U') {
+ fa = nfa;
+ if (na == NULL) usage("Expected argument to input white point scale flag -U");
+ iwpscale = atof(na);
+ if (iwpscale < 0.0 || iwpscale > 100.0)
+ usage("Argument '%s' to flag -U out of range",na);
+ }
+ /* Clip primaries */
+ else if (argv[fa][1] == 'R') {
+ clipprims = 1;
+ }
+
+ /* Inking rule */
+ else if (argv[fa][1] == 'k' || argv[fa][1] == 'K') {
+ fa = nfa;
+ if (na == NULL) usage("Expect argument to inking flag -k");
+ if (argv[fa][1] == 'k')
+ locus = 0; /* Use K value target */
+ else
+ locus = 1; /* Use K locus target */
+ switch (na[0]) {
+ case 'z':
+ case 'Z':
+ inking = 0; /* Use minimum k */
+ break;
+ case 'h':
+ case 'H':
+ inking = 1; /* Use 0.5 k */
+ break;
+ case 'x':
+ case 'X':
+ inking = 2; /* Use maximum K */
+ break;
+ case 'r':
+ case 'R':
+ inking = 3; /* Use ramping K */
+ break;
+ case 'p':
+ case 'P':
+ inking = 4; /* Use parameter curve */
+ ++fa;
+ if (fa >= argc) usage("Too few arguments to inking flag -kp");
+ Kstle = atof(argv[fa]);
+
+ ++fa;
+ if (fa >= argc) usage("Too few arguments to inking flag -kp");
+ Kstpo = atof(argv[fa]);
+
+ ++fa;
+ if (fa >= argc || argv[fa][0] == '-') usage("Too few arguments to inking flag -kp");
+ Kenpo = atof(argv[fa]);
+
+ ++fa;
+ if (fa >= argc) usage("Too few arguments to inking flag -kp");
+ Kenle = atof(argv[fa]);
+
+ ++fa;
+ if (fa >= argc || argv[fa][0] == '-') usage("Too few arguments to inking flag -kp");
+ Kshap = atof(argv[fa]);
+ break;
+ default:
+ usage("Unknown inking rule (-k) argument '%c'",na[0]);
+ }
+ }
+
+ /* Total Ink Limit */
+ else if (argv[fa][1] == 'l') {
+ fa = nfa;
+ if (na == NULL) usage("Expected argument to total ink limit flag -l");
+ tlimit = atoi(na);
+ }
+
+ /* Black Ink Limit */
+ else if (argv[fa][1] == 'L') {
+ fa = nfa;
+ if (na == NULL) usage("Expected argument to black ink limit flag -L");
+ klimit = atoi(na);
+ }
+
+ /* Algorithm type */
+ else if (argv[fa][1] == 'a' || argv[fa][1] == 'A') {
+ fa = nfa;
+ if (na == NULL) usage("Expect argument to algorithm flag -a");
+ switch (na[0]) {
+ case 'l':
+ case 'L':
+ ptype = prof_clutLab;
+ break;
+ case 'X':
+ mtxtoo = 1;
+ /* Fall though */
+ case 'x':
+ ptype = prof_clutXYZ;
+ break;
+ case 'g':
+ ptype = prof_gammat;
+ break;
+ case 'G':
+ ptype = prof_gam1mat;
+ break;
+ case 's':
+ ptype = prof_shamat;
+ break;
+ case 'S':
+ ptype = prof_sha1mat;
+ break;
+ case 'm':
+ ptype = prof_matonly;
+ break;
+ default:
+ usage("Unknown argument '%c' to algorithm flag -a",na[0] );
+ }
+ }
+ /* Profile version */
+ else if (argv[fa][1] == 'I') {
+ fa = nfa;
+ if (na == NULL) usage("Expect argument to version flag -I");
+ switch (na[0]) {
+ case '4':
+ iccver = icmVersion4_1;
+ break;
+ default:
+ usage("Unknown argument '%c' to version flag -I",na[0] );
+ }
+ }
+
+ /* FWA compensation */
+ else if (argv[fa][1] == 'f') {
+ fwacomp = 1;
+
+ if (na != NULL) { /* Argument is present - target/simulated instr. illum. */
+ fa = nfa;
+ if (strcmp(na, "A") == 0
+ || strcmp(na, "M0") == 0) {
+ spec = 1;
+ tillum = icxIT_A;
+ } else if (strcmp(na, "C") == 0) {
+ spec = 1;
+ tillum = icxIT_C;
+ } else if (strcmp(na, "D50") == 0
+ || strcmp(na, "M1") == 0) {
+ spec = 1;
+ tillum = icxIT_D50;
+ } else if (strcmp(na, "D50M2") == 0
+ || strcmp(na, "M2") == 0) {
+ spec = 1;
+ tillum = icxIT_D50M2;
+ } else if (strcmp(na, "D65") == 0) {
+ spec = 1;
+ tillum = icxIT_D65;
+ } else if (strcmp(na, "F5") == 0) {
+ spec = 1;
+ tillum = icxIT_F5;
+ } else if (strcmp(na, "F8") == 0) {
+ spec = 1;
+ tillum = icxIT_F8;
+ } else if (strcmp(na, "F10") == 0) {
+ spec = 1;
+ tillum = icxIT_F10;
+ } else { /* Assume it's a filename */
+ spec = 1;
+ tillum = icxIT_custom;
+ if (read_xspect(&cust_tillum, na) != 0)
+ usage("Failed to read custom target illuminant spectrum in file '%s'",na);
+ }
+ }
+ }
+
+ /* Spectral Illuminant type */
+ else if (argv[fa][1] == 'i') {
+ fa = nfa;
+ if (na == NULL) usage("Expect argument to illuminant flag -i");
+ if (strcmp(na, "A") == 0) {
+ spec = 1;
+ illum = icxIT_A;
+ } else if (strcmp(na, "C") == 0) {
+ spec = 1;
+ illum = icxIT_C;
+ } else if (strcmp(na, "D50") == 0) {
+ spec = 1;
+ illum = icxIT_D50;
+ } else if (strcmp(na, "D50M2") == 0) {
+ spec = 1;
+ illum = icxIT_D50M2;
+ } else if (strcmp(na, "D65") == 0) {
+ spec = 1;
+ illum = icxIT_D65;
+ } else if (strcmp(na, "F5") == 0) {
+ spec = 1;
+ illum = icxIT_F5;
+ } else if (strcmp(na, "F8") == 0) {
+ spec = 1;
+ illum = icxIT_F8;
+ } else if (strcmp(na, "F10") == 0) {
+ spec = 1;
+ illum = icxIT_F10;
+ } else { /* Assume it's a filename */
+ spec = 1;
+ illum = icxIT_custom;
+ if (read_xspect(&cust_illum, na) != 0)
+ usage("Failed to read custom illuminant spectrum in file '%s'",na);
+ }
+ }
+
+ /* Spectral Observer type */
+ else if (argv[fa][1] == 'o') {
+ fa = nfa;
+ if (na == NULL) usage("Expect argument to observer flag -o");
+ if (strcmp(na, "1931_2") == 0) { /* Classic 2 degree */
+ spec = 1;
+ observ = icxOT_CIE_1931_2;
+ } else if (strcmp(na, "1964_10") == 0) { /* Classic 10 degree */
+ spec = 1;
+ observ = icxOT_CIE_1964_10;
+ } else if (strcmp(na, "1955_2") == 0) { /* Stiles and Burch 1955 2 degree */
+ spec = 1;
+ observ = icxOT_Stiles_Burch_2;
+ } else if (strcmp(na, "1978_2") == 0) { /* Judd and Voss 1978 2 degree */
+ spec = 1;
+ observ = icxOT_Judd_Voss_2;
+ } else if (strcmp(na, "shaw") == 0) { /* Shaw and Fairchilds 1997 2 degree */
+ spec = 1;
+ observ = icxOT_Shaw_Fairchild_2;
+ } else
+ usage("Unrecognised argument '%s' to observer flag -o",na);
+ }
+
+
+ /* Average Deviation percentage */
+ else if (argv[fa][1] == 'r') {
+ fa = nfa;
+ if (na == NULL) usage("Expected argument to average deviation flag -r");
+ if (na[0] == 's') { /* (relative, for verification) */
+ smooth = atof(na+1);
+ if (smooth < 0.0)
+ usage("Optimised smoothing factor argument to '-rs' must be over 0.0");
+ } else if (na[0] == 'r') { /* (absolute, for testing) */
+ smooth = atof(na+1);
+ if (smooth < 0.0)
+ usage("Raw smoothing factor argument to '-rr' must be over 0.0");
+ smooth = -smooth; /* Signal raw factor */
+ } else {
+ avgdev = 0.01 * atof(na);
+ if (avgdev < 0.0 || avgdev > 1.0)
+ usage("Average Deviation argument must be between 0.0 and 100.0");
+ }
+ }
+
+ /* Percetual Source Gamut and Perceptual/Saturation Gamut Maping mode enable */
+ else if (argv[fa][1] == 's' || argv[fa][1] == 'S') {
+ if (argv[fa][1] == 'S')
+ sepsat = 1;
+ if (na == NULL)
+ usage("Unrecognised argument to source gamut flag -%c",argv[fa][1]);
+
+ fa = nfa;
+ strncpy(ipname,na,MAXNAMEL); ipname[MAXNAMEL] = '\000';
+ }
+
+ /* Source image gamut */
+ else if (argv[fa][1] == 'g' || argv[fa][1] == 'G') {
+ if (na == NULL)
+ usage("Unrecognised argument to source image gamut flag -g",argv[fa][1]);
+ fa = nfa;
+ strncpy(sgname,na,MAXNAMEL); sgname[MAXNAMEL] = '\000';
+ }
+
+ /* Abstract profile */
+ else if (argv[fa][1] == 'p') {
+ char *f1 = NULL, *f2 = NULL;
+ if (na == NULL) usage("Expected abstract profile filename after -p");
+ fa = nfa;
+ strncpy(absstring,na,MAXNAMEL*3); absstring[MAXNAMEL*3] = '\000';
+ if ((f1 = strchr(absstring, ',')) == NULL) { /* Only one profile */
+ absnames[2] = absnames[1] = absnames[0] = absstring; /* Duplicate */
+ } else { /* At least one comma */
+ *f1++ = '\000';
+ if ((f2 = strchr(f1, ',')) != NULL) /* Two commas */
+ *f2++ = '\000';
+ if (*absstring != '\000')
+ absnames[0] = absstring;
+ if (*f1 != '\000')
+ absnames[1] = f1;
+ if (f2 != NULL && *f2 != '\000')
+ absnames[2] = f2;
+ }
+ }
+
+ /* Perceptual Mapping intent override */
+ else if (argv[fa][1] == 't') {
+ fa = nfa;
+ if (na == NULL) usage("Expect argument to perceptul intent override flag -t");
+ if (xicc_enum_gmapintent(&pgmi, icxNoGMIntent, na) == icxIllegalGMIntent)
+ usage("Unrecognised intent '%s' to perceptual override flag -t",na);
+ pgmi_set = 1;
+ }
+
+ /* Saturation Mapping intent override */
+ else if (argv[fa][1] == 'T') {
+ fa = nfa;
+ if (na == NULL) usage("Expect argument to saturation intent override flag -T");
+ if (xicc_enum_gmapintent(&sgmi, icxNoGMIntent, na) == icxIllegalGMIntent)
+ usage("Unrecognised intent '%s' to saturation override flag -T",na);
+ sgmi_set = 1;
+ }
+
+ /* Viewing conditions */
+ else if (argv[fa][1] == 'c' || argv[fa][1] == 'd') {
+ icxViewCond *vc;
+
+ if (argv[fa][1] == 'c') {
+ vc = &ivc_p;
+ } else {
+ vc = &ovc_p;
+ }
+
+ fa = nfa;
+ if (na == NULL) usage("Viewing conditions flag (-c) needs an argument");
+ if (na[1] != ':') {
+ if (vc == &ivc_p) {
+ if ((ivc_e = xicc_enum_viewcond(NULL, NULL, -2, na, 1, NULL)) == -999)
+ usage("Urecognised Enumerated Viewing conditions '%s'",na);
+ } else {
+ if ((ovc_e = xicc_enum_viewcond(NULL, NULL, -2, na, 1, NULL)) == -999)
+ usage("Urecognised Enumerated Viewing conditions '%s'",na);
+ }
+ } else if (na[0] == 's' || na[0] == 'S') {
+ if (na[1] != ':')
+ usage("Viewing conditions (-cs) missing ':'");
+ if (na[2] == 'n' || na[2] == 'N') {
+ vc->Ev = vc_none; /* Automatic */
+ } else if (na[2] == 'a' || na[2] == 'A') {
+ vc->Ev = vc_average;
+ } else if (na[2] == 'm' || na[2] == 'M') {
+ vc->Ev = vc_dim;
+ } else if (na[2] == 'd' || na[2] == 'D') {
+ vc->Ev = vc_dark;
+ } else if (na[2] == 'c' || na[2] == 'C') {
+ vc->Ev = vc_cut_sheet;
+ } else
+ usage("Viewing condition (-c) unrecognised surround '%c'",na[2]);
+ } else if (na[0] == 'w' || na[0] == 'W') {
+ double x, y, z;
+ if (sscanf(na+1,":%lf:%lf:%lf",&x,&y,&z) == 3) {
+ vc->Wxyz[0] = x; vc->Wxyz[1] = y; vc->Wxyz[2] = z;
+ } else if (sscanf(na+1,":%lf:%lf",&x,&y) == 2) {
+ vc->Wxyz[0] = x; vc->Wxyz[1] = y;
+ } else
+ usage("Viewing condition (-cw) unrecognised white point '%s'",na+1);
+ } else if (na[0] == 'a' || na[0] == 'A') {
+ if (na[1] != ':')
+ usage("Viewing conditions (-ca) missing ':'");
+ vc->La = atof(na+2);
+ } else if (na[0] == 'b' || na[0] == 'B') {
+ if (na[1] != ':')
+ usage("Viewing conditions (-cb) missing ':'");
+ vc->Yb = atof(na+2)/100.0;
+ } else if (na[0] == 'l' || na[0] == 'L') {
+ if (na[1] != ':')
+ usage("Viewing conditions (-[cd]l) missing ':'");
+ vc->Lv = atof(na+2);
+ } else if (na[0] == 'f' || na[0] == 'F') {
+ double x, y, z;
+ if (sscanf(na+1,":%lf:%lf:%lf",&x,&y,&z) == 3) {
+ vc->Fxyz[0] = x; vc->Fxyz[1] = y; vc->Fxyz[2] = z;
+ } else if (sscanf(na+1,":%lf:%lf",&x,&y) == 2) {
+ vc->Fxyz[0] = x; vc->Fxyz[1] = y;
+ } else if (sscanf(na+1,":%lf",&x) == 1) {
+ vc->Yf = x/100.0;
+ } else
+ usage("Viewing condition (-cf) unrecognised flare '%s'",na+1);
+ } else
+ usage("Viewing condition (-c) unrecognised sub flag '%c'",na[0]);
+ }
+
+ /* Gammut mapping diagnostic plots */
+ else if (argv[fa][1] == 'P')
+ gamdiag = 1;
+
+ /* Output file name */
+ else if (argv[fa][1] == 'O') {
+ fa = nfa;
+ if (na == NULL) usage("Output filename override (-O) needs an argument");
+ strncpy(outname,na,MAXNAMEL); outname[MAXNAMEL] = '\000';
+ }
+
+ else
+ usage("Unknown flag '%c'",argv[fa][1]);
+ } else
+ break;
+ }
+
+ /* Get the file name argument */
+ if (fa >= argc || argv[fa][0] == '-') usage("Missing input .ti3 and output ICC basename");
+ strncpy(baname,argv[fa++],MAXNAMEL-4); baname[MAXNAMEL-4] = '\000';
+ if (xpi.profDesc == NULL)
+ xpi.profDesc = baname; /* Default description */
+ strcpy(inname,baname);
+ strcat(inname,".ti3");
+ if (outname[0] == '\000') { /* If not overridden */
+ strcpy(outname,baname);
+ strcat(outname,ICC_FILE_EXT);
+ }
+
+ /* Issue some errors & warnings for strange combinations */
+ if (fwacomp && spec == 0)
+ error("FWA compensation only works when viewer and/or illuminant selected");
+
+ if (pgmi_set && ipname[0] == '\000')
+ warning("-t perceptual intent override only works if -s srcprof or -S srcprof is used");
+
+ if (sgmi_set && ipname[0] == '\000')
+ warning("-T saturation intent override only works if -S srcprof is used");
+
+ if (sgmi_set && sepsat == 0) { /* Won't do much otherwise */
+ if (verb)
+ printf("Saturation intent override was set, so adding saturation intent table\n");
+ sepsat = 1;
+ }
+
+ if (gamdiag && ipname[0] == '\000')
+ warning("no gamut mapping called for, so -P will produce nothing");
+
+ if (sgname[0] != '\000' && ipname[0] == '\000')
+ warning("-g srcgam will do nothing without -s srcprof or -S srcprof");
+
+ if (oquality == -1) { /* B2A tables will be used */
+ oquality = iquality;
+ }
+
+ /* Open and look at the .ti3 profile patches file */
+ icg = new_cgats(); /* Create a CGATS structure */
+ icg->add_other(icg, "CTI3"); /* our special input type is Calibration Target Information 3 */
+ icg->add_other(icg, "CAL"); /* our special device Calibration state */
+
+ if (icg->read_name(icg, inname))
+ error("CGATS file read error : %s",icg->err);
+
+ if (icg->ntables == 0 || icg->t[0].tt != tt_other || icg->t[0].oi != 0)
+ error ("Input file isn't a CTI3 format file");
+ if (icg->ntables < 1)
+ error ("Input file doesn't contain at least one table");
+
+ /* See if CIE is actually available - some sources of .TI3 don't provide it */
+ if (!spec
+ && icg->find_field(icg, 0, "LAB_L") < 0
+ && icg->find_field(icg, 0, "XYZ_X") < 0) {
+
+ if (icg->find_kword(icg, 0, "SPECTRAL_BANDS") < 0)
+ error("Neither CIE nor spectral data found in file '%s'",inname);
+
+ /* Switch to using spectral information */
+ if (verb)
+ printf("No CIE data found, switching to spectral with standard observer & D50\n");
+ spec = 1;
+ illum = icxIT_D50;
+ observ = icxOT_CIE_1931_2;
+ }
+
+ /* If we requested spectral, check that it is available */
+ if (spec) {
+ if (icg->find_kword(icg, 0, "SPECTRAL_BANDS") < 0)
+ error ("Requested spectral interpretation when data not available");
+ }
+
+ /* read the device class, and call function to create profile. */
+ if ((ti = icg->find_kword(icg, 0, "DEVICE_CLASS")) < 0)
+ error ("Input file doesn't contain keyword DEVICE_CLASS");
+
+ if (strcmp(icg->t[0].kdata[ti],"OUTPUT") == 0) {
+ icxInk ink; /* Ink parameters */
+
+ if ((ti = icg->find_kword(icg, 0, "TOTAL_INK_LIMIT")) >= 0) {
+ int imax;
+ imax = atoi(icg->t[0].kdata[ti]);
+ if (imax > 0 && imax <= 400.0) {
+ if (tlimit > 0 && tlimit <= 400.0) { /* User has specified limit as option */
+ if (imax < tlimit) {
+ warning("Ink limit greater than original chart! (%d%% > %d%%)",tlimit,imax);
+ }
+ } else {
+ if (imax > 80.0)
+ tlimit = (int)imax - 10; /* Rule of thumb - 10% below chart maximum */
+ else
+ tlimit = (int)imax;
+ }
+ }
+ }
+
+ /* (Note that this isn't set by any of the Argyll tools currently, */
+ /* but can be set manually.) */
+ if ((ti = icg->find_kword(icg, 0, "BLACK_INK_LIMIT")) >= 0) {
+ int kmax;
+ kmax = atoi(icg->t[0].kdata[ti]);
+ if (kmax > 0 && kmax <= 100.0) {
+ if (klimit > 0 && klimit <= 100.0) { /* User has specified limit as option */
+ if (kmax < klimit) {
+ warning("Black ink limit greater than original chart! (%d%% > %d%%)",klimit,kmax);
+ }
+ } else {
+ klimit = (int)kmax;
+ }
+ }
+ }
+
+ if (tlimit >= 0 && tlimit < 400.0) {
+ if (verb)
+ printf("Total ink limit being used is %d%%\n",tlimit);
+ ink.tlimit = tlimit/100.0; /* Set a total ink limit */
+ } else {
+ if (verb)
+ printf("No total ink limit being used\n");
+ ink.tlimit = -1.0; /* Don't use a limit */
+ }
+
+ if (klimit >= 0 && klimit < 100.0) {
+ if (verb)
+ printf("Black ink limit being used is %d%%\n",klimit);
+ ink.klimit = klimit/100.0; /* Set a black ink limit */
+ } else {
+ if (verb)
+ printf("No black ink limit being used\n");
+ ink.klimit = -1.0; /* Don't use a limit */
+ }
+
+ ink.KonlyLmin = 0; /* Use normal black Lmin for locus */
+ ink.c.Ksmth = ICXINKDEFSMTH; /* default black curve smoothing */
+ ink.c.Kskew = ICXINKDEFSKEW; /* default black curve skew */
+ ink.x.Ksmth = ICXINKDEFSMTH;
+ ink.x.Kskew = ICXINKDEFSKEW;
+
+ if (inking == 0) { /* Use minimum */
+ ink.k_rule = locus ? icxKluma5 : icxKluma5k;
+ ink.c.Kstle = 0.0;
+ ink.c.Kstpo = 0.0;
+ ink.c.Kenpo = 1.0;
+ ink.c.Kenle = 0.0;
+ ink.c.Kshap = 1.0;
+ } else if (inking == 1) { /* Use 0.5 */
+ ink.k_rule = locus ? icxKluma5 : icxKluma5k;
+ ink.c.Kstle = 0.5;
+ ink.c.Kstpo = 0.0;
+ ink.c.Kenpo = 1.0;
+ ink.c.Kenle = 0.5;
+ ink.c.Kshap = 1.0;
+ } else if (inking == 2) { /* Use maximum */
+ ink.k_rule = locus ? icxKluma5 : icxKluma5k;
+ ink.c.Kstle = 1.0;
+ ink.c.Kstpo = 0.0;
+ ink.c.Kenpo = 1.0;
+ ink.c.Kenle = 1.0;
+ ink.c.Kshap = 1.0;
+ } else if (inking == 3) { /* Use ramp */
+ ink.k_rule = locus ? icxKluma5 : icxKluma5k;
+ ink.c.Kstle = 0.0;
+ ink.c.Kstpo = 0.0;
+ ink.c.Kenpo = 1.0;
+ ink.c.Kenle = 1.0;
+ ink.c.Kshap = 1.0;
+ } else { /* Use specified curve */
+ ink.k_rule = locus ? icxKluma5 : icxKluma5k;
+ ink.c.Kstle = Kstle;
+ ink.c.Kstpo = Kstpo;
+ ink.c.Kenpo = Kenpo;
+ ink.c.Kenle = Kenle;
+ ink.c.Kshap = Kshap;
+ }
+
+ if (ptype == prof_default)
+ ptype = prof_clutLab;
+ else if (ptype != prof_clutLab && ptype != prof_clutXYZ) {
+ error ("Output profile can only be a cLUT algorithm");
+ }
+
+ if (autowpsc)
+ error ("Input auto WP scale mode isn't applicable to an output device");
+ if (clipovwp)
+ error ("Input cLUT clipping above WP mode isn't applicable to an output device");
+
+ make_output_icc(ptype, 0, iccver, verb, iquality, oquality,
+ noisluts, noipluts, nooluts, nocied, noptop, nostos,
+ gamdiag, verify, clipprims, &ink, inname, outname, icg,
+ spec, tillum, &cust_tillum, illum, &cust_illum, observ, fwacomp,
+ smooth, avgdev,
+ ipname[0] != '\000' ? ipname : NULL,
+ sgname[0] != '\000' ? sgname : NULL,
+ absnames,
+ sepsat, &ivc_p, &ovc_p, ivc_e, ovc_e,
+ &pgmi, &sgmi, &xpi);
+
+ } else if (strcmp(icg->t[0].kdata[ti],"INPUT") == 0) {
+
+ if (ptype == prof_default)
+ ptype = prof_clutLab; /* For best possible quality */
+
+ if (clipovwp && ptype != prof_clutLab && ptype != prof_clutXYZ)
+ error ("Input cLUT clipping above WP mode isn't applicable to a matrix profile");
+
+ make_input_icc(ptype, iccver, verb, iquality, oquality, noisluts, noipluts, nooluts, nocied,
+ verify, autowpsc, clipovwp, iwpscale, doinb2a, doinextrap, clipprims,
+ inname, outname, icg, spec, illum, &cust_illum, observ,
+ smooth, avgdev, &xpi);
+
+ } else if (strcmp(icg->t[0].kdata[ti],"DISPLAY") == 0) {
+
+ if (autowpsc)
+ error ("Input auto WP scale mode isn't applicable to an output device");
+ if (clipovwp)
+ error ("Input cLUT clipping above WP mode isn't applicable to an output device");
+
+ if (fwacomp)
+ error ("FWA compensation isn't applicable to a display device");
+
+ if (ptype == prof_default)
+ ptype = prof_clutLab; /* ?? or should it default to prof_shamat ?? */
+
+ /* If a source gamut is provided for a Display, then a V2.4.0 profile will be created */
+ make_output_icc(ptype, mtxtoo, iccver, verb, iquality, oquality,
+ noisluts, noipluts, nooluts, nocied, noptop, nostos,
+ gamdiag, verify, clipprims, NULL, inname, outname, icg,
+ spec, icxIT_none, NULL, illum, &cust_illum, observ, 0,
+ smooth, avgdev,
+ ipname[0] != '\000' ? ipname : NULL,
+ sgname[0] != '\000' ? sgname : NULL,
+ absnames,
+ sepsat, &ivc_p, &ovc_p, ivc_e, ovc_e,
+ &pgmi, &sgmi, &xpi);
+
+ } else
+ error ("Input file keyword DEVICE_CLASS has unknown value");
+
+ icg->del(icg); /* Clean up */
+
+#ifdef DO_TIME /* Time the operation */
+ ttime = clock() - stime;
+ printf("Exectution time = %f seconds\n",(double)ttime/(double)CLOCKS_PER_SEC);
+#endif /* DO_TIME */
+
+ return 0;
+}
+
+
+
+
+
+
+
+
+
+
diff --git a/profile/example.sp b/profile/example.sp
new file mode 100644
index 0000000..08d836e
--- /dev/null
+++ b/profile/example.sp
@@ -0,0 +1,132 @@
+SPECT
+
+DESCRIPTOR "Argyll Example Spectral power/reflectance information (D50)"
+ORIGINATOR "Argyll CMS"
+CREATED "Fri Jul 06 17:49:57 2001"
+# If you want the FWA compensation to work properly, you need to specify your
+# light source down to 300nm
+KEYWORD "SPECTRAL_BANDS"
+SPECTRAL_BANDS "107"
+KEYWORD "SPECTRAL_START_NM"
+SPECTRAL_START_NM "300.000000"
+KEYWORD "SPECTRAL_END_NM"
+SPECTRAL_END_NM "830.000000"
+KEYWORD "SPECTRAL_NORM"
+SPECTRAL_NORM "100.000000"
+
+KEYWORD "SPEC_300"
+KEYWORD "SPEC_305"
+KEYWORD "SPEC_310"
+KEYWORD "SPEC_315"
+KEYWORD "SPEC_320"
+KEYWORD "SPEC_325"
+KEYWORD "SPEC_330"
+KEYWORD "SPEC_335"
+KEYWORD "SPEC_340"
+KEYWORD "SPEC_345"
+KEYWORD "SPEC_350"
+KEYWORD "SPEC_355"
+KEYWORD "SPEC_360"
+KEYWORD "SPEC_365"
+KEYWORD "SPEC_370"
+KEYWORD "SPEC_375"
+KEYWORD "SPEC_380"
+KEYWORD "SPEC_385"
+KEYWORD "SPEC_390"
+KEYWORD "SPEC_395"
+KEYWORD "SPEC_400"
+KEYWORD "SPEC_405"
+KEYWORD "SPEC_410"
+KEYWORD "SPEC_415"
+KEYWORD "SPEC_420"
+KEYWORD "SPEC_425"
+KEYWORD "SPEC_430"
+KEYWORD "SPEC_435"
+KEYWORD "SPEC_440"
+KEYWORD "SPEC_445"
+KEYWORD "SPEC_450"
+KEYWORD "SPEC_455"
+KEYWORD "SPEC_460"
+KEYWORD "SPEC_465"
+KEYWORD "SPEC_470"
+KEYWORD "SPEC_475"
+KEYWORD "SPEC_480"
+KEYWORD "SPEC_485"
+KEYWORD "SPEC_490"
+KEYWORD "SPEC_495"
+KEYWORD "SPEC_500"
+KEYWORD "SPEC_505"
+KEYWORD "SPEC_510"
+KEYWORD "SPEC_515"
+KEYWORD "SPEC_520"
+KEYWORD "SPEC_525"
+KEYWORD "SPEC_530"
+KEYWORD "SPEC_535"
+KEYWORD "SPEC_540"
+KEYWORD "SPEC_545"
+KEYWORD "SPEC_550"
+KEYWORD "SPEC_555"
+KEYWORD "SPEC_560"
+KEYWORD "SPEC_565"
+KEYWORD "SPEC_570"
+KEYWORD "SPEC_575"
+KEYWORD "SPEC_580"
+KEYWORD "SPEC_585"
+KEYWORD "SPEC_590"
+KEYWORD "SPEC_595"
+KEYWORD "SPEC_600"
+KEYWORD "SPEC_605"
+KEYWORD "SPEC_610"
+KEYWORD "SPEC_615"
+KEYWORD "SPEC_620"
+KEYWORD "SPEC_625"
+KEYWORD "SPEC_630"
+KEYWORD "SPEC_635"
+KEYWORD "SPEC_640"
+KEYWORD "SPEC_645"
+KEYWORD "SPEC_650"
+KEYWORD "SPEC_655"
+KEYWORD "SPEC_660"
+KEYWORD "SPEC_665"
+KEYWORD "SPEC_670"
+KEYWORD "SPEC_675"
+KEYWORD "SPEC_680"
+KEYWORD "SPEC_685"
+KEYWORD "SPEC_690"
+KEYWORD "SPEC_695"
+KEYWORD "SPEC_700"
+KEYWORD "SPEC_705"
+KEYWORD "SPEC_710"
+KEYWORD "SPEC_715"
+KEYWORD "SPEC_720"
+KEYWORD "SPEC_725"
+KEYWORD "SPEC_730"
+KEYWORD "SPEC_735"
+KEYWORD "SPEC_740"
+KEYWORD "SPEC_745"
+KEYWORD "SPEC_750"
+KEYWORD "SPEC_755"
+KEYWORD "SPEC_760"
+KEYWORD "SPEC_765"
+KEYWORD "SPEC_770"
+KEYWORD "SPEC_775"
+KEYWORD "SPEC_780"
+KEYWORD "SPEC_785"
+KEYWORD "SPEC_790"
+KEYWORD "SPEC_795"
+KEYWORD "SPEC_800"
+KEYWORD "SPEC_805"
+KEYWORD "SPEC_810"
+KEYWORD "SPEC_815"
+KEYWORD "SPEC_820"
+KEYWORD "SPEC_825"
+KEYWORD "SPEC_830"
+NUMBER_OF_FIELDS 107
+BEGIN_DATA_FORMAT
+SPEC_300 SPEC_305 SPEC_310 SPEC_315 SPEC_320 SPEC_325 SPEC_330 SPEC_335 SPEC_340 SPEC_345 SPEC_350 SPEC_355 SPEC_360 SPEC_365 SPEC_370 SPEC_375 SPEC_380 SPEC_385 SPEC_390 SPEC_395 SPEC_400 SPEC_405 SPEC_410 SPEC_415 SPEC_420 SPEC_425 SPEC_430 SPEC_435 SPEC_440 SPEC_445 SPEC_450 SPEC_455 SPEC_460 SPEC_465 SPEC_470 SPEC_475 SPEC_480 SPEC_485 SPEC_490 SPEC_495 SPEC_500 SPEC_505 SPEC_510 SPEC_515 SPEC_520 SPEC_525 SPEC_530 SPEC_535 SPEC_540 SPEC_545 SPEC_550 SPEC_555 SPEC_560 SPEC_565 SPEC_570 SPEC_575 SPEC_580 SPEC_585 SPEC_590 SPEC_595 SPEC_600 SPEC_605 SPEC_610 SPEC_615 SPEC_620 SPEC_625 SPEC_630 SPEC_635 SPEC_640 SPEC_645 SPEC_650 SPEC_655 SPEC_660 SPEC_665 SPEC_670 SPEC_675 SPEC_680 SPEC_685 SPEC_690 SPEC_695 SPEC_700 SPEC_705 SPEC_710 SPEC_715 SPEC_720 SPEC_725 SPEC_730 SPEC_735 SPEC_740 SPEC_745 SPEC_750 SPEC_755 SPEC_760 SPEC_765 SPEC_770 SPEC_775 SPEC_780 SPEC_785 SPEC_790 SPEC_795 SPEC_800 SPEC_805 SPEC_810 SPEC_815 SPEC_820 SPEC_825 SPEC_830
+END_DATA_FORMAT
+
+NUMBER_OF_SETS 1
+BEGIN_DATA
+0.020000 1.0300 2.0500 4.9100 7.7800 11.260 14.750 16.350 17.950 19.480 21.010 22.480 23.940 25.450 26.960 25.720 24.490 27.180 29.870 39.590 49.310 52.910 56.510 58.270 60.030 58.930 57.820 66.320 74.820 81.040 87.250 88.930 90.610 90.990 91.370 93.240 95.110 93.540 91.960 93.840 95.720 96.170 96.610 96.870 97.130 99.610 102.10 101.43 100.75 101.54 102.32 101.16 100.00 98.870 97.740 98.330 98.920 96.210 93.500 95.590 97.690 98.480 99.270 99.160 99.040 97.380 95.720 97.290 98.860 97.260 95.670 96.930 98.190 100.60 103.00 101.07 99.130 93.260 87.380 89.490 91.600 92.250 92.890 84.870 76.850 81.680 86.510 89.550 92.580 85.400 78.230 67.960 57.690 70.310 82.920 80.600 78.270 78.910 79.550 76.480 73.400 68.660 63.920 67.350 70.780 72.610 74.440
+END_DATA
diff --git a/profile/example121.sp b/profile/example121.sp
new file mode 100644
index 0000000..d139c38
--- /dev/null
+++ b/profile/example121.sp
@@ -0,0 +1,147 @@
+SPECT
+
+DESCRIPTOR "Argyll Example Spectral power/reflectance information with 121 bands from an i1 pro (Fluorescent lamp)"
+ORIGINATOR "Argyll CMS"
+CREATED "Wed Sep 12 16:45:26 2007"
+# If you want the FWA compensation to work properly, you need to specify your
+# light source down to 300nm
+
+KEYWORD "SPECTRAL_BANDS"
+SPECTRAL_BANDS "121"
+KEYWORD "SPECTRAL_START_NM"
+SPECTRAL_START_NM "350"
+KEYWORD "SPECTRAL_END_NM"
+SPECTRAL_END_NM "750"
+KEYWORD "SPECTRAL_NORM"
+SPECTRAL_NORM "100"
+
+KEYWORD "SPEC_350"
+KEYWORD "SPEC_353"
+KEYWORD "SPEC_357"
+KEYWORD "SPEC_360"
+KEYWORD "SPEC_363"
+KEYWORD "SPEC_367"
+KEYWORD "SPEC_370"
+KEYWORD "SPEC_373"
+KEYWORD "SPEC_377"
+KEYWORD "SPEC_380"
+KEYWORD "SPEC_383"
+KEYWORD "SPEC_387"
+KEYWORD "SPEC_390"
+KEYWORD "SPEC_393"
+KEYWORD "SPEC_397"
+KEYWORD "SPEC_400"
+KEYWORD "SPEC_403"
+KEYWORD "SPEC_407"
+KEYWORD "SPEC_410"
+KEYWORD "SPEC_413"
+KEYWORD "SPEC_417"
+KEYWORD "SPEC_420"
+KEYWORD "SPEC_423"
+KEYWORD "SPEC_427"
+KEYWORD "SPEC_430"
+KEYWORD "SPEC_433"
+KEYWORD "SPEC_437"
+KEYWORD "SPEC_440"
+KEYWORD "SPEC_443"
+KEYWORD "SPEC_447"
+KEYWORD "SPEC_450"
+KEYWORD "SPEC_453"
+KEYWORD "SPEC_457"
+KEYWORD "SPEC_460"
+KEYWORD "SPEC_463"
+KEYWORD "SPEC_467"
+KEYWORD "SPEC_470"
+KEYWORD "SPEC_473"
+KEYWORD "SPEC_477"
+KEYWORD "SPEC_480"
+KEYWORD "SPEC_483"
+KEYWORD "SPEC_487"
+KEYWORD "SPEC_490"
+KEYWORD "SPEC_493"
+KEYWORD "SPEC_497"
+KEYWORD "SPEC_500"
+KEYWORD "SPEC_503"
+KEYWORD "SPEC_507"
+KEYWORD "SPEC_510"
+KEYWORD "SPEC_513"
+KEYWORD "SPEC_517"
+KEYWORD "SPEC_520"
+KEYWORD "SPEC_523"
+KEYWORD "SPEC_527"
+KEYWORD "SPEC_530"
+KEYWORD "SPEC_533"
+KEYWORD "SPEC_537"
+KEYWORD "SPEC_540"
+KEYWORD "SPEC_543"
+KEYWORD "SPEC_547"
+KEYWORD "SPEC_550"
+KEYWORD "SPEC_553"
+KEYWORD "SPEC_557"
+KEYWORD "SPEC_560"
+KEYWORD "SPEC_563"
+KEYWORD "SPEC_567"
+KEYWORD "SPEC_570"
+KEYWORD "SPEC_573"
+KEYWORD "SPEC_577"
+KEYWORD "SPEC_580"
+KEYWORD "SPEC_583"
+KEYWORD "SPEC_587"
+KEYWORD "SPEC_590"
+KEYWORD "SPEC_593"
+KEYWORD "SPEC_597"
+KEYWORD "SPEC_600"
+KEYWORD "SPEC_603"
+KEYWORD "SPEC_607"
+KEYWORD "SPEC_610"
+KEYWORD "SPEC_613"
+KEYWORD "SPEC_617"
+KEYWORD "SPEC_620"
+KEYWORD "SPEC_623"
+KEYWORD "SPEC_627"
+KEYWORD "SPEC_630"
+KEYWORD "SPEC_633"
+KEYWORD "SPEC_637"
+KEYWORD "SPEC_640"
+KEYWORD "SPEC_643"
+KEYWORD "SPEC_647"
+KEYWORD "SPEC_650"
+KEYWORD "SPEC_653"
+KEYWORD "SPEC_657"
+KEYWORD "SPEC_660"
+KEYWORD "SPEC_663"
+KEYWORD "SPEC_667"
+KEYWORD "SPEC_670"
+KEYWORD "SPEC_673"
+KEYWORD "SPEC_677"
+KEYWORD "SPEC_680"
+KEYWORD "SPEC_683"
+KEYWORD "SPEC_687"
+KEYWORD "SPEC_690"
+KEYWORD "SPEC_693"
+KEYWORD "SPEC_697"
+KEYWORD "SPEC_700"
+KEYWORD "SPEC_703"
+KEYWORD "SPEC_707"
+KEYWORD "SPEC_710"
+KEYWORD "SPEC_713"
+KEYWORD "SPEC_717"
+KEYWORD "SPEC_720"
+KEYWORD "SPEC_723"
+KEYWORD "SPEC_727"
+KEYWORD "SPEC_730"
+KEYWORD "SPEC_733"
+KEYWORD "SPEC_737"
+KEYWORD "SPEC_740"
+KEYWORD "SPEC_743"
+KEYWORD "SPEC_747"
+KEYWORD "SPEC_750"
+NUMBER_OF_FIELDS 121
+BEGIN_DATA_FORMAT
+SPEC_350 SPEC_353 SPEC_357 SPEC_360 SPEC_363 SPEC_367 SPEC_370 SPEC_373 SPEC_377 SPEC_380 SPEC_383 SPEC_387 SPEC_390 SPEC_393 SPEC_397 SPEC_400 SPEC_403 SPEC_407 SPEC_410 SPEC_413 SPEC_417 SPEC_420 SPEC_423 SPEC_427 SPEC_430 SPEC_433 SPEC_437 SPEC_440 SPEC_443 SPEC_447 SPEC_450 SPEC_453 SPEC_457 SPEC_460 SPEC_463 SPEC_467 SPEC_470 SPEC_473 SPEC_477 SPEC_480 SPEC_483 SPEC_487 SPEC_490 SPEC_493 SPEC_497 SPEC_500 SPEC_503 SPEC_507 SPEC_510 SPEC_513 SPEC_517 SPEC_520 SPEC_523 SPEC_527 SPEC_530 SPEC_533 SPEC_537 SPEC_540 SPEC_543 SPEC_547 SPEC_550 SPEC_553 SPEC_557 SPEC_560 SPEC_563 SPEC_567 SPEC_570 SPEC_573 SPEC_577 SPEC_580 SPEC_583 SPEC_587 SPEC_590 SPEC_593 SPEC_597 SPEC_600 SPEC_603 SPEC_607 SPEC_610 SPEC_613 SPEC_617 SPEC_620 SPEC_623 SPEC_627 SPEC_630 SPEC_633 SPEC_637 SPEC_640 SPEC_643 SPEC_647 SPEC_650 SPEC_653 SPEC_657 SPEC_660 SPEC_663 SPEC_667 SPEC_670 SPEC_673 SPEC_677 SPEC_680 SPEC_683 SPEC_687 SPEC_690 SPEC_693 SPEC_697 SPEC_700 SPEC_703 SPEC_707 SPEC_710 SPEC_713 SPEC_717 SPEC_720 SPEC_723 SPEC_727 SPEC_730 SPEC_733 SPEC_737 SPEC_740 SPEC_743 SPEC_747 SPEC_750
+END_DATA_FORMAT
+
+NUMBER_OF_SETS 1
+BEGIN_DATA
+-0.041690 0.24392 4.3572 5.4017 6.5222 7.0710 8.6925 12.563 16.482 18.309 17.829 17.021 16.072 16.687 87.378 282.88 411.34 306.20 188.94 221.27 286.97 368.32 461.52 591.86 962.15 2078.2 2435.7 1583.7 985.14 1000.3 1013.1 1010.9 994.64 967.45 926.07 875.50 826.62 776.65 728.56 729.41 894.38 1230.5 1346.3 1108.1 831.25 636.32 518.37 473.21 477.58 492.45 491.10 457.30 399.25 348.62 326.58 367.02 707.07 1919.0 3813.6 4662.9 3291.3 1432.5 513.04 229.68 141.83 108.25 90.996 144.02 387.80 680.40 822.45 858.52 763.41 621.14 498.90 391.19 345.59 749.61 2255.0 3078.6 1993.9 866.06 750.13 785.14 718.71 498.72 228.97 128.41 127.71 158.54 217.06 231.70 191.86 166.43 164.48 146.81 124.29 112.00 109.04 115.61 129.27 146.74 143.18 117.28 87.783 80.151 141.77 286.25 374.19 308.51 156.95 59.626 32.257 32.390 32.801 33.930 35.539 37.845 36.214 31.438 23.866
+END_DATA
diff --git a/profile/invprofcheck.c b/profile/invprofcheck.c
new file mode 100644
index 0000000..0965096
--- /dev/null
+++ b/profile/invprofcheck.c
@@ -0,0 +1,733 @@
+
+/*
+ * Argyll Color Correction System
+ * Inverse profile checker.
+ *
+ * Author: Graeme W. Gill
+ * Date: 1999/11/29
+ *
+ * Copyright 1999 - 2005 Graeme W. Gill
+ * All rights reserved.
+ *
+ * This material is licenced under the GNU AFFERO GENERAL PUBLIC LICENSE Version 3 :-
+ * see the License.txt file for licencing details.
+ */
+
+/*
+ * This program takes checks the round trip errors of
+ * the colorimetric forward and inverse profile direction
+ * of an ICC profile.
+ * (Was called icc/fbtest.c)
+ */
+
+
+/* TTBD:
+ *
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <fcntl.h>
+#include <string.h>
+#include <math.h>
+#include <ctype.h>
+#include "copyright.h"
+#include "aconfig.h"
+#include "numlib.h"
+#include "icc.h"
+#include "xicc.h"
+
+/* Resolution of the sampling modes */
+#define TRES 11
+#define HTRES 27
+#define UHTRES 61
+
+/* ------------------------------------------------------- */
+/* Macros for an di or fdi dimensional counter */
+/* Declare the counter name nn, dimensions di, & count */
+
+#define DCOUNT(nn, di, start, reset, count) \
+ int nn[MAX_CHAN]; /* counter value */ \
+ int nn##_di = (di); /* Number of dimensions */ \
+ int nn##_stt = (start); /* start count value */ \
+ int nn##_rst = (reset); /* reset on carry value */ \
+ int nn##_res = (count); /* last count +1 */ \
+ int nn##_e /* dimension index */
+
+/* Set the counter value to 0 */
+#define DC_INIT(nn) \
+{ \
+ for (nn##_e = 0; nn##_e < nn##_di; nn##_e++) \
+ nn[nn##_e] = nn##_stt; \
+ nn##_e = 0; \
+}
+
+/* Increment the counter value */
+#define DC_INC(nn) \
+{ \
+ for (nn##_e = 0; nn##_e < nn##_di; nn##_e++) { \
+ nn[nn##_e]++; \
+ if (nn[nn##_e] < nn##_res) \
+ break; /* No carry */ \
+ nn[nn##_e] = nn##_rst; \
+ } \
+}
+
+/* After increment, expression is TRUE if counter is done */
+#define DC_DONE(nn) \
+ (nn##_e >= nn##_di)
+
+/* ---------------------------------------- */
+
+void usage(void) {
+ fprintf(stderr,"Check fwd to bwd relative transfer of an ICC file, Version %s\n",ARGYLL_VERSION_STR);
+ fprintf(stderr,"Author: Graeme W. Gill\n");
+ fprintf(stderr,"usage: invprofcheck [-] profile.icm\n");
+ fprintf(stderr," -v [level] verbosity level (default 1), 2 to print each DE\n");
+ fprintf(stderr," -l limit set total ink limit (estimate by default)\n");
+ fprintf(stderr," -L klimit set black channel ink limit (estimate by default)\n");
+ fprintf(stderr," -h high res test (%d)\n",HTRES);
+ fprintf(stderr," -u Ultra high res test (%d)\n",UHTRES);
+ fprintf(stderr," -R res Specific grid resolution\n");
+ fprintf(stderr," -c Show CIE94 delta E values\n");
+ fprintf(stderr," -k Show CIEDE2000 delta E values\n");
+ fprintf(stderr," -w create VRML visualisation (profile.wrl)\n");
+ fprintf(stderr," -x Use VRML axes\n");
+ fprintf(stderr," -e Color vectors acording to delta E\n");
+ fprintf(stderr," profile.icm Profile to check\n");
+ exit(1);
+}
+
+FILE *start_vrml(char *name, int doaxes);
+void start_line_set(FILE *wrl);
+void add_vertex(FILE *wrl, double pp[3]);
+void make_lines(FILE *wrl, int ppset);
+void make_de_lines(FILE *wrl);
+void end_vrml(FILE *wrl);
+
+#if defined(__IBMC__) && defined(_M_IX86)
+void bug_workaround(int *co) { }; /* Workaround optimiser bug */
+#endif
+
+int
+main(
+ int argc,
+ char *argv[]
+) {
+ int fa,nfa; /* argument we're looking at */
+ int verb = 0;
+ int cie94 = 0;
+ int cie2k = 0;
+ int dovrml = 0;
+ int doaxes = 0;
+ int dodecol = 0;
+ char in_name[MAXNAMEL+1];
+ char out_name[MAXNAMEL+1], *xl; /* VRML name */
+ icmFile *rd_fp;
+ icc *icco;
+ int rv = 0;
+ int tres = TRES;
+ double tlimit = -1.0;
+ double klimit = -1.0;
+ FILE *wrl = NULL;
+
+ error_program = "invprofcheck";
+
+ if (argc < 2)
+ usage();
+
+ /* Process the arguments */
+ for(fa = 1;fa < argc;fa++) {
+ nfa = fa; /* skip to nfa if next argument is used */
+ if (argv[fa][0] == '-') { /* Look for any flags */
+ char *na = NULL; /* next argument after flag, null if none */
+
+ if (argv[fa][2] != '\000')
+ na = &argv[fa][2]; /* next is directly after flag */
+ else {
+ if ((fa+1) < argc) {
+ if (argv[fa+1][0] != '-') {
+ nfa = fa + 1;
+ na = argv[nfa]; /* next is seperate non-flag argument */
+ }
+ }
+ }
+
+ if (argv[fa][1] == '?')
+ usage();
+
+ /* Verbosity */
+ else if (argv[fa][1] == 'v' || argv[fa][1] == 'V') {
+ verb = 1;
+ if (na != NULL && isdigit(na[0])) {
+ verb = atoi(na);
+ }
+ }
+
+ /* Resolution */
+ else if (argv[fa][1] == 'h' || argv[fa][1] == 'H') {
+ tres = HTRES;
+
+ }
+ /* Resolution */
+ else if (argv[fa][1] == 'u' || argv[fa][1] == 'U') {
+ tres = UHTRES;
+ }
+
+ /* Resolution */
+ else if (argv[fa][1] == 'R') {
+ int res;
+ fa = nfa;
+ if (na == NULL) usage();
+ res = atoi(na);
+ if (res < 2 || res > 500)
+ usage();
+ tres = res;
+ }
+
+ else if (argv[fa][1] == 'l') {
+ int limit;
+ fa = nfa;
+ if (na == NULL) usage();
+ limit = atoi(na);
+ if (limit < 1)
+ limit = 1;
+ tlimit = limit/100.0;
+ }
+
+ else if (argv[fa][1] == 'L') {
+ int limit;
+ fa = nfa;
+ if (na == NULL) usage();
+ limit = atoi(na);
+ if (limit < 1)
+ limit = 1;
+ klimit = limit/100.0;
+ }
+
+ /* VRML */
+ else if (argv[fa][1] == 'w' || argv[fa][1] == 'W')
+ dovrml = 1;
+
+ /* Axes */
+ else if (argv[fa][1] == 'x' || argv[fa][1] == 'X')
+ doaxes = 1;
+
+ /* Delta E coloring */
+ else if (argv[fa][1] == 'e' || argv[fa][1] == 'E')
+ dodecol = 1;
+
+ else if (argv[fa][1] == 'c' || argv[fa][1] == 'C') {
+ cie94 = 1;
+ cie2k = 0;
+ }
+
+ else if (argv[fa][1] == 'k' || argv[fa][1] == 'K') {
+ cie94 = 0;
+ cie2k = 1;
+ }
+
+ else
+ usage();
+ }
+ else
+ break;
+ }
+
+ if (fa >= argc || argv[fa][0] == '-') usage();
+ strncpy(in_name,argv[fa++],MAXNAMEL); in_name[MAXNAMEL] = '\000';
+
+
+ strncpy(out_name,in_name,MAXNAMEL-4); out_name[MAXNAMEL-4] = '\000';
+ if ((xl = strrchr(out_name, '.')) == NULL) /* Figure where extention is */
+ xl = out_name + strlen(out_name);
+ strcpy(xl,".wrl");
+
+ /* Open up the file for reading */
+ if ((rd_fp = new_icmFileStd_name(in_name,"r")) == NULL)
+ error ("Read: Can't open file '%s'",in_name);
+
+ if ((icco = new_icc()) == NULL)
+ error ("Read: Creation of ICC object failed");
+
+ /* Read the header and tag list */
+ if ((rv = icco->read(icco,rd_fp,0)) != 0)
+ error ("Read: %d, %s",rv,icco->err);
+
+ /* Check the forward lookup against the bwd function */
+ {
+ xcal *cal = NULL; /* Device calibration curves */
+ icColorSpaceSignature ins, outs; /* Type of input and output spaces of fwd */
+ int inn, outn; /* Channels of fwd conversion */
+ int kch; /* Black channel, -1 if not known/applicable */
+ icmLuBase *luo1, *luo2;
+ double merr = 0.0; /* Max */
+ double aerr = 0.0; /* Avg */
+ double rerr = 0.0; /* RMS */
+ double nsamps = 0.0;
+
+ /* Get a Device to PCS conversion object */
+ if ((luo1 = icco->get_luobj(icco, icmFwd, icRelativeColorimetric, icSigLabData, icmLuOrdNorm)) == NULL) {
+ if ((luo1 = icco->get_luobj(icco, icmFwd, icmDefaultIntent, icSigLabData, icmLuOrdNorm)) == NULL)
+ error ("%d, %s",icco->errc, icco->err);
+ }
+ /* Get details of conversion */
+ luo1->spaces(luo1, &ins, &inn, &outs, &outn, NULL, NULL, NULL, NULL, NULL);
+
+ /* Get a PCS to Device conversion object */
+ if ((luo2 = icco->get_luobj(icco, icmBwd, icRelativeColorimetric, icSigLabData, icmLuOrdNorm)) == NULL) {
+ if ((luo2 = icco->get_luobj(icco, icmBwd, icmDefaultIntent, icSigLabData, icmLuOrdNorm)) == NULL)
+ error ("%d, %s",icco->errc, icco->err);
+ }
+
+ if (dovrml) {
+ wrl = start_vrml(out_name, doaxes);
+ start_line_set(wrl);
+ }
+
+ /* Grab any device calibration curves */
+ cal = xiccReadCalTag(icco);
+
+ kch = icxGuessBlackChan(icco);
+
+ /* Set the default ink limits if not set by user */
+ if (tlimit < 0.0 || klimit < 0.0) {
+ double max[MAX_CHAN], total;
+
+ total = icco->get_tac(icco, max, cal != NULL ? xiccCalCallback : NULL, (void *)cal);
+
+ if (tlimit < 0.0)
+ tlimit = total;
+
+ if (klimit < 0.0 && kch >= 0)
+ klimit = max[kch];
+ }
+
+ if (verb) {
+ printf("Grid resolution is %d\n",tres);
+ if (tlimit >= 0.0)
+ printf("Input total ink limit assumed is %3.1f%%\n",100.0 * tlimit);
+ if (klimit >= 0.0)
+ printf("Input black ink limit assumed is %3.1f%%\n",100.0 * klimit);
+ }
+
+ {
+ double dev[MAX_CHAN], cdev[MAX_CHAN], pcsin[3], devout[MAX_CHAN], pcsout[3];
+ DCOUNT(co, inn, 0, 0, tres); /* Multi-D counter */
+
+ /* Go through the chosen device grid */
+ DC_INIT(co)
+ for (; !DC_DONE(co);) {
+ int n, rv1, rv2;
+ double sum;
+ double de;
+
+ /* Check the (possibly calibrated) device values */
+ /* end reject any over the limits. */
+ for (sum = 0, n = 0; n < inn; n++) {
+ cdev[n] = dev[n] = co[n]/(tres-1.0);
+ sum += cdev[n];
+ }
+ if (cal != NULL) {
+ cal->interp(cal, cdev, dev);
+ for (sum = 0, n = 0; n < inn; n++)
+ sum += cdev[n];
+ }
+
+ if ((tlimit > 0.0 && sum > tlimit)
+ || (klimit > 0.0 && kch >= 0 && cdev[kch] > klimit)) {
+ DC_INC(co);
+ continue;
+ }
+
+ /* Generate the in-gamut PCS test point */
+ /* by converting device to pcsin */
+ if ((rv1 = luo1->lookup(luo1, pcsin, dev)) > 1)
+ error ("%d, %s",icco->errc,icco->err);
+
+ /* Now do the check */
+ /* PCS -> Device */
+ if ((rv2 = luo2->lookup(luo2, devout, pcsin)) > 1)
+ error ("%d, %s",icco->errc,icco->err);
+
+ /* Device to PCS */
+ if ((rv2 = luo1->lookup(luo1, pcsout, devout)) > 1)
+ error ("%d, %s",icco->errc,icco->err);
+
+ /* Delta E */
+ if (dovrml) {
+ add_vertex(wrl, pcsin);
+ add_vertex(wrl, pcsout);
+ }
+
+ /* Check the result */
+ if (cie2k)
+ de = icmCIE2K(pcsout, pcsin);
+ else if (cie94)
+ de = icmCIE94(pcsout, pcsin);
+ else
+ de = icmLabDE(pcsout, pcsin);
+
+ aerr += de;
+ rerr += de * de;
+ if (de > merr)
+ merr = de;
+ nsamps++;
+
+ if (verb > 1) {
+ printf("[%f] %f %f %f -> ",de, pcsin[0], pcsin[1], pcsin[2]);
+ for (n = 0; n < inn; n++)
+ printf("%f ",devout[n]);
+ printf("-> %f %f %f\n",pcsout[0], pcsout[1], pcsout[2]);
+ }
+
+ DC_INC(co);
+ }
+ }
+ if (dovrml) {
+ if (dodecol)
+ make_de_lines(wrl);
+ else
+ make_lines(wrl, 2);
+ end_vrml(wrl);
+ }
+
+ printf("Profile check complete, errors%s: max. = %f, avg. = %f, RMS = %f\n",
+ cie2k ? "(CIEDE2000)" : cie94 ? " (CIE94)" : "", merr, aerr/nsamps, sqrt(rerr/nsamps));
+
+ /* Done with lookup object */
+ luo1->del(luo1);
+ luo2->del(luo2);
+ }
+
+ icco->del(icco);
+ rd_fp->del(rd_fp);
+
+ return 0;
+}
+
+
+/* ------------------------------------------------ */
+/* Some simple functions to do basix VRML work */
+/* !!! Should change to plot/vrml lib !!! */
+
+#define GAMUT_LCENT 50.0
+static int npoints = 0;
+static int paloc = 0;
+static struct { double pp[3]; } *pary;
+
+static void Lab2RGB(double *out, double *in);
+static void DE2RGB(double *out, double in);
+
+FILE *start_vrml(char *name, int doaxes) {
+ FILE *wrl;
+
+ /* Define the axis boxes */
+ struct {
+ double x, y, z; /* Box center */
+ double wx, wy, wz; /* Box size */
+ double r, g, b; /* Box color */
+ } axes[5] = {
+ { 0, 0, 50-GAMUT_LCENT, 2, 2, 100, .7, .7, .7 }, /* L axis */
+ { 50, 0, 0-GAMUT_LCENT, 100, 2, 2, 1, 0, 0 }, /* +a (red) axis */
+ { 0, -50, 0-GAMUT_LCENT, 2, 100, 2, 0, 0, 1 }, /* -b (blue) axis */
+ { -50, 0, 0-GAMUT_LCENT, 100, 2, 2, 0, 1, 0 }, /* -a (green) axis */
+ { 0, 50, 0-GAMUT_LCENT, 2, 100, 2, 1, 1, 0 }, /* +b (yellow) axis */
+ };
+
+ /* Define the labels */
+ struct {
+ double x, y, z;
+ double size;
+ char *string;
+ double r, g, b;
+ } labels[6] = {
+ { -2, 2, -GAMUT_LCENT + 100 + 10, 10, "+L*", .7, .7, .7 }, /* Top of L axis */
+ { -2, 2, -GAMUT_LCENT - 10, 10, "0", .7, .7, .7 }, /* Bottom of L axis */
+ { 100 + 5, -3, 0-GAMUT_LCENT, 10, "+a*", 1, 0, 0 }, /* +a (red) axis */
+ { -5, -100 - 10, 0-GAMUT_LCENT, 10, "-b*", 0, 0, 1 }, /* -b (blue) axis */
+ { -100 - 15, -3, 0-GAMUT_LCENT, 10, "-a*", 0, 0, 1 }, /* -a (green) axis */
+ { -5, 100 + 5, 0-GAMUT_LCENT, 10, "+b*", 1, 1, 0 }, /* +b (yellow) axis */
+ };
+
+ if ((wrl = fopen(name,"w")) == NULL)
+ error("Error opening VRML file '%s'\n",name);
+
+ npoints = 0;
+
+ fprintf(wrl,"#VRML V2.0 utf8\n");
+ fprintf(wrl,"\n");
+ fprintf(wrl,"# Created by the Argyll CMS\n");
+ fprintf(wrl,"Transform {\n");
+ fprintf(wrl,"children [\n");
+ fprintf(wrl," NavigationInfo {\n");
+ fprintf(wrl," type \"EXAMINE\" # It's an object we examine\n");
+ fprintf(wrl," } # We'll add our own light\n");
+ fprintf(wrl,"\n");
+ fprintf(wrl," DirectionalLight {\n");
+ fprintf(wrl," direction 0 0 -1 # Light illuminating the scene\n");
+ fprintf(wrl," direction 0 -1 0 # Light illuminating the scene\n");
+ fprintf(wrl," }\n");
+ fprintf(wrl,"\n");
+ fprintf(wrl," Viewpoint {\n");
+ fprintf(wrl," position 0 0 340 # Position we view from\n");
+ fprintf(wrl," }\n");
+ fprintf(wrl,"\n");
+ if (doaxes != 0) {
+ int n;
+ fprintf(wrl," # Lab axes as boxes:\n");
+ for (n = 0; n < 5; n++) {
+ fprintf(wrl," Transform { translation %f %f %f\n", axes[n].x, axes[n].y, axes[n].z);
+ fprintf(wrl," children [\n");
+ fprintf(wrl," Shape{\n");
+ fprintf(wrl," geometry Box { size %f %f %f }\n",
+ axes[n].wx, axes[n].wy, axes[n].wz);
+ fprintf(wrl," appearance Appearance { material Material ");
+ fprintf(wrl,"{ diffuseColor %f %f %f} }\n", axes[n].r, axes[n].g, axes[n].b);
+ fprintf(wrl," }\n");
+ fprintf(wrl," ]\n");
+ fprintf(wrl," }\n");
+ }
+ fprintf(wrl," # Axes identification:\n");
+ for (n = 0; n < 6; n++) {
+ fprintf(wrl," Transform { translation %f %f %f\n", labels[n].x, labels[n].y, labels[n].z);
+ fprintf(wrl," children [\n");
+ fprintf(wrl," Shape{\n");
+ fprintf(wrl," geometry Text { string [\"%s\"]\n",labels[n].string);
+ fprintf(wrl," fontStyle FontStyle { family \"SANS\" style \"BOLD\" size %f }\n",
+ labels[n].size);
+ fprintf(wrl," }\n");
+ fprintf(wrl," appearance Appearance { material Material ");
+ fprintf(wrl,"{ diffuseColor %f %f %f} }\n", labels[n].r, labels[n].g, labels[n].b);
+ fprintf(wrl," }\n");
+ fprintf(wrl," ]\n");
+ fprintf(wrl," }\n");
+ }
+ fprintf(wrl,"\n");
+ }
+
+ return wrl;
+}
+
+void
+start_line_set(FILE *wrl) {
+
+ fprintf(wrl,"\n");
+ fprintf(wrl,"Shape {\n");
+ fprintf(wrl," geometry IndexedLineSet { \n");
+ fprintf(wrl," coord Coordinate { \n");
+ fprintf(wrl," point [\n");
+}
+
+void add_vertex(FILE *wrl, double pp[3]) {
+
+ fprintf(wrl,"%f %f %f,\n",pp[1], pp[2], pp[0]-GAMUT_LCENT);
+
+ if (paloc < (npoints+1)) {
+ paloc = (paloc + 10) * 2;
+ if (pary == NULL)
+ pary = malloc(paloc * 3 * sizeof(double));
+ else
+ pary = realloc(pary, paloc * 3 * sizeof(double));
+
+ if (pary == NULL)
+ error ("Malloc failed");
+ }
+ pary[npoints].pp[0] = pp[0];
+ pary[npoints].pp[1] = pp[1];
+ pary[npoints].pp[2] = pp[2];
+ npoints++;
+}
+
+
+void make_lines(FILE *wrl, int ppset) {
+ int i, j;
+
+ fprintf(wrl," ]\n");
+ fprintf(wrl," }\n");
+ fprintf(wrl," coordIndex [\n");
+
+ for (i = 0; i < npoints;) {
+ for (j = 0; j < ppset; j++, i++) {
+ fprintf(wrl,"%d, ", i);
+ }
+ fprintf(wrl,"-1,\n");
+ }
+ fprintf(wrl," ]\n");
+
+ /* Color */
+ fprintf(wrl," colorPerVertex TRUE\n");
+ fprintf(wrl," color Color {\n");
+ fprintf(wrl," color [ # RGB colors of each vertex\n");
+
+ for (i = 0; i < npoints; i++) {
+ double rgb[3], Lab[3];
+ Lab[0] = pary[i].pp[0];
+ Lab[1] = pary[i].pp[1];
+ Lab[2] = pary[i].pp[2];
+ Lab2RGB(rgb, Lab);
+ fprintf(wrl," %f %f %f,\n", rgb[0], rgb[1], rgb[2]);
+ }
+ fprintf(wrl," ] \n");
+ fprintf(wrl," }\n");
+ /* End color */
+
+ fprintf(wrl," }\n");
+ fprintf(wrl,"} # end shape\n");
+}
+
+/* Assume 2 ppset, and make line color prop to length */
+void make_de_lines(FILE *wrl) {
+ int i, j;
+
+ fprintf(wrl," ]\n");
+ fprintf(wrl," }\n");
+ fprintf(wrl," coordIndex [\n");
+
+ for (i = 0; i < npoints;) {
+ for (j = 0; j < 2; j++, i++) {
+ fprintf(wrl,"%d, ", i);
+ }
+ fprintf(wrl,"-1,\n");
+ }
+ fprintf(wrl," ]\n");
+
+ /* Color */
+ fprintf(wrl," colorPerVertex TRUE\n");
+ fprintf(wrl," color Color {\n");
+ fprintf(wrl," color [ # RGB colors of each vertex\n");
+
+ for (i = 0; i < npoints; i++) {
+ double rgb[3], ss;
+ for (ss = 0.0, j = 0; j < 3; j++) {
+ double tt = (pary[i & ~1].pp[j] - pary[i | 1].pp[j]);
+ ss += tt * tt;
+ }
+ ss = sqrt(ss);
+ DE2RGB(rgb, ss);
+ fprintf(wrl," %f %f %f,\n", rgb[0], rgb[1], rgb[2]);
+ }
+ fprintf(wrl," ] \n");
+ fprintf(wrl," }\n");
+ /* End color */
+
+ fprintf(wrl," }\n");
+ fprintf(wrl,"} # end shape\n");
+}
+
+void end_vrml(FILE *wrl) {
+
+ fprintf(wrl,"\n");
+ fprintf(wrl," ] # end of children for world\n");
+ fprintf(wrl,"}\n");
+
+ if (fclose(wrl) != 0)
+ error("Error closing VRML file\n");
+}
+
+
+/* Convert a gamut Lab value to an RGB value for display purposes */
+static void
+Lab2RGB(double *out, double *in) {
+ double L = in[0], a = in[1], b = in[2];
+ double x,y,z,fx,fy,fz;
+ double R, G, B;
+
+ /* Scale so that black is visible */
+ L = L * (100 - 40.0)/100.0 + 40.0;
+
+ /* First convert to XYZ using D50 white point */
+ if (L > 8.0) {
+ fy = (L + 16.0)/116.0;
+ y = pow(fy,3.0);
+ } else {
+ y = L/903.2963058;
+ fy = 7.787036979 * y + 16.0/116.0;
+ }
+
+ fx = a/500.0 + fy;
+ if (fx > 24.0/116.0)
+ x = pow(fx,3.0);
+ else
+ x = (fx - 16.0/116.0)/7.787036979;
+
+ fz = fy - b/200.0;
+ if (fz > 24.0/116.0)
+ z = pow(fz,3.0);
+ else
+ z = (fz - 16.0/116.0)/7.787036979;
+
+ x *= 0.9642; /* Multiply by white point, D50 */
+ y *= 1.0;
+ z *= 0.8249;
+
+ /* Now convert to sRGB values */
+ R = x * 3.2410 + y * -1.5374 + z * -0.4986;
+ G = x * -0.9692 + y * 1.8760 + z * 0.0416;
+ B = x * 0.0556 + y * -0.2040 + z * 1.0570;
+
+ if (R < 0.0)
+ R = 0.0;
+ else if (R > 1.0)
+ R = 1.0;
+
+ if (G < 0.0)
+ G = 0.0;
+ else if (G > 1.0)
+ G = 1.0;
+
+ if (B < 0.0)
+ B = 0.0;
+ else if (B > 1.0)
+ B = 1.0;
+
+ R = pow(R, 1.0/2.2);
+ G = pow(G, 1.0/2.2);
+ B = pow(B, 1.0/2.2);
+
+ out[0] = R;
+ out[1] = G;
+ out[2] = B;
+}
+
+/* Convert a delta E value into a signal color: */
+static void
+DE2RGB(double *out, double in) {
+ struct {
+ double de;
+ double r, g, b;
+ } range[6] = {
+ { 10.0, 1, 1, 0 }, /* yellow */
+ { 4.0, 1, 0, 0 }, /* red */
+ { 2.0, 1, 0, 1 }, /* magenta */
+ { 1.0, 0, 0, 1 }, /* blue */
+ { 0.5, 0, 1, 1 }, /* cyan */
+ { 0.0, 0, 1, 0 } /* green */
+ };
+ int i;
+ double bl;
+
+//printf("~1 input de = %f\n",in);
+
+ /* Locate the range we're in */
+ if (in > range[0].de) {
+ out[0] = range[0].r;
+ out[1] = range[0].g;
+ out[2] = range[0].b;
+//printf("~1 too big\n");
+ } else {
+ for (i = 0; i < 5; i++) {
+ if (in <= range[i].de && in >= range[i+1].de)
+ break;
+ }
+ bl = (in - range[i+1].de)/(range[i].de - range[i+1].de);
+//printf("~1 located at ix %d, bl = %f\n",i,bl);
+ out[0] = bl * range[i].r + (1.0 - bl) * range[i+1].r;
+ out[1] = bl * range[i].g + (1.0 - bl) * range[i+1].g;
+ out[2] = bl * range[i].b + (1.0 - bl) * range[i+1].b;
+ }
+//printf("~1 returning rgb %f %f %f\n",out[0],out[1],out[2]);
+}
+
+
diff --git a/profile/kodak2ti3.c b/profile/kodak2ti3.c
new file mode 100644
index 0000000..53278e1
--- /dev/null
+++ b/profile/kodak2ti3.c
@@ -0,0 +1,1273 @@
+
+/*
+ * Argyll Color Correction System
+ *
+ * Read in the raw data from a Kodak print profile,
+ * and convert it into a .ti3 CGATs format suitable for
+ * the Argyll CMM.
+ *
+ * Derived from spectro.c
+ * Author: Graeme W. Gill
+ * Date: 4/9/00
+ *
+ * Copyright 2000, 2010, Graeme W. Gill
+ * All rights reserved.
+ *
+ * This material is licenced under the GNU AFFERO GENERAL PUBLIC LICENSE Version 3 :-
+ * see the License.txt file for licencing details.
+ */
+
+/* TTBD
+ */
+
+#undef DEBUG
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <math.h>
+#include <sys/types.h>
+#include <time.h>
+#include <string.h>
+#include "copyright.h"
+#include "aconfig.h"
+#include "numlib.h"
+#include "cgats.h"
+#include "icc.h"
+#include <stdarg.h>
+
+void
+usage(void) {
+ fprintf(stderr,"Convert Kodak raw printer profile data to Argyll print data, Version %s\n",ARGYLL_VERSION_STR);
+ fprintf(stderr,"Author: Graeme W. Gill, licensed under the AGPL Version 3\n");
+ fprintf(stderr,"usage: kodak2ti3 [-v] [-l limit] infile outfile\n");
+ fprintf(stderr," -v Verbose mode\n");
+ fprintf(stderr," -l limit set ink limit, 0 - 400%%\n");
+ fprintf(stderr," -r filename Use an alternate 928 patch reference file\n");
+ fprintf(stderr," infile Base name for input.pat file\n");
+ fprintf(stderr," outfile Base name for output.ti3 file\n");
+ exit(1);
+ }
+
+#define NPAT 928
+
+FILE *open_928(char *filename);
+int next_928(FILE *fp, int i, double *CMYK);
+void close_928(FILE *fp);
+
+FILE *open_pat(char *filename);
+int next_pat(FILE *fp, double *Lab);
+void close_pat(FILE *fp);
+
+int main(int argc, char *argv[])
+{
+ int i;
+ int fa,nfa; /* current argument we're looking at */
+ int verb = 0;
+ int limit = -1;
+ static char tarname[200] = { 0 }; /* optional 928 patch reference file */
+ static char inname[200] = { 0 }; /* Input .pat file base name */
+ static char outname[200] = { 0 }; /* Output cgats .ti3 file base name */
+ FILE *d928_fp = NULL;
+ FILE *pat_fp;
+ cgats *ocg; /* output cgats structure */
+ time_t clk = time(0);
+ struct tm *tsp = localtime(&clk);
+ char *atm = asctime(tsp); /* Ascii time */
+
+ error_program = "kodak2ti3";
+
+ if (argc <= 1)
+ usage();
+
+ /* Process the arguments */
+ for(fa = 1;fa < argc;fa++)
+ {
+ nfa = fa; /* skip to nfa if next argument is used */
+ if (argv[fa][0] == '-') /* Look for any flags */
+ {
+ char *na = NULL; /* next argument after flag, null if none */
+
+ if (argv[fa][2] != '\000')
+ na = &argv[fa][2]; /* next is directly after flag */
+ else
+ {
+ if ((fa+1) < argc)
+ {
+ if (argv[fa+1][0] != '-')
+ {
+ nfa = fa + 1;
+ na = argv[nfa]; /* next is seperate non-flag argument */
+ }
+ }
+ }
+
+ if (argv[fa][1] == '?')
+ usage();
+
+ else if (argv[fa][1] == 'l' || argv[fa][1] == 'L') {
+ fa = nfa;
+ if (na == NULL) usage();
+ limit = atoi(na);
+ if (limit < 1)
+ limit = 1;
+ }
+
+ else if (argv[fa][1] == 'r' || argv[fa][1] == 'r') {
+ fa = nfa;
+ if (na == NULL) usage();
+ strcpy(tarname, na);
+ }
+
+ else if (argv[fa][1] == 'v' || argv[fa][1] == 'V')
+ verb = 1;
+ else
+ usage();
+ }
+ else
+ break;
+ }
+
+ /* Get the file name argument */
+ if (fa >= argc || argv[fa][0] == '-') usage();
+
+ strcpy(inname,argv[fa++]);
+ strcat(inname,".pat");
+
+ if (fa >= argc || argv[fa][0] == '-') usage();
+ strcpy(outname, argv[fa++]);
+ strcat(outname,".ti3");
+
+ /* Open up the Test chart reference file if we were given one */
+ if (tarname[0] != '\000') {
+ if (verb)
+ printf("Using alternate reference file '%s'\n",tarname);
+ if ((d928_fp = open_928(tarname)) == NULL)
+ error ("Read: Can't open file '%s'",tarname);
+ }
+
+ if ((pat_fp = open_pat(inname)) == NULL)
+ error ("Read: Can't open file '%s'",inname);
+
+ /* Setup output cgats file */
+ ocg = new_cgats(); /* Create a CGATS structure */
+ ocg->add_other(ocg, "CTI3"); /* our special type is Calibration Target Information 3 */
+ ocg->add_table(ocg, tt_other, 0); /* Start the first table */
+
+ ocg->add_kword(ocg, 0, "DESCRIPTOR", "Argyll Calibration Target chart information 3",NULL);
+ ocg->add_kword(ocg, 0, "ORIGINATOR", "Argyll target", NULL);
+ atm[strlen(atm)-1] = '\000'; /* Remove \n from end */
+ ocg->add_kword(ocg, 0, "CREATED",atm, NULL);
+ ocg->add_kword(ocg, 0, "DEVICE_CLASS","OUTPUT", NULL); /* What sort of device this is */
+
+ /* Fields we want */
+ ocg->add_field(ocg, 0, "SAMPLE_ID", nqcs_t);
+
+ if (limit > 0) {
+ char buf[100];
+ sprintf(buf, "%d", limit);
+ ocg->add_kword(ocg, 0, "TOTAL_INK_LIMIT", buf, NULL);
+ }
+
+ ocg->add_field(ocg, 0, "CMYK_C", r_t);
+ ocg->add_field(ocg, 0, "CMYK_M", r_t);
+ ocg->add_field(ocg, 0, "CMYK_Y", r_t);
+ ocg->add_field(ocg, 0, "CMYK_K", r_t);
+ ocg->add_kword(ocg, 0, "COLOR_REP","CMYK_LAB", NULL);
+ ocg->add_field(ocg, 0, "LAB_L", r_t);
+ ocg->add_field(ocg, 0, "LAB_A", r_t);
+ ocg->add_field(ocg, 0, "LAB_B", r_t);
+
+ /* Write out the patch info to the output CGATS file */
+ for (i = 0; i < NPAT; i++) {
+ char id[100];
+ double cmykv[4];
+ double labv[3];
+
+ if (next_928(d928_fp, i, cmykv) != 0)
+ error("Error reading reference information from '%s' file",tarname);
+
+ if (next_pat(pat_fp, labv) != 0)
+ error("Error reading Kodak .pat file");
+
+ sprintf(id, "%d", i+1);
+ ocg->add_set(ocg, 0, id, 100.0 * cmykv[0], 100.0 * cmykv[1],
+ 100.0 * cmykv[2], 100.0 * cmykv[3],
+ labv[0], labv[1], labv[2]);
+ }
+
+ {
+ double wp[3]; /* Paper white XYZ */
+ double D50wp[3] /* D50 white point Kodak uses */
+ = { 0.9642, 1.0000, 0.8249 };
+ double tt[3];
+ int li;
+
+ /* Get offset of Lab values in cgats file */
+ if ((li = ocg->find_field(ocg, 0, "LAB_L")) < 0)
+ error("Internal - cgats doesn't field LAB_L");
+
+ /* Get last line of pat file - this is the paper white in XYZ */
+ if (next_pat(pat_fp, wp) != 0)
+ error("Error reading Kodak .pat file");
+
+//printf("~1 white point is XYZ %f %f %f\n",wp[0],wp[1],wp[2]);
+
+ /* Run through all the data points, and adjust them back to the absolute */
+ /* white point. */
+
+ for (i = 0; i < NPAT; i++) {
+ double in[3], out[3];
+
+ tt[0] = *((double *)ocg->t[0].fdata[i][li+0]);
+ tt[1] = *((double *)ocg->t[0].fdata[i][li+1]);
+ tt[2] = *((double *)ocg->t[0].fdata[i][li+2]);
+
+ icmLab2XYZ(&icmD50, in, tt);
+
+ /* Undo Kodak's D50->paper white point adjustment */
+ out[0] = in[0] * (D50wp[1] * wp[0])/(D50wp[0] * wp[1]);
+ out[1] = in[1]; /* Y remains unchanged */
+ out[2] = in[2] * (D50wp[1] * wp[2])/(D50wp[2] * wp[1]);
+
+ icmXYZ2Lab(&icmD50, tt, out);
+
+ *((double *)ocg->t[0].fdata[i][li+0]) = tt[0];
+ *((double *)ocg->t[0].fdata[i][li+1]) = tt[1];
+ *((double *)ocg->t[0].fdata[i][li+2]) = tt[2];
+ }
+
+
+ }
+
+ if (ocg->write_name(ocg, outname))
+ error("Write error : %s",ocg->err);
+
+ ocg->del(ocg); /* Clean up */
+
+ if (d928_fp != NULL)
+ close_928(d928_fp);
+ close_pat(pat_fp);
+
+ return 0;
+}
+/* ------------------------------------------------ */
+/* Library to read in a Kodak .pat file */
+
+/* Open the file, return NULL on error */
+FILE *open_pat(char *filename) {
+ FILE *fp;
+ char buf[200];
+
+ if ((fp = fopen(filename,"r")) == NULL)
+ return NULL;
+
+ /* First line should be "KCMSPATCHFILE 2 1" */
+ if (fgets(buf, 200, fp) == NULL)
+ return NULL;
+ if (strncmp(buf, "KCMSPATCHFILE 2 1", strlen("KCMSPATCHFILE 2 1")) != 0) {
+ fclose(fp);
+ return NULL;
+ }
+
+ /* Second line should be "928" */
+ if (fgets(buf, 200, fp) == NULL)
+ return NULL;
+ if (strncmp(buf, "928", strlen("928")) != 0) {
+ fclose(fp);
+ return NULL;
+ }
+
+ return fp;
+}
+
+/* return non-zero on error */
+int next_pat(FILE *fp, double *Lab) {
+ char buf[200];
+
+ if (fgets(buf, 200, fp) == NULL)
+ return 1;
+
+ if (sscanf(buf, "%lf, %lf, %lf", &Lab[0], &Lab[1], &Lab[2]) != 3)
+ return 1;
+
+ return 0;
+}
+
+void close_pat(FILE *fp) {
+ fclose(fp);
+}
+
+/* ------------------------------------------------ */
+/* Library to read in a Kodak CMYK_Large_Target_928 file */
+
+/* default reference values */
+double refvs[NPAT][4] = {
+ { 0.00, 0.00, 0.00, 0.00 },
+ { 0.00, 0.00, 0.00, 0.00 },
+ { 0.00, 0.40, 1.00, 0.60 },
+ { 0.20, 1.00, 0.40, 0.60 },
+ { 0.00, 0.20, 0.70, 0.20 },
+ { 0.00, 1.00, 0.20, 0.20 },
+ { 0.40, 0.20, 0.00, 0.40 },
+ { 0.00, 0.00, 0.30, 0.00 },
+ { 0.00, 1.00, 0.20, 0.40 },
+ { 0.00, 0.70, 0.70, 0.20 },
+ { 0.10, 1.00, 1.00, 0.20 },
+ { 0.70, 0.70, 0.70, 0.40 },
+ { 1.00, 0.00, 0.00, 0.60 },
+ { 0.00, 0.00, 0.00, 0.40 },
+ { 0.03, 0.00, 0.00, 0.00 },
+ { 0.70, 0.20, 0.70, 0.00 },
+ { 0.20, 1.00, 0.10, 0.20 },
+ { 0.40, 0.00, 0.70, 0.00 },
+ { 0.00, 0.10, 0.40, 0.20 },
+ { 0.00, 0.00, 0.00, 0.40 },
+ { 0.40, 0.40, 0.00, 0.60 },
+ { 0.70, 0.10, 0.40, 0.20 },
+ { 0.00, 0.10, 0.20, 0.00 },
+ { 0.10, 0.40, 0.10, 0.00 },
+ { 0.70, 1.00, 0.40, 0.00 },
+ { 0.10, 0.40, 0.70, 0.20 },
+ { 0.70, 0.70, 0.20, 0.20 },
+ { 1.00, 0.40, 0.40, 0.00 },
+ { 1.00, 0.00, 0.70, 0.40 },
+ { 1.00, 0.20, 1.00, 0.40 },
+ { 0.10, 0.00, 0.10, 0.20 },
+ { 0.70, 0.40, 0.40, 0.60 },
+ { 0.10, 1.00, 0.00, 0.20 },
+ { 0.20, 0.10, 0.00, 0.20 },
+ { 0.20, 0.00, 1.00, 0.40 },
+ { 0.00, 1.00, 1.00, 0.70 },
+ { 0.00, 0.70, 1.00, 0.40 },
+ { 0.70, 0.00, 0.40, 0.80 },
+ { 0.20, 0.00, 0.70, 0.40 },
+ { 0.20, 0.00, 0.10, 0.20 },
+ { 0.40, 1.00, 1.00, 0.00 },
+ { 0.90, 0.00, 0.00, 0.00 },
+ { 0.20, 0.00, 1.00, 0.60 },
+ { 0.00, 0.70, 0.20, 0.40 },
+ { 0.40, 0.40, 0.40, 0.60 },
+ { 0.10, 0.20, 0.40, 0.00 },
+ { 0.20, 0.40, 0.20, 0.00 },
+ { 0.00, 0.00, 0.20, 0.00 },
+ { 0.00, 0.00, 0.70, 0.80 },
+ { 1.00, 0.85, 0.85, 0.60 },
+ { 0.00, 0.00, 0.40, 0.00 },
+ { 0.00, 0.40, 0.40, 0.00 },
+ { 0.20, 0.10, 0.20, 0.20 },
+ { 1.00, 0.40, 0.20, 0.40 },
+ { 0.10, 0.06, 0.06, 0.40 },
+ { 0.40, 0.40, 0.70, 0.40 },
+ { 0.70, 1.00, 0.70, 0.60 },
+ { 1.00, 1.00, 0.70, 0.00 },
+ { 0.00, 0.40, 0.70, 0.60 },
+ { 0.40, 0.40, 0.00, 0.20 },
+ { 0.20, 0.40, 0.00, 0.60 },
+ { 0.20, 1.00, 0.40, 0.00 },
+ { 0.40, 0.40, 1.00, 0.80 },
+ { 1.00, 0.00, 0.40, 0.20 },
+ { 1.00, 1.00, 1.00, 0.60 },
+ { 0.20, 0.20, 0.40, 0.00 },
+ { 0.40, 0.70, 0.70, 0.80 },
+ { 0.70, 0.70, 1.00, 0.80 },
+ { 0.00, 1.00, 0.40, 0.60 },
+ { 0.20, 0.00, 0.40, 0.40 },
+ { 0.10, 0.40, 0.10, 0.20 },
+ { 0.70, 0.10, 0.70, 0.00 },
+ { 0.40, 0.70, 0.00, 0.00 },
+ { 0.20, 0.12, 0.12, 0.40 },
+ { 1.00, 0.70, 0.20, 0.00 },
+ { 1.00, 0.40, 0.70, 0.00 },
+ { 0.70, 0.00, 0.70, 0.00 },
+ { 0.70, 0.40, 0.70, 0.40 },
+ { 0.40, 0.40, 0.40, 0.00 },
+ { 0.70, 0.40, 0.20, 0.00 },
+ { 0.70, 0.00, 1.00, 0.00 },
+ { 1.00, 0.40, 0.20, 0.20 },
+ { 0.60, 0.45, 0.45, 0.20 },
+ { 1.00, 0.70, 0.00, 0.60 },
+ { 1.00, 0.00, 0.40, 0.80 },
+ { 0.70, 1.00, 0.10, 0.00 },
+ { 0.40, 0.00, 0.40, 0.20 },
+ { 1.00, 1.00, 0.20, 0.60 },
+ { 1.00, 0.00, 1.00, 0.40 },
+ { 0.40, 0.70, 0.40, 0.60 },
+ { 0.20, 0.40, 0.70, 0.60 },
+ { 0.70, 0.70, 0.40, 0.00 },
+ { 0.00, 1.00, 0.70, 0.80 },
+ { 0.00, 0.00, 1.00, 0.80 },
+ { 1.00, 0.40, 0.00, 0.40 },
+ { 0.20, 0.10, 0.40, 0.00 },
+ { 0.00, 0.40, 0.40, 0.80 },
+ { 0.10, 0.40, 0.20, 0.20 },
+ { 0.00, 0.70, 1.00, 0.20 },
+ { 0.00, 0.10, 0.70, 0.20 },
+ { 0.20, 0.70, 1.00, 0.40 },
+ { 0.10, 0.20, 0.00, 0.20 },
+ { 1.00, 0.00, 0.00, 0.40 },
+ { 0.10, 0.00, 0.10, 0.00 },
+ { 0.10, 0.40, 1.00, 0.00 },
+ { 0.40, 0.27, 0.27, 0.00 },
+ { 0.70, 0.70, 1.00, 0.00 },
+ { 0.10, 0.20, 0.10, 0.20 },
+ { 0.00, 0.70, 0.40, 0.00 },
+ { 0.40, 1.00, 1.00, 0.20 },
+ { 0.00, 0.20, 0.40, 0.00 },
+ { 0.20, 0.10, 1.00, 0.00 },
+ { 0.40, 0.20, 0.00, 0.60 },
+ { 0.20, 0.20, 0.40, 0.40 },
+ { 0.40, 0.40, 0.40, 0.20 },
+ { 0.00, 0.20, 1.00, 0.20 },
+ { 0.70, 0.40, 0.40, 0.00 },
+ { 0.40, 0.40, 0.20, 0.00 },
+ { 0.00, 1.00, 0.40, 0.40 },
+ { 0.00, 0.70, 1.00, 0.60 },
+ { 0.40, 0.00, 1.00, 0.40 },
+ { 1.00, 0.00, 1.00, 0.80 },
+ { 1.00, 0.70, 1.00, 0.60 },
+ { 0.00, 1.00, 0.10, 0.20 },
+ { 0.40, 0.40, 1.00, 0.40 },
+ { 0.70, 0.00, 1.00, 0.40 },
+ { 0.70, 0.40, 0.00, 0.20 },
+ { 0.20, 1.00, 0.40, 0.40 },
+ { 0.00, 0.90, 0.00, 0.00 },
+ { 0.40, 0.10, 0.00, 0.20 },
+ { 0.20, 0.20, 0.20, 0.00 },
+ { 0.00, 1.00, 1.00, 0.20 },
+ { 0.40, 0.00, 0.20, 0.40 },
+ { 0.70, 0.00, 1.00, 0.80 },
+ { 0.10, 0.20, 0.00, 0.00 },
+ { 0.70, 0.20, 0.00, 0.20 },
+ { 0.20, 0.40, 0.20, 0.20 },
+ { 1.00, 0.00, 0.10, 0.20 },
+ { 0.00, 0.20, 0.00, 0.00 },
+ { 0.00, 0.00, 1.00, 0.40 },
+ { 0.20, 0.00, 1.00, 0.00 },
+ { 0.70, 0.40, 0.00, 0.60 },
+ { 0.20, 0.00, 0.70, 0.60 },
+ { 0.10, 0.70, 0.20, 0.20 },
+ { 0.00, 0.70, 0.40, 0.80 },
+ { 0.40, 0.70, 0.10, 0.20 },
+ { 1.00, 0.40, 1.00, 0.40 },
+ { 0.40, 0.00, 0.00, 0.00 },
+ { 0.20, 0.40, 0.40, 0.60 },
+ { 0.00, 1.00, 0.00, 0.70 },
+ { 0.00, 1.00, 0.70, 0.40 },
+ { 1.00, 1.00, 0.40, 0.40 },
+ { 0.00, 0.20, 1.00, 0.40 },
+ { 0.70, 0.70, 0.20, 0.40 },
+ { 0.70, 0.00, 0.00, 0.80 },
+ { 0.00, 0.25, 0.00, 0.00 },
+ { 0.20, 0.40, 0.40, 0.40 },
+ { 0.00, 0.40, 0.00, 0.00 },
+ { 0.40, 0.70, 0.10, 0.00 },
+ { 0.40, 0.00, 0.20, 0.20 },
+ { 0.70, 0.00, 1.00, 0.20 },
+ { 0.70, 0.10, 0.20, 0.20 },
+ { 0.20, 0.00, 0.70, 0.00 },
+ { 1.00, 0.00, 0.00, 1.00 },
+ { 0.00, 0.10, 1.00, 0.20 },
+ { 0.70, 0.40, 1.00, 0.80 },
+ { 0.20, 0.00, 0.20, 0.00 },
+ { 0.00, 0.20, 0.20, 0.00 },
+ { 0.70, 0.10, 0.00, 0.00 },
+ { 0.00, 0.70, 0.20, 0.20 },
+ { 0.10, 1.00, 0.10, 0.20 },
+ { 0.00, 0.10, 0.00, 0.20 },
+ { 0.70, 0.20, 0.70, 0.40 },
+ { 0.10, 0.70, 0.70, 0.00 },
+ { 0.40, 0.70, 1.00, 0.60 },
+ { 0.70, 0.70, 0.20, 0.00 },
+ { 0.20, 1.00, 0.00, 0.60 },
+ { 0.00, 0.00, 0.00, 0.60 },
+ { 0.20, 1.00, 0.00, 0.00 },
+ { 0.20, 0.20, 0.00, 0.00 },
+ { 0.00, 0.15, 0.00, 0.00 },
+ { 1.00, 0.20, 0.10, 0.20 },
+ { 0.10, 0.10, 0.00, 0.20 },
+ { 0.20, 0.40, 0.00, 0.20 },
+ { 1.00, 0.40, 1.00, 0.20 },
+ { 1.00, 1.00, 0.20, 0.20 },
+ { 1.00, 0.70, 0.70, 0.00 },
+ { 0.40, 1.00, 0.40, 0.00 },
+ { 0.10, 0.00, 0.70, 0.20 },
+ { 0.00, 1.00, 0.20, 0.60 },
+ { 0.00, 0.20, 0.70, 0.40 },
+ { 0.00, 0.00, 0.00, 0.80 },
+ { 0.20, 0.00, 0.00, 0.40 },
+ { 1.00, 1.00, 0.20, 0.00 },
+ { 0.70, 1.00, 0.40, 0.80 },
+ { 0.00, 0.00, 1.00, 0.20 },
+ { 0.40, 1.00, 0.70, 0.20 },
+ { 1.00, 1.00, 0.40, 0.80 },
+ { 0.20, 0.20, 0.40, 0.60 },
+ { 0.70, 0.10, 0.20, 0.00 },
+ { 0.70, 1.00, 0.20, 0.40 },
+ { 0.40, 0.20, 0.40, 0.00 },
+ { 0.40, 0.70, 1.00, 0.00 },
+ { 0.10, 0.20, 0.20, 0.20 },
+ { 0.10, 0.00, 0.70, 0.00 },
+ { 0.70, 0.00, 0.70, 0.40 },
+ { 1.00, 0.00, 0.20, 0.40 },
+ { 0.00, 0.40, 1.00, 0.20 },
+ { 0.40, 0.40, 0.40, 0.40 },
+ { 0.00, 0.40, 0.40, 0.60 },
+ { 0.00, 1.00, 0.20, 0.00 },
+ { 0.40, 0.70, 1.00, 0.40 },
+ { 0.10, 0.40, 1.00, 0.20 },
+ { 0.70, 0.40, 0.70, 0.00 },
+ { 0.20, 0.20, 1.00, 0.60 },
+ { 0.10, 0.70, 0.20, 0.00 },
+ { 0.20, 1.00, 0.70, 0.40 },
+ { 0.70, 1.00, 1.00, 0.00 },
+ { 1.00, 0.70, 0.20, 0.40 },
+ { 0.00, 0.20, 0.10, 0.20 },
+ { 0.20, 0.70, 1.00, 0.20 },
+ { 0.00, 0.70, 0.70, 0.00 },
+ { 0.70, 1.00, 1.00, 0.20 },
+ { 1.00, 1.00, 1.00, 0.00 },
+ { 0.70, 1.00, 0.00, 0.60 },
+ { 0.20, 0.40, 0.40, 0.00 },
+ { 0.40, 0.40, 1.00, 0.00 },
+ { 0.00, 0.70, 0.20, 0.60 },
+ { 0.25, 0.00, 0.00, 0.00 },
+ { 0.40, 0.40, 0.00, 0.00 },
+ { 0.70, 0.20, 1.00, 0.40 },
+ { 1.00, 1.00, 0.00, 0.20 },
+ { 0.00, 0.10, 0.70, 0.00 },
+ { 0.70, 0.40, 0.00, 0.00 },
+ { 0.20, 0.40, 0.00, 0.00 },
+ { 0.70, 0.00, 0.70, 0.80 },
+ { 0.20, 1.00, 0.00, 0.20 },
+ { 0.40, 0.10, 0.20, 0.00 },
+ { 0.20, 1.00, 0.20, 0.40 },
+ { 0.40, 0.00, 1.00, 0.60 },
+ { 0.40, 0.40, 0.00, 0.80 },
+ { 0.40, 0.20, 0.70, 0.60 },
+ { 0.40, 0.27, 0.27, 0.40 },
+ { 0.40, 0.20, 0.10, 0.20 },
+ { 0.40, 0.70, 0.40, 0.20 },
+ { 1.00, 0.00, 1.00, 0.00 },
+ { 0.00, 0.40, 0.20, 0.40 },
+ { 1.00, 0.70, 0.40, 0.80 },
+ { 1.00, 0.85, 0.85, 1.00 },
+ { 0.20, 0.10, 0.10, 0.00 },
+ { 0.10, 0.10, 0.10, 0.20 },
+ { 0.20, 0.70, 0.00, 0.60 },
+ { 0.00, 0.40, 0.70, 0.00 },
+ { 0.20, 0.00, 0.20, 0.60 },
+ { 0.40, 0.00, 0.40, 0.00 },
+ { 0.50, 0.00, 0.00, 0.00 },
+ { 0.40, 0.40, 0.40, 0.80 },
+ { 0.70, 0.20, 0.20, 0.40 },
+ { 0.40, 0.20, 0.40, 0.40 },
+ { 0.00, 0.40, 0.40, 0.20 },
+ { 0.40, 0.20, 1.00, 0.00 },
+ { 1.00, 1.00, 0.00, 0.20 },
+ { 1.00, 1.00, 0.00, 0.00 },
+ { 0.40, 0.20, 0.40, 0.60 },
+ { 0.20, 0.40, 0.10, 0.20 },
+ { 0.00, 0.00, 1.00, 0.00 },
+ { 0.70, 0.10, 1.00, 0.20 },
+ { 0.10, 0.20, 1.00, 0.20 },
+ { 0.20, 0.00, 0.10, 0.00 },
+ { 0.70, 1.00, 0.40, 0.20 },
+ { 0.00, 1.00, 1.00, 0.80 },
+ { 1.00, 0.20, 0.00, 0.20 },
+ { 0.00, 0.10, 0.10, 0.00 },
+ { 0.40, 0.00, 0.20, 0.00 },
+ { 0.20, 0.20, 0.00, 0.60 },
+ { 1.00, 0.00, 0.40, 0.00 },
+ { 0.20, 0.20, 0.70, 0.40 },
+ { 0.10, 1.00, 0.40, 0.00 },
+ { 0.40, 0.70, 0.40, 0.00 },
+ { 1.00, 0.20, 0.00, 0.40 },
+ { 0.20, 0.12, 0.12, 0.10 },
+ { 0.00, 0.00, 0.25, 0.00 },
+ { 0.40, 0.00, 1.00, 0.20 },
+ { 0.70, 0.70, 0.00, 0.40 },
+ { 1.00, 0.20, 0.10, 0.00 },
+ { 0.00, 1.00, 0.10, 0.00 },
+ { 0.70, 1.00, 0.20, 0.60 },
+ { 0.00, 0.20, 0.40, 0.40 },
+ { 0.40, 0.40, 0.70, 0.00 },
+ { 1.00, 1.00, 0.00, 0.00 },
+ { 0.00, 0.20, 0.40, 0.20 },
+ { 0.70, 0.00, 0.20, 0.40 },
+ { 0.40, 0.20, 1.00, 0.40 },
+ { 0.40, 0.10, 0.70, 0.00 },
+ { 0.00, 0.00, 0.40, 0.40 },
+ { 0.20, 1.00, 0.20, 0.20 },
+ { 0.00, 0.40, 1.00, 0.40 },
+ { 0.20, 0.40, 1.00, 0.20 },
+ { 0.20, 1.00, 1.00, 0.60 },
+ { 0.00, 0.20, 1.00, 0.00 },
+ { 1.00, 0.00, 1.00, 1.00 },
+ { 0.40, 1.00, 0.00, 0.00 },
+ { 0.70, 0.70, 0.00, 0.60 },
+ { 0.40, 0.10, 0.40, 0.20 },
+ { 1.00, 0.00, 0.20, 0.00 },
+ { 0.40, 0.20, 0.20, 0.20 },
+ { 0.70, 0.20, 1.00, 0.60 },
+ { 0.20, 0.40, 1.00, 0.60 },
+ { 0.40, 0.00, 0.40, 0.40 },
+ { 0.70, 0.20, 0.40, 0.40 },
+ { 0.70, 0.00, 0.10, 0.20 },
+ { 0.20, 0.40, 0.70, 0.20 },
+ { 0.00, 1.00, 0.40, 0.20 },
+ { 1.00, 1.00, 0.00, 0.40 },
+ { 0.40, 1.00, 0.00, 0.20 },
+ { 1.00, 0.40, 1.00, 0.00 },
+ { 0.40, 0.00, 0.40, 0.60 },
+ { 0.10, 0.06, 0.06, 0.80 },
+ { 1.00, 1.00, 0.00, 1.00 },
+ { 0.70, 0.20, 1.00, 0.00 },
+ { 1.00, 0.40, 0.00, 0.00 },
+ { 0.40, 0.00, 0.00, 0.80 },
+ { 0.40, 0.40, 1.00, 0.20 },
+ { 0.20, 0.70, 0.00, 0.40 },
+ { 1.00, 1.00, 0.40, 0.60 },
+ { 0.00, 0.30, 0.00, 0.00 },
+ { 0.00, 0.20, 0.70, 0.60 },
+ { 1.00, 0.40, 0.70, 0.40 },
+ { 0.20, 0.70, 0.00, 0.00 },
+ { 0.40, 0.40, 0.70, 0.60 },
+ { 0.20, 0.20, 0.20, 0.00 },
+ { 0.10, 0.06, 0.06, 0.20 },
+ { 0.00, 1.00, 1.00, 0.40 },
+ { 1.00, 0.20, 0.20, 0.60 },
+ { 1.00, 0.10, 0.00, 0.20 },
+ { 0.40, 0.20, 0.20, 0.00 },
+ { 0.00, 1.00, 1.00, 1.00 },
+ { 0.20, 0.20, 0.40, 0.00 },
+ { 1.00, 0.10, 0.10, 0.00 },
+ { 0.00, 0.00, 0.00, 0.07 },
+ { 0.20, 0.70, 0.10, 0.20 },
+ { 0.40, 0.70, 0.00, 0.60 },
+ { 0.40, 0.00, 1.00, 0.80 },
+ { 0.20, 0.70, 0.70, 0.00 },
+ { 1.00, 0.40, 0.20, 0.00 },
+ { 1.00, 1.00, 1.00, 0.00 },
+ { 0.00, 0.40, 0.00, 0.80 },
+ { 0.40, 1.00, 0.70, 0.80 },
+ { 1.00, 0.10, 0.20, 0.00 },
+ { 0.10, 0.00, 0.40, 0.00 },
+ { 0.00, 0.00, 0.00, 0.80 },
+ { 0.00, 0.20, 0.00, 0.00 },
+ { 0.00, 0.00, 0.00, 0.50 },
+ { 1.00, 1.00, 0.70, 0.00 },
+ { 1.00, 0.40, 0.40, 0.40 },
+ { 0.00, 0.00, 0.20, 0.20 },
+ { 0.70, 0.40, 1.00, 0.60 },
+ { 0.20, 0.20, 0.70, 0.20 },
+ { 0.20, 0.70, 0.20, 0.20 },
+ { 0.40, 1.00, 0.40, 0.80 },
+ { 0.70, 0.70, 1.00, 0.40 },
+ { 0.40, 0.00, 0.70, 0.60 },
+ { 1.00, 0.40, 0.40, 0.00 },
+ { 0.40, 1.00, 0.70, 0.40 },
+ { 1.00, 1.00, 1.00, 1.00 },
+ { 0.40, 1.00, 0.10, 0.00 },
+ { 0.00, 0.20, 0.10, 0.00 },
+ { 0.40, 1.00, 1.00, 0.40 },
+ { 1.00, 0.20, 0.00, 0.60 },
+ { 0.70, 1.00, 0.20, 0.00 },
+ { 1.00, 0.70, 0.00, 0.40 },
+ { 1.00, 0.00, 0.00, 0.20 },
+ { 0.40, 1.00, 0.20, 0.20 },
+ { 0.00, 0.00, 0.00, 0.90 },
+ { 1.00, 0.70, 0.00, 0.00 },
+ { 0.10, 1.00, 0.20, 0.20 },
+ { 0.20, 0.00, 0.20, 0.20 },
+ { 1.00, 1.00, 0.00, 0.40 },
+ { 0.40, 0.10, 0.00, 0.00 },
+ { 0.40, 1.00, 0.40, 0.60 },
+ { 0.40, 0.70, 0.20, 0.20 },
+ { 0.00, 0.00, 0.10, 0.00 },
+ { 0.00, 0.00, 0.50, 0.00 },
+ { 0.20, 0.70, 0.20, 0.00 },
+ { 0.00, 1.00, 0.40, 0.80 },
+ { 0.20, 1.00, 1.00, 0.20 },
+ { 0.20, 0.20, 0.70, 0.60 },
+ { 0.40, 0.40, 1.00, 0.00 },
+ { 0.00, 0.00, 0.00, 0.30 },
+ { 0.00, 0.40, 0.00, 0.20 },
+ { 0.20, 0.12, 0.12, 0.80 },
+ { 0.00, 0.10, 0.00, 0.00 },
+ { 0.00, 0.10, 0.00, 0.00 },
+ { 0.20, 0.70, 1.00, 0.00 },
+ { 0.00, 0.70, 0.00, 0.00 },
+ { 0.60, 0.45, 0.45, 0.80 },
+ { 0.40, 0.20, 1.00, 0.60 },
+ { 1.00, 0.85, 0.85, 0.00 },
+ { 1.00, 0.70, 0.70, 0.80 },
+ { 0.70, 0.00, 0.00, 0.20 },
+ { 0.70, 0.00, 0.00, 0.40 },
+ { 0.00, 0.00, 1.00, 1.00 },
+ { 0.00, 0.10, 0.20, 0.20 },
+ { 0.40, 0.40, 0.70, 0.80 },
+ { 0.10, 0.10, 0.10, 0.00 },
+ { 1.00, 0.00, 0.10, 0.00 },
+ { 0.10, 1.00, 0.00, 0.00 },
+ { 0.70, 0.70, 0.10, 0.00 },
+ { 0.00, 0.00, 0.40, 0.20 },
+ { 1.00, 1.00, 0.70, 0.20 },
+ { 0.10, 1.00, 0.20, 0.00 },
+ { 0.20, 0.70, 0.70, 0.60 },
+ { 0.00, 0.00, 0.60, 0.00 },
+ { 1.00, 0.00, 0.00, 0.00 },
+ { 0.40, 0.10, 0.10, 0.00 },
+ { 0.00, 1.00, 0.70, 0.20 },
+ { 0.10, 0.00, 0.00, 0.00 },
+ { 0.70, 0.00, 1.00, 0.60 },
+ { 0.70, 0.40, 0.40, 0.20 },
+ { 0.70, 0.20, 0.20, 0.00 },
+ { 1.00, 0.20, 0.70, 0.60 },
+ { 0.10, 0.00, 1.00, 0.20 },
+ { 0.00, 0.00, 0.70, 0.00 },
+ { 1.00, 0.20, 1.00, 0.60 },
+ { 0.70, 1.00, 0.70, 0.00 },
+ { 0.80, 0.00, 0.00, 0.00 },
+ { 0.00, 0.00, 0.80, 0.00 },
+ { 0.70, 0.40, 0.10, 0.00 },
+ { 1.00, 0.20, 0.20, 0.20 },
+ { 0.40, 0.20, 0.00, 0.00 },
+ { 1.00, 0.00, 0.00, 0.00 },
+ { 0.70, 0.40, 1.00, 0.00 },
+ { 0.10, 0.20, 0.10, 0.00 },
+ { 0.70, 0.40, 0.70, 0.60 },
+ { 0.40, 0.70, 0.70, 0.20 },
+ { 0.70, 0.10, 0.00, 0.20 },
+ { 1.00, 1.00, 0.70, 0.60 },
+ { 0.20, 1.00, 0.10, 0.00 },
+ { 1.00, 0.00, 1.00, 0.40 },
+ { 1.00, 0.10, 0.40, 0.20 },
+ { 0.00, 0.40, 0.40, 0.40 },
+ { 1.00, 0.00, 1.00, 0.20 },
+ { 0.00, 1.00, 0.00, 0.60 },
+ { 0.10, 0.10, 0.40, 0.20 },
+ { 0.00, 0.20, 0.20, 0.20 },
+ { 0.00, 0.70, 0.00, 0.80 },
+ { 0.40, 0.20, 0.70, 0.20 },
+ { 0.20, 0.40, 0.40, 0.00 },
+ { 0.70, 0.40, 1.00, 0.20 },
+ { 0.70, 0.40, 0.00, 0.80 },
+ { 0.70, 0.20, 0.20, 0.60 },
+ { 1.00, 0.70, 0.20, 0.60 },
+ { 1.00, 0.40, 1.00, 0.00 },
+ { 1.00, 0.70, 0.20, 0.00 },
+ { 0.00, 0.03, 0.00, 0.00 },
+ { 0.40, 0.00, 0.00, 0.00 },
+ { 0.00, 1.00, 1.00, 0.60 },
+ { 0.20, 0.00, 0.00, 0.60 },
+ { 0.70, 0.70, 0.00, 0.80 },
+ { 0.10, 0.06, 0.06, 0.10 },
+ { 0.40, 0.40, 0.40, 0.00 },
+ { 0.00, 0.20, 0.00, 0.20 },
+ { 0.00, 1.00, 1.00, 0.00 },
+ { 0.00, 0.40, 0.10, 0.00 },
+ { 1.00, 0.20, 0.70, 0.20 },
+ { 0.20, 0.70, 0.70, 0.20 },
+ { 0.70, 0.00, 0.20, 0.20 },
+ { 0.70, 1.00, 0.00, 0.00 },
+ { 0.40, 1.00, 1.00, 0.60 },
+ { 1.00, 0.20, 0.70, 0.00 },
+ { 0.20, 0.10, 0.40, 0.20 },
+ { 0.70, 1.00, 1.00, 0.40 },
+ { 1.00, 0.20, 0.70, 0.00 },
+ { 0.40, 0.27, 0.27, 0.10 },
+ { 0.10, 0.10, 0.20, 0.20 },
+ { 1.00, 0.70, 1.00, 0.80 },
+ { 0.00, 0.20, 0.40, 0.60 },
+ { 0.20, 0.40, 0.40, 0.20 },
+ { 0.70, 0.70, 0.70, 0.00 },
+ { 0.20, 0.20, 0.00, 0.20 },
+ { 0.70, 0.00, 0.10, 0.00 },
+ { 0.40, 0.20, 0.00, 0.20 },
+ { 0.10, 0.10, 0.20, 0.00 },
+ { 0.00, 0.40, 0.40, 0.00 },
+ { 0.40, 1.00, 0.00, 0.80 },
+ { 0.00, 0.00, 1.00, 0.20 },
+ { 0.70, 0.20, 0.20, 0.20 },
+ { 1.00, 0.10, 1.00, 0.20 },
+ { 0.00, 0.40, 1.00, 0.80 },
+ { 0.00, 0.00, 0.00, 0.20 },
+ { 0.70, 0.70, 0.00, 0.20 },
+ { 0.70, 0.40, 0.20, 0.40 },
+ { 0.20, 0.20, 0.10, 0.20 },
+ { 0.00, 1.00, 1.00, 0.20 },
+ { 0.20, 0.10, 0.70, 0.00 },
+ { 1.00, 0.10, 0.00, 0.00 },
+ { 0.00, 0.40, 0.70, 0.20 },
+ { 0.20, 0.10, 1.00, 0.20 },
+ { 1.00, 0.00, 0.70, 0.60 },
+ { 0.40, 0.70, 1.00, 0.80 },
+ { 0.00, 0.70, 1.00, 0.80 },
+ { 0.20, 1.00, 0.20, 0.60 },
+ { 0.00, 0.70, 0.70, 0.80 },
+ { 0.70, 0.40, 0.40, 0.00 },
+ { 0.70, 0.70, 0.70, 0.00 },
+ { 0.40, 1.00, 0.40, 0.40 },
+ { 0.00, 0.00, 0.03, 0.00 },
+ { 0.20, 0.12, 0.12, 0.20 },
+ { 0.70, 0.20, 1.00, 0.20 },
+ { 0.00, 0.00, 0.20, 0.00 },
+ { 0.70, 0.70, 0.40, 0.80 },
+ { 0.00, 0.00, 0.70, 0.00 },
+ { 0.40, 0.00, 0.40, 0.80 },
+ { 0.10, 0.20, 0.40, 0.20 },
+ { 1.00, 0.70, 0.40, 0.20 },
+ { 0.70, 0.20, 0.40, 0.20 },
+ { 0.70, 1.00, 0.20, 0.00 },
+ { 0.80, 0.65, 0.65, 0.00 },
+ { 0.40, 0.70, 1.00, 0.20 },
+ { 0.20, 1.00, 0.70, 0.20 },
+ { 0.10, 0.40, 0.00, 0.20 },
+ { 1.00, 1.00, 0.00, 0.80 },
+ { 0.70, 0.20, 1.00, 0.00 },
+ { 0.00, 0.10, 0.40, 0.00 },
+ { 1.00, 0.70, 1.00, 0.00 },
+ { 0.70, 0.40, 0.10, 0.20 },
+ { 0.70, 0.20, 0.10, 0.00 },
+ { 0.40, 1.00, 1.00, 0.00 },
+ { 0.40, 0.40, 0.10, 0.20 },
+ { 0.80, 0.65, 0.65, 1.00 },
+ { 0.20, 0.00, 0.20, 0.40 },
+ { 0.70, 0.00, 0.70, 0.20 },
+ { 0.70, 1.00, 0.40, 0.40 },
+ { 1.00, 0.10, 0.70, 0.00 },
+ { 0.40, 0.70, 0.00, 0.80 },
+ { 0.70, 0.40, 0.40, 0.80 },
+ { 0.70, 0.70, 0.00, 0.00 },
+ { 0.40, 0.20, 0.40, 0.20 },
+ { 0.20, 0.00, 1.00, 0.20 },
+ { 0.00, 0.00, 0.00, 1.00 },
+ { 0.70, 0.20, 0.00, 0.60 },
+ { 0.40, 1.00, 0.10, 0.20 },
+ { 0.20, 1.00, 0.00, 0.40 },
+ { 0.20, 0.20, 0.70, 0.00 },
+ { 1.00, 0.00, 1.00, 0.70 },
+ { 0.00, 0.20, 0.20, 0.00 },
+ { 0.40, 0.40, 0.00, 0.40 },
+ { 1.00, 0.00, 0.00, 0.70 },
+ { 1.00, 0.85, 0.85, 0.80 },
+ { 0.10, 0.40, 0.40, 0.00 },
+ { 0.40, 0.70, 0.70, 0.40 },
+ { 0.40, 0.27, 0.27, 0.60 },
+ { 0.10, 0.10, 0.70, 0.00 },
+ { 0.10, 0.20, 0.70, 0.00 },
+ { 0.20, 0.70, 0.40, 0.20 },
+ { 0.00, 0.40, 0.70, 0.80 },
+ { 0.00, 0.40, 0.20, 0.20 },
+ { 0.70, 0.00, 0.00, 0.00 },
+ { 0.70, 0.40, 0.00, 0.40 },
+ { 1.00, 0.70, 0.00, 0.80 },
+ { 0.40, 0.00, 0.40, 0.70 },
+ { 0.40, 0.40, 0.00, 0.00 },
+ { 0.00, 0.00, 0.40, 0.80 },
+ { 0.00, 0.70, 0.70, 0.40 },
+ { 0.40, 0.00, 0.00, 0.40 },
+ { 0.10, 0.40, 0.40, 0.20 },
+ { 0.60, 0.45, 0.45, 0.00 },
+ { 0.10, 1.00, 0.40, 0.20 },
+ { 0.40, 0.00, 0.40, 0.40 },
+ { 0.10, 0.20, 0.70, 0.20 },
+ { 0.20, 0.00, 0.00, 0.00 },
+ { 0.00, 0.00, 0.00, 0.03 },
+ { 0.70, 0.20, 0.00, 0.00 },
+ { 1.00, 1.00, 0.40, 0.00 },
+ { 0.20, 1.00, 1.00, 0.40 },
+ { 0.20, 0.20, 0.70, 0.00 },
+ { 0.00, 0.00, 0.00, 0.70 },
+ { 1.00, 0.00, 1.00, 0.20 },
+ { 1.00, 0.70, 0.00, 0.20 },
+ { 1.00, 0.40, 0.00, 0.00 },
+ { 1.00, 0.40, 0.70, 0.20 },
+ { 0.70, 0.70, 0.70, 0.80 },
+ { 0.70, 0.70, 0.70, 0.60 },
+ { 0.00, 0.40, 1.00, 0.00 },
+ { 0.60, 0.45, 0.45, 0.60 },
+ { 0.10, 1.00, 0.70, 0.00 },
+ { 0.40, 0.00, 0.70, 0.80 },
+ { 0.20, 0.70, 0.20, 0.60 },
+ { 0.20, 0.70, 0.20, 0.40 },
+ { 0.00, 0.40, 1.00, 0.00 },
+ { 0.20, 0.70, 0.40, 0.60 },
+ { 1.00, 0.40, 0.10, 0.20 },
+ { 0.20, 0.20, 0.00, 0.00 },
+ { 0.00, 0.10, 0.10, 0.20 },
+ { 0.40, 0.00, 0.40, 0.00 },
+ { 0.70, 0.40, 0.20, 0.20 },
+ { 0.70, 0.70, 0.20, 0.60 },
+ { 1.00, 0.00, 0.70, 0.80 },
+ { 0.20, 0.00, 0.00, 0.00 },
+ { 1.00, 0.40, 0.00, 0.60 },
+ { 0.00, 0.00, 0.10, 0.00 },
+ { 0.40, 1.00, 0.70, 0.60 },
+ { 0.40, 0.20, 0.20, 0.40 },
+ { 0.20, 0.70, 1.00, 0.00 },
+ { 1.00, 0.20, 0.20, 0.40 },
+ { 1.00, 1.00, 0.40, 0.20 },
+ { 0.20, 0.20, 1.00, 0.20 },
+ { 0.20, 0.70, 0.40, 0.00 },
+ { 1.00, 0.00, 0.40, 0.00 },
+ { 0.00, 0.20, 1.00, 0.60 },
+ { 1.00, 0.40, 0.40, 0.60 },
+ { 1.00, 0.10, 1.00, 0.00 },
+ { 0.70, 0.10, 0.10, 0.20 },
+ { 0.10, 0.40, 0.70, 0.00 },
+ { 0.00, 1.00, 0.70, 0.00 },
+ { 0.00, 0.70, 0.00, 0.20 },
+ { 1.00, 0.00, 0.20, 0.60 },
+ { 0.10, 0.70, 0.40, 0.20 },
+ { 0.40, 0.00, 1.00, 0.00 },
+ { 0.10, 0.70, 0.10, 0.00 },
+ { 0.40, 0.40, 0.70, 0.00 },
+ { 0.10, 0.00, 0.00, 0.00 },
+ { 0.00, 0.70, 0.10, 0.20 },
+ { 0.70, 0.20, 0.40, 0.00 },
+ { 0.70, 0.10, 0.70, 0.20 },
+ { 1.00, 1.00, 0.20, 0.40 },
+ { 0.70, 0.00, 0.40, 0.20 },
+ { 0.40, 0.40, 0.20, 0.60 },
+ { 1.00, 0.70, 0.10, 0.20 },
+ { 0.00, 1.00, 0.00, 0.20 },
+ { 0.40, 0.70, 0.20, 0.60 },
+ { 0.40, 0.10, 1.00, 0.20 },
+ { 0.40, 1.00, 0.40, 0.00 },
+ { 0.00, 1.00, 0.00, 0.20 },
+ { 0.00, 0.00, 0.40, 0.60 },
+ { 0.40, 0.20, 0.40, 0.00 },
+ { 0.20, 0.20, 0.00, 0.40 },
+ { 0.70, 0.20, 0.70, 0.20 },
+ { 0.20, 0.70, 0.70, 0.00 },
+ { 0.40, 1.00, 0.70, 0.00 },
+ { 0.40, 0.20, 0.70, 0.40 },
+ { 0.10, 0.06, 0.06, 0.60 },
+ { 0.00, 0.40, 0.10, 0.20 },
+ { 0.20, 0.00, 0.40, 0.20 },
+ { 0.20, 0.40, 0.70, 0.00 },
+ { 0.00, 0.40, 0.00, 0.00 },
+ { 0.40, 1.00, 0.00, 0.40 },
+ { 0.00, 0.00, 0.15, 0.00 },
+ { 1.00, 0.10, 0.20, 0.20 },
+ { 0.40, 1.00, 0.00, 0.60 },
+ { 1.00, 1.00, 0.40, 0.00 },
+ { 0.70, 1.00, 0.70, 0.00 },
+ { 0.80, 0.65, 0.65, 0.80 },
+ { 0.70, 0.20, 0.00, 0.40 },
+ { 0.00, 0.40, 0.40, 0.70 },
+ { 0.40, 1.00, 0.20, 0.00 },
+ { 1.00, 0.00, 0.70, 0.00 },
+ { 0.70, 1.00, 1.00, 0.00 },
+ { 0.10, 0.00, 0.20, 0.00 },
+ { 0.70, 0.40, 0.40, 0.40 },
+ { 0.20, 0.20, 0.10, 0.00 },
+ { 0.00, 0.40, 0.00, 0.60 },
+ { 0.40, 0.70, 0.40, 0.40 },
+ { 0.10, 0.40, 0.20, 0.00 },
+ { 0.20, 0.20, 0.20, 0.60 },
+ { 0.20, 0.20, 0.40, 0.20 },
+ { 0.00, 0.20, 0.20, 0.60 },
+ { 0.20, 0.70, 0.00, 0.20 },
+ { 0.00, 1.00, 0.00, 0.00 },
+ { 0.70, 0.20, 0.70, 0.00 },
+ { 0.70, 0.10, 0.10, 0.00 },
+ { 0.00, 0.70, 0.70, 0.60 },
+ { 0.00, 0.40, 0.40, 0.40 },
+ { 0.20, 0.40, 1.00, 0.00 },
+ { 0.00, 0.70, 0.10, 0.00 },
+ { 1.00, 0.70, 0.70, 0.40 },
+ { 0.10, 1.00, 0.10, 0.00 },
+ { 0.00, 0.70, 0.40, 0.60 },
+ { 1.00, 0.70, 0.40, 0.40 },
+ { 0.70, 1.00, 0.00, 0.20 },
+ { 0.70, 0.70, 0.40, 0.60 },
+ { 1.00, 0.00, 0.40, 0.60 },
+ { 0.40, 1.00, 0.00, 0.00 },
+ { 0.20, 0.12, 0.12, 1.00 },
+ { 0.00, 0.00, 0.00, 0.60 },
+ { 0.20, 0.10, 0.10, 0.20 },
+ { 0.40, 0.40, 0.20, 0.20 },
+ { 0.00, 0.50, 0.00, 0.00 },
+ { 0.70, 1.00, 0.70, 0.20 },
+ { 0.60, 0.45, 0.45, 1.00 },
+ { 1.00, 0.70, 0.40, 0.60 },
+ { 0.40, 0.70, 0.70, 0.60 },
+ { 0.20, 0.20, 0.20, 0.40 },
+ { 1.00, 0.20, 0.40, 0.40 },
+ { 0.80, 0.65, 0.65, 0.40 },
+ { 0.00, 0.70, 0.70, 0.00 },
+ { 0.00, 0.00, 0.00, 0.25 },
+ { 0.20, 1.00, 1.00, 0.00 },
+ { 0.10, 0.06, 0.06, 0.00 },
+ { 0.20, 0.70, 0.20, 0.00 },
+ { 0.20, 0.10, 0.70, 0.20 },
+ { 1.00, 0.70, 0.10, 0.00 },
+ { 0.70, 1.00, 1.00, 0.60 },
+ { 0.10, 1.00, 0.70, 0.20 },
+ { 0.20, 0.20, 1.00, 0.00 },
+ { 0.70, 0.10, 0.40, 0.00 },
+ { 0.40, 0.00, 0.70, 0.20 },
+ { 0.00, 0.20, 0.00, 0.40 },
+ { 1.00, 1.00, 0.00, 0.60 },
+ { 0.10, 0.70, 0.00, 0.00 },
+ { 0.00, 1.00, 0.40, 0.00 },
+ { 0.70, 0.70, 0.70, 0.20 },
+ { 0.00, 0.10, 1.00, 0.00 },
+ { 1.00, 1.00, 0.10, 0.20 },
+ { 0.00, 0.00, 0.70, 0.20 },
+ { 0.00, 0.40, 0.70, 0.40 },
+ { 0.70, 0.20, 0.20, 0.00 },
+ { 0.40, 0.20, 0.70, 0.00 },
+ { 0.40, 0.10, 0.70, 0.20 },
+ { 0.00, 0.40, 0.20, 0.60 },
+ { 0.20, 0.40, 0.10, 0.00 },
+ { 0.40, 0.70, 0.40, 0.80 },
+ { 0.00, 0.00, 1.00, 0.60 },
+ { 0.10, 0.10, 0.70, 0.20 },
+ { 0.10, 0.10, 1.00, 0.20 },
+ { 0.70, 1.00, 0.70, 0.40 },
+ { 0.10, 0.00, 0.20, 0.20 },
+ { 0.05, 0.03, 0.03, 0.00 },
+ { 0.70, 1.00, 0.10, 0.20 },
+ { 1.00, 0.70, 0.70, 0.20 },
+ { 1.00, 0.20, 1.00, 0.20 },
+ { 0.20, 0.20, 1.00, 0.40 },
+ { 0.00, 0.00, 0.00, 0.20 },
+ { 1.00, 0.40, 0.70, 0.80 },
+ { 1.00, 0.40, 1.00, 0.60 },
+ { 0.40, 0.20, 0.20, 0.60 },
+ { 0.40, 0.40, 0.70, 0.20 },
+ { 0.60, 0.45, 0.45, 0.40 },
+ { 0.40, 1.00, 0.40, 0.20 },
+ { 1.00, 0.00, 0.20, 0.20 },
+ { 0.40, 0.20, 0.10, 0.00 },
+ { 0.70, 0.70, 0.00, 0.00 },
+ { 1.00, 0.00, 0.00, 0.80 },
+ { 0.20, 0.40, 0.20, 0.00 },
+ { 0.20, 0.40, 0.20, 0.40 },
+ { 0.40, 0.40, 0.00, 0.70 },
+ { 0.10, 0.70, 1.00, 0.00 },
+ { 0.00, 0.70, 0.00, 0.40 },
+ { 1.00, 0.00, 0.00, 0.20 },
+ { 0.40, 0.70, 0.20, 0.40 },
+ { 0.00, 0.00, 0.90, 0.00 },
+ { 0.00, 0.40, 0.20, 0.00 },
+ { 1.00, 0.10, 0.40, 0.00 },
+ { 0.70, 0.00, 0.40, 0.60 },
+ { 0.70, 0.40, 0.20, 0.60 },
+ { 0.00, 0.80, 0.00, 0.00 },
+ { 1.00, 0.70, 0.40, 0.00 },
+ { 0.40, 1.00, 0.20, 0.60 },
+ { 1.00, 0.70, 0.20, 0.20 },
+ { 0.00, 0.20, 0.70, 0.00 },
+ { 0.40, 0.00, 0.40, 0.20 },
+ { 0.70, 0.40, 0.70, 0.00 },
+ { 0.10, 0.10, 0.40, 0.00 },
+ { 0.70, 1.00, 1.00, 0.80 },
+ { 0.10, 0.10, 1.00, 0.00 },
+ { 0.40, 0.40, 0.10, 0.00 },
+ { 0.00, 0.00, 1.00, 0.70 },
+ { 0.70, 0.20, 0.70, 0.60 },
+ { 1.00, 0.10, 0.10, 0.20 },
+ { 0.00, 0.70, 0.00, 0.00 },
+ { 0.00, 0.70, 0.40, 0.20 },
+ { 0.40, 1.00, 1.00, 0.80 },
+ { 0.70, 1.00, 0.20, 0.20 },
+ { 0.20, 1.00, 0.70, 0.00 },
+ { 0.00, 0.20, 0.20, 0.40 },
+ { 0.00, 1.00, 0.00, 0.00 },
+ { 0.40, 0.40, 1.00, 0.60 },
+ { 0.70, 1.00, 0.00, 0.80 },
+ { 0.20, 0.00, 0.20, 0.00 },
+ { 1.00, 0.70, 1.00, 0.40 },
+ { 0.00, 0.00, 0.00, 0.10 },
+ { 0.70, 0.00, 0.20, 0.00 },
+ { 0.70, 1.00, 0.40, 0.60 },
+ { 0.40, 0.00, 0.20, 0.60 },
+ { 0.70, 0.10, 1.00, 0.00 },
+ { 0.70, 0.70, 0.20, 0.00 },
+ { 1.00, 0.40, 0.10, 0.00 },
+ { 0.10, 0.70, 0.10, 0.20 },
+ { 1.00, 0.70, 0.70, 0.60 },
+ { 0.00, 0.07, 0.00, 0.00 },
+ { 0.70, 1.00, 0.00, 0.40 },
+ { 1.00, 0.40, 0.00, 0.80 },
+ { 0.10, 0.06, 0.06, 1.00 },
+ { 1.00, 0.00, 1.00, 0.00 },
+ { 1.00, 0.40, 0.70, 0.60 },
+ { 0.70, 0.20, 0.10, 0.20 },
+ { 0.00, 1.00, 1.00, 0.00 },
+ { 0.40, 0.10, 0.10, 0.20 },
+ { 1.00, 0.00, 0.70, 0.20 },
+ { 1.00, 0.20, 0.20, 0.00 },
+ { 1.00, 0.40, 0.00, 0.20 },
+ { 0.20, 1.00, 0.70, 0.60 },
+ { 0.70, 0.00, 0.00, 0.00 },
+ { 0.10, 0.40, 0.00, 0.00 },
+ { 0.20, 0.00, 0.00, 0.20 },
+ { 0.07, 0.00, 0.00, 0.00 },
+ { 0.70, 0.20, 0.40, 0.60 },
+ { 0.00, 0.70, 0.00, 0.60 },
+ { 0.30, 0.00, 0.00, 0.00 },
+ { 0.40, 0.27, 0.27, 0.20 },
+ { 0.00, 0.40, 0.40, 0.20 },
+ { 0.20, 1.00, 0.70, 0.00 },
+ { 1.00, 0.20, 0.70, 0.40 },
+ { 0.70, 1.00, 0.70, 0.80 },
+ { 1.00, 1.00, 1.00, 0.40 },
+ { 1.00, 0.00, 0.40, 0.40 },
+ { 0.10, 0.00, 0.40, 0.20 },
+ { 0.40, 0.10, 1.00, 0.00 },
+ { 1.00, 0.00, 1.00, 0.60 },
+ { 0.00, 0.00, 0.70, 0.60 },
+ { 1.00, 0.40, 1.00, 0.80 },
+ { 0.70, 0.70, 1.00, 0.60 },
+ { 0.20, 0.00, 0.40, 0.00 },
+ { 0.40, 0.00, 0.10, 0.20 },
+ { 0.70, 0.00, 0.00, 0.60 },
+ { 0.00, 0.60, 0.00, 0.00 },
+ { 0.10, 0.70, 0.70, 0.20 },
+ { 1.00, 0.20, 1.00, 0.00 },
+ { 0.00, 0.00, 1.00, 0.00 },
+ { 0.20, 1.00, 0.20, 0.00 },
+ { 0.40, 0.40, 0.00, 0.20 },
+ { 0.40, 0.10, 0.20, 0.20 },
+ { 0.20, 0.12, 0.12, 0.00 },
+ { 0.20, 0.70, 1.00, 0.60 },
+ { 0.40, 0.70, 0.00, 0.40 },
+ { 0.00, 0.00, 0.40, 0.00 },
+ { 0.40, 0.00, 0.10, 0.00 },
+ { 0.00, 1.00, 0.40, 0.00 },
+ { 0.70, 0.70, 0.40, 0.20 },
+ { 0.20, 0.00, 0.70, 0.20 },
+ { 0.10, 1.00, 1.00, 0.00 },
+ { 0.70, 0.00, 0.70, 0.00 },
+ { 0.10, 0.70, 1.00, 0.20 },
+ { 0.00, 1.00, 1.00, 0.40 },
+ { 1.00, 0.70, 1.00, 0.20 },
+ { 0.20, 0.70, 0.40, 0.40 },
+ { 0.70, 0.70, 1.00, 0.00 },
+ { 0.40, 0.27, 0.27, 0.80 },
+ { 0.40, 0.00, 0.00, 0.60 },
+ { 0.10, 0.20, 1.00, 0.00 },
+ { 0.00, 0.00, 0.00, 0.15 },
+ { 1.00, 1.00, 1.00, 0.20 },
+ { 1.00, 0.40, 0.40, 0.20 },
+ { 0.00, 1.00, 0.00, 0.40 },
+ { 0.70, 0.40, 0.70, 0.80 },
+ { 0.40, 0.70, 0.70, 0.00 },
+ { 0.20, 0.40, 0.00, 0.40 },
+ { 0.70, 0.40, 1.00, 0.40 },
+ { 0.40, 1.00, 0.20, 0.40 },
+ { 0.40, 0.00, 1.00, 0.00 },
+ { 0.40, 0.20, 0.20, 0.00 },
+ { 0.10, 0.20, 0.20, 0.00 },
+ { 0.20, 0.70, 0.40, 0.00 },
+ { 1.00, 0.40, 0.40, 0.80 },
+ { 0.40, 0.00, 0.00, 0.20 },
+ { 0.60, 0.00, 0.00, 0.00 },
+ { 1.00, 0.40, 0.20, 0.60 },
+ { 0.40, 0.20, 1.00, 0.20 },
+ { 0.70, 0.00, 0.70, 0.60 },
+ { 0.40, 0.70, 0.20, 0.00 },
+ { 0.70, 0.00, 0.40, 0.40 },
+ { 0.20, 0.20, 0.20, 0.20 },
+ { 1.00, 0.20, 0.00, 0.00 },
+ { 0.40, 0.70, 0.00, 0.20 },
+ { 0.70, 0.40, 0.70, 0.20 },
+ { 0.70, 0.70, 0.40, 0.40 },
+ { 0.00, 0.00, 0.20, 0.40 },
+ { 0.40, 0.27, 0.27, 1.00 },
+ { 0.40, 0.40, 0.20, 0.40 },
+ { 0.00, 0.40, 0.00, 0.40 },
+ { 0.10, 0.00, 1.00, 0.00 },
+ { 0.10, 0.70, 0.40, 0.00 },
+ { 1.00, 1.00, 0.10, 0.00 },
+ { 0.20, 0.70, 0.70, 0.40 },
+ { 0.20, 0.10, 0.00, 0.00 },
+ { 0.70, 0.70, 1.00, 0.20 },
+ { 0.00, 0.70, 0.40, 0.40 },
+ { 1.00, 0.20, 0.40, 0.60 },
+ { 1.00, 1.00, 0.00, 0.70 },
+ { 0.00, 0.00, 0.70, 0.40 },
+ { 0.10, 0.10, 0.00, 0.00 },
+ { 0.10, 0.00, 0.00, 0.20 },
+ { 0.70, 0.00, 0.40, 0.00 },
+ { 0.00, 1.00, 0.70, 0.60 },
+ { 0.40, 0.00, 0.70, 0.40 },
+ { 0.20, 0.12, 0.12, 0.60 },
+ { 0.40, 0.40, 0.20, 0.00 },
+ { 0.80, 0.65, 0.65, 0.60 },
+ { 1.00, 1.00, 1.00, 0.80 },
+ { 0.20, 0.00, 0.40, 0.60 },
+ { 0.20, 1.00, 0.40, 0.20 },
+ { 0.15, 0.00, 0.00, 0.00 },
+ { 1.00, 1.00, 0.70, 0.40 },
+ { 0.20, 0.10, 0.20, 0.00 },
+ { 0.10, 0.70, 0.00, 0.20 },
+ { 0.00, 1.00, 0.00, 0.80 },
+ { 0.00, 0.00, 0.20, 0.60 },
+ { 0.70, 0.00, 0.20, 0.60 },
+ { 0.20, 0.40, 0.70, 0.40 },
+ { 0.00, 0.70, 0.20, 0.00 },
+ { 0.20, 0.40, 0.20, 0.60 },
+ { 0.00, 0.00, 0.10, 0.20 },
+ { 0.70, 0.70, 0.10, 0.20 },
+ { 1.00, 1.00, 0.70, 0.80 },
+ { 0.40, 0.40, 0.00, 0.40 },
+ { 0.40, 0.70, 0.40, 0.00 },
+ { 0.00, 0.00, 0.07, 0.00 },
+ { 1.00, 0.20, 0.40, 0.20 },
+ { 1.00, 0.20, 0.40, 0.00 },
+ { 0.20, 0.70, 0.10, 0.00 },
+ { 0.00, 0.70, 1.00, 0.00 },
+ { 1.00, 0.10, 0.70, 0.20 },
+ { 0.00, 0.20, 0.00, 0.60 },
+ { 1.00, 0.70, 1.00, 0.00 },
+ { 0.00, 1.00, 0.00, 1.00 },
+ { 0.20, 0.40, 1.00, 0.40 },
+ { 1.00, 0.70, 0.70, 0.00 },
+ { 0.40, 0.10, 0.40, 0.00 }
+};
+
+/* Open the file, return NULL on error */
+FILE *open_928(char *filename) {
+ FILE *fp;
+
+ if ((fp = fopen(filename,"r")) == NULL)
+ return NULL;
+
+ return fp;
+}
+
+/* return non-zero on error */
+int next_928(FILE *fp, int i, double *cmyk) {
+ char buf[200];
+ double w;
+
+ if (fp != NULL) { /* We're reading from a file */
+
+ if (fgets(buf, 200, fp) == NULL)
+ return 1;
+
+ if (sscanf(buf, " %lf %lf %lf %lf %lf", &cmyk[0], &cmyk[1], &cmyk[2], &cmyk[3], &w) != 5)
+ return 1;
+
+ } else { /* Fetch it from our array */
+ cmyk[0] = refvs[i][0];
+ cmyk[1] = refvs[i][1];
+ cmyk[2] = refvs[i][2];
+ cmyk[3] = refvs[i][3];
+ }
+
+ return 0;
+}
+
+void close_928(FILE *fp) {
+ fclose(fp);
+}
+
+
+
diff --git a/profile/mppcheck.c b/profile/mppcheck.c
new file mode 100644
index 0000000..2efe878
--- /dev/null
+++ b/profile/mppcheck.c
@@ -0,0 +1,588 @@
+
+/*
+ * Argyll Color Correction System
+ * Color Printer Device Model Profile checker.
+ * Check an mpp profile against a .ti3 file.
+ *
+ * Author: Graeme W. Gill
+ * Date: 19/3/2003
+ *
+ * Copyright 2003 Graeme W. Gill
+ * All rights reserved.
+ *
+ * This material is licenced under the GNU AFFERO GENERAL PUBLIC LICENSE Version 3 :-
+ * see the License.txt file for licencing details.
+ */
+
+#undef DEBUG
+
+#define verbo stdout
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <math.h>
+#include <sys/types.h>
+#include <time.h>
+#if defined(__IBMC__) && defined(_M_IX86)
+#include <float.h>
+#endif
+#include "copyright.h"
+#include "aconfig.h"
+#include "numlib.h"
+#include "cgats.h"
+#include "xicc.h"
+#include "prof.h"
+#include "sort.h"
+
+void
+usage(void) {
+ fprintf(stderr,"Check Model Printer Profile, Version %s\n",ARGYLL_VERSION_STR);
+ fprintf(stderr,"Author: Graeme W. Gill, licensed under the AGPL Version 3\n");
+ fprintf(stderr,"usage: %s [-v] [-c] [-s] [-y] values.ti3 profile.mpp\n",error_program);
+ fprintf(stderr," -v Verbose mode\n");
+ fprintf(stderr," -c Show CIE94 delta E values\n");
+ fprintf(stderr," -k Show CIEDE2000 delta E values\n");
+ fprintf(stderr," -s Check spectral model too\n");
+ fprintf(stderr," -y Detail each value\n");
+ fprintf(stderr," values.ti3 Test values to check against\n");
+ fprintf(stderr," profile.mpp Profile to check\n");
+ exit(1);
+ }
+
+int main(int argc, char *argv[])
+{
+ int fa,nfa; /* current argument we're looking at */
+ int verb = 0;
+ int cie94 = 0; /* Display CIE94 delta E */
+ int cie2k = 0; /* Display CIEDE2000 delta E */
+ int verify = 0;
+ int ospec = 0; /* Output spectral model flag */
+ static char ti3name[200] = { 0 }; /* Input cgats file base name */
+ static char mppname[200] = { 0 }; /* Profile file base name */
+
+ int i, j;
+ int ti; /* Temporary index */
+ cgats *icg; /* input cgats structure */
+ int devmask; /* ICX ink mask of device space */
+ int devchan; /* Number of chanels in device space */
+ int isLab = 0; /* Flag indicating whether PCS is XYZ or Lab */
+ int isDisplay = 0; /* Flag indicating that this is a display device, not output */
+ double limit = -1.0; /* Ink limit */
+ instType itype = instUnknown; /* Spectral instrument type */
+ int spec_n = 0; /* Number of spectral bands, 0 if not valid */
+ double spec_wl_short = 0.0; /* First reading wavelength in nm (shortest) */
+ double spec_wl_long = 0.0; /* Last reading wavelength in nm (longest) */
+ double norm = 0.0; /* Normalising scale value */
+ int nodp; /* Number of test patches */
+ mppcol *cols; /* Test patches */
+ mpp *p; /* Model Printer Profile */
+
+ double merr = 0.0;
+ double aerr = 0.0;
+ double nsamps = 0.0;
+
+#if defined(__IBMC__) && defined(_M_IX86)
+ _control87(EM_UNDERFLOW, EM_UNDERFLOW);
+ _control87(EM_OVERFLOW, EM_OVERFLOW);
+#endif
+ error_program = argv[0];
+
+ if (argc <= 1)
+ usage();
+
+ /* Process the arguments */
+ for(fa = 1;fa < argc;fa++) {
+
+ nfa = fa; /* skip to nfa if next argument is used */
+ if (argv[fa][0] == '-') { /* Look for any flags */
+ char *na = NULL; /* next argument after flag, null if none */
+
+ if (argv[fa][2] != '\000')
+ na = &argv[fa][2]; /* next is directly after flag */
+ else {
+ if ((fa+1) < argc) {
+ if (argv[fa+1][0] != '-') {
+ nfa = fa + 1;
+ na = argv[nfa]; /* next is seperate non-flag argument */
+ }
+ }
+ }
+
+ if (argv[fa][1] == '?')
+ usage();
+
+ else if (argv[fa][1] == 'v' || argv[fa][1] == 'V')
+ verb = 1;
+
+ else if (argv[fa][1] == 'c' || argv[fa][1] == 'C') {
+ cie94 = 1;
+ cie2k = 0;
+ }
+
+ else if (argv[fa][1] == 'k' || argv[fa][1] == 'K') {
+ cie94 = 0;
+ cie2k = 1;
+ }
+
+ /* Verify model against input points */
+ else if (argv[fa][1] == 'y' || argv[fa][1] == 'Y')
+ verify = 1;
+
+ /* Check spectral model */
+ else if (argv[fa][1] == 's' || argv[fa][1] == 'S')
+ ospec = 1;
+
+ else
+ usage();
+ } else
+ break;
+ }
+
+ /* Get the file name argument */
+ if (fa >= argc || argv[fa][0] == '-') usage();
+ strcpy(ti3name,argv[fa++]);
+
+ if (fa >= argc || argv[fa][0] == '-') usage();
+ strcpy(mppname,argv[fa++]);
+
+ /* Open and look at the .ti3 profile patches file */
+ icg = new_cgats(); /* Create a CGATS structure */
+ icg->add_other(icg, "CTI3"); /* our special input type is Calibration Target Information 3 */
+
+ if (icg->read_name(icg, ti3name))
+ error("CGATS file read error : %s",icg->err);
+
+ if (icg->ntables == 0 || icg->t[0].tt != tt_other || icg->t[0].oi != 0)
+ error ("Input file isn't a CTI3 format file");
+ if (icg->ntables != 1)
+ error ("Input file doesn't contain exactly one table");
+
+ /* If we requested spectral, check that it is available */
+ if (ospec) {
+ if ((ti = icg->find_kword(icg, 0, "SPECTRAL_BANDS")) < 0) {
+ if (ospec) {
+ error ("No spectral data, so no spectral model output");
+ ospec = 0; /* Can't output spectral model */
+ }
+ } else {
+ spec_n = atoi(icg->t[0].kdata[ti]);
+ if (spec_n > MPP_MXBANDS) {
+ error ("MPP can't cope with %d spectral components", spec_n);
+ ospec = 0; /* Can't output spectral model */
+ /* Alternative would be to downsample the spectrum to fit */
+ }
+ }
+ }
+
+ /* read the device class, and call function to create profile. */
+ if ((ti = icg->find_kword(icg, 0, "DEVICE_CLASS")) < 0)
+ error ("Input file doesn't contain keyword DEVICE_CLASS");
+
+ if (strcmp(icg->t[0].kdata[ti],"OUTPUT") == 0) {
+ isDisplay = 0;
+ } else if (strcmp(icg->t[0].kdata[ti],"DISPLAY") == 0) {
+ isDisplay = 1;
+ } else {
+ error ("Input file must be for an output device");
+ }
+
+ /* Deal with color representation of input */
+ {
+ char *buf;
+ char *inc, *outc;
+
+ if ((ti = icg->find_kword(icg, 0, "COLOR_REP")) < 0)
+ error("Input file doesn't contain keyword COLOR_REPS");
+
+ if ((buf = strdup(icg->t[0].kdata[ti])) == NULL)
+ error("Malloc failed - color rep");
+
+ /* Split COLOR_REP into device and PCS space */
+ inc = buf;
+ if ((outc = strchr(buf, '_')) == NULL)
+ error("COLOR_REP '%s' invalid", icg->t[0].kdata[ti]);
+ *outc++ = '\000';
+
+ if (strcmp(outc, "XYZ") == 0)
+ isLab = 0;
+ else if (strcmp(outc, "LAB") == 0)
+ isLab = 1;
+ else
+ error("COLOR_REP '%s' invalid (Neither XYZ nor LAB)", icg->t[0].kdata[ti]);
+
+ devmask = icx_char2inkmask(inc);
+ devchan = icx_noofinks(devmask);
+
+ if (devchan == 0)
+ error("COLOR_REP '%s' invalid (No matching devmask)", icg->t[0].kdata[ti]);
+
+ if ((nodp = icg->t[0].nsets) <= 0)
+ error ("No sets of data");
+
+ free(buf);
+ }
+
+ /* Deal with ink limit */
+ if ((ti = icg->find_kword(icg, 0, "TOTAL_INK_LIMIT")) >= 0) {
+ double imax;
+ imax = atof(icg->t[0].kdata[ti]);
+ if (imax > 1e-4 && imax <= (ICX_MXINKS * 100.0)) {
+ if (limit > 1e-4 && limit <= (ICX_MXINKS * 100.0)) {
+ /* User has specified limit as option */
+ if (imax < limit) {
+ warning("Ink limit greater than original chart! (%f > %f)",limit,imax);
+ }
+ } else {
+ if (imax > 80.0)
+ limit = imax - 10.0; /* Rule of thumb - 10% below chart maximum */
+ else
+ limit = imax;
+ }
+ }
+ }
+
+ if (limit > 1e-4 && limit <= (ICX_MXINKS * 100.0)) {
+ if (verb)
+ printf("Total ink limit being used is %f\n",limit);
+ limit = limit/100.0; /* Set a total ink limit */
+ } else {
+ if (verb)
+ printf("No total ink limit being used\n");
+ limit = 0.0; /* Don't use a limit */
+ }
+
+ if (ospec && !isDisplay) {
+
+ /* Deal with instrument type */
+ if ((ti = icg->find_kword(icg, 0, "TARGET_INSTRUMENT")) < 0)
+ error ("Can't find target instrument needed for FWA compensation");
+
+ if ((itype = inst_enum(icg->t[0].kdata[ti])) == instUnknown)
+ error ("Unrecognised target instrument '%s'", icg->t[0].kdata[ti]);
+ }
+
+ if (verb)
+ printf("Device has %d colorants, key = '%s', %s\n", devchan, icx_inkmask2char(devmask, 1),
+ devmask & ICX_ADDITIVE ? "Additive" : "Subtractive");
+
+ if ((cols = new_mppcols(nodp, devchan, spec_n)) == NULL)
+ error("Malloc failed! - cols (%d colors x %d bytes",nodp,sizeof(mppcol));
+
+ /* Read in all the patch values */
+ {
+ int chix[ICX_MXINKS];
+ char *bident;
+ int ii, Xi, Yi, Zi;
+ xspect sp;
+ char buf[100];
+ int spi[XSPECT_MAX_BANDS]; /* CGATS indexes for each wavelength */
+
+ bident = icx_inkmask2char(devmask, 0);
+
+ /* Find the device value fields */
+ for (j = 0; j < devchan; j++) {
+ int ii, imask;
+ char fname[100];
+
+ imask = icx_index2ink(devmask, j);
+ sprintf(fname,"%s_%s",bident,icx_ink2char(imask));
+
+ if ((ii = icg->find_field(icg, 0, fname)) < 0)
+ error ("Input file doesn't contain field %s",fname);
+ if (icg->t[0].ftype[ii] != r_t)
+ error ("Field %s is wrong type",fname);
+
+ chix[j] = ii;
+ }
+
+ if (isLab) { /* Expect Lab */
+ if (verb)
+ printf("Using the instruments Lab values\n");
+ if ((Xi = icg->find_field(icg, 0, "LAB_L")) < 0)
+ error("Input file doesn't contain field LAB_L");
+ if (icg->t[0].ftype[Xi] != r_t)
+ error("Field LAB_L is wrong type");
+ if ((Yi = icg->find_field(icg, 0, "LAB_A")) < 0)
+ error("Input file doesn't contain field LAB_A");
+ if (icg->t[0].ftype[Yi] != r_t)
+ error("Field LAB_A is wrong type");
+ if ((Zi = icg->find_field(icg, 0, "LAB_B")) < 0)
+ error("Input file doesn't contain field LAB_B");
+ if (icg->t[0].ftype[Zi] != r_t)
+ error("Field LAB_B is wrong type");
+
+ } else { /* Expect XYZ */
+ if (verb)
+ printf("Using the instruments XYZ values\n");
+ if ((Xi = icg->find_field(icg, 0, "XYZ_X")) < 0)
+ error("Input file doesn't contain field XYZ_X");
+ if (icg->t[0].ftype[Xi] != r_t)
+ error("Field XYZ_X is wrong type");
+ if ((Yi = icg->find_field(icg, 0, "XYZ_Y")) < 0)
+ error("Input file doesn't contain field XYZ_Y");
+ if (icg->t[0].ftype[Yi] != r_t)
+ error("Field XYZ_Y is wrong type");
+ if ((Zi = icg->find_field(icg, 0, "XYZ_Z")) < 0)
+ error("Input file doesn't contain field XYZ_Z");
+ if (icg->t[0].ftype[Zi] != r_t)
+ error("Field XYZ_Z is wrong type");
+ }
+
+ /* If we need the spectral information, find the fields */
+ if (ospec) {
+ if ((ii = icg->find_kword(icg, 0, "SPECTRAL_BANDS")) < 0)
+ error ("Input file doesn't contain keyword SPECTRAL_BANDS");
+ sp.spec_n = atoi(icg->t[0].kdata[ii]);
+ if ((ii = icg->find_kword(icg, 0, "SPECTRAL_START_NM")) < 0)
+ error ("Input file doesn't contain keyword SPECTRAL_START_NM");
+ sp.spec_wl_short = atof(icg->t[0].kdata[ii]);
+ if ((ii = icg->find_kword(icg, 0, "SPECTRAL_END_NM")) < 0)
+ error ("Input file doesn't contain keyword SPECTRAL_END_NM");
+ sp.spec_wl_long = atof(icg->t[0].kdata[ii]);
+ sp.norm = 100.0;
+
+ /* Find the fields for spectral values */
+ for (j = 0; j < sp.spec_n; j++) {
+ int nm;
+
+ /* Compute nearest integer wavelength */
+ nm = (int)(sp.spec_wl_short + ((double)j/(sp.spec_n-1.0))
+ * (sp.spec_wl_long - sp.spec_wl_short) + 0.5);
+
+ sprintf(buf,"SPEC_%03d",nm);
+
+ if ((spi[j] = icg->find_field(icg, 0, buf)) < 0)
+ error("Input file doesn't contain field %s",buf);
+ }
+
+ /* Record spectral parameters */
+ spec_n = sp.spec_n;
+ spec_wl_short = sp.spec_wl_short;
+ spec_wl_long = sp.spec_wl_long;
+ norm = sp.norm;
+ } else {
+ spec_n = 0; /* Not using spectral in model */
+ }
+
+ /* Load up all the patch values */
+ for (i = 0; i < nodp; i++) {
+
+ /* read in device values */
+ for (j = 0; j < devchan; j++)
+ cols[i].nv[j] = *((double *)icg->t[0].fdata[i][chix[j]])/100.0;
+
+ /* Read the spectral values for this patch */
+ if (ospec) {
+
+ /* norm takes care of 100 scale */
+ for (j = 0; j < sp.spec_n; j++) {
+ sp.spec[j] = *((double *)icg->t[0].fdata[i][spi[j]]);
+ if (ospec)
+ cols[i].band[3+j] = sp.spec[j];
+ }
+ }
+
+ /* Use the instrument CIE values */
+ if (isLab) {
+ cols[i].band[0] = *((double *)icg->t[0].fdata[i][Xi]);
+ cols[i].band[1] = *((double *)icg->t[0].fdata[i][Yi]);
+ cols[i].band[2] = *((double *)icg->t[0].fdata[i][Zi]);
+ icmLab2XYZ(&icmD50, cols[i].band, cols[i].band);
+ } else {
+ cols[i].band[0] = *((double *)icg->t[0].fdata[i][Xi])/100.0;
+ cols[i].band[1] = *((double *)icg->t[0].fdata[i][Yi])/100.0;
+ cols[i].band[2] = *((double *)icg->t[0].fdata[i][Zi])/100.0;
+ }
+ }
+
+ free(bident);
+
+ } /* End of reading in CGATs file */
+
+ /* Done with inputs to mpp->create() */
+ icg->del(icg);
+
+ merr = 0.0;
+ aerr = 0.0;
+ nsamps = 0.0;
+
+ if ((p = new_mpp()) == NULL)
+ error("Failed to create an mpp");
+
+ if (p->read_mpp(p, mppname))
+ error("Read error : %s",p->err);
+
+ /* Set just PCS and use XYZ model */
+ p->set_ilob(p, icxIT_default, NULL, icxOT_default, NULL, icSigLabData, 0);
+
+ for (i = 0; i < nodp; i++) {
+ double out[3], ref[3];
+ double mxd;
+
+ /* Lookup the profile PCS value for this data point */
+ p->lookup(p, out, cols[i].nv);
+
+ /* Convert our cols data to Lab */
+ icmXYZ2Lab(&icmD50, ref, cols[i].band);
+
+ if (verify && verb) {
+ printf("[%f] ", cie2k ? icmCIE2K(ref, out) :
+ cie94 ? icmCIE94(ref, out) : icmLabDE(ref, out));
+ for (j = 0; j < devchan; j++)
+ printf("%6.4f ", cols[i].nv[j]);
+ printf("-> %5.1f %5.1f %5.1f should be %5.1f %5.1f %5.1f\n",
+ out[0],out[1],out[2], ref[0],ref[1],ref[2]);
+ }
+
+ /* Check the result */
+ mxd = cie2k ? icmCIE2K(ref, out) : cie94 ? icmCIE94(ref, out) : icmLabDE(ref, out);
+ if (mxd > merr)
+ merr = mxd;
+
+ aerr += mxd;
+ nsamps++;
+ }
+ printf("Read profile %s check complete, avg err = %f, max err = %f\n",
+ cie2k ? "CIEDE2000" : cie94 ? "CIE94" : "Lab", aerr/nsamps, merr); fflush(stdout);
+
+ if (ospec) {
+ merr = 0.0;
+ aerr = 0.0;
+ nsamps = 0.0;
+
+ for (i = 0; i < nodp; i++) {
+ xspect out;
+ double avd, mxd;
+
+ /* Lookup the profile spectral value for this data point */
+ p->lookup_spec(p, &out, cols[i].nv);
+
+ if (spec_n != out.spec_n)
+ error("Mismatch between original spectral and returned");
+
+ avd = mxd = 0.0;
+ for (j = 0; j < spec_n; j++) {
+ double ded;
+ ded = fabs(out.spec[j]/out.norm - cols[i].band[3+j]/norm);
+ avd += ded;
+ if (ded > mxd)
+ mxd = ded;
+ }
+ avd /= (double)spec_n;
+
+ if (verify && verb) {
+ printf("[%f %f] ", avd, mxd);
+ for (j = 0; j < devchan; j++)
+ printf("%6.4f ", cols[i].nv[j]);
+ printf("-> ");
+ for (j = 0; j < spec_n; j++)
+ printf("%2.0f ", out.spec[j]);
+
+ printf("should be ");
+ for (j = 0; j < spec_n; j++)
+ printf("%2.0f ", cols[i].band[3+j]);
+ printf("\n");
+ }
+
+ if (mxd > merr)
+ merr = mxd;
+
+ aerr += avd;
+ nsamps++;
+ }
+ printf("profile spectral check complete, avg err = %f%%, max err = %f%%\n",
+ aerr * 100.0/nsamps, merr * 100.0); fflush(stdout);
+
+ /* Check spectrally derived Lab values */
+ {
+ xsp2cie *sc;
+ xspect sp;
+
+ if ((sc = new_xsp2cie(icxIT_D50, NULL, icxOT_CIE_1931_2, NULL, icSigLabData, icxClamp)) == NULL)
+ error("Failed to create xsp2cie object");
+
+ /* Set standard D50 viewer & illum. */
+ p->set_ilob(p, icxIT_D50, NULL, icxOT_CIE_1931_2, NULL, icSigLabData, 0);
+
+ merr = 0.0;
+ aerr = 0.0;
+ nsamps = 0.0;
+
+ for (i = 0; i < nodp; i++) {
+ double out[3], ref[3];
+ double mxd;
+
+ /* Lookup the profile PCS value for this data point */
+ p->lookup(p, out, cols[i].nv);
+
+ /* Convert our cols ref data to Lab */
+ sp.spec_n = spec_n;
+ sp.spec_wl_short = spec_wl_short;
+ sp.spec_wl_long = spec_wl_long;
+ sp.norm = norm;
+ for (j = 0; j < spec_n; j++)
+ sp.spec[j] = cols[i].band[3+j];
+ sc->convert(sc, ref, &sp);
+
+ if (verify && verb) {
+ printf("[%f] ", cie2k ? icmCIE2K(ref, out) :
+ cie94 ? icmCIE94(ref, out) : icmLabDE(ref, out));
+ for (j = 0; j < devchan; j++)
+ printf("%6.4f ", cols[i].nv[j]);
+ printf("-> %5.1f %5.1f %5.1f should be %5.1f %5.1f %5.1f\n",
+ out[0],out[1],out[2], ref[0],ref[1],ref[2]);
+ }
+
+ /* Check the result */
+ mxd = cie2k ? icmCIE2K(ref, out) : cie94 ? icmCIE94(ref, out) : icmLabDE(ref, out);
+ if (mxd > merr)
+ merr = mxd;
+
+ aerr += mxd;
+ nsamps++;
+ }
+ printf("Read profile spectral %s check complete, avg err = %f, max err = %f\n",
+ cie2k ? "CIEDE2000" : cie94 ? "CIE94" : "Lab", aerr/nsamps, merr);
+ fflush(stdout);
+
+ sc->del(sc);
+ }
+ }
+
+ p->del(p);
+
+ /* Clean up */
+ del_mppcols(cols, nodp, devchan, spec_n);
+
+ return 0;
+}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/profile/mppprof.c b/profile/mppprof.c
new file mode 100644
index 0000000..0c3593f
--- /dev/null
+++ b/profile/mppprof.c
@@ -0,0 +1,961 @@
+
+/*
+ * Argyll Color Correction System
+ * Color Printer Device Model Profile generator.
+ *
+ * Author: Graeme W. Gill
+ * Date: 24/2/2002
+ *
+ * Copyright 2003 Graeme W. Gill
+ * All rights reserved.
+ *
+ * This material is licenced under the GNU AFFERO GENERAL PUBLIC LICENSE Version 3 :-
+ * see the License.txt file for licencing details.
+ */
+
+/*
+ * This program takes in the scattered test chart
+ * points, and creates a model based forward printer profile
+ * (Device -> CIE + spectral), based on Neugenbauer equations.
+ * It is designed to handle an arbitrary number of colorants,
+ * and in the future, (optionaly) create an aproximate ink overlap/mixing model
+ * to allow synthesis of a forward model for a hyperthetical
+ * similar printing process with aditional inks.
+ *
+ * This code is based on profile.c, sprof.c and xlut.c
+ *
+ */
+
+/*
+ * TTBD:
+ *
+ * Add ink order and overlay modeling stuff back in, with
+ * new ink overlay model (see mpprof0.c).
+ *
+ * Add options to set extra information in xpi structure.
+ *
+ * Rather than computing XYZ based versios of the print model
+ * and ink mixing models, should compute spectrally sharpened
+ * equivalents to XYZ ??
+ *
+ * Fixup error handling in make_output_mpp()
+ *
+ */
+
+#undef DEBUG
+
+#undef DOQUAD /* Minimise error^4 */
+
+#define verbo stdout
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <math.h>
+#include <sys/types.h>
+#include <time.h>
+#if defined(__IBMC__) && defined(_M_IX86)
+#include <float.h>
+#endif
+#include "copyright.h"
+#include "aconfig.h"
+#include "cgats.h"
+#include "numlib.h"
+#include "xicc.h"
+#include "prof.h"
+#include "../h/sort.h"
+
+void
+usage(void) {
+ fprintf(stderr,"Create Model Printer Profile, Version %s\n",ARGYLL_VERSION_STR);
+ fprintf(stderr,"Author: Graeme W. Gill, licensed under the AGPL Version 3\n");
+ fprintf(stderr,"usage: %s [options] outfile\n",error_program);
+ fprintf(stderr," -v [level] Verbose mode\n");
+ fprintf(stderr," -q [lmhus] Quality - Low, Medium (def), High, Ultra, Simple\n");
+// fprintf(stderr," -q [vfmsu] Speed - Very Fast, Medium (def), Slow, Ultra Slow\n");
+ fprintf(stderr," -l limit override default ink limit, 1 - n00%%\n");
+ fprintf(stderr," -s Generate spectral model too\n");
+ fprintf(stderr," -m Generate ink mixing model\n");
+ fprintf(stderr," -y [level] Verify profile, 2 = read/write verify\n");
+ fprintf(stderr," -L Output Lab values\n");
+ fprintf(stderr," outfile Base name for input.ti3/output.mpp file\n");
+ exit(1);
+ }
+
+/* Worker function */
+static int make_output_mpp(int verb, int quality, int verify, char *inname, char *outname,
+int dolab, double ilimit, int ospec, int omix, profxinf *xpi);
+
+int main(int argc, char *argv[])
+{
+ int fa,nfa,mfa; /* current argument we're looking at */
+ int verb = 0;
+ int iquality = 1; /* Forward quality, default medium */
+ int dolab = 0;
+ int verify = 0; /* Verify each point */
+ double limit = -1.0; /* Ink limit */
+ int ospec = 0; /* Output spectral model flag */
+ int omix = 0; /* Output mixing model flag */
+ static char inname[200] = { 0 }; /* Input cgats file base name */
+ static char outname[200] = { 0 }; /* Output cgats file base name */
+ profxinf xpi; /* Extra profile information */
+
+#if defined(__IBMC__) && defined(_M_IX86)
+ _control87(EM_UNDERFLOW, EM_UNDERFLOW);
+ _control87(EM_OVERFLOW, EM_OVERFLOW);
+#endif
+ error_program = argv[0];
+ check_if_not_interactive();
+ memset((void *)&xpi, 0, sizeof(profxinf)); /* Init extra profile info to defaults */
+
+ if (argc <= 1)
+ usage();
+
+ /* Process the arguments */
+ mfa = 1; /* Expect out filename */
+ for(fa = 1;fa < argc;fa++) {
+
+ nfa = fa; /* skip to nfa if next argument is used */
+ if (argv[fa][0] == '-') { /* Look for any flags */
+ char *na = NULL; /* next argument after flag, null if none */
+
+ if (argv[fa][2] != '\000')
+ na = &argv[fa][2]; /* next is directly after flag */
+ else {
+ if ((fa+1+mfa) < argc) {
+ if (argv[fa+1][0] != '-') {
+ nfa = fa + 1;
+ na = argv[nfa]; /* next is seperate non-flag argument */
+ }
+ }
+ }
+
+ if (argv[fa][1] == '?')
+ usage();
+
+ else if (argv[fa][1] == 'v' || argv[fa][1] == 'V') {
+ fa = nfa;
+ if (na == NULL) {
+ verb = 1;
+ } else {
+ verb = atoi(na);
+ }
+ }
+
+ /* Ink Limit */
+ else if (argv[fa][1] == 'l') {
+ fa = nfa;
+ if (na == NULL) usage();
+ limit = atof(na);
+ }
+
+ /* Verify model against input points */
+ else if (argv[fa][1] == 'y' || argv[fa][1] == 'Y') {
+ fa = nfa;
+ if (na == NULL) {
+ verify = 1;
+ } else {
+ verify = atoi(na);
+ }
+ }
+
+ /* Quality */
+ else if (argv[fa][1] == 'q' || argv[fa][1] == 'Q') {
+ fa = nfa;
+ if (na == NULL) usage();
+ switch (na[0]) {
+ case 'v': /* Very fast */
+ case 'V':
+ iquality = 99;
+ break;
+ case 'f': /* fast */
+ case 'l':
+ case 'L':
+ iquality = 0;
+ break;
+ case 'm': /* medium */
+ case 'M':
+ iquality = 1;
+ break;
+ case 's': /* slow */
+ case 'h':
+ case 'H':
+ iquality = 2;
+ break;
+ case 'u': /* ultra slow */
+ case 'U':
+ iquality = 3;
+ break;
+ default:
+ usage();
+ }
+ }
+
+ /* Output spectral model */
+ else if (argv[fa][1] == 's' || argv[fa][1] == 'S')
+ ospec = 1;
+
+ /* Output mixing model */
+ else if (argv[fa][1] == 'm' || argv[fa][1] == 'M')
+ omix = 1;
+
+ /* Output Lab values rather than XYZ */
+ else if (argv[fa][1] == 'L')
+ dolab = 1;
+
+ else
+ usage();
+ } else
+ break;
+ }
+
+ /* Get the file name argument */
+ if (fa >= argc || argv[fa][0] == '-') usage();
+ strcpy(inname,argv[fa]);
+ strcat(inname,".ti3");
+ strcpy(outname,argv[fa]);
+ strcat(outname,".mpp");
+
+ if (make_output_mpp(verb, iquality, verify, inname, outname,
+ dolab, limit, ospec, omix, &xpi) != 0) {
+ error ("making mpp failed");
+ }
+
+ return 0;
+}
+
+/* ===================================== */
+/* Make a DeviceN model printing profile */
+/* return nz on error */
+static int
+make_output_mpp(
+ int verb, /* Vebosity level, 0 = none, 1 = summary, 2 = detail */
+ int quality, /* quality, 0..3 */
+ int verify, /* verify result flag */
+ char *inname, /* Input .ti3 file name */
+ char *outname, /* Output .mpp file name */
+ int dolab, /* NZ if Lab output */
+ double limit, /* Ink limit, -1.0 == default */
+ int ospec, /* Output spectral model */
+ int omix, /* Output ink mixing model */
+ profxinf *xpi /* Optional Profile creation extra data */
+) {
+ int i, j;
+ int ti; /* Temporary index */
+ cgats *icg; /* input cgats structure */
+ int devmask; /* ICX ink mask of device space */
+ int devchan; /* Number of chanels in device space */
+ int isLab = 0; /* Flag indicating whether PCS is XYZ or Lab */
+ int isDisplay = 0; /* Flag indicating that this is a display device, not output */
+ int isdnormed = 0; /* Has display data been normalised to 100 ? */
+ instType itype = instUnknown; /* Spectral instrument type */
+ int spec_n = 0; /* Number of spectral bands, 0 if not valid */
+ double spec_wl_short = 0.0; /* First reading wavelength in nm (shortest) */
+ double spec_wl_long = 0.0; /* Last reading wavelength in nm (longest) */
+ double norm = 0.0; /* Normalising scale value */
+ int nodp; /* Number of test patches */
+ mppcol *cols; /* Test patches */
+ mpp *p; /* Model Printer Profile */
+
+ /* Open and look at the .ti3 profile patches file */
+ icg = new_cgats(); /* Create a CGATS structure */
+ icg->add_other(icg, "CTI3"); /* our special input type is Calibration Target Information 3 */
+
+ if (icg->read_name(icg, inname))
+ error("CGATS file read error : %s",icg->err);
+
+ if (icg->ntables == 0 || icg->t[0].tt != tt_other || icg->t[0].oi != 0)
+ error ("Input file isn't a CTI3 format file");
+ if (icg->ntables < 1)
+ error ("Input file doesn't contain at least one table");
+
+ /* If we requested spectral, check that it is available */
+ if (ospec) {
+ if ((ti = icg->find_kword(icg, 0, "SPECTRAL_BANDS")) < 0) {
+ if (ospec) {
+ error ("No spectral data, so no spectral model output");
+ ospec = 0; /* Can't output spectral model */
+ }
+ } else {
+ spec_n = atoi(icg->t[0].kdata[ti]);
+ if (spec_n > MPP_MXBANDS) {
+ error ("MPP can't cope with %d spectral components", spec_n);
+ ospec = 0; /* Can't output spectral model */
+ /* Alternative would be to downsample the spectrum to fit */
+ }
+ }
+ }
+
+ /* read the device class, and call function to create profile. */
+ if ((ti = icg->find_kword(icg, 0, "DEVICE_CLASS")) < 0)
+ error ("Input file doesn't contain keyword DEVICE_CLASS");
+
+ if (strcmp(icg->t[0].kdata[ti],"OUTPUT") == 0) {
+ isDisplay = 0;
+ } else if (strcmp(icg->t[0].kdata[ti],"DISPLAY") == 0) {
+ isDisplay = 1;
+ } else {
+ error ("Input file must be for an output device");
+ }
+
+ /* See if the display CIE data has been normalised to Y = 100 */
+ if ((ti = icg->find_kword(icg, 0, "NORMALIZED_TO_Y_100")) < 0
+ || strcmp(icg->t[0].kdata[ti],"NO") == 0) {
+ isdnormed = 0;
+ } else {
+ isdnormed = 1;
+ }
+
+ /* Deal with color representation of input */
+ {
+ char *buf;
+ char *inc, *outc;
+
+ if ((ti = icg->find_kword(icg, 0, "COLOR_REP")) < 0)
+ error("Input file doesn't contain keyword COLOR_REPS");
+
+ if ((buf = strdup(icg->t[0].kdata[ti])) == NULL)
+ error("Malloc failed - color rep");
+
+ /* Split COLOR_REP into device and PCS space */
+ inc = buf;
+ if ((outc = strchr(buf, '_')) == NULL)
+ error("COLOR_REP '%s' invalid", icg->t[0].kdata[ti]);
+ *outc++ = '\000';
+
+ if (strcmp(outc, "XYZ") == 0)
+ isLab = 0;
+ else if (strcmp(outc, "LAB") == 0)
+ isLab = 1;
+ else
+ error("COLOR_REP '%s' invalid (Neither XYZ nor LAB)", icg->t[0].kdata[ti]);
+
+ devmask = icx_char2inkmask(inc);
+ devchan = icx_noofinks(devmask);
+
+ if (devchan == 0)
+ error("COLOR_REP '%s' invalid (No matching devmask)", icg->t[0].kdata[ti]);
+
+ if ((nodp = icg->t[0].nsets) <= 0)
+ error ("No sets of data");
+
+ free(buf);
+ }
+
+ /* Deal with ink limit */
+ if ((ti = icg->find_kword(icg, 0, "TOTAL_INK_LIMIT")) >= 0) {
+ double imax;
+ imax = atof(icg->t[0].kdata[ti]);
+ if (imax > 1e-4 && imax <= (ICX_MXINKS * 100.0)) {
+ if (limit > 1e-4 && limit <= (ICX_MXINKS * 100.0)) {
+ /* User has specified limit as option */
+ if (imax < limit) {
+ warning("Ink limit greater than original chart! (%f > %f)",limit,imax);
+ }
+ } else {
+#ifdef NEVER /* Don't need rule of thumb in MPP's ?? */
+ if (imax > 80.0)
+ limit = imax - 10.0; /* Rule of thumb - 10% below chart maximum */
+ else
+#endif /* NEVER */
+ limit = imax;
+ }
+ }
+ }
+
+ if (limit > 1e-4 && limit <= (ICX_MXINKS * 100.0)) {
+ if (verb)
+ printf("Total ink limit being used is %f\n",limit);
+ limit = limit/100.0; /* Set a total ink limit */
+ } else {
+ if (verb)
+ printf("No total ink limit being used\n");
+ limit = 0.0; /* Don't use a limit */
+ }
+
+ if (ospec && !isDisplay) {
+
+ /* Deal with instrument type */
+ if ((ti = icg->find_kword(icg, 0, "TARGET_INSTRUMENT")) < 0)
+ error ("Can't find target instrument needed for FWA compensation");
+
+ if ((itype = inst_enum(icg->t[0].kdata[ti])) == instUnknown)
+ error ("Unrecognised target instrument '%s'", icg->t[0].kdata[ti]);
+ }
+
+ if (verb)
+ printf("Device has %d colorants, key = '%s', %s\n", devchan, icx_inkmask2char(devmask, 1),
+ devmask & ICX_ADDITIVE ? "Additive" : "Subtractive");
+
+ if ((cols = new_mppcols(nodp, devchan, spec_n)) == NULL)
+ error("Malloc failed! - cols (%d colors x %d bytes",nodp,sizeof(mppcol));
+
+ /* Read in all the patch values from the CGATS file */
+ {
+ int chix[ICX_MXINKS];
+ char *bident;
+ int ii, Xi, Yi, Zi;
+ xspect sp;
+ char buf[100];
+ int spi[XSPECT_MAX_BANDS]; /* CGATS indexes for each wavelength */
+
+ bident = icx_inkmask2char(devmask, 0);
+
+ /* Find the device value fields */
+ for (j = 0; j < devchan; j++) {
+ int ii, imask;
+ char fname[100];
+
+ imask = icx_index2ink(devmask, j);
+ sprintf(fname,"%s_%s",bident,icx_ink2char(imask));
+
+ if ((ii = icg->find_field(icg, 0, fname)) < 0)
+ error ("Input file doesn't contain field %s",fname);
+ if (icg->t[0].ftype[ii] != r_t)
+ error ("Field %s is wrong type",fname);
+
+ chix[j] = ii;
+ }
+
+ if (isLab) { /* Expect Lab */
+ if (verb)
+ printf("Using the instruments Lab values\n");
+ if ((Xi = icg->find_field(icg, 0, "LAB_L")) < 0)
+ error("Input file doesn't contain field LAB_L");
+ if (icg->t[0].ftype[Xi] != r_t)
+ error("Field LAB_L is wrong type");
+ if ((Yi = icg->find_field(icg, 0, "LAB_A")) < 0)
+ error("Input file doesn't contain field LAB_A");
+ if (icg->t[0].ftype[Yi] != r_t)
+ error("Field LAB_A is wrong type");
+ if ((Zi = icg->find_field(icg, 0, "LAB_B")) < 0)
+ error("Input file doesn't contain field LAB_B");
+ if (icg->t[0].ftype[Zi] != r_t)
+ error("Field LAB_B is wrong type");
+
+ } else { /* Expect XYZ */
+ if (verb)
+ printf("Using the instruments XYZ values\n");
+ if ((Xi = icg->find_field(icg, 0, "XYZ_X")) < 0)
+ error("Input file doesn't contain field XYZ_X");
+ if (icg->t[0].ftype[Xi] != r_t)
+ error("Field XYZ_X is wrong type");
+ if ((Yi = icg->find_field(icg, 0, "XYZ_Y")) < 0)
+ error("Input file doesn't contain field XYZ_Y");
+ if (icg->t[0].ftype[Yi] != r_t)
+ error("Field XYZ_Y is wrong type");
+ if ((Zi = icg->find_field(icg, 0, "XYZ_Z")) < 0)
+ error("Input file doesn't contain field XYZ_Z");
+ if (icg->t[0].ftype[Zi] != r_t)
+ error("Field XYZ_Z is wrong type");
+ }
+
+ /* If we need the spectral information, find the fields */
+ if (ospec) {
+ if ((ii = icg->find_kword(icg, 0, "SPECTRAL_BANDS")) < 0)
+ error ("Input file doesn't contain keyword SPECTRAL_BANDS");
+ sp.spec_n = atoi(icg->t[0].kdata[ii]);
+ if ((ii = icg->find_kword(icg, 0, "SPECTRAL_START_NM")) < 0)
+ error ("Input file doesn't contain keyword SPECTRAL_START_NM");
+ sp.spec_wl_short = atof(icg->t[0].kdata[ii]);
+ if ((ii = icg->find_kword(icg, 0, "SPECTRAL_END_NM")) < 0)
+ error ("Input file doesn't contain keyword SPECTRAL_END_NM");
+ sp.spec_wl_long = atof(icg->t[0].kdata[ii]);
+ sp.norm = 1.0; /* MPP uses norm of 1.0 */
+
+ /* Find the fields for spectral values */
+ for (j = 0; j < sp.spec_n; j++) {
+ int nm;
+
+ /* Compute nearest integer wavelength */
+ nm = (int)(sp.spec_wl_short + ((double)j/(sp.spec_n-1.0))
+ * (sp.spec_wl_long - sp.spec_wl_short) + 0.5);
+
+ sprintf(buf,"SPEC_%03d",nm);
+
+ if ((spi[j] = icg->find_field(icg, 0, buf)) < 0)
+ error("Input file doesn't contain field %s",buf);
+ }
+ }
+
+ if (ospec) { /* Record spectral parameters */
+ spec_n = sp.spec_n;
+ spec_wl_short = sp.spec_wl_short;
+ spec_wl_long = sp.spec_wl_long;
+ norm = sp.norm;
+ } else {
+ spec_n = 0; /* Not using spectral in model */
+ }
+
+ /* Load up all the patch values */
+ for (i = 0; i < nodp; i++) {
+
+ /* read in device values */
+ for (j = 0; j < devchan; j++)
+ cols[i].nv[j] = *((double *)icg->t[0].fdata[i][chix[j]])/100.0;
+
+ /* Read the spectral values for this patch */
+ if (ospec) {
+
+ for (j = 0; j < sp.spec_n; j++) {
+ sp.spec[j] = *((double *)icg->t[0].fdata[i][spi[j]]);
+ if (ospec) {
+ if (!isDisplay || isdnormed)
+ cols[i].band[3+j] = sp.spec[j]/100.0; /* Convert to 1.0 norm */
+ else
+ cols[i].band[3+j] = sp.spec[j]; /* Absolute */
+ }
+ }
+ }
+
+ /* Use the instrument CIE values */
+ if (isLab) {
+ cols[i].band[0] = *((double *)icg->t[0].fdata[i][Xi]);
+ cols[i].band[1] = *((double *)icg->t[0].fdata[i][Yi]);
+ cols[i].band[2] = *((double *)icg->t[0].fdata[i][Zi]);
+ icmLab2XYZ(&icmD50, cols[i].band, cols[i].band);
+ } else {
+ cols[i].band[0] = *((double *)icg->t[0].fdata[i][Xi]);
+ cols[i].band[1] = *((double *)icg->t[0].fdata[i][Yi]);
+ cols[i].band[2] = *((double *)icg->t[0].fdata[i][Zi]);
+
+ /* Convert % to 1.0 scale */
+ if (!isDisplay || isdnormed) {
+ cols[i].band[0] /= 100.0;
+ cols[i].band[1] /= 100.0;
+ cols[i].band[2] /= 100.0;
+ }
+ }
+ }
+
+ free(bident);
+
+ /* Normalize display values to Y = 1.0 for display */
+ if (isDisplay && !isdnormed) {
+
+ /* XYZ not already normed */
+ if (isdnormed == 0) {
+ double scale = -1e6;
+
+ /* Locate max Y */
+ for (i = 0; i < nodp; i++) {
+ if (cols[i].band[1] > scale)
+ scale = cols[i].band[1];
+ }
+
+ scale = 1.0/scale;
+
+ for (i = 0; i < nodp; i++) {
+ cols[i].band[0] *= scale;
+ cols[i].band[1] *= scale;
+ cols[i].band[2] *= scale;
+ }
+
+ /* Keep spectral consistent, but won't necessarily */
+ /* give Y = 1.0 for a non 1931_2 observer. */
+ if (ospec) {
+ for (i = 0; i < nodp; i++) {
+ for (j = 0; j < sp.spec_n; j++) {
+ cols[i].band[3+j] *= scale;
+ }
+ }
+ }
+ }
+ }
+ } /* End of reading in CGATs file */
+
+ /* Create the mpp */
+ if ((p = new_mpp()) == NULL)
+ return 1;
+
+ /* Create from scattered data */
+ if (p->create(p, verb, quality, isDisplay, limit, devmask, spec_n, spec_wl_short, spec_wl_long,
+ norm, itype, nodp, cols) != 0) {
+ return 1;
+ }
+
+ /* Done with inputs to mpp->create() */
+ icg->del(icg);
+
+ /* Estimate the ink mixing model */
+ if (omix) {
+ printf("The ink mixing model isn't implimented here yet\n");
+ }
+
+ /* create and write the cgats profile */
+ if (p->write_mpp(p, outname, dolab))
+ error("Write error : %s",p->err);
+
+ /* Check the forward profile accuracy against the data points */
+ if (verb || verify) {
+ double merr = 0.0;
+ double aerr = 0.0;
+ double nsamps = 0.0;
+
+ /* Set just PCS and use XYZ model */
+ p->set_ilob(p, icxIT_default, NULL, icxOT_default, NULL, icSigLabData, 0);
+
+ for (i = 0; i < nodp; i++) {
+ double out[3], ref[3];
+ double mxd;
+
+ /* Lookup the profile PCS value for this data point */
+ p->lookup(p, out, cols[i].nv);
+
+ /* Convert our cols data to Lab */
+ icmXYZ2Lab(&icmD50, ref, cols[i].band);
+
+ if ((verify && verb) || verb >= 2) {
+ printf("[%f] ", icmLabDE(ref, out));
+ for (j = 0; j < devchan; j++)
+ printf("%6.4f ", cols[i].nv[j]);
+ printf("-> %5.1f %5.1f %5.1f should be %5.1f %5.1f %5.1f\n",
+ out[0],out[1],out[2], ref[0],ref[1],ref[2]);
+ }
+
+ /* Check the result */
+ mxd = icmLabDE(ref, out);
+ if (mxd > merr)
+ merr = mxd;
+
+ aerr += mxd;
+ nsamps++;
+ }
+ printf("Profile Lab check complete, peak err = %f, avg err = %f\n",
+ merr, aerr/nsamps);
+
+ if (ospec) {
+ double maxsp = -1e6;
+ merr = 0.0;
+ aerr = 0.0;
+ nsamps = 0.0;
+
+ for (i = 0; i < nodp; i++) {
+ xspect out;
+ double avd, mxd;
+
+ /* Lookup the profile spectral value for this data point */
+ p->lookup_spec(p, &out, cols[i].nv);
+
+ if (spec_n != out.spec_n)
+ error("Mismatch between original spectral and returned");
+
+ avd = mxd = 0.0;
+ for (j = 0; j < spec_n; j++) {
+ double ded;
+ if (out.spec[j] > maxsp)
+ maxsp = out.spec[j];
+ if (cols[i].band[3+j] > maxsp)
+ maxsp = out.spec[j];
+ ded = fabs(out.spec[j] - cols[i].band[3+j]);
+ avd += ded;
+ if (ded > mxd)
+ mxd = ded;
+ }
+ avd /= (double)spec_n;
+
+ if ((verify && verb) || verb >= 2) {
+ printf("[%f %f] ", avd, mxd);
+ for (j = 0; j < devchan; j++)
+ printf("%6.4f ", cols[i].nv[j]);
+ printf("-> ");
+#ifdef NEVER
+ for (j = 0; j < spec_n; j++)
+ printf("%2.0f ", out.spec[j]);
+ printf("should be ");
+ for (j = 0; j < spec_n; j++)
+ printf("%2.0f ", cols[i].band[3+j]);
+#else
+ for (j = 0; j < spec_n; j++)
+ printf("%f ", out.spec[j]);
+ printf("should be ");
+ for (j = 0; j < spec_n; j++)
+ printf("%f ", cols[i].band[3+j]);
+#endif
+ printf("\n");
+ }
+
+ if (mxd > merr)
+ merr = mxd;
+
+ aerr += avd;
+ nsamps++;
+ }
+ printf("profile spectral check complete, avg err = %f%%, max err = %f%%\n",
+ aerr * 100.0/nsamps * 1.0/maxsp, merr * 100.0/maxsp);
+
+ /* Check spectrally derived Lab values */
+ {
+ xsp2cie *sc;
+ xspect sp;
+
+ if (isDisplay) {
+ if ((sc = new_xsp2cie(icxIT_none, NULL, icxOT_CIE_1931_2, NULL, icSigLabData, icxClamp)) == NULL)
+ error("Failed to create xsp2cie object");
+
+ p->set_ilob(p, icxIT_none, NULL, icxOT_CIE_1931_2, NULL, icSigLabData, 0);
+ } else {
+ /* Set standard D50 viewer & illum. */
+ if ((sc = new_xsp2cie(icxIT_D50, NULL, icxOT_CIE_1931_2, NULL, icSigLabData, icxClamp)) == NULL)
+ error("Failed to create xsp2cie object");
+
+ p->set_ilob(p, icxIT_D50, NULL, icxOT_CIE_1931_2, NULL, icSigLabData, 0);
+ }
+
+ merr = 0.0;
+ aerr = 0.0;
+ nsamps = 0.0;
+
+ for (i = 0; i < nodp; i++) {
+ double out[3], ref[3];
+ double mxd;
+
+ /* Lookup the profile PCS value for this data point */
+ p->lookup(p, out, cols[i].nv);
+
+ /* Convert our cols ref data to Lab */
+ sp.spec_n = spec_n;
+ sp.spec_wl_short = spec_wl_short;
+ sp.spec_wl_long = spec_wl_long;
+ sp.norm = norm;
+ for (j = 0; j < spec_n; j++)
+ sp.spec[j] = cols[i].band[3+j];
+ sc->convert(sc, ref, &sp);
+
+ if ((verify && verb) || verb >= 2) {
+ printf("[%f] ", icmLabDE(ref, out));
+ for (j = 0; j < devchan; j++)
+ printf("%6.4f ", cols[i].nv[j]);
+ printf("-> %5.1f %5.1f %5.1f should be %5.1f %5.1f %5.1f\n",
+ out[0],out[1],out[2], ref[0],ref[1],ref[2]);
+ }
+
+ /* Check the result */
+ mxd = icmLabDE(ref, out);
+ if (mxd > merr)
+ merr = mxd;
+
+ aerr += mxd;
+ nsamps++;
+ }
+ printf("Profile spectral Lab check complete, avg err = %f, max err = %f\n",
+ aerr/nsamps, merr);
+
+ sc->del(sc);
+ }
+ }
+ }
+
+
+ /* Test again by reading and loading the profile */
+ if (verify >= 2) {
+ mpp *p2; /* Second test profile */
+ double merr = 0.0;
+ double aerr = 0.0;
+ double nsamps = 0.0;
+
+ printf("\n");
+ if ((p2 = new_mpp()) == NULL)
+ error("Failed to create an mpp");
+
+ if (p2->read_mpp(p2, outname))
+ error("Read error : %s",p2->err);
+
+ {
+ /* Set just PCS and use XYZ model */
+ p2->set_ilob(p2, icxIT_default, NULL, icxOT_default, NULL, icSigLabData, 0);
+
+ for (i = 0; i < nodp; i++) {
+ double out[3], ref[3];
+ double mxd;
+
+ /* Lookup the profile PCS value for this data point */
+ p2->lookup(p2, out, cols[i].nv);
+
+ /* Convert our cols data to Lab */
+ icmXYZ2Lab(&icmD50, ref, cols[i].band);
+
+ if (verify && verb) {
+ printf("[%f] ", icmLabDE(ref, out));
+ for (j = 0; j < devchan; j++)
+ printf("%6.4f ", cols[i].nv[j]);
+ printf("-> %5.1f %5.1f %5.1f should be %5.1f %5.1f %5.1f\n",
+ out[0],out[1],out[2], ref[0],ref[1],ref[2]);
+ }
+
+ /* Check the result */
+ mxd = icmLabDE(ref, out);
+ if (mxd > merr)
+ merr = mxd;
+
+ aerr += mxd;
+ nsamps++;
+ }
+ printf("Read profile Lab check complete, avg err = %f, max err = %f\n",
+ aerr/nsamps, merr); fflush(stdout);
+
+ if (ospec) {
+ double maxsp = -1e6;
+ merr = 0.0;
+ aerr = 0.0;
+ nsamps = 0.0;
+
+ for (i = 0; i < nodp; i++) {
+ xspect out;
+ double avd, mxd;
+
+ /* Lookup the profile spectral value for this data point */
+ p2->lookup_spec(p2, &out, cols[i].nv);
+
+ if (spec_n != out.spec_n)
+ error("Mismatch between original spectral and returned");
+
+ avd = mxd = 0.0;
+ for (j = 0; j < spec_n; j++) {
+ double ded;
+ if (out.spec[j] > maxsp)
+ maxsp = out.spec[j];
+ if (cols[i].band[3+j] > maxsp)
+ maxsp = out.spec[j];
+ ded = fabs(out.spec[j] - cols[i].band[3+j]);
+ avd += ded;
+ if (ded > mxd)
+ mxd = ded;
+ }
+ avd /= (double)spec_n;
+
+ if (verify && verb) {
+ printf("[%f %f] ", avd, mxd);
+ for (j = 0; j < devchan; j++)
+ printf("%6.4f ", cols[i].nv[j]);
+ printf("-> ");
+#ifdef NEVER
+ for (j = 0; j < spec_n; j++)
+ printf("%2.0f ", out.spec[j]);
+ printf("should be ");
+ for (j = 0; j < spec_n; j++)
+ printf("%2.0f ", cols[i].band[3+j]);
+#else
+ for (j = 0; j < spec_n; j++)
+ printf("%f ", out.spec[j]);
+ printf("should be ");
+ for (j = 0; j < spec_n; j++)
+ printf("%f ", cols[i].band[3+j]);
+#endif
+ printf("\n");
+ }
+
+ if (mxd > merr)
+ merr = mxd;
+
+ aerr += avd;
+ nsamps++;
+ }
+ printf("profile spectral check complete, avg err = %f, max err = %f\n",
+ aerr * 100.0/nsamps * 1.0/maxsp, merr * 100.0/maxsp); fflush(stdout);
+
+ /* Check spectrally derived Lab values */
+ {
+ xsp2cie *sc;
+ xspect sp;
+
+
+ if (isDisplay) {
+ /* Set emissive viewer. */
+ if ((sc = new_xsp2cie(icxIT_none, NULL, icxOT_CIE_1931_2, NULL, icSigLabData, icxClamp)) == NULL)
+ error("Failed to create xsp2cie object");
+ /* (mpp will ignore illuminant for display anyway ??) */
+ p2->set_ilob(p2, icxIT_none, NULL, icxOT_CIE_1931_2, NULL, icSigLabData, 0);
+ } else {
+ /* Set standard D50 viewer & illum. */
+ if ((sc = new_xsp2cie(icxIT_D50, NULL, icxOT_CIE_1931_2, NULL, icSigLabData, icxClamp)) == NULL)
+ error("Failed to create xsp2cie object");
+ p2->set_ilob(p2, icxIT_D50, NULL, icxOT_CIE_1931_2, NULL, icSigLabData, 0);
+ }
+
+ merr = 0.0;
+ aerr = 0.0;
+ nsamps = 0.0;
+
+ for (i = 0; i < nodp; i++) {
+ double out[3], ref[3];
+ double mxd;
+
+ /* Lookup the profile PCS value for this data point */
+ p2->lookup(p2, out, cols[i].nv);
+
+ /* Convert our cols ref data to Lab */
+ sp.spec_n = spec_n;
+ sp.spec_wl_short = spec_wl_short;
+ sp.spec_wl_long = spec_wl_long;
+ sp.norm = norm;
+ for (j = 0; j < spec_n; j++)
+ sp.spec[j] = cols[i].band[3+j];
+ sc->convert(sc, ref, &sp);
+
+ if (verify && verb) {
+ printf("[%f] ", icmLabDE(ref, out));
+ for (j = 0; j < devchan; j++)
+ printf("%6.4f ", cols[i].nv[j]);
+ printf("-> %5.1f %5.1f %5.1f should be %5.1f %5.1f %5.1f\n",
+ out[0],out[1],out[2], ref[0],ref[1],ref[2]);
+ }
+
+ /* Check the result */
+ mxd = icmLabDE(ref, out);
+ if (mxd > merr)
+ merr = mxd;
+
+ aerr += mxd;
+ nsamps++;
+ }
+ printf("Read profile spectral Lab check complete, avg err = %f, max err = %f\n",
+ aerr/nsamps, merr); fflush(stdout);
+
+ sc->del(sc);
+ }
+ }
+ }
+
+ if (p2->write_mpp(p2, "xxxx.mpp", dolab))
+ error("Write error : %s",p2->err);
+
+ p2->del(p2);
+ }
+
+ /* Clean up */
+ del_mppcols(cols, nodp, devchan, spec_n);
+
+ p->del(p);
+
+ return 0;
+}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/profile/printcal.c b/profile/printcal.c
new file mode 100644
index 0000000..f9107eb
--- /dev/null
+++ b/profile/printcal.c
@@ -0,0 +1,2320 @@
+
+
+
+/*
+ * Argyll Color Correction System
+ * Print Device calibration curve generator.
+ *
+ * Author: Graeme W. Gill
+ * Date: 2008/3/3
+ *
+ * Copyright 1996-2008 Graeme W. Gill
+ * All rights reserved.
+ *
+ * This material is licenced under the GNU AFFERO GENERAL PUBLIC LICENSE Version 3 :-
+ * see the License.txt file for licencing details.
+ */
+
+/*
+ * This program takes in the colorent wedge test chart
+ * points, and creates a set of per channel correction curves.
+ */
+
+/*
+ * TTBD:
+ * Allow auto max threshold to be scaled on command line ?
+ * ie. -m# set % to go below the default optimal maximum.
+ */
+
+
+/*
+ Additive spaces are handled by inverting the device values internally.
+ (Such a space should probably have ICX_INVERTED set as well, indicating
+ that the underlying device is actually subtractive.)
+ */
+
+#undef DEBUG
+
+#define verbo stdout
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <fcntl.h>
+#include <string.h>
+#include <time.h>
+#include "copyright.h"
+#include "aconfig.h"
+#include "cgats.h"
+#include "numlib.h"
+#include "sort.h"
+#include "rspl.h"
+#include "xicc.h"
+#include "plot.h"
+
+
+#define RSPLFLAGS (0 /* | RSPL_2PASSSMTH | RSPL_EXTRAFIT2 */)
+
+#define RSPLSMOOTH 2.0 /* RSPL Smoothness factor use on measured device points */
+
+#define TCURVESMOOTH 1.0 /* RSPL smoothness factor for target aim points */
+
+#define GRES 256 /* Rspl grid resolution */
+#define SLOPE_NORM 70.0 /* Normalized delta E for below thresholds */
+#define MIN_SLOPE_A 8.0 /* Criteria for Auto max, DE/dDev at max */
+#define MIN_SLOPE_O 3.0 /* Criteria for Auto max, min DE/dDev below max */
+
+#define CAL_RES 256 /* Resolution saved to .cal file */
+
+#define PRES 256 /* Plotting resolution */
+
+void usage(char *diag, ...) {
+ int i;
+ fprintf(stderr,"Create printer calibration, Version %s\n",ARGYLL_VERSION_STR);
+ fprintf(stderr,"Author: Graeme W. Gill, licensed under the AGPL Version 3\n");
+ if (diag != NULL) {
+ va_list args;
+ fprintf(stderr," Diagnostic: ");
+ va_start(args, diag);
+ vfprintf(stderr, diag, args);
+ va_end(args);
+ fprintf(stderr,"\n");
+ }
+ fprintf(stderr,"usage: %s [-options] [prevcal] inoutfile\n",error_program);
+ fprintf(stderr," -v verbosity Verbose mode\n");
+ fprintf(stderr," -p Plot graphs.\n");
+ fprintf(stderr," -i Initial calibration, set targets, create .cal\n");
+ fprintf(stderr," -r Re-calibrate against previous .cal and create new .cal\n");
+ fprintf(stderr," -e Verify against previous .cal\n");
+ fprintf(stderr," -I Create imitation target from .ti3 and null calibration\n");
+ fprintf(stderr," -d Go through the motions but don't write any files\n");
+ fprintf(stderr," -s smoothing Extra curve smoothing (default 1.0)\n");
+ fprintf(stderr," -A manufacturer Set the manufacturer description string\n");
+ fprintf(stderr," -M model Set the model description string\n");
+ fprintf(stderr," -D description Set the profile Description string\n");
+ fprintf(stderr," -C copyright Set the copyright string\n");
+ fprintf(stderr," -x# percent Set initial maximum device %% target (override auto)\n");
+ fprintf(stderr," -m# percent Set initial dev target to %% of auto maximum\n");
+ fprintf(stderr," -n# deltaE Set initial white minimum deltaE target\n");
+ fprintf(stderr," -t# percent Set initial 50%% transfer curve percentage target\n");
+ fprintf(stderr," # = c, r, 0 First channel\n");
+ fprintf(stderr," m, g, 1 Second channel\n");
+ fprintf(stderr," y, b, 2 Third channel\n");
+ fprintf(stderr," k, 3 Fourth channel, etc.\n");
+ fprintf(stderr," -a Create an Adobe Photoshop .AMP file as well as a .cal\n");
+ fprintf(stderr," prevcal Base name of previous .cal file for recal or verify.\n");
+ fprintf(stderr," inoutname Base name of input .ti3 file, output .cal file\n");
+ exit(1);
+}
+
+/* - - - - - - - - - - - - - - - - - - - - - - - */
+typedef struct {
+ double loc; /* Location up the curve 0.0 - 1.0 */
+ double val[MAX_CHAN]; /* Value at that location 0.0 - 1.0 */
+} trans_point;
+
+/* Class to hold a print calibration target */
+struct _pcaltarg {
+ inkmask devmask; /* ICX ink mask of device space */
+
+ /* Note that with all of these, a value < 0.0 */
+ /* indicates no value set. */
+ int devmaxset; /* Flag - nz if the devmax is set */
+ double devmax[MAX_CHAN]; /* Device value maximum 0.0 - 1.0 */
+
+ int ademaxset; /* Flag - nz if the ademax is set */
+ double ademax[MAX_CHAN]; /* abs DE maximum for each channel */
+
+ int ademinset; /* Flag - nz if the ademin is set */
+ double ademin[MAX_CHAN]; /* abs DE minimum for each channel */
+
+ int no_tpoints; /* Number of transfer curve points */
+ trans_point *tpoints; /* Array of transfer curve points */
+
+ char err[500]; /* Error message from diagnostics */
+
+ /* Methods */
+ void (*del)(struct _pcaltarg *p);
+
+ /* Save/restore to a CGATS file */
+ int (*write)(struct _pcaltarg *p, cgats *cg, int tab); /* return nz on error */
+ int (*read)(struct _pcaltarg *p, cgats *cg, int tab); /* return nz on error */
+
+ /* Set values in the target */
+ void (*update_devmax)(struct _pcaltarg *p, int chan, double val);
+ void (*update_ademax)(struct _pcaltarg *p, int chan, double val);
+ void (*update_ademin)(struct _pcaltarg *p, int chan, double val);
+ void (*update_tcurve)(struct _pcaltarg *p, int chan, double loc, double val);
+
+ /* Reurn nz if the target has been set */
+ int (*is_set)(struct _pcaltarg *p);
+
+ /* Update settings or from one from another */
+ void (*update)(struct _pcaltarg *p, struct _pcaltarg *s);
+
+}; typedef struct _pcaltarg pcaltarg;
+
+static void pcaltarg_del(pcaltarg *p) {
+ if (p != NULL) {
+ free(p);
+ }
+}
+
+/* Write the cal target to a givent cgats table */
+static int pcaltarg_write(pcaltarg *p, cgats *cg, int tab) {
+ int i, j;
+ time_t clk = time(0);
+ struct tm *tsp = localtime(&clk);
+ char *atm = asctime(tsp); /* Ascii time */
+ char *ident = icx_inkmask2char(p->devmask, 1);
+ char *bident = icx_inkmask2char(p->devmask, 0);
+ int devchan = icx_noofinks(p->devmask);
+ int nsetel = 0;
+ cgats_set_elem *setel; /* Array of set value elements */
+ char buf[100];
+
+ atm[strlen(atm)-1] = '\000'; /* Remove \n from end */
+
+ /* Setup output cgats file */
+ cg->add_table(cg, tt_other, 0); /* Add a table for Calibration TarGet values */
+ cg->add_kword(cg, tab, "DESCRIPTOR", "Argyll Calibration Target Definition File",NULL);
+ cg->add_kword(cg, tab, "ORIGINATOR", "Argyll printcal", NULL);
+ cg->add_kword(cg, tab, "CREATED",atm, NULL);
+ cg->add_kword(cg, tab, "COLOR_REP", ident, NULL);
+
+ /* Setup the table, which holds all the model parameters. */
+ /* There is always a parameter per X Y Z or spectral band */
+ cg->add_field(cg, tab, "PARAMTYPE", nqcs_t);
+ nsetel++;
+ sprintf(buf, "%s_I",bident);
+ cg->add_field(cg, tab, buf, r_t);
+ nsetel++;
+ for (i = 0; i < devchan; i++) {
+ inkmask imask = icx_index2ink(p->devmask, i);
+ sprintf(buf, "%s_%s",bident,icx_ink2char(imask));
+ cg->add_field(cg, tab, buf, r_t);
+ nsetel++;
+ }
+
+ if ((setel = (cgats_set_elem *)malloc(sizeof(cgats_set_elem) * nsetel)) == NULL) {
+ free(ident);
+ free(bident);
+ sprintf(p->err,"ctg_write: malloc of setel failed");
+ return 1;
+ }
+
+ /* Write out the values */
+ if (p->devmaxset) {
+ /* This is informational only */
+ setel[0].c = "DEVMAX_USED";
+ setel[1].d = 0.0; /* Not used */
+
+ if (p->devmask & ICX_ADDITIVE) {
+ for (i = 0; i < devchan; i++)
+ setel[2+i].d = 1.0 - p->devmax[i];
+ } else {
+ for (i = 0; i < devchan; i++)
+ setel[2+i].d = p->devmax[i];
+ }
+ cg->add_setarr(cg, tab, setel);
+ }
+ if (p->ademaxset) {
+ setel[0].c = "DELMAX_AIM";
+ setel[1].d = 0.0; /* Not used */
+
+ for (i = 0; i < devchan; i++)
+ setel[2+i].d = p->ademax[i];
+ cg->add_setarr(cg, tab, setel);
+ }
+ if (p->ademinset) {
+ setel[0].c = "DELMIN_AIM";
+ setel[1].d = 0.0; /* Not used */
+
+ for (i = 0; i < devchan; i++)
+ setel[2+i].d = p->ademin[i];
+ cg->add_setarr(cg, tab, setel);
+ }
+ for (j = 0; j < p->no_tpoints; j++) {
+ setel[0].c = "TRANS_PNT";
+ setel[1].d = p->tpoints[j].loc;
+
+ for (i = 0; i < devchan; i++)
+ setel[2+i].d = p->tpoints[j].val[i];
+ cg->add_setarr(cg, tab, setel);
+ }
+ free(setel);
+ free(ident);
+ free(bident);
+
+ return 0;
+}
+
+/* Read the cal target from a given cgats table */
+static int pcaltarg_read(pcaltarg *p, cgats *cg, int tab) {
+ char *bident;
+ int devchan;
+ int i, j, ix;
+ int ti; /* Temporary CGATs index */
+ int spi[2+MAX_CHAN]; /* CGATS indexes for each field */
+ char buf[100];
+
+ if ((ti = cg->find_kword(cg, tab, "COLOR_REP")) < 0) {
+ sprintf(p->err, "ctg_read: Can't fint COLOR_REP");
+ return 1;
+ }
+
+ if ((p->devmask = icx_char2inkmask(cg->t[tab].kdata[ti]) ) == 0) {
+ sprintf(p->err, "ctg_read: unrecognized COLOR_REP '%s'",cg->t[tab].kdata[ti]);
+ return 1;
+ }
+ devchan = icx_noofinks(p->devmask);
+ bident = icx_inkmask2char(p->devmask, 0);
+
+ /* Figure out the indexes of all the fields */
+ if ((spi[0] = cg->find_field(cg, tab, "PARAMTYPE")) < 0) {
+ sprintf(p->err, "ctg_read: Can't find field PARAMTYPE");
+ free(bident);
+ return 1;
+ }
+ sprintf(buf, "%s_I",bident);
+ if ((spi[1] = cg->find_field(cg, tab, buf)) < 0) {
+ sprintf(p->err, "ctg_read: Can't find field %s",buf);
+ free(bident);
+ return 1;
+ }
+ for (i = 0; i < devchan; i++) {
+ inkmask imask = icx_index2ink(p->devmask, i);
+ sprintf(buf, "%s_%s",bident,icx_ink2char(imask));
+ if ((spi[2+i] = cg->find_field(cg, tab, buf)) < 0) {
+ sprintf(p->err, "ctg_read: Can't find field %s",buf);
+ free(bident);
+ return 1;
+ }
+ }
+
+ /* Go through all the entries in the table, putting them in the right place */
+ for (ix = 0; ix < cg->t[tab].nsets; ix++) {
+
+ if (strcmp((char *)cg->t[tab].fdata[ix][spi[0]], "DELMAX_AIM") == 0) {
+ for (i = 0; i < devchan; i++)
+ p->ademax[i] = *((double *)cg->t[tab].fdata[ix][spi[2+i]]);
+ p->ademaxset = 1;
+
+ } else if (strcmp((char *)cg->t[tab].fdata[ix][spi[0]], "DELMIN_AIM") == 0) {
+ for (i = 0; i < devchan; i++)
+ p->ademin[i] = *((double *)cg->t[tab].fdata[ix][spi[2+i]]);
+ p->ademinset = 1;
+
+ } else if (strcmp((char *)cg->t[tab].fdata[ix][spi[0]], "TRANS_PNT") == 0) {
+ if ((p->tpoints = (trans_point *)realloc(p->tpoints, sizeof(trans_point)
+ * (p->no_tpoints+1))) == NULL)
+ error("Realloc of tpoints");
+ p->tpoints[p->no_tpoints].loc = *((double *)cg->t[tab].fdata[ix][spi[1]]);
+ for (i = 0; i < devchan; i++)
+ p->tpoints[p->no_tpoints].val[i] = *((double *)cg->t[tab].fdata[ix][spi[2+i]]);
+ p->no_tpoints++;
+ }
+ }
+ free(bident);
+
+ return 0;
+}
+
+/* Update an individual setting. Use chan < 0 to set all to default */
+void pcaltarg_update_devmax(struct _pcaltarg *p, int chan, double val) {
+ int i;
+ if (p->devmaxset == 0) {
+ for (i = 0; i < MAX_CHAN; i++)
+ p->devmax[i] = -1.0;
+ p->devmaxset = 1;
+ }
+ if (chan >= 0)
+ p->devmax[chan] = val;
+}
+void pcaltarg_update_ademax(struct _pcaltarg *p, int chan, double val) {
+ int i;
+ if (p->ademaxset == 0) {
+ for (i = 0; i < MAX_CHAN; i++)
+ p->ademax[i] = -1.0;
+ p->ademaxset = 1;
+ }
+ if (chan >= 0)
+ p->ademax[chan] = val;
+}
+void pcaltarg_update_ademin(struct _pcaltarg *p, int chan, double val) {
+ int i;
+ if (p->ademinset == 0) {
+ for (i = 0; i < MAX_CHAN; i++)
+ p->ademin[i] = -1.0;
+ p->ademinset = 1;
+ }
+ if (chan >= 0)
+ p->ademin[chan] = val;
+}
+void pcaltarg_update_tcurve(struct _pcaltarg *p, int chan, double loc, double val) {
+ int i, j;
+
+ /* See if a transfer curve point already exists */
+ for (j = 0; j < p->no_tpoints; j++) {
+ if (p->tpoints[j].loc == loc)
+ break;
+ }
+ /* If not, allocate a new one */
+ if (j >= p->no_tpoints) {
+ p->no_tpoints++;
+ if ((p->tpoints = (trans_point *)realloc(p->tpoints, sizeof(trans_point) * p->no_tpoints)) == NULL)
+ error("Realloc of tpoints");
+ p->tpoints[j].loc = loc;
+ for (i = 0; i < MAX_CHAN; i++)
+ p->tpoints[j].val[i] = -1.0;
+ }
+ p->tpoints[j].val[chan] = val;
+
+ if (p->no_tpoints > 0) {
+ /* Sort the transfer points into loc order */
+#define HEAP_COMPARE(A,B) ((A).loc < (B).loc)
+ HEAPSORT(trans_point, p->tpoints, p->no_tpoints);
+#undef HEAP_COMPARE
+ }
+}
+
+/* Reurn nz if the target has been set */
+static int pcaltarg_is_set(pcaltarg *p) {
+ if (p->devmaxset != 0
+ || p->ademaxset != 0
+ || p->ademinset != 0
+ || p->no_tpoints > 0)
+ return 1;
+ return 0;
+}
+
+/* Update one from another */
+static void pcaltarg_update(pcaltarg *p, pcaltarg *s) {
+ int i, j, k;
+
+ if (s->devmaxset) {
+ if (p->devmaxset == 0) {
+ for (i = 0; i < MAX_CHAN; i++)
+ p->devmax[i] = -1.0;
+ p->devmaxset = 1;
+ }
+ for (i = 0; i < MAX_CHAN; i++) {
+ if (s->devmax[i] >= 0.0)
+ p->devmax[i] = s->devmax[i];
+ }
+ }
+
+ if (s->ademaxset) {
+ if (p->ademaxset == 0) {
+ for (i = 0; i < MAX_CHAN; i++)
+ p->ademax[i] = -1.0;
+ p->ademaxset = 1;
+ }
+ for (i = 0; i < MAX_CHAN; i++) {
+ if (s->ademax[i] >= 0.0)
+ p->ademax[i] = s->ademax[i];
+ }
+ }
+
+ if (s->ademinset) {
+ if (p->ademinset == 0) {
+ for (i = 0; i < MAX_CHAN; i++)
+ p->ademin[i] = -1.0;
+ p->ademinset = 1;
+ }
+ for (i = 0; i < MAX_CHAN; i++) {
+ if (s->ademin[i] >= 0.0)
+ p->ademin[i] = s->ademin[i];
+ }
+ }
+
+ /* For each source transfer curve point */
+ for (k = 0; k < s->no_tpoints; k++) {
+
+ /* See if a transfer curve point already exists */
+ for (j = 0; j < p->no_tpoints; j++) {
+ if (p->tpoints[j].loc == s->tpoints[k].loc)
+ break;
+ }
+ /* If not, allocate a new one */
+ if (j >= p->no_tpoints) {
+ p->no_tpoints++;
+ if ((p->tpoints = (trans_point *)realloc(p->tpoints, sizeof(trans_point) * p->no_tpoints)) == NULL)
+ error("Realloc of tpoints");
+ p->tpoints[j].loc = s->tpoints[k].loc;
+ for (i = 0; i < MAX_CHAN; i++)
+ p->tpoints[j].val[i] = -1.0;
+ }
+ for (i = 0; i < MAX_CHAN; i++) {
+ if (s->tpoints[k].val[i] >= 0.0)
+ p->tpoints[j].val[i] = s->tpoints[k].val[i];
+ }
+ }
+ if (s->no_tpoints > 0) {
+ /* Sort the transfer points into loc order */
+#define HEAP_COMPARE(A,B) ((A).loc < (B).loc)
+ HEAPSORT(trans_point, p->tpoints, p->no_tpoints);
+#undef HEAP_COMPARE
+ }
+}
+
+/* Create a new, empty pcaltarget */
+/* Return NULL on error */
+pcaltarg *new_pcaltarg() {
+ pcaltarg *p;
+
+ if ((p = (pcaltarg *)calloc(1, sizeof(pcaltarg))) == NULL) {
+ return NULL;
+ }
+
+ /* Set method pointers */
+ p->del = pcaltarg_del;
+ p->write = pcaltarg_write;
+ p->read = pcaltarg_read;
+ p->update_devmax = pcaltarg_update_devmax;
+ p->update_ademax = pcaltarg_update_ademax;
+ p->update_ademin = pcaltarg_update_ademin;
+ p->update_tcurve = pcaltarg_update_tcurve;
+ p->is_set = pcaltarg_is_set;
+ p->update = pcaltarg_update;
+
+ return p;
+}
+
+/* - - - - - - - - - - - - - - - - - - - - - - - */
+
+/* A wedge sample value */
+typedef struct {
+ double inv; /* Input value (cal table) */
+ double dev; /* Device value */
+ double XYZ[3]; /* XYZ value */
+ double Lab[3]; /* Lab value */
+ double del; /* Absolute delta (to white) */
+} wval;
+
+#define MAX_INVSOLN 10 /* Rspl maximum reverse solutions */
+
+/* rspl setting functions */
+static void rsplset1(void *cbntx, double *out, double *in) {
+ co *dpoints = (co *)cbntx;
+ int ix;
+
+ ix = *((int*)&in[-0-1]); /* Get grid index being looked up */
+ out[0] = dpoints[ix].v[0];
+}
+
+/* Do an inverse lookup of an rspl. Return -1.0 on error. */
+/* dir is value to favour if there are multiple solutions. */
+static double rspl_ilookup(rspl *r, double dir, double in) {
+ int nsoln; /* Number of solutions found */
+ co pp[MAX_INVSOLN]; /* Room for all the solutions found */
+ int k; /* Chosen solution */
+
+ pp[0].v[0] = in;
+
+ nsoln = r->rev_interp (
+ r, /* this */
+ RSPL_NEARCLIP, /* Clip to nearest (faster than vector) */
+ MAX_INVSOLN, /* Maximum number of solutions allowed for */
+ NULL, /* No auxiliary input targets */
+ NULL, /* Clip vector direction and length */
+ pp); /* Input and output values */
+
+ nsoln &= RSPL_NOSOLNS; /* Get number of solutions */
+
+ if (nsoln == 1) { /* Exactly one solution */
+ k = 0;
+ } else if (nsoln == 0) { /* Zero solutions. This is unexpected. */
+ return -1.0;
+ } else { /* Multiple solutions */
+ double bdist = 1e300;
+ int bsoln = 0;
+// warning("Multiple solutions for curve %d for DE %f",j,pp[0].v[0]);
+ for (k = 0; k < nsoln; k++) {
+ double tt;
+ tt = pp[k].p[0] - dir;
+ tt *= tt;
+ if (tt < bdist) { /* Better solution */
+ bdist = tt;
+ bsoln = k;
+ }
+ }
+ k = bsoln;
+ }
+ return pp[k].p[0];
+}
+
+int main(int argc, char *argv[]) {
+ int fa,nfa,mfa; /* current argument we're looking at */
+ int verb = 0;
+ int doplot = 0;
+ int initial = 0; /* Do initial creation of cal target and calibration */
+ int recal = 0; /* Do recalibrate/use cal target. */
+ int verify = 0; /* Do verification */
+ int imitate = 0; /* Do target directly from input */
+ int dowrite = 1; /* Write to files */
+ int doamp = 0; /* Write Adobe Photoshop .AMP file */
+ profxinf xpi; /* Extra profile/calibration information */
+ pcaltarg *upct = NULL; /* User settings of print calibration target */
+ pcaltarg *pct = NULL; /* Settings of print calibration target */
+ double smooth = RSPLSMOOTH; /* RSPL Smoothness factor */
+ double xsmooth = 1.0; /* Smoothing multiplier */
+ double ver_maxde = 2.0; /* Verify maximum Delta E (1.0 for smooth == 1.0) */
+ int spec = 0; /* Use spectral data flag */
+ icxIllumeType illum = icxIT_D50; /* Spectral defaults */
+ xspect cust_illum; /* Custom illumination spectrum */
+ icxObserverType observ = icxOT_CIE_1931_2; /* The classic observer */
+
+ char baname[MAXNAMEL+1] = ""; /* Input & Output base name */
+ char inname[MAXNAMEL+1] = ""; /* new .ti3 input file name */
+ char calname[MAXNAMEL+1] = ""; /* previous .cal input file name */
+ char outname[MAXNAMEL+1] = ""; /* new .cal output file name */
+ char ampname[MAXNAMEL+1] = ""; /* new .amp output file name */
+ double maxscale[MAX_CHAN]; /* Scale auto device maximum to % */
+ cgats *icg = NULL; /* .ti3 input cgats structure */
+ int ti; /* Temporary CGATs index */
+ inkmask devmask; /* ICX ink mask of device space */
+ int devchan; /* Number of chanels in device space */
+ int isLab = 0; /* Flag indicating whether PCS is XYZ or Lab */
+ int n_pvals[MAX_CHAN]; /* Number of measurement values */
+ wval *pvals[MAX_CHAN]; /* Patch measurement values */
+ wval white; /* Average white value */
+ int n_white = 0; /* Number of values to average */
+ icmXYZNumber wht; /* White value */
+ rspl *raw[MAX_CHAN]; /* Raw Lab values fitted to rspl */
+ rspl *ade[MAX_CHAN]; /* Absolute delta E */
+ rspl *rde[MAX_CHAN]; /* Relative delta E */
+ rspl *pcade[MAX_CHAN]; /* Previous calibrated absolute delta E */
+ double mxade[MAX_CHAN]; /* Maximum ade value */
+ double idpow[MAX_CHAN] = { -1.0 }; /* Ideal power-like of targen values */
+ int n_cvals; /* Number of calibration curve values */
+ wval *cvals[MAX_CHAN]; /* Calibration curve tables */
+ rspl *tcurves[MAX_CHAN]; /* Tweak target curves */
+
+ int i, j;
+
+ /* Init pointers to NULL */
+ for (j = 0; j < MAX_CHAN; j++) {
+ maxscale[j] = -1.0;
+ pvals[j] = NULL;
+ raw[j] = NULL;
+ ade[j] = NULL;
+ rde[j] = NULL;
+ pcade[j] = NULL;
+ cvals[j] = NULL;
+ tcurves[j] = NULL;
+ }
+
+ error_program = argv[0];
+ memset((void *)&xpi, 0, sizeof(profxinf)); /* Init extra profile info to defaults */
+ if ((upct = new_pcaltarg()) == NULL || (pct = new_pcaltarg()) == NULL)
+ error("new_caltarg failed");
+
+ if (argc < 3)
+ usage("Too few arguments, got %d expect at least %d",argc-1,2);
+
+#ifdef NEVER
+ {
+ double src, dst, pp;
+
+ src = 0.5;
+ dst = 0.25;
+ pp = icx_powlike_needed(src, dst);
+ printf("%f -> %f needs %f, check %f\n",src,dst,pp,icx_powlike(src,pp));
+
+ src = 0.25;
+ dst = 0.5;
+ pp = icx_powlike_needed(src, dst);
+ printf("%f -> %f needs %f, check %f\n",src,dst,pp,icx_powlike(src,pp));
+
+ src = 0.5;
+ dst = 0.707106;
+ pp = icx_powlike_needed(src, dst);
+ printf("%f -> %f needs %f, check %f\n",src,dst,pp,icx_powlike(src,pp));
+
+ src = 0.5;
+ dst = 0.5;
+ pp = icx_powlike_needed(src, dst);
+ printf("%f -> %f needs %f, check %f\n",src,dst,pp,icx_powlike(src,pp));
+ }
+#endif // NEVER
+
+ /* Process the arguments */
+ mfa = 2; /* Minimum final arguments */
+ for(fa = 1;fa < argc;fa++) {
+
+ nfa = fa; /* skip to nfa if next argument is used */
+ if (argv[fa][0] == '-') { /* Look for any flags */
+ char *na = NULL; /* next argument after flag, null if none */
+
+ if (argv[fa][2] != '\000')
+ na = &argv[fa][2]; /* next is directly after flag */
+ else {
+ if ((fa+1+mfa) < argc) {
+ if (argv[fa+1][0] != '-') {
+ nfa = fa + 1;
+ na = argv[nfa]; /* next is seperate non-flag argument */
+ }
+ }
+ }
+
+ if (argv[fa][1] == '?')
+ usage("Usage requested");
+
+ else if (argv[fa][1] == 'v') {
+ if (na != NULL) {
+ fa = nfa;
+ verb = atoi(na);
+ } else
+ verb = 1;
+ }
+
+ else if (argv[fa][1] == 'p') {
+ if (na != NULL) {
+ fa = nfa;
+ doplot = atoi(na);
+ } else
+ doplot = 1; /* Plot various graphs */
+ }
+
+ else if (argv[fa][1] == 'i') {
+ initial = 1; /* Initial calibration */
+ recal = 0;
+ verify = 0;
+ imitate = 0;
+ }
+
+ else if (argv[fa][1] == 'r') {
+ initial = 0;
+ recal = 1; /* Recalibrate */
+ verify = 0;
+ imitate = 0;
+ }
+
+ else if (argv[fa][1] == 'e') {
+ initial = 0;
+ recal = 0;
+ verify = 1; /* Verify */
+ imitate = 0;
+ }
+
+ else if (argv[fa][1] == 'I') {
+ initial = 0;
+ recal = 0;
+ verify = 0;
+ imitate = 1; /* Imitation target */
+ }
+
+ else if (argv[fa][1] == 'd')
+ dowrite = 0; /* Don't write to files */
+
+ else if (argv[fa][1] == 'a')
+ doamp = 1; /* write AMP file */
+
+ /* Smoothing modfider */
+ else if (argv[fa][1] == 's') {
+ fa = nfa;
+ if (na == NULL) usage("Expect argument to smoothing flag -s");
+ xsmooth = atof(na);
+ }
+
+ /* Manufacturer description string */
+ else if (argv[fa][1] == 'A') {
+ fa = nfa;
+ if (na == NULL) usage("Expect argument to manufacturer description flag -A");
+ xpi.deviceMfgDesc = na;
+ }
+
+ /* Model description string */
+ else if (argv[fa][1] == 'M') {
+ fa = nfa;
+ if (na == NULL) usage("Expect argument to model description flag -M");
+ xpi.modelDesc = na;
+ }
+
+ /* Profile Description */
+ else if (argv[fa][1] == 'D') {
+ fa = nfa;
+ if (na == NULL) usage("Expect argument to profile description flag -D");
+ xpi.profDesc = na;
+ }
+
+ /* Copyright string */
+ else if (argv[fa][1] == 'C') {
+ fa = nfa;
+ if (na == NULL) usage("Expect argument to copyright flag -C");
+ xpi.copyright = na;
+ }
+
+ /* Per channel target modifiers */
+ else if (argv[fa][1] == 'x'
+ || argv[fa][1] == 'm'
+ || argv[fa][1] == 'n'
+ || argv[fa][1] == 't') {
+ char fch = argv[fa][1];
+ int chan = -1;
+ double val = -1.0;
+ fa = nfa;
+ if (na == NULL)
+ usage("Expect channel flag after flag -%c",argv[fa][1]);
+ switch (na[0]) {
+ case 'c': case 'r': case '0':
+ chan = 0;
+ break;
+ case 'm': case 'g': case '1':
+ chan = 1;
+ break;
+ case 'y': case 'b': case '2':
+ chan = 2;
+ break;
+ case 'k': case '3':
+ chan = 3;
+ break;
+ case '4':
+ chan = 4;
+ break;
+ case '5':
+ chan = 5;
+ break;
+ case '6':
+ chan = 6;
+ break;
+ case '7':
+ chan = 7;
+ break;
+ case '8':
+ chan = 8;
+ break;
+ case '9':
+ chan = 9;
+ break;
+ case 'A':
+ chan = 10;
+ break;
+ case 'B':
+ chan = 11;
+ break;
+ case 'C':
+ chan = 12;
+ break;
+ case 'D':
+ chan = 13;
+ break;
+ case 'E':
+ chan = 14;
+ break;
+ case 'F':
+ chan = 15;
+ break;
+ default:
+ usage("Unknown channel flag '%s' after flag -%c",argv[fa][2],argv[fa][1]);
+ }
+ ++fa;
+ if (fa >= argc || argv[fa][0] == '-') usage("Expect argument after flag -%c%c",fch,na[0]);
+ val = atof(argv[fa]);
+
+ if (fch == 'x') {
+ if (val < 0.0 || val > 100.0)
+ usage("Argument to -%c%c %f from '%s' is out of range",fch,na[0],val,argv[fa]);
+ val /= 100.0;
+ upct->update_devmax(upct, chan, val);
+
+ } else if (fch == 'm') {
+ if (val < 0.0 || val > 100.0)
+ usage("Argument to -%c%c %f from '%s' is out of range",fch,na[0],val,argv[fa]);
+ val /= 100.0;
+ maxscale[chan] = val;
+
+ } else if (fch == 'n') {
+ upct->update_ademin(upct, chan, val);
+
+ } else if (fch == 't') {
+ if (val < 0.0 || val > 100.0)
+ usage("Argument to -%c%c %f from '%s' is out of range",fch,na[0],val,argv[fa]);
+ val /= 100.0;
+ upct->update_tcurve(upct, chan, 0.5, val);
+ }
+ }
+ else
+ usage("Unknown flag '%c'",argv[fa][1]);
+ } else
+ break;
+ }
+
+ smooth *= xsmooth;
+
+ if (!( (initial && !recal && !verify && !imitate)
+ || (!initial && recal && !verify && !imitate)
+ || (!initial && !recal && verify && !imitate)
+ || (!initial && !recal && !verify && imitate)))
+ error("One of -i, -r -e or -I must be set");
+
+ /* Get the file name arguments */
+ if (verify || recal) {
+ if (fa >= argc || argv[fa][0] == '-') usage("Missing prevoius .cal basename");
+ strncpy(calname,argv[fa++],MAXNAMEL-4); calname[MAXNAMEL-4] = '\000';
+ strcat(calname,".cal");
+ }
+ if (fa >= argc || argv[fa][0] == '-') usage("Missing .ti3 and new .cal basename");
+ strncpy(baname,argv[fa++],MAXNAMEL-4); baname[MAXNAMEL-4] = '\000';
+ strcpy(inname,baname); /* new .ti3 file */
+ strcat(inname,".ti3");
+ strcpy(outname,baname); /* New .cal file */
+ strcat(outname,".cal");
+ strcpy(ampname,baname); /* New .amp file */
+ strcat(ampname,".amp");
+
+ if (fa < argc) usage("Too many arguments ('%s')",argv[fa]);
+
+ /* Open and look at the .ti3 profile patches file */
+ icg = new_cgats(); /* Create a CGATS structure */
+ icg->add_other(icg, "CTI3"); /* our special input type is Calibration Target Information 3 */
+
+ if (icg->read_name(icg, inname))
+ error("CGATS file read error : %s",icg->err);
+
+ if (icg->ntables == 0 || icg->t[0].tt != tt_other || icg->t[0].oi != 0)
+ error ("Input file isn't a CTI3 format file");
+ if (icg->ntables < 1)
+ error ("Input file doesn't contain at least one table");
+
+ /* See if CIE is actually available - some sources of .TI3 don't provide it */
+ if (!spec
+ && icg->find_field(icg, 0, "LAB_L") < 0
+ && icg->find_field(icg, 0, "XYZ_X") < 0) {
+
+ if (icg->find_kword(icg, 0, "SPECTRAL_BANDS") < 0)
+ error ("Neither CIE nor spectral data found in file '%s'",inname);
+
+ /* Switch to using spectral information */
+ if (verb)
+ printf("No CIE data found, switching to spectral with standard observer & D50\n");
+ spec = 1;
+ illum = icxIT_D50;
+ observ = icxOT_CIE_1931_2;
+ }
+
+ /* If we requested spectral, check that it is available */
+ if (spec) {
+ if (icg->find_kword(icg, 0, "SPECTRAL_BANDS") < 0)
+ error ("Requested spectral interpretation when data not available");
+ }
+
+ /* Get colorspace information from input CGATS file */
+ {
+ char *buf;
+ char *inc, *outc;
+
+ if ((ti = icg->find_kword(icg, 0, "COLOR_REP")) < 0)
+ error("Input file doesn't contain keyword COLOR_REPS");
+
+ if ((buf = strdup(icg->t[0].kdata[ti])) == NULL)
+ error("Malloc failed - color rep");
+
+ /* Split COLOR_REP into device and PCS space */
+ inc = buf;
+ if ((outc = strchr(buf, '_')) == NULL)
+ error("COLOR_REP '%s' invalid", icg->t[0].kdata[ti]);
+ *outc++ = '\000';
+
+ if (strcmp(outc, "XYZ") == 0)
+ isLab = 0;
+ else if (strcmp(outc, "LAB") == 0)
+ isLab = 1;
+ else
+ error("COLOR_REP '%s' invalid (Neither XYZ nor LAB)", icg->t[0].kdata[ti]);
+
+ devmask = icx_char2inkmask(inc);
+ devchan = icx_noofinks(devmask);
+
+ if (devchan == 0)
+ error("COLOR_REP '%s' invalid (No matching devmask)", icg->t[0].kdata[ti]);
+
+ if ((devmask & ICX_ADDITIVE) && !(devmask & ICX_INVERTED))
+ warning("COLOR_REP '%s' is probably not suitable for print calibration!", icg->t[0].kdata[ti]);
+
+ free(buf);
+ }
+
+ if (verify || recal || imitate) {
+ if (upct->is_set(upct)) {
+ warning("Command line calibration target paramers ignored on re-calibrate, verify and imitate!");
+ }
+ }
+
+ /* For recalibrate or verify, load the previous calibration file */
+ if (verify || recal) {
+ cgats *tcg; /* Previous .cal file */
+
+ tcg = new_cgats(); /* Create a CGATS structure */
+ tcg->add_other(tcg, "CAL"); /* our special input type is Calibration Target */
+
+ if (tcg->read_name(tcg, calname))
+ error("No cal target '%s' found for re-calibrate (%s)\n",calname,tcg->err);
+
+ /* Check that this is an output cal file */
+ if ((ti = tcg->find_kword(tcg, 0, "DEVICE_CLASS")) < 0)
+ error ("Calibration file '%s'doesn't contain keyword DEVICE_CLASS",calname);
+ if (strcmp(tcg->t[0].kdata[ti],"OUTPUT") != 0)
+ error ("Calibration file '%s' doesn't has DEVICE_CLASS that is not OUTPUT",calname);
+
+ if (pct->read(pct, tcg, 1) != 0)
+ error("Reading cal target '%s' failed",calname);
+
+ if (pct->devmask != devmask)
+ error("Target '%s' colorspace '%s' doesn't match '%s' colorspace '%s'",
+ calname,icx_inkmask2char(pct->devmask, 1),inname,icx_inkmask2char(devmask, 1));
+
+ /* Load the previous expected absolute DE response */
+ /* It will be in the third table with other type "CAL" */
+ if (tcg->ntables >= 3 && tcg->t[2].tt == tt_other && tcg->t[0].oi == 0) {
+ int ti;
+ char *bident;
+ int spi[1+MAX_CHAN]; /* CGATS indexes for each field */
+ char buf[100];
+
+ bident = icx_inkmask2char(pct->devmask, 0);
+
+ if (tcg->t[2].nsets <= 0)
+ error ("No Calibration Expected DE Response in '%s'",calname);
+
+ /* Figure out the indexes of all the fields */
+ sprintf(buf, "%s_I_DE",bident);
+ if ((spi[0] = tcg->find_field(tcg, 2, buf)) < 0)
+ error("Can't find field %s in '%s'",buf,calname);
+
+ for (i = 0; i < devchan; i++) {
+ inkmask imask = icx_index2ink(pct->devmask, i);
+ sprintf(buf, "%s_%s_DE",bident,icx_ink2char(imask));
+ if ((spi[1+i] = tcg->find_field(tcg, 2, buf)) < 0)
+ error("Can't find field %s in '%s'",buf,calname);
+ }
+
+ /* Read in each channels values and put them in a rspl */
+ for (j = 0; j < devchan; j++) {
+ datai low,high;
+ int gres[MXDI];
+ co *dpoints;
+
+ low[0] = 0.0;
+ high[0] = 1.0;
+ gres[0] = tcg->t[2].nsets;
+
+ if ((pcade[j] = new_rspl(RSPL_NOFLAGS,1, 1)) == NULL)
+ error("new_rspl() failed");
+
+ if ((dpoints = malloc(sizeof(co) * gres[0])) == NULL)
+ error("malloc dpoints[%d] failed",gres[0]);
+
+ /* Copy the points to our array */
+ if (devmask & ICX_ADDITIVE) {
+ for (i = 0; i < gres[0]; i++) {
+ dpoints[i].p[0] = 1.0 - i/(double)(gres[0]-1);
+ dpoints[i].v[0] = *((double *)tcg->t[2].fdata[gres[0]-1-i][spi[1+j]]);
+ }
+ } else {
+ for (i = 0; i < gres[0]; i++) {
+ dpoints[i].p[0] = i/(double)(gres[0]-1);
+ dpoints[i].v[0] = *((double *)tcg->t[2].fdata[i][spi[1+j]]);
+ }
+ }
+
+ pcade[j]->set_rspl(pcade[j],
+ 0,
+ (void *)dpoints, /* Read points */
+ rsplset1, /* Setting function */
+ low, high, gres, /* Low, high, resolution of grid */
+ NULL, NULL /* Default data scale */
+ );
+ free(dpoints);
+ }
+ free(bident);
+ }
+ tcg->del(tcg);
+
+ } else { /* Must be an initial or Imitation calibration */
+
+ pct->devmask = devmask;
+
+ /* Set the cal target from any user supplied parameters */
+ pct->update(pct, upct);
+
+ /* No previous absolute de reference */
+ for (j = 0; j < devchan; j++)
+ pcade[j] = NULL;
+ }
+
+ /* Common processing: */
+
+ /* Read in the patch data */
+ {
+ char buf[100];
+ char *pcsfname[2][3] = { { "XYZ_X", "XYZ_Y", "XYZ_Z" },
+ { "LAB_L", "LAB_A", "LAB_B" } };
+ int dvi[MAX_CHAN]; /* CGATS indexes for each device field */
+ int pcsix[3]; /* XYZ/Lab chanel indexes */
+ xsp2cie *sp2cie = NULL; /* Spectral conversion object */
+ xspect sp;
+ int spi[XSPECT_MAX_BANDS]; /* CGATS indexes for each wavelength */
+ char *bident = icx_inkmask2char(devmask, 0);
+
+ /* Figure out the indexes of all the device fields */
+ for (j = 0; j < devchan; j++) {
+ inkmask imask = icx_index2ink(devmask, j);
+ sprintf(buf, "%s_%s",bident,icx_ink2char(imask));
+ if ((dvi[j] = icg->find_field(icg, 0, buf)) < 0)
+ error("Can't find field %s in '%s'",buf,inname);
+#ifdef DEBUG
+ printf("devn chan %d field %s = %d\n",j,buf,dvi[j]);
+#endif
+ }
+ free(bident);
+
+ if (spec) {
+ int ii;
+ char buf[100];
+
+ if ((ii = icg->find_kword(icg, 0, "SPECTRAL_BANDS")) < 0)
+ error ("Input file doesn't contain keyword SPECTRAL_BANDS");
+ sp.spec_n = atoi(icg->t[0].kdata[ii]);
+ if ((ii = icg->find_kword(icg, 0, "SPECTRAL_START_NM")) < 0)
+ error ("Input file doesn't contain keyword SPECTRAL_START_NM");
+ sp.spec_wl_short = atof(icg->t[0].kdata[ii]);
+ if ((ii = icg->find_kword(icg, 0, "SPECTRAL_END_NM")) < 0)
+ error ("Input file doesn't contain keyword SPECTRAL_END_NM");
+ sp.spec_wl_long = atof(icg->t[0].kdata[ii]);
+ sp.norm = 100.0;
+
+ /* Find the fields for spectral values */
+ for (j = 0; j < sp.spec_n; j++) {
+ int nm;
+
+ /* Compute nearest integer wavelength */
+ nm = (int)(sp.spec_wl_short + ((double)j/(sp.spec_n-1.0))
+ * (sp.spec_wl_long - sp.spec_wl_short) + 0.5);
+
+ sprintf(buf,"SPEC_%03d",nm);
+
+ if ((spi[j] = icg->find_field(icg, 0, buf)) < 0)
+ error("Input file doesn't contain field %s",buf);
+ }
+
+ /* Create a spectral conversion object to XYZ */
+ if ((sp2cie = new_xsp2cie(illum, &cust_illum, observ, NULL, icSigXYZData, icxClamp)) == NULL)
+ error("Creation of spectral conversion object failed");
+
+ /* To add FWA comp. would have to locate/create spectral white here, */
+ /* then set the FWA comp. on. */
+ /* See profout.c */
+
+ } else {
+ /* Figure out the indexes of the PCS fields */
+ for (j = 0; j < 3; j++) {
+ if ((i = icg->find_field(icg, 0, pcsfname[isLab][j])) >= 0) {
+ if (icg->t[0].ftype[i] != r_t)
+ error ("Field %s is wrong type",pcsfname[isLab][j]);
+ pcsix[j] = i;
+#ifdef DEBUG
+ printf("PCS chan %d field %s = %d\n",j,pcsfname[isLab][j],pcsix[j]);
+#endif
+ } else {
+ error ("Failed to find field %s",pcsfname[isLab][j]);
+ }
+ }
+ }
+
+ n_cvals = 0;
+ for (j = 0; j < devchan; j++) {
+ pvals[j] = NULL;
+ n_pvals[j] = 0;
+ cvals[j] = NULL;
+ }
+
+ /* Read all the test patches in */
+ for (i = 0; i < icg->t[0].nsets; i++) {
+ double maxv = -1.0;
+ int maxch;
+
+#ifdef DEBUG
+ printf("Reading patch %d\n",i);
+#endif
+
+ /* Locate the maximum device value of any channel */
+ for (j = 0; j < devchan; j++) {
+ double val = *((double *)icg->t[0].fdata[i][dvi[j]]) / 100.0;
+ if (devmask & ICX_ADDITIVE)
+ val = 1.0 - val;
+ if (val > maxv) {
+ maxv = val;
+ maxch = j;
+ }
+ }
+#ifdef DEBUG
+ printf("max %f at chan %d\n",maxv,maxch);
+#endif
+ /* Treat white specially, and take it out of the list */
+ if (maxv < 1e-6) {
+ double wxyz[3];
+ if (n_white == 0) {
+ white.dev = 0.0;
+ for (j = 0; j < 3; j++)
+ white.XYZ[j] = 0.0;
+ }
+ if (spec) {
+ /* Read and convert the spectral value */
+ for (j = 0; j < sp.spec_n; j++)
+ sp.spec[j] = *((double *)icg->t[0].fdata[i][spi[j]]);
+
+ sp2cie->convert(sp2cie, wxyz, &sp);
+
+ } else {
+ /* Read the CIE value */
+ for (j = 0; j < 3; j++)
+ wxyz[j] = *((double *)icg->t[0].fdata[i][pcsix[j]]);
+
+ /* And convert to XYZ 0..1 */
+ if (isLab) {
+ icmLab2XYZ(&icmD50, wxyz, wxyz);
+ } else {
+ for (j = 0; j < 3; j++)
+ wxyz[j] /= 100.0;
+ }
+ }
+
+ white.dev += maxv;
+ for (j = 0; j < 3; j++)
+ white.XYZ[j] += wxyz[j];
+ n_white++;
+#ifdef DEBUG
+ printf(" white: dev %f,XYZ %f %f %f\n",
+ white.dev,white.XYZ[0]/n_white, white.XYZ[1]/n_white, white.XYZ[2]/n_white);
+#endif
+ } else {
+ wval *vp;
+ /* Check that all the non-max value channels are zero */
+ for (j = 0; j < devchan; j++) {
+ double val = *((double *)icg->t[0].fdata[i][dvi[j]]) / 100.0;
+ if (devmask & ICX_ADDITIVE)
+ val = 1.0 - val;
+ if (j == maxch)
+ continue;
+ if (val > 0.001)
+ break;
+ }
+ if (j < devchan) {
+#ifdef DEBUG
+ printf("Skipping patch\n");
+#endif
+ continue; /* Ignore this patch */
+ }
+
+ if ((pvals[maxch] = (wval *)realloc(pvals[maxch],
+ sizeof(wval) * (n_pvals[maxch]+1))) == NULL)
+ error("Realloc of pvals failed");
+ vp = &pvals[maxch][n_pvals[maxch]];
+ vp->dev = maxv;
+
+ if (spec) {
+ /* Read and convert the spectral value */
+ for (j = 0; j < sp.spec_n; j++)
+ sp.spec[j] = *((double *)icg->t[0].fdata[i][spi[j]]);
+
+ sp2cie->convert(sp2cie, vp->XYZ, &sp);
+
+ } else {
+ /* Read the CIE value */
+ for (j = 0; j < 3; j++)
+ vp->XYZ[j] = *((double *)icg->t[0].fdata[i][pcsix[j]]);
+
+ /* And convert to XYZ 0..1 */
+ if (isLab) {
+ icmLab2XYZ(&icmD50, vp->XYZ, vp->XYZ);
+ } else {
+ for (j = 0; j < 3; j++)
+ vp->XYZ[j] /= 100.0;
+ }
+ }
+ /* Temporary D50 Lab */
+ icmXYZ2Lab(&icmD50, vp->Lab, vp->XYZ);
+#ifdef DEBUG
+ printf(" patch %d: dev %f,XYZ %f %f %f, D50 Lab %f %f %f\n",
+ n_pvals[maxch], vp->dev,vp->XYZ[0], vp->XYZ[1], vp->XYZ[2],
+ vp->Lab[0], vp->Lab[1], vp->Lab[2]);
+#endif
+ n_pvals[maxch]++;
+ }
+ }
+
+ /* Average the white */
+ if (n_white == 0)
+ error("Can't find even one white patch in '%s'",inname);
+#ifdef DEBUG
+ printf("% white patches\n",n_white);
+#endif
+ for (j = 0; j < 3; j++) {
+ white.dev /= (double)n_white;
+ white.XYZ[j] /= (double)n_white;
+ }
+ icmAry2XYZ(wht, white.XYZ);
+ icmXYZ2Lab(&icmD50, white.Lab, white.XYZ);
+
+ /* Convert the Lab white reference to absolute */
+ wht.X /= wht.Y;
+ wht.Z /= wht.Y;
+ wht.Y /= wht.Y;
+
+ if (verb) {
+ icmXYZ2Lab(&icmD50, white.Lab, white.XYZ);
+ printf("Average white = XYZ %f %f %f, D50 Lab %f %f %f\n",
+ white.XYZ[0], white.XYZ[1], white.XYZ[2], white.Lab[0], white.Lab[1], white.Lab[2]);
+ }
+
+ for (j = 0; j < devchan; j++) {
+ wval *wp;
+
+ /* Add averaged white back into each channel */
+ if ((pvals[j] = (wval *)realloc(pvals[j],
+ sizeof(wval) * (n_pvals[j]+1))) == NULL)
+ error("Realloc (%d) of pvals failed",n_pvals[j]+1);
+ wp = &pvals[j][n_pvals[j]];
+ wp->dev = white.dev;
+ wp->XYZ[0] = white.XYZ[0];
+ wp->XYZ[1] = white.XYZ[1];
+ wp->XYZ[2] = white.XYZ[2];
+ n_pvals[j]++;
+
+ /* Convert all the XYZ values to Lab paper relative */
+ for (i = 0; i < n_pvals[j]; i++) {
+ wp = &pvals[j][i];
+ icmXYZ2Lab(&wht, wp->Lab, wp->XYZ);
+ }
+
+ /* Sort the channel acording to device value */
+ /* For a consistent result for identical device values, */
+ /* secondary sort by inverse CIE value */
+//#define HEAP_COMPARE(A,B) ((A).dev < (B).dev)
+#define HEAP_COMPARE(A,B) ((A).dev != (B).dev ? ((A).dev < (B).dev) : ((A).Lab[0] > (B).Lab[0]))
+ HEAPSORT(wval, pvals[j], n_pvals[j]);
+#undef HEAP_COMPARE
+
+ /* Check the maximum value looks OK */
+ if (n_pvals[j] < 5)
+ warning("Channel %d has only %d test patches",n_pvals[j]);
+ if (pvals[j][n_pvals[j]-1].dev < 0.99)
+ warning("Channel %d has max test patch value of %f",pvals[j][n_pvals[j]-1]);
+
+ if (verb > 1) {
+ printf("Chan %d has %d raw values:\n",j,n_pvals[j]);
+ for (i = 0; i < n_pvals[j]; i++) {
+ wp = &pvals[j][i];
+ printf(" %d: dev %f,XYZ %f %f %f, Lab %f %f %f\n",
+ i,wp->dev,wp->XYZ[0], wp->XYZ[1], wp->XYZ[2],
+ wp->Lab[0], wp->Lab[1], wp->Lab[2]);
+ }
+ }
+ }
+
+ if (sp2cie != NULL)
+ sp2cie->del(sp2cie);
+ }
+ icg->del(icg); /* Clean up */
+
+ /* Interpolate Lab using rspl */
+ for (j = 0; j < devchan; j++) {
+ datai low,high;
+ datao olow,ohigh;
+ int gres[MXDI];
+ double avgdev[MXDO];
+ cow *dpoints;
+
+ low[0] = 0.0;
+ high[0] = 1.0;
+ gres[0] = GRES;
+ olow[0] = 0.0;
+ ohigh[0] = 100.0;
+ olow[1] = olow[2] = -128.0;
+ ohigh[1] = ohigh[2] = 128.0;
+ avgdev[0] = 0.0025;
+ avgdev[1] = 0.005;
+ avgdev[2] = 0.005;
+
+ if ((raw[j] = new_rspl(RSPL_NOFLAGS,1, 3)) == NULL)
+ error("new_rspl() failed");
+
+ if ((dpoints = (cow *)malloc(sizeof(cow) * n_pvals[j])) == NULL)
+ error("malloc dpoints[%d] failed",n_pvals[j]);
+
+ for (i = 0; i < n_pvals[j]; i++) {
+ dpoints[i].p[0] = pvals[j][i].dev;
+ dpoints[i].v[0] = pvals[j][i].Lab[0];
+ dpoints[i].v[1] = pvals[j][i].Lab[1];
+ dpoints[i].v[2] = pvals[j][i].Lab[2];
+ if (i == 0)
+ dpoints[i].w = (double)n_white;
+ else
+ dpoints[i].w = 1.0;
+ }
+
+ raw[j]->fit_rspl_w(raw[j],
+ RSPLFLAGS,
+ dpoints, /* Test points */
+ n_pvals[j], /* Number of test points */
+ low, high, gres, /* Low, high, resolution of grid */
+ olow, ohigh, /* Default data scale */
+ smooth, /* Smoothing */
+ avgdev, /* Average deviation */
+ NULL); /* iwidth */
+
+
+ if (verb > 1) {
+ }
+
+ free(dpoints);
+ }
+
+ /* Plot the raw curves */
+ if (doplot > 1) {
+ double xx[PRES];
+ double yy[3][PRES];
+
+ for (j = 0; j < devchan; j++) {
+ printf("Chan %d raw L*a*b*:\n",j);
+ for (i = 0; i < PRES; i++) {
+ co tp; /* Test point */
+ xx[i] = i/(double)(PRES-1);
+ tp.p[0] = xx[i];
+ raw[j]->interp(raw[j], &tp);
+ yy[0][i] = tp.v[0];
+ yy[1][i] = tp.v[1];
+ yy[2][i] = tp.v[2];
+ }
+ do_plot(xx, yy[0], yy[1], yy[2], PRES);
+ }
+ }
+
+ /* Create a RSPL of absolute deltaE and relative deltaE '94 */
+ for (j = 0; j < devchan; j++) {
+ datai low,high;
+ int gres[MXDI];
+ double avgdev[MXDO];
+ co *dpoints_a;
+ co *dpoints_r;
+ double wh[3], prev[3], tot;
+
+ low[0] = 0.0;
+ high[0] = 1.0;
+ gres[0] = GRES;
+ avgdev[0] = 0.0;
+
+ if ((ade[j] = new_rspl(RSPL_NOFLAGS,1, 1)) == NULL)
+ error("new_rspl() failed");
+ if (imitate) {
+ if ((pcade[j] = new_rspl(RSPL_NOFLAGS,1, 1)) == NULL)
+ error("new_rspl() failed");
+ }
+ if ((rde[j] = new_rspl(RSPL_NOFLAGS,1, 1)) == NULL)
+ error("new_rspl() failed");
+
+ if ((dpoints_a = malloc(sizeof(co) * GRES)) == NULL)
+ error("malloc dpoints[%d] failed",GRES);
+ if ((dpoints_r = malloc(sizeof(co) * GRES)) == NULL)
+ error("malloc dpoints[%d] failed",GRES);
+
+//printf("~1 Chan %d:\n",j);
+ for (i = 0; i < GRES; i++) {
+ co tp; /* Test point */
+
+ tp.p[0] = i/(double)(GRES-1);
+ raw[j]->interp(raw[j], &tp);
+
+ dpoints_a[i].p[0] = tp.p[0];
+ dpoints_r[i].p[0] = tp.p[0];
+ if (i == 0) {
+//printf("~1 wht = %f %f %f\n",tp.v[0],tp.v[1],tp.v[2]);
+ tot = 0.0;
+ prev[0] = wh[0] = tp.v[0];
+ prev[1] = wh[1] = tp.v[1];
+ prev[2] = wh[2] = tp.v[2];
+ dpoints_a[i].v[0] = 0.0;
+ dpoints_r[i].v[0] = 0.0;
+ } else {
+//printf("~1 samp %d = %f %f %f\n",i,tp.v[0],tp.v[1],tp.v[2]);
+ /* Use Euclidean for large DE: (CIE94 stuffs up here) */
+ dpoints_a[i].v[0] = icmLabDE(tp.v, wh);
+ /* And CIE94 for small: */
+ tot += icmCIE94(tp.v, prev);
+ prev[0] = tp.v[0];
+ prev[1] = tp.v[1];
+ prev[2] = tp.v[2];
+ dpoints_r[i].v[0] = tot;
+ }
+//printf("~1 %d: dev %f, ade %f, rde %f\n",i,tp.p[0],dpoints_a[i].v[0],dpoints_r[i].v[0]);
+ }
+
+ ade[j]->set_rspl(ade[j],
+ 0,
+ (void *)dpoints_a, /* Test points */
+ rsplset1, /* Setting function */
+ low, high, gres, /* Low, high, resolution of grid */
+ NULL, NULL /* Default data scale */
+ );
+ if (imitate) {
+ pcade[j]->set_rspl(pcade[j],
+ 0,
+ (void *)dpoints_a, /* Test points */
+ rsplset1, /* Setting function */
+ low, high, gres, /* Low, high, resolution of grid */
+ NULL, NULL /* Default data scale */
+ );
+ }
+ rde[j]->set_rspl(rde[j],
+ 0,
+ (void *)dpoints_r, /* Test points */
+ rsplset1, /* Setting function */
+ low, high, gres, /* Low, high, resolution of grid */
+ NULL, NULL /* Default data scale */
+ );
+ free(dpoints_a);
+ free(dpoints_r);
+ }
+
+ if (initial) {
+ /* Establish the ademax values */
+ pct->update_devmax(pct, -1, -1.0); /* Make sure there is a value for each */
+ pct->update_ademax(pct, -1, -1.0);
+ pct->update_ademin(pct, -1, -1.0);
+ for (j = 0; j < devchan; j++) {
+ co tp; /* Test point */
+
+ if (pct->devmax[j] < 0.0) { /* Auto */
+ double maxd, maxde, maxix;
+ /* Locate the point of maximum aDE */
+ for (maxde = -1.0, i = 0; i < GRES; i++) {
+
+ tp.p[0] = i/(GRES-1.0);
+ ade[j]->interp(ade[j], &tp);
+ if (tp.v[0] > maxde) {
+ maxd = tp.p[0];
+ maxde = tp.v[0];
+ maxix = i;
+ }
+ }
+ pct->devmax[j] = maxd;
+ pct->ademax[j] = maxde; /* Temporary */
+//printf("Chan %d, dev %f, max de = %f\n", j, maxd, maxde);
+
+ if (maxd < 0.2) {
+ warning("Chan %d, max DE point %f is below < 0.2 - ignored\n", j, maxd);
+ maxix = GRES-1;
+ }
+ /* Then locate the point below that where the slope */
+ /* becomes reasonable. */
+ for (i = maxix; i >= 40; i--) {
+ double aslope, minslope = 1e6;
+ double naslope, nminslope;
+ int k;
+
+ /* Compute the minimum over a span of 20/GRES */
+ for (k = 0; k < 40; k++) {
+ double dp, dv, slope;
+
+ tp.p[0] = (i-k)/(GRES-1.0);
+ dp = tp.p[0];
+ ade[j]->interp(ade[j], &tp);
+ dv = tp.v[0];
+
+ tp.p[0] = (i-k-1)/(GRES-1.0);
+ ade[j]->interp(ade[j], &tp);
+ slope = (dv - tp.v[0])/(dp - (i-k-1)/(GRES-1.0));
+ if (k == 0)
+ aslope = slope;
+//printf(" Chan %d, dev %f, dv = %f, slope = %f\n", j, (i-k)/(GRES-1.0),dv - tp.v[0],slope);
+ if (slope < minslope)
+ minslope = slope;
+ }
+//printf("Chan %d, dev %f, aslope = %f, min slope = %f\n", j, i/(GRES-1.0),aslope,minslope);
+ /* Normalize the slopes */
+ naslope = aslope * SLOPE_NORM/pct->ademax[j];
+ nminslope = minslope * SLOPE_NORM/pct->ademax[j];
+//printf("Chan %d, dev %f, norm aslope = %f, min slope = %f\n", j, i/(GRES-1.0),naslope,nminslope);
+
+ if (naslope > MIN_SLOPE_A && nminslope >= MIN_SLOPE_O)
+ break;
+ }
+ pct->devmax[j] = i/(GRES-1.0);
+
+ /* Scale auto max device value */
+ if (maxscale[j] >= 0.0)
+ pct->devmax[j] *= maxscale[j];
+
+ /* Manually set initial dev max */
+ } else {
+ if (maxscale[j] >= 0.0)
+ warning("Chan %d, scale %.1f%% of auto max ignored since max override used\n", j, maxscale[j] * 100.0);
+ }
+
+ /* Lookup devmax to set ademax */
+ tp.p[0] = pct->devmax[j];
+ ade[j]->interp(ade[j], &tp);
+ pct->ademax[j] = tp.v[0];
+
+
+ /* Establish a default ademin value */
+ if (pct->ademin[j] < 0.0)
+ pct->ademin[j] = 0.0;
+ }
+
+ } else if (recal) {
+
+ /* Since the plot markers use devmax, look it up */
+ for (j = 0; j < devchan; j++) {
+ if ((pct->devmax[j] = rspl_ilookup(ade[j], 0.5, pct->ademax[j])) < 0.0)
+ error("Unexpected failure to invert curve %d for ADE %f",j,pct->ademax[j]);
+ }
+ }
+
+ /* Find the maximum aDE value for each curve */
+ for (j = 0; j < devchan; j++) {
+ co tp; /* Test point */
+ mxade[j] = -1e6;
+ for (i = 0; i < PRES; i++) {
+ tp.p[0] = i/(double)(PRES-1);
+ ade[j]->interp(ade[j], &tp);
+ if (tp.v[0] > mxade[j])
+ mxade[j] = tp.v[0];
+ }
+ }
+
+ if (initial || recal) {
+ /* Compute an ideal power-like value for test target */
+ for (j = 0; j < devchan; j++) {
+ double hdv; /* Half device value */
+ double hdvrde; /* Half device rDE value */
+ double thdvrde; /* Target half device rDE value */
+ double thdv; /* Target half device value */
+ double fdvrde; /* Full device rDE value */
+ co tp; /* Test point */
+
+ /* full rDE */
+ tp.p[0] = pct->devmax[j];
+ rde[j]->interp(rde[j], &tp);
+ fdvrde = tp.v[0];
+
+ /* Half device value of maximum */
+ hdv = pct->devmax[j] * 0.5;
+
+ /* rDE value half the device value */
+ tp.p[0] = hdv;
+ rde[j]->interp(rde[j], &tp);
+ hdvrde = tp.v[0];
+
+ /* rDE value we'd like at half the device value */
+ thdvrde = 0.5 * fdvrde;
+
+ /* Device value to get the rDE value we'd like at half */
+ if ((thdv = rspl_ilookup(rde[j], 0.5, thdvrde)) < 0.0)
+ error("Unexpected failure to invert curve %d for ADE %f",j,thdvrde);
+
+//printf("hdv %f, hdvrde %f, thdvrde %f, fdvrde %f, thdv %f\n",hdv,hdvrde,thdvrde, fdvrde,thdv);
+
+ /* Power like value needed to get rDE value we'd like at hald device */
+ idpow[j] = icx_powlike_needed(hdv, thdv);
+
+ }
+ }
+
+ if (verb > 1) {
+ printf("Abs DE values:\n");
+ for (i = 0; i < PRES; i++) {
+ co tp; /* Test point */
+ tp.p[0]= i/(double)(PRES-1);
+
+ printf(" dev %f, aDE",tp.p[0]);
+ for (j = 0; j < 6 && j < devchan; j++) {
+ ade[j]->interp(ade[j], &tp);
+ printf(" %f",tp.v[0]);
+ }
+ printf("\n");
+ }
+ printf("Rel DE values:\n");
+ for (i = 0; i < PRES; i++) {
+ co tp; /* Test point */
+ tp.p[0]= i/(double)(PRES-1);
+
+ printf(" dev %f, rdev",tp.p[0]);
+ for (j = 0; j < 6 && j < devchan; j++) {
+ rde[j]->interp(rde[j], &tp);
+ printf(" %f",tp.v[0]);
+ }
+ printf("\n");
+ }
+ }
+
+ if (initial || recal) {
+ if (verb && pct->is_set(pct)) {
+ for (j = 0; j < devchan; j++) {
+ printf("Chan %d Dev max %f, aDE Max %f, aDE Min %f\n",j,pct->devmax[j],pct->ademax[j],pct->ademin[j]);
+ }
+ }
+
+ if (verb) {
+ double avgpow = 0.0;
+ for (j = 0; j < devchan; j++) {
+ printf("Chan %d ideal targen power = %f\n",j,idpow[j]);
+ avgpow += idpow[j];
+ }
+ avgpow /= (double)devchan;
+ printf("Average ideal targen power = %f\n",avgpow);
+ }
+ }
+
+ /* Plot both the delta E curves, and markers */
+ if (doplot) {
+ co tp; /* Test point */
+ double xx[PRES];
+ double yy[10][PRES];
+ double cx[10], cy[10];
+ int nmark;
+
+ printf("Absolute DE plot:\n");
+
+ for (i = 0; i < PRES; i++) {
+ xx[i] = i/(double)(PRES-1);
+
+ for (j = 0; j < 10 && j < devchan; j++) {
+ tp.p[0] = xx[i];
+ ade[j]->interp(ade[j], &tp);
+ yy[j][i] = tp.v[0];
+ }
+ }
+ nmark = 0;
+ if (pct->is_set(pct)) {
+ /* Add markers for deMax */
+ for (j = 0; j < 10 && j < devchan; j++) {
+ cx[j] = pct->devmax[j];
+ cy[j] = pct->ademax[j];
+ nmark++;
+ }
+ }
+ do_plot10p(xx, devchan > 3 ? yy[3] : NULL,
+ devchan > 1 ? yy[1] : NULL,
+ devchan > 4 ? yy[4] : NULL,
+ devchan > 0 ? yy[0] : NULL,
+ devchan > 2 ? yy[2] : NULL,
+ devchan > 5 ? yy[5] : NULL,
+ devchan > 6 ? yy[6] : NULL,
+ devchan > 7 ? yy[7] : NULL,
+ devchan > 8 ? yy[8] : NULL,
+ devchan > 9 ? yy[9] : NULL,
+ PRES,
+ cx, cy, verify ? 0 : nmark);
+
+ printf("Relative DE plot:\n");
+
+ for (i = 0; i < PRES; i++) {
+ xx[i] = i/(double)(PRES-1.0);
+
+ for (j = 0; j < 10 && j < devchan; j++) {
+ tp.p[0] = xx[i];
+ rde[j]->interp(rde[j], &tp);
+ yy[j][i] = tp.v[0];
+ }
+ }
+ nmark = 0;
+ if (pct->is_set(pct)) {
+ /* Add markers for deMax */
+ for (j = 0; j < 10 && j < devchan; j++) {
+ cx[j] = pct->devmax[j];
+ tp.p[0] = cx[j];
+ rde[j]->interp(rde[j], &tp);
+ cy[j] = tp.v[0];
+ nmark++;
+ }
+ }
+ do_plot10p(xx, devchan > 3 ? yy[3] : NULL,
+ devchan > 1 ? yy[1] : NULL,
+ devchan > 4 ? yy[4] : NULL,
+ devchan > 0 ? yy[0] : NULL,
+ devchan > 2 ? yy[2] : NULL,
+ devchan > 5 ? yy[5] : NULL,
+ devchan > 6 ? yy[6] : NULL,
+ devchan > 7 ? yy[7] : NULL,
+ devchan > 8 ? yy[8] : NULL,
+ devchan > 9 ? yy[9] : NULL,
+ PRES,
+ cx, cy, verify ? 0 : nmark);
+
+ if (idpow[0] > 0.0) {
+ printf("Relative DE plot with ideal targen power applied:\n");
+
+ for (i = 0; i < PRES; i++) {
+ xx[i] = i/(double)(PRES-1.0);
+
+ for (j = 0; j < 10 && j < devchan; j++) {
+ tp.p[0] = icx_powlike(xx[i],idpow[j]);
+ rde[j]->interp(rde[j], &tp);
+ yy[j][i] = tp.v[0];
+ }
+ }
+ nmark = 0;
+ if (pct->is_set(pct)) {
+ /* Add markers for deMax */
+ for (j = 0; j < 10 && j < devchan; j++) {
+ cx[j] = pct->devmax[j];
+ tp.p[0] = icx_powlike(cx[j],idpow[j]);
+ rde[j]->interp(rde[j], &tp);
+ cy[j] = tp.v[0];
+ nmark++;
+ }
+ }
+ do_plot10p(xx, devchan > 3 ? yy[3] : NULL,
+ devchan > 1 ? yy[1] : NULL,
+ devchan > 4 ? yy[4] : NULL,
+ devchan > 0 ? yy[0] : NULL,
+ devchan > 2 ? yy[2] : NULL,
+ devchan > 5 ? yy[5] : NULL,
+ devchan > 6 ? yy[6] : NULL,
+ devchan > 7 ? yy[7] : NULL,
+ devchan > 8 ? yy[8] : NULL,
+ devchan > 9 ? yy[9] : NULL,
+ PRES,
+ cx, cy, verify ? 0 : nmark);
+ }
+ }
+
+ /* Compare the previous expected aDE against the current one */
+ if (verify) {
+ co tp; /* Test point */
+ double avg[MAX_CHAN];
+ double max[MAX_CHAN];
+ double rms[MAX_CHAN];
+ int verified = 1;
+
+ /* Verify each channel */
+ for (j = 0; j < devchan; j++) {
+ co tp;
+
+ avg[j] = 0.0;
+ max[j] = -1.0;
+ rms[j] = 0.0;
+
+ /* Sample it at GRES */
+ for (i = 0; i < GRES; i++) {
+ double iv, targ, val, tt;
+
+ iv = i/(GRES-1.0);
+
+ /* Lookup the ade that we expect */
+ tp.p[0] = iv;
+ pcade[j]->interp(pcade[j], &tp);
+ targ = tp.v[0];
+
+ /* Lookup the ade that we have */
+ tp.p[0] = iv;
+ ade[j]->interp(ade[j], &tp);
+ val = tp.v[0];
+
+ /* Compute the stats */
+ tt = fabs(targ - val);
+ avg[j] += tt;
+ rms[j] += tt * tt;
+ if (tt > max[j])
+ max[j] = tt;
+//printf("~1 chan %d, ix %d, iv %f, targ %f, actual %f, err %f\n",j,i,iv,targ,val,tt);
+ }
+ avg[j] /= (double)GRES;
+ rms[j] /= (double)GRES;
+ rms[j] = sqrt(rms[j]);
+ if (max[j] > ver_maxde)
+ verified = 0;
+ }
+ if (verb) {
+ for (j = 0; j < devchan; j++) {
+ printf("Verify results:\n");
+ printf("Channel %d has DE avg %.1f, rms %.1f, max %.1f\n",j,avg[j],rms[j],max[j]);
+ }
+ if (verified)
+ printf("Verified OK\n");
+ else
+ printf("Verification FAILED\n");
+ }
+
+ /* Plot the verification curves */
+ if (doplot) {
+ double xx[PRES];
+ double yy[10][PRES];
+
+ printf("Verification match plot:\n");
+
+ for (j = 0; j < 6 && j < devchan; j++) {
+ co tp; /* Test point */
+ double max;
+
+ /* Establish the scale */
+ tp.p[0] = 1.0;
+ pcade[j]->interp(pcade[j], &tp);
+ max = tp.v[0];
+
+ for (i = 0; i < PRES; i++) {
+ xx[i] = i/(double)(PRES-1);
+
+ /* Convert ade target to device */
+ tp.v[0] = max * xx[i];
+ if ((tp.p[0] = rspl_ilookup(pcade[j], 1.0, tp.v[0])) < 0.0)
+ error("Unexpected failure to invert curve %d for pcADE %f",j,tp.v[0]);
+ /* Convert device to actual ade */
+ ade[j]->interp(ade[j], &tp);
+ if (fabs(max) > 0.1)
+ yy[j][i] = tp.v[0]/max;
+ else
+ yy[j][i] = 0.0;
+ }
+ }
+ do_plot10(xx, devchan > 3 ? yy[3] : NULL,
+ devchan > 1 ? yy[1] : NULL,
+ devchan > 4 ? yy[4] : NULL,
+ devchan > 0 ? yy[0] : NULL,
+ devchan > 2 ? yy[2] : NULL,
+ devchan > 5 ? yy[5] : NULL,
+ devchan > 6 ? yy[6] : NULL,
+ devchan > 7 ? yy[7] : NULL,
+ devchan > 8 ? yy[8] : NULL,
+ devchan > 9 ? yy[9] : NULL,
+ PRES, 0);
+ if (!verified)
+ exit(1);
+ }
+ } else if (initial) {
+
+ /* Convert any transfer curve target points into a smooth curve */
+ if (pct->no_tpoints > 0) {
+ int gres[MXDI] = { GRES };
+ co *pnts;
+
+ if ((pnts = (co *)calloc(pct->no_tpoints + 2, sizeof(co))) == NULL)
+ error ("Malloc of rspl points failed");
+
+ for (j = 0; j < devchan; j++) {
+ int npts;
+ int gotmin, gotmax;
+
+ /* Count the number of valid points */
+ for (npts = i = 0; i < pct->no_tpoints; i++) {
+ if (pct->tpoints[i].val[j] >= 0.0)
+ npts++;
+ }
+ if (npts == 0)
+ continue; /* No target curve for this channel */
+
+ if ((tcurves[j] = new_rspl(RSPL_NOFLAGS, 1, 1)) == NULL)
+ error("new_rspl(1,1) failed");
+
+ gotmin = gotmax = 0;
+ for (npts = i = 0; i < pct->no_tpoints; i++) {
+ if (pct->tpoints[i].val[j] < 0.0)
+ continue;
+
+ pnts[npts].p[0] = pct->tpoints[i].loc;
+ pnts[npts].v[0] = pct->tpoints[i].val[j];
+ if (pnts[npts].p[0] < 0.0)
+ pnts[npts].p[0] = 0.0;
+ else if (pnts[npts].p[0] > 1.0)
+ pnts[npts].p[0] = 1.0;
+ if (pnts[npts].v[0] < 0.0)
+ pnts[npts].v[0] = 0.0;
+ else if (pnts[npts].v[0] > 1.0)
+ pnts[npts].v[0] = 1.0;
+
+ if (pnts[npts].p[0] <= 0.05)
+ gotmin = 1;
+ if (pnts[npts].p[0] >= 0.95)
+ gotmax = 1;
+ npts++;
+ }
+ /* Add default anchors if there are none supplied */
+ if (gotmin == 0) {
+ pnts[npts].p[0] = 0.0;
+ pnts[npts++].v[0] = 0.0;
+ }
+ if (gotmax == 0) {
+ pnts[npts].p[0] = 1.0;
+ pnts[npts++].v[0] = 1.0;
+ }
+
+ /* Fit the curve to the given points */
+ tcurves[j]->fit_rspl(tcurves[j], RSPLFLAGS, pnts, npts,
+ NULL, NULL, gres, NULL, NULL, TCURVESMOOTH, NULL, NULL);
+ }
+ free(pnts);
+
+ /* Plot the target curves */
+ if (doplot) {
+ double xx[PRES];
+ double yy[10][PRES];
+
+ printf("Target curves plot:\n");
+
+ for (i = 0; i < (PRES-1); i++) {
+ co tp; /* Test point */
+ double pp = i/(PRES-1.0);
+
+ xx[i] = pp;
+ for (j = 0; j < 10 && j < devchan; j++) {
+ if (tcurves[j] != NULL) {
+ tp.p[0] = pp;
+ tcurves[j]->interp(tcurves[j], &tp);
+ yy[j][i] = tp.v[0];
+ } else
+ yy[j][i] = pp;
+ }
+ }
+ do_plot10(xx, devchan > 3 ? yy[3] : NULL,
+ devchan > 1 ? yy[1] : NULL,
+ devchan > 4 ? yy[4] : NULL,
+ devchan > 0 ? yy[0] : NULL,
+ devchan > 2 ? yy[2] : NULL,
+ devchan > 5 ? yy[5] : NULL,
+ devchan > 6 ? yy[6] : NULL,
+ devchan > 7 ? yy[7] : NULL,
+ devchan > 8 ? yy[8] : NULL,
+ devchan > 9 ? yy[9] : NULL,
+ PRES, 0);
+ }
+ }
+
+ /* Do inverse lookup to create relative linearization curves */
+ n_cvals = CAL_RES;
+ for (j = 0; j < devchan; j++) {
+ co tp;
+ double rdemin, rdemax; /* Relative DE min and max targets */
+
+ /* Convert absolute de aims to relative */
+ if ((rdemax = rspl_ilookup(ade[j], 0.0, pct->ademax[j])) < 0.0)
+ error("Unexpected failure to invert curve %d for DE %f",j,pct->ademax[j]);
+
+ tp.p[0] = rdemax;
+ rde[j]->interp(rde[j], &tp);
+ rdemax = tp.v[0];
+
+ if ((rdemin = rspl_ilookup(ade[j], 1.0, pct->ademin[j])) < 0.0)
+ error("Unexpected failure to invert curve %d for DE %f",j,pct->ademax[j]);
+
+ tp.p[0] = rdemin;
+ rde[j]->interp(rde[j], &tp);
+ rdemin = tp.v[0];
+
+ if (verb > 0)
+ printf("Chan %d: rDE Max = %f, rDE Min = %f\n",j,rdemax,rdemin);
+
+ if ((cvals[j] = (wval *)malloc(sizeof(wval) * n_cvals)) == NULL)
+ error("Malloc of %d cvals failed",n_cvals);
+
+ /* Convert relative delta E aim to device value */
+ for (i = 0; i < n_cvals; i++) {
+ double x = i/(n_cvals-1.0);
+ double inv;
+
+ cvals[j][i].inv = x;
+
+ /* Apply any aim tweak curve */
+ if (tcurves[j] != NULL) {
+ tp.p[0] = x;
+ tcurves[j]->interp(tcurves[j], &tp);
+ x = tp.v[0];
+ }
+
+ inv = x * (rdemax - rdemin) + rdemin;
+
+ if ((cvals[j][i].dev = rspl_ilookup(rde[j], 0.5, inv)) < 0.0)
+ error("Unexpected failure to invert curve %d for DE %f",j,inv);
+//printf("~1 chan %d, step %d, inv %f, detarg %f, got dev %f\n",j,i,x,pp[0].v[0],pp[k].p[0]);
+ }
+ }
+
+ } else if (recal || imitate) {
+
+ n_cvals = CAL_RES;
+ for (j = 0; j < devchan; j++) {
+ co tp;
+
+ if ((cvals[j] = (wval *)malloc(sizeof(wval) * n_cvals)) == NULL)
+ error("Malloc of %d cvals failed",n_cvals);
+
+ /* Lookup the expected ade for each input device value, and */
+ /* then translate it into the required output device value */
+ for (i = 0; i < n_cvals; i++) {
+ double x = i/(n_cvals-1.0);
+
+ cvals[j][i].inv = tp.p[0] = x;
+ pcade[j]->interp(pcade[j], &tp);
+
+ if ((cvals[j][i].dev = rspl_ilookup(ade[j], 0.5, tp.v[0])) < 0.0)
+ error("Unexpected failure to invert curve %d for DE %f",j,tp.v[0]);
+//printf("~1 chan %d, ix %d, inv %f, pcade %f, iade %f\n",j,i,x,tp.v[0],cvals[j][i].dev);
+ }
+ }
+ }
+
+ if (initial || recal || imitate) {
+
+ if (verb > 1) {
+ printf("Calibration curve values:\n");
+ for (i = 0; i < n_cvals; i++) {
+ printf(" inv %f, dev",cvals[0][i].inv);
+ for (j = 0; j < devchan; j++) {
+ printf(" %f",cvals[j][i].dev);
+ }
+ printf("\n");
+ }
+ }
+
+ /* Plot the calibration curves */
+ if (doplot) {
+ double xx[PRES];
+ double yy[10][PRES];
+
+ printf("Calibration curve plot:\n");
+
+ for (i = 0; i < n_cvals; i++) {
+ xx[i] = cvals[0][i].inv;
+ for (j = 0; j < 10 && j < devchan; j++) {
+ yy[j][i] = cvals[j][i].dev;
+ }
+ }
+ do_plot10(xx, devchan > 3 ? yy[3] : NULL, /* Black */
+ devchan > 1 ? yy[1] : NULL, /* Red */
+ devchan > 4 ? yy[4] : NULL, /* Green */
+ devchan > 0 ? yy[0] : NULL, /* Blue */
+ devchan > 2 ? yy[2] : NULL, /* Yellow */
+ devchan > 5 ? yy[5] : NULL, /* Purple */
+ devchan > 6 ? yy[6] : NULL, /* Brown */
+ devchan > 7 ? yy[7] : NULL, /* Orange */
+ devchan > 8 ? yy[8] : NULL, /* Grey */
+ devchan > 9 ? yy[9] : NULL, /* White */
+ PRES, 0);
+ }
+
+ /* Write out an Argyll .CAL file */
+ if (dowrite) {
+ cgats *ocg; /* output cgats structure */
+ time_t clk = time(0);
+ struct tm *tsp = localtime(&clk);
+ char *atm = asctime(tsp); /* Ascii time */
+ char *ident = icx_inkmask2char(devmask, 1);
+ char *bident = icx_inkmask2char(devmask, 0);
+ cgats_set_elem *setel; /* Array of set value elements */
+ int nsetel = 0;
+ int ncps; /* Number of curve parameters */
+ double *cps[3]; /* Arrays of curve parameters */
+ char *bp = NULL, buf[100]; /* Buffer to sprintf into */
+ co tp;
+
+ ocg = new_cgats(); /* Create a CGATS structure */
+ ocg->add_other(ocg, "CAL"); /* our special type is Calibration file */
+
+ ocg->add_table(ocg, tt_other, 0); /* Add a table for RAMDAC values */
+ ocg->add_kword(ocg, 0, "DESCRIPTOR", "Argyll Device Calibration Curves",NULL);
+ ocg->add_kword(ocg, 0, "ORIGINATOR", "Argyll printcal", NULL);
+ atm[strlen(atm)-1] = '\000'; /* Remove \n from end */
+ ocg->add_kword(ocg, 0, "CREATED",atm, NULL);
+
+ ocg->add_kword(ocg, 0, "DEVICE_CLASS","OUTPUT", NULL);
+ ocg->add_kword(ocg, 0, "COLOR_REP", ident, NULL);
+
+ if (xpi.deviceMfgDesc != NULL)
+ ocg->add_kword(ocg, 0, "MANUFACTURER", xpi.deviceMfgDesc, NULL);
+ if (xpi.modelDesc != NULL)
+ ocg->add_kword(ocg, 0, "MODEL", xpi.modelDesc, NULL);
+ if (xpi.profDesc != NULL)
+ ocg->add_kword(ocg, 0, "DESCRIPTION", xpi.profDesc, NULL);
+ if (xpi.copyright != NULL)
+ ocg->add_kword(ocg, 0, "COPYRIGHT", xpi.copyright, NULL);
+
+ /* Setup the table which holds the translation from calibrated */
+ /* device value "I" to the raw device channel value */
+ sprintf(buf, "%s_I",bident);
+ ocg->add_field(ocg, 0, buf, r_t);
+ nsetel++;
+ for (j = 0; j < devchan; j++) {
+ inkmask imask = icx_index2ink(devmask, j);
+ sprintf(buf, "%s_%s",bident,icx_ink2char(imask));
+ ocg->add_field(ocg, 0, buf, r_t);
+ nsetel++;
+ }
+ if ((setel = (cgats_set_elem *)malloc(sizeof(cgats_set_elem) * nsetel)) == NULL)
+ error("Malloc failed!");
+
+ /* Write the per channel device to device loolup curve values */
+ if (devmask & ICX_ADDITIVE) {
+ for (i = n_cvals-1; i >= 0; i--) {
+
+ setel[0].d = 1.0 - cvals[0][i].inv;
+ for (j = 0; j < devchan; j++)
+ setel[1+j].d = 1.0 - cvals[j][i].dev;
+ ocg->add_setarr(ocg, 0, setel);
+ }
+ } else {
+ for (i = 0; i < n_cvals; i++) {
+
+ setel[0].d = cvals[0][i].inv;
+ for (j = 0; j < devchan; j++)
+ setel[1+j].d = cvals[j][i].dev;
+ ocg->add_setarr(ocg, 0, setel);
+ }
+ }
+
+ free(setel);
+
+ /* Write the calibration target information to a second table */
+ if (pct->write(pct, ocg, 1) != 0)
+ error("Writing cal target info to cal file '%s'",outname);
+
+ /* Add a third table which is the expected absolute DE response */
+ /* of the calibrated device. */
+ ocg->add_table(ocg, tt_other, 0); /* Add a table for RAMDAC values */
+ ocg->add_kword(ocg, 2, "DESCRIPTOR", "Argyll Output Calibration Expected DE Response",NULL);
+ ocg->add_kword(ocg, 2, "ORIGINATOR", "Argyll printcal", NULL);
+ atm[strlen(atm)-1] = '\000'; /* Remove \n from end */
+ ocg->add_kword(ocg, 2, "CREATED",atm, NULL);
+
+ ocg->add_kword(ocg, 2, "DEVICE_CLASS","OUTPUT", NULL);
+ ocg->add_kword(ocg, 2, "COLOR_REP", ident, NULL);
+
+ /* Setup the table which holds the translation from calibrated */
+ /* device value "I" to the expected absolute deltaE value for */
+ /* each colorant. */
+ sprintf(buf, "%s_I_DE",bident);
+ ocg->add_field(ocg, 2, buf, r_t);
+ nsetel++;
+ for (j = 0; j < devchan; j++) {
+ inkmask imask = icx_index2ink(devmask, j);
+ sprintf(buf, "%s_%s_DE",bident,icx_ink2char(imask));
+ ocg->add_field(ocg, 2, buf, r_t);
+ nsetel++;
+ }
+ if ((setel = (cgats_set_elem *)malloc(sizeof(cgats_set_elem) * nsetel)) == NULL)
+ error("Malloc failed!");
+
+ for (i = 0; i < n_cvals; i++) {
+ int ix = i;
+
+ /* Calibrated device value */
+ if (devmask & ICX_ADDITIVE) {
+ ix = n_cvals -1 - i;
+ setel[0].d = 1.0 - cvals[0][ix].inv;
+ } else
+ setel[0].d = cvals[0][ix].inv;
+
+ for (j = 0; j < devchan; j++) {
+ tp.p[0] = cvals[j][ix].dev; /* Raw device value */
+ ade[j]->interp(ade[j], &tp); /* Corresponding ade value */
+ setel[1+j].d = tp.v[0];
+ }
+ ocg->add_setarr(ocg, 2, setel);
+ }
+
+ free(setel);
+
+ if (ocg->write_name(ocg, outname))
+ error("Write error to file '%s': %s",outname,ocg->err);
+
+ if (verb)
+ printf("Written calibration file '%s'\n",outname);
+
+ ocg->del(ocg); /* Clean up */
+ free(ident);
+ free(bident);
+ }
+
+ /*
+ The structure of *.AMP is very simple.
+
+ It has 5 tables that have
+ 256 entries that are 8-bit (byte), even for 16 bit mode.
+
+ The first table 0000..00FFh is the CMYK channel.
+ The second table 0100..01FFh is the C channel.
+ The third table 0200..02FFh is the M channel.
+ The fourth table 0300..03FFh is the Y channel.
+ The fifth table 0300..03FFh is the K channel.
+
+ Table position nn00h is the black end and table position nnFFh
+ is the white end.
+
+ */
+
+ /* Write an Adobe map format (.AMP) file */
+ /* (It's not clear if more than 4 channels is allowed) */
+ if (dowrite && doamp) {
+ FILE *fp;
+ cgatsFile *p;
+ char nmode[50] = { '\000' };
+
+ strcpy(nmode, "w");
+#if defined(O_BINARY) || defined(_O_BINARY)
+ strcat(nmode, "b");
+#endif
+ if ((fp = fopen(ampname, nmode)) == NULL)
+ error("Couldn't open '%s' for writing",ampname);
+
+ /* CMYK table is unity */
+ for (i = 0; i < 256; i++) {
+ if (putc(i,fp) == EOF)
+ error("Error writing to fle '%s'",ampname);
+ }
+ for (j = 0; j < devchan; j++) {
+ for (i = 0; i < 256; i++) {
+ int x;
+ if (devmask & ICX_ADDITIVE)
+ x = (int)(cvals[j][i].dev * 255.0 + 0.5); /* ??? */
+ else
+ x = 255 - (int)(cvals[j][255 - i].dev * 255.0 + 0.5);
+ if (putc(x,fp) == EOF)
+ error("Error writing to fle '%s'",ampname);
+//printf("~1 chan %d, inv %d, dev %d\n",j,i,x);
+ }
+ }
+ /* Extra 1:1 table */
+ for (i = 0; i < 256; i++) {
+ if (putc(i,fp) == EOF)
+ error("Error writing to fle '%s'",ampname);
+ }
+
+ if (fclose(fp) != 0)
+ error("Closing '%s' failed",ampname);
+
+ if (verb)
+ printf("Written calibration curves to '%s'\n",ampname);
+ }
+ }
+
+ /* Free up various possible allocations */
+ for (j = 0; j < devchan; j++) {
+ if (pvals[j] != NULL)
+ free(pvals[j]);
+ if (raw[j] != NULL)
+ raw[j]->del(raw[j]);
+ if (ade[j] != NULL)
+ ade[j]->del(ade[j]);
+ if (rde[j] != NULL)
+ rde[j]->del(rde[j]);
+ if (pcade[j] != NULL)
+ pcade[j]->del(pcade[j]);
+ if (cvals[j] != NULL)
+ free(cvals[j]);
+ if (tcurves[j] != NULL)
+ tcurves[j]->del(tcurves[j]);
+ }
+
+ return 0;
+}
+
+
+
+
+
+
+
+
+
+
diff --git a/profile/prof.h b/profile/prof.h
new file mode 100644
index 0000000..074bb73
--- /dev/null
+++ b/profile/prof.h
@@ -0,0 +1,109 @@
+#ifndef PROF_H
+#define PROF_H
+/*
+ * ICC Profile creation library.
+ *
+ * Author: Graeme W. Gill
+ * Date: 11/10/00
+ * Version: 1.00
+ *
+ * Copyright 2000 Graeme W. Gill
+ * All rights reserved.
+ *
+ * This material is licenced under the GNU AFFERO GENERAL PUBLIC LICENSE Version 3 :-
+ * see the License.txt file for licencing details.
+ */
+
+/*
+ * This library provide high level routines to create device ICC
+ * profiles from argyll cgats patch test data.
+ */
+
+
+/* Profile algorithm type */
+typedef enum {
+ prof_default = 0, /* Default for type of device */
+ prof_clutLab = 1, /* Lab clut. */
+ prof_clutXYZ = 2, /* XYZ clut. */
+ prof_gammat = 3, /* XYZ gamut + matrix */
+ prof_shamat = 4, /* XYZ shaper + matrix */
+ prof_gam1mat = 5, /* XYZ shared TRC gamut + matrix */
+ prof_sha1mat = 6, /* XYZ shared TRC shaper + matrix */
+ prof_matonly = 7 /* XYZ matrix, linear */
+} prof_atype;
+
+/* Output or Display device */
+void make_output_icc(
+ prof_atype ptype, /* Profile output type */
+ int mtxtoo, /* NZ if matrix tags should be created for Display XYZ cLUT */
+ icmICCVersion iccver, /* ICC profile version to create */
+ int verb, /* Vebosity level, 0 = none */
+ int iquality, /* A2B table quality, 0..2 */
+ int oquality, /* B2A table quality, 0..2 */
+ int noiluts, /* nz to supress creation of input (Device) shaper luts */
+ int noisluts, /* nz to supress creation of input sub-grid (Device) shaper luts */
+ int nooluts, /* nz to supress creation of output (PCS) shaper luts */
+ int nocied, /* nz to supress inclusion of .ti3 data in profile */
+ int noptop, /* nz to use colorimetic source gamut to make perceptual table */
+ int nostos, /* nz to use colorimetic source gamut to make perceptual table */
+ int gamdiag, /* Make gamut mapping diagnostic wrl plots */
+ int verify, /* nz to print verification */
+ int clipprims, /* Clip white, black and primaries */
+ icxInk *ink, /* Ink limit/black generation setup */
+ char *in_name, /* input .ti3 file name */
+ char *file_name, /* output icc name */
+ cgats *icg, /* input cgats structure */
+ int spec, /* Use spectral data flag */
+ icxIllumeType tillum, /* Target/simulated instrument illuminant */
+ xspect *cust_tillum, /* Possible custom target/simulated instrument illumination */
+ icxIllumeType illum, /* Spectral illuminant */
+ xspect *cust_illum, /* Possible custom illumination */
+ icxObserverType observ, /* Spectral observer */
+ int fwacomp, /* FWA compensation requested */
+ double smooth, /* RSPL smoothing factor, -ve if raw */
+ double avgdev, /* reading Average Deviation as a proportion of the input range */
+ char *ipname, /* input icc profile - enables gamut map, NULL if none */
+ char *sgname, /* source image gamut - NULL if none */
+ char *absname[3], /* abstract profile name for each table */
+ /* may be duplicated, NULL if none */
+ int sepsat, /* Create separate Saturation B2A */
+ icxViewCond *ivc_p, /* Input Viewing Parameters for CIECAM97s */
+ icxViewCond *ovc_p, /* Output Viewing Parameters for CIECAM97s (enables CAM clip) */
+ int ivc_e, /* Input Enumerated viewing condition */
+ int ovc_e, /* Output Enumerated viewing condition */
+ icxGMappingIntent *pgmi,/* Perceptual gamut mapping intent */
+ icxGMappingIntent *sgmi,/* Saturation gamut mapping intent */
+ profxinf *pi /* Optional Profile creation extra data */
+);
+
+/* Input device */
+void make_input_icc(
+ prof_atype ptype, /* Profile algorithm type */
+ icmICCVersion iccver, /* ICC profile version to create */
+ int verb,
+ int iquality, /* A2B table quality, 0..3 */
+ int oquality, /* B2A table quality, 0..3 */
+ int noisluts, /* nz to supress creation of input (Device) shaper luts */
+ int noipluts, /* nz to supress creation of input (Device) position luts */
+ int nooluts, /* nz to supress creation of output (PCS) shaper luts */
+ int nocied, /* nz to supress inclusion of .ti3 data in profile */
+ int verify,
+ int autowpsc, /* nz for Auto scale the WP to prevent clipping above WP patch */
+ int clipovwp, /* nz for Clip cLUT values above WP */
+ double wpscale, /* >= 0.0 for media white point scale factor */
+ int dob2a, /* nz to create a B2A table as well */
+ int extrap, /* nz to create extra cLUT interpolation points */
+ int clipprims, /* Clip white, black and primaries */
+ char *in_name, /* input .ti3 file name */
+ char *file_name, /* output icc name */
+ cgats *icg, /* input cgats structure */
+ int spec, /* Use spectral data flag */
+ icxIllumeType illum, /* Spectral illuminant */
+ xspect *cust_illum, /* Possible custom illumination */
+ icxObserverType observ, /* Spectral observer */
+ double smooth, /* RSPL smoothing factor, -ve if raw */
+ double avgdev, /* reading Average Deviation as a proportion of the input range */
+ profxinf *xpi /* Optional Profile creation extra data */
+);
+
+#endif /* PROF_H */
diff --git a/profile/profcheck.c b/profile/profcheck.c
new file mode 100644
index 0000000..ca9b0b2
--- /dev/null
+++ b/profile/profcheck.c
@@ -0,0 +1,1349 @@
+
+/*
+ * Argyll Color Correction System
+ * Color Device profile checker.
+ *
+ * Author: Graeme W. Gill
+ * Date: 15/7/2001
+ *
+ * Copyright 2001 - 2005 Graeme W. Gill
+ * All rights reserved.
+ *
+ * This material is licenced under the GNU AFFERO GENERAL PUBLIC LICENSE Version 3 :-
+ * see the License.txt file for licencing details.
+ */
+
+/*
+ * This program takes in the .ti3 scattered test chart
+ * points, and checks them against an ICC profile.
+ * forward ICC device profile.
+ */
+
+/*
+ * TTBD:
+ * Switch to generic colorant read code rather than Grey/RGB/CMYK,
+ * and allow checking ICC profiles > 4 colors
+ */
+
+#undef DEBUG
+
+#define IMP_MONO /* Turn on development code */
+
+#define verbo stdout
+
+#include <stdio.h>
+#include <string.h>
+#include <math.h>
+#include <ctype.h>
+#include "copyright.h"
+#include "aconfig.h"
+#include "numlib.h"
+#include "cgats.h"
+#include "xicc.h"
+#include "insttypes.h"
+#include "sort.h"
+
+void
+usage(void) {
+ fprintf(stderr,"Check accuracy of ICC profile, Version %s\n",ARGYLL_VERSION_STR);
+ fprintf(stderr,"Author: Graeme W. Gill, licensed under the AGPL Version 3\n");
+ fprintf(stderr,"usage: profcheck [-options] data.ti3 iccprofile.icm\n");
+ fprintf(stderr," -v [level] Verbosity level (default 1), 2 to print each DE\n");
+ fprintf(stderr," -c Show CIE94 delta E values\n");
+ fprintf(stderr," -k Show CIEDE2000 delta E values\n");
+ fprintf(stderr," -w create VRML visualisation (iccprofile.wrl)\n");
+ fprintf(stderr," -x Use VRML axes\n");
+ fprintf(stderr," -m Make VRML lines a minimum of 0.5\n");
+ fprintf(stderr," -e Color vectors acording to delta E\n");
+ fprintf(stderr," -d devval1,deval2,devvalN\n");
+ fprintf(stderr," Specify a device value to sort against\n");
+ fprintf(stderr," -p Sort device value by PCS (Lab) target\n");
+ fprintf(stderr," -f [illum] Use Fluorescent Whitening Agent compensation [opt. simulated inst. illum.:\n");
+ fprintf(stderr," M0, M1, M2, A, C, D50 (def.), D50M2, D65, F5, F8, F10 or file.sp]\n");
+ fprintf(stderr," -i illum Choose illuminant for computation of CIE XYZ from spectral data & FWA:\n");
+ fprintf(stderr," A, C, D50 (def.), D50M2, D65, F5, F8, F10 or file.sp\n");
+ fprintf(stderr," -o observ Choose CIE Observer for spectral data:\n");
+ fprintf(stderr," 1931_2 (def), 1964_10, S&B 1955_2, shaw, J&V 1978_2\n");
+ fprintf(stderr," -I intent r = relative colorimetric, a = absolute (default)\n");
+ fprintf(stderr," data.ti3 Test data file\n");
+ fprintf(stderr," iccprofile.icm Profile to check against\n");
+ exit(1);
+ }
+
+FILE *start_vrml(char *name, int doaxes);
+void start_line_set(FILE *wrl);
+void add_vertex(FILE *wrl, double pp[3]);
+void make_lines(FILE *wrl, int ppset);
+void make_de_lines(FILE *wrl);
+void end_vrml(FILE *wrl);
+
+/* Patch value type */
+typedef struct {
+ char sid[50]; /* sample id */
+ char slo[50]; /* sample location, "" if not known */
+ double p[MAX_CHAN]; /* Device value */
+ double v[3]; /* CIE value */
+ double dp; /* Delta from target value */
+ double dv; /* Delta from CIE value */
+} pval;
+
+int main(int argc, char *argv[])
+{
+ int fa,nfa; /* current argument we're looking at */
+ int verb = 0;
+ int cie94 = 0;
+ int cie2k = 0;
+ int dovrml = 0;
+ int dominl = 0;
+ int doaxes = 0;
+ int dodecol = 0;
+ char ti3name[MAXNAMEL+1] = { 0 }; /* Input cgats file base name */
+ cgats *icg; /* input cgats structure */
+ char iccname[MAXNAMEL+1] = { 0 }; /* Input icc file base name */
+ icmFile *rd_fp;
+ icRenderingIntent intent = icAbsoluteColorimetric;
+ icc *rd_icco;
+ icmLuBase *luo;
+ char out_name[MAXNAMEL+1], *xl; /* VRML name */
+ FILE *wrl = NULL;
+
+ int fwacomp = 0; /* FWA compensation */
+ int isdisp = 0; /* nz if this is a display device, 0 if output */
+ int isdnormed = 0; /* Has display data been normalised to 100 ? */
+ int spec = 0; /* Use spectral data flag */
+ icxIllumeType tillum = icxIT_none; /* Target/simulated instrument illuminant */
+ xspect cust_tillum, *tillump = NULL; /* Custom target/simulated illumination spectrum */
+ icxIllumeType illum = icxIT_D50; /* Spectral defaults */
+ xspect cust_illum; /* Custom illumination spectrum */
+ icxObserverType observ = icxOT_CIE_1931_2;
+
+ int ddevv = 0; /* Do device value sort */
+ double devval[MAX_CHAN]; /* device value to sort on */
+ int sortbypcs = 0; /* Sort by PCS */
+
+ int npat; /* Number of patches */
+ pval *tpat; /* Patch input values */
+ int i, j, rv = 0;
+ icColorSpaceSignature devspace = 0; /* The device colorspace */
+ int isAdditive = 0; /* 0 if subtractive, 1 if additive colorspace */
+ int isLab = 0; /* 0 if input is XYZ, 1 if input is Lab */
+ int devchan = 0; /* Number of device chanels */
+
+#if defined(__IBMC__)
+ _control87(EM_UNDERFLOW, EM_UNDERFLOW);
+ _control87(EM_OVERFLOW, EM_OVERFLOW);
+#endif
+
+ error_program = "profcheck";
+
+ if (argc <= 1)
+ usage();
+
+ /* Process the arguments */
+ for(fa = 1;fa < argc;fa++) {
+
+ nfa = fa; /* skip to nfa if next argument is used */
+ if (argv[fa][0] == '-') { /* Look for any flags */
+ char *na = NULL; /* next argument after flag, null if none */
+
+ if (argv[fa][2] != '\000')
+ na = &argv[fa][2]; /* next is directly after flag */
+ else {
+ if ((fa+1) < argc) {
+ if (argv[fa+1][0] != '-') {
+ nfa = fa + 1;
+ na = argv[nfa]; /* next is seperate non-flag argument */
+ }
+ }
+ }
+
+ if (argv[fa][1] == '?')
+ usage();
+
+ /* Verbosity */
+ else if (argv[fa][1] == 'v') {
+ verb = 1;
+ if (na != NULL && isdigit(na[0])) {
+ verb = atoi(na);
+ }
+ }
+
+ /* VRML */
+ else if (argv[fa][1] == 'w')
+ dovrml = 1;
+
+ /* Minimum line length */
+ else if (argv[fa][1] == 'm')
+ dominl = 1;
+
+ /* Axes */
+ else if (argv[fa][1] == 'x')
+ doaxes = 1;
+
+ /* Delta E coloring */
+ else if (argv[fa][1] == 'e')
+ dodecol = 1;
+
+ else if (argv[fa][1] == 'c') {
+ cie94 = 1;
+ cie2k = 0;
+ }
+
+ else if (argv[fa][1] == 'k') {
+ cie94 = 0;
+ cie2k = 1;
+ }
+
+ /* Device sort value */
+ else if (argv[fa][1] == 'd') {
+ char *tp, buf[200];
+ int ndv;
+ fa = nfa;
+ if (na == NULL) usage();
+
+ ddevv = 1;
+ strcpy(buf, na);
+
+ /* Replace ',' with '\000' */
+ for (ndv = 1,tp = buf; *tp != '\000'; tp++) {
+ if (*tp == ',') {
+ *tp = '\000';
+ ndv++;
+ }
+ }
+ if (ndv >= MAX_CHAN)
+ ndv = MAX_CHAN;
+
+ for (tp = buf, i = 0; i < ndv; i++, tp += strlen(tp) + 1) {
+ devval[i] = atof(tp);
+ }
+ }
+
+ else if (argv[fa][1] == 'p')
+ sortbypcs = 1;
+
+ /* FWA compensation */
+ else if (argv[fa][1] == 'f') {
+ fwacomp = 1;
+
+ if (na != NULL) { /* Argument is present - target/simulated instr. illum. */
+ fa = nfa;
+ if (strcmp(na, "A") == 0
+ || strcmp(na, "M0") == 0) {
+ spec = 1;
+ tillum = icxIT_A;
+ } else if (strcmp(na, "C") == 0) {
+ spec = 1;
+ tillum = icxIT_C;
+ } else if (strcmp(na, "D50") == 0
+ || strcmp(na, "M1") == 0) {
+ spec = 1;
+ tillum = icxIT_D50;
+ } else if (strcmp(na, "D50M2") == 0
+ || strcmp(na, "M2") == 0) {
+ spec = 1;
+ tillum = icxIT_D50M2;
+ } else if (strcmp(na, "D65") == 0) {
+ spec = 1;
+ tillum = icxIT_D65;
+ } else if (strcmp(na, "F5") == 0) {
+ spec = 1;
+ tillum = icxIT_F5;
+ } else if (strcmp(na, "F8") == 0) {
+ spec = 1;
+ tillum = icxIT_F8;
+ } else if (strcmp(na, "F10") == 0) {
+ spec = 1;
+ tillum = icxIT_F10;
+ } else { /* Assume it's a filename */
+ spec = 1;
+ tillum = icxIT_custom;
+ if (read_xspect(&cust_tillum, na) != 0)
+ usage();
+ }
+ }
+ }
+ /* Spectral Illuminant type */
+ else if (argv[fa][1] == 'i') {
+ fa = nfa;
+ if (na == NULL) usage();
+ if (strcmp(na, "A") == 0) {
+ spec = 1;
+ illum = icxIT_A;
+ } else if (strcmp(na, "C") == 0) {
+ spec = 1;
+ illum = icxIT_C;
+ } else if (strcmp(na, "D50") == 0) {
+ spec = 1;
+ illum = icxIT_D50;
+ } else if (strcmp(na, "D50M2") == 0) {
+ spec = 1;
+ illum = icxIT_D50M2;
+ } else if (strcmp(na, "D65") == 0) {
+ spec = 1;
+ illum = icxIT_D65;
+ } else if (strcmp(na, "F5") == 0) {
+ spec = 1;
+ illum = icxIT_F5;
+ } else if (strcmp(na, "F8") == 0) {
+ spec = 1;
+ illum = icxIT_F8;
+ } else if (strcmp(na, "F10") == 0) {
+ spec = 1;
+ illum = icxIT_F10;
+ } else { /* Assume it's a filename */
+ spec = 1;
+ illum = icxIT_custom;
+ if (read_xspect(&cust_illum, na) != 0)
+ usage();
+ }
+ }
+
+ /* Spectral Observer type */
+ else if (argv[fa][1] == 'o') {
+ fa = nfa;
+ if (na == NULL) usage();
+ if (strcmp(na, "1931_2") == 0) { /* Classic 2 degree */
+ spec = 1;
+ observ = icxOT_CIE_1931_2;
+ } else if (strcmp(na, "1964_10") == 0) { /* Classic 10 degree */
+ spec = 1;
+ observ = icxOT_CIE_1964_10;
+ } else if (strcmp(na, "1955_2") == 0) { /* Stiles and Burch 1955 2 degree */
+ spec = 1;
+ observ = icxOT_Stiles_Burch_2;
+ } else if (strcmp(na, "1978_2") == 0) { /* Judd and Voss 1978 2 degree */
+ spec = 1;
+ observ = icxOT_Judd_Voss_2;
+ } else if (strcmp(na, "shaw") == 0) { /* Shaw and Fairchilds 1997 2 degree */
+ spec = 1;
+ observ = icxOT_Shaw_Fairchild_2;
+ } else
+ usage();
+ }
+
+ /* Intent (only applies to ICC profile) */
+ else if (argv[fa][1] == 'I') {
+ fa = nfa;
+ if (na == NULL) usage();
+ switch (na[0]) {
+ case 'r':
+ intent = icRelativeColorimetric;
+ break;
+ case 'a':
+ intent = icAbsoluteColorimetric;
+ break;
+ default:
+ usage();
+ }
+ }
+
+ else
+ usage();
+ } else
+ break;
+ }
+
+ /* Get the file name arguments */
+ if (fa >= argc || argv[fa][0] == '-') usage();
+ strncpy(ti3name,argv[fa++],MAXNAMEL); ti3name[MAXNAMEL] = '\000';
+
+ if (fa >= argc || argv[fa][0] == '-') usage();
+ strncpy(iccname,argv[fa++],MAXNAMEL); iccname[MAXNAMEL] = '\000';
+
+ strncpy(out_name,iccname,MAXNAMEL-4); out_name[MAXNAMEL-4] = '\000';
+ if ((xl = strrchr(out_name, '.')) == NULL) /* Figure where extention is */
+ xl = out_name + strlen(out_name);
+ strcpy(xl,".wrl");
+
+ if (fwacomp && spec == 0)
+ error ("FWA compensation only works when viewer and/or illuminant selected");
+
+ /* Open and look at the .ti3 profile patches file */
+ icg = new_cgats(); /* Create a CGATS structure */
+ icg->add_other(icg, "CTI3"); /* our special input type is Calibration Target Information 3 */
+
+ if (icg->read_name(icg, ti3name))
+ error("CGATS file read error on '%s': %s",ti3name,icg->err);
+
+ if (icg->ntables == 0 || icg->t[0].tt != tt_other || icg->t[0].oi != 0)
+ error ("Input file '%s' isn't a CTI3 format file",ti3name);
+ if (icg->ntables < 1)
+ error ("Input file '%s' doesn't contain at least one table",ti3name);
+
+ /* See if CIE is actually available - some sources of .TI3 don't provide it */
+ if (!spec
+ && icg->find_field(icg, 0, "LAB_L") < 0
+ && icg->find_field(icg, 0, "XYZ_X") < 0) {
+
+ if (icg->find_kword(icg, 0, "SPECTRAL_BANDS") < 0)
+ error ("Neither CIE nor spectral data found in file '%s'",ti3name);
+
+ /* Switch to using spectral information */
+ if (verb)
+ printf("No CIE data found, switching to spectral with standard observer & D50\n");
+ spec = 1;
+ illum = icxIT_D50;
+ observ = icxOT_CIE_1931_2;
+ }
+
+ /* Figure out what sort of device it is */
+ {
+ int ti;
+
+ if ((ti = icg->find_kword(icg, 0, "DEVICE_CLASS")) < 0)
+ error ("Input file '%s' doesn't contain keyword DEVICE_CLASS",ti3name);
+
+ if (strcmp(icg->t[0].kdata[ti],"DISPLAY") == 0) {
+ isdisp = 1;
+ }
+
+ /* See if the CIE data has been normalised to Y = 100 */
+ if ((ti = icg->find_kword(icg, 0, "NORMALIZED_TO_Y_100")) < 0
+ || strcmp(icg->t[0].kdata[ti],"NO") == 0) {
+ isdnormed = 0;
+ } else {
+ isdnormed = 1;
+ }
+ }
+
+ /* Figure out what sort of device colorspace it is */
+ {
+ int ti;
+
+ if ((ti = icg->find_kword(icg, 0, "COLOR_REP")) < 0)
+ error("Input file '%s' doesn't contain keyword COLOR_REPS",ti3name);
+
+ if (strcmp(icg->t[0].kdata[ti],"CMYK_XYZ") == 0) {
+ devspace = icSigCmykData;
+ devchan = 4;
+ isLab = 0;
+ isAdditive = 0;
+ } else if (strcmp(icg->t[0].kdata[ti],"CMYK_LAB") == 0) {
+ devspace = icSigCmykData;
+ devchan = 4;
+ isLab = 1;
+ isAdditive = 0;
+ } else if (strcmp(icg->t[0].kdata[ti],"CMY_XYZ") == 0) {
+ devspace = icSigCmyData;
+ devchan = 3;
+ isLab = 0;
+ isAdditive = 0;
+ } else if (strcmp(icg->t[0].kdata[ti],"CMY_LAB") == 0) {
+ devspace = icSigCmyData;
+ devchan = 3;
+ isLab = 1;
+ isAdditive = 0;
+ } else if (strcmp(icg->t[0].kdata[ti],"RGB_XYZ") == 0) {
+ devspace = icSigRgbData;
+ devchan = 3;
+ isLab = 0;
+ isAdditive = 1;
+ } else if (strcmp(icg->t[0].kdata[ti],"RGB_LAB") == 0) {
+ devspace = icSigRgbData;
+ devchan = 3;
+ isLab = 1;
+ isAdditive = 1;
+ } else if (strcmp(icg->t[0].kdata[ti],"iRGB_XYZ") == 0) {
+ devspace = icSigRgbData;
+ devchan = 3;
+ isLab = 0;
+ isAdditive = 1;
+ } else if (strcmp(icg->t[0].kdata[ti],"iRGB_LAB") == 0) {
+ devspace = icSigRgbData;
+ devchan = 3;
+ isLab = 1;
+ isAdditive = 1;
+ /* Scanner .ti3 files: */
+ } else if (strcmp(icg->t[0].kdata[ti],"XYZ_RGB") == 0) {
+ devspace = icSigRgbData;
+ devchan = 3;
+ isLab = 0;
+ isAdditive = 1;
+ } else if (strcmp(icg->t[0].kdata[ti],"LAB_RGB") == 0) {
+ devspace = icSigRgbData;
+ devchan = 3;
+ isLab = 1;
+ isAdditive = 1;
+#ifdef IMP_MONO
+ } else if (strcmp(icg->t[0].kdata[ti],"K_XYZ") == 0) {
+ devspace = icSigGrayData;
+ devchan = 1;
+ isLab = 0;
+ isAdditive = 0;
+ } else if (strcmp(icg->t[0].kdata[ti],"K_LAB") == 0) {
+ devspace = icSigGrayData;
+ devchan = 1;
+ isLab = 1;
+ isAdditive = 0;
+ } else if (strcmp(icg->t[0].kdata[ti],"W_XYZ") == 0) {
+ devspace = icSigGrayData;
+ devchan = 1;
+ isLab = 0;
+ isAdditive = 1;
+ } else if (strcmp(icg->t[0].kdata[ti],"W_LAB") == 0) {
+ devspace = icSigGrayData;
+ devchan = 1;
+ isLab = 1;
+ isAdditive = 1;
+#endif /* IMP_MONO */
+
+ } else
+ error("Device input file '%s' has unhandled color representation '%s'",
+ ti3name, icg->t[0].kdata[ti]);
+ }
+
+ if ((npat = icg->t[0].nsets) <= 0)
+ error("Input file '%s' has no sets of data",ti3name);
+
+ if (verb) {
+ fprintf(verbo,"No of test patches = %d\n",npat);
+ }
+
+ /* Allocate arrays to hold test patch input and output values */
+ if ((tpat = (pval *)malloc(sizeof(pval) * npat)) == NULL)
+ error("Malloc failed - tpat[]");
+
+ /* Read in the CGATs fields */
+ {
+ int sidx; /* Sample ID index */
+ int sloc; /* Sample location indexi (if any) */
+ int ti, ci, mi, yi, ki;
+ int Xi, Yi, Zi;
+
+ if ((sidx = icg->find_field(icg, 0, "SAMPLE_ID")) < 0)
+ error("Input file '%s' doesn't contain field SAMPLE_ID",ti3name);
+ if (icg->t[0].ftype[sidx] != nqcs_t)
+ error("Input file '%s' field SAMPLE_ID is wrong type",ti3name);
+
+ if ((sloc = icg->find_field(icg, 0, "SAMPLE_LOC")) >= 0) {
+ if (icg->t[0].ftype[sloc] != cs_t)
+ error("Input file '%s' field SAMPLE_LOC is wrong type",ti3name);
+ }
+
+ if (devspace == icSigGrayData) {
+ if (isAdditive) {
+ if ((ci = icg->find_field(icg, 0, "GRAY_W")) < 0)
+ error("Input file doesn't contain field GRAY_W");
+ if (icg->t[0].ftype[ci] != r_t)
+ error("Field GRAY_W is wrong type - corrupted file ?");
+ } else {
+ if ((ci = icg->find_field(icg, 0, "GRAY_K")) < 0)
+ error("Input file doesn't contain field GRAY_K");
+ if (icg->t[0].ftype[ci] != r_t)
+ error("Field GRAY_K is wrong type - corrupted file ?");
+ }
+ mi = yi = ki = ci;
+
+ } else if (devspace == icSigRgbData) {
+ if ((ci = icg->find_field(icg, 0, "RGB_R")) < 0)
+ error("Input file '%s' doesn't contain field RGB_R",ti3name);
+ if (icg->t[0].ftype[ci] != r_t)
+ error("Input file '%s' field RGB_R is wrong type",ti3name);
+ if ((mi = icg->find_field(icg, 0, "RGB_G")) < 0)
+ error("Input file '%s' doesn't contain field RGB_G",ti3name);
+ if (icg->t[0].ftype[mi] != r_t)
+ error("Input file '%s' field RGB_G is wrong type",ti3name);
+ if ((yi = icg->find_field(icg, 0, "RGB_B")) < 0)
+ error("Input file '%s' doesn't contain field RGB_B",ti3name);
+ if (icg->t[0].ftype[yi] != r_t)
+ error("Input file '%s' field RGB_B is wrong type",ti3name);
+ ki = yi;
+
+ } else if (devspace == icSigCmyData) {
+
+ if ((ci = icg->find_field(icg, 0, "CMY_C")) < 0)
+ error("Input file '%s' doesn't contain field CMY_C",ti3name);
+ if (icg->t[0].ftype[ci] != r_t)
+ error("Input file '%s' field CMY_C is wrong type",ti3name);
+ if ((mi = icg->find_field(icg, 0, "CMY_M")) < 0)
+ error("Input file '%s' doesn't contain field CMY_M",ti3name);
+ if (icg->t[0].ftype[mi] != r_t)
+ error("Input file '%s' field CMY_M is wrong type",ti3name);
+ if ((yi = icg->find_field(icg, 0, "CMY_Y")) < 0)
+ error("Input file '%s' doesn't contain field CMY_Y",ti3name);
+ ki = yi;
+ } else { /* Assume CMYK */
+
+ if ((ci = icg->find_field(icg, 0, "CMYK_C")) < 0)
+ error("Input file '%s' doesn't contain field CMYK_C",ti3name);
+ if (icg->t[0].ftype[ci] != r_t)
+ error("Input file '%s' field CMYK_C is wrong type",ti3name);
+ if ((mi = icg->find_field(icg, 0, "CMYK_M")) < 0)
+ error("Input file '%s' doesn't contain field CMYK_M",ti3name);
+ if (icg->t[0].ftype[mi] != r_t)
+ error("Input file '%s' field CMYK_M is wrong type",ti3name);
+ if ((yi = icg->find_field(icg, 0, "CMYK_Y")) < 0)
+ error("Input file '%s' doesn't contain field CMYK_Y",ti3name);
+ if (icg->t[0].ftype[yi] != r_t)
+ error("Input file '%s' field CMYK_Y is wrong type",ti3name);
+ if ((ki = icg->find_field(icg, 0, "CMYK_K")) < 0)
+ error("Input file '%s' doesn't contain field CMYK_K",ti3name);
+ if (icg->t[0].ftype[ki] != r_t)
+ error("Input file '%s' field CMYK_K is wrong type",ti3name);
+ }
+
+ if (spec == 0) { /* Using instrument tristimulous value */
+
+ if (isLab) { /* Expect Lab */
+ if ((Xi = icg->find_field(icg, 0, "LAB_L")) < 0)
+ error("Input file '%s' doesn't contain field LAB_L",ti3name);
+ if (icg->t[0].ftype[Xi] != r_t)
+ error("Input file '%s' field LAB_L is wrong type",ti3name);
+ if ((Yi = icg->find_field(icg, 0, "LAB_A")) < 0)
+ error("Input '%s' file doesn't contain field LAB_A",ti3name);
+ if (icg->t[0].ftype[Yi] != r_t)
+ error("Input file '%s' field LAB_A is wrong type",ti3name);
+ if ((Zi = icg->find_field(icg, 0, "LAB_B")) < 0)
+ error("Input file '%s' doesn't contain field LAB_B",ti3name);
+ if (icg->t[0].ftype[Zi] != r_t)
+ error("Input file '%s' field LAB_B is wrong type",ti3name);
+
+ } else { /* Expect XYZ */
+ if ((Xi = icg->find_field(icg, 0, "XYZ_X")) < 0)
+ error("Input file '%s' doesn't contain field XYZ_X",ti3name);
+ if (icg->t[0].ftype[Xi] != r_t)
+ error("Input file '%s' field XYZ_X is wrong type",ti3name);
+ if ((Yi = icg->find_field(icg, 0, "XYZ_Y")) < 0)
+ error("Input file '%s' doesn't contain field XYZ_Y",ti3name);
+ if (icg->t[0].ftype[Yi] != r_t)
+ error("Input file '%s' field XYZ_Y is wrong type",ti3name);
+ if ((Zi = icg->find_field(icg, 0, "XYZ_Z")) < 0)
+ error("Input file '%s' doesn't contain field XYZ_Z",ti3name);
+ if (icg->t[0].ftype[Zi] != r_t)
+ error("Input file '%s' field XYZ_Z is wrong type",ti3name);
+ }
+
+ for (i = 0; i < npat; i++) {
+ strcpy(tpat[i].sid, (char *)icg->t[0].fdata[i][sidx]);
+ if (sloc >= 0)
+ strcpy(tpat[i].slo, (char *)icg->t[0].fdata[i][sloc]);
+ else
+ strcpy(tpat[i].slo, "");
+ tpat[i].p[0] = *((double *)icg->t[0].fdata[i][ci]) / 100.0;
+ tpat[i].p[1] = *((double *)icg->t[0].fdata[i][mi]) / 100.0;
+ tpat[i].p[2] = *((double *)icg->t[0].fdata[i][yi]) / 100.0;
+ tpat[i].p[3] = *((double *)icg->t[0].fdata[i][ki]) / 100.0;
+ if (tpat[i].p[0] > 1.0
+ || tpat[i].p[1] > 1.0
+ || tpat[i].p[2] > 1.0
+ || tpat[i].p[3] > 1.0) {
+ error("Input file '%s' device value field value exceeds 100.0 !",ti3name);
+ }
+ tpat[i].v[0] = *((double *)icg->t[0].fdata[i][Xi]);
+ tpat[i].v[1] = *((double *)icg->t[0].fdata[i][Yi]);
+ tpat[i].v[2] = *((double *)icg->t[0].fdata[i][Zi]);
+ if (!isLab && (!isdisp || isdnormed != 0)) {
+ tpat[i].v[0] /= 100.0; /* Normalise XYZ to range 0.0 - 1.0 */
+ tpat[i].v[1] /= 100.0;
+ tpat[i].v[2] /= 100.0;
+ }
+ if (!isLab) { /* Convert test patch result XYZ to PCS (D50 Lab) */
+ icmXYZ2Lab(&icmD50, tpat[i].v, tpat[i].v);
+ }
+ }
+
+ } else { /* Using spectral data */
+ int ii;
+ xspect sp;
+ char buf[100];
+ int spi[XSPECT_MAX_BANDS]; /* CGATS indexes for each wavelength */
+ xsp2cie *sp2cie; /* Spectral conversion object */
+
+ if ((ii = icg->find_kword(icg, 0, "SPECTRAL_BANDS")) < 0)
+ error ("Input file '%s' doesn't contain keyword SPECTRAL_BANDS",ti3name);
+ sp.spec_n = atoi(icg->t[0].kdata[ii]);
+ if ((ii = icg->find_kword(icg, 0, "SPECTRAL_START_NM")) < 0)
+ error ("Input file '%s' doesn't contain keyword SPECTRAL_START_NM",ti3name);
+ sp.spec_wl_short = atof(icg->t[0].kdata[ii]);
+ if ((ii = icg->find_kword(icg, 0, "SPECTRAL_END_NM")) < 0)
+ error ("Input file '%s; doesn't contain keyword SPECTRAL_END_NM",ti3name);
+ sp.spec_wl_long = atof(icg->t[0].kdata[ii]);
+ if (!isdisp || isdnormed != 0)
+ sp.norm = 100.0;
+ else
+ sp.norm = 1.0;
+
+ /* Find the fields for spectral values */
+ for (j = 0; j < sp.spec_n; j++) {
+ int nm;
+
+ /* Compute nearest integer wavelength */
+ nm = (int)(sp.spec_wl_short + ((double)j/(sp.spec_n-1.0))
+ * (sp.spec_wl_long - sp.spec_wl_short) + 0.5);
+
+ sprintf(buf,"SPEC_%03d",nm);
+
+ if ((spi[j] = icg->find_field(icg, 0, buf)) < 0)
+ error("Input file '%s' doesn't contain field %s",ti3name,buf);
+ }
+
+ if (isdisp) {
+ illum = icxIT_none; /* Displays are assumed to be self luminous */
+ }
+
+ /* Create a spectral conversion object */
+ if ((sp2cie = new_xsp2cie(illum, illum == icxIT_none ? NULL : &cust_illum,
+ observ, NULL, icSigLabData, icxClamp)) == NULL)
+ error("Creation of spectral conversion object failed");
+
+ if (fwacomp) {
+ double nw = 0.0; /* Number of media white patches */
+ xspect mwsp; /* Medium spectrum */
+ instType itype; /* Spectral instrument type */
+ xspect insp; /* Instrument illuminant */
+
+ mwsp = sp; /* Struct copy */
+
+ if ((ti = icg->find_kword(icg, 0, "TARGET_INSTRUMENT")) < 0)
+ error ("Input file '%s' can't find target instrument needed for FWA compensation",ti3name);
+
+ if ((itype = inst_enum(icg->t[0].kdata[ti])) == instUnknown)
+ error ("Input file '%s' unrecognised target instrument '%s'",ti3name, icg->t[0].kdata[ti]);
+
+ if (inst_illuminant(&insp, itype) != 0)
+ error ("Instrument doesn't have an FWA illuminent");
+
+ /* Find the media white spectral reflectance */
+ for (j = 0; j < mwsp.spec_n; j++)
+ mwsp.spec[j] = 0.0;
+
+ /* Compute the mean of all the media white patches */
+ for (i = 0; i < npat; i++) {
+ int use = 0;
+
+ if (devspace == icSigGrayData) {
+ if (isAdditive) {
+ if (*((double *)icg->t[0].fdata[i][ci]) > (100.0 - 0.1))
+ use = 1;
+ } else {
+ if (*((double *)icg->t[0].fdata[i][ci]) < 0.1)
+ use = 1;
+ }
+ } else if (devspace == icSigRgbData) {
+ if (*((double *)icg->t[0].fdata[i][ci]) > (100.0 - 0.1)
+ && *((double *)icg->t[0].fdata[i][mi]) > (100.0 - 0.1)
+ && *((double *)icg->t[0].fdata[i][yi]) > (100.0 - 0.1))
+ use = 1;
+ } else if (devspace == icSigCmyData) {
+ if (*((double *)icg->t[0].fdata[i][ci]) < 0.1
+ && *((double *)icg->t[0].fdata[i][mi]) < 0.1
+ && *((double *)icg->t[0].fdata[i][yi]) < 0.1)
+ use = 1;
+ } else { /* Assume CMYK */
+
+ if (*((double *)icg->t[0].fdata[i][ci]) < 0.1
+ && *((double *)icg->t[0].fdata[i][mi]) < 0.1
+ && *((double *)icg->t[0].fdata[i][yi]) < 0.1
+ && *((double *)icg->t[0].fdata[i][ki]) < 0.1) {
+ use = 1;
+ }
+ }
+
+ if (use) {
+ /* Read the spectral values for this patch */
+ for (j = 0; j < mwsp.spec_n; j++) {
+ mwsp.spec[j] += *((double *)icg->t[0].fdata[i][spi[j]]);
+ }
+ nw++;
+ }
+ }
+ if (nw == 0.0) {
+ warning("Input file '%s' can't find a media white patch to init FWA",ti3name);
+
+ /* Track the maximum reflectance for any band to determine white. */
+ /* This might give bogus results if there is no white patch... */
+ for (i = 0; i < npat; i++) {
+ for (j = 0; j < mwsp.spec_n; j++) {
+ double rv = *((double *)icg->t[0].fdata[i][spi[j]]);
+ if (rv > mwsp.spec[j])
+ mwsp.spec[j] = rv;
+ }
+ }
+ nw++;
+ }
+
+ for (j = 0; j < mwsp.spec_n; j++)
+ mwsp.spec[j] /= nw; /* Compute average */
+
+ /* If we are setting a specific simulated instrument illuminant */
+ if (tillum != icxIT_none) {
+ tillump = &cust_tillum;
+ if (tillum != icxIT_custom) {
+ if (standardIlluminant(tillump, tillum, 0.0)) {
+ error("simulated inst. illum. not recognised");
+ }
+ }
+ }
+
+ if (sp2cie->set_fwa(sp2cie, &insp, tillump, &mwsp))
+ error ("Set FWA on sp2cie failed");
+
+ if (verb) {
+ double FWAc;
+ sp2cie->get_fwa_info(sp2cie, &FWAc);
+ fprintf(verbo,"FWA content = %f\n",FWAc);
+ }
+
+ }
+
+ for (i = 0; i < npat; i++) {
+ strcpy(tpat[i].sid, (char *)icg->t[0].fdata[i][sidx]);
+ if (sloc >= 0)
+ strcpy(tpat[i].slo, (char *)icg->t[0].fdata[i][sloc]);
+ else
+ strcpy(tpat[i].slo, "");
+ tpat[i].p[0] = *((double *)icg->t[0].fdata[i][ci]) / 100.0;
+ tpat[i].p[1] = *((double *)icg->t[0].fdata[i][mi]) / 100.0;
+ tpat[i].p[2] = *((double *)icg->t[0].fdata[i][yi]) / 100.0;
+ tpat[i].p[3] = *((double *)icg->t[0].fdata[i][ki]) / 100.0;
+ if (tpat[i].p[0] > 1.0
+ || tpat[i].p[1] > 1.0
+ || tpat[i].p[2] > 1.0
+ || tpat[i].p[3] > 1.0) {
+ error("Input file '%s' device value field value exceeds 100.0 !",ti3name);
+ }
+
+ /* Read the spectral values for this patch */
+ for (j = 0; j < sp.spec_n; j++) {
+ sp.spec[j] = *((double *)icg->t[0].fdata[i][spi[j]]);
+ }
+
+ /* Convert it to CIE space */
+ sp2cie->convert(sp2cie, tpat[i].v, &sp);
+ }
+
+ sp2cie->del(sp2cie); /* Done with this */
+ }
+ /* Normalize display values to Y = 1.0 if needed */
+ /* (re-norm spec derived, since observer may be different) */
+ if (isdisp && (isdnormed == 0 || spec != 0)) {
+ double scale = -1e6;
+ double bxyz[3];
+
+ /* Locate max Y */
+ for (i = 0; i < npat; i++) {
+ icmLab2XYZ(&icmD50, bxyz, tpat[i].v);
+ if (bxyz[1] > scale)
+ scale = bxyz[1];
+ }
+
+ scale = 1.0/scale;
+
+ /* Scale max Y to 1.0 */
+ for (i = 0; i < npat; i++) {
+ icmLab2XYZ(&icmD50, tpat[i].v, tpat[i].v);
+ tpat[i].v[0] *= scale;
+ tpat[i].v[1] *= scale;
+ tpat[i].v[2] *= scale;
+ icmXYZ2Lab(&icmD50, tpat[i].v, tpat[i].v);
+ }
+ }
+
+ icg->del(icg); /* Clean up */
+ } /* End of reading in CGATs file */
+
+ /* - - - - - - - - - - */
+ /* Check the forward profile accuracy against the data points */
+ {
+ double merr = 0.0; /* Max */
+ double aerr = 0.0; /* Avg */
+ double rerr = 0.0; /* RMS */
+ double nsamps = 0.0;
+ int inn, outn; /* Chanells for input and output spaces */
+
+ if (dovrml) {
+ wrl = start_vrml(out_name, doaxes);
+ start_line_set(wrl);
+ }
+
+ /* Open up the file for reading */
+ if ((rd_fp = new_icmFileStd_name(iccname,"r")) == NULL)
+ error("Write: Can't open file '%s'",iccname);
+
+ if ((rd_icco = new_icc()) == NULL)
+ error("Read: Creation of ICC object failed");
+
+ /* Read the header and tag list */
+ if ((rv = rd_icco->read(rd_icco,rd_fp,0)) != 0)
+ error("Read: %d, %s",rv,rd_icco->err);
+
+ /* Get the Fwd table, absolute with Lab override */
+ if ((luo = rd_icco->get_luobj(rd_icco, icmFwd, intent,
+ icSigLabData, icmLuOrdNorm)) == NULL) {
+ error("%d, %s",rd_icco->errc, rd_icco->err);
+ }
+
+ /* Get details of conversion (Arguments may be NULL if info not needed) */
+ luo->spaces(luo, NULL, &inn, NULL, &outn, NULL, NULL, NULL, NULL, NULL);
+
+ for (i = 0; i < npat; i++) {
+ double out[3];
+ double mxd;
+
+ /* Lookup the patch value in the profile */
+ if (luo->lookup(luo, out, tpat[i].p) > 1)
+ error("%d, %s",rd_icco->errc,rd_icco->err);
+
+ if (verb > 1) {
+ printf("[%f] %s%s%s: %s -> %f %f %f should be %f %f %f\n",
+ cie2k ? icmCIE2K(tpat[i].v, out) :
+ cie94 ? icmCIE94(tpat[i].v, out) : icmLabDE(tpat[i].v, out),
+ tpat[i].sid,
+ tpat[i].slo[0] != '\000' ? " @ " : "",
+ tpat[i].slo,
+ icmPdv(devchan, tpat[i].p),
+ out[0],out[1],out[2],
+ tpat[i].v[0],tpat[i].v[1],tpat[i].v[2]);
+ }
+ if (dovrml) {
+ if (dominl && icmLabDE(tpat[i].v, out) < 0.5) {
+ double cent[3], vec[3], vlen;
+ double p1[3], p2[3];
+
+ /* Compute center */
+ icmAdd3(cent, tpat[i].v, out);
+ icmScale3(cent, cent, 0.5);
+ if ((vlen = icmLabDE(tpat[i].v, out)) < 1e-6) {
+ vec[0] = 0.25; vec[1] = 0.0; vec[2] = 0.0;
+ } else {
+ icmSub3(vec, tpat[i].v, out);
+ icmScale3(vec, vec, 0.25/vlen);
+ }
+ icmSub3(p1, cent, vec);
+ icmAdd3(p2, cent, vec);
+ add_vertex(wrl, p1);
+ add_vertex(wrl, p2);
+ } else {
+ add_vertex(wrl, tpat[i].v);
+ add_vertex(wrl, out);
+ }
+ }
+
+ /* Check the result */
+ if (cie2k)
+ mxd = icmCIE2K(tpat[i].v, out);
+ else if (cie94)
+ mxd = icmCIE94(tpat[i].v, out);
+ else
+ mxd = icmLabDE(tpat[i].v, out);
+
+ aerr += mxd;
+ rerr += mxd * mxd;
+
+ nsamps++;
+ if (mxd > merr)
+ merr = mxd;
+
+ }
+ if (dovrml) {
+ if (dodecol)
+ make_de_lines(wrl);
+ else
+ make_lines(wrl, 2);
+ end_vrml(wrl);
+ }
+ printf("Profile check complete, errors%s: max. = %f, avg. = %f, RMS = %f\n",
+ cie2k ? "(CIEDE2000)" : cie94 ? " (CIE94)" : "", merr, aerr/nsamps, sqrt(rerr/nsamps));
+
+ /* ------------------------------- */
+ /* If we want sort by target value */
+ if (ddevv) {
+ double cieval[3];
+
+ /* Lookup the CIE value of the target */
+ if (luo->lookup(luo, cieval, devval) > 1)
+ error("%d, %s",rd_icco->errc,rd_icco->err);
+
+ /* Compute deltas to target value. */
+ for (i = 0; i < npat; i++) {
+ if (cie2k)
+ tpat[i].dv = icmCIE2K(tpat[i].v, cieval);
+ else if (cie94)
+ tpat[i].dv = icmCIE94(tpat[i].v, cieval);
+ else
+ tpat[i].dv = icmLabDE(tpat[i].v, cieval);
+
+ tpat[i].dp = 0.0;
+ for (j = 0; j < inn; j++) {
+ double tt;
+ tt = tpat[i].p[j] - devval[j];
+ tpat[i].dp += tt * tt;
+ }
+ tpat[i].dp = sqrt(tpat[i].dp);
+ }
+
+ if (sortbypcs) {
+ /* Sort by pcs delta */
+#define HEAP_COMPARE(A,B) (A.dv < B.dv)
+ HEAPSORT(pval, tpat, npat);
+#undef HEAP_COMPARE
+ } else {
+ /* Sort by device delta */
+#define HEAP_COMPARE(A,B) (A.dp < B.dp)
+ HEAPSORT(pval, tpat, npat);
+#undef HEAP_COMPARE
+ }
+
+ printf("Target point:\n");
+ if (devspace == icSigCmykData) {
+ printf("%f %f %f %f -> %f %f %f\n",
+ devval[0],devval[1],devval[2],devval[3],
+ cieval[0],cieval[1],cieval[2]);
+ } else { /* Assume RGB/CMY */
+ printf("%f %f %f -> %f %f %f\n",
+ devval[0],devval[1],devval[2],
+ cieval[0],cieval[1],cieval[2]);
+ }
+ printf("\n");
+
+ for (i = 0; i < npat; i++) {
+ if (devspace == icSigCmykData) {
+ printf("%s: %f %f %f %f [%f] -> %f %f %f [%f]\n",
+ tpat[i].sid,
+ tpat[i].p[0],tpat[i].p[1],tpat[i].p[2],tpat[i].p[3],
+ tpat[i].dp,
+ tpat[i].v[0],tpat[i].v[1],tpat[i].v[2],
+ tpat[i].dv);
+ } else { /* Assume RGB/CMY */
+ printf("%s: %f %f %f [%f] -> %f %f %f [%f]\n",
+ tpat[i].sid,
+ tpat[i].p[0],tpat[i].p[1],tpat[i].p[2],
+ tpat[i].dp,
+ tpat[i].v[0],tpat[i].v[1],tpat[i].v[2],
+ tpat[i].dv);
+ }
+ }
+ }
+
+ /* Done with lookup object */
+ luo->del(luo);
+
+ /* Close the file */
+ rd_icco->del(rd_icco);
+ rd_fp->del(rd_fp);
+ }
+
+ return 0;
+}
+
+
+/* ------------------------------------------------ */
+/* Some simple functions to do basix VRML work */
+/* !!! Should change to plot/vrml lib !!! */
+
+#define GAMUT_LCENT 50.0
+static int npoints = 0;
+static int paloc = 0;
+static struct { double pp[3]; } *pary;
+
+static void Lab2RGB(double *out, double *in);
+static void DE2RGB(double *out, double in);
+
+FILE *start_vrml(char *name, int doaxes) {
+ FILE *wrl;
+
+ /* Define the axis boxes */
+ struct {
+ double x, y, z; /* Box center */
+ double wx, wy, wz; /* Box size */
+ double r, g, b; /* Box color */
+ } axes[5] = {
+ { 0, 0, 50-GAMUT_LCENT, 2, 2, 100, .7, .7, .7 }, /* L axis */
+ { 50, 0, 0-GAMUT_LCENT, 100, 2, 2, 1, 0, 0 }, /* +a (red) axis */
+ { 0, -50, 0-GAMUT_LCENT, 2, 100, 2, 0, 0, 1 }, /* -b (blue) axis */
+ { -50, 0, 0-GAMUT_LCENT, 100, 2, 2, 0, 1, 0 }, /* -a (green) axis */
+ { 0, 50, 0-GAMUT_LCENT, 2, 100, 2, 1, 1, 0 }, /* +b (yellow) axis */
+ };
+
+ /* Define the labels */
+ struct {
+ double x, y, z;
+ double size;
+ char *string;
+ double r, g, b;
+ } labels[6] = {
+ { -2, 2, -GAMUT_LCENT + 100 + 10, 10, "+L*", .7, .7, .7 }, /* Top of L axis */
+ { -2, 2, -GAMUT_LCENT - 10, 10, "0", .7, .7, .7 }, /* Bottom of L axis */
+ { 100 + 5, -3, 0-GAMUT_LCENT, 10, "+a*", 1, 0, 0 }, /* +a (red) axis */
+ { -5, -100 - 10, 0-GAMUT_LCENT, 10, "-b*", 0, 0, 1 }, /* -b (blue) axis */
+ { -100 - 15, -3, 0-GAMUT_LCENT, 10, "-a*", 0, 0, 1 }, /* -a (green) axis */
+ { -5, 100 + 5, 0-GAMUT_LCENT, 10, "+b*", 1, 1, 0 }, /* +b (yellow) axis */
+ };
+
+ if ((wrl = fopen(name,"w")) == NULL)
+ error("Error opening VRML file '%s'\n",name);
+
+ npoints = 0;
+
+ fprintf(wrl,"#VRML V2.0 utf8\n");
+ fprintf(wrl,"\n");
+ fprintf(wrl,"# Created by the Argyll CMS\n");
+ fprintf(wrl,"Transform {\n");
+ fprintf(wrl,"children [\n");
+ fprintf(wrl," NavigationInfo {\n");
+ fprintf(wrl," type \"EXAMINE\" # It's an object we examine\n");
+ fprintf(wrl," } # We'll add our own light\n");
+ fprintf(wrl,"\n");
+ fprintf(wrl," DirectionalLight {\n");
+ fprintf(wrl," direction 0 0 -1 # Light illuminating the scene\n");
+ fprintf(wrl," direction 0 -1 0 # Light illuminating the scene\n");
+ fprintf(wrl," }\n");
+ fprintf(wrl,"\n");
+ fprintf(wrl," Viewpoint {\n");
+ fprintf(wrl," position 0 0 340 # Position we view from\n");
+ fprintf(wrl," }\n");
+ fprintf(wrl,"\n");
+ if (doaxes != 0) {
+ int n;
+ fprintf(wrl," # Lab axes as boxes:\n");
+ for (n = 0; n < 5; n++) {
+ fprintf(wrl," Transform { translation %f %f %f\n", axes[n].x, axes[n].y, axes[n].z);
+ fprintf(wrl," children [\n");
+ fprintf(wrl," Shape{\n");
+ fprintf(wrl," geometry Box { size %f %f %f }\n",
+ axes[n].wx, axes[n].wy, axes[n].wz);
+ fprintf(wrl," appearance Appearance { material Material ");
+ fprintf(wrl,"{ diffuseColor %f %f %f} }\n", axes[n].r, axes[n].g, axes[n].b);
+ fprintf(wrl," }\n");
+ fprintf(wrl," ]\n");
+ fprintf(wrl," }\n");
+ }
+ fprintf(wrl," # Axes identification:\n");
+ for (n = 0; n < 6; n++) {
+ fprintf(wrl," Transform { translation %f %f %f\n", labels[n].x, labels[n].y, labels[n].z);
+ fprintf(wrl," children [\n");
+ fprintf(wrl," Shape{\n");
+ fprintf(wrl," geometry Text { string [\"%s\"]\n",labels[n].string);
+ fprintf(wrl," fontStyle FontStyle { family \"SANS\" style \"BOLD\" size %f }\n",
+ labels[n].size);
+ fprintf(wrl," }\n");
+ fprintf(wrl," appearance Appearance { material Material ");
+ fprintf(wrl,"{ diffuseColor %f %f %f} }\n", labels[n].r, labels[n].g, labels[n].b);
+ fprintf(wrl," }\n");
+ fprintf(wrl," ]\n");
+ fprintf(wrl," }\n");
+ }
+ fprintf(wrl,"\n");
+ }
+
+ return wrl;
+}
+
+void
+start_line_set(FILE *wrl) {
+
+ fprintf(wrl,"\n");
+ fprintf(wrl,"Shape {\n");
+ fprintf(wrl," geometry IndexedLineSet { \n");
+ fprintf(wrl," coord Coordinate { \n");
+ fprintf(wrl," point [\n");
+}
+
+void add_vertex(FILE *wrl, double pp[3]) {
+
+ fprintf(wrl,"%f %f %f,\n",pp[1], pp[2], pp[0]-GAMUT_LCENT);
+
+ if (paloc < (npoints+1)) {
+ paloc = (paloc + 10) * 2;
+ if (pary == NULL)
+ pary = malloc(paloc * 3 * sizeof(double));
+ else
+ pary = realloc(pary, paloc * 3 * sizeof(double));
+
+ if (pary == NULL)
+ error ("Malloc failed");
+ }
+ pary[npoints].pp[0] = pp[0];
+ pary[npoints].pp[1] = pp[1];
+ pary[npoints].pp[2] = pp[2];
+ npoints++;
+}
+
+
+void make_lines(FILE *wrl, int ppset) {
+ int i, j;
+
+ fprintf(wrl," ]\n");
+ fprintf(wrl," }\n");
+ fprintf(wrl," coordIndex [\n");
+
+ for (i = 0; i < npoints;) {
+ for (j = 0; j < ppset; j++, i++) {
+ fprintf(wrl,"%d, ", i);
+ }
+ fprintf(wrl,"-1,\n");
+ }
+ fprintf(wrl," ]\n");
+
+ /* Color */
+ fprintf(wrl," colorPerVertex TRUE\n");
+ fprintf(wrl," color Color {\n");
+ fprintf(wrl," color [ # RGB colors of each vertex\n");
+
+ for (i = 0; i < npoints; i++) {
+ double rgb[3], Lab[3];
+ Lab[0] = pary[i].pp[0];
+ Lab[1] = pary[i].pp[1];
+ Lab[2] = pary[i].pp[2];
+ Lab2RGB(rgb, Lab);
+ fprintf(wrl," %f %f %f,\n", rgb[0], rgb[1], rgb[2]);
+ }
+ fprintf(wrl," ] \n");
+ fprintf(wrl," }\n");
+ /* End color */
+
+ fprintf(wrl," }\n");
+ fprintf(wrl,"} # end shape\n");
+}
+
+/* Assume 2 ppset, and make line color prop to length */
+void make_de_lines(FILE *wrl) {
+ int i, j;
+
+ fprintf(wrl," ]\n");
+ fprintf(wrl," }\n");
+ fprintf(wrl," coordIndex [\n");
+
+ for (i = 0; i < npoints;) {
+ for (j = 0; j < 2; j++, i++) {
+ fprintf(wrl,"%d, ", i);
+ }
+ fprintf(wrl,"-1,\n");
+ }
+ fprintf(wrl," ]\n");
+
+ /* Color */
+ fprintf(wrl," colorPerVertex TRUE\n");
+ fprintf(wrl," color Color {\n");
+ fprintf(wrl," color [ # RGB colors of each vertex\n");
+
+ for (i = 0; i < npoints; i++) {
+ double rgb[3], ss;
+ for (ss = 0.0, j = 0; j < 3; j++) {
+ double tt = (pary[i & ~1].pp[j] - pary[i | 1].pp[j]);
+ ss += tt * tt;
+ }
+ ss = sqrt(ss);
+ DE2RGB(rgb, ss);
+ fprintf(wrl," %f %f %f,\n", rgb[0], rgb[1], rgb[2]);
+ }
+ fprintf(wrl," ] \n");
+ fprintf(wrl," }\n");
+ /* End color */
+
+ fprintf(wrl," }\n");
+ fprintf(wrl,"} # end shape\n");
+}
+
+void end_vrml(FILE *wrl) {
+
+ fprintf(wrl,"\n");
+ fprintf(wrl," ] # end of children for world\n");
+ fprintf(wrl,"}\n");
+
+ if (fclose(wrl) != 0)
+ error("Error closing VRML file\n");
+}
+
+
+/* Convert a gamut Lab value to an RGB value for display purposes */
+static void
+Lab2RGB(double *out, double *in) {
+ double L = in[0], a = in[1], b = in[2];
+ double x,y,z,fx,fy,fz;
+ double R, G, B;
+
+ /* Scale so that black is visible */
+ L = L * (100 - 40.0)/100.0 + 40.0;
+
+ /* First convert to XYZ using D50 white point */
+ if (L > 8.0) {
+ fy = (L + 16.0)/116.0;
+ y = pow(fy,3.0);
+ } else {
+ y = L/903.2963058;
+ fy = 7.787036979 * y + 16.0/116.0;
+ }
+
+ fx = a/500.0 + fy;
+ if (fx > 24.0/116.0)
+ x = pow(fx,3.0);
+ else
+ x = (fx - 16.0/116.0)/7.787036979;
+
+ fz = fy - b/200.0;
+ if (fz > 24.0/116.0)
+ z = pow(fz,3.0);
+ else
+ z = (fz - 16.0/116.0)/7.787036979;
+
+ x *= 0.9642; /* Multiply by white point, D50 */
+ y *= 1.0;
+ z *= 0.8249;
+
+ /* Now convert to sRGB values */
+ R = x * 3.2410 + y * -1.5374 + z * -0.4986;
+ G = x * -0.9692 + y * 1.8760 + z * 0.0416;
+ B = x * 0.0556 + y * -0.2040 + z * 1.0570;
+
+ if (R < 0.0)
+ R = 0.0;
+ else if (R > 1.0)
+ R = 1.0;
+
+ if (G < 0.0)
+ G = 0.0;
+ else if (G > 1.0)
+ G = 1.0;
+
+ if (B < 0.0)
+ B = 0.0;
+ else if (B > 1.0)
+ B = 1.0;
+
+ R = pow(R, 1.0/2.2);
+ G = pow(G, 1.0/2.2);
+ B = pow(B, 1.0/2.2);
+
+ out[0] = R;
+ out[1] = G;
+ out[2] = B;
+}
+
+/* Convert a delta E value into a signal color: */
+static void
+DE2RGB(double *out, double in) {
+ struct {
+ double de;
+ double r, g, b;
+ } range[6] = {
+ { 10.0, 1, 1, 0 }, /* yellow */
+ { 4.0, 1, 0, 0 }, /* red */
+ { 2.0, 1, 0, 1 }, /* magenta */
+ { 1.0, 0, 0, 1 }, /* blue */
+ { 0.5, 0, 1, 1 }, /* cyan */
+ { 0.0, 0, 1, 0 } /* green */
+ };
+ int i;
+ double bl;
+
+ /* Locate the range we're in */
+ if (in > range[0].de) {
+ out[0] = range[0].r;
+ out[1] = range[0].g;
+ out[2] = range[0].b;
+ } else {
+ for (i = 0; i < 5; i++) {
+ if (in <= range[i].de && in >= range[i+1].de)
+ break;
+ }
+ bl = (in - range[i+1].de)/(range[i].de - range[i+1].de);
+ out[0] = bl * range[i].r + (1.0 - bl) * range[i+1].r;
+ out[1] = bl * range[i].g + (1.0 - bl) * range[i+1].g;
+ out[2] = bl * range[i].b + (1.0 - bl) * range[i+1].b;
+ }
+}
+
+
+
diff --git a/profile/profin.c b/profile/profin.c
new file mode 100644
index 0000000..2f74ec2
--- /dev/null
+++ b/profile/profin.c
@@ -0,0 +1,1310 @@
+
+/*
+ * Argyll Color Correction System
+ * Input device profile creator.
+ *
+ * Author: Graeme W. Gill
+ * Date: 11/10/00
+ *
+ * Copyright 2000 - 2011 Graeme W. Gill
+ * All rights reserved.
+ *
+ * This material is licenced under the GNU AFFERO GENERAL PUBLIC LICENSE Version 3 :-
+ * see the License.txt file for licencing details.
+ */
+
+/*
+ * This program takes in the scattered test chart
+ * points, and interpolates them into a gridded
+ * forward ICC device profile.
+ * It also creates (at the moment) a limited backward
+ * profile based on the forward grid.
+ *
+ */
+
+/*
+ * TTBD:
+ * Need to make this more of a library:
+ * ** By default limit matrix primaries to have +ve XYZ
+ * Add flag to override this.
+ * Fix error handling
+ * fix verbose output
+ * hand icc object back rather than writing file ?
+ */
+
+#undef DEBUG
+
+#define verbo stdout
+
+#include <stdio.h>
+#include "counters.h"
+#include "numlib.h"
+#include "icc.h"
+#include "cgats.h"
+#include "xicc.h"
+#include "rspl.h"
+#include "prof.h"
+
+#define DOB2A /* Create B2A table as well (not implemented) */
+#define NO_B2A_PCS_CURVES /* PCS curves seem to make B2A less accurate. Why ? */
+#define USE_CAM_CLIP_OPT /* Clip out of gamut in CAM space rather than XYZ or L*a*b* */
+#undef USE_EXTRA_FITTING /* Turn on data point error compensation */
+#define USE_2ASS_SMOOTHING /* Turn on Gaussian smoothing */
+#undef WARN_CLUT_CLIPPING /* Print warning if setting clut clips */
+#define EXTRAP_MAXPNTS 10 /* Maximum number of extra extrapolated points per direction */
+#define EXTRAP_WEIGHT 1.0 /* Extra extrapolated point weighting */
+
+/*
+ Basic algorithm outline:
+
+ Scanner:
+
+ Figure out the input curves to give
+ the flattest grid.
+
+ Figure out the grid values.
+
+ Use them to generate the A2B table.
+
+ Do all the calculations in Lab space,
+ but represent the profile in XYZ space, so that
+ the white/black point normalisation doesn't cause
+ the clut values to be clipped.
+
+ This leads to a poorer accuracy as an XYZ profile,
+ but can then be compensated for using the ICX_MERGE_CLUT flag
+ together with a PCS override.
+
+ Note we're hard coded as RGB device space, so we're not coping
+ with grey scale or CMY.
+*/
+
+#ifdef DEBUG
+#undef DBG
+#define DBG(xxx) printf xxx ;
+#else
+#undef DBG
+#define DBG(xxx)
+#endif
+
+/* ---------------------------------------- */
+#ifdef DOB2A
+
+/* structure to support output icc B2A Lut initialisation calbacks */
+/* Note that we don't cope with a LUT matrix - assume it's unity. */
+
+typedef struct {
+ int verb;
+ int total, count, last; /* Progress count information */
+ int noPCScurves; /* Flag set if we don't want PCS curves */
+ icColorSpaceSignature pcsspace; /* The PCS colorspace */
+ icColorSpaceSignature devspace; /* The device colorspace */
+ icxLuLut *x; /* A2B icxLuLut we are inverting in std PCS */
+
+ double swxyz[3]; /* Source white point in XYZ */
+
+ int wantLab; /* 0 if want is XYZ PCS, 1 want is Lab PCS */
+} in_b2a_callback;
+
+
+/* --------------------------------------------------------- */
+
+/* Extra non-linearity applied to BtoA XYZ PCS */
+/* This distributes the LUT indexes more evenly in */
+/* perceptual space, greatly improving the B2A accuracy of XYZ LUT */
+static void xyzcurve(double *out, double *in) {
+ int i;
+ double sc = 65535.0/32768.0;
+
+ /* Use an L* like curve, scaled to the maximum XYZ valu */
+ out[0] = in[0]/sc;
+ out[1] = in[1]/sc;
+ out[2] = in[2]/sc;
+ for (i = 0; i < 3; i++) {
+ if (out[i] > 0.08)
+ out[i] = pow((out[i] + 0.16)/1.16, 3.0);
+ else
+ out[i] = out[i]/9.032962896;
+ }
+ out[0] = out[0] * sc;
+ out[1] = out[1] * sc;
+ out[2] = out[2] * sc;
+}
+
+static void invxyzcurve(double *out, double *in) {
+ int i;
+ double sc = 65535.0/32768.0;
+
+ out[0] = in[0]/sc;
+ out[1] = in[1]/sc;
+ out[2] = in[2]/sc;
+ for (i = 0; i < 3; i++) {
+ if (out[i] > 0.008856451586)
+ out[i] = 1.16 * pow(out[i],1.0/3.0) - 0.16;
+ else
+ out[i] = 9.032962896 * out[i];
+ }
+ out[0] = out[0] * sc;
+ out[1] = out[1] * sc;
+ out[2] = out[2] * sc;
+}
+
+/* --------------------------------------------------------- */
+/* NOTE :- the assumption that each stage of the BtoA is a mirror */
+/* of the AtoB makes for inflexibility. */
+/* Perhaps it would be better to remove this asumption from the */
+/* in_b2a_clut processing ? */
+/* To do this we then need inv_in_b2a_input(), and */
+/* inv_in_b2a_output(), and we need to clearly distinguish between */
+/* AtoB PCS' & DEV', and BtoA PCS' & DEV', since they are not */
+/* necessarily the same... */
+
+
+/* B2A Input table is the inverse of the AtoB output table */
+/* Input PCS output PCS'' */
+void in_b2a_input(void *cntx, double out[3], double in[3]) {
+ in_b2a_callback *p = (in_b2a_callback *)cntx;
+
+ DBG(("out_b2a_input got PCS %f %f %f\n",in[0],in[1],in[2]))
+
+ /* PCS to PCS' */
+ if (p->noPCScurves) {
+ out[0] = in[0];
+ out[1] = in[1];
+ out[2] = in[2];
+ } else {
+ if (p->x->inv_output(p->x, out, in) > 1)
+ error("%d, %s",p->x->pp->errc,p->x->pp->err);
+ }
+ /* PCS' to PCS'' */
+ if (p->pcsspace == icSigXYZData) /* Apply XYZ non-linearity curve */
+ invxyzcurve(out, out);
+
+ DBG(("in_b2a_input returning PCS'' %f %f %f\n",out[0],out[1],out[2]))
+}
+
+/* clut - multitable */
+/* Input PCS' output Dev' */
+/* We're applying any abstract profile after gamut mapping, */
+/* on the assumption is is primarily being used to "correct" the */
+/* output device. Ideally the gamut mapping should take the change */
+/* the abstract profile has on the output device into account, but */
+/* currently we're not doing this.. */
+void in_b2a_clut(void *cntx, double *out, double in[3]) {
+ in_b2a_callback *p = (in_b2a_callback *)cntx;
+ double in1[3];
+
+ in1[0] = in[0]; /* in[] may be aliased with out[] */
+ in1[1] = in[1]; /* so take a copy. */
+ in1[2] = in[2];
+
+ DBG(("in_b2a_clut got PCS' %f %f %f\n",in[0],in[1],in[2]))
+
+ if (p->pcsspace == icSigXYZData) /* Undo effects of extra XYZ non-linearity curve */
+ xyzcurve(in1, in1);
+
+ if (p->noPCScurves) { /* We were given PCS or have converted to PCS */
+
+ /* PCS to PCS' */
+ if (p->x->inv_output(p->x, in1, in1) > 1)
+ error("%d, %s",p->x->pp->errc,p->x->pp->err);
+
+ DBG(("convert to PCS' got %f %f %f\n",in1[0],in1[1],in1[2]))
+ }
+
+ /* Invert AtoB clut (PCS' to Dev') Colorimetric */
+ /* to producte the colorimetric tables output. */
+ if (p->x->inv_clut(p->x, out, in1) > 1)
+ error("%d, %s",p->x->pp->errc,p->x->pp->err);
+
+ DBG(("convert PCS' to DEV' got %f %f %f %f\n",out[0],out[1],out[2],out[3]))
+ DBG(("in_b2a_clut returning DEV' %f %f %f\n",out[0],out[1],out[2]))
+
+ if (p->verb) { /* Output percent intervals */
+ int pc;
+ p->count++;
+ pc = (int)(p->count * 100.0/p->total + 0.5);
+ if (pc != p->last) {
+ printf("%c%2d%%",cr_char,pc); fflush(stdout);
+ p->last = pc;
+ }
+ }
+}
+
+/* Output table is the inverse of the AtoB input table */
+/* Input Dev' output Dev */
+void in_b2a_output(void *cntx, double out[4], double in[4]) {
+ in_b2a_callback *p = (in_b2a_callback *)cntx;
+
+ DBG(("in_b2a_output got DEV' %f %f %f\n",in[0],in[1],in[2]))
+
+ if (p->x->inv_input(p->x, out, in) > 1)
+ error("%d, %s",p->x->pp->errc,p->x->pp->err);
+
+ DBG(("in_b2a_output returning DEV %f %f %f\n",out[0],out[1],out[2]))
+}
+
+#endif /* DOB2A */
+/* ---------------------------------------- */
+
+/* Make an input device profile, where we create an A2B lut */
+/* directly from the scattered input data. */
+void
+make_input_icc(
+ prof_atype ptype, /* Profile algorithm type */
+ icmICCVersion iccver, /* ICC profile version to create */
+ int verb,
+ int iquality, /* A2B table quality, 0..3 */
+ int oquality, /* B2A table quality, 0..3 */
+ int noisluts, /* nz to supress creation of input (Device) shaper luts */
+ int noipluts, /* nz to supress creation of input (Device) position luts */
+ int nooluts, /* nz to supress creation of output (PCS) shaper luts */
+ int nocied, /* nz to supress inclusion of .ti3 data in profile */
+ int verify,
+ int autowpsc, /* nz for Auto scale the WP to prevent clipping above WP patch */
+ int clipovwp, /* nz for Clip cLUT values above WP */
+ double wpscale, /* >= 0.0 for media white point scale factor */
+ int dob2a, /* nz to create a B2A table as well */
+ int extrap, /* nz to create extra cLUT interpolation points */
+ int clipprims, /* Clip white, black and primaries */
+ char *in_name, /* input .ti3 file name */
+ char *file_name, /* output icc name */
+ cgats *icg, /* input cgats structure */
+ int spec, /* Use spectral data flag */
+ icxIllumeType illum, /* Spectral illuminant */
+ xspect *cust_illum, /* Possible custom illumination */
+ icxObserverType observ, /* Spectral observer */
+ double smooth, /* RSPL smoothing factor, -ve if raw */
+ double avgdev, /* reading Average Deviation as a proportion of the input range */
+ profxinf *xpi /* Optional Profile creation extra data */
+) {
+ icmFile *wr_fp;
+ icc *wr_icco;
+ int npat; /* Number of patches */
+ int npxpat = 0; /* Number of possible extrap extrapolation patches */
+ int nxpat = 0; /* Number of extrap extrapolation patches */
+ cow *tpat; /* Patch input values */
+ int i, rv = 0;
+ int isLab = 0; /* 0 if input is XYZ, 1 if input is Lab */
+ int wantLab = 0; /* 0 if want is XYZ, 1 want is Lab. */
+ /* Values will be wantLab after reading */
+ int isLut = 0; /* 0 if shaper+ matrix, 1 if lut type */
+ int isShTRC = 0; /* 0 if separate gamma/shaper TRC, 1 if shared */
+
+ if (ptype == prof_clutLab) { /* Lab lut */
+ wantLab = 1;
+ isLut = 1;
+ } else if (ptype == prof_clutXYZ) { /* XYZ lut */
+ wantLab = 0;
+ isLut = 1;
+ } else {
+ wantLab = 0; /* gamma/shaper + matrix profile must be XYZ */
+ isLut = 0;
+ extrap = 0;
+
+ if (ptype == prof_gam1mat
+ || ptype == prof_sha1mat
+ || ptype == prof_matonly) {
+ isShTRC = 1; /* Single curve */
+ }
+ }
+
+ /* Open up the file for writing */
+ if ((wr_fp = new_icmFileStd_name(file_name,"w")) == NULL)
+ error ("Write: Can't open file '%s'",file_name);
+
+ if ((wr_icco = new_icc()) == NULL)
+ error ("Write: Creation of ICC object failed");
+
+ /* Add all the tags required */
+
+ /* The header: */
+ {
+ icmHeader *wh = wr_icco->header;
+
+ /* Values that must be set before writing */
+ wh->deviceClass = icSigInputClass;
+ wh->colorSpace = icSigRgbData; /* It's an RGB profile */
+ if (wantLab)
+ wh->pcs = icSigLabData;
+ else
+ wh->pcs = icSigXYZData;
+
+ if (xpi->default_ri != icMaxEnumIntent)
+ wh->renderingIntent = xpi->default_ri;
+ else
+ wh->renderingIntent = icRelativeColorimetric;
+
+ /* Values that should be set before writing */
+ if (xpi != NULL && xpi->manufacturer != 0L)
+ wh->manufacturer = xpi->manufacturer;
+ else
+ wh->manufacturer = icmSigUnknownType;
+
+ if (xpi != NULL && xpi->model != 0L)
+ wh->model = xpi->model;
+ else
+ wh->model = icmSigUnknownType;
+
+ /* Values that may be set before writing */
+ if (xpi != NULL && xpi->creator != 0L)
+ wh->creator = xpi->creator;
+#ifdef NT
+ wh->platform = icSigMicrosoft;
+#endif
+#ifdef __APPLE__
+ wh->platform = icSigMacintosh;
+#endif
+#if defined(UNIX) && !defined(__APPLE__)
+ wh->platform = icmSig_nix;
+#endif
+
+ if (xpi != NULL && xpi->transparency)
+ wh->attributes.l |= icTransparency;
+ if (xpi != NULL && xpi->matte)
+ wh->attributes.l |= icMatte;
+ if (xpi != NULL && xpi->negative)
+ wh->attributes.l |= icNegative;
+ if (xpi != NULL && xpi->blackandwhite)
+ wh->attributes.l |= icBlackAndWhite;
+ }
+ /* Profile Description Tag: */
+ {
+ icmTextDescription *wo;
+ char *dst; /* description */
+
+ if (xpi != NULL && xpi->profDesc != NULL)
+ dst = xpi->profDesc;
+ else {
+ dst = "This is a Lut style RGB - XYZ Input Profile";
+ }
+
+ if ((wo = (icmTextDescription *)wr_icco->add_tag(
+ wr_icco, icSigProfileDescriptionTag, icSigTextDescriptionType)) == NULL)
+ error("add_tag failed: %d, %s",wr_icco->errc,wr_icco->err);
+
+ wo->size = strlen(dst)+1; /* Allocated and used size of desc, inc null */
+ wo->allocate((icmBase *)wo);/* Allocate space */
+ strcpy(wo->desc, dst); /* Copy the string in */
+ }
+ /* Copyright Tag: */
+ {
+ icmText *wo;
+ char *crt;
+
+ if (xpi != NULL && xpi->copyright != NULL)
+ crt = xpi->copyright;
+ else
+ crt = "Copyright, the creator of this profile";
+
+ if ((wo = (icmText *)wr_icco->add_tag(
+ wr_icco, icSigCopyrightTag, icSigTextType)) == NULL)
+ error("add_tag failed: %d, %s",wr_icco->errc,wr_icco->err);
+
+ wo->size = strlen(crt)+1; /* Allocated and used size of text, inc null */
+ wo->allocate((icmBase *)wo);/* Allocate space */
+ strcpy(wo->data, crt); /* Copy the text in */
+ }
+ /* Device Manufacturers Description Tag: */
+ if (xpi != NULL && xpi->deviceMfgDesc != NULL) {
+ icmTextDescription *wo;
+ char *dst = xpi->deviceMfgDesc;
+
+ if ((wo = (icmTextDescription *)wr_icco->add_tag(
+ wr_icco, icSigDeviceMfgDescTag, icSigTextDescriptionType)) == NULL)
+ error("add_tag failed: %d, %s",wr_icco->errc,wr_icco->err);
+
+ wo->size = strlen(dst)+1; /* Allocated and used size of desc, inc null */
+ wo->allocate((icmBase *)wo);/* Allocate space */
+ strcpy(wo->desc, dst); /* Copy the string in */
+ }
+ /* Model Description Tag: */
+ if (xpi != NULL && xpi->modelDesc != NULL) {
+ icmTextDescription *wo;
+ char *dst = xpi->modelDesc;
+
+ if ((wo = (icmTextDescription *)wr_icco->add_tag(
+ wr_icco, icSigDeviceModelDescTag, icSigTextDescriptionType)) == NULL)
+ error("add_tag failed: %d, %s",wr_icco->errc,wr_icco->err);
+
+ wo->size = strlen(dst)+1; /* Allocated and used size of desc, inc null */
+ wo->allocate((icmBase *)wo);/* Allocate space */
+ strcpy(wo->desc, dst); /* Copy the string in */
+ }
+ /* White Point Tag: */
+ {
+ icmXYZArray *wo;
+ /* Note that tag types icSigXYZType and icSigXYZArrayType are identical */
+ if ((wo = (icmXYZArray *)wr_icco->add_tag(
+ wr_icco, icSigMediaWhitePointTag, icSigXYZArrayType)) == NULL)
+ error("add_tag failed: %d, %s",wr_icco->errc,wr_icco->err);
+
+ wo->size = 1;
+ wo->allocate((icmBase *)wo); /* Allocate space */
+ wo->data[0].X = 0.9642; /* Set a default value - D50 */
+ wo->data[0].Y = 1.0000;
+ wo->data[0].Z = 0.8249;
+ }
+ /* Black Point Tag: */
+ {
+ icmXYZArray *wo;
+ if ((wo = (icmXYZArray *)wr_icco->add_tag(
+ wr_icco, icSigMediaBlackPointTag, icSigXYZArrayType)) == NULL)
+ error("add_tag failed: %d, %s",wr_icco->errc,wr_icco->err);
+
+ wo->size = 1;
+ wo->allocate((icmBase *)wo); /* Allocate space */
+ wo->data[0].X = 0.00; /* Set default perfect black */
+ wo->data[0].Y = 0.00;
+ wo->data[0].Z = 0.00;
+ }
+
+ if (isLut == 0) { /* shaper + matrix type */
+
+ /* Red, Green and Blue Colorant Tags: */
+ {
+ icmXYZArray *wor, *wog, *wob;
+ if ((wor = (icmXYZArray *)wr_icco->add_tag(
+ wr_icco, icSigRedColorantTag, icSigXYZArrayType)) == NULL)
+ error("add_tag failed: %d, %s",rv,wr_icco->err);
+ if ((wog = (icmXYZArray *)wr_icco->add_tag(
+ wr_icco, icSigGreenColorantTag, icSigXYZArrayType)) == NULL)
+ error("add_tag failed: %d, %s",rv,wr_icco->err);
+ if ((wob = (icmXYZArray *)wr_icco->add_tag(
+ wr_icco, icSigBlueColorantTag, icSigXYZArrayType)) == NULL)
+ error("add_tag failed: %d, %s",rv,wr_icco->err);
+
+ wor->size = wog->size = wob->size = 1;
+ wor->allocate((icmBase *)wor); /* Allocate space */
+ wog->allocate((icmBase *)wog);
+ wob->allocate((icmBase *)wob);
+
+ /* Setup some sane dummy values */
+ /* icxMatrix will override these later */
+ wor->data[0].X = 1.0; wor->data[0].Y = 0.0; wor->data[0].Z = 0.0;
+ wog->data[0].X = 0.0; wog->data[0].Y = 1.0; wog->data[0].Z = 0.0;
+ wob->data[0].X = 0.0; wob->data[0].Y = 0.0; wob->data[0].Z = 1.0;
+ }
+
+ /* Red, Green and Blue Tone Reproduction Curve Tags: */
+ {
+ icmCurve *wor, *wog, *wob;
+ if ((wor = (icmCurve *)wr_icco->add_tag(
+ wr_icco, icSigRedTRCTag, icSigCurveType)) == NULL)
+ error("add_tag failed: %d, %s",rv,wr_icco->err);
+
+ if (isShTRC) { /* Make all TRCs shared */
+ if ((wog = (icmCurve *)wr_icco->link_tag(
+ wr_icco, icSigGreenTRCTag, icSigRedTRCTag)) == NULL)
+ error("link_tag failed: %d, %s",rv,wr_icco->err);
+ if ((wob = (icmCurve *)wr_icco->link_tag(
+ wr_icco, icSigBlueTRCTag, icSigRedTRCTag)) == NULL)
+ error("link_tag failed: %d, %s",rv,wr_icco->err);
+
+ } else { /* Else individual */
+ if ((wog = (icmCurve *)wr_icco->add_tag(
+ wr_icco, icSigGreenTRCTag, icSigCurveType)) == NULL)
+ error("add_tag failed: %d, %s",rv,wr_icco->err);
+ if ((wob = (icmCurve *)wr_icco->add_tag(
+ wr_icco, icSigBlueTRCTag, icSigCurveType)) == NULL)
+ error("add_tag failed: %d, %s",rv,wr_icco->err);
+ }
+
+ if (ptype == prof_shamat || ptype == prof_sha1mat) { /* Shaper */
+ wor->flag = wog->flag = wob->flag = icmCurveSpec;
+ wor->size = wog->size = wob->size = 256; /* Number of entries */
+ } else { /* Gamma */
+ wor->flag = wog->flag = wob->flag = icmCurveGamma;
+ wor->size = wog->size = wob->size = 1; /* Must be 1 for gamma */
+ }
+ wor->allocate((icmBase *)wor); /* Allocate space */
+ wog->allocate((icmBase *)wog);
+ wob->allocate((icmBase *)wob);
+
+ /* icxMatrix will set curve values */
+ }
+
+ } else { /* Lut type profile */
+
+ /* 16 bit dev -> pcs lut: */
+ {
+ icmLut *wo;
+
+ /* Only A2B0, no intent */
+ if ((wo = (icmLut *)wr_icco->add_tag(
+ wr_icco, icSigAToB0Tag, icSigLut16Type)) == NULL)
+ error("add_tag failed: %d, %s",wr_icco->errc,wr_icco->err);
+
+ wo->inputChan = 3;
+ wo->outputChan = 3;
+ if (iquality >= 3) {
+ wo->clutPoints = 45;
+ wo->inputEnt = 2048;
+ wo->outputEnt = 2048;
+ } else if (iquality == 2) {
+ wo->clutPoints = 33;
+ wo->inputEnt = 2048;
+ wo->outputEnt = 2048;
+ } else if (iquality == 1) {
+ wo->clutPoints = 17;
+ wo->inputEnt = 1024;
+ wo->outputEnt = 1024;
+ } else {
+ wo->clutPoints = 9;
+ wo->inputEnt = 512;
+ wo->outputEnt = 512;
+ }
+
+ wo->allocate((icmBase *)wo);/* Allocate space */
+
+ /* icxLuLut will set tables values */
+ }
+
+#ifdef DOB2A
+ /* 16 bit pcs -> dev lut: */
+ if (dob2a) {
+ icmLut *wo;
+
+ /* Only B2A0, no intent */
+ if ((wo = (icmLut *)wr_icco->add_tag(
+ wr_icco, icSigBToA0Tag, icSigLut16Type)) == NULL)
+ error("add_tag failed: %d, %s",wr_icco->errc,wr_icco->err);
+
+ wo->inputChan = 3;
+ wo->outputChan = 3;
+ if (oquality >= 3) {
+ wo->clutPoints = 45;
+ wo->inputEnt = 2048;
+ wo->outputEnt = 2048;
+ } else if (oquality == 2) {
+ wo->clutPoints = 33;
+ wo->inputEnt = 2048;
+ wo->outputEnt = 2048;
+ } else if (oquality == 1) {
+ wo->clutPoints = 17;
+ wo->inputEnt = 1024;
+ wo->outputEnt = 1024;
+ } else if (oquality >= 0) {
+ wo->clutPoints = 9;
+ wo->inputEnt = 512;
+ wo->outputEnt = 512;
+ } else { /* Special, Extremely low quality */
+ wo->clutPoints = 3;
+ wo->inputEnt = 64;
+ wo->outputEnt = 64;
+ }
+
+ wo->allocate((icmBase *)wo);/* Allocate space */
+
+ /* We set the tables below */
+ }
+#endif /* DOB2A */
+
+ }
+
+ /* Sample data use to create profile: */
+ if (nocied == 0) {
+ icmText *wo;
+ char *crt;
+ FILE *fp;
+
+ if ((wo = (icmText *)wr_icco->add_tag(
+ wr_icco, icmMakeTag('t','a','r','g'), icSigTextType)) == NULL)
+ error("add_tag failed: %d, %s",wr_icco->errc,wr_icco->err);
+
+#if defined(O_BINARY) || defined(_O_BINARY)
+ if ((fp = fopen(in_name, "rb")) == NULL)
+#else
+ if ((fp = fopen(in_name, "r")) == NULL)
+#endif
+ error("Unable to open input file '%s' for reading",in_name);
+
+ if (fseek(fp, 0, SEEK_END))
+ error("Unable to seek to end of file '%s'",in_name);
+ wo->size = ftell(fp) + 1; /* Size needed + null */
+ wo->allocate((icmBase *)wo);/* Allocate space */
+
+ if (fseek(fp, 0, SEEK_SET))
+ error("Unable to seek to end of file '%s'",in_name);
+
+ if (fread(wo->data, 1, wo->size-1, fp) != wo->size-1)
+ error("Failed to read file '%s'",in_name);
+ wo->data[wo->size-1] = '\000';
+ fclose(fp);
+
+ /* Duplicate for compatibility */
+ if (wr_icco->link_tag(
+ wr_icco, icmMakeTag('D','e','v','D'), icmMakeTag('t','a','r','g')) == NULL)
+ error("link_tag failed: %d, %s",wr_icco->errc,wr_icco->err);
+ if (wr_icco->link_tag(
+ wr_icco, icmMakeTag('C','I','E','D'), icmMakeTag('t','a','r','g')) == NULL)
+ error("link_tag failed: %d, %s",wr_icco->errc,wr_icco->err);
+ }
+
+ if ((npat = icg->t[0].nsets) <= 0)
+ error ("No sets of data");
+
+ if (verb) {
+ fprintf(verbo,"No of test patches = %d\n",npat);
+ }
+
+ if (extrap) {
+ npxpat = 4 * EXTRAP_MAXPNTS; /* Allow for up to 20 extra patches */
+ }
+
+ /* Allocate arrays to hold test patch input and output values */
+ if ((tpat = (cow *)malloc(sizeof(cow) * (npat + npxpat))) == NULL)
+ error("Malloc failed - tpat[]");
+
+ /* Read in the CGATs fields */
+ {
+ int ti;
+ int Xi, Yi, Zi;
+ int ri, gi, bi;
+
+ /* Check that we handle the color space */
+ if ((ti = icg->find_kword(icg, 0, "COLOR_REP")) < 0)
+ error ("Input file doesn't contain keyword COLOR_REPS");
+ if (strcmp(icg->t[0].kdata[ti],"LAB_RGB") == 0) {
+ isLab = 1;
+ } else {
+ if (strcmp(icg->t[0].kdata[ti],"XYZ_RGB") == 0) {
+ isLab = 0;
+ } else {
+ error ("Input device input file has unhandled color representation");
+ }
+ }
+
+ if ((ri = icg->find_field(icg, 0, "RGB_R")) < 0)
+ error ("Input file doesn't contain field RGB_R");
+ if (icg->t[0].ftype[ri] != r_t)
+ error ("Field CMYK_C is wrong type - corrupted file ?");
+ if ((gi = icg->find_field(icg, 0, "RGB_G")) < 0)
+ error ("Input file doesn't contain field RGB_G");
+ if (icg->t[0].ftype[gi] != r_t)
+ error ("Field CMYK_M is wrong type - corrupted file ?");
+ if ((bi = icg->find_field(icg, 0, "RGB_B")) < 0)
+ error ("Input file doesn't contain field RGB_B");
+ if (icg->t[0].ftype[bi] != r_t)
+ error ("Field CMYK_Y is wrong type - corrupted file ?");
+
+ if (spec == 0) { /* Using instrument tristimulous value */
+
+ if (isLab) {
+ if ((Xi = icg->find_field(icg, 0, "LAB_L")) < 0)
+ error ("Input file doesn't contain field LAB_L");
+ if (icg->t[0].ftype[Xi] != r_t)
+ error ("Field LAB_L is wrong type - corrupted file ?");
+ if ((Yi = icg->find_field(icg, 0, "LAB_A")) < 0)
+ error ("Input file doesn't contain field LAB_A");
+ if (icg->t[0].ftype[Yi] != r_t)
+ error ("Field LAB_A is wrong type - corrupted file ?");
+ if ((Zi = icg->find_field(icg, 0, "LAB_B")) < 0)
+ error ("Input file doesn't contain field LAB_B");
+ if (icg->t[0].ftype[Zi] != r_t)
+ error ("Field LAB_B is wrong type - corrupted file ?");
+ } else {
+ if ((Xi = icg->find_field(icg, 0, "XYZ_X")) < 0)
+ error ("Input file doesn't contain field XYZ_X");
+ if (icg->t[0].ftype[Xi] != r_t)
+ error ("Field XYZ_X is wrong type - corrupted file ?");
+ if ((Yi = icg->find_field(icg, 0, "XYZ_Y")) < 0)
+ error ("Input file doesn't contain field XYZ_Y");
+ if (icg->t[0].ftype[Yi] != r_t)
+ error ("Field XYZ_Y is wrong type - corrupted file ?");
+ if ((Zi = icg->find_field(icg, 0, "XYZ_Z")) < 0)
+ error ("Input file doesn't contain field XYZ_Z");
+ if (icg->t[0].ftype[Zi] != r_t)
+ error ("Field XYZ_Z is wrong type - corrupted file ?");
+ }
+
+ for (i = 0; i < npat; i++) {
+ tpat[i].w = 1.0;
+ tpat[i].p[0] = *((double *)icg->t[0].fdata[i][ri]) / 100.0;
+ tpat[i].p[1] = *((double *)icg->t[0].fdata[i][gi]) / 100.0;
+ tpat[i].p[2] = *((double *)icg->t[0].fdata[i][bi]) / 100.0;
+ if (tpat[i].p[0] > 1.0
+ || tpat[i].p[1] > 1.0
+ || tpat[i].p[2] > 1.0) {
+ error("At %d device values %f %f %f field exceeds 100.0!",i,100.0 * tpat[i].p[0],100.0 * tpat[i].p[1],100.0 * tpat[i].p[2]);
+ }
+ tpat[i].v[0] = *((double *)icg->t[0].fdata[i][Xi]);
+ tpat[i].v[1] = *((double *)icg->t[0].fdata[i][Yi]);
+ tpat[i].v[2] = *((double *)icg->t[0].fdata[i][Zi]);
+ if (!isLab) {
+ tpat[i].v[0] /= 100.0; /* Normalise XYZ to range 0.0 - 1.0 */
+ tpat[i].v[1] /= 100.0;
+ tpat[i].v[2] /= 100.0;
+ }
+ if (!isLab && wantLab) { /* Convert test patch result XYZ to PCS (D50 Lab) */
+ icmXYZ2Lab(&icmD50, tpat[i].v, tpat[i].v);
+ } else if (isLab && !wantLab) {
+ icmLab2XYZ(&icmD50, tpat[i].v, tpat[i].v);
+ }
+ }
+
+ } else { /* Using spectral data */
+ int j, ii;
+ xspect sp;
+ char buf[100];
+ int spi[XSPECT_MAX_BANDS]; /* CGATS indexes for each wavelength */
+ xsp2cie *sp2cie; /* Spectral conversion object */
+
+ if ((ii = icg->find_kword(icg, 0, "SPECTRAL_BANDS")) < 0)
+ error ("Input file doesn't contain keyword SPECTRAL_BANDS");
+ sp.spec_n = atoi(icg->t[0].kdata[ii]);
+ if ((ii = icg->find_kword(icg, 0, "SPECTRAL_START_NM")) < 0)
+ error ("Input file doesn't contain keyword SPECTRAL_START_NM");
+ sp.spec_wl_short = atof(icg->t[0].kdata[ii]);
+ if ((ii = icg->find_kword(icg, 0, "SPECTRAL_END_NM")) < 0)
+ error ("Input file doesn't contain keyword SPECTRAL_END_NM");
+ sp.spec_wl_long = atof(icg->t[0].kdata[ii]);
+ sp.norm = 100.0;
+
+ /* Find the fields for spectral values */
+ for (j = 0; j < sp.spec_n; j++) {
+ int nm;
+
+ /* Compute nearest integer wavelength */
+ nm = (int)(sp.spec_wl_short + ((double)j/(sp.spec_n-1.0))
+ * (sp.spec_wl_long - sp.spec_wl_short) + 0.5);
+
+ sprintf(buf,"SPEC_%03d",nm);
+
+ if ((spi[j] = icg->find_field(icg, 0, buf)) < 0)
+ error("Input file doesn't contain field %s",buf);
+ }
+
+ /* Create a spectral conversion object */
+ if ((sp2cie = new_xsp2cie(illum, cust_illum, observ, NULL,
+ wantLab ? icSigLabData : icSigXYZData, icxClamp)) == NULL)
+ error("Creation of spectral conversion object failed");
+
+ for (i = 0; i < npat; i++) {
+ tpat[i].w = 1.0;
+ tpat[i].p[0] = *((double *)icg->t[0].fdata[i][ri]) / 100.0;
+ tpat[i].p[1] = *((double *)icg->t[0].fdata[i][gi]) / 100.0;
+ tpat[i].p[2] = *((double *)icg->t[0].fdata[i][bi]) / 100.0;
+ if (tpat[i].p[0] > 1.0
+ || tpat[i].p[1] > 1.0
+ || tpat[i].p[2] > 1.0) {
+ error("Device value field exceeds 100.0!");
+ }
+
+ /* Read the spectral values for this patch */
+ for (j = 0; j < sp.spec_n; j++) {
+ sp.spec[j] = *((double *)icg->t[0].fdata[i][spi[j]]);
+ }
+
+ /* Convert it to CIE space */
+ sp2cie->convert(sp2cie, tpat[i].v, &sp);
+ }
+
+ sp2cie->del(sp2cie); /* Done with this */
+
+ }
+ } /* End of reading in CGATs file */
+
+ if (isLut == 0) { /* Gamma/Shaper + matrix profile */
+ xicc *wr_xicc; /* extention object */
+ icxLuBase *xluo; /* Forward ixcLu */
+ int flags = 0;
+
+ /* Wrap with an expanded icc */
+ if ((wr_xicc = new_xicc(wr_icco)) == NULL)
+ error("Creation of xicc failed");
+
+ if (verb)
+ flags |= ICX_VERBOSE;
+
+ if (ptype == prof_matonly)
+ flags |= ICX_NO_IN_SHP_LUTS; /* Make it linear */
+
+ if (clipprims)
+ flags |= ICX_CLIP_WB | ICX_CLIP_PRIMS;
+
+ flags |= ICX_SET_BLACK; /* Compute & use black */
+ flags |= ICX_SET_WHITE; /* Compute & use white */
+ if (autowpsc)
+ flags |= ICX_SET_WHITE_US; /* Compute & use white without scaling to L */
+
+ flags |= ICX_WRITE_WBL; /* Matrix: write white/black/luminence */
+
+ /* Setup Device -> XYZ conversion (Fwd) object from scattered data. */
+ if ((xluo = wr_xicc->set_luobj(
+// wr_xicc, icmFwd, icRelativeColorimetric,
+ wr_xicc, icmFwd, icmDefaultIntent,
+ icmLuOrdNorm,
+ flags, /* Flags */
+ npat, npat, tpat, NULL, 0.0, wpscale, smooth, avgdev,
+ NULL, NULL, NULL, iquality)) == NULL)
+ error("%d, %s",wr_xicc->errc, wr_xicc->err);
+
+ /* Free up xicc stuff */
+ xluo->del(xluo);
+ wr_xicc->del(wr_xicc);
+
+ } else { /* cLUT based profile */
+ int flags = 0;
+ icxMatrixModel *mm = NULL;
+
+ xicc *wr_xicc; /* extention object */
+ icxLuBase *AtoB; /* AtoB ixcLu */
+
+ if (extrap) {
+ cow *mpat;
+ int nmpat;
+ int j;
+
+ if (verb) printf("Creating extrapolation black and white points:\n");
+
+ if ((mpat = (cow *)malloc(sizeof(cow) * npat)) == NULL)
+ error("Malloc failed - mpat[]");
+
+ /* Weight points from full set to build matrix model */
+ /* to extrapolate the neutral axis */
+ for (nmpat = j = 0; j < npat; j++) {
+ double mnp, mxp;
+ int k;
+
+ icmCpy3(mpat[nmpat].p, tpat[j].p);
+ icmCpy3(mpat[nmpat].v, tpat[j].v);
+
+ /* Locate largest/smallest RGB value */
+ mxp = -1e6, mnp = 1e6;
+ for (k = 0; k < 3; k++) {
+ if (tpat[j].p[k] > mxp)
+ mxp = tpat[j].p[k];
+ if (tpat[j].p[k] < mnp)
+ mnp = tpat[j].p[k];
+ }
+ mxp -= mnp; /* Spread; 0 for R=G=B */
+
+ mpat[nmpat].w = pow(1.1 - mxp, 2.0);
+//printf("~1 added value %d: %f %f %f -> %f %f %f wt %f\n",j, mpat[nmpat].p[0], mpat[nmpat].p[1], mpat[nmpat].p[2], mpat[nmpat].v[0], mpat[nmpat].v[1], mpat[nmpat].v[2],mpat[nmpat].w);
+ nmpat++;
+ }
+
+ /* Create gamma/matrix model to extrapolate with. */
+ /* (Use ofset & gain, gamma curve as 0th order with 1 harmonic, */
+ /* and smooth it.) */
+ if ((mm = new_MatrixModel(verb, nmpat, mpat, wantLab,
+ /* quality */ -1, /* isLinear */ ptype == prof_matonly,
+ /* isGamma */ 0, /* isShTRC */ 0,
+ /* shape0gam */ 1, /* clipbw */ 0, /* clipprims */ 0,
+// /* smooth */ 1.0, /* scale */ 0.7)) == NULL) {
+ /* smooth */ 1.0, /* scale */ 1.0)) == NULL) {
+ error("Creating extrapolation matrix model failed - memory ?");
+ }
+
+#ifdef NEVER /* Plot Lab of model */
+{
+ #define XRES 100
+ double xx[XRES];
+ double y0[XRES];
+ double y1[XRES];
+ double y2[XRES];
+
+ /* Display the result fit */
+ for (i = 0; i < XRES; i++) {
+ double rgb[3], lab[3];
+ xx[i] = rgb[0] = rgb[1] = rgb[2] = i/(double)(XRES-1);
+ mm->lookup(mm, lab, rgb);
+ if (wantLab)
+ icmLab2XYZ(&icmD50,lab,lab);
+ y0[i] = lab[0];
+ y1[i] = lab[1];
+ y2[i] = lab[2];
+ }
+ do_plot(xx,y0,y1,y2,XRES);
+}
+#endif /* DEBUG_PLOT */
+ }
+
+ if (extrap) {
+ int ii, wix = 0, j;
+ int pcsy; /* Effective PCS L or Y chanel index */
+ double wpy = -1e60;
+ double dwhite[MXDI]; /* Device white */
+ double mxdw;
+ double avgdist; /* Average distance between points */
+
+ /* Figure out the device white point. */
+ /* Note that this is duplicating code in xicc/xmatrix.c */
+ /* and xfit.c */
+
+ if (wantLab)
+ pcsy = 0; /* L or Lab */
+ else
+ pcsy = 1; /* Y of XYZ */
+
+ for (i = 0; i < npat; i++) {
+ double labv[3], yv;
+
+ /* Create D50 Lab to allow some chromatic sensitivity */
+ /* in picking the white point */
+ if (wantLab)
+ icmCpy3(labv, tpat[i].v);
+ else
+ icmXYZ2Lab(&icmD50, labv, tpat[i].v);
+
+ /* Tilt things towards D50 neutral white patches */
+ yv = labv[0] - 0.3 * sqrt(labv[1] * labv[1] + labv[2] * labv[2]);
+ if (yv > wpy) {
+ for (j = 0; j < 3; j++)
+ dwhite[j] = tpat[i].p[j];
+ wpy = yv;
+ wix = i;
+ }
+ }
+
+ /* Fix extrapolation matrix to be perfect at white point */
+ mm->force(mm, tpat[wix].v, tpat[wix].p);
+
+ /* Scale the white point to make one dev value 1.0 */
+ mxdw = -1;
+ for (j = 0; j < 3; j++) {
+ if (dwhite[j] > mxdw)
+ mxdw = dwhite[j];
+ }
+ for (j = 0; j < 3; j++) {
+ dwhite[j] /= mxdw;
+ }
+
+ avgdist = pow(1.0/(double)npat, 1.0/3.0);
+ if (avgdist < 0.001)
+ avgdist = 0.001;
+ else if (avgdist > 0.3)
+ avgdist = 0.3;
+//printf("~1 avgdist = %f\n",avgdist);
+
+ /* For points with white point device ratio, */
+ /* and points with R=G=B ratio, create extrapolation points. */
+ for (ii = 0; ii < 2; ii++) {
+
+ if (ii > 1)
+ dwhite[0] = dwhite[1] = dwhite[2] = 1.0;
+
+ /* Create a series of black and white patch */
+ for (i = 0; i < 2; i++) {
+ int cix; /* Closest point index */
+ int eix; /* End point index */
+ double cde = 1e60; /* Closest point distance */
+ double tt;
+ double corr[3], cwt; /* Correction */
+
+ eix = npat + nxpat;
+
+ icmScale3(tpat[eix].p, dwhite, (double)i);
+
+ /* Locate closest point */
+ for (j = 0; j < npat; j++) {
+ double mnp, mxp;
+ int k;
+
+ /* Locate largest/smallest RGB value */
+ mxp = -1e6, mnp = 1e6;
+ for (k = 0; k < 3; k++) {
+ if (tpat[j].p[k] > mxp)
+ mxp = tpat[j].p[k];
+ if (tpat[j].p[k] < mnp)
+ mnp = tpat[j].p[k];
+ }
+ mxp -= mnp; /* Spread; 0 for R=G=B */
+
+ tt = icmNorm33(tpat[eix].p, tpat[j].p);
+ tt += mxp;
+
+ if (tt < cde) {
+ cde = tt;
+ cix = j;
+ }
+ }
+
+//printf("~1 closest %d: de %f, %f %f %f -> %f %f %f\n",cix, cde, tpat[cix].p[0], tpat[cix].p[1], tpat[cix].p[2], tpat[cix].v[0], tpat[cix].v[1], tpat[cix].v[2]);
+
+//{
+//double val[3];
+//mm->lookup(mm, val, tpat[cix].p);
+//printf("~1 closest gam/matrix -> %f %f %f\n",val[0],val[1],val[2]);
+//}
+
+ /* Lookup matrix value for our new point */
+ mm->lookup(mm, tpat[eix].v, tpat[eix].p);
+//printf("~1 got value %d: %f %f %f -> %f %f %f\n",i, tpat[eix].p[0], tpat[eix].p[1], tpat[eix].p[2], tpat[eix].v[0], tpat[eix].v[1], tpat[eix].v[2]);
+ /* Weight the extra point so that it doesn't overpower the */
+ /* nearest real point to it too much. */
+ tt = cde;
+ if (tt > avgdist) /* Distance at which sythetic point has 100% weight */
+ tt = avgdist;
+ tpat[eix].w = 0.5 * EXTRAP_WEIGHT * tt/avgdist;
+//printf("~1 weight %f\n",tpat[eix].w);
+ if (verb)
+ printf("Added synthetic point @ %f %f %f, val %f %f %f, weight %f\n",tpat[eix].p[0], tpat[eix].p[1], tpat[eix].p[2], tpat[eix].v[0], tpat[eix].v[1], tpat[eix].v[2],tpat[eix].w);
+ nxpat++;
+
+ /* If there is a lot of space, add a second intemediate point */
+//printf("~1 cde = %f, avgdist = %f\n",cde,avgdist);
+ if (cde >= (0.5 * avgdist)) {
+ int nxps; /* Number of extra points including end point */
+ nxps = 1 + (int)(cde/(0.5 * avgdist));
+ if (nxps > EXTRAP_MAXPNTS)
+ nxps = EXTRAP_MAXPNTS;
+
+//printf("~1 nxps = %d\n",nxps);
+ for (j = 1; j < nxps; j++) {
+ double bl, ipos;
+
+ bl = j/(nxps + 1.0);
+
+ ipos = (1.0 - bl) * tpat[eix].p[0]
+ + bl * (tpat[cix].p[0] + tpat[cix].p[1] + tpat[cix].p[1])/3.0;
+ icmScale3(tpat[npat + nxpat].p, dwhite, ipos);
+
+ /* Lookup matrix value for our new point */
+ mm->lookup(mm, tpat[npat + nxpat].v, tpat[npat + nxpat].p);
+
+ /* Weight the extra point so that it doesn't overpower the */
+ /* nearest real point to it too much. */
+ cde = icmNorm33(tpat[cix].p, tpat[npat + nxpat].p);
+
+ if (cde > avgdist) /* Distance at which sythetic point has 100% weight */
+ cde = avgdist;
+ tpat[npat + nxpat].w = 0.5 * EXTRAP_WEIGHT * cde/avgdist;
+ if (verb)
+ printf("Added synthetic point @ %f %f %f, val %f %f %f, weight %f\n",tpat[npat + nxpat].p[0], tpat[npat + nxpat].p[1], tpat[npat + nxpat].p[2], tpat[npat + nxpat].v[0], tpat[npat + nxpat].v[1], tpat[npat + nxpat].v[2],tpat[npat + nxpat].w);
+ nxpat++;
+ }
+ }
+ }
+ }
+ }
+
+ /* Wrap with an expanded icc */
+ if ((wr_xicc = new_xicc(wr_icco)) == NULL)
+ error ("Creation of xicc failed");
+
+ flags |= ICX_CLIP_NEAREST; /* This will avoid clip caused rev setup */
+
+ if (noisluts)
+ flags |= ICX_NO_IN_SHP_LUTS;
+
+ if (noipluts)
+ flags |= ICX_NO_IN_POS_LUTS;
+
+ if (nooluts)
+ flags |= ICX_NO_OUT_LUTS;
+
+ if (verb)
+ flags |= ICX_VERBOSE;
+
+ if (clipprims)
+ flags |= ICX_CLIP_WB;
+
+ flags |= ICX_SET_BLACK; /* Compute & use black */
+ flags |= ICX_SET_WHITE; /* Compute & use white */
+ if (clipovwp)
+ flags |= ICX_SET_WHITE_C; /* Compute & use white and clip cLUT over D50 */
+ else if (autowpsc)
+ flags |= ICX_SET_WHITE_US; /* Compute & use white without scaling to L */
+
+ /* Setup RGB -> Lab conversion object from scattered data. */
+ /* Note that we've layered it on a native XYZ icc profile. */
+ /* (The skeleton model is not used - it doesn't seem to help) */
+ if ((AtoB = wr_xicc->set_luobj(
+ wr_xicc, icmFwd, icmDefaultIntent,
+ icmLuOrdNorm,
+#ifdef USE_EXTRA_FITTING
+ ICX_EXTRA_FIT |
+#endif
+#ifdef USE_2PASSSMTH
+ ICX_2PASSSMTH |
+#endif
+ flags, /* Flags */
+ npat + nxpat, npat, tpat, NULL, 0.0, wpscale, smooth, avgdev,
+ NULL, NULL, NULL, iquality)) == NULL)
+ error ("%d, %s",wr_xicc->errc, wr_xicc->err);
+
+ if (mm != NULL)
+ mm->del(mm);
+
+ /* Free up xicc stuff */
+ AtoB->del(AtoB);
+
+#ifdef DOB2A
+ if (dob2a) {
+ icmLut *wo;
+
+ in_b2a_callback cx;
+
+ if (verb)
+ printf("Setting up B to A table lookup\n");
+
+ /* Get a suitable forward conversion object to invert. */
+ /* By creating a separate one to the one created using scattered data, */
+ /* we ge the chance to set ICX_CAM_CLIP. It is always set to Lab 'PCS' */
+ {
+ int flags = 0;
+
+ if (verb)
+ flags |= ICX_VERBOSE;
+
+ flags |= ICX_CLIP_NEAREST; /* Not vector clip */
+#ifdef USE_CAM_CLIP_OPT
+ flags |= ICX_CAM_CLIP; /* Clip in CAM Jab space rather than Lab */
+#else
+ warning("!!!! USE_CAM_CLIP_OPT in profout.c is off !!!!");
+#endif
+ if ((AtoB = wr_xicc->get_luobj(wr_xicc, flags, icmFwd,
+ icmDefaultIntent,
+ wantLab ? icSigLabData : icSigXYZData,
+ icmLuOrdNorm, NULL, NULL)) == NULL)
+ error ("%d, %s",wr_xicc->errc, wr_xicc->err);
+ }
+
+ /* setup context ready for B2A table setting */
+ cx.verb = verb;
+ cx.pcsspace = wantLab ? icSigLabData : icSigXYZData;
+ cx.wantLab = wantLab; /* Copy PCS flag over */
+#ifdef NO_B2A_PCS_CURVES
+ cx.noPCScurves = 1; /* Don't use PCS curves */
+#else
+ cx.noPCScurves = 0;
+#endif
+ cx.devspace = icSigRgbData;
+ cx.x = (icxLuLut *)AtoB; /* A2B icxLuLut created from scattered data */
+
+ if ((wo = (icmLut *)wr_icco->read_tag(
+ wr_icco, icSigBToA0Tag)) == NULL)
+ error("read_tag failed: %d, %s",wr_icco->errc,wr_icco->err);
+
+ /* We now setup an exact inverse, colorimetric style */
+ /* Use helper function to do the hard work. */
+
+ if (cx.verb) {
+ unsigned int ui;
+ int extra;
+ cx.count = 0;
+ cx.last = -1;
+ for (cx.total = 1, ui = 0; ui < wo->inputChan; ui++, cx.total *= wo->clutPoints)
+ ;
+ /* Add in cell center points */
+ for (extra = 1, ui = 0; ui < wo->inputChan; ui++, extra *= (wo->clutPoints-1))
+ ;
+ cx.total += extra;
+ printf("Creating B to A tables\n");
+ printf(" 0%%"); fflush(stdout);
+ }
+
+ if (icmSetMultiLutTables(
+ 1,
+ &wo,
+ ICM_CLUT_SET_APXLS, /* Use least squared aprox. */
+ &cx, /* Context */
+ cx.pcsspace, /* Input color space */
+ icSigRgbData, /* Output color space */
+ in_b2a_input, /* Input transform PCS->PCS' */
+ NULL, NULL, /* Use default Lab' range */
+ in_b2a_clut, /* Lab' -> Device' transfer function */
+ NULL, NULL, /* Use default Device' range */
+ in_b2a_output) != 0) /* Output transfer function, Device'->Device */
+ error("Setting 16 bit PCS->Device Lut failed: %d, %s",wr_icco->errc,wr_icco->err);
+ if (cx.verb) {
+ printf("\n");
+ }
+#ifdef WARN_CLUT_CLIPPING
+ if (wr_icco->warnc)
+ warning("Values clipped in setting LUT");
+#endif /* WARN_CLUT_CLIPPING */
+
+ if (verb)
+ printf("Done B to A table\n");
+ AtoB->del(AtoB);
+ }
+#endif /* DOB2A */
+ wr_xicc->del(wr_xicc);
+
+ }
+
+ /* Write the file (including all tags) out */
+ if ((rv = wr_icco->write(wr_icco,wr_fp,0)) != 0)
+ error ("Write file: %d, %s",rv,wr_icco->err);
+
+ /* Close the file */
+ wr_icco->del(wr_icco);
+ wr_fp->del(wr_fp);
+
+ /* Check the profile accuracy against the data points */
+ if (verb || verify) {
+ icmFile *rd_fp;
+ icc *rd_icco;
+ icmLuBase *luo;
+ double merr = 0.0;
+ double aerr = 0.0;
+ double nsamps = 0.0;
+
+ /* Open up the file for reading */
+ if ((rd_fp = new_icmFileStd_name(file_name,"r")) == NULL)
+ error ("Write: Can't open file '%s'",file_name);
+
+ if ((rd_icco = new_icc()) == NULL)
+ error ("Write: Creation of ICC object failed");
+
+ /* Read the header and tag list */
+ if ((rv = rd_icco->read(rd_icco,rd_fp,0)) != 0)
+ error ("Read: %d, %s",rv,rd_icco->err);
+
+ /* ~~ should use an xluobj with merge output ~~~ */
+ /* Get the A2B table */
+ if ((luo = rd_icco->get_luobj(rd_icco, icmFwd,
+ icAbsoluteColorimetric, icSigLabData, icmLuOrdNorm)) == NULL) {
+ error ("%d, %s",rd_icco->errc, rd_icco->err);
+ }
+
+ for (i = 0; i < npat; i++) {
+ double out[3], ref[3];
+ double mxd;
+
+ if (luo->lookup(luo, out, tpat[i].p) > 1)
+ error ("%d, %s",rd_icco->errc,rd_icco->err);
+
+ /* Our tpat data might be in XYZ, so generate an Lab ref value */
+ if (!wantLab) { /* Convert test patch result XYZ to PCS (D50 Lab) */
+ icmXYZ2Lab(&icmD50, ref, tpat[i].v);
+
+ } else {
+ ref[0] = tpat[i].v[0];
+ ref[1] = tpat[i].v[1];
+ ref[2] = tpat[i].v[2];
+ }
+
+ if (verb && verify) {
+ printf("[%f] %f %f %f -> %f %f %f should be %f %f %f\n",
+ icmLabDE(ref, out),
+ tpat[i].p[0],tpat[i].p[1],tpat[i].p[2],
+ out[0],out[1],out[2],
+ ref[0],ref[1],ref[2]);
+ }
+
+ /* Check the result */
+ mxd = icmLabDE(ref, out);
+ if (mxd > merr)
+ merr = mxd;
+
+ aerr += mxd;
+ nsamps++;
+ }
+ printf("Profile check complete, peak err = %f, avg err = %f\n",merr,aerr/nsamps);
+
+ /* Done with lookup object */
+ luo->del(luo);
+
+ /* Close the file */
+ rd_icco->del(rd_icco);
+ rd_fp->del(rd_fp);
+ }
+
+ free(tpat);
+}
+
+
diff --git a/profile/profout.c b/profile/profout.c
new file mode 100644
index 0000000..d89ae4e
--- /dev/null
+++ b/profile/profout.c
@@ -0,0 +1,2963 @@
+
+/*
+ * Argyll Color Correction System
+ * Output device profile creator.
+ *
+ * Author: Graeme W. Gill
+ * Date: 11/10/00
+ *
+ * Copyright 2000-2011 Graeme W. Gill
+ * All rights reserved.
+ *
+ * This material is licenced under the GNU AFFERO GENERAL PUBLIC LICENSE Version 3 :-
+ * see the License.txt file for licencing details.
+ */
+
+/*
+ * This program takes in the scattered test chart
+ * points, and interpolates them into a gridded
+ * forward ICC device profile for an output or display device,
+ * using a clut based profle.
+ * It also creates backward tables based on the forward grid.
+ *
+ * Preview profiles are not currently generated.
+ *
+ * The gamut clut should be implemented with xicc/rspl
+ */
+
+/*
+ * TTBD:
+ *
+ * For flexibility, it might be nice to allow the user to override the
+ * default intent for all 3 output tables, allowing fully custom usage.
+ * ie. Would be nice to offer various relative colorimetric intents,
+ * so that more image friendly versions could be chosen.
+ * (Need to break dependence between intents CAM to do this with full flexibility).
+ *
+ * Need to make this more of a library:
+ * Fix error handling
+ * fix verbose output
+ * hand icc object back rather than writing file ?
+ */
+
+/*
+ Outline of code flow:
+
+ profout:
+ Create ICC profile and all the tags. Table tags are initialy not set.
+
+ Read in the CGTATS data and convert spectral to PCS if needed.
+
+ Wrap the icc in an xicc, and then create an xicc lookup object
+ for the device->PCS tables by calling xicc->set_luobj().
+
+ For a CLUT type profile, create a gamut mapping object and
+ setup all the other bits and peices needed to convert a color
+ from PCS to device, then use (icc) icmSetMultiLutTables() which will
+ call back the out_b2a_input(), out_b2a_clut() and out_b2a_output()
+ functions.
+ */
+
+#undef DEBUG /* Print B2A processing information */
+#undef DEBUG_ONE /* Test a particular value rather than process whole grid */
+
+#define verbo stdout
+
+#undef IMP_MONO /* [Undef] Turn on development code */
+
+#define NO_B2A_PCS_CURVES /* [Define] PCS curves seem to make B2A less accurate. Why ? */
+#define USE_CAM_CLIP_OPT /* [Define] Clip out of gamut in CAM space rather than PCS */
+#define USE_LEASTSQUARES_APROX /* [Define] Use least squares fitting approximation in B2A */
+//#undef USE_EXTRA_FITTING /* [Undef] Turn on data point error compensation in A2B */
+//#undef USE_2PASSSMTH /* [Undef] Turn on Gaussian smoothing in A2B */
+#undef DISABLE_GAMUT_TAG /* [Undef] To disable gamut tag */
+#undef WARN_CLUT_CLIPPING /* [Undef] Print warning if setting clut clips */
+#undef COMPARE_INV_CLUT /* [Undef] Compare result of inv_clut with clut to diag inv probs */
+#define FILTER_B2ACLIP /* [Define] Filter clip area of B2A */
+#define FILTER_THR_DE 3.0 /* [5.0] Filtering threshold DE */
+#define FILTER_MAX_DE 5.0 /* [10.0] Filtering DE to gamut surface at whit MAX_RAD starts */
+#define FILTER_MAX_RAD 0.1 /* [0.1] Filtering maximum radius in grid */
+
+#include <stdio.h>
+#include "numlib.h"
+#include "icc.h"
+#include "cgats.h"
+#include "xicc.h"
+#include "counters.h"
+#include "rspl.h"
+#include "insttypes.h"
+#include "prof.h"
+#include "gamut.h"
+#include "gammap.h"
+
+#ifndef MAX_CAL_ENT
+#define MAX_CAL_ENT 4096
+#endif
+
+/*
+ Basic algorithm outline:
+
+ Printer:
+ Figure out the input curves to give
+ the flattest grid.
+
+ Figure out the grid values.
+
+ Use them to generate all the A2B tables.
+
+ Use the inverse rspl code to compute
+ all the B2A profiles.
+
+*/
+
+/*
+ Notes:
+
+ The shared gamma/shaper support is for silly applications
+ like which can't handle display profiles that have per
+ chanel gamma's, per chanel curves or clut based display profiles.
+
+*/
+
+/* NOTE:-
+ It's interesting that the white and black points recorded in the tags,
+ generally won't quite match the white and black points returned by
+ looking up the profile in absolute mode.
+
+ For a Matrix profile, in the case of the white point this is
+ because we're not using the ICC 16 bit quantized value to
+ create the relative transform matrix, and in the case of
+ the black point, it can never be a perfect match because the black
+ point returned by a profile lookup will be the quantized black
+ point of the matrix, transformed by the rel->abs matrix, which
+ generally won't be equal to an ICC quantized value.
+ It might help the latter case if we were at least able to convert
+ the profile quantized black point into the absolute black point
+ via the rel->abs transform, and then quantize it.
+
+ Of course all of this will be worse in the Lut type profile,
+ due to the white and black points being stored in a different
+ quantized space (XYZ vs. Lab) than the Lut grid point values!
+ */
+
+
+#ifdef DEBUG
+#undef DBG
+#define DBG(xxx) printf xxx ;
+#else
+#undef DBG
+#define DBG(xxx)
+#endif
+
+/* ---------------------------------------- */
+
+/* structure to support output icc B2A Lut initialisation calbacks. */
+/* Note that we don't cope with a LUT matrix - assume it's unity. */
+
+typedef struct {
+ int verb;
+ int total, count, last; /* Progress count information */
+ int noPCScurves; /* Flag set if we don't want PCS curves */
+ int filter; /* Filter clipped values */
+ double filter_thr; /* Clip DE threshold */
+ double filter_ratio; /* Clip DE to radius factor */
+ double filter_maxrad; /* Clip maximum filter radius */
+ icColorSpaceSignature pcsspace; /* The PCS colorspace */
+ icColorSpaceSignature devspace; /* The device colorspace */
+ icxLuLut *x; /* A2B icxLuLut we are inverting in std PCS */
+
+ int ntables; /* Number of tables being set. 1 = colorimetric */
+ /* 2 = colorimetric + saturation, 3 = all intents */
+ int ochan; /* Number of output channels for B2A */
+ gammap *pmap; /* Perceptual CAM to CAM Gamut mapping, NULL if no mapping */
+ gammap *smap; /* Saturation CAM to CAM Gamut mapping, NULL if no mapping */
+ icxLuBase *ixp; /* Source profile perceptual PCS to CAM conversion */
+ icxLuBase *ox; /* Destination profile CAM to std PCS conversion */
+ /* (This is NOT used for the colorimetric B2A table creation!) */
+
+ /* Abstract transform for each table. These may be */
+ /* duplicates. */
+ icRenderingIntent abs_intent[3]; /* Desired abstract profile rendering intent */
+ icxLuBase *abs_luo[3]; /* abstract profile transform in PCS, NULL if none */
+
+ double xyzscale[2]; /* < 1.0 if XYZ is to be scaled in destination space */
+ /* for perceptual [0], and saturation [1] */
+ double swxyz[3]; /* Source white point in XYZ */
+
+ gamut *gam; /* Output gamut object for setting gamut Lut */
+ int wantLab; /* 0 if want is XYZ PCS, 1 want is Lab PCS */
+} out_b2a_callback;
+
+/* Utility to handle abstract profile application to PCS. */
+/* PCS in creating output table is always XYZ or Lab relative colorimetric, */
+/* and abstract profile is absolute or relative, and will be */
+/* XYZ if absolute, and PCS if relative. */
+static void do_abstract(out_b2a_callback *p, int tn, double out[3], double in[3]) {
+ out[0] = in[0];
+ out[1] = in[1];
+ out[2] = in[2];
+
+ if (p->abs_intent[tn] == icAbsoluteColorimetric) {
+ if (p->pcsspace == icSigLabData) {
+ icmLab2XYZ(&icmD50, out, out);
+ }
+ p->x->plu->XYZ_Rel2Abs(p->x->plu, out, out);
+ }
+
+ p->abs_luo[tn]->lookup(p->abs_luo[tn], out, out);
+
+ if (p->abs_intent[tn] == icAbsoluteColorimetric) {
+ p->x->plu->XYZ_Abs2Rel(p->x->plu, out, out);
+ if (p->pcsspace == icSigLabData) {
+ icmXYZ2Lab(&icmD50, out, out);
+ }
+ }
+}
+
+/* --------------------------------------------------------- */
+
+/* Extra non-linearity applied to BtoA XYZ PCS. */
+/* This distributes the LUT indexes more evenly in */
+/* perceptual space, greatly improving the B2A accuracy of XYZ LUT */
+/* To better use the full range of the grid, and also to make sure */
+/* that the white point gets mapped accurately, scale the XYZ to put */
+/* the D50 white at the top corner of the grid. */
+
+/* Y to L* */
+static void y2l_curve(double *out, double *in) {
+ int i;
+ double sc = 65535.0/32768.0; /* Scale from 0.0 .. 1.999969 to 0.0 .. 1.0 and back */
+ double val;
+
+ for (i = 0; i < 3; i++) {
+ val = in[i];
+ val /= icmD50_ary3[i]; /* Put white at top of grid and scale */
+ if (val > 0.008856451586)
+ val = 1.16 * pow(val,1.0/3.0) - 0.16;
+ else
+ val = 9.032962896 * val;
+ if (val > 1.0)
+ val = 1.0;
+ val *= sc; /* Unscale */
+ out[i] = val;
+ }
+}
+
+/* L* to Y */
+static void l2y_curve(double *out, double *in) {
+ int i;
+ double sc = 65535.0/32768.0; /* Scale from 0.0 .. 1.999969 to 0.0 .. 1.0 and back */
+ double val;
+
+ /* Use an L* like curve, scaled to the maximum XYZ value */
+ for (i = 0; i < 3; i++) {
+ val = in[i];
+ val /= sc; /* Scale */
+ if (val > 0.08)
+ val = pow((val + 0.16)/1.16, 3.0);
+ else
+ val = val/9.032962896;
+ val *= icmD50_ary3[i]; /* Unscale and put white at top of grid */
+ out[i] = val;
+ }
+}
+
+/* --------------------------------------------------------- */
+
+/* sRGB device gamma encoded value to linear value 0.0 .. 1.0 */
+static double gdv2dv(double iv) {
+ double ov;
+
+ if (iv < 0.04045)
+ ov = iv/12.92;
+ else
+ ov = pow((iv + 0.055)/1.055, 2.4);
+ return ov;
+}
+
+
+/* --------------------------------------------------------- */
+/* NOTE :- the assumption that each stage of the BtoA is a mirror */
+/* of the AtoB makes for inflexibility. */
+/* Perhaps it would be better to remove this asumption from the */
+/* out_b2a_clut processing ? */
+/* To do this we then need inv_out_b2a_input(), and */
+/* inv_out_b2a_output(), and we need to clearly distinguish between */
+/* AtoB PCS' & DEV', and BtoA PCS' & DEV', since they are not */
+/* necessarily the same... */
+
+
+/* B2A Input table is the inverse of the AtoB output table */
+/* Input PCS output PCS'' */
+void out_b2a_input(void *cntx, double out[3], double in[3]) {
+ out_b2a_callback *p = (out_b2a_callback *)cntx;
+
+ DBG(("out_b2a_input got PCS %f %f %f\n",in[0],in[1],in[2]))
+
+ /* PCS to PCS' */
+ if (p->noPCScurves) {
+ out[0] = in[0];
+ out[1] = in[1];
+ out[2] = in[2];
+ } else {
+ if (p->x->inv_output(p->x, out, in) > 1)
+ error("%d, %s",p->x->pp->errc,p->x->pp->err);
+ }
+ /* PCS' to PCS'' */
+ if (p->pcsspace == icSigXYZData) /* Apply XYZ non-linearity curve */
+ y2l_curve(out, out);
+
+ DBG(("out_b2a_input returning PCS'' %f %f %f\n",out[0],out[1],out[2]))
+}
+
+/* clut - multitable */
+/* Input PCS' output Dev' */
+/* We're applying any abstract profile after gamut mapping, */
+/* on the assumption is is primarily being used to "correct" the */
+/* output device. Ideally the gamut mapping should take the change */
+/* the abstract profile has on the output device into account, but */
+/* currently we're not doing this.. */
+/* If ICM_CLUT_SET_FILTER is being used, then */
+/* we need to set a filter radius value at out[-1-table] */
+void out_b2a_clut(void *cntx, double *out, double in[3]) {
+ out_b2a_callback *p = (out_b2a_callback *)cntx;
+ double inn[3], in1[3];
+ double cdist = 0.0; /* Clipping DE */
+ int tn;
+
+ DBG(("\nout_b2a_clut got PCS'' %f %f %f\n",in[0],in[1],in[2]))
+
+ in1[0] = in[0]; /* in[] may be aliased with out[] */
+ in1[1] = in[1]; /* so take a copy. */
+ in1[2] = in[2]; /* (If we were using "index-under", we should copy it too) */
+
+ DBG(("out_b2a_clut got PCS' %f %f %f\n",in[0],in[1],in[2]))
+
+ if (p->pcsspace == icSigXYZData) /* Undo effects of extra XYZ non-linearity curve */
+ l2y_curve(in1, in1);
+
+ inn[0] = in1[0]; /* Copy of PCS' for 2nd and 3rd tables */
+ inn[1] = in1[1];
+ inn[2] = in1[2];
+
+ if (p->abs_luo[0] != NULL) { /* Abstract profile to apply to first table only. */
+
+ if (!p->noPCScurves) { /* Convert from PCS' to PCS so we can apply abstract */
+ if (p->x->output(p->x, in1, in1) > 1)
+ error("%d, %s",p->x->pp->errc,p->x->pp->err);
+ }
+ do_abstract(p, 0, in1, in1); /* Abstract profile to apply to first table */
+ DBG(("through abstract prof PCS %f %f %f\n",in1[0],in1[1],in1[2]))
+ /* We now have PCS */
+ }
+
+ if (p->noPCScurves || p->abs_luo[0] != NULL) { /* We were given PCS or have converted to PCS */
+
+ /* PCS to PCS' */
+ if (p->x->inv_output(p->x, in1, in1) > 1)
+ error("%d, %s",p->x->pp->errc,p->x->pp->err);
+
+ DBG(("noPCScurves = %d, abs_luo[0] = 0x%x\n",p->noPCScurves,p->abs_luo[0]))
+ DBG(("convert PCS to PCS' got %f %f %f\n",in1[0],in1[1],in1[2]))
+ }
+
+ /* Invert AtoB clut (PCS' to Dev') Colorimetric */
+ /* to producte the colorimetric tables output. */
+ /* (Note that any aux target if we were using one, would be in Dev space) */
+ if (p->x->inv_clut_aux(p->x, out, NULL, NULL, NULL, &cdist, in1) > 1)
+ error("%d, %s",p->x->pp->errc,p->x->pp->err);
+ if (p->filter) {
+ cdist -= p->filter_thr;
+ if (cdist < 0.0)
+ cdist = 0.0;
+ cdist *= p->filter_ratio;
+ if (cdist > p->filter_maxrad)
+ cdist = p->filter_maxrad;
+ out[-1-0] = cdist;
+ }
+
+ DBG(("convert PCS' to DEV' got %s\n",icmPdv(p->ochan, out)))
+
+#ifdef COMPARE_INV_CLUT /* Compare the inversion result with the fwd lookup */
+ {
+ double chk[3];
+
+ if (p->x->clut(p->x, chk, out) > 1)
+ error("%d, %s",p->x->pp->errc,p->x->pp->err);
+
+ DBG(("check DEV' to PCS' got %f %f %f\n",chk[0],chk[1],chk[2]))
+ }
+#endif
+ if (p->ntables > 1) { /* Do first part once for both intents */
+ double *tnout = out; /* This tables output values */
+
+ DBG(("\n"))
+
+ /* Starting with original input inn[] PCS' (no abstract profile applied to other tables) */
+ in1[0] = inn[0];
+ in1[1] = inn[1];
+ in1[2] = inn[2];
+
+ if (!p->noPCScurves) { /* Convert from PCS' to PCS */
+ if (p->x->output(p->x, in1, in1) > 1)
+ error("%d, %s",p->x->pp->errc,p->x->pp->err);
+ }
+ DBG(("convert PCS' to PCS got %f %f %f\n",in1[0],in1[1],in1[2]))
+
+ /* Convert from PCS to CAM/Gamut mapping space */
+ p->ixp->fwd_relpcs_outpcs(p->ixp, p->pcsspace, in1, in1);
+
+ DBG(("convert PCS to CAM got %f %f %f\n",in1[0],in1[1],in1[2]))
+
+ /* Apply gamut mapping in CAM space for remaining tables */
+ /* and create the output values */
+ for (tn = 1; tn < p->ntables; tn++) {
+ double in2[3];
+
+ tnout += p->ochan; /* next table/intent */
+ in2[0] = in1[0]; /* Copy in1[] so it can be used for both tables */
+ in2[1] = in1[1];
+ in2[2] = in1[2];
+
+ /* Do luminence scaling if requested */
+ if (p->xyzscale[tn-1] < 1.0) {
+ double xyz[3];
+
+ DBG(("got xyzscale = %f\n",p->xyzscale[tn-1]))
+ DBG(("PCS %f %f %f\n",in2[0], in2[1], in2[2]))
+ /* Convert our CAM to XYZ */
+ /* We're being bad in delving inside the xluo, but we'll fix it latter */
+ p->ox->cam->cam_to_XYZ(p->ox->cam, xyz, in2);
+
+ DBG(("XYZ %f %f %f\n",xyz[0], xyz[1], xyz[2]))
+ /* Scale it */
+ xyz[0] *= p->xyzscale[tn-1];
+ xyz[1] *= p->xyzscale[tn-1];
+ xyz[2] *= p->xyzscale[tn-1];
+
+ DBG(("scaled XYZ %f %f %f\n",xyz[0], xyz[1], xyz[2]))
+ /* Convert back to CAM */
+ /* We're being bad in delving inside the xluo, but we'll fix it latter */
+ p->ox->cam->XYZ_to_cam(p->ox->cam, in2, xyz);
+
+ DBG(("scaled PCS %f %f %f\n",in2[0], in2[1], in2[2]))
+ }
+
+ if (tn == 1) {
+ DBG(("percep gamut map in %f %f %f\n",in2[0],in2[1],in2[2]))
+ p->pmap->domap(p->pmap, in2, in2); /* Perceptual mapping */
+ DBG(("percep gamut map out %f %f %f\n",in2[0],in2[1],in2[2]))
+ } else {
+ DBG(("sat gamut map in %f %f %f\n",in2[0],in2[1],in2[2]))
+ p->smap->domap(p->smap, in2, in2); /* Saturation mapping */
+ DBG(("sat gamut map got %f %f %f\n",in2[0],in2[1],in2[2]))
+ }
+
+ /* Convert from Gamut maping/CAM space to PCS */
+ p->ox->bwd_outpcs_relpcs(p->ox, p->pcsspace, in2, in2);
+ DBG(("convert CAM to PCS got %f %f %f\n",in2[0],in2[1],in2[2]))
+
+ if (p->abs_luo[tn] != NULL) /* Abstract profile to other tables after gamut map */
+ do_abstract(p, tn, in2, in2);
+
+ /* Convert from PCS to PCS' */
+ if (p->x->inv_output(p->x, in2, in2) > 1)
+ error("%d, %s",p->x->pp->errc,p->x->pp->err);
+ DBG(("convert PCS to PCS' got %f %f %f\n",in2[0],in2[1],in2[2]))
+
+ /* Invert AtoB clut (PCS' to Dev') */
+ /* to producte the perceptual or saturation tables output. */
+ /* (Note that any aux target if we were using one, would be in Dev space) */
+ if (p->x->inv_clut_aux(p->x, tnout, NULL, NULL, NULL, &cdist, in2) > 1)
+ error("%d, %s",p->x->pp->errc,p->x->pp->err);
+ if (p->filter) {
+ cdist -= p->filter_thr;
+ if (cdist < 0.0)
+ cdist = 0.0;
+ cdist *= p->filter_ratio;
+ if (cdist > p->filter_maxrad)
+ cdist = p->filter_maxrad;
+ out[-1-tn] = cdist;
+ }
+ DBG(("convert PCS' to DEV' got %s\n",icmPdv(p->ochan, tnout)))
+ }
+ }
+
+ DBG(("out_b2a_clut returning DEV' %s\n",icmPdv(p->ochan, out)))
+
+ if (p->verb) { /* Output percent intervals */
+ int pc;
+ p->count++;
+ pc = (int)(p->count * 100.0/p->total + 0.5);
+ if (pc != p->last) {
+ printf("%c%2d%%",cr_char,pc); fflush(stdout);
+ p->last = pc;
+ }
+ }
+}
+
+/* Output table is the inverse of the AtoB input table */
+/* Input Dev' output Dev */
+void out_b2a_output(void *cntx, double out[4], double in[4]) {
+ out_b2a_callback *p = (out_b2a_callback *)cntx;
+
+ DBG(("out_b2a_output got DEV' %s\n",icmPdv(p->ochan,in)))
+
+ if (p->x->inv_input(p->x, out, in) > 1)
+ error("%d, %s",p->x->pp->errc,p->x->pp->err);
+
+ DBG(("out_b2a_output returning DEV %s\n",icmPdv(p->ochan,out)))
+}
+
+/* --------------------------------------------------------- */
+
+/* PCS' -> distance to gamut boundary */
+static void PCSp_bdist(void *cntx, double out[1], double in[3]) {
+ out_b2a_callback *p = (out_b2a_callback *)cntx;
+ double pcs[3]; /* PCS value of input */
+ double nrad; /* normalised radial value of point */
+ double np[3]; /* Nearest point */
+ double gdist; /* Out of gamut distance */
+
+//printf("~1 bdist got PCS %f %f %f\n",in[0],in[1],in[2]);
+ /* Do PCS' -> PCS */
+ if (p->x->inv_output(p->x, pcs, in) > 1)
+ error("%d, %s",p->x->pp->errc,p->x->pp->err);
+
+ /* If PCS is XYZ, convert to Lab */
+ if (p->wantLab == 0) {
+ icmXYZ2Lab(&icmD50, pcs, pcs);
+//printf("~1 bdist converted to Lab %f %f %f\n",pcs[0],pcs[1],pcs[2]);
+ }
+
+ /* Check if this point is in or out of gamut */
+ nrad = p->gam->nradial(p->gam, np, pcs);
+
+//printf("~1 nrad = %f\n",nrad);
+
+ /* Radial rather than nearest distance seems the best overall. */
+
+ gdist = icmNorm33(np, pcs);
+ if (nrad <= 1.0)
+ gdist = -gdist; /* -ve delta E if within gamut */
+
+//printf("~1 gdist %f\n",gdist);
+
+ /* Distance in PCS space will be roughly -128 -> 128 */
+ /* Clip to range -20 - +20, then scale to 0.0 - 1.0 */
+ if (gdist < -20.0)
+ gdist = -20.0;
+ else if (gdist > 20.0)
+ gdist = 20.0;
+
+ out[0] = (gdist + 20.0)/40.0;
+//printf("~1 bdist returning %f\n",out[0]);
+
+ if (p->verb) { /* Output percent intervals */
+ int pc;
+ p->count++;
+ pc = (int)(p->count * 100.0/p->total + 0.5);
+ if (pc != p->last) {
+ printf("%c%2d%%",cr_char,pc); fflush(stdout);
+ p->last = pc;
+ }
+ }
+}
+
+/* The output table for a Gamut lut is usually a special function, returning */
+/* a value of 0 for all inputs <= 0.5, and then outputing between */
+/* 0.0 and 1.0 for the input range 0.5 to 1.0. This is so a graduated */
+/* "gamut boundary distance" number from the multi-d lut can be */
+/* translated into the ICC "0.0 if in gamut, > 0.0 if not" number. */
+static void gamut_output(void *cntx, double out[1], double in[1]) {
+ double iv, ov;
+ iv = in[0];
+ if (iv <= 0.5)
+ ov = 0.0;
+ else
+ ov = (iv - 0.5) * 2.0;
+ out[0] = ov;
+}
+
+/* -------------------------------------------------------------- */
+/* powell() callback to set XYZ white scaling factor for */
+/* Absolute Appearance mode with scaling intent */
+
+static double xyzoptfunc(void *cntx, double *v) {
+ out_b2a_callback *p = (out_b2a_callback *)cntx;
+ double swxyz[3], jab[3], dev[MAX_CHAN];
+ double rv;
+ int rc;
+
+ rv = 2.0 - v[0]; /* Make Y as large as possible */
+
+ /* If we wanted to use this function to maximise the brightness */
+ /* we would not limit the scale to 1.0 */
+ if (v[0] > 1.0) {
+ rv += 1000.0;
+ return rv;
+ }
+ if (v[0] < 0.0) {
+ rv += 100.0;
+ return rv;
+ }
+ swxyz[0] = v[0] * p->swxyz[0];
+ swxyz[1] = v[0] * p->swxyz[1];
+ swxyz[2] = v[0] * p->swxyz[2];
+
+//printf("~1 scaled white XYZ = %f %f %f\n", swxyz[0], swxyz[1], swxyz[2]);
+
+ /* We're being bad in delving inside the xluo, but we'll fix it latter */
+ p->ox->cam->XYZ_to_cam(p->ox->cam, jab, swxyz);
+
+//printf("~1 scaled white Jab = %f %f %f\n", jab[0], jab[1], jab[2]);
+
+ /* Run the target PCS backwards through the output space to see if it clips */
+ rc = p->ox->inv_lookup(p->ox, dev, jab);
+//printf("~1 device = %f %f %f, rc = %d\n", dev[0], dev[1], dev[2],rc);
+ if (rc != 0)
+ rv += 500.0;
+
+//printf("~1 xyzoptfunc rv %f from xyzscale %f\n\n",rv,v[0]);
+ return rv;
+}
+
+/* -------------------------------------------------------------- */
+/* Make an output device profile, where a forward mapping is from */
+/* RGB/CMYK to XYZ/Lab space */
+void
+make_output_icc(
+ prof_atype ptype, /* Profile algorithm type */
+ int mtxtoo, /* NZ if matrix tags should be created for Display XYZ cLUT */
+ icmICCVersion iccver, /* ICC profile version to create */
+ int verb, /* Vebosity level, 0 = none */
+ int iquality, /* A2B table quality, 0..3 */
+ int oquality, /* B2A table quality, 0..3 */
+ int noisluts, /* nz to supress creation of input (Device) shaper luts */
+ int noipluts, /* nz to supress creation of input (Device) position luts */
+ int nooluts, /* nz to supress creation of output (PCS) shaper luts */
+ int nocied, /* nz to supress inclusion of .ti3 data in profile */
+ int noptop, /* nz to use colorimetic source gamut to make perceptual table */
+ int nostos, /* nz to use colorimetic source gamut to make saturation table */
+ int gamdiag, /* Make gamut mapping diagnostic wrl plots */
+ int verify, /* nz to print verification */
+ int clipprims, /* Clip white, black and primaries */
+ icxInk *oink, /* Ink limit/black generation setup (NULL if n/a) */
+ char *in_name, /* input .ti3 file name */
+ char *file_name, /* output icc name */
+ cgats *icg, /* input cgats structure */
+ int spec, /* Use spectral data flag */
+ icxIllumeType tillum, /* Target/simulated instrument illuminant */
+ xspect *cust_tillum, /* Possible custom target/simulated instrument illumination */
+ icxIllumeType illum, /* Spectral illuminant */
+ xspect *cust_illum, /* Possible custom illumination */
+ icxObserverType observ, /* Spectral observer */
+ int fwacomp, /* FWA compensation requested */
+ double smooth, /* RSPL smoothing factor, -ve if raw */
+ double avgdev, /* reading Average Deviation as a proportion of the input range */
+ char *ipname, /* input icc profile - enables gamut map, NULL if none */
+ char *sgname, /* source image gamut - NULL if none */
+ char *absname[3], /* abstract profile name for each table */
+ int sepsat, /* Create separate Saturation B2A */
+ icxViewCond *ivc_p, /* Input Viewing Parameters for CAM */
+ icxViewCond *ovc_p, /* Output Viewing Parameters for CAM */
+ int ivc_e, /* Input Enumerated viewing condition */
+ int ovc_e, /* Output Enumerated viewing condition */
+ icxGMappingIntent *pgmi,/* Perceptual gamut mapping intent */
+ icxGMappingIntent *sgmi,/* Saturation gamut mapping intent */
+ profxinf *xpi /* Optional Profile creation extra data */
+) {
+ int isdisp; /* nz if this is a display device, 0 if output */
+ double dispLuminance = 0.0; /* Display luminance. 0 if not known */
+ int isdnormed = 0; /* Has display data been normalised to 100 ? */
+ int allintents; /* nz if all intents should possibly be created */
+ icmFile *wr_fp;
+ icc *wr_icco;
+ int npat; /* Number of patches */
+ cow *tpat; /* Patch input values */
+ int i, j, rv = 0;
+ icColorSpaceSignature devspace = icmSigDefaultData; /* The device colorspace */
+ inkmask imask = 0; /* Device inkmask */
+ int isAdditive = 0; /* 0 if subtractive, 1 if additive colorspace */
+ int isLab = 0; /* 0 if input is XYZ, 1 if input is Lab */
+ int wantLab = 0; /* 0 if want XYZ, 1 want Lab */
+ int isLut = 0; /* 0 if shaper+ matrix, 1 if lut type */
+ int isShTRC = 0; /* 0 if separate gamma/shaper TRC, 1 if shared */
+ int devchan = 0; /* Number of device chanels */
+ int a2binres = 0; /* A2B input (device) table resolution */
+ int a2bres = 0; /* A2B clut resolution */
+ int a2boutres = 0; /* A2B output (PCS) table resolution */
+ int b2ainres = 0; /* B2A input (PCS) table resolution */
+ int b2ares = 0; /* B2A clut resolution */
+ int b2aoutres = 0; /* B2A output (device) table resolution */
+ xcal *cal = NULL; /* Calibration if present, NULL if none */
+ icxInk iink; /* Source profile ink limit values */
+
+ memset((void *)&iink, 0, sizeof(icxInk));
+ iink.tlimit = -1.0; /* default to unknown */
+ iink.klimit = -1.0;
+
+ if (ptype == prof_clutLab) { /* Lab lut */
+ wantLab = 1;
+ isLut = 1;
+ } else if (ptype == prof_clutXYZ) { /* XYZ lut */
+ wantLab = 0;
+ isLut = 1;
+ } else {
+ wantLab = 0; /* gamma/shaper + matrix profile must be XYZ */
+ isLut = 0;
+
+ if (ptype == prof_gam1mat
+ || ptype == prof_sha1mat
+ || ptype == prof_matonly) {
+ isShTRC = 1; /* Single curve */
+ }
+ }
+
+ {
+ int ti;
+
+ if ((ti = icg->find_kword(icg, 0, "DEVICE_CLASS")) < 0)
+ error ("Input file doesn't contain keyword DEVICE_CLASS");
+
+ if (strcmp(icg->t[0].kdata[ti],"DISPLAY") == 0) {
+ isdisp = 1;
+ if (isLut && ipname != NULL)
+ allintents = 1;
+ else
+ allintents = 0; /* Only the default intent */
+ } else {
+ isdisp = 0;
+ allintents = 1;
+ }
+ }
+
+ /* See if display luminance data is present */
+ if (isdisp) {
+ int ti;
+
+ if ((ti = icg->find_kword(icg, 0, "LUMINANCE_XYZ_CDM2")) >= 0) {
+ if (sscanf(icg->t[0].kdata[ti], " %*lf %lf %*lf ",&dispLuminance) != 1)
+ dispLuminance = 0.0;
+ }
+
+ /* See if the CIE data has been normalised to Y = 100 */
+ if ((ti = icg->find_kword(icg, 0, "NORMALIZED_TO_Y_100")) < 0
+ || strcmp(icg->t[0].kdata[ti],"NO") == 0) {
+ isdnormed = 0;
+ } else {
+ isdnormed = 1;
+ }
+ }
+
+ /* Figure out what sort of device colorspace it is */
+ {
+ int ti;
+
+ if ((ti = icg->find_kword(icg, 0, "COLOR_REP")) < 0)
+ error("Input file doesn't contain keyword COLOR_REPS");
+
+ if (strcmp(icg->t[0].kdata[ti],"CMYK_XYZ") == 0) {
+ imask = ICX_CMYK;
+ devspace = icSigCmykData;
+ devchan = 4;
+ isLab = 0;
+ isAdditive = 0;
+ } else if (strcmp(icg->t[0].kdata[ti],"CMYK_LAB") == 0) {
+ imask = ICX_CMYK;
+ devspace = icSigCmykData;
+ devchan = 4;
+ isLab = 1;
+ isAdditive = 0;
+ } else if (strcmp(icg->t[0].kdata[ti],"CMY_XYZ") == 0) {
+ imask = ICX_CMY;
+ devspace = icSigCmyData;
+ devchan = 3;
+ isLab = 0;
+ isAdditive = 0;
+ } else if (strcmp(icg->t[0].kdata[ti],"CMY_LAB") == 0) {
+ imask = ICX_CMY;
+ devspace = icSigCmyData;
+ devchan = 3;
+ isLab = 1;
+ isAdditive = 0;
+ } else if (strcmp(icg->t[0].kdata[ti],"RGB_XYZ") == 0) {
+ imask = ICX_RGB;
+ devspace = icSigRgbData;
+ devchan = 3;
+ isLab = 0;
+ isAdditive = 1;
+ } else if (strcmp(icg->t[0].kdata[ti],"RGB_LAB") == 0) {
+ imask = ICX_RGB;
+ devspace = icSigRgbData;
+ devchan = 3;
+ isLab = 1;
+ isAdditive = 1;
+ } else if ( strcmp(icg->t[0].kdata[ti],"iRGB_XYZ") == 0) {
+ imask = ICX_IRGB;
+ devspace = icSigRgbData;
+ devchan = 3;
+ isLab = 0;
+ isAdditive = 1;
+ } else if (strcmp(icg->t[0].kdata[ti],"iRGB_LAB") == 0) {
+ imask = ICX_IRGB;
+ devspace = icSigRgbData;
+ devchan = 3;
+ isLab = 1;
+ isAdditive = 1;
+#ifdef IMP_MONO
+ } else if (strcmp(icg->t[0].kdata[ti],"K_XYZ") == 0) {
+ imask = ICX_K;
+ devspace = icSigGrayData;
+ devchan = 1;
+ isLab = 0;
+ isAdditive = 0;
+ } else if (strcmp(icg->t[0].kdata[ti],"K_LAB") == 0) {
+ imask = ICX_K;
+ devspace = icSigGrayData;
+ devchan = 1;
+ isLab = 1;
+ isAdditive = 0;
+ } else if (strcmp(icg->t[0].kdata[ti],"W_XYZ") == 0) {
+ imask = ICX_W;
+ devspace = icSigGrayData;
+ devchan = 1;
+ isLab = 0;
+ isAdditive = 1;
+ } else if (strcmp(icg->t[0].kdata[ti],"W_LAB") == 0) {
+ imask = ICX_W;
+ devspace = icSigGrayData;
+ devchan = 1;
+ isLab = 1;
+ isAdditive = 1;
+#endif /* IMP_MONO */
+ } else
+ error("Output device input file has unhandled color representation '%s'",
+ icg->t[0].kdata[ti]);
+ /* Figure out some suitable table sizes */
+ if (devchan >= 4) { /* devchan == 4 or greater */
+ if (iquality >= 3) {
+ a2binres = 2048;
+ a2bres = 23;
+ a2boutres = 2048;
+ } else if (iquality == 2) {
+ a2binres = 2048;
+ a2bres = 17;
+ a2boutres = 2048;
+ } else if (iquality == 1) {
+ a2binres = 1024;
+ a2bres = 9;
+ a2boutres = 1024;
+ } else {
+ a2binres = 512;
+ a2bres = 5;
+ a2boutres = 512;
+ }
+ } else if (devchan >= 2) { /* devchan == 2 or 3 */
+ if (iquality >= 3) {
+ a2binres = 2048;
+ a2bres = 45;
+ a2boutres = 2048;
+ } else if (iquality == 2) {
+ a2binres = 2048;
+ a2bres = 33;
+ a2boutres = 2048;
+ } else if (iquality == 1) {
+ a2binres = 1024;
+ a2bres = 17;
+ a2boutres = 1024;
+ } else {
+ a2binres = 512;
+ a2bres = 9;
+ a2boutres = 512;
+ }
+ } else { /* devchan == 1 */
+ if (iquality >= 3) {
+ a2binres = 256;
+ a2bres = 2048;
+ a2boutres = 256;
+ } else if (iquality == 2) {
+ a2binres = 256;
+ a2bres = 1024;
+ a2boutres = 256;
+ } else if (iquality == 1) {
+ a2binres = 256;
+ a2bres = 512;
+ a2boutres = 256;
+ } else {
+ a2binres = 256;
+ a2bres = 256;
+ a2boutres = 256;
+ }
+ }
+
+ if (devchan >= 2) {
+ if (oquality >= 3) { /* Ultra High */
+ b2ainres = 2048;
+ b2ares = 45;
+ b2aoutres = 2048;
+ } else if (oquality == 2) {
+ b2ainres = 2048;
+ b2ares = 33; /* High */
+ b2aoutres = 2048;
+ } else if (oquality == 1) {
+ b2ainres = 1024;
+ b2ares = 17; /* Medium */
+ b2aoutres = 1024;
+ } else if (oquality >= 0) {
+ b2ainres = 512;
+ b2ares = 9; /* Low */
+ b2aoutres = 512;
+ } else { /* Special, Extremely low quality */
+ b2ainres = 64;
+ b2ares = 3;
+ b2aoutres = 64;
+ }
+ } else { /* devchan == 1 */
+ if (oquality >= 3) { /* Ultra High */
+ b2ainres = 256;
+ b2ares = 3;
+ b2aoutres = 4096;
+ } else if (oquality == 2) {
+ b2ainres = 256;
+ b2ares = 3; /* High */
+ b2aoutres = 2048;
+ } else if (oquality == 1) {
+ b2ainres = 256;
+ b2ares = 3; /* Medium */
+ b2aoutres = 1024;
+ } else if (oquality >= 0) {
+ b2ainres = 256;
+ b2ares = 3; /* Low */
+ b2aoutres = 512;
+ } else { /* Special, Extremely low quality */
+ b2ainres = 64;
+ b2ares = 3;
+ b2aoutres = 64;
+ }
+ }
+
+ }
+
+ /* See if there is a calibration in the .ti3, and read it if there is */
+ {
+ int oi, tab;
+
+ if ((oi = icg->get_oi(icg, "CAL")) < 0)
+ error("Expect CAL type to be registered");
+
+ for (tab = 0; tab < icg->ntables; tab++) {
+ if (icg->t[tab].tt == tt_other && icg->t[tab].oi == oi) {
+ break;
+ }
+ }
+ if (tab < icg->ntables) {
+
+ if ((cal = new_xcal()) == NULL) {
+ error("new_xcal failed");
+ }
+ if (cal->read_cgats(cal, icg, tab, in_name) != 0) {
+ error("%s",cal->err);
+ }
+
+ if (cal->devmask != imask)
+ error("Calibrate colorspace %s doesn't match .ti3 %s",
+ icx_inkmask2char(cal->devmask, 1),
+ icx_inkmask2char(imask, 1));
+
+ if ((isdisp && cal->devclass != icSigDisplayClass)
+ || (!isdisp && cal->devclass != icSigOutputClass))
+ error("Calibration class %s doesn't match .ti3 %s",
+ icm2str(icmProfileClassSignature,cal->devclass),
+ isdisp ? "Display" : "Output");
+ }
+ }
+
+ /* Open up the file for writing */
+ if ((wr_fp = new_icmFileStd_name(file_name,"w")) == NULL)
+ error("Write: Can't open file '%s'",file_name);
+
+ if ((wr_icco = new_icc()) == NULL)
+ error("Write: Creation of ICC object failed");
+
+ /* Add all the tags required */
+
+ /* The header: */
+ {
+ icmHeader *wh = wr_icco->header;
+
+ /* Values that must be set before writing */
+ if (isdisp)
+ wh->deviceClass = icSigDisplayClass;
+ else
+ wh->deviceClass = icSigOutputClass;
+ wh->colorSpace = devspace; /* Device space it is */
+ if (wantLab)
+ wh->pcs = icSigLabData;
+ else
+ wh->pcs = icSigXYZData; /* Must be XYZ for matrix based profile */
+
+ if (xpi->default_ri != icMaxEnumIntent)
+ wh->renderingIntent = xpi->default_ri;
+ else
+ wh->renderingIntent = icRelativeColorimetric;
+
+ /* Values that should be set before writing */
+ if (xpi != NULL && xpi->manufacturer != 0L)
+ wh->manufacturer = xpi->manufacturer;
+ else
+ wh->manufacturer = icmSigUnknownType;
+
+ if (xpi != NULL && xpi->model != 0L)
+ wh->model = xpi->model;
+ else
+ wh->model = icmSigUnknownType;
+
+ /* Values that may be set before writing */
+ if (xpi != NULL && xpi->creator != 0L)
+ wh->creator = xpi->creator;
+
+#ifdef NT
+ wh->platform = icSigMicrosoft;
+#endif
+#ifdef __APPLE__
+ wh->platform = icSigMacintosh;
+#endif
+#if defined(UNIX) && !defined(__APPLE__)
+ wh->platform = icmSig_nix;
+#endif
+
+ if (xpi != NULL && xpi->transparency)
+ wh->attributes.l |= icTransparency;
+ if (xpi != NULL && xpi->matte)
+ wh->attributes.l |= icMatte;
+ if (xpi != NULL && xpi->negative)
+ wh->attributes.l |= icNegative;
+ if (xpi != NULL && xpi->blackandwhite)
+ wh->attributes.l |= icBlackAndWhite;
+ }
+
+ /* mtxtoo only applies to Display cLUT profiles */
+ if (!isLut
+ || wr_icco->header->deviceClass != icSigDisplayClass
+ || wr_icco->header->pcs != icSigXYZData) {
+ mtxtoo = 0;
+ }
+
+ /* Set the version of ICC profile we want */
+ if (isdisp && allintents) {
+ if (iccver < icmVersion2_4) {
+ iccver = icmVersion2_4; /* Need 2.4.0 for Display intents */
+ if (verb)
+ fprintf(verbo,"Bumped ICC version to 2.4.0 to accomodate multiple Display intents\n");
+ }
+ }
+ if (wr_icco->set_version(wr_icco, iccver) != 0)
+ error("set_version failed: %d, %s",wr_icco->errc,wr_icco->err);
+
+ /* Profile Description Tag: */
+ {
+ icmTextDescription *wo;
+ char *dst, dstm[200]; /* description */
+
+ if (xpi != NULL && xpi->profDesc != NULL)
+ dst = xpi->profDesc;
+ else {
+ sprintf(dstm, "This is a Lut style %s - %s Output Profile",
+ devspace == icSigCmykData ? "CMYK" :
+ devspace == icSigCmyData ? "CMY" : "RGB",
+ wantLab ? "Lab" : "XYZ");
+ dst = dstm;
+ }
+
+ if ((wo = (icmTextDescription *)wr_icco->add_tag(
+ wr_icco, icSigProfileDescriptionTag, icSigTextDescriptionType)) == NULL)
+ error("add_tag failed: %d, %s",wr_icco->errc,wr_icco->err);
+
+ wo->size = strlen(dst)+1; /* Allocated and used size of desc, inc null */
+ wo->allocate((icmBase *)wo);/* Allocate space */
+ strcpy(wo->desc, dst); /* Copy the string in */
+ }
+ /* Copyright Tag: */
+ {
+ icmText *wo;
+ char *crt;
+
+ if (xpi != NULL && xpi->copyright != NULL)
+ crt = xpi->copyright;
+ else
+ crt = "Copyright, the creator of this profile";
+
+ if ((wo = (icmText *)wr_icco->add_tag(
+ wr_icco, icSigCopyrightTag, icSigTextType)) == NULL)
+ error("add_tag failed: %d, %s",wr_icco->errc,wr_icco->err);
+
+ wo->size = strlen(crt)+1; /* Allocated and used size of text, inc null */
+ wo->allocate((icmBase *)wo);/* Allocate space */
+ strcpy(wo->data, crt); /* Copy the text in */
+ }
+ /* Device Manufacturers Description Tag: */
+ if (xpi != NULL && xpi->deviceMfgDesc != NULL) {
+ icmTextDescription *wo;
+ char *dst = xpi->deviceMfgDesc;
+
+ if ((wo = (icmTextDescription *)wr_icco->add_tag(
+ wr_icco, icSigDeviceMfgDescTag, icSigTextDescriptionType)) == NULL)
+ error("add_tag failed: %d, %s",wr_icco->errc,wr_icco->err);
+
+ wo->size = strlen(dst)+1; /* Allocated and used size of desc, inc null */
+ wo->allocate((icmBase *)wo);/* Allocate space */
+ strcpy(wo->desc, dst); /* Copy the string in */
+ }
+ /* Model Description Tag: */
+ if (xpi != NULL && xpi->modelDesc != NULL) {
+ icmTextDescription *wo;
+ char *dst = xpi->modelDesc;
+
+ if ((wo = (icmTextDescription *)wr_icco->add_tag(
+ wr_icco, icSigDeviceModelDescTag, icSigTextDescriptionType)) == NULL)
+ error("add_tag failed: %d, %s",wr_icco->errc,wr_icco->err);
+
+ wo->size = strlen(dst)+1; /* Allocated and used size of desc, inc null */
+ wo->allocate((icmBase *)wo);/* Allocate space */
+ strcpy(wo->desc, dst); /* Copy the string in */
+ }
+ /* Display Luminance tag */
+ if (isdisp && dispLuminance > 0.0) {
+ {
+ icmXYZArray *wo;;
+
+ if ((wo = (icmXYZArray *)wr_icco->add_tag(
+ wr_icco, icSigLuminanceTag, icSigXYZArrayType)) == NULL)
+ error("add_tag failed: %d, %s",wr_icco->errc,wr_icco->err);
+
+ wo->size = 1;
+ wo->allocate((icmBase *)wo); /* Allocate space */
+ wo->data[0].X = 0.0; /* Set a default value */
+ wo->data[0].Y = dispLuminance; /* Set a default value */
+ wo->data[0].Z = 0.0; /* Set a default value */
+ }
+ }
+ /* White Point Tag: */
+ {
+ icmXYZArray *wo;
+ /* Note that tag types icSigXYZType and icSigXYZArrayType are identical */
+ if ((wo = (icmXYZArray *)wr_icco->add_tag(
+ wr_icco, icSigMediaWhitePointTag, icSigXYZArrayType)) == NULL)
+ error("add_tag failed: %d, %s",wr_icco->errc,wr_icco->err);
+
+ wo->size = 1;
+ wo->allocate((icmBase *)wo); /* Allocate space */
+ wo->data[0] = icmD50; /* Set a default value - D50 */
+ }
+ /* Black Point Tag: */
+ {
+ icmXYZArray *wo;
+ if ((wo = (icmXYZArray *)wr_icco->add_tag(
+ wr_icco, icSigMediaBlackPointTag, icSigXYZArrayType)) == NULL)
+ error("add_tag failed: %d, %s",wr_icco->errc,wr_icco->err);
+
+ wo->size = 1;
+ wo->allocate((icmBase *)wo); /* Allocate space */
+ wo->data[0] = icmBlack; /* Set a default value - absolute black */
+ }
+
+ /* Colorant Table Tag: */
+ {
+ unsigned int i;
+ icmColorantTable *wo;
+ if ((wo = (icmColorantTable *)wr_icco->add_tag(
+ wr_icco, icSigColorantTableTag, icSigColorantTableType)) == NULL)
+ error("add_tag failed: %d, %s",wr_icco->errc,wr_icco->err);
+
+ wo->count = icmCSSig2nchan(devspace);
+ if (wo->count != (unsigned long)icx_noofinks(imask))
+ error("Interna: device colorspace and inkmask conflict!");
+
+ wo->allocate((icmBase *)wo); /* Allocate ColorantTable structures */
+
+ for (i = 0; i < wo->count; i++) {
+ inkmask iimask; /* Individual ink mask */
+ char *name;
+
+ iimask = icx_index2ink(imask, i);
+ name = icx_ink2string(iimask);
+ if (strlen(name) > 31)
+ error("Internal: colorant name exceeds 31 characters");
+ strcpy(wo->data[i].name, name);
+ }
+ /* Fill in the colorant PCS values when we've got something to lookup */
+ }
+
+ /* vcgt tag */
+ if (verb & isdisp && cal != NULL && cal->noramdac) { /* We've been given vcgt information */
+ fprintf(verbo,"Not writing calibration to 'vcgt' because there is no VideoLUT access\n");
+ }
+ if (isdisp && cal != NULL && !cal->noramdac) { /* We've been given vcgt information */
+ int j, i;
+ int ncal = 256; /* This is safe with other s/w */
+ icmVideoCardGamma *wo;
+ wo = (icmVideoCardGamma *)wr_icco->add_tag(wr_icco,
+ icSigVideoCardGammaTag, icSigVideoCardGammaType);
+ if (wo == NULL)
+ error("add_tag failed: %d, %s",wr_icco->errc,wr_icco->err);
+
+ wo->tagType = icmVideoCardGammaTableType;
+ wo->u.table.channels = 3; /* rgb */
+ wo->u.table.entryCount = ncal; /* full lut */
+ wo->u.table.entrySize = 2; /* 16 bits */
+ wo->allocate((icmBase*)wo);
+ for (j = 0; j < 3; j++) {
+ for (i = 0; i < ncal; i++) {
+ double cc, vv = i/(ncal - 1.0);
+ cc = cal->interp_ch(cal, j, vv);
+ if (cc < 0.0)
+ cc = 0.0;
+ else if (cc > 1.0)
+ cc = 1.0;
+ ((unsigned short*)wo->u.table.data)[ncal * j + i] = (int)(cc * 65535.0 + 0.5);
+ }
+ }
+ }
+
+ if (isLut) { /* Lut type profile */
+
+ /* Up to and including ICC Version 2.3, Display LUT profiles were assumed */
+ /* to have AtoB0 with no interpretation of the intent, which */
+ /* implies Relative Colorimetric. */
+
+ /* 16 bit dev -> pcs lut: (A2B) */
+ {
+ icmLut *wo;
+
+ if (!allintents) { /* Only A2B0, no intent */
+ if ((wo = (icmLut *)wr_icco->add_tag(
+ wr_icco, icSigAToB0Tag, icSigLut16Type)) == NULL)
+ error("add_tag failed: %d, %s",wr_icco->errc,wr_icco->err);
+ } else {
+ /* Intent 1 = relative colorimetric */
+ if ((wo = (icmLut *)wr_icco->add_tag(
+ wr_icco, icSigAToB1Tag, icSigLut16Type)) == NULL)
+ error("add_tag failed: %d, %s",wr_icco->errc,wr_icco->err);
+ }
+
+ wo->inputChan = devchan;
+ wo->outputChan = 3;
+ wo->clutPoints = a2bres;
+ wo->inputEnt = a2binres;
+ wo->outputEnt = a2boutres;
+ wo->allocate((icmBase *)wo);/* Allocate space */
+
+ /* icxLuLut will set tables values */
+ }
+
+ if (allintents) { /* All the intents may be needed */
+
+ /* 16 bit dev -> pcs lut - link intent 0 to intent 1 */
+ {
+ icmLut *wo;
+ /* Intent 0 = perceptual */
+ if ((wo = (icmLut *)wr_icco->link_tag(
+ wr_icco, icSigAToB0Tag, icSigAToB1Tag)) == NULL)
+ error("link_tag failed: %d, %s",wr_icco->errc,wr_icco->err);
+ }
+
+ /* 16 dev -> pcs bit lut - link intent 2 to intent 1 */
+ {
+ icmLut *wo;
+ /* Intent 2 = saturation */
+ if ((wo = (icmLut *)wr_icco->link_tag(
+ wr_icco, icSigAToB2Tag, icSigAToB1Tag)) == NULL)
+ error("link_tag failed: %d, %s",wr_icco->errc,wr_icco->err);
+ }
+ }
+
+ /* 16 bit pcs -> dev lut: (B2A) */
+ {
+ icmLut *wo;
+
+ if (!allintents) { /* Only B2A0, no intent */
+ if ((wo = (icmLut *)wr_icco->add_tag(
+ wr_icco, icSigBToA0Tag, icSigLut16Type)) == NULL)
+ error("add_tag failed: %d, %s",wr_icco->errc,wr_icco->err);
+
+ } else {
+
+ /* Intent 1 = relative colorimetric */
+ if ((wo = (icmLut *)wr_icco->add_tag(
+ wr_icco, icSigBToA1Tag, icSigLut16Type)) == NULL)
+ error("add_tag failed: %d, %s",wr_icco->errc,wr_icco->err);
+ }
+
+ wo->inputChan = 3;
+ wo->outputChan = devchan;
+ wo->clutPoints = b2ares;
+ wo->inputEnt = b2ainres;
+ wo->outputEnt = b2aoutres;
+ wo->allocate((icmBase *)wo);/* Allocate space */
+ }
+
+ if (allintents) { /* All the intents may be needed */
+
+ if (ipname == NULL) { /* No gamut mapping */
+ icmLut *wo;
+
+ /* link intent 0 = perceptual to intent 1 = colorimetric */
+ if ((wo = (icmLut *)wr_icco->link_tag(
+ wr_icco, icSigBToA0Tag, icSigBToA1Tag)) == NULL)
+ error("link_tag failed: %d, %s",wr_icco->errc,wr_icco->err);
+
+ /* Intent 2 = saturation */
+ /* link intent 2 = saturation to intent 1 = colorimetric */
+ if ((wo = (icmLut *)wr_icco->link_tag(
+ wr_icco, icSigBToA2Tag, icSigBToA1Tag)) == NULL)
+ error("link_tag failed: %d, %s",wr_icco->errc,wr_icco->err);
+
+ } else { /* We have gamut mapping */
+ icmLut *wo;
+
+ /* Intent 0 = perceptual */
+ if ((wo = (icmLut *)wr_icco->add_tag(
+ wr_icco, icSigBToA0Tag, icSigLut16Type)) == NULL)
+ error("add_tag failed: %d, %s",wr_icco->errc,wr_icco->err);
+
+ wo->inputChan = 3;
+ wo->outputChan = devchan;
+ wo->inputEnt = b2ainres;
+ wo->clutPoints = b2ares;
+ wo->outputEnt = b2aoutres;
+ wo->allocate((icmBase *)wo);/* Allocate space */
+
+ if (sepsat == 0) { /* No separate gamut mapping for saturation */
+ /* link intent 2 = saturation to intent 0 = perceptual */
+ if ((wo = (icmLut *)wr_icco->link_tag(
+ wr_icco, icSigBToA2Tag, icSigBToA0Tag)) == NULL)
+ error("link_tag failed: %d, %s",wr_icco->errc,wr_icco->err);
+ } else {
+ /* Intent 2 = saturation */
+ if ((wo = (icmLut *)wr_icco->add_tag(
+ wr_icco, icSigBToA2Tag, icSigLut16Type)) == NULL)
+ error("add_tag failed: %d, %s",wr_icco->errc,wr_icco->err);
+
+ wo->inputChan = 3;
+ wo->outputChan = devchan;
+ wo->inputEnt = b2ainres;
+ wo->clutPoints = b2ares;
+ wo->outputEnt = b2aoutres;
+ wo->allocate((icmBase *)wo);/* Allocate space */
+ }
+ }
+
+ /* 16 bit pcs -> gamut lut: */
+ {
+ icmLut *wo;
+
+ if ((wo = (icmLut *)wr_icco->add_tag(
+ wr_icco, icSigGamutTag, icSigLut16Type)) == NULL)
+ error("add_tag failed: %d, %s",wr_icco->errc,wr_icco->err);
+
+ wo->inputChan = 3;
+ wo->outputChan = 1;
+ wo->inputEnt = 256;
+ wo->clutPoints = b2ares;
+ wo->outputEnt = 256;
+ wo->allocate((icmBase *)wo);/* Allocate space */
+ }
+ }
+ }
+
+ /* shaper + matrix type tags */
+ if (!isLut
+ || ( wr_icco->header->deviceClass == icSigDisplayClass
+ && wr_icco->header->pcs == icSigXYZData)) {
+
+ /* Red, Green and Blue Colorant Tags: */
+ {
+ icmXYZArray *wor, *wog, *wob;
+ if ((wor = (icmXYZArray *)wr_icco->add_tag(
+ wr_icco, icSigRedColorantTag, icSigXYZArrayType)) == NULL)
+ error("add_tag failed: %d, %s",rv,wr_icco->err);
+ if ((wog = (icmXYZArray *)wr_icco->add_tag(
+ wr_icco, icSigGreenColorantTag, icSigXYZArrayType)) == NULL)
+ error("add_tag failed: %d, %s",rv,wr_icco->err);
+ if ((wob = (icmXYZArray *)wr_icco->add_tag(
+ wr_icco, icSigBlueColorantTag, icSigXYZArrayType)) == NULL)
+ error("add_tag failed: %d, %s",rv,wr_icco->err);
+
+ wor->size = wog->size = wob->size = 1;
+ wor->allocate((icmBase *)wor); /* Allocate space */
+ wog->allocate((icmBase *)wog);
+ wob->allocate((icmBase *)wob);
+
+ wor->data[0].X = 1.0; wor->data[0].Y = 0.0; wor->data[0].Z = 0.0;
+ wog->data[0].X = 0.0; wog->data[0].Y = 1.0; wog->data[0].Z = 0.0;
+ wob->data[0].X = 0.0; wob->data[0].Y = 0.0; wob->data[0].Z = 1.0;
+
+ /* Setup deliberately wrong dummy values (channels rotated). */
+ /* icxMatrix may override override these later */
+ wor->data[0].X = 0.143066; wor->data[0].Y = 0.060608; wor->data[0].Z = 0.714096;
+ wog->data[0].X = 0.436066; wog->data[0].Y = 0.222488; wog->data[0].Z = 0.013916;
+ wob->data[0].X = 0.385147; wob->data[0].Y = 0.716873; wob->data[0].Z = 0.097076;
+ }
+
+ /* Red, Green and Blue Tone Reproduction Curve Tags: */
+ {
+ icmCurve *wor, *wog, *wob;
+ if ((wor = (icmCurve *)wr_icco->add_tag(
+ wr_icco, icSigRedTRCTag, icSigCurveType)) == NULL)
+ error("add_tag failed: %d, %s",rv,wr_icco->err);
+
+ if (isShTRC) { /* Make all TRCs shared */
+ if ((wog = (icmCurve *)wr_icco->link_tag(
+ wr_icco, icSigGreenTRCTag, icSigRedTRCTag)) == NULL)
+ error("link_tag failed: %d, %s",rv,wr_icco->err);
+ if ((wob = (icmCurve *)wr_icco->link_tag(
+ wr_icco, icSigBlueTRCTag, icSigRedTRCTag)) == NULL)
+ error("link_tag failed: %d, %s",rv,wr_icco->err);
+
+ } else { /* Else individual */
+ if ((wog = (icmCurve *)wr_icco->add_tag(
+ wr_icco, icSigGreenTRCTag, icSigCurveType)) == NULL)
+ error("add_tag failed: %d, %s",rv,wr_icco->err);
+ if ((wob = (icmCurve *)wr_icco->add_tag(
+ wr_icco, icSigBlueTRCTag, icSigCurveType)) == NULL)
+ error("add_tag failed: %d, %s",rv,wr_icco->err);
+ }
+
+ /* Shaper */
+ if (ptype == prof_shamat || ptype == prof_sha1mat || ptype == prof_clutXYZ) {
+ wor->flag = wog->flag = wob->flag = icmCurveSpec;
+ wor->size = wog->size = wob->size = 256; /* Number of entries */
+ } else { /* Gamma */
+ wor->flag = wog->flag = wob->flag = icmCurveGamma;
+ wor->size = wog->size = wob->size = 1; /* Must be 1 for gamma */
+ }
+ wor->allocate((icmBase *)wor); /* Allocate space */
+ wog->allocate((icmBase *)wog);
+ wob->allocate((icmBase *)wob);
+
+ /* Setup a default sRGB like curve. */
+ /* icxMatrix will may override curve values later */
+ for (i = 0; i < wor->size; i++) {
+ wor->data[i] =
+ wog->data[i] =
+ wob->data[i] = gdv2dv(i/(wor->size-1.0));
+ }
+
+ }
+ }
+ /* .ti3 Sample data use to create profile, plus any calibration curves: */
+ if (nocied == 0) {
+ icmText *wo;
+ char *crt;
+ FILE *fp;
+
+ if ((wo = (icmText *)wr_icco->add_tag(
+ wr_icco, icmMakeTag('t','a','r','g'), icSigTextType)) == NULL)
+ error("add_tag failed: %d, %s",wr_icco->errc,wr_icco->err);
+
+#if defined(O_BINARY) || defined(_O_BINARY)
+ if ((fp = fopen(in_name, "rb")) == NULL)
+#else
+ if ((fp = fopen(in_name, "r")) == NULL)
+#endif
+ error("Unable to open input file '%s' for reading",in_name);
+
+ if (fseek(fp, 0, SEEK_END))
+ error("Unable to seek to end of file '%s'",in_name);
+ wo->size = ftell(fp) + 1; /* Size needed + null */
+ wo->allocate((icmBase *)wo);/* Allocate space */
+
+ if (fseek(fp, 0, SEEK_SET))
+ error("Unable to seek to end of file '%s'",in_name);
+
+ if (fread(wo->data, 1, wo->size-1, fp) != wo->size-1)
+ error("Failed to read file '%s'",in_name);
+ wo->data[wo->size-1] = '\000';
+ fclose(fp);
+
+ /* Duplicate for compatibility */
+ if (wr_icco->link_tag(
+ wr_icco, icmMakeTag('D','e','v','D'), icmMakeTag('t','a','r','g')) == NULL)
+ error("link_tag failed: %d, %s",wr_icco->errc,wr_icco->err);
+ if (wr_icco->link_tag(
+ wr_icco, icmMakeTag('C','I','E','D'), icmMakeTag('t','a','r','g')) == NULL)
+ error("link_tag failed: %d, %s",wr_icco->errc,wr_icco->err);
+ }
+
+ if ((npat = icg->t[0].nsets) <= 0)
+ error("No sets of data");
+
+ if (verb)
+ fprintf(verbo,"No of test patches = %d\n",npat);
+
+ /* Allocate arrays to hold test patch input and output values */
+ if ((tpat = (cow *)malloc(sizeof(cow) * npat)) == NULL)
+ error("Malloc failed - tpat[]");
+
+ /* Read in the CGATs fields */
+ {
+ int ti, ii, ci, mi, yi, ki;
+ int Xi, Yi, Zi;
+
+ /* Read the ink limit */
+ if (oink != NULL && (ii = icg->find_kword(icg, 0, "TOTAL_INK_LIMIT")) >= 0) {
+ double ilimit = -1;
+ ilimit = atof(icg->t[0].kdata[ii]);
+ if (ilimit > 1e-4 && ilimit <= 400.0 && oink->tlimit < 0.0) {
+ oink->tlimit = ilimit/100.0; /* Set requested ink limit */
+ }
+ }
+
+ /* A sanity check */
+ if (isAdditive && oink != NULL && oink->tlimit > 0 && oink->tlimit < (double)devchan) {
+ warning("\n!!!!!!! Additive space has ink limit of %.0f%% set !!!!!!!\n"
+ ">> You probably don't want to do this, as it will limit the white point <<",oink->tlimit * 100.0);
+ }
+
+ /* Should targen/.ti3 file allow for BLACK_INK_LIMIT ?? */
+
+ /* A problem here is that if the .ti3 is corrupted, then */
+ /* often this results in the field type being "wrong", */
+ /* rather than a more inteligable message. */
+
+ if (devspace == icSigGrayData) {
+
+ if (isAdditive) {
+ if ((ci = icg->find_field(icg, 0, "GRAY_W")) < 0)
+ error("Input file doesn't contain field GRAY_W");
+ if (icg->t[0].ftype[ci] != r_t)
+ error("Field GRAY_W is wrong type - corrupted file ?");
+ } else {
+ if ((ci = icg->find_field(icg, 0, "GRAY_K")) < 0)
+ error("Input file doesn't contain field GRAY_K");
+ if (icg->t[0].ftype[ci] != r_t)
+ error("Field GRAY_K is wrong type - corrupted file ?");
+ }
+ mi = yi = ki = ci;
+
+ } else if (devspace == icSigRgbData) {
+
+ if ((ci = icg->find_field(icg, 0, "RGB_R")) < 0)
+ error("Input file doesn't contain field RGB_R");
+ if (icg->t[0].ftype[ci] != r_t)
+ error("Field RGB_R is wrong type - corrupted file ?");
+ if ((mi = icg->find_field(icg, 0, "RGB_G")) < 0)
+ error("Input file doesn't contain field RGB_G");
+ if (icg->t[0].ftype[mi] != r_t)
+ error("Field RGB_G is wrong type - corrupted file ?");
+ if ((yi = icg->find_field(icg, 0, "RGB_B")) < 0)
+ error("Input file doesn't contain field RGB_B");
+ if (icg->t[0].ftype[yi] != r_t)
+ error("Field RGB_B is wrong type - corrupted file ?");
+ ki = yi;
+
+ } else if (devspace == icSigCmyData) {
+
+ if ((ci = icg->find_field(icg, 0, "CMY_C")) < 0)
+ error("Input file doesn't contain field CMY_C");
+ if (icg->t[0].ftype[ci] != r_t)
+ error("Field CMY_C is wrong type - corrupted file ?");
+ if ((mi = icg->find_field(icg, 0, "CMY_M")) < 0)
+ error("Input file doesn't contain field CMY_M");
+ if (icg->t[0].ftype[mi] != r_t)
+ error("Field CMY_M is wrong type - corrupted file ?");
+ if ((yi = icg->find_field(icg, 0, "CMY_Y")) < 0)
+ error("Input file doesn't contain field CMY_Y");
+ if (icg->t[0].ftype[yi] != r_t)
+ error("Field CMY_Y is wrong type - corrupted file ?");
+ ki = yi;
+
+ } else { /* Assume CMYK */
+
+ if ((ci = icg->find_field(icg, 0, "CMYK_C")) < 0)
+ error("Input file doesn't contain field CMYK_C");
+ if (icg->t[0].ftype[ci] != r_t)
+ error("Field CMYK_C is wrong type - corrupted file ?",icg->t[0].ftype[ci],r_t);
+ if ((mi = icg->find_field(icg, 0, "CMYK_M")) < 0)
+ error("Input file doesn't contain field CMYK_M");
+ if (icg->t[0].ftype[mi] != r_t)
+ error("Field CMYK_M is wrong type - corrupted file ?");
+ if ((yi = icg->find_field(icg, 0, "CMYK_Y")) < 0)
+ error("Input file doesn't contain field CMYK_Y");
+ if (icg->t[0].ftype[yi] != r_t)
+ error("Field CMYK_Y is wrong type - corrupted file ?");
+ if ((ki = icg->find_field(icg, 0, "CMYK_K")) < 0)
+ error("Input file doesn't contain field CMYK_K");
+ if (icg->t[0].ftype[ki] != r_t)
+ error("Field CMYK_K is wrong type - corrupted file ?");
+ }
+
+ if (spec == 0) { /* Using instrument tristimulous value */
+
+ if (isLab) { /* Expect Lab */
+ if ((Xi = icg->find_field(icg, 0, "LAB_L")) < 0)
+ error("Input file doesn't contain field LAB_L");
+ if (icg->t[0].ftype[Xi] != r_t)
+ error("Field LAB_L is wrong type - corrupted file ?");
+ if ((Yi = icg->find_field(icg, 0, "LAB_A")) < 0)
+ error("Input file doesn't contain field LAB_A");
+ if (icg->t[0].ftype[Yi] != r_t)
+ error("Field LAB_A is wrong type - corrupted file ?");
+ if ((Zi = icg->find_field(icg, 0, "LAB_B")) < 0)
+ error("Input file doesn't contain field LAB_B");
+ if (icg->t[0].ftype[Zi] != r_t)
+ error("Field LAB_B is wrong type - corrupted file ?");
+
+ } else { /* Expect XYZ */
+ if ((Xi = icg->find_field(icg, 0, "XYZ_X")) < 0)
+ error("Input file doesn't contain field XYZ_X");
+ if (icg->t[0].ftype[Xi] != r_t)
+ error("Field XYZ_X is wrong type - corrupted file ?");
+ if ((Yi = icg->find_field(icg, 0, "XYZ_Y")) < 0)
+ error("Input file doesn't contain field XYZ_Y");
+ if (icg->t[0].ftype[Yi] != r_t)
+ error("Field XYZ_Y is wrong type - corrupted file ?");
+ if ((Zi = icg->find_field(icg, 0, "XYZ_Z")) < 0)
+ error("Input file doesn't contain field XYZ_Z");
+ if (icg->t[0].ftype[Zi] != r_t)
+ error("Field XYZ_Z is wrong type - corrupted file ?");
+ }
+
+ for (i = 0; i < npat; i++) {
+ tpat[i].w = 1.0;
+ tpat[i].p[0] = *((double *)icg->t[0].fdata[i][ci]) / 100.0;
+ tpat[i].p[1] = *((double *)icg->t[0].fdata[i][mi]) / 100.0;
+ tpat[i].p[2] = *((double *)icg->t[0].fdata[i][yi]) / 100.0;
+ tpat[i].p[3] = *((double *)icg->t[0].fdata[i][ki]) / 100.0;
+ if (tpat[i].p[0] > 1.0
+ || tpat[i].p[1] > 1.0
+ || tpat[i].p[2] > 1.0
+ || tpat[i].p[3] > 1.0) {
+ double bgst = 0.0;
+ int j, bj = 0;
+ for (j = 0; j < 4; j++) {
+ if (tpat[i].p[j] > bgst) {
+ bgst = tpat[i].p[j];
+ bj = j;
+ }
+ }
+ error("Device value field value exceeds 100.0 (%d:%d:%f) !",i,bj,bgst * 100.0);
+ }
+ tpat[i].v[0] = *((double *)icg->t[0].fdata[i][Xi]);
+ tpat[i].v[1] = *((double *)icg->t[0].fdata[i][Yi]);
+ tpat[i].v[2] = *((double *)icg->t[0].fdata[i][Zi]);
+ if (!isLab && (!isdisp || isdnormed != 0)) {
+ tpat[i].v[0] /= 100.0; /* Normalise XYZ to range 0.0 - 1.0 */
+ tpat[i].v[1] /= 100.0;
+ tpat[i].v[2] /= 100.0;
+ }
+ if (!isLab && wantLab) { /* Convert test patch result XYZ to PCS (D50 Lab) */
+ icmXYZ2Lab(&icmD50, tpat[i].v, tpat[i].v);
+ } else if (isLab && !wantLab) {
+ icmLab2XYZ(&icmD50, tpat[i].v, tpat[i].v);
+ }
+ }
+
+ } else { /* Using spectral data */
+ int ii;
+ xspect sp;
+ char buf[100];
+ int spi[XSPECT_MAX_BANDS]; /* CGATS indexes for each wavelength */
+ xsp2cie *sp2cie; /* Spectral conversion object */
+
+ if ((ii = icg->find_kword(icg, 0, "SPECTRAL_BANDS")) < 0)
+ error ("Input file doesn't contain keyword SPECTRAL_BANDS");
+ sp.spec_n = atoi(icg->t[0].kdata[ii]);
+ if ((ii = icg->find_kword(icg, 0, "SPECTRAL_START_NM")) < 0)
+ error ("Input file doesn't contain keyword SPECTRAL_START_NM");
+ sp.spec_wl_short = atof(icg->t[0].kdata[ii]);
+ if ((ii = icg->find_kword(icg, 0, "SPECTRAL_END_NM")) < 0)
+ error ("Input file doesn't contain keyword SPECTRAL_END_NM");
+ sp.spec_wl_long = atof(icg->t[0].kdata[ii]);
+ if (!isdisp || isdnormed != 0)
+ sp.norm = 100.0;
+ else
+ sp.norm = 1.0;
+
+ /* Find the fields for spectral values */
+ for (j = 0; j < sp.spec_n; j++) {
+ int nm;
+
+ /* Compute nearest integer wavelength */
+ nm = (int)(sp.spec_wl_short + ((double)j/(sp.spec_n-1.0))
+ * (sp.spec_wl_long - sp.spec_wl_short) + 0.5);
+
+ sprintf(buf,"SPEC_%03d",nm);
+
+ if ((spi[j] = icg->find_field(icg, 0, buf)) < 0)
+ error("Input file doesn't contain field %s",buf);
+ }
+
+ if (isdisp) {
+ illum = icxIT_none; /* Displays are assumed to be self luminous */
+ cust_illum = NULL;
+ }
+
+ /* Create a spectral conversion object */
+ if ((sp2cie = new_xsp2cie(illum, cust_illum, observ, NULL,
+ wantLab ? icSigLabData : icSigXYZData, icxClamp)) == NULL)
+ error("Creation of spectral conversion object failed");
+
+ /* If Fluorescent Whitening Agent compensation is enabled */
+ if (!isdisp && fwacomp) {
+ double nw = 0.0; /* Number of media white patches */
+ xspect mwsp; /* Medium spectrum */
+ instType itype; /* Spectral instrument type */
+ xspect insp; /* Instrument illuminant */
+ xspect tinsp, *tinspp = NULL; /* Target/simulated instrument illuminant */
+
+ mwsp = sp; /* Struct copy */
+
+ if ((ti = icg->find_kword(icg, 0, "TARGET_INSTRUMENT")) < 0)
+ error ("Can't find target instrument needed for FWA compensation");
+
+ if ((itype = inst_enum(icg->t[0].kdata[ti])) == instUnknown)
+ error ("Unrecognised target instrument '%s'", icg->t[0].kdata[ti]);
+
+ if (inst_illuminant(&insp, itype) != 0)
+ error ("Instrument doesn't have an FWA illuminent");
+
+ /* Find the media white spectral reflectance */
+ for (j = 0; j < mwsp.spec_n; j++)
+ mwsp.spec[j] = 0.0;
+
+ /* Compute the mean of all the media white patches */
+ for (i = 0; i < npat; i++) {
+ int use = 0;
+
+ if (devspace == icSigGrayData) {
+ if (isAdditive) {
+ if (*((double *)icg->t[0].fdata[i][ci]) > (100.0 - 0.1))
+ use = 1;
+ } else {
+ if (*((double *)icg->t[0].fdata[i][ci]) < 0.1)
+ use = 1;
+ }
+ } else if (devspace == icSigRgbData) {
+ if (*((double *)icg->t[0].fdata[i][ci]) > (100.0 - 0.1)
+ && *((double *)icg->t[0].fdata[i][mi]) > (100.0 - 0.1)
+ && *((double *)icg->t[0].fdata[i][yi]) > (100.0 - 0.1))
+ use = 1;
+ } else if (devspace == icSigCmyData) {
+ if (*((double *)icg->t[0].fdata[i][ci]) < 0.1
+ && *((double *)icg->t[0].fdata[i][mi]) < 0.1
+ && *((double *)icg->t[0].fdata[i][yi]) < 0.1)
+ use = 1;
+ } else { /* Assume CMYK */
+
+ if (*((double *)icg->t[0].fdata[i][ci]) < 0.1
+ && *((double *)icg->t[0].fdata[i][mi]) < 0.1
+ && *((double *)icg->t[0].fdata[i][yi]) < 0.1
+ && *((double *)icg->t[0].fdata[i][ki]) < 0.1) {
+ use = 1;
+ }
+ }
+
+ if (use) {
+ /* Read the spectral values for this patch */
+ for (j = 0; j < mwsp.spec_n; j++) {
+ mwsp.spec[j] += *((double *)icg->t[0].fdata[i][spi[j]]);
+ }
+ nw++;
+ }
+ }
+
+ if (nw == 0.0) {
+ warning("Can't find a media white patch to init FWA");
+
+ /* Track the maximum reflectance for any band to determine white. */
+ /* This might give bogus results if there is no white patch... */
+ for (i = 0; i < npat; i++) {
+ for (j = 0; j < mwsp.spec_n; j++) {
+ double rv = *((double *)icg->t[0].fdata[i][spi[j]]);
+ if (rv > mwsp.spec[j])
+ mwsp.spec[j] = rv;
+ }
+ }
+ nw++;
+ }
+ for (j = 0; j < mwsp.spec_n; j++) {
+ mwsp.spec[j] /= nw; /* Compute average */
+ }
+
+ /* If the simulated instrument illumination is */
+ /* not the observer/final illuminant */
+ if (tillum != icxIT_none) {
+ if (tillum == icxIT_custom)
+ tinspp = cust_tillum;
+ else {
+ tinspp = &tinsp;
+ if (standardIlluminant(tinspp, tillum, 0.0)) {
+ error("simulated inst. illum. not recognised");
+ }
+ }
+ }
+
+ /* (Note that sp and mwsp.norm is set to 100.0) */
+ if (sp2cie->set_fwa(sp2cie, &insp, tinspp, &mwsp))
+ error ("Set FWA on sp2cie failed");
+
+ if (verb) {
+ double FWAc;
+ sp2cie->get_fwa_info(sp2cie, &FWAc);
+ fprintf(verbo,"FWA content = %f\n",FWAc);
+ }
+ }
+
+ for (i = 0; i < npat; i++) {
+
+ tpat[i].w = 1.0;
+ tpat[i].p[0] = *((double *)icg->t[0].fdata[i][ci]) / 100.0;
+ tpat[i].p[1] = *((double *)icg->t[0].fdata[i][mi]) / 100.0;
+ tpat[i].p[2] = *((double *)icg->t[0].fdata[i][yi]) / 100.0;
+ tpat[i].p[3] = *((double *)icg->t[0].fdata[i][ki]) / 100.0;
+
+ if (tpat[i].p[0] > 1.0
+ || tpat[i].p[1] > 1.0
+ || tpat[i].p[2] > 1.0
+ || tpat[i].p[3] > 1.0) {
+ error("Device value field value exceeds 100.0 !");
+ }
+
+ /* Read the spectral values for this patch */
+ for (j = 0; j < sp.spec_n; j++) {
+ sp.spec[j] = *((double *)icg->t[0].fdata[i][spi[j]]);
+ }
+
+ /* Convert it to CIE space */
+ sp2cie->convert(sp2cie, tpat[i].v, &sp);
+
+ }
+
+ sp2cie->del(sp2cie); /* Done with this */
+
+ }
+
+ /* Normalize display values to Y = 1.0 if needed */
+ /* (re-norm spec derived, since observer may be different) */
+ if (isdisp && (isdnormed == 0 || spec != 0)) {
+ double scale = -1e6;
+
+ if (wantLab) {
+ double bxyz[3];
+
+ /* Locate max Y */
+ for (i = 0; i < npat; i++) {
+ icmLab2XYZ(&icmD50, bxyz, tpat[i].v);
+ if (bxyz[1] > scale)
+ scale = bxyz[1];
+ }
+
+ scale = 1.0/scale;
+
+ /* Scale max Y to 1.0 */
+ for (i = 0; i < npat; i++) {
+ icmLab2XYZ(&icmD50, tpat[i].v, tpat[i].v);
+ tpat[i].v[0] *= scale;
+ tpat[i].v[1] *= scale;
+ tpat[i].v[2] *= scale;
+ icmXYZ2Lab(&icmD50, tpat[i].v, tpat[i].v);
+ }
+ } else {
+
+ /* Locate max Y */
+ for (i = 0; i < npat; i++) {
+ if (tpat[i].v[1] > scale)
+ scale = tpat[i].v[1];
+ }
+
+ scale = 1.0/scale;
+
+ for (i = 0; i < npat; i++) {
+ tpat[i].v[0] *= scale;
+ tpat[i].v[1] *= scale;
+ tpat[i].v[2] *= scale;
+ }
+ }
+ }
+ } /* End of reading in CGATs file */
+
+ if (isLut) {
+ xicc *wr_xicc; /* extention object */
+ icxLuBase *AtoB; /* AtoB ixcLu */
+
+ /* Create A2B clut */
+ {
+ int flags = 0;
+
+ /* Wrap with an expanded icc */
+ if ((wr_xicc = new_xicc(wr_icco)) == NULL)
+ error("Creation of xicc failed");
+
+ flags |= ICX_CLIP_NEAREST; /* This will avoid clip caused rev setup */
+
+ if (noisluts)
+ flags |= ICX_NO_IN_SHP_LUTS;
+
+ if (noipluts)
+ flags |= ICX_NO_IN_POS_LUTS;
+
+ if (nooluts)
+ flags |= ICX_NO_OUT_LUTS;
+
+ if (clipprims)
+ flags |= ICX_CLIP_WB;
+
+ if (verb)
+ flags |= ICX_VERBOSE;
+
+ flags |= ICX_SET_WHITE | ICX_SET_BLACK; /* Compute & use white & black */
+
+ /* Setup Device -> PCS conversion (Fwd) object from scattered data. */
+ if ((AtoB = wr_xicc->set_luobj(
+ wr_xicc, icmFwd, !allintents ? icmDefaultIntent : icRelativeColorimetric,
+ icmLuOrdNorm,
+#ifdef USE_EXTRA_FITTING
+ ICX_EXTRA_FIT |
+#endif
+#ifdef USE_2PASSSMTH
+ ICX_2PASSSMTH |
+#endif
+ flags,
+ npat, npat, tpat, NULL, dispLuminance, -1.0, smooth, avgdev,
+ NULL, oink, cal, iquality)) == NULL)
+ error("%d, %s",wr_xicc->errc, wr_xicc->err);
+
+ AtoB->del(AtoB); /* Done with lookup */
+ }
+
+ /* Create B2A clut */
+ {
+ icc *src_icco = NULL;
+ xicc *src_xicc = NULL; /* Source profile */
+ icxViewCond ivc; /* Input Viewing Condition for CAM */
+ icxViewCond ovc; /* Output Viewing Condition for CAM */
+ icmFile *abs_fp[3] = { NULL, NULL, NULL }; /* Abstract profile transform: */
+ icc *abs_icc[3] = { NULL, NULL, NULL };
+ xicc *abs_xicc[3] = { NULL, NULL, NULL };
+ icmLut *wo[3];
+
+ out_b2a_callback cx;
+
+ if (verb)
+ printf("Setting up B to A table lookup\n");
+
+#ifdef FILTER_B2ACLIP
+ cx.filter = 1;
+ cx.filter_thr = FILTER_THR_DE;
+ cx.filter_ratio = FILTER_MAX_RAD/FILTER_MAX_DE;
+ cx.filter_maxrad = FILTER_MAX_RAD;
+#else
+ cx.filter = 0;
+ cx.filter_thr = 100.0;
+ cx.filter_ratio = 0.0;
+ cx.filter_maxrad = 0.0;
+#endif
+
+ if (ipname != NULL) { /* There is a source profile to determine gamut mapping */
+
+ /* Open up the profile for reading */
+ if ((src_icco = read_embedded_icc(ipname)) == NULL)
+ error ("Can't open file '%s'",ipname);
+
+ /* Wrap with an expanded icc */
+ if ((src_xicc = new_xicc(src_icco)) == NULL)
+ error ("Creation of src_xicc failed");
+ }
+
+ /* Figure out the final src & dst viewing conditions */
+ for (i = 0; i < 2; i++) {
+ xicc *x;
+ icxViewCond *v, *vc;
+ int es;
+
+ if (i == 0) { /* Input */
+ v = ivc_p; /* Override parameters */
+ es = ivc_e;
+ vc = &ivc; /* Target parameters */
+ if (src_xicc == NULL)
+ continue; /* Source viewing conditions won't be used */
+ x = src_xicc;
+ } else { /* Output */
+ v = ovc_p; /* Override parameters */
+ es = ovc_e;
+ vc = &ovc; /* Target parameters */
+ x = wr_xicc;
+ }
+
+ /* Set the default */
+ xicc_enum_viewcond(x, vc, -1, NULL, 0, NULL);
+
+ /* Override the viewing conditions */
+ if (es >= 0)
+ if (xicc_enum_viewcond(x, vc, es, NULL, 0, NULL) == -2)
+ error ("%d, %s",x->errc, x->err);
+ if (v->Ev >= 0)
+ vc->Ev = v->Ev;
+ if (v->Wxyz[0] >= 0.0 && v->Wxyz[1] > 0.0 && v->Wxyz[2] >= 0.0) {
+ /* Normalise XYZ to current media white */
+ vc->Wxyz[0] = v->Wxyz[0]/v->Wxyz[1] * vc->Wxyz[1];
+ vc->Wxyz[2] = v->Wxyz[2]/v->Wxyz[1] * vc->Wxyz[1];
+ }
+ if (v->Wxyz[0] >= 0.0 && v->Wxyz[1] >= 0.0 && v->Wxyz[2] < 0.0) {
+ /* Convert Yxy to XYZ */
+ double x = v->Wxyz[0];
+ double y = v->Wxyz[1]; /* If Y == 1.0, then X+Y+Z = 1/y */
+ double z = 1.0 - x - y;
+ vc->Wxyz[0] = x/y * vc->Wxyz[1];
+ vc->Wxyz[2] = z/y * vc->Wxyz[1];
+ }
+ if (v->La >= 0.0)
+ vc->La = v->La;
+ if (v->Yb >= 0.0)
+ vc->Yb = v->Yb;
+ if (v->Lv >= 0.0)
+ vc->Lv = v->Lv;
+ if (v->Yf >= 0.0)
+ vc->Yf = v->Yf;
+ if (v->Fxyz[0] >= 0.0 && v->Fxyz[1] > 0.0 && v->Fxyz[2] >= 0.0) {
+ /* Normalise XYZ to current media white */
+ vc->Fxyz[0] = v->Fxyz[0]/v->Fxyz[1] * vc->Fxyz[1];
+ vc->Fxyz[2] = v->Fxyz[2]/v->Fxyz[1] * vc->Fxyz[1];
+ }
+ if (v->Fxyz[0] >= 0.0 && v->Fxyz[1] >= 0.0 && v->Fxyz[2] < 0.0) {
+ /* Convert Yxy to XYZ */
+ double x = v->Fxyz[0];
+ double y = v->Fxyz[1]; /* If Y == 1.0, then X+Y+Z = 1/y */
+ double z = 1.0 - x - y;
+ vc->Fxyz[0] = x/y * vc->Fxyz[1];
+ vc->Fxyz[2] = z/y * vc->Fxyz[1];
+ }
+ }
+
+ /* Get a suitable forward conversion object to invert. */
+ /* By creating a separate one to the one created using scattered data, */
+ /* we get the chance to set ICX_CAM_CLIP. */
+ {
+ int flags = 0;
+
+ if (verb)
+ flags |= ICX_VERBOSE;
+
+ flags |= ICX_CLIP_NEAREST; /* Not vector clip */
+#ifdef USE_CAM_CLIP_OPT
+ flags |= ICX_CAM_CLIP; /* Clip in CAM Jab space rather than Lab */
+#else
+ warning("!!!! USE_CAM_CLIP_OPT in profout.c is off !!!!");
+#endif
+
+ if ((AtoB = wr_xicc->get_luobj(wr_xicc, flags, icmFwd,
+ !allintents ? icmDefaultIntent : icRelativeColorimetric,
+ wantLab ? icSigLabData : icSigXYZData,
+ icmLuOrdNorm, &ovc, oink)) == NULL)
+ error ("%d, %s",wr_xicc->errc, wr_xicc->err);
+ }
+
+ /* setup context ready for B2A table setting */
+ cx.verb = verb;
+ cx.pcsspace = wantLab ? icSigLabData : icSigXYZData;
+#ifdef NO_B2A_PCS_CURVES
+ cx.noPCScurves = 1; /* Don't use PCS curves */
+#else
+ cx.noPCScurves = 0;
+#endif
+ cx.devspace = devspace;
+ cx.x = (icxLuLut *)AtoB; /* A2B icxLuLut created from scattered data */
+
+ cx.ixp = NULL; /* Perceptual PCS to CAM conversion */
+ cx.ox = NULL; /* CAM to PCS conversion */
+ cx.pmap = NULL; /* perceptual gamut map */
+ cx.smap = NULL; /* Saturation gamut map */
+
+ cx.abs_luo[0] = cx.abs_luo[1] = cx.abs_luo[2] = NULL;
+ cx.xyzscale[0] = 1.0;
+ cx.xyzscale[1] = 1.0;
+ cx.gam = NULL;
+ cx.wantLab = wantLab; /* Copy PCS flag over */
+
+ /* Determine the number of tables */
+ cx.ntables = 1;
+ if (src_xicc) { /* Creating separate perceptual and Saturation tables */
+ cx.ntables = 2;
+ if (sepsat)
+ cx.ntables = 3;
+ }
+
+ /* Open up the abstract profile if supplied, and setup luo */
+ for (i = 0; i < cx.ntables; i++) {
+ if (absname[i] != NULL && cx.abs_luo[i] == NULL) {
+
+ if ((abs_fp[i] = new_icmFileStd_name(absname[i],"r")) == NULL)
+ error ("Can't open abstract profile file '%s'",absname[i]);
+
+ if ((abs_icc[i] = new_icc()) == NULL)
+ error ("Creation of Abstract profile ICC object failed");
+
+ /* Read header etc. */
+ if ((rv = abs_icc[i]->read(abs_icc[i],abs_fp[i],0)) != 0)
+ error ("%d, %s",rv,abs_icc[i]->err);
+
+ if (abs_icc[i]->header->deviceClass != icSigAbstractClass)
+ error("Abstract profile isn't an abstract profile");
+
+ /* Take intended abstract intent from profile itself */
+ if ((cx.abs_intent[i] = abs_icc[i]->header->renderingIntent) != icAbsoluteColorimetric)
+ cx.abs_intent[i] = icRelativeColorimetric;
+
+ /* Wrap with an expanded icc */
+ if ((abs_xicc[i] = new_xicc(abs_icc[i])) == NULL)
+ error ("Creation of abstract profile xicc failed");
+
+ /* The abstract profile intent is assumed to determine how it gets applied. */
+ /* Make abstract PCS XYZ if icAbsoluteColorimetric is needed. */
+ if ((cx.abs_luo[i] = abs_xicc[i]->get_luobj(abs_xicc[i], ICX_CLIP_NEAREST, icmFwd,
+ cx.abs_intent[i],
+ (cx.pcsspace == icSigLabData && cx.abs_intent[i] == icRelativeColorimetric)
+ ? icSigLabData : icSigXYZData,
+ icmLuOrdNorm, NULL, NULL)) == NULL)
+ error ("%d, %s",abs_icc[i]->errc, abs_icc[i]->err);
+
+ /* If the same abstract profile is used in the other tables, */
+ /* duplicate the transform pointer */
+ for (j = i; j < cx.ntables; j++) {
+ if (absname[i] == absname[j]) {
+ cx.abs_intent[j] = cx.abs_intent[i];
+ cx.abs_luo[j] = cx.abs_luo[i];
+ if (verb)
+ printf("Applying %s abstract profile '%s' to %s table\n",
+ i == 0 ? "first" : i == 1 ? "second" : "third",
+ absname[i],
+ j == 0 ? "colorimetric" : j == 1 ? "perceptual" : "saturation");
+ }
+ }
+ }
+ }
+
+ if (!allintents) { /* Only B2A0, no intent */
+ if ((wo[0] = (icmLut *)wr_icco->read_tag(
+ wr_icco, icSigBToA0Tag)) == NULL)
+ error("read_tag failed: %d, %s",wr_icco->errc,wr_icco->err);
+
+ } else { /* All 3 intent tables */
+ /* Intent 1 = relative colorimetric */
+ if ((wo[0] = (icmLut *)wr_icco->read_tag(
+ wr_icco, icSigBToA1Tag)) == NULL)
+ error("read_tag failed: %d, %s",wr_icco->errc,wr_icco->err);
+
+ if (src_xicc) { /* Creating separate perceptual and Saturation tables */
+ icRenderingIntent intentp; /* Gamut mapping space perceptual selection */
+ icRenderingIntent intents; /* Gamut mapping space saturation selection */
+ icRenderingIntent intento; /* Gamut mapping space output selection */
+ gamut *csgamp; /* Incoming colorspace perceptual gamut */
+ gamut *csgams; /* Incoming colorspace saturation gamut */
+ gamut *igam = NULL; /* Incoming image gamut */
+ gamut *ogam; /* Destination colorspace gamut */
+ double gres; /* Gamut surface feature resolution */
+ int mapres; /* Mapping rspl resolution */
+
+ if (verb)
+ printf("Creating Gamut Mapping\n");
+
+ /* Gamut mapping will extend given grid res to encompas */
+ /* source gamut by a margin. */
+ if (oquality == 3) { /* Ultra High */
+ gres = 8.0;
+ mapres = 41;
+ } else if (oquality == 2) { /* High */
+ gres = 8.0;
+ mapres = 33;
+ } else if (oquality == 1) { /* Medium */
+ gres = 10.0;
+ mapres = 25;
+ } else if (oquality == 0) { /* Low quality */
+ gres = 12.0;
+ mapres = 17;
+ } else { /* Extremely low */
+ gres = 14.0;
+ mapres = 9;
+ }
+
+ /* We could lift this restriction by allowing for separate */
+ /* cx.ix and cx.ox for each intent, but that would be expensive!. */
+ if (sepsat && (pgmi->usecas & 0xff) != (sgmi->usecas & 0xff))
+ error("Can't handle percept and sat table intents with different CAM spaces");
+ /* Default perceptual input gamut mapping space is absolute perceptual */
+ intentp = noptop ? icAbsoluteColorimetric : icmAbsolutePerceptual;
+
+ /* But override this for apperance space gamut mapping */
+ if ((pgmi->usecas & 0xff) != 0x0) {
+ intentp = noptop ? icxAppearance : icxPerceptualAppearance;
+ }
+ if (sepsat) {
+ /* Default saturation gamut mapping space is absolute saturation */
+ intents = nostos ? icAbsoluteColorimetric : icmAbsoluteSaturation;
+
+ /* But override this for apperance space gamut mapping */
+ if ((sgmi->usecas & 0xff) != 0x0) {
+ intents = nostos ? icxAppearance : icxSaturationAppearance;
+ }
+ }
+ /* Default output gamut mapping space is absolute colorimetric */
+ intento = icAbsoluteColorimetric;
+
+ /* But override this for apperance space gamut mapping */
+ if ((pgmi->usecas & 0xff) != 0x0) {
+ intento = icxAppearance;
+ }
+
+ if ((pgmi->usecas & 0xff) == 0x2) {
+ double mxw;
+ intentp = intents = intento = icxAbsAppearance;
+
+ /* Make absolute common white point average between the two */
+ ivc.Wxyz[0] = 0.5 * (ivc.Wxyz[0] + ovc.Wxyz[0]);
+ ivc.Wxyz[1] = 0.5 * (ivc.Wxyz[1] + ovc.Wxyz[1]);
+ ivc.Wxyz[2] = 0.5 * (ivc.Wxyz[2] + ovc.Wxyz[2]);
+
+ /* And scale it's Y to be equal to 1.0 */
+ mxw = 1.0/ivc.Wxyz[1];
+ ivc.Wxyz[0] *= mxw;
+ ivc.Wxyz[1] *= mxw;
+ ivc.Wxyz[2] *= mxw;
+
+ /* set output view conditions the same as the input */
+ ovc = ivc; /* Structure copy */
+ }
+
+ /* Unlike icclink, we've not provided a way for the user */
+ /* to set the source profile ink limit, so estimate it */
+ /* from the profile */
+ icxDefaultLimits(src_xicc, &iink.tlimit, iink.tlimit,
+ &iink.klimit, iink.klimit);
+
+ /* Get lookup object simply for fwd_relpcs_outpcs() */
+ /* and perceptual input gamut shell creation. */
+ /* Note that the intent=Appearance will trigger Jab CAM, */
+ /* overriding icSigLabData.. */
+#ifdef NEVER
+ printf("~1 input space flags = 0x%x\n",ICX_CLIP_NEAREST);
+ printf("~1 input space intent = %s\n",icx2str(icmRenderingIntent,intentp));
+ printf("~1 input space pcs = %s\n",icx2str(icmColorSpaceSignature,icSigLabData));
+ printf("~1 input space viewing conditions =\n"); xicc_dump_viewcond(&ivc);
+ printf("~1 input space inking =\n"); xicc_dump_inking(&iink);
+#endif
+ if ((cx.ixp = src_xicc->get_luobj(src_xicc,ICX_CLIP_NEAREST
+ , icmFwd, intentp, icSigLabData, icmLuOrdNorm, &ivc, &iink)) == NULL)
+ error ("%d, %s",src_xicc->errc, src_xicc->err);
+
+ /* Create the source colorspace gamut surface */
+ if (verb)
+ printf(" Finding Source Colorspace Perceptual Gamut\n");
+
+ if ((csgamp = cx.ixp->get_gamut(cx.ixp, gres)) == NULL)
+ error ("%d, %s",src_xicc->errc, src_xicc->err);
+
+ if (sepsat) {
+ icxLuBase *ixs = NULL; /* Source profile saturation lookup for gamut */
+ /* Get lookup object for saturation input gamut shell creation */
+ /* Note that the intent=Appearance will trigger Jab CAM, */
+ /* overriding icSigLabData.. */
+ if ((ixs = src_xicc->get_luobj(src_xicc, ICX_CLIP_NEAREST
+ , icmFwd, intents, icSigLabData, icmLuOrdNorm, &ivc, &iink)) == NULL)
+ error ("%d, %s",src_xicc->errc, src_xicc->err);
+
+ if (verb)
+ printf(" Finding Source Colorspace Saturation Gamut\n");
+
+ if ((csgams = ixs->get_gamut(ixs, gres)) == NULL)
+ error ("%d, %s",src_xicc->errc, src_xicc->err);
+ ixs->del(ixs);
+ }
+
+ /* Read image source gamut if provided */
+ if (sgname != NULL) { /* Optional source gamut - ie. from an images */
+ int isJab = 0;
+
+ if ((pgmi->usecas & 0xff) != 0)
+ isJab = 1;
+
+ if (verb)
+ printf(" Loading Image Source Gamut '%s'\n",sgname);
+
+ igam = new_gamut(gres, 0, 0);
+
+ if (igam->read_gam(igam, sgname))
+ error("Reading source gamut '%s' failed",sgname);
+
+ if (igam->getisjab(igam) != isJab) {
+ /* Should really convert to/from Jab here! */
+ warning("Image gamut is wrong colorspace for gamut mapping (Lab != Jab)");
+ /* This will actually error in the gamut mapping code */
+ /* Note that we're not checking relative/absolute colorspace here. */
+ /* At the moment it's up to the user to get this right. */
+ }
+ }
+
+ /* Get lookup object for bwd_outpcs_relpcs(), */
+ /* and output gamut shell creation */
+ /* Note that the intent=Appearance will trigger Jab CAM, */
+ /* overriding icSigLabData.. */
+ if ((cx.ox = wr_xicc->get_luobj(wr_xicc, ICX_CLIP_NEAREST
+ , icmFwd, intento,
+ icSigLabData, icmLuOrdNorm, &ovc, oink)) == NULL)
+ error ("%d, %s",wr_xicc->errc, wr_xicc->err);
+
+ /* Creat the destination gamut surface */
+ if (verb)
+ printf(" Finding Destination Gamut\n");
+
+ if ((ogam = cx.ox->get_gamut(cx.ox, gres)) == NULL)
+ error ("%d, %s",wr_xicc->errc, wr_xicc->err);
+
+ if (verb)
+ printf(" Creating Gamut match\n");
+
+ /* The real range of Lab 0..100,-128..128,1-28..128 cube */
+ /* when mapped to CAM is ridiculously large (ie. */
+ /* 0..100, -288..265, -112..533), so we don't attempt to */
+ /* set a gamut mapping grid range based on this. Instead */
+ /* rely on the gamut map code to set a reasonable grid range */
+ /* around the source gamut, and to cope reasonably with */
+ /* values outside the grid range. */
+
+ /* setup perceptual gamut mapping */
+ cx.pmap = new_gammap(verb, csgamp, igam, ogam, pgmi, 0, 0, 0, 0, mapres,
+ NULL, NULL, gamdiag ? "gammap_p.wrl" : NULL
+ );
+ if (cx.pmap == NULL)
+ error ("Failed to make perceptual gamut map transform");
+
+ /* Intent 0 = perceptual */
+ if ((wo[1] = (icmLut *)wr_icco->read_tag(
+ wr_icco, icSigBToA0Tag)) == NULL)
+ error("read_tag failed: %d, %s",wr_icco->errc,wr_icco->err);
+
+ if (sepsat) {
+ /* setup saturation gamut mapping */
+ cx.smap = new_gammap(verb, csgams, igam, ogam, sgmi, 0, 0, 0, 0, mapres,
+ NULL, NULL, gamdiag ? "gammap_s.wrl" : NULL
+ );
+ if (cx.smap == NULL)
+ error ("Failed to make saturation gamut map transform");
+
+ /* Intent 2 = saturation */
+ if ((wo[2] = (icmLut *)wr_icco->read_tag(
+ wr_icco, icSigBToA2Tag)) == NULL)
+ error("read_tag failed: %d, %s",wr_icco->errc,wr_icco->err);
+ }
+ csgamp->del(csgamp);
+ csgamp = NULL;
+ if (sepsat) {
+ csgams->del(csgams);
+ csgams = NULL;
+ }
+ if (igam != NULL) {
+ igam->del(igam);
+ igam = NULL;
+ }
+ ogam->del(ogam);
+ ogam = NULL;
+ }
+ }
+ cx.ochan = wo[0]->outputChan;
+
+ /* If we've got a request for Absolute Appearance mode with scaling */
+ /* to avoid clipping the source white point, compute the needed XYZ scaling factor. */
+ if (src_xicc != NULL && allintents
+ && ((pgmi->usecas & 0x100) || (sgmi->usecas & 0x100))) {
+ double swcam[3];
+ double xyzscale[1], sa[1];
+
+ /* Grab the source white point in CAM space */
+ cx.ixp->efv_wh_bk_points(cx.ixp, swcam, NULL, NULL);
+
+ /* Convert it to destination XYZ */
+ /* We're being bad in delving inside the xluo, but we'll fix it latter */
+ cx.ox->cam->cam_to_XYZ(cx.ox->cam, cx.swxyz, swcam);
+
+//printf("~1 Source white Jab = %f %f %f\n", swcam[0], swcam[1], swcam[2]);
+//printf("~1 Source white XYZ = %f %f %f\n", cx.swxyz[0], cx.swxyz[1], cx.swxyz[2]);
+
+ /* Compute the bigest scale factor less than or equal to 1.0, */
+ /* that doesn't clip the cx.swxyz[] on the destination gamut */
+ sa[0] = 0.1;
+ xyzscale[0] = 0.5;
+ if (powell(NULL, 1, xyzscale, sa, 1e-6, 2000,
+ xyzoptfunc, (void *)&cx, NULL, NULL) != 0) {
+ warning("make_output_icc: XYZ scale powell failed to converge - set scale to 1.0");
+ } else {
+ if (pgmi->usecas & 0x100) {
+ cx.xyzscale[0] = xyzscale[0];
+ if (cx.verb) printf("Set Perceptual XYZ scale factor to %f\n",xyzscale[0]);
+ }
+ if (sgmi->usecas & 0x100) {
+ cx.xyzscale[1] = xyzscale[0];
+ if (cx.verb) printf("Set Saturation XYZ scale factor to %f\n",xyzscale[0]);
+ }
+ }
+ }
+
+// ====================================================================
+#ifdef NEVER
+// ~~99
+ /* DEVELOPMENT CODE - not complete */
+ /* Setup optimised B2A per channel curves */
+ {
+ xfit *xf; /* Curve fitting class instance */
+ int xfflags = 0; /* xfit flags */
+ double in_min[MXDI]; /* Input value scaling minimum */
+ double in_max[MXDI]; /* Input value scaling maximum */
+ double out_min[MXDO]; /* Output value scaling minimum */
+ double out_max[MXDO]; /* Output value scaling maximum */
+ int iluord, oluord;
+ int iord[MXDI]; /* Input curve orders */
+ int oord[MXDO]; /* Output curve orders */
+ int nodp; /* Number of inverse data points */
+ co *points; /* List of inverse points as PCS->dev */
+
+ optcomb tcomb = oc_imo; /* Create all by default */
+
+ if ((xf = new_xfit()) == NULL) {
+ error("profout: Creation of xfit object failed");
+ }
+
+ /* Setup for optimising run */
+ if (nooluts) /* Use option flags - swap sense for B2A */
+ tcomb &= ~oc_i;
+
+ if (noiluts)
+ tcomb &= ~oc_o;
+
+ if (verb)
+ xfflags |= XFIT_VERB;
+
+ xfflags |= XFIT_OUT_DEV; /* Outpupt is device */
+ /* (Switch to XFIT_OUT_LU latter ?) */
+ if (cx.pcsspace == icSigLabData)
+ xfflags |= XFIT_IN_ZERO; /* Adjust a & b to zero */
+
+~~~~~~~~~~~~~~~~~~~
+ /* Set the curve order for input (PCS) */
+ if (oquality >= 3) { /* Ultra high */
+ iluord = 25;
+ nodp = 2000;
+ } else if (oquality == 2) { /* High */
+ iluord = 20;
+ nodp = 1000;
+ } else if (oquality == 1) { /* Medium */
+ iluord = 17;
+ nodp = 750;
+ } else { /* Low */
+ iluord = 10;
+ nodp = 500;
+ }
+ for (e = 0; e < p->inputChan; e++) {
+ iord[e] = iluord;
+ in_min[e] = p->inmin[e];
+ in_max[e] = p->inmax[e];
+
+ /* Hack to prevent a convex L curve pushing */
+ /* the clut L values above the maximum value */
+ /* that can be represented, causing clipping. */
+ /* Do this by making sure that the L curve pivots */
+ /* through 100.0 to 100.0 */
+ if (e == 0 && cx.pcsspace == icSigLabData) {
+ if (in_min[e] < 0.0001 && in_max[e] > 100.0) {
+ in_max[e] = 100.0;
+ }
+ }
+ }
+
+ /* Set curve order for output (Device) */
+ if (oquality >= 3) { /* Ultra high */
+ oluord = 25;
+ } else if (oquality == 2) { /* High */
+ oluord = 20;
+ } else if (oquality == 1) { /* Medium */
+ oluord = 17;
+ } else { /* Low */
+ oluord = 10;
+ }
+ for (f = 0; f < p->outputChan; f++) {
+ oord[f] = oluord;
+ out_min[f] = p->outmin[f];
+ out_max[f] = p->outmax[f];
+
+ }
+
+ /* Create the sample points */
+ ~~~~~~~~~~~
+ malloc
+
+ for (i = 0; i < nodp;) {
+ generate random pcs value
+
+ if not within gamut
+ continue;
+
+ lookup through overall conversion
+ i++;
+ }
+
+ /* Fit input and output curves to our data points */
+ if (xf->fit(xf, xfflags, p->inputChan, p->outputChan, nodp, points,
+ in_min, in_max, out_min, out_max, iord, oord, tcomb, NULL) != 0) {
+ p->pp->errc = 2;
+ sprintf(p->pp->err,"xfit fitting failed");
+ xf->del(xf);
+ p->del((icxLuBase *)p);
+ return NULL;
+
+ }
+
+ /* - - - - - - - - - - - - - - - */
+ /* Set the xicc input curve rspl */
+ for (e = 0; e < p->inputChan; e++) {
+ curvectx cx;
+
+ cx.xf = xf;
+ cx.oix = -1;
+ cx.iix = e;
+
+ if ((p->inputTable[e] = new_rspl(1, 1)) == NULL) {
+ p->pp->errc = 2;
+ sprintf(p->pp->err,"Creation of input table rspl failed");
+ p->del((icxLuBase *)p);
+ return NULL;
+ }
+
+ p->inputTable[e]->set_rspl(p->inputTable[e], RSPL_NOFLAGS,
+ (void *)&cx, set_linfunc,
+ &p->ninmin[e], &p->ninmax[e],
+ &p->lut->inputEnt,
+ &p->ninmin[e], &p->ninmax[e]);
+ }
+
+ /* - - - - - - - - - - - - - - - */
+ /* Set the xicc output curve rspl */
+
+ /* Allow for a bigger than normal input and output range, to */
+ /* give some leaway in accounting for approximate white point shifted */
+ /* profile creation. */
+ for (f = 0; f < p->outputChan; f++) {
+ double min[1], max[1], exval;
+ int entries;
+ curvectx cx;
+
+ cx.xf = xf;
+ cx.iix = -1;
+ cx.oix = f;
+
+ /* Expand in and out range by 1.05 */
+ exval = (p->noutmax[f] - p->noutmin[f]);
+ min[0] = p->noutmin[f] - exval * 0.05 * 0.5;
+ max[0] = p->noutmax[f] + exval * 0.05 * 0.5;
+ entries = (int)(1.05 * (double)p->lut->outputEnt + 0.5);
+
+ if ((p->outputTable[f] = new_rspl(1, 1)) == NULL) {
+ p->pp->errc = 2;
+ sprintf(p->pp->err,"Creation of output table rspl failed");
+ p->del((icxLuBase *)p);
+ return NULL;
+ }
+
+ p->outputTable[f]->set_rspl(p->outputTable[f], RSPL_NOFLAGS,
+ (void *)&cx, set_linfunc,
+ min, max, &entries, min, max);
+
+ }
+
+ xf->del(xf);
+ }
+#endif /* NEVER (Setup optimised B2A per channel curves) */
+// ====================================================================
+
+ /* We now setup an exact inverse, colorimetric style, plus gamut mapping */
+ /* for perceptual and saturation intents */
+ /* Use helper function to do the hard work. */
+
+ if (cx.verb) {
+ unsigned int ui;
+ int extra;
+ cx.count = 0;
+ cx.last = -1;
+ for (cx.total = 1, ui = 0; ui < wo[0]->inputChan; ui++, cx.total *= wo[0]->clutPoints)
+ ;
+ /* Add in cell center points */
+ for (extra = 1, ui = 0; ui < wo[0]->inputChan; ui++, extra *= (wo[0]->clutPoints-1))
+ ;
+ cx.total += extra;
+ printf("Creating B to A tables\n");
+ printf(" 0%%"); fflush(stdout);
+ }
+
+#ifdef DEBUG_ONE
+#define DBGNO 1 /* Up to 10 */
+
+ /* Test a single given PCS (Rel D50 Lab) -> cmyk value */
+ {
+ double in[10][MAX_CHAN];
+ double out[MAX_CHAN];
+ in[0][0] = 100.0; /* White point */
+ in[0][1] = 0.001;
+ in[0][2] = 0.001;
+
+ for (i = 0; i < DBGNO; i++) {
+ printf("Input %s\n",icmPdv(3,in[i]));
+ out_b2a_input((void *)&cx, out, in[i]);
+ printf("Input' %s\n",icmPdv(3,out));
+ out_b2a_clut((void *)&cx, out, out);
+ printf("Output' %s\n\n",icmPdv(4,out));
+ out_b2a_output((void *)&cx, out, out);
+ printf("Output %s\n\n",icmPdv(4,out));
+ }
+ }
+#else /* !DEBUG_ONE */
+
+#ifndef USE_LEASTSQUARES_APROX
+ fprintf(stderr,"!!!!! profile/profout: USE_LEASTSQUARES_APROX undef !!!!!\n");
+#endif
+ if (icmSetMultiLutTables(
+ cx.ntables,
+ wo,
+#ifdef USE_LEASTSQUARES_APROX
+ ICM_CLUT_SET_APXLS |
+#endif
+#ifdef FILTER_B2ACLIP
+ ICM_CLUT_SET_FILTER |
+#endif
+ 0,
+ &cx, /* Context */
+ cx.pcsspace, /* Input color space */
+ devspace, /* Output color space */
+ out_b2a_input, /* Input transform PCS->PCS' */
+ NULL, NULL, /* Use default PCS range */
+ out_b2a_clut, /* Lab' -> Device' transfer function */
+ NULL, NULL, /* Use default Device' range */
+ out_b2a_output) != 0) /* Output transfer function, Device'->Device */
+ error("Setting 16 bit PCS->Device Lut failed: %d, %s",wr_icco->errc,wr_icco->err);
+ if (cx.verb) {
+ printf("\n");
+ }
+
+#ifdef WARN_CLUT_CLIPPING /* Print warning if setting clut clips */
+ /* Ignore clipping of the input table, because this happens */
+ /* anyway due to Lab symetry adjustment. */
+ if (wr_icco->warnc != 0 && wr_icco->warnc != 1) {
+ warning("Values clipped in setting B2A LUT!");
+ }
+#endif /* WARN_CLUT_CLIPPING */
+#endif /* !DEBUG_ONE */
+
+ /* Free up abstract transform */
+ for (i = 0; i < cx.ntables; i++) {
+ if (cx.abs_luo[i] != NULL) {
+ for (j = cx.ntables-1; j >= i; j--) { /* Free all duplicates */
+ if (cx.abs_luo[j] == cx.abs_luo[i]) {
+ cx.abs_luo[j]->del(cx.abs_luo[j]);
+ abs_xicc[j]->del(abs_xicc[j]);
+ abs_icc[j]->del(abs_icc[j]);
+ abs_fp[j]->del(abs_fp[j]);
+ cx.abs_luo[j] = NULL;
+ }
+ }
+ }
+ }
+
+ if (cx.pmap != NULL)
+ cx.pmap->del(cx.pmap), cx.pmap = NULL;
+ if (cx.smap != NULL)
+ cx.smap->del(cx.smap), cx.smap = NULL;
+ if (cx.ixp != NULL)
+ cx.ixp->del(cx.ixp), cx.ixp = NULL;
+ if (cx.ox != NULL)
+ cx.ox->del(cx.ox), cx.ox = NULL;
+
+ if (src_xicc != NULL)
+ src_xicc->del(src_xicc), src_xicc = NULL;
+ if (src_icco != NULL)
+ src_icco->del(src_icco), src_icco = NULL;
+
+ if (verb)
+ printf("Done B to A tables\n");
+ }
+
+ /* Set the ColorantTable PCS values */
+ {
+ unsigned int i;
+ icmColorantTable *wo;
+ double dv[MAX_CHAN];
+
+ if ((wo = (icmColorantTable *)wr_icco->read_tag(
+ wr_icco, icSigColorantTableTag)) == NULL)
+ error("read_tag failed: %d, %s",wr_icco->errc,wr_icco->err);
+
+ for (i = 0; i < wo->count; i++)
+ dv[i] = 0.0;
+
+ /* Lookup the colorant PCS values the recommended ICC way */
+ for (i = 0; i < wo->count; i++) {
+ dv[i] = 1.0;
+ AtoB->lookup(AtoB, wo->data[i].pcsCoords, dv);
+ dv[i] = 0.0;
+ }
+ }
+
+#ifndef DISABLE_GAMUT_TAG
+ /* Create Gamut clut for output type */
+ /* This is not mandated for V2.4.0 Display profiles, but add it anyway */
+ if (allintents) {
+ icmLut *wo;
+ out_b2a_callback cx;
+ double gres = 0.0;
+
+ cx.verb = verb;
+ cx.pcsspace = wantLab ? icSigLabData : icSigXYZData;
+ cx.devspace = devspace;
+ cx.x = (icxLuLut *)AtoB; /* A2B icxLuLut */
+
+ if (verb)
+ printf("Creating gamut boundary table\n");
+
+ /* Need to switch AtoB to be override Lab PCS */
+ /* Do this the dirty way, by delving into xicclu and icclu. Alternatively */
+ /* we could create an xlut set method, delete AtoB and recreate it, */
+ /* or fix get_gamut to independently override convert to icmSigLabData */
+ /* ~~~~~~~~999 should fix this !!! */
+ cx.x->outs = icSigLabData;
+ cx.x->pcs = icSigLabData;
+ cx.x->plu->e_outSpace = icSigLabData;
+ cx.x->plu->e_pcs = icSigLabData;
+ cx.wantLab = wantLab; /* Copy PCS flag over */
+
+ if (oquality == 3) { /* Ultra High */
+ gres = 8.0;
+ } else if (oquality == 2) { /* High */
+ gres = 9.0;
+ } else if (oquality == 1) { /* Medium */
+ gres = 10.0;
+ } else if (oquality == 0) { /* Low quality */
+ gres = 12.0;
+ } else { /* Extremely low */
+ gres = 15.0;
+ }
+
+ /* Creat a gamut surface */
+ if ((cx.gam = AtoB->get_gamut(AtoB, gres)) == NULL)
+ error("Get_gamut failed: %d, %s",AtoB->pp->errc,AtoB->pp->err);
+
+ if ((wo = (icmLut *)wr_icco->read_tag(
+ wr_icco, icSigGamutTag)) == NULL)
+ error("read_tag failed: %d, %s",wr_icco->errc,wr_icco->err);
+
+ if (cx.verb) {
+ unsigned int ui;
+ cx.count = 0;
+ cx.last = -1;
+ for (cx.total = 1, ui = 0; ui < wo->inputChan; ui++, cx.total *= wo->clutPoints)
+ ;
+ printf(" 0%%"); fflush(stdout);
+ }
+#ifndef DEBUG_ONE /* Skip this when debugging */
+ if (wo->set_tables(wo,
+ ICM_CLUT_SET_EXACT,
+ &cx, /* Context */
+ cx.pcsspace, /* Input color space */
+ icSigGrayData, /* Output color space */
+ out_b2a_input, /* Input transform PCS->PCS' */
+ NULL, NULL, /* Use default Lab' range */
+ PCSp_bdist, /* Lab' -> Boundary distance */
+ NULL, NULL, /* Use default Device' range */
+ gamut_output) != 0) /* Boundary distance to out of gamut value */
+ error("Setting 16 bit PCS->Device Gamut Lut failed: %d, %s",wr_icco->errc,wr_icco->err);
+#endif /* !DEBUG_ONE */
+ if (cx.verb) {
+ printf("\n");
+ }
+#ifdef WARN_CLUT_CLIPPING /* Print warning if setting clut clips */
+ if (wr_icco->warnc)
+ warning("Values clipped in setting Gamut LUT");
+#endif /* WARN_CLUT_CLIPPING */
+
+ cx.gam->del(cx.gam); /* Done with gamut object */
+ cx.gam = NULL;
+
+ if (verb)
+ printf("Done gamut boundary table\n");
+ }
+#endif /* !DISABLE_GAMUT_TAG */
+
+ /* Free up xicc stuff */
+ AtoB->del(AtoB); /* Done with device to PCS lookup */
+ wr_xicc->del(wr_xicc);
+
+ }
+ /* Gamma/Shaper + matrix profile */
+ /* or XYZ cLUT with matrix as well. */
+ if (!isLut || mtxtoo) {
+ xicc *wr_xicc; /* extention object */
+ icxLuBase *xluo; /* Forward ixcLu */
+ int flags = 0;
+
+ /* Wrap with an expanded icc */
+ if ((wr_xicc = new_xicc(wr_icco)) == NULL)
+ error("Creation of xicc failed");
+
+ if (verb)
+ flags |= ICX_VERBOSE;
+
+ if (ptype == prof_matonly)
+ flags |= ICX_NO_IN_SHP_LUTS; /* Make it linear */
+
+ if (clipprims)
+ flags |= ICX_CLIP_WB | ICX_CLIP_PRIMS;
+
+ flags |= ICX_SET_WHITE | ICX_SET_BLACK; /* Compute & use white & black */
+
+ if (!mtxtoo) /* Write matrix white/black/Luminance if no cLUT */
+ flags |= ICX_WRITE_WBL;
+
+ /* Setup Device -> XYZ conversion (Fwd) object from scattered data. */
+ if ((xluo = wr_xicc->set_luobj(
+ wr_xicc, icmFwd, isdisp ? icmDefaultIntent : icRelativeColorimetric,
+ icmLuOrdRev,
+ flags, /* Compute white & black */
+ npat, npat, tpat, NULL, dispLuminance, -1.0, smooth, avgdev,
+ NULL, oink, cal, iquality)) == NULL)
+ error("%d, %s",wr_xicc->errc, wr_xicc->err);
+
+ /* Free up xicc stuff */
+ xluo->del(xluo);
+
+ /* Set the ColorantTable PCS values */
+ if (!mtxtoo) {
+ unsigned int i;
+ icmColorantTable *wo;
+ double dv[MAX_CHAN];
+
+ /* Get lookup object simply for fwd_relpcs_outpcs() */
+ if ((xluo = wr_xicc->get_luobj(wr_xicc, ICX_CLIP_NEAREST, icmFwd,
+ icRelativeColorimetric, icmSigDefaultData,
+ icmLuOrdNorm, NULL, NULL)) == NULL)
+ error ("%d, %s",wr_xicc->errc, wr_xicc->err);
+
+ if ((wo = (icmColorantTable *)wr_icco->read_tag(
+ wr_icco, icSigColorantTableTag)) == NULL)
+ error("read_tag failed: %d, %s",wr_icco->errc,wr_icco->err);
+
+ for (i = 0; i < wo->count; i++)
+ dv[i] = 0.0;
+
+ /* Lookup the colorant PCS values the recommended ICC way */
+ for (i = 0; i < wo->count; i++) {
+ dv[i] = 1.0;
+ xluo->lookup(xluo, wo->data[i].pcsCoords, dv);
+ /* Matrix profile can produce -ve values not representable by 16 bit XYZ */
+ icmClipXYZ(wo->data[i].pcsCoords,wo->data[i].pcsCoords);
+ dv[i] = 0.0;
+ }
+ xluo->del(xluo);
+ }
+
+ wr_xicc->del(wr_xicc);
+ }
+
+ /* We're done with any cal now */
+ if (cal != NULL)
+ cal->del(cal);
+
+ /* Write the file (including all tags) out */
+ if ((rv = wr_icco->write(wr_icco,wr_fp,0)) != 0) {
+ error("Write file: %d, %s",rv,wr_icco->err);
+ }
+
+ /* Close the file */
+ wr_icco->del(wr_icco);
+ wr_fp->del(wr_fp);
+
+ /* Check the forward profile accuracy against the data points */
+ if (verb || verify) {
+ icmFile *rd_fp;
+ icc *rd_icco;
+ icmLuBase *luo;
+ double merr = 0.0;
+ double rerr = 0.0;
+ double aerr = 0.0;
+ double nsamps = 0.0;
+
+ /* Open up the file for reading */
+ if ((rd_fp = new_icmFileStd_name(file_name,"r")) == NULL)
+ error("Write: Can't open file '%s'",file_name);
+
+ if ((rd_icco = new_icc()) == NULL)
+ error("Write: Creation of ICC object failed");
+
+ /* Read the header and tag list */
+ if ((rv = rd_icco->read(rd_icco,rd_fp,0)) != 0)
+ error("Read: %d, %s",rv,rd_icco->err);
+
+ /* Get the Fwd table */
+ if ((luo = rd_icco->get_luobj(rd_icco, icmFwd, icAbsoluteColorimetric,
+ icSigLabData, icmLuOrdNorm)) == NULL) {
+ error("%d, %s",rd_icco->errc, rd_icco->err);
+ }
+
+ for (i = 0; i < npat; i++) {
+ double out[3], ref[3];
+ double mxd;
+
+ /* Lookup the profiles PCS for out test patch point */
+ if (luo->lookup(luo, out, tpat[i].p) > 1)
+ error("%d, %s",rd_icco->errc,rd_icco->err);
+
+ /* Our tpat data might be in XYZ, so generate an Lab ref value */
+ if (!wantLab) { /* Convert test patch result XYZ to PCS (D50 Lab) */
+ icmXYZ2Lab(&icmD50, ref, tpat[i].v);
+
+ } else {
+ ref[0] = tpat[i].v[0];
+ ref[1] = tpat[i].v[1];
+ ref[2] = tpat[i].v[2];
+ }
+
+ if (verify && verb) {
+ if (devspace == icSigCmykData) {
+ printf("[%f] %f %f %f %f -> %f %f %f should be %f %f %f\n",
+ icmLabDE(ref, out),
+ tpat[i].p[0],tpat[i].p[1],tpat[i].p[2],tpat[i].p[3],
+ out[0],out[1],out[2],
+ ref[0],ref[1],ref[2]);
+ } else {
+ printf("[%f] %f %f %f -> %f %f %f should be %f %f %f\n",
+ icmLabDE(ref, out),
+ tpat[i].p[0],tpat[i].p[1],tpat[i].p[2],
+ out[0],out[1],out[2],
+ ref[0],ref[1],ref[2]);
+ }
+ }
+
+ /* Check the result */
+ mxd = icmLabDE(ref, out);
+ if (mxd > merr)
+ merr = mxd;
+
+ rerr += mxd * mxd;
+ aerr += mxd;
+ nsamps++;
+ }
+ rerr = sqrt(rerr/nsamps);
+ aerr /= nsamps;
+ printf("Profile check complete, peak err = %f, avg err = %f, RMS = %f\n",merr,aerr,rerr);
+
+ /* Done with lookup object */
+ luo->del(luo);
+
+ /* Close the file */
+ rd_icco->del(rd_icco);
+ rd_fp->del(rd_fp);
+ }
+
+ free(tpat);
+}
+
diff --git a/profile/simpprof.c b/profile/simpprof.c
new file mode 100644
index 0000000..61128ff
--- /dev/null
+++ b/profile/simpprof.c
@@ -0,0 +1,433 @@
+
+/*
+ * Argyll Color Correction System
+ * Simple CMYK profile generator.
+ *
+ * Author: Graeme W. Gill
+ * Date: 9/11/96
+ *
+ * Copyright 1996, 2002 Graeme W. Gill
+ * All rights reserved.
+ *
+ * This material is licenced under the GNU AFFERO GENERAL PUBLIC LICENSE Version 3 :-
+ * see the License.txt file for licencing details.
+ */
+
+/* This program generates a simple mathematical profile for a CMYK device. */
+/* It is intended for use in bootstrapping the test chart generation. */
+
+#define VERSION "1.1"
+
+#undef DEBUG
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <math.h>
+#include <sys/types.h>
+#include <time.h>
+#include "../cgats/cgats.h"
+#include "../numlib/numlib.h"
+#include "icc.h"
+
+/* A color structure */
+/* This holds the test patch results */
+typedef struct {
+ double c,m,y,k;
+ double bc[8]; /* Gamma corrected blend coefficients of cmyk */
+ double Lab[3];
+ double err; /* Delta E squared */
+} col;
+
+/* Structure to hold data for optimization function */
+struct _edatas {
+ col *cols; /* Pointer to table of patch results */
+ int npat; /* Number of patches */
+ int xyzi; /* current xyz index */
+ double gam[4]; /* Gamma values */
+ double k[3][8]; /* Primary combination values */
+ }; typedef struct _edatas edatas;
+
+/* Definition of the power optimization function handed to powell() */
+/* This function is for optimising the gamma function */
+double efunc1(void *edata, double p[]) {
+ edatas *ed = (edatas *)edata;
+ double rv;
+ col *cp;
+ for (rv = 0.0, cp = &ed->cols[ed->npat-1]; cp >= &ed->cols[0]; cp--) {
+ double cc,mm,yy,kk;
+ double nc,nm,ny,nk;
+ double XYZ[3], Lab[3];
+ int j;
+
+ /* Apply gamma correction to each input */
+ for (j = 0; j < 4; j++) {
+ if (p[j] < 0.2)
+ p[j] = 0.2;
+ else if (p[j] > 5.0)
+ p[j] = 5.0;
+ }
+ cc = pow(cp->c, p[0]);
+ nc = 1.0 - cc;
+ mm = pow(cp->m, p[1]);
+ nm = 1.0 - mm;
+ yy = pow(cp->y, p[2]);
+ ny = 1.0 - yy;
+ kk = pow(cp->k, p[3]);
+ nk = 1.0 - kk;
+
+ /* Then interpolate between all combinations of primaries. */
+ /* plus one that stands for all that are close to black */
+ for (j = 0; j < 3; j++) {
+ XYZ[j] = nc * nm * ny * nk * ed->k[j][0]
+ + nc * nm * yy * nk * ed->k[j][1]
+ + nc * mm * ny * nk * ed->k[j][2]
+ + nc * mm * yy * nk * ed->k[j][3]
+ + cc * nm * ny * nk * ed->k[j][4]
+ + cc * nm * yy * nk * ed->k[j][5]
+ + cc * mm * ny * nk * ed->k[j][6]
+ + (cc * mm * yy * nk + kk) * ed->k[j][7];
+ }
+ icmXYZ2Lab(&icmD50, Lab, XYZ);
+ rv += cp->err = icmLabDEsq(Lab, cp->Lab);
+ }
+printf("Efunc1 returning %f\n",rv);
+ return rv;
+}
+
+/* Definition of the primary coefficient optimization function handed to powell() */
+/* This function is for optimising the primary values */
+double efunc2(void *edata, double p[]) {
+ edatas *ed = (edatas *)edata;
+ int j, os = ed->xyzi;
+ double tt, rv;
+ col *cp;
+
+ rv = 0.0;
+
+ for (j = 0; j < 8; j++) {
+ if (p[j] < 0.0) { /* Protect against silly values */
+ p[j] = 0.0;
+ rv += 1000.0;
+ }
+ else if (p[j] > 1.5) {
+ p[j] = 1.5;
+ rv += 1000.0;
+ }
+ ed->k[os][j] = p[j]; /* Load into current */
+ }
+
+ /* Compute error */
+ for (cp = &ed->cols[ed->npat-1]; cp >= &ed->cols[0]; cp--) {
+ double XYZ[3], Lab[3];
+
+ for (os = 0; os < 3; os++) {
+
+ /* Interpolate between all combinations of primaries. */
+ for (tt = 0.0, j = 0; j < 8; j++)
+ tt += cp->bc[j] * ed->k[os][j];
+ XYZ[os] = tt;
+ }
+ icmXYZ2Lab(&icmD50, Lab, XYZ);
+ rv += cp->err = icmLabDEsq(Lab, cp->Lab);
+ }
+printf("Efunc2 returning %f\n",rv);
+ return rv;
+}
+
+/* Calculate blend coefficients */
+void calc_bc(edatas *ed) {
+ int j;
+ col *cp;
+ for (cp = &ed->cols[ed->npat-1]; cp >= &ed->cols[0]; cp--) {
+ double cc,mm,yy,kk;
+ double nc,nm,ny,nk;
+
+ /* Apply gamma correction to each input, and calculate complement */
+ for (j = 0; j < 4; j++) {
+ if (ed->gam[j] < 0.2)
+ ed->gam[j] = 0.2;
+ else if (ed->gam[j] > 5.0)
+ ed->gam[j] = 2.0;
+ }
+ cc = pow(cp->c, ed->gam[0]);
+ nc = 1.0 - cc;
+ mm = pow(cp->m, ed->gam[1]);
+ nm = 1.0 - mm;
+ yy = pow(cp->y, ed->gam[2]);
+ ny = 1.0 - yy;
+ kk = pow(cp->k, ed->gam[3]);
+ nk = 1.0 - kk;
+
+ /* Go through all 8 combinations */
+ cp->bc[0] = nc * nm * ny * nk;
+ cp->bc[1] = nc * nm * yy * nk;
+ cp->bc[2] = nc * mm * ny * nk;
+ cp->bc[3] = nc * mm * yy * nk;
+ cp->bc[4] = cc * nm * ny * nk;
+ cp->bc[5] = cc * nm * yy * nk;
+ cp->bc[6] = cc * mm * ny * nk;
+ cp->bc[7] = cc * mm * yy * nk + kk;
+ }
+}
+
+void usage(void);
+
+int main(int argc, char *argv[])
+{
+ int i,j,k;
+ int fa,nfa; /* current argument we're looking at */
+ int verb = 0;
+ static char inname[200] = { 0 }; /* Input cgats file base name */
+ static char outname[200] = { 0 }; /* Output cgats file base name */
+ cgats *icg; /* input cgats structure */
+ cgats *ocg; /* output cgats structure */
+ time_t clk = time(0);
+ struct tm *tsp = localtime(&clk);
+ char *atm = asctime(tsp); /* Ascii time */
+ int ti; /* Temporary index */
+ edatas ed; /* Optimising function data structure */
+ double resid[4];
+ double presid,dresid;
+ double sarea;
+
+ error_program = argv[0];
+ if (argc <= 1)
+ usage();
+
+ /* Process the arguments */
+ for(fa = 1;fa < argc;fa++)
+ {
+ nfa = fa; /* skip to nfa if next argument is used */
+ if (argv[fa][0] == '-') /* Look for any flags */
+ {
+ char *na = NULL; /* next argument after flag, null if none */
+
+ if (argv[fa][2] != '\000')
+ na = &argv[fa][2]; /* next is directly after flag */
+ else
+ {
+ if ((fa+1) < argc)
+ {
+ if (argv[fa+1][0] != '-')
+ {
+ nfa = fa + 1;
+ na = argv[nfa]; /* next is seperate non-flag argument */
+ }
+ }
+ }
+
+ if (argv[fa][1] == '?')
+ usage();
+
+ else if (argv[fa][1] == 'v' || argv[fa][1] == 'V')
+ verb = 1;
+ else
+ usage();
+ }
+ else
+ break;
+ }
+
+ /* Get the file name argument */
+ if (fa >= argc || argv[fa][0] == '-') usage();
+ strcpy(inname,argv[fa]);
+ strcat(inname,".ti3");
+ strcpy(outname,argv[fa]);
+ strcat(outname,".pr1");
+
+ icg = new_cgats(); /* Create a CGATS structure */
+ icg->add_other(icg, "CTI3"); /* our special input type is Calibration Target Information 3 */
+
+ if (icg->read_name(icg, inname))
+ error("CGATS file read error : %s",icg->err);
+
+ if (icg->ntables == 0 || icg->t[0].tt != tt_other || icg->t[0].oi != 0)
+ error ("Input file isn't a CTI3 format file");
+ if (icg->ntables != 1)
+ error ("Input file doesn't contain exactly one table");
+
+ if ((ed.npat = icg->t[0].nsets) <= 0)
+ error ("No sets of data");
+
+ if (verb) {
+ printf("No of test patches = %d\n",ed.npat);
+ }
+
+ if ((ed.cols = (col *)malloc(sizeof(col) * ed.npat)) == NULL)
+ error("Malloc failed!");
+
+ /* Setup output cgats file */
+ /* This is a simple interpolation CMYK -> XYZ device profile */
+ ocg = new_cgats(); /* Create a CGATS structure */
+ ocg->add_other(ocg, "PROF1"); /* our special type is Profile type 1 */
+ ocg->add_table(ocg, tt_other, 0); /* Start the first table */
+
+ ocg->add_kword(ocg, 0, "DESCRIPTOR", "Argyll Calibration Device Profile Type 1",NULL);
+ ocg->add_kword(ocg, 0, "ORIGINATOR", "Argyll sprof", NULL);
+ atm[strlen(atm)-1] = '\000'; /* Remove \n from end */
+ ocg->add_kword(ocg, 0, "CREATED",atm, NULL);
+
+ /* Figure out the color space */
+ if ((ti = icg->find_kword(icg, 0, "COLOR_REP")) < 0)
+ error ("Input file doesn't contain keyword COLOR_REPS");
+ if (strcmp(icg->t[0].kdata[ti],"CMYK_XYZ") == 0) {
+ int ci, mi, yi, ki;
+ int Xi, Yi, Zi;
+ if ((ci = icg->find_field(icg, 0, "CMYK_C")) < 0)
+ error ("Input file doesn't contain field CMYK_C");
+ if (icg->t[0].ftype[ci] != r_t)
+ error ("Field CMYK_C is wrong type");
+ if ((mi = icg->find_field(icg, 0, "CMYK_M")) < 0)
+ error ("Input file doesn't contain field CMYK_M");
+ if (icg->t[0].ftype[mi] != r_t)
+ error ("Field CMYK_M is wrong type");
+ if ((yi = icg->find_field(icg, 0, "CMYK_Y")) < 0)
+ error ("Input file doesn't contain field CMYK_Y");
+ if (icg->t[0].ftype[yi] != r_t)
+ error ("Field CMYK_Y is wrong type");
+ if ((ki = icg->find_field(icg, 0, "CMYK_K")) < 0)
+ error ("Input file doesn't contain field CMYK_K");
+ if (icg->t[0].ftype[ki] != r_t)
+ error ("Field CMYK_K is wrong type");
+ if ((Xi = icg->find_field(icg, 0, "XYZ_X")) < 0)
+ error ("Input file doesn't contain field XYZ_X");
+ if (icg->t[0].ftype[Xi] != r_t)
+ error ("Field XYZ_X is wrong type");
+ if ((Yi = icg->find_field(icg, 0, "XYZ_Y")) < 0)
+ error ("Input file doesn't contain field XYZ_Y");
+ if (icg->t[0].ftype[Yi] != r_t)
+ error ("Field XYZ_Y is wrong type");
+ if ((Zi = icg->find_field(icg, 0, "XYZ_Z")) < 0)
+ error ("Input file doesn't contain field XYZ_Z");
+ if (icg->t[0].ftype[Zi] != r_t)
+ error ("Field XYZ_Z is wrong type");
+ for (i = 0; i < ed.npat; i++) {
+ double XYZ[3];
+ ed.cols[i].c = *((double *)icg->t[0].fdata[i][ci]) / 100.0;
+ ed.cols[i].m = *((double *)icg->t[0].fdata[i][mi]) / 100.0;
+ ed.cols[i].y = *((double *)icg->t[0].fdata[i][yi]) / 100.0;
+ ed.cols[i].k = *((double *)icg->t[0].fdata[i][ki]) / 100.0;
+ XYZ[0] = *((double *)icg->t[0].fdata[i][Xi]) / 100.0;
+ XYZ[1] = *((double *)icg->t[0].fdata[i][Yi]) / 100.0;
+ XYZ[2] = *((double *)icg->t[0].fdata[i][Zi]) / 100.0;
+ icmXYZ2Lab(&icmD50, ed.cols[i].Lab, XYZ);
+ }
+
+ /* Initialise the model */
+ ed.gam[0] = 1.0; /* First four are CMYK gamma values */
+ ed.gam[1] = 1.0;
+ ed.gam[2] = 1.0;
+ ed.gam[3] = 1.0;
+
+ /* Initialise interpolation end points for each combination of primary, */
+ /* with all combinations close to black being represented by param[7]. */
+ ed.k[0][0] = .82; ed.k[1][0] = .83; ed.k[2][0] = .75; /* White */
+ ed.k[0][1] = .66; ed.k[1][1] = .72; ed.k[2][1] = .05; /* Y */
+ ed.k[0][2] = .27; ed.k[1][2] = .12; ed.k[2][2] = .06; /* M */
+ ed.k[0][3] = .27; ed.k[1][3] = .12; ed.k[2][3] = .00; /* MY */
+ ed.k[0][4] = .09; ed.k[1][4] = .13; ed.k[2][4] = .44; /* C */
+ ed.k[0][5] = .03; ed.k[1][5] = .10; ed.k[2][5] = .04; /* C Y */
+ ed.k[0][6] = .02; ed.k[1][6] = .01; ed.k[2][6] = .05; /* CM */
+ ed.k[0][7] = .01; ed.k[1][7] = .01; ed.k[2][7] = .01; /* Black */
+
+ sarea = 0.3;
+ presid = dresid = 100.0;
+ for (k=0; /* dresid > 0.0001 && */ k < 40; k++) { /* Untill we're done */
+ double sresid;
+ double sr[8];
+ double p[8];
+
+ /* Adjust the gamma */
+ for (i = 0; i < 4; i++)
+ sr[i] = 0.1; /* Device space search radius */
+ if (powell(&resid[3], 4, &ed.gam[0], sr, 0.1, 1000, efunc1, (void *)&ed, NULL, NULL) != 0)
+ error ("Powell failed");
+
+ /* Adjust the primaries */
+ calc_bc(&ed); /* Calculate blend coefficients */
+ for (i = 0; i < 8; i++)
+ sr[i] = 0.2; /* Device space search radius */
+ sresid = 99.0;
+ for (j = 0; j < 3; j++) { /* For each of X, Y and Z */
+ ed.xyzi = j;
+
+ for (i = 0; i < 8; i++)
+ p[i] = ed.k[j][i];
+printf("##############\n");
+printf("XYZ = %d\n",j);
+ if (powell(&resid[j], 8, p, sr, 0.1, 1000, efunc2, (void *)&ed, NULL, NULL) != 0)
+ error ("Powell failed");
+
+ for (i = 0; i < 8; i++)
+ ed.k[j][i] = p[i];
+
+ if (sresid > resid[j])
+ sresid = resid[j];
+ }
+ dresid = presid - sresid;
+ if (dresid < 0.0)
+ dresid = 100.0;
+ presid = sresid;
+printf("~1 presid = %f, sresid = %f, dresid = %f\n",presid, sresid, dresid);
+ }
+
+ /* Fields we want */
+ ocg->add_kword(ocg, 0, "DSPACE","CMYK", NULL);
+ ocg->add_kword(ocg, 0, "DTYPE","PRINTER", NULL);
+ ocg->add_field(ocg, 0, "PARAM_ID", i_t);
+ ocg->add_field(ocg, 0, "PARAM", r_t);
+
+ /* Output model parameters */
+ for (j = 0; j < 4; j++)
+ ocg->add_set(ocg, 0, j, ed.gam[j]);
+
+ for (j = 0; j < 3; j++) {
+ for (i = 0; i < 8; i++)
+ ocg->add_set(ocg, 0, 10 * (j + 1) + i, 100.0 * ed.k[j][i]);
+ }
+
+ if (verb) {
+ double aver = 0.0;
+ double maxer = 0.0;
+ for (i = 0; i < ed.npat; i++) {
+ double err = sqrt(ed.cols[i].err);
+ if (err > maxer)
+ maxer = err;
+ aver += err;
+ }
+ aver = aver/((double)i);
+ printf("Average fit error = %f, maximum = %f\n",aver,maxer);
+ }
+ } else if (strcmp(icg->t[0].kdata[ti],"RGB") == 0) {
+ error ("We can't handle RGB !");
+ } else if (strcmp(icg->t[0].kdata[ti],"W") == 0) {
+ error ("We can't handle Grey !");
+ } else
+ error ("Input file keyword COLOR_REPS has unknown value");
+
+ if (ocg->write_name(ocg, outname))
+ error("Write error : %s",ocg->err);
+
+ free(ed.cols);
+ ocg->del(ocg); /* Clean up */
+ icg->del(icg); /* Clean up */
+
+ return 0;
+}
+
+/******************************************************************/
+/* Error/debug output routines */
+/******************************************************************/
+
+void
+usage(void) {
+ fprintf(stderr,"Create Simple CMYK Device Profile, Version %s\n",VERSION);
+ fprintf(stderr,"Author: Graeme W. Gill, licensed under the AGPL Version 3\n");
+ fprintf(stderr,"usage: %s [-v] outfile\n",error_program);
+ fprintf(stderr," -v Verbose mode\n");
+ fprintf(stderr," outfile Base name for input.tr3/output.pr1 file\n");
+ exit(1);
+ }
+
+
diff --git a/profile/splitti3.c b/profile/splitti3.c
new file mode 100644
index 0000000..0e170c1
--- /dev/null
+++ b/profile/splitti3.c
@@ -0,0 +1,400 @@
+/*
+ * Argyll Color Correction System
+ * Split a .ti3 (or other CGATS like) file into two parts.
+ *
+ * Author: Graeme W. Gill
+ * Date: 14/12/2005
+ *
+ * Copyright 2005, 2010 Graeme W. Gill
+ * All rights reserved.
+ *
+ * This material is licenced under the GNU AFFERO GENERAL PUBLIC LICENSE Version 3 :-
+ * see the License.txt file for licencing details.
+ */
+
+/*
+ * This program takes in a CGATS .ti3 file, and splits it into
+ * two .ti3 files, spreading the readings between them.
+ * This is intended for use in verifying the profiler.
+ */
+
+/*
+ * TTBD:
+
+ This doesn't pass calibration table information through.
+ (ie. should copy all tables after the first.)
+
+ Write a companion "combineti3" to merge .ti3's together.
+ */
+
+#undef DEBUG
+
+#define verbo stdout
+
+#include <stdio.h>
+#include <string.h>
+#if defined(__IBMC__)
+#include <float.h>
+#endif
+#include <sys/types.h>
+#include <time.h>
+#include "copyright.h"
+#include "aconfig.h"
+#include "numlib.h"
+#include "cgats.h"
+#include "xicc.h"
+#include "insttypes.h"
+#include "sort.h"
+
+void
+usage(void) {
+ fprintf(stderr,"Split a .ti3 into two, Version %s\n",ARGYLL_VERSION_STR);
+ fprintf(stderr,"Author: Graeme W. Gill, licensed under the AGPL Version 3\n");
+ fprintf(stderr,"usage: splitcgats [-options] input.ti3 output1.ti3 output2.ti3\n");
+ fprintf(stderr," -v Verbose - print each patch value\n");
+ fprintf(stderr," -n no Put no sets in first file, and balance in second file.\n");
+ fprintf(stderr," -p percent Put percent%% sets in first file, and balance in second file. (def. 50%%)\n");
+ fprintf(stderr," -w Put white patches in both files.\n");
+ fprintf(stderr," -r seed Use given random seed.\n");
+ fprintf(stderr," input.ti3 File to be split up.\n");
+ fprintf(stderr," output1.ti3 First output file\n");
+ fprintf(stderr," output2.ti3 Second output file\n");
+ exit(1);
+}
+
+int main(int argc, char *argv[]) {
+ int fa,nfa; /* current argument we're looking at */
+ int verb = 0;
+ int numb = -1; /* Number to put in first */
+ double prop = 0.5; /* Proportion to put in first */
+ int dow = 0; /* Put white patches in both files */
+ int seed = 0x12345678;
+ int doseed = 0;
+
+ cgats *cgf = NULL; /* cgats file data */
+ char in_name[MAXNAMEL+1]; /* Patch filename */
+
+ cgats *cg1 = NULL; /* cgats file data */
+ char out_name1[MAXNAMEL+4+1]; /* VRML name */
+ cgats *cg2 = NULL; /* cgats file data */
+ char out_name2[MAXNAMEL+4+1]; /* VRML name */
+
+ cgats_set_elem *setel; /* Array of set value elements */
+ int *flags; /* Point to destination of set */
+
+ int i, j, n;
+
+ error_program = "splitti3";
+
+ if (argc <= 1)
+ usage();
+
+ /* Process the arguments */
+ for(fa = 1;fa < argc;fa++) {
+
+ nfa = fa; /* skip to nfa if next argument is used */
+ if (argv[fa][0] == '-') { /* Look for any flags */
+ char *na = NULL; /* next argument after flag, null if none */
+
+ if (argv[fa][2] != '\000')
+ na = &argv[fa][2]; /* next is directly after flag */
+ else {
+ if ((fa+1) < argc) {
+ if (argv[fa+1][0] != '-') {
+ nfa = fa + 1;
+ na = argv[nfa]; /* next is seperate non-flag argument */
+ }
+ }
+ }
+
+ if (argv[fa][1] == '?') {
+ usage();
+
+ } else if (argv[fa][1] == 'v' || argv[fa][1] == 'V') {
+ verb = 1;
+
+ } else if (argv[fa][1] == 'n' || argv[fa][1] == 'N') {
+ fa = nfa;
+ if (na == NULL) usage();
+ numb = atoi(na);
+ if (numb < 0) usage();
+
+ } else if (argv[fa][1] == 'p' || argv[fa][1] == 'P') {
+ fa = nfa;
+ if (na == NULL) usage();
+ prop = atoi(na);
+ if (prop < 0) usage();
+ prop = prop / 100.0;
+
+ } else if (argv[fa][1] == 'w' || argv[fa][1] == 'W') {
+ dow = 1;
+
+ } else if (argv[fa][1] == 'r' || argv[fa][1] == 'R') {
+ fa = nfa;
+ if (na == NULL) usage();
+ seed = atoi(na);
+ doseed = 1;
+ }
+
+ else
+ usage();
+ } else
+ break;
+ }
+
+ if (doseed)
+ rand32(seed); /* Init seed deterministicaly */
+ else
+ rand32(time(NULL)); /* Init seed randomly */
+
+ /* Get the file name arguments */
+ if (fa >= argc || argv[fa][0] == '-') usage();
+ strncpy(in_name,argv[fa++],MAXNAMEL); in_name[MAXNAMEL] = '\000';
+
+ if (fa >= argc || argv[fa][0] == '-') usage();
+ strncpy(out_name1,argv[fa++],MAXNAMEL); out_name1[MAXNAMEL] = '\000';
+
+ if (fa >= argc || argv[fa][0] == '-') usage();
+ strncpy(out_name2,argv[fa++],MAXNAMEL); out_name2[MAXNAMEL] = '\000';
+
+ if ((cgf = new_cgats()) == NULL)
+ error("Failed to create cgats object");
+ cgf->add_other(cgf, ""); /* Allow any signature file */
+
+ if (cgf->read_name(cgf, in_name))
+ error("CGATS file '%s' read error : %s",in_name,cgf->err);
+
+ if (cgf->ntables < 1)
+ error ("Input file '%s' doesn't contain at least one table",in_name);
+
+ /* Create the two output files */
+ if ((cg1 = new_cgats()) == NULL)
+ error("Failed to create cgats object");
+ if ((cg2 = new_cgats()) == NULL)
+ error("Failed to create cgats object");
+
+ /* Duplicate the type of the file */
+ if (cgf->t[0].tt == cgats_X) {
+ cg1->add_other(cg1, cgf->cgats_type);
+ cg1->add_table(cg1, tt_other, 0);
+ cg2->add_other(cg2, cgf->cgats_type);
+ cg2->add_table(cg2, tt_other, 0);
+ } else if (cgf->t[0].tt == tt_other) {
+ cg1->add_other(cg1, cgf->others[cgf->t[0].oi]);
+ cg1->add_table(cg1, tt_other, 0);
+ cg2->add_other(cg2, cgf->others[cgf->t[0].oi]);
+ cg2->add_table(cg2, tt_other, 0);
+ } else {
+ cg1->add_table(cg1, cgf->t[0].tt, 0);
+ cg2->add_table(cg1, cgf->t[0].tt, 0);
+ }
+
+ /* Duplicate all the keywords */
+ for (i = 0; i < cgf->t[0].nkwords; i++) {
+ cg1->add_kword(cg1, 0, cgf->t[0].ksym[i], cgf->t[0].kdata[i], NULL);
+ cg2->add_kword(cg2, 0, cgf->t[0].ksym[i], cgf->t[0].kdata[i], NULL);
+ }
+
+ /* Duplicate all of the fields */
+ for (i = 0; i < cgf->t[0].nfields; i++) {
+ cg1->add_field(cg1, 0, cgf->t[0].fsym[i], cgf->t[0].ftype[i]);
+ cg2->add_field(cg2, 0, cgf->t[0].fsym[i], cgf->t[0].ftype[i]);
+ }
+
+ if ((setel = (cgats_set_elem *)malloc(
+ sizeof(cgats_set_elem) * cgf->t[0].nfields)) == NULL)
+ error("Malloc failed!");
+
+ if ((flags = (int *)calloc(cgf->t[0].nsets, sizeof(int))) == NULL)
+ error("Malloc failed!");
+
+ if (numb < 0) { /* Use percentage */
+ numb = (int)(cgf->t[0].nsets * prop + 0.5);
+ }
+ if (numb > cgf->t[0].nsets)
+ numb = cgf->t[0].nsets;
+
+ if (verb)
+ printf("Putting %d sets in '%s' and %d in '%s'\n",numb,out_name1,cgf->t[0].nsets-numb,out_name2);
+
+ n = 0;
+
+ /* If dow, add white patches to both sets */
+ if (dow) {
+ int ti;
+ char *buf;
+ char *xyzfname[3] = { "XYZ_X", "XYZ_Y", "XYZ_Z" };
+ char *labfname[3] = { "LAB_L", "LAB_A", "LAB_B" };
+ char *outc;
+ int nmask;
+ int nchan;
+ char *bident;
+ int chix[ICX_MXINKS]; /* Device chanel indexes */
+ int pcsix[3]; /* PCS chanel indexes */
+ int isin = 0;
+ int isadd = 0;
+ int isLab = 0;
+
+ if ((ti = cgf->find_kword(cgf, 0, "DEVICE_CLASS")) < 0)
+ error ("Input file doesn't contain keyword DEVICE_CLASS");
+
+ if (strcmp(cgf->t[0].kdata[ti],"INPUT") == 0)
+ isin = 1;
+
+ if ((ti = cgf->find_kword(cgf, 0, "COLOR_REP")) < 0)
+ error("Input file doesn't contain keyword COLOR_REPS");
+
+ if ((buf = strdup(cgf->t[0].kdata[ti])) == NULL)
+ error("Malloc failed");
+
+ /* Split COLOR_REP into device and PCS space */
+ if ((outc = strchr(buf, '_')) == NULL)
+ error("COLOR_REP '%s' invalid", cgf->t[0].kdata[ti]);
+ *outc++ = '\000';
+
+ if (strcmp(outc, "XYZ") == 0) {
+ isLab = 0;
+ } else if (strcmp(outc, "LAB") == 0) {
+ isLab = 1;
+ } else
+ error("COLOR_REP '%s' invalid (Neither XYZ nor LAB)", cgf->t[0].kdata[ti]);
+
+ if ((nmask = icx_char2inkmask(buf)) == 0) {
+ error ("File '%s' keyword COLOR_REPS has unknown device value '%s'",in_name,buf);
+ }
+
+ if (nmask & ICX_ADDITIVE)
+ isadd = 1;
+
+ nchan = icx_noofinks(nmask);
+ bident = icx_inkmask2char(nmask, 0);
+
+ /* Find device fields */
+ for (j = 0; j < nchan; j++) {
+ int ii, imask;
+ char fname[100];
+
+ imask = icx_index2ink(nmask, j);
+ sprintf(fname,"%s_%s",nmask == ICX_W || nmask == ICX_K ? "GRAY" : bident,
+ icx_ink2char(imask));
+
+ if ((ii = cgf->find_field(cgf, 0, fname)) < 0)
+ error ("Input file doesn't contain field %s",fname);
+ if (cgf->t[0].ftype[ii] != r_t)
+ error ("Field %s is wrong type",fname);
+ chix[j] = ii;
+ }
+
+ /* Find PCS fields */
+ for (j = 0; j < 3; j++) {
+ int ii;
+
+ if ((ii = cgf->find_field(cgf, 0, isLab ? labfname[j] : xyzfname[j])) < 0)
+ error ("Input file doesn't contain field %s",isLab ? labfname[j] : xyzfname[j]);
+ if (cgf->t[0].ftype[ii] != r_t)
+ error ("Field %s is wrong type",isLab ? labfname[j] : xyzfname[j]);
+ pcsix[j] = ii;
+ }
+
+ if (isin) {
+ int wix = -1;
+ double wv = -1e60;
+ int pcsy = 1;
+
+ if (isLab)
+ pcsy = 0;
+
+ /* We assume that the white point is the patch with the */
+ /* highest L* or Y value. */
+ for (i = 0; i < cgf->t[0].nsets; i++) {
+ double val;
+
+ val = *((double *)cgf->t[0].fdata[i][pcsix[pcsy]]);
+ if (val > wv) {
+ wv = val;
+ wix = i;
+ }
+ }
+ if (wix > 0) {
+ n++;
+ flags[wix] = 3;
+ if (verb)
+ printf("Found input white patch index %d\n",wix);
+ }
+
+ } else {
+
+ if (isadd) {
+ for (i = 0; i < cgf->t[0].nsets; i++) {
+ for (j = 0; j < nchan; j++) {
+ if (*((double *)cgf->t[0].fdata[i][chix[j]]) < 99.99)
+ break;
+ }
+ if (j >= nchan) {
+ n++;
+ flags[i] = 3;
+ if (verb)
+ printf("Found additive white patch index %d\n",i);
+ }
+ }
+ } else {
+ for (i = 0; i < cgf->t[0].nsets; i++) {
+ for (j = 0; j < nchan; j++) {
+ if (*((double *)cgf->t[0].fdata[i][chix[j]]) > 0.01)
+ break;
+ }
+ if (j >= nchan) {
+ n++;
+ flags[i] = 3;
+ if (verb)
+ printf("Found subtractive white patch index %d\n",i);
+ }
+ }
+ }
+ }
+ free(bident);
+ }
+
+ /* Chose which of the sets go into file 1 and 2*/
+ for (;n < numb;) {
+ i = i_rand(0, cgf->t[0].nsets-1);
+ if (flags[i] == 0) {
+ flags[i] = 1;
+ n++;
+ }
+ }
+
+ /* Assume any patch not flagged goes to 2 */
+ for (i = 0; i < cgf->t[0].nsets; i++) {
+ if (flags[i] == 0)
+ flags[i] = 2;
+ }
+
+ /* Copy them approproately */
+ for (i = 0; i < cgf->t[0].nsets; i++) {
+ cgf->get_setarr(cgf, 0, i, setel);
+ if (flags[i] & 1) {
+ cg1->add_setarr(cg1, 0, setel);
+ }
+ if (flags[i] & 2) {
+ cg2->add_setarr(cg2, 0, setel);
+ }
+ }
+
+ /* Write out the files */
+ if (cg1->write_name(cg1, out_name1))
+ error("CGATS file '%s' write error : %s",out_name1,cg1->err);
+ if (cg2->write_name(cg2, out_name2))
+ error("CGATS file '%s' write error : %s",out_name2,cg2->err);
+
+
+ free(flags);
+ free(setel);
+
+ return 0;
+}
+
+
+
+
+
diff --git a/profile/txt2ti3.c b/profile/txt2ti3.c
new file mode 100644
index 0000000..9b425f3
--- /dev/null
+++ b/profile/txt2ti3.c
@@ -0,0 +1,830 @@
+
+/*
+ * Argyll Color Correction System
+ *
+ * Read in the RGB/CMYK CGATS device data from Gretag/Logo/X-Rite etc.
+ * and convert it into a .ti3 CGATs format suitable for the Argyll CMS.
+ *
+ * Derived from kodak2cgats.c
+ * Author: Graeme W. Gill
+ * Date: 16/11/00
+ *
+ * Copyright 2000 - 2010, Graeme W. Gill
+ * All rights reserved.
+ *
+ * This material is licenced under the GNU AFFERO GENERAL PUBLIC LICENSE Version 3 :-
+ * see the License.txt file for licencing details.
+ */
+
+/* TTBD
+
+ Need to add support for SPECTRAL_NM SPECTRAL_PCT type spectral values.
+ See /src/argyll/test/JosvanRiswick/R080505.cgt
+
+ Do we need to worry about normalising display values to Y = 100, or marking
+ them not normalised ?
+
+ Should have an option to output .ti1 files instead of .ti3.
+
+ */
+
+#undef DEBUG
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <math.h>
+#include <sys/types.h>
+#include <time.h>
+#include <string.h>
+#include <stdarg.h>
+#include "copyright.h"
+#include "aconfig.h"
+#include "cgats.h"
+#include "xspect.h"
+#include "insttypes.h"
+#include "numlib.h"
+
+void
+usage(char *mes) {
+ fprintf(stderr,"Convert Gretag/Logo or X-Rite ColorPport raw RGB or CMYK device profile data to Argyll CGATS data, Version %s\n",ARGYLL_VERSION_STR);
+ fprintf(stderr,"Author: Graeme W. Gill, licensed under the AGPL Version 3\n");
+ if (mes != NULL)
+ fprintf(stderr,"error: %s\n",mes);
+ fprintf(stderr,"usage: txt2ti3 [-v] [-l limit] [devfile] infile [specfile] outfile\n");
+/* fprintf(stderr," -v Verbose mode\n"); */
+ fprintf(stderr," -2 Create dummy .ti2 file as well\n");
+ fprintf(stderr," -l limit set ink limit, 0 - 400%% (default max in file)\n");
+ fprintf(stderr," -d Set type of device as Display, not Output\n");
+ fprintf(stderr," -i Set type of device as Input, not Output\n");
+ fprintf(stderr," [devfile] Input Device CMYK target file (typically file.txt)\n");
+ fprintf(stderr," infile Input CIE, Spectral or Device & Spectral file (typically file.txt)\n");
+ fprintf(stderr," [specfile] Input Spectral file (typically file.txt)\n");
+ fprintf(stderr," outbasename Output file basename for .ti3 and .ti2\n");
+ exit(1);
+ }
+
+int main(int argc, char *argv[])
+{
+ int i, j;
+ int fa,nfa; /* current argument we're looking at */
+ int verb = 0;
+ int out2 = 0; /* Create dumy .ti2 file output */
+ int disp = 0; /* nz if this is a display device */
+ int inp = 0; /* nz if this is an input device */
+ static char devname[200] = { 0 }; /* Input CMYK/Device .txt file (may be null) */
+ static char ciename[200] = { 0 }; /* Input CIE .txt file (may be null) */
+ static char specname[200] = { 0 }; /* Input Device / Spectral .txt file */
+ static char outname[200] = { 0 }; /* Output cgats .ti3 file base name */
+ static char outname2[200] = { 0 }; /* Output cgats .ti2 file base name */
+ cgats *cmy = NULL; /* Input RGB/CMYK reference file */
+ int f_id1 = -1, f_c, f_m, f_y, f_k = 0; /* Field indexes */
+ double dev_scale = 1.0; /* Device value scaling */
+ cgats *ncie = NULL; /* Input CIE readings file (may be Dev & spectral too) */
+ int f_id2, f_cie[3]; /* Field indexes */
+ cgats *spec = NULL; /* Input spectral readings (NULL if none) */
+ double spec_scale = 1.0; /* Spectral value scaling */
+ int f_id3 = 0; /* Field indexes */
+ int spi[100]; /* CGATS indexes for each wavelength */
+ cgats *ocg; /* output cgats structure for .ti3 */
+ cgats *ocg2; /* output cgats structure for .ti2 */
+ time_t clk = time(0);
+ struct tm *tsp = localtime(&clk);
+ char *atm = asctime(tsp); /* Ascii time */
+ int islab = 0; /* CIE is Lab rather than XYZ */
+ int specmin = 0, specmax = 0, specnum = 0; /* Min and max spectral in nm, inclusive */
+ int npat = 0; /* Number of patches */
+ int ndchan = 0; /* Number of device channels, 0 = no device, RGB = 3, CMYK = 4 */
+ int tlimit = -1; /* Not set */
+ double mxsum = -1.0; /* Maximim sum of inks found in file */
+ int mxsumix = 0;
+
+ error_program = "txt2ti3";
+
+ if (argc <= 1)
+ usage("Too few arguments");
+
+ /* Process the arguments */
+ for(fa = 1;fa < argc;fa++) {
+ nfa = fa; /* skip to nfa if next argument is used */
+ if (argv[fa][0] == '-') { /* Look for any flags */
+ char *na = NULL; /* next argument after flag, null if none */
+
+ if (argv[fa][2] != '\000')
+ na = &argv[fa][2]; /* next is directly after flag */
+ else {
+ if ((fa+1) < argc) {
+ if (argv[fa+1][0] != '-') {
+ nfa = fa + 1;
+ na = argv[nfa]; /* next is seperate non-flag argument */
+ }
+ }
+ }
+
+ if (argv[fa][1] == '?')
+ usage(NULL);
+
+ else if (argv[fa][1] == '2')
+ out2 = 1;
+
+ else if (argv[fa][1] == 'l' || argv[fa][1] == 'L') {
+ fa = nfa;
+ if (na == NULL) usage("No ink limit parameter");
+ tlimit = atoi(na);
+ if (tlimit < 1)
+ tlimit = -1;
+ }
+
+ else if (argv[fa][1] == 'd') {
+ disp = 1;
+ inp = 0;
+
+ } else if (argv[fa][1] == 'i') {
+ disp = 0;
+ inp = 1;
+
+ } else if (argv[fa][1] == 'v' || argv[fa][1] == 'V')
+ verb = 1;
+ else
+ usage("Unknown flag");
+ } else
+ break;
+ }
+
+ /* See how many arguments remain */
+ switch (argc - fa) {
+ case 2: /* Must be a new combined device + cie/spectral */
+ if (fa >= argc || argv[fa][0] == '-') usage("Bad dev filename");
+ strcpy(devname,argv[fa]);
+ strcpy(ciename,argv[fa++]);
+ if (fa >= argc || argv[fa][0] == '-') usage("Bad output filename");
+ strcpy(outname, argv[fa++]);
+ if (verb) printf("Single source file, assumed dev/cie/spectral\n");
+ break;
+ case 3: /* Device + cie or spectral */
+ /* or combined cie + spectral */
+ if (fa >= argc || argv[fa][0] == '-') usage("Bad dev filename");
+ strcpy(devname,argv[fa++]);
+ if (fa >= argc || argv[fa][0] == '-') usage("Bad cie/spec filename");
+ strcpy(ciename,argv[fa++]);
+ if (fa >= argc || argv[fa][0] == '-') usage("Bad output filename");
+ strcpy(outname, argv[fa++]);
+ if (verb) printf("Two source files, assumed dev + cie/spectral or dev/cie + spectral\n");
+ break;
+ case 4: /* Device, ci and spectral */
+ if (fa >= argc || argv[fa][0] == '-') usage("Bad dev filename");
+ strcpy(devname,argv[fa++]);
+ if (fa >= argc || argv[fa][0] == '-') usage("Bad cie filename");
+ strcpy(ciename,argv[fa++]);
+ if (fa >= argc || argv[fa][0] == '-') usage("Bad spec filename");
+ strcpy(specname,argv[fa++]);
+ if (fa >= argc || argv[fa][0] == '-') usage("Bad output filename");
+ strcpy(outname, argv[fa++]);
+ if (verb) printf("Three source files, assumed dev + cie + spectral\n");
+ break;
+ default:
+ usage("Wrong number of filenames");
+ }
+
+ if (out2) {
+ strcpy (outname2, outname);
+ strcat(outname2,".ti2");
+ }
+
+ /* Convert basename into .ti3 */
+ strcat(outname,".ti3");
+
+ /* Open up the Input CMYK/RGB reference file (might be same as ncie/spec) */
+ cmy = new_cgats(); /* Create a CGATS structure */
+ cmy->add_other(cmy, "LGOROWLENGTH"); /* Gretag/Logo Target file */
+ cmy->add_other(cmy, "Date:"); /* Gretag/Logo Target file */
+ cmy->add_other(cmy, "ECI2002"); /* Gretag/Logo Target file */
+ cmy->add_other(cmy, ""); /* Wildcard */
+ if (cmy->read_name(cmy, devname))
+ error ("Read: Can't read dev file '%s'. Unknown format or corrupted file ?",devname);
+ if (cmy->ntables != 1)
+ warning("Input file '%s' doesn't contain exactly one table",devname);
+
+ if ((npat = cmy->t[0].nsets) <= 0)
+ error("No patches");
+
+ if ((f_id1 = cmy->find_field(cmy, 0, "SampleName")) < 0
+ && (f_id1 = cmy->find_field(cmy, 0, "Sample_Name")) < 0
+ && (f_id1 = cmy->find_field(cmy, 0, "SAMPLE_NAME")) < 0
+ && (f_id1 = cmy->find_field(cmy, 0, "SAMPLE_ID")) < 0)
+ error("Input file '%s' doesn't contain field SampleName, Sample_Name, SAMPLE_NAME or SAMPLE_ID",devname);
+ if (cmy->t[0].ftype[f_id1] != nqcs_t
+ && cmy->t[0].ftype[f_id1] != cs_t
+ && cmy->t[0].ftype[f_id1] != i_t)
+ error("Field SampleName (%s) from CMYK/RGB file '%s' is wrong type",cmy->t[0].fsym[f_id1],devname);
+
+ if (cmy->find_field(cmy, 0, "RGB_R") >= 0) {
+ ndchan = 3;
+ if (verb) {
+ if (inp || disp)
+ printf("Seems to be an RGB device\n");
+ else
+ printf("Seems to be a psuedo-RGB device\n");
+ }
+ } else if (cmy->find_field(cmy, 0, "CMYK_C") >= 0) {
+ ndchan = 4;
+ if (verb)
+ printf("Seems to be a CMYK device\n");
+ } else {
+ printf("No device values found - hope that's OK!\n");
+ }
+
+ if (ndchan == 3) {
+ if ((f_c = cmy->find_field(cmy, 0, "RGB_R")) < 0) {
+ error("Input file '%s' doesn't contain field RGB_R",devname);
+ }
+ if (cmy->t[0].ftype[f_c] != r_t)
+ error("Field RGB_R from file '%s' is wrong type",devname);
+
+ if ((f_m = cmy->find_field(cmy, 0, "RGB_G")) < 0)
+ error("Input file '%s' doesn't contain field RGB_G",devname);
+ if (cmy->t[0].ftype[f_m] != r_t)
+ error("Field RGB_G from file '%s' is wrong type",devname);
+
+ if ((f_y = cmy->find_field(cmy, 0, "RGB_B")) < 0)
+ error("Input file '%s' doesn't contain field RGB_B",devname);
+ if (cmy->t[0].ftype[f_y] != r_t)
+ error("Field RGB_B from file '%s' is wrong type",devname);
+
+ } else if (ndchan == 4) {
+ if ((f_c = cmy->find_field(cmy, 0, "CMYK_C")) < 0) {
+ error("Input file '%s' doesn't contain field CMYK_C",devname);
+ }
+ if (cmy->t[0].ftype[f_c] != r_t)
+ error("Field CMYK_C from file '%s' is wrong type",devname);
+
+ if ((f_m = cmy->find_field(cmy, 0, "CMYK_M")) < 0)
+ error("Input file '%s' doesn't contain field CMYK_M",devname);
+ if (cmy->t[0].ftype[f_m] != r_t)
+ error("Field CMYK_M from file '%s' is wrong type",devname);
+
+ if ((f_y = cmy->find_field(cmy, 0, "CMYK_Y")) < 0)
+ error("Input file '%s' doesn't contain field CMYK_Y",devname);
+ if (cmy->t[0].ftype[f_y] != r_t)
+ error("Field CMYK_Y from file '%s' is wrong type",devname);
+
+ if ((f_k = cmy->find_field(cmy, 0, "CMYK_K")) < 0)
+ error("Input file '%s' doesn't contain field CMYK_Y",devname);
+ if (cmy->t[0].ftype[f_k] != r_t)
+ error("Field CMYK_K from file '%s' is wrong type",devname);
+ }
+ if (verb && ndchan > 0) printf("Read device values\n");
+
+ if (cmy->find_field(cmy, 0, "XYZ_X") >= 0
+ || cmy->find_field(cmy, 0, "LAB_L") >= 0) {
+ /* We've got a new combined device+cie file as the first one. */
+ /* Shuffle it into ciename , and ciename into specname */
+
+ strcpy(specname, ciename);
+ strcpy(ciename, devname);
+
+ if (verb) printf("We've got a combined device + instrument readings file\n");
+ }
+
+ /* Open up the input nCIE or Spectral device data file */
+ ncie = new_cgats(); /* Create a CGATS structure */
+ ncie->add_other(ncie, "LGOROWLENGTH"); /* Gretag/Logo Target file */
+ ncie->add_other(ncie, "ECI2002"); /* Gretag/Logo Target file */
+ ncie->add_other(ncie, ""); /* Wildcard */
+ if (ncie->read_name(ncie, ciename))
+ error ("Read: Can't read cie file '%s'. Unknown format or corrupted file ?",ciename);
+ if (ncie->ntables != 1)
+ warning("Input file '%s' doesn't contain exactly one table",ciename);
+
+ if (npat != ncie->t[0].nsets)
+ error("Number of patches between '%s' and '%s' doesn't match",devname,ciename);
+
+ if ((f_id2 = ncie->find_field(ncie, 0, "SampleName")) < 0
+ && (f_id2 = ncie->find_field(ncie, 0, "Sample_Name")) < 0
+ && (f_id2 = ncie->find_field(ncie, 0, "SAMPLE_NAME")) < 0
+ && (f_id2 = ncie->find_field(ncie, 0, "SAMPLE_ID")) < 0)
+ error("Input file '%s' doesn't contain field SampleName, Sample_Name, SAMPLE_NAME or SAMPLE_ID",ciename);
+ if (ncie->t[0].ftype[f_id2] != nqcs_t
+ && ncie->t[0].ftype[f_id2] != cs_t
+ && cmy->t[0].ftype[f_id2] != i_t)
+ error("Field SampleName (%s) from cie file '%s' is wrong type",ncie->t[0].fsym[f_id2],ciename);
+
+ if (ncie->find_field(ncie, 0, "XYZ_X") < 0
+ && ncie->find_field(ncie, 0, "LAB_L") < 0) {
+
+ /* Not a cie file. See if it's a spectral file */
+ if (ncie->find_field(ncie, 0, "nm500") < 0
+ && ncie->find_field(ncie, 0, "NM_500") < 0
+ && ncie->find_field(ncie, 0, "SPECTRAL_NM_500") < 0
+ && ncie->find_field(ncie, 0, "R_500") < 0
+ && ncie->find_field(ncie, 0, "SPECTRAL_500") < 0)
+ error("Input file '%s' doesn't contain field XYZ_X or spectral",ciename); /* Nope */
+
+ /* We have a spectral file only. Fix things and drop through */
+ ncie->del(ncie);
+ ncie = NULL;
+ strcpy(specname, ciename);
+ ciename[0] = '\000';
+
+ } else { /* Continue dealing with cie value file */
+ char *fields[2][3] = {
+ { "XYZ_X", "XYZ_Y", "XYZ_Z" },
+ { "LAB_L", "LAB_A", "LAB_B" }
+ };
+
+ if (ncie->find_field(ncie, 0, "nm500") >= 0
+ || ncie->find_field(ncie, 0, "NM_500") < 0
+ || ncie->find_field(ncie, 0, "SPECTRAL_NM_500") >= 0
+ || ncie->find_field(ncie, 0, "R_500") >= 0
+ || ncie->find_field(ncie, 0, "SPECTRAL_500") >= 0) {
+ if (verb) printf("Found spectral values\n");
+ /* It's got spectral data too. Make sure we read it */
+ strcpy(specname, ciename);
+ }
+
+ if (ncie->find_field(ncie, 0, "LAB_L") >= 0)
+ islab = 1;
+
+ for (i = 0; i < 3; i++) {
+
+ if ((f_cie[i] = ncie->find_field(ncie, 0, fields[islab][i])) < 0)
+ error("Input file '%s' doesn't contain field XYZ_Y",fields[islab][i], ciename);
+
+ if (ncie->t[0].ftype[f_cie[i]] != r_t)
+ error("Field %s from file '%s' is wrong type",fields[islab][i], ciename);
+ }
+
+ if (verb) printf("Found CIE values\n");
+ }
+
+ /* Open up the input Spectral device data file */
+ if (specname[0] != '\000') {
+ char bufs[5][50];
+
+ spec = new_cgats(); /* Create a CGATS structure */
+ spec->add_other(spec, "LGOROWLENGTH"); /* Gretag/Logo Target file */
+ spec->add_other(spec, "ECI2002"); /* Gretag/Logo Target file */
+ spec->add_other(spec, ""); /* Wildcard */
+ if (spec->read_name(spec, specname))
+ error ("Read: Can't read spec file '%s'. Unknown format or corrupted file ?",specname);
+ if (spec->ntables != 1)
+ warning("Input file '%s' doesn't contain exactly one table",specname);
+
+ if (npat != spec->t[0].nsets)
+ error("Number of patches between '%s' and '%s' doesn't match",specname);
+
+ if ((f_id3 = spec->find_field(spec, 0, "SampleName")) < 0
+ && (f_id3 = spec->find_field(spec, 0, "Sample_Name")) < 0
+ && (f_id3 = spec->find_field(spec, 0, "SAMPLE_NAME")) < 0
+ && (f_id3 = spec->find_field(spec, 0, "SAMPLE_ID")) < 0)
+ error("Input file '%s' doesn't contain field SampleName, Sample_Name, SAMPLE_NAME or SAMPLE_ID",specname);
+ if (spec->t[0].ftype[f_id3] != nqcs_t
+ && spec->t[0].ftype[f_id3] != cs_t
+ && cmy->t[0].ftype[f_id3] != i_t)
+ error("Field SampleName (%s) from spec file '%s' is wrong type",spec->t[0].fsym[f_id3],specname);
+
+ /* Find the spectral readings nm range */
+ for (specmin = 500; specmin >= 300; specmin -= 10) {
+ sprintf(bufs[0],"nm%03d", specmin);
+ sprintf(bufs[1],"NM_%03d", specmin);
+ sprintf(bufs[2],"SPECTRAL_NM_%03d", specmin);
+ sprintf(bufs[3],"R_%03d", specmin);
+ sprintf(bufs[4],"SPECTRAL_%03d", specmin);
+
+ if (spec->find_field(spec, 0, bufs[0]) < 0
+ && spec->find_field(spec, 0, bufs[1]) < 0
+ && spec->find_field(spec, 0, bufs[2]) < 0
+ && spec->find_field(spec, 0, bufs[3]) < 0
+ && spec->find_field(spec, 0, bufs[4]) < 0) /* Not found */
+ break;
+ }
+ specmin += 10;
+ for (specmax = 500; specmax <= 900; specmax += 10) {
+ sprintf(bufs[0],"nm%03d", specmax);
+ sprintf(bufs[1],"NM_%03d", specmax);
+ sprintf(bufs[2],"SPECTRAL_NM_%03d", specmax);
+ sprintf(bufs[3],"R_%03d", specmax);
+ sprintf(bufs[4],"SPECTRAL_%03d", specmax);
+
+ if (spec->find_field(spec, 0, bufs[0]) < 0
+ && spec->find_field(spec, 0, bufs[1]) < 0
+ && spec->find_field(spec, 0, bufs[2]) < 0
+ && spec->find_field(spec, 0, bufs[3]) < 0
+ && spec->find_field(spec, 0, bufs[4]) < 0) /* Not found */
+ break;
+ }
+ specmax -= 10;
+
+ if (specmin > 420 || specmax < 680) { /* Not enough range to be useful */
+ spec->del(spec);
+ spec = NULL;
+ specname[0] = '\000';
+ } else {
+
+ specnum = (specmax - specmin)/10 + 1;
+
+ if (verb)
+ printf("Found there are %d spectral values, from %d to %d nm\n",specnum,specmin,specmax);
+
+
+ /* Locate the fields for spectral values */
+ for (j = 0; j < specnum; j++) {
+ sprintf(bufs[0],"nm%03d", specmin + 10 * j);
+ sprintf(bufs[1],"NM_%03d", specmin + 10 * j);
+ sprintf(bufs[2],"SPECTRAL_NM_%03d", specmin + 10 * j);
+ sprintf(bufs[3],"R_%03d", specmin + 10 * j);
+ sprintf(bufs[4],"SPECTRAL_%03d", specmin + 10 * j);
+
+ if ((spi[j] = spec->find_field(spec, 0, bufs[0])) < 0
+ && (spi[j] = spec->find_field(spec, 0, bufs[1])) < 0
+ && (spi[j] = spec->find_field(spec, 0, bufs[2])) < 0
+ && (spi[j] = spec->find_field(spec, 0, bufs[3])) < 0
+ && (spi[j] = spec->find_field(spec, 0, bufs[4])) < 0) { /* Not found */
+
+ spec->del(spec);
+ spec = NULL;
+ specname[0] = '\000';
+ error("Failed to find spectral band %d nm in file '%s'\n",specmin + 10 * j,specname);
+ } else {
+ if (spec->t[0].ftype[spi[j]] != r_t)
+ error("Field '%s' from file '%s' is wrong type",spec->t[0].fsym[spi[j]], specname);
+ }
+
+ }
+ }
+ }
+
+ if (ciename[0] == '\000' && specname[0] == '\000')
+ error("Input file doesn't contain either CIE or spectral data");
+
+ /* Setup output cgats file */
+ ocg = new_cgats(); /* Create a CGATS structure */
+ ocg->add_other(ocg, "CTI3"); /* our special type is Calibration Target Information 3 */
+ ocg->add_table(ocg, tt_other, 0); /* Start the first table */
+
+ ocg->add_kword(ocg, 0, "DESCRIPTOR", "Argyll Calibration Target chart information 3",NULL);
+ ocg->add_kword(ocg, 0, "ORIGINATOR", "Argyll target", NULL);
+ atm[strlen(atm)-1] = '\000'; /* Remove \n from end */
+ ocg->add_kword(ocg, 0, "CREATED",atm, NULL);
+ if (disp)
+ ocg->add_kword(ocg, 0, "DEVICE_CLASS","DISPLAY", NULL); /* What sort of device this is */
+ else if (inp)
+ ocg->add_kword(ocg, 0, "DEVICE_CLASS","INPUT", NULL); /* What sort of device this is */
+ else
+ ocg->add_kword(ocg, 0, "DEVICE_CLASS","OUTPUT", NULL); /* What sort of device this is */
+
+ /* Note what instrument the chart was read with */
+ /* Assume this - could try reading from file INSTRUMENTATION "SpectroScan" ?? */
+ ocg->add_kword(ocg, 0, "TARGET_INSTRUMENT", inst_name(instSpectrolino) , NULL);
+
+ /* Fields we want */
+ ocg->add_field(ocg, 0, "SAMPLE_ID", nqcs_t);
+ if (f_id1 >= 0)
+ ocg->add_field(ocg, 0, "SAMPLE_NAME", cs_t);
+
+ if (ndchan == 3) {
+ ocg->add_field(ocg, 0, "RGB_R", r_t);
+ ocg->add_field(ocg, 0, "RGB_G", r_t);
+ ocg->add_field(ocg, 0, "RGB_B", r_t);
+ if (inp) {
+ if (islab)
+ ocg->add_kword(ocg, 0, "COLOR_REP","LAB_RGB", NULL);
+ else
+ ocg->add_kword(ocg, 0, "COLOR_REP","XYZ_RGB", NULL);
+ } else if (disp) {
+ if (islab)
+ ocg->add_kword(ocg, 0, "COLOR_REP","RGB_LAB", NULL);
+ else
+ ocg->add_kword(ocg, 0, "COLOR_REP","RGB_XYZ", NULL);
+ } else {
+ if (islab)
+ ocg->add_kword(ocg, 0, "COLOR_REP","iRGB_LAB", NULL);
+ else
+ ocg->add_kword(ocg, 0, "COLOR_REP","iRGB_XYZ", NULL);
+ }
+ } else if (ndchan == 4) {
+ ocg->add_field(ocg, 0, "CMYK_C", r_t);
+ ocg->add_field(ocg, 0, "CMYK_M", r_t);
+ ocg->add_field(ocg, 0, "CMYK_Y", r_t);
+ ocg->add_field(ocg, 0, "CMYK_K", r_t);
+ if (inp) { /* Does this make any sense ? */
+ if (islab)
+ ocg->add_kword(ocg, 0, "COLOR_REP","LAB_CMYK", NULL);
+ else
+ ocg->add_kword(ocg, 0, "COLOR_REP","XYZ_CMYK", NULL);
+ } else {
+ if (islab)
+ ocg->add_kword(ocg, 0, "COLOR_REP","CMYK_LAB", NULL);
+ else
+ ocg->add_kword(ocg, 0, "COLOR_REP","CMYK_XYZ", NULL);
+ }
+ }
+
+ if (ncie != NULL) {
+ if (islab) {
+ ocg->add_field(ocg, 0, "LAB_L", r_t);
+ ocg->add_field(ocg, 0, "LAB_A", r_t);
+ ocg->add_field(ocg, 0, "LAB_B", r_t);
+ } else {
+ ocg->add_field(ocg, 0, "XYZ_X", r_t);
+ ocg->add_field(ocg, 0, "XYZ_Y", r_t);
+ ocg->add_field(ocg, 0, "XYZ_Z", r_t);
+ }
+ }
+
+ /* Guess the device data scaling */
+ if (ndchan > 0) {
+ double maxv = 0.0;
+ int f_dev[4] = { f_c, f_m, f_y, f_k };
+
+ /* Guess what scale the spectral data is set to */
+ for (i = 0; i < npat; i++) {
+ for (j = 0; j < ndchan; j++) {
+ double vv;
+ vv = *((double *)cmy->t[0].fdata[i][f_dev[j]]);
+ if (vv > maxv)
+ maxv = vv;
+ }
+ }
+ if (maxv < 10.0) {
+ dev_scale = 100.0/1.0;
+ if (verb) printf("Device max found = %f, scale by 100.0\n",maxv);
+ } else if (maxv > 160.0) {
+ dev_scale = 100.0/255.0;
+ if (verb) printf("Device max found = %f, scale by 100/255\n",maxv);
+ } else {
+ dev_scale = 100.0/100.0;
+ if (verb) printf("Device max found = %f, scale by 1.0\n",maxv);
+ }
+ }
+
+ if (spec != NULL) {
+ char buf[100];
+ double maxv = 0.0;
+ sprintf(buf,"%d", specnum);
+ ocg->add_kword(ocg, 0, "SPECTRAL_BANDS",buf, NULL);
+ sprintf(buf,"%d", specmin);
+ ocg->add_kword(ocg, 0, "SPECTRAL_START_NM",buf, NULL);
+ sprintf(buf,"%d", specmax);
+ ocg->add_kword(ocg, 0, "SPECTRAL_END_NM",buf, NULL);
+
+ /* Generate fields for spectral values */
+ for (j = 0; j < specnum; j++) {
+ sprintf(buf,"SPEC_%03d", specmin + 10 * j);
+ ocg->add_field(ocg, 0, buf, r_t);
+ }
+
+ /* Guess what scale the spectral data is set to */
+ for (i = 0; i < npat; i++) {
+ for (j = 0; j < specnum; j++) {
+ double vv;
+ vv = *((double *)spec->t[0].fdata[i][spi[j]]);
+ if (vv > maxv)
+ maxv = vv;
+ }
+ }
+ if (maxv < 10.0) {
+ spec_scale = 100.0/1.0;
+ if (verb) printf("Spectral max found = %f, scale by 100.0\n",maxv);
+ } else if (maxv > 160.0) {
+ spec_scale = 100.0/255.0;
+ if (verb) printf("Spectral max found = %f, scale by 100/255\n",maxv);
+ } else {
+ spec_scale = 100.0/100.0;
+ if (verb) printf("Spectral max found = %f, scale by 1.0\n",maxv);
+ }
+ }
+
+ /* Write out the patch info to the output CGATS file */
+ {
+ cgats_set_elem *setel; /* Array of set value elements */
+
+ if ((setel = (cgats_set_elem *)malloc(
+ sizeof(cgats_set_elem) * ocg->t[0].nfields)) == NULL)
+ error("Malloc failed!");
+
+ /* Write out the patch info to the output CGATS file */
+ for (i = 0; i < npat; i++) {
+ char id[100];
+ int k = 0;
+
+ if (ncie != NULL) {
+ if (strcmp(((char *)cmy->t[0].rfdata[i][f_id1]),
+ ((char *)ncie->t[0].rfdata[i][f_id2])) != 0) {
+ error("Patch label mismatch to CIE values, patch %d, '%s' != '%s'\n",
+ i, ((char *)cmy->t[0].rfdata[i][f_id1]),
+ ((char *)ncie->t[0].rfdata[i][f_id2]));
+ }
+ }
+
+ if (spec != NULL) {
+ if (strcmp(((char *)cmy->t[0].rfdata[i][f_id1]),
+ ((char *)spec->t[0].rfdata[i][f_id3])) != 0) {
+ error("Patch label mismatch to spectral values, patch %d, '%s' != '%s'\n",
+ i, ((char *)cmy->t[0].rfdata[i][f_id1]),
+ ((char *)spec->t[0].rfdata[i][f_id3]));
+ }
+ }
+
+ /* SAMPLE ID */
+ sprintf(id, "%d", i+1);
+ setel[k++].c = id;
+
+ /* SAMPLE NAME */
+ if (f_id1 >= 0)
+ setel[k++].c = (char *)cmy->t[0].rfdata[i][f_id1];
+
+ if (ndchan == 3) {
+ setel[k++].d = dev_scale * *((double *)cmy->t[0].fdata[i][f_c]);
+ setel[k++].d = dev_scale * *((double *)cmy->t[0].fdata[i][f_m]);
+ setel[k++].d = dev_scale * *((double *)cmy->t[0].fdata[i][f_y]);
+ } else if (ndchan == 4){
+ double sum = 0.0;
+ sum += setel[k++].d = dev_scale * *((double *)cmy->t[0].fdata[i][f_c]);
+ sum += setel[k++].d = dev_scale * *((double *)cmy->t[0].fdata[i][f_m]);
+ sum += setel[k++].d = dev_scale * *((double *)cmy->t[0].fdata[i][f_y]);
+ sum += setel[k++].d = dev_scale * *((double *)cmy->t[0].fdata[i][f_k]);
+ if (sum > mxsum) {
+ mxsum = sum;
+ mxsumix = i;
+ }
+ }
+
+ if (ncie != NULL) {
+ setel[k++].d = *((double *)ncie->t[0].fdata[i][f_cie[0]]);
+ setel[k++].d = *((double *)ncie->t[0].fdata[i][f_cie[1]]);
+ setel[k++].d = *((double *)ncie->t[0].fdata[i][f_cie[2]]);
+ }
+
+ if (spec) {
+ for (j = 0; j < specnum; j++) {
+ setel[k++].d = spec_scale * *((double *)spec->t[0].fdata[i][spi[j]]);
+ }
+ }
+
+ ocg->add_setarr(ocg, 0, setel);
+ }
+
+ free(setel);
+ }
+
+ if (tlimit < 0 && mxsum > 0.0) {
+ if (verb)
+ printf("No ink limit given, using maximum %f found in file at %d\n",mxsum,mxsumix+1);
+
+ tlimit = (int)(mxsum + 0.5);
+ }
+
+ if (tlimit > 0) {
+ char buf[100];
+ sprintf(buf, "%d", tlimit);
+ ocg->add_kword(ocg, 0, "TOTAL_INK_LIMIT", buf, NULL);
+ }
+
+ if (ocg->write_name(ocg, outname))
+ error("Write error : %s",ocg->err);
+
+ /* Create a dummy .ti2 file (used with scanin -r) */
+ if (out2) {
+
+ /* Setup output cgats file */
+ ocg2 = new_cgats(); /* Create a CGATS structure */
+ ocg2->add_other(ocg2, "CTI2"); /* our special type is Calibration Target Information 2 */
+ ocg2->add_table(ocg2, tt_other, 0); /* Start the first table */
+
+ ocg2->add_kword(ocg2, 0, "DESCRIPTOR", "Argyll Calibration Target chart information 2",NULL);
+ ocg2->add_kword(ocg2, 0, "ORIGINATOR", "Argyll txt2ti3", NULL);
+ atm[strlen(atm)-1] = '\000'; /* Remove \n from end */
+ ocg2->add_kword(ocg2, 0, "CREATED",atm, NULL);
+ if (disp)
+ ocg2->add_kword(ocg2, 0, "DEVICE_CLASS","DISPLAY", NULL); /* What sort of device this is */
+ else
+ ocg2->add_kword(ocg2, 0, "DEVICE_CLASS","OUTPUT", NULL); /* What sort of device this is */
+ /* Note what instrument the chart was read with */
+ /* Assume this - could try reading from file INSTRUMENTATION "SpectroScan" ?? */
+ ocg2->add_kword(ocg2, 0, "TARGET_INSTRUMENT", inst_name(instSpectrolino) , NULL);
+
+ /* Fields we want */
+ ocg2->add_field(ocg2, 0, "SAMPLE_ID", nqcs_t);
+ ocg2->add_field(ocg2, 0, "SAMPLE_LOC", nqcs_t);
+
+ /* We're missing lots of .ti2 stuff like: */
+ /* ocg->add_kword(ocg, 0, "APPROX_WHITE_POINT",icg->t[0].kdata[fi], NULL); */
+ /* ocg->add_kword(ocg, 0, "PATCH_LENGTH", buf, NULL); */
+ /* ocg->add_kword(ocg, 0, "GAP_LENGTH", buf, NULL); */
+ /* ocg->add_kword(ocg, 0, "TRAILER_LENGTH", buf, NULL); */
+ /* ocg->add_kword(ocg, 0, "STEPS_IN_PASS", buf, NULL); */
+ /* ocg->add_kword(ocg, 0, "PASSES_IN_STRIPS", pis, NULL); */
+ /* ocg->add_kword(ocg, 0, "STRIP_INDEX_PATTERN", sixpat, NULL); */
+ /* ocg->add_kword(ocg, 0, "PATCH_INDEX_PATTERN", pixpat, NULL); */
+ /* ocg->add_kword(ocg, 0, "INDEX_ORDER", ixord ? "PATCH_THEN_STRIP" : "STRIP_THEN_PATCH", NULL); */
+
+ if (tlimit > 0) {
+ char buf[100];
+ sprintf(buf, "%d", tlimit);
+ ocg2->add_kword(ocg2, 0, "TOTAL_INK_LIMIT", buf, NULL);
+ }
+
+ if (ndchan == 3) {
+ ocg2->add_field(ocg2, 0, "RGB_R", r_t);
+ ocg2->add_field(ocg2, 0, "RGB_G", r_t);
+ ocg2->add_field(ocg2, 0, "RGB_B", r_t);
+ if (inp || disp) {
+ ocg2->add_kword(ocg2, 0, "COLOR_REP","RGB", NULL);
+ } else {
+ ocg2->add_kword(ocg2, 0, "COLOR_REP","iRGB", NULL);
+ }
+ } else if (ndchan == 4) {
+ ocg2->add_field(ocg2, 0, "CMYK_C", r_t);
+ ocg2->add_field(ocg2, 0, "CMYK_M", r_t);
+ ocg2->add_field(ocg2, 0, "CMYK_Y", r_t);
+ ocg2->add_field(ocg2, 0, "CMYK_K", r_t);
+ ocg2->add_kword(ocg2, 0, "COLOR_REP","CMYK", NULL);
+ }
+
+ if (ncie != NULL) {
+ if (islab) {
+ ocg2->add_field(ocg2, 0, "LAB_L", r_t);
+ ocg2->add_field(ocg2, 0, "LAB_A", r_t);
+ ocg2->add_field(ocg2, 0, "LAB_B", r_t);
+ } else {
+ ocg2->add_field(ocg2, 0, "XYZ_X", r_t);
+ ocg2->add_field(ocg2, 0, "XYZ_Y", r_t);
+ ocg2->add_field(ocg2, 0, "XYZ_Z", r_t);
+ }
+ }
+
+ /* Write out the patch info to the output CGATS file */
+ {
+ cgats_set_elem *setel; /* Array of set value elements */
+
+ if ((setel = (cgats_set_elem *)malloc(
+ sizeof(cgats_set_elem) * (2 + (ndchan) + (ncie != NULL ? 3 : 0)))) == NULL)
+ error("Malloc failed!");
+
+ /* Write out the patch info to the output CGATS file */
+ for (i = 0; i < npat; i++) {
+ char id[100];
+ int k = 0;
+
+ if (ncie != NULL) {
+ if (strcmp(((char *)cmy->t[0].rfdata[i][f_id1]),
+ ((char *)ncie->t[0].rfdata[i][f_id2])) != 0) {
+ error("Patch label mismatch to CIE values, patch %d, '%s' != '%s'\n",
+ i, ((char *)cmy->t[0].rfdata[i][f_id1]),
+ ((char *)ncie->t[0].rfdata[i][f_id2]));
+ }
+ }
+
+ if (spec != NULL) {
+ if (strcmp(((char *)cmy->t[0].rfdata[i][f_id1]),
+ ((char *)spec->t[0].rfdata[i][f_id3])) != 0) {
+ error("Patch label mismatch to spectral values, patch %d, '%s' != '%s'\n",
+ i, ((char *)cmy->t[0].rfdata[i][f_id1]),
+ ((char *)spec->t[0].rfdata[i][f_id3]));
+ }
+ }
+
+ sprintf(id, "%d", i+1);
+ setel[k++].c = id; /* ID */
+ setel[k++].c = ((char *)cmy->t[0].rfdata[i][f_id1]); /* Location */
+
+ if (ndchan == 3) {
+ setel[k++].d = 100.0/255.0 * *((double *)cmy->t[0].fdata[i][f_c]);
+ setel[k++].d = 100.0/255.0 * *((double *)cmy->t[0].fdata[i][f_m]);
+ setel[k++].d = 100.0/255.0 * *((double *)cmy->t[0].fdata[i][f_y]);
+ } else if (ndchan == 4) {
+ setel[k++].d = *((double *)cmy->t[0].fdata[i][f_c]);
+ setel[k++].d = *((double *)cmy->t[0].fdata[i][f_m]);
+ setel[k++].d = *((double *)cmy->t[0].fdata[i][f_y]);
+ setel[k++].d = *((double *)cmy->t[0].fdata[i][f_k]);
+ }
+
+ if (ncie != NULL) {
+ setel[k++].d = *((double *)ncie->t[0].fdata[i][f_cie[0]]);
+ setel[k++].d = *((double *)ncie->t[0].fdata[i][f_cie[1]]);
+ setel[k++].d = *((double *)ncie->t[0].fdata[i][f_cie[2]]);
+ }
+ ocg2->add_setarr(ocg2, 0, setel);
+ }
+
+ free(setel);
+ }
+
+ if (ocg2->write_name(ocg2, outname2))
+ error("Write error : %s",ocg2->err);
+
+ }
+
+ /* Clean up */
+ cmy->del(cmy);
+ if (ncie != NULL)
+ ncie->del(ncie);
+ if (spec != NULL)
+ spec->del(spec);
+ ocg->del(ocg);
+
+ return 0;
+}
+
+
+
diff --git a/profile/verify.c b/profile/verify.c
new file mode 100644
index 0000000..849d7fc
--- /dev/null
+++ b/profile/verify.c
@@ -0,0 +1,783 @@
+/*
+ * Argyll Color Correction System
+ * Verify two sets of PCS values.
+ *
+ * Author: Graeme W. Gill
+ * Date: 7/6/2005
+ *
+ * Copyright 2005 Graeme W. Gill
+ * All rights reserved.
+ *
+ * This material is licenced under the GNU AFFERO GENERAL PUBLIC LICENSE Version 3 :-
+ * see the License.txt file for licencing details.
+ */
+
+/*
+ * This program takes in two CGATS files (probably but not necesserily .ti3 files) of PCS
+ * values (either XYZ, L*a*b* or spectral), matches the values, and computes
+ * overall errors. This is useful for verifying proofing systems.
+ */
+
+/*
+ * TTBD:
+ */
+
+#undef DEBUG
+
+#define verbo stdout
+
+#include <stdio.h>
+#include <string.h>
+#if defined(__IBMC__)
+#include <float.h>
+#endif
+#include "copyright.h"
+#include "aconfig.h"
+#include "numlib.h"
+#include "vrml.h"
+#include "cgats.h"
+#include "xicc.h"
+#include "ccmx.h"
+#include "insttypes.h"
+#include "sort.h"
+
+void
+usage(void) {
+ fprintf(stderr,"Verify CIE values, Version %s\n",ARGYLL_VERSION_STR);
+ fprintf(stderr,"Author: Graeme W. Gill, licensed under the AGPL Version 3\n");
+ fprintf(stderr,"usage: verify [-options] target.ti3 measured.ti3\n");
+ fprintf(stderr," -v Verbose - print each patch value\n");
+ fprintf(stderr," -n Normalise each files reading to white Y\n");
+ fprintf(stderr," -N Normalise each files reading to white XYZ\n");
+ fprintf(stderr," -D Use D50 100.0 as L*a*b* white reference\n");
+ fprintf(stderr," -c Show CIE94 delta E values\n");
+ fprintf(stderr," -k Show CIEDE2000 delta E values\n");
+ fprintf(stderr," -s Sort patch values by error\n");
+ fprintf(stderr," -w create VRML vector visualisation (measured.wrl)\n");
+ fprintf(stderr," -W create VRML marker visualisation (measured.wrl)\n");
+ fprintf(stderr," -x Use VRML axes\n");
+ fprintf(stderr," -f [illum] Use Fluorescent Whitening Agent compensation [opt. simulated inst. illum.:\n");
+ fprintf(stderr," M0, M1, M2, A, C, D50 (def.), D50M2, D65, F5, F8, F10 or file.sp]\n");
+ fprintf(stderr," -i illum Choose illuminant for computation of CIE XYZ from spectral data & FWA:\n");
+ fprintf(stderr," A, C, D50 (def.), D50M2, D65, F5, F8, F10 or file.sp\n");
+ fprintf(stderr," -o observ Choose CIE Observer for spectral data:\n");
+ fprintf(stderr," 1931_2 (def), 1964_10, S&B 1955_2, shaw, J&V 1978_2\n");
+ fprintf(stderr," -X file.ccmx Apply Colorimeter Correction Matrix to second file\n");
+ fprintf(stderr," target.ti3 Target (reference) PCS or spectral values.\n");
+ fprintf(stderr," measured.ti3 Measured (actual) PCS or spectral values\n");
+ exit(1);
+ }
+
+/* Patch value type */
+typedef struct {
+ char sid[50]; /* sample id */
+ double v[3]; /* Lab value */
+ double de; /* Delta E */
+} pval;
+
+int main(int argc, char *argv[])
+{
+ int fa,nfa; /* current argument we're looking at */
+ int verb = 0;
+ int norm = 0; /* 1 = norm to Y, 2 = norm to XYZ */
+ int usestdd50 = 0; /* Use standard D50 instead of scaled D50 as Lab reference */
+ int cie94 = 0;
+ int cie2k = 0;
+ int dovrml = 0;
+ int doaxes = 0;
+ int dosort = 0;
+ char ccmxname[MAXNAMEL+1] = "\000"; /* Colorimeter Correction Matrix name */
+ ccmx *cmx = NULL; /* Colorimeter Correction Matrix */
+
+ struct {
+ char name[MAXNAMEL+1]; /* Patch filename */
+ int isdisp; /* nz if display */
+ int isdnormed; /* Has display data been normalised to 100 ? */
+ int npat; /* Number of patches */
+ pval *pat; /* patch values */
+ } cg[2]; /* Target and current patch file information */
+
+ int *match; /* Array mapping first list indexes to corresponding second */
+ int *sort; /* Array of first list indexes in sorted order */
+ int fwacomp = 0; /* FWA compensation */
+ int spec = 0; /* Use spectral data flag */
+ icxIllumeType tillum = icxIT_none; /* Target/simulated instrument illuminant */
+ xspect cust_tillum, *tillump = NULL; /* Custom target/simulated illumination spectrum */
+ icxIllumeType illum = icxIT_D50; /* Spectral defaults */
+ xspect cust_illum; /* Custom illumination spectrum */
+ icxObserverType observ = icxOT_CIE_1931_2;
+
+ icmXYZNumber labw = icmD50; /* The Lab white reference */
+
+ char out_name[MAXNAMEL+4+1]; /* VRML name */
+ vrml *wrl = NULL;
+
+ int i, j, n;
+
+#if defined(__IBMC__)
+ _control87(EM_UNDERFLOW, EM_UNDERFLOW);
+ _control87(EM_OVERFLOW, EM_OVERFLOW);
+#endif
+
+ if (argc <= 1)
+ usage();
+
+ /* Process the arguments */
+ for(fa = 1;fa < argc;fa++) {
+
+ nfa = fa; /* skip to nfa if next argument is used */
+ if (argv[fa][0] == '-') { /* Look for any flags */
+ char *na = NULL; /* next argument after flag, null if none */
+
+ if (argv[fa][2] != '\000')
+ na = &argv[fa][2]; /* next is directly after flag */
+ else {
+ if ((fa+1) < argc) {
+ if (argv[fa+1][0] != '-') {
+ nfa = fa + 1;
+ na = argv[nfa]; /* next is seperate non-flag argument */
+ }
+ }
+ }
+
+ if (argv[fa][1] == '?')
+ usage();
+
+ else if (argv[fa][1] == 'v' || argv[fa][1] == 'V')
+ verb = 1;
+
+ /* normalize */
+ else if (argv[fa][1] == 'n' || argv[fa][1] == 'N') {
+ norm = 1;
+ if (argv[fa][1] == 'N')
+ norm = 2;
+ }
+
+ else if (argv[fa][1] == 'D')
+ usestdd50 = 1;
+
+ /* VRML */
+ else if (argv[fa][1] == 'w')
+ dovrml = 1;
+ else if (argv[fa][1] == 'W')
+ dovrml = 2;
+
+ /* Axes */
+ else if (argv[fa][1] == 'x')
+ doaxes = 1;
+
+ /* CIE94 delta E */
+ else if (argv[fa][1] == 'c' || argv[fa][1] == 'C') {
+ cie94 = 1;
+ cie2k = 0;
+ }
+
+ else if (argv[fa][1] == 'k' || argv[fa][1] == 'K') {
+ cie94 = 0;
+ cie2k = 1;
+ }
+
+ /* Sort */
+ else if (argv[fa][1] == 's' || argv[fa][1] == 'S')
+ dosort = 1;
+
+ /* FWA compensation */
+ else if (argv[fa][1] == 'f') {
+ fwacomp = 1;
+
+ if (na != NULL) { /* Argument is present - target/simulated instr. illum. */
+ fa = nfa;
+ if (strcmp(na, "A") == 0
+ || strcmp(na, "M0") == 0) {
+ spec = 1;
+ tillum = icxIT_A;
+ } else if (strcmp(na, "C") == 0) {
+ spec = 1;
+ tillum = icxIT_C;
+ } else if (strcmp(na, "D50") == 0
+ || strcmp(na, "M1") == 0) {
+ spec = 1;
+ tillum = icxIT_D50;
+ } else if (strcmp(na, "D50M2") == 0
+ || strcmp(na, "M2") == 0) {
+ spec = 1;
+ tillum = icxIT_D50M2;
+ } else if (strcmp(na, "D65") == 0) {
+ spec = 1;
+ tillum = icxIT_D65;
+ } else if (strcmp(na, "F5") == 0) {
+ spec = 1;
+ tillum = icxIT_F5;
+ } else if (strcmp(na, "F8") == 0) {
+ spec = 1;
+ tillum = icxIT_F8;
+ } else if (strcmp(na, "F10") == 0) {
+ spec = 1;
+ tillum = icxIT_F10;
+ } else { /* Assume it's a filename */
+ spec = 1;
+ tillum = icxIT_custom;
+ if (read_xspect(&cust_tillum, na) != 0)
+ usage();
+ }
+ }
+ }
+
+ /* Spectral to CIE Illuminant type */
+ else if (argv[fa][1] == 'i' || argv[fa][1] == 'I') {
+ fa = nfa;
+ if (na == NULL) usage();
+ if (strcmp(na, "A") == 0) {
+ spec = 1;
+ illum = icxIT_A;
+ } else if (strcmp(na, "C") == 0) {
+ spec = 1;
+ illum = icxIT_C;
+ } else if (strcmp(na, "D50") == 0) {
+ spec = 1;
+ illum = icxIT_D50;
+ } else if (strcmp(na, "D50M2") == 0) {
+ spec = 1;
+ illum = icxIT_D50M2;
+ } else if (strcmp(na, "D65") == 0) {
+ spec = 1;
+ illum = icxIT_D65;
+ } else if (strcmp(na, "F5") == 0) {
+ spec = 1;
+ illum = icxIT_F5;
+ } else if (strcmp(na, "F8") == 0) {
+ spec = 1;
+ illum = icxIT_F8;
+ } else if (strcmp(na, "F10") == 0) {
+ spec = 1;
+ illum = icxIT_F10;
+ } else { /* Assume it's a filename */
+ spec = 1;
+ illum = icxIT_custom;
+ if (read_xspect(&cust_illum, na) != 0)
+ usage();
+ }
+ }
+
+ /* Spectral Observer type */
+ else if (argv[fa][1] == 'o' || argv[fa][1] == 'O') {
+ fa = nfa;
+ if (na == NULL) usage();
+ if (strcmp(na, "1931_2") == 0) { /* Classic 2 degree */
+ spec = 1;
+ observ = icxOT_CIE_1931_2;
+ } else if (strcmp(na, "1964_10") == 0) { /* Classic 10 degree */
+ spec = 1;
+ observ = icxOT_CIE_1964_10;
+ } else if (strcmp(na, "1955_2") == 0) { /* Stiles and Burch 1955 2 degree */
+ spec = 1;
+ observ = icxOT_Stiles_Burch_2;
+ } else if (strcmp(na, "1978_2") == 0) { /* Judd and Voss 1978 2 degree */
+ spec = 1;
+ observ = icxOT_Judd_Voss_2;
+ } else if (strcmp(na, "shaw") == 0) { /* Shaw and Fairchilds 1997 2 degree */
+ spec = 1;
+ observ = icxOT_Shaw_Fairchild_2;
+ } else
+ usage();
+ }
+
+ /* Colorimeter Correction Matrix for second file */
+ else if (argv[fa][1] == 'X') {
+ fa = nfa;
+ if (na == NULL) usage();
+ strncpy(ccmxname,na,MAXNAMEL-1); ccmxname[MAXNAMEL-1] = '\000';
+
+ } else
+ usage();
+ } else
+ break;
+ }
+
+ /* Get the file name arguments */
+ if (fa >= argc || argv[fa][0] == '-') usage();
+ strncpy(cg[0].name,argv[fa++],MAXNAMEL); cg[0].name[MAXNAMEL] = '\000';
+
+ if (fa >= argc || argv[fa][0] == '-') usage();
+ strncpy(cg[1].name,argv[fa],MAXNAMEL); cg[1].name[MAXNAMEL] = '\000';
+
+ /* Create VRML name */
+ {
+ char *xl;
+ strcpy(out_name, cg[1].name);
+ if ((xl = strrchr(out_name, '.')) == NULL) /* Figure where extention is */
+ xl = out_name + strlen(out_name);
+ strcpy(xl,".wrl");
+ }
+
+ if (fwacomp && spec == 0)
+ error ("FWA compensation only works when viewer and/or illuminant selected");
+
+ /* Colorimeter Correction Matrix */
+ if (ccmxname[0] != '\000') {
+ if ((cmx = new_ccmx()) == NULL)
+ error("new_ccmx failed\n");
+ if (cmx->read_ccmx(cmx,ccmxname))
+ error("Reading Colorimeter Correction Matrix file '%s' failed with error %d:'%s'\n",
+ ccmxname, cmx->errc, cmx->err);
+ }
+
+ /* Open up each file in turn, target then measured, */
+ /* and read in the CIE values. */
+ for (n = 0; n < 2; n++) {
+ cgats *cgf = NULL; /* cgats file data */
+ int isLab = 0; /* 0 if file CIE is XYZ, 1 if is Lab */
+ int sidx; /* Sample ID index */
+ int xix, yix, zix;
+
+ /* Open CIE target values */
+ cgf = new_cgats(); /* Create a CGATS structure */
+ cgf->add_other(cgf, ""); /* Allow any signature file */
+
+ if (cgf->read_name(cgf, cg[n].name))
+ error("CGATS file '%s' read error : %s",cg[n].name,cgf->err);
+
+ if (cgf->ntables < 1)
+ error ("Input file '%s' doesn't contain at least one table",cg[n].name);
+
+ /* Check if the file is suitable */
+ if (!spec
+ && cgf->find_field(cgf, 0, "LAB_L") < 0
+ && cgf->find_field(cgf, 0, "XYZ_X") < 0) {
+
+ if (cgf->find_kword(cgf, 0, "SPECTRAL_BANDS") < 0)
+ error ("Neither CIE nor spectral data found in file '%s'",cg[n].name);
+
+ /* Switch to using spectral information */
+ if (verb)
+ printf("No CIE data found, switching to spectral with standard observer & D50 for file '%s'\n",cg[n].name);
+ spec = 1;
+ illum = icxIT_D50;
+ observ = icxOT_CIE_1931_2;
+ }
+ if (spec && cgf->find_kword(cgf, 0, "SPECTRAL_BANDS") < 0)
+ error ("No spectral data data found in file '%s' when spectral expected",cg[n].name);
+
+ if (!spec && cgf->find_field(cgf, 0, "LAB_L") >= 0)
+ isLab = 1;
+
+ cg[n].npat = cgf->t[0].nsets; /* Number of patches */
+
+ /* Figure out what sort of device it is */
+ {
+ int ti;
+
+ cg[n].isdisp = 0;
+
+ if ((ti = cgf->find_kword(cgf, 0, "DEVICE_CLASS")) < 0)
+ error ("Input file '%s' doesn't contain keyword DEVICE_CLASS",cg[n].name);
+
+ if (strcmp(cgf->t[0].kdata[ti],"DISPLAY") == 0) {
+ cg[n].isdisp = 1;
+ illum = icxIT_none; /* Displays are assumed to be self luminous */
+ /* ?? What if two files are different ?? */
+ }
+
+ /* See if the CIE data has been normalised to Y = 100 */
+ if ((ti = cgf->find_kword(cgf, 0, "NORMALIZED_TO_Y_100")) < 0
+ || strcmp(cgf->t[0].kdata[ti],"NO") == 0) {
+ cg[n].isdnormed = 0;
+ } else {
+ cg[n].isdnormed = 1;
+ }
+ }
+
+ /* Read all the target patches */
+ if (cg[n].npat <= 0)
+ error("No sets of data in file '%s'",cg[n].name);
+
+ if (verb && n == 0) {
+ fprintf(verbo,"No of test patches = %d\n",cg[n].npat);
+ }
+
+ /* Allocate arrays to hold test patch input and output values */
+ if ((cg[n].pat = (pval *)malloc(sizeof(pval) * cg[n].npat)) == NULL)
+ error("Malloc failed - pat[]");
+
+ /* Read in the CGATs fields */
+ if ((sidx = cgf->find_field(cgf, 0, "SAMPLE_ID")) < 0
+ && (sidx = cgf->find_field(cgf, 0, "SampleName")) < 0
+ && (sidx = cgf->find_field(cgf, 0, "Sample_Name")) < 0
+ && (sidx = cgf->find_field(cgf, 0, "SAMPLE_NAME")) < 0
+ && (sidx = cgf->find_field(cgf, 0, "SAMPLE_LOC")) < 0)
+ error("Input file '%s' doesn't contain field SAMPLE_ID, SampleName, Sample_Name, SAMPLE_NAME or SAMPLE_LOC",cg[n].name);
+ if (cgf->t[0].ftype[sidx] != nqcs_t
+ && cgf->t[0].ftype[sidx] != cs_t)
+ error("Sample ID/Name field isn't a quoted or non quoted character string");
+
+ if (spec == 0) { /* Using instrument tristimulous value */
+
+ if (isLab) { /* Expect Lab */
+ if ((xix = cgf->find_field(cgf, 0, "LAB_L")) < 0)
+ error("Input file '%s' doesn't contain field LAB_L",cg[n].name);
+ if (cgf->t[0].ftype[xix] != r_t)
+ error("Field LAB_L is wrong type");
+ if ((yix = cgf->find_field(cgf, 0, "LAB_A")) < 0)
+ error("Input file '%s' doesn't contain field LAB_A",cg[n].name);
+ if (cgf->t[0].ftype[yix] != r_t)
+ error("Field LAB_A is wrong type");
+ if ((zix = cgf->find_field(cgf, 0, "LAB_B")) < 0)
+ error("Input file '%s' doesn't contain field LAB_B",cg[n].name);
+ if (cgf->t[0].ftype[zix] != r_t)
+ error("Field LAB_B is wrong type");
+
+ } else { /* Expect XYZ */
+ if ((xix = cgf->find_field(cgf, 0, "XYZ_X")) < 0)
+ error("Input file '%s' doesn't contain field XYZ_X",cg[n].name);
+ if (cgf->t[0].ftype[xix] != r_t)
+ error("Field XYZ_X is wrong type");
+ if ((yix = cgf->find_field(cgf, 0, "XYZ_Y")) < 0)
+ error("Input file '%s' doesn't contain field XYZ_Y",cg[n].name);
+ if (cgf->t[0].ftype[yix] != r_t)
+ error("Field XYZ_Y is wrong type");
+ if ((zix = cgf->find_field(cgf, 0, "XYZ_Z")) < 0)
+ error("Input file '%s' doesn't contain field XYZ_Z",cg[n].name);
+ if (cgf->t[0].ftype[zix] != r_t)
+ error("Field XYZ_Z is wrong type");
+ }
+
+ for (i = 0; i < cg[n].npat; i++) {
+ strcpy(cg[n].pat[i].sid, (char *)cgf->t[0].fdata[i][sidx]);
+ cg[n].pat[i].v[0] = *((double *)cgf->t[0].fdata[i][xix]);
+ cg[n].pat[i].v[1] = *((double *)cgf->t[0].fdata[i][yix]);
+ cg[n].pat[i].v[2] = *((double *)cgf->t[0].fdata[i][zix]);
+
+ if (!isLab) { /* If XYZ */
+
+ /* If normalised to 100, scale back to 1.0 */
+ if (!cg[n].isdisp || !cg[n].isdnormed) {
+ cg[n].pat[i].v[0] /= 100.0; /* scale back to 1.0 */
+ cg[n].pat[i].v[1] /= 100.0;
+ cg[n].pat[i].v[2] /= 100.0;
+ }
+ } else { /* If Lab */
+ icmLab2XYZ(&icmD50, cg[n].pat[i].v, cg[n].pat[i].v);
+ }
+ /* Apply ccmx */
+ if (n == 1 && cmx != NULL) {
+ cmx->xform(cmx, cg[n].pat[i].v, cg[n].pat[i].v);
+ }
+ }
+
+ } else { /* Using spectral data */
+ int ii;
+ xspect sp;
+ char buf[100];
+ int spi[XSPECT_MAX_BANDS]; /* CGATS indexes for each wavelength */
+ xsp2cie *sp2cie; /* Spectral conversion object */
+
+ if ((ii = cgf->find_kword(cgf, 0, "SPECTRAL_BANDS")) < 0)
+ error ("Input file doesn't contain keyword SPECTRAL_BANDS");
+ sp.spec_n = atoi(cgf->t[0].kdata[ii]);
+ if ((ii = cgf->find_kword(cgf, 0, "SPECTRAL_START_NM")) < 0)
+ error ("Input file doesn't contain keyword SPECTRAL_START_NM");
+ sp.spec_wl_short = atof(cgf->t[0].kdata[ii]);
+ if ((ii = cgf->find_kword(cgf, 0, "SPECTRAL_END_NM")) < 0)
+ error ("Input file doesn't contain keyword SPECTRAL_END_NM");
+ sp.spec_wl_long = atof(cgf->t[0].kdata[ii]);
+ if (!cg[n].isdisp || cg[n].isdnormed != 0)
+ sp.norm = 100.0;
+ else
+ sp.norm = 1.0;
+
+ /* Find the fields for spectral values */
+ for (j = 0; j < sp.spec_n; j++) {
+ int nm;
+
+ /* Compute nearest integer wavelength */
+ nm = (int)(sp.spec_wl_short + ((double)j/(sp.spec_n-1.0))
+ * (sp.spec_wl_long - sp.spec_wl_short) + 0.5);
+
+ sprintf(buf,"SPEC_%03d",nm);
+
+ if ((spi[j] = cgf->find_field(cgf, 0, buf)) < 0)
+ error("Input file doesn't contain field %s",buf);
+ }
+
+ /* Create a spectral conversion object */
+ if ((sp2cie = new_xsp2cie(illum, illum == icxIT_none ? NULL : &cust_illum,
+ observ, NULL, icSigXYZData, icxClamp)) == NULL)
+ error("Creation of spectral conversion object failed");
+
+ if (fwacomp) {
+ int ti;
+ xspect mwsp; /* Medium spectrum */
+ instType itype; /* Spectral instrument type */
+ xspect insp; /* Instrument illuminant */
+
+ mwsp = sp; /* Struct copy */
+
+ if ((ti = cgf->find_kword(cgf, 0, "TARGET_INSTRUMENT")) < 0)
+ error ("Can't find target instrument in '%s' needed for FWA compensation",cg[n].name);
+
+ if ((itype = inst_enum(cgf->t[0].kdata[ti])) == instUnknown)
+ error ("Unrecognised target instrument '%s'", cgf->t[0].kdata[ti]);
+
+ if (inst_illuminant(&insp, itype) != 0)
+ error ("Instrument doesn't have an FWA illuminent");
+
+ /* Determine a media white spectral reflectance */
+ for (j = 0; j < mwsp.spec_n; j++)
+ mwsp.spec[j] = 0.0;
+
+ /* Since we don't want to assume that there are any associated device */
+ /* values present in each file, we can't use this as means of */
+ /* determining the media color. Use an alternative approach here, */
+ /* which may give slightly different results to profile. */
+
+ /* Track the maximum reflectance for any band to determine white. */
+ /* This might silently fail, if there isn't white in the sample set. */
+ for (i = 0; i < cg[0].npat; i++) {
+ for (j = 0; j < mwsp.spec_n; j++) {
+ double rv = *((double *)cgf->t[0].fdata[i][spi[j]]);
+ if (rv > mwsp.spec[j])
+ mwsp.spec[j] = rv;
+ }
+ }
+
+ /* If we are setting a specific simulated instrument illuminant */
+ if (tillum != icxIT_none) {
+ tillump = &cust_tillum;
+ if (tillum != icxIT_custom) {
+ if (standardIlluminant(tillump, tillum, 0.0)) {
+ error("simulated inst. illum. not recognised");
+ }
+ }
+ }
+
+ if (sp2cie->set_fwa(sp2cie, &insp, tillump, &mwsp))
+ error ("Set FWA on sp2cie failed");
+
+ if (verb) {
+ double FWAc;
+ sp2cie->get_fwa_info(sp2cie, &FWAc);
+ fprintf(verbo,"FWA content = %f\n",FWAc);
+ }
+ }
+
+ for (i = 0; i < cg[0].npat; i++) {
+
+ strcpy(cg[n].pat[i].sid, (char *)cgf->t[0].fdata[i][sidx]);
+
+ /* Read the spectral values for this patch */
+ for (j = 0; j < sp.spec_n; j++) {
+ sp.spec[j] = *((double *)cgf->t[0].fdata[i][spi[j]]);
+ }
+
+ /* Convert it to XYZ space */
+ sp2cie->convert(sp2cie, cg[n].pat[i].v, &sp);
+
+ /* Applu ccmx */
+ if (n == 1 && cmx != NULL) {
+ cmx->xform(cmx, cg[n].pat[i].v, cg[n].pat[i].v);
+ }
+ }
+
+ sp2cie->del(sp2cie); /* Done with this */
+
+ } /* End of reading in CGATs file */
+
+
+ /* Normalise this file to white = 1.0 or D50 */
+ if (norm) {
+ double bxyz[3] = { 0.0, -100.0, 0.0 };
+
+ /* Locate patch with biggest Y */
+ for (i = 0; i < cg[n].npat; i++) {
+ double xyz[3];
+ icmLab2XYZ(&icmD50, xyz, cg[n].pat[i].v);
+ if (cg[n].pat[i].v[1] > bxyz[1]) {
+ icmCpy3(bxyz, cg[n].pat[i].v);
+ }
+ }
+
+ /* Then normalize all the values */
+ for (i = 0; i < cg[n].npat; i++) {
+ if (norm == 1) {
+ cg[n].pat[i].v[0] /= bxyz[1];
+ cg[n].pat[i].v[1] /= bxyz[1];
+ cg[n].pat[i].v[2] /= bxyz[1];
+ } else {
+ cg[n].pat[i].v[0] *= icmD50.X/bxyz[0];
+ cg[n].pat[i].v[1] *= icmD50.Y/bxyz[1];
+ cg[n].pat[i].v[2] *= icmD50.Z/bxyz[2];
+ }
+ }
+ }
+ cgf->del(cgf); /* Clean up */
+ }
+ if (cmx != NULL)
+ cmx->del(cmx);
+ cmx = NULL;
+
+ /* Check that the number of test patches matches */
+ if (cg[0].npat != cg[1].npat)
+ error("Number of patches between '%s' and '%s' doesn't match",cg[0].name,cg[1].name);
+
+ /* Create a list to map the second list of patches to the first */
+ if ((match = (int *)malloc(sizeof(int) * cg[0].npat)) == NULL)
+ error("Malloc failed - match[]");
+ for (i = 0; i < cg[0].npat; i++) {
+ for (j = 0; j < cg[1].npat; j++) {
+ if (strcmp(cg[0].pat[i].sid, cg[1].pat[j].sid) == 0)
+ break; /* Found it */
+ }
+ if (j < cg[1].npat) {
+ match[i] = j;
+ } else {
+ error("Failed to find matching patch to '%s'",cg[0].pat[i].sid);
+ }
+ }
+
+ /* Adjust the reference white Y to be larger than the largest Y of the two files */
+ if (!usestdd50) {
+ double maxy = -1e6;
+
+ for (n = 0; n < 2; n++) {
+ for (i = 0; i < cg[n].npat; i++) {
+ if (cg[n].pat[i].v[1] > maxy)
+ maxy = cg[n].pat[i].v[1];
+ }
+ }
+ labw.X *= maxy/icmD50.Y; /* Scale white uniformly */
+ labw.Y *= maxy/icmD50.Y; /* Scale white uniformly */
+ labw.Z *= maxy/icmD50.Y;
+
+ if (verb)
+ printf("L*a*b* white reference = XYZ %f %f %f\n",labw.X,labw.Y,labw.Z);
+ }
+
+ /* Convert XYZ to Lab */
+ for (n = 0; n < 2; n++) {
+ for (i = 0; i < cg[n].npat; i++) {
+ icmXYZ2Lab(&labw, cg[n].pat[i].v, cg[n].pat[i].v);
+ }
+ }
+
+ /* Compute the delta E's */
+ for (i = 0; i < cg[0].npat; i++) {
+ if (cie2k)
+ cg[0].pat[i].de = icmCIE2K(cg[0].pat[i].v, cg[1].pat[match[i]].v);
+ else if (cie94)
+ cg[0].pat[i].de = icmCIE94(cg[0].pat[i].v, cg[1].pat[match[i]].v);
+ else
+ cg[0].pat[i].de = icmLabDE(cg[0].pat[i].v, cg[1].pat[match[i]].v);
+ }
+
+ /* Create sorted list, from worst to best. */
+ if ((sort = (int *)malloc(sizeof(int) * cg[0].npat)) == NULL)
+ error("Malloc failed - sort[]");
+ for (i = 0; i < cg[0].npat; i++)
+ sort[i] = i;
+
+#define HEAP_COMPARE(A,B) (cg[0].pat[A].de > cg[0].pat[B].de)
+ HEAPSORT(int, sort, cg[0].npat);
+#undef HEAP_COMPARE
+
+ /* - - - - - - - - - - */
+ /* Figure out the report */
+ {
+ double merr = 0.0, aerr = 0.0;
+ int n90;
+ double merr90 = 0.0, aerr90 = 0.0;
+ int n10;
+ double merr10 = 0.0, aerr10 = 0.0;
+ double rad;
+
+ if (dovrml) {
+ wrl = new_vrml(out_name, doaxes, 0);
+ wrl->start_line_set(wrl, 0);
+
+ /* Fudge sphere diameter */
+ rad = 10.0/pow(cg[0].npat, 1.0/3.0);
+ }
+
+ /* Do overall results */
+ for (i = 0; i < cg[0].npat; i++) {
+ double de;
+ if (dosort)
+ j = sort[i];
+ else
+ j = i;
+
+ de = cg[0].pat[j].de;
+ aerr += de;
+
+ if (verb) {
+ printf("%s: %f %f %f <=> %f %f %f de %f\n",
+ cg[0].pat[j].sid,
+ cg[0].pat[j].v[0], cg[0].pat[j].v[1], cg[0].pat[j].v[2],
+ cg[1].pat[match[j]].v[0], cg[1].pat[match[j]].v[1], cg[1].pat[match[j]].v[2],
+ de);
+ }
+
+ if (de > merr)
+ merr = de;
+
+ if (dovrml) {
+ if (de > 1e-6) {
+ wrl->add_vertex(wrl, 0, cg[0].pat[j].v);
+ wrl->add_vertex(wrl, 0, cg[1].pat[j].v);
+ }
+ if (dovrml == 2) {
+ wrl->add_marker(wrl, cg[0].pat[j].v, NULL, rad);
+ wrl->add_marker(wrl, cg[1].pat[j].v, NULL, rad);
+ }
+ }
+
+ }
+ if (cg[0].npat > 0)
+ aerr /= (double)cg[0].npat;
+
+ if (dovrml) {
+ wrl->make_lines(wrl, 0, 2);
+ wrl->del(wrl);
+ wrl = NULL;
+ }
+
+ /* Do best 90% */
+ n90 = (int)(cg[0].npat * 9.0/10.0 + 0.5);
+ for (i = (cg[0].npat-n90); i < cg[0].npat; i++) {
+ double de = cg[0].pat[sort[i]].de;
+ aerr90 += de;
+ if (de > merr90)
+ merr90 = de;
+ }
+ if (n90 > 0)
+ aerr90 /= (double)n90;
+
+ /* Do worst 10% */
+ n10 = (int)(cg[0].npat * 1.0/10.0 + 0.5);
+ for (i = 0; i < n10; i++) {
+ double de = cg[0].pat[sort[i]].de;
+ aerr10 += de;
+ if (de > merr10)
+ merr10 = de;
+ }
+ if (n10 > 0)
+ aerr10 /= (double)n10;
+
+ if (verb) {
+ fprintf(verbo,"No of test patches in worst 10%% are = %d\n",n10);
+ fprintf(verbo,"No of test patches in best 90%% are = %d\n",n90);
+ }
+ printf("Verify results:\n");
+ printf(" Total errors%s: peak = %f, avg = %f\n", cie2k ? " (CIEDE2000)" : cie94 ? " (CIE94)" : "", merr, aerr);
+ printf(" Worst 10%% errors%s: peak = %f, avg = %f\n", cie2k ? " (CIEDE2000)" : cie94 ? " (CIE94)" : "", merr10, aerr10);
+ printf(" Best 90%% errors%s: peak = %f, avg = %f\n", cie2k ? " (CIEDE2000)" : cie94 ? " (CIE94)" : "", merr90, aerr90);
+
+ free(sort);
+ free(match);
+ free(cg[0].pat);
+ free(cg[1].pat);
+ }
+
+ return 0;
+}
+
+